diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml new file mode 100644 index 0000000000..27ec1b2828 --- /dev/null +++ b/.github/workflows/coding-style.yml @@ -0,0 +1,17 @@ +name: Coding Style + +on: [push, pull_request] + +jobs: + nette_cc: + name: Nette Code Checker + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: shivammathur/setup-php@v2 + with: + php-version: 8.3 + coverage: none + + - run: composer create-project nette/code-checker temp/code-checker ^3 --no-progress + - run: php temp/code-checker/code-checker --no-progress diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 550b89053d..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -language: php - -script: - - php code-checker/src/code-checker.php - -before_script: - - travis_retry composer create-project nette/code-checker code-checker ~2.5 --no-interaction - -sudo: false - -cache: - directories: - - $HOME/.composer/cache diff --git a/ai/cs/@home.texy b/ai/cs/@home.texy new file mode 100644 index 0000000000..e2385cdf80 --- /dev/null +++ b/ai/cs/@home.texy @@ -0,0 +1,11 @@ +Nette AI +******** + +- [Introduction |guide] +- [Getting Started |getting-started] +- [MCP Inspector |mcp-inspector] +- [Claude Code |claude-code] +- [Tips & Best Practices |tips] + +{{maintitle: Nette AI – Vibe Coding with Nette Framework}} +{{description: Build Nette applications with AI assistance. MCP Inspector gives any AI tool deep knowledge of your application's DI container, database, routing, and errors. No hallucinations, just clean code.}} diff --git a/ai/cs/@meta.texy b/ai/cs/@meta.texy new file mode 100644 index 0000000000..e06cc9886c --- /dev/null +++ b/ai/cs/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette AI}} diff --git a/ai/en/@home.texy b/ai/en/@home.texy new file mode 100644 index 0000000000..e2385cdf80 --- /dev/null +++ b/ai/en/@home.texy @@ -0,0 +1,11 @@ +Nette AI +******** + +- [Introduction |guide] +- [Getting Started |getting-started] +- [MCP Inspector |mcp-inspector] +- [Claude Code |claude-code] +- [Tips & Best Practices |tips] + +{{maintitle: Nette AI – Vibe Coding with Nette Framework}} +{{description: Build Nette applications with AI assistance. MCP Inspector gives any AI tool deep knowledge of your application's DI container, database, routing, and errors. No hallucinations, just clean code.}} diff --git a/ai/en/@left-menu.texy b/ai/en/@left-menu.texy new file mode 100644 index 0000000000..54132f474a --- /dev/null +++ b/ai/en/@left-menu.texy @@ -0,0 +1,5 @@ +- [Introduction |guide] +- [Getting Started |getting-started] +- [MCP Inspector |mcp-inspector] +- [Claude Code |claude-code] +- [Tips & Best Practices |tips] diff --git a/ai/en/@meta.texy b/ai/en/@meta.texy new file mode 100644 index 0000000000..e06cc9886c --- /dev/null +++ b/ai/en/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette AI}} diff --git a/ai/en/claude-code.texy b/ai/en/claude-code.texy new file mode 100644 index 0000000000..8895eda6d7 --- /dev/null +++ b/ai/en/claude-code.texy @@ -0,0 +1,251 @@ +Claude Code Plugin +****************** + +
+ +The Nette plugin gives Claude deep knowledge of the framework. Instead of generic PHP advice, you get recommendations that follow Nette conventions – from presenters and forms to Latte templates and database queries. + +The plugin includes: +- **10 skills** covering all major areas of Nette development +- **Automatic validation** that catches errors in Latte and NEON files +- **MCP Inspector integration** for real-time application introspection + +
+ + +Installation +============ + +If you haven't installed Claude Code yet, see the [complete setup guide |getting-started]. Once Claude Code is running, install the Nette plugin: + +```shell +/plugin marketplace add nette/claude-code +/plugin install nette@nette +``` + + +How Skills Work +=============== + +You don't need to activate skills manually. They turn on automatically based on what you're talking about. + +- Ask about "presenter structure" → the `nette-architecture` skill activates +- Ask about "form validation" → the `nette-forms` skill activates +- Ask about "Latte filters" → the `latte-templates` skill activates + +This means you get relevant, context-aware help without having to think about which skill you need. + + +Available Skills +================ + +Here's what each skill covers: + + +nette-architecture +------------------ + +When you're designing your application structure, this skill guides you through: + +- **Directory organization** – Where to put presenters, services, entities, and components +- **Module design** – How to split your application into logical modules (Admin, Front, Api) +- **Presenter patterns** – When to use base presenters, how to handle authentication +- **Evolution strategy** – Start minimal, grow organically, refactor when needed + +The key principle: Don't over-engineer. Create subdirectories when you have 5+ related files, not before. + + +nette-configuration +------------------- + +Everything about the DI container and NEON configuration: + +- **Service registration** – How to define services in `services.neon` +- **Autowiring** – When it works automatically and when you need explicit configuration +- **Parameters** – How to use configuration parameters across your application +- **Extensions** – Working with DI extensions from Nette and third parties + + +nette-database +-------------- + +Covers both the raw SQL approach and the Database Explorer: + +- **Database Explorer** – Using `Selection` for queries, `ActiveRow` for entities +- **Entity conventions** – The `Row` suffix pattern, type hints with `@property-read` +- **Relationships** – Navigating foreign keys with colon notation +- **When to use what** – Explorer for CRUD, raw SQL for complex analytics + +Example: Claude knows that `->where('category.slug', $slug)` automatically joins the category table. + + +nette-forms +----------- + +Creating and handling forms the Nette way: + +- **Controls** – All built-in controls from text inputs to file uploads +- **Validation** – Built-in rules, custom validators, conditional validation +- **Rendering** – Manual rendering, Bootstrap integration, custom renderers +- **Patterns** – Create/edit forms, form components, AJAX submissions + + +nette-schema +------------ + +Data validation and normalization with the Schema component: + +- **Expect class** – Building validation schemas for arrays and objects +- **Configuration schemas** – Validating NEON configuration in DI extensions +- **Type coercion** – Automatic conversion of strings to integers, dates, etc. +- **Custom validators** – Adding your own validation rules + +Example: Claude knows how to create schemas like: + +```php +$schema = Expect::structure([ + 'name' => Expect::string()->required(), + 'age' => Expect::int()->min(0)->max(120), + 'email' => Expect::email(), + 'roles' => Expect::listOf('string')->default([]), +]); +``` + + +nette-testing +------------- + +Writing tests with Nette Tester: + +- **Test structure** – The `.phpt` format, `@testCase` annotation, file organization +- **Assertions** – All `Assert::*` methods: `same()`, `equal()`, `exception()`, `match()`, and more +- **Fixtures** – Setting up test data, mocking dependencies, database transactions +- **Running tests** – Command-line options, parallel execution, code coverage + +Example: Claude can generate proper test files: + +```php +/** @testCase */ +class UserServiceTest extends TestCase +{ + public function testCreateUser(): void + { + $service = new UserService($this->mockDatabase()); + $user = $service->create(['name' => 'John']); + Assert::same('John', $user->name); + } +} +``` + + +nette-utils +----------- + +The utility classes that make PHP development easier: + +- **Arrays** – `Nette\Utils\Arrays` with `get()`, `getRef()`, `map()`, `flatten()`, and more +- **Strings** – Unicode-safe operations: `webalize()`, `truncate()`, `contains()`, `startsWith()` +- **Finder** – File system traversal with filtering by name, size, date +- **Image** – Resize, crop, sharpen with automatic format detection +- **Json** – Safe JSON encoding/decoding with proper error handling +- **Validators** – Email, URL, numeric validation helpers +- **DateTime** – Immutable date/time with Czech locale support + + +frontend-development +-------------------- + +Integrating frontend tools with Nette: + +- **Vite** – Setting up Vite for modern JavaScript/TypeScript development, HMR configuration +- **Nette Assets** – Using the asset system for cache-busted URLs in production +- **Tailwind CSS** – Configuration for Tailwind with Latte templates, purging unused styles +- **ESLint & Prettier** – Code quality tools integration +- **Build scripts** – npm/package.json scripts for development and production builds + +Claude can help configure `vite.config.js` to work with Nette's directory structure and generate proper asset references in Latte templates. + + +latte-templates +--------------- + +Everything about the Latte templating engine: + +- **Syntax** – Tags, filters, blocks, and inheritance +- **Security** – Auto-escaping, content-aware output +- **Custom filters** – Creating and registering your own filters +- **Template classes** – Using typed templates for better IDE support + + +neon-format +----------- + +The NEON configuration format: + +- **Syntax** – Mappings, sequences, entities, and multiline strings +- **Common patterns** – Service definitions, parameter references +- **Debugging** – Finding and fixing syntax errors + + +Automatic Validation +==================== + +One of the most useful features is automatic validation. After every file edit, the plugin checks for errors: + +| What | How It Works | +|------|--------------| +| **Latte templates** | Runs `latte-lint` to check syntax after every `.latte` edit | +| **NEON files** | Validates NEON syntax after every `.neon` edit | +| **Tracy errors** | Watches the exception log and alerts Claude about new errors | + +If there's a syntax error in your Latte template, Claude knows about it immediately and can suggest a fix. No more discovering errors in the browser. + + +Plugin for Framework Contributors +================================= + +If you're contributing to Nette itself, there's an additional plugin with coding standards: + +```shell +/plugin install nette-dev@nette +``` + +| Skill | What It Covers | +|-------|----------------| +| `php-coding-standards` | Nette's PHP coding style – indentation, naming, structure | +| `php-doc` | PHPDoc conventions – when to document, what format to use | +| `commit-messages` | How to write commit messages for Nette repositories | + + +Automatic PHP Style Fixing +========================== + +For automatic code style fixing after every PHP file edit, install the optional php-fixer plugin: + +```shell +/plugin install php-fixer@nette +/install-php-fixer +``` + +The second command installs `nette/coding-standard` globally. After that, every PHP file you edit will be automatically formatted according to Nette coding standards. + + +MCP Inspector Integration +========================= + +The plugin works even better with [MCP Inspector |mcp-inspector] – a tool that lets Claude see your actual application state. With MCP Inspector, Claude can: + +- Query your real database schema instead of guessing +- List your registered DI services and their configuration +- Read Tracy error logs for debugging +- Match URLs to presenters using your actual routes + +Install it with a single command: + +```shell +/install-mcp-inspector +``` + +Then restart Claude Code. See the [MCP Inspector documentation |mcp-inspector] for all 20 available tools. + +{{composer: nette/claude-code}} diff --git a/ai/en/getting-started.texy b/ai/en/getting-started.texy new file mode 100644 index 0000000000..3cb35e0bea --- /dev/null +++ b/ai/en/getting-started.texy @@ -0,0 +1,260 @@ +Getting Started +*************** + +
+ +Ready to try [vibe coding |guide] with Nette? This guide walks you through the complete setup: + +- Choosing and installing an AI tool +- Setting up MCP Inspector so AI can see your application +- Making your first AI-assisted changes + +The whole process takes about 10 minutes. Let's get started! + +
+ + +Choosing Your AI Tool +===================== + +Nette AI tools work with any MCP-compatible AI assistant. We recommend **Claude Code** for the best experience – it has a dedicated Nette plugin with deep framework knowledge and automatic code validation. + +Other options include **Cursor**, **VS Code with Continue**, and other MCP-compatible tools. See [Other AI Tools |#other-ai-tools] at the end of this guide. + + +What You'll Need +================ + +Before we begin, make sure you have: + +- **A Nette project** – existing or new (`composer create-project nette/web-project`) +- **PHP 8.2+** – required for MCP Inspector +- **An AI tool** – we'll install Claude Code below (Claude Pro costs $20/month) + + +Installation on macOS and Linux +=============================== + +```shell +curl -fsSL https://claude.ai/install.sh | bash +``` + +On macOS, you can alternatively use Homebrew: + +```shell +brew install --cask claude-code +``` + +After installation, skip to [Starting Claude Code |#starting-claude-code]. + + +Installation on Windows via WSL +=============================== + +Claude Code requires a Unix environment. On Windows, use WSL: + +```shell +wsl --install +``` + +This installs Ubuntu. **Restart your computer** after installation. + +After restart, launch Ubuntu: + +```shell +ubuntu +``` + +First launch will ask for a username and password – you'll need it for `sudo` later. + +Installing Claude Code in WSL in the Ubuntu terminal: + +```shell +curl -fsSL https://claude.ai/install.sh | bash +``` + +Your Windows drives are mounted under `/mnt/`: +- `C:\Users\Jan\Projects` → `/mnt/c/Users/Jan/Projects` +- `D:\Work` → `/mnt/d/Work` + +From Windows, access Linux files via `\\wsl$\Ubuntu\home\username` in Explorer. + + +Starting Claude Code +==================== + +Great, you have Claude Code installed! Let's start it up. + +Navigate to your project directory: + +```shell +# Windows (WSL) +cd /mnt/c/Users/Jan/Projects/my-app + +# macOS/Linux +cd ~/projects/my-app +``` + +Start Claude Code: + +```shell +claude +``` + +The first time you run it, Claude Code will ask you to authenticate. It will open a browser window where you can log in to your Anthropic account. After successful authentication, you'll see the `claude>` prompt and you're ready to go. + +You can also use Claude Code "on the web":https://claude.ai/code or via the "desktop app":https://claude.com/download, but local installation provides the best experience with direct file access. + + +Adding the Nette Plugin +======================= + +Now let's give Claude deep knowledge of Nette. First, add the Nette marketplace and enable auto-updating: + +```shell +/plugin marketplace add nette/claude-code +``` + +Then install the plugin: + +```shell +/plugin install nette@nette +``` + +That's it! The plugin is now active. It includes 10 specialized skills that automatically activate based on what you're working on. When you ask about forms, it knows about Nette Forms. When you ask about templates, it knows about Latte. + + +Setting Up MCP Inspector +======================== + +The final piece is MCP Inspector, which lets Claude see your actual application – your services, database schema, routes, and error logs. + +The easiest way to install it is through Claude Code: + +```shell +/install-mcp-inspector +``` + +This command adds the `nette/mcp-inspector` package to your project and configures everything automatically. + +Alternatively, you can install it manually with Composer: + +```shell +composer require nette/mcp-inspector +``` + +**Important:** After installing MCP Inspector, restart Claude Code (type `/exit` and run `claude` again) to activate the connection. + + +Testing Your Setup +================== + +Let's verify everything works. Try these prompts: + + +Test the Plugin Knowledge +------------------------- + +Type: + +``` +What's the recommended directory structure for a Nette application? +``` + +Claude should respond with detailed information about presenters, models, templates, and configuration – knowledge that comes from the `nette-architecture` skill. + + +Test MCP Inspector +------------------ + +Type: + +``` +What services do I have registered in my DI container? +``` + +If MCP Inspector is working, Claude will call `di_get_services()` and show you the actual services from your application. If you see a list of your real services, congratulations – everything is set up correctly! + + +Test Database Introspection +--------------------------- + +If your application uses a database, try: + +``` +What tables do I have? Show me the columns in the user table. +``` + + +Your First Real Task +==================== + +Now that everything is set up, let's do something useful. Try this prompt: + +``` +I need a simple ArticlePresenter with list and detail actions. +Generate the presenter, templates, and tell me what routes I need. +``` + +Watch as Claude generates a complete, working presenter following Nette conventions. It will: +- Create the presenter class with proper type hints +- Generate Latte templates for both actions +- Suggest the appropriate route configuration + +If you have MCP Inspector set up and an `article` table in your database, try: + +``` +Look at my article table and generate an ArticleRow entity with proper type hints. +``` + + +Other AI Tools +============== + +While we recommend Claude Code for the best Nette experience, MCP Inspector works with any MCP-compatible tool. + + +Cursor +------ + +Cursor is a popular AI-first code editor. To use MCP Inspector with Cursor: + +1. Install MCP Inspector: `composer require nette/mcp-inspector` +2. Create `.cursor/mcp.json` in your project: + +```json +{ + "mcpServers": { + "nette-inspector": { + "command": "php", + "args": ["vendor/bin/mcp-inspector"] + } + } +} +``` + +3. Restart Cursor + +Note: Cursor doesn't have the Nette-specific skills that the Claude Code plugin provides, but MCP Inspector will still give it access to your application's services, database, routes, and logs. + + +VS Code + Continue +------------------ + +Continue is an open-source AI coding assistant for VS Code. Configure MCP Inspector in Continue's settings following their MCP documentation. + + +Other MCP Tools +--------------- + +Any tool supporting the Model Context Protocol can use MCP Inspector. See the [MCP Inspector manual configuration |mcp-inspector#manual-mcp-configuration] for setup instructions. + + +What's Next +=========== + +You're now ready for AI-assisted Nette development! Here's where to go from here: + +- [MCP Inspector |mcp-inspector] – Learn about all 20 introspection tools +- [Claude Code Plugin |claude-code] – Explore all 13 skills (Claude Code users) +- [Tips & Best Practices |tips] – Get the most out of your AI assistant diff --git a/ai/en/guide.texy b/ai/en/guide.texy new file mode 100644 index 0000000000..1852a706c8 --- /dev/null +++ b/ai/en/guide.texy @@ -0,0 +1,127 @@ +Vibe Coding +*********** + +
+ +Vibe coding is a new way of programming where you describe what you want in plain language and AI writes the code for you. Nette is ideal for this style of development – strict dependency injection, strong typing, and clear conventions allow AI to generate precise, working code. + +- **MCP Inspector** – Gives any AI tool real-time access to your application +- **Claude Code Plugin** – Deep Nette knowledge for Claude Code users +- **Best Practices** – Proven patterns for effective AI collaboration + +
+ + +What is Vibe Coding? +==================== + +"The hottest new programming language is English." + +That's the core idea behind vibe coding – instead of writing every line yourself, you describe your intent and let AI handle the implementation. Want a presenter for managing products? Just say so. Need a form with validation? Describe the fields and rules. + +But here's the important part: **AI doesn't replace programmers**. It's a powerful assistant that accelerates routine work: + +- Generate boilerplate code (presenters, forms, entities) in seconds +- Understand existing code and explain how it works +- Find bugs and suggest fixes +- Write tests based on your implementation + +The catch? AI doesn't truly know your application. It sees only what you show it and guesses the rest based on patterns it learned during training. That's where Nette AI tools come in. + + +Why Nette is Perfect for AI +=========================== + +Not all frameworks work equally well with AI. Nette has properties that make it exceptionally suited for AI-assisted development: + +**Strict Dependency Injection** + +In Nette, all services are registered in the DI container. AI can inspect exactly what services exist and how they're configured – no guessing required. + +**Strong Typing** + +Type hints on methods and properties mean AI generates code that actually works. Fewer runtime errors, less debugging. + +**Clear Conventions** + +Presenters, components, templates – everything has its place. AI can follow these patterns and produce code that looks like it was written by an experienced Nette developer. + +The key principle: + +.**"Without MCP, AI guesses. With MCP, AI knows."** + + +How It Works +============ + +The magic happens through **MCP (Model Context Protocol)** – an open standard for connecting AI assistants to external data sources. Instead of guessing based on training data, AI can query your actual application state. + +Here's the flow: + +1. **You** describe what you want: "Create an entity for the product table" +2. **AI tool** (Claude, Cursor, etc.) needs to know your database schema +3. **MCP Inspector** queries your application and returns the actual schema +4. **AI** generates code that matches your real database + +No hallucinations. No guessing. Just accurate code. + + +Nette AI Tools +============== + + +MCP Inspector +------------- + +The core of Nette's AI integration. MCP Inspector is an MCP server that gives **any compatible AI tool** real-time access to your application: + +| What AI Can See | Examples | +|-----------------|----------| +| **DI Container** | Services, parameters, extensions | +| **Database** | Tables, columns, relationships | +| **Router** | Routes, URL matching, generation | +| **Tracy** | Exceptions, warnings, logs | + +MCP Inspector works with Claude Code, Cursor, VS Code with Continue, and any other tool that supports the MCP protocol. + +[Learn more about MCP Inspector |mcp-inspector] + + +Claude Code Plugin +------------------ + +For users of Claude Code, there's an additional plugin that gives Claude deep knowledge of Nette conventions. It includes 10 specialized "skills" that activate automatically: + +| Skill | What It Covers | +|-------|----------------| +| nette-architecture | Presenters, modules, directory structure | +| nette-database | Database Explorer, entities, queries | +| nette-forms | Controls, validation, rendering | +| latte-templates | Syntax, filters, security | +| + 6 more... | [See complete list |claude-code] | + +The plugin also automatically validates Latte templates and NEON files after every edit. + +[Learn more about Claude Code Plugin |claude-code] + + +Other AI Tools +-------------- + +MCP Inspector works with any MCP-compatible tool. Setup guides for additional tools are coming soon: + +- **Cursor** – Popular AI-first code editor +- **VS Code + Continue** – Open-source AI coding assistant +- **Gemini CLI** – Google's command-line AI tool + + +Getting Started +=============== + +Ready to try vibe coding with Nette? The setup takes about 10 minutes: + +1. **Choose your AI tool** – We recommend Claude Code for the best Nette experience +2. **Install MCP Inspector** – The core that gives AI access to your application +3. **Start coding** – Describe what you want and let AI help + +[Complete setup guide |getting-started] diff --git a/ai/en/mcp-inspector.texy b/ai/en/mcp-inspector.texy new file mode 100644 index 0000000000..2b0b8808d7 --- /dev/null +++ b/ai/en/mcp-inspector.texy @@ -0,0 +1,448 @@ +MCP Inspector +************* + +
+ +MCP Inspector is the bridge between **any AI tool** and your Nette application. It allows AI assistants to look directly at your running app – to see what services you have registered, what your database schema looks like, which routes are defined, and what errors have occurred. + +This is what makes the difference between AI that guesses and AI that knows. + +
+ + +Supported AI Tools +================== + +MCP Inspector works with any tool that supports the **Model Context Protocol (MCP)**: + +- **[Claude Code |claude-code]** – Full support with dedicated Nette plugin +- **Cursor** – Configure via `.cursor/mcp.json` +- **VS Code + Continue** – Configure via Continue settings +- **Any MCP-compatible tool** – See [manual configuration |#manual-mcp-configuration] + + +Why MCP Matters +=============== + +Imagine you ask your AI: "Generate an entity for the product table." + +Without MCP Inspector, the AI has to guess what columns your table has. It might assume common patterns like `id`, `name`, `price` – but what if your table has different columns? What if `price` is called `unit_price`? What if you have a `currency_id` foreign key? + +With MCP Inspector, the AI doesn't guess. It calls `db_get_columns("product")` and sees your actual schema: + +The result is code that actually works with your database, not code you have to fix. + + +Installation +============ + +If you're using the [Nette plugin for Claude Code |claude-code], installation is simple: + +```shell +/install-mcp-inspector +``` + +This command adds `nette/mcp-inspector` to your project and configures everything automatically. + +For other AI tools or manual installation: + +```shell +composer require nette/mcp-inspector +``` + +Then configure your AI tool to use the MCP server – see [manual configuration |#manual-mcp-configuration] below. + +**Important:** After installation, restart your AI tool. The MCP server only connects when the tool starts. + + +How It Works +============ + +MCP Inspector runs as a background process that your AI tool can communicate with. When AI needs information about your application, it sends a request to MCP Inspector, which: + +1. Loads your application's DI container (using `App\Bootstrap`) +2. Executes the requested query (get services, read database schema, etc.) +3. Returns the result to the AI + +All operations are **read-only**. MCP Inspector can't modify your database, change configuration, or execute commands. + + +DI Container Tools +================== + +These tools let AI explore your service definitions. + + +di_get_services +--------------- + +Lists all registered services. You can filter by name or type. + +When AI asks "What mail services do I have?", it calls: + +``` +di_get_services("mail") +``` + +And gets a list like: + +``` +- mail.mailer (Nette\Mail\Mailer) +- App\Model\QueueMailer +- App\Core\SmtpTransport +``` + + +di_get_service +-------------- + +Gets detailed information about a specific service – how it's created, what setup methods are called, what tags it has. + + +di_get_parameters +----------------- + +Reads configuration parameters. Want to know what your database settings are? + +``` +di_get_parameters("database") +``` + +Note: Sensitive values (passwords, tokens, API keys) are automatically masked. + + +di_find_by_tag +-------------- + +Finds services with a specific tag. Useful for discovering CLI commands: + +``` +di_find_by_tag("console.command") +``` + + +di_find_by_type +--------------- + +Finds services implementing a specific interface: + +``` +di_find_by_type("Nette\\Security\\Authenticator") +``` + + +di_get_extensions +----------------- + +Lists all registered DI extensions with their configuration. + + +Database Tools +============== + +These tools give AI visibility into your database structure. + + +db_get_tables +------------- + +Lists all tables in your database. + + +db_get_columns +-------------- + +Gets detailed column information for a table – types, whether they're nullable, default values, and foreign key relationships. + +``` +db_get_columns("order") +``` + +Returns something like: + +``` +- id: int (PRIMARY KEY) +- customer_id: int (FK → customer.id) +- status: varchar(20) +- total: decimal(10,2) +- created_at: datetime +``` + + +db_get_relationships +-------------------- + +Shows all foreign key relationships in your database – which tables reference which other tables. + + +db_get_indexes +-------------- + +Lists indexes for a specific table. + + +db_explain_query +---------------- + +Runs `EXPLAIN` on a SELECT query to analyze its performance. AI can use this to suggest query optimizations. + + +db_generate_entity +------------------ + +The most useful tool for quick development. Given a table name, it generates a complete PHP entity class with proper type hints: + +``` +db_generate_entity("product") +``` + +Generates: + +```php +/** + * @property-read int $id + * @property-read string $name + * @property-read float $unit_price + * @property-read ?CategoryRow $category + * @property-read DateTimeImmutable $created_at + */ +final class ProductRow extends Table\ActiveRow +{ +} +``` + + +Router Tools +============ + +These tools help AI understand your URL structure. + + +router_get_routes +----------------- + +Lists all registered routes with their masks and default values. + + +router_match_url +---------------- + +Given a URL, finds which presenter and action handles it: + +``` +router_match_url("/admin/products/edit/5") +``` + +Returns: + +``` +Presenter: Admin:Product +Action: edit +Parameters: id=5 +``` + + +router_generate_url +------------------- + +Generates a URL for a given presenter and action: + +``` +router_generate_url("Admin:Product:edit", {"id": 5}) +``` + + +Tracy Tools +=========== + +These tools let AI see error logs and help with debugging. They're incredibly useful when something goes wrong – instead of you describing the error, AI can read it directly. + + +tracy_get_last_exception +------------------------ + +Gets the most recent exception from Tracy's log, including the full stack trace. When something breaks, this is the first thing AI checks. + +``` +tracy_get_last_exception() +``` + +Returns the exception class, message, file, line number, and complete stack trace. AI can analyze this to identify the root cause and suggest a fix. + +Example response: +``` +Exception: Nette\Database\UniqueConstraintViolationException +Message: Duplicate entry 'john@example.com' for key 'email' +File: /app/Model/UserService.php:45 +Stack trace: + #0 /app/Presentation/Admin/UserPresenter.php:32 + #1 /vendor/nette/application/src/... +``` + + +tracy_get_exceptions +-------------------- + +Lists recent exception files from Tracy's log directory. Useful for finding patterns or recurring issues. + +``` +tracy_get_exceptions(5) +``` + +Returns the 5 most recent exception files with timestamps. You can then use `tracy_get_exception_detail` to examine any of them. + + +tracy_get_exception_detail +-------------------------- + +Gets the complete details of a specific exception file. Use this when you want to examine an older exception, not just the latest one. + +``` +tracy_get_exception_detail("exception-2024-01-15-143022-abc123.html") +``` + + +tracy_get_warnings +------------------ + +Shows recent PHP warnings and notices from Tracy's log. These often indicate problems that don't crash the application but should be fixed. + +``` +tracy_get_warnings(10) +``` + +Common warnings AI can help fix: +- Undefined array key +- Deprecated function calls +- Type mismatch warnings + + +tracy_get_log +------------- + +Reads entries from any Tracy log level. Tracy supports multiple log files: `error.log`, `warning.log`, `info.log`, and custom levels. + +``` +tracy_get_log("error", 20) +``` + +This reads the last 20 entries from the error log. Useful for seeing a history of issues, not just the most recent one. + + +Creating Custom Tools +===================== + +You can extend MCP Inspector with your own tools. This is useful if you have application-specific data that AI should be able to query. + +Create a class implementing the `Toolkit` interface: + +```php +use Mcp\Capability\Attribute\McpTool; +use Nette\McpInspector\Toolkit; +use Nette\McpInspector\Bridge\BootstrapBridge; + +class OrderToolkit implements Toolkit +{ + public function __construct( + private BootstrapBridge $bridge, + ) {} + + /** + * Get pending orders count and total value. + */ + #[McpTool(name: 'orders_get_pending_summary')] + public function getPendingSummary(): array + { + $db = $this->bridge->getContainer() + ->getByType(Nette\Database\Explorer::class); + + $result = $db->table('order') + ->where('status', 'pending') + ->select('COUNT(*) AS count, SUM(total) AS total') + ->fetch(); + + return [ + 'count' => $result->count, + 'total' => $result->total, + ]; + } +} +``` + +Register it in `mcp-config.neon`: + +```neon +toolkits: + - App\Mcp\OrderToolkit +``` + +Now AI can call `orders_get_pending_summary()` to get real-time order statistics. + + +Configuration +============= + +MCP Inspector works out of the box with the default Nette project structure. If your setup is different, create `mcp-config.neon` in your project root: + +```neon +# Path to Bootstrap file (if not in default location) +bootstrap: src/Bootstrap.php + +# Bootstrap class name (if different from default) +bootstrapClass: MyApp\Bootstrap + +# Additional custom toolkits +toolkits: + - App\Mcp\OrderToolkit + - App\Mcp\CustomerToolkit +``` + + +Manual MCP Configuration +------------------------ + +For AI tools other than Claude Code (which configures automatically via the plugin), add MCP Inspector to your tool's configuration: + +**For most MCP-compatible tools**, create `.mcp.json` in your project root: + +```json +{ + "mcpServers": { + "nette-inspector": { + "command": "php", + "args": ["vendor/bin/mcp-inspector"] + } + } +} +``` + +**For Cursor**, add to `.cursor/mcp.json`: + +```json +{ + "mcpServers": { + "nette-inspector": { + "command": "php", + "args": ["vendor/bin/mcp-inspector"] + } + } +} +``` + +Consult your AI tool's documentation for the exact configuration location. + + +Security Considerations +======================= + +MCP Inspector is designed for development environments. Here's what you should know: + +**Read-only by design** – All tools only read data, never modify it. + +**Database protection** – The `db_explain_query` tool only accepts SELECT, SHOW, DESCRIBE, and EXPLAIN queries. INSERT, UPDATE, DELETE, and other modifying queries are rejected. + +**Sensitive data masking** – Configuration values containing words like "password", "secret", "token", or "apikey" are automatically masked with `***MASKED***`. + +**Do not expose in production** – MCP Inspector should only run on development machines. It provides detailed information about your application internals that you don't want exposed publicly. + +{{composer: nette/mcp-inspector}} diff --git a/ai/en/tips.texy b/ai/en/tips.texy new file mode 100644 index 0000000000..586f558519 --- /dev/null +++ b/ai/en/tips.texy @@ -0,0 +1,296 @@ +Tips for AI-Assisted Development +******************************** + +
+ +AI is a powerful tool, but like any tool, you get better results when you know how to use it well. This page collects practical advice from real-world experience with AI-assisted Nette development. + +- How to write prompts that get better results +- Making the most of MCP Inspector +- Proven workflows for common tasks +- Mistakes to avoid + +
+ + +Writing Better Prompts +====================== + + +The Art of Being Specific +------------------------- + +The single biggest improvement you can make is being specific. Compare these two prompts: + +**Vague prompt:** + +``` +Create a form +``` + +This gives the AI almost no context. What form? What fields? What validation? The AI has to make assumptions, and those assumptions might not match what you need. + +**Specific prompt:** + +``` +Create a ProductForm with: +- name: text field, required, max 100 characters +- price: float field, required, must be positive +- description: textarea, optional +- category: select from CategoryRow entities + +Use Bootstrap 5 rendering. The form should work for both creating new products and editing existing ones. +``` + +Now the AI knows exactly what you need and can generate code that works on the first try. + + +Point to Existing Patterns +-------------------------- + +Your codebase already has patterns. Instead of explaining them, point the AI to examples: + +``` +Create an OrderPresenter. Follow the same patterns as ProductPresenter – +same structure, same way of handling forms, same template organization. +``` + +The AI will read ProductPresenter and replicate the patterns you're already using. + + +Let MCP Do the Heavy Lifting +---------------------------- + +If you have MCP Inspector installed (and you should!), don't explain your application – let the AI discover it: + +**Instead of:** + +``` +My product table has columns: id (int), name (varchar), price (decimal), +category_id (int, foreign key to category), created_at (datetime)... +``` + +**Just say:** + +``` +Generate an entity for the product table. +``` + +The AI will call `db_get_columns("product")` and see the actual schema. The generated entity will match your real database, including any columns you might have forgotten to mention. + + +Give Context About Your Goals +----------------------------- + +AI can't read your mind. If there's a reason behind your request, share it: + +``` +I need to optimize the product listing page. It's currently loading +all products at once, which is slow when there are thousands of items. +The page needs to support filtering by category and sorting by price or name. +``` + +This helps the AI suggest an appropriate solution (pagination, lazy loading, caching) rather than just blindly implementing what you asked for. + + +Working with MCP Inspector +========================== + +MCP Inspector is most powerful when you use it strategically. + + +Explore Before You Build +------------------------ + +Starting a new feature? Let the AI understand the context first: + +``` +I'm going to add order tracking. Before we start: +1. What services do I have related to orders? +2. What does my order table look like? +3. What routes handle order-related pages? +``` + +This gives the AI context about your existing code, so the new feature fits naturally. + + +Debug Smarter +------------- + +When something goes wrong, don't describe the error – let the AI see it: + +``` +Something broke. Check the Tracy log for the last exception +and tell me what went wrong. +``` + +The AI will call `tracy_get_last_exception()`, read the stack trace, and can often identify the problem faster than you could explain it. + + +Verify Before You Create +------------------------ + +Before adding new routes or links, verify what exists: + +``` +What presenter handles /admin/products/edit? I want to make sure +I'm not creating something that conflicts. +``` + + +Common Workflows +================ + +Here are proven approaches for common tasks. + + +Creating a Complete CRUD +------------------------ + +Don't ask for one piece at a time. Give the AI the full picture: + +``` +Create a complete CRUD for managing products: + +1. ProductPresenter with actions: list, add, edit, delete +2. ProductForm as a component (works for both add and edit) +3. Latte templates for all actions +4. Route suggestions + +Use the actual product table schema. Follow the patterns +in CategoryPresenter if it exists. +``` + + +Adding a New Feature +-------------------- + +Break it down into phases that build on each other: + +``` +I need to add customer reviews to products. + +Phase 1 - Data layer: +- Look at the product table +- Suggest the review table schema +- Create ReviewRow entity + +Phase 2 - Business logic: +- Create ReviewService for CRUD operations +- Add methods to get reviews for a product + +Phase 3 - UI: +- Add review display to ProductPresenter:detail +- Create ReviewForm for submitting reviews +``` + + +Refactoring Existing Code +------------------------- + +Let the AI understand before it changes: + +``` +Analyze the OrderService class. What does each method do? +Are there any code smells or improvements you'd suggest? +``` + +Then: + +``` +The calculateTotal method is doing too much. Split it into +smaller methods while keeping the same public interface. +``` + + +Mistakes to Avoid +================= + + +Not Reviewing Generated Code +---------------------------- + +AI generates code quickly, but that doesn't mean every line is perfect. Always review: + +- **Database queries** – Are they efficient? Do they need indexes? +- **Security** – Is input validated? Are there authorization checks? +- **Edge cases** – What happens with empty data? Null values? + +AI is very good, but it's still an assistant. You're the developer responsible for the final code. + + +Ignoring Validation Feedback +---------------------------- + +If you're using the [Claude Code plugin |claude-code], it validates your Latte templates and NEON files automatically. When it reports an error, the AI knows about it. Instead of manually fixing the error, just say: + +``` +Fix the error you just created. +``` + +The AI will read the validation output and correct the mistake. + + +Forgetting Service Registration +------------------------------- + +When the AI creates a new service class, it sometimes forgets to register it in the DI container. If you get "Service not found" errors, ask: + +``` +What changes do I need in services.neon for the new OrderExportService? +``` + + +Asking for Too Much at Once +--------------------------- + +While AI can handle complex tasks, sometimes it helps to break them down: + +**Too ambitious:** + +``` +Build me a complete e-commerce system with product catalog, +shopping cart, checkout, payments, order tracking, and admin panel. +``` + +**Better approach:** + +``` +Let's build an e-commerce system step by step. Start with the product +catalog – I need to list, view, and admin products. +``` + + +When AI Excels (and When It Doesn't) +==================================== + + +AI is Great For +--------------- + +- **Boilerplate code** – Presenters, forms, entities, basic templates +- **Following patterns** – "Do it like X but for Y" +- **Understanding code** – "What does this method do?" +- **Generating tests** – Given implementation, create tests +- **Refactoring** – Improving code structure while keeping behavior + + +Consider Manual Coding For +-------------------------- + +- **Complex business logic** – Domain rules that require careful thinking +- **Performance-critical code** – Algorithms that need optimization +- **Security-sensitive code** – Authentication, authorization, encryption +- **Novel solutions** – Things that don't follow existing patterns + +AI is a multiplier, not a replacement. It makes good developers faster, but it still needs a good developer guiding it. + + +Final Thoughts +============== + +The best way to learn AI-assisted development is to practice. Start with simple tasks, pay attention to what works, and gradually take on more complex projects. + +And remember: the AI is your assistant. You're still the developer. You make the decisions, you review the code, and you're responsible for the quality of the final product. + +Happy coding! diff --git a/ai/meta.json b/ai/meta.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/ai/meta.json @@ -0,0 +1 @@ +{} diff --git a/application/bg/@home.texy b/application/bg/@home.texy index e01b54f4ee..4c10c2af32 100644 --- a/application/bg/@home.texy +++ b/application/bg/@home.texy @@ -1,36 +1,85 @@ -Приложение Nette -**************** +Nette Application +***************** .[perex] -Пакетът `nette/application` е основа за създаване на интерактивни уеб приложения. +Nette Application е ядрото на Nette framework, което предоставя мощни инструменти за създаване на модерни уеб приложения. Предлага редица изключителни характеристики, които значително улесняват разработката и подобряват сигурността и поддръжката на кода. -- [Как работят приложенията |how-it-works]? -- [Bootstrap |Bootstrap] -- [Презентатори |presenters] -- [Шаблони |templates] -- [Модули |modules] -- [Маршрутизиране |routing] -- [Създаване на URL |creating-links] -- [Интерактивни компоненти |components] -- [AJAX и фрагменти |ajax] -- [Мултипликатор |Multiplier] -- [Конфигурация |configuration] +Инсталация +---------- -Настройка ---------- - -Изтеглете и инсталирайте пакета с помощта на [Composer |best-practices:composer]: +Изтеглете и инсталирайте библиотеката с помощта на [Composer|best-practices:composer]: ```shell composer require nette/application ``` -| версия на пакета | съвместима версия на PHP -|-----------------------|----------------------- -| Nette Application 4.0 | PHP 8.0 - 8.1 -| Nette Application 3.1 | PHP 7.2 - 8.1 -| Nette Application 3.0 | PHP 7.1 - 8.0 -| Nette Application 2.4 | PHP 5.6 - 8.0 -Отнася се за най-новите версии на кръпките. +Защо да изберете Nette Application? +----------------------------------- + +Nette винаги е бил пионер в областта на уеб технологиите. + +**Двупосочен рутер:** Nette разполага с усъвършенствана система за маршрутизация, която е уникална със своята двупосочност - не само преобразува URL адреси в действия на приложението, но също така може да генерира обратно URL адреси. Това означава, че: +- Можете по всяко време да промените структурата на URL адресите на цялото приложение, без да е необходимо да редактирате шаблоните +- URL адресите се канонизират автоматично, което подобрява SEO +- Маршрутизацията се дефинира на едно място, а не е разпръсната в анотации + +**Компоненти и сигнали:** Вградената компонентна система, вдъхновена от Delphi и React.js, е напълно изключителна сред PHP framework-ците: +- Позволява създаването на повторно използваеми UI елементи +- Поддържа йерархично композиране на компоненти +- Предлага елегантна обработка на AJAX заявки с помощта на сигнали +- Богата библиотека от готови компоненти на [Componette](https://componette.org) + +**AJAX и снипети:** Nette представи революционен начин за работа с AJAX още през 2009 г., много преди подобни решения като Hotwire за Ruby on Rails или Symfony UX Turbo: +- Снипетите позволяват актуализиране само на части от страницата, без да е необходимо да се пише JavaScript +- Автоматична интеграция с компонентната система +- Интелигентна инвалидация на части от страници +- Минимално количество предавани данни + +**Интуитивни шаблони [Latte|latte:]:** Най-сигурната система за шаблони за PHP с разширени функции: +- Автоматична защита срещу XSS с контекстно чувствително екраниране +- Разширяемост с помощта на персонализирани филтри, функции и тагове +- Наследяване на шаблони и снипети за AJAX +- Отлична поддръжка на PHP 8.x с типова система + +**Dependency Injection:** Nette напълно използва Dependency Injection: +- Автоматично предаване на зависимости (autowiring) +- Конфигурация чрез ясен NEON формат +- Поддръжка на фабрики за компоненти + + +Основни предимства +------------------ + +- **Сигурност**: Автоматична защита срещу [уязвимости|nette:vulnerability-protection] като XSS, CSRF и др. +- **Продуктивност**: По-малко писане, повече функции благодарение на интелигентния дизайн +- **Дебъгване**: [Tracy debugger|tracy:] с панел за маршрутизация +- **Производителност**: Интелигентен кеш, lazy loading на компоненти +- **Гъвкавост**: Лесно модифициране на URL адреси дори след завършване на приложението +- **Компоненти**: Уникална система от повторно използваеми UI елементи +- **Модерност**: Пълна поддръжка на PHP 8.4+ и типова система + + +Да започваме +------------ + +1. [Как работят приложенията? |how-it-works] - Разбиране на основната архитектура +2. [Presenters |presenters] - Работа с презентери и действия +3. [Шаблони |templates] - Създаване на шаблони в Latte +4. [Маршрутизация |routing] - Конфигуриране на URL адреси +5. [Интерактивни компоненти |components] - Използване на компонентната система + + +Съвместимост с PHP +------------------ + +| версия | съвместим с PHP +|-----------|------------------- +| Nette Application 4.0 | PHP 8.1 – 8.4 +| Nette Application 3.2 | PHP 8.1 – 8.4 +| Nette Application 3.1 | PHP 7.2 – 8.3 +| Nette Application 3.0 | PHP 7.1 – 8.0 +| Nette Application 2.4 | PHP 5.6 – 8.0 + +Важи за последната пач версия. diff --git a/application/bg/@left-menu.texy b/application/bg/@left-menu.texy index e19ec32175..89db4642b8 100644 --- a/application/bg/@left-menu.texy +++ b/application/bg/@left-menu.texy @@ -1,19 +1,22 @@ -Приложение Nette -**************** -- [Как работят приложенията |how-it-works]? -- [Bootstrap |Bootstrap] -- [Презентатори |presenters] +Nette Application +***************** +- [Как работят приложенията? |how-it-works] +- [Bootstrapping] +- [Presenters |presenters] - [Шаблони |templates] -- [Модули |modules] -- [Маршрутизиране |routing] -- [Създаване на URL |creating-links] +- [Директорийна структура |directory-structure] +- [Маршрутизация |routing] +- [Създаване на URL връзки |creating-links] - [Интерактивни компоненти |components] -- [AJAX и фрагменти |ajax] -- [Мултипликатор |Multiplier] +- [AJAX & снипети |ajax] +- [Multiplier |multiplier] - [Конфигурация |configuration] Допълнително четене ******************* -- [Най-добри практики |best-practices:] -- [Отстраняване на неизправности |nette:troubleshooting] +- [Защо да използвате Nette? |www:10-reasons-why-nette] +- [Инсталация |nette:installation] +- [Пишем първото си приложение! |quickstart:] +- [Ръководства и процедури |best-practices:] +- [Решаване на проблеми |nette:troubleshooting] diff --git a/application/bg/@meta.texy b/application/bg/@meta.texy new file mode 100644 index 0000000000..57804a1127 --- /dev/null +++ b/application/bg/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документация на Nette}} diff --git a/application/bg/ajax.texy b/application/bg/ajax.texy index 924c419cf7..93b9e9c397 100644 --- a/application/bg/ajax.texy +++ b/application/bg/ajax.texy @@ -1,38 +1,45 @@ -AJAX и фрагменти -**************** +AJAX & снипети +**************
-Съвременните уеб приложения днес се изпълняват наполовина на сървъра и наполовина в браузъра. AJAX е важен обединяващ фактор. Каква поддръжка предлага рамката Nette? -- Подаване на фрагменти (наречени *фрагменти*) -- прехвърляне на променливи между PHP и JavaScript -- отстраняване на грешки в приложенията AJAX +В ерата на съвременните уеб приложения, където функционалността често се разпределя между сървъра и браузъра, AJAX е незаменим свързващ елемент. Какви възможности ни предлага Nette Framework в тази област? +- изпращане на части от шаблона, т.нар. снипети +- предаване на променливи между PHP и JavaScript +- инструменти за дебъгване на AJAX заявки
-Заявката AJAX може да бъде открита чрез метода на услугата, който [капсулира HTTP заявката |http:request] `$httpRequest->isAjax()` (определя се въз основа на HTTP заглавието `X-Requested-With`). Съществува и съкратен метод в програмата за представяне: `$this->isAjax()`. -Заявката AJAX не се различава от обикновената заявка - водещият се извиква с определено представяне и параметри. От водещия зависи и как ще реагира: той може да използва процедурите си, за да върне фрагмент от HTML код, XML документ, JSON обект или част от Javascript код. +AJAX заявка +=========== -Съществува предварително обработен обект `payload`, предназначен за изпращане на данни към браузъра във формат JSON. +AJAX заявката по същество не се различава от класическата HTTP заявка. Извиква се презентер с определени параметри. И от презентера зависи как ще реагира на заявката - може да върне данни във формат JSON, да изпрати част от HTML код, XML документ и т.н. -```php -public function actionDelete(int $id): void -{ - if ($this->isAjax()) { - $this->payload->message = 'Успешно'; - } - // ... -} +От страна на браузъра инициализираме AJAX заявката с помощта на функцията `fetch()`: + +```js +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) +.then(response => response.json()) +.then(payload => { + // обработка на отговора +}); ``` -За да имате пълен контрол върху извеждането на JSON, използвайте метода `sendJson` в презентатора. Това веднага ще прекъсне презентатора и ще се справите без шаблона: +От страна на сървъра разпознаваме AJAX заявка с метода `$httpRequest->isAjax()` на сървиса [капсулиращ HTTP заявка |http:request]. За откриване се използва HTTP хедърът `X-Requested-With`, затова е важно да го изпращате. В рамките на презентера може да се използва методът `$this->isAjax()`. + +Ако искате да изпратите данни във формат JSON, използвайте метода [`sendJson()` |presenters#Изпращане на отговор]. Методът също така прекратява дейността на презентера. ```php -$this->sendJson(['key' => 'value', /* ... */]); +public function actionExport(): void +{ + $this->sendJson($this->model->getData); +} ``` -Ако искаме да изпращаме HTML, можем да създадем специален шаблон за AJAX заявки: +Ако планирате да отговорите със специален шаблон, предназначен за AJAX, можете да го направите по следния начин: ```php public function handleClick($param): void @@ -45,74 +52,80 @@ public function handleClick($param): void ``` -Naja .[#toc-naja] -================= +Снипети +======= + +Най-мощният инструмент, който Nette предлага за свързване на сървъра с клиента, са снипетите. Благодарение на тях можете да превърнете обикновено приложение в AJAX приложение с минимални усилия и няколко реда код. Как работи всичко това, демонстрира примерът Fifteen, чийто код можете да намерите на [GitHub |https://github.com/nette-examples/fifteen]. + +Снипетите, или изрезките, позволяват да се актуализират само части от страницата, вместо да се презарежда цялата страница. Това е не само по-бързо и по-ефективно, но и осигурява по-комфортно потребителско изживяване. Снипетите могат да ви напомнят за Hotwire за Ruby on Rails или Symfony UX Turbo. Интересно е, че Nette представи снипетите 14 години по-рано. + +Как работят снипетите? При първото зареждане на страницата (не-AJAX заявка) се зарежда цялата страница, включително всички снипети. Когато потребителят взаимодейства със страницата (напр. кликне върху бутон, изпрати формуляр и т.н.), вместо да се зарежда цялата страница, се извиква AJAX заявка. Кодът в презентера извършва действието и решава кои снипети трябва да бъдат актуализирани. Nette рендира тези снипети и ги изпраща под формата на масив във формат JSON. Обслужващият код в браузъра вмъква получените снипети обратно в страницата. Така се пренася само кодът на променените снипети, което спестява трафик и ускорява зареждането в сравнение с пренасянето на съдържанието на цялата страница. + -[Библиотеката Naja |https://naja.js.org] се използва за обработка на AJAX заявки от страна на браузъра. [Инсталирайте |https://naja.js.org/#/guide/01-install-setup-naja] го като пакет за node.js (за използване с Webpack, Rollup, Vite, Parcel и други): +Naja +---- + +За обслужване на снипети от страна на браузъра се използва [библиотеката Naja |https://naja.js.org]. [Инсталирайте я |https://naja.js.org/#/guide/01-install-setup-naja] като node.js пакет (за използване с приложения Webpack, Rollup, Vite, Parcel и други): ```shell npm install naja ``` -...или да вмъкнете директно в шаблон на страница: +…или директно я вмъкнете в шаблона на страницата: ```html ``` +Първо е необходимо библиотеката да бъде [инициализирана |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization]: -Извадки .[#toc-snippety] -======================== +```js +naja.initialize(); +``` -Има обаче много по-мощен инструмент за вградена поддръжка на AJAX - фрагменти. Използването им ви позволява да превърнете обикновено приложение в AJAX приложение само с няколко реда код. Как работи всичко това, е показано в примера Fifteen, чийто код също е наличен в компилацията или в [GitHub |https://github.com/nette-examples/fifteen]. +За да превърнете обикновена връзка (сигнал) или изпращане на формуляр в AJAX заявка, е достатъчно да маркирате съответната връзка, формуляр или бутон с клас `ajax`: -Принципът на фрагментите е, че цялата страница се прехвърля по време на първоначалната (т.е. не-AJAX) заявка и след това при всяка AJAX [подзаявка |components#Signal] (заявка на същия изглед от същия водещ) само кодът на променените части се прехвърля в хранилището `payload`, споменато по-рано. +```html +Go -Извадките може да ви напомнят за Hotwire за Ruby on Rails или Symfony UX Turbo, но Nette ги изобретява четиринадесет години по-рано. +
+ +
+или -Инвалидизация .[#toc-invalidation-of-snippets] -============================================== +
+ +
+``` -Всеки наследник на класа [Control |components] (какъвто е Presenter) може да запомни дали по време на заявката е имало промени, които изискват повторно картографиране. Съществуват няколко начина за справяне с това: `redrawControl()` и `isControlInvalid()`. Пример: + +Прерисуване на снипети +---------------------- + +Всеки обект от клас [Control |components] (включително самият Presenter) следи дали са настъпили промени, изискващи неговото прерисуване. За това служи методът `redrawControl()`: ```php public function handleLogin(string $user): void { - // Обектът трябва да бъде показан отново, след като потребителят е влязъл в системата + // след влизане е необходимо да се прерисува съответната част $this->redrawControl(); // ... } ``` -Nette обаче осигурява още по-фина разделителна способност от целите компоненти. Изброените методи приемат името на така наречения "фрагмент" като незадължителен параметър. "Фрагмент" по същество е елемент от вашия шаблон, маркиран с макрос Latte за тази цел, за който ще стане дума по-късно. По този начин можете да поискате от компонент да прерисува само *част* от шаблона. Ако целият компонент е невалиден, всички негови фрагменти се прерисуват. Компонент е "невалиден", ако някой от неговите подкомпоненти е невалиден. - -```php -$this->isControlInvalid(); // -> false -$this->redrawControl('header'); // обезсилване на фрагмента с име 'header' -$this->isControlInvalid('header'); // -> true -$this->isControlInvalid('footer'); // -> false -$this->isControlInvalid(); // -> true, поне един фрагмент е невалиден +Nette позволява още по-фин контрол върху това, което трябва да се прерисува. Споменатият метод може да приема името на снипета като аргумент. Така може да се инвалидира (разбирай: да се наложи прерисуване) на ниво части от шаблона. Ако се инвалидира целият компонент, тогава се прерисува и всеки негов снипет: -$this->redrawControl(); // обезсилва целия компонент, всеки фрагмент -$this->isControlInvalid('footer'); // -> true +```php +// инвалидира снипета 'header' +$this->redrawControl('header'); ``` -Компонент, който е получил сигнал, автоматично се маркира за прерисуване. - -Чрез преначертаване на срезовете знаем точно кои части от кои елементи трябва да бъдат преначертани. - - -Етикет `{snippet} … {/snippet}` .{toc: Tag snippet} -=================================================== -Страницата се визуализира по същия начин, както при обикновена заявка: зареждат се същите шаблони и т.н. Най-важното обаче е да се предотвратят онези части, които не трябва да се извеждат; останалите части трябва да се свържат с идентификатор и да се изпратят на потребителя във формат, който обработващият JavaScript може да разбере. +Снипети в Latte +--------------- - -Синтаксис .[#toc-sintaksis] ---------------------------- - -Ако в шаблона има контролен елемент или фрагмент, трябва да го обгърнем с помощта на сдвоения таг `{snippet} ... {/snippet}` - визуализираният фрагмент ще бъде "изрязан" и изпратен на браузъра. Освен това той ще го обгърне със спомагателен таг `
` (можете да използвате друга). Следващият пример дефинира фрагмент с име `header`. Той може да представлява и шаблон на компонент: +Използването на снипети в Latte е изключително лесно. Ако искате да дефинирате част от шаблона като снипет, просто я обвийте с таговете `{snippet}` и `{/snippet}`: ```latte {snippet header} @@ -120,7 +133,9 @@ $this->isControlInvalid('footer'); // -> true {/snippet} ``` -Ако искате да създадете фрагмент със съдържащ елемент, различен от `
`, или да добавите потребителски атрибути към елемента, можете да използвате следното определение: +Снипетът създава в HTML страницата елемент `
` със специално генериран `id`. При прерисуване на снипета се актуализира съдържанието на този елемент. Затова е необходимо при първоначалното рендиране на страницата да се рендират и всички снипети, дори и ако в началото са празни. + +Можете да създадете и снипет с друг елемент освен `
` с помощта на n:атрибут: ```latte
@@ -129,138 +144,106 @@ $this->isControlInvalid('footer'); // -> true ``` -Динамични фрагменти .[#toc-dinamiceskie-snippety] -================================================= +Области на снипети +------------------ -В Nette можете също така да дефинирате фрагменти с динамично име въз основа на параметър по време на изпълнение. Това е най-подходящо за различни списъци, в които трябва да променим само един ред, но не искаме да преместим целия списък заедно с него. Пример за това е: +Имената на снипетите могат да бъдат и изрази: ```latte - +{foreach $items as $id => $item} +
  • {$item}
  • +{/foreach} ``` -Съществува един статичен фрагмент `itemsContainer`, който съдържа няколко динамични фрагмента: `пункт-0`, `пункт-1` и т.н. +Така ще ни се създадат няколко снипета `item-0`, `item-1` и т.н. Ако директно инвалидираме динамичен снипет (например `item-1`), нищо няма да се прерисува. Причината е, че снипетите наистина работят като изрезки и се рендират само те самите. Но в шаблона всъщност няма снипет с име `item-1`. Той се създава едва при изпълнението на кода около снипета, т.е. цикъла foreach. Затова ще маркираме частта от шаблона, която трябва да се изпълни, с помощта на тага `{snippetArea}`: -Не можете да прерисувате динамичния фрагмент директно (прерисуването на `item-1` няма ефект), а трябва да прерисувате родителския фрагмент ( `itemsContainer` в този пример). При това се изпълнява кодът на родителския фрагмент, но на браузъра се предават само неговите вложени фрагменти. Ако искате да предадете само един от вложените фрагменти, трябва да промените входа за родителския фрагмент, за да избегнете генерирането на други вложени фрагменти. +```latte +
      + {foreach $items as $id => $item} +
    • {$item}
    • + {/foreach} +
    +``` -В горния пример трябва да се уверите, че само един елемент ще бъде добавен към масива `$list`, когато бъде направена заявката AJAX, така че цикълът `foreach` ще изведе само един динамичен фрагмент. +И ще накараме да се прерисува както самият снипет, така и цялата родителска област: ```php -class HomePresenter extends Nette\Application\UI\Presenter -{ - /** - * Этот метод возвращает данные для списка. - * Обычно это просто запрос данных из модели. - * Для целей этого примера данные жёстко закодированы. - */ - private function getTheWholeList(): array - { - return [ - 'First', - 'Second', - 'Third', - ]; - } - - public function renderDefault(): void - { - if (!isset($this->template->list)) { - $this->template->list = $this->getTheWholeList(); - } - } - - public function handleUpdate(int $id): void - { - $this->template->list = $this->isAjax() - ? [] - : $this->getTheWholeList(); - $this->template->list[$id] = 'Updated item'; - $this->redrawControl('itemsContainer'); - } -} +$this->redrawControl('itemsContainer'); +$this->redrawControl('item-1'); ``` +Същевременно е добре да се уверим, че масивът `$items` съдържа само тези елементи, които трябва да се прерисуват. -Извадки в активирания шаблон .[#toc-snippety-vo-vklyucennom-sablone] -==================================================================== - -Възможно е даден фрагмент да се намира в шаблон, който е включен от друг шаблон. В този случай е необходимо да се обгърне кодът за разрешаване във втория шаблон с макроса `snippetArea`, след което да се прерисуват както областта snippetArea, така и самият фрагмент. - -Макросът `snippetArea` гарантира, че кодът в него ще бъде изпълнен, но само действителният фрагмент от включения шаблон ще бъде изпратен на браузъра. +Ако в шаблона вмъкваме с помощта на тага `{include}` друг шаблон, който съдържа снипети, е необходимо вмъкването на шаблона отново да се включи в `snippetArea` и тя да се инвалидира заедно със снипета: ```latte -{* parent.latte *} -{snippetArea wrapper} - {include 'child.latte'} +{snippetArea include} + {include 'included.latte'} {/snippetArea} ``` + ```latte -{* child.latte *} +{* included.latte *} {snippet item} -... + ... {/snippet} ``` + ```php -$this->redrawControl('wrapper'); +$this->redrawControl('include'); $this->redrawControl('item'); ``` -Можете също така да го комбинирате с динамични фрагменти. - -Добавяне и премахване .[#toc-dobavlenie-i-udalenie] -=================================================== +Снипети в компоненти +-------------------- -Ако добавите нов елемент в списъка и отмените `itemsContainer`, заявката AJAX ще върне фрагменти, включващи новия елемент, но обработващият javascript няма да може да го визуализира. Това е така, защото няма HTML елемент с новосъздадения ID. - -В този случай най-лесният начин е да обвиете целия списък в друг фрагмент и да го обезсилите: +Можете да създавате снипети и в [компоненти|components] и Nette ще ги прерисува автоматично. Но тук има определено ограничение: за прерисуване на снипети се извиква методът `render()` без параметри. Следователно предаването на параметри в шаблона няма да работи: ```latte -{snippet wholeList} - -{/snippet} -Добавить +OK +{control productGrid} + +няма да работи: +{control productGrid $arg, $arg} +{control productGrid:paginator} ``` + +Изпращане на потребителски данни +-------------------------------- + +Заедно със снипетите можете да изпратите на клиента всякакви други данни. Достатъчно е да ги запишете в обекта `payload`: + ```php -public function handleAdd(): void +public function actionDelete(int $id): void { - $this->template->list = $this->getTheWholeList(); - $this->template->list[] = 'New one'; - $this->redrawControl('wholeList'); + // ... + if ($this->isAjax()) { + $this->payload->message = 'Success'; + } } ``` -Същото важи и за изтриване на елемент. Може да се подаде празен фрагмент, но обикновено списъците могат да бъдат странирани и би било трудно да се приложи премахването на един елемент и зареждането на друг (който преди това е бил на друга страница на странирания списък). - -Изпращане на параметри към компонент .[#toc-otpravka-parametrov-komponentu] -=========================================================================== +Предаване на параметри +====================== -Когато изпращаме параметри към компонент чрез заявка AJAX, независимо дали става въпрос за сигнални или постоянни параметри, трябва да предоставим тяхното глобално име, което съдържа и името на компонента. Пълното име на параметъра се връща от метода `getParameterId()`. +Ако изпращаме параметри на компонент чрез AJAX заявка, било то параметри на сигнал или персистентни параметри, трябва да посочим тяхното глобално име в заявката, което включва и името на компонента. Цялото име на параметъра се връща от метода `getParameterId()`. ```js -$.getJSON( - {link changeCountBasket!}, - { - {$control->getParameterId('id')}: id, - {$control->getParameterId('count')}: count - } -}); +let url = new URL({link //foo!}); +url.searchParams.set({$control->getParameterId('bar')}, bar); + +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) ``` -И обработете метода със съответните параметри в компонента. +И handle метод със съответните параметри в компонента: ```php -public function handleChangeCountBasket(int $id, int $count): void +public function handleFoo(int $bar): void { - } ``` diff --git a/application/bg/bootstrap.texy b/application/bg/bootstrap.texy deleted file mode 100644 index 17833a42e7..0000000000 --- a/application/bg/bootstrap.texy +++ /dev/null @@ -1,233 +0,0 @@ -Bootstrap -********* - -
    - -Bootstrap е кодът на bootstrap, който инициализира средата, създава контейнера за изпълнение на зависимости (DI) и стартира приложението. Обсъждаме: - -- Как да настроите приложение, използващо файлове NEON -- Как да работите с режими за производство и разработка -- Как да създадете контейнер DI - -
    - - -Приложенията, независимо дали са уеб приложения или скриптове от команден ред, започват с инициализиране на средата под една или друга форма. В древни времена за това може да е отговарял файл, наречен например `include.inc.php`, който е бил включен в изходния файл. -В съвременните приложения на Nette той е заменен с класа `Bootstrap`, който се намира като част от приложението във файла `app/Bootstrap.php`. Това може да изглежда например така: - -```php -use Nette\Bootstrap\Configurator; - -class Bootstrap -{ - public static function boot(): Configurator - { - $appDir = dirname(__DIR__); - $configurator = new Configurator; - //$configurator->setDebugMode('secret@23.75.345.200'); - $configurator->enableTracy($appDir . '/log'); - $configurator->setTempDirectory($appDir . '/temp'); - $configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); - $configurator->addConfig($appDir . '/config/common.neon'); - return $configurator; - } -} -``` - - -index.php .[#toc-index-php] -=========================== - -В случая на уеб приложения началният файл е `index.php`, който се намира в публичната директория `www/`. Той позволява на класа `Bootstrap` да инициализира средата и връща `$configurator`, който създава контейнера DI. След това тя извлича услугата `Application`, която стартира уеб приложението: - -```php -// инициализиране на средата + получаване на обект Configurator -$configurator = App\Bootstrap::boot(); -// създаване на DI-контейнер -$container = $configurator->createContainer(); -// DI-контейнерът ще създаде обект Nette\Application\Application -$application = $container->getByType(Nette\Application\Application::class); -//стартиране на приложението Nette -$application->run(); -``` - -Както виждате, класът [api:Nette\Bootstrap\Configurator], който сега ще представим по-подробно, помага при настройването на средата и създаването на контейнера за инжектиране на зависимости (DI). - - -Режим на разработка и производствен режим .[#toc-development-vs-production-mode] -================================================================================ - -Nette прави разграничение между два основни режима, в които се изпълнява заявката: разработка и производство. Режимът за разработка е насочен към максимално удобство за програмиста, показва се Tracy, кешът се обновява автоматично при промяна на шаблоните или конфигурацията на DI контейнера и т.н. Производственият режим е ориентиран към производителността, Tracy регистрира само грешки, а промените в шаблоните и другите файлове не се проверяват. - -Режимът се избира чрез автоматично разпознаване, така че обикновено не е необходимо да конфигурирате или превключвате нещо ръчно. Режимът за разработка се използва, ако приложението се изпълнява на локален хост (т.е. IP адресът е `127.0.0.1` или `::1`) и няма прокси сървър (т.е. HTTP заглавието му). В противен случай приложението работи в производствен режим. - -Ако искате да активирате режима за разработка в други случаи, например за програмисти, които имат достъп от определен IP адрес, можете да използвате `setDebugMode()`: - -```php -$configurator->setDebugMode('23.75.345.200'); // един или повече IP адреси -``` - -Определено препоръчваме да комбинирате IP адреса с "бисквитка". Ще съхраним тайния токен в "бисквитката" `nette-debug', например, `secret1234`, а режимът за разработка ще бъде активиран за програмистите с тази комбинация от IP и "бисквитка". - -```php -$configurator->setDebugMode('secret1234@23.75.345.200'); -``` - -Можете да деактивирате напълно режима за разработчици, дори за localhost: - -```php -$configurator->setDebugMode(false); -``` - -Обърнете внимание, че стойността `true` активира плътно режима за разработчици, което никога не трябва да се случва на производствен сървър. - - -Инструмент за отстраняване на грешки в Tracy .[#toc-debugging-tool-tracy] -========================================================================= - -За да улесним дебъгването, ще включим чудесния инструмент [Tracy |tracy:]. В режим за разработчици той визуализира грешките, а в производствен режим записва грешките в определена директория: - -```php -$configurator->enableTracy($appDir . '/log'); -``` - - -Временни файлове .[#toc-temporary-files] -======================================== - -Nette използва кеш за DI-контейнер, RobotLoader, шаблони и др. Затова е необходимо да се зададе пътят до директорията, в която се съхранява кешът: - -```php -$configurator->setTempDirectory($appDir . '/temp'); -``` - -В Linux или macOS задайте [разрешения за запис |nette:troubleshooting#Setting-Directory-Permissions] за директориите `log/` и `temp/`. - - -RobotLoader .[#toc-robotloader] -=============================== - -Обикновено искаме да заредим класовете автоматично с помощта на [RobotLoader |robot-loader:], така че трябва да го стартираме и да му позволим да зареди класовете от директорията, в която се намира `Bootstrap.php` (т.е. `__DIR__`) и всички негови поддиректории: - -```php -$configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); -``` - -Алтернативен начин е да се използва само автозадаващото устройство PSR-4 [Composer |best-practices:composer]. - - -Часова зона .[#toc-timezone] -============================ - -Конфигураторът ви позволява да зададете часовата зона за вашето приложение. - -```php -$configurator->setTimeZone('Europe/Prague'); -``` - - -Конфигурация на DI-контейнер .[#toc-di-container-configuration] -=============================================================== - -Част от процеса на изтегляне е създаването на контейнера DI, т.е. фабриката за обекти, която е сърцето на цялото приложение. Всъщност това е клас на PHP, създаден от Nette и съхраняван в директория за кеш. Фабриката създава ключовите обекти на приложението, а конфигурационните файлове я инструктират как да ги създава и конфигурира, като по този начин влияем върху поведението на цялото приложение. - -Файловете за конфигурация обикновено се записват във формат [NEON |neon:format]. Можете да прочетете [какво може да се конфигурира тук |nette:configuring]. - -.[tip] -В режим на разработка контейнерът се актуализира автоматично при всяка промяна на кода или конфигурационните файлове. В производствен режим той се генерира само веднъж и промените във файловете не се проверяват за максимална производителност. - -Файловете за конфигурация се зареждат с помощта на `addConfig()`: - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -``` - -Методът `addConfig()` може да се извика няколко пъти, за да се добавят няколко файла. - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -$configurator->addConfig($appDir . '/config/local.neon'); -if (PHP_SAPI === 'cli') { - $configurator->addConfig($appDir . '/config/cli.php'); -} -``` - -Свързването `cli.php` не е печатна грешка, конфигурацията може да бъде записана и в PHP файл, който я връща като масив. - -Като алтернатива можем да използваме [раздела `includes` |dependency-injection:configuration#Including-Files] за зареждане на конфигурационни файлове. - -Ако в конфигурационните файлове се появят елементи със същите ключове, те ще бъдат [презаписани или обединени в |dependency-injection:configuration#Merging] случай на масиви. По-късно включен файл има по-висок приоритет от предишния. Файлът, посочен в раздела `includes`, има по-висок приоритет от файловете, включени в него. - - -Статични параметри .[#toc-static-parameters] --------------------------------------------- - -Параметрите, използвани в конфигурационните файлове, могат да бъдат дефинирани [в раздела `parameters` |dependency-injection:configuration#parameters] и да бъдат взети (или презаписани) от метода `addStaticParameters()` (той има псевдоним `addParameters()`). Важно е, че различните стойности на параметрите водят до генериране на допълнителни DI-контейнери, т.е. допълнителни класове. - -```php -$configurator->addStaticParameters([ - 'projectId' => 23, -]); -``` - -Конфигурационните файлове могат да използват нормалния запис `%projectId%` за достъп до параметъра с име `projectId`. По подразбиране конфигураторът попълва следните параметри: `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` и `consoleMode`. - - -Динамични параметри .[#toc-dynamic-parameters] ----------------------------------------------- - -Възможно е също така да се добавят динамични параметри към контейнер. Различните им стойности, за разлика от статичните параметри, не генерират нови контейнери DI. - -```php -$configurator->addDynamicParameters([ - 'remoteIp' => $_SERVER['REMOTE_ADDR'], -]); -``` - -Достъпът до променливите на средата е лесен с помощта на динамични параметри. Достъпът до тях се осъществява чрез `%env.variable%` в конфигурационните файлове. - -```php -$configurator->addDynamicParameters([ - 'env' => getenv(), -]); -``` - - -Внесени услуги .[#toc-imported-services] ----------------------------------------- - -Нека се задълбочим. Въпреки че целта на контейнера DI е да създава обекти, може да се наложи в контейнера да се вмъкне съществуващ обект. Това става чрез дефиниране на услуга с атрибута `imported: true`: - -```neon -services: - myservice: - type: App\Model\MyCustomService - imported: true -``` - -Създайте нов екземпляр и го вмъкнете в Bootstrap: - -```php -$configurator->addServices([ - 'myservice' => new App\Model\MyCustomService('foobar'), -]); -``` - - -Различни среди .[#toc-different-environments] -============================================= - -Не се колебайте да персонализирате класа `Bootstrap` според нуждите си. Можете да добавите параметри към метода `boot()`, за да разделите уеб проектите, или да добавите други методи, като например `bootForTests()`, който инициализира средата за тестове на единици, `bootForCli()` за скриптове, извикани от командния ред, и т.н. - -```php -public static function bootForTests(): Configurator -{ - $configurator = self::boot(); - Tester\Environment::setup(); // Инициализация Nette Tester - return $configurator; -} -``` diff --git a/application/bg/bootstrapping.texy b/application/bg/bootstrapping.texy new file mode 100644 index 0000000000..12decd318d --- /dev/null +++ b/application/bg/bootstrapping.texy @@ -0,0 +1,297 @@ +Зареждане +********* + +
    + +Зареждането е процесът на инициализиране на средата на приложението, създаване на контейнер за инжектиране на зависимости (DI) и стартиране на приложението. Ще обсъдим: + +- как класът Bootstrap инициализира средата +- как приложенията се конфигурират чрез NEON файлове +- как да разграничаваме между производствен и разработчически режим +- как да създаваме и конфигурираме DI контейнера + +
    + + +Приложенията, независимо дали са уеб или скриптове, стартирани от командния ред, започват своята работа с някаква форма на инициализация на средата. В миналото за това отговаряше файл с име например `include.inc.php`, който първоначалният файл включваше. В съвременните Nette приложения той е заменен от клас `Bootstrap`, който като част от приложението ще намерите във файла `app/Bootstrap.php`. Може да изглежда например така: + +```php +use Nette\Bootstrap\Configurator; + +class Bootstrap +{ + private Configurator $configurator; + private string $rootDir; + + public function __construct() + { + $this->rootDir = dirname(__DIR__); + // Конфигураторът е отговорен за настройката на средата на приложението и сървисите. + $this->configurator = new Configurator; + // Задава директорията за временни файлове, генерирани от Nette (напр. компилирани шаблони) + $this->configurator->setTempDirectory($this->rootDir . '/temp'); + } + + public function bootWebApplication(): Nette\DI\Container + { + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); + } + + private function initializeEnvironment(): void + { + // Nette е умно и режимът за разработка се включва автоматично, + // или можете да го разрешите за конкретен IP адрес, като разкоментирате следния ред: + // $this->configurator->setDebugMode('secret@23.75.345.200'); + + // Активира Tracy: ултимативният "швейцарски нож" за дебъгване. + $this->configurator->enableTracy($this->rootDir . '/log'); + + // RobotLoader: автоматично зарежда всички класове в избраната директория + $this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); + } + + private function setupContainer(): void + { + // Зарежда конфигурационните файлове + $this->configurator->addConfig($this->rootDir . '/config/common.neon'); + } +} +``` + + +index.php +========= + +Първоначалният файл в случай на уеб приложения е `index.php`, който се намира в [публичната директория |directory-structure#Публична директория www] `www/`. Той изисква от клас Bootstrap да инициализира средата и да създаде DI контейнер. След това от него получава сървиса `Application`, който стартира уеб приложението: + +```php +$bootstrap = new App\Bootstrap; +// Инициализация на средата + създаване на DI контейнер +$container = $bootstrap->bootWebApplication(); +// DI контейнерът създава обект Nette\Application\Application +$application = $container->getByType(Nette\Application\Application::class); +// Стартиране на приложението Nette и обработка на входящата заявка +$application->run(); +``` + +Както се вижда, с настройката на средата и създаването на dependency injection (DI) контейнер помага класът [api:Nette\Bootstrap\Configurator], който сега ще разгледаме по-подробно. + + +Режим за разработка срещу продукционен режим +============================================ + +Nette се държи различно в зависимост от това дали работи на сървър за разработка или на продукционен сървър: + +🛠️ Режим за разработка (Development): + - Показва Tracy debugbar с полезна информация (SQL заявки, време за изпълнение, използвана памет) + - При грешка показва подробна страница за грешка с извиквания на функции и съдържание на променливи + - Автоматично обновява кеша при промяна на Latte шаблони, редактиране на конфигурационни файлове и т.н. + + +🚀 Продукционен режим (Production): + - Не показва никаква информация за дебъгване, всички грешки се записват в лога + - При грешка показва ErrorPresenter или обща страница "Server Error" + - Кешът никога не се обновява автоматично! + - Оптимизиран за скорост и сигурност + + +Изборът на режим се извършва чрез автодетекция, така че обикновено не е необходимо нищо да се конфигурира или ръчно да се превключва: + +- режим за разработка: на localhost (IP адрес `127.0.0.1` или `::1`), ако няма прокси (т.е. неговия HTTP хедър) +- продукционен режим: навсякъде другаде + +Ако искаме да разрешим режима за разработка и в други случаи, например за програмисти, достъпващи от конкретен IP адрес, използваме `setDebugMode()`: + +```php +$this->configurator->setDebugMode('23.75.345.200'); // може да се посочи и масив от IP адреси +``` + +Определено препоръчваме да комбинирате IP адрес с бисквитка. В бисквитката `nette-debug` ще запазим таен токен, напр. `secret1234`, и по този начин ще активираме режима за разработка за програмисти, достъпващи от конкретен IP адрес и същевременно имащи споменатия токен в бисквитката: + +```php +$this->configurator->setDebugMode('secret1234@23.75.345.200'); +``` + +Можем също така да изключим напълно режима за разработка, дори и за localhost: + +```php +$this->configurator->setDebugMode(false); +``` + +Внимание, стойността `true` включва режима за разработка принудително, което никога не трябва да се случва на продукционен сървър. + + +Инструмент за дебъгване Tracy +============================= + +За лесно дебъгване ще включим и страхотния инструмент [Tracy |tracy:]. В режим за разработка той визуализира грешките, а в продукционен режим ги записва в лога в посочената директория: + +```php +$this->configurator->enableTracy($this->rootDir . '/log'); +``` + + +Временни файлове +================ + +Nette използва кеш за DI контейнер, RobotLoader, шаблони и т.н. Затова е необходимо да се зададе път до директорията, където ще се съхранява кешът: + +```php +$this->configurator->setTempDirectory($this->rootDir . '/temp'); +``` + +На Linux или macOS задайте на директориите `log/` и `temp/` [права за запис |nette:troubleshooting#Настройка на правата на директориите]. + + +RobotLoader +=========== + +Обикновено ще искаме автоматично да зареждаме класове с помощта на [RobotLoader |robot-loader:], затова трябва да го стартираме и да го накараме да зарежда класове от директорията, където се намира `Bootstrap.php` (т.е. `__DIR__`), и всички поддиректории: + +```php +$this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); +``` + +Алтернативен подход е да оставите класовете да се зареждат само чрез [Composer |best-practices:composer] при спазване на PSR-4. + + +Часова зона +=========== + +Чрез конфигуратора можете да зададете подразбиращата се часова зона. + +```php +$this->configurator->setTimeZone('Europe/Prague'); +``` + + +Конфигурация на DI контейнера +============================= + +Част от процеса на зареждане е създаването на DI контейнер или фабрика за обекти, което е сърцето на цялото приложение. Всъщност това е PHP клас, който Nette генерира и съхранява в директорията с кеша. Фабриката произвежда ключови обекти на приложението и с помощта на конфигурационни файлове я инструктираме как да ги създава и настройва, като по този начин влияем на поведението на цялото приложение. + +Конфигурационните файлове обикновено се записват във формат [NEON |neon:format]. В отделна глава ще научите [какво всичко може да се конфигурира |nette:configuring]. + +.[tip] +В режим за разработка контейнерът се актуализира автоматично при всяка промяна на кода или конфигурационните файлове. В продукционен режим той се генерира само веднъж и промените не се проверяват заради максимална производителност. + +Конфигурационните файлове зареждаме с помощта на `addConfig()`: + +```php +$this->configurator->addConfig($this->rootDir . '/config/common.neon'); +``` + +Ако искаме да добавим повече конфигурационни файлове, можем да извикаме функцията `addConfig()` няколко пъти. + +```php +$configDir = $this->rootDir . '/config'; +$this->configurator->addConfig($configDir . '/common.neon'); +$this->configurator->addConfig($configDir . '/services.neon'); +if (PHP_SAPI === 'cli') { + $this->configurator->addConfig($configDir . '/cli.php'); +} +``` + +Името `cli.php` не е грешка, конфигурацията може да бъде записана и в PHP файл, който я връща като масив. + +Също така можем да добавим други конфигурационни файлове в [секцията `includes` |dependency-injection:configuration#Включване на файлове]. + +Ако в конфигурационните файлове се появят елементи със същите ключове, те ще бъдат презаписани или в случай на [масиви слети |dependency-injection:configuration#Сливане]. По-късно включеният файл има по-висок приоритет от предходния. Файлът, в който е посочена секцията `includes`, има по-висок приоритет от включените в него файлове. + + +Статични параметри +------------------ + +Параметрите, използвани в конфигурационните файлове, можем да дефинираме [в секцията `parameters` |dependency-injection:configuration#Параметри] и също така да ги предаваме (или презаписваме) с метода `addStaticParameters()` (има псевдоним `addParameters()`). Важно е, че различните стойности на параметрите ще доведат до генериране на допълнителни DI контейнери, т.е. допълнителни класове. + +```php +$this->configurator->addStaticParameters([ + 'projectId' => 23, +]); +``` + +Към параметъра `projectId` може да се обърнем в конфигурацията с обичайния запис `%projectId%`. + + +Динамични параметри +------------------- + +В контейнера можем да добавим и динамични параметри, чиито различни стойности, за разлика от статичните параметри, не предизвикват генериране на нови DI контейнери. + +```php +$this->configurator->addDynamicParameters([ + 'remoteIp' => $_SERVER['REMOTE_ADDR'], +]); +``` + +Лесно можем да добавим напр. променливи на средата, към които след това можем да се обърнем в конфигурацията със записа `%env.variable%`. + +```php +$this->configurator->addDynamicParameters([ + 'env' => getenv(), +]); +``` + + +Параметри по подразбиране +------------------------- + +В конфигурационните файлове можете да използвате тези статични параметри: + +- `%appDir%` е абсолютният път до директорията с файла `Bootstrap.php` +- `%wwwDir%` е абсолютният път до директорията с входния файл `index.php` +- `%tempDir%` е абсолютният път до директорията за временни файлове +- `%vendorDir%` е абсолютният път до директорията, където Composer инсталира библиотеките +- `%rootDir%` е абсолютният път до коренната директория на проекта +- `%debugMode%` указва дали приложението е в режим на дебъгване +- `%consoleMode%` указва дали заявката е дошла през командния ред + + +Импортирани сървиси +------------------- + +Сега вече навлизаме по-дълбоко. Въпреки че смисълът на DI контейнера е да произвежда обекти, по изключение може да възникне нужда да се вмъкне съществуващ обект в контейнера. Правим това, като дефинираме сървиса с флаг `imported: true`. + +```neon +services: + myservice: + type: App\Model\MyCustomService + imported: true +``` + +И в bootstrap вмъкваме обекта в контейнера: + +```php +$this->configurator->addServices([ + 'myservice' => new App\Model\MyCustomService('foobar'), +]); +``` + + +Различна среда +============== + +Не се страхувайте да промените клас Bootstrap според вашите нужди. Към метода `bootWebApplication()` можете да добавите параметри за разграничаване на уеб проекти. Или можем да добавим други методи, например `bootTestEnvironment()`, който инициализира средата за единични тестове, `bootConsoleApplication()` за скриптове, извиквани от командния ред и т.н. + +```php +public function bootTestEnvironment(): Nette\DI\Container +{ + Tester\Environment::setup(); // инициализация на Nette Tester + $this->setupContainer(); + return $this->configurator->createContainer(); +} + +public function bootConsoleApplication(): Nette\DI\Container +{ + $this->configurator->setDebugMode(false); + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); +} +``` diff --git a/application/bg/components.texy b/application/bg/components.texy index 27b0129100..42049c0017 100644 --- a/application/bg/components.texy +++ b/application/bg/components.texy @@ -3,27 +3,27 @@
    -Компонентите са отделни обекти за многократна употреба, които поставяме на страниците. Те могат да бъдат формуляри, решетки за данни, проучвания, изобщо всичко, което има смисъл да се използва многократно. След това научаваме: +Компонентите са самостоятелни обекти за многократна употреба, които вмъкваме в страниците. Това могат да бъдат формуляри, datagrid-ове, анкети, всъщност всичко, което има смисъл да се използва многократно. Ще покажем: -- как да използвате компонентите? -- Как да ги напишем? -- Какво представляват сигналите? +- как да използваме компоненти? +- как да ги пишем? +- какво са сигналите?
    -Nette има вградена система от компоненти. По-възрастните може да си спомнят нещо подобно от Delphi или ASP.NET Web Forms. React или Vue.js са изградени на базата на нещо отдалечено подобно. В света на PHP фреймуърците обаче това е напълно уникална функция. +Nette има вградена компонентна система. Нещо подобно може да е познато на ветераните от Delphi или ASP.NET Web Forms, на нещо отдалечено подобно са базирани React или Vue.js. Въпреки това, в света на PHP фреймуърците това е уникално явление. -В същото време компонентите променят из основи начина на разработване на приложения. Можете да съставяте страници от предварително подготвени блокове. Имате ли нужда от мрежа за данни в администрацията си? Можете да го намерите в [Componette |https://componette.org/search/component], хранилището за добавки с отворен код (не само компоненти) за Nette, и просто да го вмъкнете в презентатора. +При това компонентите фундаментално влияят на подхода към създаването на приложения. Можете да сглобявате страници от предварително подготвени единици. Нуждаете се от datagrid в администрацията? Намерете го на [Componette |https://componette.org/search/component], хранилище на open-source добавки (т.е. не само компоненти) за Nette и просто го вмъкнете в презентера. -Можете да включите произволен брой компоненти в презентатора. Можете да вмъкнете други компоненти в някои компоненти. Така се създава дърво на компонентите, в което водещият е коренът. +В презентера можете да включите произволен брой компоненти. А в някои компоненти можете да вмъквате други компоненти. Така се създава компонентно дърво, чийто корен е презентерът. -Фабрични методи .[#toc-factory-methods] -======================================= +Фабрични методи +=============== -Как се поставят и впоследствие използват компонентите в презентатора? Обикновено се използват сглобяеми методи. +Как се вмъкват компоненти в презентера и след това се използват? Обикновено с помощта на фабрични методи. -Фабриката за компоненти е елегантен начин за създаване на компоненти само когато са необходими (лениво/при поискване). Магията се крие в изпълнението на метода `createComponent()`където `` - е името на компонента, който ще бъде създаден и върнат. +Фабриката за компоненти представлява елегантен начин за създаване на компоненти едва в момента, когато те са наистина необходими (lazy / on demand). Цялата магия се състои в имплементирането на метод с име `createComponent()`, където `` е името на създавания компонент, и който създава и връща компонента. ```php .{file:DefaultPresenter.php} class DefaultPresenter extends Nette\Application\UI\Presenter @@ -37,43 +37,43 @@ class DefaultPresenter extends Nette\Application\UI\Presenter } ``` -Тъй като всички компоненти се създават в отделни методи, кодът е по-чист и по-лесен за четене. +Благодарение на това, че всички компоненти се създават в отделни методи, кодът става по-прегледен. .[note] -Имената на компонентите винаги започват с малка буква, въпреки че в името на метода те се пишат с главна буква. +Имената на компонентите винаги започват с малка буква, въпреки че в името на метода се пишат с главна. -Никога не извикваме фабриките директно, те се извикват автоматично при първото използване на компонентите. Това гарантира, че компонентът се създава в точното време и само ако наистина е необходим. Ако не използваме даден компонент (например при AJAX-запитване, когато връщаме само част от страница или когато частите са кеширани), той дори няма да бъде създаден и ще спестим от производителността на сървъра. +Фабриките никога не се извикват директно, те се извикват сами в момента, когато използваме компонента за първи път. Благодарение на това компонентът се създава в правилния момент и само в случай, че е наистина необходим. Ако не използваме компонента (например при AJAX заявка, когато се пренася само част от страницата, или при кеширане на шаблона), той изобщо не се създава и спестяваме производителност на сървъра. ```php .{file:DefaultPresenter.php} -// имаме достъп до компонента и дали това е първият път, -// извиква createComponentPoll(), за да го създаде +// достъпваме компонента и ако това е за първи път, +// се извиква createComponentPoll(), която го създава $poll = $this->getComponent('poll'); // алтернативен синтаксис: $poll = $this['poll']; ``` -В шаблона можете да визуализирате компонент с помощта на тага [{control} |#Rendering]. Затова не е необходимо ръчно да предавате компоненти на шаблона. +В шаблона е възможно да се рендира компонент с помощта на тага [{control} |#Рендиране]. Затова не е необходимо ръчно да се предават компоненти в шаблона. ```latte -

    Проголосуйте, пожалуйста

    +

    Гласувайте

    {control poll} ``` -Холивудски стил .[#toc-hollywood-style] -======================================= +Hollywood style +=============== -Компонентите обикновено използват един страхотен трик, който наричаме холивудски стил. Вероятно знаете това клише, което актьорите често чуват по време на кастингите: "Не се обаждайте на нас, ние ще се обадим на вас". И това е най-важното. +Компонентите обикновено използват една свежа техника, която обичаме да наричаме Hollywood style. Със сигурност познавате крилатата фраза, която толкова често чуват участниците във филмови кастинги: „Не ни звънете, ние ще ви се обадим“. И точно за това става въпрос. -В Nette, вместо постоянно да задавате въпроси ("Подадена ли е формата?", "Валидна ли е?" или "Кликна ли някой върху този бутон?"), казвате на фреймуърка "Когато това се случи, извикайте този метод" и оставяте по-нататъшната работа върху него. Ако програмирате на JavaScript, този стил на програмиране ви е познат. Пишете функции, които се извикват при настъпване на определено събитие. И двигателят им предава съответните параметри. +В Nette, вместо постоянно да се налага да питате нещо („беше ли изпратен формулярът?“, „беше ли валиден?“ или „натисна ли потребителят този бутон?“), казвате на фреймуърка „когато това се случи, извикай този метод“ и оставяте по-нататъшната работа на него. Ако програмирате на JavaScript, този стил на програмиране ви е добре познат. Пишете функции, които се извикват, когато настъпи определено събитие. И езикът им предава съответните параметри. -Това изцяло променя начина на писане на приложения. Колкото повече задачи можете да делегирате на рамката, толкова по-малко работа ще трябва да свършите. И колкото по-малко можете да забравите. +Това напълно променя гледната точка към писането на приложения. Колкото повече задачи можете да оставите на фреймуърка, толкова по-малко работа имате вие. И толкова по-малко неща можете да пропуснете. -Как да напишем компонент .[#toc-how-to-write-a-component] -========================================================= +Пишем компонент +=============== -Под компонент обикновено разбираме потомък на класа [api:Nette\Application\UI\Control]. Самият презентатор [api:Nette\Application\UI\Presenter] също е потомък на класа `Control`. +Под понятието компонент обикновено разбираме наследник на клас [api:Nette\Application\UI\Control]. (По-точно би било да се използва терминът „controls“, но „контроли“ на български има съвсем различно значение и по-скоро се е наложило „компоненти“.) Самият презентер [api:Nette\Application\UI\Presenter] между другото също е наследник на клас `Control`. ```php .{file:PollControl.php} use Nette\Application\UI\Control; @@ -84,22 +84,22 @@ class PollControl extends Control ``` -Изобразяване на .[#toc-rendering] -================================= +Рендиране +========= -Вече знаем, че тагът `{control componentName}` се използва за визуализиране на компонент. Всъщност се извиква методът `render()` на компонента, в който ние се грижим за рендирането. Както и в презентатора, имаме шаблон [Latte |latte:] в променливата `$this->template`, на който подаваме параметри. За разлика от използването в презентатора, тук трябва да посочим файла на шаблона и да го оставим да се визуализира: +Вече знаем, че за рендиране на компонент се използва тагът `{control componentName}`. Той всъщност извиква метода `render()` на компонента, в който се грижим за рендирането. На разположение имаме, точно както в презентера, [Latte шаблон|templates] в променливата `$this->template`, на която предаваме параметри. За разлика от презентера, трябва да посочим файла с шаблона и да го накараме да се рендира: ```php .{file:PollControl.php} public function render(): void { - // въвеждаме някои параметри в шаблона + // вмъкваме в шаблона някакви параметри $this->template->param = $value; - //и го визуализирайте + // и го рендираме $this->template->render(__DIR__ . '/poll.latte'); } ``` -Тагът `{control}` ни позволява да предаваме параметри на метода `render()`: +Тагът `{control}` позволява да се предадат параметри на метода `render()`: ```latte {control poll $id, $message} @@ -112,7 +112,7 @@ public function render(int $id, string $message): void } ``` -Понякога един компонент може да се състои от няколко части, които искаме да визуализираме поотделно. За всяка от тях ще създадем различен метод за визуализация, например `renderPaginator()`: +Понякога компонентът може да се състои от няколко части, които искаме да рендираме отделно. За всяка от тях създаваме собствен метод за рендиране, тук в примера например `renderPaginator()`: ```php .{file:PollControl.php} public function renderPaginator(): void @@ -121,107 +121,107 @@ public function renderPaginator(): void } ``` -След това в шаблона го извикваме с: +А в шаблона след това го извикваме с помощта на: ```latte {control poll:paginator} ``` -Полезно е да знаете как даден таг се компилира в PHP код, за да го разберете по-добре. +За по-добро разбиране е добре да знаете как този таг се превежда на PHP. ```latte {control poll} {control poll:paginator 123, 'hello'} ``` -Това е събрано в: +се превежда като: ```php $control->getComponent('poll')->render(); $control->getComponent('poll')->renderPaginator(123, 'hello'); ``` -Методът `getComponent()` връща компонента `poll` и след това за него се извиква съответно методът `render()` или `renderPaginator()`. +Методът `getComponent()` връща компонента `poll` и върху този компонент извиква метода `render()`, респ. `renderPaginator()`, ако в тага след двоеточието е посочен друг начин на рендиране. .[caution] -Ако някъде в параметрите се използва **`=>`**, всички параметри ще бъдат обвити в масив и предадени като първи аргумент: +Внимание, ако някъде в параметрите се появи **`=>`**, всички параметри ще бъдат опаковани в масив и предадени като първи аргумент: ```latte -{control poll, id => 123, message => 'hello'} +{control poll, id: 123, message: 'hello'} ``` -съставен за: +се превежда като: ```php $control->getComponent('poll')->render(['id' => 123, 'message' => 'hello']); ``` -Изобразяване на вложен компонент: +Рендиране на подкомпонент: ```latte {control cartControl-someForm} ``` -съставени в: +се превежда като: ```php $control->getComponent("cartControl-someForm")->render(); ``` -Компонентите, както и презентаторите, автоматично предават няколко полезни променливи на шаблоните: +Компонентите, както и презентерите, предават на шаблоните няколко полезни променливи автоматично: -- `$basePath` - абсолютен URL път до главната директория (напр. `/CD-collection`). -- `$baseUrl` е абсолютният URL адрес на главната директория (напр, `http://localhost/CD-collection`) -- `$user` е обектът, който [представлява потребителя |security:authentication]. -- `$presenter` - настоящ водещ +- `$basePath` е абсолютният URL път до коренната директория (напр. `/eshop`) +- `$baseUrl` е абсолютният URL до коренната директория (напр. `http://localhost/eshop`) +- `$user` е обект [представляващ потребителя |security:authentication] +- `$presenter` е текущият презентер - `$control` е текущият компонент -- `$flashes` е списък на [съобщенията, |#flash-сообщений] изпратени по метода `flashMessage()`. +- `$flashes` масив от [съобщения |#Flash съобщения], изпратени с функцията `flashMessage()` -Сигнал .[#toc-signal] -===================== +Сигнал +====== -Вече знаем, че навигацията в приложението Nette се състои от връзки или пренасочвания към двойки `Presenter:action`. Но какво да правим, ако искаме да извършим действие само на **текущата страница**? Например, промяна на реда на сортиране на колоните в таблица; изтриване на елемент; превключване на светъл/тъмен режим; изпращане на формуляр; гласуване в анкета и т.н. +Вече знаем, че навигацията в Nette приложение се състои в свързване или пренасочване към двойки `Presenter:action`. Но какво, ако просто искаме да извършим действие на **текущата страница**? Например да променим сортирането на колони в таблица; да изтрием елемент; да превключим светъл/тъмен режим; да изпратим формуляр; да гласуваме в анкета; и т.н. -Този тип заявка се нарича сигнал. И как действията извикват методи `action()` или `render()`, сигналите извикват методи `handle()`. Докато понятието за действие (или изглед) се отнася само за презентаторите, сигналите се отнасят за всички компоненти. А оттам и за водещите, защото `UI\Presenter` е потомък на `UI\Control`. +Този вид заявки се наричат сигнали. И подобно на действията, които извикват методи `action()` или `render()`, сигналите извикват методи `handle()`. Докато понятието действие (или view) е свързано чисто само с презентерите, сигналите се отнасят до всички компоненти. И следователно и до презентерите, защото `UI\Presenter` е наследник на `UI\Control`. ```php public function handleClick(int $x, int $y): void { - // ... обработка сигнала ... + // ... обработка на сигнала ... } ``` -Връзката, която задейства сигнала, се създава както обикновено, т.е. в шаблона с атрибута `n:href` или тага `{link}`, в кода с метода `link()`. Прочетете повече в глава [Създаване на URL връзка |creating-links#Links-to-Signal]. +Връзка, която извиква сигнал, създаваме по обичайния начин, т.е. в шаблона с атрибут `n:href` или таг `{link}`, в кода с метод `link()`. Повече в главата [Създаване на URL връзки |creating-links#Връзки към сигнал]. ```latte -нажмите сюда +кликнете тук ``` -Сигналът се извиква винаги за текущия презентатор и изглед, така че не е възможно да се свърже сигналът с друг презентатор/действие. +Сигналът винаги се извиква на текущия презентер и действие, не е възможно да се извика на друг презентер или друго действие. -Така че сигналът кара страницата да се презареди по същия начин, както при първоначалната заявка, само че в допълнение се извиква методът за обработка на сигнала със съответните параметри. Ако не съществува такъв метод, на адрес [api:Nette\Application\UI\BadSignalException] се хвърля изключение, което се показва на потребителя като страница за грешка 403 Forbidden. +Сигналът следователно предизвиква презареждане на страницата точно както при първоначалната заявка, само че допълнително извиква обслужващия метод на сигнала със съответните параметри. Ако методът не съществува, се хвърля изключение [api:Nette\Application\UI\BadSignalException], което се показва на потребителя като страница за грешка 403 Forbidden. -Извадки и AJAX .[#toc-snippets-and-ajax] -======================================== +Снипети и AJAX +============== -Сигналите може да ви напомнят малко за AJAX: обработващи програми, които се извикват на текущата страница. И сте прави, сигналите наистина често се извикват с AJAX, а след това предаваме на браузъра само променените части на страницата. Те се наричат фрагменти. Можете да намерите повече информация на [страницата за AJAX |ajax]. +Сигналите може би малко ви напомнят на AJAX: хендлъри, които се извикват на текущата страница. И сте прави, сигналите наистина често се извикват с помощта на AJAX и след това предаваме на браузъра само променените части от страницата. Или т.нар. снипети. Повече информация ще намерите на [страницата, посветена на AJAX |ajax]. -Светкавични съобщения .[#toc-flash-messages] -============================================ +Flash съобщения +=============== -Компонентът разполага със собствено хранилище за светкавични съобщения, независимо от водещия. Това са съобщения, които например ви информират за резултата от дадена транзакция. Важната характеристика на флаш съобщенията е, че те са налични в шаблона дори след пренасочване. Дори след като бъдат показани, те ще останат живи още 30 секунди - например в случай, че потребителят неволно опресни страницата - съобщението няма да бъде изгубено. +Компонентът има собствено хранилище за flash съобщения, независимо от презентера. Това са съобщения, които например информират за резултата от операция. Важна характеристика на flash съобщенията е, че те са достъпни в шаблона и след пренасочване. Дори след показване остават активни още 30 секунди – например в случай, че поради грешка при прехвърлянето потребителят обнови страницата - съобщението няма да изчезне веднага. -Изпращането се извършва чрез метода [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Първият параметър е текстът на съобщението или обектът `stdClass`, представляващ съобщението. Вторият незадължителен параметър е неговият тип (грешка, предупреждение, информация и т.н.). Методът `flashMessage()` връща екземпляр на флаш съобщение като stdClass обект, на който може да се предаде информация. +Изпращането се извършва от метода [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Първият параметър е текстът на съобщението или обект `stdClass`, представляващ съобщението. Незадължителният втори параметър е неговият тип (error, warning, info и др.). Методът `flashMessage()` връща инстанция на flash съобщението като обект `stdClass`, към който могат да се добавят допълнителни информации. ```php -$this->flashMessage('Артикулът е премахнат.'); -$this->redirect(/* ... */); // пренасочване +$this->flashMessage('Елементът беше изтрит.'); +$this->redirect(/* ... */); // и пренасочваме ``` -В шаблона тези съобщения са налични в променливата `$flashes` като обекти `stdClass`, които съдържат свойства `message` (текст на съобщението), `type` (тип на съобщението) и могат да съдържат вече споменатата информация за потребителя. Показваме ги по следния начин: +В шаблона тези съобщения са достъпни в променливата `$flashes` като обекти `stdClass`, които съдържат свойства `message` (текст на съобщението), `type` (тип на съобщението) и могат да съдържат вече споменатите потребителски информации. Рендираме ги например така: ```latte {foreach $flashes as $flash} @@ -230,44 +230,66 @@ $this->redirect(/* ... */); // пренасочване ``` -Постоянни параметри .[#toc-persistent-parameters] -================================================= +Пренасочване след сигнал +======================== -Постоянните параметри се използват за поддържане на състоянието на компонентите между различните заявки. Тяхната стойност остава същата дори след щракване върху връзката. За разлика от данните за сесията, те се прехвърлят в URL адреса. И се прехвърлят автоматично, включително връзките, създадени в други компоненти на същата страница. +След обработка на сигнала на компонента често следва пренасочване. Това е подобна ситуация като при формулярите - след тяхното изпращане също пренасочваме, за да не се изпратят данните отново при обновяване на страницата в браузъра. -Например, имате компонент за страниране на съдържание. На една страница може да има няколко такива компонента. И искате всички компоненти да останат на текущата си страница, когато щракнете върху връзката. Затова правим номера на страницата (`page`) постоянен параметър. +```php +$this->redirect('this') // пренасочва към текущия презентер и действие +``` + +Тъй като компонентът е елемент за многократна употреба и обикновено не трябва да има пряка връзка с конкретни презентери, методите `redirect()` и `link()` автоматично интерпретират параметъра като сигнал на компонента: + +```php +$this->redirect('click') // пренасочва към сигнала 'click' на същия компонент +``` + +Ако трябва да пренасочите към друг презентер или действие, можете да го направите чрез презентера: + +```php +$this->getPresenter()->redirect('Product:show'); // пренасочва към друг презентер/действие +``` + + +Персистентни параметри +====================== + +Персистентните параметри служат за поддържане на състоянието в компонентите между различни заявки. Тяхната стойност остава същата и след кликване върху връзка. За разлика от данните в сесията, те се пренасят в URL. И това става напълно автоматично, включително за връзки, създадени в други компоненти на същата страница. + +Имате например компонент за пагиниране на съдържание. Такива компоненти могат да бъдат няколко на страницата. И искаме след кликване върху връзка всички компоненти да останат на своята текуща страница. Затова от номера на страницата (`page`) ще направим персистентен параметър. -Създаването на постоянен параметър е изключително лесно в Nette. Просто създайте публично свойство и го маркирайте с атрибута: (преди се използваше `/** @persistent */` ) +Създаването на персистентен параметър в Nette е изключително лесно. Достатъчно е да създадете публично свойство и да го маркирате с атрибут: (преди се използваше `/** @persistent */`) ```php -use Nette\Application\Attributes\Persistent; // този ред е важен +use Nette\Application\Attributes\Persistent; // този ред е важен class PaginatingControl extends Control { #[Persistent] - public int $page = 1; // трябва да бъдат публични + public int $page = 1; // трябва да е public } ``` -Препоръчваме ви да включите типа данни (например `int`) към свойството, като можете да включите и стойност по подразбиране. Стойностите на параметрите могат да бъдат [валидирани |#Validation of Persistent Parameters]. +При свойството препоръчваме да посочите и типа данни (напр. `int`) и можете да посочите и стойност по подразбиране. Стойностите на параметрите могат да бъдат [валидирани |#Валидация на персистентни параметри]. -Можете да променяте стойността на постоянен параметър, когато създавате връзка: +При създаване на връзка може да се промени стойността на персистентния параметър: ```latte next ``` -Или може да бъде *ресетнат*, т.е. да бъде премахнат от URL адреса. След това той ще приеме стойността си по подразбиране: +Или може да бъде *ресетнат*, т.е. премахнат от URL. Тогава ще приеме своята стойност по подразбиране: ```latte reset ``` -Постоянни компоненти .[#toc-persistent-components] -================================================== +Персистентни компоненти +======================= -Не само параметрите, но и компонентите могат да бъдат постоянни. Техните постоянни параметри се предават и между различни действия или между различни водещи. Маркираме постоянните компоненти с тази анотация за класа на презентатора. Например, тук обозначаваме компонентите `calendar` и `poll` по следния начин: +Не само параметрите, но и компонентите могат да бъдат персистентни. При такъв компонент неговите персистентни параметри се пренасят и между различни действия на презентера или между няколко презентера. Персистентните компоненти маркираме с анотация при класа на презентера. Например така маркираме компонентите `calendar` и `poll`: ```php /** @@ -278,9 +300,9 @@ class DefaultPresenter extends Nette\Application\UI\Presenter } ``` -Не е необходимо да отбелязвате подкомпонентите като постоянни, те стават постоянни автоматично. +Подкомпонентите вътре в тези компоненти не е необходимо да се маркират, те също стават персистентни. -В PHP 8 можете също така да използвате атрибути, за да маркирате постоянни компоненти: +В PHP 8 можете да използвате и атрибути за маркиране на персистентни компоненти: ```php use Nette\Application\Attributes\Persistent; @@ -292,18 +314,18 @@ class DefaultPresenter extends Nette\Application\UI\Presenter ``` -Компоненти със зависимости .[#toc-components-with-dependencies] -=============================================================== +Компоненти със зависимости +========================== -Как да създадете компоненти със зависимости, без да "объркате" презентаторите, които ще ги използват? Благодарение на интелигентните функции на DI-контейнерите в Nette, както и при традиционните услуги, можем да оставим по-голямата част от работата на рамката. +Как да създаваме компоненти със зависимости, без да „замърсяваме“ презентерите, които ще ги използват? Благодарение на умните свойства на DI контейнера в Nette, както при използването на класически сървиси, можем да оставим по-голямата част от работата на фреймуърка. -Нека вземем за пример компонент, който има зависимост от услугата `PollFacade`: +Да вземем за пример компонент, който има зависимост от сървиса `PollFacade`: ```php class PollControl extends Control { public function __construct( - private int $id, // Id опроса, для которого создается компонент + private int $id, // Id на анкетата, за която създаваме компонента private PollFacade $facade, ) { } @@ -316,11 +338,11 @@ class PollControl extends Control } ``` -Ако пишехме класическа услуга, нямаше да има за какво да се притесняваме. Контейнерът DI безпроблемно ще се погрижи за предаването на всички зависимости. Обикновено обаче работим с компоненти, като създаваме нова тяхна инстанция директно в презентатора чрез [фабрични методи |#factory methods] `createComponent...()`. Но предаването на всички зависимости на всички компоненти на водещия, за да ги предаде след това на компонентите, е тромаво. И количеството написан код... +Ако пишехме класически сървис, нямаше да има какво да се решава. За предаването на всички зависимости невидимо щеше да се погрижи DI контейнерът. Но с компонентите обикновено постъпваме така, че създаваме нова инстанция директно в презентера в [фабричните методи |#Фабрични методи] `createComponent…()`. Но да предаваме всички зависимости на всички компоненти в презентера, за да ги предадем след това на компонентите, е тромаво. И колко написан код… -Логичен въпрос: защо просто не регистрираме компонента като класическа услуга, не го предадем на презентатора и не го върнем в метода `createComponent...()`? Но този подход е неподходящ, тъй като искаме да можем да създаваме компонента многократно. +Логичният въпрос е защо просто не регистрираме компонента като класически сървис, не го предадем на презентера и след това в метода `createComponent…()` не го връщаме? Такъв подход обаче е неподходящ, защото искаме да имаме възможност да създаваме компонента дори няколко пъти. -Правилното решение е да напишем фабрика за компонента, т.е. клас, който създава компонента вместо нас: +Правилното решение е да напишем за компонента фабрика, т.е. клас, който ще ни създаде компонента: ```php class PollControlFactory @@ -337,17 +359,17 @@ class PollControlFactory } ``` -Сега регистрираме нашата услуга в DI-контейнера за конфигуриране: +Така регистрираме фабриката в нашия контейнер в конфигурацията: ```neon services: - PollControlFactory ``` -Накрая ще използваме тази фабрика в нашия презентатор: +и накрая я използваме в нашия презентер: ```php -class PollPresenter extends Nette\UI\Application\Presenter +class PollPresenter extends Nette\Application\UI\Presenter { public function __construct( private PollControlFactory $pollControlFactory, @@ -356,13 +378,13 @@ class PollPresenter extends Nette\UI\Application\Presenter protected function createComponentPollControl(): PollControl { - $pollId = 1; // ние можем да предадем нашия параметър + $pollId = 1; // можем да си предадем нашия параметър return $this->pollControlFactory->create($pollId); } } ``` -Чудесното е, че Nette DI може да [генерира |dependency-injection:factory] такива прости фабрики, така че вместо да пишете целия код, трябва само да напишете нейния интерфейс: +Страхотно е, че Nette DI може да [генерира |dependency-injection:factory] такива прости фабрики, така че вместо целия й код е достатъчно да напишем само нейния интерфейс: ```php interface PollControlFactory @@ -371,21 +393,21 @@ interface PollControlFactory } ``` -Това е всичко. Nette имплементира вътрешно този интерфейс и го предава на нашия презентатор, където можем да го използваме. Той също така магически предава нашия параметър `$id` и инстанция на класа `PollFacade` на нашия компонент. +И това е всичко. Nette вътрешно ще имплементира този интерфейс и ще го предаде на презентера, където вече можем да го използваме. Магически ще добави към нашия компонент и параметъра `$id` и инстанция на класа `PollFacade`. -Компоненти в дълбочина .[#toc-components-in-depth] -================================================== +Компоненти в дълбочина +====================== -Компонентите в Nette Application са части за многократна употреба на уеб приложение, които вграждаме в страници, което е предмет на тази глава. Какви са възможностите на такъв компонент? +Компонентите в Nette Application представляват части от уеб приложението за многократна употреба, които вмъкваме в страниците и на които всъщност е посветена цялата тази глава. Какви точно възможности има такъв компонент? -1) може да се показва в шаблон -2) той знае каква част от себе си да покаже по време на [заявката AJAX |ajax#invalidation] (фрагменти) -3) има възможност да съхранява състоянието си в URL (постоянни параметри). -4) има възможност да реагира на действията (сигналите) на потребителя. -5) създава йерархична структура (където коренът е главният). +1) може да се рендира в шаблон +2) знае [коя своя част |ajax#Снипети] трябва да рендира при AJAX заявка (снипети) +3) има способността да съхранява своето състояние в URL (персистентни параметри) +4) има способността да реагира на потребителски действия (сигнали) +5) създава йерархична структура (където коренът е презентерът) -Всяка от тези функции се обработва от един от класовете на линията на наследяване. Изобразяването (1 + 2) се обработва от [api:Nette\Application\UI\Control], включването в [жизнения цикъл |presenters#life-cycle-of-presenter] (3, 4) - от класа [api:Nette\Application\UI\Component], а създаването на йерархичната структура (5) - от класовете [Container (контейнер) и Component (компонент) |component-model:]. +Всяка от тези функции се обслужва от някой от класовете на наследствената линия. За рендирането (1 + 2) отговаря [api:Nette\Application\UI\Control], за включването в [жизнения цикъл |presenters#Жизнен цикъл на презентера] (3, 4) класът [api:Nette\Application\UI\Component], а за създаването на йерархична структура (5) класовете [Container и Component |component-model:]. ``` Nette\ComponentModel\Component { IComponent } @@ -400,18 +422,18 @@ Nette\ComponentModel\Component { IComponent } ``` -Жизнен цикъл на компонента .[#toc-life-cycle-of-component] ----------------------------------------------------------- +Жизнен цикъл на компонента +-------------------------- -[* lifecycle-component.svg *] *** * Жизнен цикъл на компонента* .<> +[* lifecycle-component.svg *] *** *Жизнен цикъл на компонента* .<> -Утвърждаване на постоянни параметри .[#toc-validation-of-persistent-parameters] -------------------------------------------------------------------------------- +Валидация на персистентни параметри +----------------------------------- -Стойностите на [постоянните параметри, |#persistent parameters] получени от URL адреси, се записват в свойствата чрез метода `loadState()`. Той също така проверява дали типът данни, зададен за свойството, съвпада, в противен случай ще отговори с грешка 404 и страницата няма да бъде показана. +Стойностите на [персистентните параметри |#Персистентни параметри], получени от URL, се записват в свойствата от метода `loadState()`. Той също така проверява дали съответства типът данни, посочен при свойството, в противен случай отговаря с грешка 404 и страницата не се показва. -Никога не се доверявайте сляпо на постоянните параметри, защото те лесно могат да бъдат пренаписани от потребителя в URL адреса. Например, така проверяваме дали номерът на страницата `$this->page` е по-голям от 0. Добър начин да направите това е да презапишете метода `loadState()`, споменат по-горе: +Никога не вярвайте сляпо на персистентните параметри, защото те могат лесно да бъдат презаписани от потребителя в URL. Така например ще проверим дали номерът на страницата `$this->page` е по-голям от 0. Подходящ начин е да презапишем споменатия метод `loadState()`: ```php class PaginatingControl extends Control @@ -422,7 +444,7 @@ class PaginatingControl extends Control public function loadState(array $params): void { parent::loadState($params); // тук се задава $this->page - // следва проверката на потребителската стойност: + // следва собствена проверка на стойността: if ($this->page < 1) { $this->error(); } @@ -430,27 +452,27 @@ class PaginatingControl extends Control } ``` -Противоположният процес, т.е. събирането на стойности от постоянни пропъртита, се обработва от метода `saveState()`. +Обратният процес, т.е. събирането на стойности от персистентните свойства, се извършва от метода `saveState()`. -Сигнали в дълбочина .[#toc-signals-in-depth] --------------------------------------------- +Сигнали в дълбочина +------------------- -Сигналът предизвиква презареждане на страницата, подобно на първоначалната заявка (с изключение на AJAX), и извиква метода `signalReceived($signal)`, чиято реализация по подразбиране в класа `Nette\Application\UI\Component` се опитва да извика метода, състоящ се от думите `handle{Signal}`. По-нататъшната обработка зависи от този обект. Обектите, които са наследници на `Component` (т.е. `Control` и `Presenter`), се опитват да извикат `handle{Signal}` със съответните параметри. +Сигналът предизвиква презареждане на страницата точно както при първоначалната заявка (освен в случай, че е извикан с AJAX) и извиква метода `signalReceived($signal)`, чиято имплементация по подразбиране в класа `Nette\Application\UI\Component` се опитва да извика метод, съставен от думите `handle{signal}`. По-нататъшната обработка зависи от дадения обект. Обектите, които наследяват `Component` (т.е. `Control` и `Presenter`), реагират така, че се опитват да извикат метода `handle{signal}` със съответните параметри. -С други думи: взема се дефиницията на метода `handle{Signal}` и всички параметри, които са получени в заявката, се съпоставят с параметрите на метода. Това означава, че параметърът `id` от URL адреса се съпоставя с параметъра на метода `$id`, `something` с `$something` и т.н. А ако не съществува метод, тогава методът `signalReceived` хвърля [изключение |api:Nette\Application\UI\BadSignalException]. +С други думи: взема се дефиницията на функцията `handle{signal}` и всички параметри, които са дошли със заявката, и към аргументите се присвояват параметри от URL по име и се опитва да се извика даденият метод. Напр. като параметър `$id` се предава стойността от параметъра `id` в URL, като `$something` се предава `something` от URL и т.н. И ако методът не съществува, методът `signalReceived` хвърля [изключение |api:Nette\Application\UI\BadSignalException]. -Сигналът може да бъде приет от всеки компонент, главен обект, който реализира интерфейса `SignalReceiver`, ако е свързан към дървото на компонентите. +Сигнал може да приема всякакъв компонент, презентер или обект, който имплементира интерфейса `SignalReceiver` и е свързан към дървото на компонентите. -Основните получатели на сигналите са презентаторите и визуалните компоненти, които се разширяват `Control`. Сигналът е знак за даден обект, че трябва да направи нещо - анкета отчита гласа на потребителя, кутията с новини трябва да се разшири, формулярът е изпратен и трябва да обработи данните и т.н. +Сред основните получатели на сигнали ще бъдат `Presenters` и визуалните компоненти, наследяващи `Control`. Сигналът трябва да служи като знак за обекта, че трябва да направи нещо – анкетата трябва да преброи гласа от потребителя, блокът с новини трябва да се разгъне и да покаже два пъти повече новини, формулярът е изпратен и трябва да обработи данните и т.н. -URL адресът на сигнала се създава чрез метода [Component::link() |api:Nette\Application\UI\Component::link()]. Последователността `{signal}!` се предава като параметър `$destination`, а масивът от аргументи, който искаме да предадем на обработчика на сигнали, се предава като `$args`. Параметрите на сигнала са свързани с URL адреса на текущия водещ/представител. **Параметърът `?do` в URL адреса определя сигнала, който ще бъде извикан. +URL за сигнал създаваме с помощта на метода [Component::link() |api:Nette\Application\UI\Component::link()]. Като параметър `$destination` предаваме низ `{signal}!` и като `$args` масив от аргументи, които искаме да предадем на сигнала. Сигналът винаги се извиква на текущия презентер и действие с текущите параметри, параметрите на сигнала само се добавят. Освен това в началото се добавя **параметър `?do`, който определя сигнала**. -Форматът му е `{signal}` или `{signalReceiver}-{signal}`. `{signalReceiver}` - е името на компонента в презентатора. Ето защо в името на компонента не може да има тире (неточна чертичка) - то се използва за отделяне на името на компонента от сигнала, но могат да се съставят няколко компонента. +Неговият формат е или `{signal}`, или `{signalReceiver}-{signal}`. `{signalReceiver}` е името на компонента в презентера. Затова в името на компонента не може да има тире – използва се за разделяне на името на компонента и сигнала, но е възможно така да се вложат няколко компонента. -Методът [isSignalReceiver() |api:Nette\Application\UI\Presenter::isSignalReceiver()] проверява дали компонентът (първи аргумент) е приемник на сигнал (втори аргумент). Вторият аргумент може да бъде пропуснат - тогава се установява дали компонентът е приемник на някакъв сигнал. Ако вторият аргумент е `true`, се проверява дали компонентът или неговите наследници са приемници на сигнали. +Методът [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] проверява дали компонентът (първи аргумент) е получател на сигнала (втори аргумент). Вторият аргумент можем да пропуснем – тогава се проверява дали компонентът е получател на какъвто и да е сигнал. Като втори параметър може да се посочи `true` и така да се провери дали получател е не само посоченият компонент, но и който и да е негов наследник. -Във всяка фаза преди `handle{Signal}`, сигналът може да бъде изпълнен ръчно чрез извикване на метода [processSignal() |api:Nette\Application\UI\Presenter::processSignal()], който поема отговорността за изпълнението на сигнала. Той приема компонента на приемника (ако не е зададен, това е самият водещ) и му изпраща сигнала. +Във всяка фаза, предхождаща `handle{signal}`, можем да изпълним сигнала ръчно, като извикаме метода [processSignal()|api:Nette\Application\UI\Presenter::processSignal()], който поема отговорността за обработката на сигнала – взема компонента, който е определен като получател на сигнала (ако не е определен получател на сигнала, това е самият презентер) и му изпраща сигнала. Пример: @@ -460,4 +482,4 @@ if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, ' } ``` -Сигналът е изпълнен преждевременно и няма да бъде извикан отново. +Така сигналът е изпълнен преждевременно и вече няма да се извиква отново. diff --git a/application/bg/configuration.texy b/application/bg/configuration.texy index 339f17506d..f1e952fd3a 100644 --- a/application/bg/configuration.texy +++ b/application/bg/configuration.texy @@ -1,58 +1,71 @@ -Настройване на приложението -*************************** +Конфигурация на приложения +************************** .[perex] -Преглед на опциите за конфигуриране на приложението Nette. +Преглед на конфигурационните опции за Nette приложения. -Приложение .[#toc-application] -============================== +Application +=========== ```neon application: - # показва раздела "Nette Application" на синия екран на Tracy? + # показва ли се панелът "Nette Application" в Tracy BlueScreen? debugger: ... # (bool) по подразбиране е true - # ще бъде ли извикан презентаторът на грешки в случай на грешка? - catchExceptions: ... # (bool) по подразбиране е true на "battle" сървър + # ще се извиква ли error-presenter при грешка? + # има ефект само в режим на разработка + catchExceptions: ... # (bool) по подразбиране е true - # име на водещ на грешка - errorPresenter: Error # (string) по подразбиране е 'Nette:Error' + # име на error-presenter + errorPresenter: Error # (string|array) по подразбиране е 'Nette:Error' - # дефинира правила за съпоставяне на името на водещия с клас + # дефинира псевдоними за презентери и действия + aliases: ... + + # дефинира правила за превод на името на презентера в клас mapping: ... - # дали лошите връзки предизвикват предупреждения? - # валидно само в режим на разработка + # грешните връзки не генерират ли предупреждения? + # има ефект само в режим на разработка silentLinks: ... # (bool) по подразбиране е false ``` -Тъй като в режим на разработка презентаторите на грешки не се извикват по подразбиране, а грешките се показват от Tracy, промяната на стойността на `catchExceptions` на `true` помага да се провери дали презентаторите на грешки работят правилно по време на разработката. +От `nette/application` версия 3.2 може да се дефинира двойка error-presenter-и: -Опцията `silentLinks` определя как Nette да се държи в режим на разработчик, когато генерирането на връзки е неуспешно (например поради липса на презентатор и т.н.). Стойността по подразбиране `false` означава, че Nette работи `E_USER_WARNING`. Задаването на `true` потиска това съобщение за грешка. В производствена среда винаги се извиква `E_USER_WARNING`. Можем също така да повлияем на това поведение, като зададем променливата на водещия [$invalidLinkMode |creating-links#Invalid-Links]. +```neon +application: + errorPresenter: + 4xx: Error4xx # за изключение Nette\Application\BadRequestException + 5xx: Error5xx # за останалите изключения +``` -Съпоставянето [определя правилата, по които |modules#Mapping] името на класа се извежда от основното име. +Опцията `silentLinks` определя как Nette ще се държи в режим на разработка, когато генерирането на връзка се провали (например защото не съществува презентер и т.н.). Стойността по подразбиране `false` означава, че Nette ще хвърли грешка `E_USER_WARNING`. Задаването на `true` ще потисне това съобщение за грешка. В продукционна среда `E_USER_WARNING` винаги се извиква. Това поведение можем да контролираме и чрез задаване на променливата на презентера [$invalidLinkMode |creating-links#Невалидни връзки]. +[Псевдонимите опростяват свързването |creating-links#Псевдоними] към често използвани презентери. -Автоматично регистриране на водещи .[#toc-automatic-registration-of-presenters] -------------------------------------------------------------------------------- +[Мапингът дефинира правила |directory-structure#Мапиране на презентери], според които от името на презентера се извежда името на класа. -Nette автоматично добавя презентатори като услуги към контейнера DI, което значително ускорява създаването им. Може да се конфигурира начинът, по който Nette разпознава предентерите: + +Автоматична регистрация на презентери +------------------------------------- + +Nette автоматично добавя презентерите като сървиси в DI контейнера, което значително ускорява тяхното създаване. Как Nette намира презентерите може да се конфигурира: ```neon application: - # за търсене на водещи в картата на класовете в Composer? + # търси ли презентери в Composer class map? scanComposer: ... # (bool) по подразбиране е true - # маска, която трябва да съответства на класа и името на файла + # маска, на която трябва да отговарят името на класа и файла scanFilter: ... # (string) по подразбиране е '*Presenter' - # в кои директории трябва да се търсят презентаторите? - scanDirs: # (string[]|false) по подразбиране '%appDir%' + # в кои директории да се търсят презентери? + scanDirs: # (string[]|false) по подразбиране е '%appDir%' - %vendorDir%/mymodule ``` -Директориите, изброени в `scanDirs`, не заместват подразбиращата се `%appDir%`, а я допълват, така че `scanDirs` ще съдържа и `%appDir%`, и `%vendorDir%/mymodule`. За да презапишем директорията по подразбиране, използваме [възклицателен знак |dependency-injection:configuration#Merging]: +Директориите, посочени в `scanDirs`, не презаписват стойността по подразбиране `%appDir%`, а я допълват, така че `scanDirs` ще съдържа и двата пътя `%appDir%` и `%vendorDir%/mymodule`. Ако искаме да пропуснем директорията по подразбиране, използваме [удивителен знак |dependency-injection:configuration#Сливане], който презаписва стойността: ```neon application: @@ -60,64 +73,73 @@ application: - %vendorDir%/mymodule ``` -Сканирането на директории може да бъде деактивирано чрез задаване на `false`. Не препоръчваме автоматичното добавяне на презентатори да се прекратява напълно, защото в противен случай производителността на приложението ще намалее. +Сканирането на директории може да се изключи, като се посочи стойност false. Не препоръчваме напълно да се потиска автоматичното добавяне на презентери, защото в противен случай ще се намали производителността на приложението. -Latte .[#toc-latte] -=================== +Шаблони Latte +============= -Тази настройка влияе глобално върху поведението на Latte в компонентите и презентаторите. +С тази настройка може глобално да се повлияе на поведението на Latte в компоненти и презентери. ```neon latte: - # показва раздела Latte в панела Tracy за основния шаблон (true) или за всички компоненти (all)? + # показва ли се панелът Latte в Tracy Bar за основния шаблон (true) или за всички компоненти (all)? debugger: ... # (true|false|'all') по подразбиране е true - # генерира шаблони с declare(strict_types=1) + # генерира шаблони с хедър declare(strict_types=1) strictTypes: ... # (bool) по подразбиране е false - # клас $this->template - templateClass: App\MyTemplateClass # по подразбиране Nette\Bridges\ApplicationLatte\DefaultTemplate + # включва режим на [стриктен парсер |latte:develop#strict-mode] + strictParsing: ... # (bool) по подразбиране е false + + # активира [проверка на генерирания код |latte:develop#Checking Generated Code] + phpLinter: ... # (string) по подразбиране е null + + # задава locale + locale: cs_CZ # (string) по подразбиране е null + + # клас на обекта $this->template + templateClass: App\MyTemplateClass # по подразбиране е Nette\Bridges\ApplicationLatte\DefaultTemplate ``` -Ако използвате версия 3 на Latte, можете да добавите ново [разширение |latte:creating-extension], като използвате: +Ако използвате Latte версия 3, можете да добавяте нови [разширения |latte:extending-latte#Latte Extension] с помощта на: ```neon latte: - расширения: - - Latte\Essential\TranslatorExtension + extensions: + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -/--comment - - - - +Ако използвате Latte версия 2, можете да регистрирате нови тагове (макроси) или като посочите името на класа, или като референция към сървис. По подразбиране се извиква методът `install()`, но това може да се промени, като се посочи името на друг метод: +```neon +latte: + # регистрация на потребителски Latte тагове + macros: + - App\MyLatteMacros::register # статичен метод, classname или callable + - @App\MyLatteMacrosFactory # сървис с метод install() + - @App\MyLatteMacrosFactory::register # сървис с метод register() + +services: + - App\MyLatteMacrosFactory +``` - - - - -\-- - - -Маршрутизиране .[#toc-routing] -============================== +Маршрутизация +============= Основни настройки: ```neon routing: - # показва раздела Routing в панела Tracy? + # показва ли се панелът за маршрутизация в Tracy Bar? debugger: ... # (bool) по подразбиране е true - # сериализирайте маршрутите в DI-контейнера? + # сериализира рутера в DI контейнера cache: ... # (bool) по подразбиране е false ``` -Маршрутите обикновено се дефинират в класа RouterFactory. Алтернативно, в конфигурацията могат да се дефинират прости правила за маршрутизация, като се използват двойки `маска: действие`: +Маршрутизацията обикновено дефинираме в клас [RouterFactory |routing#Колекция от маршрути]. Алтернативно, маршрутите могат да се дефинират и в конфигурацията с помощта на двойки `маска: действие`, но този начин не предлага толкова широка вариативност в настройките: ```neon routing: @@ -127,28 +149,43 @@ routing: ``` -Константи .[#toc-constants] -=========================== +Константи +========= -Създаване на PHP константи: +Създаване на PHP константи. ```neon constants: Foobar: 'baz' ``` -Константата `Foobar` ще бъде създадена след стартиране. +След стартиране на приложението ще бъде създадена константата `Foobar`. .[note] -Константите не трябва да служат като глобално достъпни променливи. Използвайте [инжектиране на зависимости |dependency-injection:passing-dependencies], за да предавате стойности на обекти. +Константите не трябва да служат като някакви глобално достъпни променливи. За предаване на стойности към обекти използвайте [dependency injection |dependency-injection:passing-dependencies]. PHP === -Можете да инсталирате директиви на PHP. Преглед на всички директиви можете да намерите в [php.net |https://www.php.net/manual/ru/ini.list.php]. +Настройка на PHP директиви. Преглед на всички директиви ще намерите на [php.net |https://www.php.net/manual/en/ini.list.php]. ```neon php: date.timezone: Europe/Prague ``` + + +DI сървиси +========== + +Тези сървиси се добавят към DI контейнера: + +| Име | Тип | Описание +|---------------------------------------------------------- +| `application.application` | [api:Nette\Application\Application] | [стартер на цялото приложение |how-it-works#Nette Application] +| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] +| `application.presenterFactory` | [api:Nette\Application\PresenterFactory] | фабрика за презентери +| `application.###` | [api:Nette\Application\UI\Presenter] | отделни презентери +| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | фабрика за обект `Latte\Engine` +| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | фабрика за [`$this->template` |templates] diff --git a/application/bg/creating-links.texy b/application/bg/creating-links.texy index 659326e6fe..0c0bb9c665 100644 --- a/application/bg/creating-links.texy +++ b/application/bg/creating-links.texy @@ -3,217 +3,217 @@
    -Създаването на връзки в Nette е толкова лесно, колкото да посочите с пръст. Просто насочете курсора и системата ще свърши цялата работа вместо вас. Ние ви показваме: +Създаването на връзки в Nette е лесно като посочване с пръст. Достатъчно е само да насочите и фреймуъркът вече ще свърши цялата работа вместо вас. Ще покажем: -- Как да създавате връзки в шаблони и на други места -- как да маркирате връзка в текущата страница -- Какво да правим с невалидните връзки +- как да създаваме връзки в шаблони и другаде +- как да различим връзка към текущата страница +- какво да правим с невалидни връзки
    -Благодарение на [двупосочното маршрутизиране |routing] не се налага да кодирате URL адресите на приложенията в шаблони или код, който може да се промени по-късно или да бъде сложен за съставяне. Просто посочете презентатора и действието във връзката, подайте всички параметри и рамката сама ще генерира URL адреса. Всъщност това е много подобно на извикване на функция. Ще ви хареса. +Благодарение на [двупосочното маршрутизиране |routing] никога няма да се налага да записвате твърдо URL адреси на вашето приложение в шаблони или код, които могат по-късно да се променят, или сложно да ги сглобявате. Във връзката е достатъчно да посочите презентера и действието, да предадете евентуални параметри и фреймуъркът вече ще генерира URL сам. Всъщност е много подобно на извикването на функция. Това ще ви хареса. -В шаблона на водещия. .[#toc-in-the-presenter-template] -======================================================= +В шаблона на презентера +======================= -В повечето случаи създаваме връзки в шаблони, а атрибутът `n:href` е чудесен помощник: +Най-често създаваме връзки в шаблони и страхотен помощник е атрибутът `n:href`: ```latte -подробнее +детайл ``` -Обърнете внимание, че вместо HTML атрибута `href` сме използвали [n:атрибута |latte:syntax#n:attributes] `n:href`. Стойността му не е URL адресът, както сте свикнали да виждате в атрибута `href`, а името на водещия и действието. +Забележете, че вместо HTML атрибута `href` използвахме [n:атрибут |latte:syntax#n:атрибути] `n:href`. Неговата стойност тогава не е URL, както би било в случая с атрибута `href`, а името на презентера и действието. -Щракването върху връзката, казано по-просто, е нещо като извикване на метода `ProductPresenter::renderShow()`. И ако в сигнатурата му има параметри, можем да го извикаме с аргументи: +Кликването върху връзка е, опростено казано, нещо като извикване на метода `ProductPresenter::renderShow()`. И ако той има параметри в своята сигнатура, можем да го извикаме с аргументи: ```latte -подробнее +детайл на продукта ``` -Можем също така да предаваме именувани параметри. Следната връзка предава параметъра `lang` със стойност `en`: +Възможно е да се предават и именувани параметри. Следващата връзка предава параметъра `lang` със стойност `cs`: ```latte -подробнее +детайл на продукта ``` -Ако методът `ProductPresenter::renderShow()` няма `$lang` в сигнатурата си, той може да прочете стойността на параметъра, като използва `$lang = $this->getParameter('lang')`. +Ако методът `ProductPresenter::renderShow()` няма `$lang` в своята сигнатура, може да разбере стойността на параметъра с помощта на `$lang = $this->getParameter('lang')` или от [свойство |presenters#Параметри на заявката]. -Ако параметрите се съхраняват в масив, те могат да бъдат разширени с помощта на оператора `(expand)` (нещо като `...` в PHP, но работи с асоциативни масиви): +Ако параметрите са съхранени в масив, могат да се разгърнат с оператора `...` (в Latte 2.x с оператора `(expand)`): ```latte -{var $args = [$product->id, lang => en]} -подробнее +{var $args = [$product->id, lang => cs]} +детайл на продукта ``` -Така наречените [постоянни параметри |presenters#Persistent-Parameters] също се предават автоматично в референции. +Във връзките автоматично се предават и т.нар. [персистентни параметри |presenters#Персистентни параметри]. -Атрибутът `n:href` е много полезен за HTML тагове. ``. Ако искаме да покажем връзката на друго място, например в текста, използваме `{link}`: +Атрибутът `n:href` е много удобен за HTML тагове ``. Ако искаме да изпишем връзка другаде, например в текст, използваме `{link}`: ```latte -URL: {link Home:default} +Адресът е: {link Home:default} ``` -В кода .[#toc-in-the-code] -========================== +В кода +====== -Методът `link()` се използва за създаване на връзка в презентатора: +За създаване на връзка в презентера служи методът `link()`: ```php $url = $this->link('Product:show', $product->id); ``` -Параметрите могат да се предават и като масив, който може да съдържа именувани параметри: +Параметрите могат да се предадат и с помощта на масив, където могат да се посочат и именувани параметри: ```php $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); ``` -Връзки могат да се създават и без презентатор, като се използва [LinkGenerator |#LinkGenerator] и неговият метод `link()`. +Връзки могат да се създават и без презентер, за това е тук [#LinkGenerator] и неговият метод `link()`. -Препратки към водещия .[#toc-links-to-presenter] -================================================ +Връзки към презентер +==================== -Ако целта на връзката е да се свърже с представящия и действието, тя има следния синтаксис: +Ако целта на връзката е презентер и действие, тя има следния синтаксис: ``` [//] [[[[:]module:]presenter:]action | this] [#fragment] ``` -Форматът се поддържа от всички тагове на Latte и всички методи на презентатора, които работят с връзки, т.е. `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()`, както и [LinkGenerator |#LinkGenerator]. Следователно, дори ако в примерите се използва `n:href`, тук може да се използва всяка от функциите. +Форматът се поддържа от всички тагове на Latte и всички методи на презентера, които работят с връзки, т.е. `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` и също [#LinkGenerator]. Така че, дори ако в примерите е използван `n:href`, там може да бъде която и да е от функциите. -Следователно основната форма е `Presenter:action`: +Основната форма е следователно `Presenter:action`: ```latte -главная страница +начална страница ``` -Ако се позоваваме на действието на текущия водещ, можем да пропуснем името му: +Ако свързваме към действие на текущия презентер, можем да пропуснем неговото име: ```latte -главная страница +начална страница ``` -Ако действието е `default`, можем да го пропуснем, но двоеточието трябва да остане: +Ако целта е действието `default`, можем да го пропуснем, но двоеточието трябва да остане: ```latte -главная страница +начална страница ``` -Връзките могат да сочат и към други [модули |modules]. Тук връзките се разграничават на относителни към подмодули или абсолютни. Принципът е подобен на дисковите пътища, само че с двоеточия вместо с наклонени черти. Да предположим, че водещият е част от модул `Front`, тогава записваме: +Връзките могат също да сочат към други [модули |directory-structure#Презентери и шаблони]. Тук връзките се разграничават на относителни към вложен подмодул или абсолютни. Принципът е аналогичен на пътищата на диска, само че вместо наклонени черти има двоеточия. Да предположим, че текущият презентер е част от модула `Front`, тогава ще запишем: ```latte -ссылка на Front:Shop:Product:show -ссылка на Admin:Product:show +връзка към Front:Shop:Product:show +връзка към Admin:Product:show ``` -Специален случай е [самореференцията |#Link-to-Current-Page]. Тук ще напишем `this` като цел. +Специален случай е връзка [към себе си |#Връзка към текущата страница], когато като цел посочим `this`. ```latte -refresh +обнови ``` -Можем да направим връзка към определена част от HTML страницата чрез така наречения фрагмент след символа хеш `#`: +Можем да свързваме към определена част от страницата чрез т.нар. фрагмент след знака диез `#`: ```latte -ссылка на Home:default и фрагмент #main +връзка към Home:default и фрагмент #main ``` -Абсолютни пътища .[#toc-absolute-paths] -======================================= +Абсолютни пътища +================ -Връзките, генерирани от `link()` или `n:href`, винаги са абсолютни пътища (т.е. започват с `/`), но не и абсолютни URL адреси с протокол и домейн, като `https://domain`. +Връзките, генерирани с помощта на `link()` или `n:href`, са винаги абсолютни пътища (т.е. започват със знак `/`), но не и абсолютни URL с протокол и домейн като `https://domain`. -За да създадете абсолютен URL адрес, добавете две наклонени черти в началото (например `n:href="//Home:"`). Или можете да превключите презентатора да генерира само абсолютни връзки, като зададете `$this->absoluteUrls = true`. +За да генерирате абсолютен URL, добавете в началото две наклонени черти (напр. `n:href="//Home:"`). Или може да превключите презентера да генерира само абсолютни връзки, като зададете `$this->absoluteUrls = true`. -Връзка към текущата страница .[#toc-link-to-current-page] -========================================================= +Връзка към текущата страница +============================ -Целта `this` ще създаде връзка към текущата страница: +Целта `this` създава връзка към текущата страница: ```latte -обновить +обнови ``` -Това ще предаде всички параметри, посочени в сигнатурата на метода. `render()` или `action()`. Така че, ако сме на `Product:show` и `id:123`, връзката към `this` също ще предаде този параметър. +Същевременно се пренасят и всички параметри, посочени в сигнатурата на метода `action()` или `render()`, ако `action()` не е дефинирана. Така че, ако сме на страницата `Product:show` и `id: 123`, връзката към `this` ще предаде и този параметър. -Разбира се, можете да зададете параметрите и директно: +Разбира се, възможно е параметрите да се специфицират директно: ```latte -обновить +обнови ``` -Методът на водещия `isLinkCurrent()` определя дали целта на връзката съвпада с текущата страница. Това може да се използва например в шаблон за разграничаване на връзки и т.н. +Функцията `isLinkCurrent()` проверява дали целта на връзката е същата като текущата страница. Това може да се използва например в шаблон за разграничаване на връзки и др. -Параметрите са същите като при метода `link()`, но можете да използвате и символа `*` вместо конкретно действие, което означава всяко действие на водещия. +Параметрите са същите като при метода `link()`, но освен това е възможно вместо конкретно действие да се посочи заместващ знак `*`, който означава всяко действие на дадения презентер. ```latte -{if !$presenter->isLinkCurrent('Admin:login')} - Войти +{if !isLinkCurrent('Admin:login')} + Влезте {/if} -
  • +
  • ...
  • ``` -Съкратената форма може да се използва в комбинация с `n:href` в един и същ елемент: +В комбинация с `n:href` в един елемент може да се използва съкратена форма: ```latte -... +... ``` -Заместващият символ `*` замества само действието на презентатора, а не самия презентатор. +Заместващият знак `*` може да се използва само вместо действие, а не презентер. -За да разберем дали се намираме в определен модул или негов подмодул, можем да използваме метода `$presenter->isModuleCurrent(moduleName)`. +За да проверим дали сме в определен модул или негов подмодул, използваме метода `isModuleCurrent(moduleName)`. ```latte -
  • +
  • ...
  • ``` -Връзки към сигнала .[#toc-links-to-signal] -========================================== +Връзки към сигнал +================= -Целта на препратката може да бъде не само водещ и действие, но и [сигнал |components#Signal] (те извикват метод `handle()`). Синтаксисът е следният: +Целта на връзката не трябва да бъде само презентер и действие, но и [сигнал |components#Сигнал] (извикват метода `handle()`). Тогава синтаксисът е следният: ``` [//] [sub-component:]signal! [#fragment] ``` -Поради това сигналът е подчертан с възклицателен знак: +Сигналът следователно се отличава с удивителен знак: ```latte -signal +сигнал ``` -Можете също така да създадете препратка към сигнал на подкомпонент (или подкомпонент): +Може да се създаде и връзка към сигнал на подкомпонент (или под-подкомпонент): ```latte -signal +сигнал ``` -Връзки към компонентите .[#toc-links-in-component] -================================================== +Връзки в компонент +================== -Тъй като [компонентите |components] са отделни единици за многократна употреба, които не трябва да имат никаква връзка с околните презентатори, препратките работят по малко по-различен начин. Атрибутът Latte `n:href` и тагът `{link}`, както и методите на компонентите, като например `link()` и други, винаги третират целта **като име на сигнал**. Затова не е необходимо да използвате възклицателен знак: +Тъй като [компонентите|components] са самостоятелни цялости за многократна употреба, които не трябва да имат никакви връзки с околните презентери, връзките тук работят малко по-различно. Атрибутът на Latte `n:href` и тагът `{link}` и методите на компонента като `link()` и други считат целта на връзката **винаги за име на сигнал**. Затова не е необходимо дори да се посочва удивителен знак: ```latte -сигнал, не действие +сигнал, а не действие ``` -Ако искаме да направим препратка към презентаторите в шаблона на компонента, използваме тага `{plink}`: +Ако искаме в шаблона на компонента да свързваме към презентери, ще използваме за това тага `{plink}`: ```latte -главная страница +начало ``` или в кода @@ -223,17 +223,41 @@ $this->getPresenter()->link('Home:default') ``` -Невалидни връзки .[#toc-invalid-links] -====================================== +Псевдоними .{data-version:v3.2.2} +================================= -Може да се случи да създадем невалидна препратка - или защото препраща към несъществуващ презентатор, или защото предава повече параметри, отколкото целевият метод получава в сигнатурата си, или когато не може да бъде генериран URL адрес за целевото действие. Какво да се прави с невалидните препратки се определя от статичната променлива `Presenter::$invalidLinkMode`. Тя може да има една от тези стойности (константи): +Понякога може да е полезно да се присвои на двойката Presenter:действие лесно запомнящ се псевдоним. Например началната страница `Front:Home:default` да се нарече просто `home` или `Admin:Dashboard:default` като `admin`. -- `Presenter::InvalidLinkSilent` - безшумен режим, връща символа `#` като URL -- `Presenter::InvalidLinkWarning` - връща се съобщение E_USER_WARNING -- `Presenter::InvalidLinkTextual` - визуално предупреждение, текстът на грешката се показва в линка -- `Presenter::InvalidLinkException` - Хвърлено е изключение InvalidLinkException +Псевдонимите се дефинират в [конфигурацията|configuration] под ключа `application › aliases`: -Настройката по подразбиране е `InvalidLinkWarning` в производствен режим и `InvalidLinkWarning | InvalidLinkTextual` в режим на разработка. `InvalidLinkWarning` няма да убие скрипта в производствена среда, но ще се регистрира предупреждение. В средата за разработка [Tracy |tracy:] ще улови предупреждението и ще покаже синя страница за грешка. Ако е зададен `InvalidLinkTextual`, водещият и компонентите ще върнат съобщение за грешка под формата на URL адрес, който е маркиран като `#error:`. За да направим такива връзки видими, можем да добавим CSS правило към нашия набор от стилове: +```neon +application: + aliases: + home: Front:Home:default + admin: Admin:Dashboard:default + sign: Front:Sign:in +``` + +Във връзките след това се записват с помощта на знак @, например: + +```latte +администрация +``` + +Поддържат се и във всички методи, работещи с връзки, като `redirect()` и подобни. + + +Невалидни връзки +================ + +Може да се случи да създадем невалидна връзка - или защото води към несъществуващ презентер, или защото предава повече параметри, отколкото целевият метод приема в своята сигнатура, или когато за целевото действие не може да се генерира URL. Как да се постъпи с невалидните връзки определя статичната променлива `Presenter::$invalidLinkMode`. Тя може да приема комбинация от тези стойности (константи): + +- `Presenter::InvalidLinkSilent` - тих режим, като URL се връща знак # +- `Presenter::InvalidLinkWarning` - хвърля се предупреждение E_USER_WARNING, което в продукционен режим ще бъде записано в лога, но няма да предизвика прекъсване на изпълнението на скрипта +- `Presenter::InvalidLinkTextual` - визуално предупреждение, изписва грешката директно във връзката +- `Presenter::InvalidLinkException` - хвърля се изключение InvalidLinkException + +Настройката по подразбиране е `InvalidLinkWarning` в продукционен режим и `InvalidLinkWarning | InvalidLinkTextual` в режим на разработка. `InvalidLinkWarning` в продукционна среда не предизвиква прекъсване на скрипта, но предупреждението ще бъде записано в лога. В режим на разработка то се улавя от [Tracy |tracy:] и показва bluescreen. `InvalidLinkTextual` работи така, че като URL връща съобщение за грешка, което започва със знаците `#error:`. За да бъдат такива връзки забележими на пръв поглед, ще добавим към CSS: ```css a[href^="#error:"] { @@ -242,7 +266,7 @@ a[href^="#error:"] { } ``` -Ако не искаме да се генерират предупреждения в средата за разработка, можем да активираме автоматичния режим на невалидна връзка в [конфигурацията |configuration]. +Ако не искаме в режим на разработка да се генерират предупреждения, можем да настроим тих режим директно в [конфигурацията|configuration]. ```neon application: @@ -250,13 +274,13 @@ application: ``` -LinkGenerator .[#toc-linkgenerator] -=================================== +LinkGenerator +============= -Как да създавате връзки така удобно, както с метода `link()`, но без презентатора? За тази цел разполагаме с [api:Nette\Application\LinkGenerator]. +Как да създаваме връзки с подобен комфорт като метода `link()`, но без присъствието на презентер? За това е тук [api:Nette\Application\LinkGenerator]. -LinkGenerator е услуга, която може да се подаде през конструктора и след това да се създадат връзки чрез метода 'link()'. +LinkGenerator е сървис, който можете да си поискате чрез конструктор и след това да създавате връзки с неговия метод `link()`. -Има разлика в сравнение с водещите. LinkGenerator създава всички връзки като абсолютни URL адреси. Освен това няма "текущ презентатор", така че не можете да зададете само името на действието "link('default')" или относителни пътища към модули. +В сравнение с презентерите тук има разлика. LinkGenerator създава всички връзки директно като абсолютни URL. И освен това не съществува "текущ презентер", така че не може като цел да се посочи само името на действието `link('default')` или да се посочват относителни пътища към модули. -Невалидните връзки винаги хвърлят изключение `Nette\Application\UI\InvalidLinkException`. +Невалидните връзки винаги хвърлят `Nette\Application\UI\InvalidLinkException`. diff --git a/application/bg/directory-structure.texy b/application/bg/directory-structure.texy new file mode 100644 index 0000000000..1d782640dc --- /dev/null +++ b/application/bg/directory-structure.texy @@ -0,0 +1,526 @@ +Директорийна структура на приложението +************************************** + +
    + +Как да проектираме ясна и мащабируема директорийна структура за проекти в Nette Framework? Ще покажем доказани практики, които ще ви помогнат с организацията на кода. Ще научите: + +- как **логически да разделим** приложението на директории +- как да проектираме структурата така, че **добре да се мащабира** с растежа на проекта +- какви са **възможните алтернативи** и техните предимства или недостатъци + +
    + + +Важно е да се спомене, че самият Nette Framework не налага никаква конкретна структура. Той е проектиран така, че да може лесно да се адаптира към всякакви нужди и предпочитания. + + +Основна структура на проекта +============================ + +Въпреки че Nette Framework не диктува никаква твърда директорийна структура, съществува доказано подразбиращо се подреждане под формата на [Web Project|https://github.com/nette/web-project]: + +/--pre +web-project/ +├── app/ ← директория с приложението +├── assets/ ← файлове SCSS, JS, изображения..., алтернативно resources/ +├── bin/ ← скриптове за командния ред +├── config/ ← конфигурация +├── log/ ← логвани грешки +├── temp/ ← временни файлове, кеш +├── tests/ ← тестове +├── vendor/ ← библиотеки, инсталирани от Composer +└── www/ ← публична директория (document-root) +\-- + +Тази структура можете свободно да променяте според вашите нужди - да преименувате или премествате папки. След това е достатъчно само да промените относителните пътища до директориите във файла `Bootstrap.php` и евентуално `composer.json`. Нищо повече не е необходимо, никаква сложна реконфигурация, никакви промени на константи. Nette разполага с умна автодетекция и автоматично разпознава местоположението на приложението, включително неговата URL основа. + + +Принципи на организация на кода +=============================== + +Когато за първи път разглеждате нов проект, трябва бързо да се ориентирате в него. Представете си, че разгръщате директорията `app/Model/` и виждате тази структура: + +/--pre +app/Model/ +├── Services/ +├── Repositories/ +└── Entities/ +\-- + +От нея разбирате само, че проектът използва някакви сървиси, репозиторита и ентитита. За истинската цел на приложението не научавате абсолютно нищо. + +Да разгледаме друг подход - **организация по домейни**: + +/--pre +app/Model/ +├── Cart/ +├── Payment/ +├── Order/ +└── Product/ +\-- + +Тук е различно - на пръв поглед е ясно, че става въпрос за електронен магазин. Самите имена на директориите разкриват какво може приложението - работи с плащания, поръчки и продукти. + +Първият подход (организация по тип класове) носи на практика редица проблеми: код, който логически е свързан, е разпръснат в различни папки и трябва да прескачате между тях. Затова ще организираме по домейни. + + +Именни пространства +------------------- + +Прието е директорийната структура да съответства на именните пространства в приложението. Това означава, че физическото местоположение на файловете отговаря на техния namespace. Например клас, разположен в `app/Model/Product/ProductRepository.php`, трябва да има namespace `App\Model\Product`. Този принцип помага за ориентацията в кода и опростява autoloading-а. + + +Единствено срещу множествено число в имената +-------------------------------------------- + +Забележете, че при основните директории на приложението използваме единствено число: `app`, `config`, `log`, `temp`, `www`. Също така и вътре в приложението: `Model`, `Core`, `Presentation`. Това е така, защото всяка от тях представлява една цялостна концепция. + +Подобно, например `app/Model/Product` представлява всичко около продуктите. Няма да го наречем `Products`, защото не става въпрос за папка, пълна с продукти (тогава там биха били файлове `nokia.php`, `samsung.php`). Това е namespace, съдържащ класове за работа с продукти - `ProductRepository.php`, `ProductService.php`. + +Папката `app/Tasks` е в множествено число, защото съдържа набор от самостоятелни изпълними скриптове - `CleanupTask.php`, `ImportTask.php`. Всеки от тях е самостоятелна единица. + +За консистентност препоръчваме да използвате: +- Единствено число за namespace, представляващ функционална цялост (макар и работещ с множество ентитита) +- Множествено число за колекции от самостоятелни единици +- В случай на несигурност или ако не искате да мислите за това, изберете единствено число + + +Публична директория `www/` +========================== + +Тази директория е единствената достъпна от уеб (т.нар. document-root). Често можете да срещнете и името `public/` вместо `www/` - това е само въпрос на конвенция и няма влияние върху функционалността на приложението. Директорията съдържа: +- [Входна точка |bootstrapping#index.php] на приложението `index.php` +- Файл `.htaccess` с правила за mod_rewrite (при Apache) +- Статични файлове (CSS, JavaScript, изображения) +- Качени файлове + +За правилното осигуряване на сигурността на приложението е от съществено значение да имате правилно [конфигуриран document-root |nette:troubleshooting#Как да промените или премахнете директорията www от URL адреса]. + +.[note] +Никога не поставяйте в тази директория папката `node_modules/` - тя съдържа хиляди файлове, които могат да бъдат изпълними и не трябва да бъдат публично достъпни. + + +Апликационна директория `app/` +============================== + +Това е основната директория с кода на приложението. Основна структура: + +/--pre +app/ +├── Core/ ← инфраструктурни въпроси +├── Model/ ← бизнес логика +├── Presentation/ ← презентери и шаблони +├── Tasks/ ← командни скриптове +└── Bootstrap.php ← зареждащ клас на приложението +\-- + +`Bootstrap.php` е [стартовият клас на приложението|bootstrapping], който инициализира средата, зарежда конфигурацията и създава DI контейнер. + +Нека сега разгледаме отделните поддиректории по-подробно. + + +Презентери и шаблони +==================== + +Презентационната част на приложението имаме в директорията `app/Presentation`. Алтернатива е краткото `app/UI`. Това е мястото за всички презентери, техните шаблони и евентуални помощни класове. + +Този слой организираме по домейни. В сложен проект, който комбинира електронен магазин, блог и API, структурата би изглеждала така: + +/--pre +app/Presentation/ +├── Shop/ ← електронен магазин frontend +│ ├── Product/ +│ ├── Cart/ +│ └── Order/ +├── Blog/ ← блог +│ ├── Home/ +│ └── Post/ +├── Admin/ ← администрация +│ ├── Dashboard/ +│ └── Products/ +└── Api/ ← API endpoints + └── V1/ +\-- + +Напротив, при прост блог бихме използвали разделяне: + +/--pre +app/Presentation/ +├── Front/ ← frontend на уебсайта +│ ├── Home/ +│ └── Post/ +├── Admin/ ← администрация +│ ├── Dashboard/ +│ └── Posts/ +├── Error/ +└── Export/ ← RSS, sitemaps и т.н. +\-- + +Папки като `Home/` или `Dashboard/` съдържат презентери и шаблони. Папки като `Front/`, `Admin/` или `Api/` наричаме **модули**. Технически това са обикновени директории, които служат за логическо разделяне на приложението. + +Всяка папка с презентер съдържа едноименен презентер и неговите шаблони. Например папка `Dashboard/` съдържа: + +/--pre +Dashboard/ +├── DashboardPresenter.php ← презентер +└── default.latte ← шаблон +\-- + +Тази директорийна структура се отразява в именните пространства на класовете. Например `DashboardPresenter` се намира в именното пространство `App\Presentation\Admin\Dashboard` (виж [#Мапиране на презентери]): + +```php +namespace App\Presentation\Admin\Dashboard; + +class DashboardPresenter extends Nette\Application\UI\Presenter +{ + // ... +} +``` + +Към презентера `Dashboard` вътре в модула `Admin` се обръщаме в приложението с помощта на нотация с двоеточие като към `Admin:Dashboard`. Към неговото действие `default` след това като към `Admin:Dashboard:default`. В случай на вложени модули използваме повече двоеточия, например `Shop:Order:Detail:default`. + + +Гъвкаво развитие на структурата +------------------------------- + +Едно от големите предимства на тази структура е колко елегантно се адаптира към растящите нужди на проекта. Като пример да вземем частта, генерираща XML фийдове. В началото имаме проста форма: + +/--pre +Export/ +├── ExportPresenter.php ← един презентер за всички експорти +├── sitemap.latte ← шаблон за sitemap +└── feed.latte ← шаблон за RSS feed +\-- + +С времето се добавят други типове фийдове и се нуждаем от повече логика за тях... Няма проблем! Папката `Export/` просто става модул: + +/--pre +Export/ +├── Sitemap/ +│ ├── SitemapPresenter.php +│ └── sitemap.latte +└── Feed/ + ├── FeedPresenter.php + ├── zbozi.latte ← фийд за Zboží.cz + └── heureka.latte ← фийд за Heureka.cz +\-- + +Тази трансформация е напълно плавна - достатъчно е да се създадат нови подпапки, да се раздели кодът в тях и да се актуализират връзките (напр. от `Export:feed` на `Export:Feed:zbozi`). Благодарение на това можем постепенно да разширяваме структурата според нуждите, нивото на влагане не е никак ограничено. + +Ако например в администрацията имате много презентери, свързани с управлението на поръчки, като `OrderDetail`, `OrderEdit`, `OrderDispatch` и т.н., можете за по-добра организираност на това място да създадете модул (папка) `Order`, в който ще бъдат (папки за) презентерите `Detail`, `Edit`, `Dispatch` и други. + + +Местоположение на шаблоните +--------------------------- + +В предишните примери видяхме, че шаблоните са разположени директно в папката с презентера: + +/--pre +Dashboard/ +├── DashboardPresenter.php ← презентер +├── DashboardTemplate.php ← незадължителен клас за шаблона +└── default.latte ← шаблон +\-- + +Това местоположение на практика се оказва най-удобно - всички свързани файлове са ви веднага под ръка. + +Алтернативно можете да поставите шаблоните в подпапка `templates/`. Nette поддържа и двата варианта. Дори можете да поставите шаблоните изцяло извън папката `Presentation/`. Всичко за възможностите за разполагане на шаблони ще намерите в главата [Търсене на шаблони |templates#Търсене на шаблони]. + + +Помощни класове и компоненти +---------------------------- + +Към презентерите и шаблоните често принадлежат и други помощни файлове. Разполагаме ги логично според тяхната област на действие: + +1. **Директно при презентера** в случай на специфични компоненти за дадения презентер: + +/--pre +Product/ +├── ProductPresenter.php +├── ProductGrid.php ← компонент за извеждане на продукти +└── FilterForm.php ← формуляр за филтриране +\-- + +2. **За модула** - препоръчваме да използвате папка `Accessory`, която се поставя прегледно веднага в началото на азбуката: + +/--pre +Front/ +├── Accessory/ +│ ├── NavbarControl.php ← компоненти за frontend +│ └── TemplateFilters.php +├── Product/ +└── Cart/ +\-- + +3. **За цялото приложение** - в `Presentation/Accessory/`: +/--pre +app/Presentation/ +├── Accessory/ +│ ├── LatteExtension.php +│ └── TemplateFilters.php +├── Front/ +└── Admin/ +\-- + +Или можете да поставите помощни класове като `LatteExtension.php` или `TemplateFilters.php` в инфраструктурната папка `app/Core/Latte/`. А компонентите в `app/Components`. Изборът зависи от навиците на екипа. + + +Модел - сърцето на приложението +=============================== + +Моделът съдържа цялата бизнес логика на приложението. За неговата организация важи отново правилото - структурираме по домейни: + +/--pre +app/Model/ +├── Payment/ ← всичко около плащанията +│ ├── PaymentFacade.php ← основна входна точка +│ ├── PaymentRepository.php +│ ├── Payment.php ← ентитит +├── Order/ ← всичко около поръчките +│ ├── OrderFacade.php +│ ├── OrderRepository.php +│ ├── Order.php +└── Shipping/ ← всичко около доставката +\-- + +В модела типично ще срещнете тези типове класове: + +**Фасади**: представляват основната входна точка към конкретен домейн в приложението. Действат като оркестратор, който координира сътрудничеството между различни сървиси с цел имплементиране на пълни use-cases (като "създай поръчка" или "обработи плащане"). Под своя оркестрационен слой фасадата скрива имплементационните детайли от останалата част на приложението, като по този начин предоставя чист интерфейс за работа с дадения домейн. + +```php +class OrderFacade +{ + public function createOrder(Cart $cart): Order + { + // валидация + // създаване на поръчка + // изпращане на имейл + // записване в статистики + } +} +``` + +**Сървиси**: фокусират се върху специфична бизнес операция в рамките на домейна. За разлика от фасадата, която оркестрира цели use-cases, сървисът имплементира конкретна бизнес логика (като изчисления на цени или обработка на плащания). Сървисите са типично безсъстоянийни и могат да бъдат използвани или от фасади като строителни блокове за по-сложни операции, или директно от други части на приложението за по-прости задачи. + +```php +class PricingService +{ + public function calculateTotal(Order $order): Money + { + // изчисление на цена + } +} +``` + +**Репозиторита**: осигуряват цялата комуникация с хранилището на данни, типично база данни. Неговата задача е зареждане и съхраняване на ентитита и имплементиране на методи за тяхното търсене. Репозиторият изолира останалата част от приложението от имплементационните детайли на базата данни и предоставя обектно-ориентиран интерфейс за работа с данни. + +```php +class OrderRepository +{ + public function find(int $id): ?Order + { + } + + public function findByCustomer(int $customerId): array + { + } +} +``` + +**Ентитита**: обекти, представляващи основните бизнес концепции в приложението, които имат своя идентичност и се променят във времето. Типично става въпрос за класове, мапнати към таблици в базата данни с помощта на ORM (като Nette Database Explorer или Doctrine). Ентититата могат да съдържат бизнес правила, свързани с техните данни и валидационна логика. + +```php +// Ентитит, мапнат към таблицата orders в базата данни +class Order extends Nette\Database\Table\ActiveRow +{ + public function addItem(Product $product, int $quantity): void + { + $this->related('order_items')->insert([ + 'product_id' => $product->id, + 'quantity' => $quantity, + 'unit_price' => $product->price, + ]); + } +} +``` + +**Value обекти**: неизменни обекти, представляващи стойности без собствена идентичност - например парична сума или имейл адрес. Две инстанции на value обект със същите стойности се считат за идентични. + + +Инфраструктурен код +=================== + +Папката `Core/` (или също `Infrastructure/`) е домът на техническата основа на приложението. Инфраструктурният код типично включва: + +/--pre +app/Core/ +├── Router/ ← маршрутизация и управление на URL +│ └── RouterFactory.php +├── Security/ ← автентикация и авторизация +│ ├── Authenticator.php +│ └── Authorizator.php +├── Logging/ ← логване и мониторинг +│ ├── SentryLogger.php +│ └── FileLogger.php +├── Cache/ ← кеширащ слой +│ └── FullPageCache.php +└── Integration/ ← интеграция с външни сървиси + ├── Slack/ + └── Stripe/ +\-- + +При по-малки проекти, разбира се, е достатъчно плоско разделяне: + +/--pre +Core/ +├── RouterFactory.php +├── Authenticator.php +└── QueueMailer.php +\-- + +Става въпрос за код, който: + +- Решава техническата инфраструктура (маршрутизация, логване, кеширане) +- Интегрира външни сървиси (Sentry, Elasticsearch, Redis) +- Предоставя основни сървиси за цялото приложение (поща, база данни) +- Е предимно независим от конкретния домейн - кешът или логерът работи еднакво за електронен магазин или блог. + +Чудите се дали определен клас принадлежи тук, или към модела? Ключовата разлика е в това, че кодът в `Core/`: + +- Не знае нищо за домейна (продукти, поръчки, статии) +- Е предимно възможно да се пренесе в друг проект +- Решава "как работи" (как да се изпрати имейл), а не "какво прави" (какъв имейл да се изпрати) + +Пример за по-добро разбиране: + +- `App\Core\MailerFactory` - създава инстанции на клас за изпращане на имейли, решава SMTP настройките +- `App\Model\OrderMailer` - използва `MailerFactory` за изпращане на имейли за поръчки, знае техните шаблони и кога трябва да се изпратят + + +Командни скриптове +================== + +Приложенията често трябва да извършват дейности извън обичайните HTTP заявки - било то обработка на данни във фонов режим, поддръжка или периодични задачи. За стартиране служат прости скриптове в директорията `bin/`, самата имплементационна логика след това поставяме в `app/Tasks/` (евентуално `app/Commands/`). + +Пример: + +/--pre +app/Tasks/ +├── Maintenance/ ← скриптове за поддръжка +│ ├── CleanupCommand.php ← изтриване на стари данни +│ └── DbOptimizeCommand.php ← оптимизация на базата данни +├── Integration/ ← интеграция с външни системи +│ ├── ImportProducts.php ← импорт от доставчикова система +│ └── SyncOrders.php ← синхронизация на поръчки +└── Scheduled/ ← редовни задачи + ├── NewsletterCommand.php ← разпращане на бюлетини + └── ReminderCommand.php ← нотификации към клиенти +\-- + +Какво принадлежи към модела и какво към командните скриптове? Например логиката за изпращане на един имейл е част от модела, масовото разпращане на хиляди имейли вече принадлежи към `Tasks/`. + +Задачите обикновено [стартираме от командния ред |https://blog.nette.org/en/cli-scripts-in-nette-application] или чрез cron. Могат да се стартират и чрез HTTP заявка, но е необходимо да се мисли за сигурността. Презентерът, който стартира задачата, трябва да бъде защитен, например само за влезли потребители или със силен токен и достъп от разрешени IP адреси. При дълги задачи е необходимо да се увеличи времевият лимит на скрипта и да се използва `session_write_close()`, за да не се заключва сесията. + + +Други възможни директории +========================= + +Освен споменатите основни директории, можете според нуждите на проекта да добавите други специализирани папки. Да разгледаме най-често срещаните от тях и тяхното използване: + +/--pre +app/ +├── Api/ ← логика за API, независима от презентационния слой +├── Database/ ← миграционни скриптове и seeders за тестови данни +├── Components/ ← споделени визуални компоненти в цялото приложение +├── Event/ ← полезно, ако използвате event-driven архитектура +├── Mail/ ← имейл шаблони и свързана логика +└── Utils/ ← помощни класове +\-- + +За споделени визуални компоненти, използвани в презентерите в цялото приложение, може да се използва папка `app/Components` или `app/Controls`: + +/--pre +app/Components/ +├── Form/ ← споделени формулярни компоненти +│ ├── SignInForm.php +│ └── UserForm.php +├── Grid/ ← компоненти за извеждане на данни +│ └── DataGrid.php +└── Navigation/ ← навигационни елементи + ├── Breadcrumbs.php + └── Menu.php +\-- + +Тук принадлежат компоненти, които имат по-сложна логика. Ако искате да споделяте компоненти между няколко проекта, е препоръчително да ги изнесете в отделен composer пакет. + +В директорията `app/Mail` можете да поставите управлението на имейл комуникацията: + +/--pre +app/Mail/ +├── templates/ ← имейл шаблони +│ ├── order-confirmation.latte +│ └── welcome.latte +└── OrderMailer.php +\-- + + +Мапиране на презентери +====================== + +Мапирането дефинира правила за извеждане на името на класа от името на презентера. Специфицираме ги в [конфигурацията|configuration] под ключа `application › mapping`. + +На тази страница показахме, че поставяме презентерите в папка `app/Presentation` (евентуално `app/UI`). Тази конвенция трябва да съобщим на Nette в конфигурационния файл. Достатъчен е един ред: + +```neon +application: + mapping: App\Presentation\*\**Presenter +``` + +Как работи мапирането? За по-добро разбиране първо си представете приложение без модули. Искаме класовете на презентерите да попадат в именното пространство `App\Presentation`, така че презентерът `Home` да се мапира към класа `App\Presentation\HomePresenter`. Което постигаме с тази конфигурация: + +```neon +application: + mapping: App\Presentation\*Presenter +``` + +Мапирането работи така, че името на презентера `Home` замества звездичката в маската `App\Presentation\*Presenter`, с което получаваме крайния име на класа `App\Presentation\HomePresenter`. Просто! + +Както обаче виждате в примерите в тази и други глави, класовете на презентерите поставяме в едноименни поддиректории, например презентерът `Home` се мапира към класа `App\Presentation\Home\HomePresenter`. Това постигаме с удвояване на двоеточието (изисква Nette Application 3.2): + +```neon +application: + mapping: App\Presentation\**Presenter +``` + +Сега ще пристъпим към мапиране на презентери в модули. За всеки модул можем да дефинираме специфично мапиране: + +```neon +application: + mapping: + Front: App\Presentation\Front\**Presenter + Admin: App\Presentation\Admin\**Presenter + Api: App\Api\*Presenter +``` + +Според тази конфигурация презентерът `Front:Home` се мапира към класа `App\Presentation\Front\Home\HomePresenter`, докато презентерът `Api:OAuth` към класа `App\Api\OAuthPresenter`. + +Тъй като модулите `Front` и `Admin` имат подобен начин на мапиране и такива модули най-вероятно ще бъдат повече, е възможно да се създаде общо правило, което да ги замени. В маската на класа така ще се добави нова звездичка за модула: + +```neon +application: + mapping: + *: App\Presentation\*\**Presenter + Api: App\Api\*Presenter +``` + +Това работи и за по-дълбоко вложени директорийни структури, като например презентер `Admin:User:Edit`, сегментът със звездичка се повтаря за всяко ниво и резултатът е клас `App\Presentation\Admin\User\Edit\EditPresenter`. + +Алтернативен запис е вместо низ да се използва масив, състоящ се от три сегмента. Този запис е еквивалентен на предходния: + +```neon +application: + mapping: + *: [App\Presentation, *, **Presenter] + Api: [App\Api, '', *Presenter] +``` diff --git a/application/bg/how-it-works.texy b/application/bg/how-it-works.texy index 6f267f3b26..2015bc5145 100644 --- a/application/bg/how-it-works.texy +++ b/application/bg/how-it-works.texy @@ -3,99 +3,101 @@
    -В момента четете основния документ на Nette. Ще научите всички принципи на работа на уеб приложенията. Всички подробности от А до Я, от момента на раждането до последния дъх на PHP скрипта. След като я прочетете, ще разберете: +Току-що прочетохте основния документ на документацията на Nette. Ще научите целия принцип на работа на уеб приложенията. От А до Я, от момента на създаването до последния дъх на PHP скрипта. След като прочетете, ще знаете: -- Как работи всичко това. -- какво представляват контейнерите Bootstrap, Presenter и DI -- как изглежда структурата на директорията +- как работи всичко +- какво е Bootstrap, Presenter и DI контейнер +- как изглежда директорийната структура
    -Структура на директорията .[#toc-directory-structure] -===================================================== +Директорийна структура +====================== -Отворете пример за скелет на уеб приложение, наречено [WebProject |https://github.com/nette/web-project], и можете да наблюдавате как се записват файловете. +Отворете примера за скелет на уеб приложение, наречен [WebProject|https://github.com/nette/web-project], и докато четете, можете да разглеждате файловете, за които става въпрос. -Структурата на директорията изглежда по следния начин: +Директорийната структура изглежда приблизително така: /--pre web-project/ -├── app/ ← каталог с приложением -│ ├── Presenters/ ← классы презентеров -│ │ ├── HomePresenter.php ← Класс презентера главной страницы -│ │ └── templates/ ← директория шаблонов -│ │ ├── @layout.latte ← шаблон общего макета -│ │ └── Home/ ← шаблоны презентера главной страницы -│ │ └── default.latte ← шаблон действия `default` -│ ├── Router/ ← конфигурация URL-адресов -│ └── Bootstrap.php ← загрузочный класс Bootstrap -├── bin/ ← скрипты командной строки -├── config/ ← файлы конфигурации +├── app/ ← директория с приложението +│ ├── Core/ ← основни класове, необходими за работа +│ │ └── RouterFactory.php ← конфигурация на URL адреси +│ ├── Presentation/ ← презентери, шаблони и др. +│ │ ├── @layout.latte ← шаблон на лейаута +│ │ └── Home/ ← директория на презентера Home +│ │ ├── HomePresenter.php ← клас на презентера Home +│ │ └── default.latte ← шаблон на действието default +│ └── Bootstrap.php ← зареждащ клас Bootstrap +├── assets/ ← ресурси (SCSS, TypeScript, изходни изображения) +├── bin/ ← скриптове, стартирани от командния ред +├── config/ ← конфигурационни файлове │ ├── common.neon -│ └── local.neon -├── log/ ← журналы ошибок -├── temp/ ← временные файлы, кэш, … -├── vendor/ ← библиотеки, установленные через Composer +│ └── services.neon +├── log/ ← логвани грешки +├── temp/ ← временни файлове, кеш, … +├── vendor/ ← библиотеки, инсталирани от Composer │ ├── ... -│ └── autoload.php ← автозагрузчик библиотек, установленных через Composer -├── www/ ← публичный корневой каталог проекта -│ ├── .htaccess ← правила mod_rewrite и т. д. -│ └── index.php ← начальный файл, запускающий приложение -└── .htaccess ← запрещает доступ ко всем каталогам, кроме www +│ └── autoload.php ← autoloading на всички инсталирани пакети +├── www/ ← публична директория или document-root на проекта +│ ├── assets/ ← компилирани статични файлове (CSS, JS, изображения, ...) +│ ├── .htaccess ← правила mod_rewrite +│ └── index.php ← първоначален файл, с който се стартира приложението +└── .htaccess ← забранява достъпа до всички директории освен www \-- -Можете да променяте структурата на директориите по всякакъв начин, да преименувате или премествате папки и след това просто да редактирате пътищата до `log/` и `temp/` във файла `Bootstrap.php` и пътя до този файл в `composer.json` в раздела `autoload`. Нищо друго, никакво сложно преконфигуриране, никакви постоянни промени. Nette има [интелигентно автоматично откриване |bootstrap#development-vs-production-mode]. +Директорийната структура можете да променяте както искате, да преименувате или премествате папки, тя е напълно гъвкава. Nette освен това разполага с умна автодетекция и автоматично разпознава местоположението на приложението, включително неговата URL основа. -За малко по-големи приложения можем да разделим главната папка и папките с шаблони на подпапки (на диска) и пространства от имена (в кода), които наричаме [модули |modules]. +При малко по-големи приложения можем [да разделим папките с презентери и шаблони на поддиректории |directory-structure#Презентери и шаблони] и класовете на именни пространства, които наричаме модули. -Публичната директория `www/` може да бъде променена, без да се налага да инсталирате нещо друго. Всъщност често се случва, че поради спецификата на вашия хостинг ще трябва да я преименувате или да инсталирате т.нар. document-root към тази директория в конфигурацията на хостинга. Ако хостингът ви не позволява да създавате папки на едно ниво над публичната директория, предлагаме ви да потърсите друга хостинг услуга. В противен случай ще се изложите на значителен риск за сигурността. +Директорията `www/` представлява т.нар. публична директория или document-root на проекта. Можете да я преименувате без нужда от каквото и да било друго настройване от страна на приложението. Само е необходимо [да конфигурирате хостинга |nette:troubleshooting#Как да промените или премахнете директорията www от URL адреса] така, че document-root да сочи към тази директория. -Можете също така да качвате WebProject директно, включително Nette, с помощта на [Composer |best-practices:composer]: +WebProject можете също така директно да изтеглите, включително Nette, с помощта на [Composer |best-practices:composer]: ```shell composer create-project nette/web-project ``` -В Linux или macOS задайте [разрешения за запис |nette:troubleshooting#Setting-Directory-Permissions] за директориите `log/` и `temp/`. +На Linux или macOS задайте на директориите `log/` и `temp/` [права за запис |nette:troubleshooting#Настройка на правата на директориите]. -Приложението WebProject е готово за работа, не е необходимо да конфигурирате нищо друго и можете да го видите директно в браузъра си, като отворите папката `www/`. +Приложението WebProject е готово за стартиране, не е необходимо изобщо нищо да се конфигурира и можете директно да го покажете в браузъра, като достъпите папката `www/`. -HTTP заявка .[#toc-http-request] -================================ +HTTP заявка +=========== -Всичко започва с това, че потребителят отваря страница в браузъра и браузърът подава HTTP заявка към сървъра. Заявката се изпраща към PHP файл, разположен в публичната директория `www/`, който се нарича `index.php`. Нека предположим, че това е заявка към `https://example.com/product/123` и ще бъде изпълнен. +Всичко започва в момента, когато потребителят отвори страница в браузъра. Тоест, когато браузърът почука на сървъра с HTTP заявка. Заявката сочи към единствен PHP файл, който се намира в публичната директория `www/`, и това е `index.php`. Да кажем, че става въпрос за заявка към адреса `https://example.com/product/123`. Благодарение на подходящо [настройване на сървъра |nette:troubleshooting#Как да настроите сървъра за красиви URL адреси], и този URL се мапва към файла `index.php` и той се изпълнява. -Задачата му е следната: +Неговата задача е: -1) инициализиране на средата -2) получаване на фабрика -3) стартиране на приложението Nette, което обработва заявката +1) да инициализира средата +2) да получи фабриката +3) да стартира Nette приложението, което ще обработи заявката -Какъв вид фабрика? Ние не произвеждаме трактори, а уебсайтове! Изчакайте, след малко ще бъде обяснено. +Каква фабрика? Не произвеждаме трактори, а уеб страници! Изчакайте, веднага ще се изясни. -Под "инициализиране на средата" разбираме например активирането на услугата [Tracy |tracy:], която е невероятен инструмент за регистриране или визуализиране на грешки. Той регистрира грешките на производствения сървър и ги показва директно на сървъра за разработка. Затова по време на инициализацията трябва да решите дали сайтът ще работи в производствен режим или в режим за разработчици. Nette използва автоматично откриване за това: ако стартирате сайта на localhost, той се стартира в режим за разработчици. Не е необходимо да конфигурирате каквото и да било и приложението е готово както за разработка, така и за внедряване в производството. Тези стъпки се следват и са описани подробно в главата [Bootstrap |bootstrap]. +С думите „инициализация на средата“ имаме предвид например това, че се активира [Tracy|tracy:], което е страхотен инструмент за логване или визуализация на грешки. На продукционен сървър той логва грешки, на сървър за разработка ги показва директно. Следователно към инициализацията принадлежи и решението дали уебсайтът работи в продукционна или развойна среда. За това Nette използва [умна автодетекция |bootstrapping#Режим за разработка срещу продукционен режим]: ако стартирате уебсайта на localhost, той работи в развойна среда. Не е необходимо нищо да конфигурирате и приложението е веднага готово както за разработка, така и за реално внедряване. Тези стъпки се извършват и са подробно описани в главата за [клас Bootstrap|bootstrapping]. -Третата точка (да, пропуснахме втората, но ще се върнем към нея) е да стартирате приложението. Класът `Nette\Application\Application` (наричан по-нататък `Application`) обработва HTTP заявките в Nette, така че когато казваме "стартиране на приложение", имаме предвид извикване на метод с име `run()` върху обект от този клас. +Третата точка (да, прескочихме втората, но ще се върнем към нея) е стартирането на приложението. Обработката на HTTP заявки в Nette се извършва от класа `Nette\Application\Application` (наричан по-нататък `Application`), така че когато казваме стартиране на приложението, имаме предвид конкретно извикване на метода със знаковото име `run()` върху обекта на този клас. -Nette е наставник, който ви напътства да пишете чисти приложения според доказани методологии. Най-проверяваният от тях се нарича **имплементиране на зависимости**, накратко DI. На този етап не искаме да ви натоварваме с обяснение на DI, тъй като това е разгледано в [отделна глава |dependency-injection:introduction], важното тук е, че ключовите обекти обикновено се създават от фабрика за обекти, наречена **DI контейнер** (съкратено DIC). Да, това е същата фабрика, която беше спомената преди време. Освен това той създава обекта `Application` за нас, така че първо се нуждаем от контейнер. Получаваме го с класа `Configurator` и му позволяваме да създаде обекта `Application`, да извика метода `run()` и това стартира приложението Nette. Точно това се случва във файла [index.php |bootstrap#index-php]. +Nette е ментор, който ви води към писането на чисти приложения според доказани методики. И една от тези абсолютно най-доказани се нарича **dependency injection**, съкратено DI. В този момент не искаме да ви натоварваме с обяснение на DI, за това има [отделна глава|dependency-injection:introduction], същественото последствие е, че ключовите обекти обикновено ще ни ги създава фабрика за обекти, която се нарича **DI контейнер** (съкратено DIC). Да, това е фабриката, за която стана дума преди малко. И тя ще ни произведе и обекта `Application`, затова първо се нуждаем от контейнера. Получаваме го с помощта на класа `Configurator` и го караме да произведе обекта `Application`, извикваме върху него метода `run()` и така се стартира Nette приложението. Точно това се случва във файла [index.php |bootstrapping#index.php]. -Приложението Nette .[#toc-nette-application] -============================================ +Nette Application +================= -Класът Application има една единствена задача: да отговори на HTTP заявка. +Класът Application има една-единствена задача: да отговори на HTTP заявка. -Приложенията, написани на Nette, са разделени на много така наречени презентатори (в други фреймуъркове може да срещнете термина *контролер*, което е същото нещо), които представляват класове, представящи определена страница на уебсайт: например начална страница; продукт в електронен магазин; регистрационна форма; rss-карта и т.н. В едно приложение може да има между един и хиляда презентатори. +Приложенията, написани на Nette, се разделят на много т.нар. презентери (в други фреймуърци може да срещнете термина controller, става въпрос за същото), които са класове, всеки от които представлява някаква конкретна страница на уебсайта: напр. начална страница; продукт в електронен магазин; формуляр за вход; sitemap feed и т.н. Приложението може да има от един до хиляди презентери. -Приложението започва с искане към т.нар. маршрутизатор да реши на кой от презентаторите да изпрати текущата заявка за обработка. Маршрутизаторът решава чия е отговорността. Той разглежда входния URL адрес `https://example.com/product/123`, който иска продукт `показать` с `id: 123` като действие. Добър навик е да записвате двойките водещ + действие, разделени с двоеточие: `Продукт:показать`. +Application започва с това, че моли т.нар. рутер да реши на кой от презентерите да предаде текущата заявка за обработка. Рутерът решава чия е отговорността. Поглежда входния URL `https://example.com/product/123` и въз основа на това как е настроен, решава, че това е работа напр. за **презентера** `Product`, от който ще иска като **действие** показване (`show`) на продукта с `id: 123`. Двойката презентер + действие е добър навик да се записва, разделена с двоеточие, като `Product:show`. -Следователно маршрутизаторът е преобразувал URL адреса в двойка `Presenter:action` + параметри, в нашия случай `Product:show` + `id`: 123`. Вы можете увидеть, как выглядит маршрутизатор в файле `app/Router/RouterFactory.php`, и ще го опишем подробно в главата [Маршрутизация |routing]. +Следователно рутерът трансформира URL в двойка `Presenter:action` + параметри, в нашия случай `Product:show` + `id: 123`. Как изглежда такъв рутер можете да видите във файла `app/Core/RouterFactory.php` и го описваме подробно в главата [Маршрутизация |Routing]. -Да продължим. Приложението вече знае името на водещия и може да продължи. Чрез създаване на обект `ProductPresenter`, който е кодът на предентера `Product`. По-точно, той иска от контейнера DI да създаде презентатора, тъй като създаването на обекти е негова работа. +Да продължим нататък. Application вече знае името на презентера и може да продължи напред. Като произведе обект от класа `ProductPresenter`, което е кодът на презентера `Product`. По-точно казано, моли DI контейнера да произведе презентера, защото производството е негова работа. -Водещият може да изглежда по следния начин: +Презентерът може да изглежда например така: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -113,92 +115,86 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -Заявката се обработва от водещия. И задачата е ясна: да се изпълни действието `show` с `id: 123`. На езика preenter това означава да се извика методът `renderShow()` с параметър `$id`, равен на `123`. +Обработката на заявката се поема от презентера. И задачата е ясна: извърши действието `show` с `id: 123`. Което на езика на презентерите означава, че се извиква методът `renderShow()` и в параметъра `$id` получава `123`. -Презентаторът може да извършва няколко действия, т.е. да има няколко метода `render()`. Препоръчваме ви обаче да разработвате преентери с едно или възможно най-малко действия. +Презентерът може да обслужва повече действия, т.е. да има повече методи `render()`. Но препоръчваме да проектирате презентери с едно или възможно най-малко действия. -Така че методът `renderShow(123)`, чийто код е измислен пример, е извикан, но на него можете да видите как данните се прехвърлят към шаблона, т.е. чрез запис в `$this->template`. +Така че, извика се методът `renderShow(123)`, чийто код е измислен пример, но можете да видите на него как се предават данни към шаблона, т.е. със запис в `$this->template`. -След това водещият връща отговор. Това може да бъде HTML страница, изображение, XML документ, файл, изпратен от диска, JSON или пренасочване към друга страница. Важно е да се отбележи, че ако не посочим изрично как да се отговори (какъвто е случаят с `ProductPresenter`), отговорът ще бъде шаблон, показващ HTML страница. Защо? Ами защото в 99% от случаите искаме да покажем шаблон, водещият приема това поведение по подразбиране и иска да улесни работата ни. Това е гледната точка на Нете. +Впоследствие презентерът връща отговор. Той може да бъде HTML страница, изображение, XML документ, изпращане на файл от диска, JSON или например пренасочване към друга страница. Важно е, че ако изрично не кажем как трябва да отговори (което е случаят с `ProductPresenter`), отговорът ще бъде рендиране на шаблон с HTML страница. Защо? Защото в 99% от случаите искаме да рендираме шаблон, следователно презентерът приема това поведение като подразбиращо се и иска да ни улесни работата. Това е смисълът на Nette. -Дори не е необходимо да указваме кой шаблон да се покаже, той сам извежда пътя до него според проста логика. В случая с водещия `Product` и действието `show`, той се опитва да провери дали някой от тези файлове с шаблони съществува спрямо директорията, в която се намира класът `ProductPresenter`: +Не е необходимо дори да посочваме кой шаблон да се рендира, пътят до него се извежда сам. В случай на действие `show` просто се опитва да зареди шаблона `show.latte` в директорията с класа `ProductPresenter`. Също така се опитва да намери лейаут във файла `@layout.latte` (по-подробно за [намиране на шаблони |templates#Търсене на шаблони]). -- `templates/Product/show.latte` -- `templates/Product.show.latte` - -След това се показва шаблонът. Задачата на водещия и на цялото приложение вече е изпълнена. Ако шаблонът не съществува, ще бъде върната страница за грешка 404. Можете да прочетете повече за водещите на страницата [Водещи |presenters]. +И впоследствие рендира шаблоните. С това задачата на презентера и на цялото приложение е изпълнена и делото е завършено. Ако шаблонът не съществува, се връща страница с грешка 404. Повече за презентерите ще прочетете на страницата [Презентери|presenters]. [* request-flow.svg *] -За да сме сигурни, нека повторим целия процес, като използваме малко по-различен URL адрес: +За всеки случай, нека опитаме да рекапитулираме целия процес с малко по-различен URL: -1) URL адресът ще `https://example.com` -2) изтегляме приложението, създаваме контейнер и стартираме `Application::run()` -3) маршрутизаторът декодира URL адреса като двойка `Home:default` -4) обектът е създаден `HomePresenter` +1) URL ще бъде `https://example.com` +2) зареждаме приложението, създава се контейнер и се стартира `Application::run()` +3) рутерът декодира URL като двойка `Home:default` +4) създава се обект от класа `HomePresenter` 5) извиква се методът `renderDefault()` (ако съществува) -6) шаблонът `templates/Home/default.latte` с оформлението `templates/@layout.latte` се визуализира +6) рендира се шаблон напр. `default.latte` с лейаут напр. `@layout.latte` -Може би сега ще се сблъскате с много нови концепции, но ние смятаме, че те имат смисъл. Създаването на приложения в Nette е лесно. +Може би сега сте се сблъскали с голям брой нови понятия, но вярваме, че те имат смисъл. Създаването на приложения в Nette е огромно удоволствие. -Шаблони .[#toc-templates] -========================= +Шаблони +======= -Що се отнася до шаблоните, Nette използва системата за шаблони [Latte |latte:]. Ето защо файловете на шаблоните се намират на адрес `.latte`. Latte се използва, защото е най-сигурната система за шаблони за PHP и в същото време е най-интуитивна. Не е необходимо да научавате много, достатъчно е да знаете PHP и няколко тага за Latte. Всичко ще научите от [документацията |latte:]. +Когато вече стана дума за шаблони, в Nette се използва шаблониращата система [Latte |latte:]. Затова и тези разширения `.latte` при шаблоните. Latte се използва от една страна, защото е най-добре защитената шаблонираща система за PHP, а същевременно и най-интуитивната система. Не е необходимо да учите много нови неща, достатъчно е да познавате PHP и няколко тага. Всичко ще научите [в документацията |templates]. -В шаблона [създаваме връзки |creating-links] към други водещи и действия, както следва: +В шаблона се [създават връзки |creating-links] към други презентери и действия по следния начин: ```latte -страница товара +детайл на продукта ``` -Просто напишете познатата двойка `Presenter:action` вместо истинския URL адрес и включете всички параметри. Трикът е `n:href`, който казва, че този атрибут ще бъде обработен от Nette. И тя ще генерира: +Просто вместо реален URL напишете познатата двойка `Presenter:action` и посочете евентуални параметри. Трикът е в `n:href`, което казва, че този атрибут ще бъде обработен от Nette. И ще генерира: ```latte -страница товара +детайл на продукта ``` -Споменатият по-горе маршрутизатор е отговорен за генерирането на URL адреса. Всъщност маршрутизаторите в Nette са уникални с това, че могат не само да извършват преобразувания от URL към двойка презентатор:действие, но и обратното - да генерират URL от име на презентатор + действие + параметри. -Благодарение на това в Nette можете напълно да промените формата на URL адреса в цялото готово приложение, без да променяте нито един символ в шаблона или презентатора, само чрез промяна на маршрутизатора. -Поради това работи т.нар. канонизация - още една уникална функция на Nette, която подобрява SEO оптимизацията, като автоматично предотвратява дублираното съдържание в различни URL адреси. -Много програмисти намират това за невероятно. +Генерирането на URL се извършва от вече споменатия рутер. Всъщност рутерите в Nette са изключителни с това, че могат да извършват не само трансформации от URL към двойка presenter:action, но и обратно, т.е. от името на презентера + действието + параметрите да генерират URL. Благодарение на това в Nette можете напълно да промените формите на URL в цялото готово приложение, без да променяте нито един знак в шаблона или презентера. Само като промените рутера. Също така благодарение на това работи т.нар. канонизация, което е друга уникална характеристика на Nette, която допринася за по-добро SEO (оптимизация за намиране в интернет), като автоматично предотвратява съществуването на дублирано съдържание на различни URL адреси. Много програмисти смятат това за изумително. -Интерактивни компоненти .[#toc-interactive-components] -====================================================== +Интерактивни компоненти +======================= -Искаме да ви кажем още нещо за водещите: те имат вградена система от компоненти. По-възрастните може да си спомнят нещо подобно от Delphi или ASP.NET Web Forms. React или Vue.js са изградени на базата на нещо отдалечено подобно. В света на PHP фреймуърците това е напълно уникална функция. +За презентерите трябва да ви разкрием още нещо: те имат вградена компонентна система. Нещо подобно може да е познато на ветераните от Delphi или ASP.NET Web Forms, на нещо отдалечено подобно са базирани React или Vue.js. В света на PHP фреймуърците това е абсолютно уникално явление. -Компонентите са отделни блокове за многократна употреба, които поставяме в страници (напр. презентатори). Те могат да бъдат [формуляри |forms:in-presenter], [решетки с данни |https://componette.org/contributte/datagrid/], менюта, анкети, изобщо всичко, което има смисъл да се използва многократно. Можем да създадем собствени компоненти или да използваме някои от [огромния брой |https://componette.org] компоненти с отворен код. +Компонентите са самостоятелни цялости за многократна употреба, които вмъкваме в страниците (т.е. презентерите). Могат да бъдат [формуляри |forms:in-presenter], [datagrid-ове |https://componette.org/contributte/datagrid/], менюта, анкети за гласуване, всъщност всичко, което има смисъл да се използва многократно. Можем да създаваме собствени компоненти или да използваме някои от [огромното предлагане |https://componette.org] на open source компоненти. -Компонентите променят из основи начина, по който разработваме приложения. Те ще открият нови възможности за създаване на страници от предварително дефинирани блокове. И те имат нещо общо с [Холивуд |components#Hollywood-Style]. +Компонентите фундаментално влияят на подхода към създаването на приложения. Ще ви отворят нови възможности за сглобяване на страници от предварително подготвени единици. И освен това имат нещо общо с [Холивуд |components#Hollywood style]. -Контейнер и конфигурация на DI .[#toc-di-container-and-configuration] -===================================================================== +DI контейнер и конфигурация +=========================== -Контейнерът DI (фабрика за обекти) е сърцето на цялото приложение. +DI контейнерът, или фабриката за обекти, е сърцето на цялото приложение. -Не се притеснявайте, това не е магическа черна кутия, както може да се предположи от предишните думи. Всъщност това е един доста скучен клас на PHP, генериран от Nette и съхраняван в директория с кеш. Той има много методи, наречени `createServiceAbcd()`, като всеки от тях създава и връща обект. Да, има и метод `createServiceApplication()`, който създава `Nette\Application\Application`, който ни е необходим във файла `index.php`, за да стартираме приложението. Съществуват и методи за подготовка на отделни презентатори. И така нататък. +Не се притеснявайте, това не е никаква магическа черна кутия, както може би изглежда от предишните редове. Всъщност това е един доста скучен PHP клас, който Nette генерира и съхранява в директорията с кеша. Има много методи, наречени като `createServiceAbcd()`, и всеки от тях може да произведе и върне някакъв обект. Да, там има и метод `createServiceApplication()`, който произвежда `Nette\Application\Application`, който ни беше необходим във файла `index.php` за стартиране на приложението. И има методи, произвеждащи отделните презентери. И така нататък. -Обектите, които контейнерът DI създава, по някаква причина се наричат услуги. +Обектите, които DI контейнерът създава, по някаква причина се наричат сървиси. -Особеността на този клас е, че той не се програмира от вас, а от рамката. Всъщност той генерира PHP код и го съхранява на диска. Просто давате инструкции за това какви обекти и как точно трябва да произвежда контейнерът. Тези инструкции са записани в [конфигурационни файлове |bootstrap#di-container-configuration] във [формат NEON |neon:format] и затова имат разширение `.neon`. +Това, което е наистина специално в този клас, е, че не го програмирате вие, а фреймуъркът. Той наистина генерира PHP код и го съхранява на диска. Вие само давате инструкции какви обекти трябва да може да произвежда контейнерът и как точно. И тези инструкции са записани в [конфигурационни файлове |bootstrapping#Конфигурация на DI контейнера], за които се използва форматът [NEON|neon:format] и следователно имат и разширение `.neon`. -Файловете за конфигурация се използват изключително за обучение на контейнера DI. Така например, ако посоча `expiration: 14 days` в раздела за [сесията |http:configuration#session], контейнерът DI ще извика своя метод `setExpiration('14 days')` при създаването на обекта `Nette\Http\Session`, представляващ сесията, и по този начин конфигурацията ще стане реалност. +Конфигурационните файлове служат чисто за инструктиране на DI контейнера. Така че, когато например посоча в секцията [session |http:configuration#Сесия] опцията `expiration: 14 days`, DI контейнерът при създаването на обекта `Nette\Http\Session`, представляващ сесията, ще извика неговия метод `setExpiration('14 days')` и така конфигурацията ще стане реалност. -За вас е подготвена цяла глава, в която се описва какво можете да [конфигурирате |nette:configuring] и как да [дефинирате собствени услуги |dependency-injection:services]. +Има подготвена за вас цяла глава, описваща какво всичко може да се [конфигурира |nette:configuring] и как да се [дефинират собствени сървиси |dependency-injection:services]. -След като стигнете до създаването на услуги, ще срещнете думата *autowiring*. Това е притурка, която ще улесни живота ви изключително много. Той може автоматично да предава обекти, когато са ви необходими (например към конструкторите на класа), без да е необходимо да правите каквото и да било. Ще видите, че контейнерът DI в Nette е малко чудо. +Щом малко навлезете в създаването на сървиси, ще се сблъскате с думата [autowiring |dependency-injection:autowiring]. Това е хитринка, която по невероятен начин ще ви улесни живота. Може автоматично да предава обекти там, където ги имате нужда (например в конструкторите на вашите класове), без да е необходимо да правите каквото и да било. Ще откриете, че DI контейнерът в Nette е малко чудо. -Какво следва? .[#toc-what-next] -=============================== +Накъде да продължим? +==================== -Обхванахме основите на работата на приложенията в Nette. Засега много повърхностно, но скоро ще навлезете в дълбочина и ще създадете страхотни уеб приложения. Къде да продължим? Опитахте ли урока [Създаване на първото ви приложение |quickstart:]? +Преминахме през основните принципи на приложенията в Nette. Засега много повърхностно, но скоро ще навлезете в дълбочина и с времето ще създадете прекрасни уеб приложения. Накъде да продължим? Опитахте ли вече урока [Пишем първото приложение|quickstart:]? -В допълнение към горното Nette разполага с цял арсенал от [полезни класове |utils:], [слой за бази данни |database:] и др. Опитайте се целенасочено да прегледате документацията. Или посетете [блога |https://blog.nette.org]. Ще откриете много интересни неща. +Освен описаното по-горе, Nette разполага с цял арсенал от [полезни класове|utils:], [слой за работа с бази данни|database:] и т.н. Опитайте просто да прегледате документацията. Или [блога|https://blog.nette.org]. Ще откриете много интересно. -Нека тази рамка ви донесе много радост 💙. +Нека фреймуъркът ви носи много радост 💙 diff --git a/application/bg/modules.texy b/application/bg/modules.texy deleted file mode 100644 index 622fd48b2e..0000000000 --- a/application/bg/modules.texy +++ /dev/null @@ -1,148 +0,0 @@ -Модули -****** - -.[perex] -В Nette модулите са логическите единици, от които се състои едно приложение. Те включват главни модули, шаблони, евентуално компоненти и класове модели. - -Един компонент за презентатори и един за шаблони няма да са достатъчни за реални проекти. Натрупването на десетки файлове в една папка е меко казано неорганизирано. Как да излезем от тази ситуация? Просто ги разделяме на поддиректории на диска и на пространства от имена в кода. Точно това правят модулите Nette. - -Така че нека забравим за една папка за презентатори и шаблони и вместо това да създадем модули като `Admin` и `Front`. - -/--pre -app/ -├── Presenters/ -├── Modules/ ← директория с модулями -│ ├── Admin/ ← модуль Admin -│ │ ├── Presenters/ ← его презентеры -│ │ │ ├── DashboardPresenter.php -│ │ │ └── templates/ -│ └── Front/ ← модуль Front -│ └── Presenters/ ← его презентеры -│ └── ... -\-- - -Тази структура на директориите ще бъде отразена в пространствата за имена на класовете, така че например `DashboardPresenter` ще бъде в пространството `App\Modules\Admin\Presenters`: - -```php -namespace App\Modules\Admin\Presenters; - -class DashboardPresenter extends Nette\Application\UI\Presenter -{ - // ... -} -``` - -Главното устройство `Dashboard` в модула `Admin` се обозначава в приложението с помощта на запис с двойна точка като `Admin:Dashboard`, а неговото действие `default` се обозначава като `Admin:Dashboard:default`. -И откъде Nette знае, че `Admin:Dashboard` представлява класа `App\Modules\Admin\Presenters\DashboardPresenter`? Говорим за това, като използваме [картографирането |#Mapping] в конфигурацията. -Така че дадената структура не е фиксирана и можете да я променяте по свое усмотрение. - -Модулите, разбира се, могат да съдържат всички други части, освен презентатори и шаблони, като компоненти, класове модели и др. - - -Вложени модули .[#toc-nested-modules] -------------------------------------- - -Модулите не трябва да образуват само плоска структура, можете да създавате и подмодули, например: - -/--pre -app/ -├── Modules/ ← директория с модулями -│ ├── Blog/ ← модуль Blog -│ │ ├── Admin/ ← подмодуль Admin -│ │ │ ├── Presenters/ -│ │ │ └── ... -│ │ └── Front/ ← подмодуль Front -│ │ ├── Presenters/ -│ │ └── ... -│ ├── Forum/ ← модуль Forum -│ │ └── ... -\-- - -Така модулът `Blog` се разделя на подмодули `Admin` и `Front`. Това отново ще бъде отразено в пространствата от имена, които ще бъдат `App\Modules\Blog\Admin\Presenters` и т.н. Главният модул `Dashboard` в рамките на подмодула се нарича `Blog:Admin:Dashboard`. - -Разклоненията могат да бъдат толкова дълбоки, колкото искате, така че можете да създавате подмодули. - - -Създаване на връзки .[#toc-creating-links] ------------------------------------------- - -Връзките в главните шаблони са относителни към текущия модул. По този начин връзка `Foo:default` води до главния `Foo` в същия модул като текущия главен. Например, ако текущият модул е `Front`, връзката изглежда по следния начин - -```latte -odkaz na Front:Product:show -``` - -Връзката е относителна, дори ако името на модула е част от нея, тогава той се счита за подмодул: - -```latte -odkaz na Front:Shop:Product:show -``` - -Абсолютните връзки се записват подобно на абсолютните пътища на диска, но с двоеточие вместо с наклонена черта. Така абсолютната връзка започва с двоеточие: - -```latte -odkaz na Admin:Product:show -``` - -За да разберем дали се намираме в определен модул или подмодул, използваме функцията `isModuleCurrent(moduleName)`. - -```latte -
  • - ... -
  • -``` - - -Маршрутизиране .[#toc-routing] ------------------------------- - -Вижте [главата за маршрутизиране |routing#modules]. - - -Картографиране .[#toc-mapping] ------------------------------- - -Определя правилата, по които името на класа се извежда от главното име. Записваме ги в [конфигурацията |configuration] под ключа `application › mapping`. - -Нека започнем с пример, при който не се използват модули. Искаме само главните класове да имат пространството от имена `App\Presenters`. Това означава, че искаме главното име, например `Home`, да се съпостави с класа `App\Presenters\HomePresenter`. Това може да се постигне със следната конфигурация: - -```neon -application: - mapping: - *: App\Presenters\*Presenter -``` - -Името на водещия се заменя със звездичка и резултатът е името на класа. Лесно! - -Ако разделим презентаторите на модули, можем да използваме различни карти за всеки модул: - -```neon -application: - mapping: - Front: App\Modules\Front\Presenters\*Presenter - Admin: App\Modules\Admin\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -Сега водещият `Front:Home` е определен от класа `App\Modules\Front\HomePresenter`, а презентер `Admin:Dashboard` - `App\AdminModule\DashboardPresenter`. - -Би било по-удобно да се създаде общо правило (звездичка), което да замени първите две правила, и да се добави допълнителна звездичка само за модула: - -```neon -application: - mapping: - *: App\Modules\*\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -Но какво става, ако използваме няколко вложени модула и имаме например главен модул `Admin:User:Edit`? В този случай сегментът със звездичка, представляващ модула за всяко ниво, просто ще се повтори и резултатът ще бъде класът `App\Modules\Admin\User\Presenters\EditPresenter`. - -Алтернативен начин за записване е използването на масив от три сегмента вместо низ. Този запис е еквивалентен на предишния: - -```neon -application: - mapping: - *: [App\Modules, *, Presenters\*Presenter] -``` - -Стойността по подразбиране е `*: *Module\*Presenter`. diff --git a/application/bg/multiplier.texy b/application/bg/multiplier.texy index daa3436778..0ca2717c6f 100644 --- a/application/bg/multiplier.texy +++ b/application/bg/multiplier.texy @@ -1,30 +1,32 @@ -Мултипликатор: динамични компоненти -*********************************** +Multiplier: динамични компоненти +******************************** -Инструмент за динамично създаване на интерактивни компоненти. +.[perex] +Инструмент за динамично създаване на интерактивни компоненти -Нека да започнем с типичен проблем: имаме списък с продукти в сайт за електронна търговия и искаме да придружим всеки продукт с формуляр за *добавяне в количката*. Един от начините е да обхванете целия списък в един формуляр. По-удобен начин е да използвате [api:Nette\Application\UI\Multiplier]. +Да започнем с типичен пример: имаме списък със стоки в електронен магазин, като за всяка искаме да покажем формуляр за добавяне на стоката в количката. Един от възможните варианти е да обвием целия списък в един формуляр. Много по-удобен начин обаче ни предлага [api:Nette\Application\UI\Multiplier]. -Multiplier ви позволява да дефинирате фабрика за няколко компонента. Той се основава на принципа на вложените компоненти - всеки компонент, който наследява от [api:Nette\ComponentModel\Container], може да съдържа други компоненти. +Multiplier позволява удобно да се дефинира фабрика за множество компоненти. Работи на принципа на вложените компоненти - всеки компонент, наследяващ [api:Nette\ComponentModel\Container], може да съдържа други компоненти. -Вижте [модела на компонента |components#Components in Depth] в документацията. +.[tip] +Вижте главата за [компонентния модел |components#Компоненти в дълбочина] в документацията или [лекцията от Honza Tvrdík|https://www.youtube.com/watch?v=8y3LLexWu-I]. -Multiplier е родителски компонент, който може динамично да създава своите деца, като използва обратната връзка, предадена в конструктора. Вижте пример: +Същността на Multiplier е, че той действа като родител, който може да създава своите потомци динамично с помощта на callback, предаден в конструктора. Вижте примера: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function () { $form = new Nette\Application\UI\Form; - $form->addInteger('amount', 'Amount:') + $form->addInteger('count', 'Брой стоки:') ->setRequired(); - $form->addSubmit('send', 'Add to cart'); + $form->addSubmit('send', 'Добави в количката'); return $form; }); } ``` -В шаблона можем да покажем формуляр за всеки продукт - и всеки формуляр ще бъде уникален компонент. +Сега можем в шаблона лесно при всяка стока да накараме да се рендира формуляр - и всеки ще бъде наистина уникален компонент. ```latte {foreach $items as $item} @@ -35,26 +37,26 @@ protected function createComponentShopForm(): Multiplier {/foreach} ``` -Аргументът, предаден в тага `{control}`, гласи: +Аргументът, предаден в тага `{control}`, е във формат, който казва: -1. вземете компонента `shopForm` -2. да се върне при своя наследник `$item->id` +1. вземи компонента `shopForm` +2. и от него вземи потомъка `$item->id` -При първото извикване на **1.** компонентът `shopForm` все още не съществува, затова се извиква методът `createComponentShopForm`, за да се създаде. След това се извиква анонимна функция, предадена като параметър на Multiplier, и се създава форма. +При първото извикване на точка **1.** `shopForm` все още не съществува, така че се извиква неговата фабрика `createComponentShopForm`. Върху получения компонент (инстанция на Multiplier) след това се извиква фабриката на конкретния формуляр - което е анонимната функция, която предадохме на Multiplier в конструктора. -В следващите итерации на `foreach` методът `createComponentShopForm` вече не се извиква, защото компонентът вече съществува. Но тъй като препращаме към друг потомък (`$item->id` варира между итерациите), анонимната функция се извиква отново и се създава нова форма. +В следващата итерация на foreach методът `createComponentShopForm` вече няма да бъде извикван (компонентът съществува), но тъй като търсим друг негов потомък (`$item->id` ще бъде различно във всяка итерация), отново ще бъде извикана анонимната функция и ще ни върне нов формуляр. -Последното нещо, което трябва да се направи, е да се уверим, че формулярът действително добавя правилния продукт към количката, тъй като в сегашното състояние всички формуляри са еднакви и не можем да разграничим към кои продукти принадлежат. За да направим това, можем да използваме свойството на класа Multiplier (и като цяло всеки метод за фабрика на компоненти в рамката Nette), че всеки метод за фабрика на компоненти получава името на създадения компонент като първи аргумент. В нашия случай това ще бъде `$item->id`, което е точно това, от което се нуждаем, за да разграничаваме отделните продукти. Всичко, което трябва да направите, е да промените кода за създаване на формуляра: +Единственото, което остава, е да осигурим, че формулярът ще добави в количката наистина тази стока, която трябва - в момента формулярът при всяка стока е напълно идентичен. Ще ни помогне свойството на Multiplier (и общо на всяка фабрика за компонент в Nette Framework), а именно това, че всяка фабрика като свой първи аргумент получава името на създавания компонент. В нашия случай това ще бъде `$item->id`, което е точно информацията, от която се нуждаем. Достатъчно е леко да променим създаването на формуляра: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function ($itemId) { $form = new Nette\Application\UI\Form; - $form->addInteger('amount', 'Количество:') + $form->addInteger('count', 'Брой стоки:') ->setRequired(); $form->addHidden('itemId', $itemId); - $form->addSubmit('send', 'Добавить в корзину'); + $form->addSubmit('send', 'Добави в количката'); return $form; }); } diff --git a/application/bg/presenters.texy b/application/bg/presenters.texy index 36c4d6d1e3..77de773f85 100644 --- a/application/bg/presenters.texy +++ b/application/bg/presenters.texy @@ -1,39 +1,39 @@ -Презентатори -************ +Презентери +**********
    -Научете как да създавате презентатори и шаблони в Nette. След като прочетете тази статия, ще знаете. +Ще се запознаем с това как се пишат презентери и шаблони в Nette. След като прочетете, ще знаете: -- Как работи водещият -- какво представляват фиксираните параметри -- Как да визуализирате шаблон +- как работи презентерът +- какво са персистентните параметри +- как се рендират шаблони
    -[Вече знаем, |how-it-works#Nette-Application] че презентаторът е клас, който представлява определена страница на уеб приложение, например началната страница, страницата на продукта в онлайн магазин, формата за вход, картата на сайта и т.н. Едно приложение може да има от един до хиляди презентатори. В други рамки те са известни и като контролери. +[Вече знаем |how-it-works#Nette Application], че презентерът е клас, който представлява някаква конкретна страница на уеб приложение, напр. начална страница; продукт в електронен магазин; формуляр за вход; sitemap feed и т.н. Приложението може да има от един до хиляди презентери. В други фреймуърци те се наричат и контролери. -Обикновено терминът *presenter* се отнася до наследника на класа [api:Nette\Application\UI\Presenter], който е подходящ за уеб интерфейси. Ще обсъдим този клас в останалата част на тази глава. В общ смисъл презентатор е всеки обект, който реализира интерфейса [api:Nette\Application\IPresenter]. +Обикновено под понятието презентер се разбира наследник на клас [api:Nette\Application\UI\Presenter], който е подходящ за генериране на уеб интерфейси и на който ще се посветим в останалата част от тази глава. В общ смисъл презентерът е всеки обект, имплементиращ интерфейса [api:Nette\Application\IPresenter]. -Жизнен цикъл на водещия .[#toc-life-cycle-of-presenter] -======================================================= +Жизнен цикъл на презентера +========================== -Задачата на водещия е да обработи заявката и да върне отговор (това може да бъде HTML страница, изображение, пренасочване и т.н.). +Задачата на презентера е да обработи заявка и да върне отговор (който може да бъде HTML страница, изображение, пренасочване и т.н.). -Така че в началото има молба. Това не е самата HTTP заявка, а обектът [api:Nette\Application\Request], в който HTTP заявката е преобразувана от маршрутизатора. Обикновено не се сблъскваме с този обект, тъй като водещият умело делегира обработката на заявката на специални методи, които ще видим след малко. +Следователно в началото му се предава заявка. Това не е директно HTTP заявка, а обект [api:Nette\Application\Request], в който HTTP заявката е била трансформирана с помощта на рутера. С този обект обикновено не влизаме в контакт, тъй като презентерът умно делегира обработката на заявката на други методи, които сега ще покажем. -[* lifecycle.svg *] *** *Предварително въвеждане на жизнения цикъл* .<> +[* lifecycle.svg *] *** *Жизнен цикъл на презентера* .<> -На фигурата е показан списък с методи, които се извикват последователно отгоре надолу, ако съществуват. Всички те не са задължителни, можем да имаме напълно празен презентатор без нито един метод и да изградим прост статичен уеб върху него. +Изображението представлява списък с методи, които се извикват последователно отгоре надолу, ако съществуват. Никой от тях не е задължителен, можем да имаме напълно празен презентер без нито един метод и да изградим върху него прост статичен уебсайт. `__construct()` --------------- -Конструкторът не е от значение за жизнения цикъл на презентатора, тъй като се извиква в момента на създаване на обекта. Но го споменаваме поради важността му, тъй като се използва за предаване на зависимости. +Конструкторът не принадлежи съвсем към жизнения цикъл на презентера, защото се извиква в момента на създаване на обекта. Но го споменаваме поради важността му. Конструкторът (заедно с [метода inject|best-practices:inject-method-attribute]) служи за предаване на зависимости. -На водещия не се налага да се грижи за бизнес логиката на приложението, да записва и чете от базата данни, да извършва изчисления и т.н. Това е задача за класовете от слоя, който наричаме модел. Например класът `ArticleRepository` може да отговаря за зареждането и запазването на статии. За да може презентаторът да я използва, тя [се предава чрез имплементация на зависимост |dependency-injection:passing-dependencies]: +Презентерът не трябва да се занимава с бизнес логиката на приложението, да записва и чете от база данни, да извършва изчисления и т.н. За това са класовете от слоя, който наричаме модел. Например класът `ArticleRepository` може да отговаря за зареждането и съхраняването на статии. За да може презентерът да работи с него, той си го [изисква чрез dependency injection |dependency-injection:passing-dependencies]: ```php @@ -50,44 +50,44 @@ class ArticlePresenter extends Nette\Application\UI\Presenter `startup()` ----------- -Веднага след получаване на заявка се извиква методът `startup()`. Можете да го използвате за инициализиране на свойства, проверка на привилегиите на потребителите и т.н. Необходимо е винаги да се извиква предшественикът `parent::startup()`. +Веднага след получаване на заявката се извиква методът `startup()`. Можете да го използвате за инициализация на свойства, проверка на потребителски права и т.н. Изисква се методът винаги да извиква родителя `parent::startup()`. `action(args...)` .{toc: action()} -------------------------------------------------- -Подобно на метода `render()`. Като има предвид, че `render()` има за цел да подготви данните за конкретен шаблон, който впоследствие се визуализира в `action()` заявката се обработва без последващо визуализиране на шаблона. Например данните се обработват, потребителят влиза или излиза от системата и т.н., след което се [пренасочват другаде |#Redirection]. +Аналог на метода `render()`. Докато `render()` е предназначен да подготви данни за конкретен шаблон, който след това се рендира, то в `action()` се обработва заявка без връзка с рендирането на шаблон. Например се обработват данни, потребителят се вписва или изписва, и така нататък, и след това [се пренасочва другаде |#Пренасочване]. -Важното е, че `action()` се извиква преди `render()`, така че в него можем евентуално да променим следващия жизнен цикъл, т.е. да променим шаблона за визуализиране и метода `render()`която ще бъде извикана с помощта на `setView('otherView')`. +Важно е, че `action()` се извиква преди `render()`, така че в него можем евентуално да променим по-нататъшния ход на събитията, т.е. да променим шаблона, който ще се рендира, както и метода `render()`, който ще се извика. И това става с помощта на `setView('jineView')`. -Параметрите от заявката се предават на метода. Възможно и препоръчително е да се посочат типове за параметрите, например `actionShow(int $id, string $slug = null)` - ако параметърът `id` липсва или ако не е цяло число, презентаторът ще върне [грешка 404 |#Error-404-etc] и ще прекрати операцията. +На метода се предават параметри от заявката. Възможно е и се препоръчва да се посочат типове на параметрите, напр. `actionShow(int $id, ?string $slug = null)` - ако параметърът `id` липсва или ако не е integer, презентерът ще върне [грешка 404 |#Грешка 404 и др] и ще прекрати дейността си. `handle(args...)` .{toc: handle()} -------------------------------------------------- -Този метод обработва така наречените сигнали, за които ще говорим в главата за [Компоненти |components#Signal]. Той е предназначен основно за компоненти и обработка на AJAX заявки. +Методът обработва т.нар. сигнали, с които ще се запознаем в главата, посветена на [компонентите |components#Сигнал]. Той е предназначен основно за компоненти и обработка на AJAX заявки. -Параметрите се предават на метода, както в `action()`включително проверка на типа. +На метода се предават параметри от заявката, както в случая с `action()`, включително проверка на типа. `beforeRender()` ---------------- -Методът `beforeRender`, както подсказва името, се извиква преди всеки метод `render()`. Той се използва за обща персонализация на шаблона, предаване на променливи за оформление и т.н. +Методът `beforeRender`, както подсказва името, се извиква преди всеки метод `render()`. Използва се за обща конфигурация на шаблона, предаване на променливи за лейаута и подобни. `render(args...)` .{toc: render()} ---------------------------------------------- -Мястото, където подготвяме шаблона за последващо визуализиране, прехвърляме данни към него и т.н. +Мястото, където подготвяме шаблона за последващо рендиране, предаваме му данни и т.н. -Параметрите се предават на метода, както в `action()`включително проверка на типа. +На метода се предават параметри от заявката, както в случая с `action()`, включително проверка на типа. ```php public function renderShow(int $id): void { - //получаваме данни от модела и ги предаваме в шаблона + // получаваме данни от модела и ги предаваме на шаблона $this->template->article = $this->articles->getById($id); } ``` @@ -96,104 +96,104 @@ public function renderShow(int $id): void `afterRender()` --------------- -Методът `afterRender`, както подсказва името, се извиква след всеки метод `render()`. Той се използва рядко. +Методът `afterRender`, както отново подсказва името, се извиква след всеки метод `render()`. Използва се по-скоро рядко. `shutdown()` ------------ -Извиква се в края на жизнения цикъл на презентатора. +Извиква се в края на жизнения цикъл на презентера. -**Добър съвет, преди да продължим**. Както виждате, презентаторът може да обработва повече действия/изгледи, т.е. да има повече методи. `render()`. Но препоръчваме да разработвате презентатори с едно или възможно най-малко действия. +**Добър съвет, преди да продължим**. Презентерът, както се вижда, може да обслужва повече действия/view, т.е. да има повече методи `render()`. Но препоръчваме да проектирате презентери с едно или възможно най-малко действия. -Изпращане на отговор .[#toc-sending-a-response] -=============================================== +Изпращане на отговор +==================== -Обикновено отговорът от главния модул е [шаблон за визуализиране на HTML страница |templates], но може да бъде и изпращане на файл, JSON или дори пренасочване към друга страница. +Отговорът на презентера обикновено е [рендиране на шаблон с HTML страница|templates], но може да бъде и изпращане на файл, JSON или например пренасочване към друга страница. -Във всеки момент от жизнения цикъл можем да използваме един от следните методи, за да изпратим отговора и да прекратим работата на презентатора: +По всяко време на жизнения цикъл можем с някой от следните методи да изпратим отговор и същевременно да прекратим презентера: -- [Пренасочва |#Redirection] `redirect()`, `redirectPermanent()`, `redirectUrl()` и `forward()`. -- `error()` прекратява работата на водещия [поради грешка |#Error-404-etc]. -- `sendJson($data)` излиза от презентатора и изпраща данни във формат JSON -- `sendTemplate()` излиза от презентатора и незабавно визуализира шаблона -- `sendResponse($response)` излиза от презентатора и изпраща [свой собствен отговор |#Ответы]. -- `terminate()` прекратява участието на водещия без отговор. +- `redirect()`, `redirectPermanent()`, `redirectUrl()` и `forward()` [пренасочват |#Пренасочване] +- `error()` прекратява презентера [поради грешка |#Грешка 404 и др] +- `sendJson($data)` прекратява презентера и [изпраща данни |#Изпращане на JSON] във формат JSON +- `sendTemplate()` прекратява презентера и веднага [рендира шаблон |templates] +- `sendResponse($response)` прекратява презентера и изпраща [собствен отговор |#Отговори] +- `terminate()` прекратява презентера без отговор -**Нящо важно**: ако не кажем изрично какъв отговор трябва да изпрати водещият, отговорът е [визуализиране на шаблони на |#Рендеринг шаблонов] HTML. Защо? Ами защото в 99% от случаите искаме да визуализираме шаблон, така че презентаторът приема това поведение по подразбиране и иска да улесни работата ни. +Ако не извикате никой от тези методи, презентерът автоматично ще пристъпи към рендиране на шаблона. Защо? Защото в 99% от случаите искаме да рендираме шаблон, следователно презентерът приема това поведение като подразбиращо се и иска да ни улесни работата. -Създаване на връзки .[#toc-creating-links] -========================================== +Създаване на връзки +=================== -Презентаторът има метод `link()`, който се използва за създаване на URL връзки към други презентатори. Първият параметър е целевият презентатор и действие, последвани от аргументи, които могат да бъдат предадени като масив: +Презентерът разполага с метод `link()`, с помощта на който могат да се създават URL връзки към други презентери. Първият параметър е целевият презентер и действие, следват предаваните аргументи, които могат да бъдат посочени като масив: ```php $url = $this->link('Product:show', $id); -$url = $this->link('Product:show', [$id, 'lang' => 'en']); +$url = $this->link('Product:show', [$id, 'lang' => 'cs']); ``` -В шаблона създаваме връзки към други водещи и действия, както следва: +В шаблона се създават връзки към други презентери и действия по следния начин: ```latte -страница товара +детайл на продукта ``` -Просто напишете познатата двойка `Presenter:action` вместо истинския URL адрес и включете всички параметри. Трикът е `n:href`, който казва, че този атрибут ще бъде обработен от Latte и ще генерира истинския URL адрес. В Nette изобщо не е необходимо да мислите за URL адреси, а само за презентатори и действия. +Просто вместо реален URL напишете познатата двойка `Presenter:action` и посочете евентуални параметри. Трикът е в `n:href`, което казва, че този атрибут ще бъде обработен от Latte и ще генерира реален URL. В Nette така изобщо не е необходимо да мислите за URL, само за презентери и действия. -За повече информация вижте. [Създаване на връзки |creating-links]. +Повече информация ще намерите в главата [Създаване на URL връзки|creating-links]. -Пренасочване на .[#toc-redirection] -=================================== +Пренасочване +============ -Методите `redirect()` и `forward()` се използват за пренасочване към друг презентатор и имат синтаксис, много сходен с този на метода [link( |#Creating-Links]). +За преход към друг презентер служат методите `redirect()` и `forward()`, които имат много подобен синтаксис на метода [link() |#Създаване на връзки]. -Функцията `forward()` незабавно превключва към новия презентатор без HTTP пренасочване: +Методът `forward()` преминава към новия презентер веднага без HTTP пренасочване: ```php $this->forward('Product:show'); ``` -Пример за временно пренасочване с HTTP код 302 или 303: +Пример за т.нар. временно пренасочване с HTTP код 302 (или 303, ако методът на текущата заявка е POST): ```php $this->redirect('Product:show', $id); ``` -За да постигнете постоянно пренасочване с HTTP код 301, използвайте: +Постоянно пренасочване с HTTP код 301 постигате така: ```php $this->redirectPermanent('Product:show', $id); ``` -Можете да пренасочите към друг URL адрес извън приложението, като използвате метода `redirectUrl()`: +Към друг URL извън приложението може да се пренасочи с метода `redirectUrl()`. Като втори параметър може да се посочи HTTP код, по подразбиране е 302 (или 303, ако методът на текущата заявка е POST): ```php $this->redirectUrl('https://nette.org'); ``` -Пренасочването незабавно прекратява жизнения цикъл на водещия, като хвърля т.нар. изключение за тихо прекратяване `Nette\Application\AbortException`. +Пренасочването веднага прекратява дейността на презентера, като хвърля т.нар. тихо прекратяващо изключение `Nette\Application\AbortException`. -Преди пренасочването може да се изпрати [светкавично съобщение |#Flash-Messages], което ще се покаже в шаблона след пренасочването. +Преди пренасочване може да се изпрати [flash съобщение |#Flash съобщения], т.е. съобщения, които ще бъдат показани в шаблона след пренасочването. -Светкавични съобщения .[#toc-flash-messages] -============================================ +Flash съобщения +=============== -Това са съобщения, които обикновено ви информират за резултата от дадена транзакция. Важна характеристика на флаш съобщенията е, че те са налични в шаблона дори след пренасочване. Дори след като бъдат показани, те ще останат живи още 30 секунди - например в случай, че потребителят неволно опресни страницата - съобщението няма да бъде изгубено. +Това са съобщения, обикновено информиращи за резултата от някаква операция. Важна характеристика на flash съобщенията е, че те са достъпни в шаблона и след пренасочване. Дори след показване остават активни още 30 секунди – например в случай, че поради грешка при прехвърлянето потребителят обнови страницата - съобщението няма да изчезне веднага. -Просто извикайте метода [flashMessage( |api:Nette\Application\UI\Control::flashMessage()] ) и презентаторът ще се погрижи да предаде съобщението на шаблона. Първият аргумент е текстът на съобщението, а вторият незадължителен аргумент е неговият тип (грешка, предупреждение, информация и т.н.). Методът `flashMessage()` връща инстанция на флаш съобщението, за да можем да добавим повече информация. +Достатъчно е да извикате метода [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] и за предаването в шаблона ще се погрижи презентерът. Първият параметър е текстът на съобщението, а незадължителният втори параметър е неговият тип (error, warning, info и др.). Методът `flashMessage()` връща инстанция на flash съобщението, към което могат да се добавят допълнителни информации. ```php -$this->flashMessage('Item was removed.'); -$this->redirect(/* ... */); +$this->flashMessage('Елементът беше изтрит.'); +$this->redirect(/* ... */); // и пренасочваме ``` -В шаблона тези съобщения са налични в променливата `$flashes` като обекти `stdClass`, които съдържат свойства `message` (текст на съобщението), `type` (тип на съобщението) и могат да съдържат вече споменатата информация за потребителя. Извеждаме ги, както следва: +В шаблона тези съобщения са достъпни в променливата `$flashes` като обекти `stdClass`, които съдържат свойства `message` (текст на съобщението), `type` (тип на съобщението) и могат да съдържат вече споменатите потребителски информации. Рендираме ги например така: ```latte {foreach $flashes as $flash} @@ -202,10 +202,10 @@ $this->redirect(/* ... */); ``` -Грешка 404 и т.н. .[#toc-error-404-etc] -======================================= +Грешка 404 и др. +================ -Когато не можем да изпълним дадена заявка, защото например статията, която искаме да покажем, не съществува в базата данни, ще хвърлим грешка 404, като използваме метода `error(string $message = null, int $httpCode = 404)`, който представлява HTTP грешка 404: +Ако не може да се изпълни заявката, например поради това, че статията, която искаме да покажем, не съществува в базата данни, хвърляме грешка 404 с метода `error(?string $message = null, int $httpCode = 404)`. ```php public function renderShow(int $id): void @@ -218,14 +218,13 @@ public function renderShow(int $id): void } ``` -Кодът за грешка в HTTP може да бъде подаден като втори параметър, по подразбиране е 404. Методът работи, като хвърля изключение `Nette\Application\BadRequestException`, след което `Application` предава управлението на представящия грешката. Задачата му е да покаже страница, която информира за грешката. -Предварителният селектор на грешки се задава в [конфигурацията на приложението |configuration]. +HTTP кодът на грешката може да се предаде като втори параметър, по подразбиране е 404. Методът работи така, че хвърля изключение `Nette\Application\BadRequestException`, след което `Application` предава управлението на error-presenter. Което е презентер, чиято задача е да покаже страница, информираща за възникналата грешка. Настройката на error-preseter се извършва в [конфигурацията application|configuration]. -Изпращане на JSON .[#toc-sending-json] -====================================== +Изпращане на JSON +================= -Пример за метод на действие, който изпраща данни във формат JSON и оставя главния модул: +Пример за action-метод, който изпраща данни във формат JSON и прекратява презентера: ```php public function actionData(): void @@ -236,33 +235,59 @@ public function actionData(): void ``` -Постоянни параметри .[#toc-persistent-parameters] -================================================= +Параметри на заявката .{data-version:3.1.14} +============================================ + +Презентерът, както и всеки компонент, получава своите параметри от HTTP заявката. Тяхната стойност можете да разберете с метода `getParameter($name)` или `getParameters()`. Стойностите са низове или масиви от низове, това са по същество сурови данни, получени директно от URL. + +За по-голямо удобство препоръчваме параметрите да се достъпват чрез свойство. Достатъчно е да ги маркирате с атрибута `#[Parameter]`: + +```php +use Nette\Application\Attributes\Parameter; // този ред е важен + +class HomePresenter extends Nette\Application\UI\Presenter +{ + #[Parameter] + public string $theme; // трябва да е public +} +``` -Постоянните параметри се използват за поддържане на състоянието между различните заявки. Стойността им остава същата дори след щракване върху връзката. За разлика от данните за сесията, те се предават в URL адреса. Това става напълно автоматично, така че не е необходимо да ги посочвате изрично в `link()` или `n:href`. +При свойството препоръчваме да посочите и типа данни (напр. `string`) и Nette според него автоматично претипира стойността. Стойностите на параметрите могат също да бъдат [валидирани |#Валидация на параметри]. -Пример за използване? Имате многоезично приложение. Действителният език е параметър, който трябва да бъде част от URL адреса по всяко време. Но би било изключително досадно да го включвате във всяка връзка. Затова го правите постоянен параметър с име `lang` и той ще се пренася сам. Страхотно! +При създаване на връзка може директно да се зададе стойност на параметрите: -Създаването на постоянен параметър е изключително лесно в Nette. Просто създайте публично свойство и го маркирайте с атрибута: (преди се използваше `/** @persistent */` ) +```latte +кликни +``` + + +Персистентни параметри +====================== + +Персистентните параметри служат за поддържане на състоянието между различни заявки. Тяхната стойност остава същата и след кликване върху връзка. За разлика от данните в сесията, те се пренасят в URL. И това става напълно автоматично, не е необходимо да се посочват изрично в `link()` или `n:href`. + +Пример за употреба? Имате многоезично приложение. Текущият език е параметър, който трябва постоянно да бъде част от URL. Но би било изключително уморително да го посочвате във всяка връзка. Така че го правите персистентен параметър `lang` и той ще се пренася сам. Страхотно! + +Създаването на персистентен параметър в Nette е изключително лесно. Достатъчно е да създадете публично свойство и да го маркирате с атрибут: (преди се използваше `/** @persistent */`) ```php -use Nette\Application\Attributes\Persistent; // този ред е важен +use Nette\Application\Attributes\Persistent; // този ред е важен class ProductPresenter extends Nette\Application\UI\Presenter { #[Persistent] - public string $lang; // трябва да бъдат публични + public string $lang; // трябва да е public } ``` -Ако `$this->lang` има стойност като `'en'`, то връзките, създадени с помощта на `link()` или `n:href`, ще съдържат и параметъра `lang=en`. И когато върху връзката се щракне, тя отново ще бъде `$this->lang = 'en'`. +Ако `$this->lang` има стойност например `'en'`, то и връзките, създадени с помощта на `link()` или `n:href`, ще съдържат параметъра `lang=en`. И след кликване върху връзката отново ще бъде `$this->lang = 'en'`. -За свойствата препоръчваме да включите типа данни (например `string`), а също така можете да включите стойност по подразбиране. Стойностите на параметрите могат да бъдат [валидирани |#Validation of Persistent Parameters]. +При свойството препоръчваме да посочите и типа данни (напр. `string`) и можете да посочите и стойност по подразбиране. Стойностите на параметрите могат да бъдат [валидирани |#Валидация на параметри]. -Постоянните параметри се предават между всички действия на даден презентатор по подразбиране. За да ги предадете между няколко водещи, трябва да ги дефинирате или: +Персистентните параметри стандартно се пренасят между всички действия на дадения презентер. За да се пренасят и между няколко презентера, е необходимо да се дефинират или: -- в общ предшественик, от който презентаторите наследяват -- в чертата, която презентаторите използват: +- в общ родител, от който презентерите наследяват +- в trait, който презентерите използват: ```php trait LanguageAware @@ -277,48 +302,42 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -Можете да променяте стойността на постоянен параметър, когато създавате връзка: +При създаване на връзка може да се промени стойността на персистентния параметър: ```latte -detail in Czech +детайл на български ``` -Или може да бъде *ресетнат*, т.е. да бъде премахнат от URL адреса. След това той ще приеме стойността си по подразбиране: +Или може да бъде *ресетнат*, т.е. премахнат от URL. Тогава ще приеме своята стойност по подразбиране: ```latte -click +кликни ``` -Интерактивни компоненти .[#toc-interactive-components] -====================================================== +Интерактивни компоненти +======================= -Презентаторите имат вградена система от компоненти. Компонентите са отделни единици за многократна употреба, които поставяме в презентаторите. Това могат да бъдат [формуляри |forms:in-presenter], решетки за данни, менюта, изобщо всичко, което има смисъл да се използва многократно. +Презентерите имат вградена компонентна система. Компонентите са самостоятелни цялости за многократна употреба, които вмъкваме в презентерите. Могат да бъдат [формуляри |forms:in-presenter], datagrid-ове, менюта, всъщност всичко, което има смисъл да се използва многократно. -Как се поставят и впоследствие използват компонентите в презентатора? Това е обяснено в глава [Компоненти |components]. Дори ще разберете какво общо имат те с Холивуд. +Как се вмъкват компоненти в презентера и след това се използват? Това ще научите в главата [Компоненти |components]. Дори ще разберете какво общо имат с Холивуд. -Къде мога да купя някои компоненти? На страницата [Componette |https://componette.org] можете да намерите някои компоненти с отворен код и други добавки за Nette, които са създадени и се разпространяват от общността на рамката Nette. +А къде мога да намеря компоненти? На страницата [Componette |https://componette.org/search/component] ще намерите open-source компоненти, както и редица други добавки за Nette, които са поставени тук от доброволци от общността около фреймуърка. -Навлезте по-дълбоко в .[#toc-going-deeper] -========================================== +Навлизаме в дълбочина +===================== .[tip] -Това, което показахме досега в тази глава, вероятно ще бъде достатъчно. Следващите редове са за тези, които се интересуват от презентаторите обстойно и искат да знаят всичко. - +С това, което показахме досега в тази глава, най-вероятно ще се справите напълно. Следващите редове са предназначени за тези, които се интересуват от презентерите в дълбочина и искат да знаят абсолютно всичко. -Изисквания и параметри .[#toc-requirement-and-parameters] ---------------------------------------------------------- -Заявката, която се обработва от водещия, е обектът [api:Nette\Application\Request] и се връща от метода на водещия `getRequest()`. Тя включва масив от параметри и всеки от тях принадлежи или на някой от компонентите, или директно на водещия (който всъщност също е компонент, макар и специален). Затова Nette преразпределя параметрите и преминава между отделните компоненти (и водещия), като извиква метода `loadState(array $params)`. Параметрите могат да бъдат получени чрез метода `getParameters(): array`, а поотделно чрез `getParameter($name)`. Стойностите на параметрите са низове или масиви от низове, те по същество са необработени данни, получени директно от URL адреса. - - -Утвърждаване на постоянни параметри .[#toc-validation-of-persistent-parameters] -------------------------------------------------------------------------------- +Валидация на параметри +---------------------- -Стойностите на [постоянните параметри, |#persistent parameters] получени от URL адреси, се записват в свойствата чрез метода `loadState()`. Той също така проверява дали типът данни, посочен в свойството, съвпада, в противен случай ще отговори с грешка 404 и страницата няма да бъде показана. +Стойностите на [параметрите на заявката |#Параметри на заявката] и [персистентните параметри |#Персистентни параметри], получени от URL, се записват в свойствата от метода `loadState()`. Той също така проверява дали съответства типът данни, посочен при свойството, в противен случай отговаря с грешка 404 и страницата не се показва. -Никога не се доверявайте сляпо на постоянните параметри, тъй като те лесно могат да бъдат пренаписани от потребителя в URL адреса. Например, така проверяваме дали `$this->lang` е сред поддържаните езици. Добър начин да направите това е да пренастроите метода `loadState()`, споменат по-горе: +Никога не вярвайте сляпо на параметрите, защото те могат лесно да бъдат презаписани от потребителя в URL. Така например ще проверим дали езикът `$this->lang` е сред поддържаните. Подходящ начин е да презапишем споменатия метод `loadState()`: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -329,7 +348,7 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function loadState(array $params): void { parent::loadState($params); // тук се задава $this->lang - // следва проверката на потребителската стойност: + // следва собствена проверка на стойността: if (!in_array($this->lang, ['en', 'cs'])) { $this->error(); } @@ -338,43 +357,45 @@ class ProductPresenter extends Nette\Application\UI\Presenter ``` -Запазване и възстановяване на заявка .[#toc-save-and-restore-the-request] -------------------------------------------------------------------------- +Запазване и възстановяване на заявка +------------------------------------ -Можете да запазите текущата заявка в сесия или да я възстановите от сесия и да позволите на водещия да я изпълни отново. Това е полезно, например когато потребителят попълни формуляр и срокът му на влизане изтече. За да избегнем загубата на данни, преди да пренасочим към страницата за регистрация, запазваме текущата заявка в сесията с функцията `$reqId = $this->storeRequest()`, която връща идентификатора като кратък низ и го предава като параметър на водещия за регистрация. +Заявката, която обработва презентерът, е обект [api:Nette\Application\Request] и се връща от метода на презентера `getRequest()`. -След като влезем в системата, извикваме метода `$this->restoreRequest($reqId)`, който извлича заявката от сесията и я препраща към нея. Методът проверява дали заявката е създадена от същия потребител, който в момента е влязъл в системата. Ако е влязъл друг потребител или ключът е невалиден, не се прави нищо и програмата продължава. +Текущата заявка може да се запази в сесията или обратно, да се възстанови от нея и да се остави презентерът да я изпълни отново. Това е полезно например в ситуация, когато потребителят попълва формуляр и му изтече сесията. За да не загуби данните, преди пренасочването към страницата за вход запазваме текущата заявка в сесията с помощта на `$reqId = $this->storeRequest()`, което връща нейния идентификатор под формата на кратък низ и го предаваме като параметър на презентера за вход. -Вижте глава [Как да се върнете на предишната страница |best-practices:restore-request]. +След влизане извикваме метода `$this->restoreRequest($reqId)`, който извлича заявката от сесията и пренасочва към нея. Методът при това проверява дали заявката е създадена от същия потребител, който сега се е вписал. Ако се е вписал друг потребител или ключът е невалиден, не прави нищо и програмата продължава нататък. +Вижте ръководството [Как да се върнем към предишна страница |best-practices:restore-request]. -Канонизация .[#toc-canonization] --------------------------------- -Презентаторите имат една наистина чудесна функция, която подобрява SEO. Те автоматично предотвратяват съществуването на дублирано съдържание на различни URL адреси. Ако няколко URL адреса водят до определена дестинация, например `/index` и `/index?page=1`, рамката определя един от тях за основен (каноничен) URL адрес и пренасочва останалите към него, като използва код 301 HTTP. Това не позволява на търсачките да индексират страниците два пъти и да влошат класирането им. +Канонизация +----------- + +Презентерите имат една наистина страхотна характеристика, която допринася за по-добро SEO (оптимизация за намиране в интернет). Те автоматично предотвратяват съществуването на дублирано съдържание на различни URL адреси. Ако към определена цел водят няколко URL адреса, напр. `/index` и `/index?page=1`, фреймуъркът определя един от тях за основен (каноничен) и останалите пренасочва към него с помощта на HTTP код 301. Благодарение на това търсачките не индексират страниците ви два пъти и не размиват техния page rank. -Този процес се нарича канонизиране. Каноничният URL адрес е URL адрес, генериран от [маршрут |routing], обикновено първият съвпадащ маршрут в колекцията. +Този процес се нарича канонизация. Каноничният URL е този, който генерира [рутерът|routing], обикновено първият съответстващ маршрут в колекцията. -Канонизацията е разрешена по подразбиране и може да бъде забранена с помощта на `$this->autoCanonicalize = false`. +Канонизацията е включена по подразбиране и може да се изключи чрез `$this->autoCanonicalize = false`. -Пренасочването не се извършва при заявка AJAX или POST, тъй като това ще доведе до загуба на данни или няма да има полза за SEO. +Пренасочване не се извършва при AJAX или POST заявка, защото би довело до загуба на данни или не би имало добавена стойност от гледна точка на SEO. -Можете също така да извикате канонизацията ръчно с метода `canonicalize()`, който, както и методът `link()`, приема като аргументи водещия, действията и параметрите. Тя създава връзка и я сравнява с текущия URL адрес. Ако те са различни, се пренасочва към генерираната връзка. +Канонизацията можете да извикате и ръчно с помощта на метода `canonicalize()`, на който, подобно на метода `link()`, се предават презентер, действие и параметри. Той създава връзка и я сравнява с текущия URL адрес. Ако се различават, пренасочва към генерираната връзка. ```php -public function actionShow(int $id, string $slug = null): void +public function actionShow(int $id, ?string $slug = null): void { $realSlug = $this->facade->getSlugForId($id); - // пренасочва, ако $slug е различен от $realSlug + // пренасочва, ако $slug се различава от $realSlug $this->canonicalize('Product:show', [$id, $realSlug]); } ``` -Събития .[#toc-events] ----------------------- +Събития +------- -В допълнение към методите `startup()`, `beforeRender()` и `shutdown()`, които се извикват като част от жизнения цикъл на презентатора, можете да дефинирате други функции, които да се извикват автоматично. Презентаторът дефинира така наречените [събития |nette:glossary#Events], а вие добавяте техните обработчици към масивите `$onStartup`, `$onRender` и `$onShutdown`. +Освен методите `startup()`, `beforeRender()` и `shutdown()`, които се извикват като част от жизнения цикъл на презентера, могат да се дефинират и други функции, които да се извикват автоматично. Презентерът дефинира т.нар. [събития |nette:glossary#Събития events], чиито хендлъри добавяте към масивите `$onStartup`, `$onRender` и `$onShutdown`. ```php class ArticlePresenter extends Nette\Application\UI\Presenter @@ -388,23 +409,23 @@ class ArticlePresenter extends Nette\Application\UI\Presenter } ``` -Обработващите в масива `$onStartup` се извикват непосредствено преди метода `startup()`, след това `$onRender` между `beforeRender()` и `render()` и накрая `$onShutdown` точно преди `shutdown()`. +Хендлърите в масива `$onStartup` се извикват точно преди метода `startup()`, след това `$onRender` между `beforeRender()` и `render()` и накрая `$onShutdown` точно преди `shutdown()`. -Отговори .[#toc-responses] --------------------------- +Отговори +-------- -Отговорът, върнат от водещия, е обект, който реализира интерфейса [api:Nette\Application\Response]. Има няколко готови отговора: +Отговорът, който връща презентерът, е обект, имплементиращ интерфейса [api:Nette\Application\Response]. На разположение са редица готови отговори: -- [api:Nette\Application\Responses\CallbackResponse] - изпраща обратно повикване +- [api:Nette\Application\Responses\CallbackResponse] - изпраща callback - [api:Nette\Application\Responses\FileResponse] - изпраща файл -- [api:Nette\Application\Responses\ForwardResponse] - напред () +- [api:Nette\Application\Responses\ForwardResponse] - forward() - [api:Nette\Application\Responses\JsonResponse] - изпраща JSON -- [api:Nette\Application\Responses\RedirectResponse] - пренасочвания +- [api:Nette\Application\Responses\RedirectResponse] - пренасочване - [api:Nette\Application\Responses\TextResponse] - изпраща текст - [api:Nette\Application\Responses\VoidResponse] - празен отговор -Отговорите се изпращат по метода `sendResponse()`: +Отговорите се изпращат с метода `sendResponse()`: ```php use Nette\Application\Responses; @@ -415,7 +436,7 @@ $this->sendResponse(new Responses\TextResponse('Hello Nette!')); // Изпраща файл $this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf')); -// Изпраща обратна връзка +// Отговорът ще бъде callback $callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse) { if ($httpResponse->getHeader('Content-Type') === 'text/html') { echo '

    Hello

    '; @@ -425,10 +446,55 @@ $this->sendResponse(new Responses\CallbackResponse($callback)); ``` -Допълнително четене .[#toc-further-reading] -=========================================== +Ограничаване на достъпа с `#[Requires]` .{data-version:3.2.2} +------------------------------------------------------------- + +Атрибутът `#[Requires]` предоставя разширени възможности за ограничаване на достъпа до презентери и техните методи. Може да се използва за специфициране на HTTP методи, изискване на AJAX заявка, ограничаване до същия произход (same origin) и достъп само чрез пренасочване (forward). Атрибутът може да се прилага както към класове на презентери, така и към отделни методи `action()`, `render()`, `handle()` и `createComponent()`. + +Можете да посочите следните ограничения: +- на HTTP методи: `#[Requires(methods: ['GET', 'POST'])]` +- изискване на AJAX заявка: `#[Requires(ajax: true)]` +- достъп само от същия произход: `#[Requires(sameOrigin: true)]` +- достъп само чрез forward: `#[Requires(forward: true)]` +- ограничение до конкретни действия: `#[Requires(actions: 'default')]` + +Подробности ще намерите в ръководството [Как да използваме атрибута Requires |best-practices:attribute-requires]. + + +Проверка на HTTP метода +----------------------- + +Презентерите в Nette автоматично проверяват HTTP метода на всяка входяща заявка. Причината за тази проверка е предимно сигурността. Стандартно са разрешени методите `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH`. + +Ако искате да разрешите допълнително например метода `OPTIONS`, използвайте за това атрибута `#[Requires]` (от Nette Application v3.2): + +```php +#[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])] +class MyPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Във версия 3.1 проверката се извършва в `checkHttpMethod()`, която проверява дали методът, специфициран в заявката, се съдържа в масива `$presenter->allowedMethods`. Добавянето на метод направете така: + +```php +class MyPresenter extends Nette\Application\UI\Presenter +{ + protected function checkHttpMethod(): void + { + $this->allowedMethods[] = 'OPTIONS'; + parent::checkHttpMethod(); + } +} +``` + +Важно е да се подчертае, че ако разрешите метода `OPTIONS`, трябва впоследствие и да го обслужите подобаващо в рамките на вашия презентер. Методът често се използва като т.нар. preflight request, който браузърът автоматично изпраща преди реалната заявка, когато е необходимо да се провери дали заявката е разрешена от гледна точка на CORS (Cross-Origin Resource Sharing) политиката. Ако разрешите метода, но не имплементирате правилен отговор, това може да доведе до неконсистентности и потенциални проблеми със сигурността. + + +Друго четене +============ -- [Инжектиране на методи и атрибути |best-practices:inject-method-attribute] -- [Съставяне на презентатори от черти |best-practices:presenter-traits] -- [Предаване на настройки към презентатори |best-practices:passing-settings-to-presenters] -- [Как да се върнете към предишна страница |best-practices:restore-request] +- [Методи и атрибути inject |best-practices:inject-method-attribute] +- [Сглобяване на презентери от trait |best-practices:presenter-traits] +- [Предаване на настройки към презентери |best-practices:passing-settings-to-presenters] +- [Как да се върнем към предишна страница |best-practices:restore-request] diff --git a/application/bg/routing.texy b/application/bg/routing.texy index 96e134bc79..d3a66e32fa 100644 --- a/application/bg/routing.texy +++ b/application/bg/routing.texy @@ -1,29 +1,28 @@ -Маршрутизиране -************** +Маршрутизация +*************
    -Маршрутизаторът ще се погрижи за всичко, свързано с URL адресите, така че повече няма да ви се налага да мислите за тях. Нека ви покажем: +Рутерът отговаря за всичко около URL адресите, за да не се налага вие да мислите за тях. Ще покажем: -- как да настроите маршрутизатора така, че URL адресите да са такива, каквито искате да бъдат -- Как да настроите SEO и пренасочвания. -- как да напишете собствен маршрутизатор +- как да настроим рутера, така че URL адресите да са според представите ни +- ще поговорим за SEO и пренасочване +- и ще покажем как да напишем собствен рутер
    -По-човешките URL адреси (или готините или красивите URL адреси) са по-използваеми, по-добре запомнящи се и имат положително въздействие върху SEO. Nette взема това предвид и напълно удовлетворява желанията на разработчиците. Можете да проектирате структурата на URL адресите на приложението си точно по желания от вас начин. -Можете да го проектирате дори след като приложението е готово, тъй като това може да стане без промени в кода или шаблона. Тя е дефинирана елегантно на едно [място |#Integration], в маршрутизатора, а не е разпръсната под формата на анотации навсякъде из презентаторите. +По-човешките URL адреси (или също cool или pretty URL) са по-използваеми, по-лесно запомнящи се и допринасят положително за SEO. Nette мисли за това и излиза напълно в помощ на разработчиците. Можете да проектирате за своето приложение точно такава структура на URL адресите, каквато искате. Можете да я проектирате дори когато приложението вече е готово, защото това става без намеса в кода или шаблоните. Дефинира се по елегантен начин на едно [единствено място |#Включване в приложението], в рутера, и не е разпръснато под формата на анотации във всички презентери. -Маршрутизаторът в Nette е специален, защото е **двупосочен** - той може да декодира URL адресите на HTTP заявките и да създава връзки. Ето защо той играе важна роля в [приложението Nette |how-it-works#Nette-Application], тъй като той решава кой главен потребител и действие ще изпълни текущата заявка, а също така се използва за [генериране на URL адреси |creating-links] в шаблона и т.н. +Рутерът в Nette е изключителен с това, че е **двупосочен.** Той може както да декодира URL в HTTP заявка, така и да създава връзки. Следователно играе ключова роля в [Nette Application |how-it-works#Nette Application], защото от една страна решава кой презентер и действие ще изпълняват текущата заявка, но също така се използва за [генериране на URL |creating-links] в шаблон и т.н. -Маршрутизаторът обаче не се ограничава само до това приложение, можете да го използвате в приложения, в които презентаторите изобщо не се използват, за REST API и т.н. За повече подробности вижте раздела за [разделно използване |#separated-usage]. +Въпреки това, рутерът не е ограничен само до тази употреба, можете да го използвате в приложения, където изобщо не се използват презентери, за REST API и т.н. Повече в частта [#Самостоятелно използване]. -Събиране на маршрути .[#toc-route-collection] -============================================= +Колекция от маршрути +==================== -Най-красивият начин за дефиниране на URL адреси в приложението е класът [api:Nette\Application\Routers\RouteList]. Дефиницията се състои от списък с така наречените маршрути, т.е. URL маски и свързани с тях преемници и действия, използващи прост API. Не е необходимо да назоваваме маршрутите. +Най-приятният начин за дефиниране на формата на URL адресите в приложението предлага класът [api:Nette\Application\Routers\RouteList]. Дефиницията се състои от списък с т.нар. маршрути, т.е. маски на URL адреси и към тях асоциирани презентери и действия с помощта на просто API. Не е необходимо да именуваме маршрутите по никакъв начин. ```php $router = new Nette\Application\Routers\RouteList; @@ -32,180 +31,180 @@ $router->addRoute('article/', 'Article:view'); // ... ``` -В примера се казва, че ако отворим `https://any-domain.com/rss.xml` с действие `rss` и т.н. Ако не бъде намерен подходящ маршрут, приложението Nette отговаря с [BadRequestException |api:Nette\Application\BadRequestException], което се показва на потребителя като страница за грешка 404 Not Found. +Примерът казва, че ако в браузъра отворим `https://domain.com/rss.xml`, ще се покаже презентерът `Feed` с действие `rss`, ако `https://domain.com/article/12`, ще се покаже презентерът `Article` с действие `view` и т.н. В случай на ненамерен подходящ маршрут, Nette Application реагира с хвърляне на изключение [BadRequestException |api:Nette\Application\BadRequestException], което се показва на потребителя като страница за грешка 404 Not Found. -Ред на маршрутите .[#toc-order-of-routes] ------------------------------------------ +Ред на маршрутите +----------------- -Редът на изброяване на маршрутите е много важен**, тъй като те се оценяват последователно отгоре надолу. Правилото е, че декларираме маршрути **от специфични към общи**: +Абсолютно **ключов е редът**, в който са посочени отделните маршрути, защото те се оценяват последователно отгоре надолу. Важи правилото, че маршрутите декларираме **от специфични към общи**: ```php -// ГРЕШКА: 'rss.xml' съвпада с първия маршрут и се третира неправилно като . +// ГРЕШНО: 'rss.xml' се улавя от първия маршрут и разбира този низ като $router->addRoute('', 'Article:view'); $router->addRoute('rss.xml', 'Feed:rss'); -// ПОВЕЧЕ +// ДОБРЕ $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('', 'Article:view'); ``` -Маршрутите също се оценяват отгоре надолу при генерирането на препратки: +Маршрутите се оценяват отгоре надолу и при генериране на връзки: ```php -// ГРЕШКА: генерира препратка към 'Feed:rss' като 'admin/feed/rss' +// ГРЕШНО: връзка към 'Feed:rss' генерира като 'admin/feed/rss' $router->addRoute('admin//', 'Admin:default'); $router->addRoute('rss.xml', 'Feed:rss'); -// ПОВЕЧЕ +// ДОБРЕ $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('admin//', 'Admin:default'); ``` -Няма да крием от вас, че за правилното създаване на списък са необходими известни умения. Докато се научите, панелът за [маршрутизиране |#Debugging-Router] ще бъде полезен инструмент. +Няма да крием от вас, че правилното съставяне на маршрути изисква известна умелост. Преди да я усвоите, полезен помощник ще ви бъде [панелът за маршрутизация |#Дебъгване на рутера]. -Маска и параметри .[#toc-mask-and-parameters] ---------------------------------------------- +Маска и параметри +----------------- -Маската описва относителен път, базиран на корена на сайта. Най-простата маска е статичен URL адрес: +Маската описва относителния път от коренната директория на уебсайта. Най-простата маска е статичен URL: ```php $router->addRoute('products', 'Products:default'); ``` -Маските често съдържат така наречените **параметри**. Те се поставят в ъглови скоби (напр, ``) и се предават на целевия водещ, например на метода `renderShow(int $year)` или на постоянния параметър `$year`: +Често маските съдържат т.нар. **параметри**. Те са посочени в ъглови скоби (напр. ``) и се предават на целевия презентер, например на метода `renderShow(int $year)` или на персистентния параметър `$year`: ```php $router->addRoute('chronicle/', 'History:show'); ``` -В примера се казва, че ако отворим `https://any-domain.com/chronicle/2020` и действието `show` с параметър `year: 2020`. +Примерът казва, че ако в браузъра отворим `https://example.com/chronicle/2020`, ще се покаже презентерът `History` с действие `show` и параметър `year: 2020`. -Можем да зададем стойност по подразбиране за параметрите директно в маската и по този начин тя става незадължителна: +На параметрите можем да зададем стойност по подразбиране директно в маската и така те стават незадължителни: ```php $router->addRoute('chronicle/', 'History:show'); ``` -Маршрутът вече ще приема URL адреса `https://any-domain.com/chronicle/` с параметъра `year`: 2020`. +Маршрутът сега ще приема и URL `https://example.com/chronicle/`, който отново ще покаже `History:show` с параметър `year: 2020`. -Разбира се, името на водещия и действието също могат да бъдат параметри. Например: +Параметърът може, разбира се, да бъде и името на презентера и действието. Например така: ```php $router->addRoute('/', 'Home:default'); ``` -Този маршрут приема например URL адреси под формата съответно на `/article/edit` и `/catalog/list` и ги превръща в презентатори и действия съответно `Article:edit` и `Catalog:list`. +Посоченият маршрут приема напр. URL във формата `/article/edit` или също `/catalog/list` и ги разбира като презентери и действия `Article:edit` и `Catalog:list`. -Той също така дава на `presenter` и `action` стойности по подразбиране за `Home` и `default` и следователно те не са задължителни. Затова маршрутът също така взема URL адреса `/article` и го превежда като `Article:default`. Или обратното, връзка към `Product:default` генерира пътя `/product`, а връзка към стандартния `Home:default` генерира пътя `/`. +Същевременно дава на параметрите `presenter` и `action` стойности по подразбиране `Home` и `default` и следователно те също са незадължителни. Така че маршрутът приема и URL във формата `/article` и го разбира като `Article:default`. Или обратно, връзка към `Product:default` генерира пътя `/product`, връзка към подразбиращия се `Home:default` пътя `/`. -Маската може да описва не само относителен път, базиран на корена на сайта, но и абсолютен път, ако започва с наклонена черта, или дори цял абсолютен URL адрес, ако започва с две наклонени черти: +Маската може да описва не само относителния път от коренната директория на уебсайта, но и абсолютния път, ако започва с наклонена черта, или дори целия абсолютен URL, ако започва с две наклонени черти: ```php -// относителен път до корена на приложението +// относително към document root $router->addRoute('/', /* ... */); -// абсолютен път, относително към името на хоста на сървъра +// абсолютен път (относителен към домейна) $router->addRoute('//', /* ... */); -// абсолютен URL адрес, включително името на хоста (относително към схемата) +// абсолютен URL, включително домейна (относителен към схемата) $router->addRoute('//.example.com//', /* ... */); -// абсолютен URL адрес, включително схемата +// абсолютен URL, включително схемата $router->addRoute('https://.example.com//', /* ... */); ``` -Изрази за валидиране .[#toc-validation-expressions] ---------------------------------------------------- +Валидационни изрази +------------------- -За всеки параметър можете да зададете условие за валидиране, като използвате [регулярен израз |https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Например, нека зададем `id` само цифрово, като използваме `\d+` regexp: +За всеки параметър може да се установи валидационно условие с помощта на [регулярен израз|https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Например на параметъра `id` ще определим, че може да приема само цифри с помощта на регулярен израз `\d+`: ```php $router->addRoute('/[/]', /* ... */); ``` -По подразбиране регулярният израз за всички параметри е `[^/]+`т.е. всичко освен наклонената черта. Ако параметърът трябва да съответства и на наклонената черта, задаваме регулярния израз на `.+`. +Регулярният израз по подразбиране за всички параметри е `[^/]+`, т.е. всичко освен наклонена черта. Ако параметърът трябва да приема и наклонени черти, ще посочим израз `.+`: ```php -// приема https://example.com/a/b/c, път - 'a/b/c' +// приема https://example.com/a/b/c, path ще бъде 'a/b/c' $router->addRoute('', /* ... */); ``` -Незадължителни последователности .[#toc-optional-sequences] ------------------------------------------------------------ +Незадължителни последователности +-------------------------------- -С квадратни скоби са обозначени допълнителните части на маската. Всяка част от маската може да бъде посочена като незадължителна, включително тези, които съдържат параметри: +В маската могат да се маркират незадължителни части с помощта на квадратни скоби. Незадължителна може да бъде всяка част от маската, в нея могат да се намират и параметри: ```php $router->addRoute('[/]', /* ... */); -// Приети URL адреси: Параметри: -// /en/download lang => en, name => download -// /download lang => null, name => download +// Приема пътища: +// /cs/download => lang => cs, name => download +// /download => lang => null, name => download ``` -Разбира се, когато даден параметър е част от незадължителна последователност, той също става незадължителен. Ако няма стойност по подразбиране, тя ще бъде нула. +Когато параметърът е част от незадължителна последователност, той става разбира се също незадължителен. Ако няма посочена стойност по подразбиране, тогава ще бъде null. -Незадължителните части също могат да бъдат в домейна: +Незадължителни части могат да бъдат и в домейна: ```php $router->addRoute('//[.]example.com//', /* ... */); ``` -Последователностите могат да бъдат свободно вложени и обединени: +Последователностите могат да се влагат и комбинират свободно: ```php $router->addRoute( - '[[-]/][page-]', + '[[-]/][/page-]', 'Home:default', ); -//получени URL адреси: -// /ru/hello -// /en-us/hello -// /hello -// /hello/page-12 +// Приема пътища: +// /cs/hello +// /en-us/hello +// /hello +// /hello/page-12 ``` -Генераторът на URL адреси се опитва да направи URL адреса възможно най-кратък, така че това, което може да се пропусне, се пропуска. Затова например маршрутът `index[.html]` генерира пътя `/index`. Можете да промените това поведение, като напишете възклицателен знак след лявата квадратна скоба: +При генериране на URL се стремим към най-краткия вариант, така че всичко, което може да се пропусне, се пропуска. Затова например маршрутът `index[.html]` генерира пътя `/index`. Обръщането на поведението е възможно чрез посочване на удивителен знак след лявата квадратна скоба: ```php -// взема /hello и /hello.html, генерира /hello +// приема /hello и /hello.html, генерира /hello $router->addRoute('[.html]', /* ... */); -// взема /hello и /hello.html, генерира /hello.html +// приема /hello и /hello.html, генерира /hello.html $router->addRoute('[!.html]', /* ... */); ``` -Незадължителни параметри (т.е. параметри, които имат стойност по подразбиране) без квадратни скоби се държат така, сякаш са обвити по този начин: +Незадължителните параметри (т.е. параметри, имащи стойност по подразбиране) без квадратни скоби се държат по същество така, сякаш са оградени по следния начин: ```php $router->addRoute('//', /* ... */); -// е равно на: +// съответства на това: $router->addRoute('[/[/[]]]', /* ... */); ``` -За да промените начина, по който се генерира най-дясната наклонена черта, т.е. вместо `/home/` да получите `/home`, конфигурирайте маршрута по този начин: +Ако искаме да повлияем на поведението на крайната наклонена черта, така че напр. вместо `/home/` да се генерира само `/home`, това може да се постигне така: ```php $router->addRoute('[[/[/]]]', /* ... */); ``` -Символи за заместване .[#toc-wildcards] ---------------------------------------- +Заместващи знаци +---------------- -В маската на абсолютния път можем да използваме следните заместващи символи, за да избегнем например необходимостта да записваме домейн в маската, който може да е различен в средата за разработка и в производствената среда: +В маската на абсолютния път можем да използваме следните заместващи знаци и така да избегнем напр. необходимостта да записваме в маската домейна, който може да се различава в среда за разработка и продукционна среда: -- `%tld%` = домейн от първо ниво, например `com` или `org` -- `%sld%` = домейн от второ ниво, напр, `example` -- `%domain%` = домейн без поддомейни, напр, `example.com` -- `%host%` = целият хост, напр, `www.example.com` -- `%basePath%` = път до главната директория +- `%tld%` = top level domain, напр. `com` или `org` +- `%sld%` = second level domain, напр. `example` +- `%domain%` = домейн без субдомейни, напр. `example.com` +- `%host%` = цял хост, напр. `www.example.com` +- `%basePath%` = път към коренната директория ```php $router->addRoute('//www.%domain%/%basePath%//', /* ... */); @@ -213,10 +212,10 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ @@ -225,7 +224,7 @@ $router->addRoute('/[/]', [ ]); ``` -Или можем да използваме тази форма, като отбележим пренаписването на израза за регулярна проверка: +За по-подробна спецификация може да се използва още по-разширена форма, където освен стойностите по подразбиране можем да зададем и други свойства на параметрите, като например валидационен регулярен израз (виж параметъра `id`): ```php use Nette\Routing\Route; @@ -243,19 +242,19 @@ $router->addRoute('/[/]', [ ]); ``` -Тези по-подробни формати са полезни за добавяне на повече метаданни. +Важно е да се отбележи, че ако параметрите, дефинирани в масива, не са посочени в маската на пътя, техните стойности не могат да бъдат променени, дори и с помощта на query параметри, посочени след въпросителния знак в URL. -Филтри и преводи .[#toc-filters-and-translations] -------------------------------------------------- +Филтри и преводи +---------------- -Добра практика е изходният код да бъде написан на английски език, но какво да правите, ако URL адресът на уебсайта ви трябва да бъде преведен на друг език? +Изходните кодове на приложението пишем на английски, но ако уебсайтът трябва да има български URL адреси, тогава простото маршрутизиране от типа: ```php $router->addRoute('/', 'Home:default'); ``` -ще генерира английски URL адреси като `/product/123` или `/cart`. Ако искаме презентаторите и действията в URL адреса да бъдат преведени на немски език (напр. `/produkt/123` или `/einkaufswagen`), можем да използваме речник за превод. За да го добавим, вече се нуждаем от "по-ясна" версия на втория параметър: +ще генерира английски URL адреси, като например `/product/123` или `/cart`. Ако искаме презентерите и действията в URL да бъдат представени с български думи (напр. `/produkt/123` или `/kosik`), можем да използваме преводен речник. За неговия запис вече се нуждаем от "по-многословния" вариант на втория параметър: ```php use Nette\Routing\Route; @@ -264,26 +263,26 @@ $router->addRoute('/', [ 'presenter' => [ Route::Value => 'Home', Route::FilterTable => [ - // строка в URL => ведущий + // низ в URL => презентер 'produkt' => 'Product', - 'einkaufswagen' => 'Cart', + 'kosik' => 'Cart', 'katalog' => 'Catalog', ], ], 'action' => [ Route::Value => 'default', Route::FilterTable => [ - 'liste' => 'list', + 'seznam' => 'list', ], ], ]); ``` -За един и същ водещ могат да се използват няколко речникови ключа. Те ще създадат различни псевдоними за него. Последният ключ се счита за каноничния вариант (т.е. този, който ще бъде в генерирания URL адрес). +Повече ключове на преводния речник могат да водят към един и същ презентер. Така към него се създават различни псевдоними. За каноничен вариант (т.е. този, който ще бъде в генерирания URL) се счита последният ключ. -По този начин към всеки параметър може да се приложи таблица за превод. Ако обаче няма превод, се приема оригиналната стойност. Можем да променим това поведение, като добавим `Route::FilterStrict => true`, след което маршрутът ще отхвърли URL адреса, ако стойността не е в речника. +Преводната таблица може по този начин да се използва за всеки параметър. При което, ако преводът не съществува, се взема оригиналната стойност. Това поведение можем да променим, като добавим `Route::FilterStrict => true` и маршрутът тогава ще отхвърли URL, ако стойността не е в речника. -В допълнение към речника за превод като масив можем да дефинираме собствени функции за превод: +Освен преводния речник под формата на масив, могат да се приложат и собствени преводни функции. ```php use Nette\Routing\Route; @@ -299,15 +298,15 @@ $router->addRoute('//', [ ]); ``` -Функцията `Route::FilterIn` извършва преобразуване между параметър в URL адреса и низ, който след това се предава на презентатора, а функцията `FilterOut` осигурява преобразуване в обратна посока. +Функцията `Route::FilterIn` преобразува между параметър в URL и низ, който след това се предава на презентера, функцията `FilterOut` осигурява преобразуването в обратна посока. -Параметрите `presenter`, `action` и `module` вече имат предварително дефинирани филтри, които преобразуват между PascalCase, camelCase и kebab-case, използвани съответно в URL адреса. Стойността по подразбиране на параметрите вече е записана в преобразуваната форма, така че например в случая с презентатора пишем `` вместо ``. +Параметрите `presenter`, `action` и `module` вече имат предварително дефинирани филтри, които преобразуват между стила PascalCase, респ. camelCase, и kebab-case, използван в URL. Стойността по подразбиране на параметрите се записва вече в трансформирана форма, така че например в случая с презентера пишем ``, а не ``. -Общи филтри .[#toc-general-filters] ------------------------------------ +Общи филтри +----------- -В допълнение към филтрите за конкретни параметри можете да дефинирате и общи филтри, които получават асоциативен масив от всички параметри, които могат да променят по какъвто и да е начин, и след това да върнат. Общите филтри се определят от ключа `null`. +Освен филтрите, предназначени за конкретни параметри, можем да дефинираме и общи филтри, които получават асоциативен масив от всички параметри, които могат да модифицират по всякакъв начин и след това ги връщат. Общите филтри дефинираме под ключ `null`. ```php use Nette\Routing\Route; @@ -315,62 +314,85 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => 'Home', 'action' => 'default', - null => [ + '' => [ Route::FilterIn => function (array $params): array { /* ... */ }, Route::FilterOut => function (array $params): array { /* ... */ }, ], ]); ``` -Общите филтри ви дават възможност да персонализирате поведението на даден маршрут по абсолютно всякакъв начин. Можем да ги използваме например за промяна на настройки въз основа на други настройки. Например, превод `` и `` въз основа на текущата стойност на параметъра ``. +Общите филтри дават възможност да се промени поведението на маршрута по абсолютно всякакъв начин. Можем да ги използваме например за модификация на параметри въз основа на други параметри. Например превеждане на `` и `` въз основа на текущата стойност на параметъра ``. -Ако за даден параметър е дефиниран потребителски филтър и едновременно с това съществува общ филтър, потребителският `FilterIn` се изпълнява преди общия, и обратно, общият `FilterOut` се изпълнява преди потребителския. По този начин вътре в общия филтър се намират стойностите на параметрите `presenter` и `action`, записани съответно с PascalCase и camelCase. +Ако параметърът има дефиниран собствен филтър и същевременно съществува общ филтър, се изпълнява собственият `FilterIn` преди общия и обратно, общият `FilterOut` преди собствения. Тоест, вътре в общия филтър стойностите на параметрите `presenter`, респ. `action`, са записани в стил PascalCase, респ. camelCase. -Еднопосочен флаг .[#toc-oneway-flag] ------------------------------------- +Еднопосочни OneWay +------------------ -Еднопосочните маршрути се използват за запазване на функционалността на стари URL адреси, които приложението вече не генерира, но все още приема. Маркираме ги с флага `OneWay`: +Еднопосочните маршрути се използват за запазване на функционалността на стари URL адреси, които приложението вече не генерира, но все още приема. Маркираме ги с флаг `OneWay`: ```php -// старый URL /product-info?id=123 +// стар URL /product-info?id=123 $router->addRoute('product-info', 'Product:detail', $router::ONE_WAY); // нов URL /product/123 $router->addRoute('product/', 'Product:detail'); ``` -При достъп до стария URL адрес презентаторът автоматично пренасочва към новия URL адрес, така че търсачките да не индексират тези страници два пъти (вж. [SEO и канонизация |#seo-and-canonization]). +При достъп до стария URL презентерът автоматично пренасочва към новия URL, така че търсачките няма да индексират тези страници два пъти (виж [#SEO и канонизация]). + + +Динамично маршрутизиране с callback-ове +--------------------------------------- +Динамичното маршрутизиране с callback-ове ви позволява да присвоите на маршрутите директно функции (callback-ове), които се изпълняват, когато даденият път е посетен. Тази гъвкава функционалност ви позволява бързо и ефективно да създавате различни крайни точки (endpoints) за вашето приложение: -Модули .[#toc-modules] ----------------------- +```php +$router->addRoute('test', function () { + echo 'вие сте на адрес /test'; +}); +``` -Ако имаме няколко маршрута, принадлежащи към един и същ модул, можем да използваме `withModule()`, за да ги групираме: +Можете също така да дефинирате в маската параметри, които автоматично се предават на вашия callback: + +```php +$router->addRoute('', function (string $lang) { + echo match ($lang) { + 'cs' => 'Добре дошли в българската версия на нашия уебсайт!', + 'en' => 'Welcome to the English version of our website!', + }; +}); +``` + + +Модули +------ + +Ако имаме повече маршрути, които попадат в общ [модул |directory-structure#Презентери и шаблони], ще използваме `withModule()`: ```php $router = new RouteList; -$router->withModule('Forum') // следните маршрутизатори са част от модула Forum - ->addRoute('rss', 'Feed:rss') // водещият Форум:Feed +$router->withModule('Forum') // следващите маршрути са част от модула Forum + ->addRoute('rss', 'Feed:rss') // презентерът ще бъде Forum:Feed ->addRoute('/') - ->withModule('Admin') // следните маршрутизатори са част от модула Forum:Admin + ->withModule('Admin') // следващите маршрути са част от модула Forum:Admin ->addRoute('sign:in', 'Sign:in'); ``` -Алтернативата е да се използва параметърът `module`: +Алтернатива е използването на параметъра `module`: ```php -// URL адресът manage/dashboard/default се показва в главното меню на Admin:Dashboard +// URL manage/dashboard/default се мапва към презентера Admin:Dashboard $router->addRoute('manage//', [ 'module' => 'Admin', ]); ``` -Поддомейни .[#toc-subdomains] ------------------------------ +Субдомейни +---------- -Колекциите от маршрути могат да бъдат групирани в поддомейни: +Колекциите от маршрути можем да групираме по субдомейни: ```php $router = new RouteList; @@ -379,7 +401,7 @@ $router->withDomain('example.com') ->addRoute('/'); ``` -Можете също така да използвате [заместващи символи |#Wildcards] в името на домейна: +В името на домейна могат да се използват и [#Заместващи знаци]: ```php $router = new RouteList; @@ -388,23 +410,23 @@ $router->withDomain('example.%tld%') ``` -Префикс на пътя .[#toc-path-prefix] ------------------------------------ +Префикс на пътя +--------------- -Колекциите от маршрути могат да бъдат групирани по път в URL адреса: +Колекциите от маршрути можем да групираме по път в URL: ```php $router = new RouteList; $router->withPath('eshop') - ->addRoute('rss', 'Feed:rss') // съответства на URL /eshop/rss - ->addRoute('/'); // съответства на URL /eshop// + ->addRoute('rss', 'Feed:rss') // улавя URL /eshop/rss + ->addRoute('/'); // улавя URL /eshop// ``` -Комбинации .[#toc-combinations] -------------------------------- +Комбинации +---------- -Горните употреби могат да се комбинират: +Горепосочените групирания можем да комбинираме взаимно: ```php $router = (new RouteList) @@ -424,40 +446,40 @@ $router = (new RouteList) ``` -Опции за заявка .[#toc-query-parameters] ----------------------------------------- +Query параметри +--------------- -Маските могат да съдържат и параметри на заявката (параметри след въпросителния знак в URL адреса). Те не могат да дефинират израз за валидиране, но могат да променят името, чрез което се предават на водещия: +Маските могат също да съдържат query параметри (параметри след въпросителния знак в URL). За тях не може да се дефинира валидационен израз, но може да се промени името, под което се предават на презентера: ```php -// използвайте параметъра на заявката 'cat' като 'categoryId' в приложението +// query параметъра 'cat' искаме в приложението да използваме под името 'categoryId' $router->addRoute('product ? id= & cat=', /* ... */); ``` -Параметри на Foo .[#toc-foo-parameters] ---------------------------------------- +Foo параметри +------------- -Сега се задълбочаваме. Параметрите Foo по същество са неназовани параметри, които позволяват да се съпостави регулярен израз. Следният маршрут съвпада с `/index`, `/index.html`, `/index.htm` и `/index.php`: +Сега вече навлизаме по-дълбоко. Foo параметрите са по същество неименувани параметри, които позволяват съвпадение с регулярен израз. Пример е маршрут, приемащ `/index`, `/index.html`, `/index.htm` и `/index.php`: ```php $router->addRoute('index', /* ... */); ``` -Можете също така изрично да посочите низ, който да се използва за генериране на URL адреса. Реда трябва да се намира непосредствено след въпросителния знак. Следващият маршрут е подобен на предишния, но генерира `/index.html` вместо `/index`, тъй като символният низ `.html` е зададен като "генерирана стойност". +Може също така изрично да се дефинира низ, който ще бъде използван при генериране на URL. Низът трябва да бъде поставен директно след въпросителния знак. Следващият маршрут е подобен на предходния, но генерира `/index.html` вместо `/index`, защото низът `.html` е зададен като генерираща стойност: ```php $router->addRoute('index', /* ... */); ``` -Интеграция .[#toc-integration] -============================== +Включване в приложението +======================== -За да свържем маршрутизатора си с приложението, трябва да информираме за това контейнера DI. Най-лесният начин е да се подготви фабрика, която ще създаде обект маршрутизатор, и да се каже на конфигурацията на контейнера да го използва. Да предположим, че напишем метод за това, `App\Router\RouterFactory::createRouter()`: +За да включим създадения рутер в приложението, трябва да кажем за него на DI контейнера. Най-лесният начин е да подготвим фабрика, която ще произведе обекта на рутера, и да съобщим в конфигурацията на контейнера, че трябва да я използва. Да кажем, че за тази цел ще напишем метод `App\Core\RouterFactory::createRouter()`: ```php -namespace App\Router; +namespace App\Core; use Nette\Application\Routers\RouteList; @@ -472,14 +494,14 @@ class RouterFactory } ``` -След това пишем в [конфигурацията |dependency-injection:services]: +В [конфигурацията |dependency-injection:services] след това ще запишем: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter ``` -Всички зависимости, като например връзки към бази данни и т.н., се предават на метода на фабриката като параметри, като се използва [автоматично свързване |dependency-injection:autowiring]: +Всякакви зависимости, например към база данни и т.н., се предават на фабричния метод като негови параметри с помощта на [autowiring|dependency-injection:autowiring]: ```php public static function createRouter(Nette\Database\Connection $db): RouteList @@ -489,25 +511,25 @@ public static function createRouter(Nette\Database\Connection $db): RouteList ``` -SimpleRouter .[#toc-simplerouter] -================================= +SimpleRouter +============ -Много по-прост маршрутизатор от колекция от маршрути е [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Той може да се използва, когато няма нужда от конкретен формат на URL, когато не е наличен `mod_rewrite` (или алтернативи) или когато просто не искаме да се занимаваме с удобни за потребителя URL адреси. +Много по-прост рутер от колекцията от маршрути е [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Използваме го тогава, когато нямаме специални изисквания към формата на URL, когато не е наличен `mod_rewrite` (или негови алтернативи) или когато засега не искаме да се занимаваме с хубави URL адреси. -Той генерира адреси с форма, подобна на тази: +Генерира адреси приблизително в този вид: ``` http://example.com/?presenter=Product&action=detail&id=123 ``` -Параметърът на конструктора `SimpleRouter` е презентаторът и действието по подразбиране, т.е. действието, което ще се изпълни, ако отворим например `http://example.com/` без никакви допълнителни параметри. +Параметърът на конструктора на SimpleRouter е подразбиращият се презентер и действие, към който трябва да се насочи, ако отворим страница без параметри, напр. `http://example.com/`. ```php -// използвайте презентатора 'Home' и действието 'default' +// подразбиращият се презентер ще бъде 'Home' и действието 'default' $router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` -Препоръчваме да дефинирате SimpleRouter директно в [конфигурацията |dependency-injection:services]: +Препоръчваме SimpleRouter директно да се дефинира в [конфигурацията |dependency-injection:services]: ```neon services: @@ -515,22 +537,22 @@ services: ``` -SEO и канонизация .[#toc-seo-and-canonization] -============================================== +SEO и канонизация +================= -Рамката подобрява SEO оптимизацията, като предотвратява дублиращото се съдържание на различни URL адреси. Ако няколко URL адреса сочат към една и съща дестинация, например `/index` и `/index.html`, рамката идентифицира първия като основен (каноничен) и пренасочва останалите към него, като използва HTTP код 301. Това гарантира, че търсачките няма да индексират страниците два пъти и няма да нарушат класирането им. +Фреймуъркът допринася за SEO (оптимизация за намиране в интернет), като предотвратява дублирането на съдържание на различни URL адреси. Ако към определена цел водят няколко адреса, напр. `/index` и `/index.html`, фреймуъркът определя първия от тях за основен (каноничен) и останалите пренасочва към него с помощта на HTTP код 301. Благодарение на това търсачките не индексират страниците ви два пъти и не размиват техния page rank. -Този процес се нарича канонизиране. Каноничният URL адрес е URL адрес, генериран от маршрутизатор, т.е. първият съвпадащ маршрут в [колекцията |#Route-Collection] без флага OneWay. Ето защо в колекцията са изброени **първите маршрути**. +Този процес се нарича канонизация. Каноничният URL е този, който генерира рутерът, т.е. първият удовлетворяващ маршрут в колекцията без флаг OneWay. Затова в колекцията посочваме **основните маршрути като първи**. -Канонизацията се извършва от водещия, за подробности вижте глава [Канонизация |presenters#canonization]. +Канонизацията се извършва от презентера, повече в главата [канонизация |presenters#Канонизация]. -HTTPS .[#toc-https] -=================== +HTTPS +===== -За да използвате протокола HTTPS, трябва да го активирате в хостинг услугата си и да конфигурирате сървъра. +За да можем да използваме HTTPS протокол, е необходимо да го разрешим на хостинга и правилно да конфигурираме сървъра си. -Пренасочването на целия сайт към HTTPS трябва да се извърши на ниво сървър, например с помощта на файл .htaccess в главната директория на нашето приложение с HTTP код 301. Настройките могат да варират в зависимост от хостинга и да изглеждат по следния начин: +Пренасочването на целия уебсайт към HTTPS трябва да се настрои на ниво сървър, например с помощта на файла .htaccess в коренната директория на нашето приложение, и то с HTTP код 301. Настройката може да се различава според хостинга и изглежда приблизително така: ``` @@ -542,40 +564,40 @@ HTTPS .[#toc-https] ``` -Маршрутизаторът генерира URL адреса със същия протокол, с който е изтеглена страницата, така че не е необходимо да задавате нищо друго. +Рутерът генерира URL със същия протокол, с който е била заредена страницата, така че нищо повече не е необходимо да се настройва. -Въпреки това, ако искаме различни маршрути да работят с различни протоколи, ще поставим това в маската на маршрута: +Ако обаче по изключение се нуждаем различните маршрути да работят под различни протоколи, ще го посочим в маската на маршрута: ```php -// Генерира HTTP адрес +// Ще генерира адрес с HTTP $router->addRoute('http://%host%//', /* ... */); -// Генерира HTTPS адрес +// Ще генерира адрес с HTTPS $router->addRoute('https://%host%//', /* ... */); ``` -Дебъгер за маршрутизиране .[#toc-debugging-router] -================================================== +Дебъгване на рутера +=================== -Лентата за маршрутизиране, показана в [лентата на Tracy |tracy:], е полезен инструмент, който показва списък с маршрути, както и параметрите, които маршрутизаторът е извлякъл от URL адреса. +Панелът за маршрутизация, показващ се в [Tracy Bar |tracy:], е полезен помощник, който показва списък с маршрути, както и параметри, които рутерът е получил от URL. -Зелената лента със символ ✓ представлява маршрута, който съответства на текущия URL адрес, а сините ленти със символи ≈ показват маршрутите, които също биха съответствали на URL адреса, ако зеленият цвят не ги е изпреварил. След това виждаме текущия главен учител и действията. +Зелената лента със символ ✓ представлява маршрута, който е обработил текущия URL, със син цвят и символ ≈ са маркирани маршрутите, които също биха обработили URL, ако зеленият не ги беше изпреварил. По-нататък виждаме текущия презентер и действие. [* routing-debugger.webp *] -В същото време, ако се появи неочаквано пренасочване поради [канонизация |#seo-and-canonization], е полезно да погледнете в панела *пренасочване*, за да видите как маршрутизаторът първоначално е разбрал URL адреса и защо го е пренасочил. +Същевременно, ако се случи неочаквано пренасочване поради [канонизация |#SEO и канонизация], е полезно да се погледне в панела в лентата *redirect*, къде ще разберете как рутерът първоначално е разбрал URL и защо е пренасочил. .[note] -При отстраняване на грешки в маршрутизатора се препоръчва да отворите Developer Tools (Инструменти за разработчици) в браузъра (Ctrl+Shift+I или Cmd+Option+I) и да деактивирате кеша в панела Network (Мрежа), така че пренасочванията да не се запазват в него. +При дебъгване на рутера препоръчваме да отворите в браузъра Developer Tools (Ctrl+Shift+I или Cmd+Option+I) и в панела Network да изключите кеша, за да не се съхраняват в него пренасочванията. -Производителност .[#toc-performance] -==================================== +Производителност +================ -Броят на маршрутите влияе на скоростта на маршрутизатора. Броят им, разбира се, не трябва да надвишава няколко десетки. Ако сайтът ви има твърде сложна структура на URL адресите, можете да напишете [потребителски маршрутизатор |#custom-router]. +Броят на маршрутите влияе на скоростта на рутера. Техният брой определено не трябва да надхвърля няколко десетки. Ако вашият уебсайт има прекалено сложна структура на URL, можете да си напишете по мярка [#Собствен рутер]. -Ако маршрутизаторът няма зависимости, като например база данни, и фабриката му няма аргументи, можем да сериализираме компилираната му форма директно в контейнер DI и така да ускорим малко приложението. +Ако рутерът няма никакви зависимости, например към база данни, и неговата фабрика не приема никакви аргументи, можем да сериализираме неговата сглобена форма директно в DI контейнера и така леко да ускорим приложението. ```neon routing: @@ -583,10 +605,10 @@ routing: ``` -Потребителски рутер .[#toc-custom-router] -========================================= +Собствен рутер +============== -Следващите редове са за много опитни потребители. Можете да създадете свой собствен маршрутизатор и, разбира се, да го добавите към колекцията от маршрути. Маршрутизаторът е реализация на интерфейса [Router |api:Nette\Routing\Router] с два метода: +Следващите редове са предназначени за много напреднали потребители. Можете да си създадете собствен рутер и напълно естествено да го включите в колекцията от маршрути. Рутерът е имплементация на интерфейса [api:Nette\Routing\Router] с два метода: ```php use Nette\Http\IRequest as HttpRequest; @@ -606,8 +628,7 @@ class MyRouter implements Nette\Routing\Router } ``` -Методът `match` обработва текущото [$httpRequest |http:request], от което може да се извлече не само URL адресът, но и заглавията и т.н., в масив, съдържащ името на главния потребител и неговите параметри. Ако не може да обработи заявката, тя връща null. -Когато обработваме заявката, трябва да върнем поне главния потребител и действието. Основното име е пълно и включва всички модули: +Методът `match` обработва текущата заявка [$httpRequest |http:request], от която може да се получи не само URL, но и хедъри и т.н., в масив, съдържащ името на презентера и неговите параметри. Ако не може да обработи заявката, връща null. При обработка на заявката трябва да върнем поне презентер и действие. Името на презентера е пълно и съдържа и евентуални модули: ```php [ @@ -616,31 +637,31 @@ class MyRouter implements Nette\Routing\Router ] ``` -Методът `constructUrl`, от друга страна, генерира абсолютен URL адрес от масив от параметри. Той може да използва информация от параметъра `$refUrl`, който е текущият URL адрес. +Методът `constructUrl` обратно, сглобява от масив с параметри крайния абсолютен URL. За това може да използва информация от параметъра [`$refUrl`|api:Nette\Http\UrlScript], което е текущият URL. -За да добавите потребителски маршрутизатор към колекцията от маршрути, използвайте `add()`: +В колекцията от маршрути го добавяте с помощта на `add()`: ```php $router = new Nette\Application\Routers\RouteList; -$router->add(new MyRouter); +$router->add($myRouter); $router->addRoute(/* ... */); // ... ``` -Отделна употреба .[#toc-separated-usage] -======================================== +Самостоятелно използване +======================== -Под разделно използване се разбира използването на възможностите на маршрутизатора в приложение, което не използва приложението Nette и презентаторите. Почти всичко, което показахме в тази глава, се отнася за него със следните разлики: +Под самостоятелно използване разбираме използването на възможностите на рутера в приложение, което не използва Nette Application и презентери. За него важи почти всичко, което показахме в тази глава, със следните разлики: -- За събиране на маршрути използваме класа [api:Nette\Routing\RouteList]. -- като обикновен клас маршрутизатор [api:Nette\Routing\SimpleRouter]. -- тъй като няма двойка `Presenter:action`, използваме [разширена нотация |#advanced-notation]. +- за колекции от маршрути използваме клас [api:Nette\Routing\RouteList] +- като simple router клас [api:Nette\Routing\SimpleRouter] +- тъй като не съществува двойка `Presenter:action`, използваме [#Разширен запис] -Затова отново ще добавим метод, който ще създаде например маршрутизатор: +Така че отново си създаваме метод, който ще ни сглоби рутера, напр.: ```php -namespace App\Router; +namespace App\Core; use Nette\Routing\RouteList; @@ -661,35 +682,35 @@ class RouterFactory } ``` -Ако използвате DI контейнер, както препоръчваме, добавете метода отново към конфигурацията и след това получете маршрутизатора заедно с HTTP заявката от контейнера: +Ако използвате DI контейнер, което препоръчваме, отново добавяме метода в конфигурацията и след това рутера заедно с HTTP заявката получаваме от контейнера: ```php $router = $container->getByType(Nette\Routing\Router::class); $httpRequest = $container->getByType(Nette\Http\IRequest::class); ``` -Или ще създадем обектите директно: +Или обектите директно произвеждаме: ```php -$router = App\Router\RouterFactory::createRouter(); +$router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` -Сега трябва да оставим маршрутизатора да свърши работата: +Сега вече остава да пуснем рутера да работи: ```php $params = $router->match($httpRequest); if ($params === null) { - // не е намерен съответстващ маршрут, изпратете 404 + // не беше намерен удовлетворяващ маршрут, изпращаме грешка 404 exit; } -// обработка на получените параметри +// обработваме получените параметри $controller = $params['controller']; // ... ``` -И обратното, ще използваме маршрутизатора, за да създадем връзката: +И обратно, използваме рутера за сглобяване на връзка: ```php $params = ['controller' => 'ArticleController', 'id' => 123]; diff --git a/application/bg/templates.texy b/application/bg/templates.texy index 05a8985093..d6f421e713 100644 --- a/application/bg/templates.texy +++ b/application/bg/templates.texy @@ -2,15 +2,15 @@ ******* .[perex] -Nette използва системата за шаблони [Latte |latte:]. Latte се използва, защото е най-сигурната система за шаблони за PHP и в същото време е най-интуитивна. Не е необходимо да научавате много, достатъчно е да знаете PHP и няколко тага за Latte. +Nette използва шаблониращата система [Latte |latte:]. От една страна, защото е най-добре защитената шаблонираща система за PHP, а същевременно и най-интуитивната система. Не е необходимо да учите много нови неща, достатъчно е да познавате PHP и няколко тага. -Обикновено страницата се попълва от шаблон за оформление + шаблон за действие. Ето как може да изглежда един шаблон за оформление, обърнете внимание на блоковете `{block}` и тага `{include}`: +Обичайно е страницата да се състои от шаблон на лейаута + шаблон на даденото действие. Така например може да изглежда шаблонът на лейаута, забележете блоковете `{block}` и тага `{include}`: ```latte - {block title}Мое приложение{/block} + {block title}My App{/block}
    ...
    @@ -20,61 +20,109 @@ Nette използва системата за шаблони [Latte |latte:]. L ``` -И това може да бъде шаблон за действие: +А това ще бъде шаблонът на действието: ```latte -{block title}Главная страница{/block} +{block title}Homepage{/block} {block content} -

    Главная страница

    +

    Homepage

    ... {/block} ``` -Той дефинира блок `content`, който се вмъква вместо `{include content}` в оформлението, и замества блока `title`, който презаписва `{block title}` в оформлението. Опитайте се да си представите резултата. +Той дефинира блок `content`, който се вмъква на мястото на `{include content}` в лейаута, и също така ре-дефинира блок `title`, с който презаписва `{block title}` в лейаута. Опитайте да си представите резултата. -Търсене на шаблони .[#toc-search-for-templates] ------------------------------------------------ +Търсене на шаблони +------------------ -Пътят към шаблоните се определя от главния модул с помощта на проста логика. Той ще се опита да провери дали има някой от тези файлове, разположен спрямо главната директория на класа, където `` е името на текущия главен модул, а `` е името на текущото събитие: +Не е необходимо в презентерите да посочвате кой шаблон трябва да се рендира, фреймуъркът сам извежда пътя и ви спестява писане. -- `templates//.latte` -- `templates/..latte` +Ако използвате директорийна структура, където всеки презентер има собствена директория, просто поставете шаблона в тази директория под името на действието (респ. view), т.е. за действието `default` използвайте шаблона `default.latte`: -Ако шаблонът не е намерен, отговорът ще бъде [грешка 404 |presenters#error-404-etc]. +/--pre +app/ +└── Presentation/ + └── Home/ + ├── HomePresenter.php + └── default.latte +\-- -Можете също така да промените изгледа с помощта на `$this->setView('jineView')`. Или вместо да търсите директно, посочете името на файла с шаблона, като използвате `$this->template->setFile('/path/to/template.latte')`. +Ако използвате структура, където презентерите са заедно в една директория, а шаблоните в папка `templates`, съхранете го или във файл `..latte`, или `/.latte`: + +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── Home.default.latte ← 1. вариант + └── Home/ + └── default.latte ← 2. вариант +\-- + +Директорията `templates` може да бъде разположена и едно ниво по-високо, т.е. на същото ниво, на което е директорията с класовете на презентерите. + +Ако шаблонът не бъде намерен, презентерът отговаря с [грешка 404 - page not found |presenters#Грешка 404 и др]. + +View се променя с помощта на `$this->setView('jineView')`. Също така може директно да се посочи файл с шаблон с помощта на `$this->template->setFile('/path/to/template.latte')`. .[note] -Файловете, които се търсят за шаблони, могат да се променят чрез наслагване на метода [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], който връща масив от възможни имена на файлове. +Файловете, където се търсят шаблони, могат да се променят чрез презаписване на метода [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], който връща масив от възможни имена на файлове. + + +Търсене на шаблон на лейаута +---------------------------- + +Nette също така автоматично търси файл с лейаут. + +Ако използвате директорийна структура, където всеки презентер има собствена директория, поставете лейаута или в папката с презентера, ако е специфичен само за него, или едно ниво по-високо, ако е общ за няколко презентера: + +/--pre +app/ +└── Presentation/ + ├── @layout.latte ← общ лейаут + └── Home/ + ├── @layout.latte ← само за презентера Home + ├── HomePresenter.php + └── default.latte +\-- + +Ако използвате структура, където презентерите са заедно в една директория, а шаблоните в папка `templates`, лейаутът ще се очаква на тези места: -В тези файлове се очаква оформление: +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── @layout.latte ← общ лейаут + ├── Home.@layout.latte ← само за Home, 1. вариант + └── Home/ + └── @layout.latte ← само за Home, 2. вариант +\-- -- `templates//@.latte` -- `templates/.@.latte` -- `templates/@.latte` Разположение, общо за няколко високоговорителя +Ако презентерът се намира в модул, ще се търси и на други директорийни нива по-високо, според влагането на модула. -Къде: `` е името на текущия водещ, а `` е името на оформлението, което по подразбиране е `'layout'`. Името може да бъде променено с помощта на `$this->setLayout('jinyLayout')`, така че ще бъдат изпробвани файлове `@jinyLayout.latte`. +Името на лейаута може да се промени с помощта на `$this->setLayout('layoutAdmin')` и тогава ще се очаква във файл `@layoutAdmin.latte`. Също така може директно да се посочи файл с шаблон на лейаута с помощта на `$this->setLayout('/path/to/template.latte')`. -Можете също така директно да посочите името на файла на шаблона за оформление, като използвате `$this->setLayout('/path/to/template.latte')`. Използването на `$this->setLayout(false)` деактивира проследяването на оформлението. +С помощта на `$this->setLayout(false)` или тага `{layout none}` вътре в шаблона търсенето на лейаут се изключва. .[note] -Файловете, в които се търсят шаблоните за оформление, могат да се променят чрез наслагване на метода [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], който връща масив от възможни имена на файлове. +Файловете, където се търсят шаблони на лейаута, могат да се променят чрез презаписване на метода [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], който връща масив от възможни имена на файлове. -Променливи в шаблона .[#toc-variables-in-the-template] ------------------------------------------------------- +Променливи в шаблона +-------------------- -Променливите се предават на шаблона, като се записват в `$this->template`, след което са достъпни в шаблона като локални променливи: +Променливите в шаблона предаваме така, че ги записваме в `$this->template` и след това ги имаме на разположение в шаблона като локални променливи: ```php $this->template->article = $this->articles->getById($id); ``` -По този начин можем лесно да предаваме всякакви променливи в шаблоните. Често обаче е по-полезно да се ограничим, когато разработваме надеждни приложения. Например чрез изрично дефиниране на списък с променливи, които шаблонът очаква, и техните типове. Това ще позволи на PHP да проверява типовете, на IDE да шепне правилно, а на статичния анализ да открива грешки. +Така лесно можем да предадем в шаблоните всякакви променливи. При разработката на стабилни приложения обаче е по-полезно да се ограничим. Например така, че изрично да дефинираме списък с променливите, които шаблонът очаква, и техните типове. Благодарение на това PHP ще може да проверява типовете, IDE правилно да подсказва и статичният анализ да открива грешки. -И как да определим такова изброяване? Просто под формата на клас и неговите свойства. Ще го наречем като презентатор, но с `Template` накрая: +А как да дефинираме такъв списък? Просто под формата на клас и неговите свойства. Ще го наречем подобно на презентера, само с `Template` накрая: ```php /** @@ -89,24 +137,24 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template public Model\Article $article; public Nette\Security\User $user; - // и други переменные + // и други променливи } ``` -Обектът `$this->template` в presenter вече ще бъде инстанция на класа `ArticleTemplate`. По този начин PHP ще проверява за декларирани типове при писане. От версия 8.2 на PHP тя ще предупреждава и при запис на променлива, която не съществува; в предишните версии това може да се постигне със свойството [Nette\SmartObject |utils:smartobject]. +Обектът `$this->template` в презентера сега ще бъде инстанция на класа `ArticleTemplate`. Така че PHP при запис ще проверява декларираните типове. И започвайки от версия PHP 8.2 ще предупреждава и за запис в несъществуваща променлива, в предишните версии същото може да се постигне с използването на trait [Nette\SmartObject |utils:smartobject]. -Анотацията `@property-read` е предназначена за IDE и статичен анализ, тя ще накара шепота да работи, вж. "PhpStorm и завършване на кода за $this->template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. +Анотацията `@property-read` е предназначена за IDE и статичен анализ, благодарение на нея ще работи подсказването, вижте "PhpStorm and code completion for $this⁠-⁠>⁠template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. [* phpstorm-completion.webp *] -Можете също така да си позволите лукса да шепнете в шаблоните, просто инсталирайте плъгина Latte в PhpStorm и поставете името на класа в началото на шаблона, за повече информация вижте статията "Latte: как да въведем системата":https://blog.nette.org/bg/latte-kak-da-izpolzvame-sistemata-ot-tipove: +Лукса на подсказването можете да си позволите и в шаблоните, достатъчно е да инсталирате в PhpStorm плъгин за Latte и да посочите в началото на шаблона името на класа, повече в статията "Latte: как да използваме системата за типове":https://blog.nette.org/bg/latte-how-to-use-type-system: ```latte -{templateType App\Presenters\ArticleTemplate} +{templateType App\Presentation\Article\ArticleTemplate} ... ``` -По същия начин работят и шаблоните в компонентите, просто следвайте конвенцията за именуване и създайте клас на шаблона `FifteenTemplate` за даден компонент, например `FifteenControl`. +Така работят и шаблоните в компонентите, достатъчно е само да се спазва именната конвенция и за компонент напр. `FifteenControl` да се създаде клас на шаблона `FifteenTemplate`. Ако трябва да създадете `$template` като инстанция на друг клас, използвайте метода `createTemplate()`: @@ -121,43 +169,43 @@ public function renderDefault(): void ``` -Променливи по подразбиране .[#toc-default-variables] ----------------------------------------------------- +Променливи по подразбиране +-------------------------- -Презентаторите и компонентите автоматично предават няколко полезни променливи на шаблоните: +Презентерите и компонентите предават на шаблоните няколко полезни променливи автоматично: -- `$basePath` е абсолютният URL адрес на главната директория (напр. `/eshop`). -- `$baseUrl` е абсолютният URL адрес на основната директория (напр. `http://localhost/eshop`) -- `$user` е обектът, който [представлява потребителя |security:authentication]. -- `$presenter` е настоящият майстор -- `$control` е текущият компонент или главният компонент -- `$flashes` е масив от [съобщения, |presenters#flash-messages] изпратени от функции `flashMessage()` +- `$basePath` е абсолютният URL път до коренната директория (напр. `/eshop`) +- `$baseUrl` е абсолютният URL до коренната директория (напр. `http://localhost/eshop`) +- `$user` е обект [представляващ потребителя |security:authentication] +- `$presenter` е текущият презентер +- `$control` е текущият компонент или презентер +- `$flashes` масив от [съобщения |presenters#Flash съобщения], изпратени с функцията `flashMessage()` -Ако използвате потребителски клас на шаблона, тези променливи ще бъдат предадени, ако създадете свойство за тях. +Ако използвате собствен клас на шаблона, тези променливи се предават, ако създадете свойство за тях. -Създаване на връзки .[#toc-creating-links] ------------------------------------------- +Създаване на връзки +------------------- -По този начин шаблонът създава връзки към други водещи и събития: +В шаблона се създават връзки към други презентери и действия по следния начин: ```latte -detail produktu +детайл на продукта ``` -Атрибутът `n:href` е много удобен за HTML таговете. ``. Ако искаме да посочим връзка на друго място, например в текста, използваме `{link}`: +Атрибутът `n:href` е много удобен за HTML тагове ``. Ако искаме да изпишем връзка другаде, например в текст, използваме `{link}`: ```latte -Adresa je: {link Home:default} +Адресът е: {link Home:default} ``` -Вижте [Създаване на URL връзки |creating-links] за повече информация. +Повече информация ще намерите в главата [Създаване на URL връзки|creating-links]. -Потребителски филтри, тагове и др. .[#toc-custom-filters-tags-etc] ------------------------------------------------------------------- +Собствени филтри, тагове и др. +------------------------------ -Системата за шаблони Latte може да бъде разширена с персонализирани филтри, функции, тагове и др. Това може да се направи директно в метода `render` или `beforeRender()`: +Шаблониращата система Latte може да бъде разширена със собствени филтри, функции, тагове и др. Това може да се направи директно в метода `render` или `beforeRender()`: ```php public function beforeRender(): void @@ -165,16 +213,16 @@ public function beforeRender(): void // добавяне на филтър $this->template->addFilter('foo', /* ... */); - // или да конфигурирате директно обекта Latte\Engine + // или конфигурираме директно обекта Latte\Engine $latte = $this->template->getLatte(); $latte->addFilterLoader(/* ... */); } ``` -Версия 3 на Latte предлага по-усъвършенстван начин за създаване на [разширение за |latte:creating-extension] всеки уеб проект. Ето кратък пример за такъв клас: +Latte във версия 3 предлага по-напреднал начин, а именно създаването на [extension |latte:extending-latte#Latte Extension] за всеки уеб проект. Частичен пример за такъв клас: ```php -namespace App\Templating; +namespace App\Presentation\Accessory; final class LatteExtension extends Latte\Extension { @@ -207,22 +255,21 @@ final class LatteExtension extends Latte\Extension } ``` -Регистрираме го в [конфигурацията |configuration#Latte]: +Регистрираме го с помощта на [конфигурацията |configuration#Шаблони Latte]: ```neon latte: extensions: - - App\Templating\LatteExtension + - App\Presentation\Accessory\LatteExtension ``` -Превод на .[#toc-translating] ------------------------------ +Превод +------ -Ако програмирате многоезично приложение, вероятно ще ви се наложи да извеждате част от текста в шаблона на различни езици. За тази цел в Nette Framework е дефиниран интерфейс за превод [api:Nette\Localization\Translator], който има един-единствен метод `translate()`. Той приема съобщението `$message`, което обикновено е низ, и всякакви други параметри. Задачата е да се върне преведеният низ. -В Nette няма имплементация по подразбиране, можете да изберете според нуждите си от няколко готови решения, които могат да бъдат намерени в [Componette |https://componette.org/search/localization]. В тяхната документация е описано как да конфигурирате преводача. +Ако програмирате многоезично приложение, най-вероятно ще трябва да изпишете някои текстове в шаблона на различни езици. Nette Framework за тази цел дефинира интерфейс за превод [api:Nette\Localization\Translator], който има единствен метод `translate()`. Той приема съобщение `$message`, което обикновено е низ, и всякакви други параметри. Задачата е да върне преведен низ. В Nette няма реализация по подразбиране, можете да изберете според своите нужди от няколко готови решения, които ще намерите на [Componette |https://componette.org/search/localization]. В тяхната документация ще научите как да конфигурирате преводача. -Шаблоните могат да бъдат настроени с преводач, който [ще ни бъде предаден |dependency-injection:passing-dependencies], като се използва методът `setTranslator()`: +На шаблоните може да се зададе преводач, който си [изискваме |dependency-injection:passing-dependencies], с метода `setTranslator()`: ```php protected function beforeRender(): void @@ -232,38 +279,38 @@ protected function beforeRender(): void } ``` -Алтернативно, преводачът може да бъде зададен чрез [конфигурацията |configuration#Latte]: +Преводачът може алтернативно да се настрои с помощта на [конфигурацията |configuration#Шаблони Latte]: ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -След това транслаторът може да се използва например като филтър `|translate`, като на метода `translate()` се предават допълнителни параметри (вж. `foo, bar`): +След това преводачът може да се използва например като филтър `|translate`, и то включително с допълнителни параметри, които се предават на метода `translate()` (виж `foo, bar`): ```latte -{='Basket'|translate} +{='Количка'|translate} {$item|translate} {$item|translate, foo, bar} ``` -Или като таг за подчертаване: +Или като таг с долна черта: ```latte -{_'Basket'} +{_'Количка'} {_$item} {_$item, foo, bar} ``` -За превод на секциите на шаблоните има сдвоен таг `{translate}` (от Latte 2.11 насам преди се използваше тагът `{_}` ): +За превод на част от шаблона съществува двоен таг `{translate}` (от Latte 2.11, преди се използваше тагът `{_}`): ```latte -{translate}Order{/translate} -{translate foo, bar}Order{/translate} +{translate}Поръчка{/translate} +{translate foo, bar}Поръчка{/translate} ``` -Преводачът се извиква по подразбиране по време на изпълнение, когато се визуализира шаблонът. Latte версия 3 обаче може да превежда целия статичен текст по време на компилирането на шаблона. Това спестява производителност, тъй като всеки низ се превежда само веднъж и полученият превод се записва в компилирания формуляр. По този начин се създават няколко компилирани версии на шаблона в кеш директорията, по една за всеки език. За да направите това, трябва само да посочите езика като втори параметър: +Преводачът стандартно се извиква по време на изпълнение при рендиране на шаблона. Latte версия 3 обаче може да превежда всички статични текстове още по време на компилацията на шаблона. С това се спестява производителност, защото всеки низ се превежда само веднъж и крайният превод се записва в компилираната форма. В директорията с кеша така възникват повече компилирани версии на шаблона, по една за всеки език. За това е достатъчно само да се посочи езикът като втори параметър: ```php protected function beforeRender(): void @@ -273,4 +320,4 @@ protected function beforeRender(): void } ``` -Под статичен текст разбираме например `{_'hello'}` или `{translate}hello{/translate}`. Нестатичният текст, като например `{_$foo}`, ще продължи да се компилира в движение. +Статичен текст е например `{_'hello'}` или `{translate}hello{/translate}`. Нестатичните текстове, като например `{_$foo}`, ще продължат да се превеждат по време на изпълнение. diff --git a/application/cs/@home.texy b/application/cs/@home.texy index 833406fcbd..9bbdba7d6b 100644 --- a/application/cs/@home.texy +++ b/application/cs/@home.texy @@ -2,19 +2,7 @@ Nette Application ***************** .[perex] -Balíček `nette/application` představuje základ pro tvorbu interaktivních webových aplikací. - -- [Jak fungují aplikace? |how-it-works] -- [Bootstrap] -- [Presentery |presenters] -- [Šablony |templates] -- [Moduly |modules] -- [Routování |routing] -- [Vytváření odkazů URL |creating-links] -- [Interaktivní komponenty |components] -- [AJAX & snippety |ajax] -- [Multiplier |multiplier] -- [Konfigurace |configuration] +Nette Application je jádrem frameworku Nette, které přináší výkonné nástroje pro vytváření moderních webových aplikací. Nabízí řadu výjimečných vlastností, které výrazně usnadňují vývoj a zlepšují bezpečnost i udržovatelnost kódu. Instalace @@ -26,10 +14,71 @@ Knihovnu stáhnete a nainstalujete pomocí nástroje [Composer|best-practices:co composer require nette/application ``` + +Proč zvolit Nette Application? +------------------------------ + +Nette bylo vždy průkopníkem v oblasti webových technologií. + +**Obousměrný router:** Nette disponuje pokročilým routovacím systémem, který je unikátní svou obousměrností - nejen že překládá URL na akce aplikace, ale také dokáže zpětně generovat URL adresy. To znamená, že: +- Můžete kdykoliv změnit strukturu URL celé aplikace bez nutnosti upravovat šablony +- URL jsou automaticky kanonizovány, což zlepšuje SEO +- Routování je definováno na jednom místě, nikoliv roztroušeně v anotacích + +**Komponenty a signály:** Vestavěný komponentový systém inspirovaný Delphi a React.js je mezi PHP frameworky zcela výjimečný: +- Umožňuje vytvářet znovupoužitelné UI prvky +- Podporuje hierarchické skládání komponent +- Nabízí elegantní zpracování AJAX požadavků pomocí signálů +- Bohatá knihovna hotových komponent na [Componette](https://componette.org) + +**AJAX a snippety:** Nette představilo revoluční způsob práce s AJAXem již v roce 2009, dlouho před podobnými řešeními jako Hotwire pro Ruby on Rails nebo Symfony UX Turbo: +- Snippety umožňují aktualizovat jen části stránky bez nutnosti psát JavaScript +- Automatická integrace s komponentovým systémem +- Chytrá invalidace částí stránek +- Minimální množství přenášených dat + +**Intuitivní šablony [Latte|latte:]:** Nejbezpečnější šablonovací systém pro PHP s pokročilými funkcemi: +- Automatická ochrana proti XSS s kontextově citlivým escapováním +- Rozšiřitelnost pomocí vlastních filtrů, funkcí a značek +- Dědičnost šablon a snippety pro AJAX +- Vynikající podpora PHP 8.x s typovým systémem + +**Dependency Injection:** Nette plně využívá Dependency Injection: +- Automatické předávání závislostí (autowiring) +- Konfigurace pomocí přehledného NEON formátu +- Podpora pro továrny na komponenty + + +Hlavní výhody +------------- + +- **Bezpečnost**: Automatická obrana proti [zranitelnostem|nette:vulnerability-protection] jako XSS, CSRF, atd. +- **Produktivita**: Méně psaní, více funkcí díky chytrému návrhu +- **Debugging**: [Tracy debugger|tracy:] s routovacím panelem +- **Výkon**: Chytrá cache, lazy loading komponent +- **Flexibilita**: Snadná úprava URL i po dokončení aplikace +- **Komponenty**: Unikátní systém znovupoužitelných UI prvků +- **Moderní**: Plná podpora PHP 8.4+ a typového systému + + +Začínáme +-------- + +1. [Jak fungují aplikace? |how-it-works] - Pochopení základní architektury +2. [Presentery |presenters] - Práce s presentery a akcemi +3. [Šablony |templates] - Tvorba šablon v Latte +4. [Routování |routing] - Konfigurace URL adres +5. [Interaktivní komponenty |components] - Využití komponentového systému + + +Kompatbility s PHP +------------------ + | verze | kompatibilní s PHP |-----------|------------------- -| Nette Application 4.0 | PHP 8.0 – 8.2 -| Nette Application 3.1 | PHP 7.2 – 8.2 +| Nette Application 4.0 | PHP 8.1 – 8.4 +| Nette Application 3.2 | PHP 8.1 – 8.4 +| Nette Application 3.1 | PHP 7.2 – 8.3 | Nette Application 3.0 | PHP 7.1 – 8.0 | Nette Application 2.4 | PHP 5.6 – 8.0 diff --git a/application/cs/@left-menu.texy b/application/cs/@left-menu.texy index 9c512de998..68d0549dc9 100644 --- a/application/cs/@left-menu.texy +++ b/application/cs/@left-menu.texy @@ -1,10 +1,10 @@ -Aplikace v Nette -**************** +Nette Application +***************** - [Jak fungují aplikace? |how-it-works] -- [Bootstrap] +- [Bootstrapping] - [Presentery |presenters] - [Šablony |templates] -- [Moduly |modules] +- [Adresářová struktura |directory-structure] - [Routování |routing] - [Vytváření odkazů URL |creating-links] - [Interaktivní komponenty |components] @@ -15,5 +15,8 @@ Aplikace v Nette Další četba *********** +- [Proč používat Nette? |www:10-reasons-why-nette] +- [Instalace |nette:installation] +- [Píšeme první aplikaci! |quickstart:] - [Návody a postupy |best-practices:] - [Řešení problémů |nette:troubleshooting] diff --git a/application/cs/@meta.texy b/application/cs/@meta.texy new file mode 100644 index 0000000000..462d9add80 --- /dev/null +++ b/application/cs/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokumentace}} diff --git a/application/cs/ajax.texy b/application/cs/ajax.texy index ac3792e0b6..0277720811 100644 --- a/application/cs/ajax.texy +++ b/application/cs/ajax.texy @@ -3,36 +3,43 @@ AJAX & snippety
    -Moderní webové aplikace dnes běží napůl na serveru, napůl v prohlížeči. AJAX je tím klíčovým spojovacím prvkem. Jakou podporu nabízí Nette Framework? -- posílání výřezů šablony (tzv. snippety) +V éře moderních webových aplikací, kde se často rozkládá funkcionalita mezi serverem a prohlížečem, je AJAX nezbytným spojovacím prvkem. Jaké možnosti nám v této oblasti nabízí Nette Framework? +- odesílání částí šablony, tzv. snippetů - předávání proměnných mezi PHP a JavaScriptem -- debugování AJAXových aplikací +- nástroje pro debugování AJAXových požadavků
    -AJAXový požadavek lze detekovat metodou služby [zapouzdřující HTTP požadavek |http:request] `$httpRequest->isAjax()` (detekuje podle HTTP hlavičky `X-Requested-With`). Uvnitř presenteru je k dispozici "zkratka" v podobě metody `$this->isAjax()`. -AJAXový požadavek se nijak neliší od klasického požadavku - je zavolán presenter s určitým view a parametry. Je také věcí presenteru, jak bude na něj reagovat: může použít vlastní rutinu, která vrátí nějaký fragment HTML kódu (HTML snippet), XML dokument, JSON objekt nebo kód v JavaScriptu. +AJAXový požadavek +================= -Pro odesílání dat prohlížeči ve formátu JSON lze využít předpřipravený objekt `payload`: +AJAXový požadavek se v zásadě neliší od klasického HTTP požadavku. Zavolá se presenter s určitými parametry. A je na presenteru, jakým způsobem bude na požadavek reagovat - může vrátit data ve formátu JSON, odeslat část HTML kódu, XML dokument, atd. -```php -public function actionDelete(int $id): void -{ - if ($this->isAjax()) { - $this->payload->message = 'Success'; - } - // ... -} +Na straně prohlížeče inicializujeme AJAXový požadavek pomocí funkce `fetch()`: + +```js +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) +.then(response => response.json()) +.then(payload => { + // zpracování odpovědi +}); ``` -Pokud potřebujete plnou kontrolu nad odeslaným JSONem, použijte metodu `sendJson` v presenteru. Tím ihned ukončíte činnost presenteru a obejdete se i bez šablony: +Na straně serveru rozpoznáme AJAXový požadavek metodou `$httpRequest->isAjax()` služby [zapouzdřující HTTP požadavek |http:request]. K detekci používá HTTP hlavičku `X-Requested-With`, proto je důležité ji odesílat. V rámci presenteru lze použít metodu `$this->isAjax()`. + +Chcete-li odeslat data ve formátu JSON, použijte metodu [`sendJson()` |presenters#Odeslání odpovědi]. Metoda rovněž ukončí činnost presenteru. ```php -$this->sendJson(['key' => 'value', /* ... */]); +public function actionExport(): void +{ + $this->sendJson($this->model->getData); +} ``` -Když chceme odeslat HTML, můžeme jednak zvolit speciální šablonu pro AJAX: +Máte-li v plánu odpovědět pomocí speciální šablony určené pro AJAX, můžete to udělat následovně: ```php public function handleClick($param): void @@ -45,10 +52,20 @@ public function handleClick($param): void ``` +Snippety +======== + +Nejsilnější prostředek, který nabízí Nette pro propojení serveru s klientem, představují snippety. Díky nim můžete z obyčejné aplikace udělat AJAXovou jen s minimálním úsilím a několika řádky kódu. Jak to celé funguje demonstruje příklad Fifteen, jehož kód najdete na [GitHubu |https://github.com/nette-examples/fifteen]. + +Snippety, nebo-li výstřižky, umožnují aktualizovat jen části stránky, místo toho, aby se celá stránka znovunačítala. Jednak je to rychlejší a efektivnější, ale poskytuje to také komfortnější uživatelský zážitek. Snippety vám mohou připomínat Hotwire pro Ruby on Rails nebo Symfony UX Turbo. Zajímavé je, že Nette představilo snippety již o 14 let dříve. + +Jak snippety fungují? Při prvním načtení stránky (ne-AJAXovém požadavku) se načte celá stránka včetně všech snippetů. Když uživatel interaguje se stránkou (např. klikne na tlačítko, odešle formulář, atd.), místo načtení celé stránky se vyvolá AJAXový požadavek. Kód v presenteru provede akci a rozhodne, které snippety je třeba aktualizovat. Nette tyto snippety vykreslí a odešle ve formě pole ve formátu JSON. Obslužný kód v prohlížeči získané snippety vloží zpět do stránky. Přenáší se tedy jen kód změněných snippetů, což šetří šířku pásma a zrychluje načítání oproti přenášení obsahu celé stránky. + + Naja -==== +---- -K obsluze AJAXových požadavků na straně prohlížeče slouží [knihovna Naja |https://naja.js.org]. Tu [nainstalujte |https://naja.js.org/#/guide/01-install-setup-naja] jako node.js balíček (pro použití s aplikacemi Webpack, Rollup, Vite, Parcel a dalšími): +K obsluze snippetů na straně prohlížeče slouží [knihovna Naja |https://naja.js.org]. Tu [nainstalujte |https://naja.js.org/#/guide/01-install-setup-naja] jako node.js balíček (pro použití s aplikacemi Webpack, Rollup, Vite, Parcel a dalšími): ```shell npm install naja @@ -60,59 +77,55 @@ npm install naja ``` +Nejprve je potřeba knihovnu [inicializovat |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization]: -Snippety -======== +```js +naja.initialize(); +``` -Daleko silnější nástroj představuje vestavěná podpora AJAXových snippetů. Díky ní lze udělat z obyčejné aplikace AJAXovou prakticky několika řádky kódu. Jak to celé funguje, demonstruje příklad Fifteen, jehož kód najdete na [GitHubu |https://github.com/nette-examples/fifteen]. +Aby se z obyčejného odkazu (signálu) nebo odeslání formuláře vytvořil AJAXový požadavek, stačí označit příslušný odkaz, formulář nebo tlačítko třídou `ajax`: -Snippety fungují tak, že při prvotním (tedy neAJAXovém) požadavku se přenese celá stránka a poté se při každém již AJAXovém [subrequestu |components#Signál] (= požadavku na stejný presenter a view) přenáší pouze kód změněných částí ve zmíněném úložišti `payload`. K tomu slouží dva mechanismy: invalidace a renderování snippetů. +```html +Go -Snippety vám mohou připomínat Hotwire pro Ruby on Rails nebo Symfony UX Turbo, nicméně Nette s nimi přišlo už o čtrnáct let dříve. +
    + +
    +nebo -Invalidace snippetů -=================== +
    + +
    +``` + + +Překreslení snippetů +-------------------- -Každý objekt třídy [Control |components] (což je i samotný Presenter) si umí zapamatovat, jestli při signálu došlo ke změnám, které si vyžadují jej překreslit. K tomu slouží dvojice metod `redrawControl()` a `isControlInvalid()`. Příklad: +Každý objekt třídy [Control |components] (včetně samotného Presenteru) eviduje, zda došlo ke změnám vyžadujícím jeho překreslení. K tomu slouží metoda `redrawControl()`: ```php public function handleLogin(string $user): void { - // po přihlášení uživatele se musí objekt překreslit + // po přihlášení je potřeba překreslit relevantní část $this->redrawControl(); // ... } ``` -Nette však nabízí ještě jemnější rozlišení, než na úrovni komponent. Uvedené metody mohou totiž jako argument přijímat název tzv. "snippetu", nebo-li výstřižku. Lze tedy invalidovat (rozuměj: vynutit překreslení) na úrovni těchto snippetů (každý objekt může mít libovolné množství snippetů). Pokud se invaliduje celá komponenta, tak se i každý snippet překreslí. Komponenta je "invalidní" i tehdy, pokud je invalidní některá její subkomponenta. -```php -$this->isControlInvalid(); // -> false - -$this->redrawControl('header'); // invaliduje snippet 'header' -$this->isControlInvalid('header'); // -> true -$this->isControlInvalid('footer'); // -> false -$this->isControlInvalid(); // -> true, alespoň jeden snippet je invalid +Nette umožňuje ještě jemnější kontrolu toho, co se má překreslit. Uvedená metoda totiž může jako argument přijímat název snippetu. Lze tedy invalidovat (rozuměj: vynutit překreslení) na úrovni částí šablony. Pokud se invaliduje celá komponenta, tak se překreslí i každý její snippet: -$this->redrawControl(); // invaliduje celou komponentu, každý snippet -$this->isControlInvalid('footer'); // -> true +```php +// invaliduje snippet 'header' +$this->redrawControl('header'); ``` -Komponenta, která přijímá signál, je automaticky označena za invalidní. - -Díky invalidaci snippetů přesně víme, které části kterých prvků bude potřeba překreslit. - -Tagy `{snippet} … {/snippet}` .{toc: Tag snippet} -================================================= +Snippety v Latte +---------------- -Vykreslování stránky probíhá velmi podobně jako při běžném požadavku: načtou se stejné šablony atd. Podstatné však je vynechání částí, které se nemají dostat na výstup; ostatní části se přiřadí k identifikátoru a pošlou se uživateli ve formátu srozumitelném pro obslužný program JavaScriptu. - - -Syntaxe -------- - -Pokud se uvnitř šablony nachází control nebo snippet, musíme jej obalit párovou značkou `{snippet} ... {/snippet}` - ty totiž zajistí, že se vykreslený snippet vystřihne a pošle do prohlížeče. Také jej obalí pomocnou značkou `
    ` s vygenerovaným `id`. V uvedeném příkladě je snippet pojmenován jako `header` a může představovat i například šablonu controlu: +Používání snippetů v Latte je nesmírně snadné. Chcete-li definovat část šablony jako snippet, obalte ji jednoduše značkami `{snippet}` a `{/snippet}`: ```latte {snippet header} @@ -120,7 +133,9 @@ Pokud se uvnitř šablony nachází control nebo snippet, musíme jej obalit pá {/snippet} ``` -Snippetu jiného typu než `
    ` nebo snippetu s dalšími HTML atributy docílíme použitím atributové varianty: +Snippet vytvoří v HTML stránce element `
    ` se speciálním vygenerovaným `id`. Při překreslení snippetu se pak aktulizuje obsah tohoto elementu. Proto je nutné, aby při prvotním vykreslení stránky se vykreslily také všechny snippety, byť mohou být třeba na začátku prázdné. + +Můžete vytvořit i snippet s jiným elementem než `
    ` pomocí n:attributu: ```latte
    @@ -129,138 +144,112 @@ Snippetu jiného typu než `
    ` nebo snippetu s dalšími HTML atributy docí ``` -Dynamické snippety -================== +Oblasti snippetů +---------------- -Nette také umožňuje používání snippetů, jejichž název se vytvoří až za běhu - tj. dynamicky. Hodí se to pro různé seznamy, kde při změně jednoho řádku nechceme přenášet AJAXem celý seznam, ale stačí onen samotný řádek. Příklad: +Názvy snippetů mohou být také výrazy: ```latte -
      - {foreach $list as $id => $item} -
    • {$item} update
    • - {/foreach} -
    +{foreach $items as $id => $item} +
  • {$item}
  • +{/foreach} ``` -Zde máme statický snippet `itemsContainer`, obsahující několik dynamických snippetů `item-0`, `item-1` atd. +Takto nám vznikne několik snippetů `item-0`, `item-1` atd. Pokud bychom přímo invalidovali dynamický snippet (například `item-1`), nepřekreslilo by se nic. Důvod je ten, že snippety opravdu fungují jako výstřižky a vykreslují se jen přímo ony samotné. Jenže v šabloně fakticky žádný snippet pojmenovaný `item-1` není. Ten vznikne až vykonáváním kódu v okolí snippetu, tedy cyklu foreach. Označíme proto část šablony, která se má vykonat pomocí značky `{snippetArea}`: -Dynamické snippety nelze invalidovat přímo (invalidace `item-1` neudělá vůbec nic), musíte invalidovat jim nadřazený statický snippet (zde snippet `itemsContainer`). Potom dojde k tomu, že se provede celý kód toho kontejneru, ale prohlížeči se pošlou jenom jeho sub-snippety. Pokud chcete, aby prohlížeč dostal pouze jediný z nich, musíte upravit vstup toho kontejneru tak, aby ostatní negeneroval. +```latte +
      + {foreach $items as $id => $item} +
    • {$item}
    • + {/foreach} +
    +``` -V příkladu výše zkrátka musíte zajistit, aby při ajaxovém požadavku byla v proměnné `$list` pouze jedna položka a tedy aby ten cyklus `foreach` naplnil pouze jeden dynamický snippet: +A necháme překreslit jak samotný snippet, tak i celou nadřazenou oblast: ```php -class HomePresenter extends Nette\Application\UI\Presenter -{ - /** - * Tato metoda vrací data pro seznam. - * Obvykle se jedná pouze o vyžádání dat z modelu. - * Pro účely tohoto příkladu jsou data zadána natvrdo. - */ - private function getTheWholeList(): array - { - return [ - 'První', - 'Druhý', - 'Třetí', - ]; - } - - public function renderDefault(): void - { - if (!isset($this->template->list)) { - $this->template->list = $this->getTheWholeList(); - } - } - - public function handleUpdate(int $id): void - { - $this->template->list = $this->isAjax() - ? [] - : $this->getTheWholeList(); - $this->template->list[$id] = 'Updated item'; - $this->redrawControl('itemsContainer'); - } -} +$this->redrawControl('itemsContainer'); +$this->redrawControl('item-1'); ``` +Zároveň je vhodné zajistit, aby pole `$items` obsahovalo jen ty položky, které se mají překreslit. -Snippety v includované šabloně -============================== - -Může se stát, že máme snippet v šabloně, kterou teprve includujeme do jiné šablony. V takovém případě je nutné vkládání této šablony obalit značkami `snippetArea`, které pak invalidujeme spolu se samotnym snippetem. - -Tagy `snippetArea` zaručí, že se daný kód, který vkládá šablonu, provede, do prohlížeče se však odešle pouze snippet v includované šabloně. +Pokud do šablony vkládáme pomocí značky `{include}` jinou šablonu, která obsahuje snippety, je nutné vložení šablony opět zahrnout do `snippetArea` a tu invalidovat společně se snippetem: ```latte -{* parent.latte *} -{snippetArea wrapper} -{include 'child.latte'} +{snippetArea include} + {include 'included.latte'} {/snippetArea} ``` + ```latte -{* child.latte *} +{* included.latte *} {snippet item} -... + ... {/snippet} ``` + ```php -$this->redrawControl('wrapper'); +$this->redrawControl('include'); $this->redrawControl('item'); ``` -Tento přístup se nechá použít i v kombinaci s dynamickými snippety. - - -Přidávání a mazání -================== -Pokud přidáte novou položku a invalidujete `itemsContainer`, pak vám AJAXový požadavek sice vrátí i nový snippet, ale obslužný javascript ho neumí nikam přiřadit. Na stránce totiž zatím není žádný HTML prvek s takovým ID. +Snippety v komponentách +----------------------- -V takovém případě je nejjednodušší celý ten seznam obalit ještě jedním snippetem a invalidovat to celé: +Snippety můžete vytvářet i v [komponentách|components] a Nette je bude automaticky překreslovat. Ale platí tu určité omezení: pro překreslení snippetů volá metodu `render()` bez parametrů. Tedy nebude fungovat předávání parametrů v šabloně: ```latte -{snippet wholeList} -
      - {foreach $list as $id => $item} -
    • {$item} update
    • - {/foreach} -
    -{/snippet} -Add +OK +{control productGrid} + +nebude fungovat: +{control productGrid $arg, $arg} +{control productGrid:paginator} ``` + +Posílání uživatelských dat +-------------------------- + +Společně se snippety můžete klientovi poslat libovolná další data. Stačí je zapsat do objektu `payload`: + ```php -public function handleAdd(): void +public function actionDelete(int $id): void { - $this->template->list = $this->getTheWholeList(); - $this->template->list[] = 'New one'; - $this->redrawControl('wholeList'); + // ... + if ($this->isAjax()) { + $this->payload->message = 'Success'; + } } ``` -Totéž platí i pro mazání. Sice by se dal nějak poslat prázdný snippet, jenže v praxi jsou většinou seznamy stránkované a řešit úsporněji smazání jednoho plus případné načtení jiného (který se předtím nevešel) by bylo příliš složité. - -Posílání parametrů do komponenty -================================ +Předávání parametrů +=================== Pokud komponentě pomocí AJAXového požadavku odesíláme parametry, ať už parametry signálu nebo persistentní parametry, musíme u požadavku uvést jejich globální název, který obsahuje i jméno komponenty. Celý název parametru vrací metoda `getParameterId()`. ```js -$.getJSON( - {link changeCountBasket!}, - { - {$control->getParameterId('id')}: id, - {$control->getParameterId('count')}: count - } -}); +let url = new URL({link //foo!}); +url.searchParams.set({$control->getParameterId('bar')}, bar); + +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) ``` -A handle metoda s odpovídajícími parametry v komponentě. +A handle metoda s odpovídajícími parametry v komponentě: ```php -public function handleChangeCountBasket(int $id, int $count): void +public function handleFoo(int $bar): void { - } ``` + + +Další četba +=========== + +- [Dynamické snippety |best-practices:dynamic-snippets] diff --git a/application/cs/bootstrap.texy b/application/cs/bootstrap.texy deleted file mode 100644 index fdb5decb4d..0000000000 --- a/application/cs/bootstrap.texy +++ /dev/null @@ -1,233 +0,0 @@ -Bootstrap -********* - -
    - -Bootstrap je zaváděcí kód, který inicializuje prostředí, vytvoří dependency injection (DI) kontejner a spustí aplikaci. Řekneme si: - -- jak se konfiguruje pomocí NEON souborů -- jak rozlišit produkční a vývojářský režim -- jak vytvořit DI kontejner - -
    - - -Aplikace, ať už jde o ty webové nebo skripty spouštěné z příkazové řádky, začínají svůj běh nějakou formou inicializace prostředí. V dávných dobách to míval na starosti soubor s názvem třeba `include.inc.php`, který prvotní soubor inkludoval. -V moderních Nette aplikacích jej nahradila třída `Bootstrap`, kterou jakožto součást aplikace najdete v souboru `app/Bootstrap.php`. Může vypadat kupříkladu takto: - -```php -use Nette\Bootstrap\Configurator; - -class Bootstrap -{ - public static function boot(): Configurator - { - $appDir = dirname(__DIR__); - $configurator = new Configurator; - //$configurator->setDebugMode('secret@23.75.345.200'); - $configurator->enableTracy($appDir . '/log'); - $configurator->setTempDirectory($appDir . '/temp'); - $configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); - $configurator->addConfig($appDir . '/config/common.neon'); - return $configurator; - } -} -``` - - -index.php -========= - -Prvotní soubor je v případě webových aplikací `index.php`, který se nachází ve veřejném adresáři `www/`. Ten si nechá od třídy Bootstrap inicializovat prostředí a vrátit `$configurator` a následně vyrobí DI kontejner. Poté z něj získá službu `Application`, kterou spustí webovou aplikaci: - -```php -// inicializace prostředí + získání objektu Configurator -$configurator = App\Bootstrap::boot(); -// vytvoření DI kontejneru -$container = $configurator->createContainer(); -// DI kontejner vytvoří objekt Nette\Application\Application -$application = $container->getByType(Nette\Application\Application::class); -// spuštění Nette aplikace -$application->run(); -``` - -Jak vidno, s nastavením prostředí a vytvořením dependency injection (DI) kontejneru pomáhá třída [api:Nette\Bootstrap\Configurator], kterou si nyní blíže představíme. - - -Vývojářský vs produkční režim -============================= - -Nette rozlišuje dva základní režimy, ve kterých se požadavek vykoná: vývojářský a produkční. Vývojářský je zaměřen na maximální pohodlí programátora, zobrazuje se Tracy, automaticky se aktualizuje cache při změně šablon nebo konfigurace DI kontejneru, atd. Produkční je zaměřený na výkon a ostré nasazení, Tracy chyby pouze loguje a změny šablon a dalších souborů se netestují. - -Volba režimu se provádí autodetekcí, takže obvykle není potřeba nic konfigurovat nebo ručně přepínat. Režim je vývojářský tehdy, pokud je aplikace spuštěna na localhostu (tj. IP adresa `127.0.0.1` nebo `::1`) a není přitomna proxy (tj. její HTTP hlavička). Jinak běží v produkčním režimu. - -Pokud chceme vývojářský režim povolit i v dalších případech, například programátorům přistupujícím z konkrétní IP adresy, použijeme `setDebugMode()`: - -```php -$configurator->setDebugMode('23.75.345.200'); // lze uvést i pole IP adres -``` - -Rozhodně doporučujeme kombinovat IP adresu s cookie. Do cookie `nette-debug` uložíme tajný token, např. `secret1234`, a tímto způsobem aktivujeme vývojářský režim pro programátory přistupující z konkrétní IP adresy a zároveň mající v cookie zmíněný token: - -```php -$configurator->setDebugMode('secret1234@23.75.345.200'); -``` - -Vývojářský režim můžeme také vypnout úplně, i pro localhost: - -```php -$configurator->setDebugMode(false); -``` - -Pozor, hodnota `true` zapne vývojářský režim natvrdo, což se nikdy nesmí stát na produkčním serveru. - - -Debugovací nástroj Tracy -======================== - -Pro snadné debugování ještě zapneme skvělý nástroj [Tracy |tracy:]. Ve vývojářském režimu chyby vizualizuje a v produkčním režimu chyby loguje do uvedeného adresáře: - -```php -$configurator->enableTracy($appDir . '/log'); -``` - - -Dočasné soubory -=============== - -Nette využívá cache pro DI kontejner, RobotLoader, šablony atd. Proto je nutné nastavit cestu k adresáři, kam se bude cache ukládat: - -```php -$configurator->setTempDirectory($appDir . '/temp'); -``` - -Na Linuxu nebo macOS nastavte adresářům `log/` a `temp/` [práva pro zápis |nette:troubleshooting#Nastavení práv adresářů]. - - -RobotLoader -=========== - -Zpravidla budeme chtít automaticky načítat třídy pomocí [RobotLoaderu |robot-loader:], musíme ho tedy nastartovat a necháme jej načítat třídy z adresáře, kde je umístěný `Bootstrap.php` (tj. `__DIR__`), a všech podadresářů: - -```php -$configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); -``` - -Alternativní přístup je nechat třídy načítat pouze přes [Composer |best-practices:composer] při dodržení PSR-4. - - -Timezone -======== - -Přes konfigurátor můžete nastavit výchozí časovou zónu. - -```php -$configurator->setTimeZone('Europe/Prague'); -``` - - -Konfigurace DI kontejneru -========================= - -Součástí bootovacího procesu je vytvoření DI kontejneru neboli továrny na objekty, což je srdce celé aplikace. Jde vlastně o PHP třídu, kterou vygeneruje Nette a uloží do adresáře s cache. Továrna vyrábí klíčové objekty aplikace a pomocí konfiguračních souborů jí instruujeme, jak je má vytvářet a nastavovat, čímž ovlivňujeme chování celé aplikace. - -Konfigurační soubory se obvykle zapisují ve formátu [NEON |neon:format]. V samostatné kapitole se dočtete, [co vše lze konfigurovat |nette:configuring]. - -.[tip] -Ve vývojářském režimu se kontejner automaticky aktualizuje při každé změně kódu nebo konfiguračních souborů. V produkčním režimu se vygeneruje jen jednou a změny se kvůli maximalizaci výkonu nekontrolují. - -Konfigurační soubory načteme pomocí `addConfig()`: - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -``` - -Pokud chceme přidat více konfiguračních souborů, můžeme funkci `addConfig()` zavolat vícekrát. - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -$configurator->addConfig($appDir . '/config/local.neon'); -if (PHP_SAPI === 'cli') { - $configurator->addConfig($appDir . '/config/cli.php'); -} -``` - -Název `cli.php` není překlep, konfigurace může být zapsaná také v PHP souboru, který ji vrátí jako pole. - -Také můžeme přidat další konfigurační soubory v [sekci `includes` |dependency-injection:configuration#Vkládání souborů]. - -Pokud se v konfiguračních souborech objeví prvky se stejnými klíči, budou přepsány, nebo v případě [polí sloučeny |dependency-injection:configuration#Slučování]. Později vkládaný soubor má vyšší prioritu než předchozí. Soubor, ve kterém je sekce `includes` uvedena, má vyšší prioritu než v něm inkludované soubory. - - -Statické parametry ------------------- - -Parametry používané v konfiguračních souborech můžeme definovat [v sekci `parameters`|dependency-injection:configuration#parametry] a také je předávat (či přepisovat) metodou `addStaticParameters()` (má alias `addParameters()`). Důležité je, že různé hodnoty parametrů způsobí vygenerování dalších DI kontejnerů, tedy dalších tříd. - -```php -$configurator->addStaticParameters([ - 'projectId' => 23, -]); -``` - -Na parametr `projectId` se lze v konfiguraci odkázat obvyklým zápisem `%projectId%`. Třída Configurator automaticky přidává parametry `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` a `consoleMode`. - - -Dynamické parametry -------------------- - -Do kontejneru můžeme přidat i dynamické parametry, jejichž různé hodnoty na rozdíl od statických parameterů nezpůsobí generování nových DI kontejnerů. - -```php -$configurator->addDynamicParameters([ - 'remoteIp' => $_SERVER['REMOTE_ADDR'], -]); -``` - -Jednoduše tak můžeme přidat např. environmentální proměnné, na které se pak lze v konfiguraci odkázat zápisem `%env.variable%`. - -```php -$configurator->addDynamicParameters([ - 'env' => getenv(), -]); -``` - - -Importované služby ------------------- - -Nyní už jdeme hlouběji. Ačkoliv je smyslem DI kontejneru objekty vyrábet, výjimečně může vzniknout potřeba do kontejneru existující objekt vložit. Uděláme to tak, že službu definujeme s příznakem `imported: true`. - -```neon -services: - myservice: - type: App\Model\MyCustomService - imported: true -``` - -A v bootstrapu do kontejneru vložíme objekt: - -```php -$configurator->addServices([ - 'myservice' => new App\Model\MyCustomService('foobar'), -]); -``` - - -Odlišné prostředí -================= - -Nebojte se upravit třídu Bootstrap podle svých potřeb. Metodě `boot()` můžete přidat parametry pro rozlišení webových projektů nebo doplnit další metody, například `bootForTests()`, která inicializuje prostředí pro jednotkové testy, `bootForCli()` pro skripty volané z příkazové řádky atd. - -```php -public static function bootForTests(): Configurator -{ - $configurator = self::boot(); - Tester\Environment::setup(); // inicializace Nette Testeru - return $configurator; -} -``` diff --git a/application/cs/bootstrapping.texy b/application/cs/bootstrapping.texy new file mode 100644 index 0000000000..2b4a2dec58 --- /dev/null +++ b/application/cs/bootstrapping.texy @@ -0,0 +1,298 @@ +Bootstrapping +************* + +
    + +Bootstrapping je proces inicializace prostředí aplikace, vytvoření kontejneru pro dependency injection (DI) a spuštění aplikace. Budeme probírat: + +- jak třída Bootstrap inicializuje prostředí +- jak jsou aplikace konfigurovány pomocí NEON souborů +- jak rozlišovat mezi produkčním a vývojářským režimem +- jak vytvořit a nakonfigurovat DI kontejner + +
    + + +Aplikace, ať už jde o ty webové nebo skripty spouštěné z příkazové řádky, začínají svůj běh nějakou formou inicializace prostředí. V dávných dobách to míval na starosti soubor s názvem třeba `include.inc.php`, který prvotní soubor inkludoval. V moderních Nette aplikacích jej nahradila třída `Bootstrap`, kterou jakožto součást aplikace najdete v souboru `app/Bootstrap.php`. Může vypadat kupříkladu takto: + +```php +use Nette\Bootstrap\Configurator; + +class Bootstrap +{ + private Configurator $configurator; + private string $rootDir; + + public function __construct() + { + $this->rootDir = dirname(__DIR__); + // Konfigurátor je zodpovědný za nastavení prostředí aplikace a služeb. + $this->configurator = new Configurator; + // Nastaví adresář pro dočasné soubory generované Nette (např. zkompilované šablony) + $this->configurator->setTempDirectory($this->rootDir . '/temp'); + } + + public function bootWebApplication(): Nette\DI\Container + { + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); + } + + private function initializeEnvironment(): void + { + // Nette je chytré a vývojový režim se zapíná automaticky, + // nebo jej můžete povolit pro konkrétní IP adresu odkomentováním následujícího řádku: + // $this->configurator->setDebugMode('secret@23.75.345.200'); + + // Aktivuje Tracy: ultimátní "švýcarský nůž" pro ladění. + $this->configurator->enableTracy($this->rootDir . '/log'); + + // RobotLoader: automaticky načítá všechny třídy ve zvoleném adresáři + $this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); + } + + private function setupContainer(): void + { + // Načte konfigurační soubory + $this->configurator->addConfig($this->rootDir . '/config/common.neon'); + } +} +``` + + +index.php +========= + +Prvotní soubor je v případě webových aplikací `index.php`, který se nachází ve [veřejném adresáři |directory-structure#Veřejný adresář www] `www/`. Ten si nechá od třídy Bootstrap inicializovat prostředí a vyrobit DI kontejner. Poté z něj získá službu `Application`, která spustí webovou aplikaci: + +```php +$bootstrap = new App\Bootstrap; +// Inicializace prostředí + vytvoření DI kontejneru +$container = $bootstrap->bootWebApplication(); +// DI kontejner vytvoří objekt Nette\Application\Application +$application = $container->getByType(Nette\Application\Application::class); +// Spuštění aplikace Nette a zpracování příchozího požadavku +$application->run(); +``` + +Jak vidno, s nastavením prostředí a vytvořením dependency injection (DI) kontejneru pomáhá třída [api:Nette\Bootstrap\Configurator], kterou si nyní blíže představíme. + + +Vývojářský vs produkční režim +============================= + +Nette se chová různě podle toho, zda běží na vývojářském nebo produkčním serveru: + +🛠️ Vývojářský režim (Development): + - Zobrazuje Tracy debugbar s užitečnými informacemi (SQL dotazy, čas vykonání, použitá paměť) + - Při chybě zobrazí detailní chybovou stránku s voláním funkcí a obsahem proměnných + - Automaticky obnovuje cache při změně Latte šablon, úpravě konfiguračních souborů atd. + + +🚀 Produkční režim (Production): + - Nezobrazuje žádné ladící informace, všechny chyby zapisuje do logu + - Při chybě zobrazí ErrorPresenter nebo obecnou stránku "Server Error" + - Cache se nikdy automaticky neobnovuje! + - Optimalizovaný pro rychlost a bezpečnost + + +Volba režimu se provádí autodetekcí, takže obvykle není potřeba nic konfigurovat nebo ručně přepínat: + +- vývojářský režim: na localhostu (IP adresa `127.0.0.1` nebo `::1`) pokud není přítomná proxy (tj. její HTTP hlavička) +- produkční režim: všude jinde + +Pokud chceme vývojářský režim povolit i v dalších případech, například programátorům přistupujícím z konkrétní IP adresy, použijeme `setDebugMode()`: + +```php +$this->configurator->setDebugMode('23.75.345.200'); // lze uvést i pole IP adres +``` + +Rozhodně doporučujeme kombinovat IP adresu s cookie. Do cookie `nette-debug` uložíme tajný token, např. `secret1234`, a tímto způsobem aktivujeme vývojářský režim pro programátory přistupující z konkrétní IP adresy a zároveň mající v cookie zmíněný token: + +```php +$this->configurator->setDebugMode('secret1234@23.75.345.200'); +``` + +Vývojářský režim můžeme také vypnout úplně, i pro localhost: + +```php +$this->configurator->setDebugMode(false); +``` + +Pozor, hodnota `true` zapne vývojářský režim natvrdo, což se nikdy nesmí stát na produkčním serveru. + + +Debugovací nástroj Tracy +======================== + +Pro snadné debugování ještě zapneme skvělý nástroj [Tracy |tracy:]. Ve vývojářském režimu chyby vizualizuje a v produkčním režimu chyby loguje do uvedeného adresáře: + +```php +$this->configurator->enableTracy($this->rootDir . '/log'); +``` + + +Dočasné soubory +=============== + +Nette využívá cache pro DI kontejner, RobotLoader, šablony atd. Proto je nutné nastavit cestu k adresáři, kam se bude cache ukládat: + +```php +$this->configurator->setTempDirectory($this->rootDir . '/temp'); +``` + +Na Linuxu nebo macOS nastavte adresářům `log/` a `temp/` [práva pro zápis |nette:troubleshooting#Nastavení práv adresářů]. + + +RobotLoader +=========== + +Zpravidla budeme chtít automaticky načítat třídy pomocí [RobotLoaderu |robot-loader:], musíme ho tedy nastartovat a necháme jej načítat třídy z adresáře, kde je umístěný `Bootstrap.php` (tj. `__DIR__`), a všech podadresářů: + +```php +$this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); +``` + +Alternativní přístup je nechat třídy načítat pouze přes [Composer |best-practices:composer] při dodržení PSR-4. + + +Timezone +======== + +Přes konfigurátor můžete nastavit výchozí časovou zónu. + +```php +$this->configurator->setTimeZone('Europe/Prague'); +``` + + +Konfigurace DI kontejneru +========================= + +Součástí bootovacího procesu je vytvoření DI kontejneru neboli továrny na objekty, což je srdce celé aplikace. Jde vlastně o PHP třídu, kterou vygeneruje Nette a uloží do adresáře s cache. Továrna vyrábí klíčové objekty aplikace a pomocí konfiguračních souborů jí instruujeme, jak je má vytvářet a nastavovat, čímž ovlivňujeme chování celé aplikace. + +Konfigurační soubory se obvykle zapisují ve formátu [NEON |neon:format]. V samostatné kapitole se dočtete, [co vše lze konfigurovat |nette:configuring]. + +.[tip] +Ve vývojářském režimu se kontejner automaticky aktualizuje při každé změně kódu nebo konfiguračních souborů. V produkčním režimu se vygeneruje jen jednou a změny se kvůli maximalizaci výkonu nekontrolují. + +Konfigurační soubory načteme pomocí `addConfig()`: + +```php +$this->configurator->addConfig($this->rootDir . '/config/common.neon'); +``` + +Pokud chceme přidat více konfiguračních souborů, můžeme funkci `addConfig()` zavolat vícekrát. + +```php +$configDir = $this->rootDir . '/config'; +$this->configurator->addConfig($configDir . '/common.neon'); +$this->configurator->addConfig($configDir . '/services.neon'); +if (PHP_SAPI === 'cli') { + $this->configurator->addConfig($configDir . '/cli.php'); +} +``` + +Název `cli.php` není překlep, konfigurace může být zapsaná také v PHP souboru, který ji vrátí jako pole. + +Také můžeme přidat další konfigurační soubory v [sekci `includes` |dependency-injection:configuration#Vkládání souborů]. + +Pokud se v konfiguračních souborech objeví prvky se stejnými klíči, budou přepsány, nebo v případě [polí sloučeny |dependency-injection:configuration#Slučování]. Později vkládaný soubor má vyšší prioritu než předchozí. Soubor, ve kterém je sekce `includes` uvedena, má vyšší prioritu než v něm inkludované soubory. + + +Statické parametry +------------------ + +Parametry používané v konfiguračních souborech můžeme definovat [v sekci `parameters` |dependency-injection:configuration#Parametry] a také je předávat (či přepisovat) metodou `addStaticParameters()` (má alias `addParameters()`). Důležité je, že různé hodnoty parametrů způsobí vygenerování dalších DI kontejnerů, tedy dalších tříd. + +```php +$this->configurator->addStaticParameters([ + 'projectId' => 23, +]); +``` + +Na parametr `projectId` se lze v konfiguraci odkázat obvyklým zápisem `%projectId%`. + + +Dynamické parametry +------------------- + +Do kontejneru můžeme přidat i dynamické parametry, jejichž různé hodnoty na rozdíl od statických parameterů nezpůsobí generování nových DI kontejnerů. + +```php +$this->configurator->addDynamicParameters([ + 'remoteIp' => $_SERVER['REMOTE_ADDR'], +]); +``` + +Jednoduše tak můžeme přidat např. environmentální proměnné, na které se pak lze v konfiguraci odkázat zápisem `%env.variable%`. + +```php +$this->configurator->addDynamicParameters([ + 'env' => getenv(), +]); +``` + + +Výchozí parametry +----------------- + +V konfiguračních souborech můžete využít tyto statické parametry: + +- `%appDir%` je absolutní cesta k adresáři se souborem `Bootstrap.php` +- `%wwwDir%` je absolutní cesta k adresáři se vstupním souborem `index.php` +- `%tempDir%` je absolutní cesta k adresáři pro dočasné soubory +- `%vendorDir%` je absolutní cesta k adresáři, kam Composer instaluje knihovny +- `%rootDir%` je absolutní cesta ke kořenovému adresáři projektu +- `%baseUrl%` je absolutní URL ke kořenovému adresáři +- `%debugMode%` udává, zda je aplikace v debugovacím režimu +- `%consoleMode%` udává, zda request přišel přes příkazovou řádku + + +Importované služby +------------------ + +Nyní už jdeme hlouběji. Ačkoliv je smyslem DI kontejneru objekty vyrábet, výjimečně může vzniknout potřeba do kontejneru existující objekt vložit. Uděláme to tak, že službu definujeme s příznakem `imported: true`. + +```neon +services: + myservice: + type: App\Model\MyCustomService + imported: true +``` + +A v bootstrapu do kontejneru vložíme objekt: + +```php +$this->configurator->addServices([ + 'myservice' => new App\Model\MyCustomService('foobar'), +]); +``` + + +Odlišné prostředí +================= + +Nebojte se upravit třídu Bootstrap podle svých potřeb. Metodě `bootWebApplication()` můžete přidat parametry pro rozlišení webových projektů. Nebo můžeme doplnit další metody, například `bootTestEnvironment()`, která inicializuje prostředí pro jednotkové testy, `bootConsoleApplication()` pro skripty volané z příkazové řádky atd. + +```php +public function bootTestEnvironment(): Nette\DI\Container +{ + Tester\Environment::setup(); // inicializace Nette Testeru + $this->setupContainer(); + return $this->configurator->createContainer(); +} + +public function bootConsoleApplication(): Nette\DI\Container +{ + $this->configurator->setDebugMode(false); + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); +} +``` diff --git a/application/cs/components.texy b/application/cs/components.texy index b9a22c0f5e..7d433ab890 100644 --- a/application/cs/components.texy +++ b/application/cs/components.texy @@ -87,7 +87,7 @@ class PollControl extends Control Vykreslení ========== -Už víme, že k vykreslení komponenty se používá značka `{control componentName}`. Ta vlastně zavolá metodu `render()` komponenty, ve které se postáráme o vykreslení. K dispozici máme, úplně stejně jako v presenteru, [Latte|latte:] šablonu v proměnné `$this->template`, do které předáme parametry. Na rozdíl od presenteru musíme uvést soubor se šablonou a nechat ji vykreslit: +Už víme, že k vykreslení komponenty se používá značka `{control componentName}`. Ta vlastně zavolá metodu `render()` komponenty, ve které se postáráme o vykreslení. K dispozici máme, úplně stejně jako v presenteru, [Latte šablonu|templates] v proměnné `$this->template`, do které předáme parametry. Na rozdíl od presenteru musíme uvést soubor se šablonou a nechat ji vykreslit: ```php .{file:PollControl.php} public function render(): void @@ -175,7 +175,7 @@ Komponenty, stejně jako presentery, předávají do šablon několik užitečn - `$user` je objekt [reprezentující uživatele |security:authentication] - `$presenter` je aktuální presenter - `$control` je aktuální komponenta -- `$flashes` pole [zpráv |#flash zprávy] zaslaných funkcí `flashMessage()` +- `$flashes` pole [zpráv |#Flash zprávy] zaslaných funkcí `flashMessage()` Signál @@ -192,13 +192,13 @@ public function handleClick(int $x, int $y): void } ``` -Odkaz, který zavolá signál, vytvoříme obvyklým způsobem, tedy v šabloně atributem `n:href` nebo značkou `{link}`, v kódu metodou `link()`. Více v kapitole [Vytváření odkazů URL|creating-links#Odkazy na signál]. +Odkaz, který zavolá signál, vytvoříme obvyklým způsobem, tedy v šabloně atributem `n:href` nebo značkou `{link}`, v kódu metodou `link()`. Více v kapitole [Vytváření odkazů URL |creating-links#Odkazy na signál]. ```latte click here ``` -Signál se vždy volá na aktuálním presenteru a view, tudíž není možné jej vyvolat na jiném presenteru nebo view. +Signál se vždy volá na aktuálním presenteru a action, není možné jej vyvolat na jiném presenteru nebo jiné action. Signál tedy způsobí znovunačtení stránky úplně stejně jako při původním požadavku, jen navíc zavolá obslužnou metodu signálu s příslušnými parametry. Pokud metoda neexistuje, vyhodí se výjimka [api:Nette\Application\UI\BadSignalException], která se uživateli zobrazí jako chybová stránka 403 Forbidden. @@ -230,6 +230,28 @@ $this->redirect(/* ... */); // a přesměrujeme ``` +Přesměrování po signálu +======================= + +Po zpracování signálu komponenty často následuje přesměrování. Je to podobná situace jako u formulářů - po jejich odeslání také přesměrováváme, aby při obnovení stránky v prohlížeči nedošlo k opětovnému odeslání dat. + +```php +$this->redirect('this') // přesměruje na aktuální presenter a action +``` + +Protože komponenta je znovupoužitelný prvek a obvykle by neměla mít přímou vazbu na konkrétní presentery, metody `redirect()` a `link()` automaticky interpretují parametr jako signál komponenty: + +```php +$this->redirect('click') // přesměruje na signál 'click' téže komponenty +``` + +Pokud potřebujete přesměrovat na jiný presenter či akci, můžete to udělat prostřednictvím presenteru: + +```php +$this->getPresenter()->redirect('Product:show'); // přesměruje na jiný presenter/action +``` + + Persistentní parametry ====================== @@ -310,7 +332,7 @@ class PollControl extends Control public function handleVote(int $voteId): void { - $this->facade->vote($id, $voteId); + $this->facade->vote($this->id, $voteId); // ... } } @@ -347,7 +369,7 @@ services: a nakonec ji použijeme v našem presenteru: ```php -class PollPresenter extends Nette\UI\Application\Presenter +class PollPresenter extends Nette\Application\UI\Presenter { public function __construct( private PollControlFactory $pollControlFactory, @@ -380,12 +402,12 @@ Komponenty do hloubky Komponenty v Nette Application představují znovupoužitelné součásti webové aplikace, které vkládáme do stránek a kterým se ostatně věnuje celá tato kapitola. Jaké přesně schopnosti taková komponenta má? 1) je vykreslitelná v šabloně -2) ví, kterou svou část má vykreslit při [AJAXovém požadavku |ajax#invalidace] (snippety) +2) ví, [kterou svou část |ajax#Snippety] má vykreslit při AJAXovém požadavku (snippety) 3) má schopnost ukládat svůj stav do URL (persistentní parametry) 4) má schopnost reagovat na uživatelské akce (signály) 5) vytváří hierarchickou strukturu (kde kořenem je presenter) -Každou z těchto funkcí obstarává některá z tříd dědičné linie. Vykreslování (1 + 2) má na starosti [api:Nette\Application\UI\Control], začlenění do [životního cyklu |presenters#zivotni-cyklus-presenteru] (3, 4) třída [api:Nette\Application\UI\Component] a vytváření hierachické struktury (5) třídy [Container a Component |component-model:]. +Každou z těchto funkcí obstarává některá z tříd dědičné linie. Vykreslování (1 + 2) má na starosti [api:Nette\Application\UI\Control], začlenění do [životního cyklu |presenters#Životní cyklus presenteru] (3, 4) třída [api:Nette\Application\UI\Component] a vytváření hierachické struktury (5) třídy [Container a Component |component-model:]. ``` Nette\ComponentModel\Component { IComponent } @@ -400,10 +422,10 @@ Nette\ComponentModel\Component { IComponent } ``` -Životní cyklus componenty +Životní cyklus komponenty ------------------------- -[* lifecycle-component.svg *] *** *Životní cyklus componenty* .<> +[* lifecycle-component.svg *] *** *Životní cyklus komponenty* .<> Validace persistentních parametrů @@ -430,7 +452,7 @@ class PaginatingControl extends Control } ``` -Opačný proces, tedy sesbírání hodnot z persistentních properites, má na starosti metoda `saveState()`. +Opačný proces, tedy sesbírání hodnot z persistentních properties, má na starosti metoda `saveState()`. Signály do hloubky @@ -444,7 +466,7 @@ Signál může přijímat jakákoliv komponenta, presenter nebo objekt, který i Mezi hlavní příjemce signálů budou patřit `Presentery` a vizuální komponenty dědící od `Control`. Signál má sloužit jako znamení pro objekt, že má něco udělat – anketa si má započítat hlas od uživatele, blok s novinkami se má rozbalit a zobrazit dvakrát tolik novinek, formulář byl odeslán a má zpracovat data a podobně. -URL pro signál vytváříme pomocí metody [Component::link() |api:Nette\Application\UI\Component::link()]. Jako parametr `$destination` předáme řetězec `{signal}!` a jako `$args` pole argumentů, které chceme signálu předat. Signál se vždy volá na aktuální view s aktuálními parametry, parametry signálu se jen přidají. Navíc se přidává hned na začátku **parametr `?do`, který určuje signál**. +URL pro signál vytváříme pomocí metody [Component::link() |api:Nette\Application\UI\Component::link()]. Jako parametr `$destination` předáme řetězec `{signal}!` a jako `$args` pole argumentů, které chceme signálu předat. Signál se vždy volá na aktuálním presenteru a action s aktuálními parametry, parametry signálu se jen přidají. Navíc se přidává hned na začátku **parametr `?do`, který určuje signál**. Jeho formát je buď `{signal}`, nebo `{signalReceiver}-{signal}`. `{signalReceiver}` je název komponenty v presenteru. Proto nemůže být v názvu komponenty pomlčka – používá se k oddělení názvu komponenty a signálu, je ovšem možné takto zanořit několik komponent. @@ -461,16 +483,3 @@ if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, ' ``` Tím je signál provedený předčasně a už se nebude znovu volat. - - -/--comment - /** @var callable[]&(callable(Component $sender): void)[]; Occurs when component is attached to presenter */ - public $onAnchor; - - /** - * Returns destination as Link object. - * @param string $destination in format "[homepage] [[[module:]presenter:]action | signal! | this] [#fragment]" - * @param array|mixed $args - */ - public function lazyLink(string $destination, $args = []): Link -\-- diff --git a/application/cs/configuration.texy b/application/cs/configuration.texy index fe28676b92..b105f5154e 100644 --- a/application/cs/configuration.texy +++ b/application/cs/configuration.texy @@ -14,10 +14,14 @@ application: debugger: ... # (bool) výchozí je true # bude se při chybě volat error-presenter? - catchExceptions: ... # (bool) výchozí je true v produkčním režimu + # má efekt pouze ve vývojářském režimu + catchExceptions: ... # (bool) výchozí je true # název error-presenteru - errorPresenter: Error # (string) výchozí je 'Nette:Error' + errorPresenter: Error # (string|array) výchozí je 'Nette:Error' + + # definuje aliasy pro presentery a akce + aliases: ... # definuje pravidla pro překlad názvu presenteru na třídu mapping: ... @@ -27,11 +31,20 @@ application: silentLinks: ... # (bool) výchozí je false ``` -Protože ve vývojovém režimu se error-presentery standardně nevolají a chybu zobrazí až Tracy, změnou hodnoty `catchExceptions` na `true` můžeme při vývoji ověřit jejich správnou funkčnost. +Od `nette/application` verze 3.2 lze definovat dvojici error-presenterů: + +```neon +application: + errorPresenter: + 4xx: Error4xx # pro výjimku Nette\Application\BadRequestException + 5xx: Error5xx # pro ostatní výjimky +``` + +Volba `silentLinks` určuje, jak se Nette zachová ve vývojářském režimu, když selže generování odkazu (třeba proto, že neexistuje presenter, atd). Výchozí hodnota `false` znamená, že Nette vyhodí `E_USER_WARNING` chybu. Nastavením na `true` dojde k potlačení této chybové hlášky. V produkčním prostředí se `E_USER_WARNING` vyvolá vždy. Toto chování můžeme také ovlivnit nastavením proměnné presenteru [$invalidLinkMode |creating-links#Neplatné odkazy]. -Volba `silentLinks` určuje, jak se Nette zachová ve vývojářském režimu, když selže generování odkazu (třeba proto, že neexistuje presenter, atd). Výchozí hodnota `false` znamená, že Nette vyhodí `E_USER_WARNING` chybu. Nastavením na `true` dojde k potlačení této chybové hlášky. V produkčním prostředí se `E_USER_WARNING` vyvolá vždy. Toto chování můžeme také ovlivnit nastavením proměnné presenteru [$invalidLinkMode|creating-links#neplatne-odkazy]. +[Aliasy zjednodušují odkazování |creating-links#Aliasy] na často používané presentery. -[Mapování definuje pravidla |modules#mapování], podle kterých se z názvu presenteru odvodí název třídy. +[Mapování definuje pravidla |directory-structure#Mapování presenterů], podle kterých se z názvu presenteru odvodí název třídy. Automatická registrace presenterů @@ -76,16 +89,25 @@ latte: # generuje šablony s hlavičkou declare(strict_types=1) strictTypes: ... # (bool) výchozí je false + # zapne režim [striktního parseru |latte:develop#striktní režim] + strictParsing: ... # (bool) výchozí je false + + # aktivuje [kontrolu vygenerovaného kódu |latte:develop#Kontrola vygenerovaného kódu] + phpLinter: ... # (string) výchozí je null + + # nastaví locale + locale: cs_CZ # (string) výchozí je null + # třída objektu $this->template templateClass: App\MyTemplateClass # výchozí je Nette\Bridges\ApplicationLatte\DefaultTemplate ``` -Pokud používáte Latte verze 3, můžete přidávat nové [rozšíření |latte:creating-extension] pomocí: +Pokud používáte Latte verze 3, můžete přidávat nové [rozšíření |latte:extending-latte#Latte Extension] pomocí: ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` Pokud používáte Latte verze 2, můžete registrovat nové tagy (makra) buď uvedením jména třídy, nebo referencí na službu. Jako výchozí je zavolána metoda `install()`, ale to lze změnit tím, že uvedeme jméno jiné metody: @@ -152,3 +174,18 @@ Nastavení direktiv PHP. Přehled všech direktiv naleznete na [php.net |https:/ php: date.timezone: Europe/Prague ``` + + +Služby DI +========= + +Tyto služby se přidávají do DI kontejneru: + +| Název | Typ | Popis +|---------------------------------------------------------- +| `application.application` | [api:Nette\Application\Application] | [spouštěč celé aplikace |how-it-works#Nette Application] +| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] +| `application.presenterFactory` | [api:Nette\Application\PresenterFactory] | továrna na presentery +| `application.###` | [api:Nette\Application\UI\Presenter] | jednotlivé presentery +| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | továrna objektu `Latte\Engine` +| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | továrna pro [`$this->template` |templates] diff --git a/application/cs/creating-links.texy b/application/cs/creating-links.texy index a21c986098..8658cb3646 100644 --- a/application/cs/creating-links.texy +++ b/application/cs/creating-links.texy @@ -12,7 +12,7 @@ Tvořit odkazy v Nette je jednoduché, jako ukazovat prstem. Stačí jen namíř
    -Díky [obousměrnému routování |routing] nebudete nikdy muset do šablon či kódu zapisovat navrdo URL adresy vaší aplikace, které se mohou později měnit, nebo je komplikovaně skládat. V odkazu stačí uvést presenter a akci, předat případné parametry a framework už URL vygeneruje sám. Vlastně je to velice podobné, jako když voláte funkci. To se vám bude líbit. +Díky [obousměrnému routování |routing] nebudete nikdy muset do šablon či kódu zapisovat natvrdo URL adresy vaší aplikace, které se mohou později měnit, nebo je komplikovaně skládat. V odkazu stačí uvést presenter a akci, předat případné parametry a framework už URL vygeneruje sám. Vlastně je to velice podobné, jako když voláte funkci. To se vám bude líbit. V šabloně presenteru @@ -24,7 +24,7 @@ Nejčastěji vytváříme odkazy v šablonách a skvělým pomocníkem je atribu detail ``` -Všimněte si, že místo HTML atributu `href` jsme použili [n:atribut |latte:syntax#n-atributy] `n:href`. Jeho hodnotou pak není URL, jak by tomu bylo v případě atributu `href`, ale název presenteru a akce. +Všimněte si, že místo HTML atributu `href` jsme použili [n:atribut |latte:syntax#n:atributy] `n:href`. Jeho hodnotou pak není URL, jak by tomu bylo v případě atributu `href`, ale název presenteru a akce. Kliknutí na odkaz je, zjednodušeně řečeno, něco jako zavolání metody `ProductPresenter::renderShow()`. A pokud má ve své signatuře parametry, můžeme ji volat s argumenty: @@ -38,7 +38,7 @@ Je možné předávat i pojmenované parametry. Následující odkaz předává detail produktu ``` -Pokud metoda `ProductPresenter::renderShow()` nemá `$lang` ve své signatuře, může si hodnotu parametru zjistit pomocí `$lang = $this->getParameter('lang')`. +Pokud metoda `ProductPresenter::renderShow()` nemá `$lang` ve své signatuře, může si hodnotu parametru zjistit pomocí `$lang = $this->getParameter('lang')` nebo z [property |presenters#Parametry požadavku]. Pokud jsou parametry uložené v poli, lze je rozvinout operátorem `...` (v Latte 2.x operátorem `(expand)`): @@ -47,7 +47,7 @@ Pokud jsou parametry uložené v poli, lze je rozvinout operátorem `...` (v Lat detail produktu ``` -V odkazech se také automaticky předávají tzv. [persistentní parametry|presenters#persistentní parametry]. +V odkazech se také automaticky předávají tzv. [persistentní parametry |presenters#Persistentní parametry]. Atribut `n:href` je velmi šikovný pro HTML značky ``. Chceme-li odkaz vypsat jinde, například v textu, použijeme `{link}`: @@ -103,14 +103,14 @@ Pokud je cílem akce `default`, můžeme ji vynechat, ale dvojtečka musí zůst úvodní stránka ``` -Odkazy mohou také směřovat do jiných [modulů |modules]. Zde se odkazy rozlišují na relativní do zanořeného submodulu, nebo absolutní. Princip je analogický k cestám na disku, jen místo lomítek jsou dvojtečky. Předpokládejme, že aktuální presenter je součástí modulu `Front`, potom zapíšeme: +Odkazy mohou také směřovat do jiných [modulů |directory-structure#Presentery a šablony]. Zde se odkazy rozlišují na relativní do zanořeného submodulu, nebo absolutní. Princip je analogický k cestám na disku, jen místo lomítek jsou dvojtečky. Předpokládejme, že aktuální presenter je součástí modulu `Front`, potom zapíšeme: ```latte odkaz na Front:Shop:Product:show odkaz na Admin:Product:show ``` -Speciálním případem je odkaz [na sebe sama|#Odkaz na aktuální stránku], kdy jako cíl uvedeme `this`. +Speciálním případem je odkaz [na sebe sama |#Odkaz na aktuální stránku], kdy jako cíl uvedeme `this`. ```latte refresh @@ -130,6 +130,8 @@ Odkazy generované pomocí `link()` nebo `n:href` jsou vždy absolutní cesty (t Pro vygenerování absolutní URL přidejte na začátek dvě lomítka (např. `n:href="//Home:"`). Nebo lze přepnout presenter, aby generoval jen absolutní odkazy nastavením `$this->absoluteUrls = true`. +V šabloně lze také použít filtr `|absoluteUrl`, který relativní cestu převede na absolutní. + Odkaz na aktuální stránku ========================= @@ -140,7 +142,7 @@ Cíl `this` vytvoří odkaz na aktuální stránku: refresh ``` -Zároveň se přenáší i všechny parametry uvedené v signatuře `render()` nebo `action()` metody. Takže pokud jsme na stránce `Product:show` a `id: 123`, odkaz na `this` předá i tento parameter. +Zároveň se přenáší i všechny parametry uvedené v signatuře metody `action()` nebo `render()`, pokud není `action()` definovaná. Takže pokud jsme na stránce `Product:show` a `id: 123`, odkaz na `this` předá i tento parameter. Samozřejmě je možné parametry specifikovat přímo: @@ -179,10 +181,25 @@ Pro zjištění, zda jsme v určitém modulu nebo jeho submodulu, použijeme met ``` +Změna základu pro odkazy .{data-version:v3.2.7} +=============================================== + +Ve výchozím stavu se relativní odkazy odvíjejí od aktuálního presenteru. To lze změnit pomocí `{linkBase}`: + +```latte +{linkBase Admin:Dashboard} +detail produktu +``` + +Odkaz povede na `Admin:Dashboard:Product:show`. Ovlivněny jsou pouze relativní odkazy - absolutní odkazy začínající dvojtečkou a odkazy na aktuální presenter (`this`, `show`) zůstávají nezměněny. + +`{linkBase}` platí pro celou šablonu a je užitečné zejména v šablonách layoutu, kde zajistí konzistentní odkazy nezávisle na volajícím presenteru. + + Odkazy na signál ================ -Cílem odkazu nemusí být jen presenter a akce, ale také [signál|components#Signál] (volají metodu `handle()`). Pak je syntaxe následující: +Cílem odkazu nemusí být jen presenter a akce, ale také [signál |components#Signál] (volají metodu `handle()`). Pak je syntaxe následující: ``` [//] [sub-component:]signal! [#fragment] @@ -213,7 +230,7 @@ Protože [komponenty|components] jsou samostatné znovupoužitelné celky, kter Pokud bychom chtěli v šabloně komponenty odkazovat na presentery, použijeme k tomu značku `{plink}`: ```latte -úvod +úvod ``` nebo v kódu @@ -223,6 +240,30 @@ $this->getPresenter()->link('Home:default') ``` +Aliasy .{data-version:v3.2.2} +============================= + +Občas se může hodit přiřadit dvojici Presenter:akce snadno zapamatovatelný alias. Například úvodní stránku `Front:Home:default` pojmenovat jednoduše jako `home` nebo `Admin:Dashboard:default` jako `admin`. + +Aliasy se definují v [konfiguraci|configuration] pod klíčem `application › aliases`: + +```neon +application: + aliases: + home: Front:Home:default + admin: Admin:Dashboard:default + sign: Front:Sign:in +``` + +V odkazech se pak zapisují pomocí zavináče, například: + +```latte +administrace +``` + +Podporované jsou i ve všech metodách pracujících s odkazy, jako je `redirect()` a podobně. + + Neplatné odkazy =============== @@ -257,6 +298,6 @@ Jak vytvářet odkazy s podobným komfortem jako má metoda `link()`, ale bez p LinkGenerátor je služba, kterou si můžete nechat předat přes konstruktor a poté vytvářet odkazy jeho metodou `link()`. -Oproti presenterům je tu rozdíl. LinkGenerator vytváří všechny odkazy rovnou jako absolutní URL. A dále neexistuje žádný "aktuální presenter", takže nelze jako cíl uvést jen název akce `link('default')` nebo uvádět relativní cesty k [modulům |modules]. +Oproti presenterům je tu rozdíl. LinkGenerator vytváří všechny odkazy rovnou jako absolutní URL. A dále neexistuje žádný "aktuální presenter", takže nelze jako cíl uvést jen název akce `link('default')` nebo uvádět relativní cesty k modulům. Neplatné odkazy vždy vyhazují `Nette\Application\UI\InvalidLinkException`. diff --git a/application/cs/directory-structure.texy b/application/cs/directory-structure.texy new file mode 100644 index 0000000000..3e3f48918a --- /dev/null +++ b/application/cs/directory-structure.texy @@ -0,0 +1,526 @@ +Adresářová struktura aplikace +***************************** + +
    + +Jak navrhnout přehlednou a škálovatelnou adresářovou strukturu pro projekty v Nette Framework? Ukážeme si osvědčené postupy, které vám pomohou s organizací kódu. Dozvíte se: + +- jak **logicky rozčlenit** aplikaci do adresářů +- jak strukturu navrhnout tak, aby **dobře škálovala** s růstem projektu +- jaké jsou **možné alternativy** a jejich výhody či nevýhody + +
    + + +Důležité je zmínit, že Nette Framework samotný na žádné konkrétní struktuře nelpí. Je navržen tak, aby se dal snadno přizpůsobit jakýmkoliv potřebám a preferencím. + + +Základní struktura projektu +=========================== + +Přestože Nette Framework nediktuje žádnou pevnou adresářovou strukturu, existuje osvědčené výchozí uspořádání v podobě [Web Project|https://github.com/nette/web-project]: + +/--pre +web-project/ +├── app/ ← adresář s aplikací +├── assets/ ← soubory SCSS, JS, obrázky..., alternativně resources/ +├── bin/ ← skripty pro příkazovou řádku +├── config/ ← konfigurace +├── log/ ← logované chyby +├── temp/ ← dočasné soubory, cache +├── tests/ ← testy +├── vendor/ ← knihovny instalované Composerem +└── www/ ← veřejný adresář (document-root) +\-- + +Tuto strukturu můžete libovolně upravovat podle svých potřeb - složky přejmenovat či přesouvat. Poté stačí pouze upravit relativní cesty k adresářům v souboru `Bootstrap.php` a případně `composer.json`. Nic víc není potřeba, žádná složitá rekonfigurace, žádné změny konstant. Nette disponuje chytrou autodetekcí a automaticky rozpozná umístění aplikace včetně její URL základny. + + +Principy organizace kódu +======================== + +Když poprví prozkoumáváte nový projekt, měli byste se v něm rychle zorientovat. Představte si, že rozkliknete adresář `app/Model/` a uvidíte tuto strukturu: + +/--pre +app/Model/ +├── Services/ +├── Repositories/ +└── Entities/ +\-- + +Z ní vyčtete jen to, že projekt používá nějaké služby, repozitáře a entity. O skutečném účelu aplikace se nedozvíte vůbec nic. + +Podívejme se na jiný přístup - **organizaci podle domén**: + +/--pre +app/Model/ +├── Cart/ +├── Payment/ +├── Order/ +└── Product/ +\-- + +Tady je to jiné - na první pohled je jasné, že jde o e-shop. Už samotné názvy adresářů prozrazují, co aplikace umí - pracuje s platbami, objednávkami a produkty. + +První přístup (organizace podle typu tříd) přináší v praxi řadu problémů: kód, který spolu logicky souvisí, je roztříštěný do různých složek a musíte mezi nimi přeskakovat. Proto budeme organizovat podle domén. + + +Jmenné prostory +--------------- + +Je zvykem, že adresářová struktura koresponduje se jmennými prostory v aplikaci. To znamená, že fyzické umístění souborů odpovídá jejich namespace. Například třída umístěná v `app/Model/Product/ProductRepository.php` by měla mít namespace `App\Model\Product`. Tento princip pomáhá v orientaci v kódu a zjednodušuje autoloading. + + +Jednotné vs množné číslo v názvech +---------------------------------- + +Všimněte si, že u hlavních adresářů aplikace používáme jednotné číslo: `app`, `config`, `log`, `temp`, `www`. Stejně tak i uvnitř aplikace: `Model`, `Core`, `Presentation`. Je to proto, že každý z nich představuje jeden ucelený koncept. + +Podobně třeba `app/Model/Product` reprezentuje vše kolem produktů. Nenazveme to `Products`, protože nejde o složku plnou produktů (to by tam byly soubory `nokia.php`, `samsung.php`). Je to namespace obsahující třídy pro práci s produkty - `ProductRepository.php`, `ProductService.php`. + +Složka `app/Tasks` je v množném čísle proto, že obsahuje sadu samostatných spustitelných skriptů - `CleanupTask.php`, `ImportTask.php`. Každý z nich je samostatnou jednotkou. + +Pro konzistenci doporučujeme používat: +- Jednotné číslo pro namespace reprezentující funkční celek (byť pracující s více entitami) +- Množné číslo pro kolekce samostatných jednotek +- V případě nejistoty nebo pokud nad tím nechcete přemýšlet, zvolte jednotné číslo + + +Veřejný adresář `www/` +====================== + +Tento adresář je jediný přístupný z webu (tzv. document-root). Často se můžete setkat i s názvem `public/` místo `www/` - je to jen otázka konvence a na funkčnost rostlináře to nemá vliv. Adresář obsahuje: +- [Vstupní bod |bootstrapping#index.php] aplikace `index.php` +- Soubor `.htaccess` s pravidly pro mod_rewrite (u Apache) +- Statické soubory (CSS, JavaScript, obrázky) +- Uploadované soubory + +Pro správné zabezpečení aplikace je zásadní mít správně [nakonfigurovaný document-root |nette:troubleshooting#Jak změnit či ostranit z URL adresář www]. + +.[note] +Nikdy neumisťujte do tohoto adresáře složku `node_modules/` - obsahuje tisíce souborů, které mohou být spustitelné a neměly by být veřejně dostupné. + + +Aplikační adresář `app/` +======================== + +Toto je hlavní adresář s aplikačním kódem. Základní struktura: + +/--pre +app/ +├── Core/ ← infrastrukturní záležitosti +├── Model/ ← business logika +├── Presentation/ ← presentery a šablony +├── Tasks/ ← příkazové skripty +└── Bootstrap.php ← zaváděcí třída aplikace +\-- + +`Bootstrap.php` je [startovací třída aplikace|bootstrapping], která inicializuje prostředí, načítá konfiguraci a vytváří DI kontejner. + +Pojďme se nyní podívat na jednotlivé podadresáře podrobněji. + + +Presentery a šablony +==================== + +Prezentační část aplikace máme v adresáři `app/Presentation`. Alternativou je krátké `app/UI`. Je to místo pro všechny presentery, jejich šablony a případné pomocné třídy. + +Tuto vrstvu organizujeme podle domén. V komplexním projektu, který kombinuje e-shop, blog a API, by struktura vypadala takto: + +/--pre +app/Presentation/ +├── Shop/ ← e-shop frontend +│ ├── Product/ +│ ├── Cart/ +│ └── Order/ +├── Blog/ ← blog +│ ├── Home/ +│ └── Post/ +├── Admin/ ← administrace +│ ├── Dashboard/ +│ └── Products/ +└── Api/ ← API endpointy + └── V1/ +\-- + +Naopak u jednoduchého blogu bychom použili členění: + +/--pre +app/Presentation/ +├── Front/ ← frontend webu +│ ├── Home/ +│ └── Post/ +├── Admin/ ← administrace +│ ├── Dashboard/ +│ └── Posts/ +├── Error/ +└── Export/ ← RSS, sitemapy atd. +\-- + +Složky jako `Home/` nebo `Dashboard/` obsahují presentery a šablony. Složky jako `Front/`, `Admin/` nebo `Api/` nazýváme **moduly**. Technicky jde o běžné adresáře, které slouží k logickému členění aplikace. + +Každá složka s presenterem obsahuje stejně pojmenovaný presenter a jeho šablony. Například složka `Dashboard/` obsahuje: + +/--pre +Dashboard/ +├── DashboardPresenter.php ← presenter +└── default.latte ← šablona +\-- + +Tato adresářová struktura se odráží ve jmenných prostorech tříd. Například `DashboardPresenter` se nachází ve jmenném prostoru `App\Presentation\Admin\Dashboard` (viz [#mapování presenterů]): + +```php +namespace App\Presentation\Admin\Dashboard; + +class DashboardPresenter extends Nette\Application\UI\Presenter +{ + // ... +} +``` + +Na presenter `Dashboard` uvnitř modulu `Admin` odkazujeme v aplikaci pomocí dvojtečkové notace jako na `Admin:Dashboard`. Na jeho akci `default` potom jako na `Admin:Dashboard:default`. V případě zanořených modulů používáme více dvojteček, například `Shop:Order:Detail:default`. + + +Flexibilní vývoj struktury +-------------------------- + +Jednou z velkých výhod této struktury je, jak elegantně se přizpůsobuje rostoucím potřebám projektu. Jako příklad si vezměme část generující XML feedy. Na začátku máme jednoduchou podobu: + +/--pre +Export/ +├── ExportPresenter.php ← jeden presenter pro všechny exporty +├── sitemap.latte ← šablona pro sitemapu +└── feed.latte ← šablona pro RSS feed +\-- + +Časem přibydou další typy feedů a potřebujeme pro ně více logiky... Žádný problém! Složka `Export/` se jednoduše stane modulem: + +/--pre +Export/ +├── Sitemap/ +│ ├── SitemapPresenter.php +│ └── sitemap.latte +└── Feed/ + ├── FeedPresenter.php + ├── zbozi.latte ← feed pro Zboží.cz + └── heureka.latte ← feed pro Heureka.cz +\-- + +Tato transformace je naprosto plynulá - stačí vytvořit nové podsložky, rozdělit do nich kód a aktualizovat odkazy (např. z `Export:feed` na `Export:Feed:zbozi`). Díky tomu můžeme strukturu postupně rozšiřovat podle potřeby, úroveň zanoření není nijak omezena. + +Pokud například v administraci máte mnoho presenterů týkajících se správy objednávek, jako jsou `OrderDetail`, `OrderEdit`, `OrderDispatch` atd., můžete pro lepší organizovanost v tomto místě vytvořit modul (složku) `Order`, ve kterém budou (složky pro) presentery `Detail`, `Edit`, `Dispatch` a další. + + +Umístění šablon +--------------- + +V předchozích ukázkách jsme viděli, že šablony jsou umístěny přímo ve složce s presenterem: + +/--pre +Dashboard/ +├── DashboardPresenter.php ← presenter +├── DashboardTemplate.php ← volitelná třída pro šablonu +└── default.latte ← šablona +\-- + +Toto umístění se v praxi ukazuje jako nejpohodlnější - všechny související soubory máte hned po ruce. + +Alternativně můžete šablony umístit do podsložky `templates/`. Nette podporuje obě varianty. Dokonce můžete šablony umístit i úplně mimo `Presentation/` složku. Vše o možnostech umístění šablon najdete v kapitole [Hledání šablon |templates#Hledání šablon]. + + +Pomocné třídy a komponenty +-------------------------- + +K prezenterům a šablonám často patří i další pomocné soubory. Umístíme je logicky podle jejich působnosti: + +1. **Přímo u presenteru** v případě specifických komponent pro daný presenter: + +/--pre +Product/ +├── ProductPresenter.php +├── ProductGrid.php ← komponenta pro výpis produktů +└── FilterForm.php ← formulář pro filtrování +\-- + +2. **Pro modul** - doporučujeme využít složku `Accessory`, která se umístí přehledně hned na začátku abecedy: + +/--pre +Front/ +├── Accessory/ +│ ├── NavbarControl.php ← komponenty pro frontend +│ └── TemplateFilters.php +├── Product/ +└── Cart/ +\-- + +3. **Pro celou aplikaci** - v `Presentation/Accessory/`: +/--pre +app/Presentation/ +├── Accessory/ +│ ├── LatteExtension.php +│ └── TemplateFilters.php +├── Front/ +└── Admin/ +\-- + +Nebo můžete pomocné třídy jako `LatteExtension.php` nebo `TemplateFilters.php` umístit do infrastrukturní složky `app/Core/Latte/`. A komponenty do `app/Components`. Volba závisí na zvyklostech týmu. + + +Model - srdce aplikace +====================== + +Model obsahuje veškerou business logiku aplikace. Pro jeho organizaci platí opět pravidlo - strukturujeme podle domén: + +/--pre +app/Model/ +├── Payment/ ← vše kolem plateb +│ ├── PaymentFacade.php ← hlavní vstupní bod +│ ├── PaymentRepository.php +│ ├── Payment.php ← entita +├── Order/ ← vše kolem objednávek +│ ├── OrderFacade.php +│ ├── OrderRepository.php +│ ├── Order.php +└── Shipping/ ← vše kolem dopravy +\-- + +V modelu se typicky setkáte s těmito typy tříd: + +**Fasády**: představují hlavní vstupní bod do konkrétní domény v aplikaci. Působí jako orchestrátor, který koordinuje spolupráci mezi různými službami za účelem implementace kompletních use-cases (jako "vytvoř objednávku" nebo "zpracuj platbu"). Pod svojí orchestrační vrstvou fasáda skrývá implementační detaily před zbytkem aplikace, čímž poskytuje čisté rozhraní pro práci s danou doménou. + +```php +class OrderFacade +{ + public function createOrder(Cart $cart): Order + { + // validace + // vytvoření objednávky + // odeslání e-mailu + // zapsání do statistik + } +} +``` + +**Služby**: zaměřují se na specifickou business operaci v rámci domény. Na rozdíl od fasády, která orchestruje celé use-cases, služba implementuje konkrétní byznys logiku (jako výpočty cen nebo zpracování plateb). Služby jsou typicky bezstavové a mohou být použity buď fasádami jako stavební bloky pro komplexnější operace, nebo přímo jinými částmi aplikace pro jednodušší úkony. + +```php +class PricingService +{ + public function calculateTotal(Order $order): Money + { + // výpočet ceny + } +} +``` + +**Repozitáře**: zajišťují veškerou komunikaci s datovým úložištěm, typicky databází. Jeho úkolem je načítání a ukládání entit a implementace metod pro jejich vyhledávání. Repozitář odstiňuje zbytek aplikace od implementačních detailů databáze a poskytuje objektově orientované rozhraní pro práci s daty. + +```php +class OrderRepository +{ + public function find(int $id): ?Order + { + } + + public function findByCustomer(int $customerId): array + { + } +} +``` + +**Entity**: objekty reprezentující hlavní byznys koncepty v aplikaci, které mají svou identitu a mění se v čase. Typicky jde o třídy mapované na databázové tabulky pomocí ORM (jako Nette Database Explorer nebo Doctrine). Entity mohou obsahovat business pravidla týkající se jejich dat a validační logiku. + +```php +// Entita mapovaná na databázovou tabulku orders +class Order extends Nette\Database\Table\ActiveRow +{ + public function addItem(Product $product, int $quantity): void + { + $this->related('order_items')->insert([ + 'product_id' => $product->id, + 'quantity' => $quantity, + 'unit_price' => $product->price, + ]); + } +} +``` + +**Value objekty**: neměnné objekty reprezentující hodnoty bez vlastní identity - například peněžní částka nebo e-mailová adresa. Dvě instance value objektu se stejnými hodnotami jsou považovány za identické. + + +Infrastrukturní kód +=================== + +Složka `Core/` (nebo také `Infrastructure/`) je domovem pro technický základ aplikace. Infrastrukturní kód typicky zahrnuje: + +/--pre +app/Core/ +├── Router/ ← routování a URL management +│ └── RouterFactory.php +├── Security/ ← autentizace a autorizace +│ ├── Authenticator.php +│ └── Authorizator.php +├── Logging/ ← logování a monitoring +│ ├── SentryLogger.php +│ └── FileLogger.php +├── Cache/ ← cachovací vrstva +│ └── FullPageCache.php +└── Integration/ ← integrace s ext. službami + ├── Slack/ + └── Stripe/ +\-- + +U menších projektů pochopitelně stačí ploché členění: + +/--pre +Core/ +├── RouterFactory.php +├── Authenticator.php +└── QueueMailer.php +\-- + +Jde o kód, který: + +- Řeší technickou infrastrukturu (routování, logování, cachování) +- Integruje externí služby (Sentry, Elasticsearch, Redis) +- Poskytuje základní služby pro celou aplikaci (mail, databáze) +- Je většinou nezávislý na konkrétní doméně - cache nebo logger funguje stejně pro eshop či blog. + +Tápete, jestli určitá třída patří sem, nebo do modelu? Klíčový rozdíl je v tom, že kód v `Core/`: + +- Neví nic o doméně (produkty, objednávky, články) +- Je většinou možné ho přenést do jiného projektu +- Řeší "jak to funguje" (jak poslat mail), nikoliv "co to dělá" (jaký mail poslat) + +Příklad pro lepší pochopení: + +- `App\Core\MailerFactory` - vytváří instance třídy pro odesílání e-mailů, řeší SMTP nastavení +- `App\Model\OrderMailer` - používá `MailerFactory` k odesílání e-mailů o objednávkách, zná jejich šablony a ví, kdy se mají poslat + + +Příkazové skripty +================= + +Aplikace často potřebují vykonávat činnosti mimo běžné HTTP požadavky - ať už jde o zpracování dat v pozadí, údržbu, nebo periodické úlohy. Pro spouštění slouží jednoduché skripty v adresáři `bin/`, samotnou implementační logiku pak umisťujeme do `app/Tasks/` (případně `app/Commands/`). + +Příklad: + +/--pre +app/Tasks/ +├── Maintenance/ ← údržbové skripty +│ ├── CleanupCommand.php ← mazání starých dat +│ └── DbOptimizeCommand.php ← optimalizace databáze +├── Integration/ ← integrace s externími systémy +│ ├── ImportProducts.php ← import z dodavatelského systému +│ └── SyncOrders.php ← synchronizace objednávek +└── Scheduled/ ← pravidelné úlohy + ├── NewsletterCommand.php ← rozesílání newsletterů + └── ReminderCommand.php ← notifikace zákazníkům +\-- + +Co patří do modelu a co do příkazových skriptů? Například logika pro odeslání jednoho e-mailu je součástí modelu, hromadná rozesílka tisíců e-mailů už patří do `Tasks/`. + +Úlohy obvykle [spouštíme z příkazového řádku |https://blog.nette.org/cs/cli-skripty-v-nette-aplikaci] nebo přes cron. Lze je spouštět i přes HTTP požadavek, ale je nutné myslet na bezpečnost. Presenter, který úlohu spustí, je potřeba zabezpečit, například jen pro přihlášené uživatele nebo silným tokenem a přístupem z povolených IP adres. U dlouhých úloh je nutné zvýšit časový limit skriptu a použít `session_write_close()`, aby se nezamykala session. + + +Další možné adresáře +==================== + +Kromě zmíněných základních adresářů můžete podle potřeb projektu přidat další specializované složky. Podívejme se na nejčastější z nich a jejich použití: + +/--pre +app/ +├── Api/ ← logika pro API nezávislá na prezentační vrstvě +├── Database/ ← migrační skripty a seedery pro testovací data +├── Components/ ← sdílené vizuální komponenty napříč celou aplikací +├── Event/ ← užitečné pokud používáte event-driven architekturu +├── Mail/ ← e-mailové šablony a související logika +└── Utils/ ← pomocné třídy +\-- + +Pro sdílené vizuální komponenty používané v presenterech napříč aplikací lze použít složku `app/Components` nebo `app/Controls`: + +/--pre +app/Components/ +├── Form/ ← sdílené formulářové komponenty +│ ├── SignInForm.php +│ └── UserForm.php +├── Grid/ ← komponenty pro výpisy dat +│ └── DataGrid.php +└── Navigation/ ← navigační prvky + ├── Breadcrumbs.php + └── Menu.php +\-- + +Sem patří komponenty, které mají komplexnější logiku. Pokud chcete komponenty sdílet mezi více projekty, je vhodné je vyčlenit do samostatného composer balíčku. + +Do adresáře `app/Mail` můžete umístit správu e-mailové komunikace: + +/--pre +app/Mail/ +├── templates/ ← e-mailové šablony +│ ├── order-confirmation.latte +│ └── welcome.latte +└── OrderMailer.php +\-- + + +Mapování presenterů +=================== + +Mapování definuje pravidla pro odvozování názvu třídy z názvu presenteru. Specifikujeme je v [konfiguraci|configuration] pod klíčem `application › mapping`. + +Na této stránce jsme si ukázali, že presentery umísťujeme do složky `app/Presentation` (případně `app/UI`). Tuto konvenci musíme Nette sdělit v konfiguračním souboru. Stačí jeden řádek: + +```neon +application: + mapping: App\Presentation\*\**Presenter +``` + +Jak mapování funguje? Pro lepší pochopení si nejprve představme aplikaci bez modulů. Chceme, aby třídy presenterů spadaly do jmenného prostoru `App\Presentation`, aby se presenter `Home` mapoval na třídu `App\Presentation\HomePresenter`. Což dosáhneme touto konfigurací: + +```neon +application: + mapping: App\Presentation\*Presenter +``` + +Mapování funguje tak, že název presenteru `Home` nahradí hvězdičku v masce `App\Presentation\*Presenter`, čímž získáme výsledný název třídy `App\Presentation\HomePresenter`. Jednoduché! + +Jak ale vidíte v ukázkách v této a dalších kapitolách, třídy presenterů umisťujeme do eponymních podadresářů, například presenter `Home` se mapuje na třídu `App\Presentation\Home\HomePresenter`. Toho dosáhneme zdvojením dvojtečky (vyžaduje Nette Application 3.2): + +```neon +application: + mapping: App\Presentation\**Presenter +``` + +Nyní přistoupíme k mapování presenterů do modulů. Pro každý modul můžeme definovat specifické mapování: + +```neon +application: + mapping: + Front: App\Presentation\Front\**Presenter + Admin: App\Presentation\Admin\**Presenter + Api: App\Api\*Presenter +``` + +Podle této konfigurace se presenter `Front:Home` mapuje na třídu `App\Presentation\Front\Home\HomePresenter`, zatímco presenter `Api:OAuth` na třídu `App\Api\OAuthPresenter`. + +Protože moduly `Front` i `Admin` mají podobný způsob mapování a takových modulů bude nejspíš více, je možné vytvořit obecné pravidlo, které je nahradí. Do masky třídy tak přibude nová hvězdička pro modul: + +```neon +application: + mapping: + *: App\Presentation\*\**Presenter + Api: App\Api\*Presenter +``` + +Funguje to i pro hlouběji zanořené adresářové struktury, jako je například presenter `Admin:User:Edit`, se segment s hvězdičkou opakuje pro každou úroveň a výsledkem je třída `App\Presentation\Admin\User\Edit\EditPresenter`. + +Alternativním zápisem je místo řetězce použít pole skládající se ze tří segmentů. Tento zápis je ekvivaletní s předchozím: + +```neon +application: + mapping: + *: [App\Presentation, *, **Presenter] + Api: [App\Api, '', *Presenter] +``` diff --git a/application/cs/how-it-works.texy b/application/cs/how-it-works.texy index b0025a12a0..f3c4b84a03 100644 --- a/application/cs/how-it-works.texy +++ b/application/cs/how-it-works.texy @@ -22,32 +22,34 @@ Adresářová struktura vypadá nějak takto: /--pre web-project/ ├── app/ ← adresář s aplikací -│ ├── Presenters/ ← presentery a šablony -│ │ ├── HomePresenter.php ← třída presenteru Home -│ │ └── templates/ ← adresář se šablonami -│ │ ├── @layout.latte ← šablona layoutu -│ │ └── Home/ ← šablony presenteru Home -│ │ └── default.latte ← šablona akce 'default' -│ ├── Router/ ← konfigurace URL adres +│ ├── Core/ ← základní třídy nutné pro chod +│ │ └── RouterFactory.php ← konfigurace URL adres +│ ├── Presentation/ ← presentery, šablony & spol. +│ │ ├── @layout.latte ← šablona layoutu +│ │ └── Home/ ← adresář presenteru Home +│ │ ├── HomePresenter.php ← třída presenteru Home +│ │ └── default.latte ← šablona akce default │ └── Bootstrap.php ← zaváděcí třída Bootstrap +├── assets/ ← zdroje (SCSS, TypeScript, zdrojové obrázky) ├── bin/ ← skripty spouštěné z příkazové řádky ├── config/ ← konfigurační soubory │ ├── common.neon -│ └── local.neon +│ └── services.neon ├── log/ ← logované chyby ├── temp/ ← dočasné soubory, cache, … ├── vendor/ ← knihovny instalované Composerem │ ├── ... │ └── autoload.php ← autoloading všech nainstalovaných balíčků ├── www/ ← veřejný adresář neboli document-root projektu +│ ├── assets/ ← zkompilované statické soubory (CSS, JS, obrázky, …) │ ├── .htaccess ← pravidla mod_rewrite │ └── index.php ← prvotní soubor, kterým se aplikace spouští └── .htaccess ← zakazuje přístup do všech adresářů krom www \-- -Adresářovou strukturu můžete jakkoliv měnit, složky přejmenovat či přesunout, a poté pouze upravit cesty k `log/` a `temp/` v souboru `Bootstrap.php` a dále cestu k tomuto souboru v `composer.json` v sekci `autoload`. Nic víc, žádná složitá rekonfigurace, žádné změny konstant. Nette totiž disponuje [chytrou autodetekcí|bootstrap#vyvojarsky-vs-produkcni-rezim]. +Adresářovou strukturu můžete jakkoliv měnit, složky přejmenovat či přesunout, je zcela flexibilní. Nette navíc disponuje chytrou autodetekcí a automaticky rozpozná umístění aplikace včetně její URL základny. -U trošku větších aplikací můžeme složky s presentery a šablonami rozčlenit na disku do podadresářů a třídy do jmenných prostorů, kterým říkáme [moduly |modules]. +U trošku větších aplikací můžeme složky s presentery a šablonami [rozčlenit do podadresářů |directory-structure#Presentery a šablony] a třídy do jmenných prostorů, kterým říkáme moduly. Adresář `www/` představuje tzv. veřejný adresář neboli document-root projektu. Můžete jej přejmenovat bez nutnosti cokoliv dalšího nastavovat na straně aplikace. Jen je potřeba [nakonfigurovat hosting |nette:troubleshooting#Jak změnit či ostranit z URL adresář www] tak, aby document-root mířil do tohoto adresáře. @@ -65,7 +67,7 @@ Aplikace WebProject je připravená ke spuštění, není třeba vůbec nic konf HTTP požadavek ============== -Vše začíná ve chvíli, kdy uživatel v prohlížeči otevře stránku. Tedy když prohlížeč zaklepe na server s HTTP požadavkem. Požadavek míří na jediný PHP soubor, který se nachází ve veřejném adresáři `www/`, a tím je `index.php`. Dejme tomu, že jde o požadavek na adresu `https://example.com/product/123`. Díky vhodnému [nastavení serveru|nette:troubleshooting#Jak nastavit server pro hezká URL?] se i tohle URL mapuje na soubor `index.php` a ten se vykoná. +Vše začíná ve chvíli, kdy uživatel v prohlížeči otevře stránku. Tedy když prohlížeč zaklepe na server s HTTP požadavkem. Požadavek míří na jediný PHP soubor, který se nachází ve veřejném adresáři `www/`, a tím je `index.php`. Dejme tomu, že jde o požadavek na adresu `https://example.com/product/123`. Díky vhodnému [nastavení serveru |nette:troubleshooting#Jak nastavit server pro hezká URL] se i tohle URL mapuje na soubor `index.php` a ten se vykoná. Jeho úkolem je: @@ -75,11 +77,11 @@ Jeho úkolem je: Jakou že továrnu? Nevyrábíme přece traktory, ale webové stránky! Vydržte, hned se to vysvětlí. -Slovy „inicializace prostředí“ myslíme například to, že se aktivuje [Tracy|tracy:], což je úžasný nástroj pro logování nebo vizualizaci chyb. Na produkčním serveru chyby loguje, na vývojovém rovnou zobrazuje. Tudíž k inicializaci patří i rozhodnutí, zda web běží v produkčním nebo vývojářském režimu. K tomu Nette používá autodetekci: pokud web spouštíte na localhost, běží v režimu vývojářském. Nemusíte tak nic konfigurovat a aplikace je rovnou připravena jak pro vývoj, tak ostré nasazení. Tyhle kroky se provádějí a jsou podrobně rozepsané v kapitole o [třídě Bootstrap|bootstrap]. +Slovy „inicializace prostředí“ myslíme například to, že se aktivuje [Tracy|tracy:], což je úžasný nástroj pro logování nebo vizualizaci chyb. Na produkčním serveru chyby loguje, na vývojovém rovnou zobrazuje. Tudíž k inicializaci patří i rozhodnutí, zda web běží v produkčním nebo vývojářském režimu. K tomu Nette používá [chytrou autodetekci |bootstrapping#Vývojářský vs produkční režim]: pokud web spouštíte na localhost, běží v režimu vývojářském. Nemusíte tak nic konfigurovat a aplikace je rovnou připravena jak pro vývoj, tak ostré nasazení. Tyhle kroky se provádějí a jsou podrobně rozepsané v kapitole o [třídě Bootstrap|bootstrapping]. Třetím bodem (ano, druhý jsme přeskočili, ale vrátíme se k němu) je spuštění aplikace. Vyřizování HTTP požadavků má v Nette na starosti třída `Nette\Application\Application` (dále `Application`), takže když říkáme spustit aplikaci, myslíme tím konkrétně zavolání metody s příznačným názvem `run()` na objektu této třídy. -Nette je mentor, který vás vede k psaní čistých aplikací podle osvědčených metodik. A jedna z těch naprosto nejosvědčenějších se nazývá **dependency injection**, zkráceně DI. V tuto chvíli vás nechceme zatěžovat vysvětlováním DI, od toho je tu [samostatná kapitola|dependency-injection:introduction], podstatný je důsledek, že klíčové objekty nám bude obvykle vytvářet továrna na objekty, které se říká **DI kontejner** (zkráceně DIC). Ano, to je ta továrna, o které byla před chvíli řeč. A vyrobí nám i objekt `Application`, proto potřebujeme nejprve kontejner. Získáme jej pomocí třídy `Configurator` a necháme jej vyrobit objekt `Application`, zavoláme na něm metodu `run()` a tím se spustí Nette aplikace. Přesně tohle se děje v souboru [index.php|bootstrap#index.php]. +Nette je mentor, který vás vede k psaní čistých aplikací podle osvědčených metodik. A jedna z těch naprosto nejosvědčenějších se nazývá **dependency injection**, zkráceně DI. V tuto chvíli vás nechceme zatěžovat vysvětlováním DI, od toho je tu [samostatná kapitola|dependency-injection:introduction], podstatný je důsledek, že klíčové objekty nám bude obvykle vytvářet továrna na objekty, které se říká **DI kontejner** (zkráceně DIC). Ano, to je ta továrna, o které byla před chvíli řeč. A vyrobí nám i objekt `Application`, proto potřebujeme nejprve kontejner. Získáme jej pomocí třídy `Configurator` a necháme jej vyrobit objekt `Application`, zavoláme na něm metodu `run()` a tím se spustí Nette aplikace. Přesně tohle se děje v souboru [index.php |bootstrapping#index.php]. Nette Application @@ -91,7 +93,7 @@ Aplikace psané v Nette se člení do spousty tzv. presenterů (v jiných framew Application začne tím, že požádá tzv. router, aby rozhodl, kterému z presenterů předat aktuální požadavek k vyřízení. Router rozhodne, čí je to zodpovědnost. Podívá se na vstupní URL `https://example.com/product/123` a na základě toho, jak je nastavený, rozhodne, že tohle je práce např. pro **presenter** `Product`, po kterém bude chtít jako **akci** zobrazení (`show`) produktu s `id: 123`. Dvojici presenter + akce je dobrým zvykem zapisovat oddělené dvojtečkou jako `Product:show`. -Tedy router transformoval URL na dvojici `Presenter:action` + parametry, v našem případě `Product:show` + `id: 123`. Jak takový router vypadá se můžete podívat v souboru `app/Router/RouterFactory.php` a podrobně ho popisujeme v kapitole [Routing]. +Tedy router transformoval URL na dvojici `Presenter:action` + parametry, v našem případě `Product:show` + `id: 123`. Jak takový router vypadá se můžete podívat v souboru `app/Core/RouterFactory.php` a podrobně ho popisujeme v kapitole [Routing]. Pojďme dál. Application už zná jméno presenteru a může pokračovat dál. Tím že vyrobí objekt třídy `ProductPresenter`, což je kód presenteru `Product`. Přesněji řečeno, požádá DI kontejner, aby presenter vyrobil, protože od vyrábění je tu on. @@ -121,12 +123,9 @@ Takže, zavolala se metoda `renderShow(123)`, jejíž kód je sice smyšlený p Následně presenter vrátí odpověď. Tou může být HTML stránka, obrázek, XML dokument, odeslání souboru z disku, JSON nebo třeba přesměrování na jinou stránku. Důležité je, že pokud explicitně neřekneme, jak má odpovědět (což je případ `ProductPresenter`), bude odpovědí vykreslení šablony s HTML stránkou. Proč? Protože v 99 % případů chceme vykreslit šablonu, tudíž presenter tohle chování bere jako výchozí a chce nám ulehčit práci. To je smyslem Nette. -Nemusíme ani uvádět, jakou šablonu vykreslit, cestu k ní si odvodí podle jednoduché logiky. V případě presenteru `Product` a akce `show` zkusí, zda existuje jeden z těchto souborů se šablonou uložených relativně od adresáře s třídou `ProductPresenter`: +Nemusíme ani uvádět, jakou šablonu vykreslit, cestu k ní si odvodí sám. V případě akce `show` jednodušše zkusí načíst šablonu `show.latte` v adresáři s třídou `ProductPresenter`. Taktéž se pokusí dohledat layout v souboru `@layout.latte` (podrobněji o [dohledávání šablon |templates#Hledání šablon]). -- `templates/Product/show.latte` -- `templates/Product.show.latte` - -Taktéž se pokusí dohledat layout v souboru `@layout.latte` a následně šablonu vykreslí. Tím je úkol presenteru i celé aplikace dokonán a dílo jest završeno. Pokud by šablona neexistovala, vrátí se stránka s chybou 404. Více se o presenterech dočtete na stránce [Presentery|presenters]. +A následně šablony vykreslí. Tím je úkol presenteru i celé aplikace dokonán a dílo jest završeno. Pokud by šablona neexistovala, vrátí se stránka s chybou 404. Více se o presenterech dočtete na stránce [Presentery|presenters]. [* request-flow.svg *] @@ -137,7 +136,7 @@ Pro jistotu, zkusme si zrekapitulovat celý proces s trošku jinou URL: 3) router URL dekóduje jako dvojici `Home:default` 4) vytvoří se objekt třídy `HomePresenter` 5) zavolá se metoda `renderDefault()` (pokud existuje) -6) vykreslí se šablona např. `templates/Home/default.latte` s layoutem např. `templates/@layout.latte` +6) vykreslí se šablona např. `default.latte` s layoutem např. `@layout.latte` Možná jste se teď setkali s velkou spoustou nových pojmů, ale věříme, že dávají smysl. Tvorba aplikací v Nette je ohromná pohodička. @@ -146,7 +145,7 @@ Možná jste se teď setkali s velkou spoustou nových pojmů, ale věříme, ž Šablony ======= -Když už přišla řeč na šablony, v Nette se používá šablonovací systém [Latte |latte:]. Proto taky ty koncovky `.latte` u šablon. Latte se používá jednak proto, že jde o nejlépe zabezpečený šablonovací systém pro PHP, a zároveň také systém nejintuitivnější. Nemusíte se učit mnoho nového, vystačíte si se znalostí PHP a několika značek. Všechno se dozvíte [v dokumentaci |latte:]. +Když už přišla řeč na šablony, v Nette se používá šablonovací systém [Latte |latte:]. Proto taky ty koncovky `.latte` u šablon. Latte se používá jednak proto, že jde o nejlépe zabezpečený šablonovací systém pro PHP, a zároveň také systém nejintuitivnější. Nemusíte se učit mnoho nového, vystačíte si se znalostí PHP a několika značek. Všechno se dozvíte [v dokumentaci |templates]. V šabloně se [vytvářejí odkazy |creating-links] na další presentery & akce takto: @@ -160,10 +159,7 @@ Prostě místo reálného URL napíšete známý pár `Presenter:action` a uvede detail produktu ``` -Generování URL má na starosti už dříve zmíněný router. Totiž routery v Nette jsou výjimečné tím, že dokáží provádět nejen transformace z URL na dvojici presenter:action, ale také obráceně, tedy z názvu presenteru + akce + parametrů vygenerovat URL. -Díky tomu v Nette můžete úplně změnit tvary URL v celé hotové aplikaci, aniž byste změnili jediný znak v šabloně nebo presenteru. Jen tím, že upravíte router. -Také díky tomu funguje tzv. kanonizace, což je další unikátní vlastnost Nette, která přispívá k lepšímu SEO (optimalizaci nalezitelnosti na internetu) tím, že automaticky zabraňuje existenci duplicitního obsahu na různých URL. -Hodně programátorů to považuje za ohromující. +Generování URL má na starosti už dříve zmíněný router. Totiž routery v Nette jsou výjimečné tím, že dokáží provádět nejen transformace z URL na dvojici presenter:action, ale také obráceně, tedy z názvu presenteru + akce + parametrů vygenerovat URL. Díky tomu v Nette můžete úplně změnit tvary URL v celé hotové aplikaci, aniž byste změnili jediný znak v šabloně nebo presenteru. Jen tím, že upravíte router. Také díky tomu funguje tzv. kanonizace, což je další unikátní vlastnost Nette, která přispívá k lepšímu SEO (optimalizaci nalezitelnosti na internetu) tím, že automaticky zabraňuje existenci duplicitního obsahu na různých URL. Hodně programátorů to považuje za ohromující. Interaktivní komponenty @@ -173,7 +169,7 @@ O presenterech vám musíme prozradit ještě jednu věc: mají v sobě zabudova Komponenty jsou samostatné znovupoužitelné celky, které vkládáme do stránek (tedy presenterů). Mohou to být [formuláře |forms:in-presenter], [datagridy |https://componette.org/contributte/datagrid/], menu, hlasovací ankety, vlastně cokoliv, co má smysl používat opakovaně. Můžeme vytvářet vlastní komponenty nebo používat některé z [ohromné nabídky |https://componette.org] open source komponent. -Komponenty zásadním způsobem ovlivňují přístup k tvorbě aplikacím. Otevřou vám nové možnosti skládání stránek z předpřipravených jednotek. A navíc mají něco společného s [Hollywoodem|components#Hollywood style]. +Komponenty zásadním způsobem ovlivňují přístup k tvorbě aplikacím. Otevřou vám nové možnosti skládání stránek z předpřipravených jednotek. A navíc mají něco společného s [Hollywoodem |components#Hollywood style]. DI kontejner a konfigurace @@ -185,9 +181,9 @@ Nemějte obavy, není to žádný magický black box, jak by se třeba mohlo z p Objektům, které DI kontejner vytváří, se z nějakého důvodu říká služby. -Co je na této třídě opravdu speciálního, tak že ji neprogramujete vy, ale framework. On skutečně vygeneruje PHP kód a uloží ho na disk. Vy jen dáváte instrukce, jaké objekty má umět kontejner vyrábět a jak přesně. A tyhle instrukce jsou zapsané v [konfiguračních souborech|bootstrap#konfigurace-di-kontejneru], pro které se používá formát [NEON|neon:format] a tedy mají i příponu `.neon`. +Co je na této třídě opravdu speciálního, tak že ji neprogramujete vy, ale framework. On skutečně vygeneruje PHP kód a uloží ho na disk. Vy jen dáváte instrukce, jaké objekty má umět kontejner vyrábět a jak přesně. A tyhle instrukce jsou zapsané v [konfiguračních souborech |bootstrapping#Konfigurace DI kontejneru], pro které se používá formát [NEON|neon:format] a tedy mají i příponu `.neon`. -Konfigurační soubory slouží čistě k instruování DI kontejneru. Takže když například uvedu v sekci [session|http:configuration#Session] volbu `expiration: 14 days`, tak DI kontejner při vytváření objektu `Nette\Http\Session` reprezentujícího session zavolá jeho metodu `setExpiration('14 days')` a tím se konfigurace stane realitou. +Konfigurační soubory slouží čistě k instruování DI kontejneru. Takže když například uvedu v sekci [session |http:configuration#Session] volbu `expiration: 14 days`, tak DI kontejner při vytváření objektu `Nette\Http\Session` reprezentujícího session zavolá jeho metodu `setExpiration('14 days')` a tím se konfigurace stane realitou. Je tu pro vás připravená celá kapitola popisující, co vše lze [konfigurovat |nette:configuring] a jak [definovat vlastní služby |dependency-injection:services]. diff --git a/application/cs/modules.texy b/application/cs/modules.texy deleted file mode 100644 index 2cb3e42719..0000000000 --- a/application/cs/modules.texy +++ /dev/null @@ -1,148 +0,0 @@ -Moduly -****** - -.[perex] -Moduly představují v Nette logické celky, ze kterých se aplikace skládá. Jejich součástí jsou presentery, šablony, případně i komponenty a modelové třídy. - -S jednou složkou pro presentery a jednou pro šablony bychom si u reálných projektů nevystačili. Mít v jedné složce desítky souborů je minimálně nepřehledné. Jak z toho ven? Jednoduše je na disku rozdělíme do podadresářů a v kódu do jmenných prostorů. A přesně to jsou v Nette moduly. - -Zapomeňme tedy na jednu složku pro presentery a šablony a místo toho vytvoříme moduly, například `Admin` a `Front`. - -/--pre -app/ -├── Presenters/ -├── Modules/ ← adresář s moduly -│ ├── Admin/ ← modul Admin -│ │ ├── Presenters/ ← jeho presentery -│ │ │ ├── DashboardPresenter.php -│ │ │ └── templates/ -│ └── Front/ ← modul Front -│ └── Presenters/ ← jeho presentery -│ └── ... -\-- - -Tuto adresářovou strukturu budou reflektovat jmenné prostory tříd, takže třeba `DashboardPresenter` bude v prostoru `App\Modules\Admin\Presenters`: - -```php -namespace App\Modules\Admin\Presenters; - -class DashboardPresenter extends Nette\Application\UI\Presenter -{ - // ... -} -``` - -Na presenter `Dashboard` uvnitř modulu `Admin` se v rámci aplikace odkazujeme pomocí dvojtečkové notace jako na `Admin:Dashboard`, na jeho akci `default` potom jako na `Admin:Dashboard:default`. -A jak Nette vlastní ví, že `Admin:Dashboard` představuje třídu `App\Modules\Admin\Presenters\DashboardPresenter`? To mu řekneme pomocí [#mapování] v konfiguraci. -Tedy uvedená struktura není pevná a můžete si ji upravit podle potřeb. - -Moduly mohou kromě presenterů a šablon samozřejmě obsahovat všechny další součásti, jako jsou třeba komponenty, modelové třídy, atd. - - -Vnořené moduly --------------- - -Moduly nemusí tvořit jen plochou strukturu, lze vytvářet i submoduly, například: - -/--pre -app/ -├── Modules/ ← adresář s moduly -│ ├── Blog/ ← modul Blog -│ │ ├── Admin/ ← submodul Admin -│ │ │ ├── Presenters/ -│ │ │ └── ... -│ │ └── Front/ ← submodul Front -│ │ ├── Presenters/ -│ │ └── ... -│ ├── Forum/ ← modul Forum -│ │ └── ... -\-- - -Tedy modul `Blog` je rozdělen do submodulů `Admin` a `Front`. A opět se to odrazí na jmenných prostorech, které budou `App\Modules\Blog\Admin\Presenters` apod. Na presenter `Dashboard` uvnitř submodulu se odkazujeme jako `Blog:Admin:Dashboard`. - -Zanořování může pokračovat libovolně hluboko, lze tedy vytvářet sub-submoduly. - - -Vytváření odkazů ----------------- - -Odkazy v šablonách presenterů jsou relativní vůči aktuálnímu modulu. Tedy odkaz `Foo:default` vede na presenter `Foo` v tomtéž modulu, v jakém je aktuální presenter. Pokud je aktuální modul například `Front`, pak odkaz vede takto: - -```latte -odkaz na Front:Product:show -``` - -Odkaz je relativní i pokud je jeho součástí název modulu, ten se pak považuje za submodul: - -```latte -odkaz na Front:Shop:Product:show -``` - -Absolutní odkazy zapisujeme analogicky k absolutním cestám na disku, jen místo lomítek jsou dvojtečky. Tedy absolutní odkaz začíná dvojtečkou: - -```latte -odkaz na Admin:Product:show -``` - -Pro zjištění, zda jsme v určitém modulu nebo jeho submodulu, použijeme funkci `isModuleCurrent(moduleName)`. - -```latte -
  • - ... -
  • -``` - - -Routování ---------- - -Viz [kapitola o routování |routing#Moduly]. - - -Mapování --------- - -Definuje pravidla, podle kterých se z názvu presenteru odvodí název třídy. Zapisujeme je v [konfiguraci|configuration] pod klíčem `application › mapping`. - -Začněme ukázkou, která moduly nepoužívá. Budeme jen chtít, aby třídy presenterů měly jmenný prostor `App\Presenters`. Tedy aby se presenter například `Home` mapoval na třídu `App\Presenters\HomePresenter`. Toho lze docílit následující konfigurací: - -```neon -application: - mapping: - *: App\Presenters\*Presenter -``` - -Název presenteru se nahradí za hvezdičku v masce třídy a výsledkem je název třídy. Snadné! - -Pokud presentery členíme do modulů, můžeme pro každý modul mít vlastní mapování: - -```neon -application: - mapping: - Front: App\Modules\Front\Presenters\*Presenter - Admin: App\Modules\Admin\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -Nyní se presenter `Front:Home` mapuje na třídu `App\Modules\Front\Presenters\HomePresenter` a presenter `Admin:Dashboard` na třídu `App\Modules\Admin\Presenters\DashboardPresenter`. - -Praktičtější bude vytvořit obecné (hvězdičkové) pravidlo, které první dvě nahradí. V masce třídy přibude hvezdička navíc právě pro modul: - -```neon -application: - mapping: - *: App\Modules\*\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -Ale co když používáme vícenásobně zanořené moduly a máme třeba presenter `Admin:User:Edit`? V takovém případě se segment s hvězdičkou představující modul pro každou úroveň jednoduše zopakuje a výsledkem bude třída `App\Modules\Admin\User\Presenters\EditPresenter`. - -Alternativním zápisem je místo řetězce použít pole skládající se ze tří segmentů. Tento zápis je ekvivaletní s předchozím: - -```neon -application: - mapping: - *: [App\Modules, *, Presenters\*Presenter] -``` - -Výchozí hodnotou je `*: *Module\*Presenter`. diff --git a/application/cs/multiplier.texy b/application/cs/multiplier.texy index 864e157001..4bd4c80f76 100644 --- a/application/cs/multiplier.texy +++ b/application/cs/multiplier.texy @@ -1,13 +1,15 @@ Multiplier: dynamické komponenty ******************************** -Nástroj na dynamickou tvorbu interaktivních komponent .[perex] +.[perex] +Nástroj na dynamickou tvorbu interaktivních komponent Vyjděme od typického příkladu: mějme seznam zboží v eshopu, přičemž u každého budeme chtít vypsat formulář pro přidání zboží do košíku. Jednou z možných variant je obalit celý výpis do jednoho formuláře. Mnohem pohodlnější způsob nám však nabízí [api:Nette\Application\UI\Multiplier]. Multiplier umožňuje pohodlně definovat továrničku pro více komponent. Funguje na principu vnořených komponent - každá komponenta dědící od [api:Nette\ComponentModel\Container] může obsahovat další komponenty. -Viz kapitola o [komponentovém modelu|components#komponenty-do-hloubky] v dokumentaci či [přednáška od Honzy Tvrdíka|https://www.youtube.com/watch?v=8y3LLexWu-I]. .[tip] +.[tip] +Viz kapitola o [komponentovém modelu |components#Komponenty do hloubky] v dokumentaci či [přednáška od Honzy Tvrdíka|https://www.youtube.com/watch?v=8y3LLexWu-I]. Podstatou Multiplieru je, že vystupuje v pozici rodiče, který si své potomky dokáže vytvářet dynamicky pomocí callbacku předaného v konstruktoru. Viz příklad: diff --git a/application/cs/presenters.texy b/application/cs/presenters.texy index be5356e816..04cd942e32 100644 --- a/application/cs/presenters.texy +++ b/application/cs/presenters.texy @@ -11,7 +11,7 @@ Seznámíme se s tím, jak se v Nette píší presentery a šablony. Po přečte
    -[Už víme |how-it-works#nette-application], že presenter je třída, která představuje nějakou konkrétní stránku webové aplikace, např. homepage; produkt v e-shopu; přihlašovací formulář; sitemap feed atd. Aplikace může mít od jednoho po tisíce presenterů. V jiných frameworcích se jim také říká controllery. +[Už víme |how-it-works#Nette Application], že presenter je třída, která představuje nějakou konkrétní stránku webové aplikace, např. homepage; produkt v e-shopu; přihlašovací formulář; sitemap feed atd. Aplikace může mít od jednoho po tisíce presenterů. V jiných frameworcích se jim také říká controllery. Obvykle se pod pojmem presenter myslí potomek třídy [api:Nette\Application\UI\Presenter], který je vhodný pro generování webových rozhraní a kterému se budeme věnovat ve zbytku této kapitoly. V obecném smyslu je presenter jakýkoliv objekt implementující rozhraní [api:Nette\Application\IPresenter]. @@ -56,11 +56,11 @@ Ihned po obdržení požadavku se zavolá metoda `startup()`. Můžete ji využ `action(args...)` .{toc: action()} -------------------------------------------------- -Obdoba metody `render()`. Zatímco `render()` je určená k tomu, aby připravila data pro konkrétní šablonu, která se následně vykreslí, tak v `action()` se zpracovává požadavek bez návaznosti na vykreslování šablony. Například se zpracují data, přihlásí či odhlásí uživatel, a tak podobně, a poté [přesměruje jinam|#Přesměrování]. +Obdoba metody `render()`. Zatímco `render()` je určená k tomu, aby připravila data pro konkrétní šablonu, která se následně vykreslí, tak v `action()` se zpracovává požadavek bez návaznosti na vykreslování šablony. Například se zpracují data, přihlásí či odhlásí uživatel, a tak podobně, a poté [přesměruje jinam |#Přesměrování]. Důležité je, že `action()` se volá dříve než `render()`, takže v ní můžeme případně změnit další běh dějin, tj. změnit šablonu, která se bude kreslit, a také metodu `render()`, která se bude volat. A to pomocí `setView('jineView')`. -Metodě se předávají parametry z požadavku. Je možné a doporučené uvést parametrům typy, např. `actionShow(int $id, string $slug = null)` - pokud bude parametr `id` chybět nebo pokud nebude integer, presenter vrátí [chybu 404|#Chyba 404 a spol.] a ukončí činnost. +Metodě se předávají parametry z požadavku. Je možné a doporučené uvést parametrům typy, např. `actionShow(int $id, ?string $slug = null)` - pokud bude parametr `id` chybět nebo pokud nebude integer, presenter vrátí [chybu 404 |#Chyba 404 a spol] a ukončí činnost. `handle(args...)` .{toc: handle()} @@ -115,13 +115,15 @@ Odpovědí presenteru je zpravidla [vykreslení šablony s HTML stránkou|templa Kdykoliv během životního cyklu můžeme některou z následujících metod odeslat odpověď a zároveň tak ukončit presenter: -- `redirect()`, `redirectPermanent()`, `redirectUrl()` a `forward()` [přesměruje|#přesměrování] -- `error()` ukončí presenter [kvůli chybě|#Chyba 404 a spol.] +- `redirect()`, `redirectPermanent()`, `redirectUrl()` a `forward()` [přesměruje |#Přesměrování] +- `error()` ukončí presenter [kvůli chybě |#Chyba 404 a spol] - `sendJson($data)` presenter ukončí a [odešle data |#Odeslání JSON] ve formátu JSON - `sendTemplate()` presenter ukončí a ihned [vykreslí šablonu |templates] -- `sendResponse($response)` presenter ukončí a odešle [vlastní odpověď|#Odpovědi] +- `sendResponse($response)` presenter ukončí a odešle [vlastní odpověď |#Odpovědi] - `terminate()` presenter ukončí bez odpovědi +Každá z těchto metod okamžitě ukončí činnost presenteru vyhozením tzv. tiché ukončovací výjimky `Nette\Application\AbortException`. + Pokud žádnou z těchto metod nezavoláte, presenter automaticky přistoupí k vykreslí šablony. Proč? Protože v 99 % případů chceme vykreslit šablonu, tudíž presenter tohle chování bere jako výchozí a chce nám ulehčit práci. @@ -158,7 +160,7 @@ Metoda `forward()` přejde na nový presenter okamžitě bez HTTP přesměrován $this->forward('Product:show'); ``` -Příklad tzv. dočasného přesměrování s HTTP kódem 302 nebo 303: +Příklad tzv. dočasného přesměrování s HTTP kódem 302 (nebo 303, je-li metoda aktuálního požadavku POST): ```php $this->redirect('Product:show', $id); @@ -170,7 +172,7 @@ Permanentní přesměrování s HTTP kódem 301 docílíte takto: $this->redirectPermanent('Product:show', $id); ``` -Na jinou URL mimo aplikaci lze přesměrovat metodou `redirectUrl()`: +Na jinou URL mimo aplikaci lze přesměrovat metodou `redirectUrl()`. Jako druhý parametr lze uvést HTTP kód, výchozí je 302 (nebo 303, je-li metoda aktuálního požadavku POST): ```php $this->redirectUrl('https://nette.org'); @@ -178,7 +180,7 @@ $this->redirectUrl('https://nette.org'); Přesměrování okamžitě ukončí činnost presenteru vyhozením tzv. tiché ukončovací výjimky `Nette\Application\AbortException`. -Před přesměrováním lze odeslat [flash message |#flash zprávy], tedy zprávy, které budou po přesměrování zobrazeny v šabloně. +Před přesměrováním lze odeslat [flash message |#Flash zprávy], tedy zprávy, které budou po přesměrování zobrazeny v šabloně. Flash zprávy @@ -205,7 +207,7 @@ $this->redirect(/* ... */); // a přesměrujeme Chyba 404 a spol. ================= -Pokud nelze splnit požadavek, třeba z důvodu, že článek který chceme zobrazit neexistuje v databázi, vyhodíme chybu 404 metodou `error(string $message = null, int $httpCode = 404)`. +Pokud nelze splnit požadavek, třeba z důvodu, že článek který chceme zobrazit neexistuje v databázi, vyhodíme chybu 404 metodou `error(?string $message = null, int $httpCode = 404)`. ```php public function renderShow(int $id): void @@ -218,8 +220,7 @@ public function renderShow(int $id): void } ``` -HTTP kód chyby lze předat jako druhý parametr, výchozí je 404. Metoda funguje tak, že vyhodí výjimku `Nette\Application\BadRequestException`, načež `Application` předá řízení error-presenteru. Což je presenter, jehož úkolem je zobrazit stránku informující o nastalé chybě. -Nastavení error-preseteru se provádí v [konfiguraci application|configuration]. +HTTP kód chyby lze předat jako druhý parametr, výchozí je 404. Metoda funguje tak, že vyhodí výjimku `Nette\Application\BadRequestException`, načež `Application` předá řízení error-presenteru. Což je presenter, jehož úkolem je zobrazit stránku informující o nastalé chybě. Nastavení error-preseteru se provádí v [konfiguraci application|configuration]. Odeslání JSON @@ -236,6 +237,32 @@ public function actionData(): void ``` +Parametry požadavku .{data-version:3.1.14} +========================================== + +Presenter a také každá komponenta získává z HTTP požadavku své parametry. Jejich hodnotu zjistíte metodou `getParameter($name)` nebo `getParameters()`. Hodnoty jsou řetězce či pole řetězců, jde v podstatě o surové data získané přímo z URL. + +Pro větší pohodlí doporučujeme parametry zpřístupnit přes property. Stačí je označit atributem `#[Parameter]`: + +```php +use Nette\Application\Attributes\Parameter; // tento řádek je důležitý + +class HomePresenter extends Nette\Application\UI\Presenter +{ + #[Parameter] + public string $theme; // musí být public +} +``` + +U property doporučujeme uvádět i datový typ (např. `string`) a Nette podle něj hodnotu automaticky přetypuje. Hodnoty parametrů lze také [validovat |#Validace parametrů]. + +Při vytváření odkazu lze parametrům hodnotu přímo nastavit: + +```latte +click +``` + + Persistentní parametry ====================== @@ -257,7 +284,7 @@ class ProductPresenter extends Nette\Application\UI\Presenter Pokud bude `$this->lang` mít hodnotu například `'en'`, tak i odkazy vytvořené pomocí `link()` nebo `n:href` budou obsahovat parameter `lang=en`. A po kliknutí na odkaz bude opět `$this->lang = 'en'`. -U property doporučujeme uvádět i datový typ (např. `string`) a můžete uvést i výchozí hodnotu. Hodnoty parametrů lze [validovat |#Validace persistentních parametrů]. +U property doporučujeme uvádět i datový typ (např. `string`) a můžete uvést i výchozí hodnotu. Hodnoty parametrů lze [validovat |#Validace parametrů]. Persistentní parametry se standardně přenášejí mezi všemi akcemi daného presenteru. Aby se přenášely i mezi více presentery, je potřeba je definovat buď: @@ -307,18 +334,12 @@ Jdeme do hloubky S tím, co jsme si dosud v této kapitole ukázali, si nejspíš úplně vystačíte. Následující řádky jsou určeny těm, kdo se zajímají o presentery do hloubky a chtějí vědět úplně všechno. -Požadavek a parametry ---------------------- - -Požadavek, který vyřizuje presenter, je objekt [api:Nette\Application\Request] a vrací ho metoda presenteru `getRequest()`. Jeho součástí je pole parametrů a každý z nich patří buď některé z komponent, nebo přímo presenteru (což je vlastně také komponenta, byť speciální). Nette tedy parametry přerozdělí a předá mezi jednotlivé komponenty (a presenter) zavoláním metody `loadState(array $params)`. Získat parametry lze metodu `getParameters(): array`, jednotlivě pomocí `getParameter($name)`. Hodnoty parametrů jsou řetězce či pole řetězců, jde v podstatě o surové data získané přímo z URL. - +Validace parametrů +------------------ -Validace persistentních parametrů ---------------------------------- +Hodnoty [parametrů požadavku |#Parametry požadavku] a [persistentních parametrů |#Persistentní parametry] přijatých z URL zapisuje do properties metoda `loadState()`. Ta také kontroluje, zda odpovídá datový typ uvedený u property, jinak odpoví chybou 404 a stránka se nezobrazí. -Hodnoty [persistentních parametrů |#Persistentní parametry] přijatých z URL zapisuje do properties metoda `loadState()`. Ta také kontroluje, zda odpovídá datový typ uvedený u property, jinak odpoví chybou 404 a stránka se nezobrazí. - -Nikdy slepě nevěřte persistentním parametrům, protože mohou být snadno uživatelem přepsány v URL. Takto například ověříme, zda je jazyk `$this->lang` mezi podporovanými. Vhodnou cestou je přepsat zmíněnou metodu `loadState()`: +Nikdy slepě nevěřte parametrům, protože mohou být snadno uživatelem přepsány v URL. Takto například ověříme, zda je jazyk `$this->lang` mezi podporovanými. Vhodnou cestou je přepsat zmíněnou metodu `loadState()`: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -341,6 +362,8 @@ class ProductPresenter extends Nette\Application\UI\Presenter Uložení a obnovení požadavku ---------------------------- +Požadavek, který vyřizuje presenter, je objekt [api:Nette\Application\Request] a vrací ho metoda presenteru `getRequest()`. + Aktuální požadavek lze uložit do session nebo naopak z ní obnovit a nechat jej presenter znovu vykonat. To se hodí například v situaci, když uživatel vyplňuje formulář a vyprší mu přihlášení. Aby o data nepřišel, před přesměrováním na přihlašovací stránku aktuální požadavek uložíme do session pomocí `$reqId = $this->storeRequest()`, které vrátí jeho identifikátor v podobě krátkého řetězce a ten předáme jako parameter přihlašovacímu presenteru. Po přihlášení zavoláme metodu `$this->restoreRequest($reqId)`, která požadavek vyzvedne ze session a forwarduje na něj. Metoda přitom ověří, že požadavek vytvořil stejný uživatel, jako se nyní přihlásil. Pokud by se přihlásil jiný uživatel nebo klíč byl neplatný, neudělá nic a program pokračuje dál. @@ -362,7 +385,7 @@ K přesměrování nedojde při AJAXovém nebo POST požadavku, protože by doš Kanonizaci můžete vyvolat i manuálně pomocí metody `canonicalize()`, které se podobně jako metodě `link()` předá presenter, akce a parametry. Vyrobí odkaz a porovná ho s aktuální URL adresou. Pokud se liší, tak přesměruje na vygenerovaný odkaz. ```php -public function actionShow(int $id, string $slug = null): void +public function actionShow(int $id, ?string $slug = null): void { $realSlug = $this->facade->getSlugForId($id); // přesměruje, pokud $slug se liší od $realSlug @@ -374,7 +397,7 @@ public function actionShow(int $id, string $slug = null): void Události -------- -Kromě metod `startup()`, `beforeRender()` a `shutdown()`, které se volají jako součást životního cyklu presenteru, lze definovat ještě další funkce, které se mají automaticky zavolat. Presenter definuje tzv. [událost|nette:glossary#Události], jejichž handlery přidáte do polí `$onStartup`, `$onRender` a `$onShutdown`. +Kromě metod `startup()`, `beforeRender()` a `shutdown()`, které se volají jako součást životního cyklu presenteru, lze definovat ještě další funkce, které se mají automaticky zavolat. Presenter definuje tzv. [událost |nette:glossary#události], jejichž handlery přidáte do polí `$onStartup`, `$onRender` a `$onShutdown`. ```php class ArticlePresenter extends Nette\Application\UI\Presenter @@ -425,6 +448,59 @@ $this->sendResponse(new Responses\CallbackResponse($callback)); ``` +Omezení přístupu pomocí `#[Requires]` .{data-version:3.2.2} +----------------------------------------------------------- + +Atribut `#[Requires]` poskytuje pokročilé možnosti pro omezení přístupu k presenterům a jejich metodám. Lze jej použít pro specifikaci HTTP metod, vyžadování AJAXového požadavku, omezení na stejný původ (same origin), a přístup pouze přes forwardování. Atribut lze aplikovat jak na třídy presenterů, tak na jednotlivé metody `action()`, `render()`, `handle()` a `createComponent()`. + +Můžete určit tyto omezení: +- na HTTP metody: `#[Requires(methods: ['GET', 'POST'])]` +- vyžadování AJAXového požadavku: `#[Requires(ajax: true)]` +- přístup pouze ze stejného původu: `#[Requires(sameOrigin: true)]` +- přístup pouze přes forward: `#[Requires(forward: true)]` +- omezení na konkrétní akce: `#[Requires(actions: 'default')]` + +Podrobnosti najdete v návodu [Jak používat atribut Requires |best-practices:attribute-requires]. + + +Kontrola HTTP metody +-------------------- + +Presentery v Nette automaticky ověřují HTTP metodu každého příchozího požadavku. Důvodem pro tuto kontrolu je především bezpečnost. Standardně jsou povoleny metody `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH`. + +Chcete-li povolit navíc například metodu `OPTIONS`, použijte k tomu atribut `#[Requires]` (od Nette Application v3.2): + +```php +#[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])] +class MyPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Ve verzi 3.1 se ověření provádí v `checkHttpMethod()`, která zjišťuje, zda je metoda specifikovaná v požadavku obsažena v poli `$presenter->allowedMethods`. Přidání metody udělejte takto: + +```php +class MyPresenter extends Nette\Application\UI\Presenter +{ + protected function checkHttpMethod(): void + { + $this->allowedMethods[] = 'OPTIONS'; + parent::checkHttpMethod(); + } +} +``` + +Je důležité zdůraznit, že pokud povolíte metodu `OPTIONS`, musíte ji následně také patřičně obsloužit v rámci svého presenteru. Metoda je často používána jako tzv. preflight request, který prohlížeč automaticky odesílá před skutečným požadavkem, když je potřeba zjistit, zda je požadavek povolený z hlediska CORS (Cross-Origin Resource Sharing) politiky. Pokud metodu povolíte, ale neimplementujete správnou odpověď, může to vést k nekonzistencím a potenciálním bezpečnostním problémům. + + +Označení zastaralých akcí .{data-version:3.2.3} +----------------------------------------------- + +Atribut `#[Deprecated]` slouží k označení akcí, signálů nebo celých presenterů, které jsou zastaralé a měly by být v budoucnu odstraněny. Při generování odkazů na takto označené části aplikace Nette vyhodí varování, které vývojáře upozorní. + +Atribut lze aplikovat jak na celou třídu presenteru, tak na jednotlivé metody `action()`, `render()` a `handle()`. + + Další četba =========== diff --git a/application/cs/routing.texy b/application/cs/routing.texy index 816436fac4..3a2e753731 100644 --- a/application/cs/routing.texy +++ b/application/cs/routing.texy @@ -12,10 +12,9 @@ Router má na starosti vše okolo URL adres, aby vy už jste nad nimi nemuseli p
    -Lidštější URL (nebo taky cool či pretty URL) jsou použitelnější, zapamatovatelnější a pozitivně přispívají k SEO. Nette na to myslí a vychází vývojářům plně vstříc. Můžete si pro svou aplikaci navrhnout přesně takovou strukturu URL adres, jakou budete chtít. -Můžete ji navrhnout dokonce až ve chvíli, když už je aplikace hotová, protože se to obejde bez zásahů do kódu či šablon. Definuje se totiž elegantním způsobem na jednom [jediném místě |#Začlenění do aplikace], v routeru, a není tak roztroušen ve formě anotací ve všech presenterech. +Lidštější URL (nebo taky cool či pretty URL) jsou použitelnější, zapamatovatelnější a pozitivně přispívají k SEO. Nette na to myslí a vychází vývojářům plně vstříc. Můžete si pro svou aplikaci navrhnout přesně takovou strukturu URL adres, jakou budete chtít. Můžete ji navrhnout dokonce až ve chvíli, když už je aplikace hotová, protože se to obejde bez zásahů do kódu či šablon. Definuje se totiž elegantním způsobem na jednom [jediném místě |#Začlenění do aplikace], v routeru, a není tak roztroušen ve formě anotací ve všech presenterech. -Router v Nette je mimořádný tím, že je **obousměrný.** Umí jak dekódovat URL v HTTP požadavku, tak i odkazy vytvářet. Hraje tedy zásadní roli v [Nette Application|how-it-works#Nette Application], protože jednak rozhoduje o tom, který presenter a action bude vykonávat aktuální požadavek, ale také se využívá pro [generování URL |creating-links] v šabloně atd. +Router v Nette je mimořádný tím, že je **obousměrný.** Umí jak dekódovat URL v HTTP požadavku, tak i odkazy vytvářet. Hraje tedy zásadní roli v [Nette Application |how-it-works#Nette Application], protože jednak rozhoduje o tom, který presenter a action bude vykonávat aktuální požadavek, ale také se využívá pro [generování URL |creating-links] v šabloně atd. Ovšem router není limitován jen pro tohle využití, můžete jej použít v aplikacích, kde se vůbec presentery nepoužívají, pro REST API, atd. Více v části [#samostatné použití]. @@ -216,7 +215,7 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ @@ -225,7 +224,7 @@ $router->addRoute('/[/]', [ ]); ``` -Nebo můžeme použít tuto formu, všimněte si přepisu validačního regulárního výrazu: +Pro detailnější specifikaci lze použít ještě rozšířenější formu, kde kromě výchozích hodnot můžeme nastavit i další vlastnosti parametrů, jako třeba validační regulární výraz (viz parametr `id`): ```php use Nette\Routing\Route; @@ -243,7 +242,7 @@ $router->addRoute('/[/]', [ ]); ``` -Tyto upovídanější formáty se hodí pro doplnění dalších metadat. +Je důležité poznamenat, že pokud parametry definované v poli nejsou uvedeny v masce cesty, jejich hodnoty nelze změnit, ani pomocí query parametrů uvedených za otazníkem v URL. Filtry a překlady @@ -307,7 +306,7 @@ Parametry `presenter`, `action` a `module` už mají předdefinované filtry, kt Obecné filtry ------------- -Vedle filtrů určených pro konkrétní parametry můžeme definovat též obecné filtry, které obdrží asociativní pole všech parametrů, které mohou jakkoliv modifikovat a poté je vrátí. Obecné filtry definujeme pod klíčem `null`. +Vedle filtrů určených pro konkrétní parametry můžeme definovat též obecné filtry, které obdrží asociativní pole všech parametrů, které mohou jakkoliv modifikovat a poté je vrátí. Obecné filtry definujeme pod prázdným klíčem. ```php use Nette\Routing\Route; @@ -315,7 +314,7 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => 'Home', 'action' => 'default', - null => [ + '' => [ Route::FilterIn => function (array $params): array { /* ... */ }, Route::FilterOut => function (array $params): array { /* ... */ }, ], @@ -342,10 +341,33 @@ $router->addRoute('product/', 'Product:detail'); Při přístupu na starou URL presenter automaticky přesměruje na nové URL, takže vám tyto stránky vyhledávače nezaindexují dvakrát (viz [#SEO a kanonizace]). +Dynamické routování s callbacky +------------------------------- + +Dynamické routování s callbacky vám umožňuje přiřadit routám přímo funkce (callbacky), které se vykonají, když je daná cesta navštívena. Tato flexibilní funkčnost vám umožní rychle a efektivně vytvářet různé koncové body (endpoints) pro vaši aplikaci: + +```php +$router->addRoute('test', function () { + echo 'jste na adrese /test'; +}); +``` + +Můžete také definovat v masce parametry, které se automaticky předají do vašeho callbacku: + +```php +$router->addRoute('', function (string $lang) { + echo match ($lang) { + 'cs' => 'Vítejte na české verzi našeho webu!', + 'en' => 'Welcome to the English version of our website!', + }; +}); +``` + + Moduly ------ -Pokud máme více rout, které spadají do společného [modulu |modules], využijeme `withModule()`: +Pokud máme více rout, které spadají do společného [modulu |directory-structure#Presentery a šablony], využijeme `withModule()`: ```php $router = new RouteList; @@ -454,10 +476,10 @@ $router->addRoute('index', /* ... */); Začlenění do aplikace ===================== -Abychom vytvořený router zapojili do aplikace, musíme o něm říci DI kontejneru. Nejsnazší cesta je připravit továrnu, která objekt routeru vyrobí, a sdělit v konfiguraci kontejneru, že ji má použít. Dejme tomu, že k tomu účelu napíšeme metodu `App\Router\RouterFactory::createRouter()`: +Abychom vytvořený router zapojili do aplikace, musíme o něm říci DI kontejneru. Nejsnazší cesta je připravit továrnu, která objekt routeru vyrobí, a sdělit v konfiguraci kontejneru, že ji má použít. Dejme tomu, že k tomu účelu napíšeme metodu `App\Core\RouterFactory::createRouter()`: ```php -namespace App\Router; +namespace App\Core; use Nette\Application\Routers\RouteList; @@ -476,7 +498,7 @@ Do [konfigurace |dependency-injection:services] pak zapíšeme: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter ``` Jakékoliv závislosti, třeba na databázi atd, se předají tovární metodě jako její parametry pomocí [autowiringu|dependency-injection:autowiring]: @@ -522,7 +544,7 @@ Framework přispívá k SEO (optimalizaci nalezitelnosti na internetu) tím, že Tomuto procesu se říká kanonizace. Kanonickou URL je ta, kterou vygeneruje router, tj. první vyhovující routa v kolekci bez příznaku OneWay. Proto v kolekci uvádíme **primární routy jako první**. -Kanonizaci provádí presenter, více v kapitole [kanonizace|presenters#kanonizace]. +Kanonizaci provádí presenter, více v kapitole [kanonizace |presenters#Kanonizace]. HTTPS @@ -606,8 +628,7 @@ class MyRouter implements Nette\Routing\Router } ``` -Metoda `match` zpracuje aktuální požadavek [$httpRequest |http:request], ze kterého lze získat nejen URL, ale i hlavičky atd., do pole obsahující název presenteru a jeho parametry. Pokud požadavek zpracovat neumí, vrátí null. -Při zpracování požadavku musíme vrátit minimálně presenter a akci. Název presenteru je úplný a obsahuje i případné moduly: +Metoda `match` zpracuje aktuální požadavek [$httpRequest |http:request], ze kterého lze získat nejen URL, ale i hlavičky atd., do pole obsahující název presenteru a jeho parametry. Pokud požadavek zpracovat neumí, vrátí null. Při zpracování požadavku musíme vrátit minimálně presenter a akci. Název presenteru je úplný a obsahuje i případné moduly: ```php [ @@ -640,7 +661,7 @@ Samostatným použitím myslíme využití schopností routeru v aplikaci, kter Takže opět si vytvoříme metodu, která nám sestaví router, např.: ```php -namespace App\Router; +namespace App\Core; use Nette\Routing\RouteList; @@ -671,7 +692,7 @@ $httpRequest = $container->getByType(Nette\Http\IRequest::class); Anebo objekty přímo vyrobíme: ```php -$router = App\Router\RouterFactory::createRouter(); +$router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` diff --git a/application/cs/templates.texy b/application/cs/templates.texy index 3630e00ee4..568b40837e 100644 --- a/application/cs/templates.texy +++ b/application/cs/templates.texy @@ -37,27 +37,75 @@ Ta definuje blok `content`, který se vloží na místo `{include content}` v la Hledání šablon -------------- -Cestu k šablonám odvodí presenter podle jednoduché logiky. Zkusí, zda existuje jeden z těchto souborů umístěných relativně od adresáře s třídou presenteru, kde `` je název aktuálního presenteru a `` je název aktuální akce: +Nemusíte v presenterech uvádět, jaká šablona se má vykreslit, framework cestu odvodí sám a ušetří vám psaní. -- `templates//.latte` -- `templates/..latte` +Pokud používáte adresářovou strukturu, kde každý presenter má vlastní adresář, jednodušše umístěte šablonu do tohoto adresáře pod jménem akce (resp. view), tj. pro akci `default` použijte šablonu `default.latte`: -Pokud šablonu nenajde, je odpovědí [chyba 404|presenters#Chyba 404 a spol.]. +/--pre +app/ +└── Presentation/ + └── Home/ + ├── HomePresenter.php + └── default.latte +\-- -Můžete také změnit view pomocí `$this->setView('jineView')`. Nebo místo dohledávání přímo určit jméno souboru se šablonou pomocí `$this->template->setFile('/path/to/template.latte')`. +Pokud používáte strukturu, kde jsou společně presentery v jednom adresáři a šablony ve složce `templates`, uložte ji buď do souboru `..latte` nebo `/.latte`: + +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── Home.default.latte ← 1. varianta + └── Home/ + └── default.latte ← 2. varianta +\-- + +Adresář `templates` může být umístěn také o úroveň výš, tj. na stejné úrovni, jako je adresář s třídami presenterů. + +Pokud se šablona nenajde, presenter odpoví [chybou 404 - page not found |presenters#Chyba 404 a spol]. + +View změníte pomocí `$this->setView('jineView')`. Také lze přímo určit soubor se šablonou pomocí `$this->template->setFile('/path/to/template.latte')`. .[note] Soubory, kde se dohledávají šablony, lze změnit překrytím metody [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], která vrací pole možných názvů souborů. -Layout se očekává v těchto souborech: -- `templates//@.latte` -- `templates/.@.latte` -- `templates/@.latte` layout společný pro více presenterů +Hledání šablony layoutu +----------------------- + +Nette také automaticky dohledává soubor s layoutem. + +Pokud používáte adresářovou strukturu, kde každý presenter má vlastní adresář, umístěte layout buď do složky s presenterem, pokud je specifický jen pro něj, nebo o úroveň výš, pokud je společný pro více presenterů: -Kde `` je název aktuálního presenteru a `` je název layoutu, což je standardně `'layout'`. Název lze změnit pomocí `$this->setLayout('jinyLayout')`, takže se budou zkoušet soubory `@jinyLayout.latte`. +/--pre +app/ +└── Presentation/ + ├── @layout.latte ← společný layout + └── Home/ + ├── @layout.latte ← jen pro presenter Home + ├── HomePresenter.php + └── default.latte +\-- -Můžete také přímo určit jméno souboru se šablonou layoutu pomocí `$this->setLayout('/path/to/template.latte')`. Pomocí `$this->setLayout(false)` se dohledávání layoutu vypne. +Pokud používáte strukturu, kde jsou společně presentery v jednom adresáři a šablony ve složce `templates`, bude se layout očekávat na těchto místech: + +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── @layout.latte ← společný layout + ├── Home.@layout.latte ← jen pro Home, 1. varianta + └── Home/ + └── @layout.latte ← jen pro Home, 2. varianta +\-- + +Pokud se presenter nachází v modulu, bude se dohledávat i o další adresářové úrovně výš, podle zanoření modulu. + +Název layoutu lze změnit pomocí `$this->setLayout('layoutAdmin')` a pak se bude očekávat v souboru `@layoutAdmin.latte`. Také lze přímo určit soubor se šablonou layoutu pomocí `$this->setLayout('/path/to/template.latte')`. + +Pomocí `$this->setLayout(false)` nebo značky `{layout none}` uvnitř šablony se dohledávání layoutu vypne. .[note] Soubory, kde se dohledávají šablony layoutu, lze změnit překrytím metody [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], která vrací pole možných názvů souborů. @@ -66,15 +114,48 @@ Soubory, kde se dohledávají šablony layoutu, lze změnit překrytím metody [ Proměnné v šabloně ------------------ -Proměnné do šablony předáváme tak, že je zapíšeme do `$this->template` a potom je máme k dispozici v šabloně jako lokální proměnné: +Proměnné do šablony předáváme zápisem do `$this->template`. V šabloně jsou pak dostupné jako lokální proměnné: ```php $this->template->article = $this->articles->getById($id); ``` -Takto jednoduše můžeme do šablon předat jakékoliv proměnné. Při vývoji robustních aplikací ale bývá užitečnější se omezit. Například tak, že explicitně nadefinujeme výčet proměnných, které šablona očekává, a jejich typů. Díky tomu nám bude moci PHP kontrolovat typy, IDE správně našeptávat a statická analýza odhalovat chyby. +Pokud chcete, aby se hodnota určité property automaticky předala do šablony jako proměnná, označte ji atributem `#[TemplateVariable]` a viditelností public nebo protected: .{data-version:3.2.9} + +```php +use Nette\Application\Attributes\TemplateVariable; + +class ArticlePresenter extends Nette\Application\UI\Presenter +{ + #[TemplateVariable] + public string $siteName = 'Můj blog'; +} +``` + +Pokud do šablony vložíte proměnnou se stejným názvem, `#[TemplateVariable]` ji nepřepíše. + + +Výchozí proměnné +---------------- + +Presentery a komponenty předávají do šablon několik užitečných proměnných automaticky: + +- `$basePath` je absolutní URL cesta ke kořenovému adresáři (např. `/eshop`) +- `$baseUrl` je absolutní URL ke kořenovému adresáři (např. `http://localhost/eshop`) +- `$user` je objekt [reprezentující uživatele |security:authentication] +- `$presenter` je aktuální presenter +- `$control` je aktuální komponenta nebo presenter +- `$flashes` pole [zpráv |presenters#Flash zprávy] zaslaných funkcí `flashMessage()` + +Pokud používáte vlastní třídu šablony, tyto proměnné se předají, pokud pro ně vytvoříte property. -A jak takový výčet nadefinujeme? Jednoduše v podobě třídy a její properties. Pojmenujeme ji podobně jako presenter, jen s `Template` na konci: + +Typově bezpečné šablony +----------------------- + +Při vývoji robustních aplikací je užitečné explicitně nadefinovat, jaké proměnné šablona očekává a jakého jsou typu. Získáte tak typovou kontrolu v PHP, chytré našeptávání v IDE a schopnost statické analýzy odhalovat chyby. + +Jak takový výčet nadefinovat? Jednoduše v podobě třídy s properties reprezentujícími proměnné šablony. Pojmenujeme ji podobně jako presenter, jen s `Template` na konci: ```php /** @@ -93,22 +174,22 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template } ``` -Objekt `$this->template` v presenteru bude nyní instancí třídy `ArticleTemplate`. Takže PHP bude při zápisu kontrolovat deklarované typy. A počínaje verzí PHP 8.2 upozorní i na zápis do neexistující proměnné, v předchozích verzích lze téhož dosáhnout použitím traity [Nette\SmartObject |utils:smartobject]. +Objekt `$this->template` v presenteru bude nyní instancí třídy `ArticleTemplate`. PHP tak bude při zápisu kontrolovat deklarované typy. -Anotace `@property-read` je určená pro IDE a statickou analýzu, díky ní bude fungovat našeptávání, viz "PhpStorm and code completion for $this⁠-⁠>⁠template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. +Anotace `@property-read` slouží pro IDE a statickou analýzu, díky ní bude fungovat našeptávání, viz "PhpStorm and code completion for $this⁠-⁠>⁠template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. [* phpstorm-completion.webp *] -Luxusu našeptávání si můžete dopřát i v šablonách, stačí do PhpStorm nainstalovat plugin pro Latte a uvést na začátek šablony název třídy, více v článku "Latte: jak na typový systém":https://blog.nette.org/cs/latte-jak-na-typovy-system: +Našeptávání můžete využít i přímo v šablonách. Stačí do PhpStorm nainstalovat plugin pro Latte a uvést na začátek šablony název třídy parametrů šablony, více v článku "Latte: jak na typový systém":https://blog.nette.org/cs/latte-jak-na-typovy-system: ```latte -{templateType App\Presenters\ArticleTemplate} +{templateType App\Presentation\Article\ArticleTemplate} ... ``` -Takto fungují i šablony v komponentách, stačí jen dodržet jmennou konvenci a pro komponentu např. `FifteenControl` vytvořit třídu šablony `FifteenTemplate`. +Totéž platí i pro komponenty. Stačí dodržet jmennou konvenci a pro komponentu např. `FifteenControl` vytvořit třídu parametrů `FifteenTemplate`. -Pokud potřebujete vytvořit `$template` jako instanci jiné třídy, využijte metodu `createTemplate()`: +Pokud potřebujete použít jinou třídu parametrů, využijte metodu `createTemplate()`: ```php public function renderDefault(): void @@ -121,21 +202,6 @@ public function renderDefault(): void ``` -Výchozí proměnné ----------------- - -Presentery a komponenty předávají do šablon několik užitečných proměnných automaticky: - -- `$basePath` je absolutní URL cesta ke kořenovému adresáři (např. `/eshop`) -- `$baseUrl` je absolutní URL ke kořenovému adresáři (např. `http://localhost/eshop`) -- `$user` je objekt [reprezentující uživatele |security:authentication] -- `$presenter` je aktuální presenter -- `$control` je aktuální komponenta nebo presenter -- `$flashes` pole [zpráv |presenters#flash zprávy] zaslaných funkcí `flashMessage()` - -Pokud používáte vlastní třídu šablony, tyto proměnné se předají, pokud pro ně vytvoříte property. - - Vytváření odkazů ---------------- @@ -157,24 +223,70 @@ Více informací najdete v kapitole [Vytváření odkazů URL|creating-links]. Vlastní filtry, značky apod. ---------------------------- -Šablonovací systém Latte lze rozšířit o vlastní filtry, funkce, značky apod. Lze tak učinit přímo v metodě `render` nebo `beforeRender()`: +Šablonovací systém Latte lze rozšířit o vlastní filtry, funkce, značky a další prvky. K dispozici jsou tři způsoby, jak to udělat, od nejrychlejších ad-hoc řešení až po architektonický přístup pro celou aplikaci. + +**Ad-hoc v metodách presenteru** + +Nejrychlejší způsob je přidat filtr nebo funkci přímo v kódu presenteru či komponenty. V presenteru je k tomu vhodná metoda `beforeRender()` nebo `render()`: ```php -public function beforeRender(): void +protected function beforeRender(): void { // přidání filtru - $this->template->addFilter('foo', /* ... */); + $this->template->addFilter('money', fn($val) => round($val) . ' Kč'); - // nebo konfigurujeme přímo objekt Latte\Engine + // přidání funkce + $this->template->addFunction('isWeekend', fn($date) => $date->format('N') >= 6); +} +``` + +V šabloně pak: + +```latte +

    Cena: {$price|money}

    + +{if isWeekend($now)} ... {/if} +``` + +Pro složitější logiku můžete konfigurovat přímo objekt `Latte\Engine`: + +```php +protected function beforeRender(): void +{ $latte = $this->template->getLatte(); - $latte->addFilterLoader(/* ... */); + $latte->setFeature(Latte\Feature::MigrationWarnings); } ``` -Latte ve verzi 3 nabízí pokročilejší způsob a to vytvoření si [extension |latte:creating-extension] pro každý webový projekt. Kusý příklad takové třídy: +**Pomocí atributů** + +Elegantní způsob je definovat filtry a funkce jako metody přímo ve [třídě parametrů šablony|#Typově bezpečné šablony] presenteru nebo komponenty a označit je atributy: ```php -namespace App\Templating; +class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template +{ + #[Latte\Attributes\TemplateFilter] + public function money(float $val): string + { + return round($val) . ' Kč'; + } + + #[Latte\Attributes\TemplateFunction] + public function isWeekend(DateTimeInterface $date): bool + { + return $date->format('N') >= 6 + } +} +``` + +Latte automaticky rozpozná a zaregistruje metody označené těmito atributy. Název filtru nebo funkce v šabloně odpovídá názvu metody. Tyto metody nesmí být privátní. + +**Globálně pomocí Extension** + +Předchozí způsoby jsou vhodné pro filtry a funkce, které potřebujete jen v konkrétním presenteru nebo komponentě, nikoliv v celé aplikaci. Pro celou aplikaci je nejvhodnější vytvořit si [extension |latte:extending-latte#Latte Extension]. Jde o třídu, která centralizuje všechna rozšíření Latte pro celý projekt. Kusý příklad: + +```php +namespace App\Presentation\Accessory; final class LatteExtension extends Latte\Extension { @@ -203,24 +315,30 @@ final class LatteExtension extends Latte\Extension ]; } + private function filterTimeAgoInWords(DateTimeInterface $time): string + { + // ... + } + // ... } ``` -Zaregistrujeme ji pomocí [konfigurace |configuration#Šablony Latte]: +Extension zaregistrujeme pomocí [konfigurace |configuration#Šablony Latte]: ```neon latte: extensions: - - App\Templating\LatteExtension + - App\Presentation\Accessory\LatteExtension ``` +Výhodou extension je, že lze využít dependency injection, mít přístup k modelové vrstvě aplikace a všechna rozšíření mít přehledně na jednom místě. Extension umožnuje definovat i vlastní značky, providery, průchody pro Latte kompilátor a další. + Překládání ---------- -Pokud programujete vícejazyčnou aplikaci, budete nejspíš potřebovat některé texty v šabloně vypsat v různých jazycích. Nette Framework k tomuto účelu definuje rozhraní pro překlad [api:Nette\Localization\Translator], které má jedinou metodu `translate()`. Ta přijímá zprávu `$message`, což zpravidla bývá řetězec, a libovolné další parametry. Úkolem je vrátit přeložený řetězec. -V Nette není žádná výchozí implementace, můžete si vybrat podle svých potřeb z několika hotových řešeních, které najdete na [Componette |https://componette.org/search/localization]. V jejich dokumentaci se dozvíte, jak translator konfigurovat. +Pokud programujete vícejazyčnou aplikaci, budete nejspíš potřebovat některé texty v šabloně vypsat v různých jazycích. Nette Framework k tomuto účelu definuje rozhraní pro překlad [api:Nette\Localization\Translator], které má jedinou metodu `translate()`. Ta přijímá zprávu `$message`, což zpravidla bývá řetězec, a libovolné další parametry. Úkolem je vrátit přeložený řetězec. V Nette není žádná výchozí implementace, můžete si vybrat podle svých potřeb z několika hotových řešeních, které najdete na [Componette |https://componette.org/search/localization]. V jejich dokumentaci se dozvíte, jak translator konfigurovat. Šablonám lze nastavit překladač, který si [necháme předat |dependency-injection:passing-dependencies], metodou `setTranslator()`: @@ -237,7 +355,7 @@ Translator je alternativně možné nastavit pomocí [konfigurace |configuration ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` Poté lze překladač používat například jako filtr `|translate`, a to včetně doplňujících parametrů, které se předají metodě `translate()` (viz `foo, bar`): diff --git a/application/de/@home.texy b/application/de/@home.texy index 91906d2888..a151863801 100644 --- a/application/de/@home.texy +++ b/application/de/@home.texy @@ -2,35 +2,84 @@ Nette Application ***************** .[perex] -Das Paket `nette/application` ist die Grundlage für die Erstellung interaktiver Webanwendungen. +Nette Application ist der Kern des Nette Frameworks und bietet leistungsstarke Werkzeuge zur Erstellung moderner Webanwendungen. Es bietet eine Reihe außergewöhnlicher Eigenschaften, die die Entwicklung erheblich vereinfachen und die Sicherheit sowie die Wartbarkeit des Codes verbessern. -- [Wie funktionieren die Anwendungen? |how-it-works] -- [Bootstrap] -- [Präsentatoren |Presenters] -- [Schablonen |Templates] -- [Module |Modules] -- [Routing |Routing] -- [URL-Links erstellen |creating-links] -- [Interaktive Komponenten |components] -- [AJAX & Schnipsel |ajax] -- [Multiplikator |multiplier] -- [Konfiguration |Configuration] +Installation +------------ -Einrichtung ------------ - -Laden Sie das Paket herunter und installieren Sie es mit [Composer |best-practices:composer]: +Laden Sie die Bibliothek herunter und installieren Sie sie mit [Composer|best-practices:composer]: ```shell composer require nette/application ``` -| Version | kompatibel mit PHP -|-----------|------------------- -| Nette-Anwendung 4.0 | PHP 8.0 - 8.2 -| Nette Anwendung 3.1 | PHP 7.2 - 8.2 -| Nette Anwendung 3.0 | PHP 7.1 - 8.0 -| Nette-Anwendung 2.4 | PHP 5.6 - 8.0 -Gilt für die neuesten Patch-Versionen. +Warum Nette Application wählen? +------------------------------- + +Nette war schon immer ein Pionier im Bereich der Webtechnologien. + +**Bidirektionaler Router:** Nette verfügt über ein fortschrittliches Routing-System, das durch seine Bidirektionalität einzigartig ist – es übersetzt nicht nur URLs in Anwendungsaktionen, sondern kann auch rückwirkend URL-Adressen generieren. Das bedeutet: +- Sie können jederzeit die URL-Struktur der gesamten Anwendung ändern, ohne die Templates anpassen zu müssen +- URLs werden automatisch kanonisiert, was die SEO verbessert +- Das Routing wird an einer Stelle definiert, nicht verstreut in Annotationen + +**Komponenten und Signale:** Das integrierte Komponentensystem, inspiriert von Delphi und React.js, ist unter PHP-Frameworks völlig einzigartig: +- Ermöglicht die Erstellung wiederverwendbarer UI-Elemente +- Unterstützt die hierarchische Zusammensetzung von Komponenten +- Bietet eine elegante Verarbeitung von AJAX-Anfragen mittels Signalen +- Umfangreiche Bibliothek fertiger Komponenten auf [Componette](https://componette.org) + +**AJAX und Snippets:** Nette führte bereits 2009 eine revolutionäre Methode zur Arbeit mit AJAX ein, lange vor ähnlichen Lösungen wie Hotwire für Ruby on Rails oder Symfony UX Turbo: +- Snippets ermöglichen die Aktualisierung nur von Teilen der Seite, ohne JavaScript schreiben zu müssen +- Automatische Integration mit dem Komponentensystem +- Intelligente Invalidierung von Seitenteilen +- Minimale Menge an übertragenen Daten + +**Intuitive Templates [Latte|latte:]:** Das sicherste Template-System für PHP mit erweiterten Funktionen: +- Automatischer Schutz vor XSS durch kontextsensitives Escaping +- Erweiterbarkeit durch eigene Filter, Funktionen und Tags +- Template-Vererbung und Snippets für AJAX +- Hervorragende Unterstützung für PHP 8.x mit Typsystem + +**Dependency Injection:** Nette nutzt Dependency Injection vollständig aus: +- Automatische Übergabe von Abhängigkeiten (Autowiring) +- Konfiguration mittels übersichtlichem NEON-Format +- Unterstützung für Komponenten-Factories + + +Hauptvorteile +------------- + +- **Sicherheit**: Automatischer Schutz vor [Schwachstellen|nette:vulnerability-protection] wie XSS, CSRF, etc. +- **Produktivität**: Weniger schreiben, mehr Funktionen dank cleverem Design +- **Debugging**: [Tracy Debugger|tracy:] mit Routing-Panel +- **Leistung**: Intelligenter Cache, Lazy Loading von Komponenten +- **Flexibilität**: Einfache Anpassung von URLs auch nach Fertigstellung der Anwendung +- **Komponenten**: Einzigartiges System wiederverwendbarer UI-Elemente +- **Modern**: Volle Unterstützung für PHP 8.4+ und Typsystem + + +Erste Schritte +-------------- + +1. [Wie Anwendungen funktionieren |how-it-works] - Verständnis der grundlegenden Architektur +2. [Presenter |presenters] - Arbeit mit Presentern und Aktionen +3. [Templates |templates] - Erstellung von Templates in Latte +4. [Routing |routing] - Konfiguration von URL-Adressen +5. [Interaktive Komponenten |components] - Nutzung des Komponentensystems + + +PHP-Kompatibilität +------------------ + +| Version | kompatibel mit PHP +|-------------------|------------------- +| Nette Application 4.0 | PHP 8.1 – 8.4 +| Nette Application 3.2 | PHP 8.1 – 8.4 +| Nette Application 3.1 | PHP 7.2 – 8.3 +| Nette Application 3.0 | PHP 7.1 – 8.0 +| Nette Application 2.4 | PHP 5.6 – 8.0 + +Gilt für die letzte Patch-Version. diff --git a/application/de/@left-menu.texy b/application/de/@left-menu.texy index 8fac50f03b..2a16b701bb 100644 --- a/application/de/@left-menu.texy +++ b/application/de/@left-menu.texy @@ -1,19 +1,22 @@ -Nette Bewerbung -*************** -- [Wie funktionieren die Anwendungen? |how-it-works] -- [Bootstrap] -- [Präsentatoren |Presenters] -- [Schablonen |Templates] -- [Module |Modules] -- [Routing |Routing] -- [URL-Links erstellen |creating-links] +Nette Application +***************** +- [Wie Anwendungen funktionieren |how-it-works] +- [Bootstrapping] +- [Presenter |presenters] +- [Templates |templates] +- [Verzeichnisstruktur |directory-structure] +- [Routing |routing] +- [Erstellen von URL-Links |creating-links] - [Interaktive Komponenten |components] -- [AJAX & Schnipsel |ajax] -- [Multiplikator |multiplier] -- [Konfiguration |Configuration] +- [AJAX & Snippets |ajax] +- [Multiplier |Multiplier] +- [Konfiguration |configuration] Weitere Lektüre *************** -- [Bewährte Praktiken |best-practices:] -- [Fehlersuche |nette:troubleshooting] +- [Warum Nette verwenden? |www:10-reasons-why-nette] +- [Installation |nette:installation] +- [Schreiben wir die erste Anwendung! |quickstart:] +- [Anleitungen und Verfahren |best-practices:] +- [Fehlerbehebung |nette:troubleshooting] diff --git a/application/de/@meta.texy b/application/de/@meta.texy new file mode 100644 index 0000000000..b3b806b2ca --- /dev/null +++ b/application/de/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokumentation}} diff --git a/application/de/ajax.texy b/application/de/ajax.texy index bcdbd4ac93..3ef05ebfe4 100644 --- a/application/de/ajax.texy +++ b/application/de/ajax.texy @@ -1,38 +1,45 @@ -AJAX & Schnipsel -**************** +AJAX & Snippets +***************
    -Moderne Webanwendungen laufen heute zur Hälfte auf einem Server und zur Hälfte in einem Browser. AJAX ist ein wichtiger verbindender Faktor. Welche Unterstützung bietet das Nette Framework? -- Senden von Template-Fragmenten (sogenannte *snippets*) +In der Ära moderner Webanwendungen, in der die Funktionalität oft zwischen Server und Browser aufgeteilt ist, ist AJAX ein unverzichtbares Bindeglied. Welche Möglichkeiten bietet uns das Nette Framework in diesem Bereich? +- Senden von Teilen des Templates, sogenannten Snippets - Übergabe von Variablen zwischen PHP und JavaScript -- Debugging von AJAX-Anwendungen +- Tools zum Debuggen von AJAX-Anfragen
    -Eine AJAX-Anfrage kann mit einer Methode eines Dienstes, [der eine HTTP-Anfrage kapselt |http:request], erkannt werden `$httpRequest->isAjax()` (erkennt anhand des `X-Requested-With` HTTP-Headers). Es gibt auch eine Kurzform der Methode in Presenter: `$this->isAjax()`. -Eine AJAX-Anfrage unterscheidet sich nicht von einer normalen Anfrage - ein Presenter wird mit einer bestimmten Ansicht und Parametern aufgerufen. Auch hier ist es dem Präsentator überlassen, wie er reagiert: Er kann seine Routinen nutzen, um entweder ein Fragment von HTML-Code (ein Snippet), ein XML-Dokument, ein JSON-Objekt oder ein Stück Javascript-Code zurückzugeben. +AJAX-Anfrage +============ -Es gibt ein vorverarbeitetes Objekt namens `payload`, das für das Senden von Daten in JSON an den Browser bestimmt ist. +Eine AJAX-Anfrage unterscheidet sich im Grunde nicht von einer klassischen HTTP-Anfrage. Ein Presenter wird mit bestimmten Parametern aufgerufen. Und es liegt am Presenter, wie er auf die Anfrage reagiert – er kann Daten im JSON-Format zurückgeben, einen Teil des HTML-Codes senden, ein XML-Dokument usw. -```php -public function actionDelete(int $id): void -{ - if ($this->isAjax()) { - $this->payload->message = 'Erfolg'; - } - // ... -} +Auf der Browserseite initialisieren wir die AJAX-Anfrage mit der Funktion `fetch()`: + +```js +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) +.then(response => response.json()) +.then(payload => { + // Verarbeitung der Antwort +}); ``` -Um die volle Kontrolle über Ihre JSON-Ausgabe zu haben, verwenden Sie die Methode `sendJson` in Ihrem Presenter. Sie beendet den Presenter sofort und Sie können auf eine Vorlage verzichten: +Auf der Serverseite erkennen wir eine AJAX-Anfrage mit der Methode `$httpRequest->isAjax()` des [die HTTP-Anfrage kapselnden |http:request] Dienstes. Zur Erkennung verwendet sie den HTTP-Header `X-Requested-With`, daher ist es wichtig, diesen mitzusenden. Innerhalb des Presenters kann die Methode `$this->isAjax()` verwendet werden. + +Wenn Sie Daten im JSON-Format senden möchten, verwenden Sie die Methode [`sendJson()` |presenters#Senden der Antwort]. Die Methode beendet auch die Aktivität des Presenters. ```php -$this->sendJson(['key' => 'value', /* ... */]); +public function actionExport(): void +{ + $this->sendJson($this->model->getData); +} ``` -Wenn wir HTML senden wollen, können wir entweder eine spezielle Vorlage für AJAX-Anfragen festlegen: +Wenn Sie planen, mit einem speziellen Template für AJAX zu antworten, können Sie dies wie folgt tun: ```php public function handleClick($param): void @@ -45,222 +52,198 @@ public function handleClick($param): void ``` -Naja .[#toc-naja] -================= +Snippets +======== + +Das stärkste Mittel, das Nette zur Verbindung von Server und Client bietet, sind Snippets. Dank ihnen können Sie eine gewöhnliche Anwendung mit minimalem Aufwand und wenigen Codezeilen in eine AJAX-Anwendung verwandeln. Wie das Ganze funktioniert, demonstriert das Beispiel Fifteen, dessen Code Sie auf [GitHub |https://github.com/nette-examples/fifteen] finden. + +Snippets, oder Ausschnitte, ermöglichen es, nur Teile der Seite zu aktualisieren, anstatt die gesamte Seite neu zu laden. Dies ist nicht nur schneller und effizienter, sondern bietet auch eine komfortablere Benutzererfahrung. Snippets erinnern vielleicht an Hotwire für Ruby on Rails oder Symfony UX Turbo. Interessanterweise hat Nette Snippets bereits 14 Jahre früher eingeführt. + +Wie funktionieren Snippets? Beim ersten Laden der Seite (nicht-AJAX-Anfrage) wird die gesamte Seite einschließlich aller Snippets geladen. Wenn der Benutzer mit der Seite interagiert (z. B. auf einen Button klickt, ein Formular absendet usw.), wird anstelle des Ladens der gesamten Seite eine AJAX-Anfrage ausgelöst. Der Code im Presenter führt die Aktion aus und entscheidet, welche Snippets aktualisiert werden müssen. Nette rendert diese Snippets und sendet sie in Form eines Arrays im JSON-Format. Der Verarbeitungscode im Browser fügt die empfangenen Snippets wieder in die Seite ein. Es wird also nur der Code der geänderten Snippets übertragen, was Bandbreite spart und das Laden im Vergleich zur Übertragung des gesamten Seiteninhalts beschleunigt. -Die [Naja-Bibliothek |https://naja.js.org] wird verwendet, um AJAX-Anfragen auf der Browserseite zu verarbeiten. [Installieren |https://naja.js.org/#/guide/01-install-setup-naja] Sie sie als node.js-Paket (zur Verwendung mit Webpack, Rollup, Vite, Parcel und mehr): + +Naja +---- + +Zur Verarbeitung von Snippets auf der Browserseite dient die [Bibliothek Naja |https://naja.js.org]. Diese [installieren Sie |https://naja.js.org/#/guide/01-install-setup-naja] als node.js-Paket (zur Verwendung mit Anwendungen wie Webpack, Rollup, Vite, Parcel und anderen): ```shell npm install naja ``` -...oder fügen Sie sie direkt in die Seitenvorlage ein: +…oder fügen Sie sie direkt in das Seiten-Template ein: ```html ``` +Zuerst muss die Bibliothek [initialisiert |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] werden: -Schnipsel .[#toc-snippets] -========================== +```js +naja.initialize(); +``` -Es gibt ein weitaus mächtigeres Werkzeug der eingebauten AJAX-Unterstützung - Snippets. Mit ihnen ist es möglich, eine normale Anwendung mit nur wenigen Zeilen Code in eine AJAX-Anwendung zu verwandeln. Wie das Ganze funktioniert, wird im Fifteen-Beispiel gezeigt, dessen Code auch im Build oder auf [GitHub |https://github.com/nette-examples/fifteen] verfügbar ist. +Um aus einem gewöhnlichen Link (Signal) oder dem Absenden eines Formulars eine AJAX-Anfrage zu machen, genügt es, den entsprechenden Link, das Formular oder den Button mit der Klasse `ajax` zu kennzeichnen: -Die Funktionsweise der Snippets besteht darin, dass bei der ersten (d.h. Nicht-AJAX-) Anfrage die gesamte Seite übertragen wird und dann bei jeder [AJAX-Unteranfrage |components#signal] (Anfrage derselben Ansicht desselben Präsentators) nur der Code der geänderten Teile in das bereits erwähnte Repository `payload` übertragen wird. +```html +Go -Snippets erinnern vielleicht an Hotwire für Ruby on Rails oder Symfony UX Turbo, aber Nette hat sie schon vierzehn Jahre früher erfunden. +
    + +
    +oder + +
    + +
    +``` -Invalidierung von Snippets .[#toc-invalidation-of-snippets] -=========================================================== -Jeder Nachkomme der Klasse [Control |components] (also auch ein Presenter) ist in der Lage, sich zu merken, ob es während einer Anfrage Änderungen gab, die ein erneutes Rendern erforderlich machen. Dafür gibt es zwei Methoden: `redrawControl()` und `isControlInvalid()`. Ein Beispiel: +Neuzeichnen von Snippets +------------------------ + +Jedes Objekt der Klasse [Control |components] (einschließlich des Presenters selbst) verfolgt, ob Änderungen aufgetreten sind, die sein Neuzeichnen erfordern. Dazu dient die Methode `redrawControl()`: ```php public function handleLogin(string $user): void { - // Das Objekt muss neu gerendert werden, nachdem sich der Benutzer angemeldet hat + // nach dem Login muss der relevante Teil neu gezeichnet werden $this->redrawControl(); // ... } ``` -Nette bietet jedoch eine noch feinere Auflösung als ganze Komponenten. Die aufgeführten Methoden akzeptieren den Namen eines sogenannten "Snippets" als optionalen Parameter. Ein "Snippet" ist im Grunde ein Element in Ihrer Vorlage, das zu diesem Zweck durch ein Latte-Makro markiert wurde, mehr dazu später. So ist es möglich, eine Komponente aufzufordern, nur *Teile* ihrer Vorlage neu zu zeichnen. Wenn die gesamte Komponente ungültig ist, werden alle ihre Schnipsel neu gerendert. Eine Komponente ist auch dann "ungültig", wenn eine ihrer Unterkomponenten ungültig ist. - -```php -$this->isControlInvalid(); // -> false -$this->redrawControl('header'); // macht das Snippet namens 'header' ungültig -$this->isControlInvalid('header'); // -> true -$this->isControlInvalid('footer'); // -> false -$this->isControlInvalid(); // -> true, mindestens ein Snippet ist ungültig +Nette ermöglicht eine noch feinere Kontrolle darüber, was neu gezeichnet werden soll. Die genannte Methode kann nämlich als Argument den Namen des Snippets entgegennehmen. Man kann also auf der Ebene von Template-Teilen invalidieren (sprich: Neuzeichnen erzwingen). Wenn die gesamte Komponente invalidiert wird, wird auch jedes ihrer Snippets neu gezeichnet: -$this->redrawControl(); // macht die gesamte Komponente ungültig, jedes Snippet -$this->isControlInvalid('footer'); // -> true +```php +// invalidiert das Snippet 'header' +$this->redrawControl('header'); ``` -Eine Komponente, die ein Signal erhält, wird automatisch zum Neuzeichnen markiert. - -Dank des Snippet-Redrawing wissen wir genau, welche Teile welcher Elemente neu gezeichnet werden sollten. - - -Tag `{snippet} … {/snippet}` .{toc: Tag snippet} -================================================ -Das Rendering der Seite verläuft ganz ähnlich wie bei einer normalen Anfrage: Es werden die gleichen Vorlagen geladen usw. Entscheidend ist jedoch, dass die Teile, die nicht in die Ausgabe gelangen sollen, weggelassen werden; die anderen Teile sollen mit einem Bezeichner verknüpft und in einem für einen JavaScript-Handler verständlichen Format an den Benutzer gesendet werden. +Snippets in Latte +----------------- - -Syntax .[#toc-syntax] ---------------------- - -Wenn ein Steuerelement oder ein Snippet in der Vorlage vorhanden ist, müssen wir es mit dem `{snippet} ... {/snippet}` pair-Tag einpacken - er sorgt dafür, dass das gerenderte Snippet "ausgeschnitten" und an den Browser gesendet wird. Außerdem wird es in ein Hilfs-Tag eingebettet `
    ` Tag (es ist möglich, einen anderen zu verwenden). Im folgenden Beispiel wird ein Snippet mit dem Namen `header` definiert. Es kann ebenso gut die Vorlage einer Komponente darstellen: +Die Verwendung von Snippets in Latte ist extrem einfach. Um einen Teil des Templates als Snippet zu definieren, umschließen Sie ihn einfach mit den Tags `{snippet}` und `{/snippet}`: ```latte {snippet header} -

    Hello ...

    +

    Hallo ...

    {/snippet} ``` -Wenn Sie ein Snippet mit einem anderen Element erstellen möchten als `
    ` erstellen oder dem Element benutzerdefinierte Attribute hinzufügen möchten, können Sie die folgende Definition verwenden: +Das Snippet erstellt in der HTML-Seite ein `
    `-Element mit einer speziellen generierten `id`. Beim Neuzeichnen des Snippets wird dann der Inhalt dieses Elements aktualisiert. Daher ist es notwendig, dass beim erstmaligen Rendern der Seite auch alle Snippets gerendert werden, auch wenn sie anfangs leer sein mögen. + +Sie können auch ein Snippet mit einem anderen Element als `
    ` erstellen, indem Sie ein n:Attribut verwenden: ```latte
    -

    Hello ...

    +

    Hallo ...

    ``` -Dynamische Schnipsel .[#toc-dynamic-snippets] -============================================= +Snippet-Bereiche +---------------- -In Nette können Sie auch Snippets mit einem dynamischen Namen definieren, der auf einem Laufzeitparameter basiert. Dies eignet sich besonders für verschiedene Listen, bei denen nur eine Zeile geändert werden muss, aber nicht die ganze Liste mit übertragen werden soll. Ein Beispiel hierfür wäre: +Snippet-Namen können auch Ausdrücke sein: ```latte -
      - {foreach $list as $id => $item} -
    • {$item} update
    • - {/foreach} -
    +{foreach $items as $id => $item} +
  • {$item}
  • +{/foreach} ``` -Es gibt ein statisches Snippet namens `itemsContainer`, das mehrere dynamische Snippets enthält: `item-0`, `item-1` und so weiter. +So entstehen mehrere Snippets `item-0`, `item-1` usw. Wenn wir ein dynamisches Snippet direkt invalidieren würden (zum Beispiel `item-1`), würde nichts neu gezeichnet werden. Der Grund dafür ist, dass Snippets wirklich wie Ausschnitte funktionieren und nur sie selbst direkt gerendert werden. Im Template gibt es jedoch faktisch kein Snippet namens `item-1`. Dieses entsteht erst durch die Ausführung des Codes um das Snippet herum, also der foreach-Schleife. Wir kennzeichnen daher den Teil des Templates, der ausgeführt werden soll, mit dem Tag `{snippetArea}`: -Ein dynamisches Snippet kann nicht direkt neu gezeichnet werden (das erneute Zeichnen von `item-1` hat keine Auswirkung), Sie müssen das übergeordnete Snippet (in diesem Beispiel `itemsContainer`) neu zeichnen. Dies bewirkt, dass der Code des übergeordneten Snippets ausgeführt wird, aber nur seine Unter-Snippets an den Browser gesendet werden. Wenn Sie nur einen der Teil-Snippets senden wollen, müssen Sie die Eingabe für das übergeordnete Snippet so ändern, dass die anderen Teil-Snippets nicht erzeugt werden. +```latte +
      + {foreach $items as $id => $item} +
    • {$item}
    • + {/foreach} +
    +``` -Im obigen Beispiel müssen Sie sicherstellen, dass bei einer AJAX-Anfrage nur ein Element zum Array `$list` hinzugefügt wird, so dass die Schleife `foreach` nur ein dynamisches Snippet ausgibt. +Und lassen sowohl das Snippet selbst als auch den gesamten übergeordneten Bereich neu zeichnen: ```php -class HomePresenter extends Nette\Application\UI\Presenter -{ - /** - * This method returns data for the list. - * Usually this would just request the data from a model. - * For the purpose of this example, the data is hard-coded. - */ - private function getTheWholeList(): array - { - return [ - 'First', - 'Second', - 'Third', - ]; - } - - public function renderDefault(): void - { - if (!isset($this->template->list)) { - $this->template->list = $this->getTheWholeList(); - } - } - - public function handleUpdate(int $id): void - { - $this->template->list = $this->isAjax() - ? [] - : $this->getTheWholeList(); - $this->template->list[$id] = 'Updated item'; - $this->redrawControl('itemsContainer'); - } -} +$this->redrawControl('itemsContainer'); +$this->redrawControl('item-1'); ``` +Gleichzeitig ist es ratsam sicherzustellen, dass das Array `$items` nur die Elemente enthält, die neu gezeichnet werden sollen. -Snippets in einer eingebundenen Vorlage .[#toc-snippets-in-an-included-template] -================================================================================ - -Es kann vorkommen, dass sich das Snippet in einer Vorlage befindet, die von einer anderen Vorlage eingebunden wird. In diesem Fall müssen wir den Einbindungscode in der zweiten Vorlage mit dem `snippetArea` -Makro einschließen, dann zeichnen wir sowohl den Snippet-Bereich als auch das eigentliche Snippet neu. - -Das Makro `snippetArea` sorgt dafür, dass der darin enthaltene Code ausgeführt wird, aber nur das eigentliche Snippet in der eingebundenen Vorlage an den Browser gesendet wird. +Wenn wir mittels des `{include}`-Tags ein anderes Template einfügen, das Snippets enthält, muss das Einfügen des Templates ebenfalls in eine `snippetArea` eingeschlossen und diese zusammen mit dem Snippet invalidiert werden: ```latte -{* parent.latte *} -{snippetArea wrapper} - {include 'child.latte'} +{snippetArea include} + {include 'included.latte'} {/snippetArea} ``` + ```latte -{* child.latte *} +{* included.latte *} {snippet item} -... + ... {/snippet} ``` + ```php -$this->redrawControl('wrapper'); +$this->redrawControl('include'); $this->redrawControl('item'); ``` -Sie können es auch mit dynamischen Snippets kombinieren. - - -Hinzufügen und Löschen .[#toc-adding-and-deleting] -================================================== -Wenn Sie ein neues Element in die Liste einfügen und `itemsContainer` ungültig machen, liefert die AJAX-Anfrage Snippets, die das neue Element enthalten, aber der Javascript-Handler kann es nicht darstellen. Das liegt daran, dass es kein HTML-Element mit der neu erstellten ID gibt. +Snippets in Komponenten +----------------------- -In diesem Fall besteht die einfachste Möglichkeit darin, die gesamte Liste in ein weiteres Snippet zu verpacken und alles ungültig zu machen: +Sie können Snippets auch in [Komponenten|components] erstellen und Nette wird sie automatisch neu zeichnen. Es gibt jedoch eine gewisse Einschränkung: Für das Neuzeichnen von Snippets ruft Nette die Methode `render()` ohne Parameter auf. Das bedeutet, dass die Übergabe von Parametern im Template nicht funktioniert: ```latte -{snippet wholeList} -
      - {foreach $list as $id => $item} -
    • {$item} update
    • - {/foreach} -
    -{/snippet} -Add +OK +{control productGrid} + +wird nicht funktionieren: +{control productGrid $arg, $arg} +{control productGrid:paginator} ``` + +Senden von Benutzerdaten +------------------------ + +Zusammen mit den Snippets können Sie beliebige zusätzliche Daten an den Client senden. Schreiben Sie diese einfach in das `payload`-Objekt: + ```php -public function handleAdd(): void +public function actionDelete(int $id): void { - $this->template->list = $this->getTheWholeList(); - $this->template->list[] = 'New one'; - $this->redrawControl('wholeList'); + // ... + if ($this->isAjax()) { + $this->payload->message = 'Erfolg'; + } } ``` -Das Gleiche gilt für das Löschen eines Eintrags. Es wäre möglich, ein leeres Snippet zu senden, aber in der Regel können Listen paginiert werden, und es wäre kompliziert, das Löschen eines Elements und das Laden eines anderen (das sich auf einer anderen Seite der paginierten Liste befand) zu implementieren. +Parameterübergabe +================= -Parameter an die Komponente senden .[#toc-sending-parameters-to-component] -========================================================================== - -Wenn wir Parameter über eine AJAX-Anfrage an die Komponente senden, egal ob es sich um Signalparameter oder dauerhafte Parameter handelt, müssen wir ihren globalen Namen angeben, der auch den Namen der Komponente enthält. Den vollständigen Namen des Parameters gibt die Methode `getParameterId()` zurück. +Wenn wir einer Komponente über eine AJAX-Anfrage Parameter senden, seien es Signalparameter oder persistente Parameter, müssen wir bei der Anfrage deren globalen Namen angeben, der auch den Namen der Komponente enthält. Den vollständigen Namen des Parameters gibt die Methode `getParameterId()` zurück. ```js -$.getJSON( - {link changeCountBasket!}, - { - {$control->getParameterId('id')}: id, - {$control->getParameterId('count')}: count - } -}); +let url = new URL({link //foo!}); +url.searchParams.set({$control->getParameterId('bar')}, bar); + +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) ``` -Und behandeln Sie die Methode mit den entsprechenden Parametern in der Komponente. +Und die handle-Methode mit den entsprechenden Parametern in der Komponente: ```php -public function handleChangeCountBasket(int $id, int $count): void +public function handleFoo(int $bar): void { - } ``` diff --git a/application/de/bootstrap.texy b/application/de/bootstrap.texy deleted file mode 100644 index 217e1b2407..0000000000 --- a/application/de/bootstrap.texy +++ /dev/null @@ -1,233 +0,0 @@ -Bootstrap -********* - -
    - -Bootstrap ist ein Bootcode, der die Umgebung initialisiert, einen Dependency Injection (DI) Container erstellt und die Anwendung startet. Wir werden das besprechen: - -- wie Sie Ihre Anwendung mithilfe von NEON-Dateien konfigurieren -- wie man den Produktions- und den Entwicklungsmodus handhabt -- wie man den DI-Container erstellt - -
    - - -Anwendungen, ob webbasiert oder als Befehlszeilenskript, beginnen mit einer Art Initialisierung der Umgebung. In früheren Zeiten konnte das eine Datei namens `include.inc.php` sein, die dafür zuständig war und in der Startdatei enthalten war. -In modernen Nette-Anwendungen wurde sie durch die Klasse `Bootstrap` ersetzt, die als Teil der Anwendung in der Datei `app/Bootstrap.php` zu finden ist. Sie könnte zum Beispiel so aussehen: - -```php -use Nette\Bootstrap\Configurator; - -class Bootstrap -{ - public static function boot(): Configurator - { - $appDir = dirname(__DIR__); - $configurator = new Configurator; - //$configurator->setDebugMode('secret@23.75.345.200'); - $configurator->enableTracy($appDir . '/log'); - $configurator->setTempDirectory($appDir . '/temp'); - $configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); - $configurator->addConfig($appDir . '/config/common.neon'); - return $configurator; - } -} -``` - - -index.php .[#toc-index-php] -=========================== - -Im Falle von Webanwendungen ist die Ausgangsdatei `index.php`, die sich im öffentlichen Verzeichnis `www/` befindet. Sie überlässt es der Klasse `Bootstrap`, die Umgebung zu initialisieren und den `$configurator` zurückzugeben, der den DI-Container erstellt. Dann wird der Dienst `Application` aufgerufen, der die Webanwendung ausführt: - -```php -// Initialisieren der Umgebung + Abrufen des Configurator-Objekts -$configurator = App\Bootstrap::boot(); -// Erstellen eines DI-Containers -$container = $configurator->createContainer(); -// DI-Container erstellt ein Nette\Application\Application-Objekt -$application = $container->getByType(Nette\Application\Application::class); -// Nette-Anwendung starten -$application->run(); -``` - -Wie Sie sehen, hilft die Klasse [api:Nette\Bootstrap\Configurator], die wir nun genauer vorstellen werden, bei der Einrichtung der Umgebung und der Erstellung eines Dependency-Injection-Containers (DI). - - -Entwicklungs- vs. Produktionsmodus .[#toc-development-vs-production-mode] -========================================================================= - -Nette unterscheidet zwischen zwei grundlegenden Modi, in denen eine Anfrage ausgeführt wird: Entwicklungs- und Produktionsmodus. Der Entwicklungsmodus ist auf maximalen Komfort für den Programmierer ausgerichtet, Tracy wird angezeigt, der Cache wird automatisch aktualisiert, wenn Vorlagen oder die DI-Containerkonfiguration geändert werden, usw. Im Produktionsmodus liegt der Schwerpunkt auf der Leistung, Tracy protokolliert nur Fehler und Änderungen an Vorlagen und anderen Dateien werden nicht überprüft. - -Die Auswahl des Modus erfolgt durch automatische Erkennung, so dass in der Regel keine Notwendigkeit besteht, etwas manuell zu konfigurieren oder umzuschalten. Der Modus ist Entwicklung, wenn die Anwendung auf localhost läuft (d.h. IP-Adresse `127.0.0.1` oder `::1`) und kein Proxy vorhanden ist (d.h. sein HTTP-Header). Ansonsten läuft sie im Produktionsmodus. - -Wenn Sie den Entwicklungsmodus in anderen Fällen aktivieren möchten, z. B. für Programmierer, die von einer bestimmten IP-Adresse aus zugreifen, können Sie `setDebugMode()` verwenden: - -```php -$configurator->setDebugMode('23.75.345.200'); // eine oder mehrere IP-Adressen -``` - -Wir empfehlen auf jeden Fall, eine IP-Adresse mit einem Cookie zu kombinieren. Wir speichern ein geheimes Token im `nette-debug` Cookie, z.B. `secret1234`, und der Entwicklungsmodus wird für Programmierer mit dieser Kombination von IP und Cookie aktiviert. - -```php -$configurator->setDebugMode('secret1234@23.75.345.200'); -``` - -Wir können den Entwicklermodus auch komplett abschalten, sogar für localhost: - -```php -$configurator->setDebugMode(false); -``` - -Beachten Sie, dass der Wert `true` den Entwicklermodus standardmäßig einschaltet, was auf einem Produktionsserver niemals passieren sollte. - - -Debugging-Werkzeug Tracy .[#toc-debugging-tool-tracy] -===================================================== - -Zur einfachen Fehlersuche werden wir das großartige Tool [Tracy |tracy:] einschalten. Im Entwicklermodus zeigt es Fehler an und im Produktionsmodus protokolliert es Fehler in das angegebene Verzeichnis: - -```php -$configurator->enableTracy($appDir . '/log'); -``` - - -Temporäre Dateien .[#toc-temporary-files] -========================================= - -Nette verwendet den Cache für DI-Container, RobotLoader, Vorlagen usw. Daher ist es notwendig, den Pfad zu dem Verzeichnis festzulegen, in dem der Cache gespeichert werden soll: - -```php -$configurator->setTempDirectory($appDir . '/temp'); -``` - -Unter Linux oder macOS setzen Sie die [Schreibrechte |nette:troubleshooting#Setting directory permissions] für die Verzeichnisse `log/` und `temp/`. - - -RobotLoader .[#toc-robotloader] -=============================== - -Normalerweise wollen wir die Klassen automatisch mit [RobotLoader |robot-loader:] laden, also müssen wir ihn starten und ihn Klassen aus dem Verzeichnis laden lassen, in dem sich `Bootstrap.php` befindet (d.h. `__DIR__`) und aus allen seinen Unterverzeichnissen: - -```php -$configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); -``` - -Eine alternative Möglichkeit ist, nur das automatische Laden von [Composer |best-practices:composer] PSR-4 zu verwenden. - - -Zeitzone .[#toc-timezone] -========================= - -Configurator ermöglicht es Ihnen, eine Zeitzone für Ihre Anwendung festzulegen. - -```php -$configurator->setTimeZone('Europe/Prague'); -``` - - -DI-Container-Konfiguration .[#toc-di-container-configuration] -============================================================= - -Teil des Boot-Prozesses ist die Erstellung eines DI-Containers, d.h. einer Fabrik für Objekte, die das Herzstück der gesamten Anwendung ist. Dabei handelt es sich um eine PHP-Klasse, die von Nette generiert und in einem Cache-Verzeichnis gespeichert wird. Die Factory erzeugt wichtige Anwendungsobjekte, und Konfigurationsdateien weisen sie an, wie sie zu erstellen und zu konfigurieren sind, wodurch wir das Verhalten der gesamten Anwendung beeinflussen. - -Konfigurationsdateien werden normalerweise im [NEON-Format |neon:format] geschrieben. [Was konfiguriert werden kann |nette:configuring], können Sie [hier |nette:configuring] nachlesen. - -.[tip] -Im Entwicklungsmodus wird der Container jedes Mal automatisch aktualisiert, wenn Sie den Code oder die Konfigurationsdateien ändern. Im Produktionsmodus wird er nur einmal generiert und Dateiänderungen werden nicht überprüft, um die Leistung zu maximieren. - -Konfigurationsdateien werden mit `addConfig()` geladen: - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -``` - -Die Methode `addConfig()` kann mehrfach aufgerufen werden, um mehrere Dateien hinzuzufügen. - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -$configurator->addConfig($appDir . '/config/local.neon'); -if (PHP_SAPI === 'cli') { - $configurator->addConfig($appDir . '/config/cli.php'); -} -``` - -Der Name `cli.php` ist kein Tippfehler, die Konfiguration kann auch in eine PHP-Datei geschrieben werden, die sie als Array zurückgibt. - -Alternativ können wir auch den [Abschnitt`includes` |dependency-injection:configuration#including files] verwenden, um weitere Konfigurationsdateien zu laden. - -Wenn Elemente mit denselben Schlüsseln in Konfigurationsdateien vorkommen, werden sie [überschrieben oder |dependency-injection:configuration#Merging] im Falle von Arrays [zusammengeführt |dependency-injection:configuration#Merging]. Die später eingebundene Datei hat eine höhere Priorität als die vorherige. Die Datei, in der der Abschnitt `includes` aufgeführt ist, hat eine höhere Priorität als die in ihr enthaltenen Dateien. - - -Statische Parameter .[#toc-static-parameters] ---------------------------------------------- - -Parameter, die in Konfigurationsdateien verwendet werden, können [im Abschnitt `parameters` |dependency-injection:configuration#parameters] definiert und auch von der Methode `addStaticParameters()` übergeben (oder überschrieben) werden (sie hat den Alias `addParameters()`). Wichtig ist, dass unterschiedliche Parameterwerte die Erzeugung zusätzlicher DI-Container, d.h. zusätzlicher Klassen, bewirken. - -```php -$configurator->addStaticParameters([ - 'projectId' => 23, -]); -``` - -In Konfigurationsdateien können wir die übliche Notation `%projectId%` verwenden, um auf den Parameter mit dem Namen `projectId` zuzugreifen. Standardmäßig füllt der Configurator die folgenden Parameter aus: `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` und `consoleMode`. - - -Dynamische Parameter .[#toc-dynamic-parameters] ------------------------------------------------ - -Wir können dem Container auch dynamische Parameter hinzufügen, deren unterschiedliche Werte, im Gegensatz zu statischen Parametern, nicht die Erzeugung neuer DI-Container verursachen. - -```php -$configurator->addDynamicParameters([ - 'remoteIp' => $_SERVER['REMOTE_ADDR'], -]); -``` - -Umgebungsvariablen können mit dynamischen Parametern leicht verfügbar gemacht werden. Wir können über `%env.variable%` in Konfigurationsdateien auf sie zugreifen. - -```php -$configurator->addDynamicParameters([ - 'env' => getenv(), -]); -``` - - -Importierte Dienste .[#toc-imported-services] ---------------------------------------------- - -Wir gehen jetzt in die Tiefe. Obwohl der Zweck eines DI-Containers darin besteht, Objekte zu erstellen, kann es in Ausnahmefällen erforderlich sein, ein vorhandenes Objekt in den Container einzufügen. Wir tun dies, indem wir den Dienst mit dem Attribut `imported: true` definieren. - -```neon -services: - myservice: - type: App\Model\MyCustomService - imported: true -``` - -Erstellen Sie eine neue Instanz und fügen Sie sie in Bootstrap ein: - -```php -$configurator->addServices([ - 'myservice' => new App\Model\MyCustomService('foobar'), -]); -``` - - -Verschiedene Umgebungen .[#toc-different-environments] -====================================================== - -Es steht Ihnen frei, die Klasse `Bootstrap` an Ihre Bedürfnisse anzupassen. Sie können der Methode `boot()` Parameter hinzufügen, um Webprojekte zu unterscheiden, oder andere Methoden hinzufügen, wie `bootForTests()`, die die Umgebung für Unit-Tests initialisiert, `bootForCli()` für Skripte, die von der Befehlszeile aus aufgerufen werden, und so weiter. - -```php -public static function bootForTests(): Configurator -{ - $configurator = self::boot(); - Tester\Environment::setup(); // Nette Tester Initialisierung - return $configurator; -} -``` diff --git a/application/de/bootstrapping.texy b/application/de/bootstrapping.texy new file mode 100644 index 0000000000..6a61ac820c --- /dev/null +++ b/application/de/bootstrapping.texy @@ -0,0 +1,297 @@ +Bootstrapping +************* + +
    + +Bootstrapping ist der Prozess der Initialisierung der Anwendungsumgebung, der Erstellung eines Dependency Injection (DI) Containers und des Startens der Anwendung. Wir werden besprechen: + +- wie die Bootstrap-Klasse die Umgebung initialisiert +- wie Anwendungen mit NEON-Dateien konfiguriert werden +- wie zwischen Produktions- und Entwicklungsmodus unterschieden wird +- wie der DI-Container erstellt und konfiguriert wird + +
    + + +Anwendungen, egal ob Webanwendungen oder von der Kommandozeile gestartete Skripte, beginnen ihre Ausführung mit einer Form der Initialisierung der Umgebung. Früher war dafür eine Datei wie `include.inc.php` verantwortlich, die von der initialen Datei eingebunden wurde. In modernen Nette-Anwendungen wurde dies durch die Klasse `Bootstrap` ersetzt, die Sie als Teil der Anwendung in der Datei `app/Bootstrap.php` finden. Sie könnte zum Beispiel so aussehen: + +```php +use Nette\Bootstrap\Configurator; + +class Bootstrap +{ + private Configurator $configurator; + private string $rootDir; + + public function __construct() + { + $this->rootDir = dirname(__DIR__); + // Der Configurator ist für die Einstellung der Anwendungsumgebung und der Dienste verantwortlich. + $this->configurator = new Configurator; + // Legt das Verzeichnis für temporäre Dateien fest, die von Nette generiert werden (z. B. kompilierte Templates) + $this->configurator->setTempDirectory($this->rootDir . '/temp'); + } + + public function bootWebApplication(): Nette\DI\Container + { + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); + } + + private function initializeEnvironment(): void + { + // Nette ist schlau und der Entwicklungsmodus wird automatisch aktiviert, + // oder Sie können ihn für eine bestimmte IP-Adresse aktivieren, indem Sie die folgende Zeile auskommentieren: + // $this->configurator->setDebugMode('secret@23.75.345.200'); + + // Aktiviert Tracy: das ultimative "Schweizer Taschenmesser" für das Debugging. + $this->configurator->enableTracy($this->rootDir . '/log'); + + // RobotLoader: lädt automatisch alle Klassen im ausgewählten Verzeichnis + $this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); + } + + private function setupContainer(): void + { + // Lädt Konfigurationsdateien + $this->configurator->addConfig($this->rootDir . '/config/common.neon'); + } +} +``` + + +index.php +========= + +Die initiale Datei für Webanwendungen ist `index.php`, die sich im [öffentlichen Verzeichnis |directory-structure#Öffentliches Verzeichnis www] `www/` befindet. Diese lässt die Umgebung von der Bootstrap-Klasse initialisieren und den DI-Container erstellen. Danach holt sie sich den Dienst `Application` daraus, der die Webanwendung startet: + +```php +$bootstrap = new App\Bootstrap; +// Initialisierung der Umgebung + Erstellung des DI-Containers +$container = $bootstrap->bootWebApplication(); +// Der DI-Container erstellt das Objekt Nette\Application\Application +$application = $container->getByType(Nette\Application\Application::class); +// Start der Nette-Anwendung und Verarbeitung der eingehenden Anfrage +$application->run(); +``` + +Wie man sieht, hilft die Klasse [api:Nette\Bootstrap\Configurator] bei der Einstellung der Umgebung und der Erstellung des Dependency Injection (DI) Containers, die wir uns nun genauer ansehen werden. + + +Entwicklungs- vs. Produktionsmodus +================================== + +Nette verhält sich unterschiedlich, je nachdem, ob es auf einem Entwicklungs- oder Produktionsserver läuft: + +🛠️ Entwicklungsmodus (Development): + - Zeigt die Tracy Debugbar mit nützlichen Informationen an (SQL-Abfragen, Ausführungszeit, verwendeter Speicher) + - Zeigt im Fehlerfall eine detaillierte Fehlerseite mit Funktionsaufrufen und Variableninhalten an + - Erneuert automatisch den Cache bei Änderungen an Latte-Templates, Konfigurationsdateien usw. + + +🚀 Produktionsmodus (Production): + - Zeigt keine Debugging-Informationen an, alle Fehler werden im Log protokolliert + - Zeigt im Fehlerfall den ErrorPresenter oder eine allgemeine "Server Error"-Seite an + - Der Cache wird niemals automatisch erneuert! + - Optimiert für Geschwindigkeit und Sicherheit + + +Die Moduswahl erfolgt durch Auto-Detektion, sodass normalerweise nichts konfiguriert oder manuell umgeschaltet werden muss: + +- Entwicklungsmodus: auf Localhost (IP-Adresse `127.0.0.1` oder `::1`), wenn kein Proxy vorhanden ist (d. h. dessen HTTP-Header) +- Produktionsmodus: überall sonst + +Wenn wir den Entwicklungsmodus auch in anderen Fällen aktivieren möchten, z. B. für Programmierer, die von einer bestimmten IP-Adresse zugreifen, verwenden wir `setDebugMode()`: + +```php +$this->configurator->setDebugMode('23.75.345.200'); // es kann auch ein Array von IP-Adressen angegeben werden +``` + +Wir empfehlen dringend, die IP-Adresse mit einem Cookie zu kombinieren. Wir speichern einen geheimen Token, z. B. `secret1234`, im Cookie `nette-debug` und aktivieren auf diese Weise den Entwicklungsmodus für Programmierer, die von einer bestimmten IP-Adresse zugreifen und gleichzeitig den erwähnten Token im Cookie haben: + +```php +$this->configurator->setDebugMode('secret1234@23.75.345.200'); +``` + +Wir können den Entwicklungsmodus auch vollständig deaktivieren, sogar für Localhost: + +```php +$this->configurator->setDebugMode(false); +``` + +Achtung, der Wert `true` schaltet den Entwicklungsmodus fest ein, was auf einem Produktionsserver niemals passieren darf. + + +Debugging-Tool Tracy +==================== + +Für einfaches Debugging aktivieren wir noch das großartige Werkzeug [Tracy |tracy:]. Im Entwicklungsmodus visualisiert es Fehler und im Produktionsmodus protokolliert es Fehler in das angegebene Verzeichnis: + +```php +$this->configurator->enableTracy($this->rootDir . '/log'); +``` + + +Temporäre Dateien +================= + +Nette verwendet einen Cache für den DI-Container, RobotLoader, Templates usw. Daher ist es notwendig, den Pfad zum Verzeichnis festzulegen, in dem der Cache gespeichert wird: + +```php +$this->configurator->setTempDirectory($this->rootDir . '/temp'); +``` + +Unter Linux oder macOS setzen Sie für die Verzeichnisse `log/` und `temp/` [Schreibrechte |nette:troubleshooting#Einstellung der Verzeichnisberechtigungen]. + + +RobotLoader +=========== + +In der Regel möchten wir Klassen automatisch mit dem [RobotLoader |robot-loader:] laden, also müssen wir ihn starten und ihn Klassen aus dem Verzeichnis laden lassen, in dem sich `Bootstrap.php` befindet (d. h. `__DIR__`), sowie aus allen Unterverzeichnissen: + +```php +$this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); +``` + +Ein alternativer Ansatz besteht darin, Klassen nur über [Composer |best-practices:composer] unter Einhaltung von PSR-4 laden zu lassen. + + +Zeitzone +======== + +Über den Konfigurator können Sie die Standard-Zeitzone einstellen. + +```php +$this->configurator->setTimeZone('Europe/Prague'); +``` + + +Konfiguration des DI-Containers +=============================== + +Ein Teil des Boot-Prozesses ist die Erstellung des DI-Containers, auch Objektfabrik genannt, der das Herz der gesamten Anwendung ist. Es handelt sich tatsächlich um eine PHP-Klasse, die von Nette generiert und im Cache-Verzeichnis gespeichert wird. Die Fabrik erstellt die Schlüsselobjekte der Anwendung, und mithilfe von Konfigurationsdateien weisen wir sie an, wie sie diese erstellen und einstellen soll, wodurch wir das Verhalten der gesamten Anwendung beeinflussen. + +Konfigurationsdateien werden normalerweise im [NEON |neon:format]-Format geschrieben. In einem separaten Kapitel erfahren Sie, [was alles konfiguriert werden kann |nette:configuring]. + +.[tip] +Im Entwicklungsmodus wird der Container bei jeder Änderung des Codes oder der Konfigurationsdateien automatisch aktualisiert. Im Produktionsmodus wird er nur einmal generiert, und Änderungen werden zur Maximierung der Leistung nicht überprüft. + +Konfigurationsdateien laden wir mit `addConfig()`: + +```php +$this->configurator->addConfig($this->rootDir . '/config/common.neon'); +``` + +Wenn wir mehrere Konfigurationsdateien hinzufügen möchten, können wir die Funktion `addConfig()` mehrmals aufrufen. + +```php +$configDir = $this->rootDir . '/config'; +$this->configurator->addConfig($configDir . '/common.neon'); +$this->configurator->addConfig($configDir . '/services.neon'); +if (PHP_SAPI === 'cli') { + $this->configurator->addConfig($configDir . '/cli.php'); +} +``` + +Der Name `cli.php` ist kein Tippfehler; die Konfiguration kann auch in einer PHP-Datei geschrieben sein, die sie als Array zurückgibt. + +Wir können auch weitere Konfigurationsdateien im [Abschnitt `includes` |dependency-injection:configuration#Dateien einbinden] hinzufügen. + +Wenn in den Konfigurationsdateien Elemente mit denselben Schlüsseln erscheinen, werden sie überschrieben oder im Falle von [Arrays zusammengeführt |dependency-injection:configuration#Zusammenführen]. Die später eingebundene Datei hat eine höhere Priorität als die vorherige. Die Datei, in der der Abschnitt `includes` aufgeführt ist, hat eine höhere Priorität als die darin eingebundenen Dateien. + + +Statische Parameter +------------------- + +Parameter, die in Konfigurationsdateien verwendet werden, können [im Abschnitt `parameters` |dependency-injection:configuration#Parameter] definiert und auch über die Methode `addStaticParameters()` (hat den Alias `addParameters()`) übergeben (oder überschrieben) werden. Wichtig ist, dass unterschiedliche Werte der Parameter die Generierung zusätzlicher DI-Container, also zusätzlicher Klassen, verursachen. + +```php +$this->configurator->addStaticParameters([ + 'projectId' => 23, +]); +``` + +Auf den Parameter `projectId` kann in der Konfiguration mit der üblichen Schreibweise `%projectId%` verwiesen werden. + + +Dynamische Parameter +-------------------- + +Wir können dem Container auch dynamische Parameter hinzufügen, deren unterschiedliche Werte im Gegensatz zu statischen Parametern nicht die Generierung neuer DI-Container verursachen. + +```php +$this->configurator->addDynamicParameters([ + 'remoteIp' => $_SERVER['REMOTE_ADDR'], +]); +``` + +So können wir einfach z. B. Umgebungsvariablen hinzufügen, auf die dann in der Konfiguration mit der Schreibweise `%env.variable%` verwiesen werden kann. + +```php +$this->configurator->addDynamicParameters([ + 'env' => getenv(), +]); +``` + + +Standardparameter +----------------- + +In Konfigurationsdateien können Sie diese statischen Parameter verwenden: + +- `%appDir%` ist der absolute Pfad zum Verzeichnis mit der Datei `Bootstrap.php` +- `%wwwDir%` ist der absolute Pfad zum Verzeichnis mit der Eingabedatei `index.php` +- `%tempDir%` ist der absolute Pfad zum Verzeichnis für temporäre Dateien +- `%vendorDir%` ist der absolute Pfad zum Verzeichnis, in dem Composer Bibliotheken installiert +- `%rootDir%` ist der absolute Pfad zum Stammverzeichnis des Projekts +- `%debugMode%` gibt an, ob sich die Anwendung im Debugging-Modus befindet +- `%consoleMode%` gibt an, ob die Anfrage über die Kommandozeile kam + + +Importierte Dienste +------------------- + +Jetzt gehen wir tiefer. Obwohl der Zweck des DI-Containers darin besteht, Objekte zu erstellen, kann es ausnahmsweise notwendig sein, ein vorhandenes Objekt in den Container einzufügen. Dies tun wir, indem wir den Dienst mit dem Flag `imported: true` definieren. + +```neon +services: + myservice: + type: App\Model\MyCustomService + imported: true +``` + +Und im Bootstrap fügen wir das Objekt in den Container ein: + +```php +$this->configurator->addServices([ + 'myservice' => new App\Model\MyCustomService('foobar'), +]); +``` + + +Unterschiedliche Umgebungen +=========================== + +Scheuen Sie sich nicht, die Bootstrap-Klasse an Ihre Bedürfnisse anzupassen. Sie können der Methode `bootWebApplication()` Parameter hinzufügen, um Webprojekte zu unterscheiden. Oder wir können weitere Methoden hinzufügen, zum Beispiel `bootTestEnvironment()`, die die Umgebung für Unit-Tests initialisiert, `bootConsoleApplication()` für Skripte, die von der Kommandozeile aufgerufen werden, usw. + +```php +public function bootTestEnvironment(): Nette\DI\Container +{ + Tester\Environment::setup(); // Initialisierung von Nette Tester + $this->setupContainer(); + return $this->configurator->createContainer(); +} + +public function bootConsoleApplication(): Nette\DI\Container +{ + $this->configurator->setDebugMode(false); + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); +} +``` diff --git a/application/de/components.texy b/application/de/components.texy index 4429cd3cd7..b39a8e5abd 100644 --- a/application/de/components.texy +++ b/application/de/components.texy @@ -3,27 +3,27 @@ Interaktive Komponenten
    -Komponenten sind separate, wiederverwendbare Objekte, die wir in Seiten einfügen. Sie können Formulare, Datenfelder, Umfragen sein, eigentlich alles, was sinnvoll ist, um es wiederholt zu verwenden. Wir werden es zeigen: +Komponenten sind eigenständige, wiederverwendbare Objekte, die wir in Seiten einfügen. Das können Formulare, Datengrids, Umfragen sein, eigentlich alles, was sinnvoll wiederverwendet werden kann. Wir zeigen Ihnen: -- wie man Komponenten verwendet? -- wie man sie schreibt? +- Wie man Komponenten verwendet? +- Wie man sie schreibt? - Was sind Signale?
    -Nette hat ein eingebautes Komponentensystem. Ältere von Ihnen erinnern sich vielleicht an etwas Ähnliches aus Delphi oder ASP.NET Web Forms. React oder Vue.js bauen auf etwas entfernt Ähnlichem auf. In der Welt der PHP-Frameworks ist dies jedoch eine völlig einzigartige Funktion. +Nette verfügt über ein eingebautes Komponentensystem. Etwas Ähnliches kennen Kenner vielleicht noch aus Delphi oder ASP.NET Web Forms, und React oder Vue.js bauen auf etwas entfernt Ähnlichem auf. In der Welt der PHP-Frameworks ist dies jedoch eine einzigartige Angelegenheit. -Gleichzeitig verändern Komponenten die Herangehensweise an die Anwendungsentwicklung grundlegend. Sie können Seiten aus vorgefertigten Einheiten zusammenstellen. Benötigen Sie ein Datagrid in der Verwaltung? Sie finden es bei [Componette |https://componette.org/search/component], einem Repository von Open-Source-Add-ons (nicht nur Komponenten) für Nette, und fügen es einfach in den Presenter ein. +Dabei beeinflussen Komponenten den Ansatz zur Anwendungsentwicklung grundlegend. Sie können Seiten aus vorgefertigten Einheiten zusammenstellen. Benötigen Sie ein Datagrid in der Administration? Sie finden es auf [Componette |https://componette.org/search/component], einem Repository für Open-Source-Add-ons (also nicht nur Komponenten) für Nette, und fügen es einfach in den Presenter ein. -Sie können eine beliebige Anzahl von Komponenten in den Presenter einfügen. Und Sie können andere Komponenten in einige Komponenten einfügen. So entsteht ein Komponentenbaum mit einem Presenter als Wurzel. +Sie können beliebig viele Komponenten in einen Presenter integrieren. Und in einige Komponenten können Sie weitere Komponenten einfügen. So entsteht ein Komponentenbaum, dessen Wurzel der Presenter ist. -Fabrik-Methoden .[#toc-factory-methods] -======================================= +Factory-Methoden +================ -Wie werden Komponenten im Presenter platziert und anschließend verwendet? Normalerweise mit Hilfe von Fabrikmethoden. +Wie werden Komponenten in den Presenter eingefügt und anschließend verwendet? Normalerweise über Factory-Methoden. -Die Komponentenfabrik ist eine elegante Möglichkeit, Komponenten nur dann zu erstellen, wenn sie wirklich benötigt werden (lazy / on-demand). Der ganze Zauber liegt in der Implementierung einer Methode namens `createComponent()`, wobei `` der Name der Komponente ist, die erstellt und zurückgegeben wird. +Eine Komponenten-Factory stellt eine elegante Möglichkeit dar, Komponenten erst dann zu erstellen, wenn sie tatsächlich benötigt werden (lazy / on demand). Der ganze Zauber besteht darin, eine Methode mit dem Namen `createComponent()` zu implementieren, wobei `` der Name der zu erstellenden Komponente ist, und die die Komponente erstellt und zurückgibt. ```php .{file:DefaultPresenter.php} class DefaultPresenter extends Nette\Application\UI\Presenter @@ -37,43 +37,43 @@ class DefaultPresenter extends Nette\Application\UI\Presenter } ``` -Da alle Komponenten in separaten Methoden erstellt werden, ist der Code sauberer und leichter zu lesen. +Dadurch, dass alle Komponenten in separaten Methoden erstellt werden, gewinnt der Code an Übersichtlichkeit. .[note] -Komponentennamen beginnen immer mit einem Kleinbuchstaben, obwohl sie im Methodennamen groß geschrieben werden. +Komponentennamen beginnen immer mit einem Kleinbuchstaben, obwohl sie im Methodennamen großgeschrieben werden. -Wir rufen die Factories nie direkt auf, sondern sie werden automatisch aufgerufen, wenn wir Komponenten zum ersten Mal verwenden. Dadurch wird eine Komponente im richtigen Moment erstellt, und nur dann, wenn sie wirklich benötigt wird. Wenn wir die Komponente nicht verwenden würden (z. B. bei einer AJAX-Anfrage, bei der nur ein Teil der Seite zurückgegeben wird, oder wenn Teile im Cache gespeichert sind), wird sie gar nicht erst erstellt, und wir sparen Leistung des Servers. +Factories werden niemals direkt aufgerufen, sie rufen sich selbst auf, wenn die Komponente zum ersten Mal verwendet wird. Dadurch wird die Komponente zum richtigen Zeitpunkt erstellt und nur dann, wenn sie tatsächlich benötigt wird. Wenn wir die Komponente nicht verwenden (z. B. bei einer AJAX-Anfrage, bei der nur ein Teil der Seite übertragen wird, oder beim Caching des Templates), wird sie überhaupt nicht erstellt und wir sparen Serverleistung. ```php .{file:DefaultPresenter.php} -// wir greifen auf die Komponente zu und wenn es das erste Mal war, -// wird createComponentPoll() aufgerufen, um sie zu erstellen +// Wir greifen auf die Komponente zu, und wenn es das erste Mal war, +// wird createComponentPoll() aufgerufen, die sie erstellt $poll = $this->getComponent('poll'); // alternative Syntax: $poll = $this['poll']; ``` -In der Vorlage können Sie eine Komponente mit dem Tag [{control} |#Rendering] rendern. Es besteht also keine Notwendigkeit, die Komponenten manuell an die Vorlage zu übergeben. +Im Template kann eine Komponente mit dem Tag [{control} |#Rendern] gerendert werden. Es ist daher nicht notwendig, Komponenten manuell an das Template zu übergeben. ```latte -

    Please Vote

    +

    Stimmen Sie ab

    {control poll} ``` -Hollywood-Stil .[#toc-hollywood-style] -====================================== +Hollywood Style +=============== -Komponenten verwenden häufig eine coole Technik, die wir gerne als Hollywood-Stil bezeichnen. Sicherlich kennen Sie das Klischee, das Schauspieler bei Casting-Aufrufen oft hören: "Rufen Sie uns nicht an, wir rufen Sie an." Und genau darum geht es hier. +Komponenten verwenden üblicherweise eine frische Technik, die wir gerne Hollywood Style nennen. Sie kennen sicher den geflügelten Satz, den Teilnehmer von Filmcastings so oft hören: „Rufen Sie uns nicht an, wir rufen Sie an“. Und genau darum geht es. -Anstatt in Nette ständig Fragen zu stellen ("wurde das Formular abgeschickt?", "war es gültig?" oder "hat jemand diesen Knopf gedrückt?"), sagen Sie dem Framework "wenn das passiert, rufe diese Methode auf" und lassen die weitere Arbeit darauf beruhen. Wenn Sie in JavaScript programmieren, sind Sie mit dieser Art der Programmierung vertraut. Sie schreiben Funktionen, die aufgerufen werden, wenn ein bestimmtes Ereignis eintritt. Und die Engine übergibt die entsprechenden Parameter an sie. +In Nette sagen Sie dem Framework nämlich, anstatt ständig nachfragen zu müssen („wurde das Formular abgeschickt?“, „war es gültig?“ oder „hat der Benutzer diesen Button gedrückt?“), „wenn das passiert, ruf diese Methode auf“ und überlassen die weitere Arbeit ihm. Wenn Sie in JavaScript programmieren, kennen Sie diesen Programmierstil genau. Sie schreiben Funktionen, die aufgerufen werden, wenn ein bestimmtes Ereignis eintritt. Und die Sprache übergibt ihnen die entsprechenden Parameter. -Das verändert die Art und Weise, wie Sie Anwendungen schreiben, völlig. Je mehr Aufgaben Sie an das Framework delegieren können, desto weniger Arbeit haben Sie. Und desto weniger können Sie vergessen. +Dies verändert die Sichtweise auf das Schreiben von Anwendungen grundlegend. Je mehr Aufgaben Sie dem Framework überlassen können, desto weniger Arbeit haben Sie. Und desto weniger können Sie vielleicht übersehen. -Wie man eine Komponente schreibt .[#toc-how-to-write-a-component] -================================================================= +Wir schreiben eine Komponente +============================= -Mit Komponente sind in der Regel Abkömmlinge der Klasse [api:Nette\Application\UI\Control] gemeint. Der Präsentator [api:Nette\Application\UI\Presenter] selbst ist ebenfalls ein Abkömmling der Klasse `Control`. +Unter dem Begriff Komponente verstehen wir normalerweise einen Nachfahren der Klasse [api:Nette\Application\UI\Control]. (Genauer wäre es also, den Begriff „Controls“ zu verwenden, aber „Kontrollen“ hat im Deutschen eine ganz andere Bedeutung, und eher haben sich „Komponenten“ durchgesetzt.) Der Presenter [api:Nette\Application\UI\Presenter] selbst ist übrigens auch ein Nachfahre der Klasse `Control`. ```php .{file:PollControl.php} use Nette\Application\UI\Control; @@ -84,22 +84,22 @@ class PollControl extends Control ``` -Rendering .[#toc-rendering] -=========================== +Rendern +======= -Wir wissen bereits, dass das Tag `{control componentName}` zum Zeichnen einer Komponente verwendet wird. Es ruft die Methode `render()` der Komponente auf, in der wir uns um das Rendering kümmern. Wir haben, genau wie im Presenter, eine [Lattenvorlage |latte:] in der Variablen `$this->template`, der wir die Parameter übergeben. Im Gegensatz zur Verwendung im Presenter müssen wir eine Vorlagendatei angeben und sie rendern lassen: +Wir wissen bereits, dass zum Rendern einer Komponente der Tag `{control componentName}` verwendet wird. Dieser ruft eigentlich die Methode `render()` der Komponente auf, in der wir uns um das Rendern kümmern. Uns steht, genau wie im Presenter, eine [Latte-Vorlage|templates] in der Variablen `$this->template` zur Verfügung, an die wir Parameter übergeben. Im Gegensatz zum Presenter müssen wir die Template-Datei angeben und sie rendern lassen: ```php .{file:PollControl.php} public function render(): void { - // wir werden einige Parameter in die Vorlage einfügen + // Wir fügen einige Parameter in das Template ein $this->template->param = $value; - // und zeichnen es + // und rendern es $this->template->render(__DIR__ . '/poll.latte'); } ``` -Das Tag `{control}` erlaubt die Übergabe von Parametern an die Methode `render()`: +Der `{control}`-Tag ermöglicht es, Parameter an die `render()`-Methode zu übergeben: ```latte {control poll $id, $message} @@ -112,7 +112,7 @@ public function render(int $id, string $message): void } ``` -Manchmal kann eine Komponente aus mehreren Teilen bestehen, die wir separat rendern wollen. Für jedes dieser Teile wird eine eigene Rendering-Methode erstellt, hier zum Beispiel `renderPaginator()`: +Manchmal kann eine Komponente aus mehreren Teilen bestehen, die wir getrennt rendern möchten. Für jeden davon erstellen wir eine eigene Rendering-Methode, hier im Beispiel etwa `renderPaginator()`: ```php .{file:PollControl.php} public function renderPaginator(): void @@ -121,69 +121,69 @@ public function renderPaginator(): void } ``` -Und in der Vorlage rufen wir sie dann mit auf: +Und im Template rufen wir sie dann auf mit: ```latte {control poll:paginator} ``` -Zum besseren Verständnis ist es gut zu wissen, wie der Tag in PHP-Code übersetzt wird. +Zum besseren Verständnis ist es gut zu wissen, wie dieser Tag in PHP übersetzt wird. ```latte {control poll} {control poll:paginator 123, 'hello'} ``` -Dies kompiliert zu: +wird übersetzt als: ```php $control->getComponent('poll')->render(); $control->getComponent('poll')->renderPaginator(123, 'hello'); ``` -`getComponent()` Die Methode `poll` gibt die Komponente zurück, und dann wird die Methode `render()` bzw. `renderPaginator()` für sie aufgerufen. +Die Methode `getComponent()` gibt die Komponente `poll` zurück und ruft für diese Komponente die Methode `render()` bzw. `renderPaginator()` auf, wenn im Tag nach dem Doppelpunkt eine andere Rendering-Art angegeben ist. .[caution] -Wenn irgendwo im Parameterteil **`=>`** verwendet wird, werden alle Parameter in ein Array eingeschlossen und als erstes Argument übergeben: +Achtung, wenn irgendwo in den Parametern **`=>`** vorkommt, werden alle Parameter in ein Array verpackt und als erstes Argument übergeben: ```latte {control poll, id: 123, message: 'hello'} ``` -kompiliert zu: +wird übersetzt als: ```php $control->getComponent('poll')->render(['id' => 123, 'message' => 'hello']); ``` -Rendering der Unterkomponente: +Rendern einer Sub-Komponente: ```latte {control cartControl-someForm} ``` -kompiliert zu: +wird übersetzt als: ```php $control->getComponent("cartControl-someForm")->render(); ``` -Komponenten, wie Präsentatoren, übergeben automatisch mehrere nützliche Variablen an Vorlagen: +Komponenten, ebenso wie Presenter, übergeben automatisch einige nützliche Variablen an die Templates: -- `$basePath` ist ein absoluter URL-Pfad zum Stammverzeichnis (z. B. `/CD-collection`) -- `$baseUrl` ist eine absolute URL zum Stammverzeichnis (z. B. `http://localhost/CD-collection`) -- `$user` ist ein Objekt [, das den Benutzer repräsentiert |security:authentication] -- `$presenter` ist der aktuelle Präsentator +- `$basePath` ist der absolute URL-Pfad zum Wurzelverzeichnis (z. B. `/eshop`) +- `$baseUrl` ist die absolute URL zum Wurzelverzeichnis (z. B. `http://localhost/eshop`) +- `$user` ist das Objekt, das [den Benutzer repräsentiert |security:authentication] +- `$presenter` ist der aktuelle Presenter - `$control` ist die aktuelle Komponente -- `$flashes` Liste der von der Methode gesendeten [Meldungen |#flash-messages] `flashMessage()` +- `$flashes` ist ein Array von [Nachrichten |#Flash-Nachrichten], die mit der Funktion `flashMessage()` gesendet wurden -Signal .[#toc-signal] -===================== +Signal +====== -Wir wissen bereits, dass die Navigation in der Nette-Anwendung aus der Verknüpfung oder Routing zu Paaren `Presenter:action` besteht. Was aber, wenn wir nur eine Aktion auf der **aktuellen Seite** durchführen wollen? Zum Beispiel die Sortierreihenfolge der Tabellenspalte ändern, ein Element löschen, den Hell/Dunkel-Modus umschalten, das Formular abschicken, an der Umfrage teilnehmen usw. +Wir wissen bereits, dass die Navigation in einer Nette-Anwendung im Verlinken oder Weiterleiten auf Paare von `Presenter:action` besteht. Aber was ist, wenn wir nur eine Aktion auf der **aktuellen Seite** durchführen wollen? Zum Beispiel die Sortierreihenfolge von Spalten in einer Tabelle ändern; einen Eintrag löschen; den Hell-/Dunkelmodus umschalten; ein Formular absenden; in einer Umfrage abstimmen; usw. -Diese Art von Anfrage wird als Signal bezeichnet. Und wie Aktionen rufen Methoden auf `action()` oder `render()`aufrufen, rufen Signale Methoden auf `handle()`. Während sich das Konzept der Aktion (oder Ansicht) nur auf Präsentatoren bezieht, gelten Signale für alle Komponenten. Und damit auch für Presenter, denn `UI\Presenter` ist ein Abkömmling von `UI\Control`. +Diese Art von Anfragen wird als Signale bezeichnet. Und ähnlich wie Aktionen die Methoden `action()` oder `render()` aufrufen, rufen Signale die Methoden `handle()` auf. Während der Begriff Aktion (oder View) rein mit Presentern zusammenhängt, betreffen Signale alle Komponenten. Und somit auch Presenter, da `UI\Presenter` ein Nachfahre von `UI\Control` ist. ```php public function handleClick(int $x, int $y): void @@ -192,36 +192,36 @@ public function handleClick(int $x, int $y): void } ``` -Der Link, der das Signal aufruft, wird auf die übliche Weise erstellt, d.h. in der Vorlage durch das Attribut `n:href` oder den Tag `{link}`, im Code durch die Methode `link()`. Mehr dazu im Kapitel [URL-Links erstellen |creating-links#Links to Signal]. +Einen Link, der ein Signal aufruft, erstellen wir auf die übliche Weise, d. h. im Template mit dem Attribut `n:href` oder dem Tag `{link}`, im Code mit der Methode `link()`. Mehr dazu im Kapitel [Erstellen von URL-Links |creating-links#Links zu Signalen]. ```latte -click here +Hier klicken ``` -Das Signal wird immer im aktuellen Präsentator und in der aktuellen Ansicht aufgerufen, es ist also nicht möglich, das Signal in einem anderen Präsentator / einer anderen Aktion zu verlinken. +Ein Signal wird immer im aktuellen Presenter und der aktuellen Action aufgerufen, es kann nicht in einem anderen Presenter oder einer anderen Action ausgelöst werden. -Das Signal bewirkt also, dass die Seite genau so neu geladen wird wie in der ursprünglichen Anfrage, nur dass zusätzlich die Signalbehandlungsmethode mit den entsprechenden Parametern aufgerufen wird. Wenn die Methode nicht existiert, wird die Ausnahme [api:Nette\Application\UI\BadSignalException] ausgelöst, die dem Benutzer als Fehlerseite 403 Forbidden angezeigt wird. +Ein Signal bewirkt also das Neuladen der Seite genau wie bei der ursprünglichen Anfrage, ruft aber zusätzlich die Signal-Handler-Methode mit den entsprechenden Parametern auf. Wenn die Methode nicht existiert, wird eine Ausnahme [api:Nette\Application\UI\BadSignalException] ausgelöst, die dem Benutzer als Fehlerseite 403 Forbidden angezeigt wird. -Schnipsel und AJAX .[#toc-snippets-and-ajax] -============================================ +Snippets und AJAX +================= -Die Signale erinnern Sie vielleicht ein wenig an AJAX: Handler, die auf der aktuellen Seite aufgerufen werden. Und Sie haben Recht, Signale werden wirklich oft mit AJAX aufgerufen, und dann übertragen wir nur geänderte Teile der Seite an den Browser. Sie werden Snippets genannt. Mehr Informationen finden Sie auf der [Seite über AJAX |ajax]. +Signale erinnern Sie vielleicht ein wenig an AJAX: Handler, die auf der aktuellen Seite aufgerufen werden. Und Sie haben Recht, Signale werden tatsächlich oft mittels AJAX aufgerufen, und anschließend übertragen wir nur die geänderten Teile der Seite an den Browser. Also sogenannte Snippets. Weitere Informationen finden Sie auf der [AJAX gewidmeten Seite |ajax]. -Flash-Meldungen .[#toc-flash-messages] -====================================== +Flash-Nachrichten +================= -Eine Komponente verfügt über einen eigenen, vom Presenter unabhängigen Speicher für Flash-Nachrichten. Dies sind Meldungen, die z.B. über das Ergebnis der Operation informieren. Eine wichtige Eigenschaft von Flash-Meldungen ist, dass sie auch nach einer Routing in der Vorlage verfügbar sind. Selbst nachdem sie angezeigt wurden, bleiben sie noch 30 Sekunden lang erhalten - zum Beispiel für den Fall, dass der Benutzer die Seite ungewollt aktualisiert - die Nachricht geht nicht verloren. +Eine Komponente hat ihren eigenen Speicher für Flash-Nachrichten, unabhängig vom Presenter. Dies sind Nachrichten, die z. B. über das Ergebnis einer Operation informieren. Ein wichtiges Merkmal von Flash-Nachrichten ist, dass sie auch nach einer Weiterleitung im Template verfügbar sind. Auch nach der Anzeige bleiben sie weitere 30 Sekunden aktiv – zum Beispiel für den Fall, dass der Benutzer aufgrund einer fehlerhaften Übertragung die Seite neu lädt – die Nachricht verschwindet also nicht sofort. -Das Versenden erfolgt mit der Methode [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Der erste Parameter ist der Nachrichtentext oder das `stdClass` Objekt, das die Nachricht repräsentiert. Der optionale zweite Parameter ist der Typ der Nachricht (Fehler, Warnung, Info, etc.). Die Methode `flashMessage()` gibt eine Instanz von flash message als Objekt stdClass zurück, an das Sie Informationen übergeben können. +Das Senden übernimmt die Methode [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Der erste Parameter ist der Nachrichtentext oder ein `stdClass`-Objekt, das die Nachricht repräsentiert. Der optionale zweite Parameter ist ihr Typ (error, warning, info usw.). Die Methode `flashMessage()` gibt eine Instanz der Flash-Nachricht als `stdClass`-Objekt zurück, dem weitere Informationen hinzugefügt werden können. ```php -$this->flashMessage('Artikel wurde gelöscht.'); -$this->redirect(/* ... */); // und umleiten +$this->flashMessage('Der Eintrag wurde gelöscht.'); +$this->redirect(/* ... */); // und wir leiten weiter ``` -In der Vorlage stehen diese Meldungen in der Variablen `$flashes` als Objekte `stdClass` zur Verfügung, die die Eigenschaften `message` (Meldungstext), `type` (Meldungstyp) enthalten und die bereits erwähnten Benutzerinformationen enthalten können. Wir zeichnen sie wie folgt: +Dem Template stehen diese Nachrichten in der Variablen `$flashes` als `stdClass`-Objekte zur Verfügung, die die Eigenschaften `message` (Nachrichtentext), `type` (Nachrichtentyp) enthalten und die bereits erwähnten Benutzerinformationen enthalten können. Wir rendern sie zum Beispiel so: ```latte {foreach $flashes as $flash} @@ -230,44 +230,66 @@ In der Vorlage stehen diese Meldungen in der Variablen `$flashes` als Objekte `s ``` -Dauerhafte Parameter .[#toc-persistent-parameters] -================================================== +Weiterleitung nach Signal +========================= -Persistente Parameter werden verwendet, um den Zustand von Komponenten zwischen verschiedenen Anfragen zu erhalten. Ihr Wert bleibt gleich, auch wenn ein Link angeklickt wird. Im Gegensatz zu Sitzungsdaten werden sie in der URL übertragen. Und sie werden automatisch übertragen, einschließlich Links, die in anderen Komponenten auf derselben Seite erstellt wurden. +Nach der Verarbeitung eines Komponentensignals folgt oft eine Weiterleitung. Dies ist eine ähnliche Situation wie bei Formularen – nach dem Absenden leiten wir ebenfalls weiter, damit beim Neuladen der Seite im Browser die Daten nicht erneut gesendet werden. -Sie haben zum Beispiel eine Komponente zum Paging von Inhalten. Es kann mehrere solcher Komponenten auf einer Seite geben. Und Sie möchten, dass alle Komponenten auf ihrer aktuellen Seite bleiben, wenn Sie auf den Link klicken. Deshalb machen wir die Seitennummer (`page`) zu einem dauerhaften Parameter. +```php +$this->redirect('this') // leitet zum aktuellen Presenter und zur aktuellen Action weiter +``` -Das Erstellen eines dauerhaften Parameters ist in Nette extrem einfach. Erstellen Sie einfach eine öffentliche Eigenschaft und versehen Sie sie mit dem Attribut: (früher wurde `/** @persistent */` verwendet) +Da eine Komponente ein wiederverwendbares Element ist und normalerweise keine direkte Bindung an bestimmte Presenter haben sollte, interpretieren die Methoden `redirect()` und `link()` den Parameter automatisch als Signal der Komponente: ```php -use Nette\Application\Attributes\Persistent; // diese Zeile ist wichtig +$this->redirect('click') // leitet zum Signal 'click' derselben Komponente weiter +``` + +Wenn Sie zu einem anderen Presenter oder einer anderen Aktion weiterleiten müssen, können Sie dies über den Presenter tun: + +```php +$this->getPresenter()->redirect('Product:show'); // leitet zu einem anderen Presenter/Action weiter +``` + + +Persistente Parameter +===================== + +Persistente Parameter dienen dazu, den Zustand in Komponenten über verschiedene Anfragen hinweg zu erhalten. Ihr Wert bleibt auch nach dem Klick auf einen Link gleich. Im Gegensatz zu Daten in der Session werden sie in der URL übertragen. Und das vollautomatisch, einschließlich Links, die in anderen Komponenten auf derselben Seite erstellt wurden. + +Sie haben z. B. eine Komponente für die Paginierung von Inhalten. Solche Komponenten können auf einer Seite mehrmals vorkommen. Und wir möchten, dass nach dem Klick auf einen Link alle Komponenten auf ihrer aktuellen Seite bleiben. Deshalb machen wir die Seitenzahl (`page`) zu einem persistenten Parameter. + +Die Erstellung eines persistenten Parameters ist in Nette äußerst einfach. Es genügt, eine öffentliche Eigenschaft zu erstellen und sie mit einem Attribut zu kennzeichnen: (früher wurde `/** @persistent */` verwendet) + +```php +use Nette\Application\Attributes\Persistent; // diese Zeile ist wichtig class PaginatingControl extends Control { #[Persistent] - public int $page = 1; // muss öffentlich sein + public int $page = 1; // muss public sein } ``` -Es wird empfohlen, den Datentyp (z. B. `int`) mit der Eigenschaft zu verknüpfen, und Sie können auch einen Standardwert angeben. Parameterwerte können [validiert |#Validation of Persistent Parameters] werden. +Für die Eigenschaft empfehlen wir, auch den Datentyp anzugeben (z. B. `int`), und Sie können auch einen Standardwert angeben. Die Werte der Parameter können [validiert |#Validierung persistenter Parameter] werden. -Sie können den Wert eines persistenten Parameters beim Erstellen eines Links ändern: +Beim Erstellen eines Links kann der Wert des persistenten Parameters geändert werden: ```latte -next +weiter ``` -Oder er kann *zurückgesetzt* werden, d.h. aus der URL entfernt werden. Er nimmt dann seinen Standardwert an: +Oder er kann *zurückgesetzt* werden, d. h. aus der URL entfernt werden. Dann nimmt er seinen Standardwert an: ```latte -reset +zurücksetzen ``` -Persistente Komponenten .[#toc-persistent-components] -===================================================== +Persistente Komponenten +======================= -Nicht nur Parameter, sondern auch Komponenten können persistent sein. Ihre persistenten Parameter werden auch zwischen verschiedenen Aktionen oder zwischen verschiedenen Präsentatoren übertragen. Wir kennzeichnen persistente Komponenten mit diesen Annotationen für die Presenter-Klasse. Zum Beispiel markieren wir hier die Komponenten `calendar` und `poll` wie folgt: +Nicht nur Parameter, sondern auch Komponenten können persistent sein. Bei einer solchen Komponente werden ihre persistenten Parameter auch zwischen verschiedenen Aktionen des Presenters oder zwischen mehreren Presentern übertragen. Persistente Komponenten kennzeichnen wir mit einer Annotation in der Presenter-Klasse. So kennzeichnen wir beispielsweise die Komponenten `calendar` und `poll`: ```php /** @@ -278,9 +300,9 @@ class DefaultPresenter extends Nette\Application\UI\Presenter } ``` -Sie müssen die Unterkomponenten nicht als persistent kennzeichnen, sie sind automatisch persistent. +Sub-Komponenten innerhalb dieser Komponenten müssen nicht gekennzeichnet werden, sie werden ebenfalls persistent. -In PHP 8 können Sie auch Attribute verwenden, um persistente Komponenten zu kennzeichnen: +In PHP 8 können Sie zur Kennzeichnung persistenter Komponenten auch Attribute verwenden: ```php use Nette\Application\Attributes\Persistent; @@ -292,18 +314,18 @@ class DefaultPresenter extends Nette\Application\UI\Presenter ``` -Komponenten mit Abhängigkeiten .[#toc-components-with-dependencies] -=================================================================== +Komponenten mit Abhängigkeiten +============================== -Wie kann man Komponenten mit Abhängigkeiten erstellen, ohne die Präsentatoren, die sie verwenden werden, zu "versauen"? Dank der cleveren Funktionen des DI-Containers in Nette können wir, wie bei der Verwendung herkömmlicher Dienste, den Großteil der Arbeit dem Framework überlassen. +Wie erstellt man Komponenten mit Abhängigkeiten, ohne die Presenter, die sie verwenden, zu „verschmutzen“? Dank der intelligenten Eigenschaften des DI-Containers in Nette kann man, wie bei der Verwendung klassischer Dienste, den größten Teil der Arbeit dem Framework überlassen. -Nehmen wir als Beispiel eine Komponente, die von dem Dienst `PollFacade` abhängig ist: +Nehmen wir als Beispiel eine Komponente, die eine Abhängigkeit vom Dienst `PollFacade` hat: ```php class PollControl extends Control { public function __construct( - private int $id, // Id einer Umfrage, für die die Komponente erstellt wird + private int $id, // ID der Umfrage, für die wir die Komponente erstellen private PollFacade $facade, ) { } @@ -316,11 +338,11 @@ class PollControl extends Control } ``` -Wenn wir einen klassischen Dienst schreiben würden, gäbe es nichts zu befürchten. Der DI-Container würde sich unsichtbar um die Übergabe aller Abhängigkeiten kümmern. Aber wir handhaben die Komponenten normalerweise so, dass wir eine neue Instanz von ihnen direkt im Presenter in [Factory-Methoden |#factory methods] `createComponent...()` erstellen. Aber die Übergabe aller Abhängigkeiten aller Komponenten an den Presenter, um sie dann an die Komponenten weiterzugeben, ist umständlich. Und die Menge des geschriebenen Codes... +Wenn wir einen klassischen Dienst schreiben würden, gäbe es nichts zu lösen. Der DI-Container würde sich unsichtbar um die Übergabe aller Abhängigkeiten kümmern. Aber mit Komponenten gehen wir normalerweise so um, dass wir ihre neue Instanz direkt im Presenter in den [#Factory-Methoden] `createComponent…()` erstellen. Aber alle Abhängigkeiten aller Komponenten an den Presenter zu übergeben, nur um sie dann an die Komponenten weiterzugeben, ist umständlich. Und der viele geschriebene Code… -Die logische Frage ist, warum registrieren wir die Komponente nicht einfach als klassischen Dienst, übergeben sie an den Präsentator und geben sie dann in der Methode `createComponent...()` zurück? Dieser Ansatz ist jedoch ungeeignet, da wir die Komponente mehrfach erstellen können möchten. +Die logische Frage ist, warum registrieren wir die Komponente nicht einfach als klassischen Dienst, übergeben sie an den Presenter und geben sie dann in der Methode `createComponent…()` zurück? Dieser Ansatz ist jedoch ungeeignet, da wir die Komponente möglicherweise mehrmals erstellen möchten. -Die richtige Lösung besteht darin, eine Fabrik für die Komponente zu schreiben, d. h. eine Klasse, die die Komponente für uns erstellt: +Die richtige Lösung ist, eine Factory für die Komponente zu schreiben, also eine Klasse, die uns die Komponente erstellt: ```php class PollControlFactory @@ -337,17 +359,17 @@ class PollControlFactory } ``` -Jetzt registrieren wir unseren Dienst am DI-Container zur Konfiguration: +Diese Factory registrieren wir in unserem Container in der Konfiguration: ```neon services: - PollControlFactory ``` -Schließlich werden wir diese Fabrik in unserem Präsentator verwenden: +und schließlich verwenden wir sie in unserem Presenter: ```php -class PollPresenter extends Nette\UI\Application\Presenter +class PollPresenter extends Nette\Application\UI\Presenter { public function __construct( private PollControlFactory $pollControlFactory, @@ -362,7 +384,7 @@ class PollPresenter extends Nette\UI\Application\Presenter } ``` -Das Tolle daran ist, dass Nette DI solche einfachen Fabriken [generieren |dependency-injection:factory] kann, so dass man nicht den ganzen Code schreiben muss, sondern nur die Schnittstelle: +Das Tolle ist, dass Nette DI solche einfachen Factories [generieren |dependency-injection:factory] kann, sodass statt des gesamten Codes nur ihr Interface geschrieben werden muss: ```php interface PollControlFactory @@ -371,21 +393,21 @@ interface PollControlFactory } ``` -Das ist alles. Nette implementiert diese Schnittstelle intern und injiziert sie in unseren Präsentator, wo wir sie verwenden können. Außerdem werden auf magische Weise unser Parameter `$id` und die Instanz der Klasse `PollFacade` an unsere Komponente übergeben. +Und das ist alles. Nette implementiert dieses Interface intern und übergibt es an den Presenter, wo wir es bereits verwenden können. Es fügt magischerweise auch den Parameter `$id` und eine Instanz der Klasse `PollFacade` zu unserer Komponente hinzu. -Komponenten im Detail .[#toc-components-in-depth] -================================================= +Komponenten im Detail +===================== -Komponenten in einer Nette-Anwendung sind die wiederverwendbaren Teile einer Web-Anwendung, die wir in Seiten einbetten, was das Thema dieses Kapitels ist. Was genau sind die Fähigkeiten einer solchen Komponente? +Komponenten in Nette Application stellen wiederverwendbare Teile einer Webanwendung dar, die wir in Seiten einfügen und denen dieses ganze Kapitel gewidmet ist. Welche Fähigkeiten hat eine solche Komponente genau? -1) sie ist in einer Vorlage renderbar -2) sie weiß, welcher Teil von ihr bei einer [AJAX-Anfrage |ajax#invalidation] gerendert werden soll (Snippets) -3) er kann seinen Zustand in einer URL speichern (persistente Parameter) -4) es hat die Fähigkeit, auf Benutzeraktionen zu reagieren (Signale) -5) er erstellt eine hierarchische Struktur (wobei die Wurzel der Präsentator ist) +1) Sie ist im Template renderbar. +2) Sie weiß, [welchen Teil |ajax#Snippets] sie bei einer AJAX-Anfrage rendern soll (Snippets). +3) Sie hat die Fähigkeit, ihren Zustand in der URL zu speichern (persistente Parameter). +4) Sie hat die Fähigkeit, auf Benutzeraktionen zu reagieren (Signale). +5) Sie bildet eine hierarchische Struktur (deren Wurzel der Presenter ist). -Jede dieser Funktionen wird von einer der Klassen der Vererbungslinie ausgeführt. Die Darstellung (1 + 2) wird von [api:Nette\Application\UI\Control] übernommen, die Einbindung in den [Lebenszyklus |presenters#life-cycle-of-presenter] (3, 4) von der Klasse [api:Nette\Application\UI\Component] und die Erstellung der hierarchischen Struktur (5) von den Klassen [Container und Component |component-model:]. +Jede dieser Funktionen wird von einer der Klassen der Vererbungslinie übernommen. Das Rendern (1 + 2) übernimmt [api:Nette\Application\UI\Control], die Einbindung in den [Lebenszyklus |presenters#Lebenszyklus des Presenters] (3, 4) die Klasse [api:Nette\Application\UI\Component] und die Erstellung der hierarchischen Struktur (5) die Klassen [Container und Component |component-model:]. ``` Nette\ComponentModel\Component { IComponent } @@ -400,18 +422,18 @@ Nette\ComponentModel\Component { IComponent } ``` -Lebenszyklus der Komponente .[#toc-life-cycle-of-component] ------------------------------------------------------------ +Lebenszyklus einer Komponente +----------------------------- [* lifecycle-component.svg *] *** *Lebenszyklus einer Komponente* .<> -Validierung von persistenten Parametern .[#toc-validation-of-persistent-parameters] ------------------------------------------------------------------------------------ +Validierung persistenter Parameter +---------------------------------- -Die Werte von [persistenten Parametern |#persistent parameters], die von URLs empfangen werden, werden von der Methode `loadState()` in Eigenschaften geschrieben. Sie prüft auch, ob der für die Eigenschaft angegebene Datentyp übereinstimmt, andernfalls antwortet sie mit einem 404-Fehler und die Seite wird nicht angezeigt. +Die Werte der [persistenten Parameter |#Persistente Parameter], die aus der URL empfangen wurden, schreibt die Methode `loadState()` in die Eigenschaften. Sie prüft auch, ob der bei der Eigenschaft angegebene Datentyp übereinstimmt, andernfalls antwortet sie mit einem 404-Fehler und die Seite wird nicht angezeigt. -Verlassen Sie sich niemals blind auf persistente Parameter, da sie leicht vom Benutzer in der URL überschrieben werden können. So prüfen wir zum Beispiel, ob die Seitenzahl `$this->page` größer als 0 ist. Eine gute Möglichkeit, dies zu tun, ist, die oben erwähnte Methode `loadState()` zu überschreiben: +Vertrauen Sie niemals blind persistenten Parametern, da sie vom Benutzer leicht in der URL überschrieben werden können. So überprüfen wir beispielsweise, ob die Seitenzahl `$this->page` größer als 0 ist. Ein geeigneter Weg ist, die erwähnte Methode `loadState()` zu überschreiben: ```php class PaginatingControl extends Control @@ -421,8 +443,8 @@ class PaginatingControl extends Control public function loadState(array $params): void { - parent::loadState($params); // hier wird die $this->page gesetzt - // auf die Prüfung der Benutzerwerte: + parent::loadState($params); // hier wird $this->page gesetzt + // es folgt die eigene Wertprüfung: if ($this->page < 1) { $this->error(); } @@ -430,27 +452,27 @@ class PaginatingControl extends Control } ``` -Der umgekehrte Prozess, d. h. das Sammeln von Werten aus dauerhaften Propertys, wird von der Methode `saveState()` übernommen. +Der umgekehrte Prozess, also das Sammeln von Werten aus persistenten Eigenschaften, wird von der Methode `saveState()` übernommen. -Signale in der Tiefe .[#toc-signals-in-depth] ---------------------------------------------- +Signale im Detail +----------------- -Ein Signal bewirkt ein Neuladen der Seite wie die ursprüngliche Anfrage (mit Ausnahme von AJAX) und ruft die Methode `signalReceived($signal)` auf, deren Standardimplementierung in der Klasse `Nette\Application\UI\Component` versucht, eine Methode aufzurufen, die aus den Worten `handle{Signal}` besteht. Die weitere Verarbeitung hängt von dem angegebenen Objekt ab. Objekte, die Nachkommen von `Component` sind (d.h. `Control` und `Presenter`), versuchen, `handle{Signal}` mit den entsprechenden Parametern aufzurufen. +Ein Signal bewirkt das Neuladen der Seite genau wie bei der ursprünglichen Anfrage (außer wenn es per AJAX aufgerufen wird) und ruft die Methode `signalReceived($signal)` auf, deren Standardimplementierung in der Klasse `Nette\Application\UI\Component` versucht, eine Methode aufzurufen, die aus den Wörtern `handle{signal}` zusammengesetzt ist. Die weitere Verarbeitung liegt beim jeweiligen Objekt. Objekte, die von `Component` erben (d. h. `Control` und `Presenter`), reagieren, indem sie versuchen, die Methode `handle{signal}` mit den entsprechenden Parametern aufzurufen. -Mit anderen Worten: die Definition der Methode `handle{Signal}` wird genommen und alle Parameter, die in der Anfrage empfangen wurden, werden mit den Parametern der Methode abgeglichen. Das bedeutet, dass der Parameter `id` aus der URL mit dem Parameter `$id` der Methode abgeglichen wird, `something` mit `$something` und so weiter. Und wenn die Methode nicht existiert, löst die Methode `signalReceived` [eine Ausnahme |api:Nette\Application\UI\BadSignalException] aus. +Mit anderen Worten: Es wird die Definition der Funktion `handle{signal}` und alle mit der Anfrage übermittelten Parameter genommen, und den Argumenten werden anhand des Namens Parameter aus der URL zugewiesen, und es wird versucht, die betreffende Methode aufzurufen. Z. B. wird als Parameter `$id` der Wert des Parameters `id` aus der URL übergeben, als `$something` wird `something` aus der URL übergeben, usw. Und wenn die Methode nicht existiert, löst die Methode `signalReceived` eine [Ausnahme |api:Nette\Application\UI\BadSignalException] aus. -Das Signal kann von jeder Komponente empfangen werden, die die Schnittstelle `SignalReceiver` implementiert, wenn sie mit dem Komponentenbaum verbunden ist. +Ein Signal kann von jeder Komponente, jedem Presenter oder jedem Objekt empfangen werden, das das Interface `SignalReceiver` implementiert und in den Komponentenbaum eingebunden ist. -Die Hauptempfänger von Signalen sind `Presenters` und visuelle Komponenten, die `Control` erweitern. Ein Signal ist ein Zeichen für ein Objekt, dass es etwas zu tun hat - eine Umfrage zählt eine Stimme vom Benutzer, eine Box mit Nachrichten muss sich entfalten, ein Formular wurde gesendet und muss Daten verarbeiten und so weiter. +Die Hauptempfänger von Signalen sind `Presenter` und visuelle Komponenten, die von `Control` erben. Ein Signal soll als Zeichen für ein Objekt dienen, dass es etwas tun soll – die Umfrage soll eine Stimme vom Benutzer zählen, der Nachrichtenblock soll sich aufklappen und doppelt so viele Nachrichten anzeigen, das Formular wurde abgeschickt und soll die Daten verarbeiten und so weiter. -Die URL für das Signal wird mit der Methode [Component::link() |api:Nette\Application\UI\Component::link()] erstellt. Als Parameter `$destination` übergeben wir den String `{signal}!` und als `$args` ein Array von Argumenten, die wir an den Signalhandler übergeben wollen. Die Signalparameter werden an die URL des aktuellen Presenters/Views angehängt. **Der Parameter `?do` in der URL bestimmt das aufgerufene Signal. +Die URL für ein Signal erstellen wir mit der Methode [Component::link() |api:Nette\Application\UI\Component::link()]. Als Parameter `$destination` übergeben wir den String `{signal}!` und als `$args` ein Array von Argumenten, die wir dem Signal übergeben möchten. Das Signal wird immer im aktuellen Presenter und der aktuellen Action mit den aktuellen Parametern aufgerufen, die Signalparameter werden nur hinzugefügt. Zusätzlich wird ganz am Anfang der **Parameter `?do`, der das Signal bestimmt**, hinzugefügt. -Sein Format ist `{signal}` oder `{signalReceiver}-{signal}`. `{signalReceiver}` ist der Name der Komponente im Presenter. Aus diesem Grund kann der Bindestrich nicht im Namen der Komponente vorkommen - er wird verwendet, um den Namen der Komponente und des Signals zu trennen, aber es ist möglich, mehrere Komponenten zusammenzustellen. +Sein Format ist entweder `{signal}` oder `{signalReceiver}-{signal}`. `{signalReceiver}` ist der Name der Komponente im Presenter. Deshalb darf im Komponentennamen kein Bindestrich vorkommen – er wird zur Trennung von Komponentennamen und Signal verwendet, es ist jedoch möglich, mehrere Komponenten auf diese Weise zu verschachteln. -Die Methode [isSignalReceiver() |api:Nette\Application\UI\Presenter::isSignalReceiver()] prüft, ob eine Komponente (erstes Argument) ein Empfänger eines Signals (zweites Argument) ist. Das zweite Argument kann weggelassen werden - dann wird festgestellt, ob die Komponente ein Empfänger eines Signals ist. Wenn der zweite Parameter `true` lautet, wird geprüft, ob die Komponente oder ihre Nachkommen Empfänger eines Signals sind. +Die Methode [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] prüft, ob eine Komponente (erstes Argument) der Empfänger eines Signals ist (zweites Argument). Das zweite Argument kann weggelassen werden – dann wird geprüft, ob die Komponente Empfänger irgendeines Signals ist. Als zweites Parameter kann `true` angegeben werden, um zu prüfen, ob nicht nur die angegebene Komponente, sondern auch einer ihrer Nachfahren Empfänger ist. -In jeder Phase, die `handle{Signal}` vorausgeht, kann ein Signal manuell ausgeführt werden, indem die Methode [processSignal() |api:Nette\Application\UI\Presenter::processSignal()] aufgerufen wird, die die Verantwortung für die Signalausführung übernimmt. Nimmt die Empfängerkomponente (wenn nicht gesetzt, ist es der Präsentator selbst) und sendet ihr das Signal. +In jeder Phase vor `handle{signal}` können wir das Signal manuell ausführen, indem wir die Methode [processSignal()|api:Nette\Application\UI\Presenter::processSignal()] aufrufen, die die Bearbeitung des Signals übernimmt – sie nimmt die Komponente, die als Signalempfänger bestimmt wurde (wenn kein Signalempfänger bestimmt ist, ist es der Presenter selbst) und sendet ihr das Signal. Beispiel: @@ -460,4 +482,4 @@ if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, ' } ``` -Das Signal wird vorzeitig ausgeführt und wird nicht mehr aufgerufen. +Damit wird das Signal vorzeitig ausgeführt und nicht mehr erneut aufgerufen. diff --git a/application/de/configuration.texy b/application/de/configuration.texy index aa8d370e08..638fba748b 100644 --- a/application/de/configuration.texy +++ b/application/de/configuration.texy @@ -1,58 +1,71 @@ -Anwendung konfigurieren -*********************** +Konfiguration von Anwendungen +***************************** .[perex] -Überblick über die Konfigurationsoptionen für die Nette-Anwendung. +Übersicht über die Konfigurationsoptionen für Nette-Anwendungen. -Anwendung .[#toc-application] -============================= +Application +=========== ```neon -Anwendung: - # zeigt das Feld "Nette Anwendung" in Tracy BlueScreen? - debugger: ... # (bool) standardmäßig true +application: + # Das "Nette Application"-Panel im Tracy BlueScreen anzeigen? + debugger: ... # (bool) Standard ist true + + # Soll bei einem Fehler der Error-Presenter aufgerufen werden? + # wirkt sich nur im Entwicklungsmodus aus + catchExceptions: ... # (bool) Standard ist true - # wird der error-presenter im Fehlerfall aufgerufen? - catchExceptions: ... # (bool) steht im Produktionsmodus standardmäßig auf true + # Name des Error-Presenters + errorPresenter: Error # (string|array) Standard ist 'Nette:Error' - # Name des Fehlermelders - errorPresenter: Error # (string) Standardwert ist 'Nette:Error' + # definiert Aliase für Presenter und Aktionen + aliases: ... - # definiert die Regeln für die Auflösung des Presenter-Namens in eine Klasse + # definiert Regeln für die Übersetzung des Presenter-Namens in eine Klasse mapping: ... - # werden bei schlechten Links Warnungen erzeugt? - # hat nur im Entwicklermodus Auswirkungen - silentLinks: ... # (bool) ist standardmäßig auf false eingestellt + # Erzeugen fehlerhafte Links keine Warnungen? + # wirkt sich nur im Entwicklungsmodus aus + silentLinks: ... # (bool) Standard ist false +``` + +Seit `nette/application` Version 3.2 kann ein Paar von Error-Presentern definiert werden: + +```neon +application: + errorPresenter: + 4xx: Error4xx # für die Ausnahme Nette\Application\BadRequestException + 5xx: Error5xx # für andere Ausnahmen ``` -Da Fehlerpräsenter im Entwicklungsmodus standardmäßig nicht aufgerufen werden und die Fehler von Tracy angezeigt werden, hilft das Ändern des Wertes `catchExceptions` in `true` dabei, zu überprüfen, ob die Fehlerpräsenter während der Entwicklung korrekt funktionieren. +Die Option `silentLinks` bestimmt, wie sich Nette im Entwicklungsmodus verhält, wenn die Linkgenerierung fehlschlägt (z. B. weil der Presenter nicht existiert usw.). Der Standardwert `false` bedeutet, dass Nette einen `E_USER_WARNING`-Fehler auslöst. Durch Setzen auf `true` wird diese Fehlermeldung unterdrückt. In der Produktionsumgebung wird `E_USER_WARNING` immer ausgelöst. Dieses Verhalten kann auch durch Setzen der Presenter-Variable [$invalidLinkMode |creating-links#Ungültige Links] beeinflusst werden. -Die Option `silentLinks` legt fest, wie sich Nette im Entwicklermodus verhält, wenn die Link-Generierung fehlschlägt (z. B. weil kein Presenter vorhanden ist usw.). Der Standardwert `false` bedeutet, dass Nette `E_USER_WARNING` auslöst. Die Einstellung `true` unterdrückt diese Fehlermeldung. In einer Produktionsumgebung wird immer `E_USER_WARNING` aufgerufen. Wir können dieses Verhalten auch beeinflussen, indem wir die Presenter-Variable [$invalidLinkMode |creating-links#Invalid Links] setzen. +[Aliase vereinfachen das Verlinken |creating-links#Aliase] zu häufig verwendeten Presentern. -Das [Mapping definiert die Regeln |modules#mapping], nach denen der Klassenname aus dem Presenter-Namen abgeleitet wird. +[Mapping definiert Regeln |directory-structure#Presenter-Mapping], nach denen der Klassenname vom Presenter-Namen abgeleitet wird. -Automatische Registrierung von Präsentatoren .[#toc-automatic-registration-of-presenters] ------------------------------------------------------------------------------------------ +Automatische Registrierung von Presentern +----------------------------------------- -Nette fügt Presenter automatisch als Dienste zum DI-Container hinzu, was ihre Erstellung erheblich beschleunigt. Wie Nette Presenter findet, kann konfiguriert werden: +Nette fügt Presenter automatisch als Dienste zum DI-Container hinzu, was deren Erstellung erheblich beschleunigt. Wie Nette Presenter findet, kann konfiguriert werden: ```neon -Anwendung: - # um nach Präsentatoren in der Composer-Klassenkarte zu suchen? - scanComposer: ... # (bool) standardmäßig auf true +application: + # Presenter in der Composer Class Map suchen? + scanComposer: ... # (bool) Standard ist true - # eine Maske, die mit der Klasse und dem Dateinamen übereinstimmen muss - scanFilter: ... # (string) Standardwert ist '*Presenter'. + # Maske, der Klassen- und Dateiname entsprechen müssen + scanFilter: ... # (string) Standard ist '*Presenter' - # in welchen Verzeichnissen soll nach Präsentatoren gesucht werden? - scanDirs: # (string[]|false) Standardwert ist '%appDir%' + # In welchen Verzeichnissen sollen Presenter gesucht werden? + scanDirs: # (string[]|false) Standard ist '%appDir%' - %vendorDir%/mymodule ``` -Die in `scanDirs` aufgeführten Verzeichnisse überschreiben nicht den Standardwert `%appDir%`, sondern ergänzen ihn, so dass `scanDirs` die beiden Pfade `%appDir%` und `%vendorDir%/mymodule` enthält. Wenn wir das Standardverzeichnis überschreiben wollen, verwenden wir ein [Ausrufezeichen |dependency-injection:configuration#Merging]: +Die in `scanDirs` angegebenen Verzeichnisse überschreiben nicht den Standardwert `%appDir%`, sondern ergänzen ihn, sodass `scanDirs` beide Pfade `%appDir%` und `%vendorDir%/mymodule` enthält. Wenn wir das Standardverzeichnis auslassen möchten, verwenden wir ein [Ausrufezeichen |dependency-injection:configuration#Zusammenführen], das den Wert überschreibt: ```neon application: @@ -60,64 +73,73 @@ application: - %vendorDir%/mymodule ``` -Das Scannen von Verzeichnissen kann durch die Einstellung false ausgeschaltet werden. Es wird nicht empfohlen, das automatische Hinzufügen von Präsentatoren vollständig zu unterdrücken, da sonst die Leistung der Anwendung beeinträchtigt wird. +Das Scannen von Verzeichnissen kann durch Angabe des Wertes false deaktiviert werden. Wir empfehlen nicht, das automatische Hinzufügen von Presentern vollständig zu unterdrücken, da dies die Leistung der Anwendung beeinträchtigen würde. -Latte .[#toc-latte] -=================== +Latte-Templates +=============== -Diese Einstellung wirkt sich global auf das Verhalten von Latte in Komponenten und Presentern aus. +Mit dieser Einstellung kann das Verhalten von Latte in Komponenten und Presentern global beeinflusst werden. ```neon -Latte: - # zeigt das Latte-Panel in der Tracy Bar für die Hauptvorlage (true) oder für alle Komponenten (all)? - debugger: ... # (true|false|'all') ist standardmäßig true +latte: + # Latte-Panel in der Tracy Bar für das Haupttemplate (true) oder alle Komponenten (all) anzeigen? + debugger: ... # (true|false|'all') Standard ist true + + # generiert Templates mit dem Header declare(strict_types=1) + strictTypes: ... # (bool) Standard ist false + + # schaltet den [strengen Parser-Modus |latte:develop#strict-mode] ein + strictParsing: ... # (bool) Standard ist false + + # aktiviert die [Überprüfung des generierten Codes |latte:develop#checking-generated-code] + phpLinter: ... # (string) Standard ist null - # erzeugt Vorlagen mit declare(strict_types=1) - strictTypes: ... # (bool) ist standardmäßig false + # setzt die Locale + locale: cs_CZ # (string) Standard ist null - # Klasse von $this->template - templateClass: App\MyTemplateClass # Standardwert ist Nette\Bridges\ApplicationLatte\DefaultTemplate + # Klasse des $this->template-Objekts + templateClass: App\MyTemplateClass # Standard ist Nette\Bridges\ApplicationLatte\DefaultTemplate ``` -Wenn Sie Latte Version 3 verwenden, können Sie neue [Erweiterungen |latte:creating-extension] mit hinzufügen: +Wenn Sie Latte Version 3 verwenden, können Sie neue [Erweiterungen |latte:extending-latte#Latte Extension] hinzufügen mit: ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -/--comment - - - +Wenn Sie Latte Version 2 verwenden, können Sie neue Tags (Makros) registrieren, indem Sie entweder den Klassennamen oder eine Referenz auf einen Dienst angeben. Standardmäßig wird die Methode `install()` aufgerufen, dies kann jedoch geändert werden, indem ein anderer Methodenname angegeben wird: +```neon +latte: + # Registrierung benutzerdefinierter Latte-Tags + macros: + - App\MyLatteMacros::register # statische Methode, Klassenname oder Callable + - @App\MyLatteMacrosFactory # Dienst mit install()-Methode + - @App\MyLatteMacrosFactory::register # Dienst mit register()-Methode + +services: + - App\MyLatteMacrosFactory +``` - - - - - -\-- - - -Routing .[#toc-routing] -======================= +Routing +======= Grundeinstellungen: ```neon routing: - # zeigt Routing-Panel in Tracy Bar? - debugger: ... # (bool) standardmäßig true + # Routing-Panel in der Tracy Bar anzeigen? + debugger: ... # (bool) Standard ist true - # Router in DI-Container serialisieren? - cache: ... # (bool) Standardwert ist false + # serialisiert den Router in den DI-Container + cache: ... # (bool) Standard ist false ``` -Router werden normalerweise in der Klasse [RouterFactory |routing#Route Collection] definiert. Alternativ können Router auch in der Konfiguration mit `mask: action` Paaren definiert werden, aber diese Methode bietet keine so große Variationsbreite an Einstellungen: +Das Routing wird normalerweise in der Klasse [RouterFactory |routing#Routen-Sammlung] definiert. Alternativ können Routen auch in der Konfiguration über Paare `Maske: Aktion` definiert werden, aber diese Methode bietet nicht so viel Flexibilität bei den Einstellungen: ```neon routing: @@ -127,8 +149,8 @@ routing: ``` -Konstanten .[#toc-constants] -============================ +Konstanten +========== Erstellen von PHP-Konstanten. @@ -137,18 +159,33 @@ constants: Foobar: 'baz' ``` -Die Konstante `Foobar` wird nach dem Start der Anwendung erstellt. +Nach dem Start der Anwendung wird die Konstante `Foobar` erstellt. .[note] -Konstanten sollten nicht als global verfügbare Variablen dienen. Um Werte an Objekte zu übergeben, verwenden Sie [Dependency Injection |dependency-injection:passing-dependencies]. +Konstanten sollten nicht als global verfügbare Variablen dienen. Verwenden Sie [Dependency Injection |dependency-injection:passing-dependencies], um Werte an Objekte zu übergeben. PHP === -Sie können PHP-Direktiven setzen. Eine Übersicht über alle Direktiven finden Sie auf [php.net |https://www.php.net/manual/en/ini.list.php]. +Einstellen von PHP-Direktiven. Eine Übersicht über alle Direktiven finden Sie auf [php.net |https://www.php.net/manual/en/ini.list.php]. ```neon php: date.timezone: Europe/Prague ``` + + +DI-Dienste +========== + +Diese Dienste werden dem DI-Container hinzugefügt: + +| Name | Typ | Beschreibung +|---------------------------------------------------------------------------------------------------------- +| `application.application` | [api:Nette\Application\Application] | [Starter der gesamten Anwendung |how-it-works#Nette Application] +| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] +| `application.presenterFactory` | [api:Nette\Application\PresenterFactory] | Factory für Presenter +| `application.###` | [api:Nette\Application\UI\Presenter] | einzelne Presenter +| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | Factory für das `Latte\Engine`-Objekt +| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | Factory für [`$this->template` |templates] diff --git a/application/de/creating-links.texy b/application/de/creating-links.texy index 1ec3fbd1e9..ff63ee2419 100644 --- a/application/de/creating-links.texy +++ b/application/de/creating-links.texy @@ -1,160 +1,160 @@ -URL-Links erstellen -******************* +Erstellen von URL-Links +***********************
    -Das Erstellen von Links in Nette ist so einfach wie ein Fingerzeig. Zeigen Sie einfach darauf und das Framework erledigt die ganze Arbeit für Sie. Wir werden es zeigen: +Das Erstellen von Links in Nette ist so einfach wie mit dem Finger zu zeigen. Sie müssen nur zielen, und das Framework erledigt die ganze Arbeit für Sie. Wir zeigen Ihnen: -- wie man Links in Vorlagen und anderswo erstellt -- wie man einen Link von der aktuellen Seite unterscheidet -- was mit ungültigen Links geschieht +- wie man Links in Templates und anderswo erstellt +- wie man einen Link zur aktuellen Seite unterscheidet +- was mit ungültigen Links zu tun ist
    -Dank des [bidirektionalen Routings |routing] müssen Sie die URLs der Anwendung nicht mehr in den Vorlagen oder im Code fest codieren, was sich später ändern oder kompliziert sein kann. Geben Sie einfach den Präsentator und die Aktion im Link an, übergeben Sie beliebige Parameter und das Framework generiert die URL selbst. Tatsächlich ist es dem Aufruf einer Funktion sehr ähnlich. Sie werden es mögen. +Dank [bidirektionalem Routing |routing] müssen Sie niemals fest codierte URL-Adressen Ihrer Anwendung in Templates oder Code schreiben, die sich später ändern könnten, oder sie kompliziert zusammensetzen. Im Link genügt es, den Presenter und die Aktion anzugeben, eventuelle Parameter zu übergeben, und das Framework generiert die URL selbst. Eigentlich ist es sehr ähnlich wie der Aufruf einer Funktion. Das wird Ihnen gefallen. -In der Präsentatorvorlage .[#toc-in-the-presenter-template] -=========================================================== +Im Presenter-Template +===================== -Am häufigsten erstellen wir Links in Vorlagen und ein großer Helfer ist das Attribut `n:href`: +Am häufigsten erstellen wir Links in Templates, und ein großartiger Helfer ist das Attribut `n:href`: ```latte -detail +Detail ``` -Beachten Sie, dass wir anstelle des HTML-Attributs `href` das [n:attribute |latte:syntax#n:attributes] `n:href` verwendet haben. Sein Wert ist keine URL, wie Sie es von dem Attribut `href` gewohnt sind, sondern der Name des Präsentators und der Aktion. +Beachten Sie, dass wir anstelle des HTML-Attributs `href` das [n:Attribut |latte:syntax#n:Attribute] `n:href` verwendet haben. Sein Wert ist dann nicht eine URL, wie es beim `href`-Attribut der Fall wäre, sondern der Name des Presenters und der Aktion. -Ein Klick auf einen Link ist, einfach gesagt, so etwas wie der Aufruf einer Methode `ProductPresenter::renderShow()`. Und wenn sie Parameter in ihrer Signatur hat, können wir sie mit Argumenten aufrufen: +Ein Klick auf den Link ist, vereinfacht gesagt, so etwas wie der Aufruf der Methode `ProductPresenter::renderShow()`. Und wenn diese Parameter in ihrer Signatur hat, können wir sie mit Argumenten aufrufen: ```latte -detail +Produktdetail ``` -Es ist auch möglich, benannte Parameter zu übergeben. Der folgende Link übergibt den Parameter `lang` mit dem Wert `en`: +Es ist auch möglich, benannte Parameter zu übergeben. Der folgende Link übergibt den Parameter `lang` mit dem Wert `cs`: ```latte -detail +Produktdetail ``` -Wenn die Methode `ProductPresenter::renderShow()` nicht `$lang` in ihrer Signatur hat, kann sie den Wert des Parameters mit `$lang = $this->getParameter('lang')` lesen. +Wenn die Methode `ProductPresenter::renderShow()` `$lang` nicht in ihrer Signatur hat, kann sie den Wert des Parameters mit `$lang = $this->getParameter('lang')` oder aus der [Property |presenters#Anfrageparameter] ermitteln. -Wenn die Parameter in einem Array gespeichert sind, können sie mit dem `...` -Operator (oder `(expand)` -Operator in Latte 2.x) expandiert werden: +Wenn die Parameter in einem Array gespeichert sind, können sie mit dem Operator `...` (in Latte 2.x mit dem Operator `(expand)`) erweitert werden: ```latte -{var $args = [$product->id, lang => en]} -detail +{var $args = [$product->id, lang => cs]} +Produktdetail ``` -Die so genannten [persistenten Parameter |presenters#persistent parameters] werden ebenfalls automatisch in den Links übergeben. +In Links werden auch automatisch sogenannte [persistente Parameter |presenters#Persistente Parameter] übergeben. -Das Attribut `n:href` ist sehr praktisch für HTML-Tags ``. Wenn wir den Link an anderer Stelle, zum Beispiel im Text, ausgeben wollen, verwenden wir `{link}`: +Das Attribut `n:href` ist sehr praktisch für HTML-Tags ``. Wenn wir einen Link an anderer Stelle ausgeben möchten, zum Beispiel im Text, verwenden wir `{link}`: ```latte -URL is: {link Home:default} +Die Adresse lautet: {link Home:default} ``` -Im Code .[#toc-in-the-code] -=========================== +Im Code +======= -Die Methode `link()` wird verwendet, um einen Link im Presenter zu erstellen: +Zum Erstellen eines Links im Presenter dient die Methode `link()`: ```php $url = $this->link('Product:show', $product->id); ``` -Parameter können auch als Array übergeben werden, wobei auch benannte Parameter angegeben werden können: +Parameter können auch über ein Array übergeben werden, wo auch benannte Parameter angegeben werden können: ```php $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); ``` -Links können auch ohne Presenter erstellt werden, indem der [LinkGenerator |#LinkGenerator] und seine Methode `link()` verwendet werden. +Links können auch ohne Presenter erstellt werden, dafür gibt es den [#LinkGenerator] und seine Methode `link()`. -Links zum Presenter .[#toc-links-to-presenter] -============================================== +Links zu Presentern +=================== -Wenn das Ziel des Links Presenter und Action ist, hat er diese Syntax: +Wenn das Ziel des Links ein Presenter und eine Aktion ist, hat er diese Syntax: ``` [//] [[[[:]module:]presenter:]action | this] [#fragment] ``` -Das Format wird von allen Latte-Tags und allen Presenter-Methoden unterstützt, die mit Links arbeiten, also `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` und auch [LinkGenerator |#LinkGenerator]. Also auch wenn `n:href` in den Beispielen verwendet wird, könnte es jede der Funktionen sein. +Das Format wird von allen Latte-Tags und allen Presenter-Methoden unterstützt, die mit Links arbeiten, also `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` und auch dem [#LinkGenerator]. Auch wenn in den Beispielen `n:href` verwendet wird, könnte dort jede dieser Funktionen stehen. Die Grundform ist also `Presenter:action`: ```latte -home +Startseite ``` -Wenn wir auf die Aktion des aktuellen Moderators verweisen, können wir seinen Namen weglassen: +Wenn wir auf eine Aktion des aktuellen Presenters verlinken, können wir seinen Namen weglassen: ```latte -home +Startseite ``` -Wenn die Aktion `default` lautet, können wir sie weglassen, aber der Doppelpunkt muss bleiben: +Wenn das Ziel die Aktion `default` ist, können wir sie weglassen, aber der Doppelpunkt muss bleiben: ```latte -home +Startseite ``` -Links können auch auf andere [Module |modules] verweisen. Hier unterscheidet man zwischen relativen und absoluten Links zu den Untermodulen. Das Prinzip ist analog zu Plattenpfaden, nur dass anstelle von Schrägstrichen Doppelpunkte stehen. Nehmen wir an, dass der eigentliche Präsentator Teil des Moduls `Front` ist, dann schreiben wir: +Links können auch zu anderen [Modulen |directory-structure#Presenter und Templates] führen. Hier werden Links in relative zu verschachtelten Submodulen oder absolute unterschieden. Das Prinzip ist analog zu Pfaden auf der Festplatte, nur dass anstelle von Schrägstrichen Doppelpunkte verwendet werden. Angenommen, der aktuelle Presenter ist Teil des Moduls `Front`, dann schreiben wir: ```latte -link to Front:Shop:Product:show -link to Admin:Product:show +Link zu Front:Shop:Product:show +Link zu Admin:Product:show ``` -Ein Sonderfall ist die [Verknüpfung mit sich selbst |#Links to Current Page]. Hier schreiben wir `this` als Ziel. +Ein Sonderfall ist der Link [auf sich selbst |#Link zur aktuellen Seite], bei dem wir als Ziel `this` angeben. ```latte -refresh +aktualisieren ``` -Wir können auf einen bestimmten Teil der HTML-Seite über ein so genanntes Fragment nach dem Rautezeichen `#` verlinken: +Wir können auf einen bestimmten Teil der Seite über das sogenannte Fragment nach dem Rautezeichen `#` verlinken: ```latte -link to Home:default and fragment #main +Link zu Home:default und Fragment #main ``` -Absolute Pfade .[#toc-absolute-paths] -===================================== +Absolute Pfade +============== -Die von `link()` oder `n:href` erzeugten Links sind immer absolute Pfade (d. h. sie beginnen mit `/`), nicht aber absolute URLs mit Protokoll und Domäne wie `https://domain`. +Links, die mit `link()` oder `n:href` generiert werden, sind immer absolute Pfade (d. h. sie beginnen mit einem `/`), aber keine absoluten URLs mit Protokoll und Domain wie `https://domain`. -Um eine absolute URL zu erzeugen, fügen Sie zwei Schrägstriche am Anfang hinzu (z. B. `n:href="//Home:"`). Sie können den Präsentator auch so einstellen, dass er nur absolute Links erzeugt, indem Sie `$this->absoluteUrls = true` einstellen. +Um eine absolute URL zu generieren, fügen Sie am Anfang zwei Schrägstriche hinzu (z. B. `n:href="//Home:"`). Oder Sie können den Presenter so umschalten, dass er nur absolute Links generiert, indem Sie `$this->absoluteUrls = true` setzen. -Link zur aktuellen Seite .[#toc-link-to-current-page] -===================================================== +Link zur aktuellen Seite +======================== -Das Ziel `this` wird einen Link zur aktuellen Seite erstellen: +Das Ziel `this` erstellt einen Link zur aktuellen Seite: ```latte -refresh +aktualisieren ``` -Gleichzeitig werden alle Parameter, die in der Signatur des Befehls `render()` oder `action()` Methode angegeben sind, übertragen. Wenn wir uns also auf den Seiten `Product:show` und `id:123` befinden, wird der Link zu `this` auch diesen Parameter übergeben. +Gleichzeitig werden auch alle Parameter übertragen, die in der Signatur der Methode `action()` oder `render()` angegeben sind, falls `action()` nicht definiert ist. Wenn wir also auf der Seite `Product:show` mit `id: 123` sind, übergibt der Link auf `this` auch diesen Parameter. -Natürlich ist es auch möglich, die Parameter direkt anzugeben: +Natürlich ist es möglich, Parameter direkt anzugeben: ```latte -refresh +aktualisieren ``` -Die Funktion `isLinkCurrent()` ermittelt, ob das Ziel des Links mit der aktuellen Seite identisch ist. Dies kann z. B. in einer Vorlage zur Unterscheidung von Links usw. verwendet werden. +Die Funktion `isLinkCurrent()` prüft, ob das Ziel des Links mit der aktuellen Seite übereinstimmt. Dies kann beispielsweise in einem Template verwendet werden, um Links zu unterscheiden usw. -Die Parameter sind die gleichen wie bei der Methode `link()`, aber es ist auch möglich, den Platzhalter `*` anstelle einer bestimmten Aktion zu verwenden, d.h. jede Aktion des Präsentators. +Die Parameter sind die gleichen wie bei der Methode `link()`, zusätzlich ist es jedoch möglich, anstelle einer konkreten Aktion den Platzhalter `*` anzugeben, der jede Aktion des gegebenen Presenters bedeutet. ```latte {if !isLinkCurrent('Admin:login')} - Přihlaste se + Anmelden {/if}
  • @@ -162,58 +162,58 @@ Die Parameter sind die gleichen wie bei der Methode `link()`, aber es ist auch m
  • ``` -Eine abgekürzte Form kann in Kombination mit `n:href` in einem einzelnen Element verwendet werden: +In Kombination mit `n:href` in einem Element kann eine verkürzte Form verwendet werden: ```latte -... +... ``` -Das Platzhalterzeichen "*" ersetzt nur die Aktion des Präsentators, nicht den Präsentator selbst. +Der Platzhalter `*` kann nur anstelle der Aktion verwendet werden, nicht für den Presenter. -Um herauszufinden, ob wir uns in einem bestimmten Modul oder dessen Untermodul befinden, können wir die Funktion `isModuleCurrent(moduleName)` verwenden. +Um festzustellen, ob wir uns in einem bestimmten Modul oder dessen Submodul befinden, verwenden wir die Methode `isModuleCurrent(moduleName)`. ```latte -
  • +
  • ...
  • ``` -Links zu Signal .[#toc-links-to-signal] -======================================= +Links zu Signalen +================= -Das Ziel des Links kann nicht nur der Präsentator und die Aktion sein, sondern auch das [Signal |components#Signal] (sie rufen die Methode `handle()`). Die Syntax lautet wie folgt: +Das Ziel eines Links muss nicht nur ein Presenter und eine Aktion sein, sondern auch ein [Signal |components#Signal] (sie rufen die Methode `handle()` auf). Dann lautet die Syntax wie folgt: ``` [//] [sub-component:]signal! [#fragment] ``` -Das Signal ist also durch ein Ausrufezeichen gekennzeichnet: +Das Signal wird also durch ein Ausrufezeichen unterschieden: ```latte -signal +Signal ``` -Sie können auch einen Verweis auf das Signal der Unterkomponente (oder Unter-Unterkomponente) erstellen: +Es kann auch ein Link zum Signal einer Subkomponente (oder Sub-Subkomponente) erstellt werden: ```latte -signal +Signal ``` -Verknüpfungen in der Komponente .[#toc-links-in-component] -========================================================== +Links in einer Komponente +========================= -Da [Komponenten |components] separate, wiederverwendbare Einheiten sind, die keine Beziehungen zu umgebenden Präsentatoren haben sollten, funktionieren die Links etwas anders. Das Latte-Attribut `n:href` und das Tag `{link}` sowie Komponentenmethoden wie `link()` und andere betrachten immer das Ziel **als Signalnamen**. Daher ist es nicht notwendig, ein Ausrufezeichen zu verwenden: +Da [Komponenten |components] separate wiederverwendbare Einheiten sind, die keine Bindungen an umgebende Presenter haben sollten, funktionieren Links hier etwas anders. Das Latte-Attribut `n:href` und der Tag `{link}` sowie Komponentenmethoden wie `link()` und andere betrachten das Linkziel **immer als den Namen des Signals**. Daher ist es nicht notwendig, ein Ausrufezeichen anzugeben: ```latte -signal, not an action +Signal, nicht Aktion ``` -Wenn wir auf Präsentatoren in der Komponentenvorlage verlinken wollen, verwenden wir das Tag `{plink}`: +Wenn wir im Template einer Komponente auf Presenter verlinken möchten, verwenden wir dazu den Tag `{plink}`: ```latte -home +Startseite ``` oder im Code @@ -223,17 +223,41 @@ $this->getPresenter()->link('Home:default') ``` -Ungültige Links .[#toc-invalid-links] -===================================== +Aliase .{data-version:v3.2.2} +============================= -Es kann vorkommen, dass wir einen ungültigen Link erstellen - entweder weil er auf einen nicht existierenden Presenter verweist, oder weil er mehr Parameter übergibt, als die Zielmethode in ihrer Signatur erhält, oder wenn es keine generierte URL für die angestrebte Aktion geben kann. Was mit ungültigen Links zu tun ist, wird durch die statische Variable `Presenter::$invalidLinkMode` bestimmt. Sie kann einen der folgenden Werte (Konstanten) haben: +Manchmal kann es nützlich sein, dem Paar Presenter:Aktion einen leicht zu merkenden Alias zuzuordnen. Zum Beispiel die Startseite `Front:Home:default` einfach als `home` benennen oder `Admin:Dashboard:default` als `admin`. -- `Presenter::InvalidLinkSilent` - stiller Modus, gibt das Symbol `#` als URL zurück -- `Presenter::InvalidLinkWarning` - E_USER_WARNING wird erzeugt -- `Presenter::InvalidLinkTextual` - visuelle Warnung, der Fehlertext wird im Link angezeigt -- `Presenter::InvalidLinkException` - Es wird eine InvalidLinkException ausgelöst +Aliase werden in der [Konfiguration |configuration] unter dem Schlüssel `application › aliases` definiert: -Die Standardeinstellung im Produktionsmodus ist `InvalidLinkWarning` und im Entwicklungsmodus ist `InvalidLinkWarning | InvalidLinkTextual`. `InvalidLinkWarning` beendet das Skript in der Produktionsumgebung nicht, aber die Warnung wird protokolliert. In der Entwicklungsumgebung fängt [Tracy |tracy:] die Warnung ab und zeigt den Fehlerbluescreen an. Wenn `InvalidLinkTextual` gesetzt ist, geben Presenter und Komponenten die Fehlermeldung als URL zurück, die mit `#error:` beginnt. Um solche Links sichtbar zu machen, können wir eine CSS-Regel zu unserem Stylesheet hinzufügen: +```neon +application: + aliases: + home: Front:Home:default + admin: Admin:Dashboard:default + sign: Front:Sign:in +``` + +In Links werden sie dann mit einem At-Zeichen geschrieben, zum Beispiel: + +```latte +Administration +``` + +Sie werden auch in allen Methoden unterstützt, die mit Links arbeiten, wie `redirect()` und ähnliche. + + +Ungültige Links +=============== + +Es kann vorkommen, dass wir einen ungültigen Link erstellen – entweder weil er auf einen nicht existierenden Presenter verweist, oder weil er mehr Parameter übergibt, als die Zielmethode in ihrer Signatur akzeptiert, oder wenn für die Zielaktion keine URL generiert werden kann. Wie mit ungültigen Links umgegangen wird, bestimmt die statische Variable `Presenter::$invalidLinkMode`. Diese kann eine Kombination der folgenden Werte (Konstanten) annehmen: + +- `Presenter::InvalidLinkSilent` - stiller Modus, als URL wird das Zeichen # zurückgegeben +- `Presenter::InvalidLinkWarning` - es wird eine E_USER_WARNING-Warnung ausgegeben, die im Produktionsmodus protokolliert wird, aber die Skriptausführung nicht unterbricht +- `Presenter::InvalidLinkTextual` - visuelle Warnung, gibt den Fehler direkt im Link aus +- `Presenter::InvalidLinkException` - es wird eine InvalidLinkException-Ausnahme ausgelöst + +Die Standardeinstellung ist `InvalidLinkWarning` im Produktionsmodus und `InvalidLinkWarning | InvalidLinkTextual` im Entwicklungsmodus. `InvalidLinkWarning` im Produktionsmodus führt nicht zur Unterbrechung des Skripts, aber die Warnung wird protokolliert. Im Entwicklungsmodus wird sie von [Tracy |tracy:] abgefangen und zeigt einen Bluescreen an. `InvalidLinkTextual` funktioniert so, dass als URL eine Fehlermeldung zurückgegeben wird, die mit den Zeichen `#error:` beginnt. Damit solche Links auf den ersten Blick erkennbar sind, ergänzen wir unser CSS: ```css a[href^="#error:"] { @@ -242,7 +266,7 @@ a[href^="#error:"] { } ``` -Wenn wir nicht wollen, dass in der Entwicklungsumgebung Warnungen ausgegeben werden, können wir in der [Konfiguration |configuration] den Modus "Stiller ungültiger Link" einschalten. +Wenn wir nicht möchten, dass im Entwicklungsmodus Warnungen erzeugt werden, können wir den stillen Modus direkt in der [Konfiguration |configuration] einstellen. ```neon application: @@ -250,13 +274,13 @@ application: ``` -LinkGenerator .[#toc-linkgenerator] -=================================== +LinkGenerator +============= -Wie erstellt man Links mit der Methode `link()` comfort, aber ohne die Anwesenheit eines Presenters? Deshalb gibt es hier [api:Nette\Application\LinkGenerator]. +Wie erstellt man Links mit ähnlichem Komfort wie die Methode `link()`, aber ohne die Anwesenheit eines Presenters? Dafür gibt es den [api:Nette\Application\LinkGenerator]. -LinkGenerator ist ein Dienst, den Sie über den Konstruktor übergeben können und dann Links mit seiner Methode `link()` erstellen. +Der LinkGenerator ist ein Dienst, den Sie sich über den Konstruktor übergeben lassen und dann Links mit seiner Methode `link()` erstellen können. -Es gibt einen Unterschied zu Presentern. LinkGenerator erstellt alle Links als absolute URLs. Außerdem gibt es keinen "aktuellen Präsentator", so dass es nicht möglich ist, nur den Namen der Aktion `link('default')` oder die relativen Pfade zu den [Modulen |modules] anzugeben. +Im Vergleich zu Presentern gibt es hier einen Unterschied. Der LinkGenerator erstellt alle Links direkt als absolute URLs. Außerdem gibt es keinen "aktuellen Presenter", sodass man als Ziel nicht nur den Aktionsnamen `link('default')` angeben oder relative Pfade zu Modulen verwenden kann. -Ungültige Links führen immer zu `Nette\Application\UI\InvalidLinkException`. +Ungültige Links lösen immer eine `Nette\Application\UI\InvalidLinkException` aus. diff --git a/application/de/directory-structure.texy b/application/de/directory-structure.texy new file mode 100644 index 0000000000..c7c34615b7 --- /dev/null +++ b/application/de/directory-structure.texy @@ -0,0 +1,526 @@ +Verzeichnisstruktur der Anwendung +********************************* + +
    + +Wie entwirft man eine übersichtliche und skalierbare Verzeichnisstruktur für Projekte im Nette Framework? Wir zeigen Ihnen bewährte Praktiken, die Ihnen bei der Organisation Ihres Codes helfen. Sie erfahren: + +- wie Sie die Anwendung **logisch in Verzeichnisse gliedern** +- wie Sie die Struktur so gestalten, dass sie mit dem Wachstum des Projekts **gut skaliert** +- welche **möglichen Alternativen** es gibt und welche Vor- oder Nachteile sie haben + +
    + + +Es ist wichtig zu erwähnen, dass das Nette Framework selbst keine bestimmte Struktur vorschreibt. Es ist so konzipiert, dass es sich leicht an alle Bedürfnisse und Präferenzen anpassen lässt. + + +Grundlegende Projektstruktur +============================ + +Obwohl das Nette Framework keine feste Verzeichnisstruktur vorschreibt, gibt es eine bewährte Standardanordnung in Form des [Web Project|https://github.com/nette/web-project]: + +/--pre +web-project/ +├── app/ ← Anwendungsverzeichnis +├── assets/ ← SCSS-, JS-Dateien, Bilder..., alternativ resources/ +├── bin/ ← Skripte für die Befehlszeile +├── config/ ← Konfiguration +├── log/ ← protokollierte Fehler +├── temp/ ← temporäre Dateien, Cache +├── tests/ ← Tests +├── vendor/ ← Bibliotheken, die mit Composer installiert wurden +└── www/ ← öffentliches Verzeichnis (Document-Root) +\-- + +Sie können diese Struktur beliebig an Ihre Bedürfnisse anpassen – Ordner umbenennen oder verschieben. Anschließend müssen Sie nur die relativen Pfade zu den Verzeichnissen in der Datei `Bootstrap.php` und gegebenenfalls `composer.json` anpassen. Mehr ist nicht nötig, keine komplexe Neukonfiguration, keine Änderung von Konstanten. Nette verfügt über eine intelligente Autoerkennung und erkennt automatisch den Speicherort der Anwendung einschließlich ihrer URL-Basis. + + +Prinzipien der Code-Organisation +================================ + +Wenn Sie ein neues Projekt zum ersten Mal untersuchen, sollten Sie sich schnell darin zurechtfinden. Stellen Sie sich vor, Sie klicken auf das Verzeichnis `app/Model/` und sehen diese Struktur: + +/--pre +app/Model/ +├── Services/ +├── Repositories/ +└── Entities/ +\-- + +Daraus können Sie nur entnehmen, dass das Projekt einige Dienste, Repositories und Entitäten verwendet. Über den tatsächlichen Zweck der Anwendung erfahren Sie überhaupt nichts. + +Schauen wir uns einen anderen Ansatz an – die **Organisation nach Domänen**: + +/--pre +app/Model/ +├── Cart/ +├── Payment/ +├── Order/ +└── Product/ +\-- + +Hier ist es anders – auf den ersten Blick ist klar, dass es sich um einen E-Shop handelt. Schon die Verzeichnisnamen verraten, was die Anwendung kann – sie arbeitet mit Zahlungen, Bestellungen und Produkten. + +Der erste Ansatz (Organisation nach Klassentypen) bringt in der Praxis eine Reihe von Problemen mit sich: Code, der logisch zusammenhängt, ist auf verschiedene Ordner verteilt, und Sie müssen zwischen ihnen hin- und herspringen. Deshalb werden wir nach Domänen organisieren. + + +Namespaces +---------- + +Es ist üblich, dass die Verzeichnisstruktur mit den Namespaces in der Anwendung korrespondiert. Das bedeutet, dass der physische Speicherort der Dateien ihrem Namespace entspricht. Zum Beispiel sollte eine Klasse, die sich in `app/Model/Product/ProductRepository.php` befindet, den Namespace `App\Model\Product` haben. Dieses Prinzip hilft bei der Orientierung im Code und vereinfacht das Autoloading. + + +Singular vs. Plural in Namen +---------------------------- + +Beachten Sie, dass wir für die Hauptverzeichnisse der Anwendung den Singular verwenden: `app`, `config`, `log`, `temp`, `www`. Ebenso innerhalb der Anwendung: `Model`, `Core`, `Presentation`. Das liegt daran, dass jedes dieser Verzeichnisse ein zusammenhängendes Konzept darstellt. + +Ähnlich repräsentiert z.B. `app/Model/Product` alles rund um Produkte. Wir nennen es nicht `Products`, weil es sich nicht um einen Ordner voller Produkte handelt (dann wären dort Dateien wie `nokia.php`, `samsung.php`). Es ist ein Namespace, der Klassen für die Arbeit mit Produkten enthält – `ProductRepository.php`, `ProductService.php`. + +Der Ordner `app/Tasks` steht im Plural, weil er einen Satz eigenständiger ausführbarer Skripte enthält – `CleanupTask.php`, `ImportTask.php`. Jedes davon ist eine eigenständige Einheit. + +Zur Konsistenz empfehlen wir die Verwendung von: +- Singular für einen Namespace, der eine funktionale Einheit repräsentiert (auch wenn er mit mehreren Entitäten arbeitet) +- Plural für Sammlungen eigenständiger Einheiten +- Im Zweifelsfall oder wenn Sie nicht darüber nachdenken möchten, wählen Sie den Singular + + +Öffentliches Verzeichnis `www/` +=============================== + +Dieses Verzeichnis ist das einzige, das vom Web aus zugänglich ist (sog. Document-Root). Oft trifft man auch auf den Namen `public/` anstelle von `www/` – das ist nur eine Frage der Konvention und hat keinen Einfluss auf die Funktionalität des Frameworks. Das Verzeichnis enthält: +- Den [Einstiegspunkt |bootstrapping#index.php] der Anwendung `index.php` +- Die Datei `.htaccess` mit Regeln für mod_rewrite (bei Apache) +- Statische Dateien (CSS, JavaScript, Bilder) +- Hochgeladene Dateien + +Für die korrekte Sicherheit der Anwendung ist es entscheidend, den [konfigurierten Document-Root |nette:troubleshooting#Wie ändert oder entfernt man das Verzeichnis www aus der URL] richtig eingestellt zu haben. + +.[note] +Platzieren Sie niemals den Ordner `node_modules/` in diesem Verzeichnis – er enthält Tausende von Dateien, die ausführbar sein könnten und nicht öffentlich zugänglich sein sollten. + + +Anwendungsverzeichnis `app/` +============================ + +Dies ist das Hauptverzeichnis mit dem Anwendungscode. Die Grundstruktur: + +/--pre +app/ +├── Core/ ← Infrastrukturangelegenheiten +├── Model/ ← Geschäftslogik +├── Presentation/ ← Presenter und Templates +├── Tasks/ ← Befehlszeilenskripte +└── Bootstrap.php ← Bootstrap-Klasse der Anwendung +\-- + +`Bootstrap.php` ist die [Startklasse der Anwendung|bootstrapping], die die Umgebung initialisiert, die Konfiguration lädt und den DI-Container erstellt. + +Schauen wir uns nun die einzelnen Unterverzeichnisse genauer an. + + +Presenter und Templates +======================= + +Der Präsentationsteil der Anwendung befindet sich im Verzeichnis `app/Presentation`. Eine Alternative ist das kurze `app/UI`. Dies ist der Ort für alle Presenter, ihre Templates und eventuelle Hilfsklassen. + +Diese Schicht organisieren wir nach Domänen. In einem komplexen Projekt, das einen E-Shop, einen Blog und eine API kombiniert, würde die Struktur so aussehen: + +/--pre +app/Presentation/ +├── Shop/ ← E-Shop Frontend +│ ├── Product/ +│ ├── Cart/ +│ └── Order/ +├── Blog/ ← Blog +│ ├── Home/ +│ └── Post/ +├── Admin/ ← Administration +│ ├── Dashboard/ +│ └── Products/ +└── Api/ ← API-Endpunkte + └── V1/ +\-- + +Bei einem einfachen Blog hingegen würden wir folgende Gliederung verwenden: + +/--pre +app/Presentation/ +├── Front/ ← Frontend der Website +│ ├── Home/ +│ └── Post/ +├── Admin/ ← Administration +│ ├── Dashboard/ +│ └── Posts/ +├── Error/ +└── Export/ ← RSS, Sitemaps etc. +\-- + +Ordner wie `Home/` oder `Dashboard/` enthalten Presenter und Templates. Ordner wie `Front/`, `Admin/` oder `Api/` nennen wir **Module**. Technisch gesehen sind dies normale Verzeichnisse, die zur logischen Gliederung der Anwendung dienen. + +Jeder Ordner mit einem Presenter enthält den gleichnamigen Presenter und seine Templates. Zum Beispiel enthält der Ordner `Dashboard/`: + +/--pre +Dashboard/ +├── DashboardPresenter.php ← Presenter +└── default.latte ← Template +\-- + +Diese Verzeichnisstruktur spiegelt sich in den Namespaces der Klassen wider. Zum Beispiel befindet sich `DashboardPresenter` im Namespace `App\Presentation\Admin\Dashboard` (siehe [#Presenter-Mapping]): + +```php +namespace App\Presentation\Admin\Dashboard; + +class DashboardPresenter extends Nette\Application\UI\Presenter +{ + // ... +} +``` + +Auf den Presenter `Dashboard` innerhalb des Moduls `Admin` verweisen wir in der Anwendung mittels Doppelpunktnotation als `Admin:Dashboard`. Auf seine Aktion `default` dann als `Admin:Dashboard:default`. Bei verschachtelten Modulen verwenden wir mehrere Doppelpunkte, zum Beispiel `Shop:Order:Detail:default`. + + +Flexible Strukturentwicklung +---------------------------- + +Einer der großen Vorteile dieser Struktur ist, wie elegant sie sich an die wachsenden Anforderungen des Projekts anpasst. Nehmen wir als Beispiel den Teil, der XML-Feeds generiert. Am Anfang haben wir eine einfache Form: + +/--pre +Export/ +├── ExportPresenter.php ← ein Presenter für alle Exporte +├── sitemap.latte ← Template für die Sitemap +└── feed.latte ← Template für den RSS-Feed +\-- + +Mit der Zeit kommen weitere Feed-Typen hinzu und wir benötigen mehr Logik für sie... Kein Problem! Der Ordner `Export/` wird einfach zu einem Modul: + +/--pre +Export/ +├── Sitemap/ +│ ├── SitemapPresenter.php +│ └── sitemap.latte +└── Feed/ + ├── FeedPresenter.php + ├── zbozi.latte ← Feed für Zboží.cz + └── heureka.latte ← Feed für Heureka.cz +\-- + +Diese Transformation ist absolut nahtlos – es genügt, neue Unterordner zu erstellen, den Code darin aufzuteilen und die Links zu aktualisieren (z.B. von `Export:feed` zu `Export:Feed:zbozi`). Dadurch können wir die Struktur nach Bedarf schrittweise erweitern, die Verschachtelungsebene ist in keiner Weise begrenzt. + +Wenn Sie beispielsweise in der Administration viele Presenter haben, die sich auf die Verwaltung von Bestellungen beziehen, wie `OrderDetail`, `OrderEdit`, `OrderDispatch` usw., können Sie zur besseren Organisation an dieser Stelle ein Modul (Ordner) `Order` erstellen, in dem sich die (Ordner für die) Presenter `Detail`, `Edit`, `Dispatch` und weitere befinden. + + +Platzierung von Templates +------------------------- + +In den vorherigen Beispielen haben wir gesehen, dass die Templates direkt im Ordner mit dem Presenter platziert sind: + +/--pre +Dashboard/ +├── DashboardPresenter.php ← Presenter +├── DashboardTemplate.php ← optionale Klasse für das Template +└── default.latte ← Template +\-- + +Diese Platzierung erweist sich in der Praxis als am bequemsten – Sie haben alle zusammengehörigen Dateien sofort zur Hand. + +Alternativ können Sie die Templates in einem Unterordner `templates/` platzieren. Nette unterstützt beide Varianten. Sie können Templates sogar ganz außerhalb des `Presentation/`-Ordners platzieren. Alles über die Möglichkeiten zur Platzierung von Templates finden Sie im Kapitel [Suche nach Templates |templates#Finden von Vorlagen]. + + +Hilfsklassen und Komponenten +---------------------------- + +Zu Presentern und Templates gehören oft auch weitere Hilfsdateien. Wir platzieren sie logisch nach ihrem Wirkungsbereich: + +1. **Direkt beim Presenter** im Falle spezifischer Komponenten für den jeweiligen Presenter: + +/--pre +Product/ +├── ProductPresenter.php +├── ProductGrid.php ← Komponente zur Produktauflistung +└── FilterForm.php ← Formular zur Filterung +\-- + +2. **Für das Modul** – wir empfehlen die Verwendung des Ordners `Accessory`, der übersichtlich gleich am Anfang des Alphabets platziert wird: + +/--pre +Front/ +├── Accessory/ +│ ├── NavbarControl.php ← Komponenten für das Frontend +│ └── TemplateFilters.php +├── Product/ +└── Cart/ +\-- + +3. **Für die gesamte Anwendung** – in `Presentation/Accessory/`: +/--pre +app/Presentation/ +├── Accessory/ +│ ├── LatteExtension.php +│ └── TemplateFilters.php +├── Front/ +└── Admin/ +\-- + +Oder Sie können Hilfsklassen wie `LatteExtension.php` oder `TemplateFilters.php` im Infrastrukturordner `app/Core/Latte/` platzieren. Und Komponenten in `app/Components`. Die Wahl hängt von den Gewohnheiten des Teams ab. + + +Model - Das Herz der Anwendung +============================== + +Das Model enthält die gesamte Geschäftslogik der Anwendung. Für seine Organisation gilt wieder die Regel – wir strukturieren nach Domänen: + +/--pre +app/Model/ +├── Payment/ ← alles rund um Zahlungen +│ ├── PaymentFacade.php ← Hauptzugangspunkt +│ ├── PaymentRepository.php +│ ├── Payment.php ← Entität +├── Order/ ← alles rund um Bestellungen +│ ├── OrderFacade.php +│ ├── OrderRepository.php +│ ├── Order.php +└── Shipping/ ← alles rund um den Versand +\-- + +Im Model treffen Sie typischerweise auf diese Klassentypen: + +**Fassaden**: stellen den Hauptzugangspunkt zu einer bestimmten Domäne in der Anwendung dar. Sie fungieren als Orchestrator, der die Zusammenarbeit zwischen verschiedenen Diensten koordiniert, um vollständige Use Cases (wie "Bestellung erstellen" oder "Zahlung verarbeiten") zu implementieren. Unter ihrer Orchestrierungsschicht verbirgt die Fassade Implementierungsdetails vor dem Rest der Anwendung und bietet so eine saubere Schnittstelle für die Arbeit mit der jeweiligen Domäne. + +```php +class OrderFacade +{ + public function createOrder(Cart $cart): Order + { + // Validierung + // Erstellung der Bestellung + // Senden der E-Mail + // Eintrag in die Statistiken + } +} +``` + +**Dienste**: konzentrieren sich auf eine spezifische Geschäftsoperation innerhalb der Domäne. Im Gegensatz zur Fassade, die ganze Use Cases orchestriert, implementiert ein Dienst spezifische Geschäftslogik (wie Preisberechnungen oder Zahlungsverarbeitung). Dienste sind typischerweise zustandslos und können entweder von Fassaden als Bausteine für komplexere Operationen oder direkt von anderen Teilen der Anwendung für einfachere Aufgaben verwendet werden. + +```php +class PricingService +{ + public function calculateTotal(Order $order): Money + { + // Preisberechnung + } +} +``` + +**Repositories**: stellen die gesamte Kommunikation mit dem Datenspeicher sicher, typischerweise einer Datenbank. Ihre Aufgabe ist das Laden und Speichern von Entitäten und die Implementierung von Methoden zu deren Suche. Das Repository schirmt den Rest der Anwendung von den Implementierungsdetails der Datenbank ab und bietet eine objektorientierte Schnittstelle für die Arbeit mit Daten. + +```php +class OrderRepository +{ + public function find(int $id): ?Order + { + } + + public function findByCustomer(int $customerId): array + { + } +} +``` + +**Entitäten**: Objekte, die die Hauptgeschäftskonzepte in der Anwendung repräsentieren, ihre eigene Identität haben und sich im Laufe der Zeit ändern. Typischerweise handelt es sich um Klassen, die mittels ORM (wie Nette Database Explorer oder Doctrine) auf Datenbanktabellen abgebildet sind. Entitäten können Geschäftsregeln enthalten, die sich auf ihre Daten beziehen, sowie Validierungslogik. + +```php +// Entität, die auf die Datenbanktabelle orders abgebildet ist +class Order extends Nette\Database\Table\ActiveRow +{ + public function addItem(Product $product, int $quantity): void + { + $this->related('order_items')->insert([ + 'product_id' => $product->id, + 'quantity' => $quantity, + 'unit_price' => $product->price, + ]); + } +} +``` + +**Value Objects**: unveränderliche Objekte, die Werte ohne eigene Identität repräsentieren – beispielsweise ein Geldbetrag oder eine E-Mail-Adresse. Zwei Instanzen eines Value Objects mit gleichen Werten werden als identisch betrachtet. + + +Infrastrukturcode +================= + +Der Ordner `Core/` (oder auch `Infrastructure/`) ist die Heimat für die technische Grundlage der Anwendung. Infrastrukturcode umfasst typischerweise: + +/--pre +app/Core/ +├── Router/ ← Routing und URL-Management +│ └── RouterFactory.php +├── Security/ ← Authentifizierung und Autorisierung +│ ├── Authenticator.php +│ └── Authorizator.php +├── Logging/ ← Logging und Monitoring +│ ├── SentryLogger.php +│ └── FileLogger.php +├── Cache/ ← Caching-Schicht +│ └── FullPageCache.php +└── Integration/ ← Integration mit externen Diensten + ├── Slack/ + └── Stripe/ +\-- + +Bei kleineren Projekten genügt natürlich eine flache Gliederung: + +/--pre +Core/ +├── RouterFactory.php +├── Authenticator.php +└── QueueMailer.php +\-- + +Es handelt sich um Code, der: + +- Sich um die technische Infrastruktur kümmert (Routing, Logging, Caching) +- Externe Dienste integriert (Sentry, Elasticsearch, Redis) +- Basisdienste für die gesamte Anwendung bereitstellt (Mail, Datenbank) +- Meistens unabhängig von der spezifischen Domäne ist – Cache oder Logger funktionieren gleich für E-Shop oder Blog. + +Sind Sie unsicher, ob eine bestimmte Klasse hierher oder ins Modell gehört? Der Hauptunterschied besteht darin, dass der Code in `Core/`: + +- Nichts über die Domäne weiß (Produkte, Bestellungen, Artikel) +- Meistens in ein anderes Projekt übertragen werden kann +- Löst "wie es funktioniert" (wie man eine E-Mail sendet), nicht "was es tut" (welche E-Mail gesendet werden soll) + +Beispiel zum besseren Verständnis: + +- `App\Core\MailerFactory` - erstellt Instanzen der Klasse zum Senden von E-Mails, kümmert sich um SMTP-Einstellungen +- `App\Model\OrderMailer` - verwendet `MailerFactory` zum Senden von E-Mails über Bestellungen, kennt deren Templates und weiß, wann sie gesendet werden sollen + + +Befehlszeilenskripte +==================== + +Anwendungen müssen oft Tätigkeiten außerhalb normaler HTTP-Anfragen ausführen – sei es Datenverarbeitung im Hintergrund, Wartung oder periodische Aufgaben. Zur Ausführung dienen einfache Skripte im Verzeichnis `bin/`, die eigentliche Implementierungslogik platzieren wir dann in `app/Tasks/` (oder `app/Commands/`). + +Beispiel: + +/--pre +app/Tasks/ +├── Maintenance/ ← Wartungsskripte +│ ├── CleanupCommand.php ← Löschen alter Daten +│ └── DbOptimizeCommand.php ← Datenbankoptimierung +├── Integration/ ← Integration mit externen Systemen +│ ├── ImportProducts.php ← Import aus dem Liefersystem +│ └── SyncOrders.php ← Synchronisation von Bestellungen +└── Scheduled/ ← regelmäßige Aufgaben + ├── NewsletterCommand.php ← Versand von Newslettern + └── ReminderCommand.php ← Benachrichtigungen an Kunden +\-- + +Was gehört ins Modell und was in die Befehlszeilenskripte? Zum Beispiel ist die Logik zum Senden einer einzelnen E-Mail Teil des Modells, der Massenversand von Tausenden von E-Mails gehört bereits zu `Tasks/`. + +Aufgaben werden normalerweise [über die Befehlszeile ausgeführt |https://blog.nette.org/en/cli-scripts-in-nette-application] oder über Cron. Sie können auch über eine HTTP-Anfrage gestartet werden, aber man muss an die Sicherheit denken. Der Presenter, der die Aufgabe startet, muss abgesichert werden, zum Beispiel nur für angemeldete Benutzer oder mit einem starken Token und Zugriff von erlaubten IP-Adressen. Bei langen Aufgaben muss das Zeitlimit des Skripts erhöht und `session_write_close()` verwendet werden, damit die Session nicht gesperrt wird. + + +Weitere mögliche Verzeichnisse +============================== + +Neben den genannten grundlegenden Verzeichnissen können Sie je nach Projektbedarf weitere spezialisierte Ordner hinzufügen. Schauen wir uns die häufigsten davon und ihre Verwendung an: + +/--pre +app/ +├── Api/ ← API-Logik unabhängig von der Präsentationsschicht +├── Database/ ← Migrationsskripte und Seeder für Testdaten +├── Components/ ← gemeinsam genutzte visuelle Komponenten über die gesamte Anwendung hinweg +├── Event/ ← nützlich, wenn Sie eine ereignisgesteuerte Architektur verwenden +├── Mail/ ← E-Mail-Templates und zugehörige Logik +└── Utils/ ← Hilfsklassen +\-- + +Für gemeinsam genutzte visuelle Komponenten, die in Presentern über die gesamte Anwendung hinweg verwendet werden, kann der Ordner `app/Components` oder `app/Controls` verwendet werden: + +/--pre +app/Components/ +├── Form/ ← gemeinsam genutzte Formularkomponenten +│ ├── SignInForm.php +│ └── UserForm.php +├── Grid/ ← Komponenten für Datenlisten +│ └── DataGrid.php +└── Navigation/ ← Navigationselemente + ├── Breadcrumbs.php + └── Menu.php +\-- + +Hierher gehören Komponenten mit komplexerer Logik. Wenn Sie Komponenten zwischen mehreren Projekten teilen möchten, ist es ratsam, sie in ein separates Composer-Paket auszulagern. + +Im Verzeichnis `app/Mail` können Sie die Verwaltung der E-Mail-Kommunikation platzieren: + +/--pre +app/Mail/ +├── templates/ ← E-Mail-Templates +│ ├── order-confirmation.latte +│ └── welcome.latte +└── OrderMailer.php +\-- + + +Presenter-Mapping +================= + +Das Mapping definiert Regeln zur Ableitung des Klassennamens aus dem Presenter-Namen. Wir spezifizieren sie in der [Konfiguration|configuration] unter dem Schlüssel `application › mapping`. + +Auf dieser Seite haben wir gezeigt, dass wir Presenter im Ordner `app/Presentation` (oder `app/UI`) platzieren. Diese Konvention müssen wir Nette in der Konfigurationsdatei mitteilen. Eine Zeile genügt: + +```neon +application: + mapping: App\Presentation\*\**Presenter +``` + +Wie funktioniert das Mapping? Zum besseren Verständnis stellen wir uns zunächst eine Anwendung ohne Module vor. Wir möchten, dass die Presenter-Klassen in den Namespace `App\Presentation` fallen, damit der Presenter `Home` auf die Klasse `App\Presentation\HomePresenter` abgebildet wird. Was wir mit dieser Konfiguration erreichen: + +```neon +application: + mapping: App\Presentation\*Presenter +``` + +Das Mapping funktioniert so, dass der Presenter-Name `Home` das Sternchen in der Maske `App\Presentation\*Presenter` ersetzt, wodurch wir den resultierenden Klassennamen `App\Presentation\HomePresenter` erhalten. Einfach! + +Wie Sie jedoch in den Beispielen in diesem und anderen Kapiteln sehen, platzieren wir die Presenter-Klassen in gleichnamigen Unterverzeichnissen, zum Beispiel wird der Presenter `Home` auf die Klasse `App\Presentation\Home\HomePresenter` abgebildet. Dies erreichen wir mit der `***Presenter`-Maske (erfordert Nette Application 3.2): + +```neon +application: + mapping: App\Presentation\**Presenter +``` + +Nun kommen wir zum Mapping von Presentern in Module. Für jedes Modul können wir ein spezifisches Mapping definieren: + +```neon +application: + mapping: + Front: App\Presentation\Front\**Presenter + Admin: App\Presentation\Admin\**Presenter + Api: App\Api\*Presenter +``` + +Gemäß dieser Konfiguration wird der Presenter `Front:Home` auf die Klasse `App\Presentation\Front\Home\HomePresenter` abgebildet, während der Presenter `Api:OAuth` auf die Klasse `App\Api\OAuthPresenter` abgebildet wird. + +Da die Module `Front` und `Admin` eine ähnliche Mapping-Methode haben und es solche Module wahrscheinlich mehr geben wird, ist es möglich, eine allgemeine Regel zu erstellen, die sie ersetzt. Zur Klassenmaske kommt somit ein neues Sternchen für das Modul hinzu: + +```neon +application: + mapping: + *: App\Presentation\*\**Presenter + Api: App\Api\*Presenter +``` + +Es funktioniert auch für tiefer verschachtelte Verzeichnisstrukturen, wie zum Beispiel den Presenter `Admin:User:Edit`, wobei sich das Segment mit dem Sternchen für jede Ebene wiederholt und das Ergebnis die Klasse `App\Presentation\Admin\User\Edit\EditPresenter` ist. + +Eine alternative Schreibweise ist die Verwendung eines Arrays anstelle einer Zeichenkette, bestehend aus drei Segmenten. Diese Schreibweise ist äquivalent zur vorherigen: + +```neon +application: + mapping: + *: [App\Presentation, *, **Presenter] + Api: [App\Api, '', *Presenter] +``` diff --git a/application/de/how-it-works.texy b/application/de/how-it-works.texy index ae27a40ede..f58652fbde 100644 --- a/application/de/how-it-works.texy +++ b/application/de/how-it-works.texy @@ -1,9 +1,9 @@ -Wie funktionieren Bewerbungen? +Wie funktionieren Anwendungen? ******************************
    -Sie lesen gerade das Basisdokument der Nette-Dokumentation. Sie werden das ganze Prinzip der Webanwendungen kennenlernen. Nizza von A bis Z, vom Moment der Geburt bis zum letzten Atemzug des PHP-Skripts. Nach der Lektüre werden Sie wissen: +Sie lesen gerade das grundlegende Dokument der Nette-Dokumentation. Sie werden das gesamte Funktionsprinzip von Webanwendungen kennenlernen. Schön von A bis Z, von dem Moment der Entstehung bis zum letzten Atemzug des PHP-Skripts. Nach dem Lesen werden Sie wissen: - wie das Ganze funktioniert - was Bootstrap, Presenter und DI-Container sind @@ -12,90 +12,92 @@ Sie lesen gerade das Basisdokument der Nette-Dokumentation. Sie werden das ganze
    -Struktur des Verzeichnisses .[#toc-directory-structure] -======================================================= +Verzeichnisstruktur +=================== -Öffnen Sie ein Beispiel für eine Webanwendung namens [WebProject |https://github.com/nette/web-project] und beobachten Sie, wie die Dateien beschrieben werden. +Öffnen Sie das Beispiel-Skelett einer Webanwendung namens [WebProject|https://github.com/nette/web-project] und beim Lesen können Sie sich die Dateien ansehen, von denen die Rede ist. -Die Verzeichnisstruktur sieht in etwa so aus: +Die Verzeichnisstruktur sieht ungefähr so aus: /--pre web-project/ -├── app/ ← Verzeichnis mit Anwendung -│ ├── Presenters/ ← Presenter-Klassen -│ │ ├── HomePresenter.php ← Home presenterklasse -│ │ └── templates/ ← Vorlagenverzeichnis -│ │ ├── @layout.latte ← Vorlage für gemeinsames Layout -│ │ └── Home/ ← Vorlagen für Home-presenter -│ │ └── default.latte ← Vorlage für Aktion `default` -│ ├── Router/ ← Konfiguration von URL-Adressen -│ └── Bootstrap.php ← bootende Klasse Bootstrap -├── bin/ ← Skripte für die Kommandozeile +├── app/ ← Verzeichnis mit der Anwendung +│ ├── Core/ ← grundlegende Klassen, die für den Betrieb notwendig sind +│ │ └── RouterFactory.php ← Konfiguration der URL-Adressen +│ ├── Presentation/ ← Presenter, Templates & Co. +│ │ ├── @layout.latte ← Layout-Template +│ │ └── Home/ ← Verzeichnis des Home-Presenters +│ │ ├── HomePresenter.php ← Klasse des Home-Presenters +│ │ └── default.latte ← Template der default-Aktion +│ └── Bootstrap.php ← Startklasse Bootstrap +├── assets/ ← Ressourcen (SCSS, TypeScript, Quellbilder) +├── bin/ ← Skripte, die von der Kommandozeile ausgeführt werden ├── config/ ← Konfigurationsdateien │ ├── common.neon -│ └── local.neon -├── log/ ← Fehlerprotokolle +│ └── services.neon +├── log/ ← protokollierte Fehler ├── temp/ ← temporäre Dateien, Cache, … -├── vendor/ ← vom Composer installierte Bibliotheken +├── vendor/ ← Bibliotheken, die mit Composer installiert wurden │ ├── ... -│ └── autoload.php ← Automatisches Laden der vom Composer installierten Bibliotheken -├── www/ ← öffentliches Verzeichnis, Dokumentenstamm des Projekts -│ ├── .htaccess ← mod_rewrite-Regeln usw. -│ └── index.php ← Anfangsdatei, die die Anwendung startet +│ └── autoload.php ← Autoloading aller installierten Pakete +├── www/ ← öffentliches Verzeichnis oder Document-Root des Projekts +│ ├── assets/ ← kompilierte statische Dateien (CSS, JS, Bilder, ...) +│ ├── .htaccess ← mod_rewrite-Regeln +│ └── index.php ← initiale Datei, mit der die Anwendung gestartet wird └── .htaccess ← verbietet den Zugriff auf alle Verzeichnisse außer www \-- -Sie können die Verzeichnisstruktur beliebig ändern, Ordner umbenennen oder verschieben, und dann einfach die Pfade zu `log/` und `temp/` in der Datei `Bootstrap.php` und den Pfad zu dieser Datei in `composer.json` im Abschnitt `autoload` ändern. Nichts weiter, keine komplizierte Neukonfiguration, keine ständigen Änderungen. Nette hat eine [intelligente automatische Erkennung |bootstrap#development-vs-production-mode]. +Die Verzeichnisstruktur können Sie beliebig ändern, Ordner umbenennen oder verschieben, sie ist völlig flexibel. Nette verfügt zudem über eine intelligente Autodetektion und erkennt automatisch den Speicherort der Anwendung einschließlich ihrer URL-Basis. -Für etwas größere Anwendungen können wir Ordner mit Präsentatoren und Vorlagen in Unterverzeichnisse (auf der Festplatte) und in Namensräume (im Code) unterteilen, die wir [Module |modules] nennen. +Bei etwas größeren Anwendungen können wir die Ordner mit Presentern und Templates [in Unterverzeichnisse aufteilen |directory-structure#Presenter und Templates] und Klassen in Namespaces, die wir Module nennen. -Das Verzeichnis `www/` ist das öffentliche Verzeichnis oder die Dokumenten-Wurzel des Projekts. Sie können es umbenennen, ohne etwas anderes auf der Anwendungsseite einstellen zu müssen. Sie müssen nur [das Hosting |nette:troubleshooting#How to change or remove www directory from URL] so [konfigurieren |nette:troubleshooting#How to change or remove www directory from URL], dass die Dokumentenwurzel in dieses Verzeichnis führt. +Das Verzeichnis `www/` stellt das sogenannte öffentliche Verzeichnis oder Document-Root des Projekts dar. Sie können es umbenennen, ohne etwas Weiteres auf Anwendungsseite einstellen zu müssen. Es ist nur notwendig, [das Hosting zu konfigurieren |nette:troubleshooting#Wie ändert oder entfernt man das Verzeichnis www aus der URL], damit der Document-Root auf dieses Verzeichnis zeigt. -Sie können das WebProjekt auch direkt herunterladen, einschließlich Nette, indem Sie [Composer |best-practices:composer] verwenden: +WebProject können Sie sich auch direkt inklusive Nette herunterladen, und zwar mittels [Composer |best-practices:composer]: ```shell composer create-project nette/web-project ``` -Unter Linux oder macOS setzen Sie die [Schreibrechte |nette:troubleshooting#Setting directory permissions] für die Verzeichnisse `log/` und `temp/`. +Unter Linux oder macOS setzen Sie für die Verzeichnisse `log/` und `temp/` [Schreibrechte |nette:troubleshooting#Einstellung der Verzeichnisberechtigungen]. -Die WebProject-Anwendung ist einsatzbereit, es muss nichts weiter konfiguriert werden und Sie können sie direkt im Browser anzeigen, indem Sie auf den Ordner `www/` zugreifen. +Die Anwendung WebProject ist startbereit, es muss überhaupt nichts konfiguriert werden und Sie können sie direkt im Browser anzeigen, indem Sie auf den Ordner `www/` zugreifen. -HTTP-Anfrage .[#toc-http-request] -================================= +HTTP-Anfrage +============ -Alles beginnt damit, dass ein Benutzer die Seite in einem Browser öffnet und der Browser mit einer HTTP-Anfrage an den Server klopft. Die Anfrage geht an eine PHP-Datei, die sich im öffentlichen Verzeichnis `www/` befindet, also an `index.php`. Nehmen wir an, dass es sich um eine Anfrage an `https://example.com/product/123` zugeordnet und ausgeführt. +Alles beginnt in dem Moment, in dem der Benutzer im Browser eine Seite öffnet. Also wenn der Browser beim Server mit einer HTTP-Anfrage anklopft. Die Anfrage zielt auf eine einzige PHP-Datei, die sich im öffentlichen Verzeichnis `www/` befindet, und das ist `index.php`. Nehmen wir an, es handelt sich um eine Anfrage an die Adresse `https://example.com/product/123`. Dank geeigneter [Serverkonfiguration |nette:troubleshooting#Wie konfiguriert man den Server für schöne URLs Pretty URLs] wird auch diese URL auf die Datei `index.php` abgebildet und diese wird ausgeführt. -Seine Aufgabe ist: +Ihre Aufgabe ist es: 1) die Umgebung zu initialisieren -2) Abrufen der Fabrik +2) die Factory zu erhalten 3) die Nette-Anwendung zu starten, die die Anfrage bearbeitet -Welche Art von Fabrik? Wir stellen keine Traktoren her, sondern Websites! Warten Sie, ich erkläre es Ihnen gleich. +Welche Factory denn? Wir stellen doch keine Traktoren her, sondern Webseiten! Bleiben Sie dran, das wird gleich erklärt. -Mit "die Umgebung initialisieren" meinen wir zum Beispiel, dass [Tracy |tracy:] aktiviert wird, ein erstaunliches Werkzeug zur Protokollierung oder Visualisierung von Fehlern. Es protokolliert Fehler auf dem Produktionsserver und zeigt sie direkt auf dem Entwicklungsserver an. Daher muss bei der Initialisierung auch entschieden werden, ob die Site im Produktions- oder im Entwicklermodus läuft. Hierfür verwendet Nette eine automatische Erkennung: Wenn Sie die Site auf localhost ausführen, läuft sie im Entwicklermodus. Sie müssen nichts konfigurieren, und die Anwendung ist sowohl für die Entwicklung als auch für den Produktionseinsatz bereit. Diese Schritte werden im Kapitel über die [Bootstrap-Klasse |bootstrap] durchgeführt und ausführlich beschrieben. +Mit „Initialisierung der Umgebung“ meinen wir zum Beispiel, dass [Tracy|tracy:] aktiviert wird, ein großartiges Werkzeug zur Protokollierung oder Visualisierung von Fehlern. Auf dem Produktionsserver protokolliert es Fehler, auf dem Entwicklungsserver zeigt es sie direkt an. Zur Initialisierung gehört also auch die Entscheidung, ob die Website im Produktions- oder Entwicklungsmodus läuft. Dazu verwendet Nette eine [intelligente Autodetektion |bootstrapping#Entwicklungs- vs. Produktionsmodus]: Wenn Sie die Website auf localhost starten, läuft sie im Entwicklungsmodus. Sie müssen also nichts konfigurieren und die Anwendung ist direkt bereit sowohl für die Entwicklung als auch für den Live-Einsatz. Diese Schritte werden durchgeführt und sind im Kapitel über die [Bootstrap-Klasse|bootstrapping] ausführlich beschrieben. -Der dritte Punkt (ja, wir haben den zweiten übersprungen, aber wir werden darauf zurückkommen) ist das Starten der Anwendung. Die Bearbeitung von HTTP-Anfragen in Nette erfolgt durch die Klasse `Nette\Application\Application` (im Folgenden `Application` genannt). Wenn wir also sagen "eine Anwendung starten", meinen wir den Aufruf einer Methode mit dem Namen `run()` auf einem Objekt dieser Klasse. +Der dritte Punkt (ja, den zweiten haben wir übersprungen, aber wir kommen darauf zurück) ist der Start der Anwendung. Die Bearbeitung von HTTP-Anfragen übernimmt in Nette die Klasse `Nette\Application\Application` (weiter `Application`), wenn wir also sagen, die Anwendung starten, meinen wir konkret den Aufruf der Methode mit dem treffenden Namen `run()` auf einem Objekt dieser Klasse. -Nette ist ein Mentor, der Sie anleitet, saubere Anwendungen nach bewährten Methoden zu schreiben. Und die bewährteste heißt **dependency injection**, abgekürzt DI. Wir wollen Sie an dieser Stelle nicht mit der Erklärung von DI belasten, dafür gibt es ein [eigenes Kapitel |dependency-injection:introduction], wichtig ist, dass die Schlüsselobjekte in der Regel von einer Fabrik für Objekte namens **DI-Container** (abgekürzt DIC) erzeugt werden. Ja, das ist die Fabrik, die vor einiger Zeit erwähnt wurde. Und sie erstellt auch das `Application` Objekt für uns, also brauchen wir zuerst einen Container. Den holen wir uns mit der Klasse `Configurator` und lassen ihn das Objekt `Application` erzeugen, rufen die Methode `run()` auf und starten damit die Nette-Anwendung. Das ist genau das, was in der Datei [index.php |bootstrap#index.php] passiert. +Nette ist ein Mentor, der Sie dazu anleitet, saubere Anwendungen nach bewährten Methoden zu schreiben. Und eine der absolut bewährtesten heißt **Dependency Injection**, abgekürzt DI. An dieser Stelle möchten wir Sie nicht mit der Erklärung von DI belasten, dafür gibt es ein [eigenes Kapitel|dependency-injection:introduction], wesentlich ist die Konsequenz, dass uns Schlüsselobjekte üblicherweise von einer Objekt-Factory erstellt werden, die **DI-Container** (abgekürzt DIC) genannt wird. Ja, das ist die Factory, von der vorhin die Rede war. Und sie stellt uns auch das `Application`-Objekt her, deshalb benötigen wir zuerst den Container. Wir erhalten ihn mittels der Klasse `Configurator` und lassen ihn das `Application`-Objekt erstellen, rufen darauf die Methode `run()` auf und damit startet die Nette-Anwendung. Genau das geschieht in der Datei [index.php |bootstrapping#index.php]. -Nette-Anwendung .[#toc-nette-application] -========================================= +Nette Application +================= -Die Klasse Application hat eine einzige Aufgabe: Sie soll auf eine HTTP-Anfrage antworten. +Die Klasse Application hat eine einzige Aufgabe: auf eine HTTP-Anfrage zu antworten. -In Nette geschriebene Anwendungen sind in viele so genannte Presenter unterteilt (in anderen Frameworks stößt man vielleicht auf den Begriff Controller, der dasselbe bedeutet), bei denen es sich um Klassen handelt, die eine bestimmte Website-Seite repräsentieren: z. B. Homepage; Produkt im E-Shop; Anmeldeformular; Sitemap-Feed usw. Die Anwendung kann zwischen einem und Tausenden von Presentern haben. +Anwendungen, die in Nette geschrieben sind, gliedern sich in viele sogenannte Presenter (in anderen Frameworks können Sie auf den Begriff Controller stoßen, es handelt sich um dasselbe), das sind Klassen, von denen jede eine bestimmte Webseite repräsentiert: z.B. die Homepage; ein Produkt im E-Shop; ein Anmeldeformular; ein Sitemap-Feed usw. Eine Anwendung kann von einem bis zu Tausenden von Presentern haben. -Die Anwendung beginnt damit, dass sie den so genannten Router bittet, zu entscheiden, an welchen der Presenter die aktuelle Anfrage zur Bearbeitung weitergeleitet werden soll. Der Router entscheidet, wer dafür zuständig ist. Er sieht sich die Eingabe-URL `https://example.com/product/123` handelt, der ein Produkt mit `id: 123` als Aktion an `show` weiterleiten möchte. Es ist eine gute Angewohnheit, ein durch einen Doppelpunkt getrenntes Paar aus Präsentator + Aktion als `Product:show` zu schreiben. +Die Application beginnt damit, den sogenannten Router zu bitten, zu entscheiden, welchem der Presenter die aktuelle Anfrage zur Bearbeitung übergeben werden soll. Der Router entscheidet, wessen Verantwortung das ist. Er schaut sich die Eingabe-URL `https://example.com/product/123` an und entscheidet auf Basis seiner Konfiguration, dass dies die Arbeit z.B. für den **Presenter** `Product` ist, von dem er als **Aktion** die Anzeige (`show`) des Produkts mit `id: 123` verlangen wird. Das Paar Presenter + Aktion wird üblicherweise durch einen Doppelpunkt getrennt als `Product:show` geschrieben. -Der Router verwandelt also die URL in ein Paar `Presenter:action` + Parameter, in unserem Fall `Product:show` + `id: 123`. Sie können sehen, wie ein Router in der Datei `app/Router/RouterFactory.php` aussieht, und wir werden ihn im Kapitel [Routing] ausführlich beschreiben. +Also hat der Router die URL in das Paar `Presenter:action` + Parameter transformiert, in unserem Fall `Product:show` + `id: 123`. Wie ein solcher Router aussieht, können Sie sich in der Datei `app/Core/RouterFactory.php` ansehen und wir beschreiben ihn detailliert im Kapitel [Routing]. -Machen wir weiter. Die Anwendung kennt bereits den Namen des Präsentators und kann fortfahren. Sie erstellt ein Objekt `ProductPresenter`, das den Code des Presenters `Product` darstellt. Genauer gesagt, sie bittet den DI-Container um die Erstellung des Presenters, denn die Erstellung von Objekten ist seine Aufgabe. +Gehen wir weiter. Die Application kennt nun den Namen des Presenters und kann weitermachen. Indem sie ein Objekt der Klasse `ProductPresenter` erstellt, was der Code des Presenters `Product` ist. Genauer gesagt, bittet sie den DI-Container, den Presenter zu erstellen, denn für das Erstellen ist er da. -Der Presenter könnte wie folgt aussehen: +Der Presenter kann etwa so aussehen: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -107,98 +109,92 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function renderShow(int $id): void { - // wir beziehen Daten aus dem Modell und übergeben sie an die Vorlage + // Wir holen Daten aus dem Modell und übergeben sie an das Template $this->template->product = $this->repository->getProduct($id); } } ``` -Die Anfrage wird vom Präsentator bearbeitet. Und die Aufgabe ist klar: Führe die Aktion `show` mit `id: 123` aus. Was in der Sprache der Moderatoren bedeutet, dass die Methode `renderShow()` aufgerufen wird und im Parameter `$id` steht `123`. +Die Bearbeitung der Anfrage übernimmt der Presenter. Und die Aufgabe lautet klar: führe die Aktion `show` mit `id: 123` aus. Was in der Sprache der Presenter bedeutet, dass die Methode `renderShow()` aufgerufen wird und im Parameter `$id` den Wert `123` erhält. -Ein Presenter kann mehrere Aktionen verarbeiten, also mehrere Methoden haben `render()`. Wir empfehlen jedoch, Presenter mit einer oder so wenigen Aktionen wie möglich zu entwerfen. +Ein Presenter kann mehrere Aktionen bedienen, also mehrere Methoden `render()` haben. Wir empfehlen jedoch, Presenter mit einer oder möglichst wenigen Aktionen zu entwerfen. -So wurde die Methode `renderShow(123)` aufgerufen, deren Code ein fiktives Beispiel ist, aber Sie können daran sehen, wie die Daten an die Vorlage übergeben werden, d.h. durch Schreiben an `$this->template`. +Also, die Methode `renderShow(123)` wurde aufgerufen, deren Code zwar ein fiktives Beispiel ist, aber Sie können daran sehen, wie Daten an das Template übergeben werden, nämlich durch Schreiben in `$this->template`. -Anschließend gibt der Präsentator die Antwort zurück. Dies kann eine HTML-Seite, ein Bild, ein XML-Dokument, das Senden einer Datei von der Festplatte, JSON oder die Routing zu einer anderen Seite sein. Wichtig ist, dass, wenn wir nicht ausdrücklich sagen, wie zu antworten ist (was bei `ProductPresenter` der Fall ist), die Antwort darin besteht, die Vorlage mit einer HTML-Seite wiederzugeben. Und warum? Nun, weil wir in 99 % der Fälle eine Vorlage zeichnen wollen, so dass der Präsentator dieses Verhalten als Standard annimmt und uns die Arbeit erleichtern will. Das ist der Punkt von Nette. +Anschließend gibt der Presenter eine Antwort zurück. Das kann eine HTML-Seite, ein Bild, ein XML-Dokument, das Senden einer Datei von der Festplatte, JSON oder auch eine Weiterleitung auf eine andere Seite sein. Wichtig ist, dass wenn wir nicht explizit sagen, wie geantwortet werden soll (was der Fall bei `ProductPresenter` ist), die Antwort das Rendern eines Templates mit einer HTML-Seite sein wird. Warum? Weil wir in 99 % der Fälle ein Template rendern wollen, daher nimmt der Presenter dieses Verhalten als Standard an und möchte uns die Arbeit erleichtern. Das ist der Sinn von Nette. -Wir müssen nicht einmal angeben, welche Vorlage gezeichnet werden soll, er leitet den Pfad dorthin nach einer einfachen Logik ab. Im Fall von presenter `Product` und action `show` versucht er zu sehen, ob eine dieser Vorlagendateien relativ zu dem Verzeichnis existiert, in dem sich die Klasse `ProductPresenter` befindet: +Wir müssen nicht einmal angeben, welches Template gerendert werden soll, den Pfad dazu leitet er selbst ab. Im Falle der Aktion `show` versucht er einfach, das Template `show.latte` im Verzeichnis der Klasse `ProductPresenter` zu laden. Ebenso versucht er, das Layout in der Datei `@layout.latte` zu finden (mehr dazu unter [Template-Suche |templates#Finden von Vorlagen]). -- `templates/Product/show.latte` -- `templates/Product.show.latte` - -Außerdem wird versucht, das Layout in der Datei `@layout.latte` zu finden, und dann wird die Vorlage gerendert. Nun ist die Aufgabe des Präsentators und der gesamten Anwendung abgeschlossen. Wenn die Vorlage nicht existiert, wird eine Seite mit dem Fehler 404 zurückgegeben. Weitere Informationen über Präsentatoren finden Sie auf der Seite [Präsentatoren |Presenters]. +Und anschließend rendert er die Templates. Damit ist die Aufgabe des Presenters und der gesamten Anwendung erfüllt und das Werk vollendet. Wenn das Template nicht existiert, wird eine Seite mit dem Fehler 404 zurückgegeben. Mehr über Presenter erfahren Sie auf der Seite [Presenter |presenters]. [* request-flow.svg *] -Um sicherzugehen, versuchen wir, den gesamten Prozess mit einer etwas anderen URL zu rekapitulieren: +Zur Sicherheit versuchen wir, den gesamten Prozess mit einer etwas anderen URL zu rekapitulieren: -1) Die URL lautet dann `https://example.com` -2) wir booten die Anwendung, erstellen einen Container und starten `Application::run()` -3) der Router dekodiert die URL als ein Paar `Home:default` -4) ein `HomePresenter` Objekt wird erstellt -5) die Methode `renderDefault()` wird aufgerufen (falls vorhanden) -6) eine Vorlage `templates/Home/default.latte` mit einem Layout `templates/@layout.latte` wird gerendert +1) Die URL lautet `https://example.com` +2) Wir booten die Anwendung, der DI-Container wird erstellt und `Application::run()` wird gestartet +3) Der Router dekodiert die URL als Paar `Home:default` +4) Ein Objekt der Klasse `HomePresenter` wird erstellt +5) Die Methode `renderDefault()` wird aufgerufen (falls sie existiert) +6) Das Template z.B. `default.latte` mit dem Layout z.B. `@layout.latte` wird gerendert -Vielleicht sind Sie jetzt auf eine Menge neuer Konzepte gestoßen, aber wir glauben, dass sie sinnvoll sind. Das Erstellen von Anwendungen in Nette ist ein Kinderspiel. +Vielleicht sind Sie jetzt auf viele neue Begriffe gestoßen, aber wir glauben, dass sie Sinn ergeben. Die Entwicklung von Anwendungen in Nette ist unglaublich entspannt. -Schablonen .[#toc-templates] -============================ +Templates +========= -Für die Vorlagen verwendet Nette das [Latte-Vorlagensystem |latte:]. Deshalb enden die Dateien mit Vorlagen mit `.latte`. Latte wird verwendet, weil es das sicherste Templatesystem für PHP und gleichzeitig das intuitivste System ist. Sie müssen nicht viel Neues lernen, Sie müssen nur PHP und ein paar Latte-Tags kennen. Sie werden alles [in der Dokumentation |latte:] finden. +Wo wir schon bei Templates sind, in Nette wird das Template-System [Latte |latte:] verwendet. Daher auch die Endungen `.latte` bei den Templates. Latte wird zum einen verwendet, weil es das sicherste Template-System für PHP ist, und zum anderen auch das intuitivste System. Sie müssen nicht viel Neues lernen, Sie kommen mit PHP-Kenntnissen und ein paar Tags aus. Alles erfahren Sie [in der Dokumentation |templates]. -In der Vorlage [erstellen wir eine Verknüpfung |creating-links] zu anderen Moderatoren & Aktionen wie folgt: +Im Template werden [Links erstellt |creating-links] zu anderen Presentern & Aktionen wie folgt: ```latte -product detail +Produktdetail ``` -Schreiben Sie einfach das bekannte `Presenter:action` -Paar anstelle der echten URL und fügen Sie beliebige Parameter ein. Der Trick ist `n:href`, das besagt, dass dieses Attribut von Nette verarbeitet wird. Und es wird erzeugt: +Einfach statt der realen URL schreiben Sie das bekannte Paar `Presenter:action` und geben eventuelle Parameter an. Der Trick liegt im `n:href`, das besagt, dass dieses Attribut von Nette verarbeitet wird. Und es generiert: ```latte -product detail +Produktdetail ``` -Der bereits erwähnte Router ist für die Generierung der URL zuständig. Die Router in Nette sind insofern einzigartig, als sie nicht nur Transformationen von einer URL in ein Presenter:Action-Paar durchführen können, sondern auch umgekehrt eine URL aus dem Namen des Presenters + Action + Parameter generieren können. -Dadurch kann man in Nette die Form der URL in der gesamten fertigen Anwendung komplett ändern, ohne auch nur ein einziges Zeichen in der Vorlage oder dem Presenter zu ändern, indem man einfach den Router modifiziert. -Und dank dessen funktioniert die so genannte Kanonisierung, ein weiteres einzigartiges Feature von Nette, das die SEO (Optimierung der Auffindbarkeit im Internet) verbessert, indem es automatisch das Vorhandensein von doppeltem Inhalt unter verschiedenen URLs verhindert. -Viele Programmierer finden das erstaunlich. +Die Generierung der URL übernimmt der bereits erwähnte Router. Denn Router in Nette sind außergewöhnlich, da sie nicht nur Transformationen von URL zum Paar Presenter:Aktion durchführen können, sondern auch umgekehrt, also aus dem Namen des Presenters + Aktion + Parametern eine URL generieren. Dadurch können Sie in Nette die Formen der URLs in der gesamten fertigen Anwendung vollständig ändern, ohne ein einziges Zeichen im Template oder Presenter zu ändern. Nur durch die Anpassung des Routers. Dadurch funktioniert auch die sogenannte Kanonisierung, was eine weitere einzigartige Eigenschaft von Nette ist, die zu besserem SEO (Optimierung der Auffindbarkeit im Internet) beiträgt, indem sie automatisch die Existenz von doppeltem Inhalt unter verschiedenen URLs verhindert. Viele Programmierer finden das erstaunlich. -Interaktive Komponenten .[#toc-interactive-components] -====================================================== +Interaktive Komponenten +======================= -Es gibt noch eine weitere Sache, die wir Ihnen über Presenter erzählen wollen: Sie haben ein eingebautes Komponentensystem. Die Älteren unter Ihnen erinnern sich vielleicht an etwas Ähnliches aus Delphi oder ASP.NET Web Forms. React oder Vue.js bauen auf etwas entfernt Ähnlichem auf. In der Welt der PHP-Frameworks ist dies eine völlig einzigartige Funktion. +Über Presenter müssen wir Ihnen noch eine Sache verraten: Sie haben ein eingebautes Komponentensystem. Etwas Ähnliches kennen Ältere vielleicht aus Delphi oder ASP.NET Web Forms, auf etwas entfernt Ähnlichem bauen React oder Vue.js auf. In der Welt der PHP-Frameworks ist dies eine absolut einzigartige Angelegenheit. -Komponenten sind separate wiederverwendbare Einheiten, die wir in Seiten (d.h. Präsentatoren) einfügen. Dabei kann es sich um [Formulare |forms:in-presenter], [Datenfelder |https://componette.org/contributte/datagrid/], Menüs, Umfragen, eigentlich um alles handeln, was für eine wiederholte Verwendung sinnvoll ist. Wir können unsere eigenen Komponenten erstellen oder eine der [zahlreichen |https://componette.org] Open-Source-Komponenten verwenden. +Komponenten sind eigenständige wiederverwendbare Einheiten, die wir in Seiten (also Presenter) einfügen. Das können [Formulare |forms:in-presenter], [Datagrids |https://componette.org/contributte/datagrid/], Menüs, Abstimmungsumfragen sein, eigentlich alles, was sinnvoll wiederverwendet werden kann. Wir können eigene Komponenten erstellen oder einige aus dem [riesigen Angebot |https://componette.org] an Open-Source-Komponenten verwenden. -Komponenten verändern den Ansatz der Anwendungsentwicklung grundlegend. Sie eröffnen neue Möglichkeiten, Seiten aus vordefinierten Einheiten zusammenzustellen. Und sie haben etwas mit [Hollywood |components#Hollywood style] gemeinsam. +Komponenten beeinflussen grundlegend den Ansatz zur Anwendungsentwicklung. Sie eröffnen Ihnen neue Möglichkeiten, Seiten aus vorgefertigten Einheiten zusammenzusetzen. Und außerdem haben sie etwas mit [Hollywood gemeinsam |components#Hollywood Style]. -DI-Container und Konfiguration .[#toc-di-container-and-configuration] -===================================================================== +DI-Container und Konfiguration +============================== -Der DI-Container (Fabrik für Objekte) ist das Herzstück der gesamten Anwendung. +Der DI-Container oder die Objekt-Factory ist das Herz der gesamten Anwendung. -Keine Sorge, es handelt sich nicht um eine magische Blackbox, wie es nach den vorangegangenen Worten den Anschein haben könnte. Eigentlich handelt es sich um eine ziemlich langweilige PHP-Klasse, die von Nette generiert und in einem Cache-Verzeichnis gespeichert wird. Sie hat eine Menge Methoden mit dem Namen `createServiceAbcd()` und jede von ihnen erzeugt ein Objekt und gibt es zurück. Ja, es gibt auch eine Methode `createServiceApplication()`, die `Nette\Application\Application` erzeugt, die wir in der Datei `index.php` benötigen, um die Anwendung auszuführen. Und es gibt Methoden zur Erzeugung einzelner Präsentatoren. Und so weiter. +Keine Sorge, es ist keine magische Blackbox, wie es vielleicht aus den vorherigen Zeilen scheinen mag. Eigentlich ist es eine ziemlich langweilige PHP-Klasse, die Nette generiert und im Cache-Verzeichnis speichert. Sie hat viele Methoden namens `createServiceAbcd()` und jede von ihnen kann ein bestimmtes Objekt erstellen und zurückgeben. Ja, es gibt auch eine Methode `createServiceApplication()`, die `Nette\Application\Application` erstellt, das wir in der Datei `index.php` zum Starten der Anwendung benötigten. Und es gibt Methoden, die einzelne Presenter erstellen. Und so weiter. -Die Objekte, die der DI-Container erzeugt, werden aus irgendeinem Grund Dienste genannt. +Objekte, die der DI-Container erstellt, werden aus irgendeinem Grund Dienste genannt. -Das Besondere an dieser Klasse ist, dass sie nicht von Ihnen programmiert wird, sondern von dem Framework. Es generiert den PHP-Code und speichert ihn auf der Festplatte. Sie geben lediglich Anweisungen, welche Objekte der Container wie genau erzeugen soll. Und diese Anweisungen werden in [Konfigurationsdateien |bootstrap#DI Container Configuration] im [NEON-Format |neon:format] geschrieben und haben daher die Endung `.neon`. +Was an dieser Klasse wirklich besonders ist, ist, dass nicht Sie sie programmieren, sondern das Framework. Es generiert tatsächlich den PHP-Code und speichert ihn auf der Festplatte. Sie geben nur Anweisungen, welche Objekte der Container erstellen können soll und wie genau. Und diese Anweisungen sind in [Konfigurationsdateien |bootstrapping#Konfiguration des DI-Containers] geschrieben, für die das Format [NEON|neon:format] verwendet wird und die daher auch die Erweiterung `.neon` haben. -Die Konfigurationsdateien dienen nur dazu, den DI-Container zu instruieren. Wenn ich also zum Beispiel die Option `expiration: 14 days` im Abschnitt [Session |http:configuration#Session] angebe, wird der DI-Container bei der Erstellung des `Nette\Http\Session` -Objekts, das die Session repräsentiert, seine Methode `setExpiration('14 days')` aufrufen, und damit wird die Konfiguration Wirklichkeit. +Konfigurationsdateien dienen rein dazu, den DI-Container zu instruieren. Wenn ich also zum Beispiel im Abschnitt [session |http:configuration#Session] die Option `expiration: 14 days` angebe, ruft der DI-Container beim Erstellen des Objekts `Nette\Http\Session`, das die Session repräsentiert, dessen Methode `setExpiration('14 days')` auf und damit wird die Konfiguration Realität. -Es steht ein ganzes Kapitel für Sie bereit, in dem beschrieben wird, was [konfiguriert |nette:configuring] werden kann und wie Sie [Ihre eigenen Dienste definieren |dependency-injection:services] können. +Es gibt für Sie ein ganzes Kapitel, das beschreibt, was alles [konfiguriert |nette:configuring] werden kann und wie man [eigene Dienste definiert |dependency-injection:services]. -Sobald Sie sich mit der Erstellung von Diensten befassen, werden Sie auf das Wort [Autowiring |dependency-injection:autowiring] stoßen. Das ist ein Gadget, das Ihnen das Leben unglaublich erleichtern wird. Es kann Objekte automatisch dort übergeben, wo Sie sie brauchen (z. B. in den Konstruktoren Ihrer Klassen), ohne dass Sie etwas tun müssen. Sie werden feststellen, dass der DI-Container in Nette ein kleines Wunderwerk ist. +Sobald Sie etwas tiefer in die Erstellung von Diensten eintauchen, werden Sie auf das Wort [Autowiring |dependency-injection:autowiring] stoßen. Das ist ein Feature, das Ihnen das Leben unglaublich vereinfacht. Es kann Objekte automatisch dorthin übergeben, wo Sie sie benötigen (z.B. in den Konstruktoren Ihrer Klassen), ohne dass Sie etwas tun müssen. Sie werden feststellen, dass der DI-Container in Nette ein kleines Wunder ist. -Wie geht es weiter? .[#toc-what-next] -===================================== +Wohin als nächstes? +=================== -Wir haben die Grundprinzipien von Anwendungen in Nette durchgenommen. Bisher sehr oberflächlich, aber Sie werden bald in die Tiefe gehen und schließlich wunderbare Webanwendungen erstellen. Wo soll es weitergehen? Haben Sie schon das Tutorial [Erstellen Sie Ihre erste Anwendung |quickstart:] ausprobiert? +Wir sind die Grundprinzipien von Anwendungen in Nette durchgegangen. Bisher sehr oberflächlich, aber bald werden Sie in die Tiefe vordringen und mit der Zeit wunderbare Webanwendungen erstellen. Wohin soll es als nächstes gehen? Haben Sie schon das Tutorial [Meine erste Anwendung schreiben|quickstart:] ausprobiert? -Darüber hinaus verfügt Nette über ein ganzes Arsenal an [nützlichen Klassen |utils:], [Datenbankschichten |database:] usw. Klicken Sie sich doch einfach mal durch die Dokumentation. Oder besuchen Sie den [Blog |https://blog.nette.org]. Sie werden eine Menge interessanter Dinge entdecken. +Neben dem oben Beschriebenen verfügt Nette über ein ganzes Arsenal an [nützlichen Klassen|utils:], eine [Datenbankschicht|database:], usw. Versuchen Sie doch mal, einfach so durch die Dokumentation zu klicken. Oder den [Blog|https://blog.nette.org]. Sie werden viele interessante Dinge entdecken. -Möge das Framework Ihnen viel Freude bereiten 💙. +Möge Ihnen das Framework viel Freude bereiten 💙 diff --git a/application/de/modules.texy b/application/de/modules.texy deleted file mode 100644 index fcdf4331c4..0000000000 --- a/application/de/modules.texy +++ /dev/null @@ -1,148 +0,0 @@ -Module -****** - -.[perex] -In Nette stellen Module die logischen Einheiten dar, aus denen eine Anwendung besteht. Sie umfassen Presenter, Templates, eventuell auch Komponenten und Modellklassen. - -Ein Verzeichnis für Presenter und eines für Templates würde für echte Projekte nicht ausreichen. Dutzende von Dateien in einem Ordner zu haben, ist zumindest unorganisiert. Wie kommt man da wieder raus? Wir teilen sie einfach in Unterverzeichnisse auf der Festplatte und in Namensräume im Code auf. Und das ist genau das, was die Nette-Module tun. - -Vergessen wir also einen einzigen Ordner für Präsentatoren und Vorlagen und erstellen wir stattdessen Module, zum Beispiel `Admin` und `Front`. - -/--pre -app/ -├── Presenters/ -├── Modules/ ← Verzeichnis mit Modulen -│ ├── Admin/ ← Modul Admin -│ │ ├── Presenters/ ← seine Presenters -│ │ │ ├── DashboardPresenter.php -│ │ │ └── templates/ -│ └── Front/ ← Modul Front -│ └── Presenters/ ← seine Presenters -│ └── ... -\-- - -Diese Verzeichnisstruktur spiegelt sich in den Klassennamensräumen wider, so dass z. B. `DashboardPresenter` im Namensraum `App\Modules\Admin\Presenters` liegt: - -```php -namespace App\Modules\Admin\Presenters; - -class DashboardPresenter extends Nette\Application\UI\Presenter -{ - // ... -} -``` - -Der Präsentator `Dashboard` innerhalb des Moduls `Admin` wird innerhalb der Anwendung mit der Doppelpunktschreibweise als `Admin:Dashboard` referenziert, und seine Aktion `default` als `Admin:Dashboard:default`. -Und woher weiß Nette selbst, dass `Admin:Dashboard` die Klasse `App\Modules\Admin\Presenters\DashboardPresenter` repräsentiert? Dies wird durch ein [Mapping |#mapping] in der Konfiguration festgelegt. -Die vorgegebene Struktur ist also nicht fest vorgegeben und kann nach Belieben verändert werden. - -Module können neben Presentern und Templates natürlich auch alle anderen Elemente enthalten, wie z.B. Komponenten, Modellklassen, etc. - - -Verschachtelte Module .[#toc-nested-modules] --------------------------------------------- - -Module müssen nicht nur eine flache Struktur bilden, Sie können auch Untermodule erstellen, zum Beispiel: - -/--pre -app/ -├── Modules/ ← Verzeichnis mit Modulen -│ ├── Blog/ ← Modul Blog -│ │ ├── Admin/ ← Submodul Admin -│ │ │ ├── Presenters/ -│ │ │ └── ... -│ │ └── Front/ ← Submodul Front -│ │ ├── Presenters/ -│ │ └── ... -│ ├── Forum/ ← Modul Forum -│ │ └── ... -\-- - -So wird das Modul `Blog` in die Untermodule `Admin` und `Front` aufgeteilt. Dies spiegelt sich auch in den Namensräumen wider, die dann `App\Modules\Blog\Admin\Presenters` usw. lauten. Der Präsentator `Dashboard` innerhalb des Submoduls wird als `Blog:Admin:Dashboard` bezeichnet. - -Die Verschachtelung kann beliebig tief gehen, so dass Sub-Submodule erstellt werden können. - - -Erstellen von Links .[#toc-creating-links] ------------------------------------------- - -Links in Präsentatorvorlagen sind relativ zum aktuellen Modul. So führt der Link `Foo:default` zu dem Präsentator `Foo` im gleichen Modul wie der aktuelle Präsentator. Wenn das aktuelle Modul zum Beispiel `Front` ist, sieht der Link so aus: - -```latte -link to Front:Product:show -``` - -Ein Link ist auch dann relativ, wenn er den Namen eines Moduls enthält, das dann als Untermodul betrachtet wird: - -```latte -link to Front:Shop:Product:show -``` - -Absolute Links werden analog zu absoluten Pfaden auf der Festplatte geschrieben, jedoch mit Doppelpunkten anstelle von Schrägstrichen. Ein absoluter Link beginnt also mit einem Doppelpunkt: - -```latte -link to Admin:Product:show -``` - -Um herauszufinden, ob wir uns in einem bestimmten Modul oder dessen Untermodul befinden, können wir die Funktion `isModuleCurrent(moduleName)` verwenden. - -```latte -
  • - ... -
  • -``` - - -Routing .[#toc-routing] ------------------------ - -Siehe [Kapitel über Routing |routing#Modules]. - - -Abbildung .[#toc-mapping] -------------------------- - -Legt die Regeln fest, nach denen der Klassenname aus dem Namen des Präsentators abgeleitet wird. Wir schreiben sie in die [Konfiguration |configuration] unter dem Schlüssel `application › mapping`. - -Beginnen wir mit einem Beispiel, das keine Module verwendet. Wir wollen nur, dass die Presenter-Klassen den Namespace `App\Presenters` haben. Das bedeutet, dass ein Presenter wie `Home` auf die Klasse `App\Presenters\HomePresenter` abgebildet werden soll. Dies kann durch die folgende Konfiguration erreicht werden: - -```neon -application: - mapping: - *: App\Presenters\*Presenter -``` - -Der Name des Presenters wird durch das Sternchen in der Klassenmaske ersetzt und das Ergebnis ist der Klassenname. Einfach! - -Wenn wir die Vortragenden in Module unterteilen, können wir für jedes Modul eine eigene Zuordnung vornehmen: - -```neon -application: - mapping: - Front: App\Modules\Front\Presenters\*Presenter - Admin: App\Modules\Admin\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -Der Referent `Front:Home` wird der Klasse `App\Modules\Front\Presenters\HomePresenter` zugeordnet und der Referent `Admin:Dashboard` der Klasse `App\Modules\Admin\Presenters\DashboardPresenter`. - -Es ist praktischer, eine allgemeine (Stern-)Regel zu erstellen, um die ersten beiden zu ersetzen. Das zusätzliche Sternchen wird der Klassenmaske nur für dieses Modul hinzugefügt: - -```neon -application: - mapping: - *: App\Modules\*\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -Was aber, wenn wir verschachtelte Module verwenden und einen Präsentator `Admin:User:Edit` haben? In diesem Fall wird das Segment mit dem Sternchen, das das Modul für jede Ebene darstellt, einfach wiederholt und das Ergebnis ist die Klasse `App\Modules\Admin\User\Presenters\EditPresenter`. - -Eine alternative Schreibweise ist die Verwendung eines Arrays, das aus drei Segmenten anstelle einer Zeichenkette besteht. Diese Notation ist gleichwertig mit der vorherigen: - -```neon -application: - mapping: - *: [App\Modules, *, Presenters\*Presenter] -``` - -Der Standardwert ist `*: *Module\*Presenter`. diff --git a/application/de/multiplier.texy b/application/de/multiplier.texy index 36f60fcc31..c4b504a032 100644 --- a/application/de/multiplier.texy +++ b/application/de/multiplier.texy @@ -1,30 +1,32 @@ -Multiplikator: Dynamische Komponenten -************************************* +Multiplier: dynamische Komponenten +********************************** -Ein Werkzeug zur dynamischen Erstellung von interaktiven Komponenten .[perex] +.[perex] +Werkzeug zur dynamischen Erstellung interaktiver Komponenten -Beginnen wir mit einem typischen Problem: Wir haben eine Liste von Produkten auf einer E-Commerce-Website und möchten jedes Produkt mit einem Formular *in den Warenkorb legen* begleiten. Eine Möglichkeit ist, die gesamte Liste in ein einziges Formular zu verpacken. Eine bequemere Möglichkeit ist die Verwendung von [api:Nette\Application\UI\Multiplier]. +Beginnen wir mit einem typischen Beispiel: Wir haben eine Liste von Waren in einem E-Shop, und für jede Ware möchten wir ein Formular zum Hinzufügen in den Warenkorb anzeigen. Eine mögliche Variante ist, die gesamte Auflistung in ein einziges Formular zu packen. Einen viel bequemeren Weg bietet uns jedoch [api:Nette\Application\UI\Multiplier]. -Multiplier ermöglicht es Ihnen, eine Fabrik für mehrere Komponenten zu definieren. Sie basiert auf dem Prinzip der verschachtelten Komponenten - jede Komponente, die von [api:Nette\ComponentModel\Container] erbt, kann andere Komponenten enthalten. +Multiplier ermöglicht es, bequem eine Factory für mehrere Komponenten zu definieren. Es funktioniert nach dem Prinzip verschachtelter Komponenten - jede Komponente, die von [api:Nette\ComponentModel\Container] erbt, kann weitere Komponenten enthalten. -Siehe [Komponentenmodell |components#Components in Depth] in der Dokumentation. .[tip] +.[tip] +Siehe das Kapitel über das [Komponentenmodell |components#Komponenten im Detail] in der Dokumentation oder den [Vortrag von Honza Tvrdík|https://www.youtube.com/watch?v=8y3LLexWu-I]. -Multiplier stellt eine übergeordnete Komponente dar, die ihre Kinder dynamisch über den im Konstruktor übergebenen Callback erzeugen kann. Siehe Beispiel: +Das Wesen des Multipliers ist, dass er als Elternteil fungiert, der seine Nachkommen dynamisch mithilfe eines im Konstruktor übergebenen Callbacks erstellen kann. Siehe Beispiel: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function () { $form = new Nette\Application\UI\Form; - $form->addInteger('amount', 'Amount:') + $form->addInteger('count', 'Anzahl der Artikel:') ->setRequired(); - $form->addSubmit('send', 'Add to cart'); + $form->addSubmit('send', 'In den Warenkorb legen'); return $form; }); } ``` -In der Vorlage können wir ein Formular für jedes Produkt darstellen - und jedes Formular wird tatsächlich eine einzigartige Komponente sein. +Jetzt können wir im Template einfach für jeden Artikel das Formular rendern lassen - und jedes wird tatsächlich eine eindeutige Komponente sein. ```latte {foreach $items as $item} @@ -35,26 +37,26 @@ In der Vorlage können wir ein Formular für jedes Produkt darstellen - und jede {/foreach} ``` -Das Argument, das an den `{control}` -Tag übergeben wird, lautet: +Das im Tag `{control}` übergebene Argument hat ein Format, das besagt: -1. eine Komponente erhalten `shopForm` -2. und ihr Kind zurückgeben `$item->id` +1. hole die Komponente `shopForm` +2. und hole daraus den Nachkommen `$item->id` -Beim ersten Aufruf von **1.** ist die Komponente `shopForm` noch nicht vorhanden, daher wird die Methode `createComponentShopForm` aufgerufen, um sie zu erstellen. Eine anonyme Funktion, die als Parameter an Multiplier übergeben wird, wird dann aufgerufen und ein Formular wird erstellt. +Beim ersten Aufruf von Punkt **1.** existiert `shopForm` noch nicht, also wird seine Factory `createComponentShopForm` aufgerufen. Auf der erhaltenen Komponente (Instanz des Multipliers) wird dann die Factory des konkreten Formulars aufgerufen - was die anonyme Funktion ist, die wir dem Multiplier im Konstruktor übergeben haben. -In den folgenden Iterationen des `foreach` wird die Methode `createComponentShopForm` nicht mehr aufgerufen, da die Komponente bereits existiert. Da wir aber auf ein anderes Kind verweisen (`$item->id` variiert zwischen den Iterationen), wird erneut eine anonyme Funktion aufgerufen und ein neues Formular erstellt. +In der nächsten Iteration des foreache wird die Methode `createComponentShopForm` nicht mehr aufgerufen (die Komponente existiert), aber da wir einen anderen Nachkommen suchen (`$item->id` wird in jeder Iteration anders sein), wird die anonyme Funktion erneut aufgerufen und gibt uns ein neues Formular zurück. -Als Letztes müssen wir sicherstellen, dass das Formular tatsächlich das richtige Produkt zum Warenkorb hinzufügt, denn im aktuellen Zustand sind alle Formulare gleich und wir können nicht unterscheiden, zu welchen Produkten sie gehören. Hierfür können wir die Eigenschaft von Multiplier (und generell von jeder Komponentenfabrikmethode im Nette Framework) nutzen, dass jede Komponentenfabrikmethode den Namen der erstellten Komponente als erstes Argument erhält. In unserem Fall wäre das `$item->id`, was genau das ist, was wir brauchen, um einzelne Produkte zu unterscheiden. Alles, was Sie tun müssen, ist, den Code für die Erstellung des Formulars zu ändern: +Das Einzige, was übrig bleibt, ist sicherzustellen, dass das Formular tatsächlich die richtige Ware in den Warenkorb legt - derzeit ist das Formular für jeden Artikel völlig identisch. Hier hilft uns eine Eigenschaft des Multipliers (und generell jeder Komponenten-Factory im Nette Framework), nämlich dass jede Factory als erstes Argument den Namen der zu erstellenden Komponente erhält. In unserem Fall ist das `$item->id`, was genau die Information ist, die wir benötigen. Es genügt also, die Erstellung des Formulars leicht anzupassen: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function ($itemId) { $form = new Nette\Application\UI\Form; - $form->addInteger('amount', 'Amount:') + $form->addInteger('count', 'Anzahl der Artikel:') ->setRequired(); $form->addHidden('itemId', $itemId); - $form->addSubmit('send', 'Add to cart'); + $form->addSubmit('send', 'In den Warenkorb legen'); return $form; }); } diff --git a/application/de/presenters.texy b/application/de/presenters.texy index 57c36c0bd7..e8764b84bc 100644 --- a/application/de/presenters.texy +++ b/application/de/presenters.texy @@ -1,39 +1,39 @@ -Vortragende -*********** +Presenter +*********
    -Wir werden lernen, wie man Presenter und Vorlagen in Nette schreibt. Nach der Lektüre werden Sie wissen: +Wir werden lernen, wie man Presenter und Templates in Nette schreibt. Nach dem Lesen werden Sie wissen: -- wie der Presenter funktioniert +- wie ein Presenter funktioniert - was persistente Parameter sind -- wie man eine Vorlage rendert +- wie Templates gerendert werden
    -[Wir wissen bereits, |how-it-works#nette-application] dass ein Presenter eine Klasse ist, die eine bestimmte Seite einer Webanwendung darstellt, z. B. eine Homepage, ein Produkt im E-Shop, ein Anmeldeformular, ein Sitemap-Feed usw. Die Anwendung kann von einem bis zu Tausenden von Presentern haben. In anderen Frameworks werden sie auch als Controller bezeichnet. +[Wir wissen bereits |how-it-works#Nette Application], dass ein Presenter eine Klasse ist, die eine bestimmte Seite einer Webanwendung repräsentiert, z. B. die Homepage, ein Produkt in einem E-Shop, ein Anmeldeformular, einen Sitemap-Feed usw. Eine Anwendung kann einen bis tausende Presenter haben. In anderen Frameworks werden sie auch Controller genannt. -Normalerweise bezieht sich der Begriff Presenter auf einen Abkömmling der Klasse [api:Nette\Application\UI\Presenter], die für Web-Interfaces geeignet ist und die wir im weiteren Verlauf dieses Kapitels besprechen werden. Im allgemeinen Sinne ist ein Presenter jedes Objekt, das die Schnittstelle [api:Nette\Application\IPresenter] implementiert. +Normalerweise ist mit dem Begriff Presenter ein Nachkomme der Klasse [api:Nette\Application\UI\Presenter] gemeint, der für die Generierung von Weboberflächen geeignet ist und dem wir uns im Rest dieses Kapitels widmen werden. Im allgemeinen Sinn ist ein Presenter jedes Objekt, das das Interface [api:Nette\Application\IPresenter] implementiert. -Lebenszyklus eines Presenters .[#toc-life-cycle-of-presenter] -============================================================= +Lebenszyklus des Presenters +=========================== -Die Aufgabe des Presenters besteht darin, die Anfrage zu verarbeiten und eine Antwort zu liefern (die eine HTML-Seite, ein Bild, eine Routing usw. sein kann). +Die Aufgabe des Presenters ist es, eine Anfrage zu bearbeiten und eine Antwort zurückzugeben (dies kann eine HTML-Seite, ein Bild, eine Weiterleitung usw. sein). -Am Anfang steht also eine Anfrage. Dabei handelt es sich nicht direkt um eine HTTP-Anfrage, sondern um ein [api:Nette\Application\Request] -Objekt, in das die HTTP-Anfrage mithilfe eines Routers umgewandelt wurde. Mit diesem Objekt kommen wir in der Regel nicht in Berührung, da der Präsentator die Verarbeitung der Anfrage geschickt an spezielle Methoden delegiert, die wir jetzt sehen werden. +Zu Beginn wird ihm also eine Anfrage übergeben. Dies ist keine direkte HTTP-Anfrage, sondern ein Objekt [api:Nette\Application\Request], in das die HTTP-Anfrage mithilfe des Routers umgewandelt wurde. Mit diesem Objekt kommen wir normalerweise nicht in Berührung, da der Presenter die Verarbeitung der Anfrage geschickt an weitere Methoden delegiert, die wir uns jetzt ansehen werden. [* lifecycle.svg *] *** *Lebenszyklus des Presenters* .<> -Die Abbildung zeigt eine Liste von Methoden, die nacheinander von oben nach unten aufgerufen werden, sofern sie existieren. Keine von ihnen muss existieren, wir können einen völlig leeren Presenter ohne eine einzige Methode haben und ein einfaches statisches Web darauf aufbauen. +Das Bild zeigt eine Liste von Methoden, die der Reihe nach von oben nach unten aufgerufen werden, sofern sie existieren. Keine davon muss existieren, wir können einen völlig leeren Presenter ohne eine einzige Methode haben und darauf eine einfache statische Website aufbauen. `__construct()` --------------- -Der Konstruktor gehört nicht direkt zum Lebenszyklus des Präsentators, da er zum Zeitpunkt der Erstellung des Objekts aufgerufen wird. Aber wir erwähnen ihn wegen seiner Bedeutung. Der Konstruktor (zusammen mit der [Methode inject |best-practices:inject-method-attribute]) wird verwendet, um Abhängigkeiten zu übergeben. +Der Konstruktor gehört nicht ganz zum Lebenszyklus des Presenters, da er im Moment der Objekterstellung aufgerufen wird. Aber wir erwähnen ihn wegen seiner Wichtigkeit. Der Konstruktor (zusammen mit der [inject-Methode|best-practices:inject-method-attribute]) dient zur Übergabe von Abhängigkeiten. -Der Presenter sollte sich nicht um die Geschäftslogik der Anwendung kümmern, nicht in die Datenbank schreiben und aus ihr lesen, keine Berechnungen durchführen, usw. Dies ist die Aufgabe für Klassen aus einer Schicht, die wir Modell nennen. Zum Beispiel kann die Klasse `ArticleRepository` für das Laden und Speichern von Artikeln zuständig sein. Damit der Presenter sie verwenden kann, wird sie [mittels Dependency Injection übergeben |dependency-injection:passing-dependencies]: +Ein Presenter sollte nicht die Geschäftslogik der Anwendung handhaben, aus der Datenbank schreiben und lesen, Berechnungen durchführen usw. Dafür gibt es Klassen aus der Schicht, die wir als Model bezeichnen. Zum Beispiel kann die Klasse `ArticleRepository` für das Laden und Speichern von Artikeln zuständig sein. Damit der Presenter damit arbeiten kann, lässt er sie sich [mittels Dependency Injection übergeben |dependency-injection:passing-dependencies]: ```php @@ -50,44 +50,44 @@ class ArticlePresenter extends Nette\Application\UI\Presenter `startup()` ----------- -Unmittelbar nach Erhalt der Anfrage wird die Methode `startup ()` aufgerufen. Sie können sie verwenden, um Eigenschaften zu initialisieren, Benutzerrechte zu prüfen, usw. Es ist erforderlich, immer den Vorgänger `parent::startup()` aufzurufen. +Unmittelbar nach Erhalt der Anfrage wird die Methode `startup()` aufgerufen. Sie können sie zur Initialisierung von Properties, zur Überprüfung von Benutzerberechtigungen usw. verwenden. Es ist erforderlich, dass die Methode immer den Vorfahren `parent::startup()` aufruft. `action(args...)` .{toc: action()} -------------------------------------------------- -Ähnlich wie bei der Methode `render()`. Während `render()` dazu gedacht ist, Daten für eine bestimmte Vorlage vorzubereiten, die anschließend gerendert wird, wird in `action()` wird eine Anfrage ohne anschließendes Rendering der Vorlage verarbeitet. So werden z. B. Daten verarbeitet, ein Benutzer an- oder abgemeldet usw., und dann wird er [an eine andere Stelle weitergeleitet |#Redirection]. +Ähnlich der Methode `render()`. Während `render()` dazu dient, Daten für ein bestimmtes Template vorzubereiten, das anschließend gerendert wird, wird in `action()` die Anfrage ohne Bezug zum Rendern des Templates verarbeitet. Zum Beispiel werden Daten verarbeitet, der Benutzer an- oder abgemeldet usw., und danach [woandershin weitergeleitet |#Weiterleitung]. -Es ist wichtig, dass `action()` vor aufgerufen wird `render()`aufgerufen wird, damit wir darin möglicherweise den weiteren Verlauf des Lebenszyklus ändern können, d. h. die Vorlage, die gerendert wird, und auch die Methode `render()` die aufgerufen wird, mit `setView('otherView')`. +Wichtig ist, dass `action()` vor `render()` aufgerufen wird, sodass wir darin gegebenenfalls den weiteren Verlauf ändern können, d.h. das zu rendernde Template und auch die aufzurufende `render()`-Methode ändern können. Und zwar mittels `setView('anderesView')`. -Die Parameter der Anfrage werden an die Methode übergeben. Es ist möglich und empfehlenswert, Typen für die Parameter anzugeben, z. B. `actionShow(int $id, string $slug = null)` - wenn der Parameter `id` fehlt oder keine ganze Zahl ist, gibt der Präsentator den [Fehler 404 |#Error 404 etc.] zurück und bricht die Operation ab. +Der Methode werden Parameter aus der Anfrage übergeben. Es ist möglich und empfohlen, den Parametern Typen anzugeben, z.B. `actionShow(int $id, ?string $slug = null)` - wenn der Parameter `id` fehlt oder kein Integer ist, gibt der Presenter einen [Fehler 404 |#Fehler 404 und Co] zurück und beendet die Tätigkeit. `handle(args...)` .{toc: handle()} -------------------------------------------------- -Diese Methode verarbeitet die sogenannten Signale, die wir im Kapitel über [Komponenten |components#Signal] besprechen werden. Sie ist hauptsächlich für Komponenten und die Verarbeitung von AJAX-Anfragen gedacht. +Diese Methode verarbeitet sogenannte Signale, die wir im Kapitel über [Komponenten |components#Signal] kennenlernen werden. Sie ist nämlich hauptsächlich für Komponenten und die Verarbeitung von AJAX-Anfragen gedacht. -Die Parameter werden an die Methode übergeben, wie im Fall von `action()`übergeben, einschließlich der Typüberprüfung. +Der Methode werden Parameter aus der Anfrage übergeben, wie im Fall von `action()`, einschließlich Typüberprüfung. `beforeRender()` ---------------- -Die Methode `beforeRender` wird, wie der Name schon sagt, vor jeder Methode aufgerufen `render()`. Sie wird für die allgemeine Konfiguration von Vorlagen, die Übergabe von Variablen für das Layout und so weiter verwendet. +Die Methode `beforeRender`, wie der Name schon sagt, wird vor jeder `render()`-Methode aufgerufen. Sie wird für die gemeinsame Konfiguration des Templates, die Übergabe von Variablen für das Layout und ähnliches verwendet. `render(args...)` .{toc: render()} ---------------------------------------------- -Der Ort, an dem wir die Vorlage für das spätere Rendering vorbereiten, Daten an sie übergeben usw. +Der Ort, an dem wir das Template für das anschließende Rendern vorbereiten, ihm Daten übergeben usw. -Die Parameter werden an die Methode übergeben, wie im Fall von `action()`übergeben, einschließlich der Typüberprüfung. +Der Methode werden Parameter aus der Anfrage übergeben, wie im Fall von `action()`, einschließlich Typüberprüfung. ```php public function renderShow(int $id): void { - // wir beziehen Daten aus dem Modell und übergeben sie an die Vorlage + // Wir holen Daten aus dem Model und übergeben sie an das Template $this->template->article = $this->articles->getById($id); } ``` @@ -96,104 +96,104 @@ public function renderShow(int $id): void `afterRender()` --------------- -Die Methode `afterRender` wird, wie der Name schon sagt, nach jeder `render()` Methode aufgerufen. Sie wird eher selten verwendet. +Die Methode `afterRender`, wie der Name wiederum andeutet, wird nach jeder `render()`-Methode aufgerufen. Sie wird eher selten verwendet. `shutdown()` ------------ -Sie wird am Ende des Lebenszyklus des Präsentators aufgerufen. +Wird am Ende des Lebenszyklus des Presenters aufgerufen. -**Guter Rat, bevor wir weitermachen**. Wie Sie sehen können, kann der Präsentator mehr Aktionen/Ansichten verarbeiten, d.h. mehr Methoden haben `render()`. Wir empfehlen jedoch, Presenter mit einer oder so wenigen Aktionen wie möglich zu entwerfen. +**Ein guter Rat, bevor wir weitermachen**. Ein Presenter kann, wie man sieht, mehrere Aktionen/Views bedienen, also mehrere `render()`-Methoden haben. Wir empfehlen jedoch, Presenter mit einer oder möglichst wenigen Aktionen zu entwerfen. -Senden einer Antwort .[#toc-sending-a-response] -=============================================== +Senden der Antwort +================== -Die Antwort des Präsentators ist in der Regel das [Rendern der Vorlage mit der HTML-Seite |templates], aber es kann auch das Senden einer Datei, JSON oder sogar die Routing zu einer anderen Seite sein. +Die Antwort des Presenters ist normalerweise das [Rendern eines Templates mit einer HTML-Seite|templates], es kann aber auch das Senden einer Datei, JSON oder eine Weiterleitung zu einer anderen Seite sein. -Zu jedem Zeitpunkt des Lebenszyklus können Sie eine der folgenden Methoden verwenden, um eine Antwort zu senden und gleichzeitig den Präsentator zu beenden: +Wir können jederzeit während des Lebenszyklus mit einer der folgenden Methoden eine Antwort senden und gleichzeitig den Presenter beenden: -- `redirect()`, `redirectPermanent()`, `redirectUrl()` und `forward()` [leitet um |#Redirection] -- `error()` verlässt den Präsentator [aufgrund eines Fehlers |#Error 404 etc.] -- `sendJson($data)` verlässt den Presenter und [sendet die Daten |#Sending JSON] im JSON-Format -- `sendTemplate()` verlässt den Presenter und [rendert |templates] sofort [die Vorlage |templates] -- `sendResponse($response)` verlässt den Präsentator und sendet [eine eigene Antwort |#Responses] -- `terminate()` verlässt den Präsentator ohne Antwort +- `redirect()`, `redirectPermanent()`, `redirectUrl()` und `forward()` [leiten weiter |#Weiterleitung] +- `error()` beendet den Presenter [aufgrund eines Fehlers |#Fehler 404 und Co] +- `sendJson($data)` beendet den Presenter und [sendet Daten |#Senden von JSON] im JSON-Format +- `sendTemplate()` beendet den Presenter und [rendert sofort das Template |templates] +- `sendResponse($response)` beendet den Presenter und sendet eine [eigene Antwort |#Antworten] +- `terminate()` beendet den Presenter ohne Antwort -Wenn Sie keine dieser Methoden aufrufen, fährt der Presenter automatisch mit dem Rendern der Vorlage fort. Und warum? Nun, weil wir in 99% der Fälle eine Vorlage zeichnen wollen, also nimmt der Präsentator dieses Verhalten als Standard an und will uns die Arbeit erleichtern. +Wenn Sie keine dieser Methoden aufrufen, greift der Presenter automatisch auf das Rendern des Templates zurück. Warum? Weil wir in 99 % der Fälle ein Template rendern möchten, daher betrachtet der Presenter dieses Verhalten als Standard und möchte uns die Arbeit erleichtern. -Links erstellen .[#toc-creating-links] -====================================== +Erstellen von Links +=================== -Presenter hat eine Methode `link()`, die verwendet wird, um URL-Links zu anderen Presentern zu erstellen. Der erste Parameter ist der Zielmoderator und die Aktion, gefolgt von den Argumenten, die als Array übergeben werden können: +Der Presenter verfügt über die Methode `link()`, mit der URL-Links zu anderen Presentern erstellt werden können. Der erste Parameter ist der Ziel-Presenter & Aktion, gefolgt von den übergebenen Argumenten, die als Array angegeben werden können: ```php $url = $this->link('Product:show', $id); -$url = $this->link('Product:show', [$id, 'lang' => 'en']); +$url = $this->link('Product:show', [$id, 'lang' => 'cs']); ``` -In der Vorlage erstellen wir Links zu anderen Präsentatoren und Aktionen wie folgt: +Im Template werden Links zu anderen Presentern & Aktionen auf diese Weise erstellt: ```latte -product detail +Produktdetail ``` -Schreiben Sie einfach das bekannte Paar `Presenter:action` anstelle der echten URL und fügen Sie beliebige Parameter ein. Der Trick ist `n:href`, das besagt, dass dieses Attribut von Latte verarbeitet wird und eine echte URL erzeugt. In Nette müssen Sie überhaupt nicht über URLs nachdenken, sondern nur über Presenter und Aktionen. +Statt der echten URL schreiben Sie einfach das bekannte Paar `Presenter:action` und geben eventuelle Parameter an. Der Trick liegt in `n:href`, das besagt, dass dieses Attribut von Latte verarbeitet wird und die echte URL generiert. In Nette müssen Sie also überhaupt nicht über URLs nachdenken, nur über Presenter und Aktionen. -Weitere Informationen finden Sie unter [Links erstellen |Creating Links]. +Weitere Informationen finden Sie im Kapitel [Erstellen von URL-Links|creating-links]. -Umleitung .[#toc-redirection] -============================= +Weiterleitung +============= -Die Methoden `redirect()` und `forward()` werden verwendet, um zu einem anderen Präsentator zu springen. Sie haben eine sehr ähnliche Syntax wie die Methode [link() |#Creating Links]. +Zum Wechsel zu einem anderen Presenter dienen die Methoden `redirect()` und `forward()`, die eine sehr ähnliche Syntax wie die Methode [link() |#Erstellen von Links] haben. -Die `forward()` schaltet ohne HTTP-Umleitung sofort auf den neuen Präsentator um: +Die Methode `forward()` wechselt sofort zum neuen Presenter ohne HTTP-Weiterleitung: ```php $this->forward('Product:show'); ``` -Beispiel einer temporären Umleitung mit HTTP-Code 302 oder 303: +Ein Beispiel für eine sogenannte temporäre Weiterleitung mit dem HTTP-Code 302 (oder 303, wenn die Methode der aktuellen Anfrage POST ist): ```php $this->redirect('Product:show', $id); ``` -Um eine dauerhafte Umleitung mit HTTP-Code 301 zu erreichen, verwenden Sie: +Eine permanente Weiterleitung mit dem HTTP-Code 301 erreichen Sie so: ```php $this->redirectPermanent('Product:show', $id); ``` -Sie können mit der Methode `redirectUrl()` zu einer anderen URL außerhalb der Anwendung umleiten: +Zu einer anderen URL außerhalb der Anwendung kann mit der Methode `redirectUrl()` weitergeleitet werden. Als zweiter Parameter kann der HTTP-Code angegeben werden, Standard ist 302 (oder 303, wenn die Methode der aktuellen Anfrage POST ist): ```php $this->redirectUrl('https://nette.org'); ``` -Die Umleitung beendet sofort den Lebenszyklus des Präsentators, indem sie die sogenannte Silent Termination Exception `Nette\Application\AbortException` auslöst. +Die Weiterleitung beendet sofort die Tätigkeit des Presenters durch Auslösen der sogenannten stillen Beendigungs-Ausnahme `Nette\Application\AbortException`. -Vor der Routing ist es möglich, eine [Flash-Nachricht |#Flash Messages] zu senden, die nach der Routing in der Vorlage angezeigt wird. +Vor der Weiterleitung können [#Flash-Nachrichten] gesendet werden, also Nachrichten, die nach der Weiterleitung im Template angezeigt werden. -Flash-Nachrichten .[#toc-flash-messages] -======================================== +Flash-Nachrichten +================= -Dies sind Meldungen, die in der Regel über das Ergebnis eines Vorgangs informieren. Ein wichtiges Merkmal von Flash-Meldungen ist, dass sie auch nach einer Routing in der Vorlage verfügbar sind. Selbst nachdem sie angezeigt wurden, bleiben sie noch 30 Sekunden lang erhalten - zum Beispiel für den Fall, dass der Benutzer die Seite unbeabsichtigt aktualisiert - die Nachricht geht nicht verloren. +Dies sind Nachrichten, die normalerweise über das Ergebnis einer Operation informieren. Ein wichtiges Merkmal von Flash-Nachrichten ist, dass sie auch nach einer Weiterleitung im Template verfügbar sind. Auch nach der Anzeige bleiben sie noch weitere 30 Sekunden aktiv – zum Beispiel für den Fall, dass der Benutzer aufgrund einer fehlerhaften Übertragung die Seite neu lädt - die Nachricht verschwindet also nicht sofort. -Rufen Sie einfach die Methode [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] auf und Presenter kümmert sich um die Übergabe der Nachricht an die Vorlage. Das erste Argument ist der Text der Nachricht und das zweite optionale Argument ist ihr Typ (Fehler, Warnung, Info usw.). Die Methode `flashMessage()` gibt eine Instanz von flash message zurück, damit wir weitere Informationen hinzufügen können. +Rufen Sie einfach die Methode [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] auf, und der Presenter kümmert sich um die Übergabe an das Template. Der erste Parameter ist der Text der Nachricht und der optionale zweite Parameter ihr Typ (error, warning, info usw.). Die Methode `flashMessage()` gibt eine Instanz der Flash-Nachricht zurück, der weitere Informationen hinzugefügt werden können. ```php -$this->flashMessage('Item was removed.'); -$this->redirect(/* ... */); +$this->flashMessage('Der Eintrag wurde gelöscht.'); +$this->redirect(/* ... */); // und wir leiten weiter ``` -In der Vorlage sind diese Meldungen in der Variablen `$flashes` als Objekte `stdClass` verfügbar, die die Eigenschaften `message` (Meldungstext) und `type` (Meldungstyp) enthalten und die bereits erwähnten Benutzerinformationen enthalten können. Wir zeichnen sie wie folgt: +Im Template stehen diese Nachrichten in der Variable `$flashes` als `stdClass`-Objekte zur Verfügung, die die Eigenschaften `message` (Nachrichtentext), `type` (Nachrichtentyp) enthalten und die bereits erwähnten Benutzerinformationen enthalten können. Wir rendern sie zum Beispiel so: ```latte {foreach $flashes as $flash} @@ -202,10 +202,10 @@ In der Vorlage sind diese Meldungen in der Variablen `$flashes` als Objekte `std ``` -Fehler 404 usw. .[#toc-error-404-etc] -===================================== +Fehler 404 und Co. +================== -Wenn wir die Anfrage nicht erfüllen können, weil z.B. der Artikel, den wir anzeigen wollen, nicht in der Datenbank existiert, werden wir den Fehler 404 mit der Methode `error(string $message = null, int $httpCode = 404)` ausgeben, die den HTTP-Fehler 404 darstellt: +Wenn die Anfrage nicht erfüllt werden kann, zum Beispiel weil der Artikel, den wir anzeigen möchten, nicht in der Datenbank existiert, werfen wir einen 404-Fehler mit der Methode `error(?string $message = null, int $httpCode = 404)` aus. ```php public function renderShow(int $id): void @@ -218,14 +218,13 @@ public function renderShow(int $id): void } ``` -Der HTTP-Fehlercode kann als zweiter Parameter übergeben werden, der Standardwert ist 404. Die Methode funktioniert, indem sie die Ausnahme `Nette\Application\BadRequestException` auslöst, woraufhin `Application` die Kontrolle an den Fehler-Moderator weitergibt. Dabei handelt es sich um einen Presenter, dessen Aufgabe es ist, eine Seite anzuzeigen, die über den Fehler informiert. -Der Fehler-Presenter wird in der [Anwendungskonfiguration |configuration] festgelegt. +Der HTTP-Fehlercode kann als zweiter Parameter übergeben werden, Standard ist 404. Die Methode funktioniert so, dass sie die Ausnahme `Nette\Application\BadRequestException` auslöst, woraufhin die `Application` die Steuerung an den Error-Presenter übergibt. Dies ist ein Presenter, dessen Aufgabe es ist, eine Seite anzuzeigen, die über den aufgetretenen Fehler informiert. Die Einstellung des Error-Presenters erfolgt in der [Anwendungskonfiguration|configuration]. -Senden von JSON .[#toc-sending-json] -==================================== +Senden von JSON +=============== -Beispiel für eine Action-Methode, die Daten im JSON-Format sendet und den Präsentator verlässt: +Ein Beispiel für eine Action-Methode, die Daten im JSON-Format sendet und den Presenter beendet: ```php public function actionData(): void @@ -236,33 +235,59 @@ public function actionData(): void ``` -Dauerhafte Parameter .[#toc-persistent-parameters] -================================================== +Anfrageparameter .{data-version:3.1.14} +======================================= + +Der Presenter und auch jede Komponente erhalten ihre Parameter aus der HTTP-Anfrage. Ihren Wert erhalten Sie mit der Methode `getParameter($name)` oder `getParameters()`. Die Werte sind Zeichenketten oder Arrays von Zeichenketten, es handelt sich im Grunde um Rohdaten, die direkt aus der URL stammen. + +Für mehr Komfort empfehlen wir, die Parameter über Properties zugänglich zu machen. Markieren Sie sie einfach mit dem Attribut `#[Parameter]`: + +```php +use Nette\Application\Attributes\Parameter; // diese Zeile ist wichtig + +class HomePresenter extends Nette\Application\UI\Presenter +{ + #[Parameter] + public string $theme; // muss public sein +} +``` + +Bei der Property empfehlen wir, auch den Datentyp anzugeben (z.B. `string`), und Nette wandelt den Wert entsprechend automatisch um. Die Parameterwerte können auch [validiert werden |#Validierung von Parametern]. + +Beim Erstellen eines Links kann der Wert der Parameter direkt festgelegt werden: + +```latte +klicken +``` -Persistente Parameter werden verwendet, um den Zustand zwischen verschiedenen Anfragen zu erhalten. Ihr Wert bleibt gleich, auch wenn ein Link angeklickt wird. Im Gegensatz zu Sitzungsdaten werden sie in der URL übergeben. Dies geschieht völlig automatisch, so dass es nicht notwendig ist, sie in `link()` oder `n:href` explizit anzugeben. -Beispiel für die Verwendung? Sie haben eine mehrsprachige Anwendung. Die aktuelle Sprache ist ein Parameter, der immer Teil der URL sein muss. Es wäre aber unglaublich mühsam, ihn in jeden Link aufzunehmen. Also machen Sie ihn zu einem dauerhaften Parameter mit dem Namen `lang` und er wird sich selbst tragen. Toll! +Persistente Parameter +===================== -Das Erstellen eines dauerhaften Parameters ist in Nette extrem einfach. Erstellen Sie einfach eine öffentliche Eigenschaft und kennzeichnen Sie sie mit dem Attribut: (früher wurde `/** @persistent */` verwendet) +Persistente Parameter dienen dazu, den Zustand zwischen verschiedenen Anfragen aufrechtzuerhalten. Ihr Wert bleibt auch nach dem Klicken auf einen Link gleich. Im Gegensatz zu Daten in der Session werden sie in der URL übertragen. Und das völlig automatisch, es ist also nicht notwendig, sie explizit in `link()` oder `n:href` anzugeben. + +Ein Anwendungsbeispiel? Sie haben eine mehrsprachige Anwendung. Die aktuelle Sprache ist ein Parameter, der ständig Teil der URL sein muss. Aber es wäre unglaublich mühsam, ihn in jedem Link anzugeben. Also machen Sie daraus einen persistenten Parameter `lang`, und er wird von selbst übertragen. Großartig! + +Das Erstellen eines persistenten Parameters ist in Nette extrem einfach. Erstellen Sie einfach eine öffentliche Property und markieren Sie sie mit einem Attribut: (früher wurde `/** @persistent */` verwendet) ```php -use Nette\Application\Attributes\Persistent; // diese Zeile ist wichtig +use Nette\Application\Attributes\Persistent; // diese Zeile ist wichtig class ProductPresenter extends Nette\Application\UI\Presenter { #[Persistent] - public string $lang; // muss öffentlich sein + public string $lang; // muss public sein } ``` -Wenn `$this->lang` einen Wert wie `'en'` hat, dann werden Links, die mit `link()` oder `n:href` erstellt werden, auch den Parameter `lang=en` enthalten. Und wenn der Link angeklickt wird, wird er wieder `$this->lang = 'en'` sein. +Wenn `$this->lang` beispielsweise den Wert `'en'` hat, dann werden auch Links, die mit `link()` oder `n:href` erstellt wurden, den Parameter `lang=en` enthalten. Und nach dem Klicken auf den Link wird wieder `$this->lang = 'en'` sein. -Für Eigenschaften wird empfohlen, den Datentyp anzugeben (z. B. `string`), und Sie können auch einen Standardwert angeben. Parameterwerte können [validiert |#Validation of Persistent Parameters] werden. +Bei der Property empfehlen wir, auch den Datentyp anzugeben (z.B. `string`), und Sie können auch einen Standardwert angeben. Die Parameterwerte können [validiert werden |#Validierung von Parametern]. -Persistente Parameter werden standardmäßig zwischen allen Aktionen eines bestimmten Präsentators weitergegeben. Um sie zwischen mehreren Präsentatoren zu übergeben, müssen Sie sie entweder definieren: +Persistente Parameter werden standardmäßig zwischen allen Aktionen des jeweiligen Presenters übertragen. Damit sie auch zwischen mehreren Presentern übertragen werden, müssen sie entweder definiert werden: -- in einem gemeinsamen Vorfahren, von dem die Präsentatoren erben -- in der Eigenschaft, die die Präsentatoren verwenden: +- in einem gemeinsamen Vorfahren, von dem die Presenter erben +- in einem Trait, den die Presenter verwenden: ```php trait LanguageAware @@ -277,48 +302,42 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -Sie können den Wert eines dauerhaften Parameters ändern, wenn Sie einen Link erstellen: +Beim Erstellen eines Links kann der Wert eines persistenten Parameters geändert werden: ```latte -detail in Czech +Detail auf Tschechisch ``` -Oder er kann *zurückgesetzt* werden, d.h. aus der URL entfernt werden. Er nimmt dann seinen Standardwert an: +Oder er kann *zurückgesetzt* werden, d.h. aus der URL entfernt werden. Dann nimmt er seinen Standardwert an: ```latte -click +klicken ``` -Interaktive Komponenten .[#toc-interactive-components] -====================================================== +Interaktive Komponenten +======================= -Presenter haben ein eingebautes Komponentensystem. Komponenten sind separate, wiederverwendbare Einheiten, die wir in Presenter einfügen. Es kann sich dabei um [Formulare |forms:in-presenter], Datenfelder, Menüs und alles handeln, was für eine wiederholte Verwendung sinnvoll ist. +Presenter haben ein eingebautes Komponentensystem. Komponenten sind separate, wiederverwendbare Einheiten, die wir in Presenter einfügen. Dies können [Formulare |forms:in-presenter], Datagrids, Menüs sein, eigentlich alles, was sinnvoll wiederverwendet werden kann. -Wie werden Komponenten im Presenter platziert und anschließend verwendet? Das wird im Kapitel [Komponenten |Components] erklärt. Sie werden sogar erfahren, was sie mit Hollywood zu tun haben. +Wie werden Komponenten in den Presenter eingefügt und anschließend verwendet? Das erfahren Sie im Kapitel [Komponenten |components]. Sie werden sogar herausfinden, was sie mit Hollywood gemeinsam haben. -Wo kann ich einige Komponenten bekommen? Auf der Seite [Componette |https://componette.org] finden Sie einige Open-Source-Komponenten und andere Erweiterungen für Nette, die von der Nette-Framework-Gemeinschaft erstellt und geteilt werden. +Und wo kann ich Komponenten bekommen? Auf der Seite [Componette |https://componette.org/search/component] finden Sie Open-Source-Komponenten sowie eine Reihe weiterer Add-ons für Nette, die von Freiwilligen aus der Community rund um das Framework hier platziert wurden. -Tiefer gehen .[#toc-going-deeper] -================================= +Wir gehen in die Tiefe +====================== .[tip] -Was wir bisher in diesem Kapitel gezeigt haben, wird wahrscheinlich ausreichen. Die folgenden Zeilen sind für diejenigen gedacht, die sich eingehend mit Moderatoren beschäftigen und alles wissen wollen. +Mit dem, was wir bisher in diesem Kapitel gezeigt haben, werden Sie wahrscheinlich vollkommen auskommen. Die folgenden Zeilen sind für diejenigen gedacht, die sich eingehender für Presenter interessieren und alles wissen möchten. -Anforderung und Parameter .[#toc-requirement-and-parameters] ------------------------------------------------------------- +Validierung von Parametern +-------------------------- -Die vom Präsentator bearbeitete Anfrage ist das Objekt [api:Nette\Application\Request] und wird von der Methode `getRequest()` des Präsentators zurückgegeben. Sie enthält ein Array von Parametern, und jeder von ihnen gehört entweder zu einer der Komponenten oder direkt zum Präsentator (der eigentlich auch eine Komponente ist, wenn auch eine spezielle). Nette verteilt also die Parameter um und übergibt sie zwischen den einzelnen Komponenten (und dem Präsentator) durch Aufruf der Methode `loadState(array $params)`. Die Parameter können mit der Methode `getParameters(): array`, einzeln mit `getParameter($name)` abgerufen werden. Bei den Parameterwerten handelt es sich um Strings oder Arrays von Strings, also im Grunde um Rohdaten, die direkt aus einer URL bezogen werden. +Die Werte der [#Anfrageparameter] und [persistenten Parameter |#Persistente Parameter], die aus der URL empfangen werden, schreibt die Methode `loadState()` in die Properties. Sie überprüft auch, ob der bei der Property angegebene Datentyp übereinstimmt, andernfalls antwortet sie mit einem 404-Fehler und die Seite wird nicht angezeigt. - -Validierung von persistenten Parametern .[#toc-validation-of-persistent-parameters] ------------------------------------------------------------------------------------ - -Die Werte von [persistenten Parametern |#persistent parameters], die von URLs empfangen werden, werden von der Methode `loadState()` in Eigenschaften geschrieben. Sie prüft auch, ob der in der Eigenschaft angegebene Datentyp übereinstimmt, andernfalls antwortet sie mit einem 404-Fehler und die Seite wird nicht angezeigt. - -Verlassen Sie sich niemals blind auf persistente Parameter, da sie leicht vom Benutzer in der URL überschrieben werden können. So überprüfen wir zum Beispiel, ob `$this->lang` zu den unterstützten Sprachen gehört. Eine gute Möglichkeit, dies zu tun, besteht darin, die oben erwähnte Methode `loadState()` zu überschreiben: +Vertrauen Sie niemals blind Parametern, da sie vom Benutzer leicht in der URL überschrieben werden können. So überprüfen wir beispielsweise, ob die Sprache `$this->lang` zu den unterstützten gehört. Ein geeigneter Weg ist, die erwähnte Methode `loadState()` zu überschreiben: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -328,8 +347,8 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function loadState(array $params): void { - parent::loadState($params); // hier wird die $this->lang gesetzt - // nach der Prüfung der Benutzerwerte: + parent::loadState($params); // hier wird $this->lang gesetzt + // es folgt die eigene Überprüfung des Wertes: if (!in_array($this->lang, ['en', 'cs'])) { $this->error(); } @@ -338,43 +357,45 @@ class ProductPresenter extends Nette\Application\UI\Presenter ``` -Speichern und Wiederherstellen der Anfrage .[#toc-save-and-restore-the-request] -------------------------------------------------------------------------------- +Speichern und Wiederherstellen der Anfrage +------------------------------------------ -Sie können die aktuelle Anfrage in einer Session speichern oder sie aus der Session wiederherstellen und den Präsentator sie erneut ausführen lassen. Dies ist z. B. nützlich, wenn ein Benutzer ein Formular ausfüllt und seine Anmeldung abläuft. Um keine Daten zu verlieren, speichern wir vor der Routing zur Anmeldeseite die aktuelle Anfrage in der Session mit `$reqId = $this->storeRequest()`, die einen Bezeichner in Form einer kurzen Zeichenkette zurückgibt und ihn als Parameter an den Anmeldepräsentator übergibt. +Die Anfrage, die der Presenter bearbeitet, ist ein Objekt [api:Nette\Application\Request] und wird von der Presenter-Methode `getRequest()` zurückgegeben. -Nach der Anmeldung rufen wir die Methode `$this->restoreRequest($reqId)` auf, die die Anfrage aus der Session abholt und an diese weiterleitet. Die Methode prüft, ob die Anfrage von demselben Benutzer erstellt wurde, der jetzt angemeldet ist. Wenn sich ein anderer Benutzer anmeldet oder der Schlüssel ungültig ist, tut sie nichts und das Programm läuft weiter. +Die aktuelle Anfrage kann in der Session gespeichert oder daraus wiederhergestellt und vom Presenter erneut ausgeführt werden. Dies ist nützlich, zum Beispiel in einer Situation, in der ein Benutzer ein Formular ausfüllt und seine Anmeldung abläuft. Um die Daten nicht zu verlieren, speichern wir vor der Weiterleitung zur Anmeldeseite die aktuelle Anfrage mit `$reqId = $this->storeRequest()` in der Session. Diese Methode gibt ihren Identifikator in Form einer kurzen Zeichenkette zurück, den wir als Parameter an den Anmelde-Presenter übergeben. -Siehe das Kochbuch [How to return to a earlier page |best-practices:restore-request]. +Nach der Anmeldung rufen wir die Methode `$this->restoreRequest($reqId)` auf, die die Anfrage aus der Session holt und dorthin weiterleitet (forward). Die Methode überprüft dabei, ob die Anfrage vom selben Benutzer erstellt wurde, der sich jetzt angemeldet hat. Wenn sich ein anderer Benutzer angemeldet hat oder der Schlüssel ungültig ist, tut sie nichts und das Programm läuft weiter. +Schauen Sie sich die Anleitung [Wie man zu einer früheren Seite zurückkehrt |best-practices:restore-request] an. -Kanonisierung .[#toc-canonization] ----------------------------------- -Presenter haben eine wirklich großartige Funktion, die SEO (Optimierung der Auffindbarkeit im Internet) verbessert. Sie verhindern automatisch das Vorhandensein von doppeltem Inhalt unter verschiedenen URLs. Wenn mehrere URLs zu einem bestimmten Ziel führen, z. B. `/index` und `/index?page=1`, bestimmt das Framework eine von ihnen als die primäre (kanonische) und leitet die anderen mit dem HTTP-Code 301 auf diese um. Auf diese Weise werden die Seiten von den Suchmaschinen nicht doppelt indiziert und ihr Page Rank nicht geschwächt. +Kanonisierung +------------- -Dieser Vorgang wird als Kanonisierung bezeichnet. Die kanonische URL ist die vom [Router |routing] generierte URL, in der Regel die erste geeignete Route in der Sammlung. +Presenter haben eine wirklich großartige Eigenschaft, die zu besserem SEO (Optimierung der Auffindbarkeit im Internet) beiträgt. Sie verhindern automatisch das Vorhandensein von doppeltem Inhalt unter verschiedenen URLs. Wenn zu einem bestimmten Ziel mehrere URL-Adressen führen, z.B. `/index` und `/index?page=1`, bestimmt das Framework eine davon als primäre (kanonische) und leitet die anderen mit dem HTTP-Code 301 dorthin weiter. Dank dessen indizieren Suchmaschinen die Seiten nicht zweimal und verwässern nicht deren Page Rank. -Die Kanonisierung ist standardmäßig aktiviert und kann über `$this->autoCanonicalize = false` ausgeschaltet werden. +Dieser Prozess wird Kanonisierung genannt. Die kanonische URL ist diejenige, die vom [Router|routing] generiert wird, in der Regel also die erste passende Route in der Sammlung. -Eine Umleitung findet bei einer AJAX- oder POST-Anfrage nicht statt, da dies zu Datenverlust oder keinem SEO-Mehrwert führen würde. +Die Kanonisierung ist standardmäßig aktiviert und kann über `$this->autoCanonicalize = false` deaktiviert werden. -Sie können die Kanonisierung auch manuell mit der Methode `canonicalize()` aufrufen, die wie die Methode `link()` den Präsentator, Aktionen und Parameter als Argumente erhält. Sie erstellt einen Link und vergleicht ihn mit der aktuellen URL. Wenn sie sich unterscheidet, wird sie auf den erzeugten Link umgeleitet. +Bei einer AJAX- oder POST-Anfrage erfolgt keine Weiterleitung, da dies zu Datenverlust führen würde oder aus SEO-Sicht keinen Mehrwert hätte. + +Sie können die Kanonisierung auch manuell mit der Methode `canonicalize()` auslösen, der ähnlich wie der Methode `link()` der Presenter, die Aktion und die Parameter übergeben werden. Sie erstellt einen Link und vergleicht ihn mit der aktuellen URL-Adresse. Wenn sie sich unterscheiden, wird auf den generierten Link weitergeleitet. ```php -public function actionShow(int $id, string $slug = null): void +public function actionShow(int $id, ?string $slug = null): void { $realSlug = $this->facade->getSlugForId($id); - // leitet um, wenn $slug nicht mit $realSlug übereinstimmt + // leitet weiter, wenn $slug sich von $realSlug unterscheidet $this->canonicalize('Product:show', [$id, $realSlug]); } ``` -Ereignisse .[#toc-events] -------------------------- +Ereignisse +---------- -Zusätzlich zu den Methoden `startup()`, `beforeRender()` und `shutdown()`, die im Rahmen des Lebenszyklus des Presenters aufgerufen werden, können weitere Funktionen definiert werden, die automatisch aufgerufen werden. Der Präsentator definiert die sogenannten [Ereignisse |nette:glossary#events], und Sie fügen deren Handler zu den Arrays `$onStartup`, `$onRender` und `$onShutdown` hinzu. +Neben den Methoden `startup()`, `beforeRender()` und `shutdown()`, die als Teil des Lebenszyklus des Presenters aufgerufen werden, können noch weitere Funktionen definiert werden, die automatisch aufgerufen werden sollen. Der Presenter definiert sogenannte [Ereignisse |nette:glossary#Events Ereignisse], deren Handler Sie zu den Arrays `$onStartup`, `$onRender` und `$onShutdown` hinzufügen. ```php class ArticlePresenter extends Nette\Application\UI\Presenter @@ -388,19 +409,19 @@ class ArticlePresenter extends Nette\Application\UI\Presenter } ``` -Die Handler im Array `$onStartup` werden kurz vor der Methode `startup()` aufgerufen, dann `$onRender` zwischen `beforeRender()` und `render()` und schließlich `$onShutdown` kurz vor `shutdown()`. +Die Handler im Array `$onStartup` werden kurz vor der Methode `startup()` aufgerufen, `$onRender` zwischen `beforeRender()` und `render()` und schließlich `$onShutdown` kurz vor `shutdown()`. -Antworten .[#toc-responses] ---------------------------- +Antworten +--------- -Die vom Präsentator zurückgegebene Antwort ist ein Objekt, das die Schnittstelle [api:Nette\Application\Response] implementiert. Es gibt eine Reihe von vorgefertigten Antworten: +Die Antwort, die ein Presenter zurückgibt, ist ein Objekt, das das Interface [api:Nette\Application\Response] implementiert. Es stehen eine Reihe von vorbereiteten Antworten zur Verfügung: -- [api:Nette\Application\Responses\CallbackResponse] - sendet einen Rückruf -- [api:Nette\Application\Responses\FileResponse] - sendet die Datei -- [api:Nette\Application\Responses\ForwardResponse] - weiterleiten () +- [api:Nette\Application\Responses\CallbackResponse] - sendet einen Callback +- [api:Nette\Application\Responses\FileResponse] - sendet eine Datei +- [api:Nette\Application\Responses\ForwardResponse] - forward() - [api:Nette\Application\Responses\JsonResponse] - sendet JSON -- [api:Nette\Application\Responses\RedirectResponse] - umleiten +- [api:Nette\Application\Responses\RedirectResponse] - Weiterleitung - [api:Nette\Application\Responses\TextResponse] - sendet Text - [api:Nette\Application\Responses\VoidResponse] - leere Antwort @@ -409,26 +430,71 @@ Antworten werden mit der Methode `sendResponse()` gesendet: ```php use Nette\Application\Responses; -// Klartext +// Einfacher Text $this->sendResponse(new Responses\TextResponse('Hello Nette!')); // Sendet eine Datei $this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf')); -// Sendet einen Rückruf +// Die Antwort wird ein Callback sein $callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse) { if ($httpResponse->getHeader('Content-Type') === 'text/html') { - echo '

    Hallo

    '; + echo '

    Hello

    '; } }; $this->sendResponse(new Responses\CallbackResponse($callback)); ``` -Weitere Lektüre .[#toc-further-reading] -======================================= +Zugriffsbeschränkung mit `#[Requires]` .{data-version:3.2.2} +------------------------------------------------------------ + +Das Attribut `#[Requires]` bietet erweiterte Möglichkeiten zur Zugriffsbeschränkung für Presenter und deren Methoden. Es kann verwendet werden, um HTTP-Methoden zu spezifizieren, eine AJAX-Anfrage zu erfordern, den Zugriff auf den gleichen Ursprung (Same Origin) zu beschränken und den Zugriff nur über Forwarding zu erlauben. Das Attribut kann sowohl auf Presenter-Klassen als auch auf einzelne Methoden `action()`, `render()`, `handle()` und `createComponent()` angewendet werden. + +Sie können folgende Beschränkungen festlegen: +- auf HTTP-Methoden: `#[Requires(methods: ['GET', 'POST'])]` +- Erfordern einer AJAX-Anfrage: `#[Requires(ajax: true)]` +- Zugriff nur vom gleichen Ursprung: `#[Requires(sameOrigin: true)]` +- Zugriff nur über Forwarding: `#[Requires(forward: true)]` +- Beschränkung auf bestimmte Aktionen: `#[Requires(actions: 'default')]` + +Details finden Sie in der Anleitung [Verwendung des Requires-Attributs |best-practices:attribute-requires]. + + +Überprüfung der HTTP-Methode +---------------------------- + +Presenter in Nette überprüfen automatisch die HTTP-Methode jeder eingehenden Anfrage. Der Grund für diese Überprüfung ist hauptsächlich die Sicherheit. Standardmäßig sind die Methoden `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH` erlaubt. + +Wenn Sie zusätzlich beispielsweise die Methode `OPTIONS` erlauben möchten, verwenden Sie dazu das Attribut `#[Requires]` (ab Nette Application v3.2): + +```php +#[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])] +class MyPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +In Version 3.1 erfolgt die Überprüfung in `checkHttpMethod()`, die prüft, ob die in der Anfrage angegebene Methode im Array `$presenter->allowedMethods` enthalten ist. Fügen Sie die Methode wie folgt hinzu: + +```php +class MyPresenter extends Nette\Application\UI\Presenter +{ + protected function checkHttpMethod(): void + { + $this->allowedMethods[] = 'OPTIONS'; + parent::checkHttpMethod(); + } +} +``` + +Es ist wichtig zu betonen, dass wenn Sie die Methode `OPTIONS` zulassen, Sie diese anschließend auch entsprechend in Ihrem Presenter behandeln müssen. Die Methode wird oft als sogenannter Preflight Request verwendet, den der Browser automatisch vor der eigentlichen Anfrage sendet, wenn überprüft werden muss, ob die Anfrage gemäß der CORS (Cross-Origin Resource Sharing) Richtlinie zulässig ist. Wenn Sie die Methode zulassen, aber keine korrekte Antwort implementieren, kann dies zu Inkonsistenzen und potenziellen Sicherheitsproblemen führen. + + +Weitere Lektüre +=============== -- [Methoden und Attribute einfügen |best-practices:inject-method-attribute] -- [Zusammenstellen von Presentern aus Traits |best-practices:presenter-traits] +- [Inject-Methoden und -Attribute |best-practices:inject-method-attribute] +- [Zusammensetzen von Presentern aus Traits |best-practices:presenter-traits] - [Übergabe von Einstellungen an Presenter |best-practices:passing-settings-to-presenters] - [Wie man zu einer früheren Seite zurückkehrt |best-practices:restore-request] diff --git a/application/de/routing.texy b/application/de/routing.texy index 1cd5fcc40d..ff2f7378e0 100644 --- a/application/de/routing.texy +++ b/application/de/routing.texy @@ -1,29 +1,28 @@ -Routenplanung -************* +Routing +*******
    -Der Router ist für alles zuständig, was mit URLs zu tun hat, so dass Sie nicht mehr darüber nachdenken müssen. Das werden wir zeigen: +Der Router kümmert sich um alles rund um URL-Adressen, damit Sie nicht mehr darüber nachdenken müssen. Wir zeigen Ihnen: -- wie man den Router so einrichtet, dass die URLs so aussehen, wie Sie es wünschen -- ein paar Hinweise zur SEO-Routing -- und wir zeigen Ihnen, wie Sie Ihren eigenen Router schreiben können +- wie man den Router einstellt, damit die URLs den Vorstellungen entsprechen +- wir sprechen über SEO und Weiterleitungen +- und zeigen Ihnen, wie Sie einen eigenen Router schreiben
    -Menschlichere URLs (oder coole oder schöne URLs) sind benutzerfreundlicher, einprägsamer und tragen positiv zur Suchmaschinenoptimierung bei. Nette hat dies im Sinn und kommt den Wünschen der Entwickler voll entgegen. Sie können die URL-Struktur für Ihre Anwendung genau so gestalten, wie Sie es wünschen. -Sie können sie sogar entwerfen, nachdem die Anwendung fertig ist, da dies ohne Code- oder Vorlagenänderungen möglich ist. Sie wird auf elegante Weise an [einer einzigen Stelle |#Integration], nämlich im Router, definiert und ist nicht in Form von Anmerkungen in allen Presentern verstreut. +Menschenfreundlichere URLs (oder auch coole oder pretty URLs) sind benutzbarer, leichter zu merken und tragen positiv zur SEO bei. Nette berücksichtigt dies und kommt Entwicklern voll entgegen. Sie können für Ihre Anwendung genau die Struktur der URL-Adressen entwerfen, die Sie möchten. Sie können sie sogar erst dann entwerfen, wenn die Anwendung bereits fertig ist, da dies ohne Eingriffe in den Code oder die Templates auskommt. Sie wird nämlich auf elegante Weise an einer [einzigen Stelle |#Integration in die Anwendung] definiert, im Router, und ist somit nicht in Form von Annotationen in allen Presentern verstreut. -Der Router in Nette ist etwas Besonderes, denn er ist **bidirektional**, er kann sowohl HTTP-Anfrage-URLs dekodieren als auch Links erstellen. Er spielt also eine wichtige Rolle in der [Nette-Anwendung |how-it-works#Nette Application], denn er entscheidet, welcher Presenter und welche Aktion die aktuelle Anfrage ausführen wird, und er wird auch für die [URL-Generierung |creating-links] in der Vorlage usw. verwendet. +Der Router in Nette ist außergewöhnlich, da er **bidirektional** ist. Er kann sowohl URLs in einer HTTP-Anfrage dekodieren als auch Links erstellen. Er spielt daher eine entscheidende Rolle in [Nette Application |how-it-works#Nette Application], da er einerseits entscheidet, welcher Presenter und welche Aktion die aktuelle Anfrage ausführen wird, andererseits aber auch für die [Generierung von URLs |creating-links] im Template usw. verwendet wird. -Der Router ist jedoch nicht auf diese Verwendung beschränkt, sondern kann auch in Anwendungen eingesetzt werden, in denen Presenter überhaupt nicht verwendet werden, für REST-APIs usw. Mehr dazu im Abschnitt [Getrennte Nutzung |#separated usage]. +Der Router ist jedoch nicht nur auf diese Verwendung beschränkt, Sie können ihn auch in Anwendungen einsetzen, in denen überhaupt keine Presenter verwendet werden, für REST-APIs usw. Mehr dazu im Abschnitt [#Eigenständige Verwendung]. -Routen-Sammlung .[#toc-route-collection] -======================================== +Routen-Sammlung +=============== -Die angenehmste Art, die URL-Adressen in der Anwendung zu definieren, ist über die Klasse [api:Nette\Application\Routers\RouteList]. Die Definition besteht aus einer Liste sogenannter Routen, d.h. Masken von URL-Adressen und den dazugehörigen Presentern und Aktionen über eine einfache API. Wir müssen die Routen nicht benennen. +Die angenehmste Art, die Form der URL-Adressen in der Anwendung zu definieren, bietet die Klasse [api:Nette\Application\Routers\RouteList]. Die Definition besteht aus einer Liste sogenannter Routen, d. h. Masken von URL-Adressen und den ihnen zugeordneten Presentern und Aktionen über eine einfache API. Wir müssen die Routen nicht benennen. ```php $router = new Nette\Application\Routers\RouteList; @@ -32,16 +31,16 @@ $router->addRoute('article/', 'Article:view'); // ... ``` -Das Beispiel besagt, dass, wenn wir `https://any-domain.com/rss.xml` mit der Aktion `rss` angezeigt wird, wenn `https://domain.com/article/12` mit der Aktion `view` angezeigt, usw. Wenn keine passende Route gefunden wird, reagiert Nette Application mit der Ausnahme [BadRequestException |api:Nette\Application\BadRequestException], die dem Benutzer als 404 Not Found Fehlerseite angezeigt wird. +Das Beispiel besagt, dass wenn wir im Browser `https://domain.com/rss.xml` öffnen, der Presenter `Feed` mit der Aktion `rss` angezeigt wird, wenn `https://domain.com/article/12`, wird der Presenter `Article` mit der Aktion `view` angezeigt usw. Wenn keine passende Route gefunden wird, reagiert Nette Application mit dem Auslösen einer [BadRequestException |api:Nette\Application\BadRequestException], die dem Benutzer als Fehlerseite 404 Not Found angezeigt wird. -Reihenfolge der Routen .[#toc-order-of-routes] ----------------------------------------------- +Reihenfolge der Routen +---------------------- -Die Reihenfolge, in der die Routen aufgelistet werden, ist **sehr wichtig**, da sie von oben nach unten ausgewertet werden. Die Regel lautet, dass wir Routen **von spezifisch nach allgemein** deklarieren: +Ganz **entscheidend ist die Reihenfolge**, in der die einzelnen Routen aufgeführt sind, da sie schrittweise von oben nach unten ausgewertet werden. Es gilt die Regel, dass wir Routen **von spezifisch nach allgemein** deklarieren: ```php -// FALSCH: 'rss.xml' stimmt mit der ersten Route überein und missversteht diese als +// FALSCH: 'rss.xml' wird von der ersten Route abgefangen und diese Zeichenkette als verstanden $router->addRoute('', 'Article:view'); $router->addRoute('rss.xml', 'Feed:rss'); @@ -50,10 +49,10 @@ $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('', 'Article:view'); ``` -Die Routen werden auch bei der Erstellung von Links von oben nach unten ausgewertet: +Routen werden auch bei der Generierung von Links von oben nach unten ausgewertet: ```php -// FALSCH: erzeugt einen Link zu 'Feed:rss' als 'admin/feed/rss' +// FALSCH: Ein Link zu 'Feed:rss' wird als 'admin/feed/rss' generiert $router->addRoute('admin//', 'Admin:default'); $router->addRoute('rss.xml', 'Feed:rss'); @@ -62,100 +61,100 @@ $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('admin//', 'Admin:default'); ``` -Wir wollen Ihnen nicht verschweigen, dass es einiges an Geschick erfordert, eine Liste richtig aufzubauen. Bis Sie sich damit vertraut gemacht haben, ist das [Routing-Panel |#Debugging Router] ein nützliches Werkzeug. +Wir werden Ihnen nicht verheimlichen, dass die korrekte Zusammenstellung der Routen eine gewisse Fertigkeit erfordert. Bevor Sie diese beherrschen, wird Ihnen das [Routing-Panel |#Debugging des Routers] ein nützlicher Helfer sein. -Maske und Parameter .[#toc-mask-and-parameters] ------------------------------------------------ +Maske und Parameter +------------------- -Die Maske beschreibt den relativen Pfad auf der Grundlage des Stammverzeichnisses der Website. Die einfachste Maske ist eine statische URL: +Die Maske beschreibt den relativen Pfad vom Stammverzeichnis der Website. Die einfachste Maske ist eine statische URL: ```php $router->addRoute('products', 'Products:default'); ``` -Oft enthalten Masken sogenannte **Parameter**. Sie sind in spitzen Klammern eingeschlossen (z.B. ``) und werden an den Zielmoderator übergeben, z. B. an die Methode `renderShow(int $year)` oder an persistente Parameter `$year`: +Oft enthalten Masken sogenannte **Parameter**. Diese werden in spitzen Klammern angegeben (z. B. ``) und an den Ziel-Presenter übergeben, beispielsweise an die Methode `renderShow(int $year)` oder an den persistenten Parameter `$year`: ```php $router->addRoute('chronicle/', 'History:show'); ``` -Das Beispiel besagt, dass, wenn wir `https://any-domain.com/chronicle/2020` und die Aktion `show` mit dem Parameter `year: 2020` angezeigt werden. +Das Beispiel besagt, dass wenn wir im Browser `https://example.com/chronicle/2020` öffnen, der Presenter `History` mit der Aktion `show` und dem Parameter `year: 2020` angezeigt wird. -Wir können direkt in der Maske einen Standardwert für die Parameter angeben, so dass sie optional werden: +Wir können Parametern direkt in der Maske einen Standardwert zuweisen, wodurch sie optional werden: ```php $router->addRoute('chronicle/', 'History:show'); ``` -Die Route wird nun die URL `https://any-domain.com/chronicle/` mit dem Parameter `year: 2020` anzeigt. +Die Route akzeptiert nun auch die URL `https://example.com/chronicle/`, die wiederum `History:show` mit dem Parameter `year: 2020` anzeigt. -Natürlich kann auch der Name des Präsentators und der Aktion ein Parameter sein. Zum Beispiel: +Parameter können natürlich auch der Name des Presenters und der Aktion sein. Zum Beispiel so: ```php $router->addRoute('/', 'Home:default'); ``` -Diese Route nimmt z.B. eine URL in der Form `/article/edit` bzw. `/catalog/list` entgegen und übersetzt sie in Präsentatoren und Aktionen `Article:edit` bzw. `Catalog:list`. +Die angegebene Route akzeptiert z. B. URLs in der Form `/article/edit` oder auch `/catalog/list` und versteht sie als Presenter und Aktionen `Article:edit` und `Catalog:list`. -Außerdem werden den Parametern `presenter` und `action` die Standardwerte`Home` und `default` zugewiesen, so dass sie optional sind. So akzeptiert die Route auch eine URL `/article` und übersetzt sie als `Article:default`. Oder umgekehrt, ein Link auf `Product:default` erzeugt einen Pfad `/product`, ein Link auf den Standardwert `Home:default` erzeugt einen Pfad `/`. +Gleichzeitig gibt sie den Parametern `presenter` und `action` die Standardwerte `Home` und `default`, wodurch sie ebenfalls optional sind. Die Route akzeptiert also auch eine URL in der Form `/article` und versteht sie als `Article:default`. Oder umgekehrt, ein Link zu `Product:default` generiert den Pfad `/product`, ein Link zum Standard `Home:default` den Pfad `/`. -Die Maske kann nicht nur den relativen Pfad auf der Grundlage des Stammverzeichnisses der Website beschreiben, sondern auch den absoluten Pfad, wenn er mit einem Schrägstrich beginnt, oder sogar die gesamte absolute URL, wenn sie mit zwei Schrägstrichen beginnt: +Die Maske kann nicht nur den relativen Pfad vom Stammverzeichnis der Website beschreiben, sondern auch einen absoluten Pfad, wenn sie mit einem Schrägstrich beginnt, oder sogar eine gesamte absolute URL, wenn sie mit zwei Schrägstrichen beginnt: ```php -// relativer Pfad zum Stamm des Anwendungsdokuments +// relativ zum Document Root $router->addRoute('/', /* ... */); -// absoluter Pfad, relativ zum Server-Hostnamen +// absoluter Pfad (relativ zur Domain) $router->addRoute('//', /* ... */); -// absolute URL einschließlich Hostname (aber schema-relativ) +// absolute URL inklusive Domain (relativ zum Schema) $router->addRoute('//.example.com//', /* ... */); -// absolute URL einschließlich Schema +// absolute URL inklusive Schema $router->addRoute('https://.example.com//', /* ... */); ``` -Validierungsausdrücke .[#toc-validation-expressions] ----------------------------------------------------- +Validierungsausdrücke +--------------------- -Für jeden Parameter kann mit Hilfe eines [regulären Ausdrucks |https://www.php.net/manual/en/reference.pcre.pattern.syntax.php] eine Validierungsbedingung angegeben werden. Ein Beispiel: `id` soll nur numerisch sein, mit `\d+` regexp: +Für jeden Parameter kann eine Validierungsbedingung mithilfe eines [Regulären Ausdrucks|https://www.php.net/manual/en/reference.pcre.pattern.syntax.php] festgelegt werden. Zum Beispiel bestimmen wir für den Parameter `id`, dass er nur Ziffern annehmen darf, mit dem Regulären Ausdruck `\d+`: ```php $router->addRoute('/[/]', /* ... */); ``` -Der reguläre Standardausdruck für alle Parameter ist `[^/]+`d.h. alles außer dem Schrägstrich. Wenn ein Parameter auch auf einen Schrägstrich passen soll, setzen wir den regulären Ausdruck auf `.+`. +Der standardmäßige reguläre Ausdruck für alle Parameter ist `[^/]+`, d. h. alles außer einem Schrägstrich. Wenn ein Parameter auch Schrägstriche akzeptieren soll, geben wir den Ausdruck `.+` an: ```php -// akzeptiert https://example.com/a/b/c, Pfad ist 'a/b/c' +// akzeptiert https://example.com/a/b/c, path wird 'a/b/c' $router->addRoute('', /* ... */); ``` -Optionale Sequenzen .[#toc-optional-sequences] ----------------------------------------------- +Optionale Sequenzen +------------------- -Eckige Klammern kennzeichnen optionale Teile der Maske. Jeder Teil der Maske kann als optional festgelegt werden, auch die Teile, die Parameter enthalten: +In der Maske können optionale Teile mithilfe von eckigen Klammern markiert werden. Jeder Teil der Maske kann optional sein, und er kann auch Parameter enthalten: ```php $router->addRoute('[/]', /* ... */); -// Akzeptierte URLs: Parameter: -// /de/download lang => de, name => download -// /download lang => null, Name => download +// Akzeptiert Pfade: +// /cs/download => lang => cs, name => download +// /download => lang => null, name => download ``` -Wenn ein Parameter Teil einer optionalen Sequenz ist, wird er natürlich auch optional. Wenn er keinen Standardwert hat, ist er null. +Wenn ein Parameter Teil einer optionalen Sequenz ist, wird er natürlich auch optional. Wenn kein Standardwert angegeben ist, wird er null sein. -Optionale Abschnitte können sich auch in der Domäne befinden: +Optionale Teile können auch in der Domain vorkommen: ```php $router->addRoute('//[.]example.com//', /* ... */); ``` -Sequenzen können frei verschachtelt und kombiniert werden: +Sequenzen können beliebig verschachtelt und kombiniert werden: ```php $router->addRoute( @@ -163,48 +162,48 @@ $router->addRoute( 'Home:default', ); -// Akzeptierte URLs: -// /de/hello -// /en-us/hello -// /hallo -// /hallo/seite-12 +// Akzeptiert Pfade: +// /cs/hello +// /en-us/hello +// /hello +// /hello/page-12 ``` -Der URL-Generator versucht, die URL so kurz wie möglich zu halten, so dass alles, was weggelassen werden kann, auch weggelassen wird. Daher erzeugt zum Beispiel eine Route `index[.html]` einen Pfad `/index` erzeugt. Sie können dieses Verhalten umkehren, indem Sie ein Ausrufezeichen hinter die linke eckige Klammer schreiben: +Bei der URL-Generierung wird die kürzeste Variante angestrebt, sodass alles, was weggelassen werden kann, weggelassen wird. Daher generiert beispielsweise die Route `index[.html]` den Pfad `/index`. Das Verhalten kann durch Angabe eines Ausrufezeichens nach der linken eckigen Klammer umgekehrt werden: ```php -// akzeptiert sowohl /hello als auch /hello.html, erzeugt /hello +// akzeptiert /hello und /hello.html, generiert /hello $router->addRoute('[.html]', /* ... */); -// akzeptiert sowohl /hello als auch /hello.html, erzeugt /hello.html +// akzeptiert /hello und /hello.html, generiert /hello.html $router->addRoute('[!.html]', /* ... */); ``` -Optionale Parameter (d.h. Parameter mit Standardwerten) ohne eckige Klammern verhalten sich so, als wären sie so umbrochen: +Optionale Parameter (d. h. Parameter mit einem Standardwert) ohne eckige Klammern verhalten sich im Grunde so, als wären sie wie folgt geklammert: ```php $router->addRoute('//', /* ... */); -// gleichbedeutend mit: +// entspricht diesem: $router->addRoute('[/[/[]]]', /* ... */); ``` -Um zu ändern, wie der Schrägstrich ganz rechts erzeugt wird, d. h. statt `/home/` ein `/home` zu erhalten, passen Sie die Route folgendermaßen an: +Wenn wir das Verhalten des abschließenden Schrägstrichs beeinflussen möchten, damit z. B. anstelle von `/home/` nur `/home` generiert wird, kann dies wie folgt erreicht werden: ```php $router->addRoute('[[/[/]]]', /* ... */); ``` -Platzhalter .[#toc-wildcards] ------------------------------ +Platzhalter +----------- -In der absoluten Pfadmaske können wir die folgenden Platzhalter verwenden, um z. B. die Notwendigkeit zu vermeiden, eine Domäne in die Maske zu schreiben, die in der Entwicklungs- und Produktionsumgebung unterschiedlich sein kann: +In der Maske des absoluten Pfads können wir folgende Platzhalter verwenden, um beispielsweise die Notwendigkeit zu vermeiden, die Domain in die Maske zu schreiben, die sich in der Entwicklungs- und Produktionsumgebung unterscheiden kann: -- `%tld%` = Top-Level-Domain, z.B. `com` oder `org` -- `%sld%` = Domain der zweiten Ebene, z. B. `example` -- `%domain%` = Domain ohne Subdomains, z.B. `example.com` -- `%host%` = ganzer Host, z. B. `www.example.com` +- `%tld%` = Top-Level-Domain, z. B. `com` oder `org` +- `%sld%` = Second-Level-Domain, z. B. `example` +- `%domain%` = Domain ohne Subdomains, z. B. `example.com` +- `%host%` = Gesamter Host, z. B. `www.example.com` - `%basePath%` = Pfad zum Stammverzeichnis ```php @@ -213,10 +212,10 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ @@ -225,7 +224,7 @@ $router->addRoute('/[/]', [ ]); ``` -Oder wir können diese Form verwenden, beachten Sie die Umschreibung des regulären Ausdrucks für die Validierung: +Für eine detailliertere Spezifikation kann eine noch erweiterte Form verwendet werden, in der wir neben den Standardwerten auch andere Eigenschaften der Parameter einstellen können, wie z. B. den Validierungs-regulären Ausdruck (siehe Parameter `id`): ```php use Nette\Routing\Route; @@ -243,19 +242,19 @@ $router->addRoute('/[/]', [ ]); ``` -Diese gesprächigeren Formate sind nützlich, um andere Metadaten hinzuzufügen. +Es ist wichtig zu beachten, dass wenn die im Array definierten Parameter nicht in der Pfadmaske aufgeführt sind, ihre Werte nicht geändert werden können, auch nicht durch Query-Parameter, die nach dem Fragezeichen in der URL aufgeführt sind. -Filter und Übersetzungen .[#toc-filters-and-translations] ---------------------------------------------------------- +Filter und Übersetzungen +------------------------ -Es ist eine gute Praxis, den Quellcode auf Englisch zu schreiben, aber was ist, wenn die URL Ihrer Website in eine andere Sprache übersetzt werden soll? Einfache Routen wie z.B.: +Wir schreiben den Quellcode der Anwendung auf Englisch, aber wenn die Website deutsche URLs haben soll, dann wird einfaches Routing vom Typ: ```php $router->addRoute('/', 'Home:default'); ``` -erzeugen englische URLs, wie `/product/123` oder `/cart`. Wenn wir Präsentatoren und Aktionen in der URL ins Deutsche übersetzt haben wollen (z. B. `/produkt/123` oder `/einkaufswagen`), können wir ein Übersetzungswörterbuch verwenden. Um es hinzuzufügen, benötigen wir bereits eine "gesprächigere" Variante des zweiten Parameters: +englische URLs generieren, wie z. B. `/product/123` oder `/cart`. Wenn wir Presenter und Aktionen in der URL durch deutsche Wörter repräsentieren lassen wollen (z. B. `/produkt/123` oder `/warenkorb`), können wir ein Übersetzungswörterbuch verwenden. Für dessen Notation benötigen wir bereits die "gesprächigere" Variante des zweiten Parameters: ```php use Nette\Routing\Route; @@ -264,9 +263,9 @@ $router->addRoute('/', [ 'presenter' => [ Route::Value => 'Home', Route::FilterTable => [ - // String in URL => Präsentator + // Zeichenkette in der URL => Presenter 'produkt' => 'Product', - 'einkaufswagen' => 'Cart', + 'warenkorb' => 'Cart', 'katalog' => 'Catalog', ], ], @@ -279,11 +278,11 @@ $router->addRoute('/', [ ]); ``` -Es können mehrere Wörterbuchschlüssel für denselben Präsentator verwendet werden. Sie erzeugen dann verschiedene Aliase für ihn. Der letzte Schlüssel gilt als die kanonische Variante (d. h. diejenige, die in der generierten URL enthalten sein wird). +Mehrere Schlüssel des Übersetzungswörterbuchs können auf denselben Presenter verweisen. Dadurch werden verschiedene Aliase dafür erstellt. Als kanonische Variante (also diejenige, die in der generierten URL enthalten sein wird) gilt der letzte Schlüssel. -Die Übersetzungstabelle kann auf diese Weise auf jeden Parameter angewendet werden. Wenn die Übersetzung jedoch nicht existiert, wird der ursprüngliche Wert genommen. Wir können dieses Verhalten ändern, indem wir `Route::FilterStrict => true` hinzufügen, und die Route wird dann die URL zurückweisen, wenn der Wert nicht im Wörterbuch enthalten ist. +Die Übersetzungstabelle kann auf diese Weise für jeden Parameter verwendet werden. Wobei, wenn die Übersetzung nicht existiert, der ursprüngliche Wert genommen wird. Dieses Verhalten können wir durch Hinzufügen von `Route::FilterStrict => true` ändern, und die Route lehnt dann die URL ab, wenn der Wert nicht im Wörterbuch enthalten ist. -Zusätzlich zum Übersetzungswörterbuch in Form eines Arrays ist es möglich, eigene Übersetzungsfunktionen festzulegen: +Neben dem Übersetzungswörterbuch in Form eines Arrays können auch eigene Übersetzungsfunktionen eingesetzt werden. ```php use Nette\Routing\Route; @@ -299,15 +298,15 @@ $router->addRoute('//', [ ]); ``` -Die Funktion `Route::FilterIn` konvertiert zwischen dem Parameter in der URL und dem String, der dann an den Presenter übergeben wird, die Funktion `FilterOut` sorgt für die Konvertierung in die umgekehrte Richtung. +Die Funktion `Route::FilterIn` konvertiert zwischen dem Parameter in der URL und der Zeichenkette, die dann an den Presenter übergeben wird, die Funktion `FilterOut` stellt die Konvertierung in die entgegengesetzte Richtung sicher. -Die Parameter `presenter`, `action` und `module` haben bereits vordefinierte Filter, die zwischen der in der URL verwendeten PascalCase- bzw. camelCase-Schreibweise und der Kebab-Case-Schreibweise konvertieren. Der Standardwert der Parameter wird bereits in der umgewandelten Form geschrieben, so dass wir z.B. im Falle eines Presenters `` anstelle von ``. +Die Parameter `presenter`, `action` und `module` haben bereits vordefinierte Filter, die zwischen dem PascalCase- bzw. camelCase-Stil und dem in URLs verwendeten kebab-case konvertieren. Der Standardwert der Parameter wird bereits in transformierter Form geschrieben, sodass wir beispielsweise im Fall des Presenters `` schreiben, nicht ``. -Allgemeine Filter .[#toc-general-filters] ------------------------------------------ +Allgemeine Filter +----------------- -Neben Filtern für bestimmte Parameter können Sie auch allgemeine Filter definieren, die ein assoziatives Array mit allen Parametern erhalten, die sie in beliebiger Weise ändern und dann zurückgeben können. Allgemeine Filter werden unter dem Schlüssel `null` definiert. +Neben Filtern, die für spezifische Parameter bestimmt sind, können wir auch allgemeine Filter definieren, die ein assoziatives Array aller Parameter erhalten, die sie beliebig modifizieren und dann zurückgeben können. Allgemeine Filter definieren wir unter dem Schlüssel `null`. ```php use Nette\Routing\Route; @@ -315,22 +314,22 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => 'Home', 'action' => 'default', - null => [ + '' => [ Route::FilterIn => function (array $params): array { /* ... */ }, Route::FilterOut => function (array $params): array { /* ... */ }, ], ]); ``` -Allgemeine Filter geben Ihnen die Möglichkeit, das Verhalten der Route in absolut beliebiger Weise anzupassen. Sie können z. B. verwendet werden, um Parameter in Abhängigkeit von anderen Parametern zu ändern. Zum Beispiel, Übersetzung `` und `` basierend auf dem aktuellen Wert des Parameters ``. +Allgemeine Filter bieten die Möglichkeit, das Verhalten der Route auf beliebige Weise anzupassen. Wir können sie beispielsweise zur Modifikation von Parametern basierend auf anderen Parametern verwenden. Zum Beispiel die Übersetzung von `` und `` basierend auf dem aktuellen Wert des Parameters ``. -Wenn für einen Parameter ein benutzerdefinierter Filter definiert ist und gleichzeitig ein allgemeiner Filter existiert, wird der benutzerdefinierte Filter `FilterIn` vor dem allgemeinen Filter ausgeführt und umgekehrt wird der allgemeine Filter `FilterOut` vor dem benutzerdefinierten Filter ausgeführt. Innerhalb des allgemeinen Filters werden also die Werte der Parameter `presenter` bzw. `action` im PascalCase- bzw. camelCase-Stil geschrieben. +Wenn ein Parameter einen eigenen Filter definiert hat und gleichzeitig ein allgemeiner Filter existiert, wird der eigene `FilterIn` vor dem allgemeinen ausgeführt und umgekehrt der allgemeine `FilterOut` vor dem eigenen. Das heißt, innerhalb des allgemeinen Filters sind die Werte der Parameter `presenter` bzw. `action` im PascalCase- bzw. camelCase-Stil geschrieben. -Einweg-Flag .[#toc-oneway-flag] -------------------------------- +Einwegrouten (OneWay) +--------------------- -Einweg-Routen werden verwendet, um die Funktionalität alter URLs zu erhalten, die von der Anwendung nicht mehr generiert, aber noch akzeptiert werden. Wir kennzeichnen sie mit `OneWay`: +Einwegrouten werden verwendet, um die Funktionalität alter URLs beizubehalten, die die Anwendung nicht mehr generiert, aber weiterhin akzeptiert. Wir markieren sie mit dem Flag `OneWay`: ```php // alte URL /product-info?id=123 @@ -339,38 +338,61 @@ $router->addRoute('product-info', 'Product:detail', $router::ONE_WAY); $router->addRoute('product/', 'Product:detail'); ``` -Beim Zugriff auf die alte URL leitet der Presenter automatisch auf die neue URL um, damit Suchmaschinen diese Seiten nicht doppelt indizieren (siehe [SEO und Kanonisierung |#SEO and canonization]). +Beim Zugriff auf die alte URL leitet der Presenter automatisch auf die neue URL weiter, sodass Suchmaschinen diese Seiten nicht doppelt indizieren (siehe [#SEO und Kanonisierung]). -Module .[#toc-modules] ----------------------- +Dynamisches Routing mit Callbacks +--------------------------------- + +Dynamisches Routing mit Callbacks ermöglicht es Ihnen, Routen direkt Funktionen (Callbacks) zuzuordnen, die ausgeführt werden, wenn der entsprechende Pfad besucht wird. Diese flexible Funktionalität ermöglicht es Ihnen, schnell und effizient verschiedene Endpunkte (Endpoints) für Ihre Anwendung zu erstellen: + +```php +$router->addRoute('test', function () { + echo 'Sie befinden sich unter der Adresse /test'; +}); +``` + +Sie können auch Parameter in der Maske definieren, die automatisch an Ihren Callback übergeben werden: -Wenn wir mehrere Routen haben, die zu einem [Modul |modules] gehören, können wir `withModule()` verwenden, um sie zu gruppieren: +```php +$router->addRoute('', function (string $lang) { + echo match ($lang) { + 'cs' => 'Willkommen auf der tschechischen Version unserer Website!', + 'en' => 'Welcome to the English version of our website!', + }; +}); +``` + + +Module +------ + +Wenn wir mehrere Routen haben, die zu einem gemeinsamen [Modul |directory-structure#Presenter und Templates] gehören, verwenden wir `withModule()`: ```php $router = new RouteList; -$router->withModule('Forum') // die folgenden Router sind Teil des Forum-Moduls - ->addRoute('rss', 'Feed:rss') // Präsentator ist Forum:Feed +$router->withModule('Forum') // Die folgenden Routen sind Teil des Moduls Forum + ->addRoute('rss', 'Feed:rss') // Der Presenter wird Forum:Feed sein ->addRoute('/') - ->withModule('Admin') // die folgenden Router sind Teil des Moduls Forum:Admin + ->withModule('Admin') // Die folgenden Routen sind Teil des Moduls Forum:Admin ->addRoute('sign:in', 'Sign:in'); ``` Eine Alternative ist die Verwendung des Parameters `module`: ```php -// Die URL manage/dashboard/default entspricht dem Präsentator Admin:Dashboard +// Die URL manage/dashboard/default wird auf den Presenter Admin:Dashboard gemappt $router->addRoute('manage//', [ 'module' => 'Admin', ]); ``` -Subdomains .[#toc-subdomains] ------------------------------ +Subdomains +---------- -Routensammlungen können nach Subdomänen gruppiert werden: +Routen-Sammlungen können nach Subdomains gegliedert werden: ```php $router = new RouteList; @@ -379,7 +401,7 @@ $router->withDomain('example.com') ->addRoute('/'); ``` -Sie können auch [Platzhalter |#wildcards] in Ihrem Domänennamen verwenden: +Im Domainnamen können auch [#Platzhalter] verwendet werden: ```php $router = new RouteList; @@ -388,23 +410,23 @@ $router->withDomain('example.%tld%') ``` -Pfad-Präfix .[#toc-path-prefix] -------------------------------- +Pfadpräfix +---------- -Routensammlungen können nach dem Pfad in der URL gruppiert werden: +Routen-Sammlungen können nach dem Pfad in der URL gegliedert werden: ```php $router = new RouteList; $router->withPath('eshop') - ->addRoute('rss', 'Feed:rss') // entspricht der URL /eshop/rss - ->addRoute('/'); // entspricht der URL /eshop// + ->addRoute('rss', 'Feed:rss') // Fängt URL /eshop/rss ab + ->addRoute('/'); // Fängt URL /eshop// ab ``` -Kombinationen .[#toc-combinations] ----------------------------------- +Kombinationen +------------- -Die oben genannten Verwendungen können kombiniert werden: +Die oben genannten Gliederungen können miteinander kombiniert werden: ```php $router = (new RouteList) @@ -424,40 +446,40 @@ $router = (new RouteList) ``` -Abfrageparameter .[#toc-query-parameters] ------------------------------------------ +Query-Parameter +--------------- -Masken können auch Abfrageparameter (Parameter nach dem Fragezeichen in der URL) enthalten. Sie können keinen Validierungsausdruck definieren, aber sie können den Namen ändern, unter dem sie an den Präsentator übergeben werden: +Masken können auch Query-Parameter enthalten (Parameter nach dem Fragezeichen in der URL). Für diese kann kein Validierungsausdruck definiert werden, aber der Name, unter dem sie an den Presenter übergeben werden, kann geändert werden: ```php -// Abfrageparameter "cat" als "categoryId" in der Anwendung verwenden +// Den Query-Parameter 'cat' möchten wir in der Anwendung unter dem Namen 'categoryId' verwenden $router->addRoute('product ? id= & cat=', /* ... */); ``` -Foo-Parameter .[#toc-foo-parameters] ------------------------------------- +Foo-Parameter +------------- -Wir gehen jetzt noch weiter in die Tiefe. Foo-Parameter sind im Grunde genommen unbenannte Parameter, die eine Übereinstimmung mit einem regulären Ausdruck ermöglichen. Die folgende Route entspricht `/index`, `/index.html`, `/index.htm` und `/index.php`: +Jetzt gehen wir tiefer. Foo-Parameter sind im Grunde unbenannte Parameter, die es ermöglichen, einen regulären Ausdruck abzugleichen. Ein Beispiel ist eine Route, die `/index`, `/index.html`, `/index.htm` und `/index.php` akzeptiert: ```php $router->addRoute('index', /* ... */); ``` -Es ist auch möglich, explizit eine Zeichenkette zu definieren, die für die URL-Generierung verwendet werden soll. Die Zeichenkette muss direkt nach dem Fragezeichen stehen. Die folgende Route ähnelt der vorherigen, erzeugt aber `/index.html` statt `/index`, da die Zeichenfolge `.html` als "generierter Wert" festgelegt ist. +Es kann auch explizit eine Zeichenkette definiert werden, die bei der URL-Generierung verwendet wird. Die Zeichenkette muss direkt nach dem Fragezeichen platziert werden. Die folgende Route ähnelt der vorherigen, aber generiert `/index.html` anstelle von `/index`, da die Zeichenkette `.html` als Generierungswert festgelegt ist: ```php $router->addRoute('index', /* ... */); ``` -Einbindung .[#toc-integration] -============================== +Integration in die Anwendung +============================ -Um unseren Router in die Anwendung einzubinden, müssen wir ihn dem DI-Container mitteilen. Am einfachsten ist es, die Fabrik vorzubereiten, die das Router-Objekt erstellt, und der Container-Konfiguration mitzuteilen, dass sie es verwenden soll. Schreiben wir also eine Methode für diesen Zweck `App\Router\RouterFactory::createRouter()`: +Um den erstellten Router in die Anwendung einzubinden, müssen wir den DI-Container darüber informieren. Der einfachste Weg ist, eine Factory vorzubereiten, die das Router-Objekt erstellt, und dem Container in der Konfiguration mitzuteilen, dass er sie verwenden soll. Nehmen wir an, wir schreiben zu diesem Zweck eine Methode `App\Core\RouterFactory::createRouter()`: ```php -namespace App\Router; +namespace App\Core; use Nette\Application\Routers\RouteList; @@ -472,14 +494,14 @@ class RouterFactory } ``` -Dann schreiben wir in [configuration |dependency-injection:services]: +In die [Konfiguration |dependency-injection:services] schreiben wir dann: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter ``` -Alle Abhängigkeiten, wie z. B. eine Datenbankverbindung usw., werden der Factory-Methode als Parameter über [Autowiring |dependency-injection:autowiring] übergeben: +Jegliche Abhängigkeiten, zum Beispiel auf eine Datenbank usw., werden der Factory-Methode als ihre Parameter mittels [Autowiring|dependency-injection:autowiring] übergeben: ```php public static function createRouter(Nette\Database\Connection $db): RouteList @@ -489,25 +511,25 @@ public static function createRouter(Nette\Database\Connection $db): RouteList ``` -SimpleRouter .[#toc-simplerouter] -================================= +SimpleRouter +============ -Ein viel einfacherer Router als die Route Collection ist [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Er kann verwendet werden, wenn kein bestimmtes URL-Format erforderlich ist, wenn `mod_rewrite` (oder Alternativen) nicht verfügbar ist oder wenn wir uns einfach noch nicht mit benutzerfreundlichen URLs befassen wollen. +Ein viel einfacherer Router als die Routen-Sammlung ist [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Wir verwenden ihn, wenn wir keine besonderen Anforderungen an die URL-Form haben, wenn `mod_rewrite` (oder seine Alternativen) nicht verfügbar ist oder wenn wir uns noch nicht mit schönen URLs befassen wollen. -Erzeugt Adressen in etwa dieser Form: +Er generiert Adressen ungefähr in dieser Form: ``` http://example.com/?presenter=Product&action=detail&id=123 ``` -Der Parameter des `SimpleRouter` -Konstruktors ist ein Standard-Präsentator und eine Aktion, d.h. eine Aktion, die ausgeführt wird, wenn wir z.B. `http://example.com/` ohne zusätzliche Parameter öffnen. +Der Parameter des SimpleRouter-Konstruktors ist der Standard-Presenter & die Standard-Aktion, auf den verwiesen werden soll, wenn wir eine Seite ohne Parameter öffnen, z. B. `http://example.com/`. ```php -// Standardmäßig wird der Präsentator 'Home' und die Aktion 'default' verwendet +// Der Standard-Presenter wird 'Home' sein und die Aktion 'default' $router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` -Wir empfehlen, SimpleRouter direkt in der [Konfiguration |dependency-injection:services] zu definieren: +Wir empfehlen, den SimpleRouter direkt in der [Konfiguration |dependency-injection:services] zu definieren: ```neon services: @@ -515,22 +537,22 @@ services: ``` -SEO und Kanonisierung .[#toc-seo-and-canonization] -================================================== +SEO und Kanonisierung +===================== -Das Framework verbessert die Suchmaschinenoptimierung (SEO), indem es die Duplizierung von Inhalten unter verschiedenen URLs verhindert. Wenn mehrere Adressen auf ein und dasselbe Ziel verweisen, z. B. `/index` und `/index.html`, legt das Framework die erste als primär (kanonisch) fest und leitet die anderen mit dem HTTP-Code 301 auf sie um. Auf diese Weise werden die Seiten von den Suchmaschinen nicht doppelt indiziert und ihr Page Rank wird nicht beeinträchtigt. . +Das Framework trägt zur SEO (Optimierung der Auffindbarkeit im Internet) bei, indem es doppelte Inhalte unter verschiedenen URLs verhindert. Wenn mehrere Adressen zu einem bestimmten Ziel führen, z. B. `/index` und `/index.html`, bestimmt das Framework die erste davon als primäre (kanonische) und leitet die anderen mit dem HTTP-Code 301 darauf um. Dadurch indizieren Suchmaschinen die Seiten nicht doppelt und verwässern ihren Page Rank nicht. -Dieser Vorgang wird Kanonisierung genannt. Die kanonische URL ist diejenige, die vom Router erzeugt wird, d. h. von der ersten übereinstimmenden Route in der [Sammlung |#route-collection] ohne das OneWay-Flag. Daher führen wir in der Sammlung **primäre Routen zuerst** auf. +Dieser Prozess wird Kanonisierung genannt. Die kanonische URL ist diejenige, die vom Router generiert wird, d. h. die erste passende Route in der Sammlung ohne das OneWay-Flag. Deshalb führen wir in der Sammlung die **primären Routen zuerst** auf. -Die Kanonisierung wird vom Präsentator durchgeführt, mehr dazu im Kapitel [Kanonisierung |presenters#Canonization]. +Die Kanonisierung wird vom Presenter durchgeführt, mehr im Kapitel [Kanonisierung |presenters#Kanonisierung]. -HTTPS .[#toc-https] -=================== +HTTPS +===== -Um das HTTPS-Protokoll verwenden zu können, muss es beim Hosting aktiviert und der Server konfiguriert werden. +Um das HTTPS-Protokoll verwenden zu können, ist es notwendig, es beim Hosting zu aktivieren und den Server korrekt zu konfigurieren. -Die Umleitung der gesamten Website auf HTTPS muss auf Serverebene erfolgen, zum Beispiel über die Datei .htaccess im Stammverzeichnis unserer Anwendung mit dem HTTP-Code 301. Die Einstellungen können je nach Hosting unterschiedlich sein und sehen etwa so aus: +Die Weiterleitung der gesamten Website auf HTTPS muss auf Serverebene eingestellt werden, zum Beispiel mithilfe der .htaccess-Datei im Stammverzeichnis unserer Anwendung, und zwar mit dem HTTP-Code 301. Die Einstellungen können je nach Hosting variieren und sehen etwa so aus: ``` @@ -542,40 +564,40 @@ Die Umleitung der gesamten Website auf HTTPS muss auf Serverebene erfolgen, zum ``` -Der Router generiert eine URL mit demselben Protokoll, mit dem die Seite geladen wurde, es muss also nichts weiter eingestellt werden. +Der Router generiert URLs mit demselben Protokoll, mit dem die Seite geladen wurde, daher muss nichts weiter eingestellt werden. -Wenn wir jedoch ausnahmsweise verschiedene Routen unter verschiedenen Protokollen benötigen, werden wir dies in der Routenmaske angeben: +Wenn wir aber ausnahmsweise benötigen, dass verschiedene Routen unter verschiedenen Protokollen laufen, geben wir es in der Routenmaske an: ```php -// Erzeugt eine HTTP-Adresse +// Wird eine Adresse mit HTTP generieren $router->addRoute('http://%host%//', /* ... */); -// Erzeugt eine HTTPS-Adresse +// Wird eine Adresse mit HTTPS generieren $router->addRoute('https://%host%//', /* ... */); ``` -Router debuggen .[#toc-debugging-router] -======================================== +Debugging des Routers +===================== -Die Routing-Leiste, die in [Tracy Bar |tracy:] angezeigt wird, ist ein nützliches Werkzeug, das eine Liste von Routen und auch die Parameter anzeigt, die der Router von der URL erhalten hat. +Das Routing-Panel, das in der [Tracy Bar |tracy:] angezeigt wird, ist ein nützlicher Helfer, der eine Liste der Routen sowie die Parameter anzeigt, die der Router aus der URL erhalten hat. -Der grüne Balken mit dem Symbol ✓ steht für die Route, die mit der aktuellen URL übereinstimmt, die blauen Balken mit den Symbolen ≈ zeigen die Routen an, die ebenfalls mit der URL übereinstimmen würden, wenn Grün sie nicht überholen würde. Wir sehen den aktuellen Präsentator & Aktion weiter. +Der grüne Balken mit dem Symbol ✓ stellt die Route dar, die die aktuelle URL verarbeitet hat, mit blauer Farbe und dem Symbol ≈ sind Routen gekennzeichnet, die die URL ebenfalls verarbeitet hätten, wenn die grüne sie nicht überholt hätte. Weiterhin sehen wir den aktuellen Presenter & die aktuelle Aktion. [* routing-debugger.webp *] -Gleichzeitig ist es bei einer unerwarteten Umleitung aufgrund einer [Kanonisierung |#SEO and Canonization] nützlich, in der *Redirect*-Leiste nachzusehen, wie der Router die URL ursprünglich verstanden hat und warum er sie umgeleitet hat. +Gleichzeitig, wenn es zu einer unerwarteten Weiterleitung aufgrund der [Kanonisierung |#SEO und Kanonisierung] kommt, ist es nützlich, in das Panel in der Leiste *redirect* zu schauen, wo Sie herausfinden, wie der Router die URL ursprünglich verstanden hat und warum er weitergeleitet hat. .[note] -Bei der Fehlersuche im Router empfiehlt es sich, die Entwicklertools im Browser zu öffnen (Strg+Umschalt+I oder Cmd+Option+I) und den Cache im Netzwerk-Panel zu deaktivieren, damit Umleitungen nicht darin gespeichert werden. +Beim Debuggen des Routers empfehlen wir, die Developer Tools im Browser zu öffnen (Strg+Shift+I oder Cmd+Option+I) und im Network-Panel den Cache zu deaktivieren, damit Weiterleitungen nicht darin gespeichert werden. -Leistung .[#toc-performance] -============================ +Leistung +======== -Die Anzahl der Routen wirkt sich auf die Geschwindigkeit des Routers aus. Ihre Anzahl sollte auf keinen Fall einige Dutzend überschreiten. Wenn Ihre Website eine übermäßig komplizierte URL-Struktur hat, können Sie einen [eigenen Router |#custom router] schreiben. +Die Anzahl der Routen beeinflusst die Geschwindigkeit des Routers. Ihre Anzahl sollte definitiv nicht mehrere Dutzend überschreiten. Wenn Ihre Website eine zu komplizierte URL-Struktur hat, können Sie einen maßgeschneiderten [#Eigener Router] schreiben. -Wenn der Router keine Abhängigkeiten hat, z. B. von einer Datenbank, und seine Factory keine Argumente hat, können wir seine kompilierte Form direkt in einen DI-Container serialisieren und so die Anwendung etwas schneller machen. +Wenn der Router keine Abhängigkeiten hat, zum Beispiel von einer Datenbank, und seine Factory keine Argumente entgegennimmt, können wir seine kompilierte Form direkt in den DI-Container serialisieren und dadurch die Anwendung geringfügig beschleunigen. ```neon routing: @@ -583,10 +605,10 @@ routing: ``` -Benutzerdefinierter Router .[#toc-custom-router] -================================================ +Eigener Router +============== -Die folgenden Zeilen sind für sehr fortgeschrittene Benutzer gedacht. Sie können Ihren eigenen Router erstellen und ihn natürlich in Ihre Routensammlung aufnehmen. Der Router ist eine Implementierung der Schnittstelle [api:Nette\Routing\Router] mit zwei Methoden: +Die folgenden Zeilen sind für sehr fortgeschrittene Benutzer bestimmt. Sie können Ihren eigenen Router erstellen und ihn ganz natürlich in die Routen-Sammlung integrieren. Der Router ist eine Implementierung des Interfaces [api:Nette\Routing\Router] mit zwei Methoden: ```php use Nette\Http\IRequest as HttpRequest; @@ -606,8 +628,7 @@ class MyRouter implements Nette\Routing\Router } ``` -Die Methode `match` verarbeitet den aktuellen [$httpRequest |http:request], aus dem nicht nur die URL, sondern auch Header etc. abgerufen werden können, in ein Array, das den Presenter-Namen und seine Parameter enthält. Kann sie die Anfrage nicht verarbeiten, gibt sie null zurück. -Bei der Verarbeitung der Anfrage müssen wir zumindest den Präsentator und die Aktion zurückgeben. Der Name des Präsentators ist vollständig und enthält alle Module: +Die Methode `match` verarbeitet die aktuelle Anfrage [$httpRequest |http:request], aus der nicht nur die URL, sondern auch Header usw. abgerufen werden können, in ein Array, das den Namen des Presenters und seine Parameter enthält. Wenn sie die Anfrage nicht verarbeiten kann, gibt sie null zurück. Bei der Verarbeitung der Anfrage müssen wir mindestens den Presenter und die Aktion zurückgeben. Der Name des Presenters ist vollständig und enthält auch eventuelle Module: ```php [ @@ -616,9 +637,9 @@ Bei der Verarbeitung der Anfrage müssen wir zumindest den Präsentator und die ] ``` -Die Methode `constructUrl` hingegen generiert eine absolute URL aus dem Array der Parameter. Sie kann die Informationen aus dem Parameter `$refUrl` verwenden, der die aktuelle URL ist. +Die Methode `constructUrl` erstellt umgekehrt aus dem Parameter-Array die resultierende absolute URL. Dazu kann sie Informationen aus dem Parameter [`$refUrl`|api:Nette\Http\UrlScript] verwenden, was die aktuelle URL ist. -Um der Routensammlung einen benutzerdefinierten Router hinzuzufügen, verwenden Sie `add()`: +Zur Routen-Sammlung fügen Sie ihn mit `add()` hinzu: ```php $router = new Nette\Application\Routers\RouteList; @@ -628,19 +649,19 @@ $router->addRoute(/* ... */); ``` -Getrennte Verwendung .[#toc-separated-usage] -============================================ +Eigenständige Verwendung +======================== -Unter getrennter Nutzung verstehen wir die Verwendung der Router-Funktionen in einer Anwendung, die Nette Application und Presenter nicht verwendet. Fast alles, was wir in diesem Kapitel gezeigt haben, trifft auf sie zu, mit den folgenden Unterschieden: +Unter eigenständiger Verwendung verstehen wir die Nutzung der Fähigkeiten des Routers in einer Anwendung, die Nette Application und Presenter nicht verwendet. Dafür gilt fast alles, was wir in diesem Kapitel gezeigt haben, mit diesen Unterschieden: -- für Routensammlungen verwenden wir die Klasse [api:Nette\Routing\RouteList] -- als einfache Router-Klasse [api:Nette\Routing\SimpleRouter] -- da es kein Paar `Presenter:action` gibt, verwenden wir die [Advanced-Notation |#Advanced notation] +- für Routen-Sammlungen verwenden wir die Klasse [api:Nette\Routing\RouteList] +- als einfachen Router die Klasse [api:Nette\Routing\SimpleRouter] +- da das Paar `Presenter:action` nicht existiert, verwenden wir die [#Erweiterte Notation] -Wir werden also wieder eine Methode erstellen, die einen Router aufbaut, zum Beispiel: +Also erstellen wir wieder eine Methode, die uns den Router zusammenstellt, z.B.: ```php -namespace App\Router; +namespace App\Core; use Nette\Routing\RouteList; @@ -661,7 +682,7 @@ class RouterFactory } ``` -Wenn Sie einen DI-Container verwenden, was wir empfehlen, fügen Sie die Methode erneut zur Konfiguration hinzu und holen Sie den Router zusammen mit der HTTP-Anfrage aus dem Container: +Wenn Sie einen DI-Container verwenden, was wir empfehlen, fügen wir die Methode wieder zur Konfiguration hinzu und holen danach den Router zusammen mit der HTTP-Anfrage aus dem Container: ```php $router = $container->getByType(Nette\Routing\Router::class); @@ -671,25 +692,25 @@ $httpRequest = $container->getByType(Nette\Http\IRequest::class); Oder wir erstellen die Objekte direkt: ```php -$router = App\Router\RouterFactory::createRouter(); +$router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` -Jetzt müssen wir den Router arbeiten lassen: +Jetzt muss der Router nur noch seine Arbeit aufnehmen: ```php $params = $router->match($httpRequest); if ($params === null) { - // keine passende Route gefunden, wir senden einen 404-Fehler + // Es wurde keine passende Route gefunden, senden wir einen 404-Fehler exit; } -// wir verarbeiten die empfangenen Parameter +// Wir verarbeiten die erhaltenen Parameter $controller = $params['controller']; // ... ``` -Umgekehrt werden wir den Router benutzen, um die Verbindung herzustellen: +Und umgekehrt verwenden wir den Router, um einen Link zu erstellen: ```php $params = ['controller' => 'ArticleController', 'id' => 123]; diff --git a/application/de/templates.texy b/application/de/templates.texy index 10b1ae1db5..4b36ccee84 100644 --- a/application/de/templates.texy +++ b/application/de/templates.texy @@ -1,16 +1,16 @@ -Vorlagen -******** +Templates +********* .[perex] -Nette verwendet das Vorlagensystem [Latte |latte:]. Latte wird verwendet, weil es das sicherste Vorlagensystem für PHP und gleichzeitig das intuitivste System ist. Sie müssen nicht viel Neues lernen, Sie müssen nur PHP und ein paar Latte-Tags kennen. +Nette verwendet das Template-System [Latte |latte:]. Zum einen, weil es das sicherste Template-System für PHP ist, und zum anderen, weil es das intuitivste System ist. Sie müssen nicht viel Neues lernen, PHP-Kenntnisse und ein paar Tags reichen aus. -In der Regel wird die Seite aus der Layout-Vorlage und der Aktionsvorlage erstellt. So könnte eine Layout-Vorlage aussehen, beachten Sie die Blöcke `{block}` und den Tag `{include}`: +Es ist üblich, dass eine Seite aus einer Layout-Vorlage und einer Vorlage für die jeweilige Aktion zusammengesetzt wird. So kann zum Beispiel eine Layout-Vorlage aussehen, beachten Sie die `{block}` Blöcke und den `{include}` Tag: ```latte - {block title}My App{/block} + {block title}Meine App{/block}
    ...
    @@ -20,7 +20,7 @@ In der Regel wird die Seite aus der Layout-Vorlage und der Aktionsvorlage erstel ``` -Und dies könnte die Aktionsvorlage sein: +Und das wird die Aktionsvorlage sein: ```latte {block title}Homepage{/block} @@ -31,50 +31,98 @@ Und dies könnte die Aktionsvorlage sein: {/block} ``` -Sie definiert den Block `content`, der anstelle von `{include content}` in das Layout eingefügt wird, und definiert auch den Block `title` neu, der `{block title}` im Layout überschreibt. Versuchen Sie, sich das Ergebnis vorzustellen. +Diese definiert den Block `content`, der anstelle von `{include content}` im Layout eingefügt wird, und definiert auch den Block `title` neu, der `{block title}` im Layout überschreibt. Versuchen Sie, sich das Ergebnis vorzustellen. -Suche nach Templates .[#toc-search-for-templates] -------------------------------------------------- +Finden von Vorlagen +------------------- -Der Pfad zu den Vorlagen wird nach einer einfachen Logik hergeleitet. Es wird versucht zu sehen, ob eine dieser Vorlagendateien relativ zu dem Verzeichnis existiert, in dem sich die Presenter-Klasse befindet, wobei `` der Name des aktuellen Präsentators ist und `` der Name der aktuellen Aktion ist: +Sie müssen in den Presentern nicht angeben, welche Vorlage gerendert werden soll; das Framework leitet den Pfad selbst ab und erspart Ihnen das Schreiben. -- `templates//.latte` -- `templates/..latte` +Wenn Sie eine Verzeichnisstruktur verwenden, in der jeder Presenter sein eigenes Verzeichnis hat, platzieren Sie die Vorlage einfach in diesem Verzeichnis unter dem Namen der Aktion (bzw. der View), d.h. für die Aktion `default` verwenden Sie die Vorlage `default.latte`: -Wenn die Vorlage nicht gefunden wird, wird der [Fehler 404 |presenters#Error 404 etc.] ausgegeben. +/--pre +app/ +└── Presentation/ + └── Home/ + ├── HomePresenter.php + └── default.latte +\-- -Sie können die Ansicht auch mit `$this->setView('otherView')` ändern. Oder geben Sie statt der Suche direkt den Namen der Vorlagendatei mit `$this->template->setFile('/path/to/template.latte')` an. +Wenn Sie eine Struktur verwenden, bei der sich die Presenter gemeinsam in einem Verzeichnis und die Vorlagen im Ordner `templates` befinden, speichern Sie sie entweder in der Datei `..latte` oder `/.latte`: + +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── Home.default.latte ← 1. Variante + └── Home/ + └── default.latte ← 2. Variante +\-- + +Der Ordner `templates` kann auch eine Ebene höher platziert werden, d.h. auf derselben Ebene wie das Verzeichnis mit den Presenter-Klassen. + +Wenn die Vorlage nicht gefunden wird, antwortet der Presenter [mit einem Fehler 404 - Seite nicht gefunden |presenters#Fehler 404 und Co]. + +Die View ändern Sie mit `$this->setView('andereView')`. Es ist auch möglich, die Datei mit der Vorlage direkt mit `$this->template->setFile('/path/to/template.latte')` anzugeben. .[note] -Sie können die Pfade, in denen Vorlagen gesucht werden, ändern, indem Sie die Methode [formatTemplateFiles |api:Nette\Application\UI\Presenter::formatTemplateFiles()] überschreiben, die ein Array mit möglichen Dateipfaden zurückgibt. +Die Dateien, in denen nach Vorlagen gesucht wird, können durch Überschreiben der Methode [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()] geändert werden, die ein Array möglicher Dateinamen zurückgibt. + + +Finden der Layout-Vorlage +------------------------- + +Nette sucht auch automatisch nach der Layout-Datei. + +Wenn Sie eine Verzeichnisstruktur verwenden, in der jeder Presenter sein eigenes Verzeichnis hat, platzieren Sie das Layout entweder im Ordner des Presenters, wenn es nur für diesen spezifisch ist, oder eine Ebene höher, wenn es für mehrere Presenter gemeinsam genutzt wird: + +/--pre +app/ +└── Presentation/ + ├── @layout.latte ← gemeinsames Layout + └── Home/ + ├── @layout.latte ← nur für Presenter Home + ├── HomePresenter.php + └── default.latte +\-- + +Wenn Sie eine Struktur verwenden, bei der sich die Presenter gemeinsam in einem Verzeichnis und die Vorlagen im Ordner `templates` befinden, wird das Layout an diesen Stellen erwartet: -Das Layout wird in den folgenden Dateien erwartet: +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── @layout.latte ← gemeinsames Layout + ├── Home.@layout.latte ← nur für Home, 1. Variante + └── Home/ + └── @layout.latte ← nur für Home, 2. Variante +\-- -- `templates//@.latte` -- `templates/.@.latte` -- `templates/@.latte` gemeinsames Layout für mehrere Präsentatoren +Wenn sich der Presenter in einem Modul befindet, wird auch auf weiteren Verzeichnisebenen höher gesucht, je nach Verschachtelung des Moduls. -`` ist der Name des aktuellen Präsentators und `` ist der Name des Layouts, der standardmäßig `'layout'` lautet. Der Name kann mit `$this->setLayout('otherLayout')` geändert werden, so dass `@otherLayout.latte` Dateien ausprobiert werden. +Der Name des Layouts kann mit `$this->setLayout('layoutAdmin')` geändert werden, dann wird es in der Datei `@layoutAdmin.latte` erwartet. Es ist auch möglich, die Datei mit der Layout-Vorlage direkt mit `$this->setLayout('/path/to/template.latte')` anzugeben. -Sie können auch direkt den Dateinamen der Layoutvorlage mit `$this->setLayout('/path/to/template.latte')` angeben. Durch die Verwendung von `$this->setLayout(false)` wird die Layout-Suche deaktiviert. +Mit `$this->setLayout(false)` oder dem Tag `{layout none}` innerhalb der Vorlage wird die Layout-Suche deaktiviert. .[note] -Sie können die Pfade, in denen Vorlagen gesucht werden, ändern, indem Sie die Methode [formatLayoutTemplateFiles |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()] überschreiben, die ein Array mit möglichen Dateipfaden zurückgibt. +Die Dateien, in denen nach Layout-Vorlagen gesucht wird, können durch Überschreiben der Methode [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()] geändert werden, die ein Array möglicher Dateinamen zurückgibt. -Variablen in der Vorlage .[#toc-variables-in-the-template] ----------------------------------------------------------- +Variablen in der Vorlage +------------------------ -Variablen werden an die Vorlage übergeben, indem sie in `$this->template` geschrieben werden, und sind dann in der Vorlage als lokale Variablen verfügbar: +Variablen werden an die Vorlage übergeben, indem wir sie in `$this->template` schreiben. Danach stehen sie in der Vorlage als lokale Variablen zur Verfügung: ```php $this->template->article = $this->articles->getById($id); ``` -Auf diese Weise können wir problemlos beliebige Variablen an die Vorlagen übergeben. Bei der Entwicklung robuster Anwendungen ist es jedoch oft sinnvoller, sich einzuschränken. Zum Beispiel durch die explizite Definition einer Liste von Variablen, die die Vorlage erwartet, und deren Typen. Auf diese Weise kann PHP eine Typüberprüfung durchführen, die IDE eine korrekte Autovervollständigung vornehmen und die statische Analyse Fehler erkennen. +So einfach können wir beliebige Variablen an Vorlagen übergeben. Bei der Entwicklung robuster Anwendungen ist es jedoch nützlicher, sich einzuschränken. Zum Beispiel, indem wir explizit eine Liste der Variablen definieren, die die Vorlage erwartet, und deren Typen. Dadurch kann PHP die Typen prüfen, die IDE korrekt Vorschläge machen und die statische Analyse Fehler aufdecken. -Und wie definieren wir eine solche Aufzählung? Einfach in Form einer Klasse und ihrer Eigenschaften. Wir nennen sie ähnlich wie presenter, aber mit `Template` am Ende: +Und wie definieren wir eine solche Liste? Einfach in Form einer Klasse und ihrer Eigenschaften. Wir benennen sie ähnlich wie den Presenter, nur mit `Template` am Ende: ```php /** @@ -89,26 +137,26 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template public Model\Article $article; public Nette\Security\User $user; - // und andere Variablen + // und weitere Variablen } ``` -Das Objekt `$this->template` im Presenter wird nun eine Instanz der Klasse `ArticleTemplate` sein. PHP wird also die deklarierten Typen überprüfen, wenn sie geschrieben werden. Und ab PHP 8.2 wird auch vor dem Schreiben in eine nicht existierende Variable gewarnt. In früheren Versionen kann dasselbe mit der [Nette\SmartObject-Eigenschaft |utils:smartobject] erreicht werden. +Das Objekt `$this->template` im Presenter ist nun eine Instanz der Klasse `ArticleTemplate`. PHP prüft also beim Schreiben die deklarierten Typen. Ab PHP Version 8.2 wird auch auf das Schreiben in eine nicht existierende Variable hingewiesen, in früheren Versionen kann dasselbe durch die Verwendung des Traits [Nette\SmartObject |utils:smartobject] erreicht werden. -Die `@property-read` Annotation ist für die IDE und die statische Analyse, sie sorgt dafür, dass die Autovervollständigung funktioniert, siehe "PhpStorm and code completion for $this->template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. +Die Annotation `@property-read` ist für IDEs und statische Analyse gedacht, dank ihr funktioniert die Autovervollständigung, siehe [PhpStorm und Code-Vervollständigung für $this->template |https://blog.nette.org/de/phpstorm-und-code-completion-for-this-template]. [* phpstorm-completion.webp *] -Sie können sich auch den Luxus gönnen, in Vorlagen zu flüstern. Installieren Sie einfach das Latte-Plugin in PhpStorm und geben Sie den Klassennamen am Anfang der Vorlage an, siehe den Artikel "Latte: how to type system":https://blog.nette.org/de/latte-wie-benutzt-man-das-typensystem: +Den Luxus der Autovervollständigung können Sie sich auch in Vorlagen gönnen. Installieren Sie einfach das Latte-Plugin für PhpStorm und geben Sie den Klassennamen am Anfang der Vorlage an, mehr dazu im Artikel [Latte: wie man das Typsystem benutzt |https://blog.nette.org/de/latte-wie-man-das-typsystem-benutzt]: ```latte -{templateType App\Presenters\ArticleTemplate} +{templateType App\Presentation\Article\ArticleTemplate} ... ``` -So funktionieren Templates auch in Komponenten, folgen Sie einfach der Namenskonvention und erstellen Sie eine Template-Klasse `FifteenTemplate` für die Komponente, z.B. `FifteenControl`. +So funktionieren auch Vorlagen in Komponenten. Halten Sie einfach die Namenskonvention ein und erstellen Sie für eine Komponente wie `FifteenControl` eine Vorlagenklasse `FifteenTemplate`. -Wenn Sie eine `$template` als Instanz einer anderen Klasse erstellen müssen, verwenden Sie die Methode `createTemplate()`: +Wenn Sie `$template` als Instanz einer anderen Klasse erstellen müssen, verwenden Sie die Methode `createTemplate()`: ```php public function renderDefault(): void @@ -121,43 +169,43 @@ public function renderDefault(): void ``` -Standard-Variablen .[#toc-default-variables] --------------------------------------------- +Standardvariablen +----------------- -Presenter und Komponenten übergeben automatisch mehrere nützliche Variablen an Vorlagen: +Presenter und Komponenten übergeben automatisch einige nützliche Variablen an die Vorlagen: -- `$basePath` ist ein absoluter URL-Pfad zum Stammverzeichnis (z. B. `/CD-collection`) -- `$baseUrl` ist eine absolute URL zum Stammverzeichnis (z. B. `http://localhost/CD-collection`) -- `$user` ist ein Objekt [, das den Benutzer repräsentiert |security:authentication] -- `$presenter` ist der aktuelle Präsentator -- `$control` ist die aktuelle Komponente oder der aktuelle Präsentator -- `$flashes` Liste der von der Methode gesendeten [Nachrichten |presenters#flash-messages] `flashMessage()` +- `$basePath` ist der absolute URL-Pfad zum Stammverzeichnis (z.B. `/eshop`) +- `$baseUrl` ist die absolute URL zum Stammverzeichnis (z.B. `http://localhost/eshop`) +- `$user` ist das Objekt, das den [Benutzer repräsentiert |security:authentication] +- `$presenter` ist der aktuelle Presenter +- `$control` ist die aktuelle Komponente oder der aktuelle Presenter +- `$flashes` Array von [Nachrichten |presenters#Flash-Nachrichten], die mit der Funktion `flashMessage()` gesendet wurden -Wenn Sie eine benutzerdefinierte Vorlagenklasse verwenden, werden diese Variablen übergeben, wenn Sie eine Eigenschaft für sie erstellen. +Wenn Sie Ihre eigene Vorlagenklasse verwenden, werden diese Variablen übergeben, wenn Sie eine Eigenschaft für sie erstellen. -Erstellen von Links .[#toc-creating-links] ------------------------------------------- +Erstellen von Links +------------------- -In der Vorlage erstellen wir Links zu anderen Präsentatoren und Aktionen wie folgt: +In der Vorlage werden Links zu anderen Presentern & Aktionen auf diese Weise erstellt: ```latte -detail +Produktdetail ``` -Das Attribut `n:href` ist sehr praktisch für HTML-Tags ``. Wenn wir den Link an anderer Stelle, zum Beispiel im Text, ausgeben wollen, verwenden wir `{link}`: +Das Attribut `n:href` ist sehr praktisch für HTML-Tags ``. Wenn wir den Link an anderer Stelle ausgeben möchten, zum Beispiel im Text, verwenden wir `{link}`: ```latte -URL is: {link Home:default} +Die Adresse ist: {link Home:default} ``` -Weitere Informationen finden Sie unter [Links erstellen |Creating Links]. +Weitere Informationen finden Sie im Kapitel [Erstellen von URL-Links|creating-links]. -Benutzerdefinierte Filter, Tags, etc. .[#toc-custom-filters-tags-etc] ---------------------------------------------------------------------- +Eigene Filter, Tags usw. +------------------------ -Das Latte-Vorlagensystem kann mit benutzerdefinierten Filtern, Funktionen, Tags usw. erweitert werden. Dies kann direkt in der `render` oder `beforeRender()` Methode erfolgen: +Das Latte-Template-System kann um eigene Filter, Funktionen, Tags usw. erweitert werden. Dies kann direkt in der Methode `render` oder `beforeRender()` geschehen: ```php public function beforeRender(): void @@ -165,16 +213,16 @@ public function beforeRender(): void // Hinzufügen eines Filters $this->template->addFilter('foo', /* ... */); - // oder das Latte\Engine-Objekt direkt konfigurieren + // oder wir konfigurieren direkt das Latte\Engine Objekt $latte = $this->template->getLatte(); $latte->addFilterLoader(/* ... */); } ``` -Latte Version 3 bietet einen fortgeschritteneren Weg, indem es eine [Erweiterung |latte:creating-extension] für jedes Webprojekt erstellt. Hier ist ein grobes Beispiel für eine solche Klasse: +Latte in Version 3 bietet einen fortgeschritteneren Weg, nämlich die Erstellung einer [Extension |latte:extending-latte#Latte Extension] für jedes Webprojekt. Ein kurzes Beispiel einer solchen Klasse: ```php -namespace App\Templating; +namespace App\Presentation\Accessory; final class LatteExtension extends Latte\Extension { @@ -207,22 +255,21 @@ final class LatteExtension extends Latte\Extension } ``` -Wir registrieren sie mit [configuration |configuration#Latte]: +Wir registrieren sie über die [Konfiguration |configuration#Latte-Templates]: ```neon latte: extensions: - - App\Templating\LatteExtension + - App\Presentation\Accessory\LatteExtension ``` -Übersetzen .[#toc-translating] ------------------------------- +Übersetzung +----------- -Wenn Sie eine mehrsprachige Anwendung programmieren, werden Sie wahrscheinlich einen Teil des Textes in der Vorlage in verschiedenen Sprachen ausgeben müssen. Zu diesem Zweck definiert das Nette Framework eine Übersetzungsschnittstelle [api:Nette\Localization\Translator], die über eine einzige Methode `translate()` verfügt. Diese akzeptiert die Nachricht `$message`, bei der es sich in der Regel um eine Zeichenkette handelt, und beliebige andere Parameter. Die Aufgabe besteht darin, die übersetzte Zeichenkette zurückzugeben. -Es gibt keine Standardimplementierung in Nette, Sie können nach Ihren Bedürfnissen aus mehreren fertigen Lösungen wählen, die Sie auf [Componette |https://componette.org/search/localization] finden. Die Dokumentation erklärt Ihnen, wie Sie den Übersetzer konfigurieren können. +Wenn Sie eine mehrsprachige Anwendung programmieren, müssen Sie wahrscheinlich einige Texte in der Vorlage in verschiedenen Sprachen ausgeben. Zu diesem Zweck definiert das Nette Framework ein Übersetzungs-Interface [api:Nette\Localization\Translator], das nur eine Methode `translate()` hat. Diese nimmt die Nachricht `$message` entgegen, die normalerweise eine Zeichenkette ist, sowie beliebige weitere Parameter. Die Aufgabe besteht darin, die übersetzte Zeichenkette zurückzugeben. In Nette gibt es keine Standardimplementierung; Sie können je nach Bedarf aus mehreren fertigen Lösungen wählen, die Sie auf [Componette |https://componette.org/search/localization] finden. In deren Dokumentation erfahren Sie, wie Sie den Translator konfigurieren. -Vorlagen können mit einem Übersetzer eingerichtet werden, den [wir |dependency-injection:passing-dependencies] mit der Methode `setTranslator()` [an uns übergeben |dependency-injection:passing-dependencies]: +Für Vorlagen kann ein Translator, den [wir uns übergeben lassen |dependency-injection:passing-dependencies], mit der Methode `setTranslator()` festgelegt werden: ```php protected function beforeRender(): void @@ -232,18 +279,18 @@ protected function beforeRender(): void } ``` -Alternativ kann der Übersetzer auch über die [Konfiguration |configuration#Latte] eingestellt werden: +Alternativ kann der Translator über die [Konfiguration |configuration#Latte-Templates] eingestellt werden: ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -Der Übersetzer kann dann z.B. als Filter `|translate` verwendet werden, wobei zusätzliche Parameter an die Methode `translate()` übergeben werden (siehe `foo, bar`): +Danach kann der Translator beispielsweise als Filter `|translate` verwendet werden, einschließlich zusätzlicher Parameter, die an die Methode `translate()` übergeben werden (siehe `foo, bar`): ```latte -{='Basket'|translate} +{='Warenkorb'|translate} {$item|translate} {$item|translate, foo, bar} ``` @@ -251,19 +298,19 @@ Der Übersetzer kann dann z.B. als Filter `|translate` verwendet werden, wobei z Oder als Unterstrich-Tag: ```latte -{_'Basket'} +{_'Warenkorb'} {_$item} {_$item, foo, bar} ``` -Für die Übersetzung von Vorlagenabschnitten gibt es ein gepaartes Tag `{translate}` (seit Latte 2.11, vorher wurde das Tag `{_}` verwendet): +Für die Übersetzung eines Vorlagenabschnitts gibt es den paarweisen Tag `{translate}` (seit Latte 2.11, früher wurde der Tag `{_}` verwendet): ```latte -{translate}Order{/translate} -{translate foo, bar}Order{/translate} +{translate}Bestellung{/translate} +{translate foo, bar}Bestellung{/translate} ``` -Der Translator wird standardmäßig zur Laufzeit beim Rendern der Vorlage aufgerufen. Latte Version 3 kann jedoch den gesamten statischen Text während der Kompilierung der Vorlage übersetzen. Dies spart Leistung, da jede Zeichenkette nur einmal übersetzt wird und die resultierende Übersetzung in das kompilierte Formular geschrieben wird. Dadurch werden mehrere kompilierte Versionen der Vorlage im Cache-Verzeichnis erstellt, eine für jede Sprache. Dazu müssen Sie nur die Sprache als zweiten Parameter angeben: +Der Translator wird standardmäßig zur Laufzeit beim Rendern der Vorlage aufgerufen. Latte Version 3 kann jedoch alle statischen Texte bereits während der Kompilierung der Vorlage übersetzen. Dadurch wird Leistung gespart, da jede Zeichenkette nur einmal übersetzt wird und die resultierende Übersetzung in die kompilierte Form geschrieben wird. Im Cache-Verzeichnis entstehen so mehrere kompilierte Versionen der Vorlage, eine für jede Sprache. Dazu genügt es, die Sprache als zweiten Parameter anzugeben: ```php protected function beforeRender(): void @@ -273,4 +320,4 @@ protected function beforeRender(): void } ``` -Unter statischem Text verstehen wir z. B. `{_'hello'}` oder `{translate}hello{/translate}`. Nicht-statischer Text, wie z. B. `{_$foo}`, wird weiterhin im laufenden Betrieb kompiliert. +Mit statischem Text ist z.B. `{_'hello'}` oder `{translate}hello{/translate}` gemeint. Nicht-statische Texte, wie z.B. `{_$foo}`, werden weiterhin zur Laufzeit übersetzt. diff --git a/application/el/@home.texy b/application/el/@home.texy index 8020e59b5f..1abad7558e 100644 --- a/application/el/@home.texy +++ b/application/el/@home.texy @@ -1,36 +1,85 @@ -Εφαρμογή Nette -************** +Nette Application +***************** .[perex] -Το πακέτο `nette/application` αποτελεί τη βάση για τη δημιουργία διαδραστικών εφαρμογών ιστού. - -- [Πώς λειτουργούν οι εφαρμογές; |how-it-works] -- [Bootstrap |Bootstrap] -- [Παρουσιαστές |Presenters] -- [Πρότυπα |Templates] -- [Ενότητες |Modules] -- [Δρομολόγηση |Routing] -- [Δημιουργία συνδέσμων URL |creating-links] -- [Διαδραστικά στοιχεία |components] -- [AJAX & Snippets |ajax] -- [Πολλαπλασιαστής |multiplier] -- [Διαμόρφωση |Configuration] +Η Nette Application είναι ο πυρήνας του Nette Framework, παρέχοντας ισχυρά εργαλεία για τη δημιουργία σύγχρονων web εφαρμογών. Προσφέρει μια σειρά από εξαιρετικά χαρακτηριστικά που διευκολύνουν σημαντικά την ανάπτυξη και βελτιώνουν την ασφάλεια και τη συντηρησιμότητα του κώδικα. Εγκατάσταση ----------- -Κατεβάστε και εγκαταστήστε το πακέτο χρησιμοποιώντας το [Composer |best-practices:composer]: +Κατεβάστε και εγκαταστήστε τη βιβλιοθήκη χρησιμοποιώντας το εργαλείο [Composer|best-practices:composer]: ```shell composer require nette/application ``` -| έκδοση | συμβατό με PHP + +Γιατί να επιλέξετε την Nette Application; +----------------------------------------- + +Το Nette ήταν πάντα πρωτοπόρο στον τομέα των web τεχνολογιών. + +**Αμφίδρομος router:** Το Nette διαθέτει ένα προηγμένο σύστημα δρομολόγησης, το οποίο είναι μοναδικό για την αμφίδρομη φύση του - όχι μόνο μεταφράζει τα URL σε ενέργειες (actions) της εφαρμογής, αλλά μπορεί επίσης να δημιουργήσει αντίστροφα διευθύνσεις URL. Αυτό σημαίνει ότι: +- Μπορείτε να αλλάξετε τη δομή των URL ολόκληρης της εφαρμογής ανά πάσα στιγμή χωρίς να χρειάζεται να επεξεργαστείτε τα templates +- Τα URL κανονικοποιούνται αυτόματα, γεγονός που βελτιώνει το SEO +- Η δρομολόγηση ορίζεται σε ένα σημείο, αντί να είναι διάσπαρτη σε annotations + +**Components και signals:** Το ενσωματωμένο σύστημα component, εμπνευσμένο από το Delphi και το React.js, είναι εντελώς μοναδικό μεταξύ των PHP frameworks: +- Επιτρέπει τη δημιουργία επαναχρησιμοποιήσιμων στοιχείων UI +- Υποστηρίζει την ιεραρχική σύνθεση components +- Προσφέρει κομψή επεξεργασία αιτημάτων AJAX χρησιμοποιώντας signals +- Πλούσια βιβλιοθήκη έτοιμων components στο [Componette](https://componette.org) + +**AJAX και snippets:** Το Nette παρουσίασε έναν επαναστατικό τρόπο εργασίας με AJAX ήδη από το 2009, πολύ πριν από παρόμοιες λύσεις όπως το Hotwire για Ruby on Rails ή το Symfony UX Turbo: +- Τα snippets επιτρέπουν την ενημέρωση μόνο τμημάτων της σελίδας χωρίς την ανάγκη γραφής JavaScript +- Αυτόματη ενσωμάτωση με το σύστημα component +- Έξυπνη ακύρωση (invalidation) τμημάτων σελίδων +- Ελάχιστη ποσότητα μεταφερόμενων δεδομένων + +**Διαισθητικά templates [Latte|latte:]:** Το ασφαλέστερο σύστημα templating για PHP με προηγμένες λειτουργίες: +- Αυτόματη προστασία από XSS με context-aware escaping +- Επεκτασιμότητα μέσω προσαρμοσμένων φίλτρων, συναρτήσεων και tags +- Κληρονομικότητα templates και snippets για AJAX +- Εξαιρετική υποστήριξη PHP 8.x με σύστημα τύπων + +**Dependency Injection:** Το Nette αξιοποιεί πλήρως το Dependency Injection: +- Αυτόματη μεταβίβαση εξαρτήσεων (autowiring) +- Διαμόρφωση μέσω σαφούς μορφής NEON +- Υποστήριξη για factories component + + +Κύρια πλεονεκτήματα +------------------- + +- **Ασφάλεια**: Αυτόματη άμυνα έναντι [ευπαθειών |nette:vulnerability-protection] όπως XSS, CSRF, κ.λπ. +- **Παραγωγικότητα**: Λιγότερη πληκτρολόγηση, περισσότερες λειτουργίες χάρη στον έξυπνο σχεδιασμό +- **Debugging**: [Tracy debugger |tracy:] με πίνακα δρομολόγησης +- **Απόδοση**: Έξυπνη cache, lazy loading components +- **Ευελιξία**: Εύκολη τροποποίηση των URL ακόμη και μετά την ολοκλήρωση της εφαρμογής +- **Components**: Μοναδικό σύστημα επαναχρησιμοποιήσιμων στοιχείων UI +- **Σύγχρονο**: Πλήρης υποστήριξη PHP 8.4+ και συστήματος τύπων + + +Ξεκινώντας +---------- + +1. [Πώς λειτουργούν οι εφαρμογές; |how-it-works] - Κατανόηση της βασικής αρχιτεκτονικής +2. [Presenters |presenters] - Εργασία με presenters και actions +3. [Templates |templates] - Δημιουργία templates στο Latte +4. [Δρομολόγηση |routing] - Διαμόρφωση διευθύνσεων URL +5. [Διαδραστικά components |components] - Χρήση του συστήματος component + + +Συμβατότητες με PHP +------------------- + +| έκδοση | συμβατό με PHP |-----------|------------------- -| Εφαρμογή Nette 4.0 | PHP 8.0 - 8.2 -| Nette Application 3.1 | PHP 7.2 - 8.2 -| Nette Application 3.0 | PHP 7.1 - 8.0 -| Nette Application 2.4 | PHP 5.6 - 8.0 +| Nette Application 4.0 | PHP 8.1 – 8.4 +| Nette Application 3.2 | PHP 8.1 – 8.4 +| Nette Application 3.1 | PHP 7.2 – 8.3 +| Nette Application 3.0 | PHP 7.1 – 8.0 +| Nette Application 2.4 | PHP 5.6 – 8.0 -Ισχύει για τις τελευταίες εκδόσεις διορθώσεων. +Ισχύει για την τελευταία έκδοση patch. diff --git a/application/el/@left-menu.texy b/application/el/@left-menu.texy index 79024816ce..2f84a69e5d 100644 --- a/application/el/@left-menu.texy +++ b/application/el/@left-menu.texy @@ -1,19 +1,22 @@ -Εφαρμογή Nette -************** +Nette Application +***************** - [Πώς λειτουργούν οι εφαρμογές; |how-it-works] -- [Bootstrap |Bootstrap] -- [Παρουσιαστές |Presenters] -- [Πρότυπα |Templates] -- [Ενότητες |Modules] -- [Δρομολόγηση |Routing] +- [Bootstrapping] +- [Presenters |presenters] +- [Templates |templates] +- [Δομή Καταλόγων |directory-structure] +- [Δρομολόγηση |routing] - [Δημιουργία συνδέσμων URL |creating-links] -- [Διαδραστικά στοιχεία |components] -- [AJAX & Snippets |ajax] -- [Πολλαπλασιαστής |multiplier] -- [Διαμόρφωση |Configuration] +- [Διαδραστικά Components |components] +- [AJAX & snippets |ajax] +- [Multiplier] +- [Διαμόρφωση |configuration] Περαιτέρω ανάγνωση ****************** -- [Βέλτιστες πρακτικές |best-practices:] +- [Γιατί να χρησιμοποιήσετε το Nette; |www:10-reasons-why-nette] +- [Εγκατάσταση |nette:installation] +- [Γράφουμε την πρώτη εφαρμογή! |quickstart:] +- [Οδηγοί και διαδικασίες |best-practices:] - [Αντιμετώπιση προβλημάτων |nette:troubleshooting] diff --git a/application/el/@meta.texy b/application/el/@meta.texy new file mode 100644 index 0000000000..88e29852c7 --- /dev/null +++ b/application/el/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Τεκμηρίωση}} diff --git a/application/el/ajax.texy b/application/el/ajax.texy index 7e9e5c2d76..85fd76c042 100644 --- a/application/el/ajax.texy +++ b/application/el/ajax.texy @@ -3,36 +3,43 @@ AJAX & Snippets
    -Οι σύγχρονες διαδικτυακές εφαρμογές τρέχουν σήμερα κατά το ήμισυ σε έναν διακομιστή και κατά το ήμισυ σε ένα πρόγραμμα περιήγησης. Το AJAX είναι ένας ζωτικής σημασίας ενωτικός παράγοντας. Τι υποστήριξη προσφέρει το Nette Framework; -- αποστολή τμημάτων προτύπου (τα λεγόμενα *snippets*) +Στην εποχή των σύγχρονων διαδικτυακών εφαρμογών, όπου η λειτουργικότητα συχνά κατανέμεται μεταξύ του διακομιστή και του προγράμματος περιήγησης, το AJAX είναι ένα απαραίτητο συνδετικό στοιχείο. Ποιες επιλογές μας προσφέρει το Nette Framework σε αυτόν τον τομέα; +- αποστολή τμημάτων του template, τα λεγόμενα snippets - μεταβίβαση μεταβλητών μεταξύ PHP και JavaScript -- αποσφαλμάτωση εφαρμογών AJAX +- εργαλεία για την αποσφαλμάτωση αιτήσεων AJAX
    -Μια αίτηση AJAX μπορεί να ανιχνευθεί με τη χρήση μιας μεθόδου μιας υπηρεσίας που [ενθυλακώνει μια αίτηση HTTP |http:request] `$httpRequest->isAjax()` (ανιχνεύει με βάση την επικεφαλίδα `X-Requested-With` HTTP). Υπάρχει επίσης μια σύντομη μέθοδος στο presenter: `$this->isAjax()`. -Μια αίτηση AJAX δεν διαφέρει από μια κανονική αίτηση - καλείται ένας παρουσιαστής με μια συγκεκριμένη προβολή και παραμέτρους. Εξαρτάται, επίσης, από τον παρουσιαστή πώς θα αντιδράσει: μπορεί να χρησιμοποιήσει τις ρουτίνες του για να επιστρέψει είτε ένα τμήμα κώδικα HTML (ένα απόσπασμα), ένα έγγραφο XML, ένα αντικείμενο JSON ή ένα κομμάτι κώδικα Javascript. +Αίτηση AJAX +=========== -Υπάρχει ένα προεπεξεργασμένο αντικείμενο που ονομάζεται `payload` και είναι αφιερωμένο στην αποστολή δεδομένων στο πρόγραμμα περιήγησης σε JSON. +Μια αίτηση AJAX δεν διαφέρει ουσιαστικά από μια κλασική αίτηση HTTP. Καλείται ένας presenter με συγκεκριμένες παραμέτρους. Και εξαρτάται από τον presenter πώς θα ανταποκριθεί στην αίτηση - μπορεί να επιστρέψει δεδομένα σε μορφή JSON, να στείλει ένα τμήμα κώδικα HTML, ένα έγγραφο XML κ.λπ. -```php -public function actionDelete(int $id): void -{ - if ($this->isAjax()) { - $this->payload->message = 'Success'; - } - // ... -} +Στην πλευρά του προγράμματος περιήγησης, αρχικοποιούμε την αίτηση AJAX χρησιμοποιώντας τη συνάρτηση `fetch()`: + +```js +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) +.then(response => response.json()) +.then(payload => { + // επεξεργασία της απάντησης +}); ``` -Για πλήρη έλεγχο της εξόδου JSON χρησιμοποιήστε τη μέθοδο `sendJson` στον παρουσιαστή σας. Τερματίζει αμέσως τον presenter και θα κάνετε χωρίς πρότυπο: +Στην πλευρά του διακομιστή, αναγνωρίζουμε μια αίτηση AJAX χρησιμοποιώντας τη μέθοδο `$httpRequest->isAjax()` της υπηρεσίας [που ενσωματώνει την αίτηση HTTP |http:request]. Χρησιμοποιεί την κεφαλίδα HTTP `X-Requested-With` για την ανίχνευση, γι' αυτό είναι σημαντικό να την στέλνετε. Μέσα στον presenter, μπορείτε να χρησιμοποιήσετε τη μέθοδο `$this->isAjax()`. + +Αν θέλετε να στείλετε δεδομένα σε μορφή JSON, χρησιμοποιήστε τη μέθοδο [`sendJson()` |presenters#Αποστολή απάντησης]. Η μέθοδος τερματίζει επίσης τη δραστηριότητα του presenter. ```php -$this->sendJson(['key' => 'value', /* ... */]); +public function actionExport(): void +{ + $this->sendJson($this->model->getData); +} ``` -Εάν θέλουμε να στείλουμε HTML, μπορούμε είτε να ορίσουμε ένα ειδικό πρότυπο για αιτήσεις AJAX: +Αν σκοπεύετε να απαντήσετε με ένα ειδικό template σχεδιασμένο για AJAX, μπορείτε να το κάνετε ως εξής: ```php public function handleClick($param): void @@ -45,74 +52,80 @@ public function handleClick($param): void ``` -Naja .[#toc-naja] -================= +Snippets +======== + +Το πιο ισχυρό εργαλείο που προσφέρει το Nette για τη σύνδεση του διακομιστή με τον client είναι τα snippets. Χάρη σε αυτά, μπορείτε να μετατρέψετε μια συνηθισμένη εφαρμογή σε μια εφαρμογή AJAX με ελάχιστη προσπάθεια και λίγες γραμμές κώδικα. Το παράδειγμα Fifteen, του οποίου ο κώδικας βρίσκεται στο [GitHub |https://github.com/nette-examples/fifteen], δείχνει πώς λειτουργεί όλο αυτό. + +Τα snippets, ή αποσπάσματα, επιτρέπουν την ενημέρωση μόνο τμημάτων της σελίδας, αντί για την επαναφόρτωση ολόκληρης της σελίδας. Αυτό δεν είναι μόνο ταχύτερο και πιο αποτελεσματικό, αλλά παρέχει επίσης μια πιο άνετη εμπειρία χρήστη. Τα snippets μπορεί να σας θυμίζουν το Hotwire για Ruby on Rails ή το Symfony UX Turbo. Είναι ενδιαφέρον ότι το Nette εισήγαγε τα snippets 14 χρόνια νωρίτερα. + +Πώς λειτουργούν τα snippets; Κατά την πρώτη φόρτωση της σελίδας (αίτηση μη-AJAX), φορτώνεται ολόκληρη η σελίδα, συμπεριλαμβανομένων όλων των snippets. Όταν ο χρήστης αλληλεπιδρά με τη σελίδα (π.χ. κάνει κλικ σε ένα κουμπί, υποβάλλει μια φόρμα κ.λπ.), αντί να φορτωθεί ολόκληρη η σελίδα, γίνεται μια αίτηση AJAX. Ο κώδικας στον presenter εκτελεί την ενέργεια και αποφασίζει ποια snippets πρέπει να ενημερωθούν. Το Nette αποδίδει αυτά τα snippets και τα στέλνει με τη μορφή ενός πίνακα σε μορφή JSON. Ο κώδικας χειρισμού στο πρόγραμμα περιήγησης εισάγει τα ληφθέντα snippets πίσω στη σελίδα. Έτσι, μεταδίδεται μόνο ο κώδικας των αλλαγμένων snippets, εξοικονομώντας εύρος ζώνης και επιταχύνοντας τη φόρτωση σε σύγκριση με τη μετάδοση του περιεχομένου ολόκληρης της σελίδας. + + +Naja +---- -Η [βιβλιοθήκη Naja |https://naja.js.org] χρησιμοποιείται για το χειρισμό αιτημάτων AJAX στην πλευρά του προγράμματος περιήγησης. [Εγκαταστήστε |https://naja.js.org/#/guide/01-install-setup-naja] την ως πακέτο node.js (για χρήση με Webpack, Rollup, Vite, Parcel και άλλα): +Για τον χειρισμό των snippets στην πλευρά του προγράμματος περιήγησης, χρησιμοποιείται η [βιβλιοθήκη Naja |https://naja.js.org]. [Εγκαταστήστε την |https://naja.js.org/#/guide/01-install-setup-naja] ως πακέτο node.js (για χρήση με εφαρμογές Webpack, Rollup, Vite, Parcel και άλλες): ```shell npm install naja ``` -...ή να την εισαγάγετε απευθείας στο πρότυπο της σελίδας: +…ή εισάγετέ την απευθείας στο template της σελίδας: ```html ``` +Πρώτα, πρέπει να [αρχικοποιήσετε |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] τη βιβλιοθήκη: -Snippets .[#toc-snippets] -========================= +```js +naja.initialize(); +``` -Υπάρχει ένα πολύ πιο ισχυρό εργαλείο ενσωματωμένης υποστήριξης AJAX - τα αποσπάσματα. Η χρήση τους καθιστά δυνατή τη μετατροπή μιας κανονικής εφαρμογής σε εφαρμογή AJAX χρησιμοποιώντας μόνο μερικές γραμμές κώδικα. Το πώς λειτουργούν όλα αυτά παρουσιάζεται στο παράδειγμα Fifteen του οποίου ο κώδικας είναι επίσης προσβάσιμος στο build ή στο [GitHub |https://github.com/nette-examples/fifteen]. +Για να μετατρέψετε έναν συνηθισμένο σύνδεσμο (signal) ή την υποβολή μιας φόρμας σε αίτηση AJAX, απλά επισημάνετε τον σχετικό σύνδεσμο, φόρμα ή κουμπί με την κλάση `ajax`: -Ο τρόπος που λειτουργούν τα snippets είναι ότι ολόκληρη η σελίδα μεταφέρεται κατά το αρχικό (δηλαδή μη-AJAX) αίτημα και στη συνέχεια με κάθε AJAX [υποερώτημα |components#signal] (αίτημα της ίδιας προβολής του ίδιου παρουσιαστή) μεταφέρεται μόνο ο κώδικας των αλλαγμένων τμημάτων στο αποθετήριο `payload` που αναφέρθηκε προηγουμένως. +```html +Go -Τα Snippets μπορεί να σας θυμίζουν το Hotwire για το Ruby on Rails ή το Symfony UX Turbo, αλλά η Nette τα επινόησε δεκατέσσερα χρόνια νωρίτερα. +
    + +
    +ή -Ακύρωση των Snippets .[#toc-invalidation-of-snippets] -===================================================== +
    + +
    +``` -Κάθε απόγονος της κλάσης [Control |components] (που είναι και ένας Παρουσιαστής) είναι σε θέση να θυμάται αν υπήρξαν αλλαγές κατά τη διάρκεια μιας αίτησης που απαιτούν την εκ νέου εμφάνιση. Υπάρχει ένα ζευγάρι μεθόδων για το χειρισμό αυτό: `redrawControl()` και `isControlInvalid()`. Ένα παράδειγμα: + +Επανασχεδίαση Snippets +---------------------- + +Κάθε αντικείμενο της κλάσης [Control |components] (συμπεριλαμβανομένου του ίδιου του Presenter) παρακολουθεί εάν έχουν γίνει αλλαγές που απαιτούν την επανασχεδίασή του. Η μέθοδος `redrawControl()` χρησιμοποιείται για αυτό: ```php public function handleLogin(string $user): void { - // Το αντικείμενο πρέπει να αναδημιουργηθεί εκ νέου μετά τη σύνδεση του χρήστη. + // μετά τη σύνδεση, το σχετικό τμήμα πρέπει να επανασχεδιαστεί $this->redrawControl(); // ... } ``` -Η Nette ωστόσο προσφέρει μια ακόμη πιο λεπτή ανάλυση από ολόκληρα στοιχεία. Οι αναφερόμενες μέθοδοι δέχονται το όνομα ενός λεγόμενου "αποσπάσματος" ως προαιρετική παράμετρο. Ένα "απόσπασμα" είναι ουσιαστικά ένα στοιχείο στο πρότυπό σας που επισημαίνεται για το σκοπό αυτό με μια ετικέτα Latte, περισσότερα γι' αυτό αργότερα. Έτσι είναι δυνατόν να ζητήσετε από ένα στοιχείο να ξανασχεδιάσει μόνο *μέρη* του προτύπου του. Εάν ακυρωθεί ολόκληρο το συστατικό, τότε όλα τα αποσπάσματά του αναδημιουργούνται εκ νέου. Ένα συστατικό είναι "άκυρο" επίσης εάν οποιοδήποτε από τα υποσυστήματά του είναι άκυρο. - -```php -$this->isControlInvalid(); // -> false -$this->redrawControl('header'); // ακυρώνει το απόσπασμα με το όνομα 'header' -$this->isControlInvalid('header'); // -> true -$this->isControlInvalid('footer'); // -> false -$this->isControlInvalid(); // -> true, τουλάχιστον ένα απόσπασμα είναι άκυρο +Το Nette επιτρέπει ακόμη πιο λεπτομερή έλεγχο του τι πρέπει να επανασχεδιαστεί. Η αναφερόμενη μέθοδος μπορεί να δεχτεί το όνομα του snippet ως όρισμα. Έτσι, μπορείτε να ακυρώσετε (δηλαδή: να επιβάλετε την επανασχεδίαση) σε επίπεδο τμημάτων του template. Εάν ακυρωθεί ολόκληρο το component, κάθε snippet του θα επανασχεδιαστεί επίσης: -$this->redrawControl(); // ακυρώνει ολόκληρο το συστατικό, κάθε απόσπασμα -$this->isControlInvalid('footer'); // -> true +```php +// ακυρώνει το snippet 'header' +$this->redrawControl('header'); ``` -Ένα συστατικό που λαμβάνει σήμα επισημαίνεται αυτόματα για επανασχεδίαση. - -Χάρη στην επανασχεδίαση αποσπασμάτων γνωρίζουμε επακριβώς ποια τμήματα ποιων στοιχείων πρέπει να επανασχεδιαστούν. - -Ετικέτα `{snippet} … {/snippet}` .{toc: Tag snippet} -==================================================== +Snippets στο Latte +------------------ -Η απόδοση της σελίδας εξελίσσεται πολύ παρόμοια με μια κανονική αίτηση: φορτώνονται τα ίδια πρότυπα κ.λπ. Το ζωτικής σημασίας μέρος είναι, ωστόσο, να παραλείπονται τα μέρη που δεν πρέπει να φτάσουν στην έξοδο- τα υπόλοιπα μέρη πρέπει να συσχετίζονται με ένα αναγνωριστικό και να αποστέλλονται στο χρήστη σε κατανοητή μορφή για έναν χειριστή JavaScript. - - -Σύνταξη .[#toc-syntax] ----------------------- - -Εάν υπάρχει ένα στοιχείο ελέγχου ή ένα απόσπασμα στο πρότυπο, πρέπει να το τυλίξουμε χρησιμοποιώντας την ετικέτα `{snippet} ... {/snippet}` pair - θα διασφαλίσει ότι το αποδιδόμενο απόσπασμα θα "αποκοπεί" και θα σταλεί στο πρόγραμμα περιήγησης. Θα το περικλείσει επίσης σε ένα βοηθητικό `
    ` tag (είναι δυνατόν να χρησιμοποιηθεί ένα διαφορετικό). Στο ακόλουθο παράδειγμα ορίζεται ένα απόσπασμα με το όνομα `header`. Μπορεί κάλλιστα να αντιπροσωπεύει το πρότυπο ενός στοιχείου: +Η χρήση snippets στο Latte είναι εξαιρετικά εύκολη. Για να ορίσετε ένα τμήμα του template ως snippet, απλά περικλείστε το με τις ετικέτες `{snippet}` και `{/snippet}`: ```latte {snippet header} @@ -120,7 +133,9 @@ $this->isControlInvalid('footer'); // -> true {/snippet} ``` -Ένα απόσπασμα άλλου τύπου από το `
    ` ή ένα απόσπασμα με πρόσθετα χαρακτηριστικά HTML επιτυγχάνεται με τη χρήση της παραλλαγής χαρακτηριστικών: +Το snippet δημιουργεί ένα στοιχείο `
    ` στη σελίδα HTML με ένα ειδικό, παραγόμενο `id`. Κατά την επανασχεδίαση του snippet, το περιεχόμενο αυτού του στοιχείου ενημερώνεται. Επομένως, είναι απαραίτητο κατά την αρχική απόδοση της σελίδας να αποδοθούν επίσης όλα τα snippets, ακόμα κι αν μπορεί να είναι αρχικά κενά. + +Μπορείτε επίσης να δημιουργήσετε ένα snippet με ένα στοιχείο διαφορετικό από το `
    ` χρησιμοποιώντας ένα n:attribute: ```latte
    @@ -129,138 +144,106 @@ $this->isControlInvalid('footer'); // -> true ``` -Δυναμικά αποσπάσματα .[#toc-dynamic-snippets] -============================================= +Περιοχές Snippet +---------------- -Στο Nette μπορείτε επίσης να ορίσετε αποσπάσματα με δυναμικό όνομα βάσει μιας παραμέτρου εκτέλεσης. Αυτό είναι πιο κατάλληλο για διάφορες λίστες όπου πρέπει να αλλάξουμε μόνο μια γραμμή αλλά δεν θέλουμε να μεταφέρουμε ολόκληρη τη λίστα μαζί με αυτήν. Ένα τέτοιο παράδειγμα θα ήταν το εξής: +Τα ονόματα των snippets μπορούν επίσης να είναι εκφράσεις: ```latte -
      - {foreach $list as $id => $item} -
    • {$item} update
    • - {/foreach} -
    +{foreach $items as $id => $item} +
  • {$item}
  • +{/foreach} ``` -Υπάρχει ένα στατικό απόσπασμα που ονομάζεται `itemsContainer`, το οποίο περιέχει διάφορα δυναμικά αποσπάσματα: `item-0`, `item-1` κ.ο.κ. +Αυτό δημιουργεί πολλά snippets `item-0`, `item-1`, κ.λπ. Αν ακυρώναμε απευθείας ένα δυναμικό snippet (για παράδειγμα `item-1`), τίποτα δεν θα επανασχεδιαζόταν. Ο λόγος είναι ότι τα snippets λειτουργούν πραγματικά ως αποσπάσματα και αποδίδονται μόνο αυτά τα ίδια. Ωστόσο, στο template, δεν υπάρχει στην πραγματικότητα κανένα snippet με το όνομα `item-1`. Αυτό δημιουργείται μόνο κατά την εκτέλεση του κώδικα γύρω από το snippet, δηλαδή του βρόχου foreach. Επομένως, επισημαίνουμε το τμήμα του template που πρέπει να εκτελεστεί χρησιμοποιώντας την ετικέτα `{snippetArea}`: -Δεν μπορείτε να ξανασχεδιάσετε άμεσα ένα δυναμικό απόσπασμα (η επανασχεδίαση του `item-1` δεν έχει κανένα αποτέλεσμα), πρέπει να ξανασχεδιάσετε το γονικό του απόσπασμα (σε αυτό το παράδειγμα `itemsContainer`). Αυτό προκαλεί την εκτέλεση του κώδικα του γονικού αποσπάσματος, αλλά στη συνέχεια αποστέλλονται στο πρόγραμμα περιήγησης μόνο τα επιμέρους αποσπάσματά του. Αν θέλετε να στείλετε μόνο ένα από τα υπο-στοιχεία, πρέπει να τροποποιήσετε την είσοδο για το γονικό απόσπασμα ώστε να μην παράγει τα άλλα υπο-στοιχεία. +```latte +
      + {foreach $items as $id => $item} +
    • {$item}
    • + {/foreach} +
    +``` -Στο παραπάνω παράδειγμα πρέπει να βεβαιωθείτε ότι για μια αίτηση AJAX θα προστεθεί μόνο ένα στοιχείο στον πίνακα `$list`, επομένως ο βρόχος `foreach` θα εκτυπώσει μόνο ένα δυναμικό απόσπασμα. +Και ζητάμε την επανασχεδίαση τόσο του ίδιου του snippet όσο και ολόκληρης της γονικής περιοχής: ```php -class HomePresenter extends Nette\Application\UI\Presenter -{ - /** - * This method returns data for the list. - * Usually this would just request the data from a model. - * For the purpose of this example, the data is hard-coded. - */ - private function getTheWholeList(): array - { - return [ - 'First', - 'Second', - 'Third', - ]; - } - - public function renderDefault(): void - { - if (!isset($this->template->list)) { - $this->template->list = $this->getTheWholeList(); - } - } - - public function handleUpdate(int $id): void - { - $this->template->list = $this->isAjax() - ? [] - : $this->getTheWholeList(); - $this->template->list[$id] = 'Updated item'; - $this->redrawControl('itemsContainer'); - } -} +$this->redrawControl('itemsContainer'); +$this->redrawControl('item-1'); ``` +Ταυτόχρονα, είναι καλό να διασφαλίσουμε ότι ο πίνακας `$items` περιέχει μόνο τα στοιχεία που πρέπει να επανασχεδιαστούν. -Αποσπάσματα σε συμπεριλαμβανόμενο πρότυπο .[#toc-snippets-in-an-included-template] -================================================================================== - -Μπορεί να συμβεί το απόσπασμα να βρίσκεται σε ένα πρότυπο το οποίο συμπεριλαμβάνεται από ένα διαφορετικό πρότυπο. Σε αυτή την περίπτωση πρέπει να τυλίξουμε τον κώδικα συμπερίληψης στο δεύτερο πρότυπο με την ετικέτα `snippetArea`, και στη συνέχεια να ξανασχεδιάσουμε τόσο το snippetArea όσο και το πραγματικό απόσπασμα. - -Η ετικέτα `snippetArea` διασφαλίζει ότι ο κώδικας στο εσωτερικό της εκτελείται, αλλά μόνο το πραγματικό απόσπασμα στο συμπεριλαμβανόμενο πρότυπο αποστέλλεται στο πρόγραμμα περιήγησης. +Αν εισάγουμε ένα άλλο template που περιέχει snippets στο template χρησιμοποιώντας την ετικέτα `{include}`, είναι απαραίτητο να συμπεριλάβουμε ξανά την εισαγωγή του template σε ένα `snippetArea` και να το ακυρώσουμε μαζί με το snippet: ```latte -{* parent.latte *} -{snippetArea wrapper} - {include 'child.latte'} +{snippetArea include} + {include 'included.latte'} {/snippetArea} ``` + ```latte -{* child.latte *} +{* included.latte *} {snippet item} -... + ... {/snippet} ``` + ```php -$this->redrawControl('wrapper'); +$this->redrawControl('include'); $this->redrawControl('item'); ``` -Μπορείτε επίσης να το συνδυάσετε με δυναμικά αποσπάσματα. - - -Προσθήκη και διαγραφή .[#toc-adding-and-deleting] -================================================= -Εάν προσθέσετε ένα νέο στοιχείο στη λίστα και ακυρώσετε το `itemsContainer`, η αίτηση AJAX επιστρέφει αποσπάσματα που περιλαμβάνουν το νέο στοιχείο, αλλά ο χειριστής javascript δεν θα είναι σε θέση να το αποδώσει. Αυτό συμβαίνει επειδή δεν υπάρχει κανένα στοιχείο HTML με το νεοδημιουργηθέν ID. +Snippets σε Components +---------------------- -Σε αυτή την περίπτωση, ο απλούστερος τρόπος είναι να τυλίξετε ολόκληρη τη λίστα σε ένα ακόμη απόσπασμα και να τα ακυρώσετε όλα: +Μπορείτε επίσης να δημιουργήσετε snippets σε [components|components] και το Nette θα τα επανασχεδιάζει αυτόματα. Ωστόσο, υπάρχει ένας περιορισμός: για την επανασχεδίαση των snippets, καλεί τη μέθοδο `render()` χωρίς παραμέτρους. Επομένως, η μεταβίβαση παραμέτρων στο template δεν θα λειτουργήσει: ```latte -{snippet wholeList} -
      - {foreach $list as $id => $item} -
    • {$item} update
    • - {/foreach} -
    -{/snippet} -Add +OK +{control productGrid} + +δεν θα λειτουργήσει: +{control productGrid $arg, $arg} +{control productGrid:paginator} ``` + +Αποστολή Δεδομένων Χρήστη +------------------------- + +Μαζί με τα snippets, μπορείτε να στείλετε οποιαδήποτε άλλα δεδομένα στον client. Απλά γράψτε τα στο αντικείμενο `payload`: + ```php -public function handleAdd(): void +public function actionDelete(int $id): void { - $this->template->list = $this->getTheWholeList(); - $this->template->list[] = 'New one'; - $this->redrawControl('wholeList'); + // ... + if ($this->isAjax()) { + $this->payload->message = 'Success'; + } } ``` -Το ίδιο ισχύει και για τη διαγραφή ενός στοιχείου. Θα ήταν δυνατό να στείλετε κενό snippet, αλλά συνήθως οι λίστες μπορούν να είναι σελιδοποιημένες και θα ήταν περίπλοκο να υλοποιήσετε τη διαγραφή ενός στοιχείου και τη φόρτωση ενός άλλου (το οποίο βρισκόταν σε διαφορετική σελίδα της σελιδοποιημένης λίστας). - -Αποστολή παραμέτρων στο συστατικό .[#toc-sending-parameters-to-component] -========================================================================= +Μεταβίβαση Παραμέτρων +===================== -Όταν στέλνουμε παραμέτρους στο στοιχείο μέσω αίτησης AJAX, είτε πρόκειται για παραμέτρους σήματος είτε για μόνιμες παραμέτρους, πρέπει να παρέχουμε το συνολικό τους όνομα, το οποίο περιέχει επίσης το όνομα του στοιχείου. Το πλήρες όνομα της παραμέτρου επιστρέφει η μέθοδος `getParameterId()`. +Αν στέλνουμε παραμέτρους σε ένα component μέσω μιας αίτησης AJAX, είτε πρόκειται για παραμέτρους signal είτε για persistent παραμέτρους, πρέπει να καθορίσουμε το καθολικό τους όνομα στην αίτηση, το οποίο περιλαμβάνει και το όνομα του component. Η μέθοδος `getParameterId()` επιστρέφει το πλήρες όνομα της παραμέτρου. ```js -$.getJSON( - {link changeCountBasket!}, - { - {$control->getParameterId('id')}: id, - {$control->getParameterId('count')}: count - } -}); +let url = new URL({link //foo!}); +url.searchParams.set({$control->getParameterId('bar')}, bar); + +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) ``` -Και χειρίζεται τη μέθοδο με s αντίστοιχες παραμέτρους στο συστατικό. +Και η μέθοδος handle με τις αντίστοιχες παραμέτρους στο component: ```php -public function handleChangeCountBasket(int $id, int $count): void +public function handleFoo(int $bar): void { - } ``` diff --git a/application/el/bootstrap.texy b/application/el/bootstrap.texy deleted file mode 100644 index 830a951dd6..0000000000 --- a/application/el/bootstrap.texy +++ /dev/null @@ -1,233 +0,0 @@ -Bootstrap -********* - -
    - -Το Bootstrap είναι ο κώδικας εκκίνησης που αρχικοποιεί το περιβάλλον, δημιουργεί ένα δοχείο έγχυσης εξαρτήσεων (DI) και εκκινεί την εφαρμογή. Θα συζητήσουμε: - -- πώς να ρυθμίσετε την εφαρμογή σας χρησιμοποιώντας αρχεία NEON -- πώς να χειρίζεστε τις λειτουργίες παραγωγής και ανάπτυξης -- πώς να δημιουργήσετε το δοχείο DI - -
    - - -Οι εφαρμογές, είτε βασίζονται στο διαδίκτυο είτε σε σενάρια γραμμής εντολών, ξεκινούν με κάποια μορφή αρχικοποίησης του περιβάλλοντος. Στην αρχαιότητα, θα μπορούσε να είναι ένα αρχείο με το όνομα eg `include.inc.php` που ήταν υπεύθυνο γι' αυτό και συμπεριλαμβανόταν στο αρχικό αρχείο. -Στις σύγχρονες εφαρμογές Nette, έχει αντικατασταθεί από την κλάση `Bootstrap`, η οποία ως μέρος της εφαρμογής μπορεί να βρεθεί στο `app/Bootstrap.php`. Μπορεί να μοιάζει για παράδειγμα ως εξής: - -```php -use Nette\Bootstrap\Configurator; - -class Bootstrap -{ - public static function boot(): Configurator - { - $appDir = dirname(__DIR__); - $configurator = new Configurator; - //$configurator->setDebugMode('secret@23.75.345.200'); - $configurator->enableTracy($appDir . '/log'); - $configurator->setTempDirectory($appDir . '/temp'); - $configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); - $configurator->addConfig($appDir . '/config/common.neon'); - return $configurator; - } -} -``` - - -index.php .[#toc-index-php] -=========================== - -Στην περίπτωση των διαδικτυακών εφαρμογών, το αρχικό αρχείο είναι το `index.php`, το οποίο βρίσκεται στον δημόσιο κατάλογο `www/`. Επιτρέπει στην κλάση `Bootstrap` να αρχικοποιήσει το περιβάλλον και να επιστρέψει το `$configurator` που δημιουργεί το DI container. Στη συνέχεια αποκτά την υπηρεσία `Application`, η οποία εκτελεί την εφαρμογή ιστού: - -```php -// αρχικοποίηση του περιβάλλοντος + λήψη του αντικειμένου Configurator -$configurator = App\Bootstrap::boot(); -// Δημιουργία ενός δοχείου DI -$container = $configurator->createContainer(); -// Το δοχείο DI δημιουργεί ένα αντικείμενο Nette\Application\Application -$application = $container->getByType(Nette\Application\Application::class); -// έναρξη της εφαρμογής Nette -$application->run(); -``` - -Όπως βλέπετε, η κλάση [api:Nette\Bootstrap\Configurator], την οποία θα παρουσιάσουμε τώρα με περισσότερες λεπτομέρειες, βοηθάει στη ρύθμιση του περιβάλλοντος και στη δημιουργία ενός δοχείου έγχυσης εξαρτήσεων (DI). - - -Λειτουργία ανάπτυξης έναντι λειτουργίας παραγωγής .[#toc-development-vs-production-mode] -======================================================================================== - -Η Nette διακρίνει μεταξύ δύο βασικών τρόπων εκτέλεσης μιας αίτησης: ανάπτυξη και παραγωγή. Η λειτουργία ανάπτυξης επικεντρώνεται στη μέγιστη άνεση του προγραμματιστή, εμφανίζεται το Tracy, η προσωρινή μνήμη ενημερώνεται αυτόματα όταν αλλάζουν τα πρότυπα ή η διαμόρφωση του DI container, κ.λπ. Η λειτουργία παραγωγής επικεντρώνεται στην απόδοση, το Tracy καταγράφει μόνο τα σφάλματα και δεν ελέγχονται οι αλλαγές των προτύπων και άλλων αρχείων. - -Η επιλογή της λειτουργίας γίνεται με αυτόματη ανίχνευση, οπότε συνήθως δεν χρειάζεται να ρυθμίσετε ή να αλλάξετε κάτι χειροκίνητα. Η κατάσταση λειτουργίας είναι development εάν η εφαρμογή εκτελείται στο localhost (δηλαδή στη διεύθυνση IP `127.0.0.1` ή `::1`) και δεν υπάρχει proxy (δηλαδή η επικεφαλίδα HTTP του). Διαφορετικά, εκτελείται σε κατάσταση παραγωγής. - -Αν θέλετε να ενεργοποιήσετε τη λειτουργία ανάπτυξης σε άλλες περιπτώσεις, για παράδειγμα, για προγραμματιστές που έχουν πρόσβαση από μια συγκεκριμένη διεύθυνση IP, μπορείτε να χρησιμοποιήσετε τη διεύθυνση `setDebugMode()`: - -```php -$configurator->setDebugMode('23.75.345.200'); // μία ή περισσότερες διευθύνσεις IP -``` - -Συνιστούμε οπωσδήποτε τον συνδυασμό μιας διεύθυνσης IP με ένα cookie. Θα αποθηκεύσουμε ένα μυστικό token στο cookie `nette-debug`, π.χ. `secret1234`, και η λειτουργία ανάπτυξης θα ενεργοποιηθεί για τους προγραμματιστές με αυτόν τον συνδυασμό IP και cookie. - -```php -$configurator->setDebugMode('secret1234@23.75.345.200'); -``` - -Μπορούμε επίσης να απενεργοποιήσουμε εντελώς τη λειτουργία προγραμματιστή, ακόμη και για το localhost: - -```php -$configurator->setDebugMode(false); -``` - -Σημειώστε ότι η τιμή `true` ενεργοποιεί τη λειτουργία προγραμματιστή με σκληρό τρόπο, κάτι που δεν πρέπει ποτέ να συμβαίνει σε έναν διακομιστή παραγωγής. - - -Εργαλείο εντοπισμού σφαλμάτων Tracy .[#toc-debugging-tool-tracy] -================================================================ - -Για εύκολη αποσφαλμάτωση, θα ενεργοποιήσουμε το σπουδαίο εργαλείο [Tracy |tracy:]. Στη λειτουργία προγραμματιστή απεικονίζει τα σφάλματα και στη λειτουργία παραγωγής καταγράφει τα σφάλματα στον καθορισμένο κατάλογο: - -```php -$configurator->enableTracy($appDir . '/log'); -``` - - -Προσωρινά αρχεία .[#toc-temporary-files] -======================================== - -Η Nette χρησιμοποιεί την κρυφή μνήμη για το DI container, το RobotLoader, τα πρότυπα κ.λπ. Ως εκ τούτου, είναι απαραίτητο να ορίσετε τη διαδρομή προς τον κατάλογο όπου θα αποθηκεύεται η προσωρινή μνήμη: - -```php -$configurator->setTempDirectory($appDir . '/temp'); -``` - -Σε Linux ή macOS, ορίστε τα [δικαιώματα εγγραφής |nette:troubleshooting#Setting directory permissions] για τους καταλόγους `log/` και `temp/`. - - -RobotLoader .[#toc-robotloader] -=============================== - -Συνήθως, θα θέλουμε να φορτώνουμε αυτόματα τις κλάσεις χρησιμοποιώντας [τον RobotLoader |robot-loader:], οπότε πρέπει να τον εκκινήσουμε και να τον αφήσουμε να φορτώσει κλάσεις από τον κατάλογο όπου βρίσκεται το `Bootstrap.php` (δηλαδή το `__DIR__`) και όλους τους υποκαταλόγους του: - -```php -$configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); -``` - -Ένας εναλλακτικός τρόπος είναι να χρησιμοποιήσετε μόνο την αυτόματη φόρτωση του [Composer |best-practices:composer] PSR-4. - - -Χρονοζώνη .[#toc-timezone] -========================== - -Το Configurator σας επιτρέπει να καθορίσετε μια ζώνη ώρας για την εφαρμογή σας. - -```php -$configurator->setTimeZone('Europe/Prague'); -``` - - -Διαμόρφωση εμπορευματοκιβωτίων DI .[#toc-di-container-configuration] -==================================================================== - -Μέρος της διαδικασίας εκκίνησης είναι η δημιουργία ενός DI container, δηλαδή ενός εργοστασίου για αντικείμενα, το οποίο αποτελεί την καρδιά ολόκληρης της εφαρμογής. Πρόκειται στην πραγματικότητα για μια κλάση PHP που παράγεται από τη Nette και αποθηκεύεται σε έναν κατάλογο cache. Το εργοστάσιο παράγει βασικά αντικείμενα της εφαρμογής και τα αρχεία διαμόρφωσης του δίνουν οδηγίες για το πώς να τα δημιουργεί και να τα διαμορφώνει, και έτσι επηρεάζουμε τη συμπεριφορά ολόκληρης της εφαρμογής. - -Τα αρχεία διαμόρφωσης γράφονται συνήθως σε [μορφή NEON |neon:format]. Μπορείτε να διαβάσετε [τι μπορεί να ρυθμιστεί εδώ |nette:configuring]. - -.[tip] -Στη λειτουργία ανάπτυξης, ο περιέκτης ενημερώνεται αυτόματα κάθε φορά που αλλάζετε τον κώδικα ή τα αρχεία διαμόρφωσης. Στη λειτουργία παραγωγής, δημιουργείται μόνο μία φορά και οι αλλαγές αρχείων δεν ελέγχονται για να μεγιστοποιηθεί η απόδοση. - -Τα αρχεία διαμόρφωσης φορτώνονται με τη χρήση του `addConfig()`: - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -``` - -Η μέθοδος `addConfig()` μπορεί να κληθεί πολλές φορές για την προσθήκη πολλών αρχείων. - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -$configurator->addConfig($appDir . '/config/local.neon'); -if (PHP_SAPI === 'cli') { - $configurator->addConfig($appDir . '/config/cli.php'); -} -``` - -Το όνομα `cli.php` δεν είναι τυπογραφικό λάθος, η διαμόρφωση μπορεί επίσης να γραφτεί σε ένα αρχείο PHP, το οποίο την επιστρέφει ως πίνακα. - -Εναλλακτικά, μπορούμε να χρησιμοποιήσουμε το [τμήμα`includes` |dependency-injection:configuration#including files] για να φορτώσουμε περισσότερα αρχεία ρυθμίσεων. - -Εάν στοιχεία με τα ίδια κλειδιά εμφανίζονται μέσα σε αρχεία διαμόρφωσης, θα [αντικατασταθούν ή θα συγχωνευθούν |dependency-injection:configuration#Merging] στην περίπτωση των πινάκων. Το αρχείο που περιλαμβάνεται αργότερα έχει υψηλότερη προτεραιότητα από το προηγούμενο. Το αρχείο στο οποίο παρατίθεται το τμήμα `includes` έχει υψηλότερη προτεραιότητα από τα αρχεία που περιλαμβάνονται σε αυτό. - - -Στατικές παράμετροι .[#toc-static-parameters] ---------------------------------------------- - -Οι παράμετροι που χρησιμοποιούνται σε αρχεία ρυθμίσεων μπορούν να οριστούν [στην ενότητα `parameters` |dependency-injection:configuration#parameters] και επίσης να μεταβιβαστούν (ή να αντικατασταθούν) από τη μέθοδο `addStaticParameters()` (έχει το ψευδώνυμο `addParameters()`). Είναι σημαντικό ότι διαφορετικές τιμές παραμέτρων προκαλούν τη δημιουργία πρόσθετων δοχείων DI, δηλαδή πρόσθετων κλάσεων. - -```php -$configurator->addStaticParameters([ - 'projectId' => 23, -]); -``` - -Στα αρχεία ρυθμίσεων, μπορούμε να γράψουμε τον συνήθη συμβολισμό `%projectId%` για να αποκτήσουμε πρόσβαση στην παράμετρο με το όνομα `projectId`. Από προεπιλογή, ο διαμορφωτής συμπληρώνει τις ακόλουθες παραμέτρους: `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` και `consoleMode`. - - -Δυναμικές παράμετροι .[#toc-dynamic-parameters] ------------------------------------------------ - -Μπορούμε επίσης να προσθέσουμε δυναμικές παραμέτρους στο δοχείο, οι διαφορετικές τιμές τους, σε αντίθεση με τις στατικές παραμέτρους, δεν θα προκαλέσουν τη δημιουργία νέων δοχείων DI. - -```php -$configurator->addDynamicParameters([ - 'remoteIp' => $_SERVER['REMOTE_ADDR'], -]); -``` - -Οι μεταβλητές περιβάλλοντος θα μπορούσαν εύκολα να γίνουν διαθέσιμες με τη χρήση δυναμικών παραμέτρων. Μπορούμε να έχουμε πρόσβαση σε αυτές μέσω της διεύθυνσης `%env.variable%` στα αρχεία ρυθμίσεων. - -```php -$configurator->addDynamicParameters([ - 'env' => getenv(), -]); -``` - - -Εισαγόμενες υπηρεσίες .[#toc-imported-services] ------------------------------------------------ - -Πάμε πιο βαθιά τώρα. Παρόλο που ο σκοπός ενός DI container είναι η δημιουργία αντικειμένων, κατ' εξαίρεση μπορεί να υπάρξει ανάγκη εισαγωγής ενός υπάρχοντος αντικειμένου στο container. Αυτό το κάνουμε ορίζοντας την υπηρεσία με το χαρακτηριστικό `imported: true`. - -```neon -services: - myservice: - type: App\Model\MyCustomService - imported: true -``` - -Δημιουργούμε μια νέα περίπτωση και την εισάγουμε στο bootstrap: - -```php -$configurator->addServices([ - 'myservice' => new App\Model\MyCustomService('foobar'), -]); -``` - - -Διαφορετικά περιβάλλοντα .[#toc-different-environments] -======================================================= - -Μπορείτε να προσαρμόσετε την τάξη `Bootstrap` ανάλογα με τις ανάγκες σας. Μπορείτε να προσθέσετε παραμέτρους στη μέθοδο `boot()` για να διαφοροποιήσετε τα έργα ιστού ή να προσθέσετε άλλες μεθόδους, όπως η `bootForTests()`, η οποία αρχικοποιεί το περιβάλλον για δοκιμές μονάδας, η `bootForCli()` για σενάρια που καλούνται από τη γραμμή εντολών κ.ο.κ. - -```php -public static function bootForTests(): Configurator -{ - $configurator = self::boot(); - Tester\Environment::setup(); // Αρχικοποίηση Nette Tester - return $configurator; -} -``` diff --git a/application/el/bootstrapping.texy b/application/el/bootstrapping.texy new file mode 100644 index 0000000000..d14b96b9e8 --- /dev/null +++ b/application/el/bootstrapping.texy @@ -0,0 +1,297 @@ +Εκκίνηση +******** + +
    + +Η εκκίνηση είναι η διαδικασία αρχικοποίησης του περιβάλλοντος της εφαρμογής, δημιουργίας ενός κοντέινερ dependency injection (DI) και εκκίνησης της εφαρμογής. Θα συζητήσουμε: + +- πώς η κλάση Bootstrap αρχικοποιεί το περιβάλλον +- πώς οι εφαρμογές διαμορφώνονται χρησιμοποιώντας αρχεία NEON +- πώς να διακρίνουμε μεταξύ παραγωγικής και αναπτυξιακής λειτουργίας +- πώς να δημιουργήσουμε και να διαμορφώσουμε το DI κοντέινερ + +
    + + +Οι εφαρμογές, είτε πρόκειται για διαδικτυακές εφαρμογές είτε για σενάρια που εκτελούνται από τη γραμμή εντολών, ξεκινούν την εκτέλεσή τους με κάποια μορφή αρχικοποίησης περιβάλλοντος. Στο παρελθόν, αυτό γινόταν συνήθως από ένα αρχείο με όνομα όπως `include.inc.php`, το οποίο το αρχικό αρχείο συμπεριλάμβανε. Στις σύγχρονες εφαρμογές Nette, αυτό έχει αντικατασταθεί από την κλάση `Bootstrap`, την οποία, ως μέρος της εφαρμογής, θα βρείτε στο αρχείο `app/Bootstrap.php`. Μπορεί να μοιάζει κάπως έτσι: + +```php +use Nette\Bootstrap\Configurator; + +class Bootstrap +{ + private Configurator $configurator; + private string $rootDir; + + public function __construct() + { + $this->rootDir = dirname(__DIR__); + // Ο Configurator είναι υπεύθυνος για τη ρύθμιση του περιβάλλοντος της εφαρμογής και των υπηρεσιών. + $this->configurator = new Configurator; + // Ορίζει τον κατάλογο για προσωρινά αρχεία που δημιουργούνται από το Nette (π.χ. μεταγλωττισμένα templates) + $this->configurator->setTempDirectory($this->rootDir . '/temp'); + } + + public function bootWebApplication(): Nette\DI\Container + { + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); + } + + private function initializeEnvironment(): void + { + // Το Nette είναι έξυπνο και η λειτουργία ανάπτυξης ενεργοποιείται αυτόματα, + // ή μπορείτε να την ενεργοποιήσετε για μια συγκεκριμένη διεύθυνση IP αποσχολιάζοντας την ακόλουθη γραμμή: + // $this->configurator->setDebugMode('secret@23.75.345.200'); + + // Ενεργοποιεί το Tracy: το απόλυτο "ελβετικό μαχαίρι" για αποσφαλμάτωση. + $this->configurator->enableTracy($this->rootDir . '/log'); + + // RobotLoader: φορτώνει αυτόματα όλες τις κλάσεις στον επιλεγμένο κατάλογο + $this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); + } + + private function setupContainer(): void + { + // Φορτώνει αρχεία διαμόρφωσης + $this->configurator->addConfig($this->rootDir . '/config/common.neon'); + } +} +``` + + +index.php +========= + +Το αρχικό αρχείο στην περίπτωση των διαδικτυακών εφαρμογών είναι το `index.php`, το οποίο βρίσκεται στον [δημόσιο κατάλογο |directory-structure#Δημόσιος Κατάλογος www] `www/`. Αυτό ζητά από την κλάση Bootstrap να αρχικοποιήσει το περιβάλλον και να δημιουργήσει το DI container. Στη συνέχεια, λαμβάνει την υπηρεσία `Application` από αυτό, η οποία εκκινεί την διαδικτυακή εφαρμογή: + +```php +$bootstrap = new App\Bootstrap; +// Αρχικοποίηση περιβάλλοντος + δημιουργία DI container +$container = $bootstrap->bootWebApplication(); +// Το DI container δημιουργεί ένα αντικείμενο Nette\Application\Application +$application = $container->getByType(Nette\Application\Application::class); +// Εκκίνηση της εφαρμογής Nette και επεξεργασία της εισερχόμενης αίτησης +$application->run(); +``` + +Όπως μπορείτε να δείτε, η κλάση [api:Nette\Bootstrap\Configurator] βοηθά στη ρύθμιση του περιβάλλοντος και στη δημιουργία του dependency injection (DI) container, την οποία θα παρουσιάσουμε τώρα λεπτομερέστερα. + + +Λειτουργία Ανάπτυξης vs Παραγωγής +================================= + +Το Nette συμπεριφέρεται διαφορετικά ανάλογα με το αν εκτελείται σε διακομιστή ανάπτυξης ή παραγωγής: + +🛠️ Λειτουργία Ανάπτυξης (Development): + - Εμφανίζει τη γραμμή αποσφαλμάτωσης Tracy με χρήσιμες πληροφορίες (ερωτήματα SQL, χρόνος εκτέλεσης, χρησιμοποιούμενη μνήμη) + - Σε περίπτωση σφάλματος, εμφανίζει μια λεπτομερή σελίδα σφάλματος με κλήσεις συναρτήσεων και περιεχόμενο μεταβλητών + - Ανανεώνει αυτόματα την cache κατά την αλλαγή templates Latte, την τροποποίηση αρχείων διαμόρφωσης κ.λπ. + + +🚀 Λειτουργία Παραγωγής (Production): + - Δεν εμφανίζει καμία πληροφορία αποσφαλμάτωσης, όλα τα σφάλματα καταγράφονται στο αρχείο καταγραφής + - Σε περίπτωση σφάλματος, εμφανίζει τον ErrorPresenter ή μια γενική σελίδα "Server Error" + - Η cache δεν ανανεώνεται ποτέ αυτόματα! + - Βελτιστοποιημένο για ταχύτητα και ασφάλεια + + +Η επιλογή της λειτουργίας γίνεται με αυτόματη ανίχνευση, οπότε συνήθως δεν χρειάζεται να διαμορφώσετε ή να αλλάξετε τίποτα χειροκίνητα: + +- λειτουργία ανάπτυξης: στο localhost (διεύθυνση IP `127.0.0.1` ή `::1`) εάν δεν υπάρχει proxy (δηλαδή η κεφαλίδα HTTP του) +- λειτουργία παραγωγής: παντού αλλού + +Αν θέλουμε να ενεργοποιήσουμε τη λειτουργία ανάπτυξης και σε άλλες περιπτώσεις, για παράδειγμα για προγραμματιστές που έχουν πρόσβαση από μια συγκεκριμένη διεύθυνση IP, χρησιμοποιούμε το `setDebugMode()`: + +```php +$this->configurator->setDebugMode('23.75.345.200'); // μπορείτε επίσης να καθορίσετε έναν πίνακα διευθύνσεων IP +``` + +Συνιστούμε οπωσδήποτε να συνδυάσετε τη διεύθυνση IP με ένα cookie. Αποθηκεύουμε ένα μυστικό token, π.χ. `secret1234`, στο cookie `nette-debug` και με αυτόν τον τρόπο ενεργοποιούμε τη λειτουργία ανάπτυξης για προγραμματιστές που έχουν πρόσβαση από μια συγκεκριμένη διεύθυνση IP και ταυτόχρονα έχουν το αναφερόμενο token στο cookie: + +```php +$this->configurator->setDebugMode('secret1234@23.75.345.200'); +``` + +Μπορούμε επίσης να απενεργοποιήσουμε εντελώς τη λειτουργία ανάπτυξης, ακόμη και για το localhost: + +```php +$this->configurator->setDebugMode(false); +``` + +Προσοχή, η τιμή `true` ενεργοποιεί τη λειτουργία ανάπτυξης μόνιμα, κάτι που δεν πρέπει ποτέ να συμβεί σε διακομιστή παραγωγής. + + +Εργαλείο Αποσφαλμάτωσης Tracy +============================= + +Για εύκολη αποσφαλμάτωση, ενεργοποιούμε επίσης το εξαιρετικό εργαλείο [Tracy |tracy:]. Στη λειτουργία ανάπτυξης, οπτικοποιεί τα σφάλματα και στη λειτουργία παραγωγής, καταγράφει τα σφάλματα στον καθορισμένο κατάλογο: + +```php +$this->configurator->enableTracy($this->rootDir . '/log'); +``` + + +Προσωρινά Αρχεία +================ + +Το Nette χρησιμοποιεί cache για το DI container, το RobotLoader, τα templates κ.λπ. Επομένως, είναι απαραίτητο να ορίσετε τη διαδρομή προς τον κατάλογο όπου θα αποθηκεύεται η cache: + +```php +$this->configurator->setTempDirectory($this->rootDir . '/temp'); +``` + +Σε Linux ή macOS, ορίστε [δικαιώματα εγγραφής |nette:troubleshooting#Ρύθμιση δικαιωμάτων καταλόγου] για τους καταλόγους `log/` και `temp/`. + + +RobotLoader +=========== + +Συνήθως, θα θέλουμε να φορτώνουμε αυτόματα κλάσεις χρησιμοποιώντας το [RobotLoader |robot-loader:], οπότε πρέπει να το ξεκινήσουμε και να το αφήσουμε να φορτώνει κλάσεις από τον κατάλογο όπου βρίσκεται το `Bootstrap.php` (δηλαδή `__DIR__`), και όλους τους υποκαταλόγους: + +```php +$this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); +``` + +Μια εναλλακτική προσέγγιση είναι να αφήσετε τις κλάσεις να φορτώνονται μόνο μέσω του [Composer |best-practices:composer] τηρώντας το PSR-4. + + +Ζώνη Ώρας +========= + +Μέσω του configurator, μπορείτε να ορίσετε την προεπιλεγμένη ζώνη ώρας. + +```php +$this->configurator->setTimeZone('Europe/Prague'); +``` + + +Διαμόρφωση του DI Container +=========================== + +Μέρος της διαδικασίας εκκίνησης είναι η δημιουργία του DI container ή factory αντικειμένων, το οποίο είναι η καρδιά ολόκληρης της εφαρμογής. Πρόκειται στην πραγματικότητα για μια κλάση PHP που δημιουργείται από το Nette και αποθηκεύεται στον κατάλογο cache. Το factory παράγει τα βασικά αντικείμενα της εφαρμογής και, χρησιμοποιώντας αρχεία διαμόρφωσης, το καθοδηγούμε πώς να τα δημιουργεί και να τα ρυθμίζει, επηρεάζοντας έτσι τη συμπεριφορά ολόκληρης της εφαρμογής. + +Τα αρχεία διαμόρφωσης συνήθως γράφονται σε μορφή [NEON |neon:format]. Σε ένα ξεχωριστό κεφάλαιο, θα μάθετε [τι μπορεί να διαμορφωθεί |nette:configuring]. + +.[tip] +Στη λειτουργία ανάπτυξης, το container ενημερώνεται αυτόματα κάθε φορά που αλλάζει ο κώδικας ή τα αρχεία διαμόρφωσης. Στη λειτουργία παραγωγής, δημιουργείται μόνο μία φορά και οι αλλαγές δεν ελέγχονται για μεγιστοποίηση της απόδοσης. + +Φορτώνουμε τα αρχεία διαμόρφωσης χρησιμοποιώντας το `addConfig()`: + +```php +$this->configurator->addConfig($this->rootDir . '/config/common.neon'); +``` + +Αν θέλουμε να προσθέσουμε περισσότερα αρχεία διαμόρφωσης, μπορούμε να καλέσουμε τη συνάρτηση `addConfig()` πολλές φορές. + +```php +$configDir = $this->rootDir . '/config'; +$this->configurator->addConfig($configDir . '/common.neon'); +$this->configurator->addConfig($configDir . '/services.neon'); +if (PHP_SAPI === 'cli') { + $this->configurator->addConfig($configDir . '/cli.php'); +} +``` + +Το όνομα `cli.php` δεν είναι τυπογραφικό λάθος, η διαμόρφωση μπορεί επίσης να γραφτεί σε ένα αρχείο PHP που την επιστρέφει ως array. + +Μπορούμε επίσης να προσθέσουμε άλλα αρχεία διαμόρφωσης στην [ενότητα `includes` |dependency-injection:configuration#Εισαγωγή αρχείων]. + +Αν εμφανιστούν στοιχεία με τα ίδια κλειδιά στα αρχεία διαμόρφωσης, θα αντικατασταθούν ή, στην περίπτωση [arrays, θα συγχωνευθούν |dependency-injection:configuration#Συγχώνευση]. Το αρχείο που εισάγεται αργότερα έχει υψηλότερη προτεραιότητα από το προηγούμενο. Το αρχείο στο οποίο αναφέρεται η ενότητα `includes` έχει υψηλότερη προτεραιότητα από τα αρχεία που περιλαμβάνονται σε αυτό. + + +Στατικές Παράμετροι +------------------- + +Μπορούμε να ορίσουμε παραμέτρους που χρησιμοποιούνται στα αρχεία διαμόρφωσης στην [ενότητα `parameters` |dependency-injection:configuration#Παράμετροι] και επίσης να τις μεταβιβάσουμε (ή να τις αντικαταστήσουμε) με τη μέθοδο `addStaticParameters()` (έχει το ψευδώνυμο `addParameters()`). Είναι σημαντικό ότι διαφορετικές τιμές παραμέτρων προκαλούν τη δημιουργία πρόσθετων DI containers, δηλαδή πρόσθετων κλάσεων. + +```php +$this->configurator->addStaticParameters([ + 'projectId' => 23, +]); +``` + +Στην παράμετρο `projectId` μπορείτε να αναφερθείτε στη διαμόρφωση με τη συνηθισμένη σύνταξη `%projectId%`. + + +Δυναμικές Παράμετροι +-------------------- + +Μπορούμε επίσης να προσθέσουμε δυναμικές παραμέτρους στο container, των οποίων οι διαφορετικές τιμές, σε αντίθεση με τις στατικές παραμέτρους, δεν προκαλούν τη δημιουργία νέων DI containers. + +```php +$this->configurator->addDynamicParameters([ + 'remoteIp' => $_SERVER['REMOTE_ADDR'], +]); +``` + +Με αυτόν τον τρόπο, μπορούμε εύκολα να προσθέσουμε, για παράδειγμα, μεταβλητές περιβάλλοντος, στις οποίες μπορείτε στη συνέχεια να αναφερθείτε στη διαμόρφωση χρησιμοποιώντας τη σύνταξη `%env.variable%`. + +```php +$this->configurator->addDynamicParameters([ + 'env' => getenv(), +]); +``` + + +Προεπιλεγμένες Παράμετροι +------------------------- + +Στα αρχεία διαμόρφωσης, μπορείτε να χρησιμοποιήσετε αυτές τις στατικές παραμέτρους: + +- `%appDir%` είναι η απόλυτη διαδρομή προς τον κατάλογο με το αρχείο `Bootstrap.php` +- `%wwwDir%` είναι η απόλυτη διαδρομή προς τον κατάλογο με το αρχείο εισόδου `index.php` +- `%tempDir%` είναι η απόλυτη διαδρομή προς τον κατάλογο για προσωρινά αρχεία +- `%vendorDir%` είναι η απόλυτη διαδρομή προς τον κατάλογο όπου ο Composer εγκαθιστά βιβλιοθήκες +- `%rootDir%` είναι η απόλυτη διαδρομή προς τον ριζικό κατάλογο του έργου +- `%debugMode%` υποδεικνύει εάν η εφαρμογή βρίσκεται σε λειτουργία debugging +- `%consoleMode%` υποδεικνύει εάν η request προήλθε από τη γραμμή εντολών + + +Εισαγόμενες Υπηρεσίες +--------------------- + +Τώρα πηγαίνουμε βαθύτερα. Αν και ο σκοπός του DI container είναι να παράγει αντικείμενα, εξαιρετικά μπορεί να προκύψει η ανάγκη να εισαγάγουμε ένα υπάρχον αντικείμενο στο container. Αυτό το κάνουμε ορίζοντας την υπηρεσία με τη σημαία `imported: true`. + +```neon +services: + myservice: + type: App\Model\MyCustomService + imported: true +``` + +Και στο bootstrap, εισάγουμε το αντικείμενο στο container: + +```php +$this->configurator->addServices([ + 'myservice' => new App\Model\MyCustomService('foobar'), +]); +``` + + +Διαφορετικό Περιβάλλον +====================== + +Μη διστάσετε να τροποποιήσετε την κλάση Bootstrap σύμφωνα με τις ανάγκες σας. Μπορείτε να προσθέσετε παραμέτρους στη μέθοδο `bootWebApplication()` για να διακρίνετε τα διαδικτυακά έργα. Ή μπορούμε να προσθέσουμε άλλες μεθόδους, όπως `bootTestEnvironment()`, που αρχικοποιεί το περιβάλλον για unit tests, `bootConsoleApplication()` για σενάρια που καλούνται από τη γραμμή εντολών κ.λπ. + +```php +public function bootTestEnvironment(): Nette\DI\Container +{ + Tester\Environment::setup(); // αρχικοποίηση του Nette Tester + $this->setupContainer(); + return $this->configurator->createContainer(); +} + +public function bootConsoleApplication(): Nette\DI\Container +{ + $this->configurator->setDebugMode(false); + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); +} +``` diff --git a/application/el/components.texy b/application/el/components.texy index 7f4088b591..b50bd4fdb0 100644 --- a/application/el/components.texy +++ b/application/el/components.texy @@ -1,29 +1,29 @@ -Διαδραστικά στοιχεία -******************** +Διαδραστικά Components +**********************
    -Τα συστατικά είναι ξεχωριστά επαναχρησιμοποιήσιμα αντικείμενα που τοποθετούμε σε σελίδες. Μπορεί να είναι φόρμες, datagrids, δημοσκοπήσεις, στην πραγματικότητα οτιδήποτε έχει νόημα να χρησιμοποιείται επανειλημμένα. Θα δείξουμε: +Τα components είναι ανεξάρτητα, επαναχρησιμοποιήσιμα αντικείμενα που ενσωματώνουμε σε σελίδες. Μπορεί να είναι φόρμες, datagrids, δημοσκοπήσεις, στην πραγματικότητα οτιδήποτε έχει νόημα να χρησιμοποιείται επανειλημμένα. Θα δείξουμε: -- Πώς να χρησιμοποιήσετε τα συστατικά; -- πώς να τα γράψετε; -- Τι είναι τα σήματα; +- πώς να χρησιμοποιείτε τα components; +- πώς να τα γράφετε; +- τι είναι τα signals;
    -Η Nette διαθέτει ένα ενσωματωμένο σύστημα συστατικών. Οι παλαιότεροι από εσάς μπορεί να θυμάστε κάτι παρόμοιο από τους Delphi ή τις ASP.NET Web Forms. Το React ή το Vue.js είναι χτισμένο πάνω σε κάτι εξ αποστάσεως παρόμοιο. Ωστόσο, στον κόσμο των πλαισίων PHP, αυτό είναι ένα εντελώς μοναδικό χαρακτηριστικό. +Το Nette έχει ενσωματωμένο ένα σύστημα components. Κάτι παρόμοιο μπορεί να θυμούνται οι παλαιότεροι από τα Delphi ή τα ASP.NET Web Forms, ενώ κάτι παρόμοιο αποτελεί τη βάση του React ή του Vue.js. Ωστόσο, στον κόσμο των PHP frameworks, πρόκειται για ένα μοναδικό χαρακτηριστικό. -Ταυτόχρονα, τα συστατικά αλλάζουν ριζικά την προσέγγιση στην ανάπτυξη εφαρμογών. Μπορείτε να συνθέσετε σελίδες από προπαρασκευασμένες μονάδες. Χρειάζεστε πλέγμα δεδομένων στη διαχείριση; Μπορείτε να το βρείτε στο [Componette |https://componette.org/search/component], ένα αποθετήριο πρόσθετων στοιχείων ανοικτού κώδικα (όχι μόνο συστατικών) για το Nette, και απλά να το επικολλήσετε στον παρουσιαστή. +Τα components επηρεάζουν θεμελιωδώς την προσέγγιση στην ανάπτυξη εφαρμογών. Μπορείτε να συνθέτετε σελίδες από προκατασκευασμένες μονάδες. Χρειάζεστε ένα datagrid στη διαχείριση; Θα το βρείτε στο [Componette |https://componette.org/search/component], ένα αποθετήριο open-source πρόσθετων (όχι μόνο components) για το Nette, και απλά το ενσωματώνετε στον presenter. -Μπορείτε να ενσωματώσετε οποιονδήποτε αριθμό συστατικών στον παρουσιαστή. Και μπορείτε να εισαγάγετε άλλα συστατικά σε ορισμένα συστατικά. Έτσι δημιουργείται ένα δέντρο συστατικών με ρίζα τον παρουσιαστή. +Μπορείτε να ενσωματώσετε οποιονδήποτε αριθμό components σε έναν presenter. Και σε ορισμένα components, μπορείτε να ενσωματώσετε άλλα components. Αυτό δημιουργεί ένα δέντρο components, του οποίου η ρίζα είναι ο presenter. -Μέθοδοι εργοστασίου .[#toc-factory-methods] -=========================================== +Μέθοδοι Εργοστασίου +=================== -Πώς τοποθετούνται και στη συνέχεια χρησιμοποιούνται τα στοιχεία στον παρουσιαστή; Συνήθως με τη χρήση μεθόδων εργοστασίου. +Πώς ενσωματώνονται και στη συνέχεια χρησιμοποιούνται τα components στον presenter; Συνήθως μέσω factory μεθόδων. -Το εργοστάσιο συστατικών είναι ένας κομψός τρόπος να δημιουργούνται συστατικά μόνο όταν πραγματικά χρειάζονται (lazy / on-demand). Όλη η μαγεία βρίσκεται στην υλοποίηση μιας μεθόδου που ονομάζεται `createComponent()`, όπου `` είναι το όνομα του συστατικού, που θα δημιουργήσει και θα επιστρέψει. +Ένα factory component είναι ένας κομψός τρόπος δημιουργίας components μόνο όταν είναι πραγματικά απαραίτητα (lazy / on demand). Η όλη μαγεία έγκειται στην υλοποίηση μιας μεθόδου με το όνομα `createComponent()`, όπου `` είναι το όνομα του component που δημιουργείται, και η οποία δημιουργεί και επιστρέφει το component. ```php .{file:DefaultPresenter.php} class DefaultPresenter extends Nette\Application\UI\Presenter @@ -37,43 +37,43 @@ class DefaultPresenter extends Nette\Application\UI\Presenter } ``` -Επειδή όλα τα συστατικά δημιουργούνται σε ξεχωριστές μεθόδους, ο κώδικας είναι πιο καθαρός και πιο ευανάγνωστος. +Χάρη στο γεγονός ότι όλα τα components δημιουργούνται σε ξεχωριστές μεθόδους, ο κώδικας γίνεται πιο ευανάγνωστος. .[note] -Τα ονόματα των συστατικών αρχίζουν πάντα με πεζό γράμμα, αν και στο όνομα της μεθόδου γράφονται με κεφαλαίο. +Τα ονόματα των components ξεκινούν πάντα με μικρό γράμμα, παρόλο που γράφονται με κεφαλαίο στο όνομα της μεθόδου. -Δεν καλούμε ποτέ απευθείας τα εργοστάσια, καλούνται αυτόματα, όταν χρησιμοποιούμε συστατικά για πρώτη φορά. Χάρη σε αυτό, ένα συστατικό δημιουργείται την κατάλληλη στιγμή και μόνο αν πραγματικά χρειάζεται. Αν δεν θα χρησιμοποιούσαμε το συστατικό (για παράδειγμα σε κάποια αίτηση AJAX, όπου επιστρέφουμε μόνο ένα μέρος της σελίδας, ή όταν τα μέρη είναι αποθηκευμένα στην προσωρινή μνήμη), δεν θα δημιουργούνταν καν και θα εξοικονομούσαμε την απόδοση του διακομιστή. +Δεν καλούμε ποτέ απευθείας τα factories, καλούνται μόνα τους την πρώτη φορά που χρησιμοποιούμε το component. Χάρη σε αυτό, το component δημιουργείται τη σωστή στιγμή και μόνο όταν είναι πραγματικά απαραίτητο. Αν δεν χρησιμοποιήσουμε το component (για παράδειγμα, κατά τη διάρκεια μιας αίτησης AJAX όπου μεταδίδεται μόνο ένα μέρος της σελίδας, ή κατά την προσωρινή αποθήκευση του template), δεν δημιουργείται καθόλου και εξοικονομούμε απόδοση του διακομιστή. ```php .{file:DefaultPresenter.php} -// έχουμε πρόσβαση στο στοιχείο και αν ήταν η πρώτη φορά, -// καλεί την createComponentPoll() για να το δημιουργήσει +// προσπελάζουμε το component και αν είναι η πρώτη φορά, +// καλείται η createComponentPoll() η οποία το δημιουργεί $poll = $this->getComponent('poll'); -// εναλλακτική σύνταξη: $poll = $this['poll'], +// εναλλακτική σύνταξη: $poll = $this['poll']; ``` -Στο πρότυπο, μπορείτε να αποδώσετε ένα συστατικό χρησιμοποιώντας την ετικέτα [{control} |#Rendering]. Έτσι, δεν χρειάζεται να περνάτε χειροκίνητα τα συστατικά στο πρότυπο. +Στο template, είναι δυνατό να αποδοθεί ένα component χρησιμοποιώντας την ετικέτα [{control} |#Απόδοση]. Επομένως, δεν χρειάζεται να μεταβιβάζετε χειροκίνητα τα components στο template. ```latte -

    Please Vote

    +

    Ψηφίστε

    {control poll} ``` -Στυλ Hollywood .[#toc-hollywood-style] -====================================== +Hollywood Style +=============== -Τα στοιχεία χρησιμοποιούν συνήθως μια δροσερή τεχνική, την οποία αποκαλούμε στυλ Χόλιγουντ. Σίγουρα γνωρίζετε το κλισέ που ακούνε συχνά οι ηθοποιοί στα κάστινγκ: "Μη μας καλέσετε, θα σας καλέσουμε εμείς". Και περί αυτού πρόκειται. +Τα components χρησιμοποιούν συνήθως μια φρέσκια τεχνική, την οποία μας αρέσει να αποκαλούμε Hollywood style. Σίγουρα γνωρίζετε τη φράση που ακούν τόσο συχνά οι συμμετέχοντες σε οντισιόν ταινιών: «Μην μας καλέσετε, θα σας καλέσουμε εμείς». Και ακριβώς περί αυτού πρόκειται. -Στη Nette, αντί να χρειάζεται να κάνετε συνεχώς ερωτήσεις ("υποβλήθηκε η φόρμα;", "ήταν έγκυρη;" ή "πάτησε κανείς αυτό το κουμπί;"), λέτε στο πλαίσιο "όταν συμβεί αυτό, καλέστε αυτή τη μέθοδο" και αφήνετε την περαιτέρω εργασία σε αυτό. Αν προγραμματίζετε σε JavaScript, είστε εξοικειωμένοι με αυτό το στυλ προγραμματισμού. Γράφετε συναρτήσεις που καλούνται όταν συμβαίνει ένα συγκεκριμένο γεγονός. Και η μηχανή περνάει τις κατάλληλες παραμέτρους σε αυτές. +Στο Nette, αντί να πρέπει συνεχώς να ρωτάτε κάτι («υποβλήθηκε η φόρμα;», «ήταν έγκυρη;» ή «πάτησε ο χρήστης αυτό το κουμπί;»), λέτε στο framework «όταν συμβεί αυτό, κάλεσε αυτή τη μέθοδο» και αφήνετε την υπόλοιπη δουλειά σε αυτό. Αν προγραμματίζετε σε JavaScript, αυτό το στυλ προγραμματισμού σας είναι οικείο. Γράφετε συναρτήσεις που καλούνται όταν συμβεί ένα συγκεκριμένο γεγονός. Και η γλώσσα τους μεταβιβάζει τις κατάλληλες παραμέτρους. -Αυτό αλλάζει εντελώς τον τρόπο με τον οποίο γράφετε εφαρμογές. Όσο περισσότερες εργασίες μπορείτε να αναθέσετε στο πλαίσιο, τόσο λιγότερη δουλειά έχετε. Και τόσο λιγότερο μπορείτε να ξεχάσετε. +Αυτό αλλάζει εντελώς την οπτική γωνία της συγγραφής εφαρμογών. Όσο περισσότερες εργασίες μπορείτε να αφήσετε στο framework, τόσο λιγότερη δουλειά έχετε εσείς. Και τόσο λιγότερα πράγματα μπορείτε, για παράδειγμα, να παραλείψετε. -Πώς να γράψετε ένα συστατικό .[#toc-how-to-write-a-component] -============================================================= +Γράφοντας ένα Component +======================= -Με τον όρο συστατικό συνήθως εννοούμε απογόνους της κλάσης [api:Nette\Application\UI\Control]. Ο ίδιος ο παρουσιαστής [api:Nette\Application\UI\Presenter] είναι επίσης απόγονος της κλάσης `Control`. +Με τον όρο component, συνήθως εννοούμε έναν απόγονο της κλάσης [api:Nette\Application\UI\Control]. (Θα ήταν πιο ακριβές να χρησιμοποιούμε τον όρο «controls», αλλά οι «έλεγχοι» έχουν εντελώς διαφορετική σημασία στα Ελληνικά και ο όρος «components» έχει επικρατήσει.) Ο ίδιος ο presenter [api:Nette\Application\UI\Presenter] είναι, παρεμπιπτόντως, επίσης απόγονος της κλάσης `Control`. ```php .{file:PollControl.php} use Nette\Application\UI\Control; @@ -84,22 +84,22 @@ class PollControl extends Control ``` -Παρουσίαση .[#toc-rendering] -============================ +Απόδοση +======= -Γνωρίζουμε ήδη ότι η ετικέτα `{control componentName}` χρησιμοποιείται για τη σχεδίαση ενός στοιχείου. Στην πραγματικότητα καλεί τη μέθοδο `render()` του συστατικού, στην οποία αναλαμβάνουμε την απόδοση. Έχουμε, όπως ακριβώς και στον παρουσιαστή, ένα πρότυπο [Latte |latte:] στη μεταβλητή `$this->template`, στην οποία περνάμε τις παραμέτρους. Σε αντίθεση με τη χρήση σε έναν παρουσιαστή, πρέπει να καθορίσουμε ένα αρχείο προτύπου και να το αφήσουμε να κάνει render: +Γνωρίζουμε ήδη ότι για την απόδοση ενός component χρησιμοποιείται η ετικέτα `{control componentName}`. Αυτή στην πραγματικότητα καλεί τη μέθοδο `render()` του component, στην οποία φροντίζουμε για την απόδοση. Έχουμε στη διάθεσή μας, ακριβώς όπως στον presenter, ένα [Latte template|templates] στη μεταβλητή `$this->template`, στην οποία μεταβιβάζουμε παραμέτρους. Σε αντίθεση με τον presenter, πρέπει να καθορίσουμε το αρχείο με το template και να το αφήσουμε να αποδοθεί: ```php .{file:PollControl.php} public function render(): void { - // θα βάλουμε κάποιες παραμέτρους στο πρότυπο + // εισάγουμε κάποιες παραμέτρους στο template $this->template->param = $value; - // και θα το σχεδιάσουμε + // και το αποδίδουμε $this->template->render(__DIR__ . '/poll.latte'); } ``` -Η ετικέτα `{control}` επιτρέπει να περάσουμε παραμέτρους στη μέθοδο `render()`: +Η ετικέτα `{control}` επιτρέπει τη μεταβίβαση παραμέτρων στη μέθοδο `render()`: ```latte {control poll $id, $message} @@ -112,7 +112,7 @@ public function render(int $id, string $message): void } ``` -Μερικές φορές ένα συστατικό μπορεί να αποτελείται από διάφορα μέρη που θέλουμε να απεικονίσουμε ξεχωριστά. Για κάθε ένα από αυτά θα δημιουργήσουμε τη δική μας μέθοδο απόδοσης, εδώ είναι για παράδειγμα το `renderPaginator()`: +Μερικές φορές, ένα component μπορεί να αποτελείται από πολλά μέρη που θέλουμε να αποδώσουμε ξεχωριστά. Για καθένα από αυτά, δημιουργούμε τη δική του μέθοδο απόδοσης, εδώ στο παράδειγμα, για παράδειγμα, `renderPaginator()`: ```php .{file:PollControl.php} public function renderPaginator(): void @@ -121,107 +121,107 @@ public function renderPaginator(): void } ``` -Και στο πρότυπο στη συνέχεια την καλούμε χρησιμοποιώντας: +Και στο template, την καλούμε στη συνέχεια χρησιμοποιώντας: ```latte {control poll:paginator} ``` -Για καλύτερη κατανόηση είναι καλό να γνωρίζουμε πώς η ετικέτα μεταφράζεται σε κώδικα PHP. +Για καλύτερη κατανόηση, είναι καλό να γνωρίζουμε πώς μεταφράζεται αυτή η ετικέτα σε PHP. ```latte {control poll} {control poll:paginator 123, 'hello'} ``` -Αυτό μεταγλωττίζεται σε: +μεταφράζεται ως: ```php $control->getComponent('poll')->render(); $control->getComponent('poll')->renderPaginator(123, 'hello'); ``` -`getComponent()` `poll` και στη συνέχεια καλείται η μέθοδος `render()` ή `renderPaginator()`, αντίστοιχα, σε αυτό. +Η μέθοδος `getComponent()` επιστρέφει το component `poll` και πάνω σε αυτό το component καλεί τη μέθοδο `render()`, ή `renderPaginator()` αν έχει καθοριστεί διαφορετικός τρόπος απόδοσης στην ετικέτα μετά την άνω και κάτω τελεία. .[caution] -Εάν οπουδήποτε στο τμήμα παραμέτρων χρησιμοποιείται **`=>`**, όλες οι παράμετροι θα περιτυλιχθούν με έναν πίνακα και θα περάσουν ως πρώτο όρισμα: +Προσοχή, αν εμφανιστεί οπουδήποτε στις παραμέτρους το **`=>`**, όλες οι παράμετροι θα συσκευαστούν σε έναν πίνακα και θα μεταβιβαστούν ως το πρώτο όρισμα: ```latte {control poll, id: 123, message: 'hello'} ``` -compiles to: +μεταφράζεται ως: ```php $control->getComponent('poll')->render(['id' => 123, 'message' => 'hello']); ``` -Απόδοση του υπο-στοιχείου: +Απόδοση υπο-component: ```latte {control cartControl-someForm} ``` -μεταγλωττίζει σε: +μεταφράζεται ως: ```php $control->getComponent("cartControl-someForm")->render(); ``` -Τα συστατικά, όπως οι παρουσιαστές, μεταβιβάζουν αυτόματα διάφορες χρήσιμες μεταβλητές στα πρότυπα: +Τα components, όπως και οι presenters, μεταβιβάζουν αυτόματα αρκετές χρήσιμες μεταβλητές στα templates: -- `$basePath` είναι μια απόλυτη διαδρομή URL στο root dir (για παράδειγμα `/CD-collection`) -- `$baseUrl` είναι μια απόλυτη διεύθυνση URL στο root dir (για παράδειγμα `http://localhost/CD-collection`) -- `$user` είναι ένα αντικείμενο [που αντιπροσωπεύει τον χρήστη |security:authentication] -- `$presenter` είναι ο τρέχων παρουσιαστής -- `$control` είναι το τρέχον συστατικό -- `$flashes` κατάλογος [μηνυμάτων |#flash-messages] που αποστέλλονται από τη μέθοδο `flashMessage()` +- `$basePath` είναι η απόλυτη διαδρομή URL προς τον ριζικό κατάλογο (π.χ. `/eshop`) +- `$baseUrl` είναι η απόλυτη URL προς τον ριζικό κατάλογο (π.χ. `http://localhost/eshop`) +- `$user` είναι το αντικείμενο [που αντιπροσωπεύει τον χρήστη |security:authentication] +- `$presenter` είναι ο τρέχων presenter +- `$control` είναι το τρέχον component +- `$flashes` array [μηνυμάτων |#Flash Μηνύματα] που στάλθηκαν από τη συνάρτηση `flashMessage()` -Σήμα .[#toc-signal] -=================== +Σήμα +==== -Γνωρίζουμε ήδη ότι η πλοήγηση στην εφαρμογή Nette συνίσταται στη σύνδεση ή την ανακατεύθυνση σε ζεύγη `Presenter:action`. Τι γίνεται όμως αν θέλουμε απλώς να εκτελέσουμε μια ενέργεια στην **τρέχουσα σελίδα**; Για παράδειγμα, να αλλάξουμε τη σειρά ταξινόμησης της στήλης στον πίνακα, να διαγράψουμε στοιχείο, να αλλάξουμε τη λειτουργία φωτός/σκοταδιού, να υποβάλουμε τη φόρμα, να ψηφίσουμε στην ψηφοφορία κ.λπ. +Γνωρίζουμε ήδη ότι η πλοήγηση σε μια εφαρμογή Nette βασίζεται στη σύνδεση ή την ανακατεύθυνση σε ζεύγη `Presenter:action`. Αλλά τι γίνεται αν θέλουμε απλώς να εκτελέσουμε μια ενέργεια στην **τρέχουσα σελίδα**; Για παράδειγμα, να αλλάξουμε τη διάταξη των στηλών σε έναν πίνακα; να διαγράψουμε ένα στοιχείο; να αλλάξουμε σε φωτεινή/σκοτεινή λειτουργία; να υποβάλουμε μια φόρμα; να ψηφίσουμε σε μια δημοσκόπηση; κ.λπ. -Αυτός ο τύπος αιτήματος ονομάζεται σήμα. Και όπως οι ενέργειες επικαλούνται μεθόδους `action()` ή `render()`, τα σήματα καλούν μεθόδους `handle()`. Ενώ η έννοια της ενέργειας (ή της προβολής) αφορά μόνο τους παρουσιαστές, τα σήματα εφαρμόζονται σε όλα τα στοιχεία. Και επομένως και στους παρουσιαστές, επειδή το `UI\Presenter` είναι απόγονος του `UI\Control`. +Αυτό το είδος αιτήματος ονομάζεται signal. Και όπως οι ενέργειες καλούν τις μεθόδους `action()` ή `render()`, τα signals καλούν τις μεθόδους `handle()`. Ενώ ο όρος ενέργεια (ή view) σχετίζεται καθαρά μόνο με τους presenters, τα signals αφορούν όλα τα components. Και επομένως και τους presenters, επειδή το `UI\Presenter` είναι απόγονος του `UI\Control`. ```php public function handleClick(int $x, int $y): void { - // ... επεξεργασία σήματος ... + // ... επεξεργασία του signal ... } ``` -Ο σύνδεσμος που καλεί το σήμα δημιουργείται με τον συνήθη τρόπο, δηλαδή στο πρότυπο με το χαρακτηριστικό `n:href` ή την ετικέτα `{link}`, στον κώδικα με τη μέθοδο `link()`. Περισσότερα στο κεφάλαιο [Δημιουργία συνδέσμων URL |creating-links#Links to Signal]. +Έναν σύνδεσμο που καλεί ένα signal τον δημιουργούμε με τον συνηθισμένο τρόπο, δηλαδή στο template με το χαρακτηριστικό `n:href` ή την ετικέτα `{link}`, στον κώδικα με τη μέθοδο `link()`. Περισσότερα στο κεφάλαιο [Δημιουργία συνδέσμων URL |creating-links#Σύνδεσμοι προς Σήμα]. ```latte -click here +κάντε κλικ εδώ ``` -Το σήμα καλείται πάντα στον τρέχοντα παρουσιαστή και προβολή, επομένως δεν είναι δυνατή η σύνδεση με το σήμα σε διαφορετικό παρουσιαστή/δράση. +Ένα signal καλείται πάντα στον τρέχοντα presenter και action, δεν είναι δυνατό να το καλέσετε σε άλλο presenter ή άλλη action. -Έτσι, το σήμα προκαλεί την επαναφόρτωση της σελίδας με τον ίδιο ακριβώς τρόπο όπως στην αρχική αίτηση, μόνο που επιπλέον καλεί τη μέθοδο χειρισμού του σήματος με τις κατάλληλες παραμέτρους. Εάν η μέθοδος δεν υπάρχει, δημιουργείται η εξαίρεση [api:Nette\Application\UI\BadSignalException], η οποία εμφανίζεται στο χρήστη ως σελίδα σφάλματος 403 Forbidden. +Ένα signal προκαλεί λοιπόν την επαναφόρτωση της σελίδας ακριβώς όπως στην αρχική αίτηση, απλώς επιπλέον καλεί τη μέθοδο χειρισμού του signal με τις κατάλληλες παραμέτρους. Αν η μέθοδος δεν υπάρχει, δημιουργείται μια εξαίρεση [api:Nette\Application\UI\BadSignalException], η οποία εμφανίζεται στον χρήστη ως σελίδα σφάλματος 403 Forbidden. -Αποσπάσματα και AJAX .[#toc-snippets-and-ajax] -============================================== +Snippets και AJAX +================= -Τα σήματα μπορεί να σας θυμίζουν λίγο AJAX: χειριστές που καλούνται στην τρέχουσα σελίδα. Και έχετε δίκιο, τα σήματα καλούνται πραγματικά συχνά με τη χρήση AJAX, και στη συνέχεια μεταδίδουμε μόνο τα αλλαγμένα τμήματα της σελίδας στο πρόγραμμα περιήγησης. Ονομάζονται αποσπάσματα. Περισσότερες πληροφορίες μπορείτε να βρείτε [στη σελίδα σχετικά με το AJAX |ajax]. +Τα signals μπορεί να σας θυμίζουν λίγο το AJAX: handlers που καλούνται στην τρέχουσα σελίδα. Και έχετε δίκιο, τα signals καλούνται πράγματι συχνά μέσω AJAX και στη συνέχεια μεταφέρουμε στο πρόγραμμα περιήγησης μόνο τα αλλαγμένα τμήματα της σελίδας. Δηλαδή τα λεγόμενα snippets. Περισσότερες πληροφορίες θα βρείτε στη [σελίδα αφιερωμένη στο AJAX |ajax]. -Μηνύματα Flash .[#toc-flash-messages] -===================================== +Flash Μηνύματα +============== -Ένα στοιχείο έχει τη δική του αποθήκευση μηνυμάτων flash ανεξάρτητα από τον παρουσιαστή. Πρόκειται για μηνύματα που, για παράδειγμα, ενημερώνουν για το αποτέλεσμα της λειτουργίας. Ένα σημαντικό χαρακτηριστικό των μηνυμάτων flash είναι ότι είναι διαθέσιμα στο πρότυπο ακόμη και μετά την ανακατεύθυνση. Ακόμη και μετά την εμφάνισή τους, θα παραμείνουν ζωντανά για άλλα 30 δευτερόλεπτα - για παράδειγμα, σε περίπτωση που ο χρήστης θα ανανεώσει ακούσια τη σελίδα - το μήνυμα δεν θα χαθεί. +Ένα component έχει το δικό του χώρο αποθήκευσης flash μηνυμάτων, ανεξάρτητο από τον presenter. Πρόκειται για μηνύματα που, για παράδειγμα, ενημερώνουν για το αποτέλεσμα μιας λειτουργίας. Ένα σημαντικό χαρακτηριστικό των flash μηνυμάτων είναι ότι είναι διαθέσιμα στο template ακόμη και μετά από ανακατεύθυνση. Ακόμη και μετά την εμφάνισή τους, παραμένουν ενεργά για άλλα 30 δευτερόλεπτα – για παράδειγμα, σε περίπτωση που ο χρήστης ανανεώσει τη σελίδα λόγω σφάλματος μετάδοσης - το μήνυμα δεν εξαφανίζεται αμέσως. -Η αποστολή γίνεται με τη μέθοδο [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Η πρώτη παράμετρος είναι το κείμενο του μηνύματος ή το αντικείμενο `stdClass` που αντιπροσωπεύει το μήνυμα. Η προαιρετική δεύτερη παράμετρος είναι ο τύπος του (σφάλμα, προειδοποίηση, πληροφορία κ.λπ.). Η μέθοδος `flashMessage()` επιστρέφει ένα instance του flash message ως αντικείμενο stdClass στο οποίο μπορείτε να περάσετε πληροφορίες. +Η αποστολή γίνεται από τη μέθοδο [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Η πρώτη παράμετρος είναι το κείμενο του μηνύματος ή ένα αντικείμενο `stdClass` που αντιπροσωπεύει το μήνυμα. Η προαιρετική δεύτερη παράμετρος είναι ο τύπος του (error, warning, info κ.λπ.). Η μέθοδος `flashMessage()` επιστρέφει μια παρουσία του flash μηνύματος ως αντικείμενο `stdClass`, στο οποίο μπορούν να προστεθούν περαιτέρω πληροφορίες. ```php -$this->flashMessage('Item was deleted.'); -$this->redirect(/* ... */); // και ανακατεύθυνση +$this->flashMessage('Το στοιχείο διαγράφηκε.'); +$this->redirect(/* ... */); // και ανακατευθύνουμε ``` -Στο πρότυπο, τα μηνύματα αυτά είναι διαθέσιμα στη μεταβλητή `$flashes` ως αντικείμενα `stdClass`, τα οποία περιέχουν τις ιδιότητες `message` (κείμενο μηνύματος), `type` (τύπος μηνύματος) και μπορούν να περιέχουν τις ήδη αναφερθείσες πληροφορίες χρήστη. Τα σχεδιάζουμε ως εξής: +Στο template, αυτά τα μηνύματα είναι διαθέσιμα στη μεταβλητή `$flashes` ως αντικείμενα `stdClass`, τα οποία περιέχουν τις ιδιότητες `message` (κείμενο μηνύματος), `type` (τύπος μηνύματος) και μπορούν να περιέχουν τις ήδη αναφερθείσες πληροφορίες χρήστη. Τα αποδίδουμε, για παράδειγμα, ως εξής: ```latte {foreach $flashes as $flash} @@ -230,44 +230,66 @@ $this->redirect(/* ... */); // και ανακατεύθυνση ``` -Μόνιμες παράμετροι .[#toc-persistent-parameters] -================================================ +Ανακατεύθυνση μετά από Σήμα +=========================== -Οι μόνιμες παράμετροι χρησιμοποιούνται για τη διατήρηση της κατάστασης των στοιχείων μεταξύ διαφορετικών αιτήσεων. Η τιμή τους παραμένει η ίδια ακόμη και μετά το κλικ σε έναν σύνδεσμο. Σε αντίθεση με τα δεδομένα συνόδου, μεταφέρονται στη διεύθυνση URL. Και μεταφέρονται αυτόματα, συμπεριλαμβανομένων των συνδέσμων που δημιουργούνται σε άλλα στοιχεία στην ίδια σελίδα. +Μετά την επεξεργασία ενός signal component, συχνά ακολουθεί ανακατεύθυνση. Είναι μια παρόμοια κατάσταση με τις φόρμες - μετά την υποβολή τους, ανακατευθύνουμε επίσης, ώστε η ανανέωση της σελίδας στο πρόγραμμα περιήγησης να μην προκαλέσει εκ νέου υποβολή των δεδομένων. -Για παράδειγμα, έχετε ένα στοιχείο σελιδοποίησης περιεχομένου. Μπορεί να υπάρχουν πολλά τέτοια στοιχεία σε μια σελίδα. Και θέλετε όλα τα συστατικά να παραμένουν στην τρέχουσα σελίδα τους όταν κάνετε κλικ στο σύνδεσμο. Επομένως, κάνουμε τον αριθμό σελίδας (`page`) μια μόνιμη παράμετρο. +```php +$this->redirect('this') // ανακατευθύνει στον τρέχοντα presenter και action +``` -Η δημιουργία μιας μόνιμης παραμέτρου είναι εξαιρετικά εύκολη στη Nette. Απλά δημιουργήστε μια δημόσια ιδιότητα και επισημάνετέ την με το χαρακτηριστικό: (προηγουμένως χρησιμοποιούνταν το `/** @persistent */` ) +Επειδή ένα component είναι ένα επαναχρησιμοποιήσιμο στοιχείο και συνήθως δεν θα πρέπει να έχει άμεση σύνδεση με συγκεκριμένους presenters, οι μέθοδοι `redirect()` και `link()` ερμηνεύουν αυτόματα την παράμετρο ως signal του component: ```php -use Nette\Application\Attributes\Persistent; // αυτή η γραμμή είναι σημαντική +$this->redirect('click') // ανακατευθύνει στο signal 'click' του ίδιου component +``` + +Αν χρειαστεί να ανακατευθύνετε σε άλλο presenter ή ενέργεια, μπορείτε να το κάνετε μέσω του presenter: + +```php +$this->getPresenter()->redirect('Product:show'); // ανακατευθύνει σε άλλο presenter/action +``` + + +Persistent Παράμετροι +===================== + +Οι persistent παράμετροι χρησιμοποιούνται για τη διατήρηση της κατάστασης στα components μεταξύ διαφορετικών αιτήσεων. Η τιμή τους παραμένει η ίδια ακόμη και μετά το κλικ σε έναν σύνδεσμο. Σε αντίθεση με τα δεδομένα στη session, μεταφέρονται στη διεύθυνση URL. Και αυτό γίνεται εντελώς αυτόματα, συμπεριλαμβανομένων των συνδέσμων που δημιουργούνται σε άλλα components στην ίδια σελίδα. + +Έχετε, για παράδειγμα, ένα component για τη σελιδοποίηση περιεχομένου. Μπορεί να υπάρχουν πολλά τέτοια components σε μια σελίδα. Και θέλουμε, μετά το κλικ σε έναν σύνδεσμο, όλα τα components να παραμείνουν στην τρέχουσα σελίδα τους. Γι' αυτό, κάνουμε τον αριθμό σελίδας (`page`) μια persistent παράμετρο. + +Η δημιουργία μιας persistent παραμέτρου στο Nette είναι εξαιρετικά απλή. Αρκεί να δημιουργήσετε μια δημόσια property και να την επισημάνετε με ένα attribute: (παλαιότερα χρησιμοποιούνταν το `/** @persistent */`) + +```php +use Nette\Application\Attributes\Persistent; // αυτή η γραμμή είναι σημαντική class PaginatingControl extends Control { #[Persistent] - public int $page = 1; // πρέπει να είναι δημόσια + public int $page = 1; // πρέπει να είναι public } ``` -Συνιστούμε να συμπεριλάβετε τον τύπο δεδομένων (π.χ. `int`) μαζί με την ιδιότητα και μπορείτε επίσης να συμπεριλάβετε μια προεπιλεγμένη τιμή. Οι τιμές των παραμέτρων μπορούν να [επικυρωθούν |#Validation of Persistent Parameters]. +Συνιστούμε να καθορίσετε τον τύπο δεδομένων για την property (π.χ. `int`) και μπορείτε επίσης να καθορίσετε μια προεπιλεγμένη τιμή. Οι τιμές των παραμέτρων μπορούν να [επικυρωθούν |#Επικύρωση Persistent Παραμέτρων]. -Μπορείτε να αλλάξετε την τιμή μιας μόνιμης παραμέτρου κατά τη δημιουργία ενός συνδέσμου: +Κατά τη δημιουργία ενός συνδέσμου, η τιμή της persistent παραμέτρου μπορεί να αλλάξει: ```latte -next +επόμενο ``` -Ή μπορεί να *επαναρυθμιστεί*, δηλαδή να αφαιρεθεί από τη διεύθυνση URL. Τότε θα πάρει την προεπιλεγμένη τιμή της: +Ή μπορεί να *επαναφερθεί*, δηλαδή να αφαιρεθεί από τη διεύθυνση URL. Στη συνέχεια, θα πάρει την προεπιλεγμένη της τιμή: ```latte -reset +επαναφορά ``` -Εμμένουσες συνιστώσες .[#toc-persistent-components] -=================================================== +Persistent Components +===================== -Όχι μόνο οι παράμετροι αλλά και τα συστατικά μπορούν να είναι μόνιμα. Οι μόνιμες παράμετροι τους μεταφέρονται επίσης μεταξύ διαφορετικών ενεργειών ή μεταξύ διαφορετικών παρουσιαστών. Χαρακτηρίζουμε τα μόνιμα συστατικά με αυτές τις σημειώσεις για την κλάση presenter. Για παράδειγμα, εδώ επισημαίνουμε τα συστατικά `calendar` και `poll` ως εξής: +Όχι μόνο οι παράμετροι, αλλά και τα components μπορούν να είναι persistent. Σε ένα τέτοιο component, οι persistent παράμετροί του μεταφέρονται ακόμη και μεταξύ διαφορετικών actions του presenter ή μεταξύ πολλών presenters. Σημειώνουμε τα persistent components με μια annotation στην κλάση του presenter. Για παράδειγμα, έτσι σημειώνουμε τα components `calendar` και `poll`: ```php /** @@ -278,9 +300,9 @@ class DefaultPresenter extends Nette\Application\UI\Presenter } ``` -Δεν χρειάζεται να επισημάνετε τα υποστοιχεία ως μόνιμα, είναι μόνιμα αυτόματα. +Τα υπο-components μέσα σε αυτά τα components δεν χρειάζεται να σημειωθούν, γίνονται επίσης persistent. -Στην PHP 8, μπορείτε επίσης να χρησιμοποιήσετε χαρακτηριστικά για να επισημάνετε μόνιμα συστατικά: +Στην PHP 8, μπορείτε επίσης να χρησιμοποιήσετε attributes για να σημειώσετε τα persistent components: ```php use Nette\Application\Attributes\Persistent; @@ -292,18 +314,18 @@ class DefaultPresenter extends Nette\Application\UI\Presenter ``` -Εξαρτήματα με εξαρτήσεις .[#toc-components-with-dependencies] -============================================================= +Components με Εξαρτήσεις +======================== -Πώς να δημιουργήσετε συστατικά με εξαρτήσεις χωρίς να "μπερδέψετε" τους παρουσιαστές που θα τα χρησιμοποιήσουν; Χάρη στα έξυπνα χαρακτηριστικά του DI container στο Nette, όπως και με τη χρήση παραδοσιακών υπηρεσιών, μπορούμε να αφήσουμε το μεγαλύτερο μέρος της δουλειάς στο πλαίσιο. +Πώς να δημιουργήσετε components με εξαρτήσεις χωρίς να «μολύνετε» τους presenters που θα τα χρησιμοποιήσουν; Χάρη στις έξυπνες ιδιότητες του DI container στο Nette, όπως και με τη χρήση κλασικών υπηρεσιών, μπορείτε να αφήσετε το μεγαλύτερο μέρος της δουλειάς στο framework. -Ας πάρουμε ως παράδειγμα ένα συστατικό που έχει εξάρτηση από την υπηρεσία `PollFacade`: +Ας πάρουμε ως παράδειγμα ένα component που έχει εξάρτηση από την υπηρεσία `PollFacade`: ```php class PollControl extends Control { public function __construct( - private int $id, // Id μιας δημοσκόπησης, για την οποία δημιουργείται το στοιχείο + private int $id, // Id της δημοσκόπησης για την οποία δημιουργούμε το component private PollFacade $facade, ) { } @@ -316,11 +338,11 @@ class PollControl extends Control } ``` -Αν γράφαμε μια κλασική υπηρεσία, δεν θα υπήρχε λόγος ανησυχίας. Το DI container θα φρόντιζε αόρατα να περάσει όλες τις εξαρτήσεις. Αλλά συνήθως χειριζόμαστε τα συστατικά δημιουργώντας μια νέα τους περίπτωση απευθείας στον παρουσιαστή σε [μεθόδους εργοστασίου |#factory methods] `createComponent...()`. Αλλά το πέρασμα όλων των εξαρτήσεων όλων των συστατικών στον presenter για να τις περάσουμε στη συνέχεια στα συστατικά είναι δυσκίνητο. Και η ποσότητα του κώδικα που γράφεται... +Αν γράφαμε μια κλασική υπηρεσία, δεν θα υπήρχε πρόβλημα. Ο DI container θα φρόντιζε αόρατα για τη μεταβίβαση όλων των εξαρτήσεων. Αλλά με τα components, συνήθως τα χειριζόμαστε δημιουργώντας μια νέα παρουσία τους απευθείας στον presenter στις [factory μεθόδους |#Μέθοδοι Εργοστασίου] `createComponent…()`. Αλλά η μεταβίβαση όλων των εξαρτήσεων όλων των components στον presenter, για να τις μεταβιβάσουμε στη συνέχεια στα components, είναι δυσκίνητη. Και πόσος γραμμένος κώδικας… -Το λογικό ερώτημα είναι, γιατί δεν καταχωρούμε απλώς το συστατικό ως κλασική υπηρεσία, να το περάσουμε στον παρουσιαστή και στη συνέχεια να το επιστρέψουμε στη μέθοδο `createComponent...()`; Αλλά αυτή η προσέγγιση είναι ακατάλληλη, επειδή θέλουμε να μπορούμε να δημιουργήσουμε το συστατικό πολλές φορές. +Το λογικό ερώτημα είναι, γιατί απλά δεν καταχωρούμε το component ως κλασική υπηρεσία, δεν το μεταβιβάζουμε στον presenter και στη συνέχεια δεν το επιστρέφουμε στη μέθοδο `createComponent…()`? Αυτή η προσέγγιση είναι όμως ακατάλληλη, επειδή θέλουμε να έχουμε τη δυνατότητα να δημιουργούμε το component ακόμη και πολλές φορές. -Η σωστή λύση είναι να γράψουμε ένα εργοστάσιο για το συστατικό, δηλαδή μια κλάση που δημιουργεί το συστατικό για εμάς: +Η σωστή λύση είναι να γράψουμε ένα factory για το component, δηλαδή μια κλάση που θα μας δημιουργήσει το component: ```php class PollControlFactory @@ -337,17 +359,17 @@ class PollControlFactory } ``` -Τώρα εγγράφουμε την υπηρεσία μας στο DI container στη διαμόρφωση: +Καταχωρούμε αυτό το factory στο container μας στη διαμόρφωση: ```neon services: - PollControlFactory ``` -Τέλος, θα χρησιμοποιήσουμε αυτό το εργοστάσιο στον παρουσιαστή μας: +και τέλος το χρησιμοποιούμε στον presenter μας: ```php -class PollPresenter extends Nette\UI\Application\Presenter +class PollPresenter extends Nette\Application\UI\Presenter { public function __construct( private PollControlFactory $pollControlFactory, @@ -356,13 +378,13 @@ class PollPresenter extends Nette\UI\Application\Presenter protected function createComponentPollControl(): PollControl { - $pollId = 1; // μπορούμε να περάσουμε την παράμετρο μας + $pollId = 1; // μπορούμε να περάσουμε την παράμετρό μας return $this->pollControlFactory->create($pollId); } } ``` -Το σπουδαίο είναι ότι το Nette DI μπορεί να [δημιουργήσει |dependency-injection:factory] τέτοια απλά εργοστάσια, οπότε αντί να γράψετε ολόκληρο τον κώδικα, χρειάζεται απλώς να γράψετε τη διεπαφή του: +Το υπέροχο είναι ότι το Nette DI μπορεί να [δημιουργήσει |dependency-injection:factory] τέτοια απλά factories, οπότε αντί για ολόκληρο τον κώδικά του, αρκεί να γράψουμε μόνο το interface του: ```php interface PollControlFactory @@ -371,21 +393,21 @@ interface PollControlFactory } ``` -Αυτό είναι όλο. Η Nette υλοποιεί εσωτερικά αυτή τη διεπαφή και την εγχέει στον παρουσιαστή μας, όπου μπορούμε να τη χρησιμοποιήσουμε. Επίσης, περνάει μαγικά την παράμετρο `$id` και την περίπτωση της κλάσης `PollFacade` στο στοιχείο μας. +Και αυτό είναι όλο. Το Nette υλοποιεί εσωτερικά αυτό το interface και το μεταβιβάζει στον presenter, όπου μπορούμε ήδη να το χρησιμοποιήσουμε. Προσθέτει μαγικά στην component μας την παράμετρο `$id` και την παρουσία της κλάσης `PollFacade`. -Συστατικά σε βάθος .[#toc-components-in-depth] -============================================== +Components σε Βάθος +=================== -Τα συστατικά σε μια εφαρμογή Nette είναι τα επαναχρησιμοποιήσιμα μέρη μιας διαδικτυακής εφαρμογής που ενσωματώνουμε σε σελίδες, τα οποία αποτελούν το αντικείμενο αυτού του κεφαλαίου. Ποιες ακριβώς είναι οι δυνατότητες ενός τέτοιου συστατικού; +Τα components στην Nette Application είναι επαναχρησιμοποιήσιμα μέρη μιας διαδικτυακής εφαρμογής που ενσωματώνουμε σε σελίδες και στα οποία, άλλωστε, είναι αφιερωμένο ολόκληρο αυτό το κεφάλαιο. Ποιες ακριβώς δυνατότητες έχει ένα τέτοιο component; -1) είναι δυνατό να αποδοθεί σε ένα πρότυπο -2) γνωρίζει ποιο μέρος του εαυτού του να αποδώσει κατά τη διάρκεια μιας [αίτησης AJAX |ajax#invalidation] (αποσπάσματα) -3) έχει τη δυνατότητα να αποθηκεύει την κατάστασή του σε μια διεύθυνση URL (μόνιμες παράμετροι) -4) έχει τη δυνατότητα να ανταποκρίνεται σε ενέργειες του χρήστη (σήματα) -5) δημιουργεί μια ιεραρχική δομή (όπου η ρίζα είναι ο παρουσιαστής) +1) μπορεί να αποδοθεί σε ένα template +2) γνωρίζει [ποιο μέρος του |ajax#Snippets] πρέπει να αποδώσει κατά τη διάρκεια μιας αίτησης AJAX (snippets) +3) έχει τη δυνατότητα να αποθηκεύει την κατάστασή του στη διεύθυνση URL (persistent παράμετροι) +4) έχει τη δυνατότητα να αντιδρά στις ενέργειες του χρήστη (signals) +5) δημιουργεί μια ιεραρχική δομή (όπου η ρίζα είναι ο presenter) -Κάθε μία από αυτές τις λειτουργίες χειρίζεται μία από τις κλάσεις της κληρονομικής γραμμής. Την απόδοση (1 + 2) χειρίζεται η κλάση [api:Nette\Application\UI\Control], την ενσωμάτωση στον [κύκλο ζωής |presenters#life-cycle-of-presenter] (3, 4) η κλάση [api:Nette\Application\UI\Component] και τη δημιουργία της ιεραρχικής δομής (5) οι κλάσεις [Container και Component |component-model:]. +Κάθε μία από αυτές τις λειτουργίες παρέχεται από κάποια από τις κλάσεις της γραμμής κληρονομικότητας. Η απόδοση (1 + 2) γίνεται από την [api:Nette\Application\UI\Control], η ενσωμάτωση στον [κύκλο ζωής |presenters#Κύκλος ζωής του presenter] (3, 4) από την κλάση [api:Nette\Application\UI\Component] και η δημιουργία της ιεραρχικής δομής (5) από τις κλάσεις [Container και Component |component-model:]. ``` Nette\ComponentModel\Component { IComponent } @@ -400,18 +422,18 @@ Nette\ComponentModel\Component { IComponent } ``` -Κύκλος ζωής του συστατικού .[#toc-life-cycle-of-component] ----------------------------------------------------------- +Κύκλος Ζωής του Component +------------------------- -[* lifecycle-component.svg *] *** *Κύκλος ζωής του συστατικού* .<> +[* lifecycle-component.svg *] *** *Κύκλος ζωής του Component* .<> -Επικύρωση μόνιμων παραμέτρων .[#toc-validation-of-persistent-parameters] ------------------------------------------------------------------------- +Επικύρωση Persistent Παραμέτρων +------------------------------- -Οι τιμές των [μόνιμων παραμέτρων |#persistent parameters] που λαμβάνονται από τις διευθύνσεις URL εγγράφονται στις ιδιότητες με τη μέθοδο `loadState()`. Ελέγχει επίσης αν ο τύπος δεδομένων που έχει καθοριστεί για την ιδιότητα ταιριάζει, διαφορετικά θα απαντήσει με σφάλμα 404 και η σελίδα δεν θα εμφανιστεί. +Οι τιμές των [persistent παραμέτρων |#Persistent Παράμετροι] που λαμβάνονται από τη διεύθυνση URL γράφονται στις properties από τη μέθοδο `loadState()`. Αυτή ελέγχει επίσης εάν ο τύπος δεδομένων που καθορίζεται στην property αντιστοιχεί, διαφορετικά απαντά με σφάλμα 404 και η σελίδα δεν εμφανίζεται. -Ποτέ μην εμπιστεύεστε τυφλά τις μόνιμες παραμέτρους, επειδή μπορούν εύκολα να αντικατασταθούν από τον χρήστη στη διεύθυνση URL. Για παράδειγμα, με αυτόν τον τρόπο ελέγχουμε αν ο αριθμός σελίδας `$this->page` είναι μεγαλύτερος από 0. Ένας καλός τρόπος για να το κάνετε αυτό είναι να παρακάμψετε τη μέθοδο `loadState()` που αναφέρθηκε παραπάνω: +Ποτέ μην εμπιστεύεστε τυφλά τις persistent παραμέτρους, επειδή μπορούν εύκολα να αντικατασταθούν από τον χρήστη στη διεύθυνση URL. Έτσι, για παράδειγμα, επαληθεύουμε εάν ο αριθμός σελίδας `$this->page` είναι μεγαλύτερος από 0. Ένας κατάλληλος τρόπος είναι να αντικαταστήσετε την αναφερόμενη μέθοδο `loadState()`: ```php class PaginatingControl extends Control @@ -421,8 +443,8 @@ class PaginatingControl extends Control public function loadState(array $params): void { - parent::loadState($params); // εδώ ορίζεται η σελίδα $this->page - // ακολουθεί τον έλεγχο της τιμής του χρήστη: + parent::loadState($params); // εδώ ορίζεται το $this->page + // ακολουθεί ο έλεγχος της τιμής: if ($this->page < 1) { $this->error(); } @@ -430,27 +452,27 @@ class PaginatingControl extends Control } ``` -Η αντίθετη διαδικασία, δηλαδή η συλλογή τιμών από persistent properites, αντιμετωπίζεται από τη μέθοδο `saveState()`. +Η αντίστροφη διαδικασία, δηλαδή η συλλογή τιμών από τις persistent properties, γίνεται από τη μέθοδο `saveState()`. -Σήματα σε βάθος .[#toc-signals-in-depth] ----------------------------------------- +Σήματα σε Βάθος +--------------- -Ένα σήμα προκαλεί επαναφόρτωση της σελίδας όπως και το αρχικό αίτημα (με εξαίρεση το AJAX) και καλεί τη μέθοδο `signalReceived($signal)` της οποίας η προεπιλεγμένη υλοποίηση στην κλάση `Nette\Application\UI\Component` προσπαθεί να καλέσει μια μέθοδο που αποτελείται από τις λέξεις `handle{Signal}`. Η περαιτέρω επεξεργασία βασίζεται στο συγκεκριμένο αντικείμενο. Τα αντικείμενα που είναι απόγονοι του `Component` (δηλ. `Control` και `Presenter`) προσπαθούν να καλέσουν το `handle{Signal}` με σχετικές παραμέτρους. +Ένα signal προκαλεί την επαναφόρτωση της σελίδας ακριβώς όπως στην αρχική αίτηση (εκτός από την περίπτωση που καλείται μέσω AJAX) και καλεί τη μέθοδο `signalReceived($signal)`, της οποίας η προεπιλεγμένη υλοποίηση στην κλάση `Nette\Application\UI\Component` προσπαθεί να καλέσει μια μέθοδο που αποτελείται από τις λέξεις `handle{signal}`. Η περαιτέρω επεξεργασία εξαρτάται από το συγκεκριμένο αντικείμενο. Τα αντικείμενα που κληρονομούν από το `Component` (δηλαδή `Control` και `Presenter`) αντιδρούν προσπαθώντας να καλέσουν τη μέθοδο `handle{signal}` με τις κατάλληλες παραμέτρους. -Με άλλα λόγια: λαμβάνεται ο ορισμός της μεθόδου `handle{Signal}` και όλες οι παράμετροι που ελήφθησαν στην αίτηση αντιστοιχίζονται με τις παραμέτρους της μεθόδου. Αυτό σημαίνει ότι η παράμετρος `id` από τη διεύθυνση URL αντιστοιχίζεται με την παράμετρο της μεθόδου `$id`, η `something` με την `$something` κ.ο.κ. Και αν η μέθοδος δεν υπάρχει, η μέθοδος `signalReceived` πετάει [μια εξαίρεση |api:Nette\Application\UI\BadSignalException]. +Με άλλα λόγια: λαμβάνεται ο ορισμός της συνάρτησης `handle{signal}` και όλες οι παράμετροι που ήρθαν με την αίτηση, και στα ορίσματα αντιστοιχίζονται οι παράμετροι από τη διεύθυνση URL με βάση το όνομα και γίνεται προσπάθεια κλήσης της συγκεκριμένης μεθόδου. Για παράδειγμα, ως παράμετρος `$id` μεταβιβάζεται η τιμή από την παράμετρο `id` στη διεύθυνση URL, ως `$something` μεταβιβάζεται το `something` από τη διεύθυνση URL, κ.λπ. Και αν η μέθοδος δεν υπάρχει, η μέθοδος `signalReceived` δημιουργεί μια [εξαίρεση |api:Nette\Application\UI\BadSignalException]. -Το σήμα μπορεί να ληφθεί από οποιοδήποτε συστατικό, παρουσιαστή αντικειμένου που υλοποιεί τη διεπαφή `SignalReceiver` εάν είναι συνδεδεμένο με το δέντρο συστατικών. +Ένα signal μπορεί να ληφθεί από οποιοδήποτε component, presenter ή αντικείμενο που υλοποιεί το interface `SignalReceiver` και είναι συνδεδεμένο στο δέντρο των components. -Οι κύριοι δέκτες σημάτων είναι το `Presenters` και τα οπτικά συστατικά που επεκτείνουν το `Control`. Ένα σήμα είναι ένα σημάδι για ένα αντικείμενο ότι πρέπει να κάνει κάτι - η δημοσκόπηση μετράει την ψήφο του χρήστη, το πλαίσιο με τις ειδήσεις πρέπει να ξεδιπλωθεί, η φόρμα στάλθηκε και πρέπει να επεξεργαστεί τα δεδομένα κ.ο.κ. +Οι κύριοι παραλήπτες signals θα είναι οι `Presenters` και τα οπτικά components που κληρονομούν από το `Control`. Ένα signal προορίζεται να χρησιμεύσει ως ένδειξη για ένα αντικείμενο ότι πρέπει να κάνει κάτι – μια δημοσκόπηση πρέπει να καταμετρήσει μια ψήφο από έναν χρήστη, ένα μπλοκ με ειδήσεις πρέπει να επεκταθεί και να εμφανίσει διπλάσιες ειδήσεις, μια φόρμα υποβλήθηκε και πρέπει να επεξεργαστεί τα δεδομένα, και ούτω καθεξής. -Η διεύθυνση URL για το σήμα δημιουργείται με τη χρήση της μεθόδου [Component::link() |api:Nette\Application\UI\Component::link()]. Ως παράμετρος `$destination` περνάμε το string `{signal}!` και ως `$args` έναν πίνακα με τα ορίσματα που θέλουμε να περάσουμε στον χειριστή του σήματος. Οι παράμετροι του σήματος συνδέονται με τη διεύθυνση URL του τρέχοντος παρουσιαστή/προβολής. **Η παράμετρος `?do` στη διεύθυνση URL καθορίζει το σήμα που καλείται.** +Η διεύθυνση URL για ένα signal δημιουργείται χρησιμοποιώντας τη μέθοδο [Component::link() |api:Nette\Application\UI\Component::link()]. Ως παράμετρο `$destination` μεταβιβάζουμε τη συμβολοσειρά `{signal}!` και ως `$args` έναν πίνακα ορισμάτων που θέλουμε να μεταβιβάσουμε στο signal. Το signal καλείται πάντα στον τρέχοντα presenter και action με τις τρέχουσες παραμέτρους, οι παράμετροι του signal απλώς προστίθενται. Επιπλέον, προστίθεται αμέσως στην αρχή η **παράμετρος `?do`, η οποία καθορίζει το signal**. -Η μορφή της είναι `{signal}` ή `{signalReceiver}-{signal}`. `{signalReceiver}` είναι το όνομα του στοιχείου στον παρουσιαστή. Αυτός είναι ο λόγος για τον οποίο η παύλα (ανακριβώς παύλα) δεν μπορεί να υπάρχει στο όνομα των συστατικών - χρησιμοποιείται για να χωρίσει το όνομα του συστατικού και του σήματος, αλλά είναι δυνατόν να συνθέσετε πολλά συστατικά. +Η μορφή του είναι είτε `{signal}`, είτε `{signalReceiver}-{signal}`. Το `{signalReceiver}` είναι το όνομα του component στον presenter. Γι' αυτό δεν μπορεί να υπάρχει παύλα στο όνομα του component – χρησιμοποιείται για να διαχωρίσει το όνομα του component και του signal, ωστόσο είναι δυνατό να ενσωματωθούν έτσι πολλά components. -Η μέθοδος [isSignalReceiver() |api:Nette\Application\UI\Presenter::isSignalReceiver()] επαληθεύει αν ένα συστατικό (πρώτο όρισμα) είναι δέκτης ενός σήματος (δεύτερο όρισμα). Το δεύτερο όρισμα μπορεί να παραλειφθεί - τότε διαπιστώνει αν το συστατικό είναι δέκτης οποιουδήποτε σήματος. Εάν το δεύτερο όρισμα είναι `true`, ελέγχει εάν το συστατικό ή οι απόγονοί του είναι δέκτες ενός σήματος. +Η μέθοδος [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] ελέγχει εάν το component (πρώτο όρισμα) είναι ο παραλήπτης του signal (δεύτερο όρισμα). Μπορούμε να παραλείψουμε το δεύτερο όρισμα – τότε ελέγχει εάν το component είναι ο παραλήπτης οποιουδήποτε signal. Ως δεύτερη παράμετρο, μπορείτε να καθορίσετε `true` για να επαληθεύσετε εάν ο παραλήπτης δεν είναι μόνο το αναφερόμενο component, αλλά και οποιοσδήποτε απόγονός του. -Σε οποιαδήποτε φάση που προηγείται του `handle{Signal}` μπορεί να εκτελεστεί το σήμα χειροκίνητα καλώντας τη μέθοδο [processSignal() |api:Nette\Application\UI\Presenter::processSignal()] η οποία αναλαμβάνει την ευθύνη για την εκτέλεση του σήματος. Παίρνει το συστατικό δέκτη (αν δεν έχει οριστεί είναι ο ίδιος ο παρουσιαστής) και του στέλνει το σήμα. +Σε οποιαδήποτε φάση πριν από το `handle{signal}`, μπορούμε να εκτελέσουμε το signal χειροκίνητα καλώντας τη μέθοδο [processSignal()|api:Nette\Application\UI\Presenter::processSignal()], η οποία αναλαμβάνει τη διαχείριση του signal – παίρνει το component που έχει οριστεί ως παραλήπτης του signal (αν δεν έχει οριστεί παραλήπτης signal, είναι ο ίδιος ο presenter) και του στέλνει το signal. Παράδειγμα: @@ -460,4 +482,4 @@ if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, ' } ``` -Το σήμα εκτελείται πρόωρα και δεν θα κληθεί ξανά. +Με αυτόν τον τρόπο, το signal εκτελείται πρόωρα και δεν θα κληθεί ξανά. diff --git a/application/el/configuration.texy b/application/el/configuration.texy index dbd28b0c35..c97ce203af 100644 --- a/application/el/configuration.texy +++ b/application/el/configuration.texy @@ -1,58 +1,71 @@ -Ρύθμιση της εφαρμογής -********************* +Διαμόρφωση εφαρμογών +******************** .[perex] -Επισκόπηση των επιλογών διαμόρφωσης για την εφαρμογή Nette. +Επισκόπηση των επιλογών διαμόρφωσης για τις Εφαρμογές Nette. -Εφαρμογή .[#toc-application] -============================ +Application +=========== ```neon application: - # δείχνει τον πίνακα "Nette Application" στο Tracy BlueScreen? - debugger: ... # (bool) προεπιλογή true + # εμφάνιση του πίνακα "Nette Application" στο Tracy BlueScreen; + debugger: ... # (bool) προεπιλογή είναι true - # θα καλείται ο παρουσιαστής σφαλμάτων σε περίπτωση σφάλματος; - catchExceptions: ... # (bool) προεπιλεγμένη τιμή true σε κατάσταση παραγωγής + # θα κληθεί ο error-presenter σε περίπτωση σφάλματος; + # ισχύει μόνο σε κατάσταση ανάπτυξης + catchExceptions: ... # (bool) προεπιλογή είναι true # όνομα του error-presenter - errorPresenter: Error # (string) προεπιλογή 'Nette:Error' + errorPresenter: Error # (string|array) προεπιλογή είναι 'Nette:Error' - # ορίζει τους κανόνες για την επίλυση του ονόματος του παρουσιαστή σε μια κλάση + # ορίζει ψευδώνυμα για presenters και actions + aliases: ... + + # ορίζει κανόνες για τη μετάφραση του ονόματος του presenter σε κλάση mapping: ... - # οι κακοί σύνδεσμοι δημιουργούν προειδοποιήσεις; - # έχει αποτέλεσμα μόνο στη λειτουργία προγραμματιστή - silentLinks: ... # (bool) προεπιλογή σε false + # οι μη έγκυροι σύνδεσμοι δεν δημιουργούν προειδοποιήσεις; + # ισχύει μόνο σε κατάσταση ανάπτυξης + silentLinks: ... # (bool) προεπιλογή είναι false ``` -Επειδή οι παρουσιαστές σφαλμάτων δεν καλούνται εξ ορισμού σε κατάσταση ανάπτυξης και τα σφάλματα εμφανίζονται από το Tracy, η αλλαγή της τιμής `catchExceptions` σε `true` βοηθάει στην επαλήθευση της σωστής λειτουργίας των παρουσιαστών σφαλμάτων κατά την ανάπτυξη. +Από την έκδοση `nette/application` 3.2, μπορείτε να ορίσετε ένα ζεύγος error-presenters: -Η επιλογή `silentLinks` καθορίζει τον τρόπο με τον οποίο η Nette συμπεριφέρεται στη λειτουργία ανάπτυξης όταν η δημιουργία συνδέσμων αποτυγχάνει (για παράδειγμα, επειδή δεν υπάρχει παρουσιαστής κ.λπ.). Η προεπιλεγμένη τιμή `false` σημαίνει ότι η Nette ενεργοποιεί το `E_USER_WARNING`. Η ρύθμιση σε `true` καταστέλλει αυτό το μήνυμα σφάλματος. Σε περιβάλλον παραγωγής, το `E_USER_WARNING` ενεργοποιείται πάντα. Μπορούμε επίσης να επηρεάσουμε αυτή τη συμπεριφορά θέτοντας τη μεταβλητή του παρουσιαστή [$invalidLinkMode |creating-links#Invalid Links]. +```neon +application: + errorPresenter: + 4xx: Error4xx # για την εξαίρεση Nette\Application\BadRequestException + 5xx: Error5xx # για άλλες εξαιρέσεις +``` -Η [αντιστοίχιση ορίζει τους κανόνες |modules#mapping] με τους οποίους το όνομα της κλάσης προκύπτει από το όνομα του παρουσιαστή. +Η επιλογή `silentLinks` καθορίζει πώς συμπεριφέρεται το Nette στην κατάσταση ανάπτυξης όταν η δημιουργία ενός συνδέσμου αποτυγχάνει (για παράδειγμα, επειδή ο presenter δεν υπάρχει, κ.λπ.). Η προεπιλεγμένη τιμή `false` σημαίνει ότι το Nette θα δημιουργήσει ένα σφάλμα `E_USER_WARNING`. Η ρύθμιση σε `true` θα καταστείλει αυτό το μήνυμα σφάλματος. Στο περιβάλλον παραγωγής, το `E_USER_WARNING` δημιουργείται πάντα. Αυτή η συμπεριφορά μπορεί επίσης να ελεγχθεί ορίζοντας τη μεταβλητή του presenter [$invalidLinkMode |creating-links#Μη Έγκυροι Σύνδεσμοι]. +Τα [Ψευδώνυμα απλοποιούν τη σύνδεση |creating-links#Ψευδώνυμα] σε συχνά χρησιμοποιούμενους presenters. -Αυτόματη εγγραφή παρουσιαστών .[#toc-automatic-registration-of-presenters] --------------------------------------------------------------------------- +Η [Αντιστοίχιση ορίζει κανόνες |directory-structure#Αντιστοίχιση Presenters], σύμφωνα με τους οποίους το όνομα της κλάσης προκύπτει από το όνομα του presenter. -Η Nette προσθέτει αυτόματα τους παρουσιαστές ως υπηρεσίες στο δοχείο DI, γεγονός που επιταχύνει σημαντικά τη δημιουργία τους. Ο τρόπος με τον οποίο η Nette βρίσκει τους παρουσιαστές μπορεί να ρυθμιστεί: + +Αυτόματη καταχώρηση presenters +------------------------------ + +Το Nette προσθέτει αυτόματα τους presenters ως υπηρεσίες στο DI container, γεγονός που επιταχύνει σημαντικά τη δημιουργία τους. Ο τρόπος με τον οποίο το Nette βρίσκει τους presenters μπορεί να διαμορφωθεί: ```neon application: - # να αναζητήσετε παρουσιαστές στο χάρτη τάξεων του Composer; - scanComposer: ... # (bool) προεπιλογή true + # αναζήτηση presenters στο Composer class map; + scanComposer: ... # (bool) προεπιλογή είναι true - # μια μάσκα που πρέπει να ταιριάζει με το όνομα της κλάσης και του αρχείου - scanFilter: ... # (string) προεπιλογή '*Presenter' + # μάσκα που πρέπει να ταιριάζει με το όνομα της κλάσης και του αρχείου + scanFilter: ... # (string) προεπιλογή είναι '*Presenter' - # σε ποιους καταλόγους να αναζητηθούν οι παρουσιαστές; - scanDirs: # (string[]|false) προεπιλογή '%appDir%' + # σε ποιους καταλόγους να αναζητηθούν οι presenters; + scanDirs: # (string[]|false) προεπιλογή είναι '%appDir%' - %vendorDir%/mymodule ``` -Οι κατάλογοι που παρατίθενται στο `scanDirs` δεν αντικαθιστούν την προεπιλεγμένη τιμή `%appDir%`, αλλά τη συμπληρώνουν, οπότε το `scanDirs` θα περιέχει και τα δύο μονοπάτια `%appDir%` και `%vendorDir%/mymodule`. Αν θέλουμε να αντικαταστήσουμε τον προεπιλεγμένο κατάλογο, χρησιμοποιούμε [θαυμαστικό |dependency-injection:configuration#Merging]: +Οι κατάλογοι που αναφέρονται στο `scanDirs` δεν αντικαθιστούν την προεπιλεγμένη τιμή `%appDir%`, αλλά την συμπληρώνουν, οπότε το `scanDirs` θα περιέχει και τις δύο διαδρομές `%appDir%` και `%vendorDir%/mymodule`. Αν θέλουμε να παραλείψουμε τον προεπιλεγμένο κατάλογο, χρησιμοποιούμε ένα [θαυμαστικό |dependency-injection:configuration#Συγχώνευση], το οποίο αντικαθιστά την τιμή: ```neon application: @@ -60,64 +73,73 @@ application: - %vendorDir%/mymodule ``` -Η σάρωση καταλόγων μπορεί να απενεργοποιηθεί με τη ρύθμιση false. Δεν συνιστούμε την πλήρη καταστολή της αυτόματης προσθήκης παρουσιαστών, διαφορετικά θα μειωθεί η απόδοση της εφαρμογής. +Η σάρωση καταλόγων μπορεί να απενεργοποιηθεί καθορίζοντας την τιμή false. Δεν συνιστούμε την πλήρη καταστολή της αυτόματης προσθήκης presenters, καθώς αυτό θα μειώσει την απόδοση της εφαρμογής. -Latte .[#toc-latte] -=================== +Templates Latte +=============== -Αυτή η ρύθμιση επηρεάζει συνολικά τη συμπεριφορά του Latte σε συστατικά και παρουσιαστές. +Με αυτή τη ρύθμιση, μπορείτε να επηρεάσετε καθολικά τη συμπεριφορά του Latte στα components και τους presenters. ```neon latte: - # εμφανίζει τον πίνακα Latte στη γραμμή Tracy για το κύριο πρότυπο (true) ή για όλα τα στοιχεία (all); - debugger: ... # (true|false|'all') προεπιλογή true + # εμφάνιση του πίνακα Latte στο Tracy Bar για το κύριο template (true) ή όλα τα components (all); + debugger: ... # (true|false|'all') προεπιλογή είναι true + + # δημιουργεί templates με την κεφαλίδα declare(strict_types=1) + strictTypes: ... # (bool) προεπιλογή είναι false - # παράγει πρότυπα με declare(strict_types=1) - strictTypes: ... # (bool) προεπιλογή σε false + # ενεργοποιεί την [κατάσταση αυστηρού parser |latte:develop#striktní režim] + strictParsing: ... # (bool) προεπιλογή είναι false - # κλάση του $this->template - templateClass: App\MyTemplateClass # προεπιλογή σε Nette\Bridges\ApplicationLatte\DefaultTemplate + # ενεργοποιεί τον [έλεγχο του παραγόμενου κώδικα |latte:develop#Kontrola vygenerovaného kódu] + phpLinter: ... # (string) προεπιλογή είναι null + + # ορίζει το locale + locale: cs_CZ # (string) προεπιλογή είναι null + + # κλάση του αντικειμένου $this->template + templateClass: App\MyTemplateClass # προεπιλογή είναι Nette\Bridges\ApplicationLatte\DefaultTemplate ``` -Αν χρησιμοποιείτε την έκδοση 3 του Latte, μπορείτε να προσθέσετε νέα [επέκταση |latte:creating-extension] χρησιμοποιώντας: +Αν χρησιμοποιείτε την έκδοση 3 του Latte, μπορείτε να προσθέσετε νέες [επεκτάσεις |latte:extending-latte#Latte Extension] χρησιμοποιώντας: ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -/--comment - - - - +Αν χρησιμοποιείτε την έκδοση 2 του Latte, μπορείτε να καταχωρήσετε νέα tags (macros) είτε καθορίζοντας το όνομα της κλάσης είτε με αναφορά σε μια υπηρεσία. Ως προεπιλογή, καλείται η μέθοδος `install()`, αλλά αυτό μπορεί να αλλάξει καθορίζοντας το όνομα μιας άλλης μεθόδου: +```neon +latte: + # καταχώρηση προσαρμοσμένων Latte tags + macros: + - App\MyLatteMacros::register # στατική μέθοδος, όνομα κλάσης ή callable + - @App\MyLatteMacrosFactory # υπηρεσία με μέθοδο install() + - @App\MyLatteMacrosFactory::register # υπηρεσία με μέθοδο register() + +services: + - App\MyLatteMacrosFactory +``` - - - - -\-- - - -Δρομολόγηση .[#toc-routing] -=========================== +Δρομολόγηση +=========== Βασικές ρυθμίσεις: ```neon routing: - # εμφανίζει πίνακα δρομολόγησης στο Tracy Bar? - debugger: ... # (bool) προεπιλογή true + # εμφάνιση του πίνακα δρομολόγησης στο Tracy Bar; + debugger: ... # (bool) προεπιλογή είναι true - # για να σειριοποιήσετε το δρομολογητή σε δοχείο DI? - cache: ... # (bool) προεπιλογή σε false + # σειριοποιεί τον router στο DI container + cache: ... # (bool) προεπιλογή είναι false ``` -Router ορίζεται συνήθως στην κλάση [RouterFactory |routing#Route Collection]. Εναλλακτικά, οι δρομολογήσεις μπορούν επίσης να οριστούν στη διαμόρφωση χρησιμοποιώντας ζεύγη `mask: action`, αλλά αυτή η μέθοδος δεν προσφέρει τόσο μεγάλη ποικιλία ρυθμίσεων: +Η δρομολόγηση συνήθως ορίζεται στην κλάση [RouterFactory |routing#Συλλογή διαδρομών]. Εναλλακτικά, οι διαδρομές (routes) μπορούν επίσης να οριστούν στη διαμόρφωση χρησιμοποιώντας ζεύγη `mask: action`, αλλά αυτή η μέθοδος δεν προσφέρει τόσο μεγάλη ευελιξία στις ρυθμίσεις: ```neon routing: @@ -127,8 +149,8 @@ routing: ``` -Σταθερές .[#toc-constants] -========================== +Σταθερές +======== Δημιουργία σταθερών PHP. @@ -137,18 +159,33 @@ constants: Foobar: 'baz' ``` -Η σταθερά `Foobar` θα δημιουργηθεί μετά την εκκίνηση. +Μετά την εκκίνηση της εφαρμογής, θα δημιουργηθεί η σταθερά `Foobar`. .[note] -Οι σταθερές δεν πρέπει να χρησιμεύουν ως παγκόσμια διαθέσιμες μεταβλητές. Για να περάσετε τιμές σε αντικείμενα, χρησιμοποιήστε το [dependency injection |dependency-injection:passing-dependencies]. +Οι σταθερές δεν πρέπει να χρησιμεύουν ως κάποιου είδους καθολικά διαθέσιμες μεταβλητές. Για τη μεταβίβαση τιμών σε αντικείμενα, χρησιμοποιήστε το [dependency injection |dependency-injection:passing-dependencies]. PHP === -Μπορείτε να ορίσετε οδηγίες PHP. Μια επισκόπηση όλων των οδηγιών μπορείτε να βρείτε στο [php |https://www.php.net/manual/en/ini.list.php]. +Ρύθμιση οδηγιών PHP. Μια επισκόπηση όλων των οδηγιών μπορείτε να βρείτε στο [php.net |https://www.php.net/manual/en/ini.list.php]. ```neon php: date.timezone: Europe/Prague ``` + + +Υπηρεσίες DI +============ + +Αυτές οι υπηρεσίες προστίθενται στο DI container: + +| Όνομα | Τύπος | Περιγραφή +|---------------------------------------------------------- +| `application.application` | [api:Nette\Application\Application] | [εκκινητής ολόκληρης της εφαρμογής |how-it-works#Nette Application] +| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] +| `application.presenterFactory` | [api:Nette\Application\PresenterFactory] | factory για presenters +| `application.###` | [api:Nette\Application\UI\Presenter] | μεμονωμένοι presenters +| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | factory αντικειμένου `Latte\Engine` +| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | factory για [`$this->template` |templates] diff --git a/application/el/creating-links.texy b/application/el/creating-links.texy index e36d982225..bd357844e1 100644 --- a/application/el/creating-links.texy +++ b/application/el/creating-links.texy @@ -3,158 +3,158 @@
    -Η δημιουργία συνδέσμων στη Nette είναι τόσο εύκολη όσο το να δείξετε με το δάχτυλο. Απλά δείξτε και το πλαίσιο θα κάνει όλη τη δουλειά για εσάς. Θα σας δείξουμε: +Η δημιουργία συνδέσμων στο Nette είναι τόσο απλή όσο το να δείχνεις με το δάχτυλο. Απλά στοχεύστε και το framework θα κάνει όλη τη δουλειά για εσάς. Θα δείξουμε: -- πώς να δημιουργείτε συνδέσμους σε πρότυπα και αλλού -- πώς να διακρίνετε έναν σύνδεσμο στην τρέχουσα σελίδα -- τι γίνεται με τους άκυρους συνδέσμους +- πώς να δημιουργείτε συνδέσμους σε templates και αλλού +- πώς να διακρίνετε έναν σύνδεσμο προς την τρέχουσα σελίδα +- τι να κάνετε με τους μη έγκυρους συνδέσμους
    -Χάρη στην [αμφίδρομη δρομολόγηση |routing], δεν θα χρειαστεί ποτέ να κωδικοποιήσετε σκληρά τις διευθύνσεις URL της εφαρμογής στα πρότυπα ή στον κώδικα, οι οποίες μπορεί να αλλάξουν αργότερα ή να είναι περίπλοκες στη σύνταξη. Απλώς καθορίστε τον παρουσιαστή και την ενέργεια στο σύνδεσμο, περάστε τυχόν παραμέτρους και το πλαίσιο θα δημιουργήσει μόνο του τη διεύθυνση URL. Στην πραγματικότητα, μοιάζει πολύ με την κλήση μιας συνάρτησης. Θα σας αρέσει. +Χάρη στην [αμφίδρομη δρομολόγηση |routing], δεν θα χρειαστεί ποτέ να γράψετε σκληρά κωδικοποιημένες διευθύνσεις URL της εφαρμογής σας σε templates ή κώδικα, οι οποίες μπορεί να αλλάξουν αργότερα, ή να τις συνθέσετε πολύπλοκα. Στον σύνδεσμο, αρκεί να καθορίσετε τον presenter και την action, να περάσετε τυχόν παραμέτρους και το framework θα δημιουργήσει το URL μόνο του. Στην πραγματικότητα, είναι πολύ παρόμοιο με την κλήση μιας συνάρτησης. Αυτό θα σας αρέσει. -Στο πρότυπο παρουσιαστή .[#toc-in-the-presenter-template] -========================================================= +Στο Πρότυπο του Presenter +========================= -Τις περισσότερες φορές δημιουργούμε συνδέσμους σε πρότυπα και ένας μεγάλος βοηθός είναι το χαρακτηριστικό `n:href`: +Τις περισσότερες φορές δημιουργούμε συνδέσμους σε templates και ένα εξαιρετικό βοήθημα είναι το attribute `n:href`: ```latte -detail +λεπτομέρεια ``` -Σημειώστε, ότι αντί για το χαρακτηριστικό HTML `href` χρησιμοποιήσαμε το [n:attribute |latte:syntax#n:attributes] `n:href`. Η τιμή του δεν είναι ένα URL, όπως έχετε συνηθίσει με το χαρακτηριστικό `href`, αλλά το όνομα του παρουσιαστή και της ενέργειας. +Παρατηρήστε ότι αντί για το HTML attribute `href`, χρησιμοποιήσαμε το [n:attribute |latte:syntax#n:attributes] `n:href`. Η τιμή του δεν είναι ένα URL, όπως θα ήταν στην περίπτωση του attribute `href`, αλλά το όνομα του presenter και της action. -Το κλικ σε έναν σύνδεσμο είναι, απλά ειπωμένο, κάτι σαν την κλήση μιας μεθόδου `ProductPresenter::renderShow()`. Και αν αυτή έχει παραμέτρους στην υπογραφή της, μπορούμε να την καλέσουμε με ορίσματα: +Το κλικ σε έναν σύνδεσμο είναι, απλοποιημένα, κάτι σαν την κλήση της μεθόδου `ProductPresenter::renderShow()`. Και αν έχει παραμέτρους στην υπογραφή της, μπορούμε να την καλέσουμε με ορίσματα: ```latte -detail +λεπτομέρεια προϊόντος ``` -Είναι επίσης δυνατό να περάσουμε ονομαστικές παραμέτρους. Ο ακόλουθος σύνδεσμος περνάει την παράμετρο `lang` με την τιμή `en`: +Είναι επίσης δυνατό να περάσετε ονομασμένες παραμέτρους. Ο παρακάτω σύνδεσμος περνάει την παράμετρο `lang` με την τιμή `cs`: ```latte -detail +λεπτομέρεια προϊόντος ``` -Εάν η μέθοδος `ProductPresenter::renderShow()` δεν έχει στην υπογραφή της την `$lang`, μπορεί να διαβάσει την τιμή της παραμέτρου χρησιμοποιώντας την `$lang = $this->getParameter('lang')`. +Αν η μέθοδος `ProductPresenter::renderShow()` δεν έχει το `$lang` στην υπογραφή της, μπορεί να λάβει την τιμή της παραμέτρου χρησιμοποιώντας το `$lang = $this->getParameter('lang')` ή από την [property |presenters#Παράμετροι αιτήματος]. -Εάν οι παράμετροι είναι αποθηκευμένες σε πίνακα, μπορούν να επεκταθούν με τον τελεστή `...` (ή `(expand)` στο Latte 2.x): +Αν οι παράμετροι είναι αποθηκευμένες σε έναν πίνακα, μπορούν να επεκταθούν με τον τελεστή `...` (στο Latte 2.x με τον τελεστή `(expand)`): ```latte -{var $args = [$product->id, lang => en]} -detail +{var $args = [$product->id, lang => cs]} +λεπτομέρεια προϊόντος ``` -Οι λεγόμενες [μόνιμες παράμετροι |presenters#persistent parameters] περνούν επίσης αυτόματα στους συνδέσμους. +Στους συνδέσμους, μεταβιβάζονται επίσης αυτόματα οι λεγόμενες [persistent παράμετροι |presenters#Persistent παράμετροι]. -Το χαρακτηριστικό `n:href` είναι πολύ χρήσιμο για τις ετικέτες HTML ``. Αν θέλουμε να εκτυπώσουμε τον σύνδεσμο αλλού, για παράδειγμα στο κείμενο, χρησιμοποιούμε το `{link}`: +Το attribute `n:href` είναι πολύ χρήσιμο για τις ετικέτες HTML ``. Αν θέλουμε να εμφανίσουμε έναν σύνδεσμο αλλού, για παράδειγμα σε κείμενο, χρησιμοποιούμε το `{link}`: ```latte -URL is: {link Home:default} +Η διεύθυνση είναι: {link Home:default} ``` -Στον κώδικα .[#toc-in-the-code] -=============================== +Στον Κώδικα +=========== -Η μέθοδος `link()` χρησιμοποιείται για τη δημιουργία ενός συνδέσμου στον παρουσιαστή: +Για τη δημιουργία ενός συνδέσμου στον presenter, χρησιμοποιείται η μέθοδος `link()`: ```php $url = $this->link('Product:show', $product->id); ``` -Οι παράμετροι μπορούν επίσης να μεταβιβαστούν ως πίνακας όπου μπορούν επίσης να καθοριστούν ονομαστικές παράμετροι: +Οι παράμετροι μπορούν επίσης να περαστούν χρησιμοποιώντας έναν πίνακα, όπου μπορούν επίσης να καθοριστούν ονομασμένες παράμετροι: ```php $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); ``` -Οι σύνδεσμοι μπορούν να δημιουργηθούν και χωρίς παρουσιαστή, χρησιμοποιώντας το [LinkGenerator |#LinkGenerator] και τη μέθοδο `link()`. +Οι σύνδεσμοι μπορούν επίσης να δημιουργηθούν χωρίς presenter, γι' αυτό υπάρχει το [#LinkGenerator] και η μέθοδός του `link()`. -Σύνδεσμοι με παρουσιαστή .[#toc-links-to-presenter] -=================================================== +Σύνδεσμοι προς Presenter +======================== -Εάν ο στόχος του συνδέσμου είναι παρουσιαστής και δράση, έχει την εξής σύνταξη: +Αν ο στόχος του συνδέσμου είναι ένας presenter και μια action, έχει αυτή τη σύνταξη: ``` [//] [[[[:]module:]presenter:]action | this] [#fragment] ``` -Η μορφή υποστηρίζεται από όλες τις ετικέτες Latte και όλες τις μεθόδους παρουσιαστή που λειτουργούν με συνδέσμους, δηλαδή `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` και επίσης [LinkGenerator |#LinkGenerator]. Έτσι, ακόμη και αν στα παραδείγματα χρησιμοποιείται η `n:href`, θα μπορούσε να υπάρχει οποιαδήποτε από τις συναρτήσεις. +Η μορφή υποστηρίζεται από όλες τις ετικέτες Latte και όλες τις μεθόδους του presenter που λειτουργούν με συνδέσμους, δηλαδή `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` και επίσης το [#LinkGenerator]. Έτσι, ακόμα κι αν χρησιμοποιείται το `n:href` στα παραδείγματα, θα μπορούσε να είναι οποιαδήποτε από τις συναρτήσεις. Η βασική μορφή είναι επομένως `Presenter:action`: ```latte -home +αρχική σελίδα ``` -Αν συνδεθούμε με τη δράση του τρέχοντος παρουσιαστή, μπορούμε να παραλείψουμε το όνομά του: +Αν συνδέουμε σε μια action του τρέχοντος presenter, μπορούμε να παραλείψουμε το όνομά του: ```latte -home +αρχική σελίδα ``` -Αν η ενέργεια είναι `default`, μπορούμε να την παραλείψουμε, αλλά η άνω και κάτω τελεία πρέπει να παραμείνει: +Αν ο στόχος είναι η action `default`, μπορούμε να την παραλείψουμε, αλλά η άνω και κάτω τελεία πρέπει να παραμείνει: ```latte -home +αρχική σελίδα ``` -Οι σύνδεσμοι μπορούν επίσης να παραπέμπουν σε άλλες [ενότητες |modules]. Εδώ, οι σύνδεσμοι διακρίνονται σε σχετικούς με τις υποενότητες ή απόλυτους. Η αρχή είναι ανάλογη με τις διαδρομές δίσκου, μόνο που αντί για κάθετους υπάρχουν άνω και κάτω τελεία. Ας υποθέσουμε ότι ο πραγματικός παρουσιαστής είναι μέρος της ενότητας `Front`, τότε θα γράψουμε: +Οι σύνδεσμοι μπορούν επίσης να οδηγούν σε άλλα [modules |directory-structure#Presenters και Πρότυπα]. Εδώ, οι σύνδεσμοι διακρίνονται σε σχετικούς προς ένα ένθετο sub-module, ή απόλυτους. Η αρχή είναι ανάλογη με τις διαδρομές στο δίσκο, μόνο που αντί για κάθετες χρησιμοποιούνται άνω και κάτω τελείες. Ας υποθέσουμε ότι ο τρέχων presenter είναι μέρος του module `Front`, τότε γράφουμε: ```latte -link to Front:Shop:Product:show -link to Admin:Product:show +σύνδεσμος προς Front:Shop:Product:show +σύνδεσμος προς Admin:Product:show ``` -Μια ειδική περίπτωση είναι η [σύνδεση με τον εαυτό της |#Links to Current Page]. Εδώ θα γράψουμε `this` ως στόχο. +Μια ειδική περίπτωση είναι ένας σύνδεσμος [προς τον εαυτό του |#Σύνδεσμος προς την Τρέχουσα Σελίδα], όπου καθορίζουμε το `this` ως στόχο. ```latte -refresh +ανανέωση ``` -Μπορούμε να συνδεθούμε σε ένα συγκεκριμένο τμήμα της σελίδας HTML μέσω ενός λεγόμενου αποσπάσματος μετά το σύμβολο κατακερματισμού `#`: +Μπορούμε να συνδέσουμε σε ένα συγκεκριμένο τμήμα της σελίδας μέσω ενός λεγόμενου fragment μετά το σύμβολο δίεσης `#`: ```latte -link to Home:default and fragment #main +σύνδεσμος προς Home:default και fragment #main ``` -Απόλυτες διαδρομές .[#toc-absolute-paths] -========================================= +Απόλυτες Διαδρομές +================== -Οι σύνδεσμοι που δημιουργούνται από το `link()` ή το `n:href` είναι πάντα απόλυτες διαδρομές (δηλ. ξεκινούν με `/`), αλλά όχι απόλυτες διευθύνσεις URL με πρωτόκολλο και τομέα όπως `https://domain`. +Οι σύνδεσμοι που δημιουργούνται χρησιμοποιώντας το `link()` ή το `n:href` είναι πάντα απόλυτες διαδρομές (δηλαδή ξεκινούν με το σύμβολο `/`), αλλά όχι απόλυτες διευθύνσεις URL με πρωτόκολλο και domain όπως `https://domain`. -Για να δημιουργήσετε μια απόλυτη διεύθυνση URL, προσθέστε δύο κάθετους στην αρχή (π.χ. `n:href="//Home:"`). Ή μπορείτε να αλλάξετε τον παρουσιαστή ώστε να παράγει μόνο απόλυτους συνδέσμους, ρυθμίζοντας το `$this->absoluteUrls = true`. +Για να δημιουργήσετε μια απόλυτη διεύθυνση URL, προσθέστε δύο κάθετες στην αρχή (π.χ. `n:href="//Home:"`). Ή μπορείτε να αλλάξετε τον presenter ώστε να δημιουργεί μόνο απόλυτους συνδέσμους ορίζοντας `$this->absoluteUrls = true`. -Σύνδεσμος προς την τρέχουσα σελίδα .[#toc-link-to-current-page] -=============================================================== +Σύνδεσμος προς την Τρέχουσα Σελίδα +================================== -Ο στόχος `this` θα δημιουργήσει έναν σύνδεσμο προς την τρέχουσα σελίδα: +Ο στόχος `this` δημιουργεί έναν σύνδεσμο προς την τρέχουσα σελίδα: ```latte -refresh +ανανέωση ``` -Ταυτόχρονα, όλες οι παράμετροι που καθορίζονται στην υπογραφή της εντολής `render()` ή `action()` μεταφέρονται. Έτσι, αν βρισκόμαστε στις σελίδες `Product:show` και `id:123`, ο σύνδεσμος προς την `this` θα μεταφέρει και αυτή την παράμετρο. +Ταυτόχρονα, μεταβιβάζονται όλες οι παράμετροι που καθορίζονται στην υπογραφή της μεθόδου `action()` ή `render()`, αν η `action()` δεν έχει οριστεί. Έτσι, αν βρισκόμαστε στη σελίδα `Product:show` και `id: 123`, ο σύνδεσμος προς το `this` θα μεταβιβάσει και αυτή την παράμετρο. -Φυσικά, είναι δυνατόν να καθορίσετε τις παραμέτρους απευθείας: +Φυσικά, είναι δυνατό να καθορίσετε τις παραμέτρους απευθείας: ```latte -refresh +ανανέωση ``` -Η συνάρτηση `isLinkCurrent()` καθορίζει αν ο στόχος του συνδέσμου είναι ο ίδιος με την τρέχουσα σελίδα. Αυτό μπορεί να χρησιμοποιηθεί, για παράδειγμα, σε ένα πρότυπο για τη διαφοροποίηση των συνδέσμων κ.λπ. +Η συνάρτηση `isLinkCurrent()` ελέγχει εάν ο στόχος του συνδέσμου είναι ο ίδιος με την τρέχουσα σελίδα. Αυτό μπορεί να χρησιμοποιηθεί, για παράδειγμα, σε ένα template για τη διάκριση συνδέσμων κ.λπ. -Οι παράμετροι είναι οι ίδιες με αυτές της μεθόδου `link()`, αλλά είναι επίσης δυνατό να χρησιμοποιηθεί το μπαλαντέρ `*` αντί για μια συγκεκριμένη ενέργεια, που σημαίνει οποιαδήποτε ενέργεια του παρουσιαστή. +Οι παράμετροι είναι ίδιες με αυτές της μεθόδου `link()`, αλλά επιπλέον είναι δυνατό να καθορίσετε έναν χαρακτήρα μπαλαντέρ `*` αντί για μια συγκεκριμένη action, ο οποίος σημαίνει οποιαδήποτε action του συγκεκριμένου presenter. ```latte {if !isLinkCurrent('Admin:login')} - Přihlaste se + Σύνδεση {/if}
  • @@ -162,58 +162,58 @@ $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']);
  • ``` -Μια συντομευμένη μορφή μπορεί να χρησιμοποιηθεί σε συνδυασμό με το `n:href` σε ένα μόνο στοιχείο: +Σε συνδυασμό με το `n:href` σε ένα στοιχείο, μπορεί να χρησιμοποιηθεί μια συντομευμένη μορφή: ```latte -... +... ``` -Ο χαρακτήρας μπαλαντέρ `*` αντικαθιστά μόνο τη δράση του παρουσιαστή, όχι τον ίδιο τον παρουσιαστή. +Ο χαρακτήρας μπαλαντέρ `*` μπορεί να χρησιμοποιηθεί μόνο αντί για την action, όχι για τον presenter. -Για να μάθουμε αν βρισκόμαστε σε μια συγκεκριμένη ενότητα ή υποενότητά της μπορούμε να χρησιμοποιήσουμε τη συνάρτηση `isModuleCurrent(moduleName)`. +Για να ελέγξουμε εάν βρισκόμαστε σε ένα συγκεκριμένο module ή το sub-module του, χρησιμοποιούμε τη μέθοδο `isModuleCurrent(moduleName)`. ```latte -
  • +
  • ...
  • ``` -Σύνδεσμοι προς το Signal .[#toc-links-to-signal] -================================================ +Σύνδεσμοι προς Σήμα +=================== -Ο στόχος του συνδέσμου μπορεί να είναι όχι μόνο ο παρουσιαστής και η ενέργεια, αλλά και το [σήμα |components#Signal] (καλούν τη μέθοδο `handle()`). Η σύνταξη έχει ως εξής: +Ο στόχος ενός συνδέσμου δεν χρειάζεται να είναι μόνο ένας presenter και μια action, αλλά μπορεί επίσης να είναι ένα [signal |components#Σήμα] (καλούν τη μέθοδο `handle()`). Τότε η σύνταξη είναι η εξής: ``` [//] [sub-component:]signal! [#fragment] ``` -Το σήμα διακρίνεται επομένως με θαυμαστικό: +Το signal διακρίνεται λοιπόν από το θαυμαστικό: ```latte signal ``` -Μπορείτε επίσης να δημιουργήσετε έναν σύνδεσμο προς το σήμα του υποστοιχείου (ή υπο-υποστοιχείου): +Μπορείτε επίσης να δημιουργήσετε έναν σύνδεσμο προς το signal ενός sub-component (ή sub-sub-component): ```latte signal ``` -Σύνδεσμοι στο συστατικό .[#toc-links-in-component] -================================================== +Σύνδεσμοι σε Component +====================== -Επειδή [τα συστατικά |components] είναι ξεχωριστές επαναχρησιμοποιήσιμες μονάδες που δεν πρέπει να έχουν καμία σχέση με τους περιβάλλοντες παρουσιαστές, οι σύνδεσμοι λειτουργούν λίγο διαφορετικά. Το χαρακτηριστικό Latte `n:href` και η ετικέτα `{link}` και οι μέθοδοι συστατικών όπως η `link()` και άλλες θεωρούν πάντα τον στόχο **ως το όνομα του σήματος**. Επομένως, δεν είναι απαραίτητο να χρησιμοποιείται θαυμαστικό: +Επειδή τα [components|components] είναι ανεξάρτητες, επαναχρησιμοποιήσιμες μονάδες που δεν θα πρέπει να έχουν καμία σύνδεση με τους γύρω presenters, οι σύνδεσμοι λειτουργούν λίγο διαφορετικά εδώ. Το attribute Latte `n:href` και η ετικέτα `{link}` καθώς και οι μέθοδοι του component όπως το `link()` και άλλες θεωρούν τον στόχο του συνδέσμου **πάντα ως το όνομα του signal**. Επομένως, δεν είναι καν απαραίτητο να συμπεριλάβετε το θαυμαστικό: ```latte -signal, not an action +signal, όχι action ``` -Αν θέλουμε να συνδέσουμε με παρουσιαστές στο πρότυπο συστατικού, χρησιμοποιούμε την ετικέτα `{plink}`: +Αν θέλαμε να συνδέσουμε σε presenters στο template του component, θα χρησιμοποιούσαμε την ετικέτα `{plink}`: ```latte -home +αρχική ``` ή στον κώδικα @@ -223,17 +223,41 @@ $this->getPresenter()->link('Home:default') ``` -Άκυροι σύνδεσμοι .[#toc-invalid-links] -====================================== +Ψευδώνυμα .{data-version:v3.2.2} +================================ -Μπορεί να συμβεί να δημιουργήσουμε έναν άκυρο σύνδεσμο - είτε επειδή αναφέρεται σε έναν μη υπάρχοντα παρουσιαστή, είτε επειδή περνάει περισσότερες παραμέτρους από αυτές που λαμβάνει η μέθοδος-στόχος στην υπογραφή της, είτε όταν δεν μπορεί να δημιουργηθεί URL για τη στοχευόμενη ενέργεια. Το τι πρέπει να γίνει με τους άκυρους συνδέσμους καθορίζεται από τη στατική μεταβλητή `Presenter::$invalidLinkMode`. Μπορεί να έχει μία από αυτές τις τιμές (σταθερές): +Μερικές φορές μπορεί να είναι χρήσιμο να αντιστοιχίσετε ένα εύκολα απομνημονεύσιμο ψευδώνυμο σε ένα ζεύγος Presenter:action. Για παράδειγμα, να ονομάσετε την αρχική σελίδα `Front:Home:default` απλά ως `home` ή το `Admin:Dashboard:default` ως `admin`. -- `Presenter::InvalidLinkSilent` - σιωπηλή λειτουργία, επιστρέφει το σύμβολο `#` ως διεύθυνση URL -- `Presenter::InvalidLinkWarning` - θα παραχθεί E_USER_WARNING -- `Presenter::InvalidLinkTextual` - οπτική προειδοποίηση, το κείμενο σφάλματος εμφανίζεται στο σύνδεσμο -- `Presenter::InvalidLinkException` - θα εκπέμπεται η εξαίρεση InvalidLinkException +Τα ψευδώνυμα ορίζονται στη [διαμόρφωση|configuration] κάτω από το κλειδί `application › aliases`: -Η προεπιλεγμένη ρύθμιση σε κατάσταση παραγωγής είναι `InvalidLinkWarning` και σε κατάσταση ανάπτυξης είναι `InvalidLinkWarning | InvalidLinkTextual`. Το `InvalidLinkWarning` δεν τερματίζει το σενάριο στο περιβάλλον παραγωγής, αλλά η προειδοποίηση θα καταγραφεί. Στο περιβάλλον ανάπτυξης, το [Tracy |tracy:] θα αναχαιτίσει την προειδοποίηση και θα εμφανίσει την μπλε οθόνη σφάλματος. Εάν έχει οριστεί το `InvalidLinkTextual`, ο παρουσιαστής και τα στοιχεία επιστρέφουν μήνυμα σφάλματος ως URL που ξεκινά με το `#error:`. Για να κάνουμε τέτοιους συνδέσμους ορατούς, μπορούμε να προσθέσουμε έναν κανόνα CSS στο φύλλο στυλ μας: +```neon +application: + aliases: + home: Front:Home:default + admin: Admin:Dashboard:default + sign: Front:Sign:in +``` + +Στους συνδέσμους, γράφονται στη συνέχεια χρησιμοποιώντας το σύμβολο @, για παράδειγμα: + +```latte +διαχείριση +``` + +Υποστηρίζονται επίσης σε όλες τις μεθόδους που λειτουργούν με συνδέσμους, όπως το `redirect()` και παρόμοιες. + + +Μη Έγκυροι Σύνδεσμοι +==================== + +Μπορεί να συμβεί να δημιουργήσουμε έναν μη έγκυρο σύνδεσμο - είτε επειδή οδηγεί σε έναν ανύπαρκτο presenter, είτε επειδή περνάει περισσότερες παραμέτρους από όσες δέχεται η μέθοδος προορισμού στην υπογραφή της, είτε όταν δεν μπορεί να δημιουργηθεί URL για την action προορισμού. Ο τρόπος χειρισμού των μη έγκυρων συνδέσμων καθορίζεται από τη στατική μεταβλητή `Presenter::$invalidLinkMode`. Αυτή μπορεί να πάρει έναν συνδυασμό αυτών των τιμών (σταθερών): + +- `Presenter::InvalidLinkSilent` - σιωπηλή λειτουργία, το σύμβολο # επιστρέφεται ως URL +- `Presenter::InvalidLinkWarning` - δημιουργείται μια προειδοποίηση E_USER_WARNING, η οποία θα καταγραφεί στη λειτουργία παραγωγής, αλλά δεν θα προκαλέσει διακοπή της εκτέλεσης του σεναρίου +- `Presenter::InvalidLinkTextual` - οπτική προειδοποίηση, εμφανίζει το σφάλμα απευθείας στον σύνδεσμο +- `Presenter::InvalidLinkException` - δημιουργείται η εξαίρεση InvalidLinkException + +Η προεπιλεγμένη ρύθμιση είναι `InvalidLinkWarning` στη λειτουργία παραγωγής και `InvalidLinkWarning | InvalidLinkTextual` στη λειτουργία ανάπτυξης. Το `InvalidLinkWarning` στο περιβάλλον παραγωγής δεν προκαλεί διακοπή του σεναρίου, αλλά η προειδοποίηση θα καταγραφεί. Στο περιβάλλον ανάπτυξης, το [Tracy |tracy:] το συλλαμβάνει και εμφανίζει ένα bluescreen. Το `InvalidLinkTextual` λειτουργεί επιστρέφοντας ένα μήνυμα σφάλματος ως URL, το οποίο ξεκινά με τους χαρακτήρες `#error:`. Για να κάνουμε τέτοιους συνδέσμους ορατούς με την πρώτη ματιά, προσθέτουμε στο CSS μας: ```css a[href^="#error:"] { @@ -242,7 +266,7 @@ a[href^="#error:"] { } ``` -Αν δεν θέλουμε να παράγονται προειδοποιήσεις στο περιβάλλον ανάπτυξης, μπορούμε να ενεργοποιήσουμε τη λειτουργία silent invalid link mode στη [ρύθμιση παραμέτρων |configuration]. +Αν δεν θέλουμε να δημιουργούνται προειδοποιήσεις στο περιβάλλον ανάπτυξης, μπορούμε να ορίσουμε τη σιωπηλή λειτουργία απευθείας στη [διαμόρφωση|configuration]. ```neon application: @@ -250,13 +274,13 @@ application: ``` -LinkGenerator .[#toc-linkgenerator] -=================================== +LinkGenerator +============= -Πώς να δημιουργήσετε συνδέσμους με τη μέθοδο `link()` comfort, αλλά χωρίς την παρουσία ενός παρουσιαστή; Γι' αυτό υπάρχει εδώ το [api:Nette\Application\LinkGenerator]. +Πώς να δημιουργήσετε συνδέσμους με παρόμοια άνεση όπως η μέθοδος `link()`, αλλά χωρίς την παρουσία ενός presenter; Γι' αυτό υπάρχει το [api:Nette\Application\LinkGenerator]. -LinkGenerator είναι μια υπηρεσία που μπορείτε να έχετε περάσει μέσω του κατασκευαστή και στη συνέχεια να δημιουργήσετε συνδέσμους χρησιμοποιώντας τη μέθοδό του `link()`. +Το LinkGenerator είναι μια υπηρεσία που μπορείτε να ζητήσετε να σας περάσει μέσω του constructor και στη συνέχεια να δημιουργήσετε συνδέσμους χρησιμοποιώντας τη μέθοδό του `link()`. -Υπάρχει μια διαφορά σε σχέση με τους παρουσιαστές. Το LinkGenerator δημιουργεί όλους τους συνδέσμους ως απόλυτες διευθύνσεις URL. Επιπλέον, δεν υπάρχει "τρέχων παρουσιαστής", οπότε δεν είναι δυνατόν να καθορίσετε μόνο το όνομα της ενέργειας `link('default')` ή τις σχετικές διαδρομές προς τις [ενότητες |modules]. +Υπάρχει μια διαφορά σε σύγκριση με τους presenters. Το LinkGenerator δημιουργεί όλους τους συνδέσμους απευθείας ως απόλυτες διευθύνσεις URL. Επιπλέον, δεν υπάρχει "τρέχων presenter", οπότε δεν μπορείτε να καθορίσετε μόνο το όνομα της action ως στόχο `link('default')` ή να καθορίσετε σχετικές διαδρομές προς τα modules. -Οι άκυροι σύνδεσμοι πάντα προκαλούν `Nette\Application\UI\InvalidLinkException`. +Οι μη έγκυροι σύνδεσμοι δημιουργούν πάντα την εξαίρεση `Nette\Application\UI\InvalidLinkException`. diff --git a/application/el/directory-structure.texy b/application/el/directory-structure.texy new file mode 100644 index 0000000000..c0a5c4906d --- /dev/null +++ b/application/el/directory-structure.texy @@ -0,0 +1,526 @@ +Δομή Καταλόγου της Εφαρμογής +**************************** + +
    + +Πώς να σχεδιάσετε μια σαφή και επεκτάσιμη δομή καταλόγων για έργα στο Nette Framework; Θα σας δείξουμε δοκιμασμένες πρακτικές που θα σας βοηθήσουν να οργανώσετε τον κώδικά σας. Θα μάθετε: + +- πώς να **χωρίσετε λογικά** την εφαρμογή σε καταλόγους +- πώς να σχεδιάσετε τη δομή ώστε να **επεκτείνεται καλά** με την ανάπτυξη του έργου +- ποιες είναι οι **πιθανές εναλλακτικές** και τα πλεονεκτήματα ή μειονεκτήματά τους + +
    + + +Είναι σημαντικό να αναφέρουμε ότι το ίδιο το Nette Framework δεν επιμένει σε καμία συγκεκριμένη δομή. Είναι σχεδιασμένο έτσι ώστε να μπορεί εύκολα να προσαρμοστεί σε οποιεσδήποτε ανάγκες και προτιμήσεις. + + +Βασική Δομή Έργου +================= + +Παρόλο που το Nette Framework δεν υπαγορεύει καμία σταθερή δομή καταλόγων, υπάρχει μια δοκιμασμένη προεπιλεγμένη διάταξη με τη μορφή του [Web Project|https://github.com/nette/web-project]: + +/--pre +web-project/ +├── app/ ← κατάλογος με την εφαρμογή +├── assets/ ← αρχεία SCSS, JS, εικόνες..., εναλλακτικά resources/ +├── bin/ ← σενάρια για τη γραμμή εντολών +├── config/ ← διαμόρφωση +├── log/ ← καταγεγραμμένα σφάλματα +├── temp/ ← προσωρινά αρχεία, cache +├── tests/ ← δοκιμές +├── vendor/ ← βιβλιοθήκες εγκατεστημένες από τον Composer +└── www/ ← δημόσιος κατάλογος (document-root) +\-- + +Μπορείτε να τροποποιήσετε αυτή τη δομή ελεύθερα σύμφωνα με τις ανάγκες σας - να μετονομάσετε ή να μετακινήσετε φακέλους. Στη συνέχεια, αρκεί μόνο να ενημερώσετε τις σχετικές διαδρομές προς τους καταλόγους στο αρχείο `Bootstrap.php` και ενδεχομένως στο `composer.json`. Τίποτα περισσότερο δεν χρειάζεται, καμία πολύπλοκη επαναδιαμόρφωση, καμία αλλαγή σταθερών. Το Nette διαθέτει έξυπνη αυτόματη ανίχνευση και αναγνωρίζει αυτόματα τη θέση της εφαρμογής, συμπεριλαμβανομένης της βασικής της διεύθυνσης URL. + + +Αρχές Οργάνωσης Κώδικα +====================== + +Όταν εξερευνάτε για πρώτη φορά ένα νέο έργο, θα πρέπει να μπορείτε να προσανατολιστείτε γρήγορα σε αυτό. Φανταστείτε ότι ανοίγετε τον κατάλογο `app/Model/` και βλέπετε αυτή τη δομή: + +/--pre +app/Model/ +├── Services/ +├── Repositories/ +└── Entities/ +\-- + +Από αυτό, μπορείτε να συμπεράνετε μόνο ότι το έργο χρησιμοποιεί κάποιες υπηρεσίες, repositories και entities. Δεν μαθαίνετε τίποτα για τον πραγματικό σκοπό της εφαρμογής. + +Ας δούμε μια διαφορετική προσέγγιση - **οργάνωση ανά τομείς**: + +/--pre +app/Model/ +├── Cart/ +├── Payment/ +├── Order/ +└── Product/ +\-- + +Εδώ είναι διαφορετικά - με την πρώτη ματιά είναι σαφές ότι πρόκειται για ένα e-shop. Τα ίδια τα ονόματα των καταλόγων αποκαλύπτουν τι μπορεί να κάνει η εφαρμογή - λειτουργεί με πληρωμές, παραγγελίες και προϊόντα. + +Η πρώτη προσέγγιση (οργάνωση ανά τύπο κλάσης) φέρνει στην πράξη μια σειρά προβλημάτων: ο κώδικας που σχετίζεται λογικά είναι διάσπαρτος σε διαφορετικούς φακέλους και πρέπει να πηδάτε μεταξύ τους. Γι' αυτό θα οργανώσουμε ανά τομείς. + + +Χώροι Ονομάτων +-------------- + +Είναι σύνηθες η δομή καταλόγων να αντιστοιχεί στους χώρους ονομάτων στην εφαρμογή. Αυτό σημαίνει ότι η φυσική θέση των αρχείων αντιστοιχεί στο namespace τους. Για παράδειγμα, μια κλάση που βρίσκεται στο `app/Model/Product/ProductRepository.php` θα πρέπει να έχει το namespace `App\Model\Product`. Αυτή η αρχή βοηθά στον προσανατολισμό στον κώδικα και απλοποιεί την αυτόματη φόρτωση (autoloading). + + +Ενικός vs Πληθυντικός Αριθμός στα Ονόματα +----------------------------------------- + +Παρατηρήστε ότι για τους κύριους καταλόγους της εφαρμογής χρησιμοποιούμε ενικό αριθμό: `app`, `config`, `log`, `temp`, `www`. Το ίδιο και μέσα στην εφαρμογή: `Model`, `Core`, `Presentation`. Αυτό συμβαίνει επειδή καθένας από αυτούς αντιπροσωπεύει μια ενιαία, ολοκληρωμένη έννοια. + +Ομοίως, για παράδειγμα, το `app/Model/Product` αντιπροσωπεύει τα πάντα γύρω από τα προϊόντα. Δεν θα το ονομάσουμε `Products`, επειδή δεν είναι ένας φάκελος γεμάτος προϊόντα (αυτό θα σήμαινε ότι θα υπήρχαν αρχεία `nokia.php`, `samsung.php`). Είναι ένας namespace που περιέχει κλάσεις για την εργασία με προϊόντα - `ProductRepository.php`, `ProductService.php`. + +Ο φάκελος `app/Tasks` είναι στον πληθυντικό αριθμό επειδή περιέχει ένα σύνολο ανεξάρτητων εκτελέσιμων σεναρίων - `CleanupTask.php`, `ImportTask.php`. Καθένα από αυτά είναι μια ξεχωριστή μονάδα. + +Για λόγους συνέπειας, συνιστούμε να χρησιμοποιείτε: +- Ενικό αριθμό για namespace που αντιπροσωπεύει μια λειτουργική ενότητα (byť pracující s více entitami) +- Πληθυντικό αριθμό για συλλογές ανεξάρτητων μονάδων +- Σε περίπτωση αβεβαιότητας ή αν δεν θέλετε να το σκεφτείτε, επιλέξτε τον ενικό αριθμό + + +Δημόσιος Κατάλογος `www/` +========================= + +Αυτός ο κατάλογος είναι ο μόνος προσβάσιμος από τον ιστό (το λεγόμενο document-root). Συχνά μπορείτε να συναντήσετε και το όνομα `public/` αντί για `www/` - είναι απλώς θέμα σύμβασης και δεν επηρεάζει τη λειτουργικότητα. Ο κατάλογος περιέχει: +- Το [σημείο εισόδου |bootstrapping#index.php] της εφαρμογής `index.php` +- Το αρχείο `.htaccess` με κανόνες για το mod_rewrite (για τον Apache) +- Στατικά αρχεία (CSS, JavaScript, εικόνες) +- Ανεβασμένα αρχεία + +Για τη σωστή ασφάλεια της εφαρμογής, είναι ζωτικής σημασίας να έχετε σωστά [διαμορφωμένο το document-root |nette:troubleshooting#Πώς να αλλάξετε ή να αφαιρέσετε τον κατάλογο www από το URL]. + +.[note] +Ποτέ μην τοποθετείτε τον φάκελο `node_modules/` σε αυτόν τον κατάλογο - περιέχει χιλιάδες αρχεία που μπορεί να είναι εκτελέσιμα και δεν θα πρέπει να είναι δημόσια προσβάσιμα. + + +Κατάλογος Εφαρμογής `app/` +========================== + +Αυτός είναι ο κύριος κατάλογος με τον κώδικα της εφαρμογής. Η βασική δομή: + +/--pre +app/ +├── Core/ ← θέματα υποδομής +├── Model/ ← business λογική +├── Presentation/ ← presenters και templates +├── Tasks/ ← σενάρια εντολών +└── Bootstrap.php ← κλάση εκκίνησης της εφαρμογής +\-- + +Το `Bootstrap.php` είναι η [κλάση εκκίνησης της εφαρμογής|bootstrapping], η οποία αρχικοποιεί το περιβάλλον, φορτώνει τη διαμόρφωση και δημιουργεί το DI container. + +Ας ρίξουμε τώρα μια πιο λεπτομερή ματιά στους επιμέρους υποκαταλόγους. + + +Presenters και Πρότυπα +====================== + +Το τμήμα παρουσίασης της εφαρμογής βρίσκεται στον κατάλογο `app/Presentation`. Μια εναλλακτική είναι το σύντομο `app/UI`. Είναι ο τόπος για όλους τους presenters, τα templates τους και τυχόν βοηθητικές κλάσεις. + +Οργανώνουμε αυτό το επίπεδο ανά τομείς. Σε ένα σύνθετο έργο που συνδυάζει e-shop, blog και API, η δομή θα έμοιαζε ως εξής: + +/--pre +app/Presentation/ +├── Shop/ ← e-shop frontend +│ ├── Product/ +│ ├── Cart/ +│ └── Order/ +├── Blog/ ← blog +│ ├── Home/ +│ └── Post/ +├── Admin/ ← διαχείριση +│ ├── Dashboard/ +│ └── Products/ +└── Api/ ← API endpoints + └── V1/ +\-- + +Αντίθετα, για ένα απλό blog, θα χρησιμοποιούσαμε την εξής διάρθρωση: + +/--pre +app/Presentation/ +├── Front/ ← frontend webu +│ ├── Home/ +│ └── Post/ +├── Admin/ ← διαχείριση +│ ├── Dashboard/ +│ └── Posts/ +├── Error/ +└── Export/ ← RSS, sitemaps κ.λπ. +\-- + +Φάκελοι όπως `Home/` ή `Dashboard/` περιέχουν presenters και templates. Φάκελοι όπως `Front/`, `Admin/` ή `Api/` ονομάζονται **modules**. Τεχνικά, πρόκειται για συνηθισμένους καταλόγους που χρησιμεύουν για τη λογική διάρθρωση της εφαρμογής. + +Κάθε φάκελος με presenter περιέχει έναν ομώνυμο presenter και τα templates του. Για παράδειγμα, ο φάκελος `Dashboard/` περιέχει: + +/--pre +Dashboard/ +├── DashboardPresenter.php ← presenter +└── default.latte ← template +\-- + +Αυτή η δομή καταλόγων αντικατοπτρίζεται στους χώρους ονομάτων των κλάσεων. Για παράδειγμα, το `DashboardPresenter` βρίσκεται στον χώρο ονομάτων `App\Presentation\Admin\Dashboard` (βλ. [#Αντιστοίχιση Presenters]): + +```php +namespace App\Presentation\Admin\Dashboard; + +class DashboardPresenter extends Nette\Application\UI\Presenter +{ + // ... +} +``` + +Στον presenter `Dashboard` μέσα στο module `Admin` αναφερόμαστε στην εφαρμογή χρησιμοποιώντας τη σημειογραφία με άνω και κάτω τελεία ως `Admin:Dashboard`. Στην action του `default` στη συνέχεια ως `Admin:Dashboard:default`. Σε περίπτωση ένθετων modules, χρησιμοποιούμε περισσότερες άνω και κάτω τελείες, για παράδειγμα `Shop:Order:Detail:default`. + + +Ευέλικτη Ανάπτυξη Δομής +----------------------- + +Ένα από τα μεγάλα πλεονεκτήματα αυτής της δομής είναι το πόσο κομψά προσαρμόζεται στις αυξανόμενες ανάγκες του έργου. Ας πάρουμε ως παράδειγμα το τμήμα που δημιουργεί XML feeds. Στην αρχή, έχουμε μια απλή μορφή: + +/--pre +Export/ +├── ExportPresenter.php ← ένας presenter για όλες τις εξαγωγές +├── sitemap.latte ← template για το sitemap +└── feed.latte ← template για το RSS feed +\-- + +Με τον καιρό, προστίθενται περισσότεροι τύποι feeds και χρειαζόμαστε περισσότερη λογική γι' αυτούς... Κανένα πρόβλημα! Ο φάκελος `Export/` γίνεται απλά ένα module: + +/--pre +Export/ +├── Sitemap/ +│ ├── SitemapPresenter.php +│ └── sitemap.latte +└── Feed/ + ├── FeedPresenter.php + ├── zbozi.latte ← feed για το Zboží.cz + └── heureka.latte ← feed για το Heureka.cz +\-- + +Αυτή η μετατροπή είναι απολύτως ομαλή - αρκεί να δημιουργήσετε νέους υποφακέλους, να χωρίσετε τον κώδικα σε αυτούς και να ενημερώσετε τους συνδέσμους (π.χ. από `Export:feed` σε `Export:Feed:zbozi`). Χάρη σε αυτό, μπορούμε να επεκτείνουμε σταδιακά τη δομή ανάλογα με τις ανάγκες, το επίπεδο ένθεσης δεν περιορίζεται με κανέναν τρόπο. + +Αν, για παράδειγμα, στη διαχείριση έχετε πολλούς presenters που σχετίζονται με τη διαχείριση παραγγελιών, όπως `OrderDetail`, `OrderEdit`, `OrderDispatch` κ.λπ., μπορείτε για καλύτερη οργάνωση σε αυτό το σημείο να δημιουργήσετε ένα module (φάκελο) `Order`, στο οποίο θα βρίσκονται (οι φάκελοι για) οι presenters `Detail`, `Edit`, `Dispatch` και άλλοι. + + +Τοποθέτηση Προτύπων +------------------- + +Στα προηγούμενα παραδείγματα, είδαμε ότι τα templates βρίσκονται απευθείας στον φάκελο με τον presenter: + +/--pre +Dashboard/ +├── DashboardPresenter.php ← presenter +├── DashboardTemplate.php ← προαιρετική κλάση για το template +└── default.latte ← template +\-- + +Αυτή η τοποθέτηση αποδεικνύεται στην πράξη η πιο βολική - έχετε όλα τα σχετικά αρχεία αμέσως πρόχειρα. + +Εναλλακτικά, μπορείτε να τοποθετήσετε τα templates στον υποφάκελο `templates/`. Το Nette υποστηρίζει και τις δύο παραλλαγές. Μπορείτε ακόμη και να τοποθετήσετε τα templates εντελώς εκτός του φακέλου `Presentation/`. Όλα σχετικά με τις δυνατότητες τοποθέτησης templates θα βρείτε στο κεφάλαιο [Αναζήτηση templates |templates#Αναζήτηση προτύπου]. + + +Βοηθητικές Κλάσεις και Components +--------------------------------- + +Στους presenters και τα templates συχνά ανήκουν και άλλα βοηθητικά αρχεία. Τα τοποθετούμε λογικά ανάλογα με το πεδίο εφαρμογής τους: + +1. **Απευθείας στον presenter** σε περίπτωση συγκεκριμένων components για τον συγκεκριμένο presenter: + +/--pre +Product/ +├── ProductPresenter.php +├── ProductGrid.php ← component για την εμφάνιση προϊόντων +└── FilterForm.php ← φόρμα για φιλτράρισμα +\-- + +2. **Για το module** - συνιστούμε να χρησιμοποιήσετε τον φάκελο `Accessory`, ο οποίος τοποθετείται βολικά στην αρχή της αλφαβήτου: + +/--pre +Front/ +├── Accessory/ +│ ├── NavbarControl.php ← components για το frontend +│ └── TemplateFilters.php +├── Product/ +└── Cart/ +\-- + +3. **Για ολόκληρη την εφαρμογή** - στο `Presentation/Accessory/`: +/--pre +app/Presentation/ +├── Accessory/ +│ ├── LatteExtension.php +│ └── TemplateFilters.php +├── Front/ +└── Admin/ +\-- + +Ή μπορείτε να τοποθετήσετε βοηθητικές κλάσεις όπως `LatteExtension.php` ή `TemplateFilters.php` στον φάκελο υποδομής `app/Core/Latte/`. Και τα components στο `app/Components`. Η επιλογή εξαρτάται από τις συνήθειες της ομάδας. + + +Model - Η Καρδιά της Εφαρμογής +============================== + +Το Model περιέχει όλη την business λογική της εφαρμογής. Για την οργάνωσή του ισχύει ξανά ο κανόνας - δομούμε ανά τομείς: + +/--pre +app/Model/ +├── Payment/ ← όλα γύρω από τις πληρωμές +│ ├── PaymentFacade.php ← κύριο σημείο εισόδου +│ ├── PaymentRepository.php +│ ├── Payment.php ← entity +├── Order/ ← όλα γύρω από τις παραγγελίες +│ ├── OrderFacade.php +│ ├── OrderRepository.php +│ ├── Order.php +└── Shipping/ ← όλα γύρω από την αποστολή +\-- + +Στο model, τυπικά συναντάμε αυτούς τους τύπους κλάσεων: + +**Facades**: αντιπροσωπεύουν το κύριο σημείο εισόδου σε έναν συγκεκριμένο τομέα στην εφαρμογή. Λειτουργούν ως ενορχηστρωτής, που συντονίζει τη συνεργασία μεταξύ διαφόρων υπηρεσιών με σκοπό την υλοποίηση πλήρων use-cases (όπως "δημιουργία παραγγελίας" ή "επεξεργασία πληρωμής"). Κάτω από το επίπεδο ενορχήστρωσης, η facade κρύβει τις λεπτομέρειες υλοποίησης από την υπόλοιπη εφαρμογή, παρέχοντας έτσι μια καθαρή διεπαφή για την εργασία με τον συγκεκριμένο τομέα. + +```php +class OrderFacade +{ + public function createOrder(Cart $cart): Order + { + // επικύρωση + // δημιουργία παραγγελίας + // αποστολή e-mail + // καταγραφή στα στατιστικά + } +} +``` + +**Υπηρεσίες (Services)**: εστιάζουν σε μια συγκεκριμένη business λειτουργία εντός του τομέα. Σε αντίθεση με τη facade, η οποία ενορχηστρώνει ολόκληρα use-cases, μια υπηρεσία υλοποιεί συγκεκριμένη business λογική (όπως υπολογισμούς τιμών ή επεξεργασία πληρωμών). Οι υπηρεσίες είναι τυπικά stateless και μπορούν να χρησιμοποιηθούν είτε από facades ως δομικά στοιχεία για πιο σύνθετες λειτουργίες, είτε απευθείας από άλλα μέρη της εφαρμογής για απλούστερες εργασίες. + +```php +class PricingService +{ + public function calculateTotal(Order $order): Money + { + // υπολογισμός τιμής + } +} +``` + +**Repositories**: εξασφαλίζουν όλη την επικοινωνία με τον χώρο αποθήκευσης δεδομένων, τυπικά μια βάση δεδομένων. Ο ρόλος του είναι η φόρτωση και η αποθήκευση entities και η υλοποίηση μεθόδων για την αναζήτησή τους. Το repository απομονώνει την υπόλοιπη εφαρμογή από τις λεπτομέρειες υλοποίησης της βάσης δεδομένων και παρέχει μια αντικειμενοστραφή διεπαφή για την εργασία με δεδομένα. + +```php +class OrderRepository +{ + public function find(int $id): ?Order + { + } + + public function findByCustomer(int $customerId): array + { + } +} +``` + +**Entities**: αντικείμενα που αντιπροσωπεύουν τις κύριες business έννοιες στην εφαρμογή, οι οποίες έχουν τη δική τους ταυτότητα και αλλάζουν με την πάροδο του χρόνου. Τυπικά, πρόκειται για κλάσεις που αντιστοιχίζονται σε πίνακες βάσης δεδομένων χρησιμοποιώντας ORM (όπως το Nette Database Explorer ή το Doctrine). Οι entities μπορούν να περιέχουν business κανόνες που σχετίζονται με τα δεδομένα τους και λογική επικύρωσης. + +```php +// Entity αντιστοιχισμένη στον πίνακα βάσης δεδομένων orders +class Order extends Nette\Database\Table\ActiveRow +{ + public function addItem(Product $product, int $quantity): void + { + $this->related('order_items')->insert([ + 'product_id' => $product->id, + 'quantity' => $quantity, + 'unit_price' => $product->price, + ]); + } +} +``` + +**Value objects**: αμετάβλητα αντικείμενα που αντιπροσωπεύουν τιμές χωρίς δική τους ταυτότητα - για παράδειγμα, ένα χρηματικό ποσό ή μια διεύθυνση e-mail. Δύο παρουσίες ενός value object με τις ίδιες τιμές θεωρούνται ταυτόσημες. + + +Κώδικας Υποδομής +================ + +Ο φάκελος `Core/` (ή επίσης `Infrastructure/`) είναι το σπίτι για την τεχνική βάση της εφαρμογής. Ο κώδικας υποδομής τυπικά περιλαμβάνει: + +/--pre +app/Core/ +├── Router/ ← δρομολόγηση και διαχείριση URL +│ └── RouterFactory.php +├── Security/ ← αυθεντικοποίηση και εξουσιοδότηση +│ ├── Authenticator.php +│ └── Authorizator.php +├── Logging/ ← καταγραφή και παρακολούθηση +│ ├── SentryLogger.php +│ └── FileLogger.php +├── Cache/ ← επίπεδο προσωρινής αποθήκευσης (caching) +│ └── FullPageCache.php +└── Integration/ ← ενσωμάτωση με εξωτερικές υπηρεσίες + ├── Slack/ + └── Stripe/ +\-- + +Για μικρότερα έργα, φυσικά, αρκεί μια επίπεδη διάρθρωση: + +/--pre +Core/ +├── RouterFactory.php +├── Authenticator.php +└── QueueMailer.php +\-- + +Πρόκειται για κώδικα που: + +- Επιλύει την τεχνική υποδομή (δρομολόγηση, καταγραφή, caching) +- Ενσωματώνει εξωτερικές υπηρεσίες (Sentry, Elasticsearch, Redis) +- Παρέχει βασικές υπηρεσίες για ολόκληρη την εφαρμογή (mail, βάση δεδομένων) +- Είναι ως επί το πλείστον ανεξάρτητος από τον συγκεκριμένο τομέα - η cache ή ο logger λειτουργεί το ίδιο για eshop ή blog. + +Αναρωτιέστε αν μια συγκεκριμένη κλάση ανήκει εδώ, ή στο model; Η βασική διαφορά είναι ότι ο κώδικας στο `Core/`: + +- Δεν γνωρίζει τίποτα για τον τομέα (προϊόντα, παραγγελίες, άρθρα) +- Είναι ως επί το πλείστον δυνατό να μεταφερθεί σε άλλο έργο +- Επιλύει "πώς λειτουργεί" (πώς να στείλετε mail), όχι "τι κάνει" (ποιο mail να στείλετε) + +Παράδειγμα για καλύτερη κατανόηση: + +- `App\Core\MailerFactory` - δημιουργεί παρουσίες της κλάσης για την αποστολή e-mail, διαχειρίζεται τις ρυθμίσεις SMTP +- `App\Model\OrderMailer` - χρησιμοποιεί το `MailerFactory` για την αποστολή e-mail σχετικά με παραγγελίες, γνωρίζει τα templates τους και πότε πρέπει να σταλούν + + +Σενάρια Εντολών +=============== + +Οι εφαρμογές συχνά χρειάζεται να εκτελούν δραστηριότητες εκτός των συνηθισμένων HTTP requests - είτε πρόκειται για επεξεργασία δεδομένων στο παρασκήνιο, συντήρηση, ή περιοδικές εργασίες. Για την εκτέλεση χρησιμοποιούνται απλά σενάρια στον κατάλογο `bin/`, ενώ η λογική υλοποίησης τοποθετείται στο `app/Tasks/` (ή `app/Commands/`). + +Παράδειγμα: + +/--pre +app/Tasks/ +├── Maintenance/ ← σενάρια συντήρησης +│ ├── CleanupCommand.php ← διαγραφή παλιών δεδομένων +│ └── DbOptimizeCommand.php ← βελτιστοποίηση βάσης δεδομένων +├── Integration/ ← ενσωμάτωση με εξωτερικά συστήματα +│ ├── ImportProducts.php ← εισαγωγή από σύστημα προμηθευτή +│ └── SyncOrders.php ← συγχρονισμός παραγγελιών +└── Scheduled/ ← τακτικές εργασίες + ├── NewsletterCommand.php ← αποστολή newsletter + └── ReminderCommand.php ← ειδοποιήσεις πελατών +\-- + +Τι ανήκει στο model και τι στα σενάρια εντολών; Για παράδειγμα, η λογική για την αποστολή ενός e-mail είναι μέρος του model, η μαζική αποστολή χιλιάδων e-mail ανήκει ήδη στο `Tasks/`. + +Οι εργασίες συνήθως [εκκινούνται από τη γραμμή εντολών |https://blog.nette.org/en/cli-scripts-in-nette-application] ή μέσω cron. Μπορούν επίσης να εκκινηθούν μέσω HTTP request, αλλά είναι απαραίτητο να σκεφτείτε την ασφάλεια. Ο presenter που εκκινεί την εργασία πρέπει να ασφαλιστεί, για παράδειγμα, μόνο για συνδεδεμένους χρήστες ή με ισχυρό token και πρόσβαση από επιτρεπόμενες διευθύνσεις IP. Για μεγάλες εργασίες, είναι απαραίτητο να αυξήσετε το χρονικό όριο του σεναρίου και να χρησιμοποιήσετε το `session_write_close()`, ώστε να μην κλειδώνεται η session. + + +Άλλοι Πιθανοί Κατάλογοι +======================= + +Εκτός από τους βασικούς καταλόγους που αναφέρθηκαν, μπορείτε να προσθέσετε άλλους εξειδικευμένους φακέλους ανάλογα με τις ανάγκες του έργου. Ας ρίξουμε μια ματιά στους πιο συνηθισμένους από αυτούς και τη χρήση τους: + +/--pre +app/ +├── Api/ ← λογική για API ανεξάρτητη από το επίπεδο παρουσίασης +├── Database/ ← σενάρια μετανάστευσης και seeders για δοκιμαστικά δεδομένα +├── Components/ ← κοινόχρηστα οπτικά components σε ολόκληρη την εφαρμογή +├── Event/ ← χρήσιμο αν χρησιμοποιείτε event-driven αρχιτεκτονική +├── Mail/ ← e-mail templates και σχετική λογική +└── Utils/ ← βοηθητικές κλάσεις +\-- + +Για κοινόχρηστα οπτικά components που χρησιμοποιούνται σε presenters σε ολόκληρη την εφαρμογή, μπορείτε να χρησιμοποιήσετε τον φάκελο `app/Components` ή `app/Controls`: + +/--pre +app/Components/ +├── Form/ ← κοινόχρηστα components φόρμας +│ ├── SignInForm.php +│ └── UserForm.php +├── Grid/ ← components για λίστες δεδομένων +│ └── DataGrid.php +└── Navigation/ ← στοιχεία πλοήγησης + ├── Breadcrumbs.php + └── Menu.php +\-- + +Εδώ ανήκουν τα components που έχουν πιο σύνθετη λογική. Αν θέλετε να μοιραστείτε components μεταξύ πολλών έργων, είναι σκόπιμο να τα διαχωρίσετε σε ένα ξεχωριστό composer πακέτο. + +Στον κατάλογο `app/Mail` μπορείτε να τοποθετήσετε τη διαχείριση της επικοινωνίας μέσω e-mail: + +/--pre +app/Mail/ +├── templates/ ← e-mail templates +│ ├── order-confirmation.latte +│ └── welcome.latte +└── OrderMailer.php +\-- + + +Αντιστοίχιση Presenters +======================= + +Η αντιστοίχιση (mapping) ορίζει κανόνες για την εξαγωγή του ονόματος της κλάσης από το όνομα του presenter. Τους καθορίζουμε στη [διαμόρφωση|configuration] κάτω από το κλειδί `application › mapping`. + +Σε αυτή τη σελίδα, δείξαμε ότι τοποθετούμε τους presenters στον φάκελο `app/Presentation` (ή `app/UI`). Πρέπει να ενημερώσουμε το Nette για αυτή τη σύμβαση στο αρχείο διαμόρφωσης. Μια γραμμή αρκεί: + +```neon +application: + mapping: App\Presentation\*\**Presenter +``` + +Πώς λειτουργεί η αντιστοίχιση; Για καλύτερη κατανόηση, ας φανταστούμε πρώτα μια εφαρμογή χωρίς modules. Θέλουμε οι κλάσεις των presenters να ανήκουν στον χώρο ονομάτων `App\Presentation`, ώστε ο presenter `Home` να αντιστοιχεί στην κλάση `App\Presentation\HomePresenter`. Αυτό το επιτυγχάνουμε με αυτή τη διαμόρφωση: + +```neon +application: + mapping: App\Presentation\*Presenter +``` + +Η αντιστοίχιση λειτουργεί έτσι ώστε το όνομα του presenter `Home` να αντικαθιστά τον αστερίσκο στη μάσκα `App\Presentation\*Presenter`, δίνοντας το τελικό όνομα κλάσης `App\Presentation\HomePresenter`. Απλό! + +Ωστόσο, όπως βλέπετε στα παραδείγματα σε αυτό και σε άλλα κεφάλαια, τοποθετούμε τις κλάσεις των presenters σε ομώνυμους υποκαταλόγους, για παράδειγμα, ο presenter `Home` αντιστοιχεί στην κλάση `App\Presentation\Home\HomePresenter`. Αυτό το επιτυγχάνουμε διπλασιάζοντας την άνω και κάτω τελεία (απαιτεί Nette Application 3.2): + +```neon +application: + mapping: App\Presentation\**Presenter +``` + +Τώρα προχωράμε στην αντιστοίχιση presenters σε modules. Για κάθε module, μπορούμε να ορίσουμε μια συγκεκριμένη αντιστοίχιση: + +```neon +application: + mapping: + Front: App\Presentation\Front\**Presenter + Admin: App\Presentation\Admin\**Presenter + Api: App\Api\*Presenter +``` + +Σύμφωνα με αυτή τη διαμόρφωση, ο presenter `Front:Home` αντιστοιχεί στην κλάση `App\Presentation\Front\Home\HomePresenter`, ενώ ο presenter `Api:OAuth` στην κλάση `App\Api\OAuthPresenter`. + +Επειδή τα modules `Front` και `Admin` έχουν παρόμοιο τρόπο αντιστοίχισης και πιθανότατα θα υπάρχουν περισσότερα τέτοια modules, είναι δυνατό να δημιουργηθεί ένας γενικός κανόνας που τα αντικαθιστά. Έτσι, στη μάσκα της κλάσης προστίθεται ένας νέος αστερίσκος για το module: + +```neon +application: + mapping: + *: App\Presentation\*\**Presenter + Api: App\Api\*Presenter +``` + +Λειτουργεί επίσης για βαθύτερα ένθετες δομές καταλόγων, όπως για παράδειγμα ο presenter `Admin:User:Edit`, με το τμήμα με τον αστερίσκο να επαναλαμβάνεται για κάθε επίπεδο και το αποτέλεσμα να είναι η κλάση `App\Presentation\Admin\User\Edit\EditPresenter`. + +Μια εναλλακτική σύνταξη είναι να χρησιμοποιήσετε έναν πίνακα που αποτελείται από τρία τμήματα αντί για μια συμβολοσειρά. Αυτή η σύνταξη είναι ισοδύναμη με την προηγούμενη: + +```neon +application: + mapping: + *: [App\Presentation, *, **Presenter] + Api: [App\Api, '', *Presenter] +``` diff --git a/application/el/how-it-works.texy b/application/el/how-it-works.texy index b5b1f766dd..30acf528ad 100644 --- a/application/el/how-it-works.texy +++ b/application/el/how-it-works.texy @@ -3,99 +3,101 @@
    -Αυτή τη στιγμή διαβάζετε το βασικό έγγραφο της τεκμηρίωσης της Nette. Θα μάθετε όλες τις αρχές των διαδικτυακών εφαρμογών. Νίκαια από το Α έως το Ω, από τη στιγμή της γέννησης μέχρι την τελευταία ανάσα του PHP script. Μετά την ανάγνωση θα γνωρίζετε: +Διαβάζετε το βασικό έγγραφο της τεκμηρίωσης του Nette. Θα μάθετε ολόκληρη την αρχή λειτουργίας των διαδικτυακών εφαρμογών. Από το Α έως το Ω, από τη στιγμή της γέννησης μέχρι την τελευταία πνοή του σεναρίου PHP. Αφού το διαβάσετε, θα γνωρίζετε: -- πώς λειτουργούν όλα +- πώς λειτουργεί όλο αυτό - τι είναι το Bootstrap, ο Presenter και το DI container -- πώς είναι η δομή των καταλόγων +- πώς μοιάζει η δομή καταλόγων
    -Δομή καταλόγου .[#toc-directory-structure] -========================================== +Δομή καταλόγου +============== -Ανοίξτε ένα παράδειγμα ενός σκελετού μιας διαδικτυακής εφαρμογής που ονομάζεται [WebProject |https://github.com/nette/web-project] και μπορείτε να παρακολουθήσετε τα αρχεία που γράφονται. +Ανοίξτε το παράδειγμα του σκελετού της διαδικτυακής εφαρμογής που ονομάζεται [WebProject|https://github.com/nette/web-project] και κατά την ανάγνωση μπορείτε να δείτε τα αρχεία για τα οποία γίνεται λόγος. -Η δομή του καταλόγου μοιάζει κάπως έτσι: +Η δομή καταλόγων μοιάζει κάπως έτσι: /--pre web-project/ -├── app/ ← directory with application -│ ├── Presenters/ ← presenter classes -│ │ ├── HomePresenter.php ← Home presenter class -│ │ └── templates/ ← templates directory -│ │ ├── @layout.latte ← template of shared layout -│ │ └── Home/ ← templates for Home presenter -│ │ └── default.latte ← template for action `default` -│ ├── Router/ ← configuration of URL addresses -│ └── Bootstrap.php ← booting class Bootstrap -├── bin/ ← scripts for the command line -├── config/ ← configuration files +├── app/ ← κατάλογος με την εφαρμογή +│ ├── Core/ ← βασικές κλάσεις απαραίτητες για τη λειτουργία +│ │ └── RouterFactory.php ← διαμόρφωση διευθύνσεων URL +│ ├── Presentation/ ← presenters, πρότυπα & λοιπά +│ │ ├── @layout.latte ← πρότυπο διάταξης +│ │ └── Home/ ← κατάλογος του presenter Home +│ │ ├── HomePresenter.php ← κλάση του presenter Home +│ │ └── default.latte ← πρότυπο της ενέργειας default +│ └── Bootstrap.php ← κλάση εκκίνησης Bootstrap +├── assets/ ← πόροι (SCSS, TypeScript, εικόνες πηγής) +├── bin/ ← σενάρια που εκτελούνται από τη γραμμή εντολών +├── config/ ← αρχεία διαμόρφωσης │ ├── common.neon -│ └── local.neon -├── log/ ← error logs -├── temp/ ← temporary files, cache, … -├── vendor/ ← libraries installed by Composer +│ └── services.neon +├── log/ ← καταγεγραμμένα σφάλματα +├── temp/ ← προσωρινά αρχεία, cache, … +├── vendor/ ← βιβλιοθήκες εγκατεστημένες από τον Composer │ ├── ... -│ └── autoload.php ← autoloading of libs installed by Composer -├── www/ ← public directory, document root of project -│ ├── .htaccess ← mod_rewrite rules etc -│ └── index.php ← initial file that launches the application -└── .htaccess ← prohibits access to all directories except www +│ └── autoload.php ← αυτόματη φόρτωση όλων των εγκατεστημένων πακέτων +├── www/ ← δημόσιος κατάλογος ή document-root του έργου +│ ├── assets/ ← μεταγλωττισμένα στατικά αρχεία (CSS, JS, εικόνες, ...) +│ ├── .htaccess ← κανόνες mod_rewrite +│ └── index.php ← αρχικό αρχείο με το οποίο εκκινεί η εφαρμογή +└── .htaccess ← απαγορεύει την πρόσβαση σε όλους τους καταλόγους εκτός του www \-- -Μπορείτε να αλλάξετε τη δομή του καταλόγου με οποιονδήποτε τρόπο, να μετονομάσετε ή να μετακινήσετε φακέλους και στη συνέχεια να επεξεργαστείτε απλώς τις διαδρομές προς τα `log/` και `temp/` στο αρχείο `Bootstrap.php` και τη διαδρομή προς αυτό το αρχείο στο `composer.json` στο τμήμα `autoload`. Τίποτα περισσότερο, καμία περίπλοκη αναδιαμόρφωση, καμία συνεχής αλλαγή. Το Nette διαθέτει μια [έξυπνη αυτόματη αναγνώριση |bootstrap#development-vs-production-mode]. +Μπορείτε να αλλάξετε τη δομή καταλόγων όπως θέλετε, να μετονομάσετε ή να μετακινήσετε φακέλους, είναι εντελώς ευέλικτη. Το Nette διαθέτει επίσης έξυπνη αυτόματη ανίχνευση και αναγνωρίζει αυτόματα τη θέση της εφαρμογής, συμπεριλαμβανομένης της βασικής της διεύθυνσης URL. -Για λίγο μεγαλύτερες εφαρμογές, μπορούμε να χωρίσουμε τους φακέλους με τους παρουσιαστές και τα πρότυπα σε υποκαταλόγους (στο δίσκο) και σε χώρους ονομάτων (στον κώδικα), τους οποίους ονομάζουμε [ενότητες |modules]. +Για λίγο μεγαλύτερες εφαρμογές, μπορούμε να [χωρίσουμε τους φακέλους με τους presenters και τα πρότυπα σε υποκαταλόγους |directory-structure#Presenters και Πρότυπα] και τις κλάσεις σε χώρους ονομάτων, τους οποίους ονομάζουμε modules. -Ο κατάλογος `www/` είναι ο δημόσιος κατάλογος ή το document-root του έργου. Μπορείτε να τον μετονομάσετε χωρίς να χρειάζεται να ορίσετε κάτι άλλο στην πλευρά της εφαρμογής. Απλά πρέπει να [ρυθμίσετε τη φιλοξενία |nette:troubleshooting#How to change or remove www directory from URL] έτσι ώστε το document-root να πηγαίνει σε αυτόν τον κατάλογο. +Ο κατάλογος `www/` αντιπροσωπεύει τον λεγόμενο δημόσιο κατάλογο ή document-root του έργου. Μπορείτε να τον μετονομάσετε χωρίς να χρειάζεται να ρυθμίσετε τίποτα άλλο στην πλευρά της εφαρμογής. Απλά πρέπει να [διαμορφώσετε το hosting |nette:troubleshooting#Πώς να αλλάξετε ή να αφαιρέσετε τον κατάλογο www από το URL] έτσι ώστε το document-root να δείχνει σε αυτόν τον κατάλογο. -Μπορείτε επίσης να κατεβάσετε απευθείας το WebProject, συμπεριλαμβανομένου του Nette, χρησιμοποιώντας το [Composer |best-practices:composer]: +Μπορείτε επίσης να κατεβάσετε απευθείας το WebProject συμπεριλαμβανομένου του Nette χρησιμοποιώντας τον [Composer |best-practices:composer]: ```shell composer create-project nette/web-project ``` -Στο Linux ή το macOS, ορίστε τα [δικαιώματα εγγραφής |nette:troubleshooting#Setting directory permissions] για τους καταλόγους `log/` και `temp/`. +Σε Linux ή macOS, ορίστε δικαιώματα εγγραφής για τους καταλόγους `log/` και `temp/` [δικαιώματα εγγραφής |nette:troubleshooting#Ρύθμιση δικαιωμάτων καταλόγου]. -Η εφαρμογή WebProject είναι έτοιμη να εκτελεστεί, δεν χρειάζεται να ρυθμίσετε τίποτε άλλο και μπορείτε να την δείτε απευθείας στο πρόγραμμα περιήγησης με πρόσβαση στο φάκελο `www/`. +Η εφαρμογή WebProject είναι έτοιμη για εκκίνηση, δεν χρειάζεται να διαμορφώσετε απολύτως τίποτα και μπορείτε να την εμφανίσετε απευθείας στο πρόγραμμα περιήγησης μεταβαίνοντας στον φάκελο `www/`. -Αίτημα HTTP .[#toc-http-request] -================================ +Αίτημα HTTP +=========== -Όλα ξεκινούν όταν ένας χρήστης ανοίγει τη σελίδα σε ένα πρόγραμμα περιήγησης και το πρόγραμμα περιήγησης χτυπάει τον διακομιστή με ένα αίτημα HTTP. Το αίτημα πηγαίνει σε ένα αρχείο PHP που βρίσκεται στο δημόσιο κατάλογο `www/`, το οποίο είναι το `index.php`. Ας υποθέσουμε ότι πρόκειται για ένα αίτημα προς το `https://example.com/product/123` και θα εκτελεστεί. +Όλα ξεκινούν τη στιγμή που ο χρήστης ανοίγει μια σελίδα στο πρόγραμμα περιήγησης. Δηλαδή, όταν το πρόγραμμα περιήγησης χτυπάει την πόρτα του διακομιστή με ένα αίτημα HTTP. Το αίτημα κατευθύνεται σε ένα μόνο αρχείο PHP, το οποίο βρίσκεται στον δημόσιο κατάλογο `www/`, και αυτό είναι το `index.php`. Ας υποθέσουμε ότι πρόκειται για ένα αίτημα στη διεύθυνση `https://example.com/product/123`. Χάρη στην κατάλληλη [ρύθμιση του διακομιστή |nette:troubleshooting#Πώς να ρυθμίσετε τον διακομιστή για όμορφα URLs], ακόμη και αυτό το URL αντιστοιχίζεται στο αρχείο `index.php` και αυτό εκτελείται. -Η αποστολή του είναι η εξής: +Ο ρόλος του είναι: 1) να αρχικοποιήσει το περιβάλλον -2) να πάρει το εργοστάσιο -3) να εκκινήσει την εφαρμογή Nette που χειρίζεται το αίτημα +2) να αποκτήσει το factory +3) να εκκινήσει την εφαρμογή Nette, η οποία θα διεκπεραιώσει το αίτημα -Τι είδους εργοστάσιο; Δεν παράγουμε τρακτέρ, αλλά ιστοσελίδες! Περιμένετε, θα σας εξηγήσω αμέσως. +Ποιο factory; Δεν κατασκευάζουμε τρακτέρ, αλλά ιστοσελίδες! Υπομονή, θα εξηγηθεί αμέσως. -Με τον όρο "αρχικοποίηση του περιβάλλοντος" εννοούμε, για παράδειγμα, ότι ενεργοποιείται το [Tracy |tracy:], το οποίο είναι ένα καταπληκτικό εργαλείο για την καταγραφή ή την οπτικοποίηση σφαλμάτων. Καταγράφει τα σφάλματα στον διακομιστή παραγωγής και τα εμφανίζει απευθείας στον διακομιστή ανάπτυξης. Επομένως, η αρχικοποίηση πρέπει επίσης να αποφασίσει αν ο ιστότοπος εκτελείται σε κατάσταση παραγωγής ή ανάπτυξης. Για να το κάνει αυτό, η Nette χρησιμοποιεί αυτόματη ανίχνευση: αν τρέξετε τον ιστότοπο στο localhost, τρέχει σε λειτουργία προγραμματιστή. Δεν χρειάζεται να ρυθμίσετε τίποτα και η εφαρμογή είναι έτοιμη τόσο για ανάπτυξη όσο και για ανάπτυξη παραγωγής. Αυτά τα βήματα εκτελούνται και περιγράφονται λεπτομερώς στο κεφάλαιο σχετικά με την [κλάση Bootstrap |bootstrap]. +Με τις λέξεις «αρχικοποίηση περιβάλλοντος» εννοούμε, για παράδειγμα, ότι ενεργοποιείται το [Tracy|tracy:], το οποίο είναι ένα καταπληκτικό εργαλείο για την καταγραφή ή την οπτικοποίηση σφαλμάτων. Στον διακομιστή παραγωγής καταγράφει τα σφάλματα, στον διακομιστή ανάπτυξης τα εμφανίζει απευθείας. Επομένως, η αρχικοποίηση περιλαμβάνει επίσης την απόφαση εάν ο ιστότοπος εκτελείται σε λειτουργία παραγωγής ή ανάπτυξης. Για αυτό, το Nette χρησιμοποιεί [έξυπνη αυτόματη ανίχνευση |bootstrapping#Λειτουργία Ανάπτυξης vs Παραγωγής]: εάν εκτελείτε τον ιστότοπο στο localhost, εκτελείται σε λειτουργία ανάπτυξης. Έτσι, δεν χρειάζεται να διαμορφώσετε τίποτα και η εφαρμογή είναι αμέσως έτοιμη τόσο για ανάπτυξη όσο και για παραγωγική λειτουργία. Αυτά τα βήματα εκτελούνται και περιγράφονται λεπτομερώς στο κεφάλαιο για την [κλάση Bootstrap|bootstrapping]. -Το τρίτο σημείο (ναι, παραλείψαμε το δεύτερο, αλλά θα επανέλθουμε σε αυτό) είναι η εκκίνηση της εφαρμογής. Ο χειρισμός των αιτημάτων HTTP στη Nette γίνεται από την κλάση `Nette\Application\Application` (στο εξής θα αναφέρεται ως `Application`), οπότε όταν λέμε "τρέχουμε μια εφαρμογή", εννοούμε να καλέσουμε μια μέθοδο με το όνομα `run()` σε ένα αντικείμενο αυτής της κλάσης. +Το τρίτο σημείο (ναι, παραλείψαμε το δεύτερο, αλλά θα επιστρέψουμε σε αυτό) είναι η εκκίνηση της εφαρμογής. Η διεκπεραίωση των αιτημάτων HTTP στο Nette γίνεται από την κλάση `Nette\Application\Application` (στο εξής `Application`), οπότε όταν λέμε εκκίνηση της εφαρμογής, εννοούμε συγκεκριμένα την κλήση της μεθόδου με το εύστοχο όνομα `run()` στο αντικείμενο αυτής της κλάσης. -Η Nette είναι ένας μέντορας που σας καθοδηγεί να γράψετε καθαρές εφαρμογές με αποδεδειγμένες μεθοδολογίες. Και η πιο αποδεδειγμένη ονομάζεται **dependency injection**, συντομογραφία DI. Προς το παρόν δεν θέλουμε να σας επιβαρύνουμε με την εξήγηση του DI, αφού υπάρχει [ξεχωριστό κεφάλαιο |dependency-injection:introduction], το σημαντικό εδώ είναι ότι τα βασικά αντικείμενα θα δημιουργούνται συνήθως από ένα εργοστάσιο για αντικείμενα που ονομάζεται **DI container** (συντομογραφία DIC). Ναι, αυτό είναι το εργοστάσιο που αναφέρθηκε πριν από λίγο καιρό. Και δημιουργεί επίσης το αντικείμενο `Application` για εμάς, οπότε χρειαζόμαστε πρώτα ένα δοχείο. Το παίρνουμε χρησιμοποιώντας την κλάση `Configurator` και το αφήνουμε να παράγει το αντικείμενο `Application`, καλούμε τη μέθοδο `run()` και έτσι ξεκινάει η εφαρμογή Nette. Αυτό ακριβώς συμβαίνει στο αρχείο [index. |bootstrap#index.php] php. +Το Nette είναι ένας μέντορας που σας καθοδηγεί στη συγγραφή καθαρών εφαρμογών σύμφωνα με δοκιμασμένες μεθοδολογίες. Και μία από τις πιο δοκιμασμένες ονομάζεται **dependency injection**, συντομογραφικά DI. Αυτή τη στιγμή, δεν θέλουμε να σας επιβαρύνουμε με την εξήγηση του DI, γι' αυτό υπάρχει ένα [ξεχωριστό κεφάλαιο|dependency-injection:introduction], το σημαντικό αποτέλεσμα είναι ότι τα βασικά αντικείμενα συνήθως δημιουργούνται από ένα factory αντικειμένων, το οποίο ονομάζεται **DI container** (συντομογραφικά DIC). Ναι, αυτό είναι το factory για το οποίο μιλήσαμε πριν λίγο. Και θα μας δημιουργήσει επίσης το αντικείμενο `Application`, γι' αυτό χρειαζόμαστε πρώτα το container. Το αποκτούμε χρησιμοποιώντας την κλάση `Configurator` και το αφήνουμε να δημιουργήσει το αντικείμενο `Application`, καλούμε τη μέθοδο `run()` σε αυτό και έτσι εκκινεί η εφαρμογή Nette. Ακριβώς αυτό συμβαίνει στο αρχείο [index.php |bootstrapping#index.php]. -Εφαρμογή Nette .[#toc-nette-application] -======================================== +Nette Application +================= -Η κλάση Application έχει ένα και μόνο καθήκον: να απαντά σε ένα αίτημα HTTP. +Η κλάση Application έχει έναν μόνο ρόλο: να απαντήσει στο αίτημα HTTP. -Οι εφαρμογές που γράφονται στο Nette χωρίζονται σε πολλούς λεγόμενους παρουσιαστές (σε άλλα πλαίσια μπορεί να συναντήσετε τον όρο ελεγκτής, που είναι ο ίδιος), οι οποίοι είναι κλάσεις που αντιπροσωπεύουν μια συγκεκριμένη σελίδα του ιστότοπου: π.χ. αρχική σελίδα, προϊόν στο ηλεκτρονικό κατάστημα, φόρμα εγγραφής, τροφοδοσία χάρτη σελίδων, κ.λπ. Η εφαρμογή μπορεί να έχει από έναν έως χιλιάδες παρουσιαστές. +Οι εφαρμογές που γράφονται στο Nette χωρίζονται σε πολλούς λεγόμενους presenters (σε άλλα frameworks μπορεί να συναντήσετε τον όρο controller, πρόκειται για το ίδιο πράγμα), οι οποίοι είναι κλάσεις, καθεμία από τις οποίες αντιπροσωπεύει μια συγκεκριμένη σελίδα του ιστότοπου: π.χ. την αρχική σελίδα, ένα προϊόν σε ένα e-shop, μια φόρμα σύνδεσης, ένα sitemap feed κ.λπ. Μια εφαρμογή μπορεί να έχει από έναν έως χιλιάδες presenters. -Η εφαρμογή ξεκινά ζητώντας από τον λεγόμενο δρομολογητή να αποφασίσει ποιος από τους παρουσιαστές θα περάσει το τρέχον αίτημα για επεξεργασία. Ο δρομολογητής αποφασίζει ποιανού ευθύνη είναι. Κοιτάζει τη διεύθυνση URL εισόδου `https://example.com/product/123`, ο οποίος θέλει να `show` ένα προϊόν με `id: 123` ως ενέργεια. Είναι καλή συνήθεια να γράφετε τα ζεύγη παρουσιαστής + δράση χωρισμένα με άνω και κάτω τελεία ως `Product:show`. +Η Application ξεκινά ζητώντας από τον λεγόμενο router να αποφασίσει σε ποιον από τους presenters θα παραδώσει το τρέχον αίτημα για διεκπεραίωση. Ο router αποφασίζει ποιος έχει την ευθύνη. Εξετάζει το εισερχόμενο URL `https://example.com/product/123` και με βάση το πώς είναι ρυθμισμένος, αποφασίζει ότι αυτή είναι δουλειά, για παράδειγμα, για τον **presenter** `Product`, από τον οποίο θα ζητήσει ως **action** την εμφάνιση (`show`) του προϊόντος με `id: 123`. Το ζεύγος presenter + action συνηθίζεται να γράφεται χωρισμένο με άνω και κάτω τελεία ως `Product:show`. -Έτσι, ο δρομολογητής μετατρέπει τη διεύθυνση URL σε ένα ζεύγος `Presenter:action` + παράμετροι, στην περίπτωσή μας `Product:show` + `id: 123`. Μπορείτε να δείτε πώς μοιάζει ένας δρομολογητής στο αρχείο `app/Router/RouterFactory.php` και θα τον περιγράψουμε αναλυτικά στο κεφάλαιο [Δρομολόγηση |Routing]. +Έτσι, ο router μετέτρεψε το URL στο ζεύγος `Presenter:action` + παραμέτρους, στην περίπτωσή μας `Product:show` + `id: 123`. Πώς μοιάζει ένας τέτοιος router μπορείτε να δείτε στο αρχείο `app/Core/RouterFactory.php` και τον περιγράφουμε λεπτομερώς στο κεφάλαιο [Routing |Routing]. -Ας συνεχίσουμε. Η εφαρμογή γνωρίζει ήδη το όνομα του παρουσιαστή και μπορεί να συνεχίσει. Δημιουργώντας ένα αντικείμενο `ProductPresenter`, το οποίο είναι ο κώδικας του παρουσιαστή `Product`. Πιο συγκεκριμένα, ζητάει από το DI container τη δημιουργία του presenter, επειδή η παραγωγή αντικειμένων είναι η δουλειά του. +Ας προχωρήσουμε. Η Application γνωρίζει ήδη το όνομα του presenter και μπορεί να συνεχίσει. Δημιουργώντας το αντικείμενο της κλάσης `ProductPresenter`, που είναι ο κώδικας του presenter `Product`. Πιο συγκεκριμένα, ζητά από το DI container να δημιουργήσει τον presenter, επειδή η δημιουργία είναι δική του δουλειά. -Ο παρουσιαστής μπορεί να μοιάζει ως εξής: +Ο presenter μπορεί να μοιάζει κάπως έτσι: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -107,98 +109,92 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function renderShow(int $id): void { - // λαμβάνουμε δεδομένα από το μοντέλο και τα περνάμε στο πρότυπο + // λήψη δεδομένων από το μοντέλο και μεταβίβασή τους στο πρότυπο $this->template->product = $this->repository->getProduct($id); } } ``` -Η αίτηση χειρίζεται από τον παρουσιαστή. Και η αποστολή είναι σαφής: κάντε την ενέργεια `show` με το `id: 123`. Το οποίο στη γλώσσα των παρουσιαστών σημαίνει ότι καλείται η μέθοδος `renderShow()` και στην παράμετρο `$id` παίρνει το `123`. +Η διεκπεραίωση του αιτήματος αναλαμβάνεται από τον presenter. Και ο στόχος είναι σαφής: εκτέλεσε την action `show` με `id: 123`. Αυτό, στη γλώσσα των presenters, σημαίνει ότι καλείται η μέθοδος `renderShow()` και στην παράμετρο `$id` λαμβάνει το `123`. -Ένας παρουσιαστής μπορεί να χειριστεί πολλαπλές ενέργειες, δηλαδή να έχει πολλαπλές μεθόδους `render()`. Συνιστούμε όμως να σχεδιάζουμε παρουσιαστές με μία ή όσο το δυνατόν λιγότερες ενέργειες. +Ο presenter μπορεί να εξυπηρετεί πολλαπλές actions, δηλαδή να έχει πολλαπλές μεθόδους `render()`. Αλλά συνιστούμε να σχεδιάζετε presenters με μία ή όσο το δυνατόν λιγότερες actions. -Έτσι, κλήθηκε η μέθοδος `renderShow(123)`, της οποίας ο κώδικας είναι φανταστικό παράδειγμα, αλλά μπορείτε να δείτε σε αυτό πώς τα δεδομένα περνούν στο πρότυπο, δηλαδή με την εγγραφή στο `$this->template`. +Έτσι, κλήθηκε η μέθοδος `renderShow(123)`, ο κώδικας της οποίας είναι μεν ένα φανταστικό παράδειγμα, αλλά μπορείτε να δείτε σε αυτό πώς μεταβιβάζονται δεδομένα στο πρότυπο, δηλαδή γράφοντας στο `$this->template`. -Στη συνέχεια, ο παρουσιαστής επιστρέφει την απάντηση. Αυτό μπορεί να είναι μια σελίδα HTML, μια εικόνα, ένα έγγραφο XML, η αποστολή ενός αρχείου από το δίσκο, JSON ή η ανακατεύθυνση σε μια άλλη σελίδα. Σημαντικό είναι ότι, αν δεν πούμε ρητά πώς να απαντήσουμε (κάτι που συμβαίνει στην περίπτωση του `ProductPresenter`), η απάντηση θα είναι η απόδοση του προτύπου με μια σελίδα HTML. Γιατί; Λοιπόν, επειδή στο 99% των περιπτώσεων θέλουμε να σχεδιάσουμε ένα πρότυπο, οπότε ο παρουσιαστής θεωρεί αυτή τη συμπεριφορά ως προεπιλεγμένη και θέλει να διευκολύνει τη δουλειά μας. Αυτό είναι το νόημα της Nette. +Στη συνέχεια, ο presenter επιστρέφει μια response. Αυτή μπορεί να είναι μια σελίδα HTML, μια εικόνα, ένα έγγραφο XML, η αποστολή ενός αρχείου από τον δίσκο, JSON ή ίσως μια ανακατεύθυνση σε άλλη σελίδα. Είναι σημαντικό ότι αν δεν πούμε ρητά πώς πρέπει να απαντήσει (που είναι η περίπτωση του `ProductPresenter`), η response θα είναι η απόδοση ενός προτύπου με μια σελίδα HTML. Γιατί; Επειδή στο 99% των περιπτώσεων θέλουμε να αποδώσουμε ένα πρότυπο, επομένως ο presenter θεωρεί αυτή τη συμπεριφορά ως προεπιλεγμένη και θέλει να μας διευκολύνει τη δουλειά. Αυτός είναι ο σκοπός του Nette. -Δεν χρειάζεται καν να δηλώσουμε ποιο πρότυπο θέλουμε να σχεδιάσουμε, αυτός εξάγει τη διαδρομή προς αυτό σύμφωνα με απλή λογική. Στην περίπτωση του presenter `Product` και της δράσης `show`, προσπαθεί να δει αν ένα από αυτά τα αρχεία προτύπων υπάρχει σε σχέση με τον κατάλογο όπου βρίσκεται η κλάση `ProductPresenter`: +Δεν χρειάζεται καν να καθορίσουμε ποιο πρότυπο να αποδοθεί, θα βρει τη διαδρομή προς αυτό μόνος του. Στην περίπτωση της action `show`, απλά θα προσπαθήσει να φορτώσει το πρότυπο `show.latte` στον κατάλογο με την κλάση `ProductPresenter`. Επίσης, θα προσπαθήσει να βρει τη διάταξη στο αρχείο `@layout.latte` (περισσότερα για την [αναζήτηση προτύπων |templates#Αναζήτηση προτύπου]). -- `templates/Product/show.latte` -- `templates/Product.show.latte` - -Θα προσπαθήσει επίσης να βρει τη διάταξη στο αρχείο `@layout.latte` και στη συνέχεια θα αποδώσει το πρότυπο. Τώρα ολοκληρώνεται η εργασία του παρουσιαστή και ολόκληρης της εφαρμογής. Εάν το πρότυπο δεν υπάρχει, θα επιστραφεί μια σελίδα με σφάλμα 404. Μπορείτε να διαβάσετε περισσότερα για τους παρουσιαστές στη σελίδα [Παρουσιαστές |Presenters]. +Και στη συνέχεια αποδίδει τα πρότυπα. Με αυτό, ο στόχος του presenter και ολόκληρης της εφαρμογής ολοκληρώνεται και το έργο τελειώνει. Αν το πρότυπο δεν υπήρχε, θα επιστρεφόταν μια σελίδα με σφάλμα 404. Περισσότερα για τους presenters μπορείτε να διαβάσετε στη σελίδα [Presenters|presenters]. [* request-flow.svg *] -Για να είμαστε σίγουροι, ας προσπαθήσουμε να ανακεφαλαιώσουμε την όλη διαδικασία με μια ελαφρώς διαφορετική διεύθυνση URL: +Για σιγουριά, ας προσπαθήσουμε να ανακεφαλαιώσουμε ολόκληρη τη διαδικασία με ένα ελαφρώς διαφορετικό URL: -1) η διεύθυνση URL θα είναι `https://example.com` -2) εκκινούμε την εφαρμογή, δημιουργούμε έναν περιέκτη και εκτελούμε `Application::run()` -3) ο δρομολογητής αποκωδικοποιεί τη διεύθυνση URL ως ζεύγος `Home:default` -4) δημιουργείται ένα αντικείμενο `HomePresenter` +1) Το URL θα είναι `https://example.com` +2) εκκινούμε την εφαρμογή, δημιουργείται το container και εκτελείται το `Application::run()` +3) ο router αποκωδικοποιεί το URL ως το ζεύγος `Home:default` +4) δημιουργείται το αντικείμενο της κλάσης `HomePresenter` 5) καλείται η μέθοδος `renderDefault()` (αν υπάρχει) -6) αποδίδεται ένα πρότυπο `templates/Home/default.latte` με διάταξη `templates/@layout.latte` +6) αποδίδεται το πρότυπο π.χ. `default.latte` με τη διάταξη π.χ. `@layout.latte` -Μπορεί να έχετε συναντήσει πολλές νέες έννοιες τώρα, αλλά πιστεύουμε ότι βγάζουν νόημα. Η δημιουργία εφαρμογών στη Nette είναι πανεύκολη. +Μπορεί να έχετε συναντήσει τώρα πολλούς νέους όρους, αλλά πιστεύουμε ότι βγάζουν νόημα. Η δημιουργία εφαρμογών στο Nette είναι εξαιρετικά εύκολη. -Πρότυπα .[#toc-templates] -========================= +Πρότυπα +======= -Όσον αφορά τα πρότυπα, η Nette χρησιμοποιεί το σύστημα προτύπων [Latte |latte:]. Αυτός είναι ο λόγος για τον οποίο τα αρχεία με τα πρότυπα τελειώνουν με `.latte`. Το Latte χρησιμοποιείται επειδή είναι το πιο ασφαλές σύστημα προτύπων για την PHP και ταυτόχρονα το πιο διαισθητικό σύστημα. Δεν χρειάζεται να μάθετε πολλά καινούργια, αρκεί να γνωρίζετε την PHP και μερικές ετικέτες Latte. Θα μάθετε τα πάντα στην [τεκμηρίωση |latte:]. +Αφού αναφερθήκαμε στα πρότυπα, στο Nette χρησιμοποιείται το σύστημα προτύπων [Latte |latte:]. Γι' αυτό και οι καταλήξεις `.latte` στα πρότυπα. Το Latte χρησιμοποιείται αφενός επειδή είναι το πιο ασφαλές σύστημα προτύπων για PHP, και αφετέρου το πιο διαισθητικό σύστημα. Δεν χρειάζεται να μάθετε πολλά νέα πράγματα, αρκεί η γνώση της PHP και μερικών ετικετών. Όλα θα τα μάθετε [στην τεκμηρίωση |templates]. -Στο πρότυπο [δημιουργούμε μια σύνδεση |creating-links] με άλλους παρουσιαστές & δράσεις ως εξής: +Στο πρότυπο, [δημιουργούνται σύνδεσμοι |creating-links] προς άλλους presenters & actions ως εξής: ```latte -product detail +λεπτομέρεια προϊόντος ``` -Απλά γράψτε το γνωστό ζεύγος `Presenter:action` αντί της πραγματικής διεύθυνσης URL και συμπεριλάβετε τυχόν παραμέτρους. Το τέχνασμα είναι το `n:href`, το οποίο λέει ότι αυτό το χαρακτηριστικό θα επεξεργαστεί από τη Nette. Και θα δημιουργήσει: +Απλά αντί για το πραγματικό URL, γράφετε το γνωστό ζεύγος `Presenter:action` και καθορίζετε τυχόν παραμέτρους. Το κόλπο είναι στο `n:href`, το οποίο λέει ότι αυτό το attribute θα επεξεργαστεί το Nette. Και θα δημιουργήσει: ```latte -product detail +λεπτομέρεια προϊόντος ``` -Ο δρομολογητής που αναφέρθηκε προηγουμένως είναι υπεύθυνος για τη δημιουργία της διεύθυνσης URL. Στην πραγματικότητα, οι δρομολογητές στη Nette είναι μοναδικοί στο ότι μπορούν να εκτελέσουν όχι μόνο μετασχηματισμούς από μια διεύθυνση URL σε ένα ζεύγος παρουσιαστής:δράση, αλλά και αντίστροφα να δημιουργήσουν μια διεύθυνση URL από το όνομα του παρουσιαστή + δράση + παράμετροι. -Χάρη σε αυτό, στη Nette μπορείτε να αλλάξετε εντελώς τη μορφή της διεύθυνσης URL σε ολόκληρη την ολοκληρωμένη εφαρμογή χωρίς να αλλάξετε ούτε έναν χαρακτήρα στο πρότυπο ή στον παρουσιαστή, απλώς τροποποιώντας τον δρομολογητή. -Και χάρη σε αυτό, λειτουργεί το λεγόμενο canonization, το οποίο είναι ένα άλλο μοναδικό χαρακτηριστικό της Nette, το οποίο βελτιώνει το SEO (βελτιστοποίηση της δυνατότητας αναζήτησης στο διαδίκτυο) αποτρέποντας αυτόματα την ύπαρξη διπλού περιεχομένου σε διαφορετικές διευθύνσεις URL. -Πολλοί προγραμματιστές το βρίσκουν εκπληκτικό αυτό. +Η δημιουργία των URL γίνεται από τον προαναφερθέντα router. Συγκεκριμένα, οι routers στο Nette είναι εξαιρετικοί στο ότι μπορούν να εκτελούν όχι μόνο μετασχηματισμούς από URL σε ζεύγος presenter:action, αλλά και αντίστροφα, δηλαδή από το όνομα του presenter + action + παραμέτρους να δημιουργούν ένα URL. Χάρη σε αυτό, στο Nette μπορείτε να αλλάξετε εντελώς τις μορφές των URL σε ολόκληρη την ολοκληρωμένη εφαρμογή, χωρίς να αλλάξετε ούτε έναν χαρακτήρα στο πρότυπο ή τον presenter. Απλά τροποποιώντας τον router. Επίσης, χάρη σε αυτό λειτουργεί η λεγόμενη κανονικοποίηση, η οποία είναι ένα άλλο μοναδικό χαρακτηριστικό του Nette που συμβάλλει στο καλύτερο SEO (βελτιστοποίηση για μηχανές αναζήτησης) αποτρέποντας αυτόματα την ύπαρξη διπλού περιεχομένου σε διαφορετικά URL. Πολλοί προγραμματιστές το θεωρούν εντυπωσιακό. -Διαδραστικά στοιχεία .[#toc-interactive-components] -=================================================== +Διαδραστικά Components +====================== -Έχουμε κάτι ακόμα να σας πούμε για τους παρουσιαστές: διαθέτουν ένα ενσωματωμένο σύστημα συστατικών. Οι παλαιότεροι από εσάς μπορεί να θυμάστε κάτι παρόμοιο από τους Delphi ή τις ASP.NET Web Forms. Το React ή το Vue.js είναι χτισμένο πάνω σε κάτι εξ αποστάσεως παρόμοιο. Στον κόσμο των πλαισίων PHP, αυτό είναι ένα εντελώς μοναδικό χαρακτηριστικό. +Για τους presenters πρέπει να σας αποκαλύψουμε ακόμα ένα πράγμα: έχουν ενσωματωμένο σύστημα components. Κάτι παρόμοιο μπορεί να θυμούνται οι παλαιότεροι από τα Delphi ή τα ASP.NET Web Forms, ενώ κάτι παρόμοιο αποτελεί τη βάση του React ή του Vue.js. Στον κόσμο των PHP frameworks, πρόκειται για ένα εντελώς μοναδικό χαρακτηριστικό. -Τα συστατικά είναι ξεχωριστές επαναχρησιμοποιήσιμες μονάδες που τοποθετούμε σε σελίδες (δηλαδή παρουσιαστές). Μπορούν να είναι [φόρμες |forms:in-presenter], [datagrids |https://componette.org/contributte/datagrid/], μενού, δημοσκοπήσεις, στην πραγματικότητα οτιδήποτε έχει νόημα να χρησιμοποιείται επανειλημμένα. Μπορούμε να δημιουργήσουμε τα δικά μας συστατικά ή να χρησιμοποιήσουμε κάποιο από το [τεράστιο φάσμα |https://componette.org] των συστατικών ανοιχτού κώδικα. +Τα components είναι ανεξάρτητες, επαναχρησιμοποιήσιμες μονάδες που ενσωματώνουμε σε σελίδες (δηλαδή presenters). Μπορεί να είναι [φόρμες |forms:in-presenter], [datagrids |https://componette.org/contributte/datagrid/], μενού, δημοσκοπήσεις, στην πραγματικότητα οτιδήποτε έχει νόημα να χρησιμοποιείται επανειλημμένα. Μπορούμε να δημιουργήσουμε δικά μας components ή να χρησιμοποιήσουμε κάποια από την [τεράστια προσφορά |https://componette.org] open source components. -Τα συστατικά αλλάζουν ριζικά την προσέγγιση στην ανάπτυξη εφαρμογών. Θα ανοίξουν νέες δυνατότητες για τη σύνθεση σελίδων από προκαθορισμένες μονάδες. Και έχουν κάτι κοινό με το [Χόλιγουντ |components#Hollywood style]. +Τα components επηρεάζουν θεμελιωδώς την προσέγγιση στην ανάπτυξη εφαρμογών. Θα σας ανοίξουν νέες δυνατότητες σύνθεσης σελίδων από προκατασκευασμένες μονάδες. Και επιπλέον, έχουν κάτι κοινό με το [Hollywood |components#Hollywood Style]. -Δοχείο DI και διαμόρφωση παραμέτρων .[#toc-di-container-and-configuration] -========================================================================== +DI container και Διαμόρφωση +=========================== -Το DI container (εργοστάσιο για αντικείμενα) είναι η καρδιά ολόκληρης της εφαρμογής. +Το DI container ή factory αντικειμένων είναι η καρδιά ολόκληρης της εφαρμογής. -Μην ανησυχείτε, δεν είναι ένα μαγικό μαύρο κουτί, όπως μπορεί να φαίνεται από τα προηγούμενα λόγια. Στην πραγματικότητα, είναι μια αρκετά βαρετή κλάση PHP που παράγεται από τη Nette και αποθηκεύεται σε έναν κατάλογο cache. Έχει πολλές μεθόδους που ονομάζονται `createServiceAbcd()` και κάθε μία από αυτές δημιουργεί και επιστρέφει ένα αντικείμενο. Ναι, υπάρχει επίσης μια μέθοδος `createServiceApplication()` που θα παράγει το `Nette\Application\Application`, το οποίο χρειαζόμασταν στο αρχείο `index.php` για να τρέξουμε την εφαρμογή. Και υπάρχουν μέθοδοι για την παραγωγή μεμονωμένων παρουσιαστών. Και ούτω καθεξής. +Μην ανησυχείτε, δεν είναι κάποιο μαγικό μαύρο κουτί, όπως ίσως φάνηκε από τις προηγούμενες γραμμές. Στην πραγματικότητα, είναι μια αρκετά βαρετή κλάση PHP, την οποία δημιουργεί το Nette και την αποθηκεύει στον κατάλογο cache. Έχει πολλές μεθόδους με ονόματα όπως `createServiceAbcd()` και καθεμία από αυτές μπορεί να δημιουργήσει και να επιστρέψει κάποιο αντικείμενο. Ναι, υπάρχει και η μέθοδος `createServiceApplication()`, η οποία δημιουργεί το `Nette\Application\Application`, το οποίο χρειαζόμασταν στο αρχείο `index.php` για την εκκίνηση της εφαρμογής. Και υπάρχουν μέθοδοι που δημιουργούν τους επιμέρους presenters. Και ούτω καθεξής. -Τα αντικείμενα που δημιουργεί ο περιέκτης DI ονομάζονται υπηρεσίες για κάποιο λόγο. +Τα αντικείμενα που δημιουργεί το DI container ονομάζονται για κάποιο λόγο services. -Αυτό που είναι πραγματικά ιδιαίτερο σε αυτή την κλάση είναι ότι δεν προγραμματίζεται από εσάς, αλλά από το πλαίσιο. Στην πραγματικότητα παράγει τον κώδικα PHP και τον αποθηκεύει στο δίσκο. Εσείς απλώς δίνετε οδηγίες για το ποια αντικείμενα πρέπει να μπορεί να παράγει ο περιέκτης και πώς ακριβώς. Και αυτές οι οδηγίες γράφονται σε [αρχεία ρυθμίσεων |bootstrap#DI Container Configuration] σε [μορφή NEON |neon:format] και επομένως έχουν την επέκταση `.neon`. +Αυτό που είναι πραγματικά ιδιαίτερο σε αυτή την κλάση είναι ότι δεν την προγραμματίζετε εσείς, αλλά το framework. Αυτό πράγματι δημιουργεί τον κώδικα PHP και τον αποθηκεύει στον δίσκο. Εσείς απλά δίνετε οδηγίες για το ποια αντικείμενα πρέπει να μπορεί να δημιουργεί το container και πώς ακριβώς. Και αυτές οι οδηγίες είναι γραμμένες στα [αρχεία διαμόρφωσης |bootstrapping#Διαμόρφωση του DI Container], για τα οποία χρησιμοποιείται η μορφή [NEON|neon:format] και επομένως έχουν και την επέκταση `.neon`. -Τα αρχεία ρυθμίσεων χρησιμοποιούνται καθαρά για την παροχή οδηγιών στον περιέκτη DI. Έτσι, για παράδειγμα, αν καθορίσω την επιλογή `expiration: 14 days` στο τμήμα [session |http:configuration#Session], ο DI container κατά τη δημιουργία του αντικειμένου `Nette\Http\Session` που αντιπροσωπεύει το session θα καλέσει τη μέθοδό του `setExpiration('14 days')`, και έτσι η διαμόρφωση γίνεται πραγματικότητα. +Τα αρχεία διαμόρφωσης χρησιμεύουν καθαρά για την καθοδήγηση του DI container. Έτσι, όταν για παράδειγμα αναφέρω στην ενότητα [session |http:configuration#Session] την επιλογή `expiration: 14 days`, τότε το DI container κατά τη δημιουργία του αντικειμένου `Nette\Http\Session` που αντιπροσωπεύει τη session, καλεί τη μέθοδό του `setExpiration('14 days')` και έτσι η διαμόρφωση γίνεται πραγματικότητα. -Υπάρχει ένα ολόκληρο κεφάλαιο έτοιμο για εσάς, που περιγράφει τι μπορεί να [ρυθμιστεί |nette:configuring] και πώς να [ορίσετε τις δικές σας υπηρεσίες |dependency-injection:services]. +Υπάρχει ένα ολόκληρο κεφάλαιο έτοιμο για εσάς που περιγράφει τι μπορείτε να [διαμορφώσετε |nette:configuring] και πώς να [ορίσετε τις δικές σας services |dependency-injection:services]. -Μόλις μπείτε στη δημιουργία υπηρεσιών, θα συναντήσετε τη λέξη [autowiring |dependency-injection:autowiring]. Πρόκειται για ένα gadget που θα κάνει τη ζωή σας απίστευτα πιο εύκολη. Μπορεί να μεταβιβάζει αυτόματα αντικείμενα όπου τα χρειάζεστε (στους κατασκευαστές των κλάσεών σας, για παράδειγμα) χωρίς να χρειάζεται να κάνετε τίποτα. Θα διαπιστώσετε ότι το δοχείο DI στη Nette είναι ένα μικρό θαύμα. +Μόλις εμβαθύνετε λίγο στη δημιουργία services, θα συναντήσετε τη λέξη [autowiring |dependency-injection:autowiring]. Αυτό είναι ένα χαρακτηριστικό που θα απλοποιήσει απίστευτα τη ζωή σας. Μπορεί να μεταβιβάσει αυτόματα αντικείμενα εκεί που τα χρειάζεστε (για παράδειγμα, στους κατασκευαστές των κλάσεών σας), χωρίς να χρειάζεται να κάνετε τίποτα. Θα διαπιστώσετε ότι το DI container στο Nette είναι ένα μικρό θαύμα. -Τι θα γίνει στη συνέχεια; .[#toc-what-next] -=========================================== +Πού να πάτε μετά; +================= -Εξετάσαμε τις βασικές αρχές των εφαρμογών στη Nette. Μέχρι στιγμής, πολύ επιφανειακά, αλλά σύντομα θα εμβαθύνετε στα βάθη και τελικά θα δημιουργήσετε θαυμάσιες διαδικτυακές εφαρμογές. Πού να συνεχίσουμε; Έχετε δοκιμάσει το σεμινάριο [Δημιουργία της πρώτης σας εφαρμογής |quickstart:]; +Έχουμε καλύψει τις βασικές αρχές των εφαρμογών στο Nette. Μέχρι στιγμής πολύ επιφανειακά, αλλά σύντομα θα εμβαθύνετε και με τον καιρό θα δημιουργήσετε υπέροχες διαδικτυακές εφαρμογές. Πού να συνεχίσετε; Έχετε δοκιμάσει ήδη το tutorial [Γράφοντας την πρώτη εφαρμογή|quickstart:]? -Εκτός από τα παραπάνω, η Nette διαθέτει ένα ολόκληρο οπλοστάσιο [χρήσιμων κλάσεων |utils:], [στρώμα βάσης δεδομένων |database:] κ.λπ. Δοκιμάστε σκόπιμα να κάνετε απλώς κλικ μέσα από την τεκμηρίωση. Ή επισκεφθείτε το [ιστολόγιο |https://blog.nette.org]. Θα ανακαλύψετε πολλά ενδιαφέροντα πράγματα. +Εκτός από τα παραπάνω, το Nette διαθέτει ένα ολόκληρο οπλοστάσιο [χρήσιμων κλάσεων|utils:], [επίπεδο βάσης δεδομένων|database:], κ.λπ. Δοκιμάστε απλά να περιηγηθείτε στην τεκμηρίωση. Ή στο [blog|https://blog.nette.org]. Θα ανακαλύψετε πολλά ενδιαφέροντα πράγματα. -Αφήστε το πλαίσιο να σας φέρει πολλή χαρά 💙 +Ας σας φέρει το framework πολλή χαρά 💙 diff --git a/application/el/modules.texy b/application/el/modules.texy deleted file mode 100644 index b2bb1dffd0..0000000000 --- a/application/el/modules.texy +++ /dev/null @@ -1,148 +0,0 @@ -Ενότητες -******** - -.[perex] -Στη Nette, οι ενότητες αντιπροσωπεύουν τις λογικές μονάδες που συνθέτουν μια εφαρμογή. Περιλαμβάνουν παρουσιαστές, πρότυπα, ενδεχομένως επίσης συστατικά και κλάσεις μοντέλων. - -Ένας κατάλογος για τους παρουσιαστές και ένας για τα πρότυπα δεν θα ήταν αρκετός για πραγματικά έργα. Το να έχετε δεκάδες αρχεία σε έναν φάκελο είναι τουλάχιστον ανοργάνωτο. Πώς να απαλλαγείτε από αυτό; Απλώς τα χωρίζουμε σε υποκαταλόγους στο δίσκο και σε χώρους ονομάτων στον κώδικα. Και αυτό ακριβώς κάνουν τα modules της Nette. - -Ας ξεχάσουμε λοιπόν έναν ενιαίο φάκελο για τους παρουσιαστές και τα πρότυπα και ας δημιουργήσουμε αντ' αυτού ενότητες, για παράδειγμα `Admin` και `Front`. - -/--pre -app/ -├── Presenters/ -├── Modules/ ← directory with modules -│ ├── Admin/ ← module Admin -│ │ ├── Presenters/ ← its presenters -│ │ │ ├── DashboardPresenter.php -│ │ │ └── templates/ -│ └── Front/ ← module Front -│ └── Presenters/ ← its presenters -│ └── ... -\-- - -Αυτή η δομή καταλόγου θα αντικατοπτρίζεται από τα namespaces των κλάσεων, έτσι για παράδειγμα το `DashboardPresenter` θα βρίσκεται στο namespace `App\Modules\Admin\Presenters`: - -```php -namespace App\Modules\Admin\Presenters; - -class DashboardPresenter extends Nette\Application\UI\Presenter -{ - // ... -} -``` - -Ο παρουσιαστής `Dashboard` μέσα στην ενότητα `Admin` αναφέρεται μέσα στην εφαρμογή χρησιμοποιώντας τον συμβολισμό της άνω και κάτω τελείας ως `Admin:Dashboard`, και η ενέργεια `default` ως `Admin:Dashboard:default`. -Και πώς γνωρίζει η Nette proper ότι το `Admin:Dashboard` αντιπροσωπεύει την κλάση `App\Modules\Admin\Presenters\DashboardPresenter`; Αυτό καθορίζεται από την [αντιστοίχιση |#mapping] στη διαμόρφωση. -Έτσι, η δεδομένη δομή δεν είναι αυστηρά καθορισμένη και μπορείτε να την τροποποιήσετε ανάλογα με τις ανάγκες σας. - -Οι ενότητες μπορούν φυσικά να περιέχουν όλα τα άλλα στοιχεία εκτός από τους παρουσιαστές και τα πρότυπα, όπως συστατικά, κλάσεις μοντέλων κ.λπ. - - -Ενσωματωμένες ενότητες .[#toc-nested-modules] ---------------------------------------------- - -Οι ενότητες δεν χρειάζεται να σχηματίζουν μόνο μια επίπεδη δομή, μπορείτε επίσης να δημιουργήσετε υποενότητες, για παράδειγμα: - -/--pre -app/ -├── Modules/ ← directory with modules -│ ├── Blog/ ← module Blog -│ │ ├── Admin/ ← submodule Admin -│ │ │ ├── Presenters/ -│ │ │ └── ... -│ │ └── Front/ ← submodule Front -│ │ ├── Presenters/ -│ │ └── ... -│ ├── Forum/ ← module Forum -│ │ └── ... -\-- - -Έτσι, η ενότητα `Blog` χωρίζεται σε υποενότητες `Admin` και `Front`. Και πάλι, αυτό θα αντικατοπτρίζεται στα namespaces, τα οποία θα είναι `App\Modules\Blog\Admin\Presenters` κ.λπ. Ο παρουσιαστής `Dashboard` μέσα στην υποενότητα αναφέρεται ως `Blog:Admin:Dashboard`. - -Η ένθεση μπορεί να προχωρήσει όσο βαθιά θέλετε, οπότε μπορούν να δημιουργηθούν υπο-υποενότητες. - - -Δημιουργία συνδέσμων .[#toc-creating-links] -------------------------------------------- - -Οι σύνδεσμοι στα πρότυπα παρουσιαστή είναι σχετικοί με την τρέχουσα ενότητα. Έτσι, ο σύνδεσμος `Foo:default` οδηγεί στον παρουσιαστή `Foo` στην ίδια ενότητα με τον τρέχοντα παρουσιαστή. Εάν η τρέχουσα ενότητα είναι `Front`, για παράδειγμα, τότε ο σύνδεσμος έχει ως εξής: - -```latte -link to Front:Product:show -``` - -Ένας σύνδεσμος είναι σχετικός ακόμη και αν περιλαμβάνει το όνομα μιας ενότητας, η οποία τότε θεωρείται υποενότητα: - -```latte -link to Front:Shop:Product:show -``` - -Οι απόλυτοι σύνδεσμοι γράφονται ανάλογα με τις απόλυτες διαδρομές στο δίσκο, αλλά με άνω και κάτω τελεία αντί για κάθετους. Έτσι, ένας απόλυτος σύνδεσμος αρχίζει με άνω και κάτω τελεία: - -```latte -link to Admin:Product:show -``` - -Για να μάθουμε αν βρισκόμαστε σε μια συγκεκριμένη ενότητα ή υποενότητά της μπορούμε να χρησιμοποιήσουμε τη συνάρτηση `isModuleCurrent(moduleName)`. - -```latte -
  • - ... -
  • -``` - - -Δρομολόγηση .[#toc-routing] ---------------------------- - -Βλέπε [κεφάλαιο για τη δρομολόγηση |routing#Modules]. - - -Χαρτογράφηση .[#toc-mapping] ----------------------------- - -Καθορίζει τους κανόνες με τους οποίους το όνομα της κλάσης προκύπτει από το όνομα του παρουσιαστή. Τους γράφουμε στη [διαμόρφωση |configuration] κάτω από το κλειδί `application › mapping`. - -Ας ξεκινήσουμε με ένα δείγμα που δεν χρησιμοποιεί ενότητες. Θα θέλουμε απλώς οι κλάσεις presenter να έχουν το namespace `App\Presenters`. Αυτό σημαίνει ότι ένας παρουσιαστής όπως το `Home` θα πρέπει να αντιστοιχίζεται στην κλάση `App\Presenters\HomePresenter`. Αυτό μπορεί να επιτευχθεί με την ακόλουθη διαμόρφωση: - -```neon -application: - mapping: - *: App\Presenters\*Presenter -``` - -Το όνομα του παρουσιαστή αντικαθίσταται με τον αστερίσκο στη μάσκα κλάσης και το αποτέλεσμα είναι το όνομα της κλάσης. Εύκολο! - -Αν χωρίσουμε τους παρουσιαστές σε ενότητες, μπορούμε να έχουμε τη δική μας χαρτογράφηση για κάθε ενότητα: - -```neon -application: - mapping: - Front: App\Modules\Front\Presenters\*Presenter - Admin: App\Modules\Admin\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -Τώρα ο παρουσιαστής `Front:Home` αντιστοιχίζεται στην κλάση `App\Modules\Front\Presenters\HomePresenter` και ο παρουσιαστής `Admin:Dashboard` στην κλάση `App\Modules\Admin\Presenters\DashboardPresenter`. - -Είναι πιο πρακτικό να δημιουργήσετε έναν γενικό κανόνα (αστέρι) για να αντικαταστήσετε τους δύο πρώτους. Ο επιπλέον αστερίσκος θα προστεθεί στη μάσκα κλάσης μόνο για την ενότητα: - -```neon -application: - mapping: - *: App\Modules\*\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -Τι γίνεται όμως αν χρησιμοποιούμε φωλιασμένες ενότητες και έχουμε έναν παρουσιαστή `Admin:User:Edit`; Σε αυτή την περίπτωση, το τμήμα με τον αστερίσκο που αντιπροσωπεύει την ενότητα για κάθε επίπεδο απλώς επαναλαμβάνεται και το αποτέλεσμα είναι η κλάση `App\Modules\Admin\User\Presenters\EditPresenter`. - -Ένας εναλλακτικός συμβολισμός είναι η χρήση ενός πίνακα που αποτελείται από τρία τμήματα αντί για συμβολοσειρά. Αυτή η σημειογραφία είναι ισοδύναμη με την προηγούμενη: - -```neon -application: - mapping: - *: [App\Modules, *, Presenters\*Presenter] -``` - -Η προεπιλεγμένη τιμή είναι `*: *Module\*Presenter`. diff --git a/application/el/multiplier.texy b/application/el/multiplier.texy index 174e97ce0c..daec1e64bb 100644 --- a/application/el/multiplier.texy +++ b/application/el/multiplier.texy @@ -1,30 +1,32 @@ -Πολλαπλασιαστής: Δυναμικά στοιχεία -********************************** +Πολλαπλασιαστής: Δυναμικά Components +************************************ -Ένα εργαλείο για τη δυναμική δημιουργία διαδραστικών στοιχείων .[perex] +.[perex] +Εργαλείο για δυναμική δημιουργία διαδραστικών components -Ας ξεκινήσουμε με ένα τυπικό πρόβλημα: έχουμε μια λίστα προϊόντων σε έναν ιστότοπο ηλεκτρονικού εμπορίου και θέλουμε να συνοδεύσουμε κάθε προϊόν με μια φόρμα *προσθήκης στο καλάθι*. Ένας τρόπος είναι να τυλίξουμε ολόκληρη τη λίστα σε μια ενιαία φόρμα. Ένας πιο βολικός τρόπος είναι να χρησιμοποιήσουμε το [api:Nette\Application\UI\Multiplier]. +Ας ξεκινήσουμε από ένα τυπικό παράδειγμα: έχουμε μια λίστα προϊόντων σε ένα e-shop, και για καθένα θέλουμε να εμφανίσουμε μια φόρμα για την προσθήκη του προϊόντος στο καλάθι. Μια πιθανή παραλλαγή είναι να περικλείσουμε ολόκληρη τη λίστα σε μια ενιαία φόρμα. Ωστόσο, ένας πολύ πιο βολικός τρόπος μας προσφέρεται από το [api:Nette\Application\UI\Multiplier]. -Ο πολλαπλασιαστής σας επιτρέπει να ορίσετε ένα εργοστάσιο για πολλαπλά στοιχεία. Βασίζεται στην αρχή των εμφωλευμένων συστατικών - κάθε συστατικό που κληρονομεί από το [api:Nette\ComponentModel\Container] μπορεί να περιέχει άλλα συστατικά. +Ο Multiplier επιτρέπει τον βολικό ορισμό ενός μικρού factory για πολλαπλά components. Λειτουργεί με την αρχή των ένθετων components - κάθε component που κληρονομεί από το [api:Nette\ComponentModel\Container] μπορεί να περιέχει άλλα components. -Δείτε το [μοντέλο συστατικών |components#Components in Depth] στην τεκμηρίωση. .[tip] +.[tip] +Δείτε το κεφάλαιο για το [μοντέλο component |components#Components σε Βάθος] στην τεκμηρίωση ή την [παρουσίαση του Honza Tvrdík|https://www.youtube.com/watch?v=8y3LLexWu-I]. -Το Multiplier παριστάνει το γονικό συστατικό το οποίο μπορεί να δημιουργήσει δυναμικά τα παιδιά του χρησιμοποιώντας το callback που περνάει στον κατασκευαστή. Βλέπε παράδειγμα: +Η ουσία του Multiplier είναι ότι λειτουργεί ως γονέας που μπορεί να δημιουργήσει δυναμικά τα παιδιά του χρησιμοποιώντας ένα callback που περνιέται στον κατασκευαστή. Δείτε το παράδειγμα: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function () { $form = new Nette\Application\UI\Form; - $form->addInteger('amount', 'Amount:') + $form->addInteger('count', 'Πλήθος ειδών:') ->setRequired(); - $form->addSubmit('send', 'Add to cart'); + $form->addSubmit('send', 'Προσθήκη στο καλάθι'); return $form; }); } ``` -Στο πρότυπο μπορούμε να αποδώσουμε μια φόρμα για κάθε προϊόν - και κάθε φόρμα θα είναι πράγματι ένα μοναδικό συστατικό. +Τώρα μπορούμε απλά στο πρότυπο να αφήσουμε να αποδοθεί η φόρμα για κάθε προϊόν - και καθένα θα είναι πραγματικά ένα μοναδικό component. ```latte {foreach $items as $item} @@ -35,26 +37,26 @@ protected function createComponentShopForm(): Multiplier {/foreach} ``` -Το επιχείρημα που περνάει στην ετικέτα `{control}` λέει: +Το όρισμα που περνιέται στην ετικέτα `{control}` είναι σε μορφή που λέει: -1. `shopForm` -2. και να επιστρέψει το παιδί του `$item->id` +1. πάρε το component `shopForm` +2. και από αυτό πάρε τον απόγονο `$item->id` -Κατά την πρώτη κλήση της **1.** το συστατικό `shopForm` δεν υπάρχει ακόμη, οπότε καλείται η μέθοδος `createComponentShopForm` για τη δημιουργία του. Στη συνέχεια καλείται μια ανώνυμη συνάρτηση που περνά ως παράμετρος στον Πολλαπλασιαστή και δημιουργείται μια φόρμα. +Κατά την πρώτη κλήση του σημείου **1.** το `shopForm` δεν υπάρχει ακόμα, οπότε καλείται το factory του `createComponentShopForm`. Στο ληφθέν component (παρουσία του Multiplier) καλείται στη συνέχεια το factory της συγκεκριμένης φόρμας - που είναι η ανώνυμη συνάρτηση που περάσαμε στον Multiplier στον κατασκευαστή. -Στις επόμενες επαναλήψεις του `foreach` η μέθοδος `createComponentShopForm` δεν καλείται πλέον επειδή το συστατικό υπάρχει ήδη. Επειδή όμως αναφερόμαστε σε ένα άλλο παιδί (`$item->id` μεταβάλλεται μεταξύ των επαναλήψεων), καλείται ξανά μια ανώνυμη συνάρτηση και δημιουργείται μια νέα φόρμα. +Στην επόμενη επανάληψη του foreach, η μέθοδος `createComponentShopForm` δεν θα κληθεί πλέον (το component υπάρχει), αλλά επειδή ψάχνουμε για έναν άλλο απόγονό του (`$item->id` θα είναι διαφορετικό σε κάθε επανάληψη), η ανώνυμη συνάρτηση θα κληθεί ξανά και θα μας επιστρέψει μια νέα φόρμα. -Το τελευταίο πράγμα είναι να διασφαλίσουμε ότι η φόρμα προσθέτει πράγματι το σωστό προϊόν στο καλάθι, διότι στην τρέχουσα κατάσταση όλες οι φόρμες είναι ίδιες και δεν μπορούμε να διακρίνουμε σε ποια προϊόντα ανήκουν. Για αυτό μπορούμε να χρησιμοποιήσουμε την ιδιότητα του Multiplier (και γενικά κάθε μεθόδου εργοστασίου συστατικών στο Nette Framework) ότι κάθε μέθοδος εργοστασίου συστατικών λαμβάνει ως πρώτο όρισμα το όνομα του συστατικού που δημιουργήθηκε. Στην περίπτωσή μας αυτό θα ήταν το `$item->id`, το οποίο είναι ακριβώς αυτό που χρειαζόμαστε για να διακρίνουμε τα επιμέρους προϊόντα. Το μόνο που χρειάζεται να κάνετε είναι να τροποποιήσετε τον κώδικα για τη δημιουργία της φόρμας: +Το μόνο που μένει είναι να διασφαλίσουμε ότι η φόρμα προσθέτει στο καλάθι πραγματικά το προϊόν που πρέπει - αυτή τη στιγμή η φόρμα είναι εντελώς ίδια για κάθε προϊόν. Η ιδιότητα του Multiplier (και γενικά κάθε factory component στο Nette Framework) θα μας βοηθήσει, και αυτή είναι ότι κάθε factory λαμβάνει ως πρώτο του όρισμα το όνομα του component που δημιουργείται. Στην περίπτωσή μας, αυτό θα είναι το `$item->id`, που είναι ακριβώς η πληροφορία που χρειαζόμαστε. Αρκεί λοιπόν να τροποποιήσουμε ελαφρώς τη δημιουργία της φόρμας: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function ($itemId) { $form = new Nette\Application\UI\Form; - $form->addInteger('amount', 'Amount:') + $form->addInteger('count', 'Πλήθος ειδών:') ->setRequired(); $form->addHidden('itemId', $itemId); - $form->addSubmit('send', 'Add to cart'); + $form->addSubmit('send', 'Προσθήκη στο καλάθι'); return $form; }); } diff --git a/application/el/presenters.texy b/application/el/presenters.texy index a8d2b94611..a8d6a6f9a8 100644 --- a/application/el/presenters.texy +++ b/application/el/presenters.texy @@ -1,39 +1,39 @@ -Παρουσιαστές -************ +Presenters +**********
    -Θα μάθουμε πώς να γράφουμε παρουσιαστές και πρότυπα στη Nette. Μετά την ανάγνωση θα ξέρετε: +Θα εξοικειωθούμε με τον τρόπο συγγραφής presenters και προτύπων στο Nette. Μετά την ανάγνωση, θα γνωρίζετε: -- πώς λειτουργεί ο παρουσιαστής -- τι είναι οι μόνιμες παράμετροι -- πώς να αποδώσετε ένα πρότυπο +- πώς λειτουργεί ένας presenter +- τι είναι οι persistent παράμετροι +- πώς αποδίδονται τα πρότυπα
    -[Γνωρίζουμε ήδη |how-it-works#nette-application] ότι ένας παρουσιαστής είναι μια κλάση που αναπαριστά μια συγκεκριμένη σελίδα μιας διαδικτυακής εφαρμογής, όπως μια αρχική σελίδα, ένα προϊόν στο ηλεκτρονικό κατάστημα, μια φόρμα εγγραφής, μια τροφοδοσία χάρτη σελίδων κ.λπ. Η εφαρμογή μπορεί να έχει από έναν έως χιλιάδες παρουσιαστές. Σε άλλα πλαίσια, είναι επίσης γνωστοί ως ελεγκτές. +[Γνωρίζουμε ήδη |how-it-works#Nette Application] ότι ένας presenter είναι μια κλάση που αντιπροσωπεύει μια συγκεκριμένη σελίδα μιας διαδικτυακής εφαρμογής, π.χ. την αρχική σελίδα, ένα προϊόν σε ένα e-shop, μια φόρμα σύνδεσης, ένα sitemap feed κ.λπ. Μια εφαρμογή μπορεί να έχει από έναν έως χιλιάδες presenters. Σε άλλα frameworks, ονομάζονται επίσης controllers. -Συνήθως, ο όρος παρουσιαστής αναφέρεται σε έναν απόγονο της κλάσης [api:Nette\Application\UI\Presenter], η οποία είναι κατάλληλη για διεπαφές ιστού και την οποία θα συζητήσουμε στο υπόλοιπο του κεφαλαίου. Σε γενικές γραμμές, παρουσιαστής είναι κάθε αντικείμενο που υλοποιεί τη διεπαφή [api:Nette\Application\IPresenter]. +Συνήθως, με τον όρο presenter εννοούμε έναν απόγονο της κλάσης [api:Nette\Application\UI\Presenter], ο οποίος είναι κατάλληλος για τη δημιουργία διαδικτυακών διεπαφών και στον οποίο θα επικεντρωθούμε στο υπόλοιπο αυτού του κεφαλαίου. Με γενική έννοια, ένας presenter είναι οποιοδήποτε αντικείμενο που υλοποιεί το interface [api:Nette\Application\IPresenter]. -Κύκλος ζωής του παρουσιαστή .[#toc-life-cycle-of-presenter] -=========================================================== +Κύκλος ζωής του presenter +========================= -Η δουλειά του παρουσιαστή είναι να επεξεργάζεται το αίτημα και να επιστρέφει μια απάντηση (η οποία μπορεί να είναι μια σελίδα HTML, μια εικόνα, μια ανακατεύθυνση κ.λπ.). +Ο ρόλος του presenter είναι να διεκπεραιώσει ένα αίτημα και να επιστρέψει μια response (η οποία μπορεί να είναι μια σελίδα HTML, μια εικόνα, μια ανακατεύθυνση κ.λπ.). -Έτσι, στην αρχή υπάρχει ένα αίτημα. Δεν είναι άμεσα ένα αίτημα HTTP, αλλά ένα αντικείμενο [api:Nette\Application\Request] στο οποίο μετατράπηκε το αίτημα HTTP με τη χρήση ενός δρομολογητή. Συνήθως δεν ερχόμαστε σε επαφή με αυτό το αντικείμενο, επειδή ο παρουσιαστής αναθέτει έξυπνα την επεξεργασία του αιτήματος σε ειδικές μεθόδους, τις οποίες θα δούμε τώρα. +Έτσι, στην αρχή, του παραδίδεται ένα αίτημα. Δεν είναι απευθείας ένα αίτημα HTTP, αλλά ένα αντικείμενο [api:Nette\Application\Request], στο οποίο το αίτημα HTTP μετασχηματίστηκε με τη βοήθεια του router. Συνήθως δεν ερχόμαστε σε επαφή με αυτό το αντικείμενο, καθώς ο presenter αναθέτει έξυπνα την επεξεργασία του αιτήματος σε άλλες μεθόδους, τις οποίες θα δείξουμε τώρα. -[* lifecycle.svg *] *** *Κύκλος ζωής του παρουσιαστή* .<> +[* lifecycle.svg *] *** *Κύκλος ζωής του presenter* .<> -Το σχήμα δείχνει μια λίστα μεθόδων που καλούνται διαδοχικά από πάνω προς τα κάτω, εφόσον υπάρχουν. Καμία από αυτές δεν χρειάζεται να υπάρχει, μπορούμε να έχουμε έναν εντελώς άδειο presenter χωρίς ούτε μία μέθοδο και να φτιάξουμε έναν απλό στατικό ιστό πάνω του. +Η εικόνα παρουσιάζει μια λίστα μεθόδων που καλούνται διαδοχικά από πάνω προς τα κάτω, αν υπάρχουν. Καμία από αυτές δεν χρειάζεται να υπάρχει, μπορούμε να έχουμε έναν εντελώς κενό presenter χωρίς ούτε μία μέθοδο και να χτίσουμε πάνω του έναν απλό στατικό ιστότοπο. `__construct()` --------------- -Ο κατασκευαστής δεν ανήκει ακριβώς στον κύκλο ζωής του παρουσιαστή, επειδή καλείται τη στιγμή της δημιουργίας του αντικειμένου. Τον αναφέρουμε όμως λόγω της σημασίας του. Ο κατασκευαστής (μαζί με τη [μέθοδο inject |best-practices:inject-method-attribute]) χρησιμοποιείται για να περάσει εξαρτήσεις. +Ο κατασκευαστής δεν ανήκει ακριβώς στον κύκλο ζωής του presenter, επειδή καλείται τη στιγμή της δημιουργίας του αντικειμένου. Αλλά τον αναφέρουμε λόγω της σημασίας του. Ο κατασκευαστής (μαζί με τη [μέθοδο inject|best-practices:inject-method-attribute]) χρησιμεύει για τη μεταβίβαση εξαρτήσεων. -Ο παρουσιαστής δεν πρέπει να φροντίζει την επιχειρησιακή λογική της εφαρμογής, να γράφει και να διαβάζει από τη βάση δεδομένων, να εκτελεί υπολογισμούς κ.λπ. Αυτό είναι το καθήκον για τις κλάσεις από ένα επίπεδο, το οποίο ονομάζουμε μοντέλο. Για παράδειγμα, η κλάση `ArticleRepository` μπορεί να είναι υπεύθυνη για τη φόρτωση και την αποθήκευση άρθρων. Προκειμένου ο παρουσιαστής να τη χρησιμοποιήσει, [περνάει χρησιμοποιώντας την έγχυση εξάρτησης (dependency injection |dependency-injection:passing-dependencies]): +Ο presenter δεν θα πρέπει να χειρίζεται την επιχειρηματική λογική της εφαρμογής, να γράφει και να διαβάζει από τη βάση δεδομένων, να εκτελεί υπολογισμούς κ.λπ. Γι' αυτό υπάρχουν κλάσεις από το επίπεδο που ονομάζουμε model. Για παράδειγμα, η κλάση `ArticleRepository` μπορεί να είναι υπεύθυνη για τη φόρτωση και την αποθήκευση άρθρων. Για να μπορεί ο presenter να συνεργαστεί μαζί της, ζητά να του [περαστεί μέσω dependency injection |dependency-injection:passing-dependencies]: ```php @@ -50,44 +50,44 @@ class ArticlePresenter extends Nette\Application\UI\Presenter `startup()` ----------- -Αμέσως μετά τη λήψη της αίτησης, καλείται η μέθοδος `startup ()`. Μπορείτε να τη χρησιμοποιήσετε για να αρχικοποιήσετε τις ιδιότητες, να ελέγξετε τα προνόμια του χρήστη κ.λπ. Απαιτείται να καλείται πάντα ο πρόγονος `parent::startup()`. +Αμέσως μετά τη λήψη του αιτήματος, καλείται η μέθοδος `startup()`. Μπορείτε να τη χρησιμοποιήσετε για την αρχικοποίηση ιδιοτήτων, την επαλήθευση δικαιωμάτων χρήστη κ.λπ. Απαιτείται η μέθοδος να καλεί πάντα τον πρόγονο `parent::startup()`. `action(args...)` .{toc: action()} -------------------------------------------------- -Παρόμοια με τη μέθοδο `render()`. Ενώ `render()` προορίζεται για την προετοιμασία δεδομένων για ένα συγκεκριμένο πρότυπο, το οποίο στη συνέχεια αποδίδεται, στην `action()` μια αίτηση επεξεργάζεται χωρίς να ακολουθεί η απόδοση του προτύπου. Για παράδειγμα, γίνεται επεξεργασία δεδομένων, γίνεται είσοδος ή έξοδος ενός χρήστη κ.ο.κ. και στη συνέχεια γίνεται [ανακατεύθυνση αλλού |#Redirection]. +Αντίστοιχο της μεθόδου `render()`. Ενώ η `render()` προορίζεται για την προετοιμασία δεδομένων για ένα συγκεκριμένο πρότυπο που θα αποδοθεί στη συνέχεια, στην `action()` επεξεργάζεται το αίτημα χωρίς σύνδεση με την απόδοση του προτύπου. Για παράδειγμα, επεξεργάζονται δεδομένα, συνδέεται ή αποσυνδέεται ο χρήστης, και ούτω καθεξής, και στη συνέχεια [ανακατευθύνεται αλλού |#Ανακατεύθυνση]. -Είναι σημαντικό ότι `action()` καλείται πριν από την `render()`, ώστε μέσα σε αυτό να μπορούμε ενδεχομένως να αλλάξουμε την επόμενη πορεία του κύκλου ζωής, δηλαδή να αλλάξουμε το πρότυπο που θα αποδοθεί και επίσης τη μέθοδο `render()` που θα κληθεί, χρησιμοποιώντας το `setView('otherView')`. +Είναι σημαντικό ότι η `action()` καλείται νωρίτερα από την `render()`, οπότε σε αυτήν μπορούμε ενδεχομένως να αλλάξουμε την περαιτέρω πορεία των γεγονότων, δηλαδή να αλλάξουμε το πρότυπο που θα αποδοθεί, καθώς και τη μέθοδο `render()` που θα κληθεί. Και αυτό γίνεται χρησιμοποιώντας το `setView('jineView')`. -Οι παράμετροι από το αίτημα περνούν στη μέθοδο. Είναι δυνατόν και συνιστάται να καθορίσετε τύπους για τις παραμέτρους, π.χ. `actionShow(int $id, string $slug = null)` - αν η παράμετρος `id` λείπει ή αν δεν είναι ακέραιος αριθμός, ο παρουσιαστής επιστρέφει [σφάλμα 404 |#Error 404 etc.] και τερματίζει τη λειτουργία. +Στη μέθοδο μεταβιβάζονται παράμετροι από το αίτημα. Είναι δυνατό και συνιστάται να καθορίσετε τύπους για τις παραμέτρους, π.χ. `actionShow(int $id, ?string $slug = null)` - αν η παράμετρος `id` λείπει ή αν δεν είναι integer, ο presenter θα επιστρέψει [σφάλμα 404 |#Σφάλμα 404 κ.λπ] και θα τερματίσει τη λειτουργία του. `handle(args...)` .{toc: handle()} -------------------------------------------------- -Αυτή η μέθοδος επεξεργάζεται τα λεγόμενα σήματα, τα οποία θα συζητήσουμε στο κεφάλαιο για τα [συστατικά |components#Signal]. Προορίζεται κυρίως για συστατικά και επεξεργασία αιτήσεων AJAX. +Η μέθοδος επεξεργάζεται τα λεγόμενα signals, με τα οποία θα εξοικειωθούμε στο κεφάλαιο που είναι αφιερωμένο στα [components |components#Σήμα]. Προορίζεται κυρίως για components και την επεξεργασία αιτήσεων AJAX. -Οι παράμετροι μεταβιβάζονται στη μέθοδο, όπως στην περίπτωση της `action()`, συμπεριλαμβανομένου του ελέγχου τύπου. +Στη μέθοδο μεταβιβάζονται παράμετροι από το αίτημα, όπως στην περίπτωση της `action()`, συμπεριλαμβανομένου του ελέγχου τύπου. `beforeRender()` ---------------- -Η μέθοδος `beforeRender`, όπως υποδηλώνει το όνομα, καλείται πριν από κάθε μέθοδο `render()`. Χρησιμοποιείται για την κοινή διαμόρφωση του προτύπου, το πέρασμα μεταβλητών για τη διάταξη και ούτω καθεξής. +Η μέθοδος `beforeRender`, όπως υποδηλώνει και το όνομά της, καλείται πριν από κάθε μέθοδο `render()`. Χρησιμοποιείται για την κοινή διαμόρφωση του προτύπου, τη μεταβίβαση μεταβλητών για τη διάταξη και παρόμοια. `render(args...)` .{toc: render()} ---------------------------------------------- -Το μέρος όπου προετοιμάζουμε το πρότυπο για την μετέπειτα απόδοση, του περνάμε δεδομένα κ.λπ. +Το μέρος όπου προετοιμάζουμε το πρότυπο για την επακόλουθη απόδοση, του μεταβιβάζουμε δεδομένα κ.λπ. -Οι παράμετροι περνούν στη μέθοδο, όπως στην περίπτωση της `action()`, συμπεριλαμβανομένου του ελέγχου τύπου. +Στη μέθοδο μεταβιβάζονται παράμετροι από το αίτημα, όπως στην περίπτωση της `action()`, συμπεριλαμβανομένου του ελέγχου τύπου. ```php public function renderShow(int $id): void { - // λαμβάνουμε δεδομένα από το μοντέλο και τα περνάμε στο πρότυπο + // λήψη δεδομένων από το μοντέλο και μεταβίβασή τους στο πρότυπο $this->template->article = $this->articles->getById($id); } ``` @@ -96,104 +96,104 @@ public function renderShow(int $id): void `afterRender()` --------------- -Η μέθοδος `afterRender`, όπως υποδηλώνει και πάλι το όνομα, καλείται μετά από κάθε `render()` μέθοδο. Χρησιμοποιείται μάλλον σπάνια. +Η μέθοδος `afterRender`, όπως υποδηλώνει ξανά το όνομα, καλείται μετά από κάθε μέθοδο `render()`. Χρησιμοποιείται μάλλον σπάνια. `shutdown()` ------------ -Καλείται στο τέλος του κύκλου ζωής του παρουσιαστή. +Καλείται στο τέλος του κύκλου ζωής του presenter. -**Καλή συμβουλή πριν προχωρήσουμε**. Όπως μπορείτε να δείτε, ο παρουσιαστής μπορεί να χειριστεί περισσότερες ενέργειες/προβολές, δηλαδή να έχει περισσότερες μεθόδους `render()`. Αλλά συνιστούμε να σχεδιάζετε παρουσιαστές με μία ή όσο το δυνατόν λιγότερες ενέργειες. +**Καλή συμβουλή, πριν προχωρήσουμε**. Ο presenter, όπως φαίνεται, μπορεί να εξυπηρετεί πολλαπλές actions/views, δηλαδή να έχει πολλαπλές μεθόδους `render()`. Αλλά συνιστούμε να σχεδιάζετε presenters με μία ή όσο το δυνατόν λιγότερες actions. -Αποστολή μιας απάντησης .[#toc-sending-a-response] -================================================== +Αποστολή απάντησης +================== -Η απάντηση του παρουσιαστή είναι συνήθως η [απόδοση του προτύπου με τη σελίδα HTML |templates], αλλά μπορεί επίσης να είναι η αποστολή ενός αρχείου, JSON ή ακόμα και η ανακατεύθυνση σε μια άλλη σελίδα. +Η response του presenter είναι συνήθως η [απόδοση ενός προτύπου με μια σελίδα HTML|templates], αλλά μπορεί επίσης να είναι η αποστολή ενός αρχείου, JSON ή ίσως μια ανακατεύθυνση σε άλλη σελίδα. -Σε οποιαδήποτε στιγμή κατά τη διάρκεια του κύκλου ζωής, μπορείτε να χρησιμοποιήσετε οποιαδήποτε από τις παρακάτω μεθόδους για να στείλετε μια απάντηση και να βγείτε από τον παρουσιαστή ταυτόχρονα: +Οποιαδήποτε στιγμή κατά τη διάρκεια του κύκλου ζωής, μπορούμε να στείλουμε μια response με μία από τις παρακάτω μεθόδους και ταυτόχρονα να τερματίσουμε τον presenter: -- `redirect()`, `redirectPermanent()`, `redirectUrl()` και `forward()` [ανακατευθύνσεις |#Redirection] -- `error()` τερματίζει τον παρουσιαστή [λόγω σφάλματος |#Error 404 etc.] -- `sendJson($data)` εγκαταλείπει τον παρουσιαστή και [αποστέλλει τα δεδομένα |#Sending JSON] σε μορφή JSON -- `sendTemplate()` εγκαταλείπει τον παρουσιαστή και αποδίδει αμέσως [το πρότυπο |templates]. -- `sendResponse($response)` εγκαταλείπει τον παρουσιαστή και αποστέλλει [τη δική του απάντηση |#Responses]. -- `terminate()` εγκαταλείπει τον παρουσιαστή χωρίς απάντηση +- `redirect()`, `redirectPermanent()`, `redirectUrl()` και `forward()` [ανακατευθύνουν |#Ανακατεύθυνση] +- `error()` τερματίζει τον presenter [λόγω σφάλματος |#Σφάλμα 404 κ.λπ] +- `sendJson($data)` τερματίζει τον presenter και [στέλνει δεδομένα |#Αποστολή JSON] σε μορφή JSON +- `sendTemplate()` τερματίζει τον presenter και αμέσως [αποδίδει το πρότυπο |templates] +- `sendResponse($response)` τερματίζει τον presenter και στέλνει [μια προσαρμοσμένη response |#Απαντήσεις] +- `terminate()` τερματίζει τον presenter χωρίς response -Εάν δεν καλέσετε καμία από αυτές τις μεθόδους, ο παρουσιαστής θα προχωρήσει αυτόματα στην απόδοση του προτύπου. Γιατί; Λοιπόν, επειδή στο 99% των περιπτώσεων θέλουμε να σχεδιάσουμε ένα πρότυπο, οπότε ο παρουσιαστής θεωρεί αυτή τη συμπεριφορά ως προεπιλεγμένη και θέλει να διευκολύνει τη δουλειά μας. +Αν δεν καλέσετε καμία από αυτές τις μεθόδους, ο presenter θα προχωρήσει αυτόματα στην απόδοση του προτύπου. Γιατί; Επειδή στο 99% των περιπτώσεων θέλουμε να αποδώσουμε ένα πρότυπο, επομένως ο presenter θεωρεί αυτή τη συμπεριφορά ως προεπιλεγμένη και θέλει να μας διευκολύνει τη δουλειά. -Δημιουργία συνδέσμων .[#toc-creating-links] -=========================================== +Δημιουργία συνδέσμων +==================== -Ο παρουσιαστής διαθέτει τη μέθοδο `link()`, η οποία χρησιμοποιείται για τη δημιουργία συνδέσμων URL προς άλλους παρουσιαστές. Η πρώτη παράμετρος είναι ο παρουσιαστής & η ενέργεια-στόχος, ακολουθούμενη από τα ορίσματα, τα οποία μπορούν να περάσουν ως πίνακας: +Ο presenter διαθέτει τη μέθοδο `link()`, με την οποία μπορείτε να δημιουργήσετε συνδέσμους URL προς άλλους presenters. Η πρώτη παράμετρος είναι ο presenter & η action προορισμού, ακολουθούν τα μεταβιβαζόμενα ορίσματα, τα οποία μπορούν να καθοριστούν ως array: ```php $url = $this->link('Product:show', $id); -$url = $this->link('Product:show', [$id, 'lang' => 'en']); +$url = $this->link('Product:show', [$id, 'lang' => 'cs']); ``` -Στο πρότυπο δημιουργούμε συνδέσμους προς άλλους παρουσιαστές & ενέργειες ως εξής: +Στο πρότυπο, οι σύνδεσμοι προς άλλους presenters & actions δημιουργούνται με αυτόν τον τρόπο: ```latte -product detail +λεπτομέρεια προϊόντος ``` -Απλά γράψτε το γνωστό ζεύγος `Presenter:action` αντί της πραγματικής διεύθυνσης URL και συμπεριλάβετε τυχόν παραμέτρους. Το κόλπο είναι το `n:href`, το οποίο λέει ότι αυτό το χαρακτηριστικό θα επεξεργαστεί από το Latte και θα δημιουργήσει ένα πραγματικό URL. Στη Nette, δεν χρειάζεται να σκέφτεστε καθόλου τις διευθύνσεις URL, παρά μόνο τους παρουσιαστές και τις ενέργειες. +Απλά αντί για το πραγματικό URL, γράφετε το γνωστό ζεύγος `Presenter:action` και καθορίζετε τυχόν παραμέτρους. Το κόλπο είναι στο `n:href`, το οποίο λέει ότι αυτό το attribute θα επεξεργαστεί το Latte και θα δημιουργήσει το πραγματικό URL. Στο Nette, επομένως, δεν χρειάζεται καθόλου να σκέφτεστε τα URL, μόνο τους presenters και τις actions. -Για περισσότερες πληροφορίες, ανατρέξτε στην ενότητα [Δημιουργία συνδέσμων |Creating Links]. +Περισσότερες πληροφορίες θα βρείτε στο κεφάλαιο [Δημιουργία συνδέσμων URL|creating-links]. -Ανακατεύθυνση .[#toc-redirection] -================================= +Ανακατεύθυνση +============= -Οι μέθοδοι `redirect()` και `forward()` χρησιμοποιούνται για τη μετάβαση σε έναν άλλο παρουσιαστή, οι οποίες έχουν πολύ παρόμοια σύνταξη με τη μέθοδο [link() |#Creating Links]. +Για τη μετάβαση σε άλλο presenter, χρησιμοποιούνται οι μέθοδοι `redirect()` και `forward()`, οι οποίες έχουν πολύ παρόμοια σύνταξη με τη μέθοδο [link() |#Δημιουργία συνδέσμων]. -Η `forward()` μεταβαίνει στον νέο παρουσιαστή αμέσως χωρίς ανακατεύθυνση HTTP: +Η μέθοδος `forward()` μεταβαίνει στον νέο presenter αμέσως χωρίς ανακατεύθυνση HTTP: ```php $this->forward('Product:show'); ``` -με κωδικό HTTP 302 ή 303: +Παράδειγμα της λεγόμενης προσωρινής ανακατεύθυνσης με κωδικό HTTP 302 (ή 303, αν η μέθοδος της τρέχουσας αίτησης είναι POST): ```php $this->redirect('Product:show', $id); ``` -Για να επιτύχετε μόνιμη ανακατεύθυνση με κωδικό HTTP 301 χρησιμοποιήστε: +Μόνιμη ανακατεύθυνση με κωδικό HTTP 301 επιτυγχάνεται ως εξής: ```php $this->redirectPermanent('Product:show', $id); ``` -Μπορείτε να ανακατευθύνετε σε μια άλλη διεύθυνση URL εκτός της εφαρμογής με τη μέθοδο `redirectUrl()`: +Σε άλλη διεύθυνση URL εκτός της εφαρμογής μπορείτε να ανακατευθύνετε με τη μέθοδο `redirectUrl()`. Ως δεύτερη παράμετρο, μπορείτε να καθορίσετε τον κωδικό HTTP, ο προεπιλεγμένος είναι 302 (ή 303, αν η μέθοδος της τρέχουσας αίτησης είναι POST): ```php $this->redirectUrl('https://nette.org'); ``` -Η ανακατεύθυνση τερματίζει αμέσως τον κύκλο ζωής του παρουσιαστή πετώντας τη λεγόμενη εξαίρεση σιωπηλού τερματισμού `Nette\Application\AbortException`. +Η ανακατεύθυνση τερματίζει αμέσως τη λειτουργία του presenter δημιουργώντας τη λεγόμενη σιωπηλή εξαίρεση τερματισμού `Nette\Application\AbortException`. -Πριν από την ανακατεύθυνση, είναι δυνατή η αποστολή ενός [μηνύματος flash |#Flash Messages], μηνύματα που θα εμφανιστούν στο πρότυπο μετά την ανακατεύθυνση. +Πριν από την ανακατεύθυνση, μπορείτε να στείλετε [flash message |#Flash μηνύματα], δηλαδή μηνύματα που θα εμφανιστούν στο πρότυπο μετά την ανακατεύθυνση. -Μηνύματα flash .[#toc-flash-messages] -===================================== +Flash μηνύματα +============== -Πρόκειται για μηνύματα που συνήθως ενημερώνουν για το αποτέλεσμα μιας λειτουργίας. Ένα σημαντικό χαρακτηριστικό των μηνυμάτων flash είναι ότι είναι διαθέσιμα στο πρότυπο ακόμη και μετά την ανακατεύθυνση. Ακόμη και μετά την εμφάνισή τους, θα παραμείνουν ζωντανά για άλλα 30 δευτερόλεπτα - για παράδειγμα, σε περίπτωση που ο χρήστης θα ανανεώσει ακούσια τη σελίδα - το μήνυμα δεν θα χαθεί. +Πρόκειται για μηνύματα που συνήθως ενημερώνουν για το αποτέλεσμα κάποιας λειτουργίας. Ένα σημαντικό χαρακτηριστικό των flash μηνυμάτων είναι ότι είναι διαθέσιμα στο πρότυπο ακόμη και μετά από ανακατεύθυνση. Ακόμη και μετά την εμφάνισή τους, παραμένουν ενεργά για άλλα 30 δευτερόλεπτα – για παράδειγμα, σε περίπτωση που ο χρήστης ανανεώσει τη σελίδα λόγω σφάλματος μετάδοσης - το μήνυμα δεν εξαφανίζεται αμέσως. -Απλά καλέστε τη μέθοδο [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] και ο presenter θα φροντίσει να περάσει το μήνυμα στο πρότυπο. Το πρώτο όρισμα είναι το κείμενο του μηνύματος και το δεύτερο προαιρετικό όρισμα είναι ο τύπος του (σφάλμα, προειδοποίηση, πληροφορία κ.λπ.). Η μέθοδος `flashMessage()` επιστρέφει ένα instance του flash message, για να μας επιτρέψει να προσθέσουμε περισσότερες πληροφορίες. +Αρκεί να καλέσετε τη μέθοδο [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] και ο presenter θα φροντίσει για τη μεταβίβασή τους στο πρότυπο. Η πρώτη παράμετρος είναι το κείμενο του μηνύματος και η προαιρετική δεύτερη παράμετρος ο τύπος του (error, warning, info κ.λπ.). Η μέθοδος `flashMessage()` επιστρέφει μια παρουσία του flash μηνύματος, στην οποία μπορούν να προστεθούν περαιτέρω πληροφορίες. ```php -$this->flashMessage('Item was removed.'); -$this->redirect(/* ... */); +$this->flashMessage('Το στοιχείο διαγράφηκε.'); +$this->redirect(/* ... */); // και ανακατεύθυνση ``` -Στο πρότυπο, τα μηνύματα αυτά είναι διαθέσιμα στη μεταβλητή `$flashes` ως αντικείμενα `stdClass`, τα οποία περιέχουν τις ιδιότητες `message` (κείμενο μηνύματος), `type` (τύπος μηνύματος) και μπορούν να περιέχουν τις ήδη αναφερθείσες πληροφορίες χρήστη. Τα σχεδιάζουμε ως εξής: +Στο πρότυπο, αυτά τα μηνύματα είναι διαθέσιμα στη μεταβλητή `$flashes` ως αντικείμενα `stdClass`, τα οποία περιέχουν τις ιδιότητες `message` (κείμενο μηνύματος), `type` (τύπος μηνύματος) και μπορούν να περιέχουν τις ήδη αναφερθείσες πληροφορίες χρήστη. Τα αποδίδουμε, για παράδειγμα, ως εξής: ```latte {foreach $flashes as $flash} @@ -202,10 +202,10 @@ $this->redirect(/* ... */); ``` -Σφάλμα 404 κ.λπ. .[#toc-error-404-etc] -====================================== +Σφάλμα 404 κ.λπ. +================ -Όταν δεν μπορούμε να ικανοποιήσουμε το αίτημα επειδή για παράδειγμα το άρθρο που θέλουμε να εμφανίσουμε δεν υπάρχει στη βάση δεδομένων, θα πετάξουμε το σφάλμα 404 χρησιμοποιώντας τη μέθοδο `error(string $message = null, int $httpCode = 404)`, η οποία αντιπροσωπεύει το σφάλμα HTTP 404: +Αν δεν είναι δυνατό να ικανοποιηθεί το αίτημα, για παράδειγμα, επειδή το άρθρο που θέλουμε να εμφανίσουμε δεν υπάρχει στη βάση δεδομένων, δημιουργούμε σφάλμα 404 με τη μέθοδο `error(?string $message = null, int $httpCode = 404)`. ```php public function renderShow(int $id): void @@ -218,14 +218,13 @@ public function renderShow(int $id): void } ``` -Ο κωδικός σφάλματος HTTP μπορεί να περάσει ως δεύτερη παράμετρος, η προεπιλογή είναι 404. Η μέθοδος λειτουργεί ρίχνοντας την εξαίρεση `Nette\Application\BadRequestException`, μετά την οποία η `Application` περνά τον έλεγχο στον παρουσιαστή του σφάλματος. Ο οποίος είναι ένας παρουσιαστής του οποίου η δουλειά είναι να εμφανίζει μια σελίδα που ενημερώνει για το σφάλμα. -Ο error-preseter ορίζεται στη [διαμόρφωση της εφαρμογής |configuration]. +Ο κωδικός HTTP του σφάλματος μπορεί να περαστεί ως δεύτερη παράμετρος, ο προεπιλεγμένος είναι 404. Η μέθοδος λειτουργεί δημιουργώντας την εξαίρεση `Nette\Application\BadRequestException`, οπότε η `Application` παραδίδει τον έλεγχο στον error-presenter. Αυτός είναι ένας presenter του οποίου ο ρόλος είναι να εμφανίσει μια σελίδα που ενημερώνει για το σφάλμα που προέκυψε. Η ρύθμιση του error-presenter γίνεται στη [διαμόρφωση application|configuration]. -Αποστολή JSON .[#toc-sending-json] -================================== +Αποστολή JSON +============= -Παράδειγμα δράσης-μεθόδου που στέλνει δεδομένα σε μορφή JSON και εξέρχεται από τον παρουσιαστή: +Παράδειγμα μεθόδου action που στέλνει δεδομένα σε μορφή JSON και τερματίζει τον presenter: ```php public function actionData(): void @@ -236,33 +235,59 @@ public function actionData(): void ``` -Εμμένουσες παράμετροι .[#toc-persistent-parameters] -=================================================== +Παράμετροι αιτήματος .{data-version:3.1.14} +=========================================== -Οι μόνιμες παράμετροι χρησιμοποιούνται για τη διατήρηση της κατάστασης μεταξύ διαφορετικών αιτήσεων. Η τιμή τους παραμένει η ίδια ακόμη και μετά το κλικ σε έναν σύνδεσμο. Σε αντίθεση με τα δεδομένα συνόδου, μεταβιβάζονται στη διεύθυνση URL. Αυτό γίνεται εντελώς αυτόματα, οπότε δεν χρειάζεται να τις δηλώσετε ρητά στο `link()` ή στο `n:href`. +Ο presenter και επίσης κάθε component λαμβάνει τις παραμέτρους του από το αίτημα HTTP. Μπορείτε να βρείτε την τιμή τους χρησιμοποιώντας τη μέθοδο `getParameter($name)` ή `getParameters()`. Οι τιμές είναι strings ή arrays από strings, πρόκειται ουσιαστικά για ακατέργαστα δεδομένα που λαμβάνονται απευθείας από το URL. -Παράδειγμα χρήσης; Έχετε μια πολύγλωσση εφαρμογή. Η πραγματική γλώσσα είναι μια παράμετρος που πρέπει να αποτελεί μέρος του URL ανά πάσα στιγμή. Αλλά θα ήταν απίστευτα κουραστικό να τη συμπεριλάβετε σε κάθε σύνδεσμο. Οπότε την κάνετε μια μόνιμη παράμετρο με το όνομα `lang` και θα μεταφέρεται μόνη της. Ωραία! +Για μεγαλύτερη ευκολία, συνιστούμε να κάνετε τις παραμέτρους προσβάσιμες μέσω ιδιοτήτων. Αρκεί να τις επισημάνετε με το attribute `#[Parameter]`: -Η δημιουργία μιας μόνιμης παραμέτρου είναι εξαιρετικά εύκολη στη Nette. Απλά δημιουργήστε μια δημόσια ιδιότητα και επισημάνετέ την με το χαρακτηριστικό: (προηγουμένως χρησιμοποιούνταν το `/** @persistent */` ) +```php +use Nette\Application\Attributes\Parameter; // αυτή η γραμμή είναι σημαντική + +class HomePresenter extends Nette\Application\UI\Presenter +{ + #[Parameter] + public string $theme; // πρέπει να είναι public +} +``` + +Συνιστούμε να καθορίσετε τον τύπο δεδομένων για την ιδιότητα (π.χ. `string`) και το Nette θα μετατρέψει αυτόματα την τιμή σύμφωνα με αυτόν. Οι τιμές των παραμέτρων μπορούν επίσης να [επικυρωθούν |#Επικύρωση παραμέτρων]. + +Κατά τη δημιουργία ενός συνδέσμου, η τιμή των παραμέτρων μπορεί να οριστεί απευθείας: + +```latte +κάντε κλικ +``` + + +Persistent παράμετροι +===================== + +Οι persistent παράμετροι χρησιμοποιούνται για τη διατήρηση της κατάστασης μεταξύ διαφορετικών αιτήσεων. Η τιμή τους παραμένει η ίδια ακόμη και μετά το κλικ σε έναν σύνδεσμο. Σε αντίθεση με τα δεδομένα στη session, μεταφέρονται στη διεύθυνση URL. Και αυτό γίνεται εντελώς αυτόματα, δεν χρειάζεται δηλαδή να τις καθορίσετε ρητά στο `link()` ή στο `n:href`. + +Παράδειγμα χρήσης; Έχετε μια πολύγλωσση εφαρμογή. Η τρέχουσα γλώσσα είναι μια παράμετρος που πρέπει να είναι συνεχώς μέρος της διεύθυνσης URL. Αλλά θα ήταν απίστευτα κουραστικό να την καθορίζετε σε κάθε σύνδεσμο. Έτσι, την κάνετε μια persistent παράμετρο `lang` και θα μεταφέρεται μόνη της. Υπέροχο! + +Η δημιουργία μιας persistent παραμέτρου στο Nette είναι εξαιρετικά απλή. Αρκεί να δημιουργήσετε μια δημόσια ιδιότητα και να την επισημάνετε με ένα attribute: (παλαιότερα χρησιμοποιούνταν το `/** @persistent */`) ```php -use Nette\Application\Attributes\Persistent; // αυτή η γραμμή είναι σημαντική +use Nette\Application\Attributes\Persistent; // αυτή η γραμμή είναι σημαντική class ProductPresenter extends Nette\Application\UI\Presenter { #[Persistent] - public string $lang; // πρέπει να είναι δημόσια + public string $lang; // πρέπει να είναι public } ``` -Εάν το `$this->lang` έχει μια τιμή όπως `'en'`, τότε οι σύνδεσμοι που δημιουργούνται με χρήση των `link()` ή `n:href` θα περιέχουν επίσης την παράμετρο `lang=en`. Και όταν ο σύνδεσμος πατηθεί, θα είναι και πάλι `$this->lang = 'en'`. +Αν το `$this->lang` έχει την τιμή, για παράδειγμα, `'en'`, τότε και οι σύνδεσμοι που δημιουργούνται χρησιμοποιώντας το `link()` ή το `n:href` θα περιέχουν την παράμετρο `lang=en`. Και μετά το κλικ στον σύνδεσμο, το `$this->lang` θα είναι ξανά `'en'`. -Για τις ιδιότητες, συνιστούμε να περιλαμβάνετε τον τύπο δεδομένων (π.χ. `string`) και μπορείτε επίσης να συμπεριλάβετε μια προεπιλεγμένη τιμή. Οι τιμές των παραμέτρων μπορούν να [επικυρωθούν |#Validation of Persistent Parameters]. +Συνιστούμε να καθορίσετε τον τύπο δεδομένων για την ιδιότητα (π.χ. `string`) και μπορείτε επίσης να καθορίσετε μια προεπιλεγμένη τιμή. Οι τιμές των παραμέτρων μπορούν να [επικυρωθούν |#Επικύρωση παραμέτρων]. -Οι μόνιμες παράμετροι μεταβιβάζονται μεταξύ όλων των ενεργειών ενός συγκεκριμένου παρουσιαστή από προεπιλογή. Για να τις περάσετε μεταξύ πολλαπλών παρουσιαστών, πρέπει να τις ορίσετε είτε: +Οι persistent παράμετροι μεταφέρονται κανονικά μεταξύ όλων των actions του συγκεκριμένου presenter. Για να μεταφέρονται και μεταξύ πολλών presenters, πρέπει να οριστούν είτε: -- σε έναν κοινό πρόγονο από τον οποίο κληρονομούν οι παρουσιαστές -- στην ιδιότητα που χρησιμοποιούν οι παρουσιαστές: +- σε έναν κοινό πρόγονο, από τον οποίο κληρονομούν οι presenters +- σε ένα trait, το οποίο χρησιμοποιούν οι presenters: ```php trait LanguageAware @@ -277,48 +302,42 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -Μπορείτε να αλλάξετε την τιμή μιας μόνιμης παραμέτρου κατά τη δημιουργία ενός συνδέσμου: +Κατά τη δημιουργία ενός συνδέσμου, η τιμή της persistent παραμέτρου μπορεί να αλλάξει: ```latte -detail in Czech +λεπτομέρεια στα Τσέχικα ``` -Ή μπορεί να *επαναρυθμιστεί*, δηλαδή να αφαιρεθεί από τη διεύθυνση URL. Τότε θα πάρει την προεπιλεγμένη τιμή της: +Ή μπορεί να *επαναφερθεί*, δηλαδή να αφαιρεθεί από τη διεύθυνση URL. Στη συνέχεια, θα πάρει την προεπιλεγμένη της τιμή: ```latte -click +κάντε κλικ ``` -Διαδραστικά στοιχεία .[#toc-interactive-components] -=================================================== +Διαδραστικά Components +====================== -Οι παρουσιαστές διαθέτουν ένα ενσωματωμένο σύστημα συστατικών. Τα συστατικά είναι ξεχωριστές επαναχρησιμοποιήσιμες μονάδες που τοποθετούμε στους παρουσιαστές. Μπορούν να είναι [φόρμες |forms:in-presenter], datagrids, μενού, στην πραγματικότητα οτιδήποτε έχει νόημα να χρησιμοποιείται επανειλημμένα. +Οι presenters έχουν ενσωματωμένο σύστημα components. Τα components είναι ανεξάρτητες, επαναχρησιμοποιήσιμες μονάδες που ενσωματώνουμε στους presenters. Μπορεί να είναι [φόρμες |forms:in-presenter], datagrids, μενού, στην πραγματικότητα οτιδήποτε έχει νόημα να χρησιμοποιείται επανειλημμένα. -Πώς τοποθετούνται και στη συνέχεια χρησιμοποιούνται τα συστατικά στον παρουσιαστή; Αυτό εξηγείται στο κεφάλαιο [Components |Components]. Θα μάθετε ακόμη και τι σχέση έχουν με το Χόλιγουντ. +Πώς ενσωματώνονται και στη συνέχεια χρησιμοποιούνται τα components στον presenter; Αυτό θα το μάθετε στο κεφάλαιο [Components |components]. Θα ανακαλύψετε ακόμη και τι κοινό έχουν με το Hollywood. -Πού μπορώ να προμηθευτώ μερικά συστατικά; Στη σελίδα [Componette |https://componette.org] μπορείτε να βρείτε ορισμένα συστατικά ανοικτού κώδικα και άλλα πρόσθετα για το Nette που έχουν κατασκευαστεί και διαμοιραστεί από την κοινότητα του Nette Framework. +Και πού μπορώ να βρω components; Στη σελίδα [Componette |https://componette.org/search/component] θα βρείτε open-source components και επίσης μια σειρά από άλλα πρόσθετα για το Nette, τα οποία έχουν τοποθετηθεί εδώ από εθελοντές της κοινότητας γύρω από το framework. -Εμβαθύνοντας περισσότερο .[#toc-going-deeper] -============================================= +Πάμε βαθύτερα +============= .[tip] -Όσα έχουμε δείξει μέχρι στιγμής σε αυτό το κεφάλαιο μάλλον αρκούν. Οι επόμενες γραμμές απευθύνονται σε όσους ενδιαφέρονται για τους παρουσιαστές σε βάθος και θέλουν να μάθουν τα πάντα. - - -Απαίτηση και παράμετροι .[#toc-requirement-and-parameters] ----------------------------------------------------------- +Με όσα έχουμε δείξει μέχρι τώρα σε αυτό το κεφάλαιο, πιθανότατα θα τα βγάλετε πέρα. Οι παρακάτω γραμμές προορίζονται για όσους ενδιαφέρονται για τους presenters σε βάθος και θέλουν να μάθουν τα πάντα. -Το αίτημα που χειρίζεται ο παρουσιαστής είναι το αντικείμενο [api:Nette\Application\Request] και επιστρέφεται από τη μέθοδο του παρουσιαστή `getRequest()`. Περιλαμβάνει έναν πίνακα παραμέτρων και κάθε μία από αυτές ανήκει είτε σε κάποιο από τα συστατικά είτε απευθείας στον παρουσιαστή (ο οποίος στην πραγματικότητα είναι επίσης ένα συστατικό, αν και ειδικό). Έτσι, η Nette ανακατανέμει τις παραμέτρους και περνάει μεταξύ των επιμέρους συστατικών (και του παρουσιαστή) καλώντας τη μέθοδο `loadState(array $params)`. Οι παράμετροι μπορούν να ληφθούν με τη μέθοδο `getParameters(): array`, μεμονωμένα με τη χρήση του `getParameter($name)`. Οι τιμές των παραμέτρων είναι συμβολοσειρές ή πίνακες συμβολοσειρών, είναι ουσιαστικά ακατέργαστα δεδομένα που λαμβάνονται απευθείας από μια διεύθυνση URL. +Επικύρωση παραμέτρων +-------------------- -Επικύρωση μόνιμων παραμέτρων .[#toc-validation-of-persistent-parameters] ------------------------------------------------------------------------- +Οι τιμές των [παραμέτρων αιτήματος |#Παράμετροι αιτήματος] και των [persistent παραμέτρων |#Persistent παράμετροι] που λαμβάνονται από τη διεύθυνση URL γράφονται στις ιδιότητες από τη μέθοδο `loadState()`. Αυτή ελέγχει επίσης εάν ο τύπος δεδομένων που καθορίζεται στην ιδιότητα αντιστοιχεί, διαφορετικά απαντά με σφάλμα 404 και η σελίδα δεν εμφανίζεται. -Οι τιμές των [μόνιμων παραμέτρων |#persistent parameters] που λαμβάνονται από τις διευθύνσεις URL εγγράφονται στις ιδιότητες με τη μέθοδο `loadState()`. Ελέγχει επίσης αν ο τύπος δεδομένων που καθορίζεται στην ιδιότητα ταιριάζει, διαφορετικά θα απαντήσει με σφάλμα 404 και η σελίδα δεν θα εμφανιστεί. - -Ποτέ μην εμπιστεύεστε τυφλά τις μόνιμες παραμέτρους, καθώς μπορούν εύκολα να αντικατασταθούν από τον χρήστη στη διεύθυνση URL. Για παράδειγμα, με αυτόν τον τρόπο ελέγχουμε αν το `$this->lang` είναι μεταξύ των υποστηριζόμενων γλωσσών. Ένας καλός τρόπος για να το κάνετε αυτό είναι να παρακάμψετε τη μέθοδο `loadState()` που αναφέρθηκε παραπάνω: +Ποτέ μην εμπιστεύεστε τυφλά τις παραμέτρους, επειδή μπορούν εύκολα να αντικατασταθούν από τον χρήστη στη διεύθυνση URL. Έτσι, για παράδειγμα, επαληθεύουμε εάν η γλώσσα `$this->lang` είναι μεταξύ των υποστηριζόμενων. Ένας κατάλληλος τρόπος είναι να αντικαταστήσετε την αναφερόμενη μέθοδο `loadState()`: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -329,7 +348,7 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function loadState(array $params): void { parent::loadState($params); // εδώ ορίζεται το $this->lang - // ακολουθεί τον έλεγχο της τιμής του χρήστη: + // ακολουθεί προσαρμοσμένος έλεγχος τιμής: if (!in_array($this->lang, ['en', 'cs'])) { $this->error(); } @@ -338,43 +357,45 @@ class ProductPresenter extends Nette\Application\UI\Presenter ``` -Αποθήκευση και επαναφορά της αίτησης .[#toc-save-and-restore-the-request] -------------------------------------------------------------------------- +Αποθήκευση και ανάκτηση αιτήματος +--------------------------------- + +Το αίτημα που διεκπεραιώνει ο presenter είναι ένα αντικείμενο [api:Nette\Application\Request] και επιστρέφεται από τη μέθοδο του presenter `getRequest()`. -Μπορείτε να αποθηκεύσετε την τρέχουσα αίτηση σε μια συνεδρία ή να την επαναφέρετε από τη συνεδρία και να αφήσετε τον παρουσιαστή να την εκτελέσει ξανά. Αυτό είναι χρήσιμο, για παράδειγμα, όταν ένας χρήστης συμπληρώνει μια φόρμα και λήγει η σύνδεσή του. Για να μην χαθούν δεδομένα, πριν από την ανακατεύθυνση στη σελίδα σύνδεσης, αποθηκεύουμε το τρέχον αίτημα στη σύνοδο χρησιμοποιώντας το `$reqId = $this->storeRequest()`, το οποίο επιστρέφει ένα αναγνωριστικό με τη μορφή σύντομης συμβολοσειράς και το περνάει ως παράμετρο στον παρουσιαστή σύνδεσης. +Το τρέχον αίτημα μπορεί να αποθηκευτεί στη session ή, αντίθετα, να ανακτηθεί από αυτήν και να αφεθεί ο presenter να το εκτελέσει ξανά. Αυτό είναι χρήσιμο, για παράδειγμα, σε μια κατάσταση όπου ο χρήστης συμπληρώνει μια φόρμα και η σύνδεσή του λήγει. Για να μην χάσει τα δεδομένα, πριν από την ανακατεύθυνση στη σελίδα σύνδεσης, αποθηκεύουμε το τρέχον αίτημα στη session χρησιμοποιώντας το `$reqId = $this->storeRequest()`, το οποίο επιστρέφει το αναγνωριστικό του με τη μορφή μιας σύντομης συμβολοσειράς και το μεταβιβάζουμε ως παράμετρο στον presenter σύνδεσης. -Μετά την είσοδο, καλούμε τη μέθοδο `$this->restoreRequest($reqId)`, η οποία παραλαμβάνει το αίτημα από τη σύνοδο και το προωθεί σε αυτήν. Η μέθοδος επαληθεύει ότι το αίτημα δημιουργήθηκε από τον ίδιο χρήστη που τώρα έχει συνδεθεί είναι. Αν συνδεθεί άλλος χρήστης ή το κλειδί είναι άκυρο, δεν κάνει τίποτα και το πρόγραμμα συνεχίζει. +Μετά τη σύνδεση, καλούμε τη μέθοδο `$this->restoreRequest($reqId)`, η οποία ανακτά το αίτημα από τη session και προωθεί σε αυτό. Η μέθοδος επαληθεύει ταυτόχρονα ότι το αίτημα δημιουργήθηκε από τον ίδιο χρήστη που συνδέθηκε τώρα. Αν συνδεθεί άλλος χρήστης ή το κλειδί είναι άκυρο, δεν κάνει τίποτα και το πρόγραμμα συνεχίζει. -Δείτε το βιβλίο μαγειρικής [Πώς να επιστρέψετε σε μια προηγούμενη σελίδα |best-practices:restore-request]. +Δείτε τον οδηγό [Πώς να επιστρέψετε σε προηγούμενη σελίδα |best-practices:restore-request]. -Κανονικοποίηση .[#toc-canonization] ------------------------------------ +Κανονικοποίηση +-------------- -Οι παρουσιαστές έχουν ένα πραγματικά σπουδαίο χαρακτηριστικό που βελτιώνει το SEO (βελτιστοποίηση της δυνατότητας αναζήτησης στο Διαδίκτυο). Αποτρέπουν αυτόματα την ύπαρξη διπλού περιεχομένου σε διαφορετικές διευθύνσεις URL. Εάν πολλαπλές διευθύνσεις URL οδηγούν σε έναν συγκεκριμένο προορισμό, π.χ. `/index` και `/index?page=1`, το πλαίσιο ορίζει μία από αυτές ως κύρια (κανονική) και ανακατευθύνει τις υπόλοιπες σε αυτήν χρησιμοποιώντας τον κωδικό HTTP 301. Χάρη σε αυτό, οι μηχανές αναζήτησης δεν ευρετηριάζουν τις σελίδες δύο φορές και δεν αποδυναμώνουν την κατάταξή τους. +Οι presenters έχουν ένα πραγματικά εξαιρετικό χαρακτηριστικό που συμβάλλει στο καλύτερο SEO (βελτιστοποίηση για μηχανές αναζήτησης). Αποτρέπουν αυτόματα την ύπαρξη διπλού περιεχομένου σε διαφορετικά URL. Αν υπάρχουν πολλαπλά URL που οδηγούν στον ίδιο στόχο, π.χ. `/index` και `/index?page=1`, το framework καθορίζει ένα από αυτά ως το κύριο (κανονικό) και ανακατευθύνει τα υπόλοιπα σε αυτό χρησιμοποιώντας τον κωδικό HTTP 301. Χάρη σε αυτό, οι μηχανές αναζήτησης δεν ευρετηριάζουν τις σελίδες σας δύο φορές και δεν διασπούν το page rank τους. -Η διαδικασία αυτή ονομάζεται κανονικοποίηση. Η κανονική διεύθυνση URL είναι η διεύθυνση URL που δημιουργείται από τον [δρομολογητή |routing], συνήθως η πρώτη κατάλληλη διαδρομή στη συλλογή. +Αυτή η διαδικασία ονομάζεται κανονικοποίηση. Η κανονική διεύθυνση URL είναι αυτή που δημιουργείται από τον [router|routing], συνήθως δηλαδή η πρώτη αντίστοιχη διαδρομή στη συλλογή. -Η κανονικοποίηση είναι ενεργοποιημένη από προεπιλογή και μπορεί να απενεργοποιηθεί μέσω της διεύθυνσης `$this->autoCanonicalize = false`. +Η κανονικοποίηση είναι ενεργοποιημένη από προεπιλογή και μπορεί να απενεργοποιηθεί μέσω του `$this->autoCanonicalize = false`. -Η ανακατεύθυνση δεν πραγματοποιείται με ένα αίτημα AJAX ή POST, επειδή θα είχε ως αποτέλεσμα την απώλεια δεδομένων ή καμία προστιθέμενη αξία SEO. +Η ανακατεύθυνση δεν πραγματοποιείται κατά τη διάρκεια μιας αίτησης AJAX ή POST, επειδή θα προκαλούσε απώλεια δεδομένων ή δεν θα είχε προστιθέμενη αξία από άποψη SEO. -Μπορείτε επίσης να επικαλεστείτε την κανονικοποίηση χειροκίνητα χρησιμοποιώντας τη μέθοδο `canonicalize()`, η οποία, όπως και η μέθοδος `link()`, λαμβάνει τον παρουσιαστή, τις ενέργειες και τις παραμέτρους ως ορίσματα. Δημιουργεί έναν σύνδεσμο και τον συγκρίνει με την τρέχουσα διεύθυνση URL. Εάν είναι διαφορετική, ανακατευθύνει στον δημιουργημένο σύνδεσμο. +Μπορείτε επίσης να καλέσετε την κανονικοποίηση χειροκίνητα χρησιμοποιώντας τη μέθοδο `canonicalize()`, στην οποία, παρόμοια με τη μέθοδο `link()`, περνιέται ο presenter, η action και οι παράμετροι. Δημιουργεί έναν σύνδεσμο και τον συγκρίνει με την τρέχουσα διεύθυνση URL. Αν διαφέρουν, ανακατευθύνει στον δημιουργημένο σύνδεσμο. ```php -public function actionShow(int $id, string $slug = null): void +public function actionShow(int $id, ?string $slug = null): void { $realSlug = $this->facade->getSlugForId($id); - // ανακατευθύνει εάν το $slug είναι διαφορετικό από το $realSlug + // ανακατευθύνει αν το $slug διαφέρει από το $realSlug $this->canonicalize('Product:show', [$id, $realSlug]); } ``` -Εκδηλώσεις .[#toc-events] -------------------------- +Γεγονότα +-------- -Εκτός από τις μεθόδους `startup()`, `beforeRender()` και `shutdown()`, οι οποίες καλούνται ως μέρος του κύκλου ζωής του παρουσιαστή, μπορούν να οριστούν και άλλες λειτουργίες που καλούνται αυτόματα. Ο παρουσιαστής ορίζει τα λεγόμενα [συμβάντα |nette:glossary#events] και εσείς προσθέτετε τους χειριστές τους στους πίνακες `$onStartup`, `$onRender` και `$onShutdown`. +Εκτός από τις μεθόδους `startup()`, `beforeRender()` και `shutdown()`, οι οποίες καλούνται ως μέρος του κύκλου ζωής του presenter, μπορούν να οριστούν και άλλες συναρτήσεις που θα καλούνται αυτόματα. Ο presenter ορίζει τα λεγόμενα [γεγονότα |nette:glossary#Events], των οποίων τους handlers προσθέτετε στους πίνακες `$onStartup`, `$onRender` και `$onShutdown`. ```php class ArticlePresenter extends Nette\Application\UI\Presenter @@ -388,23 +409,23 @@ class ArticlePresenter extends Nette\Application\UI\Presenter } ``` -Οι χειριστές στον πίνακα `$onStartup` καλούνται ακριβώς πριν από τη μέθοδο `startup()`, στη συνέχεια `$onRender` μεταξύ `beforeRender()` και `render()` και τέλος `$onShutdown` λίγο πριν από την `shutdown()`. +Οι handlers στον πίνακα `$onStartup` καλούνται ακριβώς πριν από τη μέθοδο `startup()`, στη συνέχεια το `$onRender` μεταξύ `beforeRender()` και `render()` και τέλος το `$onShutdown` ακριβώς πριν από το `shutdown()`. -Απαντήσεις .[#toc-responses] ----------------------------- +Απαντήσεις +---------- -Η απάντηση που επιστρέφεται από τον παρουσιαστή είναι ένα αντικείμενο που υλοποιεί τη διεπαφή [api:Nette\Application\Response]. Υπάρχει ένας αριθμός έτοιμων απαντήσεων: +Η response που επιστρέφει ο presenter είναι ένα αντικείμενο που υλοποιεί το interface [api:Nette\Application\Response]. Υπάρχουν διαθέσιμες πολλές έτοιμες responses: -- [api:Nette\Application\Responses\CallbackResponse] - στέλνει μια επανάκληση -- [api:Nette\Application\Responses\FileResponse] - στέλνει το αρχείο -- [api:Nette\Application\Responses\ForwardResponse] - προώθηση () +- [api:Nette\Application\Responses\CallbackResponse] - στέλνει ένα callback +- [api:Nette\Application\Responses\FileResponse] - στέλνει ένα αρχείο +- [api:Nette\Application\Responses\ForwardResponse] - forward() - [api:Nette\Application\Responses\JsonResponse] - στέλνει JSON - [api:Nette\Application\Responses\RedirectResponse] - ανακατεύθυνση -- [api:Nette\Application\Responses\TextResponse] - αποστέλλει κείμενο -- [api:Nette\Application\Responses\VoidResponse] - κενή απάντηση +- [api:Nette\Application\Responses\TextResponse] - στέλνει κείμενο +- [api:Nette\Application\Responses\VoidResponse] - κενή response -Οι απαντήσεις αποστέλλονται με τη μέθοδο `sendResponse()`: +Οι responses στέλνονται με τη μέθοδο `sendResponse()`: ```php use Nette\Application\Responses; @@ -412,10 +433,10 @@ use Nette\Application\Responses; // Απλό κείμενο $this->sendResponse(new Responses\TextResponse('Hello Nette!')); -// Αποστέλλει ένα αρχείο +// Στέλνει ένα αρχείο $this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf')); -// Αποστέλλει ένα callback +// Η response θα είναι ένα callback $callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse) { if ($httpResponse->getHeader('Content-Type') === 'text/html') { echo '

    Hello

    '; @@ -425,10 +446,55 @@ $this->sendResponse(new Responses\CallbackResponse($callback)); ``` -Περαιτέρω ανάγνωση .[#toc-further-reading] -========================================== +Περιορισμός πρόσβασης με `#[Requires]` .{data-version:3.2.2} +------------------------------------------------------------ + +Το attribute `#[Requires]` παρέχει προηγμένες δυνατότητες για τον περιορισμό της πρόσβασης σε presenters και τις μεθόδους τους. Μπορεί να χρησιμοποιηθεί για τον καθορισμό μεθόδων HTTP, την απαίτηση αίτησης AJAX, τον περιορισμό στην ίδια προέλευση (same origin), και την πρόσβαση μόνο μέσω προώθησης (forwarding). Το attribute μπορεί να εφαρμοστεί τόσο στις κλάσεις των presenters όσο και στις μεμονωμένες μεθόδους `action()`, `render()`, `handle()` και `createComponent()`. + +Μπορείτε να καθορίσετε αυτούς τους περιορισμούς: +- σε μεθόδους HTTP: `#[Requires(methods: ['GET', 'POST'])]` +- απαίτηση αίτησης AJAX: `#[Requires(ajax: true)]` +- πρόσβαση μόνο από την ίδια προέλευση: `#[Requires(sameOrigin: true)]` +- πρόσβαση μόνο μέσω forward: `#[Requires(forward: true)]` +- περιορισμός σε συγκεκριμένες actions: `#[Requires(actions: 'default')]` + +Λεπτομέρειες θα βρείτε στον οδηγό [Πώς να χρησιμοποιήσετε το attribute Requires |best-practices:attribute-requires]. + + +Έλεγχος μεθόδου HTTP +-------------------- + +Οι presenters στο Nette επαληθεύουν αυτόματα τη μέθοδο HTTP κάθε εισερχόμενου αιτήματος. Ο λόγος για αυτόν τον έλεγχο είναι κυρίως η ασφάλεια. Από προεπιλογή, επιτρέπονται οι μέθοδοι `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH`. + +Αν θέλετε να επιτρέψετε επιπλέον, για παράδειγμα, τη μέθοδο `OPTIONS`, χρησιμοποιήστε το attribute `#[Requires]` (από το Nette Application v3.2): + +```php +#[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])] +class MyPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Στην έκδοση 3.1, η επαλήθευση γίνεται στην `checkHttpMethod()`, η οποία ελέγχει εάν η μέθοδος που καθορίζεται στην αίτηση περιλαμβάνεται στον πίνακα `$presenter->allowedMethods`. Η προσθήκη της μεθόδου γίνεται ως εξής: + +```php +class MyPresenter extends Nette\Application\UI\Presenter +{ + protected function checkHttpMethod(): void + { + $this->allowedMethods[] = 'OPTIONS'; + parent::checkHttpMethod(); + } +} +``` + +Είναι σημαντικό να τονιστεί ότι αν επιτρέψετε τη μέθοδο `OPTIONS`, πρέπει στη συνέχεια να την χειριστείτε κατάλληλα εντός του presenter σας. Η μέθοδος χρησιμοποιείται συχνά ως το λεγόμενο preflight request, το οποίο το πρόγραμμα περιήγησης στέλνει αυτόματα πριν από το πραγματικό αίτημα, όταν χρειάζεται να διαπιστωθεί εάν το αίτημα επιτρέπεται από την πολιτική CORS (Cross-Origin Resource Sharing). Αν επιτρέψετε τη μέθοδο, αλλά δεν υλοποιήσετε τη σωστή response, μπορεί να οδηγήσει σε ασυνέπειες και πιθανά προβλήματα ασφάλειας. + + +Περαιτέρω ανάγνωση +================== -- [Ένθεση μεθόδων και χαρακτηριστικών |best-practices:inject-method-attribute] -- [Σύνθεση παρουσιαστών από γνωρίσματα |best-practices:presenter-traits] -- [Πέρασμα ρυθμίσεων σε παρουσιαστές |best-practices:passing-settings-to-presenters] -- [Πώς να επιστρέψετε σε μια προηγούμενη |best-practices:restore-request]σελίδα +- [Μέθοδοι και attributes inject |best-practices:inject-method-attribute] +- [Σύνθεση presenters από traits |best-practices:presenter-traits] +- [Μεταβίβαση ρυθμίσεων σε presenters |best-practices:passing-settings-to-presenters] +- [Πώς να επιστρέψετε σε προηγούμενη σελίδα |best-practices:restore-request] diff --git a/application/el/routing.texy b/application/el/routing.texy index 19b2ceb7bf..31800ef023 100644 --- a/application/el/routing.texy +++ b/application/el/routing.texy @@ -3,27 +3,26 @@
    -Ο δρομολογητής είναι υπεύθυνος για τα πάντα σχετικά με τις διευθύνσεις URL, ώστε να μην χρειάζεται πλέον να τις σκέφτεστε. Θα δείξουμε: +Ο Router είναι υπεύθυνος για τα πάντα γύρω από τις διευθύνσεις URL, ώστε να μην χρειάζεται πλέον να τις σκέφτεστε. Θα δείξουμε: -- πώς να ρυθμίσετε τον δρομολογητή ώστε οι διευθύνσεις URL να είναι όπως τις θέλετε -- μερικές σημειώσεις σχετικά με την ανακατεύθυνση SEO -- και θα σας δείξουμε πώς να γράψετε τον δικό σας δρομολογητή +- πώς να ρυθμίσετε τον router ώστε τα URL να είναι όπως τα φαντάζεστε +- θα μιλήσουμε για SEO και ανακατεύθυνση +- και θα δείξουμε πώς να γράψετε τον δικό σας router
    -Οι πιο ανθρώπινες διευθύνσεις URL (ή cool ή όμορφες διευθύνσεις URL) είναι πιο εύχρηστες, πιο αξιομνημόνευτες και συμβάλλουν θετικά στο SEO. Η Nette έχει αυτό κατά νου και ανταποκρίνεται πλήρως στις επιθυμίες των προγραμματιστών. Μπορείτε να σχεδιάσετε τη δομή URL για την εφαρμογή σας ακριβώς όπως τη θέλετε. -Μπορείτε να τη σχεδιάσετε ακόμη και αφού η εφαρμογή είναι έτοιμη, καθώς αυτό μπορεί να γίνει χωρίς αλλαγές στον κώδικα ή στο πρότυπο. Ορίζεται με κομψό τρόπο σε [ένα μόνο σημείο |#Integration], στο δρομολογητή, και δεν είναι διάσπαρτη με τη μορφή σχολίων σε όλους τους παρουσιαστές. +Οι πιο ανθρώπινες διευθύνσεις URL (ή αλλιώς cool ή pretty URL) είναι πιο εύχρηστες, πιο εύκολα απομνημονεύσιμες και συμβάλλουν θετικά στο SEO. Το Nette το λαμβάνει υπόψη και υποστηρίζει πλήρως τους προγραμματιστές. Μπορείτε να σχεδιάσετε για την εφαρμογή σας ακριβώς τη δομή των διευθύνσεων URL που θέλετε. Μπορείτε να τη σχεδιάσετε ακόμη και όταν η εφαρμογή είναι ήδη έτοιμη, επειδή αυτό γίνεται χωρίς παρεμβάσεις στον κώδικα ή τα πρότυπα. Ορίζεται με κομψό τρόπο σε ένα [μόνο σημείο |#Ενσωμάτωση στην εφαρμογή], στον router, και δεν είναι διάσπαρτη με τη μορφή σχολιαστικών παρατηρήσεων σε όλους τους presenters. -Ο δρομολογητής στη Nette είναι ιδιαίτερος επειδή είναι **διπλής κατεύθυνσης**, μπορεί τόσο να αποκωδικοποιεί τις διευθύνσεις URL των αιτήσεων HTTP όσο και να δημιουργεί συνδέσμους. Έτσι, παίζει ζωτικό ρόλο στην [εφαρμογή Nette |how-it-works#Nette Application], επειδή αποφασίζει ποιος παρουσιαστής και ποια ενέργεια θα εκτελέσει την τρέχουσα αίτηση, και χρησιμοποιείται επίσης για τη [δημιουργία URL |creating-links] στο πρότυπο κ.λπ. +Ο router στο Nette είναι εξαιρετικός στο ότι είναι **αμφίδρομος.** Μπορεί τόσο να αποκωδικοποιεί τα URL σε αιτήματα HTTP, όσο και να δημιουργεί συνδέσμους. Παίζει επομένως καθοριστικό ρόλο στην [Nette Application |how-it-works#Nette Application], επειδή αφενός αποφασίζει ποιος presenter και action θα εκτελέσει το τρέχον αίτημα, αλλά χρησιμοποιείται επίσης για τη [δημιουργία URL |creating-links] στο πρότυπο κ.λπ. -Ωστόσο, ο δρομολογητής δεν περιορίζεται σε αυτή τη χρήση, μπορείτε να τον χρησιμοποιήσετε σε εφαρμογές όπου δεν χρησιμοποιούνται καθόλου παρουσιαστές, για REST APIs κ.λπ. Περισσότερα στην ενότητα [χωριστή χρήση |#separated usage]. +Ωστόσο, ο router δεν περιορίζεται μόνο σε αυτή τη χρήση, μπορείτε να τον χρησιμοποιήσετε σε εφαρμογές όπου δεν χρησιμοποιούνται καθόλου presenters, για REST API, κ.λπ. Περισσότερα στην ενότητα [#Αυτόνομη χρήση]. -Συλλογή διαδρομών .[#toc-route-collection] -========================================== +Συλλογή διαδρομών +================= -Ο πιο ευχάριστος τρόπος για τον ορισμό των διευθύνσεων URL στην εφαρμογή είναι μέσω της κλάσης [api:Nette\Application\Routers\RouteList]. Ο ορισμός αποτελείται από μια λίστα των λεγόμενων διαδρομών, δηλαδή μάσκες διευθύνσεων URL και των σχετικών παρουσιαστών και ενεργειών τους με τη χρήση ενός απλού API. Δεν χρειάζεται να δώσουμε όνομα στις διαδρομές. +Ο πιο ευχάριστος τρόπος για να ορίσετε τη μορφή των διευθύνσεων URL στην εφαρμογή είναι η κλάση [api:Nette\Application\Routers\RouteList]. Ο ορισμός αποτελείται από μια λίστα λεγόμενων routes, δηλαδή μασκών διευθύνσεων URL και των σχετικών presenters και actions που συνδέονται με αυτές μέσω ενός απλού API. Δεν χρειάζεται να ονομάσουμε τις routes με κανέναν τρόπο. ```php $router = new Nette\Application\Routers\RouteList; @@ -32,130 +31,130 @@ $router->addRoute('article/', 'Article:view'); // ... ``` -Το παράδειγμα λέει ότι αν ανοίξουμε το `https://any-domain.com/rss.xml` με την ενέργεια `rss`, αν `https://domain.com/article/12` με την ενέργεια `view` κ.λπ. Εάν δεν βρεθεί κατάλληλη διαδρομή, η εφαρμογή Nette Application ανταποκρίνεται με την απόρριψη μιας εξαίρεσης [BadRequestException |api:Nette\Application\BadRequestException], η οποία εμφανίζεται στο χρήστη ως σελίδα σφάλματος 404 Not Found. +Το παράδειγμα λέει ότι αν ανοίξουμε στο πρόγραμμα περιήγησης το `https://domain.com/rss.xml`, θα εμφανιστεί ο presenter `Feed` με την action `rss`, αν ανοίξουμε το `https://domain.com/article/12`, θα εμφανιστεί ο presenter `Article` με την action `view` κ.λπ. Σε περίπτωση που δεν βρεθεί κατάλληλη route, η Nette Application αντιδρά δημιουργώντας την εξαίρεση [BadRequestException |api:Nette\Application\BadRequestException], η οποία εμφανίζεται στον χρήστη ως σελίδα σφάλματος 404 Not Found. -Σειρά των διαδρομών .[#toc-order-of-routes] -------------------------------------------- +Σειρά διαδρομών +--------------- -Η σειρά με την οποία παρατίθενται οι διαδρομές είναι **πολύ σημαντική** επειδή αξιολογούνται διαδοχικά από πάνω προς τα κάτω. Ο κανόνας είναι ότι δηλώνουμε τις διαδρομές **από τις ειδικές προς τις γενικές**: +Η **σειρά** με την οποία αναφέρονται οι επιμέρους routes είναι **απολύτως κρίσιμη**, επειδή αξιολογούνται διαδοχικά από πάνω προς τα κάτω. Ισχύει ο κανόνας ότι δηλώνουμε τις routes **από τις πιο συγκεκριμένες προς τις πιο γενικές**: ```php -// ΛΑΘΟΣ: Το 'rss.xml' ταιριάζει με την πρώτη διαδρομή και το παρερμηνεύει ως +// ΛΑΘΟΣ: το 'rss.xml' πιάνεται από την πρώτη route και κατανοεί αυτή τη συμβολοσειρά ως $router->addRoute('', 'Article:view'); $router->addRoute('rss.xml', 'Feed:rss'); -// ΚΑΛΗ +// ΣΩΣΤΟ $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('', 'Article:view'); ``` -Οι διαδρομές αξιολογούνται επίσης από πάνω προς τα κάτω όταν δημιουργούνται σύνδεσμοι: +Οι routes αξιολογούνται από πάνω προς τα κάτω και κατά τη δημιουργία συνδέσμων: ```php -// ΛΑΘΟΣ: δημιουργεί έναν σύνδεσμο στο 'Feed:rss' ως 'admin/feed/rss' +// ΛΑΘΟΣ: ο σύνδεσμος προς 'Feed:rss' δημιουργείται ως 'admin/feed/rss' $router->addRoute('admin//', 'Admin:default'); $router->addRoute('rss.xml', 'Feed:rss'); -// ΚΑΛΟ +// ΣΩΣΤΟ $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('admin//', 'Admin:default'); ``` -Δεν θα το κρατήσουμε μυστικό από εσάς ότι χρειάζεται κάποια δεξιότητα για να χτίσετε σωστά μια λίστα. Μέχρι να το μάθετε, ο [πίνακας δρομολόγησης |#Debugging Router] θα είναι ένα χρήσιμο εργαλείο. +Δεν θα κρύψουμε από εσάς ότι η σωστή σύνθεση των routes απαιτεί κάποια δεξιότητα. Μέχρι να την αποκτήσετε, θα σας φανεί χρήσιμος ο [πίνακας δρομολόγησης |#Αποσφαλμάτωση του router]. -Μάσκα και παράμετροι .[#toc-mask-and-parameters] ------------------------------------------------- +Μάσκα και παράμετροι +-------------------- -Η μάσκα περιγράφει τη σχετική διαδρομή με βάση τη ρίζα της τοποθεσίας. Η απλούστερη μάσκα είναι μια στατική διεύθυνση URL: +Η μάσκα περιγράφει τη σχετική διαδρομή από τον ριζικό κατάλογο του ιστότοπου. Η απλούστερη μάσκα είναι ένα στατικό URL: ```php $router->addRoute('products', 'Products:default'); ``` -Συχνά οι μάσκες περιέχουν τις λεγόμενες **παραμέτρους**. Αυτές περικλείονται σε αγκύλες (π.χ. ``) και διαβιβάζονται στον παρουσιαστή-στόχο, για παράδειγμα στη μέθοδο `renderShow(int $year)` ή στη μόνιμη παράμετρο `$year`: +Συχνά οι μάσκες περιέχουν τις λεγόμενες **παραμέτρους**. Αυτές αναφέρονται σε αιχμηρές αγκύλες (π.χ. ``) και μεταβιβάζονται στον presenter προορισμού, για παράδειγμα στη μέθοδο `renderShow(int $year)` ή στην persistent παράμετρο `$year`: ```php $router->addRoute('chronicle/', 'History:show'); ``` -Το παράδειγμα λέει ότι αν ανοίξουμε το `https://any-domain.com/chronicle/2020` και η ενέργεια `show` με την παράμετρο `year: 2020`. +Το παράδειγμα λέει ότι αν ανοίξουμε στο πρόγραμμα περιήγησης το `https://example.com/chronicle/2020`, θα εμφανιστεί ο presenter `History` με την action `show` και την παράμετρο `year: 2020`. -Μπορούμε να καθορίσουμε μια προεπιλεγμένη τιμή για τις παραμέτρους απευθείας στη μάσκα και έτσι γίνεται προαιρετική: +Μπορούμε να ορίσουμε μια προεπιλεγμένη τιμή για τις παραμέτρους απευθείας στη μάσκα και έτσι γίνονται προαιρετικές: ```php $router->addRoute('chronicle/', 'History:show'); ``` -Η διαδρομή θα δέχεται τώρα τη διεύθυνση URL `https://any-domain.com/chronicle/` με την παράμετρο `year: 2020`. +Η route θα δέχεται τώρα και το URL `https://example.com/chronicle/`, το οποίο θα εμφανίσει ξανά το `History:show` με την παράμετρο `year: 2020`. -Φυσικά, το όνομα του παρουσιαστή και της ενέργειας μπορεί επίσης να είναι παράμετρος. Για παράδειγμα: +Παράμετρος μπορεί φυσικά να είναι και το όνομα του presenter και της action. Για παράδειγμα, έτσι: ```php $router->addRoute('/', 'Home:default'); ``` -Αυτή η διαδρομή δέχεται, για παράδειγμα, μια διεύθυνση URL της μορφής `/article/edit` ή `/catalog/list` και τις μεταφράζει σε παρουσιαστές και ενέργειες `Article:edit` ή `Catalog:list`. +Η αναφερόμενη route δέχεται, για παράδειγμα, URL της μορφής `/article/edit` ή επίσης `/catalog/list` και τα κατανοεί ως presenters και actions `Article:edit` και `Catalog:list`. -Δίνει επίσης στις παραμέτρους `presenter` και `action` προεπιλεγμένες τιμές`Home` και `default` και επομένως είναι προαιρετικές. Έτσι, η διαδρομή δέχεται επίσης ένα URL `/article` και το μεταφράζει ως `Article:default`. Ή αντίστροφα, ένας σύνδεσμος προς το `Product:default` δημιουργεί μια διαδρομή `/product`, ένας σύνδεσμος προς την προεπιλεγμένη `Home:default` δημιουργεί μια διαδρομή `/`. +Ταυτόχρονα, δίνει στις παραμέτρους `presenter` και `action` τις προεπιλεγμένες τιμές `Home` και `default` και είναι επομένως επίσης προαιρετικές. Έτσι, η route δέχεται και URL της μορφής `/article` και το κατανοεί ως `Article:default`. Ή αντίστροφα, ένας σύνδεσμος προς το `Product:default` θα δημιουργήσει τη διαδρομή `/product`, ένας σύνδεσμος προς το προεπιλεγμένο `Home:default` τη διαδρομή `/`. -Η μάσκα μπορεί να περιγράφει όχι μόνο τη σχετική διαδρομή με βάση τη ρίζα του ιστότοπου, αλλά και την απόλυτη διαδρομή όταν αρχίζει με μια κάθετο, ή ακόμη και ολόκληρη την απόλυτη διεύθυνση URL όταν αρχίζει με δύο κάθετους: +Η μάσκα μπορεί να περιγράφει όχι μόνο τη σχετική διαδρομή από τον ριζικό κατάλογο του ιστότοπου, αλλά και την απόλυτη διαδρομή, αν ξεκινά με κάθετο, ή ακόμα και ολόκληρο το απόλυτο URL, αν ξεκινά με δύο κάθετους: ```php -// σχετική διαδρομή προς τη ρίζα του εγγράφου της εφαρμογής +// σχετικά με το document root $router->addRoute('/', /* ... */); -// απόλυτη διαδρομή, σχετική με το όνομα κεντρικού υπολογιστή του διακομιστή +// απόλυτη διαδρομή (σχετικά με τον τομέα) $router->addRoute('//', /* ... */); -// απόλυτη διεύθυνση URL που περιλαμβάνει το όνομα κεντρικού υπολογιστή (αλλά σχετικό με το σχήμα) +// απόλυτο URL συμπεριλαμβανομένου του τομέα (σχετικά με το σχήμα) $router->addRoute('//.example.com//', /* ... */); -// απόλυτη διεύθυνση URL που περιλαμβάνει το σχήμα +// απόλυτο URL συμπεριλαμβανομένου του σχήματος $router->addRoute('https://.example.com//', /* ... */); ``` -Εκφράσεις επικύρωσης .[#toc-validation-expressions] ---------------------------------------------------- +Εκφράσεις επικύρωσης +-------------------- -Μια συνθήκη επικύρωσης μπορεί να καθοριστεί για κάθε παράμετρο χρησιμοποιώντας [κανονική έκφραση |https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Για παράδειγμα, ας ορίσουμε το `id` να είναι μόνο αριθμητικό, χρησιμοποιώντας το `\d+` regexp: +Για κάθε παράμετρο, μπορεί να οριστεί μια συνθήκη επικύρωσης χρησιμοποιώντας μια [κανονική έκφραση|https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Για παράδειγμα, για την παράμετρο `id`, καθορίζουμε ότι μπορεί να πάρει μόνο αριθμητικές τιμές χρησιμοποιώντας την κανονική έκφραση `\d+`: ```php $router->addRoute('/[/]', /* ... */); ``` -Η προεπιλεγμένη κανονική έκφραση για όλες τις παραμέτρους είναι `[^/]+`, δηλαδή όλα εκτός από την κάθετο. Εάν μια παράμετρος πρέπει να ταιριάζει και με μια κάθετο, ορίζουμε την κανονική έκφραση σε `.+`. +Η προεπιλεγμένη κανονική έκφραση για όλες τις παραμέτρους είναι `[^/]+`, δηλαδή οτιδήποτε εκτός από κάθετο. Αν μια παράμετρος πρέπει να δέχεται και κάθετους, καθορίζουμε την έκφραση `.+`: ```php -// δέχεται https://example.com/a/b/c, η διαδρομή είναι 'a/b/c' +// δέχεται https://example.com/a/b/c, η διαδρομή θα είναι 'a/b/c' $router->addRoute('', /* ... */); ``` -Προαιρετικές ακολουθίες .[#toc-optional-sequences] --------------------------------------------------- +Προαιρετικές ακολουθίες +----------------------- -Οι τετράγωνες αγκύλες υποδηλώνουν προαιρετικά μέρη της μάσκας. Οποιοδήποτε μέρος της μάσκας μπορεί να οριστεί ως προαιρετικό, συμπεριλαμβανομένων εκείνων που περιέχουν παραμέτρους: +Στη μάσκα, μπορείτε να επισημάνετε προαιρετικά τμήματα χρησιμοποιώντας αγκύλες. Οποιοδήποτε τμήμα της μάσκας μπορεί να είναι προαιρετικό, μπορεί να περιέχει και παραμέτρους: ```php $router->addRoute('[/]', /* ... */); -// Αποδεκτές διευθύνσεις URL: Παράμετροι: -// /en/download lang => en, name => download -// /download lang => null, name => download +// Δέχεται διαδρομές: +// /cs/download => lang => cs, name => download +// /download => lang => null, name => download ``` -Φυσικά, όταν μια παράμετρος είναι μέρος μιας προαιρετικής ακολουθίας, γίνεται επίσης προαιρετική. Εάν δεν έχει προεπιλεγμένη τιμή, θα είναι null. +Όταν μια παράμετρος είναι μέρος μιας προαιρετικής ακολουθίας, γίνεται φυσικά επίσης προαιρετική. Αν δεν έχει καθορισμένη προεπιλεγμένη τιμή, θα είναι null. -Τα προαιρετικά τμήματα μπορούν επίσης να βρίσκονται στον τομέα: +Προαιρετικά τμήματα μπορούν να υπάρχουν και στον τομέα: ```php $router->addRoute('//[.]example.com//', /* ... */); ``` -Οι ακολουθίες μπορούν να φωλιάζουν και να συνδυάζονται ελεύθερα: +Οι ακολουθίες μπορούν να ενσωματωθούν και να συνδυαστούν ελεύθερα: ```php $router->addRoute( @@ -163,48 +162,48 @@ $router->addRoute( 'Home:default', ); -// Αποδεκτές διευθύνσεις URL: -// /en/hello -// /en-us/hello -// /hello -// /hello/page-12 +// Δέχεται διαδρομές: +// /cs/hello +// /en-us/hello +// /hello +// /hello/page-12 ``` -Η γεννήτρια URL προσπαθεί να κρατήσει τη διεύθυνση URL όσο το δυνατόν συντομότερη, οπότε ό,τι μπορεί να παραλειφθεί παραλείπεται. Επομένως, για παράδειγμα, μια διαδρομή `index[.html]` παράγει μια διαδρομή `/index`. Μπορείτε να αντιστρέψετε αυτή τη συμπεριφορά γράφοντας ένα θαυμαστικό μετά την αριστερή τετράγωνη αγκύλη: +Κατά τη δημιουργία URL, επιδιώκεται η συντομότερη παραλλαγή, οπότε οτιδήποτε μπορεί να παραλειφθεί, παραλείπεται. Γι' αυτό, για παράδειγμα, η route `index[.html]` δημιουργεί τη διαδρομή `/index`. Η αναστροφή της συμπεριφοράς είναι δυνατή με την προσθήκη ενός θαυμαστικού μετά την αριστερή αγκύλη: ```php -// δέχεται τόσο το /hello όσο και το /hello.html, παράγει το /hello +// δέχεται /hello και /hello.html, δημιουργεί /hello $router->addRoute('[.html]', /* ... */); -// δέχεται τόσο το /hello όσο και το /hello.html, παράγει το /hello.html +// δέχεται /hello και /hello.html, δημιουργεί /hello.html $router->addRoute('[!.html]', /* ... */); ``` -Οι προαιρετικές παράμετροι (δηλαδή οι παράμετροι που έχουν προεπιλεγμένη τιμή) χωρίς τετράγωνες αγκύλες συμπεριφέρονται σαν να είναι τυλιγμένες έτσι: +Οι προαιρετικές παράμετροι (δηλαδή οι παράμετροι που έχουν προεπιλεγμένη τιμή) χωρίς αγκύλες συμπεριφέρονται ουσιαστικά σαν να ήταν περικλεισμένες με τον ακόλουθο τρόπο: ```php $router->addRoute('//', /* ... */); -// ισούται με: +// αντιστοιχεί σε αυτό: $router->addRoute('[/[/[]]]', /* ... */); ``` -Για να αλλάξετε τον τρόπο με τον οποίο παράγεται η δεξιότερη κάθετος, δηλαδή αντί για `/home/` να πάρετε ένα `/home`, προσαρμόστε τη διαδρομή με αυτόν τον τρόπο: +Αν θέλαμε να επηρεάσουμε τη συμπεριφορά της τελικής κάθετου, ώστε για παράδειγμα αντί για `/home/` να δημιουργείται μόνο `/home`, αυτό μπορεί να επιτευχθεί ως εξής: ```php $router->addRoute('[[/[/]]]', /* ... */); ``` -Αγριόχαρτα .[#toc-wildcards] ----------------------------- +Χαρακτήρες μπαλαντέρ +-------------------- -Στη μάσκα απόλυτης διαδρομής, μπορούμε να χρησιμοποιήσουμε τα ακόλουθα μπαλαντέρ για να αποφύγουμε, για παράδειγμα, την ανάγκη εγγραφής ενός τομέα στη μάσκα, ο οποίος μπορεί να διαφέρει στο περιβάλλον ανάπτυξης και παραγωγής: +Στη μάσκα μιας απόλυτης διαδρομής, μπορούμε να χρησιμοποιήσουμε τους ακόλουθους χαρακτήρες μπαλαντέρ και να αποφύγουμε έτσι, για παράδειγμα, την ανάγκη να γράψουμε στη μάσκα τον τομέα, ο οποίος μπορεί να διαφέρει στο περιβάλλον ανάπτυξης και παραγωγής: -- `%tld%` = τομέας ανώτατου επιπέδου, π.χ. `com` ή `org` -- `%sld%` = τομέας δεύτερου επιπέδου, π.χ. `example` +- `%tld%` = top level domain, π.χ. `com` ή `org` +- `%sld%` = second level domain, π.χ. `example` - `%domain%` = τομέας χωρίς υποτομείς, π.χ. `example.com` -- `%host%` = ολόκληρος κεντρικός υπολογιστής, π.χ. `www.example.com` +- `%host%` = ολόκληρος ο host, π.χ. `www.example.com` - `%basePath%` = διαδρομή προς τον ριζικό κατάλογο ```php @@ -213,10 +212,10 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ @@ -225,7 +224,7 @@ $router->addRoute('/[/]', [ ]); ``` -Ή μπορούμε να χρησιμοποιήσουμε αυτή τη μορφή, παρατηρήστε την αναδιατύπωση της κανονικής έκφρασης επικύρωσης: +Για πιο λεπτομερή προδιαγραφή, μπορεί να χρησιμοποιηθεί μια ακόμη πιο εκτεταμένη μορφή, όπου εκτός από τις προεπιλεγμένες τιμές, μπορούμε να ορίσουμε και άλλες ιδιότητες των παραμέτρων, όπως για παράδειγμα την κανονική έκφραση επικύρωσης (βλ. παράμετρο `id`): ```php use Nette\Routing\Route; @@ -243,19 +242,19 @@ $router->addRoute('/[/]', [ ]); ``` -Αυτές οι πιο ομιλητικές μορφές είναι χρήσιμες για την προσθήκη άλλων μεταδεδομένων. +Είναι σημαντικό να σημειωθεί ότι αν οι παράμετροι που ορίζονται στον πίνακα δεν αναφέρονται στη μάσκα της διαδρομής, οι τιμές τους δεν μπορούν να αλλάξουν, ούτε με παραμέτρους query που αναφέρονται μετά το ερωτηματικό στο URL. -Φίλτρα και μεταφράσεις .[#toc-filters-and-translations] -------------------------------------------------------- +Φίλτρα και μεταφράσεις +---------------------- -Είναι μια καλή πρακτική να γράφετε τον πηγαίο κώδικα στα αγγλικά, αλλά τι γίνεται αν θέλετε ο ιστότοπός σας να έχει μεταφρασμένο URL σε διαφορετική γλώσσα; Απλές διαδρομές, όπως: "Η γλώσσα που θα χρησιμοποιηθεί για να μεταφραστεί σε άλλη γλώσσα": +Γράφουμε τον πηγαίο κώδικα της εφαρμογής στα Αγγλικά, αλλά αν ο ιστότοπος πρέπει να έχει ελληνικά URL, τότε η απλή δρομολόγηση του τύπου: ```php $router->addRoute('/', 'Home:default'); ``` -θα δημιουργήσουν αγγλικές διευθύνσεις URL, όπως `/product/123` ή `/cart`. Αν θέλουμε να έχουμε παρουσιαστές και ενέργειες στη διεύθυνση URL μεταφρασμένες στα γερμανικά (π.χ. `/produkt/123` ή `/einkaufswagen`), μπορούμε να χρησιμοποιήσουμε ένα μεταφραστικό λεξικό. Για να το προσθέσουμε, χρειαζόμαστε ήδη μια "πιο ομιλητική" παραλλαγή της δεύτερης παραμέτρου: +θα δημιουργήσει αγγλικά URL, όπως `/product/123` ή `/cart`. Αν θέλουμε οι presenters και οι actions στο URL να αντιπροσωπεύονται από ελληνικές λέξεις (π.χ. `/produkt/123` ή `/kosik`), μπορούμε να χρησιμοποιήσουμε ένα λεξικό μετάφρασης. Για τη σύνταξή του, χρειαζόμαστε ήδη την "πιο ομιλητική" παραλλαγή της δεύτερης παραμέτρου: ```php use Nette\Routing\Route; @@ -264,26 +263,26 @@ $router->addRoute('/', [ 'presenter' => [ Route::Value => 'Home', Route::FilterTable => [ - // συμβολοσειρά στη διεύθυνση URL => παρουσιαστής + // συμβολοσειρά στο URL => presenter 'produkt' => 'Product', - 'einkaufswagen' => 'Cart', + 'kosik' => 'Cart', 'katalog' => 'Catalog', ], ], 'action' => [ Route::Value => 'default', Route::FilterTable => [ - 'liste' => 'list', + 'seznam' => 'list', ], ], ]); ``` -Πολλαπλά κλειδιά λεξικού μπορούν να χρησιμοποιηθούν για τον ίδιο παρουσιαστή. Θα δημιουργήσουν διάφορα ψευδώνυμα για αυτόν. Το τελευταίο κλειδί θεωρείται ως η κανονική παραλλαγή (δηλαδή αυτή που θα υπάρχει στο παραγόμενο URL). +Πολλά κλειδιά του λεξικού μετάφρασης μπορούν να οδηγούν στον ίδιο presenter. Έτσι, δημιουργούνται διάφορα ψευδώνυμα γι' αυτόν. Ως κανονική παραλλαγή (δηλαδή αυτή που θα βρίσκεται στο δημιουργημένο URL) θεωρείται το τελευταίο κλειδί. -Ο πίνακας μεταφράσεων μπορεί να εφαρμοστεί σε οποιαδήποτε παράμετρο με αυτόν τον τρόπο. Ωστόσο, εάν η μετάφραση δεν υπάρχει, λαμβάνεται η αρχική τιμή. Μπορούμε να αλλάξουμε αυτή τη συμπεριφορά προσθέτοντας το `Route::FilterStrict => true` και τότε η διαδρομή θα απορρίπτει το URL αν η τιμή δεν υπάρχει στο λεξικό. +Ο πίνακας μετάφρασης μπορεί να χρησιμοποιηθεί με αυτόν τον τρόπο για οποιαδήποτε παράμετρο. Ενώ αν η μετάφραση δεν υπάρχει, λαμβάνεται η αρχική τιμή. Αυτή η συμπεριφορά μπορεί να αλλάξει συμπληρώνοντας `Route::FilterStrict => true` και η route θα απορρίψει τότε το URL αν η τιμή δεν βρίσκεται στο λεξικό. -Εκτός από το μεταφραστικό λεξικό με τη μορφή πίνακα, είναι δυνατό να ορίσετε δικές σας μεταφραστικές συναρτήσεις: +Εκτός από το λεξικό μετάφρασης με τη μορφή πίνακα, μπορούν να εφαρμοστούν και προσαρμοσμένες συναρτήσεις μετάφρασης. ```php use Nette\Routing\Route; @@ -299,15 +298,15 @@ $router->addRoute('//', [ ]); ``` -Η συνάρτηση `Route::FilterIn` μετατρέπει μεταξύ της παραμέτρου στη διεύθυνση URL και της συμβολοσειράς, η οποία στη συνέχεια διαβιβάζεται στον παρουσιαστή, η συνάρτηση `FilterOut` εξασφαλίζει τη μετατροπή προς την αντίθετη κατεύθυνση. +Η συνάρτηση `Route::FilterIn` μετατρέπει μεταξύ της παραμέτρου στο URL και της συμβολοσειράς που στη συνέχεια μεταβιβάζεται στον presenter, η συνάρτηση `FilterOut` εξασφαλίζει τη μετατροπή προς την αντίθετη κατεύθυνση. -Οι παράμετροι `presenter`, `action` και `module` έχουν ήδη προκαθορισμένα φίλτρα που μετατρέπουν μεταξύ του στυλ PascalCase ή camelCase και του kebab-case που χρησιμοποιείται στο URL. Η προεπιλεγμένη τιμή των παραμέτρων είναι ήδη γραμμένη στη μετασχηματισμένη μορφή, έτσι, για παράδειγμα, στην περίπτωση ενός παρουσιαστή, γράφουμε `` αντί για ``. +Οι παράμετροι `presenter`, `action` και `module` έχουν ήδη προκαθορισμένα φίλτρα που μετατρέπουν μεταξύ του στυλ PascalCase ή camelCase και του kebab-case που χρησιμοποιείται στα URL. Η προεπιλεγμένη τιμή των παραμέτρων γράφεται ήδη στη μετασχηματισμένη μορφή, οπότε για παράδειγμα στην περίπτωση του presenter γράφουμε ``, όχι ``. -Γενικά φίλτρα .[#toc-general-filters] -------------------------------------- +Γενικά φίλτρα +------------- -Εκτός από φίλτρα για συγκεκριμένες παραμέτρους, μπορείτε επίσης να ορίσετε γενικά φίλτρα που λαμβάνουν έναν συσχετιστικό πίνακα όλων των παραμέτρων που μπορούν να τροποποιήσουν με οποιονδήποτε τρόπο και στη συνέχεια να επιστρέψουν. Τα γενικά φίλτρα ορίζονται στο κλειδί `null`. +Εκτός από τα φίλτρα που προορίζονται για συγκεκριμένες παραμέτρους, μπορούμε επίσης να ορίσουμε γενικά φίλτρα που λαμβάνουν έναν συσχετιστικό πίνακα όλων των παραμέτρων, τα οποία μπορούν να τροποποιήσουν με οποιονδήποτε τρόπο και στη συνέχεια να τα επιστρέψουν. Ορίζουμε τα γενικά φίλτρα κάτω από το κλειδί `null`. ```php use Nette\Routing\Route; @@ -315,62 +314,85 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => 'Home', 'action' => 'default', - null => [ + '' => [ Route::FilterIn => function (array $params): array { /* ... */ }, Route::FilterOut => function (array $params): array { /* ... */ }, ], ]); ``` -Τα γενικά φίλτρα σας δίνουν τη δυνατότητα να προσαρμόσετε τη συμπεριφορά της διαδρομής με απολύτως οποιονδήποτε τρόπο. Μπορούμε να τα χρησιμοποιήσουμε, για παράδειγμα, για να τροποποιήσουμε παραμέτρους με βάση άλλες παραμέτρους. Για παράδειγμα, η μετάφραση `` και `` με βάση την τρέχουσα τιμή της παραμέτρου ``. +Τα γενικά φίλτρα δίνουν τη δυνατότητα να τροποποιήσετε τη συμπεριφορά της route με απολύτως οποιονδήποτε τρόπο. Μπορούμε να τα χρησιμοποιήσουμε, για παράδειγμα, για την τροποποίηση παραμέτρων με βάση άλλες παραμέτρους. Για παράδειγμα, τη μετάφραση των `` και `` με βάση την τρέχουσα τιμή της παραμέτρου ``. -Εάν για μια παράμετρο έχει οριστεί ένα προσαρμοσμένο φίλτρο και υπάρχει ταυτόχρονα ένα γενικό φίλτρο, το προσαρμοσμένο `FilterIn` εκτελείται πριν από το γενικό και αντίστροφα το γενικό `FilterOut` εκτελείται πριν από το προσαρμοσμένο. Έτσι, εντός του γενικού φίλτρου βρίσκονται οι τιμές των παραμέτρων `presenter` και `action` γραμμένες σε PascalCase και camelCase. +Αν μια παράμετρος έχει ορισμένο δικό της φίλτρο και ταυτόχρονα υπάρχει ένα γενικό φίλτρο, εκτελείται το δικό της `FilterIn` πριν από το γενικό και αντίστροφα το γενικό `FilterOut` πριν από το δικό της. Επομένως, μέσα στο γενικό φίλτρο, οι τιμές των παραμέτρων `presenter` ή `action` είναι γραμμένες σε στυλ PascalCase ή camelCase. -Σημαία μονής κατεύθυνσης .[#toc-oneway-flag] --------------------------------------------- +Μονόδρομες OneWay +----------------- -Οι μονόδρομες διαδρομές χρησιμοποιούνται για τη διατήρηση της λειτουργικότητας παλαιών διευθύνσεων URL που η εφαρμογή δεν παράγει πλέον, αλλά εξακολουθεί να δέχεται. Τις επισημαίνουμε με το `OneWay`: +Οι μονόδρομες routes χρησιμοποιούνται για τη διατήρηση της λειτουργικότητας παλιών URL, τα οποία η εφαρμογή δεν δημιουργεί πλέον, αλλά εξακολουθεί να δέχεται. Τις επισημαίνουμε με τη σημαία `OneWay`: ```php -// παλιά διεύθυνση URL /product-info?id=123 +// παλιό URL /product-info?id=123 $router->addRoute('product-info', 'Product:detail', $router::ONE_WAY); -// νέα διεύθυνση URL /product/123 +// νέο URL /product/123 $router->addRoute('product/', 'Product:detail'); ``` -Κατά την πρόσβαση στην παλιά διεύθυνση URL, ο παρουσιαστής ανακατευθύνει αυτόματα στη νέα διεύθυνση URL, ώστε οι μηχανές αναζήτησης να μην ευρετηριάζουν αυτές τις σελίδες δύο φορές (βλ. [SEO και κανονικοποίηση |#SEO and canonization]). +Κατά την πρόσβαση στο παλιό URL, ο presenter ανακατευθύνει αυτόματα στο νέο URL, οπότε οι μηχανές αναζήτησης δεν θα ευρετηριάσουν αυτές τις σελίδες δύο φορές (βλ. [#SEO και κανονικοποίηση]). -Ενότητες .[#toc-modules] ------------------------- +Δυναμική δρομολόγηση με callbacks +--------------------------------- -Αν έχουμε περισσότερες διαδρομές που ανήκουν σε ένα [module |modules], μπορούμε να χρησιμοποιήσουμε το `withModule()` για να τις ομαδοποιήσουμε: +Η δυναμική δρομολόγηση με callbacks σας επιτρέπει να αντιστοιχίσετε απευθείας συναρτήσεις (callbacks) στις routes, οι οποίες εκτελούνται όταν επισκέπτεστε τη συγκεκριμένη διαδρομή. Αυτή η ευέλικτη λειτουργικότητα σας επιτρέπει να δημιουργείτε γρήγορα και αποτελεσματικά διάφορα τελικά σημεία (endpoints) για την εφαρμογή σας: + +```php +$router->addRoute('test', function () { + echo 'βρίσκεστε στη διεύθυνση /test'; +}); +``` + +Μπορείτε επίσης να ορίσετε παραμέτρους στη μάσκα, οι οποίες θα μεταβιβαστούν αυτόματα στο callback σας: + +```php +$router->addRoute('', function (string $lang) { + echo match ($lang) { + 'cs' => 'Καλώς ήρθατε στην τσέχικη έκδοση του ιστότοπού μας!', + 'en' => 'Welcome to the English version of our website!', + }; +}); +``` + + +Modules +------- + +Αν έχουμε πολλαπλές routes που ανήκουν σε ένα κοινό [module |directory-structure#Presenters και Πρότυπα], χρησιμοποιούμε το `withModule()`: ```php $router = new RouteList; -$router->withModule('Forum') // οι ακόλουθοι δρομολογητές αποτελούν μέρος της ενότητας Forum - ->addRoute('rss', 'Feed:rss') // παρουσιαστής είναι το Forum:Feed +$router->withModule('Forum') // οι ακόλουθες routes είναι μέρος του module Forum + ->addRoute('rss', 'Feed:rss') // ο presenter θα είναι Forum:Feed ->addRoute('/') - ->withModule('Admin') // οι ακόλουθοι δρομολογητές αποτελούν μέρος της ενότητας Forum:Admin + ->withModule('Admin') // οι ακόλουθες routes είναι μέρος του module Forum:Admin ->addRoute('sign:in', 'Sign:in'); ``` -Μια εναλλακτική λύση είναι να χρησιμοποιήσουμε την παράμετρο `module`: +Μια εναλλακτική είναι η χρήση της παραμέτρου `module`: ```php -// URL διαχείριση/πίνακας/προεπιλογή χαρτών στον παρουσιαστή Admin:Dashboard +// Το URL manage/dashboard/default αντιστοιχεί στον presenter Admin:Dashboard $router->addRoute('manage//', [ 'module' => 'Admin', ]); ``` -Υποτομείς .[#toc-subdomains] ----------------------------- +Υποτομείς +--------- -Οι συλλογές διαδρομών μπορούν να ομαδοποιηθούν ανά υποτομέα: +Μπορούμε να χωρίσουμε τις συλλογές routes ανάλογα με τους υποτομείς: ```php $router = new RouteList; @@ -379,7 +401,7 @@ $router->withDomain('example.com') ->addRoute('/'); ``` -Μπορείτε επίσης να χρησιμοποιήσετε [μπαλαντέρ |#wildcards] στο όνομα του τομέα σας: +Στο όνομα του τομέα, μπορείτε επίσης να χρησιμοποιήσετε [#Χαρακτήρες μπαλαντέρ]: ```php $router = new RouteList; @@ -388,23 +410,23 @@ $router->withDomain('example.%tld%') ``` -Πρόθεμα διαδρομής .[#toc-path-prefix] -------------------------------------- +Πρόθεμα διαδρομής +----------------- -Οι συλλογές διαδρομών μπορούν να ομαδοποιηθούν με βάση τη διαδρομή στη διεύθυνση URL: +Μπορούμε να χωρίσουμε τις συλλογές routes ανάλογα με τη διαδρομή στο URL: ```php $router = new RouteList; $router->withPath('eshop') - ->addRoute('rss', 'Feed:rss') // ταιριάζει με τη διεύθυνση URL /eshop/rss - ->addRoute('/'); // ταιριάζει με τη διεύθυνση URL /eshop// + ->addRoute('rss', 'Feed:rss') // πιάνει το URL /eshop/rss + ->addRoute('/'); // πιάνει το URL /eshop// ``` -Συνδυασμοί .[#toc-combinations] -------------------------------- +Συνδυασμός +---------- -Η παραπάνω χρήση μπορεί να συνδυαστεί: +Μπορούμε να συνδυάσουμε τις παραπάνω διαρθρώσεις μεταξύ τους: ```php $router = (new RouteList) @@ -424,40 +446,40 @@ $router = (new RouteList) ``` -Παράμετροι ερώτησης .[#toc-query-parameters] --------------------------------------------- +Παράμετροι Query +---------------- -Οι μάσκες μπορούν επίσης να περιέχουν παραμέτρους ερωτήματος (παράμετροι μετά το ερωτηματικό στη διεύθυνση URL). Δεν μπορούν να ορίσουν μια έκφραση επικύρωσης, αλλά μπορούν να αλλάξουν το όνομα με το οποίο μεταβιβάζονται στον παρουσιαστή: +Οι μάσκες μπορούν επίσης να περιέχουν παραμέτρους query (παραμέτρους μετά το ερωτηματικό στο URL). Δεν μπορεί να οριστεί γι' αυτές κανονική έκφραση επικύρωσης, αλλά μπορεί να αλλάξει το όνομα με το οποίο μεταβιβάζονται στον presenter: ```php -// χρήση της παραμέτρου ερωτήματος 'cat' ως 'categoryId' στην εφαρμογή +// θέλουμε να χρησιμοποιήσουμε την παράμετρο query 'cat' στην εφαρμογή με το όνομα 'categoryId' $router->addRoute('product ? id= & cat=', /* ... */); ``` -Foo Parameters .[#toc-foo-parameters] -------------------------------------- +Παράμετροι Foo +-------------- -Πάμε πιο βαθιά τώρα. Οι παράμετροι Foo είναι βασικά μη ονομαστικές παράμετροι που επιτρέπουν να ταιριάζουν με μια κανονική έκφραση. Η παρακάτω διαδρομή ταιριάζει με τις `/index`, `/index.html`, `/index.htm` και `/index.php`: +Τώρα πηγαίνουμε βαθύτερα. Οι παράμετροι Foo είναι ουσιαστικά ανώνυμες παράμετροι που επιτρέπουν την αντιστοίχιση μιας κανονικής έκφρασης. Παράδειγμα είναι μια route που δέχεται `/index`, `/index.html`, `/index.htm` και `/index.php`: ```php $router->addRoute('index', /* ... */); ``` -Είναι επίσης δυνατό να ορίσετε ρητά μια συμβολοσειρά η οποία θα χρησιμοποιηθεί για τη δημιουργία URL. Η συμβολοσειρά πρέπει να τοποθετηθεί αμέσως μετά το ερωτηματικό. Η παρακάτω διαδρομή είναι παρόμοια με την προηγούμενη, αλλά παράγει το `/index.html` αντί για το `/index` επειδή η συμβολοσειρά `.html` έχει οριστεί ως "παραγόμενη τιμή". +Μπορείτε επίσης να ορίσετε ρητά τη συμβολοσειρά που θα χρησιμοποιηθεί κατά τη δημιουργία του URL. Η συμβολοσειρά πρέπει να τοποθετηθεί αμέσως μετά το ερωτηματικό. Η ακόλουθη route είναι παρόμοια με την προηγούμενη, αλλά δημιουργεί `/index.html` αντί για `/index`, επειδή η συμβολοσειρά `.html` έχει οριστεί ως τιμή δημιουργίας: ```php $router->addRoute('index', /* ... */); ``` -Ενσωμάτωση .[#toc-integration] -============================== +Ενσωμάτωση στην εφαρμογή +======================== -Για να συνδέσουμε τον δρομολογητή μας στην εφαρμογή, πρέπει να ενημερώσουμε το DI container σχετικά με αυτόν. Ο ευκολότερος τρόπος είναι να προετοιμάσουμε το εργοστάσιο που θα κατασκευάσει το αντικείμενο του δρομολογητή και να πούμε στη διαμόρφωση του δοχείου να το χρησιμοποιήσει. Ας πούμε λοιπόν ότι γράφουμε μια μέθοδο για το σκοπό αυτό `App\Router\RouterFactory::createRouter()`: +Για να ενσωματώσουμε τον δημιουργημένο router στην εφαρμογή, πρέπει να ενημερώσουμε το DI container γι' αυτόν. Ο ευκολότερος τρόπος είναι να προετοιμάσουμε ένα factory που θα παράγει το αντικείμενο του router και να πούμε στη διαμόρφωση του container ότι πρέπει να το χρησιμοποιήσει. Ας υποθέσουμε ότι γι' αυτόν τον σκοπό γράφουμε τη μέθοδο `App\Core\RouterFactory::createRouter()`: ```php -namespace App\Router; +namespace App\Core; use Nette\Application\Routers\RouteList; @@ -472,14 +494,14 @@ class RouterFactory } ``` -Στη συνέχεια, γράφουμε στη [διαμόρφωση |dependency-injection:services]: +Στη [διαμόρφωση |dependency-injection:services] στη συνέχεια γράφουμε: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter ``` -Οποιεσδήποτε εξαρτήσεις, όπως μια σύνδεση βάσης δεδομένων κ.λπ., περνούν στη μέθοδο factory ως παράμετροι με τη χρήση [αυτόματης σύνδεσης |dependency-injection:autowiring]: +Οποιεσδήποτε εξαρτήσεις, για παράδειγμα από τη βάση δεδομένων κ.λπ., μεταβιβάζονται στην factory μέθοδο ως παράμετροί της χρησιμοποιώντας το [autowiring|dependency-injection:autowiring]: ```php public static function createRouter(Nette\Database\Connection $db): RouteList @@ -489,25 +511,25 @@ public static function createRouter(Nette\Database\Connection $db): RouteList ``` -SimpleRouter .[#toc-simplerouter] -================================= +SimpleRouter +============ -Ένας πολύ απλούστερος δρομολογητής από το Route Collection είναι ο [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Μπορεί να χρησιμοποιηθεί όταν δεν υπάρχει ανάγκη για μια συγκεκριμένη μορφή URL, όταν το `mod_rewrite` (ή εναλλακτικές λύσεις) δεν είναι διαθέσιμες ή όταν απλά δεν θέλουμε να ασχοληθούμε ακόμα με φιλικές προς το χρήστη διευθύνσεις URL. +Ένας πολύ απλούστερος router από τη συλλογή routes είναι ο [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Τον χρησιμοποιούμε όταν δεν έχουμε ιδιαίτερες απαιτήσεις για τη μορφή των URL, όταν δεν είναι διαθέσιμο το `mod_rewrite` (ή οι εναλλακτικές του) ή όταν δεν θέλουμε ακόμα να ασχοληθούμε με όμορφα URL. -Παράγει διευθύνσεις με αυτή περίπου τη μορφή: +Δημιουργεί διευθύνσεις περίπου σε αυτή τη μορφή: ``` http://example.com/?presenter=Product&action=detail&id=123 ``` -Η παράμετρος του κατασκευαστή `SimpleRouter` είναι ένας προεπιλεγμένος παρουσιαστής & ενέργεια, δηλαδή ενέργεια που θα εκτελεστεί αν ανοίξουμε π.χ. το `http://example.com/` χωρίς πρόσθετες παραμέτρους. +Η παράμετρος του κατασκευαστή του SimpleRouter είναι ο προεπιλεγμένος presenter & η action, στην οποία πρέπει να κατευθυνθεί, αν ανοίξουμε τη σελίδα χωρίς παραμέτρους, π.χ. `http://example.com/`. ```php -// προεπιλογή παρουσιαστή 'Home' και δράσης 'default' +// ο προεπιλεγμένος presenter θα είναι 'Home' και η action 'default' $router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` -Συνιστούμε τον ορισμό του SimpleRouter απευθείας στη [διαμόρφωση |dependency-injection:services]: +Συνιστούμε να ορίσετε τον SimpleRouter απευθείας στη [διαμόρφωση |dependency-injection:services]: ```neon services: @@ -515,22 +537,22 @@ services: ``` -SEO και Canonization .[#toc-seo-and-canonization] -================================================= +SEO και κανονικοποίηση +====================== -Το πλαίσιο αυξάνει το SEO (βελτιστοποίηση μηχανών αναζήτησης) αποτρέποντας την επανάληψη περιεχομένου σε διαφορετικές διευθύνσεις URL. Εάν πολλαπλές διευθύνσεις παραπέμπουν στον ίδιο προορισμό, π.χ. `/index` και `/index.html`, το πλαίσιο καθορίζει την πρώτη ως κύρια (κανονική) και ανακατευθύνει τις υπόλοιπες σε αυτήν χρησιμοποιώντας τον κωδικό HTTP 301. Χάρη σε αυτό, οι μηχανές αναζήτησης δεν ευρετηριάζουν τις σελίδες δύο φορές και δεν χαλάει η κατάταξη της σελίδας τους. . +Το framework συμβάλλει στο SEO (βελτιστοποίηση για μηχανές αναζήτησης) αποτρέποντας τη διπλή εμφάνιση περιεχομένου σε διαφορετικά URL. Αν υπάρχουν πολλαπλές διευθύνσεις που οδηγούν στον ίδιο στόχο, π.χ. `/index` και `/index.html`, το framework καθορίζει την πρώτη από αυτές ως την κύρια (κανονική) και ανακατευθύνει τις υπόλοιπες σε αυτήν χρησιμοποιώντας τον κωδικό HTTP 301. Χάρη σε αυτό, οι μηχανές αναζήτησης δεν ευρετηριάζουν τις σελίδες σας δύο φορές και δεν διασπούν το page rank τους. -Αυτή η διαδικασία ονομάζεται κανονικοποίηση. Η κανονική διεύθυνση URL είναι αυτή που δημιουργείται από τον δρομολογητή, δηλαδή από την πρώτη αντίστοιχη διαδρομή στη [συλλογή |#route-collection] χωρίς τη σημαία OneWay. Επομένως, στη συλλογή, παραθέτουμε πρώτα τις **πρωτεύουσες διαδρομές**. +Αυτή η διαδικασία ονομάζεται κανονικοποίηση. Η κανονική διεύθυνση URL είναι αυτή που δημιουργείται από τον router, δηλαδή η πρώτη κατάλληλη route στη συλλογή χωρίς τη σημαία OneWay. Γι' αυτό στη συλλογή αναφέρουμε τις **κύριες routes πρώτες**. -Η κανονικοποίηση εκτελείται από τον παρουσιαστή, περισσότερα στο κεφάλαιο [κανονικοποίηση |presenters#Canonization]. +Η κανονικοποίηση εκτελείται από τον presenter, περισσότερα στο κεφάλαιο [κανονικοποίηση |presenters#Κανονικοποίηση]. -HTTPS .[#toc-https] -=================== +HTTPS +===== -Για να χρησιμοποιήσετε το πρωτόκολλο HTTPS, είναι απαραίτητο να το ενεργοποιήσετε στη φιλοξενία και να ρυθμίσετε τις παραμέτρους του διακομιστή. +Για να μπορούμε να χρησιμοποιούμε το πρωτόκολλο HTTPS, είναι απαραίτητο να το ενεργοποιήσουμε στο hosting και να διαμορφώσουμε σωστά τον διακομιστή. -Η ανακατεύθυνση ολόκληρου του ιστότοπου σε HTTPS πρέπει να πραγματοποιηθεί σε επίπεδο διακομιστή, για παράδειγμα με τη χρήση του αρχείου .htaccess στον ριζικό κατάλογο της εφαρμογής μας, με κωδικό HTTP 301. Οι ρυθμίσεις μπορεί να διαφέρουν ανάλογα με τη φιλοξενία και μοιάζουν κάπως έτσι: +Η ανακατεύθυνση ολόκληρου του ιστότοπου σε HTTPS πρέπει να ρυθμιστεί σε επίπεδο διακομιστή, για παράδειγμα, χρησιμοποιώντας το αρχείο .htaccess στον ριζικό κατάλογο της εφαρμογής μας, και αυτό με τον κωδικό HTTP 301. Η ρύθμιση μπορεί να διαφέρει ανάλογα με το hosting και μοιάζει περίπου έτσι: ``` @@ -542,40 +564,40 @@ HTTPS .[#toc-https] ``` -Ο δρομολογητής δημιουργεί μια διεύθυνση URL με το ίδιο πρωτόκολλο με αυτό που φορτώθηκε η σελίδα, οπότε δεν χρειάζεται να ρυθμίσετε κάτι άλλο. +Ο router δημιουργεί URL με το ίδιο πρωτόκολλο με το οποίο φορτώθηκε η σελίδα, οπότε τίποτα περισσότερο δεν χρειάζεται να ρυθμιστεί. -Ωστόσο, αν κατ' εξαίρεση χρειαζόμαστε διαφορετικές διαδρομές για να εκτελούνται κάτω από διαφορετικά πρωτόκολλα, θα το βάλουμε στη μάσκα διαδρομής: +Αν όμως εξαιρετικά χρειαζόμαστε διαφορετικές routes να εκτελούνται με διαφορετικά πρωτόκολλα, το αναφέρουμε στη μάσκα της route: ```php -// Θα δημιουργήσει μια διεύθυνση HTTP +// Θα δημιουργήσει διεύθυνση με HTTP $router->addRoute('http://%host%//', /* ... */); -// Θα δημιουργήσει μια διεύθυνση HTTPS +// Θα δημιουργήσει διεύθυνση με HTTPS $router->addRoute('https://%host%//', /* ... */); ``` -Αποσφαλμάτωση δρομολογητή .[#toc-debugging-router] -================================================== +Αποσφαλμάτωση του router +======================== -Η μπάρα δρομολόγησης που εμφανίζεται στο [Tracy |tracy:] Bar είναι ένα χρήσιμο εργαλείο που εμφανίζει μια λίστα με τις διαδρομές και επίσης τις παραμέτρους που έχει λάβει ο δρομολογητής από τη διεύθυνση URL. +Ο πίνακας δρομολόγησης που εμφανίζεται στη [Tracy Bar |tracy:] είναι ένα χρήσιμο βοήθημα που εμφανίζει τη λίστα των routes και επίσης τις παραμέτρους που απέκτησε ο router από το URL. -Η πράσινη μπάρα με το σύμβολο ✓ αντιπροσωπεύει τη διαδρομή που ταιριάζει με την τρέχουσα διεύθυνση URL, οι μπλε μπάρες με τα σύμβολα ≈ υποδεικνύουν τις διαδρομές που θα ταιριάζουν επίσης με τη διεύθυνση URL αν δεν τις προσπεράσει το πράσινο. Βλέπουμε περαιτέρω τον τρέχοντα παρουσιαστή & τη δράση. +Η πράσινη γραμμή με το σύμβολο ✓ αντιπροσωπεύει τη route που επεξεργάστηκε το τρέχον URL, με μπλε χρώμα και το σύμβολο ≈ επισημαίνονται οι routes που θα επεξεργάζονταν επίσης το URL αν η πράσινη δεν τις είχε προλάβει. Παρακάτω βλέπουμε τον τρέχοντα presenter & την action. [* routing-debugger.webp *] -Ταυτόχρονα, αν υπάρχει μια απροσδόκητη ανακατεύθυνση λόγω [κανονικοποίησης |#SEO and Canonization], είναι χρήσιμο να κοιτάξουμε στη μπάρα *ανακατεύθυνση* για να δούμε πώς ο δρομολογητής αρχικά κατανόησε τη διεύθυνση URL και γιατί ανακατεύθυνε. +Ταυτόχρονα, αν συμβεί μια μη αναμενόμενη ανακατεύθυνση λόγω [κανονικοποίησης |#SEO και κανονικοποίηση], είναι χρήσιμο να κοιτάξετε τον πίνακα στη γραμμή *redirect*, όπου θα μάθετε πώς ο router κατανόησε αρχικά το URL και γιατί ανακατεύθυνε. .[note] -Κατά την αποσφαλμάτωση του δρομολογητή, συνιστάται να ανοίγετε τα Εργαλεία ανάπτυξης στο πρόγραμμα περιήγησης (Ctrl+Shift+I ή Cmd+Option+I) και να απενεργοποιείτε την προσωρινή μνήμη στο πάνελ Δικτύου, ώστε να μην αποθηκεύονται σε αυτήν οι ανακατευθύνσεις. +Κατά την αποσφαλμάτωση του router, συνιστούμε να ανοίξετε τα Developer Tools στο πρόγραμμα περιήγησης (Ctrl+Shift+I ή Cmd+Option+I) και στον πίνακα Network να απενεργοποιήσετε την cache, ώστε να μην αποθηκεύονται σε αυτήν οι ανακατευθύνσεις. -Απόδοση .[#toc-performance] -=========================== +Απόδοση +======= -Ο αριθμός των διαδρομών επηρεάζει την ταχύτητα του δρομολογητή. Ο αριθμός τους δεν θα πρέπει οπωσδήποτε να υπερβαίνει μερικές δεκάδες. Αν ο ιστότοπός σας έχει μια υπερβολικά περίπλοκη δομή URL, μπορείτε να γράψετε έναν [προσαρμοσμένο δρομολογητή |#custom router]. +Ο αριθμός των routes επηρεάζει την ταχύτητα του router. Ο αριθμός τους σίγουρα δεν θα πρέπει να υπερβαίνει μερικές δεκάδες. Αν ο ιστότοπός σας έχει πολύπλοκη δομή URL, μπορείτε να γράψετε έναν προσαρμοσμένο [#Προσαρμοσμένος router]. -Αν ο δρομολογητής δεν έχει εξαρτήσεις, όπως από μια βάση δεδομένων, και το εργοστάσιό του δεν έχει ορίσματα, μπορούμε να σειριοποιήσουμε τη μεταγλωττισμένη μορφή του απευθείας σε ένα DI container και έτσι να κάνουμε την εφαρμογή ελαφρώς ταχύτερη. +Αν ο router δεν έχει εξαρτήσεις, για παράδειγμα από τη βάση δεδομένων, και το factory του δεν δέχεται ορίσματα, μπορούμε να σειριοποιήσουμε τη συναρμολογημένη του μορφή απευθείας στο DI container και έτσι να επιταχύνουμε ελαφρώς την εφαρμογή. ```neon routing: @@ -583,10 +605,10 @@ routing: ``` -Προσαρμοσμένος δρομολογητής .[#toc-custom-router] -================================================= +Προσαρμοσμένος router +===================== -Οι ακόλουθες γραμμές προορίζονται για πολύ προχωρημένους χρήστες. Μπορείτε να δημιουργήσετε το δικό σας δρομολογητή και φυσικά να τον προσθέσετε στη συλλογή δρομολογίων σας. Ο δρομολογητής είναι μια υλοποίηση της διεπαφής [api:Nette\Routing\Router] με δύο μεθόδους: +Οι παρακάτω γραμμές προορίζονται για πολύ προχωρημένους χρήστες. Μπορείτε να δημιουργήσετε τον δικό σας router και να τον ενσωματώσετε εντελώς φυσικά στη συλλογή των routes. Ο router είναι μια υλοποίηση του interface [api:Nette\Routing\Router] με δύο μεθόδους: ```php use Nette\Http\IRequest as HttpRequest; @@ -606,8 +628,7 @@ class MyRouter implements Nette\Routing\Router } ``` -Η μέθοδος `match` επεξεργάζεται το τρέχον [$httpRequest |http:request], από το οποίο μπορεί να ανακτηθεί όχι μόνο η διεύθυνση URL, αλλά και οι επικεφαλίδες κ.λπ., σε έναν πίνακα που περιέχει το όνομα του παρουσιαστή και τις παραμέτρους του. Εάν δεν μπορεί να επεξεργαστεί το αίτημα, επιστρέφει null. -Κατά την επεξεργασία του αιτήματος, πρέπει να επιστρέψουμε τουλάχιστον τον παρουσιαστή και την ενέργεια. Το όνομα του παρουσιαστή είναι πλήρες και περιλαμβάνει τυχόν ενότητες: +Η μέθοδος `match` επεξεργάζεται το τρέχον αίτημα [$httpRequest |http:request], από το οποίο μπορείτε να λάβετε όχι μόνο το URL, αλλά και τις κεφαλίδες κ.λπ., σε έναν πίνακα που περιέχει το όνομα του presenter και τις παραμέτρους του. Αν δεν μπορεί να επεξεργαστεί το αίτημα, επιστρέφει null. Κατά την επεξεργασία του αιτήματος, πρέπει να επιστρέψουμε τουλάχιστον τον presenter και την action. Το όνομα του presenter είναι πλήρες και περιέχει και τυχόν modules: ```php [ @@ -616,9 +637,9 @@ class MyRouter implements Nette\Routing\Router ] ``` -Η μέθοδος `constructUrl`, από την άλλη πλευρά, παράγει μια απόλυτη διεύθυνση URL από τον πίνακα παραμέτρων. Μπορεί να χρησιμοποιήσει τις πληροφορίες από την παράμετρο `$refUrl`, η οποία είναι η τρέχουσα διεύθυνση URL. +Η μέθοδος `constructUrl` αντίθετα συναρμολογεί από τον πίνακα παραμέτρων το τελικό απόλυτο URL. Γι' αυτό μπορεί να χρησιμοποιήσει πληροφορίες από την παράμετρο [`$refUrl`|api:Nette\Http\UrlScript], που είναι το τρέχον URL. -Για να προσθέσετε προσαρμοσμένο δρομολογητή στη συλλογή διαδρομών, χρησιμοποιήστε τη μέθοδο `add()`: +Τον προσθέτετε στη συλλογή των routes χρησιμοποιώντας το `add()`: ```php $router = new Nette\Application\Routers\RouteList; @@ -628,19 +649,19 @@ $router->addRoute(/* ... */); ``` -Ξεχωριστή χρήση .[#toc-separated-usage] -======================================= +Αυτόνομη χρήση +============== -Με τον όρο διαχωρισμένη χρήση εννοούμε τη χρήση των δυνατοτήτων του δρομολογητή σε μια εφαρμογή που δεν χρησιμοποιεί την εφαρμογή Nette και τους παρουσιαστές. Σχεδόν όλα όσα παρουσιάσαμε σε αυτό το κεφάλαιο ισχύουν γι' αυτήν, με τις ακόλουθες διαφορές: +Με την αυτόνομη χρήση εννοούμε τη χρήση των δυνατοτήτων του router σε μια εφαρμογή που δεν χρησιμοποιεί το Nette Application και τους presenters. Ισχύουν γι' αυτόν σχεδόν όλα όσα δείξαμε σε αυτό το κεφάλαιο, με τις εξής διαφορές: -- για τις συλλογές διαδρομών χρησιμοποιούμε την κλάση [api:Nette\Routing\RouteList] -- ως μια απλή κλάση δρομολογητή [api:Nette\Routing\SimpleRouter] -- επειδή δεν υπάρχει ζεύγος `Presenter:action`, χρησιμοποιούμε [τον συμβολισμό Advanced |#Advanced notation] +- για συλλογές routes χρησιμοποιούμε την κλάση [api:Nette\Routing\RouteList] +- ως simple router την κλάση [api:Nette\Routing\SimpleRouter] +- επειδή δεν υπάρχει το ζεύγος `Presenter:action`, χρησιμοποιούμε την [#Εκτεταμένη σημειογραφία] -Έτσι και πάλι θα δημιουργήσουμε μια μέθοδο που θα κατασκευάσει ένα δρομολογητή, για παράδειγμα: +Έτσι, ξανά δημιουργούμε μια μέθοδο που θα μας συναρμολογήσει τον router, π.χ.: ```php -namespace App\Router; +namespace App\Core; use Nette\Routing\RouteList; @@ -661,35 +682,35 @@ class RouterFactory } ``` -Εάν χρησιμοποιείτε ένα DI container, το οποίο συνιστούμε, προσθέστε ξανά τη μέθοδο στη διαμόρφωση και στη συνέχεια λάβετε το δρομολογητή μαζί με την αίτηση HTTP από το container: +Αν χρησιμοποιείτε DI container, το οποίο συνιστούμε, προσθέτουμε ξανά τη μέθοδο στη διαμόρφωση και στη συνέχεια λαμβάνουμε τον router μαζί με το αίτημα HTTP από το container: ```php $router = $container->getByType(Nette\Routing\Router::class); $httpRequest = $container->getByType(Nette\Http\IRequest::class); ``` -Ή θα δημιουργήσουμε αντικείμενα απευθείας: +Ή δημιουργούμε τα αντικείμενα απευθείας: ```php -$router = App\Router\RouterFactory::createRouter(); +$router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` -Τώρα πρέπει να αφήσουμε τον δρομολογητή να λειτουργήσει: +Τώρα μένει μόνο να αφήσουμε τον router να δουλέψει: ```php $params = $router->match($httpRequest); if ($params === null) { - // δεν βρέθηκε αντίστοιχη διαδρομή, θα στείλουμε ένα σφάλμα 404 + // δεν βρέθηκε αντίστοιχη route, αποστολή σφάλματος 404 exit; } -// επεξεργαζόμαστε τις παραμέτρους που λάβαμε +// επεξεργασία των ληφθέντων παραμέτρων $controller = $params['controller']; // ... ``` -Και αντίστροφα, θα χρησιμοποιήσουμε το δρομολογητή για να δημιουργήσουμε τη σύνδεση: +Και αντίστροφα, χρησιμοποιούμε τον router για να συναρμολογήσουμε έναν σύνδεσμο: ```php $params = ['controller' => 'ArticleController', 'id' => 123]; diff --git a/application/el/templates.texy b/application/el/templates.texy index c4ee865e55..41c922b8a1 100644 --- a/application/el/templates.texy +++ b/application/el/templates.texy @@ -2,9 +2,9 @@ ******* .[perex] -Η Nette χρησιμοποιεί το σύστημα προτύπων [Latte |latte:]. Το Latte χρησιμοποιείται επειδή είναι το πιο ασφαλές σύστημα προτύπων για την PHP και ταυτόχρονα το πιο διαισθητικό σύστημα. Δεν χρειάζεται να μάθετε πολλά καινούργια, αρκεί να γνωρίζετε PHP και μερικές ετικέτες Latte. +Το Nette χρησιμοποιεί το σύστημα προτύπων [Latte |latte:]. Αφενός επειδή είναι το πιο ασφαλές σύστημα προτύπων για PHP, και αφετέρου το πιο διαισθητικό σύστημα. Δεν χρειάζεται να μάθετε πολλά νέα πράγματα, αρκεί η γνώση της PHP και μερικών ετικετών. -Συνήθως η σελίδα ολοκληρώνεται από το πρότυπο διάταξης + το πρότυπο δράσης. Έτσι μπορεί να μοιάζει ένα πρότυπο διάταξης, προσέξτε τα μπλοκ `{block}` και την ετικέτα `{include}`: +Είναι σύνηθες μια σελίδα να αποτελείται από ένα πρότυπο διάταξης + το πρότυπο της συγκεκριμένης action. Έτσι μπορεί να μοιάζει ένα πρότυπο διάταξης, παρατηρήστε τα μπλοκ `{block}` και την ετικέτα `{include}`: ```latte @@ -20,7 +20,7 @@ ``` -Και αυτό μπορεί να είναι το πρότυπο δράσης: +Και αυτό θα είναι το πρότυπο της action: ```latte {block title}Homepage{/block} @@ -31,50 +31,98 @@ {/block} ``` -Ορίζει το μπλοκ `content`, το οποίο εισάγεται στη θέση του `{include content}` στη διάταξη, και επίσης επαναπροσδιορίζει το μπλοκ `title`, το οποίο αντικαθιστά το `{block title}` στη διάταξη. Προσπαθήστε να φανταστείτε το αποτέλεσμα. +Αυτό ορίζει το μπλοκ `content`, το οποίο εισάγεται στη θέση του `{include content}` στη διάταξη, και επίσης επαναπροσδιορίζει το μπλοκ `title`, το οποίο αντικαθιστά το `{block title}` στη διάταξη. Προσπαθήστε να φανταστείτε το αποτέλεσμα. -Αναζήτηση προτύπων .[#toc-search-for-templates] ------------------------------------------------ +Αναζήτηση προτύπου +------------------ -Η διαδρομή προς τα πρότυπα προκύπτει σύμφωνα με μια απλή λογική. Προσπαθεί να δει αν ένα από αυτά τα αρχεία προτύπων υπάρχει σε σχέση με τον κατάλογο όπου βρίσκεται η κλάση presenter, όπου `` είναι το όνομα του τρέχοντος παρουσιαστή και `` είναι το όνομα της τρέχουσας δράσης: +Δεν χρειάζεται να καθορίσετε στους presenters ποιο πρότυπο πρέπει να αποδοθεί, το framework θα βρει τη διαδρομή μόνο του και θα σας γλιτώσει από το γράψιμο. -- `templates//.latte` -- `templates/..latte` +Αν χρησιμοποιείτε μια δομή καταλόγων όπου κάθε presenter έχει τον δικό του κατάλογο, απλά τοποθετήστε το πρότυπο σε αυτόν τον κατάλογο με το όνομα της action (ή του view), δηλαδή για την action `default` χρησιμοποιήστε το πρότυπο `default.latte`: -Αν δεν βρει το πρότυπο, η απάντηση είναι [σφάλμα 404 |presenters#Error 404 etc.]. +/--pre +app/ +└── Presentation/ + └── Home/ + ├── HomePresenter.php + └── default.latte +\-- -Μπορείτε επίσης να αλλάξετε την προβολή χρησιμοποιώντας το `$this->setView('otherView')`. Ή, αντί για αναζήτηση, καθορίστε απευθείας το όνομα του αρχείου προτύπου χρησιμοποιώντας τη διεύθυνση `$this->template->setFile('/path/to/template.latte')`. +Αν χρησιμοποιείτε μια δομή όπου οι presenters βρίσκονται μαζί σε έναν κατάλογο και τα πρότυπα στον φάκελο `templates`, αποθηκεύστε το είτε στο αρχείο `..latte` είτε στο `/.latte`: + +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── Home.default.latte ← 1η παραλλαγή + └── Home/ + └── default.latte ← 2η παραλλαγή +\-- + +Ο κατάλογος `templates` μπορεί επίσης να βρίσκεται ένα επίπεδο πιο πάνω, δηλαδή στο ίδιο επίπεδο με τον κατάλογο με τις κλάσεις των presenters. + +Αν το πρότυπο δεν βρεθεί, ο presenter απαντά με [σφάλμα 404 - η σελίδα δεν βρέθηκε |presenters#Σφάλμα 404 κ.λπ]. + +Μπορείτε να αλλάξετε το view χρησιμοποιώντας το `$this->setView('jineView')`. Μπορείτε επίσης να καθορίσετε απευθείας το αρχείο με το πρότυπο χρησιμοποιώντας το `$this->template->setFile('/path/to/template.latte')`. .[note] -Μπορείτε να αλλάξετε τις διαδρομές στις οποίες αναζητούνται τα πρότυπα υπερκαλύπτοντας τη μέθοδο [formatTemplateFiles |api:Nette\Application\UI\Presenter::formatTemplateFiles()], η οποία επιστρέφει έναν πίνακα πιθανών διαδρομών αρχείων. +Τα αρχεία όπου αναζητούνται τα πρότυπα μπορούν να αλλάξουν αντικαθιστώντας τη μέθοδο [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], η οποία επιστρέφει έναν πίνακα πιθανών ονομάτων αρχείων. + + +Αναζήτηση προτύπου διάταξης +--------------------------- + +Το Nette αναζητά επίσης αυτόματα το αρχείο με τη διάταξη. + +Αν χρησιμοποιείτε μια δομή καταλόγων όπου κάθε presenter έχει τον δικό του κατάλογο, τοποθετήστε τη διάταξη είτε στον φάκελο με τον presenter, αν είναι συγκεκριμένη μόνο γι' αυτόν, είτε ένα επίπεδο πιο πάνω, αν είναι κοινή για πολλούς presenters: + +/--pre +app/ +└── Presentation/ + ├── @layout.latte ← κοινή διάταξη + └── Home/ + ├── @layout.latte ← μόνο για τον presenter Home + ├── HomePresenter.php + └── default.latte +\-- + +Αν χρησιμοποιείτε μια δομή όπου οι presenters βρίσκονται μαζί σε έναν κατάλογο και τα πρότυπα στον φάκελο `templates`, η διάταξη θα αναμένεται σε αυτές τις θέσεις: -Η διάταξη αναμένεται στα ακόλουθα αρχεία: +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── @layout.latte ← κοινή διάταξη + ├── Home.@layout.latte ← μόνο για Home, 1η παραλλαγή + └── Home/ + └── @layout.latte ← μόνο για Home, 2η παραλλαγή +\-- -- `templates//@.latte` -- `templates/.@.latte` -- `templates/@.latte` διάταξη κοινή για πολλούς παρουσιαστές +Αν ο presenter βρίσκεται σε ένα module, η αναζήτηση θα γίνει και σε περαιτέρω επίπεδα καταλόγων, ανάλογα με την ένθεση του module. -`` είναι το όνομα του τρέχοντος παρουσιαστή και `` είναι το όνομα της διάταξης, η οποία είναι εξ ορισμού `'layout'`. Το όνομα μπορεί να αλλάξει με το `$this->setLayout('otherLayout')`, έτσι ώστε να δοκιμάζονται τα αρχεία `@otherLayout.latte`. +Το όνομα της διάταξης μπορεί να αλλάξει χρησιμοποιώντας το `$this->setLayout('layoutAdmin')` και τότε θα αναμένεται στο αρχείο `@layoutAdmin.latte`. Μπορείτε επίσης να καθορίσετε απευθείας το αρχείο με το πρότυπο διάταξης χρησιμοποιώντας το `$this->setLayout('/path/to/template.latte')`. -Μπορείτε επίσης να καθορίσετε απευθείας το όνομα του αρχείου του προτύπου διάταξης χρησιμοποιώντας το `$this->setLayout('/path/to/template.latte')`. Η χρήση του `$this->setLayout(false)` θα απενεργοποιήσει την αναζήτηση διάταξης. +Χρησιμοποιώντας το `$this->setLayout(false)` ή την ετικέτα `{layout none}` μέσα στο πρότυπο, η αναζήτηση διάταξης απενεργοποιείται. .[note] -Μπορείτε να αλλάξετε τις διαδρομές στις οποίες αναζητούνται τα πρότυπα με την παράκαμψη της μεθόδου [formatLayoutTemplateFiles |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], η οποία επιστρέφει έναν πίνακα πιθανών διαδρομών αρχείων. +Τα αρχεία όπου αναζητούνται τα πρότυπα διάταξης μπορούν να αλλάξουν αντικαθιστώντας τη μέθοδο [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], η οποία επιστρέφει έναν πίνακα πιθανών ονομάτων αρχείων. -Μεταβλητές στο πρότυπο .[#toc-variables-in-the-template] --------------------------------------------------------- +Μεταβλητές στο πρότυπο +---------------------- -Οι μεταβλητές περνούν στο πρότυπο γράφοντάς τες στο `$this->template` και στη συνέχεια είναι διαθέσιμες στο πρότυπο ως τοπικές μεταβλητές: +Μεταβιβάζουμε μεταβλητές στο πρότυπο γράφοντάς τες στο `$this->template` και στη συνέχεια τις έχουμε διαθέσιμες στο πρότυπο ως τοπικές μεταβλητές: ```php $this->template->article = $this->articles->getById($id); ``` -Με αυτόν τον τρόπο μπορούμε εύκολα να περάσουμε οποιεσδήποτε μεταβλητές στα πρότυπα. Ωστόσο, κατά την ανάπτυξη εύρωστων εφαρμογών, είναι συχνά πιο χρήσιμο να περιοριστούμε. Για παράδειγμα, ορίζοντας ρητά μια λίστα με τις μεταβλητές που αναμένει το πρότυπο και τους τύπους τους. Αυτό θα επιτρέψει στην PHP να κάνει έλεγχο τύπου, στο IDE να συμπληρώσει σωστά την αυτόματη συμπλήρωση και στη στατική ανάλυση να εντοπίσει σφάλματα. +Με αυτόν τον απλό τρόπο, μπορούμε να μεταβιβάσουμε οποιεσδήποτε μεταβλητές στα πρότυπα. Ωστόσο, κατά την ανάπτυξη στιβαρών εφαρμογών, είναι συνήθως πιο χρήσιμο να περιοριστούμε. Για παράδειγμα, ορίζοντας ρητά μια λίστα μεταβλητών που αναμένει το πρότυπο και τους τύπους τους. Χάρη σε αυτό, η PHP θα μπορεί να ελέγχει τους τύπους, το IDE να προτείνει σωστά και η στατική ανάλυση να εντοπίζει σφάλματα. -Και πώς ορίζουμε μια τέτοια απαρίθμηση; Απλά με τη μορφή μιας κλάσης και των ιδιοτήτων της. Την ονομάζουμε παρόμοια με την presenter, αλλά με `Template` στο τέλος: +Και πώς ορίζουμε μια τέτοια λίστα; Απλά με τη μορφή μιας κλάσης και των ιδιοτήτων της. Την ονομάζουμε παρόμοια με τον presenter, απλώς με το `Template` στο τέλος: ```php /** @@ -93,22 +141,22 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template } ``` -Το αντικείμενο `$this->template` στον παρουσιαστή θα είναι τώρα μια περίπτωση της κλάσης `ArticleTemplate`. Έτσι, η PHP θα ελέγχει τους δηλωμένους τύπους όταν γράφονται. Και ξεκινώντας από την PHP 8.2 θα προειδοποιεί επίσης για εγγραφή σε μη υπάρχουσα μεταβλητή, στις προηγούμενες εκδόσεις το ίδιο μπορεί να επιτευχθεί με τη χρήση του γνωρίσματος [Nette\SmartObject |utils:smartobject]. +Το αντικείμενο `$this->template` στον presenter θα είναι τώρα μια παρουσία της κλάσης `ArticleTemplate`. Έτσι, η PHP θα ελέγχει τους δηλωμένους τύπους κατά την εγγραφή. Και από την έκδοση PHP 8.2, θα προειδοποιεί και για εγγραφή σε ανύπαρκτη μεταβλητή, σε προηγούμενες εκδόσεις το ίδιο μπορεί να επιτευχθεί χρησιμοποιώντας το trait [Nette\SmartObject |utils:smartobject]. -Το σχόλιο `@property-read` είναι για το IDE και τη στατική ανάλυση, θα κάνει την αυτόματη συμπλήρωση να λειτουργεί, δείτε "PhpStorm και συμπλήρωση κώδικα για $this->template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. +Η σχολιαστική παρατήρηση `@property-read` προορίζεται για το IDE και τη στατική ανάλυση, χάρη σε αυτήν θα λειτουργεί η αυτόματη συμπλήρωση, βλ. "PhpStorm and code completion for $this⁠-⁠>⁠template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. [* phpstorm-completion.webp *] -Μπορείτε επίσης να αφεθείτε στην πολυτέλεια του ψιθυρίσματος στα πρότυπα, απλά εγκαταστήστε το πρόσθετο Latte στο PhpStorm και καθορίστε το όνομα της κλάσης στην αρχή του προτύπου, δείτε το άρθρο "Latte: πώς να πληκτρολογήσετε το σύστημα":https://blog.nette.org/el/latte-pos-na-chresimopoiesete-to-systema-typon: +Μπορείτε να απολαύσετε την πολυτέλεια της αυτόματης συμπλήρωσης και στα πρότυπα, αρκεί να εγκαταστήσετε το plugin για το Latte στο PhpStorm και να αναφέρετε στην αρχή του προτύπου το όνομα της κλάσης, περισσότερα στο άρθρο "Latte: πώς να χρησιμοποιήσετε το σύστημα τύπων":https://blog.nette.org/el/latte-how-to-use-type-system: ```latte -{templateType App\Presenters\ArticleTemplate} +{templateType App\Presentation\Article\ArticleTemplate} ... ``` -Έτσι λειτουργούν τα πρότυπα και στα συστατικά, απλά ακολουθήστε τη σύμβαση ονοματοδοσίας και δημιουργήστε μια κλάση προτύπου `FifteenTemplate` για το συστατικό π.χ. `FifteenControl`. +Έτσι λειτουργούν και τα πρότυπα στα components, αρκεί απλώς να τηρήσετε τη σύμβαση ονοματοδοσίας και για ένα component π.χ. `FifteenControl` να δημιουργήσετε μια κλάση προτύπου `FifteenTemplate`. -Εάν πρέπει να δημιουργήσετε ένα `$template` ως παράδειγμα μιας άλλης κλάσης, χρησιμοποιήστε τη μέθοδο `createTemplate()`: +Αν χρειαστεί να δημιουργήσετε το `$template` ως παρουσία μιας άλλης κλάσης, χρησιμοποιήστε τη μέθοδο `createTemplate()`: ```php public function renderDefault(): void @@ -121,43 +169,43 @@ public function renderDefault(): void ``` -Default Variables .[#toc-default-variables] -------------------------------------------- +Προεπιλεγμένες μεταβλητές +------------------------- -Οι παρουσιαστές και τα στοιχεία μεταβιβάζουν αυτόματα διάφορες χρήσιμες μεταβλητές στα πρότυπα: +Οι presenters και τα components μεταβιβάζουν αυτόματα αρκετές χρήσιμες μεταβλητές στα πρότυπα: -- `$basePath` είναι μια απόλυτη διαδρομή URL στο root dir (για παράδειγμα `/CD-collection`) -- `$baseUrl` είναι μια απόλυτη διεύθυνση URL στο root dir (για παράδειγμα `http://localhost/CD-collection`) -- `$user` είναι ένα αντικείμενο [που αντιπροσωπεύει τον χρήστη |security:authentication] -- `$presenter` είναι ο τρέχων παρουσιαστής -- `$control` είναι το τρέχον συστατικό ή ο παρουσιαστής -- `$flashes` κατάλογος [μηνυμάτων |presenters#flash-messages] που αποστέλλονται από τη μέθοδο `flashMessage()` +- `$basePath` είναι η απόλυτη διαδρομή URL προς τον ριζικό κατάλογο (π.χ. `/eshop`) +- `$baseUrl` είναι η απόλυτη URL προς τον ριζικό κατάλογο (π.χ. `http://localhost/eshop`) +- `$user` είναι το αντικείμενο [που αντιπροσωπεύει τον χρήστη |security:authentication] +- `$presenter` είναι ο τρέχων presenter +- `$control` είναι το τρέχον component ή presenter +- `$flashes` πίνακας [μηνυμάτων |presenters#Flash μηνύματα] που στάλθηκαν από τη συνάρτηση `flashMessage()` -Εάν χρησιμοποιείτε μια προσαρμοσμένη κλάση προτύπου, αυτές οι μεταβλητές μεταβιβάζονται εάν δημιουργήσετε μια ιδιότητα για αυτές. +Αν χρησιμοποιείτε τη δική σας κλάση προτύπου, αυτές οι μεταβλητές μεταβιβάζονται αν δημιουργήσετε μια ιδιότητα γι' αυτές. -Δημιουργία συνδέσμων .[#toc-creating-links] -------------------------------------------- +Δημιουργία συνδέσμων +-------------------- -Στο πρότυπο δημιουργούμε συνδέσμους προς άλλους παρουσιαστές & δράσεις ως εξής: +Στο πρότυπο, οι σύνδεσμοι προς άλλους presenters & actions δημιουργούνται με αυτόν τον τρόπο: ```latte -detail +λεπτομέρεια προϊόντος ``` -Το χαρακτηριστικό `n:href` είναι πολύ βολικό για τις ετικέτες HTML ``. Αν θέλουμε να εκτυπώσουμε τον σύνδεσμο αλλού, για παράδειγμα στο κείμενο, χρησιμοποιούμε το `{link}`: +Το attribute `n:href` είναι πολύ χρήσιμο για τις ετικέτες HTML ``. Αν θέλουμε να εμφανίσουμε έναν σύνδεσμο αλλού, για παράδειγμα σε κείμενο, χρησιμοποιούμε το `{link}`: ```latte -URL is: {link Home:default} +Η διεύθυνση είναι: {link Home:default} ``` -Για περισσότερες πληροφορίες, ανατρέξτε στην ενότητα [Δημιουργία συνδέσμων |Creating Links]. +Περισσότερες πληροφορίες θα βρείτε στο κεφάλαιο [Δημιουργία συνδέσμων URL|creating-links]. -Προσαρμοσμένα φίλτρα, ετικέτες κ.λπ. .[#toc-custom-filters-tags-etc] --------------------------------------------------------------------- +Προσαρμοσμένα φίλτρα, ετικέτες κ.λπ. +------------------------------------ -Το σύστημα δημιουργίας προτύπων Latte μπορεί να επεκταθεί με προσαρμοσμένα φίλτρα, συναρτήσεις, ετικέτες κ.λπ. Αυτό μπορεί να γίνει απευθείας στο `render` ή `beforeRender()` μέθοδο: +Το σύστημα προτύπων Latte μπορεί να επεκταθεί με προσαρμοσμένα φίλτρα, συναρτήσεις, ετικέτες κ.λπ. Αυτό μπορεί να γίνει απευθείας στη μέθοδο `render` ή `beforeRender()`: ```php public function beforeRender(): void @@ -165,16 +213,16 @@ public function beforeRender(): void // προσθήκη φίλτρου $this->template->addFilter('foo', /* ... */); - // ή να ρυθμίσετε απευθείας το αντικείμενο Latte\Engine + // ή διαμορφώνουμε απευθείας το αντικείμενο Latte\Engine $latte = $this->template->getLatte(); $latte->addFilterLoader(/* ... */); } ``` -Latte έκδοση 3 προσφέρει έναν πιο προηγμένο τρόπο δημιουργώντας μια [επέκταση |latte:creating-extension] για κάθε έργο ιστού. Εδώ είναι ένα πρόχειρο παράδειγμα μιας τέτοιας κλάσης: +Το Latte στην έκδοση 3 προσφέρει έναν πιο προηγμένο τρόπο, δημιουργώντας μια [extension |latte:extending-latte#Latte Extension] για κάθε διαδικτυακό έργο. Ένα αποσπασματικό παράδειγμα μιας τέτοιας κλάσης: ```php -namespace App\Templating; +namespace App\Presentation\Accessory; final class LatteExtension extends Latte\Extension { @@ -207,22 +255,21 @@ final class LatteExtension extends Latte\Extension } ``` -Την καταχωρούμε χρησιμοποιώντας την [configuration#Latte]: +Την καταχωρούμε χρησιμοποιώντας τη [διαμόρφωση |configuration#Templates Latte]: ```neon latte: extensions: - - App\Templating\LatteExtension + - App\Presentation\Accessory\LatteExtension ``` -Μετάφραση .[#toc-translating] ------------------------------ +Μετάφραση +--------- -Αν προγραμματίζετε μια πολύγλωσση εφαρμογή, πιθανόν να χρειαστεί να εκδώσετε κάποιο από το κείμενο του προτύπου σε διαφορετικές γλώσσες. Για να το κάνετε αυτό, το Nette Framework ορίζει μια διεπαφή μετάφρασης [api:Nette\Localization\Translator], η οποία διαθέτει μια μόνο μέθοδο `translate()`. Αυτή δέχεται το μήνυμα `$message`, το οποίο είναι συνήθως μια συμβολοσειρά, και οποιεσδήποτε άλλες παραμέτρους. Η αποστολή της είναι να επιστρέψει το μεταφρασμένο αλφαριθμητικό. -Δεν υπάρχει προεπιλεγμένη υλοποίηση στο Nette, μπορείτε να επιλέξετε ανάλογα με τις ανάγκες σας από διάφορες έτοιμες λύσεις που μπορείτε να βρείτε στο [Componette |https://componette.org/search/localization]. Η τεκμηρίωσή τους σας ενημερώνει για το πώς να ρυθμίσετε τον μεταφραστή. +Αν προγραμματίζετε μια πολύγλωσση εφαρμογή, πιθανότατα θα χρειαστεί να εμφανίσετε ορισμένα κείμενα στο πρότυπο σε διαφορετικές γλώσσες. Το Nette Framework ορίζει γι' αυτόν τον σκοπό ένα interface για τη μετάφραση [api:Nette\Localization\Translator], το οποίο έχει μία μόνο μέθοδο `translate()`. Αυτή δέχεται το μήνυμα `$message`, το οποίο συνήθως είναι μια συμβολοσειρά, και οποιεσδήποτε άλλες παραμέτρους. Ο στόχος είναι να επιστρέψει τη μεταφρασμένη συμβολοσειρά. Στο Nette δεν υπάρχει προεπιλεγμένη υλοποίηση, μπορείτε να επιλέξετε ανάλογα με τις ανάγκες σας από πολλές έτοιμες λύσεις που θα βρείτε στο [Componette |https://componette.org/search/localization]. Στην τεκμηρίωσή τους θα μάθετε πώς να διαμορφώσετε τον translator. -Τα πρότυπα μπορούν να ρυθμιστούν με έναν μεταφραστή, τον οποίο [θα μας έχει περάσει |dependency-injection:passing-dependencies], χρησιμοποιώντας τη μέθοδο `setTranslator()`: +Στα πρότυπα μπορεί να οριστεί ένας μεταφραστής, τον οποίο [ζητάμε να μας περάσει |dependency-injection:passing-dependencies], με τη μέθοδο `setTranslator()`: ```php protected function beforeRender(): void @@ -232,38 +279,38 @@ protected function beforeRender(): void } ``` -Εναλλακτικά, ο μεταφραστής μπορεί να οριστεί χρησιμοποιώντας τη [διαμόρφωση |configuration#Latte]: +Ο Translator μπορεί εναλλακτικά να οριστεί χρησιμοποιώντας τη [διαμόρφωση |configuration#Templates Latte]: ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -Ο μεταφραστής μπορεί στη συνέχεια να χρησιμοποιηθεί, για παράδειγμα, ως φίλτρο `|translate`, με πρόσθετες παραμέτρους που περνούν στη μέθοδο `translate()` (βλ. `foo, bar`): +Στη συνέχεια, ο μεταφραστής μπορεί να χρησιμοποιηθεί, για παράδειγμα, ως φίλτρο `|translate`, συμπεριλαμβανομένων των συμπληρωματικών παραμέτρων που μεταβιβάζονται στη μέθοδο `translate()` (βλ. `foo, bar`): ```latte -{='Basket'|translate} +{='Καλάθι'|translate} {$item|translate} {$item|translate, foo, bar} ``` -Ή ως ετικέτα υπογράμμισης: +Ή ως ετικέτα με κάτω παύλα: ```latte -{_'Basket'} +{_'Καλάθι'} {_$item} {_$item, foo, bar} ``` - `{translate}` (από το Latte 2.11, προηγουμένως χρησιμοποιούνταν η ετικέτα `{_}` ): +Για τη μετάφραση ενός τμήματος του προτύπου, υπάρχει η ζευγαρωτή ετικέτα `{translate}` (από το Latte 2.11, παλαιότερα χρησιμοποιούνταν η ετικέτα `{_}`): ```latte -{translate}Order{/translate} -{translate foo, bar}Order{/translate} +{translate}Παραγγελία{/translate} +{translate foo, bar}Παραγγελία{/translate} ``` -Ο μεταφραστής καλείται από προεπιλογή κατά την εκτέλεση κατά την απόδοση του προτύπου. Η έκδοση 3 του Latte, ωστόσο, μπορεί να μεταφράσει όλο το στατικό κείμενο κατά τη διάρκεια της σύνταξης του προτύπου. Αυτό εξοικονομεί επιδόσεις επειδή κάθε συμβολοσειρά μεταφράζεται μόνο μία φορά και η προκύπτουσα μετάφραση εγγράφεται στη μεταγλωττισμένη φόρμα. Αυτό δημιουργεί πολλαπλές μεταγλωττισμένες εκδόσεις του προτύπου στον κατάλογο cache, μία για κάθε γλώσσα. Για να το κάνετε αυτό, χρειάζεται να καθορίσετε μόνο τη γλώσσα ως δεύτερη παράμετρο: +Ο Translator καλείται κανονικά κατά το χρόνο εκτέλεσης κατά την απόδοση του προτύπου. Ωστόσο, το Latte έκδοση 3 μπορεί να μεταφράσει όλα τα στατικά κείμενα ήδη κατά τη μεταγλώττιση του προτύπου. Αυτό εξοικονομεί απόδοση, επειδή κάθε συμβολοσειρά μεταφράζεται μόνο μία φορά και η τελική μετάφραση γράφεται στη μεταγλωττισμένη μορφή. Έτσι, στον κατάλογο cache δημιουργούνται πολλαπλές μεταγλωττισμένες εκδόσεις του προτύπου, μία για κάθε γλώσσα. Γι' αυτό, αρκεί απλώς να αναφέρετε τη γλώσσα ως δεύτερη παράμετρο: ```php protected function beforeRender(): void @@ -273,4 +320,4 @@ protected function beforeRender(): void } ``` -Με τον όρο στατικό κείμενο εννοούμε, για παράδειγμα, το `{_'hello'}` ή το `{translate}hello{/translate}`. Μη στατικό κείμενο, όπως το `{_$foo}`, θα συνεχίσει να μεταγλωττίζεται εν κινήσει. +Στατικό κείμενο σημαίνει, για παράδειγμα, `{_'hello'}` ή `{translate}hello{/translate}`. Μη στατικά κείμενα, όπως για παράδειγμα `{_$foo}`, θα συνεχίσουν να μεταφράζονται κατά το χρόνο εκτέλεσης. diff --git a/application/en/@home.texy b/application/en/@home.texy index fac8c0d767..3999c48492 100644 --- a/application/en/@home.texy +++ b/application/en/@home.texy @@ -2,35 +2,84 @@ Nette Application ***************** .[perex] -The `nette/application` package is the basis for creating interactive web applications. - -- [How do applications work? |how-it-works] -- [Bootstrap] -- [Presenters] -- [Templates] -- [Modules] -- [Routing] -- [Creating URL Links |creating-links] -- [Interactive Components |components] -- [AJAX & Snippets |ajax] -- [Multiplier |multiplier] -- [Configuration] +Nette Application is the core of the Nette framework, providing powerful tools for creating modern web applications. It offers a range of exceptional features that significantly simplify development and improve code security and maintainability. Installation ------------ -Download and install the package using [Composer|best-practices:composer]: +Download and install the library using [Composer|best-practices:composer]: ```shell composer require nette/application ``` -| version | compatible with PHP -|-----------|------------------- -| Nette Application 4.0 | PHP 8.0 – 8.2 -| Nette Application 3.1 | PHP 7.2 – 8.2 + +Why choose Nette Application? +----------------------------- + +Nette has always been a pioneer in web technologies. + +**Bidirectional Router:** Nette features an advanced routing system unique in its bidirectionality - it not only translates URLs to application actions but can also generate URLs in reverse. This means: +- You can modify the URL structure of the entire application at any time without needing to modify templates +- URLs are automatically canonicalized, improving SEO +- Routing is defined in one place, not scattered in annotations + +**Components and Signals:** The built-in component system inspired by Delphi and React.js is unique among PHP frameworks: +- Enables creating reusable UI elements +- Supports hierarchical component composition +- Offers elegant AJAX request handling using signals +- Rich library of ready-made components on [Componette](https://componette.org) + +**AJAX and Snippets:** Nette introduced a revolutionary way of working with AJAX in 2009, long before similar solutions like Hotwire for Ruby on Rails or Symfony UX Turbo: +- Snippets allow updating only parts of the page without needing to write JavaScript +- Automatic integration with the component system +- Smart invalidation of page sections +- Minimal data transfer + +**Intuitive [Latte|latte:] Templates:** The most secure templating system for PHP with advanced features: +- Automatic XSS protection with context-sensitive escaping +- Extensible with custom filters, functions, and tags +- Template inheritance and snippets for AJAX +- Excellent PHP 8.x support with type system + +**Dependency Injection:** Nette fully utilizes Dependency Injection: +- Automatic dependency passing (autowiring) +- Configuration using clear NEON format +- Support for component factories + + +Main Benefits +------------- + +- **Security**: Automatic protection against [vulnerabilities|nette:vulnerability-protection] like XSS, CSRF, etc. +- **Productivity**: Less writing, more features thanks to smart design +- **Debugging**: [Tracy debugger|tracy:] with routing panel +- **Performance**: Smart cache, lazy loading of components +- **Flexibility**: Easy URL modification even after application completion +- **Components**: Unique system of reusable UI elements +- **Modern**: Full support for PHP 8.4+ and type system + + +Getting Started +--------------- + +1. [How Applications Work? |how-it-works] - Understanding the basic architecture +2. [Presenters |presenters] - Working with presenters and actions +3. [Templates |templates] - Creating templates in Latte +4. [Routing |routing] - Configuring URL addresses +5. [Interactive Components |components] - Using the component system + + +PHP Compatibility +----------------- + +| version | compatible with PHP +|-----------------------|------------------- +| Nette Application 4.0 | PHP 8.1 – 8.4 +| Nette Application 3.2 | PHP 8.1 – 8.4 +| Nette Application 3.1 | PHP 7.2 – 8.3 | Nette Application 3.0 | PHP 7.1 – 8.0 | Nette Application 2.4 | PHP 5.6 – 8.0 -Applies to the latest patch versions. +Valid for the latest patch versions. diff --git a/application/en/@left-menu.texy b/application/en/@left-menu.texy index 3506cff2c9..0b0defb537 100644 --- a/application/en/@left-menu.texy +++ b/application/en/@left-menu.texy @@ -1,10 +1,10 @@ Nette Application ***************** -- [How do applications work? |how-it-works] -- [Bootstrap] +- [How Do Applications Work? |how-it-works] +- [Bootstrapping] - [Presenters] - [Templates] -- [Modules] +- [Directory Structure |directory-structure] - [Routing] - [Creating URL Links |creating-links] - [Interactive Components |components] @@ -15,5 +15,8 @@ Nette Application Further Reading *************** +- [Why Use Nette?|www:10-reasons-why-nette] +- [Installation |nette:installation] +- [Create Your First Application! |quickstart:] - [Best practices |best-practices:] - [Troubleshooting |nette:troubleshooting] diff --git a/application/en/@meta.texy b/application/en/@meta.texy new file mode 100644 index 0000000000..42471908b0 --- /dev/null +++ b/application/en/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Documentation}} diff --git a/application/en/ajax.texy b/application/en/ajax.texy index b5d635a10c..b81b4d5952 100644 --- a/application/en/ajax.texy +++ b/application/en/ajax.texy @@ -3,36 +3,43 @@ AJAX & Snippets
    -Modern web applications nowadays run half on a server and half in a browser. AJAX is a vital uniting factor. What support does the Nette Framework offer? -- sending template fragments (so-called *snippets*) +In the era of modern web applications, where functionality is often distributed between the server and the browser, AJAX is an essential connecting element. What options does the Nette Framework offer in this area? +- sending parts of the template, known as snippets - passing variables between PHP and JavaScript -- AJAX applications debugging +- tools for debugging AJAX requests
    -An AJAX request can be detected using a method of a service [encapsulating a HTTP request |http:request] `$httpRequest->isAjax()` (detects based on the `X-Requested-With` HTTP header). There is also a shorthand method in presenter: `$this->isAjax()`. -An AJAX request is no different from a normal one – a presenter is called with a certain view and parameters. It is, too, up to the presenter how will it react: it can use its routines to either return a fragment of HTML code (a snippet), an XML document, a JSON object or a piece of Javascript code. +AJAX Request +============ -There is a pre-processed object called `payload` dedicated to sending data to the browser in JSON. +An AJAX request does not fundamentally differ from a classic HTTP request. A presenter is called with specific parameters. It is up to the presenter to decide how to respond to the request - it can return data in JSON format, send a part of HTML code, an XML document, etc. -```php -public function actionDelete(int $id): void -{ - if ($this->isAjax()) { - $this->payload->message = 'Success'; - } - // ... -} +On the browser side, we initiate an AJAX request using the `fetch()` function: + +```js +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) +.then(response => response.json()) +.then(payload => { + // process the response +}); ``` -For a full control over your JSON output use the `sendJson` method in your presenter. It terminates presenter immediately and you'll do without a template: +On the server side, an AJAX request is recognized by the `$httpRequest->isAjax()` method of the service [encapsulating the HTTP request |http:request]. It uses the `X-Requested-With` HTTP header for detection, so it is crucial to send it. Within the presenter, you can use the `$this->isAjax()` method. + +If you want to send data in JSON format, use the [`sendJson()` |presenters#Sending a Response] method. The method also terminates the presenter's activity. ```php -$this->sendJson(['key' => 'value', /* ... */]); +public function actionExport(): void +{ + $this->sendJson($this->model->getData); +} ``` -If we want to send HTML, we can either set a special template for AJAX requests: +If you plan to respond with a special template designed for AJAX, you can do it as follows: ```php public function handleClick($param): void @@ -45,10 +52,20 @@ public function handleClick($param): void ``` +Snippets +======== + +The most powerful tool offered by Nette for connecting the server with the client are snippets. With them, you can turn an ordinary application into an AJAX one with minimal effort and just a few lines of code. The Fifteen example demonstrates how it all works, and its code can be found on [GitHub |https://github.com/nette-examples/fifteen]. + +Snippets allow you to update only parts of the page, instead of reloading the entire page. This is not only faster and more efficient but also provides a more comfortable user experience. Snippets might remind you of Hotwire for Ruby on Rails or Symfony UX Turbo. Interestingly, Nette introduced snippets 14 years earlier. + +How do snippets work? When the page is first loaded (a non-AJAX request), the entire page, including all snippets, is loaded. When the user interacts with the page (e.g., clicks a button, submits a form, etc.), an AJAX request is initiated instead of reloading the entire page. The code in the presenter performs the action and decides which snippets need updating. Nette renders these snippets and sends them as a JSON payload containing an array with snippets. The handling code in the browser then inserts the received snippets back into the page. Thus, only the code of the changed snippets is transferred, saving bandwidth and speeding up loading compared to transferring the entire page content. + + Naja -==== +---- -The [Naja library|https://naja.js.org] is used to handle AJAX requests on the browser side. [Install |https://naja.js.org/#/guide/01-install-setup-naja] it as a node.js package (to use with Webpack, Rollup, Vite, Parcel and more): +To handle snippets on the browser side, the [Naja library |https://naja.js.org] is used. [Install it |https://naja.js.org/#/guide/01-install-setup-naja] as a Node.js package (for use with applications like Webpack, Rollup, Vite, Parcel, and others): ```shell npm install naja @@ -60,59 +77,55 @@ npm install naja ``` +First, you need to [initialize |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] the library: -Snippets -======== +```js +naja.initialize(); +``` -There is a far more powerful tool of built-in AJAX support – snippets. Using them makes it possible to turn a regular application into an AJAX one using only a few lines of code. How it all works is demonstrated in the Fifteen example whose code is also accessible in the build or on [GitHub |https://github.com/nette-examples/fifteen]. +To turn an ordinary link (signal) or form submission into an AJAX request, simply mark the relevant link, form, or button with the `ajax` class: -The way snippets work is that the whole page is transferred during the initial (i.e. non-AJAX) request and then with every AJAX [subrequest |components#signal] (request of the same view of the same presenter) only the code of the changed parts is transferred in the `payload` repository mentioned earlier. +```html +Go + +
    + +
    -Snippets may remind you of Hotwire for Ruby on Rails or Symfony UX Turbo, but Nette came up with them fourteen years earlier. +or +
    + +
    +``` -Invalidation of Snippets -======================== -Each descendant of class [Control |components] (which a Presenter is too) is able to remember whether there were any changes during a request that require it to re-render. There are a pair of methods to handle this: `redrawControl()` and `isControlInvalid()`. An example: +Redrawing Snippets +------------------ + +Every object of the [Control |components] class (including the Presenter itself) keeps track of whether changes have occurred that require it to be redrawn. The `redrawControl()` method is used for this purpose: ```php public function handleLogin(string $user): void { - // The object has to re-render after the user has logged in + // after login, it is necessary to redraw the relevant part $this->redrawControl(); // ... } ``` -Nette however offers an even finer resolution than whole components. The listed methods accept the name of a so-called "snippet" as an optional parameter. A "snippet" is basically an element in your template marked for that purpose by a Latte tag, more on that later. Thus it is possible to ask a component to redraw only *parts* of its template. If the entire component is invalidated then all of its snippets are re-rendered. A component is “invalid” also if any of its subcomponents is invalid. -```php -$this->isControlInvalid(); // -> false +Nette allows for even finer control over what needs to be redrawn. The method can accept the name of the snippet as an argument. Thus, it is possible to invalidate (meaning: force redrawing) at the level of template parts. If the entire component is invalidated, every snippet within it will also be redrawn: -$this->redrawControl('header'); // invalidates the snippet named 'header' -$this->isControlInvalid('header'); // -> true -$this->isControlInvalid('footer'); // -> false -$this->isControlInvalid(); // -> true, at least one snippet is invalid - -$this->redrawControl(); // invalidates the whole component, every snippet -$this->isControlInvalid('footer'); // -> true +```php +// invalidates the 'header' snippet +$this->redrawControl('header'); ``` -A component which receives a signal is automatically marked for redrawing. - -Thanks to snippet redrawing we know exactly which parts of which elements should be re-rendered. - - -Tag `{snippet} … {/snippet}` .{toc: Tag snippet} -================================================ -Rendering of the page proceeds very similarly to a regular request: the same templates are loaded, etc. The vital part is, however, to leave out the parts that are not supposed to reach the output; the other parts shall be associated with an identifier and sent to the user in a comprehensible format for a JavaScript handler. +Snippets in Latte +----------------- - -Syntax ------- - -If there is a control or a snippet in the template, we have to wrap it using the `{snippet} ... {/snippet}` pair tag - it will make sure that the rendered snippet will be "cut out" and sent to the browser. It will also enclose it in a helper `
    ` tag (it is possible to use a different one). In the following example a snippet named `header` is defined. It may as well represent the template of a component: +Using snippets in Latte is extremely easy. To define a part of the template as a snippet, simply wrap it with the `{snippet}` and `{/snippet}` tags: ```latte {snippet header} @@ -120,7 +133,9 @@ If there is a control or a snippet in the template, we have to wrap it using the {/snippet} ``` -A snippet of a type other than `
    ` or a snippet with additional HTML attributes is achieved by using the attribute variant: +The snippet creates a `
    ` element in the HTML page with a special generated `id`. When the snippet is redrawn, the content of this element is updated. Therefore, it is necessary that when the page is initially rendered, all snippets are also rendered, even if they might be empty at the beginning. + +You can also create a snippet using an element other than `
    ` with an n:attribute: ```latte
    @@ -129,138 +144,112 @@ A snippet of a type other than `
    ` or a snippet with additional HTML attribu ``` -Dynamic Snippets -================ +Snippet Areas +------------- -In Nette you can also define snippets with a dynamic name based on a runtime parameter. This is most suitable for various lists where we need to change just one row but we don't want transfer the whole list along with it. An example of this would be: +Snippet names can also be expressions: ```latte -
      - {foreach $list as $id => $item} -
    • {$item} update
    • - {/foreach} -
    +{foreach $items as $id => $item} +
  • {$item}
  • +{/foreach} ``` -There is one static snippet called `itemsContainer`, containing several dynamic snippets: `item-0`, `item-1` and so on. +This creates several snippets like `item-0`, `item-1`, etc. If we were to directly invalidate a dynamic snippet (e.g., `item-1`), nothing would be redrawn. The reason is that snippets truly function as excerpts, and only they themselves are rendered directly. However, in the template, there is technically no snippet named `item-1`. It only comes into existence when the code surrounding the snippet, i.e., the foreach loop, is executed. Therefore, we mark the part of the template that needs to be executed using the `{snippetArea}` tag: -You can't redraw a dynamic snippet directly (redrawing of `item-1` has no effect), you have to redraw its parent snippet (in this example `itemsContainer`). This causes the code of the parent snippet to be executed, but then just its sub-snippets are sent to the browser. If you want to send over just one of the sub-snippets, you have to modify input for the parent snippet to not generate the other sub-snippets. +```latte +
      + {foreach $items as $id => $item} +
    • {$item}
    • + {/foreach} +
    +``` -In the example above you have to make sure that for an AJAX request only one item will be added to the `$list` array, therefore the `foreach` loop will print just one dynamic snippet. +And we request the redrawing of both the individual snippet and the entire parent area: ```php -class HomePresenter extends Nette\Application\UI\Presenter -{ - /** - * This method returns data for the list. - * Usually this would just request the data from a model. - * For the purpose of this example, the data is hard-coded. - */ - private function getTheWholeList(): array - { - return [ - 'First', - 'Second', - 'Third', - ]; - } - - public function renderDefault(): void - { - if (!isset($this->template->list)) { - $this->template->list = $this->getTheWholeList(); - } - } - - public function handleUpdate(int $id): void - { - $this->template->list = $this->isAjax() - ? [] - : $this->getTheWholeList(); - $this->template->list[$id] = 'Updated item'; - $this->redrawControl('itemsContainer'); - } -} +$this->redrawControl('itemsContainer'); +$this->redrawControl('item-1'); ``` +At the same time, it is advisable to ensure that the `$items` array contains only the items that should be redrawn. -Snippets in an Included Template -================================ - -It can happen that the snippet is in a template which is being included from a different template. In that case we need to wrap the inclusion code in the second template with the `snippetArea` tag, then we redraw both the snippetArea and the actual snippet. - -Tag `snippetArea` ensures that the code inside is executed but only the actual snippet in the included template is sent to the browser. +If we include another template containing snippets into the main template using the `{include}` tag, it is necessary to wrap the template inclusion within a `snippetArea` again and invalidate it along with the snippet: ```latte -{* parent.latte *} -{snippetArea wrapper} - {include 'child.latte'} +{snippetArea include} + {include 'included.latte'} {/snippetArea} ``` + ```latte -{* child.latte *} +{* included.latte *} {snippet item} -... + ... {/snippet} ``` + ```php -$this->redrawControl('wrapper'); +$this->redrawControl('include'); $this->redrawControl('item'); ``` -You can also combine it with dynamic snippets. - -Adding and Deleting -=================== +Snippets in Components +---------------------- -If you add a new item into the list and invalidate `itemsContainer`, the AJAX request returns snippets including the new one, but the javascript handler won’t be able to render it. This is because there is no HTML element with the newly created ID. - -In this case, the simplest way is to wrap the whole list in one more snippet and invalidate it all: +You can create snippets within [components|components], and Nette will automatically redraw them. However, there is a limitation: to redraw snippets, Nette calls the `render()` method without any parameters. Therefore, passing parameters in the template will not work: ```latte -{snippet wholeList} -
      - {foreach $list as $id => $item} -
    • {$item} update
    • - {/foreach} -
    -{/snippet} -Add +OK +{control productGrid} + +will not work: +{control productGrid $arg, $arg} +{control productGrid:paginator} ``` + +Sending Custom Data +------------------- + +Along with snippets, you can send any additional data to the client. Simply write them into the `payload` object: + ```php -public function handleAdd(): void +public function actionDelete(int $id): void { - $this->template->list = $this->getTheWholeList(); - $this->template->list[] = 'New one'; - $this->redrawControl('wholeList'); + // ... + if ($this->isAjax()) { + $this->payload->message = 'Success'; + } } ``` -The same goes for deleting an item. It would be possible to send empty snippet, but usually lists can be paginated and it would be complicated to implement deleting one item and loading another (which used to be on a different page of the paginated list). - -Sending Parameters to Component -=============================== +Passing Parameters +================== -When we send parameters to the component via AJAX request, whether signal parameters or persistent parameters, we must provide their global name, which also contains the name of the component. The full name of parameter returns the `getParameterId()` method. +When sending parameters to a component via an AJAX request, whether they are signal parameters or persistent parameters, we must specify their global name in the request, which includes the component's name. The `getParameterId()` method returns the full parameter name. ```js -$.getJSON( - {link changeCountBasket!}, - { - {$control->getParameterId('id')}: id, - {$control->getParameterId('count')}: count - } -}); +let url = new URL({link //foo!}); +url.searchParams.set({$control->getParameterId('bar')}, bar); + +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) ``` -And handle method with s corresponding parameters in component. +And the handle method with corresponding parameters in the component: ```php -public function handleChangeCountBasket(int $id, int $count): void +public function handleFoo(int $bar): void { - } ``` + + +Further Reading +=============== + +- [Dynamic Snippets |best-practices:dynamic-snippets] diff --git a/application/en/bootstrap.texy b/application/en/bootstrap.texy deleted file mode 100644 index c7927f9733..0000000000 --- a/application/en/bootstrap.texy +++ /dev/null @@ -1,233 +0,0 @@ -Bootstrap -********* - -
    - -Bootstrap is boot code that initializes the environment, creates a dependency injection (DI) container, and starts the application. We will discuss: - -- how to configure your application using NEON files -- how to handle production and development modes -- how to create the DI container - -
    - - -Applications, whether web-based or command-line scripts, begin by some form of environment initialization. In ancient times, it could be a file named eg `include.inc.php` that was in charge of this, and was included in the initial file. -In modern Nette applications, it has been replaced by the `Bootstrap` class, which as part of the application can be found in the `app/Bootstrap.php`. It might look for example like this: - -```php -use Nette\Bootstrap\Configurator; - -class Bootstrap -{ - public static function boot(): Configurator - { - $appDir = dirname(__DIR__); - $configurator = new Configurator; - //$configurator->setDebugMode('secret@23.75.345.200'); - $configurator->enableTracy($appDir . '/log'); - $configurator->setTempDirectory($appDir . '/temp'); - $configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); - $configurator->addConfig($appDir . '/config/common.neon'); - return $configurator; - } -} -``` - - -index.php -========= - -In the case of web applications, the initial file is `index.php`, which is located in the public directory `www/`. It lets the `Bootstrap` class to initialize the environment and return the `$configurator` which creates DI container. Then it obtains the `Application` service, that runs the web application: - -```php -// initialize the environment + get Configurator object -$configurator = App\Bootstrap::boot(); -// create a DI container -$container = $configurator->createContainer(); -// DI container creates a Nette\Application\Application object -$application = $container->getByType(Nette\Application\Application::class); -// start Nette application -$application->run(); -``` - -As you can see, the [api:Nette\Bootstrap\Configurator] class, which we will now introduce in more detail, helps with setting up the environment and creating a dependency injection (DI) container. - - -Development vs Production Mode -============================== - -Nette distinguishes between two basic modes in which a request is executed: development and production. The development mode is focused on the maximum comfort of the programmer, Tracy is displayed, the cache is automatically updated when changing templates or DI container configuration, etc. Production mode is focused on performance, Tracy only logs errors and changes of templates and other files are not checked. - -Mode selection is done by autodetection, so there is usually no need to configure or switch anything manually. The mode is development if the application is running on localhost (ie IP address `127.0.0.1` or `::1`) and no proxy is present (ie its HTTP header). Otherwise, it runs in production mode. - -If you want to enable development mode in other cases, for example, for programmers accessing from a specific IP address, you can use `setDebugMode()`: - -```php -$configurator->setDebugMode('23.75.345.200'); // one or more IP addresses -``` - -We definitely recommend combining an IP address with a cookie. We will store a secret token into the `nette-debug` cookie, e.g. `secret1234`, and the development mode will be activated for programmers with this combination of IP and cookie. - -```php -$configurator->setDebugMode('secret1234@23.75.345.200'); -``` - -We can also turn off developer mode completely, even for localhost: - -```php -$configurator->setDebugMode(false); -``` - -Note that the value `true` turns on developer mode by hard, which should never happen on a production server. - - -Debugging Tool Tracy -==================== - -For easy debugging, we will turn on the great tool [Tracy |tracy:]. In developer mode it visualizes errors and in production mode it logs errors to the specified directory: - -```php -$configurator->enableTracy($appDir . '/log'); -``` - - -Temporary Files -=============== - -Nette uses the cache for DI container, RobotLoader, templates, etc. Therefore it is necessary to set the path to the directory where the cache will be stored: - -```php -$configurator->setTempDirectory($appDir . '/temp'); -``` - -On Linux or macOS, set the [write permissions |nette:troubleshooting#Setting directory permissions] for directories `log/` and `temp/`. - - -RobotLoader -=========== - -Usually, we will want to automatically load the classes using [RobotLoader |robot-loader:], so we have to start it up and let it load classes from the directory where `Bootstrap.php` is located (i.e. `__DIR__`) and all its subdirectories: - -```php -$configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); -``` - -An alternative way is to only use [Composer |best-practices:composer] PSR-4 autoloading. - - -Timezone -======== - -Configurator allows you to specify a timezone for your application. - -```php -$configurator->setTimeZone('Europe/Prague'); -``` - - -DI Container Configuration -========================== - -Part of the boot process is the creation of a DI container, ie a factory for objects, which is the heart of the whole application. It is actually a PHP class generated by Nette and stored in a cache directory. The factory produces key application objects and configuration files instruct it how to create and configure them, and thus we influence the behavior of the whole application. - -Configuration files are usually written in the [NEON format|neon:format]. You can read [what can be configured here|nette:configuring]. - -.[tip] -In the development mode, the container is automatically updated each time you change the code or configuration files. In production mode, it is generated only once and file changes are not checked to maximize performance. - -Configuration files are loaded using `addConfig()`: - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -``` - -The method `addConfig()` can be called multiple times to add multiple files. - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -$configurator->addConfig($appDir . '/config/local.neon'); -if (PHP_SAPI === 'cli') { - $configurator->addConfig($appDir . '/config/cli.php'); -} -``` - -The name `cli.php` is not a typo, the configuration can also be written in a PHP file, which returns it as an array. - -Alternatively, we can use the [`includes` section|dependency-injection:configuration#including files] to load more configuration files. - -If items with the same keys appear within configuration files, they will be [overwritten or merged |dependency-injection:configuration#Merging] in the case of arrays. Later included file has a higher priority than the previous one. The file in which the `includes` section is listed has a higher priority than the files included in it. - - -Static Parameters ------------------ - -Parameters used in configuration files can be defined [in the section `parameters`|dependency-injection:configuration#parameters] and also passed (or overwritten) by the `addStaticParameters()` method (it has alias `addParameters()`). It is important that different parameter values cause the generation of additional DI containers, i.e. additional classes. - -```php -$configurator->addStaticParameters([ - 'projectId' => 23, -]); -``` - -In configuration files, we can write usual notation `%projectId%` to access the parameter named `projectId`. By default, the Configurator populates the following parameters: `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` and `consoleMode`. - - -Dynamic Parameters ------------------- - -We can also add dynamic parameters to the container, their different values, unlike static parameters, will not cause the generation of new DI containers. - -```php -$configurator->addDynamicParameters([ - 'remoteIp' => $_SERVER['REMOTE_ADDR'], -]); -``` - -Environment variables could be easily made available using dynamic parameters. We can access them via `%env.variable%` in configuration files. - -```php -$configurator->addDynamicParameters([ - 'env' => getenv(), -]); -``` - - -Imported Services ------------------ - -We're going deeper now. Although the purpose of a DI container is to create objects, exceptionally there may be a need to insert an existing object into the container. We do this by defining the service with the `imported: true` attribute. - -```neon -services: - myservice: - type: App\Model\MyCustomService - imported: true -``` - -Create a new instance and insert it in bootstrap: - -```php -$configurator->addServices([ - 'myservice' => new App\Model\MyCustomService('foobar'), -]); -``` - - -Different Environments -====================== - -Feel free to customize the `Bootstrap` class to suit your needs. You can add parameters to the `boot()` method to differentiate web projects, or add other methods, such as `bootForTests()`, which initializes the environment for unit tests, `bootForCli()` for scripts called from the command line, and so on. - -```php -public static function bootForTests(): Configurator -{ - $configurator = self::boot(); - Tester\Environment::setup(); // Nette Tester initialization - return $configurator; -} -``` diff --git a/application/en/bootstrapping.texy b/application/en/bootstrapping.texy new file mode 100644 index 0000000000..8ee9c78236 --- /dev/null +++ b/application/en/bootstrapping.texy @@ -0,0 +1,298 @@ +Bootstrapping +************* + +
    + +Bootstrapping is the process of initializing the application environment, creating a dependency injection (DI) container, and starting the application. We will discuss: + +- how the Bootstrap class initializes the environment +- how applications are configured using NEON files +- how to distinguish between production and development mode +- how to create and configure the DI container + +
    + + +Applications, whether web-based or scripts run from the command line, begin their execution with some form of environment initialization. In the old days, a file named perhaps `include.inc.php` was responsible for this, included by the initial file. In modern Nette applications, it has been replaced by the `Bootstrap` class, which, as part of the application, can be found in the `app/Bootstrap.php` file. It might look like this, for example: + +```php +use Nette\Bootstrap\Configurator; + +class Bootstrap +{ + private Configurator $configurator; + private string $rootDir; + + public function __construct() + { + $this->rootDir = dirname(__DIR__); + // The configurator is responsible for setting up the application environment and services. + $this->configurator = new Configurator; + // Set the directory for temporary files generated by Nette (e.g., compiled templates) + $this->configurator->setTempDirectory($this->rootDir . '/temp'); + } + + public function bootWebApplication(): Nette\DI\Container + { + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); + } + + private function initializeEnvironment(): void + { + // Nette is smart, and the development mode turns on automatically, + // or you can enable it for a specific IP address by uncommenting the following line: + // $this->configurator->setDebugMode('secret@23.75.345.200'); + + // Enables Tracy: the ultimate "swiss army knife" debugging tool. + $this->configurator->enableTracy($this->rootDir . '/log'); + + // RobotLoader: automatically loads all classes in the chosen directory + $this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); + } + + private function setupContainer(): void + { + // Load configuration files + $this->configurator->addConfig($this->rootDir . '/config/common.neon'); + } +} +``` + + +index.php +========= + +In the case of web applications, the initial file is `index.php`, located in the [public directory |directory-structure#Public Directory www] `www/`. It instructs the Bootstrap class to initialize the environment and create the DI container. Then, it retrieves the `Application` service from the container, which runs the web application: + +```php +$bootstrap = new App\Bootstrap; +// Initialize the environment + create a DI container +$container = $bootstrap->bootWebApplication(); +// DI container creates a Nette\Application\Application object +$application = $container->getByType(Nette\Application\Application::class); +// Start the Nette application and process the incoming request +$application->run(); +``` + +As you can see, the [api:Nette\Bootstrap\Configurator] class helps with setting up the environment and creating the dependency injection (DI) container. We will now introduce it in more detail. + + +Development vs Production Mode +============================== + +Nette behaves differently depending on whether it is running on a development or production server: + +🛠️ Development Mode: + - Displays the Tracy debug bar with useful information (SQL queries, execution time, memory used) + - In case of an error, displays a detailed error page with function calls and variable contents + - Automatically refreshes the cache when Latte templates, configuration files, etc., are changed + + +🚀 Production Mode: + - Does not display any debugging information; all errors are written to the log + - In case of an error, displays an ErrorPresenter or a generic "Server Error" page + - Cache is never automatically refreshed! + - Optimized for speed and security + + +Mode selection is done by autodetection, so usually, there is no need to configure anything or manually switch modes: + +- development mode: on localhost (IP address `127.0.0.1` or `::1`) if no proxy is present (i.e., its HTTP header is not detected) +- production mode: everywhere else + +If we want to enable development mode in other cases, for example, for programmers accessing from a specific IP address, we use `setDebugMode()`: + +```php +$this->configurator->setDebugMode('23.75.345.200'); // an array of IP addresses can also be provided +``` + +We strongly recommend combining an IP address with a cookie. Store a secret token, e.g., `secret1234`, in the `nette-debug` cookie, and this way, activate development mode for programmers accessing from a specific IP address who also have the mentioned token in their cookie: + +```php +$this->configurator->setDebugMode('secret1234@23.75.345.200'); +``` + +We can also disable development mode completely, even for localhost: + +```php +$this->configurator->setDebugMode(false); +``` + +Note that the value `true` forces development mode on, which should **never** happen on a production server. + + +Debugging Tool Tracy +==================== + +For easy debugging, we will enable the excellent tool [Tracy |tracy:]. In development mode, it visualizes errors, and in production mode, it logs errors to the specified directory: + +```php +$this->configurator->enableTracy($this->rootDir . '/log'); +``` + + +Temporary Files +=============== + +Nette uses cache for the DI container, RobotLoader, templates, etc. Therefore, it is necessary to set the path to the directory where the cache will be stored: + +```php +$this->configurator->setTempDirectory($this->rootDir . '/temp'); +``` + +On Linux or macOS, set [write permissions |nette:troubleshooting#Setting Directory Permissions] for the `log/` and `temp/` directories. + + +RobotLoader +=========== + +Usually, we will want to automatically load classes using [RobotLoader |robot-loader:], so we need to start it and let it load classes from the directory where `Bootstrap.php` is located (i.e., `__DIR__`), and all its subdirectories: + +```php +$this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); +``` + +An alternative approach is to load classes solely through [Composer |best-practices:composer] following PSR-4. + + +Timezone +======== + +You can set the default timezone via the configurator. + +```php +$this->configurator->setTimeZone('Europe/Prague'); +``` + + +DI Container Configuration +========================== + +Part of the booting process is the creation of the DI container, or object factory, which is the heart of the entire application. It is actually a PHP class generated by Nette and stored in the cache directory. The factory produces key application objects, and using configuration files, we instruct it how to create and set them up, thereby influencing the behavior of the entire application. + +Configuration files are usually written in the [NEON format |neon:format]. In a separate chapter, you can read about [what can be configured |nette:configuring]. + +.[tip] +In development mode, the container is automatically updated whenever the code or configuration files change. In production mode, it is generated only once, and changes are not checked to maximize performance. + +Configuration files are loaded using `addConfig()`: + +```php +$this->configurator->addConfig($this->rootDir . '/config/common.neon'); +``` + +If we want to add more configuration files, we can call the `addConfig()` function multiple times. + +```php +$configDir = $this->rootDir . '/config'; +$this->configurator->addConfig($configDir . '/common.neon'); +$this->configurator->addConfig($configDir . '/services.neon'); +if (PHP_SAPI === 'cli') { + $this->configurator->addConfig($configDir . '/cli.php'); +} +``` + +The name `cli.php` is not a typo; configuration can also be written in a PHP file that returns it as an array. + +We can also add other configuration files in [the `includes` section |dependency-injection:configuration#Including Files]. + +If items with the same keys appear in configuration files, they will be overwritten, or in the case of [arrays, merged |dependency-injection:configuration#Merging]. A file included later has higher priority than the previous one. The file in which the `includes` section is listed has higher priority than the files included within it. + + +Static Parameters +----------------- + +Parameters used in configuration files can be defined [in the `parameters` section |dependency-injection:configuration#Parameters] and also passed (or overridden) using the `addStaticParameters()` method (it has an alias `addParameters()`). It is important that different parameter values will cause the generation of additional DI containers, i.e., additional classes. + +```php +$this->configurator->addStaticParameters([ + 'projectId' => 23, +]); +``` + +The `projectId` parameter can be referenced in the configuration using the standard notation `%projectId%`. + + +Dynamic Parameters +------------------ + +We can also add dynamic parameters to the container, whose different values, unlike static parameters, will not cause the generation of new DI containers. + +```php +$this->configurator->addDynamicParameters([ + 'remoteIp' => $_SERVER['REMOTE_ADDR'], +]); +``` + +This way, we can easily add, for example, environment variables, which can then be referenced in the configuration using the notation `%env.variable%`. + +```php +$this->configurator->addDynamicParameters([ + 'env' => getenv(), +]); +``` + + +Default Parameters +------------------ + +You can use these static parameters in the configuration files: + +- `%appDir%` is the absolute path to the directory containing the `Bootstrap.php` file +- `%wwwDir%` is the absolute path to the directory containing the entry file `index.php` +- `%tempDir%` is the absolute path to the directory for temporary files +- `%vendorDir%` is the absolute path to the directory where Composer installs libraries +- `%rootDir%` is the absolute path to the root directory of the project +- `%baseUrl%` is the absolute URL to the root directory +- `%debugMode%` indicates whether the application is in debug mode +- `%consoleMode%` indicates whether the request came through the command line + + +Imported Services +----------------- + +Now we go deeper. Although the purpose of the DI container is to create objects, occasionally there might be a need to insert an existing object into the container. We do this by defining the service with the `imported: true` flag. + +```neon +services: + myservice: + type: App\Model\MyCustomService + imported: true +``` + +And in the bootstrap, we insert the object into the container: + +```php +$this->configurator->addServices([ + 'myservice' => new App\Model\MyCustomService('foobar'), +]); +``` + + +Different Environments +====================== + +Feel free to modify the `Bootstrap` class according to your needs. You can add parameters to the `bootWebApplication()` method to distinguish between web projects. Or we can add other methods, such as `bootTestEnvironment()` which initializes the environment for unit tests, `bootConsoleApplication()` for scripts called from the command line, etc. + +```php +public function bootTestEnvironment(): Nette\DI\Container +{ + Tester\Environment::setup(); // Nette Tester initialization + $this->setupContainer(); + return $this->configurator->createContainer(); +} + +public function bootConsoleApplication(): Nette\DI\Container +{ + $this->configurator->setDebugMode(false); + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); +} +``` diff --git a/application/en/components.texy b/application/en/components.texy index bdf8052c3a..615b03033b 100644 --- a/application/en/components.texy +++ b/application/en/components.texy @@ -3,7 +3,7 @@ Interactive Components
    -Components are separate reusable objects that we place into pages. They can be forms, datagrids, polls, in fact anything that makes sense to use repeatedly. We will show: +Components are separate reusable objects that we embed into pages. They can be forms, datagrids, polls, essentially anything that makes sense to use repeatedly. We will show: - how to use components? - how to write them? @@ -11,19 +11,19 @@ Components are separate reusable objects that we place into pages. They can be f
    -Nette has a built-in component system. Older of you may remember something similar from Delphi or ASP.NET Web Forms. React or Vue.js is built on something remotely similar. However, in the world of PHP frameworks, this is a completely unique feature. +Nette has a built-in component system. Something similar might be familiar to veterans from Delphi or ASP.NET Web Forms; React or Vue.js is built on something remotely similar. However, in the world of PHP frameworks, this is a unique feature. -At the same time, components fundamentally change the approach to application development. You can compose pages from pre-prepared units. Do you need datagrid in administration? You can find it at [Componette |https://componette.org/search/component], a repository of open-source add-ons (not just components) for Nette, and simply paste it into the presenter. +At the same time, components fundamentally influence the approach to application development. You can compose pages from pre-prepared units. Need a datagrid in your administration? Find it on [Componette |https://componette.org/search/component], a repository of open-source add-ons (not just components) for Nette, and simply insert it into the presenter. -You can incorporate any number of components into the presenter. And you can insert other components into some components. This creates a component tree with a presenter as a root. +You can incorporate any number of components into the presenter. And you can embed other components within some components. This creates a component tree, with the presenter as its root. Factory Methods =============== -How are components placed and subsequently used in the presenter? Usually using factory methods. +How are components inserted into the presenter and subsequently used? Usually via factory methods. -The component factory is an elegant way to create components only when they are really needed (lazy / on-demand). The whole magic is in implementation of a method called `createComponent()`, where `` is the name of the component, that will create and return. +A component factory is an elegant way to create components only when they are actually needed (lazy / on demand). The whole magic lies in implementing a method named `createComponent()`, where `` is the name of the component being created, which creates and returns the component. ```php .{file:DefaultPresenter.php} class DefaultPresenter extends Nette\Application\UI\Presenter @@ -37,21 +37,21 @@ class DefaultPresenter extends Nette\Application\UI\Presenter } ``` -Because all components are created in separate methods, the code is cleaner and easier to read. +Because all components are created in separate methods, the code becomes clearer. .[note] -Component names always start with a lowercase letter, although they are capitalized in the method name. +Component names always start with a lowercase letter, even though they are capitalized in the method name. -We never call factories directly, they get called automatically, when we use components for the first time. Thanks to that, a component is created at the right moment, and only if it's really needed. If we wouldn't use the component (for example on some AJAX request, where we return only part of the page, or when parts are cached), it wouldn't even be created and we save performance of the server. +We never call factories directly; they are called automatically the first time we use the component. Thanks to this, the component is created at the right moment and only if it is actually needed. If we don't use the component (e.g., during an AJAX request where only part of the page is transferred, or when caching the template), it won't be created at all, saving server performance. ```php .{file:DefaultPresenter.php} // we access the component and if it was the first time, -// it calls createComponentPoll() to create it +// createComponentPoll() is called which creates it $poll = $this->getComponent('poll'); // alternative syntax: $poll = $this['poll']; ``` -In the template, you can render a component using tag [{control} |#Rendering]. So there is no need of manually passing the components to template. +In the template, it is possible to render a component using the [{control} |#Rendering] tag. Therefore, there is no need to manually pass components to the template. ```latte

    Please Vote

    @@ -63,17 +63,17 @@ In the template, you can render a component using tag [{control} |#Rendering]. S Hollywood Style =============== -Components commonly use one cool technique, which we like to call Hollywood style. Surely you know the cliché that actors hear often at the casting calls: "Don't call us, we'll call you." And that's what this is about. +Components commonly use a fresh technique we like to call the Hollywood style. You surely know the cliché often heard by participants in film auditions: "Don't call us, we'll call you." And that's precisely what it's about. -In Nette, instead of having to constantly ask questions ("was the form submitted?", "was it valid?" or "did anyone press this button?"), you tell the framework "when this happens, call this method" and leave further work on it. If you program in JavaScript, you are familiar with this style of programming. You write functions that are called when a certain event occurs. And the engine passes the appropriate parameters to them. +In Nette, instead of constantly having to ask questions ("was the form submitted?", "was it valid?", or "did the user press this button?"), you tell the framework "when this happens, call this method" and leave further work to it. If you program in JavaScript, you are intimately familiar with this style of programming. You write functions that are called when a certain event occurs. And the language passes the appropriate parameters to them. -This completely changes the way you write applications. The more tasks you can delegate to the framework, the less work you have. And the less you can forget. +This completely changes the perspective on writing applications. The more tasks you can leave to the framework, the less work you have. And the less you might overlook. -How to Write a Component -======================== +Writing a Component +=================== -By component we usually mean descendants of the class [api:Nette\Application\UI\Control]. The presenter [api:Nette\Application\UI\Presenter] itself is also a descendant of the `Control` class. +By the term component, we usually mean a descendant of the [api:Nette\Application\UI\Control] class. (It would be more accurate to use the term "controls", but that has a different meaning in some languages, and "components" has become more established.) The presenter [api:Nette\Application\UI\Presenter] itself is also a descendant of the `Control` class. ```php .{file:PollControl.php} use Nette\Application\UI\Control; @@ -87,19 +87,19 @@ class PollControl extends Control Rendering ========= -We already know that the `{control componentName}` tag is used to draw a component. It actually calls the method `render()` of the component, in which we take care of the rendering. We have, just like in the presenter, a [Latte |latte:] template in the variable `$this->template`, to which we pass the parameters. Unlike use in a presenter, we must specify a template file and let it render: +We already know that the `{control componentName}` tag is used to render a component. It actually calls the `render()` method of the component, in which we take care of the rendering. We have available, just like in the presenter, a [Latte template|templates] in the `$this->template` variable, to which we pass parameters. Unlike in the presenter, we must specify the template file and have it rendered: ```php .{file:PollControl.php} public function render(): void { - // we will put some parameters into the template + // insert some parameters into the template $this->template->param = $value; - // and draw it + // and render it $this->template->render(__DIR__ . '/poll.latte'); } ``` -The tag `{control}` allows to pass parameters to the method `render()`: +The `{control}` tag allows passing parameters to the `render()` method: ```latte {control poll $id, $message} @@ -112,7 +112,7 @@ public function render(int $id, string $message): void } ``` -Sometimes a component can consist of several parts that we want to render separately. For each of them we will create own rendering method, here is for example `renderPaginator()`: +Sometimes a component may consist of several parts that we want to render separately. For each of them, we create our own rendering method, here in the example, `renderPaginator()`: ```php .{file:PollControl.php} public function renderPaginator(): void @@ -121,69 +121,69 @@ public function renderPaginator(): void } ``` -And in the template we then call it using: +And in the template, we then invoke it using: ```latte {control poll:paginator} ``` -For better understanding it's good to know how the tag is compiled to PHP code. +For a better understanding, it's good to know how this tag translates into PHP code. ```latte {control poll} {control poll:paginator 123, 'hello'} ``` -This compiles to: +translates to: ```php $control->getComponent('poll')->render(); $control->getComponent('poll')->renderPaginator(123, 'hello'); ``` -`getComponent()` method returns the `poll` component and then the `render()` or `renderPaginator()` method, respectively, is called on it. +The `getComponent()` method returns the `poll` component, and the `render()` method, or `renderPaginator()` if a different rendering method is specified in the tag after the colon, is called on this component. .[caution] -If anywhere in the parameter part **`=>`** is used, all parameters will be wrapped with an array and passed as the first argument: +Beware, if **`=>`** appears anywhere in the parameters, all parameters will be wrapped in an array and passed as the first argument: ```latte {control poll, id: 123, message: 'hello'} ``` -compiles to: +translates to: ```php $control->getComponent('poll')->render(['id' => 123, 'message' => 'hello']); ``` -Rendering of sub-component: +Rendering a sub-component: ```latte {control cartControl-someForm} ``` -compiles to: +translates to: ```php $control->getComponent("cartControl-someForm")->render(); ``` -Components, like presenters, pass several useful variables to templates automatically: +Components, like presenters, automatically pass several useful variables to templates: -- `$basePath` is an absolute URL path to root dir (for example `/CD-collection`) -- `$baseUrl` is an absolute URL to root dir (for example `http://localhost/CD-collection`) +- `$basePath` is the absolute URL path to the root directory (e.g., `/eshop`) +- `$baseUrl` is the absolute URL to the root directory (e.g., `http://localhost/eshop`) - `$user` is an object [representing the user |security:authentication] - `$presenter` is the current presenter - `$control` is the current component -- `$flashes` list of [messages |#flash-messages] sent by method `flashMessage()` +- `$flashes` is an array of [messages |#Flash Messages] sent by the `flashMessage()` function Signal ====== -We already know that navigation in the Nette application consists of linking or redirecting to pairs `Presenter:action`. But what if we just want to perform an action on the **current page**? For example, change the sorting order of the column in the table; delete item; switch light/dark mode; submit the form; vote in the poll; etc. +We already know that navigation in a Nette application consists of linking or redirecting to `Presenter:action` pairs. But what if we just want to perform an action on the **current page**? For example, change the sorting of columns in a table; delete an item; switch light/dark mode; submit a form; vote in a poll; etc. -This type of request is called a signal. And like actions invoke methods `action()` or `render()`, signals call methods `handle()`. While the concept of action (or view) relates only to presenters, signals apply to all components. And therefore also to presenters, because `UI\Presenter` is a descendant of `UI\Control`. +This type of request is called a signal. And just as actions invoke `action()` or `render()` methods, signals call `handle()` methods. While the concept of action (or view) relates purely to presenters, signals concern all components. And thus also presenters, because `UI\Presenter` is a descendant of `UI\Control`. ```php public function handleClick(int $x, int $y): void @@ -192,36 +192,36 @@ public function handleClick(int $x, int $y): void } ``` -The link that calls the signal is created in the usual way, i.e. in the template by the attribute `n:href` or the tag `{link}`, in the code by the method `link()`. More in the chapter [Creating URL links |creating-links#Links to Signal]. +A link that calls a signal is created in the usual way, i.e., in the template with the `n:href` attribute or the `{link}` tag, in the code with the `link()` method. More in the chapter [Creating URL Links |creating-links#Links to Signal]. ```latte click here ``` -The signal is always called on the current presenter and view, so it is not possible to link to signal in different presenter / action. +A signal is always called on the current presenter and action; it is not possible to invoke it on another presenter or action. -Thus, the signal causes the page to be reloaded in exactly the same way as in the original request, only in addition it calls the signal handling method with the appropriate parameters. If the method does not exist, exception [api:Nette\Application\UI\BadSignalException] is thrown, which is displayed to the user as error page 403 Forbidden. +Thus, a signal causes the page to reload just like the original request, but additionally calls the signal handling method with the appropriate parameters. If the method does not exist, an [api:Nette\Application\UI\BadSignalException] exception is thrown, which is displayed to the user as a 403 Forbidden error page. Snippets and AJAX ================= -The signals may remind you a little bit AJAX: handlers that are called on the current page. And you're right, signals are really often called using AJAX, and then we only transmit changed parts of the page to the browser. They are called snippets. More information can be found on [the page about AJAX |ajax]. +Signals might remind you a bit of AJAX: handlers that are invoked on the current page. And you are right, signals are indeed often called using AJAX, and subsequently, only the changed parts of the page are transferred to the browser. These are called snippets. More information can be found on the [page dedicated to AJAX |ajax]. Flash Messages ============== -A component has its own storage of flash messages independent of the presenter. These are messages that, for example, inform about the result of the operation. An important feature of flash messages is that they are available in the template even after redirection. Even after being displayed, they will remain alive for another 30 seconds - for example, in case the user would unintentionally refresh the page - the message will not be lost. +A component has its own storage for flash messages, independent of the presenter. These are messages that, for example, inform about the result of an operation. An important feature of flash messages is that they are available in the template even after redirection. Even after being displayed, they remain active for another 30 seconds – for example, in case the user refreshes the page due to a transmission error - the message won't disappear immediately. -Sending is done by the method [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. The first parameter is the message text or the `stdClass` object representing the message. Optional second parameter is its type (error, warning, info, etc.). The method `flashMessage()` returns an instance of flash message as object stdClass to which you can pass information. +Sending is handled by the [flashMessage |api:Nette\Application\UI\Control::flashMessage()] method. The first parameter is the message text or an `stdClass` object representing the message. The optional second parameter is its type (error, warning, info, etc.). The `flashMessage()` method returns an instance of the flash message as an `stdClass` object, to which further information can be added. ```php $this->flashMessage('Item was deleted.'); $this->redirect(/* ... */); // and redirect ``` -In the template, these messages are available in the variable `$flashes` as objects `stdClass`, which contain the properties `message` (message text), `type` (message type) and can contain the already mentioned user information. We draw them as follows: +These messages are available to the template in the `$flashes` variable as `stdClass` objects, which contain the properties `message` (message text), `type` (message type), and can contain the aforementioned user information. We render them like this, for example: ```latte {foreach $flashes as $flash} @@ -230,17 +230,39 @@ In the template, these messages are available in the variable `$flashes` as obje ``` +Redirection After a Signal +========================== + +Processing a component's signal is often followed by a redirect. This is similar to forms - after submitting them, we also redirect to prevent data resubmission if the page is refreshed in the browser. + +```php +$this->redirect('this'); // redirects to the current presenter and action +``` + +Because a component is a reusable element and typically should not have a direct link to specific presenters, the `redirect()` and `link()` methods automatically interpret the parameter as a component signal: + +```php +$this->redirect('click'); // redirects to the 'click' signal of the same component +``` + +If you need to redirect to another presenter or action, you can do it through the presenter: + +```php +$this->getPresenter()->redirect('Product:show'); // redirects to another presenter/action +``` + + Persistent Parameters ===================== -Persistent parameters are used to maintain state in components between different requests. Their value remains the same even after a link is clicked. Unlike session data, they are transferred in the URL. And they are transferred automatically, including links created in other components on the same page. +Persistent parameters are used to maintain state in components across different requests. Their value remains the same even after clicking a link. Unlike session data, they are transferred in the URL. And this happens completely automatically, including links created in other components on the same page. -For example, you have a content paging component. There can be several such components on a page. And you want all components to stay on their current page when you click on the link. Therefore, we make the page number (`page`) a persistent parameter. +For example, you have a component for content pagination. There might be several such components on a page. And we want all components to remain on their current page after clicking a link. Therefore, we make the page number (`page`) a persistent parameter. -Creating a persistent parameter is extremely easy in Nette. Just create a public property and tag it with the attribute: (previously `/** @persistent */` was used) +Creating a persistent parameter in Nette is extremely simple. Just create a public property and mark it with the attribute: (previously `/** @persistent */` was used) ```php -use Nette\Application\Attributes\Persistent; // this line is important +use Nette\Application\Attributes\Persistent; // this line is important class PaginatingControl extends Control { @@ -249,15 +271,15 @@ class PaginatingControl extends Control } ``` -We recommend that you include the data type (e.g. `int`) with the property, and you can also include a default value. Parameter values can be [validated |#Validation of Persistent Parameters]. +We recommend specifying the data type for the property (e.g., `int`), and you can also provide a default value. Parameter values can be [validated |#Validation of Persistent Parameters]. -You can change the value of a persistent parameter when creating a link: +When creating a link, the value of a persistent parameter can be changed: ```latte next ``` -Or it can be *reset*, i.e. removed from the URL. It will then take its default value: +Or it can be *reset*, i.e., removed from the URL. It will then assume its default value: ```latte reset @@ -267,7 +289,7 @@ Or it can be *reset*, i.e. removed from the URL. It will then take its default v Persistent Components ===================== -Not only parameters but also components can be persistent. Their persistent parameters are also transferred between different actions or between different presenters. We mark persistent components with this annotations for the presenter class. For example here we mark components `calendar` and `poll` as follows: +Not only parameters but also components can be persistent. For such a component, its persistent parameters are transferred even between different actions of the presenter or between multiple presenters. Persistent components are marked with an annotation in the presenter class. For example, we mark the `calendar` and `poll` components like this: ```php /** @@ -278,7 +300,7 @@ class DefaultPresenter extends Nette\Application\UI\Presenter } ``` -You don't have to mark subcomponents as persistent, they are persistent automatically. +Subcomponents within these components do not need to be marked; they become persistent too. In PHP 8, you can also use attributes to mark persistent components: @@ -295,32 +317,32 @@ class DefaultPresenter extends Nette\Application\UI\Presenter Components with Dependencies ============================ -How to create components with dependencies without "messing up" the presenters that will use them? Thanks to the clever features of the DI container in Nette, as with using traditional services, we can leave most of the work to the framework. +How to create components with dependencies without "cluttering" the presenters that will use them? Thanks to the smart features of the DI container in Nette, similar to using classic services, most of the work can be left to the framework. -Let's take as an example a component that has a dependency on the `PollFacade` service: +Let's take an example of a component that has a dependency on the `PollFacade` service: ```php class PollControl extends Control { public function __construct( - private int $id, // Id of a poll, for which the component is created + private int $id, // ID of the poll for which we are creating the component private PollFacade $facade, ) { } public function handleVote(int $voteId): void { - $this->facade->vote($id, $voteId); + $this->facade->vote($this->id, $voteId); // ... } } ``` -If we were writing a classic service, there would be nothing to worry about. The DI container would invisibly take care of passing all the dependencies. But we usually handle components by creating a new instance of them directly in the presenter in [#factory methods] `createComponent...()`. But passing all the dependencies of all the components to the presenter to then pass them to the components is cumbersome. And the amount of code written... +If we were writing a classic service, there would be nothing to discuss. The DI container would invisibly handle passing all dependencies. However, with components, we usually handle them by creating a new instance directly in the presenter within the [#factory methods] `createComponent…()`. But passing all dependencies of all components into the presenter just to pass them on to the components is cumbersome. And the amount of code written... -The logical question is, why don't we just register the component as a classic service, pass it to the presenter, and then return it in the `createComponent...()` method? But this approach is inappropriate because we want to be able to create the component multiple times. +The logical question is, why don't we simply register the component as a classic service, pass it to the presenter, and then return it in the `createComponent…()` method? However, this approach is inappropriate because we want the ability to create the component multiple times if needed. -The correct solution is to write a factory for the component, i.e. a class that creates the component for us: +The correct solution is to write a factory for the component, i.e., a class that creates the component for us: ```php class PollControlFactory @@ -337,17 +359,17 @@ class PollControlFactory } ``` -Now we register our service to DI container to configuration: +We register this factory in our container in the configuration: ```neon services: - PollControlFactory ``` -Finally, we will use this factory in our presenter: +and finally, we use it in our presenter: ```php -class PollPresenter extends Nette\UI\Application\Presenter +class PollPresenter extends Nette\Application\UI\Presenter { public function __construct( private PollControlFactory $pollControlFactory, @@ -362,7 +384,7 @@ class PollPresenter extends Nette\UI\Application\Presenter } ``` -The great thing is that Nette DI can [generate |dependency-injection:factory] such simple factories, so instead of writing the whole code, you just need to write its interface: +What's great is that Nette DI can [generate |dependency-injection:factory] such simple factories, so instead of writing its entire code, you just need to write its interface: ```php interface PollControlFactory @@ -371,21 +393,21 @@ interface PollControlFactory } ``` -That's all. Nette internally implements this interface and injects it to our presenter, where we can use it. It also magically passes our parameter `$id` and instance of class `PollFacade` into our component. +And that's all. Nette internally implements this interface and injects it into the presenter, where we can use it. It magically adds the `$id` parameter and an instance of the `PollFacade` class to our component. Components in Depth =================== -Components in a Nette Application are the reusable parts of a web application that we embed in pages, which is the subject of this chapter. What exactly are the capabilities of such a component? +Components in Nette Application represent reusable parts of a web application that we embed into pages, and which this entire chapter is dedicated to. What exactly are the capabilities of such a component? -1) it is renderable in a template -2) it knows which part of itself to render during an [AJAX request |ajax#invalidation] (snippets) -3) it has the ability to store its state in a URL (persistent parameters) -4) has the ability to respond to user actions (signals) -5) creates a hierarchical structure (where the root is the presenter) +1) It is renderable in a template +2) It knows [which part of itself |ajax#Snippets] to render during an AJAX request (snippets) +3) It has the ability to store its state in the URL (persistent parameters) +4) It has the ability to react to user actions (signals) +5) It creates a hierarchical structure (where the root is the presenter) -Each of these functions is handled by one of the inheritance lineage classes. Rendering (1 + 2) is handled by [api:Nette\Application\UI\Control], incorporation into the [lifecycle |presenters#life-cycle-of-presenter] (3, 4) by the [api:Nette\Application\UI\Component] class, and the creation of the hierarchical structure (5) by the [Container and Component |component-model:] classes. +Each of these functions is handled by one of the classes in the inheritance line. Rendering (1 + 2) is handled by [api:Nette\Application\UI\Control], integration into the [lifecycle |presenters#Presenter Life Cycle] (3, 4) by the [api:Nette\Application\UI\Component] class, and the creation of the hierarchical structure (5) by the [Container and Component |component-model:] classes. ``` Nette\ComponentModel\Component { IComponent } @@ -400,18 +422,18 @@ Nette\ComponentModel\Component { IComponent } ``` -Life Cycle of Component ------------------------ +Component Lifecycle +------------------- -[* lifecycle-component.svg *] *** *Life cycle of component* .<> +[* lifecycle-component.svg *] *** *Component lifecycle* .<> Validation of Persistent Parameters ----------------------------------- -The values of [#persistent parameters] received from URLs are written to properties by the `loadState()` method. It also checks if the data type specified for the property matches, otherwise it will respond with a 404 error and the page will not be displayed. +The values of [#persistent parameters] received from URLs are written to properties by the `loadState()` method. It also checks whether the data type specified for the property matches; otherwise, it responds with a 404 error and the page is not displayed. -Never blindly trust persistent parameters because they can easily be overwritten by the user in the URL. For example, this is how we check if the page number `$this->page` is greater than 0. A good way to do this is to override the `loadState()` method mentioned above: +Never blindly trust persistent parameters, as they can be easily overwritten by the user in the URL. This is how we check, for example, if the page number `$this->page` is greater than 0. A suitable way is to override the mentioned `loadState()` method: ```php class PaginatingControl extends Control @@ -421,8 +443,8 @@ class PaginatingControl extends Control public function loadState(array $params): void { - parent::loadState($params); // here is set the $this->page - // follows the user value check: + parent::loadState($params); // $this->page is set here + // follows the custom value check: if ($this->page < 1) { $this->error(); } @@ -430,27 +452,27 @@ class PaginatingControl extends Control } ``` -The opposite process, that is, collecting values from persistent properites, is handled by the `saveState()` method. +The opposite process, i.e., collecting values from persistent properties, is handled by the `saveState()` method. Signals in Depth ---------------- -A signal causes a page reload like the original request (with the exception of AJAX) and invokes the method `signalReceived($signal)` whose default implementation in class `Nette\Application\UI\Component` tries to call a method composed of the words `handle{Signal}`. Further processing relies on the given object. Objects which are descendants of `Component` (i.e. `Control` and `Presenter`) try to call `handle{Signal}` with relevant parameters. +A signal causes the page to reload exactly like the original request (except when called via AJAX) and invokes the `signalReceived($signal)` method, whose default implementation in the `Nette\Application\UI\Component` class attempts to call a method composed of the words `handle{Signal}`. Further processing is up to the given object. Objects inheriting from `Component` (i.e., `Control` and `Presenter`) react by trying to call the `handle{Signal}` method with the appropriate parameters. -In other words: the definition of the method `handle{Signal}` is taken and all parameters which were received in the request are matched with the method's parameters. It means that the parameter `id` from the URL is matched to the method's parameter `$id`, `something` to `$something` and so on. And if the method doesn't exist, the method `signalReceived` throws [an exception |api:Nette\Application\UI\BadSignalException]. +In other words: the definition of the `handle{Signal}` function is taken, along with all parameters that came with the request, and parameters from the URL are assigned to the arguments by name, and an attempt is made to call the method. For example, the value from the `id` parameter in the URL is passed as the `$id` argument, `something` from the URL is passed as `$something`, etc. And if the method does not exist, the `signalReceived` method throws an [exception |api:Nette\Application\UI\BadSignalException]. -Signal can be received by any component, presenter of object which implements interface `SignalReceiver` if it's connected to component tree. +A signal can be received by any component, presenter, or object that implements the `SignalReceiver` interface and is connected to the component tree. -The main receivers of signals are `Presenters` and visual components extending `Control`. A signal is a sign for an object that it has to do something - poll counts in a vote from user, box with news has to unfold, form was sent and has to process data and so on. +The main recipients of signals will be `Presenters` and visual components inheriting from `Control`. A signal is intended to serve as a sign for an object that it should do something – a poll should count a vote from the user, a news block should expand and display twice as many news items, a form has been submitted and should process data, and so on. -The URL for the signal is created using the method [Component::link() |api:Nette\Application\UI\Component::link()]. As parameter `$destination` we pass string `{signal}!` and as `$args` an array of arguments which we want to pass to the signal handler. Signal parameters are attached to the URL of the current presenter/view. **The parameter `?do` in the URL determines the signal called.** +The URL for a signal is created using the [Component::link() |api:Nette\Application\UI\Component::link()] method. As the `$destination` parameter, we pass the string `{signal}!` and as `$args`, an array of arguments we want to pass to the signal. The signal is always called on the current presenter and action with the current parameters; the signal parameters are just added. Additionally, the **parameter `?do` which specifies the signal** is added right at the beginning. -Its format is `{signal}` or `{signalReceiver}-{signal}`. `{signalReceiver}` is the name of the component in the presenter. This is why hyphen (inaccurately dash) can't be present in the name of components - it is used to divide the name of the component and signal, but it's possible to compose several components. +Its format is either `{signal}` or `{signalReceiver}-{signal}`. `{signalReceiver}` is the name of the component in the presenter. Therefore, a hyphen cannot be used in the component name – it is used to separate the component name and the signal, although it is possible to nest multiple components this way. -The method [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] verifies whether a component (first argument) is a receiver of a signal (second argument). The second argument can be omitted - then it finds out if the component is a receiver of any signal. If the second parameter is `true` it verifies whether the component or its descendants are receivers of a signal. +The [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] method checks whether the component (first argument) is the recipient of the signal (second argument). The second argument can be omitted – then it checks if the component is the recipient of any signal. If the second parameter is set to `true`, it verifies whether the specified component or any of its descendants is the recipient. -In any phase preceding `handle{Signal}` can be signal performed manually by calling the method [processSignal()|api:Nette\Application\UI\Presenter::processSignal()] which takes responsibility for signal execution. Takes receiver component (if not set it is presenter itself) and sends it the signal. +At any stage preceding `handle{Signal}`, we can execute the signal manually by calling the [processSignal()|api:Nette\Application\UI\Presenter::processSignal()] method, which takes care of handling the signal – it takes the component identified as the signal recipient (if no recipient is specified, it is the presenter itself) and sends the signal to it. Example: @@ -460,4 +482,4 @@ if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, ' } ``` -The signal is executed prematurely and it won't be called again. +This executes the signal prematurely, and it will not be called again. diff --git a/application/en/configuration.texy b/application/en/configuration.texy index 10b9f4abf8..a7c91cb890 100644 --- a/application/en/configuration.texy +++ b/application/en/configuration.texy @@ -1,8 +1,8 @@ -Configuring Application -*********************** +Application Configuration +************************* .[perex] -Overview of configuration options for the Nette Application. +Overview of configuration options for Nette Application. Application @@ -10,41 +10,54 @@ Application ```neon application: - # shows "Nette Application" panel in Tracy BlueScreen? + # show the "Nette Application" panel in Tracy BlueScreen? debugger: ... # (bool) defaults to true - # will error-presenter be called on error? - catchExceptions: ... # (bool) defaults to true in production mode + # will the error-presenter be called on error? + # effective only in development mode + catchExceptions: ... # (bool) defaults to true - # name of error-presenter - errorPresenter: Error # (string) defaults to 'Nette:Error' + # name of the error-presenter + errorPresenter: Error # (string|array) defaults to 'Nette:Error' - # defines the rules for resolving the presenter name to a class + # defines aliases for presenters and actions + aliases: ... + + # defines the rules for translating the presenter name to a class mapping: ... - # do bad links generate warnings? - # has effect only in developer mode + # suppress warnings for invalid links? + # effective only in development mode silentLinks: ... # (bool) defaults to false ``` -Because error-presenters are not called by default in development mode and the errors are displayed by Tracy, changing the value `catchExceptions` to `true` helps to verify that error-presenters works correct during development. +Since `nette/application` version 3.2, it is possible to define a pair of error presenters: -Option `silentLinks` determines how Nette behaves in developer mode when link generation fails (for example, because there is no presenter, etc). The default value `false` means that Nette triggers `E_USER_WARNING`. Setting to `true` suppresses this error message. In a production environment, `E_USER_WARNING` is always invoked. We can also influence this behavior by setting the presenter variable [$invalidLinkMode |creating-links#Invalid Links]. +```neon +application: + errorPresenter: + 4xx: Error4xx # for Nette\Application\BadRequestException + 5xx: Error5xx # for other exceptions +``` -The [mapping defines the rules |modules#mapping] by which the class name is derived from the presenter name. +The `silentLinks` option determines how Nette behaves in development mode when link generation fails (for example, because the presenter does not exist, etc.). The default value `false` means that Nette triggers an `E_USER_WARNING` error. Setting it to `true` suppresses this error message. In a production environment, `E_USER_WARNING` is always triggered. This behavior can also be influenced by setting the presenter variable [$invalidLinkMode |creating-links#Invalid Links]. + +[Aliases simplify referencing |creating-links#Aliases] frequently used presenters. + +The [mapping defines the rules |directory-structure#Presenter Mapping] by which the class name is derived from the presenter name. Automatic Registration of Presenters ------------------------------------ -Nette automatically adds presenters as services to the DI container, which significantly speeds up their creation. How Nette finds out presenters can be configured: +Nette automatically adds presenters as services to the DI container, which significantly speeds up their creation. How Nette locates presenters can be configured: ```neon application: - # to look for presenters in Composer class map? + # look for presenters in Composer class map? scanComposer: ... # (bool) defaults to true - # a mask that must match the class and file name + # a mask that the class and file name must match scanFilter: ... # (string) defaults to '*Presenter' # in which directories to look for presenters? @@ -52,7 +65,7 @@ application: - %vendorDir%/mymodule ``` -The directories listed in `scanDirs` do not override the default value `%appDir%`, but complement it, so `scanDirs` will contain both paths `%appDir%` and `%vendorDir%/mymodule`. If we want to overwrite the default directory, we use [exclamation mark |dependency-injection:configuration#Merging]: +The directories listed in `scanDirs` do not override the default value `%appDir%`, but complement it, so `scanDirs` will contain both paths `%appDir%` and `%vendorDir%/mymodule`. If we want to omit the default directory, we use an [exclamation mark |dependency-injection:configuration#Merging]: ```neon application: @@ -60,43 +73,52 @@ application: - %vendorDir%/mymodule ``` -Directory scanning can be turned off by setting false. We do not recommend completely suppressing the automatic addition of presenters, otherwise application performance will be reduced. +Directory scanning can be turned off by setting the value to false. We do not recommend completely suppressing the automatic addition of presenters, as this will reduce application performance. -Latte -===== +Latte Templates +=============== This setting globally affects the behavior of Latte in components and presenters. ```neon latte: - # shows Latte panel in the Tracy Bar for the main template (true) or for all components (all)? + # show the Latte panel in the Tracy Bar for the main template (true) or for all components (all)? debugger: ... # (true|false|'all') defaults to true - # generates templates with declare(strict_types=1) + # generate templates with declare(strict_types=1) header strictTypes: ... # (bool) defaults to false - # class of $this->template + # enable [strict parser mode |latte:develop#strict mode] + strictParsing: ... # (bool) default is false + + # enable [checking of generated code |latte:develop#Checking Generated Code] + phpLinter: ... # (string) default is null + + # set the locale + locale: cs_CZ # (string) default is null + + # class of the $this->template object templateClass: App\MyTemplateClass # defaults to Nette\Bridges\ApplicationLatte\DefaultTemplate ``` -If you are using Latte version 3, you can add new [extension |latte:creating-extension] using: +If you are using Latte version 3, you can add new [extensions |latte:extending-latte#Latte Extension] using: ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -If you are using Latte version 2, you can register new tags either by entering the class name or by referring to the service. Method `install()` is called by default, but this can be changed by specifying the name of another method: +If you are using Latte version 2, you can register new tags either by specifying the class name or by referencing a service. By default, the `install()` method is called, but this can be changed by specifying the name of another method: ```neon latte: - # registration of user Latte tags + # registration of custom Latte tags macros: - App\MyLatteMacros::register # static method, classname or callable - - @App\MyLatteMacrosFactory # service with install method - - @App\MyLatteMacrosFactory::register # service with register method + - @App\MyLatteMacrosFactory # service with install() method + - @App\MyLatteMacrosFactory::register # service with register() method services: - App\MyLatteMacrosFactory @@ -110,14 +132,14 @@ Basic settings: ```neon routing: - # shows routing panel in Tracy Bar? + # show the routing panel in Tracy Bar? debugger: ... # (bool) defaults to true - # to serialize router to DI container? + # serialize the router into the DI container cache: ... # (bool) defaults to false ``` -Router is usually defined in the [RouterFactory |routing#Route Collection] class. Alternatively, routs can also be defined in the configuration using `mask: action` pairs, but this method does not offer such a wide variation in settings: +Routing is usually defined in the [RouterFactory |routing#Route Collection] class. Alternatively, routes can also be defined in the configuration using `mask: action` pairs, but this method does not offer much flexibility: ```neon routing: @@ -137,7 +159,7 @@ constants: Foobar: 'baz' ``` -The `Foobar` constant will created after startup. +The `Foobar` constant will be created after the application starts. .[note] Constants should not serve as globally available variables. To pass values to objects, use [dependency injection |dependency-injection:passing-dependencies]. @@ -146,9 +168,24 @@ Constants should not serve as globally available variables. To pass values to ob PHP === -You can set PHP directives. An overview of all directives can be found at [php.net |https://www.php.net/manual/en/ini.list.php]. +Setting PHP directives. An overview of all directives can be found at [php.net |https://www.php.net/manual/en/ini.list.php]. ```neon php: date.timezone: Europe/Prague ``` + + +DI Services +=========== + +These services are added to the DI container: + +| Name | Type | Description +|----------------------------|---------------------------------------------------|----------------------------------------- +| `application.application` | [api:Nette\Application\Application] | the [application runner |how-it-works#Nette Application] +| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] +| `application.presenterFactory` | [api:Nette\Application\PresenterFactory] | presenter factory +| `application.###` | [api:Nette\Application\UI\Presenter] | individual presenters +| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | factory for `Latte\Engine` object +| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | factory for [`$this->template` |templates] diff --git a/application/en/creating-links.texy b/application/en/creating-links.texy index 0fb783c609..f831b5e018 100644 --- a/application/en/creating-links.texy +++ b/application/en/creating-links.texy @@ -3,53 +3,53 @@ Creating URL Links
    -Creating links in Nette is as easy as pointing a finger. Just point and the framework will do all the work for you. We will show: +Creating links in Nette is as simple as pointing a finger. Just aim, and the framework will do all the work for you. We will show: - how to create links in templates and elsewhere - how to distinguish a link to the current page -- what about invalid links +- what to do with invalid links
    -Thanks to [bidirectional routing|routing], you'll never have to hardcode application's URLs in the templates or code, which may change later or be complicated to compose. Just specify the presenter and the action in the link, pass any parameters and the framework will generate the URL itself. In fact, it's very similar to calling a function. You will like it. +Thanks to [bidirectional routing |routing], you will never have to hardcode URLs of your application into templates or code, which might change later or be complicated to assemble. In the link, just specify the presenter and action, pass any parameters, and the framework will generate the URL itself. Actually, it's very similar to calling a function. You'll like this. In the Presenter Template ========================= -Most often we create links in templates and a great helper is the attribute `n:href`: +Most often, we create links in templates, and the `n:href` attribute is a great helper: ```latte detail ``` -Note, that instead of the HTML attribute `href` we've used [n:attribute |latte:syntax#n:attributes] `n:href`. Its value isn't a URL, as you are used to with the `href` attribute, but name of the presenter and the action. +Notice that instead of the HTML attribute `href`, we used the [n:attribute |latte:syntax#n:attributes] `n:href`. Its value is not a URL, as would be the case with the `href` attribute, but the name of the presenter and action. -Clicking on a link is, simply said, something like calling a method `ProductPresenter::renderShow()`. And if it has parameters in its signature, we can call it with arguments: +Clicking on a link is, simply put, something like calling the `ProductPresenter::renderShow()` method. And if it has parameters in its signature, we can call it with arguments: ```latte -detail +product detail ``` -It is also possible to pass named parameters. The following link passes parameter `lang` with value `en`: +It is also possible to pass named parameters. The following link passes the parameter `lang` with the value `en`: ```latte -detail +product detail ``` -If method `ProductPresenter::renderShow()` does not have `$lang` in its signature, it can read the value of the parameter using `$lang = $this->getParameter('lang')`. +If the `ProductPresenter::renderShow()` method does not have `$lang` in its signature, it can retrieve the parameter's value using `$lang = $this->getParameter('lang')` or from a [property |presenters#Request Parameters]. -If the parameters are stored in an array, they can be expanded with the `...` operator (or `(expand)` operator in Latte 2.x): +If the parameters are stored in an array, they can be expanded using the `...` operator (or the `(expand)` operator in Latte 2.x): ```latte {var $args = [$product->id, lang => en]} -detail +product detail ``` -The so-called [persistent parameters|presenters#persistent parameters] are also automatically passed in the links. +So-called [persistent parameters |presenters#Persistent Parameters] are also automatically passed in links. -Attribute `n:href` is very handy for HTML tags ``. If we want to print the link elsewhere, for example in the text, we use `{link}`: +The `n:href` attribute is very handy for HTML `` tags. If we want to print the link elsewhere, for example in text, we use `{link}`: ```latte URL is: {link Home:default} @@ -59,64 +59,64 @@ URL is: {link Home:default} In the Code =========== -The method `link()` is used to create a link in the presenter: +The `link()` method is used to create a link in the presenter: ```php $url = $this->link('Product:show', $product->id); ``` -Parameters can also be passed as an array where named parameters can also be specified: +Parameters can also be passed as an array, where named parameters can also be specified: ```php -$url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); +$url = $this->link('Product:show', [$product->id, 'lang' => 'en']); ``` -Links can be created without a presenter too, using the [#LinkGenerator] and its method `link()`. +Links can also be created without a presenter, using the [#LinkGenerator] and its `link()` method. Links to Presenter ================== -If the target of the link is presenter and action, it has this syntax: +If the target of the link is a presenter and action, it has this syntax: ``` [//] [[[[:]module:]presenter:]action | this] [#fragment] ``` -The format is supported by all Latte tags and all presenter methods that work with links, ie `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` and also [#LinkGenerator]. So even if `n:href` is used in the examples, there could be any of the functions. +This format is supported by all Latte tags and all presenter methods that work with links, i.e., `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()`, and also [#LinkGenerator]. So even if `n:href` is used in the examples, any of these functions could be there. The basic form is therefore `Presenter:action`: ```latte -home +home page ``` -If we link to the action of the current presenter, we can omit its name: +If we are linking to an action of the current presenter, we can omit its name: ```latte -home +home page ``` -If the action is `default`, we can omit it, but the colon must remain: +If the target action is `default`, we can omit it, but the colon must remain: ```latte -home +home page ``` -Links may also point to other [modules]. Here, the links are distinguished into relative to the submodules, or absolute. The principle is analogous to disk paths, only instead of slashes there are colons. Let's assume that the actual presenter is part of module `Front`, then we will write: +Links can also point to other [modules |directory-structure#Presenters and Templates]. Here, links are distinguished as relative to a nested submodule, or absolute. The principle is analogous to disk paths, only colons are used instead of slashes. Assuming the current presenter is part of the `Front` module, we would write: ```latte link to Front:Shop:Product:show link to Admin:Product:show ``` -A special case is [linking to itself|#Links to Current Page]. Here we'll write `this` as the target. +A special case is a link [to itself |#Link to Current Page], where we specify `this` as the target. ```latte refresh ``` -We can link to a certain part of the HTML page via a so-called fragment after the `#` hash symbol: +We can link to a specific part of the page via a so-called fragment after the hash sign `#`: ```latte link to Home:default and fragment #main @@ -126,35 +126,37 @@ We can link to a certain part of the HTML page via a so-called fragment after th Absolute Paths ============== -Links generated by `link()` or `n:href` are always absolute paths (i.e., they start with `/`), but not absolute URLs with a protocol and domain like `https://domain`. +Links generated using `link()` or `n:href` are always absolute paths (i.e., they start with `/`), but not absolute URLs with protocol and domain like `https://domain`. -To generate an absolute URL, add two slashes to the beginning (e.g., `n:href="//Home:"`). Or you can switch the presenter to generate only absolute links by setting `$this->absoluteUrls = true`. +To generate an absolute URL, add two slashes at the beginning (e.g., `n:href="//Home:"`). Alternatively, you can switch the presenter to generate only absolute links by setting `$this->absoluteUrls = true`. + +The `|absoluteUrl` filter can also be used in the template to convert a relative path to an absolute path. Link to Current Page ==================== -The target `this` will create a link to the current page: +The target `this` creates a link to the current page: ```latte refresh ``` -At the same time, all parameters specified in the signature of the `render()` or `action()` method are transferred. So if we are on the `Product:show` and `id:123` pages, the link to `this` will also pass this parameter. +At the same time, all parameters specified in the signature of the `action()` or `render()` method are transferred (if `action()` is not defined). So if we are on the `Product:show` page with `id: 123`, the link to `this` will also pass this parameter. -Of course, it is possible to specify the parameters directly: +Of course, it is possible to specify parameters directly: ```latte refresh ``` -Function `isLinkCurrent()` determines if the target of the link is the same as the current page. This can be used, for example, in a template to differentiate links, etc. +The `isLinkCurrent()` function checks if the link target is identical to the current page. This can be used, for example, in a template to distinguish links, etc. -The parameters are the same as for the `link()` method, but it is also possible to use the wildcard `*` instead of a specific action, which means any action of the presenter. +The parameters are the same as for the `link()` method, but it is also possible to use the wildcard `*` instead of a specific action, which means any action of the given presenter. ```latte {if !isLinkCurrent('Admin:login')} - Přihlaste se + Login {/if}
  • @@ -162,39 +164,54 @@ The parameters are the same as for the `link()` method, but it is also possible
  • ``` -An abbreviated form can be used in combination with `n:href` in single element: +In combination with `n:href` in a single element, a shorthand form can be used: ```latte -... +... ``` -Wildcard character `*` replaces presenter's action only, not presenter itself. +The wildcard `*` can only be used instead of the action, not the presenter. -To find out if we are in a certain module or its submodule we can use `isModuleCurrent(moduleName)` function. +To determine if we are in a specific module or its submodule, use the `isModuleCurrent(moduleName)` method. ```latte -
  • +
  • ...
  • ``` +Changing Link Base .{data-version:v3.2.7} +========================================= + +By default, relative links are derived from the current presenter. This can be changed using `{linkBase}`: + +```latte +{linkBase Admin:Dashboard} +product detail +``` + +The link will lead to `Admin:Dashboard:Product:show`. Only relative links are affected - absolute links starting with a colon and links to the current presenter (`this`, `show`) remain unchanged. + +`{linkBase}` applies to the entire template and is especially useful in layout templates, where it ensures consistent links regardless of the calling presenter. + + Links to Signal =============== -The target of the link may not only be the presenter and action, but also the [signal |components#Signal] (they call the method `handle()`). The syntax is as follows: +The target of a link doesn't have to be just a presenter and action, but also a [signal |components#Signal] (they call the `handle()` method). Then the syntax is as follows: ``` [//] [sub-component:]signal! [#fragment] ``` -The signal is therefore distinguishes by exclamation mark: +The signal is thus distinguished by an exclamation mark: ```latte signal ``` -You can also create a link to the signal of the subcomponent (or sub-subcomponent): +You can also create a link to a signal of a subcomponent (or sub-subcomponent): ```latte signal @@ -204,16 +221,16 @@ You can also create a link to the signal of the subcomponent (or sub-subcomponen Links in Component ================== -Because [components] are separate reusable units that should have no relations to surrounding presenters, the links work a little differently. The Latte attribute `n:href` and tag `{link}` and component methods such as `link()` and others always consider the target **as the signal name**. Therefore it is not necessary to use an exclamation mark: +Because [components|components] are separate reusable units that should not have any ties to surrounding presenters, links work a bit differently here. The Latte attribute `n:href` and the tag `{link}`, as well as component methods like `link()` and others, **always consider the link target as the signal name**. Therefore, it is not even necessary to include an exclamation mark: ```latte signal, not an action ``` -If we want to link to presenters in the component template, we use the tag `{plink}`: +If we wanted to link to presenters in the component template, we would use the `{plink}` tag: ```latte -home +home ``` or in the code @@ -223,17 +240,41 @@ $this->getPresenter()->link('Home:default') ``` +Aliases .{data-version:v3.2.2} +============================== + +Sometimes it can be useful to assign an easily memorable alias to a Presenter:action pair. For example, naming the homepage `Front:Home:default` simply as `home` or `Admin:Dashboard:default` as `admin`. + +Aliases are defined in the [configuration|configuration] under the key `application › aliases`: + +```neon +application: + aliases: + home: Front:Home:default + admin: Admin:Dashboard:default + sign: Front:Sign:in +``` + +In links, they are then written using an at sign, for example: + +```latte +administration +``` + +They are also supported in all methods that work with links, such as `redirect()` and similar. + + Invalid Links ============= -It may happen that we create an invalid link - either because it refers to a non-existing presenter, or because it passes more parameters that the target method receives in its signature, or when there can't be a generated URL for the targeted action. What to do with invalid links is determined by the static variable `Presenter::$invalidLinkMode`. It can have one of these values (constants): +It may happen that we create an invalid link - either because it leads to a non-existent presenter, or because it passes more parameters than the target method accepts in its signature, or when a URL cannot be generated for the target action. How to handle invalid links is determined by the static variable `Presenter::$invalidLinkMode`. It can take a combination of these values (constants): -- `Presenter::InvalidLinkSilent` - silent mode, returns symbol `#` as URL -- `Presenter::InvalidLinkWarning` - E_USER_WARNING will be produced -- `Presenter::InvalidLinkTextual` - visual warning, the error text is displayed in the link -- `Presenter::InvalidLinkException` - InvalidLinkException will be thrown +- `Presenter::InvalidLinkSilent` - silent mode, returns the character # as the URL +- `Presenter::InvalidLinkWarning` - an E_USER_WARNING warning is thrown, which will be logged in production mode, but will not interrupt script execution +- `Presenter::InvalidLinkTextual` - visual warning, prints the error directly into the link +- `Presenter::InvalidLinkException` - throws InvalidLinkException -The default setup in production mode is `InvalidLinkWarning` and in development mode is `InvalidLinkWarning | InvalidLinkTextual`. `InvalidLinkWarning` doesn't kill the script in the production environment, but the warning will be logged. In the development environment, [Tracy |tracy:] will intercept the warning and display the error bluescreen. If the `InvalidLinkTextual` is set, presenter and components return error message as URL which stars with `#error:`. To make such links visible, we can add a CSS rule to our stylesheet: +The default setting is `InvalidLinkWarning` in production mode and `InvalidLinkWarning | InvalidLinkTextual` in development mode. `InvalidLinkWarning` in the production environment does not cause script interruption, but the warning will be logged. In the development environment, [Tracy |tracy:] catches it and displays a bluescreen. `InvalidLinkTextual` works by returning an error message as the URL, starting with the characters `#error:`. To make such links noticeable at first glance, add the following to your CSS: ```css a[href^="#error:"] { @@ -242,7 +283,7 @@ a[href^="#error:"] { } ``` -If we don't want warnings to be produced in the development environment we can turn on silent invalid link mode in the [configuration]. +If we do not want warnings to be produced in the development environment, we can set the silent mode directly in the [configuration|configuration]. ```neon application: @@ -253,10 +294,10 @@ application: LinkGenerator ============= -How to create links with the method `link()` comfort, but without the presence of a presenter? That's why here is [api:Nette\Application\LinkGenerator]. +How to create links with similar comfort as the `link()` method, but without the presence of a presenter? That's what [api:Nette\Application\LinkGenerator] is for. -LinkGenerator is a service that you can have passed through the constructor and then create links using its method `link()`. +LinkGenerator is a service that you can have passed via the constructor and then create links using its `link()` method. -There is a difference compared to presenters. LinkGenerator creates all links as absolute URLs. Furthermore, there is no "current presenter", so it is not possible to specify only the name of the action `link('default')` or the relative paths to the [modules]. +There is a difference compared to presenters. LinkGenerator creates all links directly as absolute URLs. Furthermore, there is no "current presenter", so it is not possible to specify only the action name `link('default')` as the target or use relative paths to modules. Invalid links always throw `Nette\Application\UI\InvalidLinkException`. diff --git a/application/en/directory-structure.texy b/application/en/directory-structure.texy new file mode 100644 index 0000000000..a94b6148c7 --- /dev/null +++ b/application/en/directory-structure.texy @@ -0,0 +1,526 @@ +Directory Structure of the Application +************************************** + +
    + +How to design a clear and scalable directory structure for projects in Nette Framework? We will show you proven practices that will help you organize your code. You will learn: + +- how to **logically structure** the application into directories +- how to design the structure so that it **scales well** as the project grows +- what are the **possible alternatives** and their advantages or disadvantages + +
    + + +It is important to mention that Nette Framework itself does not enforce any specific structure. It is designed to be easily adaptable to any needs and preferences. + + +Basic Project Structure +======================= + +Although Nette Framework does not dictate any fixed directory structure, there is a proven default arrangement in the form of the [Web Project|https://github.com/nette/web-project]: + +/--pre +web-project/ +├── app/ ← application directory +├── assets/ ← SCSS, JS files, images..., alternatively resources/ +├── bin/ ← scripts for command line +├── config/ ← configuration +├── log/ ← logged errors +├── temp/ ← temporary files, cache +├── tests/ ← tests +├── vendor/ ← libraries installed by Composer +└── www/ ← public directory (document-root) +\-- + +You can modify this structure freely according to your needs - rename or move folders. Then you just need to adjust the relative paths to directories in `Bootstrap.php` and possibly `composer.json`. Nothing more is needed, no complex reconfiguration, no changes to constants. Nette has smart autodetection and automatically recognizes the application's location, including its base URL. + + +Code Organization Principles +============================ + +When you first explore a new project, you should be able to quickly orient yourself. Imagine clicking on the `app/Model/` directory and seeing this structure: + +/--pre +app/Model/ +├── Services/ +├── Repositories/ +└── Entities/ +\-- + +From this, you only learn that the project uses some services, repositories, and entities. You learn nothing about the actual purpose of the application. + +Let's look at a different approach - **organization by domains**: + +/--pre +app/Model/ +├── Cart/ +├── Payment/ +├── Order/ +└── Product/ +\-- + +Here it's different - at first glance, it's clear that this is an e-shop. The directory names themselves reveal what the application can do - it works with payments, orders, and products. + +The first approach (organization by class type) brings several problems in practice: code that is logically related is fragmented across different folders, and you have to jump between them. Therefore, we will organize by domains. + + +Namespaces +---------- + +It is customary for the directory structure to correspond to the namespaces in the application. This means that the physical location of files matches their namespace. For example, a class located in `app/Model/Product/ProductRepository.php` should have the namespace `App\Model\Product`. This principle helps in navigating the code and simplifies autoloading. + + +Singular vs Plural in Names +--------------------------- + +Notice that we use singular for the main application directories: `app`, `config`, `log`, `temp`, `www`. The same applies inside the application: `Model`, `Core`, `Presentation`. This is because each represents a single cohesive concept. + +Similarly, `app/Model/Product` represents everything related to products. We don't call it `Products` because it's not a folder full of products (that would contain files like `nokia.php`, `samsung.php`). It's a namespace containing classes for working with products - `ProductRepository.php`, `ProductService.php`. + +The folder `app/Tasks` is plural because it contains a set of separate executable scripts - `CleanupTask.php`, `ImportTask.php`. Each of them is an independent unit. + +For consistency, we recommend using: +- Singular for namespaces representing a functional unit (even if working with multiple entities) +- Plural for collections of independent units +- In case of uncertainty, or if you don't want to think about it, choose singular + + +Public Directory `www/` +======================= + +This directory is the only one accessible from the web (the document-root). You might often encounter the name `public/` instead of `www/` - it's just a matter of convention and does not affect the application's functionality. The directory contains: +- Application [entry point |bootstrapping#index.php] `index.php` +- `.htaccess` file with rules for mod_rewrite (for Apache) +- Static files (CSS, JavaScript, images) +- Uploaded files + +For proper application security, it is crucial to have the [document-root configured correctly |nette:troubleshooting#How to Change or Remove www Directory from URL]. + +.[note] +Never place the `node_modules/` folder in this directory - it contains thousands of files that might be executable and should not be publicly accessible. + + +Application Directory `app/` +============================ + +This is the main directory containing the application code. Basic structure: + +/--pre +app/ +├── Core/ ← infrastructure concerns +├── Model/ ← business logic +├── Presentation/ ← presenters and templates +├── Tasks/ ← command scripts +└── Bootstrap.php ← application bootstrap class +\-- + +`Bootstrap.php` is the [application startup class|bootstrapping] that initializes the environment, loads configuration, and creates the DI container. + +Let's now look at the individual subdirectories in more detail. + + +Presenters and Templates +======================== + +The presentation part of the application is located in the `app/Presentation` directory. An alternative is the shorter `app/UI`. This is the place for all presenters, their templates, and any associated helper classes. + +We organize this layer according to domains. In a complex project combining an e-shop, blog, and API, the structure would look like this: + +/--pre +app/Presentation/ +├── Shop/ ← e-shop frontend +│ ├── Product/ +│ ├── Cart/ +│ └── Order/ +├── Blog/ ← blog +│ ├── Home/ +│ └── Post/ +├── Admin/ ← administration +│ ├── Dashboard/ +│ └── Products/ +└── Api/ ← API endpoints + └── V1/ +\-- + +Conversely, for a simple blog, we would use the following structure: + +/--pre +app/Presentation/ +├── Front/ ← website frontend +│ ├── Home/ +│ └── Post/ +├── Admin/ ← administration +│ ├── Dashboard/ +│ └── Posts/ +├── Error/ +└── Export/ ← RSS, sitemaps, etc. +\-- + +Folders like `Home/` or `Dashboard/` contain presenters and templates. Folders like `Front/`, `Admin/`, or `Api/` are called **modules**. Technically, these are regular directories used for the logical partitioning of the application. + +Each folder containing a presenter includes the presenter file itself and its templates. For example, the `Dashboard/` folder contains: + +/--pre +Dashboard/ +├── DashboardPresenter.php ← presenter +└── default.latte ← template +\-- + +This directory structure is reflected in the class namespaces. For example, `DashboardPresenter` is located in the `App\Presentation\Admin\Dashboard` namespace (see [#Presenter Mapping]): + +```php +namespace App\Presentation\Admin\Dashboard; + +class DashboardPresenter extends Nette\Application\UI\Presenter +{ + // ... +} +``` + +We refer to the `Dashboard` presenter within the `Admin` module in the application using colon notation as `Admin:Dashboard`. Its `default` action is then referred to as `Admin:Dashboard:default`. For nested modules, we use multiple colons, for example, `Shop:Order:Detail:default`. + + +Flexible Structure Development +------------------------------ + +One of the great advantages of this structure is how elegantly it adapts to the growing needs of the project. As an example, let's take the part generating XML feeds. Initially, we have a simple form: + +/--pre +Export/ +├── ExportPresenter.php ← one presenter for all exports +├── sitemap.latte ← template for sitemap +└── feed.latte ← template for RSS feed +\-- + +Over time, more feed types are added, and we need more logic for them... No problem! The `Export/` folder simply becomes a module: + +/--pre +Export/ +├── Sitemap/ +│ ├── SitemapPresenter.php +│ └── sitemap.latte +└── Feed/ + ├── FeedPresenter.php + ├── amazon.latte ← feed for Amazon + └── ebay.latte ← feed for eBay +\-- + +This transformation is completely smooth - just create new subfolders, divide the code into them and update links (e.g. from `Export:feed` to `Export:Feed:amazon`). Thanks to this, we can gradually expand the structure as needed, the nesting level is not limited in any way. + +For example, if in the administration you have many presenters related to order management, such as `OrderDetail`, `OrderEdit`, `OrderDispatch`, etc., you can create a module (folder) named `Order` for better organization, which will contain (folders for) presenters `Detail`, `Edit`, `Dispatch`, and others. + + +Template Location +----------------- + +In the previous examples, we saw that templates are located directly in the folder with the presenter: + +/--pre +Dashboard/ +├── DashboardPresenter.php ← presenter +├── DashboardTemplate.php ← optional template class +└── default.latte ← template +\-- + +This location proves to be the most convenient in practice - you have all related files readily available. + +Alternatively, you can place templates in a `templates/` subfolder. Nette supports both variants. You can even place templates completely outside the `Presentation/` folder. Everything about template location options can be found in the chapter [Finding Templates |templates#Template Lookup]. + + +Helper Classes and Components +----------------------------- + +Presenters and templates often come with other helper files. We place them logically according to their scope: + +1. **Directly with the presenter** in the case of specific components for that presenter: + +/--pre +Product/ +├── ProductPresenter.php +├── ProductGrid.php ← component for product listing +└── FilterForm.php ← form for filtering +\-- + +2. **For the module** - we recommend using the `Accessory` folder, which is placed conveniently at the beginning alphabetically: + +/--pre +Front/ +├── Accessory/ +│ ├── NavbarControl.php ← components for frontend +│ └── TemplateFilters.php +├── Product/ +└── Cart/ +\-- + +3. **For the entire application** - in `Presentation/Accessory/`: +/--pre +app/Presentation/ +├── Accessory/ +│ ├── LatteExtension.php +│ └── TemplateFilters.php +├── Front/ +└── Admin/ +\-- + +Alternatively, you can place helper classes like `LatteExtension.php` or `TemplateFilters.php` in the infrastructure folder `app/Core/Latte/`. And components in `app/Components`. The choice depends on team conventions. + + +Model - Heart of the Application +================================ + +The model contains all the business logic of the application. The rule for its organization is again - structure by domains: + +/--pre +app/Model/ +├── Payment/ ← everything about payments +│ ├── PaymentFacade.php ← main entry point +│ ├── PaymentRepository.php +│ ├── Payment.php ← entity +├── Order/ ← everything about orders +│ ├── OrderFacade.php +│ ├── OrderRepository.php +│ ├── Order.php +└── Shipping/ ← everything about shipping +\-- + +In the model, you typically encounter these types of classes: + +**Facades**: represent the main entry point into a specific domain within the application. They act as an orchestrator, coordinating cooperation between various services to implement complete use-cases (like "create order" or "process payment"). Beneath its orchestration layer, the facade hides implementation details from the rest of the application, thereby providing a clean interface for working with the given domain. + +```php +class OrderFacade +{ + public function createOrder(Cart $cart): Order + { + // validation + // order creation + // email sending + // writing to statistics + } +} +``` + +**Services**: focus on specific business operations within a domain. Unlike facades, which orchestrate entire use-cases, a service implements specific business logic (like price calculations or payment processing). Services are typically stateless and can be used either by facades as building blocks for more complex operations or directly by other parts of the application for simpler tasks. + +```php +class PricingService +{ + public function calculateTotal(Order $order): Money + { + // price calculation + } +} +``` + +**Repositories**: handle all communication with the data storage, typically a database. Their task is to load and save entities and implement methods for searching them. A repository shields the rest of the application from the implementation details of the database and provides an object-oriented interface for working with data. + +```php +class OrderRepository +{ + public function find(int $id): ?Order + { + } + + public function findByCustomer(int $customerId): array + { + } +} +``` + +**Entities**: objects representing the main business concepts in the application, which have their own identity and change over time. Typically, these are classes mapped to database tables using an ORM (like Nette Database Explorer or Doctrine). Entities can contain business rules related to their data and validation logic. + +```php +// Entity mapped to the 'orders' database table +class Order extends Nette\Database\Table\ActiveRow +{ + public function addItem(Product $product, int $quantity): void + { + $this->related('order_items')->insert([ + 'product_id' => $product->id, + 'quantity' => $quantity, + 'unit_price' => $product->price, + ]); + } +} +``` + +**Value Objects**: immutable objects representing values without their own identity - for example, a monetary amount or an email address. Two instances of a value object with the same values are considered identical. + + +Infrastructure Code +=================== + +The `Core/` folder (or alternatively `Infrastructure/`) is home to the technical foundation of the application. Infrastructure code typically includes: + +/--pre +app/Core/ +├── Router/ ← routing and URL management +│ └── RouterFactory.php +├── Security/ ← authentication and authorization +│ ├── Authenticator.php +│ └── Authorizator.php +├── Logging/ ← logging and monitoring +│ ├── SentryLogger.php +│ └── FileLogger.php +├── Cache/ ← caching layer +│ └── FullPageCache.php +└── Integration/ ← integration with external services + ├── Slack/ + └── Stripe/ +\-- + +For smaller projects, a flat structure is naturally sufficient: + +/--pre +Core/ +├── RouterFactory.php +├── Authenticator.php +└── QueueMailer.php +\-- + +This is code that: + +- Handles technical infrastructure (routing, logging, caching) +- Integrates external services (Sentry, Elasticsearch, Redis) +- Provides basic services for the entire application (mail, database) +- Is mostly independent of a specific domain - cache or logger works the same for an e-shop or a blog. + +Are you wondering whether a certain class belongs here or in the model? The key difference is that code in `Core/`: + +- Knows nothing about the domain (products, orders, articles) +- Can usually be transferred to another project +- Solves "how it works" (how to send an email), not "what it does" (which email to send) + +An example for better understanding: + +- `App\Core\MailerFactory` - creates instances of the class for sending emails, handles SMTP settings +- `App\Model\OrderMailer` - uses `MailerFactory` to send emails about orders, knows their templates and when they should be sent + + +Command Scripts +=============== + +Applications often need to perform activities outside of regular HTTP requests - whether it's background data processing, maintenance, or periodic tasks. Simple scripts in the `bin/` directory are used for execution, while the actual implementation logic is placed in `app/Tasks/` (or `app/Commands/`). + +Example: + +/--pre +app/Tasks/ +├── Maintenance/ ← maintenance scripts +│ ├── CleanupCommand.php ← deleting old data +│ └── DbOptimizeCommand.php ← database optimization +├── Integration/ ← integration with external systems +│ ├── ImportProducts.php ← import from supplier system +│ └── SyncOrders.php ← order synchronization +└── Scheduled/ ← regular tasks + ├── NewsletterCommand.php ← sending newsletters + └── ReminderCommand.php ← customer notifications +\-- + +What belongs in the model and what in command scripts? For example, the logic for sending a single email is part of the model, while the bulk sending of thousands of emails belongs in `Tasks/`. + +Tasks are usually [run from the command line |https://blog.nette.org/en/cli-scripts-in-nette-application] or via cron. They can also be run via an HTTP request, but security must be considered. The presenter that runs the task needs to be secured, for example, only for logged-in users or with a strong token and access from allowed IP addresses. For long-running tasks, it is necessary to increase the script time limit and use `session_write_close()` to avoid locking the session. + + +Other Possible Directories +========================== + +In addition to the mentioned basic directories, you can add other specialized folders according to project needs. Let's look at the most common ones and their use: + +/--pre +app/ +├── Api/ ← API logic independent of the presentation layer +├── Database/ ← migration scripts and seeders for test data +├── Components/ ← shared visual components across the entire application +├── Event/ ← useful if using an event-driven architecture +├── Mail/ ← email templates and related logic +└── Utils/ ← helper classes +\-- + +For shared visual components used in presenters across the application, you can use the `app/Components` or `app/Controls` folder: + +/--pre +app/Components/ +├── Form/ ← shared form components +│ ├── SignInForm.php +│ └── UserForm.php +├── Grid/ ← components for data listings +│ └── DataGrid.php +└── Navigation/ ← navigation elements + ├── Breadcrumbs.php + └── Menu.php +\-- + +This is where components with more complex logic belong. If you want to share components between multiple projects, it is advisable to extract them into a separate Composer package. + +In the `app/Mail` directory, you can place email communication management: + +/--pre +app/Mail/ +├── templates/ ← email templates +│ ├── order-confirmation.latte +│ └── welcome.latte +└── OrderMailer.php +\-- + + +Presenter Mapping +================= + +Mapping defines the rules for deriving the class name from the presenter name. We specify them in the [configuration|configuration] under the key `application › mapping`. + +On this page, we have shown that we place presenters in the `app/Presentation` folder (or `app/UI`). We must inform Nette of this convention in the configuration file. One line is sufficient: + +```neon +application: + mapping: App\Presentation\*\**Presenter +``` + +How does mapping work? For a better understanding, let's first imagine an application without modules. We want the presenter classes to fall under the `App\Presentation` namespace, so that the `Home` presenter maps to the `App\Presentation\HomePresenter` class. This is achieved with this configuration: + +```neon +application: + mapping: App\Presentation\*Presenter +``` + +Mapping works by replacing the asterisk in the mask `App\Presentation\*Presenter` with the presenter name `Home`, resulting in the final class name `App\Presentation\HomePresenter`. Simple! + +However, as you see in the examples in this and other chapters, we place presenter classes in eponymous subdirectories, for example, the `Home` presenter maps to the class `App\Presentation\Home\HomePresenter`. We achieve this by using double asterisks `**` (requires Nette Application 3.2): + +```neon +application: + mapping: App\Presentation\**Presenter +``` + +Now we proceed to mapping presenters into modules. We can define specific mapping for each module: + +```neon +application: + mapping: + Front: App\Presentation\Front\**Presenter + Admin: App\Presentation\Admin\**Presenter + Api: App\Api\*Presenter +``` + +According to this configuration, the presenter `Front:Home` maps to the class `App\Presentation\Front\Home\HomePresenter`, while the presenter `Api:OAuth` maps to the class `App\Api\OAuthPresenter`. + +Since the `Front` and `Admin` modules have a similar mapping pattern, and there will likely be more such modules, it is possible to create a general rule that replaces them. A new asterisk for the module is added to the class mask: + +```neon +application: + mapping: + *: App\Presentation\*\**Presenter + Api: App\Api\*Presenter +``` + +It also works for deeper nested directory structures, such as the presenter `Admin:User:Edit`, where the segment with the asterisk repeats for each module level, resulting in the class `App\Presentation\Admin\User\Edit\EditPresenter`. + +An alternative notation is to use an array consisting of three segments instead of a string. This notation is equivalent to the previous one: + +```neon +application: + mapping: + *: [App\Presentation, *, **Presenter] + Api: [App\Api, '', *Presenter] +``` diff --git a/application/en/how-it-works.texy b/application/en/how-it-works.texy index 569d48065f..69d2cc91b4 100644 --- a/application/en/how-it-works.texy +++ b/application/en/how-it-works.texy @@ -3,10 +3,10 @@ How Do Applications Work?
    -You are currently reading the basic document of the Nette documentation. You will learn all the principle of web applications. Nice from A to Z, from the moment of birth until the last breath of the PHP script. After reading you will know: +You are currently reading the foundational chapter of the Nette documentation. You will learn the complete principles behind how web applications work, from A to Z, from the moment a request is born until the PHP script completes execution. After reading, you will understand: - how it all works -- what is Bootstrap, Presenter and DI container +- what Bootstrap, Presenter, and the DI container are - what the directory structure looks like
    @@ -15,85 +15,87 @@ You are currently reading the basic document of the Nette documentation. You wil Directory Structure =================== -Open a skeleton example of a web application called [WebProject|https://github.com/nette/web-project] and you can watch the files being written about. +Open the example skeleton of a web application called [WebProject|https://github.com/nette/web-project]. As you read, you can refer to the files being discussed. The directory structure looks something like this: /--pre web-project/ -├── app/ ← directory with application -│ ├── Presenters/ ← presenter classes -│ │ ├── HomePresenter.php ← Home presenter class -│ │ └── templates/ ← templates directory -│ │ ├── @layout.latte ← template of shared layout -│ │ └── Home/ ← templates for Home presenter -│ │ └── default.latte ← template for action `default` -│ ├── Router/ ← configuration of URL addresses +├── app/ ← application directory +│ ├── Core/ ← core classes necessary for operation +│ │ └── RouterFactory.php ← URL address configuration +│ ├── Presentation/ ← presenters, templates & co. +│ │ ├── @layout.latte ← layout template +│ │ └── Home/ ← Home presenter directory +│ │ ├── HomePresenter.php ← Home presenter class +│ │ └── default.latte ← template for default action │ └── Bootstrap.php ← booting class Bootstrap -├── bin/ ← scripts for the command line +├── assets/ ← resources (SCSS, TypeScript, source images) +├── bin/ ← scripts executed from the command line ├── config/ ← configuration files │ ├── common.neon -│ └── local.neon -├── log/ ← error logs +│ └── services.neon +├── log/ ← logged errors ├── temp/ ← temporary files, cache, … ├── vendor/ ← libraries installed by Composer │ ├── ... -│ └── autoload.php ← autoloading of libs installed by Composer -├── www/ ← public directory, document root of project -│ ├── .htaccess ← mod_rewrite rules etc +│ └── autoload.php ← autoloading of all installed packages +├── www/ ← public directory, document root of the project +│ ├── assets/ ← compiled static files (CSS, JS, images, ...) +│ ├── .htaccess ← mod_rewrite rules │ └── index.php ← initial file that launches the application └── .htaccess ← prohibits access to all directories except www \-- -You can change the directory structure in any way, rename or move folders, and then just edit the paths to `log/` and `temp/` in the `Bootstrap.php` file and the path to this file in `composer.json` in the `autoload` section. Nothing more, no complicated reconfiguration, no constant changes. Nette has a [smart autodetection|bootstrap#development-vs-production-mode]. +You can change the directory structure in any way, rename or move folders; it is completely flexible. Nette also features smart autodetection and automatically recognizes the application's location, including its URL base. -For slightly larger applications, we can divide folders with presenters and templates into subdirectories (on disk) and into namespaces (in code), which we call [modules]. +For slightly larger applications, we can organize presenter and template folders into [subdirectories |directory-structure#Presenters and Templates] and group classes into namespaces, which we call modules. -The `www/` directory is the public directory or document-root of the project. You can rename it without having to set anything else on the application side. You just need to [configure the hosting |nette:troubleshooting#How to change or remove www directory from URL] so that the document-root goes to this directory. +The `www/` directory represents the public directory or document-root of the project. You can rename it without needing to configure anything else on the application side. You just need to [configure the hosting |nette:troubleshooting#How to Change or Remove www Directory from URL] so that the document-root points to this directory. -You can also download the WebProject directly, including Nette, using [Composer |best-practices:composer]: +You can also download WebProject directly, including Nette, using [Composer |best-practices:composer]: ```shell composer create-project nette/web-project ``` -On Linux or macOS, set the [write permissions |nette:troubleshooting#Setting directory permissions] for directories `log/` and `temp/`. +On Linux or macOS, set [write permissions |nette:troubleshooting#Setting Directory Permissions] for the `log/` and `temp/` directories. -The WebProject application is ready to run, there is no need to configure anything else at all and you can view it directly in the browser by accessing the folder `www/`. +The WebProject application is ready to run; there is no need to configure anything at all, and you can view it directly in the browser by accessing the `www/` folder. HTTP Request ============ -It all begins when a user opens the page in a browser and browser knocks on the server with an HTTP request. The request goes to a PHP file located in the public directory `www/`, which is `index.php`. Let's suppose that this is a request to `https://example.com/product/123`. Thanks to the appropriate [server settings |nette:troubleshooting#How to configure a server for nice URLs?], this URL is also mapped to the `index.php` file and will be executed. +Everything starts when a user opens a page in their browser. The browser sends an HTTP request to the server. This request targets a single PHP file located in the public directory `www/`, which is `index.php`. Let's assume the request is for the address `https://example.com/product/123`. Thanks to appropriate [server configuration |nette:troubleshooting#How to Configure a Server for Nice URLs], even this URL is mapped to the `index.php` file, which is then executed. -Its task is: +Its task is to: 1) initialize the environment -2) get the factory -3) launch the Nette application that handles the request +2) obtain the factory +3) run the Nette application, which handles the request -What kind of factory? We do not produce tractors, but websites! Hold on, it'll be explained right away. +What factory? We're not producing tractors, we're building websites! Hold on, it will be explained shortly. -By "initialize the environment" we mean, for example, that [Tracy |tracy:] is activated, which is an amazing tool for logging or visualizing errors. It logs errors on the production server and displays them directly on the development server. Therefore, initialization also needs to decide whether the site is running in production or developer mode. To do this, Nette uses autodetection: if you run the site on localhost, it runs in developer mode. You don't have to configure anything and the application is ready for both development and production deployment. These steps are performed and described in detail in the chapter about [Bootstrap class |bootstrap]. +By 'environment initialization', we mean, for example, activating [Tracy|tracy:], which is an amazing tool for logging or visualizing errors. On a production server, it logs errors; in a development environment, it displays them directly. Thus, initialization also includes determining whether the site is running in production or development mode. Nette uses [smart autodetection |bootstrapping#Development vs Production Mode] for this: if you run the site on localhost, it operates in development mode. You don't need to configure anything, and the application is immediately ready for both development and live deployment. These steps are performed and described in detail in the chapter about the [Bootstrap class|bootstrapping]. -The third point (yes, we skipped the second, but we will return to it) is to start the application. The handling of HTTP requests in Nette is done by the class `Nette\Application\Application` (hereinafter referred to as the `Application`), so when we say "run an application", we mean to call a method with the name `run()` on an object of this class. +The third point (yes, we skipped the second, but we'll return to it) is launching the application. Handling HTTP requests in Nette is the responsibility of the `Nette\Application\Application` class (hereafter `Application`). So, when we say run the application, we specifically mean calling the aptly named `run()` method on an object of this class. -Nette is a mentor who guides you to write clean applications by proven methodologies. And the most proven is called **dependency injection**, abbreviated DI. At the moment we don't want to burden you with explaining DI, since there is a [separate chapter |dependency-injection:introduction], the important thing here is that the key objects will usually be created by a factory for objects called **DI container** (abbreviated DIC). Yes, this is the factory that was mentioned a while ago. And it also creates the `Application` object for us, so we need a container first. We get it using the `Configurator` class and let it produce `Application` object, call the method `run()` and this starts Nette application. This is exactly what happens in the [index.php |bootstrap#index.php] file. +Nette acts as a mentor, guiding you to write clean applications according to proven methodologies. One of the most established of these is **dependency injection**, abbreviated as DI. We don't want to burden you with explaining DI right now; there's a [dedicated chapter|dependency-injection:introduction] for that. The essential consequence is that key objects are typically created by an object factory known as the **DI container** (or DIC). Yes, this is the factory mentioned earlier. It also produces the `Application` object for us, which is why we need the container first. We obtain it using the `Configurator` class, let it create the `Application` object, call the `run()` method on it, and thus the Nette application starts. This is precisely what happens in the [index.php |bootstrapping#index.php] file. Nette Application ================= -The Application class has a single task: to respond to an HTTP request. +The `Application` class has a single task: to respond to the HTTP request. -Applications written in Nette are divided into many so-called presenters (in other frameworks you may come across the term controller, which is the same), which are classes representing a specific website page: eg homepage; product in e-shop; sign-in form; sitemap feed, etc. The application can have from one to thousands of presenters. +Applications written in Nette are divided into many so-called presenters (you might encounter the term 'controller' in other frameworks, which is essentially the same thing). These are classes, each representing a specific page of the website: e.g., the homepage, a product in an e-shop, a login form, a sitemap feed, etc. An application can have anywhere from one to thousands of presenters. -The application starts by asking the so-called router to decide which of the presenters to pass the current request for processing. The router decides whose responsibility it is. It looks at the input URL `https://example.com/product/123` and, based on how it is set up, decides that this is a job, for example, for **presenter** `Product`, who wants to `show` a product with `id: 123` as an action. It is a good habit to write a pairs of presenter + action separated by a colon as `Product:show`. +The `Application` starts by asking the so-called router to decide which presenter should handle the current request. The router determines the responsibility. It examines the input URL `https://example.com/product/123` and, based on its configuration, decides that this task belongs, for example, to the `Product` **presenter**, which should perform the `show` **action** for the product with `id: 123`. It's good practice to write the presenter + action pair separated by a colon, like `Product:show`. -So the router transformed the URL into a pair `Presenter:action` + parameters, in our case `Product:show` + `id: 123`. You can see how a router looks like in file `app/Router/RouterFactory.php` and we will describe it in detail in chapter [Routing]. +Thus, the router transformed the URL into the pair `Presenter:action` + parameters, in our case `Product:show` + `id: 123`. You can see what such a router looks like in the file `app/Core/RouterFactory.php`, and we describe it in detail in the [Routing |Routing] chapter. -Let's move on. The application already knows the name of the presenter and can continue. By creating an object `ProductPresenter`, which is the code of presenter `Product`. More precisely, it asks the DI container for creating the presenter, because producting objects is its job. +Let's move on. The `Application` now knows the name of the presenter and can proceed. It does this by creating an instance of the `ProductPresenter` class, which contains the code for the `Product` presenter. More precisely, it asks the DI container to create the presenter, because creating objects is its responsibility. The presenter might look like this: @@ -107,98 +109,92 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function renderShow(int $id): void { - // we obtain data from the model and pass it to the template + // obtain data from the model and pass it to the template $this->template->product = $this->repository->getProduct($id); } } ``` -The request is handled by the presenter. And the task is clear: do action `show` with `id: 123`. Which in the language of presenters means that the method `renderShow()` is called and in the parameter `$id` it gets `123`. +The presenter takes over handling the request. The task is clear: execute the `show` action with `id: 123`. In presenter terminology, this means the `renderShow()` method is called, receiving `123` in the `$id` parameter. -A presenter can handle multiple actions, ie have multiple methods `render()`. But we recommend designing presenters with one or as few actions as possible. +A presenter can handle multiple actions, meaning it can have multiple `render()` methods. However, we recommend designing presenters with one or as few actions as possible. -So, the method `renderShow(123)` was called, whose code is fictional example, but you can see on it how the data is passed to the template, ie by writing to `$this->template`. +So, the `renderShow(123)` method was called. Its code is a fictional example, but it demonstrates how data is passed to the template, specifically by writing to `$this->template`. -Subsequently, the presenter returns the answer. This can be an HTML page, an image, an XML document, sending a file from disk, JSON or redirecting to another page. Importantly, if we do not explicitly say how to respond (which is the case of `ProductPresenter`), the answer will be to render the template with an HTML page. Why? Well, because in 99% of cases we want to draw a template, so the presenter takes this behavior as the default and wants to make our work easier. That's Nette's point. +Subsequently, the presenter returns a response. This could be an HTML page, an image, an XML document, sending a file from the disk, JSON, or perhaps a redirect to another page. Importantly, if we don't explicitly specify how to respond (which is the case with `ProductPresenter`), the response will be to render a template into an HTML page. Why? Because in 99% of cases, we want to render a template. Therefore, the presenter adopts this behavior as the default to simplify our work. That's the essence of Nette. -We don't even have to state which template to draw, he derives the path to it according to simple logic. In the case of presenter `Product` and action `show`, it tries to see if one of these template files exists relative to the directory where class `ProductPresenter` is located: +We don't even need to specify which template to render; the framework deduces the path automatically. In the case of the `show` action, it simply attempts to load the `show.latte` template located in the same directory as the `ProductPresenter` class. It also tries to find the layout in the `@layout.latte` file (more details on [template lookup |templates#Template Lookup]). -- `templates/Product/show.latte` -- `templates/Product.show.latte` - -It will also try to find the layout in file `@layout.latte` and then it renders the template. Now the task of the presenter and the entire application is completed. If the template does not exist, a page with error 404 will be returned. You can read more about presenters on the [Presenters] page. +Then, the templates are rendered. This completes the task of the presenter and the entire application. If the template doesn't exist, a 404 error page is returned. You can learn more about presenters on the [Presenters|presenters] page. [* request-flow.svg *] -Just to be sure, let's try to recap the whole process with a slightly different URL: +To be sure, let's recap the entire process with a slightly different URL: -1) the URL will be `https://example.com` -2) we boot the application, create a container and run `Application::run()` -3) the router decodes the URL as a pair `Home:default` -4) an `HomePresenter` object is created -5) method `renderDefault()` is called (if exists) -6) a template `templates/Home/default.latte` with a layout `templates/@layout.latte` is rendered +1) The URL is `https://example.com` +2) The application boots, the DI container is created, and `Application::run()` is executed. +3) The router decodes the URL into the pair `Home:default`. +4) An instance of the `HomePresenter` class is created. +5) The `renderDefault()` method is called (if it exists). +6) The template, e.g., `default.latte`, is rendered along with the layout, e.g., `@layout.latte`. -You may have come across a lot of new concepts now, but we believe they make sense. Creating applications in Nette is a breeze. +You might have encountered many new concepts just now, but we believe they make sense. Developing applications in Nette is remarkably straightforward. Templates ========= -When it comes to the templates, Nette uses the [Latte |latte:] template system. That's why the files with templates ends with `.latte`. Latte is used because it is the most secure template system for PHP, and at the same time the most intuitive system. You don't have to learn much new, you just need to know PHP and a few Latte tags. You will find out everything [in the documentation |latte:]. +Speaking of templates, Nette uses the [Latte |latte:] templating system. That's why template files have the `.latte` extension. Latte is used primarily because it's the most secure templating system for PHP, and also the most intuitive. You don't need to learn much new; knowledge of PHP and a few tags is sufficient. You'll find everything you need [in the documentation |templates]. -In template we [create a links |creating-links] to other presenters & actions as follows: +In the template, you [create links |creating-links] to other presenters & actions like this: ```latte product detail ``` -Simply write the familiar `Presenter:action` pair instead of the real URL and include any parameters. The trick is `n:href`, which says that this attribute will be processed by Nette. And it will generate: +Simply write the familiar `Presenter:action` pair instead of the actual URL and include any necessary parameters. The trick lies in `n:href`, which tells Nette to process this attribute. It will then generate: ```latte product detail ``` -The previously mentioned router is in charge of generating the URL. In fact, routers in Nette are unique in that they can perform not only transformations from a URL to a pair of presenter:action, but also vice versa generate a URL from the name of the presenter + action + parameters. -Thanks to this, in Nette you can completely change the form of the URL in the whole finished application without changing a single character in the template or presenter just by modifying the router. -And thanks to this, the so-called canonization works, which is another unique feature of Nette, which improves SEO (optimization of searchability on the internet) by automatically preventing the existence of duplicate content at different URLs. -Many programmers find this amazing. +URL generation is handled by the aforementioned router. Routers in Nette are exceptional because they can perform not only the transformation from a URL to a `Presenter:action` pair but also the reverse: generating a URL from the presenter name, action, and parameters. Thanks to this, you can completely change the URL format throughout your entire finished application in Nette without altering a single character in the templates or presenters—simply by modifying the router. This also enables so-called canonization, another unique Nette feature that enhances SEO (Search Engine Optimization) by automatically preventing duplicate content from existing on different URLs. Many programmers find this capability astounding. Interactive Components ====================== -We have one more thing to tell you about presenters: they have a built-in component system. Older of you may remember something similar from Delphi or ASP.NET Web Forms. React or Vue.js is built on something remotely similar. In the world of PHP frameworks, this is a completely unique feature. +We need to tell you one more thing about presenters: they have a built-in component system. Those with more experience might recall something similar from Delphi or ASP.NET Web Forms; React or Vue.js are built on somewhat related concepts. In the world of PHP frameworks, this is a completely unique feature. -Components are separate reusable units that we place into pages (ie presenters). They can be [forms|forms:in-presenter], [datagrids |https://componette.org/contributte/datagrid/], menus, polls, in fact anything that makes sense to use repeatedly. We can create our own components or use some of the [huge range |https://componette.org] of opensource components. +Components are independent, reusable units that we embed into pages (i.e., presenters). These can be [forms |forms:in-presenter], [datagrids |https://componette.org/contributte/datagrid/], menus, polls—essentially anything that makes sense to reuse. We can create our own components or utilize some from the [vast selection |https://componette.org] of open-source components. -Components fundamentally change the approach to application development. They will open up new possibilities for composing pages from pre-defined units. And they have something in common with [Hollywood|components#Hollywood style]. +Components fundamentally influence the approach to application development. They open up new possibilities for composing pages from pre-prepared units. And they also have something in common with [Hollywood |components#Hollywood Style]. DI Container and Configuration ============================== -DI container (factory for objects) is the heart of the whole application. +The DI container, or object factory, is the heart of the entire application. -Don't worry, it's not a magical black box, as it might seem from the previous words. Actually, it's one pretty boring PHP class generated by Nette and stored in a cache directory. It has a lot of methods named as `createServiceAbcd()` and each of them creates and returns an object. Yes, there is also a method `createServiceApplication()` that will produce `Nette\Application\Application`, which we needed in the file `index.php` to run the application. And there are methods for producing individual presenters. And so on. +Don't worry, it's not some magical black box, as the preceding lines might suggest. In reality, it's a rather mundane PHP class generated by Nette and stored in the cache directory. It contains many methods named like `createServiceAbcd()`, each capable of creating and returning a specific object. Yes, there's also a `createServiceApplication()` method that produces the `Nette\Application\Application` instance we needed in `index.php` to run the application. There are also methods for creating individual presenters, and so on. -The objects that the DI container creates are called services for some reason. +The objects created by the DI container are, for some reason, called services. -What is really special about this class is that it is not programmed by you, but by the framework. It actually generates the PHP code and saves it to disk. You just give instructions on what objects the container should be able to produce and how exactly. And these instructions are written in [configuration files |bootstrap#DI Container Configuration] in the [NEON format|neon:format] and therefore have the extension `.neon`. +What's truly special about this class is that you don't program it—the framework does. It actually generates the PHP code and saves it to disk. You simply provide instructions on which objects the container should be able to create and how exactly. These instructions are written in [configuration files |bootstrapping#DI Container Configuration], which use the [NEON|neon:format] format and thus have the `.neon` extension. -The configuration files are used purely to instruct the DI container. So, for example, if I specify the `expiration: 14 days` option in the [session|http:configuration#Session] section, the DI container when creating the `Nette\Http\Session` object representing the session will call its method `setExpiration('14 days')`, and thus configuration becomes a reality. +Configuration files serve purely to instruct the DI container. So, for example, if you specify the `expiration: 14 days` option in the [session |http:configuration#Session] section, the DI container, when creating the `Nette\Http\Session` object representing the session, will call its `setExpiration('14 days')` method, thereby making the configuration a reality. -There is a whole chapter ready for you, describing what can be [configured |nette:configuring] and how to [define your own services |dependency-injection:services]. +There's an entire chapter prepared for you describing what can be [configured |nette:configuring] and how to [define your own services |dependency-injection:services]. -Once you get into the creation of services, you will come across the word [autowiring |dependency-injection:autowiring]. This is a gadget that will make your life incredibly easier. It can automatically pass objects where you need them (in the constructors of your classes, for example) without having to do anything. You will find that the DI container in Nette is a small miracle. +Once you delve a bit into service creation, you'll encounter the term [autowiring |dependency-injection:autowiring]. This is a feature that will simplify your life incredibly. It can automatically pass objects where you need them (for example, in the constructors of your classes) without you having to do anything. You'll discover that the DI container in Nette is a small miracle. What Next? ========== -We went through the basic principles of applications in Nette. So far, very superficially, but you will soon delve into the depths and eventually create wonderful web applications. Where to continue? Have you tried the tutorial [Create Your First Application |quickstart:]? +We've covered the fundamental principles of Nette applications. While it's been a surface-level overview so far, you'll soon delve deeper and, in time, create amazing web applications. Where to go next? Have you tried the [Create Your First Application|quickstart:] tutorial yet? -In addition to the above, Nette has a whole arsenal of [useful classes |utils:], [database layer |database:], etc. Try purposely just click through documentation. Or visit [blog |https://blog.nette.org]. You will discover a lot of interesting things. +In addition to what's described above, Nette offers a whole arsenal of [useful classes|utils:], a [database layer|database:], etc. Try clicking through the documentation. Or visit the [blog|https://blog.nette.org]. You'll discover many interesting things. -Let the framework bring you a lot of joy 💙 +May the framework bring you much joy 💙 diff --git a/application/en/modules.texy b/application/en/modules.texy deleted file mode 100644 index 6312143fdd..0000000000 --- a/application/en/modules.texy +++ /dev/null @@ -1,148 +0,0 @@ -Modules -******* - -.[perex] -In Nette, modules represent the logical units that make up an application. They include presenters, templates, possibly also components and model classes. - -One directory for presenters and one for templates would not be enough for real projects. Having dozens of files in one folder is at least unorganized. How to get out of it? We simply split them into subdirectories on disk and into namespaces in the code. And that's exactly what the Nette modules do. - -So let's forget about a single folder for presenters and templates and instead create modules, for example `Admin` and `Front`. - -/--pre -app/ -├── Presenters/ -├── Modules/ ← directory with modules -│ ├── Admin/ ← module Admin -│ │ ├── Presenters/ ← its presenters -│ │ │ ├── DashboardPresenter.php -│ │ │ └── templates/ -│ └── Front/ ← module Front -│ └── Presenters/ ← its presenters -│ └── ... -\-- - -This directory structure will be reflected by the class namespaces, so for example `DashboardPresenter` will be in the `App\Modules\Admin\Presenters` namespace: - -```php -namespace App\Modules\Admin\Presenters; - -class DashboardPresenter extends Nette\Application\UI\Presenter -{ - // ... -} -``` - -The `Dashboard` presenter inside the `Admin` module is referenced within the application using the colon notation as `Admin:Dashboard`, and its `default` action as `Admin:Dashboard:default`. -And how does Nette proper know that `Admin:Dashboard` represents the `App\Modules\Admin\Presenters\DashboardPresenter` class? This is determined by [#mapping] in the configuration. -Thus, the given structure is not hard set and you can modify it according to your needs. - -Modules can of course contain all other items besides presenters and templates, such as components, model classes, etc. - - -Nested Modules --------------- - -Modules don't have to form only a flat structure, you can also create submodules, for example: - -/--pre -app/ -├── Modules/ ← directory with modules -│ ├── Blog/ ← module Blog -│ │ ├── Admin/ ← submodule Admin -│ │ │ ├── Presenters/ -│ │ │ └── ... -│ │ └── Front/ ← submodule Front -│ │ ├── Presenters/ -│ │ └── ... -│ ├── Forum/ ← module Forum -│ │ └── ... -\-- - -Thus, the `Blog` module is divided into `Admin` and `Front` submodules. Again, this will be reflected in the namespaces, which will be `App\Modules\Blog\Admin\Presenters` etc. The presenter `Dashboard` inside the submodule is referred to as `Blog:Admin:Dashboard`. - -The nesting can go as deep as you like, so sub-submodules can be created. - - -Creating Links --------------- - -Links in presenter templates are relative to the current module. Thus, the link `Foo:default` leads to the presenter `Foo` in the same module as the current presenter. If the current module is `Front`, for example, then the link goes like this: - -```latte -link to Front:Product:show -``` - -A link is relative even if it includes the name of a module, which is then considered a submodule: - -```latte -link to Front:Shop:Product:show -``` - -Absolute links are written analogously to absolute paths on disk, but with colons instead of slashes. Thus, an absolute link starts with a colon: - -```latte -link to Admin:Product:show -``` - -To find out if we are in a certain module or its submodule we can use `isModuleCurrent(moduleName)` function. - -```latte -
  • - ... -
  • -``` - - -Routing -------- - -See [chapter on routing |routing#Modules]. - - -Mapping -------- - -Defines the rules by which the class name is derived from the presenter name. We write them in [configuration] under the `application › mapping` key. - -Let's start with a sample that doesn't use modules. We'll just want the presenter classes to have the `App\Presenters` namespace. That means that a presenter such as `Home` should map to the `App\Presenters\HomePresenter` class. This can be achieved by the following configuration: - -```neon -application: - mapping: - *: App\Presenters\*Presenter -``` - -The presenter name is replaced with the asterisk in the class mask and the result is the class name. Easy! - -If we divide presenters into modules, we can have our own mapping for each module: - -```neon -application: - mapping: - Front: App\Modules\Front\Presenters\*Presenter - Admin: App\Modules\Admin\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -Now presenter `Front:Home` maps to class `App\Modules\Front\Presenters\HomePresenter` and presenter `Admin:Dashboard` to class `App\Modules\Admin\Presenters\DashboardPresenter`. - -It is more practical to create a general (star) rule to replace the first two. The extra asterisk will be added to the class mask just for the module: - -```neon -application: - mapping: - *: App\Modules\*\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -But what if we use nested modules and have a presenter `Admin:User:Edit`? In this case, the segment with an asterisk representing the module for each level is simply repeated and the result is class `App\Modules\Admin\User\Presenters\EditPresenter`. - -An alternative notation is to use an array consisting of three segments instead of a string. This notation is equivalent to the previous one: - -```neon -application: - mapping: - *: [App\Modules, *, Presenters\*Presenter] -``` - -The default value is `*: *Module\*Presenter`. diff --git a/application/en/multiplier.texy b/application/en/multiplier.texy index 2ca1b92466..2fc8d760c4 100644 --- a/application/en/multiplier.texy +++ b/application/en/multiplier.texy @@ -1,15 +1,17 @@ Multiplier: Dynamic Components ****************************** -A tool for dynamical creation of interactive components .[perex] +.[perex] +A tool for dynamic creation of interactive components. -Let's start with a typical problem: we have a list of products on an e-commerce site and we want to accompany each product with an *add to cart* form. One way is to wrap the whole listing in a single form. A more convenient way is to use [api:Nette\Application\UI\Multiplier]. +Let's start with a typical example: imagine a product list in an e-shop where you want an 'Add to Cart' form for each item. One possible approach is to wrap the entire listing in a single form. However, a much more convenient method is offered by [api:Nette\Application\UI\Multiplier]. -Multiplier allows you to define a factory for multiple components. It is based on the principle of nested components - each component inheriting from [api:Nette\ComponentModel\Container] may contain other components. +Multiplier allows you to conveniently define a factory for multiple components. It works on the principle of nested components – any component inheriting from [api:Nette\ComponentModel\Container] can contain other components. -See [component model|components#Components in Depth] in the documentation. .[tip] +.[tip] +See the chapter on the [component model |components#Components in Depth] in the documentation. -Multiplier poses as a parent component which can dynamically create its children using the callback passed in the constructor. See example: +The essence of Multiplier is that it acts as a parent that can dynamically create its children using a callback passed in the constructor. See the example: ```php protected function createComponentShopForm(): Multiplier @@ -24,7 +26,7 @@ protected function createComponentShopForm(): Multiplier } ``` -In the template we can render a form for each product - and each form will indeed be a unique component. +Now, in the template, we can simply render the form for each product – and each one will truly be a unique component. ```latte {foreach $items as $item} @@ -35,16 +37,16 @@ In the template we can render a form for each product - and each form will indee {/foreach} ``` -Argument passed to `{control}` tag says: +The argument passed in the `{control}` tag follows a format that means: -1. get a component `shopForm` -2. and return its child `$item->id` +1. Get the component `shopForm`. +2. From it, get the child named `$item->id`. -During the first call of **1.** the `shopForm` component does not yet exist, so the method `createComponentShopForm` is called to create it. An anonymous function passed as a parameter to Multiplier, is then called and a form is created. +During the first call of point **1**, the `shopForm` component doesn't exist yet, so its factory `createComponentShopForm` is called. Then, on the obtained component (an instance of Multiplier), the factory for the specific form is called – which is the anonymous function we passed to the Multiplier's constructor. -In the subsequent iterations of the `foreach` the method `createComponentShopForm` is no longer called because the component already exists. But since we reference another child (`$item->id` varies between iterations), an anonymous function is called again and a new form is created. +In the next iteration of the foreach loop, the `createComponentShopForm` method will not be called again (as the component already exists). However, because we are looking for a different child (since `$item->id` will be different in each iteration), the anonymous function will be called again, returning a new form. -The last thing is to ensure that the form actually adds the correct product to the cart because in the current state all the forms are equal and we cannot distinguish to which products they belong. For this we can use the property of Multiplier (and in general of any component factory method in Nette Framework) that every component factory method receives the name of the created component as the first argument. In our case that would be `$item->id`, which is exactly what we need to distinguish individual products. All you need to do is modify the code for creating the form: +The only thing left is to ensure that the form adds the correct product to the cart – currently, the form is identical for every product. A feature of Multiplier (and generally of any component factory in Nette Framework) helps us here: every factory receives the name of the component being created as its first argument. In our case, this will be `$item->id`, which is precisely the information we need. So, we just need to slightly modify the form creation: ```php protected function createComponentShopForm(): Multiplier diff --git a/application/en/presenters.texy b/application/en/presenters.texy index c669c174bb..7658686bbf 100644 --- a/application/en/presenters.texy +++ b/application/en/presenters.texy @@ -3,37 +3,37 @@ Presenters
    -We will learn how to write presenters and templates in Nette. After reading you will know: +We will explore how presenters and templates are written in Nette. After reading, you will understand: -- how the presenter works -- what are persistent parameters -- how to render a template +- how presenters work +- what persistent parameters are +- how templates are rendered
    -[We already know |how-it-works#nette-application] that a presenter is a class that represents a specific page of a web application, such as a homepage; product in e-shop; sign-in form; sitemap feed, etc. The application can have from one to thousands of presenters. In other frameworks, they are also known as controllers. +[We already know |how-it-works#Nette Application] that a presenter is a class representing a specific page of a web application, such as the homepage, a product in an e-shop, a login form, a sitemap feed, etc. An application can have anywhere from one to thousands of presenters. In other frameworks, they are also known as controllers. -Usually, the term presenter refers to a descendant of the class [api:Nette\Application\UI\Presenter], which is suitable for web interfaces and which we will discuss in the rest of this chapter. In a general sense, a presenter is any object that implements the [api:Nette\Application\IPresenter] interface. +Usually, the term presenter refers to a descendant of the [api:Nette\Application\UI\Presenter] class, which is suitable for generating web interfaces and will be the focus of the rest of this chapter. In a general sense, a presenter is any object implementing the [api:Nette\Application\IPresenter] interface. -Life Cycle of Presenter -======================= +Presenter Life Cycle +==================== -The job of the presenter is to process the request and return a response (which can be an HTML page, image, redirect, etc.). +The presenter's task is to handle a request and return a response (which could be an HTML page, an image, a redirect, etc.). -So at the beginning is a request. It is not directly an HTTP request, but an [api:Nette\Application\Request] object into which the HTTP request was transformed using a router. We usually do not come into contact with this object, because the presenter cleverly delegates the processing of the request to special methods, which we will now see. +So, initially, a request is passed to it. This isn't the direct HTTP request, but a [api:Nette\Application\Request] object, into which the HTTP request was transformed with the help of the router. We usually don't interact directly with this object, as the presenter cleverly delegates request processing to other methods, which we will now explore. -[* lifecycle.svg *] *** *Life cycle of presenter* .<> +[* lifecycle.svg *] *** Presenter Life Cycle .<> -The figure shows a list of methods that are called sequentially from top to bottom, if they exist. None of them need to exist, we can have a completely empty presenter without a single method and build a simple static web on it. +The diagram shows a list of methods that are called sequentially from top to bottom, if they exist. None of them are mandatory; you can have a completely empty presenter without a single method and build a simple static website upon it. `__construct()` --------------- -The constructor does not belong exactly to the life cycle of the presenter, because it is called at the moment of creating the object. But we mention it because of its importance. The constructor (together with [method inject|best-practices:inject-method-attribute]) is used to pass dependencies. +The constructor doesn't strictly belong to the presenter's life cycle, as it's called at the moment the object is created. However, we mention it due to its importance. The constructor (along with the [inject method|best-practices:inject-method-attribute]) is used for passing dependencies. -The presenter should not take care of the business logic of the application, write and read from the database, perform calculations, etc. This is the task for classes from a layer, which we call a model. For example, class `ArticleRepository` may be responsible for loading and saving articles. In order for the presenter to use it, it is [passed using dependency injection |dependency-injection:passing-dependencies]: +The presenter should not handle the application's business logic, write to or read from the database, perform calculations, etc. That's the responsibility of classes in the layer we call the model. For example, an `ArticleRepository` class might be responsible for loading and saving articles. For the presenter to work with it, it needs to have it [passed via dependency injection |dependency-injection:passing-dependencies]: ```php @@ -50,44 +50,44 @@ class ArticlePresenter extends Nette\Application\UI\Presenter `startup()` ----------- -Immediately after receiving the request, method `startup ()` is invoked. You can use it to initialize properties, check user privileges, etc. It is required to always call the `parent::startup()` ancestor. +Immediately after receiving the request, the `startup()` method is invoked. You can use it to initialize properties, check user permissions, etc. It is required that this method always calls its parent: `parent::startup()`. `action(args...)` .{toc: action()} -------------------------------------------------- -Similar to the method `render()`. While `render()` is intended to prepare data for a specific template, which is subsequently rendered, in `action()` a request is processed without following-up template rendering. For example, data is processed, a user is logged in or out, and so on, and then it [redirects elsewhere |#Redirection]. +Similar to the `render()` method. While `render()` is intended to prepare data for a specific template that will subsequently be rendered, `action()` processes a request without necessarily rendering a template afterwards. For example, it might process data, log a user in or out, and so on, and then [redirect elsewhere |#Redirection]. -It is important that `action()` is called before `render()`, so inside it we can possibly change the next course of life cycle, i.e. change the template that will be rendered and also the method `render()` that will be called, using `setView('otherView')`. +It's important that `action()` is called *before* `render()`. This allows us to potentially change the course of the request within the action method, for instance, by changing the template that will be rendered or even the `render()` method that will be called, using `setView('otherView')`. -The parameters from the request are passed to the method. It is possible and recommended to specify types for the parameters, e.g. `actionShow(int $id, string $slug = null)` - if parameter `id` is missing or if it is not an integer, the presenter returns [error 404|#Error 404 etc.] and terminates the operation. +Parameters from the request are passed to the method. It's possible and recommended to specify types for these parameters, e.g., `actionShow(int $id, ?string $slug = null)`. If the `id` parameter is missing or is not an integer, the presenter returns a [404 error |#Error 404 etc] and terminates. `handle(args...)` .{toc: handle()} -------------------------------------------------- -This method processes the so-called signals, which we will discuss in the chapter about [Components |components#Signal]. It is intended mainly for components and processing of AJAX requests. +This method processes so-called signals, which we'll learn about in the chapter dedicated to [components |components#Signal]. It's primarily intended for components and handling AJAX requests. -The parameters are passed to the method, as in the case of `action()`, including type checking. +Parameters from the request are passed to the method, just like with `action()`, including type checking. `beforeRender()` ---------------- -Method `beforeRender`, as the name suggests, is called before each method `render()`. Is used for common template configuration, passing variables for layout and so on. +The `beforeRender` method, as its name suggests, is called before every `render()` method. It's used for common template configuration, passing variables to the layout, and similar tasks. `render(args...)` .{toc: render()} ---------------------------------------------- -The place where we prepare the template for subsequent rendering, we pass data to it, etc. +This is where we prepare the template for subsequent rendering, pass data to it, etc. -The parameters are passed to the method, as in the case of `action()`, including type checking. +Parameters from the request are passed to the method, just like with `action()`, including type checking. ```php public function renderShow(int $id): void { - // we obtain data from the model and pass it to the template + // obtain data from the model and pass it to the template $this->template->article = $this->articles->getById($id); } ``` @@ -96,39 +96,41 @@ public function renderShow(int $id): void `afterRender()` --------------- -Method `afterRender`, as the name suggests again, is called after each `render()` method. It is used rather rarely. +The `afterRender` method, as the name suggests again, is called after every `render()` method. It's used rather rarely. `shutdown()` ------------ -It is called at the end of the presenter's life cycle. +Called at the end of the presenter's life cycle. -**Good advice before we move on**. As you can see, the presenter can handle more actions/views, i.e. have more methods `render()`. But we recommend designing presenters with one or as few actions as possible. +**A piece of advice before we continue:** As you can see, a presenter can handle multiple actions/views, meaning it can have multiple `render()` methods. However, we recommend designing presenters with one or as few actions as possible. Sending a Response ================== -The presenter's response is usually [rendering the template with the HTML page|templates], but it can also be sending a file, JSON or even redirecting to another page. +The presenter's response is typically [rendering a template into an HTML page|templates], but it can also be sending a file, JSON, or even redirecting to another page. -At any time during the lifecycle, you can use any of the following methods to send a response and exit the presenter at the same time: +At any point during the life cycle, we can use one of the following methods to send a response and simultaneously terminate the presenter: -- `redirect()`, `redirectPermanent()`, `redirectUrl()` and `forward()` [redirects |#Redirection] -- `error()` quits presenter [due to error |#Error 404 etc.] -- `sendJson($data)` quits presenter and [sends the data |#Sending JSON] in JSON format -- `sendTemplate()` quits presenter and immediately [renderes the template |templates] -- `sendResponse($response)` quits presenter and sends [own response |#Responses] -- `terminate()` quits presenter without answer +- `redirect()`, `redirectPermanent()`, `redirectUrl()`, and `forward()` perform a [redirect |#Redirection] +- `error()` terminates the presenter [due to an error |#Error 404 etc] +- `sendJson($data)` terminates the presenter and [sends data |#Sending JSON] in JSON format +- `sendTemplate()` terminates the presenter and immediately [renders the template |templates] +- `sendResponse($response)` terminates the presenter and sends a [custom response |#Responses] +- `terminate()` terminates the presenter without a response -If you do not call any of these methods, the presenter will automatically proceed to render the template. Why? Well, because in 99% of cases we want to draw a template, so the presenter takes this behavior as the default and wants to make our work easier. +Each of these methods immediately terminates the presenter by throwing a silent termination exception `Nette\Application\AbortException`. + +If you don't call any of these methods, the presenter automatically proceeds to render the template. Why? Because in 99% of cases, we want to render a template, so the presenter adopts this behavior as the default to simplify our work. Creating Links ============== -Presenter has a method `link()`, which is used to create URL links to other presenters. The first parameter is the target presenter & action, followed by the arguments, which can be passed as array: +The presenter has a `link()` method used to create URL links to other presenters. The first parameter is the target presenter & action, followed by arguments, which can be passed as an array: ```php $url = $this->link('Product:show', $id); @@ -136,64 +138,64 @@ $url = $this->link('Product:show', $id); $url = $this->link('Product:show', [$id, 'lang' => 'en']); ``` -In template we create links to other presenters & actions as follows: +In the template, links to other presenters & actions are created like this: ```latte product detail ``` -Simply write the familiar `Presenter:action` pair instead of the real URL and include any parameters. The trick is `n:href`, which says that this attribute will be processed by Latte and generates a real URL. In Nette, you don't have to think about URLs at all, just about presenters and actions. +Simply write the familiar `Presenter:action` pair instead of the actual URL and include any necessary parameters. The trick lies in `n:href`, which tells Latte to process this attribute and generate the real URL. In Nette, you don't need to think about URLs at all, just about presenters and actions. -For more information, see [Creating Links]. +You can find more information in the chapter [Creating URL Links|creating-links]. Redirection =========== -Methods `redirect()` and `forward()` are used to jump to another presenter, which have a very similar syntax as the method [link() |#Creating Links]. +The `redirect()` and `forward()` methods are used to switch to another presenter. They have a very similar syntax to the [link() |#Creating Links] method. -The `forward()` switches to the new presenter immediately without HTTP redirection: +The `forward()` method switches to the new presenter immediately without an HTTP redirect: ```php $this->forward('Product:show'); ``` -Example of temporary redirection with HTTP code 302 or 303: +Example of a temporary redirect with HTTP code 302 (or 303 if the current request method is POST): ```php $this->redirect('Product:show', $id); ``` -To achieve permanent redirection with HTTP code 301 use: +To achieve a permanent redirect with HTTP code 301, use this: ```php $this->redirectPermanent('Product:show', $id); ``` -You can redirect to another URL outside the application with the `redirectUrl()` method: +You can redirect to another URL outside the application using the `redirectUrl()` method. The HTTP code can be specified as the second parameter; the default is 302 (or 303 if the current request method is POST): ```php $this->redirectUrl('https://nette.org'); ``` -Redirection immediately terminates the presenter's life cycle by throwing the so-called silent termination exception `Nette\Application\AbortException`. +Redirection immediately terminates the presenter's activity by throwing the so-called silent termination exception, `Nette\Application\AbortException`. -Before redirection, it is possible to send a [flash message |#Flash Messages], messages that will be displayed in the template after redirection. +Before redirection, it's possible to send [#flash messages], i.e., messages that will be displayed in the template after redirection. Flash Messages ============== -These are messages that usually inform about the result of an operation. An important feature of flash messages is that they are available in the template even after redirection. Even after being displayed, they will remain alive for another 30 seconds - for example, in case the user would unintentionally refresh the page - the message will not be lost. +These are messages typically informing about the result of some operation. An important feature of flash messages is that they remain available in the template even after redirection. Once displayed, they stay active for an additional 30 seconds – for instance, if the user refreshes the page due to a transmission error, the message won't disappear immediately. -Just call the [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] method and presenter will take care of passing the message to the template. The first argument is the text of the message and the second optional argument is its type (error, warning, info etc.). The method `flashMessage()` returns an instance of flash message, to allow us to add more information. +Simply call the [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] method, and the presenter handles passing it to the template. The first parameter is the message text, and the optional second parameter is its type (e.g., error, warning, info). The `flashMessage()` method returns an instance of the flash message, allowing additional information to be added. ```php -$this->flashMessage('Item was removed.'); -$this->redirect(/* ... */); +$this->flashMessage('The item has been deleted.'); +$this->redirect(/* ... */); // and redirect ``` -In the template, these messages are available in the variable `$flashes` as objects `stdClass`, which contain the properties `message` (message text), `type` (message type) and can contain the already mentioned user information. We draw them as follows: +In the template, these messages are available in the `$flashes` variable as `stdClass` objects containing the properties `message` (the message text), `type` (the message type), and potentially the user-added information mentioned earlier. We render them like this: ```latte {foreach $flashes as $flash} @@ -205,7 +207,7 @@ In the template, these messages are available in the variable `$flashes` as obje Error 404 etc. ============== -When we can't fulfill the request because for example the article we want to display does not exist in the database, we will throw out the 404 error using method `error(string $message = null, int $httpCode = 404)`, which represents HTTP error 404: +If the request cannot be fulfilled, for example, because the article we want to display doesn't exist in the database, we throw a 404 error using the `error(?string $message = null, int $httpCode = 404)` method. ```php public function renderShow(int $id): void @@ -218,14 +220,13 @@ public function renderShow(int $id): void } ``` -The HTTP error code can be passed as the second parameter, the default is 404. The method works by throwing exception `Nette\Application\BadRequestException`, after which `Application` passes control to the error-presenter. Which is a presenter whose job is to display a page informing about the error. -The error-preseter is set in [application configuration |configuration]. +The HTTP error code can be passed as the second parameter; the default is 404. The method works by throwing a `Nette\Application\BadRequestException`, after which the `Application` passes control to the error presenter. This is a presenter whose task is to display a page informing about the error that occurred. The error presenter is configured in the [application configuration|configuration]. Sending JSON ============ -Example of action-method that sends data in JSON format and exits the presenter: +Example of an action method that sends data in JSON format and terminates the presenter: ```php public function actionData(): void @@ -236,17 +237,43 @@ public function actionData(): void ``` +Request Parameters .{data-version:3.1.14} +========================================= + +The presenter, and also each component, obtains its parameters from the HTTP request. You can retrieve their values using the `getParameter($name)` or `getParameters()` methods. The values are strings or arrays of strings, essentially raw data obtained directly from the URL. + +For greater convenience, we recommend accessing parameters via properties. Simply mark them with the `#[Parameter]` attribute: + +```php +use Nette\Application\Attributes\Parameter; // this line is important + +class HomePresenter extends Nette\Application\UI\Presenter +{ + #[Parameter] + public string $theme; // must be public +} +``` + +For the property, we recommend specifying the data type (e.g., `string`), and Nette will automatically cast the value accordingly. Parameter values can also be [validated |#Validation of Parameters]. + +When creating a link, you can set the parameter's value directly: + +```latte +click +``` + + Persistent Parameters ===================== -Persistent parameters are used to maintain state between different requests. Their value remains the same even after a link is clicked. Unlike session data, they are passed in the URL. This is completely automatic, so there is no need to explicitly state them in `link()` or `n:href`. +Persistent parameters are used to maintain state across different requests. Their value remains the same even after clicking a link. Unlike session data, they are transmitted in the URL. And this happens completely automatically, so there's no need to explicitly include them in `link()` or `n:href`. -Example of use? You have a multilingual application. The actual language is a parameter that needs to be part of the URL at all times. But it would be incredibly tedious to include it in every link. So you make it a persistent parameter named `lang` and it will carry itself. Cool! +An example use case? Imagine you have a multilingual application. The current language is a parameter that must always be part of the URL. But it would be incredibly tedious to include it in every link. So, you make it a persistent parameter `lang`, and it will be carried along automatically. Neat! -Creating a persistent parameter is extremely easy in Nette. Just create a public property and tag it with the attribute: (previously `/** @persistent */` was used) +Creating a persistent parameter in Nette is extremely simple. Just create a public property and mark it with the attribute: (previously, `/** @persistent */` was used) ```php -use Nette\Application\Attributes\Persistent; // this line is important +use Nette\Application\Attributes\Persistent; // this line is important class ProductPresenter extends Nette\Application\UI\Presenter { @@ -255,14 +282,14 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -If `$this->lang` has a value such as `'en'`, then links created using `link()` or `n:href` will also contain the `lang=en` parameter. And when the link is clicked, it will again be `$this->lang = 'en'`. +If `$this->lang` has a value like `'en'`, then links created using `link()` or `n:href` will also contain the parameter `lang=en`. And after clicking the link, `$this->lang` will again be `'en'`. -For properties, we recommend that you include the data type (e.g. `string`) and you can also include a default value. Parameter values can be [validated |#Validation of Persistent Parameters]. +For the property, we recommend specifying the data type (e.g., `string`), and you can also provide a default value. Parameter values can be [validated |#Validation of Parameters]. -Persistent parameters are passed between all actions of a given presenter by default. To pass them between multiple presenters, you need to define them either: +Persistent parameters are typically transferred between all actions of a given presenter. To transfer them across multiple presenters as well, they need to be defined either: - in a common ancestor from which the presenters inherit -- in the trait that the presenters use: +- or in a trait that the presenters use: ```php trait LanguageAware @@ -277,13 +304,13 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -You can change the value of a persistent parameter when creating a link: +When creating a link, the value of a persistent parameter can be changed: ```latte detail in Czech ``` -Or it can be *reset*, i.e. removed from the URL. It will then take its default value: +Alternatively, it can be *reset*, i.e., removed from the URL. It will then assume its default value: ```latte click @@ -293,32 +320,26 @@ Or it can be *reset*, i.e. removed from the URL. It will then take its default v Interactive Components ====================== -Presenters have a built-in component system. Components are separate reusable units that we place into presenters. They can be [forms|forms:in-presenter], datagrids, menus, in fact anything that makes sense to use repeatedly. +Presenters have a built-in component system. Components are separate reusable units that we embed into presenters. They can be [forms |forms:in-presenter], datagrids, menus, essentially anything that makes sense to use repeatedly. -How are components placed and subsequently used in the presenter? This is explained in chapter [Components]. You'll even find out what they have to do with Hollywood. +How are components embedded into presenters and subsequently used? You'll learn this in the [Components |components] chapter. You'll even find out what they have in common with Hollywood. -Where Can I Get Some Components? On page [Componette |https://componette.org] you can find some open-source components and other addons for Nette that are made and shared by the community of Nette Framework. +And where can I get components? On [Componette |https://componette.org/search/component], you'll find open-source components and many other add-ons for Nette, contributed by volunteers from the framework community. Going Deeper ============ .[tip] -What we have shown so far in this chapter will probably suffice. The following lines are intended for those who are interested in presenters in depth and want to know everything. - - -Requirement and Parameters --------------------------- +What we've covered so far in this chapter will likely be sufficient for most uses. The following sections are intended for those interested in delving deeper into presenters and wanting to know absolutely everything. -The request handled by the presenter is the [api:Nette\Application\Request] object and is returned by the presenter's method `getRequest()`. It includes an array of parameters and each of them belongs either to some of the components or directly to the presenter (which is actually also a component, albeit a special one). So Nette redistributes the parameters and passes between the individual components (and the presenter) by calling the method `loadState(array $params)`. The parameters can be obtained by the method `getParameters(): array`, individually using `getParameter($name)`. Parameter values ​​are strings or arrays of strings, they are basically raw data obtained directly from a URL. +Validation of Parameters +------------------------ -Validation of Persistent Parameters ------------------------------------ +The values of [#Request-Parameters] and [#Persistent-Parameters] received from URLs are written to properties by the `loadState()` method. It also checks if the data type specified in the property matches, otherwise it will respond with a 404 error and the page will not be displayed. -The values of [#persistent parameters] received from URLs are written to properties by the `loadState()` method. It also checks if the data type specified in the property matches, otherwise it will respond with a 404 error and the page will not be displayed. - -Never blindly trust persistent parameters, as they can easily be overwritten by the user in the URL. For example, this is how we check if `$this->lang` is among the supported languages. A good way to do this is to override the `loadState()` method mentioned above: +Never blindly trust parameters received from the URL, as they can easily be overwritten by the user. For example, this is how we would verify if the language `$this->lang` is among the supported ones. A suitable way to do this is by overriding the aforementioned `loadState()` method: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -328,8 +349,8 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function loadState(array $params): void { - parent::loadState($params); // here is set the $this->lang - // follows the user value check: + parent::loadState($params); // $this->lang is set here + // followed by custom value check: if (!in_array($this->lang, ['en', 'cs'])) { $this->error(); } @@ -341,28 +362,30 @@ class ProductPresenter extends Nette\Application\UI\Presenter Save and Restore the Request ---------------------------- -You can save the current request to a session or restore it from the session and let the presenter execute it again. This is useful, for example, when a user fills out a form and its login expires. In order not to lose data, before redirecting to the sign-in page, we save the current request to the session using `$reqId = $this->storeRequest()`, which returns an identifier in the form of a short string and passes it as a parameter to the sign-in presenter. +The request handled by the presenter is a [api:Nette\Application\Request] object, returned by the presenter's `getRequest()` method. + +The current request can be saved to the session or, conversely, restored from it and have the presenter execute it again. This is useful, for example, when a user is filling out a form and their login session expires. To avoid data loss, before redirecting to the login page, we save the current request to the session using `$reqId = $this->storeRequest()`. This returns its identifier as a short string, which we then pass as a parameter to the login presenter. -After sign in, we call the method `$this->restoreRequest($reqId)`, which picks up the request from the session and forwards it to it. The method verifies that the request was created by the same user as now logged in is. If another user logs in or the key is invalid, it does nothing and the program continues. +After logging in, we call the `$this->restoreRequest($reqId)` method, which retrieves the request from the session and forwards to it. The method verifies that the request was created by the same user who is now logged in. If a different user logs in or the key is invalid, it does nothing, and the program continues as usual. -See the cookbook [How to return to an earlier page |best-practices:restore-request]. +See the guide [How to Return to a Previous Page |best-practices:restore-request]. Canonization ------------ -Presenters have one really great feature that improves SEO (optimization of searchability on the Internet). They automatically prevent the existence of duplicate content at different URLs. If multiple URLs lead to a certain destination, e.g. `/index` and `/index?page=1`, the framework designates one of them as the primary (canonical) and redirects the others to it using HTTP code 301. Thanks to this, search engines do not index pages twice and do not weaken their page rank. +Presenters have a truly excellent feature that contributes to better SEO (Search Engine Optimization). They automatically prevent the existence of duplicate content at different URLs. If multiple URLs lead to a specific destination, e.g., `/index` and `/index?page=1`, the framework designates one of them as primary (canonical) and redirects the others to it using HTTP code 301. Thanks to this, search engines don't index your pages twice and dilute their page rank. -This process is called canonization. The canonical URL is the URL generated by [router |routing], usually the first appropriate route in the collection. +This process is called canonization. The canonical URL is the one generated by the [router|routing], typically the first matching route in the collection. -Canonization is on by default and can be turned off via `$this->autoCanonicalize = false`. +Canonization is enabled by default and can be disabled via `$this->autoCanonicalize = false`. -Redirection does not occur with an AJAX or POST request because it would result in data loss or no SEO added value. +Redirection does not occur during AJAX or POST requests, as this could lead to data loss or would offer no added SEO value. -You can also invoke canonization manually using method `canonicalize()`, which, like method `link()`, receives the presenter, actions, and parameters as arguments. It creates a link and compares it to the current URL. If it is different, it redirects to the generated link. +You can also trigger canonization manually using the `canonicalize()` method. Similar to the `link()` method, you pass it the presenter, action, and parameters. It generates a link and compares it with the current URL address. If they differ, it redirects to the generated link. ```php -public function actionShow(int $id, string $slug = null): void +public function actionShow(int $id, ?string $slug = null): void { $realSlug = $this->facade->getSlugForId($id); // redirects if $slug is different from $realSlug @@ -374,7 +397,7 @@ public function actionShow(int $id, string $slug = null): void Events ------ -In addition to methods `startup()`, `beforeRender()` and `shutdown()`, which are called as part of the presenter's life cycle, other functions can be defined to be called automatically. The presenter defines the so-called [events |nette:glossary#events], and you add their handlers to arrays `$onStartup`, `$onRender` and `$onShutdown`. +In addition to the `startup()`, `beforeRender()`, and `shutdown()` methods, which are called as part of the presenter's life cycle, other functions can be defined to be called automatically. The presenter defines so-called [events |nette:glossary#Events], and you add their handlers to the `$onStartup`, `$onRender`, and `$onShutdown` arrays. ```php class ArticlePresenter extends Nette\Application\UI\Presenter @@ -388,23 +411,23 @@ class ArticlePresenter extends Nette\Application\UI\Presenter } ``` -Handlers in array `$onStartup` are called just before the method `startup()`, then `$onRender` between `beforeRender()` and `render()` and finally `$onShutdown` just before `shutdown()`. +Handlers in the `$onStartup` array are called just before the `startup()` method, `$onRender` handlers between `beforeRender()` and `render()`, and finally `$onShutdown` handlers just before `shutdown()`. Responses --------- -The response returned by the presenter is an object implementing the [api:Nette\Application\Response] interface. There are a number of ready-made answers: +The response returned by the presenter is an object implementing the [api:Nette\Application\Response] interface. Several pre-built responses are available: - [api:Nette\Application\Responses\CallbackResponse] - sends a callback - [api:Nette\Application\Responses\FileResponse] - sends the file -- [api:Nette\Application\Responses\ForwardResponse] - forward () +- [api:Nette\Application\Responses\ForwardResponse] - forward() - [api:Nette\Application\Responses\JsonResponse] - sends JSON - [api:Nette\Application\Responses\RedirectResponse] - redirect - [api:Nette\Application\Responses\TextResponse] - sends text - [api:Nette\Application\Responses\VoidResponse] - blank response -Responses are sent by method `sendResponse()`: +Responses are sent using the `sendResponse()` method: ```php use Nette\Application\Responses; @@ -425,10 +448,63 @@ $this->sendResponse(new Responses\CallbackResponse($callback)); ``` +Access Restriction Using `#[Requires]` .{data-version:3.2.2} +------------------------------------------------------------ + +The `#[Requires]` attribute provides advanced options for restricting access to presenters and their methods. It can be used to specify HTTP methods, require an AJAX request, restrict to the same origin, and allow access only via forwarding. The attribute can be applied both to presenter classes and to individual methods like `action()`, `render()`, `handle()`, and `createComponent()`. + +You can specify these restrictions: +- on HTTP methods: `#[Requires(methods: ['GET', 'POST'])]` +- requiring an AJAX request: `#[Requires(ajax: true)]` +- access only from the same origin: `#[Requires(sameOrigin: true)]` +- access only via forwarding: `#[Requires(forward: true)]` +- restrictions on specific actions: `#[Requires(actions: 'default')]` + +Details can be found in the guide [How to Use the Requires Attribute |best-practices:attribute-requires]. + + +HTTP Method Check +----------------- + +Presenters in Nette automatically verify the HTTP method of every incoming request, primarily for security reasons. By default, the methods `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH` are allowed. + +If you want to additionally allow, for example, the `OPTIONS` method, use the `#[Requires]` attribute (since Nette Application v3.2): + +```php +#[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])] +class MyPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +In version 3.1, verification is performed in `checkHttpMethod()`, which checks if the method specified in the request is included in the `$presenter->allowedMethods` array. Add a method like this: + +```php +class MyPresenter extends Nette\Application\UI\Presenter +{ + protected function checkHttpMethod(): void + { + $this->allowedMethods[] = 'OPTIONS'; + parent::checkHttpMethod(); + } +} +``` + +It's important to emphasize that if you enable the `OPTIONS` method, you must subsequently handle it appropriately within your presenter. This method is often used as a so-called preflight request, which the browser automatically sends before the actual request when it's necessary to determine if the request is permissible according to the CORS (Cross-Origin Resource Sharing) policy. If you enable the method but don't implement the correct response, it can lead to inconsistencies and potential security problems. + + +Marking Deprecated Actions .{data-version:3.2.3} +------------------------------------------------ + +The `#[Deprecated]` attribute marks actions, signals, or entire presenters as deprecated and scheduled for future removal. When generating links to deprecated parts of the application, Nette throws a warning to alert developers. + +You can apply the attribute to either the entire presenter class or to individual `action()`, `render()`, and `handle()` methods. + + Further Reading =============== - [Inject methods and attributes |best-practices:inject-method-attribute] - [Composing presenters from traits |best-practices:presenter-traits] - [Passing settings to presenters |best-practices:passing-settings-to-presenters] -- [How to return to an earlier page |best-practices:restore-request] +- [How to Return to a Previous Page |best-practices:restore-request] diff --git a/application/en/routing.texy b/application/en/routing.texy index bb30283d14..4b83f317f2 100644 --- a/application/en/routing.texy +++ b/application/en/routing.texy @@ -3,27 +3,26 @@ Routing
    -The router is responsible for everything about URLs so that you no longer have to think about them. We will show: +The router handles everything related to URL addresses, so you don't have to think about them. We will show you: -- how to set up the router so that the URLs look like you want -- a few notes about SEO redirection -- and we'll show you how to write your own router +- how to configure the router to make URLs look as you wish +- discuss SEO and redirection +- and demonstrate how to write a custom router
    -More human URLs (or cool or pretty URLs) are more usable, more memorable and contribute positively to SEO. Nette has this in mind and fully meets developers' desires. You can design your URL structure for your application exactly the way you want it. -You can even design it after the app is ready, as it can be done without any code or template changes. It is defined in an elegant way in [one single place |#Integration], in the router, and is not scattered in the form of annotations in all presenters. +More human-friendly URLs (also known as cool or pretty URLs) are more usable, memorable, and contribute positively to SEO. Nette keeps this in mind and fully caters to developers' needs. You can design the exact URL structure you want for your application. You can even design it when the application is already finished, as it requires no changes to code or templates. It's defined elegantly in [a single place |#Integration], the router, rather than being scattered as annotations throughout all presenters. -The router in Nette is special because it is **bidirectional**, it can both decode HTTP request URLs as well as create links. So it plays a vital role in [Nette Application |how-it-works#Nette Application], because it decides which presenter and action will execute the current request, and is also used for [URL generation |creating-links] in the template, etc. +The router in Nette is exceptional because it is **bidirectional.** It can both decode URLs from HTTP requests and create links. Thus, it plays a crucial role in [Nette Application |how-it-works#Nette Application], as it not only decides which presenter and action will execute the current request but is also used for [generating URLs |creating-links] in templates, etc. -However, the router is not limited to this use, you can use it in applications where presenters are not used at all, for REST APIs, etc. More in the section [#separated usage]. +However, the router isn't limited to just this usage; you can use it in applications where presenters aren't used at all, for REST APIs, etc. More details are in the section on [#Standalone Usage]. Route Collection ================ -The most pleasant way to define the URL addresses in the application is via the class [api:Nette\Application\Routers\RouteList]. The definition consists of a list of so-called routes, ie masks of URL addresses and their associated presenters and actions using a simple API. We do not have to name the routes. +The most pleasant way to define the structure of URL addresses in an application is offered by the [api:Nette\Application\Routers\RouteList] class. The definition consists of a list of so-called routes, i.e., masks of URL addresses and their associated presenters and actions using a simple API. We don't need to name the routes in any way. ```php $router = new Nette\Application\Routers\RouteList; @@ -32,16 +31,16 @@ $router->addRoute('article/', 'Article:view'); // ... ``` -The example says that if we open `https://any-domain.com/rss.xml` in the browser, the presenter `Feed` with the action `rss` will be displayed, if `https://domain.com/article/12`, the `Article` with the `view` action is displayed, etc. If no suitable route is found, Nette Application responds by throwing an exception [BadRequestException |api:Nette\Application\BadRequestException], which appears to the user as a 404 Not Found error page. +The example shows that if we open `https://domain.com/rss.xml` in the browser, the `Feed` presenter with the `rss` action will be displayed. If `https://domain.com/article/12`, the `Article` presenter with the `view` action will be displayed, etc. If no suitable route is found, Nette Application responds by throwing a [BadRequestException |api:Nette\Application\BadRequestException], which is displayed to the user as a 404 Not Found error page. Order of Routes --------------- -The order in which the routes are listed is **very important** because they are evaluated sequentially from top to bottom. The rule is that we declare routes **from specific to general**: +The **order** in which the individual routes are listed is absolutely **crucial**, because they are evaluated sequentially from top to bottom. The rule is that we declare routes **from specific to general**: ```php -// WRONG: 'rss.xml' matches the first route and misunderstands this as +// WRONG: 'rss.xml' is captured by the first route and understands this string as $router->addRoute('', 'Article:view'); $router->addRoute('rss.xml', 'Feed:rss'); @@ -50,10 +49,10 @@ $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('', 'Article:view'); ``` -Routes are also evaluated from top to bottom when links are generated: +Routes are also evaluated from top to bottom when generating links: ```php -// WRONG: generates a link to 'Feed:rss' as 'admin/feed/rss' +// WRONG: link to 'Feed:rss' generates as 'admin/feed/rss' $router->addRoute('admin//', 'Admin:default'); $router->addRoute('rss.xml', 'Feed:rss'); @@ -62,57 +61,57 @@ $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('admin//', 'Admin:default'); ``` -We won't keep it a secret from you that it takes some skill to build a list correctly. Until you get into it, the [routing panel |#Debugging Router] will be a useful tool. +We won't hide from you that correctly assembling routes requires some skill. Until you master it, the [routing panel |#Debugging Router] will be a useful tool. Mask and Parameters ------------------- -The mask describes the relative path based on the site root. The simplest mask is a static URL: +The mask describes the relative path from the website's root directory. The simplest mask is a static URL: ```php $router->addRoute('products', 'Products:default'); ``` -Often masks contain so-called **parameters**. They are enclosed in angle brackets (e.g. ``) and are passed to the target presenter, for example to the `renderShow(int $year)` method or to persistent parameter `$year`: +Often, masks contain so-called **parameters**. These are enclosed in angle brackets (e.g., ``) and are passed to the target presenter, for example, to the `renderShow(int $year)` method or to the persistent parameter `$year`: ```php $router->addRoute('chronicle/', 'History:show'); ``` -The example says that if we open `https://any-domain.com/chronicle/2020` in the browser, the presenter `History` and the action `show` with parameter `year: 2020` will be displayed. +The example shows that if we open `https://example.com/chronicle/2020` in the browser, the `History` presenter with the `show` action and the parameter `year: 2020` will be displayed. -We can specify a default value for the parameters directly in the mask and thus it becomes optional: +We can specify a default value for parameters directly in the mask, making them optional: ```php $router->addRoute('chronicle/', 'History:show'); ``` -The route will now accept the URL `https://any-domain.com/chronicle/`, which will again display `History:show` with parameter `year: 2020`. +The route will now also accept the URL `https://example.com/chronicle/`, which will again display `History:show` with the parameter `year: 2020`. -Of course, the name of the presenter and the action can also be a parameter. For example: +Of course, the presenter and action names can also be parameters. For example: ```php $router->addRoute('/', 'Home:default'); ``` -This route accepts, for example, a URL in the form `/article/edit` resp. `/catalog/list` and translates them to presenters and actions `Article:edit` resp. `Catalog:list`. +The specified route accepts, for example, URLs in the form `/article/edit` or `/catalog/list` and understands them as presenters and actions `Article:edit` and `Catalog:list`, respectively. -It also gives to parameters `presenter` and `action` default values ​​`Home` and `default` and therefore they are optional. So the route also accepts a URL `/article` and translates it as `Article:default`. Or vice versa, a link to `Product:default` generates a path `/product`, a link to the default `Home:default` generates a path `/`. +At the same time, it gives the parameters `presenter` and `action` default values `Home` and `default`, making them optional as well. Thus, the route also accepts a URL like `/article` and understands it as `Article:default`. Or conversely, a link to `Product:default` generates the path `/product`, and a link to the default `Home:default` generates the path `/`. -The mask can describe not only the relative path based on the site root, but also the absolute path when it begins with a slash, or even the entire absolute URL when it begins with two slashes: +The mask can describe not only the relative path from the website's root directory but also an absolute path if it starts with a slash, or even the entire absolute URL if it starts with two slashes: ```php -// relative path to application document root +// relative to the document root $router->addRoute('/', /* ... */); -// absolute path, relative to server hostname +// absolute path (relative to the domain) $router->addRoute('//', /* ... */); -// absolute URL including hostname (but scheme-relative) +// absolute URL including domain (relative to the scheme) $router->addRoute('//.example.com//', /* ... */); -// absolute URL including schema +// absolute URL including scheme $router->addRoute('https://.example.com//', /* ... */); ``` @@ -120,16 +119,16 @@ $router->addRoute('https://.example.com//', /* ... */); Validation Expressions ---------------------- -A validation condition can be specified for each parameter using [regular expression |https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. For example, let's set `id` to be only numerical, using `\d+` regexp: +A validation condition can be specified for each parameter using a [regular expression|https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. For example, for the parameter `id`, we specify that it can only contain digits using the regex `\d+`: ```php $router->addRoute('/[/]', /* ... */); ``` -The default regular expression for all parameters is `[^/]+`, ie everything except the slash. If a parameter is supposed to match a slash as well, we set the regular expression to `.+`. +The default regular expression for all parameters is `[^/]+`, i.e., everything except a slash. If a parameter is supposed to accept slashes as well, we set the expression to `.+`: ```php -// accepts https://example.com/a/b/c, path is 'a/b/c' +// accepts https://example.com/a/b/c, path will be 'a/b/c' $router->addRoute('', /* ... */); ``` @@ -137,25 +136,25 @@ $router->addRoute('', /* ... */); Optional Sequences ------------------ -Square brackets denote optional parts of mask. Any part of mask may be set as optional, including those containing parameters: +In the mask, optional parts can be marked using square brackets. Any part of the mask can be optional, and it can contain parameters: ```php $router->addRoute('[/]', /* ... */); -// Accepted URLs: Parameters: -// /en/download lang => en, name => download -// /download lang => null, name => download +// Accepts paths: +// /en/download => lang => en, name => download +// /download => lang => null, name => download ``` -Of course, when a parameter is part of an optional sequence, it also becomes optional. If it does not have a default value, it will be null. +When a parameter is part of an optional sequence, it naturally becomes optional too. If it doesn't have a specified default value, it will be null. -Optional sections can also be in the domain: +Optional parts can also be in the domain: ```php $router->addRoute('//[.]example.com//', /* ... */); ``` -Sequences may be freely nested and combined: +Sequences can be nested and combined arbitrarily: ```php $router->addRoute( @@ -163,33 +162,33 @@ $router->addRoute( 'Home:default', ); -// Accepted URLs: -// /cs/hello -// /en-us/hello -// /hello -// /hello/page-12 +// Accepts paths: +// /en/hello +// /en-us/hello +// /hello +// /hello/page-12 ``` -URL generator tries to keep the URL as short as possible, so what can be omitted is omitted. Therefore, for example, a route `index[.html]` generates a path `/index`. You can reverse this behavior by writing an exclamation mark after the left square bracket: +When generating URLs, the shortest variant is preferred, so everything that can be omitted is omitted. Therefore, for example, the route `index[.html]` generates the path `/index`. This behavior can be reversed by placing an exclamation mark after the left square bracket: ```php -// accepts both /hello and /hello.html, generates /hello +// accepts /hello and /hello.html, generates /hello $router->addRoute('[.html]', /* ... */); -// accepts both /hello and /hello.html, generates /hello.html +// accepts /hello and /hello.html, generates /hello.html $router->addRoute('[!.html]', /* ... */); ``` -Optional parameters (ie. parameters having default value) without square brackets do behave as if wrapped like this: +Optional parameters (i.e., parameters with a default value) without square brackets essentially behave as if they were enclosed in the following way: ```php $router->addRoute('//', /* ... */); -// equals to: +// corresponds to this: $router->addRoute('[/[/[]]]', /* ... */); ``` -To change how the rightmost slash is generated, i.e. instead of `/home/` get a `/home`, adjust the route this way: +If we want to influence the behavior of the trailing slash, so that, for example, `/home` is generated instead of `/home/`, this can be achieved as follows: ```php $router->addRoute('[[/[/]]]', /* ... */); @@ -199,12 +198,12 @@ $router->addRoute('[[/[/]]]', /* ... */); Wildcards --------- -In the absolute path mask, we can use the following wildcards to avoid, for example, the need to write a domain to the mask, which may differ in the development and production environment: +In the absolute path mask, we can use the following wildcards to avoid, for example, having to write the domain into the mask, which might differ between development and production environments: -- `%tld%` = top level domain, e.g. `com` or `org` -- `%sld%` = second level domain, e.g. `example` -- `%domain%` = domain without subdomains, e.g. `example.com` -- `%host%` = whole host, e.g. `www.example.com` +- `%tld%` = top level domain, e.g., `com` or `org` +- `%sld%` = second level domain, e.g., `example` +- `%domain%` = domain without subdomains, e.g., `example.com` +- `%host%` = entire host, e.g., `www.example.com` - `%basePath%` = path to the root directory ```php @@ -216,7 +215,7 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ @@ -225,7 +224,7 @@ $router->addRoute('/[/]', [ ]); ``` -Or we can use this form, notice the rewriting of the validation regular expression: +For more detailed specification, an even more extended form can be used, where besides default values, we can set other parameter properties, such as a validation regular expression (see the `id` parameter): ```php use Nette\Routing\Route; @@ -243,19 +242,19 @@ $router->addRoute('/[/]', [ ]); ``` -These more talkative formats are useful for adding other metadata. +It is important to note that if parameters defined in the array are not listed in the path mask, their values cannot be changed, not even using query parameters specified after the question mark in the URL. Filters and Translations ------------------------ -It's a good practice to write source code in English, but what if you need your website to have translated URL to different language? Simple routes such as: +We write the application's source code in English, but if the website needs to have Czech URLs, then simple routing like: ```php $router->addRoute('/', 'Home:default'); ``` -will generate English URLs, such as `/product/123` or `/cart`. If we want to have presenters and actions in the URL translated to Deutsch (e.g. `/produkt/123` or `/einkaufswagen`), we can use a translation dictionary. To add it, we already need a "more talkative" variant of the second parameter: +will generate English URLs, such as `/product/123` or `/cart`. If we want presenters and actions in the URL to be represented by Czech words (e.g., `/produkt/123` or `/kosik`), we can use a translation dictionary. To write it, we already need the "more verbose" variant of the second parameter: ```php use Nette\Routing\Route; @@ -279,11 +278,11 @@ $router->addRoute('/', [ ]); ``` -Multiple dictionary keys can by used for the same presenter. They will create various aliases for it. The last key is considered to be the canonical variant (i.e. the one that will be in the generated URL). +Multiple keys in the translation dictionary can lead to the same presenter. This creates various aliases for it. The last key is considered the canonical variant (i.e., the one that will be in the generated URL). -The translation table can be applied to any parameter in this way. However, if the translation does not exist, the original value is taken. We can change this behavior by adding `Route::FilterStrict => true` and the route will then reject the URL if the value is not in the dictionary. +The translation table can be used in this way for any parameter. If a translation doesn't exist, the original value is taken. We can change this behavior by adding `Route::FilterStrict => true`, and the route will then reject the URL if the value is not in the dictionary. -In addition to the translation dictionary in the form of an array, it is possible to set own translation functions: +In addition to the translation dictionary in the form of an array, custom translation functions can be deployed. ```php use Nette\Routing\Route; @@ -299,15 +298,15 @@ $router->addRoute('//', [ ]); ``` -The function `Route::FilterIn` converts between the parameter in the URL and the string, which is then passed to the presenter, the function `FilterOut` ensures the conversion in the opposite direction. +The `Route::FilterIn` function converts between the parameter in the URL and the string that is then passed to the presenter; the `FilterOut` function ensures the conversion in the opposite direction. -The parameters `presenter`, `action` and `module` already have predefined filters that convert between the PascalCase resp. camelCase style and kebab-case used in the URL. The default value of the parameters is already written in the transformed form, so, for example, in the case of a presenter, we write `` instead of ``. +The parameters `presenter`, `action`, and `module` already have predefined filters that convert between PascalCase or camelCase style and the kebab-case used in URLs. The default value of the parameters is written in the transformed form, so for example, in the case of a presenter, we write ``, not ``. General Filters --------------- -Besides filters for specific parameters, you can also define general filters that receive an associative array of all parameters that they can modify in any way and then return. General filters are defined under `null` key. +Besides filters intended for specific parameters, we can also define general filters that receive an associative array of all parameters, which they can modify in any way and then return. General filters are defined under the empty key. ```php use Nette\Routing\Route; @@ -315,22 +314,22 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => 'Home', 'action' => 'default', - null => [ + '' => [ Route::FilterIn => function (array $params): array { /* ... */ }, Route::FilterOut => function (array $params): array { /* ... */ }, ], ]); ``` -General filters give you the ability to adjust the behavior of the route in absolutely any way. We can use them, for example, to modify parameters based on other parameters. For example, translation `` and `` based on the current value of parameter ``. +General filters provide the ability to modify the route's behavior in absolutely any way. We can use them, for example, to modify parameters based on other parameters. For instance, translating `` and `` based on the current value of the `` parameter. -If a parameter has a custom filter defined and a general filter exists at the same time, custom `FilterIn` is executed before the general and vice versa general `FilterOut` is executed before the custom. Thus, inside the general filter are the values of the parameters `presenter` resp. `action` written in PascalCase resp. camelCase style. +If a parameter has its own filter defined and a general filter also exists, the custom `FilterIn` is executed before the general one, and conversely, the general `FilterOut` is executed before the custom one. Thus, inside the general filter, the values of the parameters `presenter` and `action` are written in PascalCase or camelCase style, respectively. OneWay Flag ----------- -One-way routes are used to preserve the functionality of old URLs that the application no longer generates but still accepts. We flag them with `OneWay`: +One-way routes are used to maintain the functionality of old URLs that the application no longer generates but still accepts. We mark them with the `OneWay` flag: ```php // old URL /product-info?id=123 @@ -339,21 +338,44 @@ $router->addRoute('product-info', 'Product:detail', $router::ONE_WAY); $router->addRoute('product/', 'Product:detail'); ``` -When accessing the old URL, the presenter automatically redirects to the new URL so that search engines do not index these pages twice (see [#SEO and canonization]). +When accessing the old URL, the presenter automatically redirects to the new URL, so search engines won't index these pages twice (see [#SEO and Canonization]). + + +Dynamic Routing with Callbacks +------------------------------ + +Dynamic routing with callbacks allows you to directly assign functions (callbacks) to routes, which are executed when the given path is visited. This flexible functionality allows you to quickly and efficiently create various endpoints for your application: + +```php +$router->addRoute('test', function () { + echo 'You are at the /test address'; +}); +``` + +You can also define parameters in the mask, which are automatically passed to your callback: + +```php +$router->addRoute('', function (string $lang) { + echo match ($lang) { + 'cs' => 'Welcome to the Czech version of our website!', + 'en' => 'Welcome to the English version of our website!', + }; +}); +``` Modules ------- -If we have more routes that belong to one [module |modules], we can use `withModule()` to group them: +If we have multiple routes that belong to a common [module |directory-structure#Presenters and Templates], we use `withModule()`: ```php $router = new RouteList; -$router->withModule('Forum') // the following routers are part of the Forum module - ->addRoute('rss', 'Feed:rss') // presenter is Forum:Feed +$router->withModule('Forum') // the following routes are part of the Forum module + ->addRoute('rss', 'Feed:rss') // presenter will be Forum:Feed ->addRoute('/') - ->withModule('Admin') // the following routers are part of the Forum:Admin module + ->withModule('Admin') // the following routes are part of the Forum:Admin module ->addRoute('sign:in', 'Sign:in'); ``` @@ -370,7 +392,7 @@ $router->addRoute('manage//', [ Subdomains ---------- -Route collections can be grouped by subdomains: +Route collections can be divided according to subdomains: ```php $router = new RouteList; @@ -379,7 +401,7 @@ $router->withDomain('example.com') ->addRoute('/'); ``` -You can also use [#wildcards] in your domain name: +[#Wildcards] can also be used in the domain name: ```php $router = new RouteList; @@ -391,7 +413,7 @@ $router->withDomain('example.%tld%') Path Prefix ----------- -Route collections can be grouped by path in URL: +Route collections can be divided according to the path in the URL: ```php $router = new RouteList; @@ -404,7 +426,7 @@ $router->withPath('eshop') Combinations ------------ -The above usage can be combined: +The above groupings can be combined with each other: ```php $router = (new RouteList) @@ -427,10 +449,10 @@ $router = (new RouteList) Query Parameters ---------------- -Masks can also contain query parameters (parameters after the question mark in the URL). They cannot define a validation expression, but they can change the name under which they are passed to the presenter: +Masks can also contain query parameters (parameters after the question mark in the URL). A validation expression cannot be defined for these, but the name under which they are passed to the presenter can be changed: ```php -// use query parameter 'cat' as a 'categoryId' in application +// we want to use the query parameter 'cat' under the name 'categoryId' in the application $router->addRoute('product ? id= & cat=', /* ... */); ``` @@ -438,13 +460,13 @@ $router->addRoute('product ? id= & cat=', /* ... */); Foo Parameters -------------- -We're going deeper now. Foo parameters are basically unnamed parameters which allow to match a regular expression. The following route matches `/index`, `/index.html`, `/index.htm` and `/index.php`: +Now we're going deeper. Foo parameters are essentially unnamed parameters that allow matching a regular expression. An example is a route accepting `/index`, `/index.html`, `/index.htm`, and `/index.php`: ```php $router->addRoute('index', /* ... */); ``` -It's also possible to explicitly define a string which will be used for URL generation. The string must be placed directly after the question mark. The following route is similar to the previous one, but generates `/index.html` instead of `/index` because the string `.html` is set as a "generated value". +It is also possible to explicitly define the string that will be used when generating the URL. The string must be placed directly after the question mark. The following route is similar to the previous one, but generates `/index.html` instead of `/index`, because the string `.html` is set as the generation value: ```php $router->addRoute('index', /* ... */); @@ -454,10 +476,10 @@ $router->addRoute('index', /* ... */); Integration =========== -In order to connect the our router into the application, we must tell the DI container about it. The easiest way is to prepare the factory that will build the router object and tell the container configuration to use it. So let's say we write a method for this purpose `App\Router\RouterFactory::createRouter()`: +To integrate the created router into the application, we need to tell the DI container about it. The easiest way is to prepare a factory that will create the router object and tell the container in the configuration to use it. Let's say we write the method `App\Core\RouterFactory::createRouter()` for this purpose: ```php -namespace App\Router; +namespace App\Core; use Nette\Application\Routers\RouteList; @@ -472,14 +494,14 @@ class RouterFactory } ``` -Then we write in [configuration |dependency-injection:services]: +Then we write in the [configuration |dependency-injection:services]: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter ``` -Any dependencies, such as a database connection etc., are passed to the factory method as its parameters using [autowiring |dependency-injection:autowiring]: +Any dependencies, such as on a database, etc., are passed to the factory method as its parameters using [autowiring|dependency-injection:autowiring]: ```php public static function createRouter(Nette\Database\Connection $db): RouteList @@ -492,18 +514,18 @@ public static function createRouter(Nette\Database\Connection $db): RouteList SimpleRouter ============ -A much simpler router than the Route Collection is [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. It can be used when there's no need for a specific URL format, when `mod_rewrite` (or alternatives) is not available or when we simply do not want to bother with user-friendly URLs yet. +A much simpler router than the route collection is [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. We use it when we don't have special requirements for the URL format, if `mod_rewrite` (or its alternatives) is not available, or if we don't want to deal with pretty URLs yet. -Generates addresses in roughly this form: +It generates addresses roughly in this form: ``` http://example.com/?presenter=Product&action=detail&id=123 ``` -The parameter of the `SimpleRouter` constructor is a default presenter & action, ie. action to be executed if we open e.g. `http://example.com/` without additional parameters. +The parameter of the `SimpleRouter` constructor is a default presenter & action, i.e. action to be executed if we open e.g. `http://example.com/` without additional parameters. ```php -// defaults to presenter 'Home' and action 'default' +// the default presenter will be 'Home' and action 'default' $router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` @@ -518,9 +540,9 @@ services: SEO and Canonization ==================== -The framework increases SEO (search engine optimization) by preventing duplication of content at different URLs. If multiple addresses link to a same destination, eg `/index` and `/index.html`, the framework determines the first one as primary (canonical) and redirects the others to it using HTTP code 301. Thanks to this, search engines will not index pages twice and do not break their page rank. . +The framework contributes to SEO (Search Engine Optimization) by preventing duplicate content on different URLs. If multiple addresses lead to a certain destination, e.g., `/index` and `/index.html`, the framework designates the first one as primary (canonical) and redirects the others to it using HTTP code 301. Thanks to this, search engines do not index pages twice and do not dilute their page rank. -This process is called canonization. The canonical URL is the one generated by the router, i.e. by the first matching route in the [collection |#route-collection] without the OneWay flag. Therefore, in the collection, we list **primary routes first**. +This process is called canonization. The canonical URL is the one generated by the router, i.e. by the first matching route in the collection without the OneWay flag. Therefore, in the collection, we list **primary routes first**. Canonization is performed by the presenter, more in the chapter [canonization |presenters#Canonization]. @@ -528,9 +550,9 @@ Canonization is performed by the presenter, more in the chapter [canonization |p HTTPS ===== -In order to use the HTTPS protocol, it is necessary to activate it on hosting and to configure the server. +To use the HTTPS protocol, it is necessary to enable it on the hosting and configure the server correctly. -Redirection of the entire site to HTTPS must be performed at the server level, for example using the .htaccess file in the root directory of our application, with HTTP code 301. The settings may differ depending on the hosting and looks something like this: +Redirecting the entire website to HTTPS must be set at the server level, for example, using the `.htaccess` file in the root directory of our application, with HTTP code 301. The settings may vary depending on the hosting and look something like this: ``` @@ -542,9 +564,9 @@ Redirection of the entire site to HTTPS must be performed at the server level, f ``` -The router generates a URL with the same protocol as the page was loaded, so there is no need to set anything else. +The router generates URLs with the same protocol as the page was loaded, so nothing more needs to be set. -However, if we exceptionally need different routes to run under different protocols, we will put it in the route mask: +However, if we exceptionally need different routes to run under different protocols, we specify it in the route mask: ```php // Will generate an HTTP address @@ -558,24 +580,24 @@ $router->addRoute('https://%host%//', /* ... */); Debugging Router ================ -The routing bar displayed in [Tracy Bar |tracy:] is a useful tool that displays a list of routes and also the parameters that the router has obtained from the URL. +The routing panel displayed in the [Tracy Bar |tracy:] is a useful helper that shows a list of routes and also the parameters that the router obtained from the URL. -The green bar with symbol ✓ represents the route that matched the current URL, the blue bars with symbols ≈ indicate the routes that would also match the URL if green did not overtake them. We see the current presenter & action further. +The green bar with the symbol ✓ represents the route that processed the current URL; blue color and the symbol ≈ indicate routes that would also process the URL if the green one hadn't overtaken them. Further, we see the current presenter & action. [* routing-debugger.webp *] -At the same time, if there is an unexpected redirect due to [canonicalization |#SEO and Canonization], it is useful to look in the *redirect* bar to see how the router originally understood the URL and why it redirected. +At the same time, if an unexpected redirect occurs due to [canonization |#SEO and Canonization], it is useful to look at the panel in the *redirect* bar, where you can find out how the router originally understood the URL and why it redirected. .[note] -When debugging the router, it is recommended to open Developer Tools in the browser (Ctrl+Shift+I or Cmd+Option+I) and disable the cache in the Network panel so that redirects are not stored in it. +When debugging the router, we recommend opening Developer Tools in the browser (Ctrl+Shift+I or Cmd+Option+I) and disabling the cache in the Network panel so that redirects are not stored in it. Performance =========== -The number of routes affects the speed of the router. Their number should certainly not exceed a few dozen. If your site has an overly complicated URL structure, you can write a [#custom router]. +The number of routes affects the speed of the router. Their number should definitely not exceed several dozen. If your website has a too complicated URL structure, you can write a custom [#Custom Router]. -If the router has no dependencies, such as on a database, and its factory has no arguments, we can serialize its compiled form directly into a DI container and thus make the application slightly faster. +If the router has no dependencies, for example, on a database, and its factory accepts no arguments, we can serialize its compiled form directly into the DI container and thus slightly speed up the application. ```neon routing: @@ -586,7 +608,7 @@ routing: Custom Router ============= -The following lines are intended for very advanced users. You can create your own router and naturally add it into your route collection. The router is an implementation of the [api:Nette\Routing\Router] interface with two methods: +The following lines are intended for very advanced users. You can create your own router and naturally integrate it into the collection of routes. The router is an implementation of the [api:Nette\Routing\Router] interface with two methods: ```php use Nette\Http\IRequest as HttpRequest; @@ -606,8 +628,7 @@ class MyRouter implements Nette\Routing\Router } ``` -The `match` method processes the current [$httpRequest |http:request], from which not only the URL, but also headers etc. can be retrieved, into an array containing the presenter name and its parameters. If it cannot process the request, it returns null. -When processing the request, we must return at least the presenter and the action. The presenter name is complete and includes any modules: +The `match` method processes the current request [$httpRequest |http:request], from which not only the URL but also headers, etc., can be obtained, into an array containing the presenter name and its parameters. If it cannot process the request, it returns null. When processing the request, we must return at least the presenter and action. The presenter name is complete and includes any modules: ```php [ @@ -616,9 +637,9 @@ When processing the request, we must return at least the presenter and the actio ] ``` -Method `constructUrl`, on the other hand, generates an absolute URL from the array of parameters. It can use the information from parameter `$refUrl`, which is the current URL. +The `constructUrl` method, on the contrary, constructs the resulting absolute URL from the array of parameters. It can use information from the [`$refUrl`|api:Nette\Http\UrlScript] parameter, which is the current URL. -To add custom router to the route collection, use `add()`: +Add it to the route collection using `add()`: ```php $router = new Nette\Application\Routers\RouteList; @@ -628,19 +649,19 @@ $router->addRoute(/* ... */); ``` -Separated Usage -=============== +Standalone Usage +================ -By separated usage, we mean the use of the router's capabilities in an application that does not use Nette Application and presenters. Almost everything we have shown in this chapter applies to it, with the following differences: +By standalone usage, we mean utilizing the router's capabilities in an application that does not use Nette Application and presenters. Almost everything we have shown in this chapter applies to it, with these differences: -- for route collections we use class [api:Nette\Routing\RouteList] -- as a simple router class [api:Nette\Routing\SimpleRouter] -- because there is no pair `Presenter:action`, we use [#Advanced notation] +- for route collections, we use the [api:Nette\Routing\RouteList] class +- as a simple router, the [api:Nette\Routing\SimpleRouter] class +- because the `Presenter:action` pair does not exist, we use [#Advanced Notation] -So again we will create a method that will build a router, for example: +So again, we create a method that will assemble the router for us, e.g.: ```php -namespace App\Router; +namespace App\Core; use Nette\Routing\RouteList; @@ -661,35 +682,35 @@ class RouterFactory } ``` -If you use a DI container, which we recommend, add the method to the configuration again and then get the router together with the HTTP request from the container: +If you use a DI container, which we recommend, add the method to the configuration again, and then obtain the router along with the HTTP request from the container: ```php $router = $container->getByType(Nette\Routing\Router::class); $httpRequest = $container->getByType(Nette\Http\IRequest::class); ``` -Or we will create objects directly: +Or create the objects directly: ```php -$router = App\Router\RouterFactory::createRouter(); +$router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` -Now we have to let the router to work: +Now all that remains is to let the router do its work: ```php $params = $router->match($httpRequest); if ($params === null) { - // no matching route found, we will send a 404 error + // no matching route found, send a 404 error exit; } -// we process the received parameters +// process the obtained parameters $controller = $params['controller']; // ... ``` -And vice versa, we will use the router to create the link: +And conversely, use the router to construct a link: ```php $params = ['controller' => 'ArticleController', 'id' => 123]; diff --git a/application/en/templates.texy b/application/en/templates.texy index 9c743b280b..6a5ba43fd3 100644 --- a/application/en/templates.texy +++ b/application/en/templates.texy @@ -2,9 +2,9 @@ Templates ********* .[perex] -Nette uses the [Latte |latte:] template system. Latte is used because it is the most secure template system for PHP, and at the same time the most intuitive system. You don't have to learn much new, you just need to know PHP and a few Latte tags. +Nette uses the [Latte |latte:] templating system. Latte is used because it is the most secure templating system for PHP, and at the same time, the most intuitive system. You don't need to learn much new; knowledge of PHP and a few tags will suffice. -It is usual that the page is completed from the layout template + the action template. This is what a layout template might look like, notice the blocks `{block}` and tag `{include}`: +It's common for a page to be composed of a layout template + the template for the specific action. This is what a layout template might look like; notice the `{block}` blocks and the `{include}` tag: ```latte @@ -20,7 +20,7 @@ It is usual that the page is completed from the layout template + the action tem ``` -And this might be the action template: +And this would be the action template: ```latte {block title}Homepage{/block} @@ -31,50 +31,131 @@ And this might be the action template: {/block} ``` -It defines block `content`, which is inserted in place of `{include content}` in the layout, and also re-defines block `title`, which overwrites `{block title}` in the layout. Try to imagine the result. +It defines the `content` block, which is inserted in place of `{include content}` in the layout, and also re-defines the `title` block, which overwrites `{block title}` in the layout. Try to imagine the result. -Search for Templates --------------------- +Template Lookup +--------------- -The path to the templates is deduced according to simple logic. It tries to see if one of these template files exists relative to the directory where presenter class is located, where `` is the name of the current presenter and `` is the name of the current action: +In presenters, you don't need to specify which template should be rendered; the framework automatically deduces the path, saving you from writing it. -- `templates//.latte` -- `templates/..latte` +If you use a directory structure where each presenter has its own directory, simply place the template in this directory under the name of the action (i.e., view). For example, for the `default` action, use the `default.latte` template: -If it does not find the template, the response is [error 404 |presenters#Error 404 etc.]. +/--pre +app/ +└── Presentation/ + └── Home/ + ├── HomePresenter.php + └── default.latte +\-- -You can also change the view using `$this->setView('otherView')`. Or, instead of searching, directly specify the name of the template file using `$this->template->setFile('/path/to/template.latte')`. +If you use a structure where presenters are together in one directory and templates are in a `templates` folder, save it either in the file `..latte` or `/.latte`: + +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── Home.default.latte ← 1st variant + └── Home/ + └── default.latte ← 2nd variant +\-- + +The `templates` directory can also be placed one level higher, i.e., at the same level as the directory with presenter classes. + +If the template is not found, the presenter responds with a [404 - page not found error |presenters#Error 404 etc]. + +You can change the view using `$this->setView('otherView')`. It is also possible to directly specify the template file using `$this->template->setFile('/path/to/template.latte')`. .[note] -You can change the paths where templates are searched by overriding the [formatTemplateFiles |api:Nette\Application\UI\Presenter::formatTemplateFiles()] method, which returns an array of possible file paths. +The files where templates are looked up can be changed by overriding the [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()] method, which returns an array of possible file names. + + +Layout Template Lookup +---------------------- + +Nette also automatically searches for the layout file. + +If you use a directory structure where each presenter has its own directory, place the layout either in the folder with the presenter, if it is specific only to it, or one level higher, if it is common to multiple presenters: + +/--pre +app/ +└── Presentation/ + ├── @layout.latte ← common layout + └── Home/ + ├── @layout.latte ← only for Home presenter + ├── HomePresenter.php + └── default.latte +\-- + +If you use a structure where presenters are grouped together in one directory and templates are in a `templates` folder, the layout will be expected in these locations: -The layout is expected in the following files: +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── @layout.latte ← common layout + ├── Home.@layout.latte ← only for Home, 1st variant + └── Home/ + └── @layout.latte ← only for Home, 2nd variant +\-- -- `templates//@.latte` -- `templates/.@.latte` -- `templates/@.latte` layout common to multiple presenters +If the presenter is located in a module, it will also search further up the directory levels, according to the module nesting. -`` is the name of the current presenter and `` is the name of the layout, which is by default `'layout'`. The name can be changed with `$this->setLayout('otherLayout')`, so that `@otherLayout.latte` files will be tried. +The layout name can be changed using `$this->setLayout('layoutAdmin')`, and then it will be expected in the file `@layoutAdmin.latte`. You can also directly specify the layout template file using `$this->setLayout('/path/to/template.latte')`. -You can also directly specify the file name of the layout template using `$this->setLayout('/path/to/template.latte')`. Using `$this->setLayout(false)` will disable the layout searching. +Using `$this->setLayout(false)` or the `{layout none}` tag inside the template disables layout lookup. .[note] -You can change the paths where templates are searched by overriding the [formatLayoutTemplateFiles |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()] method, which returns an array of possible file paths. +The files where layout templates are looked up can be changed by overriding the [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()] method, which returns an array of possible file names. -Variables in the Template -------------------------- +Template Variables +------------------ -Variables are passed to the template by writing them to `$this->template` and then they are available in the template as local variables: +Variables are passed to templates by writing them to `$this->template`. They then become available in the template as local variables: ```php $this->template->article = $this->articles->getById($id); ``` -In this way we can easily pass any variables to the templates. However, when developing robust applications, it is often more useful to limit ourselves. For example, by explicitly defining a list of variables that the template expects and their types. This will allow PHP to type-check, the IDE to autocomplete correctly, and static analysis to detect errors. +To automatically pass a property value to the template as a variable, mark it with the `#[TemplateVariable]` attribute and public or protected visibility: .{data-version:3.2.9} + +```php +use Nette\Application\Attributes\TemplateVariable; + +class ArticlePresenter extends Nette\Application\UI\Presenter +{ + #[TemplateVariable] + public string $siteName = 'My blog'; +} +``` + +If you pass a variable with the same name to the template, `#[TemplateVariable]` won’t override it. -And how do we define such an enumeration? Simply in the form of a class and its properties. We name it similarly to presenter, but with `Template` at the end: + +Default Variables +----------------- + +Presenters and components automatically pass several useful variables to templates: + +- `$basePath` is the absolute URL path to the root directory (e.g., `/eshop`) +- `$baseUrl` is the absolute URL to the root directory (e.g., `http://localhost/eshop`) +- `$user` is an object [representing the user |security:authentication] +- `$presenter` is the current presenter +- `$control` is the current component or presenter +- `$flashes` is an array of [messages |presenters#Flash Messages] sent by the `flashMessage()` function + +If you use a custom template class, these variables are passed if you create a property for them. + + +Type-Safe Templates +------------------- + +When developing robust applications, it's useful to explicitly define which variables the template expects and their types. This provides type checking in PHP, smart hints in your IDE, and enables static analysis to catch errors. + +How do you define such a list? Simply as a class with properties representing template variables. Name it similarly to the presenter, just with `Template` at the end: ```php /** @@ -93,22 +174,22 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template } ``` -The `$this->template` object in the presenter will now be an instance of the `ArticleTemplate` class. So PHP will check the declared types when they are written. And starting with PHP 8.2 it will also warn about writing to a non-existent variable, in previous versions the same can be achieved using the [Nette\SmartObject |utils:smartobject] trait. +The `$this->template` object in the presenter will now be an instance of the `ArticleTemplate` class. PHP will thus check the declared types when writing. -The `@property-read` annotation is for IDE and static analysis, it will make autocomplete work, see "PhpStorm and code completion for $this->template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. +The `@property-read` annotation is for the IDE and static analysis, enabling code completion, see "PhpStorm and code completion for $this⁠-⁠>⁠template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. [* phpstorm-completion.webp *] -You can indulge in the luxury of whispering in templates too, just install the Latte plugin in PhpStorm and specify the class name at the beginning of the template, see the article "Latte: how to type system":https://blog.nette.org/en/latte-how-to-use-type-system: +You can also use code completion directly in templates. Just install the Latte plugin for PhpStorm and specify the template parameter class name at the beginning of the template, more in the article "Latte: how to use the type system":https://blog.nette.org/en/latte-how-to-use-type-system: ```latte -{templateType App\Presenters\ArticleTemplate} +{templateType App\Presentation\Article\ArticleTemplate} ... ``` -This is also how templates work in components, just follow the naming convention and create a template class `FifteenTemplate` for the component e.g. `FifteenControl`. +The same applies to components. Just follow the naming convention and create a parameter class `FifteenTemplate` for a component like `FifteenControl`. -If you need to create a `$template` as an instance of another class, use the `createTemplate()` method: +If you need to use a different parameter class, use the `createTemplate()` method: ```php public function renderDefault(): void @@ -121,60 +202,91 @@ public function renderDefault(): void ``` -Default Variables ------------------ - -Presenters and components pass several useful variables to templates automatically: - -- `$basePath` is an absolute URL path to root dir (for example `/CD-collection`) -- `$baseUrl` is an absolute URL to root dir (for example `http://localhost/CD-collection`) -- `$user` is an object [representing the user |security:authentication] -- `$presenter` is the current presenter -- `$control` is the current component or presenter -- `$flashes` list of [messages |presenters#flash-messages] sent by method `flashMessage()` - -If you use a custom template class, these variables are passed if you create a property for them. - - Creating Links -------------- -In template we create links to other presenters & actions as follows: +In the template, links to other presenters & actions are created this way: ```latte -detail +product detail ``` -Attribute `n:href` is very handy for HTML tags ``. If we want to print the link elsewhere, for example in the text, we use `{link}`: +The `n:href` attribute is very handy for HTML `` tags. If we want to print the link elsewhere, for example in text, we use `{link}`: ```latte URL is: {link Home:default} ``` -For more information, see [Creating Links]. +More information can be found in the chapter [Creating URL Links|creating-links]. Custom Filters, Tags, etc. -------------------------- -The Latte templating system can be extended with custom filters, functions, tags, etc. This can be done directly in the `render` or `beforeRender()` method: +The Latte templating system can be extended with custom filters, functions, tags, and other elements. There are three approaches available, ranging from quick ad-hoc solutions to architectural patterns for entire applications. + +**Ad-hoc in Presenter Methods** + +The quickest approach is adding filters or functions directly in presenter or component code. In presenters, the `beforeRender()` or `render()` methods work well for this: ```php -public function beforeRender(): void +protected function beforeRender(): void { // adding a filter - $this->template->addFilter('foo', /* ... */); + $this->template->addFilter('money', fn($val) => '$' . number_format($val, 2)); + + // adding a function + $this->template->addFunction('isWeekend', fn($date) => $date->format('N') >= 6); +} +``` - // or configure the Latte\Engine object directly +In the template: + +```latte +

    Price: {$price|money}

    + +{if isWeekend($now)} ... {/if} +``` + +For more complex logic, you can configure the `Latte\Engine` object directly: + +```php +protected function beforeRender(): void +{ $latte = $this->template->getLatte(); - $latte->addFilterLoader(/* ... */); + $latte->setFeature(Latte\Feature::MigrationWarnings); } ``` -Latte version 3 offers a more advanced way by creating an [extension |latte:creating-extension] for each web project. Here is a rough example of such a class: +**Using Attributes** + +A more elegant approach is defining filters and functions as methods directly in the presenter or component's [template parameter class|#Type-safe templates], marked with attributes: ```php -namespace App\Templating; +class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template +{ + #[Latte\Attributes\TemplateFilter] + public function money(float $val): string + { + return '$' . number_format($val, 2); + } + + #[Latte\Attributes\TemplateFunction] + public function isWeekend(DateTimeInterface $date): bool + { + return $date->format('N') >= 6 + } +} +``` + +Latte automatically discovers and registers methods marked with these attributes. The filter or function name in templates matches the method name. These methods must not be private. + +**Globally Using Extensions** + +The previous approaches suit filters and functions needed only in specific presenters or components, not application-wide. For the entire application, creating an [extension |latte:extending-latte#Latte Extension] works best. This class centralizes all Latte extensions for your project. A brief example: + +```php +namespace App\Presentation\Accessory; final class LatteExtension extends Latte\Extension { @@ -203,26 +315,32 @@ final class LatteExtension extends Latte\Extension ]; } + private function filterTimeAgoInWords(DateTimeInterface $time): string + { + // ... + } + // ... } ``` -We register it using [configuration#Latte]: +Register the extension through [configuration |configuration#Latte Templates]: ```neon latte: extensions: - - App\Templating\LatteExtension + - App\Presentation\Accessory\LatteExtension ``` +Extensions offer several advantages: dependency injection support, access to your application's model layer, and centralized management of all extensions. They also support custom tags, providers, compiler passes, and more. + Translating ----------- -If you are programming a multilingual application, you will probably need to output some of the text in the template in different languages. To do this, the Nette Framework defines a translation interface [api:Nette\Localization\Translator], which has a single method `translate()`. This accepts the message `$message`, which is usually a string, and any other parameters. The task is to return the translated string. -There is no default implementation in Nette, you can choose according to your needs from several ready-made solutions that can be found on [Componette |https://componette.org/search/localization]. Their documentation tells you how to configure the translator. +If you are programming a multilingual application, you will likely need to output some texts in the template in different languages. Nette Framework defines a translation interface [api:Nette\Localization\Translator] for this purpose, which has a single method `translate()`. It accepts the message `$message`, which is usually a string, and any other parameters. The task is to return the translated string. Nette does not have a default implementation; you can choose from several ready-made solutions available on [Componette |https://componette.org/search/localization] according to your needs. Their documentation explains how to configure the translator. -Templates can be set up with a translator, which [we will have passed to us|dependency-injection:passing-dependencies], using the `setTranslator()` method: +Templates can be set up with a translator, which we [get passed |dependency-injection:passing-dependencies], using the `setTranslator()` method: ```php protected function beforeRender(): void @@ -232,15 +350,15 @@ protected function beforeRender(): void } ``` -Alternatively, the translator can be set using the [configuration |configuration#Latte]: +Alternatively, the translator can be set using [configuration |configuration#Latte Templates]: ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -The translator can then be used, for example, as a filter `|translate`, with additional parameters passed to the `translate()` method (see `foo, bar`): +Then, the translator can be used, for example, as a filter `|translate`, including additional parameters that are passed to the `translate()` method (see `foo, bar`): ```latte
    {='Basket'|translate} @@ -256,14 +374,14 @@ Or as an underscore tag: {_$item, foo, bar} ``` -For template section translation, there is a paired tag `{translate}` (since Latte 2.11, previously the tag `{_}` was used ): +For translating a section of the template, there is a paired tag `{translate}` (since Latte 2.11, previously the tag `{_}` was used): ```latte {translate}Order{/translate} {translate foo, bar}Order{/translate} ``` -Translator is called by default at runtime when rendering the template. Latte version 3, however, can translate all static text during template compilation. This saves performance because each string is translated only once and the resulting translation is written to the compiled form. This creates multiple compiled versions of the template in the cache directory, one for each language. To do this, you only need to specify the language as the second parameter: +The translator is normally called at runtime when rendering the template. Latte version 3, however, can translate all static texts already during template compilation. This saves performance, as each string is translated only once, and the resulting translation is written into the compiled form. This creates multiple compiled versions of the template in the cache directory, one for each language. To do this, simply specify the language as the second parameter: ```php protected function beforeRender(): void @@ -273,4 +391,4 @@ protected function beforeRender(): void } ``` -By static text we mean, for example, `{_'hello'}` or `{translate}hello{/translate}`. Non-static text, such as `{_$foo}`, will continue to be compiled on the fly. +Static text means, for example, `{_'hello'}` or `{translate}hello{/translate}`. Non-static texts, like `{_$foo}`, will continue to be translated at runtime. diff --git a/application/es/@home.texy b/application/es/@home.texy index b2966f5062..3e865c2c10 100644 --- a/application/es/@home.texy +++ b/application/es/@home.texy @@ -1,36 +1,85 @@ -Aplicación Nette -**************** +Nette Application +***************** .[perex] -El paquete `nette/application` es la base para crear aplicaciones web interactivas. - -- [¿Cómo funcionan las aplicaciones? |how-it-works] -- [Bootstrap] -- [Presentadores |Presenters] -- [Plantillas |Templates] -- [Módulos |Modules] -- [Enrutamiento |Routing] -- [Creación de enlaces URL |creating-links] -- [Componentes interactivos |components] -- [AJAX y fragmentos |ajax] -- [Multiplicador |multiplier] -- [Configuración |Configuration] +Nette Application es el núcleo del framework Nette, que proporciona potentes herramientas para crear aplicaciones web modernas. Ofrece una serie de características excepcionales que facilitan significativamente el desarrollo y mejoran la seguridad y la mantenibilidad del código. Instalación ----------- -Descargue e instale el paquete utilizando [Composer |best-practices:composer]: +Puede descargar e instalar la librería usando [Composer|best-practices:composer]: ```shell composer require nette/application ``` -| versión | compatible con PHP + +¿Por qué elegir Nette Application? +---------------------------------- + +Nette siempre ha sido un pionero en el campo de las tecnologías web. + +**Router bidireccional:** Nette cuenta con un sistema de enrutamiento avanzado que es único por su bidireccionalidad: no solo traduce las URL en acciones de la aplicación, sino que también puede generar direcciones URL inversamente. Esto significa que: +- Puede cambiar la estructura de URL de toda la aplicación en cualquier momento sin necesidad de modificar las plantillas +- Las URL se canonizan automáticamente, lo que mejora el SEO +- El enrutamiento se define en un solo lugar, no disperso en anotaciones + +**Componentes y señales:** El sistema de componentes incorporado, inspirado en Delphi y React.js, es completamente excepcional entre los frameworks PHP: +- Permite crear elementos de UI reutilizables +- Soporta la composición jerárquica de componentes +- Ofrece un manejo elegante de las peticiones AJAX mediante señales +- Una rica librería de componentes listos para usar en [Componette](https://componette.org) + +**AJAX y snippets:** Nette introdujo una forma revolucionaria de trabajar con AJAX ya en 2009, mucho antes de soluciones similares como Hotwire para Ruby on Rails o Symfony UX Turbo: +- Los snippets permiten actualizar solo partes de la página sin necesidad de escribir JavaScript +- Integración automática con el sistema de componentes +- Invalidación inteligente de partes de las páginas +- Cantidad mínima de datos transferidos + +**Plantillas intuitivas [Latte|latte:]:** El sistema de plantillas más seguro para PHP con funciones avanzadas: +- Protección automática contra XSS con escape sensible al contexto +- Extensibilidad mediante filtros, funciones y etiquetas personalizadas +- Herencia de plantillas y snippets para AJAX +- Excelente soporte para PHP 8.x con sistema de tipos + +**Inyección de dependencias:** Nette aprovecha al máximo la Inyección de Dependencias: +- Paso automático de dependencias (autowiring) +- Configuración mediante el claro formato NEON +- Soporte para fábricas de componentes + + +Principales ventajas +-------------------- + +- **Seguridad**: Defensa automática contra [vulnerabilidades|nette:vulnerability-protection] como XSS, CSRF, etc. +- **Productividad**: Menos escritura, más funciones gracias a un diseño inteligente +- **Depuración**: [Depurador Tracy|tracy:] con panel de enrutamiento +- **Rendimiento**: Caché inteligente, carga diferida de componentes +- **Flexibilidad**: Fácil modificación de URL incluso después de completar la aplicación +- **Componentes**: Sistema único de elementos de UI reutilizables +- **Moderno**: Soporte completo para PHP 8.4+ y sistema de tipos + + +Empezando +--------- + +1. [¿Cómo funcionan las aplicaciones? |how-it-works] - Comprensión de la arquitectura básica +2. [Presenters |presenters] - Trabajo con presenters y acciones +3. [Plantillas |templates] - Creación de plantillas en Latte +4. [Enrutamiento |routing] - Configuración de direcciones URL +5. [Componentes interactivos |components] - Uso del sistema de componentes + + +Compatibilidad con PHP +---------------------- + +| versión | compatible con PHP |-----------|------------------- -| Aplicación Nette 4.0 | PHP 8.0 - 8.2 -| Aplicación Nette 3.1 | PHP 7.2 - 8.2 -| Aplicación Nette 3.0 | PHP 7.1 - 8.0 -| Aplicación Nette 2.4 | PHP 5.6 - 8.0 +| Nette Application 4.0 | PHP 8.1 – 8.4 +| Nette Application 3.2 | PHP 8.1 – 8.4 +| Nette Application 3.1 | PHP 7.2 – 8.3 +| Nette Application 3.0 | PHP 7.1 – 8.0 +| Nette Application 2.4 | PHP 5.6 – 8.0 -Se aplica a las últimas versiones de parches. +Válido para la última versión del parche. diff --git a/application/es/@left-menu.texy b/application/es/@left-menu.texy index aea80f2e18..caab7237fb 100644 --- a/application/es/@left-menu.texy +++ b/application/es/@left-menu.texy @@ -1,19 +1,22 @@ -Aplicación Nette -**************** +Nette Application +***************** - [¿Cómo funcionan las aplicaciones? |how-it-works] -- [Arranque |Bootstrap] -- [Presentadores |Presenters] -- [Plantillas |Templates] -- [Módulos |Modules] -- [Enrutamiento |Routing] +- [Bootstrapping] +- [Presenters |presenters] +- [Plantillas |templates] +- [Estructura de directorios |directory-structure] +- [Enrutamiento |routing] - [Creación de enlaces URL |creating-links] - [Componentes interactivos |components] -- [AJAX y fragmentos |ajax] -- [Multiplicador |multiplier] -- [Configuración |Configuration] +- [AJAX & snippets |ajax] +- [Multiplier |multiplier] +- [Configuración |configuration] -Más información -*************** -- [Buenas prácticas |best-practices:] -- [Solución de problemas |nette:troubleshooting] +Lectura adicional +***************** +- [¿Por qué usar Nette? |www:10-reasons-why-nette] +- [Instalación |nette:installation] +- [¡Escribamos nuestra primera aplicación! |quickstart:] +- [Tutoriales y procedimientos |best-practices:] +- [Resolución de problemas |nette:troubleshooting] diff --git a/application/es/@meta.texy b/application/es/@meta.texy new file mode 100644 index 0000000000..1670b124ad --- /dev/null +++ b/application/es/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Documentación}} diff --git a/application/es/ajax.texy b/application/es/ajax.texy index a81eb64441..876e612def 100644 --- a/application/es/ajax.texy +++ b/application/es/ajax.texy @@ -3,36 +3,43 @@ AJAX y fragmentos
    -Hoy en día, las aplicaciones web modernas se ejecutan mitad en un servidor y mitad en un navegador. AJAX es un factor de unión vital. ¿Qué soporte ofrece Nette Framework? -- envío de fragmentos de plantillas (los llamados *snippets*) +En la era de las aplicaciones web modernas, donde la funcionalidad a menudo se divide entre el servidor y el navegador, AJAX es un elemento de conexión esencial. ¿Qué posibilidades nos ofrece Nette Framework en esta área? +- envío de partes de la plantilla, los llamados fragmentos (snippets) - paso de variables entre PHP y JavaScript -- depuración de aplicaciones AJAX +- herramientas para depurar peticiones AJAX
    -Una solicitud AJAX puede detectarse utilizando un método de un servicio que [encapsula una solicitud HTTP |http:request] `$httpRequest->isAjax()` (detecta basándose en la cabecera HTTP `X-Requested-With` ). También existe un método abreviado en presentador: `$this->isAjax()`. -Una petición AJAX no difiere de una normal: se llama a un presentador con una vista y unos parámetros determinados. También depende del presentador cómo reaccionará: puede utilizar sus rutinas para devolver un fragmento de código HTML (un snippet), un documento XML, un objeto JSON o un fragmento de código Javascript. +Petición AJAX +============= -Existe un objeto preprocesado llamado `payload` dedicado a enviar datos al navegador en JSON. +Una petición AJAX no difiere en principio de una petición HTTP clásica. Se llama a un presenter con ciertos parámetros. Y depende del presenter cómo reaccionará a la petición: puede devolver datos en formato JSON, enviar parte del código HTML, un documento XML, etc. -```php -public function actionDelete(int $id): void -{ - if ($this->isAjax()) { - $this->payload->message = 'Success'; - } - // ... -} +En el lado del navegador, inicializamos la petición AJAX usando la función `fetch()`: + +```js +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) +.then(response => response.json()) +.then(payload => { + // procesar la respuesta +}); ``` -Para un control total sobre su salida JSON utilice el método `sendJson` en su presentador. Terminará el presentador inmediatamente y prescindirá de una plantilla: +En el lado del servidor, reconocemos una petición AJAX con el método `$httpRequest->isAjax()` del servicio [que encapsula la petición HTTP |http:request]. Para la detección, utiliza la cabecera HTTP `X-Requested-With`, por lo que es importante enviarla. Dentro del presenter, se puede usar el método `$this->isAjax()`. + +Si desea enviar datos en formato JSON, use el método [`sendJson()` |presenters#Envío de la respuesta]. El método también finaliza la actividad del presenter. ```php -$this->sendJson(['key' => 'value', /* ... */]); +public function actionExport(): void +{ + $this->sendJson($this->model->getData); +} ``` -Si queremos enviar HTML, podemos establecer una plantilla especial para peticiones AJAX: +Si planea responder con una plantilla especial diseñada para AJAX, puede hacerlo de la siguiente manera: ```php public function handleClick($param): void @@ -45,222 +52,198 @@ public function handleClick($param): void ``` -Naja .[#toc-naja] -================= +Fragmentos (Snippets) +===================== + +El recurso más potente que ofrece Nette para conectar el servidor con el cliente son los fragmentos (snippets). Gracias a ellos, puedes convertir una aplicación ordinaria en una AJAX con un esfuerzo mínimo y unas pocas líneas de código. El ejemplo Fifteen demuestra cómo funciona todo, cuyo código puedes encontrar en [GitHub |https://github.com/nette-examples/fifteen]. + +Los fragmentos, o recortes, permiten actualizar solo partes de la página, en lugar de recargar toda la página. Esto no solo es más rápido y eficiente, sino que también proporciona una experiencia de usuario más cómoda. Los fragmentos pueden recordarle a Hotwire para Ruby on Rails o Symfony UX Turbo. Curiosamente, Nette introdujo los fragmentos 14 años antes. + +¿Cómo funcionan los fragmentos? En la primera carga de la página (petición no AJAX), se carga toda la página, incluidos todos los fragmentos. Cuando el usuario interactúa con la página (por ejemplo, hace clic en un botón, envía un formulario, etc.), en lugar de cargar toda la página, se realiza una petición AJAX. El código en el presenter ejecuta la acción y decide qué fragmentos deben actualizarse. Nette renderiza estos fragmentos y los envía en forma de array en formato JSON. El código de manejo en el navegador inserta los fragmentos recibidos de nuevo en la página. Por lo tanto, solo se transfiere el código de los fragmentos modificados, lo que ahorra ancho de banda y acelera la carga en comparación con la transferencia del contenido de toda la página. + -K obsluze AJAXových požadavků na straně prohlížeče slouží [knihovna Naja |https://naja.js.org], [instálalo |https://naja.js.org/#/guide/01-install-setup-naja] como un paquete node.js (para usarlo con Webpack, Rollup, Vite, Parcel y más): +Naja +---- + +Para manejar los fragmentos en el lado del navegador, se utiliza la [librería Naja |https://naja.js.org]. [Instálela |https://naja.js.org/#/guide/01-install-setup-naja] como un paquete node.js (para usar con aplicaciones Webpack, Rollup, Vite, Parcel y otras): ```shell npm install naja ``` -...o insertarlo directamente en la plantilla de la página: +…o insértela directamente en la plantilla de la página: ```html ``` +Primero, es necesario [inicializar |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] la librería: -Fragmentos .[#toc-snippets] -=========================== +```js +naja.initialize(); +``` -Existe una herramienta mucho más potente de soporte AJAX integrado: los snippets. Su uso permite convertir una aplicación normal en una aplicación AJAX utilizando sólo unas pocas líneas de código. Cómo funciona todo se demuestra en el ejemplo Fifteen cuyo código también está accesible en la compilación o en [GitHub |https://github.com/nette-examples/fifteen]. +Para convertir un enlace ordinario (señal) o el envío de un formulario en una petición AJAX, basta con marcar el enlace, formulario o botón correspondiente con la clase `ajax`: -La forma en que funcionan los snippets es que toda la página se transfiere durante la petición inicial (es decir, no AJAX) y luego con cada [subpetición |components#signal] AJAX (petición de la misma vista del mismo presentador) sólo se transfiere el código de las partes modificadas en el repositorio `payload` mencionado anteriormente. +```html +Ir -Puede que los snippets te recuerden a Hotwire para Ruby on Rails o a Symfony UX Turbo, pero Nette los inventó catorce años antes. +
    + +
    +o -Invalidación de Snippets .[#toc-invalidation-of-snippets] -========================================================= +
    + +
    +``` -Cada descendiente de la clase [Control |components] (que también es un Presentador) es capaz de recordar si hubo algún cambio durante una petición que requiera que se vuelva a renderizar. Hay un par de métodos para manejar esto: `redrawControl()` y `isControlInvalid()`. Un ejemplo: + +Redibujar fragmentos +-------------------- + +Cada objeto de la clase [Control |components] (incluido el propio Presenter) registra si se han producido cambios que requieran su redibujado. Para ello se utiliza el método `redrawControl()`: ```php public function handleLogin(string $user): void { - // The object has to re-render after the user has logged in + // después de iniciar sesión, es necesario redibujar la parte relevante $this->redrawControl(); // ... } ``` -Nette, sin embargo, ofrece una resolución aún más fina que la de los componentes completos. Los métodos mencionados aceptan el nombre de un "fragmento" como parámetro opcional. Un "fragmento" es básicamente un elemento de su plantilla marcado para ese propósito por una tag Latte, más sobre esto más adelante. Así, es posible pedir a un componente que redibuje sólo *partes* de su plantilla. Si se invalida todo el componente, entonces se vuelven a renderizar todos sus fragmentos. Un componente es "inválido" también si cualquiera de sus subcomponentes es inválido. - -```php -$this->isControlInvalid(); // -> false -$this->redrawControl('header'); // invalidates the snippet named 'header' -$this->isControlInvalid('header'); // -> true -$this->isControlInvalid('footer'); // -> false -$this->isControlInvalid(); // -> true, at least one snippet is invalid +Nette permite un control aún más fino de lo que se debe redibujar. De hecho, el método mencionado puede aceptar el nombre del fragmento como argumento. Por lo tanto, es posible invalidar (léase: forzar el redibujado) a nivel de partes de la plantilla. Si se invalida todo el componente, también se redibujará cada uno de sus fragmentos: -$this->redrawControl(); // invalidates the whole component, every snippet -$this->isControlInvalid('footer'); // -> true +```php +// invalida el fragmento 'header' +$this->redrawControl('header'); ``` -Un componente que recibe una señal se marca automáticamente para ser redibujado. - -Gracias al redibujado de fragmentos, sabemos exactamente qué partes de qué elementos deben redibujarse. - - -Etiqueta `{snippet} … {/snippet}` .{toc: Tag snippet} -===================================================== -El renderizado de la página procede de forma muy similar a una petición normal: se cargan las mismas plantillas, etc. Sin embargo, lo esencial es dejar fuera las partes que no deben llegar a la salida; las demás partes se asociarán a un identificador y se enviarán al usuario en un formato comprensible para un manipulador JavaScript. +Fragmentos en Latte +------------------- - -Sintaxis .[#toc-syntax] ------------------------ - -Si hay un control o un fragmento en la plantilla, tenemos que envolverlo usando la etiqueta `{snippet} ... {/snippet}` pair - se asegurará de que el fragmento renderizado será "recortado" y enviado al navegador. También lo encerrará en una etiqueta helper `
    ` (es posible utilizar otra). En el siguiente ejemplo se define un fragmento llamado `header`. También puede representar la plantilla de un componente: +Usar fragmentos en Latte es extremadamente fácil. Si desea definir una parte de la plantilla como un fragmento, simplemente envuélvala con las etiquetas `{snippet}` y `{/snippet}`: ```latte {snippet header} -

    Hello ...

    +

    Hola ...

    {/snippet} ``` -Si desea crear un fragmento con un elemento contenedor distinto de `
    ` o añadir atributos personalizados al elemento, puede utilizar la siguiente definición: +El fragmento crea un elemento `
    ` en la página HTML con un `id` especial generado. Al redibujar el fragmento, se actualiza el contenido de este elemento. Por lo tanto, es necesario que en la renderización inicial de la página se rendericen también todos los fragmentos, aunque puedan estar vacíos al principio. + +También puede crear un fragmento con un elemento distinto de `
    ` usando un n:attribute: ```latte
    -

    Hello ...

    +

    Hola ...

    ``` -Fragmentos dinámicos .[#toc-dynamic-snippets] -============================================= +Áreas de fragmentos +------------------- -En Nette también puede definir fragmentos con un nombre dinámico basado en un parámetro de tiempo de ejecución. Esto es más adecuado para varias listas en las que necesitamos cambiar sólo una fila pero no queremos transferir toda la lista junto con ella. Un ejemplo de esto sería: +Los nombres de los fragmentos también pueden ser expresiones: ```latte -
      - {foreach $list as $id => $item} -
    • {$item} update
    • - {/foreach} -
    +{foreach $items as $id => $item} +
  • {$item}
  • +{/foreach} ``` -Hay un fragmento estático llamado `itemsContainer`, que contiene varios fragmentos dinámicos: `item-0` `item-1` y así sucesivamente. +De esta manera, creamos varios fragmentos `item-0`, `item-1`, etc. Si invalidáramos directamente un fragmento dinámico (por ejemplo, `item-1`), no se redibujaría nada. La razón es que los fragmentos realmente funcionan como recortes y solo se renderizan ellos mismos directamente. Sin embargo, en la plantilla no hay realmente ningún fragmento llamado `item-1`. Este se crea solo al ejecutar el código alrededor del fragmento, es decir, el bucle foreach. Por lo tanto, marcamos la parte de la plantilla que se debe ejecutar usando la etiqueta `{snippetArea}`: -No puedes redibujar un fragmento dinámico directamente (redibujar `item-1` no tiene ningún efecto), tienes que redibujar su fragmento padre (en este ejemplo `itemsContainer`). Esto hace que se ejecute el código del fragmento padre, pero entonces sólo se envían al navegador sus sub fragmentos. Si desea enviar sólo uno de los sub fragmentos, debe modificar la entrada del fragmento padre para que no genere los otros sub fragmentos. +```latte +
      + {foreach $items as $id => $item} +
    • {$item}
    • + {/foreach} +
    +``` -En el ejemplo anterior tiene que asegurarse de que para una petición AJAX sólo se añadirá un elemento a la matriz `$list`, por lo que el bucle `foreach` sólo imprimirá un fragmento dinámico. +Y hacemos que se redibuje tanto el fragmento en sí como toda el área padre: ```php -class HomePresenter extends Nette\Application\UI\Presenter -{ - /** - * This method returns data for the list. - * Usually this would just request the data from a model. - * For the purpose of this example, the data is hard-coded. - */ - private function getTheWholeList(): array - { - return [ - 'First', - 'Second', - 'Third', - ]; - } - - public function renderDefault(): void - { - if (!isset($this->template->list)) { - $this->template->list = $this->getTheWholeList(); - } - } - - public function handleUpdate(int $id): void - { - $this->template->list = $this->isAjax() - ? [] - : $this->getTheWholeList(); - $this->template->list[$id] = 'Updated item'; - $this->redrawControl('itemsContainer'); - } -} +$this->redrawControl('itemsContainer'); +$this->redrawControl('item-1'); ``` +Al mismo tiempo, es conveniente asegurarse de que el array `$items` contenga solo los elementos que se deben redibujar. -Fragmentos en una plantilla incluida .[#toc-snippets-in-an-included-template] -============================================================================= - -Puede ocurrir que el snippet esté en una plantilla que está siendo incluida desde una plantilla diferente. En ese caso necesitamos envolver el código de inclusión en la segunda plantilla con la tag `snippetArea`, entonces redibujamos tanto el snippetArea como el snippet real. - -La tag `snippetArea` garantiza que se ejecute el código que contiene, pero que sólo se envíe al navegador el fragmento real de la plantilla incluida. +Si incluimos otra plantilla que contiene fragmentos en la plantilla usando la etiqueta `{include}`, es necesario incluir de nuevo la inclusión de la plantilla en `snippetArea` e invalidarla junto con el fragmento: ```latte -{* parent.latte *} -{snippetArea wrapper} - {include 'child.latte'} +{snippetArea include} + {include 'included.latte'} {/snippetArea} ``` + ```latte -{* child.latte *} +{* included.latte *} {snippet item} -... + ... {/snippet} ``` + ```php -$this->redrawControl('wrapper'); +$this->redrawControl('include'); $this->redrawControl('item'); ``` -También se puede combinar con fragmentos dinámicos. - -Añadir y eliminar .[#toc-adding-and-deleting] -============================================= +Fragmentos en componentes +------------------------- -Si añades un nuevo elemento a la lista e invalidas `itemsContainer`, la petición AJAX devuelve fragmentos que incluyen el nuevo, pero el manejador javascript no podrá renderizarlo. Esto se debe a que no hay ningún elemento HTML con el ID recién creado. - -En este caso, la forma más sencilla es envolver toda la lista en un fragmento más e invalidarlo todo: +También puede crear fragmentos en [componentes|components] y Nette los redibujará automáticamente. Pero hay una cierta limitación: para redibujar los fragmentos, llama al método `render()` sin parámetros. Por lo tanto, no funcionará pasar parámetros en la plantilla: ```latte -{snippet wholeList} -
      - {foreach $list as $id => $item} -
    • {$item} update
    • - {/foreach} -
    -{/snippet} -Add +OK +{control productGrid} + +no funcionará: +{control productGrid $arg, $arg} +{control productGrid:paginator} ``` + +Envío de datos de usuario +------------------------- + +Junto con los fragmentos, puede enviar cualquier otro dato al cliente. Simplemente escríbalos en el objeto `payload`: + ```php -public function handleAdd(): void +public function actionDelete(int $id): void { - $this->template->list = $this->getTheWholeList(); - $this->template->list[] = 'New one'; - $this->redrawControl('wholeList'); + // ... + if ($this->isAjax()) { + $this->payload->message = 'Éxito'; + } } ``` -Lo mismo ocurre con la eliminación de un elemento. Sería posible enviar un fragmento vacío, pero normalmente las listas pueden paginarse y sería complicado implementar la eliminación de un elemento y la carga de otro (que solía estar en una página diferente de la lista paginada). - -Envío de parámetros al componente .[#toc-sending-parameters-to-component] -========================================================================= +Paso de parámetros +================== -Cuando enviamos parámetros al componente a través de una petición AJAX, ya sean parámetros de señal o parámetros persistentes, debemos proporcionar su nombre global, que también contiene el nombre del componente. El nombre completo del parámetro devuelve el método `getParameterId()`. +Si enviamos parámetros a un componente mediante una petición AJAX, ya sean parámetros de señal o parámetros persistentes, debemos indicar en la petición su nombre global, que también incluye el nombre del componente. El nombre completo del parámetro lo devuelve el método `getParameterId()`. ```js -$.getJSON( - {link changeCountBasket!}, - { - {$control->getParameterId('id')}: id, - {$control->getParameterId('count')}: count - } -}); +let url = new URL({link //foo!}); +url.searchParams.set({$control->getParameterId('bar')}, bar); + +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) ``` -Y manejar el método con s parámetros correspondientes en el componente. +Y el método handle con los parámetros correspondientes en el componente: ```php -public function handleChangeCountBasket(int $id, int $count): void +public function handleFoo(int $bar): void { - } ``` diff --git a/application/es/bootstrap.texy b/application/es/bootstrap.texy deleted file mode 100644 index ef1140b871..0000000000 --- a/application/es/bootstrap.texy +++ /dev/null @@ -1,233 +0,0 @@ -Bootstrap -********* - -
    - -Bootstrap es el código de arranque que inicializa el entorno, crea un contenedor de inyección de dependencias (DI) e inicia la aplicación. Vamos a discutir: - -- cómo configurar su aplicación utilizando archivos NEON -- cómo manejar los modos de producción y desarrollo -- cómo crear el contenedor DI - -
    - - -Las aplicaciones, ya sean basadas en web o en scripts de línea de comandos, comienzan por algún tipo de inicialización del entorno. En la antigüedad, podía ser un archivo llamado por ejemplo `include.inc.php` el que se encargaba de esto, y se incluía en el archivo inicial. -En las aplicaciones Nette modernas, ha sido sustituido por la clase `Bootstrap`, que como parte de la aplicación se puede encontrar en el `app/Bootstrap.php`. Por ejemplo, podría tener este aspecto: - -```php -use Nette\Bootstrap\Configurator; - -class Bootstrap -{ - public static function boot(): Configurator - { - $appDir = dirname(__DIR__); - $configurator = new Configurator; - //$configurator->setDebugMode('secret@23.75.345.200'); - $configurator->enableTracy($appDir . '/log'); - $configurator->setTempDirectory($appDir . '/temp'); - $configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); - $configurator->addConfig($appDir . '/config/common.neon'); - return $configurator; - } -} -``` - - -index.php .[#toc-index-php] -=========================== - -En el caso de las aplicaciones web, el archivo inicial es `index.php`, que se encuentra en el directorio público `www/`. Permite a la clase `Bootstrap` inicializar el entorno y devolver el `$configurator` que crea el contenedor DI. Luego obtiene el servicio `Application`, que ejecuta la aplicación web: - -```php -// initialize the environment + get Configurator object -$configurator = App\Bootstrap::boot(); -// create a DI container -$container = $configurator->createContainer(); -// DI container creates a Nette\Application\Application object -$application = $container->getByType(Nette\Application\Application::class); -// start Nette application -$application->run(); -``` - -Como puedes ver, la clase [api:Nette\Bootstrap\Configurator], que ahora presentaremos con más detalle, ayuda a configurar el entorno y a crear un contenedor de inyección de dependencias (DI). - - -Modo Desarrollo vs Producción .[#toc-development-vs-production-mode] -==================================================================== - -Nette distingue entre dos modos básicos en los que se ejecuta una petición: desarrollo y producción. El modo de desarrollo está enfocado a la máxima comodidad del programador, se visualiza Tracy, la caché se actualiza automáticamente al cambiar las plantillas o la configuración del contenedor DI, etc. El modo de producción está enfocado al rendimiento, Tracy sólo registra los errores y no se comprueban los cambios de plantillas y otros ficheros. - -La selección del modo se hace por autodetección, por lo que normalmente no hay necesidad de configurar o cambiar nada manualmente. El modo es desarrollo si la aplicación se ejecuta en localhost (es decir, la dirección IP `127.0.0.1` o `::1`) y no hay proxy presente (es decir, su cabecera HTTP). De lo contrario, se ejecuta en modo de producción. - -Si desea habilitar el modo de desarrollo en otros casos, por ejemplo, para los programadores que acceden desde una dirección IP específica, puede utilizar `setDebugMode()`: - -```php -$configurator->setDebugMode('23.75.345.200'); // one or more IP addresses -``` - -Recomendamos encarecidamente combinar una dirección IP con una cookie. Almacenaremos un token secreto en la cookie `nette-debug`, por ejemplo `secret1234`, y el modo de desarrollo se activará para los programadores con esta combinación de IP y cookie. - -```php -$configurator->setDebugMode('secret1234@23.75.345.200'); -``` - -También podemos desactivar completamente el modo de desarrollo, incluso para localhost: - -```php -$configurator->setDebugMode(false); -``` - -Nótese que el valor `true` activa el modo desarrollador por fuerza, lo que nunca debería ocurrir en un servidor de producción. - - -Herramienta de depuración Tracy .[#toc-debugging-tool-tracy] -============================================================ - -Para facilitar la depuración, activaremos la gran herramienta [Tracy |tracy:]. En modo desarrollador visualiza los errores y en modo producción los registra en el directorio especificado: - -```php -$configurator->enableTracy($appDir . '/log'); -``` - - -Archivos temporales .[#toc-temporary-files] -=========================================== - -Nette utiliza la caché para el contenedor DI, RobotLoader, plantillas, etc. Por lo tanto es necesario establecer la ruta al directorio donde se almacenará la caché: - -```php -$configurator->setTempDirectory($appDir . '/temp'); -``` - -En Linux o macOS, establezca los [permisos de escritura |nette:troubleshooting#Setting directory permissions] para los directorios `log/` y `temp/`. - - -RobotLoader .[#toc-robotloader] -=============================== - -Normalmente, querremos cargar automáticamente las clases usando [RobotLoader |robot-loader:], así que tenemos que iniciarlo y dejar que cargue las clases desde el directorio donde se encuentra `Bootstrap.php` (es decir, `__DIR__`) y todos sus subdirectorios: - -```php -$configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); -``` - -Una forma alternativa es utilizar únicamente la carga automática de [Composer |best-practices:composer] PSR-4. - - -Zona horaria .[#toc-timezone] -============================= - -Configurator le permite especificar una zona horaria para su aplicación. - -```php -$configurator->setTimeZone('Europe/Prague'); -``` - - -Configuración del Contenedor DI .[#toc-di-container-configuration] -================================================================== - -Parte del proceso de arranque es la creación de un contenedor DI, es decir, una fábrica de objetos, que es el corazón de toda la aplicación. En realidad es una clase PHP generada por Nette y almacenada en un directorio caché. La fábrica produce objetos clave de la aplicación y los archivos de configuración le indican cómo crearlos y configurarlos, y así influimos en el comportamiento de toda la aplicación. - -Los ficheros de configuración suelen estar escritos en [formato NEON |neon:format]. Puede leer [lo que se puede configurar aquí |nette:configuring]. - -.[tip] -En el modo de desarrollo, el contenedor se actualiza automáticamente cada vez que cambia el código o los archivos de configuración. En el modo de producción, se genera sólo una vez y los cambios en los archivos no se comprueban para maximizar el rendimiento. - -Los archivos de configuración se cargan utilizando `addConfig()`: - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -``` - -El método `addConfig()` se puede llamar varias veces para añadir varios archivos. - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -$configurator->addConfig($appDir . '/config/local.neon'); -if (PHP_SAPI === 'cli') { - $configurator->addConfig($appDir . '/config/cli.php'); -} -``` - -El nombre `cli.php` no es una errata, la configuración también se puede escribir en un archivo PHP, que la devuelve como un array. - -Alternativamente, podemos utilizar la [sección`includes` |dependency-injection:configuration#including files] para cargar más archivos de configuración. - -Si aparecen elementos con las mismas claves dentro de los archivos de configuración, se [sobrescribirán o fusionarán |dependency-injection:configuration#Merging] en el caso de los arrays. El fichero incluido más tarde tiene una prioridad mayor que el anterior. El fichero en el que aparece la sección `includes` tiene mayor prioridad que los ficheros incluidos en él. - - -Parámetros estáticos .[#toc-static-parameters] ----------------------------------------------- - -Los parámetros utilizados en los archivos de configuración pueden definirse [en la sección `parameters` |dependency-injection:configuration#parameters] y también pasarse (o sobrescribirse) por el método `addStaticParameters()` (tiene el alias `addParameters()`). Es importante que los diferentes valores de los parámetros provoquen la generación de contenedores DI adicionales, es decir, clases adicionales. - -```php -$configurator->addStaticParameters([ - 'projectId' => 23, -]); -``` - -En los archivos de configuración, podemos escribir la notación habitual `%projectId%` para acceder al parámetro denominado `projectId`. Por defecto, el Configurador rellena los siguientes parámetros: `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` y `consoleMode`. - - -Parámetros dinámicos .[#toc-dynamic-parameters] ------------------------------------------------ - -También podemos añadir parámetros dinámicos al contenedor, sus diferentes valores, a diferencia de los parámetros estáticos, no provocarán la generación de nuevos contenedores DI. - -```php -$configurator->addDynamicParameters([ - 'remoteIp' => $_SERVER['REMOTE_ADDR'], -]); -``` - -Las variables de entorno podrían estar fácilmente disponibles utilizando parámetros dinámicos. Podemos acceder a ellas a través de `%env.variable%` en archivos de configuración. - -```php -$configurator->addDynamicParameters([ - 'env' => getenv(), -]); -``` - - -Servicios importados .[#toc-imported-services] ----------------------------------------------- - -Ahora vamos a profundizar más. Aunque el propósito de un contenedor DI es crear objetos, excepcionalmente puede existir la necesidad de insertar un objeto existente en el contenedor. Esto lo hacemos definiendo el servicio con el atributo `imported: true`. - -```neon -services: - myservice: - type: App\Model\MyCustomService - imported: true -``` - -Creamos una nueva instancia y la insertamos en bootstrap: - -```php -$configurator->addServices([ - 'myservice' => new App\Model\MyCustomService('foobar'), -]); -``` - - -Diferentes entornos .[#toc-different-environments] -================================================== - -Siéntete libre de personalizar la clase `Bootstrap` para adaptarla a tus necesidades. Puedes añadir parámetros al método `boot()` para diferenciar proyectos web, o añadir otros métodos, como `bootForTests()`, que inicializa el entorno para pruebas unitarias, `bootForCli()` para scripts llamados desde la línea de comandos, etc. - -```php -public static function bootForTests(): Configurator -{ - $configurator = self::boot(); - Tester\Environment::setup(); // Nette Tester initialization - return $configurator; -} -``` diff --git a/application/es/bootstrapping.texy b/application/es/bootstrapping.texy new file mode 100644 index 0000000000..88870cd20d --- /dev/null +++ b/application/es/bootstrapping.texy @@ -0,0 +1,297 @@ +Bootstrapping +************* + +
    + +El bootstrapping es el proceso de inicialización del entorno de la aplicación, creación del contenedor de inyección de dependencias (DI) e inicio de la aplicación. Discutiremos: + +- cómo la clase Bootstrap inicializa el entorno +- cómo las aplicaciones se configuran usando archivos NEON +- cómo distinguir entre modo de producción y desarrollo +- cómo crear y configurar el contenedor DI + +
    + + +Las aplicaciones, ya sean web o scripts ejecutados desde la línea de comandos, comienzan su ejecución con alguna forma de inicialización del entorno. En tiempos pasados, esto solía estar a cargo de un archivo llamado, por ejemplo, `include.inc.php`, que el archivo inicial incluía. En las aplicaciones Nette modernas, ha sido reemplazado por la clase `Bootstrap`, que como parte de la aplicación se encuentra en el archivo `app/Bootstrap.php`. Puede verse, por ejemplo, así: + +```php +use Nette\Bootstrap\Configurator; + +class Bootstrap +{ + private Configurator $configurator; + private string $rootDir; + + public function __construct() + { + $this->rootDir = dirname(__DIR__); + // El configurador es responsable de configurar el entorno de la aplicación y los servicios. + $this->configurator = new Configurator; + // Establece el directorio para los archivos temporales generados por Nette (p. ej., plantillas compiladas) + $this->configurator->setTempDirectory($this->rootDir . '/temp'); + } + + public function bootWebApplication(): Nette\DI\Container + { + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); + } + + private function initializeEnvironment(): void + { + // Nette es inteligente y el modo de desarrollo se activa automáticamente, + // o puedes habilitarlo para una dirección IP específica descomentando la siguiente línea: + // $this->configurator->setDebugMode('secret@23.75.345.200'); + + // Activa Tracy: la "navaja suiza" definitiva para la depuración. + $this->configurator->enableTracy($this->rootDir . '/log'); + + // RobotLoader: carga automáticamente todas las clases en el directorio seleccionado + $this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); + } + + private function setupContainer(): void + { + // Carga los archivos de configuración + $this->configurator->addConfig($this->rootDir . '/config/common.neon'); + } +} +``` + + +index.php +========= + +El archivo inicial en el caso de las aplicaciones web es `index.php`, que se encuentra en el [directorio público |directory-structure#Directorio público www] `www/`. Este solicita a la clase Bootstrap que inicialice el entorno y cree el contenedor DI. Luego, obtiene de él el servicio `Application`, que ejecuta la aplicación web: + +```php +$bootstrap = new App\Bootstrap; +// Inicialización del entorno + creación del contenedor DI +$container = $bootstrap->bootWebApplication(); +// El contenedor DI crea un objeto Nette\Application\Application +$application = $container->getByType(Nette\Application\Application::class); +// Ejecución de la aplicación Nette y procesamiento de la petición entrante +$application->run(); +``` + +Como se puede ver, la clase [api:Nette\Bootstrap\Configurator] ayuda con la configuración del entorno y la creación del contenedor de inyección de dependencias (DI), que ahora presentaremos con más detalle. + + +Modo de desarrollo vs producción +================================ + +Nette se comporta de manera diferente dependiendo de si se ejecuta en un servidor de desarrollo o de producción: + +🛠️ Modo de desarrollo (Development): + - Muestra la barra de depuración de Tracy con información útil (consultas SQL, tiempo de ejecución, memoria utilizada) + - En caso de error, muestra una página de error detallada con llamadas a funciones y contenido de variables + - Actualiza automáticamente la caché al cambiar las plantillas Latte, modificar archivos de configuración, etc. + + +🚀 Modo de producción (Production): + - No muestra ninguna información de depuración, todos los errores se registran en el log + - En caso de error, muestra ErrorPresenter o una página genérica "Server Error" + - ¡La caché nunca se actualiza automáticamente! + - Optimizado para velocidad y seguridad + + +La elección del modo se realiza por autodetección, por lo que generalmente no es necesario configurar nada ni cambiar manualmente: + +- modo de desarrollo: en localhost (dirección IP `127.0.0.1` o `::1`) si no hay proxy presente (es decir, su cabecera HTTP) +- modo de producción: en cualquier otro lugar + +Si queremos habilitar el modo de desarrollo también en otros casos, por ejemplo, para programadores que acceden desde una dirección IP específica, usamos `setDebugMode()`: + +```php +$this->configurator->setDebugMode('23.75.345.200'); // también se puede indicar un array de direcciones IP +``` + +Definitivamente recomendamos combinar la dirección IP con una cookie. Guardamos un token secreto en la cookie `nette-debug`, por ejemplo, `secret1234`, y de esta manera activamos el modo de desarrollo para los programadores que acceden desde una dirección IP específica y que además tienen el token mencionado en la cookie: + +```php +$this->configurator->setDebugMode('secret1234@23.75.345.200'); +``` + +También podemos desactivar completamente el modo de desarrollo, incluso para localhost: + +```php +$this->configurator->setDebugMode(false); +``` + +Atención, el valor `true` activa el modo de desarrollo de forma permanente, lo cual nunca debe ocurrir en un servidor de producción. + + +Herramienta de depuración Tracy +=============================== + +Para facilitar la depuración, también activaremos la excelente herramienta [Tracy |tracy:]. En el modo de desarrollo, visualiza los errores y en el modo de producción, registra los errores en el directorio especificado: + +```php +$this->configurator->enableTracy($this->rootDir . '/log'); +``` + + +Archivos temporales +=================== + +Nette utiliza caché para el contenedor DI, RobotLoader, plantillas, etc. Por lo tanto, es necesario establecer la ruta al directorio donde se almacenará la caché: + +```php +$this->configurator->setTempDirectory($this->rootDir . '/temp'); +``` + +En Linux o macOS, establezca [permisos de escritura |nette:troubleshooting#Configuración de permisos de directorio] para los directorios `log/` y `temp/`. + + +RobotLoader +=========== + +Generalmente, querremos cargar clases automáticamente usando [RobotLoader |robot-loader:], por lo que debemos iniciarlo y hacer que cargue clases desde el directorio donde se encuentra `Bootstrap.php` (es decir, `__DIR__`), y todos los subdirectorios: + +```php +$this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); +``` + +Un enfoque alternativo es dejar que las clases se carguen solo a través de [Composer |best-practices:composer] cumpliendo con PSR-4. + + +Zona horaria +============ + +A través del configurador, puede establecer la zona horaria predeterminada. + +```php +$this->configurator->setTimeZone('Europe/Prague'); +``` + + +Configuración del contenedor DI +=============================== + +Parte del proceso de arranque es la creación del contenedor DI o fábrica de objetos, que es el corazón de toda la aplicación. En realidad, es una clase PHP que Nette genera y guarda en el directorio de caché. La fábrica produce los objetos clave de la aplicación y, mediante archivos de configuración, le instruimos cómo debe crearlos y configurarlos, influyendo así en el comportamiento de toda la aplicación. + +Los archivos de configuración generalmente se escriben en formato [NEON |neon:format]. En un capítulo aparte, aprenderá [qué se puede configurar |nette:configuring]. + +.[tip] +En el modo de desarrollo, el contenedor se actualiza automáticamente cada vez que se cambia el código o los archivos de configuración. En el modo de producción, se genera solo una vez y los cambios no se verifican para maximizar el rendimiento. + +Cargamos los archivos de configuración usando `addConfig()`: + +```php +$this->configurator->addConfig($this->rootDir . '/config/common.neon'); +``` + +Si queremos agregar más archivos de configuración, podemos llamar a la función `addConfig()` varias veces. + +```php +$configDir = $this->rootDir . '/config'; +$this->configurator->addConfig($configDir . '/common.neon'); +$this->configurator->addConfig($configDir . '/services.neon'); +if (PHP_SAPI === 'cli') { + $this->configurator->addConfig($configDir . '/cli.php'); +} +``` + +El nombre `cli.php` no es un error tipográfico, la configuración también puede estar escrita en un archivo PHP que la devuelve como un array. + +También podemos agregar otros archivos de configuración en la [sección `includes` |dependency-injection:configuration#Inclusión de archivos]. + +Si aparecen elementos con las mismas claves en los archivos de configuración, se sobrescribirán o, en el caso de [arrays, se fusionarán |dependency-injection:configuration#Fusión]. El archivo cargado posteriormente tiene mayor prioridad que el anterior. El archivo en el que se indica la sección `includes` tiene mayor prioridad que los archivos incluidos en él. + + +Parámetros estáticos +-------------------- + +Los parámetros utilizados en los archivos de configuración se pueden definir en la [sección `parameters` |dependency-injection:configuration#Parámetros] y también se pueden pasar (o sobrescribir) con el método `addStaticParameters()` (tiene el alias `addParameters()`). Es importante que diferentes valores de parámetros provoquen la generación de contenedores DI adicionales, es decir, clases adicionales. + +```php +$this->configurator->addStaticParameters([ + 'projectId' => 23, +]); +``` + +Al parámetro `projectId` se puede hacer referencia en la configuración con la notación habitual `%projectId%`. + + +Parámetros dinámicos +-------------------- + +También podemos agregar parámetros dinámicos al contenedor, cuyos diferentes valores, a diferencia de los parámetros estáticos, no provocan la generación de nuevos contenedores DI. + +```php +$this->configurator->addDynamicParameters([ + 'remoteIp' => $_SERVER['REMOTE_ADDR'], +]); +``` + +De esta manera, podemos agregar fácilmente, por ejemplo, variables de entorno, a las que luego se puede hacer referencia en la configuración con la notación `%env.variable%`. + +```php +$this->configurator->addDynamicParameters([ + 'env' => getenv(), +]); +``` + + +Parámetros predeterminados +-------------------------- + +En los archivos de configuración, puede utilizar estos parámetros estáticos: + +- `%appDir%` es la ruta absoluta al directorio con el archivo `Bootstrap.php` +- `%wwwDir%` es la ruta absoluta al directorio con el archivo de entrada `index.php` +- `%tempDir%` es la ruta absoluta al directorio para archivos temporales +- `%vendorDir%` es la ruta absoluta al directorio donde Composer instala las librerías +- `%rootDir%` es la ruta absoluta al directorio raíz del proyecto +- `%debugMode%` indica si la aplicación está en modo de depuración +- `%consoleMode%` indica si la petición llegó a través de la línea de comandos + + +Servicios importados +-------------------- + +Ahora vamos más profundo. Aunque el propósito del contenedor DI es fabricar objetos, excepcionalmente puede surgir la necesidad de insertar un objeto existente en el contenedor. Hacemos esto definiendo el servicio con el indicador `imported: true`. + +```neon +services: + myservice: + type: App\Model\MyCustomService + imported: true +``` + +Y en bootstrap insertamos el objeto en el contenedor: + +```php +$this->configurator->addServices([ + 'myservice' => new App\Model\MyCustomService('foobar'), +]); +``` + + +Entorno diferente +================= + +No dude en modificar la clase Bootstrap según sus necesidades. Puede agregar parámetros al método `bootWebApplication()` para distinguir proyectos web. O podemos agregar otros métodos, por ejemplo, `bootTestEnvironment()`, que inicializa el entorno para pruebas unitarias, `bootConsoleApplication()` para scripts llamados desde la línea de comandos, etc. + +```php +public function bootTestEnvironment(): Nette\DI\Container +{ + Tester\Environment::setup(); // inicialización de Nette Tester + $this->setupContainer(); + return $this->configurator->createContainer(); +} + +public function bootConsoleApplication(): Nette\DI\Container +{ + $this->configurator->setDebugMode(false); + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); +} +``` diff --git a/application/es/components.texy b/application/es/components.texy index d3dad11756..e4cce895a9 100644 --- a/application/es/components.texy +++ b/application/es/components.texy @@ -3,7 +3,7 @@ Componentes interactivos
    -Los componentes son objetos separados reutilizables que colocamos en las páginas. Pueden ser formularios, datagrids, encuestas, de hecho cualquier cosa que tenga sentido usar repetidamente. Lo mostraremos: +Los componentes son objetos reutilizables independientes que insertamos en las páginas. Pueden ser formularios, datagrids, encuestas, en realidad cualquier cosa que tenga sentido usar repetidamente. Mostraremos: - ¿cómo usar componentes? - ¿cómo escribirlos? @@ -11,19 +11,19 @@ Los componentes son objetos separados reutilizables que colocamos en las página
    -Nette tiene un sistema de componentes incorporado. Los más veteranos recordarán algo similar de Delphi o ASP.NET Web Forms. React o Vue.js están construidos sobre algo remotamente similar. Sin embargo, en el mundo de los frameworks PHP, esta es una característica completamente única. +Nette tiene incorporado un sistema de componentes. Algo similar pueden recordar los veteranos de Delphi o ASP.NET Web Forms, algo remotamente similar es la base de React o Vue.js. Sin embargo, en el mundo de los frameworks PHP, es una característica única. -Al mismo tiempo, los componentes cambian fundamentalmente el enfoque del desarrollo de aplicaciones. Puedes componer páginas a partir de unidades pre-preparadas. ¿Necesitas datagrid en la administración? Puedes encontrarlo en [Componette |https://componette.org/search/component], un repositorio de complementos de código abierto (no sólo componentes) para Nette, y simplemente pegarlo en el presentador. +Mientras tanto, los componentes influyen fundamentalmente en el enfoque para la creación de aplicaciones. Puede componer páginas a partir de unidades prefabricadas. ¿Necesita un datagrid en la administración? Lo encontrará en [Componette |https://componette.org/search/component], un repositorio de complementos de código abierto (es decir, no solo componentes) para Nette y simplemente insértelo en el presenter. -Puede incorporar cualquier número de componentes en el presentador. Y puede insertar otros componentes en algunos componentes. Esto crea un árbol de componentes con un presentador como raíz. +Puede incorporar cualquier número de componentes en el presenter. Y en algunos componentes puede insertar otros componentes. Esto crea un árbol de componentes, cuya raíz es el presenter. -Métodos de fábrica .[#toc-factory-methods] -========================================== +Métodos de fábrica +================== -¿Cómo se colocan y utilizan posteriormente los componentes en el presentador? Normalmente utilizando métodos de fábrica. +¿Cómo se insertan los componentes en el presenter y se usan posteriormente? Generalmente mediante métodos de fábrica. -La fábrica de componentes es una forma elegante de crear componentes sólo cuando son realmente necesarios (lazy / on-demand). Toda la magia está en la implementación de un método llamado `createComponent()`donde `` es el nombre del componente, que creará y devolverá. +Una fábrica de componentes es una forma elegante de crear componentes solo cuando realmente se necesitan (lazy / on demand). Toda la magia reside en la implementación de un método llamado `createComponent()`, donde `` es el nombre del componente a crear, y que crea y devuelve el componente. ```php .{file:DefaultPresenter.php} class DefaultPresenter extends Nette\Application\UI\Presenter @@ -37,43 +37,43 @@ class DefaultPresenter extends Nette\Application\UI\Presenter } ``` -Como todos los componentes se crean en métodos separados, el código es más limpio y fácil de leer. +Gracias a que todos los componentes se crean en métodos separados, el código gana en claridad. .[note] -Los nombres de los componentes empiezan siempre con minúscula, aunque se escriben en mayúscula en el nombre del método. +Los nombres de los componentes siempre comienzan con una letra minúscula, aunque en el nombre del método se escriban con mayúscula. -Nunca llamamos a las fábricas directamente, se llaman automáticamente, cuando usamos componentes por primera vez. Gracias a ello, un componente se crea en el momento adecuado, y sólo si es realmente necesario. Si no usáramos el componente (por ejemplo en alguna petición AJAX, donde devolvemos sólo parte de la página, o cuando se almacenan partes en caché), ni siquiera se crearía y ahorramos rendimiento del servidor. +Nunca llamamos a las fábricas directamente, se llaman solas en el momento en que usamos el componente por primera vez. Gracias a esto, el componente se crea en el momento adecuado y solo si realmente es necesario. Si no usamos el componente (por ejemplo, en una petición AJAX donde solo se transfiere una parte de la página, o al almacenar en caché la plantilla), no se crea en absoluto y ahorramos rendimiento del servidor. ```php .{file:DefaultPresenter.php} -// accedemos al componente y si es la primera vez -// se llama a createComponentPoll() para crearlo +// accedemos al componente y si fue la primera vez, +// se llama a createComponentPoll() que lo crea $poll = $this->getComponent('poll'); // sintaxis alternativa: $poll = $this['poll']; ``` -En la plantilla, puedes renderizar un componente usando la etiqueta [{control} |#Rendering]. Así que no hay necesidad de pasar manualmente los componentes a la plantilla. +En la plantilla, es posible renderizar el componente usando la etiqueta [{control} |#Renderizado]. Por lo tanto, no es necesario pasar manualmente los componentes a la plantilla. ```latte -

    Please Vote

    +

    Votar

    {control poll} ``` -Estilo Hollywood .[#toc-hollywood-style] -======================================== +Estilo Hollywood +================ -Los componentes suelen utilizar una técnica genial, que nos gusta llamar estilo Hollywood. Seguro que conoces el tópico que los actores oyen a menudo en los castings: "No nos llame a nosotros, nosotros le llamaremos a usted". Y de eso se trata. +Los componentes suelen utilizar una técnica fresca, que nos gusta llamar estilo Hollywood. Seguramente conoce la frase célebre que tan a menudo escuchan los participantes en las audiciones de cine: "No nos llame, nosotros le llamaremos". Y de eso se trata precisamente. -En Nette, en lugar de tener que hacer preguntas constantemente ("¿se ha enviado el formulario?", "¿era válido?" o "¿alguien ha pulsado este botón?"), le dices al framework "cuando ocurra esto, llama a este método" y dejas que siga trabajando en ello. Si programas en JavaScript, estás familiarizado con este estilo de programación. Escribes funciones que se llaman cuando ocurre un determinado evento. Y el motor les pasa los parámetros apropiados. +En Nette, en lugar de tener que preguntar constantemente ("¿se envió el formulario?", "¿fue válido?" o "¿presionó el usuario este botón?"), le dice al framework "cuando suceda, llama a este método" y deja el resto del trabajo en él. Si programa en JavaScript, conoce íntimamente este estilo de programación. Escribe funciones que se llaman cuando ocurre un evento determinado. Y el lenguaje les pasa los parámetros apropiados. -Esto cambia por completo la forma de escribir aplicaciones. Cuantas más tareas puedas delegar en el framework, menos trabajo tendrás. Y menos puedes olvidar. +Esto cambia por completo la perspectiva sobre la escritura de aplicaciones. Cuantas más tareas pueda dejar en manos del framework, menos trabajo tendrá usted. Y menos cosas podrá olvidar. -Cómo escribir un componente .[#toc-how-to-write-a-component] -============================================================ +Escribiendo un componente +========================= -Por componente solemos entender descendientes de la clase [api:Nette\Application\UI\Control]. El propio presentador [api:Nette\Application\UI\Presenter] también es descendiente de la clase `Control`. +Bajo el término componente, generalmente nos referimos a un descendiente de la clase [api:Nette\Application\UI\Control]. (Por lo tanto, sería más preciso usar el término "controls", pero "controles" tiene un significado completamente diferente en español y más bien se ha impuesto "componentes".) El propio presenter [api:Nette\Application\UI\Presenter] es, por cierto, también un descendiente de la clase `Control`. ```php .{file:PollControl.php} use Nette\Application\UI\Control; @@ -84,17 +84,17 @@ class PollControl extends Control ``` -Presentación de .[#toc-rendering] -================================= +Renderizado +=========== -Ya sabemos que la etiqueta `{control componentName}` se utiliza para dibujar un componente. En realidad llama al método `render()` del componente, en el que nos encargamos de la renderización. Tenemos, al igual que en el presentador, una plantilla [Latte |latte:] en la variable `$this->template`, a la que pasamos los parámetros. A diferencia del uso en un presentador, debemos especificar un archivo de plantilla y dejar que se renderice: +Ya sabemos que para renderizar un componente se usa la etiqueta `{control componentName}`. Esta en realidad llama al método `render()` del componente, en el que nos encargamos del renderizado. Tenemos disponible, al igual que en el presenter, una [plantilla Latte|templates] en la variable `$this->template`, a la que pasamos parámetros. A diferencia del presenter, debemos indicar el archivo con la plantilla y hacer que se renderice: ```php .{file:PollControl.php} public function render(): void { - // pondremos algunos parámetros en la plantilla + // insertamos algunos parámetros en la plantilla $this->template->param = $value; - // y la dibujaremos + // y la renderizamos $this->template->render(__DIR__ . '/poll.latte'); } ``` @@ -112,7 +112,7 @@ public function render(int $id, string $message): void } ``` -A veces un componente puede constar de varias partes que queremos renderizar por separado. Para cada una de ellas crearemos nuestro propio método de renderizado, aquí está por ejemplo `renderPaginator()`: +A veces, un componente puede constar de varias partes que queremos renderizar por separado. Para cada una de ellas, creamos nuestro propio método de renderizado, aquí en el ejemplo, por ejemplo, `renderPaginator()`: ```php .{file:PollControl.php} public function renderPaginator(): void @@ -121,107 +121,107 @@ public function renderPaginator(): void } ``` -Y en la plantilla lo llamamos usando: +Y en la plantilla, luego la llamamos usando: ```latte {control poll:paginator} ``` -Para una mejor comprensión es bueno saber cómo se compila la etiqueta a código PHP. +Para una mejor comprensión, es bueno saber cómo se traduce esta etiqueta a PHP. ```latte {control poll} {control poll:paginator 123, 'hello'} ``` -Esto se compila a: +se traduce como: ```php $control->getComponent('poll')->render(); $control->getComponent('poll')->renderPaginator(123, 'hello'); ``` -`getComponent()` el método devuelve el componente `poll` y luego se llama sobre él al método `render()` o `renderPaginator()`, respectivamente. +El método `getComponent()` devuelve el componente `poll` y sobre este componente llama al método `render()`, respectivamente `renderPaginator()` si se indica otro método de renderizado en la etiqueta después de los dos puntos. .[caution] -Si en algún lugar de la parte de parámetros se utiliza **`=>`**, todos los parámetros se envolverán con una matriz y se pasarán como primer argumento: +Atención, si en cualquier lugar de los parámetros aparece **`=>`**, todos los parámetros se empaquetarán en un array y se pasarán como primer argumento: ```latte {control poll, id: 123, message: 'hello'} ``` -compila a: +se traduce como: ```php $control->getComponent('poll')->render(['id' => 123, 'message' => 'hello']); ``` -Renderización del subcomponente: +Renderizado de un subcomponente: ```latte {control cartControl-someForm} ``` -compila a: +se traduce como: ```php $control->getComponent("cartControl-someForm")->render(); ``` -Los componentes, como los presentadores, pasan varias variables útiles a las plantillas automáticamente: +Los componentes, al igual que los presenters, pasan automáticamente varias variables útiles a las plantillas: -- `$basePath` es una ruta URL absoluta al directorio raíz (por ejemplo `/CD-collection`) -- `$baseUrl` es una URL absoluta al directorio raíz (por ejemplo `http://localhost/CD-collection`) -- `$user` es un objeto [que representa al usuario |security:authentication] -- `$presenter` es el presentador actual +- `$basePath` es la ruta URL absoluta al directorio raíz (p. ej., `/eshop`) +- `$baseUrl` es la URL absoluta al directorio raíz (p. ej., `http://localhost/eshop`) +- `$user` es el objeto [que representa al usuario |security:authentication] +- `$presenter` es el presenter actual - `$control` es el componente actual -- `$flashes` lista de [mensajes |#flash-messages] enviados por el método `flashMessage()` +- `$flashes` array de [mensajes |#Mensajes flash] enviados por la función `flashMessage()` -Señal .[#toc-signal] -==================== +Señal +===== -Ya sabemos que la navegación en la aplicación Nette consiste en enlazar o redirigir a pares `Presenter:action`. Pero, ¿y si sólo queremos realizar una acción en la **página actual**? Por ejemplo, cambiar el orden de clasificación de la columna en la tabla; eliminar elemento; cambiar modo claro/oscuro; enviar el formulario; votar en la encuesta; etc. +Ya sabemos que la navegación en una aplicación Nette consiste en enlazar o redirigir a pares `Presenter:action`. Pero, ¿qué pasa si solo queremos realizar una acción en la **página actual**? Por ejemplo, cambiar el orden de las columnas en una tabla; eliminar un elemento; cambiar el modo claro/oscuro; enviar un formulario; votar en una encuesta; etc. -Este tipo de petición se llama señal. Y al igual que las acciones invocan métodos `action()` o `render()`, las señales llaman a métodos `handle()`. Mientras que el concepto de acción (o vista) sólo se refiere a los presentadores, las señales se aplican a todos los componentes. Y, por tanto, también a los presentadores, porque `UI\Presenter` es descendiente de `UI\Control`. +Este tipo de peticiones se llaman señales. Y de manera similar a como las acciones invocan los métodos `action()` o `render()`, las señales llaman a los métodos `handle()`. Mientras que el concepto de acción (o vista) está relacionado puramente con los presenters, las señales se aplican a todos los componentes. Y, por lo tanto, también a los presenters, porque `UI\Presenter` es un descendiente de `UI\Control`. ```php public function handleClick(int $x, int $y): void { - // ... procesamiento de señales ... + // ... procesamiento de la señal ... } ``` -El enlace que llama a la señal se crea de la forma habitual, es decir, en la plantilla mediante el atributo `n:href` o la etiqueta `{link}`, en el código mediante el método `link()`. Más información en el capítulo [Creación de enlaces URL |creating-links#Links to Signal]. +El enlace que llama a la señal se crea de la manera habitual, es decir, en la plantilla con el atributo `n:href` o la etiqueta `{link}`, en el código con el método `link()`. Más en el capítulo [Creación de enlaces URL |creating-links#Enlaces a señal]. ```latte -click here +haz clic aquí ``` -La señal siempre se llama en el presentador y vista actuales, por lo que no es posible enlazar a la señal en un presentador / acción diferente. +La señal siempre se llama en el presenter y la acción actuales, no es posible invocarla en otro presenter u otra acción. -Así, la señal hace que la página se recargue exactamente igual que en la petición original, sólo que además llama al método de gestión de la señal con los parámetros adecuados. Si el método no existe, se lanza la excepción [api:Nette\Application\UI\BadSignalException], que se muestra al usuario como página de error 403 Forbidden. +Por lo tanto, la señal provoca la recarga de la página exactamente igual que en la petición original, solo que además llama al método de manejo de la señal con los parámetros correspondientes. Si el método no existe, se lanza una excepción [api:Nette\Application\UI\BadSignalException], que se muestra al usuario como una página de error 403 Forbidden. -Fragmentos y AJAX .[#toc-snippets-and-ajax] -=========================================== +Fragmentos (Snippets) y AJAX +============================ -Las señales pueden recordarte un poco a AJAX: manejadores que son llamados en la página actual. Y tienes razón, las señales se llaman muy a menudo usando AJAX, y entonces sólo transmitimos partes cambiadas de la página al navegador. Se llaman snippets. Puedes encontrar más información en [la página sobre AJAX |ajax]. +Las señales pueden recordarle un poco a AJAX: manejadores que se invocan en la página actual. Y tiene razón, las señales realmente se llaman a menudo usando AJAX y posteriormente transferimos al navegador solo las partes modificadas de la página. Es decir, los llamados fragmentos (snippets). Encontrará más información en la [página dedicada a AJAX |ajax]. -Mensajes Flash .[#toc-flash-messages] -===================================== +Mensajes flash +============== -Un componente dispone de su propio almacén de mensajes flash independiente del presentador. Se trata de mensajes que, por ejemplo, informan sobre el resultado de la operación. Una característica importante de los mensajes flash es que están disponibles en el modelo incluso después de la redirección. Incluso después de ser mostrados, permanecerán vivos durante otros 30 segundos - por ejemplo, en caso de que el usuario refrescara involuntariamente la página - el mensaje no se perderá. +El componente tiene su propio almacenamiento de mensajes flash independiente del presenter. Son mensajes que, por ejemplo, informan sobre el resultado de una operación. Una característica importante de los mensajes flash es que están disponibles en la plantilla incluso después de una redirección. Incluso después de mostrarse, permanecen activos durante otros 30 segundos, por ejemplo, en caso de que el usuario actualice la página debido a una transmisión errónea, el mensaje no desaparecerá de inmediato. -El envío se realiza mediante el método [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. El primer parámetro es el texto del mensaje o el objeto `stdClass` que representa el mensaje. El segundo parámetro opcional es su tipo (error, advertencia, información, etc.). El método `flashMessage()` devuelve una instancia de flash mensaje como objeto stdClass al que se le puede pasar información. +El envío lo realiza el método [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. El primer parámetro es el texto del mensaje o un objeto `stdClass` que representa el mensaje. El segundo parámetro opcional es su tipo (error, warning, info, etc.). El método `flashMessage()` devuelve una instancia del mensaje flash como un objeto `stdClass`, al que se le puede agregar información adicional. ```php -$this->flashMessage('Item was deleted.'); -$this->redirect(/* ... */); // y redirigir +$this->flashMessage('El elemento ha sido eliminado.'); +$this->redirect(/* ... */); // y redirigimos ``` -En la plantilla, estos mensajes están disponibles en la variable `$flashes` como objetos `stdClass`, que contienen las propiedades `message` (texto del mensaje), `type` (tipo de mensaje) y pueden contener la información de usuario ya mencionada. Los dibujamos como sigue: +En la plantilla, estos mensajes están disponibles en la variable `$flashes` como objetos `stdClass`, que contienen las propiedades `message` (texto del mensaje), `type` (tipo de mensaje) y pueden contener la información de usuario ya mencionada. Los renderizamos, por ejemplo, así: ```latte {foreach $flashes as $flash} @@ -230,44 +230,66 @@ En la plantilla, estos mensajes están disponibles en la variable `$flashes` com ``` -Parámetros persistentes .[#toc-persistent-parameters] -===================================================== +Redirección después de una señal +================================ -Los parámetros persistentes se utilizan para mantener el estado de los componentes entre diferentes peticiones. Su valor sigue siendo el mismo incluso después de hacer clic en un enlace. A diferencia de los datos de sesión, se transfieren en la URL. Y se transfieren automáticamente, incluidos los enlaces creados en otros componentes de la misma página. +Después de procesar una señal de componente, a menudo sigue una redirección. Es una situación similar a la de los formularios: después de enviarlos, también redirigimos para que al actualizar la página en el navegador no se vuelvan a enviar los datos. -Por ejemplo, tiene un componente de paginación de contenido. Puede haber varios componentes de este tipo en una página. Y quiere que todos los componentes permanezcan en su página actual cuando haga clic en el enlace. Por lo tanto, hacemos que el número de página (`page`) sea un parámetro persistente. +```php +$this->redirect('this') // redirige al presenter y acción actuales +``` + +Dado que el componente es un elemento reutilizable y generalmente no debería tener una vinculación directa con presenters específicos, los métodos `redirect()` y `link()` interpretan automáticamente el parámetro como una señal del componente: + +```php +$this->redirect('click') // redirige a la señal 'click' del mismo componente +``` + +Si necesita redirigir a otro presenter o acción, puede hacerlo a través del presenter: + +```php +$this->getPresenter()->redirect('Product:show'); // redirige a otro presenter/acción +``` + + +Parámetros persistentes +======================= + +Los parámetros persistentes sirven para mantener el estado en los componentes entre diferentes peticiones. Su valor permanece igual incluso después de hacer clic en un enlace. A diferencia de los datos en la sesión, se transfieren en la URL. Y esto de forma totalmente automática, incluidos los enlaces creados en otros componentes en la misma página. + +Tiene, por ejemplo, un componente para paginar contenido. Puede haber varios de estos componentes en una página. Y deseamos que después de hacer clic en un enlace, todos los componentes permanezcan en su página actual. Por lo tanto, hacemos que el número de página (`page`) sea un parámetro persistente. -Crear un parámetro persistente es extremadamente fácil en Nette. Basta con crear una propiedad pública y etiquetarla con el atributo: (antes se utilizaba `/** @persistent */` ) +Crear un parámetro persistente es extremadamente simple en Nette. Basta con crear una propiedad pública y marcarla con un atributo: (anteriormente se usaba `/** @persistent */`) ```php -use Nette\Application\Attributes\Persistent; // esta línea es importante +use Nette\Application\Attributes\Persistent; // esta línea es importante class PaginatingControl extends Control { #[Persistent] - public int $page = 1; // debe ser público + public int $page = 1; // debe ser public } ``` -Te recomendamos que incluyas el tipo de dato (por ejemplo `int`) con la propiedad, y también puedes incluir un valor por defecto. Los valores de los parámetros se pueden [validar |#Validation of Persistent Parameters]. +Recomendamos indicar también el tipo de dato para la propiedad (p. ej., `int`) y puede indicar también un valor predeterminado. Los valores de los parámetros se pueden [validar |#Validación de parámetros persistentes]. -Puede cambiar el valor de un parámetro persistente al crear un enlace: +Al crear un enlace, se puede cambiar el valor del parámetro persistente: ```latte -next +siguiente ``` -O puede ser *reset*, es decir, eliminado de la URL. Entonces tomará su valor por defecto: +O se puede *resetear*, es decir, eliminar de la URL. Entonces tomará su valor predeterminado: ```latte -reset +resetear ``` -Componentes Persistentes .[#toc-persistent-components] -====================================================== +Componentes persistentes +======================== -No sólo los parámetros, sino también los componentes pueden ser persistentes. Sus parámetros persistentes también se transfieren entre diferentes acciones o entre diferentes presentadores. Marcamos los componentes persistentes con estas anotaciones para la clase presentador. Por ejemplo aquí marcamos los componentes `calendar` y `poll` como sigue: +No solo los parámetros, sino también los componentes pueden ser persistentes. En tal componente, sus parámetros persistentes se transfieren también entre diferentes acciones del presenter o entre varios presenters. Marcamos los componentes persistentes con una anotación en la clase del presenter. Por ejemplo, así marcamos los componentes `calendar` y `poll`: ```php /** @@ -278,9 +300,9 @@ class DefaultPresenter extends Nette\Application\UI\Presenter } ``` -No es necesario marcar los subcomponentes como persistentes, son persistentes automáticamente. +Los subcomponentes dentro de estos componentes no necesitan ser marcados, también se volverán persistentes. -En PHP 8, también puede utilizar atributos para marcar componentes persistentes: +En PHP 8, también puede usar atributos para marcar componentes persistentes: ```php use Nette\Application\Attributes\Persistent; @@ -292,10 +314,10 @@ class DefaultPresenter extends Nette\Application\UI\Presenter ``` -Componentes con Dependencias .[#toc-components-with-dependencies] -================================================================= +Componentes con dependencias +============================ -¿Cómo crear componentes con dependencias sin "fastidiar" a los presentadores que los utilizarán? Gracias a las inteligentes características del contenedor DI en Nette, al igual que con el uso de servicios tradicionales, podemos dejar la mayor parte del trabajo al framework. +¿Cómo crear componentes con dependencias sin "ensuciar" los presenters que los usarán? Gracias a las propiedades inteligentes del contenedor DI en Nette, al igual que al usar servicios clásicos, se puede dejar la mayor parte del trabajo al framework. Tomemos como ejemplo un componente que tiene una dependencia del servicio `PollFacade`: @@ -303,24 +325,24 @@ Tomemos como ejemplo un componente que tiene una dependencia del servicio `PollF class PollControl extends Control { public function __construct( - private int $id, // Id de un sondeo para el que se crea el componente + private int $id, // Id de la encuesta para la que creamos el componente private PollFacade $facade, ) { } public function handleVote(int $voteId): void { - $this->facade->vote($id, $voteId); + $this->facade->vote($this->id, $voteId); // ... } } ``` -Si estuviéramos escribiendo un servicio clásico, no habría nada de qué preocuparse. El contenedor DI se encargaría invisiblemente de pasar todas las dependencias. Pero normalmente manejamos los componentes creando una nueva instancia de ellos directamente en el presentador en [los métodos de fábrica |#factory methods] `createComponent...()`. Pero pasar todas las dependencias de todos los componentes al presentador para luego pasarlas a los componentes es engorroso. Y la cantidad de código escrito... +Si estuviéramos escribiendo un servicio clásico, no habría nada que resolver. El contenedor DI se encargaría invisiblemente de pasar todas las dependencias. Pero con los componentes, generalmente los tratamos de tal manera que creamos su nueva instancia directamente en el presenter en los [#métodos de fábrica] `createComponent…()`. Pero pasar todas las dependencias de todos los componentes al presenter para luego pasarlas a los componentes es engorroso. Y la cantidad de código escrito… -La pregunta lógica es, ¿por qué no registramos el componente como un servicio clásico, se lo pasamos al presentador y luego lo devolvemos en el método `createComponent...()`? Pero este enfoque es inadecuado porque queremos poder crear el componente varias veces. +La pregunta lógica es, ¿por qué simplemente no registramos el componente como un servicio clásico, lo pasamos al presenter y luego lo devolvemos en el método `createComponent…()`? Sin embargo, tal enfoque es inapropiado, porque queremos poder crear el componente incluso varias veces. -La solución correcta es escribir una fábrica para el componente, es decir, una clase que cree el componente por nosotros: +La solución correcta es escribir una fábrica para el componente, es decir, una clase que nos cree el componente: ```php class PollControlFactory @@ -337,17 +359,17 @@ class PollControlFactory } ``` -Ahora registramos nuestro servicio al contenedor DI a la configuración: +Así registramos la fábrica en nuestro contenedor en la configuración: ```neon services: - PollControlFactory ``` -Por último, vamos a utilizar esta fábrica en nuestro presentador: +y finalmente la usamos en nuestro presenter: ```php -class PollPresenter extends Nette\UI\Application\Presenter +class PollPresenter extends Nette\Application\UI\Presenter { public function __construct( private PollControlFactory $pollControlFactory, @@ -362,7 +384,7 @@ class PollPresenter extends Nette\UI\Application\Presenter } ``` -Lo bueno es que Nette DI puede [generar |dependency-injection:factory] fábricas tan simples, así que en lugar de escribir todo el código, sólo tienes que escribir su interfaz: +Lo genial es que Nette DI puede [generar |dependency-injection:factory] tales fábricas simples, por lo que en lugar de todo su código, basta con escribir solo su interfaz: ```php interface PollControlFactory @@ -371,21 +393,21 @@ interface PollControlFactory } ``` -Eso es todo. Nette implementa internamente esta interfaz y la inyecta en nuestro presentador, donde podemos utilizarla. También pasa mágicamente nuestro parámetro `$id` y la instancia de la clase `PollFacade` a nuestro componente. +Y eso es todo. Nette implementa internamente esta interfaz y la pasa al presenter, donde ya podemos usarla. Mágicamente, también agrega a nuestro componente el parámetro `$id` y la instancia de la clase `PollFacade`. -Componentes en profundidad .[#toc-components-in-depth] -====================================================== +Componentes en profundidad +========================== -Los componentes en una aplicación Nette son las partes reutilizables de una aplicación web que incrustamos en las páginas, que es el tema de este capítulo. ¿Cuáles son exactamente las capacidades de un componente? +Los componentes en Nette Application representan partes reutilizables de una aplicación web que insertamos en las páginas y a las que, por cierto, se dedica todo este capítulo. ¿Qué capacidades exactas tiene tal componente? -1) es renderizable en una plantilla -2) sabe qué parte de sí mismo renderizar durante una [petición AJAX |ajax#invalidation] (fragmentos) -3) tiene la capacidad de almacenar su estado en una URL (parámetros persistentes) -4) tiene la capacidad de responder a las acciones del usuario (señales) -5) crea una estructura jerárquica (donde la raíz es el presentador) +1) es renderizable en la plantilla +2) sabe [qué parte suya |ajax#Fragmentos Snippets] debe renderizar en una petición AJAX (fragmentos) +3) tiene la capacidad de guardar su estado en la URL (parámetros persistentes) +4) tiene la capacidad de reaccionar a las acciones del usuario (señales) +5) crea una estructura jerárquica (donde la raíz es el presenter) -Cada una de estas funciones es gestionada por una de las clases del linaje de herencia. La renderización (1 + 2) es gestionada por [api:Nette\Application\UI\Control], la incorporación al [ciclo de vida |presenters#life-cycle-of-presenter] (3, 4) por la clase [api:Nette\Application\UI\Component], y la creación de la estructura jerárquica (5) por las clases [Container y Component |component-model:]. +Cada una de estas funciones la realiza alguna de las clases de la línea de herencia. El renderizado (1 + 2) está a cargo de [api:Nette\Application\UI\Control], la inclusión en el [ciclo de vida |presenters#Ciclo de vida del presenter] (3, 4) de la clase [api:Nette\Application\UI\Component] y la creación de la estructura jerárquica (5) de las clases [Container y Component |component-model:]. ``` Nette\ComponentModel\Component { IComponent } @@ -400,18 +422,18 @@ Nette\ComponentModel\Component { IComponent } ``` -Ciclo de vida del componente .[#toc-life-cycle-of-component] ------------------------------------------------------------- +Ciclo de vida del componente +---------------------------- [* lifecycle-component.svg *] *** *Ciclo de vida del componente* .<> -Validación de parámetros persistentes .[#toc-validation-of-persistent-parameters] ---------------------------------------------------------------------------------- +Validación de parámetros persistentes +------------------------------------- -Los valores de los parámetros [persistentes |#persistent parameters] recibidos de las URLs son escritos en las propiedades por el método `loadState()`. También comprueba si el tipo de datos especificado para la propiedad coincide, de lo contrario responderá con un error 404 y la página no se mostrará. +Los valores de los [#parámetros persistentes] recibidos de la URL se escriben en las propiedades mediante el método `loadState()`. Este también comprueba si el tipo de dato indicado en la propiedad coincide, de lo contrario responde con un error 404 y la página no se muestra. -Nunca confíes ciegamente en los parámetros persistentes porque pueden ser fácilmente sobrescritos por el usuario en la URL. Por ejemplo, así es como comprobamos si el número de página `$this->page` es mayor que 0. Una buena forma de hacerlo es sobrescribir el método `loadState()` mencionado anteriormente: +Nunca confíe ciegamente en los parámetros persistentes, ya que pueden ser fácilmente sobrescritos por el usuario en la URL. Así, por ejemplo, verificamos si el número de página `$this->page` es mayor que 0. Una forma adecuada es sobrescribir el método mencionado `loadState()`: ```php class PaginatingControl extends Control @@ -421,8 +443,8 @@ class PaginatingControl extends Control public function loadState(array $params): void { - parent::loadState($params); // aquí se establece el $this->page - // sigue la comprobación del valor del usuario: + parent::loadState($params); // aquí se establece $this->page + // sigue la verificación propia del valor: if ($this->page < 1) { $this->error(); } @@ -430,27 +452,27 @@ class PaginatingControl extends Control } ``` -El proceso opuesto, es decir, recolectar valores de propiedades persistentes, es manejado por el método `saveState()`. +El proceso opuesto, es decir, la recopilación de valores de las propiedades persistentes, está a cargo del método `saveState()`. -Señales en profundidad .[#toc-signals-in-depth] ------------------------------------------------ +Señales en profundidad +---------------------- -Una señal provoca una recarga de la página como la petición original (con la excepción de AJAX) e invoca el método `signalReceived($signal)` cuya implementación por defecto en la clase `Nette\Application\UI\Component` intenta llamar a un método compuesto por las palabras `handle{Signal}`. El procesamiento posterior depende del objeto dado. Los objetos descendientes de `Component` (es decir, `Control` y `Presenter`) intentan llamar a `handle{Signal}` con los parámetros pertinentes. +Una señal provoca la recarga de la página exactamente igual que en la petición original (excepto en el caso de que se llame por AJAX) e invoca el método `signalReceived($signal)`, cuya implementación predeterminada en la clase `Nette\Application\UI\Component` intenta llamar a un método compuesto por las palabras `handle{signal}`. El procesamiento posterior depende del objeto en cuestión. Los objetos que heredan de `Component` (es decir, `Control` y `Presenter`) reaccionan intentando llamar al método `handle{signal}` con los parámetros correspondientes. -En otras palabras: se toma la definición del método `handle{Signal}` y se cotejan todos los parámetros que se recibieron en la solicitud con los parámetros del método. Esto significa que el parámetro `id` de la URL se empareja con el parámetro del método `$id`, `something` con `$something` y así sucesivamente. Y si el método no existe, el método `signalReceived` lanza [una excepción |api:Nette\Application\UI\BadSignalException]. +En otras palabras: se toma la definición de la función `handle{signal}` y todos los parámetros que llegaron con la petición, y a los argumentos se les asignan los parámetros de la URL según el nombre e intenta llamar al método dado. Por ejemplo, como parámetro `$id` se pasa el valor del parámetro `id` en la URL, como `$something` se pasa `something` de la URL, etc. Y si el método no existe, el método `signalReceived` lanza una [excepción |api:Nette\Application\UI\BadSignalException]. -La señal puede ser recibida por cualquier componente, presentador de objeto que implemente la interfaz `SignalReceiver` si está conectado al árbol de componentes. +La señal puede ser recibida por cualquier componente, presenter u objeto que implemente la interfaz `SignalReceiver` y esté conectado al árbol de componentes. -Los principales receptores de señales son `Presenters` y los componentes visuales que amplían `Control`. Una señal es una señal para un objeto que tiene que hacer algo - la encuesta cuenta con un voto del usuario, la caja con noticias tiene que desplegarse, el formulario fue enviado y tiene que procesar datos y así sucesivamente. +Los principales receptores de señales serán los `Presenters` y los componentes visuales que heredan de `Control`. La señal debe servir como una indicación para el objeto de que debe hacer algo: la encuesta debe contar el voto del usuario, el bloque de noticias debe expandirse y mostrar el doble de noticias, el formulario se envió y debe procesar los datos, y así sucesivamente. -La URL para la señal se crea usando el método [Component::link() |api:Nette\Application\UI\Component::link()]. Como parámetro `$destination` pasamos la cadena `{signal}!` y como `$args` un array de argumentos que queremos pasar al manejador de la señal. Los parámetros de la señal se adjuntan a la URL del presentador/vista actual. **El parámetro `?do` en la URL determina la señal llamada.** +La URL para la señal la creamos usando el método [Component::link() |api:Nette\Application\UI\Component::link()]. Como parámetro `$destination` pasamos la cadena `{signal}!` y como `$args` un array de argumentos que queremos pasar a la señal. La señal siempre se llama en el presenter y acción actuales con los parámetros actuales, los parámetros de la señal simplemente se agregan. Además, se agrega al principio el **parámetro `?do`, que determina la señal**. -Su formato es `{signal}` o `{signalReceiver}-{signal}`. `{signalReceiver}` es el nombre del componente en el presentador. Esta es la razón por la que el guión (inexactamente dash) no puede estar presente en el nombre de los componentes - se utiliza para dividir el nombre del componente y la señal, pero es posible componer varios componentes. +Su formato es `{signal}` o `{signalReceiver}-{signal}`. `{signalReceiver}` es el nombre del componente en el presenter. Por eso no puede haber un guion en el nombre del componente; se usa para separar el nombre del componente y la señal, sin embargo, es posible anidar varios componentes de esta manera. -El método [isSignalReceiver() |api:Nette\Application\UI\Presenter::isSignalReceiver()] verifica si un componente (primer argumento) es receptor de una señal (segundo argumento). El segundo argumento puede omitirse - entonces averigua si el componente es receptor de alguna señal. Si el segundo parámetro es `true` verifica si el componente o sus descendientes son receptores de una señal. +El método [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] verifica si el componente (primer argumento) es el receptor de la señal (segundo argumento). Podemos omitir el segundo argumento; entonces verifica si el componente es receptor de cualquier señal. Como segundo parámetro se puede indicar `true` y así verificar si el receptor no es solo el componente indicado, sino también cualquiera de sus descendientes. -En cualquier fase anterior a `handle{Signal}` se puede realizar la señal manualmente llamando al método [processSignal() |api:Nette\Application\UI\Presenter::processSignal()] que se responsabiliza de la ejecución de la señal. Toma el componente receptor (si no está establecido es el propio presentador) y le envía la señal. +En cualquier fase anterior a `handle{signal}` podemos ejecutar la señal manualmente llamando al método [processSignal()|api:Nette\Application\UI\Presenter::processSignal()], que se encarga de gestionar la señal: toma el componente que se determinó como receptor de la señal (si no se especifica un receptor de señal, es el propio presenter) y le envía la señal. Ejemplo: @@ -460,4 +482,4 @@ if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, ' } ``` -La señal se ejecuta prematuramente y no se volverá a llamar. +De esta manera, la señal se ejecuta prematuramente y ya no se volverá a llamar. diff --git a/application/es/configuration.texy b/application/es/configuration.texy index 651dd108f5..9496f35bd5 100644 --- a/application/es/configuration.texy +++ b/application/es/configuration.texy @@ -1,58 +1,71 @@ -Configuración de la aplicación -****************************** +Configuración de aplicaciones +***************************** .[perex] -Visión general de las opciones de configuración de la Aplicación Nette. +Resumen de las opciones de configuración para las aplicaciones Nette. -Aplicación .[#toc-application] -============================== +Application +=========== ```neon application: - # muestra el panel "Nette Application" en Tracy BlueScreen? - debugger: ... # (bool) por defecto true + # mostrar el panel "Nette Application" en Tracy BlueScreen? + debugger: ... # (bool) predeterminado es true - # ¿se llamará al presentador de errores en caso de error? - catchExceptions: ... # (bool) por defecto a true en modo producción + # se llamará al error-presenter en caso de error? + # solo tiene efecto en modo de desarrollo + catchExceptions: ... # (bool) predeterminado es true - # nombre del presentador de errores - errorPresenter: Error # (string) por defecto 'Nette:Error' + # nombre del error-presenter + errorPresenter: Error # (string|array) predeterminado es 'Nette:Error' - # define las reglas para resolver el nombre del presentador a una clase + # define alias para presenters y acciones + aliases: ... + + # define reglas para traducir el nombre del presenter a una clase mapping: ... - # ¿los enlaces erróneos generan advertencias? - # sólo tiene efecto en modo desarrollador - silentLinks: ... # (bool) por defecto false + # los enlaces erróneos no generan advertencias? + # solo tiene efecto en modo de desarrollo + silentLinks: ... # (bool) predeterminado es false ``` -Debido a que los presentadores de errores no son llamados por defecto en modo desarrollo y los errores son mostrados por Tracy, cambiar el valor `catchExceptions` a `true` ayuda a verificar que los presentadores de errores funcionan correctamente durante el desarrollo. +Desde la versión 3.2 de `nette/application`, se puede definir un par de error-presenters: -La opción `silentLinks` determina cómo se comporta Nette en modo desarrollo cuando falla la generación de enlaces (por ejemplo, porque no hay presentador, etc). El valor por defecto `false` significa que Nette activa `E_USER_WARNING`. El valor `true` suprime este mensaje de error. En un entorno de producción, siempre se invoca `E_USER_WARNING`. También podemos influir en este comportamiento configurando la variable del presentador [$invalidLinkMode |creating-links#Invalid Links]. +```neon +application: + errorPresenter: + 4xx: Error4xx # para la excepción Nette\Application\BadRequestException + 5xx: Error5xx # para otras excepciones +``` -El [mapeo define las reglas |modules#mapping] por las cuales el nombre de la clase se deriva del nombre del presentador. +La opción `silentLinks` determina cómo se comporta Nette en modo de desarrollo cuando falla la generación de un enlace (por ejemplo, porque no existe el presenter, etc.). El valor predeterminado `false` significa que Nette lanzará un error `E_USER_WARNING`. Establecerlo en `true` suprimirá este mensaje de error. En el entorno de producción, siempre se lanza `E_USER_WARNING`. Este comportamiento también se puede influir estableciendo la variable del presenter [$invalidLinkMode |creating-links#Enlaces no válidos]. +Los [Alias simplifican el enlace |creating-links#Alias] a presenters de uso frecuente. -Registro automático de presentadores .[#toc-automatic-registration-of-presenters] ---------------------------------------------------------------------------------- +El [Mapeo define reglas |directory-structure#Mapeo de presenters] según las cuales se deriva el nombre de la clase a partir del nombre del presenter. -Nette añade automáticamente presentadores como servicios al contenedor DI, lo que acelera significativamente su creación. La forma en que Nette descubre a los presentadores puede configurarse: + +Registro automático de presenters +--------------------------------- + +Nette agrega automáticamente los presenters como servicios al contenedor DI, lo que acelera significativamente su creación. Cómo Nette busca los presenters se puede configurar: ```neon application: - # ¿buscar presentadores en el mapa de clases de Composer? - scanComposer: ... # (bool) por defecto a true + # buscar presenters en el mapa de clases de Composer? + scanComposer: ... # (bool) predeterminado es true - # una máscara que debe coincidir con la clase y el nombre del archivo - scanFilter: ... # (string) por defecto '*Presenter' + # máscara que debe cumplir el nombre de la clase y el archivo + scanFilter: ... # (string) predeterminado es '*Presenter' - # ¿en qué directorios buscar los presentadores? - scanDirs: # (string[]|false) por defecto '%appDir%' + # en qué directorios buscar presenters? + scanDirs: # (string[]|false) predeterminado es '%appDir%' - %vendorDir%/mymodule ``` -Los directorios listados en `scanDirs` no sobreescriben el valor por defecto `%appDir%`, sino que lo complementan, así `scanDirs` contendrá ambas rutas `%appDir%` y `%vendorDir%/mymodule`. Si queremos sobreescribir el directorio por defecto, usamos el [signo de exclamación |dependency-injection:configuration#Merging]: +Los directorios indicados en `scanDirs` no sobrescriben el valor predeterminado `%appDir%`, sino que lo complementan, por lo que `scanDirs` contendrá ambas rutas `%appDir%` y `%vendorDir%/mymodule`. Si quisiéramos omitir el directorio predeterminado, usaríamos un [signo de exclamación |dependency-injection:configuration#Fusión], que sobrescribe el valor: ```neon application: @@ -60,64 +73,73 @@ application: - %vendorDir%/mymodule ``` -El escaneo de directorios puede desactivarse configurando false. No recomendamos suprimir por completo la adición automática de presentadores, ya que de lo contrario se reducirá el rendimiento de la aplicación. +El escaneo de directorios se puede desactivar indicando el valor false. No recomendamos suprimir por completo la adición automática de presenters, ya que de lo contrario se reducirá el rendimiento de la aplicación. -Latte .[#toc-latte] -=================== +Plantillas Latte +================ -Esta configuración afecta globalmente al comportamiento de Latte en componentes y presentadores. +Con esta configuración, se puede influir globalmente en el comportamiento de Latte en componentes y presenters. ```neon latte: - # ¿muestra el panel Latte en la Tracy Bar para la plantilla principal (true) o para todos los componentes (all)? - debugger: ... # (true|false|'all') por defecto true + # mostrar el panel Latte en Tracy Bar para la plantilla principal (true) o todos los componentes (all)? + debugger: ... # (true|false|'all') predeterminado es true + + # genera plantillas con la cabecera declare(strict_types=1) + strictTypes: ... # (bool) predeterminado es false - # genera plantillas con declare(strict_types=1) - strictTypes: ... # (bool) por defecto false + # activa el modo de [parser estricto |latte:develop#striktní režim] + strictParsing: ... # (bool) predeterminado es false - # clase de $this->plantilla - templateClass: App\MyTemplateClass # por defecto Nette\Bridges\ApplicationLatte\DefaultTemplate + # activa la [verificación del código generado |latte:develop#Kontrola vygenerovaného kódu] + phpLinter: ... # (string) predeterminado es null + + # establece la configuración regional + locale: cs_CZ # (string) predeterminado es null + + # clase del objeto $this->template + templateClass: App\MyTemplateClass # predeterminado es Nette\Bridges\ApplicationLatte\DefaultTemplate ``` -Si está utilizando la versión 3 de Latte, puede añadir una nueva [extensión |latte:creating-extension] utilizando: +Si usa Latte versión 3, puede agregar nuevas [extensiones |latte:extending-latte#Latte Extension] usando: ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -/--comment - - - - +Si usa Latte versión 2, puede registrar nuevas etiquetas (macros) ya sea indicando el nombre de la clase o una referencia al servicio. Por defecto, se llama al método `install()`, pero esto se puede cambiar indicando el nombre de otro método: +```neon +latte: + # registro de etiquetas Latte personalizadas + macros: + - App\MyLatteMacros::register # método estático, nombre de clase o callable + - @App\MyLatteMacrosFactory # servicio con método install() + - @App\MyLatteMacrosFactory::register # servicio con método register() + +services: + - App\MyLatteMacrosFactory +``` - - - - -\-- - - -Enrutamiento .[#toc-routing] -============================ +Enrutamiento +============ Configuración básica: ```neon routing: - # ¿muestra el panel de enrutamiento en Tracy Bar? - debugger: ... # (bool) por defecto a true + # mostrar el panel de enrutamiento en Tracy Bar? + debugger: ... # (bool) predeterminado es true - # serializar enrutador a contenedor DI? - cache: ... # (bool) por defecto false + # serializa el router en el contenedor DI + cache: ... # (bool) predeterminado es false ``` -El enrutador se define normalmente en la clase [RouterFactory |routing#Route Collection]. Alternativamente, los enrutadores también se pueden definir en la configuración utilizando pares `mask: action`, pero este método no ofrece una variación tan amplia en la configuración: +El enrutamiento generalmente lo definimos en la clase [RouterFactory |routing#Colección de rutas]. Alternativamente, las rutas también se pueden definir en la configuración usando pares `máscara: acción`, pero este método no ofrece una variabilidad tan amplia en la configuración: ```neon routing: @@ -127,8 +149,8 @@ routing: ``` -Constantes .[#toc-constants] -============================ +Constantes +========== Creación de constantes PHP. @@ -137,18 +159,33 @@ constants: Foobar: 'baz' ``` -La constante `Foobar` se creará después del arranque. +Después de iniciar la aplicación, se creará la constante `Foobar`. .[note] -Las constantes no deben servir como variables disponibles globalmente. Para pasar valores a objetos, utilice [la inyección de dependencia |dependency-injection:passing-dependencies]. +Las constantes no deben servir como variables disponibles globalmente. Para pasar valores a objetos, utilice la [inyección de dependencias |dependency-injection:passing-dependencies]. PHP === -Puede establecer directivas PHP. Una visión general de todas las directivas se puede encontrar en [php.net |https://www.php.net/manual/en/ini.list.php]. +Configuración de directivas PHP. Un resumen de todas las directivas se encuentra en [php.net |https://www.php.net/manual/en/ini.list.php]. ```neon php: date.timezone: Europe/Prague ``` + + +Servicios DI +============ + +Estos servicios se agregan al contenedor DI: + +| Nombre | Tipo | Descripción +|---------------------------------------------------------- +| `application.application` | [api:Nette\Application\Application] | [ejecutor de toda la aplicación |how-it-works#Nette Application] +| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] +| `application.presenterFactory` | [api:Nette\Application\PresenterFactory] | fábrica de presenters +| `application.###` | [api:Nette\Application\UI\Presenter] | presenters individuales +| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | fábrica del objeto `Latte\Engine` +| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | fábrica para [`$this->template` |templates] diff --git a/application/es/creating-links.texy b/application/es/creating-links.texy index cd4645f3a5..76be2a6ad9 100644 --- a/application/es/creating-links.texy +++ b/application/es/creating-links.texy @@ -3,158 +3,158 @@ Creación de enlaces URL
    -Crear enlaces en Nette es tan fácil como señalar con el dedo. Sólo tienes que señalar y el framework hará todo el trabajo por ti. Se lo mostraremos: +Crear enlaces en Nette es tan simple como señalar con el dedo. Solo necesita apuntar y el framework hará todo el trabajo por usted. Mostraremos: - cómo crear enlaces en plantillas y en otros lugares -- cómo distinguir un enlace de la página actual -- qué pasa con los enlaces no válidos +- cómo distinguir un enlace a la página actual +- qué hacer con los enlaces no válidos
    -Gracias al [enrutamiento bidireccional |routing], nunca tendrás que codificar las URL de la aplicación en las plantillas o el código, que pueden cambiar más tarde o ser complicadas de componer. Basta con especificar el presentador y la acción en el enlace, pasar cualquier parámetro y el framework generará la URL por sí mismo. De hecho, es muy similar a llamar a una función. Te gustará. +Gracias al [enrutamiento bidireccional |routing], nunca tendrá que escribir direcciones URL de su aplicación directamente en plantillas o código, que pueden cambiar más tarde, o componerlas de forma complicada. En el enlace, basta con indicar el presenter y la acción, pasar los parámetros necesarios y el framework generará la URL por sí mismo. De hecho, es muy similar a llamar a una función. Esto le gustará. -En la plantilla de presentador .[#toc-in-the-presenter-template] -================================================================ +En la plantilla del presenter +============================= -La mayoría de las veces creamos enlaces en las plantillas y una gran ayuda es el atributo `n:href`: +La mayoría de las veces creamos enlaces en plantillas y un excelente ayudante es el atributo `n:href`: ```latte -detail +detalle ``` -Tenga en cuenta que en lugar del atributo HTML `href` hemos utilizado [n:attribute |latte:syntax#n:attributes] `n:href`. Su valor no es una URL, como estás acostumbrado con el atributo `href`, sino el nombre del presentador y la acción. +Observe que en lugar del atributo HTML `href`, usamos el [n:atributo |latte:syntax#n:atributos] `n:href`. Su valor no es una URL, como sería el caso del atributo `href`, sino el nombre del presenter y la acción. -Hacer clic en un enlace es, en pocas palabras, algo así como llamar a un método `ProductPresenter::renderShow()`. Y si tiene parámetros en su firma, podemos llamarlo con argumentos: +Hacer clic en un enlace es, simplificando, algo así como llamar al método `ProductPresenter::renderShow()`. Y si tiene parámetros en su firma, podemos llamarlo con argumentos: ```latte -detail +detalle del producto ``` -También es posible pasar parámetros con nombre. El siguiente enlace pasa el parámetro `lang` con el valor `en`: +También es posible pasar parámetros con nombre. El siguiente enlace pasa el parámetro `lang` con el valor `cs`: ```latte -detail +detalle del producto ``` -Si el método `ProductPresenter::renderShow()` no tiene `$lang` en su firma, puede leer el valor del parámetro usando `$lang = $this->getParameter('lang')`. +Si el método `ProductPresenter::renderShow()` no tiene `$lang` en su firma, puede obtener el valor del parámetro usando `$lang = $this->getParameter('lang')` o desde la [propiedad |presenters#Parámetros de la petición]. -Si los parámetros se almacenan en una matriz, pueden expandirse con el operador `...` (o `(expand)` en Latte 2.x): +Si los parámetros están almacenados en un array, se pueden expandir con el operador `...` (en Latte 2.x con el operador `(expand)`): ```latte -{var $args = [$product->id, lang => en]} -detail +{var $args = [$product->id, lang => cs]} +detalle del producto ``` -Los llamados [parámetros persistentes |presenters#persistent parameters] también se pasan automáticamente en los enlaces. +En los enlaces también se pasan automáticamente los llamados [parámetros persistentes |presenters#Parámetros persistentes]. -El atributo `n:href` es muy útil para las etiquetas HTML ``. Si queremos imprimir el enlace en otro lugar, por ejemplo en el texto, utilizamos `{link}`: +El atributo `n:href` es muy útil para las etiquetas HTML ``. Si queremos mostrar el enlace en otro lugar, por ejemplo en el texto, usamos `{link}`: ```latte -URL is: {link Home:default} +La dirección es: {link Home:default} ``` -En el código .[#toc-in-the-code] -================================ +En el código +============ -El método `link()` se utiliza para crear un enlace en el presentador: +Para crear un enlace en el presenter, se utiliza el método `link()`: ```php $url = $this->link('Product:show', $product->id); ``` -Los parámetros también se pueden pasar como una matriz en la que también se pueden especificar parámetros con nombre: +Los parámetros también se pueden pasar mediante un array, donde también se pueden indicar parámetros con nombre: ```php $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); ``` -También se pueden crear enlaces sin presentador, utilizando el [LinkGenerator |#LinkGenerator] y su método `link()`. +Los enlaces también se pueden crear sin un presenter, para eso está [#LinkGenerator] y su método `link()`. -Enlaces con presentador .[#toc-links-to-presenter] -================================================== +Enlaces a presenter +=================== -Si el objetivo del enlace es presentador y acción, tiene esta sintaxis: +Si el destino del enlace es un presenter y una acción, tiene esta sintaxis: ``` [//] [[[[:]module:]presenter:]action | this] [#fragment] ``` -El formato es soportado por todas las etiquetas Latte y todos los métodos de presentador que trabajan con enlaces, es decir `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` y también [LinkGenerator |#LinkGenerator]. Así que aunque en los ejemplos se utilice `n:href`, podría ser cualquiera de las funciones. +El formato es compatible con todas las etiquetas Latte y todos los métodos del presenter que trabajan con enlaces, es decir, `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` y también [#LinkGenerator]. Así que, aunque en los ejemplos se use `n:href`, podría estar cualquiera de las funciones. -Por lo tanto, la forma básica es `Presenter:action`: +La forma básica es, por lo tanto, `Presenter:action`: ```latte -home +página de inicio ``` -Si enlazamos con la acción del presentador actual, podemos omitir su nombre: +Si enlazamos a una acción del presenter actual, podemos omitir su nombre: ```latte -home +página de inicio ``` -Si la acción es `default`, podemos omitirlo, pero los dos puntos deben permanecer: +Si el destino es la acción `default`, podemos omitirla, pero los dos puntos deben permanecer: ```latte -home +página de inicio ``` -Los enlaces también pueden apuntar a otros [módulos |modules]. Aquí, los enlaces se distinguen en relativos a los submódulos, o absolutos. El principio es análogo a las rutas de disco, sólo que en lugar de barras inclinadas hay dos puntos. Supongamos que el presentador real forma parte del módulo `Front`, entonces escribiremos: +Los enlaces también pueden apuntar a otros [módulos |directory-structure#Presenters y plantillas]. Aquí, los enlaces se distinguen entre relativos a un submódulo anidado o absolutos. El principio es análogo a las rutas en el disco, solo que en lugar de barras inclinadas hay dos puntos. Supongamos que el presenter actual es parte del módulo `Front`, entonces escribimos: ```latte -link to Front:Shop:Product:show -link to Admin:Product:show +enlace a Front:Shop:Product:show +enlace a Admin:Product:show ``` -Un caso especial es el [enlace a sí mismo |#Links to Current Page]. Aquí escribiremos `this` como objetivo. +Un caso especial es un enlace [a sí mismo |#Enlace a la página actual], donde indicamos `this` como destino. ```latte -refresh +refrescar ``` -Podemos enlazar a una parte determinada de la página HTML mediante un fragmento llamado `#` después del símbolo de almohadilla `#`: +Podemos enlazar a una parte específica de la página a través del llamado fragmento después del signo de almohadilla `#`: ```latte -link to Home:default and fragment #main +enlace a Home:default y fragmento #main ``` -Rutas absolutas .[#toc-absolute-paths] -====================================== +Rutas absolutas +=============== -Los enlaces generados por `link()` o `n:href` son siempre rutas absolutas (es decir, empiezan por `/`), pero no URLs absolutas con protocolo y dominio como `https://domain`. +Los enlaces generados mediante `link()` o `n:href` son siempre rutas absolutas (es decir, comienzan con el carácter `/`), pero no URL absolutas con protocolo y dominio como `https://domain`. -Para generar una URL absoluta, añada dos barras al principio (por ejemplo, `n:href="//Home:"`). También puede hacer que el presentador genere sólo enlaces absolutos configurando `$this->absoluteUrls = true`. +Para generar una URL absoluta, agregue dos barras inclinadas al principio (p. ej., `n:href="//Home:"`). O se puede cambiar el presenter para que genere solo enlaces absolutos estableciendo `$this->absoluteUrls = true`. -Enlace a la página actual .[#toc-link-to-current-page] -====================================================== +Enlace a la página actual +========================= -El destino `this` creará un enlace a la página actual: +El destino `this` crea un enlace a la página actual: ```latte -refresh +refrescar ``` -Al mismo tiempo, todos los parámetros especificados en la firma de la directiva `render()` o `action()` se transfieren. Así, si estamos en las páginas `Product:show` y `id:123`, el enlace a `this` también pasará este parámetro. +Al mismo tiempo, se transfieren todos los parámetros indicados en la firma del método `action()` o `render()`, si `action()` no está definida. Así que si estamos en la página `Product:show` e `id: 123`, el enlace a `this` también pasará este parámetro. Por supuesto, es posible especificar los parámetros directamente: ```latte -refresh +refrescar ``` -La función `isLinkCurrent()` determina si el destino del enlace es el mismo que la página actual. Esto puede utilizarse, por ejemplo, en una plantilla para diferenciar enlaces, etc. +La función `isLinkCurrent()` comprueba si el destino del enlace coincide con la página actual. Esto se puede usar, por ejemplo, en la plantilla para distinguir enlaces, etc. -Los parámetros son los mismos que para el método `link()`, pero también es posible utilizar el comodín `*` en lugar de una acción específica, lo que significa cualquier acción del presentador. +Los parámetros son los mismos que en el método `link()`, pero además es posible indicar un comodín `*` en lugar de una acción específica, que significa cualquier acción del presenter dado. ```latte {if !isLinkCurrent('Admin:login')} - Přihlaste se + Inicie sesión {/if}
  • @@ -162,58 +162,58 @@ Los parámetros son los mismos que para el método `link()`, pero también es po
  • ``` -Se puede utilizar una forma abreviada en combinación con `n:href` en un solo elemento: +En combinación con `n:href` en un solo elemento, se puede usar una forma abreviada: ```latte -... +... ``` -El carácter comodín `*` sólo sustituye a la acción del presentador, no al presentador en sí. +El comodín `*` solo se puede usar en lugar de la acción, nikoliv presenteru. -Para saber si estamos en un módulo determinado o en su submódulo podemos utilizar la función `isModuleCurrent(moduleName)`. +Para determinar si estamos en un módulo específico o en su submódulo, usamos el método `isModuleCurrent(moduleName)`. ```latte -
  • +
  • ...
  • ``` -Enlaces a la señal .[#toc-links-to-signal] -========================================== +Enlaces a señal +=============== -El objetivo del enlace no sólo puede ser el presentador y la acción, sino también la [señal |components#Signal] (llaman al método `handle()`). La sintaxis es la siguiente: +El destino de un enlace no tiene por qué ser solo un presenter y una acción, sino también una [señal |components#Señal] (llaman al método `handle()`). Entonces la sintaxis es la siguiente: ``` [//] [sub-component:]signal! [#fragment] ``` -Por lo tanto, la señal se distingue por el signo de exclamación: +La señal se distingue por el signo de exclamación: ```latte -signal +señal ``` -También puede crear un enlace a la señal del subcomponente (o sub-subcomponente): +También se puede crear un enlace a la señal de un subcomponente (o sub-subcomponente): ```latte -signal +señal ``` -Enlaces en el componente .[#toc-links-in-component] -=================================================== +Enlaces en componente +===================== -Dado que los [componentes |components] son unidades reutilizables independientes que no deben tener ninguna relación con los presentadores circundantes, los enlaces funcionan de forma un poco diferente. El atributo Latte `n:href` y la etiqueta `{link}` y los métodos de componentes como `link()` y otros siempre consideran el objetivo **como el nombre de la señal**. Por lo tanto, no es necesario utilizar un signo de exclamación: +Dado que los [componentes|components] son unidades reutilizables independientes que no deberían tener ninguna vinculación con los presenters circundantes, los enlaces funcionan aquí de manera un poco diferente. El atributo Latte `n:href` y la etiqueta `{link}`, así como los métodos del componente como `link()` y otros, consideran el destino del enlace **siempre como el nombre de la señal**. Por lo tanto, ni siquiera es necesario indicar el signo de exclamación: ```latte -signal, not an action +señal, no acción ``` -Si queremos enlazar con presentadores en la plantilla de componentes, utilizaremos la etiqueta `{plink}`: +Si quisiéramos enlazar a presenters en la plantilla del componente, usaríamos la etiqueta `{plink}`: ```latte -home +inicio ``` o en el código @@ -223,17 +223,41 @@ $this->getPresenter()->link('Home:default') ``` -Enlaces no válidos .[#toc-invalid-links] -======================================== +Alias .{data-version:v3.2.2} +============================ -Puede ocurrir que creemos un enlace no válido, bien porque haga referencia a un presentador inexistente, bien porque pase más parámetros de los que el método de destino recibe en su firma, o bien cuando no puede haber una URL generada para la acción de destino. Lo que hay que hacer con los enlaces no válidos viene determinado por la variable estática `Presenter::$invalidLinkMode`. Puede tener uno de estos valores (constantes): +A veces puede ser útil asignar un alias fácil de recordar al par Presenter:acción. Por ejemplo, nombrar la página de inicio `Front:Home:default` simplemente como `home` o `Admin:Dashboard:default` como `admin`. -- `Presenter::InvalidLinkSilent` - modo silencioso, devuelve el símbolo `#` como URL -- `Presenter::InvalidLinkWarning` - se producirá E_USER_WARNING -- `Presenter::InvalidLinkTextual` - advertencia visual, el texto del error se muestra en el enlace -- `Presenter::InvalidLinkException` - se lanzará una InvalidLinkException +Los alias se definen en la [configuración|configuration] bajo la clave `application › aliases`: -La configuración por defecto en modo producción es `InvalidLinkWarning` y en modo desarrollo es `InvalidLinkWarning | InvalidLinkTextual`. `InvalidLinkWarning` no mata el script en el entorno de producción, pero la advertencia será registrada. En el entorno de desarrollo, [Tracy |tracy:] interceptará la advertencia y mostrará la pantalla azul de error. Si el `InvalidLinkTextual` está configurado, el presentador y los componentes devuelven el mensaje de error como URL que comienza con `#error:`. Para hacer visibles estos enlaces, podemos añadir una regla CSS a nuestra hoja de estilos: +```neon +application: + aliases: + home: Front:Home:default + admin: Admin:Dashboard:default + sign: Front:Sign:in +``` + +En los enlaces, luego se escriben usando arroba, por ejemplo: + +```latte +administración +``` + +También son compatibles con todos los métodos que trabajan con enlaces, como `redirect()` y similares. + + +Enlaces no válidos +================== + +Puede suceder que creemos un enlace no válido, ya sea porque apunta a un presenter inexistente, o porque pasa más parámetros de los que el método de destino acepta en su firma, o cuando no se puede generar una URL para la acción de destino. Cómo tratar los enlaces no válidos lo determina la variable estática `Presenter::$invalidLinkMode`. Esta puede tomar una combinación de estos valores (constantes): + +- `Presenter::InvalidLinkSilent` - modo silencioso, se devuelve el carácter # como URL +- `Presenter::InvalidLinkWarning` - se lanza una advertencia E_USER_WARNING, que se registrará en modo de producción, pero no causará la interrupción de la ejecución del script +- `Presenter::InvalidLinkTextual` - advertencia visual, muestra el error directamente en el enlace +- `Presenter::InvalidLinkException` - se lanza la excepción InvalidLinkException + +La configuración predeterminada es `InvalidLinkWarning` en modo de producción y `InvalidLinkWarning | InvalidLinkTextual` en desarrollo. `InvalidLinkWarning` en el entorno de producción no causa la interrupción del script, pero la advertencia se registrará. En el entorno de desarrollo, [Tracy |tracy:] lo captura y muestra una pantalla azul. `InvalidLinkTextual` funciona devolviendo un mensaje de error como URL, que comienza con los caracteres `#error:`. Para que dichos enlaces sean evidentes a primera vista, agregaremos a nuestro CSS: ```css a[href^="#error:"] { @@ -242,7 +266,7 @@ a[href^="#error:"] { } ``` -Si no queremos que se produzcan advertencias en el entorno de desarrollo podemos activar el modo de enlace inválido silencioso en la [configuración |configuration]. +Si no queremos que se produzcan advertencias en el entorno de desarrollo, podemos establecer el modo silencioso directamente en la [configuración|configuration]. ```neon application: @@ -250,13 +274,13 @@ application: ``` -Generador de enlaces .[#toc-linkgenerator] -========================================== +LinkGenerator +============= -¿Cómo crear enlaces con el método `link()` comodidad, pero sin la presencia de un presentador? Es por eso que aquí está [api:Nette\Application\LinkGenerator]. +¿Cómo crear enlaces con una comodidad similar a la del método `link()`, pero sin la presencia de un presenter? Para eso está [api:Nette\Application\LinkGenerator]. -LinkGenerator es un servicio que puede haber pasado por el constructor y luego crear enlaces utilizando su método `link()`. +LinkGenerator es un servicio que puede solicitar que se le pase a través del constructor y luego crear enlaces con su método `link()`. -Hay una diferencia en comparación con los presentadores. LinkGenerator crea todos los enlaces como URLs absolutas. Además, no existe un "presentador actual", por lo que no es posible especificar únicamente el nombre de la acción `link('default')` o las rutas relativas a los [módulos |modules]. +Hay una diferencia con respecto a los presenters. LinkGenerator crea todos los enlaces directamente como URL absolutas. Y además, no existe un "presenter actual", por lo que no se puede indicar solo el nombre de la acción como destino `link('default')` ni indicar rutas relativas a módulos. Los enlaces no válidos siempre lanzan `Nette\Application\UI\InvalidLinkException`. diff --git a/application/es/directory-structure.texy b/application/es/directory-structure.texy new file mode 100644 index 0000000000..2392b7c719 --- /dev/null +++ b/application/es/directory-structure.texy @@ -0,0 +1,526 @@ +Estructura de directorios de la aplicación +****************************************** + +
    + +¿Cómo diseñar una estructura de directorios clara y escalable para proyectos en Nette Framework? Mostraremos las mejores prácticas que le ayudarán a organizar su código. Aprenderá: + +- cómo **dividir lógicamente** la aplicación en directorios +- cómo diseñar la estructura para que **escale bien** con el crecimiento del proyecto +- cuáles son las **alternativas posibles** y sus ventajas o desventajas + +
    + + +Es importante mencionar que Nette Framework en sí mismo no impone ninguna estructura específica. Está diseñado para adaptarse fácilmente a cualquier necesidad y preferencia. + + +Estructura básica del proyecto +============================== + +Aunque Nette Framework no dicta ninguna estructura de directorios fija, existe una disposición predeterminada probada en forma de [Web Project|https://github.com/nette/web-project]: + +/--pre +web-project/ +├── app/ ← directorio con la aplicación +├── assets/ ← archivos SCSS, JS, imágenes..., alternativamente resources/ +├── bin/ ← scripts para la línea de comandos +├── config/ ← configuración +├── log/ ← errores registrados +├── temp/ ← archivos temporales, caché +├── tests/ ← pruebas +├── vendor/ ← librerías instaladas por Composer +└── www/ ← directorio público (document-root) +\-- + +Puede modificar esta estructura libremente según sus necesidades: renombrar o mover carpetas. Después, solo necesita actualizar las rutas relativas a los directorios en el archivo `Bootstrap.php` y, opcionalmente, en `composer.json`. No se necesita nada más, ninguna reconfiguración complicada, ningún cambio de constantes. Nette dispone de una autodetección inteligente y reconoce automáticamente la ubicación de la aplicación, incluida su base de URL. + + +Principios de organización del código +===================================== + +Cuando explora un nuevo proyecto por primera vez, debería poder orientarse rápidamente en él. Imagine que expande el directorio `app/Model/` y ve esta estructura: + +/--pre +app/Model/ +├── Services/ +├── Repositories/ +└── Entities/ +\-- + +De ella, solo deduce que el proyecto utiliza algunos servicios, repositorios y entidades. No aprenderá nada sobre el propósito real de la aplicación. + +Veamos otro enfoque: **organización por dominios**: + +/--pre +app/Model/ +├── Cart/ +├── Payment/ +├── Order/ +└── Product/ +\-- + +Aquí es diferente: a primera vista, está claro que se trata de una tienda electrónica. Los propios nombres de los directorios revelan lo que hace la aplicación: trabaja con pagos, pedidos y productos. + +El primer enfoque (organización por tipo de clase) presenta una serie de problemas en la práctica: el código que está lógicamente relacionado está disperso en diferentes carpetas y tiene que saltar entre ellas. Por lo tanto, organizaremos por dominios. + + +Espacios de nombres +------------------- + +Es costumbre que la estructura de directorios corresponda a los espacios de nombres en la aplicación. Esto significa que la ubicación física de los archivos corresponde a su espacio de nombres. Por ejemplo, una clase ubicada en `app/Model/Product/ProductRepository.php` debería tener el espacio de nombres `App\Model\Product`. Este principio ayuda a orientarse en el código y simplifica la autocarga. + + +Singular vs plural en los nombres +--------------------------------- + +Observe que para los directorios principales de la aplicación usamos el singular: `app`, `config`, `log`, `temp`, `www`. Lo mismo dentro de la aplicación: `Model`, `Core`, `Presentation`. Esto se debe a que cada uno de ellos representa un concepto coherente. + +De manera similar, por ejemplo, `app/Model/Product` representa todo lo relacionado con los productos. No lo llamaremos `Products`, porque no es una carpeta llena de productos (eso significaría que habría archivos `nokia.php`, `samsung.php`). Es un espacio de nombres que contiene clases para trabajar con productos: `ProductRepository.php`, `ProductService.php`. + +La carpeta `app/Tasks` está en plural porque contiene un conjunto de scripts ejecutables independientes: `CleanupTask.php`, `ImportTask.php`. Cada uno de ellos es una unidad independiente. + +Para mantener la coherencia, recomendamos usar: +- Singular para espacios de nombres que representan una unidad funcional (aunque trabajen con múltiples entidades) +- Plural para colecciones de unidades independientes +- En caso de duda o si no quiere pensar en ello, elija el singular + + +Directorio público `www/` +========================= + +Este directorio es el único accesible desde la web (el llamado document-root). A menudo también puede encontrar el nombre `public/` en lugar de `www/`: es solo una cuestión de convención y no afecta la funcionalidad del framework. El directorio contiene: +- El [punto de entrada |bootstrapping#index.php] de la aplicación `index.php` +- El archivo `.htaccess` con reglas para mod_rewrite (para Apache) +- Archivos estáticos (CSS, JavaScript, imágenes) +- Archivos subidos + +Para la seguridad adecuada de la aplicación, es crucial tener el [document-root correctamente configurado |nette:troubleshooting#Cómo cambiar o eliminar el directorio www de la URL]. + +.[note] +Nunca coloque la carpeta `node_modules/` en este directorio: contiene miles de archivos que pueden ser ejecutables y no deben ser accesibles públicamente. + + +Directorio de aplicación `app/` +=============================== + +Este es el directorio principal con el código de la aplicación. Estructura básica: + +/--pre +app/ +├── Core/ ← asuntos de infraestructura +├── Model/ ← lógica de negocio +├── Presentation/ ← presenters y plantillas +├── Tasks/ ← scripts de comandos +└── Bootstrap.php ← clase de arranque de la aplicación +\-- + +`Bootstrap.php` es la [clase de inicio de la aplicación|bootstrapping] que inicializa el entorno, carga la configuración y crea el contenedor DI. + +Ahora veamos los subdirectorios individuales con más detalle. + + +Presenters y plantillas +======================= + +La parte de presentación de la aplicación la tenemos en el directorio `app/Presentation`. Una alternativa es el corto `app/UI`. Es el lugar para todos los presenters, sus plantillas y posibles clases auxiliares. + +Organizamos esta capa por dominios. En un proyecto complejo que combina una tienda electrónica, un blog y una API, la estructura se vería así: + +/--pre +app/Presentation/ +├── Shop/ ← frontend de la tienda electrónica +│ ├── Product/ +│ ├── Cart/ +│ └── Order/ +├── Blog/ ← blog +│ ├── Home/ +│ └── Post/ +├── Admin/ ← administración +│ ├── Dashboard/ +│ └── Products/ +└── Api/ ← endpoints de la API + └── V1/ +\-- + +Por el contrario, para un blog simple, usaríamos la siguiente división: + +/--pre +app/Presentation/ +├── Front/ ← frontend del sitio web +│ ├── Home/ +│ └── Post/ +├── Admin/ ← administración +│ ├── Dashboard/ +│ └── Posts/ +├── Error/ +└── Export/ ← RSS, sitemaps, etc. +\-- + +Carpetas como `Home/` o `Dashboard/` contienen presenters y plantillas. Carpetas como `Front/`, `Admin/` o `Api/` las llamamos **módulos**. Técnicamente, son directorios normales que sirven para la división lógica de la aplicación. + +Cada carpeta con un presenter contiene un presenter con el mismo nombre y sus plantillas. Por ejemplo, la carpeta `Dashboard/` contiene: + +/--pre +Dashboard/ +├── DashboardPresenter.php ← presenter +└── default.latte ← plantilla +\-- + +Esta estructura de directorios se refleja en los espacios de nombres de las clases. Por ejemplo, `DashboardPresenter` se encuentra en el espacio de nombres `App\Presentation\Admin\Dashboard` (ver [#Mapeo de presenters]): + +```php +namespace App\Presentation\Admin\Dashboard; + +class DashboardPresenter extends Nette\Application\UI\Presenter +{ + // ... +} +``` + +Al presenter `Dashboard` dentro del módulo `Admin` nos referimos en la aplicación usando la notación de dos puntos como `Admin:Dashboard`. A su acción `default` entonces como `Admin:Dashboard:default`. En caso de módulos anidados, usamos más dos puntos, por ejemplo `Shop:Order:Detail:default`. + + +Desarrollo flexible de la estructura +------------------------------------ + +Una de las grandes ventajas de esta estructura es cómo se adapta elegantemente a las crecientes necesidades del proyecto. Como ejemplo, tomemos la parte que genera feeds XML. Al principio, tenemos una forma simple: + +/--pre +Export/ +├── ExportPresenter.php ← un presenter para todas las exportaciones +├── sitemap.latte ← plantilla para el sitemap +└── feed.latte ← plantilla para el feed RSS +\-- + +Con el tiempo, se agregan otros tipos de feeds y necesitamos más lógica para ellos... ¡No hay problema! La carpeta `Export/` simplemente se convierte en un módulo: + +/--pre +Export/ +├── Sitemap/ +│ ├── SitemapPresenter.php +│ └── sitemap.latte +└── Feed/ + ├── FeedPresenter.php + ├── zbozi.latte ← feed para Zboží.cz + └── heureka.latte ← feed para Heureka.cz +\-- + +Esta transformación es completamente fluida: basta con crear nuevas subcarpetas, dividir el código en ellas y actualizar los enlaces (p. ej., de `Export:feed` a `Export:Feed:zbozi`). Gracias a esto, podemos expandir gradualmente la estructura según sea necesario, el nivel de anidamiento no está limitado de ninguna manera. + +Si, por ejemplo, en la administración tiene muchos presenters relacionados con la gestión de pedidos, como `OrderDetail`, `OrderEdit`, `OrderDispatch`, etc., puede crear un módulo (carpeta) `Order` en este lugar para una mejor organización, que contendrá (carpetas para) los presenters `Detail`, `Edit`, `Dispatch` y otros. + + +Ubicación de las plantillas +--------------------------- + +En los ejemplos anteriores, vimos que las plantillas se ubican directamente en la carpeta con el presenter: + +/--pre +Dashboard/ +├── DashboardPresenter.php ← presenter +├── DashboardTemplate.php ← clase opcional para la plantilla +└── default.latte ← plantilla +\-- + +Esta ubicación resulta ser la más conveniente en la práctica: tiene todos los archivos relacionados a mano. + +Alternativamente, puede colocar las plantillas en una subcarpeta `templates/`. Nette admite ambas variantes. Incluso puede colocar las plantillas completamente fuera de la carpeta `Presentation/`. Todo sobre las opciones de ubicación de plantillas se encuentra en el capítulo [Búsqueda de plantillas |templates#Búsqueda de plantillas]. + + +Clases auxiliares y componentes +------------------------------- + +A los presenters y plantillas a menudo les pertenecen otros archivos auxiliares. Los ubicamos lógicamente según su ámbito: + +1. **Directamente junto al presenter** en caso de componentes específicos para ese presenter: + +/--pre +Product/ +├── ProductPresenter.php +├── ProductGrid.php ← componente para listar productos +└── FilterForm.php ← formulario para filtrar +\-- + +2. **Para el módulo** - recomendamos usar la carpeta `Accessory`, que se coloca convenientemente al principio del alfabeto: + +/--pre +Front/ +├── Accessory/ +│ ├── NavbarControl.php ← componentes para el frontend +│ └── TemplateFilters.php +├── Product/ +└── Cart/ +\-- + +3. **Para toda la aplicación** - en `Presentation/Accessory/`: +/--pre +app/Presentation/ +├── Accessory/ +│ ├── LatteExtension.php +│ └── TemplateFilters.php +├── Front/ +└── Admin/ +\-- + +O puede colocar clases auxiliares como `LatteExtension.php` o `TemplateFilters.php` en la carpeta de infraestructura `app/Core/Latte/`. Y los componentes en `app/Components`. La elección depende de las costumbres del equipo. + + +Modelo - el corazón de la aplicación +==================================== + +El modelo contiene toda la lógica de negocio de la aplicación. Para su organización, se aplica nuevamente la regla: estructuramos por dominios: + +/--pre +app/Model/ +├── Payment/ ← todo sobre pagos +│ ├── PaymentFacade.php ← punto de entrada principal +│ ├── PaymentRepository.php +│ ├── Payment.php ← entidad +├── Order/ ← todo sobre pedidos +│ ├── OrderFacade.php +│ ├── OrderRepository.php +│ ├── Order.php +└── Shipping/ ← todo sobre envíos +\-- + +En el modelo, típicamente encontrará estos tipos de clases: + +**Fachadas (Facades)**: representan el punto de entrada principal a un dominio específico en la aplicación. Actúan como un orquestador que coordina la colaboración entre diferentes servicios con el fin de implementar casos de uso completos (como "crear pedido" o "procesar pago"). Bajo su capa de orquestación, la fachada oculta los detalles de implementación del resto de la aplicación, proporcionando así una interfaz limpia para trabajar con el dominio dado. + +```php +class OrderFacade +{ + public function createOrder(Cart $cart): Order + { + // validación + // creación del pedido + // envío de correo electrónico + // registro en estadísticas + } +} +``` + +**Servicios**: se centran en una operación de negocio específica dentro del dominio. A diferencia de la fachada, que orquesta casos de uso completos, el servicio implementa lógica de negocio específica (como cálculos de precios o procesamiento de pagos). Los servicios suelen ser sin estado y pueden ser utilizados ya sea por fachadas como bloques de construcción para operaciones más complejas, o directamente por otras partes de la aplicación para tareas más simples. + +```php +class PricingService +{ + public function calculateTotal(Order $order): Money + { + // cálculo del precio + } +} +``` + +**Repositorios**: aseguran toda la comunicación con el almacenamiento de datos, típicamente una base de datos. Su tarea es cargar y guardar entidades e implementar métodos para su búsqueda. El repositorio aísla al resto de la aplicación de los detalles de implementación de la base de datos y proporciona una interfaz orientada a objetos para trabajar con los datos. + +```php +class OrderRepository +{ + public function find(int $id): ?Order + { + } + + public function findByCustomer(int $customerId): array + { + } +} +``` + +**Entidades**: objetos que representan los principales conceptos de negocio en la aplicación, que tienen su identidad y cambian con el tiempo. Típicamente, son clases mapeadas a tablas de bases de datos usando un ORM (como Nette Database Explorer o Doctrine). Las entidades pueden contener reglas de negocio relacionadas con sus datos y lógica de validación. + +```php +// Entidad mapeada a la tabla de base de datos orders +class Order extends Nette\Database\Table\ActiveRow +{ + public function addItem(Product $product, int $quantity): void + { + $this->related('order_items')->insert([ + 'product_id' => $product->id, + 'quantity' => $quantity, + 'unit_price' => $product->price, + ]); + } +} +``` + +**Objetos de valor (Value objects)**: objetos inmutables que representan valores sin identidad propia - por ejemplo, una cantidad monetaria o una dirección de correo electrónico. Dos instancias de un objeto de valor con los mismos valores son consideradas idénticas. + + +Código de infraestructura +========================= + +La carpeta `Core/` (o también `Infrastructure/`) es el hogar de la base técnica de la aplicación. El código de infraestructura típicamente incluye: + +/--pre +app/Core/ +├── Router/ ← enrutamiento y gestión de URL +│ └── RouterFactory.php +├── Security/ ← autenticación y autorización +│ ├── Authenticator.php +│ └── Authorizator.php +├── Logging/ ← registro y monitoreo +│ ├── SentryLogger.php +│ └── FileLogger.php +├── Cache/ ← capa de caché +│ └── FullPageCache.php +└── Integration/ ← integración con servicios ext. + ├── Slack/ + └── Stripe/ +\-- + +En proyectos más pequeños, por supuesto, basta con una estructura plana: + +/--pre +Core/ +├── RouterFactory.php +├── Authenticator.php +└── QueueMailer.php +\-- + +Se trata de código que: + +- Resuelve la infraestructura técnica (enrutamiento, registro, caché) +- Integra servicios externos (Sentry, Elasticsearch, Redis) +- Proporciona servicios básicos para toda la aplicación (correo, base de datos) +- Es mayormente independiente del dominio específico - la caché o el logger funciona igual para una tienda electrónica o un blog. + +¿Duda si una clase determinada pertenece aquí o al modelo? La diferencia clave es que el código en `Core/`: + +- No sabe nada sobre el dominio (productos, pedidos, artículos) +- Es mayormente posible transferirlo a otro proyecto +- Resuelve "cómo funciona" (cómo enviar un correo), no "qué hace" (qué correo enviar) + +Ejemplo para una mejor comprensión: + +- `App\Core\MailerFactory` - crea instancias de la clase para enviar correos electrónicos, resuelve la configuración SMTP +- `App\Model\OrderMailer` - usa `MailerFactory` para enviar correos electrónicos sobre pedidos, conoce sus plantillas y sabe cuándo deben enviarse + + +Scripts de comandos +=================== + +Las aplicaciones a menudo necesitan realizar actividades fuera de las peticiones HTTP normales - ya sea procesamiento de datos en segundo plano, mantenimiento o tareas periódicas. Para la ejecución sirven scripts simples en el directorio `bin/`, la lógica de implementación la colocamos en `app/Tasks/` (o `app/Commands/`). + +Ejemplo: + +/--pre +app/Tasks/ +├── Maintenance/ ← scripts de mantenimiento +│ ├── CleanupCommand.php ← eliminación de datos antiguos +│ └── DbOptimizeCommand.php ← optimización de la base de datos +├── Integration/ ← integración con sistemas externos +│ ├── ImportProducts.php ← importación desde el sistema del proveedor +│ └── SyncOrders.php ← sincronización de pedidos +└── Scheduled/ ← tareas periódicas + ├── NewsletterCommand.php ← envío de newsletters + └── ReminderCommand.php ← notificaciones a clientes +\-- + +¿Qué pertenece al modelo y qué a los scripts de comandos? Por ejemplo, la lógica para enviar un correo electrónico es parte del modelo, el envío masivo de miles de correos electrónicos ya pertenece a `Tasks/`. + +Las tareas generalmente [se ejecutan desde la línea de comandos |https://blog.nette.org/en/cli-scripts-in-nette-application] o a través de cron. También se pueden ejecutar a través de una petición HTTP, pero es necesario pensar en la seguridad. El presenter que ejecuta la tarea debe estar protegido, por ejemplo, solo para usuarios autenticados o con un token fuerte y acceso desde direcciones IP permitidas. Para tareas largas, es necesario aumentar el límite de tiempo del script y usar `session_write_close()` para que la sesión no se bloquee. + + +Otros directorios posibles +========================== + +Además de los directorios básicos mencionados, puede agregar otras carpetas especializadas según las necesidades del proyecto. Veamos las más comunes y su uso: + +/--pre +app/ +├── Api/ ← lógica para la API independiente de la capa de presentación +├── Database/ ← scripts de migración y seeders para datos de prueba +├── Components/ ← componentes visuales compartidos en toda la aplicación +├── Event/ ← útil si usa arquitectura dirigida por eventos +├── Mail/ ← plantillas de correo electrónico y lógica relacionada +└── Utils/ ← clases auxiliares +\-- + +Para componentes visuales compartidos utilizados en presenters en toda la aplicación, se puede usar la carpeta `app/Components` o `app/Controls`: + +/--pre +app/Components/ +├── Form/ ← componentes de formulario compartidos +│ ├── SignInForm.php +│ └── UserForm.php +├── Grid/ ← componentes para listados de datos +│ └── DataGrid.php +└── Navigation/ ← elementos de navegación + ├── Breadcrumbs.php + └── Menu.php +\-- + +Aquí pertenecen los componentes que tienen una lógica más compleja. Si desea compartir componentes entre varios proyectos, es recomendable extraerlos a un paquete composer separado. + +En el directorio `app/Mail` puede colocar la gestión de la comunicación por correo electrónico: + +/--pre +app/Mail/ +├── templates/ ← plantillas de correo electrónico +│ ├── order-confirmation.latte +│ └── welcome.latte +└── OrderMailer.php +\-- + + +Mapeo de presenters +=================== + +El mapeo define reglas para derivar el nombre de la clase a partir del nombre del presenter. Las especificamos en la [configuración|configuration] bajo la clave `application › mapping`. + +En esta página, hemos mostrado que colocamos los presenters en la carpeta `app/Presentation` (o `app/UI`). Debemos comunicar esta convención a Nette en el archivo de configuración. Basta con una línea: + +```neon +application: + mapping: App\Presentation\*\**Presenter +``` + +¿Cómo funciona el mapeo? Para una mejor comprensión, imaginemos primero una aplicación sin módulos. Queremos que las clases de los presenters caigan en el espacio de nombres `App\Presentation`, para que el presenter `Home` se mapee a la clase `App\Presentation\HomePresenter`. Lo cual logramos con esta configuración: + +```neon +application: + mapping: App\Presentation\*Presenter +``` + +El mapeo funciona de tal manera que el nombre del presenter `Home` reemplaza el asterisco en la máscara `App\Presentation\*Presenter`, obteniendo así el nombre de clase resultante `App\Presentation\HomePresenter`. ¡Simple! + +Pero como puede ver en los ejemplos de este y otros capítulos, colocamos las clases de los presenters en subdirectorios epónimos, por ejemplo, el presenter `Home` se mapea a la clase `App\Presentation\Home\HomePresenter`. Esto se logra duplicando los dos puntos (requiere Nette Application 3.2): + +```neon +application: + mapping: App\Presentation\**Presenter +``` + +Ahora procederemos a mapear los presenters a módulos. Para cada módulo podemos definir un mapeo específico: + +```neon +application: + mapping: + Front: App\Presentation\Front\**Presenter + Admin: App\Presentation\Admin\**Presenter + Api: App\Api\*Presenter +``` + +Según esta configuración, el presenter `Front:Home` se mapea a la clase `App\Presentation\Front\Home\HomePresenter`, mientras que el presenter `Api:OAuth` a la clase `App\Api\OAuthPresenter`. + +Dado que los módulos `Front` y `Admin` tienen una forma similar de mapeo y probablemente habrá más módulos de este tipo, es posible crear una regla general que los reemplace. Así, se agregará un nuevo asterisco a la máscara de clase para el módulo: + +```neon +application: + mapping: + *: App\Presentation\*\**Presenter + Api: App\Api\*Presenter +``` + +Funciona también para estructuras de directorios más profundamente anidadas, como por ejemplo el presenter `Admin:User:Edit`, el segmento con asterisco se repite para cada nivel y el resultado es la clase `App\Presentation\Admin\User\Edit\EditPresenter`. + +Una notación alternativa es usar un array compuesto por tres segmentos en lugar de una cadena. Esta notación es equivalente a la anterior: + +```neon +application: + mapping: + *: [App\Presentation, *, **Presenter] + Api: [App\Api, '', *Presenter] +``` diff --git a/application/es/how-it-works.texy b/application/es/how-it-works.texy index 41276637aa..40246de021 100644 --- a/application/es/how-it-works.texy +++ b/application/es/how-it-works.texy @@ -3,99 +3,101 @@
    -Está leyendo el documento básico de la documentación de Nette. Aprenderá todo el principio de las aplicaciones web. Nette de la A a la Z, desde el momento del nacimiento hasta el último suspiro del script PHP. Después de leer usted sabrá: +Está leyendo el documento fundamental de la documentación de Nette. Aprenderá todo el principio de funcionamiento de las aplicaciones web. De la A a la Z, desde el momento del nacimiento hasta el último suspiro del script PHP. Después de leerlo, sabrá: - cómo funciona todo -- qué es Bootstrap, Presenter y DI container +- qué son Bootstrap, Presenter y el contenedor DI - cómo es la estructura de directorios
    -Estructura del directorio .[#toc-directory-structure] -===================================================== +Estructura de directorios +========================= -Abra un esqueleto de ejemplo de una aplicación web llamada [WebProject |https://github.com/nette/web-project] y podrá observar los archivos sobre los que se escribe. +Abra el ejemplo del esqueleto de una aplicación web llamado [WebProject|https://github.com/nette/web-project] y mientras lee, puede mirar los archivos de los que se habla. -La estructura de directorios se parece a esto +La estructura de directorios tiene este aspecto: /--pre web-project/ ├── app/ ← directorio con la aplicación -│ ├── Presenters/ ← clases para presentadores -│ │ ├── HomePresenter.php ← Home de inicio de la clase de presentador -│ │ └── templates/ ← directorio de plantillas -│ │ ├── @layout.latte ← plantilla de diseño compartida -│ │ └── Home/ ← plantillas para Home presentador de inicio -│ │ └── default.latte ← plantilla para la acción `default` -│ ├── Router/ ← configuración de direcciones URL +│ ├── Core/ ← clases básicas necesarias para el funcionamiento +│ │ └── RouterFactory.php ← configuración de direcciones URL +│ ├── Presentation/ ← presenters, plantillas, etc. +│ │ ├── @layout.latte ← plantilla de layout +│ │ └── Home/ ← directorio del presenter Home +│ │ ├── HomePresenter.php ← clase del presenter Home +│ │ └── default.latte ← plantilla de la acción default │ └── Bootstrap.php ← clase de arranque Bootstrap -├── bin/ ← scripts para la línea de comandos +├── assets/ ← recursos (SCSS, TypeScript, imágenes de origen). +├── bin/ ← scripts ejecutados desde la línea de comandos ├── config/ ← archivos de configuración │ ├── common.neon -│ └── local.neon -├── log/ ← registros de errores -├── temp/ ← archivos temporales, caché, ... -├── vendor/ ← libraries installed by Composer +│ └── services.neon +├── log/ ← errores registrados +├── temp/ ← archivos temporales, caché, etc. +├── vendor/ ← librerías instaladas por Composer │ ├── ... -│ └── autoload.php ← carga automática de librerías instaladas por Composer -├── www/ ← directorio público, raíz documental del proyecto -│ ├── .htaccess ← reglas mod_rewrite, etc. -│ └── index.php ← archivo inicial que lanza la aplicación +│ └── autoload.php ← autoloading de todos los paquetes instalados +├── www/ ← directorio público o document-root del proyecto +│ ├── assets/ ← archivos estáticos compilados (CSS, JS, imágenes, ...) +│ ├── .htaccess ← reglas mod_rewrite +│ └── index.php ← archivo inicial con el que se inicia la aplicación └── .htaccess ← prohíbe el acceso a todos los directorios excepto www \-- -Puedes cambiar la estructura de directorios de cualquier forma, renombrar o mover carpetas, y entonces sólo tienes que editar las rutas a `log/` y `temp/` en el archivo `Bootstrap.php` y la ruta a este archivo en `composer.json` en la sección `autoload`. Nada más, sin reconfiguraciones complicadas ni cambios constantes. Nette tiene una [autodetección inteligente |bootstrap#development-vs-production-mode]. +Puede cambiar la estructura de directorios como desee, renombrar o mover carpetas, es completamente flexible. Nette, además, dispone de una autodetección inteligente y reconoce automáticamente la ubicación de la aplicación, incluida su base de URL. -Para aplicaciones un poco más grandes, podemos dividir las carpetas con presentadores y plantillas en subdirectorios (en el disco) y en espacios de nombres (en el código), que llamamos [módulos |modules]. +En aplicaciones un poco más grandes, podemos [dividir las carpetas con presenters y plantillas en subdirectorios |directory-structure#Presenters y plantillas] y las clases en espacios de nombres, que llamamos módulos. -El directorio `www/` es el directorio público o raíz documental del proyecto. Puedes renombrarlo sin tener que configurar nada más en el lado de la aplicación. Solo necesitas [configurar el hosting |nette:troubleshooting#How to change or remove www directory from URL] para que el document-root vaya a este directorio. +El directorio `www/` representa el llamado directorio público o document-root del proyecto. Puede renombrarlo sin necesidad de configurar nada más en el lado de la aplicación. Solo es necesario [configurar el hosting |nette:troubleshooting#Cómo cambiar o eliminar el directorio www de la URL] para que el document-root apunte a este directorio. -También puede descargar el WebProject directamente, incluyendo Nette, utilizando [Composer |best-practices:composer]: +También puede descargar WebProject directamente incluyendo Nette usando [Composer |best-practices:composer]: ```shell composer create-project nette/web-project ``` -En Linux o macOS, establezca los [permisos de escritura |nette:troubleshooting#Setting directory permissions] para los directorios `log/` y `temp/`. +En Linux o macOS, establezca [permisos de escritura |nette:troubleshooting#Configuración de permisos de directorio] para los directorios `log/` y `temp/`. -La aplicación WebProject está lista para ejecutarse, no es necesario configurar nada más y puede verla directamente en el navegador accediendo a la carpeta `www/`. +La aplicación WebProject está lista para ejecutarse, no es necesario configurar absolutamente nada y puede mostrarla directamente en el navegador accediendo a la carpeta `www/`. -Petición HTTP .[#toc-http-request] -================================== +Petición HTTP +============= -Todo comienza cuando un usuario abre la página en un navegador y éste llama al servidor con una petición HTTP. La petición va a un fichero PHP situado en el directorio público `www/`, que es `index.php`. Supongamos que se trata de una petición a `https://example.com/product/123` y se ejecutará. +Todo comienza en el momento en que el usuario abre una página en el navegador. Es decir, cuando el navegador llama al servidor con una petición HTTP. La petición apunta a un único archivo PHP, que se encuentra en el directorio público `www/`, y este es `index.php`. Supongamos que se trata de una petición a la dirección `https://example.com/product/123`. Gracias a la [configuración adecuada del servidor |nette:troubleshooting#Cómo configurar el servidor para URLs amigables], incluso esta URL se mapea al archivo `index.php` y este se ejecuta. Su tarea es: 1) inicializar el entorno 2) obtener la fábrica -3) lanzar la aplicación Nette que gestiona la solicitud +3) iniciar la aplicación Nette, que gestionará la petición -¿Qué tipo de fábrica? No fabricamos tractores, ¡sino páginas web! Espere, se lo explicaremos enseguida. +¿Qué fábrica? ¡No fabricamos tractores, sino páginas web! Espere, se explicará de inmediato. -Por "inicializar el entorno" entendemos, por ejemplo, que se activa [Tracy |tracy:], que es una herramienta increíble para registrar o visualizar errores. Registra los errores en el servidor de producción y los muestra directamente en el servidor de desarrollo. Por lo tanto, la inicialización también necesita decidir si el sitio se está ejecutando en modo de producción o de desarrollo. Para ello, Nette utiliza la autodetección: si ejecuta el sitio en localhost, se ejecuta en modo desarrollador. No hay que configurar nada y la aplicación está lista tanto para el desarrollo como para el despliegue en producción. Estos pasos se realizan y describen en detalle en el capítulo sobre la [clase Bootstrap |bootstrap]. +Con las palabras "inicialización del entorno" nos referimos, por ejemplo, a que se activa [Tracy|tracy:], que es una herramienta increíble para el registro o la visualización de errores. En el servidor de producción, registra los errores, en el de desarrollo los muestra directamente. Por lo tanto, la inicialización también incluye la decisión de si el sitio web se ejecuta en modo de producción o de desarrollo. Para esto, Nette utiliza una [autodetección inteligente |bootstrapping#Modo de desarrollo vs producción]: si ejecuta el sitio web en localhost, se ejecuta en modo de desarrollo. Por lo tanto, no necesita configurar nada y la aplicación está lista tanto para el desarrollo como para la implementación en producción. Estos pasos se realizan y se describen detalladamente en el capítulo sobre la [clase Bootstrap|bootstrapping]. -El tercer punto (sí, nos hemos saltado el segundo, pero volveremos a él) es iniciar la aplicación. El manejo de las peticiones HTTP en Nette lo realiza la clase `Nette\Application\Application` (en adelante `Application`), por lo que cuando decimos "ejecutar una aplicación", nos referimos a llamar a un método con el nombre `run()` sobre un objeto de esta clase. +El tercer punto (sí, saltamos el segundo, pero volveremos a él) es el inicio de la aplicación. La gestión de las peticiones HTTP en Nette está a cargo de la clase `Nette\Application\Application` (en adelante `Application`), por lo que cuando decimos iniciar la aplicación, nos referimos específicamente a llamar al método con el nombre apropiado `run()` en el objeto de esta clase. -Nette es un mentor que te guía para escribir aplicaciones limpias mediante metodologías probadas. Y la más probada se llama **inyección de dependencia**, abreviada DI. De momento no queremos agobiarte explicando DI, ya que hay un [capítulo |dependency-injection:introduction] aparte, lo importante aquí es que los objetos clave normalmente serán creados por una fábrica de objetos llamada **contenedor DI** (abreviado DIC). Sí, esta es la fábrica que se mencionó hace un rato. Y también crea el objeto `Application` por nosotros, así que primero necesitamos un contenedor. Lo obtenemos usando la clase `Configurator` y dejamos que produzca el objeto `Application`, llamamos al método `run()` y esto inicia la aplicación Nette. Esto es exactamente lo que sucede en el archivo [index.php |bootstrap#index.php]. +Nette es un mentor que le guía para escribir aplicaciones limpias según metodologías probadas. Y una de las más probadas se llama **inyección de dependencias**, abreviada como DI. En este momento no queremos cargarle con la explicación de DI, para eso está [un capítulo aparte|dependency-injection:introduction], lo importante es la consecuencia de que los objetos clave generalmente nos los creará una fábrica de objetos, que se llama **contenedor DI** (abreviado como DIC). Sí, esa es la fábrica de la que hablamos hace un momento. Y también nos fabricará el objeto `Application`, por eso necesitamos primero el contenedor. Lo obtenemos usando la clase `Configurator` y le pedimos que fabrique el objeto `Application`, llamamos al método `run()` en él y así se inicia la aplicación Nette. Exactamente esto sucede en el archivo [index.php |bootstrapping#index.php]. -Aplicación Nette .[#toc-nette-application] -========================================== +Nette Application +================= -La clase Aplicación tiene una única tarea: responder a una petición HTTP. +La clase Application tiene una única tarea: responder a la petición HTTP. -Las aplicaciones escritas en Nette se dividen en muchos de los llamados presentadores (en otros frameworks se puede encontrar con el término controlador, que es lo mismo), que son clases que representan una página web específica: por ejemplo, página de inicio; producto en e-shop; formulario de registro; feed sitemap, etc. La aplicación puede tener de uno a miles de presentadores. +Las aplicaciones escritas en Nette se dividen en muchos llamados presenters (en otros frameworks puede encontrar el término controller, es lo mismo), que son clases, cada una de las cuales representa alguna página específica del sitio web: p. ej., la página de inicio; un producto en una tienda electrónica; un formulario de inicio de sesión; un feed sitemap, etc. Una aplicación puede tener desde uno hasta miles de presenters. -La aplicación comienza pidiendo al llamado enrutador que decida a cuál de los presentadores debe pasar la petición actual para su procesamiento. El enrutador decide de quién es la responsabilidad. Mira la URL de entrada `https://example.com/product/123`, que quiere `show` un producto con `id: 123` como acción. Es una buena costumbre escribir pares de presentador + acción separados por dos puntos como `Product:show`. +Application comienza pidiendo al llamado router que decida a cuál de los presenters pasar la petición actual para su gestión. El router decide de quién es la responsabilidad. Mira la URL de entrada `https://example.com/product/123` y, basándose en cómo está configurado, decide que este es trabajo, por ejemplo, para el **presenter** `Product`, al que le pedirá como **acción** la visualización (`show`) del producto con `id: 123`. Es una buena costumbre escribir el par presenter + acción separado por dos puntos como `Product:show`. -Así que el enrutador transforma la URL en un par `Presenter:action` + parámetros, en nuestro caso `Product:show` + `id: 123`. Puedes ver el aspecto de un enrutador en el archivo `app/Router/RouterFactory.php` y lo describiremos en detalle en el capítulo [Enrutamiento |Routing]. +Por lo tanto, el router transformó la URL en el par `Presenter:action` + parámetros, en nuestro caso `Product:show` + `id: 123`. Puede ver cómo es un router de este tipo en el archivo `app/Core/RouterFactory.php` y lo describimos detalladamente en el capítulo [Enrutamiento |Routing]. -Sigamos. La aplicación ya conoce el nombre del presentador y puede continuar. Creando un objeto `ProductPresenter`, que es el código del presentador `Product`. Más concretamente, le pide al contenedor DI que cree el presentador, porque producir objetos es su trabajo. +Sigamos. Application ya conoce el nombre del presenter y puede continuar. Creando el objeto de la clase `ProductPresenter`, que es el código del presenter `Product`. Más precisamente, pide al contenedor DI que fabrique el presenter, porque para eso está él. -El presentador podría tener este aspecto: +El presenter puede verse así: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -113,92 +115,86 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -El presentador gestiona la solicitud. Y la tarea está clara: hacer la acción `show` con `id: 123`. Lo que en el lenguaje de los presentadores significa que se llama al método `renderShow()` y en el parámetro `$id` se obtiene `123`. +La gestión de la petición la asume el presenter. Y la tarea es clara: realiza la acción `show` con `id: 123`. Lo que en el lenguaje de los presenters significa que se llama al método `renderShow()` y en el parámetro `$id` recibe `123`. -Un presentador puede manejar múltiples acciones, es decir, tener múltiples métodos `render()`. Pero recomendamos diseñar los presentadores con una o las menos acciones posibles. +Un presenter puede manejar múltiples acciones, es decir, tener múltiples métodos `render()`. Pero recomendamos diseñar presenters con una o la menor cantidad posible de acciones. -Así, se llamó al método `renderShow(123)`, cuyo código es ficticio ejemplo, pero se puede ver en él cómo los datos se pasan a la plantilla, es decir, escribiendo a `$this->template`. +Entonces, se llamó al método `renderShow(123)`, cuyo código es un ejemplo ficticio, pero puede ver en él cómo se pasan los datos a la plantilla, es decir, escribiendo en `$this->template`. -Posteriormente, el presentador devuelve la respuesta. Esta puede ser una página HTML, una imagen, un documento XML, el envío de un fichero desde disco, JSON o la redirección a otra página. Es importante destacar que, si no decimos explícitamente cómo responder (que es el caso de `ProductPresenter`), la respuesta será renderizar la plantilla con una página HTML. ¿Por qué? Pues porque en el 99% de los casos queremos dibujar una plantilla, así que el presentador toma este comportamiento por defecto y quiere facilitarnos el trabajo. Ese es el punto de Nette. +Posteriormente, el presenter devuelve una respuesta. Esta puede ser una página HTML, una imagen, un documento XML, el envío de un archivo desde el disco, JSON o incluso una redirección a otra página. Es importante que si no decimos explícitamente cómo debe responder (que es el caso de `ProductPresenter`), la respuesta será la renderización de una plantilla con una página HTML. ¿Por qué? Porque en el 99% de los casos queremos renderizar una plantilla, por lo que el presenter toma este comportamiento como predeterminado y quiere facilitarnos el trabajo. Ese es el propósito de Nette. -Ni siquiera tenemos que indicar qué plantilla dibujar, él deriva la ruta hacia ella según una lógica simple. En el caso del presentador `Product` y la acción `show`, intenta ver si uno de estos archivos de plantilla existe en relación al directorio donde se encuentra la clase `ProductPresenter`: +Ni siquiera tenemos que indicar qué plantilla renderizar, él mismo deduce la ruta hacia ella. En el caso de la acción `show`, simplemente intenta cargar la plantilla `show.latte` en el directorio con la clase `ProductPresenter`. También intentará localizar el layout en el archivo `@layout.latte` (más detalles sobre la [búsqueda de plantillas |templates#Búsqueda de plantillas]). -- `templates/Product/show.latte` -- `templates/Product.show.latte` - -También intentará encontrar el diseño en el archivo `@layout.latte` y luego renderizará la plantilla. Ahora se completa la tarea del presentador y de toda la aplicación. Si la plantilla no existe, se devolverá una página con el error 404. Puedes leer más sobre los presentadores en la página de [Presentadores |Presenters]. +Y posteriormente renderiza las plantillas. Con esto, la tarea del presenter y de toda la aplicación está completa y la obra está terminada. Si la plantilla no existiera, se devolvería una página con error 404. Puede leer más sobre los presenters en la página [Presenters|presenters]. [* request-flow.svg *] -Sólo para estar seguros, intentemos recapitular todo el proceso con una URL ligeramente diferente: +Por si acaso, intentemos recapitular todo el proceso con una URL ligeramente diferente: -1) la URL será `https://example.com` -2) arrancamos la aplicación, creamos un contenedor y ejecutamos `Application::run()` -3) el router decodifica la URL como un par `Home:default` -4) se crea un objeto `HomePresenter` -5) se llama al método `renderDefault()` (si existe) -6) se renderiza una plantilla `templates/Home/default.latte` con un diseño `templates/@layout.latte` +1) La URL será `https://example.com` +2) Arrancamos la aplicación, se crea el contenedor y se ejecuta `Application::run()` +3) El router decodifica la URL como el par `Home:default` +4) Se crea el objeto de la clase `HomePresenter` +5) Se llama al método `renderDefault()` (si existe) +6) Se renderiza la plantilla, p. ej., `default.latte` con el layout, p. ej., `@layout.latte` -Puede que ahora te hayas encontrado con un montón de conceptos nuevos, pero creemos que tienen sentido. Crear aplicaciones en Nette es pan comido. +Puede que ahora se haya encontrado con muchos conceptos nuevos, pero creemos que tienen sentido. Crear aplicaciones en Nette es increíblemente fácil. -Plantillas .[#toc-templates] -============================ +Plantillas +========== -Cuando se trata de las plantillas, Nette utiliza el sistema de plantillas [Latte |latte:]. Es por eso que los archivos con plantillas terminan con `.latte`. Se usa Latte porque es el sistema de plantillas más seguro para PHP, y al mismo tiempo el sistema más intuitivo. No tienes que aprender mucho nuevo, sólo necesitas saber PHP y algunas etiquetas Latte. Encontrarás todo [en la documentación |latte:]. +Ya que hablamos de plantillas, en Nette se utiliza el sistema de plantillas [Latte |latte:]. Por eso esas extensiones `.latte` en las plantillas. Latte se utiliza, por un lado, porque es el sistema de plantillas más seguro para PHP y, al mismo tiempo, el sistema más intuitivo. No necesita aprender mucho nuevo, le basta con conocer PHP y algunas etiquetas. Todo lo aprenderá [en la documentación |templates]. -En la plantilla [creamos enlaces |creating-links] a otros presentadores y acciones de la siguiente manera: +En la plantilla, se [crean enlaces |creating-links] a otros presenters y acciones de esta manera: ```latte -product detail +detalle del producto ``` -Basta con escribir el conocido par `Presenter:action` en lugar de la URL real e incluir cualquier parámetro. El truco es `n:href`, que dice que este atributo será procesado por Nette. Y lo generará: +Simplemente, en lugar de la URL real, escribe el par conocido `Presenter:action` e indica los parámetros necesarios. El truco está en `n:href`, que indica que este atributo será procesado por Nette. Y generará: ```latte -product detail +detalle del producto ``` -El enrutador antes mencionado es el encargado de generar la URL. De hecho, los routers en Nette son únicos en el sentido de que pueden realizar no sólo transformaciones de una URL a un par presentador:acción, sino también viceversa generar una URL a partir del nombre del presentador + acción + parámetros. -Gracias a esto, en Nette se puede cambiar completamente la forma de la URL en toda la aplicación terminada sin cambiar ni un solo carácter de la plantilla o presentador sólo modificando el enrutador. -Y gracias a esto, funciona la llamada canonización, que es otra característica única de Nette, que mejora el SEO (optimización de la buscabilidad en internet) evitando automáticamente la existencia de contenido duplicado en diferentes URLs. -A muchos programadores esto les parece asombroso. +La generación de URL está a cargo del router mencionado anteriormente. Es decir, los routers en Nette son excepcionales porque pueden realizar no solo transformaciones de URL al par presenter:action, sino también al revés, es decir, generar una URL a partir del nombre del presenter + acción + parámetros. Gracias a esto, en Nette puede cambiar completamente las formas de las URL en toda la aplicación terminada, sin cambiar un solo carácter en la plantilla o el presenter. Simplemente modificando el router. También gracias a esto funciona la llamada canonización, que es otra característica única de Nette que contribuye a un mejor SEO (optimización para motores de búsqueda) al evitar automáticamente la existencia de contenido duplicado en diferentes URL. Muchos programadores lo consideran asombroso. -Componentes interactivos .[#toc-interactive-components] -======================================================= +Componentes interactivos +======================== -Tenemos una cosa más que contarte sobre los presentadores: tienen un sistema de componentes incorporado. Los más veteranos recordaréis algo parecido de Delphi o ASP.NET Web Forms. React o Vue.js están construidos sobre algo remotamente similar. En el mundo de los frameworks PHP, esta es una característica completamente única. +Sobre los presenters debemos revelarle una cosa más: tienen incorporado un sistema de componentes. Algo similar pueden recordar los veteranos de Delphi o ASP.NET Web Forms, algo remotamente similar es la base de React o Vue.js. En el mundo de los frameworks PHP, es una característica absolutamente única. -Los componentes son unidades separadas reutilizables que colocamos en páginas (es decir, presentadores). Pueden ser [formularios |forms:in-presenter], [datagrids |https://componette.org/contributte/datagrid/], menús, encuestas, de hecho cualquier cosa que tenga sentido usar repetidamente. Podemos crear nuestros propios componentes o utilizar algunos de la [enorme gama |https://componette.org] de componentes de código abierto. +Los componentes son unidades reutilizables independientes que insertamos en las páginas (es decir, presenters). Pueden ser [formularios |forms:in-presenter], [datagrids |https://componette.org/contributte/datagrid/], menús, encuestas de votación, en realidad cualquier cosa que tenga sentido usar repetidamente. Podemos crear nuestros propios componentes o usar algunos de la [enorme oferta |https://componette.org] de componentes de código abierto. -Los componentes cambian radicalmente el enfoque del desarrollo de aplicaciones. Abrirán nuevas posibilidades para componer páginas a partir de unidades predefinidas. Y tienen algo en común con [Hollywood |components#Hollywood style]. +Los componentes influyen fundamentalmente en el enfoque para la creación de aplicaciones. Le abrirán nuevas posibilidades de componer páginas a partir de unidades prefabricadas. Y además tienen algo en común con [Hollywood |components#Estilo Hollywood]. -Contenedor DI y configuración .[#toc-di-container-and-configuration] -==================================================================== +Contenedor DI y configuración +============================= -El contenedor DI (fábrica de objetos) es el corazón de toda la aplicación. +El contenedor DI o fábrica de objetos es el corazón de toda la aplicación. -No te preocupes, no es una caja negra mágica, como podría parecer por las palabras anteriores. En realidad, es una clase PHP bastante aburrida generada por Nette y almacenada en un directorio caché. Tiene un montón de métodos llamados como `createServiceAbcd()` y cada uno de ellos crea y devuelve un objeto. Sí, también hay un método `createServiceApplication()` que producirá `Nette\Application\Application`, que necesitamos en el archivo `index.php` para ejecutar la aplicación. Y hay métodos para producir presentadores individuales. Y así sucesivamente. +No se preocupe, no es ninguna caja negra mágica, como podría parecer por las líneas anteriores. En realidad, es una clase PHP bastante aburrida, que Nette genera y guarda en el directorio de caché. Tiene muchos métodos llamados como `createServiceAbcd()` y cada uno de ellos sabe cómo fabricar y devolver algún objeto. Sí, también está el método `createServiceApplication()`, que fabrica `Nette\Application\Application`, que necesitábamos en el archivo `index.php` para iniciar la aplicación. Y hay métodos que fabrican los presenters individuales. Y así sucesivamente. -Los objetos que crea el contenedor DI se llaman servicios por alguna razón. +A los objetos que crea el contenedor DI, por alguna razón, se les llama servicios. -Lo realmente especial de esta clase es que no es programada por ti, sino por el framework. En realidad genera el código PHP y lo guarda en el disco. Tú sólo das instrucciones sobre qué objetos debe ser capaz de producir el contenedor y cómo exactamente. Y estas instrucciones están escritas en [archivos de configuración |bootstrap#DI Container Configuration] en el [formato NEON |neon:format] y por lo tanto tienen la extensión `.neon`. +Lo que es realmente especial de esta clase es que no la programa usted, sino el framework. Él realmente genera el código PHP y lo guarda en el disco. Usted solo da instrucciones sobre qué objetos debe saber fabricar el contenedor y cómo exactamente. Y estas instrucciones están escritas en [archivos de configuración |bootstrapping#Configuración del contenedor DI], para los cuales se utiliza el formato [NEON|neon:format] y, por lo tanto, también tienen la extensión `.neon`. -Los archivos de configuración se utilizan únicamente para dar instrucciones al contenedor DI. Así, por ejemplo, si especifico la opción `expiration: 14 days` en la sección de [sesión |http:configuration#Session], el contenedor DI al crear el objeto `Nette\Http\Session` que representa la sesión llamará a su método `setExpiration('14 days')`, y así la configuración se hace realidad. +Los archivos de configuración sirven puramente para instruir al contenedor DI. Así que, por ejemplo, si indico en la sección [session |http:configuration#Sesión] la opción `expiration: 14 days`, el contenedor DI al crear el objeto `Nette\Http\Session` que representa la sesión, llamará a su método `setExpiration('14 days')` y así la configuración se hará realidad. -Hay todo un capítulo preparado para ti, describiendo qué se puede [configurar |nette:configuring] y cómo [definir tus propios servicios |dependency-injection:services]. +Hay un capítulo completo preparado para usted que describe qué se puede [configurar |nette:configuring] y cómo [definir servicios propios |dependency-injection:services]. -Una vez que te adentres en la creación de servicios, te encontrarás con la palabra [autocableado |dependency-injection:autowiring]. Este es un gadget que te hará la vida increíblemente más fácil. Puede pasar objetos automáticamente donde los necesites (en los constructores de tus clases, por ejemplo) sin que tengas que hacer nada. Usted encontrará que el contenedor DI en Nette es un pequeño milagro. +Una vez que se adentre un poco en la creación de servicios, se encontrará con la palabra [autowiring |dependency-injection:autowiring]. Esta es una característica que le simplificará la vida de manera increíble. Sabe cómo pasar automáticamente objetos donde los necesita (por ejemplo, en los constructores de sus clases), sin que tenga que hacer nada. Descubrirá que el contenedor DI en Nette es un pequeño milagro. -¿Y ahora qué? .[#toc-what-next] -=============================== +¿A dónde ir ahora? +================== -Hemos repasado los principios básicos de las aplicaciones en Nette. Hasta aquí, muy superficialmente, pero pronto ahondarás en las profundidades y acabarás creando maravillosas aplicaciones web. ¿Dónde continuar? ¿Has probado el tutorial [Crea tu primera |quickstart:] aplicación? +Hemos repasado los principios básicos de las aplicaciones en Nette. Hasta ahora muy superficialmente, pero pronto profundizará y con el tiempo creará maravillosas aplicaciones web. ¿A dónde continuar ahora? ¿Ya ha probado el tutorial [Escribiendo la primera aplicación|quickstart:]? -Además de lo anterior, Nette tiene todo un arsenal de [clases útiles |utils:], [capa de base de datos |database:], etc. Prueba a hacer clic a propósito en la documentación. O visita [el blog |https://blog.nette.org]. Descubrirás un montón de cosas interesantes. +Además de lo descrito anteriormente, Nette dispone de todo un arsenal de [clases útiles|utils:], [capa de base de datos|database:], etc. Intente simplemente hacer clic en la documentación. O en el [blog|https://blog.nette.org]. Descubrirá muchas cosas interesantes. -Deje que el marco de traer un montón de alegría 💙 +Que el framework le traiga mucha alegría 💙 diff --git a/application/es/modules.texy b/application/es/modules.texy deleted file mode 100644 index e183346e5f..0000000000 --- a/application/es/modules.texy +++ /dev/null @@ -1,148 +0,0 @@ -Módulos -******* - -.[perex] -En Nette, los módulos representan las unidades lógicas que componen una aplicación. Incluyen presentadores, plantillas, posiblemente también componentes y clases modelo. - -Un directorio para los presentadores y otro para las plantillas no serían suficientes para los proyectos reales. Tener docenas de archivos en una carpeta es, como mínimo, desorganizado. ¿Cómo salir de ello? Simplemente los dividimos en subdirectorios en el disco y en espacios de nombres en el código. Y eso es exactamente lo que hacen los módulos de Nette. - -Así que olvidémonos de una única carpeta para presentadores y plantillas y en su lugar creemos módulos, por ejemplo `Admin` y `Front`. - -/--pre -app/ -├── Presenters/ -├── Modules/ ← directorio con módulos -│ ├── Admin/ ← módulo Admin -│ │ ├── Presenters/ ← sus presentadores -│ │ │ ├── DashboardPresenter.php -│ │ │ └── templates/ -│ └── Front/ ← módulo Front -│ └── Presenters/ ← sus presentadores -│ └── ... -\-- - -Esta estructura de directorios se reflejará en los espacios de nombres de las clases, así por ejemplo `DashboardPresenter` estará en el espacio de nombres `App\Modules\Admin\Presenters`: - -```php -namespace App\Modules\Admin\Presenters; - -class DashboardPresenter extends Nette\Application\UI\Presenter -{ - // ... -} -``` - -El presentador `Dashboard` dentro del módulo `Admin` es referenciado dentro de la aplicación usando la notación de dos puntos como `Admin:Dashboard`, y su acción `default` como `Admin:Dashboard:default`. -¿Y cómo sabe Nette que `Admin:Dashboard` representa la clase `App\Modules\Admin\Presenters\DashboardPresenter`? Esto se determina mediante el [mapeo |#mapping] en la configuración. -Por lo tanto, la estructura dada no es rígida y puede modificarla según sus necesidades. - -Por supuesto, los módulos pueden contener todos los demás elementos además de presentadores y plantillas, como componentes, clases modelo, etc. - - -Módulos anidados .[#toc-nested-modules] ---------------------------------------- - -Los módulos no tienen por qué formar sólo una estructura plana, también puedes crear submódulos, por ejemplo: - -/--pre -app/ -├── Modules/ ← directorio con módulos -│ ├── Blog/ ← módulo Blog -│ │ ├── Admin/ ← submódulo Admin -│ │ │ ├── Presenters/ -│ │ │ └── ... -│ │ └── Front/ ← submódulo Front -│ │ ├── Presenters/ -│ │ └── ... -│ ├── Forum/ ← módulo Forum -│ │ └── ... -\-- - -Así, el módulo `Blog` se divide en los submódulos `Admin` y `Front`. De nuevo, esto se reflejará en los espacios de nombres, que serán `App\Modules\Blog\Admin\Presenters`, etc. El presentador `Dashboard` dentro del submódulo se denomina `Blog:Admin:Dashboard`. - -El anidamiento puede ser tan profundo como se desee, por lo que pueden crearse submódulos. - - -Creación de enlaces .[#toc-creating-links] ------------------------------------------- - -Los enlaces de las plantillas de presentador son relativos al módulo actual. Así, el enlace `Foo:default` lleva al presentador `Foo` en el mismo módulo que el presentador actual. Si el módulo actual es `Front`, por ejemplo, el enlace será el siguiente: - -```latte -enlace a Front:Product:show -``` - -Un enlace es relativo aunque incluya el nombre de un módulo, que se considera entonces un submódulo: - -```latte -enlace a Front:Shop:Product:show -``` - -Los enlaces absolutos se escriben de forma análoga a las rutas absolutas en disco, pero con dos puntos en lugar de barras. Así, un enlace absoluto comienza con dos puntos: - -```latte -enlace a Admin:Product:show -``` - -Para saber si estamos en un módulo determinado o en su submódulo podemos utilizar la función `isModuleCurrent(moduleName)`. - -```latte -
  • - ... -
  • -``` - - -Enrutamiento .[#toc-routing] ----------------------------- - -Véase el [capítulo sobre en rutamiento|routing#Modules]. - - -Mapeo .[#toc-mapping] ---------------------- - -Define las reglas por las que el nombre de la clase se deriva del nombre del presentador. Las escribimos en [configuración |configuration] bajo la clave `application › mapping`. - -Empecemos con un ejemplo que no utiliza módulos. Sólo querremos que las clases del presentador tengan el espacio de nombres `App\Presenters`. Eso significa que un presentador como `Home` debe mapearse a la clase `App\Presenters\HomePresenter`. Esto se puede lograr con la siguiente configuración: - -```neon -application: - mapping: - *: App\Presenters\*Presenter -``` - -El nombre del presentador se sustituye por el asterisco en la máscara de clase y el resultado es el nombre de la clase. Muy fácil. - -Si dividimos a los presentadores en módulos, podemos tener nuestra propia asignación para cada módulo: - -```neon -application: - mapping: - Front: App\Modules\Front\Presenters\*Presenter - Admin: App\Modules\Admin\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -Ahora el presentador `Front:Home` se asigna a la clase `App\Modules\Front\Presenters\HomePresenter` y el presentador `Admin:Dashboard` a la clase `App\Modules\Admin\Presenters\DashboardPresenter`. - -Es más práctico crear una regla general (estrella) para sustituir a las dos primeras. El asterisco adicional se añadirá a la máscara de clase sólo para el módulo: - -```neon -application: - mapping: - *: App\Modules\*\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -Pero, ¿y si utilizamos módulos anidados y tenemos un presentador `Admin:User:Edit`? En este caso, el segmento con un asterisco que representa el módulo para cada nivel simplemente se repite y el resultado es la clase `App\Modules\Admin\User\Presenters\EditPresenter`. - -Una notación alternativa es utilizar una matriz formada por tres segmentos en lugar de una cadena. Esta notación es equivalente a la anterior: - -```neon -application: - mapping: - *: [App\Modules, *, Presenters\*Presenter] -``` - -El valor por defecto es `*: *Module\*Presenter`. diff --git a/application/es/multiplier.texy b/application/es/multiplier.texy index dc05cae1d1..7dc7ac9411 100644 --- a/application/es/multiplier.texy +++ b/application/es/multiplier.texy @@ -1,30 +1,32 @@ -Multiplicador: Componentes dinámicos -************************************ +Multiplier: componentes dinámicos +********************************* -Una herramienta para la creación dinámica de componentes interactivos .[perex] +.[perex] +Herramienta para la creación dinámica de componentes interactivos -Empecemos con un problema típico: tenemos un listado de productos en un sitio de comercio electrónico y queremos acompañar cada producto con un formulario *añadir al carrito*. Una forma es envolver todo el listado en un único formulario. Una forma más conveniente es utilizar [api:Nette\Application\UI\Multiplier]. +Partamos de un ejemplo típico: tenemos una lista de productos en una tienda online, y para cada uno queremos mostrar un formulario para añadir el producto al carrito. Una de las posibles variantes es envolver todo el listado en un único formulario. Sin embargo, un método mucho más cómodo nos lo ofrece [api:Nette\Application\UI\Multiplier]. -Multiplier permite definir una fábrica para múltiples componentes. Se basa en el principio de componentes anidados - cada componente que hereda de [api:Nette\ComponentModel\Container] puede contener otros componentes. +Multiplier permite definir cómodamente una pequeña fábrica para múltiples componentes. Funciona según el principio de componentes anidados: cada componente que hereda de [api:Nette\ComponentModel\Container] puede contener otros componentes. -Véase el [modelo de componentes |components#Components in Depth] en la documentación. .[tip] +.[tip] +Vea el capítulo sobre el [modelo de componentes |components#Componentes en profundidad] en la documentación o la [charla de Honza Tvrdík|https://www.youtube.com/watch?v=8y3LLexWu-I]. -Multiplier se presenta como un componente padre que puede crear dinámicamente sus hijos utilizando la llamada de retorno pasada en el constructor. Véase el ejemplo: +La esencia de Multiplier es que actúa como un padre que puede crear dinámicamente sus hijos mediante un callback pasado en el constructor. Vea el ejemplo: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function () { $form = new Nette\Application\UI\Form; - $form->addInteger('amount', 'Amount:') + $form->addInteger('count', 'Cantidad de productos:') ->setRequired(); - $form->addSubmit('send', 'Add to cart'); + $form->addSubmit('send', 'Añadir al carrito'); return $form; }); } ``` -En la plantilla podemos renderizar un formulario para cada producto - y cada formulario será de hecho un componente único. +Ahora podemos simplemente, en la plantilla, renderizar un formulario para cada producto, y cada uno será realmente un componente único. ```latte {foreach $items as $item} @@ -35,26 +37,26 @@ En la plantilla podemos renderizar un formulario para cada producto - y cada for {/foreach} ``` -Argumento pasado a `{control}` etiqueta dice: +El argumento pasado en la etiqueta `{control}` tiene un formato que indica: -1. obtener un componente `shopForm` -2. y devuelve su hijo `$item->id` +1. obtén el componente `shopForm` +2. y de él obtén el hijo `$item->id` -Durante la primera llamada de **1.** el componente `shopForm` aún no existe, por lo que se llama al método `createComponentShopForm` para crearlo. A continuación, se llama a una función anónima que se pasa como parámetro a Multiplier y se crea un formulario. +En la primera llamada al punto **1.**, `shopForm` aún no existe, por lo que se llama a su fábrica `createComponentShopForm`. Sobre el componente obtenido (instancia de Multiplier) se llama entonces a la fábrica del formulario específico, que es la función anónima que pasamos a Multiplier en el constructor. -En las siguientes iteraciones de `foreach` ya no se llama al método `createComponentShopForm` porque el componente ya existe. Pero como hacemos referencia a otro hijo (`$item->id` varía entre iteraciones), se vuelve a llamar a una función anónima y se crea un nuevo formulario. +En la siguiente iteración del `foreach`, el método `createComponentShopForm` ya no será llamado (el componente existe), pero como buscamos a su otro hijo (`$item->id` será diferente en cada iteración), se volverá a llamar a la función anónima y nos devolverá un nuevo formulario. -Lo último es asegurarnos de que el formulario realmente añade el producto correcto al carrito porque en el estado actual todos los formularios son iguales y no podemos distinguir a qué productos pertenecen. Para ello podemos utilizar la propiedad de Multiplier (y en general de cualquier método de fábrica de componentes en Nette Framework) de que todo método de fábrica de componentes recibe como primer argumento el nombre del componente creado. En nuestro caso sería `$item->id`, que es exactamente lo que necesitamos para distinguir los productos individuales. Todo lo que hay que hacer es modificar el código de creación del formulario: +Lo único que queda es asegurar que el formulario añada al carrito realmente el producto que debe; actualmente, el formulario es completamente idéntico para cada producto. Nos ayudará una propiedad de Multiplier (y en general de cada fábrica de componentes en Nette Framework), y es que cada fábrica recibe como primer argumento el nombre del componente que se está creando. En nuestro caso, será `$item->id`, que es exactamente el dato que necesitamos. Basta con modificar ligeramente la creación del formulario: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function ($itemId) { $form = new Nette\Application\UI\Form; - $form->addInteger('amount', 'Amount:') + $form->addInteger('count', 'Cantidad de productos:') ->setRequired(); $form->addHidden('itemId', $itemId); - $form->addSubmit('send', 'Add to cart'); + $form->addSubmit('send', 'Añadir al carrito'); return $form; }); } diff --git a/application/es/presenters.texy b/application/es/presenters.texy index cfc6e1b472..20185ba405 100644 --- a/application/es/presenters.texy +++ b/application/es/presenters.texy @@ -1,39 +1,39 @@ -Presentadores -************* +Presenters +**********
    -Aprenderemos a escribir presentadores y plantillas en Nette. Después de leer sabrás: +Nos familiarizaremos con cómo se escriben los presenters y las plantillas en Nette. Después de leerlo, sabrá: -- cómo funciona el presentador +- cómo funciona un presenter - qué son los parámetros persistentes -- cómo renderizar una plantilla +- cómo se dibujan las plantillas
    -[Ya sabemos |how-it-works#nette-application] que un presentador es una clase que representa una página específica de una aplicación web, como una página de inicio; un producto en una tienda electrónica; un formulario de registro; un feed de mapa del sitio, etc. La aplicación puede tener de uno a miles de presentadores. En otros frameworks, también se conocen como controladores. +[Ya sabemos |how-it-works#Nette Application] que un presenter es una clase que representa alguna página específica de una aplicación web, p. ej., la página de inicio; un producto en una tienda electrónica; un formulario de inicio de sesión; un feed sitemap, etc. Una aplicación puede tener desde uno hasta miles de presenters. En otros frameworks también se les llama controllers. -Normalmente, el término presentador se refiere a un descendiente de la clase [api:Nette\Application\UI\Presenter], que es adecuada para interfaces web y de la que hablaremos en el resto de este capítulo. En un sentido general, un presentador es cualquier objeto que implemente la interfaz [api:Nette\Application\IPresenter]. +Generalmente, bajo el término presenter se entiende un descendiente de la clase [api:Nette\Application\UI\Presenter], que es adecuado para generar interfaces web y al que nos dedicaremos en el resto de este capítulo. En sentido general, un presenter es cualquier objeto que implementa la interfaz [api:Nette\Application\IPresenter]. -Ciclo de vida del presentador .[#toc-life-cycle-of-presenter] -============================================================= +Ciclo de vida del presenter +=========================== -El trabajo del presentador es procesar la solicitud y devolver una respuesta (que puede ser una página HTML, una imagen, una redirección, etc.). +La tarea del presenter es gestionar una petición y devolver una respuesta (que puede ser una página HTML, una imagen, una redirección, etc.). -Así que al principio hay una petición. No es directamente una petición HTTP, sino un objeto [api:Nette\Application\Request] en el que se ha transformado la petición HTTP mediante un enrutador. Normalmente no entramos en contacto con este objeto, porque el presentador delega inteligentemente el procesamiento de la petición a métodos especiales, que ahora veremos. +Por lo tanto, al principio se le pasa una petición. No es directamente una petición HTTP, sino un objeto [api:Nette\Application\Request], al que se transformó la petición HTTP con la ayuda del router. Generalmente no interactuamos con este objeto, ya que el presenter delega inteligentemente el procesamiento de la petición a otros métodos, que ahora mostraremos. -[* lifecycle.svg *] *** *Ciclo de vida del presentador* .<> +[* lifecycle.svg *] *** *Ciclo de vida del presenter* .<> -La figura muestra una lista de métodos que son llamados secuencialmente de arriba a abajo, si es que existen. No es necesario que exista ninguno de ellos, podemos tener un presentador completamente vacío sin un solo método y construir una simple web estática sobre él. +La imagen representa una lista de métodos que se llaman sucesivamente de arriba abajo, si existen. Ninguno de ellos tiene por qué existir, podemos tener un presenter completamente vacío sin un solo método y construir sobre él un sitio web estático simple. `__construct()` --------------- -El constructor no pertenece exactamente al ciclo de vida del presentador, porque se llama en el momento de crear el objeto. Pero lo mencionamos por su importancia. El constructor (junto con [el método inject |best-practices:inject-method-attribute]) se utiliza para pasar dependencias. +El constructor no pertenece exactamente al ciclo de vida del presenter, porque se llama en el momento de la creación del objeto. Pero lo mencionamos por su importancia. El constructor (junto con el [método inject|best-practices:inject-method-attribute]) sirve para pasar dependencias. -El presentador no debe encargarse de la lógica de negocio de la aplicación, escribir y leer de la base de datos, realizar cálculos, etc. Esta es la tarea para las clases de una capa, que llamamos modelo. Por ejemplo, la clase `ArticleRepository` puede encargarse de cargar y guardar artículos. Para que el presentador pueda utilizarla, se [le pasa mediante inyección de dependencia |dependency-injection:passing-dependencies]: +El presenter no debería encargarse de la lógica de negocio de la aplicación, escribir y leer de la base de datos, realizar cálculos, etc. Para eso están las clases de la capa que llamamos modelo. Por ejemplo, la clase `ArticleRepository` puede encargarse de cargar y guardar artículos. Para que el presenter pueda trabajar con ella, se la deja [pasar mediante inyección de dependencias |dependency-injection:passing-dependencies]: ```php @@ -50,39 +50,39 @@ class ArticlePresenter extends Nette\Application\UI\Presenter `startup()` ----------- -Inmediatamente después de recibir la petición, se invoca el método `startup ()`. Se puede utilizar para inicializar propiedades, comprobar privilegios de usuario, etc. Es necesario llamar siempre al ancestro `parent::startup()`. +Inmediatamente después de recibir la petición, se llama al método `startup()`. Puede usarlo para inicializar propiedades, verificar permisos de usuario, etc. Se requiere que el método siempre llame al ancestro `parent::startup()`. `action(args...)` .{toc: action()} -------------------------------------------------- -Similar al método `render()`. Mientras que `render()` está destinado a preparar datos para una plantilla específica, que posteriormente se renderiza, en `action()` se procesa una solicitud sin renderizar posteriormente la plantilla. Por ejemplo, los datos se procesan, un usuario se conecta o desconecta, y así sucesivamente, y luego se [redirige a otro lugar |#Redirection]. +Análogo al método `render()`. Mientras que `render()` está destinado a preparar datos para una plantilla específica que luego se renderizará, en `action()` se procesa la petición sin conexión con la renderización de la plantilla. Por ejemplo, se procesan datos, se inicia o cierra sesión de usuario, y así sucesivamente, y luego [se redirige a otro lugar |#Redirección]. -Es importante que `action()` se llame antes que `render()`para que dentro de él podamos posiblemente cambiar el siguiente curso del ciclo de vida, es decir, cambiar la plantilla que será renderizada y también el método `render()` que será llamado, usando `setView('otherView')`. +Es importante que `action()` se llame antes que `render()`, por lo que en él podemos cambiar el curso posterior de los acontecimientos, es decir, cambiar la plantilla que se dibujará, y también el método `render()` que se llamará. Y esto usando `setView('otraVista')`. -Los parámetros de la petición se pasan al método. Es posible y recomendable especificar tipos para los parámetros, por ejemplo `actionShow(int $id, string $slug = null)` - si el parámetro `id` falta o si no es un entero, el presentador devuelve [el error 404 |#Error 404 etc.] y termina la operación. +Al método se le pasan parámetros de la petición. Es posible y recomendable indicar tipos para los parámetros, p. ej., `actionShow(int $id, ?string $slug = null)` - si falta el parámetro `id` o si no es un entero, el presenter devolverá un [error 404 |#Error 404 y cía] y finalizará la actividad. `handle(args...)` .{toc: handle()} -------------------------------------------------- -Este método procesa las llamadas señales, de las que hablaremos en el capítulo sobre [Componentes |components#Signal]. Está pensado principalmente para componentes y procesamiento de peticiones AJAX. +El método procesa las llamadas señales, con las que nos familiarizaremos en el capítulo dedicado a los [componentes |components#Señal]. De hecho, está destinado principalmente a componentes y al procesamiento de peticiones AJAX. -Los parámetros se pasan al método, como en el caso de `action()`incluyendo la comprobación de tipos. +Al método se le pasan parámetros de la petición, como en el caso de `action()`, incluida la verificación de tipos. `beforeRender()` ---------------- -El método `beforeRender`, como su nombre indica, se llama antes de cada método `render()`. Se utiliza para la configuración de plantillas comunes, pasando variables para el diseño y así sucesivamente. +El método `beforeRender`, como su nombre indica, se llama antes de cada método `render()`. Se utiliza para la configuración común de la plantilla, pasar variables para el layout y similares. `render(args...)` .{toc: render()} ---------------------------------------------- -El lugar donde preparamos la plantilla para su posterior renderizado, le pasamos datos, etc. +El lugar donde preparamos la plantilla para su posterior renderización, le pasamos datos, etc. -Los parámetros se pasan al método, como en el caso de `action()`incluyendo la comprobación de tipos. +Al método se le pasan parámetros de la petición, como en el caso de `action()`, incluida la verificación de tipos. ```php public function renderShow(int $id): void @@ -96,104 +96,104 @@ public function renderShow(int $id): void `afterRender()` --------------- -Método `afterRender`, como su nombre indica de nuevo, se llama después de cada `render()` método. Se utiliza más bien poco. +El método `afterRender`, como su nombre indica de nuevo, se llama después de cada método `render()`. Se usa más bien excepcionalmente. `shutdown()` ------------ -Se llama al final del ciclo de vida del presentador. +Se llama al final del ciclo de vida del presenter. -**Un buen consejo antes de continuar**. Como puedes ver, el presentador puede manejar más acciones/vistas, es decir, tener más métodos `render()`. Pero recomendamos diseñar presentadores con una o tan pocas acciones como sea posible. +**Un buen consejo antes de continuar**. Como puede ver, un presenter puede manejar múltiples acciones/vistas, es decir, tener múltiples métodos `render()`. Pero recomendamos diseñar presenters con una o la menor cantidad posible de acciones. -Envío de una respuesta .[#toc-sending-a-response] -================================================= +Envío de la respuesta +===================== -La respuesta del presentador suele ser [renderizar la plantilla con la página HTML |templates], pero también puede ser enviar un archivo, JSON o incluso redirigir a otra página. +La respuesta del presenter suele ser la [renderización de una plantilla con una página HTML|templates], pero también puede ser el envío de un archivo, JSON o incluso una redirección a otra página. -En cualquier momento durante el ciclo de vida, puede utilizar cualquiera de los siguientes métodos para enviar una respuesta y salir del presentador al mismo tiempo: +En cualquier momento durante el ciclo de vida, podemos enviar una respuesta con uno de los siguientes métodos y, al mismo tiempo, finalizar el presenter: -- `redirect()`, `redirectPermanent()`, `redirectUrl()` y `forward()` [redirecciona |#Redirection] -- `error()` sale del presentador [debido a un error |#Error 404 etc.] -- `sendJson($data)` sale del presentador y [envía los datos |#Sending JSON] en formato JSON -- `sendTemplate()` abandona el presentador y [renderiza |templates] inmediatamente [la plantilla |templates] -- `sendResponse($response)` abandona el presentador y envía [su propia respuesta |#Responses] -- `terminate()` abandona el presentador sin respuesta +- `redirect()`, `redirectPermanent()`, `redirectUrl()` y `forward()` [redirigen |#Redirección] +- `error()` finaliza el presenter [debido a un error |#Error 404 y cía] +- `sendJson($data)` finaliza el presenter y [envía datos |#Envío de JSON] en formato JSON +- `sendTemplate()` finaliza el presenter e inmediatamente [renderiza la plantilla |templates] +- `sendResponse($response)` finaliza el presenter y envía una [respuesta propia |#Respuestas] +- `terminate()` finaliza el presenter sin respuesta -Si no llama a ninguno de estos métodos, el presentador procederá automáticamente a renderizar la plantilla. ¿Por qué? Pues porque en el 99% de los casos queremos dibujar una plantilla, así que el presentador toma este comportamiento por defecto y quiere facilitarnos el trabajo. +Si no llama a ninguno de estos métodos, el presenter procederá automáticamente a renderizar la plantilla. ¿Por qué? Porque en el 99% de los casos queremos renderizar una plantilla, por lo que el presenter toma este comportamiento como predeterminado y quiere facilitarnos el trabajo. -Creación de enlaces .[#toc-creating-links] -========================================== +Creación de enlaces +=================== -Presenter tiene un método `link()`, que se utiliza para crear enlaces URL a otros presentadores. El primer parámetro es el presentador y la acción de destino, seguido de los argumentos, que pueden pasarse como matriz: +El presenter dispone del método `link()`, mediante el cual se pueden crear enlaces URL a otros presenters. El primer parámetro es el presenter y la acción de destino, seguido de los argumentos pasados, que pueden indicarse como un array: ```php $url = $this->link('Product:show', $id); -$url = $this->link('Product:show', [$id, 'lang' => 'en']); +$url = $this->link('Product:show', [$id, 'lang' => 'cs']); ``` -En la plantilla creamos enlaces a otros presentadores y acciones de la siguiente manera: +En la plantilla, se crean enlaces a otros presenters y acciones de esta manera: ```latte -product detail +detalle del producto ``` -Basta con escribir el conocido par `Presenter:action` en lugar de la URL real e incluir cualquier parámetro. El truco es `n:href`, que dice que este atributo será procesado por Latte y genera una URL real. En Nette, no tienes que pensar en URLs en absoluto, sólo en presentadores y acciones. +Simplemente, en lugar de la URL real, escribe el par conocido `Presenter:action` e indica los parámetros necesarios. El truco está en `n:href`, que indica que este atributo será procesado por Latte y generará la URL real. En Nette, por lo tanto, no necesita pensar en absoluto en las URL, solo en los presenters y las acciones. -Para más información, vea [Creando Enlaces |Creating Links]. +Encontrará más información en el capítulo [Creación de enlaces URL|creating-links]. -Redirección .[#toc-redirection] -=============================== +Redirección +=========== -Para saltar a otro presentador se utilizan los métodos `redirect()` y `forward()`, que tienen una sintaxis muy similar a la del método [link() |#Creating Links]. +Para pasar a otro presenter se utilizan los métodos `redirect()` y `forward()`, que tienen una sintaxis muy similar al método [link() |#Creación de enlaces]. -El `forward()` cambia al nuevo presentador inmediatamente sin redirección HTTP: +El método `forward()` pasa al nuevo presenter inmediatamente sin redirección HTTP: ```php $this->forward('Product:show'); ``` -Ejemplo de redirección temporal con código HTTP 302 o 303: +Ejemplo de la llamada redirección temporal con código HTTP 302 (o 303, si el método de la petición actual es POST): ```php $this->redirect('Product:show', $id); ``` -Para conseguir una redirección permanente con código HTTP 301 utilice: +La redirección permanente con código HTTP 301 se logra así: ```php $this->redirectPermanent('Product:show', $id); ``` -Puede redirigir a otra URL fuera de la aplicación con el método `redirectUrl()`: +A otra URL fuera de la aplicación se puede redirigir con el método `redirectUrl()`. Como segundo parámetro se puede indicar el código HTTP, el predeterminado es 302 (o 303, si el método de la petición actual es POST): ```php $this->redirectUrl('https://nette.org'); ``` -La redirección termina inmediatamente el ciclo de vida del presentador lanzando la llamada excepción de terminación silenciosa `Nette\Application\AbortException`. +La redirección finaliza inmediatamente la actividad del presenter lanzando la llamada excepción de finalización silenciosa `Nette\Application\AbortException`. -Antes de la redirección, es posible enviar un [mensaje flash |#Flash Messages], mensajes que se mostrarán en la plantilla después de la redirección. +Antes de la redirección se puede enviar un [mensaje flash |#Mensajes flash], es decir, mensajes que se mostrarán en la plantilla después de la redirección. -Mensajes flash .[#toc-flash-messages] -===================================== +Mensajes flash +============== -Son mensajes que suelen informar sobre el resultado de una operación. Una característica importante de los mensajes flash es que están disponibles en la plantilla incluso después de la redirección. Incluso después de ser mostrados, permanecerán vivos durante otros 30 segundos - por ejemplo, en caso de que el usuario refrescara involuntariamente la página - el mensaje no se perderá. +Son mensajes que generalmente informan sobre el resultado de alguna operación. Una característica importante de los mensajes flash es que están disponibles en la plantilla incluso después de una redirección. Incluso después de mostrarse, permanecen activos durante otros 30 segundos, por ejemplo, en caso de que el usuario actualice la página debido a una transmisión errónea, el mensaje no desaparecerá de inmediato. -Basta con llamar al método [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] y el presentador se encargará de pasar el mensaje a la plantilla. El primer argumento es el texto del mensaje y el segundo argumento opcional es su tipo (error, advertencia, información, etc.). El método `flashMessage()` devuelve una instancia de flash message, para permitirnos añadir más información. +Basta con llamar al método [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] y el presenter se encargará de pasarlo a la plantilla. El primer parámetro es el texto del mensaje y el segundo parámetro opcional es su tipo (error, warning, info, etc.). El método `flashMessage()` devuelve una instancia del mensaje flash, a la que se le puede agregar información adicional. ```php -$this->flashMessage('Item was removed.'); -$this->redirect(/* ... */); +$this->flashMessage('El elemento ha sido eliminado.'); +$this->redirect(/* ... */); // y redirigimos ``` -En la plantilla, estos mensajes están disponibles en la variable `$flashes` como objetos `stdClass`, que contienen las propiedades `message` (texto del mensaje), `type` (tipo de mensaje) y pueden contener la ya mencionada información del usuario. Los dibujamos como sigue: +En la plantilla, estos mensajes están disponibles en la variable `$flashes` como objetos `stdClass`, que contienen las propiedades `message` (texto del mensaje), `type` (tipo de mensaje) y pueden contener la información de usuario ya mencionada. Los renderizamos, por ejemplo, así: ```latte {foreach $flashes as $flash} @@ -202,10 +202,10 @@ En la plantilla, estos mensajes están disponibles en la variable `$flashes` com ``` -Error 404 etc. .[#toc-error-404-etc] -==================================== +Error 404 y cía. +================ -Cuando no podamos satisfacer la petición porque por ejemplo el artículo que queremos mostrar no existe en la base de datos, lanzaremos el error 404 utilizando el método `error(string $message = null, int $httpCode = 404)`, que representa el error HTTP 404: +Si no se puede cumplir la petición, por ejemplo, porque el artículo que queremos mostrar no existe en la base de datos, lanzamos un error 404 con el método `error(?string $message = null, int $httpCode = 404)`. ```php public function renderShow(int $id): void @@ -218,14 +218,13 @@ public function renderShow(int $id): void } ``` -El código de error HTTP se puede pasar como segundo parámetro, por defecto es 404. El método funciona lanzando la excepción `Nette\Application\BadRequestException`, tras lo cual `Application` pasa el control al presentador del error. Que es un presentador cuyo trabajo es mostrar una página informando sobre el error. -El presentador de errores se establece en [la configuración de la aplicación |configuration]. +El código HTTP del error se puede pasar como segundo parámetro, el predeterminado es 404. El método funciona lanzando la excepción `Nette\Application\BadRequestException`, tras lo cual `Application` pasa el control al error-presenter. Que es un presenter cuya tarea es mostrar una página informando sobre el error ocurrido. La configuración del error-presenter se realiza en la [configuración de application|configuration]. -Envío de JSON .[#toc-sending-json] -================================== +Envío de JSON +============= -Ejemplo de action-method que envía datos en formato JSON y sale del presentador: +Ejemplo de un método de acción que envía datos en formato JSON y finaliza el presenter: ```php public function actionData(): void @@ -236,33 +235,59 @@ public function actionData(): void ``` -Parámetros persistentes .[#toc-persistent-parameters] -===================================================== +Parámetros de la petición .{data-version:3.1.14} +================================================ + +El presenter y también cada componente obtienen sus parámetros de la petición HTTP. Puede obtener su valor con el método `getParameter($name)` o `getParameters()`. Los valores son cadenas o arrays de cadenas, son básicamente datos brutos obtenidos directamente de la URL. + +Para mayor comodidad, recomendamos acceder a los parámetros a través de propiedades. Basta con marcarlas con el atributo `#[Parameter]`: + +```php +use Nette\Application\Attributes\Parameter; // esta línea es importante + +class HomePresenter extends Nette\Application\UI\Presenter +{ + #[Parameter] + public string $theme; // debe ser public +} +``` -Los parámetros persistentes se utilizan para mantener el estado entre diferentes peticiones. Su valor sigue siendo el mismo incluso después de hacer clic en un enlace. A diferencia de los datos de sesión, se pasan en la URL. Esto es completamente automático, por lo que no es necesario indicarlos explícitamente en `link()` o `n:href`. +Recomendamos indicar también el tipo de dato para la propiedad (p. ej., `string`) y Nette lo convertirá automáticamente según él. Los valores de los parámetros también se pueden [validar |#Validación de parámetros]. -¿Ejemplo de uso? Tiene una aplicación multilingüe. El idioma real es un parámetro que debe formar parte de la URL en todo momento. Pero sería increíblemente tedioso incluirlo en cada enlace. Así que lo conviertes en un parámetro persistente llamado `lang` y se guardará solo. Genial. +Al crear un enlace, se puede establecer directamente el valor de los parámetros: + +```latte +click +``` -Crear un parámetro persistente es extremadamente fácil en Nette. Basta con crear una propiedad pública y etiquetarla con el atributo: (antes se utilizaba `/** @persistent */` ) + +Parámetros persistentes +======================= + +Los parámetros persistentes sirven para mantener el estado entre diferentes peticiones. Su valor permanece igual incluso después de hacer clic en un enlace. A diferencia de los datos en la sesión, se transfieren en la URL. Y esto de forma totalmente automática, por lo que no es necesario indicarlos explícitamente en `link()` o `n:href`. + +¿Un ejemplo de uso? Tiene una aplicación multilingüe. El idioma actual es un parámetro que debe estar constantemente presente en la URL. Pero sería increíblemente tedioso indicarlo en cada enlace. Así que lo convierte en un parámetro persistente `lang` y se transferirá solo. ¡Genial! + +Crear un parámetro persistente es extremadamente simple en Nette. Basta con crear una propiedad pública y marcarla con un atributo: (anteriormente se usaba `/** @persistent */`) ```php -use Nette\Application\Attributes\Persistent; // esta línea es importante +use Nette\Application\Attributes\Persistent; // esta línea es importante class ProductPresenter extends Nette\Application\UI\Presenter { #[Persistent] - public string $lang; // debe ser público + public string $lang; // debe ser public } ``` -Si `$this->lang` tiene un valor como `'en'`, entonces los enlaces creados usando `link()` o `n:href` también contendrán el parámetro `lang=en`. Y cuando se haga clic en el enlace, volverá a ser `$this->lang = 'en'`. +Si `$this->lang` tiene el valor, por ejemplo, `'en'`, entonces también los enlaces creados mediante `link()` o `n:href` contendrán el parámetro `lang=en`. Y después de hacer clic en el enlace, nuevamente `$this->lang = 'en'`. -Para las propiedades, se recomienda incluir el tipo de datos (por ejemplo, `string`) y también se puede incluir un valor por defecto. Los valores de los parámetros se pueden [validar |#Validation of Persistent Parameters]. +Recomendamos indicar también el tipo de dato para la propiedad (p. ej., `string`) y puede indicar también un valor predeterminado. Los valores de los parámetros se pueden [validar |#Validación de parámetros]. -Los parámetros persistentes se pasan entre todas las acciones de un presentador determinado por defecto. Para pasarlos entre varios presentadores, es necesario definirlos: +Los parámetros persistentes se transfieren estándarmente entre todas las acciones del presenter dado. Para que se transfieran también entre varios presenters, es necesario definirlos ya sea: -- en un ancestro común del que hereden los presentadores -- en el rasgo que utilizan los presentadores: +- en un ancestro común del que heredan los presenters +- en un trait que usen los presenters: ```php trait LanguageAware @@ -277,48 +302,42 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -Puede cambiar el valor de un parámetro persistente al crear un enlace: +Al crear un enlace, se puede cambiar el valor del parámetro persistente: ```latte -detail in Czech +detalle en checo ``` -O puede ser *reset*, es decir, eliminado de la URL. Entonces tomará su valor por defecto: +O se puede *resetear*, es decir, eliminar de la URL. Entonces tomará su valor predeterminado: ```latte -click +haz clic ``` -Componentes interactivos .[#toc-interactive-components] -======================================================= +Componentes interactivos +======================== -Los presentadores incorporan un sistema de componentes. Los componentes son unidades separadas reutilizables que colocamos en los presentadores. Pueden ser [formularios |forms:in-presenter], cuadrículas de datos, menús, de hecho cualquier cosa que tenga sentido utilizar repetidamente. +Los presenters tienen incorporado un sistema de componentes. Los componentes son unidades reutilizables independientes que insertamos en los presenters. Pueden ser [formularios |forms:in-presenter], datagrids, menús, en realidad cualquier cosa que tenga sentido usar repetidamente. -¿Cómo se colocan y utilizan posteriormente los componentes en el presentador? Esto se explica en el capítulo [Componentes |Components]. Incluso descubrirá qué tienen que ver con Hollywood. +¿Cómo se insertan los componentes en el presenter y se usan posteriormente? Eso lo aprenderá en el capítulo [Componentes |components]. Incluso descubrirá qué tienen en común con Hollywood. -¿Dónde puedo conseguir algunos componentes? En la página [Componette |https://componette.org] puedes encontrar algunos componentes de código abierto y otros addons para Nette que están hechos y compartidos por la comunidad de Nette Framework. +¿Y dónde puedo obtener componentes? En la página [Componette |https://componette.org/search/component] encontrará componentes de código abierto y también muchos otros complementos para Nette, que voluntarios de la comunidad alrededor del framework han colocado aquí. -Profundizando .[#toc-going-deeper] -================================== +Vamos a profundizar +=================== .[tip] -Lo que hemos mostrado hasta ahora en este capítulo probablemente será suficiente. Las líneas siguientes están pensadas para quienes estén interesados en los presentadores en profundidad y quieran saberlo todo. - - -Requisitos y parámetros .[#toc-requirement-and-parameters] ----------------------------------------------------------- +Con lo que hemos mostrado hasta ahora en este capítulo, probablemente le sea suficiente. Las siguientes líneas están destinadas a aquellos que se interesan por los presenters en profundidad y quieren saber absolutamente todo. -La solicitud gestionada por el presentador es el objeto [api:Nette\Application\Request] y es devuelta por el método del presentador `getRequest()`. Incluye una matriz de parámetros y cada uno de ellos pertenece a alguno de los componentes o directamente al presentador (que en realidad también es un componente, aunque especial). Así pues, Nette redistribuye los parámetros y los pasa entre los distintos componentes (y el presentador) llamando al método `loadState(array $params)`. Los parámetros pueden obtenerse mediante el método `getParameters(): array`, individualmente mediante `getParameter($name)`. Los valores de los parámetros son cadenas o matrices de cadenas, son básicamente datos en bruto obtenidos directamente de una URL. +Validación de parámetros +------------------------ -Validación de parámetros persistentes .[#toc-validation-of-persistent-parameters] ---------------------------------------------------------------------------------- +Los valores de los [#parámetros de la petición] y los [#parámetros persistentes] recibidos de la URL se escriben en las propiedades mediante el método `loadState()`. Este también comprueba si el tipo de dato indicado en la propiedad coincide, de lo contrario responde con un error 404 y la página no se muestra. -Los valores de los parámetros [persistentes |#persistent parameters] recibidos de las URLs son escritos en propiedades por el método `loadState()`. También comprueba si el tipo de datos especificado en la propiedad coincide, de lo contrario responderá con un error 404 y no se mostrará la página. - -Nunca confíes ciegamente en los parámetros persistentes, ya que pueden ser fácilmente sobrescritos por el usuario en la URL. Por ejemplo, así es como comprobamos si `$this->lang` está entre los idiomas soportados. Una buena forma de hacerlo es sobrescribir el método `loadState()` mencionado anteriormente: +Nunca confíe ciegamente en los parámetros, ya que pueden ser fácilmente sobrescritos por el usuario en la URL. Así, por ejemplo, verificamos si el idioma `$this->lang` está entre los soportados. Una forma adecuada es sobrescribir el método mencionado `loadState()`: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -328,8 +347,8 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function loadState(array $params): void { - parent::loadState($params); // aquí se establece el $this->lang - // sigue la comprobación del valor del usuario: + parent::loadState($params); // aquí se establece $this->lang + // sigue la verificación propia del valor: if (!in_array($this->lang, ['en', 'cs'])) { $this->error(); } @@ -338,43 +357,45 @@ class ProductPresenter extends Nette\Application\UI\Presenter ``` -Guardar y restaurar la petición .[#toc-save-and-restore-the-request] --------------------------------------------------------------------- +Guardado y restauración de la petición +-------------------------------------- + +La petición que gestiona el presenter es un objeto [api:Nette\Application\Request] y lo devuelve el método del presenter `getRequest()`. -Puede guardar la solicitud actual en una sesión o restaurarla desde la sesión y dejar que el presentador la ejecute de nuevo. Esto es útil, por ejemplo, cuando un usuario rellena un formulario y su login expira. Para no perder datos, antes de redirigir a la página de inicio de sesión, guardamos la solicitud actual en la sesión utilizando `$reqId = $this->storeRequest()`, que devuelve un identificador en forma de cadena corta y lo pasa como parámetro al presentador de inicio de sesión. +La petición actual se puede guardar en la sesión o, por el contrario, restaurarla desde ella y hacer que el presenter la ejecute de nuevo. Esto es útil, por ejemplo, en una situación en la que el usuario está rellenando un formulario y su sesión expira. Para no perder los datos, antes de redirigir a la página de inicio de sesión, guardamos la petición actual en la sesión usando `$reqId = $this->storeRequest()`, que devuelve su identificador en forma de cadena corta y lo pasamos como parámetro al presenter de inicio de sesión. -Después de iniciar sesión, llamamos al método `$this->restoreRequest($reqId)`, que recoge la solicitud de la sesión y se la reenvía. El método verifica que la petición ha sido creada por el mismo usuario que ahora ha iniciado la sesión. Si otro usuario inicia sesión o la clave no es válida, no hace nada y el programa continúa. +Después de iniciar sesión, llamamos al método `$this->restoreRequest($reqId)`, que recupera la petición de la sesión y hace forward a ella. El método, al mismo tiempo, verifica que la petición la creó el mismo usuario que ahora ha iniciado sesión. Si iniciara sesión otro usuario o la clave fuera inválida, no hace nada y el programa continúa. -Consulte el libro de recetas [Cómo volver a una página anterior |best-practices:restore-request]. +Consulte el tutorial [Cómo volver a una página anterior |best-practices:restore-request]. -Canonización .[#toc-canonization] ---------------------------------- +Canonización +------------ -Los presentadores tienen una función realmente fantástica que mejora el SEO (optimización de la capacidad de búsqueda en Internet). Evitan automáticamente la existencia de contenido duplicado en distintas URL. Si varias URL llevan a un determinado destino, por ejemplo `/index` y `/index?page=1`, el framework designa una de ellas como la principal (canónica) y redirige a las demás hacia ella utilizando el código HTTP 301. Gracias a ello, los motores de búsqueda no indexan las páginas dos veces y no debilitan su page rank. +Los presenters tienen una característica realmente genial que contribuye a un mejor SEO (optimización para motores de búsqueda). Evitan automáticamente la existencia de contenido duplicado en diferentes URL. Si a un destino determinado conducen varias direcciones URL, p. ej., `/index` y `/index?page=1`, el framework determina una de ellas como primaria (canónica) y redirige las demás a ella usando el código HTTP 301. Gracias a esto, los motores de búsqueda no indexan sus páginas dos veces y no diluyen su page rank. -Este proceso se denomina canonización. La URL canónica es la URL generada por [el enrutador |routing], normalmente la primera ruta apropiada de la colección. +Este proceso se llama canonización. La URL canónica es la que genera el [router|routing], generalmente, por lo tanto, la primera ruta correspondiente en la colección. -La canonización está activada por defecto y puede desactivarse a través de `$this->autoCanonicalize = false`. +La canonización está activada por defecto y se puede desactivar mediante `$this->autoCanonicalize = false`. -La redirección no se produce con una solicitud AJAX o POST porque provocaría una pérdida de datos o no aportaría ningún valor añadido SEO. +La redirección no ocurre en una petición AJAX o POST, porque se perderían datos o no tendría valor añadido desde el punto de vista del SEO. -También puede invocar la canonización manualmente mediante el método `canonicalize()`, que, al igual que el método `link()`, recibe el presentador, las acciones y los parámetros como argumentos. Crea un enlace y lo compara con la URL actual. Si es diferente, redirige al enlace generado. +También puede invocar la canonización manualmente usando el método `canonicalize()`, al que, de manera similar al método `link()`, se le pasa el presenter, la acción y los parámetros. Crea un enlace y lo compara con la dirección URL actual. Si difieren, redirige al enlace generado. ```php -public function actionShow(int $id, string $slug = null): void +public function actionShow(int $id, ?string $slug = null): void { $realSlug = $this->facade->getSlugForId($id); - // redirects if $slug is different from $realSlug + // redirige si $slug difiere de $realSlug $this->canonicalize('Product:show', [$id, $realSlug]); } ``` -Eventos .[#toc-events] ----------------------- +Eventos +------- -Además de los métodos `startup()`, `beforeRender()` y `shutdown()`, que se llaman como parte del ciclo de vida del presentador, se pueden definir otras funciones para que se llamen automáticamente. El presentador define los llamados [eventos |nette:glossary#events], y usted añade sus manejadores a las matrices `$onStartup`, `$onRender` y `$onShutdown`. +Además de los métodos `startup()`, `beforeRender()` y `shutdown()`, que se llaman como parte del ciclo de vida del presenter, se pueden definir otras funciones que deben llamarse automáticamente. El presenter define los llamados [eventos |nette:glossary#Eventos], cuyos manejadores agrega a los arrays `$onStartup`, `$onRender` y `$onShutdown`. ```php class ArticlePresenter extends Nette\Application\UI\Presenter @@ -388,34 +409,34 @@ class ArticlePresenter extends Nette\Application\UI\Presenter } ``` -Los manejadores de la matriz `$onStartup` se llaman justo antes del método `startup()`, luego `$onRender` entre `beforeRender()` y `render()` y finalmente `$onShutdown` justo antes de `shutdown()`. +Los manejadores en el array `$onStartup` se llaman justo antes del método `startup()`, luego `$onRender` entre `beforeRender()` y `render()` y finalmente `$onShutdown` justo antes de `shutdown()`. -Respuestas .[#toc-responses] ----------------------------- +Respuestas +---------- -La respuesta devuelta por el presentador es un objeto que implementa la interfaz [api:Nette\Application\Response]. Existen varias respuestas ya preparadas: +La respuesta que devuelve el presenter es un objeto que implementa la interfaz [api:Nette\Application\Response]. Hay disponibles varias respuestas preparadas: -- [api:Nette\Application\Responses\CallbackResponse] - envía una devolución de llamada -- [api:Nette\Application\Responses\FileResponse] - envía el archivo -- [api:Nette\Application\Responses\ForwardResponse] - envía () +- [api:Nette\Application\Responses\CallbackResponse] - envía un callback +- [api:Nette\Application\Responses\FileResponse] - envía un archivo +- [api:Nette\Application\Responses\ForwardResponse] - forward() - [api:Nette\Application\Responses\JsonResponse] - envía JSON -- [api:Nette\Application\Responses\RedirectResponse] - redirige +- [api:Nette\Application\Responses\RedirectResponse] - redirección - [api:Nette\Application\Responses\TextResponse] - envía texto -- [api:Nette\Application\Responses\VoidResponse] - respuesta en blanco +- [api:Nette\Application\Responses\VoidResponse] - respuesta vacía -Las respuestas se envían por el método `sendResponse()`: +Las respuestas se envían con el método `sendResponse()`: ```php use Nette\Application\Responses; -// Texto sin formato +// Texto plano $this->sendResponse(new Responses\TextResponse('Hello Nette!')); -// Envío de un archivo +// Envía un archivo $this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf')); -// Envío de una devolución de llamada +// La respuesta será un callback $callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse) { if ($httpResponse->getHeader('Content-Type') === 'text/html') { echo '

    Hello

    '; @@ -425,10 +446,55 @@ $this->sendResponse(new Responses\CallbackResponse($callback)); ``` -Lecturas complementarias .[#toc-further-reading] -================================================ +Restricción de acceso mediante `#[Requires]` .{data-version:3.2.2} +------------------------------------------------------------------ + +El atributo `#[Requires]` proporciona opciones avanzadas para restringir el acceso a presenters y sus métodos. Se puede usar para especificar métodos HTTP, requerir una petición AJAX, restringir al mismo origen (same origin) y acceso solo a través de forward. El atributo se puede aplicar tanto a clases de presenters como a métodos individuales `action()`, `render()`, `handle()` y `createComponent()`. + +Puede especificar estas restricciones: +- a métodos HTTP: `#[Requires(methods: ['GET', 'POST'])]` +- requerir una petición AJAX: `#[Requires(ajax: true)]` +- acceso solo desde el mismo origen: `#[Requires(sameOrigin: true)]` +- acceso solo a través de forward: `#[Requires(forward: true)]` +- restricción a acciones específicas: `#[Requires(actions: 'default')]` + +Encontrará detalles en el tutorial [Cómo usar el atributo Requires |best-practices:attribute-requires]. + + +Verificación del método HTTP +---------------------------- + +Los presenters en Nette verifican automáticamente el método HTTP de cada petición entrante. La razón de esta verificación es principalmente la seguridad. Por defecto, se permiten los métodos `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH`. + +Si desea permitir adicionalmente, por ejemplo, el método `OPTIONS`, use para ello el atributo `#[Requires]` (desde Nette Application v3.2): + +```php +#[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])] +class MyPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +En la versión 3.1, la verificación se realiza en `checkHttpMethod()`, que comprueba si el método especificado en la petición está contenido en el array `$presenter->allowedMethods`. La adición del método se hace así: + +```php +class MyPresenter extends Nette\Application\UI\Presenter +{ + protected function checkHttpMethod(): void + { + $this->allowedMethods[] = 'OPTIONS'; + parent::checkHttpMethod(); + } +} +``` + +Es importante destacar que si permite el método `OPTIONS`, debe luego también gestionarlo adecuadamente dentro de su presenter. El método se usa a menudo como la llamada petición preflight, que el navegador envía automáticamente antes de la petición real, cuando es necesario averiguar si la petición está permitida desde el punto de vista de la política CORS (Cross-Origin Resource Sharing). Si permite el método, pero no implementa la respuesta correcta, puede llevar a inconsistencias y posibles problemas de seguridad. + + +Lectura adicional +================= -- [Inyectar métodos y atributos |best-practices:inject-method-attribute] -- [Componer presentadores a partir de rasgos |best-practices:presenter-traits] -- [Pasar configuraciones a |best-practices:passing-settings-to-presenters]los presentadores +- [Métodos y atributos inject |best-practices:inject-method-attribute] +- [Composición de presenters a partir de traits |best-practices:presenter-traits] +- [Paso de configuraciones a presenters |best-practices:passing-settings-to-presenters] - [Cómo volver a una página anterior |best-practices:restore-request] diff --git a/application/es/routing.texy b/application/es/routing.texy index 2e886aa907..d906f83f48 100644 --- a/application/es/routing.texy +++ b/application/es/routing.texy @@ -3,27 +3,26 @@ Enrutamiento
    -El enrutador se encarga de todo lo relacionado con las URL para que no tengas que pensar más en ellas. Se lo mostraremos: +El Router se encarga de todo lo relacionado con las direcciones URL, para que usted ya no tenga que pensar en ellas. Mostraremos: -- cómo configurar el router para que las URLs sean como tú quieres -- algunas notas sobre la redirección SEO -- y le mostraremos cómo escribir su propio enrutador +- cómo configurar el router para que las URL sean según sus deseos +- hablaremos de SEO y redirección +- y mostraremos cómo escribir su propio router
    -Las URLs más humanas (o URLs chulas o bonitas) son más usables, más memorables y contribuyen positivamente al SEO. Nette tiene esto en mente y satisface plenamente los deseos de los desarrolladores. Puedes diseñar la estructura de URL de tu aplicación exactamente como quieras. -Incluso puede diseñarla después de que la aplicación esté lista, ya que puede hacerse sin ningún cambio de código o plantilla. Se define de forma elegante en [un único lugar |#Integration], en el enrutador, y no está dispersa en forma de anotaciones en todos los presentadores. +Las URL más humanas (o también cool o pretty URL) son más usables, memorables y contribuyen positivamente al SEO. Nette piensa en esto y apoya plenamente a los desarrolladores. Puede diseñar para su aplicación exactamente la estructura de direcciones URL que desee. Incluso puede diseñarla cuando la aplicación ya está terminada, porque se puede hacer sin intervenciones en el código o las plantillas. Se define de manera elegante en un [único lugar |#Integración en la aplicación], en el router, y no está dispersa en forma de anotaciones en todos los presenters. -El enrutador en Nette es especial porque es **bidireccional**, puede tanto decodificar URLs de peticiones HTTP como crear enlaces. Así que juega un papel vital en la [aplicación |how-it-works#Nette Application] de Nette, porque decide qué presentador y qué acción ejecutarán la solicitud actual, y también se utiliza para la [generación de URL |creating-links] en la plantilla, etc. +El Router en Nette es extraordinario porque es **bidireccional.** Sabe tanto decodificar URL en la petición HTTP como crear enlaces. Juega, por lo tanto, un papel fundamental en [Nette Application |how-it-works#Nette Application], porque por un lado decide qué presenter y acción ejecutará la petición actual, pero también se utiliza para [generar URL |creating-links] en la plantilla, etc. -Sin embargo, el enrutador no se limita a este uso, puede utilizarlo en aplicaciones donde los presentadores no se utilizan en absoluto, para las API REST, etc. Más en la sección [uso separado |#separated usage]. +Sin embargo, el router no está limitado solo a este uso, puede usarlo en aplicaciones donde no se usan presenters en absoluto, para API REST, etc. Más en la sección [#Uso independiente]. -Colección de rutas .[#toc-route-collection] -=========================================== +Colección de rutas +================== -La forma más agradable de definir las direcciones URL en la aplicación es a través de la clase [api:Nette\Application\Routers\RouteList]. La definición consiste en una lista de las llamadas rutas, es decir, máscaras de direcciones URL y sus presentadores y acciones asociadas mediante una API sencilla. No es necesario dar nombre a las rutas. +La forma más agradable de definir la apariencia de las direcciones URL en la aplicación la ofrece la clase [api:Nette\Application\Routers\RouteList]. La definición consiste en una lista de las llamadas rutas, es decir, máscaras de direcciones URL y sus presenters y acciones asociados mediante una API simple. No necesitamos nombrar las rutas de ninguna manera. ```php $router = new Nette\Application\Routers\RouteList; @@ -32,84 +31,84 @@ $router->addRoute('article/', 'Article:view'); // ... ``` -El ejemplo dice que si abrimos `https://any-domain.com/rss.xml` con la acción `rss`, si `https://domain.com/article/12` con la acción `view`, etc. Si no se encuentra una ruta adecuada, Nette Application responde lanzando una excepción [BadRequestException |api:Nette\Application\BadRequestException], que aparece al usuario como una página de error 404 Not Found. +El ejemplo dice que si abrimos `https://domain.com/rss.xml` en el navegador, se mostrará el presenter `Feed` con la acción `rss`, si `https://domain.com/article/12`, se mostrará el presenter `Article` con la acción `view`, etc. En caso de no encontrar una ruta adecuada, Nette Application reacciona lanzando una excepción [BadRequestException |api:Nette\Application\BadRequestException], que se muestra al usuario como una página de error 404 Not Found. -Orden de las rutas .[#toc-order-of-routes] ------------------------------------------- +Orden de las rutas +------------------ -El orden en que se enumeran las rutas es **muy importante** porque se evalúan secuencialmente de arriba a abajo. La regla es que declaremos las rutas **de específica a general**: +Es absolutamente **clave el orden** en que se indican las rutas individuales, porque se evalúan secuencialmente de arriba abajo. Se aplica la regla de que declaramos las rutas **de específicas a generales**: ```php -// INCORRECTO: coincide con la primera ruta y la malinterpreta como . +// MAL: 'rss.xml' lo captura la primera ruta y entiende esta cadena como $router->addRoute('', 'Article:view'); $router->addRoute('rss.xml', 'Feed:rss'); -// BUENO +// BIEN $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('', 'Article:view'); ``` -Las rutas también se evalúan de arriba a abajo cuando se generan los enlaces: +Las rutas también se evalúan de arriba abajo al generar enlaces: ```php -// INCORRECTO: genera un enlace a 'Feed:rss' como 'admin/feed/rss' +// MAL: el enlace a 'Feed:rss' se genera como 'admin/feed/rss' $router->addRoute('admin//', 'Admin:default'); $router->addRoute('rss.xml', 'Feed:rss'); -// BUENO +// BIEN $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('admin//', 'Admin:default'); ``` -No le ocultaremos que se necesita cierta habilidad para construir una lista correctamente. Hasta que te pongas a ello, el [panel de en |#Debugging Router] rutamiento será una herramienta útil. +No le ocultaremos que la correcta composición de las rutas requiere cierta habilidad. Antes de que la domine, le será útil el [panel de enrutamiento |#Depuración del router]. -Máscara y parámetros .[#toc-mask-and-parameters] ------------------------------------------------- +Máscara y parámetros +-------------------- -La máscara describe la ruta relativa basada en la raíz del sitio. La máscara más simple es una URL estática: +La máscara describe la ruta relativa desde el directorio raíz del sitio web. La máscara más simple es una URL estática: ```php $router->addRoute('products', 'Products:default'); ``` -A menudo, las máscaras contienen los llamados **parámetros**. Van entre corchetes angulares (por ejemplo ``) y se pasan al presentador de destino, por ejemplo al método `renderShow(int $year)` o al parámetro persistente `$year`: +A menudo, las máscaras contienen los llamados **parámetros**. Estos se indican entre corchetes angulares (p. ej., ``) y se pasan al presenter de destino, por ejemplo, al método `renderShow(int $year)` o al parámetro persistente `$year`: ```php $router->addRoute('chronicle/', 'History:show'); ``` -El ejemplo dice que si abrimos `https://any-domain.com/chronicle/2020` y la acción `show` con el parámetro `year: 2020`. +El ejemplo dice que si abrimos `https://example.com/chronicle/2020` en el navegador, se mostrará el presenter `History` con la acción `show` y el parámetro `year: 2020`. -Podemos especificar un valor por defecto para los parámetros directamente en la máscara y así se convierte en opcional: +Podemos asignar un valor predeterminado a los parámetros directamente en la máscara y así se vuelven opcionales: ```php $router->addRoute('chronicle/', 'History:show'); ``` -La ruta aceptará ahora la URL `https://any-domain.com/chronicle/` con el parámetro `year: 2020`. +La ruta ahora aceptará también la URL `https://example.com/chronicle/`, que nuevamente mostrará `History:show` con el parámetro `year: 2020`. -Por supuesto, el nombre del presentador y la acción también pueden ser un parámetro. Por ejemplo: +El parámetro puede ser, por supuesto, también el nombre del presenter y la acción. Por ejemplo, así: ```php $router->addRoute('/', 'Home:default'); ``` -Esta ruta acepta, por ejemplo, una URL de la forma `/article/edit` resp. `/catalog/list` y las traduce a presentadores y acciones `Article:edit` resp. `Catalog:list`. +La ruta indicada acepta, p. ej., URL en la forma `/article/edit` o también `/catalog/list` y las entiende como presenters y acciones `Article:edit` y `Catalog:list`. -También da a los parámetros `presenter` y `action` valores por defecto`Home` y `default` y, por tanto, son opcionales. Así, la ruta también acepta una URL `/article` y la traduce como `Article:default`. O viceversa, un enlace a `Product:default` genera una ruta `/product`, un enlace al valor por defecto `Home:default` genera una ruta `/`. +Al mismo tiempo, da a los parámetros `presenter` y `action` los valores predeterminados `Home` y `default` y, por lo tanto, también son opcionales. Así que la ruta acepta también URL en la forma `/article` y la entiende como `Article:default`. O al revés, un enlace a `Product:default` generará la ruta `/product`, un enlace al predeterminado `Home:default` la ruta `/`. -La máscara puede describir no sólo la ruta relativa basada en la raíz del sitio, sino también la ruta absoluta cuando comienza con una barra, o incluso toda la URL absoluta cuando comienza con dos barras: +La máscara puede describir no solo la ruta relativa desde el directorio raíz del sitio web, sino también la ruta absoluta, si comienza con una barra inclinada, o incluso una URL absoluta completa, si comienza con dos barras inclinadas: ```php -// ruta relativa a la raíz del documento de la aplicación +// relativo al document root $router->addRoute('/', /* ... */); -// ruta absoluta, relativa al nombre del servidor +// ruta absoluta (relativa al dominio) $router->addRoute('//', /* ... */); -// URL absoluta incluyendo nombre de host (pero relativa al esquema) +// URL absoluta incluyendo dominio (relativa al esquema) $router->addRoute('//.example.com//', /* ... */); // URL absoluta incluyendo esquema @@ -117,45 +116,45 @@ $router->addRoute('https://.example.com//', /* ... */); ``` -Expresiones de validación .[#toc-validation-expressions] --------------------------------------------------------- +Expresiones de validación +------------------------- -Se puede especificar una condición de validación para cada parámetro utilizando una expresión [regular |https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Por ejemplo, establezcamos que `id` sea sólo numérico, utilizando `\d+` regexp: +Para cada parámetro se puede establecer una condición de validación mediante una [expresión regular|https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Por ejemplo, para el parámetro `id` determinamos que solo puede tomar dígitos usando la expresión regular `\d+`: ```php $router->addRoute('/[/]', /* ... */); ``` -La expresión regular por defecto para todos los parámetros es `[^/]+`es decir, todo excepto la barra. Si un parámetro también debe coincidir con una barra oblicua, la expresión regular será `.+`. +La expresión regular predeterminada para todos los parámetros es `[^/]+`, es decir, todo excepto la barra inclinada. Si un parámetro debe aceptar también barras inclinadas, indicamos la expresión `.+`: ```php -// acepta https://example.com/a/b/c, la ruta es 'a/b/c' +// acepta https://example.com/a/b/c, path será 'a/b/c' $router->addRoute('', /* ... */); ``` -Secuencias opcionales .[#toc-optional-sequences] ------------------------------------------------- +Secuencias opcionales +--------------------- -Los corchetes indican las partes opcionales de la máscara. Cualquier parte de la máscara puede ser opcional, incluidas las que contienen parámetros: +En la máscara se pueden marcar partes opcionales usando corchetes. Opcional puede ser cualquier parte de la máscara, también pueden contener parámetros: ```php $router->addRoute('[/]', /* ... */); -// URL aceptadas: Parámetros: -// /en/download lang => en, name => download -// /download lang => null, name => download +// Acepta rutas: +// /cs/download => lang => cs, name => download +// /download => lang => null, name => download ``` -Por supuesto, cuando un parámetro forma parte de una secuencia opcional, también se convierte en opcional. Si no tiene un valor por defecto, será nulo. +Cuando un parámetro es parte de una secuencia opcional, se vuelve, por supuesto, también opcional. Si no tiene un valor predeterminado indicado, será null. -Las secciones opcionales también pueden estar en el dominio: +Las partes opcionales también pueden estar en el dominio: ```php $router->addRoute('//[.]example.com//', /* ... */); ``` -Las secuencias pueden anidarse y combinarse libremente: +Las secuencias se pueden anidar y combinar libremente: ```php $router->addRoute( @@ -163,48 +162,48 @@ $router->addRoute( 'Home:default', ); -// URL aceptadas: -// /en/hello -// /en-us/hello -// /hello -// /hello/page-12 +// Acepta rutas: +// /cs/hello +// /en-us/hello +// /hello +// /hello/page-12 ``` -El generador de URL intenta que la URL sea lo más corta posible, por lo que se omite lo que se puede omitir. Así, por ejemplo, una ruta `index[.html]` genera una ruta `/index`. Puede invertir este comportamiento escribiendo un signo de exclamación después del corchete izquierdo: +Al generar URL, se busca la variante más corta, por lo que todo lo que se puede omitir, se omite. Por eso, por ejemplo, la ruta `index[.html]` genera la ruta `/index`. Se puede invertir el comportamiento indicando un signo de exclamación después del corchete izquierdo: ```php -// acepta tanto /hello como /hello.html, genera /hello +// acepta /hello y /hello.html, genera /hello $router->addRoute('[.html]', /* ... */); -// acepta tanto /hello como /hello.html, genera /hello.html +// acepta /hello y /hello.html, genera /hello.html $router->addRoute('[!.html]', /* ... */); ``` -Los parámetros opcionales (es decir, los parámetros que tienen un valor por defecto) sin corchetes se comportan como si estuvieran envueltos de esta manera: +Los parámetros opcionales (es decir, parámetros que tienen un valor predeterminado) sin corchetes se comportan básicamente como si estuvieran entre paréntesis de la siguiente manera: ```php $router->addRoute('//', /* ... */); -// igual a: +// corresponde a esto: $router->addRoute('[/[/[]]]', /* ... */); ``` -Para cambiar cómo se genera la barra más a la derecha, es decir, en lugar de `/home/` obtener un `/home`, ajustar la ruta de esta manera: +Si quisiéramos influir en el comportamiento de la barra inclinada final, para que, por ejemplo, en lugar de `/home/` se genere solo `/home`, se puede lograr así: ```php $router->addRoute('[[/[/]]]', /* ... */); ``` -Comodines .[#toc-wildcards] ---------------------------- +Comodines +--------- -En la máscara de ruta absoluta, podemos utilizar los siguientes comodines para evitar, por ejemplo, la necesidad de escribir un dominio en la máscara, que puede diferir en el entorno de desarrollo y de producción: +En la máscara de ruta absoluta, podemos usar los siguientes comodines y evitar así, por ejemplo, la necesidad de escribir en la máscara el dominio, que puede diferir en el entorno de desarrollo y producción: -- `%tld%` = dominio de primer nivel, por ejemplo `com` o `org` -- `%sld%` = dominio de segundo nivel, por ejemplo `example` -- `%domain%` = dominio sin subdominios, p. ej. `example.com` -- `%host%` = host completo, por ejemplo `www.example.com` +- `%tld%` = dominio de nivel superior, p. ej., `com` u `org` +- `%sld%` = dominio de segundo nivel, p. ej., `example` +- `%domain%` = dominio sin subdominios, p. ej., `example.com` +- `%host%` = host completo, p. ej., `www.example.com` - `%basePath%` = ruta al directorio raíz ```php @@ -213,10 +212,10 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ @@ -225,7 +224,7 @@ $router->addRoute('/[/]', [ ]); ``` -O podemos utilizar esta forma, observe la reescritura de la expresión regular de validación: +Para una especificación más detallada, se puede usar una forma aún más extendida, donde además de los valores predeterminados podemos establecer otras propiedades de los parámetros, como por ejemplo la expresión regular de validación (ver parámetro `id`): ```php use Nette\Routing\Route; @@ -243,19 +242,19 @@ $router->addRoute('/[/]', [ ]); ``` -Estos formatos más locuaces son útiles para añadir otros metadatos. +Es importante señalar que si los parámetros definidos en el array no se indican en la máscara de la ruta, sus valores no se pueden cambiar, ni siquiera mediante parámetros de consulta indicados después del signo de interrogación en la URL. -Filtros y traducciones .[#toc-filters-and-translations] -------------------------------------------------------- +Filtros y traducciones +---------------------- -Es una buena práctica escribir el código fuente en inglés, pero ¿qué pasa si necesitas que tu sitio web tenga la URL traducida a otro idioma? Rutas simples como: +Escribimos los códigos fuente de la aplicación en inglés, pero si el sitio web debe tener URL en español, entonces un enrutamiento simple como: ```php $router->addRoute('/', 'Home:default'); ``` -generarán URL en inglés, como `/product/123` o `/cart`. Si queremos que los presentadores y las acciones de la URL se traduzcan al alemán (por ejemplo, `/produkt/123` o `/einkaufswagen`), podemos utilizar un diccionario de traducción. Para añadirlo, ya necesitamos una variante "más locuaz" del segundo parámetro: +generará URL en inglés, como `/product/123` o `/cart`. Si queremos que los presenters y las acciones en la URL estén representados por palabras en español (p. ej., `/producto/123` o `/carrito`), podemos utilizar un diccionario de traducción. Para su escritura ya necesitamos la variante "más detallada" del segundo parámetro: ```php use Nette\Routing\Route; @@ -264,26 +263,26 @@ $router->addRoute('/', [ 'presenter' => [ Route::Value => 'Home', Route::FilterTable => [ - // cadena en URL => presentador - 'produkt' => 'Product', - 'einkaufswagen' => 'Cart', - 'katalog' => 'Catalog', + // cadena en la URL => presenter + 'producto' => 'Product', + 'carrito' => 'Cart', + 'catalogo' => 'Catalog', ], ], 'action' => [ Route::Value => 'default', Route::FilterTable => [ - 'liste' => 'list', + 'lista' => 'list', ], ], ]); ``` -Se pueden utilizar varias claves de diccionario para el mismo presentador. Se crearán varios alias para él. La última clave se considera la variante canónica (es decir, la que aparecerá en la URL generada). +Varias claves del diccionario de traducción pueden llevar al mismo presenter. De esta manera se crean diferentes alias para él. La variante canónica (es decir, la que estará en la URL generada) se considera la última clave. -De este modo, la tabla de traducción puede aplicarse a cualquier parámetro. Sin embargo, si la traducción no existe, se toma el valor original. Podemos cambiar este comportamiento añadiendo `Route::FilterStrict => true` y la ruta rechazará entonces la URL si el valor no está en el diccionario. +La tabla de traducción se puede usar de esta manera para cualquier parámetro. Si la traducción no existe, se toma el valor original. Este comportamiento se puede cambiar agregando `Route::FilterStrict => true` y la ruta rechazará la URL si el valor no está en el diccionario. -Además del diccionario de traducción en forma de array, es posible establecer funciones de traducción propias: +Además del diccionario de traducción en forma de array, se pueden implementar funciones de traducción propias. ```php use Nette\Routing\Route; @@ -299,15 +298,15 @@ $router->addRoute('//', [ ]); ``` -La función `Route::FilterIn` convierte entre el parámetro en la URL y la cadena, que luego se pasa al presentador, la función `FilterOut` asegura la conversión en la dirección opuesta. +La función `Route::FilterIn` convierte entre el parámetro en la URL y la cadena que luego se pasa al presenter, la función `FilterOut` asegura la conversión en la dirección opuesta. -Los parámetros `presenter`, `action` y `module` ya tienen filtros predefinidos que convierten entre el estilo PascalCase resp. camelCase y kebab-case utilizado en la URL. El valor por defecto de los parámetros ya está escrito en la forma transformada, por lo que, por ejemplo, en el caso de un presentador, escribimos `` en lugar de ``. +Los parámetros `presenter`, `action` y `module` ya tienen filtros predefinidos que convierten entre el estilo PascalCase resp. camelCase y kebab-case utilizado en la URL. El valor predeterminado de los parámetros se escribe ya en la forma transformada, por lo que, por ejemplo, en el caso del presenter escribimos ``, no ``. -Filtros generales .[#toc-general-filters] ------------------------------------------ +Filtros generales +----------------- -Además de filtros para parámetros específicos, también puede definir filtros generales que reciben una matriz asociativa de todos los parámetros que pueden modificar de cualquier manera y luego devolver. Los filtros generales se definen con la tecla `null`. +Además de los filtros destinados a parámetros específicos, también podemos definir filtros generales que reciben un array asociativo de todos los parámetros, que pueden modificar de cualquier manera y luego devolverlos. Los filtros generales los definimos bajo la clave `null`. ```php use Nette\Routing\Route; @@ -315,62 +314,85 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => 'Home', 'action' => 'default', - null => [ + '' => [ Route::FilterIn => function (array $params): array { /* ... */ }, Route::FilterOut => function (array $params): array { /* ... */ }, ], ]); ``` -Los filtros generales le dan la posibilidad de ajustar el comportamiento de la ruta de absolutamente cualquier manera. Podemos utilizarlos, por ejemplo, para modificar parámetros en función de otros parámetros. Por ejemplo, la traducción `` y `` en función del valor actual del parámetro ``. +Los filtros generales dan la posibilidad de modificar el comportamiento de la ruta de absolutamente cualquier manera. Podemos usarlos, por ejemplo, para modificar parámetros basándose en otros parámetros. Por ejemplo, la traducción de `` y `` basada en el valor actual del parámetro ``. -Si un parámetro tiene definido un filtro personalizado y existe al mismo tiempo un filtro general, el personalizado `FilterIn` se ejecuta antes que el general y viceversa el general `FilterOut` se ejecuta antes que el personalizado. Así, dentro del filtro general están los valores de los parámetros `presenter` resp. `action` escritos en estilo PascalCase resp. camelCase. +Si un parámetro tiene definido un filtro propio y al mismo tiempo existe un filtro general, se ejecuta el `FilterIn` propio antes del general y, a la inversa, el `FilterOut` general antes del propio. Es decir, dentro del filtro general, los valores de los parámetros `presenter` resp. `action` están escritos en estilo PascalCase resp. camelCase. -Indicador unidireccional .[#toc-oneway-flag] --------------------------------------------- +Rutas de un solo sentido OneWay +------------------------------- -Las rutas unidireccionales se utilizan para preservar la funcionalidad de las URL antiguas que la aplicación ya no genera pero que aún acepta. Las marcamos con `OneWay`: +Las rutas de un solo sentido se utilizan para mantener la funcionalidad de las URL antiguas que la aplicación ya no genera, pero sigue aceptando. Las marcamos con el indicador `OneWay`: ```php -// antigua URL /product-info?id=123 +// URL antigua /product-info?id=123 $router->addRoute('product-info', 'Product:detail', $router::ONE_WAY); -// nueva URL /producto/123 +// nueva URL /product/123 $router->addRoute('product/', 'Product:detail'); ``` -Al acceder a la URL antigua, el presentador redirige automáticamente a la URL nueva para que los motores de búsqueda no indexen estas páginas dos veces (véase [SEO y canonización |#SEO and canonization]). +Al acceder a la URL antigua, el presenter redirige automáticamente a la nueva URL, por lo que los motores de búsqueda no indexarán estas páginas dos veces (ver [#SEO y canonización]). -Módulos .[#toc-modules] ------------------------ +Enrutamiento dinámico con callbacks +----------------------------------- + +El enrutamiento dinámico con callbacks le permite asignar directamente funciones (callbacks) a las rutas, que se ejecutarán cuando se visite la ruta dada. Esta funcionalidad flexible le permite crear rápida y eficientemente diferentes puntos finales (endpoints) para su aplicación: + +```php +$router->addRoute('test', function () { + echo 'estás en la dirección /test'; +}); +``` + +También puede definir parámetros en la máscara, que se pasarán automáticamente a su callback: + +```php +$router->addRoute('', function (string $lang) { + echo match ($lang) { + 'cs' => '¡Bienvenido a la versión checa de nuestro sitio web!', + 'en' => 'Welcome to the English version of our website!', + }; +}); +``` -Si tenemos más rutas que pertenecen a un [módulo |modules], podemos utilizar `withModule()` para agruparlas: + +Módulos +------- + +Si tenemos varias rutas que pertenecen a un [módulo |directory-structure#Presenters y plantillas] común, utilizamos `withModule()`: ```php $router = new RouteList; -$router->withModule('Forum') // los siguientes routers forman parte del módulo Forum - ->addRoute('rss', 'Feed:rss') // el presentador es Forum:Feed +$router->withModule('Forum') // las siguientes rutas son parte del módulo Forum + ->addRoute('rss', 'Feed:rss') // el presenter será Forum:Feed ->addRoute('/') - ->withModule('Admin') // los siguientes routers forman parte del módulo Forum:Admin + ->withModule('Admin') // las siguientes rutas son parte del módulo Forum:Admin ->addRoute('sign:in', 'Sign:in'); ``` -Una alternativa es utilizar el parámetro `module`: +Una alternativa es usar el parámetro `module`: ```php -// URL manage/dashboard/default se asigna al presentador Admin:Dashboard +// La URL manage/dashboard/default se mapea al presenter Admin:Dashboard $router->addRoute('manage//', [ 'module' => 'Admin', ]); ``` -Subdominios .[#toc-subdomains] ------------------------------- +Subdominios +----------- -Las colecciones de rutas pueden agruparse por subdominios: +Podemos dividir las colecciones de rutas según subdominios: ```php $router = new RouteList; @@ -379,7 +401,7 @@ $router->withDomain('example.com') ->addRoute('/'); ``` -También puede utilizar [comodines |#wildcards] en su nombre de dominio: +En el nombre del dominio también se pueden usar [#Comodines]: ```php $router = new RouteList; @@ -388,23 +410,23 @@ $router->withDomain('example.%tld%') ``` -Prefijo de ruta .[#toc-path-prefix] ------------------------------------ +Prefijo de ruta +--------------- -Las colecciones de rutas pueden agruparse por ruta en la URL: +Podemos dividir las colecciones de rutas según la ruta en la URL: ```php $router = new RouteList; $router->withPath('eshop') - ->addRoute('rss', 'Feed:rss') // coincide con URL /eshop/rss - ->addRoute('/'); // coincide con la URL /eshop// + ->addRoute('rss', 'Feed:rss') // captura la URL /eshop/rss + ->addRoute('/'); // captura la URL /eshop// ``` -Combinaciones .[#toc-combinations] ----------------------------------- +Combinaciones +------------- -Los usos anteriores pueden combinarse: +Podemos combinar las divisiones anteriores entre sí: ```php $router = (new RouteList) @@ -424,40 +446,40 @@ $router = (new RouteList) ``` -Parámetros de consulta .[#toc-query-parameters] ------------------------------------------------ +Parámetros de consulta +---------------------- -Las máscaras también pueden contener parámetros de consulta (parámetros después del signo de interrogación en la URL). No pueden definir una expresión de validación, pero pueden cambiar el nombre con el que se pasan al presentador: +Las máscaras también pueden contener parámetros de consulta (parámetros después del signo de interrogación en la URL). A estos no se les puede definir una expresión de validación, pero se puede cambiar el nombre bajo el cual se pasan al presenter: ```php -// utilizar el parámetro de consulta 'cat' como 'categoryId' en la aplicación +// queremos usar el parámetro de consulta 'cat' en la aplicación bajo el nombre 'categoryId' $router->addRoute('product ? id= & cat=', /* ... */); ``` -Parámetros Foo .[#toc-foo-parameters] -------------------------------------- +Parámetros Foo +-------------- -Ahora vamos más a fondo. Los parámetros Foo son básicamente parámetros sin nombre que permiten coincidir con una expresión regular. La siguiente ruta coincide con `/index`, `/index.html`, `/index.htm` y `/index.php`: +Ahora vamos más profundo. Los parámetros Foo son básicamente parámetros sin nombre que permiten hacer coincidir una expresión regular. Un ejemplo es una ruta que acepta `/index`, `/index.html`, `/index.htm` y `/index.php`: ```php $router->addRoute('index', /* ... */); ``` -También es posible definir explícitamente una cadena que se utilizará para generar la URL. La cadena debe colocarse directamente después del signo de interrogación. La siguiente ruta es similar a la anterior, pero genera `/index.html` en lugar de `/index` porque la cadena `.html` está configurada como "valor generado". +También se puede definir explícitamente la cadena que se usará al generar la URL. La cadena debe colocarse directamente después del signo de interrogación. La siguiente ruta es similar a la anterior, pero genera `/index.html` en lugar de `/index`, porque la cadena `.html` está configurada como valor de generación: ```php $router->addRoute('index', /* ... */); ``` -Integración .[#toc-integration] -=============================== +Integración en la aplicación +============================ -Para conectar nuestro router a la aplicación, debemos informar al contenedor DI sobre él. La forma más sencilla es preparar la fábrica que construirá el objeto router y decirle a la configuración del contenedor que lo utilice. Digamos que escribimos un método para este propósito `App\Router\RouterFactory::createRouter()`: +Para incorporar el router creado en la aplicación, debemos decírselo al contenedor DI. La forma más fácil es preparar una fábrica que produzca el objeto router e indicar en la configuración del contenedor que debe usarla. Supongamos que para este propósito escribimos el método `App\Core\RouterFactory::createRouter()`: ```php -namespace App\Router; +namespace App\Core; use Nette\Application\Routers\RouteList; @@ -472,14 +494,14 @@ class RouterFactory } ``` -Luego escribimos en [configuración |dependency-injection:services]: +En la [configuración |dependency-injection:services] luego escribimos: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter ``` -Cualquier dependencia, como una conexión de base de datos, etc., se pasa al método de fábrica como sus parámetros utilizando [autowiring |dependency-injection:autowiring]: +Cualquier dependencia, por ejemplo, a la base de datos, etc., se pasa al método de fábrica como sus parámetros mediante [autowiring|dependency-injection:autowiring]: ```php public static function createRouter(Nette\Database\Connection $db): RouteList @@ -489,21 +511,21 @@ public static function createRouter(Nette\Database\Connection $db): RouteList ``` -SimpleRouter .[#toc-simplerouter] -================================= +SimpleRouter +============ -Un enrutador mucho más simple que Route Collection es [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Se puede utilizar cuando no hay necesidad de un formato de URL específico, cuando `mod_rewrite` (o alternativas) no está disponible o cuando simplemente no queremos molestarnos con URLs fáciles de usar todavía. +Un router mucho más simple que la colección de rutas es [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Lo usamos cuando no tenemos requisitos especiales sobre la forma de la URL, si no está disponible `mod_rewrite` (o sus alternativas) o si aún no queremos ocuparnos de URL bonitas. -Genera direcciones más o menos de esta forma: +Genera direcciones aproximadamente en esta forma: ``` http://example.com/?presenter=Product&action=detail&id=123 ``` -El parámetro del constructor `SimpleRouter` es un presentador y una acción por defecto, es decir, la acción que se ejecutará si abrimos, por ejemplo, `http://example.com/` sin parámetros adicionales. +El parámetro del constructor de SimpleRouter es el presenter y la acción predeterminados a los que se debe dirigir si abrimos la página sin parámetros, p. ej., `http://example.com/`. ```php -// por defecto el presentador es 'Home' y la acción es 'default +// el presenter predeterminado será 'Home' y la acción 'default' $router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` @@ -515,22 +537,22 @@ services: ``` -SEO y Canonización .[#toc-seo-and-canonization] -=============================================== +SEO y canonización +================== -El framework aumenta el SEO (optimización para motores de búsqueda) al evitar la duplicación de contenidos en distintas URL. Si varias direcciones enlazan a un mismo destino, por ejemplo `/index` y `/index.html`, el framework determina la primera como primaria (canónica) y redirige las demás a ella utilizando el código HTTP 301. Gracias a esto, los motores de búsqueda no indexarán las páginas dos veces y no romperán su page rank. . +El framework contribuye al SEO (optimización para motores de búsqueda) evitando la duplicidad de contenido en diferentes URL. Si a un destino determinado conducen varias direcciones, p. ej., `/index` y `/index.html`, el framework determina la primera de ellas como primaria (canónica) y redirige las demás a ella usando el código HTTP 301. Gracias a esto, los motores de búsqueda no indexan sus páginas dos veces y no diluyen su page rank. -Este proceso se denomina canonización. La URL canónica es la generada por el enrutador, es decir, por la primera ruta coincidente de la [colección |#route-collection] sin la bandera OneWay. Por lo tanto, en la colección, enumeramos primero las **rutas primarias**. +Este proceso se llama canonización. La URL canónica es la que genera el router, es decir, la primera ruta que cumple en la colección sin el indicador OneWay. Por eso, en la colección indicamos **las rutas primarias primero**. -La canonización la realiza el presentador, más en el capítulo [canonización |presenters#Canonization]. +La canonización la realiza el presenter, más en el capítulo [canonización |presenters#Canonización]. -HTTPS .[#toc-https] -=================== +HTTPS +===== -Para utilizar el protocolo HTTPS, es necesario activarlo en el alojamiento y configurar el servidor. +Para poder usar el protocolo HTTPS, es necesario habilitarlo en el hosting y configurar correctamente el servidor. -La redirección de todo el sitio a HTTPS debe realizarse a nivel de servidor, por ejemplo utilizando el archivo .htaccess en el directorio raíz de nuestra aplicación, con código HTTP 301. La configuración puede variar dependiendo del hosting y tiene un aspecto similar a este: +La redirección de todo el sitio a HTTPS debe configurarse a nivel de servidor, por ejemplo, mediante el archivo .htaccess en el directorio raíz de nuestra aplicación, y con el código HTTP 301. La configuración puede variar según el hosting y se ve aproximadamente así: ``` @@ -542,40 +564,40 @@ La redirección de todo el sitio a HTTPS debe realizarse a nivel de servidor, po ``` -El router genera una URL con el mismo protocolo con el que se cargó la página, por lo que no es necesario configurar nada más. +El router genera URL con el mismo protocolo con el que se cargó la página, por lo que no es necesario configurar nada más. -Sin embargo, si excepcionalmente necesitamos que diferentes rutas se ejecuten bajo diferentes protocolos, lo pondremos en la máscara de ruta: +Pero si excepcionalmente necesitamos que diferentes rutas se ejecuten bajo diferentes protocolos, lo indicamos en la máscara de la ruta: ```php -// Generará una dirección HTTP +// Generará una dirección con HTTP $router->addRoute('http://%host%//', /* ... */); -// Generará una dirección HTTPS +// Generará una dirección con HTTPs $router->addRoute('https://%host%//', /* ... */); ``` -Debugging Router .[#toc-debugging-router] -========================================= +Depuración del router +===================== -La barra de enrutamiento mostrada en [Tracy Bar |tracy:] es una herramienta útil que muestra una lista de rutas y también los parámetros que el enrutador ha obtenido de la URL. +El panel de enrutamiento que se muestra en [Tracy Bar |tracy:] es un ayudante útil que muestra la lista de rutas y también los parámetros que el router obtuvo de la URL. -La barra verde con el símbolo ✓ representa la ruta que coincide con la URL actual, las barras azules con los símbolos ≈ indican las rutas que también coincidirían con la URL si el verde no las superara. Vemos más adelante el presentador actual y la acción. +La barra verde con el símbolo ✓ representa la ruta que procesó la URL actual, en color azul y con el símbolo ≈ están marcadas las rutas que también procesarían la URL si la verde no se les hubiera adelantado. Además, vemos el presenter y la acción actuales. [* routing-debugger.webp *] -Al mismo tiempo, si hay una redirección inesperada debido a [la canonicalización |#SEO and Canonization], es útil mirar en la barra *redirect* para ver cómo entendió originalmente el enrutador la URL y por qué redirigió. +Al mismo tiempo, si ocurre una redirección inesperada debido a la [canonización |#SEO y canonización], es útil mirar el panel en la barra *redirect*, donde descubrirá cómo el router entendió originalmente la URL y por qué redirigió. .[note] -Al depurar el enrutador, se recomienda abrir Herramientas de desarrollo en el navegador (Ctrl+Mayús+I o Cmd+Opción+I) y desactivar la caché en el panel Red para que las redirecciones no se almacenen en ella. +Al depurar el router, recomendamos abrir las Herramientas de desarrollador en el navegador (Ctrl+Shift+I o Cmd+Option+I) y en el panel Network desactivar la caché, para que no se guarden las redirecciones en ella. -Rendimiento .[#toc-performance] -=============================== +Rendimiento +=========== -El número de rutas afecta a la velocidad del router. Su número no debería exceder de unas pocas docenas. Si su sitio tiene una estructura de URL demasiado complicada, puede escribir un enrutador [personalizado |#custom router]. +El número de rutas influye en la velocidad del router. Su número definitivamente no debería exceder varias decenas. Si su sitio web tiene una estructura de URL demasiado complicada, puede escribir un [#Router propio] a medida. -Si el enrutador no tiene dependencias, como en una base de datos, y su fábrica no tiene argumentos, podemos serializar su forma compilada directamente en un contenedor DI y así hacer la aplicación ligeramente más rápida. +Si el router no tiene dependencias, por ejemplo, a la base de datos, y su fábrica no acepta ningún argumento, podemos serializar su forma compilada directamente en el contenedor DI y así acelerar ligeramente la aplicación. ```neon routing: @@ -583,10 +605,10 @@ routing: ``` -Enrutador personalizado .[#toc-custom-router] -============================================= +Router propio +============= -Las siguientes líneas están pensadas para usuarios muy avanzados. Puedes crear tu propio enrutador y, naturalmente, añadirlo a tu colección de rutas. El enrutador es una implementación de la interfaz [api:Nette\Routing\Router] con dos métodos: +Las siguientes líneas están destinadas a usuarios muy avanzados. Puede crear su propio router e integrarlo de forma completamente natural en la colección de rutas. El router es una implementación de la interfaz [api:Nette\Routing\Router] con dos métodos: ```php use Nette\Http\IRequest as HttpRequest; @@ -606,8 +628,7 @@ class MyRouter implements Nette\Routing\Router } ``` -El método `match` procesa la [$httpRequest |http:request] actual, de la que se puede recuperar no sólo la URL, sino también las cabeceras, etc., en un array que contiene el nombre del presentador y sus parámetros. Si no puede procesar la petición, devuelve null. -Al procesar la solicitud, debemos devolver al menos el presentador y la acción. El nombre del presentador está completo e incluye cualquier módulo: +El método `match` procesa la petición actual [$httpRequest |http:request], de la cual se puede obtener no solo la URL, sino también las cabeceras, etc., en un array que contiene el nombre del presenter y sus parámetros. Si no puede procesar la petición, devuelve null. Al procesar la petición, debemos devolver como mínimo el presenter y la acción. El nombre del presenter es completo y contiene también posibles módulos: ```php [ @@ -616,9 +637,9 @@ Al procesar la solicitud, debemos devolver al menos el presentador y la acción. ] ``` -El método `constructUrl`, por otro lado, genera una URL absoluta a partir de la matriz de parámetros. Puede utilizar la información del parámetro `$refUrl`, que es la URL actual. +El método `constructUrl`, por el contrario, construye la URL absoluta resultante a partir del array de parámetros. Para ello puede utilizar información del parámetro [`$refUrl`|api:Nette\Http\UrlScript], que es la URL actual. -Para añadir un enrutador personalizado a la colección de rutas, utilice `add()`: +Lo agrega a la colección de rutas usando `add()`: ```php $router = new Nette\Application\Routers\RouteList; @@ -628,19 +649,19 @@ $router->addRoute(/* ... */); ``` -Uso separado .[#toc-separated-usage] -==================================== +Uso independiente +================= -Por uso separado, nos referimos al uso de las capacidades del router en una aplicación que no utiliza Nette Application y presentadores. Casi todo lo que hemos mostrado en este capítulo se aplica a ella, con las siguientes diferencias: +Por uso independiente entendemos el uso de las capacidades del router en una aplicación que no utiliza Nette Application ni presenters. Se aplica casi todo lo que hemos mostrado en este capítulo, con estas diferencias: -- para las colecciones de rutas utilizamos la clase [api:Nette\Routing\RouteList] -- como clase de enrutador simple [api:Nette\Routing\SimpleRouter] -- como no existe el par `Presenter:action`, utilizamos [la notación Advanced |#Advanced notation] +- para colecciones de rutas usamos la clase [api:Nette\Routing\RouteList] +- como simple router la clase [api:Nette\Routing\SimpleRouter] +- como no existe el par `Presenter:action`, usamos la [#Notación extendida] -Así que de nuevo crearemos un método que construirá un enrutador, por ejemplo +Así que nuevamente creamos un método que nos construya el router, p. ej.: ```php -namespace App\Router; +namespace App\Core; use Nette\Routing\RouteList; @@ -661,35 +682,35 @@ class RouterFactory } ``` -Si usas un contenedor DI, que es lo que recomendamos, añade de nuevo el método a la configuración y luego obtén el router junto con la petición HTTP del contenedor: +Si usa un contenedor DI, lo cual recomendamos, nuevamente agregamos el método a la configuración y luego obtenemos el router junto con la petición HTTP del contenedor: ```php $router = $container->getByType(Nette\Routing\Router::class); $httpRequest = $container->getByType(Nette\Http\IRequest::class); ``` -O crearemos los objetos directamente: +O fabricamos los objetos directamente: ```php -$router = App\Router\RouterFactory::createRouter(); +$router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` -Ahora tenemos que dejar que el router para trabajar: +Ahora solo queda poner el router a trabajar: ```php $params = $router->match($httpRequest); if ($params === null) { - // no se encuentra ninguna ruta coincidente, enviaremos un error 404 + // no se encontró una ruta que cumpliera, enviamos error 404 exit; } -// procesamos los parámetros recibidos +// procesamos los parámetros obtenidos $controller = $params['controller']; // ... ``` -Y viceversa, usaremos el router para crear el enlace: +Y a la inversa, usamos el router para construir un enlace: ```php $params = ['controller' => 'ArticleController', 'id' => 123]; diff --git a/application/es/templates.texy b/application/es/templates.texy index bbae32eb8a..b9c7148897 100644 --- a/application/es/templates.texy +++ b/application/es/templates.texy @@ -2,15 +2,15 @@ Plantillas ********** .[perex] -Nette utiliza el sistema de plantillas [Latte |latte:]. Se utiliza Latte porque es el sistema de plantillas más seguro para PHP, y al mismo tiempo el sistema más intuitivo. Usted no tiene que aprender mucho nuevo, sólo necesita saber PHP y algunas etiquetas Latte. +Nette utiliza el sistema de plantillas [Latte |latte:]. Por un lado, porque es el sistema de plantillas más seguro para PHP y, al mismo tiempo, el sistema más intuitivo. No necesita aprender mucho nuevo, le basta con conocer PHP y algunas etiquetas. -Lo habitual es que la página se complete a partir de la plantilla layout + la plantilla action. Este es el aspecto que podría tener una plantilla de maquetación, fíjate en los bloques `{block}` y la etiqueta `{include}`: +Es habitual que una página se componga de una plantilla de layout + la plantilla de la acción dada. Así puede verse, por ejemplo, una plantilla de layout, observe los bloques `{block}` y la etiqueta `{include}`: ```latte - {block title}My App{/block} + {block title}Mi Aplicación{/block}
    ...
    @@ -20,61 +20,109 @@ Lo habitual es que la página se complete a partir de la plantilla layout + la p ``` -Y esta podría ser la plantilla de acción: +Y esta será la plantilla de la acción: ```latte -{block title}Homepage{/block} +{block title}Página de inicio{/block} {block content} -

    Homepage

    +

    Página de inicio

    ... {/block} ``` -Define el bloque `content`, que se inserta en lugar de `{include content}` en el diseño, y también redefine el bloque `title`, que sobrescribe `{block title}` en el diseño. Intenta imaginar el resultado. +Esta define el bloque `content`, que se inserta en lugar de `{include content}` en el layout, y también re-define el bloque `title`, que sobrescribe `{block title}` en el layout. Intente imaginar el resultado. -Búsqueda de plantillas .[#toc-search-for-templates] ---------------------------------------------------- +Búsqueda de plantillas +---------------------- -La ruta a las plantillas se deduce según una lógica simple. Se intenta ver si uno de estos archivos de plantilla existe en relación con el directorio donde se encuentra la clase de presentador, donde `` es el nombre del presentador actual y `` es el nombre de la acción actual: +No necesita indicar en los presenters qué plantilla se debe renderizar, el framework deduce la ruta por sí mismo y le ahorra escribir. -- `templates//.latte` -- `templates/..latte` +Si utiliza una estructura de directorios donde cada presenter tiene su propio directorio, simplemente coloque la plantilla en este directorio bajo el nombre de la acción (resp. vista), es decir, para la acción `default` use la plantilla `default.latte`: -Si no encuentra la plantilla, la respuesta es [error 404 |presenters#Error 404 etc.]. +/--pre +app/ +└── Presentation/ + └── Home/ + ├── HomePresenter.php + └── default.latte +\-- -También puede cambiar la vista utilizando `$this->setView('otherView')`. O, en lugar de buscar, especifique directamente el nombre del archivo de plantilla utilizando `$this->template->setFile('/path/to/template.latte')`. +Si utiliza una estructura donde los presenters están juntos en un directorio y las plantillas en la carpeta `templates`, guárdela ya sea en el archivo `..latte` o `/.latte`: + +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── Home.default.latte ← 1ª variante + └── Home/ + └── default.latte ← 2ª variante +\-- + +El directorio `templates` también puede estar ubicado un nivel más arriba, es decir, al mismo nivel que el directorio con las clases de los presenters. + +Si no se encuentra la plantilla, el presenter responde con un [error 404 - página no encontrada |presenters#Error 404 y cía]. + +Puede cambiar la vista usando `$this->setView('otraVista')`. También puede especificar directamente el archivo de plantilla usando `$this->template->setFile('/ruta/a/plantilla.latte')`. .[note] -Puede cambiar las rutas donde se buscan las plantillas anulando el método [formatTemplateFiles |api:Nette\Application\UI\Presenter::formatTemplateFiles()], que devuelve una matriz de posibles rutas de archivo. +Los archivos donde se buscan las plantillas se pueden cambiar sobrescribiendo el método [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], que devuelve un array de posibles nombres de archivo. + + +Búsqueda de la plantilla de layout +---------------------------------- + +Nette también localiza automáticamente el archivo de layout. + +Si utiliza una estructura de directorios donde cada presenter tiene su propio directorio, coloque el layout ya sea en la carpeta con el presenter, si es específico solo para él, o un nivel más arriba, si es común para varios presenters: + +/--pre +app/ +└── Presentation/ + ├── @layout.latte ← layout común + └── Home/ + ├── @layout.latte ← solo para el presenter Home + ├── HomePresenter.php + └── default.latte +\-- + +Si utiliza una estructura donde los presenters están juntos en un directorio y las plantillas en la carpeta `templates`, se esperará el layout en estos lugares: -El diseño se espera en los siguientes archivos: +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── @layout.latte ← layout común + ├── Home.@layout.latte ← solo para Home, 1ª variante + └── Home/ + └── @layout.latte ← solo para Home, 2ª variante +\-- -- `templates//@.latte` -- `templates/.@.latte` -- `templates/@.latte` diseño común a varios presentadores +Si el presenter se encuentra en un módulo, también se buscará en niveles de directorio superiores, según el anidamiento del módulo. -`` es el nombre del presentador actual y `` es el nombre de la maquetación, que por defecto es `'layout'`. El nombre puede cambiarse con `$this->setLayout('otherLayout')`, de modo que se intentarán los archivos `@otherLayout.latte`. +El nombre del layout se puede cambiar usando `$this->setLayout('layoutAdmin')` y entonces se esperará en el archivo `@layoutAdmin.latte`. También se puede especificar directamente el archivo de plantilla de layout usando `$this->setLayout('/ruta/a/plantilla.latte')`. -También puede especificar directamente el nombre de archivo de la plantilla de maquetación con `$this->setLayout('/path/to/template.latte')`. El uso de `$this->setLayout(false)` desactivará la búsqueda de diseños. +Usando `$this->setLayout(false)` o la etiqueta `{layout none}` dentro de la plantilla, se desactiva la búsqueda de layout. .[note] -Puede cambiar las rutas donde se buscan las plantillas anulando el método [formatLayoutTemplateFiles |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], que devuelve una matriz de posibles rutas de archivo. +Los archivos donde se buscan las plantillas de layout se pueden cambiar sobrescribiendo el método [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], que devuelve un array de posibles nombres de archivo. -Variables en la plantilla .[#toc-variables-in-the-template] ------------------------------------------------------------ +Variables en la plantilla +------------------------- -Las variables se pasan a la plantilla escribiéndolas en `$this->template` y luego están disponibles en la plantilla como variables locales: +Pasamos variables a la plantilla escribiéndolas en `$this->template` y luego las tenemos disponibles en la plantilla como variables locales: ```php $this->template->article = $this->articles->getById($id); ``` -De esta forma podemos pasar fácilmente cualquier variable a las plantillas. Sin embargo, cuando desarrollamos aplicaciones robustas, a menudo es más útil limitarnos. Por ejemplo, definiendo explícitamente una lista de variables que la plantilla espera y sus tipos. Esto permitirá a PHP comprobar los tipos, al IDE autocompletar correctamente, y al análisis estático detectar errores. +De esta manera simple podemos pasar cualquier variable a las plantillas. Sin embargo, en el desarrollo de aplicaciones robustas, suele ser más útil limitarse. Por ejemplo, definiendo explícitamente la lista de variables que espera la plantilla y sus tipos. Gracias a esto, PHP podrá verificar los tipos, el IDE sugerir correctamente y el análisis estático detectar errores. -¿Y cómo definimos tal enumeración? Simplemente en forma de una clase y sus propiedades. La nombramos de forma similar a presenter, pero con `Template` al final: +¿Y cómo definimos tal lista? Simplemente en forma de una clase y sus propiedades. La nombramos de manera similar al presenter, solo que con `Template` al final: ```php /** @@ -93,22 +141,22 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template } ``` -El objeto `$this->template` en el presentador será ahora una instancia de la clase `ArticleTemplate`. Así que PHP comprobará los tipos declarados cuando se escriban. Y a partir de PHP 8.2 también advertirá sobre la escritura en una variable no existente, en versiones anteriores se puede lograr lo mismo usando el rasgo [Nette\SmartObject |utils:smartobject]. +El objeto `$this->template` en el presenter será ahora una instancia de la clase `ArticleTemplate`. Así que PHP verificará los tipos declarados al escribir. Y a partir de la versión PHP 8.2 advertirá también sobre la escritura en una variable inexistente, en versiones anteriores se puede lograr lo mismo usando el trait [Nette\SmartObject |utils:smartobject]. -La anotación `@property-read` es para IDE y análisis estático, hará que funcione el autocompletado, vea "PhpStorm y el completado de código para $this->template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. +La anotación `@property-read` está destinada al IDE y al análisis estático, gracias a ella funcionará la sugerencia, ver [PhpStorm y autocompletado de código para $this⁠-⁠>⁠template |https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template]. [* phpstorm-completion.webp *] -Puedes permitirte el lujo de susurrar en las plantillas también, simplemente instala el plugin Latte en PhpStorm y especifica el nombre de la clase al principio de la plantilla, ver el artículo "Latte: cómo escribir sistema:https://blog.nette.org/es/latte-como-utilizar-el-sistema-de-tipos": +Puede disfrutar del lujo de la sugerencia también en las plantillas, basta con instalar el plugin para Latte en PhpStorm e indicar al principio de la plantilla el nombre de la clase, más en el artículo [Latte: cómo usar el sistema de tipos |https://blog.nette.org/es/latte-how-to-use-type-system]: ```latte -{templateType App\Presenters\ArticleTemplate} +{templateType App\Presentation\Article\ArticleTemplate} ... ``` -Así es también como funcionan las plantillas en los componentes, sólo tienes que seguir la convención de nomenclatura y crear una clase de plantilla `FifteenTemplate` para el componente, por ejemplo `FifteenControl`. +Así funcionan también las plantillas en los componentes, basta con seguir la convención de nombres y para un componente, p. ej., `FifteenControl` crear una clase de plantilla `FifteenTemplate`. -Si necesitas crear un `$template` como una instancia de otra clase, utiliza el método `createTemplate()`: +Si necesita crear `$template` como una instancia de otra clase, utilice el método `createTemplate()`: ```php public function renderDefault(): void @@ -121,60 +169,60 @@ public function renderDefault(): void ``` -Variables por defecto .[#toc-default-variables] ------------------------------------------------ +Variables predeterminadas +------------------------- -Los presentadores y componentes pasan varias variables útiles a las plantillas de forma automática: +Los presenters y componentes pasan automáticamente varias variables útiles a las plantillas: -- `$basePath` es una ruta URL absoluta al directorio raíz (por ejemplo `/CD-collection`) -- `$baseUrl` es una URL absoluta al directorio raíz (por ejemplo `http://localhost/CD-collection`) -- `$user` es un objeto [que representa al usuario |security:authentication] -- `$presenter` es el presentador actual -- `$control` es el componente o presentador actual -- `$flashes` lista de [mensajes |presenters#flash-messages] enviados por el método `flashMessage()` +- `$basePath` es la ruta URL absoluta al directorio raíz (p. ej., `/eshop`) +- `$baseUrl` es la URL absoluta al directorio raíz (p. ej., `http://localhost/eshop`) +- `$user` es el objeto [que representa al usuario |security:authentication] +- `$presenter` es el presenter actual +- `$control` es el componente o presenter actual +- `$flashes` array de [mensajes |presenters#Mensajes flash] enviados por la función `flashMessage()` -Si utilizas una clase de plantilla personalizada, estas variables se pasan si creas una propiedad para ellas. +Si utiliza su propia clase de plantilla, estas variables se pasarán si crea una propiedad para ellas. -Creación de enlaces .[#toc-creating-links] ------------------------------------------- +Creación de enlaces +------------------- -En la plantilla creamos enlaces a otros presentadores y acciones de la siguiente manera: +En la plantilla, se crean enlaces a otros presenters y acciones de esta manera: ```latte -detail +detalle del producto ``` -Atributo `n:href` es muy útil para etiquetas HTML ``. Si queremos imprimir el enlace en otro lugar, por ejemplo en el texto, utilizamos `{link}`: +El atributo `n:href` es muy útil para las etiquetas HTML ``. Si queremos mostrar el enlace en otro lugar, por ejemplo en el texto, usamos `{link}`: ```latte -URL is: {link Home:default} +La dirección es: {link Home:default} ``` -Para más información, véase [Creación de enlaces |Creating Links]. +Encontrará más información en el capítulo [Creación de enlaces URL|creating-links]. -Filtros personalizados, etiquetas, etc. .[#toc-custom-filters-tags-etc] ------------------------------------------------------------------------ +Filtros personalizados, etiquetas, etc. +--------------------------------------- -El sistema de plantillas Latte puede ampliarse con filtros personalizados, funciones, etiquetas, etc. Esto puede hacerse directamente en el método `render` o en el método `beforeRender()`: +El sistema de plantillas Latte se puede extender con filtros, funciones, etiquetas, etc. personalizados. Se puede hacer directamente en el método `render` o `beforeRender()`: ```php public function beforeRender(): void { - // adding a filter + // agregar filtro $this->template->addFilter('foo', /* ... */); - // or configure the Latte\Engine object directly + // o configuramos directamente el objeto Latte\Engine $latte = $this->template->getLatte(); $latte->addFilterLoader(/* ... */); } ``` -La versión 3 de Latte ofrece una forma más avanzada creando una [extensión |latte:creating-extension] para cada proyecto web. He aquí un ejemplo aproximado de una clase de este tipo: +Latte en la versión 3 ofrece una forma más avanzada y es crear una [extension |latte:extending-latte#Latte Extension] para cada proyecto web. Un ejemplo fragmentario de tal clase: ```php -namespace App\Templating; +namespace App\Presentation\Accessory; final class LatteExtension extends Latte\Extension { @@ -207,22 +255,21 @@ final class LatteExtension extends Latte\Extension } ``` -La registramos usando [configuration|configuration#Latte]: +La registramos usando la [configuración |configuration#Plantillas Latte]: ```neon latte: extensions: - - App\Templating\LatteExtension + - App\Presentation\Accessory\LatteExtension ``` -Traducir .[#toc-translating] ----------------------------- +Traducción +---------- -Si estás programando una aplicación multilingüe, es probable que necesites mostrar parte del texto de la plantilla en diferentes idiomas. Para ello, Nette Framework define una interfaz de traducción [api:Nette\Localization\Translator], que tiene un único método `translate()`. Éste acepta el mensaje `$message`, que normalmente es una cadena, y cualquier otro parámetro. La tarea consiste en devolver la cadena traducida. -No existe una implementación por defecto en Nette, puede elegir según sus necesidades entre varias soluciones ya preparadas que puede encontrar en [Componette |https://componette.org/search/localization]. Su documentación le indica cómo configurar el traductor. +Si programa una aplicación multilingüe, probablemente necesitará mostrar algunos textos en la plantilla en diferentes idiomas. Nette Framework define para este propósito una interfaz para la traducción [api:Nette\Localization\Translator], que tiene un único método `translate()`. Este recibe el mensaje `$message`, que generalmente suele ser una cadena, y cualquier otro parámetro. La tarea es devolver la cadena traducida. En Nette no hay ninguna implementación predeterminada, puede elegir según sus necesidades entre varias soluciones listas que encontrará en [Componette |https://componette.org/search/localization]. En su documentación aprenderá cómo configurar el traductor. -Las plantillas se pueden configurar con un traductor, que [nos hab |dependency-injection:passing-dependencies]rán pasado, utilizando el método `setTranslator()`: +A las plantillas se les puede establecer un traductor, que [pasamos |dependency-injection:passing-dependencies], con el método `setTranslator()`: ```php protected function beforeRender(): void @@ -232,38 +279,38 @@ protected function beforeRender(): void } ``` -Alternativamente, el traductor se puede establecer utilizando la [configuración |configuration#Latte]: +El traductor alternativamente se puede establecer mediante la [configuración |configuration#Plantillas Latte]: ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -El traductor puede utilizarse, por ejemplo, como un filtro `|translate`, con parámetros adicionales pasados al método `translate()` (véase `foo, bar`): +Luego, el traductor se puede usar, por ejemplo, como filtro `|translate`, incluyendo parámetros complementarios que se pasan al método `translate()` (ver `foo, bar`): ```latte -{='Basket'|translate} +{='Carrito'|translate} {$item|translate} {$item|translate, foo, bar} ``` -O como una etiqueta de subrayado: +O como etiqueta de guion bajo: ```latte -{_'Basket'} +{_'Carrito'} {_$item} {_$item, foo, bar} ``` -Para la traducción de secciones de plantillas, existe una etiqueta emparejada `{translate}` (desde Latte 2.11, antes se utilizaba la etiqueta `{_}` ): +Para traducir una sección de la plantilla, existe una etiqueta par `{translate}` (desde Latte 2.11, antes se usaba la etiqueta `{_}`): ```latte -{translate}Order{/translate} -{translate foo, bar}Order{/translate} +{translate}Pedido{/translate} +{translate foo, bar}Pedido{/translate} ``` -Translator se llama por defecto en tiempo de ejecución al renderizar la plantilla. La versión 3 de Latte, sin embargo, puede traducir todo el texto estático durante la compilación de la plantilla. Esto ahorra rendimiento porque cada cadena se traduce sólo una vez y la traducción resultante se escribe en el formulario compilado. Esto crea múltiples versiones compiladas de la plantilla en el directorio caché, una para cada idioma. Para ello, sólo tiene que especificar el idioma como segundo parámetro: +El traductor se llama estándarmente en tiempo de ejecución al renderizar la plantilla. Sin embargo, Latte versión 3 puede traducir todos los textos estáticos ya durante la compilación de la plantilla. Esto ahorra rendimiento, porque cada cadena se traduce solo una vez y la traducción resultante se escribe en la forma compilada. En el directorio de caché se crean así múltiples versiones compiladas de la plantilla, una para cada idioma. Para ello basta con indicar el idioma como segundo parámetro: ```php protected function beforeRender(): void @@ -273,4 +320,4 @@ protected function beforeRender(): void } ``` -Por texto estático entendemos, por ejemplo, `{_'hello'}` o `{translate}hello{/translate}`. El texto no estático, como `{_$foo}`, seguirá compilándose sobre la marcha. +Por texto estático se entiende, por ejemplo, `{_'hello'}` o `{translate}hello{/translate}`. Los textos no estáticos, como por ejemplo `{_$foo}`, seguirán traduciéndose en tiempo de ejecución. diff --git a/application/fr/@home.texy b/application/fr/@home.texy index f0388b9636..2548bf3db7 100644 --- a/application/fr/@home.texy +++ b/application/fr/@home.texy @@ -1,36 +1,85 @@ -Application Nette +Nette Application ***************** .[perex] -Le paquet `nette/application` est la base de la création d'applications web interactives. - -- [Comment fonctionnent les applications ? |how-it-works] -- [Bootstrap] -- [Présentateurs |Presenters] -- [Modèles |Templates] -- [Modules] -- [Acheminement |Routing] -- [Création de liens URL |creating-links] -- [Composants interactifs |components] -- [AJAX et Snippets |ajax] -- [Multiplicateur |multiplier] -- [Configuration] +Nette Application est le cœur du framework Nette, offrant des outils puissants pour créer des applications web modernes. Il propose un certain nombre de caractéristiques exceptionnelles qui facilitent considérablement le développement et améliorent la sécurité ainsi que la maintenabilité du code. Installation ------------ -Téléchargez et installez le paquet en utilisant [Composer |best-practices:composer]: +La bibliothèque peut être téléchargée et installée en utilisant l'outil [Composer|best-practices:composer] : ```shell composer require nette/application ``` -| version | compatible avec PHP + +Pourquoi choisir Nette Application ? +------------------------------------ + +Nette a toujours été un pionnier dans le domaine des technologies web. + +**Routeur bidirectionnel :** Nette dispose d'un système de routage avancé, unique par sa bidirectionnalité - non seulement il traduit les URL en actions de l'application, mais il peut également générer des adresses URL en retour. Cela signifie que : +- Vous pouvez changer à tout moment la structure des URL de toute l'application sans avoir à modifier les templates +- Les URL sont automatiquement canonisées, ce qui améliore le SEO +- Le routage est défini à un seul endroit, plutôt que dispersé dans les annotations + +**Composants et signaux :** Le système de composants intégré, inspiré de Delphi et React.js, est tout à fait exceptionnel parmi les frameworks PHP : +- Permet de créer des éléments d'interface utilisateur réutilisables +- Prend en charge la composition hiérarchique des composants +- Offre un traitement élégant des requêtes AJAX en utilisant des signaux +- Riche bibliothèque de composants prêts à l'emploi sur [Componette](https://componette.org) + +**AJAX et snippets :** Nette a introduit une manière révolutionnaire de travailler avec AJAX dès 2009, bien avant des solutions similaires comme Hotwire pour Ruby on Rails ou Symfony UX Turbo : +- Les snippets permettent de mettre à jour seulement des parties de la page sans avoir besoin d'écrire du JavaScript +- Intégration automatique avec le système de composants +- Invalidation intelligente des parties de pages +- Quantité minimale de données transférées + +**Templates intuitifs [Latte|latte:] :** Le système de templates le plus sûr pour PHP avec des fonctionnalités avancées : +- Protection automatique contre XSS avec échappement sensible au contexte +- Extensibilité grâce à des filtres, fonctions et balises personnalisés +- Héritage de templates et snippets pour AJAX +- Excellent support de PHP 8.x avec le système de types + +**Dependency Injection :** Nette utilise pleinement l'Injection de Dépendances : +- Passage automatique des dépendances (autowiring) +- Configuration en utilisant le format clair NEON +- Support pour les factories de composants + + +Principaux avantages +-------------------- + +- **Sécurité** : Défense automatique contre [les vulnérabilités|nette:vulnerability-protection] telles que XSS, CSRF, etc. +- **Productivité** : Moins de code, plus de fonctionnalités grâce à une conception intelligente +- **Débogage** : [Débogueur Tracy |tracy:] avec le panneau de routage +- **Performance** : Cache intelligent, chargement différé (lazy loading) des composants +- **Flexibilité** : Modification facile des URL même après la finalisation de l'application +- **Composants** : Système unique d'éléments d'interface utilisateur réutilisables +- **Moderne** : Support complet de PHP 8.4+ et du système de types + + +Pour commencer +-------------- + +1. [Comment fonctionnent les applications ? |how-it-works] - Comprendre l'architecture de base +2. [Presenters |presenters] - Travailler avec les presenters et les actions +3. [Templates |templates] - Création de templates en Latte +4. [Routage |routing] - Configuration des adresses URL +5. [Composants interactifs |components] - Utilisation du système de composants + + +Compatibilité avec PHP +---------------------- + +| version | compatible avec PHP |-----------|------------------- -| Nette Application 4.0 | PHP 8.0 - 8.2 -| Nette Application 3.1 | PHP 7.2 - 8.2 -| Nette Application 3.0 | PHP 7.1 - 8.0 -| Nette Application 2.4 | PHP 5.6 - 8.0 +| Nette Application 4.0 | PHP 8.1 – 8.4 +| Nette Application 3.2 | PHP 8.1 – 8.4 +| Nette Application 3.1 | PHP 7.2 – 8.3 +| Nette Application 3.0 | PHP 7.1 – 8.0 +| Nette Application 2.4 | PHP 5.6 – 8.0 -S'applique aux dernières versions du patch. +S'applique à la dernière version patch. diff --git a/application/fr/@left-menu.texy b/application/fr/@left-menu.texy index 93d1d53128..3d59487c72 100644 --- a/application/fr/@left-menu.texy +++ b/application/fr/@left-menu.texy @@ -1,19 +1,22 @@ -Application Nette +Nette Application ***************** - [Comment fonctionnent les applications ? |how-it-works] -- [Bootstrap] -- [Présentateurs |Presenters] -- [Modèles |Templates] -- [Modules] -- [Acheminement |Routing] +- [Bootstrapping] +- [Presenters |presenters] +- [Templates |templates] +- [Structure des répertoires |directory-structure] +- [Routage |routing] - [Création de liens URL |creating-links] - [Composants interactifs |components] -- [AJAX et Snippets |ajax] -- [Multiplicateur |multiplier] -- [Configuration] +- [AJAX & snippets |ajax] +- [Multiplier |multiplier] +- [Configuration |configuration] -Autres lectures -*************** -- [Meilleures pratiques |best-practices:] -- [Dépannage |nette:troubleshooting] +Lectures complémentaires +************************ +- [Pourquoi utiliser Nette ? |www:10-reasons-why-nette] +- [Installation |nette:installation] +- [Écrivons notre première application ! |quickstart:] +- [Tutoriels et bonnes pratiques |best-practices:] +- [Résolution de problèmes |nette:troubleshooting] diff --git a/application/fr/@meta.texy b/application/fr/@meta.texy new file mode 100644 index 0000000000..72ae4b8db8 --- /dev/null +++ b/application/fr/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentation Nette}} diff --git a/application/fr/ajax.texy b/application/fr/ajax.texy index 06f9ffb892..28fb3799a8 100644 --- a/application/fr/ajax.texy +++ b/application/fr/ajax.texy @@ -1,38 +1,45 @@ -AJAX et Snippets -**************** +AJAX & Snippets +***************
    -Les applications web modernes fonctionnent aujourd'hui pour moitié sur un serveur et pour moitié dans un navigateur. AJAX est un facteur d'unité essentiel. Quel est le support offert par le Nette Framework ? -- l'envoi de fragments de modèles (appelés *snippets*) -- le passage de variables entre PHP et JavaScript -- débogage des applications AJAX +À l'ère des applications web modernes, où la fonctionnalité est souvent répartie entre le serveur et le navigateur, AJAX est un élément de liaison essentiel. Quelles possibilités Nette Framework nous offre-t-il dans ce domaine ? +- envoi de parties de template, appelées snippets +- transmission de variables entre PHP et JavaScript +- outils pour le débogage des requêtes AJAX
    -Une requête AJAX peut être détectée à l'aide d'une méthode d'un service [encapsulant une requête HTTP |http:request] `$httpRequest->isAjax()` (détecte sur la base de l'en-tête HTTP `X-Requested-With` ). Il existe également une méthode abrégée dans Presenter : `$this->isAjax()`. -Une demande AJAX n'est pas différente d'une demande normale - un présentateur est appelé avec une certaine vue et des paramètres. La réaction du présentateur est également libre : il peut utiliser ses routines pour renvoyer un fragment de code HTML (un snippet), un document XML, un objet JSON ou un morceau de code Javascript. +Requête AJAX +============ -Il existe un objet prétraité appelé `payload` dédié à l'envoi de données au navigateur en JSON. +Une requête AJAX ne diffère fondamentalement pas d'une requête HTTP classique. Un presenter est appelé avec certains paramètres. Et c'est au presenter de décider comment réagir à la requête - il peut retourner des données au format JSON, envoyer une partie du code HTML, un document XML, etc. -```php -public function actionDelete(int $id): void -{ - if ($this->isAjax()) { - $this->payload->message = 'Success'; - } - // ... -} +Côté navigateur, nous initialisons la requête AJAX à l'aide de la fonction `fetch()` : + +```js +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) +.then(response => response.json()) +.then(payload => { + // traitement de la réponse +}); ``` -Pour un contrôle total de votre sortie JSON, utilisez la méthode `sendJson` dans votre présentateur. Elle met immédiatement fin au présentateur et vous vous passerez de modèle : +Côté serveur, nous reconnaissons une requête AJAX avec la méthode `$httpRequest->isAjax()` du service [encapsulant la requête HTTP |http:request]. Pour la détection, il utilise l'en-tête HTTP `X-Requested-With`, il est donc important de l'envoyer. Au sein du presenter, vous pouvez utiliser la méthode `$this->isAjax()`. + +Si vous souhaitez envoyer des données au format JSON, utilisez la méthode [`sendJson()` |presenters#Envoi de la réponse]. La méthode termine également l'activité du presenter. ```php -$this->sendJson(['key' => 'value', /* ... */]); +public function actionExport(): void +{ + $this->sendJson($this->model->getData); +} ``` -Si nous voulons envoyer du HTML, nous pouvons soit définir un modèle spécial pour les demandes AJAX : +Si vous prévoyez de répondre en utilisant un template spécial conçu pour AJAX, vous pouvez le faire comme suit : ```php public function handleClick($param): void @@ -45,222 +52,198 @@ public function handleClick($param): void ``` -Naja .[#toc-naja] -================= +Snippets +======== + +Le moyen le plus puissant offert par Nette pour connecter le serveur et le client sont les snippets. Grâce à eux, vous pouvez transformer une application ordinaire en une application AJAX avec un minimum d'effort et quelques lignes de code. Le fonctionnement est démontré par l'exemple Fifteen, dont le code se trouve sur [GitHub |https://github.com/nette-examples/fifteen]. + +Les snippets, ou extraits, permettent de mettre à jour uniquement des parties de la page, au lieu de recharger la page entière. C'est non seulement plus rapide et plus efficace, mais cela offre également une expérience utilisateur plus confortable. Les snippets peuvent vous rappeler Hotwire pour Ruby on Rails ou Symfony UX Turbo. Il est intéressant de noter que Nette a introduit les snippets 14 ans plus tôt. + +Comment fonctionnent les snippets ? Lors du premier chargement de la page (requête non-AJAX), la page entière est chargée, y compris tous les snippets. Lorsque l'utilisateur interagit avec la page (par exemple, clique sur un bouton, soumet un formulaire, etc.), une requête AJAX est déclenchée au lieu de charger la page entière. Le code dans le presenter exécute l'action et décide quels snippets doivent être mis à jour. Nette rend ces snippets et les envoie sous forme de tableau au format JSON. Le code de gestion dans le navigateur réinsère les snippets reçus dans la page. Ainsi, seul le code des snippets modifiés est transféré, ce qui économise de la bande passante et accélère le chargement par rapport au transfert du contenu de la page entière. + -La [bibliothèque Naja |https://naja.js.org] est utilisée pour gérer les requêtes AJAX du côté du navigateur. [Installez-la |https://naja.js.org/#/guide/01-install-setup-naja] en tant que paquet node.js (à utiliser avec Webpack, Rollup, Vite, Parcel et plus) : +Naja +---- + +Pour gérer les snippets côté navigateur, la [bibliothèque Naja |https://naja.js.org] est utilisée. [Installez-la |https://naja.js.org/#/guide/01-install-setup-naja] en tant que paquet node.js (pour une utilisation avec des applications comme Webpack, Rollup, Vite, Parcel, et autres) : ```shell npm install naja ``` -...ou insérez-la directement dans le modèle de page : +…ou insérez-la directement dans le template de la page : ```html ``` +Tout d'abord, la bibliothèque doit être [initialisée |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] : -Extraits de texte .[#toc-snippets] -================================== +```js +naja.initialize(); +``` -Il existe un outil bien plus puissant que le support AJAX intégré : les snippets. Leur utilisation permet de transformer une application ordinaire en une application AJAX en utilisant seulement quelques lignes de code. La façon dont tout cela fonctionne est démontrée dans l'exemple Fifteen dont le code est également accessible dans le build ou sur [GitHub |https://github.com/nette-examples/fifteen]. +Pour transformer un lien ordinaire (signal) ou une soumission de formulaire en une requête AJAX, il suffit de marquer le lien, le formulaire ou le bouton correspondant avec la classe `ajax` : -Le fonctionnement des snippets est le suivant : la page entière est transférée lors de la requête initiale (c'est-à-dire non-AJAX), puis à chaque [sous-requête |components#signal] AJAX (requête de la même vue du même présentateur), seul le code des parties modifiées est transféré dans le dépôt `payload` mentionné précédemment. +```html +Go -Les snippets vous rappellent peut-être Hotwire pour Ruby on Rails ou Symfony UX Turbo, mais Nette les a inventés quatorze ans plus tôt. +
    + +
    +ou -Invalidation des Snippets .[#toc-invalidation-of-snippets] -========================================================== +
    + +
    +``` -Chaque descendant de la classe [Control |components] (ce qu'est aussi un Presenter) est capable de se souvenir si des changements sont intervenus au cours d'une requête qui nécessitent un nouveau rendu. Il existe une paire de méthodes pour gérer cela : `redrawControl()` et `isControlInvalid()`. Un exemple : + +Redessiner les Snippets +----------------------- + +Chaque objet de la classe [Control |components] (y compris le Presenter lui-même) enregistre si des changements nécessitant son redessin se sont produits. La méthode `redrawControl()` est utilisée pour cela : ```php public function handleLogin(string $user): void { - // L'objet doit être rendu à nouveau après que l'utilisateur se soit connecté. + // après la connexion, la partie pertinente doit être redessinée $this->redrawControl(); // ... } ``` -Nette offre cependant une résolution encore plus fine que les composants entiers. Les méthodes listées acceptent le nom d'un "snippet" comme paramètre optionnel. Un "snippet" est en fait un élément de votre modèle marqué à cet effet par une tag Latte, nous y reviendrons plus tard. Il est donc possible de demander à un composant de ne redessiner que des *parties* de son modèle. Si le composant entier est invalidé, tous ses snippets sont redessinés. Un composant est également "invalide" si l'un de ses sous-composants est invalide. - -```php -$this->isControlInvalid(); // -> false -$this->redrawControl('header'); // invalide le snippet nommé 'header'. -$this->isControlInvalid('header'); // -> true -$this->isControlInvalid('footer'); // -> false -$this->isControlInvalid(); // -> true, au moins un extrait est invalide. +Nette permet un contrôle encore plus fin de ce qui doit être redessiné. En effet, la méthode mentionnée peut accepter le nom du snippet comme argument. Il est donc possible d'invalider (c'est-à-dire : forcer le redessin) au niveau des parties du template. Si le composant entier est invalidé, chacun de ses snippets sera également redessiné : -$this->redrawControl(); // invalide l'ensemble du composant, chaque extrait. -$this->isControlInvalid('footer'); // -> true +```php +// invalide le snippet 'header' +$this->redrawControl('header'); ``` -Un composant qui reçoit un signal est automatiquement marqué pour être redessiné. - -Grâce au redessin de snippet, nous savons exactement quelles parties de quels éléments doivent être redessinées. - - -Balise `{snippet} … {/snippet}` .{toc: Tag snippet} -=================================================== -Le rendu de la page se déroule de manière très similaire à une requête ordinaire : les mêmes modèles sont chargés, etc. L'essentiel est toutefois de laisser de côté les parties qui ne sont pas censées atteindre la sortie ; les autres parties doivent être associées à un identifiant et envoyées à l'utilisateur dans un format compréhensible pour un gestionnaire JavaScript. +Snippets dans Latte +------------------- - -Syntaxe .[#toc-syntax] ----------------------- - -Si le modèle contient un contrôle ou un extrait, nous devons l'envelopper à l'aide de la balise de paire `{snippet} ... {/snippet}`. Elle veillera à ce que l'extrait rendu soit "découpé" et envoyé au navigateur. Elle l'enfermera également dans une balise auxiliaire `
    ` (il est possible d'en utiliser une autre). Dans l'exemple suivant, un extrait nommé `header` est défini. Il peut tout aussi bien représenter le modèle d'un composant : +L'utilisation des snippets dans Latte est extrêmement facile. Pour définir une partie du template comme snippet, enveloppez-la simplement avec les balises `{snippet}` et `{/snippet}` : ```latte {snippet header} -

    Hello ...

    +

    Bonjour ...

    {/snippet} ``` -Si vous souhaitez créer un snippet avec un élément contenant différent de `
    ` ou ajouter des attributs personnalisés à l'élément, vous pouvez utiliser la définition suivante : +Le snippet crée un élément `
    ` dans la page HTML avec un `id` spécial généré. Lors du redessin du snippet, le contenu de cet élément est mis à jour. Il est donc nécessaire que lors du rendu initial de la page, tous les snippets soient également rendus, même s'ils peuvent être vides au début. + +Vous pouvez également créer un snippet avec un élément autre que `
    ` en utilisant un n:attribut : ```latte
    -

    Hello ...

    +

    Bonjour ...

    ``` -Dynamic Snippets .[#toc-dynamic-snippets] -========================================= +Zones de Snippets +----------------- -Dans Nette, vous pouvez également définir des snippets avec un nom dynamique basé sur un paramètre d'exécution. C'est la solution la plus appropriée pour les listes diverses où nous devons modifier une seule ligne mais où nous ne voulons pas transférer toute la liste avec elle. Un exemple de ceci serait : +Les noms des snippets peuvent aussi être des expressions : ```latte -
      - {foreach $list as $id => $item} -
    • {$item} update
    • - {/foreach} -
    +{foreach $items as $id => $item} +
  • {$item}
  • +{/foreach} ``` -Il y a un snippet statique appelé `itemsContainer`, contenant plusieurs snippets dynamiques : `item-0`, `item-1` et ainsi de suite. +Cela crée plusieurs snippets `item-0`, `item-1`, etc. Si nous invalidions directement un snippet dynamique (par exemple `item-1`), rien ne serait redessiné. La raison est que les snippets fonctionnent vraiment comme des extraits et ne sont rendus qu'eux-mêmes directement. Cependant, il n'y a en fait aucun snippet nommé `item-1` dans le template. Il n'est créé que lors de l'exécution du code autour du snippet, c'est-à-dire la boucle foreach. Nous marquons donc la partie du template qui doit être exécutée à l'aide de la balise `{snippetArea}` : -Vous ne pouvez pas redessiner un extrait dynamique directement (redessiner `item-1` n'a aucun effet), vous devez redessiner son extrait parent (dans cet exemple `itemsContainer`). Le code du snippet parent est alors exécuté, mais seuls ses sous-snippets sont envoyés au navigateur. Si vous souhaitez n'envoyer qu'un seul des sous-ensembles, vous devez modifier l'entrée du snippet parent pour ne pas générer les autres sous-ensembles. +```latte +
      + {foreach $items as $id => $item} +
    • {$item}
    • + {/foreach} +
    +``` -Dans l'exemple ci-dessus, vous devez vous assurer que, pour une requête AJAX, un seul élément sera ajouté au tableau `$list`. Par conséquent, la boucle `foreach` n'imprimera qu'un seul extrait dynamique. +Et nous laissons redessiner à la fois le snippet lui-même et toute la zone parente : ```php -class HomePresenter extends Nette\Application\UI\Presenter -{ - /** - * This method returns data for the list. - * Usually this would just request the data from a model. - * For the purpose of this example, the data is hard-coded. - */ - private function getTheWholeList(): array - { - return [ - 'First', - 'Second', - 'Third', - ]; - } - - public function renderDefault(): void - { - if (!isset($this->template->list)) { - $this->template->list = $this->getTheWholeList(); - } - } - - public function handleUpdate(int $id): void - { - $this->template->list = $this->isAjax() - ? [] - : $this->getTheWholeList(); - $this->template->list[$id] = 'Updated item'; - $this->redrawControl('itemsContainer'); - } -} +$this->redrawControl('itemsContainer'); +$this->redrawControl('item-1'); ``` +En même temps, il est conseillé de s'assurer que le tableau `$items` ne contient que les éléments qui doivent être redessinés. -Extraits dans un modèle inclus .[#toc-snippets-in-an-included-template] -======================================================================= - -Il peut arriver que le snippet se trouve dans un modèle qui est inclus à partir d'un autre modèle. Dans ce cas, nous devons envelopper le code d'inclusion dans le second modèle avec la tag `snippetArea`, puis nous redessinons à la fois la snippetArea et le snippet lui-même. - -La tag `snippetArea` garantit que le code qu'elle contient est exécuté mais que seul l'extrait réel du modèle inclus est envoyé au navigateur. +Si nous insérons un autre template contenant des snippets dans le template principal à l'aide de la balise `{include}`, il est nécessaire d'inclure à nouveau l'insertion du template dans une `snippetArea` et de l'invalider avec le snippet : ```latte -{* parent.latte *} -{snippetArea wrapper} - {include 'child.latte'} +{snippetArea include} + {include 'included.latte'} {/snippetArea} ``` + ```latte -{* child.latte *} +{* included.latte *} {snippet item} -... + ... {/snippet} ``` + ```php -$this->redrawControl('wrapper'); +$this->redrawControl('include'); $this->redrawControl('item'); ``` -Vous pouvez également la combiner avec des extraits dynamiques. - -Ajout et suppression .[#toc-adding-and-deleting] -================================================ +Snippets dans les Composants +---------------------------- -Si vous ajoutez un nouvel élément dans la liste et que vous invalidez `itemsContainer`, la requête AJAX renvoie des extraits incluant le nouvel élément, mais le gestionnaire javascript ne sera pas en mesure de le rendre. Cela est dû au fait qu'il n'y a pas d'élément HTML avec l'ID nouvellement créé. - -Dans ce cas, le moyen le plus simple est d'envelopper toute la liste dans un autre extrait et de l'invalider : +Vous pouvez également créer des snippets dans les [composants|components] et Nette les redessinera automatiquement. Mais il y a une certaine limitation : pour redessiner les snippets, il appelle la méthode `render()` sans paramètres. Par conséquent, la transmission de paramètres dans le template ne fonctionnera pas : ```latte -{snippet wholeList} -
      - {foreach $list as $id => $item} -
    • {$item} update
    • - {/foreach} -
    -{/snippet} -Add +OK +{control productGrid} + +ne fonctionnera pas : +{control productGrid $arg, $arg} +{control productGrid:paginator} ``` + +Envoi de Données Utilisateur +---------------------------- + +Avec les snippets, vous pouvez envoyer n'importe quelles autres données au client. Il suffit de les écrire dans l'objet `payload` : + ```php -public function handleAdd(): void +public function actionDelete(int $id): void { - $this->template->list = $this->getTheWholeList(); - $this->template->list[] = 'New one'; - $this->redrawControl('wholeList'); + // ... + if ($this->isAjax()) { + $this->payload->message = 'Succès'; + } } ``` -Il en va de même pour la suppression d'un élément. Il serait possible d'envoyer un extrait vide, mais les listes peuvent généralement être paginées et il serait compliqué d'implémenter la suppression d'un élément et le chargement d'un autre (qui se trouvait sur une page différente de la liste paginée). - -Envoi de paramètres au composant .[#toc-sending-parameters-to-component] -======================================================================== +Transmission de Paramètres +========================== -Lorsque nous envoyons des paramètres au composant via une requête AJAX, qu'il s'agisse de paramètres de signal ou de paramètres persistants, nous devons fournir leur nom global, qui contient également le nom du composant. Le nom global du paramètre renvoie la méthode `getParameterId()`. +Si nous envoyons des paramètres à un composant via une requête AJAX, qu'il s'agisse de paramètres de signal ou de paramètres persistants, nous devons spécifier leur nom global dans la requête, qui inclut également le nom du composant. Le nom complet du paramètre est retourné par la méthode `getParameterId()`. ```js -$.getJSON( - {link changeCountBasket!}, - { - {$control->getParameterId('id')}: id, - {$control->getParameterId('count')}: count - } -}); +let url = new URL({link //foo!}); +url.searchParams.set({$control->getParameterId('bar')}, bar); + +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) ``` -Et traiter la méthode avec les paramètres correspondants dans le composant. +Et la méthode handle avec les paramètres correspondants dans le composant : ```php -public function handleChangeCountBasket(int $id, int $count): void +public function handleFoo(int $bar): void { - } ``` diff --git a/application/fr/bootstrap.texy b/application/fr/bootstrap.texy deleted file mode 100644 index fbb3566d18..0000000000 --- a/application/fr/bootstrap.texy +++ /dev/null @@ -1,233 +0,0 @@ -Bootstrap -********* - -
    - -Bootstrap est le code de démarrage qui initialise l'environnement, crée un conteneur d'injection de dépendances (DI) et démarre l'application. Nous en discuterons : - -- comment configurer votre application à l'aide de fichiers NEON -- comment gérer les modes production et développement -- comment créer le conteneur DI - -
    - - -Les applications, qu'elles soient basées sur le Web ou sur des scripts en ligne de commande, commencent par une certaine forme d'initialisation de l'environnement. Dans les temps anciens, il pouvait s'agir d'un fichier nommé eg `include.inc.php` qui s'en chargeait, et qui était inclus dans le fichier initial. -Dans les applications Nette modernes, il a été remplacé par la classe `Bootstrap`, qui, en tant que partie intégrante de l'application, se trouve dans le fichier `app/Bootstrap.php`. Il peut ressembler à ceci par exemple : - -```php -use Nette\Bootstrap\Configurator; - -class Bootstrap -{ - public static function boot(): Configurator - { - $appDir = dirname(__DIR__); - $configurator = new Configurator; - //$configurator->setDebugMode('secret@23.75.345.200'); - $configurator->enableTracy($appDir . '/log'); - $configurator->setTempDirectory($appDir . '/temp'); - $configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); - $configurator->addConfig($appDir . '/config/common.neon'); - return $configurator; - } -} -``` - - -index.php .[#toc-index-php] -=========================== - -Dans le cas des applications web, le fichier initial est `index.php`, qui se trouve dans le répertoire public `www/`. Il laisse la classe `Bootstrap` pour initialiser l'environnement et retourne la classe `$configurator` qui crée le conteneur DI. Ensuite, il obtient le service `Application`, qui exécute l'application web : - -```php -// initialisation de l'environnement + obtention de l'objet Configurateur -$configurator = App\Bootstrap::boot(); -// créer un conteneur DI -$container = $configurator->createContainer(); -// Le conteneur DI crée un objet Nette\Application\Application -$application = $container->getByType(Nette\Application\Application::class); -// Démarrage de l'application Nette -$application->run(); -``` - -Comme vous pouvez le constater, la classe [api:Nette\Bootstrap\Configurator], que nous allons maintenant présenter plus en détail, aide à configurer l'environnement et à créer un conteneur d'injection de dépendances (DI). - - -Mode développement et mode production .[#toc-development-vs-production-mode] -============================================================================ - -Nette distingue deux modes de base dans lesquels une requête est exécutée : développement et production. Le mode développement est axé sur le confort maximal du programmeur, Tracy est affiché, le cache est automatiquement mis à jour lors de la modification des modèles ou de la configuration du conteneur DI, etc. Le mode production est axé sur les performances, Tracy ne consigne que les erreurs et les modifications des templates et autres fichiers ne sont pas vérifiées. - -La sélection du mode se fait par autodétection, il n'est donc généralement pas nécessaire de configurer ou de changer quoi que ce soit manuellement. Le mode est développement si l'application est exécutée sur l'hôte local (c'est-à-dire l'adresse IP `127.0.0.1` ou `::1`) et qu'aucun proxy n'est présent (c'est-à-dire son en-tête HTTP). Sinon, elle s'exécute en mode production. - -Si vous souhaitez activer le mode développement dans d'autres cas, par exemple pour les programmeurs accédant depuis une adresse IP spécifique, vous pouvez utiliser `setDebugMode()`: - -```php -$configurator->setDebugMode('23.75.345.200'); // une ou plusieurs adresses IP -``` - -Nous recommandons vivement de combiner une adresse IP avec un cookie. Nous stockerons un jeton secret dans le cookie `nette-debug`, par exemple `secret1234`, et le mode de développement sera activé pour les programmeurs avec cette combinaison d'IP et de cookie. - -```php -$configurator->setDebugMode('secret1234@23.75.345.200'); -``` - -Nous pouvons également désactiver complètement le mode de développement, même pour localhost : - -```php -$configurator->setDebugMode(false); -``` - -Notez que la valeur `true` active le mode développeur par défaut, ce qui ne devrait jamais arriver sur un serveur de production. - - -Outil de débogage Tracy .[#toc-debugging-tool-tracy] -==================================================== - -Pour faciliter le débogage, nous allons activer l'excellent outil [Tracy |tracy:]. En mode développeur, il visualise les erreurs et en mode production, il enregistre les erreurs dans le répertoire spécifié : - -```php -$configurator->enableTracy($appDir . '/log'); -``` - - -Fichiers temporaires .[#toc-temporary-files] -============================================ - -Nette utilise le cache pour le conteneur DI, RobotLoader, les modèles, etc. Il est donc nécessaire de définir le chemin d'accès au répertoire où le cache sera stocké : - -```php -$configurator->setTempDirectory($appDir . '/temp'); -``` - -Sous Linux ou macOS, définissez les [droits d'écriture |nette:troubleshooting#Setting directory permissions] pour les répertoires `log/` et `temp/`. - - -RobotLoader .[#toc-robotloader] -=============================== - -En général, nous voulons charger automatiquement les classes à l'aide de [RobotLoader |robot-loader:], nous devons donc le lancer et le laisser charger les classes du répertoire où se trouve `Bootstrap.php` (c'est-à-dire `__DIR__`) et de tous ses sous-répertoires : - -```php -$configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); -``` - -Une autre solution consiste à utiliser uniquement le chargement automatique de [Composer |best-practices:composer] PSR-4. - - -Fuseau horaire .[#toc-timezone] -=============================== - -Le configurateur vous permet de spécifier un fuseau horaire pour votre application. - -```php -$configurator->setTimeZone('Europe/Prague'); -``` - - -Configuration du conteneur DI .[#toc-di-container-configuration] -================================================================ - -Une partie du processus de démarrage est la création d'un conteneur DI, c'est-à-dire une fabrique d'objets, qui est le cœur de toute l'application. Il s'agit en fait d'une classe PHP générée par Nette et stockée dans un répertoire de cache. La fabrique produit les objets clés de l'application et les fichiers de configuration lui indiquent comment les créer et les configurer, et ainsi nous influençons le comportement de l'application entière. - -Les fichiers de configuration sont généralement écrits au [format NEON |neon:format]. Vous pouvez lire [ce qui peut être configuré ici |nette:configuring]. - -.[tip] -En mode développement, le conteneur est automatiquement mis à jour chaque fois que vous modifiez le code ou les fichiers de configuration. En mode production, il n'est généré qu'une seule fois et les modifications de fichiers ne sont pas vérifiées afin de maximiser les performances. - -Les fichiers de configuration sont chargés à l'aide de `addConfig()`: - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -``` - -La méthode `addConfig()` peut être appelée plusieurs fois pour ajouter plusieurs fichiers. - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -$configurator->addConfig($appDir . '/config/local.neon'); -if (PHP_SAPI === 'cli') { - $configurator->addConfig($appDir . '/config/cli.php'); -} -``` - -Le nom `cli.php` n'est pas une faute de frappe, la configuration peut également être écrite dans un fichier PHP, qui la renvoie sous forme de tableau. - -Alternativement, nous pouvons utiliser la [section`includes` |dependency-injection:configuration#including files] pour charger plus de fichiers de configuration. - -Si des éléments avec les mêmes clés apparaissent dans les fichiers de configuration, ils seront [écrasés ou fusionnés |dependency-injection:configuration#Merging] dans le cas de tableaux. Le dernier fichier inclus a une priorité plus élevée que le précédent. Le fichier dans lequel figure la section `includes` a une priorité plus élevée que les fichiers qui y sont inclus. - - -Paramètres statiques .[#toc-static-parameters] ----------------------------------------------- - -Les paramètres utilisés dans les fichiers de configuration peuvent être définis [dans la section `parameters` |dependency-injection:configuration#parameters] et également transmis (ou écrasés) par la méthode `addStaticParameters()` (qui a un alias `addParameters()`). Il est important que les différentes valeurs des paramètres entraînent la génération de conteneurs DI supplémentaires, c'est-à-dire de classes supplémentaires. - -```php -$configurator->addStaticParameters([ - 'projectId' => 23, -]); -``` - -Dans les fichiers de configuration, nous pouvons écrire la notation usuelle `%projectId%` pour accéder au paramètre nommé `projectId`. Par défaut, le configurateur renseigne les paramètres suivants : `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` et `consoleMode`. - - -Paramètres dynamiques .[#toc-dynamic-parameters] ------------------------------------------------- - -Nous pouvons également ajouter des paramètres dynamiques au conteneur, leurs différentes valeurs, contrairement aux paramètres statiques, ne provoqueront pas la génération de nouveaux conteneurs DI. - -```php -$configurator->addDynamicParameters([ - 'remoteIp' => $_SERVER['REMOTE_ADDR'], -]); -``` - -Les variables d'environnement peuvent être facilement mises à disposition à l'aide de paramètres dynamiques. Nous pouvons y accéder via `%env.variable%` dans les fichiers de configuration. - -```php -$configurator->addDynamicParameters([ - 'env' => getenv(), -]); -``` - - -Services importés .[#toc-imported-services] -------------------------------------------- - -Nous allons maintenant plus loin. Bien que le but d'un conteneur DI soit de créer des objets, il peut exceptionnellement être nécessaire d'insérer un objet existant dans le conteneur. Nous le faisons en définissant le service avec l'attribut `imported: true`. - -```neon -services: - myservice: - type: App\Model\MyCustomService - imported: true -``` - -Créez une nouvelle instance et insérez-la dans bootstrap : - -```php -$configurator->addServices([ - 'myservice' => new App\Model\MyCustomService('foobar'), -]); -``` - - -Différents environnements .[#toc-different-environments] -======================================================== - -N'hésitez pas à personnaliser la classe `Bootstrap` en fonction de vos besoins. Vous pouvez ajouter des paramètres à la méthode `boot()` pour différencier les projets Web, ou ajouter d'autres méthodes, comme `bootForTests()`, qui initialise l'environnement pour les tests unitaires, `bootForCli()` pour les scripts appelés depuis la ligne de commande, etc. - -```php -public static function bootForTests(): Configurator -{ - $configurator = self::boot(); - Tester\Environment::setup(); // Initialisation du testeur de nappe - return $configurator; -} -``` diff --git a/application/fr/bootstrapping.texy b/application/fr/bootstrapping.texy new file mode 100644 index 0000000000..eb39389dad --- /dev/null +++ b/application/fr/bootstrapping.texy @@ -0,0 +1,297 @@ +Bootstrapping +************* + +
    + +Le bootstrapping est le processus d'initialisation de l'environnement de l'application, de création d'un conteneur d'injection de dépendances (DI) et de démarrage de l'application. Nous discuterons : + +- comment la classe Bootstrap initialise l'environnement +- comment les applications sont configurées en utilisant des fichiers NEON +- comment distinguer entre le mode de production et de développement +- comment créer et configurer le conteneur DI + +
    + + +Les applications, qu'elles soient web ou des scripts exécutés depuis la ligne de commande, commencent leur exécution par une forme d'initialisation de l'environnement. Autrefois, un fichier nommé par exemple `include.inc.php` s'en chargeait, inclus par le fichier initial. Dans les applications Nette modernes, il a été remplacé par la classe `Bootstrap`, que vous trouverez dans le fichier `app/Bootstrap.php` en tant que partie de l'application. Elle peut ressembler à ceci, par exemple : + +```php +use Nette\Bootstrap\Configurator; + +class Bootstrap +{ + private Configurator $configurator; + private string $rootDir; + + public function __construct() + { + $this->rootDir = dirname(__DIR__); + // Le Configurator est responsable de la configuration de l'environnement et des services de l'application. + $this->configurator = new Configurator; + // Définit le répertoire pour les fichiers temporaires générés par Nette (par exemple, les templates compilés) + $this->configurator->setTempDirectory($this->rootDir . '/temp'); + } + + public function bootWebApplication(): Nette\DI\Container + { + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); + } + + private function initializeEnvironment(): void + { + // Nette est intelligent et le mode développement est activé automatiquement, + // ou vous pouvez l'activer pour une adresse IP spécifique en décommentant la ligne suivante : + // $this->configurator->setDebugMode('secret@23.75.345.200'); + + // Active Tracy : l'ultime "couteau suisse" pour le débogage. + $this->configurator->enableTracy($this->rootDir . '/log'); + + // RobotLoader : charge automatiquement toutes les classes dans le répertoire sélectionné + $this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); + } + + private function setupContainer(): void + { + // Charge les fichiers de configuration + $this->configurator->addConfig($this->rootDir . '/config/common.neon'); + } +} +``` + + +index.php +========= + +Le fichier initial pour les applications web est `index.php`, situé dans le [répertoire public |directory-structure#Répertoire public www] `www/`. Il demande à la classe Bootstrap d'initialiser l'environnement et de créer le conteneur DI. Ensuite, il obtient le service `Application` à partir de celui-ci, qui lance l'application web : + +```php +$bootstrap = new App\Bootstrap; +// Initialisation de l'environnement + création du conteneur DI +$container = $bootstrap->bootWebApplication(); +// Le conteneur DI crée l'objet Nette\Application\Application +$application = $container->getByType(Nette\Application\Application::class); +// Lancement de l'application Nette et traitement de la requête entrante +$application->run(); +``` + +Comme vous pouvez le voir, la classe [api:Nette\Bootstrap\Configurator] aide à configurer l'environnement et à créer le conteneur d'injection de dépendances (DI), que nous allons maintenant présenter plus en détail. + + +Mode Développement vs Production +================================ + +Nette se comporte différemment selon qu'il s'exécute sur un serveur de développement ou de production : + +🛠️ Mode Développement (Development): + - Affiche la barre de débogage Tracy avec des informations utiles (requêtes SQL, temps d'exécution, mémoire utilisée) + - En cas d'erreur, affiche une page d'erreur détaillée avec les appels de fonction et le contenu des variables + - Rafraîchit automatiquement le cache lors de la modification des templates Latte, des fichiers de configuration, etc. + + +🚀 Mode Production (Production): + - N'affiche aucune information de débogage, toutes les erreurs sont écrites dans le journal + - En cas d'erreur, affiche l'ErrorPresenter ou une page générique "Erreur Serveur" + - Le cache n'est jamais rafraîchi automatiquement ! + - Optimisé pour la vitesse et la sécurité + + +La sélection du mode se fait par autodétection, il n'est donc généralement pas nécessaire de configurer quoi que ce soit ou de basculer manuellement : + +- mode développement : sur localhost (adresse IP `127.0.0.1` ou `::1`) s'il n'y a pas de proxy présent (c'est-à-dire son en-tête HTTP) +- mode production : partout ailleurs + +Si nous voulons activer le mode développement dans d'autres cas, par exemple pour les programmeurs accédant depuis une adresse IP spécifiques, nous utilisons `setDebugMode()` : + +```php +$this->configurator->setDebugMode('23.75.345.200'); // un tableau d'adresses IP peut également être fourni +``` + +Nous recommandons vivement de combiner l'adresse IP avec un cookie. Nous stockons un jeton secret, par exemple `secret1234`, dans le cookie `nette-debug`, et activons ainsi le mode développement pour les programmeurs accédant depuis une adresse IP spécifique et ayant également le jeton mentionné dans le cookie : + +```php +$this->configurator->setDebugMode('secret1234@23.75.345.200'); +``` + +Nous pouvons également désactiver complètement le mode développement, même pour localhost : + +```php +$this->configurator->setDebugMode(false); +``` + +Attention, la valeur `true` active le mode développement de manière forcée, ce qui ne doit jamais se produire sur un serveur de production. + + +Outil de Débogage Tracy +======================= + +Pour faciliter le débogage, nous activons également l'excellent outil [Tracy |tracy:]. En mode développement, il visualise les erreurs, et en mode production, il enregistre les erreurs dans le répertoire spécifié : + +```php +$this->configurator->enableTracy($this->rootDir . '/log'); +``` + + +Fichiers Temporaires +==================== + +Nette utilise un cache pour le conteneur DI, RobotLoader, les templates, etc. Il est donc nécessaire de définir le chemin vers le répertoire où le cache sera stocké : + +```php +$this->configurator->setTempDirectory($this->rootDir . '/temp'); +``` + +Sous Linux ou macOS, définissez les [permissions d'écriture |nette:troubleshooting#Configuration des permissions de répertoire] pour les répertoires `log/` et `temp/`. + + +RobotLoader +=========== + +En règle générale, nous voudrons charger automatiquement les classes à l'aide de [RobotLoader |robot-loader:], nous devons donc le démarrer et le laisser charger les classes du répertoire où se trouve `Bootstrap.php` (c'est-à-dire `__DIR__`), ainsi que de tous ses sous-répertoires : + +```php +$this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); +``` + +Une approche alternative consiste à laisser les classes être chargées uniquement via [Composer |best-practices:composer] tout en respectant PSR-4. + + +Fuseau Horaire +============== + +Via le configurateur, vous pouvez définir le fuseau horaire par défaut. + +```php +$this->configurator->setTimeZone('Europe/Prague'); +``` + + +Configuration du Conteneur DI +============================= + +Une partie du processus de démarrage est la création du conteneur DI, ou factory d'objets, qui est le cœur de toute l'application. Il s'agit en fait d'une classe PHP générée par Nette et stockée dans le répertoire cache. La factory produit les objets clés de l'application, et à l'aide des fichiers de configuration, nous lui indiquons comment les créer et les configurer, influençant ainsi le comportement de toute l'application. + +Les fichiers de configuration sont généralement écrits au format [NEON |neon:format]. Dans un chapitre séparé, vous apprendrez [tout ce qui peut être configuré |nette:configuring]. + +.[tip] +En mode développement, le conteneur est automatiquement mis à jour à chaque modification du code ou des fichiers de configuration. En mode production, il n'est généré qu'une seule fois et les modifications ne sont pas vérifiées pour maximiser les performances. + +Nous chargeons les fichiers de configuration à l'aide de `addConfig()` : + +```php +$this->configurator->addConfig($this->rootDir . '/config/common.neon'); +``` + +Si nous voulons ajouter plusieurs fichiers de configuration, nous pouvons appeler la fonction `addConfig()` plusieurs fois. + +```php +$configDir = $this->rootDir . '/config'; +$this->configurator->addConfig($configDir . '/common.neon'); +$this->configurator->addConfig($configDir . '/services.neon'); +if (PHP_SAPI === 'cli') { + $this->configurator->addConfig($configDir . '/cli.php'); +} +``` + +Le nom `cli.php` n'est pas une faute de frappe ; la configuration peut également être écrite dans un fichier PHP qui la retourne sous forme de tableau. + +Nous pouvons également ajouter d'autres fichiers de configuration dans la [section `includes` |dependency-injection:configuration#Inclusion de fichiers]. + +Si des éléments avec les mêmes clés apparaissent dans les fichiers de configuration, ils seront écrasés ou, dans le cas des [tableaux, fusionnés |dependency-injection:configuration#Fusion]. Le fichier inclus ultérieurement a une priorité plus élevée que le précédent. Le fichier dans lequel la section `includes` est listée a une priorité plus élevée que les fichiers qui y sont inclus. + + +Paramètres Statiques +-------------------- + +Les paramètres utilisés dans les fichiers de configuration peuvent être définis [dans la section `parameters` |dependency-injection:configuration#Paramètres] et également transmis (ou écrasés) via la méthode `addStaticParameters()` (qui a un alias `addParameters()`). Il est important de noter que différentes valeurs de paramètres entraîneront la génération de conteneurs DI supplémentaires, c'est-à-dire de classes supplémentaires. + +```php +$this->configurator->addStaticParameters([ + 'projectId' => 23, +]); +``` + +Le paramètre `projectId` peut être référencé dans la configuration en utilisant la notation habituelle `%projectId%`. + + +Paramètres Dynamiques +--------------------- + +Nous pouvons également ajouter des paramètres dynamiques au conteneur, dont les différentes valeurs, contrairement aux paramètres statiques, ne provoquent pas la génération de nouveaux conteneurs DI. + +```php +$this->configurator->addDynamicParameters([ + 'remoteIp' => $_SERVER['REMOTE_ADDR'], +]); +``` + +Nous pouvons ainsi facilement ajouter, par exemple, des variables d'environnement, qui peuvent ensuite être référencées dans la configuration en utilisant la notation `%env.variable%`. + +```php +$this->configurator->addDynamicParameters([ + 'env' => getenv(), +]); +``` + + +Paramètres par Défaut +--------------------- + +Dans les fichiers de configuration, vous pouvez utiliser ces paramètres statiques : + +- `%appDir%` est le chemin absolu vers le répertoire contenant le fichier `Bootstrap.php` +- `%wwwDir%` est le chemin absolu vers le répertoire contenant le fichier d'entrée `index.php` +- `%tempDir%` est le chemin absolu vers le répertoire des fichiers temporaires +- `%vendorDir%` est le chemin absolu vers le répertoire où Composer installe les bibliothèques +- `%rootDir%` est le chemin absolu vers le répertoire racine du projet +- `%debugMode%` indique si l'application est en mode débogage +- `%consoleMode%` indique si la requête provient de la ligne de commande + + +Services Importés +----------------- + +Maintenant, allons plus en profondeur. Bien que le but du conteneur DI soit de créer des objets, il peut exceptionnellement être nécessaire d'insérer un objet existant dans le conteneur. Nous le faisons en définissant le service avec l'indicateur `imported: true`. + +```neon +services: + myservice: + type: App\Model\MyCustomService + imported: true +``` + +Et dans le bootstrap, nous insérons l'objet dans le conteneur : + +```php +$this->configurator->addServices([ + 'myservice' => new App\Model\MyCustomService('foobar'), +]); +``` + + +Environnements Différents +========================= + +N'hésitez pas à modifier la classe Bootstrap selon vos besoins. Vous pouvez ajouter des paramètres à la méthode `bootWebApplication()` pour distinguer les projets web. Ou nous pouvons ajouter d'autres méthodes, par exemple `bootTestEnvironment()`, qui initialise l'environnement pour les tests unitaires, `bootConsoleApplication()` pour les scripts appelés depuis la ligne de commande, etc. + +```php +public function bootTestEnvironment(): Nette\DI\Container +{ + Tester\Environment::setup(); // initialisation de Nette Tester + $this->setupContainer(); + return $this->configurator->createContainer(); +} + +public function bootConsoleApplication(): Nette\DI\Container +{ + $this->configurator->setDebugMode(false); + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); +} +``` diff --git a/application/fr/components.texy b/application/fr/components.texy index 9c269642a6..4392cdea5b 100644 --- a/application/fr/components.texy +++ b/application/fr/components.texy @@ -3,7 +3,7 @@ Composants interactifs
    -Les composants sont des objets distincts réutilisables que nous plaçons dans les pages. Il peut s'agir de formulaires, de grilles de données, de sondages, en fait de tout ce qui peut être utilisé de manière répétée. Nous allons montrer : +Les composants sont des objets réutilisables indépendants que nous insérons dans les pages. Il peut s'agir de formulaires, de datagrids, de sondages, en fait de tout ce qu'il est judicieux d'utiliser de manière répétée. Nous allons montrer : - comment utiliser les composants ? - comment les écrire ? @@ -11,19 +11,19 @@ Les composants sont des objets distincts réutilisables que nous plaçons dans l
    -Nette dispose d'un système de composants intégré. Les plus anciens d'entre vous se souviennent peut-être de quelque chose de similaire dans Delphi ou ASP.NET Web Forms. React ou Vue.js sont construits sur quelque chose de vaguement similaire. Cependant, dans le monde des frameworks PHP, il s'agit d'une fonctionnalité tout à fait unique. +Nette intègre un système de composants. Les vétérans peuvent se souvenir de quelque chose de similaire dans Delphi ou ASP.NET Web Forms ; React ou Vue.js sont basés sur quelque chose de vaguement similaire. Cependant, dans le monde des frameworks PHP, c'est une caractéristique unique. -Dans le même temps, les composants changent fondamentalement l'approche du développement d'applications. Vous pouvez composer des pages à partir d'unités préparées à l'avance. Vous avez besoin d'un datagrid dans l'administration ? Vous pouvez la trouver dans [Componette |https://componette.org/search/component], un référentiel de modules complémentaires (et pas seulement de composants) open-source pour Nette, et la coller simplement dans le présentateur. +Pourtant, les composants influencent fondamentalement l'approche de la création d'applications. Vous pouvez composer des pages à partir d'unités préfabriquées. Besoin d'un datagrid dans l'administration ? Vous le trouverez sur [Componette |https://componette.org/search/component], un dépôt d'add-ons open-source (donc pas seulement des composants) pour Nette, et l'insérerez simplement dans le presenter. -Vous pouvez incorporer n'importe quel nombre de composants dans le présentateur. Et vous pouvez insérer d'autres composants dans certains composants. Cela crée un arbre de composants avec un présentateur comme racine. +Vous pouvez intégrer n'importe quel nombre de composants dans un presenter. Et vous pouvez insérer d'autres composants dans certains composants. Cela crée un arbre de composants, dont la racine est le presenter. -Méthodes d'usine .[#toc-factory-methods] -======================================== +Méthodes Factory +================ -Comment les composants sont-ils placés et ensuite utilisés dans le présentateur ? Généralement à l'aide de méthodes d'usine. +Comment les composants sont-ils insérés dans le presenter et ensuite utilisés ? Généralement via des méthodes factory. -La fabrique de composants est un moyen élégant de créer des composants uniquement lorsqu'ils sont vraiment nécessaires (paresseux / à la demande). Toute la magie réside dans la mise en œuvre d'une méthode appelée `createComponent()`où `` est le nom du composant, qui sera créé et retourné. +Une factory de composants représente une manière élégante de créer des composants uniquement lorsqu'ils sont réellement nécessaires (lazy / on demand). Toute la magie réside dans l'implémentation d'une méthode nommée `createComponent()`, où `` est le nom du composant à créer, et qui crée et retourne le composant. ```php .{file:DefaultPresenter.php} class DefaultPresenter extends Nette\Application\UI\Presenter @@ -37,43 +37,43 @@ class DefaultPresenter extends Nette\Application\UI\Presenter } ``` -Comme tous les composants sont créés dans des méthodes distinctes, le code est plus propre et plus facile à lire. +Grâce au fait que tous les composants sont créés dans des méthodes séparées, le code gagne en clarté. .[note] -Les noms des composants commencent toujours par une lettre minuscule, bien qu'ils soient en majuscules dans le nom de la méthode. +Les noms des composants commencent toujours par une lettre minuscule, même s'ils sont écrits avec une majuscule dans le nom de la méthode. -Nous n'appelons jamais les fabriques directement, elles sont appelées automatiquement, lorsque nous utilisons des composants pour la première fois. Grâce à cela, un composant est créé au bon moment, et seulement s'il est vraiment nécessaire. Si nous n'utilisons pas le composant (par exemple sur une requête AJAX, où nous ne retournons qu'une partie de la page, ou lorsque des parties sont mises en cache), il ne sera même pas créé et nous économisons les performances du serveur. +Nous n'appelons jamais les factories directement ; elles s'appellent elles-mêmes la première fois que nous utilisons le composant. Grâce à cela, le composant est créé au bon moment et seulement s'il est réellement nécessaire. Si nous n'utilisons pas le composant (par exemple, lors d'une requête AJAX où seule une partie de la page est transmise, ou lors de la mise en cache du template), il n'est pas créé du tout et nous économisons les performances du serveur. ```php .{file:DefaultPresenter.php} -// on accède au composant et si c'est la première fois, -// on appelle createComponentPoll() pour le créer +// nous accédons au composant et si c'est la première fois, +// createComponentPoll() est appelée, qui le crée $poll = $this->getComponent('poll'); -// syntaxe alternative: $poll = $this['poll']; +// syntaxe alternative : $poll = $this['poll']; ``` -Dans le modèle, vous pouvez rendre un composant en utilisant la balise [{control} |#Rendering]. Il n'est donc pas nécessaire de passer manuellement les composants au modèle. +Dans le template, il est possible de rendre un composant à l'aide de la balise [{control} |#Rendu]. Il n'est donc pas nécessaire de transmettre manuellement les composants au template. ```latte -

    Please Vote

    +

    Votez

    {control poll} ``` -Le style Hollywood .[#toc-hollywood-style] -========================================== +Style Hollywood +=============== -Les composants utilisent couramment une technique cool, que nous aimons appeler le style Hollywood. Vous connaissez certainement le cliché que les acteurs entendent souvent lors des appels de casting : "Ne nous appelez pas, nous vous appellerons." Et c'est bien de cela qu'il s'agit. +Les composants utilisent couramment une technique fraîche que nous aimons appeler le style Hollywood. Vous connaissez sûrement la phrase célèbre que les participants aux auditions de films entendent si souvent : "Ne nous appelez pas, nous vous appellerons". Et c'est exactement de cela qu'il s'agit. -Dans Nette, au lieu de devoir constamment poser des questions ("le formulaire a-t-il été soumis ?", "était-il valide ?" ou "quelqu'un a-t-il appuyé sur ce bouton ?"), vous dites au framework "lorsque ceci se produit, appelez cette méthode" et vous lui laissez la suite du travail. Si vous programmez en JavaScript, vous êtes familier avec ce style de programmation. Vous écrivez des fonctions qui sont appelées lorsqu'un certain événement se produit. Et le moteur leur passe les paramètres appropriés. +Dans Nette, au lieu de devoir constamment demander ("le formulaire a-t-il été soumis ?", "était-il valide ?" ou "l'utilisateur a-t-il appuyé sur ce bouton ?"), vous dites au framework "quand cela arrivera, appelle cette méthode" et vous lui laissez le reste du travail. Si vous programmez en JavaScript, vous connaissez bien ce style de programmation. Vous écrivez des fonctions qui sont appelées lorsqu'un certain événement se produit. Et le langage leur transmet les paramètres appropriés. -Cela change complètement la façon dont vous écrivez des applications. Plus vous pouvez déléguer de tâches au framework, moins vous avez de travail. Et moins vous pouvez oublier. +Cela change complètement la façon d'écrire des applications. Plus vous pouvez laisser de tâches au framework, moins vous avez de travail. Et moins vous risquez d'oublier quelque chose. -Comment écrire un composant .[#toc-how-to-write-a-component] -============================================================ +Écrire un composant +=================== -Par composant, nous entendons généralement les descendants de la classe [api:Nette\Application\UI\Control]. Le présentateur [api:Nette\Application\UI\Presenter] lui-même est également un descendant de la classe `Control`. +Par le terme composant, nous entendons généralement un descendant de la classe [api:Nette\Application\UI\Control]. (Il serait donc plus précis d'utiliser le terme "controls", mais "contrôles" a un sens différent en français et "composants" s'est plutôt imposé.) Le presenter lui-même [api:Nette\Application\UI\Presenter] est d'ailleurs aussi un descendant de la classe `Control`. ```php .{file:PollControl.php} use Nette\Application\UI\Control; @@ -84,22 +84,22 @@ class PollControl extends Control ``` -Rendu .[#toc-rendering] -======================= +Rendu +===== -Nous savons déjà que la balise `{control componentName}` est utilisée pour dessiner un composant. Elle appelle en fait la méthode `render()` du composant, dans laquelle nous nous occupons du rendu. Nous disposons, comme dans le présentateur, d'un modèle [Latte |latte:] dans la variable `$this->template`, à laquelle nous passons les paramètres. Contrairement à l'utilisation dans un présentateur, nous devons spécifier un fichier de modèle et le laisser effectuer le rendu : +Nous savons déjà que pour rendre un composant, on utilise la balise `{control componentName}`. Celle-ci appelle en fait la méthode `render()` du composant, dans laquelle nous nous occupons du rendu. Nous avons à notre disposition, tout comme dans le presenter, un [template Latte|templates] dans la variable `$this->template`, auquel nous passons des paramètres. Contrairement au presenter, nous devons spécifier le fichier de template et le faire rendre : ```php .{file:PollControl.php} public function render(): void { - // nous allons mettre quelques paramètres dans le modèle + // nous insérons quelques paramètres dans le template $this->template->param = $value; - // et le dessiner + // et nous le rendons $this->template->render(__DIR__ . '/poll.latte'); } ``` -Le tag `{control}` permet de passer des paramètres à la méthode `render()`: +La balise `{control}` permet de passer des paramètres à la méthode `render()` : ```latte {control poll $id, $message} @@ -112,7 +112,7 @@ public function render(int $id, string $message): void } ``` -Parfois un composant peut être composé de plusieurs parties que nous voulons rendre séparément. Pour chacune d'entre elles, nous allons créer notre propre méthode de rendu, voici par exemple `renderPaginator()`: +Parfois, un composant peut être constitué de plusieurs parties que nous voulons rendre séparément. Pour chacune d'elles, nous créons notre propre méthode de rendu, ici dans l'exemple `renderPaginator()` : ```php .{file:PollControl.php} public function renderPaginator(): void @@ -121,69 +121,69 @@ public function renderPaginator(): void } ``` -Et dans le modèle, nous l'appelons ensuite en utilisant : +Et dans le template, nous l'appelons ensuite en utilisant : ```latte {control poll:paginator} ``` -Pour une meilleure compréhension, il est bon de savoir comment la balise est compilée en code PHP. +Pour une meilleure compréhension, il est bon de savoir comment cette balise est traduite en PHP. ```latte {control poll} {control poll:paginator 123, 'hello'} ``` -Cela se compile en : +se traduit par : ```php $control->getComponent('poll')->render(); $control->getComponent('poll')->renderPaginator(123, 'hello'); ``` -`getComponent()` La méthode renvoie le composant `poll`, puis la méthode `render()` ou `renderPaginator()`, respectivement, est appelée sur celui-ci. +La méthode `getComponent()` retourne le composant `poll` et appelle la méthode `render()` sur ce composant, ou `renderPaginator()` si un autre mode de rendu est spécifié dans la balise après les deux-points. .[caution] -Si n'importe où dans la partie paramètre **`=>`** est utilisé, tous les paramètres seront enveloppés dans un tableau et passés comme premier argument : +Attention, si **`=>`** apparaît n'importe où dans les paramètres, tous les paramètres seront enveloppés dans un tableau et passés comme premier argument : ```latte {control poll, id: 123, message: 'hello'} ``` -compile vers : +se traduit par : ```php $control->getComponent('poll')->render(['id' => 123, 'message' => 'hello']); ``` -Rendu du sous-composant : +Rendu d'un sous-composant : ```latte {control cartControl-someForm} ``` -compile vers : +se traduit par : ```php $control->getComponent("cartControl-someForm")->render(); ``` -Les composants, comme les présentateurs, transmettent automatiquement plusieurs variables utiles aux modèles : +Les composants, tout comme les presenters, transmettent automatiquement plusieurs variables utiles aux templates : -- `$basePath` est un chemin URL absolu vers le répertoire racine (par exemple `/CD-collection`) -- `$baseUrl` est une URL absolue vers le répertoire racine (par exemple `http://localhost/CD-collection`) -- `$user` est un objet [représentant l'utilisateur |security:authentication] -- `$presenter` est le présentateur actuel +- `$basePath` est le chemin URL absolu vers le répertoire racine (par ex. `/eshop`) +- `$baseUrl` est l'URL absolue vers le répertoire racine (par ex. `http://localhost/eshop`) +- `$user` est l'objet [représentant l'utilisateur |security:authentication] +- `$presenter` est le presenter actuel - `$control` est le composant actuel -- `$flashes` liste des [messages |#flash-messages] envoyés par la méthode `flashMessage()` +- `$flashes` tableau des [messages |#Messages Flash] envoyés par la fonction `flashMessage()` -Signal .[#toc-signal] -===================== +Signal +====== -Nous savons déjà que la navigation dans l'application Nette consiste à créer des liens ou à rediriger vers des paires `Presenter:action`. Mais qu'en est-il si nous voulons simplement effectuer une action sur la **page courante** ? Par exemple, changer l'ordre de tri de la colonne dans le tableau ; supprimer un élément ; passer en mode lumière/obscurité ; soumettre le formulaire ; voter dans le sondage ; etc. +Nous savons déjà que la navigation dans une application Nette consiste à créer des liens ou des redirections vers des paires `Presenter:action`. Mais que faire si nous voulons simplement effectuer une action sur la **page actuelle** ? Par exemple, changer le tri des colonnes dans un tableau ; supprimer un élément ; basculer entre le mode clair/sombre ; soumettre un formulaire ; voter dans un sondage ; etc. -Ce type de demande s'appelle un signal. Et comme les actions invoquent des méthodes `action()` ou `render()`les signaux appellent des méthodes `handle()`. Alors que le concept d'action (ou de vue) ne concerne que les présentateurs, les signaux s'appliquent à tous les composants. Et donc aussi aux présentateurs, car `UI\Presenter` est un descendant de `UI\Control`. +Ce type de requête est appelé signal. Et tout comme les actions appellent les méthodes `action()` ou `render()`, les signaux appellent les méthodes `handle()`. Alors que le concept d'action (ou de vue) est purement lié aux presenters, les signaux concernent tous les composants. Et donc aussi les presenters, car `UI\Presenter` est un descendant de `UI\Control`. ```php public function handleClick(int $x, int $y): void @@ -192,36 +192,36 @@ public function handleClick(int $x, int $y): void } ``` -Le lien qui appelle le signal est créé de la manière habituelle, c'est-à-dire dans le modèle par l'attribut `n:href` ou la balise `{link}`, dans le code par la méthode `link()`. Pour en savoir plus, consultez le chapitre [Création de liens URL |creating-links#Links to Signal]. +Nous créons un lien qui appelle un signal de la manière habituelle, c'est-à-dire dans le template avec l'attribut `n:href` ou la balise `{link}`, dans le code avec la méthode `link()`. Plus d'informations dans le chapitre [Création de liens URL |creating-links#Liens vers un signal]. ```latte -click here +cliquez ici ``` -Le signal est toujours appelé dans le présentateur et la vue actuels, il n'est donc pas possible d'établir un lien vers le signal dans un présentateur/une action différents. +Un signal est toujours appelé sur le presenter et l'action actuels ; il n'est pas possible de l'appeler sur un autre presenter ou une autre action. -Ainsi, le signal provoque le rechargement de la page exactement de la même manière que dans la requête originale, seulement en plus il appelle la méthode de traitement du signal avec les paramètres appropriés. Si la méthode n'existe pas, l'exception [api:Nette\Application\UI\BadSignalException] est levée, ce qui est affiché à l'utilisateur comme page d'erreur 403 Forbidden. +Le signal provoque donc un rechargement de la page exactement comme lors de la requête initiale, mais appelle en plus la méthode de gestion du signal avec les paramètres appropriés. Si la méthode n'existe pas, une exception [api:Nette\Application\UI\BadSignalException] est levée, qui s'affiche à l'utilisateur comme une page d'erreur 403 Interdit. -Snippets et AJAX .[#toc-snippets-and-ajax] -========================================== +Snippets et AJAX +================ -Les signaux peuvent vous rappeler un peu AJAX : des gestionnaires qui sont appelés sur la page actuelle. Et vous avez raison, les signaux sont très souvent appelés en utilisant AJAX, et ensuite nous ne transmettons au navigateur que les parties modifiées de la page. On les appelle des snippets. Vous trouverez plus d'informations sur la [page concernant AJAX |ajax]. +Les signaux vous rappellent peut-être un peu AJAX : des gestionnaires qui sont appelés sur la page actuelle. Et vous avez raison, les signaux sont en effet souvent appelés via AJAX, puis seules les parties modifiées de la page sont transmises au navigateur. C'est-à-dire les fameux snippets. Plus d'informations peuvent être trouvées sur la [page dédiée à AJAX |ajax]. -Messages Flash .[#toc-flash-messages] -===================================== +Messages Flash +============== -Un composant dispose de son propre stockage de messages flash, indépendamment du présentateur. Il s'agit de messages qui, par exemple, informent sur le résultat de l'opération. Une caractéristique importante des messages flash est qu'ils sont disponibles dans le modèle même après une redirection. Même après avoir été affichés, ils restent vivants pendant 30 secondes supplémentaires - par exemple, au cas où l'utilisateur rafraîchirait involontairement la page - le message ne sera pas perdu. +Un composant possède son propre stockage de messages flash indépendant du presenter. Ce sont des messages qui informent par exemple du résultat d'une opération. Une caractéristique importante des messages flash est qu'ils sont disponibles dans le template même après une redirection. Même après affichage, ils restent actifs pendant 30 secondes supplémentaires – par exemple, au cas où l'utilisateur rafraîchirait la page en raison d'une erreur de transmission - le message ne disparaîtra donc pas immédiatement. -L'envoi se fait par la méthode [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Le premier paramètre est le texte du message ou l'objet `stdClass` représentant le message. Le deuxième paramètre facultatif est son type (erreur, avertissement, info, etc.). La méthode `flashMessage()` renvoie une instance de flashMessage sous forme d'objet stdClass auquel vous pouvez passer des informations. +L'envoi est géré par la méthode [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Le premier paramètre est le texte du message ou un objet `stdClass` représentant le message. Le deuxième paramètre facultatif est son type (erreur, avertissement, info, etc.). La méthode `flashMessage()` retourne une instance du message flash sous forme d'objet `stdClass`, auquel des informations supplémentaires peuvent être ajoutées. ```php -$this->flashMessage('L'article a été supprimé.'); -$this->redirect(/* ... */); // et redirection +$this->flashMessage('L\'élément a été supprimé.'); +$this->redirect(/* ... */); // et nous redirigeons ``` -Dans le modèle, ces messages sont disponibles dans la variable `$flashes` sous forme d'objets `stdClass`, qui contiennent les propriétés `message` (texte du message), `type` (type de message) et peuvent contenir les informations utilisateur déjà mentionnées. Nous les dessinons comme suit : +Dans le template, ces messages sont disponibles dans la variable `$flashes` sous forme d'objets `stdClass`, qui contiennent les propriétés `message` (texte du message), `type` (type de message) et peuvent contenir les informations utilisateur mentionnées précédemment. Nous les rendons par exemple comme ceci : ```latte {foreach $flashes as $flash} @@ -230,44 +230,66 @@ Dans le modèle, ces messages sont disponibles dans la variable `$flashes` sous ``` -Paramètres persistants .[#toc-persistent-parameters] -==================================================== +Redirection après un signal +=========================== -Les paramètres persistants sont utilisés pour maintenir l'état des composants entre les différentes requêtes. Leur valeur reste inchangée même après avoir cliqué sur un lien. Contrairement aux données de session, ils sont transférés dans l'URL. Ils sont également transférés automatiquement, y compris les liens créés dans d'autres composants de la même page. +Le traitement d'un signal de composant est souvent suivi d'une redirection. C'est une situation similaire à celle des formulaires - après leur soumission, nous redirigeons également pour éviter que les données ne soient renvoyées si la page est rafraîchie dans le navigateur. -Par exemple, vous avez un composant de pagination de contenu. Il peut y avoir plusieurs composants de ce type sur une page. Vous souhaitez que tous les composants restent sur leur page actuelle lorsque vous cliquez sur le lien. C'est pourquoi nous faisons du numéro de page (`page`) un paramètre persistant. +```php +$this->redirect('this') // redirige vers le presenter et l'action actuels +``` + +Comme un composant est un élément réutilisable et ne devrait généralement pas avoir de lien direct avec des presenters spécifiques, les méthodes `redirect()` et `link()` interprètent automatiquement le paramètre comme un signal du composant : + +```php +$this->redirect('click') // redirige vers le signal 'click' du même composant +``` + +Si vous avez besoin de rediriger vers un autre presenter ou une autre action, vous pouvez le faire via le presenter : + +```php +$this->getPresenter()->redirect('Product:show'); // redirige vers un autre presenter/action +``` + + +Paramètres persistants +====================== + +Les paramètres persistants sont utilisés pour maintenir l'état dans les composants entre différentes requêtes. Leur valeur reste la même même après avoir cliqué sur un lien. Contrairement aux données de session, ils sont transmis dans l'URL. Et cela de manière entièrement automatique, y compris pour les liens créés dans d'autres composants sur la même page. + +Vous avez par exemple un composant pour la pagination du contenu. Il peut y avoir plusieurs de ces composants sur une page. Et nous souhaitons qu'après avoir cliqué sur un lien, tous les composants restent sur leur page actuelle. C'est pourquoi nous faisons du numéro de page (`page`) un paramètre persistant. -La création d'un paramètre persistant est extrêmement simple dans Nette. Il suffit de créer une propriété publique et de la baliser avec l'attribut : (auparavant `/** @persistent */` était utilisé) +La création d'un paramètre persistant est extrêmement simple dans Nette. Il suffit de créer une propriété publique et de la marquer avec un attribut : (auparavant, `/** @persistent */` était utilisé) ```php -use Nette\Application\Attributes\Persistent; // cette ligne est importante +use Nette\Application\Attributes\Persistent; // cette ligne est importante class PaginatingControl extends Control { #[Persistent] - public int $page = 1; // doit être publique + public int $page = 1; // doit être public } ``` -Nous vous recommandons d'inclure le type de données (par exemple `int`) avec la propriété, et vous pouvez également inclure une valeur par défaut. Les valeurs des paramètres peuvent être [validées |#Validation of Persistent Parameters]. +Nous recommandons d'indiquer également le type de données pour la propriété (par ex. `int`) et vous pouvez également spécifier une valeur par défaut. Les valeurs des paramètres peuvent être [validées |#Validation des paramètres persistants]. -Vous pouvez modifier la valeur d'un paramètre persistant lors de la création d'un lien : +Lors de la création d'un lien, la valeur du paramètre persistant peut être modifiée : ```latte -next +suivant ``` Ou il peut être *réinitialisé*, c'est-à-dire supprimé de l'URL. Il prendra alors sa valeur par défaut : ```latte -reset +réinitialiser ``` -Composants persistants .[#toc-persistent-components] -==================================================== +Composants persistants +====================== -Non seulement les paramètres mais aussi les composants peuvent être persistants. Leurs paramètres persistants sont également transférés entre différentes actions ou entre différents présentateurs. Nous marquons les composants persistants avec ces annotations pour la classe de présentateur. Par exemple, nous marquons ici les composants `calendar` et `poll` comme suit : +Non seulement les paramètres, mais aussi les composants peuvent être persistants. Pour un tel composant, ses paramètres persistants sont également transmis entre différentes actions du presenter ou entre plusieurs presenters. Nous marquons les composants persistants avec une annotation dans la classe du presenter. Par exemple, nous marquons ainsi les composants `calendar` et `poll` : ```php /** @@ -278,7 +300,7 @@ class DefaultPresenter extends Nette\Application\UI\Presenter } ``` -Il n'est pas nécessaire de marquer les sous-composants comme persistants, ils le sont automatiquement. +Il n'est pas nécessaire de marquer les sous-composants à l'intérieur de ces composants ; ils deviendront également persistants. En PHP 8, vous pouvez également utiliser des attributs pour marquer les composants persistants : @@ -292,35 +314,35 @@ class DefaultPresenter extends Nette\Application\UI\Presenter ``` -Composants avec dépendances .[#toc-components-with-dependencies] -================================================================ +Composants avec dépendances +=========================== -Comment créer des composants avec des dépendances sans "bousiller" les présentateurs qui les utiliseront ? Grâce aux fonctionnalités astucieuses du conteneur DI de Nette, comme pour l'utilisation de services traditionnels, nous pouvons laisser la majeure partie du travail au framework. +Comment créer des composants avec des dépendances sans "polluer" les presenters qui les utiliseront ? Grâce aux fonctionnalités intelligentes du conteneur DI de Nette, comme pour l'utilisation de services classiques, la majeure partie du travail peut être laissée au framework. -Prenons l'exemple d'un composant qui a une dépendance avec le service `PollFacade`: +Prenons comme exemple un composant qui a une dépendance envers le service `PollFacade` : ```php class PollControl extends Control { public function __construct( - private int $id, // Id d'un sondage, pour lequel le composant est créé + private int $id, // Id du sondage pour lequel nous créons le composant private PollFacade $facade, ) { } public function handleVote(int $voteId): void { - $this->facade->vote($id, $voteId); + $this->facade->vote($this->id, $voteId); // ... } } ``` -Si nous écrivions un service classique, il n'y aurait pas à s'inquiéter. Le conteneur DI se chargerait invisiblement de passer toutes les dépendances. Mais nous gérons généralement les composants en créant une nouvelle instance de ceux-ci directement dans le présentateur à l'aide de [méthodes d'usine |#factory methods] `createComponent...()`. Mais transmettre toutes les dépendances de tous les composants au présentateur pour ensuite les transmettre aux composants est fastidieux. Et la quantité de code écrite... +Si nous écrivions un service classique, il n'y aurait rien à faire. Le conteneur DI se chargerait invisiblement de transmettre toutes les dépendances. Mais avec les composants, nous les traitons généralement en créant leur nouvelle instance directement dans le presenter dans les [#méthodes factory] `createComponent…()`. Mais transmettre toutes les dépendances de tous les composants au presenter pour ensuite les transmettre aux composants est lourd. Et tout ce code écrit… -La question logique est la suivante : pourquoi ne pas simplement enregistrer le composant en tant que service classique, le transmettre au présentateur, puis le retourner dans la méthode `createComponent...()`? Mais cette approche est inappropriée car nous voulons pouvoir créer le composant plusieurs fois. +La question logique est, pourquoi ne pas simplement enregistrer le composant comme un service classique, le passer au presenter et ensuite le retourner dans la méthode `createComponent…()` ? Une telle approche est cependant inappropriée, car nous voulons pouvoir créer le composant plusieurs fois si nécessaire. -La bonne solution consiste à écrire une fabrique pour le composant, c'est-à-dire une classe qui crée le composant pour nous : +La solution correcte est d'écrire une factory pour le composant, c'est-à-dire une classe qui nous créera le composant : ```php class PollControlFactory @@ -337,17 +359,17 @@ class PollControlFactory } ``` -Maintenant nous enregistrons notre service à la configuration du conteneur DI : +Nous enregistrons cette factory dans notre conteneur dans la configuration : ```neon services: - PollControlFactory ``` -Enfin, nous allons utiliser cette fabrique dans notre présentateur : +et enfin, nous l'utilisons dans notre presenter : ```php -class PollPresenter extends Nette\UI\Application\Presenter +class PollPresenter extends Nette\Application\UI\Presenter { public function __construct( private PollControlFactory $pollControlFactory, @@ -362,7 +384,7 @@ class PollPresenter extends Nette\UI\Application\Presenter } ``` -Ce qui est génial, c'est que Nette DI peut [générer des |dependency-injection:factory] fabriques aussi simples, donc au lieu d'écrire tout le code, il suffit d'écrire son interface : +Ce qui est génial, c'est que Nette DI peut [générer |dependency-injection:factory] de telles factories simples, donc au lieu de tout son code, il suffit d'écrire seulement son interface : ```php interface PollControlFactory @@ -371,21 +393,21 @@ interface PollControlFactory } ``` -C'est tout. Nette implémente cette interface en interne et l'injecte dans notre présentateur, où nous pouvons l'utiliser. Elle transmet également comme par magie notre paramètre `$id` et notre instance de la classe `PollFacade` à notre composant. +Et c'est tout. Nette implémente intérieurement cette interface et la transmet au presenter, où nous pouvons déjà l'utiliser. Il ajoute magiquement le paramètre `$id` et l'instance de la classe `PollFacade` à notre composant. -Les composants en profondeur .[#toc-components-in-depth] -======================================================== +Composants en profondeur +======================== -Les composants d'une application Nette sont les parties réutilisables d'une application Web que nous intégrons dans les pages, ce qui est le sujet de ce chapitre. Quelles sont exactement les capacités d'un tel composant ? +Les composants dans Nette Application représentent des parties réutilisables d'une application web que nous insérons dans les pages et auxquelles ce chapitre entier est d'ailleurs consacré. Quelles sont exactement les capacités d'un tel composant ? -1) il peut être rendu dans un modèle -2) Il sait quelle partie de lui-même doit être rendue lors d'une [requête AJAX |ajax#invalidation] (snippets). -3) il a la capacité de stocker son état dans une URL (paramètres persistants) -4) il a la capacité de répondre aux actions de l'utilisateur (signaux) -5) il crée une structure hiérarchique (dont la racine est le présentateur). +1) il est rendable dans un template +2) il sait [quelle partie de lui-même |ajax#Snippets] rendre lors d'une requête AJAX (snippets) +3) il a la capacité de sauvegarder son état dans l'URL (paramètres persistants) +4) il a la capacité de réagir aux actions de l'utilisateur (signaux) +5) il crée une structure hiérarchique (où la racine est le presenter) -Chacune de ces fonctions est gérée par l'une des classes de la lignée d'héritage. Le rendu (1 + 2) est géré par [api:Nette\Application\UI\Control], l'incorporation dans le [cycle de vie |presenters#life-cycle-of-presenter] (3, 4) par la classe [api:Nette\Application\UI\Component] et la création de la structure hiérarchique (5) par les classes [Container et Component |component-model:]. +Chacune de ces fonctions est assurée par l'une des classes de la lignée d'héritage. Le rendu (1 + 2) est géré par [api:Nette\Application\UI\Control], l'intégration dans le [cycle de vie |presenters#Cycle de vie du presenter] (3, 4) par la classe [api:Nette\Application\UI\Component] et la création d'une structure hiérarchique (5) par les classes [Container et Component |component-model:]. ``` Nette\ComponentModel\Component { IComponent } @@ -400,18 +422,18 @@ Nette\ComponentModel\Component { IComponent } ``` -Cycle de vie du composant .[#toc-life-cycle-of-component] ---------------------------------------------------------- +Cycle de vie du composant +------------------------- [* lifecycle-component.svg *] *** *Cycle de vie du composant* .<> -Validation des paramètres persistants .[#toc-validation-of-persistent-parameters] ---------------------------------------------------------------------------------- +Validation des paramètres persistants +------------------------------------- -Les valeurs des [paramètres persistants |#persistent parameters] reçus des URL sont écrites dans les propriétés par la méthode `loadState()`. Elle vérifie également si le type de données spécifié pour la propriété correspond, sinon elle répondra par une erreur 404 et la page ne sera pas affichée. +Les valeurs des [#paramètres persistants] reçues de l'URL sont écrites dans les propriétés par la méthode `loadState()`. Celle-ci vérifie également si le type de données indiqué pour la propriété correspond, sinon elle répond par une erreur 404 et la page ne s'affiche pas. -Il ne faut jamais faire aveuglément confiance aux paramètres persistants, car ils peuvent facilement être remplacés par l'utilisateur dans l'URL. Par exemple, voici comment nous vérifions si le numéro de page `$this->page` est supérieur à 0. Une bonne façon de le faire est de surcharger la méthode `loadState()` mentionnée ci-dessus : +Ne faites jamais confiance aveuglément aux paramètres persistants, car ils peuvent être facilement modifiés par l'utilisateur dans l'URL. Voici comment nous vérifions, par exemple, si le numéro de page `$this->page` est supérieur à 0. Une bonne approche consiste à redéfinir la méthode `loadState()` mentionnée : ```php class PaginatingControl extends Control @@ -421,8 +443,8 @@ class PaginatingControl extends Control public function loadState(array $params): void { - parent::loadState($params); // ici est définie la page $this->page - // suit la vérification de la valeur de l'utilisateur: + parent::loadState($params); // ici $this->page est défini + // suit la vérification personnalisée de la valeur : if ($this->page < 1) { $this->error(); } @@ -430,27 +452,27 @@ class PaginatingControl extends Control } ``` -Le processus inverse, c'est-à-dire la collecte de valeurs à partir de propriétés persistantes, est géré par la méthode `saveState()`. +Le processus inverse, c'est-à-dire la collecte des valeurs des propriétés persistantes, est géré par la méthode `saveState()`. -Signaux en profondeur .[#toc-signals-in-depth] ----------------------------------------------- +Signaux en profondeur +--------------------- -Un signal provoque un rechargement de la page comme la requête originale (à l'exception d'AJAX) et invoque la méthode `signalReceived($signal)` dont l'implémentation par défaut dans la classe `Nette\Application\UI\Component` tente d'appeler une méthode composée des mots `handle{Signal}`. La suite du traitement repose sur l'objet donné. Les objets qui sont des descendants de `Component` (c'est-à-dire `Control` et `Presenter`) tentent d'appeler `handle{Signal}` avec les paramètres appropriés. +Un signal provoque un rechargement de la page exactement comme lors de la requête initiale (sauf s'il est appelé via AJAX) et appelle la méthode `signalReceived($signal)`, dont l'implémentation par défaut dans la classe `Nette\Application\UI\Component` tente d'appeler une méthode composée des mots `handle{signal}`. Le traitement ultérieur dépend de l'objet donné. Les objets qui héritent de `Component` (c'est-à-dire `Control` et `Presenter`) réagissent en essayant d'appeler la méthode `handle{signal}` avec les paramètres appropriés. -En d'autres termes, la définition de la méthode `handle{Signal}` est reprise et tous les paramètres reçus dans la demande sont mis en correspondance avec les paramètres de la méthode. Cela signifie que le paramètre `id` de l'URL correspond au paramètre de la méthode `$id`, `something` à `$something` et ainsi de suite. Et si la méthode n'existe pas, la méthode `signalReceived` lève une [exception |api:Nette\Application\UI\BadSignalException]. +En d'autres termes : la définition de la fonction `handle{signal}` est prise, ainsi que tous les paramètres qui sont arrivés avec la requête, et les paramètres de l'URL sont substitués aux arguments par nom, puis on tente d'appeler la méthode donnée. Par exemple, la valeur du paramètre `id` dans l'URL est passée comme paramètre `$id`, `something` de l'URL est passé comme `$something`, etc. Et si la méthode n'existe pas, la méthode `signalReceived` lève une [exception |api:Nette\Application\UI\BadSignalException]. -Le signal peut être reçu par n'importe quel composant, présentateur d'objet qui implémente l'interface `SignalReceiver` s'il est connecté à l'arbre des composants. +Un signal peut être reçu par n'importe quel composant, presenter ou objet qui implémente l'interface `SignalReceiver` et est connecté à l'arbre des composants. -Les principaux récepteurs de signaux sont `Presenters` et les composants visuels étendant `Control`. Un signal est un signe pour un objet qu'il doit faire quelque chose - un sondage compte dans un vote de l'utilisateur, la boîte avec des nouvelles doit se déplier, le formulaire a été envoyé et doit traiter des données et ainsi de suite. +Les principaux destinataires des signaux seront les `Presenters` et les composants visuels héritant de `Control`. Un signal doit servir de signe à un objet qu'il doit faire quelque chose – un sondage doit compter un vote d'un utilisateur, un bloc d'actualités doit se déplier et afficher deux fois plus d'actualités, un formulaire a été soumis et doit traiter les données, etc. -L'URL pour le signal est créé en utilisant la méthode [Component::link() |api:Nette\Application\UI\Component::link()]. Comme paramètre `$destination` nous passons la chaîne `{signal}!` et comme `$args` un tableau d'arguments que nous voulons passer au gestionnaire du signal. Les paramètres du signal sont attachés à l'URL du présentateur/de la vue en cours. **Le paramètre `?do` dans l'URL détermine le signal appelé.** +Nous créons l'URL pour un signal à l'aide de la méthode [Component::link() |api:Nette\Application\UI\Component::link()]. Comme paramètre `$destination`, nous passons la chaîne `{signal}!` et comme `$args`, un tableau d'arguments que nous voulons passer au signal. Le signal est toujours appelé sur le presenter et l'action actuels avec les paramètres actuels ; les paramètres du signal sont simplement ajoutés. De plus, le **paramètre `?do`, qui spécifie le signal**, est ajouté au début. -Son format est `{signal}` ou `{signalReceiver}-{signal}`. `{signalReceiver}` est le nom du composant dans le présentateur. C'est pourquoi le tiret ne peut pas être présent dans le nom des composants - il est utilisé pour diviser le nom du composant et du signal, mais il est possible de composer plusieurs composants. +Son format est soit `{signal}`, soit `{signalReceiver}-{signal}`. `{signalReceiver}` est le nom du composant dans le presenter. C'est pourquoi un trait d'union ne peut pas être utilisé dans le nom d'un composant – il est utilisé pour séparer le nom du composant et le signal, mais il est possible d'imbriquer plusieurs composants de cette manière. -La méthode [isSignalReceiver() |api:Nette\Application\UI\Presenter::isSignalReceiver()] vérifie si un composant (premier argument) est un récepteur d'un signal (deuxième argument). Le deuxième argument peut être omis - dans ce cas, la méthode vérifie si le composant est un récepteur d'un signal quelconque. Si le deuxième paramètre est `true`, elle vérifie si le composant ou ses descendants sont des récepteurs d'un signal. +La méthode [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] vérifie si le composant (premier argument) est le destinataire du signal (deuxième argument). Nous pouvons omettre le deuxième argument – il vérifie alors si le composant est le destinataire de n'importe quel signal. On peut passer `true` comme deuxième paramètre pour vérifier si non seulement le composant spécifié est le destinataire, mais aussi n'importe lequel de ses descendants. -Dans toute phase précédant `handle{Signal}`, le signal peut être exécuté manuellement en appelant la méthode [processSignal() |api:Nette\Application\UI\Presenter::processSignal()] qui prend en charge l'exécution du signal. Elle prend le composant récepteur (s'il n'est pas défini, c'est le présentateur lui-même) et lui envoie le signal. +À n'importe quelle étape précédant `handle{signal}`, nous pouvons exécuter le signal manuellement en appelant la méthode [processSignal()|api:Nette\Application\UI\Presenter::processSignal()], qui se charge de traiter le signal – elle prend le composant désigné comme destinataire du signal (s'il n'y a pas de destinataire spécifié, c'est le presenter lui-même) et lui envoie le signal. Exemple : @@ -460,4 +482,4 @@ if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, ' } ``` -Le signal est exécuté prématurément et il ne sera pas appelé à nouveau. +Le signal est ainsi exécuté prématurément et ne sera plus appelé à nouveau. diff --git a/application/fr/configuration.texy b/application/fr/configuration.texy index 5c6d8332df..aedc9c6739 100644 --- a/application/fr/configuration.texy +++ b/application/fr/configuration.texy @@ -1,58 +1,71 @@ -Configuration de l'application +Configuration des applications ****************************** .[perex] -Aperçu des options de configuration de l'application Nette. +Aperçu des options de configuration pour les applications Nette. -Application .[#toc-application] -=============================== +Application +=========== ```neon application: - # affiche le panneau "Nette Application" dans Tracy BlueScreen ? - debugger: ... # (bool) par défaut à true + # afficher le panneau "Nette Application" dans Tracy BlueScreen ? + debugger: ... # (bool) par défaut true - # le présentateur d'erreur sera-t-il appelé en cas d'erreur ? - catchExceptions: ... # (bool) vaut true par défaut en mode production + # l'error-presenter sera-t-il appelé en cas d'erreur ? + # n'a d'effet qu'en mode développement + catchExceptions: ... # (bool) par défaut true - # nom du présentateur d'erreur - errorPresenter: Error # (string) vaut par défaut 'Nette:Error'. + # nom de l'error-presenter + errorPresenter: Error # (string|array) par défaut 'Nette:Error' - # définit les règles pour résoudre le nom du présentateur vers une classe + # définit les alias pour les presenters et les actions + aliases: ... + + # définit les règles pour traduire le nom du presenter en classe mapping: ... - # les mauvais liens génèrent-ils des avertissements ? - # n'a d'effet qu'en mode développeur - silentLinks: ... # (bool) valeur par défaut: false + # les liens invalides ne génèrent pas d'avertissement ? + # n'a d'effet qu'en mode développement + silentLinks: ... # (bool) par défaut false ``` -Comme les présentateurs d'erreurs ne sont pas appelés par défaut en mode développement et que les erreurs sont affichées par Tracy, le fait de changer la valeur `catchExceptions` en `true` permet de vérifier que les présentateurs d'erreurs fonctionnent correctement pendant le développement. +Depuis la version 3.2 de `nette/application`, il est possible de définir une paire d'error-presenters : -L'option `silentLinks` détermine comment Nette se comporte en mode développeur lorsque la génération de liens échoue (par exemple, parce qu'il n'y a pas de présentateur, etc). La valeur par défaut `false` signifie que Nette déclenche `E_USER_WARNING`. Le réglage sur `true` supprime ce message d'erreur. Dans un environnement de production, `E_USER_WARNING` est toujours invoqué. Nous pouvons également influencer ce comportement en définissant la variable du présentateur [$invalidLinkMode |creating-links#Invalid Links]. +```neon +application: + errorPresenter: + 4xx: Error4xx # pour l'exception Nette\Application\BadRequestException + 5xx: Error5xx # pour les autres exceptions +``` -Le [mappage définit les règles |modules#mapping] selon lesquelles le nom de la classe est dérivé du nom du présentateur. +L'option `silentLinks` détermine comment Nette se comporte en mode développement lorsque la génération d'un lien échoue (par exemple, parce que le presenter n'existe pas, etc.). La valeur par défaut `false` signifie que Nette lèvera une erreur `E_USER_WARNING`. La définir sur `true` supprimera ce message d'erreur. En environnement de production, `E_USER_WARNING` est toujours levé. Ce comportement peut également être influencé en définissant la variable du presenter [$invalidLinkMode |creating-links#Liens invalides]. +Les [alias simplifient la création de liens |creating-links#Alias] vers les presenters fréquemment utilisés. -Enregistrement automatique des présentateurs .[#toc-automatic-registration-of-presenters] ------------------------------------------------------------------------------------------ +Le [mapping définit les règles |directory-structure#Mapping des presenters] selon lesquelles le nom de la classe est dérivé du nom du presenter. -Nette ajoute automatiquement les présentateurs en tant que services au conteneur DI, ce qui accélère considérablement leur création. La manière dont Nette découvre les diffuseurs peut être configurée : + +Enregistrement automatique des presenters +----------------------------------------- + +Nette ajoute automatiquement les presenters en tant que services au conteneur DI, ce qui accélère considérablement leur création. La manière dont Nette trouve les presenters peut être configurée : ```neon application: - # rechercher les diffuseurs dans la carte des classes Composer ? - scanComposer: ... # (bool) valeur par défaut: true + # rechercher les presenters dans la class map de Composer ? + scanComposer: ... # (bool) par défaut true - # un masque qui doit correspondre à la classe et au nom du fichier - scanFilter: ... # (string) Valeur par défaut: '*Presenter'. + # masque auquel le nom de la classe et du fichier doit correspondre + scanFilter: ... # (string) par défaut '*Presenter' - # dans quels répertoires chercher les diffuseurs ? - scanDirs: # (string[]|false) Valeur par défaut: '%appDir%'. + # dans quels répertoires rechercher les presenters ? + scanDirs: # (string[]|false) par défaut '%appDir%' - %vendorDir%/mymodule ``` -Les répertoires listés dans `scanDirs` ne remplacent pas la valeur par défaut `%appDir%`, mais la complètent, ainsi `scanDirs` contiendra les chemins `%appDir%` et `%vendorDir%/mymodule`. Si nous voulons remplacer le répertoire par défaut, nous utilisons le [point d'exclamation |dependency-injection:configuration#Merging]: +Les répertoires spécifiés dans `scanDirs` ne remplacent pas la valeur par défaut `%appDir%`, mais la complètent, donc `scanDirs` contiendra les deux chemins `%appDir%` et `%vendorDir%/mymodule`. Si nous voulions omettre le répertoire par défaut, nous utiliserions un [point d'exclamation |dependency-injection:configuration#Fusion], qui écrase la valeur : ```neon application: @@ -60,64 +73,73 @@ application: - %vendorDir%/mymodule ``` -L'analyse des répertoires peut être désactivée en définissant false. Nous ne recommandons pas de supprimer complètement l'ajout automatique de diffuseurs, sinon les performances de l'application seront réduites. +L'analyse des répertoires peut être désactivée en spécifiant la valeur `false`. Nous ne recommandons pas de supprimer complètement l'ajout automatique des presenters, car cela réduirait les performances de l'application. -Latte .[#toc-latte] -=================== +Templates Latte +=============== -Ce paramètre affecte globalement le comportement de Latte dans les composants et les présentateurs. +Ce paramètre permet d'influencer globalement le comportement de Latte dans les composants et les presenters. ```neon latte: - # affiche le panneau Latte dans la barre Tracy pour le modèle principal (true) ou pour tous les composants (all) ? - debugger: ... # (true|false|'all') vaut true par défaut + # afficher le panneau Latte dans la barre Tracy pour le template principal (true) ou tous les composants (all) ? + debugger: ... # (true|false|'all') par défaut true + + # génère les templates avec l'en-tête declare(strict_types=1) + strictTypes: ... # (bool) par défaut false - # génère des modèles avec declare(strict_types=1) - strictTypes: ... # (bool) vaut false par défaut + # active le mode [parseur strict |latte:develop#strict-mode] + strictParsing: ... # (bool) par défaut false - # classe de $this->template - templateClass: App\MyTemplateClass # Valeur par défaut: Nette\Bridges\ApplicationLatte\DefaultTemplate + # active le [contrôle du code généré |latte:develop#checking-generated-code] + phpLinter: ... # (string) par défaut null + + # définit la locale + locale: cs_CZ # (string) par défaut null + + # classe de l'objet $this->template + templateClass: App\MyTemplateClass # par défaut Nette\Bridges\ApplicationLatte\DefaultTemplate ``` -Si vous utilisez la version 3 de Latte, vous pouvez ajouter une nouvelle [extension |latte:creating-extension] en utilisant : +Si vous utilisez Latte version 3, vous pouvez ajouter de nouvelles [extensions |latte:extending-latte#Latte Extension] en utilisant : ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -/--comment - - - - - +Si vous utilisez Latte version 2, vous pouvez enregistrer de nouvelles balises soit en spécifiant le nom de la classe, soit par référence à un service. Par défaut, la méthode `install()` est appelée, mais cela peut être modifié en spécifiant le nom d'une autre méthode : +```neon +latte: + # enregistrement des balises Latte personnalisées + macros: + - App\MyLatteMacros::register # méthode statique, nom de classe ou callable + - @App\MyLatteMacrosFactory # service avec la méthode install() + - @App\MyLatteMacrosFactory::register # service avec la méthode register() + +services: + - App\MyLatteMacrosFactory +``` - - - -\-- - - -Routage .[#toc-routing] -======================= +Routage +======= Paramètres de base : ```neon -routage: - # affiche le panneau de routage dans Tracy Bar ? - debugger: ... # (bool) par défaut à true +routing: + # afficher le panneau de routage dans la barre Tracy ? + debugger: ... # (bool) par défaut true - # pour sérialiser le routeur dans le conteneur DI ? - cache: ... # (bool) par défaut à false + # sérialise le routeur dans le conteneur DI + cache: ... # (bool) par défaut false ``` -Le routeur est généralement défini dans la classe [RouterFactory |routing#Route Collection]. Il est également possible de définir les routeurs dans la configuration en utilisant les paires `mask: action`, mais cette méthode n'offre pas une aussi grande variation des paramètres : +Le routage est généralement défini dans la classe [RouterFactory |routing#Collection de routes]. Alternativement, les routes peuvent également être définies dans la configuration en utilisant des paires `masque: action`, mais cette méthode n'offre pas une aussi grande variété de paramètres : ```neon routing: @@ -127,8 +149,8 @@ routing: ``` -Constantes .[#toc-constants] -============================ +Constantes +========== Création de constantes PHP. @@ -137,18 +159,33 @@ constants: Foobar: 'baz' ``` -La constante `Foobar` sera créée après le démarrage. +Après le démarrage de l'application, la constante `Foobar` sera créée. .[note] -Les constantes ne doivent pas servir de variables disponibles dans le monde entier. Pour transmettre des valeurs aux objets, utilisez l'[injection de dépendances |dependency-injection:passing-dependencies]. +Les constantes ne doivent pas servir de variables globales. Pour transmettre des valeurs aux objets, utilisez l'[injection de dépendances |dependency-injection:passing-dependencies]. PHP === -Vous pouvez définir des directives PHP. Un aperçu de toutes les directives peut être trouvé sur [php.net |https://www.php.net/manual/en/ini.list.php]. +Configuration des directives PHP. Un aperçu de toutes les directives se trouve sur [php.net |https://www.php.net/manual/en/ini.list.php]. ```neon php: date.timezone: Europe/Prague ``` + + +Services DI +=========== + +Ces services sont ajoutés au conteneur DI : + +| Nom | Type | Description +|---------------------------------------------------------------------------------| +| `application.application` | [api:Nette\Application\Application] | [lanceur de toute l'application |how-it-works#Nette Application] +| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] +| `application.presenterFactory` | [api:Nette\Application\PresenterFactory] | factory de presenters +| `application.###` | [api:Nette\Application\UI\Presenter] | presenters individuels +| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | factory de l'objet `Latte\Engine` +| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | factory pour [`$this->template` |templates] diff --git a/application/fr/creating-links.texy b/application/fr/creating-links.texy index c6786ac362..1ceacf2bd0 100644 --- a/application/fr/creating-links.texy +++ b/application/fr/creating-links.texy @@ -3,158 +3,158 @@ Création de liens URL
    -Créer des liens dans Nette est aussi facile que de pointer un doigt. Il suffit de pointer et le framework fera tout le travail pour vous. Nous allons vous montrer : +Créer des liens dans Nette est aussi simple que de pointer du doigt. Il suffit de viser et le framework fait tout le travail pour vous. Nous allons montrer : -- comment créer des liens dans les modèles et ailleurs -- comment distinguer un lien de la page actuelle -- qu'en est-il des liens invalides ? +- comment créer des liens dans les templates et ailleurs +- comment distinguer un lien vers la page actuelle +- que faire des liens invalides
    -Grâce au [routage bidirectionnel |routing], vous n'aurez jamais à coder en dur les URL des applications dans les modèles ou le code, qui peuvent changer ultérieurement ou être compliqués à composer. Il suffit de spécifier le présentateur et l'action dans le lien, de passer des paramètres et le framework générera l'URL lui-même. En fait, c'est très similaire à l'appel d'une fonction. Vous allez l'apprécier. +Grâce au [routage bidirectionnel |routing], vous n'aurez jamais à écrire en dur les URL de votre application dans les templates ou le code, URL qui pourraient changer plus tard, ou à les composer de manière compliquée. Dans le lien, il suffit d'indiquer le presenter et l'action, de passer d'éventuels paramètres, et le framework générera l'URL lui-même. En fait, c'est très similaire à l'appel d'une fonction. Vous allez adorer ça. -Dans le modèle de présentateur .[#toc-in-the-presenter-template] -================================================================ +Dans le template du presenter +============================= -Le plus souvent, nous créons des liens dans les modèles et l'attribut `n:href` est d'une grande aide : +Le plus souvent, nous créons des liens dans les templates et l'attribut `n:href` est un excellent assistant : ```latte -detail +détail ``` -Notez qu'au lieu de l'attribut HTML `href`, nous avons utilisé l'[attribut n: |latte:syntax#n:attributes] `n:href`. Sa valeur n'est pas une URL, comme vous avez l'habitude de le faire avec l'attribut `href`, mais le nom du présentateur et de l'action. +Notez qu'au lieu de l'attribut HTML `href`, nous avons utilisé un [n:attribut |latte:syntax#n:attributs] `n:href`. Sa valeur n'est alors pas une URL, comme ce serait le cas avec l'attribut `href`, mais le nom du presenter et de l'action. -Cliquer sur un lien revient, pour simplifier, à appeler une méthode `ProductPresenter::renderShow()`. Et si elle a des paramètres dans sa signature, nous pouvons l'appeler avec des arguments : +Cliquer sur le lien est, pour simplifier, quelque chose comme appeler la méthode `ProductPresenter::renderShow()`. Et si elle a des paramètres dans sa signature, nous pouvons l'appeler avec des arguments : ```latte -detail +détail du produit ``` -Il est également possible de passer des paramètres nommés. Le lien suivant passe le paramètre `lang` avec la valeur `en`: +Il est également possible de passer des paramètres nommés. Le lien suivant passe le paramètre `lang` avec la valeur `cs` : ```latte -detail +détail du produit ``` -Si la méthode `ProductPresenter::renderShow()` n'a pas `$lang` dans sa signature, elle peut lire la valeur du paramètre en utilisant `$lang = $this->getParameter('lang')`. +Si la méthode `ProductPresenter::renderShow()` n'a pas `$lang` dans sa signature, elle peut récupérer la valeur du paramètre en utilisant `$lang = $this->getParameter('lang')` ou depuis une [propriété |presenters#Paramètres de la requête]. -Si les paramètres sont stockés dans un tableau, ils peuvent être développés avec l'opérateur `...` (ou `(expand)` dans Latte 2.x) : +Si les paramètres sont stockés dans un tableau, ils peuvent être développés avec l'opérateur `...` (dans Latte 2.x, l'opérateur `(expand)`) : ```latte -{var $args = [$product->id, lang => en]} -detail +{var $args = [$product->id, lang => cs]} +détail du produit ``` -Les [paramètres |presenters#persistent parameters] dits [persistants |presenters#persistent parameters] sont également transmis automatiquement dans les liens. +Les liens transmettent également automatiquement les [paramètres persistants |presenters#Paramètres persistants]. -L'attribut `n:href` est très pratique pour les balises HTML ``. Si nous voulons imprimer le lien ailleurs, par exemple dans le texte, nous utilisons `{link}`: +L'attribut `n:href` est très pratique pour les balises HTML ``. Si nous voulons afficher le lien ailleurs, par exemple dans du texte, nous utilisons `{link}` : ```latte -URL is: {link Home:default} +L'adresse est : {link Home:default} ``` -Dans le code .[#toc-in-the-code] -================================ +Dans le code +============ -La méthode `link()` est utilisée pour créer un lien dans le présentateur : +Pour créer un lien dans le presenter, la méthode `link()` est utilisée : ```php $url = $this->link('Product:show', $product->id); ``` -Les paramètres peuvent également être transmis sous forme de tableau où des paramètres nommés peuvent également être spécifiés : +Les paramètres peuvent également être passés via un tableau, où des paramètres nommés peuvent également être spécifiés : ```php $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); ``` -Les liens peuvent également être créés sans présentateur, en utilisant le [LinkGenerator |#LinkGenerator] et sa méthode `link()`. +Les liens peuvent également être créés sans presenter, c'est à cela que sert [#LinkGenerator] et sa méthode `link()`. -Liens vers le présentateur .[#toc-links-to-presenter] -===================================================== +Liens vers un presenter +======================= -Si la cible du lien est le présentateur et l'action, il a cette syntaxe : +Si la cible du lien est un presenter et une action, la syntaxe est la suivante : ``` [//] [[[[:]module:]presenter:]action | this] [#fragment] ``` -Le format est pris en charge par toutes les balises Latte et toutes les méthodes du présentateur qui fonctionnent avec des liens, à savoir `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` et également [LinkGenerator |#LinkGenerator]. Ainsi, même si `n:href` est utilisé dans les exemples, il pourrait s'agir de n'importe laquelle de ces fonctions. +Ce format est pris en charge par toutes les balises Latte et toutes les méthodes du presenter qui fonctionnent avec des liens, c'est-à-dire `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` et aussi [#LinkGenerator]. Ainsi, même si `n:href` est utilisé dans les exemples, n'importe laquelle de ces fonctions pourrait être là. -La forme de base est donc `Presenter:action`: +La forme de base est donc `Presenter:action` : ```latte -home +page d'accueil ``` -Si nous établissons un lien avec l'action du présentateur actuel, nous pouvons omettre son nom : +Si nous lions vers une action du presenter actuel, nous pouvons omettre son nom : ```latte -home +page d'accueil ``` -Si l'action est `default`, nous pouvons l'omettre, mais les deux points doivent rester : +Si la cible est l'action `default`, nous pouvons l'omettre, mais les deux-points doivent rester : ```latte -home +page d'accueil ``` -Les liens peuvent également pointer vers d'autres [modules]. Ici, on distingue les liens relatifs aux sous-modules et les liens absolus. Le principe est analogue à celui des chemins d'accès aux disques, mais les deux-points remplacent les barres obliques. Supposons que le présentateur actuel fasse partie du module `Front`, nous écrirons alors : +Les liens peuvent également pointer vers d'autres [modules |directory-structure#Presenters et templates]. Ici, les liens sont distingués comme relatifs à un sous-module imbriqué, ou absolus. Le principe est analogue aux chemins sur disque, sauf que les deux-points remplacent les barres obliques. Supposons que le presenter actuel fasse partie du module `Front`, alors nous écririons : ```latte -link to Front:Shop:Product:show -link to Admin:Product:show +lien vers Front:Shop:Product:show +lien vers Admin:Product:show ``` -Un cas particulier est la [liaison à lui-même |#Links to Current Page]. Ici, nous écrirons `this` comme cible. +Un cas spécial est un lien [vers soi-même |#Lien vers la page actuelle], où nous spécifions `this` comme cible. ```latte -refresh +rafraîchir ``` -Nous pouvons créer un lien vers une certaine partie de la page HTML par le biais de ce que l'on appelle un fragment après le symbole dièse `#` : +Nous pouvons lier vers une partie spécifique de la page via un fragment après le signe dièse `#` : ```latte -link to Home:default and fragment #main +lien vers Home:default et le fragment #main ``` -Chemins absolus .[#toc-absolute-paths] -====================================== +Chemins absolus +=============== -Les liens générés par `link()` ou `n:href` sont toujours des chemins absolus (c'est-à-dire qu'ils commencent par `/`), mais pas des URL absolus avec un protocole et un domaine comme `https://domain`. +Les liens générés à l'aide de `link()` ou `n:href` sont toujours des chemins absolus (c'est-à-dire qu'ils commencent par `/`), mais pas des URL absolues avec protocole et domaine comme `https://domain`. -Pour générer une URL absolue, ajoutez deux barres obliques au début (par exemple, `n:href="//Home:"`). Vous pouvez aussi faire en sorte que le diffuseur ne génère que des liens absolus en définissant `$this->absoluteUrls = true`. +Pour générer une URL absolue, ajoutez deux barres obliques au début (par ex. `n:href="//Home:"`). Ou vous pouvez configurer le presenter pour ne générer que des liens absolus en définissant `$this->absoluteUrls = true`. -Lien vers la page actuelle .[#toc-link-to-current-page] -======================================================= +Lien vers la page actuelle +========================== -La cible `this` créera un lien vers la page actuelle : +La cible `this` crée un lien vers la page actuelle : ```latte -refresh +rafraîchir ``` -En même temps, tous les paramètres spécifiés dans la signature de l'instruction `render()` ou `action()` sont transférés. Ainsi, si nous sommes sur les pages `Product:show` et `id:123`, le lien vers `this` transmettra également ce paramètre. +En même temps, tous les paramètres spécifiés dans la signature de la méthode `action()` ou `render()` sont également transmis, si `action()` n'est pas définie. Donc, si nous sommes sur la page `Product:show` avec `id: 123`, le lien vers `this` transmettra également ce paramètre. Bien sûr, il est possible de spécifier les paramètres directement : ```latte -refresh +rafraîchir ``` -La fonction `isLinkCurrent()` détermine si la cible du lien est la même que la page actuelle. Cela peut être utilisé, par exemple, dans un modèle pour différencier les liens, etc. +La fonction `isLinkCurrent()` vérifie si la cible du lien est identique à la page actuelle. Cela peut être utilisé, par exemple, dans un template pour distinguer les liens, etc. -Les paramètres sont les mêmes que pour la méthode `link()`, mais il est également possible d'utiliser le joker `*` au lieu d'une action spécifique, c'est-à-dire n'importe quelle action du présentateur. +Les paramètres sont les mêmes que pour la méthode `link()`, mais il est en plus possible de spécifier un caractère générique `*` à la place d'une action spécifiques, ce qui signifie n'importe quelle action du presenter donné. ```latte {if !isLinkCurrent('Admin:login')} - Přihlaste se + Connectez-vous {/if}
  • @@ -162,27 +162,27 @@ Les paramètres sont les mêmes que pour la méthode `link()`, mais il est égal
  • ``` -Une forme abrégée peut être utilisée en combinaison avec `n:href` dans un seul élément : +En combinaison avec `n:href` dans un seul élément, une forme abrégée peut être utilisée : ```latte -... +... ``` -Le caractère générique `*` remplace uniquement l'action du présentateur, pas le présentateur lui-même. +Le caractère générique `*` ne peut être utilisé qu'à la place de l'action, pas du presenter. -Pour savoir si nous sommes dans un certain module ou son sous-module, nous pouvons utiliser la fonction `isModuleCurrent(moduleName)`. +Pour vérifier si nous sommes dans un certain module ou son sous-module, utilisez la méthode `isModuleCurrent(moduleName)`. ```latte -
  • +
  • ...
  • ``` -Liens vers le signal .[#toc-links-to-signal] -============================================ +Liens vers un signal +==================== -La cible du lien peut être non seulement le présentateur et l'action, mais aussi le [signal |components#Signal] (ils appellent la méthode `handle()`). La syntaxe est la suivante : +La cible d'un lien ne doit pas nécessairement être seulement un presenter et une action, mais aussi un [signal |components#Signal] (ils appellent la méthode `handle()`). La syntaxe est alors la suivante : ``` [//] [sub-component:]signal! [#fragment] @@ -194,26 +194,26 @@ Le signal est donc distingué par un point d'exclamation : signal ``` -Vous pouvez également créer un lien vers le signal de la sous-composante (ou sous-sous-composante) : +Il est également possible de créer un lien vers le signal d'un sous-composant (ou sous-sous-composant) : ```latte signal ``` -Liens dans le composant .[#toc-links-in-component] -================================================== +Liens dans un composant +======================= -Comme les [composants |components] sont des unités distinctes réutilisables qui ne doivent avoir aucune relation avec les présentateurs environnants, les liens fonctionnent un peu différemment. L'attribut Latte `n:href` et la balise `{link}` ainsi que les méthodes de composants telles que `link()` et autres considèrent toujours la cible **comme le nom du signal**. Il n'est donc pas nécessaire d'utiliser un point d'exclamation : +Parce que les [composants|components] sont des unités autonomes et réutilisables qui ne devraient avoir aucun lien avec les presenters environnants, les liens fonctionnent un peu différemment ici. L'attribut Latte `n:href` et la balise `{link}` ainsi que les méthodes de composant comme `link()` et autres considèrent la cible du lien **toujours comme le nom d'un signal**. Par conséquent, il n'est même pas nécessaire d'inclure un point d'exclamation : ```latte -signal, not an action +signal, pas une action ``` -Si nous voulons créer un lien vers les présentateurs dans le modèle de composant, nous utilisons la balise `{plink}`: +Si nous voulions lier vers des presenters dans le template d'un composant, nous utiliserions la balise `{plink}` : ```latte -home +accueil ``` ou dans le code @@ -223,17 +223,41 @@ $this->getPresenter()->link('Home:default') ``` -Liens non valides .[#toc-invalid-links] -======================================= +Alias .{data-version:v3.2.2} +============================ -Il peut arriver que nous créions un lien invalide - soit parce qu'il fait référence à un présentateur inexistant, soit parce qu'il passe plus de paramètres que la méthode cible ne reçoit dans sa signature, soit lorsqu'il ne peut y avoir d'URL générée pour l'action ciblée. Ce qu'il faut faire avec les liens invalides est déterminé par la variable statique `Presenter::$invalidLinkMode`. Elle peut avoir l'une de ces valeurs (constantes) : +Parfois, il peut être utile d'attribuer un alias facile à mémoriser à une paire Presenter:action. Par exemple, nommer la page d'accueil `Front:Home:default` simplement `home` ou `Admin:Dashboard:default` comme `admin`. -- `Presenter::InvalidLinkSilent` - mode silencieux, renvoie le symbole `#` comme URL -- `Presenter::InvalidLinkWarning` - E_USER_WARNING sera produit -- `Presenter::InvalidLinkTextual` - avertissement visuel, le texte de l'erreur est affiché dans le lien -- `Presenter::InvalidLinkException` - Une exception InvalidLinkException sera lancée. +Les alias sont définis dans la [configuration|configuration] sous la clé `application › aliases` : -La configuration par défaut en mode production est `InvalidLinkWarning` et en mode développement est `InvalidLinkWarning | InvalidLinkTextual`. `InvalidLinkWarning` ne tue pas le script dans l'environnement de production, mais l'avertissement sera enregistré. Dans l'environnement de développement, [Tracy |tracy:] interceptera l'avertissement et affichera l'écran bleu d'erreur. Si l'adresse `InvalidLinkTextual` est définie, le présentateur et les composants renvoient le message d'erreur sous forme d'URL dont l'étoile est `#error:`. Pour rendre ces liens visibles, nous pouvons ajouter une règle CSS à notre feuille de style : +```neon +application: + aliases: + home: Front:Home:default + admin: Admin:Dashboard:default + sign: Front:Sign:in +``` + +Dans les liens, ils sont ensuite écrits en utilisant un arobase, par exemple : + +```latte +administration +``` + +Ils sont également pris en charge dans toutes les méthodes fonctionnant avec des liens, comme `redirect()` et similaires. + + +Liens invalides +=============== + +Il peut arriver que nous créions un lien invalide - soit parce qu'il mène à un presenter inexistant, soit parce qu'il passe plus de paramètres que la méthode cible n'en accepte dans sa signature, soit lorsqu'une URL ne peut pas être générée pour l'action cible. La manière de traiter les liens invalides est déterminée par la variable statique `Presenter::$invalidLinkMode`. Elle peut prendre une combinaison de ces valeurs (constantes) : + +- `Presenter::InvalidLinkSilent` - mode silencieux, le caractère # est retourné comme URL +- `Presenter::InvalidLinkWarning` - un avertissement E_USER_WARNING est généré, qui sera enregistré en mode production, mais n'interrompra pas l'exécution du script +- `Presenter::InvalidLinkTextual` - avertissement visuel, affiche l'erreur directement dans le lien +- `Presenter::InvalidLinkException` - une exception InvalidLinkException est levée + +Le paramètre par défaut est `InvalidLinkWarning` en mode production et `InvalidLinkWarning | InvalidLinkTextual` en mode développement. `InvalidLinkWarning` en environnement de production ne provoque pas l'interruption du script, mais l'avertissement sera enregistré. En environnement de développement, il est intercepté par [Tracy |tracy:] et affiche un écran bleu. `InvalidLinkTextual` fonctionne en retournant un message d'erreur comme URL, commençant par les caractères `#error:`. Pour rendre ces liens visibles au premier coup d'œil, ajoutons à notre CSS : ```css a[href^="#error:"] { @@ -242,7 +266,7 @@ a[href^="#error:"] { } ``` -Si nous ne voulons pas que des avertissements soient produits dans l'environnement de développement, nous pouvons activer le mode de lien invalide silencieux dans la [configuration]. +Si nous ne voulons pas que des avertissements soient produits en environnement de développement, nous pouvons définir le mode silencieux directement dans la [configuration|configuration]. ```neon application: @@ -250,13 +274,13 @@ application: ``` -Générateur de liens .[#toc-linkgenerator] -========================================= +LinkGenerator +============= -Comment créer des liens avec le confort de la méthode `link()`, mais sans la présence d'un présentateur ? C'est pourquoi voici [api:Nette\Application\LinkGenerator]. +Comment créer des liens avec un confort similaire à celui de la méthode `link()`, mais sans la présence d'un presenter ? C'est là qu'intervient [api:Nette\Application\LinkGenerator]. -LinkGenerator est un service que vous pouvez faire passer par le constructeur et ensuite créer des liens en utilisant sa méthode `link()`. +LinkGenerator est un service que vous pouvez vous faire passer via le constructeur et ensuite créer des liens avec sa méthode `link()`. -Il y a une différence par rapport aux présentateurs. LinkGenerator crée tous les liens sous forme d'URL absolus. De plus, il n'y a pas de "présentateur actuel", il n'est donc pas possible de spécifier uniquement le nom de l'action `link('default')` ou les chemins relatifs vers les [modules]. +Il y a une différence par rapport aux presenters. LinkGenerator crée tous les liens directement comme des URL absolues. De plus, il n'y a pas de "presenter actuel", il n'est donc pas possible de spécifier uniquement le nom de l'action comme cible `link('default')` ou d'utiliser des chemins relatifs vers les modules. -Les liens non valides provoquent toujours une erreur à l'adresse `Nette\Application\UI\InvalidLinkException`. +Les liens invalides lèvent toujours `Nette\Application\UI\InvalidLinkException`. diff --git a/application/fr/directory-structure.texy b/application/fr/directory-structure.texy new file mode 100644 index 0000000000..48cf3ac4bd --- /dev/null +++ b/application/fr/directory-structure.texy @@ -0,0 +1,526 @@ +Structure des répertoires de l'application +****************************************** + +
    + +Comment concevoir une structure de répertoires claire et évolutive pour les projets Nette Framework ? Nous vous montrerons les meilleures pratiques qui vous aideront à organiser votre code. Vous apprendrez : + +- comment **diviser logiquement** l'application en répertoires +- comment concevoir la structure pour qu'elle **évolue bien** avec la croissance du projet +- quelles sont les **alternatives possibles** et leurs avantages ou inconvénients + +
    + + +Il est important de mentionner que Nette Framework lui-même n'impose aucune structure spécifique. Il est conçu pour être facilement adaptable à tous les besoins et préférences. + + +Structure de base du projet +=========================== + +Bien que Nette Framework ne dicte aucune structure de répertoires fixe, il existe une disposition par défaut éprouvée sous la forme du [Web Project|https://github.com/nette/web-project] : + +/--pre +web-project/ +├── app/ ← répertoire de l'application +├── assets/ ← fichiers SCSS, JS, images..., alternativement resources/ +├── bin/ ← scripts pour la ligne de commande +├── config/ ← configuration +├── log/ ← erreurs journalisées +├── temp/ ← fichiers temporaires, cache +├── tests/ ← tests +├── vendor/ ← bibliothèques installées par Composer +└── www/ ← répertoire public (document-root) +\-- + +Vous pouvez modifier cette structure librement selon vos besoins - renommer ou déplacer des dossiers. Ensuite, il suffit de modifier les chemins relatifs vers les répertoires dans le fichier `Bootstrap.php` et éventuellement `composer.json`. Rien de plus n'est nécessaire, pas de reconfiguration complexe, pas de changement de constantes. Nette dispose d'une autodétection intelligente et reconnaît automatiquement l'emplacement de l'application, y compris sa base d'URL. + + +Principes d'organisation du code +================================ + +Lorsque vous explorez un nouveau projet pour la première fois, vous devriez pouvoir vous y orienter rapidement. Imaginez que vous ouvrez le répertoire `app/Model/` et voyez cette structure : + +/--pre +app/Model/ +├── Services/ +├── Repositories/ +└── Entities/ +\-- + +Vous en déduisez seulement que le projet utilise des services, des dépôts et des entités. Vous n'apprenez rien sur le but réel de l'application. + +Regardons une autre approche - **l'organisation par domaines** : + +/--pre +app/Model/ +├── Cart/ +├── Payment/ +├── Order/ +└── Product/ +\-- + +Ici, c'est différent - au premier coup d'œil, il est clair qu'il s'agit d'une boutique en ligne. Les noms mêmes des répertoires révèlent ce que l'application fait - elle travaille avec les paiements, les commandes et les produits. + +La première approche (organisation par type de classe) pose en pratique de nombreux problèmes : le code qui est logiquement lié est dispersé dans différents dossiers et vous devez passer de l'un à l'autre. C'est pourquoi nous organiserons par domaines. + + +Espaces de noms +--------------- + +Il est d'usage que la structure des répertoires corresponde aux espaces de noms dans l'application. Cela signifie que l'emplacement physique des fichiers correspond à leur namespace. Par exemple, une classe située dans `app/Model/Product/ProductRepository.php` devrait avoir le namespace `App\Model\Product`. Ce principe aide à s'orienter dans le code et simplifie l'autoloading. + + +Singulier vs pluriel dans les noms +---------------------------------- + +Notez que pour les répertoires principaux de l'application, nous utilisons le singulier : `app`, `config`, `log`, `temp`, `www`. De même à l'intérieur de l'application : `Model`, `Core`, `Presentation`. C'est parce que chacun d'eux représente un concept cohérent unique. + +De même, par exemple, `app/Model/Product` représente tout ce qui concerne les produits. Nous ne l'appellerons pas `Products`, car ce n'est pas un dossier plein de produits (il y aurait des fichiers `nokia.php`, `samsung.php`). C'est un namespace contenant des classes pour travailler avec les produits - `ProductRepository.php`, `ProductService.php`. + +Le dossier `app/Tasks` est au pluriel car il contient un ensemble de scripts exécutables distincts - `CleanupTask.php`, `ImportTask.php`. Chacun d'eux est une unité distincte. + +Pour la cohérence, nous recommandons d'utiliser : +- Le singulier pour un namespace représentant une unité fonctionnelle (même s'il travaille avec plusieurs entités) +- Le pluriel pour les collections d'unités distinctes +- En cas d'incertitude ou si vous ne voulez pas y penser, choisissez le singulier + + +Répertoire public `www/` +======================== + +Ce répertoire est le seul accessible depuis le web (appelé document-root). Vous pouvez souvent rencontrer le nom `public/` au lieu de `www/` - c'est juste une question de convention et cela n'affecte pas la fonctionnalité du framework. Le répertoire contient : +- Le [point d'entrée |bootstrapping#index.php] de l'application `index.php` +- Le fichier `.htaccess` avec les règles pour mod_rewrite (pour Apache) +- Les fichiers statiques (CSS, JavaScript, images) +- Les fichiers uploadés + +Pour une sécurité correcte de l'application, il est essentiel d'avoir le [document-root correctement configuré |nette:troubleshooting#Comment changer ou supprimer le répertoire www de l URL]. + +.[note] +Ne placez jamais le dossier `node_modules/` dans ce répertoire - il contient des milliers de fichiers qui peuvent être exécutables et ne devraient pas être accessibles publiquement. + + +Répertoire applicatif `app/` +============================ + +C'est le répertoire principal contenant le code de l'application. Structure de base : + +/--pre +app/ +├── Core/ ← questions d'infrastructure +├── Model/ ← logique métier +├── Presentation/ ← presenters et templates +├── Tasks/ ← scripts de commande +└── Bootstrap.php ← classe de démarrage de l'application +\-- + +`Bootstrap.php` est la [classe de démarrage de l'application|bootstrapping] qui initialise l'environnement, charge la configuration et crée le conteneur DI. + +Examinons maintenant plus en détail les différents sous-répertoires. + + +Presenters et templates +======================= + +La partie présentation de l'application se trouve dans le répertoire `app/Presentation`. Une alternative est le court `app/UI`. C'est l'endroit pour tous les presenters, leurs templates et d'éventuelles classes auxiliaires. + +Nous organisons cette couche par domaines. Dans un projet complexe combinant une boutique en ligne, un blog et une API, la structure ressemblerait à ceci : + +/--pre +app/Presentation/ +├── Shop/ ← frontend de la boutique en ligne +│ ├── Product/ +│ ├── Cart/ +│ └── Order/ +├── Blog/ ← blog +│ ├── Home/ +│ └── Post/ +├── Admin/ ← administration +│ ├── Dashboard/ +│ └── Products/ +└── Api/ ← points de terminaison API + └── V1/ +\-- + +Inversement, pour un blog simple, nous utiliserions la division suivante : + +/--pre +app/Presentation/ +├── Front/ ← frontend du site +│ ├── Home/ +│ └── Post/ +├── Admin/ ← administration +│ ├── Dashboard/ +│ └── Posts/ +├── Error/ +└── Export/ ← RSS, sitemaps etc. +\-- + +Les dossiers comme `Home/` ou `Dashboard/` contiennent les presenters et les templates. Les dossiers comme `Front/`, `Admin/` ou `Api/` sont appelés **modules**. Techniquement, ce sont des répertoires normaux qui servent à la division logique de l'application. + +Chaque dossier avec un presenter contient un presenter du même nom et ses templates. Par exemple, le dossier `Dashboard/` contient : + +/--pre +Dashboard/ +├── DashboardPresenter.php ← presenter +└── default.latte ← template +\-- + +Cette structure de répertoires se reflète dans les espaces de noms des classes. Par exemple, `DashboardPresenter` se trouve dans le namespace `App\Presentation\Admin\Dashboard` (voir [#Mapping des presenters]) : + +```php +namespace App\Presentation\Admin\Dashboard; + +class DashboardPresenter extends Nette\Application\UI\Presenter +{ + // ... +} +``` + +Nous faisons référence au presenter `Dashboard` à l'intérieur du module `Admin` dans l'application en utilisant la notation à deux points comme `Admin:Dashboard`. À son action `default` ensuite comme `Admin:Dashboard:default`. En cas de modules imbriqués, nous utilisons plus de deux points, par exemple `Shop:Order:Detail:default`. + + +Développement flexible de la structure +-------------------------------------- + +L'un des grands avantages de cette structure est la manière élégante dont elle s'adapte aux besoins croissants du projet. Prenons comme exemple la partie générant les flux XML. Au début, nous avons une forme simple : + +/--pre +Export/ +├── ExportPresenter.php ← un presenter pour toutes les exportations +├── sitemap.latte ← template pour le sitemap +└── feed.latte ← template pour le flux RSS +\-- + +Avec le temps, d'autres types de flux sont ajoutés et nous avons besoin de plus de logique pour eux... Pas de problème ! Le dossier `Export/` devient simplement un module : + +/--pre +Export/ +├── Sitemap/ +│ ├── SitemapPresenter.php +│ └── sitemap.latte +└── Feed/ + ├── FeedPresenter.php + ├── zbozi.latte ← flux pour Zboží.cz + └── heureka.latte ← flux pour Heureka.cz +\-- + +Cette transformation est absolument fluide - il suffit de créer de nouveaux sous-dossiers, d'y répartir le code et de mettre à jour les liens (par exemple de `Export:feed` à `Export:Feed:zbozi`). Grâce à cela, nous pouvons étendre progressivement la structure selon les besoins, le niveau d'imbrication n'est pas limité. + +Si, par exemple, dans l'administration, vous avez de nombreux presenters concernant la gestion des commandes, tels que `OrderDetail`, `OrderEdit`, `OrderDispatch`, etc., vous pouvez créer un module (dossier) `Order` à cet endroit pour une meilleure organisation, qui contiendra (les dossiers pour) les presenters `Detail`, `Edit`, `Dispatch` et autres. + + +Emplacement des templates +------------------------- + +Dans les exemples précédents, nous avons vu que les templates sont placés directement dans le dossier avec le presenter : + +/--pre +Dashboard/ +├── DashboardPresenter.php ← presenter +├── DashboardTemplate.php ← classe optionnelle pour le template +└── default.latte ← template +\-- + +Cet emplacement s'avère le plus pratique en pratique - vous avez tous les fichiers associés à portée de main. + +Alternativement, vous pouvez placer les templates dans un sous-dossier `templates/`. Nette prend en charge les deux variantes. Vous pouvez même placer les templates complètement en dehors du dossier `Presentation/`. Tout sur les options d'emplacement des templates se trouve dans le chapitre [Recherche de templates |templates#Recherche de templates]. + + +Classes auxiliaires et composants +--------------------------------- + +Aux presenters et templates s'ajoutent souvent d'autres fichiers auxiliaires. Nous les plaçons logiquement en fonction de leur portée : + +1. **Directement avec le presenter** dans le cas de composants spécifiques pour ce presenter : + +/--pre +Product/ +├── ProductPresenter.php +├── ProductGrid.php ← composant pour l'affichage des produits +└── FilterForm.php ← formulaire pour le filtrage +\-- + +2. **Pour le module** - nous recommandons d'utiliser le dossier `Accessory`, qui se place de manière claire au début de l'alphabet : + +/--pre +Front/ +├── Accessory/ +│ ├── NavbarControl.php ← composants pour le frontend +│ └── TemplateFilters.php +├── Product/ +└── Cart/ +\-- + +3. **Pour toute l'application** - dans `Presentation/Accessory/` : +/--pre +app/Presentation/ +├── Accessory/ +│ ├── LatteExtension.php +│ └── TemplateFilters.php +├── Front/ +└── Admin/ +\-- + +Ou vous pouvez placer les classes auxiliaires comme `LatteExtension.php` ou `TemplateFilters.php` dans le dossier d'infrastructure `app/Core/Latte/`. Et les composants dans `app/Components`. Le choix dépend des habitudes de l'équipe. + + +Modèle - cœur de l'application +============================== + +Le modèle contient toute la logique métier de l'application. Pour son organisation, la règle s'applique à nouveau - nous structurons par domaines : + +/--pre +app/Model/ +├── Payment/ ← tout ce qui concerne les paiements +│ ├── PaymentFacade.php ← point d'entrée principal +│ ├── PaymentRepository.php +│ ├── Payment.php ← entité +├── Order/ ← tout ce qui concerne les commandes +│ ├── OrderFacade.php +│ ├── OrderRepository.php +│ ├── Order.php +└── Shipping/ ← tout ce qui concerne la livraison +\-- + +Dans le modèle, vous rencontrerez généralement ces types de classes : + +**Façades**: représentent le point d'entrée principal dans un domaine spécifique de l'application. Elles agissent comme un orchestrateur qui coordonne la coopération entre différents services afin d'implémenter des cas d'utilisation complets (comme "créer une commande" ou "traiter un paiement"). Sous sa couche d'orchestration, la façade masque les détails d'implémentation au reste de l'application, offrant ainsi une interface propre pour travailler avec le domaine donné. + +```php +class OrderFacade +{ + public function createOrder(Cart $cart): Order + { + // validation + // création de la commande + // envoi d'e-mail + // écriture dans les statistiques + } +} +``` + +**Services**: se concentrent sur une opération métier spécifique au sein du domaine. Contrairement à la façade, qui orchestre des cas d'utilisation entiers, un service implémente une logique métier spécifique (comme le calcul des prix ou le traitement des paiements). Les services sont typiquement sans état et peuvent être utilisés soit par les façades comme blocs de construction pour des opérations plus complexes, soit directement par d'autres parties de l'application pour des tâches plus simples. + +```php +class PricingService +{ + public function calculateTotal(Order $order): Money + { + // calcul du prix + } +} +``` + +**Dépôts (Repositories)**: assurent toute la communication avec le stockage de données, typiquement une base de données. Leur tâche est de charger et de sauvegarder des entités et d'implémenter des méthodes pour leur recherche. Le dépôt isole le reste de l'application des détails d'implémentation de la base de données et fournit une interface orientée objet pour travailler avec les données. + +```php +class OrderRepository +{ + public function find(int $id): ?Order + { + } + + public function findByCustomer(int $customerId): array + { + } +} +``` + +**Entités**: objets représentant les principaux concepts métier dans l'application, qui ont leur propre identité et changent avec le temps. Il s'agit généralement de classes mappées sur des tables de base de données à l'aide d'un ORM (comme Nette Database Explorer ou Doctrine). Les entités peuvent contenir des règles métier concernant leurs données et une logique de validation. + +```php +// Entité mappée sur la table de base de données orders +class Order extends Nette\Database\Table\ActiveRow +{ + public function addItem(Product $product, int $quantity): void + { + $this->related('order_items')->insert([ + 'product_id' => $product->id, + 'quantity' => $quantity, + 'unit_price' => $product->price, + ]); + } +} +``` + +**Objets Valeur (Value Objects)**: objets immuables représentant des valeurs sans identité propre - par exemple, un montant monétaire ou une adresse e-mail. Deux instances d'un objet valeur avec les mêmes valeurs sont considérées comme identiques. + + +Code d'infrastructure +===================== + +Le dossier `Core/` (ou aussi `Infrastructure/`) abrite la base technique de l'application. Le code d'infrastructure comprend généralement : + +/--pre +app/Core/ +├── Router/ ← routage et gestion des URL +│ └── RouterFactory.php +├── Security/ ← authentification et autorisation +│ ├── Authenticator.php +│ └── Authorizator.php +├── Logging/ ← journalisation et surveillance +│ ├── SentryLogger.php +│ └── FileLogger.php +├── Cache/ ← couche de mise en cache +│ └── FullPageCache.php +└── Integration/ ← intégration avec les services ext. + ├── Slack/ + └── Stripe/ +\-- + +Pour les petits projets, une structure plate suffit bien sûr : + +/--pre +Core/ +├── RouterFactory.php +├── Authenticator.php +└── QueueMailer.php +\-- + +Il s'agit de code qui : + +- Gère l'infrastructure technique (routage, journalisation, mise en cache) +- Intègre des services externes (Sentry, Elasticsearch, Redis) +- Fournit des services de base pour toute l'application (mail, base de données) +- Est généralement indépendant du domaine spécifique - le cache ou le logger fonctionne de la même manière pour une boutique en ligne ou un blog. + +Vous hésitez si une certaine classe appartient ici ou au modèle ? La différence clé est que le code dans `Core/` : + +- Ne sait rien du domaine (produits, commandes, articles) +- Est généralement portable vers un autre projet +- Gère "comment ça marche" (comment envoyer un mail), pas "ce que ça fait" (quel mail envoyer) + +Exemple pour une meilleure compréhension : + +- `App\Core\MailerFactory` - crée des instances de la classe pour l'envoi d'e-mails, gère les paramètres SMTP +- `App\Model\OrderMailer` - utilise `MailerFactory` pour envoyer des e-mails concernant les commandes, connaît leurs templates et sait quand ils doivent être envoyés + + +Scripts de commande +=================== + +Les applications ont souvent besoin d'effectuer des activités en dehors des requêtes HTTP normales - qu'il s'agisse de traitement de données en arrière-plan, de maintenance ou de tâches périodiques. Pour l'exécution, des scripts simples dans le répertoire `bin/` sont utilisés, la logique d'implémentation elle-même est placée dans `app/Tasks/` (ou `app/Commands/`). + +Exemple : + +/--pre +app/Tasks/ +├── Maintenance/ ← scripts de maintenance +│ ├── CleanupCommand.php ← suppression des anciennes données +│ └── DbOptimizeCommand.php ← optimisation de la base de données +├── Integration/ ← intégration avec des systèmes externes +│ ├── ImportProducts.php ← importation depuis le système fournisseur +│ └── SyncOrders.php ← synchronisation des commandes +└── Scheduled/ ← tâches régulières + ├── NewsletterCommand.php ← envoi de newsletters + └── ReminderCommand.php ← notifications aux clients +\-- + +Qu'est-ce qui appartient au modèle et qu'est-ce qui appartient aux scripts de commande ? Par exemple, la logique pour envoyer un seul e-mail fait partie du modèle, l'envoi en masse de milliers d'e-mails appartient déjà à `Tasks/`. + +Les tâches sont généralement [lancées depuis la ligne de commande |https://blog.nette.org/en/cli-scripts-in-nette-application] ou via cron. Elles peuvent également être lancées via une requête HTTP, mais il faut penser à la sécurité. Le presenter qui lance la tâche doit être sécurisé, par exemple uniquement pour les utilisateurs connectés ou avec un jeton fort et un accès depuis des adresses IP autorisées. Pour les tâches longues, il est nécessaire d'augmenter la limite de temps du script et d'utiliser `session_write_close()` pour ne pas verrouiller la session. + + +Autres répertoires possibles +============================ + +En plus des répertoires de base mentionnés, vous pouvez ajouter d'autres dossiers spécialisés en fonction des besoins du projet. Jetons un coup d'œil aux plus courants et à leur utilisation : + +/--pre +app/ +├── Api/ ← logique pour l'API indépendante de la couche de présentation +├── Database/ ← scripts de migration et seeders pour les données de test +├── Components/ ← composants visuels partagés dans toute l'application +├── Event/ ← utile si vous utilisez une architecture événementielle +├── Mail/ ← templates d'e-mail et logique associée +└── Utils/ ← classes utilitaires +\-- + +Pour les composants visuels partagés utilisés dans les presenters à travers l'application, le dossier `app/Components` ou `app/Controls` peut être utilisé : + +/--pre +app/Components/ +├── Form/ ← composants de formulaire partagés +│ ├── SignInForm.php +│ └── UserForm.php +├── Grid/ ← composants pour l'affichage de données +│ └── DataGrid.php +└── Navigation/ ← éléments de navigation + ├── Breadcrumbs.php + └── Menu.php +\-- + +C'est ici que se trouvent les composants qui ont une logique plus complexe. Si vous souhaitez partager des composants entre plusieurs projets, il est conseillé de les extraire dans un paquet composer distinct. + +Dans le répertoire `app/Mail`, vous pouvez placer la gestion de la communication par e-mail : + +/--pre +app/Mail/ +├── templates/ ← templates d'e-mail +│ ├── order-confirmation.latte +│ └── welcome.latte +└── OrderMailer.php +\-- + + +Mapping des presenters +====================== + +Le mapping définit les règles pour dériver le nom de la classe à partir du nom du presenter. Nous les spécifions dans la [configuration|configuration] sous la clé `application › mapping`. + +Sur cette page, nous avons montré que nous plaçons les presenters dans le dossier `app/Presentation` (ou `app/UI`). Nous devons communiquer cette convention à Nette dans le fichier de configuration. Une seule ligne suffit : + +```neon +application: + mapping: App\Presentation\*\**Presenter +``` + +Comment fonctionne le mapping ? Pour mieux comprendre, imaginons d'abord une application sans modules. Nous voulons que les classes de presenter appartiennent au namespace `App\Presentation`, afin que le presenter `Home` soit mappé sur la classe `App\Presentation\HomePresenter`. Ce que nous obtenons avec cette configuration : + +```neon +application: + mapping: App\Presentation\*Presenter +``` + +Le mapping fonctionne de telle sorte que le nom du presenter `Home` remplace l'astérisque dans le masque `App\Presentation\*Presenter`, ce qui donne le nom de classe résultant `App\Presentation\HomePresenter`. Simple ! + +Mais comme vous pouvez le voir dans les exemples de ce chapitre et d'autres, nous plaçons les classes de presenter dans des sous-répertoires éponymes, par exemple le presenter `Home` est mappé sur la classe `App\Presentation\Home\HomePresenter`. Nous obtenons cela en doublant les deux-points (nécessite Nette Application 3.2) : + +```neon +application: + mapping: App\Presentation\**Presenter +``` + +Passons maintenant au mapping des presenters dans les modules. Pour chaque module, nous pouvons définir un mapping spécifique : + +```neon +application: + mapping: + Front: App\Presentation\Front\**Presenter + Admin: App\Presentation\Admin\**Presenter + Api: App\Api\*Presenter +``` + +Selon cette configuration, le presenter `Front:Home` est mappé sur la classe `App\Presentation\Front\Home\HomePresenter`, tandis que le presenter `Api:OAuth` est mappé sur la classe `App\Api\OAuthPresenter`. + +Comme les modules `Front` et `Admin` ont un mode de mapping similaire et qu'il y aura probablement plus de modules de ce type, il est possible de créer une règle générale qui les remplacera. Une nouvelle astérisque pour le module est ainsi ajoutée au masque de classe : + +```neon +application: + mapping: + *: App\Presentation\*\**Presenter + Api: App\Api\*Presenter +``` + +Cela fonctionne également pour les structures de répertoires plus profondément imbriquées, comme par exemple le presenter `Admin:User:Edit`, le segment avec l'astérisque se répète pour chaque niveau et le résultat est la classe `App\Presentation\Admin\User\Edit\EditPresenter`. + +Une notation alternative consiste à utiliser un tableau composé de trois segments au lieu d'une chaîne. Cette notation est équivalente à la précédente : + +```neon +application: + mapping: + *: [App\Presentation, *, **Presenter] + Api: [App\Api, '', *Presenter] +``` diff --git a/application/fr/how-it-works.texy b/application/fr/how-it-works.texy index 065eb5aa33..160ad93fba 100644 --- a/application/fr/how-it-works.texy +++ b/application/fr/how-it-works.texy @@ -3,99 +3,101 @@ Comment fonctionnent les applications ?
    -Vous êtes en train de lire le document de base de la documentation Nette. Vous y apprendrez tous les principes des applications web. Nice de A à Z, du moment de la naissance jusqu'au dernier souffle du script PHP. Après la lecture, vous saurez : +Vous lisez actuellement le guide fondamental de la documentation Nette. Vous apprendrez tout le principe de fonctionnement des applications web. De A à Z, du début à la fin de l'exécution du script PHP. Après lecture, vous saurez : - comment tout cela fonctionne -- ce qu'est Bootstrap, Presenter et le conteneur DI +- ce qu'est Bootstrap, un Presenter et un conteneur DI - à quoi ressemble la structure des répertoires
    -Structure du répertoire .[#toc-directory-structure] -=================================================== +Structure des répertoires +========================= -Ouvrez un exemple de squelette d'une application web appelée [WebProject |https://github.com/nette/web-project] et vous pouvez observer les fichiers sur lesquels on écrit. +Ouvrez l'exemple de squelette d'application web appelé [WebProject|https://github.com/nette/web-project] et pendant la lecture, examinez les fichiers mentionnés. -La structure des répertoires ressemble à ceci : +La structure des répertoires ressemble à quelque chose comme ceci : /--pre web-project/ -├── app/ ← répertoire avec application -│ ├── Presenters/ ← classes d'presenter -│ │ ├── HomePresenter.php ← Home classe des présentateurs -│ │ └── templates/ ← répertoire des modèles -│ │ ├── @layout.latte ← modèle de disposition partagée -│ │ └── Home/ ← Modèles pour le présentateur de la page d'accueil -│ │ └── default.latte ← modèle pour l'action `default` -│ ├── Router/ ← configuration des adresses URL +├── app/ ← répertoire de l'application +│ ├── Core/ ← classes de base nécessaires au fonctionnement +│ │ └── RouterFactory.php ← configuration des adresses URL +│ ├── Presentation/ ← presenters, templates & co. +│ │ ├── @layout.latte ← template de layout +│ │ └── Home/ ← répertoire du presenter Home +│ │ ├── HomePresenter.php ← classe du presenter Home +│ │ └── default.latte ← template de l'action default │ └── Bootstrap.php ← classe de démarrage Bootstrap -├── bin/ ← scripts pour la ligne de commande -├── config/ ← configuration files +├── assets/ ← ressources (SCSS, TypeScript, images sources) +├── bin/ ← scripts exécutés depuis la ligne de commande +├── config/ ← fichiers de configuration │ ├── common.neon -│ └── local.neon -├── log/ ← journaux d'erreurs +│ └── services.neon +├── log/ ← erreurs journalisées ├── temp/ ← fichiers temporaires, cache, … ├── vendor/ ← bibliothèques installées par Composer │ ├── ... -│ └── autoload.php ← autoloading of libs installed by Composer -├── www/ ← répertoire public, racine du document du projet -│ ├── .htaccess ← règles du mod_rewrite, etc. -│ └── index.php ← fichier initial qui lance l'application +│ └── autoload.php ← autoloading de tous les paquets installés +├── www/ ← répertoire public ou document-root du projet +│ ├── assets/ ← fichiers statiques compilés (CSS, JS, images, ...) +│ ├── .htaccess ← règles mod_rewrite +│ └── index.php ← fichier initial par lequel l'application est lancée └── .htaccess ← interdit l'accès à tous les répertoires sauf www \-- -Vous pouvez modifier la structure des répertoires de n'importe quelle manière, renommer ou déplacer des dossiers, puis modifier simplement les chemins d'accès à `log/` et `temp/` dans le fichier `Bootstrap.php` et le chemin d'accès à ce fichier dans `composer.json` dans la section `autoload`. Rien de plus, pas de reconfiguration compliquée, pas de changements constants. Nette dispose d'une [autodétection intelligente |bootstrap#development-vs-production-mode]. +Vous pouvez modifier la structure des répertoires comme vous le souhaitez, renommer ou déplacer des dossiers, elle est entièrement flexible. De plus, Nette dispose d'une autodétection intelligente et reconnaît automatiquement l'emplacement de l'application, y compris sa base d'URL. -Pour les applications un peu plus importantes, nous pouvons diviser les dossiers contenant des présentateurs et des modèles en sous-répertoires (sur le disque) et en espaces de noms (dans le code), que nous appelons [modules]. +Pour les applications légèrement plus grandes, nous pouvons [diviser les dossiers avec les presenters et les templates en sous-répertoires |directory-structure#Presenters et templates] et les classes en espaces de noms, que nous appelons modules. -Le répertoire `www/` est le répertoire public ou la racine du document du projet. Vous pouvez le renommer sans avoir à définir quoi que ce soit d'autre du côté de l'application. Il suffit de [configurer l'hébergement |nette:troubleshooting#How to change or remove www directory from URL] pour que le document-root aille dans ce répertoire. +Le répertoire `www/` représente le répertoire public ou document-root du projet. Vous pouvez le renommer sans aucune configuration supplémentaire côté application. Il suffit de [configurer l'hébergement |nette:troubleshooting#Comment changer ou supprimer le répertoire www de l URL] pour que le document-root pointe vers ce répertoire. -Vous pouvez également télécharger directement le projet Web, y compris Nette, en utilisant [Composer |best-practices:composer]: +Vous pouvez également télécharger directement WebProject incluant Nette en utilisant [Composer |best-practices:composer] : ```shell composer create-project nette/web-project ``` -Sous Linux ou macOS, définissez les [droits d'écriture |nette:troubleshooting#Setting directory permissions] pour les répertoires `log/` et `temp/`. +Sous Linux ou macOS, définissez les [permissions d'écriture |nette:troubleshooting#Configuration des permissions de répertoire] pour les répertoires `log/` et `temp/`. -L'application WebProject est prête à fonctionner, il n'est pas nécessaire de configurer quoi que ce soit d'autre et vous pouvez la visualiser directement dans le navigateur en accédant au dossier `www/`. +L'application WebProject est prête à être lancée, il n'y a absolument rien à configurer et vous pouvez l'afficher directement dans le navigateur en accédant au dossier `www/`. -Demande HTTP .[#toc-http-request] -================================= +Requête HTTP +============ -Tout commence lorsqu'un utilisateur ouvre la page dans un navigateur et que le navigateur frappe le serveur avec une requête HTTP. La requête est dirigée vers un fichier PHP situé dans le répertoire public `www/`, qui est `index.php`. Supposons qu'il s'agisse d'une requête vers `https://example.com/product/123` et sera exécutée. +Tout commence au moment où l'utilisateur ouvre une page dans le navigateur. C'est-à-dire lorsque le navigateur contacte le serveur avec une requête HTTP. La requête pointe vers un seul fichier PHP, qui se trouve dans le répertoire public `www/`, et c'est `index.php`. Supposons qu'il s'agisse d'une requête pour l'adresse `https://example.com/product/123`. Grâce à une [configuration serveur appropriée |nette:troubleshooting#Comment configurer le serveur pour les jolies URL], cette URL est également associée au fichier `index.php`, qui est ensuite exécuté. -Sa tâche est la suivante : +Ses tâches sont : 1) initialiser l'environnement -2) récupérer l'usine -3) lancer l'application Nette qui traite la demande. +2) obtenir la factory +3) lancer l'application Nette, qui traitera la requête -Quel genre d'usine ? Nous ne produisons pas de tracteurs, mais des sites web ! Attendez, je vais vous expliquer tout de suite. +Quelle factory ? Nous ne fabriquons pas de tracteurs, mais des sites web ! Patientez, cela va s'éclaircir. -Par "initialiser l'environnement", nous voulons dire, par exemple, que [Tracy |tracy:] est activé, qui est un outil étonnant pour consigner ou visualiser les erreurs. Il enregistre les erreurs sur le serveur de production et les affiche directement sur le serveur de développement. Par conséquent, l'initialisation doit également décider si le site fonctionne en mode production ou en mode développement. Pour ce faire, Nette utilise l'autodétection : si vous exécutez le site sur localhost, il fonctionne en mode développeur. Vous n'avez rien à configurer et l'application est prête à être déployée aussi bien en développement qu'en production. Ces étapes sont réalisées et décrites en détail dans le chapitre sur la [classe Bootstrap |bootstrap]. +Par "initialisation de l'environnement", nous entendons par exemple l'activation de [Tracy|tracy:], qui est un outil exceptionnel pour la journalisation ou la visualisation des erreurs. Sur un serveur de production, il journalise les erreurs, sur un serveur de développement, il les affiche directement. L'initialisation comprend donc également la décision de savoir si le site fonctionne en mode production ou développement. Pour cela, Nette utilise une [autodétection intelligente |bootstrapping#Mode Développement vs Production] : si vous lancez le site sur localhost, il fonctionne en mode développement. Vous n'avez donc rien à configurer et l'application est prête à la fois pour le développement et le déploiement en production. Ces étapes sont effectuées et décrites en détail dans le chapitre sur la [classe Bootstrap|bootstrapping]. -Le troisième point (oui, nous avons sauté le deuxième, mais nous y reviendrons) consiste à démarrer l'application. Le traitement des demandes HTTP dans Nette est effectué par la classe `Nette\Application\Application` (ci-après dénommée `Application`), donc lorsque nous disons "lancer une application", nous voulons dire appeler une méthode portant le nom `run()` sur un objet de cette classe. +Le troisième point (oui, nous avons sauté le deuxième, mais nous y reviendrons) est le lancement de l'application. Le traitement des requêtes HTTP dans Nette est géré par la classe `Nette\Application\Application` (ci-après `Application`), donc quand nous disons lancer l'application, nous entendons spécifiquement appeler la méthode `run()` sur l'objet de cette classe. -Nette est un mentor qui vous guide pour écrire des applications propres grâce à des méthodologies éprouvées. Et la plus éprouvée est appelée **injection de dépendance**, en abrégé DI. Pour l'instant, nous ne voulons pas vous ennuyer avec l'explication de DI, car il y a un [chapitre séparé |dependency-injection:introduction], la chose importante ici est que les objets clés seront généralement créés par une usine pour les objets appelés **conteneur DI** (abrégé DIC). Oui, c'est la fabrique dont on a parlé il y a un moment. Et elle crée également l'objet `Application` pour nous, donc nous avons d'abord besoin d'un conteneur. Nous l'obtenons en utilisant la classe `Configurator` et la laissons produire l'objet `Application`, appelons la méthode `run()` et cela démarre l'application Nette. C'est exactement ce qui se passe dans le fichier [index.php |bootstrap#index.php]. +Nette est un mentor qui vous guide vers l'écriture d'applications propres selon des méthodologies éprouvées. Et l'une de celles qui sont absolument les plus éprouvées s'appelle **l'injection de dépendances**, en abrégé DI. Pour le moment, nous ne voulons pas vous surcharger avec l'explication de la DI, il y a un [chapitre séparé|dependency-injection:introduction] pour cela, l'important est la conséquence que les objets clés nous seront généralement créés par une factory d'objets appelée **conteneur DI** (abrégé en DIC). Oui, c'est la factory dont il était question il y a un instant. Et elle nous créera aussi l'objet `Application`, c'est pourquoi nous avons d'abord besoin du conteneur. Nous l'obtenons à l'aide de la classe `Configurator` et nous lui demandons de créer l'objet `Application`, nous appelons sa méthode `run()` et l'application Nette est ainsi lancée. C'est exactement ce qui se passe dans le fichier [index.php |bootstrapping#index.php]. -Application Nette .[#toc-nette-application] -=========================================== +Nette Application +================= -La classe Application a une seule tâche : répondre à une requête HTTP. +La classe `Application` n'a qu'une seule tâche : répondre à la requête HTTP. -Les applications écrites dans Nette sont divisées en plusieurs présentateurs (dans d'autres frameworks, vous pouvez rencontrer le terme contrôleur, qui est le même), qui sont des classes représentant une page spécifique du site Web : par exemple, la page d'accueil, un produit dans une boutique en ligne, un formulaire d'inscription, un flux sitemap, etc. L'application peut avoir de un à plusieurs milliers de présentateurs. +Les applications écrites en Nette sont divisées en de nombreux presenters (dans d'autres frameworks, vous pouvez rencontrer le terme controller, c'est la même chose), qui sont des classes, dont chacune représente une page web spécifique : par ex. la page d'accueil ; un produit dans une boutique en ligne ; un formulaire de connexion ; un flux sitemap, etc. Une application peut avoir d'un à des milliers de presenters. -L'application commence par demander à ce qu'on appelle le routeur de décider lequel des présentateurs doit transmettre la demande actuelle pour traitement. Le routeur décide de la responsabilité qui lui incombe. Il examine l'URL d'entrée `https://example.com/product/123`, qui veut `show` un produit avec `id: 123` comme action. C'est une bonne habitude d'écrire une paire présentateur + action séparée par un deux-points comme `Product:show`. +`Application` commence par demander au routeur de décider à quel presenter transmettre la requête actuelle pour traitement. Le routeur détermine qui est responsable. Il regarde l'URL d'entrée `https://example.com/product/123` et sur la base de sa configuration, décide que c'est le travail, par ex., du **presenter** `Product`, auquel il demandera comme **action** l'affichage (`show`) du produit avec `id: 123`. Il est de bonne pratique de noter la paire presenter + action séparée par deux-points, comme `Product:show`. -Le routeur transforme donc l'URL en une paire `Presenter:action` + paramètres, dans notre cas `Product:show` + `id: 123`. Vous pouvez voir à quoi ressemble un routeur dans le fichier `app/Router/RouterFactory.php` et nous le décrirons en détail dans le chapitre [Routage |Routing]. +Le routeur a donc transformé l'URL en une paire `Presenter:action` + paramètres, dans notre cas `Product:show` + `id: 123`. À quoi ressemble un tel routeur, vous pouvez le voir dans le fichier `app/Core/RouterFactory.php` et nous le décrivons en détail dans le chapitre [Routage |Routing]. -Continuons. L'application connaît déjà le nom du présentateur et peut continuer. En créant un objet `ProductPresenter`, qui est le code du présentateur `Product`. Plus précisément, elle demande au conteneur DI de créer le présentateur, car la production d'objets est son travail. +Continuons. `Application` connaît déjà le nom du presenter et peut continuer. En créant l'objet de la classe `ProductPresenter`, qui est le code du presenter `Product`. Plus précisément, il demande au conteneur DI de créer le presenter, car c'est son rôle. -Le présentateur pourrait ressembler à ceci : +Le presenter peut ressembler à ceci : ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -107,98 +109,92 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function renderShow(int $id): void { - // nous obtenons des données du modèle et les transmettons au modèle + // nous obtenons les données du modèle et les passons au template $this->template->product = $this->repository->getProduct($id); } } ``` -La demande est traitée par le présentateur. Et la tâche est claire : faire l'action `show` avec `id: 123`. Ce qui dans le langage des présentateurs signifie que la méthode `renderShow()` est appelée et que dans le paramètre `$id` on obtient `123`. - -Un présentateur peut gérer plusieurs actions, c'est-à-dire avoir plusieurs méthodes. `render()`. Mais nous recommandons de concevoir des présentateurs avec une ou aussi peu d'actions que possible. +Le presenter prend en charge le traitement de la requête. Et la tâche est claire : effectuer l'action `show` avec `id: 123`. Ce qui, dans le langage des presenters, signifie que la méthode `renderShow()` est appelée et reçoit `123` dans le paramètre `$id`. -Ainsi, la méthode `renderShow(123)` a été appelée, dont le code est un exemple fictif, mais vous pouvez y voir comment les données sont transmises au modèle, c'est-à-dire en écrivant à `$this->template`. +Un presenter peut gérer plusieurs actions, c'est-à-dire avoir plusieurs méthodes `render()`. Mais nous recommandons de concevoir des presenters avec une seule ou le moins d'actions possible. -Ensuite, le présentateur renvoie la réponse. Cela peut être une page HTML, une image, un document XML, l'envoi d'un fichier depuis le disque, JSON ou la redirection vers une autre page. Il est important de noter que si nous ne disons pas explicitement comment répondre (ce qui est le cas de `ProductPresenter`), la réponse sera de rendre le modèle avec une page HTML. Pourquoi ? Eh bien, parce que dans 99% des cas, nous voulons dessiner un modèle, donc le présentateur prend ce comportement par défaut et veut nous faciliter le travail. C'est le point de vue de Nette. +Donc, la méthode `renderShow(123)` a été appelée, dont le code est un exemple fictif, mais vous pouvez y voir comment les données sont passées au template, c'est-à-dire en écrivant dans `$this->template`. -Nous n'avons même pas besoin d'indiquer quel modèle dessiner, il dérive le chemin vers celui-ci selon une logique simple. Dans le cas du présentateur `Product` et de l'action `show`, il essaie de voir si l'un de ces fichiers modèles existe par rapport au répertoire où se trouve la classe `ProductPresenter`: +Ensuite, le presenter retourne une réponse. Cela peut être une page HTML, une image, un document XML, l'envoi d'un fichier depuis le disque, du JSON ou même une redirection vers une autre page. Il est important que si nous ne disons pas explicitement comment répondre (ce qui est le cas de `ProductPresenter`), la réponse sera le rendu d'un template avec une page HTML. Pourquoi ? Parce que dans 99% des cas, nous voulons rendre un template, donc le presenter considère ce comportement comme celui par défaut et veut nous faciliter le travail. C'est le but de Nette. -- `templates/Product/show.latte` -- `templates/Product.show.latte` +Nous n'avons même pas besoin de spécifier quel template rendre, il déduit le chemin lui-même. Dans le cas de l'action `show`, il essaie simplement de charger le template `show.latte` dans le répertoire de la classe `ProductPresenter`. Il essaie également de trouver le layout dans le fichier `@layout.latte` (plus de détails sur la [recherche de templates |templates#Recherche de templates]). -Il essaiera également de trouver la mise en page dans le fichier `@layout.latte`, puis il effectuera le rendu du modèle. La tâche du présentateur et de l'ensemble de l'application est maintenant terminée. Si le modèle n'existe pas, une page d'erreur 404 sera renvoyée. Vous pouvez en savoir plus sur les présentateurs sur la page [Présentateurs |Presenters]. +Et ensuite, il rend les templates. La tâche du presenter et de toute l'application est ainsi terminée. Si le template n'existait pas, une page d'erreur 404 serait retournée. Vous en apprendrez plus sur les presenters sur la page [Presenters |Presenters]. [* request-flow.svg *] -Juste pour être sûr, essayons de récapituler l'ensemble du processus avec une URL légèrement différente : +Pour être sûr, essayons de récapituler tout le processus avec une URL légèrement différente : -1) l'URL sera `https://example.com` -2) nous démarrons l'application, nous créons un conteneur et nous l'exécutons `Application::run()` -3) le routeur décode l'URL comme une paire `Home:default` -4) un objet `HomePresenter` est créé +1) L'URL sera `https://example.com` +2) nous démarrons l'application, le conteneur est créé et `Application::run()` est lancé +3) le routeur décode l'URL comme la paire `Home:default` +4) l'objet de la classe `HomePresenter` est créé 5) la méthode `renderDefault()` est appelée (si elle existe) -6) un modèle `templates/Home/default.latte` avec une mise en page `templates/@layout.latte` est rendu +6) le template (par ex. `default.latte`) avec le layout (par ex. `@layout.latte`) est rendu -Vous avez peut-être rencontré beaucoup de nouveaux concepts maintenant, mais nous pensons qu'ils ont un sens. Créer des applications dans Nette est un jeu d'enfant. +Vous avez peut-être rencontré de nombreux nouveaux termes maintenant, mais nous espérons qu'ils ont du sens. Créer des applications avec Nette est incroyablement simple et agréable. -Modèles .[#toc-templates] -========================= +Templates +========= -En ce qui concerne les modèles, Nette utilise le système de modèles [Latte |latte:]. C'est pourquoi les fichiers contenant des modèles se terminent par `.latte`. Latte est utilisé parce que c'est le système de modèles le plus sûr pour PHP, et en même temps le système le plus intuitif. Vous n'avez pas besoin d'apprendre grand-chose de nouveau, il vous suffit de connaître PHP et quelques balises Latte. Vous trouverez tout [dans la documentation |latte:]. +Puisque nous avons parlé des templates, Nette utilise le système de templates [Latte |latte:]. D'une part parce que c'est le système de templates le plus sécurisé pour PHP, et d'autre part parce que c'est aussi le système le plus intuitif. Vous n'avez pas besoin d'apprendre beaucoup de nouveautés, la connaissance de PHP et de quelques balises suffit. Vous apprendrez tout dans [la documentation |templates]. -Dans le template, nous [créons un lien |creating-links] vers d'autres présentateurs et actions comme suit : +Dans le template, des [liens |creating-links] sont créés vers d'autres presenters & actions comme ceci : ```latte -product detail +détail du produit ``` -Il suffit d'écrire la paire familière `Presenter:action` au lieu de l'URL réelle et d'inclure tous les paramètres. L'astuce est `n:href`, qui indique que cet attribut sera traité par Nette. Et elle le fera : +Au lieu d'une URL réelle, écrivez simplement la paire connue `Presenter:action` et spécifiez d'éventuels paramètres. L'astuce réside dans `n:href`, qui indique que cet attribut sera traité par Nette. Et il générera : ```latte -product detail +détail du produit ``` -Le routeur mentionné précédemment est chargé de générer l'URL. En fait, les routeurs de Nette sont uniques en ce sens qu'ils peuvent non seulement transformer une URL en une paire présentateur:action, mais aussi, à l'inverse, générer une URL à partir du nom du présentateur + action + paramètres. -Grâce à cela, dans Nette, vous pouvez changer complètement la forme de l'URL dans toute l'application finie sans changer un seul caractère dans le modèle ou le présentateur, juste en modifiant le routeur. -Et grâce à cela, ce que l'on appelle la canonisation fonctionne, ce qui est une autre caractéristique unique de Nette, qui améliore le SEO (optimisation de la facilité de recherche sur Internet) en empêchant automatiquement l'existence de contenu dupliqué à différentes URL. -De nombreux programmeurs trouvent cela étonnant. +La génération d'URL est gérée par le routeur mentionné précédemment. En effet, les routeurs dans Nette sont exceptionnels car ils peuvent effectuer non seulement des transformations d'URL en paire presenter:action, mais aussi l'inverse, c'est-à-dire générer une URL à partir du nom du presenter + action + paramètres. Grâce à cela, dans Nette, vous pouvez complètement changer les formes d'URL dans toute une application terminée, sans changer un seul caractère dans le template ou le presenter. Juste en modifiant le routeur. Grâce à cela fonctionne également la canonisation, qui est une autre caractéristique unique de Nette, contribuant à un meilleur SEO (optimisation pour les moteurs de recherche) en empêchant automatiquement l'existence de contenu dupliqué sur différentes URL. De nombreux programmeurs trouvent cela impressionnant. -Composants interactifs .[#toc-interactive-components] -===================================================== +Composants interactifs +====================== -Nous avons encore une chose à vous dire sur les présentateurs : ils ont un système de composants intégré. Les plus anciens d'entre vous se souviennent peut-être de quelque chose de similaire dans Delphi ou ASP.NET Web Forms. React ou Vue.js sont construits sur quelque chose de très similaire. Dans le monde des frameworks PHP, il s'agit d'une fonctionnalité tout à fait unique. +Nous devons vous dire encore une chose sur les presenters : ils ont un système de composants intégré. Les vétérans peuvent se souvenir de quelque chose de similaire dans Delphi ou ASP.NET Web Forms ; React ou Vue.js sont basés sur quelque chose de vaguement similaire. Dans le monde des frameworks PHP, c'est une caractéristique absolument unique. -Les composants sont des unités distinctes réutilisables que nous plaçons dans des pages (c'est-à-dire des présentateurs). Il peut s'agir de [formulaires |forms:in-presenter], de [grilles de données |https://componette.org/contributte/datagrid/], de menus, de sondages, en fait de tout ce qui peut être utilisé de manière répétée. Nous pouvons créer nos propres composants ou utiliser une partie de la [vaste gamme |https://componette.org] de composants open source. +Les composants sont des unités autonomes et réutilisables que nous insérons dans les pages (c'est-à-dire les presenters). Il peut s'agir de [formulaires |forms:in-presenter], de [datagrids |https://componette.org/contributte/datagrid/], de menus, de sondages, en fait de tout ce qu'il est judicieux d'utiliser de manière répétée. Nous pouvons créer nos propres composants ou utiliser certains de la [vaste offre |https://componette.org] de composants open source. -Les composants modifient fondamentalement l'approche du développement d'applications. Ils ouvrent de nouvelles possibilités pour composer des pages à partir d'unités prédéfinies. Et ils ont quelque chose en commun avec [Hollywood |components#Hollywood style]. +Les composants influencent fondamentalement l'approche de la création d'applications. Ils vous ouvrent de nouvelles possibilités de composition de pages à partir d'unités préfabriquées. Et en plus, ils ont quelque chose en commun avec [Hollywood |components#Style Hollywood]. -Conteneur et configuration de DI .[#toc-di-container-and-configuration] -======================================================================= +Conteneur DI et configuration +============================= -Le conteneur DI (fabrique d'objets) est le cœur de toute l'application. +Le conteneur DI ou factory d'objets est le cœur de toute l'application. -Ne vous inquiétez pas, il ne s'agit pas d'une boîte noire magique, comme cela pourrait sembler dans les mots précédents. En fait, il s'agit d'une classe PHP assez ennuyeuse générée par Nette et stockée dans un répertoire de cache. Elle possède un grand nombre de méthodes nommées `createServiceAbcd()` et chacune d'entre elles crée et renvoie un objet. Oui, il y a aussi une méthode `createServiceApplication()` qui produit `Nette\Application\Application`, dont nous avons besoin dans le fichier `index.php` pour exécuter l'application. Et il y a des méthodes pour produire des présentateurs individuels. Et ainsi de suite. +Ne vous inquiétez pas, ce n'est pas une boîte noire magique, comme on pourrait le penser d'après les lignes précédentes. En fait, c'est une classe PHP plutôt simple, générée par Nette et stockée dans le répertoire cache. Elle a beaucoup de méthodes nommées comme `createServiceAbcd()` et chacune d'elles sait comment créer et retourner un objet. Oui, il y a aussi une méthode `createServiceApplication()`, qui crée `Nette\Application\Application`, dont nous avions besoin dans le fichier `index.php` pour lancer l'application. Et il y a des méthodes créant les presenters individuels. Et ainsi de suite. -Les objets que le conteneur DI crée sont appelés services pour une raison quelconque. +Les objets que le conteneur DI crée sont, pour une raison quelconque, appelés services. -Ce qui est vraiment spécial à propos de cette classe est qu'elle n'est pas programmée par vous, mais par le framework. Il génère en fait le code PHP et l'enregistre sur le disque. Vous donnez simplement des instructions sur les objets que le conteneur doit être capable de produire et comment exactement. Ces instructions sont écrites dans des [fichiers de configuration |bootstrap#DI Container Configuration] au [format NEON |neon:format] et portent donc l'extension `.neon`. +Ce qui est vraiment spécial à propos de cette classe, c'est que ce n'est pas vous qui la programmez, mais le framework. Il génère réellement le code PHP et le sauvegarde sur le disque. Vous donnez simplement des instructions sur les objets que le conteneur doit savoir créer et comment précisément. Et ces instructions sont écrites dans des [fichiers de configuration |bootstrapping#Configuration du Conteneur DI], pour lesquels le format [NEON|neon:format] est utilisé et qui ont donc aussi l'extension `.neon`. -Les fichiers de configuration sont utilisés uniquement pour donner des instructions au conteneur DI. Ainsi, par exemple, si je spécifie l'option `expiration: 14 days` dans la section [session |http:configuration#Session], le conteneur DI, lorsqu'il créera l'objet `Nette\Http\Session` représentant la session, appellera sa méthode `setExpiration('14 days')`, et la configuration deviendra ainsi une réalité. +Les fichiers de configuration servent uniquement à instruire le conteneur DI. Ainsi, par exemple, si j'indique dans la section [session |http:configuration#Session] l'option `expiration: 14 days`, alors le conteneur DI, lors de la création de l'objet `Nette\Http\Session` représentant la session, appellera sa méthode `setExpiration('14 days')` et la configuration deviendra ainsi réalité. -Un chapitre entier est prêt pour vous, décrivant ce qui peut être [configuré |nette:configuring] et comment [définir vos propres services |dependency-injection:services]. +Il y a tout un chapitre préparé pour vous décrivant tout ce qui peut être [configuré |nette:configuring] et comment [définir vos propres services |dependency-injection:services]. -Lorsque vous entrerez dans la création de services, vous rencontrerez le mot [autowiring |dependency-injection:autowiring]. Il s'agit d'un gadget qui vous rendra la vie incroyablement plus facile. Il permet de passer automatiquement des objets là où vous en avez besoin (dans les constructeurs de vos classes, par exemple) sans avoir à faire quoi que ce soit. Vous constaterez que le conteneur DI de Nette est un petit miracle. +Dès que vous vous familiariserez un peu avec la création de services, vous rencontrerez le mot [autowiring |dependency-injection:autowiring]. C'est une astuce qui vous simplifiera incroyablement la vie. Elle sait comment passer automatiquement des objets là où vous en avez besoin (par exemple dans les constructeurs de vos classes), sans que vous ayez à faire quoi que ce soit. Vous découvrirez que le conteneur DI de Nette est un petit miracle. -Et maintenant ? .[#toc-what-next] -================================= +Où aller ensuite ? +================== -Nous avons passé en revue les principes de base des applications dans Nette. Jusqu'ici, très superficiellement, mais vous allez bientôt plonger dans les profondeurs et finalement créer de merveilleuses applications web. Où continuer ? Avez-vous essayé le tutoriel [Créer votre première application |quickstart:]? +Nous avons parcouru les principes de base des applications en Nette. Pour l'instant très superficiellement, mais vous approfondirez bientôt et créerez avec le temps de merveilleuses applications web. Où continuer ? Avez-vous déjà essayé le tutoriel [Écrivons notre première application|quickstart:] ? -En plus de ce qui précède, Nette dispose de tout un arsenal de [classes utiles |utils:], d'une [couche de base de données |database:], etc. Essayez volontairement de cliquer sur la documentation. Ou visitez le [blog |https://blog.nette.org]. Vous découvrirez beaucoup de choses intéressantes. +En plus de ce qui a été décrit ci-dessus, Nette dispose de tout un arsenal de [classes utiles|utils:], d'une [couche de base de données|database:], etc. Essayez juste de parcourir la documentation. Ou le [blog|https://blog.nette.org]. Vous découvrirez beaucoup de choses intéressantes. -Laissez le framework vous apporter beaucoup de joie 💙 +Que le framework vous apporte beaucoup de joie 💙 diff --git a/application/fr/modules.texy b/application/fr/modules.texy deleted file mode 100644 index ade58f61b9..0000000000 --- a/application/fr/modules.texy +++ /dev/null @@ -1,148 +0,0 @@ -Modules -******* - -.[perex] -Dans Nette, les modules représentent les unités logiques qui composent une application. Ils comprennent des présentateurs, des modèles, éventuellement aussi des composants et des classes de modèles. - -Un répertoire pour les présentateurs et un autre pour les modèles ne seraient pas suffisants pour les projets réels. Avoir des dizaines de fichiers dans un seul dossier est pour le moins inorganisé. Comment s'en sortir ? Il suffit de les répartir en sous-répertoires sur le disque et en espaces de noms dans le code. Et c'est exactement ce que font les modules Nette. - -Oublions donc le dossier unique pour les présentateurs et les modèles et créons plutôt des modules, par exemple `Admin` et `Front`. - -/--pre -app/ -├── Presenters/ -├── Modules/ ← répertoire avec les modules -│ ├── Admin/ ← module Admin -│ │ ├── Presenters/ ← ses présentateurs -│ │ │ ├── DashboardPresenter.php -│ │ │ └── templates/ -│ └── Front/ ← module Front -│ └── Presenters/ ← ses présentateurs -│ └── ... -\-- - -Cette structure de répertoire sera reflétée par les espaces de noms des classes, ainsi par exemple `DashboardPresenter` sera dans l'espace de noms `App\Modules\Admin\Presenters`: - -```php -namespace App\Modules\Admin\Presenters; - -class DashboardPresenter extends Nette\Application\UI\Presenter -{ - // ... -} -``` - -Le présentateur `Dashboard` dans le module `Admin` est référencé dans l'application en utilisant la notation deux points comme `Admin:Dashboard`, et son action `default` comme `Admin:Dashboard:default`. -Et comment Nette proper sait-elle que `Admin:Dashboard` représente la classe `App\Modules\Admin\Presenters\DashboardPresenter`? Cela est déterminé par le [mappage |#mapping] dans la configuration. -Ainsi, la structure donnée n'est pas figée et vous pouvez la modifier en fonction de vos besoins. - -Les modules peuvent bien sûr contenir tous les éléments autres que les présentateurs et les modèles, tels que les composants, les classes de modèles, etc. - - -Modules imbriqués .[#toc-nested-modules] ----------------------------------------- - -Les modules ne doivent pas uniquement former une structure plate, vous pouvez également créer des sous-modules, par exemple : - -/--pre -app/ -├── Modules/ ← répertoire avec les modules -│ ├── Blog/ ← module Blog -│ │ ├── Admin/ ← sous-module Admin -│ │ │ ├── Presenters/ -│ │ │ └── ... -│ │ └── Front/ ← sous-module Front -│ │ ├── Presenters/ -│ │ └── ... -│ ├── Forum/ ← module Forum -│ │ └── ... -\-- - -Ainsi, le module `Blog` est divisé en sous-modules `Admin` et `Front`. Là encore, cela se reflétera dans les espaces de noms, qui seront `App\Modules\Blog\Admin\Presenters` etc. Le présentateur `Dashboard` à l'intérieur du sous-module est désigné par `Blog:Admin:Dashboard`. - -L'imbrication peut aller aussi loin que vous le souhaitez, de sorte que des sous-sous-modules peuvent être créés. - - -Création de liens .[#toc-creating-links] ----------------------------------------- - -Les liens dans les modèles de présentateur sont relatifs au module actuel. Ainsi, le lien `Foo:default` mène au présentateur `Foo` dans le même module que le présentateur actuel. Si le module actuel est `Front`, par exemple, le lien est le suivant : - -```latte -link to Front:Product:show -``` - -Un lien est relatif même s'il inclut le nom d'un module, qui est alors considéré comme un sous-module : - -```latte -link to Front:Shop:Product:show -``` - -Les liens absolus sont écrits de manière analogue aux chemins absolus sur le disque, mais avec des deux-points à la place des barres obliques. Ainsi, un lien absolu commence par un deux-points : - -```latte -link to Admin:Product:show -``` - -Pour savoir si nous sommes dans un certain module ou son sous-module, nous pouvons utiliser la fonction `isModuleCurrent(moduleName)`. - -```latte -
  • - ... -
  • -``` - - -Acheminement .[#toc-routing] ----------------------------- - -Voir le [chapitre sur le routage |routing#Modules]. - - -Cartographie .[#toc-mapping] ----------------------------- - -Définit les règles par lesquelles le nom de la classe est dérivé du nom du présentateur. On les inscrit dans la [configuration] sous la clé `application › mapping`. - -Commençons par un exemple qui n'utilise pas de modules. Nous voulons simplement que les classes du présentateur aient l'espace de nom `App\Presenters`. Cela signifie qu'un présentateur tel que `Home` doit correspondre à la classe `App\Presenters\HomePresenter`. Ceci peut être réalisé par la configuration suivante : - -```neon -application: - mapping: - *: App\Presenters\*Presenter -``` - -Le nom du présentateur est remplacé par l'astérisque dans le masque de classe et le résultat est le nom de la classe. Facile ! - -Si nous divisons les présentateurs en modules, nous pouvons avoir notre propre mappage pour chaque module : - -```neon -application: - mapping: - Front: App\Modules\Front\Presenters\*Presenter - Admin: App\Modules\Admin\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -Maintenant, le présentateur `Front:Home` correspond à la classe `App\Modules\Front\Presenters\HomePresenter` et le présentateur `Admin:Dashboard` à la classe `App\Modules\Admin\Presenters\DashboardPresenter`. - -Il est plus pratique de créer une règle générale (étoile) pour remplacer les deux premières. L'astérisque supplémentaire sera ajouté au masque de classe uniquement pour le module : - -```neon -application: - mapping: - *: App\Modules\*\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -Mais qu'en est-il si nous utilisons des modules imbriqués et que nous avons un présentateur `Admin:User:Edit`? Dans ce cas, le segment avec un astérisque représentant le module pour chaque niveau est simplement répété et le résultat est la classe `App\Modules\Admin\User\Presenters\EditPresenter`. - -Une notation alternative consiste à utiliser un tableau composé de trois segments au lieu d'une chaîne de caractères. Cette notation est équivalente à la précédente : - -```neon -application: - mapping: - *: [App\Modules, *, Presenters\*Presenter] -``` - -La valeur par défaut est `*: *Module\*Presenter`. diff --git a/application/fr/multiplier.texy b/application/fr/multiplier.texy index 6eeb49f67b..c8c57bdd44 100644 --- a/application/fr/multiplier.texy +++ b/application/fr/multiplier.texy @@ -1,30 +1,32 @@ -Multiplicateur : Composants dynamiques -************************************** +Multiplier : composants dynamiques +********************************** -Un outil pour la création dynamique de composants interactifs .[perex] +.[perex] +Outil pour la création dynamique de composants interactifs -Commençons par un problème typique : nous avons une liste de produits sur un site de commerce électronique et nous voulons accompagner chaque produit d'un formulaire *add to cart*. L'une des solutions consiste à regrouper toute la liste dans un seul formulaire. Un moyen plus pratique est d'utiliser [api:Nette\Application\UI\Multiplier]. +Partons d'un exemple typique : nous avons une liste de produits dans une boutique en ligne, et pour chacun, nous voulons afficher un formulaire pour ajouter le produit au panier. Une possibilité est d'englober toute la liste dans un seul formulaire. Cependant, [api:Nette\Application\UI\Multiplier] nous offre une manière beaucoup plus pratique. -Multiplier vous permet de définir une usine pour plusieurs composants. Il est basé sur le principe des composants imbriqués - chaque composant héritant de [api:Nette\ComponentModel\Container] peut contenir d'autres composants. +Multiplier permet de définir facilement une petite factory pour plusieurs composants. Il fonctionne sur le principe des composants imbriqués - chaque composant héritant de [api:Nette\ComponentModel\Container] peut contenir d'autres composants. -Voir le [modèle de composant |components#Components in Depth] dans la documentation. .[tip] +.[tip] +Voir le chapitre sur le [modèle de composant |components#Composants en profondeur] dans la documentation ou la [présentation de Honza Tvrdík|https://www.youtube.com/watch?v=8y3LLexWu-I]. -Multiplier se pose comme un composant parent qui peut créer dynamiquement ses enfants en utilisant le callback passé dans le constructeur. Voir l'exemple : +L'essence de Multiplier est qu'il agit en tant que parent capable de créer dynamiquement ses enfants à l'aide d'un callback passé dans le constructeur. Voir l'exemple : ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function () { $form = new Nette\Application\UI\Form; - $form->addInteger('amount', 'Amount:') + $form->addInteger('count', 'Nombre de produits :') ->setRequired(); - $form->addSubmit('send', 'Add to cart'); + $form->addSubmit('send', 'Ajouter au panier'); return $form; }); } ``` -Dans le modèle, nous pouvons rendre un formulaire pour chaque produit - et chaque formulaire sera effectivement un composant unique. +Maintenant, nous pouvons simplement faire afficher le formulaire pour chaque produit dans le template - et chacun sera réellement un composant unique. ```latte {foreach $items as $item} @@ -35,26 +37,26 @@ Dans le modèle, nous pouvons rendre un formulaire pour chaque produit - et chaq {/foreach} ``` -L'argument passé à la balise `{control}` dit : +L'argument passé dans la balise `{control}` est dans un format qui dit : -1. obtenir un composant `shopForm` -2. et retourne son enfant `$item->id` +1. obtenir le composant `shopForm` +2. et à partir de celui-ci, obtenir l'enfant `$item->id` -Lors du premier appel de **1.** le composant `shopForm` n'existe pas encore, la méthode `createComponentShopForm` est donc appelée pour le créer. Une fonction anonyme passée en paramètre à Multiplier, est ensuite appelée et un formulaire est créé. +Lors du premier appel du point **1.**, `shopForm` n'existe pas encore, donc sa factory `createComponentShopForm` est appelée. Sur le composant obtenu (instance de Multiplier), la factory du formulaire spécifique est ensuite appelée - c'est la fonction anonyme que nous avons passée à Multiplier dans le constructeur. -Dans les itérations suivantes de `foreach`, la méthode `createComponentShopForm` n'est plus appelée car le composant existe déjà. Mais comme nous faisons référence à un autre enfant (`$item->id` varie entre les itérations), une fonction anonyme est à nouveau appelée et un nouveau formulaire est créé. +Dans l'itération suivante de foreach, la méthode `createComponentShopForm` ne sera plus appelée (le composant existe), mais comme nous recherchons un autre de ses enfants (`$item->id` sera différent à chaque itération), la fonction anonyme sera à nouveau appelée et nous retournera un nouveau formulaire. -La dernière chose à faire est de s'assurer que le formulaire ajoute effectivement le bon produit au panier car dans l'état actuel, tous les formulaires sont identiques et nous ne pouvons pas distinguer à quels produits ils appartiennent. Pour cela nous pouvons utiliser la propriété de Multiplier (et en général de toute méthode de fabrique de composant dans Nette Framework) que chaque méthode de fabrique de composant reçoit le nom du composant créé comme premier argument. Dans notre cas, ce serait `$item->id`, ce qui est exactement ce dont nous avons besoin pour distinguer les produits individuels. Tout ce que vous avez à faire est de modifier le code de création du formulaire : +La seule chose qui reste à faire est de s'assurer que le formulaire ajoute réellement au panier le produit qu'il doit ajouter - actuellement, le formulaire est exactement le même pour chaque produit. La propriété de Multiplier (et en général de chaque factory de composant dans Nette Framework) nous aide, à savoir que chaque factory reçoit comme premier argument le nom du composant créé. Dans notre cas, ce sera `$item->id`, ce qui est exactement l'information dont nous avons besoin. Il suffit donc de modifier légèrement la création du formulaire : ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function ($itemId) { $form = new Nette\Application\UI\Form; - $form->addInteger('amount', 'Amount:') + $form->addInteger('count', 'Nombre de produits :') ->setRequired(); $form->addHidden('itemId', $itemId); - $form->addSubmit('send', 'Add to cart'); + $form->addSubmit('send', 'Ajouter au panier'); return $form; }); } diff --git a/application/fr/presenters.texy b/application/fr/presenters.texy index 8229b2f9eb..670c01a8b7 100644 --- a/application/fr/presenters.texy +++ b/application/fr/presenters.texy @@ -1,39 +1,39 @@ -Présentateurs -************* +Presenters +**********
    -Nous allons apprendre à écrire des présentateurs et des modèles dans Nette. Après la lecture, vous saurez : +Nous allons découvrir comment écrire des presenters et des templates en Nette. Après lecture, vous saurez : -- comment fonctionne le présentateur +- comment fonctionne un presenter - ce que sont les paramètres persistants -- comment rendre un modèle +- comment les templates sont rendus
    -[Nous savons déjà |how-it-works#nette-application] qu'un présentateur est une classe qui représente une page spécifique d'une application Web, comme une page d'accueil, un produit dans une boutique en ligne, un formulaire d'inscription, un flux sitemap, etc. L'application peut avoir de un à plusieurs milliers de présentateurs. Dans d'autres frameworks, ils sont également connus sous le nom de contrôleurs. +[Nous savons déjà |how-it-works#Nette Application] qu'un presenter est une classe qui représente une page web spécifique d'une application, par ex. la page d'accueil ; un produit dans une boutique en ligne ; un formulaire de connexion ; un flux sitemap, etc. Une application peut avoir d'un à des milliers de presenters. Dans d'autres frameworks, on les appelle aussi controllers. -En général, le terme "présentateur" fait référence à un descendant de la classe [api:Nette\Application\UI\Presenter], qui convient aux interfaces Web et dont nous parlerons dans la suite de ce chapitre. D'une manière générale, un présentateur est tout objet qui implémente l'interface [api:Nette\Application\IPresenter]. +Habituellement, par le terme presenter, on entend un descendant de la classe [api:Nette\Application\UI\Presenter], qui est adapté à la génération d'interfaces web et auquel nous nous consacrerons dans le reste de ce chapitre. Au sens général, un presenter est n'importe quel objet implémentant l'interface [api:Nette\Application\IPresenter]. -Cycle de vie du présentateur .[#toc-life-cycle-of-presenter] -============================================================ +Cycle de vie du presenter +========================= -Le travail du diffuseur consiste à traiter la demande et à renvoyer une réponse (qui peut être une page HTML, une image, une redirection, etc.) +La tâche du presenter est de traiter une requête et de retourner une réponse (qui peut être une page HTML, une image, une redirection, etc.). -Au départ, il y a donc une demande. Ce n'est pas directement une requête HTTP, mais un objet [api:Nette\Application\Request] en lequel la requête HTTP a été transformée à l'aide d'un routeur. Nous n'entrons généralement pas en contact avec cet objet, car le diffuseur délègue astucieusement le traitement de la requête à des méthodes spéciales, que nous allons voir maintenant. +Donc, au début, une requête lui est transmise. Ce n'est pas directement une requête HTTP, mais un objet [api:Nette\Application\Request], dans lequel la requête HTTP a été transformée à l'aide du routeur. Nous n'interagissons généralement pas directement avec cet objet, car le presenter délègue intelligemment le traitement de la requête à d'autres méthodes, que nous allons présenter maintenant. -[* lifecycle.svg *] *** *Cycle de vie du diffuseur* .<> +[* lifecycle.svg *] *** *Cycle de vie du presenter* .<> -La figure montre une liste de méthodes qui sont appelées séquentiellement de haut en bas, si elles existent. Aucune d'entre elles n'a besoin d'exister, nous pouvons avoir un présentateur complètement vide sans une seule méthode et construire un simple web statique dessus. +L'image représente la liste des méthodes qui sont appelées successivement de haut en bas, si elles existent. Aucune d'elles n'est obligatoire, nous pouvons avoir un presenter complètement vide sans une seule méthode et construire un site web statique simple dessus. `__construct()` --------------- -Le constructeur n'appartient pas exactement au cycle de vie du présentateur, car il est appelé au moment de la création de l'objet. Mais nous le mentionnons en raison de son importance. Le constructeur (avec la [méthode inject |best-practices:inject-method-attribute]) est utilisé pour passer les dépendances. +Le constructeur n'appartient pas tout à fait au cycle de vie du presenter, car il est appelé au moment de la création de l'objet. Mais nous le mentionnons en raison de son importance. Le constructeur (avec la [méthode inject|best-practices:inject-method-attribute]) sert à passer les dépendances. -Le présentateur ne doit pas s'occuper de la logique métier de l'application, écrire et lire dans la base de données, effectuer des calculs, etc. C'est la tâche des classes d'une couche, que nous appelons un modèle. Par exemple, la classe `ArticleRepository` peut être responsable du chargement et de la sauvegarde des articles. Pour que le présentateur puisse l'utiliser, elle est [passée en utilisant l'injection de dépendances |dependency-injection:passing-dependencies]: +Un presenter ne devrait pas gérer la logique métier de l'application, écrire et lire dans la base de données, effectuer des calculs, etc. C'est le rôle des classes de la couche que nous appelons modèle. Par exemple, la classe `ArticleRepository` peut être responsable du chargement et de la sauvegarde des articles. Pour que le presenter puisse travailler avec elle, il la reçoit via [l'injection de dépendances |dependency-injection:passing-dependencies] : ```php @@ -50,44 +50,44 @@ class ArticlePresenter extends Nette\Application\UI\Presenter `startup()` ----------- -Immédiatement après la réception de la demande, la méthode `startup ()` est invoquée. Vous pouvez l'utiliser pour initialiser les propriétés, vérifier les privilèges des utilisateurs, etc. Il est nécessaire de toujours appeler l'ancêtre `parent::startup()`. +Immédiatement après réception de la requête, la méthode `startup()` est appelée. Vous pouvez l'utiliser pour initialiser les propriétés, vérifier les permissions utilisateur, etc. Il est requis que la méthode appelle toujours le parent `parent::startup()`. `action(args...)` .{toc: action()} -------------------------------------------------- -Similaire à la méthode `render()`. Alors que `render()` a pour but de préparer les données pour un modèle spécifique, qui est ensuite rendu, en `action()` une requête est traitée sans qu'il y ait de rendu ultérieur du modèle. Par exemple, des données sont traitées, un utilisateur est connecté ou déconnecté, et ainsi de suite, puis il est [redirigé ailleurs |#Redirection]. +Analogue à la méthode `render()`. Alors que `render()` est destinée à préparer les données pour un template spécifique qui sera ensuite rendu, `action()` traite la requête sans lien avec le rendu du template. Par exemple, elle traite les données, connecte ou déconnecte l'utilisateur, etc., puis [redirige ailleurs |#Redirection]. -Il est important que `action()` soit appelé avant `render()`afin qu'à l'intérieur de celui-ci, nous puissions éventuellement modifier le cours suivant du cycle de vie, c'est-à-dire changer le modèle qui sera rendu et également la méthode `render()` qui sera appelée, en utilisant `setView('otherView')`. +Il est important que `action()` soit appelée avant `render()`, nous pouvons donc éventuellement y modifier le déroulement ultérieur, c'est-à-dire changer le template qui sera rendu, ainsi que la méthode `render()` qui sera appelée. Ceci est fait en utilisant `setView('autreVue')`. -Les paramètres de la requête sont transmis à la méthode. Il est possible et recommandé de spécifier des types pour les paramètres, par exemple `actionShow(int $id, string $slug = null)` - si le paramètre `id` est manquant ou s'il ne s'agit pas d'un nombre entier, le présentateur renvoie l'[erreur 404 |#Error 404 etc.] et met fin à l'opération. +Les paramètres de la requête sont passés à la méthode. Il est possible et recommandé de spécifier les types des paramètres, par ex. `actionShow(int $id, ?string $slug = null)` - si le paramètre `id` est manquant ou s'il n'est pas un entier, le presenter retournera une [erreur 404 |#Erreur 404 et autres] et terminera son activité. `handle(args...)` .{toc: handle()} -------------------------------------------------- -Cette méthode traite ce que l'on appelle les signaux, dont nous parlerons dans le chapitre sur les [composants |components#Signal]. Elle est destinée principalement aux composants et au traitement des requêtes AJAX. +La méthode traite les signaux, que nous découvrirons dans le chapitre consacré aux [composants |components#Signal]. Elle est en effet principalement destinée aux composants et au traitement des requêtes AJAX. -Les paramètres sont passés à la méthode, comme dans le cas de `action()`y compris la vérification de type. +Les paramètres de la requête sont passés à la méthode, comme dans le cas de `action()`, y compris la vérification de type. `beforeRender()` ---------------- -La méthode `beforeRender`, comme son nom l'indique, est appelée avant chaque méthode `render()`. Elle est utilisée pour la configuration commune des modèles, le passage de variables pour la mise en page, etc. +La méthode `beforeRender`, comme son nom l'indique, est appelée avant chaque méthode `render()`. Elle est utilisée pour la configuration commune du template, passer des variables au layout, etc. `render(args...)` .{toc: render()} ---------------------------------------------- -L'endroit où nous préparons le modèle pour un rendu ultérieur, nous lui passons des données, etc. +L'endroit où nous préparons le template pour le rendu ultérieur, lui passons des données, etc. -Les paramètres sont passés à la méthode, comme dans le cas de `action()`y compris la vérification de type. +Les paramètres de la requête sont passés à la méthode, comme dans le cas de `action()`, y compris la vérification de type. ```php public function renderShow(int $id): void { - // nous obtenons des données du modèle et les passons au modèle + // nous obtenons les données du modèle et les passons au template $this->template->article = $this->articles->getById($id); } ``` @@ -96,104 +96,104 @@ public function renderShow(int $id): void `afterRender()` --------------- -La méthode `afterRender`, comme son nom l'indique, est appelée après chaque méthode. `render()` méthode. Elle est utilisée assez rarement. +La méthode `afterRender`, comme son nom l'indique encore une fois, est appelée après chaque méthode `render()`. Elle est utilisée plutôt exceptionnellement. `shutdown()` ------------ -Il est appelé à la fin du cycle de vie du présentateur. +Appelée à la fin du cycle de vie du presenter. -**Bon conseil avant de passer à autre chose**. Comme vous pouvez le constater, le présentateur peut gérer plus d'actions/visites, c'est-à-dire avoir plus de méthodes... `render()`. Mais nous recommandons de concevoir des présentateurs avec une ou aussi peu d'actions que possible. +**Un bon conseil avant de continuer**. Comme vous pouvez le voir, un presenter peut gérer plusieurs actions/vues, c'est-à-dire avoir plusieurs méthodes `render()`. Mais nous recommandons de concevoir des presenters avec une seule ou le moins d'actions possible. -Envoi d'une réponse .[#toc-sending-a-response] -============================================== +Envoi de la réponse +=================== -La réponse du présentateur consiste généralement à [rendre le modèle avec la page HTML |templates], mais il peut aussi s'agir d'envoyer un fichier, JSON ou même de rediriger vers une autre page. +La réponse d'un presenter est généralement le [rendu d'un template avec une page HTML |templates], mais elle peut aussi être l'envoi d'un fichier, de JSON ou même une redirection vers une autre page. -À tout moment au cours du cycle de vie, vous pouvez utiliser l'une des méthodes suivantes pour envoyer une réponse et quitter le présentateur en même temps : +À tout moment du cycle de vie, nous pouvons envoyer une réponse avec l'une des méthodes suivantes et ainsi terminer le presenter : -- `redirect()`, `redirectPermanent()`, `redirectUrl()` et `forward()` [redirections |#Redirection] -- `error()` quitte le présentateur [en raison d'une erreur |#Error 404 etc.] -- `sendJson($data)` quitte le présentateur et [envoie les données |#Sending JSON] au format JSON. -- `sendTemplate()` quitte le présentateur et [rend |templates] immédiatement le [modèle |templates]. -- `sendResponse($response)` quitte le présentateur et envoie [sa propre réponse |#Responses] -- `terminate()` quitte le présentateur sans réponse +- `redirect()`, `redirectPermanent()`, `redirectUrl()` et `forward()` [redirigent |#Redirection] +- `error()` termine le presenter [en raison d'une erreur |#Erreur 404 et autres] +- `sendJson($data)` termine le presenter et [envoie les données |#Envoi de JSON] au format JSON +- `sendTemplate()` termine le presenter et [rend immédiatement le template |templates] +- `sendResponse($response)` termine le presenter et envoie une [réponse personnalisée |#Réponses] +- `terminate()` termine le presenter sans réponse -Si vous n'appelez aucune de ces méthodes, le présentateur procède automatiquement au rendu du modèle. Pourquoi ? Eh bien, parce que dans 99% des cas, nous voulons dessiner un modèle, donc le présentateur prend ce comportement par défaut et veut nous faciliter le travail. +Si vous n'appelez aucune de ces méthodes, le presenter procédera automatiquement au rendu du template. Pourquoi ? Parce que dans 99 % des cas, nous voulons rendre un template, donc le presenter considère ce comportement comme celui par défaut et veut nous faciliter le travail. -Création de liens .[#toc-creating-links] -======================================== +Création de liens +================= -Le présentateur possède une méthode `link()`, qui est utilisée pour créer des liens URL vers d'autres présentateurs. Le premier paramètre est le présentateur cible et l'action, suivi des arguments, qui peuvent être passés sous forme de tableau : +Le presenter dispose de la méthode `link()`, à l'aide de laquelle il est possible de créer des liens URL vers d'autres presenters. Le premier paramètre est le presenter & action cible, suivi des arguments passés, qui peuvent être spécifiés sous forme de tableau : ```php $url = $this->link('Product:show', $id); -$url = $this->link('Product:show', [$id, 'lang' => 'en']); +$url = $this->link('Product:show', [$id, 'lang' => 'cs']); ``` -Dans le modèle, nous créons des liens vers d'autres présentateurs et actions comme suit : +Dans le template, les liens vers d'autres presenters & actions sont créés de cette manière : ```latte -product detail +détail du produit ``` -Il suffit d'écrire la paire familière `Presenter:action` au lieu de l'URL réelle et d'inclure tous les paramètres. L'astuce est `n:href`, qui indique que cet attribut sera traité par Latte et générera une véritable URL. Dans Nette, vous ne devez pas du tout penser aux URL, mais seulement aux présentateurs et aux actions. +Au lieu d'une URL réelle, écrivez simplement la paire connue `Presenter:action` et spécifiez d'éventuels paramètres. L'astuce réside dans `n:href`, qui indique que cet attribut sera traité par Latte et générera une URL réelle. Dans Nette, vous n'avez donc pas du tout à penser aux URL, seulement aux presenters et aux actions. -Pour plus d'informations, voir [Création de liens |Creating Links]. +Plus d'informations peuvent être trouvées dans le chapitre [Création de liens URL|creating-links]. -Redirection .[#toc-redirection] -=============================== +Redirection +=========== -Les méthodes `redirect()` et `forward()` sont utilisées pour passer à un autre présentateur. Elles ont une syntaxe très similaire à celle de la méthode [link() |#Creating Links]. +Pour passer à un autre presenter, les méthodes `redirect()` et `forward()` sont utilisées, qui ont une syntaxe très similaire à la méthode [link() |#Création de liens]. -La méthode `forward()` permet de passer immédiatement au nouveau présentateur sans redirection HTTP : +La méthode `forward()` passe immédiatement au nouveau presenter sans redirection HTTP : ```php $this->forward('Product:show'); ``` -Exemple de redirection temporaire avec le code HTTP 302 ou 303 : +Exemple de redirection dite temporaire avec le code HTTP 302 (ou 303 si la méthode de la requête actuelle est POST) : ```php $this->redirect('Product:show', $id); ``` -Pour obtenir une redirection permanente avec le code HTTP 301, utilisez : +Vous obtenez une redirection permanente avec le code HTTP 301 comme ceci : ```php $this->redirectPermanent('Product:show', $id); ``` -Vous pouvez rediriger vers une autre URL en dehors de l'application avec la méthode `redirectUrl()`: +Il est possible de rediriger vers une autre URL en dehors de l'application avec la méthode `redirectUrl()`. Le code HTTP peut être spécifié comme deuxième paramètre, la valeur par défaut est 302 (ou 303 si la méthode de la requête actuelle est POST) : ```php $this->redirectUrl('https://nette.org'); ``` -La redirection met immédiatement fin au cycle de vie du présentateur en lançant l'exception dite de terminaison silencieuse `Nette\Application\AbortException`. +La redirection termine immédiatement l'activité du presenter en levant une exception de terminaison silencieuse appelée `Nette\Application\AbortException`. -Avant la redirection, il est possible d'envoyer un [message flash |#Flash Messages], message qui sera affiché dans le modèle après la redirection. +Avant la redirection, il est possible d'envoyer des [#messages flash], c'est-à-dire des messages qui seront affichés dans le template après la redirection. -Messages flash .[#toc-flash-messages] -===================================== +Messages Flash +============== -Il s'agit de messages qui informent généralement sur le résultat d'une opération. Une caractéristique importante des messages flash est qu'ils sont disponibles dans le modèle même après une redirection. Même après avoir été affichés, ils restent vivants pendant 30 secondes supplémentaires - par exemple, au cas où l'utilisateur rafraîchirait involontairement la page - le message ne sera pas perdu. +Ce sont des messages informant généralement du résultat d'une opération. Une caractéristique importante des messages flash est qu'ils sont disponibles dans le template même après une redirection. Même après affichage, ils restent actifs pendant 30 secondes supplémentaires – par exemple, au cas où l'utilisateur rafraîchirait la page en raison d'une erreur de transmission - le message ne disparaîtra donc pas immédiatement. -Il suffit d'appeler la méthode [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] et Presenter se chargera de transmettre le message au modèle. Le premier argument est le texte du message et le deuxième argument facultatif est son type (erreur, avertissement, info, etc.). La méthode `flashMessage()` renvoie une instance de message flash, pour nous permettre d'ajouter plus d'informations. +Il suffit d'appeler la méthode [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] et le presenter se chargera de la transmettre au template. Le premier paramètre est le texte du message et le deuxième paramètre facultatif est son type (error, warning, info, etc.). La méthode `flashMessage()` retourne une instance du message flash, à laquelle des informations supplémentaires peuvent être ajoutées. ```php -$this->flashMessage('Item was removed.'); -$this->redirect(/* ... */); +$this->flashMessage('L\'élément a été supprimé.'); +$this->redirect(/* ... */); // et nous redirigeons ``` -Dans le modèle, ces messages sont disponibles dans la variable `$flashes` en tant qu'objets `stdClass`, qui contiennent les propriétés `message` (texte du message), `type` (type de message) et peuvent contenir les informations utilisateur déjà mentionnées. Nous les dessinons comme suit : +Dans le template, ces messages sont disponibles dans la variable `$flashes` sous forme d'objets `stdClass`, qui contiennent les propriétés `message` (texte du message), `type` (type de message) et peuvent contenir les informations utilisateur mentionnées précédemment. Nous les rendons par exemple comme ceci : ```latte {foreach $flashes as $flash} @@ -202,10 +202,10 @@ Dans le modèle, ces messages sont disponibles dans la variable `$flashes` en ta ``` -Erreur 404 etc. .[#toc-error-404-etc] -===================================== +Erreur 404 et autres +==================== -Lorsque nous ne pouvons pas répondre à la demande, par exemple parce que l'article que nous voulons afficher n'existe pas dans la base de données, nous envoyons l'erreur 404 en utilisant la méthode `error(string $message = null, int $httpCode = 404)`, qui représente l'erreur HTTP 404 : +Si la requête ne peut pas être satisfaite, par exemple parce que l'article que nous voulons afficher n'existe pas dans la base de données, nous levons une erreur 404 avec la méthode `error(?string $message = null, int $httpCode = 404)`. ```php public function renderShow(int $id): void @@ -218,14 +218,13 @@ public function renderShow(int $id): void } ``` -Le code d'erreur HTTP peut être passé comme deuxième paramètre, la valeur par défaut est 404. La méthode fonctionne en lançant l'exception `Nette\Application\BadRequestException`, après quoi `Application` passe le contrôle au présentateur de l'erreur. Il s'agit d'un présentateur dont le rôle est d'afficher une page d'information sur l'erreur. -Le présentateur d'erreur est défini dans la [configuration de l'application |configuration]. +Le code d'erreur HTTP peut être passé comme deuxième paramètre, la valeur par défaut est 404. La méthode fonctionne en levant une exception `Nette\Application\BadRequestException`, après quoi `Application` passe le contrôle à l'error-presenter. C'est un presenter dont la tâche est d'afficher une page informant de l'erreur survenue. La configuration de l'error-preseter se fait dans la [configuration application|configuration]. -Envoi de JSON .[#toc-sending-json] -================================== +Envoi de JSON +============= -Exemple de méthode d'action qui envoie des données au format JSON et quitte le présentateur : +Exemple de méthode d'action qui envoie des données au format JSON et termine le presenter : ```php public function actionData(): void @@ -236,33 +235,59 @@ public function actionData(): void ``` -Paramètres persistants .[#toc-persistent-parameters] -==================================================== +Paramètres de la requête .{data-version:3.1.14} +=============================================== -Les paramètres persistants sont utilisés pour maintenir l'état entre les différentes requêtes. Leur valeur reste inchangée même après avoir cliqué sur un lien. Contrairement aux données de session, ils sont transmis dans l'URL. Cette opération est entièrement automatique, il n'est donc pas nécessaire de les indiquer explicitement dans `link()` ou `n:href`. +Le presenter ainsi que chaque composant obtiennent leurs paramètres de la requête HTTP. Vous pouvez connaître leur valeur avec la méthode `getParameter($name)` ou `getParameters()`. Les valeurs sont des chaînes ou des tableaux de chaînes, il s'agit essentiellement de données brutes obtenues directement de l'URL. -Exemple d'utilisation ? Vous avez une application multilingue. La langue réelle est un paramètre qui doit toujours faire partie de l'URL. Mais il serait incroyablement fastidieux de l'inclure dans chaque lien. Vous en faites donc un paramètre persistant, nommé `lang`, qui se chargera de lui-même. C'est super ! +Pour plus de commodité, nous recommandons de rendre les paramètres accessibles via une propriété. Il suffit de les marquer avec l'attribut `#[Parameter]` : + +```php +use Nette\Application\Attributes\Parameter; // cette ligne est importante + +class HomePresenter extends Nette\Application\UI\Presenter +{ + #[Parameter] + public string $theme; // doit être public +} +``` + +Nous recommandons d'indiquer également le type de données pour la propriété (par ex. `string`) et Nette transtypera automatiquement la valeur en conséquence. Les valeurs des paramètres peuvent également être [validées |#Validation des paramètres]. + +Lors de la création d'un lien, la valeur des paramètres peut être définie directement : + +```latte +cliquer +``` -La création d'un paramètre persistant est extrêmement simple dans Nette. Il suffit de créer une propriété publique et de la baliser avec l'attribut : (auparavant `/** @persistent */` était utilisé) + +Paramètres persistants +====================== + +Les paramètres persistants sont utilisés pour maintenir l'état entre différentes requêtes. Leur valeur reste la même même après avoir cliqué sur un lien. Contrairement aux données de session, ils sont transmis dans l'URL. Et cela de manière entièrement automatique, il n'est donc pas nécessaire de les spécifier explicitement dans `link()` ou `n:href`. + +Exemple d'utilisation ? Vous avez une application multilingue. La langue actuelle est un paramètre qui doit constamment faire partie de l'URL. Mais il serait incroyablement fastidieux de l'indiquer dans chaque lien. Vous en faites donc un paramètre persistant `lang` et il sera transmis automatiquement. Génial ! + +La création d'un paramètre persistant est extrêmement simple dans Nette. Il suffit de créer une propriété publique et de la marquer avec un attribut : (auparavant, `/** @persistent */` était utilisé) ```php -use Nette\Application\Attributes\Persistent; // cette ligne est importante +use Nette\Application\Attributes\Persistent; // cette ligne est importante class ProductPresenter extends Nette\Application\UI\Presenter { #[Persistent] - public string $lang; // doit être publique + public string $lang; // doit être public } ``` -Si `$this->lang` a une valeur telle que `'en'`, les liens créés à l'aide de `link()` ou `n:href` contiendront également le paramètre `lang=en`. Et lorsque le lien sera cliqué, il s'agira à nouveau de `$this->lang = 'en'`. +Si `$this->lang` a la valeur, par exemple, `'en'`, alors les liens créés à l'aide de `link()` ou `n:href` contiendront également le paramètre `lang=en`. Et après avoir cliqué sur le lien, `$this->lang` sera à nouveau `'en'`. -Pour les propriétés, nous vous recommandons d'indiquer le type de données (par exemple `string`) et vous pouvez également inclure une valeur par défaut. Les valeurs des paramètres peuvent être [validées |#Validation of Persistent Parameters]. +Nous recommandons d'indiquer également le type de données pour la propriété (par ex. `string`) et vous pouvez également spécifier une valeur par défaut. Les valeurs des paramètres peuvent être [validées |#Validation des paramètres]. -Les paramètres persistants sont transmis par défaut entre toutes les actions d'un présentateur donné. Pour les transmettre entre plusieurs présentateurs, vous devez les définir : +Les paramètres persistants sont transmis par défaut entre toutes les actions du presenter donné. Pour qu'ils soient également transmis entre plusieurs presenters, il faut les définir soit : -- dans un ancêtre commun dont les présentateurs héritent -- dans le trait que les présentateurs utilisent : +- dans un ancêtre commun dont les presenters héritent +- dans un trait que les presenters utilisent : ```php trait LanguageAware @@ -277,48 +302,42 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -Vous pouvez modifier la valeur d'un paramètre persistant lors de la création d'un lien : +Lors de la création d'un lien, la valeur du paramètre persistant peut être modifiée : ```latte -detail in Czech +détail en tchèque ``` Ou il peut être *réinitialisé*, c'est-à-dire supprimé de l'URL. Il prendra alors sa valeur par défaut : ```latte -click +cliquer ``` -Composants interactifs .[#toc-interactive-components] -===================================================== +Composants interactifs +====================== -Les présentateurs ont un système de composants intégré. Les composants sont des unités distinctes réutilisables que nous plaçons dans les présentateurs. Il peut s'agir de [formulaires |forms:in-presenter], de grilles de données, de menus, en fait de tout ce qui peut être utilisé de manière répétée. +Les presenters ont un système de composants intégré. Les composants sont des unités autonomes et réutilisables que nous insérons dans les presenters. Il peut s'agir de [formulaires |forms:in-presenter], de datagrids, de menus, en fait de tout ce qu'il est judicieux d'utiliser de manière répétée. -Comment les composants sont-ils placés et ensuite utilisés dans le présentateur ? Ceci est expliqué dans le chapitre [Composants |Components]. Vous découvrirez même ce qu'ils ont à voir avec Hollywood. +Comment les composants sont-ils insérés dans le presenter et ensuite utilisés ? Vous l'apprendrez dans le chapitre [Composants |components]. Vous découvrirez même ce qu'ils ont en commun avec Hollywood. -Où puis-je trouver des composants ? Sur la page [Componette |https://componette.org], vous trouverez des composants open-source et d'autres modules complémentaires pour Nette qui sont réalisés et partagés par la communauté de Nette Framework. +Et où puis-je obtenir des composants ? Sur la page [Componette |https://componette.org/search/component], vous trouverez des composants open-source ainsi que de nombreux autres add-ons pour Nette, placés ici par des bénévoles de la communauté autour du framework. -Pour aller plus loin .[#toc-going-deeper] -========================================= +Allons plus en profondeur +========================= .[tip] -Ce que nous avons montré jusqu'à présent dans ce chapitre suffira probablement. Les lignes suivantes sont destinées à ceux qui s'intéressent aux présentateurs en profondeur et veulent tout savoir. - - -Exigences et paramètres .[#toc-requirement-and-parameters] ----------------------------------------------------------- +Ce que nous avons montré jusqu'à présent dans ce chapitre vous suffira probablement amplement. Les lignes suivantes sont destinées à ceux qui s'intéressent aux presenters en profondeur et veulent tout savoir. -La requête traitée par le présentateur est l'objet [api:Nette\Application\Request] et est renvoyée par la méthode du présentateur `getRequest()`. Elle comprend un tableau de paramètres et chacun d'entre eux appartient soit à l'un des composants, soit directement au présentateur (qui est également un composant, bien qu'il soit spécial). Nette redistribue donc les paramètres et les transmet entre les différents composants (et le présentateur) en appelant la méthode `loadState(array $params)`. Les paramètres peuvent être obtenus par la méthode `getParameters(): array`, individuellement en utilisant `getParameter($name)`. Les valeurs des paramètres sont des chaînes ou des tableaux de chaînes, il s'agit essentiellement de données brutes obtenues directement à partir d'une URL. +Validation des paramètres +------------------------- -Validation des paramètres persistants .[#toc-validation-of-persistent-parameters] ---------------------------------------------------------------------------------- - -Les valeurs des [paramètres persistants |#persistent parameters] reçus des URL sont écrites dans les propriétés par la méthode `loadState()`. Elle vérifie également si le type de données spécifié dans la propriété correspond, sinon elle répondra par une erreur 404 et la page ne sera pas affichée. +Les valeurs des [#paramètres de la requête] et des [#paramètres persistants] reçues de l'URL sont écrites dans les propriétés par la méthode `loadState()`. Celle-ci vérifie également si le type de données indiqué pour la propriété correspond, sinon elle répond par une erreur 404 et la page ne s'affiche pas. -Ne faites jamais aveuglément confiance aux paramètres persistants, car ils peuvent facilement être remplacés par l'utilisateur dans l'URL. Par exemple, voici comment nous vérifions si `$this->lang` fait partie des langues prises en charge. Une bonne façon de le faire est de surcharger la méthode `loadState()` mentionnée ci-dessus : +Ne faites jamais confiance aveuglément aux paramètres, car ils peuvent être facilement modifiés par l'utilisateur dans l'URL. Voici comment nous vérifions, par exemple, si la langue `$this->lang` fait partie des langues prises en charge. Une bonne approche consiste à redéfinir la méthode `loadState()` mentionnée : ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -328,8 +347,8 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function loadState(array $params): void { - parent::loadState($params); // ici est défini le $this->lang - // suit la vérification de la valeur de l'utilisateur: + parent::loadState($params); // ici $this->lang est défini + // suit la vérification personnalisée de la valeur : if (!in_array($this->lang, ['en', 'cs'])) { $this->error(); } @@ -338,43 +357,45 @@ class ProductPresenter extends Nette\Application\UI\Presenter ``` -Sauvegarde et restauration de la demande .[#toc-save-and-restore-the-request] ------------------------------------------------------------------------------ +Sauvegarde et restauration de la requête +---------------------------------------- -Vous pouvez enregistrer la demande en cours dans une session ou la restaurer à partir de la session et laisser le présentateur l'exécuter à nouveau. Ceci est utile, par exemple, lorsqu'un utilisateur remplit un formulaire et que sa connexion expire. Afin de ne pas perdre de données, avant de rediriger l'utilisateur vers la page de connexion, nous sauvegardons la demande actuelle dans la session à l'aide de `$reqId = $this->storeRequest()`, qui renvoie un identifiant sous la forme d'une chaîne courte et le transmet comme paramètre au présentateur de connexion. +La requête traitée par le presenter est un objet [api:Nette\Application\Request] et est retournée par la méthode du presenter `getRequest()`. -Après l'ouverture de session, nous appelons la méthode `$this->restoreRequest($reqId)`, qui récupère la demande de la session et la lui transmet. La méthode vérifie que la requête a été créée par le même utilisateur que celui qui est maintenant connecté. Si un autre utilisateur se connecte ou si la clé n'est pas valide, elle ne fait rien et le programme continue. +La requête actuelle peut être sauvegardée dans la session ou au contraire restaurée à partir de celle-ci et laisser le presenter l'exécuter à nouveau. C'est utile par exemple dans une situation où l'utilisateur remplit un formulaire et sa connexion expire. Pour ne pas perdre les données, avant de rediriger vers la page de connexion, nous sauvegardons la requête actuelle dans la session à l'aide de `$reqId = $this->storeRequest()`, qui retourne son identifiant sous forme de chaîne courte, et nous le passons comme paramètre au presenter de connexion. -Voir le livre de recettes [Comment revenir à une page antérieure |best-practices:restore-request]. +Après la connexion, nous appelons la méthode `$this->restoreRequest($reqId)`, qui récupère la requête de la session et la transmet (forward). La méthode vérifie en même temps que la requête a été créée par le même utilisateur que celui qui vient de se connecter. Si un autre utilisateur se connecte ou si la clé est invalide, elle ne fait rien et le programme continue. +Consultez le guide [Comment revenir à une page précédente |best-practices:restore-request]. -Canonisation .[#toc-canonization] ---------------------------------- -Les présentateurs ont une fonction vraiment formidable qui améliore le référencement (optimisation de la possibilité de recherche sur Internet). Ils empêchent automatiquement l'existence de contenu dupliqué à différentes URL. Si plusieurs URL mènent à une certaine destination, par exemple `/index` et `/index?page=1`, le framework désigne l'une d'entre elles comme primaire (canonique) et redirige les autres vers elle en utilisant le code HTTP 301. Grâce à cela, les moteurs de recherche n'indexent pas deux fois les pages et n'affaiblissent pas leur page rank. +Canonisation +------------ -Ce processus est appelé canonisation. L'URL canonique est l'URL générée par le [routeur |routing], généralement la première route appropriée dans la collection. +Les presenters ont une fonctionnalité vraiment géniale qui contribue à un meilleur SEO (optimisation pour les moteurs de recherche). Ils empêchent automatiquement l'existence de contenu dupliqué sur différentes URL. Si plusieurs adresses URL mènent à une certaine cible, par ex. `/index` et `/index?page=1`, le framework détermine l'une d'elles comme étant la principale (canonique) et redirige les autres vers elle à l'aide du code HTTP 301. Grâce à cela, les moteurs de recherche n'indexent pas vos pages deux fois et ne diluent pas leur page rank. + +Ce processus est appelé canonisation. L'URL canonique est celle générée par le [routeur|routing], généralement la première route correspondante dans la collection. La canonisation est activée par défaut et peut être désactivée via `$this->autoCanonicalize = false`. -La redirection ne se produit pas avec une demande AJAX ou POST, car elle entraînerait une perte de données ou une absence de valeur ajoutée en termes de référencement. +La redirection ne se produit pas lors d'une requête AJAX ou POST, car cela entraînerait une perte de données ou n'aurait aucune valeur ajoutée du point de vue du SEO. -Vous pouvez également invoquer la canonisation manuellement à l'aide de la méthode `canonicalize()`, qui, comme la méthode `link()`, reçoit le présentateur, les actions et les paramètres comme arguments. Elle crée un lien et le compare à l'URL actuelle. Si elle est différente, elle redirige vers le lien généré. +Vous pouvez également déclencher la canonisation manuellement à l'aide de la méthode `canonicalize()`, à laquelle, comme à la méthode `link()`, sont passés le presenter, l'action et les paramètres. Elle crée un lien et le compare à l'URL actuelle. S'ils diffèrent, elle redirige vers le lien généré. ```php -public function actionShow(int $id, string $slug = null): void +public function actionShow(int $id, ?string $slug = null): void { $realSlug = $this->facade->getSlugForId($id); - // redirige si $slug est différent de $realSlug + // redirige si $slug diffère de $realSlug $this->canonicalize('Product:show', [$id, $realSlug]); } ``` -Événements .[#toc-events] -------------------------- +Événements +---------- -En plus des méthodes `startup()`, `beforeRender()` et `shutdown()`, qui sont appelées dans le cadre du cycle de vie du diffuseur, d'autres fonctions peuvent être définies pour être appelées automatiquement. Le diffuseur définit ce que l'on appelle des [événements |nette:glossary#events], et vous ajoutez leurs gestionnaires aux tableaux `$onStartup`, `$onRender` et `$onShutdown`. +En plus des méthodes `startup()`, `beforeRender()` et `shutdown()`, qui sont appelées dans le cadre du cycle de vie du presenter, il est possible de définir d'autres fonctions qui doivent être appelées automatiquement. Le presenter définit ce qu'on appelle des [événements |nette:glossary#Événements events], dont vous ajoutez les gestionnaires aux tableaux `$onStartup`, `$onRender` et `$onShutdown`. ```php class ArticlePresenter extends Nette\Application\UI\Presenter @@ -388,34 +409,34 @@ class ArticlePresenter extends Nette\Application\UI\Presenter } ``` -Les gestionnaires du tableau `$onStartup` sont appelés juste avant la méthode `startup()`, puis `$onRender` entre `beforeRender()` et `render()` et enfin `$onShutdown` juste avant `shutdown()`. +Les gestionnaires dans le tableau `$onStartup` sont appelés juste avant la méthode `startup()`, ensuite `$onRender` entre `beforeRender()` et `render()` et enfin `$onShutdown` juste avant `shutdown()`. -Réponses .[#toc-responses] --------------------------- +Réponses +-------- -La réponse renvoyée par le présentateur est un objet implémentant l'interface [api:Nette\Application\Response]. Il existe un certain nombre de réponses toutes faites : +La réponse retournée par le presenter est un objet implémentant l'interface [api:Nette\Application\Response]. Il existe un certain nombre de réponses prêtes à l'emploi : - [api:Nette\Application\Responses\CallbackResponse] - envoie un callback -- [api:Nette\Application\Responses\FileResponse] - envoie le fichier -- [api:Nette\Application\Responses\ForwardResponse] - envoie () -- [api:Nette\Application\Responses\JsonResponse] - envoie JSON +- [api:Nette\Application\Responses\FileResponse] - envoie un fichier +- [api:Nette\Application\Responses\ForwardResponse] - forward() +- [api:Nette\Application\Responses\JsonResponse] - envoie du JSON - [api:Nette\Application\Responses\RedirectResponse] - redirection - [api:Nette\Application\Responses\TextResponse] - envoie du texte - [api:Nette\Application\Responses\VoidResponse] - réponse vide -Les réponses sont envoyées par la méthode `sendResponse()`: +Les réponses sont envoyées avec la méthode `sendResponse()` : ```php use Nette\Application\Responses; -// Texte en clair +// Texte simple $this->sendResponse(new Responses\TextResponse('Hello Nette!')); -// Envoi d'un fichier +// Envoie un fichier $this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf')); -// Envoi d'un callback +// La réponse sera un callback $callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse) { if ($httpResponse->getHeader('Content-Type') === 'text/html') { echo '

    Hello

    '; @@ -425,10 +446,55 @@ $this->sendResponse(new Responses\CallbackResponse($callback)); ``` -Autres lectures .[#toc-further-reading] -======================================= +Restriction d'accès avec `#[Requires]` .{data-version:3.2.2} +------------------------------------------------------------ + +L'attribut `#[Requires]` offre des options avancées pour restreindre l'accès aux presenters et à leurs méthodes. Il peut être utilisé pour spécifier les méthodes HTTP, exiger une requête AJAX, limiter à la même origine (same origin), et l'accès uniquement via le forwarding. L'attribut peut être appliqué à la fois aux classes de presenter et aux méthodes individuelles `action()`, `render()`, `handle()` et `createComponent()`. + +Vous pouvez spécifier ces restrictions : +- sur les méthodes HTTP : `#[Requires(methods: ['GET', 'POST'])]` +- exiger une requête AJAX : `#[Requires(ajax: true)]` +- accès uniquement depuis la même origine : `#[Requires(sameOrigin: true)]` +- accès uniquement via forward : `#[Requires(forward: true)]` +- restriction à des actions spécifiques : `#[Requires(actions: 'default')]` + +Les détails se trouvent dans le guide [Comment utiliser l'attribut Requires |best-practices:attribute-requires]. + + +Vérification de la méthode HTTP +------------------------------- + +Les presenters dans Nette vérifient automatiquement la méthode HTTP de chaque requête entrante. La raison de cette vérification est principalement la sécurité. Par défaut, les méthodes `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH` sont autorisées. + +Si vous souhaitez autoriser en plus, par exemple, la méthode `OPTIONS`, utilisez l'attribut `#[Requires]` (depuis Nette Application v3.2) : + +```php +#[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])] +class MyPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Dans la version 3.1, la vérification est effectuée dans `checkHttpMethod()`, qui vérifie si la méthode spécifiée dans la requête est contenue dans le tableau `$presenter->allowedMethods`. L'ajout de la méthode se fait comme ceci : + +```php +class MyPresenter extends Nette\Application\UI\Presenter +{ + protected function checkHttpMethod(): void + { + $this->allowedMethods[] = 'OPTIONS'; + parent::checkHttpMethod(); + } +} +``` + +Il est important de souligner que si vous autorisez la méthode `OPTIONS`, vous devez ensuite la gérer correctement dans votre presenter. La méthode est souvent utilisée comme une requête dite preflight, que le navigateur envoie automatiquement avant la requête réelle lorsqu'il est nécessaire de déterminer si la requête est autorisée du point de vue de la politique CORS (Cross-Origin Resource Sharing). Si vous autorisez la méthode mais n'implémentez pas la réponse correcte, cela peut entraîner des incohérences et des problèmes de sécurité potentiels. + + +Lectures complémentaires +======================== -- [Injecter des méthodes et des attributs |best-practices:inject-method-attribute] -- [Composer des présentateurs à partir de traits |best-practices:presenter-traits] -- [Transmettre des paramètres aux présentateurs |best-practices:passing-settings-to-presenters] +- [Méthodes et attributs inject |best-practices:inject-method-attribute] +- [Composition de presenters à partir de traits |best-practices:presenter-traits] +- [Passer des paramètres aux presenters |best-practices:passing-settings-to-presenters] - [Comment revenir à une page précédente |best-practices:restore-request] diff --git a/application/fr/routing.texy b/application/fr/routing.texy index a2ec2aff40..66bfebc31b 100644 --- a/application/fr/routing.texy +++ b/application/fr/routing.texy @@ -1,29 +1,28 @@ -Acheminement -************ +Routage +*******
    -Le routeur s'occupe de tout ce qui concerne les URL afin que vous n'ayez plus à y penser. Nous allons le montrer : +Le routeur s'occupe de tout ce qui concerne les adresses URL, afin que vous n'ayez plus à y penser. Nous allons montrer : -- comment configurer le routeur pour que les URLs ressemblent à ce que vous voulez -- quelques remarques sur la redirection SEO -- et nous vous montrerons comment écrire votre propre routeur. +- comment configurer le routeur pour que les URL soient conformes aux attentes +- nous parlerons de SEO et de redirection +- et nous montrerons comment écrire votre propre routeur
    -Les URL plus humaines (ou cool ou jolies URL) sont plus utilisables, plus mémorables et contribuent positivement au référencement. Nette a cette idée en tête et répond parfaitement aux souhaits des développeurs. Vous pouvez concevoir la structure de l'URL de votre application exactement comme vous le souhaitez. -Vous pouvez même la concevoir après que l'application soit prête, car cela peut se faire sans aucune modification du code ou du modèle. Elle est définie de manière élégante en [un seul endroit |#Integration], dans le routeur, et n'est pas dispersée sous forme d'annotations dans tous les présentateurs. +Les URL conviviales (aussi appelées cool ou pretty URL) sont plus utilisables, plus faciles à mémoriser et contribuent positivement au SEO. Nette y pense et répond pleinement aux attentes des développeurs. Vous pouvez concevoir pour votre application exactement la structure d'adresses URL que vous souhaitez. Vous pouvez même la concevoir lorsque l'application est déjà terminée, car cela se fait sans intervention dans le code ou les templates. Elle est définie de manière élégante en un [seul endroit |#Intégration dans l application], dans le routeur, et n'est donc pas dispersée sous forme d'annotations dans tous les presenters. -Le routeur de Nette est spécial car il est **bidirectionnel**, il peut à la fois décoder les URL des requêtes HTTP et créer des liens. Il joue donc un rôle essentiel dans l'[application Nette |how-it-works#Nette Application], car il décide du présentateur et de l'action qui exécuteront la requête en cours, et est également utilisé pour la [génération d'URL |creating-links] dans le modèle, etc. +Le routeur dans Nette est exceptionnel car il est **bidirectionnel.** Il sait à la fois décoder les URL dans la requête HTTP et créer des liens. Il joue donc un rôle essentiel dans [Nette Application |how-it-works#Nette Application], car il décide non seulement quel presenter et quelle action exécuteront la requête actuelle, mais il est également utilisé pour la [génération d'URL |creating-links] dans le template, etc. -Cependant, le routeur n'est pas limité à cet usage, vous pouvez l'utiliser dans des applications où les présentateurs ne sont pas du tout utilisés, pour les API REST, etc. Pour en savoir plus, consultez la section [Utilisation séparée |#separated usage]. +Cependant, le routeur n'est pas limité à cette seule utilisation, vous pouvez l'utiliser dans des applications où les presenters ne sont pas du tout utilisés, pour des API REST, etc. Plus d'informations dans la section [#Utilisation autonome]. -Collection de routes .[#toc-route-collection] -============================================= +Collection de routes +==================== -La manière la plus agréable de définir les adresses URL dans l'application est via la classe [api:Nette\Application\Routers\RouteList]. La définition consiste en une liste de ce que l'on appelle des routes, c'est-à-dire des masques d'adresses URL et leurs présentateurs et actions associés utilisant une API simple. Il n'est pas nécessaire de nommer les routes. +La manière la plus agréable de définir la forme des adresses URL dans l'application est offerte par la classe [api:Nette\Application\Routers\RouteList]. La définition est constituée d'une liste de routes, c'est-à-dire de masques d'adresses URL et des presenters et actions qui leur sont associés via une API simple. Nous n'avons pas besoin de nommer les routes. ```php $router = new Nette\Application\Routers\RouteList; @@ -32,16 +31,16 @@ $router->addRoute('article/', 'Article:view'); // ... ``` -L'exemple dit que si nous ouvrons `https://any-domain.com/rss.xml` avec l'action `rss` sera affiché, si `https://domain.com/article/12` avec l'action `view` est affiché, etc. Si aucune route appropriée n'est trouvée, Nette Application répond en lançant une exception [BadRequestException |api:Nette\Application\BadRequestException], qui apparaît à l'utilisateur comme une page d'erreur 404 Not Found. +L'exemple indique que si nous ouvrons `https://domain.com/rss.xml` dans le navigateur, le presenter `Feed` avec l'action `rss` sera affiché, si `https://domain.com/article/12`, le presenter `Article` avec l'action `view` sera affiché, etc. En cas de non-correspondance de route appropriée, Nette Application réagit en levant une exception [BadRequestException |api:Nette\Application\BadRequestException], qui s'affiche à l'utilisateur comme une page d'erreur 404 Not Found. -Ordre des routes .[#toc-order-of-routes] ----------------------------------------- +Ordre des routes +---------------- -L'ordre dans lequel les routes sont listées est **très important** car elles sont évaluées séquentiellement de haut en bas. La règle est que l'on déclare les routes **du spécifique au général** : +L'**ordre** dans lequel les différentes routes sont listées est **absolument crucial**, car elles sont évaluées séquentiellement de haut en bas. La règle est que nous déclarons les routes **des plus spécifiques aux plus générales** : ```php -// FAUX: 'rss.xml' correspond à la première route et comprend qu'il s'agit de . +// MAUVAIS : 'rss.xml' est capturé par la première route et interprète cette chaîne comme $router->addRoute('', 'Article:view'); $router->addRoute('rss.xml', 'Feed:rss'); @@ -50,10 +49,10 @@ $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('', 'Article:view'); ``` -Les routes sont également évaluées de haut en bas lorsque les liens sont générés : +Les routes sont également évaluées de haut en bas lors de la génération de liens : ```php -// FAUX: génère un lien vers 'Feed:rss' comme 'admin/feed/rss'. +// MAUVAIS : le lien vers 'Feed:rss' générera 'admin/feed/rss' $router->addRoute('admin//', 'Admin:default'); $router->addRoute('rss.xml', 'Feed:rss'); @@ -62,54 +61,54 @@ $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('admin//', 'Admin:default'); ``` -Nous ne vous cacherons pas qu'il faut un certain savoir-faire pour construire correctement une liste. En attendant que vous vous y mettiez, le [panneau de routage |#Debugging Router] sera un outil utile. +Nous n'allons pas vous cacher que la composition correcte des routes demande une certaine compétence. Avant de la maîtriser, le [panneau de routage |#Débogage du routeur] vous sera un outil utile. -Masque et paramètres .[#toc-mask-and-parameters] ------------------------------------------------- +Masque et paramètres +-------------------- -Le masque décrit le chemin relatif basé sur la racine du site. Le masque le plus simple est une URL statique : +Le masque décrit le chemin relatif depuis le répertoire racine du site web. Le masque le plus simple est une URL statique : ```php $router->addRoute('products', 'Products:default'); ``` -Les masques contiennent souvent ce que l'on appelle des **paramètres**. Ils sont placés entre des crochets (par ex. ``) et sont transmis au diffuseur cible, par exemple à la méthode `renderShow(int $year)` ou au paramètre persistant `$year`: +Souvent, les masques contiennent des **paramètres**. Ils sont indiqués entre chevrons (par ex. ``) et sont transmis au presenter cible, par exemple à la méthode `renderShow(int $year)` ou au paramètre persistant `$year` : ```php $router->addRoute('chronicle/', 'History:show'); ``` -L'exemple indique que si nous ouvrons `https://any-domain.com/chronicle/2020` et l'action `show` avec le paramètre `year: 2020` s'afficheront. +L'exemple indique que si nous ouvrons `https://example.com/chronicle/2020` dans le navigateur, le presenter `History` avec l'action `show` et le paramètre `year: 2020` sera affiché. -Nous pouvons spécifier une valeur par défaut pour les paramètres directement dans le masque et ainsi il devient facultatif : +Nous pouvons spécifier une valeur par défaut pour les paramètres directement dans le masque, ce qui les rend facultatifs : ```php $router->addRoute('chronicle/', 'History:show'); ``` -La route acceptera maintenant l'URL `https://any-domain.com/chronicle/` avec le paramètre `year: 2020`. +La route acceptera désormais également l'URL `https://example.com/chronicle/`, qui affichera à nouveau `History:show` avec le paramètre `year: 2020`. -Bien entendu, le nom du présentateur et de l'action peut également être un paramètre. Par exemple : +Le paramètre peut bien sûr aussi être le nom du presenter et de l'action. Par exemple comme ceci : ```php $router->addRoute('/', 'Home:default'); ``` -Cette route accepte, par exemple, une URL sous la forme `/article/edit` resp. `/catalog/list` et les traduit en présentateurs et actions `Article:edit` resp. `Catalog:list`. +La route spécifiée accepte par ex. des URL de la forme `/article/edit` ou aussi `/catalog/list` et les interprète comme des presenters et actions `Article:edit` et `Catalog:list`. -Elle donne également aux paramètres `presenter` et `action` des valeurs par défaut`Home` et `default` et ils sont donc facultatifs. Ainsi, la route accepte également une URL `/article` et la traduit en `Article:default`. Ou vice versa, un lien vers `Product:default` génère un chemin `/product`, un lien vers le défaut `Home:default` génère un chemin `/`. +En même temps, elle donne aux paramètres `presenter` et `action` les valeurs par défaut `Home` et `default` et ils sont donc également facultatifs. Ainsi, la route accepte également une URL de la forme `/article` et l'interprète comme `Article:default`. Ou inversement, un lien vers `Product:default` générera le chemin `/product`, un lien vers le `Home:default` par défaut générera le chemin `/`. -Le masque peut décrire non seulement le chemin relatif basé sur la racine du site, mais aussi le chemin absolu lorsqu'il commence par une barre oblique, ou même l'URL absolue entière lorsqu'elle commence par deux barres obliques : +Le masque peut décrire non seulement le chemin relatif depuis le répertoire racine du site web, mais aussi le chemin absolu s'il commence par une barre oblique, ou même une URL absolue entière si elle commence par deux barres obliques : ```php -// chemin relatif vers la racine du document de l'application +// relatif au document root $router->addRoute('/', /* ... */); -// chemin absolu, relatif au nom d'hôte du serveur +// chemin absolu (relatif au domaine) $router->addRoute('//', /* ... */); -// URL absolue incluant le nom d'hôte (mais relative au schéma) +// URL absolue incluant le domaine (relative au schéma) $router->addRoute('//.example.com//', /* ... */); // URL absolue incluant le schéma @@ -117,45 +116,45 @@ $router->addRoute('https://.example.com//', /* ... */); ``` -Expressions de validation .[#toc-validation-expressions] --------------------------------------------------------- +Expressions de validation +------------------------- -Une condition de validation peut être spécifiée pour chaque paramètre à l'aide d'une [expression régulière |https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Par exemple, définissons `id` pour qu'il soit uniquement numérique, en utilisant l'expression régulière `\d+`: +Pour chaque paramètre, une condition de validation peut être établie à l'aide d'une [expression régulière|https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Par exemple, pour le paramètre `id`, nous spécifions qu'il ne peut contenir que des chiffres à l'aide de l'expression régulière `\d+` : ```php $router->addRoute('/[/]', /* ... */); ``` -L'expression régulière par défaut pour tous les paramètres est `[^/]+`c'est-à-dire tout sauf la barre oblique. Si un paramètre est censé correspondre à un slash également, nous définissons l'expression régulière à `.+`. +L'expression régulière par défaut pour tous les paramètres est `[^/]+`, c'est-à-dire tout sauf la barre oblique. Si un paramètre doit également accepter des barres obliques, nous spécifions l'expression `.+` : ```php -// accepte https://example.com/a/b/c, le chemin est 'a/b/c'. +// accepte https://example.com/a/b/c, path sera 'a/b/c' $router->addRoute('', /* ... */); ``` -Séquences facultatives .[#toc-optional-sequences] -------------------------------------------------- +Séquences facultatives +---------------------- -Les crochets indiquent les parties facultatives du masque. Toute partie du masque peut être définie comme facultative, y compris celles contenant des paramètres : +Dans le masque, les parties facultatives peuvent être marquées à l'aide de crochets. N'importe quelle partie du masque peut être facultative, elle peut également contenir des paramètres : ```php $router->addRoute('[/]', /* ... */); -// URLs acceptées: Paramètres: -// /en/download lang => en, name => download -// /download lang => null, name => download +// Accepte les chemins : +// /cs/download => lang => cs, name => download +// /download => lang => null, name => download ``` -Bien entendu, lorsqu'un paramètre fait partie d'une séquence optionnelle, il devient également optionnel. S'il n'a pas de valeur par défaut, il sera nul. +Lorsqu'un paramètre fait partie d'une séquence facultative, il devient bien sûr également facultatif. S'il n'a pas de valeur par défaut spécifiée, il sera null. -Les sections optionnelles peuvent également se trouver dans le domaine : +Les parties facultatives peuvent également se trouver dans le domaine : ```php $router->addRoute('//[.]example.com//', /* ... */); ``` -Les séquences peuvent être librement imbriquées et combinées : +Les séquences peuvent être imbriquées et combinées librement : ```php $router->addRoute( @@ -163,49 +162,49 @@ $router->addRoute( 'Home:default', ); -// URLs acceptées: -// /en/hello -// /en-us/hello -// /hello -// /hello/page-12 +// Accepte les chemins : +// /cs/hello +// /en-us/hello +// /hello +// /hello/page-12 ``` -Le générateur d'URL essaie de garder l'URL aussi courte que possible, donc ce qui peut être omis est omis. Ainsi, par exemple, un chemin `index[.html]` génère un chemin `/index`. Vous pouvez inverser ce comportement en écrivant un point d'exclamation après le crochet gauche : +Lors de la génération d'URL, on s'efforce d'obtenir la variante la plus courte, donc tout ce qui peut être omis est omis. C'est pourquoi, par exemple, la route `index[.html]` génère le chemin `/index`. Il est possible d'inverser ce comportement en ajoutant un point d'exclamation après le crochet gauche : ```php -// accepte à la fois /hello et /hello.html, génère /hello +// accepte /hello et /hello.html, génère /hello $router->addRoute('[.html]', /* ... */); -// accepte à la fois /hello et /hello.html, génère /hello.html +// accepte /hello et /hello.html, génère /hello.html $router->addRoute('[!.html]', /* ... */); ``` -Les paramètres optionnels (c'est-à-dire les paramètres ayant une valeur par défaut) sans crochets se comportent comme s'ils étaient enveloppés de cette façon : +Les paramètres facultatifs (c'est-à-dire les paramètres ayant une valeur par défaut) sans crochets se comportent essentiellement comme s'ils étaient encadrés de la manière suivante : ```php $router->addRoute('//', /* ... */); -// équivaut à: +// correspond à ceci : $router->addRoute('[/[/[]]]', /* ... */); ``` -Pour modifier la façon dont la barre oblique la plus à droite est générée, c'est-à-dire qu'au lieu de `/home/`, vous obtenez `/home`, ajustez la route de cette façon : +Si nous voulions influencer le comportement de la barre oblique finale, pour que par exemple `/home` soit généré au lieu de `/home/`, cela peut être réalisé comme suit : ```php $router->addRoute('[[/[/]]]', /* ... */); ``` -Caractères génériques .[#toc-wildcards] ---------------------------------------- +Caractères génériques +--------------------- -Dans le masque de chemin absolu, nous pouvons utiliser les caractères génériques suivants pour éviter, par exemple, de devoir écrire un domaine dans le masque, qui peut être différent dans l'environnement de développement et de production : +Dans le masque d'un chemin absolu, nous pouvons utiliser les caractères génériques suivants et éviter ainsi, par exemple, d'avoir à écrire le domaine dans le masque, qui peut différer entre les environnements de développement et de production : -- `%tld%` = domaine de premier niveau, par exemple `com` ou `org` -- `%sld%` = domaine de deuxième niveau, par ex. `example` +- `%tld%` = domaine de premier niveau, par ex. `com` ou `org` +- `%sld%` = domaine de second niveau, par ex. `example` - `%domain%` = domaine sans sous-domaines, par ex. `example.com` - `%host%` = hôte complet, par ex. `www.example.com` -- `%basePath%` = chemin d'accès au répertoire racine +- `%basePath%` = chemin vers le répertoire racine ```php $router->addRoute('//www.%domain%/%basePath%//', /* ... */); @@ -213,10 +212,10 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ @@ -225,7 +224,7 @@ $router->addRoute('/[/]', [ ]); ``` -Ou nous pouvons utiliser cette forme, remarquez la réécriture de l'expression régulière de validation : +Pour une spécification plus détaillée, une forme encore plus étendue peut être utilisée, où en plus des valeurs par défaut, nous pouvons également définir d'autres propriétés des paramètres, comme par exemple l'expression régulière de validation (voir le paramètre `id`) : ```php use Nette\Routing\Route; @@ -243,19 +242,19 @@ $router->addRoute('/[/]', [ ]); ``` -Ces formats plus bavards sont utiles pour ajouter d'autres métadonnées. +Il est important de noter que si les paramètres définis dans le tableau ne sont pas spécifiés dans le masque du chemin, leurs valeurs ne peuvent pas être modifiées, même à l'aide des paramètres de requête spécifiés après le point d'interrogation dans l'URL. -Filtres et traductions .[#toc-filters-and-translations] -------------------------------------------------------- +Filtres et traductions +---------------------- -C'est une bonne pratique d'écrire le code source en anglais, mais que faire si vous avez besoin que l'URL de votre site Web soit traduite dans une autre langue ? Des routes simples telles que : +Nous écrivons le code source de l'application en anglais, mais si le site web doit avoir des URL en français, alors un routage simple du type : ```php $router->addRoute('/', 'Home:default'); ``` -généreront des URL en anglais, comme `/product/123` ou `/cart`. Si nous voulons que les présentateurs et les actions dans l'URL soient traduits en allemand (par exemple `/produkt/123` ou `/einkaufswagen`), nous pouvons utiliser un dictionnaire de traduction. Pour l'ajouter, nous avons déjà besoin d'une variante "plus bavarde" du deuxième paramètre : +générera des URL en anglais, comme `/product/123` ou `/cart`. Si nous voulons que les presenters et les actions dans l'URL soient représentés par des mots français (par ex. `/produit/123` ou `/panier`), nous pouvons utiliser un dictionnaire de traduction. Pour l'écrire, nous avons besoin de la variante "plus verbeuse" du deuxième paramètre : ```php use Nette\Routing\Route; @@ -264,10 +263,10 @@ $router->addRoute('/', [ 'presenter' => [ Route::Value => 'Home', Route::FilterTable => [ - // string dans l'URL => presenter - 'produkt' => 'Product', - 'einkaufswagen' => 'Cart', - 'katalog' => 'Catalog', + // chaîne dans l'URL => presenter + 'produit' => 'Product', + 'panier' => 'Cart', + 'catalogue' => 'Catalog', ], ], 'action' => [ @@ -279,11 +278,11 @@ $router->addRoute('/', [ ]); ``` -Plusieurs clés de dictionnaire peuvent être utilisées pour le même présentateur. Elles permettront de créer différents alias pour celui-ci. La dernière clé est considérée comme la variante canonique (c'est-à-dire celle qui figurera dans l'URL générée). +Plusieurs clés du dictionnaire de traduction peuvent mener au même presenter. Cela crée différents alias pour lui. La variante canonique (c'est-à-dire celle qui sera dans l'URL générée) est considérée comme la dernière clé. -La table de traduction peut être appliquée à n'importe quel paramètre de cette manière. Cependant, si la traduction n'existe pas, la valeur originale est prise. Nous pouvons modifier ce comportement en ajoutant `Route::FilterStrict => true` et la route rejettera alors l'URL si la valeur ne figure pas dans le dictionnaire. +La table de traduction peut être utilisée de cette manière pour n'importe quel paramètre. Si la traduction n'existe pas, la valeur d'origine est prise. Ce comportement peut être modifié en ajoutant `Route::FilterStrict => true` et la route refusera alors l'URL si la valeur n'est pas dans le dictionnaire. -En plus du dictionnaire de traduction sous forme de tableau, il est possible de définir des fonctions de traduction propres : +En plus du dictionnaire de traduction sous forme de tableau, des fonctions de traduction personnalisées peuvent également être utilisées. ```php use Nette\Routing\Route; @@ -299,15 +298,15 @@ $router->addRoute('//', [ ]); ``` -La fonction `Route::FilterIn` effectue la conversion entre le paramètre dans l'URL et la chaîne de caractères, qui est ensuite transmise au présentateur, la fonction `FilterOut` assure la conversion dans le sens inverse. +La fonction `Route::FilterIn` convertit entre le paramètre dans l'URL et la chaîne qui est ensuite transmise au presenter, la fonction `FilterOut` assure la conversion dans le sens inverse. -Les paramètres `presenter`, `action` et `module` ont déjà des filtres prédéfinis qui convertissent le style PascalCase resp. camelCase en kebab-case utilisé dans l'URL. La valeur par défaut des paramètres est déjà écrite dans la forme transformée, ainsi, par exemple, dans le cas d'un présentateur, on écrit `` au lieu de ``. +Les paramètres `presenter`, `action` et `module` ont déjà des filtres prédéfinis qui convertissent entre le style PascalCase ou camelCase et le style kebab-case utilisé dans les URL. La valeur par défaut des paramètres se écrit déjà sous forme transformée, donc par exemple dans le cas du presenter, nous écrivons ``, et non ``. -Filtres généraux .[#toc-general-filters] ----------------------------------------- +Filtres généraux +---------------- -Outre les filtres pour des paramètres spécifiques, vous pouvez également définir des filtres généraux qui reçoivent un tableau associatif de tous les paramètres qu'ils peuvent modifier de quelque façon que ce soit, puis retourner. Les filtres généraux sont définis sous la clé `null`. +En plus des filtres destinés à des paramètres spécifiques, nous pouvons également définir des filtres généraux qui reçoivent un tableau associatif de tous les paramètres, qu'ils peuvent modifier de n'importe quelle manière, puis les retournent. Nous définissons les filtres généraux sous la clé `null`. ```php use Nette\Routing\Route; @@ -315,62 +314,85 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => 'Home', 'action' => 'default', - null => [ + '' => [ Route::FilterIn => function (array $params): array { /* ... */ }, Route::FilterOut => function (array $params): array { /* ... */ }, ], ]); ``` -Les filtres généraux vous donnent la possibilité d'ajuster le comportement de l'itinéraire de n'importe quelle manière. Nous pouvons les utiliser, par exemple, pour modifier des paramètres en fonction d'autres paramètres. Par exemple, la traduction `` et `` en fonction de la valeur actuelle du paramètre ``. +Les filtres généraux donnent la possibilité de modifier le comportement de la route de manière absolument quelconque. Nous pouvons les utiliser par exemple pour modifier des paramètres en fonction d'autres paramètres. Par exemple, la traduction de `` et `` en fonction de la valeur actuelle du paramètre ``. -Si un paramètre a un filtre personnalisé défini et qu'un filtre général existe en même temps, le filtre personnalisé `FilterIn` est exécuté avant le filtre général et vice versa le filtre général `FilterOut` est exécuté avant le filtre personnalisé. Ainsi, à l'intérieur du filtre général se trouvent les valeurs des paramètres `presenter` resp. `action` écrites en style PascalCase resp. camelCase. +Si un paramètre a un filtre personnalisé défini et qu'un filtre général existe également, le `FilterIn` personnalisé est exécuté avant le général et inversement, le `FilterOut` général est exécuté avant le personnalisé. Ainsi, à l'intérieur du filtre général, les valeurs des paramètres `presenter` ou `action` sont écrites dans le style PascalCase ou camelCase. -Drapeau unidirectionnel .[#toc-oneway-flag] -------------------------------------------- +Routes unidirectionnelles OneWay +-------------------------------- -Les routes à sens unique sont utilisées pour préserver la fonctionnalité des anciennes URL que l'application ne génère plus mais accepte toujours. Nous les signalons avec `OneWay`: +Les routes unidirectionnelles sont utilisées pour maintenir la fonctionnalité des anciennes URL que l'application ne génère plus, mais accepte toujours. Nous les marquons avec l'indicateur `OneWay` : ```php // ancienne URL /product-info?id=123 $router->addRoute('product-info', 'Product:detail', $router::ONE_WAY); -// nouvelle URL /produit/123 +// nouvelle URL /product/123 $router->addRoute('product/', 'Product:detail'); ``` -Lors de l'accès à l'ancienne URL, le diffuseur redirige automatiquement vers la nouvelle URL afin que les moteurs de recherche n'indexent pas ces pages deux fois (voir [SEO et canonisation |#SEO and canonization]). +Lors de l'accès à l'ancienne URL, le presenter redirige automatiquement vers la nouvelle URL, de sorte que les moteurs de recherche n'indexeront pas ces pages deux fois (voir [#SEO et canonisation]). + + +Routage dynamique avec callbacks +-------------------------------- + +Le routage dynamique avec callbacks vous permet d'assigner directement des fonctions (callbacks) aux routes, qui seront exécutées lorsque le chemin donné sera visité. Cette fonctionnalité flexible vous permet de créer rapidement et efficacement différents points de terminaison (endpoints) pour votre application : + +```php +$router->addRoute('test', function () { + echo 'vous êtes à l\'adresse /test'; +}); +``` + +Vous pouvez également définir des paramètres dans le masque, qui seront automatiquement transmis à votre callback : + +```php +$router->addRoute('', function (string $lang) { + echo match ($lang) { + 'fr' => 'Bienvenue sur la version française de notre site !', + 'en' => 'Welcome to the English version of our website!', + }; +}); +``` -Modules .[#toc-modules] ------------------------ +Modules +------- -Si nous avons plusieurs routes qui appartiennent à un seul [module |modules], nous pouvons utiliser `withModule()` pour les regrouper : +Si nous avons plusieurs routes qui appartiennent à un [module |directory-structure#Presenters et templates] commun, nous utilisons `withModule()` : ```php $router = new RouteList; -$router->withModule('Forum') // les routeurs suivants font partie du module Forum - ->addRoute('rss', 'Feed:rss') // le présentateur est Forum:Feed +$router->withModule('Forum') // les routes suivantes font partie du module Forum + ->addRoute('rss', 'Feed:rss') // le presenter sera Forum:Feed ->addRoute('/') - ->withModule('Admin') // les routeurs suivants font partie du module Forum:Admin + ->withModule('Admin') // les routes suivantes font partie du module Forum:Admin ->addRoute('sign:in', 'Sign:in'); ``` -Une alternative est d'utiliser le paramètre `module`: +Une alternative est d'utiliser le paramètre `module` : ```php -// L'URL manage/dashboard/default correspond au présentateur Admin:Dashboard +// L'URL manage/dashboard/default est mappée sur le presenter Admin:Dashboard $router->addRoute('manage//', [ 'module' => 'Admin', ]); ``` -Sous-domaines .[#toc-subdomains] --------------------------------- +Sous-domaines +------------- -Les collections d'itinéraires peuvent être regroupées par sous-domaines : +Nous pouvons diviser les collections de routes par sous-domaines : ```php $router = new RouteList; @@ -379,7 +401,7 @@ $router->withDomain('example.com') ->addRoute('/'); ``` -Vous pouvez également utiliser des [caractères génériques |#wildcards] dans votre nom de domaine : +Dans le nom de domaine, on peut également utiliser des [#Caractères génériques] : ```php $router = new RouteList; @@ -388,23 +410,23 @@ $router->withDomain('example.%tld%') ``` -Préfixe de chemin .[#toc-path-prefix] -------------------------------------- +Préfixe de chemin +----------------- -Les collections de routes peuvent être regroupées par chemin d'accès dans l'URL : +Nous pouvons diviser les collections de routes par chemin dans l'URL : ```php $router = new RouteList; $router->withPath('eshop') - ->addRoute('rss', 'Feed:rss') // correspond à l'URL /eshop/rss - ->addRoute('/'); // correspond à l'URL /shop//. + ->addRoute('rss', 'Feed:rss') // attrape l'URL /eshop/rss + ->addRoute('/'); // attrape l'URL /eshop// ``` -Combinaisons .[#toc-combinations] ---------------------------------- +Combinaisons +------------ -Les utilisations ci-dessus peuvent être combinées : +Nous pouvons combiner les divisions ci-dessus : ```php $router = (new RouteList) @@ -424,40 +446,40 @@ $router = (new RouteList) ``` -Paramètres de la requête .[#toc-query-parameters] -------------------------------------------------- +Paramètres de requête +--------------------- -Les masques peuvent également contenir des paramètres de requête (paramètres situés après le point d'interrogation dans l'URL). Ils ne peuvent pas définir une expression de validation, mais ils peuvent modifier le nom sous lequel ils sont transmis au diffuseur : +Les masques peuvent également contenir des paramètres de requête (paramètres après le point d'interrogation dans l'URL). On ne peut pas leur définir d'expression de validation, mais on peut changer le nom sous lequel ils sont transmis au presenter : ```php -// utiliser le paramètre de requête 'cat' comme 'categoryId' dans l'application +// le paramètre de requête 'cat' que nous voulons utiliser dans l'application sous le nom 'categoryId' $router->addRoute('product ? id= & cat=', /* ... */); ``` -Paramètres Foo .[#toc-foo-parameters] -------------------------------------- +Paramètres Foo +-------------- -Nous allons maintenant plus loin. Les paramètres Foo sont essentiellement des paramètres sans nom qui permettent de faire correspondre une expression régulière. La route suivante correspond à `/index`, `/index.html`, `/index.htm` et `/index.php`: +Maintenant, nous allons plus en profondeur. Les paramètres Foo sont essentiellement des paramètres sans nom qui permettent de faire correspondre une expression régulière. Un exemple est une route acceptant `/index`, `/index.html`, `/index.htm` et `/index.php` : ```php $router->addRoute('index', /* ... */); ``` -Il est également possible de définir explicitement une chaîne qui sera utilisée pour la génération d'URL. La chaîne doit être placée directement après le point d'interrogation. La route suivante est similaire à la précédente, mais génère `/index.html` au lieu de `/index` car la chaîne `.html` est définie comme une "valeur générée". +Il est également possible de définir explicitement la chaîne qui sera utilisée lors de la génération de l'URL. La chaîne doit être placée directement après le point d'interrogation. La route suivante est similaire à la précédente, mais génère `/index.html` au lieu de `/index`, car la chaîne `.html` est définie comme valeur de génération : ```php $router->addRoute('index', /* ... */); ``` -Intégration .[#toc-integration] -=============================== +Intégration dans l'application +============================== -Afin de connecter notre routeur à l'application, nous devons en informer le conteneur DI. Le moyen le plus simple est de préparer la fabrique qui construira l'objet routeur et de dire à la configuration du conteneur de l'utiliser. Disons que nous écrivons une méthode dans ce but `App\Router\RouterFactory::createRouter()`: +Pour intégrer le routeur créé dans l'application, nous devons en informer le conteneur DI. Le moyen le plus simple est de préparer une factory qui fabriquera l'objet routeur et d'indiquer dans la configuration du conteneur qu'elle doit être utilisée. Supposons que nous écrivions à cet effet la méthode `App\Core\RouterFactory::createRouter()` : ```php -namespace App\Router; +namespace App\Core; use Nette\Application\Routers\RouteList; @@ -472,14 +494,14 @@ class RouterFactory } ``` -Puis nous écrivons dans la [configuration |dependency-injection:services]: +Dans la [configuration |dependency-injection:services], nous écrirons alors : ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter ``` -Toutes les dépendances, telles qu'une connexion à une base de données, etc., sont transmises à la méthode factory en tant que paramètres en utilisant le [câblage automatique |dependency-injection:autowiring]: +Toutes les dépendances, par exemple envers la base de données, etc., sont transmises à la méthode factory comme ses paramètres via [l'autowiring|dependency-injection:autowiring] : ```php public static function createRouter(Nette\Database\Connection $db): RouteList @@ -489,10 +511,10 @@ public static function createRouter(Nette\Database\Connection $db): RouteList ``` -SimpleRouter .[#toc-simplerouter] -================================= +SimpleRouter +============ -Un routeur beaucoup plus simple que la collection de routes est [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Il peut être utilisé lorsqu'il n'y a pas besoin d'un format d'URL spécifique, lorsque `mod_rewrite` (ou des alternatives) n'est pas disponible ou lorsque nous ne voulons tout simplement pas encore nous embêter avec des URL conviviales. +Un routeur beaucoup plus simple que la collection de routes est [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Nous l'utilisons lorsque nous n'avons pas d'exigences particulières sur la forme de l'URL, si `mod_rewrite` (ou ses alternatives) n'est pas disponible ou si nous ne voulons pas encore nous préoccuper des URL conviviales. Il génère des adresses à peu près sous cette forme : @@ -500,14 +522,14 @@ Il génère des adresses à peu près sous cette forme : http://example.com/?presenter=Product&action=detail&id=123 ``` -Le paramètre du constructeur `SimpleRouter` est un présentateur et une action par défaut, c'est-à-dire une action à exécuter si nous ouvrons par exemple `http://example.com/` sans paramètres supplémentaires. +Le paramètre du constructeur de SimpleRouter est le presenter & action par défaut vers lequel il faut diriger si nous ouvrons la page sans paramètres, par ex. `http://example.com/`. ```php -// par défaut, le présentateur est 'Home' et l'action 'default'. +// le presenter par défaut sera 'Home' et l'action 'default' $router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` -Nous recommandons de définir SimpleRouter directement dans la [configuration |dependency-injection:services]: +Nous recommandons de définir SimpleRouter directement dans la [configuration |dependency-injection:services] : ```neon services: @@ -515,22 +537,22 @@ services: ``` -Référencement et Canonisation .[#toc-seo-and-canonization] -========================================================== +SEO et canonisation +=================== -Le cadre améliore le référencement (optimisation pour les moteurs de recherche) en empêchant la duplication du contenu à différentes URL. Si plusieurs adresses renvoient à une même destination, par exemple `/index` et `/index.html`, le framework détermine la première comme primaire (canonique) et redirige les autres vers elle en utilisant le code HTTP 301. Grâce à cela, les moteurs de recherche n'indexent pas les pages deux fois et ne cassent pas leur page rank. . +Le framework contribue au SEO (optimisation pour les moteurs de recherche) en empêchant la duplication de contenu sur différentes URL. Si plusieurs adresses mènent à une certaine cible, par ex. `/index` et `/index.html`, le framework détermine la première comme étant la principale (canonique) et redirige les autres vers elle à l'aide du code HTTP 301. Grâce à cela, les moteurs de recherche n'indexeront pas vos pages deux fois et ne dilueront pas leur page rank. -Ce processus est appelé canonisation. L'URL canonique est celle générée par le routeur, c'est-à-dire par la première route correspondante dans la [collection |#route-collection] sans le drapeau OneWay. Par conséquent, dans la collection, nous listons d'abord les routes primaires. +Ce processus est appelé canonisation. L'URL canonique est celle générée par le routeur, c'est-à-dire la première route correspondante dans la collection sans l'indicateur OneWay. C'est pourquoi dans la collection, nous listons les **routes primaires en premier**. -La canonisation est effectuée par le présentateur, plus dans le chapitre [canonisation |presenters#Canonization]. +La canonisation est effectuée par le presenter, plus d'informations dans le chapitre [canonisation |presenters#Canonisation]. -HTTPS .[#toc-https] -=================== +HTTPS +===== -Afin d'utiliser le protocole HTTPS, il est nécessaire de l'activer sur l'hébergement et de configurer le serveur. +Pour pouvoir utiliser le protocole HTTPS, il est nécessaire de l'activer sur l'hébergement et de configurer correctement le serveur. -La redirection de tout le site vers HTTPS doit être effectuée au niveau du serveur, par exemple à l'aide du fichier .htaccess dans le répertoire racine de notre application, avec le code HTTP 301. Les paramètres peuvent différer selon l'hébergement et ressemblent à quelque chose comme ceci : +La redirection de l'ensemble du site vers HTTPS doit être configurée au niveau du serveur, par exemple à l'aide du fichier .htaccess dans le répertoire racine de notre application, et ce avec le code HTTP 301. La configuration peut varier en fonction de l'hébergement et ressemble à peu près à ceci : ``` @@ -542,40 +564,40 @@ La redirection de tout le site vers HTTPS doit être effectuée au niveau du ser ``` -Le routeur génère une URL avec le même protocole que la page a été chargée, il n'est donc pas nécessaire de définir autre chose. +Le routeur génère des URL avec le même protocole que celui avec lequel la page a été chargée, il n'y a donc rien de plus à configurer. -Cependant, si nous avons exceptionnellement besoin que différentes routes fonctionnent sous différents protocoles, nous le mettrons dans le masque de route : +Cependant, si exceptionnellement nous avons besoin que différentes routes fonctionnent sous différents protocoles, nous l'indiquons dans le masque de la route : ```php -// Générera une adresse HTTP +// Générera une adresse avec HTTP $router->addRoute('http://%host%//', /* ... */); -// Générera une adresse HTTPS +// Générera une adresse avec HTTPS $router->addRoute('https://%host%//', /* ... */); ``` -Routeur de débogage .[#toc-debugging-router] -============================================ +Débogage du routeur +=================== -La barre de routage affichée dans [Tracy Bar |tracy:] est un outil utile qui affiche une liste de routes et également les paramètres que le routeur a obtenus de l'URL. +Le panneau de routage affiché dans la [barre Tracy |tracy:] est un outil utile qui affiche la liste des routes ainsi que les paramètres que le routeur a obtenus de l'URL. -La barre verte avec le symbole ✓ représente la route qui correspond à l'URL actuelle, les barres bleues avec les symboles ≈ indiquent les routes qui correspondraient également à l'URL si le vert ne les dépassait pas. Nous voyons le présentateur actuel & l'action plus loin. +La barre verte avec le symbole ✓ représente la route qui a traité l'URL actuelle, la couleur bleue et le symbole ≈ indiquent les routes qui auraient également traité l'URL si la verte ne les avait pas devancées. Ensuite, nous voyons le presenter & action actuel. [* routing-debugger.webp *] -En même temps, s'il y a une redirection inattendue due à la [canonicalisation |#SEO and Canonization], il est utile de regarder dans la barre *redirect* pour voir comment le routeur a compris l'URL à l'origine et pourquoi il a redirigé. +En même temps, si une redirection inattendue se produit en raison de la [canonisation |#SEO et canonisation], il est utile de regarder le panneau dans la barre *redirect*, où vous découvrirez comment le routeur a initialement compris l'URL et pourquoi il a redirigé. .[note] -Lors du débogage du routeur, il est recommandé d'ouvrir les Outils du développeur dans le navigateur (Ctrl+Shift+I ou Cmd+Option+I) et de désactiver le cache dans le panneau Réseau afin que les redirections n'y soient pas stockées. +Lors du débogage du routeur, nous recommandons d'ouvrir les Outils de développement dans le navigateur (Ctrl+Shift+I ou Cmd+Option+I) et de désactiver le cache dans le panneau Réseau, afin que les redirections n'y soient pas enregistrées. -Performances .[#toc-performance] -================================ +Performance +=========== -Le nombre de routes affecte la vitesse du routeur. Leur nombre ne devrait certainement pas dépasser quelques dizaines. Si votre site a une structure d'URL trop compliquée, vous pouvez écrire un [routeur personnalisé |#custom router]. +Le nombre de routes a une influence sur la vitesse du routeur. Leur nombre ne devrait certainement pas dépasser quelques dizaines. Si votre site a une structure d'URL trop compliquée, vous pouvez écrire un [#Routeur personnalisé] sur mesure. -Si le routeur n'a pas de dépendances, par exemple sur une base de données, et que sa fabrique n'a pas d'arguments, nous pouvons sérialiser sa forme compilée directement dans un conteneur DI et ainsi rendre l'application légèrement plus rapide. +Si le routeur n'a pas de dépendances, par exemple envers la base de données, et que sa factory n'accepte aucun argument, nous pouvons sérialiser sa forme compilée directement dans le conteneur DI et ainsi accélérer légèrement l'application. ```neon routing: @@ -583,10 +605,10 @@ routing: ``` -Routeur personnalisé .[#toc-custom-router] -========================================== +Routeur personnalisé +==================== -Les lignes suivantes sont destinées aux utilisateurs très avancés. Vous pouvez créer votre propre routeur et l'ajouter naturellement à votre collection de routes. Le routeur est une implémentation de l'interface [api:Nette\Routing\Router] avec deux méthodes : +Les lignes suivantes sont destinées aux utilisateurs très avancés. Vous pouvez créer votre propre routeur et l'intégrer tout naturellement dans la collection de routes. Le routeur est une implémentation de l'interface [api:Nette\Routing\Router] se dvěma metodami: ```php use Nette\Http\IRequest as HttpRequest; @@ -606,8 +628,7 @@ class MyRouter implements Nette\Routing\Router } ``` -La méthode `match` traite la [requête |http:request] courante [$httpRequest |http:request], à partir de laquelle non seulement l'URL, mais aussi les en-têtes etc. peuvent être récupérés, dans un tableau contenant le nom du diffuseur et ses paramètres. Si elle ne peut pas traiter la requête, elle renvoie null. -Lors du traitement de la demande, nous devons retourner au moins le diffuseur et l'action. Le nom du diffuseur est complet et inclut les modules éventuels : +La méthode `match` traite la requête actuelle [$httpRequest |http:request], à partir de laquelle on peut obtenir non seulement l'URL, mais aussi les en-têtes, etc., en un tableau contenant le nom du presenter et ses paramètres. Si elle ne peut pas traiter la requête, elle retourne null. Lors du traitement de la requête, nous devons retourner au minimum le presenter et l'action. Le nom du presenter est complet et contient également d'éventuels modules : ```php [ @@ -616,9 +637,9 @@ Lors du traitement de la demande, nous devons retourner au moins le diffuseur et ] ``` -La méthode `constructUrl`, quant à elle, génère une URL absolue à partir du tableau de paramètres. Elle peut utiliser les informations du paramètre `$refUrl`, qui est l'URL actuelle. +La méthode `constructUrl` assemble au contraire l'URL absolue résultante à partir du tableau de paramètres. Pour cela, elle peut utiliser les informations du paramètre [`$refUrl`|api:Nette\Http\UrlScript], qui est l'URL actuelle. -Pour ajouter un routeur personnalisé à la collection de routes, utilisez `add()`: +Vous l'ajoutez à la collection de routes à l'aide de `add()` : ```php $router = new Nette\Application\Routers\RouteList; @@ -628,19 +649,19 @@ $router->addRoute(/* ... */); ``` -Utilisation séparée .[#toc-separated-usage] -=========================================== +Utilisation autonome +==================== -Par utilisation séparée, nous entendons l'utilisation des capacités du routeur dans une application qui n'utilise pas Nette Application et les présentateurs. Presque tout ce que nous avons montré dans ce chapitre s'y applique, avec les différences suivantes : +Par utilisation autonome, nous entendons l'utilisation des capacités du routeur dans une application qui n'utilise pas Nette Application et les presenters. Presque tout ce que nous avons montré dans ce chapitre s'applique, avec ces différences : - pour les collections de routes, nous utilisons la classe [api:Nette\Routing\RouteList] -- comme une simple classe de routeur [api:Nette\Routing\SimpleRouter] -- parce qu'il n'y a pas de paire `Presenter:action`, nous utilisons la [notation Advanced |#Advanced notation] +- comme routeur simple, la classe [api:Nette\Routing\SimpleRouter] +- comme il n'existe pas de paire `Presenter:action`, nous utilisons la [#Notation étendue] -Donc encore une fois nous allons créer une méthode qui va construire un routeur, par exemple : +Donc, nous créons à nouveau une méthode qui nous assemblera le routeur, par ex. : ```php -namespace App\Router; +namespace App\Core; use Nette\Routing\RouteList; @@ -661,35 +682,35 @@ class RouterFactory } ``` -Si vous utilisez un conteneur DI, ce que nous recommandons, ajoutez à nouveau la méthode à la configuration et récupérez le routeur avec la requête HTTP du conteneur : +Si vous utilisez un conteneur DI, ce que nous recommandons, nous ajoutons à nouveau la méthode à la configuration et obtenons ensuite le routeur avec la requête HTTP du conteneur : ```php $router = $container->getByType(Nette\Routing\Router::class); $httpRequest = $container->getByType(Nette\Http\IRequest::class); ``` -Ou bien nous créerons directement des objets : +Ou nous créons directement les objets : ```php -$router = App\Router\RouterFactory::createRouter(); +$router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` -Il faut maintenant laisser le routeur fonctionner : +Maintenant, il ne reste plus qu'à laisser le routeur travailler : ```php $params = $router->match($httpRequest); if ($params === null) { - // aucune route correspondante trouvée, nous enverrons une erreur 404 + // aucune route correspondante n'a été trouvée, nous envoyons une erreur 404 exit; } -// nous traitons les paramètres reçus +// nous traitons les paramètres obtenus $controller = $params['controller']; // ... ``` -Et vice versa, nous allons utiliser le routeur pour créer le lien : +Et inversement, nous utilisons le routeur pour assembler un lien : ```php $params = ['controller' => 'ArticleController', 'id' => 123]; diff --git a/application/fr/templates.texy b/application/fr/templates.texy index 83ac0a9d46..7216f421d8 100644 --- a/application/fr/templates.texy +++ b/application/fr/templates.texy @@ -1,16 +1,16 @@ -Modèles -******* +Templates +********* .[perex] -Nette utilise le système de modèles [Latte |latte:]. Latte est utilisé parce que c'est le système de modèles le plus sûr pour PHP, et en même temps le système le plus intuitif. Vous n'avez pas besoin d'apprendre grand chose de nouveau, il vous suffit de connaître PHP et quelques balises Latte. +Nette utilise le système de templates [Latte |latte:]. D'une part parce que c'est le système de templates le plus sécurisé pour PHP, et d'autre part parce que c'est aussi le système le plus intuitif. Vous n'avez pas besoin d'apprendre beaucoup de nouveautés, la connaissance de PHP et de quelques balises suffit. -Il est courant que la page soit complétée à partir du modèle de mise en page + le modèle d'action. Voici à quoi peut ressembler un modèle de mise en page, remarquez les blocs `{block}` et la balise `{include}`: +Il est courant qu'une page soit composée d'un template de layout + du template de l'action donnée. Voici à quoi peut ressembler un template de layout, remarquez les blocs `{block}` et la balise `{include}` : ```latte - {block title}My App{/block} + {block title}Mon App{/block}
    ...
    @@ -20,61 +20,109 @@ Il est courant que la page soit complétée à partir du modèle de mise en page ``` -Et ceci pourrait être le modèle d'action : +Et voici ce que sera le template de l'action : ```latte -{block title}Homepage{/block} +{block title}Page d'accueil{/block} {block content} -

    Homepage

    +

    Page d'accueil

    ... {/block} ``` -Il définit le bloc `content`, qui est inséré à la place de `{include content}` dans la mise en page, et redéfinit également le bloc `title`, qui écrase `{block title}` dans la mise en page. Essayez d'imaginer le résultat. +Il définit le bloc `content`, qui sera inséré à la place de `{include content}` dans le layout, et redéfinit également le bloc `title`, qui écrasera `{block title}` dans le layout. Essayez d'imaginer le résultat. -Recherche de modèles .[#toc-search-for-templates] -------------------------------------------------- +Recherche de templates +---------------------- -Le chemin vers les modèles est déduit selon une logique simple. Il essaie de voir si l'un de ces fichiers modèles existe par rapport au répertoire où se trouve la classe du présentateur, où `` est le nom du présentateur actuel et `` est le nom de l'action en cours : +Vous n'avez pas besoin de spécifier dans les presenters quel template doit être rendu, le framework déduit le chemin lui-même et vous évite d'écrire. -- `templates//.latte` -- `templates/..latte` +Si vous utilisez une structure de répertoires où chaque presenter a son propre répertoire, placez simplement le template dans ce répertoire sous le nom de l'action (ou de la vue), c'est-à-dire pour l'action `default`, utilisez le template `default.latte` : -S'il ne trouve pas le modèle, la réponse est une [erreur 404 |presenters#Error 404 etc.]. +/--pre +app/ +└── Presentation/ + └── Home/ + ├── HomePresenter.php + └── default.latte +\-- -Vous pouvez également changer la vue en utilisant `$this->setView('otherView')`. Ou, au lieu de chercher, spécifiez directement le nom du fichier de modèle en utilisant `$this->template->setFile('/path/to/template.latte')`. +Si vous utilisez une structure où les presenters sont regroupés dans un seul répertoire et les templates dans un dossier `templates`, enregistrez-le soit dans le fichier `..latte` soit `/.latte` : + +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── Home.default.latte ← 1ère variante + └── Home/ + └── default.latte ← 2ème variante +\-- + +Le répertoire `templates` peut également être placé un niveau plus haut, c'est-à-dire au même niveau que le répertoire contenant les classes des presenters. + +Si le template n'est pas trouvé, le presenter répondra par une [erreur 404 - page non trouvée |presenters#Erreur 404 et autres]. + +Vous pouvez changer la vue en utilisant `$this->setView('autreVue')`. Il est également possible de spécifier directement le fichier de template en utilisant `$this->template->setFile('/chemin/vers/template.latte')`. .[note] -Vous pouvez modifier les chemins dans lesquels les modèles sont recherchés en remplaçant la méthode [formatTemplateFiles |api:Nette\Application\UI\Presenter::formatTemplateFiles()], qui renvoie un tableau de chemins de fichiers possibles. +Les fichiers où les templates sont recherchés peuvent être modifiés en surchargeant la méthode [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], qui retourne un tableau de noms de fichiers possibles. + + +Recherche du template de layout +------------------------------- + +Nette recherche également automatiquement le fichier de layout. + +Si vous utilisez une structure de répertoires où chaque presenter a son propre répertoire, placez le layout soit dans le dossier du presenter s'il lui est spécifique, soit un niveau plus haut s'il est commun à plusieurs presenters : + +/--pre +app/ +└── Presentation/ + ├── @layout.latte ← layout commun + └── Home/ + ├── @layout.latte ← uniquement pour le presenter Home + ├── HomePresenter.php + └── default.latte +\-- + +Si vous utilisez une structure où les presenters sont regroupés dans un seul répertoire et les templates dans un dossier `templates`, le layout sera attendu à ces endroits : -Le modèle est attendu dans les fichiers suivants : +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── @layout.latte ← layout commun + ├── Home.@layout.latte ← uniquement pour Home, 1ère variante + └── Home/ + └── @layout.latte ← uniquement pour Home, 2ème variante +\-- -- `templates//@.latte` -- `templates/.@.latte` -- `templates/@.latte` mise en page commune à plusieurs présentateurs +Si le presenter se trouve dans un module, la recherche s'effectuera également aux niveaux de répertoires supérieurs, en fonction de l'imbrication du module. -`` est le nom du présentateur actuel et `` est le nom de la mise en page, qui est par défaut `'layout'`. Le nom peut être modifié avec `$this->setLayout('otherLayout')`, de sorte que les fichiers `@otherLayout.latte` seront essayés. +Le nom du layout peut être modifié à l'aide de `$this->setLayout('layoutAdmin')` et il sera alors attendu dans le fichier `@layoutAdmin.latte`. Il est également possible de spécifier directement le fichier de template de layout à l'aide de `$this->setLayout('/chemin/vers/template.latte')`. -Vous pouvez également spécifier directement le nom du fichier du modèle de présentation en utilisant `$this->setLayout('/path/to/template.latte')`. L'utilisation de `$this->setLayout(false)` désactivera la recherche de la mise en page. +En utilisant `$this->setLayout(false)` ou la balise `{layout none}` à l'intérieur du template, la recherche de layout est désactivée. .[note] -Vous pouvez modifier les chemins dans lesquels les modèles sont recherchés en remplaçant la méthode [formatLayoutTemplateFiles |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], qui renvoie un tableau de chemins de fichiers possibles. +Les fichiers où les templates de layout sont recherchés peuvent être modifiés en surchargeant la méthode [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], qui retourne un tableau de noms de fichiers possibles. -Variables dans le modèle .[#toc-variables-in-the-template] ----------------------------------------------------------- +Variables dans le template +-------------------------- -Les variables sont transmises au modèle en les écrivant à `$this->template`. Elles sont ensuite disponibles dans le modèle en tant que variables locales : +Nous passons des variables au template en les écrivant dans `$this->template` et elles sont ensuite disponibles dans le template comme variables locales : ```php $this->template->article = $this->articles->getById($id); ``` -De cette façon, nous pouvons facilement passer n'importe quelle variable aux modèles. Cependant, lors du développement d'applications robustes, il est souvent plus utile de se limiter. Par exemple, en définissant explicitement une liste de variables que le modèle attend et leurs types. Cela permettra à PHP de vérifier le type, à l'IDE d'autocompléter correctement et à l'analyse statique de détecter les erreurs. +Nous pouvons ainsi passer facilement n'importe quelle variable aux templates. Cependant, lors du développement d'applications robustes, il est plus utile de se limiter. Par exemple, en définissant explicitement la liste des variables que le template attend et leurs types. Grâce à cela, PHP pourra vérifier les types, l'IDE pourra suggérer correctement et l'analyse statique pourra détecter les erreurs. -Et comment définir une telle énumération ? Tout simplement sous la forme d'une classe et de ses propriétés. Nous la nommons de façon similaire à presenter, mais avec `Template` à la fin : +Et comment définir une telle liste ? Simplement sous la forme d'une classe et de ses propriétés. Nous la nommerons de manière similaire au presenter, mais avec `Template` à la fin : ```php /** @@ -89,26 +137,26 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template public Model\Article $article; public Nette\Security\User $user; - // et autres variables + // et d'autres variables } ``` -L'objet `$this->template` dans le présentateur sera maintenant une instance de la classe `ArticleTemplate`. Ainsi, PHP vérifie les types déclarés lorsqu'ils sont écrits. Et à partir de PHP 8.2, il préviendra également en cas d'écriture dans une variable inexistante. Dans les versions précédentes, la même chose peut être réalisée en utilisant le trait [Nette\SmartObject |utils:smartobject]. +L'objet `$this->template` dans le presenter sera désormais une instance de la classe `ArticleTemplate`. Ainsi, PHP vérifiera les types déclarés lors de l'écriture. Et à partir de PHP 8.2, il avertira également en cas d'écriture dans une variable inexistante ; dans les versions précédentes, le même résultat peut être obtenu en utilisant le trait [Nette\SmartObject |utils:smartobject]. -L'annotation `@property-read` est pour les IDE et l'analyse statique, elle fera fonctionner la complétion automatique, voir "PhpStorm et la complétion de code pour $this->template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. +L'annotation `@property-read` est destinée à l'IDE et à l'analyse statique, grâce à elle, l'autocomplétion fonctionnera, voir [PhpStorm et l'autocomplétion pour $this⁠-⁠>⁠template|https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template]. [* phpstorm-completion.webp *] -Vous pouvez aussi vous offrir le luxe de chuchoter dans les templates, il suffit d'installer le plugin Latte dans PhpStorm et de spécifier le nom de la classe au début du template, voir l'article "Latte : how to type system":https://blog.nette.org/fr/latte-comment-utiliser-le-systeme-de-type: +Vous pouvez également profiter du luxe de l'autocomplétion dans les templates, il suffit d'installer le plugin pour Latte dans PhpStorm et d'indiquer le nom de la classe au début du template, plus d'informations dans l'article [Latte : comment gérer le système de types|https://blog.nette.org/fr/latte-how-to-use-type-system] : ```latte -{templateType App\Presenters\ArticleTemplate} +{templateType App\Presentation\Article\ArticleTemplate} ... ``` -C'est également de cette façon que les modèles fonctionnent dans les composants, il suffit de suivre la convention de nommage et de créer une classe de modèle `FifteenTemplate` pour le composant, par exemple `FifteenControl`. +C'est ainsi que fonctionnent également les templates dans les composants, il suffit de respecter la convention de nommage et pour un composant par ex. `FifteenControl` créer une classe de template `FifteenTemplate`. -Si vous devez créer un `$template` en tant qu'instance d'une autre classe, utilisez la méthode `createTemplate()`: +Si vous avez besoin de créer `$template` comme instance d'une autre classe, utilisez la méthode `createTemplate()` : ```php public function renderDefault(): void @@ -121,43 +169,43 @@ public function renderDefault(): void ``` -Variables par défaut .[#toc-default-variables] ----------------------------------------------- +Variables par défaut +-------------------- -Les présentateurs et les composants transmettent automatiquement plusieurs variables utiles aux modèles : +Les presenters et les composants transmettent automatiquement plusieurs variables utiles aux templates : -- `$basePath` est un chemin URL absolu vers le répertoire racine (par exemple `/CD-collection`) -- `$baseUrl` est une URL absolue vers le répertoire racine (par exemple `http://localhost/CD-collection`) -- `$user` est un objet [représentant l'utilisateur |security:authentication] -- `$presenter` est le présentateur actuel -- `$control` est le composant ou le présentateur actuel -- `$flashes` liste des [messages |presenters#flash-messages] envoyés par la méthode `flashMessage()` +- `$basePath` est le chemin URL absolu vers le répertoire racine (par ex. `/eshop`) +- `$baseUrl` est l'URL absolue vers le répertoire racine (par ex. `http://localhost/eshop`) +- `$user` est l'objet [représentant l'utilisateur |security:authentication] +- `$presenter` est le presenter actuel +- `$control` est le composant ou presenter actuel +- `$flashes` tableau des [messages |presenters#Messages Flash] envoyés par la fonction `flashMessage()` -Si vous utilisez une classe de modèle personnalisée, ces variables sont transmises si vous créez une propriété pour elles. +Si vous utilisez votre propre classe de template, ces variables seront transmises si vous créez une propriété pour elles. -Création de liens .[#toc-creating-links] ----------------------------------------- +Création de liens +----------------- -Dans le modèle, nous créons des liens vers d'autres présentateurs et actions comme suit : +Dans le template, les liens vers d'autres presenters & actions sont créés de cette manière : ```latte -detail +détail du produit ``` -L'attribut `n:href` est très pratique pour les balises HTML ``. Si nous voulons imprimer le lien ailleurs, par exemple dans le texte, nous utilisons `{link}`: +L'attribut `n:href` est très pratique pour les balises HTML ``. Si nous voulons afficher le lien ailleurs, par exemple dans du texte, nous utilisons `{link}` : ```latte -URL is: {link Home:default} +L'adresse est : {link Home:default} ``` -Pour plus d'informations, voir [Création de liens |Creating Links]. +Plus d'informations peuvent être trouvées dans le chapitre [Création de liens URL|creating-links]. -Filtres, balises, etc. personnalisés .[#toc-custom-filters-tags-etc] --------------------------------------------------------------------- +Filtres personnalisés, balises, etc. +------------------------------------ -Le système de modélisation Latte peut être étendu avec des filtres, des fonctions, des balises, etc. personnalisés. Ceci peut être fait directement dans la méthode `render` ou `beforeRender()`: +Le système de templates Latte peut être étendu avec des filtres, fonctions, balises personnalisés, etc. Cela peut être fait directement dans la méthode `render` ou `beforeRender()` : ```php public function beforeRender(): void @@ -165,16 +213,16 @@ public function beforeRender(): void // ajout d'un filtre $this->template->addFilter('foo', /* ... */); - // ou configurer directement l'objet Latte\Engine + // ou nous configurons directement l'objet Latte\Engine $latte = $this->template->getLatte(); $latte->addFilterLoader(/* ... */); } ``` -Latte version 3 propose un moyen plus avancé en créant une [extension |latte:creating-extension] pour chaque projet web. Voici un exemple approximatif d'une telle classe : +Latte version 3 offre une méthode plus avancée, à savoir la création d'une [extension |latte:extending-latte#Latte Extension] pour chaque projet web. Exemple succinct d'une telle classe : ```php -namespace App\Templating; +namespace App\Presentation\Accessory; final class LatteExtension extends Latte\Extension { @@ -207,22 +255,21 @@ final class LatteExtension extends Latte\Extension } ``` -Nous l'enregistrons en utilisant la [configuration |configuration#Latte]: +Nous l'enregistrons à l'aide de la [configuration |configuration#Templates Latte] : ```neon latte: extensions: - - App\Templating\LatteExtension + - App\Presentation\Accessory\LatteExtension ``` -Traduction de .[#toc-translating] ---------------------------------- +Traduction +---------- -Si vous programmez une application multilingue, vous aurez probablement besoin d'éditer une partie du texte du modèle dans différentes langues. Pour ce faire, le Nette Framework définit une interface de traduction [api:Nette\Localization\Translator], qui possède une seule méthode `translate()`. Celle-ci accepte le message `$message`, qui est généralement une chaîne de caractères, et tout autre paramètre. La tâche consiste à renvoyer la chaîne traduite. -Il n'y a pas d'implémentation par défaut dans Nette, vous pouvez choisir en fonction de vos besoins parmi plusieurs solutions prêtes à l'emploi que vous trouverez sur [Componette |https://componette.org/search/localization]. Leur documentation vous indique comment configurer le traducteur. +Si vous programmez une application multilingue, vous aurez probablement besoin d'afficher certains textes dans le template dans différentes langues. Nette Framework définit à cet effet une interface pour la traduction [api:Nette\Localization\Translator], qui a une seule méthode `translate()`. Elle accepte un message `$message`, qui est généralement une chaîne, et tout autre paramètre. La tâche est de retourner la chaîne traduite. Il n'y a pas d'implémentation par défaut dans Nette, vous pouvez choisir parmi plusieurs solutions prêtes à l'emploi selon vos besoins, que vous trouverez sur [Componette |https://componette.org/search/localization]. Dans leur documentation, vous apprendrez comment configurer le traducteur. -Les modèles peuvent être configurés avec un traducteur, que l'on nous [aura passé |dependency-injection:passing-dependencies], en utilisant la méthode `setTranslator()`: +Il est possible de définir un traducteur pour les templates, que nous [recevrons via injection |dependency-injection:passing-dependencies], avec la méthode `setTranslator()` : ```php protected function beforeRender(): void @@ -232,38 +279,38 @@ protected function beforeRender(): void } ``` -Alternativement, le traducteur peut être défini à l'aide de la [configuration |configuration#Latte]: +Le traducteur peut alternativement être défini via la [configuration |configuration#Templates Latte] : ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -Le traducteur peut alors être utilisé, par exemple, comme un filtre `|translate`, avec des paramètres supplémentaires transmis à la méthode `translate()` (voir `foo, bar`) : +Ensuite, le traducteur peut être utilisé par exemple comme filtre `|translate`, y compris avec des paramètres supplémentaires qui sont passés à la méthode `translate()` (voir `foo, bar`) : ```latte -{='Basket'|translate} +{='Panier'|translate} {$item|translate} {$item|translate, foo, bar} ``` -Ou comme une balise de soulignement : +Ou comme balise soulignée : ```latte -{_'Basket'} +{_'Panier'} {_$item} {_$item, foo, bar} ``` -Pour la traduction des sections de modèles, il existe une balise appariée `{translate}` (depuis Latte 2.11, la balise `{_}` était utilisée auparavant) : +Pour traduire une section du template, il existe une balise paire `{translate}` (depuis Latte 2.11, auparavant la balise `{_}` était utilisée) : ```latte -{translate}Order{/translate} -{translate foo, bar}Order{/translate} +{translate}Commande{/translate} +{translate foo, bar}Commande{/translate} ``` -Translator est appelé par défaut au moment de l'exécution, lors du rendu du modèle. Latte version 3, cependant, peut traduire tout le texte statique pendant la compilation du modèle. Cela permet de gagner en performance car chaque chaîne n'est traduite qu'une seule fois et la traduction résultante est écrite dans le modèle compilé. Cela crée plusieurs versions compilées du modèle dans le répertoire de cache, une pour chaque langue. Pour ce faire, il suffit de spécifier la langue comme deuxième paramètre : +Le traducteur est appelé par défaut à l'exécution lors du rendu du template. Cependant, Latte version 3 peut traduire tous les textes statiques déjà pendant la compilation du template. Cela économise des performances, car chaque chaîne n'est traduite qu'une seule fois et la traduction résultante est écrite dans la forme compilée. Ainsi, plusieurs versions compilées du template sont créées dans le répertoire cache, une pour chaque langue. Pour cela, il suffit d'indiquer la langue comme deuxième paramètre : ```php protected function beforeRender(): void @@ -273,4 +320,4 @@ protected function beforeRender(): void } ``` -Par texte statique, nous entendons, par exemple, `{_'hello'}` ou `{translate}hello{/translate}`. Le texte non statique, tel que `{_$foo}`, continuera à être compilé à la volée. +Par texte statique, on entend par exemple `{_'hello'}` ou `{translate}hello{/translate}`. Les textes non statiques, comme par exemple `{_$foo}`, continueront d'être traduits à l'exécution. diff --git a/application/hu/@home.texy b/application/hu/@home.texy index 49f809ce72..b9bc4c1a11 100644 --- a/application/hu/@home.texy +++ b/application/hu/@home.texy @@ -2,35 +2,84 @@ Nette Application ***************** .[perex] -A `nette/application` csomag az interaktív webes alkalmazások készítésének alapja. - -- [Hogyan működnek az alkalmazások? |how-it-works] -- [Bootstrap |Bootstrap] -- [Bemutatók |Presenters] -- [Sablonok |Templates] -- [Modulok |Modules] -- [Útválasztás |Routing] -- [URL linkek létrehozása |creating-links] -- [Interaktív komponensek |components] -- [AJAX és snippetek |ajax] -- [Multiplikátor |multiplier] -- [Konfiguráció |Configuration] +A Nette Application a Nette keretrendszer magja, amely hatékony eszközöket kínál modern webalkalmazások létrehozásához. Számos kivételes tulajdonságot kínál, amelyek jelentősen megkönnyítik a fejlesztést, és javítják a kód biztonságát és karbantarthatóságát. Telepítés --------- -Töltse le és telepítse a csomagot a [Composer |best-practices:composer] segítségével: +A könyvtárat a [Composer|best-practices:composer] eszközzel töltheti le és telepítheti: ```shell composer require nette/application ``` -| verzió | kompatibilis a PHP-vel + +Miért válassza a Nette Applicationt? +------------------------------------ + +A Nette mindig is úttörő volt a webes technológiák területén. + +**Kétirányú router:** A Nette fejlett router rendszerrel rendelkezik, amely kétirányúsága miatt egyedülálló - nemcsak az URL-eket fordítja le az alkalmazás akcióira, hanem visszafelé is képes URL-címeket generálni. Ez azt jelenti, hogy: +- Bármikor megváltoztathatja az egész alkalmazás URL-struktúráját anélkül, hogy a sablonokat módosítania kellene +- Az URL-ek automatikusan kanonizálódnak, ami javítja a SEO-t +- Az útválasztás egy helyen van definiálva, nem pedig szétszórva az annotációkban + +**Komponensek és szignálok:** A Delphi és a React.js által inspirált beépített komponensrendszer teljesen egyedülálló a PHP keretrendszerek között: +- Lehetővé teszi újrafelhasználható UI elemek létrehozását +- Támogatja a komponensek hierarchikus összeállítását +- Elegáns AJAX kérések kezelését kínálja szignálok segítségével +- Kész komponensek gazdag könyvtára a [Componette](https://componette.org) oldalon + +**AJAX és snippettek:** A Nette már 2009-ben forradalmi módszert vezetett be az AJAX-szal való munkára, jóval megelőzve az olyan hasonló megoldásokat, mint a Hotwire a Ruby on Railshez vagy a Symfony UX Turbo: +- A snippettek lehetővé teszik az oldal csak egyes részeinek frissítését JavaScript írása nélkül +- Automatikus integráció a komponensrendszerrel +- Oldalrészek intelligens érvénytelenítése +- Minimális mennyiségű továbbított adat + +**Intuitív [Latte|latte:] sablonok:** A legbiztonságosabb sablonrendszer PHP-hoz fejlett funkciókkal: +- Automatikus védelem XSS ellen kontextusérzékeny escapeléssel +- Bővíthetőség saját szűrőkkel, függvényekkel és tagekkel +- Sablon öröklődés és snippettek AJAX-hoz +- Kiváló PHP 8.x támogatás típusrendszerrel + +**Dependency Injection:** A Nette teljes mértékben kihasználja a Dependency Injectiont: +- Függőségek automatikus átadása (autowiring) +- Konfiguráció áttekinthető NEON formátumban +- Komponens factory-k támogatása + + +Fő előnyök +---------- + +- **Biztonság**: Automatikus védelem a [sebezhetőségekkel|nette:vulnerability-protection] szemben, mint az XSS, CSRF stb. +- **Termelékenység**: Kevesebb írás, több funkció az intelligens tervezésnek köszönhetően +- **Debuggolás**: [Tracy debugger|tracy:] útválasztó panellel +- **Teljesítmény**: Intelligens cache, komponensek lusta betöltése (lazy loading) +- **Rugalmasság**: Az URL-ek egyszerű módosítása az alkalmazás befejezése után is +- **Komponensek**: Egyedülálló újrafelhasználható UI elemek rendszere +- **Modern**: Teljes PHP 8.4+ és típusrendszer támogatás + + +Első lépések +------------ + +1. [Hogyan működnek az alkalmazások? |how-it-works] - Az alapvető architektúra megértése +2. [Presenterek |presenters] - Munka presenterekkel és akciókkal +3. [Sablonok |templates] - Sablonok készítése Latte-ban +4. [Route-ok |routing] - URL címek konfigurálása +5. [Interaktív komponensek |components] - A komponensrendszer kihasználása + + +PHP kompatibilitás +------------------ + +| verzió | kompatibilis PHP-vel |-----------|------------------- -| Nette alkalmazás 4.0 | PHP 8.0 - 8.2 -| Nette alkalmazás 3.1 | PHP 7.2 - 8.2 -| Nette alkalmazás 3.0 | PHP 7.1 - 8.0 -| Nette Application 2.4 | PHP 5.6 - 8.0 +| Nette Application 4.0 | PHP 8.1 – 8.4 +| Nette Application 3.2 | PHP 8.1 – 8.4 +| Nette Application 3.1 | PHP 7.2 – 8.3 +| Nette Application 3.0 | PHP 7.1 – 8.0 +| Nette Application 2.4 | PHP 5.6 – 8.0 -A legújabb javítási verziókra vonatkozik. +Az utolsó patch verzióra érvényes. diff --git a/application/hu/@left-menu.texy b/application/hu/@left-menu.texy index cfe693b111..1c42a160dd 100644 --- a/application/hu/@left-menu.texy +++ b/application/hu/@left-menu.texy @@ -1,19 +1,22 @@ -Nette alkalmazás -**************** +Nette Application +***************** - [Hogyan működnek az alkalmazások? |how-it-works] -- [Bootstrap |Bootstrap] -- [Bemutatók |Presenters] -- [Sablonok |Templates] -- [Modulok |Modules] -- [Útválasztás |Routing] +- [Bootstrapping] +- [Presenterek |presenters] +- [Sablonok |templates] +- [Könyvtárstruktúra |directory-structure] +- [Route-ok |routing] - [URL linkek létrehozása |creating-links] - [Interaktív komponensek |components] -- [AJAX és snippetek |ajax] -- [Multiplikátor |multiplier] -- [Konfiguráció |Configuration] +- [AJAX & snippettek |ajax] +- [Multiplier |multiplier] +- [Konfiguráció |configuration] -További olvasnivaló +További olvasmányok ******************* -- [Legjobb gyakorlatok |best-practices:] -- [Hibaelhárítás |nette:troubleshooting] +- [Miért használjuk a Nette-t? |www:10-reasons-why-nette] +- [Telepítés |nette:installation] +- [Írjuk meg az első alkalmazásunkat! |quickstart:] +- [Útmutatók és eljárások |best-practices:] +- [Problémamegoldás |nette:troubleshooting] diff --git a/application/hu/@meta.texy b/application/hu/@meta.texy new file mode 100644 index 0000000000..c172d1cda5 --- /dev/null +++ b/application/hu/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette dokumentáció}} diff --git a/application/hu/ajax.texy b/application/hu/ajax.texy index adab48653b..8eec6609e2 100644 --- a/application/hu/ajax.texy +++ b/application/hu/ajax.texy @@ -3,36 +3,43 @@ AJAX & Snippetek
    -A modern webes alkalmazások manapság félig a szerveren, félig a böngészőben futnak. Az AJAX létfontosságú egyesítő tényező. Milyen támogatást nyújt a Nette Framework? -- sablonrészletek küldése (ún. *snippetek*) +A modern webalkalmazások korában, ahol a funkcionalitás gyakran megoszlik a szerver és a böngésző között, az AJAX elengedhetetlen összekötő elem. Milyen lehetőségeket kínál nekünk a Nette Framework ezen a területen? +- sablonrészek, úgynevezett snippetek küldése - változók átadása PHP és JavaScript között -- AJAX-alkalmazások hibakeresése +- eszközök AJAX kérések debuggolásához
    -Egy AJAX-kérés a `$httpRequest->isAjax()` [HTTP-kérést kapszulázó |http:request] szolgáltatás módszerével detektálható (a `X-Requested-With` HTTP-fejléc alapján detektál). A prezenterben is van egy rövidített módszer: `$this->isAjax()`. -Az AJAX-kérés nem különbözik a normál kéréstől - a prezentert egy bizonyos nézettel és paraméterekkel hívják meg. Az is a prezenteren múlik, hogy miként reagál: a rutinjaival vagy egy HTML kódrészletet (snippet), egy XML-dokumentumot, egy JSON objektumot vagy egy Javascript kódrészletet adhat vissza. +AJAX kérés +========== -Létezik egy `payload` nevű előfeldolgozott objektum, amely arra szolgál, hogy az adatokat JSON-ban küldje el a böngészőnek. +Az AJAX kérés alapvetően nem különbözik a klasszikus HTTP kéréstől. Meghív egy presentert bizonyos paraméterekkel. És a presenteren múlik, hogyan reagál a kérésre - visszaadhat adatokat JSON formátumban, küldhet HTML kód egy részét, XML dokumentumot stb. -```php -public function actionDelete(int $id): void -{ - if ($this->isAjax()) { - $this->payload->message = 'Success'; - } - // ... -} +A böngésző oldalán az AJAX kérést a `fetch()` függvénnyel inicializáljuk: + +```js +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) +.then(response => response.json()) +.then(payload => { + // válasz feldolgozása +}); ``` -A JSON kimenet teljes ellenőrzéséhez használja a `sendJson` metódust a prezenterben. Ez azonnal befejezi a prezentert, és sablon nélkül is boldogulsz: +Szerveroldalon az AJAX kérést a [HTTP kérést becsomagoló |http:request] szolgáltatás `$httpRequest->isAjax()` metódusával ismerjük fel. Az észleléshez a `X-Requested-With` HTTP fejlécet használja, ezért fontos elküldeni. A presenterben a `$this->isAjax()` metódus használható. + +Ha adatokat szeretne küldeni JSON formátumban, használja a [`sendJson()` |presenters#Válasz küldése] metódust. A metódus szintén befejezi a presenter működését. ```php -$this->sendJson(['key' => 'value', /* ... */]); +public function actionExport(): void +{ + $this->sendJson($this->model->getData); +} ``` -Ha HTML-t szeretnénk küldeni, akkor vagy beállíthatunk egy speciális sablont az AJAX-kérésekhez: +Ha egy speciális, AJAX-hoz szánt sablonnal tervez válaszolni, a következőképpen teheti meg: ```php public function handleClick($param): void @@ -45,74 +52,80 @@ public function handleClick($param): void ``` -Naja .[#toc-naja] -================= +Snippetek +========= + +A Nette által kínált legerősebb eszköz a szerver és a kliens összekapcsolására a snippetek. Ezeknek köszönhetően egy átlagos alkalmazást minimális erőfeszítéssel és néhány sor kóddal AJAX-alapúvá alakíthat. Hogy mindez hogyan működik, azt a Fifteen példa demonstrálja, amelynek kódját a [GitHubon |https://github.com/nette-examples/fifteen] találja meg. + +A snippetek, vagyis kódrészletek, lehetővé teszik az oldal csak bizonyos részeinek frissítését, ahelyett, hogy az egész oldalt újra kellene tölteni. Ez nemcsak gyorsabb és hatékonyabb, hanem kényelmesebb felhasználói élményt is nyújt. A snippetek emlékeztethetnek a Hotwire for Ruby on Rails vagy a Symfony UX Turbo megoldásokra. Érdekesség, hogy a Nette már 14 évvel korábban bemutatta a snippeteket. + +Hogyan működnek a snippetek? Az oldal első betöltésekor (nem AJAX kérés esetén) az egész oldal betöltődik, beleértve az összes snippetet is. Amikor a felhasználó interakcióba lép az oldallal (pl. gombra kattint, űrlapot küld stb.), az egész oldal betöltése helyett egy AJAX kérés indul. A presenterben lévő kód végrehajtja a műveletet, és eldönti, mely snippeteket kell frissíteni. A Nette ezeket a snippeteket rendereli és JSON formátumú tömbként küldi el. A böngészőben lévő kezelő kód a kapott snippeteket visszailleszti az oldalba. Így csak a megváltozott snippetek kódja kerül átvitelre, ami sávszélességet takarít meg és gyorsítja a betöltést az egész oldal tartalmának átvitelével szemben. + -A [Naja könyvtár |https://naja.js.org] az AJAX kérések kezelésére szolgál a böngésző oldalán. [Telepítsd |https://naja.js.org/#/guide/01-install-setup-naja] node.js csomagként (Webpack, Rollup, Vite, Parcel és más csomagokkal való használathoz): +Naja +---- + +A snippetek böngészőoldali kezelésére a [Naja könyvtár |https://naja.js.org] szolgál. Ezt [telepítse |https://naja.js.org/#/guide/01-install-setup-naja] node.js csomagként (Webpack, Rollup, Vite, Parcel és más alkalmazásokkal való használathoz): ```shell npm install naja ``` -...vagy illessze be közvetlenül az oldal sablonjába: +…vagy közvetlenül illessze be az oldal sablonjába: ```html ``` +Először is [inicializálni |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] kell a könyvtárat: -Snippets .[#toc-snippets] -========================= +```js +naja.initialize(); +``` -A beépített AJAX-támogatásnak van egy sokkal hatékonyabb eszköze - a snippetek. Használatuk lehetővé teszi, hogy egy hagyományos alkalmazásból AJAX-alkalmazássá váljon mindössze néhány sornyi kóddal. Hogy mindez hogyan működik, azt a Fifteen példa mutatja be, amelynek kódja a buildben vagy a [GitHubon |https://github.com/nette-examples/fifteen] is elérhető. +Ahhoz, hogy egy egyszerű linkből (signal) vagy űrlapküldésből AJAX kérés legyen, elegendő a megfelelő linket, űrlapot vagy gombot `ajax` osztállyal megjelölni: -A snippetek úgy működnek, hogy a kezdeti (azaz nem AJAX) kérés során a teljes oldal átkerül, majd minden AJAX [alkérésnél |components#signal] (ugyanazon bemutató azonos nézetének kérése) csak a módosított részek kódja kerül át a már említett `payload` tárolóba. +```html +Go -A Snippetek a Ruby on Rails Hotwire vagy a Symfony UX Turbo-ra emlékeztethetnek, de Nette tizennégy évvel korábban találta ki őket. +
    + +
    +vagy -A Snippetek érvénytelenítése .[#toc-invalidation-of-snippets] -============================================================= +
    + +
    +``` -A [Control |components] osztály minden leszármazottja (ami egy Presenter is) képes megjegyezni, hogy egy kérés során történt-e olyan változás, ami miatt újra kell rendeznie. Van egy pár módszer ennek kezelésére: `redrawControl()` és `isControlInvalid()`. Egy példa: + +Snippetek újrarajzolása +----------------------- + +Minden [Control |components] osztályú objektum (beleértve magát a Presentert is) nyilvántartja, hogy történt-e olyan változás, amely az újrarajzolását igényli. Erre szolgál a `redrawControl()` metódus: ```php public function handleLogin(string $user): void { - // Az objektumot újra kell renderelni, miután a felhasználó bejelentkezett. + // bejelentkezés után újra kell rajzolni a releváns részt $this->redrawControl(); // ... } ``` -A Nette azonban még finomabb felbontást kínál, mint az egész komponensek. A felsorolt módszerek opcionális paraméterként elfogadják egy úgynevezett "snippet" nevét. A "snippet" alapvetően a sablonod egy eleme, amelyet erre a célra egy Latte makróval jelöltél meg, erről később. Így lehetőség van arra, hogy egy komponenst arra kérjünk, hogy csak a sablonjának *részeit* rajzolja újra. Ha a teljes komponens érvénytelenítésre kerül, akkor az összes snippetje újrarendezésre kerül. Egy komponens akkor is "érvénytelen", ha bármelyik alkomponense érvénytelen. - -```php -$this->isControlInvalid(); // -> false -$this->redrawControl('header'); // érvényteleníti a 'header' nevű snippet-t. -$this->isControlInvalid('header'); // -> true -$this->isControlInvalid('footer'); // -> false -$this->isControlInvalid(); // -> true, legalább egy snippet érvénytelen. +A Nette még finomabb vezérlést tesz lehetővé afölött, hogy mit kell újrarajzolni. Az említett metódus ugyanis argumentumként fogadhatja a snippet nevét. Így lehet invalidálni (értsd: újrarajzolást kényszeríteni) a sablon részei szintjén. Ha az egész komponenst invalidáljuk, akkor annak minden snippetje is újrarajzolódik: -$this->redrawControl(); // érvényteleníti az egész komponenst, minden egyes snippet-tel együtt. -$this->isControlInvalid('footer'); // -> true +```php +// invalidálja a 'header' snippetet +$this->redrawControl('header'); ``` -A jelzést kapó komponens automatikusan újrarajzolásra kerül. - -A snippet-újrarajzolásnak köszönhetően pontosan tudjuk, hogy mely elemek mely részeit kell újrarajzolni. - - -Tag `{snippet} … {/snippet}` .{toc: Tag snippet} -================================================ -Az oldal megjelenítése nagyon hasonlóan zajlik, mint egy normál kérésnél: ugyanazok a sablonok töltődnek be stb. A lényeges rész azonban az, hogy a kimenetre nem szánt részek kimaradnak; a többi részhez egy azonosítót kell társítani, és egy JavaScript kezelő számára érthető formátumban kell elküldeni a felhasználónak. +Snippetek a Latte-ban +--------------------- - -Szintaxis .[#toc-syntax] ------------------------- - -Ha a sablonban van egy vezérlőelem vagy egy snippet, akkor azt a `{snippet} ... {/snippet}` páros taggel kell becsomagolnunk - ez biztosítja, hogy a renderelt snippet "kivágásra" kerüljön, és elküldjük a böngészőnek. Ez is egy segítőbe fogja beburkolni. `
    ` tagbe (lehet másikat is használni). A következő példában egy `header` nevű snippet van definiálva. Ez akár egy komponens sablonját is jelentheti: +A snippetek használata a Latte-ban rendkívül egyszerű. Ha egy sablonrészt snippetként szeretne definiálni, egyszerűen csomagolja be `{snippet}` és `{/snippet}` tag-ekkel: ```latte {snippet header} @@ -120,7 +133,9 @@ Ha a sablonban van egy vezérlőelem vagy egy snippet, akkor azt a `{snippet} .. {/snippet} ``` -A snippet egy más típusú snippet, mint a `
    ` vagy egy további HTML-attribútumokkal ellátott snippet az attribútumváltozat használatával érhető el: +A snippet létrehoz egy `
    ` elemet a HTML oldalon egy speciális, generált `id`-val. A snippet újrarajzolásakor ennek az elemnek a tartalma frissül. Ezért szükséges, hogy az oldal első renderelésekor az összes snippet is renderelődjön, még akkor is, ha esetleg kezdetben üresek. + +Létrehozhat snippetet `
    `-től eltérő elemmel is egy n:attribútum segítségével: ```latte
    @@ -129,138 +144,106 @@ A snippet egy más típusú snippet, mint a `
    ` vagy egy további HTML-attri ``` -Dynamic Snippets .[#toc-dynamic-snippets] -========================================= +Snippet területek +----------------- -A Nette-ben egy futásidejű paraméter alapján dinamikus névvel ellátott snippeteket is definiálhat. Ez leginkább olyan különböző listákhoz alkalmas, ahol csak egy sort kell megváltoztatnunk, de nem akarjuk vele együtt az egész listát is átvinni. Egy példa erre a következő lenne: +A snippetek nevei kifejezések is lehetnek: ```latte -
      - {foreach $list as $id => $item} -
    • {$item} update
    • - {/foreach} -
    +{foreach $items as $id => $item} +
  • {$item}
  • +{/foreach} ``` -Van egy statikus snippet, a `itemsContainer`, amely több dinamikus snippet tartalmaz: `item-0`, `item-1` és így tovább. +Így több snippet jön létre: `item-0`, `item-1` stb. Ha közvetlenül invalidálnánk egy dinamikus snippetet (például `item-1`), semmi sem rajzolódna újra. Ennek oka az, hogy a snippetek valóban kódrészletekként működnek, és csak közvetlenül önmaguk renderelődnek. Azonban a sablonban valójában nincs `item-1` nevű snippet. Az csak a snippet körüli kód, azaz a foreach ciklus végrehajtásakor jön létre. Ezért megjelöljük a sablon azon részét, amelyet végre kell hajtani a `{snippetArea}` tag segítségével: -Egy dinamikus snippet közvetlenül nem rajzolható újra (a `item-1` újrarajzolásának nincs hatása), a szülő snippetjét (ebben a példában `itemsContainer`) kell újrarajzolni. Ennek hatására a szülő snippet kódja végrehajtódik, de ezután csak az alszippetjei kerülnek elküldésre a böngészőnek. Ha csak az egyik alrészletet szeretné átküldeni, akkor a szülő részlet bemenetét úgy kell módosítania, hogy a többi alrészletet ne generálja. +```latte +
      + {foreach $items as $id => $item} +
    • {$item}
    • + {/foreach} +
    +``` -A fenti példában gondoskodnia kell arról, hogy egy AJAX-kérés esetén csak egy elem kerüljön a `$list` tömbhöz, ezért a `foreach` ciklus csak egy dinamikus snippetet fog kiírni. +És újrarajzoltatjuk mind a snippetet magát, mind a teljes szülő területet: ```php -class HomePresenter extends Nette\Application\UI\Presenter -{ - /** - * This method returns data for the list. - * Usually this would just request the data from a model. - * For the purpose of this example, the data is hard-coded. - */ - private function getTheWholeList(): array - { - return [ - 'First', - 'Second', - 'Third', - ]; - } - - public function renderDefault(): void - { - if (!isset($this->template->list)) { - $this->template->list = $this->getTheWholeList(); - } - } - - public function handleUpdate(int $id): void - { - $this->template->list = $this->isAjax() - ? [] - : $this->getTheWholeList(); - $this->template->list[$id] = 'Updated item'; - $this->redrawControl('itemsContainer'); - } -} +$this->redrawControl('itemsContainer'); +$this->redrawControl('item-1'); ``` +Ugyanakkor célszerű biztosítani, hogy az `$items` tömb csak azokat az elemeket tartalmazza, amelyeket újra kell rajzolni. -Snippetek egy belefoglalt sablonban .[#toc-snippets-in-an-included-template] -============================================================================ - -Előfordulhat, hogy a snippet egy olyan sablonban van, amely egy másik sablonból van beépítve. Ebben az esetben a második sablonban a `snippetArea` makróval be kell csomagolnunk a beillesztési kódot, majd újra kell rajzolnunk mind a snippetArea-t, mind a tényleges snippetet. - -A `snippetArea` makró biztosítja, hogy a benne lévő kód végrehajtásra kerül, de csak a tényleges snippet kerül a böngészőhöz a bevont sablonban. +Ha a sablonba a `{include}` tag segítségével egy másik sablont illesztünk be, amely snippeteket tartalmaz, a sablon beillesztését ismét `snippetArea`-ba kell foglalni, és azt a snippettel együtt kell invalidálni: ```latte -{* parent.latte *} -{snippetArea wrapper} - {include 'child.latte'} +{snippetArea include} + {include 'included.latte'} {/snippetArea} ``` + ```latte -{* child.latte *} +{* included.latte *} {snippet item} -... + ... {/snippet} ``` + ```php -$this->redrawControl('wrapper'); +$this->redrawControl('include'); $this->redrawControl('item'); ``` -Dinamikus snippetekkel is kombinálható. - -Hozzáadás és törlés .[#toc-adding-and-deleting] -=============================================== +Snippetek a komponensekben +-------------------------- -Ha új elemet adsz hozzá a listához, és érvényteleníted a `itemsContainer` címet, az AJAX-kérés az új elemet is tartalmazó részleteket küldi vissza, de a javascript kezelő nem tudja megjeleníteni azt. Ennek oka, hogy nincs olyan HTML-elem, amely az újonnan létrehozott azonosítóval rendelkezik. - -Ebben az esetben a legegyszerűbb megoldás, ha az egész listát még egy snippetbe csomagoljuk, és az egészet érvénytelenítjük: +Snippeteket [komponensekben|components] is létrehozhat, és a Nette automatikusan újrarajzolja őket. De van egy korlátozás: a snippetek újrarajzolásához a `render()` metódust paraméterek nélkül hívja meg. Tehát a paraméterek átadása a sablonban nem fog működni: ```latte -{snippet wholeList} -
      - {foreach $list as $id => $item} -
    • {$item} update
    • - {/foreach} -
    -{/snippet} -Add +OK +{control productGrid} + +nem fog működni: +{control productGrid $arg, $arg} +{control productGrid:paginator} ``` + +Felhasználói adatok küldése +--------------------------- + +A snippetekkel együtt tetszőleges további adatokat is küldhet a kliensnek. Egyszerűen írja be őket a `payload` objektumba: + ```php -public function handleAdd(): void +public function actionDelete(int $id): void { - $this->template->list = $this->getTheWholeList(); - $this->template->list[] = 'New one'; - $this->redrawControl('wholeList'); + // ... + if ($this->isAjax()) { + $this->payload->message = 'Sikeres'; + } } ``` -Ugyanez vonatkozik egy elem törlésére is. Lehetséges lenne üres snippet küldése, de általában a listák lehetnek oldalszámozottak, és bonyolult lenne megvalósítani egy elem törlését és egy másik betöltését (amely korábban a oldalszámozott lista egy másik oldalán volt). - -Paraméterek küldése a komponensnek .[#toc-sending-parameters-to-component] -========================================================================== +Paraméterek átadása +=================== -Amikor AJAX-kérésen keresztül paramétereket küldünk a komponensnek, legyenek azok jelparaméterek vagy állandó paraméterek, meg kell adnunk a globális nevüket, amely tartalmazza a komponens nevét is. A paraméter teljes nevét a `getParameterId()` metódus adja vissza. +Ha egy komponensnek AJAX kéréssel paramétereket küldünk, legyenek azok signal paraméterek vagy perzisztens paraméterek, a kérésnél meg kell adnunk a globális nevüket, amely tartalmazza a komponens nevét is. A paraméter teljes nevét a `getParameterId()` metódus adja vissza. ```js -$.getJSON( - {link changeCountBasket!}, - { - {$control->getParameterId('id')}: id, - {$control->getParameterId('count')}: count - } -}); +let url = new URL({link //foo!}); +url.searchParams.set({$control->getParameterId('bar')}, bar); + +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) ``` -És kezelje a módszert s megfelelő paraméterekkel a komponensben. +És a handle metódus a megfelelő paraméterekkel a komponensben: ```php -public function handleChangeCountBasket(int $id, int $count): void +public function handleFoo(int $bar): void { - } ``` diff --git a/application/hu/bootstrap.texy b/application/hu/bootstrap.texy deleted file mode 100644 index 941bad5c80..0000000000 --- a/application/hu/bootstrap.texy +++ /dev/null @@ -1,233 +0,0 @@ -Bootstrap -********* - -
    - -A Bootstrap egy indító kód, amely inicializálja a környezetet, létrehoz egy függőségi injektálási (DI) konténert, és elindítja az alkalmazást. Megbeszéljük: - -- hogyan konfigurálhatja az alkalmazást NEON fájlok segítségével. -- hogyan kezelje a termelési és a fejlesztési módokat -- hogyan hozzuk létre a DI konténert - -
    - - -Az alkalmazások, akár webes alapúak, akár parancssori szkriptek, valamilyen környezet-inicializálással kezdődnek. A régi időkben ez egy pl. `include.inc.php` nevű fájl lehetett, amely ezért volt felelős, és a kezdeti fájlban szerepelt. -A modern Nette alkalmazásokban ezt felváltotta a `Bootstrap` osztály, amely az alkalmazás részeként a `app/Bootstrap.php`. Ez például így nézhet ki: - -```php -use Nette\Bootstrap\Configurator; - -class Bootstrap -{ - public static function boot(): Configurator - { - $appDir = dirname(__DIR__); - $configurator = new Configurator; - //$configurator->setDebugMode('secret@23.75.345.200'); - $configurator->enableTracy($appDir . '/log'); - $configurator->setTempDirectory($appDir . '/temp'); - $configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); - $configurator->addConfig($appDir . '/config/common.neon'); - return $configurator; - } -} -``` - - -index.php .[#toc-index-php] -=========================== - -A webes alkalmazások esetében a kezdő fájl a `index.php`, amely a `www/` nyilvános könyvtárban található. Ez lehetővé teszi a `Bootstrap` osztály számára, hogy inicializálja a környezetet, és visszaadja a `$configurator`, amely létrehozza a DI konténert. Ezután megszerzi a `Application` szolgáltatást, amely a webalkalmazást futtatja: - -```php -// a környezet inicializálása + konfigurátor objektum kinyerése -$configurator = App\Bootstrap::boot(); -// DI konténer létrehozása -$container = $configurator->createContainer(); -// A DI konténer létrehoz egy Nette\Application\Application objektumot. -$application = $container->getByType(Nette\Application\Application::class); -// Nette alkalmazás indítása -$application->run(); -``` - -Mint látható, a [api:Nette\Bootstrap\Configurator] osztály, amelyet most részletesebben bemutatunk, segít a környezet beállításában és a függőségi injektálás (DI) konténer létrehozásában. - - -Fejlesztői vs. termelési üzemmód .[#toc-development-vs-production-mode] -======================================================================= - -A Nette két alapvető módot különböztet meg, amelyben egy kérés végrehajtásra kerül: fejlesztési és termelési mód. A fejlesztési mód a programozó maximális kényelmére összpontosít, a Tracy megjelenik, a gyorsítótár automatikusan frissül, ha a sablonok vagy a DI konténer konfigurációja változik, stb. A termelési mód a teljesítményre összpontosít, a Tracy csak a hibákat naplózza, a sablonok és egyéb fájlok módosításait nem ellenőrzi. - -A mód kiválasztása automatikus felismeréssel történik, így általában nem szükséges kézzel konfigurálni vagy váltani semmit. A mód fejlesztési, ha az alkalmazás localhoston fut (azaz a `127.0.0.1` vagy a `::1` IP-címen ) és nincs proxy (azaz a HTTP fejléce). Ellenkező esetben termelési üzemmódban fut. - -Ha más esetekben, például egy adott IP-címről hozzáférő programozók számára szeretné engedélyezni a fejlesztési üzemmódot, akkor a `setDebugMode()` címet használhatja: - -```php -$configurator->setDebugMode('23.75.345.200'); // egy vagy több IP-cím -``` - -Mindenképpen javasoljuk az IP-cím és a cookie kombinálását. A `nette-debug` cookie-ban tárolunk egy titkos tokent, pl. `secret1234`, és a fejlesztési mód az IP és a cookie ilyen kombinációjával rendelkező programozók számára aktiválódik. - -```php -$configurator->setDebugMode('secret1234@23.75.345.200'); -``` - -A fejlesztői módot teljesen ki is kapcsolhatjuk, akár a localhost esetében is: - -```php -$configurator->setDebugMode(false); -``` - -A `true` érték keményen bekapcsolja a fejlesztői módot, ami soha nem történhet meg egy termelő szerveren. - - -Hibakereső eszköz Tracy .[#toc-debugging-tool-tracy] -==================================================== - -Az egyszerű hibakeresés érdekében bekapcsoljuk a [Tracy |tracy:] nevű nagyszerű eszközt. Fejlesztői módban megjeleníti a hibákat, termelési módban pedig a megadott könyvtárba naplózza a hibákat: - -```php -$configurator->enableTracy($appDir . '/log'); -``` - - -Ideiglenes fájlok .[#toc-temporary-files] -========================================= - -A Nette a DI konténer, a RobotLoader, a sablonok stb. számára használja a gyorsítótárat. Ezért szükséges annak a könyvtárnak az elérési útvonalát beállítani, ahol a gyorsítótár tárolásra kerül: - -```php -$configurator->setTempDirectory($appDir . '/temp'); -``` - -Linuxon vagy macOS-en állítsa be a `log/` és a `temp/` könyvtárak [írási engedélyeit |nette:troubleshooting#Setting directory permissions]. - - -RobotLoader .[#toc-robotloader] -=============================== - -Általában a [RobotLoader |robot-loader:] segítségével szeretnénk automatikusan betölteni az osztályokat, ezért el kell indítanunk, és hagynunk kell, hogy betöltse az osztályokat abból a könyvtárból, ahol a `Bootstrap.php` található (azaz `__DIR__`) és annak összes alkönyvtárából: - -```php -$configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); -``` - -Egy alternatív megoldás az, hogy csak a [Composer |best-practices:composer] PSR-4 automatikus betöltését használjuk. - - -Időzóna .[#toc-timezone] -======================== - -A Configurator lehetővé teszi, hogy megadjon egy időzónát az alkalmazásához. - -```php -$configurator->setTimeZone('Europe/Prague'); -``` - - -DI konténer konfigurálása .[#toc-di-container-configuration] -============================================================ - -Az indítási folyamat része a DI konténer, azaz az objektumgyár létrehozása, amely az egész alkalmazás szíve. Ez tulajdonképpen egy Nette által generált és egy cache könyvtárban tárolt PHP osztály. A gyár létrehozza az alkalmazás kulcsfontosságú objektumait, a konfigurációs fájlok pedig utasítják, hogyan hozza létre és konfigurálja őket, és így befolyásoljuk az egész alkalmazás viselkedését. - -A konfigurációs fájlokat általában [NEON formátumban |neon:format] írjuk. Itt olvashatja el, hogy mit lehet [konfigurálni |nette:configuring]. - -.[tip] -Fejlesztői üzemmódban a konténer automatikusan frissül minden alkalommal, amikor megváltoztatja a kódot vagy a konfigurációs fájlokat. Termelési módban csak egyszer generálódik, és a teljesítmény maximalizálása érdekében a fájlváltozásokat nem ellenőrzi a rendszer. - -A konfigurációs fájlok betöltése a `addConfig()` segítségével történik: - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -``` - -A `addConfig()` metódus többször is meghívható több fájl hozzáadásához. - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -$configurator->addConfig($appDir . '/config/local.neon'); -if (PHP_SAPI === 'cli') { - $configurator->addConfig($appDir . '/config/cli.php'); -} -``` - -A `cli.php` név nem elírás, a konfiguráció egy PHP fájlba is írható, amely tömbként adja vissza. - -Alternatívaként használhatjuk a [`includes` szekciót |dependency-injection:configuration#including files] is, hogy több konfigurációs fájlt töltsünk be. - -Ha a konfigurációs fájlokban azonos kulcsú elemek jelennek meg, akkor azok [felülíródnak, vagy |dependency-injection:configuration#Merging] tömbök esetén [összevonásra |dependency-injection:configuration#Merging] kerülnek. A később bevont fájlnak magasabb prioritása van, mint az előzőnek. Az a fájl, amelyben a `includes` szakasz szerepel, magasabb prioritással rendelkezik, mint a benne foglalt fájlok. - - -Statikus paraméterek .[#toc-static-parameters] ----------------------------------------------- - -A konfigurációs fájlokban használt paramétereket a [`parameters` szakaszban |dependency-injection:configuration#parameters] lehet definiálni, és a `addStaticParameters()` metódus (amelynek alias neve `addParameters()`) is átadhatja (vagy felülírhatja). Fontos, hogy a különböző paraméterértékek további DI-konténerek, azaz további osztályok generálását okozzák. - -```php -$configurator->addStaticParameters([ - 'projectId' => 23, -]); -``` - -A konfigurációs fájlokban a `%projectId%` szokásos jelölést írhatjuk a `projectId` nevű paraméter eléréséhez. Alapértelmezés szerint a konfigurátor a következő paramétereket tölti fel: `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` és `consoleMode`. - - -Dinamikus paraméterek .[#toc-dynamic-parameters] ------------------------------------------------- - -A konténerhez dinamikus paramétereket is hozzáadhatunk, ezek eltérő értékei a statikus paraméterekkel ellentétben nem okoznak új DI-konténerek generálását. - -```php -$configurator->addDynamicParameters([ - 'remoteIp' => $_SERVER['REMOTE_ADDR'], -]); -``` - -A környezeti változókat könnyen elérhetővé tehetnénk dinamikus paraméterek segítségével. A konfigurációs fájlokban a `%env.variable%` címen keresztül érhetjük el őket. - -```php -$configurator->addDynamicParameters([ - 'env' => getenv(), -]); -``` - - -Importált szolgáltatások .[#toc-imported-services] --------------------------------------------------- - -Most mélyebbre megyünk. Bár a DI konténer célja az objektumok létrehozása, kivételesen szükség lehet arra, hogy egy meglévő objektumot beillesszünk a konténerbe. Ezt úgy tesszük meg, hogy a szolgáltatást a `imported: true` attribútummal definiáljuk. - -```neon -services: - myservice: - type: App\Model\MyCustomService - imported: true -``` - -Hozzunk létre egy új példányt, és illesszük be a bootstrapbe: - -```php -$configurator->addServices([ - 'myservice' => new App\Model\MyCustomService('foobar'), -]); -``` - - -Különböző környezetek .[#toc-different-environments] -==================================================== - -Nyugodtan testre szabhatja a `Bootstrap` osztályt, hogy megfeleljen az igényeinek. A `boot()` metódushoz paramétereket adhat a webes projektek megkülönböztetéséhez, vagy más metódusokat is hozzáadhat, például a `bootForTests()`, amely inicializálja a környezetet a unit tesztekhez, a `bootForCli()` a parancssorból hívott szkriptekhez, és így tovább. - -```php -public static function bootForTests(): Configurator -{ - $configurator = self::boot(); - Tester\Environment::setup(); // Nette Tester inicializálása - return $configurator; -} -``` diff --git a/application/hu/bootstrapping.texy b/application/hu/bootstrapping.texy new file mode 100644 index 0000000000..0d955175df --- /dev/null +++ b/application/hu/bootstrapping.texy @@ -0,0 +1,297 @@ +Bootstrapping +************* + +
    + +A bootstrapping az alkalmazás környezetének inicializálása, egy dependency injection (DI) konténer létrehozása és az alkalmazás elindítása. A következőkről fogunk beszélni: + +- hogyan inicializálja a Bootstrap osztály a környezetet +- hogyan konfigurálhatók az alkalmazások NEON fájlok használatával +- hogyan különböztessük meg a produkciós és fejlesztői módot +- hogyan hozzuk létre és konfiguráljuk a DI konténert + +
    + + +Az alkalmazások, legyenek azok webesek vagy parancssorból futtatott szkriptek, működésüket valamilyen környezet inicializálási formával kezdik. Régen ezt egy `include.inc.php` nevű fájl intézte, amelyet az elsődleges fájl inkludált. A modern Nette alkalmazásokban ezt a `Bootstrap` osztály váltotta fel, amelyet az alkalmazás részeként az `app/Bootstrap.php` fájlban találhat meg. Például így nézhet ki: + +```php +use Nette\Bootstrap\Configurator; + +class Bootstrap +{ + private Configurator $configurator; + private string $rootDir; + + public function __construct() + { + $this->rootDir = dirname(__DIR__); + // A Configurator felelős az alkalmazás környezetének és szolgáltatásainak beállításáért. + $this->configurator = new Configurator; + // Beállítja a Nette által generált ideiglenes fájlok (pl. fordított sablonok) könyvtárát + $this->configurator->setTempDirectory($this->rootDir . '/temp'); + } + + public function bootWebApplication(): Nette\DI\Container + { + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); + } + + private function initializeEnvironment(): void + { + // A Nette okos, és a fejlesztői mód automatikusan bekapcsolódik, + // vagy engedélyezheti egy adott IP-címre a következő sor kommentjének eltávolításával: + // $this->configurator->setDebugMode('secret@23.75.345.200'); + + // Aktiválja a Tracy-t: a végső "svájci bicska" a debuggoláshoz. + $this->configurator->enableTracy($this->rootDir . '/log'); + + // RobotLoader: automatikusan betölti az összes osztályt a kiválasztott könyvtárban + $this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); + } + + private function setupContainer(): void + { + // Betölti a konfigurációs fájlokat + $this->configurator->addConfig($this->rootDir . '/config/common.neon'); + } +} +``` + + +index.php +========= + +A webalkalmazások esetében az elsődleges fájl az `index.php`, amely a [nyilvános könyvtárban |directory-structure#Nyilvános könyvtár www] (`www/`) található. Ez a Bootstrap osztálytól kéri a környezet inicializálását és a DI konténer létrehozását. Ezután ebből szerzi be az `Application` szolgáltatást, amely elindítja a webalkalmazást: + +```php +$bootstrap = new App\Bootstrap; +// Környezet inicializálása + DI konténer létrehozása +$container = $bootstrap->bootWebApplication(); +// A DI konténer létrehozza a Nette\Application\Application objektumot +$application = $container->getByType(Nette\Application\Application::class); +// A Nette alkalmazás elindítása és a bejövő kérés feldolgozása +$application->run(); +``` + +Mint látható, a környezet beállításában és a dependency injection (DI) konténer létrehozásában a [api:Nette\Bootstrap\Configurator] osztály segít, amelyet most részletesebben bemutatunk. + + +Fejlesztői vs éles mód +====================== + +A Nette eltérően viselkedik attól függően, hogy fejlesztői vagy éles szerveren fut: + +🛠️ Fejlesztői mód (Development): + - Megjeleníti a Tracy debugbart hasznos információkkal (SQL lekérdezések, végrehajtási idő, felhasznált memória) + - Hiba esetén részletes hibaoldalt jelenít meg a függvényhívásokkal és a változók tartalmával + - Automatikusan frissíti a cache-t a Latte sablonok módosításakor, a konfigurációs fájlok szerkesztésekor stb. + + +🚀 Éles mód (Production): + - Nem jelenít meg semmilyen debuggolási információt, minden hibát a logba ír + - Hiba esetén az ErrorPresentert vagy egy általános "Server Error" oldalt jelenít meg + - A cache soha nem frissül automatikusan! + - Optimalizálva a sebességre és a biztonságra + + +A mód kiválasztása automatikus felismeréssel történik, így általában nincs szükség semmit konfigurálni vagy manuálisan átváltani: + +- fejlesztői mód: localhoston (IP-cím `127.0.0.1` vagy `::1`), ha nincs proxy (azaz annak HTTP fejléce) +- éles mód: mindenhol máshol + +Ha a fejlesztői módot más esetekben is engedélyezni szeretnénk, például egy adott IP-címről hozzáférő programozók számára, használjuk a `setDebugMode()` metódust: + +```php +$this->configurator->setDebugMode('23.75.345.200'); // IP-címek tömbje is megadható +``` + +Határozottan javasoljuk az IP-cím és a cookie kombinálását. A `nette-debug` cookie-ba mentsünk el egy titkos tokent, pl. `secret1234`, és így aktiváljuk a fejlesztői módot az adott IP-címről hozzáférő és a cookie-ban említett tokennel rendelkező programozók számára: + +```php +$this->configurator->setDebugMode('secret1234@23.75.345.200'); +``` + +A fejlesztői módot teljesen ki is kapcsolhatjuk, még localhostra is: + +```php +$this->configurator->setDebugMode(false); +``` + +Figyelem, a `true` érték véglegesen bekapcsolja a fejlesztői módot, ami soha nem történhet meg éles szerveren. + + +Tracy debuggoló eszköz +====================== + +A könnyű debuggolás érdekében kapcsoljuk be a nagyszerű [Tracy |tracy:] eszközt. Fejlesztői módban vizualizálja a hibákat, éles módban pedig a hibákat a megadott könyvtárba logolja: + +```php +$this->configurator->enableTracy($this->rootDir . '/log'); +``` + + +Ideiglenes fájlok +================= + +A Nette cache-t használ a DI konténerhez, a RobotLoaderhez, a sablonokhoz stb. Ezért szükséges beállítani annak a könyvtárnak az elérési útját, ahová a cache mentésre kerül: + +```php +$this->configurator->setTempDirectory($this->rootDir . '/temp'); +``` + +Linuxon vagy macOS-en állítsa be a `log/` és `temp/` könyvtáraknak az [írási jogokat |nette:troubleshooting#Könyvtárjogosultságok beállítása]. + + +RobotLoader +=========== + +Általában szeretnénk automatikusan betölteni az osztályokat a [RobotLoader |robot-loader:] segítségével, ezért el kell indítanunk, és hagynunk kell, hogy betöltse az osztályokat abból a könyvtárból, ahol a `Bootstrap.php` található (azaz `__DIR__`), és az összes alkönyvtárából: + +```php +$this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); +``` + +Alternatív megközelítés az osztályok betöltésének kizárólag a [Composer |best-practices:composer] segítségével történő engedélyezése a PSR-4 betartása mellett. + + +Időzóna +======= + +A konfigurátoron keresztül beállíthatja az alapértelmezett időzónát. + +```php +$this->configurator->setTimeZone('Europe/Prague'); +``` + + +DI konténer konfigurálása +========================= + +Az indítási folyamat része a DI konténer, vagyis az objektumgyár létrehozása, amely az egész alkalmazás szíve. Ez valójában egy PHP osztály, amelyet a Nette generál és a cache könyvtárba ment. A gyár gyártja az alkalmazás kulcsfontosságú objektumait, és a konfigurációs fájlok segítségével utasítjuk, hogyan hozza létre és állítsa be őket, ezzel befolyásolva az egész alkalmazás viselkedését. + +A konfigurációs fájlokat általában [NEON |neon:format] formátumban írják. Egy külön fejezetben olvashat arról, [mit lehet konfigurálni |nette:configuring]. + +.[tip] +Fejlesztői módban a konténer automatikusan frissül minden kód- vagy konfigurációs fájl módosításakor. Éles módban csak egyszer generálódik, és a változások a maximális teljesítmény érdekében nem kerülnek ellenőrzésre. + +A konfigurációs fájlokat a `addConfig()` segítségével töltjük be: + +```php +$this->configurator->addConfig($this->rootDir . '/config/common.neon'); +``` + +Ha több konfigurációs fájlt szeretnénk hozzáadni, többször is meghívhatjuk az `addConfig()` függvényt. + +```php +$configDir = $this->rootDir . '/config'; +$this->configurator->addConfig($configDir . '/common.neon'); +$this->configurator->addConfig($configDir . '/services.neon'); +if (PHP_SAPI === 'cli') { + $this->configurator->addConfig($configDir . '/cli.php'); +} +``` + +A `cli.php` név nem elírás, a konfiguráció PHP fájlban is megadható, amely tömbként adja vissza. + +További konfigurációs fájlokat is hozzáadhatunk az [`includes` szekcióban |dependency-injection:configuration#Fájlok beillesztése]. + +Ha a konfigurációs fájlokban azonos kulcsokkal rendelkező elemek jelennek meg, azok felülíródnak, vagy [tömbök esetén egyesülnek |dependency-injection:configuration#Összefésülés]. A később beillesztett fájlnak magasabb prioritása van, mint az előzőnek. Annak a fájlnak, amelyben az `includes` szekció szerepel, magasabb prioritása van, mint a benne inkludált fájloknak. + + +Statikus paraméterek +-------------------- + +A konfigurációs fájlokban használt paramétereket definiálhatjuk [a `parameters` szekcióban |dependency-injection:configuration#Paraméterek], és átadhatjuk (vagy felülírhatjuk) az `addStaticParameters()` metódussal (van `addParameters()` aliasa is). Fontos, hogy a paraméterek különböző értékei további DI konténerek, azaz további osztályok generálását eredményezik. + +```php +$this->configurator->addStaticParameters([ + 'projectId' => 23, +]); +``` + +A `projectId` paraméterre a konfigurációban a szokásos `%projectId%` jelöléssel lehet hivatkozni. + + +Dinamikus paraméterek +--------------------- + +A konténerhez dinamikus paramétereket is hozzáadhatunk, amelyek különböző értékei, a statikus paraméterekkel ellentétben, nem okozzák új DI konténerek generálását. + +```php +$this->configurator->addDynamicParameters([ + 'remoteIp' => $_SERVER['REMOTE_ADDR'], +]); +``` + +Így egyszerűen hozzáadhatunk pl. környezeti változókat, amelyekre aztán a konfigurációban a `%env.variable%` jelöléssel lehet hivatkozni. + +```php +$this->configurator->addDynamicParameters([ + 'env' => getenv(), +]); +``` + + +Alapértelmezett paraméterek +--------------------------- + +A konfigurációs fájlokban használhatja ezeket a statikus paramétereket: + +- `%appDir%` az abszolút elérési út a `Bootstrap.php` fájlt tartalmazó könyvtárhoz +- `%wwwDir%` az abszolút elérési út a `index.php` bemeneti fájlt tartalmazó könyvtárhoz +- `%tempDir%` az abszolút elérési út az ideiglenes fájlok könyvtárához +- `%vendorDir%` az abszolút elérési út ahhoz a könyvtárhoz, ahová a Composer telepíti a könyvtárakat +- `%rootDir%` az abszolút elérési út a projekt gyökérkönyvtárához +- `%debugMode%` jelzi, hogy az alkalmazás debug módban van-e +- `%consoleMode%` jelzi, hogy a kérés parancssorból érkezett-e + + +Importált szolgáltatások +------------------------ + +Most mélyebbre megyünk. Bár a DI konténer célja az objektumok gyártása, kivételesen szükség lehet egy meglévő objektum beillesztésére a konténerbe. Ezt úgy tehetjük meg, hogy a szolgáltatást `imported: true` jelzővel definiáljuk. + +```neon +services: + myservice: + type: App\Model\MyCustomService + imported: true +``` + +És a bootstrapban beillesztjük az objektumot a konténerbe: + +```php +$this->configurator->addServices([ + 'myservice' => new App\Model\MyCustomService('foobar'), +]); +``` + + +Eltérő környezet +================ + +Ne féljen módosítani a Bootstrap osztályt saját igényei szerint. A `bootWebApplication()` metódushoz hozzáadhat paramétereket a webprojektek megkülönböztetésére. Vagy kiegészíthetjük további metódusokkal, például `bootTestEnvironment()`, amely inicializálja a környezetet az egységtesztekhez, `bootConsoleApplication()` a parancssorból hívott szkriptekhez stb. + +```php +public function bootTestEnvironment(): Nette\DI\Container +{ + Tester\Environment::setup(); // Nette Tester inicializálása + $this->setupContainer(); + return $this->configurator->createContainer(); +} + +public function bootConsoleApplication(): Nette\DI\Container +{ + $this->configurator->setDebugMode(false); + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); +} +``` diff --git a/application/hu/components.texy b/application/hu/components.texy index 839db3273e..2b99e2c440 100644 --- a/application/hu/components.texy +++ b/application/hu/components.texy @@ -3,27 +3,27 @@ Interaktív komponensek
    -A komponensek különálló, újrafelhasználható objektumok, amelyeket oldalakba helyezünk. Ezek lehetnek űrlapok, adattáblák, közvélemény-kutatások, tulajdonképpen bármi, amit érdemes ismételten használni. Megmutatjuk: +A komponensek önálló, újrafelhasználható objektumok, amelyeket oldalakba illesztünk be. Lehetnek űrlapok, datagrid-ek, szavazások, valójában bármi, amit érdemes ismételten használni. Megmutatjuk: -- Hogyan használjuk a komponenseket? -- hogyan írjuk meg őket? -- Mik azok a jelek? +- hogyan használjuk a komponenseket? +- hogyan írjunk komponenseket? +- mik azok a signálok?
    -A Nette beépített komponensrendszerrel rendelkezik. Az idősebbek talán emlékeznek valami hasonlóra a Delphiből vagy az ASP.NET Web Formsből. A React vagy a Vue.js valami távolról hasonlóra épül. A PHP keretrendszerek világában azonban ez egy teljesen egyedi funkció. +A Nette beépített komponensrendszerrel rendelkezik. Valami hasonlót a Delphi vagy az ASP.NET Web Forms ismerői ismerhetnek, valami távolról hasonlóra épül a React vagy a Vue.js is. Azonban a PHP keretrendszerek világában ez egyedülálló dolog. -Ugyanakkor a komponensek alapvetően megváltoztatják az alkalmazásfejlesztés megközelítését. Előre elkészített egységekből állíthat össze oldalakat. Adattáblára van szüksége az adminisztrációban? Megtalálhatja a [Componette-ben |https://componette.org/search/component], a Nette nyílt forráskódú kiegészítőinek (nem csak komponensek) tárházában, és egyszerűen beillesztheti a prezenterbe. +Eközben a komponensek alapvetően befolyásolják az alkalmazásfejlesztési megközelítést. Az oldalakat előre elkészített egységekből állíthatja össze. Szüksége van egy datagridre az adminisztrációban? Megtalálja a [Componette |https://componette.org/search/component] oldalon, amely a Nette nyílt forráskódú kiegészítőinek (tehát nem csak komponenseknek) a tárolója, és egyszerűen beillesztheti a presenterbe. -Bármennyi komponenst beépíthet a prezenterbe. Néhány komponensbe pedig más komponenseket is beilleszthet. Ezáltal egy komponensfa jön létre, amelynek gyökere a prezenter. +A presenterbe tetszőleges számú komponenst beépíthet. És néhány komponensbe további komponenseket is beilleszthet. Így egy komponensfa jön létre, amelynek gyökere a presenter. -Gyári módszerek .[#toc-factory-methods] -======================================= +Factory metódusok +================= -Hogyan kerülnek a komponensek elhelyezésre és későbbi használatra a prezenterben? Általában gyári metódusok segítségével. +Hogyan illesztjük be és használjuk a komponenseket a presenterben? Általában factory metódusok segítségével. -A komponens gyár egy elegáns módja annak, hogy csak akkor hozzunk létre komponenseket, amikor valóban szükség van rájuk (lazy / on-demand). Az egész varázslat egy metódus megvalósításában rejlik, melynek neve `createComponent()`, ahol `` a komponens neve, amely létrehozza és visszaadja. +A komponens factory elegáns módja annak, hogy a komponenseket csak akkor hozzuk létre, amikor valóban szükség van rájuk (lazy / on demand). Az egész varázslat egy `createComponent()` nevű metódus implementálásában rejlik, ahol `` a létrehozandó komponens neve, és amely létrehozza és visszaadja a komponenst. ```php .{file:DefaultPresenter.php} class DefaultPresenter extends Nette\Application\UI\Presenter @@ -37,43 +37,43 @@ class DefaultPresenter extends Nette\Application\UI\Presenter } ``` -Mivel minden komponens külön metódusban jön létre, a kód tisztább és könnyebben olvasható. +Annak köszönhetően, hogy minden komponens külön metódusban jön létre, a kód áttekinthetőbbé válik. .[note] -A komponensek nevei mindig kisbetűvel kezdődnek, bár a metódusnévben nagybetűvel kezdődnek. +A komponensek nevei mindig kisbetűvel kezdődnek, annak ellenére, hogy a metódus nevében nagybetűvel íródnak. -A gyárakat sosem hívjuk közvetlenül, automatikusan meghívódnak, amikor először használjuk a komponenseket. Ennek köszönhetően egy komponens a megfelelő pillanatban jön létre, és csak akkor, ha valóban szükség van rá. Ha nem használnánk a komponenst (például valamilyen AJAX kérésnél, ahol az oldalnak csak egy részét adjuk vissza, vagy amikor a részeket gyorsítótárba helyezzük), akkor nem is jön létre, és ezzel megkíméljük a szerver teljesítményét. +A factory-kat soha nem hívjuk meg közvetlenül, maguktól hívódnak meg, amikor először használjuk a komponenst. Ennek köszönhetően a komponens a megfelelő pillanatban jön létre, és csak akkor, ha valóban szükség van rá. Ha nem használjuk a komponenst (például egy AJAX kérésnél, amikor csak az oldal egy része kerül átvitelre, vagy a sablon cache-elésekor), egyáltalán nem jön létre, és megspóroljuk a szerver teljesítményét. ```php .{file:DefaultPresenter.php} -// elérjük a komponenst, és ha ez volt az első alkalom, -// meghívja a createComponentPoll() funkciót a létrehozásához. +// hozzáférünk a komponenshez, és ha ez volt az első alkalom, +// meghívódik a createComponentPoll(), amely létrehozza $poll = $this->getComponent('poll'); // alternatív szintaxis: $poll = $this['poll']; ``` -A sablonban a [{control} |#Rendering] címkével renderelhetünk egy komponenst. Így nincs szükség a komponensek manuális átadására a sablonhoz. +A sablonban a komponenst a [{control} |#Renderelés] tag segítségével lehet renderelni. Ezért nincs szükség a komponensek manuális átadására a sablonnak. ```latte -

    Please Vote

    +

    Szavazzon

    {control poll} ``` -Hollywood stílus .[#toc-hollywood-style] -======================================== +Hollywood style +=============== -Az alkatrészek általában egy menő technikát használnak, amit mi hollywoodi stílusnak hívunk. Bizonyára ismered a közhelyet, amit a színészek gyakran hallanak a castingokon: "Ne hívjatok minket, majd mi hívunk titeket". És erről van szó. +A komponensek általában egy friss technikát használnak, amit szeretünk Hollywood style-nak nevezni. Biztosan ismeri a szállóigévé vált mondatot, amit a filmes meghallgatások résztvevői oly gyakran hallanak: „Ne hívjon minket, mi majd hívjuk önt”. És pontosan erről van szó. -A Nette-ben ahelyett, hogy állandóan kérdéseket tennél fel ("elküldted az űrlapot?", "érvényes volt?" vagy "megnyomta valaki ezt a gombot?"), azt mondod a keretrendszernek, hogy "ha ez történik, hívd ezt a módszert", és hagyd rajta a további munkát. Ha JavaScriptben programozol, ismered ezt a programozási stílust. Olyan függvényeket írsz, amelyeket akkor hívsz meg, amikor egy bizonyos esemény bekövetkezik. A motor pedig átadja nekik a megfelelő paramétereket. +A Nette-ben ugyanis ahelyett, hogy állandóan kérdezgetnie kellene („elküldték az űrlapot?”, „érvényes volt?” vagy „megnyomta a felhasználó ezt a gombot?”), azt mondja a keretrendszernek, „amikor ez megtörténik, hívd meg ezt a metódust”, és a további munkát ráhagyja. Ha JavaScriptben programozik, ezt a programozási stílust jól ismeri. Olyan függvényeket ír, amelyek akkor hívódnak meg, amikor egy bizonyos esemény bekövetkezik. És a nyelv átadja nekik a megfelelő paramétereket. -Ez teljesen megváltoztatja az alkalmazások írásának módját. Minél több feladatot delegálhatsz a keretrendszerre, annál kevesebb munkád marad. És annál kevesebbet tudsz elfelejteni. +Ez teljesen megváltoztatja az alkalmazások írásáról alkotott képet. Minél több feladatot bízhat a keretrendszerre, annál kevesebb munkája van Önnek. És annál kevesebb dolgot hagyhat ki esetleg. -Hogyan írjunk komponenst .[#toc-how-to-write-a-component] -========================================================= +Komponens írása +=============== -A komponens alatt általában a [api:Nette\Application\UI\Control] osztály leszármazottait értjük. Maga a bemutató [api:Nette\Application\UI\Presenter] is a `Control` osztály leszármazottja. +Komponens alatt általában a [api:Nette\Application\UI\Control] osztály leszármazottját értjük. (Pontosabb lenne tehát a „controls” kifejezést használni, de a „kontrolloknak” a magyarban teljesen más jelentése van, és inkább a „komponensek” terjedtek el.) Maga a presenter [api:Nette\Application\UI\Presenter] egyébként szintén a `Control` osztály leszármazottja. ```php .{file:PollControl.php} use Nette\Application\UI\Control; @@ -84,22 +84,22 @@ class PollControl extends Control ``` -A megjelenítése .[#toc-rendering] -================================== +Renderelés +========== -Azt már tudjuk, hogy a `{control componentName}` címkét egy komponens megrajzolására használjuk. Valójában a komponens `render()` metódusát hívja meg, amelyben a renderelésről gondoskodunk. A prezenterhez hasonlóan van egy [Latte |latte:] sablonunk a `$this->template` változóban, amelynek átadjuk a paramétereket. A prezenterben való használattal ellentétben itt meg kell adnunk egy sablonfájlt, és hagynunk kell, hogy renderelje: +Már tudjuk, hogy a komponens renderelésére a `{control componentName}` tag szolgál. Ez valójában a komponens `render()` metódusát hívja meg, amelyben gondoskodunk a renderelésről. Rendelkezésünkre áll, ugyanúgy, mint a presenterben, egy [Latte sablon|templates] a `$this->template` változóban, amelynek paramétereket adunk át. A presentertől eltérően itt meg kell adnunk a sablonfájlt, és hagynunk kell, hogy renderelje: ```php .{file:PollControl.php} public function render(): void { - // néhány paramétert teszünk a sablonba + // beillesztünk néhány paramétert a sablonba $this->template->param = $value; - // és lerajzoljuk + // és rendereljük $this->template->render(__DIR__ . '/poll.latte'); } ``` -A `{control}` címke segítségével paramétereket adhatunk át a `render()` metódusnak: +A `{control}` tag lehetővé teszi paraméterek átadását a `render()` metódusnak: ```latte {control poll $id, $message} @@ -112,7 +112,7 @@ public function render(int $id, string $message): void } ``` -Néha egy komponens több részből állhat, amelyeket külön-külön szeretnénk megjeleníteni. Mindegyikhez saját renderelési metódust hozunk létre, itt van például a `renderPaginator()`: +Néha egy komponens több részből állhat, amelyeket külön szeretnénk renderelni. Mindegyikhez létrehozunk egy saját renderelő metódust, itt a példában például `renderPaginator()`: ```php .{file:PollControl.php} public function renderPaginator(): void @@ -121,107 +121,107 @@ public function renderPaginator(): void } ``` -És a sablonban aztán meghívjuk a következővel: +És a sablonban ezt a következőképpen hívjuk meg: ```latte {control poll:paginator} ``` -A jobb megértés érdekében jó tudni, hogy a címke hogyan fordítódik PHP kóddá. +A jobb megértés érdekében jó tudni, hogyan fordítódik le ez a tag PHP-ra. ```latte {control poll} {control poll:paginator 123, 'hello'} ``` -Ez a következőre fordítódik: +lefordítva: ```php $control->getComponent('poll')->render(); $control->getComponent('poll')->renderPaginator(123, 'hello'); ``` -`getComponent()` `poll` komponenst adja vissza, majd a `render()` vagy a `renderPaginator()` metódust hívja meg rajta. +A `getComponent()` metódus visszaadja a `poll` komponenst, és ezen a komponensen hívja meg a `render()` metódust, illetve a `renderPaginator()` metódust, ha a tag-ben a kettőspont után más renderelési mód van megadva. .[caution] -Ha a paraméterrészben bárhol **`=>`** használatos, akkor az összes paramétert egy tömbbe csomagoljuk, és első argumentumként adjuk át: +Figyelem, ha bárhol a paraméterek között **`=>`** jelenik meg, az összes paraméter egy tömbbe lesz csomagolva és az első argumentumként kerül átadásra: ```latte {control poll, id: 123, message: 'hello'} ``` -fordítja: +lefordítva: ```php $control->getComponent('poll')->render(['id' => 123, 'message' => 'hello']); ``` -Az alkomponens renderelése: +Alkomponens renderelése: ```latte {control cartControl-someForm} ``` -fordít: +lefordítva: ```php $control->getComponent("cartControl-someForm")->render(); ``` -A komponensek, például az előadók, automatikusan átadnak számos hasznos változót a sablonoknak: +A komponensek, akárcsak a presenterek, automatikusan átadnak néhány hasznos változót a sablonoknak: -- `$basePath` egy abszolút URL elérési útvonal a gyökérkönyvtárhoz (például `/CD-collection`). -- `$baseUrl` egy abszolút URL cím a gyökérkönyvtárhoz (pl. `http://localhost/CD-collection`) -- `$user` egy objektum, [amely a felhasználót képviseli |security:authentication] -- `$presenter` az aktuális bemutató +- `$basePath` az abszolút URL elérési út a gyökérkönyvtárhoz (pl. `/eshop`) +- `$baseUrl` az abszolút URL a gyökérkönyvtárhoz (pl. `http://localhost/eshop`) +- `$user` a [felhasználót reprezentáló |security:authentication] objektum +- `$presenter` az aktuális presenter - `$control` az aktuális komponens -- `$flashes` a módszer által küldött [üzenetek |#flash-messages] listája `flashMessage()` +- `$flashes` a `flashMessage()` függvénnyel küldött [üzenetek |#Flash üzenetek] tömbje -Signal .[#toc-signal] -===================== +Signal +====== -Azt már tudjuk, hogy a Nette alkalmazásban a navigáció a `Presenter:action` párokra való hivatkozásból vagy átirányításból áll. De mi van akkor, ha csak egy műveletet szeretnénk végrehajtani az **aktuális oldalon**? Például a táblázat oszlopának rendezési sorrendjét megváltoztatni; elemet törölni; világos/sötét üzemmódot váltani; űrlapot elküldeni; szavazni a szavazáson; stb. +Már tudjuk, hogy a Nette alkalmazásban a navigáció linkekre vagy átirányításokra épül `Presenter:action` párokra. De mi van akkor, ha csak egy műveletet szeretnénk végrehajtani az **aktuális oldalon**? Például megváltoztatni az oszlopok sorrendjét egy táblázatban; törölni egy elemet; váltani világos/sötét mód között; elküldeni egy űrlapot; szavazni egy szavazáson; stb. -Az ilyen típusú kérést jelnek nevezzük. És az akciókhoz hasonlóan metódusokat hívnak meg `action()` vagy `render()`a jelek hívják a módszereket `handle()`. Míg az akció (vagy nézet) fogalma csak a prezenterekre vonatkozik, a jelek minden komponensre. És ezért a prezenterekre is, mivel a `UI\Presenter` a `UI\Control` leszármazottja. +Az ilyen típusú kéréseket signáloknak nevezzük. És ahogy az akciók a `action()` vagy `render()` metódusokat hívják meg, a signálok a `handle()` metódusokat hívják meg. Míg az akció (vagy view) fogalma tisztán csak a presenterekhez kapcsolódik, a signálok minden komponensre vonatkoznak. És így a presenterekre is, mivel az `UI\Presenter` az `UI\Control` leszármazottja. ```php public function handleClick(int $x, int $y): void { - // ... a jel feldolgozása ... + // ... signál feldolgozása ... } ``` -A jelet hívó link a szokásos módon jön létre, azaz a sablonban a `n:href` attribútummal vagy a `{link}` címkével, a kódban a `link()` metódussal. Bővebben az [URL-linkek létrehozása |creating-links#Links to Signal] című fejezetben. +A signált meghívó linket a szokásos módon hozzuk létre, azaz a sablonban az `n:href` attribútummal vagy a `{link}` taggel, a kódban pedig a `link()` metódussal. További információk az [URL linkek létrehozása |creating-links#Linkek signálhoz] fejezetben. ```latte -click here +kattints ide ``` -A jel mindig az aktuális prezenteren és nézeten kerül meghívásra, így nem lehetséges a jelre más prezenteren/akcióban linkelni. +A signál mindig az aktuális presenteren és action-ön hívódik meg, nem lehet másik presenteren vagy másik action-ön meghívni. -A jel tehát az oldal újratöltését pontosan ugyanúgy okozza, mint az eredeti kérésnél, csak ezen felül a megfelelő paraméterekkel hívja meg a jelkezelő metódust. Ha a metódus nem létezik, akkor a [api:Nette\Application\UI\BadSignalException] kivétel kerül dobásra, amely a felhasználó számára a 403 Forbidden hibalevélként jelenik meg. +A signál tehát az oldal újratöltését okozza, ugyanúgy, mint az eredeti kérésnél, csak emellett meghívja a signál kezelő metódusát a megfelelő paraméterekkel. Ha a metódus nem létezik, [api:Nette\Application\UI\BadSignalException] kivétel dobódik, amely a felhasználónak 403 Forbidden hibaoldalként jelenik meg. -Snippetek és AJAX .[#toc-snippets-and-ajax] -=========================================== +Snippetek és AJAX +================= -A jelek egy kicsit emlékeztethetnek az AJAX-ra: az aktuális oldalon meghívott kezelők. És igazad van, a szignálokat valóban gyakran hívjuk meg AJAX segítségével, és akkor csak az oldal megváltozott részeit továbbítjuk a böngészőnek. Ezeket hívják snippeteknek. További információ az [AJAX-ről szóló oldalon |ajax] található. +A signálok talán egy kicsit emlékeztetnek az AJAX-ra: handlerek, amelyek az aktuális oldalon hívódnak meg. És igaza van, a signálokat valóban gyakran AJAX segítségével hívják meg, és utána csak az oldal megváltozott részeit továbbítjuk a böngészőbe. Vagyis az ún. snippeteket. További információkat talál az [AJAX-nak szentelt oldalon |ajax]. -Flash üzenetek .[#toc-flash-messages] -===================================== +Flash üzenetek +============== -Egy komponensnek saját, a prezentertől független tárolója van a flash üzeneteknek. Ezek olyan üzenetek, amelyek például a művelet eredményéről tájékoztatnak. A flash-üzenetek fontos jellemzője, hogy a sablonban az átirányítás után is rendelkezésre állnak. A megjelenítés után is még 30 másodpercig életben maradnak - például abban az esetben, ha a felhasználó véletlenül frissítené az oldalt - az üzenet nem vész el. +A komponensnek saját flash üzenet tárolója van, amely független a presentertől. Ezek olyan üzenetek, amelyek pl. egy művelet eredményéről tájékoztatnak. A flash üzenetek fontos jellemzője, hogy a sablonban átirányítás után is elérhetők. Megjelenítésük után még további 30 másodpercig élnek – például arra az esetre, ha a felhasználó hibás átvitel miatt frissítené az oldalt - az üzenet tehát nem tűnik el azonnal. -A küldés a [flashMessage |api:Nette\Application\UI\Control::flashMessage()] metódussal történik. Az első paraméter az üzenet szövege vagy az üzenetet reprezentáló `stdClass` objektum. A választható második paraméter az üzenet típusa (hiba, figyelmeztetés, info stb.). A `flashMessage()` metódus a flash message egy példányát adja vissza stdClass objektumként, amelyhez információkat adhatunk át. +A küldést a [flashMessage |api:Nette\Application\UI\Control::flashMessage()] metódus végzi. Az első paraméter az üzenet szövege vagy egy `stdClass` objektum, amely az üzenetet reprezentálja. A nem kötelező második paraméter a típusa (error, warning, info stb.). A `flashMessage()` metódus visszaadja a flash üzenet példányát `stdClass` objektumként, amelyhez további információkat lehet hozzáadni. ```php -$this->flashMessage('Az elemet törölték.'); -$this->redirect(/* ... */); // és átirányítás +$this->flashMessage('Az elem törölve lett.'); +$this->redirect(/* ... */); // és átirányítunk ``` -A sablonban ezek az üzenetek a `$flashes` változóban állnak rendelkezésre, mint a `stdClass` objektumok, amelyek tartalmazzák a `message` (üzenet szövege), `type` (üzenet típusa) tulajdonságokat, és tartalmazhatják a már említett felhasználói információkat. Ezeket a következőképpen rajzoljuk meg: +A sablonban ezek az üzenetek a `$flashes` változóban érhetők el `stdClass` objektumokként, amelyek tartalmazzák a `message` (üzenet szövege), `type` (üzenet típusa) tulajdonságokat, és tartalmazhatják a már említett felhasználói információkat is. Például így rendereljük őket: ```latte {foreach $flashes as $flash} @@ -230,44 +230,66 @@ A sablonban ezek az üzenetek a `$flashes` változóban állnak rendelkezésre, ``` -Állandó paraméterek .[#toc-persistent-parameters] -================================================= +Átirányítás signál után +======================= -A tartós paraméterek a komponensek állapotának különböző kérések közötti fenntartására szolgálnak. Értékük a linkre való kattintás után is ugyanaz marad. A munkamenetadatokkal ellentétben ezek az URL-ben kerülnek átvitelre. És automatikusan továbbításra kerülnek, beleértve az ugyanazon az oldalon lévő más komponensekben létrehozott linkeket is. +A komponensek signáljának feldolgozása után gyakran átirányítás következik. Ez hasonló helyzet, mint az űrlapoknál - elküldésük után is átirányítunk, hogy a böngészőben az oldal frissítésekor ne küldődjenek újra az adatok. -Például van egy tartalom lapozó komponens. Egy oldalon több ilyen komponens is lehet. És azt szeretné, hogy minden komponens az aktuális oldalon maradjon, amikor a linkre kattint. Ezért az oldalszámot (`page`) állandó paraméterré tesszük. +```php +$this->redirect('this') // átirányít az aktuális presenter-re és action-re +``` + +Mivel a komponens egy újrafelhasználható elem, és általában nem kellene, hogy közvetlen kapcsolata legyen konkrét presenterekkel, a `redirect()` és `link()` metódusok automatikusan komponens signálként értelmezik a paramétert: + +```php +$this->redirect('click') // átirányít ugyanazon komponens 'click' signáljára +``` + +Ha másik presenter-re vagy akcióra kell átirányítani, ezt a presenteren keresztül teheti meg: + +```php +$this->getPresenter()->redirect('Product:show'); // átirányít másik presenter/action-re +``` + + +Perzisztens paraméterek +======================= + +A perzisztens paraméterek a komponensek állapotának megőrzésére szolgálnak a különböző kérések között. Értékük ugyanaz marad a linkre kattintás után is. A session adatokkal ellentétben az URL-ben kerülnek átvitelre. És ez teljesen automatikusan történik, beleértve az ugyanazon az oldalon lévő más komponensekben létrehozott linkeket is. + +Például van egy komponensünk a tartalom lapozásához. Ilyen komponensekből több is lehet az oldalon. És azt szeretnénk, hogy egy linkre kattintás után minden komponens az aktuális oldalán maradjon. Ezért az oldalszámból (`page`) perzisztens paramétert csinálunk. -A perzisztens paraméter létrehozása rendkívül egyszerű a Nette-ben. Csak hozzon létre egy nyilvános tulajdonságot, és címkézze meg az attribútummal: (korábban a `/** @persistent */` volt használatos). +Perzisztens paraméter létrehozása a Nette-ben rendkívül egyszerű. Csak létre kell hozni egy public property-t és megjelölni egy attribútummal: (korábban a `/** @persistent */` volt használatos) ```php -use Nette\Application\Attributes\Persistent; // ez a sor fontos +use Nette\Application\Attributes\Persistent; // ez a sor fontos class PaginatingControl extends Control { #[Persistent] - public int $page = 1; // nyilvánosnak kell lennie + public int $page = 1; // public-nak kell lennie } ``` -Javasoljuk, hogy a tulajdonsághoz adja meg az adattípust (pl. `int`), és alapértelmezett értéket is megadhat. A paraméterek értékei [érvényesíthetők |#Validation of Persistent Parameters]. +A property-nél javasoljuk az adattípus megadását (pl. `int`), és megadhat alapértelmezett értéket is. A paraméterek értékeit lehet [validálni |#Perzisztens paraméterek validálása]. -A tartós paraméterek értékét a hivatkozás létrehozásakor módosíthatja: +Link létrehozásakor a perzisztens paraméter értékét meg lehet változtatni: ```latte -next +következő ``` -Vagy *visszaállítható*, azaz eltávolítható az URL-ből. Ekkor az alapértelmezett értéket veszi fel: +Vagy *resetelhető*, azaz eltávolítható az URL-ből. Ekkor az alapértelmezett értékét veszi fel: ```latte reset ``` -Tartós komponensek .[#toc-persistent-components] -================================================ +Perzisztens komponensek +======================= -Nem csak a paraméterek, hanem a komponensek is lehetnek állandóak. A perzisztens paramétereik a különböző műveletek vagy a különböző előadók között is átvihetők. A perzisztens komponenseket ezzel a megjegyzésekkel jelöljük a prezenter osztály számára. Itt például a `calendar` és a `poll` komponenseket jelöljük a következőképpen: +Nemcsak a paraméterek, hanem a komponensek is lehetnek perzisztensek. Egy ilyen komponens perzisztens paraméterei átkerülnek a presenter különböző akciói között vagy több presenter között is. A perzisztens komponenseket annotációval jelöljük a presenter osztályánál. Például így jelöljük a `calendar` és `poll` komponenseket: ```php /** @@ -278,9 +300,9 @@ class DefaultPresenter extends Nette\Application\UI\Presenter } ``` -Az alkomponenseket nem kell tartósnak jelölni, azok automatikusan tartósak. +Az ezekben a komponensekben lévő alkomponenseket nem kell jelölni, azok is perzisztensekké válnak. -A PHP 8-ban attribútumokat is használhat a tartós komponensek jelölésére: +PHP 8-ban attribútumokat is használhat a perzisztens komponensek jelölésére: ```php use Nette\Application\Attributes\Persistent; @@ -292,35 +314,35 @@ class DefaultPresenter extends Nette\Application\UI\Presenter ``` -Függőségi komponensek .[#toc-components-with-dependencies] -========================================================== +Komponensek függőségekkel +========================= -Hogyan hozhatunk létre függőségekkel rendelkező komponenseket anélkül, hogy "összezavarnánk" az őket használó prezentereket? A Nette DI konténerének okos funkcióinak köszönhetően, a hagyományos szolgáltatások használatához hasonlóan, a munka nagy részét a keretrendszerre bízhatjuk. +Hogyan hozzunk létre komponenseket függőségekkel anélkül, hogy „beszennyeznénk” azokat a presentereket, amelyek használni fogják őket? A Nette DI konténerének okos tulajdonságainak köszönhetően, ugyanúgy, mint a klasszikus szolgáltatások használatakor, a munka nagy részét a keretrendszerre bízhatjuk. -Vegyünk példaként egy olyan komponenst, amely függőséggel rendelkezik a `PollFacade` szolgáltatástól: +Vegyünk példaként egy komponenst, amelynek függősége van a `PollFacade` szolgáltatásra: ```php class PollControl extends Control { public function __construct( - private int $id, // Annak a közvélemény-kutatásnak az azonosítója, amelyhez a komponens létrejön. + private int $id, // Annak a szavazásnak az ID-ja, amelyhez komponenst hozunk létre private PollFacade $facade, ) { } public function handleVote(int $voteId): void { - $this->facade->vote($id, $voteId); + $this->facade->vote($this->id, $voteId); // ... } } ``` -Ha egy klasszikus szolgáltatást írnánk, akkor nem kellene aggódnunk. A DI konténer láthatatlanul gondoskodna az összes függőség átadásáról. De mi általában úgy kezeljük a komponenseket, hogy közvetlenül a prezenterben hozunk létre egy új példányt belőlük [gyári metódusokban |#factory methods] `createComponent...()`. De az összes komponens összes függőségének átadása a prezentálónak, hogy aztán átadjuk őket a komponenseknek, nehézkes. És a megírt kód mennyisége... +Ha klasszikus szolgáltatást írnánk, nem lenne mit megoldani. Az összes függőség átadásáról láthatatlanul gondoskodna a DI konténer. De a komponensekkel általában úgy bánunk, hogy új példányukat közvetlenül a presenterben hozzuk létre a [factory metódusokban |#Factory metódusok] `createComponent…()`. De az összes komponens összes függőségét átadni a presenternek, hogy aztán átadjuk a komponenseknek, nehézkes. És mennyi írott kód… -A logikus kérdés az, hogy miért nem regisztráljuk a komponenst klasszikus szolgáltatásként, adjuk át a prezenternek, majd adjuk vissza a `createComponent...()` metódusban? De ez a megközelítés nem megfelelő, mert azt szeretnénk, hogy a komponenst többször is létrehozhassuk. +A logikus kérdés az, hogy miért nem regisztráljuk egyszerűen a komponenst klasszikus szolgáltatásként, adjuk át a presenternek, majd a `createComponent…()` metódusban adjuk vissza? Ez a megközelítés azonban nem megfelelő, mert a komponenst akár többször is szeretnénk létrehozni. -A helyes megoldás az, ha írunk egy gyárat a komponenshez, azaz egy olyan osztályt, amely létrehozza nekünk a komponenst: +A helyes megoldás egy factory írása a komponenshez, azaz egy osztály, amely létrehozza nekünk a komponenst: ```php class PollControlFactory @@ -337,17 +359,17 @@ class PollControlFactory } ``` -Most regisztráljuk a szolgáltatásunkat a DI konténer konfigurációjához: +Így regisztráljuk a factory-t a konténerünkbe a konfigurációban: ```neon services: - PollControlFactory ``` -Végül ezt a gyárat fogjuk használni a prezenterünkben: +és végül használjuk a presenterünkben: ```php -class PollPresenter extends Nette\UI\Application\Presenter +class PollPresenter extends Nette\Application\UI\Presenter { public function __construct( private PollControlFactory $pollControlFactory, @@ -356,13 +378,13 @@ class PollPresenter extends Nette\UI\Application\Presenter protected function createComponentPollControl(): PollControl { - $pollId = 1; // átadhatjuk a paraméterünket. + $pollId = 1; // átadhatjuk a paraméterünket return $this->pollControlFactory->create($pollId); } } ``` -A nagyszerű dolog az, hogy a Nette DI képes ilyen egyszerű gyárakat [generálni |dependency-injection:factory], így az egész kód megírása helyett csak az interfészét kell megírni: +Nagyszerű, hogy a Nette DI ilyen egyszerű factory-kat tud [generálni |dependency-injection:factory], így a teljes kód helyett elegendő csak az interfészét megírni: ```php interface PollControlFactory @@ -371,21 +393,21 @@ interface PollControlFactory } ``` -Ennyi. A Nette belsőleg megvalósítja ezt az interfészt, és befecskendezi a prezenterünkbe, ahol használhatjuk. A `$id` paraméterünket és a `PollFacade` osztály példányát is varázslatosan átadja a komponensünkbe. +És ez minden. A Nette belsőleg implementálja ezt az interfészt és átadja a presenternek, ahol már használhatjuk is. Mágikusan hozzáadja a komponensünkhöz az `$id` paramétert és a `PollFacade` osztály példányát is. -A komponensek mélysége .[#toc-components-in-depth] -================================================== +Komponensek mélységében +======================= -A komponensek egy Nette alkalmazásban a webalkalmazás újrafelhasználható részei, amelyeket oldalakba ágyazunk be, ez a fejezet témája. Pontosan milyen képességekkel rendelkezik egy ilyen komponens? +A Nette Application komponensei újrafelhasználható részei a webalkalmazásnak, amelyeket oldalakba illesztünk, és amelyekkel egyébként ez az egész fejezet foglalkozik. Milyen képességekkel rendelkezik pontosan egy ilyen komponens? -1) egy sablonban megjeleníthető. -2) tudja, hogy egy [AJAX-kérés |ajax#invalidation] során melyik részét kell renderelni (snippet) -3) képes az állapotát egy URL-ben tárolni (állandó paraméterek). -4) képes reagálni a felhasználói műveletekre (jelek) -5) hierarchikus struktúrát hoz létre (ahol a gyökér a bemutató) +1) renderelhető a sablonban +2) tudja, [melyik részét |ajax#Snippetek] kell renderelni AJAX kérés esetén (snippetek) +3) képes az állapotát az URL-ben tárolni (perzisztens paraméterek) +4) képes reagálni a felhasználói műveletekre (signálok) +5) hierarchikus struktúrát hoz létre (ahol a gyökér a presenter) -Mindegyik funkciót az öröklési vonal valamelyik osztálya kezeli. A megjelenítést (1 + 2) a [api:Nette\Application\UI\Control], az [életciklusba |presenters#life-cycle-of-presenter] való beillesztést (3, 4) a [api:Nette\Application\UI\Component] osztály, a hierarchikus struktúra létrehozását (5) pedig a [Container és a Component |component-model:] osztályok kezelik. +Ezeknek a funkcióknak mindegyikét az öröklési lánc valamelyik osztálya látja el. A renderelést (1 + 2) a [api:Nette\Application\UI\Control] osztály intézi, az [életciklusba |presenters#Presenter életciklusa] való beilleszkedést (3, 4) a [api:Nette\Application\UI\Component] osztály, a hierachikus struktúra létrehozását (5) pedig a [Container és Component |component-model:] osztályok: ``` Nette\ComponentModel\Component { IComponent } @@ -400,18 +422,18 @@ Nette\ComponentModel\Component { IComponent } ``` -A komponens életciklusa .[#toc-life-cycle-of-component] -------------------------------------------------------- +Komponens életciklusa +--------------------- -[* lifecycle-component.svg *] *** *A komponens életciklusa* .<> +[* lifecycle-component.svg *] *** *Komponens életciklusa* .<> -A tartós paraméterek validálása .[#toc-validation-of-persistent-parameters] ---------------------------------------------------------------------------- +Perzisztens paraméterek validálása +---------------------------------- -Az URL-ekből kapott [állandó paraméterek |#persistent parameters] értékeit a `loadState()` módszer írja a tulajdonságokba. A módszer azt is ellenőrzi, hogy a tulajdonsághoz megadott adattípus megfelel-e, ellenkező esetben 404-es hibával válaszol, és az oldal nem jelenik meg. +Az URL-ből kapott [#perzisztens paraméterek] értékeit a `loadState()` metódus írja be a property-kbe. Ez ellenőrzi azt is, hogy megfelelnek-e a property-nél megadott adattípusnak, különben 404-es hibával válaszol, és az oldal nem jelenik meg. -Soha ne bízzunk vakon a perzisztens paraméterekben, mert azokat a felhasználó könnyen felülírhatja az URL-ben. Például így ellenőrizzük, hogy a `$this->page` oldalszám nagyobb-e 0-nál. Erre jó megoldás a fent említett `loadState()` metódus felülírása: +Soha ne bízzon vakon a perzisztens paraméterekben, mert azokat a felhasználó könnyen felülírhatja az URL-ben. Így például ellenőrizzük, hogy az oldalszám `$this->page` nagyobb-e 0-nál. Megfelelő módszer az említett `loadState()` metódus felülírása: ```php class PaginatingControl extends Control @@ -421,8 +443,8 @@ class PaginatingControl extends Control public function loadState(array $params): void { - parent::loadState($params); // itt van beállítva a $this->page - // követi a felhasználói értékek ellenőrzését: + parent::loadState($params); // itt állítódik be a $this->page + // következik a saját értékellenőrzés: if ($this->page < 1) { $this->error(); } @@ -430,27 +452,27 @@ class PaginatingControl extends Control } ``` -Az ellenkező folyamatot, vagyis az értékek begyűjtését a perzisztens tulajdonságokból a `saveState()` metódus kezeli. +Az ellenkező folyamatot, azaz az értékek összegyűjtését a perzisztens property-kből, a `saveState()` metódus végzi. -Mélységi jelek .[#toc-signals-in-depth] ---------------------------------------- +Signálok mélységében +-------------------- -A jel az eredeti kéréshez hasonlóan az oldal újratöltését okozza (az AJAX kivételével), és meghívja a `signalReceived($signal)` metódust, amelynek alapértelmezett megvalósítása a `Nette\Application\UI\Component` osztályban a `handle{Signal}` szavakból álló metódust próbálja meghívni. A további feldolgozás az adott objektumra támaszkodik. A `Component` leszármazottai (azaz a `Control` és a `Presenter`) objektumok a `handle{Signal}` metódust próbálják meghívni a megfelelő paraméterekkel. +A signál az oldal újratöltését okozza, ugyanúgy, mint az eredeti kérésnél (kivéve, ha AJAX-szal hívják), és meghívja a `signalReceived($signal)` metódust, amelynek alapértelmezett implementációja a `Nette\Application\UI\Component` osztályban megpróbál meghívni egy `handle{signal}` szavakból összetett metódust. A további feldolgozás az adott objektumon múlik. A `Component`-től öröklődő objektumok (azaz a `Control` és a `Presenter`) úgy reagálnak, hogy megpróbálják meghívni a `handle{signal}` metódust a megfelelő paraméterekkel. -Más szóval: a `handle{Signal}` metódus definícióját vesszük, és a kérésben kapott összes paramétert összevetjük a metódus paramétereivel. Ez azt jelenti, hogy a `id` paramétert az URL-ből a `$id`, a `something` a `$something` és így tovább. Ha pedig a módszer nem létezik, a `signalReceived` módszer [kivételt |api:Nette\Application\UI\BadSignalException] dob. +Más szavakkal: veszi a `handle{signal}` függvény definícióját és az összes paramétert, amely a kéréssel érkezett, és az argumentumokhoz név szerint hozzárendeli az URL paramétereit, majd megpróbálja meghívni az adott metódust. Például az `$id` paraméterként az URL `id` paraméterének értékét adja át, a `$something` paraméterként az URL `something` értékét adja át, stb. És ha a metódus nem létezik, a `signalReceived` metódus [kivételt |api:Nette\Application\UI\BadSignalException] dob. -A jelet bármely komponens, bemutató objektum, amely a `SignalReceiver` interfészt valósítja meg, fogadhatja, ha csatlakozik a komponensfához. +Signált bármely komponens, presenter vagy objektum fogadhat, amely implementálja a `SignalReceiver` interfészt és csatlakozik a komponensfához. -A jelek fő vevői a `Presenters` és a `Control` kiterjesztő vizuális komponensek. A jel egy objektum számára jelzi, hogy valamit tennie kell - a felhasználó szavazatát számolja a szavazás, a híreket tartalmazó doboznak ki kell bontakoznia, az űrlapot elküldték és fel kell dolgoznia az adatokat, és így tovább. +A signálok fő fogadói a `Presenterek` és a `Control`-tól öröklődő vizuális komponensek lesznek. A signál jelzésként szolgál az objektum számára, hogy tegyen valamit – a szavazás számolja be a felhasználó szavazatát, a hírek blokkja bontakozzon ki és jelenítsen meg kétszer annyi hírt, az űrlap elküldésre került és dolgozza fel az adatokat, és így tovább. -A jel URL-címét a [Component::link() |api:Nette\Application\UI\Component::link()] metódus segítségével hozzuk létre. A `$destination` paraméterként a `{signal}!` stringet adjuk át, a `$args` pedig egy tömbnyi argumentumot, amelyet a jelkezelőnek szeretnénk átadni. A jelparamétereket az aktuális prezenter/nézet URL-hez csatoljuk. **A `?do` paraméter az URL-ben meghatározza a hívott jelet.** +A signál URL-jét a [Component::link() |api:Nette\Application\UI\Component::link()] metódussal hozzuk létre. A `$destination` paraméterként adjuk át a `{signal}!` stringet, a `$args` paraméterként pedig az argumentumok tömbjét, amelyeket a signálnak szeretnénk átadni. A signál mindig az aktuális presenteren és action-ön hívódik meg az aktuális paraméterekkel, a signál paraméterei csak hozzáadódnak. Ezenkívül rögtön az elején hozzáadódik a **`?do` paraméter, amely meghatározza a signált**. -Formátuma `{signal}` vagy `{signalReceiver}-{signal}`. `{signalReceiver}` a prezenterben lévő komponens neve. Ezért nem lehet kötőjel (pontatlanul kötőjel) a komponensek nevében - ez a komponens és a jel nevének elválasztására szolgál, de több komponens is összeállítható. +Formátuma vagy `{signal}`, vagy `{signalReceiver}-{signal}`. A `{signalReceiver}` a komponens neve a presenterben. Ezért nem lehet kötőjel a komponens nevében – a komponens nevének és a signálnak az elválasztására szolgál, azonban így több komponenst is be lehet ágyazni. -Az [isSignalReceiver() |api:Nette\Application\UI\Presenter::isSignalReceiver()] metódus ellenőrzi, hogy egy komponens (első argumentum) egy jel (második argumentum) vevője-e. A második argumentum elhagyható - ekkor kiderül, hogy a komponens bármely jel vevője-e. Ha a második paraméter a `true`, akkor ellenőrzi, hogy a komponens vagy annak leszármazottai egy jel vevői-e. +A [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] metódus ellenőrzi, hogy a komponens (első argumentum) a signál (második argumentum) fogadója-e. A második argumentumot elhagyhatjuk – ekkor azt vizsgálja, hogy a komponens bármilyen signál fogadója-e. Második paraméterként megadhatunk `true`-t, és ezzel ellenőrizhetjük, hogy nemcsak a megadott komponens a fogadó, hanem bármelyik leszármazottja is. -Bármelyik fázisban a `handle{Signal}` megelőzően a [processSignal() |api:Nette\Application\UI\Presenter::processSignal()] metódus meghívásával manuálisan is lehet jelet végrehajtani, amely a jelek végrehajtásáért vállal felelősséget. Átveszi a vevő komponenst (ha nincs beállítva, akkor maga a prezenter) és elküldi neki a jelet. +Bármely, a `handle{signal}` előtti fázisban manuálisan végrehajthatjuk a signált a [processSignal()|api:Nette\Application\UI\Presenter::processSignal()] metódus meghívásával, amely gondoskodik a signál elintézéséről – veszi a signál fogadójaként meghatározott komponenst (ha nincs megadva signál fogadó, akkor maga a presenter az) és elküldi neki a signált. Példa: @@ -460,4 +482,4 @@ if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, ' } ``` -A jelet idő előtt végrehajtják, és nem hívják meg újra. +Ezzel a signál idő előtt végrehajtódik, és nem fog újra meghívódni. diff --git a/application/hu/configuration.texy b/application/hu/configuration.texy index ccb386fb75..7ac8952ebd 100644 --- a/application/hu/configuration.texy +++ b/application/hu/configuration.texy @@ -1,58 +1,71 @@ -Alkalmazás konfigurálása -************************ +Alkalmazások konfigurálása +************************** .[perex] -A Nette alkalmazás konfigurációs lehetőségeinek áttekintése. +A Nette alkalmazások konfigurációs lehetőségeinek áttekintése. -Alkalmazás .[#toc-application] -============================== +Application +=========== ```neon application: - # a "Nette alkalmazás" panelt mutatja a Tracy BlueScreen? - debugger: ... # (bool) alapértelmezett értéke true + # megjelenjen a "Nette Application" panel a Tracy BlueScreen-en? + debugger: ... # (bool) alapértelmezett: true - # error-presenter meghívásra kerül hiba esetén? - catchExceptions: ... # (bool) alapértelmezés szerint true termelési üzemmódban. + # hiba esetén meghívódjon az error-presenter? + # csak fejlesztői módban van hatása + catchExceptions: ... # (bool) alapértelmezett: true + + # az error-presenter neve + errorPresenter: Error # (string|array) alapértelmezett: 'Nette:Error' - # error-presenter neve - errorPresenter: Error # (string) alapértelmezett értéke 'Nette:Error'. + # aliasokat definiál presenterekhez és akciókhoz + aliases: ... - # meghatározza a prezenter nevének egy osztályra való feloldására vonatkozó szabályokat. + # szabályokat definiál a presenter nevének osztályra való fordításához mapping: ... - # a rossz linkek figyelmeztetést generálnak? + # a hibás linkek nem generálnak figyelmeztetést? # csak fejlesztői módban van hatása - silentLinks: ... # (bool) alapértelmezett értéke false + silentLinks: ... # (bool) alapértelmezett: false +``` + +A `nette/application` 3.2-es verziójától kezdve definiálható egy error-presenter pár: + +```neon +application: + errorPresenter: + 4xx: Error4xx # Nette\Application\BadRequestException kivételhez + 5xx: Error5xx # egyéb kivételekhez ``` -Mivel a hiba-bemutatókat alapértelmezés szerint nem hívja meg a program fejlesztési módban, és a hibákat a Tracy jeleníti meg, a `catchExceptions` érték `true` -re történő módosítása segít ellenőrizni, hogy a hiba-bemutatók helyesen működnek-e a fejlesztés során. +A `silentLinks` opció meghatározza, hogyan viselkedik a Nette fejlesztői módban, ha a link generálása sikertelen (például mert nem létezik a presenter stb.). Az alapértelmezett `false` érték azt jelenti, hogy a Nette `E_USER_WARNING` hibát dob. `true`-ra állítva ez a hibaüzenet elnyomásra kerül. Éles környezetben az `E_USER_WARNING` mindig kiváltódik. Ezt a viselkedést a presenter [$invalidLinkMode |creating-links#Érvénytelen linkek] változójának beállításával is befolyásolhatjuk. -A `silentLinks` opció határozza meg, hogy a Nette hogyan viselkedjen fejlesztői módban, ha a linkgenerálás sikertelen (például mert nincs prezenter stb.). Az alapértelmezett `false` érték azt jelenti, hogy a Nette a `E_USER_WARNING` opciót váltja ki. A `true` beállítása elnyomja ezt a hibaüzenetet. Gyártási környezetben a `E_USER_WARNING` mindig meghívásra kerül. Ezt a viselkedést a [$invalidLinkMode |creating-links#Invalid Links] prezenter változó beállításával is befolyásolhatjuk. +Az [Aliasok egyszerűsítik a hivatkozást |creating-links#Aliasok] a gyakran használt presenterekre. -A [leképezés meghatározza azokat a szabályokat |modules#mapping], amelyek alapján az osztály neve a prezenter nevéből származik. +A [Mapping definiálja a szabályokat |directory-structure#Presenterek map-elése], amelyek alapján a presenter nevéből levezetődik az osztály neve. -Az előadók automatikus regisztrációja .[#toc-automatic-registration-of-presenters] ----------------------------------------------------------------------------------- +Presenterek automatikus regisztrációja +-------------------------------------- -A Nette automatikusan hozzáadja az előadókat szolgáltatásként a DI konténerhez, ami jelentősen felgyorsítja a létrehozásukat. Az, hogy a Nette hogyan találja meg az előadókat, konfigurálható: +A Nette automatikusan hozzáadja a presentereket szolgáltatásként a DI konténerhez, ami jelentősen felgyorsítja azok létrehozását. A Nette presenterek felkutatásának módja konfigurálható: ```neon -alkalmazás: - # előadókat keresni a Composer osztálytérképen? - scanComposer: ... # (bool) alapértelmezés szerint true +application: + # keresse a presentereket a Composer class map-ben? + scanComposer: ... # (bool) alapértelmezett: true - # egy maszk, amelynek meg kell egyeznie az osztály- és fájlnévvel. - scanFilter: ... # (string) alapértelmezés szerint '*Presenter' + # maszk, amelynek meg kell felelnie az osztály és a fájl nevének + scanFilter: ... # (string) alapértelmezett: '*Presenter' - # mely könyvtárakban keressük az előadókat? - scanDirs: # (string[]|false) alapértelmezett értéke '%appDir%' + # mely könyvtárakban keresse a presentereket? + scanDirs: # (string[]|false) alapértelmezett: '%appDir%' - %vendorDir%/mymodule ``` -A `scanDirs` alatt felsorolt könyvtárak nem írják felül a `%appDir%` alapértelmezett értékét, hanem kiegészítik azt, így a `scanDirs` tartalmazza a `%appDir%` és a `%vendorDir%/mymodule` elérési utakat is. Ha felül akarjuk írni az alapértelmezett könyvtárat, akkor használjunk [felkiáltójelet |dependency-injection:configuration#Merging]: +A `scanDirs`-ben megadott könyvtárak nem írják felül az alapértelmezett `%appDir%` értéket, hanem kiegészítik azt, így a `scanDirs` mindkét utat tartalmazni fogja: `%appDir%` és `%vendorDir%/mymodule`. Ha az alapértelmezett könyvtárat ki szeretnénk hagyni, használjuk a [felkiáltójelet |dependency-injection:configuration#Összefésülés], amely felülírja az értéket: ```neon application: @@ -60,64 +73,73 @@ application: - %vendorDir%/mymodule ``` -A false beállításával kikapcsolhatjuk a könyvtárak beolvasását. Nem javasoljuk a bemutatók automatikus hozzáadásának teljes elnyomását, különben az alkalmazás teljesítménye csökken. +A könyvtárak szkennelése kikapcsolható a false érték megadásával. Nem javasoljuk a presenterek automatikus hozzáadásának teljes elnyomását, mert ez csökkenti az alkalmazás teljesítményét. -Latte .[#toc-latte] -=================== +Latte sablonok +============== -Ez a beállítás globálisan befolyásolja a Latte viselkedését a komponensekben és a prezenterekben. +Ezzel a beállítással globálisan befolyásolható a Latte viselkedése a komponensekben és presenterekben. ```neon latte: - # a Latte panelt a Tracy Barban a fő sablonhoz (true) vagy az összes komponenshez (all)? - debugger: ... # (true|false|'all') alapértelmezés szerint true + # megjelenjen a Latte panel a Tracy Bar-ban a fő sablonhoz (true) vagy az összes komponenshez (all)? + debugger: ... # (true|false|'all') alapértelmezett: true + + # generál sablonokat declare(strict_types=1) fejléccel + strictTypes: ... # (bool) alapértelmezett: false + + # bekapcsolja a [szigorú parser |latte:develop#striktní režim] módot + strictParsing: ... # (bool) alapértelmezett: false + + # aktiválja a [generált kód ellenőrzését |latte:develop#Kontrola vygenerovaného kódu] + phpLinter: ... # (string) alapértelmezett: null - # generálja a sablonokat declare(strict_types=1)-el. - strictTypes: ... # (bool) alapértelmezés szerint false + # beállítja a locale-t + locale: cs_CZ # (string) alapértelmezett: null - # $this->template osztálya - templateClass: # alapértelmezett értéke Nette\Bridges\ApplicationLatte\DefaultTemplate + # a $this->template objektum osztálya + templateClass: App\MyTemplateClass # alapértelmezett: Nette\Bridges\ApplicationLatte\DefaultTemplate ``` -Ha a Latte 3. verzióját használja, akkor új [bővítményt |latte:creating-extension] adhat hozzá a következőkkel: +Ha a Latte 3-as verzióját használja, új [bővítményeket |latte:extending-latte#Latte Extension] adhat hozzá a következőkkel: ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -/--comment - - - +Ha a Latte 2-es verzióját használja, új tag-eket regisztrálhat akár az osztálynév megadásával, akár egy szolgáltatásra való hivatkozással. Alapértelmezés szerint az `install()` metódus hívódik meg, de ezt meg lehet változtatni egy másik metódus nevének megadásával: +```neon +latte: + # egyéni Latte tag-ek regisztrálása + macros: + - App\MyLatteMacros::register # statikus metódus, classname vagy callable + - @App\MyLatteMacrosFactory # szolgáltatás install() metódussal + - @App\MyLatteMacrosFactory::register # szolgáltatás register() metódussal + +services: + - App\MyLatteMacrosFactory +``` - - - - - -\-- - - -Routing .[#toc-routing] -======================= +Routing +======= Alapbeállítások: ```neon routing: - # mutatja az útvonalválasztó panelt a Tracy Barban? - debugger: ... # (bool) alapértelmezett értéke true + # megjelenjen a routing panel a Tracy Bar-ban? + debugger: ... # (bool) alapértelmezett: true - # a router DI konténerbe való szerializálása? - cache: ... # (bool) alapértelmezés szerint false + # szerializálja a routert a DI konténerbe + cache: ... # (bool) alapértelmezett: false ``` -A Router általában a [RouterFactory |routing#Route Collection] osztályban van definiálva. Alternatívaként a konfigurációban is definiálhatók útvonalak a `mask: action` párok segítségével, de ez a módszer nem kínál olyan széleskörű variációs lehetőséget a beállítások terén: +A routingot általában a [RouterFactory |routing#Route gyűjtemény] osztályban definiáljuk. Alternatívaként a route-okat a konfigurációban is definiálhatjuk `maszk: akció` párokkal, de ez a módszer nem kínál olyan széleskörű beállítási lehetőségeket: ```neon routing: @@ -127,8 +149,8 @@ routing: ``` -Állandók .[#toc-constants] -========================== +Konstansok +========== PHP konstansok létrehozása. @@ -137,18 +159,33 @@ constants: Foobar: 'baz' ``` -A `Foobar` konstans az indítás után jön létre. +Az alkalmazás indítása után létrejön a `Foobar` konstans. .[note] -A konstansok nem szolgálhatnak globálisan elérhető változóként. Ha értékeket akarsz átadni objektumoknak, használd a [függőségi injektálást |dependency-injection:passing-dependencies]. +A konstansok nem szolgálhatnak valamiféle globálisan elérhető változóként. Értékek objektumokba való átadásához használja a [dependency injectiont |dependency-injection:passing-dependencies]. PHP === -PHP irányelveket állíthat be. Az összes direktíva áttekintése megtalálható a [php.net |https://www.php.net/manual/en/ini.list.php] oldalon. +PHP direktívák beállítása. Az összes direktíva áttekintése megtalálható a [php.net |https://www.php.net/manual/en/ini.list.php] oldalon. ```neon php: date.timezone: Europe/Prague ``` + + +DI szolgáltatások +================= + +Ezek a szolgáltatások kerülnek hozzáadásra a DI konténerhez: + +| Név | Típus | Leírás +|---------------------------------------------------------- +| `application.application` | [api:Nette\Application\Application] | [az egész alkalmazás indítója |how-it-works#Nette Application] +| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] +| `application.presenterFactory` | [api:Nette\Application\PresenterFactory] | presenter factory +| `application.###` | [api:Nette\Application\UI\Presenter] | egyes presenterek +| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | `Latte\Engine` objektum factory-ja +| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | factory a [`$this->template` |templates] számára diff --git a/application/hu/creating-links.texy b/application/hu/creating-links.texy index 8f7c11af58..9aedf6052c 100644 --- a/application/hu/creating-links.texy +++ b/application/hu/creating-links.texy @@ -1,160 +1,160 @@ -URL hivatkozások létrehozása -**************************** +URL linkek létrehozása +**********************
    -A linkek létrehozása a Nette-ben olyan egyszerű, mint egy ujjal mutogatni. Csak mutasson rá, és a keretrendszer elvégzi az összes munkát Ön helyett. Megmutatjuk: +Linkek létrehozása a Nette-ben egyszerű, mint az ujjal mutogatás. Csak rá kell mutatni, és a keretrendszer elvégzi az összes munkát Ön helyett. Megmutatjuk: -- hogyan hozhatunk létre linkeket a sablonokban és máshol -- hogyan lehet megkülönböztetni az aktuális oldalra mutató linket -- mi a helyzet az érvénytelen linkekkel +- hogyan hozzunk létre linkeket sablonokban és máshol +- hogyan különböztessük meg az aktuális oldalra mutató linket +- mit tegyünk az érvénytelen linkekkel
    -A [kétirányú útválasztásnak |routing] köszönhetően soha nem kell a sablonokban vagy a kódban keményen beprogramoznia az alkalmazás URL-címeit, amelyek később megváltozhatnak, vagy bonyolult lehet az összeállításuk. Csak adja meg a bemutatót és az akciót a linkben, adjon át bármilyen paramétert, és a keretrendszer maga generálja az URL-t. Valójában ez nagyon hasonlít egy függvény meghívásához. Tetszeni fog. +Az [kétirányú routingnak |routing] köszönhetően soha nem kell majd keményen beírnia az alkalmazás URL-címeit a sablonokba vagy a kódba, amelyek később megváltozhatnak, vagy bonyolultan összeállítani őket. A linkben elegendő megadni a presentert és az akciót, átadni az esetleges paramétereket, és a keretrendszer maga generálja az URL-t. Valójában nagyon hasonlít egy függvényhívásra. Ez tetszeni fog Önnek. -A bemutató sablonban .[#toc-in-the-presenter-template] -====================================================== +A presenter sablonjában +======================= -Leggyakrabban a sablonokban hozunk létre linkeket, és ebben nagy segítségünkre van a `n:href` attribútum: +Leggyakrabban sablonokban hozunk létre linkeket, és nagyszerű segítő az `n:href` attribútum: ```latte -detail +részletek ``` -Figyeljük meg, hogy a `href` HTML attribútum helyett az [n:attribútumot |latte:syntax#n:attributes] `n:href` használjuk. Ennek értéke nem egy URL, ahogyan azt a `href` attribútummal megszoktuk, hanem a bemutató és a művelet neve. +Figyelje meg, hogy a HTML `href` attribútum helyett az [n:attribútumot |latte:syntax#n:attribútumok] `n:href` használtuk. Ennek értéke nem URL, ahogy az `href` attribútum esetében lenne, hanem a presenter és az akció neve. -A linkre kattintás, egyszerűen szólva, olyasmi, mint egy módszer meghívása `ProductPresenter::renderShow()`. És ha a szignatúrájában vannak paraméterek, akkor argumentumokkal is meg tudjuk hívni: +Egy linkre kattintás, leegyszerűsítve, olyan, mintha a `ProductPresenter::renderShow()` metódust hívnánk meg. És ha annak szignatúrájában paraméterek vannak, argumentumokkal hívhatjuk meg: ```latte -detail +termék részletei ``` -Lehetőség van nevesített paraméterek átadására is. A következő hivatkozás a `lang` paramétert adja át a `en` értékkel: +Lehetőség van elnevezett paraméterek átadására is. A következő link a `lang` paramétert adja át `cs` értékkel: ```latte -detail +termék részletei ``` -Ha a `ProductPresenter::renderShow()` metódus szignatúrájában nem szerepel a `$lang`, akkor a paraméter értékét a `$lang = $this->getParameter('lang')` segítségével tudja beolvasni. +Ha a `ProductPresenter::renderShow()` metódusnak nincs `$lang` a szignatúrájában, a paraméter értékét a `$lang = $this->getParameter('lang')` segítségével vagy a [property-ből |presenters#Kérés paraméterei] tudhatja meg. -Ha a paraméterek egy tömbben vannak tárolva, akkor a `...` operátorral (vagy a Latte 2.x-ben a `(expand)` operátorral) bővíthetők: +Ha a paraméterek tömbben vannak tárolva, kibonthatók a `...` operátorral (Latte 2.x-ben az `(expand)` operátorral): ```latte -{var $args = [$product->id, lang => en]} -detail +{var $args = [$product->id, lang => cs]} +termék részletei ``` -Az úgynevezett [tartós paraméterek |presenters#persistent parameters] is automatikusan átadásra kerülnek a hivatkozásokban. +A linkekben automatikusan átadódnak az ún. [perzisztens paraméterek |presenters#Perzisztens paraméterek] is. -A `n:href` attribútum nagyon hasznos a HTML címkékhez. ``. Ha a linket máshol, például a szövegben akarjuk kiírni, akkor a `{link}` attribútumot használjuk: +Az `n:href` attribútum nagyon praktikus a HTML `` tag-ekhez. Ha máshol szeretnénk kiírni a linket, például szövegben, használjuk a `{link}`-et: ```latte -URL is: {link Home:default} +A cím: {link Home:default} ``` -A kódban .[#toc-in-the-code] -============================ +A kódban +======== -A `link()` metódus a bemutatóban egy link létrehozására szolgál: +Link létrehozásához a presenterben a `link()` metódus szolgál: ```php $url = $this->link('Product:show', $product->id); ``` -A paraméterek tömbként is átadhatók, ahol megnevezett paraméterek is megadhatók: +A paramétereket tömb segítségével is át lehet adni, ahol elnevezett paramétereket is meg lehet adni: ```php $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); ``` -A linkek prezenter nélkül is létrehozhatók a [LinkGenerator |#LinkGenerator] és annak `link()` metódusa segítségével. +Linkeket presenter nélkül is lehet létrehozni, erre való a [#LinkGenerator] és annak `link()` metódusa. -Linkek a bemutatóhoz .[#toc-links-to-presenter] -=============================================== +Linkek presenterhez +=================== -Ha a link célpontja az előadó és a művelet, akkor a következő szintaxissal rendelkezik: +Ha a link célja egy presenter és egy akció, akkor a szintaxisa a következő: ``` [//] [[[[:]module:]presenter:]action | this] [#fragment] ``` -A formátumot az összes Latte tag és az összes olyan presenter módszer támogatja, amely linkekkel dolgozik, azaz `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` és a [LinkGenerator |#LinkGenerator]. Tehát még ha a példákban a `n:href` is szerepel, bármelyik függvény lehet. +A formátumot minden Latte tag és minden presenter metódus támogatja, amely linkekkel dolgozik, tehát `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` és a [#LinkGenerator] is. Tehát még ha a példákban `n:href` szerepel is, bármelyik függvény lehetne ott. Az alapforma tehát `Presenter:action`: ```latte -home +kezdőlap ``` -Ha az aktuális bemutató akciójára hivatkozunk, elhagyhatjuk a nevét: +Ha az aktuális presenter akciójára hivatkozunk, kihagyhatjuk a nevét: ```latte -home +kezdőlap ``` -Ha a művelet a `default`, elhagyhatjuk, de a kettőspontnak meg kell maradnia: +Ha a cél a `default` akció, kihagyhatjuk, de a kettőspontnak maradnia kell: ```latte -home +kezdőlap ``` -A linkek más [modulokra |modules] is mutathatnak. Itt a linkeket megkülönböztetjük az almodulokhoz viszonyított, illetve abszolút linkekre. Az elv analóg a lemezes elérési utakkal, csak a kötőjelek helyett kettőspontok vannak. Tegyük fel, hogy a tényleges bemutató a `Front` modul része, akkor azt írjuk: +A linkek más [modulokba |directory-structure#Presenterek és sablonok] is mutathatnak. Itt a linkeket megkülönböztetjük relatívakra egy beágyazott almodulba, vagy abszolútakra. Az elv analóg a lemezen lévő elérési utakkal, csak perjelek helyett kettőspontok vannak. Tegyük fel, hogy az aktuális presenter a `Front` modul része, akkor így írjuk: ```latte -link to Front:Shop:Product:show -link to Admin:Product:show +link a Front:Shop:Product:show-ra +link az Admin:Product:show-ra ``` -Speciális eset a [saját magára való hivatkozás |#Links to Current Page]. Itt azt írjuk, hogy `this` a cél. +Speciális eset a [saját magára mutató |#Link az aktuális oldalra] link, amikor célként a `this`-t adjuk meg. ```latte -refresh +frissítés ``` -A HTML oldal egy bizonyos részére a `#` hash szimbólum után egy úgynevezett fragmentummal tudunk hivatkozni: +Hivatkozhatunk az oldal egy bizonyos részére az ún. fragment segítségével a kettőskereszt `#` jel után: ```latte -link to Home:default and fragment #main +link a Home:default-ra és a #main fragmentre ``` -Abszolút elérési utak .[#toc-absolute-paths] -============================================ +Abszolút utak +============= -A `link()` vagy `n:href` által generált linkek mindig abszolút elérési utak (azaz `/`-vel kezdődnek), de nem abszolút URL-ek protokollal és domainnel, mint például `https://domain`. +A `link()` vagy `n:href` segítségével generált linkek mindig abszolút utak (azaz `/` jellel kezdődnek), de nem abszolút URL-ek protokollal és domainnel, mint `https://domain`. -Abszolút URL létrehozásához adjon hozzá két kötőjelet az elejéhez (pl. `n:href="//Home:"`). Vagy a `$this->absoluteUrls = true` beállítással úgy is beállíthatja a prezentert, hogy csak abszolút linkeket generáljon. +Abszolút URL generálásához adjon hozzá két perjelet az elejére (pl. `n:href="//Home:"`). Vagy átkapcsolhatja a presentert, hogy csak abszolút linkeket generáljon a `$this->absoluteUrls = true` beállításával. -Link az aktuális oldalra .[#toc-link-to-current-page] -===================================================== +Link az aktuális oldalra +======================== -A cél `this` létrehoz egy linket az aktuális oldalra: +A `this` cél linket hoz létre az aktuális oldalra: ```latte -refresh +frissítés ``` -Ugyanakkor az összes, az aláírásában megadott paramétert a `render()` vagy `action()` metódusban megadott paraméterek átadásra kerülnek. Tehát ha a `Product:show` és a `id:123` oldalakon vagyunk, akkor a `this` link is átadja ezt a paramétert. +Ugyanakkor átadódnak az összes paraméter, amelyek a `action()` vagy `render()` metódus szignatúrájában szerepelnek, ha az `action()` nincs definiálva. Tehát ha a `Product:show` oldalon vagyunk és `id: 123`, a `this`-re mutató link ezt a paramétert is átadja. -Természetesen lehetőség van a paraméterek közvetlen megadására is: +Természetesen a paramétereket közvetlenül is meg lehet adni: ```latte -refresh +frissítés ``` -A `isLinkCurrent()` függvény meghatározza, hogy a link célpontja megegyezik-e az aktuális oldallal. Ez például egy sablonban használható a linkek megkülönböztetésére stb. +Az `isLinkCurrent()` függvény ellenőrzi, hogy a link célja megegyezik-e az aktuális oldallal. Ezt például a sablonban lehet használni a linkek megkülönböztetésére stb. -A paraméterek ugyanazok, mint a `link()` metódus esetében, de lehetőség van a `*` joker karakter használatára is egy konkrét művelet helyett, ami a bemutató bármely műveletét jelenti. +A paraméterek ugyanazok, mint a `link()` metódusnál, de ezen felül lehetőség van egy konkrét akció helyett a `*` helyettesítő karakter megadására, amely az adott presenter bármely akcióját jelenti. ```latte {if !isLinkCurrent('Admin:login')} - Přihlaste se + Jelentkezzen be {/if}
  • @@ -162,58 +162,58 @@ A paraméterek ugyanazok, mint a `link()` metódus esetében, de lehetőség van
  • ``` -A rövidített formát a `n:href` kapcsolattal együtt lehet használni egyetlen elemben: +Az `n:href`-fel kombinálva egy elemen belül használható a rövidített forma: ```latte -... +... ``` -A `*` joker karakter csak a prezentáló műveletét helyettesíti, magát a prezentálót nem. +A `*` helyettesítő karakter csak az akció helyett használható, a presenter helyett nem. -Annak megállapítására, hogy egy adott modulban vagy annak almoduljában vagyunk-e, a `isModuleCurrent(moduleName)` függvényt használhatjuk. +Annak megállapítására, hogy egy adott modulban vagy annak almoduljában vagyunk-e, használjuk az `isModuleCurrent(moduleName)` metódust. ```latte -
  • +
  • ...
  • ``` -Linkek a Signalhoz .[#toc-links-to-signal] -========================================== +Linkek signálhoz +================ -A link célpontja nem csak a bemutató és az akció lehet, hanem a [jel |components#Signal] is (ők hívják a metódust `handle()`). A szintaxis a következő: +A link célja nemcsak presenter és akció lehet, hanem [signál |components#Signal] is (ezek a `handle()` metódust hívják). Ekkor a szintaxis a következő: ``` [//] [sub-component:]signal! [#fragment] ``` -A jelzést tehát felkiáltójel különbözteti meg: +A signált tehát a felkiáltójel különbözteti meg: ```latte -signal +signál ``` -Az alkomponens (vagy al-alkomponens) jelére mutató linket is létrehozhat: +Lehet linket létrehozni egy alkomponens (vagy al-alkomponens) signáljára is: ```latte -signal +signál ``` -Linkek a komponensben .[#toc-links-in-component] -================================================ +Linkek a komponensben +===================== -Mivel a [komponensek |components] különálló, újrafelhasználható egységek, amelyeknek nem lehet kapcsolatuk a környező bemutatókkal, a hivatkozások egy kicsit másképp működnek. A `n:href` Latte attribútum és a `{link}` címke, valamint az olyan komponensmódszerek, mint a `link()` és mások, mindig a célt tekintik **jelzőnévnek**. Ezért nem szükséges felkiáltójelet használni: +Mivel a [komponensek|components] önálló, újrafelhasználható egységek, amelyeknek nem kellene semmilyen kapcsolatban állniuk a környező presenterekkel, a linkek itt egy kicsit másképp működnek. A Latte `n:href` attribútuma és a `{link}` tag, valamint a komponens metódusai, mint a `link()` és mások, a link célját **mindig signál névként** kezelik. Ezért még a felkiáltójelet sem kell megadni: ```latte -signal, not an action +signál, nem akció ``` -Ha a komponenssablonban előadókra akarunk hivatkozni, akkor a `{plink}` címkét használjuk: +Ha a komponens sablonjában presenterekre szeretnénk hivatkozni, használjuk a `{plink}` taget: ```latte -home +kezdőlap ``` vagy a kódban @@ -223,17 +223,41 @@ $this->getPresenter()->link('Home:default') ``` -Érvénytelen hivatkozások .[#toc-invalid-links] -============================================== +Aliasok .{data-version:v3.2.2} +============================== -Előfordulhat, hogy érvénytelen linket hozunk létre - vagy azért, mert nem létező bemutatóra hivatkozik, vagy azért, mert több paramétert ad át, mint amennyit a célmódszer az aláírásában kap, vagy amikor nem lehet generált URL a célzott művelethez. Azt, hogy mit tegyünk az érvénytelen linkekkel, a `Presenter::$invalidLinkMode` statikus változó határozza meg. Ez a következő értékek (konstansok) egyikét veheti fel: +Néha hasznos lehet egy könnyen megjegyezhető aliast rendelni egy Presenter:akció párhoz. Például a `Front:Home:default` kezdőlapot egyszerűen `home`-nak nevezni, vagy az `Admin:Dashboard:default`-ot `admin`-nak. -- `Presenter::InvalidLinkSilent` - néma üzemmód, URL-ként a `#` szimbólumot adja vissza. -- `Presenter::InvalidLinkWarning` - E_USER_WARNING kerül előállításra. -- `Presenter::InvalidLinkTextual` - vizuális figyelmeztetés, a hiba szövege megjelenik a hivatkozásban. -- `Presenter::InvalidLinkException` - az InvalidLinkException hibaüzenetet kap. +Az aliasokat a [konfigurációban|configuration] definiáljuk az `application › aliases` kulcs alatt: -Az alapértelmezett beállítás termelési üzemmódban a `InvalidLinkWarning`, fejlesztési üzemmódban pedig a `InvalidLinkWarning | InvalidLinkTextual`. A `InvalidLinkWarning` nem öli meg a szkriptet a termelési környezetben, de a figyelmeztetés naplózásra kerül. A fejlesztői környezetben a [Tracy |tracy:] elfogja a figyelmeztetést és megjeleníti a hiba bluescreen-t. Ha a `InvalidLinkTextual` van beállítva, a prezenter és a komponensek URL-ként adják vissza a hibaüzenetet, amely a `#error:` csillaggal kezdődik. Ahhoz, hogy az ilyen linkeket láthatóvá tegyük, hozzáadhatunk egy CSS szabályt a stíluslapunkhoz: +```neon +application: + aliases: + home: Front:Home:default + admin: Admin:Dashboard:default + sign: Front:Sign:in +``` + +A linkekben ezután a kukac jellel írjuk őket, például: + +```latte +adminisztráció +``` + +Támogatottak minden olyan metódusban is, amely linkekkel dolgozik, mint a `redirect()` és hasonlók. + + +Érvénytelen linkek +================== + +Előfordulhat, hogy érvénytelen linket hozunk létre - vagy azért, mert nem létező presenterhez vezet, vagy azért, mert több paramétert ad át, mint amennyit a célmetódus a szignatúrájában elfogad, vagy ha a célakcióhoz nem lehet URL-t generálni. Az érvénytelen linkek kezelését a `Presenter::$invalidLinkMode` statikus változó határozza meg. Ez a következő értékek kombinációját veheti fel (konstansok): + +- `Presenter::InvalidLinkSilent` - csendes mód, URL-ként a # karaktert adja vissza +- `Presenter::InvalidLinkWarning` - E_USER_WARNING figyelmeztetést dob, amely éles módban logolásra kerül, de nem szakítja meg a szkript futását +- `Presenter::InvalidLinkTextual` - vizuális figyelmeztetés, a hibát közvetlenül a linkbe írja +- `Presenter::InvalidLinkException` - InvalidLinkException kivételt dob + +Az alapértelmezett beállítás `InvalidLinkWarning` éles módban és `InvalidLinkWarning | InvalidLinkTextual` fejlesztői módban. Az `InvalidLinkWarning` éles környezetben nem szakítja meg a szkript futását, de a figyelmeztetés logolásra kerül. Fejlesztői környezetben a [Tracy |tracy:] elfogja és bluescreen-t jelenít meg. Az `InvalidLinkTextual` úgy működik, hogy URL-ként egy hibaüzenetet ad vissza, amely `#error:` karakterekkel kezdődik. Hogy az ilyen linkek első pillantásra észrevehetők legyenek, adjunk hozzá a CSS-hez: ```css a[href^="#error:"] { @@ -242,7 +266,7 @@ a[href^="#error:"] { } ``` -Ha nem akarjuk, hogy a fejlesztői környezetben figyelmeztetések jelenjenek meg, akkor a [konfigurációban |configuration] bekapcsolhatjuk a silent invalid link mode-ot. +Ha nem szeretnénk, hogy fejlesztői környezetben figyelmeztetések keletkezzenek, beállíthatjuk a csendes módot közvetlenül a [konfigurációban|configuration]. ```neon application: @@ -250,13 +274,13 @@ application: ``` -LinkGenerator .[#toc-linkgenerator] -=================================== +LinkGenerator +============= -Hogyan hozhatunk létre linkeket a `link()` comfort módszerrel, de prezenter jelenléte nélkül? Ezért itt van a [api:Nette\Application\LinkGenerator]. +Hogyan hozzunk létre linkeket hasonló kényelemmel, mint a `link()` metódus, de presenter jelenléte nélkül? Erre való a [api:Nette\Application\LinkGenerator]. -LinkGenerator egy olyan szolgáltatás, amelyet átadhat a konstruktoron keresztül, majd linkeket hozhat létre a `link()` módszerével. +A LinkGenerator egy szolgáltatás, amelyet a konstruktoron keresztül kérhetünk, majd a `link()` metódusával hozhatunk létre linkeket. -Van egy különbség a bemutatókhoz képest. A LinkGenerator minden linket abszolút URL-ként hoz létre. Továbbá nincs "aktuális bemutató", így nem lehet csak a `link('default')` művelet nevét vagy a [modulok |modules] relatív elérési útvonalait megadni. +A presenterekkel szemben itt van egy különbség. A LinkGenerator minden linket rögtön abszolút URL-ként hoz létre. Továbbá nincs "aktuális presenter", így nem lehet célként csak az akció nevét megadni (`link('default')`) vagy relatív utakat megadni a modulokhoz. -Az érvénytelen linkek mindig dobnak `Nette\Application\UI\InvalidLinkException`. +Az érvénytelen linkek mindig `Nette\Application\UI\InvalidLinkException`-t dobnak. diff --git a/application/hu/directory-structure.texy b/application/hu/directory-structure.texy new file mode 100644 index 0000000000..4ee6a6ada3 --- /dev/null +++ b/application/hu/directory-structure.texy @@ -0,0 +1,526 @@ +Alkalmazás könyvtárstruktúrája +****************************** + +
    + +Hogyan tervezzünk áttekinthető és skálázható könyvtárstruktúrát Nette Framework projektekhez? Megmutatjuk a bevált gyakorlatokat, amelyek segítenek a kód szervezésében. Megtudhatja: + +- hogyan **logikusan tagoljuk** az alkalmazást könyvtárakba +- hogyan tervezzük meg a struktúrát úgy, hogy **jól skálázódjon** a projekt növekedésével +- mik a **lehetséges alternatívák** és azok előnyei vagy hátrányai + +
    + + +Fontos megemlíteni, hogy maga a Nette Framework nem ragaszkodik semmilyen konkrét struktúrához. Úgy tervezték, hogy könnyen alkalmazkodjon bármilyen igényhez és preferenciához. + + +A projekt alapstruktúrája +========================= + +Bár a Nette Framework nem diktál semmilyen merev könyvtárstruktúrát, létezik egy bevált alapértelmezett elrendezés a [Web Project|https://github.com/nette/web-project] formájában: + +/--pre +web-project/ +├── app/ ← alkalmazás könyvtára +├── assets/ ← SCSS, JS fájlok, képek..., alternatívaként resources/ +├── bin/ ← parancssori szkriptek +├── config/ ← konfiguráció +├── log/ ← logolt hibák +├── temp/ ← ideiglenes fájlok, cache +├── tests/ ← tesztek +├── vendor/ ← Composer által telepített könyvtárak +└── www/ ← nyilvános könyvtár (document-root) +\-- + +Ezt a struktúrát tetszés szerint módosíthatja igényei szerint - a mappákat átnevezheti vagy áthelyezheti. Ezután csak a relatív elérési utakat kell módosítani a `Bootstrap.php` fájlban és esetleg a `composer.json`-ban. Semmi másra nincs szükség, nincs bonyolult újrakonfigurálás, nincs konstansok módosítása. A Nette okos automatikus felismeréssel rendelkezik, és automatikusan felismeri az alkalmazás helyét, beleértve annak URL alapját is. + + +Kódszervezési elvek +=================== + +Amikor először vizsgál meg egy új projektet, gyorsan eligazodnia kell benne. Képzelje el, hogy kibontja az `app/Model/` könyvtárat, és ezt a struktúrát látja: + +/--pre +app/Model/ +├── Services/ +├── Repositories/ +└── Entities/ +\-- + +Ebből csak azt olvashatja ki, hogy a projekt valamilyen szolgáltatásokat, repository-kat és entitásokat használ. Az alkalmazás valódi céljáról semmit sem tud meg. + +Nézzünk meg egy másik megközelítést - **szervezés domainek szerint**: + +/--pre +app/Model/ +├── Cart/ +├── Payment/ +├── Order/ +└── Product/ +\-- + +Itt más a helyzet - első pillantásra világos, hogy egy webáruházról van szó. Már maguk a könyvtárnevek is elárulják, mit tud az alkalmazás - fizetésekkel, rendelésekkel és termékekkel dolgozik. + +Az első megközelítés (szervezés osztálytípus szerint) a gyakorlatban számos problémát okoz: a logikailag összetartozó kód különböző mappákba van szétszórva, és ugrálnia kell közöttük. Ezért domainek szerint fogunk szervezni. + + +Névterek +-------- + +Szokás, hogy a könyvtárstruktúra megfelel az alkalmazás névtereinek. Ez azt jelenti, hogy a fájlok fizikai elhelyezkedése megfelel a namespace-üknek. Például az `app/Model/Product/ProductRepository.php`-ban elhelyezett osztálynak `App\Model\Product` namespace-szel kellene rendelkeznie. Ez az elv segít a kódban való tájékozódásban és egyszerűsíti az autoloadingot. + + +Egyes vs többes szám a nevekben +------------------------------- + +Figyelje meg, hogy az alkalmazás fő könyvtárainál egyes számot használunk: `app`, `config`, `log`, `temp`, `www`. Ugyanígy az alkalmazáson belül is: `Model`, `Core`, `Presentation`. Ez azért van, mert mindegyik egy-egy összefüggő koncepciót képvisel. + +Hasonlóképpen például az `app/Model/Product` mindent reprezentál a termékekkel kapcsolatban. Nem nevezzük `Products`-nak, mert nem egy termékekkel teli mappa (akkor `nokia.php`, `samsung.php` fájlok lennének benne). Ez egy namespace, amely osztályokat tartalmaz a termékekkel való munkához - `ProductRepository.php`, `ProductService.php`. + +Az `app/Tasks` mappa többes számban van, mert önálló futtatható szkriptek készletét tartalmazza - `CleanupTask.php`, `ImportTask.php`. Mindegyik önálló egység. + +A következetesség érdekében javasoljuk a következők használatát: +- Egyes szám egy funkcionális egységet reprezentáló namespace-hez (még ha több entitással is dolgozik) +- Többes szám önálló egységek gyűjteményeihez +- Bizonytalanság esetén, vagy ha nem akar ezen gondolkodni, válassza az egyes számot + + +Nyilvános könyvtár `www/` +========================= + +Ez a könyvtár az egyetlen, amely a webről elérhető (ún. document-root). Gyakran találkozhat a `public/` névvel is a `www/` helyett - ez csak konvenció kérdése, és nincs hatással a funkcionalitásra. A könyvtár tartalmazza: +- Az alkalmazás [belépési pontját |bootstrapping#index.php] `index.php` +- A `.htaccess` fájlt mod_rewrite szabályokkal (Apache esetén) +- Statikus fájlokat (CSS, JavaScript, képek) +- Feltöltött fájlokat + +Az alkalmazás megfelelő biztonsága érdekében elengedhetetlen a [helyesen konfigurált document-root |nette:troubleshooting#Hogyan lehet megváltoztatni vagy eltávolítani a www könyvtárat az URL-ből]. + +.[note] +Soha ne helyezze ebbe a könyvtárba a `node_modules/` mappát - ez több ezer fájlt tartalmaz, amelyek futtathatók lehetnek, és nem kellene nyilvánosan elérhetőnek lenniük. + + +Alkalmazás könyvtára `app/` +=========================== + +Ez az alkalmazás kódjának fő könyvtára. Alapstruktúra: + +/--pre +app/ +├── Core/ ← infrastrukturális ügyek +├── Model/ ← üzleti logika +├── Presentation/ ← presenterek és sablonok +├── Tasks/ ← parancssori szkriptek +└── Bootstrap.php ← az alkalmazás indító osztálya +\-- + +A `Bootstrap.php` az [alkalmazás indító osztálya|bootstrapping], amely inicializálja a környezetet, betölti a konfigurációt és létrehozza a DI konténert. + +Most nézzük meg részletesebben az egyes alkönyvtárakat. + + +Presenterek és sablonok +======================= + +Az alkalmazás prezentációs része az `app/Presentation` könyvtárban található. Alternatíva a rövid `app/UI`. Ez a hely minden presenter, azok sablonjai és esetleges segédosztályai számára. + +Ezt a réteget domainek szerint szervezzük. Egy komplex projektben, amely kombinálja a webáruházat, a blogot és az API-t, a struktúra így nézne ki: + +/--pre +app/Presentation/ +├── Shop/ ← webáruház frontend +│ ├── Product/ +│ ├── Cart/ +│ └── Order/ +├── Blog/ ← blog +│ ├── Home/ +│ └── Post/ +├── Admin/ ← adminisztráció +│ ├── Dashboard/ +│ └── Products/ +└── Api/ ← API végpontok + └── V1/ +\-- + +Ezzel szemben egy egyszerű blog esetében a következő tagolást használnánk: + +/--pre +app/Presentation/ +├── Front/ ← web frontend +│ ├── Home/ +│ └── Post/ +├── Admin/ ← adminisztráció +│ ├── Dashboard/ +│ └── Posts/ +├── Error/ +└── Export/ ← RSS, sitemap-ek stb. +\-- + +A `Home/` vagy `Dashboard/` mappák presentereket és sablonokat tartalmaznak. A `Front/`, `Admin/` vagy `Api/` mappákat **moduloknak** nevezzük. Technikailag ezek átlagos könyvtárak, amelyek az alkalmazás logikai tagolására szolgálnak. + +Minden presenter mappa tartalmaz egy azonos nevű presentert és annak sablonjait. Például a `Dashboard/` mappa tartalmazza: + +/--pre +Dashboard/ +├── DashboardPresenter.php ← presenter +└── default.latte ← sablon +\-- + +Ez a könyvtárstruktúra tükröződik az osztályok névtereiben. Például a `DashboardPresenter` az `App\Presentation\Admin\Dashboard` névtérben található (lásd [#Presenterek map-elése]): + +```php +namespace App\Presentation\Admin\Dashboard; + +class DashboardPresenter extends Nette\Application\UI\Presenter +{ + // ... +} +``` + +Az `Admin` modulon belüli `Dashboard` presenterére az alkalmazásban kettőspontos jelöléssel hivatkozunk, mint `Admin:Dashboard`. Annak `default` akciójára pedig mint `Admin:Dashboard:default`. Beágyazott modulok esetén több kettőspontot használunk, például `Shop:Order:Detail:default`. + + +A struktúra rugalmas fejlesztése +-------------------------------- + +Ennek a struktúrának az egyik nagy előnye, hogy milyen elegánsan alkalmazkodik a projekt növekvő igényeihez. Vegyük példaként az XML feedeket generáló részt. Kezdetben egyszerű formában van: + +/--pre +Export/ +├── ExportPresenter.php ← egy presenter minden exportáláshoz +├── sitemap.latte ← sablon a sitemaphoz +└── feed.latte ← sablon az RSS feedhez +\-- + +Idővel újabb feed típusok jelennek meg, és több logikára van szükségünk hozzájuk... Semmi probléma! Az `Export/` mappa egyszerűen modullá válik: + +/--pre +Export/ +├── Sitemap/ +│ ├── SitemapPresenter.php +│ └── sitemap.latte +└── Feed/ + ├── FeedPresenter.php + ├── zbozi.latte ← feed a Zboží.cz-hez + └── heureka.latte ← feed a Heureka.cz-hez +\-- + +Ez az átalakulás teljesen zökkenőmentes - csak új almappákat kell létrehozni, szétosztani bennük a kódot és frissíteni a linkeket (pl. `Export:feed`-ről `Export:Feed:zbozi`-ra). Ennek köszönhetően a struktúrát fokozatosan bővíthetjük igény szerint, a beágyazási szint nincs korlátozva. + +Ha például az adminisztrációban sok presenter van a rendelések kezelésével kapcsolatban, mint például `OrderDetail`, `OrderEdit`, `OrderDispatch` stb., akkor a jobb szervezettség érdekében ezen a ponton létrehozhat egy `Order` modult (mappát), amelyben a `Detail`, `Edit`, `Dispatch` és további presenterek (mappái) lesznek. + + +Sablonok elhelyezése +-------------------- + +Az előző példákban láttuk, hogy a sablonok közvetlenül a presenter mappájában helyezkednek el: + +/--pre +Dashboard/ +├── DashboardPresenter.php ← presenter +├── DashboardTemplate.php ← opcionális osztály a sablonhoz +└── default.latte ← sablon +\-- + +Ez az elhelyezés a gyakorlatban a legkényelmesebbnek bizonyul - minden kapcsolódó fájl kéznél van. + +Alternatívaként a sablonokat elhelyezheti a `templates/` almappába. A Nette mindkét változatot támogatja. Sőt, a sablonokat akár teljesen a `Presentation/` mappán kívül is elhelyezheti. A sablonok elhelyezési lehetőségeiről mindent megtalál a [Sablonok keresése |templates#Sablonok keresése] fejezetben. + + +Segédosztályok és komponensek +----------------------------- + +A presenterekhez és sablonokhoz gyakran tartoznak további segédfájlok is. Ezeket logikusan a hatókörük szerint helyezzük el: + +1. **Közvetlenül a presenter mellett**, ha az adott presenterhez specifikus komponensekről van szó: + +/--pre +Product/ +├── ProductPresenter.php +├── ProductGrid.php ← komponens a termékek listázásához +└── FilterForm.php ← űrlap a szűréshez +\-- + +2. **A modulhoz** - javasoljuk az `Accessory` mappa használatát, amely áttekinthetően az ábécé elején helyezkedik el: + +/--pre +Front/ +├── Accessory/ +│ ├── NavbarControl.php ← komponensek a frontendhez +│ └── TemplateFilters.php +├── Product/ +└── Cart/ +\-- + +3. **Az egész alkalmazáshoz** - a `Presentation/Accessory/`-ban: +/--pre +app/Presentation/ +├── Accessory/ +│ ├── LatteExtension.php +│ └── TemplateFilters.php +├── Front/ +└── Admin/ +\-- + +Vagy elhelyezheti a segédosztályokat, mint a `LatteExtension.php` vagy `TemplateFilters.php`, az infrastrukturális `app/Core/Latte/` mappába. És a komponenseket az `app/Components`-be. A választás a csapat szokásaitól függ. + + +Model - az alkalmazás szíve +=========================== + +A modell tartalmazza az alkalmazás összes üzleti logikáját. Szervezésére ismét az a szabály érvényes - domainek szerint strukturálunk: + +/--pre +app/Model/ +├── Payment/ ← minden a fizetésekkel kapcsolatban +│ ├── PaymentFacade.php ← fő belépési pont +│ ├── PaymentRepository.php +│ ├── Payment.php ← entitás +├── Order/ ← minden a rendelésekkel kapcsolatban +│ ├── OrderFacade.php +│ ├── OrderRepository.php +│ ├── Order.php +└── Shipping/ ← minden a szállítással kapcsolatban +\-- + +A modellben tipikusan ezekkel az osztálytípusokkal találkozhat: + +**Fasádok (Facades)**: az alkalmazás egy adott domainjének fő belépési pontját képviselik. Orchestrátorként működnek, amely koordinálja a különböző szolgáltatások közötti együttműködést a teljes use-case-ek (mint a "rendelés létrehozása" vagy "fizetés feldolgozása") implementálása érdekében. Az orchestrációs rétege alatt a fasád elrejti az implementációs részleteket az alkalmazás többi része elől, ezáltal tiszta interfészt biztosítva az adott domainnel való munkához. + +```php +class OrderFacade +{ + public function createOrder(Cart $cart): Order + { + // validáció + // rendelés létrehozása + // e-mail küldése + // statisztikákba írás + } +} +``` + +**Szolgáltatások (Services)**: egy specifikus üzleti műveletre összpontosítanak a domainen belül. Ellentétben a fasáddal, amely teljes use-case-eket orchestrál, a szolgáltatás egy konkrét üzleti logikát implementál (mint az árkalkulációk vagy a fizetések feldolgozása). A szolgáltatások tipikusan állapotmentesek, és használhatók akár fasádok által építőelemekként komplexebb műveletekhez, akár közvetlenül az alkalmazás más részei által egyszerűbb feladatokhoz. + +```php +class PricingService +{ + public function calculateTotal(Order $order): Money + { + // árkalkuláció + } +} +``` + +**Repository-k**: biztosítják az összes kommunikációt az adattárolóval, tipikusan adatbázissal. Feladata az entitások betöltése és mentése, valamint metódusok implementálása azok kereséséhez. A repository elszigeteli az alkalmazás többi részét az adatbázis implementációs részleteitől, és objektumorientált interfészt biztosít az adatokkal való munkához. + +```php +class OrderRepository +{ + public function find(int $id): ?Order + { + } + + public function findByCustomer(int $customerId): array + { + } +} +``` + +**Entitások**: objektumok, amelyek az alkalmazás fő üzleti koncepcióit reprezentálják, saját identitással rendelkeznek és idővel változnak. Tipikusan olyan osztályokról van szó, amelyeket adatbázis táblákra map-elnek ORM segítségével (mint a Nette Database Explorer vagy a Doctrine). Az entitások tartalmazhatnak üzleti szabályokat az adataikra vonatkozóan és validációs logikát. + +```php +// Az orders adatbázis táblára map-elt entitás +class Order extends Nette\Database\Table\ActiveRow +{ + public function addItem(Product $product, int $quantity): void + { + $this->related('order_items')->insert([ + 'product_id' => $product->id, + 'quantity' => $quantity, + 'unit_price' => $product->price, + ]); + } +} +``` + +**Value objektumok**: megváltoztathatatlan objektumok, amelyek értékeket reprezentálnak saját identitás nélkül - például pénzösszeg vagy e-mail cím. Két azonos értékű value objektum példány azonosnak tekintendő. + + +Infrastrukturális kód +===================== + +A `Core/` (vagy `Infrastructure/`) mappa az alkalmazás technikai alapjának otthona. Az infrastrukturális kód tipikusan tartalmazza: + +/--pre +app/Core/ +├── Router/ ← routing és URL menedzsment +│ └── RouterFactory.php +├── Security/ ← authentikáció és autorizáció +│ ├── Authenticator.php +│ └── Authorizator.php +├── Logging/ ← logolás és monitoring +│ ├── SentryLogger.php +│ └── FileLogger.php +├── Cache/ ← cachovací réteg +│ └── FullPageCache.php +└── Integration/ ← integráció külső szolgáltatásokkal + ├── Slack/ + └── Stripe/ +\-- + +Kisebb projekteknél természetesen elegendő a lapos tagolás: + +/--pre +Core/ +├── RouterFactory.php +├── Authenticator.php +└── QueueMailer.php +\-- + +Olyan kódról van szó, amely: + +- Technikai infrastruktúrát old meg (routing, logolás, cacholás) +- Külső szolgáltatásokat integrál (Sentry, Elasticsearch, Redis) +- Alapszolgáltatásokat nyújt az egész alkalmazás számára (mail, adatbázis) +- Többnyire független a konkrét domaintól - a cache vagy a logger ugyanúgy működik egy webáruház vagy egy blog esetében. + +Bizonytalan, hogy egy adott osztály ide vagy a modellbe tartozik-e? A kulcsfontosságú különbség az, hogy a `Core/`-ban lévő kód: + +- Nem tud semmit a domainről (termékek, rendelések, cikkek) +- Többnyire átvihető egy másik projektbe +- Azt oldja meg, "hogyan működik" (hogyan küldjön e-mailt), nem pedig azt, "mit csinál" (milyen e-mailt küldjön) + +Példa a jobb megértéshez: + +- `App\Core\MailerFactory` - létrehozza az e-mailek küldésére szolgáló osztály példányait, kezeli az SMTP beállításokat +- `App\Model\OrderMailer` - használja a `MailerFactory`-t a rendelésekkel kapcsolatos e-mailek küldésére, ismeri azok sablonjait és tudja, mikor kell elküldeni őket + + +Parancssori szkriptek +===================== + +Az alkalmazásoknak gyakran kell tevékenységeket végezniük a szokásos HTTP kéréseken kívül - legyen szó akár háttérbeli adatfeldolgozásról, karbantartásról, vagy időszakos feladatokról. Futtatásukra egyszerű szkriptek szolgálnak a `bin/` könyvtárban, magát az implementációs logikát pedig az `app/Tasks/` (esetleg `app/Commands/`) mappába helyezzük. + +Példa: + +/--pre +app/Tasks/ +├── Maintenance/ ← karbantartó szkriptek +│ ├── CleanupCommand.php ← régi adatok törlése +│ └── DbOptimizeCommand.php ← adatbázis optimalizálása +├── Integration/ ← integráció külső rendszerekkel +│ ├── ImportProducts.php ← import a beszállítói rendszerből +│ └── SyncOrders.php ← rendelések szinkronizálása +└── Scheduled/ ← rendszeres feladatok + ├── NewsletterCommand.php ← hírlevelek kiküldése + └── ReminderCommand.php ← értesítések az ügyfeleknek +\-- + +Mi tartozik a modellbe és mi a parancssori szkriptekbe? Például egyetlen e-mail elküldésének logikája a modell része, több ezer e-mail tömeges kiküldése már a `Tasks/`-ba tartozik. + +A feladatokat általában [parancssorból |https://blog.nette.org/en/cli-scripts-in-nette-application] vagy cron segítségével futtatjuk. HTTP kérésen keresztül is futtathatók, de gondolni kell a biztonságra. A feladatot elindító presentert védeni kell, például csak bejelentkezett felhasználók számára, vagy erős tokennel és hozzáféréssel engedélyezett IP-címekről. Hosszú feladatok esetén növelni kell a szkript időkorlátját és használni kell a `session_write_close()`-t, hogy ne záródjon le a session. + + +További lehetséges könyvtárak +============================= + +Az említett alapkönyvtárakon kívül a projekt igényei szerint további specializált mappákat is hozzáadhat. Nézzük meg a leggyakoribbakat és azok használatát: + +/--pre +app/ +├── Api/ ← API logika, amely független a prezentációs rétegtől +├── Database/ ← migrációs szkriptek és seederek tesztadatokhoz +├── Components/ ← megosztott vizuális komponensek az egész alkalmazásban +├── Event/ ← hasznos, ha event-driven architektúrát használ +├── Mail/ ← e-mail sablonok és kapcsolódó logika +└── Utils/ ← segédosztályok +\-- + +Az alkalmazásban használt megosztott vizuális komponensekhez használható az `app/Components` vagy `app/Controls` mappa: + +/--pre +app/Components/ +├── Form/ ← megosztott űrlap komponensek +│ ├── SignInForm.php +│ └── UserForm.php +├── Grid/ ← komponensek adatlistázáshoz +│ └── DataGrid.php +└── Navigation/ ← navigációs elemek + ├── Breadcrumbs.php + └── Menu.php +\-- + +Ide tartoznak azok a komponensek, amelyek komplexebb logikával rendelkeznek. Ha komponenseket szeretne megosztani több projekt között, célszerű őket külön composer csomagba kivonni. + +Az `app/Mail` könyvtárba helyezheti az e-mail kommunikáció kezelését: + +/--pre +app/Mail/ +├── templates/ ← e-mail sablonok +│ ├── order-confirmation.latte +│ └── welcome.latte +└── OrderMailer.php +\-- + + +Presenterek map-elése +===================== + +A map-elés definiálja a szabályokat az osztály nevének levezetésére a presenter nevéből. Ezeket a [konfigurációban|configuration] adjuk meg az `application › mapping` kulcs alatt. + +Ezen az oldalon megmutattuk, hogy a presentereket az `app/Presentation` (esetleg `app/UI`) mappába helyezzük. Ezt a konvenciót közölnünk kell a Nette-vel a konfigurációs fájlban. Egyetlen sor elegendő: + +```neon +application: + mapping: App\Presentation\*\**Presenter +``` + +Hogyan működik a map-elés? A jobb megértés érdekében először képzeljünk el egy alkalmazást modulok nélkül. Azt szeretnénk, hogy a presenter osztályok az `App\Presentation` névtérbe essenek, hogy a `Home` presenter az `App\Presentation\HomePresenter` osztályra map-eljen. Ezt ezzel a konfigurációval érjük el: + +```neon +application: + mapping: App\Presentation\*Presenter +``` + +A map-elés úgy működik, hogy a `Home` presenter neve helyettesíti a csillagot az `App\Presentation\*Presenter` maszkban, így kapjuk meg az `App\Presentation\HomePresenter` végső osztálynevet. Egyszerű! + +Ahogy azonban a példákban ebben és más fejezetekben látható, a presenter osztályokat azonos nevű alkönyvtárakba helyezzük, például a `Home` presenter az `App\Presentation\Home\HomePresenter` osztályra map-el. Ezt a kettőspont megduplázásával érjük el (Nette Application 3.2-t igényel): + +```neon +application: + mapping: App\Presentation\**Presenter +``` + +Most térjünk át a presenterek modulokba való map-elésére. Minden modulhoz definiálhatunk specifikus map-elést: + +```neon +application: + mapping: + Front: App\Presentation\Front\**Presenter + Admin: App\Presentation\Admin\**Presenter + Api: App\Api\*Presenter +``` + +Ezen konfiguráció szerint a `Front:Home` presenter az `App\Presentation\Front\Home\HomePresenter` osztályra map-el, míg az `Api:OAuth` presenter az `App\Api\OAuthPresenter` osztályra. + +Mivel a `Front` és `Admin` modulok hasonló map-elési móddal rendelkeznek, és valószínűleg több ilyen modul lesz, létrehozható egy általános szabály, amely helyettesíti őket. Az osztály maszkjába így bekerül egy új csillag a modulhoz: + +```neon +application: + mapping: + *: App\Presentation\*\**Presenter + Api: App\Api\*Presenter +``` + +Ez mélyebben beágyazott könyvtárstruktúrák esetén is működik, mint például a `Admin:User:Edit` presenter, a csillaggal jelölt szegmens minden szinten megismétlődik, és az eredmény az `App\Presentation\Admin\User\Edit\EditPresenter` osztály. + +Alternatív jelölésként string helyett használhatunk egy három szegmensből álló tömböt. Ez a jelölés egyenértékű az előzővel: + +```neon +application: + mapping: + *: [App\Presentation, *, **Presenter] + Api: [App\Api, '', *Presenter] +``` diff --git a/application/hu/how-it-works.texy b/application/hu/how-it-works.texy index 59f923ab51..83bb986af3 100644 --- a/application/hu/how-it-works.texy +++ b/application/hu/how-it-works.texy @@ -3,99 +3,101 @@ Hogyan működnek az alkalmazások?
    -Ön jelenleg a Nette dokumentáció alapdokumentumát olvassa. Megismerheti a webes alkalmazások összes alapelvét. Szépen A-tól Z-ig, a születés pillanatától a PHP-szkript utolsó leheletéig. Az olvasás után tudni fogja: +Éppen a Nette dokumentáció alapdokumentumát olvassa. Megtudhatja a webalkalmazások működésének teljes elvét. Szépen A-tól Z-ig, a születés pillanatától a PHP szkript utolsó lélegzetvételéig. Az olvasás után tudni fogja: - hogyan működik az egész - mi az a Bootstrap, Presenter és DI konténer -- hogyan néz ki a könyvtárszerkezet +- hogyan néz ki a könyvtárstruktúra
    -Könyvtárszerkezet .[#toc-directory-structure] -============================================= +Könyvtárstruktúra +================= -Nyissa meg a [WebProject |https://github.com/nette/web-project] nevű webalkalmazás vázlatpéldáját, és megnézheti, hogy milyen fájlokat írnak le. +Nyissa meg a [WebProject|https://github.com/nette/web-project] nevű webalkalmazás skeleton példáját, és olvasás közben nézheti azokat a fájlokat, amelyekről szó van. -A könyvtárszerkezet valahogy így néz ki: +A könyvtárstruktúra valahogy így néz ki: /--pre web-project/ -├── app/ ← directory with application -│ ├── Presenters/ ← presenter classes -│ │ ├── HomePresenter.php ← Home presenter class -│ │ └── templates/ ← templates directory -│ │ ├── @layout.latte ← template of shared layout -│ │ └── Home/ ← templates for Home presenter -│ │ └── default.latte ← template for action `default` -│ ├── Router/ ← configuration of URL addresses -│ └── Bootstrap.php ← booting class Bootstrap -├── bin/ ← scripts for the command line -├── config/ ← configuration files +├── app/ ← alkalmazás könyvtára +│ ├── Core/ ← a működéshez szükséges alaposztályok +│ │ └── RouterFactory.php ← URL címek konfigurációja +│ ├── Presentation/ ← presenterek, sablonok & társai +│ │ ├── @layout.latte ← layout sablon +│ │ └── Home/ ← Home presenter könyvtára +│ │ ├── HomePresenter.php ← Home presenter osztálya +│ │ └── default.latte ← default akció sablonja +│ └── Bootstrap.php ← Bootstrap indító osztály +├─ assets/ ← erőforrások (SCSS, TypeScript, forrásképek) +├── bin/ ← parancssorból futtatott szkriptek +├── config/ ← konfigurációs fájlok │ ├── common.neon -│ └── local.neon -├── log/ ← error logs -├── temp/ ← temporary files, cache, … -├── vendor/ ← libraries installed by Composer +│ └── services.neon +├── log/ ← naplózott hibák +├── temp/ ← ideiglenes fájlok, cache, … +├── vendor/ ← Composer által telepített könyvtárak │ ├── ... -│ └── autoload.php ← autoloading of libs installed by Composer -├── www/ ← public directory, document root of project -│ ├── .htaccess ← mod_rewrite rules etc -│ └── index.php ← initial file that launches the application -└── .htaccess ← prohibits access to all directories except www +│ └── autoload.php ← az összes telepített csomag autoloadingja +├── www/ ← nyilvános könyvtár vagy a projekt document-rootja +│ ├──assets/ ← összeállított statikus fájlok (CSS, JS, képek, ...) +│ ├── .htaccess ← mod_rewrite szabályok +│ └── index.php ← elsődleges fájl, amellyel az alkalmazás elindul +└── .htaccess ← tiltja a hozzáférést minden könyvtárhoz a www kivételével \-- -A könyvtárszerkezetet bármilyen módon megváltoztathatja, átnevezhet vagy áthelyezhet mappákat, majd csak szerkesztheti a `log/` és a `temp/` elérési útvonalakat a `Bootstrap.php` fájlban, és az ehhez a fájlhoz vezető útvonalat a `composer.json` fájlban a `autoload` szakaszban. Semmi több, semmi bonyolult átkonfigurálás, semmi állandó változtatás. A Nette [intelligens automatikus felismeréssel |bootstrap#development-vs-production-mode] rendelkezik. +A könyvtárstruktúrát tetszés szerint módosíthatja, a mappákat átnevezheti vagy áthelyezheti, teljesen rugalmas. A Nette ráadásul okos automatikus felismeréssel rendelkezik, és automatikusan felismeri az alkalmazás helyét, beleértve annak URL alapját is. -Kicsit nagyobb alkalmazásoknál a bemutatókat és sablonokat tartalmazó mappákat alkönyvtárakra (a lemezen) és névterekre (a kódban) oszthatjuk, amelyeket [moduloknak |modules] nevezünk. +Kicsit nagyobb alkalmazásoknál a presenterek és sablonok mappáit [alkönyvtárakba tagolhatjuk |directory-structure#Presenterek és sablonok], az osztályokat pedig névterekbe, amelyeket moduloknak nevezünk. -A `www/` könyvtár a projekt nyilvános könyvtára vagy dokumentum-gyökere. Ezt átnevezhetjük anélkül, hogy az alkalmazás oldalán bármi mást be kellene állítanunk. Csak a [tárhelyet |nette:troubleshooting#How to change or remove www directory from URL] kell úgy [beállítania |nette:troubleshooting#How to change or remove www directory from URL], hogy a document-root ebbe a könyvtárba kerüljön. +A `www/` könyvtár az ún. nyilvános könyvtár vagy a projekt document-rootja. Átnevezheti anélkül, hogy bármit is be kellene állítania az alkalmazás oldalán. Csak a [hostingot kell konfigurálni |nette:troubleshooting#Hogyan lehet megváltoztatni vagy eltávolítani a www könyvtárat az URL-ből] úgy, hogy a document-root erre a könyvtárra mutasson. -A WebProjectet közvetlenül is letöltheti, beleértve a Nette-et is, a [Composer |best-practices:composer] segítségével: +A WebProjectet közvetlenül is letöltheti a Nette-vel együtt a [Composer |best-practices:composer] segítségével: ```shell composer create-project nette/web-project ``` -Linuxon vagy macOS-en állítsa be a `log/` és a `temp/` könyvtárak [írási engedélyeit |nette:troubleshooting#Setting directory permissions]. +Linuxon vagy macOS-en állítsa be a `log/` és `temp/` könyvtáraknak az [írási jogokat |nette:troubleshooting#Könyvtárjogosultságok beállítása]. -A WebProject alkalmazás készen áll a futtatásra, nincs szükség további konfigurálásra, és közvetlenül a böngészőben is megtekinthető a `www/` mappát elérve. +A WebProject alkalmazás készen áll a futtatásra, egyáltalán semmit nem kell konfigurálni, és azonnal megjelenítheti a böngészőben a `www/` mappához való hozzáféréssel. -HTTP-kérés .[#toc-http-request] -=============================== +HTTP kérés +========== -Minden akkor kezdődik, amikor a felhasználó megnyitja az oldalt a böngészőben, és a böngésző HTTP-kérelemmel kopogtat a szerveren. A kérés a `www/` nyilvános könyvtárban található PHP fájlhoz megy, amely a `index.php`. Tegyük fel, hogy ez a kérés a `https://example.com/product/123` fájlhoz kapcsolódik, és végrehajtásra kerül. +Minden akkor kezdődik, amikor a felhasználó megnyit egy oldalt a böngészőben. Tehát amikor a böngésző bekopogtat a szerverhez egy HTTP kéréssel. A kérés egyetlen PHP fájlra irányul, amely a `www/` nyilvános könyvtárban található, és ez az `index.php`. Tegyük fel, hogy a kérés a `https://example.com/product/123` címre vonatkozik. A megfelelő [szerverbeállításnak |nette:troubleshooting#Hogyan állítsuk be a szervert a szép URL-ekhez] köszönhetően ez az URL is az `index.php` fájlra map-elődik, és az végrehajtódik. -Feladata a következő: +Feladata: -1) a környezet inicializálása -2) megszerezni a gyárat -3) elindítani a Nette alkalmazást, amely a kérést kezeli. +1) inicializálni a környezetet +2) megszerezni a factory-t +3) elindítani a Nette alkalmazást, amely kezeli a kérést -Milyen gyárat? Mi nem traktorokat gyártunk, hanem weboldalakat! Várjon, mindjárt elmagyarázzuk. +Milyen factory-t? Hiszen nem traktorokat gyártunk, hanem weboldalakat! Várjon, mindjárt megmagyarázzuk. -A "környezet inicializálása" alatt például azt értjük, hogy a [Tracy |tracy:] aktiválódik, ami egy csodálatos eszköz a hibák naplózására vagy vizualizálására. Naplózza a hibákat a termelő szerveren, és közvetlenül a fejlesztői szerveren jeleníti meg azokat. Ezért az inicializálásnak azt is el kell döntenie, hogy az oldal termelő vagy fejlesztői üzemmódban fut-e. Ehhez a Nette automatikus felismerést használ: ha a webhelyet localhoston futtatja, akkor fejlesztői módban fut. Nem kell semmit sem konfigurálnia, és az alkalmazás készen áll mind a fejlesztői, mind a gyártói telepítésre. Ezeket a lépéseket a [Bootstrap osztályról |bootstrap] szóló fejezetben végezzük el és ismertetjük részletesen. +A „környezet inicializálása” alatt például azt értjük, hogy aktiválódik a [Tracy|tracy:], ami egy csodálatos eszköz a naplózáshoz vagy a hibák vizualizálásához. Éles szerveren naplózza a hibákat, fejlesztői szerveren pedig rögtön megjeleníti. Tehát az inicializáláshoz tartozik annak eldöntése is, hogy a web éles vagy fejlesztői módban fut-e. Ehhez a Nette [okos automatikus felismerést |bootstrapping#Fejlesztői vs éles mód] használ: ha a webet localhoston futtatja, fejlesztői módban fut. Így semmit sem kell konfigurálnia, és az alkalmazás rögtön készen áll mind a fejlesztésre, mind az éles bevetésre. Ezek a lépések végrehajtódnak és részletesen le vannak írva a [Bootstrap osztályról|bootstrapping] szóló fejezetben. -A harmadik pont (igen, a másodikat kihagytuk, de visszatérünk rá) az alkalmazás elindítása. A HTTP-kérések kezelését a Nette-ben a `Nette\Application\Application` osztály végzi (a továbbiakban `Application`), így amikor azt mondjuk, hogy "futtassunk egy alkalmazást", akkor azt értjük, hogy hívjunk meg egy `run()` nevű metódust ennek az osztálynak egy objektumán. +A harmadik pont (igen, a másodikat kihagytuk, de visszatérünk rá) az alkalmazás elindítása. A HTTP kérések kezelését a Nette-ben a `Nette\Application\Application` osztály (továbbiakban `Application`) végzi, tehát amikor azt mondjuk, hogy elindítjuk az alkalmazást, konkrétan ennek az osztálynak az objektumán hívjuk meg a találó nevű `run()` metódust. -A Nette egy olyan mentor, aki a bevált módszertanok segítségével vezet el a tiszta alkalmazások írásához. A legjobban bevált pedig az úgynevezett **dependency injection**, röviden DI. A DI magyarázatával most nem akarunk terhelni, hiszen van egy [külön fejezet |dependency-injection:introduction], a lényeg itt az, hogy a kulcsobjektumokat általában egy **DI container** (röviden DIC) nevű objektumgyár fogja létrehozni. Igen, ez az a gyár, amelyet nemrég említettünk. És létrehozza nekünk a `Application` objektumot is, tehát először is szükségünk van egy konténerre. Ezt megkapjuk a `Configurator` osztály segítségével, és hagyjuk, hogy létrehozza a `Application` objektumot, meghívjuk a `run()` metódust, és ez elindítja a Nette alkalmazást. Pontosan ez történik az [index.php |bootstrap#index.php] fájlban is. +A Nette egy mentor, amely a tiszta alkalmazások írására vezeti Önt a bevált módszertanok szerint. És az egyik leginkább bevált módszertan a **dependency injection**, röviden DI. Ebben a pillanatban nem akarjuk Önt a DI magyarázatával terhelni, erre van egy [külön fejezet|dependency-injection:introduction], a lényeges következmény az, hogy a kulcsfontosságú objektumokat általában egy objektumgyár hozza létre nekünk, amelyet **DI konténernek** (röviden DIC) neveznek. Igen, ez az a factory, amelyről az előbb szó volt. És ez gyártja nekünk az `Application` objektumot is, ezért először a konténerre van szükségünk. A `Configurator` osztály segítségével szerezzük meg, és hagyjuk, hogy létrehozza az `Application` objektumot, meghívjuk rajta a `run()` metódust, és ezzel elindul a Nette alkalmazás. Pontosan ez történik az [index.php |bootstrapping#index.php] fájlban. -Nette alkalmazás .[#toc-nette-application] -========================================== +Nette Application +================= -Az Application osztálynak egyetlen feladata van: válaszolni egy HTTP-kérésre. +Az Application osztálynak egyetlen feladata van: válaszolni a HTTP kérésre. -A Nette-ben írt alkalmazások sok úgynevezett prezenterre oszlanak (más keretrendszerekben találkozhatunk a controller kifejezéssel, ami ugyanez), amelyek egy adott weboldal oldalt reprezentáló osztályok: pl. honlap; termék a webáruházban; bejelentkezési űrlap; sitemap feed, stb. Az alkalmazásnak egytől akár több ezer prezenter is lehet. +A Nette-ben írt alkalmazások sok ún. presenter-re tagolódnak (más keretrendszerekben találkozhat a controller kifejezéssel, ez ugyanaz), amelyek olyan osztályok, amelyek mindegyike egy konkrét weboldalt képvisel: pl. a kezdőlapot; egy terméket a webáruházban; a bejelentkezési űrlapot; a sitemap feedet stb. Az alkalmazásnak egytől több ezer presenterig terjedhet a száma. -Az alkalmazás azzal indul, hogy az ún. routertől kéri, hogy döntse el, hogy az aktuális kérést melyik prezenternek adja át feldolgozásra. A router dönti el, hogy kinek a felelőssége. Megnézi a bemeneti URL-t `https://example.com/product/123`, aki a `show` egy terméket `id: 123` művelettel akarja ellátni. Jó szokás a prezenter + akció párokat kettősponttal elválasztva `Product:show`-ként írni. +Az Application azzal kezdi, hogy megkéri az ún. routert, hogy döntse el, melyik presenternek adja át az aktuális kérést feldolgozásra. A router eldönti, kié a felelősség. Megnézi a bemeneti URL-t `https://example.com/product/123`, és attól függően, hogyan van beállítva, eldönti, hogy ez például a `Product` **presenter** munkája, amelytől **akcióként** a termék megjelenítését (`show`) kéri `id: 123`-mal. A presenter + akció párt jó szokás kettősponttal elválasztva írni, mint `Product:show`. -Tehát a router az URL-t átalakította `Presenter:action` + paraméterek párrá, esetünkben `Product:show` + `id: 123`. A `app/Router/RouterFactory.php` fájlban láthatjuk, hogyan néz ki egy útválasztó, és ezt részletesen az [Útválasztás |Routing] fejezetben fogjuk leírni. +Tehát a router átalakította az URL-t egy `Presenter:action` párra + paraméterekre, esetünkben `Product:show` + `id: 123`. Hogy néz ki egy ilyen router, megnézheti az `app/Core/RouterFactory.php` fájlban, és részletesen leírjuk a [Routing | Routing] fejezetben. -Lépjünk tovább. Az alkalmazás már ismeri a bemutató nevét, és folytathatja. Egy `ProductPresenter` objektum létrehozásával, amely a `Product` bemutató kódja. Pontosabban megkéri a DI konténert a prezenter létrehozására, mert az objektumok előállítása az ő feladata. +Menjünk tovább. Az Application már ismeri a presenter nevét, és folytathatja. Azzal, hogy létrehozza a `ProductPresenter` osztály objektumát, ami a `Product` presenter kódja. Pontosabban szólva, megkéri a DI konténert, hogy hozza létre a presentert, mert a gyártás az ő feladata. -A prezenter így nézhet ki: +A presenter például így nézhet ki: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -107,98 +109,92 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function renderShow(int $id): void { - // adatokat kapunk a modellből és átadjuk a sablonhoz. + // adatokat szerzünk a modellből és átadjuk a sablonnak $this->template->product = $this->repository->getProduct($id); } } ``` -A kérést a prezenter kezeli. A feladat pedig egyértelmű: a `show` művelet elvégzése a `id: 123` címen. Ami a prezenterek nyelvén azt jelenti, hogy meghívja a `renderShow()` metódust, és a `$id` paraméterében a `123` kapja meg. +A kérés feldolgozását a presenter veszi át. És a feladat világos: hajtsa végre a `show` akciót `id: 123`-mal. Ami a presenterek nyelvén azt jelenti, hogy meghívódik a `renderShow()` metódus, és a `$id` paraméterben megkapja a `123`-at. -Egy prezenter több akciót is kezelhet, azaz több metódusa is lehet. `render()`. De javasoljuk, hogy a prezentereket egy vagy a lehető legkevesebb művelettel tervezzük. +A presenter több akciót is kezelhet, tehát több `render()` metódusa lehet. De javasoljuk olyan presenterek tervezését, amelyeknek egy vagy a lehető legkevesebb akciója van. -Tehát a `renderShow(123)` metódust hívtuk meg, amelynek kódja fiktív példa, de láthatjuk rajta, hogy az adatokat hogyan adjuk át a sablonba, azaz a `$this->template` címre írva . +Tehát meghívódott a `renderShow(123)` metódus, amelynek kódja ugyan kitalált példa, de láthatja rajta, hogyan adunk át adatokat a sablonnak, azaz a `$this->template`-be írással. -Ezt követően a prezenter visszaadja a választ. Ez lehet egy HTML oldal, egy kép, egy XML dokumentum, egy fájl elküldése a lemezről, JSON vagy egy másik oldalra való átirányítás. Fontos, hogy ha nem mondjuk meg kifejezetten, hogyan kell válaszolni (ami a `ProductPresenter` esetében a helyzet), akkor a válasz az lesz, hogy a sablon egy HTML-oldallal jeleníti meg a sablont. Hogy miért? Nos, mert az esetek 99%-ában egy sablont szeretnénk kirajzolni, így a prezentáló ezt a viselkedést veszi alapértelmezettnek, és a mi munkánkat akarja megkönnyíteni. Ez a Nette lényege. +Ezután a presenter visszaadja a választ. Ez lehet egy HTML oldal, egy kép, egy XML dokumentum, egy fájl elküldése a lemezről, JSON, vagy akár átirányítás egy másik oldalra. Fontos, hogy ha explicit módon nem mondjuk meg, hogyan válaszoljon (ami a `ProductPresenter` esete), akkor a válasz egy HTML oldalt tartalmazó sablon renderelése lesz. Miért? Mert az esetek 99%-ában sablont szeretnénk renderelni, ezért a presenter ezt a viselkedést veszi alapértelmezettnek, és meg akarja könnyíteni a munkánkat. Ez a Nette lényege. -Még csak meg sem kell adnunk, hogy melyik sablont rajzoljuk ki, egyszerű logika szerint levezeti az oda vezető utat. A presenter `Product` és az action `show` esetében megnézi, hogy létezik-e valamelyik sablonfájl a `ProductPresenter` osztály könyvtárához képest, ahol a osztály található: +Még azt sem kell megadnunk, hogy melyik sablont renderelje, az útvonalat maga vezeti le. A `show` akció esetében egyszerűen megpróbálja betölteni a `show.latte` sablont a `ProductPresenter` osztályt tartalmazó könyvtárban. Ugyanígy megpróbálja megtalálni a layoutot az `@layout.latte` fájlban (részletesebben a [sablonok kereséséről |templates#Sablonok keresése]). -- `templates/Product/show.latte` -- `templates/Product.show.latte` - -Megpróbálja megtalálni az elrendezést is a `@layout.latte` fájlban, majd rendereli a sablont. Ezzel a prezenter és az egész alkalmazás feladata befejeződött. Ha a sablon nem létezik, akkor egy 404-es hibaüzenetű oldal fog visszakerülni. A prezenterekről bővebben a [Prezenterek |Presenters] oldalon olvashat. +És ezután rendereli a sablonokat. Ezzel a presenter és az egész alkalmazás feladata befejeződött, és a mű elkészült. Ha a sablon nem létezne, 404-es hibaoldal jelenne meg. Többet a presenterekről a [Presenterek|presenters] oldalon olvashat. [* request-flow.svg *] -A biztonság kedvéért próbáljuk meg az egész folyamatot egy kicsit más URL-címmel felidézni: +Biztonság kedvéért próbáljuk meg összefoglalni az egész folyamatot egy kicsit más URL-lel: -1) az URL a következő lesz `https://example.com` -2) elindítjuk az alkalmazást, létrehozunk egy konténert és futtatjuk `Application::run()` -3) a router dekódolja az URL-t, mint egy párat `Home:default` -4) létrejön egy `HomePresenter` objektum -5) a `renderDefault()` metódust meghívjuk (ha létezik) -6) egy `templates/Home/default.latte` sablon `templates/@layout.latte` elrendezéssel megjelenik. +1) Az URL `https://example.com` lesz +2) Indítjuk az alkalmazást, létrejön a konténer és elindul az `Application::run()` +3) A router dekódolja az URL-t `Home:default` párként +4) Létrejön a `HomePresenter` osztály objektuma +5) Meghívódik a `renderDefault()` metódus (ha létezik) +6) Renderelődik a sablon, pl. `default.latte` a layouttal, pl. `@layout.latte` -Lehet, hogy most sok új fogalommal találkoztál, de úgy gondoljuk, hogy van értelme. Az alkalmazások létrehozása a Nette-ben gyerekjáték. +Talán most sok új fogalommal találkozott, de reméljük, hogy van értelmük. Alkalmazások fejlesztése a Nette-ben óriási kényelem. -Sablonok .[#toc-templates] -========================== +Sablonok +======== -A sablonokat illetően a Nette a [Latte |latte:] sablonrendszert használja. Ezért a sablonokat tartalmazó fájlok a `.latte` végződéssel végződnek. A Latte-t azért használjuk, mert ez a legbiztonságosabb sablonrendszer a PHP számára, ugyanakkor a legintuitívabb rendszer. Nem kell sok újat tanulnod, csak ismerned kell a PHP-t és néhány Latte taget. Mindent megtudhatsz [a dokumentációból |latte:]. +Ha már szóba kerültek a sablonok, a Nette a [Latte |latte:] sablonrendszert használja. Ezért is vannak a `.latte` kiterjesztések a sablonoknál. A Latte-t egyrészt azért használják, mert ez a legbiztonságosabb sablonrendszer PHP-hoz, másrészt pedig a legintuitívabb rendszer. Nem kell sok újat tanulnia, elegendő a PHP ismerete és néhány tag. Mindent megtudhat [a dokumentációban |templates]. -A sablonban [létrehozunk egy linket |creating-links] más előadókhoz és akciókhoz az alábbiak szerint: +A sablonban [linkeket hozunk létre |creating-links] más presenterekhez és akciókhoz így: ```latte -product detail +termék részletei ``` -Egyszerűen csak írja a megszokott `Presenter:action` párost a valódi URL helyett, és adjon meg minden paramétert. A trükk a `n:href`, amely azt mondja, hogy ezt az attribútumot a Nette fogja feldolgozni. És ez generálni fogja: +Egyszerűen a valós URL helyett írja be az ismert `Presenter:action` párt, és adja meg az esetleges paramétereket. A trükk az `n:href`-ben van, amely azt mondja, hogy ezt az attribútumot a Nette dolgozza fel. És generálja: ```latte -product detail +termék részletei ``` -Az URL generálásáért a korábban említett router felel. Valójában a Nette routerek egyedülállóak abban, hogy nem csak URL-ből prezenter:action párossá történő átalakításokat tudnak elvégezni, hanem fordítva is képesek URL-t generálni a prezenter + action + paraméterek nevéből. -Ennek köszönhetően a Nette-ben teljesen megváltoztathatja az URL formáját az egész kész alkalmazásban anélkül, hogy egyetlen karaktert is megváltoztatna a sablonban vagy a prezenterben, csupán a router módosításával. -És ennek köszönhetően működik az úgynevezett kanonizáció, ami a Nette másik egyedülálló funkciója, amely javítja a SEO-t (az internetes kereshetőség optimalizálása) azáltal, hogy automatikusan megakadályozza a duplikált tartalmak létezését a különböző URL-eken. -Sok programozó ezt elképesztőnek találja. +Az URL generálását a már korábban említett router végzi. Ugyanis a Nette routerei kivételesek abban, hogy nemcsak az URL-ből tudnak átalakítást végezni presenter:action párra, hanem fordítva is, azaz a presenter nevéből + akcióból + paraméterekből URL-t generálni. Ennek köszönhetően a Nette-ben teljesen megváltoztathatja az URL-ek formáját egy kész alkalmazásban anélkül, hogy egyetlen karaktert is megváltoztatna a sablonban vagy a presenterben. Csak a router módosításával. Ennek köszönhetően működik az ún. kanonizáció is, ami a Nette egy másik egyedülálló tulajdonsága, amely hozzájárul a jobb SEO-hoz (keresőoptimalizálás) azáltal, hogy automatikusan megakadályozza a duplikált tartalom létezését különböző URL-eken. Sok programozó ezt lenyűgözőnek tartja. -Interaktív komponensek .[#toc-interactive-components] -===================================================== +Interaktív komponensek +====================== -Még egy dolgot el kell mondanunk a prezenterekről: rendelkeznek egy beépített komponensrendszerrel. Az idősebbek talán emlékeznek valami hasonlóra a Delphiből vagy az ASP.NET Web Formsből. A React vagy a Vue.js valami távolról hasonlóra épül. A PHP keretrendszerek világában ez egy teljesen egyedülálló funkció. +A presenterekről még egy dolgot el kell árulnunk: beépített komponensrendszerük van. Valami hasonlót a Delphi vagy az ASP.NET Web Forms ismerői ismerhetnek, valami távolról hasonlóra épül a React vagy a Vue.js is. A PHP keretrendszerek világában ez teljesen egyedülálló dolog. -A komponensek különálló, újrafelhasználható egységek, amelyeket oldalakba (azaz prezenterekbe) helyezünk. Ezek lehetnek [űrlapok |forms:in-presenter], [adagramok |https://componette.org/contributte/datagrid/], menük, közvélemény-kutatások, tulajdonképpen bármi, amit érdemes ismételten használni. Készíthetünk saját komponenseket, vagy használhatjuk az opensource komponensek [hatalmas választékának |https://componette.org] valamelyikét. +A komponensek önálló, újrafelhasználható egységek, amelyeket oldalakba (azaz presenterekbe) illesztünk be. Lehetnek [űrlapok |forms:in-presenter], [datagrid-ek |https://componette.org/contributte/datagrid/], menük, szavazófelületek, valójában bármi, amit érdemes ismételten használni. Létrehozhatunk saját komponenseket, vagy használhatunk néhányat a [hatalmas kínálatból |https://componette.org] származó nyílt forráskódú komponensek közül. -A komponensek alapvetően megváltoztatják az alkalmazásfejlesztés megközelítését. Új lehetőségeket nyitnak meg az oldalak előre definiált egységekből történő összeállítására. És van valami közös bennük [Hollywooddal |components#Hollywood style]. +A komponensek alapvetően befolyásolják az alkalmazásfejlesztési megközelítést. Új lehetőségeket nyitnak meg az oldalak előre elkészített egységekből való összeállítására. És ráadásul van valami közös bennük a [Hollywooddal |components#Hollywood style]. -DI konténer és konfiguráció .[#toc-di-container-and-configuration] -================================================================== +DI konténer és konfiguráció +=========================== -A DI konténer (az objektumok gyára) az egész alkalmazás szíve. +A DI konténer vagy objektumgyár az egész alkalmazás szíve. -Ne aggódj, ez nem egy varázslatos fekete doboz, mint ahogy az az előző szavakból látszik. Valójában ez egy elég unalmas PHP osztály, amelyet a Nette generál és egy cache könyvtárban tárol. Rengeteg metódusa van, amelyeket `createServiceAbcd()` néven neveznek, és mindegyik létrehoz és visszaad egy objektumot. Igen, van egy `createServiceApplication()` metódus is, amely a `Nette\Application\Application` létrehozza, amelyre a `index.php` fájlban volt szükségünk az alkalmazás futtatásához. És vannak metódusok az egyes előadók előállítására. És így tovább. +Ne aggódjon, ez nem egy varázslatos fekete doboz, ahogy talán az előző sorokból tűnhetett. Valójában ez egy meglehetősen unalmas PHP osztály, amelyet a Nette generál és a cache könyvtárba ment. Rengeteg `createServiceAbcd()` nevű metódusa van, és mindegyik tud létrehozni és visszaadni valamilyen objektumot. Igen, van ott egy `createServiceApplication()` metódus is, amely létrehozza a `Nette\Application\Application`-t, amire szükségünk volt az `index.php` fájlban az alkalmazás elindításához. És vannak metódusok, amelyek az egyes presentereket gyártják. És így tovább. -Az objektumokat, amelyeket a DI konténer hoz létre, valamiért szolgáltatásoknak hívják. +Azokat az objektumokat, amelyeket a DI konténer létrehoz, valamilyen okból szolgáltatásoknak nevezik. -Ami igazán különleges ebben az osztályban, hogy nem te programozod, hanem a keretrendszer. Valójában ez generálja a PHP kódot, és elmenti a lemezre. Te csak utasításokat adsz arra vonatkozóan, hogy a konténer milyen objektumokat és pontosan hogyan tudjon előállítani. Ezek az utasítások pedig [NEON formátumú |neon:format] [konfigurációs fájlokban |bootstrap#DI Container Configuration] íródnak, ezért a `.neon` kiterjesztéssel rendelkeznek. +Ami ebben az osztályban igazán különleges, az az, hogy nem Ön programozza, hanem a keretrendszer. Valóban PHP kódot generál és elmenti a lemezre. Ön csak utasításokat ad, hogy milyen objektumokat tudjon a konténer gyártani és pontosan hogyan. És ezek az utasítások a [konfigurációs fájlokban |bootstrapping#DI konténer konfigurálása] vannak leírva, amelyekhez a [NEON|neon:format] formátumot használják, és ezért `.neon` kiterjesztésük van. -A konfigurációs fájlok pusztán a DI konténer utasítására szolgálnak. Így például, ha a [munkamenet |http:configuration#Session] szakaszban megadom a `expiration: 14 days` opciót, akkor a DI konténer a munkamenetet reprezentáló `Nette\Http\Session` objektum létrehozásakor meghívja annak `setExpiration('14 days')` metódusát, és így a konfiguráció valósággá válik. +A konfigurációs fájlok tisztán a DI konténer instruálására szolgálnak. Tehát ha például a [session |http:configuration#Session] szekcióban megadom az `expiration: 14 days` opciót, akkor a DI konténer a sessiont reprezentáló `Nette\Http\Session` objektum létrehozásakor meghívja annak `setExpiration('14 days')` metódusát, és ezzel a konfiguráció valósággá válik. -Egy egész fejezet áll rendelkezésünkre, amely leírja, hogy mit lehet [konfigurálni |nette:configuring], és hogyan [definiálhatjuk a saját szolgáltatásainkat |dependency-injection:services]. +Van itt Önnek egy egész fejezet, amely leírja, mit lehet [konfigurálni |nette:configuring] és hogyan lehet [saját szolgáltatásokat definiálni |dependency-injection:services]. -Amint belekezdünk a szolgáltatások létrehozásába, találkozunk az [autowiring |dependency-injection:autowiring] szóval. Ez egy olyan szerkentyű, amely hihetetlenül megkönnyíti az életét. Automatikusan át tud adni objektumokat ott, ahol szükséged van rájuk (például az osztályaid konstruktoraiban), anélkül, hogy bármit is tenned kellene. Úgy fogod találni, hogy a DI konténer a Nette-ben egy kis csoda. +Amint egy kicsit belemerül a szolgáltatások létrehozásába, találkozni fog az [autowiring |dependency-injection:autowiring] szóval. Ez egy olyan trükk, amely hihetetlenül leegyszerűsíti az életét. Képes automatikusan átadni az objektumokat oda, ahol szüksége van rájuk (például az osztályai konstruktoraiban), anélkül, hogy bármit is tennie kellene. Rájön majd, hogy a Nette DI konténere egy kis csoda. -Mi a következő lépés? .[#toc-what-next] -======================================= +Merre tovább? +============= -Végigvettük a Nette alkalmazások alapelveit. Eddig nagyon felületesen, de hamarosan elmerülhetsz a mélységekben, és végül csodálatos webes alkalmazásokat hozhatsz létre. Hol folytassuk? Kipróbálta már az [Első alkalmazás létrehozása |quickstart:] című bemutatót? +Áttekintettük a Nette alkalmazások alapelveit. Eddig nagyon felületesen, de hamarosan mélyebbre hatol, és idővel csodálatos webalkalmazásokat fog létrehozni. Merre tovább? Kipróbálta már az [Első alkalmazás írása|quickstart:] tutorialt? -A fentieken kívül a Nette egy egész arzenálnyi [hasznos osztállyal |utils:], [adatbázis réteggel |database:] stb. rendelkezik. Próbálja ki szándékosan csak a dokumentációt átkattintani. Vagy látogasson el a [blogra |https://blog.nette.org]. Sok érdekes dolgot fogsz felfedezni. +A fent leírtakon kívül a Nette egész arzenáljával rendelkezik [hasznos osztályoknak|utils:], [adatbázis rétegnek|database:], stb. Próbálja meg csak úgy átkattintgatni a dokumentációt. Vagy a [blogot|https://blog.nette.org]. Sok érdekes dolgot fog felfedezni. -Hagyd, hogy a keretrendszer sok örömet szerezzen neked 💙 +Hozzon a keretrendszer sok örömet Önnek 💙 diff --git a/application/hu/modules.texy b/application/hu/modules.texy deleted file mode 100644 index 5dde2d1a36..0000000000 --- a/application/hu/modules.texy +++ /dev/null @@ -1,148 +0,0 @@ -Modulok -******* - -.[perex] -A Nette-ben a modulok az alkalmazást alkotó logikai egységeket jelentik. Ide tartoznak a prezenterek, sablonok, esetleg komponensek és modellosztályok. - -Egy könyvtár a bemutatóknak és egy a sablonoknak nem lenne elég a valódi projektekhez. Több tucat fájl egy mappában való elhelyezése legalábbis rendezetlen. Hogyan szabadulhatunk meg ettől? Egyszerűen felosztjuk őket alkönyvtárakra a lemezen és névterekre a kódban. És pontosan ezt teszik a Nette modulok. - -Felejtsük el tehát az egyetlen mappát az előadóknak és a sablonoknak, és helyette hozzunk létre modulokat, például a `Admin` és a `Front`. - -/--pre -app/ -├── Presenters/ -├── Modules/ ← directory with modules -│ ├── Admin/ ← module Admin -│ │ ├── Presenters/ ← its presenters -│ │ │ ├── DashboardPresenter.php -│ │ │ └── templates/ -│ └── Front/ ← module Front -│ └── Presenters/ ← its presenters -│ └── ... -\-- - -Ezt a könyvtárszerkezetet az osztályok névterei is tükrözni fogják, így például a `DashboardPresenter` a `App\Modules\Admin\Presenters` névtérben lesz: - -```php -namespace App\Modules\Admin\Presenters; - -class DashboardPresenter extends Nette\Application\UI\Presenter -{ - // ... -} -``` - -A `Dashboard` prezenterre a `Admin` modulon belül az alkalmazáson belül a kettőspont jelöléssel `Admin:Dashboard`, a `default` műveletre pedig `Admin:Dashboard:default` néven hivatkozunk. -És honnan tudja a Nette proper, hogy a `Admin:Dashboard` a `App\Modules\Admin\Presenters\DashboardPresenter` osztályt képviseli? Ezt a konfigurációban történő [leképezéssel |#mapping] határozzuk meg. -A megadott struktúra tehát nem keményen meghatározott, és Ön az igényeinek megfelelően módosíthatja. - -A modulok természetesen a prezentereken és sablonokon kívül minden más elemet is tartalmazhatnak, például komponenseket, modellosztályokat stb. - - -Beágyazott modulok .[#toc-nested-modules] ------------------------------------------ - -A moduloknak nem kell csak sima struktúrát alkotniuk, létrehozhatunk például almodulokat is: - -/--pre -app/ -├── Modules/ ← directory with modules -│ ├── Blog/ ← module Blog -│ │ ├── Admin/ ← submodule Admin -│ │ │ ├── Presenters/ -│ │ │ └── ... -│ │ └── Front/ ← submodule Front -│ │ ├── Presenters/ -│ │ └── ... -│ ├── Forum/ ← module Forum -│ │ └── ... -\-- - -Így a `Blog` modul `Admin` és `Front` almodulokra oszlik. Ez ismét tükröződik a névterekben, amelyek a `App\Modules\Blog\Admin\Presenters` stb. lesznek. Az almodulon belüli `Dashboard` bemutatót `Blog:Admin:Dashboard` néven említjük. - -A beágyazás tetszőlegesen mélyre mehet, így al-almodulok hozhatók létre. - - -Linkek létrehozása .[#toc-creating-links] ------------------------------------------ - -A bemutatósablonokban lévő hivatkozások az aktuális modulhoz viszonyítva vannak. Így a `Foo:default` hivatkozás a `Foo` bemutatóhoz vezet, amely ugyanabban a modulban található, mint az aktuális bemutató. Ha az aktuális modul például a `Front`, akkor a link így néz ki: - -```latte -link to Front:Product:show -``` - -A hivatkozás akkor is relatív, ha egy modul nevét tartalmazza, amely ilyenkor almodulnak minősül: - -```latte -link to Front:Shop:Product:show -``` - -Az abszolút hivatkozások a lemezen lévő abszolút elérési utakhoz hasonlóan íródnak, de a kettőspontok helyett kettőspontokkal. Az abszolút link tehát kettősponttal kezdődik: - -```latte -link to Admin:Product:show -``` - -Hogy megtudjuk, hogy egy adott modulban vagy annak almoduljában vagyunk-e, használhatjuk a `isModuleCurrent(moduleName)` függvényt. - -```latte -
  • - ... -
  • -``` - - -Útválasztás .[#toc-routing] ---------------------------- - -Lásd [az útválasztásról szóló fejezetet |routing#Modules]. - - -Feltérképezés .[#toc-mapping] ------------------------------ - -Meghatározza azokat a szabályokat, amelyek alapján az osztály neve az előadó nevéből származik. Ezeket a [konfigurációban |configuration] a `application › mapping` kulcs alatt írjuk le. - -Kezdjük egy olyan példával, amely nem használ modulokat. Csak azt akarjuk, hogy a prezenter osztályok a `App\Presenters` névtérrel rendelkezzenek. Ez azt jelenti, hogy egy olyan prezenternek, mint a `Home`, a `App\Presenters\HomePresenter` osztályhoz kell kapcsolódnia. Ezt a következő konfigurációval érhetjük el: - -```neon -application: - mapping: - *: App\Presenters\*Presenter -``` - -Az osztálymaszkban a prezenter nevét csillaggal helyettesítjük, és az eredmény az osztály neve lesz. Easy! - -Ha az előadókat modulokra osztjuk, akkor minden modulhoz saját leképezésünk lehet: - -```neon -application: - mapping: - Front: App\Modules\Front\Presenters\*Presenter - Admin: App\Modules\Admin\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -Most a `Front:Home` bemutatót a `App\Modules\Front\Presenters\HomePresenter` osztályra, a `Admin:Dashboard` bemutatót pedig a `App\Modules\Admin\Presenters\DashboardPresenter` osztályra képezzük le. - -Praktikusabb egy általános (csillag) szabályt létrehozni az első kettő helyett. Az extra csillagot csak a modul számára adjuk hozzá az osztálymaszkhoz: - -```neon -application: - mapping: - *: App\Modules\*\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -De mi van akkor, ha egymásba ágyazott modulokat használunk, és van egy bemutató `Admin:User:Edit`? Ebben az esetben a modult jelképező csillaggal ellátott szegmens minden szinten egyszerűen megismétlődik, és az eredmény a `App\Modules\Admin\User\Presenters\EditPresenter` osztály lesz. - -Egy alternatív jelölés az, hogy a karakterlánc helyett egy három szegmensből álló tömböt használunk. Ez a jelölés egyenértékű az előzővel: - -```neon -application: - mapping: - *: [App\Modules, *, Presenters\*Presenter] -``` - -Az alapértelmezett érték a `*: *Module\*Presenter`. diff --git a/application/hu/multiplier.texy b/application/hu/multiplier.texy index 9f47a9947c..4844ec74b2 100644 --- a/application/hu/multiplier.texy +++ b/application/hu/multiplier.texy @@ -1,30 +1,32 @@ -Szorzó: Dinamikus összetevők -**************************** +Multiplier: dinamikus komponensek +********************************* -Interaktív komponensek dinamikus létrehozásának eszköze .[perex] +.[perex] +Eszköz interaktív komponensek dinamikus létrehozásához -Kezdjük egy tipikus problémával: van egy terméklistánk egy e-kereskedelmi oldalon, és minden termékhez szeretnénk egy *add to cart* űrlapot mellékelni. Az egyik lehetőség, hogy az egész listát egyetlen űrlapba csomagoljuk. Kényelmesebb megoldás a [api:Nette\Application\UI\Multiplier]. +Induljunk ki egy tipikus példából: van egy terméklistánk egy webáruházban, és mindegyiknél szeretnénk kiírni egy űrlapot a termék kosárba helyezéséhez. Az egyik lehetséges változat az egész listát egyetlen űrlapba csomagolni. Sokkal kényelmesebb módszert kínál azonban a [api:Nette\Application\UI\Multiplier]. -A Multiplier lehetővé teszi, hogy több komponenshez gyárat definiáljunk. Ez az egymásba ágyazott komponensek elvén alapul - minden egyes komponens, amely a [api:Nette\ComponentModel\Container] -tól örököl, tartalmazhat más komponenseket. +A Multiplier lehetővé teszi több komponenshez tartozó factory kényelmes definiálását. Az beágyazott komponensek elvén működik - minden [api:Nette\ComponentModel\Container]-től öröklődő komponens tartalmazhat további komponenseket. -Lásd a dokumentációban a [komponensmodellt |components#Components in Depth]. .[tip] +.[tip] +Lásd a [komponens modellről |components#Komponensek mélységében] szóló fejezetet a dokumentációban vagy [Honza Tvrdík előadását|https://www.youtube.com/watch?v=8y3LLexWu-I]. -A Multiplier olyan szülő komponensként pózol, amely dinamikusan létrehozhatja gyermekeit a konstruktorban átadott visszahívás segítségével. Lásd a példát: +A Multiplier lényege, hogy szülőként lép fel, aki a leszármazottait dinamikusan tudja létrehozni a konstruktorban átadott callback segítségével. Lásd a példát: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function () { $form = new Nette\Application\UI\Form; - $form->addInteger('amount', 'Amount:') + $form->addInteger('count', 'Termékek száma:') ->setRequired(); - $form->addSubmit('send', 'Add to cart'); + $form->addSubmit('send', 'Kosárba'); return $form; }); } ``` -A sablonban minden termékhez egy űrlapot tudunk renderelni - és minden űrlap valóban egyedi komponens lesz. +Most a sablonban egyszerűen minden terméknél megjeleníthetjük az űrlapot - és mindegyik valóban egyedi komponens lesz. ```latte {foreach $items as $item} @@ -35,26 +37,26 @@ A sablonban minden termékhez egy űrlapot tudunk renderelni - és minden űrlap {/foreach} ``` -A `{control}` tagnek átadott argumentum szerint: +A `{control}` tagben átadott argumentum formátuma a következőt mondja: -1. egy komponens kinyerése `shopForm` -2. és adja vissza a gyermekét `$item->id` +1. szerezd meg a `shopForm` komponenst +2. és abból szerezd meg a `$item->id` leszármazottat -Az **1.** első hívása során a `shopForm` komponens még nem létezik, ezért a `createComponentShopForm` metódust hívjuk meg a létrehozásához. Ezután egy névtelen függvényt hívunk meg, amelyet paraméterként átadunk a Multiplier-nek, és létrehozunk egy űrlapot. +Az **1.** pont első hívásakor a `shopForm` még nem létezik, ezért meghívódik a `createComponentShopForm` factory-ja. A megszerzett komponensen (a Multiplier példányán) ezután meghívódik a konkrét űrlap factory-ja - ami az az anonim függvény, amelyet a Multipliernek a konstruktorban átadtunk. -A `foreach` további iterációiban a `createComponentShopForm` metódus már nem hívódik meg, mivel a komponens már létezik. Mivel azonban egy másik gyermekre hivatkozunk (`$item->id` változik az iterációk között), ismét meghívódik egy névtelen függvény, és létrejön egy új űrlap. +A foreach következő iterációjában a `createComponentShopForm` metódus már nem hívódik meg (a komponens létezik), de mivel egy másik leszármazottját keressük (`$item->id` minden iterációban más lesz), újra meghívódik az anonim függvény, és visszaad nekünk egy új űrlapot. -Az utolsó dolog, hogy biztosítsuk, hogy az űrlap valóban a megfelelő terméket adja hozzá a kosárhoz, mert a jelenlegi állapotban minden űrlap egyforma, és nem tudjuk megkülönböztetni, hogy melyik termékhez tartoznak. Ehhez használhatjuk a Multiplier (és általában a Nette Frameworkben minden komponensgyári metódusnak) azt a tulajdonságát, hogy minden komponensgyári metódus első argumentumként megkapja a létrehozott komponens nevét. A mi esetünkben ez a `$item->id`, amire pontosan szükségünk van az egyes termékek megkülönböztetéséhez. Mindössze annyit kell tennünk, hogy módosítjuk az űrlap létrehozására szolgáló kódot: +Az egyetlen dolog, ami hátra van, az annak biztosítása, hogy az űrlap valóban azt a terméket adja hozzá a kosárhoz, amelyet kell - jelenleg az űrlap minden terméknél teljesen azonos. Ebben segít a Multiplier (és általában minden komponens factory a Nette Frameworkben) tulajdonsága, mégpedig az, hogy minden factory első argumentumként megkapja a létrehozott komponens nevét. Esetünkben ez `$item->id` lesz, ami pontosan az az adat, amire szükségünk van. Tehát csak kissé módosítani kell az űrlap létrehozását: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function ($itemId) { $form = new Nette\Application\UI\Form; - $form->addInteger('amount', 'Amount:') + $form->addInteger('count', 'Termékek száma:') ->setRequired(); $form->addHidden('itemId', $itemId); - $form->addSubmit('send', 'Add to cart'); + $form->addSubmit('send', 'Kosárba'); return $form; }); } diff --git a/application/hu/presenters.texy b/application/hu/presenters.texy index f0bbd1a1ff..b9708a6c70 100644 --- a/application/hu/presenters.texy +++ b/application/hu/presenters.texy @@ -1,39 +1,39 @@ -Előadók -******* +Presenterek +***********
    -Megtanuljuk, hogyan kell prezentereket és sablonokat írni a Nette-ben. Az olvasás után tudni fogja: +Megismerkedünk azzal, hogyan írjunk presentereket és sablonokat a Nette-ben. Az olvasás után tudni fogja: -- hogyan működik a prezenter -- mik a tartós paraméterek -- hogyan kell megjeleníteni egy sablont +- hogyan működik a presenter +- mik azok a perzisztens paraméterek +- hogyan renderelődnek a sablonok
    -[Azt már tudjuk |how-it-works#nette-application], hogy a prezenter egy olyan osztály, amely egy webes alkalmazás egy adott oldalát reprezentálja, például a kezdőlapot; a terméket a webáruházban; a bejelentkezési űrlapot; a webhelytérképet stb. Az alkalmazásnak egytől akár több ezer prezenter is lehet. Más keretrendszerekben ezeket kontrollereknek is nevezik. +[Már tudjuk |how-it-works#Nette Application], hogy a presenter egy olyan osztály, amely egy webalkalmazás egy konkrét oldalát képviseli, pl. a kezdőlapot; egy terméket a webáruházban; a bejelentkezési űrlapot; a sitemap feedet stb. Az alkalmazásnak egytől több ezer presenterig terjedhet a száma. Más keretrendszerekben kontrollereknek is nevezik őket. -Általában a prezenter kifejezés a [api:Nette\Application\UI\Presenter] osztály leszármazottjára utal, amely a webes felületekre alkalmas, és amelyet a fejezet további részében tárgyalunk. Általános értelemben a prezenter bármely olyan objektum, amely megvalósítja a [api:Nette\Application\IPresenter] interfészt. +Általában presenter alatt a [api:Nette\Application\UI\Presenter] osztály leszármazottját értjük, amely alkalmas webes felületek generálására, és amelynek a továbbiakban ebben a fejezetben szenteljük a figyelmet. Általános értelemben a presenter bármely objektum, amely implementálja a [api:Nette\Application\IPresenter] interfészt. -A bemutató életciklusa .[#toc-life-cycle-of-presenter] -====================================================== +Presenter életciklusa +===================== -A bemutató feladata a kérés feldolgozása és a válasz visszaküldése (ami lehet HTML oldal, kép, átirányítás stb.). +A presenter feladata a kérés feldolgozása és a válasz visszaadása (ami lehet HTML oldal, kép, átirányítás stb.). -Az elején tehát egy kérés áll. Ez nem közvetlenül egy HTTP-kérés, hanem egy [api:Nette\Application\Request] objektum, amelybe a HTTP-kérést egy útválasztó segítségével átalakították. Ezzel az objektummal általában nem kerülünk kapcsolatba, mert a bemutató okosan delegálja a kérés feldolgozását speciális metódusokba, amelyeket most látni fogunk. +Tehát az elején átadódik neki a kérés. Ez nem közvetlenül HTTP kérés, hanem egy [api:Nette\Application\Request] objektum, amelybe a HTTP kérés a router segítségével átalakításra került. Ezzel az objektummal általában nem találkozunk, mivel a presenter a kérés feldolgozását okosan delegálja további metódusokba, amelyeket most megmutatunk. -[* lifecycle.svg *] *** *A prezenter életciklusa* .<> +[* lifecycle.svg *] *** *Presenter életciklusa* .<> -Az ábra a metódusok listáját mutatja, amelyeket - ha léteznek - felülről lefelé haladva egymás után hívunk meg. Egyiknek sem kell léteznie, lehet egy teljesen üres presenterünk egyetlen metódus nélkül, és építhetünk rá egy egyszerű statikus webet. +A kép felsorolja azokat a metódusokat, amelyek sorban fentről lefelé hívódnak meg, ha léteznek. Egyiknek sem kell léteznie, lehet teljesen üres presenterünk egyetlen metódus nélkül, és építhetünk rá egy egyszerű statikus weboldalt. `__construct()` --------------- -A konstruktor nem tartozik pontosan a bemutató életciklusához, mert az objektum létrehozásának pillanatában hívjuk meg. De fontossága miatt megemlítjük. A konstruktor (az [inject metódussal |best-practices:inject-method-attribute] együtt) a függőségek átadására szolgál. +A konstruktor nem igazán tartozik a presenter életciklusához, mert az objektum létrehozásának pillanatában hívódik meg. De a fontossága miatt említjük. A konstruktor (a [inject metódussal|best-practices:inject-method-attribute] együtt) a függőségek átadására szolgál. -A prezenternek nem kell gondoskodnia az alkalmazás üzleti logikájáról, írnia és olvasnia az adatbázisból, számításokat végeznie stb. Ez a feladata egy réteg osztályainak, amit modellnek nevezünk. Például a `ArticleRepository` osztály lehet felelős a cikkek betöltéséért és mentéséért. Ahhoz, hogy a prezenter használhassa, [függőségi injektálással adjuk át |dependency-injection:passing-dependencies]: +A presenternek nem kellene az alkalmazás üzleti logikáját intéznie, adatbázisból írni és olvasni, számításokat végezni stb. Erre valók a modellnek nevezett réteg osztályai. Például az `ArticleRepository` osztály felelhet a cikkek betöltéséért és mentéséért. Hogy a presenter dolgozhasson vele, [dependency injection |dependency-injection:passing-dependencies] segítségével kéri át: ```php @@ -50,44 +50,44 @@ class ArticlePresenter extends Nette\Application\UI\Presenter `startup()` ----------- -A kérés fogadása után azonnal meghívásra kerül a `startup ()` módszer. Ezt használhatja a tulajdonságok inicializálására, a felhasználói jogosultságok ellenőrzésére stb. Mindig meg kell hívni a `parent::startup()` elődjét. +A kérés kézhezvétele után azonnal meghívódik a `startup()` metódus. Használhatja property-k inicializálására, felhasználói jogosultságok ellenőrzésére stb. Kötelező, hogy a metódus mindig meghívja az ős `parent::startup()` metódusát. `action(args...)` .{toc: action()} -------------------------------------------------- -Hasonlóan a módszerhez `render()`. Míg a `render()` célja, hogy előkészítse az adatokat egy adott sablonhoz, amelyet később renderel, a `action()` a kérés feldolgozása az azt követő sablon renderelés nélkül történik. Például az adatok feldolgozása, a felhasználó bejelentkezése vagy kijelentkezése stb. történik, majd [máshová irányít át |#Redirection]. +A `render()` metódus megfelelője. Míg a `render()` arra szolgál, hogy előkészítse az adatokat egy konkrét sablonhoz, amely aztán renderelődik, addig az `action()` a kérést dolgozza fel a sablon renderelésétől függetlenül. Például feldolgozza az adatokat, bejelentkezteti vagy kijelentkezteti a felhasználót, és így tovább, majd [átirányít máshová |#Átirányítás]. -Fontos, hogy `action()` előbb hívódik meg, mint a `render()`, így ezen belül esetleg megváltoztathatjuk az életciklus következő menetét, azaz megváltoztathatjuk a megjelenítendő sablont és a metódust is. `render()` ami meghívásra kerül, a `setView('otherView')` segítségével. +Fontos, hogy az `action()` korábban hívódik meg, mint a `render()`, így benne esetleg megváltoztathatjuk a további történéseket, azaz megváltoztathatjuk a renderelendő sablont, és a meghívandó `render()` metódust is. Ezt a `setView('jineView')` segítségével tehetjük meg. -A kérésből származó paramétereket átadjuk a metódusnak. Lehetséges és ajánlott a paraméterek típusainak megadása, pl. `actionShow(int $id, string $slug = null)` - ha a `id` paraméter hiányzik, vagy nem egész szám, a prezenter [404-es hibát |#Error 404 etc.] ad vissza, és megszakítja a műveletet. +A metódusnak a kérésből származó paraméterek adódnak át. Lehetséges és ajánlott a paraméterek típusának megadása, pl. `actionShow(int $id, ?string $slug = null)` - ha az `id` paraméter hiányzik, vagy ha nem integer, a presenter [404-es hibát |#Hiba 404 és társai] ad vissza és befejezi a működését. `handle(args...)` .{toc: handle()} -------------------------------------------------- -Ez a módszer az úgynevezett jeleket dolgozza fel, amelyeket a [komponensekről |components#Signal] szóló fejezetben tárgyalunk. Elsősorban komponensekhez és az AJAX-kérések feldolgozásához készült. +A metódus az ún. signálokat dolgozza fel, amelyekkel a [komponenseknek |components#Signal] szentelt fejezetben ismerkedünk meg. Ugyanis főként komponensekhez és AJAX kérések feldolgozásához készült. -A paramétereket a metódusnak átadjuk, mint a `action()`, beleértve a típusellenőrzést is. +A metódusnak a kérésből származó paraméterek adódnak át, mint az `action()` esetében, beleértve a típusellenőrzést is. `beforeRender()` ---------------- -A `beforeRender` metódus, ahogy a neve is mutatja, minden egyes metódus előtt meghívásra kerül. `render()`. A sablonok általános konfigurálására, az elrendezéshez szükséges változók átadására és így tovább. +A `beforeRender` metódus, ahogy a neve is sugallja, minden `render()` metódus előtt hívódik meg. A sablon közös konfigurálására, a layout változóinak átadására és hasonló dolgokra használják. `render(args...)` .{toc: render()} ---------------------------------------------- -Az a hely, ahol előkészítjük a sablont a későbbi rendereléshez, adatokat adunk át neki, stb. +Az a hely, ahol előkészítjük a sablont a későbbi renderelésre, adatokat adunk át neki stb. -A paramétereket átadjuk a metódusnak, mint a `action()`, beleértve a típusellenőrzést is. +A metódusnak a kérésből származó paraméterek adódnak át, mint az `action()` esetében, beleértve a típusellenőrzést is. ```php public function renderShow(int $id): void { - // adatokat kapunk a modellből és átadjuk a sablonhoz. + // adatokat szerzünk a modellből és átadjuk a sablonnak $this->template->article = $this->articles->getById($id); } ``` @@ -96,104 +96,104 @@ public function renderShow(int $id): void `afterRender()` --------------- -A `afterRender` metódus, ahogy a neve is mutatja, minden egyes `render()` módszer után. Meglehetősen ritkán használják. +Az `afterRender` metódus, ahogy a neve ismét sugallja, minden `render()` metódus után hívódik meg. Ritkábban használják. `shutdown()` ------------ -A bemutató életciklusának végén hívják meg. +A presenter életciklusának végén hívódik meg. -**Jó tanács, mielőtt továbblépnénk**. Mint látható, a prezenter több műveletet/nézetet tud kezelni, azaz több metódusa van. `render()`. De javasoljuk, hogy a prezentereket egy vagy a lehető legkevesebb akcióval tervezzük. +**Jó tanács, mielőtt továbbmennénk**. A presenter, mint látható, több akciót/view-t is kezelhet, tehát több `render()` metódusa lehet. De javasoljuk olyan presenterek tervezését, amelyeknek egy vagy a lehető legkevesebb akciója van. -Válasz küldése .[#toc-sending-a-response] -========================================= +Válasz küldése +============== -A bemutató válasza általában [a sablon renderelése a HTML oldallal |templates], de lehet egy fájl, JSON küldése vagy akár egy másik oldalra való átirányítás is. +A presenter válasza általában egy [HTML oldalt tartalmazó sablon renderelése|templates], de lehet fájlküldés, JSON, vagy akár átirányítás egy másik oldalra is. -Az életciklus során bármikor használhatja az alábbi módszerek bármelyikét a válasz elküldésére és a prezentálóból való kilépésre egyidejűleg: +Az életciklus bármely pontján elküldhetünk választ a következő metódusok valamelyikével, és ezzel egyidejűleg befejezhetjük a presentert: -- `redirect()`, `redirectPermanent()`, `redirectUrl()` és `forward()` [átirányítás |#Redirection]. -- `error()` [hiba |#Error 404 etc.]miatt kilép a bemutatóból -- `sendJson($data)` kilép a prezentálóból és [elküldi az adatokat |#Sending JSON] JSON formátumban. -- `sendTemplate()` kilép a prezenterből és azonnal [rendereli a sablont |templates]. -- `sendResponse($response)` kilép a prezenterből és elküldi a [saját válaszát |#Responses]. -- `terminate()` válasz nélkül kilép a prezenterből +- `redirect()`, `redirectPermanent()`, `redirectUrl()` és `forward()` [átirányít |#Átirányítás] +- `error()` befejezi a presentert [hiba miatt |#Hiba 404 és társai] +- `sendJson($data)` befejezi a presentert és [adatokat küld |#JSON küldése] JSON formátumban +- `sendTemplate()` befejezi a presentert és azonnal [rendereli a sablont |templates] +- `sendResponse($response)` befejezi a presentert és [saját választ |#Válaszok] küld +- `terminate()` befejezi a presentert válasz nélkül -Ha nem hívja meg egyik módszert sem, a prezenter automatikusan folytatja a sablon renderelését. Miért? Nos, mert az esetek 99%-ában egy sablont akarunk kirajzolni, ezért a prezenter ezt a viselkedést veszi alapértelmezettnek, és ezzel szeretné megkönnyíteni a munkánkat. +Ha egyiket sem hívja meg ezek közül a metódusok közül, a presenter automatikusan a sablon rendereléséhez fog hozzá. Miért? Mert az esetek 99%-ában sablont szeretnénk renderelni, ezért a presenter ezt a viselkedést veszi alapértelmezettnek, és meg akarja könnyíteni a munkánkat. -Linkek létrehozása .[#toc-creating-links] -========================================= +Linkek létrehozása +================== -A Presenter rendelkezik egy `link()` metódussal, amely más Presenterekre mutató URL-linkek létrehozására szolgál. Az első paraméter a célelőadó és a művelet, majd az argumentumok következnek, amelyek tömbként adhatók át: +A presenter rendelkezik a `link()` metódussal, amellyel URL linkeket lehet létrehozni más presenterekhez. Az első paraméter a cél presenter & akció, ezt követik az átadott argumentumok, amelyek tömbként is megadhatók: ```php $url = $this->link('Product:show', $id); -$url = $this->link('Product:show', [$id, 'lang' => 'en']); +$url = $this->link('Product:show', [$id, 'lang' => 'hu']); ``` -A sablonban más prezenterekre és akciókra mutató linkeket hozunk létre a következőképpen: +A sablonban a linkek más presenterekhez & akciókhoz a következőképpen hozhatók létre: ```latte -product detail +termék részletei ``` -Egyszerűen csak írjuk a megszokott `Presenter:action` párost a valódi URL helyett, és adjunk meg minden paramétert. A trükk a `n:href`, amely azt mondja, hogy ezt az attribútumot a Latte feldolgozza, és valódi URL-t generál. A Nette-ben egyáltalán nem kell URL-ekre gondolni, csak az előadókra és az akciókra. +Egyszerűen a valós URL helyett írja be az ismert `Presenter:action` párt, és adja meg az esetleges paramétereket. A trükk az `n:href`-ben van, amely azt mondja, hogy ezt az attribútumot a Latte dolgozza fel, és valós URL-t generál. A Nette-ben tehát egyáltalán nem kell az URL-eken gondolkodnia, csak a presentereken és akciókon. -További információért lásd a [Linkek létrehozása |Creating Links] című részt. +További információkat az [URL linkek létrehozása|creating-links] fejezetben talál. -Átirányítás .[#toc-redirection] -=============================== +Átirányítás +=========== -A `redirect()` és a `forward()` metódusok egy másik bemutatóra való átugrásra szolgálnak, amelyek szintaxisa nagyon hasonló a [link() |#Creating Links] metóduséhoz. +Másik presenterhez való átlépéshez a `redirect()` és `forward()` metódusok szolgálnak, amelyeknek nagyon hasonló a szintaxisa, mint a [link() |#Linkek létrehozása] metódusnak. -A `forward()` azonnal átvált az új bemutatóra HTTP átirányítás nélkül: +A `forward()` metódus azonnal átlép az új presenterhez HTTP átirányítás nélkül: ```php $this->forward('Product:show'); ``` -Példa ideiglenes átirányításra 302 vagy 303 HTTP-kóddal: +Példa az ún. ideiglenes átirányításra 302-es HTTP kóddal (vagy 303-mal, ha az aktuális kérés metódusa POST): ```php $this->redirect('Product:show', $id); ``` -A 301-es kódú HTTP-kóddal történő állandó átirányítás eléréséhez használja a következőt: +Állandó átirányítást 301-es HTTP kóddal így érhet el: ```php $this->redirectPermanent('Product:show', $id); ``` -A `redirectUrl()` módszerrel átirányíthat egy másik, az alkalmazáson kívüli URL-címre: +Más, alkalmazáson kívüli URL-re a `redirectUrl()` metódussal lehet átirányítani. Második paraméterként megadható a HTTP kód, az alapértelmezett 302 (vagy 303, ha az aktuális kérés metódusa POST): ```php $this->redirectUrl('https://nette.org'); ``` -Az átirányítás azonnal befejezi a bemutató életciklusát az úgynevezett csendes befejezési kivétel dobásával `Nette\Application\AbortException`. +Az átirányítás azonnal befejezi a presenter működését az ún. csendes befejező kivétel, a `Nette\Application\AbortException` dobásával. -Az átirányítás előtt lehetőség van [flash üzenetet |#Flash Messages] küldeni, olyan üzeneteket, amelyek az átirányítás után megjelennek a sablonban. +Az átirányítás előtt küldhetünk [flash message-t |#Flash üzenetek], azaz üzeneteket, amelyek az átirányítás után megjelennek a sablonban. -Flash-üzenetek .[#toc-flash-messages] -===================================== +Flash üzenetek +============== -Ezek olyan üzenetek, amelyek általában egy művelet eredményéről tájékoztatnak. A flash üzenetek fontos jellemzője, hogy a sablonban az átirányítás után is elérhetőek. Megjelenésük után is még 30 másodpercig életben maradnak - például abban az esetben, ha a felhasználó véletlenül frissítené az oldalt - az üzenet nem vész el. +Ezek általában valamilyen művelet eredményéről tájékoztató üzenetek. A flash üzenetek fontos jellemzője, hogy a sablonban átirányítás után is elérhetők. Megjelenítésük után még további 30 másodpercig élnek – például arra az esetre, ha a felhasználó hibás átvitel miatt frissítené az oldalt - az üzenet tehát nem tűnik el azonnal. -Csak hívjuk meg a [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] metódust, és a presenter gondoskodik az üzenet átadásáról a sablonban. Az első argumentum az üzenet szövege, a második opcionális argumentum pedig az üzenet típusa (hiba, figyelmeztetés, info stb.). A `flashMessage()` metódus egy flash message példányt ad vissza, hogy további információkat adhassunk hozzá. +Csak meg kell hívni a [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] metódust, és a sablonba való átadásról a presenter gondoskodik. Az első paraméter az üzenet szövege, a nem kötelező második paraméter pedig a típusa (error, warning, info stb.). A `flashMessage()` metódus visszaadja a flash üzenet példányát, amelyhez további információkat lehet hozzáadni. ```php -$this->flashMessage('Item was removed.'); -$this->redirect(/* ... */); +$this->flashMessage('Az elem törölve lett.'); +$this->redirect(/* ... */); // és átirányítunk ``` -A sablonban ezek az üzenetek a `$flashes` változóban `stdClass` objektumként állnak rendelkezésre, amelyek tartalmazzák a `message` (üzenet szövege), `type` (üzenet típusa) tulajdonságokat, és tartalmazhatják a már említett felhasználói információkat. Ezeket a következőképpen rajzoljuk meg: +A sablonban ezek az üzenetek a `$flashes` változóban érhetők el `stdClass` objektumokként, amelyek tartalmazzák a `message` (üzenet szövege), `type` (üzenet típusa) tulajdonságokat, és tartalmazhatják a már említett felhasználói információkat is. Például így rendereljük őket: ```latte {foreach $flashes as $flash} @@ -202,10 +202,10 @@ A sablonban ezek az üzenetek a `$flashes` változóban `stdClass` objektumként ``` -404-es hiba stb. .[#toc-error-404-etc] -====================================== +Hiba 404 és társai +================== -Ha nem tudjuk teljesíteni a kérést, mert például a megjeleníteni kívánt cikk nem létezik az adatbázisban, akkor a 404-es hibát dobjuk ki a `error(string $message = null, int $httpCode = 404)` módszerrel, amely a 404-es HTTP hibát jelenti: +Ha a kérést nem lehet teljesíteni, például azért, mert a megjeleníteni kívánt cikk nem létezik az adatbázisban, 404-es hibát dobunk az `error(?string $message = null, int $httpCode = 404)` metódussal. ```php public function renderShow(int $id): void @@ -218,14 +218,13 @@ public function renderShow(int $id): void } ``` -A HTTP hibakódot második paraméterként adhatjuk meg, az alapértelmezett érték a 404. A módszer úgy működik, hogy kivételt dob a `Nette\Application\BadRequestException`, ami után a `Application` átadja a vezérlést a hiba bemutatójának. Ami egy prezenter, amelynek az a feladata, hogy megjelenítsen egy, a hibáról tájékoztató oldalt. -A hiba-prezenter az [alkalmazás konfigurációjában |configuration] van beállítva. +A hiba HTTP kódját második paraméterként lehet átadni, az alapértelmezett 404. A metódus úgy működik, hogy `Nette\Application\BadRequestException` kivételt dob, mire az `Application` átadja a vezérlést az error-presenternek. Ez egy olyan presenter, amelynek feladata a bekövetkezett hibáról tájékoztató oldal megjelenítése. Az error-presenter beállítása az [application konfigurációban|configuration] történik. -JSON küldése .[#toc-sending-json] -================================= +JSON küldése +============ -Példa az action-módszerre, amely JSON formátumban küldi az adatokat és kilép a bemutatóból: +Példa egy action-metódusra, amely adatokat küld JSON formátumban és befejezi a presentert: ```php public function actionData(): void @@ -236,33 +235,59 @@ public function actionData(): void ``` -Tartós paraméterek .[#toc-persistent-parameters] -================================================ +Kérés paraméterei .{data-version:3.1.14} +======================================== -A perzisztens paraméterek a különböző kérések közötti állapot fenntartására szolgálnak. Értékük a linkre való kattintás után is ugyanaz marad. A munkamenetadatokkal ellentétben ezek az URL-ben kerülnek átadásra. Ez teljesen automatikus, így nincs szükség a `link()` vagy a `n:href` oldalon történő kifejezett megadásukra. +A presenter és minden komponens is megkapja a paramétereit a HTTP kérésből. Értéküket a `getParameter($name)` vagy `getParameters()` metódussal tudhatja meg. Az értékek stringek vagy string tömbök, lényegében nyers adatok, amelyeket közvetlenül az URL-ből nyerünk. -Felhasználási példa? Van egy többnyelvű alkalmazása. Az aktuális nyelv egy olyan paraméter, amelynek mindig az URL része kell, hogy legyen. De hihetetlenül fárasztó lenne minden linkben feltüntetni. Ezért egy állandó paramétert hozol létre, amelynek a neve `lang`, és ez önmagát hordozza. Király! +A nagyobb kényelem érdekében javasoljuk a paraméterek property-ken keresztüli elérhetővé tételét. Csak meg kell őket jelölni a `#[Parameter]` attribútummal: + +```php +use Nette\Application\Attributes\Parameter; // ez a sor fontos + +class HomePresenter extends Nette\Application\UI\Presenter +{ + #[Parameter] + public string $theme; // public-nak kell lennie +} +``` + +A property-nél javasoljuk az adattípus megadását (pl. `string`), és a Nette ez alapján automatikusan átalakítja az értéket. A paraméterek értékeit lehet [validálni |#Paraméterek validálása] is. + +Link létrehozásakor a paraméterek értékét közvetlenül be lehet állítani: + +```latte +kattints +``` -A tartós paraméter létrehozása rendkívül egyszerű a Nette-ben. Csak hozzon létre egy nyilvános tulajdonságot, és jelölje meg az attribútummal: (korábban a `/** @persistent */` volt használatos). + +Perzisztens paraméterek +======================= + +A perzisztens paraméterek az állapot megőrzésére szolgálnak a különböző kérések között. Értékük ugyanaz marad a linkre kattintás után is. A session adatokkal ellentétben az URL-ben kerülnek átvitelre. És ez teljesen automatikusan történik, tehát nem kell explicit módon megadni őket a `link()` vagy `n:href` esetén. + +Példa a használatra? Van egy többnyelvű alkalmazása. Az aktuális nyelv egy paraméter, amelynek folyamatosan az URL részének kell lennie. De hihetetlenül fárasztó lenne minden linkben megadni. Így csinál belőle egy `lang` perzisztens paramétert, és magától átadódik. Remek! + +Perzisztens paraméter létrehozása a Nette-ben rendkívül egyszerű. Csak létre kell hozni egy public property-t és megjelölni egy attribútummal: (korábban a `/** @persistent */` volt használatos) ```php -use Nette\Application\Attributes\Persistent; // ez a sor fontos +use Nette\Application\Attributes\Persistent; // ez a sor fontos class ProductPresenter extends Nette\Application\UI\Presenter { #[Persistent] - public string $lang; // nyilvánosnak kell lennie + public string $lang; // public-nak kell lennie } ``` -Ha a `$this->lang` értéke például `'en'`, akkor a `link()` vagy `n:href` használatával létrehozott linkek a `lang=en` paramétert is tartalmazni fogják. És amikor a linkre kattintunk, akkor ismét a `$this->lang = 'en'` lesz. +Ha a `$this->lang` értéke például `'en'` lesz, akkor a `link()` vagy `n:href` segítségével létrehozott linkek is tartalmazni fogják a `lang=en` paramétert. És a linkre kattintás után ismét `$this->lang = 'en'` lesz. -A tulajdonságok esetében javasoljuk, hogy adja meg az adattípust (pl. `string`), és egy alapértelmezett értéket is megadhat. A paraméterek értékei [érvényesíthetők |#Validation of Persistent Parameters]. +A property-nél javasoljuk az adattípus megadását (pl. `string`), és megadhat alapértelmezett értéket is. A paraméterek értékeit lehet [validálni |#Paraméterek validálása]. -A perzisztens paraméterek alapértelmezés szerint egy adott bemutató összes művelete között átadásra kerülnek. Több prezenter között történő átadásukhoz vagy meg kell határozni őket: +A perzisztens paraméterek alapértelmezés szerint az adott presenter összes akciója között átadódnak. Ahhoz, hogy több presenter között is átadódjanak, definiálni kell őket vagy: -- egy közös ősben, amelytől az előadók öröklik a paramétereket. -- abban a tulajdonságban, amelyet az előadók használnak: +- egy közös ősben, amelytől a presenterek örökölnek +- egy trait-ben, amelyet a presenterek használnak: ```php trait LanguageAware @@ -277,48 +302,42 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -Megváltoztathatja egy állandó paraméter értékét a hivatkozás létrehozásakor: +Link létrehozásakor a perzisztens paraméter értékét meg lehet változtatni: ```latte -detail in Czech +részletek magyarul ``` -Vagy *visszaállítható*, azaz eltávolítható az URL-ből. Ekkor az alapértelmezett értéket veszi fel: +Vagy *resetelhető*, azaz eltávolítható az URL-ből. Ekkor az alapértelmezett értékét veszi fel: ```latte -click +kattints ``` -Interaktív komponensek .[#toc-interactive-components] -===================================================== +Interaktív komponensek +====================== -A prezenterek beépített komponensrendszerrel rendelkeznek. A komponensek különálló, újrafelhasználható egységek, amelyeket a prezenterekbe helyezünk. Ezek lehetnek [űrlapok |forms:in-presenter], adagramok, menük, tulajdonképpen bármi, amit érdemes ismételten használni. +A presenterek beépített komponensrendszerrel rendelkeznek. A komponensek önálló, újrafelhasználható egységek, amelyeket presenterekbe illesztünk be. Lehetnek [űrlapok |forms:in-presenter], datagrid-ek, menük, valójában bármi, amit érdemes ismételten használni. -Hogyan kerülnek a komponensek a prezenterben elhelyezésre és a későbbiekben felhasználásra? Ezt a [Komponensek |Components] fejezetben magyarázzuk el. Még azt is megtudhatjuk, mi közük van Hollywoodhoz. +Hogyan illesztjük be és használjuk a komponenseket a presenterben? Ezt a [Komponensek |components] fejezetben tudhatja meg. Még azt is megtudhatja, mi közük van Hollywoodhoz. -Hol szerezhetek be komponenseket? A [Componette |https://componette.org] oldalon találsz néhány nyílt forráskódú komponenst és egyéb kiegészítőt a Nette-hez, amelyeket a Nette Framework közössége készített és osztott meg. +És hol szerezhetek komponenseket? A [Componette |https://componette.org/search/component] oldalon talál nyílt forráskódú komponenseket és számos más kiegészítőt a Nette-hez, amelyeket a keretrendszer körüli közösség önkéntesei helyeztek el itt. -Mélyebbre ásva .[#toc-going-deeper] -=================================== +Mélyebbre megyünk +================= .[tip] -Amit eddig ebben a fejezetben bemutattunk, valószínűleg elegendő lesz. A következő sorok azoknak szólnak, akiket a prezenterek mélységében érdekelnek, és mindent tudni akarnak. - - -Követelmények és paraméterek .[#toc-requirement-and-parameters] ---------------------------------------------------------------- +Azzal, amit eddig ebben a fejezetben megmutattunk, valószínűleg teljesen elboldogul. A következő sorok azoknak szólnak, akiket mélyebben érdekelnek a presenterek, és mindent tudni akarnak róluk. -A bemutató által kezelt kérés a [api:Nette\Application\Request] objektum, amelyet a bemutató `getRequest()` metódusa ad vissza. Ez paraméterek tömbjét tartalmazza, és mindegyik paraméter vagy valamelyik komponenshez, vagy közvetlenül a prezentálóhoz tartozik (amely valójában szintén egy komponens, bár egy speciális komponens). A Nette tehát a `loadState(array $params)` metódus meghívásával újraosztja a paramétereket és átadja az egyes komponensek (és a prezenter) között. A paramétereket a `getParameters(): array` metódussal lehet megszerezni, egyenként a `getParameter($name)` segítségével. A paraméterek értékei karakterláncok vagy karakterláncok tömbjei, alapvetően közvetlenül az URL-ből nyert nyers adatok. +Paraméterek validálása +---------------------- -A tartós paraméterek validálása .[#toc-validation-of-persistent-parameters] ---------------------------------------------------------------------------- +Az URL-ből kapott [kérés paramétereinek |#Kérés paraméterei] és [perzisztens paramétereinek |#Perzisztens paraméterek] értékeit a `loadState()` metódus írja be a property-kbe. Ez ellenőrzi azt is, hogy megfelelnek-e a property-nél megadott adattípusnak, különben 404-es hibával válaszol, és az oldal nem jelenik meg. -Az URL-ekből kapott [állandó paraméterek |#persistent parameters] értékeit a `loadState()` módszer írja a tulajdonságokba. A módszer azt is ellenőrzi, hogy a tulajdonságban megadott adattípus megfelel-e, ellenkező esetben 404-es hibával válaszol, és az oldal nem jelenik meg. - -Soha ne bízzunk vakon a perzisztens paraméterekben, mivel azokat a felhasználó könnyen felülírhatja az URL-ben. Például így ellenőrizzük, hogy a `$this->lang` szerepel-e a támogatott nyelvek között. Jó megoldás erre a fent említett `loadState()` metódus felülírása: +Soha ne bízzon vakon a paraméterekben, mert azokat a felhasználó könnyen felülírhatja az URL-ben. Így például ellenőrizzük, hogy a `$this->lang` nyelv a támogatottak között van-e. Megfelelő módszer az említett `loadState()` metódus felülírása: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -328,9 +347,9 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function loadState(array $params): void { - parent::loadState($params); // itt van beállítva a $this->lang - // követi a felhasználói értékek ellenőrzését: - if (!in_array($this->lang, ['en', 'cs'])) { + parent::loadState($params); // itt állítódik be a $this->lang + // következik a saját értékellenőrzés: + if (!in_array($this->lang, ['en', 'hu'])) { $this->error(); } } @@ -338,43 +357,45 @@ class ProductPresenter extends Nette\Application\UI\Presenter ``` -A kérelem mentése és visszaállítása .[#toc-save-and-restore-the-request] ------------------------------------------------------------------------- +Kérés mentése és visszaállítása +------------------------------- + +A presenter által kezelt kérés egy [api:Nette\Application\Request] objektum, és a presenter `getRequest()` metódusa adja vissza. -Az aktuális kérést elmentheti egy munkamenetbe, vagy visszaállíthatja a munkamenetből, és hagyhatja, hogy az előadó újra végrehajtsa azt. Ez például akkor hasznos, ha egy felhasználó kitölt egy űrlapot, és a bejelentkezése lejár. Annak érdekében, hogy ne veszítsünk el adatokat, a bejelentkezési oldalra való átirányítás előtt az aktuális kérést a munkamenetbe mentjük a `$reqId = $this->storeRequest()` segítségével, amely egy rövid karakterlánc formájában visszaad egy azonosítót, és paraméterként átadja a bejelentkezési prezenternek. +Az aktuális kérést el lehet menteni a sessionbe, vagy onnan visszaállítani, és hagyni, hogy a presenter újra végrehajtsa. Ez hasznos például olyan helyzetben, amikor a felhasználó egy űrlapot tölt ki, és lejár a bejelentkezése. Hogy ne veszítse el az adatokat, a bejelentkezési oldalra való átirányítás előtt az aktuális kérést elmentjük a sessionbe a `$reqId = $this->storeRequest()` segítségével, amely visszaadja annak azonosítóját egy rövid string formájában, és ezt átadjuk paraméterként a bejelentkezési presenternek. -A bejelentkezés után meghívjuk a `$this->restoreRequest($reqId)` metódust, amely átveszi a kérést a munkamenetből, és továbbítja azt. A módszer ellenőrzi, hogy a kérést ugyanaz a felhasználó hozta-e létre, mint aki most bejelentkezett. Ha egy másik felhasználó jelentkezik be, vagy a kulcs érvénytelen, akkor nem tesz semmit, és a program folytatódik. +Bejelentkezés után meghívjuk a `$this->restoreRequest($reqId)` metódust, amely kiemeli a kérést a sessionből és forwardol rá. A metódus közben ellenőrzi, hogy a kérést ugyanaz a felhasználó hozta-e létre, aki most bejelentkezett. Ha másik felhasználó jelentkezett be, vagy a kulcs érvénytelen, nem csinál semmit, és a program folytatódik tovább. -Lásd a [Hogyan térjünk vissza egy korábbi oldalra |best-practices:restore-request] című szakácskönyvben. +Nézze meg a [Hogyan térjünk vissza egy korábbi oldalra |best-practices:restore-request] útmutatót. -Kanonizálás .[#toc-canonization] --------------------------------- +Kanonizáció +----------- -A bemutatóknak van egy igazán nagyszerű funkciója, amely javítja a SEO-t (az internetes kereshetőség optimalizálása). Automatikusan megakadályozzák a duplikált tartalmak meglétét a különböző URL-címeken. Ha több URL vezet egy bizonyos célhoz, pl. `/index` és `/index?page=1`, a keretrendszer az egyiket elsődlegesnek (kanonikusnak) jelöli ki, és a többit erre irányítja át a 301-es HTTP-kóddal. Ennek köszönhetően a keresőmotorok nem indexelik kétszer az oldalakat, és nem gyengítik azok oldalrangsorát. +A presentereknek van egy igazán nagyszerű tulajdonsága, amely hozzájárul a jobb SEO-hoz (keresőoptimalizálás). Automatikusan megakadályozzák a duplikált tartalom létezését különböző URL-eken. Ha egy bizonyos célhoz több URL cím vezet, pl. `/index` és `/index?page=1`, a keretrendszer egyiküket elsődlegesnek (kanonikusnak) határozza meg, a többit pedig 301-es HTTP kóddal átirányítja rá. Ennek köszönhetően a keresőmotorok nem indexelik kétszer az oldalakat, és nem osztják meg a page rankjüket. -Ezt a folyamatot kanonizációnak nevezzük. A kanonikus URL az [útválasztó |routing] által generált URL, általában az első megfelelő útvonal a gyűjteményben. +Ezt a folyamatot kanonizációnak nevezik. A kanonikus URL az, amelyet a [router|routing] generál, általában tehát az első megfelelő route a gyűjteményben. -A kanonizálás alapértelmezés szerint be van kapcsolva, és kikapcsolható a `$this->autoCanonicalize = false` címen keresztül. +A kanonizáció alapértelmezés szerint be van kapcsolva, és kikapcsolható a `$this->autoCanonicalize = false` segítségével. -Az átirányítás nem történik AJAX vagy POST kérés esetén, mivel az adatvesztéssel járna, vagy nem eredményezne SEO hozzáadott értéket. +Az átirányítás nem történik meg AJAX vagy POST kérés esetén, mert adatvesztéshez vezetne, vagy nem lenne hozzáadott értéke SEO szempontból. -A kanonizálás manuálisan is meghívható a `canonicalize()` módszerrel, amely a `link()` módszerhez hasonlóan a prezentálót, a műveleteket és a paramétereket kapja argumentumként. Létrehoz egy linket, és összehasonlítja azt az aktuális URL-lel. Ha eltér, akkor átirányít a létrehozott linkre. +A kanonizációt manuálisan is kiválthatja a `canonicalize()` metódussal, amelynek hasonlóan a `link()` metódushoz, átadódik a presenter, az akció és a paraméterek. Létrehoz egy linket, és összehasonlítja az aktuális URL címmel. Ha különböznek, átirányít a generált linkre. ```php -public function actionShow(int $id, string $slug = null): void +public function actionShow(int $id, ?string $slug = null): void { $realSlug = $this->facade->getSlugForId($id); - // átirányít, ha a $slug különbözik a $realSlug-tól. + // átirányít, ha a $slug különbözik a $realSlug-tól $this->canonicalize('Product:show', [$id, $realSlug]); } ``` -Események .[#toc-events] ------------------------- +Események +--------- -A `startup()`, `beforeRender()` és `shutdown()` metódusokon kívül, amelyeket a prezentáló életciklusának részeként hívunk meg, más funkciók is definiálhatók, amelyeket automatikusan hívunk. A prezenter definiálja az úgynevezett [eseményeket |nette:glossary#events], és a kezelőiket a `$onStartup`, `$onRender` és `$onShutdown` tömbökhöz adjuk hozzá. +A `startup()`, `beforeRender()` és `shutdown()` metódusokon kívül, amelyek a presenter életciklusának részeként hívódnak meg, definiálhatunk további függvényeket is, amelyeket automatikusan meg kell hívni. A presenter definiálja az ún. [eseményeket |nette:glossary#Eventek események], amelyek handlereit hozzáadhatja a `$onStartup`, `$onRender` és `$onShutdown` tömbökhöz. ```php class ArticlePresenter extends Nette\Application\UI\Presenter @@ -388,34 +409,34 @@ class ArticlePresenter extends Nette\Application\UI\Presenter } ``` -A `$onStartup` tömbben lévő kezelőket közvetlenül a `startup()` módszer előtt hívja meg a rendszer, majd a `$onRender` a `beforeRender()` és a között. `render()` és végül a `$onShutdown` közvetlenül a `shutdown()` előtt. +A `$onStartup` tömb handlerei közvetlenül a `startup()` metódus előtt hívódnak meg, továbbá a `$onRender` a `beforeRender()` és `render()` között, végül a `$onShutdown` közvetlenül a `shutdown()` előtt. -Válaszok .[#toc-responses] --------------------------- +Válaszok +-------- -A bemutató által visszaküldött válasz egy objektum, amely a [api:Nette\Application\Response] interfészt valósítja meg. Számos kész válasz létezik: +A presenter által visszaadott válasz egy objektum, amely implementálja a [api:Nette\Application\Response] interfészt. Számos előkészített válasz áll rendelkezésre: -- [api:Nette\Application\Responses\CallbackResponse] - visszahívást küld -- [api:Nette\Application\Responses\FileResponse] - elküldi a fájlt -- [api:Nette\Application\Responses\ForwardResponse] - forward () +- [api:Nette\Application\Responses\CallbackResponse] - callback-et küld +- [api:Nette\Application\Responses\FileResponse] - fájlt küld +- [api:Nette\Application\Responses\ForwardResponse] - forward() - [api:Nette\Application\Responses\JsonResponse] - JSON-t küld - [api:Nette\Application\Responses\RedirectResponse] - átirányítás - [api:Nette\Application\Responses\TextResponse] - szöveget küld - [api:Nette\Application\Responses\VoidResponse] - üres válasz -A válaszok küldése a `sendResponse()` módszerrel történik: +A válaszokat a `sendResponse()` metódussal küldjük el: ```php use Nette\Application\Responses; -// Sima szöveg +// Egyszerű szöveg $this->sendResponse(new Responses\TextResponse('Hello Nette!')); -// Fájl küldése +// Fájlt küld $this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf')); -// Elküld egy visszahívást +// A válasz egy callback lesz $callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse) { if ($httpResponse->getHeader('Content-Type') === 'text/html') { echo '

    Hello

    '; @@ -425,10 +446,55 @@ $this->sendResponse(new Responses\CallbackResponse($callback)); ``` -További olvasmányok .[#toc-further-reading] -=========================================== +Hozzáférés korlátozása `#[Requires]` segítségével .{data-version:3.2.2} +----------------------------------------------------------------------- + +A `#[Requires]` attribútum fejlett lehetőségeket kínál a presenterekhez és metódusaikhoz való hozzáférés korlátozására. Használható HTTP metódusok specifikálására, AJAX kérés megkövetelésére, azonos eredetre (same origin) való korlátozásra, és csak forwardoláson keresztüli hozzáférésre. Az attribútum alkalmazható mind a presenter osztályokra, mind az egyes `action()`, `render()`, `handle()` és `createComponent()` metódusokra. + +Meghatározhatja ezeket a korlátozásokat: +- HTTP metódusokra: `#[Requires(methods: ['GET', 'POST'])]` +- AJAX kérés megkövetelése: `#[Requires(ajax: true)]` +- csak azonos eredetű hozzáférés: `#[Requires(sameOrigin: true)]` +- csak forwardon keresztüli hozzáférés: `#[Requires(forward: true)]` +- korlátozás konkrét akciókra: `#[Requires(actions: 'default')]` + +Részleteket a [Hogyan használjuk a Requires attribútumot |best-practices:attribute-requires] útmutatóban talál. + + +HTTP metódus ellenőrzése +------------------------ + +A Nette presenterei automatikusan ellenőrzik minden bejövő kérés HTTP metódusát. Ennek az ellenőrzésnek az oka elsősorban a biztonság. Alapértelmezés szerint a `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH` metódusok engedélyezettek. + +Ha további metódust szeretne engedélyezni, például az `OPTIONS`-t, használja a `#[Requires]` attribútumot (Nette Application v3.2-től): + +```php +#[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])] +class MyPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +A 3.1-es verzióban az ellenőrzés a `checkHttpMethod()`-ban történik, amely megállapítja, hogy a kérésben megadott metódus szerepel-e a `$presenter->allowedMethods` tömbben. Metódus hozzáadása így történik: + +```php +class MyPresenter extends Nette\Application\UI\Presenter +{ + protected function checkHttpMethod(): void + { + $this->allowedMethods[] = 'OPTIONS'; + parent::checkHttpMethod(); + } +} +``` + +Fontos hangsúlyozni, hogy ha engedélyezi az `OPTIONS` metódust, azt követően megfelelően kezelnie is kell a presenterében. A metódust gyakran használják ún. preflight kérésként, amelyet a böngésző automatikusan küld a tényleges kérés előtt, amikor meg kell állapítani, hogy a kérés engedélyezett-e a CORS (Cross-Origin Resource Sharing) politika szempontjából. Ha engedélyezi a metódust, de nem implementálja a megfelelő választ, az inkonzisztenciákhoz és potenciális biztonsági problémákhoz vezethet. + + +További olvasmányok +=================== -- [Injektáló módszerek és attribútumok |best-practices:inject-method-attribute] -- [Bemutatók összeállítása tulajdonságokból |best-practices:presenter-traits] -- [Beállítások átadása bemutatóknak |best-practices:passing-settings-to-presenters] -- [Hogyan térhetünk vissza egy korábbi oldalra |best-practices:restore-request] +- [Inject metódusok és attribútumok |best-practices:inject-method-attribute] +- [Presenterek összeállítása trait-ekből |best-practices:presenter-traits] +- [Beállítások átadása presentereknek |best-practices:passing-settings-to-presenters] +- [Hogyan térjünk vissza egy korábbi oldalra |best-practices:restore-request] diff --git a/application/hu/routing.texy b/application/hu/routing.texy index ca3e0ff108..4fb937eba5 100644 --- a/application/hu/routing.texy +++ b/application/hu/routing.texy @@ -1,29 +1,28 @@ -Útválasztás -*********** +Routing +*******
    -Az útválasztó felelős mindenért az URL-ekkel kapcsolatban, így nem kell többé gondolkodnod rajtuk. Megmutatjuk: +A Router felelős mindenért, ami az URL címekkel kapcsolatos, hogy Önnek már ne kelljen gondolkodnia rajtuk. Megmutatjuk: -- hogyan állítsuk be a routert, hogy az URL-ek úgy nézzenek ki, ahogyan szeretnénk. -- néhány megjegyzést a SEO átirányításról -- és megmutatjuk, hogyan írhatod meg a saját routeredet. +- hogyan állítsuk be a routert, hogy az URL-ek az elképzeléseinknek megfeleljenek +- beszélünk a SEO-ról és az átirányításról +- és megmutatjuk, hogyan írjunk saját routert
    -Az emberibb URL-ek (vagy menőbb vagy csinosabb URL-ek) jobban használhatóak, emlékezetesebbek és pozitívan járulnak hozzá a SEO-hoz. A Nette ezt tartja szem előtt, és teljes mértékben megfelel a fejlesztők vágyainak. Pontosan úgy alakíthatja ki az alkalmazás URL-szerkezetét, ahogyan szeretné. -Akár az alkalmazás elkészülte után is megtervezheti, mivel ez kód- vagy sablonmódosítás nélkül is elvégezhető. Elegáns módon, [egyetlen helyen |#Integration], a routerben kerül meghatározásra, és nem szóródik szét megjegyzések formájában az összes bemutatóban. +Az emberibb URL-ek (vagy cool vagy pretty URL-ek) használhatóbbak, megjegyezhetőbbek és pozitívan hozzájárulnak a SEO-hoz. A Nette erre gondol, és teljes mértékben támogatja a fejlesztőket. Pontosan olyan URL-struktúrát tervezhet az alkalmazásához, amilyet csak szeretne. Akár akkor is megtervezheti, amikor az alkalmazás már kész, mert ez nem igényel beavatkozást a kódba vagy a sablonokba. Ugyanis elegáns módon egy [egyetlen helyen |#Integrálás az alkalmazásba] definiálódik, a routerben, és így nincs szétszórva annotációk formájában az összes presenterben. -A Nette router azért különleges, mert **kétirányú**, képes mind a HTTP-kérő URL-ek dekódolására, mind a linkek létrehozására. Tehát létfontosságú szerepet játszik a [Nette alkalmazásban |how-it-works#Nette Application], mert eldönti, hogy melyik prezenter és akció fogja végrehajtani az aktuális kérést, és a sablonban az [URL generálásához |creating-links] is használják stb. +A Nette routere kivételes abban, hogy **kétirányú.** Képes dekódolni a HTTP kérésben lévő URL-t, és linkeket is létrehozni. Tehát kulcsfontosságú szerepet játszik a [Nette Applicationben |how-it-works#Nette Application], mert egyrészt eldönti, hogy melyik presenter és akció fogja végrehajtani az aktuális kérést, másrészt pedig a [URL generálására |creating-links] használatos a sablonban stb. -A router azonban nem korlátozódik erre a felhasználásra, használhatja olyan alkalmazásokban is, ahol egyáltalán nem használnak prezentereket, REST API-khoz stb. Bővebben az [elkülönített használat |#separated usage] szakaszban. +Azonban a router nem korlátozódik csak erre a felhasználásra, használhatja olyan alkalmazásokban is, ahol egyáltalán nem használnak presentereket, REST API-khoz stb. További információk a [#Önálló használat] részben. -Útvonalgyűjtés .[#toc-route-collection] -======================================= +Route gyűjtemény +================ -Az URL címek definiálásának legkellemesebb módja az alkalmazásban a [api:Nette\Application\Routers\RouteList] osztályon keresztül történik. A definíció úgynevezett útvonalak listájából áll, azaz URL-címek maszkjaiból és a hozzájuk tartozó bemutatókból és műveletekből egy egyszerű API segítségével. Az útvonalakat nem kell megneveznünk. +Az alkalmazás URL címeinek formájának definiálásának legkellemesebb módját a [api:Nette\Application\Routers\RouteList] osztály kínálja. A definíció ún. route-ok listájából áll, azaz URL cím maszkokból és a hozzájuk rendelt presenterekből és akciókból, egy egyszerű API segítségével. A route-okat nem kell elneveznünk. ```php $router = new Nette\Application\Routers\RouteList; @@ -32,16 +31,16 @@ $router->addRoute('article/', 'Article:view'); // ... ``` -A példa azt mondja, hogy ha megnyitjuk a `https://any-domain.com/rss.xml` prezenter a `rss` akcióval jelenik meg, ha a `https://domain.com/article/12` a `view` akcióval, stb. Ha nem találunk megfelelő útvonalat, a Nette Application egy [BadRequestException |api:Nette\Application\BadRequestException] kivétel dobásával válaszol, amely a felhasználó számára egy 404 Not Found hibaoldalként jelenik meg. +A példa azt mondja, hogy ha a böngészőben megnyitjuk a `https://domain.com/rss.xml` címet, akkor a `Feed` presenter jelenik meg az `rss` akcióval, ha a `https://domain.com/article/12` címet, akkor az `Article` presenter jelenik meg a `view` akcióval stb. Ha nem található megfelelő route, a Nette Application [BadRequestException |api:Nette\Application\BadRequestException] kivételt dob, amely a felhasználónak 404 Not Found hibaoldalként jelenik meg. -Az útvonalak sorrendje .[#toc-order-of-routes] ----------------------------------------------- +Route-ok sorrendje +------------------ -Az útvonalak sorrendje **nagyon fontos**, mivel az útvonalakat a rendszer fentről lefelé haladva értékeli ki. A szabály az, hogy az útvonalakat **specifikusról az általánosra** jelentjük be: +Teljesen **kulcsfontosságú a sorrend**, amelyben az egyes route-ok fel vannak sorolva, mert sorban fentről lefelé értékelődnek ki. Az a szabály érvényes, hogy a route-okat **a specifikusaktól az általánosakig** deklaráljuk: ```php -// ROSSZ: az 'rss.xml' az első útvonalra illeszkedik, és ezt félreérti -nak tekinti. +// ROSSZ: az 'rss.xml'-t az első route fogja el, és ezt a stringet -ként értelmezi $router->addRoute('', 'Article:view'); $router->addRoute('rss.xml', 'Feed:rss'); @@ -50,10 +49,10 @@ $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('', 'Article:view'); ``` -Az útvonalakat a linkek generálásakor is felülről lefelé értékeljük ki: +A route-ok fentről lefelé értékelődnek ki a linkek generálásakor is: ```php -// ROSSZ: a 'Feed:rss' linket 'admin/feed/rss' néven generálja. +// ROSSZ: a 'Feed:rss' linket 'admin/feed/rss'-ként generálja $router->addRoute('admin//', 'Admin:default'); $router->addRoute('rss.xml', 'Feed:rss'); @@ -62,100 +61,100 @@ $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('admin//', 'Admin:default'); ``` -Nem tartjuk titokban Ön előtt, hogy a lista helyes felépítéséhez némi szakértelemre van szükség. Amíg bele nem jön, az [útvonalválasztó panel |#Debugging Router] hasznos eszköz lesz. +Nem titkoljuk Ön elől, hogy a route-ok helyes összeállítása némi ügyességet igényel. Mielőtt elsajátítaná, hasznos segítő lesz a [routing panel |#Router debuggolása]. -Maszk és paraméterek .[#toc-mask-and-parameters] ------------------------------------------------- +Maszk és paraméterek +-------------------- -A maszk a relatív elérési utat írja le a webhely gyökere alapján. A legegyszerűbb maszk egy statikus URL: +A maszk a web gyökérkönyvtárától számított relatív utat írja le. A legegyszerűbb maszk egy statikus URL: ```php $router->addRoute('products', 'Products:default'); ``` -A maszkok gyakran tartalmaznak úgynevezett **paramétereket**. Ezeket szögletes zárójelek közé zárják (pl. ``), és a célbemutatónak adják át, például a `renderShow(int $year)` metódusnak vagy a `$year` állandó paraméternek: +Gyakran a maszkok ún. **paramétereket** tartalmaznak. Ezek hegyes zárójelekben vannak megadva (pl. ``), és átadódnak a cél presenternek, például a `renderShow(int $year)` metódusnak vagy a `$year` perzisztens paraméternek: ```php $router->addRoute('chronicle/', 'History:show'); ``` -A példa azt mondja, hogy ha megnyitjuk a `https://any-domain.com/chronicle/2020` prezenter és a `show` művelet a `year: 2020` paraméterrel megjelenik. +A példa azt mondja, hogy ha a böngészőben megnyitjuk a `https://example.com/chronicle/2020` címet, akkor a `History` presenter jelenik meg a `show` akcióval és a `year: 2020` paraméterrel. -A paraméterek alapértelmezett értékét közvetlenül a maszkban adhatjuk meg, és így opcionálissá válik: +A paramétereknek közvetlenül a maszkban adhatunk alapértelmezett értéket, és ezzel opcionálissá válnak: ```php $router->addRoute('chronicle/', 'History:show'); ``` -Az útvonal mostantól elfogadja a `https://any-domain.com/chronicle/` címet fogja megjeleníteni a `year: 2020` paraméterrel. +A route mostantól elfogadja a `https://example.com/chronicle/` URL-t is, amely szintén a `History:show`-t jeleníti meg a `year: 2020` paraméterrel. -Természetesen a bemutató és az akció neve is lehet paraméter. Például: +A paraméter természetesen lehet a presenter és az akció neve is. Például így: ```php $router->addRoute('/', 'Home:default'); ``` -Ez az útvonal elfogad például egy URL-t a `/article/edit` illetve `/catalog/list` formában, és lefordítja azokat a `Article:edit` illetve `Catalog:list` előadókra és műveletekre. +Az említett route elfogadja pl. az `/article/edit` vagy az `/catalog/list` formátumú URL-eket, és ezeket `Article:edit` és `Catalog:list` presenterekként és akciókként értelmezi. -A `presenter` és a `action` paramétereknek alapértelmezett értékeket ad:`Home` és `default`, ezért ezek opcionálisak. Az útvonal tehát elfogad egy `/article` URL-t is, és lefordítja `Article:default`-ként. Vagy fordítva, a `Product:default` hivatkozás a `/product` útvonalat generálja, az alapértelmezett `Home:default` hivatkozás a `/` útvonalat generálja. +Ugyanakkor a `presenter` és `action` paramétereknek alapértelmezett értékként `Home`-ot és `default`-ot ad, így ezek is opcionálisak. Tehát a route elfogadja az `/article` formátumú URL-t is, és azt `Article:default`-ként értelmezi. Vagy fordítva, a `Product:default` link az `/product` utat generálja, az alapértelmezett `Home:default` link pedig a `/` utat. -A maszk nem csak a webhely gyökere alapján a relatív elérési utat írhatja le, hanem az abszolút elérési utat is, ha az kötőjellel kezdődik, vagy akár a teljes abszolút URL-t, ha két kötőjellel kezdődik: +A maszk nemcsak a web gyökérkönyvtárától számított relatív utat írhatja le, hanem abszolút utat is, ha perjellel kezdődik, vagy akár teljes abszolút URL-t is, ha két perjellel kezdődik: ```php -// relatív elérési út az alkalmazási dokumentum gyökeréhez +// relatív a document roothoz $router->addRoute('/', /* ... */); -// abszolút elérési út, relatív a szerver hostnevéhez +// abszolút út (relatív a domainhez) $router->addRoute('//', /* ... */); -// abszolút URL, beleértve a hosztnevet is (de séma-relatív) +// abszolút URL domainnel együtt (relatív a sémához) $router->addRoute('//.example.com//', /* ... */); -// abszolút URL, beleértve a sémát is +// abszolút URL sémával együtt $router->addRoute('https://.example.com//', /* ... */); ``` -Érvényesítési kifejezések .[#toc-validation-expressions] --------------------------------------------------------- +Validációs kifejezések +---------------------- -Minden paraméterhez megadható egy érvényesítési feltétel [szabályos kifejezéssel |https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Például állítsuk be, hogy a `id` csak numerikus legyen, a `\d+` regexp használatával: +Minden paraméterhez meg lehet határozni egy validációs feltételt [reguláris kifejezéssel|https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Például az `id` paraméternek meghatározzuk, hogy csak számjegyeket tartalmazhat a `\d+` reguláris kifejezéssel: ```php $router->addRoute('/[/]', /* ... */); ``` -Az alapértelmezett reguláris kifejezés minden paraméterhez a következő `[^/]+`, azaz minden, kivéve a slash-t. Ha egy paraméternek a slash-re is illeszkednie kell, akkor a reguláris kifejezést a `.+`-ra állítjuk. +Minden paraméter alapértelmezett reguláris kifejezése `[^/]+`, azaz minden, kivéve a perjelet. Ha egy paraméternek perjeleket is el kell fogadnia, adjuk meg a `.+` kifejezést: ```php -// elfogadja a https://example.com/a/b/c címet, az elérési útvonal 'a/b/c'. +// elfogadja a https://example.com/a/b/c címet, a path 'a/b/c' lesz $router->addRoute('', /* ... */); ``` -Választható szekvenciák .[#toc-optional-sequences] --------------------------------------------------- +Opcionális szekvenciák +---------------------- -A szögletes zárójelek a maszk opcionális részeit jelölik. A maszk bármelyik része opcionális lehet, beleértve a paramétereket tartalmazó részeket is: +A maszkban szögletes zárójelekkel lehet jelölni az opcionális részeket. A maszk bármely része lehet opcionális, és tartalmazhatnak paramétereket is: ```php $router->addRoute('[/]', /* ... */); -// Elfogadott URL-címek: Paraméterek: -// /en/download lang => en, name => download -// /download lang => null, name => download +// Elfogadott utak: +// /cs/download => lang => cs, name => download +// /download => lang => null, name => download ``` -Természetesen, ha egy paraméter egy opcionális sorozat része, akkor az is opcionálissá válik. Ha nincs alapértelmezett értéke, akkor null lesz. +Ha egy paraméter egy opcionális szekvencia része, természetesen maga is opcionálissá válik. Ha nincs megadva alapértelmezett értéke, akkor null lesz. -Az opcionális szakaszok a tartományban is lehetnek: +Az opcionális részek a domainben is lehetnek: ```php $router->addRoute('//[.]example.com//', /* ... */); ``` -A szekvenciák szabadon egymásba ágyazhatók és kombinálhatók: +A szekvenciákat tetszőlegesen lehet egymásba ágyazni és kombinálni: ```php $router->addRoute( @@ -163,49 +162,49 @@ $router->addRoute( 'Home:default', ); -// Elfogadott URL-címek: -// /en/hello -// /en-us/hello -// /hello -// /hello/page-12 +// Elfogadott utak: +// /cs/hello +// /en-us/hello +// /hello +// /hello/page-12 ``` -Az URL-generátor igyekszik az URL-t a lehető legrövidebbre fogalmazni, ezért ami elhagyható, azt elhagyja. Ezért például egy útvonal `index[.html]` generál egy `/index` elérési utat. Ezt a viselkedést megfordíthatja, ha a bal oldali szögletes zárójel után felkiáltójelet ír: +Az URL generálásakor a legrövidebb változatra törekszünk, tehát minden, amit ki lehet hagyni, kihagyásra kerül. Ezért például az `index[.html]` route az `/index` utat generálja. A viselkedés megfordítása a bal szögletes zárójel utáni felkiáltójellel lehetséges: ```php -// elfogadja a /hello és a /hello.html fájlt is, és /hello.html-t generál. +// elfogadja a /hello és /hello.html címeket, /hello-t generál $router->addRoute('[.html]', /* ... */); -// elfogadja a /hello és a /hello.html címeket is, generálja a /hello.html címet. +// elfogadja a /hello és /hello.html címeket, /hello.html-t generál $router->addRoute('[!.html]', /* ... */); ``` -A szögletes zárójel nélküli opcionális paraméterek (azaz az alapértelmezett értékkel rendelkező paraméterek) úgy viselkednek, mintha így lennének becsomagolva: +Az opcionális paraméterek (azaz az alapértelmezett értékkel rendelkező paraméterek) szögletes zárójelek nélkül lényegében úgy viselkednek, mintha a következőképpen lennének zárójelezve: ```php $router->addRoute('//', /* ... */); -// egyenlő: +// megfelel ennek: $router->addRoute('[/[/[]]]', /* ... */); ``` -Ha meg akarja változtatni a jobb oldali slash generálásának módját, azaz a `/home/` helyett egy `/home` kapjon, állítsa be az útvonalat így: +Ha befolyásolni szeretnénk a záró perjel viselkedését, hogy pl. a `/home/` helyett csak `/home` generálódjon, azt így lehet elérni: ```php $router->addRoute('[[/[/]]]', /* ... */); ``` -. .[#toc-wildcards] -------------------- +Helyettesítő karakterek +----------------------- -Az abszolút elérési útvonal maszkjában használhatjuk a következő jokereket, hogy elkerüljük például a tartomány beírását a maszkba, ami a fejlesztői és a termelési környezetben eltérő lehet: +Az abszolút út maszkjában használhatjuk a következő helyettesítő karaktereket, és így elkerülhetjük például annak szükségességét, hogy a maszkba írjuk a domaint, amely eltérhet a fejlesztői és az éles környezetben: -- `%tld%` = legfelső szintű tartomány, pl. `com` vagy `org` -- `%sld%` = második szintű tartomány, pl. `example` -- `%domain%` = aldomainek nélküli tartomány, pl. `example.com` -- `%host%` = teljes hoszt, pl. `www.example.com` -- `%basePath%` = a gyökérkönyvtár elérési útvonala +- `%tld%` = top level domain, pl. `com` vagy `org` +- `%sld%` = second level domain, pl. `example` +- `%domain%` = domain aldomainek nélkül, pl. `example.com` +- `%host%` = teljes host, pl. `www.example.com` +- `%basePath%` = út a gyökérkönyvtárhoz ```php $router->addRoute('//www.%domain%/%basePath%//', /* ... */); @@ -213,10 +212,10 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ @@ -225,7 +224,7 @@ $router->addRoute('/[/]', [ ]); ``` -Vagy használhatjuk ezt a formát, vegyük észre az érvényesítési reguláris kifejezés átírását: +Részletesebb specifikációhoz használható egy még bővebb forma, ahol az alapértelmezett értékeken kívül beállíthatjuk a paraméterek további tulajdonságait is, mint például a validációs reguláris kifejezést (lásd az `id` paramétert): ```php use Nette\Routing\Route; @@ -243,19 +242,19 @@ $router->addRoute('/[/]', [ ]); ``` -Ezek a beszédesebb formátumok hasznosak más metaadatok hozzáadásához. +Fontos megjegyezni, hogy ha a tömbben definiált paraméterek nincsenek megadva az út maszkjában, értéküket nem lehet megváltoztatni, még az URL-ben a kérdőjel után megadott query paraméterekkel sem. -Szűrők és fordítások .[#toc-filters-and-translations] ------------------------------------------------------ +Szűrők és fordítások +-------------------- -Jó gyakorlat, hogy a forráskódot angolul írjuk, de mi van akkor, ha a weboldalunknak le kell fordítani az URL-t más nyelvre? Egyszerű útvonalak, mint például: +Az alkalmazás forráskódjait angolul írjuk, de ha a weboldalnak magyar URL-ekkel kell rendelkeznie, akkor az egyszerű routing típus: ```php $router->addRoute('/', 'Home:default'); ``` -angol nyelvű URL-eket generálnak, például `/product/123` vagy `/cart`. Ha azt szeretnénk, hogy az URL-ben szereplő bemutatókat és műveleteket németre fordítsák le (pl. `/produkt/123` vagy `/einkaufswagen`), használhatunk egy fordítószótárat. Ennek hozzáadásához már a második paraméter "beszédesebb" változatára van szükségünk: +angol URL-eket fog generálni, mint például `/product/123` vagy `/cart`. Ha azt szeretnénk, hogy a presenterek és akciók az URL-ben magyar szavakkal legyenek reprezentálva (pl. `/produkt/123` vagy `/kosik`), használhatunk fordítási szótárat. Ennek megadásához már a második paraméter "beszédesebb" változatára van szükség: ```php use Nette\Routing\Route; @@ -264,26 +263,26 @@ $router->addRoute('/', [ 'presenter' => [ Route::Value => 'Home', Route::FilterTable => [ - // string az URL-ben => prezenter + // string az URL-ben => presenter 'produkt' => 'Product', - 'einkaufswagen' => 'Cart', + 'kosik' => 'Cart', 'katalog' => 'Catalog', ], ], 'action' => [ Route::Value => 'default', Route::FilterTable => [ - 'liste' => 'list', + 'lista' => 'list', ], ], ]); ``` -Több szótárkulcsot is használhatunk ugyanahhoz a prezentálóhoz. Ezek különböző aliasokat hoznak létre hozzá. Az utolsó kulcsot tekintjük a kanonikus változatnak (vagyis annak, amelyik a generált URL-ben szerepel majd). +A fordítási szótár több kulcsa is ugyanarra a presenterhez vezethet. Ezzel különböző aliasokat hozunk létre hozzá. Kanonikus változatnak (tehát annak, amely a generált URL-ben lesz) az utolsó kulcs számít. -A fordítási táblázat így bármely paraméterre alkalmazható. Ha azonban a fordítás nem létezik, akkor az eredeti értéket veszi át. Ezt a viselkedést megváltoztathatjuk a `Route::FilterStrict => true` hozzáadásával, és az útvonal ekkor elutasítja az URL-t, ha az érték nem szerepel a szótárban. +A fordítási táblát így bármelyik paraméterre lehet alkalmazni. Ha a fordítás nem létezik, az eredeti érték veszi át. Ezt a viselkedést megváltoztathatjuk a `Route::FilterStrict => true` hozzáadásával, és a route elutasítja az URL-t, ha az érték nincs a szótárban. -A fordítási szótár mellett tömb formájában saját fordítási függvények beállítására is lehetőség van: +A tömb formájú fordítási szótár mellett saját fordítási függvényeket is bevethetünk. ```php use Nette\Routing\Route; @@ -299,15 +298,15 @@ $router->addRoute('//', [ ]); ``` -A `Route::FilterIn` függvény az URL-ben szereplő paraméter és a karakterlánc között konvertál, amelyet aztán átadunk a prezentálónak, a `FilterOut` függvény pedig az ellenkező irányú konvertálást biztosítja. +A `Route::FilterIn` függvény átalakít az URL-ben lévő paraméter és a presenternek átadott string között, a `FilterOut` függvény pedig az ellenkező irányú átalakítást biztosítja. -A `presenter`, `action` és `module` paraméterek már rendelkeznek előre definiált szűrőkkel, amelyek az URL-ben használt PascalCase, illetve camelCase stílus és a kebab-case között konvertálnak. A paraméterek alapértelmezett értékét már az átalakított formában írjuk ki, így például egy bemutató esetében azt írjuk, hogy `` helyett ``. +A `presenter`, `action` és `module` paramétereknek már vannak előre definiált szűrőik, amelyek átalakítanak a PascalCase ill. camelCase stílus és az URL-ben használt kebab-case között. A paraméterek alapértelmezett értéke már az átalakított formában íródik, tehát például a presenter esetében ``-et írunk, nem pedig ``-et. -Általános szűrők .[#toc-general-filters] ----------------------------------------- +Általános szűrők +---------------- -A specifikus paraméterekhez tartozó szűrők mellett általános szűrőket is definiálhat, amelyek megkapják az összes paraméter asszociatív tömbjét, amelyeket bármilyen módon módosíthatnak, majd visszaadnak. Az általános szűrők a `null` kulcs alatt definiálhatók. +A konkrét paraméterekhez szánt szűrők mellett definiálhatunk általános szűrőket is, amelyek megkapják az összes paraméter asszociatív tömbjét, amelyet tetszőlegesen módosíthatnak, majd visszaadják. Az általános szűrőket a `null` kulcs alatt definiáljuk. ```php use Nette\Routing\Route; @@ -315,62 +314,85 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => 'Home', 'action' => 'default', - null => [ + '' => [ Route::FilterIn => function (array $params): array { /* ... */ }, Route::FilterOut => function (array $params): array { /* ... */ }, ], ]); ``` -Az általános szűrők lehetővé teszik, hogy az útvonal viselkedését teljesen tetszőleges módon módosítsa. Használhatjuk őket például arra, hogy a paramétereket más paraméterek alapján módosítsuk. Például fordítás `` és a `` paraméter aktuális értéke alapján ``. +Az általános szűrők lehetővé teszik a route viselkedésének teljesen tetszőleges módosítását. Használhatjuk őket például paraméterek módosítására más paraméterek alapján. Például a `` és `` lefordítása az aktuális `` paraméter értéke alapján. -Ha egy paraméterhez egyéni szűrő van definiálva és egyidejűleg létezik egy általános szűrő, az egyéni `FilterIn` az általános előtt kerül végrehajtásra, és fordítva, az általános `FilterOut` az egyéni előtt kerül végrehajtásra. Így az általános szűrőn belül a `presenter` illetve a `action` paraméterek értékei PascalCase illetve camelCase stílusban íródnak. +Ha egy paraméternek van saját szűrője definiálva, és egyidejűleg létezik általános szűrő is, akkor a saját `FilterIn` hajtódik végre az általános előtt, és fordítva, az általános `FilterOut` a saját előtt. Tehát az általános szűrőn belül a `presenter` ill. `action` paraméterek értékei PascalCase ill. camelCase stílusban vannak megadva. -Egyirányú zászló .[#toc-oneway-flag] ------------------------------------- +Egyirányú OneWay +---------------- -Az egyirányú útvonalakat arra használják, hogy megőrizzék a régi URL-ek funkcionalitását, amelyeket az alkalmazás már nem generál, de még elfogad. Ezeket a `OneWay` jelzéssel jelöljük: +Az egyirányú route-okat a régi URL-ek funkcionalitásának megőrzésére használják, amelyeket az alkalmazás már nem generál, de még mindig elfogad. `OneWay` jelzővel jelöljük őket: ```php // régi URL /product-info?id=123 $router->addRoute('product-info', 'Product:detail', $router::ONE_WAY); -// új URL /termék/123 +// új URL /product/123 $router->addRoute('product/', 'Product:detail'); ``` -A régi URL elérésekor a bemutató automatikusan átirányítja az új URL-re, hogy a keresőmotorok ne indexeljék kétszer ezeket az oldalakat (lásd [SEO és kanonizáció |#SEO and canonization]). +A régi URL-re való hozzáféréskor a presenter automatikusan átirányít az új URL-re, így ezeket az oldalakat a keresőmotorok nem indexelik kétszer (lásd [#SEO és kanonizáció]). -Modulok .[#toc-modules] ------------------------ +Dinamikus routing callbackekkel +------------------------------- + +A dinamikus routing callbackekkel lehetővé teszi, hogy a route-okhoz közvetlenül függvényeket (callbackeket) rendeljen, amelyek akkor hajtódnak végre, amikor az adott utat meglátogatják. Ez a rugalmas funkcionalitás lehetővé teszi, hogy gyorsan és hatékonyan hozzon létre különböző végpontokat (endpoints) az alkalmazásához: + +```php +$router->addRoute('test', function () { + echo 'a /test címen van'; +}); +``` -Ha több olyan útvonalunk van, amelyek egy [modulhoz |modules] tartoznak, akkor a `withModule()` segítségével csoportosíthatjuk őket: +Definiálhat paramétereket is a maszkban, amelyek automatikusan átadódnak a callbacknek: + +```php +$router->addRoute('', function (string $lang) { + echo match ($lang) { + 'cs' => 'Üdvözöljük weboldalunk cseh verzióján!', + 'en' => 'Welcome to the English version of our website!', + }; +}); +``` + + +Modulok +------- + +Ha több route-unk van, amelyek egy közös [modulba |directory-structure#Presenterek és sablonok] tartoznak, használjuk a `withModule()`-t: ```php $router = new RouteList; -$router->withModule('Forum') // a következő útválasztók a Forum modul részei - ->addRoute('rss', 'Feed:rss') // a bemutató a Forum:Feed +$router->withModule('Forum') // a következő route-ok a Forum modul részei + ->addRoute('rss', 'Feed:rss') // a presenter Forum:Feed lesz ->addRoute('/') - ->withModule('Admin') // a következő útválasztók a Forum:Admin modul részei - ->addRoute('sign:in', 'Bejelentkezés:in'); + ->withModule('Admin') // a következő route-ok a Forum:Admin modul részei + ->addRoute('sign:in', 'Sign:in'); ``` -Alternatív megoldás a `module` paraméter használata: +Alternatívaként használható a `module` paraméter: ```php -// Az URL manage/dashboard/default a prezenterhez tartozik Admin:Dashboard +// Az URL manage/dashboard/default az Admin:Dashboard presenterhez map-el $router->addRoute('manage//', [ 'module' => 'Admin', ]); ``` -Aldomainek .[#toc-subdomains] ------------------------------ +Aldomainek +---------- -Az útvonalgyűjteményeket aldomainek szerint lehet csoportosítani: +A route gyűjteményeket aldomainek szerint is tagolhatjuk: ```php $router = new RouteList; @@ -379,7 +401,7 @@ $router->withDomain('example.com') ->addRoute('/'); ``` -A domainnévben használhat [vadjelzőket |#wildcards] is: +A domain névben használhatunk [#Helyettesítő karakterek] helyettesítő karaktereket is: ```php $router = new RouteList; @@ -388,23 +410,23 @@ $router->withDomain('example.%tld%') ``` -Path Prefix .[#toc-path-prefix] -------------------------------- +Útvonal prefix +-------------- -Az útvonalgyűjteményeket az URL-ben szereplő elérési útvonal szerint lehet csoportosítani: +A route gyűjteményeket az URL útvonala szerint is tagolhatjuk: ```php $router = new RouteList; $router->withPath('eshop') - ->addRoute('rss', 'Feed:rss') // megfelel az URL-nek /eshop/rss - ->addRoute('/'); // megfelel az URL-nek /eshop// + ->addRoute('rss', 'Feed:rss') // elfogja az /eshop/rss URL-t + ->addRoute('/'); // elfogja az /eshop// URL-t ``` -Kombinációk .[#toc-combinations] --------------------------------- +Kombinációk +----------- -A fenti felhasználás kombinálható: +A fenti tagolásokat kölcsönösen kombinálhatjuk: ```php $router = (new RouteList) @@ -424,40 +446,40 @@ $router = (new RouteList) ``` -A lekérdezési paraméterek .[#toc-query-parameters] --------------------------------------------------- +Query paraméterek +----------------- -A maszkok tartalmazhatnak lekérdezési paramétereket is (az URL-ben a kérdőjel utáni paraméterek). Ezek nem határozhatnak meg érvényesítési kifejezést, de megváltoztathatják azt a nevet, amely alatt a bemutatónak átadják őket: +A maszkok tartalmazhatnak query paramétereket is (paraméterek a kérdőjel után az URL-ben). Ezekhez nem lehet validációs kifejezést definiálni, de meg lehet változtatni a nevüket, amely alatt a presenternek átadódnak: ```php -// a 'cat' lekérdezési paramétert 'categoryId'-ként használja az alkalmazásban +// a 'cat' query paramétert az alkalmazásban 'categoryId' néven szeretnénk használni $router->addRoute('product ? id= & cat=', /* ... */); ``` -Foo paraméterek .[#toc-foo-parameters] --------------------------------------- +Foo paraméterek +--------------- -Most mélyebbre megyünk. A Foo paraméterek alapvetően névtelen paraméterek, amelyek lehetővé teszik a reguláris kifejezésekkel való egyezést. A következő útvonal megfelel a `/index`, `/index.html`, `/index.htm` és `/index.php`: +Most már mélyebbre megyünk. A Foo paraméterek lényegében névtelen paraméterek, amelyek lehetővé teszik reguláris kifejezések illesztését. Példa egy route-ra, amely elfogadja a `/index`, `/index.html`, `/index.htm` és `/index.php` címeket: ```php $router->addRoute('index', /* ... */); ``` -Lehetőség van arra is, hogy explicit módon definiáljunk egy karakterláncot, amelyet az URL generálásához használunk. A karakterláncot közvetlenül a kérdőjel után kell elhelyezni. A következő útvonal hasonló az előzőhöz, de a `/index` helyett a `/index.html` címet generálja, mivel a `.html` karakterláncot "generált értékként" állítja be. +Explicit módon is definiálható a string, amelyet az URL generálásakor használni kell. A stringnek közvetlenül a kérdőjel után kell elhelyezkednie. A következő route hasonló az előzőhöz, de `/index.html`-t generál `/index` helyett, mert a `.html` string van beállítva generálási értékként: ```php $router->addRoute('index', /* ... */); ``` -Integráció .[#toc-integration] -============================== +Integrálás az alkalmazásba +========================== -Ahhoz, hogy a routerünket az alkalmazásba kapcsoljuk, tájékoztatnunk kell róla a DI konténert. A legegyszerűbb, ha elkészítjük a gyárat, amely a router objektumot fogja felépíteni, és megmondjuk a konténer konfigurációjának, hogy használja azt. Tegyük fel, hogy írunk egy metódust erre a célra `App\Router\RouterFactory::createRouter()`: +Ahhoz, hogy a létrehozott routert bekapcsoljuk az alkalmazásba, szólnunk kell róla a DI konténernek. A legegyszerűbb út egy factory elkészítése, amely a router objektumot létrehozza, és a konfigurációban közölni a konténerrel, hogy azt használja. Tegyük fel, hogy erre a célra megírjuk az `App\Core\RouterFactory::createRouter()` metódust: ```php -namespace App\Router; +namespace App\Core; use Nette\Application\Routers\RouteList; @@ -472,14 +494,14 @@ class RouterFactory } ``` -Ezután beírjuk a [konfigurációba |dependency-injection:services]: +A [konfigurációba |dependency-injection:services] pedig beírjuk: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter ``` -Az esetleges függőségeket, például az adatbázis-kapcsolatot stb., az [autowiring |dependency-injection:autowiring] segítségével paraméterként átadjuk a gyári metódusnak: +Bármilyen függőség, például adatbázisra stb., átadódik a factory metódusnak annak paramétereiként [autowiring|dependency-injection:autowiring] segítségével: ```php public static function createRouter(Nette\Database\Connection $db): RouteList @@ -489,25 +511,25 @@ public static function createRouter(Nette\Database\Connection $db): RouteList ``` -SimpleRouter .[#toc-simplerouter] -================================= +SimpleRouter +============ -A Route Collectionnél sokkal egyszerűbb útválasztó a [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Ezt akkor használhatjuk, ha nincs szükség egy adott URL formátumra, ha a `mod_rewrite` (vagy alternatívák) nem állnak rendelkezésre, vagy ha egyszerűen csak nem akarunk még a felhasználóbarát URL-ekkel bajlódni. +Sokkal egyszerűbb router, mint a route gyűjtemény, a [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Akkor használjuk, ha nincsenek különösebb igényeink az URL formájára, ha nincs `mod_rewrite` (vagy annak alternatívái) elérhető, vagy ha még nem akarunk szép URL-ekkel foglalkozni. -Nagyjából ilyen formában generál címeket: +Körülbelül ilyen formátumú címeket generál: ``` http://example.com/?presenter=Product&action=detail&id=123 ``` -A `SimpleRouter` konstruktor paramétere egy alapértelmezett bemutató és művelet, azaz a végrehajtandó művelet, ha további paraméterek nélkül megnyitjuk pl. a `http://example.com/` címet. +A SimpleRouter konstruktorának paramétere az alapértelmezett presenter & akció, amelyre irányítani kell, ha paraméterek nélkül nyitjuk meg az oldalt, pl. `http://example.com/`. ```php -// alapértelmezett bemutató 'Home' és művelet 'default' +// az alapértelmezett presenter 'Home' lesz, az akció pedig 'default' $router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` -Javasoljuk, hogy a SimpleRouter-t közvetlenül a [konfigurációban |dependency-injection:services] definiáljuk: +Javasoljuk a SimpleRouter közvetlen definiálását a [konfigurációban |dependency-injection:services]: ```neon services: @@ -515,22 +537,22 @@ services: ``` -SEO és kanonizáció .[#toc-seo-and-canonization] -=============================================== +SEO és kanonizáció +================== -A keretrendszer növeli a SEO-t (keresőmotor-optimalizálás) azáltal, hogy megakadályozza a tartalom duplikálódását a különböző URL-címeken. Ha több cím hivatkozik ugyanarra a célpontra, pl. `/index` és `/index.html`, a keretrendszer az elsőt határozza meg elsődlegesnek (kanonikusnak), és a többit 301-es HTTP-kóddal átirányítja rá. Ennek köszönhetően a keresőmotorok nem indexelik kétszer az oldalakat, és nem rontják az oldalrangsorukat. . +A keretrendszer hozzájárul a SEO-hoz (keresőoptimalizálás) azáltal, hogy megakadályozza a duplikált tartalom létezését különböző URL-eken. Ha egy bizonyos célhoz több cím vezet, pl. `/index` és `/index.html`, a keretrendszer az elsőt elsődlegesnek (kanonikusnak) határozza meg, a többit pedig 301-es HTTP kóddal átirányítja rá. Ennek köszönhetően a keresőmotorok nem indexelik kétszer az oldalakat, és nem osztják meg a page rankjüket. -Ezt a folyamatot nevezzük kanonizációnak. A kanonikus URL az, amelyet az útválasztó generál, azaz a [gyűjteményben |#route-collection] az első megfelelő útvonal a OneWay jelző nélkül. Ezért a gyűjteményben először **elsődleges útvonalakat** sorolunk fel. +Ezt a folyamatot kanonizációnak nevezik. A kanonikus URL az, amelyet a router generál, azaz az első megfelelő route a gyűjteményben OneWay jelző nélkül. Ezért a gyűjteményben **az elsődleges route-okat adjuk meg először**. -A kanonizálást a bemutató végzi, bővebben a [kanonizálás |presenters#Canonization] fejezetben. +A kanonizációt a presenter végzi, további információk a [kanonizáció |presenters#Kanonizáció] fejezetben. -HTTPS .[#toc-https] -=================== +HTTPS +===== -A HTTPS protokoll használatához aktiválni kell azt a tárhelyen, és konfigurálni kell a szervert. +Ahhoz, hogy a HTTPS protokollt használhassuk, engedélyezni kell a hostingen és helyesen kell konfigurálni a szervert. -A teljes webhely átirányítását HTTPS-re szerverszinten kell elvégezni, például az alkalmazásunk gyökérkönyvtárában lévő .htaccess fájl segítségével, 301-es HTTP-kóddal. A beállítások a tárhelytől függően eltérőek lehetnek, és valahogy így néz ki: +Az egész weboldal HTTPS-re való átirányítását a szerver szintjén kell beállítani, például a `.htaccess` fájl segítségével az alkalmazásunk gyökérkönyvtárában, 301-es HTTP kóddal. A beállítás eltérhet a hostingtól függően, és kb. így néz ki: ``` @@ -542,40 +564,40 @@ A teljes webhely átirányítását HTTPS-re szerverszinten kell elvégezni, pé ``` -Az útválasztó ugyanolyan protokollal generál egy URL-t, mint amilyen protokollal az oldal betöltődött, így semmi mást nem kell beállítani. +A router ugyanazzal a protokollal generálja az URL-eket, amellyel az oldal betöltődött, így semmi mást nem kell beállítani. -Ha azonban kivételesen szükségünk van arra, hogy különböző protokollok alatt különböző útvonalakat futtassunk, akkor ezt az útvonalmaszkban fogjuk megadni: +Ha azonban kivételesen szükségünk van arra, hogy különböző route-ok különböző protokollok alatt fussanak, azt a route maszkjában adjuk meg: ```php -// HTTP-címet generál +// HTTP-vel fog címet generálni $router->addRoute('http://%host%//', /* ... */); -// HTTPS-címet generál +// HTTPS-sel fog címet generálni $router->addRoute('https://%host%//', /* ... */); ``` -Debugging Router .[#toc-debugging-router] -========================================= +Router debuggolása +================== -A [Tracy Barban |tracy:] megjelenő útválasztó sáv egy hasznos eszköz, amely megjeleníti az útvonalak listáját, valamint az útválasztó által az URL-ből kapott paramétereket. +A [Tracy Barban |tracy:] megjelenő routing panel hasznos segítő, amely megjeleníti a route-ok listáját és azokat a paramétereket is, amelyeket a router az URL-ből nyert. -A ✓ szimbólummal ellátott zöld sáv az aktuális URL-nek megfelelő útvonalat jelzi, a ≈ szimbólummal ellátott kék sávok pedig azokat az útvonalakat jelzik, amelyek szintén megfelelnének az URL-nek, ha a zöld nem előzné meg őket. Továbbra is látjuk az aktuális előadót & akciót. +A zöld sáv a ✓ szimbólummal azt a route-ot jelöli, amely feldolgozta az aktuális URL-t, a kék szín és a ≈ szimbólum azokat a route-okat jelöli, amelyek szintén feldolgozták volna az URL-t, ha a zöld nem előzte volna meg őket. Továbbá látjuk az aktuális presentert & akciót. [* routing-debugger.webp *] -Ugyanakkor, ha a [kanonikalizálás |#SEO and Canonization] miatt váratlan átirányítás történik, hasznos megnézni az *átirányítás* sávot, hogy lássuk, hogyan értette eredetileg az útválasztó az URL-t, és miért irányított át. +Ugyanakkor, ha váratlan átirányítás történik a [kanonizáció |#SEO és kanonizáció] miatt, hasznos megnézni a *redirect* sávban lévő panelt, ahol megtudhatja, hogyan értelmezte a router eredetileg az URL-t, és miért irányított át. .[note] -Az útválasztó hibakeresésekor ajánlott megnyitni a böngészőben a Fejlesztői eszközök eszközeit (Ctrl+Shift+I vagy Cmd+Option+I), és letiltani a gyorsítótárat a Hálózat panelen, hogy az átirányítások ne tárolódjanak benne. +A router debuggolásakor javasoljuk a Developer Tools (Ctrl+Shift+I vagy Cmd+Option+I) megnyitását a böngészőben, és a Network panelen a cache kikapcsolását, hogy az átirányítások ne kerüljenek bele. -Teljesítmény .[#toc-performance] -================================ +Teljesítmény +============ -Az útvonalak száma befolyásolja az útválasztó sebességét. Számuk semmiképpen sem haladhatja meg a néhány tucatot. Ha webhelye túlságosan bonyolult URL-struktúrával rendelkezik, írhat egy [egyéni útválasztót |#custom router]. +A route-ok száma befolyásolja a router sebességét. Számuknak semmiképpen sem szabadna meghaladnia a néhány tucatot. Ha a weboldalának túl bonyolult az URL struktúrája, írhat saját, testreszabott [#Saját router] routert. -Ha az útválasztónak nincsenek függőségei, például egy adatbázistól, és a gyárának nincsenek argumentumai, akkor a lefordított formáját közvetlenül egy DI konténerbe szerializálhatjuk, és így az alkalmazást valamivel gyorsabbá tehetjük. +Ha a routernek nincsenek függőségei, például adatbázisra, és a factory-ja nem fogad argumentumokat, akkor az összeállított formáját közvetlenül a DI konténerbe szerializálhatjuk, és ezzel kissé felgyorsíthatjuk az alkalmazást. ```neon routing: @@ -583,10 +605,10 @@ routing: ``` -Egyéni útválasztó .[#toc-custom-router] -======================================= +Saját router +============ -A következő sorok nagyon haladó felhasználóknak szólnak. Létrehozhatja saját útválasztóját, és természetesen hozzáadhatja az útvonalgyűjteményéhez. Az útválasztó a [api:Nette\Routing\Router] interfész implementációja két metódussal: +A következő sorok nagyon haladó felhasználóknak szólnak. Létrehozhat saját routert, és teljesen természetesen beillesztheti a route gyűjteménybe. A router a [api:Nette\Routing\Router] interfész implementációja két metódussal: ```php use Nette\Http\IRequest as HttpRequest; @@ -606,8 +628,7 @@ class MyRouter implements Nette\Routing\Router } ``` -A `match` metódus az aktuális [$httpRequest-et |http:request], amelyből nem csak az URL, hanem a fejlécek stb. is kinyerhetők, egy tömbben dolgozza fel, amely a bemutató nevét és paramétereit tartalmazza. Ha nem tudja feldolgozni a kérést, akkor null értéket ad vissza. -A kérés feldolgozásakor legalább a prezentert és az actiont kell visszaadnunk. A prezenter neve teljes, és tartalmazza az esetleges modulokat: +A `match` metódus feldolgozza az aktuális kérést [$httpRequest |http:request], amelyből nemcsak az URL-t, hanem a fejléceket stb. is meg lehet szerezni, egy tömbbe, amely tartalmazza a presenter nevét és annak paramétereit. Ha nem tudja feldolgozni a kérést, null-t ad vissza. A kérés feldolgozásakor legalább a presentert és az akciót vissza kell adnunk. A presenter neve teljes, és tartalmazza az esetleges modulokat is: ```php [ @@ -616,9 +637,9 @@ A kérés feldolgozásakor legalább a prezentert és az actiont kell visszaadnu ] ``` -A `constructUrl` módszer ezzel szemben abszolút URL-t generál a paraméterek tömbjéből. Használhatja a `$refUrl` paraméterből származó információt, amely az aktuális URL. +A `constructUrl` metódus fordítva, a paraméterek tömbjéből állítja össze a végső abszolút URL-t. Ehhez felhasználhatja a [`$refUrl`|api:Nette\Http\UrlScript] paraméterből származó információkat, ami az aktuális URL. -Egyéni útvonal hozzáadásához az útvonalkollekcióhoz a `add()` használatával: +A route gyűjteményhez az `add()` segítségével adhatja hozzá: ```php $router = new Nette\Application\Routers\RouteList; @@ -628,19 +649,19 @@ $router->addRoute(/* ... */); ``` -Elkülönített használat .[#toc-separated-usage] -============================================== +Önálló használat +================ -A szeparált használat alatt a router képességeinek olyan alkalmazásban történő használatát értjük, amely nem használja a Nette alkalmazást és a prezentereket. Szinte minden, amit ebben a fejezetben bemutattunk, vonatkozik rá, a következő különbségekkel: +Önálló használat alatt azt értjük, hogy a router képességeit olyan alkalmazásban használjuk, amely nem használja a Nette Applicationt és a presentereket. Szinte minden érvényes rá, amit ebben a fejezetben megmutattunk, a következő különbségekkel: -- az útvonalgyűjteményekhez az osztályt használjuk [api:Nette\Routing\RouteList] -- egyszerű útválasztó osztályként [api:Nette\Routing\SimpleRouter] -- mivel nincs `Presenter:action` pár, a [Advanced jelölést |#Advanced notation]használjuk. +- route gyűjteményekhez a [api:Nette\Routing\RouteList] osztályt használjuk +- simple routerként a [api:Nette\Routing\SimpleRouter] osztályt +- mivel nincs `Presenter:action` pár, a [#Bővített jelölés] jelölést használjuk -Tehát ismét létrehozunk egy metódust, amely létrehoz egy útválasztót, például: +Tehát ismét létrehozunk egy metódust, amely összeállítja nekünk a routert, pl.: ```php -namespace App\Router; +namespace App\Core; use Nette\Routing\RouteList; @@ -661,26 +682,26 @@ class RouterFactory } ``` -Ha DI konténert használsz, amit ajánlunk, add hozzá a metódust ismét a konfigurációhoz, majd a HTTP-kérelemmel együtt kapd meg a routert a konténerből: +Ha DI konténert használ, amit javasolunk, ismét hozzáadjuk a metódust a konfigurációhoz, majd a routert a HTTP kéréssel együtt megszerezzük a konténerből: ```php $router = $container->getByType(Nette\Routing\Router::class); $httpRequest = $container->getByType(Nette\Http\IRequest::class); ``` -Vagy közvetlenül hozzuk létre az objektumokat: +Vagy közvetlenül létrehozzuk az objektumokat: ```php -$router = App\Router\RouterFactory::createRouter(); +$router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` -Most hagynunk kell, hogy a router működjön: +Most már csak hagyni kell a routert dolgozni: ```php $params = $router->match($httpRequest); if ($params === null) { - // nem találtunk megfelelő útvonalat, 404-es hibát küldünk. + // nem találtunk megfelelő route-ot, 404-es hibát küldünk exit; } @@ -689,7 +710,7 @@ $controller = $params['controller']; // ... ``` -És fordítva, az útválasztót fogjuk használni a kapcsolat létrehozására: +És fordítva, a routert használjuk a link összeállításához: ```php $params = ['controller' => 'ArticleController', 'id' => 123]; diff --git a/application/hu/templates.texy b/application/hu/templates.texy index 8f9478e63b..bc60ec602d 100644 --- a/application/hu/templates.texy +++ b/application/hu/templates.texy @@ -2,15 +2,15 @@ Sablonok ******** .[perex] -A Nette a [Latte |latte:] sablonrendszert használja. A Latte-t azért használjuk, mert ez a legbiztonságosabb sablonrendszer a PHP számára, ugyanakkor a legintuitívabb rendszer. Nem kell sok újat tanulnod, csak ismerned kell a PHP-t és néhány Latte taget. +A Nette a [Latte |latte:] sablonrendszert használja. Egyrészt azért, mert ez a legbiztonságosabb sablonrendszer PHP-hoz, másrészt pedig a legintuitívabb rendszer. Nem kell sok újat tanulnia, elegendő a PHP ismerete és néhány tag. -Az a szokásos, hogy az oldal az elrendezési sablonból + az akció sablonból készül el. Így nézhet ki egy layout sablon, figyeld meg a `{block}` blokkokat és a `{include}` címkét : +Gyakori, hogy egy oldal egy layout sablonból + az adott akció sablonjából áll össze. Így nézhet ki például egy layout sablon, figyelje meg a `{block}` blokkokat és a `{include}` taget: ```latte - {block title}My App{/block} + {block title}Saját Alkalmazás{/block}
    ...
    @@ -20,61 +20,109 @@ Az a szokásos, hogy az oldal az elrendezési sablonból + az akció sablonból ``` -És ez lehet az akció sablon: +És ez lesz az akció sablonja: ```latte -{block title}Homepage{/block} +{block title}Kezdőlap{/block} {block content} -

    Homepage

    +

    Kezdőlap

    ... {/block} ``` -Meghatározza a `content` blokkot, amely az elrendezésben a `{include content}` helyére kerül, és újra definiálja a `title` blokkot is, amely felülírja az elrendezésben a `{block title}` blokkot. Próbálja meg elképzelni az eredményt. +Ez definiálja a `content` blokkot, amely a `{include content}` helyére kerül a layoutban, és újra definiálja a `title` blokkot, amely felülírja a `{block title}`-t a layoutban. Próbálja meg elképzelni az eredményt. -Sablonok keresése .[#toc-search-for-templates] ----------------------------------------------- +Sablonok keresése +----------------- -A sablonok elérési útvonalát egyszerű logika szerint vezetjük le. Megpróbálja megnézni, hogy létezik-e valamelyik sablonfájl ahhoz a könyvtárhoz képest, ahol a prezenter osztály található, ahol a `` az aktuális prezenter neve és `` az aktuális művelet neve: +Nem kell a presenterekben megadnia, hogy melyik sablont kell renderelni, a keretrendszer maga vezeti le az utat, és megspórolja Önnek az írást. -- `templates//.latte` -- `templates/..latte` +Ha olyan könyvtárstruktúrát használ, ahol minden presenternek saját könyvtára van, egyszerűen helyezze el a sablont ebben a könyvtárban az akció (ill. view) nevével, azaz a `default` akcióhoz használja a `default.latte` sablont: -Ha nem találja a sablont, a válasz [404-es hiba |presenters#Error 404 etc.]. +/--pre +app/ +└── Presentation/ + └── Home/ + ├── HomePresenter.php + └── default.latte +\-- -A nézetet a `$this->setView('otherView')` segítségével is megváltoztathatja. Vagy a keresés helyett közvetlenül megadhatja a sablonfájl nevét a `$this->template->setFile('/path/to/template.latte')` segítségével. +Ha olyan struktúrát használ, ahol a presenterek egy könyvtárban vannak, a sablonok pedig a `templates` mappában, mentse el vagy a `..latte` vagy a `/.latte` fájlba: + +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── Home.default.latte ← 1. változat + └── Home/ + └── default.latte ← 2. változat +\-- + +A `templates` könyvtár egy szinttel feljebb is elhelyezkedhet, azaz ugyanazon a szinten, mint a presenter osztályokat tartalmazó könyvtár. + +Ha a sablon nem található, a presenter [404 - page not found hibával |presenters#Hiba 404 és társai] válaszol. + +A view-t a `$this->setView('jineView')` segítségével változtathatja meg. Közvetlenül is megadhatja a sablonfájlt a `$this->template->setFile('/path/to/template.latte')` segítségével. .[note] -A [formatTemplateFiles |api:Nette\Application\UI\Presenter::formatTemplateFiles()] metódus felülbírálásával módosíthatja azokat az elérési utakat, ahol a sablonok keresése történik, amely a lehetséges fájl elérési utak tömbjét adja vissza. +A fájlokat, ahol a sablonokat keresi, meg lehet változtatni a [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()] metódus felülírásával, amely visszaadja a lehetséges fájlnevek tömbjét. + + +Layout sablon keresése +---------------------- + +A Nette automatikusan megkeresi a layout fájlt is. + +Ha olyan könyvtárstruktúrát használ, ahol minden presenternek saját könyvtára van, helyezze el a layoutot vagy a presenter mappájában, ha csak rá specifikus, vagy egy szinttel feljebb, ha több presenter számára közös: + +/--pre +app/ +└── Presentation/ + ├── @layout.latte ← közös layout + └── Home/ + ├── @layout.latte ← csak a Home presenterhez + ├── HomePresenter.php + └── default.latte +\-- + +Ha olyan struktúrát használ, ahol a presenterek egy könyvtárban vannak, a sablonok pedig a `templates` mappában, a layoutot ezeken a helyeken várja: -Az elrendezés a következő fájlokban várható: +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── @layout.latte ← közös layout + ├── Home.@layout.latte ← csak a Home-hoz, 1. változat + └── Home/ + └── @layout.latte ← csak a Home-hoz, 2. változat +\-- -- `templates//@.latte` -- `templates/.@.latte` -- `templates/@.latte` több előadónál közös elrendezés +Ha a presenter egy modulban található, akkor további könyvtárszinteken is keresni fog, a modul beágyazási mélységétől függően. -`` az aktuális előadó neve és `` az elrendezés neve, amely alapértelmezés szerint `'layout'`. A név megváltoztatható a `$this->setLayout('otherLayout')` segítségével, így a `@otherLayout.latte` fájlokat próbálja meg. +A layout nevét a `$this->setLayout('layoutAdmin')` segítségével lehet megváltoztatni, és akkor a `@layoutAdmin.latte` fájlban várja. Közvetlenül is megadhatja a layout sablonfájlt a `$this->setLayout('/path/to/template.latte')` segítségével. -Az elrendezéssablon fájlnevét közvetlenül is megadhatja a `$this->setLayout('/path/to/template.latte')` segítségével. A `$this->setLayout(false)` használata letiltja az elrendezés keresését. +A `$this->setLayout(false)` vagy a `{layout none}` tag használatával a sablonon belül kikapcsolható a layout keresése. .[note] -A [formatLayoutTemplateFiles |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()] metódus felülbírálásával módosíthatja a sablonok keresési útvonalait, amely a lehetséges fájlútvonalak tömbjét adja vissza. +A fájlokat, ahol a layout sablonokat keresi, meg lehet változtatni a [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()] metódus felülírásával, amely visszaadja a lehetséges fájlnevek tömbjét. -Változók a sablonban .[#toc-variables-in-the-template] ------------------------------------------------------- +Változók a sablonban +-------------------- -A változókat a `$this->template` címre írva adjuk át a sablonba, majd a sablonban helyi változóként állnak rendelkezésre: +Változókat úgy adunk át a sablonnak, hogy beírjuk őket a `$this->template`-be, és utána lokális változókként érhetők el a sablonban: ```php $this->template->article = $this->articles->getById($id); ``` -Így könnyen átadhatunk bármilyen változót a sablonoknak. Robusztus alkalmazások fejlesztésénél azonban gyakran hasznosabb, ha korlátozzuk magunkat. Például úgy, hogy explicit módon definiáljuk a sablon által elvárt változók listáját és azok típusát. Ez lehetővé teszi a PHP számára a típusellenőrzést, az IDE számára a helyes automatikus kitöltést, a statikus elemzés számára pedig a hibák felderítését. +Így egyszerűen bármilyen változót átadhatunk a sablonoknak. Robusztus alkalmazások fejlesztésekor azonban hasznosabb korlátozni magunkat. Például úgy, hogy explicit módon definiáljuk a sablon által várt változók listáját és azok típusait. Ennek köszönhetően a PHP ellenőrizni tudja a típusokat, az IDE helyesen tud súgni, és a statikus analízis felfedezheti a hibákat. -És hogyan definiáljunk egy ilyen felsorolást? Egyszerűen egy osztály és annak tulajdonságai formájában. A presenterhez hasonlóan nevezzük el, de a végén `Template` címmel: +És hogyan definiálunk egy ilyen listát? Egyszerűen egy osztály és annak property-jei formájában. Nevezzük el hasonlóan a presenterhez, csak `Template` végződéssel: ```php /** @@ -89,26 +137,26 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template public Model\Article $article; public Nette\Security\User $user; - // és egyéb változók + // és további változók } ``` -A `$this->template` objektum a prezenterben mostantól a `ArticleTemplate` osztály példánya lesz. A PHP tehát ellenőrizni fogja a deklarált típusokat íráskor. A PHP 8.2-től kezdve pedig figyelmeztetni fog a nem létező változóba való írásra is, a korábbi verziókban ugyanezt a [Nette\SmartObject |utils:smartobject] tulajdonsággal lehet elérni. +A `$this->template` objektum a presenterben mostantól az `ArticleTemplate` osztály példánya lesz. Így a PHP ellenőrizni fogja a deklarált típusokat íráskor. És a PHP 8.2-es verziójától kezdve figyelmeztet a nem létező változóba való írásra is, korábbi verziókban ugyanezt a [Nette\SmartObject |utils:smartobject] trait használatával lehet elérni. -A `@property-read` annotáció az IDE és a statikus elemzés számára készült, ez teszi működőképessé az automatikus kitöltést, lásd "PhpStorm és kódkiegészítés $this->template számára":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. +Az `@property-read` annotáció az IDE-nek és a statikus analízisnek szól, ennek köszönhetően működni fog a súgó, lásd "PhpStorm and code completion for $this⁠-⁠>⁠template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. [* phpstorm-completion.webp *] -A sablonokban is megengedheted magadnak a suttogás luxusát, csak telepítsd a Latte plugint a PhpStormban, és add meg az osztály nevét a sablon elején, lásd a "Latte: hogyan kell tipizálni a rendszert":https://blog.nette.org/hu/latte-hogyan-kell-hasznalni-a-tipusrendszert című cikket: +A súgó luxusát a sablonokban is élvezheti, csak telepíteni kell a PhpStorm-ba a Latte plugint, és a sablon elejére beírni az osztály nevét, további információk a "Latte: hogyan a típusrendszerre":https://blog.nette.org/hu/latte-how-to-use-type-system cikkben: ```latte -{templateType App\Presenters\ArticleTemplate} +{templateType App\Presentation\Article\ArticleTemplate} ... ``` -Így működnek a sablonok a komponensekben is, csak kövesse a névadási konvenciót, és hozzon létre egy sablon osztályt `FifteenTemplate` a komponenshez pl. `FifteenControl`. +Így működnek a sablonok a komponensekben is, csak be kell tartani a névkonvenciót, és például a `FifteenControl` komponenshez létrehozni egy `FifteenTemplate` sablonosztályt. -Ha a `$template` egy másik osztály példányaként kell létrehozni, használjuk a `createTemplate()` metódust: +Ha a `$template`-et egy másik osztály példányaként kell létrehoznia, használja a `createTemplate()` metódust: ```php public function renderDefault(): void @@ -121,43 +169,43 @@ public function renderDefault(): void ``` -Alapértelmezett változók .[#toc-default-variables] --------------------------------------------------- +Alapértelmezett változók +------------------------ -A prezenterek és komponensek számos hasznos változót adnak át automatikusan a sablonoknak: +A presenterek és komponensek automatikusan átadnak néhány hasznos változót a sablonoknak: -- `$basePath` egy abszolút URL elérési útvonal a gyökérkönyvtárhoz (például `/CD-collection`). -- `$baseUrl` egy abszolút URL cím a gyökérkönyvtárhoz (pl. `http://localhost/CD-collection`) -- `$user` egy objektum, [amely a felhasználót képviseli |security:authentication] -- `$presenter` az aktuális előadó -- `$control` az aktuális komponens vagy bemutató -- `$flashes` a módszer által küldött [üzenetek |presenters#flash-messages] listája `flashMessage()` +- `$basePath` az abszolút URL elérési út a gyökérkönyvtárhoz (pl. `/eshop`) +- `$baseUrl` az abszolút URL a gyökérkönyvtárhoz (pl. `http://localhost/eshop`) +- `$user` a [felhasználót reprezentáló |security:authentication] objektum +- `$presenter` az aktuális presenter +- `$control` az aktuális komponens vagy presenter +- `$flashes` a `flashMessage()` függvénnyel küldött [üzenetek |presenters#Flash üzenetek] tömbje -Ha egyéni sablonosztályt használ, ezeket a változókat akkor adja át, ha létrehoz egy tulajdonságot számukra. +Ha saját sablonosztályt használ, ezek a változók átadódnak, ha létrehoz hozzájuk property-t. -Linkek létrehozása .[#toc-creating-links] ------------------------------------------ +Linkek létrehozása +------------------ -A sablonban az alábbiak szerint hozunk létre linkeket más előadókra és műveletekre: +A sablonban a linkek más presenterekhez & akciókhoz a következőképpen hozhatók létre: ```latte -detail +termék részletei ``` -A `n:href` attribútum nagyon hasznos a HTML címkékhez. ``. Ha a linket máshol, például a szövegben akarjuk kiírni, akkor a `{link}`-t használjuk: +Az `n:href` attribútum nagyon praktikus a HTML `` tag-ekhez. Ha máshol szeretnénk kiírni a linket, például szövegben, használjuk a `{link}`-et: ```latte -URL is: {link Home:default} +A cím: {link Home:default} ``` -További információért lásd: [Linkek létrehozása |Creating Links]. +További információkat az [URL linkek létrehozása|creating-links] fejezetben talál. -Egyéni szűrők, címkék stb. .[#toc-custom-filters-tags-etc] ----------------------------------------------------------- +Saját szűrők, tag-ek stb. +------------------------- -A Latte templating rendszer kibővíthető egyéni szűrőkkel, függvényekkel, címkékkel stb. Ez közvetlenül a `render` vagy a `beforeRender()` metódusban: +A Latte sablonrendszert ki lehet bővíteni saját szűrőkkel, függvényekkel, tag-ekkel stb. Ezt meg lehet tenni közvetlenül a `render` vagy `beforeRender()` metódusban: ```php public function beforeRender(): void @@ -165,16 +213,16 @@ public function beforeRender(): void // szűrő hozzáadása $this->template->addFilter('foo', /* ... */); - // vagy közvetlenül a Latte\Engine objektum konfigurálása + // vagy közvetlenül konfiguráljuk a Latte\Engine objektumot $latte = $this->template->getLatte(); $latte->addFilterLoader(/* ... */); } ``` -A Latte 3. verziója egy fejlettebb módszert kínál, amely minden egyes webes projekthez egy [bővítményt |latte:creating-extension] hoz létre. Íme egy durva példa egy ilyen osztályra: +A Latte 3-as verziója fejlettebb módszert kínál, mégpedig egy [extension |latte:extending-latte#Latte Extension] létrehozását minden webprojekthez. Egy ilyen osztály töredékes példája: ```php -namespace App\Templating; +namespace App\Presentation\Accessory; final class LatteExtension extends Latte\Extension { @@ -207,22 +255,21 @@ final class LatteExtension extends Latte\Extension } ``` -A [configuration |configuration#Latte] segítségével regisztráljuk: +Regisztráljuk a [konfiguráció |configuration#Latte sablonok] segítségével: ```neon latte: extensions: - - App\Templating\LatteExtension + - App\Presentation\Accessory\LatteExtension ``` -Fordítás .[#toc-translating] ----------------------------- +Fordítás +-------- -Ha többnyelvű alkalmazást programoz, valószínűleg a sablonban lévő szöveg egy részét különböző nyelveken kell majd kiadnia. Ehhez a Nette Framework definiál egy fordítási felületet [api:Nette\Localization\Translator], amelynek egyetlen metódusa a `translate()`. Ez elfogadja a `$message` üzenetet, amely általában egy karakterlánc, és bármilyen más paramétert. A feladat a lefordított karakterlánc visszaadása. -A Nette-ben nincs alapértelmezett implementáció, a [Componette-en |https://componette.org/search/localization] található számos kész megoldás közül választhatunk igényeinknek megfelelően. A dokumentációjukból megtudhatjuk, hogyan kell a fordítót konfigurálni. +Ha többnyelvű alkalmazást programoz, valószínűleg szüksége lesz néhány szöveg lefordítására a sablonban különböző nyelvekre. A Nette Framework erre a célra definiál egy interfészt a fordításhoz [api:Nette\Localization\Translator], amelynek egyetlen metódusa van, a `translate()`. Ez fogadja az üzenetet `$message`, ami általában egy string, és tetszőleges további paramétereket. Feladata a lefordított string visszaadása. A Nette-ben nincs alapértelmezett implementáció, választhat igényei szerint több kész megoldás közül, amelyeket a [Componette |https://componette.org/search/localization] oldalon talál. Dokumentációjukban megtudhatja, hogyan konfigurálja a translatort. -A sablonokat a `setTranslator()` metódus segítségével állíthatjuk be egy fordítóval, amelyet [átadunk mag |dependency-injection:passing-dependencies]unknak: +A sablonoknak beállítható egy fordító, amelyet [átkérünk |dependency-injection:passing-dependencies], a `setTranslator()` metódussal: ```php protected function beforeRender(): void @@ -232,38 +279,38 @@ protected function beforeRender(): void } ``` -Alternatívaként a fordítót a [konfiguráció |configuration#Latte] segítségével is beállíthatjuk: +A translatort alternatívaként be lehet állítani a [konfiguráció |configuration#Latte sablonok] segítségével is: ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -A fordító ekkor például a `|translate` szűrőként használható, a `translate()` metódusnak átadott további paraméterekkel (lásd `foo, bar`): +Ezután a fordítót például használhatjuk `|translate` szűrőként, beleértve a kiegészítő paramétereket is, amelyek átadódnak a `translate()` metódusnak (lásd `foo, bar`): ```latte -{='Basket'|translate} +{='Kosár'|translate} {$item|translate} {$item|translate, foo, bar} ``` -Vagy aláhúzáscímkeként: +Vagy aláhúzásos tagként: ```latte -{_'Basket'} +{_'Kosár'} {_$item} {_$item, foo, bar} ``` -A sablonrészlet fordításához van egy párosított tag `{translate}` (a Latte 2.11 óta, korábban a `{_}` taget használták ): +A sablon egy szakaszának fordításához létezik egy páros `{translate}` tag (Latte 2.11-től, korábban a `{_}` tag volt használatos): ```latte -{translate}Order{/translate} -{translate foo, bar}Order{/translate} +{translate}Rendelés{/translate} +{translate foo, bar}Rendelés{/translate} ``` -A fordítót alapértelmezés szerint futásidőben hívja meg a sablon renderelésekor. A Latte 3. verziója azonban képes lefordítani az összes statikus szöveget a sablon összeállítása során. Ez teljesítményt takarít meg, mivel minden egyes karakterláncot csak egyszer fordít le, és az így kapott fordítás a lefordított formába kerül. Ez a sablon több lefordított változatát hozza létre a gyorsítótárban, egyet-egyet minden nyelvhez. Ehhez csak a nyelvet kell megadni második paraméterként: +A translator alapértelmezés szerint futásidőben hívódik meg a sablon renderelésekor. A Latte 3-as verziója azonban képes az összes statikus szöveget már a sablon fordítása során lefordítani. Ezzel teljesítményt takarítunk meg, mert minden string csak egyszer fordítódik le, és az eredményül kapott fordítás beíródik a lefordított formába. A cache könyvtárban így több lefordított sablonverzió jön létre, minden nyelvre egy. Ehhez elegendő csak a nyelvet második paraméterként megadni: ```php protected function beforeRender(): void @@ -273,4 +320,4 @@ protected function beforeRender(): void } ``` -Statikus szöveg alatt például a `{_'hello'}` vagy a `{translate}hello{/translate}` szöveget értjük. A nem statikus szövegek, például a `{_$foo}`, továbbra is menet közben kerülnek lefordításra. +Statikus szöveg alatt például a `{_'hello'}` vagy `{translate}hello{/translate}` értendő. A nem statikus szövegek, mint például a `{_$foo}`, továbbra is futásidőben fordítódnak. diff --git a/application/it/@home.texy b/application/it/@home.texy index 6c54a56fdd..ff9cedc27c 100644 --- a/application/it/@home.texy +++ b/application/it/@home.texy @@ -1,36 +1,85 @@ -Applicazione Nette -****************** +Nette Application +***************** .[perex] -Il pacchetto `nette/application` è la base per la creazione di applicazioni web interattive. - -- [Come funzionano le applicazioni? |how-it-works] -- [Bootstrap |Bootstrap] -- [Presentatori |Presenters] -- [Modelli |Templates] -- [Moduli |Modules] -- [Instradamento |Routing] -- [Creazione di collegamenti URL |creating-links] -- [Componenti interattivi |components] -- [AJAX e Snippet |ajax] -- [Moltiplicatore |multiplier] -- [Configurazione |Configuration] +Nette Application è il cuore del framework Nette e fornisce potenti strumenti per la creazione di moderne applicazioni web. Offre una serie di funzionalità eccezionali che semplificano notevolmente lo sviluppo e migliorano la sicurezza e la manutenibilità del codice. Installazione ------------- -Scaricare e installare il pacchetto utilizzando [Composer |best-practices:composer]: +È possibile scaricare e installare la libreria utilizzando lo strumento [Composer|best-practices:composer]: ```shell composer require nette/application ``` -| versione | compatibile con PHP -|-----------|------------------- -| Applicazione Nette 4.0 | PHP 8.0 - 8.2 -| Applicazione Nette 3.1 | PHP 7.2 - 8.2 -| Applicazione Nette 3.0 | PHP 7.1 - 8.0 -| Applicazione Nette 2.4 | PHP 5.6 - 8.0 -Si applica alle ultime versioni della patch. +Perché scegliere Nette Application? +----------------------------------- + +Nette è sempre stato un pioniere nel campo delle tecnologie web. + +**Router bidirezionale:** Nette dispone di un sistema di routing avanzato, unico per la sua bidirezionalità: non solo traduce gli URL in azioni dell'applicazione, ma può anche generare indirizzi URL a ritroso. Ciò significa che: +- È possibile modificare in qualsiasi momento la struttura degli URL dell'intera applicazione senza dover modificare i template. +- Gli URL vengono automaticamente canonizzati, migliorando la SEO. +- Il routing è definito in un unico punto, non sparso nelle annotazioni. + +**Componenti e segnali:** Il sistema di componenti integrato, ispirato a Delphi e React.js, è del tutto eccezionale tra i framework PHP: +- Permette di creare elementi UI riutilizzabili. +- Supporta la composizione gerarchica dei componenti. +- Offre un'elegante gestione delle request AJAX tramite segnali. +- Una ricca libreria di componenti pronti è disponibile su [Componette](https://componette.org). + +**AJAX e snippet:** Nette ha introdotto un modo rivoluzionario di lavorare con AJAX già nel 2009, molto prima di soluzioni simili come Hotwire per Ruby on Rails o Symfony UX Turbo: +- Gli snippet consentono di aggiornare solo parti della pagina senza dover scrivere JavaScript. +- Integrazione automatica con il sistema dei componenti. +- Invalidazione intelligente di parti delle pagine. +- Quantità minima di dati trasferiti. + +**Template intuitivi [Latte|latte:]:** Il sistema di template più sicuro per PHP con funzionalità avanzate: +- Protezione automatica contro XSS con escaping sensibile al contesto. +- Estensibilità tramite filtri, funzioni e tag personalizzati. +- Ereditarietà dei template e snippet per AJAX. +- Eccellente supporto per PHP 8.x con sistema di tipi. + +**Dependency Injection:** Nette sfrutta appieno la Dependency Injection: +- Passaggio automatico delle dipendenze (autowiring). +- Configurazione tramite il chiaro formato NEON. +- Supporto per le factory di componenti. + + +Vantaggi principali +------------------- + +- **Sicurezza**: Difesa automatica contro [vulnerabilità|nette:vulnerability-protection] come XSS, CSRF, ecc. +- **Produttività**: Meno codice da scrivere, più funzionalità grazie a un design intelligente. +- **Debugging**: [Tracy debugger|tracy:] con pannello di routing. +- **Prestazioni**: Cache intelligente, lazy loading dei componenti. +- **Flessibilità**: Facile modifica degli URL anche dopo il completamento dell'applicazione. +- **Componenti**: Sistema unico di elementi UI riutilizzabili. +- **Moderno**: Pieno supporto per PHP 8.4+ e sistema di tipi. + + +Per iniziare +------------ + +1. [Come funzionano le applicazioni? |how-it-works] - Comprensione dell'architettura di base. +2. [Presenter |presenters] - Lavorare con i presenter e le azioni. +3. [Template |templates] - Creazione di template in Latte. +4. [Routing |routing] - Configurazione degli indirizzi URL. +5. [Componenti interattivi |components] - Utilizzo del sistema di componenti. + + +Compatibilità con PHP +--------------------- + +| versione | compatibile con PHP +|------------------------|------------------- +| Nette Application 4.0 | PHP 8.1 – 8.4 +| Nette Application 3.2 | PHP 8.1 – 8.4 +| Nette Application 3.1 | PHP 7.2 – 8.3 +| Nette Application 3.0 | PHP 7.1 – 8.0 +| Nette Application 2.4 | PHP 5.6 – 8.0 + +Si applica all'ultima versione patch. diff --git a/application/it/@left-menu.texy b/application/it/@left-menu.texy index e7d6c10da4..011cf72065 100644 --- a/application/it/@left-menu.texy +++ b/application/it/@left-menu.texy @@ -1,19 +1,22 @@ -Applicazione Nette -****************** +Nette Application +***************** - [Come funzionano le applicazioni? |how-it-works] -- [Bootstrap |Bootstrap] -- [Presentatori |Presenters] -- [Modelli |Templates] -- [Moduli |Modules] -- [Instradamento |Routing] -- [Creazione di collegamenti URL |creating-links] +- [Bootstrapping] +- [Presenter |presenters] +- [Template |templates] +- [Struttura delle directory |directory-structure] +- [Routing |routing] +- [Creazione di link URL |creating-links] - [Componenti interattivi |components] -- [AJAX e Snippet |ajax] -- [Moltiplicatore |multiplier] -- [Configurazione |Configuration] +- [AJAX & snippet |ajax] +- [Multiplier |multiplier] +- [Configurazione |configuration] Ulteriori letture ***************** -- [Migliori pratiche |best-practices:] +- [Perché usare Nette? |www:10-reasons-why-nette] +- [Installazione |nette:installation] +- [Scriviamo la prima applicazione! |quickstart:] +- [Guide e procedure |best-practices:] - [Risoluzione dei problemi |nette:troubleshooting] diff --git a/application/it/@meta.texy b/application/it/@meta.texy new file mode 100644 index 0000000000..4647d0c8a2 --- /dev/null +++ b/application/it/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentazione Nette}} diff --git a/application/it/ajax.texy b/application/it/ajax.texy index 1f583a4321..8831de7c8d 100644 --- a/application/it/ajax.texy +++ b/application/it/ajax.texy @@ -1,38 +1,45 @@ -AJAX e Snippet +AJAX & snippet **************
    -Le moderne applicazioni web oggi vengono eseguite per metà su un server e per metà in un browser. AJAX è un fattore di unione vitale. Quale supporto offre il Nette Framework? -- invio di frammenti di template (i cosiddetti *snippet*) +Nell'era delle moderne applicazioni web, dove la funzionalità è spesso suddivisa tra server e browser, AJAX è un elemento di collegamento essenziale. Quali opzioni ci offre Nette Framework in questo campo? +- invio di parti del template, i cosiddetti snippet - passaggio di variabili tra PHP e JavaScript -- debug delle applicazioni AJAX +- strumenti per il debug delle richieste AJAX
    -Una richiesta AJAX può essere rilevata utilizzando un metodo di un servizio [che incapsula una richiesta HTTP |http:request] `$httpRequest->isAjax()` (rileva in base all'intestazione HTTP `X-Requested-With` ). Esiste anche un metodo abbreviato in presenter: `$this->isAjax()`. -Una richiesta AJAX non è diversa da una normale richiesta: un presenter viene chiamato con una determinata vista e con dei parametri. Anche il presentatore può decidere come reagire: può usare le sue routine per restituire un frammento di codice HTML (uno snippet), un documento XML, un oggetto JSON o un pezzo di codice Javascript. +Richiesta AJAX +============== -Esiste un oggetto pre-elaborato chiamato `payload`, dedicato all'invio di dati al browser in JSON. +Una richiesta AJAX non è fondamentalmente diversa da una classica richiesta HTTP. Viene chiamato un presenter con determinati parametri. E spetta al presenter decidere come reagire alla richiesta: può restituire dati in formato JSON, inviare una parte di codice HTML, un documento XML, ecc. -```php -public function actionDelete(int $id): void -{ - if ($this->isAjax()) { - $this->payload->message = 'Success'; - } - // ... -} +Sul lato browser, inizializziamo la richiesta AJAX utilizzando la funzione `fetch()`: + +```js +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) +.then(response => response.json()) +.then(payload => { + // elaborazione della risposta +}); ``` -Per un controllo completo sull'output JSON, utilizzare il metodo `sendJson` nel presenter. Il metodo termina immediatamente il presentatore e si può fare a meno di un template: +Sul lato server, riconosciamo una richiesta AJAX con il metodo `$httpRequest->isAjax()` del servizio [incapsulando la richiesta HTTP |http:request]. Per il rilevamento utilizza l'header HTTP `X-Requested-With`, quindi è importante inviarlo. All'interno del presenter è possibile utilizzare il metodo `$this->isAjax()`. + +Se si desidera inviare dati in formato JSON, utilizzare il metodo [`sendJson()` |presenters#Invio della risposta]. Il metodo termina anche l'attività del presenter. ```php -$this->sendJson(['key' => 'value', /* ... */]); +public function actionExport(): void +{ + $this->sendJson($this->model->getData); +} ``` -Se vogliamo inviare HTML, possiamo impostare un modello speciale per le richieste AJAX: +Se si prevede di rispondere con un template speciale progettato per AJAX, è possibile farlo come segue: ```php public function handleClick($param): void @@ -45,222 +52,198 @@ public function handleClick($param): void ``` -Naja .[#toc-naja] -================= +Snippet +======= + +Lo strumento più potente offerto da Nette per collegare il server al client sono gli snippet. Grazie ad essi, è possibile trasformare un'applicazione ordinaria in una AJAX con uno sforzo minimo e poche righe di codice. L'esempio Fifteen, il cui codice si trova su [GitHub |https://github.com/nette-examples/fifteen], dimostra come funziona il tutto. + +Gli snippet, o frammenti, consentono di aggiornare solo parti della pagina invece di ricaricare l'intera pagina. Questo non solo è più veloce ed efficiente, ma offre anche un'esperienza utente più confortevole. Gli snippet potrebbero ricordarvi Hotwire per Ruby on Rails o Symfony UX Turbo. È interessante notare che Nette ha introdotto gli snippet già 14 anni prima. + +Come funzionano gli snippet? Al primo caricamento della pagina (richiesta non AJAX), viene caricata l'intera pagina, inclusi tutti gli snippet. Quando l'utente interagisce con la pagina (ad esempio, fa clic su un pulsante, invia un form, ecc.), viene attivata una richiesta AJAX invece di caricare l'intera pagina. Il codice nel presenter esegue l'azione e decide quali snippet devono essere aggiornati. Nette esegue il rendering di questi snippet e li invia come array in formato JSON. Il codice di gestione nel browser riceve gli snippet e li inserisce nuovamente nella pagina. Viene trasferito solo il codice degli snippet modificati, risparmiando larghezza di banda e accelerando il caricamento rispetto al trasferimento dell'intero contenuto della pagina. + -La [libreria Naja |https://naja.js.org] è usata per gestire le richieste AJAX sul lato browser. [Installarla |https://naja.js.org/#/guide/01-install-setup-naja] come pacchetto node.js (da usare con Webpack, Rollup, Vite, Parcel e altri): +Naja +---- + +Per gestire gli snippet sul lato browser, viene utilizzata la [libreria Naja |https://naja.js.org]. [Installala |https://naja.js.org/#/guide/01-install-setup-naja] come pacchetto node.js (per l'uso con applicazioni Webpack, Rollup, Vite, Parcel e altre): ```shell npm install naja ``` -... o inserirla direttamente nel modello della pagina: +…o inseriscila direttamente nel template della pagina: ```html ``` +Innanzitutto, è necessario [inizializzare |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] la libreria: -Snippet .[#toc-snippets] -======================== +```js +naja.initialize(); +``` -Esiste uno strumento molto più potente del supporto AJAX integrato: gli snippet. Il loro utilizzo consente di trasformare una normale applicazione in un'applicazione AJAX utilizzando solo poche righe di codice. Il funzionamento è dimostrato nell'esempio di Fifteen, il cui codice è accessibile nella build o su [GitHub |https://github.com/nette-examples/fifteen]. +Per trasformare un link ordinario (segnale) o l'invio di un form in una richiesta AJAX, è sufficiente contrassegnare il link, il form o il pulsante pertinente con la classe `ajax`: -Il modo in cui funzionano gli snippet è che l'intera pagina viene trasferita durante la richiesta iniziale (cioè non AJAX) e poi a ogni [sotto-richiesta |components#signal] AJAX (richiesta della stessa vista dello stesso presentatore) viene trasferito solo il codice delle parti modificate nel repository `payload` menzionato in precedenza. +```html +Vai -Gli snippet possono ricordare Hotwire per Ruby on Rails o Symfony UX Turbo, ma Nette li ha ideati quattordici anni prima. +
    + +
    +oppure -Invalidazione degli Snippet .[#toc-invalidation-of-snippets] -============================================================ +
    + +
    +``` -Ogni discendente della classe [Control |components] (di cui fa parte anche un Presentatore) è in grado di ricordare se durante una richiesta sono state apportate modifiche che richiedono un nuovo rendering. Esistono due metodi per gestire questo aspetto: `redrawControl()` e `isControlInvalid()`. Un esempio: + +Ridisegno degli snippet +----------------------- + +Ogni oggetto della classe [Control |components] (incluso il Presenter stesso) tiene traccia se ci sono state modifiche che richiedono il suo ridisegno. A tale scopo viene utilizzato il metodo `redrawControl()`: ```php public function handleLogin(string $user): void { - // L'oggetto deve essere ri-renderizzato dopo che l'utente ha effettuato il login + // dopo il login, è necessario ridisegnare la parte pertinente $this->redrawControl(); // ... } ``` -Nette offre tuttavia una risoluzione ancora più fine rispetto ai componenti interi. I metodi elencati accettano il nome di un cosiddetto "snippet" come parametro opzionale. Uno "snippet" è fondamentalmente un elemento del modello contrassegnato a tale scopo da una tag di Latte, di cui si parlerà più avanti. In questo modo è possibile chiedere a un componente di ridisegnare solo *parti* del suo modello. Se l'intero componente viene invalidato, tutti i suoi frammenti vengono ridisegnati. Un componente è "invalido" anche se uno qualsiasi dei suoi sottocomponenti è invalido. - -```php -$this->isControlInvalid(); // -> false -$this->redrawControl('header'); // invalida lo snippet denominato 'header' -$this->isControlInvalid('header'); // -> true -$this->isControlInvalid('footer'); // -> false -$this->isControlInvalid(); // -> true, almeno uno snippet non è valido +Nette consente un controllo ancora più preciso su cosa deve essere ridisegnato. Il metodo menzionato può infatti accettare il nome dello snippet come argomento. È quindi possibile invalidare (cioè: forzare il ridisegno) a livello di parti del template. Se l'intero componente viene invalidato, verrà ridisegnato anche ogni suo snippet: -$this->redrawControl(); // invalida l'intero componente, ogni snippet -$this->isControlInvalid('footer'); // -> true +```php +// invalida lo snippet 'header' +$this->redrawControl('header'); ``` -Un componente che riceve un segnale viene automaticamente contrassegnato per essere ridisegnato. - -Grazie allo snippet redrawing sappiamo esattamente quali parti di quali elementi devono essere ridisegnate. - - -Tag `{snippet} … {/snippet}` .{toc: Tag snippet} -================================================ -Il rendering della pagina procede in modo molto simile a una normale richiesta: vengono caricati gli stessi template, ecc. La parte fondamentale è, tuttavia, lasciare fuori le parti che non devono raggiungere l'output; le altre parti devono essere associate a un identificatore e inviate all'utente in un formato comprensibile per un gestore JavaScript. +Snippet in Latte +---------------- - -Sintassi .[#toc-syntax] ------------------------ - -Se c'è un controllo o uno snippet nel template, dobbiamo avvolgerlo usando il tag di coppia `{snippet} ... {/snippet}` - che farà in modo che lo snippet reso venga "tagliato" e inviato al browser. Lo racchiuderà anche in un tag di aiuto `
    ` (è possibile utilizzarne uno diverso). Nell'esempio seguente viene definito uno snippet chiamato `header`. Può anche rappresentare il modello di un componente: +L'uso degli snippet in Latte è estremamente facile. Per definire una parte del template come snippet, è sufficiente racchiuderla tra i tag `{snippet}` e `{/snippet}`: ```latte {snippet header} -

    Hello ...

    +

    Ciao ...

    {/snippet} ``` -Uno snippet di tipo diverso da `
    ` o uno snippet con attributi HTML aggiuntivi si ottiene utilizzando la variante dell'attributo: +Lo snippet crea un elemento `
    ` nella pagina HTML con un `id` speciale generato. Quando lo snippet viene ridisegnato, il contenuto di questo elemento viene aggiornato. Pertanto, è necessario che al rendering iniziale della pagina vengano renderizzati anche tutti gli snippet, anche se potrebbero essere inizialmente vuoti. + +È possibile creare uno snippet con un elemento diverso da `
    ` utilizzando l'attributo n: ```latte
    -

    Hello ...

    +

    Ciao ...

    ``` -Snippet dinamici .[#toc-dynamic-snippets] -========================================= +Aree di snippet +--------------- -In Nette è possibile definire snippet con un nome dinamico basato su un parametro di runtime. Ciò è particolarmente indicato per vari elenchi in cui è necessario modificare una sola riga, ma non si vuole trasferire l'intero elenco. Un esempio potrebbe essere: +I nomi degli snippet possono anche essere espressioni: ```latte -
      - {foreach $list as $id => $item} -
    • {$item} update
    • - {/foreach} -
    +{foreach $items as $id => $item} +
  • {$item}
  • +{/foreach} ``` -Esiste uno snippet statico chiamato `itemsContainer`, che contiene diversi snippet dinamici: `item-0`, `item-1` e così via. +In questo modo vengono creati diversi snippet `item-0`, `item-1`, ecc. Se invalidassimo direttamente uno snippet dinamico (ad esempio `item-1`), non verrebbe ridisegnato nulla. Il motivo è che gli snippet funzionano davvero come ritagli e vengono renderizzati solo direttamente. Tuttavia, nel template non esiste effettivamente uno snippet chiamato `item-1`. Questo viene creato solo eseguendo il codice circostante lo snippet, cioè il ciclo foreach. Pertanto, contrassegniamo la parte del template che deve essere eseguita utilizzando il tag `{snippetArea}`: -Non è possibile ridisegnare direttamente uno snippet dinamico (il ridisegno di `item-1` non ha alcun effetto), ma è necessario ridisegnare il suo snippet padre (in questo esempio `itemsContainer`). Questo provoca l'esecuzione del codice dello snippet padre, ma poi solo i suoi sotto-snippet vengono inviati al browser. Se si vuole inviare solo uno dei sotto-nippet, è necessario modificare l'input dello snippet padre per non generare gli altri sotto-nippet. +```latte +
      + {foreach $items as $id => $item} +
    • {$item}
    • + {/foreach} +
    +``` -Nell'esempio precedente, bisogna assicurarsi che per una richiesta AJAX venga aggiunto un solo elemento all'array `$list`, quindi il ciclo `foreach` stamperà un solo frammento dinamico. +E facciamo ridisegnare sia lo snippet stesso che l'intera area genitore: ```php -class HomePresenter extends Nette\Application\UI\Presenter -{ - /** - * This method returns data for the list. - * Usually this would just request the data from a model. - * For the purpose of this example, the data is hard-coded. - */ - private function getTheWholeList(): array - { - return [ - 'First', - 'Second', - 'Third', - ]; - } - - public function renderDefault(): void - { - if (!isset($this->template->list)) { - $this->template->list = $this->getTheWholeList(); - } - } - - public function handleUpdate(int $id): void - { - $this->template->list = $this->isAjax() - ? [] - : $this->getTheWholeList(); - $this->template->list[$id] = 'Updated item'; - $this->redrawControl('itemsContainer'); - } -} +$this->redrawControl('itemsContainer'); +$this->redrawControl('item-1'); ``` +Allo stesso tempo, è consigliabile assicurarsi che l'array `$items` contenga solo gli elementi che devono essere ridisegnati. -Snippet in un template incluso .[#toc-snippets-in-an-included-template] -======================================================================= - -Può accadere che lo snippet si trovi in un template che viene incluso da un template diverso. In questo caso, occorre avvolgere il codice di inclusione nel secondo template con la tag `snippetArea`, quindi ridisegnare sia la snippetArea che lo snippet vero e proprio. - -La tag `snippetArea` assicura che il codice all'interno venga eseguito, ma che al browser venga inviato solo lo snippet effettivo nel modello incluso. +Se inseriamo un altro template nel template utilizzando il tag `{include}`, che contiene snippet, è necessario includere nuovamente l'inserimento del template in `snippetArea` e invalidarlo insieme allo snippet: ```latte -{* parent.latte *} -{snippetArea wrapper} - {include 'child.latte'} +{snippetArea include} + {include 'included.latte'} {/snippetArea} ``` + ```latte -{* child.latte *} +{* included.latte *} {snippet item} -... + ... {/snippet} ``` + ```php -$this->redrawControl('wrapper'); +$this->redrawControl('include'); $this->redrawControl('item'); ``` -Si può anche combinare con gli snippet dinamici. - -Aggiunta e cancellazione .[#toc-adding-and-deleting] -==================================================== +Snippet nei componenti +---------------------- -Se si aggiunge un nuovo elemento all'elenco e si invalida `itemsContainer`, la richiesta AJAX restituisce gli snippet che includono il nuovo elemento, ma il gestore javascript non sarà in grado di renderlo. Questo perché non esiste un elemento HTML con l'ID appena creato. - -In questo caso, il modo più semplice è avvolgere l'intero elenco in un altro frammento e invalidare il tutto: +È possibile creare snippet anche nei [componenti |components] e Nette li ridisegnerà automaticamente. Ma c'è una limitazione: per ridisegnare gli snippet, chiama il metodo `render()` senza parametri. Quindi, il passaggio di parametri nel template non funzionerà: ```latte -{snippet wholeList} -
      - {foreach $list as $id => $item} -
    • {$item} update
    • - {/foreach} -
    -{/snippet} -Add +OK +{control productGrid} + +non funzionerà: +{control productGrid $arg, $arg} +{control productGrid:paginator} ``` + +Invio di dati utente +-------------------- + +Insieme agli snippet, è possibile inviare al client qualsiasi altro dato. È sufficiente scriverli nell'oggetto `payload`: + ```php -public function handleAdd(): void +public function actionDelete(int $id): void { - $this->template->list = $this->getTheWholeList(); - $this->template->list[] = 'New one'; - $this->redrawControl('wholeList'); + // ... + if ($this->isAjax()) { + $this->payload->message = 'Success'; + } } ``` -Lo stesso vale per la cancellazione di un elemento. Sarebbe possibile inviare uno snippet vuoto, ma di solito gli elenchi possono essere paginati e sarebbe complicato implementare la cancellazione di un elemento e il caricamento di un altro (che si trovava in una pagina diversa dell'elenco paginato). - -Invio di parametri al componente .[#toc-sending-parameters-to-component] -======================================================================== +Passaggio di parametri +====================== -Quando si inviano parametri al componente tramite una richiesta AJAX, sia che si tratti di parametri di segnale che di parametri persistenti, occorre fornire il loro nome globale, che contiene anche il nome del componente. Il nome completo del parametro restituisce il metodo `getParameterId()`. +Se inviamo parametri a un componente tramite una richiesta AJAX, siano essi parametri di segnale o parametri persistenti, dobbiamo specificare il loro nome globale nella richiesta, che include anche il nome del componente. Il nome completo del parametro viene restituito dal metodo `getParameterId()`. ```js -$.getJSON( - {link changeCountBasket!}, - { - {$control->getParameterId('id')}: id, - {$control->getParameterId('count')}: count - } -}); +let url = new URL({link //foo!}); +url.searchParams.set({$control->getParameterId('bar')}, bar); + +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) ``` -E gestire il metodo con i parametri corrispondenti nel componente. +E il metodo handle con i parametri corrispondenti nel componente: ```php -public function handleChangeCountBasket(int $id, int $count): void +public function handleFoo(int $bar): void { - } ``` diff --git a/application/it/bootstrap.texy b/application/it/bootstrap.texy deleted file mode 100644 index c2032bcbb8..0000000000 --- a/application/it/bootstrap.texy +++ /dev/null @@ -1,233 +0,0 @@ -Bootstrap -********* - -
    - -Bootstrap è il codice di avvio che inizializza l'ambiente, crea un contenitore di dependency injection (DI) e avvia l'applicazione. Discuteremo di: - -- come configurare l'applicazione usando i file NEON -- come gestire le modalità di produzione e di sviluppo -- come creare il contenitore DI - -
    - - -Le applicazioni, siano esse basate sul web o su script a riga di comando, iniziano con una qualche forma di inizializzazione dell'ambiente. In tempi antichi, poteva essere un file chiamato `include.inc.php` a occuparsene, incluso nel file iniziale. -Nelle moderne applicazioni Nette, è stato sostituito dalla classe `Bootstrap`, che come parte dell'applicazione si trova nel file `app/Bootstrap.php`. Ad esempio, potrebbe avere questo aspetto: - -```php -use Nette\Bootstrap\Configurator; - -class Bootstrap -{ - public static function boot(): Configurator - { - $appDir = dirname(__DIR__); - $configurator = new Configurator; - //$configurator->setDebugMode('secret@23.75.345.200'); - $configurator->enableTracy($appDir . '/log'); - $configurator->setTempDirectory($appDir . '/temp'); - $configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); - $configurator->addConfig($appDir . '/config/common.neon'); - return $configurator; - } -} -``` - - -index.php .[#toc-index-php] -=========================== - -Nel caso delle applicazioni web, il file iniziale è `index.php`, che si trova nella cartella pubblica `www/`. Permette alla classe `Bootstrap` di inizializzare l'ambiente e restituisce la classe `$configurator` che crea il contenitore DI. Quindi ottiene il servizio `Application`, che esegue l'applicazione web: - -```php -// inizializzare l'ambiente + ottenere l'oggetto Configuratore -$configurator = App\Bootstrap::boot(); -// creare un contenitore DI -$container = $configurator->createContainer(); -// Il contenitore DI crea un oggetto Nette\Application\Application -$application = $container->getByType(Nette\Application\Application::class); -// avvia l'applicazione Nette -$application->run(); -``` - -Come si può notare, la classe [api:Nette\Bootstrap\Configurator], che ora presenteremo in modo più dettagliato, aiuta a impostare l'ambiente e a creare un contenitore di dependency injection (DI). - - -Modalità di sviluppo e modalità di produzione .[#toc-development-vs-production-mode] -==================================================================================== - -Nette distingue due modalità di base per l'esecuzione di una richiesta: sviluppo e produzione. La modalità di sviluppo è incentrata sul massimo comfort del programmatore, Tracy viene visualizzato, la cache viene aggiornata automaticamente quando si modificano i template o la configurazione del contenitore DI, ecc. La modalità di produzione è incentrata sulle prestazioni, Tracy registra solo gli errori e le modifiche dei modelli e di altri file non vengono controllate. - -La selezione della modalità avviene tramite il rilevamento automatico, quindi di solito non è necessario configurare o cambiare qualcosa manualmente. La modalità è di sviluppo se l'applicazione è in esecuzione su localhost (cioè l'indirizzo IP `127.0.0.1` o `::1`) e non è presente alcun proxy (cioè la sua intestazione HTTP). Altrimenti, viene eseguita in modalità di produzione. - -Se si vuole abilitare la modalità di sviluppo in altri casi, ad esempio per i programmatori che accedono da un indirizzo IP specifico, si può usare `setDebugMode()`: - -```php -$configurator->setDebugMode('23.75.345.200'); // uno o più indirizzi IP -``` - -Consigliamo assolutamente di combinare un indirizzo IP con un cookie. Nel cookie `nette-debug` verrà memorizzato un token segreto, ad esempio `secret1234`, e la modalità di sviluppo verrà attivata per i programmatori con questa combinazione di IP e cookie. - -```php -$configurator->setDebugMode('secret1234@23.75.345.200'); -``` - -Possiamo anche disattivare completamente la modalità sviluppatore, anche per localhost: - -```php -$configurator->setDebugMode(false); -``` - -Si noti che il valore `true` attiva la modalità sviluppatore, cosa che non dovrebbe mai accadere su un server di produzione. - - -Strumento di debug Tracy .[#toc-debugging-tool-tracy] -===================================================== - -Per facilitare il debug, attiviamo l'ottimo strumento [Tracy |tracy:]. In modalità sviluppatore visualizza gli errori e in modalità produzione li registra nella directory specificata: - -```php -$configurator->enableTracy($appDir . '/log'); -``` - - -File temporanei .[#toc-temporary-files] -======================================= - -Nette utilizza la cache per il contenitore DI, il RobotLoader, i modelli, ecc. Per questo motivo è necessario impostare il percorso della cartella in cui verrà memorizzata la cache: - -```php -$configurator->setTempDirectory($appDir . '/temp'); -``` - -Su Linux o macOS, impostare i [permessi di scrittura |nette:troubleshooting#Setting directory permissions] per le directory `log/` e `temp/`. - - -RobotLoader .[#toc-robotloader] -=============================== - -Di solito, vogliamo caricare automaticamente le classi usando [RobotLoader |robot-loader:], quindi dobbiamo avviarlo e fargli caricare le classi dalla directory in cui si trova `Bootstrap.php` (cioè `__DIR__`) e da tutte le sue sottodirectory: - -```php -$configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); -``` - -Un modo alternativo è quello di utilizzare solo il caricamento automatico di [Composer |best-practices:composer] PSR-4. - - -Fuso orario .[#toc-timezone] -============================ - -Il configuratore consente di specificare un fuso orario per l'applicazione. - -```php -$configurator->setTimeZone('Europe/Prague'); -``` - - -Configurazione del contenitore DI .[#toc-di-container-configuration] -==================================================================== - -Parte del processo di avvio è la creazione di un contenitore DI, cioè una fabbrica di oggetti, che è il cuore dell'intera applicazione. Si tratta in realtà di una classe PHP generata da Nette e memorizzata in una cartella cache. Il factory produce gli oggetti chiave dell'applicazione e i file di configurazione gli indicano come crearli e configurarli, influenzando così il comportamento dell'intera applicazione. - -I file di configurazione sono solitamente scritti nel [formato NEON |neon:format]. [Qui |nette:configuring] si può leggere [cosa si può configurare |nette:configuring]. - -.[tip] -In modalità di sviluppo, il contenitore viene aggiornato automaticamente ogni volta che si modifica il codice o i file di configurazione. In modalità di produzione, viene generato solo una volta e le modifiche ai file non vengono controllate per massimizzare le prestazioni. - -I file di configurazione vengono caricati usando `addConfig()`: - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -``` - -Il metodo `addConfig()` può essere richiamato più volte per aggiungere più file. - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -$configurator->addConfig($appDir . '/config/local.neon'); -if (PHP_SAPI === 'cli') { - $configurator->addConfig($appDir . '/config/cli.php'); -} -``` - -Il nome `cli.php` non è un refuso, la configurazione può anche essere scritta in un file PHP, che la restituisce come array. - -In alternativa, si può usare la [sezione`includes` |dependency-injection:configuration#including files] per caricare altri file di configurazione. - -Se all'interno dei file di configurazione compaiono elementi con le stesse chiavi, questi verranno [sovrascritti o uniti |dependency-injection:configuration#Merging] nel caso di array. Il file incluso successivamente ha una priorità maggiore rispetto al precedente. Il file in cui è elencata la sezione `includes` ha una priorità più alta dei file in esso inclusi. - - -Parametri statici .[#toc-static-parameters] -------------------------------------------- - -I parametri usati nei file di configurazione possono essere definiti [nella sezione `parameters` |dependency-injection:configuration#parameters] e anche passati (o sovrascritti) dal metodo `addStaticParameters()` (ha l'alias `addParameters()`). È importante che valori diversi dei parametri causino la generazione di contenitori DI aggiuntivi, cioè di classi aggiuntive. - -```php -$configurator->addStaticParameters([ - 'projectId' => 23, -]); -``` - -Nei file di configurazione, si può scrivere la solita notazione `%projectId%` per accedere al parametro chiamato `projectId`. Per impostazione predefinita, il Configuratore popola i seguenti parametri: `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` e `consoleMode`. - - -Parametri dinamici .[#toc-dynamic-parameters] ---------------------------------------------- - -Possiamo anche aggiungere parametri dinamici al contenitore; i loro diversi valori, a differenza dei parametri statici, non causeranno la generazione di nuovi contenitori DI. - -```php -$configurator->addDynamicParameters([ - 'remoteIp' => $_SERVER['REMOTE_ADDR'], -]); -``` - -Le variabili d'ambiente possono essere facilmente rese disponibili usando parametri dinamici. Possiamo accedervi tramite `%env.variable%` nei file di configurazione. - -```php -$configurator->addDynamicParameters([ - 'env' => getenv(), -]); -``` - - -Servizi importati .[#toc-imported-services] -------------------------------------------- - -Ora stiamo andando più a fondo. Sebbene lo scopo di un contenitore DI sia quello di creare oggetti, eccezionalmente può essere necessario inserire un oggetto esistente nel contenitore. Lo facciamo definendo il servizio con l'attributo `imported: true`. - -```neon -services: - myservice: - type: App\Model\MyCustomService - imported: true -``` - -Creare una nuova istanza e inserirla in bootstrap: - -```php -$configurator->addServices([ - 'myservice' => new App\Model\MyCustomService('foobar'), -]); -``` - - -Ambienti diversi .[#toc-different-environments] -=============================================== - -Sentitevi liberi di personalizzare la classe `Bootstrap` in base alle vostre esigenze. Si possono aggiungere parametri al metodo `boot()` per differenziare i progetti web, oppure aggiungere altri metodi, come `bootForTests()`, che inizializza l'ambiente per i test unitari, `bootForCli()` per gli script chiamati dalla riga di comando e così via. - -```php -public static function bootForTests(): Configurator -{ - $configurator = self::boot(); - Tester\Environment::setup(); // Nette Tester initialization - return $configurator; -} -``` diff --git a/application/it/bootstrapping.texy b/application/it/bootstrapping.texy new file mode 100644 index 0000000000..63e749dfe6 --- /dev/null +++ b/application/it/bootstrapping.texy @@ -0,0 +1,297 @@ +Bootstrapping +************* + +
    + +Il bootstrapping è il processo di inizializzazione dell'ambiente dell'applicazione, creazione di un contenitore di dependency injection (DI) e avvio dell'applicazione. Discuteremo: + +- come la classe Bootstrap inizializza l'ambiente +- come le applicazioni sono configurate utilizzando file NEON +- come distinguere tra modalità di produzione e sviluppo +- come creare e configurare il contenitore DI + +
    + + +Le applicazioni, siano esse web o script eseguiti dalla riga di comando, iniziano la loro esecuzione con una qualche forma di inizializzazione dell'ambiente. In passato, questo compito era spesso affidato a un file chiamato, ad esempio, `include.inc.php`, che il file iniziale includeva. Nelle moderne applicazioni Nette, questo è stato sostituito dalla classe `Bootstrap`, che, come parte dell'applicazione, si trova nel file `app/Bootstrap.php`. Potrebbe assomigliare, ad esempio, a questo: + +```php +use Nette\Bootstrap\Configurator; + +class Bootstrap +{ + private Configurator $configurator; + private string $rootDir; + + public function __construct() + { + $this->rootDir = dirname(__DIR__); + // Il Configurator è responsabile dell'impostazione dell'ambiente e dei servizi dell'applicazione. + $this->configurator = new Configurator; + // Imposta la directory per i file temporanei generati da Nette (es. template compilati) + $this->configurator->setTempDirectory($this->rootDir . '/temp'); + } + + public function bootWebApplication(): Nette\DI\Container + { + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); + } + + private function initializeEnvironment(): void + { + // Nette è intelligente e la modalità di sviluppo si attiva automaticamente, + // oppure puoi abilitarla per un indirizzo IP specifico decommentando la riga seguente: + // $this->configurator->setDebugMode('secret@23.75.345.200'); + + // Attiva Tracy: l'ultimo "coltellino svizzero" per il debug. + $this->configurator->enableTracy($this->rootDir . '/log'); + + // RobotLoader: carica automaticamente tutte le classi nella directory selezionata + $this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); + } + + private function setupContainer(): void + { + // Carica i file di configurazione + $this->configurator->addConfig($this->rootDir . '/config/common.neon'); + } +} +``` + + +index.php +========= + +Il file iniziale nel caso delle applicazioni web è `index.php`, che si trova nella [directory pubblica |directory-structure#Directory pubblica www] `www/`. Questo file fa inizializzare l'ambiente e creare il container DI dalla classe Bootstrap. Successivamente, ottiene il servizio `Application` da esso, che avvia l'applicazione web: + +```php +$bootstrap = new App\Bootstrap; +// Inizializzazione dell'ambiente + creazione del container DI +$container = $bootstrap->bootWebApplication(); +// Il container DI crea l'oggetto Nette\Application\Application +$application = $container->getByType(Nette\Application\Application::class); +// Avvio dell'applicazione Nette ed elaborazione della richiesta in arrivo +$application->run(); +``` + +Come si può vedere, la classe [api:Nette\Bootstrap\Configurator] aiuta con l'impostazione dell'ambiente e la creazione del container di dependency injection (DI), che ora presenteremo più in dettaglio. + + +Modalità Sviluppo vs Produzione +=============================== + +Nette si comporta diversamente a seconda che sia in esecuzione su un server di sviluppo o di produzione: + +🛠️ Modalità Sviluppo (Development): + - Mostra la debugbar di Tracy con informazioni utili (query SQL, tempo di esecuzione, memoria utilizzata) + - In caso di errore, mostra una pagina di errore dettagliata con le chiamate alle funzioni e il contenuto delle variabili + - Aggiorna automaticamente la cache quando vengono modificati i template Latte, i file di configurazione, ecc. + + +🚀 Modalità Produzione (Production): + - Non mostra alcuna informazione di debug, tutti gli errori vengono scritti nel log + - In caso di errore, mostra ErrorPresenter o una pagina generica "Server Error" + - La cache non viene mai aggiornata automaticamente! + - Ottimizzato per velocità e sicurezza + + +La selezione della modalità avviene tramite autodetect, quindi di solito non è necessario configurare nulla o passare manualmente: + +- modalità sviluppo: su localhost (indirizzo IP `127.0.0.1` o `::1`) se non è presente un proxy (cioè il suo header HTTP) +- modalità produzione: ovunque altrove + +Se vogliamo abilitare la modalità di sviluppo anche in altri casi, ad esempio per i programmatori che accedono da un indirizzo IP specifico, utilizziamo `setDebugMode()`: + +```php +$this->configurator->setDebugMode('23.75.345.200'); // è possibile specificare anche un array di indirizzi IP +``` + +Consigliamo vivamente di combinare l'indirizzo IP con un cookie. Memorizziamo un token segreto nel cookie `nette-debug`, ad esempio `secret1234`, e in questo modo attiviamo la modalità di sviluppo per i programmatori che accedono da un indirizzo IP specifico e che hanno anche il token menzionato nel cookie: + +```php +$this->configurator->setDebugMode('secret1234@23.75.345.200'); +``` + +Possiamo anche disattivare completamente la modalità di sviluppo, anche per localhost: + +```php +$this->configurator->setDebugMode(false); +``` + +Attenzione, il valore `true` attiva forzatamente la modalità di sviluppo, cosa che non deve mai accadere su un server di produzione. + + +Strumento di Debug Tracy +======================== + +Per un facile debug, attiviamo anche l'ottimo strumento [Tracy |tracy:]. In modalità sviluppo, visualizza gli errori e in modalità produzione, registra gli errori nella directory specificata: + +```php +$this->configurator->enableTracy($this->rootDir . '/log'); +``` + + +File Temporanei +=============== + +Nette utilizza la cache per il container DI, RobotLoader, template, ecc. Pertanto, è necessario impostare il percorso della directory in cui verrà memorizzata la cache: + +```php +$this->configurator->setTempDirectory($this->rootDir . '/temp'); +``` + +Su Linux o macOS, imposta i [permessi di scrittura |nette:troubleshooting#Impostazione dei permessi delle directory] per le directory `log/` e `temp/`. + + +RobotLoader +=========== + +Di solito, vorremo caricare automaticamente le classi utilizzando [RobotLoader |robot-loader:], quindi dobbiamo avviarlo e fargli caricare le classi dalla directory in cui si trova `Bootstrap.php` (cioè `__DIR__`), e da tutte le sottodirectory: + +```php +$this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); +``` + +Un approccio alternativo è far caricare le classi solo tramite [Composer |best-practices:composer] rispettando PSR-4. + + +Timezone +======== + +Tramite il configuratore è possibile impostare il fuso orario predefinito. + +```php +$this->configurator->setTimeZone('Europe/Prague'); +``` + + +Configurazione del Container DI +=============================== + +Parte del processo di avvio è la creazione del container DI, ovvero la factory di oggetti, che è il cuore dell'intera applicazione. Si tratta in realtà di una classe PHP generata da Nette e salvata nella directory della cache. La factory produce gli oggetti chiave dell'applicazione e, tramite i file di configurazione, le istruiamo su come crearli e impostarli, influenzando così il comportamento dell'intera applicazione. + +I file di configurazione sono solitamente scritti nel formato [NEON |neon:format]. In un capitolo separato, imparerai [cosa può essere configurato |nette:configuring]. + +.[tip] +In modalità sviluppo, il container viene aggiornato automaticamente ad ogni modifica del codice o dei file di configurazione. In modalità produzione, viene generato solo una volta e le modifiche non vengono controllate per massimizzare le prestazioni. + +Carichiamo i file di configurazione utilizzando `addConfig()`: + +```php +$this->configurator->addConfig($this->rootDir . '/config/common.neon'); +``` + +Se vogliamo aggiungere più file di configurazione, possiamo chiamare la funzione `addConfig()` più volte. + +```php +$configDir = $this->rootDir . '/config'; +$this->configurator->addConfig($configDir . '/common.neon'); +$this->configurator->addConfig($configDir . '/services.neon'); +if (PHP_SAPI === 'cli') { + $this->configurator->addConfig($configDir . '/cli.php'); +} +``` + +Il nome `cli.php` non è un errore di battitura, la configurazione può anche essere scritta in un file PHP che la restituisce come array. + +Possiamo anche aggiungere altri file di configurazione nella [sezione `includes` |dependency-injection:configuration#Inclusione di file]. + +Se nei file di configurazione compaiono elementi con le stesse chiavi, verranno sovrascritti o, nel caso di [array, uniti |dependency-injection:configuration#Unione]. Il file incluso successivamente ha una priorità maggiore rispetto al precedente. Il file in cui è specificata la sezione `includes` ha una priorità maggiore rispetto ai file inclusi in esso. + + +Parametri Statici +----------------- + +I parametri utilizzati nei file di configurazione possono essere definiti [nella sezione `parameters` |dependency-injection:configuration#Parametri] e anche passati (o sovrascritti) con il metodo `addStaticParameters()` (ha l'alias `addParameters()`). È importante notare che valori diversi dei parametri causeranno la generazione di ulteriori container DI, ovvero ulteriori classi. + +```php +$this->configurator->addStaticParameters([ + 'projectId' => 23, +]); +``` + +Al parametro `projectId` si può fare riferimento nella configurazione con la consueta notazione `%projectId%`. + + +Parametri Dinamici +------------------ + +Possiamo aggiungere al container anche parametri dinamici, i cui valori diversi, a differenza dei parametri statici, non causano la generazione di nuovi container DI. + +```php +$this->configurator->addDynamicParameters([ + 'remoteIp' => $_SERVER['REMOTE_ADDR'], +]); +``` + +In questo modo possiamo aggiungere semplicemente, ad esempio, variabili d'ambiente, a cui si può fare riferimento nella configurazione con la notazione `%env.variable%`. + +```php +$this->configurator->addDynamicParameters([ + 'env' => getenv(), +]); +``` + + +Parametri Predefiniti +--------------------- + +Nei file di configurazione è possibile utilizzare questi parametri statici: + +- `%appDir%` è il percorso assoluto alla directory contenente il file `Bootstrap.php` +- `%wwwDir%` è il percorso assoluto alla directory contenente il file di input `index.php` +- `%tempDir%` è il percorso assoluto alla directory per i file temporanei +- `%vendorDir%` è il percorso assoluto alla directory in cui Composer installa le librerie +- `%rootDir%` è il percorso assoluto alla directory principale del progetto +- `%debugMode%` indica se l'applicazione è in modalità debug +- `%consoleMode%` indica se la richiesta è arrivata tramite la riga di comando + + +Servizi Importati +----------------- + +Ora andiamo più a fondo. Sebbene lo scopo del container DI sia quello di creare oggetti, eccezionalmente potrebbe sorgere la necessità di inserire un oggetto esistente nel container. Lo facciamo definendo il servizio con il flag `imported: true`. + +```neon +services: + myservice: + type: App\Model\MyCustomService + imported: true +``` + +E nel bootstrap inseriamo l'oggetto nel container: + +```php +$this->configurator->addServices([ + 'myservice' => new App\Model\MyCustomService('foobar'), +]); +``` + + +Ambienti Diversi +================ + +Non aver paura di modificare la classe Bootstrap secondo le tue esigenze. Puoi aggiungere parametri al metodo `bootWebApplication()` per distinguere i progetti web. Oppure possiamo aggiungere altri metodi, ad esempio `bootTestEnvironment()`, che inizializza l'ambiente per i test unitari, `bootConsoleApplication()` per gli script chiamati dalla riga di comando, ecc. + +```php +public function bootTestEnvironment(): Nette\DI\Container +{ + Tester\Environment::setup(); // inizializzazione di Nette Tester + $this->setupContainer(); + return $this->configurator->createContainer(); +} + +public function bootConsoleApplication(): Nette\DI\Container +{ + $this->configurator->setDebugMode(false); + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); +} +``` diff --git a/application/it/components.texy b/application/it/components.texy index 38f8c6513d..2d21975f48 100644 --- a/application/it/components.texy +++ b/application/it/components.texy @@ -1,9 +1,9 @@ -Componenti interattivi +Componenti Interattivi **********************
    -I componenti sono oggetti separati e riutilizzabili che vengono inseriti nelle pagine. Possono essere moduli, griglie di dati, sondaggi, in realtà qualsiasi cosa abbia senso usare ripetutamente. Mostreremo: +I componenti sono oggetti riutilizzabili indipendenti che inseriamo nelle pagine. Possono essere form, datagrid, sondaggi, praticamente qualsiasi cosa che abbia senso usare ripetutamente. Vedremo: - come usare i componenti? - come scriverli? @@ -11,19 +11,19 @@ I componenti sono oggetti separati e riutilizzabili che vengono inseriti nelle p
    -Nette ha un sistema di componenti integrato. I più anziani potrebbero ricordare qualcosa di simile da Delphi o ASP.NET Web Forms. React o Vue.js sono costruiti su qualcosa di vagamente simile. Tuttavia, nel mondo dei framework PHP, questa è una caratteristica del tutto unica. +Nette ha un sistema di componenti integrato. Qualcosa di simile potrebbe essere familiare ai veterani di Delphi o ASP.NET Web Forms, qualcosa di lontanamente simile è alla base di React o Vue.js. Tuttavia, nel mondo dei framework PHP, è una caratteristica unica. -Allo stesso tempo, i componenti cambiano radicalmente l'approccio allo sviluppo delle applicazioni. È possibile comporre pagine a partire da unità pre-preparate. Avete bisogno di una griglia di dati nell'amministrazione? Potete trovarla su [Componette |https://componette.org/search/component], un repository di componenti aggiuntivi open-source (non solo componenti) per Nette, e incollarla semplicemente nel presenter. +Eppure, i componenti influenzano fondamentalmente l'approccio alla creazione di applicazioni. Puoi comporre le pagine da unità pre-preparate. Hai bisogno di un datagrid nell'amministrazione? Lo trovi su [Componette |https://componette.org/search/component], un repository di add-on open-source (quindi non solo componenti) per Nette, e lo inserisci semplicemente nel presenter. -È possibile incorporare nel presenter un numero qualsiasi di componenti. E si possono inserire altri componenti in alcuni componenti. In questo modo si crea un albero di componenti con un presentatore come radice. +Puoi incorporare un numero qualsiasi di componenti in un presenter. E in alcuni componenti puoi inserire altri componenti. Questo crea un albero di componenti, la cui radice è il presenter. -Metodi di fabbrica .[#toc-factory-methods] -========================================== +Metodi Factory +============== -Come vengono posizionati e successivamente utilizzati i componenti nel presentatore? Di solito utilizzando i metodi di fabbrica. +Come vengono inseriti i componenti nel presenter e successivamente utilizzati? Di solito tramite metodi factory. -Il factory dei componenti è un modo elegante per creare i componenti solo quando sono realmente necessari (lazy / on-demand). L'intera magia sta nell'implementazione di un metodo chiamato `createComponent()`dove `` è il nome del componente, che verrà creato e restituito. +Una factory di componenti è un modo elegante per creare componenti solo quando sono effettivamente necessari (lazy / on demand). L'intera magia sta nell'implementare un metodo chiamato `createComponent()`, dove `` è il nome del componente da creare, che crea e restituisce il componente. ```php .{file:DefaultPresenter.php} class DefaultPresenter extends Nette\Application\UI\Presenter @@ -37,43 +37,43 @@ class DefaultPresenter extends Nette\Application\UI\Presenter } ``` -Poiché tutti i componenti sono creati in metodi separati, il codice è più pulito e facile da leggere. +Grazie al fatto che tutti i componenti vengono creati in metodi separati, il codice guadagna in chiarezza. .[note] -I nomi dei componenti iniziano sempre con una lettera minuscola, anche se sono maiuscoli nel nome del metodo. +I nomi dei componenti iniziano sempre con una lettera minuscola, anche se sono scritti con una lettera maiuscola nel nome del metodo. -Le fabbriche non vengono mai chiamate direttamente, ma vengono richiamate automaticamente quando si utilizzano i componenti per la prima volta. Grazie a ciò, un componente viene creato al momento giusto e solo se è veramente necessario. Se non usassimo il componente (per esempio su qualche richiesta AJAX, in cui restituiamo solo una parte della pagina, o quando alcune parti vengono memorizzate nella cache), non verrebbe nemmeno creato e risparmieremmo le prestazioni del server. +Le factory non vengono mai chiamate direttamente; vengono chiamate automaticamente la prima volta che utilizziamo il componente. Grazie a ciò, il componente viene creato al momento giusto e solo quando è effettivamente necessario. Se non utilizziamo il componente (ad esempio, durante una richiesta AJAX in cui viene trasferita solo una parte della pagina, o durante la cache del template), non viene creato affatto e risparmiamo le prestazioni del server. ```php .{file:DefaultPresenter.php} -// si accede al componente e se è la prima volta, -// si chiama createComponentPoll() per crearlo +// accediamo al componente e se è la prima volta, +// viene chiamato createComponentPoll() che lo crea $poll = $this->getComponent('poll'); // sintassi alternativa: $poll = $this['poll']; ``` -Nel template, è possibile rendere un componente usando il tag [{control} |#Rendering]. Non è quindi necessario passare manualmente i componenti al template. +Nel template, è possibile renderizzare un componente utilizzando il tag [{control} |#Rendering]. Pertanto, non è necessario passare manualmente i componenti al template. ```latte -

    Please Vote

    +

    Vota

    {control poll} ``` -Stile Hollywood .[#toc-hollywood-style] -======================================= +Stile Hollywood +=============== -I componenti utilizzano comunemente una tecnica interessante, che ci piace chiamare stile hollywoodiano. Sicuramente conoscete il cliché che gli attori sentono spesso ai casting call: "Non chiamateci, vi chiameremo noi". Ed è proprio di questo che si tratta. +I componenti utilizzano comunemente una tecnica fresca che ci piace chiamare Stile Hollywood. Sicuramente conosci la frase famosa che i partecipanti ai casting cinematografici sentono così spesso: "Non chiamateci, vi chiameremo noi". Ed è proprio di questo che si tratta. -In Nette, invece di dover fare continuamente domande ("il modulo è stato inviato?", "era valido?" o "qualcuno ha premuto questo pulsante?"), si dice al framework "quando succede questo, chiama questo metodo" e si lascia che il lavoro prosegua. Chi programma in JavaScript ha familiarità con questo stile di programmazione. Si scrivono funzioni che vengono chiamate quando si verifica un determinato evento. Il motore passa loro i parametri appropriati. +In Nette, invece di dover chiedere costantemente qualcosa ("il form è stato inviato?", "era valido?" o "l'utente ha premuto questo pulsante?"), dici al framework "quando succede, chiama questo metodo" e lasci il resto del lavoro a lui. Se programmi in JavaScript, conosci intimamente questo stile di programmazione. Scrivi funzioni che vengono chiamate quando si verifica un certo evento. E il linguaggio passa loro i parametri appropriati. -Questo cambia completamente il modo di scrivere le applicazioni. Più compiti si possono delegare al framework, meno lavoro si ha. E meno ci si può dimenticare. +Questo cambia completamente la prospettiva sulla scrittura delle applicazioni. Più compiti puoi lasciare al framework, meno lavoro hai tu. E meno cose puoi dimenticare. -Come scrivere un componente .[#toc-how-to-write-a-component] -============================================================ +Scrivere un Componente +====================== -Per componente si intendono solitamente i discendenti della classe [api:Nette\Application\UI\Control]. Anche il presentatore [api:Nette\Application\UI\Presenter] è un discendente della classe `Control`. +Con il termine componente, di solito intendiamo un discendente della classe [api:Nette\Application\UI\Control]. (Sarebbe quindi più preciso usare il termine "controlli", ma "controlli" ha un significato completamente diverso in italiano e "componenti" si è affermato di più.) Il presenter stesso [api:Nette\Application\UI\Presenter] è, tra l'altro, anche un discendente della classe `Control`. ```php .{file:PollControl.php} use Nette\Application\UI\Control; @@ -84,22 +84,22 @@ class PollControl extends Control ``` -Rendering .[#toc-rendering] -=========================== +Rendering +========= -Sappiamo già che il tag `{control componentName}` viene utilizzato per disegnare un componente. In realtà richiama il metodo `render()` del componente, in cui ci occupiamo del rendering. Abbiamo, proprio come nel presentatore, un modello [Latte |latte:] nella variabile `$this->template`, a cui passiamo i parametri. A differenza dell'uso nel presentatore, dobbiamo specificare un file di modello e lasciarlo renderizzare: +Sappiamo già che per renderizzare un componente si usa il tag `{control componentName}`. Questo in realtà chiama il metodo `render()` del componente, in cui ci occupiamo del rendering. Abbiamo a disposizione, proprio come nel presenter, un [template Latte|templates] nella variabile `$this->template`, a cui passiamo i parametri. A differenza del presenter, dobbiamo specificare il file del template e farlo renderizzare: ```php .{file:PollControl.php} public function render(): void { // inseriamo alcuni parametri nel template $this->template->param = $value; - // e lo disegneremo + // e lo renderizziamo $this->template->render(__DIR__ . '/poll.latte'); } ``` -Il tag `{control}` permette di passare i parametri al metodo `render()`: +Il tag `{control}` consente di passare parametri al metodo `render()`: ```latte {control poll $id, $message} @@ -112,7 +112,7 @@ public function render(int $id, string $message): void } ``` -A volte un componente può essere costituito da diverse parti che vogliamo rendere separatamente. Per ognuna di esse creeremo un proprio metodo di rendering, ad esempio `renderPaginator()`: +A volte un componente può essere composto da più parti che vogliamo renderizzare separatamente. Per ognuna di esse, creiamo il nostro metodo di rendering, qui nell'esempio `renderPaginator()`: ```php .{file:PollControl.php} public function renderPaginator(): void @@ -121,69 +121,69 @@ public function renderPaginator(): void } ``` -E nel template lo chiamiamo usando: +E nel template, lo chiamiamo poi usando: ```latte {control poll:paginator} ``` -Per una migliore comprensione, è bene sapere come il tag viene compilato in codice PHP. +Per una migliore comprensione, è utile sapere come questo tag viene tradotto in PHP. ```latte {control poll} {control poll:paginator 123, 'hello'} ``` -Questo si compila in: +viene tradotto come: ```php $control->getComponent('poll')->render(); $control->getComponent('poll')->renderPaginator(123, 'hello'); ``` -`getComponent()` restituisce il componente `poll` e su di esso viene richiamato il metodo `render()` o `renderPaginator()`, rispettivamente. +Il metodo `getComponent()` restituisce il componente `poll` e su questo componente chiama il metodo `render()`, rispettivamente `renderPaginator()` se nel tag dopo i due punti è specificato un modo di rendering diverso. .[caution] -Se in un punto qualsiasi della parte dei parametri viene usato **`=>`**, tutti i parametri saranno avvolti da un array e passati come primo argomento: +Attenzione, se da qualche parte nei parametri compare **`=>`**, tutti i parametri verranno impacchettati in un array e passati come primo argomento: ```latte {control poll, id: 123, message: 'hello'} ``` -si compila in: +viene tradotto come: ```php $control->getComponent('poll')->render(['id' => 123, 'message' => 'hello']); ``` -Rendering del sottocomponente: +Rendering di un sub-componente: ```latte {control cartControl-someForm} ``` -si compila in: +viene tradotto come: ```php $control->getComponent("cartControl-someForm")->render(); ``` -I componenti, come i presentatori, passano automaticamente diverse variabili utili ai modelli: +I componenti, come i presenter, passano automaticamente diverse variabili utili ai template: -- `$basePath` è un percorso URL assoluto alla cartella principale (per esempio `/CD-collection`) -- `$baseUrl` è un URL assoluto alla cartella principale (per esempio `http://localhost/CD-collection`) -- `$user` è un oggetto [che rappresenta l'utente |security:authentication] -- `$presenter` è il presentatore corrente +- `$basePath` è il percorso URL assoluto alla directory principale (es. `/eshop`) +- `$baseUrl` è l'URL assoluto alla directory principale (es. `http://localhost/eshop`) +- `$user` è l'oggetto [che rappresenta l'utente |security:authentication] +- `$presenter` è il presenter corrente - `$control` è il componente corrente -- `$flashes` è l'elenco dei [messaggi |#flash-messages] inviati dal metodo `flashMessage()` +- `$flashes` è l'array di [messaggi |#Messaggi flash] inviati dalla funzione `flashMessage()` -Segnale .[#toc-signal] -====================== +Segnale +======= -Sappiamo già che la navigazione nell'applicazione Nette consiste nel collegamento o nel reindirizzamento a coppie `Presenter:action`. Ma cosa succede se vogliamo semplicemente eseguire un'azione sulla **pagina corrente**? Per esempio, cambiare l'ordine delle colonne nella tabella; cancellare un elemento; cambiare la modalità luce/buio; inviare il modulo; votare nel sondaggio; ecc. +Sappiamo già che la navigazione in un'applicazione Nette consiste nel collegare o reindirizzare a coppie `Presenter:action`. Ma cosa succede se vogliamo solo eseguire un'azione sulla **pagina corrente**? Ad esempio, cambiare l'ordinamento delle colonne in una tabella; eliminare un elemento; passare alla modalità chiaro/scuro; inviare un form; votare in un sondaggio; ecc. -Questo tipo di richiesta si chiama segnale. E come le azioni invocano metodi `action()` o `render()`i segnali chiamano i metodi `handle()`. Mentre il concetto di azione (o vista) si riferisce solo ai presentatori, i segnali si applicano a tutti i componenti. E quindi anche ai presentatori, perché `UI\Presenter` è un discendente di `UI\Control`. +Questo tipo di richiesta è chiamato segnale. E proprio come le azioni invocano i metodi `action()` o `render()`, i segnali chiamano i metodi `handle()`. Mentre il concetto di azione (o view) è legato puramente ai presenter, i segnali riguardano tutti i componenti. E quindi anche i presenter, perché `UI\Presenter` è un discendente di `UI\Control`. ```php public function handleClick(int $x, int $y): void @@ -192,36 +192,36 @@ public function handleClick(int $x, int $y): void } ``` -Il collegamento che richiama il segnale viene creato nel modo consueto, cioè nel template tramite l'attributo `n:href` o il tag `{link}`, nel codice tramite il metodo `link()`. Maggiori informazioni nel capitolo [Creazione di collegamenti URL |creating-links#Links to Signal]. +Un link che chiama un segnale viene creato nel modo consueto, cioè nel template con l'attributo `n:href` o il tag `{link}`, nel codice con il metodo `link()`. Maggiori informazioni nel capitolo [Creazione di link URL |creating-links#Link a segnali]. ```latte -click here +clicca qui ``` -Il segnale viene sempre richiamato nel presentatore e nella vista correnti, quindi non è possibile collegarsi al segnale in presentatori/azioni diversi. +Un segnale viene sempre chiamato sul presenter e sull'azione correnti, non è possibile invocarlo su un altro presenter o un'altra azione. -Pertanto, il segnale provoca il ricaricamento della pagina esattamente come nella richiesta originale, solo che in aggiunta richiama il metodo di gestione del segnale con i parametri appropriati. Se il metodo non esiste, viene lanciata l'eccezione [api:Nette\Application\UI\BadSignalException], che viene visualizzata dall'utente come pagina di errore 403 Forbidden. +Quindi, un segnale provoca il ricaricamento della pagina proprio come nella richiesta originale, ma in più chiama il metodo di gestione del segnale con i parametri appropriati. Se il metodo non esiste, viene lanciata un'eccezione [api:Nette\Application\UI\BadSignalException], che viene mostrata all'utente come una pagina di errore 403 Forbidden. -Snippet e AJAX .[#toc-snippets-and-ajax] -======================================== +Snippet e AJAX +============== -I segnali possono ricordare un po' AJAX: gestori che vengono chiamati sulla pagina corrente. E avete ragione, i segnali vengono spesso chiamati utilizzando AJAX, e poi trasmettiamo al browser solo le parti modificate della pagina. Sono chiamati snippet. Maggiori informazioni si trovano nella [pagina su AJAX |ajax]. +I segnali potrebbero ricordarvi un po' AJAX: gestori che vengono invocati sulla pagina corrente. E avete ragione, i segnali vengono infatti spesso chiamati tramite AJAX e successivamente vengono trasferite al browser solo le parti modificate della pagina. Ovvero i cosiddetti snippet. Maggiori informazioni si trovano sulla [pagina dedicata ad AJAX |ajax]. -Messaggi Flash .[#toc-flash-messages] -===================================== +Messaggi flash +============== -Un componente ha una propria memoria di messaggi flash indipendente dal presentatore. Si tratta di messaggi che, ad esempio, informano sul risultato dell'operazione. Una caratteristica importante dei messaggi flash è che sono disponibili nel modello anche dopo il reindirizzamento. Anche dopo essere stati visualizzati, rimarranno in vita per altri 30 secondi - ad esempio, nel caso in cui l'utente dovesse involontariamente aggiornare la pagina - il messaggio non andrà perso. +Un componente ha il proprio storage di messaggi flash indipendente dal presenter. Si tratta di messaggi che, ad esempio, informano sul risultato di un'operazione. Una caratteristica importante dei messaggi flash è che sono disponibili nel template anche dopo un redirect. Anche dopo essere stati visualizzati, rimangono attivi per altri 30 secondi – ad esempio, nel caso in cui l'utente aggiorni la pagina a causa di un errore di trasmissione - il messaggio non scomparirà immediatamente. -L'invio avviene tramite il metodo [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Il primo parametro è il testo del messaggio o l'oggetto `stdClass` che rappresenta il messaggio. Il secondo parametro opzionale è il tipo di messaggio (errore, avviso, informazione, ecc.). Il metodo `flashMessage()` restituisce un'istanza di messaggio flash come oggetto stdClass a cui è possibile passare informazioni. +L'invio è gestito dal metodo [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Il primo parametro è il testo del messaggio o un oggetto `stdClass` che rappresenta il messaggio. Il secondo parametro opzionale è il suo tipo (error, warning, info, ecc.). Il metodo `flashMessage()` restituisce un'istanza del messaggio flash come oggetto `stdClass`, a cui è possibile aggiungere ulteriori informazioni. ```php -$this->flashMessage('L'articolo è stato cancellato.'); -$this->redirect(/* ... */); // e reindirizzamento +$this->flashMessage('L\'elemento è stato eliminato.'); +$this->redirect(/* ... */); // e reindirizziamo ``` -Nel modello, questi messaggi sono disponibili nella variabile `$flashes` come oggetti `stdClass`, che contengono le proprietà `message` (testo del messaggio), `type` (tipo di messaggio) e possono contenere le già citate informazioni sull'utente. Li disegniamo come segue: +Nel template, questi messaggi sono disponibili nella variabile `$flashes` come oggetti `stdClass`, che contengono le proprietà `message` (testo del messaggio), `type` (tipo del messaggio) e possono contenere le informazioni utente già menzionate. Li renderizziamo ad esempio così: ```latte {foreach $flashes as $flash} @@ -230,44 +230,66 @@ Nel modello, questi messaggi sono disponibili nella variabile `$flashes` come og ``` -Parametri persistenti .[#toc-persistent-parameters] -=================================================== +Redirect dopo un segnale +======================== -I parametri persistenti sono utilizzati per mantenere lo stato dei componenti tra diverse richieste. Il loro valore rimane invariato anche dopo che si è fatto clic su un collegamento. A differenza dei dati di sessione, vengono trasferiti nell'URL. Vengono trasferiti automaticamente, compresi i collegamenti creati in altri componenti della stessa pagina. +Dopo l'elaborazione di un segnale di componente, spesso segue un redirect. È una situazione simile a quella dei form - dopo il loro invio reindirizziamo anche, in modo che l'aggiornamento della pagina nel browser non provochi un nuovo invio dei dati. -Ad esempio, si dispone di un componente di paginazione dei contenuti. In una pagina possono esserci diversi componenti di questo tipo. Si vuole che tutti i componenti rimangano nella pagina corrente quando si fa clic sul collegamento. Pertanto, il numero di pagina (`page`) è un parametro persistente. +```php +$this->redirect('this') // reindirizza al presenter e all'azione correnti +``` -Creare un parametro persistente è estremamente facile in Nette. È sufficiente creare una proprietà pubblica e contrassegnarla con l'attributo: (in precedenza si usava `/** @persistent */` ) +Poiché un componente è un elemento riutilizzabile e di solito non dovrebbe avere un legame diretto con presenter specifici, i metodi `redirect()` e `link()` interpretano automaticamente il parametro come un segnale del componente: ```php -use Nette\Application\Attributes\Persistent; // questa linea è importante +$this->redirect('click') // reindirizza al segnale 'click' dello stesso componente +``` + +Se è necessario reindirizzare a un altro presenter o azione, è possibile farlo tramite il presenter: + +```php +$this->getPresenter()->redirect('Product:show'); // reindirizza a un altro presenter/azione +``` + + +Parametri persistenti +===================== + +I parametri persistenti servono a mantenere lo stato nei componenti tra richieste diverse. Il loro valore rimane lo stesso anche dopo aver cliccato su un link. A differenza dei dati nella sessione, vengono trasferiti nell'URL. E questo avviene in modo completamente automatico, inclusi i link creati in altri componenti sulla stessa pagina. + +Ad esempio, hai un componente per la paginazione del contenuto. Possono esserci più componenti di questo tipo su una pagina. E desideriamo che, dopo aver cliccato su un link, tutti i componenti rimangano sulla loro pagina corrente. Pertanto, rendiamo il numero di pagina (`page`) un parametro persistente. + +La creazione di un parametro persistente in Nette è estremamente semplice. Basta creare una proprietà pubblica e contrassegnarla con un attributo: (in precedenza si usava `/** @persistent */`) + +```php +use Nette\Application\Attributes\Persistent; // questa riga è importante class PaginatingControl extends Control { #[Persistent] - public int $page = 1; // deve essere pubblico + public int $page = 1; // deve essere public } ``` -Si consiglia di includere nella proprietà il tipo di dati (ad esempio, `int`) e si può anche includere un valore predefinito. I valori dei parametri possono essere [convalidati |#Validation of Persistent Parameters]. +Per la proprietà, si consiglia di specificare anche il tipo di dati (es. `int`) e si può anche specificare un valore predefinito. I valori dei parametri possono essere [validati |#Validazione dei parametri persistenti]. -È possibile modificare il valore di un parametro persistente durante la creazione di un collegamento: +Durante la creazione di un link, è possibile modificare il valore del parametro persistente: ```latte -next +successivo ``` -Oppure può essere *ripristinato*, cioè rimosso dall'URL. In questo caso, assumerà il valore predefinito: +Oppure può essere *resettato*, cioè rimosso dall'URL. Assumerà quindi il suo valore predefinito: ```latte reset ``` -Componenti persistenti .[#toc-persistent-components] -==================================================== +Componenti persistenti +====================== -Non solo i parametri, ma anche i componenti possono essere persistenti. I loro parametri persistenti vengono trasferiti anche tra azioni diverse o tra presentatori diversi. I componenti persistenti vengono contrassegnati con queste annotazioni per la classe presentatore. Per esempio, qui contrassegniamo i componenti `calendar` e `poll` come segue: +Non solo i parametri, ma anche i componenti possono essere persistenti. Per un tale componente, i suoi parametri persistenti vengono trasferiti anche tra diverse azioni del presenter o tra più presenter. I componenti persistenti sono contrassegnati da un'annotazione nella classe del presenter. Ad esempio, in questo modo contrassegniamo i componenti `calendar` e `poll`: ```php /** @@ -278,9 +300,9 @@ class DefaultPresenter extends Nette\Application\UI\Presenter } ``` -Non è necessario contrassegnare i sottocomponenti come persistenti, lo sono automaticamente. +I sottocomponenti all'interno di questi componenti non devono essere contrassegnati, diventeranno persistenti anch'essi. -In PHP 8, si possono usare anche gli attributi per contrassegnare i componenti persistenti: +In PHP 8, è possibile utilizzare anche attributi per contrassegnare i componenti persistenti: ```php use Nette\Application\Attributes\Persistent; @@ -292,10 +314,10 @@ class DefaultPresenter extends Nette\Application\UI\Presenter ``` -Componenti con dipendenze .[#toc-components-with-dependencies] -============================================================== +Componenti con dipendenze +========================= -Come creare componenti con dipendenze senza "incasinare" i presentatori che li utilizzeranno? Grazie alle caratteristiche intelligenti del contenitore DI di Nette, come per l'uso dei servizi tradizionali, possiamo lasciare la maggior parte del lavoro al framework. +Come creare componenti con dipendenze senza "inquinare" i presenter che li utilizzeranno? Grazie alle proprietà intelligenti del container DI in Nette, proprio come nell'uso dei servizi classici, è possibile lasciare la maggior parte del lavoro al framework. Prendiamo come esempio un componente che ha una dipendenza dal servizio `PollFacade`: @@ -303,24 +325,24 @@ Prendiamo come esempio un componente che ha una dipendenza dal servizio `PollFac class PollControl extends Control { public function __construct( - private int $id, // Id di un sondaggio, per il quale viene creato il componente + private int $id, // Id del sondaggio per cui creiamo il componente private PollFacade $facade, ) { } public function handleVote(int $voteId): void { - $this->facade->vote($id, $voteId); + $this->facade->vote($this->id, $voteId); // ... } } ``` -Se stessimo scrivendo un servizio classico, non ci sarebbe nulla di cui preoccuparsi. Il contenitore DI si occuperebbe in modo invisibile di passare tutte le dipendenze. Ma di solito gestiamo i componenti creando una nuova istanza di essi direttamente nel presentatore, con [metodi di fabbrica |#factory methods] `createComponent...()`. Ma passare tutte le dipendenze di tutti i componenti al presentatore per poi passarle ai componenti è macchinoso. E la quantità di codice scritto... +Se stessimo scrivendo un servizio classico, non ci sarebbe nulla da risolvere. Il container DI si occuperebbe invisibilmente di passare tutte le dipendenze. Ma con i componenti, di solito li gestiamo creando una nuova istanza direttamente nel presenter nei [#metodi factory] `createComponent…()`. Ma passare tutte le dipendenze di tutti i componenti al presenter per poi passarle ai componenti è macchinoso. E quanto codice scritto… -La domanda logica è: perché non registrare il componente come un servizio classico, passarlo al presentatore e poi restituirlo nel metodo `createComponent...()`? Ma questo approccio è inadeguato, perché vogliamo poter creare il componente più volte. +La domanda logica è: perché non registriamo semplicemente il componente come un servizio classico, lo passiamo al presenter e poi lo restituiamo nel metodo `createComponent…()`? Tale approccio è però inappropriato, perché vogliamo poter creare il componente anche più volte. -La soluzione corretta è scrivere un factory per il componente, cioè una classe che crei il componente per noi: +La soluzione corretta è scrivere una factory per il componente, cioè una classe che ci creerà il componente: ```php class PollControlFactory @@ -337,17 +359,17 @@ class PollControlFactory } ``` -Ora registriamo il nostro servizio al contenitore DI per la configurazione: +Registriamo questa factory nel nostro container nella configurazione: ```neon services: - PollControlFactory ``` -Infine, utilizzeremo questo factory nel nostro presenter: +e infine la utilizziamo nel nostro presenter: ```php -class PollPresenter extends Nette\UI\Application\Presenter +class PollPresenter extends Nette\Application\UI\Presenter { public function __construct( private PollControlFactory $pollControlFactory, @@ -362,7 +384,7 @@ class PollPresenter extends Nette\UI\Application\Presenter } ``` -La cosa bella è che Nette DI può [generare |dependency-injection:factory] factory così semplici, quindi invece di scrivere l'intero codice, è sufficiente scrivere la sua interfaccia: +La cosa fantastica è che Nette DI può [generare |dependency-injection:factory] tali semplici factory, quindi invece del suo intero codice, basta scrivere solo la sua interfaccia: ```php interface PollControlFactory @@ -371,21 +393,21 @@ interface PollControlFactory } ``` -Tutto qui. Nette implementa internamente questa interfaccia e la inietta nel nostro presenter, dove possiamo usarla. Inoltre, passa magicamente il nostro parametro `$id` e l'istanza della classe `PollFacade` nel nostro componente. +E questo è tutto. Nette implementa internamente questa interfaccia e la passa al presenter, dove possiamo già utilizzarla. Aggiunge magicamente anche il parametro `$id` e l'istanza della classe `PollFacade` al nostro componente. -Componenti in profondità .[#toc-components-in-depth] -==================================================== +Componenti in profondità +======================== -I componenti di un'applicazione Nette sono le parti riutilizzabili di un'applicazione Web che vengono incorporate nelle pagine, argomento di questo capitolo. Quali sono esattamente le funzionalità di un componente? +I componenti in Nette Application rappresentano parti riutilizzabili dell'applicazione web che inseriamo nelle pagine e a cui, del resto, è dedicato l'intero capitolo. Quali capacità ha esattamente un tale componente? -1) è renderizzabile in un modello -2) sa quale parte di sé rendere durante una [richiesta AJAX |ajax#invalidation] (snippet) -3) ha la capacità di memorizzare il proprio stato in un URL (parametri persistenti) -4) ha la capacità di rispondere alle azioni dell'utente (segnali) +1) è renderizzabile nel template +2) sa [quale sua parte |ajax#Snippet] deve renderizzare durante una richiesta AJAX (snippet) +3) ha la capacità di memorizzare il proprio stato nell'URL (parametri persistenti) +4) ha la capacità di reagire alle azioni dell'utente (segnali) 5) crea una struttura gerarchica (dove la radice è il presenter) -Ciascuna di queste funzioni è gestita da una delle classi di ereditarietà. Il rendering (1 + 2) è gestito da [api:Nette\Application\UI\Control], l'incorporazione nel [ciclo di vita |presenters#life-cycle-of-presenter] (3, 4) dalla classe [api:Nette\Application\UI\Component] e la creazione della struttura gerarchica (5) dalle classi [Container e Component |component-model:]. +Ognuna di queste funzioni è gestita da una delle classi della linea ereditaria. Il rendering (1 + 2) è gestito da [api:Nette\Application\UI\Control], l'integrazione nel [ciclo di vita |presenters#Ciclo di vita del presenter] (3, 4) dalla classe [api:Nette\Application\UI\Component] e la creazione della struttura gerarchica (5) dalle classi [Container e Component |component-model:]. ``` Nette\ComponentModel\Component { IComponent } @@ -400,18 +422,18 @@ Nette\ComponentModel\Component { IComponent } ``` -Ciclo di vita di un componente .[#toc-life-cycle-of-component] --------------------------------------------------------------- +Ciclo di vita del componente +---------------------------- [* lifecycle-component.svg *] *** *Ciclo di vita del componente* .<> -Convalida dei parametri persistenti .[#toc-validation-of-persistent-parameters] -------------------------------------------------------------------------------- +Validazione dei parametri persistenti +------------------------------------- -I valori dei [parametri persistenti |#persistent parameters] ricevuti dagli URL vengono scritti nelle proprietà dal metodo `loadState()`. Il metodo controlla anche se il tipo di dati specificato per la proprietà corrisponde, altrimenti risponde con un errore 404 e la pagina non viene visualizzata. +I valori dei [#parametri persistenti] ricevuti dall'URL vengono scritti nelle proprietà dal metodo `loadState()`. Questo controlla anche se il tipo di dati specificato nella proprietà corrisponde, altrimenti risponde con un errore 404 e la pagina non viene visualizzata. -Non fidarsi mai ciecamente dei parametri persistenti, perché possono essere facilmente sovrascritti dall'utente nell'URL. Per esempio, ecco come verificare se il numero di pagina `$this->page` è maggiore di 0. Un buon modo per farlo è sovrascrivere il metodo `loadState()` citato in precedenza: +Non fidarti mai ciecamente dei parametri persistenti, perché possono essere facilmente sovrascritti dall'utente nell'URL. In questo modo, ad esempio, verifichiamo se il numero di pagina `$this->page` è maggiore di 0. Un modo appropriato è sovrascrivere il metodo `loadState()` menzionato: ```php class PaginatingControl extends Control @@ -421,8 +443,8 @@ class PaginatingControl extends Control public function loadState(array $params): void { - parent::loadState($params); // qui viene impostato $this->pagina - // segue il controllo del valore dell'utente: + parent::loadState($params); // qui viene impostato $this->page + // segue il controllo del valore personalizzato: if ($this->page < 1) { $this->error(); } @@ -430,27 +452,27 @@ class PaginatingControl extends Control } ``` -Il processo opposto, cioè la raccolta di valori da proprietà persistenti, è gestito dal metodo `saveState()`. +Il processo opposto, cioè la raccolta dei valori dalle proprietà persistenti, è gestito dal metodo `saveState()`. -Segnali in profondità .[#toc-signals-in-depth] ----------------------------------------------- +Segnali in profondità +--------------------- -Un segnale causa un ricaricamento della pagina come la richiesta originale (con l'eccezione di AJAX) e invoca il metodo `signalReceived($signal)` la cui implementazione predefinita nella classe `Nette\Application\UI\Component` tenta di chiamare un metodo composto dalle parole `handle{Signal}`. L'ulteriore elaborazione si basa sull'oggetto dato. Gli oggetti che sono discendenti di `Component` (cioè `Control` e `Presenter`) cercano di chiamare `handle{Signal}` con i relativi parametri. +Un segnale provoca il ricaricamento della pagina proprio come nella richiesta originale (tranne quando viene chiamato tramite AJAX) e invoca il metodo `signalReceived($signal)`, la cui implementazione predefinita nella classe `Nette\Application\UI\Component` tenta di chiamare un metodo composto dalle parole `handle{signal}`. L'ulteriore elaborazione dipende dall'oggetto specifico. Gli oggetti che ereditano da `Component` (cioè `Control` e `Presenter`) reagiscono cercando di chiamare il metodo `handle{signal}` con i parametri appropriati. -In altre parole, viene presa la definizione del metodo `handle{Signal}` e tutti i parametri ricevuti nella richiesta vengono abbinati ai parametri del metodo. Ciò significa che il parametro `id` dell'URL viene abbinato al parametro del metodo `$id`, `something` a `$something` e così via. Se il metodo non esiste, il metodo `signalReceived` lancia [un'eccezione |api:Nette\Application\UI\BadSignalException]. +In altre parole: prende la definizione della funzione `handle{signal}` e tutti i parametri che sono arrivati con la richiesta, e agli argomenti vengono assegnati i parametri dall'URL in base al nome e tenta di chiamare il metodo dato. Ad esempio, come parametro `$id` viene passato il valore dal parametro `id` nell'URL, come `$something` viene passato `something` dall'URL, ecc. E se il metodo non esiste, il metodo `signalReceived` lancia un'[eccezione |api:Nette\Application\UI\BadSignalException]. -Il segnale può essere ricevuto da qualsiasi componente, presentatore di oggetti che implementano l'interfaccia `SignalReceiver` se è collegato all'albero dei componenti. +Un segnale può essere ricevuto da qualsiasi componente, presenter o oggetto che implementa l'interfaccia `SignalReceiver` ed è connesso all'albero dei componenti. -I principali destinatari dei segnali sono `Presenters` e i componenti visuali che estendono `Control`. Un segnale è un segnale che indica a un oggetto che deve fare qualcosa: un sondaggio conta i voti degli utenti, un riquadro con le notizie deve essere aperto, un modulo è stato inviato e deve elaborare i dati e così via. +I principali destinatari dei segnali saranno i `Presenter` e i componenti visivi che ereditano da `Control`. Un segnale serve come indicazione per un oggetto che deve fare qualcosa – un sondaggio deve contare il voto di un utente, un blocco di notizie deve espandersi e mostrare il doppio delle notizie, un form è stato inviato e deve elaborare i dati, e così via. -L'URL per il segnale viene creato con il metodo [Component::link() |api:Nette\Application\UI\Component::link()]. Come parametro `$destination` si passa la stringa `{signal}!` e come `$args` un array di argomenti da passare al gestore del segnale. I parametri del segnale sono collegati all'URL del presentatore/vista corrente. **Il parametro `?do` nell'URL determina il segnale chiamato. +L'URL per un segnale viene creato utilizzando il metodo [Component::link() |api:Nette\Application\UI\Component::link()]. Come parametro `$destination` passiamo la stringa `{signal}!` e come `$args` un array di argomenti che vogliamo passare al segnale. Il segnale viene sempre chiamato sul presenter e sull'azione correnti con i parametri correnti, vengono aggiunti solo i parametri del segnale. Inoltre, viene aggiunto all'inizio il **parametro `?do`, che specifica il segnale**. -Il suo formato è `{signal}` o `{signalReceiver}-{signal}`. `{signalReceiver}` è il nome del componente nel presentatore. Per questo motivo il trattino (imprecisamente dash) non può essere presente nel nome dei componenti: serve a dividere il nome del componente e del segnale, ma è possibile comporre più componenti. +Il suo formato è `{signal}` o `{signalReceiver}-{signal}`. `{signalReceiver}` è il nome del componente nel presenter. Pertanto, non può esserci un trattino nel nome del componente – viene utilizzato per separare il nome del componente e il segnale, tuttavia è possibile nidificare più componenti in questo modo. -Il metodo [isSignalReceiver() |api:Nette\Application\UI\Presenter::isSignalReceiver()] verifica se un componente (primo argomento) è un ricevitore di un segnale (secondo argomento). Il secondo argomento può essere omesso: in questo caso si scopre se il componente è un ricevitore di qualsiasi segnale. Se il secondo parametro è `true`, verifica se il componente o i suoi discendenti sono ricevitori di un segnale. +Il metodo [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] verifica se il componente (primo argomento) è il destinatario del segnale (secondo argomento). Possiamo omettere il secondo argomento – quindi verifica se il componente è il destinatario di qualsiasi segnale. Come secondo parametro è possibile specificare `true` e verificare così se il destinatario non è solo il componente specificato, ma anche uno qualsiasi dei suoi discendenti. -In qualsiasi fase precedente a `handle{Signal}` il segnale può essere eseguito manualmente chiamando il metodo [processSignal() |api:Nette\Application\UI\Presenter::processSignal()] che si assume la responsabilità dell'esecuzione del segnale. Prende il componente ricevente (se non è impostato è il presentatore stesso) e gli invia il segnale. +In qualsiasi fase precedente a `handle{signal}` possiamo eseguire il segnale manualmente chiamando il metodo [processSignal()|api:Nette\Application\UI\Presenter::processSignal()], che si occupa di gestire il segnale – prende il componente che è stato determinato come destinatario del segnale (se non è specificato alcun destinatario del segnale, è il presenter stesso) e gli invia il segnale. Esempio: @@ -460,4 +482,4 @@ if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, ' } ``` -Il segnale viene eseguito prematuramente e non verrà più richiamato. +In questo modo il segnale viene eseguito prematuramente e non verrà più chiamato. diff --git a/application/it/configuration.texy b/application/it/configuration.texy index f2582dfba9..04a2397c8a 100644 --- a/application/it/configuration.texy +++ b/application/it/configuration.texy @@ -1,58 +1,71 @@ -Configurazione dell'applicazione -******************************** +Configurazione delle Applicazioni +********************************* .[perex] -Panoramica delle opzioni di configurazione dell'applicazione Nette. +Panoramica delle opzioni di configurazione per le Applicazioni Nette. -Applicazione .[#toc-application] -================================ +Application +=========== ```neon application: - # mostra il pannello "Applicazione Nette" in Tracy BlueScreen? - debugger: ... # (bool) predefinito a true + # visualizzare il pannello "Nette Application" in Tracy BlueScreen? + debugger: ... # (bool) il default è true - # Il presentatore di errori sarà chiamato in caso di errore? - catchExceptions: ... # (bool) predefinito a true in modalità di produzione + # verrà chiamato l'error-presenter in caso di errore? + # ha effetto solo in modalità sviluppo + catchExceptions: ... # (bool) il default è true - # nome del presentatore di errori - errorPresenter: Error # (string) predefinito a 'Nette:Error'. + # nome dell'error-presenter + errorPresenter: Error # (string|array) il default è 'Nette:Error' - # definisce le regole per risolvere il nome del presentatore in una classe + # definisce alias per presenter e azioni + aliases: ... + + # definisce le regole per la traduzione del nome del presenter in classe mapping: ... - # I collegamenti errati generano avvisi? - # ha effetto solo in modalità sviluppatore - silentLinks: ... # (bool) predefinito a false + # i link errati non generano avvisi? + # ha effetto solo in modalità sviluppo + silentLinks: ... # (bool) il default è false ``` -Poiché i presentatori di errori non vengono richiamati per impostazione predefinita in modalità di sviluppo e gli errori vengono visualizzati da Tracy, la modifica del valore `catchExceptions` a `true` aiuta a verificare che i presentatori di errori funzionino correttamente durante lo sviluppo. +Dalla versione `nette/application` 3.2 è possibile definire una coppia di error-presenter: -L'opzione `silentLinks` determina il comportamento di Nette in modalità sviluppatore quando la generazione dei collegamenti fallisce (ad esempio, perché non c'è un presentatore, ecc.). Il valore predefinito `false` significa che Nette attiva `E_USER_WARNING`. L'impostazione di `true` sopprime questo messaggio di errore. In un ambiente di produzione, `E_USER_WARNING` viene sempre invocato. Si può anche influenzare questo comportamento impostando la variabile del presentatore [$invalidLinkMode |creating-links#Invalid Links]. +```neon +application: + errorPresenter: + 4xx: Error4xx # per l'eccezione Nette\Application\BadRequestException + 5xx: Error5xx # per le altre eccezioni +``` -La [mappatura definisce le regole |modules#mapping] con cui il nome della classe viene derivato dal nome del presentatore. +L'opzione `silentLinks` determina come Nette si comporta in modalità sviluppo quando la generazione di un link fallisce (ad esempio perché il presenter non esiste, ecc.). Il valore predefinito `false` significa che Nette genera un errore `E_USER_WARNING`. Impostandolo su `true` si sopprime questo messaggio di errore. Nell'ambiente di produzione, `E_USER_WARNING` viene sempre generato. Questo comportamento può essere influenzato anche impostando la variabile del presenter [$invalidLinkMode |creating-links#Link non validi]. +Gli [Alias semplificano il collegamento |creating-links#Alias] ai presenter usati frequentemente. -Registrazione automatica dei presentatori .[#toc-automatic-registration-of-presenters] --------------------------------------------------------------------------------------- +La [Mappatura definisce le regole |directory-structure#Mappatura dei presenter], secondo le quali dal nome del presenter si deriva il nome della classe. -Nette aggiunge automaticamente i presentatori come servizi al contenitore DI, velocizzando notevolmente la loro creazione. Il modo in cui Nette trova i presentatori può essere configurato: + +Registrazione automatica dei presenter +-------------------------------------- + +Nette aggiunge automaticamente i presenter come servizi al container DI, il che accelera notevolmente la loro creazione. Come Nette trova i presenter può essere configurato: ```neon application: - # per cercare i presentatori nella mappa delle classi di Composer? - scanComposer: ... # (bool) predefinito a true + # cercare i presenter nella mappa delle classi di Composer? + scanComposer: ... # (bool) il default è true - # una maschera che deve corrispondere alla classe e al nome del file - scanFilter: ... # (string) predefinito a '*Presenter'. + # maschera a cui devono corrispondere il nome della classe e del file + scanFilter: ... # (string) il default è '*Presenter' - # in quali directory cercare i presentatori? - scanDirs: # (string[]|false) predefinito a '%appDir%'. + # in quali directory cercare i presenter? + scanDirs: # (string[]|false) il default è '%appDir%' - %vendorDir%/mymodule ``` -Le directory elencate in `scanDirs` non sovrascrivono il valore predefinito `%appDir%`, ma lo integrano, quindi `scanDirs` conterrà entrambi i percorsi `%appDir%` e `%vendorDir%/mymodule`. Se si vuole sovrascrivere la directory predefinita, si usa il [punto esclamativo |dependency-injection:configuration#Merging]: +Le directory specificate in `scanDirs` non sovrascrivono il valore predefinito `%appDir%`, ma lo completano, quindi `scanDirs` conterrà entrambi i percorsi `%appDir%` e `%vendorDir%/mymodule`. Se volessimo omettere la directory predefinita, useremmo un [punto esclamativo |dependency-injection:configuration#Unione], che sovrascrive il valore: ```neon application: @@ -60,64 +73,73 @@ application: - %vendorDir%/mymodule ``` -La scansione delle directory può essere disattivata impostando false. Non si consiglia di sopprimere completamente l'aggiunta automatica dei presentatori, altrimenti le prestazioni dell'applicazione saranno ridotte. +La scansione delle directory può essere disattivata specificando il valore `false`. Non consigliamo di sopprimere completamente l'aggiunta automatica dei presenter, perché altrimenti le prestazioni dell'applicazione diminuiranno. -Latte .[#toc-latte] -=================== +Template Latte +============== -Questa impostazione influenza globalmente il comportamento di Latte nei componenti e nei presentatori. +Con questa impostazione è possibile influenzare globalmente il comportamento di Latte nei componenti e nei presenter. ```neon latte: - # mostra il pannello Latte nella barra Tracy per il modello principale (true) o per tutti i componenti (all)? - debugger: ... # (true|false|'all') predefinito a true + # visualizzare il pannello Latte nella Tracy Bar per il template principale (true) o tutti i componenti (all)? + debugger: ... # (true|false|'all') il default è true + + # genera template con l'intestazione declare(strict_types=1) + strictTypes: ... # (bool) il default è false - # genera modelli con declare(strict_types=1) - strictTypes: ... # (bool) predefinito a false + # attiva la modalità [parser rigoroso |latte:develop#striktní režim] + strictParsing: ... # (bool) il default è false - # classe di $this->template - templateClass: App\MyTemplateClass # predefinita a Nette\Bridges\ApplicationLatte\DefaultTemplate + # attiva il [controllo del codice generato |latte:develop#Kontrola vygenerovaného kódu] + phpLinter: ... # (string) il default è null + + # imposta la locale + locale: it_IT # (string) il default è null + + # classe dell'oggetto $this->template + templateClass: App\MyTemplateClass # il default è Nette\Bridges\ApplicationLatte\DefaultTemplate ``` -Se si utilizza la versione 3 di Latte, è possibile aggiungere nuove [estensioni |latte:creating-extension] utilizzando: +Se usi Latte versione 3, puoi aggiungere nuove [estensioni |latte:extending-latte#Latte Extension] usando: ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -/--comment - - - - +Se usi Latte versione 2, puoi registrare nuovi tag specificando il nome della classe o un riferimento a un servizio. Per impostazione predefinita, viene chiamato il metodo `install()`, ma questo può essere modificato specificando il nome di un altro metodo: +```neon +latte: + # registrazione di tag Latte personalizzati + macros: + - App\MyLatteMacros::register # metodo statico, nomeclasse o callable + - @App\MyLatteMacrosFactory # servizio con metodo install() + - @App\MyLatteMacrosFactory::register # servizio con metodo register() + +services: + - App\MyLatteMacrosFactory +``` - - - - -\-- - - -Instradamento .[#toc-routing] -============================= +Routing +======= Impostazioni di base: ```neon routing: - # mostra il pannello di instradamento nella barra Tracy? - debugger: ... # (bool) predefinito a true + # visualizzare il pannello di routing nella Tracy Bar? + debugger: ... # (bool) il default è true - # per serializzare il router nel contenitore DI? - cache: ... # (bool) predefinito a false + # serializza il router nel container DI + cache: ... # (bool) il default è false ``` -Il router è solitamente definito nella classe [RouterFactory |routing#Route Collection]. In alternativa, i router possono essere definiti nella configurazione usando le coppie `mask: action`, ma questo metodo non offre un'ampia gamma di impostazioni: +Il routing viene solitamente definito nella classe [RouterFactory |routing#Collezione di route]. In alternativa, le route possono essere definite anche nella configurazione utilizzando coppie `maschera: azione`, ma questo metodo non offre una così ampia variabilità nelle impostazioni: ```neon routing: @@ -127,28 +149,43 @@ routing: ``` -Costanti .[#toc-constants] -========================== +Costanti +======== -Creare costanti PHP. +Creazione di costanti PHP. ```neon constants: Foobar: 'baz' ``` -La costante `Foobar` viene creata dopo l'avvio. +Dopo l'avvio dell'applicazione, verrà creata la costante `Foobar`. .[note] -Le costanti non dovrebbero servire come variabili disponibili a livello globale. Per passare valori agli oggetti, usare l'[iniezione di dipendenza |dependency-injection:passing-dependencies]. +Le costanti non dovrebbero servire come una sorta di variabili globalmente disponibili. Per passare valori agli oggetti, utilizza la [dependency injection |dependency-injection:passing-dependencies]. PHP === -È possibile impostare le direttive PHP. Una panoramica di tutte le direttive si trova su [php.net |https://www.php.net/manual/en/ini.list.php]. +Impostazione delle direttive PHP. Una panoramica di tutte le direttive si trova su [php.net |https://www.php.net/manual/en/ini.list.php]. ```neon php: - date.timezone: Europe/Prague + date.timezone: Europe/Rome ``` + + +Servizi DI +========== + +Questi servizi vengono aggiunti al container DI: + +| Nome | Tipo | Descrizione +|---------------------------------------------------------- +| `application.application` | [api:Nette\Application\Application] | [avviatore dell'intera applicazione |how-it-works#Nette Application] +| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] +| `application.presenterFactory` | [api:Nette\Application\PresenterFactory] | factory per i presenter +| `application.###` | [api:Nette\Application\UI\Presenter] | singoli presenter +| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | factory dell'oggetto `Latte\Engine` +| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | factory per [`$this->template` |templates] diff --git a/application/it/creating-links.texy b/application/it/creating-links.texy index 7b8cafdf00..6dc629a81b 100644 --- a/application/it/creating-links.texy +++ b/application/it/creating-links.texy @@ -1,160 +1,160 @@ -Creazione di collegamenti URL -***************************** +Creazione di link URL +*********************
    -Creare collegamenti in Nette è facile come puntare un dito. Basta indicare e il framework farà tutto il lavoro per voi. Vi mostreremo: +Creare link in Nette è semplice come puntare il dito. Basta mirare e il framework farà tutto il lavoro per te. Vedremo: -- come creare collegamenti nei template e altrove -- come distinguere un collegamento alla pagina corrente +- come creare link nei template e altrove +- come distinguere un link alla pagina corrente - cosa fare con i link non validi
    -Grazie al [routing bidirezionale |routing], non dovrete mai codificare gli URL delle applicazioni nei template o nel codice, che potrebbero cambiare in seguito o essere complicati da comporre. Basta specificare il presentatore e l'azione nel link, passare eventuali parametri e il framework genererà da solo l'URL. In effetti, è molto simile alla chiamata di una funzione. Vi piacerà. +Grazie al [routing bidirezionale |routing], non dovrai mai scrivere hardcoded gli indirizzi URL della tua applicazione nei template o nel codice, che potrebbero cambiare in seguito, o comporli in modo complicato. Nel link, basta specificare il presenter e l'azione, passare eventuali parametri e il framework genererà l'URL da solo. In realtà, è molto simile a chiamare una funzione. Ti piacerà. -Nel modello del presentatore .[#toc-in-the-presenter-template] -============================================================== +Nel template del presenter +========================== -Il più delle volte creiamo collegamenti nei modelli e un grande aiuto è l'attributo `n:href`: +Il più delle volte creiamo link nei template e un ottimo aiuto è l'attributo `n:href`: ```latte -detail +dettaglio ``` -Si noti che al posto dell'attributo HTML `href` abbiamo usato [n:attributo |latte:syntax#n:attributes] `n:href`. Il suo valore non è un URL, come si è abituati a vedere con l'attributo `href`, ma il nome del presentatore e l'azione. +Nota che invece dell'attributo HTML `href`, abbiamo usato l'[attributo n |latte:syntax#n:attributi] `n:href`. Il suo valore non è quindi un URL, come sarebbe nel caso dell'attributo `href`, ma il nome del presenter e dell'azione. -Cliccare su un link è, in parole povere, come chiamare un metodo `ProductPresenter::renderShow()`. E se ha dei parametri nella sua firma, possiamo chiamarlo con degli argomenti: +Cliccare sul link è, in parole povere, qualcosa come chiamare il metodo `ProductPresenter::renderShow()`. E se ha parametri nella sua firma, possiamo chiamarlo con argomenti: ```latte -detail +dettaglio prodotto ``` -È anche possibile passare parametri con nome. Il seguente collegamento passa il parametro `lang` con il valore `en`: +È possibile passare anche parametri nominati. Il seguente link passa il parametro `lang` con il valore `cs`: ```latte -detail +dettaglio prodotto ``` -Se il metodo `ProductPresenter::renderShow()` non ha `$lang` nella sua firma, può leggere il valore del parametro usando `$lang = $this->getParameter('lang')`. +Se il metodo `ProductPresenter::renderShow()` non ha `$lang` nella sua firma, può ottenere il valore del parametro usando `$lang = $this->getParameter('lang')` o dalla [proprietà |presenters#Parametri della richiesta]. -Se i parametri sono memorizzati in un array, possono essere espansi con l'operatore `...` (o `(expand)` in Latte 2.x): +Se i parametri sono memorizzati in un array, possono essere espansi con l'operatore `...` (in Latte 2.x con l'operatore `(expand)`): ```latte -{var $args = [$product->id, lang => en]} -detail +{var $args = [$product->id, lang => cs]} +dettaglio prodotto ``` -Anche i cosiddetti [parametri persistenti |presenters#persistent parameters] vengono passati automaticamente nei collegamenti. +Nei link vengono automaticamente passati anche i cosiddetti [parametri persistenti |presenters#Parametri persistenti]. -L'attributo `n:href` è molto utile per i tag HTML ``. Se vogliamo stampare il link altrove, per esempio nel testo, usiamo `{link}`: +L'attributo `n:href` è molto utile per i tag HTML ``. Se vogliamo stampare il link altrove, ad esempio nel testo, usiamo `{link}`: ```latte -URL is: {link Home:default} +L'indirizzo è: {link Home:default} ``` -Nel codice .[#toc-in-the-code] -============================== +Nel codice +========== -Il metodo `link()` viene utilizzato per creare un collegamento nel presentatore: +Per creare un link nel presenter, si usa il metodo `link()`: ```php $url = $this->link('Product:show', $product->id); ``` -I parametri possono essere passati anche come array in cui possono essere specificati anche parametri denominati: +I parametri possono essere passati anche tramite un array, dove è possibile specificare anche parametri nominati: ```php $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); ``` -I collegamenti possono essere creati anche senza presentatore, utilizzando il [LinkGenerator |#LinkGenerator] e il suo metodo `link()`. +I link possono essere creati anche senza un presenter, per questo c'è [#LinkGenerator] e il suo metodo `link()`. -Collegamenti al presentatore .[#toc-links-to-presenter] -======================================================= +Link al presenter +================= -Se l'obiettivo del collegamento è il presentatore e l'azione, la sintassi è questa: +Se la destinazione del link è un presenter e un'azione, ha questa sintassi: ``` [//] [[[[:]module:]presenter:]action | this] [#fragment] ``` -Il formato è supportato da tutti i tag Latte e da tutti i metodi del presentatore che funzionano con i collegamenti, cioè `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` e anche [LinkGenerator |#LinkGenerator]. Quindi, anche se negli esempi viene utilizzato `n:href`, potrebbe essere presente una qualsiasi delle funzioni. +Il formato è supportato da tutti i tag Latte e da tutti i metodi del presenter che lavorano con i link, cioè `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` e anche [#LinkGenerator]. Quindi, anche se negli esempi viene usato `n:href`, potrebbe esserci una qualsiasi delle funzioni. La forma base è quindi `Presenter:action`: ```latte -home +pagina iniziale ``` -Se ci si collega all'azione del presentatore corrente, si può omettere il suo nome: +Se ci colleghiamo a un'azione del presenter corrente, possiamo omettere il suo nome: ```latte -home +pagina iniziale ``` -Se l'azione è `default`, possiamo ometterlo, ma i due punti devono rimanere: +Se la destinazione è l'azione `default`, possiamo ometterla, ma i due punti devono rimanere: ```latte -home +pagina iniziale ``` -I collegamenti possono anche puntare ad altri [moduli |modules]. In questo caso, i collegamenti si distinguono in relativi ai sottomoduli o assoluti. Il principio è analogo a quello dei percorsi su disco, solo che al posto degli slash ci sono i punti. Supponiamo che il presentatore attuale faccia parte del modulo `Front`, quindi scriveremo: +I link possono anche puntare ad altri [moduli |directory-structure#Presenter e template]. Qui i link si distinguono in relativi a un sottomodulo nidificato o assoluti. Il principio è analogo ai percorsi su disco, solo che al posto degli slash ci sono i due punti. Supponiamo che il presenter corrente faccia parte del modulo `Front`, allora scriveremo: ```latte -link to Front:Shop:Product:show -link to Admin:Product:show +link a Front:Shop:Product:show +link a Admin:Product:show ``` -Un caso particolare è il [collegamento a se stesso |#Links to Current Page]. Qui scriveremo `this` come target. +Un caso speciale è un link [a se stesso |#Link alla pagina corrente], dove specifichiamo `this` come destinazione. ```latte -refresh +aggiorna ``` -Possiamo collegarci a una determinata parte della pagina HTML tramite un cosiddetto frammento dopo il simbolo hash `#`: +Possiamo collegarci a una parte specifica della pagina tramite il cosiddetto frammento dopo il simbolo cancelletto `#`: ```latte -link to Home:default and fragment #main +link a Home:default e frammento #main ``` -Percorsi assoluti .[#toc-absolute-paths] -======================================== +Percorsi assoluti +================= -I link generati da `link()` o `n:href` sono sempre percorsi assoluti (cioè iniziano con `/`), ma non URL assoluti con protocollo e dominio come `https://domain`. +I link generati usando `link()` o `n:href` sono sempre percorsi assoluti (cioè iniziano con `/`), ma non URL assoluti con protocollo e dominio come `https://domain`. -Per generare un URL assoluto, aggiungere due barre all'inizio (ad esempio, `n:href="//Home:"`). Oppure si può impostare il presentatore in modo che generi solo collegamenti assoluti, impostando `$this->absoluteUrls = true`. +Per generare un URL assoluto, aggiungi due slash all'inizio (es. `n:href="//Home:"`). Oppure si può impostare il presenter per generare solo link assoluti impostando `$this->absoluteUrls = true`. -Collegamento alla pagina corrente .[#toc-link-to-current-page] -============================================================== +Link alla pagina corrente +========================= -Il target `this` creerà un collegamento alla pagina corrente: +La destinazione `this` crea un link alla pagina corrente: ```latte -refresh +aggiorna ``` -Allo stesso tempo, tutti i parametri specificati nella firma del metodo `render()` o `action()` vengono trasferiti. Quindi, se ci troviamo nelle pagine `Product:show` e `id:123`, anche il collegamento a `this` passerà questo parametro. +Allo stesso tempo, vengono trasferiti anche tutti i parametri specificati nella firma del metodo `action()` o `render()`, se `action()` non è definita. Quindi, se siamo sulla pagina `Product:show` e `id: 123`, il link a `this` passerà anche questo parametro. -Naturalmente, è possibile specificare direttamente i parametri: +Ovviamente, è possibile specificare i parametri direttamente: ```latte -refresh +aggiorna ``` -La funzione `isLinkCurrent()` determina se la destinazione del link è la stessa della pagina corrente. Questo può essere utilizzato, ad esempio, in un modello per differenziare i collegamenti, ecc. +La funzione `isLinkCurrent()` verifica se la destinazione del link è identica alla pagina corrente. Questo può essere utilizzato, ad esempio, nel template per distinguere i link, ecc. -I parametri sono gli stessi del metodo `link()`, ma è anche possibile utilizzare il carattere jolly `*` al posto di un'azione specifica, ovvero qualsiasi azione del presentatore. +I parametri sono gli stessi del metodo `link()`, ma è anche possibile specificare un carattere jolly `*` invece di un'azione specifica, che significa qualsiasi azione del presenter dato. ```latte {if !isLinkCurrent('Admin:login')} - Přihlaste se + Accedi {/if}
  • @@ -162,58 +162,58 @@ I parametri sono gli stessi del metodo `link()`, ma è anche possibile utilizzar
  • ``` -Una forma abbreviata può essere usata in combinazione con `n:href` in un singolo elemento: +In combinazione con `n:href` in un unico elemento, si può usare la forma abbreviata: ```latte -... +... ``` -Il carattere jolly `*` sostituisce solo l'azione del presentatore, non il presentatore stesso. +Il carattere jolly `*` può essere usato solo al posto dell'azione, non del presenter. -Per sapere se ci troviamo in un certo modulo o in un suo sottomodulo, possiamo usare la funzione `isModuleCurrent(moduleName)`. +Per verificare se siamo in un certo modulo o in un suo sottomodulo, usiamo il metodo `isModuleCurrent(moduleName)`. ```latte -
  • +
  • ...
  • ``` -Collegamenti al segnale .[#toc-links-to-signal] -=============================================== +Link a segnali +============== -Il target del collegamento può essere non solo il presentatore e l'azione, ma anche il [segnale |components#Signal] (chiamano il metodo `handle()`). La sintassi è la seguente: +La destinazione di un link non deve essere solo un presenter e un'azione, ma anche un [segnale |components#Segnale] (chiamano il metodo `handle()`). Allora la sintassi è la seguente: ``` [//] [sub-component:]signal! [#fragment] ``` -Il segnale è quindi contraddistinto dal punto esclamativo: +Il segnale è quindi distinto dal punto esclamativo: ```latte -signal +segnale ``` -È inoltre possibile creare un collegamento al segnale del sottocomponente (o del sottocomponente): +È possibile creare anche un link al segnale di un sottocomponente (o sotto-sottocomponente): ```latte -signal +segnale ``` -Collegamenti nei componenti .[#toc-links-in-component] -====================================================== +Link nel componente +=================== -Poiché i [componenti |components] sono unità riutilizzabili separate che non dovrebbero avere relazioni con i presentatori circostanti, i collegamenti funzionano in modo leggermente diverso. L'attributo Latte `n:href` e il tag `{link}` e i metodi dei componenti come `link()` e altri considerano sempre la destinazione **come il nome del segnale**. Pertanto non è necessario utilizzare un punto esclamativo: +Poiché i [componenti |components] sono unità riutilizzabili separate che non dovrebbero avere legami con i presenter circostanti, i link funzionano in modo leggermente diverso qui. L'attributo Latte `n:href` e il tag `{link}` così come i metodi del componente come `link()` e altri considerano la destinazione del link **sempre come il nome del segnale**. Pertanto, non è nemmeno necessario specificare il punto esclamativo: ```latte -signal, not an action +segnale, non azione ``` -Se vogliamo collegarci ai presentatori nel modello del componente, usiamo il tag `{plink}`: +Se volessimo collegarci ai presenter nel template del componente, useremmo il tag `{plink}`: ```latte -home +inizio ``` o nel codice @@ -223,17 +223,41 @@ $this->getPresenter()->link('Home:default') ``` -Collegamenti non validi .[#toc-invalid-links] -============================================= +Alias .{data-version:v3.2.2} +============================ -Può capitare di creare un collegamento non valido, sia perché fa riferimento a un presentatore inesistente, sia perché passa più parametri di quelli che il metodo di destinazione riceve nella sua firma, sia quando non è possibile generare un URL per l'azione mirata. Cosa fare con i collegamenti non validi è determinato dalla variabile statica `Presenter::$invalidLinkMode`. Essa può avere uno dei seguenti valori (costanti): +A volte può essere utile assegnare un alias facilmente memorizzabile alla coppia Presenter:azione. Ad esempio, chiamare la pagina iniziale `Front:Home:default` semplicemente `home` o `Admin:Dashboard:default` come `admin`. -- `Presenter::InvalidLinkSilent` - modalità silenziosa, restituisce il simbolo `#` come URL -- `Presenter::InvalidLinkWarning` - verrà prodotto E_USER_WARNING -- `Presenter::InvalidLinkTextual` - avviso visivo, il testo dell'errore viene visualizzato nel link -- `Presenter::InvalidLinkException` - Viene lanciata l'eccezione InvalidLinkException. +Gli alias sono definiti nella [configurazione|configuration] sotto la chiave `application › aliases`: -L'impostazione predefinita in modalità di produzione è `InvalidLinkWarning` e in modalità di sviluppo è `InvalidLinkWarning | InvalidLinkTextual`. `InvalidLinkWarning` non uccide lo script nell'ambiente di produzione, ma l'avviso viene registrato. Nell'ambiente di sviluppo, [Tracy |tracy:] intercetta l'avviso e visualizza la schermata di errore. Se `InvalidLinkTextual` è impostato, il presentatore e i componenti restituiscono il messaggio di errore come URL che inizia con `#error:`. Per rendere visibili tali collegamenti, possiamo aggiungere una regola CSS al nostro foglio di stile: +```neon +application: + aliases: + home: Front:Home:default + admin: Admin:Dashboard:default + sign: Front:Sign:in +``` + +Nei link, vengono poi scritti usando la chiocciola, ad esempio: + +```latte +amministrazione +``` + +Sono supportati anche in tutti i metodi che lavorano con i link, come `redirect()` e simili. + + +Link non validi +=============== + +Può capitare di creare un link non valido - sia perché punta a un presenter inesistente, sia perché passa più parametri di quelli che il metodo di destinazione accetta nella sua firma, o quando non è possibile generare un URL per l'azione di destinazione. Come gestire i link non validi è determinato dalla variabile statica `Presenter::$invalidLinkMode`. Può assumere una combinazione di questi valori (costanti): + +- `Presenter::InvalidLinkSilent` - modalità silenziosa, come URL viene restituito il carattere # +- `Presenter::InvalidLinkWarning` - viene generato un avviso E_USER_WARNING, che verrà registrato in modalità produzione, ma non causerà l'interruzione dell'esecuzione dello script +- `Presenter::InvalidLinkTextual` - avviso visivo, stampa l'errore direttamente nel link +- `Presenter::InvalidLinkException` - viene lanciata l'eccezione InvalidLinkException + +L'impostazione predefinita è `InvalidLinkWarning` in modalità produzione e `InvalidLinkWarning | InvalidLinkTextual` in modalità sviluppo. `InvalidLinkWarning` nell'ambiente di produzione non causerà l'interruzione dello script, ma l'avviso verrà registrato. Nell'ambiente di sviluppo, verrà catturato da [Tracy |tracy:] e verrà visualizzato un bluescreen. `InvalidLinkTextual` funziona restituendo un messaggio di errore come URL, che inizia con i caratteri `#error:`. Per rendere tali link evidenti a prima vista, aggiungiamo al CSS: ```css a[href^="#error:"] { @@ -242,7 +266,7 @@ a[href^="#error:"] { } ``` -Se non vogliamo che vengano prodotti avvisi nell'ambiente di sviluppo, possiamo attivare la modalità silenziosa di collegamento non valido nella [configurazione |configuration]. +Se non vogliamo che vengano prodotti avvisi nell'ambiente di sviluppo, possiamo impostare la modalità silenziosa direttamente nella [configurazione|configuration]. ```neon application: @@ -250,13 +274,13 @@ application: ``` -Generatore di collegamenti .[#toc-linkgenerator] -================================================ +LinkGenerator +============= -Come creare link con il metodo `link()`, ma senza la presenza di un presentatore? Ecco perché [api:Nette\Application\LinkGenerator]. +Come creare link con una comodità simile a quella del metodo `link()`, ma senza la presenza di un presenter? Per questo c'è [api:Nette\Application\LinkGenerator]. -LinkGenerator è un servizio che si può far passare attraverso il costruttore e poi creare link con il suo metodo `link()`. +LinkGenerator è un servizio che puoi farti passare tramite il costruttore e poi creare link con il suo metodo `link()`. -C'è una differenza rispetto ai presentatori. LinkGenerator crea tutti i collegamenti come URL assoluti. Inoltre, non esiste un "presentatore corrente", quindi non è possibile specificare solo il nome dell'azione `link('default')` o i percorsi relativi ai [moduli |modules]. +Rispetto ai presenter, c'è una differenza. LinkGenerator crea tutti i link direttamente come URL assoluti. Inoltre, non esiste un "presenter corrente", quindi non è possibile specificare solo il nome dell'azione `link('default')` come destinazione o specificare percorsi relativi ai moduli. -I collegamenti non validi lanciano sempre `Nette\Application\UI\InvalidLinkException`. +I link non validi lanciano sempre `Nette\Application\UI\InvalidLinkException`. diff --git a/application/it/directory-structure.texy b/application/it/directory-structure.texy new file mode 100644 index 0000000000..7ed91fdfa6 --- /dev/null +++ b/application/it/directory-structure.texy @@ -0,0 +1,526 @@ +Struttura della Directory dell'Applicazione +******************************************* + +
    + +Come progettare una struttura di directory chiara e scalabile per i progetti in Nette Framework? Mostreremo le best practice che ti aiuteranno a organizzare il codice. Imparerai: + +- come **dividere logicamente** l'applicazione in directory +- come progettare la struttura in modo che **scali bene** con la crescita del progetto +- quali sono le **alternative possibili** e i loro vantaggi o svantaggi + +
    + + +È importante menzionare che Nette Framework stesso non impone alcuna struttura specifica. È progettato per essere facilmente adattabile a qualsiasi esigenza e preferenza. + + +Struttura di base del progetto +============================== + +Sebbene Nette Framework non detti alcuna struttura di directory fissa, esiste una disposizione predefinita comprovata sotto forma di [Web Project|https://github.com/nette/web-project]: + +/--pre +web-project/ +├── app/ ← directory con l'applicazione +├── assets/ ← file SCSS, JS, immagini..., alternativamente resources/ +├── bin/ ← script per la riga di comando +├── config/ ← configurazione +├── log/ ← errori registrati +├── temp/ ← file temporanei, cache +├── tests/ ← test +├── vendor/ ← librerie installate da Composer +└── www/ ← directory pubblica (document-root) +\-- + +Puoi modificare liberamente questa struttura in base alle tue esigenze - rinominare o spostare le cartelle. Successivamente, basta solo aggiornare i percorsi relativi alle directory nel file `Bootstrap.php` e eventualmente `composer.json`. Non è necessario nient'altro, nessuna riconfigurazione complessa, nessuna modifica delle costanti. Nette dispone di un intelligente autodetect e riconosce automaticamente la posizione dell'applicazione, inclusa la sua base URL. + + +Principi di organizzazione del codice +===================================== + +Quando esplori per la prima volta un nuovo progetto, dovresti orientarti rapidamente. Immagina di espandere la directory `app/Model/` e vedere questa struttura: + +/--pre +app/Model/ +├── Services/ +├── Repositories/ +└── Entities/ +\-- + +Da essa deduci solo che il progetto utilizza alcuni servizi, repository ed entità. Non impari assolutamente nulla sullo scopo effettivo dell'applicazione. + +Vediamo un approccio diverso - **organizzazione per domini**: + +/--pre +app/Model/ +├── Cart/ +├── Payment/ +├── Order/ +└── Product/ +\-- + +Qui è diverso - a prima vista è chiaro che si tratta di un e-shop. I nomi stessi delle directory rivelano cosa sa fare l'applicazione - lavora con pagamenti, ordini e prodotti. + +Il primo approccio (organizzazione per tipo di classi) porta in pratica una serie di problemi: il codice che è logicamente correlato è frammentato in diverse cartelle e devi saltare tra di esse. Pertanto, organizzeremo per domini. + + +Namespace +--------- + +È consuetudine che la struttura delle directory corrisponda ai namespace nell'applicazione. Ciò significa che la posizione fisica dei file corrisponde al loro namespace. Ad esempio, una classe situata in `app/Model/Product/ProductRepository.php` dovrebbe avere il namespace `App\Model\Product`. Questo principio aiuta nell'orientamento nel codice e semplifica l'autoloading. + + +Singolare vs Plurale nei nomi +----------------------------- + +Nota che per le directory principali dell'applicazione usiamo il singolare: `app`, `config`, `log`, `temp`, `www`. Allo stesso modo anche all'interno dell'applicazione: `Model`, `Core`, `Presentation`. Questo perché ognuna di esse rappresenta un concetto unitario. + +Allo stesso modo, ad esempio, `app/Model/Product` rappresenta tutto ciò che riguarda i prodotti. Non lo chiameremo `Products`, perché non è una cartella piena di prodotti (ci sarebbero file `nokia.php`, `samsung.php`). È un namespace contenente classi per lavorare con i prodotti - `ProductRepository.php`, `ProductService.php`. + +La cartella `app/Tasks` è al plurale perché contiene un insieme di script eseguibili separati - `CleanupTask.php`, `ImportTask.php`. Ognuno di essi è un'unità separata. + +Per coerenza, consigliamo di utilizzare: +- Singolare per namespace che rappresentano un'unità funzionale (anche se lavorano con più entità) +- Plurale per collezioni di unità separate +- In caso di incertezza o se non vuoi pensarci, scegli il singolare + + +Directory pubblica `www/` +========================= + +Questa directory è l'unica accessibile dal web (la cosiddetta document-root). Spesso si può incontrare anche il nome `public/` invece di `www/` - è solo una questione di convenzione e non influisce sulla funzionalità del framework. La directory contiene: +- [Punto di ingresso |bootstrapping#index.php] dell'applicazione `index.php` +- File `.htaccess` con regole per mod_rewrite (per Apache) +- File statici (CSS, JavaScript, immagini) +- File caricati + +Per una corretta sicurezza dell'applicazione, è fondamentale avere la [document-root configurata correttamente |nette:troubleshooting#Come modificare o rimuovere la directory www dall URL]. + +.[note] +Non posizionare mai la cartella `node_modules/` in questa directory - contiene migliaia di file che possono essere eseguibili e non dovrebbero essere accessibili pubblicamente. + + +Directory dell'applicazione `app/` +================================== + +Questa è la directory principale con il codice dell'applicazione. Struttura di base: + +/--pre +app/ +├── Core/ ← questioni infrastrutturali +├── Model/ ← logica di business +├── Presentation/ ← presenter e template +├── Tasks/ ← script di comando +└── Bootstrap.php ← classe di avvio dell'applicazione +\-- + +`Bootstrap.php` è la [classe di avvio dell'applicazione|bootstrapping], che inizializza l'ambiente, carica la configurazione e crea il container DI. + +Vediamo ora più nel dettaglio le singole sottodirectory. + + +Presenter e template +==================== + +La parte di presentazione dell'applicazione si trova nella directory `app/Presentation`. Un'alternativa è la breve `app/UI`. È il posto per tutti i presenter, i loro template e eventuali classi di supporto. + +Organizziamo questo layer per domini. In un progetto complesso che combina e-shop, blog e API, la struttura sarebbe simile a questa: + +/--pre +app/Presentation/ +├── Shop/ ← frontend e-shop +│ ├── Product/ +│ ├── Cart/ +│ └── Order/ +├── Blog/ ← blog +│ ├── Home/ +│ └── Post/ +├── Admin/ ← amministrazione +│ ├── Dashboard/ +│ └── Products/ +└── Api/ ← endpoint API + └── V1/ +\-- + +Al contrario, per un semplice blog, useremmo la seguente suddivisione: + +/--pre +app/Presentation/ +├── Front/ ← frontend del sito +│ ├── Home/ +│ └── Post/ +├── Admin/ ← amministrazione +│ ├── Dashboard/ +│ └── Posts/ +├── Error/ +└── Export/ ← RSS, sitemap, ecc. +\-- + +Cartelle come `Home/` o `Dashboard/` contengono presenter e template. Cartelle come `Front/`, `Admin/` o `Api/` le chiamiamo **moduli**. Tecnicamente, sono directory normali che servono a dividere logicamente l'applicazione. + +Ogni cartella con un presenter contiene un presenter con lo stesso nome e i suoi template. Ad esempio, la cartella `Dashboard/` contiene: + +/--pre +Dashboard/ +├── DashboardPresenter.php ← presenter +└── default.latte ← template +\-- + +Questa struttura di directory si riflette nei namespace delle classi. Ad esempio, `DashboardPresenter` si trova nel namespace `App\Presentation\Admin\Dashboard` (vedi [#Mappatura dei presenter]): + +```php +namespace App\Presentation\Admin\Dashboard; + +class DashboardPresenter extends Nette\Application\UI\Presenter +{ + // ... +} +``` + +Al presenter `Dashboard` all'interno del modulo `Admin` facciamo riferimento nell'applicazione usando la notazione con i due punti come `Admin:Dashboard`. Alla sua azione `default` poi come `Admin:Dashboard:default`. In caso di moduli nidificati, usiamo più due punti, ad esempio `Shop:Order:Detail:default`. + + +Sviluppo flessibile della struttura +----------------------------------- + +Uno dei grandi vantaggi di questa struttura è come si adatta elegantemente alle crescenti esigenze del progetto. Prendiamo come esempio la parte che genera feed XML. All'inizio abbiamo una forma semplice: + +/--pre +Export/ +├── ExportPresenter.php ← un presenter per tutte le esportazioni +├── sitemap.latte ← template per la sitemap +└── feed.latte ← template per il feed RSS +\-- + +Con il tempo, si aggiungono altri tipi di feed e abbiamo bisogno di più logica per essi... Nessun problema! La cartella `Export/` diventa semplicemente un modulo: + +/--pre +Export/ +├── Sitemap/ +│ ├── SitemapPresenter.php +│ └── sitemap.latte +└── Feed/ + ├── FeedPresenter.php + ├── zbozi.latte ← feed per Zboží.cz + └── heureka.latte ← feed per Heureka.cz +\-- + +Questa trasformazione è assolutamente fluida - basta creare nuove sottocartelle, dividerci il codice e aggiornare i link (ad esempio da `Export:feed` a `Export:Feed:zbozi`). Grazie a ciò, possiamo espandere gradualmente la struttura secondo necessità, il livello di nidificazione non è limitato in alcun modo. + +Se, ad esempio, nell'amministrazione hai molti presenter relativi alla gestione degli ordini, come sono `OrderDetail`, `OrderEdit`, `OrderDispatch` ecc., puoi creare un modulo (cartella) `Order` in questo punto per una migliore organizzazione, che conterrà (le cartelle per) i presenter `Detail`, `Edit`, `Dispatch` e altri. + + +Posizionamento dei template +--------------------------- + +Negli esempi precedenti abbiamo visto che i template si trovano direttamente nella cartella con il presenter: + +/--pre +Dashboard/ +├── DashboardPresenter.php ← presenter +├── DashboardTemplate.php ← classe opzionale per il template +└── default.latte ← template +\-- + +Questa posizione si rivela in pratica la più comoda - hai tutti i file correlati subito a portata di mano. + +In alternativa, puoi posizionare i template in una sottocartella `templates/`. Nette supporta entrambe le varianti. Puoi persino posizionare i template completamente al di fuori della cartella `Presentation/`. Tutto sulle possibilità di posizionamento dei template si trova nel capitolo [Ricerca dei template |templates#Ricerca dei template]. + + +Classi di supporto e componenti +------------------------------- + +Ai presenter e ai template spesso appartengono anche altri file di supporto. Li posizioniamo logicamente in base al loro ambito di applicazione: + +1. **Direttamente presso il presenter** nel caso di componenti specifici per quel presenter: + +/--pre +Product/ +├── ProductPresenter.php +├── ProductGrid.php ← componente per l'elenco dei prodotti +└── FilterForm.php ← form per il filtraggio +\-- + +2. **Per il modulo** - consigliamo di utilizzare la cartella `Accessory`, che si posiziona ordinatamente all'inizio dell'alfabeto: + +/--pre +Front/ +├── Accessory/ +│ ├── NavbarControl.php ← componenti per il frontend +│ └── TemplateFilters.php +├── Product/ +└── Cart/ +\-- + +3. **Per l'intera applicazione** - in `Presentation/Accessory/`: +/--pre +app/Presentation/ +├── Accessory/ +│ ├── LatteExtension.php +│ └── TemplateFilters.php +├── Front/ +└── Admin/ +\-- + +Oppure puoi posizionare classi di supporto come `LatteExtension.php` o `TemplateFilters.php` nella cartella infrastrutturale `app/Core/Latte/`. E i componenti in `app/Components`. La scelta dipende dalle abitudini del team. + + +Model - il cuore dell'applicazione +================================== + +Il model contiene tutta la logica di business dell'applicazione. Per la sua organizzazione vale di nuovo la regola - strutturiamo per domini: + +/--pre +app/Model/ +├── Payment/ ← tutto ciò che riguarda i pagamenti +│ ├── PaymentFacade.php ← punto di ingresso principale +│ ├── PaymentRepository.php +│ ├── Payment.php ← entità +├── Order/ ← tutto ciò che riguarda gli ordini +│ ├── OrderFacade.php +│ ├── OrderRepository.php +│ ├── Order.php +└── Shipping/ ← tutto ciò che riguarda la spedizione +\-- + +Nel model si incontrano tipicamente questi tipi di classi: + +**Facade**: rappresentano il punto di ingresso principale a un dominio specifico nell'applicazione. Agiscono come orchestratori che coordinano la collaborazione tra diversi servizi allo scopo di implementare use-case completi (come "crea ordine" o "elabora pagamento"). Sotto il suo layer di orchestrazione, la facade nasconde i dettagli implementativi al resto dell'applicazione, fornendo così un'interfaccia pulita per lavorare con il dominio dato. + +```php +class OrderFacade +{ + public function createOrder(Cart $cart): Order + { + // validazione + // creazione dell'ordine + // invio dell'e-mail + // scrittura nelle statistiche + } +} +``` + +**Servizi**: si concentrano su un'operazione di business specifica all'interno del dominio. A differenza della facade, che orchestra interi use-case, un servizio implementa una logica di business specifica (come calcoli di prezzi o elaborazione di pagamenti). I servizi sono tipicamente senza stato e possono essere utilizzati sia dalle facade come blocchi di costruzione per operazioni più complesse, sia direttamente da altre parti dell'applicazione per compiti più semplici. + +```php +class PricingService +{ + public function calculateTotal(Order $order): Money + { + // calcolo del prezzo + } +} +``` + +**Repository**: assicurano tutta la comunicazione con l'archivio dati, tipicamente un database. Il suo compito è caricare e salvare entità e implementare metodi per la loro ricerca. Il repository isola il resto dell'applicazione dai dettagli implementativi del database e fornisce un'interfaccia orientata agli oggetti per lavorare con i dati. + +```php +class OrderRepository +{ + public function find(int $id): ?Order + { + } + + public function findByCustomer(int $customerId): array + { + } +} +``` + +**Entità**: oggetti che rappresentano i principali concetti di business nell'applicazione, che hanno una loro identità e cambiano nel tempo. Tipicamente si tratta di classi mappate su tabelle di database tramite ORM (come Nette Database Explorer o Doctrine). Le entità possono contenere regole di business relative ai loro dati e logica di validazione. + +```php +// Entità mappata sulla tabella di database orders +class Order extends Nette\Database\Table\ActiveRow +{ + public function addItem(Product $product, int $quantity): void + { + $this->related('order_items')->insert([ + 'product_id' => $product->id, + 'quantity' => $quantity, + 'unit_price' => $product->price, + ]); + } +} +``` + +**Value object**: oggetti immutabili che rappresentano valori senza una propria identità - ad esempio un importo monetario o un indirizzo e-mail. Due istanze di un value object con gli stessi valori sono considerate identiche. + + +Codice infrastrutturale +======================= + +La cartella `Core/` (o anche `Infrastructure/`) è la casa della base tecnica dell'applicazione. Il codice infrastrutturale include tipicamente: + +/--pre +app/Core/ +├── Router/ ← routing e gestione URL +│ └── RouterFactory.php +├── Security/ ← autenticazione e autorizzazione +│ ├── Authenticator.php +│ └── Authorizator.php +├── Logging/ ← logging e monitoraggio +│ ├── SentryLogger.php +│ └── FileLogger.php +├── Cache/ ← layer di caching +│ └── FullPageCache.php +└── Integration/ ← integrazione con servizi est. + ├── Slack/ + └── Stripe/ +\-- + +Per progetti più piccoli, ovviamente, basta una suddivisione piatta: + +/--pre +Core/ +├── RouterFactory.php +├── Authenticator.php +└── QueueMailer.php +\-- + +Si tratta di codice che: + +- Risolve l'infrastruttura tecnica (routing, logging, caching) +- Integra servizi esterni (Sentry, Elasticsearch, Redis) +- Fornisce servizi di base per l'intera applicazione (mail, database) +- È per lo più indipendente dal dominio specifico - la cache o il logger funzionano allo stesso modo per un eshop o un blog. + +Hai dubbi se una certa classe appartiene qui o al model? La differenza chiave è che il codice in `Core/`: + +- Non sa nulla del dominio (prodotti, ordini, articoli) +- È per lo più possibile trasferirlo a un altro progetto +- Risolve "come funziona" (come inviare una mail), non "cosa fa" (quale mail inviare) + +Esempio per una migliore comprensione: + +- `App\Core\MailerFactory` - crea istanze della classe per l'invio di e-mail, gestisce le impostazioni SMTP +- `App\Model\OrderMailer` - utilizza `MailerFactory` per inviare e-mail sugli ordini, conosce i loro template e sa quando devono essere inviati + + +Script di comando +================= + +Le applicazioni spesso necessitano di eseguire attività al di fuori delle normali richieste HTTP - che si tratti di elaborazione dati in background, manutenzione o attività periodiche. Per l'esecuzione servono semplici script nella directory `bin/`, la logica implementativa la posizioniamo poi in `app/Tasks/` (eventualmente `app/Commands/`). + +Esempio: + +/--pre +app/Tasks/ +├── Maintenance/ ← script di manutenzione +│ ├── CleanupCommand.php ← cancellazione di dati vecchi +│ └── DbOptimizeCommand.php ← ottimizzazione del database +├── Integration/ ← integrazione con sistemi esterni +│ ├── ImportProducts.php ← importazione dal sistema del fornitore +│ └── SyncOrders.php ← sincronizzazione degli ordini +└── Scheduled/ ← attività regolari + ├── NewsletterCommand.php ← invio di newsletter + └── ReminderCommand.php ← notifiche ai clienti +\-- + +Cosa appartiene al model e cosa agli script di comando? Ad esempio, la logica per l'invio di una singola e-mail fa parte del model, l'invio massivo di migliaia di e-mail appartiene già a `Tasks/`. + +Le attività vengono solitamente [eseguite dalla riga di comando |https://blog.nette.org/en/cli-scripts-in-nette-application] o tramite cron. Possono essere eseguite anche tramite richiesta HTTP, ma è necessario pensare alla sicurezza. Il presenter che avvia l'attività deve essere protetto, ad esempio solo per utenti loggati o con un token forte e accesso da indirizzi IP consentiti. Per attività lunghe è necessario aumentare il limite di tempo dello script e utilizzare `session_write_close()`, per non bloccare la sessione. + + +Altre possibili directory +========================= + +Oltre alle directory di base menzionate, puoi aggiungere altre cartelle specializzate in base alle esigenze del progetto. Vediamo le più comuni e il loro utilizzo: + +/--pre +app/ +├── Api/ ← logica per API indipendente dal layer di presentazione +├── Database/ ← script di migrazione e seeder per dati di test +├── Components/ ← componenti visivi condivisi in tutta l'applicazione +├── Event/ ← utile se usi architettura event-driven +├── Mail/ ← template e-mail e logica correlata +└── Utils/ ← classi di utilità +\-- + +Per i componenti visivi condivisi utilizzati nei presenter in tutta l'applicazione, è possibile utilizzare la cartella `app/Components` o `app/Controls`: + +/--pre +app/Components/ +├── Form/ ← componenti form condivisi +│ ├── SignInForm.php +│ └── UserForm.php +├── Grid/ ← componenti per elenchi di dati +│ └── DataGrid.php +└── Navigation/ ← elementi di navigazione + ├── Breadcrumbs.php + └── Menu.php +\-- + +Qui appartengono i componenti che hanno una logica più complessa. Se vuoi condividere componenti tra più progetti, è consigliabile estrarli in un pacchetto composer separato. + +Nella directory `app/Mail` puoi posizionare la gestione della comunicazione e-mail: + +/--pre +app/Mail/ +├── templates/ ← template e-mail +│ ├── order-confirmation.latte +│ └── welcome.latte +└── OrderMailer.php +\-- + + +Mappatura dei presenter +======================= + +La mappatura definisce le regole per derivare il nome della classe dal nome del presenter. Le specifichiamo nella [configurazione|configuration] sotto la chiave `application › mapping`. + +In questa pagina abbiamo mostrato che posizioniamo i presenter nella cartella `app/Presentation` (eventualmente `app/UI`). Dobbiamo comunicare questa convenzione a Nette nel file di configurazione. Basta una riga: + +```neon +application: + mapping: App\Presentation\*\**Presenter +``` + +Come funziona la mappatura? Per una migliore comprensione, immaginiamo prima un'applicazione senza moduli. Vogliamo che le classi dei presenter rientrino nel namespace `App\Presentation`, in modo che il presenter `Home` si mappi sulla classe `App\Presentation\HomePresenter`. Cosa che otteniamo con questa configurazione: + +```neon +application: + mapping: App\Presentation\*Presenter +``` + +La mappatura funziona in modo che il nome del presenter `Home` sostituisca l'asterisco nella maschera `App\Presentation\*Presenter`, ottenendo così il nome della classe risultante `App\Presentation\HomePresenter`. Semplice! + +Come però vedi negli esempi in questo e altri capitoli, posizioniamo le classi dei presenter in sottodirectory omonime, ad esempio il presenter `Home` si mappa sulla classe `App\Presentation\Home\HomePresenter`. Otteniamo ciò raddoppiando i due punti (richiede Nette Application 3.2): + +```neon +application: + mapping: App\Presentation\**Presenter +``` + +Ora passiamo alla mappatura dei presenter nei moduli. Per ogni modulo possiamo definire una mappatura specifica: + +```neon +application: + mapping: + Front: App\Presentation\Front\**Presenter + Admin: App\Presentation\Admin\**Presenter + Api: App\Api\*Presenter +``` + +Secondo questa configurazione, il presenter `Front:Home` si mappa sulla classe `App\Presentation\Front\Home\HomePresenter`, mentre il presenter `Api:OAuth` sulla classe `App\Api\OAuthPresenter`. + +Poiché i moduli `Front` e `Admin` hanno un modo simile di mappatura e probabilmente ci saranno più moduli di questo tipo, è possibile creare una regola generale che li sostituisca. Alla maschera della classe si aggiunge così un nuovo asterisco per il modulo: + +```neon +application: + mapping: + *: App\Presentation\*\**Presenter + Api: App\Api\*Presenter +``` + +Funziona anche per strutture di directory più profondamente nidificate, come ad esempio il presenter `Admin:User:Edit`, il segmento con l'asterisco si ripete per ogni livello e il risultato è la classe `App\Presentation\Admin\User\Edit\EditPresenter`. + +Una notazione alternativa è usare un array composto da tre segmenti invece di una stringa. Questa notazione è equivalente alla precedente: + +```neon +application: + mapping: + *: [App\Presentation, *, **Presenter] + Api: [App\Api, '', *Presenter] +``` diff --git a/application/it/how-it-works.texy b/application/it/how-it-works.texy index aa331da3c3..5231df6437 100644 --- a/application/it/how-it-works.texy +++ b/application/it/how-it-works.texy @@ -3,99 +3,101 @@ Come funzionano le applicazioni?
    -State leggendo il documento di base della documentazione di Nette. Imparerete tutti i principi delle applicazioni web. Nizza dalla A alla Z, dal momento della nascita fino all'ultimo respiro dello script PHP. Dopo la lettura saprete che: +Stai leggendo il documento fondamentale della documentazione di Nette. Imparerai l'intero principio di funzionamento delle applicazioni web. Dalla A alla Z, dal momento della nascita fino all'ultimo respiro dello script PHP. Dopo aver letto, saprai: -- come funziona tutto -- cosa sono Bootstrap, Presenter e DI container -- come si presenta la struttura delle directory +- come funziona il tutto +- cos'è Bootstrap, Presenter e il container DI +- come appare la struttura delle directory
    -Struttura della directory .[#toc-directory-structure] -===================================================== +Struttura delle directory +========================= -Aprite un esempio scheletrico di applicazione web chiamata [WebProject |https://github.com/nette/web-project] e potete osservare i file che vengono scritti. +Apri l'esempio dello scheletro dell'applicazione web chiamato [WebProject|https://github.com/nette/web-project] e mentre leggi, puoi guardare i file di cui si parla. -La struttura delle directory è simile a questa: +La struttura delle directory assomiglia a qualcosa del genere: /--pre web-project/ -├── app/ ← directory with application -│ ├── Presenters/ ← presenter classes -│ │ ├── HomePresenter.php ← Home presenter class -│ │ └── templates/ ← templates directory -│ │ ├── @layout.latte ← template of shared layout -│ │ └── Home/ ← templates for Home presenter -│ │ └── default.latte ← template for action `default` -│ ├── Router/ ← configuration of URL addresses -│ └── Bootstrap.php ← booting class Bootstrap -├── bin/ ← scripts for the command line -├── config/ ← configuration files +├── app/ ← directory con l'applicazione +│ ├── Core/ ← classi di base necessarie per il funzionamento +│ │ └── RouterFactory.php ← configurazione degli indirizzi URL +│ ├── Presentation/ ← presenter, template & co. +│ │ ├── @layout.latte ← template del layout +│ │ └── Home/ ← directory del presenter Home +│ │ ├── HomePresenter.php ← classe del presenter Home +│ │ └── default.latte ← template dell'azione default +│ └── Bootstrap.php ← classe di avvio Bootstrap +├── assets/ ← risorse (SCSS, TypeScript, immagini sorgente) +├── bin/ ← script eseguiti dalla riga di comando +├── config/ ← file di configurazione │ ├── common.neon -│ └── local.neon -├── log/ ← error logs -├── temp/ ← temporary files, cache, … -├── vendor/ ← libraries installed by Composer +│ └── services.neon +├── log/ ← errori registrati +├── temp/ ← file temporanei, cache, … +├── vendor/ ← librerie installate da Composer │ ├── ... -│ └── autoload.php ← autoloading of libs installed by Composer -├── www/ ← public directory, document root of project -│ ├── .htaccess ← mod_rewrite rules etc -│ └── index.php ← initial file that launches the application -└── .htaccess ← prohibits access to all directories except www +│ └── autoload.php ← autoloading di tutti i pacchetti installati +├── www/ ← directory pubblica o document-root del progetto +│ ├── assets/ ← file statici compilati (CSS, JS, immagini, ...) +│ ├── .htaccess ← regole mod_rewrite +│ └── index.php ← file iniziale con cui si avvia l'applicazione +└── .htaccess ← vieta l'accesso a tutte le directory tranne www \-- -È possibile modificare la struttura delle directory in qualsiasi modo, rinominare o spostare cartelle, e quindi modificare semplicemente i percorsi di `log/` e `temp/` nel file `Bootstrap.php` e il percorso di questo file in `composer.json` nella sezione `autoload`. Niente di più, nessuna riconfigurazione complicata, nessuna modifica costante. Nette ha un [rilevamento automatico intelligente |bootstrap#development-vs-production-mode]. +Puoi modificare la struttura delle directory come preferisci, rinominare o spostare le cartelle, è completamente flessibile. Nette dispone inoltre di un intelligente autodetect e riconosce automaticamente la posizione dell'applicazione, inclusa la sua base URL. -Per applicazioni un po' più grandi, possiamo dividere le cartelle con presentatori e modelli in sottodirectory (su disco) e in spazi di nomi (nel codice), che chiamiamo [moduli |modules]. +Per applicazioni un po' più grandi, possiamo [dividere le cartelle con presenter e template in sottodirectory |directory-structure#Presenter e template] e le classi in namespace, che chiamiamo moduli. -La cartella `www/` è la cartella pubblica o document-root del progetto. È possibile rinominarla senza dover impostare nient'altro sul lato dell'applicazione. È sufficiente [configurare l'hosting |nette:troubleshooting#How to change or remove www directory from URL] in modo che la radice dei documenti vada a questa cartella. +La directory `www/` rappresenta la cosiddetta directory pubblica o document-root del progetto. Puoi rinominarla senza dover configurare nient'altro lato applicazione. È solo necessario [configurare l'hosting |nette:troubleshooting#Come modificare o rimuovere la directory www dall URL] in modo che la document-root punti a questa directory. -È anche possibile scaricare direttamente il progetto Web, compreso Nette, utilizzando [Composer |best-practices:composer]: +Puoi anche scaricare direttamente WebProject incluso Nette usando [Composer |best-practices:composer]: ```shell composer create-project nette/web-project ``` -Su Linux o macOS, impostare i [permessi di scrittura |nette:troubleshooting#Setting directory permissions] per le directory `log/` e `temp/`. +Su Linux o macOS, imposta i [permessi di scrittura |nette:troubleshooting#Impostazione dei permessi delle directory] per le directory `log/` e `temp/`. -L'applicazione WebProject è pronta per essere eseguita, non è necessario configurare nient'altro ed è possibile visualizzarla direttamente nel browser accedendo alla cartella `www/`. +L'applicazione WebProject è pronta per essere eseguita, non è necessario configurare assolutamente nulla e puoi visualizzarla direttamente nel browser accedendo alla cartella `www/`. -Richiesta HTTP .[#toc-http-request] -=================================== +Richiesta HTTP +============== -Tutto inizia quando un utente apre la pagina in un browser e il browser bussa al server con una richiesta HTTP. La richiesta va a un file PHP situato nella directory pubblica `www/`, che è `index.php`. Supponiamo che si tratti di una richiesta a `https://example.com/product/123` e verrà eseguito. +Tutto inizia nel momento in cui l'utente apre una pagina nel browser. Cioè quando il browser bussa al server con una richiesta HTTP. La richiesta punta a un unico file PHP, che si trova nella directory pubblica `www/`, e questo è `index.php`. Supponiamo che si tratti di una richiesta all'indirizzo `https://example.com/product/123`. Grazie a un'adeguata [configurazione del server |nette:troubleshooting#Come configurare il server per URL leggibili] anche questo URL viene mappato sul file `index.php` e questo viene eseguito. Il suo compito è: 1) inizializzare l'ambiente -2) ottenere la fabbrica -3) lanciare l'applicazione Nette che gestisce la richiesta +2) ottenere la factory +3) avviare l'applicazione Nette, che gestirà la richiesta -Che tipo di fabbrica? Non produciamo trattori, ma siti web! Aspettate, vi sarà spiegato subito. +Quale factory? Non produciamo trattori, ma pagine web! Aspetta, si chiarirà subito. -Per "inizializzare l'ambiente" intendiamo, ad esempio, che venga attivato [Tracy |tracy:], uno strumento straordinario per la registrazione o la visualizzazione degli errori. Registra gli errori sul server di produzione e li visualizza direttamente sul server di sviluppo. Pertanto, l'inizializzazione deve anche decidere se il sito è in modalità di produzione o di sviluppo. A tale scopo, Nette utilizza il rilevamento automatico: se il sito viene eseguito su localhost, viene eseguito in modalità sviluppatore. Non è necessario configurare nulla e l'applicazione è pronta per lo sviluppo e la produzione. Questi passaggi sono eseguiti e descritti in dettaglio nel capitolo sulla [classe Bootstrap |bootstrap]. +Con "inizializzazione dell'ambiente" intendiamo, ad esempio, che viene attivato [Tracy|tracy:], che è uno strumento fantastico per il logging o la visualizzazione degli errori. Sul server di produzione registra gli errori, su quello di sviluppo li visualizza direttamente. Quindi l'inizializzazione include anche la decisione se il sito web è in esecuzione in modalità produzione o sviluppo. Per questo Nette utilizza un [intelligente autodetect |bootstrapping#Modalità Sviluppo vs Produzione]: se avvii il sito web su localhost, viene eseguito in modalità sviluppo. Non devi quindi configurare nulla e l'applicazione è subito pronta sia per lo sviluppo che per la distribuzione in produzione. Questi passaggi vengono eseguiti e sono descritti in dettaglio nel capitolo sulla [classe Bootstrap|bootstrapping]. -Il terzo punto (sì, abbiamo saltato il secondo, ma ci torneremo) è l'avvio dell'applicazione. La gestione delle richieste HTTP in Nette è affidata alla classe `Nette\Application\Application` (di seguito denominata `Application`), quindi quando diciamo "avviare un'applicazione", intendiamo chiamare un metodo con il nome `run()` su un oggetto di questa classe. +Il terzo punto (sì, abbiamo saltato il secondo, ma ci torneremo) è l'avvio dell'applicazione. La gestione delle richieste HTTP in Nette è affidata alla classe `Nette\Application\Application` (di seguito `Application`), quindi quando diciamo avviare l'applicazione, intendiamo specificamente chiamare il metodo con il nome appropriato `run()` sull'oggetto di questa classe. -Nette è un mentore che vi guida nella scrittura di applicazioni pulite attraverso metodologie collaudate. La più collaudata si chiama **dependency injection**, abbreviata DI. Al momento non vogliamo appesantirvi con la spiegazione di DI, dato che c'è un [capitolo a parte |dependency-injection:introduction], la cosa importante è che gli oggetti chiave saranno solitamente creati da un factory per oggetti chiamato **DI container** (abbreviato DIC). Sì, questo è il factory di cui si è parlato poco fa. Crea anche l'oggetto `Application` per noi, quindi abbiamo bisogno di un contenitore. Lo otteniamo utilizzando la classe `Configurator` e facciamo in modo che produca l'oggetto `Application`, chiamando il metodo `run()` e avviando così l'applicazione Nette. Questo è esattamente ciò che accade nel file [index.php |bootstrap#index.php]. +Nette è un mentore che ti guida a scrivere applicazioni pulite secondo metodologie comprovate. E una di quelle assolutamente più comprovate si chiama **dependency injection**, abbreviata in DI. In questo momento non vogliamo appesantirti con la spiegazione della DI, per questo c'è un [capitolo separato|dependency-injection:introduction], l'importante è la conseguenza che gli oggetti chiave ci verranno solitamente creati da una factory di oggetti, chiamata **container DI** (abbreviato in DIC). Sì, è quella factory di cui si parlava poco fa. E ci produrrà anche l'oggetto `Application`, perciò abbiamo prima bisogno del container. Lo otteniamo tramite la classe `Configurator` e gli facciamo produrre l'oggetto `Application`, chiamiamo su di esso il metodo `run()` e così si avvia l'applicazione Nette. Esattamente questo accade nel file [index.php |bootstrapping#index.php]. -Applicazione Nette .[#toc-nette-application] -============================================ +Nette Application +================= -La classe Application ha un unico compito: rispondere a una richiesta HTTP. +La classe Application ha un unico compito: rispondere alla richiesta HTTP. -Le applicazioni scritte in Nette sono suddivise in molti cosiddetti presenter (in altri framework si può incontrare il termine controller, che è lo stesso), che sono classi che rappresentano una specifica pagina del sito web: ad esempio, la homepage; un prodotto nell'e-shop; il modulo di iscrizione; il feed della sitemap, ecc. L'applicazione può avere da uno a migliaia di presenter. +Le applicazioni scritte in Nette si dividono in molti cosiddetti presenter (in altri framework potresti incontrare il termine controller, è la stessa cosa), che sono classi, ognuna delle quali rappresenta una specifica pagina del sito web: ad esempio homepage; prodotto in un e-shop; modulo di login; feed sitemap ecc. Un'applicazione può avere da uno a migliaia di presenter. -L'applicazione inizia chiedendo al cosiddetto router di decidere a quale dei presenter passare la richiesta corrente per l'elaborazione. Il router decide di chi è la responsabilità. Osserva l'URL di ingresso `https://example.com/product/123`, che vuole `show` un prodotto con `id: 123` come azione. È buona abitudine scrivere le coppie presentatore + azione separate da due punti come `Product:show`. +Application inizia chiedendo al cosiddetto router di decidere a quale dei presenter passare la richiesta corrente per la gestione. Il router decide di chi è la responsabilità. Guarda l'URL di input `https://example.com/product/123` e in base a come è impostato, decide che questo è il lavoro, ad esempio, per il **presenter** `Product`, dal quale vorrà come **azione** la visualizzazione (`show`) del prodotto con `id: 123`. È buona abitudine scrivere la coppia presenter + azione separata da due punti come `Product:show`. -Quindi il router ha trasformato l'URL in una coppia `Presenter:action` + parametri, nel nostro caso `Product:show` + `id: 123`. Si può vedere l'aspetto di un router nel file `app/Router/RouterFactory.php` e lo descriveremo in dettaglio nel capitolo [Routing |Routing]. +Quindi il router ha trasformato l'URL nella coppia `Presenter:action` + parametri, nel nostro caso `Product:show` + `id: 123`. Come appare un tale router puoi vederlo nel file `app/Core/RouterFactory.php` e lo descriviamo in dettaglio nel capitolo [Routing]. -Andiamo avanti. L'applicazione conosce già il nome del presentatore e può continuare. Creando un oggetto `ProductPresenter`, che è il codice del presentatore `Product`. Più precisamente, chiede al contenitore DI di creare il presentatore, perché produrre oggetti è il suo lavoro. +Andiamo avanti. Application conosce già il nome del presenter e può continuare. Creando l'oggetto della classe `ProductPresenter`, che è il codice del presenter `Product`. Più precisamente, chiede al container DI di creare il presenter, perché la creazione è compito suo. -Il presentatore potrebbe avere questo aspetto: +Il presenter potrebbe assomigliare a questo: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -107,98 +109,92 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function renderShow(int $id): void { - // otteniamo i dati dal modello e li passiamo al template + // otteniamo i dati dal model e li passiamo al template $this->template->product = $this->repository->getProduct($id); } } ``` -La richiesta viene gestita dal presentatore. E il compito è chiaro: eseguire l'azione `show` con `id: 123`. Che nel linguaggio dei presentatori significa che viene chiamato il metodo `renderShow()` e nel parametro `$id` si ottiene `123`. - -Un presentatore può gestire più azioni, cioè avere più metodi `render()`. Ma si consiglia di progettare presentatori con una sola azione o con il minor numero possibile di azioni. +La gestione della richiesta viene assunta dal presenter. E il compito è chiaro: esegui l'azione `show` con `id: 123`. Che nel linguaggio dei presenter significa che viene chiamato il metodo `renderShow()` e nel parametro `$id` riceve `123`. -Quindi, è stato chiamato il metodo `renderShow(123)`, il cui codice è un esempio fittizio, ma si può vedere come i dati vengono passati al modello, cioè scrivendo su `$this->template`. +Un presenter può gestire più azioni, cioè avere più metodi `render()`. Ma consigliamo di progettare presenter con una o il minor numero possibile di azioni. -Successivamente, il presentatore restituisce la risposta. Questa può essere una pagina HTML, un'immagine, un documento XML, l'invio di un file dal disco, JSON o il reindirizzamento a un'altra pagina. È importante notare che, se non si dice esplicitamente come rispondere (come nel caso di `ProductPresenter`), la risposta sarà il rendering del modello con una pagina HTML. Perché? Perché nel 99% dei casi vogliamo disegnare un modello, quindi il presentatore prende questo comportamento come predefinito e vuole semplificarci il lavoro. Questo è il punto di Nette. +Quindi, è stato chiamato il metodo `renderShow(123)`, il cui codice è un esempio fittizio, ma puoi vedere come vengono passati i dati al template, cioè scrivendo in `$this->template`. -Non dobbiamo nemmeno dichiarare quale modello disegnare, lui ricava il percorso per raggiungerlo secondo una semplice logica. Nel caso del presenter `Product` e dell'azione `show`, cerca di vedere se esiste uno di questi file di template relativi alla directory in cui si trova la classe `ProductPresenter`: +Successivamente, il presenter restituisce una risposta. Questa può essere una pagina HTML, un'immagine, un documento XML, l'invio di un file dal disco, JSON o magari un redirect a un'altra pagina. È importante notare che se non diciamo esplicitamente come deve rispondere (che è il caso di `ProductPresenter`), la risposta sarà il rendering del template con la pagina HTML. Perché? Perché nel 99% dei casi vogliamo renderizzare un template, quindi il presenter considera questo comportamento come predefinito e vuole semplificarci il lavoro. Questo è lo scopo di Nette. -- `templates/Product/show.latte` -- `templates/Product.show.latte` +Non dobbiamo nemmeno specificare quale template renderizzare, ne deduce il percorso da solo. Nel caso dell'azione `show`, prova semplicemente a caricare il template `show.latte` nella directory con la classe `ProductPresenter`. Tenterà anche di trovare il layout nel file `@layout.latte` (maggiori dettagli sulla [ricerca dei template |templates#Ricerca dei template]). -Cercherà anche di trovare il layout nel file `@layout.latte` e quindi eseguirà il rendering del modello. Ora il compito del presentatore e dell'intera applicazione è completato. Se il modello non esiste, verrà restituita una pagina con errore 404. Per saperne di più sui presentatori, consultare la pagina [Presentatori |Presenters]. +E successivamente renderizza i template. Con questo, il compito del presenter e dell'intera applicazione è completato e l'opera è finita. Se il template non esistesse, verrebbe restituita una pagina con errore 404. Maggiori informazioni sui presenter si trovano nella pagina [Presenter|presenters]. [* request-flow.svg *] Per sicurezza, proviamo a riepilogare l'intero processo con un URL leggermente diverso: -1) l'URL sarà `https://example.com` -2) si avvia l'applicazione, si crea un contenitore e si esegue `Application::run()` -3) il router decodifica l'URL come una coppia di oggetti `Home:default` -4) viene creato un oggetto `HomePresenter` -5) viene richiamato il metodo `renderDefault()` (se esiste) -6) viene reso un modello `templates/Home/default.latte` con un layout `templates/@layout.latte` +1) L'URL sarà `https://example.com` +2) avviamo l'applicazione, viene creato il container e viene eseguito `Application::run()` +3) il router decodifica l'URL come coppia `Home:default` +4) viene creato l'oggetto della classe `HomePresenter` +5) viene chiamato il metodo `renderDefault()` (se esiste) +6) viene renderizzato il template ad es. `default.latte` con il layout ad es. `@layout.latte` -Potreste esservi imbattuti in molti concetti nuovi, ma crediamo che abbiano un senso. Creare applicazioni in Nette è un gioco da ragazzi. +Potresti aver incontrato molti nuovi concetti ora, ma crediamo che abbiano senso. Creare applicazioni in Nette è un'enorme comodità. -Modelli .[#toc-templates] -========================= +Template +======== -Per quanto riguarda i modelli, Nette utilizza il sistema di modelli [Latte |latte:]. Per questo motivo i file con i template terminano con `.latte`. Latte viene utilizzato perché è il sistema di template più sicuro per PHP e allo stesso tempo il più intuitivo. Non è necessario imparare molto di nuovo, basta conoscere PHP e qualche tag di Latte. Troverete tutto [nella documentazione |latte:]. +Dato che abbiamo menzionato i template, in Nette si utilizza il sistema di templating [Latte |latte:]. Questo perché è il sistema di templating più sicuro per PHP, e allo stesso tempo il sistema più intuitivo. Non devi imparare molto di nuovo, ti basta la conoscenza di PHP e alcuni tag. Tutto si trova [nella documentazione |templates]. -Nel template [creiamo un collegamento |creating-links] ad altri presentatori e azioni come segue: +Nel template si [creano link |creating-links] ad altri presenter & azioni in questo modo: ```latte -product detail +dettaglio prodotto ``` -Basta scrivere la nota coppia `Presenter:action` al posto del vero URL e includere eventuali parametri. Il trucco è `n:href`, che dice che questo attributo sarà elaborato da Nette. E genererà: +Semplicemente, invece dell'URL reale, scrivi la coppia nota `Presenter:action` e specifichi eventuali parametri. Il trucco sta in `n:href`, che dice che questo attributo sarà elaborato da Nette. E genererà: ```latte -product detail +dettaglio prodotto ``` -Il router già citato è incaricato di generare l'URL. In effetti, i router di Nette sono unici in quanto possono eseguire non solo trasformazioni da un URL a una coppia presenter:action, ma anche viceversa generare un URL a partire dal nome del presenter + action + parametri. -Grazie a questo, in Nette è possibile cambiare completamente la forma dell'URL in tutta l'applicazione finita senza cambiare un solo carattere nel template o nel presenter, semplicemente modificando il router. -E grazie a questo, funziona la cosiddetta canonizzazione, un'altra caratteristica unica di Nette, che migliora il SEO (ottimizzazione della ricercabilità su Internet) impedendo automaticamente l'esistenza di contenuti duplicati in URL diversi. -Molti programmatori lo trovano sorprendente. +La generazione dell'URL è gestita dal router menzionato in precedenza. Infatti, i router in Nette sono eccezionali perché possono eseguire non solo trasformazioni da URL a coppia presenter:action, ma anche viceversa, cioè generare un URL dal nome del presenter + azione + parametri. Grazie a ciò, in Nette puoi cambiare completamente le forme degli URL in tutta l'applicazione finita, senza cambiare un singolo carattere nel template o nel presenter. Semplicemente modificando il router. Grazie a ciò funziona anche la cosiddetta canonizzazione, che è un'altra caratteristica unica di Nette, che contribuisce a un migliore SEO (ottimizzazione della reperibilità su Internet) impedendo automaticamente l'esistenza di contenuti duplicati su URL diversi. Molti programmatori lo trovano sorprendente. -Componenti interattivi .[#toc-interactive-components] -===================================================== +Componenti Interattivi +====================== -C'è un'altra cosa da dire sui presenter: hanno un sistema di componenti integrato. I più anziani ricorderanno qualcosa di simile da Delphi o ASP.NET Web Forms. React o Vue.js si basano su qualcosa di molto simile. Nel mondo dei framework PHP, questa è una caratteristica del tutto unica. +Sui presenter dobbiamo rivelarti ancora una cosa: hanno un sistema di componenti integrato. Qualcosa di simile potrebbe essere familiare ai veterani di Delphi o ASP.NET Web Forms, qualcosa di lontanamente simile è alla base di React o Vue.js. Nel mondo dei framework PHP, è una caratteristica assolutamente unica. -I componenti sono unità separate e riutilizzabili che vengono inserite nelle pagine (cioè nei presenter). Possono essere [moduli |forms:in-presenter], [griglie di dati |https://componette.org/contributte/datagrid/], menu, sondaggi, qualsiasi cosa abbia senso usare ripetutamente. Possiamo creare i nostri componenti o utilizzare una [vasta gamma |https://componette.org] di componenti opensource. +I componenti sono unità riutilizzabili separate che inseriamo nelle pagine (cioè nei presenter). Possono essere [form |forms:in-presenter], [datagrid |https://componette.org/contributte/datagrid/], menu, sondaggi, praticamente qualsiasi cosa che abbia senso usare ripetutamente. Possiamo creare i nostri componenti o usare alcuni dell'[enorme offerta |https://componette.org] di componenti open source. -I componenti cambiano radicalmente l'approccio allo sviluppo delle applicazioni. Apriranno nuove possibilità di comporre pagine a partire da unità predefinite. E hanno qualcosa in comune con [Hollywood |components#Hollywood style]. +I componenti influenzano fondamentalmente l'approccio alla creazione di applicazioni. Ti apriranno nuove possibilità di comporre pagine da unità pre-preparate. E inoltre hanno qualcosa in comune con [Hollywood |components#Stile Hollywood]. -Contenitore e configurazione DI .[#toc-di-container-and-configuration] -====================================================================== +Container DI e Configurazione +============================= -Il contenitore DI (fabbrica di oggetti) è il cuore dell'intera applicazione. +Il container DI o factory di oggetti è il cuore dell'intera applicazione. -Non preoccupatevi, non è una magica scatola nera, come potrebbe sembrare dalle parole precedenti. In realtà, si tratta di una classe PHP piuttosto noiosa, generata da Nette e memorizzata in una cartella di cache. Ha molti metodi chiamati `createServiceAbcd()` e ognuno di essi crea e restituisce un oggetto. Sì, c'è anche un metodo `createServiceApplication()` che produrrà `Nette\Application\Application`, che ci serve nel file `index.php` per eseguire l'applicazione. E ci sono metodi per produrre i singoli presentatori. E così via. +Non preoccuparti, non è una scatola nera magica, come potrebbe sembrare dalle righe precedenti. In realtà, è una classe PHP piuttosto noiosa, che viene generata da Nette e salvata nella directory della cache. Ha molti metodi chiamati come `createServiceAbcd()` e ognuno di essi sa come creare e restituire un oggetto. Sì, c'è anche il metodo `createServiceApplication()`, che crea `Nette\Application\Application`, di cui avevamo bisogno nel file `index.php` per avviare l'applicazione. E ci sono metodi che creano i singoli presenter. E così via. -Gli oggetti che il contenitore DI crea sono chiamati servizi per qualche motivo. +Agli oggetti che il container DI crea, per qualche motivo, si dice servizi. -La particolarità di questa classe è che non è programmata da voi, ma dal framework. Esso genera effettivamente il codice PHP e lo salva su disco. L'utente deve solo dare istruzioni su quali oggetti il contenitore deve essere in grado di produrre e come esattamente. Queste istruzioni sono scritte in [file di configurazione |bootstrap#DI Container Configuration] nel [formato NEON |neon:format] e quindi hanno l'estensione `.neon`. +Ciò che è veramente speciale di questa classe è che non la programmi tu, ma il framework. Genera effettivamente il codice PHP e lo salva su disco. Tu dai solo istruzioni su quali oggetti il container deve saper creare e come esattamente. E queste istruzioni sono scritte nei [file di configurazione |bootstrapping#Configurazione del Container DI], per i quali si usa il formato [NEON|neon:format] e quindi hanno anche l'estensione `.neon`. -I file di configurazione sono usati esclusivamente per istruire il contenitore DI. Così, per esempio, se si specifica l'opzione `expiration: 14 days` nella sezione della [sessione |http:configuration#Session], il contenitore DI, quando crea l'oggetto `Nette\Http\Session` che rappresenta la sessione, chiamerà il suo metodo `setExpiration('14 days')`, e così la configurazione diventa realtà. +I file di configurazione servono puramente a istruire il container DI. Quindi, se ad esempio specifico nella sezione [session |http:configuration#Sessione] l'opzione `expiration: 14 days`, il container DI, durante la creazione dell'oggetto `Nette\Http\Session` che rappresenta la sessione, chiamerà il suo metodo `setExpiration('14 days')` e così la configurazione diventerà realtà. -C'è un intero capitolo pronto per voi, che descrive ciò che può essere [configurato |nette:configuring] e come [definire i vostri servizi |dependency-injection:services]. +C'è un intero capitolo preparato per te che descrive cosa può essere [configurato |nette:configuring] e come [definire i propri servizi |dependency-injection:services]. -Una volta entrati nella creazione dei servizi, ci si imbatte nella parola [autowiring |dependency-injection:autowiring]. Si tratta di un gadget che vi semplificherà incredibilmente la vita. Può passare automaticamente gli oggetti dove servono (nei costruttori delle classi, per esempio) senza dover fare nulla. Scoprirete che il contenitore DI di Nette è un piccolo miracolo. +Non appena ti addentrerai un po' nella creazione di servizi, incontrerai la parola [autowiring |dependency-injection:autowiring]. Questa è una chicca che ti semplificherà la vita in modo incredibile. Sa passare automaticamente gli oggetti dove ne hai bisogno (ad esempio nei costruttori delle tue classi), senza che tu debba fare nulla. Scoprirai che il container DI in Nette è un piccolo miracolo. -E poi? .[#toc-what-next] -======================== +Dove andare dopo? +================= -Abbiamo illustrato i principi di base delle applicazioni in Nette. Finora in modo molto superficiale, ma presto vi addentrerete nelle profondità e creerete meravigliose applicazioni web. Dove continuare? Avete provato il tutorial [Creare la prima applicazione |quickstart:]? +Abbiamo esaminato i principi di base delle applicazioni in Nette. Finora molto superficialmente, ma presto approfondirai e col tempo creerai fantastiche applicazioni web. Dove continuare? Hai già provato il tutorial [Scriviamo la prima applicazione|quickstart:]? -Oltre a quanto sopra, Nette ha un intero arsenale di [classi utili |utils:], [livelli di database |database:], ecc. Provate a cliccare di proposito sulla documentazione. Oppure visitate il [blog |https://blog.nette.org]. Scoprirete molte cose interessanti. +Oltre a quanto descritto sopra, Nette dispone di un intero arsenale di [classi utili|utils:], un [layer di database|database:], ecc. Prova a sfogliare la documentazione solo per curiosità. O il [blog|https://blog.nette.org]. Scoprirai molte cose interessanti. -Lasciate che il framework vi porti tanta gioia 💙 +Che il framework ti porti molta gioia 💙 diff --git a/application/it/modules.texy b/application/it/modules.texy deleted file mode 100644 index 55ee0c0696..0000000000 --- a/application/it/modules.texy +++ /dev/null @@ -1,148 +0,0 @@ -Moduli -****** - -.[perex] -In Nette, i moduli rappresentano le unità logiche che compongono un'applicazione. Comprendono presentatori, modelli, eventualmente anche componenti e classi di modelli. - -Una cartella per i presentatori e una per i modelli non sarebbe sufficiente per i progetti reali. Avere decine di file in una cartella è quantomeno disorganizzato. Come uscirne? Semplicemente dividendoli in sottodirectory su disco e in spazi dei nomi nel codice. E questo è esattamente ciò che fanno i moduli Nette. - -Dimentichiamo quindi un'unica cartella per i presentatori e i modelli e creiamo invece dei moduli, ad esempio `Admin` e `Front`. - -/--pre -app/ -├── Presenters/ -├── Modules/ ← directory with modules -│ ├── Admin/ ← module Admin -│ │ ├── Presenters/ ← its presenters -│ │ │ ├── DashboardPresenter.php -│ │ │ └── templates/ -│ └── Front/ ← module Front -│ └── Presenters/ ← its presenters -│ └── ... -\-- - -Questa struttura di cartelle si rifletterà negli spazi dei nomi delle classi, quindi ad esempio `DashboardPresenter` sarà nello spazio dei nomi `App\Modules\Admin\Presenters`: - -```php -namespace App\Modules\Admin\Presenters; - -class DashboardPresenter extends Nette\Application\UI\Presenter -{ - // ... -} -``` - -Il presentatore `Dashboard` all'interno del modulo `Admin` è referenziato all'interno dell'applicazione usando la notazione dei due punti come `Admin:Dashboard`, e la sua azione `default` come `Admin:Dashboard:default`. -E come fa Nette a sapere che `Admin:Dashboard` rappresenta la classe `App\Modules\Admin\Presenters\DashboardPresenter`? Questo è determinato dalla [mappatura |#mapping] nella configurazione. -Pertanto, la struttura data non è rigida e può essere modificata in base alle proprie esigenze. - -I moduli possono naturalmente contenere tutti gli altri elementi oltre ai presentatori e ai modelli, come componenti, classi di modelli, ecc. - - -Moduli annidati .[#toc-nested-modules] --------------------------------------- - -I moduli non devono formare solo una struttura piatta, ma si possono anche creare sottomoduli, ad esempio: - -/--pre -app/ -├── Modules/ ← directory with modules -│ ├── Blog/ ← module Blog -│ │ ├── Admin/ ← submodule Admin -│ │ │ ├── Presenters/ -│ │ │ └── ... -│ │ └── Front/ ← submodule Front -│ │ ├── Presenters/ -│ │ └── ... -│ ├── Forum/ ← module Forum -│ │ └── ... -\-- - -Così, il modulo `Blog` è suddiviso nei sottomoduli `Admin` e `Front`. Anche in questo caso, ciò si rifletterà negli spazi dei nomi, che saranno `App\Modules\Blog\Admin\Presenters` e così via. Il presentatore `Dashboard` all'interno del sottomodulo viene chiamato `Blog:Admin:Dashboard`. - -L'annidamento può andare in profondità quanto si vuole, quindi si possono creare dei sottomoduli. - - -Creazione di collegamenti .[#toc-creating-links] ------------------------------------------------- - -I collegamenti nei modelli di presentatore sono relativi al modulo corrente. Pertanto, il collegamento `Foo:default` porta al presentatore `Foo` nello stesso modulo del presentatore corrente. Se il modulo corrente è `Front`, ad esempio, il collegamento si presenta in questo modo: - -```latte -link to Front:Product:show -``` - -Un collegamento è relativo anche se include il nome di un modulo, che viene considerato un sottomodulo: - -```latte -link to Front:Shop:Product:show -``` - -I collegamenti assoluti sono scritti in modo analogo ai percorsi assoluti su disco, ma con i due punti al posto degli slash. Pertanto, un collegamento assoluto inizia con i due punti: - -```latte -link to Admin:Product:show -``` - -Per scoprire se ci troviamo in un certo modulo o in un suo sottomodulo possiamo usare la funzione `isModuleCurrent(moduleName)`. - -```latte -
  • - ... -
  • -``` - - -Instradamento .[#toc-routing] ------------------------------ - -Vedere il [capitolo sull'instradamento |routing#Modules]. - - -Mappatura .[#toc-mapping] -------------------------- - -Definisce le regole con cui il nome della classe viene derivato dal nome del presentatore. Vengono scritte nella [configurazione |configuration] sotto la chiave `application › mapping`. - -Cominciamo con un esempio che non usa moduli. Vogliamo solo che le classi del presentatore abbiano lo spazio dei nomi `App\Presenters`. Ciò significa che un presentatore come `Home` deve mappare alla classe `App\Presenters\HomePresenter`. Questo si può ottenere con la seguente configurazione: - -```neon -application: - mapping: - *: App\Presenters\*Presenter -``` - -Il nome del presentatore viene sostituito con l'asterisco nella maschera della classe e il risultato è il nome della classe. Facile! - -Se dividiamo i presentatori in moduli, possiamo avere la nostra mappatura per ogni modulo: - -```neon -application: - mapping: - Front: App\Modules\Front\Presenters\*Presenter - Admin: App\Modules\Admin\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -Ora il presentatore `Front:Home` mappa alla classe `App\Modules\Front\Presenters\HomePresenter` e il presentatore `Admin:Dashboard` alla classe `App\Modules\Admin\Presenters\DashboardPresenter`. - -È più pratico creare una regola generale (asterisco) per sostituire le prime due. L'asterisco in più sarà aggiunto alla maschera di classe solo per il modulo: - -```neon -application: - mapping: - *: App\Modules\*\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -Ma cosa succede se utilizziamo moduli annidati e abbiamo un presentatore `Admin:User:Edit`? In questo caso, il segmento con l'asterisco che rappresenta il modulo per ogni livello viene semplicemente ripetuto e il risultato è la classe `App\Modules\Admin\User\Presenters\EditPresenter`. - -Una notazione alternativa consiste nell'utilizzare un array composto da tre segmenti invece di una stringa. Questa notazione è equivalente alla precedente: - -```neon -application: - mapping: - *: [App\Modules, *, Presenters\*Presenter] -``` - -Il valore predefinito è `*: *Module\*Presenter`. diff --git a/application/it/multiplier.texy b/application/it/multiplier.texy index 653a805e69..4bdb5b42a0 100644 --- a/application/it/multiplier.texy +++ b/application/it/multiplier.texy @@ -1,30 +1,32 @@ -Moltiplicatore: Componenti dinamici -*********************************** +Multiplier: componenti dinamici +******************************* -Uno strumento per la creazione dinamica di componenti interattivi .[perex] +.[perex] +Strumento per la creazione dinamica di componenti interattivi -Cominciamo con un problema tipico: abbiamo un elenco di prodotti su un sito di e-commerce e vogliamo accompagnare ogni prodotto con un modulo *aggiungi al carrello*. Un modo è quello di racchiudere l'intero elenco in un unico modulo. Un modo più comodo è usare [api:Nette\Application\UI\Multiplier]. +Partiamo da un esempio tipico: abbiamo un elenco di prodotti in un e-shop, e per ognuno vogliamo visualizzare un form per aggiungere il prodotto al carrello. Una delle possibili varianti è racchiudere l'intero elenco in un unico form. Tuttavia, un modo molto più comodo ci viene offerto da [api:Nette\Application\UI\Multiplier]. -Multiplier consente di definire un factory per più componenti. Si basa sul principio dei componenti annidati: ogni componente che eredita da [api:Nette\ComponentModel\Container] può contenere altri componenti. +Multiplier consente di definire comodamente una piccola factory per più componenti. Funziona sul principio dei componenti nidificati - ogni componente che eredita da [api:Nette\ComponentModel\Container] può contenere altri componenti. -Vedere il [modello dei componenti |components#Components in Depth] nella documentazione. .[tip] +.[tip] +Vedi il capitolo sul [modello a componenti |components#Componenti in profondità] nella documentazione o la [presentazione di Honza Tvrdík|https://www.youtube.com/watch?v=8y3LLexWu-I]. -Multiplier si pone come un componente genitore che può creare dinamicamente i suoi figli, utilizzando il callback passato nel costruttore. Si veda l'esempio: +L'essenza di Multiplier è che agisce come genitore, che può creare dinamicamente i propri figli tramite un callback passato nel costruttore. Vedi l'esempio: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function () { $form = new Nette\Application\UI\Form; - $form->addInteger('amount', 'Amount:') + $form->addInteger('count', 'Quantità:') ->setRequired(); - $form->addSubmit('send', 'Add to cart'); + $form->addSubmit('send', 'Aggiungi al carrello'); return $form; }); } ``` -Nel modello possiamo rendere un modulo per ogni prodotto e ogni modulo sarà effettivamente un componente unico. +Ora possiamo semplicemente far renderizzare un form per ogni prodotto nel template - e ognuno sarà effettivamente un componente unico. ```latte {foreach $items as $item} @@ -35,26 +37,26 @@ Nel modello possiamo rendere un modulo per ogni prodotto e ogni modulo sarà eff {/foreach} ``` -L'argomento passato al tag `{control}` dice: +L'argomento passato nel tag `{control}` è nel formato che dice: -1. ottenere un componente `shopForm` -2. e restituire il suo figlio `$item->id` +1. ottieni il componente `shopForm` +2. e da esso ottieni il figlio `$item->id` -Durante la prima chiamata di **1.** il componente `shopForm` non esiste ancora, quindi viene chiamato il metodo `createComponentShopForm` per crearlo. Viene quindi richiamata una funzione anonima passata come parametro a Multiplier e viene creato un modulo. +Alla prima chiamata del punto **1.** `shopForm` non esiste ancora, quindi viene chiamata la sua factory `createComponentShopForm`. Sul componente ottenuto (istanza di Multiplier) viene poi chiamata la factory del form specifico - che è la funzione anonima che abbiamo passato a Multiplier nel costruttore. -Nelle iterazioni successive di `foreach` il metodo `createComponentShopForm` non viene più chiamato, perché il componente esiste già. Ma poiché si fa riferimento a un altro figlio (`$item->id` varia tra le iterazioni), viene chiamata nuovamente una funzione anonima e viene creato un nuovo modulo. +Nella successiva iterazione del foreach, il metodo `createComponentShopForm` non verrà più chiamato (il componente esiste), ma poiché stiamo cercando un suo figlio diverso (`$item->id` sarà diverso in ogni iterazione), la funzione anonima verrà chiamata di nuovo e ci restituirà un nuovo form. -L'ultima cosa da fare è assicurarsi che il form aggiunga effettivamente il prodotto corretto al carrello, perché nello stato attuale tutti i form sono uguali e non possiamo distinguere a quali prodotti appartengono. Per questo possiamo usare la proprietà di Multiplier (e in generale di ogni metodo di fabbrica di componenti in Nette Framework), secondo cui ogni metodo di fabbrica di componenti riceve come primo parametro il nome del componente creato. Nel nostro caso si tratta di `$item->id`, che è esattamente ciò di cui abbiamo bisogno per distinguere i singoli prodotti. È sufficiente modificare il codice per la creazione del modulo: +L'unica cosa che resta da fare è assicurarsi che il form aggiunga al carrello effettivamente il prodotto che deve - attualmente il form è completamente identico per ogni prodotto. Ci aiuta una proprietà di Multiplier (e in generale di ogni factory di componente in Nette Framework), ovvero che ogni factory riceve come primo argomento il nome del componente creato. Nel nostro caso sarà `$item->id`, che è esattamente l'informazione di cui abbiamo bisogno. Basta quindi modificare leggermente la creazione del form: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function ($itemId) { $form = new Nette\Application\UI\Form; - $form->addInteger('amount', 'Amount:') + $form->addInteger('count', 'Quantità:') ->setRequired(); $form->addHidden('itemId', $itemId); - $form->addSubmit('send', 'Add to cart'); + $form->addSubmit('send', 'Aggiungi al carrello'); return $form; }); } diff --git a/application/it/presenters.texy b/application/it/presenters.texy index e3330bd7cb..21c74bdd7f 100644 --- a/application/it/presenters.texy +++ b/application/it/presenters.texy @@ -1,39 +1,39 @@ -Presentatori -************ +Presenter +*********
    -Impareremo a scrivere presentatori e modelli in Nette. Dopo la lettura saprete che: +Impareremo come scrivere presenter e template in Nette. Dopo aver letto, saprai: -- come funziona il presentatore +- come funziona un presenter - cosa sono i parametri persistenti -- come rendere un modello +- come vengono renderizzati i template
    -[Sappiamo già |how-it-works#nette-application] che un presenter è una classe che rappresenta una pagina specifica di un'applicazione web, come ad esempio una homepage, un prodotto in un e-shop, un modulo di iscrizione, un feed sitemap, ecc. L'applicazione può avere da uno a migliaia di presenter. In altri framework, sono noti anche come controllori. +[Sappiamo già |how-it-works#Nette Application] che un presenter è una classe che rappresenta una specifica pagina di un'applicazione web, ad es. la homepage; un prodotto in un e-shop; un modulo di login; un feed sitemap, ecc. Un'applicazione può avere da uno a migliaia di presenter. In altri framework vengono anche chiamati controller. -Di solito, il termine presenter si riferisce a un discendente della classe [api:Nette\Application\UI\Presenter], adatta alle interfacce web e di cui parleremo nel resto del capitolo. In senso generale, un presentatore è un qualsiasi oggetto che implementa l'interfaccia [api:Nette\Application\IPresenter]. +Di solito, con il termine presenter si intende un discendente della classe [api:Nette\Application\UI\Presenter], che è adatto per generare interfacce web e al quale ci dedicheremo nel resto di questo capitolo. In senso generale, un presenter è qualsiasi oggetto che implementa l'interfaccia [api:Nette\Application\IPresenter]. -Ciclo di vita del presentatore .[#toc-life-cycle-of-presenter] -============================================================== +Ciclo di vita del presenter +=========================== -Il compito del presentatore è quello di elaborare la richiesta e restituire una risposta (che può essere una pagina HTML, un'immagine, un reindirizzamento, ecc.) +Il compito del presenter è gestire la richiesta e restituire una risposta (che può essere una pagina HTML, un'immagine, un redirect, ecc.). -All'inizio c'è una richiesta. Non si tratta direttamente di una richiesta HTTP, ma di un oggetto [api:Nette\Application\Request] in cui la richiesta HTTP è stata trasformata tramite un router. Di solito non entriamo in contatto con questo oggetto, perché il presentatore delega abilmente l'elaborazione della richiesta a metodi speciali, che vedremo ora. +Quindi, all'inizio, gli viene passata la richiesta. Non è direttamente la richiesta HTTP, ma l'oggetto [api:Nette\Application\Request], nel quale la richiesta HTTP è stata trasformata con l'aiuto del router. Di solito non entriamo in contatto con questo oggetto, poiché il presenter delega intelligentemente l'elaborazione della richiesta ad altri metodi, che ora vedremo. -[* lifecycle.svg *] *** *Ciclo di vita del presentatore* .<> +[* lifecycle.svg *] *** *Ciclo di vita del presenter* .<> -La figura mostra un elenco di metodi che vengono chiamati in sequenza dall'alto verso il basso, se esistono. Non è necessario che esistano, possiamo avere un presenter completamente vuoto, senza un solo metodo, e costruirci sopra un semplice web statico. +L'immagine rappresenta l'elenco dei metodi che vengono chiamati in sequenza dall'alto verso il basso, se esistono. Nessuno di essi deve esistere, possiamo avere un presenter completamente vuoto senza un singolo metodo e costruirci sopra un semplice sito web statico. `__construct()` --------------- -Il costruttore non appartiene esattamente al ciclo di vita del presentatore, perché viene chiamato al momento della creazione dell'oggetto. Ma lo citiamo per la sua importanza. Il costruttore (insieme al [metodo inject |best-practices:inject-method-attribute]) è usato per passare le dipendenze. +Il costruttore non appartiene propriamente al ciclo di vita del presenter, perché viene chiamato al momento della creazione dell'oggetto. Ma lo menzioniamo per la sua importanza. Il costruttore (insieme al [metodo inject|best-practices:inject-method-attribute]) serve a passare le dipendenze. -Il costruttore non deve occuparsi della logica di business dell'applicazione, scrivere e leggere dal database, eseguire calcoli, ecc. Questo è il compito delle classi di un livello, che chiamiamo modello. Per esempio, la classe `ArticleRepository` può essere responsabile del caricamento e del salvataggio degli articoli. Affinché il presentatore possa utilizzarla, viene [passata utilizzando la dependency injection |dependency-injection:passing-dependencies]: +Il presenter non dovrebbe occuparsi della logica di business dell'applicazione, scrivere e leggere dal database, eseguire calcoli, ecc. Per questo ci sono classi del layer che chiamiamo model. Ad esempio, la classe `ArticleRepository` può essere responsabile del caricamento e del salvataggio degli articoli. Affinché il presenter possa lavorarci, se la fa [passare tramite dependency injection |dependency-injection:passing-dependencies]: ```php @@ -50,44 +50,44 @@ class ArticlePresenter extends Nette\Application\UI\Presenter `startup()` ----------- -Subito dopo la ricezione della richiesta, viene invocato il metodo `startup ()`. Si può usare per inizializzare le proprietà, controllare i privilegi dell'utente, ecc. È necessario chiamare sempre l'antenato `parent::startup()`. +Subito dopo aver ricevuto la richiesta, viene chiamato il metodo `startup()`. Puoi usarlo per inizializzare le proprietà, verificare i permessi utente, ecc. È richiesto che il metodo chiami sempre il genitore `parent::startup()`. `action(args...)` .{toc: action()} -------------------------------------------------- -Simile al metodo `render()`. Mentre `render()` è destinato a preparare i dati per un modello specifico, che viene successivamente reso, in `action()` una richiesta viene elaborata senza una successiva resa del template. Ad esempio, i dati vengono elaborati, l'utente viene connesso o disconnesso e così via e poi viene [reindirizzato altrove |#Redirection]. +Analogo al metodo `render()`. Mentre `render()` è destinato a preparare i dati per un template specifico che verrà successivamente renderizzato, in `action()` la richiesta viene elaborata senza relazione con il rendering del template. Ad esempio, vengono elaborati i dati, l'utente viene loggato o sloggato, e così via, e poi [reindirizza altrove |#Redirect]. -È importante che `action()` sia chiamato prima di `render()`quindi al suo interno si può eventualmente modificare il corso successivo del ciclo di vita, cioè cambiare il template che sarà reso e anche il metodo `render()` che sarà chiamato, utilizzando `setView('otherView')`. +È importante notare che `action()` viene chiamato prima di `render()`, quindi al suo interno possiamo eventualmente cambiare il corso successivo degli eventi, cioè cambiare il template che verrà renderizzato, e anche il metodo `render()` che verrà chiamato. E questo tramite `setView('altraView')`. -I parametri della richiesta vengono passati al metodo. È possibile e consigliabile specificare i tipi di parametri, ad esempio `actionShow(int $id, string $slug = null)` - se il parametro `id` manca o non è un intero, il presentatore restituisce l'[errore 404 |#Error 404 etc.] e termina l'operazione. +Al metodo vengono passati i parametri dalla richiesta. È possibile e consigliato specificare i tipi per i parametri, ad es. `actionShow(int $id, ?string $slug = null)` - se il parametro `id` manca o se non è un intero, il presenter restituirà un [errore 404 |#Errore 404 e simili] e terminerà l'attività. `handle(args...)` .{toc: handle()} -------------------------------------------------- -Questo metodo elabora i cosiddetti segnali, di cui si parlerà nel capitolo sui [componenti |components#Signal]. È destinato principalmente ai componenti e all'elaborazione di richieste AJAX. +Il metodo elabora i cosiddetti segnali, che conosceremo nel capitolo dedicato ai [componenti |components#Segnale]. È infatti destinato principalmente ai componenti e all'elaborazione delle richieste AJAX. -I parametri vengono passati al metodo, come nel caso di `action()`, compreso il controllo del tipo. +Al metodo vengono passati i parametri dalla richiesta, come nel caso di `action()`, incluso il controllo del tipo. `beforeRender()` ---------------- -Il metodo `beforeRender`, come suggerisce il nome, viene chiamato prima di ogni metodo `render()`. È usato per la configurazione comune dei template, per passare le variabili per il layout e così via. +Il metodo `beforeRender`, come suggerisce il nome, viene chiamato prima di ogni metodo `render()`. Viene utilizzato per la configurazione comune del template, il passaggio di variabili per il layout e simili. `render(args...)` .{toc: render()} ---------------------------------------------- -Il luogo in cui si prepara il modello per il rendering successivo, si passano i dati ad esso, ecc. +Il luogo dove prepariamo il template per il successivo rendering, gli passiamo i dati, ecc. -I parametri vengono passati al metodo, come nel caso di `action()`, compreso il controllo del tipo. +Al metodo vengono passati i parametri dalla richiesta, come nel caso di `action()`, incluso il controllo del tipo. ```php public function renderShow(int $id): void { - // otteniamo i dati dal modello e li passiamo al template + // otteniamo i dati dal model e li passiamo al template $this->template->article = $this->articles->getById($id); } ``` @@ -96,104 +96,104 @@ public function renderShow(int $id): void `afterRender()` --------------- -Il metodo `afterRender`, come suggerisce il nome, viene richiamato dopo ogni metodo. `render()` metodo. Viene utilizzato piuttosto raramente. +Il metodo `afterRender`, come suggerisce nuovamente il nome, viene chiamato dopo ogni metodo `render()`. Viene utilizzato piuttosto eccezionalmente. `shutdown()` ------------ -Viene richiamato alla fine del ciclo di vita del presentatore. +Viene chiamato alla fine del ciclo di vita del presenter. -**Buon consiglio prima di andare avanti**. Come si può vedere, il presentatore può gestire più azioni/visualizzazioni, cioè avere più metodi `render()`. Ma si consiglia di progettare presentatori con una sola o il minor numero possibile di azioni. +**Un buon consiglio prima di andare avanti**. Come si vede, un presenter può gestire più azioni/view, cioè avere più metodi `render()`. Ma consigliamo di progettare presenter con una o il minor numero possibile di azioni. -Invio di una risposta .[#toc-sending-a-response] -================================================ +Invio della risposta +==================== -La risposta del presentatore è solitamente il [rendering del template con la pagina HTML |templates], ma può anche essere l'invio di un file, di JSON o persino il reindirizzamento a un'altra pagina. +La risposta del presenter è di solito il [rendering di un template con una pagina HTML|templates], ma può anche essere l'invio di un file, JSON o magari un redirect a un'altra pagina. -In qualsiasi momento del ciclo di vita, è possibile utilizzare uno dei seguenti metodi per inviare una risposta e allo stesso tempo uscire dal presentatore: +In qualsiasi momento durante il ciclo di vita, possiamo inviare una risposta con uno dei seguenti metodi e allo stesso tempo terminare il presenter: -- `redirect()`, `redirectPermanent()`, `redirectUrl()` e `forward()` [reindirizzamenti |#Redirection] -- `error()` chiude il presentatore [per errore |#Error 404 etc.] -- `sendJson($data)` esce dal presentatore e [invia i dati |#Sending JSON] in formato JSON -- `sendTemplate()` abbandona il presentatore e [renderizza |templates]immediatamente [il template |templates] -- `sendResponse($response)` abbandona il presentatore e invia la [propria risposta |#Responses] -- `terminate()` abbandona il presentatore senza risposta +- `redirect()`, `redirectPermanent()`, `redirectUrl()` e `forward()` [reindirizzano |#Redirect] +- `error()` termina il presenter [a causa di un errore |#Errore 404 e simili] +- `sendJson($data)` termina il presenter e [invia dati |#Invio di JSON] in formato JSON +- `sendTemplate()` termina il presenter e immediatamente [renderizza il template |templates] +- `sendResponse($response)` termina il presenter e invia una [risposta personalizzata |#Risposte] +- `terminate()` termina il presenter senza risposta -Se non si chiama nessuno di questi metodi, il presentatore procederà automaticamente al rendering del modello. Perché? Perché nel 99% dei casi si vuole disegnare un modello, quindi il presentatore assume questo comportamento come predefinito e vuole semplificarci il lavoro. +Se non chiami nessuno di questi metodi, il presenter procederà automaticamente al rendering del template. Perché? Perché nel 99% dei casi vogliamo renderizzare un template, quindi il presenter considera questo comportamento come predefinito e vuole semplificarci il lavoro. -Creazione di collegamenti .[#toc-creating-links] -================================================ +Creazione di link +================= -Il presentatore ha un metodo `link()`, utilizzato per creare collegamenti URL ad altri presentatori. Il primo parametro è il presentatore e l'azione di destinazione, seguito dagli argomenti, che possono essere passati come array: +Il presenter dispone del metodo `link()`, tramite il quale è possibile creare link URL ad altri presenter. Il primo parametro è il presenter & azione di destinazione, seguono gli argomenti passati, che possono essere specificati come array: ```php $url = $this->link('Product:show', $id); -$url = $this->link('Product:show', [$id, 'lang' => 'en']); +$url = $this->link('Product:show', [$id, 'lang' => 'cs']); ``` -Nel modello creiamo collegamenti ad altri presentatori e azioni come segue: +Nel template, i link ad altri presenter & azioni vengono creati in questo modo: ```latte -product detail +dettaglio prodotto ``` -È sufficiente scrivere la nota coppia `Presenter:action` al posto dell'URL reale e includere eventuali parametri. Il trucco è `n:href`, che dice che questo attributo sarà elaborato da Latte e genererà un vero URL. In Nette non è necessario pensare agli URL, ma solo ai presentatori e alle azioni. +Semplicemente, invece dell'URL reale, scrivi la coppia nota `Presenter:action` e specifichi eventuali parametri. Il trucco sta in `n:href`, che dice che questo attributo sarà elaborato da Latte e genererà l'URL reale. In Nette, quindi, non devi affatto pensare agli URL, ma solo ai presenter e alle azioni. -Per ulteriori informazioni, vedere [Creazione di collegamenti |Creating Links]. +Maggiori informazioni si trovano nel capitolo [Creazione di link URL|creating-links]. -Reindirizzamento .[#toc-redirection] -==================================== +Redirect +======== -Per passare a un altro presentatore si usano i metodi `redirect()` e `forward()`, che hanno una sintassi molto simile a quella del metodo [link() |#Creating Links]. +Per passare a un altro presenter si usano i metodi `redirect()` e `forward()`, che hanno una sintassi molto simile al metodo [link() |#Creazione di link]. -Il metodo `forward()` passa immediatamente al nuovo presentatore senza reindirizzamento HTTP: +Il metodo `forward()` passa immediatamente al nuovo presenter senza redirect HTTP: ```php $this->forward('Product:show'); ``` -Esempio di reindirizzamento temporaneo con codice HTTP 302 o 303: +Esempio del cosiddetto redirect temporaneo con codice HTTP 302 (o 303, se il metodo della richiesta corrente è POST): ```php $this->redirect('Product:show', $id); ``` -Per ottenere un reindirizzamento permanente con codice HTTP 301 utilizzare: +Il redirect permanente con codice HTTP 301 si ottiene così: ```php $this->redirectPermanent('Product:show', $id); ``` -È possibile reindirizzare a un altro URL esterno all'applicazione con il metodo `redirectUrl()`: +È possibile reindirizzare a un altro URL al di fuori dell'applicazione con il metodo `redirectUrl()`. Come secondo parametro è possibile specificare il codice HTTP, il predefinito è 302 (o 303, se il metodo della richiesta corrente è POST): ```php $this->redirectUrl('https://nette.org'); ``` -Il reindirizzamento termina immediatamente il ciclo di vita del presentatore lanciando la cosiddetta eccezione di terminazione silenziosa `Nette\Application\AbortException`. +Il redirect termina immediatamente l'attività del presenter lanciando la cosiddetta eccezione di terminazione silenziosa `Nette\Application\AbortException`. -Prima del reindirizzamento, è possibile inviare un [messaggio flash |#Flash Messages], che verrà visualizzato nel modello dopo il reindirizzamento. +Prima del redirect è possibile inviare un [messaggio flash |#Messaggi flash], cioè messaggi che verranno visualizzati nel template dopo il redirect. -Messaggi flash .[#toc-flash-messages] -===================================== +Messaggi flash +============== -Sono messaggi che di solito informano sul risultato di un'operazione. Una caratteristica importante dei messaggi flash è che sono disponibili nel modello anche dopo il reindirizzamento. Anche dopo essere stati visualizzati, rimarranno in vita per altri 30 secondi - ad esempio, nel caso in cui l'utente dovesse involontariamente aggiornare la pagina - il messaggio non andrà perso. +Si tratta di messaggi che solitamente informano sul risultato di qualche operazione. Una caratteristica importante dei messaggi flash è che sono disponibili nel template anche dopo un redirect. Anche dopo essere stati visualizzati, rimangono attivi per altri 30 secondi – ad esempio, nel caso in cui l'utente aggiorni la pagina a causa di un errore di trasmissione - il messaggio non scomparirà immediatamente. -Basta chiamare il metodo [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] e il presentatore si occuperà di passare il messaggio al modello. Il primo parametro è il testo del messaggio e il secondo parametro opzionale è il suo tipo (errore, avviso, info ecc.). Il metodo `flashMessage()` restituisce un'istanza di messaggio flash, per consentire di aggiungere ulteriori informazioni. +Basta chiamare il metodo [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] e il presenter si occuperà di passarlo al template. Il primo parametro è il testo del messaggio e il secondo parametro opzionale è il suo tipo (error, warning, info, ecc.). Il metodo `flashMessage()` restituisce un'istanza del messaggio flash, alla quale è possibile aggiungere ulteriori informazioni. ```php -$this->flashMessage('Item was removed.'); -$this->redirect(/* ... */); +$this->flashMessage('L\'elemento è stato eliminato.'); +$this->redirect(/* ... */); // e reindirizziamo ``` -Nel modello, questi messaggi sono disponibili nella variabile `$flashes` come oggetti `stdClass`, che contengono le proprietà `message` (testo del messaggio), `type` (tipo di messaggio) e possono contenere le già citate informazioni sull'utente. Li disegniamo come segue: +Nel template, questi messaggi sono disponibili nella variabile `$flashes` come oggetti `stdClass`, che contengono le proprietà `message` (testo del messaggio), `type` (tipo del messaggio) e possono contenere le informazioni utente già menzionate. Li renderizziamo ad esempio così: ```latte {foreach $flashes as $flash} @@ -202,10 +202,10 @@ Nel modello, questi messaggi sono disponibili nella variabile `$flashes` come og ``` -Errore 404 ecc. .[#toc-error-404-etc] -===================================== +Errore 404 e simili +=================== -Quando non possiamo soddisfare la richiesta, perché ad esempio l'articolo che vogliamo visualizzare non esiste nel database, lanceremo l'errore 404 usando il metodo `error(string $message = null, int $httpCode = 404)`, che rappresenta l'errore HTTP 404: +Se non è possibile soddisfare la richiesta, ad esempio perché l'articolo che vogliamo visualizzare non esiste nel database, lanciamo un errore 404 con il metodo `error(?string $message = null, int $httpCode = 404)`. ```php public function renderShow(int $id): void @@ -218,14 +218,13 @@ public function renderShow(int $id): void } ``` -Il codice di errore HTTP può essere passato come secondo parametro; il valore predefinito è 404. Il metodo funziona lanciando l'eccezione `Nette\Application\BadRequestException`, dopo di che `Application` passa il controllo al presentatore dell'errore. Si tratta di un presentatore che ha il compito di visualizzare una pagina che informa dell'errore. -Il presentatore di errori è impostato nella [configurazione dell'applicazione |configuration]. +Il codice HTTP dell'errore può essere passato come secondo parametro, il predefinito è 404. Il metodo funziona lanciando l'eccezione `Nette\Application\BadRequestException`, dopodiché `Application` passa il controllo all'error-presenter. Che è un presenter il cui compito è visualizzare una pagina che informa sull'errore verificatosi. L'impostazione dell'error-presenter viene eseguita nella [configurazione application|configuration]. -Invio di JSON .[#toc-sending-json] -================================== +Invio di JSON +============= -Esempio di metodo-azione che invia dati in formato JSON ed esce dal presentatore: +Esempio di un metodo action che invia dati in formato JSON e termina il presenter: ```php public function actionData(): void @@ -236,33 +235,59 @@ public function actionData(): void ``` -Parametri persistenti .[#toc-persistent-parameters] -=================================================== +Parametri della richiesta .{data-version:3.1.14} +================================================ + +Il presenter e anche ogni componente ottengono i propri parametri dalla richiesta HTTP. Il loro valore si ottiene con il metodo `getParameter($name)` o `getParameters()`. I valori sono stringhe o array di stringhe, si tratta fondamentalmente di dati grezzi ottenuti direttamente dall'URL. + +Per maggiore comodità, consigliamo di rendere accessibili i parametri tramite property. Basta contrassegnarli con l'attributo `#[Parameter]`: + +```php +use Nette\Application\Attributes\Parameter; // questa riga è importante + +class HomePresenter extends Nette\Application\UI\Presenter +{ + #[Parameter] + public string $theme; // deve essere public +} +``` + +Per la property, consigliamo di specificare anche il tipo di dati (es. `string`) e Nette convertirà automaticamente il valore in base ad esso. I valori dei parametri possono anche essere [validati |#Validazione dei parametri]. + +Durante la creazione di un link, è possibile impostare direttamente il valore dei parametri: + +```latte +clicca +``` + -I parametri persistenti sono usati per mantenere lo stato tra diverse richieste. Il loro valore rimane invariato anche dopo aver cliccato su un link. A differenza dei dati di sessione, vengono passati nell'URL. Questo è completamente automatico, quindi non è necessario dichiararli esplicitamente in `link()` o `n:href`. +Parametri persistenti +===================== -Un esempio di utilizzo? Avete un'applicazione multilingue. La lingua attuale è un parametro che deve sempre far parte dell'URL. Ma sarebbe incredibilmente noioso includerlo in ogni link. Perciò lo si rende un parametro persistente, chiamato `lang`, che si autoalimenta. Forte! +I parametri persistenti servono a mantenere lo stato tra richieste diverse. Il loro valore rimane lo stesso anche dopo aver cliccato su un link. A differenza dei dati nella sessione, vengono trasferiti nell'URL. E questo avviene in modo completamente automatico, non è quindi necessario specificarli esplicitamente in `link()` o `n:href`. -Creare un parametro persistente è estremamente facile in Nette. Basta creare una proprietà pubblica e contrassegnarla con l'attributo: (in precedenza si usava `/** @persistent */` ) +Esempio di utilizzo? Hai un'applicazione multilingue. La lingua corrente è un parametro che deve essere costantemente parte dell'URL. Ma sarebbe incredibilmente noioso specificarlo in ogni link. Quindi lo rendi un parametro persistente `lang` e verrà trasferito da solo. Fantastico! + +La creazione di un parametro persistente in Nette è estremamente semplice. Basta creare una property pubblica e contrassegnarla con un attributo: (in precedenza si usava `/** @persistent */`) ```php -use Nette\Application\Attributes\Persistent; // questa linea è importante +use Nette\Application\Attributes\Persistent; // questa riga è importante class ProductPresenter extends Nette\Application\UI\Presenter { #[Persistent] - public string $lang; // deve essere pubblico + public string $lang; // deve essere public } ``` -Se `$this->lang` ha un valore come `'en'`, i link creati con `link()` o `n:href` conterranno anche il parametro `lang=en`. E quando il link viene cliccato, sarà di nuovo `$this->lang = 'en'`. +Se `$this->lang` avrà il valore, ad esempio, `'en'`, anche i link creati tramite `link()` o `n:href` conterranno il parametro `lang=en`. E dopo aver cliccato sul link, sarà di nuovo `$this->lang = 'en'`. -Per le proprietà, si consiglia di includere il tipo di dati (ad esempio, `string`) e si può anche includere un valore predefinito. I valori dei parametri possono essere [convalidati |#Validation of Persistent Parameters]. +Per la property, consigliamo di specificare anche il tipo di dati (es. `string`) e puoi anche specificare un valore predefinito. I valori dei parametri possono essere [validati |#Validazione dei parametri]. -I parametri persistenti vengono passati per impostazione predefinita tra tutte le azioni di un determinato presentatore. Per passarli tra più presentatori, è necessario definirli: +I parametri persistenti vengono standardmente trasferiti tra tutte le azioni del presenter dato. Affinché vengano trasferiti anche tra più presenter, è necessario definirli o: -- in un antenato comune dal quale i presentatori ereditano -- nel tratto che i presentatori utilizzano: +- in un antenato comune da cui ereditano i presenter +- in un trait che i presenter utilizzeranno: ```php trait LanguageAware @@ -277,48 +302,42 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -È possibile modificare il valore di un parametro persistente durante la creazione di un collegamento: +Durante la creazione di un link, è possibile modificare il valore del parametro persistente: ```latte -detail in Czech +dettaglio in ceco ``` -Oppure può essere *ripristinato*, cioè rimosso dall'URL. In questo caso, assumerà il valore predefinito: +Oppure può essere *resettato*, cioè rimosso dall'URL. Assumerà quindi il suo valore predefinito: ```latte -click +clicca ``` -Componenti interattivi .[#toc-interactive-components] -===================================================== +Componenti Interattivi +====================== -I presentatori hanno un sistema di componenti incorporato. I componenti sono unità separate e riutilizzabili che vengono inserite nei presentatori. Possono essere [moduli |forms:in-presenter], griglie di dati, menu, qualsiasi cosa abbia senso usare ripetutamente. +I presenter hanno un sistema di componenti integrato. I componenti sono unità riutilizzabili separate che inseriamo nei presenter. Possono essere [form |forms:in-presenter], datagrid, menu, praticamente qualsiasi cosa che abbia senso usare ripetutamente. -Come si posizionano i componenti e come si utilizzano successivamente nel presentatore? Questo è spiegato nel capitolo [Componenti |Components]. Scoprirete anche cosa hanno a che fare con Hollywood. +Come vengono inseriti i componenti nel presenter e successivamente utilizzati? Lo imparerai nel capitolo [Componenti |components]. Scoprirai persino cosa hanno in comune con Hollywood. -Dove posso trovare alcuni componenti? Alla pagina [Componenti |https://componette.org] si possono trovare alcuni componenti open-source e altri addons per Nette, realizzati e condivisi dalla comunità di Nette Framework. +E dove posso ottenere i componenti? Sulla pagina [Componette |https://componette.org/search/component] troverai componenti open-source e anche numerosi altri add-on per Nette, che sono stati inseriti qui da volontari della comunità attorno al framework. -Approfondimento .[#toc-going-deeper] -==================================== +Andiamo in profondità +===================== .[tip] -Quanto mostrato finora in questo capitolo sarà probabilmente sufficiente. Le righe che seguono sono destinate a chi è interessato ad approfondire i presentatori e vuole sapere tutto. - - -Requisiti e parametri .[#toc-requirement-and-parameters] --------------------------------------------------------- +Con quello che abbiamo mostrato finora in questo capitolo, probabilmente ti basterà. Le righe seguenti sono destinate a coloro che sono interessati ai presenter in profondità e vogliono sapere assolutamente tutto. -La richiesta gestita dal presentatore è l'oggetto [api:Nette\Application\Request] e viene restituita dal metodo del presentatore `getRequest()`. Include un array di parametri e ognuno di essi appartiene o a qualche componente o direttamente al presentatore (che in realtà è anch'esso un componente, anche se speciale). Quindi Nette ridistribuisce i parametri e passa tra i singoli componenti (e il presentatore) chiamando il metodo `loadState(array $params)`. I parametri possono essere ottenuti con il metodo `getParameters(): array`, singolarmente con `getParameter($name)`. I valori dei parametri sono stringhe o array di stringhe, in pratica dati grezzi ottenuti direttamente da un URL. +Validazione dei parametri +------------------------- -Convalida dei parametri persistenti .[#toc-validation-of-persistent-parameters] -------------------------------------------------------------------------------- +I valori dei [#parametri della richiesta] e dei [#parametri persistenti] ricevuti dall'URL vengono scritti nelle properties dal metodo `loadState()`. Questo controlla anche se il tipo di dati specificato nella property corrisponde, altrimenti risponde con un errore 404 e la pagina non viene visualizzata. -I valori dei [parametri persistenti |#persistent parameters] ricevuti dagli URL vengono scritti nelle proprietà dal metodo `loadState()`. Il metodo controlla anche se il tipo di dati specificato nella proprietà corrisponde, altrimenti risponde con un errore 404 e la pagina non viene visualizzata. - -Non fidarsi mai ciecamente dei parametri persistenti, perché possono essere facilmente sovrascritti dall'utente nell'URL. Ad esempio, è così che si controlla se `$this->lang` è tra le lingue supportate. Un buon modo per farlo è sovrascrivere il metodo `loadState()` citato in precedenza: +Non fidarti mai ciecamente dei parametri, perché possono essere facilmente sovrascritti dall'utente nell'URL. In questo modo, ad esempio, verifichiamo se la lingua `$this->lang` è tra quelle supportate. Un modo appropriato è sovrascrivere il metodo `loadState()` menzionato: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -329,7 +348,7 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function loadState(array $params): void { parent::loadState($params); // qui viene impostato $this->lang - // segue il controllo del valore dell'utente: + // segue il controllo del valore personalizzato: if (!in_array($this->lang, ['en', 'cs'])) { $this->error(); } @@ -338,43 +357,45 @@ class ProductPresenter extends Nette\Application\UI\Presenter ``` -Salvare e ripristinare la richiesta .[#toc-save-and-restore-the-request] ------------------------------------------------------------------------- +Salvataggio e ripristino della richiesta +---------------------------------------- + +La richiesta che il presenter gestisce è un oggetto [api:Nette\Application\Request] e viene restituita dal metodo del presenter `getRequest()`. -È possibile salvare la richiesta corrente in una sessione o ripristinarla dalla sessione e lasciare che il presentatore la esegua di nuovo. Ciò è utile, ad esempio, quando un utente compila un modulo e il suo login scade. Per non perdere i dati, prima di reindirizzare alla pagina di accesso, salviamo la richiesta corrente nella sessione usando `$reqId = $this->storeRequest()`, che restituisce un identificatore sotto forma di stringa breve e lo passa come parametro al presentatore dell'accesso. +La richiesta corrente può essere salvata nella sessione o, al contrario, ripristinata da essa e fatta eseguire nuovamente dal presenter. Questo è utile, ad esempio, nella situazione in cui l'utente sta compilando un modulo e la sua sessione scade. Per non perdere i dati, prima del redirect alla pagina di login, salviamo la richiesta corrente nella sessione tramite `$reqId = $this->storeRequest()`, che restituisce il suo identificatore sotto forma di una breve stringa e lo passiamo come parametro al presenter di login. -Dopo l'accesso, chiamiamo il metodo `$this->restoreRequest($reqId)`, che preleva la richiesta dalla sessione e la inoltra ad essa. Il metodo verifica che la richiesta sia stata creata dallo stesso utente che ha effettuato l'accesso. Se un altro utente accede o la chiave non è valida, non fa nulla e il programma continua. +Dopo il login, chiamiamo il metodo `$this->restoreRequest($reqId)`, che recupera la richiesta dalla sessione e vi inoltra. Il metodo verifica nel frattempo che la richiesta sia stata creata dallo stesso utente che si è appena loggato. Se si loggasse un altro utente o la chiave non fosse valida, non fa nulla e il programma continua. -Vedere il ricettario [Come tornare a una pagina precedente |best-practices:restore-request]. +Dai un'occhiata alla guida [Come tornare alla pagina precedente |best-practices:restore-request]. -Canonizzazione .[#toc-canonization] ------------------------------------ +Canonizzazione +-------------- -I presentatori hanno una caratteristica davvero eccezionale che migliora la SEO (ottimizzazione della ricercabilità su Internet). Impediscono automaticamente l'esistenza di contenuti duplicati su URL diversi. Se più URL conducono a una determinata destinazione, ad esempio `/index` e `/index?page=1`, il framework ne designa uno come principale (canonico) e reindirizza gli altri verso di esso utilizzando il codice HTTP 301. In questo modo, i motori di ricerca non indicizzano le pagine due volte e non ne indeboliscono il page rank. +I presenter hanno una caratteristica davvero fantastica che contribuisce a un migliore SEO (ottimizzazione della reperibilità su Internet). Impediscono automaticamente l'esistenza di contenuti duplicati su URL diversi. Se a una determinata destinazione portano più indirizzi URL, ad es. `/index` e `/index?page=1`, il framework ne determina uno come primario (canonico) e reindirizza gli altri ad esso tramite il codice HTTP 301. Grazie a ciò, i motori di ricerca non indicizzano le pagine due volte e non diluiscono il loro page rank. -Questo processo è chiamato canonizzazione. L'URL canonico è l'URL generato dal [router |routing], di solito il primo percorso appropriato della collezione. +Questo processo si chiama canonizzazione. L'URL canonico è quello generato dal [router|routing], di solito quindi la prima route corrispondente nella collezione. -La canonizzazione è attiva per impostazione predefinita e può essere disattivata tramite `$this->autoCanonicalize = false`. +La canonizzazione è attivata per impostazione predefinita e può essere disattivata tramite `$this->autoCanonicalize = false`. -Il reindirizzamento non avviene con una richiesta AJAX o POST, perché comporterebbe una perdita di dati o non avrebbe alcun valore aggiunto dal punto di vista SEO. +Il redirect non avviene durante una richiesta AJAX o POST, perché si verificherebbe una perdita di dati o non avrebbe valore aggiunto dal punto di vista SEO. -Si può anche invocare la canonizzazione manualmente con il metodo `canonicalize()`, che, come il metodo `link()`, riceve come argomenti il presentatore, le azioni e i parametri. Crea un link e lo confronta con l'URL corrente. Se è diverso, reindirizza al link generato. +La canonizzazione può essere invocata anche manualmente tramite il metodo `canonicalize()`, al quale, in modo simile al metodo `link()`, vengono passati il presenter, l'azione e i parametri. Crea un link e lo confronta con l'URL corrente. Se differiscono, reindirizza al link generato. ```php -public function actionShow(int $id, string $slug = null): void +public function actionShow(int $id, ?string $slug = null): void { $realSlug = $this->facade->getSlugForId($id); - // reindirizza se $slug è diverso da $realSlug + // reindirizza, se $slug differisce da $realSlug $this->canonicalize('Product:show', [$id, $realSlug]); } ``` -Eventi .[#toc-events] ---------------------- +Eventi +------ -Oltre ai metodi `startup()`, `beforeRender()` e `shutdown()`, che vengono richiamati durante il ciclo di vita del presentatore, è possibile definire altre funzioni da richiamare automaticamente. Il presentatore definisce i cosiddetti [eventi |nette:glossary#events], i cui gestori vengono aggiunti agli array `$onStartup`, `$onRender` e `$onShutdown`. +Oltre ai metodi `startup()`, `beforeRender()` e `shutdown()`, che vengono chiamati come parte del ciclo di vita del presenter, è possibile definire altre funzioni che devono essere chiamate automaticamente. Il presenter definisce i cosiddetti [eventi |nette:glossary#Eventi], i cui handler aggiungi agli array `$onStartup`, `$onRender` e `$onShutdown`. ```php class ArticlePresenter extends Nette\Application\UI\Presenter @@ -388,34 +409,34 @@ class ArticlePresenter extends Nette\Application\UI\Presenter } ``` -I gestori nell'array `$onStartup` vengono chiamati subito prima del metodo `startup()`, poi `$onRender` tra `beforeRender()` e . `render()` e infine `$onShutdown` subito prima di `shutdown()`. +Gli handler nell'array `$onStartup` vengono chiamati poco prima del metodo `startup()`, poi `$onRender` tra `beforeRender()` e `render()` e infine `$onShutdown` poco prima di `shutdown()`. -Le risposte .[#toc-responses] ------------------------------ +Risposte +-------- -La risposta restituita dal presentatore è un oggetto che implementa l'interfaccia [api:Nette\Application\Response]. Esiste una serie di risposte già pronte: +La risposta restituita dal presenter è un oggetto che implementa l'interfaccia [api:Nette\Application\Response]. Sono disponibili numerose risposte pronte: - [api:Nette\Application\Responses\CallbackResponse] - invia un callback -- [api:Nette\Application\Responses\FileResponse] - invia il file -- [api:Nette\Application\Responses\ForwardResponse] - invia () +- [api:Nette\Application\Responses\FileResponse] - invia un file +- [api:Nette\Application\Responses\ForwardResponse] - forward() - [api:Nette\Application\Responses\JsonResponse] - invia JSON -- [api:Nette\Application\Responses\RedirectResponse] - reindirizza +- [api:Nette\Application\Responses\RedirectResponse] - redirect - [api:Nette\Application\Responses\TextResponse] - invia testo - [api:Nette\Application\Responses\VoidResponse] - risposta vuota -Le risposte sono inviate con il metodo `sendResponse()`: +Le risposte vengono inviate con il metodo `sendResponse()`: ```php use Nette\Application\Responses; -// Testo normale -$this->sendResponse(new Responses\TextResponse('Hello Nette!')); +// Testo semplice +$this->sendResponse(new Responses\TextResponse('Ciao Nette!')); // Invia un file $this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf')); -// Invia un callback +// La risposta sarà un callback $callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse) { if ($httpResponse->getHeader('Content-Type') === 'text/html') { echo '

    Ciao

    '; @@ -425,10 +446,55 @@ $this->sendResponse(new Responses\CallbackResponse($callback)); ``` -Ulteriori letture .[#toc-further-reading] -========================================= +Restrizione dell'accesso tramite `#[Requires]` .{data-version:3.2.2} +-------------------------------------------------------------------- + +L'attributo `#[Requires]` offre opzioni avanzate per limitare l'accesso ai presenter e ai loro metodi. Può essere utilizzato per specificare metodi HTTP, richiedere una richiesta AJAX, limitare alla stessa origine (same origin) e l'accesso solo tramite forward. L'attributo può essere applicato sia alle classi dei presenter che ai singoli metodi `action()`, `render()`, `handle()` e `createComponent()`. + +È possibile specificare queste restrizioni: +- sui metodi HTTP: `#[Requires(methods: ['GET', 'POST'])]` +- richiesta di una richiesta AJAX: `#[Requires(ajax: true)]` +- accesso solo dalla stessa origine: `#[Requires(sameOrigin: true)]` +- accesso solo tramite forward: `#[Requires(forward: true)]` +- restrizione ad azioni specifiche: `#[Requires(actions: 'default')]` + +I dettagli si trovano nella guida [Come usare l'attributo Requires |best-practices:attribute-requires]. + + +Controllo del metodo HTTP +------------------------- + +I presenter in Nette verificano automaticamente il metodo HTTP di ogni richiesta in arrivo. Il motivo di questo controllo è principalmente la sicurezza. Standardmente sono consentiti i metodi `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH`. + +Se si desidera consentire inoltre, ad esempio, il metodo `OPTIONS`, utilizzare l'attributo `#[Requires]` (da Nette Application v3.2): + +```php +#[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])] +class MyPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Nella versione 3.1, la verifica viene eseguita in `checkHttpMethod()`, che verifica se il metodo specificato nella richiesta è contenuto nell'array `$presenter->allowedMethods`. L'aggiunta del metodo si fa così: + +```php +class MyPresenter extends Nette\Application\UI\Presenter +{ + protected function checkHttpMethod(): void + { + $this->allowedMethods[] = 'OPTIONS'; + parent::checkHttpMethod(); + } +} +``` + +È importante sottolineare che se si consente il metodo `OPTIONS`, è necessario successivamente gestirlo adeguatamente all'interno del proprio presenter. Il metodo è spesso utilizzato come cosiddetta richiesta preflight, che il browser invia automaticamente prima della richiesta effettiva, quando è necessario verificare se la richiesta è consentita dal punto di vista della politica CORS (Cross-Origin Resource Sharing). Se si consente il metodo, ma non si implementa la risposta corretta, ciò può portare a incoerenze e potenziali problemi di sicurezza. + + +Ulteriori letture +================= -- [Iniettare metodi e attributi |best-practices:inject-method-attribute] -- [Comporre presentatori da tratti |best-practices:presenter-traits] -- [Passare le impostazioni ai presentatori |best-practices:passing-settings-to-presenters] -- [Come tornare a una pagina precedente |best-practices:restore-request] +- [Metodi e attributi inject |best-practices:inject-method-attribute] +- [Composizione di presenter da trait |best-practices:presenter-traits] +- [Passaggio di impostazioni ai presenter |best-practices:passing-settings-to-presenters] +- [Come tornare alla pagina precedente |best-practices:restore-request] diff --git a/application/it/routing.texy b/application/it/routing.texy index b530d0b83b..36b8f5968c 100644 --- a/application/it/routing.texy +++ b/application/it/routing.texy @@ -1,29 +1,28 @@ -Instradamento -************* +Routing +*******
    -Il router si occupa di tutto ciò che riguarda gli URL, in modo da non doverci più pensare. Mostreremo: +Il Router si occupa di tutto ciò che riguarda gli indirizzi URL, così non dovrai più pensarci tu. Vedremo: -- come impostare il router in modo che gli URL abbiano l'aspetto desiderato -- alcune note sul reindirizzamento SEO -- e vi mostreremo come scrivere il vostro router personale +- come impostare il router affinché gli URL siano come desiderato +- parleremo di SEO e redirect +- e mostreremo come scrivere un router personalizzato
    -Gli URL più umani (o gli URL belli o cool) sono più utilizzabili, più memorabili e contribuiscono positivamente alla SEO. Nette ha in mente questo aspetto e soddisfa pienamente i desideri degli sviluppatori. Potete progettare la struttura degli URL per la vostra applicazione esattamente come volete. -È possibile progettarla anche dopo che l'applicazione è pronta, senza dover modificare il codice o il modello. È definita in modo elegante in [un unico punto |#Integration], nel router, e non è sparsa sotto forma di annotazioni in tutti i presentatori. +URL più umani (o anche cool o pretty URL) sono più usabili, memorizzabili e contribuiscono positivamente al SEO. Nette ci pensa e va incontro pienamente agli sviluppatori. Puoi progettare per la tua applicazione esattamente la struttura degli indirizzi URL che desideri. Puoi progettarla persino quando l'applicazione è già finita, perché si può fare senza interventi nel codice o nei template. Si definisce infatti in modo elegante in un [unico posto |#Integrazione nell applicazione], nel router, e non è quindi disseminata sotto forma di annotazioni in tutti i presenter. -Il router di Nette è speciale perché è **bidirezionale**, può sia decodificare gli URL delle richieste HTTP sia creare collegamenti. Svolge quindi un ruolo fondamentale nell'[applicazione Nette |how-it-works#Nette Application], in quanto decide quale presentatore e quale azione eseguirà la richiesta corrente ed è anche utilizzato per la [generazione di URL |creating-links] nel modello, ecc. +Il router in Nette è eccezionale perché è **bidirezionale.** Sa sia decodificare gli URL nella richiesta HTTP, sia creare i link. Svolge quindi un ruolo fondamentale in [Nette Application |how-it-works#Nette Application], perché decide quale presenter e azione eseguirà la richiesta corrente, ma viene anche utilizzato per la [generazione di URL |creating-links] nel template, ecc. -Tuttavia, il router non si limita a questo uso: è possibile utilizzarlo in applicazioni in cui i presentatori non vengono utilizzati affatto, per le API REST, ecc. Maggiori informazioni nella sezione [Uso separato |#separated usage]. +Tuttavia, il router non è limitato solo a questo utilizzo, puoi usarlo in applicazioni dove i presenter non vengono affatto utilizzati, per API REST, ecc. Maggiori informazioni nella sezione [#Utilizzo indipendente]. -Raccolta di rotte .[#toc-route-collection] -========================================== +Collezione di route +=================== -Il modo più piacevole per definire gli indirizzi URL nell'applicazione è attraverso la classe [api:Nette\Application\Routers\RouteList]. La definizione consiste in un elenco di cosiddette rotte, ossia maschere di indirizzi URL e di presentatori e azioni ad essi associati, utilizzando una semplice API. Non è necessario dare un nome alle rotte. +Il modo più piacevole per definire la forma degli indirizzi URL nell'applicazione è offerto dalla classe [api:Nette\Application\Routers\RouteList]. La definizione è costituita da un elenco delle cosiddette route, cioè maschere di indirizzi URL e presenter e azioni associati ad esse tramite una semplice API. Non è necessario dare un nome alle route. ```php $router = new Nette\Application\Routers\RouteList; @@ -32,130 +31,130 @@ $router->addRoute('article/', 'Article:view'); // ... ``` -L'esempio dice che se apriamo `https://any-domain.com/rss.xml` con l'azione `rss`, se `https://domain.com/article/12` con l'azione `view`, ecc. Se non viene trovato un percorso adatto, Nette Application risponde lanciando un'eccezione [BadRequestException |api:Nette\Application\BadRequestException], che appare all'utente come una pagina di errore 404 Not Found. +L'esempio dice che se apriamo `https://domain.com/rss.xml` nel browser, verrà visualizzato il presenter `Feed` con l'azione `rss`, se `https://domain.com/article/12`, verrà visualizzato il presenter `Article` con l'azione `view`, ecc. In caso di mancata corrispondenza di una route adatta, Nette Application reagisce lanciando un'eccezione [BadRequestException |api:Nette\Application\BadRequestException], che viene mostrata all'utente come una pagina di errore 404 Not Found. -Ordine dei percorsi .[#toc-order-of-routes] -------------------------------------------- +Ordine delle route +------------------ -L'ordine in cui sono elencate le rotte è **molto importante** perché vengono valutate in sequenza dall'alto verso il basso. La regola è quella di dichiarare le rotte **dalla specifica alla generale**: +L'**ordine** in cui sono elencate le singole route è **assolutamente cruciale**, perché vengono valutate sequenzialmente dall'alto verso il basso. Vale la regola che dichiariamo le route **dalle specifiche alle generali**: ```php -// SBAGLIATO: 'rss.xml' corrisponde alla prima rotta e la interpreta come . +// SBAGLIATO: 'rss.xml' viene catturato dalla prima route e interpreta questa stringa come $router->addRoute('', 'Article:view'); $router->addRoute('rss.xml', 'Feed:rss'); -// BUONO +// GIUSTO $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('', 'Article:view'); ``` -Le rotte vengono valutate dall'alto verso il basso anche quando vengono generati i collegamenti: +Le route vengono valutate dall'alto verso il basso anche durante la generazione dei link: ```php -// SBAGLIATO: genera un link a 'Feed:rss' come 'admin/feed/rss'. +// SBAGLIATO: il link a 'Feed:rss' genera come 'admin/feed/rss' $router->addRoute('admin//', 'Admin:default'); $router->addRoute('rss.xml', 'Feed:rss'); -// BUONO +// GIUSTO $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('admin//', 'Admin:default'); ``` -Non vi nasconderemo che ci vuole una certa abilità per costruire correttamente un elenco. Finché non ci si riesce, il [pannello di instradamento |#Debugging Router] sarà uno strumento utile. +Non ti nasconderemo che la corretta composizione delle route richiede una certa abilità. Prima di padroneggiarla, ti sarà utile il [pannello di routing |#Debug del router]. -Maschera e parametri .[#toc-mask-and-parameters] ------------------------------------------------- +Maschera e parametri +-------------------- -La maschera descrive il percorso relativo basato sulla radice del sito. La maschera più semplice è un URL statico: +La maschera descrive il percorso relativo dalla directory principale del sito web. La maschera più semplice è un URL statico: ```php $router->addRoute('products', 'Products:default'); ``` -Spesso le maschere contengono i cosiddetti **parametri**. Essi sono racchiusi tra parentesi angolari (ad es. ``) e vengono passati al presentatore di destinazione, ad esempio al metodo `renderShow(int $year)` o al parametro persistente `$year`: +Spesso le maschere contengono i cosiddetti **parametri**. Questi sono indicati tra parentesi angolari (es. ``) e vengono passati al presenter di destinazione, ad esempio al metodo `renderShow(int $year)` o al parametro persistente `$year`: ```php $router->addRoute('chronicle/', 'History:show'); ``` -L'esempio dice che se apriamo `https://any-domain.com/chronicle/2020` e l'azione `show` con il parametro `year: 2020`. +L'esempio dice che se apriamo `https://example.com/chronicle/2020` nel browser, verrà visualizzato il presenter `History` con l'azione `show` e il parametro `year: 2020`. -Possiamo specificare un valore predefinito per i parametri direttamente nella maschera, che diventa così facoltativa: +Possiamo specificare un valore predefinito per i parametri direttamente nella maschera, rendendoli così opzionali: ```php $router->addRoute('chronicle/', 'History:show'); ``` -La rotta accetterà ora l'URL `https://any-domain.com/chronicle/` con il parametro `year: 2020`. +La route accetterà ora anche l'URL `https://example.com/chronicle/`, che visualizzerà nuovamente `History:show` con il parametro `year: 2020`. -Naturalmente, anche il nome del presentatore e l'azione possono essere un parametro. Ad esempio: +Un parametro può ovviamente essere anche il nome del presenter e dell'azione. Ad esempio così: ```php $router->addRoute('/', 'Home:default'); ``` -Questo percorso accetta, ad esempio, un URL nella forma `/article/edit` e `/catalog/list` e li traduce in presentatori e azioni `Article:edit` e `Catalog:list`. +La route specificata accetta ad es. URL nella forma `/article/edit` o anche `/catalog/list` e li interpreta come presenter e azioni `Article:edit` e `Catalog:list`. -Inoltre, assegna ai parametri `presenter` e `action` i valori predefiniti`Home` e `default`, che sono quindi facoltativi. Quindi il percorso accetta anche un URL `/article` e lo traduce in `Article:default`. O viceversa, un link a `Product:default` genera un percorso `/product`, un link al default `Home:default` genera un percorso `/`. +Allo stesso tempo, assegna ai parametri `presenter` e `action` i valori predefiniti `Home` e `default` e sono quindi anche opzionali. Quindi la route accetta anche URL nella forma `/article` e la interpreta come `Article:default`. O viceversa, un link a `Product:default` genererà il percorso `/product`, un link al predefinito `Home:default` il percorso `/`. -La maschera può descrivere non solo il percorso relativo basato sulla radice del sito, ma anche il percorso assoluto quando inizia con una barra, o addirittura l'intero URL assoluto quando inizia con due barre: +La maschera può descrivere non solo il percorso relativo dalla directory principale del sito web, ma anche il percorso assoluto, se inizia con uno slash, o persino l'intero URL assoluto, se inizia con due slash: ```php -// percorso relativo alla radice del documento dell'applicazione +// relativamente alla document root $router->addRoute('/', /* ... */); -// percorso assoluto, relativo al nome host del server +// percorso assoluto (relativo al dominio) $router->addRoute('//', /* ... */); -// URL assoluto che include il nome dell'host (ma relativo allo schema) +// URL assoluto incluso il dominio (relativo allo schema) $router->addRoute('//.example.com//', /* ... */); -// URL assoluto comprensivo di schema +// URL assoluto incluso lo schema $router->addRoute('https://.example.com//', /* ... */); ``` -Espressioni di validazione .[#toc-validation-expressions] ---------------------------------------------------------- +Espressioni di validazione +-------------------------- -È possibile specificare una condizione di validazione per ogni parametro utilizzando un ['espressione regolare |https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Per esempio, impostiamo `id` in modo che sia solo numerico, usando la regexp `\d+`: +Per ogni parametro è possibile stabilire una condizione di validazione tramite un'[espressione regolare|https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Ad esempio, per il parametro `id` specifichiamo che può assumere solo cifre tramite l'espressione regolare `\d+`: ```php $router->addRoute('/[/]', /* ... */); ``` -L'espressione regolare predefinita per tutti i parametri è `[^/]+`, cioè tutto tranne lo slash. Se un parametro deve corrispondere anche a una barra, si imposta l'espressione regolare a `.+`. +L'espressione regolare predefinita per tutti i parametri è `[^/]+`, cioè tutto tranne lo slash. Se un parametro deve accettare anche gli slash, specifichiamo l'espressione `.+`: ```php -// accetta https://example.com/a/b/c, il percorso è 'a/b/c' +// accetta https://example.com/a/b/c, path sarà 'a/b/c' $router->addRoute('', /* ... */); ``` -Sequenze opzionali .[#toc-optional-sequences] ---------------------------------------------- +Sequenze opzionali +------------------ -Le parentesi quadre indicano le parti opzionali della maschera. Qualsiasi parte della maschera può essere impostata come opzionale, comprese quelle contenenti parametri: +Nella maschera è possibile contrassegnare parti opzionali tramite parentesi quadre. Qualsiasi parte della maschera può essere opzionale, possono esserci anche parametri al suo interno: ```php $router->addRoute('[/]', /* ... */); -// URL accettati: Parametri: -// /en/download lang => en, name => download -// /download lang => null, name => download +// Accetta percorsi: +// /cs/download => lang => cs, name => download +// /download => lang => null, name => download ``` -Naturalmente, quando un parametro fa parte di una sequenza opzionale, diventa anch'esso opzionale. Se non ha un valore predefinito, sarà nullo. +Quando un parametro fa parte di una sequenza opzionale, diventa ovviamente anche opzionale. Se non ha un valore predefinito specificato, sarà null. -Le sezioni opzionali possono anche essere nel dominio: +Le parti opzionali possono essere anche nel dominio: ```php $router->addRoute('//[.]example.com//', /* ... */); ``` -Le sequenze possono essere liberamente annidate e combinate: +Le sequenze possono essere nidificate e combinate liberamente: ```php $router->addRoute( @@ -163,49 +162,49 @@ $router->addRoute( 'Home:default', ); -// URL accettati: -// /en/hello -// /en-us/hello -// /hello -// /hello/page-12 +// Accetta percorsi: +// /cs/hello +// /en-us/hello +// /hello +// /hello/page-12 ``` -Il generatore di URL cerca di mantenere l'URL il più breve possibile, quindi ciò che può essere omesso viene omesso. Pertanto, ad esempio, un percorso `index[.html]` genera un percorso `/index`. È possibile invertire questo comportamento scrivendo un punto esclamativo dopo la parentesi quadra sinistra: +Durante la generazione dell'URL, si cerca la variante più corta, quindi tutto ciò che può essere omesso viene omesso. Per questo, ad esempio, la route `index[.html]` genera il percorso `/index`. È possibile invertire il comportamento specificando un punto esclamativo dopo la parentesi quadra sinistra: ```php -// accetta sia /hello che /hello.html, genera /hello +// accetta /hello e /hello.html, genera /hello $router->addRoute('[.html]', /* ... */); -// accetta sia /hello che /hello.html, genera /hello.html +// accetta /hello e /hello.html, genera /hello.html $router->addRoute('[!.html]', /* ... */); ``` -I parametri opzionali (cioè quelli che hanno un valore predefinito) senza parentesi quadre si comportano come se fossero avvolti in questo modo: +I parametri opzionali (cioè i parametri con un valore predefinito) senza parentesi quadre si comportano essenzialmente come se fossero racchiusi tra parentesi nel modo seguente: ```php $router->addRoute('//', /* ... */); -// equivale a: +// corrisponde a questo: $router->addRoute('[/[/[]]]', /* ... */); ``` -Per cambiare il modo in cui viene generato lo slash più a destra, cioè invece di `/home/` ottenere un `/home`, regolare il percorso in questo modo: +Se volessimo influenzare il comportamento dello slash finale, in modo che ad esempio invece di `/home/` venga generato solo `/home`, si può ottenere così: ```php $router->addRoute('[[/[/]]]', /* ... */); ``` -Caratteri jolly .[#toc-wildcards] ---------------------------------- +Caratteri jolly +--------------- -Nella maschera del percorso assoluto, si possono usare i seguenti caratteri jolly per evitare, ad esempio, la necessità di scrivere un dominio nella maschera, che può essere diverso nell'ambiente di sviluppo e in quello di produzione: +Nella maschera del percorso assoluto possiamo utilizzare i seguenti caratteri jolly ed evitare così, ad esempio, la necessità di scrivere nella maschera il dominio, che può differire nell'ambiente di sviluppo e produzione: -- `%tld%` = dominio di primo livello, ad esempio `com` o `org` -- `%sld%` = dominio di secondo livello, ad es. `example` -- `%domain%` = dominio senza sottodomini, ad es. `example.com` -- `%host%` = intero host, ad es. `www.example.com` -- `%basePath%` = percorso della directory principale +- `%tld%` = top level domain, es. `com` o `org` +- `%sld%` = second level domain, es. `example` +- `%domain%` = dominio senza sottodomini, es. `example.com` +- `%host%` = intero host, es. `www.example.com` +- `%basePath%` = percorso alla directory principale ```php $router->addRoute('//www.%domain%/%basePath%//', /* ... */); @@ -213,10 +212,10 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ @@ -225,7 +224,7 @@ $router->addRoute('/[/]', [ ]); ``` -Oppure possiamo usare questa forma, notando la riscrittura dell'espressione regolare di validazione: +Per una specifica più dettagliata, è possibile utilizzare una forma ancora più estesa, dove oltre ai valori predefiniti possiamo impostare altre proprietà dei parametri, come ad esempio l'espressione regolare di validazione (vedi parametro `id`): ```php use Nette\Routing\Route; @@ -243,19 +242,19 @@ $router->addRoute('/[/]', [ ]); ``` -Questi formati più loquaci sono utili per aggiungere altri metadati. +È importante notare che se i parametri definiti nell'array non sono specificati nella maschera del percorso, i loro valori non possono essere modificati, nemmeno tramite i parametri query specificati dopo il punto interrogativo nell'URL. -Filtri e traduzioni .[#toc-filters-and-translations] ----------------------------------------------------- +Filtri e traduzioni +------------------- -È buona norma scrivere il codice sorgente in inglese, ma cosa succede se è necessario che il sito web abbia un URL tradotto in una lingua diversa? Percorsi semplici come: +Scriviamo il codice sorgente dell'applicazione in inglese, ma se il sito web deve avere URL in italiano, allora un semplice routing del tipo: ```php $router->addRoute('/', 'Home:default'); ``` -genereranno URL in inglese, come `/product/123` o `/cart`. Se vogliamo che i presenter e le azioni nell'URL siano tradotti in tedesco (per esempio `/produkt/123` o `/einkaufswagen`), possiamo usare un dizionario di traduzione. Per aggiungerlo, abbiamo già bisogno di una variante "più loquace" del secondo parametro: +genererà URL in inglese, come `/product/123` o `/cart`. Se vogliamo che i presenter e le azioni nell'URL siano rappresentati da parole italiane (es. `/prodotto/123` o `/carrello`), possiamo utilizzare un dizionario di traduzione. Per la sua scrittura abbiamo già bisogno della variante "più verbosa" del secondo parametro: ```php use Nette\Routing\Route; @@ -265,25 +264,25 @@ $router->addRoute('/', [ Route::Value => 'Home', Route::FilterTable => [ // stringa nell'URL => presenter - 'produkt' => 'Product', - 'einkaufswagen' => 'Cart', - 'katalog' => 'Catalog', + 'prodotto' => 'Product', + 'carrello' => 'Cart', + 'catalogo' => 'Catalog', ], ], 'action' => [ Route::Value => 'default', Route::FilterTable => [ - 'liste' => 'list', + 'elenco' => 'list', ], ], ]); ``` -È possibile utilizzare più chiavi di dizionario per lo stesso presentatore. Esse creeranno diversi alias per esso. L'ultima chiave è considerata la variante canonica (cioè quella che sarà presente nell'URL generato). +Più chiavi del dizionario di traduzione possono portare allo stesso presenter. In questo modo si creano diversi alias per esso. La variante canonica (cioè quella che sarà nell'URL generato) è considerata l'ultima chiave. -In questo modo, la tabella di traduzione può essere applicata a qualsiasi parametro. Tuttavia, se la traduzione non esiste, viene preso il valore originale. Possiamo modificare questo comportamento aggiungendo `Route::FilterStrict => true` e la rotta rifiuterà l'URL se il valore non è presente nel dizionario. +La tabella di traduzione può essere utilizzata in questo modo per qualsiasi parametro. Se la traduzione non esiste, viene preso il valore originale. Possiamo cambiare questo comportamento aggiungendo `Route::FilterStrict => true` e la route rifiuterà quindi l'URL se il valore non è nel dizionario. -Oltre al dizionario delle traduzioni sotto forma di array, è possibile impostare funzioni di traduzione proprie: +Oltre al dizionario di traduzione sotto forma di array, è possibile implementare anche funzioni di traduzione personalizzate. ```php use Nette\Routing\Route; @@ -299,15 +298,15 @@ $router->addRoute('//', [ ]); ``` -La funzione `Route::FilterIn` converte il parametro dell'URL in stringa, che viene poi passata al presentatore, mentre la funzione `FilterOut` assicura la conversione in senso inverso. +La funzione `Route::FilterIn` converte tra il parametro nell'URL e la stringa, che viene poi passata al presenter, la funzione `FilterOut` assicura la conversione nella direzione opposta. -I parametri `presenter`, `action` e `module` hanno già dei filtri predefiniti che convertono tra lo stile PascalCase o camelCase e kebab-case utilizzato nell'URL. Il valore predefinito dei parametri è già scritto nella forma trasformata, quindi, ad esempio, nel caso di un presentatore, si scrive `` invece di ``. +I parametri `presenter`, `action` e `module` hanno già filtri predefiniti che convertono tra lo stile PascalCase o camelCase e kebab-case utilizzato nell'URL. Il valore predefinito dei parametri viene scritto già nella forma trasformata, quindi ad esempio nel caso del presenter scriviamo ``, non ``. -Filtri generali .[#toc-general-filters] ---------------------------------------- +Filtri generali +--------------- -Oltre ai filtri per parametri specifici, è possibile definire anche filtri generali che ricevono un array associativo di tutti i parametri che possono modificare in qualsiasi modo e poi restituire. I filtri generali sono definiti sotto il tasto `null`. +Oltre ai filtri destinati a parametri specifici, possiamo definire anche filtri generali, che ricevono un array associativo di tutti i parametri, che possono modificare in qualsiasi modo e poi restituirli. I filtri generali li definiamo sotto la chiave `null`. ```php use Nette\Routing\Route; @@ -315,22 +314,22 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => 'Home', 'action' => 'default', - null => [ + '' => [ Route::FilterIn => function (array $params): array { /* ... */ }, Route::FilterOut => function (array $params): array { /* ... */ }, ], ]); ``` -I filtri generali danno la possibilità di regolare il comportamento del percorso in qualsiasi modo. Si possono usare, ad esempio, per modificare i parametri in base ad altri parametri. Ad esempio, la traduzione `` e `` in base al valore corrente del parametro ``. +I filtri generali danno la possibilità di modificare il comportamento della route in modo assolutamente qualsiasi. Possiamo usarli ad esempio per modificare i parametri in base ad altri parametri. Ad esempio, la traduzione di `` e `` in base al valore corrente del parametro ``. -Se per un parametro è stato definito un filtro personalizzato e contemporaneamente esiste un filtro generale, il filtro personalizzato `FilterIn` viene eseguito prima del generale e viceversa il generale `FilterOut` viene eseguito prima del personalizzato. Quindi, all'interno del filtro generale si trovano i valori dei parametri `presenter` risp. `action` scritti in stile PascalCase risp. camelCase. +Se un parametro ha definito un filtro personalizzato e contemporaneamente esiste un filtro generale, viene eseguito il `FilterIn` personalizzato prima di quello generale e viceversa il `FilterOut` generale prima di quello personalizzato. Quindi all'interno del filtro generale i valori dei parametri `presenter` o `action` sono scritti nello stile PascalCase o camelCase. -Bandiera unidirezionale .[#toc-oneway-flag] -------------------------------------------- +Sensi unici OneWay +------------------ -Le rotte a senso unico sono utilizzate per preservare la funzionalità di vecchi URL che l'applicazione non genera più, ma che accetta ancora. Le segnaliamo con `OneWay`: +Le route a senso unico vengono utilizzate per mantenere la funzionalità dei vecchi URL, che l'applicazione non genera più, ma accetta ancora. Le contrassegniamo con il flag `OneWay`: ```php // vecchio URL /product-info?id=123 @@ -339,38 +338,61 @@ $router->addRoute('product-info', 'Product:detail', $router::ONE_WAY); $router->addRoute('product/', 'Product:detail'); ``` -Quando si accede al vecchio URL, il presentatore reindirizza automaticamente al nuovo URL, in modo che i motori di ricerca non indicizzino queste pagine due volte (vedere [SEO e canonizzazione |#SEO and canonization]). +Accedendo al vecchio URL, il presenter reindirizza automaticamente al nuovo URL, così i motori di ricerca non indicizzeranno due volte queste pagine (vedi [#SEO e canonizzazione]). + + +Routing dinamico con callback +----------------------------- + +Il routing dinamico con callback ti consente di assegnare direttamente alle route funzioni (callback) che vengono eseguite quando viene visitato il percorso dato. Questa funzionalità flessibile ti consente di creare rapidamente ed efficacemente vari endpoint per la tua applicazione: + +```php +$router->addRoute('test', function () { + echo 'sei all\'indirizzo /test'; +}); +``` + +Puoi anche definire parametri nella maschera, che verranno automaticamente passati al tuo callback: + +```php +$router->addRoute('', function (string $lang) { + echo match ($lang) { + 'cs' => 'Vítejte na české verzi našeho webu!', // Welcome to the Czech version of our website! + 'en' => 'Welcome to the English version of our website!', + }; +}); +``` -Moduli .[#toc-modules] ----------------------- +Moduli +------ -Se abbiamo più rotte che appartengono a un [modulo |modules], possiamo usare `withModule()` per raggrupparle: +Se abbiamo più route che rientrano in un [modulo |directory-structure#Presenter e template] comune, utilizziamo `withModule()`: ```php $router = new RouteList; -$router->withModule('Forum') // i seguenti router fanno parte del modulo Forum - ->addRoute('rss', 'Feed:rss') // il presentatore è Forum:Feed +$router->withModule('Forum') // le seguenti route fanno parte del modulo Forum + ->addRoute('rss', 'Feed:rss') // il presenter sarà Forum:Feed ->addRoute('/') - ->withModule('Admin') // i seguenti router fanno parte del modulo Forum:Admin + ->withModule('Admin') // le seguenti route fanno parte del modulo Forum:Admin ->addRoute('sign:in', 'Sign:in'); ``` -Un'alternativa è usare il parametro `module`: +Un'alternativa è l'uso del parametro `module`: ```php -// L'URL manage/dashboard/default si riferisce al presentatore Admin:Dashboard +// L'URL manage/dashboard/default si mappa sul presenter Admin:Dashboard $router->addRoute('manage//', [ 'module' => 'Admin', ]); ``` -Sottodomini .[#toc-subdomains] ------------------------------- +Sottodomini +----------- -Le raccolte di rotte possono essere raggruppate per sottodomini: +Possiamo suddividere le collezioni di route per sottodomini: ```php $router = new RouteList; @@ -379,7 +401,7 @@ $router->withDomain('example.com') ->addRoute('/'); ``` -È inoltre possibile utilizzare i [caratteri jolly |#wildcards] nel nome del dominio: +Nel nome del dominio è possibile utilizzare anche [#Caratteri jolly]: ```php $router = new RouteList; @@ -388,23 +410,23 @@ $router->withDomain('example.%tld%') ``` -Prefisso di percorso .[#toc-path-prefix] ----------------------------------------- +Prefisso del percorso +--------------------- -Le raccolte di rotte possono essere raggruppate per percorso nell'URL: +Possiamo suddividere le collezioni di route per percorso nell'URL: ```php $router = new RouteList; $router->withPath('eshop') - ->addRoute('rss', 'Feed:rss') // corrisponde all'URL /eshop/rss - ->addRoute('/'); // corrisponde all'URL /eshop// + ->addRoute('rss', 'Feed:rss') // cattura l'URL /eshop/rss + ->addRoute('/'); // cattura l'URL /eshop// ``` -Combinazioni .[#toc-combinations] ---------------------------------- +Combinazioni +------------ -L'uso sopra descritto può essere combinato: +Le suddivisioni sopra menzionate possono essere combinate tra loro: ```php $router = (new RouteList) @@ -424,40 +446,40 @@ $router = (new RouteList) ``` -Parametri della query .[#toc-query-parameters] ----------------------------------------------- +Parametri query +--------------- -Le maschere possono contenere anche parametri di query (parametri dopo il punto interrogativo nell'URL). Non possono definire un'espressione di validazione, ma possono cambiare il nome con cui vengono passati al presentatore: +Le maschere possono anche contenere parametri query (parametri dopo il punto interrogativo nell'URL). A questi non è possibile definire un'espressione di validazione, ma è possibile cambiare il nome con cui vengono passati al presenter: ```php -// utilizzare il parametro di query 'cat' come 'categoryId' nell'applicazione +// il parametro query 'cat' vogliamo usarlo nell'applicazione con il nome 'categoryId' $router->addRoute('product ? id= & cat=', /* ... */); ``` -Parametri Foo .[#toc-foo-parameters] ------------------------------------- +Parametri Foo +------------- -Ora stiamo andando più a fondo. I parametri Foo sono fondamentalmente parametri senza nome che permettono di corrispondere a un'espressione regolare. Il percorso seguente corrisponde a `/index`, `/index.html`, `/index.htm` e `/index.php`: +Ora andiamo più a fondo. I parametri Foo sono essenzialmente parametri senza nome che consentono di abbinare un'espressione regolare. Un esempio è una route che accetta `/index`, `/index.html`, `/index.htm` e `/index.php`: ```php $router->addRoute('index', /* ... */); ``` -È anche possibile definire esplicitamente una stringa che sarà usata per la generazione dell'URL. La stringa deve essere posta direttamente dopo il punto interrogativo. Il percorso seguente è simile al precedente, ma genera `/index.html` invece di `/index`, perché la stringa `.html` è impostata come "valore generato". +È anche possibile definire esplicitamente la stringa che verrà utilizzata durante la generazione dell'URL. La stringa deve essere posizionata direttamente dopo il punto interrogativo. La seguente route è simile alla precedente, ma genera `/index.html` invece di `/index`, perché la stringa `.html` è impostata come valore di generazione: ```php $router->addRoute('index', /* ... */); ``` -Integrazione .[#toc-integration] -================================ +Integrazione nell'applicazione +============================== -Per collegare il nostro router all'applicazione, dobbiamo comunicarlo al contenitore DI. Il modo più semplice è preparare il factory che costruirà l'oggetto router e dire al contenitore di configurazione di usarlo. Diciamo quindi di scrivere un metodo a questo scopo `App\Router\RouterFactory::createRouter()`: +Per integrare il router creato nell'applicazione, dobbiamo informarne il container DI. Il modo più semplice è preparare una factory che produca l'oggetto router e comunicare nella configurazione del container che deve usarla. Supponiamo di scrivere a tale scopo il metodo `App\Core\RouterFactory::createRouter()`: ```php -namespace App\Router; +namespace App\Core; use Nette\Application\Routers\RouteList; @@ -472,14 +494,14 @@ class RouterFactory } ``` -Poi scriviamo nella [configurazione |dependency-injection:services]: +Nella [configurazione |dependency-injection:services] scriveremo quindi: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter ``` -Tutte le dipendenze, come la connessione al database, ecc. vengono passate al metodo factory come parametri, utilizzando l'[autowiring |dependency-injection:autowiring]: +Qualsiasi dipendenza, ad esempio dal database ecc., viene passata al metodo factory come suoi parametri tramite [autowiring|dependency-injection:autowiring]: ```php public static function createRouter(Nette\Database\Connection $db): RouteList @@ -489,25 +511,25 @@ public static function createRouter(Nette\Database\Connection $db): RouteList ``` -Router semplice .[#toc-simplerouter] -==================================== +SimpleRouter +============ -Un router molto più semplice della Route Collection è [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Può essere usato quando non c'è bisogno di un formato URL specifico, quando `mod_rewrite` (o alternative) non è disponibile o quando semplicemente non si vuole ancora preoccuparsi di URL facili da usare. +Un router molto più semplice della collezione di route è [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Lo useremo quando non abbiamo particolari esigenze sulla forma dell'URL, se non è disponibile `mod_rewrite` (o le sue alternative) o se per ora non vogliamo occuparci di URL leggibili. -Genera indirizzi più o meno in questa forma: +Genera indirizzi approssimativamente in questa forma: ``` http://example.com/?presenter=Product&action=detail&id=123 ``` -Il parametro del costruttore `SimpleRouter` è un presentatore e un'azione predefiniti, cioè l'azione da eseguire se si apre, ad esempio, `http://example.com/` senza ulteriori parametri. +Il parametro del costruttore di SimpleRouter è il presenter & azione predefinito, a cui si deve puntare se apriamo la pagina senza parametri, ad es. `http://example.com/`. ```php -// default al presenter 'Home' e all'azione 'default' +// il presenter predefinito sarà 'Home' e l'azione 'default' $router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` -Si consiglia di definire SimpleRouter direttamente nella [configurazione |dependency-injection:services]: +Consigliamo di definire SimpleRouter direttamente nella [configurazione |dependency-injection:services]: ```neon services: @@ -515,22 +537,22 @@ services: ``` -SEO e canonizzazione .[#toc-seo-and-canonization] -================================================= +SEO e canonizzazione +==================== -Il framework aumenta la SEO (ottimizzazione per i motori di ricerca) impedendo la duplicazione di contenuti in URL diversi. Se più indirizzi rimandano a una stessa destinazione, ad esempio `/index` e `/index.html`, il framework determina il primo come primario (canonico) e reindirizza gli altri ad esso utilizzando il codice HTTP 301. In questo modo, i motori di ricerca non indicizzeranno le pagine due volte e non ne comprometteranno il page rank. . +Il framework contribuisce al SEO (ottimizzazione della reperibilità su Internet) impedendo la duplicazione di contenuti su URL diversi. Se a una determinata destinazione portano più indirizzi, ad es. `/index` e `/index.html`, il framework determina il primo come primario (canonico) e reindirizza gli altri ad esso tramite il codice HTTP 301. Grazie a ciò, i motori di ricerca non indicizzano le pagine due volte e non diluiscono il loro page rank. -Questo processo è chiamato canonizzazione. L'URL canonico è quello generato dal router, cioè dal primo percorso corrispondente nella [raccolta |#route-collection] senza il flag OneWay. Pertanto, nella raccolta, vengono elencati per primi **i percorsi primari**. +Questo processo si chiama canonizzazione. L'URL canonico è quello generato dal router, cioè la prima route corrispondente nella collezione senza il flag OneWay. Pertanto, nella collezione elenchiamo **le route primarie per prime**. -La canonizzazione viene eseguita dal presentatore, come descritto nel capitolo [Canonizzazione |presenters#Canonization]. +La canonizzazione viene eseguita dal presenter, maggiori informazioni nel capitolo [canonizzazione |presenters#Canonizzazione]. -HTTPS .[#toc-https] -=================== +HTTPS +===== -Per utilizzare il protocollo HTTPS, è necessario attivarlo sull'hosting e configurare il server. +Per poter utilizzare il protocollo HTTPS, è necessario abilitarlo sull'hosting e configurare correttamente il server. -Il reindirizzamento dell'intero sito verso HTTPS deve essere eseguito a livello di server, ad esempio utilizzando il file .htaccess nella directory principale della nostra applicazione, con il codice HTTP 301. Le impostazioni possono variare a seconda dell'hosting e hanno un aspetto simile a questo: +Il redirect dell'intero sito a HTTPS deve essere impostato a livello di server, ad esempio tramite il file .htaccess nella directory principale della nostra applicazione, e con il codice HTTP 301. L'impostazione può variare a seconda dell'hosting e assomiglia circa a questo: ``` @@ -542,40 +564,40 @@ Il reindirizzamento dell'intero sito verso HTTPS deve essere eseguito a livello ``` -Il router genera un URL con lo stesso protocollo con cui è stata caricata la pagina, quindi non è necessario impostare altro. +Il router genera URL con lo stesso protocollo con cui è stata caricata la pagina, quindi non è necessario impostare nient'altro. -Tuttavia, se abbiamo eccezionalmente bisogno di rotte diverse da eseguire con protocolli diversi, lo inseriremo nella maschera di rotta: +Se però eccezionalmente abbiamo bisogno che diverse route vengano eseguite sotto protocolli diversi, lo specifichiamo nella maschera della route: ```php -// Genererà un indirizzo HTTP +// Genererà un indirizzo con HTTP $router->addRoute('http://%host%//', /* ... */); -// Genererà un indirizzo HTTPS +// Genererà un indirizzo con HTTPs $router->addRoute('https://%host%//', /* ... */); ``` -Debug del router .[#toc-debugging-router] -========================================= +Debug del router +================ -La barra di routing visualizzata in [Tracy Bar |tracy:] è un utile strumento che mostra un elenco di percorsi e anche i parametri che il router ha ottenuto dall'URL. +Il pannello di routing visualizzato nella [Tracy Bar |tracy:] è un aiuto utile che mostra l'elenco delle route e anche i parametri che il router ha ottenuto dall'URL. -La barra verde con il simbolo ✓ rappresenta il percorso che corrisponde all'URL corrente, le barre blu con i simboli ≈ indicano i percorsi che corrisponderebbero all'URL se il verde non li superasse. Vediamo ulteriormente il presentatore e l'azione corrente. +La barra verde con il simbolo ✓ rappresenta la route che ha elaborato l'URL corrente, con il colore blu e il simbolo ≈ sono contrassegnate le route che avrebbero elaborato anche l'URL se la verde non le avesse precedute. Vediamo inoltre il presenter & azione corrente. [* routing-debugger.webp *] -Allo stesso tempo, se c'è un reindirizzamento inaspettato dovuto alla [canonicalizzazione |#SEO and Canonization], è utile guardare nella barra *redirect* per vedere come il router ha originariamente compreso l'URL e perché ha reindirizzato. +Allo stesso tempo, se si verifica un redirect inaspettato a causa della [canonizzazione |#SEO e canonizzazione], è utile guardare il pannello nella barra *redirect*, dove scoprirete come il router ha originariamente compreso l'URL e perché ha reindirizzato. .[note] -Quando si esegue il debug del router, si consiglia di aprire gli Strumenti per sviluppatori nel browser (Ctrl+Maiusc+I o Cmd+Opzione+I) e di disabilitare la cache nel pannello Rete, in modo che i reindirizzamenti non vengano memorizzati. +Durante il debug del router, consigliamo di aprire gli Strumenti per sviluppatori nel browser (Ctrl+Shift+I o Cmd+Option+I) e nel pannello Network disattivare la cache, in modo che non vi vengano salvati i redirect. -Prestazioni .[#toc-performance] -=============================== +Prestazioni +=========== -Il numero di rotte influisce sulla velocità del router. Il loro numero non dovrebbe superare le poche decine. Se il sito ha una struttura di URL troppo complicata, si può scrivere un [router personalizzato |#custom router]. +Il numero di route influisce sulla velocità del router. Il loro numero non dovrebbe assolutamente superare alcune decine. Se il tuo sito web ha una struttura URL troppo complicata, puoi scrivere un [#Router personalizzato] su misura. -Se il router non ha dipendenze, ad esempio da un database, e il suo factory non ha argomenti, possiamo serializzare la sua forma compilata direttamente in un contenitore DI e quindi rendere l'applicazione leggermente più veloce. +Se il router non ha dipendenze, ad esempio dal database, e la sua factory non accetta argomenti, possiamo serializzare la sua forma compilata direttamente nel container DI e accelerare così leggermente l'applicazione. ```neon routing: @@ -583,10 +605,10 @@ routing: ``` -Router personalizzato .[#toc-custom-router] -=========================================== +Router personalizzato +===================== -Le righe seguenti sono destinate a utenti molto avanzati. È possibile creare il proprio router e aggiungerlo naturalmente alla propria collezione di rotte. Il router è un'implementazione dell'interfaccia [api:Nette\Routing\Router] con due metodi: +Le righe seguenti sono destinate a utenti molto avanzati. Puoi creare un tuo router personalizzato e integrarlo in modo del tutto naturale nella collezione di route. Il router è un'implementazione dell'interfaccia [api:Nette\Routing\Router] con due metodi: ```php use Nette\Http\IRequest as HttpRequest; @@ -606,8 +628,7 @@ class MyRouter implements Nette\Routing\Router } ``` -Il metodo `match` elabora la [$httpRequest |http:request] corrente, da cui è possibile recuperare non solo l'URL, ma anche le intestazioni ecc. in un array contenente il nome del presentatore e i suoi parametri. Se non può elaborare la richiesta, restituisce null. -Quando si elabora la richiesta, è necessario restituire almeno il presentatore e l'azione. Il nome del presentatore è completo e include eventuali moduli: +Il metodo `match` elabora la richiesta corrente [$httpRequest |http:request], dalla quale è possibile ottenere non solo l'URL, ma anche gli header, ecc., in un array contenente il nome del presenter e i suoi parametri. Se non sa elaborare la richiesta, restituisce null. Durante l'elaborazione della richiesta, dobbiamo restituire almeno il presenter e l'azione. Il nome del presenter è completo e contiene anche eventuali moduli: ```php [ @@ -616,9 +637,9 @@ Quando si elabora la richiesta, è necessario restituire almeno il presentatore ] ``` -Il metodo `constructUrl`, invece, genera un URL assoluto dall'array di parametri. Può utilizzare le informazioni del parametro `$refUrl`, che è l'URL corrente. +Il metodo `constructUrl` al contrario costruisce dall'array di parametri l'URL assoluto risultante. A tal fine può utilizzare le informazioni dal parametro [`$refUrl`|api:Nette\Http\UrlScript], che è l'URL corrente. -Per aggiungere un router personalizzato alla collezione di rotte, utilizzare `add()`: +Lo aggiungi alla collezione di route usando `add()`: ```php $router = new Nette\Application\Routers\RouteList; @@ -628,19 +649,19 @@ $router->addRoute(/* ... */); ``` -Uso separato .[#toc-separated-usage] -==================================== +Utilizzo indipendente +===================== -Per uso separato si intende l'uso delle funzionalità del router in un'applicazione che non utilizza l'applicazione Nette e i presentatori. Quasi tutto ciò che è stato mostrato in questo capitolo è applicabile, con le seguenti differenze: +Per utilizzo indipendente intendiamo l'utilizzo delle capacità del router in un'applicazione che non utilizza Nette Application e i presenter. Vale per esso quasi tutto ciò che abbiamo mostrato in questo capitolo, con queste differenze: -- per le raccolte di rotte usiamo la classe [api:Nette\Routing\RouteList] -- come semplice classe router [api:Nette\Routing\SimpleRouter] -- poiché non c'è la coppia `Presenter:action`, usiamo la [notazione avanzata |#Advanced notation] +- per le collezioni di route utilizziamo la classe [api:Nette\Routing\RouteList] +- come simple router la classe [api:Nette\Routing\SimpleRouter] +- poiché non esiste la coppia `Presenter:action`, utilizziamo la [#Notazione estesa] -Quindi creeremo di nuovo un metodo che costruirà un router, ad esempio: +Quindi di nuovo creiamo un metodo che ci costruisca il router, ad es.: ```php -namespace App\Router; +namespace App\Core; use Nette\Routing\RouteList; @@ -661,35 +682,35 @@ class RouterFactory } ``` -Se si usa un contenitore DI, cosa che consigliamo, si aggiunge nuovamente il metodo alla configurazione e si ottiene il router insieme alla richiesta HTTP dal contenitore: +Se usi un container DI, cosa che consigliamo, aggiungiamo di nuovo il metodo alla configurazione e poi otteniamo il router insieme alla richiesta HTTP dal container: ```php $router = $container->getByType(Nette\Routing\Router::class); $httpRequest = $container->getByType(Nette\Http\IRequest::class); ``` -Oppure creeremo direttamente gli oggetti: +Oppure creiamo direttamente gli oggetti: ```php -$router = App\Router\RouterFactory::createRouter(); +$router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` -Ora dobbiamo far funzionare il router: +Ora resta solo da mettere al lavoro il router: ```php $params = $router->match($httpRequest); if ($params === null) { - // non è stata trovata alcuna rotta corrispondente, verrà inviato un errore 404 + // non è stata trovata una route corrispondente, inviamo errore 404 exit; } -// elaboriamo i parametri ricevuti +// elaboriamo i parametri ottenuti $controller = $params['controller']; // ... ``` -E viceversa, useremo il router per creare il collegamento: +E viceversa usiamo il router per costruire un link: ```php $params = ['controller' => 'ArticleController', 'id' => 123]; diff --git a/application/it/templates.texy b/application/it/templates.texy index 9f9da1b537..2080f7cd54 100644 --- a/application/it/templates.texy +++ b/application/it/templates.texy @@ -1,16 +1,16 @@ -Modelli -******* +Template +******** .[perex] -Nette utilizza il sistema di template [Latte |latte:]. Latte è utilizzato perché è il sistema di template più sicuro per PHP e allo stesso tempo il più intuitivo. Non è necessario imparare molto di nuovo, basta conoscere PHP e alcuni tag di Latte. +Nette utilizza il sistema di templating [Latte |latte:]. Questo perché è il sistema di templating più sicuro per PHP, e allo stesso tempo il sistema più intuitivo. Non devi imparare molto di nuovo, ti basta la conoscenza di PHP e alcuni tag. -Di solito la pagina viene completata dal modello di layout + il modello di azione. Ecco come potrebbe apparire un modello di layout, notando i blocchi `{block}` e il tag `{include}`: +È comune che una pagina sia composta da un template di layout + il template dell'azione specifica. Ecco come potrebbe apparire un template di layout, nota i blocchi `{block}` e il tag `{include}`: ```latte - {block title}My App{/block} + {block title}La mia App{/block}
    ...
    @@ -20,7 +20,7 @@ Di solito la pagina viene completata dal modello di layout + il modello di azion ``` -E questo potrebbe essere il modello di azione: +E questo sarà il template dell'azione: ```latte {block title}Homepage{/block} @@ -31,50 +31,98 @@ E questo potrebbe essere il modello di azione: {/block} ``` -Definisce il blocco `content`, che viene inserito al posto di `{include content}` nel layout, e ridefinisce anche il blocco `title`, che sovrascrive `{block title}` nel layout. Provate a immaginare il risultato. +Definisce il blocco `content`, che verrà inserito al posto di `{include content}` nel layout, e ridefinisce anche il blocco `title`, che sovrascriverà `{block title}` nel layout. Prova a immaginare il risultato. -Ricerca dei modelli .[#toc-search-for-templates] ------------------------------------------------- +Ricerca dei template +-------------------- -Il percorso dei modelli viene dedotto secondo una semplice logica. Si cerca di vedere se uno di questi file di template esiste relativamente alla directory in cui si trova la classe del presentatore, dove `` è il nome del presentatore corrente e `` è il nome dell'azione corrente: +Non devi specificare nei presenter quale template deve essere renderizzato, il framework deduce il percorso da solo e ti risparmia la scrittura. -- `templates//.latte` -- `templates/..latte` +Se utilizzi una struttura di directory in cui ogni presenter ha la propria directory, posiziona semplicemente il template in questa directory con il nome dell'azione (o view), cioè per l'azione `default` usa il template `default.latte`: -Se non trova il modello, la risposta è un [errore 404 |presenters#Error 404 etc.]. +/--pre +app/ +└── Presentation/ + └── Home/ + ├── HomePresenter.php + └── default.latte +\-- -Si può anche cambiare la vista usando `$this->setView('otherView')`. Oppure, invece di cercare, specificare direttamente il nome del file del template usando `$this->template->setFile('/path/to/template.latte')`. +Se utilizzi una struttura in cui i presenter sono insieme in una directory e i template nella cartella `templates`, salvalo o nel file `..latte` o `/.latte`: + +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── Home.default.latte ← 1a variante + └── Home/ + └── default.latte ← 2a variante +\-- + +La directory `templates` può trovarsi anche un livello sopra, cioè allo stesso livello della directory con le classi dei presenter. + +Se il template non viene trovato, il presenter risponde con un [errore 404 - page not found |presenters#Errore 404 e simili]. + +La view viene cambiata usando `$this->setView('altraView')`. È anche possibile specificare direttamente il file del template usando `$this->template->setFile('/path/to/template.latte')`. .[note] -È possibile modificare i percorsi in cui vengono cercati i modelli sovrascrivendo il metodo [formatTemplateFiles |api:Nette\Application\UI\Presenter::formatTemplateFiles()], che restituisce un array di possibili percorsi di file. +I file in cui vengono cercati i template possono essere modificati sovrascrivendo il metodo [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], che restituisce un array di possibili nomi di file. + + +Ricerca del template di layout +------------------------------ + +Nette cerca automaticamente anche il file di layout. + +Se utilizzi una struttura di directory in cui ogni presenter ha la propria directory, posiziona il layout o nella cartella con il presenter, se è specifico solo per esso, o un livello sopra, se è comune a più presenter: + +/--pre +app/ +└── Presentation/ + ├── @layout.latte ← layout comune + └── Home/ + ├── @layout.latte ← solo per il presenter Home + ├── HomePresenter.php + └── default.latte +\-- + +Se utilizzi una struttura in cui i presenter sono insieme in una directory e i template nella cartella `templates`, il layout sarà atteso in queste posizioni: -Il layout è previsto nei seguenti file: +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── @layout.latte ← layout comune + ├── Home.@layout.latte ← solo per Home, 1a variante + └── Home/ + └── @layout.latte ← solo per Home, 2a variante +\-- -- `templates//@.latte` -- `templates/.@.latte` -- `templates/@.latte` layout comune a più presentatori +Se il presenter si trova in un modulo, la ricerca avverrà anche ai livelli di directory superiori, in base alla nidificazione del modulo. -`` è il nome del presentatore corrente e `` è il nome del layout, che per impostazione predefinita è `'layout'`. Il nome può essere modificato con `$this->setLayout('otherLayout')`, in modo da provare i file `@otherLayout.latte`. +Il nome del layout può essere cambiato usando `$this->setLayout('layoutAdmin')` e quindi sarà atteso nel file `@layoutAdmin.latte`. È anche possibile specificare direttamente il file del template di layout usando `$this->setLayout('/path/to/template.latte')`. -È anche possibile specificare direttamente il nome del file del modello di layout con `$this->setLayout('/path/to/template.latte')`. L'uso di `$this->setLayout(false)` disabilita la ricerca dei layout. +Usando `$this->setLayout(false)` o il tag `{layout none}` all'interno del template, la ricerca del layout viene disattivata. .[note] -È possibile modificare i percorsi in cui vengono cercati i modelli sovrascrivendo il metodo [formatLayoutTemplateFiles |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], che restituisce un array di possibili percorsi di file. +I file in cui vengono cercati i template di layout possono essere modificati sovrascrivendo il metodo [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], che restituisce un array di possibili nomi di file. -Variabili nel modello .[#toc-variables-in-the-template] -------------------------------------------------------- +Variabili nel template +---------------------- -Le variabili vengono passate al template scrivendole in `$this->template` e poi sono disponibili nel template come variabili locali: +Passiamo le variabili al template scrivendole in `$this->template` e poi le abbiamo disponibili nel template come variabili locali: ```php $this->template->article = $this->articles->getById($id); ``` -In questo modo possiamo passare facilmente qualsiasi variabile ai template. Tuttavia, quando si sviluppano applicazioni robuste, spesso è più utile limitarsi. Per esempio, definendo esplicitamente un elenco di variabili che il template si aspetta e i loro tipi. Ciò consentirà a PHP di effettuare il controllo dei tipi, all'IDE di effettuare il completamento automatico in modo corretto e all'analisi statica di rilevare gli errori. +In questo modo semplice possiamo passare qualsiasi variabile ai template. Tuttavia, nello sviluppo di applicazioni robuste, è più utile limitarsi. Ad esempio, definendo esplicitamente l'elenco delle variabili che il template si aspetta e i loro tipi. Grazie a ciò, PHP potrà controllare i tipi, l'IDE suggerirà correttamente e l'analisi statica rivelerà errori. -Come si definisce un'enumerazione di questo tipo? Semplicemente sotto forma di una classe e delle sue proprietà. La chiamiamo in modo simile a presenter, ma con `Template` alla fine: +E come definiamo tale elenco? Semplicemente sotto forma di una classe e delle sue properties. La nominiamo in modo simile al presenter, ma con `Template` alla fine: ```php /** @@ -93,22 +141,22 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template } ``` -L'oggetto `$this->template` nel presentatore sarà ora un'istanza della classe `ArticleTemplate`. Quindi PHP controllerà i tipi dichiarati quando vengono scritti. A partire da PHP 8.2, inoltre, avvertirà di scrivere su una variabile inesistente; nelle versioni precedenti si può ottenere lo stesso risultato utilizzando il tratto [Nette\SmartObject |utils:smartobject]. +L'oggetto `$this->template` nel presenter sarà ora un'istanza della classe `ArticleTemplate`. Quindi PHP controllerà i tipi dichiarati durante la scrittura. E a partire dalla versione PHP 8.2 avviserà anche sulla scrittura in una variabile inesistente, nelle versioni precedenti si può ottenere lo stesso risultato usando il trait [Nette\SmartObject |utils:smartobject]. -L'annotazione `@property-read` è per l'IDE e l'analisi statica, farà funzionare il completamento automatico, vedere "PhpStorm e il completamento del codice per $this->template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. +L'annotazione `@property-read` è destinata all'IDE e all'analisi statica, grazie ad essa funzionerà il suggerimento, vedi [PhpStorm and code completion for $this⁠-⁠>⁠template |https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template]. [* phpstorm-completion.webp *] -Ci si può concedere il lusso di sussurrare anche nei template, basta installare il plugin Latte in PhpStorm e specificare il nome della classe all'inizio del template, si veda l'articolo "Latte: come digitare il sistema":https://blog.nette.org/it/latte-come-usare-il-sistema-di-tipi: +Puoi goderti il lusso del suggerimento anche nei template, basta installare il plugin per Latte in PhpStorm e specificare all'inizio del template il nome della classe, maggiori informazioni nell'articolo [Latte: jak na typový systém|https://blog.nette.org/it/latte-how-to-use-type-system]: ```latte -{templateType App\Presenters\ArticleTemplate} +{templateType App\Presentation\Article\ArticleTemplate} ... ``` -Questo è anche il modo in cui i template funzionano nei componenti, basta seguire la convenzione di denominazione e creare una classe template `FifteenTemplate` per il componente, ad esempio `FifteenControl`. +Così funzionano anche i template nei componenti, basta solo rispettare la convenzione di denominazione e per un componente ad es. `FifteenControl` creare una classe di template `FifteenTemplate`. -Se si vuole creare una classe `$template` come istanza di un'altra classe, usare il metodo `createTemplate()`: +Se hai bisogno di creare `$template` come istanza di un'altra classe, utilizza il metodo `createTemplate()`: ```php public function renderDefault(): void @@ -121,43 +169,43 @@ public function renderDefault(): void ``` -Variabili predefinite .[#toc-default-variables] ------------------------------------------------ +Variabili predefinite +--------------------- -I presentatori e i componenti passano automaticamente diverse variabili utili ai modelli: +I presenter e i componenti passano automaticamente diverse variabili utili ai template: -- `$basePath` è un percorso URL assoluto alla cartella principale (ad esempio `/CD-collection`) -- `$baseUrl` è un URL assoluto alla cartella principale (per esempio `http://localhost/CD-collection`) -- `$user` è un oggetto [che rappresenta l'utente |security:authentication] -- `$presenter` è il presentatore corrente -- `$control` è il componente o presentatore corrente -- `$flashes` è un elenco di [messaggi |presenters#flash-messages] inviati dal metodo `flashMessage()` +- `$basePath` è il percorso URL assoluto alla directory principale (es. `/eshop`) +- `$baseUrl` è l'URL assoluto alla directory principale (es. `http://localhost/eshop`) +- `$user` è l'oggetto [che rappresenta l'utente |security:authentication] +- `$presenter` è il presenter corrente +- `$control` è il componente o presenter corrente +- `$flashes` è l'array di [messaggi |presenters#Messaggi flash] inviati dalla funzione `flashMessage()` -Se si utilizza una classe modello personalizzata, queste variabili vengono passate se si crea una proprietà per esse. +Se utilizzi una classe di template personalizzata, queste variabili vengono passate se crei una property per esse. -Creazione di collegamenti .[#toc-creating-links] ------------------------------------------------- +Creazione di link +----------------- -Nel modello si creano collegamenti ad altri presentatori e azioni come segue: +Nel template, i link ad altri presenter & azioni vengono creati in questo modo: ```latte -detail +dettaglio prodotto ``` L'attributo `n:href` è molto utile per i tag HTML ``. Se vogliamo stampare il link altrove, ad esempio nel testo, usiamo `{link}`: ```latte -URL is: {link Home:default} +L'indirizzo è: {link Home:default} ``` -Per ulteriori informazioni, vedere [Creazione di collegamenti |Creating Links]. +Maggiori informazioni si trovano nel capitolo [Creazione di link URL|creating-links]. -Filtri personalizzati, tag, ecc. .[#toc-custom-filters-tags-etc] ----------------------------------------------------------------- +Filtri personalizzati, tag, ecc. +-------------------------------- -Il sistema di template Latte può essere esteso con filtri personalizzati, funzioni, tag, ecc. Questo può essere fatto direttamente nel metodo `render` o nel metodo `beforeRender()`: +Il sistema di templating Latte può essere esteso con filtri, funzioni, tag personalizzati, ecc. Ciò può essere fatto direttamente nel metodo `render` o `beforeRender()`: ```php public function beforeRender(): void @@ -165,16 +213,16 @@ public function beforeRender(): void // aggiunta di un filtro $this->template->addFilter('foo', /* ... */); - // oppure configurare direttamente l'oggetto LatteEngine + // o configuriamo direttamente l'oggetto Latte\Engine $latte = $this->template->getLatte(); $latte->addFilterLoader(/* ... */); } ``` -La versione 3 di Latte offre un metodo più avanzato, creando un'[estensione |latte:creating-extension] per ogni progetto web. Ecco un esempio approssimativo di una classe di questo tipo: +Latte nella versione 3 offre un modo più avanzato, ovvero creare un'[extension |latte:extending-latte#Latte Extension] per ogni progetto web. Esempio parziale di tale classe: ```php -namespace App\Templating; +namespace App\Presentation\Accessory; final class LatteExtension extends Latte\Extension { @@ -207,22 +255,21 @@ final class LatteExtension extends Latte\Extension } ``` -La registriamo usando [configuration |configuration#Latte]: +La registriamo tramite la [configurazione |configuration#Template Latte]: ```neon latte: extensions: - - App\Templating\LatteExtension + - App\Presentation\Accessory\LatteExtension ``` -Tradurre .[#toc-translating] ----------------------------- +Traduzione +---------- -Se si sta programmando un'applicazione multilingue, è probabile che sia necessario produrre parte del testo del modello in lingue diverse. A tale scopo, il framework Nette definisce un'interfaccia di traduzione [api:Nette\Localization\Translator], che ha un unico metodo `translate()`. Questo accetta il messaggio `$message`, che di solito è una stringa, e qualsiasi altro parametro. Il compito è quello di restituire la stringa tradotta. -Non esiste un'implementazione predefinita in Nette, ma si può scegliere in base alle proprie esigenze tra diverse soluzioni già pronte che si possono trovare su [Componette |https://componette.org/search/localization]. La loro documentazione spiega come configurare il traduttore. +Se programmi un'applicazione multilingue, probabilmente avrai bisogno di stampare alcuni testi nel template in diverse lingue. Nette Framework definisce a tale scopo un'interfaccia per la traduzione [api:Nette\Localization\Translator], che ha un unico metodo `translate()`. Questo accetta il messaggio `$message`, che di solito è una stringa, e qualsiasi altro parametro. Il compito è restituire la stringa tradotta. In Nette non c'è un'implementazione predefinita, puoi scegliere in base alle tue esigenze tra diverse soluzioni pronte, che trovi su [Componette |https://componette.org/search/localization]. Nella loro documentazione imparerai come configurare il translator. -I modelli possono essere configurati con un traduttore, che [ci verrà passato |dependency-injection:passing-dependencies], utilizzando il metodo `setTranslator()`: +Ai template è possibile impostare un traduttore, che ci [facciamo passare |dependency-injection:passing-dependencies], con il metodo `setTranslator()`: ```php protected function beforeRender(): void @@ -232,38 +279,38 @@ protected function beforeRender(): void } ``` -In alternativa, il traduttore può essere impostato utilizzando la [configurazione |configuration#Latte]: +Il translator può alternativamente essere impostato tramite la [configurazione |configuration#Template Latte]: ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -Il traduttore può essere utilizzato, ad esempio, come filtro `|translate`, con parametri aggiuntivi passati al metodo `translate()` (vedere `foo, bar`): +Successivamente, il traduttore può essere utilizzato ad esempio come filtro `|translate`, inclusi parametri aggiuntivi che vengono passati al metodo `translate()` (vedi `foo, bar`): ```latte -{='Basket'|translate} +{='Carrello'|translate} {$item|translate} {$item|translate, foo, bar} ``` -Oppure come tag underscore: +O come tag con trattino basso: ```latte -{_'Basket'} +{_'Carrello'} {_$item} {_$item, foo, bar} ``` -Per la traduzione di sezioni di template, esiste un tag accoppiato `{translate}` (da Latte 2.11, in precedenza si usava il tag `{_}` ): +Per la traduzione di una sezione del template esiste un tag di coppia `{translate}` (da Latte 2.11, prima si usava il tag `{_}`): ```latte -{translate}Order{/translate} -{translate foo, bar}Order{/translate} +{translate}Ordine{/translate} +{translate foo, bar}Ordine{/translate} ``` -Il traduttore viene chiamato per impostazione predefinita in fase di esecuzione durante il rendering del template. La versione 3 di Latte, tuttavia, può tradurre tutto il testo statico durante la compilazione del modello. Ciò consente di risparmiare sulle prestazioni, perché ogni stringa viene tradotta una sola volta e la traduzione risultante viene scritta nel modello compilato. In questo modo si creano più versioni compilate del modello nella cartella cache, una per ogni lingua. Per farlo, è sufficiente specificare la lingua come secondo parametro: +Il translator viene chiamato standardmente durante l'esecuzione al rendering del template. Latte versione 3, tuttavia, può tradurre tutti i testi statici già durante la compilazione del template. Ciò consente di risparmiare prestazioni, poiché ogni stringa viene tradotta solo una volta e la traduzione risultante viene scritta nella forma compilata. Nella directory della cache vengono così create più versioni compilate del template, una per ogni lingua. Per fare ciò, basta solo specificare la lingua come secondo parametro: ```php protected function beforeRender(): void @@ -273,4 +320,4 @@ protected function beforeRender(): void } ``` -Per testo statico si intende, ad esempio, `{_'hello'}` o `{translate}hello{/translate}`. Il testo non statico, come `{_$foo}`, continuerà a essere compilato al volo. +Per testo statico si intende ad esempio `{_'ciao'}` o `{translate}ciao{/translate}`. I testi non statici, come ad esempio `{_$foo}`, continueranno ad essere tradotti durante l'esecuzione. diff --git a/application/ja/@home.texy b/application/ja/@home.texy new file mode 100644 index 0000000000..51000ea766 --- /dev/null +++ b/application/ja/@home.texy @@ -0,0 +1,85 @@ +Nette Application +***************** + +.[perex] +Nette ApplicationはNetteフレームワークの中核であり、最新のWebアプリケーションを作成するための強力なツールを提供します。開発を大幅に容易にし、コードのセキュリティと保守性を向上させる多くの優れた機能を提供します。 + + +インストール +------ + +[Composer|best-practices:composer]を使用してライブラリをダウンロードし、インストールします: + +```shell +composer require nette/application +``` + + +なぜNette Applicationを選ぶのか? +------------------------- + +Netteは常にWeb技術分野のパイオニアでした。 + +**双方向ルーター:** Netteは高度なルーティングシステムを備えており、その双方向性でユニークです - URLをアプリケーションのアクションに変換するだけでなく、逆にURLアドレスを生成することもできます。これは次のことを意味します: +- テンプレートを編集することなく、いつでもアプリケーション全体のURL構造を変更できます +- URLは自動的に正規化され、SEOが向上します +- ルーティングはアノテーションに散在するのではなく、一箇所で定義されます + +**コンポーネントとシグナル:** DelphiとReact.jsに触発された組み込みコンポーネントシステムは、PHPフレームワークの中で完全にユニークです: +- 再利用可能なUI要素の作成を可能にします +- コンポーネントの階層的な構成をサポートします +- シグナルを使用してAJAXリクエストをエレガントに処理します +- [Componette](https://componette.org)には豊富な既製コンポーネントライブラリがあります + +**AJAXとスニペット:** Netteは、Ruby on RailsのHotwireやSymfony UX Turboのような同様のソリューションが登場するずっと前の2009年に、AJAXを扱う革新的な方法を導入しました: +- スニペットを使用すると、JavaScriptを記述することなくページの一部のみを更新できます +- コンポーネントシステムとの自動統合 +- ページの一部を賢く無効化 +- 転送されるデータの最小量 + +**直感的なテンプレート [Latte|latte:]:** PHP用の最も安全なテンプレートシステムで、高度な機能を備えています: +- コンテキストに応じたエスケープによるXSSからの自動保護 +- カスタムフィルタ、関数、タグによる拡張性 +- AJAX用のテンプレート継承とスニペット +- 型システムを備えたPHP 8.xの優れたサポート + +**Dependency Injection:** NetteはDependency Injectionを完全に活用しています: +- 依存関係の自動受け渡し(autowiring) +- わかりやすいNEON形式による設定 +- コンポーネントファクトリのサポート + + +主な利点 +---- + +- **セキュリティ**: XSS、CSRFなどの[脆弱性|nette:vulnerability-protection]に対する自動防御 +- **生産性**: スマートな設計により、少ない記述でより多くの機能を実現 +- **デバッグ**: ルーティングパネルを備えた[Tracyデバッガー|tracy:] +- **パフォーマンス**: スマートキャッシュ、コンポーネントの遅延読み込み +- **柔軟性**: アプリケーション完成後でもURLを簡単に変更可能 +- **コンポーネント**: 再利用可能なUI要素のユニークなシステム +- **モダン**: PHP 8.4+と型システムを完全にサポート + + +はじめに +---- + +1. [アプリケーションはどのように動作しますか? |how-it-works] - 基本的なアーキテクチャの理解 +2. [Presenter |presenters] - Presenterとアクションの操作 +3. [テンプレート |templates] - Latteでのテンプレート作成 +4. [ルーティング |routing] - URLアドレスの設定 +5. [インタラクティブコンポーネント |components] - コンポーネントシステムの活用 + + +PHPとの互換性 +-------- + +| バージョン | PHPとの互換性 +|-----------|------------------- +| Nette Application 4.0 | PHP 8.1 – 8.4 +| Nette Application 3.2 | PHP 8.1 – 8.4 +| Nette Application 3.1 | PHP 7.2 – 8.3 +| Nette Application 3.0 | PHP 7.1 – 8.0 +| Nette Application 2.4 | PHP 5.6 – 8.0 + +最新のパッチバージョンに適用されます。 diff --git a/application/ja/@left-menu.texy b/application/ja/@left-menu.texy new file mode 100644 index 0000000000..d08783d23f --- /dev/null +++ b/application/ja/@left-menu.texy @@ -0,0 +1,22 @@ +Nette Application +***************** +- [アプリケーションはどのように動作しますか? |how-it-works] +- [Bootstrapping] +- [Presenter |presenters] +- [テンプレート |templates] +- [ディレクトリ構造 |directory-structure] +- [ルーティング |routing] +- [URLリンクの作成 |creating-links] +- [インタラクティブコンポーネント |components] +- [AJAX & スニペット |ajax] +- [Multiplier |multiplier] +- [設定 |configuration] + + +参考文献 +**** +- [なぜNetteを使うのか? |www:10-reasons-why-nette] +- [インストール |nette:installation] +- [最初のアプリケーションを作成しましょう! |quickstart:] +- [ガイドとベストプラクティス |best-practices:] +- [問題解決 |nette:troubleshooting] diff --git a/application/ja/@meta.texy b/application/ja/@meta.texy new file mode 100644 index 0000000000..d3c41dc3d7 --- /dev/null +++ b/application/ja/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette ドキュメンテーション}} diff --git a/application/ja/ajax.texy b/application/ja/ajax.texy new file mode 100644 index 0000000000..a20c5bd439 --- /dev/null +++ b/application/ja/ajax.texy @@ -0,0 +1,249 @@ +AJAX とスニペット +*********** + +
    + +最新のWebアプリケーションの時代では、機能がサーバーとブラウザの間で分割されることが多いため、AJAXは不可欠な接続要素です。Nette Frameworkはこの分野でどのような可能性を提供しているでしょうか? +- テンプレートの一部、いわゆるスニペットの送信 +- PHPとJavaScript間の変数渡し +- AJAXリクエストのデバッグツール + +
    + + +AJAXリクエスト +========= + +AJAXリクエストは、基本的に通常のHTTPリクエストと変わりません。特定のパラメータでPresenterが呼び出されます。そして、Presenterがリクエストにどのように応答するかは、Presenter次第です。JSON形式のデータを返す、HTMLコードの一部を送信する、XMLドキュメントを送信するなど、さまざまな方法があります。 + +ブラウザ側では、`fetch()` 関数を使用してAJAXリクエストを初期化します。 + +```js +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) +.then(response => response.json()) +.then(payload => { + // レスポンスの処理 +}); +``` + +サーバー側では、[HTTPリクエストをカプセル化するサービス |http:request] の `$httpRequest->isAjax()` メソッドでAJAXリクエストを認識します。検出には `X-Requested-With` HTTPヘッダーを使用するため、これを送信することが重要です。Presenter内では `$this->isAjax()` メソッドを使用できます。 + +JSON形式でデータを送信したい場合は、[`sendJson()` |presenters#応答の送信] メソッドを使用します。このメソッドはPresenterの動作も終了させます。 + +```php +public function actionExport(): void +{ + $this->sendJson($this->model->getData); +} +``` + +AJAX用に特別なテンプレートで応答する予定がある場合は、次のように行うことができます。 + +```php +public function handleClick($param): void +{ + if ($this->isAjax()) { + $this->template->setFile('path/to/ajax.latte'); + } + // ... +} +``` + + +スニペット +===== + +Netteがサーバーとクライアントを接続するために提供する最も強力な手段は、スニペットです。これらのおかげで、最小限の労力と数行のコードで、通常のアプリケーションをAJAXアプリケーションに変えることができます。これがどのように機能するかは、Fifteenの例で示されています。そのコードは[GitHub |https://github.com/nette-examples/fifteen]にあります。 + +スニペット、つまり切り抜きは、ページ全体を再読み込みする代わりに、ページの一部だけを更新することを可能にします。これはより速く、より効率的であるだけでなく、より快適なユーザーエクスペリエンスも提供します。スニペットは、Ruby on RailsのHotwireやSymfony UX Turboを思い出させるかもしれません。興味深いことに、Netteはスニペットを14年も前に導入しました。 + +スニペットはどのように機能しますか?ページの最初の読み込み(非AJAXリクエスト)では、すべてのスニペットを含むページ全体が読み込まれます。ユーザーがページと対話する(例えば、ボタンをクリックする、フォームを送信するなど)と、ページ全体を読み込む代わりにAJAXリクエストが発行されます。Presenterのコードはアクションを実行し、どのスニペットを更新する必要があるかを決定します。Netteはこれらのスニペットをレンダリングし、JSON形式の配列として送信します。ブラウザの処理コードは、受信したスニペットをページに挿入します。したがって、変更されたスニペットのコードのみが転送され、帯域幅を節約し、ページ全体のコンテンツを転送するよりも読み込みを高速化します。 + + +Naja +---- + +ブラウザ側でスニペットを処理するために、[Najaライブラリ |https://naja.js.org]が使用されます。これをnode.jsパッケージとして[インストール |https://naja.js.org/#/guide/01-install-setup-naja]します(Webpack、Rollup、Vite、Parcelなどのアプリケーションで使用するため)。 + +```shell +npm install naja +``` + +…または、ページテンプレートに直接挿入します。 + +```html + +``` + +まず、ライブラリを[初期化 |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization]する必要があります。 + +```js +naja.initialize(); +``` + +通常のリンク(シグナル)やフォーム送信からAJAXリクエストを作成するには、関連するリンク、フォーム、またはボタンに `ajax` クラスを付けるだけです。 + +```html +Go + +
    + +
    + +または + +
    + +
    +``` + + +スニペットの再描画 +--------- + +[Control |components]クラスの各オブジェクト(Presenter自体を含む)は、再描画が必要な変更が発生したかどうかを記録します。これには `redrawControl()` メソッドが使用されます。 + +```php +public function handleLogin(string $user): void +{ + // ログイン後、関連部分を再描画する必要がある + $this->redrawControl(); + // ... +} +``` + +Netteは、再描画する内容をさらに細かく制御できます。このメソッドは、引数としてスニペット名を受け取ることができます。したがって、テンプレートの一部のレベルで無効化(つまり、再描画を強制)できます。コンポーネント全体が無効化されると、そのすべてのスニペットも再描画されます。 + +```php +// 'header' スニペットを無効化 +$this->redrawControl('header'); +``` + + +Latteのスニペット +----------- + +Latteでスニペットを使用するのは非常に簡単です。テンプレートの一部をスニペットとして定義するには、単に `{snippet}` と `{/snippet}` タグで囲みます。 + +```latte +{snippet header} +

    Hello ...

    +{/snippet} +``` + +スニペットは、特別な生成された `id` を持つ `
    ` 要素をHTMLページに作成します。スニペットが再描画されると、この要素のコンテンツが更新されます。したがって、ページの初期レンダリング時に、たとえ最初は空であっても、すべてのスニペットもレンダリングする必要があります。 + +`
    ` 以外の要素でスニペットを作成することもできます。`n:attribute` を使用します。 + +```latte +
    +

    Hello ...

    +
    +``` + + +スニペット領域 +------- + +スニペット名は式にすることもできます。 + +```latte +{foreach $items as $id => $item} +
  • {$item}
  • +{/foreach} +``` + +これにより、`item-0`、`item-1`などの複数のスニペットが作成されます。動的スニペット(例えば `item-1`)を直接無効化した場合、何も再描画されません。理由は、スニペットは本当に切り抜きとして機能し、それ自体だけが直接レンダリングされるためです。しかし、テンプレートには実際には `item-1` という名前のスニペットはありません。それは、スニペットの周りのコード、つまりforeachループを実行することによってのみ作成されます。したがって、実行されるべきテンプレートの部分を `{snippetArea}` タグでマークします。 + +```latte +
      + {foreach $items as $id => $item} +
    • {$item}
    • + {/foreach} +
    +``` + +そして、スニペット自体と親領域全体の両方を再描画させます。 + +```php +$this->redrawControl('itemsContainer'); +$this->redrawControl('item-1'); +``` + +同時に、`$items` 配列には再描画されるべき項目のみが含まれるようにすることが望ましいです。 + +`{include}` タグを使用して、スニペットを含む別のテンプレートをテンプレートに挿入する場合、テンプレートの挿入を再度 `snippetArea` に含め、それをスニペットと一緒に無効化する必要があります。 + +```latte +{snippetArea include} + {include 'included.latte'} +{/snippetArea} +``` + +```latte +{* included.latte *} +{snippet item} + ... +{/snippet} +``` + +```php +$this->redrawControl('include'); +$this->redrawControl('item'); +``` + + +コンポーネントのスニペット +------------- + +[コンポーネント|components] 内にスニペットを作成することもでき、Netteはそれらを自動的に再描画します。ただし、制限があります。スニペットを再描画するために、パラメータなしで `render()` メソッドを呼び出します。したがって、テンプレートでパラメータを渡すことは機能しません。 + +```latte +OK +{control productGrid} + +動作しません: +{control productGrid $arg, $arg} +{control productGrid:paginator} +``` + + +ユーザーデータの送信 +---------- + +スニペットと一緒に、任意の追加データをクライアントに送信できます。それらを `payload` オブジェクトに書き込むだけです。 + +```php +public function actionDelete(int $id): void +{ + // ... + if ($this->isAjax()) { + $this->payload->message = 'Success'; + } +} +``` + + +パラメータの受け渡し +========== + +AJAXリクエストを使用してコンポーネントにパラメータを送信する場合、それがシグナルパラメータであろうと永続パラメータであろうと、リクエストでコンポーネント名を含むグローバル名を指定する必要があります。パラメータの完全な名前は `getParameterId()` メソッドによって返されます。 + +```js +let url = new URL({link //foo!}); +url.searchParams.set({$control->getParameterId('bar')}, bar); + +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) +``` + +そして、コンポーネント内の対応するパラメータを持つハンドルメソッド: + +```php +public function handleFoo(int $bar): void +{ +} +``` diff --git a/application/ja/bootstrapping.texy b/application/ja/bootstrapping.texy new file mode 100644 index 0000000000..602daba9c8 --- /dev/null +++ b/application/ja/bootstrapping.texy @@ -0,0 +1,297 @@ +ブートストラップ +******** + +
    + +ブートストラップは、アプリケーション環境の初期化、依存性注入(DI)コンテナの作成、およびアプリケーションの開始プロセスです。以下について説明します: + +- Bootstrapクラスが環境を初期化する方法 +- NEONファイルを使用してアプリケーションを設定する方法 +- 本番モードと開発モードを区別する方法 +- DIコンテナを作成および設定する方法 + +
    + + +Webアプリケーションであれ、コマンドラインから実行されるスクリプトであれ、アプリケーションはその実行を開始する際に何らかの形で環境を初期化します。昔は、例えば `include.inc.php` のようなファイルがこれを担当し、最初のファイルがインクルードしていました。 最新のNetteアプリケーションでは、これは `Bootstrap` クラスに置き換えられ、アプリケーションの一部として `app/Bootstrap.php` ファイルにあります。例えば、次のようになります。 + +```php +use Nette\Bootstrap\Configurator; + +class Bootstrap +{ + private Configurator $configurator; + private string $rootDir; + + public function __construct() + { + $this->rootDir = dirname(__DIR__); + // Configuratorはアプリケーション環境とサービスの設定を担当します。 + $this->configurator = new Configurator; + // Netteによって生成される一時ファイル(コンパイルされたテンプレートなど)のディレクトリを設定します。 + $this->configurator->setTempDirectory($this->rootDir . '/temp'); + } + + public function bootWebApplication(): Nette\DI\Container + { + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); + } + + private function initializeEnvironment(): void + { + // Netteは賢く、開発モードは自動的に有効になります。 + // または、次の行のコメントを解除して特定のIPアドレスに対して有効にすることもできます。 + // $this->configurator->setDebugMode('secret@23.75.345.200'); + + // Tracyを有効にします:デバッグのための究極の「スイスアーミーナイフ」。 + $this->configurator->enableTracy($this->rootDir . '/log'); + + // RobotLoader: 選択したディレクトリ内のすべてのクラスを自動的にロードします。 + $this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); + } + + private function setupContainer(): void + { + // 設定ファイルをロードします。 + $this->configurator->addConfig($this->rootDir . '/config/common.neon'); + } +} +``` + + +index.php +========= + +Webアプリケーションの場合、最初のファイルは `www/` [公開ディレクトリ |directory-structure#公開ディレクトリ www] にある `index.php` です。これは `Bootstrap` クラスに環境を初期化させ、DIコンテナを作成させます。その後、そこから `Application` サービスを取得し、Webアプリケーションを起動します。 + +```php +$bootstrap = new App\Bootstrap; +// 環境の初期化 + DIコンテナの作成 +$container = $bootstrap->bootWebApplication(); +// DIコンテナは Nette\Application\Application オブジェクトを作成します +$application = $container->getByType(Nette\Application\Application::class); +// Netteアプリケーションを起動し、受信リクエストを処理します +$application->run(); +``` + +ご覧のとおり、環境の設定と依存性注入(DI)コンテナの作成は、[api:Nette\Bootstrap\Configurator] クラスによって支援されます。これについて詳しく見ていきましょう。 + + +開発環境 vs 本番環境 +============ + +Netteは、開発サーバーで実行されているか、本番サーバーで実行されているかによって動作が異なります。 + +🛠️ 開発環境 (Development): + - 役立つ情報(SQLクエリ、実行時間、使用メモリ)を含むTracyデバッグバーを表示します。 + - エラーが発生した場合、関数呼び出しと変数内容を含む詳細なエラーページを表示します。 + - Latteテンプレートの変更、設定ファイルの編集などがあった場合にキャッシュを自動的に更新します。 + + +🚀 本番環境 (Production): + - デバッグ情報は表示せず、すべてのエラーをログに記録します。 + - エラーが発生した場合、ErrorPresenterまたは一般的な「Server Error」ページを表示します。 + - キャッシュは自動的に更新されません! + - 速度とセキュリティのために最適化されています。 + + +モードの選択は自動検出によって行われるため、通常は何も設定したり手動で切り替えたりする必要はありません。 + +- 開発環境: localhost(IPアドレス `127.0.0.1` または `::1`)で、プロキシが存在しない場合(つまり、そのHTTPヘッダーがない場合)。 +- 本番環境: それ以外のすべての場合。 + +他の場合、例えば特定のIPアドレスからアクセスするプログラマーに対して開発環境を有効にしたい場合は、`setDebugMode()` を使用します。 + +```php +$this->configurator->setDebugMode('23.75.345.200'); // IPアドレスの配列も指定できます +``` + +IPアドレスとCookieを組み合わせることを強くお勧めします。`nette-debug` Cookieに秘密のトークン、例えば `secret1234` を保存し、この方法で特定のIPアドレスからアクセスし、かつCookieに言及されたトークンを持つプログラマーに対して開発環境を有効にします。 + +```php +$this->configurator->setDebugMode('secret1234@23.75.345.200'); +``` + +localhostに対しても、開発環境を完全に無効にすることもできます。 + +```php +$this->configurator->setDebugMode(false); +``` + +注意:値 `true` は開発環境を強制的に有効にします。これは本番サーバーでは絶対に行ってはいけません。 + + +デバッグツール Tracy +============= + +簡単なデバッグのために、優れたツール[Tracy |tracy:]を有効にします。開発環境ではエラーを視覚化し、本番環境では指定されたディレクトリにエラーをログ記録します。 + +```php +$this->configurator->enableTracy($this->rootDir . '/log'); +``` + + +一時ファイル +====== + +NetteはDIコンテナ、RobotLoader、テンプレートなどにキャッシュを使用します。したがって、キャッシュが保存されるディレクトリへのパスを設定する必要があります。 + +```php +$this->configurator->setTempDirectory($this->rootDir . '/temp'); +``` + +LinuxまたはmacOSでは、`log/` および `temp/` ディレクトリに[書き込み権限を設定 |nette:troubleshooting#ディレクトリ権限の設定]してください。 + + +RobotLoader +=========== + +通常、[RobotLoader |robot-loader:]を使用してクラスを自動的にロードしたいので、それを起動し、`Bootstrap.php` が配置されているディレクトリ(つまり `__DIR__`)とそのすべてのサブディレクトリからクラスをロードさせます。 + +```php +$this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); +``` + +代替アプローチは、PSR-4に準拠しながら[Composer |best-practices:composer]経由でのみクラスをロードさせることです。 + + +タイムゾーン +====== + +Configuratorを使用して、デフォルトのタイムゾーンを設定できます。 + +```php +$this->configurator->setTimeZone('Europe/Prague'); +``` + + +DIコンテナの設定 +========= + +ブートプロセスの一部は、アプリケーション全体の心臓部であるDIコンテナ、つまりオブジェクトのファクトリを作成することです。これは実際にはNetteによって生成され、キャッシュディレクトリに保存されるPHPクラスです。ファクトリはアプリケーションの主要なオブジェクトを生成し、設定ファイルを使用してそれらをどのように作成および設定するかを指示することで、アプリケーション全体の動作に影響を与えます。 + +設定ファイルは通常、[NEON |neon:format]形式で記述されます。別の章で、[設定できるすべてのこと |nette:configuring]について学びます。 + +.[tip] +開発環境では、コードまたは設定ファイルが変更されるたびにコンテナが自動的に更新されます。本番環境では、一度だけ生成され、パフォーマンスを最大化するために変更はチェックされません。 + +`addConfig()` を使用して設定ファイルをロードします。 + +```php +$this->configurator->addConfig($this->rootDir . '/config/common.neon'); +``` + +複数の設定ファイルを追加したい場合は、`addConfig()` 関数を複数回呼び出すことができます。 + +```php +$configDir = $this->rootDir . '/config'; +$this->configurator->addConfig($configDir . '/common.neon'); +$this->configurator->addConfig($configDir . '/services.neon'); +if (PHP_SAPI === 'cli') { + $this->configurator->addConfig($configDir . '/cli.php'); +} +``` + +`cli.php` という名前はタイプミスではありません。設定はPHPファイルに記述することもでき、そのファイルが配列として返します。 + +[`includes` セクション |dependency-injection:configuration#ファイルのインクルード]で他の設定ファイルを追加することもできます。 + +設定ファイルに同じキーを持つ要素が表示された場合、それらは上書きされるか、[配列の場合はマージ |dependency-injection:configuration#マージ]されます。後でインクルードされたファイルは、前のファイルよりも優先度が高くなります。`includes` セクションが記載されているファイルは、それにインクルードされているファイルよりも優先度が高くなります。 + + +静的パラメータ +------- + +設定ファイルで使用されるパラメータは、[`parameters` セクション |dependency-injection:configuration#パラメータ]で定義でき、`addStaticParameters()` メソッド(エイリアス `addParameters()` を持つ)で渡す(または上書きする)こともできます。重要なのは、パラメータの値が異なると、追加のDIコンテナ、つまり追加のクラスが生成されることです。 + +```php +$this->configurator->addStaticParameters([ + 'projectId' => 23, +]); +``` + +`projectId` パラメータは、設定で通常の `%projectId%` 表記で参照できます。 + + +動的パラメータ +------- + +コンテナに動的パラメータを追加することもできます。静的パラメータとは異なり、それらの異なる値は新しいDIコンテナの生成を引き起こしません。 + +```php +$this->configurator->addDynamicParameters([ + 'remoteIp' => $_SERVER['REMOTE_ADDR'], +]); +``` + +このようにして、例えば環境変数を簡単に追加でき、それらは設定で `%env.variable%` 表記で参照できます。 + +```php +$this->configurator->addDynamicParameters([ + 'env' => getenv(), +]); +``` + + +デフォルトパラメータ +---------- + +設定ファイルでは、これらの静的パラメータを使用できます。 + +- `%appDir%` は `Bootstrap.php` ファイルを含むディレクトリへの絶対パスです。 +- `%wwwDir%` はエントリファイル `index.php` を含むディレクトリへの絶対パスです。 +- `%tempDir%` は一時ファイル用のディレクトリへの絶対パスです。 +- `%vendorDir%` はComposerがライブラリをインストールするディレクトリへの絶対パスです。 +- `%rootDir%` はプロジェクトのルートディレクトリへの絶対パスです。 +- `%debugMode%` はアプリケーションがデバッグモードであるかどうかを示します。 +- `%consoleMode%` はリクエストがコマンドライン経由で来たかどうかを示します。 + + +インポートされたサービス +------------ + +ここではさらに深く掘り下げます。DIコンテナの目的はオブジェクトを作成することですが、例外的に既存のオブジェクトをコンテナに挿入する必要がある場合があります。これを行うには、`imported: true` フラグを使用してサービスを定義します。 + +```neon +services: + myservice: + type: App\Model\MyCustomService + imported: true +``` + +そして、ブートストラップでオブジェクトをコンテナに挿入します。 + +```php +$this->configurator->addServices([ + 'myservice' => new App\Model\MyCustomService('foobar'), +]); +``` + + +異なる環境 +===== + +必要に応じて `Bootstrap` クラスを自由に変更してください。`bootWebApplication()` メソッドにパラメータを追加して、Webプロジェクトを区別することができます。または、他のメソッドを追加することもできます。例えば、単体テスト用の環境を初期化する `bootTestEnvironment()`、コマンドラインから呼び出されるスクリプト用の `bootConsoleApplication()` などです。 + +```php +public function bootTestEnvironment(): Nette\DI\Container +{ + Tester\Environment::setup(); // Nette Testerの初期化 + $this->setupContainer(); + return $this->configurator->createContainer(); +} + +public function bootConsoleApplication(): Nette\DI\Container +{ + $this->configurator->setDebugMode(false); + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); +} +``` diff --git a/application/ja/components.texy b/application/ja/components.texy new file mode 100644 index 0000000000..e8f6b5e4f1 --- /dev/null +++ b/application/ja/components.texy @@ -0,0 +1,485 @@ +インタラクティブコンポーネント +*************** + +
    + +コンポーネントは、ページに挿入する独立した再利用可能なオブジェクトです。フォーム、データグリッド、投票など、繰り返し使用する意味のあるものであれば何でもかまいません。ここでは以下について説明します。 + +- コンポーネントの使用方法 +- コンポーネントの作成方法 +- シグナルとは何か + +
    + +Netteには組み込みのコンポーネントシステムがあります。DelphiやASP.NET Web Formsを知っている古い世代の方々には馴染みがあるかもしれません。ReactやVue.jsも、遠いながらも似たようなものに基づいています。しかし、PHPフレームワークの世界では、これはユニークな機能です。 + +一方、コンポーネントはアプリケーション開発へのアプローチに根本的な影響を与えます。事前に準備されたユニットからページを組み立てることができます。管理画面にデータグリッドが必要ですか?Nette用のオープンソースアドオン(コンポーネントだけではありません)のリポジトリである[Componette |https://componette.org/search/component]で見つけて、Presenterに簡単に追加できます。 + +Presenterには任意の数のコンポーネントを含めることができます。そして、一部のコンポーネントには他のコンポーネントを挿入できます。これにより、Presenterをルートとするコンポーネントツリーが作成されます。 + + +ファクトリメソッド +========= + +コンポーネントはどのようにPresenterに挿入され、その後使用されるのでしょうか?通常はファクトリメソッドを使用します。 + +コンポーネントファクトリは、コンポーネントが実際に必要になったときにのみ作成する(遅延/オンデマンド)エレガントな方法です。全体の魔法は、`createComponent()` という名前のメソッドを実装することにあります。ここで `` は作成されるコンポーネントの名前であり、このメソッドがコンポーネントを作成して返します。 + +```php .{file:DefaultPresenter.php} +class DefaultPresenter extends Nette\Application\UI\Presenter +{ + protected function createComponentPoll(): PollControl + { + $poll = new PollControl; + $poll->items = $this->item; + return $poll; + } +} +``` + +すべてのコンポーネントが個別のメソッドで作成されるため、コードがより明確になります。 + +.[note] +コンポーネント名は常に小文字で始まりますが、メソッド名では大文字で記述されます。 + +ファクトリは直接呼び出すことはありません。コンポーネントを初めて使用するときに自動的に呼び出されます。これにより、コンポーネントは適切なタイミングで、実際に必要な場合にのみ作成されます。コンポーネントを使用しない場合(例えば、ページの一部のみが転送されるAJAXリクエストの場合や、テンプレートのキャッシュの場合)、コンポーネントはまったく作成されず、サーバーのパフォーマンスを節約できます。 + +```php .{file:DefaultPresenter.php} +// コンポーネントにアクセスし、初めての場合は +// それを作成する createComponentPoll() が呼び出されます +$poll = $this->getComponent('poll'); +// 代替構文: $poll = $this['poll']; +``` + +テンプレートでは、[{control} |#レンダリング] タグを使用してコンポーネントを描画できます。したがって、コンポーネントを手動でテンプレートに渡す必要はありません。 + +```latte +

    投票してください

    + +{control poll} +``` + + +ハリウッドスタイル +========= + +コンポーネントは通常、私たちがハリウッドスタイルと呼ぶのが好きな新鮮なテクニックを使用します。映画のオーディション参加者がよく聞く決まり文句をきっとご存知でしょう:「こちらから連絡しますので、電話しないでください」。まさにそれです。 + +Netteでは、常に何かを尋ねる(「フォームは送信されましたか?」、「有効でしたか?」または「ユーザーはこのボタンを押しましたか?」)代わりに、フレームワークに「それが起こったら、このメソッドを呼び出して」と伝え、残りの作業を任せます。JavaScriptでプログラミングしている場合、このプログラミングスタイルには精通しているでしょう。特定のイベントが発生したときに呼び出される関数を記述します。そして、言語は適切なパラメータを渡します。 + +これはアプリケーションの作成方法を完全に変えます。フレームワークに任せられるタスクが多ければ多いほど、あなたの作業は少なくなります。そして、見落とす可能性のあることも少なくなります。 + + +コンポーネントの作成 +========== + +コンポーネントという用語は、通常、[api:Nette\Application\UI\Control] クラスの子孫を意味します。(したがって、「コントロール」という用語を使用する方が正確ですが、日本語では「コントロール」は他の意味合いを持つことがあり、「コンポーネント」の方が一般的になりました。)Presenter自体 [api:Nette\Application\UI\Presenter] も、ちなみに `Control` クラスの子孫です。 + +```php .{file:PollControl.php} +use Nette\Application\UI\Control; + +class PollControl extends Control +{ +} +``` + + +レンダリング +====== + +コンポーネントをレンダリングするために `{control componentName}` タグが使用されることはすでに知っています。これは実際にはコンポーネントの `render()` メソッドを呼び出し、そこでレンダリングを処理します。Presenterとまったく同じように、`$this->template` 変数に [Latteテンプレート|templates] があり、それにパラメータを渡します。Presenterとは異なり、テンプレートファイルを指定してレンダリングさせる必要があります。 + +```php .{file:PollControl.php} +public function render(): void +{ + // テンプレートにいくつかのパラメータを挿入します + $this->template->param = $value; + // そしてそれをレンダリングします + $this->template->render(__DIR__ . '/poll.latte'); +} +``` + +`{control}` タグを使用すると、`render()` メソッドにパラメータを渡すことができます。 + +```latte +{control poll $id, $message} +``` + +```php .{file:PollControl.php} +public function render(int $id, string $message): void +{ + // ... +} +``` + +コンポーネントがいくつかの部分で構成され、それらを別々にレンダリングしたい場合があります。それぞれについて、独自のレンダリングメソッドを作成します。ここでは例として `renderPaginator()` を作成します。 + +```php .{file:PollControl.php} +public function renderPaginator(): void +{ + // ... +} +``` + +そして、テンプレートで次のように呼び出します。 + +```latte +{control poll:paginator} +``` + +よりよく理解するために、このタグがどのようにPHPに変換されるかを知っておくと良いでしょう。 + +```latte +{control poll} +{control poll:paginator 123, 'hello'} +``` + +は次のように変換されます。 + +```php +$control->getComponent('poll')->render(); +$control->getComponent('poll')->renderPaginator(123, 'hello'); +``` + +`getComponent()` メソッドは `poll` コンポーネントを返し、このコンポーネントに対して `render()` メソッド、またはタグのコロンの後に異なるレンダリング方法が指定されている場合は `renderPaginator()` メソッドを呼び出します。 + +.[caution] +注意:パラメータのどこかに **`=>`** が現れると、すべてのパラメータが配列にラップされ、最初の引数として渡されます。 + +```latte +{control poll, id: 123, message: 'hello'} +``` + +は次のように変換されます。 + +```php +$control->getComponent('poll')->render(['id' => 123, 'message' => 'hello']); +``` + +サブコンポーネントのレンダリング: + +```latte +{control cartControl-someForm} +``` + +は次のように変換されます。 + +```php +$control->getComponent("cartControl-someForm")->render(); +``` + +コンポーネントは、Presenterと同様に、いくつかの便利な変数を自動的にテンプレートに渡します。 + +- `$basePath` はルートディレクトリへの絶対URLパスです(例:`/eshop`) +- `$baseUrl` はルートディレクトリへの絶対URLです(例:`http://localhost/eshop`) +- `$user` は[ユーザーを表す |security:authentication]オブジェクトです +- `$presenter` は現在のPresenterです +- `$control` は現在のコンポーネントです +- `$flashes` は `flashMessage()` 関数によって送信された[メッセージ |#フラッシュメッセージ]の配列です + + +シグナル +==== + +Netteアプリケーションのナビゲーションは、`Presenter:action` のペアへのリンクまたはリダイレクトに基づいていることはすでに知っています。しかし、**現在のページ**でアクションを実行したいだけの場合はどうでしょうか?例えば、テーブルの列の並び替えを変更する、項目を削除する、ライト/ダークモードを切り替える、フォームを送信する、投票するなどです。 + +この種のリクエストはシグナルと呼ばれます。そして、アクションが `action()` または `render()` メソッドを呼び出すのと同様に、シグナルは `handle()` メソッドを呼び出します。アクション(またはビュー)という概念は純粋にPresenterに関連していますが、シグナルはすべてのコンポーネントに関係します。したがって、`UI\Presenter` は `UI\Control` の子孫であるため、Presenterにも関係します。 + +```php +public function handleClick(int $x, int $y): void +{ + // ... シグナルの処理 ... +} +``` + +シグナルを呼び出すリンクは、通常の方法で作成します。つまり、テンプレートでは `n:href` 属性または `{link}` タグを使用し、コードでは `link()` メソッドを使用します。詳細については、[URLリンクの作成 |creating-links#シグナルへのリンク]の章を参照してください。 + +```latte +ここをクリック +``` + +シグナルは常に現在のPresenterとアクションで呼び出され、別のPresenterや別のアクションで呼び出すことはできません。 + +したがって、シグナルは元のリクエストとまったく同じようにページの再読み込みを引き起こしますが、さらに適切なパラメータを持つシグナル処理メソッドを呼び出します。メソッドが存在しない場合、[api:Nette\Application\UI\BadSignalException] 例外がスローされ、ユーザーには403 Forbiddenエラーページとして表示されます。 + + +スニペットとAJAX +========== + +シグナルはAJAXを少し思い出させるかもしれません:現在のページで呼び出されるハンドラです。そして、その通りです。シグナルは実際にはAJAXを使用して呼び出されることが多く、その後、変更されたページの部分のみがブラウザに転送されます。つまり、いわゆるスニペットです。詳細については、[AJAX専用ページ |ajax]を参照してください。 + + +フラッシュメッセージ +========== + +コンポーネントには、Presenterとは独立した独自のフラッシュメッセージストレージがあります。これらは、例えば操作の結果を通知するメッセージです。フラッシュメッセージの重要な特徴は、リダイレクト後もテンプレートで利用できることです。表示後もさらに30秒間有効です。例えば、転送エラーのためにユーザーがページを更新した場合でも、メッセージはすぐには消えません。 + +送信は [flashMessage |api:Nette\Application\UI\Control::flashMessage()] メソッドによって処理されます。最初のパラメータはメッセージのテキストまたはメッセージを表す `stdClass` オブジェクトです。オプションの2番目のパラメータはそのタイプ(error、warning、infoなど)です。`flashMessage()` メソッドは、フラッシュメッセージのインスタンスを `stdClass` オブジェクトとして返し、これに追加情報を追加できます。 + +```php +$this->flashMessage('項目が削除されました。'); +$this->redirect(/* ... */); // そしてリダイレクトします +``` + +これらのメッセージは、テンプレートでは `$flashes` 変数で `stdClass` オブジェクトとして利用できます。これらには `message`(メッセージテキスト)、`type`(メッセージタイプ)プロパティが含まれ、前述のユーザー情報を含むこともできます。例えば、次のようにレンダリングします。 + +```latte +{foreach $flashes as $flash} +
    {$flash->message}
    +{/foreach} +``` + + +シグナル後のリダイレクト +============ + +コンポーネントのシグナル処理後には、しばしばリダイレクトが続きます。これはフォームの場合と似ています。フォーム送信後もリダイレクトして、ブラウザでページを更新したときにデータが再送信されないようにします。 + +```php +$this->redirect('this') // 現在のPresenterとアクションにリダイレクトします +``` + +コンポーネントは再利用可能な要素であり、通常は特定のPresenterへの直接的な依存関係を持つべきではないため、`redirect()` および `link()` メソッドはパラメータを自動的にコンポーネントのシグナルとして解釈します。 + +```php +$this->redirect('click') // 同じコンポーネントの 'click' シグナルにリダイレクトします +``` + +別のPresenterやアクションにリダイレクトする必要がある場合は、Presenterを介して行うことができます。 + +```php +$this->getPresenter()->redirect('Product:show'); // 別のPresenter/アクションにリダイレクトします +``` + + +パーシステントパラメータ +============ + +パーシステントパラメータは、異なるリクエスト間でコンポーネントの状態を維持するために使用されます。その値は、リンクをクリックした後も同じままです。セッションデータとは異なり、URLで転送されます。そして、これは完全に自動的に行われ、同じページの他のコンポーネントで作成されたリンクも含みます。 + +例えば、コンテンツをページ分割するためのコンポーネントがあるとします。このようなコンポーネントはページ上に複数存在する可能性があります。そして、リンクをクリックした後、すべてのコンポーネントが現在のページにとどまるようにしたいとします。したがって、ページ番号(`page`)をパーシステントパラメータにします。 + +Netteでパーシステントパラメータを作成するのは非常に簡単です。パブリックプロパティを作成し、属性でマークするだけです。(以前は `/** @persistent */` が使用されていました) + +```php +use Nette\Application\Attributes\Persistent; // この行は重要です + +class PaginatingControl extends Control +{ + #[Persistent] + public int $page = 1; // publicである必要があります +} +``` + +プロパティにはデータ型(例:`int`)を指定することをお勧めします。また、デフォルト値を指定することもできます。パラメータ値は[検証 |#パーシステントパラメータの検証]できます。 + +リンクを作成するときに、パーシステントパラメータの値を変更できます。 + +```latte +次へ +``` + +または、*リセット*することもできます。つまり、URLから削除します。その後、デフォルト値を取ります。 + +```latte +リセット +``` + + +パーシステントコンポーネント +============== + +パラメータだけでなく、コンポーネントもパーシステントにすることができます。このようなコンポーネントでは、そのパーシステントパラメータはPresenterの異なるアクション間、または複数のPresenter間でも転送されます。パーシステントコンポーネントは、Presenterクラスのアノテーションでマークします。例えば、このようにして `calendar` および `poll` コンポーネントをマークします。 + +```php +/** + * @persistent(calendar, poll) + */ +class DefaultPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +これらのコンポーネント内のサブコンポーネントをマークする必要はありません。それらもパーシステントになります。 + +PHP 8では、属性を使用してパーシステントコンポーネントをマークすることもできます。 + +```php +use Nette\Application\Attributes\Persistent; + +#[Persistent('calendar', 'poll')] +class DefaultPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +依存関係を持つコンポーネント +============== + +それらを使用するPresenterを「汚す」ことなく、依存関係を持つコンポーネントを作成するにはどうすればよいでしょうか?NetteのDIコンテナの賢い機能のおかげで、従来のサービスを使用する場合と同様に、ほとんどの作業をフレームワークに任せることができます。 + +例として、`PollFacade` サービスに依存するコンポーネントを取り上げましょう。 + +```php +class PollControl extends Control +{ + public function __construct( + private int $id, // コンポーネントを作成する投票のID + private PollFacade $facade, + ) { + } + + public function handleVote(int $voteId): void + { + $this->facade->vote($id, $voteId); + // ... + } +} +``` + +従来のサービスを作成する場合、問題はありませんでした。すべての依存関係の受け渡しは、DIコンテナによって目に見えない形で処理されます。しかし、コンポーネントの場合、通常はPresenterの[#ファクトリメソッド] `createComponent…()` で直接新しいインスタンスを作成します。しかし、すべてのコンポーネントのすべての依存関係をPresenterに渡してからコンポーネントに渡すのは面倒です。そして、書かれたコードの量も… + +論理的な疑問は、なぜコンポーネントを従来のサービスとして登録し、Presenterに渡してから `createComponent…()` メソッドで返さないのかということです。しかし、このアプローチは不適切です。なぜなら、コンポーネントを複数回作成できるようにしたいからです。 + +正しい解決策は、コンポーネントのファクトリ、つまりコンポーネントを作成するクラスを作成することです。 + +```php +class PollControlFactory +{ + public function __construct( + private PollFacade $facade, + ) { + } + + public function create(int $id): PollControl + { + return new PollControl($id, $this->facade); + } +} +``` + +このようにして、ファクトリを構成内のコンテナに登録します。 + +```neon +services: + - PollControlFactory +``` + +そして最後に、Presenterで使用します。 + +```php +class PollPresenter extends Nette\Application\UI\Presenter +{ + public function __construct( + private PollControlFactory $pollControlFactory, + ) { + } + + protected function createComponentPollControl(): PollControl + { + $pollId = 1; // パラメータを渡すことができます + return $this->pollControlFactory->create($pollId); + } +} +``` + +素晴らしいことに、Nette DIはそのような単純なファクトリを[生成 |dependency-injection:factory]できるので、そのコード全体を書く代わりに、そのインターフェースを書くだけで済みます。 + +```php +interface PollControlFactory +{ + public function create(int $id): PollControl; +} +``` + +これで完了です。Netteは内部的にこのインターフェースを実装し、Presenterに渡します。そこで使用できます。魔法のように、パラメータ `$id` と `PollFacade` クラスのインスタンスをコンポーネントに追加します。 + + +コンポーネントの詳細 +========== + +Nette Applicationのコンポーネントは、Webアプリケーションの再利用可能な部分であり、ページに挿入され、この章全体で扱われています。そのようなコンポーネントには、具体的にどのような機能があるのでしょうか? + +1) テンプレートでレンダリング可能 +2) AJAXリクエスト時に[どの部分 |ajax#スニペット]をレンダリングするかを知っている(スニペット) +3) 状態をURLに保存する機能がある(パーシステントパラメータ) +4) ユーザーアクションに応答する機能がある(シグナル) +5) 階層構造を作成する(ルートはPresenter) + +これらの各機能は、継承ラインのいずれかのクラスによって処理されます。レンダリング(1 + 2)は[api:Nette\Application\UI\Control]が担当し、[ライフサイクル |presenters#Presenterのライフサイクル]への統合(3, 4)は[api:Nette\Application\UI\Component]クラスが担当し、階層構造の作成(5)は[ContainerおよびComponent |component-model:]クラスが担当します。 + +``` +Nette\ComponentModel\Component { IComponent } +| ++- Nette\ComponentModel\Container { IContainer } + | + +- Nette\Application\UI\Component { SignalReceiver, StatePersistent } + | + +- Nette\Application\UI\Control { Renderable } + | + +- Nette\Application\UI\Presenter { IPresenter } +``` + + +コンポーネントのライフサイクル +--------------- + +[* lifecycle-component.svg *] *** *コンポーネントのライフサイクル* .<> + + +パーシステントパラメータの検証 +--------------- + +URLから受け取った[#パーシステントパラメータ]の値は、`loadState()` メソッドによってプロパティに書き込まれます。また、プロパティで指定されたデータ型と一致するかどうかもチェックし、一致しない場合は404エラーで応答し、ページは表示されません。 + +パーシステントパラメータは、ユーザーがURLで簡単に上書きできるため、決して盲目的に信用しないでください。例えば、このようにしてページ番号 `$this->page` が0より大きいかどうかを検証します。適切な方法は、前述の `loadState()` メソッドをオーバーライドすることです。 + +```php +class PaginatingControl extends Control +{ + #[Persistent] + public int $page = 1; + + public function loadState(array $params): void + { + parent::loadState($params); // ここで $this->page が設定されます + // 値の独自のチェックが続きます: + if ($this->page < 1) { + $this->error(); + } + } +} +``` + +逆のプロセス、つまりパーシステントプロパティから値を収集するプロセスは、`saveState()` メソッドが担当します。 + + +シグナルの詳細 +------- + +シグナルは、元のリクエストとまったく同じようにページの再読み込みを引き起こし(AJAXで呼び出された場合を除く)、`signalReceived($signal)` メソッドを呼び出します。`Nette\Application\UI\Component` クラスのデフォルト実装は、`handle{signal}` という単語で構成されるメソッドを呼び出そうとします。その後の処理は、特定のオブジェクト次第です。`Component` から継承するオブジェクト(つまり `Control` と `Presenter`)は、適切なパラメータを持つ `handle{signal}` メソッドを呼び出そうとすることで応答します。 + +言い換えれば、`handle{signal}` 関数の定義とリクエストで渡されたすべてのパラメータが取得され、URLのパラメータが名前に基づいて引数に割り当てられ、そのメソッドを呼び出そうとします。例えば、`$id` パラメータとしてURLの `id` パラメータの値が渡され、`$something` としてURLの `something` が渡されます。そして、メソッドが存在しない場合、`signalReceived` メソッドは[例外 |api:Nette\Application\UI\BadSignalException]をスローします。 + +シグナルは、`SignalReceiver` インターフェースを実装し、コンポーネントツリーに接続されている任意のコンポーネント、Presenter、またはオブジェクトが受信できます。 + +シグナルの主な受信者は、`Presenter` および `Control` から継承するビジュアルコンポーネントになります。シグナルは、オブジェクトに何かをするように指示する合図として機能することを目的としています。投票はユーザーからの投票をカウントする必要があり、ニュースブロックは展開して2倍のニュースを表示する必要があり、フォームは送信されてデータを処理する必要がある、などです。 + +シグナルのURLは、[Component::link() |api:Nette\Application\UI\Component::link()] メソッドを使用して作成します。`$destination` パラメータとして文字列 `{signal}!` を渡し、`$args` としてシグナルに渡したい引数の配列を渡します。シグナルは常に現在のPresenterとアクションで現在のパラメータとともに呼び出され、シグナルパラメータのみが追加されます。さらに、最初に**シグナルを指定するパラメータ `?do`** が追加されます。 + +その形式は `{signal}` または `{signalReceiver}-{signal}` のいずれかです。`{signalReceiver}` はPresenter内のコンポーネントの名前です。したがって、コンポーネント名にハイフンを使用することはできません。ハイフンはコンポーネント名とシグナルを区切るために使用されますが、このようにして複数のコンポーネントをネストすることが可能です。 + +[isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] メソッドは、コンポーネント(最初の引数)がシグナル(2番目の引数)の受信者であるかどうかを検証します。2番目の引数は省略できます。その場合、コンポーネントが任意のシグナルの受信者であるかどうかを判断します。2番目のパラメータとして `true` を指定すると、指定されたコンポーネントだけでなく、その子孫のいずれかが受信者であるかどうかも検証できます。 + +`handle{signal}` に先行する任意の段階で、[processSignal()|api:Nette\Application\UI\Presenter::processSignal()] メソッドを呼び出すことでシグナルを手動で実行できます。このメソッドはシグナルの処理を担当します。シグナルの受信者として指定されたコンポーネント(受信者が指定されていない場合はPresenter自体)を取得し、それにシグナルを送信します。 + +例: + +```php +if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, 'sorting')) { + $this->processSignal(); +} +``` + +これにより、シグナルは早期に実行され、再度呼び出されることはありません。 diff --git a/application/ja/configuration.texy b/application/ja/configuration.texy new file mode 100644 index 0000000000..cb0e58a8fe --- /dev/null +++ b/application/ja/configuration.texy @@ -0,0 +1,191 @@ +アプリケーション設定 +********** + +.[perex] +Netteアプリケーションの設定オプションの概要。 + + +Application +=========== + +```neon +application: + # Tracy BlueScreenに「Nette Application」パネルを表示しますか? + debugger: ... # (bool) デフォルトは true + + # エラー時に error-presenter を呼び出しますか? + # 開発モードでのみ効果があります + catchExceptions: ... # (bool) デフォルトは true + + # error-presenter の名前 + errorPresenter: Error # (string|array) デフォルトは 'Nette:Error' + + # Presenterとアクションのエイリアスを定義します + aliases: ... + + # Presenter名をクラスに変換するルールを定義します + mapping: ... + + # 不正なリンクは警告を生成しませんか? + # 開発モードでのみ効果があります + silentLinks: ... # (bool) デフォルトは false +``` + +`nette/application` バージョン 3.2 以降、エラープレゼンターのペアを定義できます。 + +```neon +application: + errorPresenter: + 4xx: Error4xx # Nette\Application\BadRequestException 例外用 + 5xx: Error5xx # その他の例外用 +``` + +`silentLinks` オプションは、リンク生成が失敗した場合(例えば、Presenterが存在しないためなど)に、開発モードでNetteがどのように動作するかを決定します。デフォルト値 `false` は、Netteが `E_USER_WARNING` エラーをスローすることを意味します。`true` に設定すると、このエラーメッセージが抑制されます。本番環境では、`E_USER_WARNING` は常に発生します。この動作は、Presenter変数 [$invalidLinkMode |creating-links#不正なリンク] を設定することでも制御できます。 + +[エイリアスは、頻繁に使用されるPresenterへのリンクを簡略化 |creating-links#エイリアス]します。 + +[マッピングは、Presenter名からクラス名を導出するルールを定義 |directory-structure#Presenterのマッピング]します。 + + +Presenterの自動登録 +-------------- + +NetteはPresenterをサービスとしてDIコンテナに自動的に追加し、これによりPresenterの作成が大幅に高速化されます。NetteがPresenterをどのように検索するかは設定可能です。 + +```neon +application: + # ComposerクラスマップでPresenterを検索しますか? + scanComposer: ... # (bool) デフォルトは true + + # クラス名とファイル名が一致する必要があるマスク + scanFilter: ... # (string) デフォルトは '*Presenter' + + # どのディレクトリでPresenterを検索しますか? + scanDirs: # (string[]|false) デフォルトは '%appDir%' + - %vendorDir%/mymodule +``` + +`scanDirs` にリストされたディレクトリは、デフォルト値 `%appDir%` を上書きするのではなく、補完します。したがって、`scanDirs` には `%appDir%` と `%vendorDir%/mymodule` の両方のパスが含まれます。デフォルトのディレクトリを除外したい場合は、値を上書きする[感嘆符 |dependency-injection:configuration#マージ]を使用します。 + +```neon +application: + scanDirs!: + - %vendorDir%/mymodule +``` + +ディレクトリのスキャンは、false値を指定することで無効にできます。Presenterの自動追加を完全に抑制することはお勧めしません。そうしないと、アプリケーションのパフォーマンスが低下します。 + + +Latte テンプレート +============ + +この設定により、コンポーネントとPresenterにおけるLatteの動作をグローバルに影響させることができます。 + +```neon +latte: + # メインテンプレート(true)またはすべてのコンポーネント(all)に対してTracy BarにLatteパネルを表示しますか? + debugger: ... # (true|false|'all') デフォルトは true + + # declare(strict_types=1) ヘッダーを持つテンプレートを生成します + strictTypes: ... # (bool) デフォルトは false + + # [厳密なパーサーモード |latte:develop#striktní režim]を有効にします + strictParsing: ... # (bool) デフォルトは false + + # [生成されたコードのチェック |latte:develop#Kontrola vygenerovaného kódu]を有効にします + phpLinter: ... # (string) デフォルトは null + + # ロケールを設定します + locale: cs_CZ # (string) デフォルトは null + + # $this->template オブジェクトのクラス + templateClass: App\MyTemplateClass # デフォルトは Nette\Bridges\ApplicationLatte\DefaultTemplate +``` + +Latteバージョン3を使用している場合は、次のようにして新しい[拡張機能 |latte:extending-latte#Latte Extension]を追加できます。 + +```neon +latte: + extensions: + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) +``` + +Latteバージョン2を使用している場合は、クラス名を指定するか、サービスへの参照を指定することで、新しいタグ(マクロ)を登録できます。デフォルトでは `install()` メソッドが呼び出されますが、別のメソッド名を指定することで変更できます。 + +```neon +latte: + # カスタムLatteタグの登録 + macros: + - App\MyLatteMacros::register # 静的メソッド、クラス名またはcallable + - @App\MyLatteMacrosFactory # install() メソッドを持つサービス + - @App\MyLatteMacrosFactory::register # register() メソッドを持つサービス + +services: + - App\MyLatteMacrosFactory +``` + + +ルーティング +====== + +基本設定: + +```neon +routing: + # Tracy Barにルーティングパネルを表示しますか? + debugger: ... # (bool) デフォルトは true + + # ルーターをDIコンテナにシリアライズします + cache: ... # (bool) デフォルトは false +``` + +ルーティングは通常、[RouterFactory |routing#ルートコレクション]クラスで定義します。あるいは、`maska: akce` のペアを使用して設定でルートを定義することもできますが、この方法では設定の柔軟性がそれほど高くありません。 + +```neon +routing: + routes: + 'detail/': Admin:Home:default + '/': Front:Home:default +``` + + +定数 +========= + +PHP定数の作成。 + +```neon +constants: + Foobar: 'baz' +``` + +アプリケーション起動後、`Foobar` 定数が作成されます。 + +.[note] +定数は、グローバルにアクセス可能な変数として使用すべきではありません。オブジェクトに値を渡すには、[依存関係注入 |dependency-injection:passing-dependencies]を使用してください。 + + +PHP +=== + +PHPディレクティブの設定。すべてのディレクティブの概要は[php.net |https://www.php.net/manual/en/ini.list.php]にあります。 + +```neon +php: + date.timezone: Europe/Prague +``` + + +DI サービス +======= + +これらのサービスはDIコンテナに追加されます。 + +| 名前 | 型 | 説明 +|---------------------------------------------------------- +| `application.application` | [api:Nette\Application\Application] | [アプリケーション全体の起動 |how-it-works#Nette Application] +| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] +| `application.presenterFactory` | [api:Nette\Application\PresenterFactory] | Presenter のファクトリ +| `application.###` | [api:Nette\Application\UI\Presenter] | 個々の Presenter +| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | `Latte\Engine` オブジェクトのファクトリ +| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | [`$this->template` |templates] のファクトリ diff --git a/application/ja/creating-links.texy b/application/ja/creating-links.texy new file mode 100644 index 0000000000..9b86073140 --- /dev/null +++ b/application/ja/creating-links.texy @@ -0,0 +1,286 @@ +URLリンクの作成 +********* + +
    + +Netteでリンクを作成するのは、指をさすのと同じくらい簡単です。指し示すだけで、フレームワークがすべての作業を代行します。ここでは以下について説明します。 + +- テンプレートやその他の場所でリンクを作成する方法 +- 現在のページへのリンクを区別する方法 +- 不正なリンクの対処法 + +
    + + +[双方向ルーティング |routing]のおかげで、後で変更される可能性のあるアプリケーションのURLをテンプレートやコードにハードコーディングしたり、複雑に組み立てたりする必要は決してありません。リンクでPresenterとアクションを指定し、必要に応じてパラメータを渡すだけで、フレームワークがURLを自動的に生成します。実際には、関数を呼び出すのと非常によく似ています。これは気に入るはずです。 + + +Presenterテンプレート内 +================ + +最も頻繁にリンクを作成するのはテンプレートであり、`n:href` 属性は素晴らしいヘルパーです。 + +```latte +詳細 +``` + +HTML属性 `href` の代わりに、[n:属性 |latte:syntax#n:属性] `n:href` を使用していることに注意してください。その値は、`href` 属性の場合のようにURLではなく、Presenterとアクションの名前です。 + +リンクをクリックすることは、簡単に言えば、`ProductPresenter::renderShow()` メソッドを呼び出すようなものです。そして、そのシグネチャにパラメータがある場合は、引数を付けて呼び出すことができます。 + +```latte +製品詳細 +``` + +名前付きパラメータを渡すことも可能です。次のリンクは、値 `cs` を持つ `lang` パラメータを渡します。 + +```latte +製品詳細 +``` + +`ProductPresenter::renderShow()` メソッドがそのシグネチャに `$lang` を持っていない場合、`$lang = $this->getParameter('lang')` を使用してパラメータの値を取得するか、[プロパティ |presenters#リクエストパラメータ]から取得できます。 + +パラメータが配列に格納されている場合、`...` 演算子(Latte 2.xでは `(expand)` 演算子)を使用して展開できます。 + +```latte +{var $args = [$product->id, lang => cs]} +製品詳細 +``` + +リンクでは、いわゆる[パーシステントパラメータ |presenters#パーシステントパラメータ]も自動的に渡されます。 + +`n:href` 属性はHTMLタグ `` に非常に便利です。リンクを他の場所、例えばテキスト内に出力したい場合は、`{link}` を使用します。 + +```latte +アドレスは: {link Home:default} +``` + + +コード内 +==== + +Presenterでリンクを作成するには、`link()` メソッドを使用します。 + +```php +$url = $this->link('Product:show', $product->id); +``` + +パラメータは、名前付きパラメータを含めることができる配列を使用して渡すこともできます。 + +```php +$url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); +``` + +リンクはPresenterなしでも作成できます。そのために[#LinkGenerator]とその `link()` メソッドがあります。 + + +Presenterへのリンク +============== + +リンクのターゲットがPresenterとアクションの場合、構文は次のようになります。 + +``` +[//] [[[[:]module:]presenter:]action | this] [#fragment] +``` + +この形式は、すべてのLatteタグと、リンクを扱うすべてのPresenterメソッド(`n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()`、および[#LinkGenerator])でサポートされています。したがって、例で `n:href` が使用されていても、これらの関数のいずれかを使用できます。 + +したがって、基本形式は `Presenter:action` です。 + +```latte +ホームページ +``` + +現在のPresenterのアクションにリンクする場合、その名前を省略できます。 + +```latte +ホームページ +``` + +ターゲットが `default` アクションの場合、省略できますが、コロンは残す必要があります。 + +```latte +ホームページ +``` + +リンクは他の[モジュール |directory-structure#Presenterとテンプレート]にも向かうことができます。ここでは、リンクはネストされたサブモジュールへの相対リンク、または絶対リンクに区別されます。原理はディスク上のパスに似ていますが、スラッシュの代わりにコロンが使用されます。現在のPresenterが `Front` モジュールの一部であると仮定すると、次のように記述します。 + +```latte +Front:Shop:Product:show へのリンク +Admin:Product:show へのリンク +``` + +特別なケースは、[自分自身へのリンク |#現在のページへのリンク]で、ターゲットとして `this` を指定します。 + +```latte +更新 +``` + +ハッシュマーク `#` の後のいわゆるフラグメントを介して、ページの特定の部分にリンクできます。 + +```latte +Home:default とフラグメント #main へのリンク +``` + + +絶対パス +==== + +`link()` または `n:href` を使用して生成されたリンクは常に絶対パス(つまり、`/` 文字で始まる)ですが、`https://domain` のようなプロトコルとドメインを持つ絶対URLではありません。 + +絶対URLを生成するには、先頭に2つのスラッシュを追加します(例:`n:href="//Home:"`)。または、`$this->absoluteUrls = true` を設定して、Presenterが絶対リンクのみを生成するように切り替えることもできます。 + + +現在のページへのリンク +=========== + +ターゲット `this` は現在のページへのリンクを作成します。 + +```latte +更新 +``` + +同時に、`action()` または `render()` メソッドのシグネチャで指定されたすべてのパラメータも転送されます(`action()` が定義されていない場合)。したがって、`Product:show` ページで `id: 123` の場合、`this` へのリンクもこのパラメータを渡します。 + +もちろん、パラメータを直接指定することも可能です。 + +```latte +更新 +``` + +`isLinkCurrent()` 関数は、リンクのターゲットが現在のページと同じかどうかを判断します。これは、例えばテンプレートでリンクを区別するためなどに使用できます。 + +パラメータは `link()` メソッドと同じですが、さらに特定のアクションの代わりにワイルドカード `*` を指定できます。これは、そのPresenterの任意のアクションを意味します。 + +```latte +{if !isLinkCurrent('Admin:login')} + ログイン +{/if} + +
  • + ... +
  • +``` + +1つの要素で `n:href` と組み合わせる場合、短縮形を使用できます。 + +```latte +... +``` + +ワイルドカード `*` はアクションの代わりにのみ使用でき、Presenterの代わりには使用できません。 + +特定のモジュールまたはそのサブモジュールにいるかどうかを判断するには、`isModuleCurrent(moduleName)` メソッドを使用します。 + +```latte +
  • + ... +
  • +``` + + +シグナルへのリンク +========= + +リンクのターゲットはPresenterとアクションだけでなく、[シグナル |components#シグナル](`handle()` メソッドを呼び出す)にすることもできます。その場合、構文は次のようになります。 + +``` +[//] [sub-component:]signal! [#fragment] +``` + +したがって、シグナルは感嘆符で区別されます。 + +```latte +シグナル +``` + +サブコンポーネント(またはサブサブコンポーネント)のシグナルへのリンクを作成することもできます。 + +```latte +シグナル +``` + + +コンポーネント内のリンク +============ + +[コンポーネント|components]は独立した再利用可能なユニットであり、周囲のPresenterへの依存関係を持つべきではないため、リンクはここで少し異なります。Latte属性 `n:href` とタグ `{link}`、および `link()` などのコンポーネントメソッドは、リンクのターゲットを**常にシグナル名と見なします**。したがって、感嘆符を指定する必要さえありません。 + +```latte +アクションではなくシグナル +``` + +コンポーネントテンプレートでPresenterにリンクしたい場合は、`{plink}` タグを使用します。 + +```latte +ホーム +``` + +またはコードで + +```php +$this->getPresenter()->link('Home:default') +``` + + +エイリアス .{data-version:v3.2.2} +============================ + +Presenter:アクションのペアに覚えやすいエイリアスを割り当てると便利な場合があります。例えば、ホームページ `Front:Home:default` を単に `home` と名付けたり、`Admin:Dashboard:default` を `admin` と名付けたりします。 + +エイリアスは、[設定|configuration]の `application › aliases` キーの下で定義されます。 + +```neon +application: + aliases: + home: Front:Home:default + admin: Admin:Dashboard:default + sign: Front:Sign:in +``` + +リンクでは、アットマークを使用して記述されます。例えば: + +```latte +管理 +``` + +これらは、`redirect()` などのリンクを扱うすべてのメソッドでもサポートされています。 + + +不正なリンク +====== + +存在しないPresenterにつながる、ターゲットメソッドがシグネチャで受け入れるよりも多くのパラメータを渡す、またはターゲットアクションのURLを生成できないなどの理由で、不正なリンクを作成することがあります。不正なリンクをどのように処理するかは、静的変数 `Presenter::$invalidLinkMode` によって決定されます。これは、これらの値(定数)の組み合わせを取ることができます。 + +- `Presenter::InvalidLinkSilent` - サイレントモード、URLとして `#` 文字が返されます +- `Presenter::InvalidLinkWarning` - E_USER_WARNING 警告がスローされ、本番モードではログに記録されますが、スクリプトの実行は中断されません +- `Presenter::InvalidLinkTextual` - 視覚的な警告、エラーをリンクに直接出力します +- `Presenter::InvalidLinkException` - InvalidLinkException 例外がスローされます + +デフォルト設定は、本番モードでは `InvalidLinkWarning`、開発モードでは `InvalidLinkWarning | InvalidLinkTextual` です。本番環境での `InvalidLinkWarning` はスクリプトの実行を中断しませんが、警告はログに記録されます。開発環境では、[Tracy |tracy:]によってキャッチされ、ブルースクリーンが表示されます。`InvalidLinkTextual` は、`#error:` 文字で始まるエラーメッセージをURLとして返すように機能します。そのようなリンクを一目でわかるようにするには、CSSに以下を追加します。 + +```css +a[href^="#error:"] { + background: red; + color: white; +} +``` + +開発環境で警告が生成されないようにしたい場合は、[設定|configuration]で直接サイレントモードを設定できます。 + +```neon +application: + silentLinks: true +``` + + +LinkGenerator +============= + +Presenterが存在しない場合に、`link()` メソッドと同様の快適さでリンクを作成するにはどうすればよいでしょうか?そのために[api:Nette\Application\LinkGenerator]があります。 + +LinkGeneratorは、コンストラクタ経由で渡してもらい、その後その `link()` メソッドを使用してリンクを作成できるサービスです。 + +Presenterとの違いがあります。LinkGeneratorはすべてのリンクを直接絶対URLとして作成します。さらに、「現在のPresenter」は存在しないため、ターゲットとしてアクション名 `link('default')` だけを指定したり、モジュールへの相対パスを指定したりすることはできません。 + +不正なリンクは常に `Nette\Application\UI\InvalidLinkException` をスローします。 diff --git a/application/ja/directory-structure.texy b/application/ja/directory-structure.texy new file mode 100644 index 0000000000..5ad882a036 --- /dev/null +++ b/application/ja/directory-structure.texy @@ -0,0 +1,526 @@ +アプリケーションのディレクトリ構造 +***************** + +
    + +Nette Frameworkプロジェクトのために、明確でスケーラブルなディレクトリ構造をどのように設計すればよいでしょうか?コードの整理に役立つベストプラクティスを紹介します。以下について学びます。 + +- アプリケーションをディレクトリに**論理的に分割**する方法 +- プロジェクトの成長に合わせて**うまくスケール**するように構造を設計する方法 +- **可能な代替案**とその利点または欠点 + +
    + + +Nette Framework自体は特定の構造に固執しないことを言及することが重要です。あらゆるニーズや好みに簡単に適応できるように設計されています。 + + +プロジェクトの基本構造 +=========== + +Nette Frameworkは固定のディレクトリ構造を指示しませんが、[Web Project|https://github.com/nette/web-project]の形で実証済みのデフォルトの配置があります。 + +/--pre +web-project/ +├── app/ ← アプリケーションディレクトリ +├── assets/ ← SCSS、JS、画像ファイルなど、代替として resources/ +├── bin/ ← コマンドラインスクリプト +├── config/ ← 設定 +├── log/ ← ログ記録されたエラー +├── temp/ ← 一時ファイル、キャッシュ +├── tests/ ← テスト +├── vendor/ ← Composerによってインストールされたライブラリ +└── www/ ← 公開ディレクトリ (document-root) +\-- + +この構造は、ニーズに応じて自由に調整できます。フォルダの名前を変更したり、移動したりできます。その後、`Bootstrap.php` ファイルと、場合によっては `composer.json` のディレクトリへの相対パスを更新するだけです。それ以上のことは必要ありません。複雑な再設定や定数の変更は不要です。Netteは賢い自動検出機能を備えており、URLベースを含むアプリケーションの場所を自動的に認識します。 + + +コード整理の原則 +======== + +新しいプロジェクトを初めて調べるときは、すぐに慣れることができるはずです。`app/Model/` ディレクトリを開いて、この構造を見ると想像してみてください。 + +/--pre +app/Model/ +├── Services/ +├── Repositories/ +└── Entities/ +\-- + +これから読み取れるのは、プロジェクトがいくつかのサービス、リポジトリ、エンティティを使用していることだけです。アプリケーションの実際の目的については何もわかりません。 + +別のアプローチを見てみましょう - **ドメインによる整理**: + +/--pre +app/Model/ +├── Cart/ +├── Payment/ +├── Order/ +└── Product/ +\-- + +ここでは違います - 一目でeコマースサイトであることがわかります。ディレクトリ名自体が、アプリケーションができること、つまり支払い、注文、製品を扱うことを示しています。 + +最初のアプローチ(クラスタイプによる整理)は、実際には多くの問題を引き起こします。論理的に関連するコードが異なるフォルダに分散され、それらの間を行き来する必要があります。したがって、ドメインごとに整理します。 + + +名前空間 +---- + +ディレクトリ構造がアプリケーションの名前空間に対応するのが慣例です。つまり、ファイルの物理的な場所がその名前空間に対応します。例えば、`app/Model/Product/ProductRepository.php` に配置されたクラスは、`App\Model\Product` 名前空間を持つべきです。この原則は、コードの理解を助け、オートローディングを簡素化します。 + + +名前の単数形 vs 複数形 +------------- + +アプリケーションのメインディレクトリでは単数形を使用していることに注意してください:`app`, `config`, `log`, `temp`, `www`。同様に、アプリケーション内部でも:`Model`, `Core`, `Presentation`。これは、それぞれが1つのまとまった概念を表しているためです。 + +同様に、例えば `app/Model/Product` は製品に関するすべてを表します。`Products` とは呼びません。なぜなら、それは製品でいっぱいのフォルダではないからです(そこには `nokia.php`, `samsung.php` のようなファイルがあるでしょう)。それは、製品を扱うクラス、つまり `ProductRepository.php`, `ProductService.php` を含む名前空間です。 + +`app/Tasks` フォルダは複数形です。なぜなら、それは独立した実行可能なスクリプトのセット、つまり `CleanupTask.php`, `ImportTask.php` を含んでいるからです。それぞれが独立したユニットです。 + +一貫性のために、以下を使用することをお勧めします。 +- 機能的な全体を表す名前空間には単数形(複数のエンティティを扱う場合でも) +- 独立したユニットのコレクションには複数形 +- 不確かな場合、またはそれについて考えたくない場合は、単数形を選択してください + + +公開ディレクトリ `www/` +=============== + +このディレクトリは、Webからアクセスできる唯一のディレクトリ(いわゆるdocument-root)です。`www/` の代わりに `public/` という名前をよく見かけることもありますが、これは単なる慣例の問題であり、機能には影響しません。ディレクトリには以下が含まれます。 +- アプリケーションの[エントリポイント |bootstrapping#index.php] `index.php` +- mod_rewrite(Apacheの場合)のルールを含む `.htaccess` ファイル +- 静的ファイル(CSS、JavaScript、画像) +- アップロードされたファイル + +アプリケーションの適切なセキュリティのためには、[document-rootを正しく設定 |nette:troubleshooting#URLから www ディレクトリを変更または削除する方法は]することが不可欠です。 + +.[note] +このディレクトリに `node_modules/` フォルダを決して配置しないでください。実行可能であり、公開すべきではない数千のファイルが含まれています。 + + +アプリケーションディレクトリ `app/` +===================== + +これはアプリケーションコードを含むメインディレクトリです。基本構造: + +/--pre +app/ +├── Core/ ← インフラストラクチャ関連 +├── Model/ ← ビジネスロジック +├── Presentation/ ← Presenterとテンプレート +├── Tasks/ ← コマンドスクリプト +└── Bootstrap.php ← アプリケーションのブートストラップクラス +\-- + +`Bootstrap.php` は、環境を初期化し、設定をロードし、DIコンテナを作成する[アプリケーションの起動クラス|bootstrapping]です。 + +次に、個々のサブディレクトリについて詳しく見ていきましょう。 + + +Presenterとテンプレート +================ + +アプリケーションのプレゼンテーション部分は `app/Presentation` ディレクトリにあります。代替案は短い `app/UI` です。これは、すべてのPresenter、そのテンプレート、および可能なヘルパークラスのための場所です。 + +このレイヤーをドメインごとに整理します。eコマース、ブログ、APIを組み合わせた複雑なプロジェクトでは、構造は次のようになります。 + +/--pre +app/Presentation/ +├── Shop/ ← eコマースフロントエンド +│ ├── Product/ +│ ├── Cart/ +│ └── Order/ +├── Blog/ ← ブログ +│ ├── Home/ +│ └── Post/ +├── Admin/ ← 管理画面 +│ ├── Dashboard/ +│ └── Products/ +└── Api/ ← APIエンドポイント + └── V1/ +\-- + +一方、単純なブログでは、次のような分割を使用します。 + +/--pre +app/Presentation/ +├── Front/ ← Webフロントエンド +│ ├── Home/ +│ └── Post/ +├── Admin/ ← 管理画面 +│ ├── Dashboard/ +│ └── Posts/ +├── Error/ +└── Export/ ← RSS、サイトマップなど +\-- + +`Home/` や `Dashboard/` のようなフォルダには、Presenterとテンプレートが含まれます。`Front/`, `Admin/`, `Api/` のようなフォルダは**モジュール**と呼ばれます。技術的には、これらはアプリケーションを論理的に分割するために使用される通常のディレクトリです。 + +Presenterを含む各フォルダには、同じ名前のPresenterとそのテンプレートが含まれます。例えば、`Dashboard/` フォルダには以下が含まれます。 + +/--pre +Dashboard/ +├── DashboardPresenter.php ← Presenter +└── default.latte ← テンプレート +\-- + +このディレクトリ構造は、クラスの名前空間に反映されます。例えば、`DashboardPresenter` は `App\Presentation\Admin\Dashboard` 名前空間に配置されます([#Presenterのマッピング]を参照)。 + +```php +namespace App\Presentation\Admin\Dashboard; + +class DashboardPresenter extends Nette\Application\UI\Presenter +{ + // ... +} +``` + +`Admin` モジュール内の `Dashboard` Presenterには、アプリケーション内でコロン表記を使用して `Admin:Dashboard` として参照します。その `default` アクションには `Admin:Dashboard:default` として参照します。ネストされたモジュールの場合、複数のコロンを使用します。例えば `Shop:Order:Detail:default` です。 + + +構造の柔軟な開発 +-------- + +この構造の大きな利点の1つは、プロジェクトの成長するニーズにエレガントに適応する方法です。例として、XMLフィードを生成する部分を取り上げましょう。最初は単純な形式です。 + +/--pre +Export/ +├── ExportPresenter.php ← すべてのエクスポート用の単一Presenter +├── sitemap.latte ← サイトマップ用テンプレート +└── feed.latte ← RSSフィード用テンプレート +\-- + +時間が経つにつれて、さらに多くのフィードタイプが追加され、それらに対してより多くのロジックが必要になります... 問題ありません!`Export/` フォルダは簡単にモジュールになります。 + +/--pre +Export/ +├── Sitemap/ +│ ├── SitemapPresenter.php +│ └── sitemap.latte +└── Feed/ + ├── FeedPresenter.php + ├── zbozi.latte ← Zboží.cz用フィード + └── heureka.latte ← Heureka.cz用フィード +\-- + +この変換は完全にスムーズです - 新しいサブフォルダを作成し、コードをそれらに分割し、リンクを更新するだけです(例:`Export:feed` から `Export:Feed:zbozi` へ)。これにより、必要に応じて構造を徐々に拡張でき、ネストのレベルに制限はありません。 + +例えば、管理画面で注文管理に関連する多くのPresenter(`OrderDetail`, `OrderEdit`, `OrderDispatch` など)がある場合、より良い整理のために、この場所に `Order` モジュール(フォルダ)を作成できます。そこにはPresenter `Detail`, `Edit`, `Dispatch` などの(フォルダ)が含まれます。 + + +テンプレートの配置 +--------- + +前の例では、テンプレートがPresenterと同じフォルダに直接配置されていることを見ました。 + +/--pre +Dashboard/ +├── DashboardPresenter.php ← Presenter +├── DashboardTemplate.php ← テンプレート用のオプションクラス +└── default.latte ← テンプレート +\-- + +この配置は、実際には最も便利であることが証明されています - すべての関連ファイルがすぐに手元にあります。 + +あるいは、テンプレートを `templates/` サブフォルダに配置することもできます。Netteは両方のバリアントをサポートしています。テンプレートを `Presentation/` フォルダの外に完全に配置することもできます。テンプレートの配置オプションに関するすべての情報は、[テンプレートの検索 |templates#テンプレートの検索]の章にあります。 + + +ヘルパークラスとコンポーネント +--------------- + +Presenterとテンプレートには、しばしば他のヘルパーファイルも伴います。それらをその適用範囲に応じて論理的に配置します。 + +1. **Presenterのすぐ隣**、特定のPresenter用の特定のコンポーネントの場合: + +/--pre +Product/ +├── ProductPresenter.php +├── ProductGrid.php ← 製品リスト用コンポーネント +└── FilterForm.php ← フィルタリング用フォーム +\-- + +2. **モジュール用** - アルファベット順の先頭に明確に配置される `Accessory` フォルダを使用することをお勧めします。 + +/--pre +Front/ +├── Accessory/ +│ ├── NavbarControl.php ← フロントエンド用コンポーネント +│ └── TemplateFilters.php +├── Product/ +└── Cart/ +\-- + +3. **アプリケーション全体用** - `Presentation/Accessory/` 内: +/--pre +app/Presentation/ +├── Accessory/ +│ ├── LatteExtension.php +│ └── TemplateFilters.php +├── Front/ +└── Admin/ +\-- + +または、`LatteExtension.php` や `TemplateFilters.php` のようなヘルパークラスをインフラストラクチャフォルダ `app/Core/Latte/` に配置することもできます。そして、コンポーネントを `app/Components` に配置します。選択はチームの慣習によります。 + + +モデル - アプリケーションの心臓部 +================== + +モデルには、アプリケーションのすべてのビジネスロジックが含まれています。その整理には、再びルールが適用されます - ドメインごとに構造化します。 + +/--pre +app/Model/ +├── Payment/ ← 支払いに関するすべて +│ ├── PaymentFacade.php ← メインエントリポイント +│ ├── PaymentRepository.php +│ ├── Payment.php ← エンティティ +├── Order/ ← 注文に関するすべて +│ ├── OrderFacade.php +│ ├── OrderRepository.php +│ ├── Order.php +└── Shipping/ ← 配送に関するすべて +\-- + +モデルでは、通常、これらのタイプのクラスに遭遇します。 + +**ファサード**: アプリケーション内の特定のドメインへのメインエントリポイントを表します。完全なユースケース(「注文を作成する」や「支払いを処理する」など)を実装するために、異なるサービス間の協力を調整するオーケストレーターとして機能します。オーケストレーションレイヤーの下で、ファサードは実装の詳細をアプリケーションの他の部分から隠し、特定のドメインを扱うためのクリーンなインターフェースを提供します。 + +```php +class OrderFacade +{ + public function createOrder(Cart $cart): Order + { + // 検証 + // 注文の作成 + // 電子メールの送信 + // 統計への書き込み + } +} +``` + +**サービス**: ドメイン内の特定のビジネス操作に焦点を当てます。ユースケース全体をオーケストレーションするファサードとは異なり、サービスは特定のビジネスロジック(価格計算や支払い処理など)を実装します。サービスは通常ステートレスであり、より複雑な操作のための構成要素としてファサードによって使用されるか、より単純なタスクのためにアプリケーションの他の部分によって直接使用されることができます。 + +```php +class PricingService +{ + public function calculateTotal(Order $order): Money + { + // 価格計算 + } +} +``` + +**リポジトリ**: データストレージ、通常はデータベースとのすべての通信を保証します。そのタスクは、エンティティのロードと保存、およびそれらを検索するためのメソッドの実装です。リポジトリは、アプリケーションの他の部分をデータベースの実装の詳細から分離し、データを扱うためのオブジェクト指向インターフェースを提供します。 + +```php +class OrderRepository +{ + public function find(int $id): ?Order + { + } + + public function findByCustomer(int $customerId): array + { + } +} +``` + +**エンティティ**: アプリケーションの主要なビジネスコンセプトを表すオブジェクトで、独自のアイデンティティを持ち、時間とともに変化します。通常、これらはORM(Nette Database ExplorerやDoctrineなど)を使用してデータベーステーブルにマッピングされるクラスです。エンティティは、そのデータに関するビジネスルールと検証ロジックを含むことができます。 + +```php +// orders データベーステーブルにマッピングされたエンティティ +class Order extends Nette\Database\Table\ActiveRow +{ + public function addItem(Product $product, int $quantity): void + { + $this->related('order_items')->insert([ + 'product_id' => $product->id, + 'quantity' => $quantity, + 'unit_price' => $product->price, + ]); + } +} +``` + +**値オブジェクト**: 独自のアイデンティティを持たない値を表す不変オブジェクト - 例えば、金額や電子メールアドレス。同じ値を持つ値オブジェクトの2つのインスタンスは同一と見なされます。 + + +インフラストラクチャコード +============= + +`Core/` フォルダ(または `Infrastructure/`)は、アプリケーションの技術的な基盤のホームです。インフラストラクチャコードには通常、以下が含まれます。 + +/--pre +app/Core/ +├── Router/ ← ルーティングとURL管理 +│ └── RouterFactory.php +├── Security/ ← 認証と認可 +│ ├── Authenticator.php +│ └── Authorizator.php +├── Logging/ ← ロギングと監視 +│ ├── SentryLogger.php +│ └── FileLogger.php +├── Cache/ ← キャッシュレイヤー +│ └── FullPageCache.php +└── Integration/ ← 外部サービスとの統合 + ├── Slack/ + └── Stripe/ +\-- + +小規模なプロジェクトでは、もちろんフラットな分割で十分です。 + +/--pre +Core/ +├── RouterFactory.php +├── Authenticator.php +└── QueueMailer.php +\-- + +これは次のようなコードです。 + +- 技術的なインフラストラクチャ(ルーティング、ロギング、キャッシュ)を扱います +- 外部サービス(Sentry、Elasticsearch、Redis)を統合します +- アプリケーション全体に基本的なサービス(メール、データベース)を提供します +- ほとんどの場合、特定のドメイン(製品、注文、記事)に依存しません - キャッシュやロガーはeコマースやブログで同じように機能します。 + +特定のクラスがここに属するか、モデルに属するか迷っていますか?重要な違いは、`Core/` のコードは: + +- ドメイン(製品、注文、記事)について何も知りません +- ほとんどの場合、別のプロジェクトに転送できます +- 「どのように機能するか」(メールを送信する方法)を扱い、「何をするか」(どのメールを送信するか)ではありません + +よりよく理解するための例: + +- `App\Core\MailerFactory` - 電子メール送信用のクラスのインスタンスを作成し、SMTP設定を扱います +- `App\Model\OrderMailer` - `MailerFactory` を使用して注文に関する電子メールを送信し、そのテンプレートを知っており、いつ送信すべきかを知っています + + +コマンドスクリプト +========= + +アプリケーションは、通常のHTTPリクエスト以外のアクティビティを実行する必要があることがよくあります - バックグラウンドでのデータ処理、メンテナンス、または定期的なタスクなどです。実行には `bin/` ディレクトリの単純なスクリプトが使用され、実装ロジック自体は `app/Tasks/`(または `app/Commands/`)に配置されます。 + +例: + +/--pre +app/Tasks/ +├── Maintenance/ ← メンテナンススクリプト +│ ├── CleanupCommand.php ← 古いデータの削除 +│ └── DbOptimizeCommand.php ← データベースの最適化 +├── Integration/ ← 外部システムとの統合 +│ ├── ImportProducts.php ← サプライヤーシステムからのインポート +│ └── SyncOrders.php ← 注文の同期 +└── Scheduled/ ← 定期的なタスク + ├── NewsletterCommand.php ← ニュースレターの送信 + └── ReminderCommand.php ← 顧客への通知 +\-- + +モデルに属するものとコマンドスクリプトに属するものは何ですか?例えば、1つの電子メールを送信するロジックはモデルの一部ですが、数千の電子メールの一括送信は `Tasks/` に属します。 + +タスクは通常、[コマンドラインから実行 |https://blog.nette.org/en/cli-scripts-in-nette-application]されるか、cron経由で実行されます。HTTPリクエスト経由で実行することもできますが、セキュリティを考慮する必要があります。タスクを実行するPresenterは、例えばログインしたユーザーのみ、または強力なトークンと許可されたIPアドレスからのアクセスのみに保護する必要があります。長いタスクの場合、スクリプトのタイムアウト制限を増やし、セッションがロックされないように `session_write_close()` を使用する必要があります。 + + +その他の可能なディレクトリ +============= + +前述の基本ディレクトリに加えて、プロジェクトのニーズに応じて他の特殊なフォルダを追加できます。最も一般的なものとその使用法を見てみましょう。 + +/--pre +app/ +├── Api/ ← プレゼンテーションレイヤーに依存しないAPIロジック +├── Database/ ← テストデータ用のマイグレーションスクリプトとシーダー +├── Components/ ← アプリケーション全体で共有されるビジュアルコンポーネント +├── Event/ ← イベント駆動アーキテクチャを使用する場合に便利 +├── Mail/ ← 電子メールテンプレートと関連ロジック +└── Utils/ ← ヘルパークラス +\-- + +アプリケーション全体のPresenterで使用される共有ビジュアルコンポーネントには、`app/Components` または `app/Controls` フォルダを使用できます。 + +/--pre +app/Components/ +├── Form/ ← 共有フォームコンポーネント +│ ├── SignInForm.php +│ └── UserForm.php +├── Grid/ ← データリスト用コンポーネント +│ └── DataGrid.php +└── Navigation/ ← ナビゲーション要素 + ├── Breadcrumbs.php + └── Menu.php +\-- + +ここには、より複雑なロジックを持つコンポーネントが属します。複数のプロジェクト間でコンポーネントを共有したい場合は、それらを別のComposerパッケージに分離することをお勧めします。 + +`app/Mail` ディレクトリに電子メール通信の管理を配置できます。 + +/--pre +app/Mail/ +├── templates/ ← 電子メールテンプレート +│ ├── order-confirmation.latte +│ └── welcome.latte +└── OrderMailer.php +\-- + + +Presenterのマッピング +=============== + +マッピングは、Presenter名からクラス名を導出するためのルールを定義します。これらは[設定|configuration]の `application › mapping` キーの下で指定します。 + +このページでは、Presenterを `app/Presentation` フォルダ(または `app/UI`)に配置することを示しました。この慣例をNetteに設定ファイルで伝える必要があります。1行で十分です。 + +```neon +application: + mapping: App\Presentation\*\**Presenter +``` + +マッピングはどのように機能しますか?よりよく理解するために、まずモジュールなしのアプリケーションを想像してみましょう。Presenterクラスが `App\Presentation` 名前空間に属するようにし、Presenter `Home` がクラス `App\Presentation\HomePresenter` にマッピングされるようにしたいとします。これは、この設定で実現できます。 + +```neon +application: + mapping: App\Presentation\*Presenter +``` + +マッピングは、Presenter名 `Home` がマスク `App\Presentation\*Presenter` のアスタリスクを置き換え、結果としてクラス名 `App\Presentation\HomePresenter` を得るように機能します。簡単です! + +しかし、この章や他の章の例でわかるように、Presenterクラスを同名のサブディレクトリに配置します。例えば、Presenter `Home` はクラス `App\Presentation\Home\HomePresenter` にマッピングされます。これは、コロンを2重にすることで実現できます(Nette Application 3.2が必要)。 + +```neon +application: + mapping: App\Presentation\**Presenter +``` + +次に、Presenterをモジュールにマッピングします。各モジュールに対して特定のマッピングを定義できます。 + +```neon +application: + mapping: + Front: App\Presentation\Front\**Presenter + Admin: App\Presentation\Admin\**Presenter + Api: App\Api\*Presenter +``` + +この設定によると、Presenter `Front:Home` はクラス `App\Presentation\Front\Home\HomePresenter` にマッピングされ、Presenter `Api:OAuth` はクラス `App\Api\OAuthPresenter` にマッピングされます。 + +モジュール `Front` と `Admin` は同様のマッピング方法を持ち、そのようなモジュールはおそらくもっと多いため、それらを置き換える一般的なルールを作成することが可能です。したがって、クラスマスクにモジュール用の新しいアスタリスクが追加されます。 + +```neon +application: + mapping: + *: App\Presentation\*\**Presenter + Api: App\Api\*Presenter +``` + +これは、例えばPresenter `Admin:User:Edit` のような、より深くネストされたディレクトリ構造でも機能します。アスタリスクを持つセグメントは各レベルで繰り返され、結果はクラス `App\Presentation\Admin\User\Edit\EditPresenter` になります。 + +代替の表記法は、文字列の代わりに3つのセグメントからなる配列を使用することです。この表記法は前のものと同等です。 + +```neon +application: + mapping: + *: [App\Presentation, *, **Presenter] + Api: [App\Api, '', *Presenter] +``` diff --git a/application/ja/how-it-works.texy b/application/ja/how-it-works.texy new file mode 100644 index 0000000000..5ba6dd8368 --- /dev/null +++ b/application/ja/how-it-works.texy @@ -0,0 +1,200 @@ +アプリケーションはどのように動作しますか? +********************* + +
    + +あなたは今、Netteドキュメントの基本憲章を読んでいます。Webアプリケーションがどのように機能するかの全体像を学びます。AからZまで、誕生の瞬間からPHPスクリプトの最後の処理まで。読み終えた後、あなたは知っているでしょう: + +- 全体がどのように機能するか +- Bootstrap、Presenter、DIコンテナとは何か +- ディレクトリ構造はどのようになっているか + +
    + + +ディレクトリ構造 +======== + +[WebProject|https://github.com/nette/web-project]と呼ばれるWebアプリケーションのスケルトンの例を開き、読みながら言及されているファイルを見ることができます。 + +ディレクトリ構造は次のようになります。 + +/--pre +web-project/ +├── app/ ← アプリケーションディレクトリ +│ ├── Core/ ← 実行に必要な基本クラス +│ │ └── RouterFactory.php ← URLアドレスの設定 +│ ├── Presentation/ ← Presenter、テンプレートなど +│ │ ├── @layout.latte ← レイアウトテンプレート +│ │ └── Home/ ← Home Presenterのディレクトリ +│ │ ├── HomePresenter.php ← Home Presenterクラス +│ │ └── default.latte ← defaultアクションのテンプレート +│ └── Bootstrap.php ← ブートストラップクラス Bootstrap +├── assets/ ←リソース(SCSS、TypeScript、ソース画像) +├── bin/ ← コマンドラインから実行されるスクリプト +├── config/ ← 設定ファイル +│ ├── common.neon +│ └── services.neon +├── log/ ← ログ記録されたエラー +├── temp/ ← 一時ファイル、キャッシュなど +├── vendor/ ← Composerによってインストールされたライブラリ +│ ├── ... +│ └── autoload.php ← インストールされたすべてのパッケージのオートローディング +├── www/ ← 公開ディレクトリまたはプロジェクトのドキュメントルート +│ ├── assets/ ←コンパイルされた静的ファイル(CSS、JS、画像、...) +│ ├── .htaccess ← mod_rewriteルール +│ └── index.php ← アプリケーションが起動する最初のファイル +└── .htaccess ← www以外のすべてのディレクトリへのアクセスを禁止 +\-- + +ディレクトリ構造は自由に変更でき、フォルダの名前を変更したり移動したりできます。完全に柔軟です。さらに、Netteは賢い自動検出機能を備えており、URLベースを含むアプリケーションの場所を自動的に認識します。 + +少し大きなアプリケーションでは、Presenterとテンプレートのフォルダを[サブディレクトリに分割 |directory-structure#Presenterとテンプレート]し、クラスをモジュールと呼ばれる名前空間に分割できます。 + +`www/` ディレクトリは、プロジェクトのいわゆる公開ディレクトリまたはドキュメントルートを表します。アプリケーション側で何も設定を変更することなく名前を変更できます。ただし、ドキュメントルートがこのディレクトリを指すように[ホスティングを設定 |nette:troubleshooting#URLから www ディレクトリを変更または削除する方法は]する必要があります。 + +WebProjectは、[Composer |best-practices:composer]を使用してNetteを含めて直接ダウンロードすることもできます。 + +```shell +composer create-project nette/web-project +``` + +LinuxまたはmacOSでは、`log/` および `temp/` ディレクトリに[書き込み権限を設定 |nette:troubleshooting#ディレクトリ権限の設定]してください。 + +WebProjectアプリケーションは実行準備ができており、何も設定する必要はなく、`www/` フォルダにアクセスしてブラウザですぐに表示できます。 + + +HTTPリクエスト +========= + +すべては、ユーザーがブラウザでページを開いたときに始まります。つまり、ブラウザがHTTPリクエストでサーバーにノックするときです。リクエストは、公開ディレクトリ `www/` にある単一のPHPファイル、つまり `index.php` に向けられます。アドレス `https://example.com/product/123` へのリクエストであるとしましょう。適切な[サーバー設定 |nette:troubleshooting#きれいなURLのためにサーバーを設定する方法は]のおかげで、このURLも `index.php` ファイルにマッピングされ、実行されます。 + +そのタスクは次のとおりです。 + +1) 環境を初期化する +2) ファクトリを取得する +3) リクエストを処理するNetteアプリケーションを起動する + +どのファクトリですか?私たちはトラクターではなく、Webページを作成しています!お待ちください、すぐに説明します。 + +「環境の初期化」という言葉は、例えば[Tracy|tracy:]を有効にすることを意味します。これは、エラーのログ記録や視覚化のための素晴らしいツールです。本番サーバーではエラーをログに記録し、開発サーバーでは直接表示します。したがって、初期化には、Webが本番モードで実行されているか開発モードで実行されているかを判断することも含まれます。これを行うために、Netteは[賢い自動検出 |bootstrapping#開発環境 vs 本番環境]を使用します。Webをlocalhostで実行すると、開発モードで実行されます。したがって、何も設定する必要はなく、アプリケーションは開発と本番展開の両方にすぐに準備ができています。[Bootstrapクラス|bootstrapping]に関する章で、これらの手順が実行され、詳細に説明されています。 + +3番目のポイント(はい、2番目はスキップしましたが、戻ってきます)は、アプリケーションの起動です。Netteでは、HTTPリクエストの処理は `Nette\Application\Application` クラス(以下 `Application`)が担当します。したがって、アプリケーションを起動すると言うとき、具体的にはこのクラスのオブジェクトで `run()` という適切な名前のメソッドを呼び出すことを意味します。 + +Netteは、実証済みの方法論に従ってクリーンなアプリケーションを作成するように導くメンターです。そして、それらの完全に実証済みの方法論の1つは、**dependency injection**、略してDIと呼ばれます。現時点ではDIの説明で負担をかけたくありません。それについては[別の章|dependency-injection:introduction]があります。重要な結果は、主要なオブジェクトは通常、**DIコンテナ**(略してDIC)と呼ばれるオブジェクトのファクトリによって作成されることです。はい、それが少し前に話したファクトリです。そして、それは `Application` オブジェクトも作成します。そのため、最初にコンテナが必要です。`Configurator` クラスを使用してそれを取得し、`Application` オブジェクトを作成させ、その上で `run()` メソッドを呼び出すことで、Netteアプリケーションが起動します。これはまさに[index.php |bootstrapping#index.php]ファイルで行われていることです。 + + +Nette Application +================= + +Applicationクラスには1つのタスクしかありません:HTTPリクエストに応答することです。 + +Netteで書かれたアプリケーションは、多数のいわゆるPresenter(他のフレームワークではコントローラーという用語に出会うかもしれませんが、同じものです)に分割されます。これらは、Webサイトの特定のページ(ホームページ、eコマースの製品、ログインフォーム、サイトマップフィードなど)を表すクラスです。アプリケーションは1つから数千のPresenterを持つことができます。 + +Applicationは、まずいわゆるルーターに、現在のリクエストを処理するためにどのPresenterに渡すかを決定するように依頼します。ルーターは、誰の責任かを決定します。入力URL `https://example.com/product/123` を見て、設定方法に基づいて、これが例えば `id: 123` の製品を表示する(`show`)**アクション**を要求する**Presenter** `Product` の仕事であると判断します。Presenter + アクションのペアは、`Product:show` のようにコロンで区切って記述するのが良い習慣です。 + +したがって、ルーターはURLを `Presenter:action` + パラメータのペア、この場合は `Product:show` + `id: 123` に変換しました。そのようなルーターがどのように見えるかは、`app/Core/RouterFactory.php` ファイルで確認でき、[ルーティング |Routing]の章で詳細に説明されています。 + +続けましょう。ApplicationはPresenterの名前を知っているので、次に進むことができます。`ProductPresenter` クラスのオブジェクトを作成します。これはPresenter `Product` のコードです。より正確には、Presenterを作成するようにDIコンテナに依頼します。なぜなら、作成するのはDIコンテナの仕事だからです。 + +Presenterは次のようになります。 + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + public function __construct( + private ProductRepository $repository, + ) { + } + + public function renderShow(int $id): void + { + // モデルからデータを取得し、テンプレートに渡します + $this->template->product = $this->repository->getProduct($id); + } +} +``` + +リクエストの処理はPresenterが引き継ぎます。そして、タスクは明確です:`id: 123` でアクション `show` を実行します。Presenterの言葉で言えば、これは `renderShow()` メソッドが呼び出され、パラメータ `$id` で `123` を受け取ることを意味します。 + +Presenterは複数のアクションを処理できます。つまり、複数の `render()` メソッドを持つことができます。ただし、1つまたはできるだけ少ないアクションを持つPresenterを設計することをお勧めします。 + +したがって、`renderShow(123)` メソッドが呼び出されました。そのコードは架空の例ですが、`$this->template` への書き込みによってデータがテンプレートにどのように渡されるかを見ることができます。 + +その後、Presenterは応答を返します。これは、HTMLページ、画像、XMLドキュメント、ディスクからのファイルの送信、JSON、または別のページへのリダイレクトなどです。重要なのは、明示的に応答方法を指示しない場合(これは `ProductPresenter` の場合です)、応答はHTMLページを含むテンプレートのレンダリングになることです。なぜですか?なぜなら、99%の場合、テンプレートをレンダリングしたいので、Presenterはこの動作をデフォルトと見なし、作業を楽にしたいからです。それがNetteの目的です。 + +どのテンプレートをレンダリングするかを指定する必要さえありません。パスは自動的に推測されます。`show` アクションの場合、単に `ProductPresenter` クラスと同じディレクトリにある `show.latte` テンプレートをロードしようとします。また、`@layout.latte` ファイルでレイアウトを見つけようとします([テンプレートの検索 |templates#テンプレートの検索]について詳しくはこちら)。 + +そして、テンプレートがレンダリングされます。これでPresenterとアプリケーション全体のタスクが完了し、作業は完了です。テンプレートが存在しない場合、404エラーページが返されます。Presenterについて詳しくは、[Presenter|presenters]ページをご覧ください。 + +[* request-flow.svg *] + +念のため、少し異なるURLでプロセス全体を要約してみましょう。 + +1) URLは `https://example.com` になります +2) アプリケーションを起動し、コンテナを作成し、`Application::run()` を実行します +3) ルーターはURLを `Home:default` ペアとしてデコードします +4) `HomePresenter` クラスのオブジェクトが作成されます +5) `renderDefault()` メソッドが呼び出されます(存在する場合) +6) 例えば `@layout.latte` のレイアウトを持つ `default.latte` などのテンプレートがレンダリングされます + + +おそらく、今、多くの新しい概念に出会ったかもしれませんが、それらが意味をなすことを願っています。Netteでのアプリケーション開発は非常に快適です。 + + +テンプレート +====== + +テンプレートについて言えば、Netteでは[Latte |latte:]テンプレートシステムが使用されます。そのため、テンプレートの拡張子は `.latte` です。Latteが使用される理由は、PHPで最も安全なテンプレートシステムであると同時に、最も直感的なシステムでもあるためです。多くの新しいことを学ぶ必要はありません。PHPの知識といくつかのタグで十分です。すべては[ドキュメント |templates]で学ぶことができます。 + +テンプレートでは、他のPresenterとアクションへの[リンクが作成 |creating-links]されます。 + +```latte +製品詳細 +``` + +単に実際のURLの代わりに、既知の `Presenter:action` ペアを記述し、必要に応じてパラメータを指定します。トリックは `n:href` にあり、これはこの属性がNetteによって処理されることを示します。そして、次のように生成します。 + +```latte +製品詳細 +``` + +URLの生成は、前述のルーターが担当します。Netteのルーターが例外的なのは、URLからPresenter:アクションのペアへの変換だけでなく、逆方向、つまりPresenter名+アクション+パラメータからURLを生成することもできることです。 これにより、NetteではテンプレートやPresenterの文字を1つも変更することなく、完成したアプリケーション全体のURL形式を完全に変更できます。ルーターを編集するだけで。 また、これにより、いわゆるカノニカル化が可能になります。これはNetteのもう1つのユニークな機能であり、異なるURLで重複コンテンツが存在するのを自動的に防ぐことで、より良いSEO(インターネットでの検索エンジンの最適化)に貢献します。 多くのプログラマーはこれを驚くべきことだと考えています。 + + +インタラクティブコンポーネント +=============== + +Presenterについてもう1つお伝えしなければならないことがあります:それらには組み込みのコンポーネントシステムがあります。DelphiやASP.NET Web Formsを知っている古い世代の方々には馴染みがあるかもしれません。ReactやVue.jsも、遠いながらも似たようなものに基づいています。PHPフレームワークの世界では、これは完全にユニークな機能です。 + +コンポーネントは、ページ(つまりPresenter)に挿入する独立した再利用可能なユニットです。[フォーム |forms:in-presenter]、[データグリッド |https://componette.org/contributte/datagrid/]、メニュー、投票など、繰り返し使用する意味のあるものであれば何でもかまいません。独自のコンポーネントを作成したり、[膨大な品揃え |https://componette.org]のオープンソースコンポーネントを使用したりできます。 + +コンポーネントは、アプリケーション開発へのアプローチに根本的な影響を与えます。事前に準備されたユニットからページを組み立てる新しい可能性を開きます。そして、さらに[ハリウッド |components#ハリウッドスタイル]と共通点があります。 + + +DIコンテナと設定 +========= + +DIコンテナ、またはオブジェクトファクトリは、アプリケーション全体の心臓部です。 + +心配しないでください。前の行からそう思われるかもしれませんが、これは魔法のブラックボックスではありません。実際には、Netteによって生成され、キャッシュディレクトリに保存される、かなり退屈なPHPクラスです。`createServiceAbcd()` のような名前の多くのメソッドがあり、それぞれがオブジェクトを作成して返すことができます。はい、`createServiceApplication()` メソッドもあり、これは `index.php` ファイルでアプリケーションを起動するために必要だった `Nette\Application\Application` を作成します。そして、個々のPresenterを作成するメソッドもあります。などなど。 + +DIコンテナが作成するオブジェクトは、何らかの理由でサービスと呼ばれます。 + +このクラスの本当に特別な点は、あなたがそれをプログラミングするのではなく、フレームワークがプログラミングすることです。それは実際にPHPコードを生成し、ディスクに保存します。あなたは、コンテナがどのオブジェクトを作成できるようにすべきか、そして具体的にどのように作成すべきかの指示を与えるだけです。そして、これらの指示は[設定ファイル |bootstrapping#DIコンテナの設定]に書かれており、[NEON|neon:format]形式が使用されるため、拡張子も `.neon` です。 + +設定ファイルは、純粋にDIコンテナに指示するために使用されます。したがって、例えば[session |http:configuration#セッション]セクションで `expiration: 14 days` オプションを指定すると、DIコンテナはセッションを表す `Nette\Http\Session` オブジェクトを作成するときに、その `setExpiration('14 days')` メソッドを呼び出し、それによって設定が現実になります。 + +[設定できるすべてのこと |nette:configuring]と、[独自のサービスを定義する方法 |dependency-injection:services]を説明する章全体が用意されています。 + +サービスの作成に少し慣れると、[autowiring |dependency-injection:autowiring]という言葉に出くわします。これは、信じられないほど人生を簡素化する機能です。何もする必要なく、必要な場所(例えばクラスのコンストラクタ)にオブジェクトを自動的に渡すことができます。NetteのDIコンテナが小さな奇跡であることに気づくでしょう。 + + +次へ進むには? +======= + +Netteのアプリケーションの基本原則を見てきました。まだ非常に表面的ですが、すぐに深く掘り下げ、やがて素晴らしいWebアプリケーションを作成できるようになるでしょう。次にどこへ進むべきですか?[最初のアプリケーションを作成する|quickstart:]チュートリアルを試しましたか? + +上記に加えて、Netteには[便利なクラス|utils:]の武器庫全体、[データベースレイヤー|database:]などがあります。ドキュメントをざっと見てみてください。または[ブログ|https://blog.nette.org]をご覧ください。多くの興味深いことを見つけるでしょう。 + +フレームワークがあなたに多くの喜びをもたらしますように 💙 diff --git a/application/ja/multiplier.texy b/application/ja/multiplier.texy new file mode 100644 index 0000000000..64f8a4ef3c --- /dev/null +++ b/application/ja/multiplier.texy @@ -0,0 +1,63 @@ +Multiplier: 動的コンポーネント +********************* + +.[perex] +インタラクティブコンポーネントを動的に作成するためのツール + +典型的な例から始めましょう:eコマースサイトに商品のリストがあり、それぞれについてカートに商品を追加するためのフォームを表示したいとします。可能なバリアントの1つは、リスト全体を1つのフォームでラップすることです。しかし、[api:Nette\Application\UI\Multiplier]ははるかに便利な方法を提供します。 + +Multiplierを使用すると、複数のコンポーネントのファクトリを便利に定義できます。これはネストされたコンポーネントの原則に基づいて機能します - [api:Nette\ComponentModel\Container]から継承する各コンポーネントは、他のコンポーネントを含むことができます。 + +.[tip] +ドキュメントの[コンポーネントモデル |components#コンポーネントの詳細]に関する章、または[Honza Tvrdíkによる講演|https://www.youtube.com/watch?v=8y3LLexWu-I]を参照してください。 + +Multiplierの本質は、コンストラクタで渡されたコールバックを使用して子を動的に作成できる親の役割を果たすことです。例を参照してください。 + +```php +protected function createComponentShopForm(): Multiplier +{ + return new Multiplier(function () { + $form = new Nette\Application\UI\Form; + $form->addInteger('count', '商品数:') + ->setRequired(); + $form->addSubmit('send', 'カートに追加'); + return $form; + }); +} +``` + +これで、テンプレートで各商品についてフォームを簡単にレンダリングできます - そして、それぞれが本当にユニークなコンポーネントになります。 + +```latte +{foreach $items as $item} +

    {$item->title}

    + {$item->description} + + {control "shopForm-$item->id"} +{/foreach} +``` + +`{control}` タグで渡される引数は、次のことを示す形式です。 + +1. `shopForm` コンポーネントを取得する +2. そして、そこから子 `$item->id` を取得する + +ポイント **1.** の最初の呼び出しでは、`shopForm` はまだ存在しないため、そのファクトリ `createComponentShopForm` が呼び出されます。取得されたコンポーネント(Multiplierのインスタンス)で、特定のフォームのファクトリが呼び出されます - これは、コンストラクタでMultiplierに渡した匿名関数です。 + +foreachの次の反復では、`createComponentShopForm` メソッドは呼び出されません(コンポーネントは存在します)が、異なる子を探しているため(`$item->id` は各反復で異なります)、匿名関数が再度呼び出され、新しいフォームが返されます。 + +残っているのは、フォームが実際に意図した商品をカートに追加することを確認することだけです - 現在、各商品のフォームはまったく同じです。Multiplierのプロパティ(および一般的にNette Frameworkのコンポーネントファクトリ)が役立ちます。つまり、各ファクトリは最初の引数として作成されるコンポーネントの名前を受け取ります。この場合、それは `$item->id` であり、これはまさに必要なデータです。したがって、フォームの作成を少し変更するだけで十分です。 + +```php +protected function createComponentShopForm(): Multiplier +{ + return new Multiplier(function ($itemId) { + $form = new Nette\Application\UI\Form; + $form->addInteger('count', '商品数:') + ->setRequired(); + $form->addHidden('itemId', $itemId); + $form->addSubmit('send', 'カートに追加'); + return $form; + }); +} +``` diff --git a/application/ja/presenters.texy b/application/ja/presenters.texy new file mode 100644 index 0000000000..6e7e8e2932 --- /dev/null +++ b/application/ja/presenters.texy @@ -0,0 +1,500 @@ +Presenter +********* + +
    + +NetteでPresenterとテンプレートを作成する方法について学びます。読み終えた後、あなたは知っているでしょう: + +- Presenterがどのように機能するか +- パーシステントパラメータとは何か +- テンプレートがどのようにレンダリングされるか + +
    + +[すでに知っているように |how-it-works#Nette Application]、PresenterはWebアプリケーションの特定のページ(ホームページ、eコマースの製品、ログインフォーム、サイトマップフィードなど)を表すクラスです。アプリケーションは1つから数千のPresenterを持つことができます。他のフレームワークでは、コントローラーとも呼ばれます。 + +通常、Presenterという用語は、Webインターフェースの生成に適した[api:Nette\Application\UI\Presenter]クラスの子孫を意味し、この章の残りの部分で扱います。一般的な意味では、Presenterは[api:Nette\Application\IPresenter]インターフェースを実装する任意のオブジェクトです。 + + +Presenterのライフサイクル +================= + +Presenterのタスクは、リクエストを処理し、応答(HTMLページ、画像、リダイレクトなど)を返すことです。 + +したがって、最初にリクエストが渡されます。これは直接のHTTPリクエストではなく、ルーターの助けを借りてHTTPリクエストが変換された[api:Nette\Application\Request]オブジェクトです。Presenterはリクエストの処理をこれから示す他のメソッドに賢く委任するため、通常はこのオブジェクトに直接触れることはありません。 + +[* lifecycle.svg *] *** *Presenterのライフサイクル* .<> + +この図は、存在する場合に上から下に順に呼び出されるメソッドのリストを表しています。これらのメソッドのいずれも存在する必要はなく、単一のメソッドを持たない完全に空のPresenterを持ち、それに基づいて単純な静的Webサイトを構築できます。 + + +`__construct()` +--------------- + +コンストラクタは、オブジェクト作成時に呼び出されるため、Presenterのライフサイクルには完全には属しません。しかし、その重要性のために記載しています。コンストラクタ([injectメソッド|best-practices:inject-method-attribute]とともに)は、依存関係を渡すために使用されます。 + +Presenterは、アプリケーションのビジネスロジックを処理したり、データベースへの書き込みや読み取りを行ったり、計算を実行したりすべきではありません。これらは、モデルと呼ばれるレイヤーのクラスの仕事です。例えば、`ArticleRepository` クラスは記事の読み込みと保存を担当するかもしれません。Presenterがそれを使用できるようにするには、[依存性注入 |dependency-injection:passing-dependencies]を使用して渡してもらいます。 + + +```php +class ArticlePresenter extends Nette\Application\UI\Presenter +{ + public function __construct( + private ArticleRepository $articles, + ) { + } +} +``` + + +`startup()` +----------- + +リクエストを受信するとすぐに `startup()` メソッドが呼び出されます。プロパティの初期化、ユーザー権限の検証などに使用できます。メソッドは常に親 `parent::startup()` を呼び出す必要があります。 + + +`action(args...)` .{toc: action()} +-------------------------------------------------- + +`render()` メソッドに似ています。`render()` は後でレンダリングされる特定のテンプレートのデータを準備することを目的としていますが、`action()` ではテンプレートのレンダリングとは無関係にリクエストが処理されます。例えば、データが処理され、ユーザーがログインまたはログアウトされ、などが行われ、その後[別の場所にリダイレクト |#リダイレクト]されます。 + +重要なのは、`action()` が `render()` より前に呼び出されるため、そこで将来のイベントの流れを変更できることです。つまり、レンダリングされるテンプレートと呼び出される `render()` メソッドを `setView('jineView')` を使用して変更できます。 + +メソッドにはリクエストからのパラメータが渡されます。パラメータに型を指定することが可能であり、推奨されます。例えば `actionShow(int $id, ?string $slug = null)` - パラメータ `id` が欠落している場合、または整数でない場合、Presenterは[404エラー |#404エラーなど]を返し、動作を終了します。 + + +`handle(args...)` .{toc: handle()} +-------------------------------------------------- + +このメソッドは、[コンポーネント |components#シグナル]に関する章で学ぶ、いわゆるシグナルを処理します。これは主にコンポーネントとAJAXリクエストの処理を目的としています。 + +メソッドには、`action()` の場合と同様に、型チェックを含むリクエストからのパラメータが渡されます。 + + +`beforeRender()` +---------------- + +`beforeRender` メソッドは、その名前が示すように、各 `render()` メソッドの前に呼び出されます。共通のテンプレート設定、レイアウトへの変数の受け渡しなどに使用されます。 + + +`render(args...)` .{toc: render()} +---------------------------------------------- + +後続のレンダリングのためにテンプレートを準備し、データを渡すなどの場所です。 + +メソッドには、`action()` の場合と同様に、型チェックを含むリクエストからのパラメータが渡されます。 + +```php +public function renderShow(int $id): void +{ + // モデルからデータを取得し、テンプレートに渡します + $this->template->article = $this->articles->getById($id); +} +``` + + +`afterRender()` +--------------- + +`afterRender` メソッドは、名前が再び示すように、各 `render()` メソッドの後に呼び出されます。これはむしろ例外的に使用されます。 + + +`shutdown()` +------------ + +Presenterのライフサイクルの最後に呼び出されます。 + + +**先に進む前に、良いアドバイス**。ご覧のとおり、Presenterは複数のアクション/ビューを処理できます。つまり、複数の `render()` メソッドを持つことができます。ただし、1つまたはできるだけ少ないアクションを持つPresenterを設計することをお勧めします。 + + +応答の送信 +===== + +Presenterの応答は通常、[HTMLページを含むテンプレートのレンダリング|templates]ですが、ファイルの送信、JSON、または別のページへのリダイレクトなども可能です。 + +ライフサイクルのいつでも、次のいずれかのメソッドを使用して応答を送信し、同時にPresenterを終了できます。 + +- `redirect()`, `redirectPermanent()`, `redirectUrl()`, `forward()` は[#リダイレクト]します +- `error()` は[エラーのため |#404エラーなど]にPresenterを終了します +- `sendJson($data)` はPresenterを終了し、JSON形式で[データを送信 |#JSONの送信]します +- `sendTemplate()` はPresenterを終了し、すぐに[テンプレートをレンダリング |templates]します +- `sendResponse($response)` はPresenterを終了し、[カスタム応答 |#応答]を送信します +- `terminate()` は応答なしでPresenterを終了します + +これらのメソッドのいずれも呼び出さない場合、Presenterは自動的にテンプレートのレンダリングに進みます。なぜですか?なぜなら、99%の場合、テンプレートをレンダリングしたいので、Presenterはこの動作をデフォルトと見なし、作業を楽にしたいからです。 + + +リンクの作成 +====== + +Presenterには `link()` メソッドがあり、これを使用して他のPresenterへのURLリンクを作成できます。最初のパラメータはターゲットのPresenterとアクションであり、その後に渡される引数が続きます。引数は配列として指定できます。 + +```php +$url = $this->link('Product:show', $id); + +$url = $this->link('Product:show', [$id, 'lang' => 'cs']); +``` + +テンプレートでは、他のPresenterとアクションへのリンクは次のように作成されます。 + +```latte +製品詳細 +``` + +単に実際のURLの代わりに、既知の `Presenter:action` ペアを記述し、必要に応じてパラメータを指定します。トリックは `n:href` にあり、これはこの属性がLatteによって処理され、実際のURLが生成されることを示します。Netteでは、URLについて考える必要はまったくなく、Presenterとアクションについて考えるだけです。 + +詳細については、[URLリンクの作成|creating-links]の章を参照してください。 + + +リダイレクト +====== + +別のPresenterに移動するには、`redirect()` および `forward()` メソッドを使用します。これらは[link() |#リンクの作成]メソッドと非常によく似た構文を持っています。 + +`forward()` メソッドは、HTTPリダイレクトなしで即座に新しいPresenterに移動します。 + +```php +$this->forward('Product:show'); +``` + +HTTPコード302(または現在のリクエストメソッドがPOSTの場合は303)を持ついわゆる一時的なリダイレクトの例: + +```php +$this->redirect('Product:show', $id); +``` + +HTTPコード301を持つ永続的なリダイレクトは、次のように実現できます。 + +```php +$this->redirectPermanent('Product:show', $id); +``` + +アプリケーション外の別のURLにリダイレクトするには、`redirectUrl()` メソッドを使用します。2番目のパラメータとしてHTTPコードを指定できます。デフォルトは302(または現在のリクエストメソッドがPOSTの場合は303)です。 + +```php +$this->redirectUrl('https://nette.org'); +``` + +リダイレクトは、いわゆるサイレント終了例外 `Nette\Application\AbortException` をスローすることで、Presenterの動作を即座に終了します。 + +リダイレクトの前に、[#フラッシュメッセージ]、つまりリダイレクト後にテンプレートに表示されるメッセージを送信できます。 + + +フラッシュメッセージ +========== + +これらは通常、何らかの操作の結果を通知するメッセージです。フラッシュメッセージの重要な特徴は、リダイレクト後もテンプレートで利用できることです。表示後もさらに30秒間有効です。例えば、転送エラーのためにユーザーがページを更新した場合でも、メッセージはすぐには消えません。 + +[flashMessage() |api:Nette\Application\UI\Control::flashMessage()] メソッドを呼び出すだけで、Presenterがテンプレートへの受け渡しを処理します。最初のパラメータはメッセージのテキストであり、オプションの2番目のパラメータはそのタイプ(error、warning、infoなど)です。`flashMessage()` メソッドは、フラッシュメッセージのインスタンスを返し、これに追加情報を追加できます。 + +```php +$this->flashMessage('項目が削除されました。'); +$this->redirect(/* ... */); // そしてリダイレクトします +``` + +これらのメッセージは、テンプレートでは `$flashes` 変数で `stdClass` オブジェクトとして利用できます。これらには `message`(メッセージテキスト)、`type`(メッセージタイプ)プロパティが含まれ、前述のユーザー情報を含むこともできます。例えば、次のようにレンダリングします。 + +```latte +{foreach $flashes as $flash} +
    {$flash->message}
    +{/foreach} +``` + + +404エラーなど +======== + +リクエストを満たせない場合、例えば表示したい記事がデータベースに存在しないなどの理由で、`error(?string $message = null, int $httpCode = 404)` メソッドを使用して404エラーをスローします。 + +```php +public function renderShow(int $id): void +{ + $article = $this->articles->getById($id); + if (!$article) { + $this->error(); + } + // ... +} +``` + +エラーのHTTPコードは2番目のパラメータとして渡すことができます。デフォルトは404です。メソッドは `Nette\Application\BadRequestException` 例外をスローするように機能し、その後 `Application` は制御をエラーPresenterに渡します。これは、発生したエラーを通知するページを表示するタスクを持つPresenterです。 エラーPresenterの設定は、[application設定|configuration]で行われます。 + + +JSONの送信 +======= + +JSON形式でデータを送信し、Presenterを終了するアクションメソッドの例: + +```php +public function actionData(): void +{ + $data = ['hello' => 'nette']; + $this->sendJson($data); +} +``` + + +リクエストパラメータ .{data-version:3.1.14} +================================= + +Presenterおよび各コンポーネントは、HTTPリクエストからパラメータを取得します。その値は `getParameter($name)` または `getParameters()` メソッドで取得できます。値は文字列または文字列の配列であり、基本的にはURLから直接取得された生のデータです。 + +より便利にするために、プロパティを介してパラメータにアクセスすることをお勧めします。`#[Parameter]` 属性でマークするだけです。 + +```php +use Nette\Application\Attributes\Parameter; // この行は重要です + +class HomePresenter extends Nette\Application\UI\Presenter +{ + #[Parameter] + public string $theme; // publicである必要があります +} +``` + +プロパティにはデータ型(例:`string`)を指定することをお勧めします。Netteはそれに基づいて値を自動的にキャストします。パラメータ値は[検証 |#パラメータの検証]することもできます。 + +リンクを作成するときに、パラメータの値を直接設定できます。 + +```latte +クリック +``` + + +パーシステントパラメータ +============ + +パーシステントパラメータは、異なるリクエスト間で状態を維持するために使用されます。その値は、リンクをクリックした後も同じままです。セッションデータとは異なり、URLで転送されます。そして、これは完全に自動的に行われるため、`link()` や `n:href` で明示的に指定する必要はありません。 + +使用例は?多言語アプリケーションがあるとします。現在の言語は、常にURLの一部である必要があるパラメータです。しかし、すべてのリンクでそれを指定するのは非常に面倒です。そこで、それをパーシステントパラメータ `lang` にし、自動的に転送されるようにします。素晴らしい! + +Netteでパーシステントパラメータを作成するのは非常に簡単です。パブリックプロパティを作成し、属性でマークするだけです。(以前は `/** @persistent */` が使用されていました) + +```php +use Nette\Application\Attributes\Persistent; // この行は重要です + +class ProductPresenter extends Nette\Application\UI\Presenter +{ + #[Persistent] + public string $lang; // publicである必要があります +} +``` + +`$this->lang` が例えば `'en'` の値を持つ場合、`link()` または `n:href` を使用して作成されたリンクも `lang=en` パラメータを含みます。そして、リンクをクリックした後も、再び `$this->lang = 'en'` になります。 + +プロパティにはデータ型(例:`string`)を指定することをお勧めします。また、デフォルト値を指定することもできます。パラメータ値は[検証 |#パラメータの検証]できます。 + +パーシステントパラメータは、通常、特定のPresenterのすべてのアクション間で転送されます。複数のPresenter間で転送するには、次のいずれかで定義する必要があります。 + +- Presenterが継承する共通の祖先で +- Presenterが使用するトレイトで: + +```php +trait LanguageAware +{ + #[Persistent] + public string $lang; +} + +class ProductPresenter extends Nette\Application\UI\Presenter +{ + use LanguageAware; +} +``` + +リンクを作成するときに、パーシステントパラメータの値を変更できます。 + +```latte +チェコ語の詳細 +``` + +または、*リセット*することもできます。つまり、URLから削除します。その後、デフォルト値を取ります。 + +```latte +クリック +``` + + +インタラクティブコンポーネント +=============== + +Presenterには組み込みのコンポーネントシステムがあります。コンポーネントは、Presenterに挿入する独立した再利用可能なユニットです。[フォーム |forms:in-presenter]、データグリッド、メニューなど、繰り返し使用する意味のあるものであれば何でもかまいません。 + +コンポーネントがどのようにPresenterに挿入され、その後使用されるのでしょうか?それは[コンポーネント |components]の章で学びます。ハリウッドと共通点があることさえ発見するでしょう。 + +そして、どこでコンポーネントを入手できますか?[Componette |https://componette.org/search/component]ページでは、オープンソースコンポーネントや、フレームワーク周辺のコミュニティのボランティアによってここに配置されたNette用の他の多くのアドオンを見つけることができます。 + + +深く掘り下げる +======= + +.[tip] +この章でこれまで見てきたことで、おそらく完全に十分でしょう。以下の行は、Presenterについて深く掘り下げ、すべてを知りたい人のためのものです。 + + +パラメータの検証 +-------- + +URLから受け取った[#リクエストパラメータ]と[#パーシステントパラメータ]の値は、`loadState()` メソッドによってプロパティに書き込まれます。また、プロパティで指定されたデータ型と一致するかどうかもチェックし、一致しない場合は404エラーで応答し、ページは表示されません。 + +パラメータは、ユーザーがURLで簡単に上書きできるため、決して盲目的に信用しないでください。例えば、このようにして言語 `$this->lang` がサポートされている言語の中にあるかどうかを検証します。適切な方法は、前述の `loadState()` メソッドをオーバーライドすることです。 + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + #[Persistent] + public string $lang; + + public function loadState(array $params): void + { + parent::loadState($params); // ここで $this->lang が設定されます + // 値の独自のチェックが続きます: + if (!in_array($this->lang, ['en', 'cs'])) { + $this->error(); + } + } +} +``` + + +リクエストの保存と復元 +----------- + +Presenterが処理するリクエストは[api:Nette\Application\Request]オブジェクトであり、Presenterの `getRequest()` メソッドによって返されます。 + +現在のリクエストはセッションに保存したり、逆にそこから復元してPresenterに再度実行させたりすることができます。これは、例えばユーザーがフォームに入力していてログインが期限切れになった場合に便利です。データを失わないように、ログインページにリダイレクトする前に現在のリクエストを `$reqId = $this->storeRequest()` を使用してセッションに保存します。これは短い文字列の形式でその識別子を返し、それをログインPresenterにパラメータとして渡します。 + +ログイン後、`$this->restoreRequest($reqId)` メソッドを呼び出します。これはセッションからリクエストを取得し、それにフォワードします。メソッドは、リクエストが現在ログインしているユーザーと同じユーザーによって作成されたことを検証します。別のユーザーがログインした場合、またはキーが無効な場合、何もしませんでプログラムは続行します。 + +[以前のページに戻る方法 |best-practices:restore-request]のガイドをご覧ください。 + + +カノニカル化 +------ + +Presenterには、より良いSEO(インターネットでの検索エンジンの最適化)に貢献する本当に素晴らしい機能が1つあります。異なるURLで重複コンテンツが存在するのを自動的に防ぎます。特定のターゲットに複数のURLアドレス(例:`/index` と `/index?page=1`)がある場合、フレームワークはそのうちの1つをプライマリ(カノニカル)として決定し、HTTPコード301を使用して他のアドレスをそれにリダイレクトします。これにより、検索エンジンはページを2回インデックス付けせず、ページランクを希釈しません。 + +このプロセスはカノニカル化と呼ばれます。カノニカルURLは、[ルーター|routing]によって生成されるURLであり、通常はコレクション内の最初の一致するルートです。 + +カノニカル化はデフォルトで有効になっており、`$this->autoCanonicalize = false` を介して無効にできます。 + +AJAXまたはPOSTリクエストの場合、データが失われたり、SEOの観点から付加価値がなかったりするため、リダイレクトは発生しません。 + +カノニカル化は、`canonicalize()` メソッドを使用して手動で呼び出すこともできます。このメソッドには、`link()` メソッドと同様に、Presenter、アクション、およびパラメータが渡されます。リンクを作成し、現在のURLアドレスと比較します。異なる場合は、生成されたリンクにリダイレクトします。 + +```php +public function actionShow(int $id, ?string $slug = null): void +{ + $realSlug = $this->facade->getSlugForId($id); + // $slug が $realSlug と異なる場合にリダイレクトします + $this->canonicalize('Product:show', [$id, $realSlug]); +} +``` + + +イベント +---- + +Presenterのライフサイクルの一部として呼び出される `startup()`、`beforeRender()`、`shutdown()` メソッドに加えて、自動的に呼び出されるように他の関数を定義することもできます。Presenterはいわゆる[イベント |nette:glossary#イベント]を定義し、そのハンドラを `$onStartup`、`$onRender`、`$onShutdown` 配列に追加します。 + +```php +class ArticlePresenter extends Nette\Application\UI\Presenter +{ + public function __construct() + { + $this->onStartup[] = function () { + // ... + }; + } +} +``` + +`$onStartup` 配列のハンドラは `startup()` メソッドの直前に呼び出され、次に `$onRender` は `beforeRender()` と `render()` の間に呼び出され、最後に `$onShutdown` は `shutdown()` の直前に呼び出されます。 + + +応答 +-------- + +Presenterが返す応答は、[api:Nette\Application\Response]インターフェースを実装するオブジェクトです。多くの準備された応答が利用可能です。 + +- [api:Nette\Application\Responses\CallbackResponse] - コールバックを送信します +- [api:Nette\Application\Responses\FileResponse] - ファイルを送信します +- [api:Nette\Application\Responses\ForwardResponse] - forward() +- [api:Nette\Application\Responses\JsonResponse] - JSONを送信します +- [api:Nette\Application\Responses\RedirectResponse] - リダイレクト +- [api:Nette\Application\Responses\TextResponse] - テキストを送信します +- [api:Nette\Application\Responses\VoidResponse] - 空の応答 + +応答は `sendResponse()` メソッドを使用して送信されます。 + +```php +use Nette\Application\Responses; + +// プレーンテキスト +$this->sendResponse(new Responses\TextResponse('Hello Nette!')); + +// ファイルを送信します +$this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf')); + +// 応答はコールバックになります +$callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse) { + if ($httpResponse->getHeader('Content-Type') === 'text/html') { + echo '

    Hello

    '; + } +}; +$this->sendResponse(new Responses\CallbackResponse($callback)); +``` + + +`#[Requires]` を使用したアクセス制限 .{data-version:3.2.2} +----------------------------------------------- + +`#[Requires]` 属性は、Presenterとそのメソッドへのアクセスを制限するための高度なオプションを提供します。HTTPメソッドの指定、AJAXリクエストの要求、同一オリジン(same origin)への制限、およびフォワーディング経由のアクセスのみに使用できます。属性は、Presenterクラスと個々の `action()`、`render()`、`handle()`、`createComponent()` メソッドの両方に適用できます。 + +これらの制限を指定できます。 +- HTTPメソッドについて:`#[Requires(methods: ['GET', 'POST'])]` +- AJAXリクエストの要求:`#[Requires(ajax: true)]` +- 同一オリジンからのアクセスのみ:`#[Requires(sameOrigin: true)]` +- フォワード経由のアクセスのみ:`#[Requires(forward: true)]` +- 特定のアクションへの制限:`#[Requires(actions: 'default')]` + +詳細については、[Requires属性の使用方法 |best-practices:attribute-requires]のガイドをご覧ください。 + + +HTTPメソッドのチェック +------------- + +NetteのPresenterは、各受信リクエストのHTTPメソッドを自動的に検証します。このチェックの理由は主にセキュリティです。標準では、`GET`、`POST`、`HEAD`、`PUT`、`DELETE`、`PATCH` メソッドが許可されています。 + +例えば `OPTIONS` メソッドを追加で許可したい場合は、`#[Requires]` 属性を使用します(Nette Application v3.2以降)。 + +```php +#[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])] +class MyPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +バージョン3.1では、検証は `checkHttpMethod()` で行われます。これは、リクエストで指定されたメソッドが `$presenter->allowedMethods` 配列に含まれているかどうかを判断します。メソッドを追加するには、次のようにします。 + +```php +class MyPresenter extends Nette\Application\UI\Presenter +{ + protected function checkHttpMethod(): void + { + $this->allowedMethods[] = 'OPTIONS'; + parent::checkHttpMethod(); + } +} +``` + +`OPTIONS` メソッドを許可する場合、その後Presenter内で適切に処理する必要があることを強調することが重要です。このメソッドは、いわゆるプリフライトリクエストとしてよく使用されます。これは、CORS(Cross-Origin Resource Sharing)ポリシーの観点からリクエストが許可されているかどうかを判断する必要がある場合に、ブラウザが実際のリクエストの前に自動的に送信します。メソッドを許可しても正しい応答を実装しない場合、不整合や潜在的なセキュリティ問題につながる可能性があります。 + + +その他の読み物 +======= + +- [injectメソッドと属性 |best-practices:inject-method-attribute] +- [トレイトからのPresenterの構成 |best-practices:presenter-traits] +- [Presenterへの設定の受け渡し |best-practices:passing-settings-to-presenters] +- [以前のページに戻る方法 |best-practices:restore-request] diff --git a/application/ja/routing.texy b/application/ja/routing.texy new file mode 100644 index 0000000000..4dd36be16c --- /dev/null +++ b/application/ja/routing.texy @@ -0,0 +1,721 @@ +ルーティング +****** + +
    + +ルータはURLアドレスに関するすべてを担当するため、もはやそれについて考える必要はありません。以下に示します: + +- URLが期待通りになるようにルータを設定する方法 +- SEOとリダイレクトについて説明します +- そして、独自のルータを作成する方法を示します + +
    + + +より人間的なURL(クールまたはプリティURLとも呼ばれます)は、より使いやすく、覚えやすく、SEOに積極的に貢献します。Netteはこれを考慮し、開発者の要望に完全に応えます。アプリケーション用に、まさにあなたが望むURLアドレス構造を設計できます。 コードやテンプレートへの介入なしに行えるため、アプリケーションがすでに完成している時点でも設計できます。それは、ルータ内の[1つの場所 |#アプリケーションへの統合]でエレガントな方法で定義され、すべてのPresenterのアノテーションに散らばることはありません。 + +Netteのルータは、**双方向**であるという点で特別です。HTTPリクエスト内のURLをデコードするだけでなく、リンクを作成することもできます。したがって、[Nette Application |how-it-works#Nette Application]において重要な役割を果たします。なぜなら、現在のリクエストを実行するPresenterとアクションを決定するだけでなく、テンプレートなどで[URLを生成 |creating-links]するためにも使用されるからです。 + +ただし、ルータはこの用途に限定されません。Presenterをまったく使用しないアプリケーション、REST APIなどで使用できます。詳細は[#スタンドアロンでの使用]セクションを参照してください。 + + +ルートコレクション +========= + +アプリケーションのURLアドレスの形式を定義する最も快適な方法は、[api:Nette\Application\Routers\RouteList]クラスを使用することです。定義は、いわゆるルートのリスト、つまりURLアドレスのマスクと、それに関連付けられたPresenterおよびアクションで構成され、シンプルなAPIを使用して行われます。ルートに名前を付ける必要はありません。 + +```php +$router = new Nette\Application\Routers\RouteList; +$router->addRoute('rss.xml', 'Feed:rss'); +$router->addRoute('article/', 'Article:view'); +// ... +``` + +この例は、ブラウザで`https://domain.com/rss.xml`を開くと、Presenter `Feed`とアクション `rss`が表示され、`https://domain.com/article/12`を開くと、Presenter `Article`とアクション `view`が表示されることを示しています。適切なルートが見つからない場合、Nette Applicationは[BadRequestException |api:Nette\Application\BadRequestException]例外をスローし、これはユーザーにエラーページ404 Not Foundとして表示されます。 + + +ルートの順序 +------ + +個々のルートがリストされる**順序は非常に重要**です。なぜなら、それらは上から下に順番に評価されるからです。ルールは、ルートを**特定のルートから一般的なルートへ**宣言することです: + +```php +// 間違い: 'rss.xml' は最初のルートにキャッチされ、この文字列は として解釈されます +$router->addRoute('', 'Article:view'); +$router->addRoute('rss.xml', 'Feed:rss'); + +// 正しい +$router->addRoute('rss.xml', 'Feed:rss'); +$router->addRoute('', 'Article:view'); +``` + +ルートは、リンクを生成する際にも上から下に評価されます: + +```php +// 間違い: 'Feed:rss' へのリンクは 'admin/feed/rss' として生成されます +$router->addRoute('admin//', 'Admin:default'); +$router->addRoute('rss.xml', 'Feed:rss'); + +// 正しい +$router->addRoute('rss.xml', 'Feed:rss'); +$router->addRoute('admin//', 'Admin:default'); +``` + +ルートを正しく組み立てるには、ある程度のスキルが必要であることを隠すつもりはありません。それを習得するまでは、[ルーティングパネル |#ルータのデバッグ]が便利なツールになります。 + + +マスクとパラメータ +--------- + +マスクは、Webサイトのルートディレクトリからの相対パスを記述します。最も単純なマスクは静的なURLです: + +```php +$router->addRoute('products', 'Products:default'); +``` + +マスクには、いわゆる**パラメータ**が含まれることがよくあります。これらは山括弧(例:``)で示され、ターゲットPresenterに渡されます。たとえば、メソッド `renderShow(int $year)` や永続パラメータ `$year` に渡されます: + +```php +$router->addRoute('chronicle/', 'History:show'); +``` + +この例は、ブラウザで `https://example.com/chronicle/2020` を開くと、Presenter `History` とアクション `show` がパラメータ `year: 2020` とともに表示されることを示しています。 + +パラメータには、マスク内で直接デフォルト値を指定でき、これによりオプションになります: + +```php +$router->addRoute('chronicle/', 'History:show'); +``` + +これで、ルートはURL `https://example.com/chronicle/` も受け入れ、これも `History:show` をパラメータ `year: 2020` とともに表示します。 + +パラメータは、もちろんPresenter名やアクション名にもなり得ます。たとえば、このように: + +```php +$router->addRoute('/', 'Home:default'); +``` + +指定されたルートは、たとえば `/article/edit` や `/catalog/list` の形式のURLを受け入れ、それらをPresenterとアクション `Article:edit` および `Catalog:list` として解釈します。 + +同時に、パラメータ `presenter` と `action` にデフォルト値 `Home` と `default` を与え、それらもオプションになります。したがって、ルートは `/article` の形式のURLも受け入れ、それを `Article:default` として解釈します。または逆に、`Product:default` へのリンクはパス `/product` を生成し、デフォルトの `Home:default` へのリンクはパス `/` を生成します。 + +マスクは、Webサイトのルートディレクトリからの相対パスだけでなく、スラッシュで始まる場合は絶対パス、または2つのスラッシュで始まる場合は完全な絶対URLも記述できます: + +```php +// ドキュメントルートからの相対パス +$router->addRoute('/', /* ... */); + +// 絶対パス(ドメインからの相対パス) +$router->addRoute('//', /* ... */); + +// ドメインを含む絶対URL(スキーマからの相対パス) +$router->addRoute('//.example.com//', /* ... */); + +// スキーマを含む絶対URL +$router->addRoute('https://.example.com//', /* ... */); +``` + + +検証式 +--- + +各パラメータには、[正規表現|https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]を使用して検証条件を設定できます。たとえば、パラメータ `id` には、正規表現 `\d+` を使用して数字のみを受け入れるように指定します: + +```php +$router->addRoute('/[/]', /* ... */); +``` + +すべてのパラメータのデフォルトの正規表現は `[^/]+` です。つまり、スラッシュ以外のすべてです。パラメータがスラッシュも受け入れる必要がある場合は、式 `.+` を指定します: + +```php +// https://example.com/a/b/c を受け入れ、path は 'a/b/c' になります +$router->addRoute('', /* ... */); +``` + + +オプションシーケンス +---------- + +マスクでは、角括弧を使用してオプションの部分をマークできます。マスクの任意の部分をオプションにでき、パラメータを含めることもできます: + +```php +$router->addRoute('[/]', /* ... */); + +// 受け入れるパス: +// /cs/download => lang => cs, name => download +// /download => lang => null, name => download +``` + +パラメータがオプションシーケンスの一部である場合、当然ながらオプションにもなります。デフォルト値が指定されていない場合は、nullになります。 + +オプションの部分はドメインにも含めることができます: + +```php +$router->addRoute('//[.]example.com//', /* ... */); +``` + +シーケンスは任意にネストおよび組み合わせることができます: + +```php +$router->addRoute( + '[[-]/][/page-]', + 'Home:default', +); + +// 受け入れるパス: +// /cs/hello +// /en-us/hello +// /hello +// /hello/page-12 +``` + +URLを生成する際には、最短のバリアントが試みられるため、省略できるものはすべて省略されます。したがって、たとえばルート `index[.html]` はパス `/index` を生成します。左角括弧の後に感嘆符を付けることで、動作を逆にすることができます: + +```php +// /hello と /hello.html を受け入れ、/hello を生成します +$router->addRoute('[.html]', /* ... */); + +// /hello と /hello.html を受け入れ、/hello.html を生成します +$router->addRoute('[!.html]', /* ... */); +``` + +角括弧なしのオプションパラメータ(つまり、デフォルト値を持つパラメータ)は、基本的に次のように括弧で囲まれているかのように動作します: + +```php +$router->addRoute('//', /* ... */); + +// これに対応します: +$router->addRoute('[/[/[]]]', /* ... */); +``` + +末尾のスラッシュの動作に影響を与えたい場合、たとえば `/home/` の代わりに `/home` だけを生成するようにするには、次のようにします: + +```php +$router->addRoute('[[/[/]]]', /* ... */); +``` + + +ワイルドカード +------- + +絶対パスのマスクでは、次のワイルドカードを使用して、たとえば開発環境と本番環境で異なる可能性のあるドメインをマスクに書き込む必要性を回避できます: + +- `%tld%` = トップレベルドメイン、例:`com` または `org` +- `%sld%` = セカンドレベルドメイン、例:`example` +- `%domain%` = サブドメインなしのドメイン、例:`example.com` +- `%host%` = 完全なホスト、例:`www.example.com` +- `%basePath%` = ルートディレクトリへのパス + +```php +$router->addRoute('//www.%domain%/%basePath%//', /* ... */); +$router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ + 'presenter' => 'Home', + 'action' => 'default', +]); +``` + +より詳細な指定には、さらに拡張された形式を使用できます。ここでは、デフォルト値に加えて、パラメータの他のプロパティ(たとえば、検証正規表現(`id` パラメータを参照))も設定できます: + +```php +use Nette\Routing\Route; + +$router->addRoute('/[/]', [ + 'presenter' => [ + Route::Value => 'Home', + ], + 'action' => [ + Route::Value => 'default', + ], + 'id' => [ + Route::Pattern => '\d+', + ], +]); +``` + +配列で定義されたパラメータがパスのマスクに含まれていない場合、その値はURLの疑問符の後に指定されたクエリパラメータを使用しても変更できないことに注意することが重要です。 + + +フィルタと翻訳 +------- + +アプリケーションのソースコードは英語で記述しますが、Webサイトにチェコ語のURLが必要な場合は、次のような単純なルーティングでは: + +```php +$router->addRoute('/', 'Home:default'); +``` + +`/product/123` や `/cart` のような英語のURLが生成されます。URL内のPresenterとアクションをチェコ語の単語(例:`/produkt/123` や `/kosik`)で表現したい場合は、翻訳辞書を使用できます。その記述には、2番目のパラメータのより「冗長な」バリアントが必要です: + +```php +use Nette\Routing\Route; + +$router->addRoute('/', [ + 'presenter' => [ + Route::Value => 'Home', + Route::FilterTable => [ + // URL内の文字列 => presenter + 'produkt' => 'Product', + 'kosik' => 'Cart', + 'katalog' => 'Catalog', + ], + ], + 'action' => [ + Route::Value => 'default', + Route::FilterTable => [ + 'seznam' => 'list', + ], + ], +]); +``` + +翻訳辞書の複数のキーが同じPresenterにつながる可能性があります。これにより、異なるエイリアスが作成されます。正規のバリアント(つまり、生成されたURLに含まれるバリアント)は、最後のキーと見なされます。 + +翻訳テーブルはこの方法で任意のパラメータに使用できます。翻訳が存在しない場合は、元の値が使用されます。この動作は、`Route::FilterStrict => true` を追加することで変更でき、値が辞書にない場合、ルートはURLを拒否します。 + +配列形式の翻訳辞書に加えて、独自の翻訳関数をデプロイすることもできます。 + +```php +use Nette\Routing\Route; + +$router->addRoute('//', [ + 'presenter' => [ + Route::Value => 'Home', + Route::FilterIn => function (string $s): string { /* ... */ }, + Route::FilterOut => function (string $s): string { /* ... */ }, + ], + 'action' => 'default', + 'id' => null, +]); +``` + +関数 `Route::FilterIn` は、URL内のパラメータとPresenterに渡される文字列の間で変換を行い、関数 `FilterOut` は逆方向の変換を保証します。 + +パラメータ `presenter`、`action`、`module` には、PascalCaseまたはcamelCaseスタイルとURLで使用されるkebab-caseの間で変換を行う事前定義されたフィルタがすでにあります。パラメータのデフォルト値はすでに変換された形式で記述されるため、たとえばPresenterの場合は `` と記述し、`` とは記述しません。 + + +一般フィルタ +------ + +特定のパラメータ向けのフィルタに加えて、すべてのパラメータの連想配列を受け取り、それらを任意に変更して返すことができる一般フィルタも定義できます。一般フィルタはキー `null` の下に定義します。 + +```php +use Nette\Routing\Route; + +$router->addRoute('/', [ + 'presenter' => 'Home', + 'action' => 'default', + '' => [ + Route::FilterIn => function (array $params): array { /* ... */ }, + Route::FilterOut => function (array $params): array { /* ... */ }, + ], +]); +``` + +一般フィルタを使用すると、ルートの動作を完全に任意の方法で変更できます。たとえば、他のパラメータに基づいてパラメータを変更するために使用できます。たとえば、パラメータ `` の現在の値に基づいて `` と `` を翻訳するなどです。 + +パラメータに独自のフィルタが定義されており、同時に一般フィルタが存在する場合、独自の `FilterIn` が一般フィルタの前に実行され、逆に一般フィルタの `FilterOut` が独自のフィルタの前に実行されます。したがって、一般フィルタ内では、パラメータ `presenter` および `action` の値はPascalCaseおよびcamelCaseスタイルで記述されます。 + + +一方向OneWay +--------- + +一方向ルートは、アプリケーションがもはや生成しないが、まだ受け入れている古いURLの機能を維持するために使用されます。それらを `OneWay` フラグでマークします: + +```php +// 古いURL /product-info?id=123 +$router->addRoute('product-info', 'Product:detail', $router::ONE_WAY); +// 新しいURL /product/123 +$router->addRoute('product/', 'Product:detail'); +``` + +古いURLにアクセスすると、Presenterは自動的に新しいURLにリダイレクトするため、検索エンジンはこれらのページを2回インデックス付けしません([#SEOとカノニカル化]を参照)。 + + +コールバックによる動的ルーティング +----------------- + +コールバックによる動的ルーティングを使用すると、ルートに直接関数(コールバック)を割り当てることができ、特定のパスが訪問されたときに実行されます。この柔軟な機能により、アプリケーションのさまざまなエンドポイントを迅速かつ効率的に作成できます: + +```php +$router->addRoute('test', function () { + echo 'あなたは /test アドレスにいます'; +}); +``` + +マスクにパラメータを定義することもでき、それらは自動的にコールバックに渡されます: + +```php +$router->addRoute('', function (string $lang) { + echo match ($lang) { + 'cs' => '私たちのウェブサイトのチェコ語版へようこそ!', + 'en' => 'Welcome to the English version of our website!', + }; +}); +``` + + +モジュール +----- + +共通の[モジュール |directory-structure#Presenterとテンプレート]に属する複数のルートがある場合は、`withModule()` を使用します: + +```php +$router = new RouteList; +$router->withModule('Forum') // 以下のルートは Forum モジュールの一部です + ->addRoute('rss', 'Feed:rss') // presenter は Forum:Feed になります + ->addRoute('/') + + ->withModule('Admin') // 以下のルートは Forum:Admin モジュールの一部です + ->addRoute('sign:in', 'Sign:in'); +``` + +代替案は、パラメータ `module` を使用することです: + +```php +// URL manage/dashboard/default は Admin:Dashboard presenter にマッピングされます +$router->addRoute('manage//', [ + 'module' => 'Admin', +]); +``` + + +サブドメイン +------ + +ルートコレクションをサブドメインごとに分割できます: + +```php +$router = new RouteList; +$router->withDomain('example.com') + ->addRoute('rss', 'Feed:rss') + ->addRoute('/'); +``` + +ドメイン名には[#ワイルドカード]を使用することもできます: + +```php +$router = new RouteList; +$router->withDomain('example.%tld%') + // ... +``` + + +パスプレフィックス +--------- + +ルートコレクションをURLのパスごとに分割できます: + +```php +$router = new RouteList; +$router->withPath('eshop') + ->addRoute('rss', 'Feed:rss') // URL /eshop/rss をキャッチします + ->addRoute('/'); // URL /eshop// をキャッチします +``` + + +組み合わせ +----- + +上記の分割は相互に組み合わせることができます: + +```php +$router = (new RouteList) + ->withDomain('admin.example.com') + ->withModule('Admin') + ->addRoute(/* ... */) + ->addRoute(/* ... */) + ->end() + ->withModule('Images') + ->addRoute(/* ... */) + ->end() + ->end() + ->withDomain('example.com') + ->withPath('export') + ->addRoute(/* ... */) + // ... +``` + + +クエリパラメータ +-------- + +マスクにはクエリパラメータ(URLの疑問符の後のパラメータ)も含めることができます。これらには検証式を定義できませんが、Presenterに渡される名前を変更できます: + +```php +// クエリパラメータ 'cat' をアプリケーションで 'categoryId' という名前で使用したい +$router->addRoute('product ? id= & cat=', /* ... */); +``` + + +Fooパラメータ +-------- + +さて、さらに深く掘り下げます。Fooパラメータは、基本的に名前のないパラメータであり、正規表現をマッチさせることができます。例としては、`/index`、`/index.html`、`/index.htm`、`/index.php` を受け入れるルートがあります: + +```php +$router->addRoute('index', /* ... */); +``` + +URLを生成する際に使用される文字列を明示的に定義することもできます。文字列は疑問符の直後に配置する必要があります。次のルートは前のルートに似ていますが、文字列 `.html` が生成値として設定されているため、`/index` の代わりに `/index.html` を生成します: + +```php +$router->addRoute('index', /* ... */); +``` + + +アプリケーションへの統合 +============ + +作成したルータをアプリケーションに組み込むには、DIコンテナにそれについて伝える必要があります。最も簡単な方法は、ルータオブジェクトを作成するファクトリを準備し、コンテナの設定でそれを使用するように指示することです。その目的のために、メソッド `App\Core\RouterFactory::createRouter()` を記述するとしましょう: + +```php +namespace App\Core; + +use Nette\Application\Routers\RouteList; + +class RouterFactory +{ + public static function createRouter(): RouteList + { + $router = new RouteList; + $router->addRoute(/* ... */); + return $router; + } +} +``` + +次に、[設定 |dependency-injection:services]に次のように記述します: + +```neon +services: + - App\Core\RouterFactory::createRouter +``` + +データベースなどへの依存関係は、[autowiring|dependency-injection:autowiring]を使用してファクトリメソッドにそのパラメータとして渡されます: + +```php +public static function createRouter(Nette\Database\Connection $db): RouteList +{ + // ... +} +``` + + +SimpleRouter +============ + +ルートコレクションよりもはるかに単純なルータは、[SimpleRouter |api:Nette\Application\Routers\SimpleRouter]です。URLの形式に特別な要件がない場合、`mod_rewrite`(またはその代替)が利用できない場合、またはまだきれいなURLを扱いたくない場合に使用します。 + +おおよそ次のような形式のアドレスを生成します: + +``` +http://example.com/?presenter=Product&action=detail&id=123 +``` + +SimpleRouterのコンストラクタのパラメータは、パラメータなしでページを開いた場合(例:`http://example.com/`)にリダイレクトされるデフォルトのPresenterとアクションです。 + +```php +// デフォルトのpresenterは 'Home'、アクションは 'default' になります +$router = new Nette\Application\Routers\SimpleRouter('Home:default'); +``` + +SimpleRouterを[設定 |dependency-injection:services]で直接定義することをお勧めします: + +```neon +services: + - Nette\Application\Routers\SimpleRouter('Home:default') +``` + + +SEOとカノニカル化 +========== + +フレームワークは、異なるURLでコンテンツが重複するのを防ぐことで、SEO(検索エンジン最適化)に貢献します。特定のターゲットに複数のアドレス(例:`/index` と `/index.html`)がある場合、フレームワークは最初のものをプライマリ(カノニカル)として指定し、その他をHTTPコード301でリダイレクトします。これにより、検索エンジンはページを2回インデックス付けせず、ページランクを希釈しません。 + +このプロセスはカノニカル化と呼ばれます。カノニカルURLは、ルータによって生成されるURL、つまりOneWayフラグのないコレクション内の最初の適合するルートです。したがって、コレクションでは**プライマリルートを最初に**リストします。 + +カノニカル化はPresenterによって実行されます。詳細は[カノニカル化 |presenters#カノニカル化]の章を参照してください。 + + +HTTPS +===== + +HTTPSプロトコルを使用するには、ホスティングで有効にし、サーバーを正しく設定する必要があります。 + +Webサイト全体をHTTPSにリダイレクトするには、サーバーレベルで設定する必要があります。たとえば、アプリケーションのルートディレクトリにある.htaccessファイルを使用して、HTTPコード301で設定します。設定はホスティングによって異なる場合があり、おおよそ次のようになります: + +``` + + RewriteEngine On + ... + RewriteCond %{HTTPS} off + RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] + ... + +``` + +ルータはページが読み込まれたのと同じプロトコルでURLを生成するため、他に設定する必要はありません。 + +ただし、例外的に異なるルートを異なるプロトコルで実行する必要がある場合は、ルートのマスクで指定します: + +```php +// HTTPでアドレスを生成します +$router->addRoute('http://%host%//', /* ... */); + +// HTTPSでアドレスを生成します +$router->addRoute('https://%host%//', /* ... */); +``` + + +ルータのデバッグ +======== + +[Tracy Bar |tracy:]に表示されるルーティングパネルは、ルートのリストと、ルータがURLから取得したパラメータを表示する便利なツールです。 + +緑色のバーと✓記号は、現在のURLを処理したルートを表し、青色と≈記号は、緑色のルートが先行しなかった場合にURLを処理したであろうルートを示します。次に、現在のPresenterとアクションが表示されます。 + +[* routing-debugger.webp *] + +同時に、[カノニカル化 |#SEOとカノニカル化]による予期しないリダイレクトが発生した場合、*redirect*バーのパネルを見て、ルータが最初にURLをどのように理解し、なぜリダイレクトしたかを確認すると便利です。 + +.[note] +ルータをデバッグする際には、ブラウザで開発者ツール(Ctrl+Shift+IまたはCmd+Option+I)を開き、ネットワークパネルでキャッシュを無効にして、リダイレクトがキャッシュされないようにすることをお勧めします。 + + +パフォーマンス +======= + +ルートの数はルータの速度に影響します。その数は数十を超えるべきではありません。WebサイトのURL構造が複雑すぎる場合は、カスタム[#カスタムルータ]を作成できます。 + +ルータにデータベースなどの依存関係がなく、そのファクトリが引数を受け取らない場合は、そのコンパイル済み形式をDIコンテナに直接シリアライズして、アプリケーションをわずかに高速化できます。 + +```neon +routing: + cache: true +``` + + +カスタムルータ +======= + +以下の行は、非常に上級のユーザー向けです。独自のルータを作成し、それを自然にルートコレクションに統合できます。ルータは、2つのメソッドを持つ[api:Nette\Routing\Router]インターフェースの実装です: + +```php +use Nette\Http\IRequest as HttpRequest; +use Nette\Http\UrlScript; + +class MyRouter implements Nette\Routing\Router +{ + public function match(HttpRequest $httpRequest): ?array + { + // ... + } + + public function constructUrl(array $params, UrlScript $refUrl): ?string + { + // ... + } +} +``` + +メソッド `match` は、現在のリクエスト [$httpRequest |http:request] を処理し、そこからURLだけでなくヘッダーなども取得して、Presenter名とそのパラメータを含む配列に変換します。リクエストを処理できない場合は、nullを返します。 リクエストを処理する際には、少なくともPresenterとアクションを返す必要があります。Presenter名は完全であり、存在する可能性のあるモジュールも含まれます: + +```php +[ + 'presenter' => 'Front:Home', + 'action' => 'default', +] +``` + +メソッド `constructUrl` は、逆にパラメータの配列から結果の絶対URLを構築します。そのために、パラメータ [`$refUrl`|api:Nette\Http\UrlScript](現在のURL)からの情報を使用できます。 + +`add()` を使用してルートコレクションに追加します: + +```php +$router = new Nette\Application\Routers\RouteList; +$router->add($myRouter); +$router->addRoute(/* ... */); +// ... +``` + + +スタンドアロンでの使用 +=========== + +スタンドアロンでの使用とは、Nette ApplicationやPresenterを使用しないアプリケーションでルータの機能を利用することを意味します。この章で示したことのほとんどすべてが適用されますが、以下の違いがあります: + +- ルートコレクションには[api:Nette\Routing\RouteList]クラスを使用します +- シンプルルータとして[api:Nette\Routing\SimpleRouter]クラスを使用します +- `Presenter:action` のペアが存在しないため、[#拡張表記]を使用します + +したがって、再びルータを構築するメソッドを作成します。例: + +```php +namespace App\Core; + +use Nette\Routing\RouteList; + +class RouterFactory +{ + public static function createRouter(): RouteList + { + $router = new RouteList; + $router->addRoute('rss.xml', [ + 'controller' => 'RssFeedController', + ]); + $router->addRoute('article/', [ + 'controller' => 'ArticleController', + ]); + // ... + return $router; + } +} +``` + +DIコンテナを使用している場合(推奨)、再びメソッドを設定に追加し、その後ルータとHTTPリクエストをコンテナから取得します: + +```php +$router = $container->getByType(Nette\Routing\Router::class); +$httpRequest = $container->getByType(Nette\Http\IRequest::class); +``` + +または、オブジェクトを直接作成します: + +```php +$router = App\Core\RouterFactory::createRouter(); +$httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); +``` + +これで、ルータを動作させるだけです: + +```php +$params = $router->match($httpRequest); +if ($params === null) { + // 適合するルートが見つかりませんでした。エラー404を送信します + exit; +} + +// 取得したパラメータを処理します +$controller = $params['controller']; +// ... +``` + +そして逆に、ルータを使用してリンクを構築します: + +```php +$params = ['controller' => 'ArticleController', 'id' => 123]; +$url = $router->constructUrl($params, $httpRequest->getUrl()); +``` + + +{{composer: nette/router}} diff --git a/application/ja/templates.texy b/application/ja/templates.texy new file mode 100644 index 0000000000..2492f2325a --- /dev/null +++ b/application/ja/templates.texy @@ -0,0 +1,323 @@ +テンプレート +****** + +.[perex] +Netteは[Latte |latte:]テンプレートエンジンを使用しています。これはPHPで最も安全なテンプレートエンジンであり、同時に最も直感的なシステムでもあるためです。多くの新しいことを学ぶ必要はなく、PHPの知識といくつかのタグで十分です。 + +ページは通常、レイアウトテンプレートと特定のアクションのテンプレートから構成されます。これはレイアウトテンプレートの例です。`{block}`ブロックと`{include}`タグに注目してください: + +```latte + + + + {block title}My App{/block} + + +
    ...
    + {include content} +
    ...
    + + +``` + +そして、これはアクションテンプレートになります: + +```latte +{block title}Homepage{/block} + +{block content} +

    Homepage

    +... +{/block} +``` + +これは、レイアウトの`{include content}`の場所に挿入される`content`ブロックを定義し、レイアウトの`{block title}`を上書きする`title`ブロックも再定義します。結果を想像してみてください。 + + +テンプレートの検索 +--------- + +Presenterでどのテンプレートをレンダリングするかを指定する必要はありません。フレームワークはパスを自動的に推測し、記述の手間を省きます。 + +各Presenterが独自のディレクトリを持つディレクトリ構造を使用している場合は、アクション(またはビュー)の名前でこのディレクトリにテンプレートを配置するだけです。つまり、アクション`default`にはテンプレート`default.latte`を使用します: + +/--pre +app/ +└── Presentation/ + └── Home/ + ├── HomePresenter.php + └── default.latte +\-- + +Presenterが1つのディレクトリにまとめられ、テンプレートが`templates`フォルダにある構造を使用している場合は、ファイルを`..latte`または`/.latte`に保存します: + +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── Home.default.latte ← 1番目のバリアント + └── Home/ + └── default.latte ← 2番目のバリアント +\-- + +`templates`ディレクトリは、Presenterクラスを含むディレクトリと同じレベル、つまり1つ上のレベルに配置することもできます。 + +テンプレートが見つからない場合、Presenterは[エラー404 - ページが見つかりません |presenters#404エラーなど]で応答します。 + +`$this->setView('jineView')`を使用してビューを変更します。`$this->template->setFile('/path/to/template.latte')`を使用してテンプレートファイルを直接指定することもできます。 + +.[note] +テンプレートが検索されるファイルは、可能なファイル名の配列を返すメソッド[formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()]をオーバーライドすることで変更できます。 + + +レイアウトテンプレートの検索 +-------------- + +Netteはレイアウトファイルも自動的に検索します。 + +各Presenterが独自のディレクトリを持つディレクトリ構造を使用している場合は、レイアウトをPresenterのフォルダに配置します(そのPresenterに固有の場合)。または、複数のPresenterで共有されている場合は1つ上のレベルに配置します: + +/--pre +app/ +└── Presentation/ + ├── @layout.latte ← 共通レイアウト + └── Home/ + ├── @layout.latte ← Home presenter 専用 + ├── HomePresenter.php + └── default.latte +\-- + +Presenterが1つのディレクトリにまとめられ、テンプレートが`templates`フォルダにある構造を使用している場合、レイアウトは次の場所にあると想定されます: + +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── @layout.latte ← 共通レイアウト + ├── Home.@layout.latte ← Home 専用、1番目のバリアント + └── Home/ + └── @layout.latte ← Home 専用、2番目のバリアント +\-- + +Presenterがモジュール内にある場合、モジュールのネストに応じて、さらに上のディレクトリレベルでも検索されます。 + +レイアウト名は`$this->setLayout('layoutAdmin')`を使用して変更でき、その場合、ファイル`@layoutAdmin.latte`にあると想定されます。`$this->setLayout('/path/to/template.latte')`を使用してレイアウトテンプレートファイルを直接指定することもできます。 + +`$this->setLayout(false)`またはテンプレート内の`{layout none}`タグを使用すると、レイアウト検索が無効になります。 + +.[note] +レイアウトテンプレートが検索されるファイルは、可能なファイル名の配列を返すメソッド[formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()]をオーバーライドすることで変更できます。 + + +テンプレート内の変数 +---------- + +テンプレートに変数を渡すには、それらを`$this->template`に書き込みます。その後、テンプレート内でローカル変数として利用できます: + +```php +$this->template->article = $this->articles->getById($id); +``` + +このようにして、任意の変数をテンプレートに簡単に渡すことができます。ただし、堅牢なアプリケーションを開発する場合、制限を設ける方が役立つ場合があります。たとえば、テンプレートが期待する変数のリストとその型を明示的に定義するなどです。これにより、PHPは型をチェックでき、IDEは正しく提案でき、静的解析はエラーを検出できます。 + +そして、そのようなリストをどのように定義するのでしょうか? 単純にクラスとそのプロパティの形式で定義します。Presenterと同様に名前を付けますが、最後に`Template`を付けます: + +```php +/** + * @property-read ArticleTemplate $template + */ +class ArticlePresenter extends Nette\Application\UI\Presenter +{ +} + +class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template +{ + public Model\Article $article; + public Nette\Security\User $user; + + // その他の変数 +} +``` + +Presenterの`$this->template`オブジェクトは、`ArticleTemplate`クラスのインスタンスになります。したがって、PHPは書き込み時に宣言された型をチェックします。そして、PHP 8.2以降では、存在しない変数への書き込みについても警告します。以前のバージョンでは、トレイト[Nette\SmartObject |utils:smartobject]を使用することで同じことが達成できます。 + +`@property-read`アノテーションはIDEと静的解析向けであり、これにより提案が機能します。「PhpStorm and code completion for $this⁠-⁠>⁠template」:https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template を参照してください。 + +[* phpstorm-completion.webp *] + +テンプレートでも提案の贅沢を楽しむことができます。PhpStormにLatteプラグインをインストールし、テンプレートの先頭にクラス名を指定するだけです。詳細は「Latte: 型システムの使い方」:https://blog.nette.org/en/latte-how-to-use-type-system の記事を参照してください: + +```latte +{templateType App\Presentation\Article\ArticleTemplate} +... +``` + +これはコンポーネントのテンプレートでも機能します。命名規則に従い、たとえばコンポーネント`FifteenControl`に対してテンプレートクラス`FifteenTemplate`を作成するだけです。 + +`$template`を別のクラスのインスタンスとして作成する必要がある場合は、`createTemplate()`メソッドを使用します: + +```php +public function renderDefault(): void +{ + $template = $this->createTemplate(SpecialTemplate::class); + $template->foo = 123; + // ... + $this->sendTemplate($template); +} +``` + + +デフォルト変数 +------- + +Presenterとコンポーネントは、いくつかの便利な変数を自動的にテンプレートに渡します: + +- `$basePath` はルートディレクトリへの絶対URLパスです(例:`/eshop`) +- `$baseUrl` はルートディレクトリへの絶対URLです(例:`http://localhost/eshop`) +- `$user` は[ユーザーを表す |security:authentication]オブジェクトです +- `$presenter` は現在のPresenterです +- `$control` は現在のコンポーネントまたはPresenterです +- `$flashes` は関数 `flashMessage()` によって送信された[メッセージ |presenters#フラッシュメッセージ]の配列です + +独自のテンプレートクラスを使用している場合、これらの変数はプロパティを作成すれば渡されます。 + + +リンクの作成 +------ + +テンプレートでは、他のPresenterとアクションへのリンクは次のように作成されます: + +```latte +製品詳細 +``` + +属性 `n:href` はHTMLタグ `` に非常に便利です。リンクを他の場所、たとえばテキスト内に出力したい場合は、`{link}` を使用します: + +```latte +アドレスは: {link Home:default} +``` + +詳細については、[URLリンクの作成|creating-links]の章を参照してください。 + + +カスタムフィルタ、タグなど +------------- + +Latteテンプレートシステムは、カスタムフィルタ、関数、タグなどで拡張できます。これは、`render`または`beforeRender()`メソッドで直接行うことができます: + +```php +public function beforeRender(): void +{ + // フィルタの追加 + $this->template->addFilter('foo', /* ... */); + + // または Latte\Engine オブジェクトを直接設定 + $latte = $this->template->getLatte(); + $latte->addFilterLoader(/* ... */); +} +``` + +Latteバージョン3では、より高度な方法として、各Webプロジェクト用に[extension |latte:extending-latte#Latte Extension]を作成する方法が提供されています。そのようなクラスの簡単な例: + +```php +namespace App\Presentation\Accessory; + +final class LatteExtension extends Latte\Extension +{ + public function __construct( + private App\Model\Facade $facade, + private Nette\Security\User $user, + // ... + ) { + } + + public function getFilters(): array + { + return [ + 'timeAgoInWords' => $this->filterTimeAgoInWords(...), + 'money' => $this->filterMoney(...), + // ... + ]; + } + + public function getFunctions(): array + { + return [ + 'canEditArticle' => + fn($article) => $this->facade->canEditArticle($article, $this->user->getId()), + // ... + ]; + } + + // ... +} +``` + +[設定 |configuration#Latte テンプレート]を使用して登録します: + +```neon +latte: + extensions: + - App\Presentation\Accessory\LatteExtension +``` + + +翻訳 +---------- + +多言語アプリケーションをプログラミングしている場合、テンプレート内の一部のテキストを異なる言語で出力する必要があるでしょう。Nette Frameworkはこの目的のために、翻訳インターフェース[api:Nette\Localization\Translator]を定義しています。これには`translate()`という1つのメソッドがあります。これはメッセージ`$message`(通常は文字列)と任意の追加パラメータを受け取ります。タスクは翻訳された文字列を返すことです。 Netteにはデフォルトの実装はありません。[Componette |https://componette.org/search/localization]で見つけることができるいくつかの既製のソリューションから、ニーズに合わせて選択できます。それらのドキュメントで、トランスレータの設定方法を学びます。 + +テンプレートには、[渡してもらう |dependency-injection:passing-dependencies]トランスレータを`setTranslator()`メソッドで設定できます: + +```php +protected function beforeRender(): void +{ + // ... + $this->template->setTranslator($translator); +} +``` + +あるいは、トランスレータは[設定 |configuration#Latte テンプレート]を使用して設定することもできます: + +```neon +latte: + extensions: + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) +``` + +その後、トランスレータは、たとえばフィルタ`|translate`として使用でき、`translate()`メソッドに渡される追加パラメータ(`foo, bar`を参照)も含みます: + +```latte +{='カート'|translate} +{$item|translate} +{$item|translate, foo, bar} +``` + +またはアンダースコアタグとして: + +```latte +{_'カート'} +{_$item} +{_$item, foo, bar} +``` + +テンプレートの一部を翻訳するには、ペアタグ`{translate}`があります(Latte 2.11以降、以前は`{_}`タグが使用されていました): + +```latte +{translate}注文{/translate} +{translate foo, bar}注文{/translate} +``` + +トランスレータは通常、テンプレートのレンダリング中に実行時に呼び出されます。ただし、Latteバージョン3では、テンプレートのコンパイル中にすべての静的テキストを翻訳できます。これにより、各文字列が一度だけ翻訳され、結果の翻訳がコンパイル済み形式に書き込まれるため、パフォーマンスが節約されます。キャッシュディレクトリには、言語ごとに複数のコンパイル済みバージョンのテンプレートが作成されます。これを行うには、言語を2番目のパラメータとして指定するだけです: + +```php +protected function beforeRender(): void +{ + // ... + $this->template->setTranslator($translator, $lang); +} +``` + +静的テキストとは、たとえば `{_'hello'}` や `{translate}hello{/translate}` のようなものを意味します。`{_$foo}` のような非静的テキストは、引き続き実行時に翻訳されます。 diff --git a/application/pl/@home.texy b/application/pl/@home.texy index 138c335e70..e7a87853d3 100644 --- a/application/pl/@home.texy +++ b/application/pl/@home.texy @@ -2,35 +2,84 @@ Nette Application ***************** .[perex] -Pakiet `nette/application` stanowi podstawę do tworzenia interaktywnych aplikacji internetowych. - -- [Jak działają aplikacje? |how-it-works] -- [Bootstrap |Bootstrap] -- [Prezenterzy |presenters] -- [Szablony |templates] -- [Moduły |modules] -- [Trasowanie |routing] -- [Tworzenie linków URL |creating-links] -- [Elementy interaktywne |components] -- [AJAX i snippety |ajax] -- [Mnożnik |multiplier] -- [Konfiguracja |configuration] +Nette Application jest rdzeniem frameworka Nette, który dostarcza potężne narzędzia do tworzenia nowoczesnych aplikacji internetowych. Oferuje szereg wyjątkowych funkcji, które znacząco ułatwiają rozwój oraz poprawiają bezpieczeństwo i utrzymywalność kodu. Instalacja ---------- -Pobierz i zainstaluj bibliotekę za pomocą [Composera |best-practices:composer]: +Bibliotekę pobierzesz i zainstalujesz za pomocą narzędzia [Composer|best-practices:composer]: ```shell composer require nette/application ``` -| wersja zgodna z PHP + +Dlaczego wybrać Nette Application? +---------------------------------- + +Nette zawsze było pionierem w dziedzinie technologii internetowych. + +**Dwukierunkowy router:** Nette dysponuje zaawansowanym systemem routingu, który jest unikalny dzięki swojej dwukierunkowości - nie tylko tłumaczy URL na akcje aplikacji, ale także potrafi generować adresy URL wstecz. Oznacza to, że: +- Możesz w dowolnym momencie zmienić strukturę URL całej aplikacji bez konieczności modyfikowania szablonów +- URL są automatycznie kanonizowane, co poprawia SEO +- Routing jest definiowany w jednym miejscu, a nie rozproszony w adnotacjach + +**Komponenty i sygnały:** Wbudowany system komponentów inspirowany Delphi i React.js jest całkowicie wyjątkowy wśród frameworków PHP: +- Umożliwia tworzenie reużywalnych elementów UI +- Obsługuje hierarchiczne składanie komponentów +- Oferuje eleganckie przetwarzanie żądań AJAX za pomocą sygnałów +- Bogata biblioteka gotowych komponentów na [Componette](https://componette.org) + +**AJAX i snippety:** Nette wprowadziło rewolucyjny sposób pracy z AJAXem już w 2009 roku, długo przed podobnymi rozwiązaniami jak Hotwire dla Ruby on Rails czy Symfony UX Turbo: +- Snippety umożliwiają aktualizację tylko części strony bez konieczności pisania JavaScriptu +- Automatyczna integracja z systemem komponentów +- Inteligentna inwalidacja części stron +- Minimalna ilość przesyłanych danych + +**Intuicyjne szablony [Latte|latte:]:** Najbezpieczniejszy system szablonów dla PHP z zaawansowanymi funkcjami: +- Automatyczna ochrona przed XSS z kontekstowym escapowaniem +- Rozszerzalność za pomocą własnych filtrów, funkcji i znaczników +- Dziedziczenie szablonów i snippety dla AJAX +- Doskonałe wsparcie PHP 8.x z systemem typów + +**Dependency Injection:** Nette w pełni wykorzystuje Dependency Injection: +- Automatyczne przekazywanie zależności (autowiring) +- Konfiguracja za pomocą przejrzystego formatu NEON +- Wsparcie dla fabryk komponentów + + +Główne zalety +------------- + +- **Bezpieczeństwo**: Automatyczna obrona przed [podatnościami|nette:vulnerability-protection] takimi jak XSS, CSRF, itd. +- **Produktywność**: Mniej pisania, więcej funkcji dzięki inteligentnemu projektowi +- **Debugowanie**: [Debugger Tracy|tracy:] z panelem routingu +- **Wydajność**: Inteligentny cache, leniwe ładowanie komponentów +- **Elastyczność**: Łatwa modyfikacja URL nawet po zakończeniu aplikacji +- **Komponenty**: Unikalny system reużywalnych elementów UI +- **Nowoczesność**: Pełne wsparcie PHP 8.4+ i systemu typów + + +Zaczynamy +--------- + +1. [Jak działają aplikacje? |how-it-works] - Zrozumienie podstawowej architektury +2. [Presentery |presenters] - Praca z presenterami i akcjami +3. [Szablony |templates] - Tworzenie szablonów w Latte +4. [Routing |routing] - Konfiguracja adresów URL +5. [Komponenty interaktywne |components] - Wykorzystanie systemu komponentów + + +Kompatybilność z PHP +-------------------- + +| wersja | kompatybilna z PHP |-----------|------------------- -| Nette Application 4.0 | PHP 8.0 - 8.2 -| Nette Application 3.1 | PHP 7.2 - 8.2 -| Nette Application 3.0 | PHP 7.1 - 8.0 -| Nette Application 2.4 | PHP 5.6 - 8.0 +| Nette Application 4.0 | PHP 8.1 – 8.4 +| Nette Application 3.2 | PHP 8.1 – 8.4 +| Nette Application 3.1 | PHP 7.2 – 8.3 +| Nette Application 3.0 | PHP 7.1 – 8.0 +| Nette Application 2.4 | PHP 5.6 – 8.0 -Ważne dla najnowszej wersji poprawki. +Dotyczy ostatniej wersji patch. diff --git a/application/pl/@left-menu.texy b/application/pl/@left-menu.texy index c5153e7498..8600cb220d 100644 --- a/application/pl/@left-menu.texy +++ b/application/pl/@left-menu.texy @@ -1,19 +1,22 @@ -Aplikacje w Nette +Nette Application ***************** - [Jak działają aplikacje? |how-it-works] -- [Bootstrap |Bootstrap] -- [Prezenterzy |presenters] +- [Bootstrapping] +- [Presentery |presenters] - [Szablony |templates] -- [Moduły |modules] -- [Trasowanie |routing] +- [Struktura katalogów |directory-structure] +- [Routing |routing] - [Tworzenie linków URL |creating-links] -- [Elementy interaktywne |components] -- [AJAX i snippety |ajax] -- [Mnożnik |multiplier] +- [Komponenty interaktywne |components] +- [AJAX & snippety |ajax] +- [Multiplier |Multiplier] - [Konfiguracja |configuration] -Dalsze czytanie -*************** -- [Samouczki i procedury |best-practices:] +Dalsza lektura +************** +- [Dlaczego używać Nette? |www:10-reasons-why-nette] +- [Instalacja |nette:installation] +- [Pisanie pierwszej aplikacji! |quickstart:] +- [Przewodniki i dobre praktyki |best-practices:] - [Rozwiązywanie problemów |nette:troubleshooting] diff --git a/application/pl/@meta.texy b/application/pl/@meta.texy new file mode 100644 index 0000000000..61ac92d1af --- /dev/null +++ b/application/pl/@meta.texy @@ -0,0 +1 @@ +{{sitename: Dokumentacja Nette}} diff --git a/application/pl/ajax.texy b/application/pl/ajax.texy index 34eb88ff52..f6944bd845 100644 --- a/application/pl/ajax.texy +++ b/application/pl/ajax.texy @@ -1,38 +1,45 @@ -AJAX i snippety +AJAX & snippety ***************
    -Nowoczesne aplikacje internetowe działają dziś w połowie na serwerze, w połowie w przeglądarce. AJAX jest kluczowym elementem łączącym. Jakie wsparcie oferuje Nette Framework? -- Wysyłanie fragmentów szablonów +W erze nowoczesnych aplikacji internetowych, gdzie funkcjonalność jest często rozdzielona między serwerem a przeglądarką, AJAX jest niezbędnym elementem łączącym. Jakie możliwości oferuje nam Nette Framework w tej dziedzinie? +- wysyłanie fragmentów szablonu, tzw. snippetów - przekazywanie zmiennych między PHP a JavaScriptem -- debugowanie aplikacji AJAX +- narzędzia do debugowania żądań AJAX
    -Żądanie AJAX może być wykryte przez metodę serwisową [enkapsulującą żądanie HTTP |http:request] `$httpRequest->isAjax()` (wykryte przez nagłówek HTTP `X-Requested-With`). Wewnątrz prezentera jest zapewniony "skrót" w postaci metody `$this->isAjax()`. -Żądanie AJAX nie różni się od tradycyjnego żądania - prezenter jest wywoływany z określonym widokiem i parametrami. To również zależy od prezentera, jak odpowiada: może użyć niestandardowej procedury, która zwraca fragment HTML, dokument XML, obiekt JSON lub kod JavaScript. +Żądanie AJAX +============ -Aby wysłać dane do przeglądarki w formacie JSON, możesz użyć gotowego obiektu `payload`: +Żądanie AJAX zasadniczo nie różni się od klasycznego żądania HTTP. Wywoływany jest presenter z określonymi parametrami. Od presentera zależy, w jaki sposób zareaguje na żądanie - może zwrócić dane w formacie JSON, wysłać fragment kodu HTML, dokument XML itp. -```php -public function actionDelete(int $id): void -{ - if ($this->isAjax()) { - $this->payload->message = 'Success'; - } - // ... -} +Po stronie przeglądarki inicjujemy żądanie AJAX za pomocą funkcji `fetch()`: + +```js +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) +.then(response => response.json()) +.then(payload => { + // przetwarzanie odpowiedzi +}); ``` -Jeśli potrzebujesz pełnej kontroli nad wysłanym JSON, użyj metody `sendJson` w prezenterze. Spowoduje to natychmiastowe zakończenie prezentera i zrezygnowanie z szablonu: +Po stronie serwera rozpoznajemy żądanie AJAX za pomocą metody `$httpRequest->isAjax()` usługi [enkapsulującej żądanie HTTP |http:request]. Do detekcji używa nagłówka HTTP `X-Requested-With`, dlatego ważne jest, aby go wysyłać. W ramach presentera można użyć metody `$this->isAjax()`. + +Jeśli chcesz wysłać dane w formacie JSON, użyj metody [`sendJson()` |presenters#Wysłanie odpowiedzi]. Metoda ta również kończy działanie presentera. ```php -$this->sendJson(['key' => 'value', /* ... */]); +public function actionExport(): void +{ + $this->sendJson($this->model->getData); +} ``` -Jeśli chcemy wysłać HTML, możemy wybrać specjalny szablon dla AJAX: +Jeśli planujesz odpowiedzieć za pomocą specjalnego szablonu przeznaczonego dla AJAX, możesz to zrobić w następujący sposób: ```php public function handleClick($param): void @@ -45,222 +52,198 @@ public function handleClick($param): void ``` -Naja .[#toc-naja] -================= +Snippety +======== + +Najpotężniejszym narzędziem oferowanym przez Nette do łączenia serwera z klientem są snippety. Dzięki nim można przekształcić zwykłą aplikację w aplikację AJAXową przy minimalnym wysiłku i kilku linijkach kodu. Jak to wszystko działa, demonstruje przykład Fifteen, którego kod znajdziesz na [GitHubie |https://github.com/nette-examples/fifteen]. + +Snippety, czyli fragmenty, umożliwiają aktualizację tylko części strony, zamiast ponownego ładowania całej strony. Jest to nie tylko szybsze i bardziej efektywne, ale także zapewnia bardziej komfortowe doświadczenie użytkownika. Snippety mogą przypominać Hotwire dla Ruby on Rails lub Symfony UX Turbo. Co ciekawe, Nette wprowadziło snippety już 14 lat wcześniej. + +Jak działają snippety? Przy pierwszym załadowaniu strony (żądanie nie-AJAXowe) ładowana jest cała strona wraz ze wszystkimi snippetami. Kiedy użytkownik wchodzi w interakcję ze stroną (np. klika przycisk, wysyła formularz itp.), zamiast ładowania całej strony wywoływane jest żądanie AJAX. Kod w presenterze wykonuje akcję i decyduje, które snippety należy zaktualizować. Nette renderuje te snippety i wysyła je w formie tablicy w formacie JSON. Kod obsługujący w przeglądarce wstawia otrzymane snippety z powrotem na stronę. Przesyłany jest więc tylko kod zmienionych snippetów, co oszczędza przepustowość i przyspiesza ładowanie w porównaniu do przesyłania całej zawartości strony. + + +Naja +---- -[Biblioteka Naja |https://naja.js.org] służy do obsługi żądań AJAX po stronie przeglądarki. [Zainstaluj |https://naja.js.org/#/guide/01-install-setup-naja] go jako pakiet node.js (do użytku z Webpack, Rollup, Vite, Parcel i innych): +Do obsługi snippetów po stronie przeglądarki służy [biblioteka Naja |https://naja.js.org]. [Zainstaluj |https://naja.js.org/#/guide/01-install-setup-naja] ją jako pakiet node.js (do użytku z aplikacjami Webpack, Rollup, Vite, Parcel i innymi): ```shell npm install naja ``` -...lub osadzić bezpośrednio w szablonie strony: +…lub bezpośrednio wstaw do szablonu strony: ```html ``` +Najpierw należy [zainicjować |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] bibliotekę: -Snippets -======== +```js +naja.initialize(); +``` -Znacznie potężniejszym narzędziem jest wbudowana obsługa snippetów AJAX. Dzięki niemu można zamienić zwykłą aplikację w AJAXową za pomocą zaledwie kilku linijek kodu. Przykład Fifteen, którego kod można znaleźć na [GitHubie |https://github.com/nette-examples/fifteen], demonstruje jak to działa. +Aby zwykły link (sygnał) lub wysłanie formularza przekształcić w żądanie AJAX, wystarczy oznaczyć odpowiedni link, formularz lub przycisk klasą `ajax`: -Działanie snippetów polega na tym, że na początkowym (czyli nie-AJAX-owym) żądaniu przenoszona jest cała strona, a następnie na każdym [podżądaniu |components#Signal] AJAX-owym (= żądanie do tego samego prezentera i widoku) przenoszony jest tylko kod zmienionych fragmentów we wspomnianym repozytorium `payload`. Istnieją dwa mechanizmy tego działania: unieważnianie i renderowanie snippetów. +```html +Go -Snippets mogą przypominać Hotwire dla Ruby on Rails lub Symfony UX Turbo, ale Nette wymyślił je czternaście lat wcześniej. +
    + +
    +lub -Unieważnianie fragmentów .[#toc-invalidation-of-snippets] -========================================================= +
    + +
    +``` -Każdy obiekt klasy [Control |components] (którą jest sam Presenter) potrafi zapamiętać, czy w sygnale zaszły zmiany wymagające jego przerysowania. Służy do tego para metod `redrawControl()` i `isControlInvalid()`. Przykład: + +Przerysowanie snippetów +----------------------- + +Każdy obiekt klasy [Control |components] (w tym sam Presenter) śledzi, czy nastąpiły zmiany wymagające jego przerysowania. Służy do tego metoda `redrawControl()`: ```php public function handleLogin(string $user): void { - // po zalogowaniu się użytkownika obiekt musi zostać przerysowany + // po zalogowaniu należy przerysować odpowiednią część $this->redrawControl(); // ... } ``` -Nette oferuje jednak jeszcze dokładniejszą rozdzielczość niż na poziomie komponentów. Metody te mogą przyjąć jako argument nazwę "snippet", czyli wycinka. Możliwe jest więc unieważnienie (czyli wymuszenie przerysowania) na poziomie tych wycinków (każdy obiekt może mieć dowolną liczbę wycinków). Jeśli cały komponent zostanie unieważniony, każdy wycinek zostanie przerysowany. Komponent jest "unieważniony" nawet jeśli podkomponent jest unieważniony. - -```php -$this->isControlInvalid(); // -> false -$this->redrawControl('header'); // unieważnia snippet 'header' -$this->isControlInvalid('header'); // -> true -$this->isControlInvalid('footer'); // -> false -$this->isControlInvalid(); // -> prawda, przynajmniej jeden fragment jest nieprawidłowy +Nette pozwala na jeszcze dokładniejszą kontrolę tego, co ma zostać przerysowane. Wspomniana metoda może bowiem przyjmować jako argument nazwę snippetu. Można więc unieważnić (czytaj: wymusić przerysowanie) na poziomie części szablonu. Jeśli unieważniony zostanie cały komponent, przerysowany zostanie również każdy jego snippet: -$this->redrawControl(); // unieważnia cały komponent, każdy fragment -$this->isControlInvalid('footer'); // -> true +```php +// unieważnia snippet 'header' +$this->redrawControl('header'); ``` -Komponent, który otrzymuje sygnał, jest automatycznie oznaczany jako wyłączony. - -Unieważniając snippety, wiemy dokładnie, które części których elementów będą musiały zostać przerysowane. - - -Tagi `{snippet} … {/snippet}` .{toc: Tag snippet} -================================================= -Renderowanie strony jest bardzo podobne do normalnego żądania: ładowane są te same szablony itp. Ważne jest jednak pominięcie części, które nie powinny być wyprowadzane; pozostałe części są przypisywane do identyfikatora i wysyłane do użytkownika w formacie zrozumiałym dla JavaScript handler. +Snippety w Latte +---------------- - -Składnia .[#toc-syntax] ------------------------ - -Jeśli wewnątrz szablonu znajduje się kontrolka lub snippet, musimy owinąć go znacznikiem `{snippet} ... {/snippet}` para - zapewniają one wycięcie wyrenderowanego snippetu i wysłanie go do przeglądarki. Zawija go również za pomocą tagu pomocniczego `
    ` z wygenerowanym `id`. W powyższym przykładzie snippet nosi nazwę `header` i może również reprezentować np. szablon kontrolny: +Używanie snippetów w Latte jest niezwykle proste. Aby zdefiniować część szablonu jako snippet, wystarczy otoczyć ją znacznikami `{snippet}` i `{/snippet}`: ```latte {snippet header} -

    Hello ...

    +

    Witaj ...

    {/snippet} ``` -Fragment o typie innym niż `
    ` lub snippet z dodatkowymi atrybutami HTML uzyskuje się poprzez zastosowanie wariantu atrybutów: +Snippet tworzy w stronie HTML element `
    ` ze specjalnym wygenerowanym `id`. Podczas przerysowywania snippeta aktualizowana jest zawartość tego elementu. Dlatego konieczne jest, aby podczas pierwszego renderowania strony renderowane były również wszystkie snippety, nawet jeśli na początku mogą być puste. + +Możesz również utworzyć snippet z innym elementem niż `
    ` za pomocą n:atrybutu: ```latte
    -

    Hello ...

    +

    Witaj ...

    ``` -Dynamiczne fragmenty .[#toc-dynamic-snippets] -============================================= +Obszary snippetów +----------------- -Nette pozwala również na stosowanie snippetów, których nazwa jest tworzona w czasie biegu - czyli dynamicznie. Jest to przydatne w przypadku różnych list, gdzie przy zmianie jednego wiersza nie chcemy AJAXować całej listy, a jedynie sam wiersz. Przykład: +Nazwy snippetów mogą być również wyrażeniami: ```latte -
      - {foreach $list as $id => $item} -
    • {$item} update
    • - {/foreach} -
    +{foreach $items as $id => $item} +
  • {$item}
  • +{/foreach} ``` -Mamy tu statyczny snippet `itemsContainer`, zawierający kilka dynamicznych snippetów `item-0`, `item-1` itd. +W ten sposób powstanie kilka snippetów `item-0`, `item-1` itd. Gdybyśmy bezpośrednio unieważnili dynamiczny snippet (na przykład `item-1`), nic by się nie przerysowało. Powodem jest to, że snippety naprawdę działają jak wycinki i renderowane są tylko one same. Jednak w szablonie faktycznie nie ma żadnego snippeta o nazwie `item-1`. Powstaje on dopiero podczas wykonywania kodu wokół snippeta, czyli pętli foreach. Dlatego oznaczamy część szablonu, która ma zostać wykonana, za pomocą znacznika `{snippetArea}`: -Nie można bezpośrednio unieważnić dynamicznych snippetów (unieważnienie `item-1` nic nie daje), trzeba unieważnić ich nadrzędny statyczny snippet (tutaj snippet `itemsContainer`). Wówczas cały kod kontenera zostanie wykonany, ale do przeglądarki zostaną wysłane tylko jego podkontenerowe snippety. Jeśli chcesz, aby przeglądarka otrzymała tylko jeden z nich, musisz zmodyfikować dane wejściowe tego kontenera, aby nie generował pozostałych. +```latte +
      + {foreach $items as $id => $item} +
    • {$item}
    • + {/foreach} +
    +``` -W powyższym przykładzie musisz po prostu upewnić się, że gdy wykonasz żądanie ajaxowe, w zmiennej `$list` znajduje się tylko jeden wpis, a zatem, że pętla `foreach` wypełnia tylko jeden dynamiczny snippet: +I zlecamy przerysowanie zarówno samego snippeta, jak i całego nadrzędnego obszaru: ```php -class HomePresenter extends Nette\Application\UI\Presenter -{ - /** - * Tato metoda vrací data pro seznam. - * Obvykle se jedná pouze o vyžádání dat z modelu. - * Pro účely tohoto příkladu jsou data zadána natvrdo. - */ - private function getTheWholeList(): array - { - return [ - 'První', - 'Druhý', - 'Třetí', - ]; - } - - public function renderDefault(): void - { - if (!isset($this->template->list)) { - $this->template->list = $this->getTheWholeList(); - } - } - - public function handleUpdate(int $id): void - { - $this->template->list = $this->isAjax() - ? [] - : $this->getTheWholeList(); - $this->template->list[$id] = 'Updated item'; - $this->redrawControl('itemsContainer'); - } -} +$this->redrawControl('itemsContainer'); +$this->redrawControl('item-1'); ``` +Jednocześnie warto zadbać o to, aby tablica `$items` zawierała tylko te elementy, które mają zostać przerysowane. -Snippety w dołączonym szablonie .[#toc-snippets-in-an-included-template] -======================================================================== - -Może się zdarzyć, że w jakimś szablonie mamy snippet, który dopiero chcemy włączyć do innego szablonu. W tym przypadku musimy owinąć osadzenie tego szablonu znacznikami `snippetArea`, które następnie unieważniamy wraz z samym snippetem. - -Znaczniki `snippetArea` gwarantują, że kod osadzający szablon zostanie wykonany, ale do przeglądarki zostanie wysłany tylko wycinek z osadzanego szablonu. +Jeśli do szablonu za pomocą znacznika `{include}` wstawiamy inny szablon zawierający snippety, konieczne jest ponowne umieszczenie wstawionego szablonu w `snippetArea` i unieważnienie go razem ze snippetem: ```latte -{* parent.latte *} -{snippetArea wrapper} -{include 'child.latte'} +{snippetArea include} + {include 'included.latte'} {/snippetArea} ``` + ```latte -{* child.latte *} +{* included.latte *} {snippet item} -... + ... {/snippet} ``` + ```php -$this->redrawControl('wrapper'); +$this->redrawControl('include'); $this->redrawControl('item'); ``` -Takie podejście może być również stosowane w połączeniu z dynamicznymi snippetami. - -Dodawanie i usuwanie .[#toc-adding-and-deleting] -================================================ - -Jeśli dodasz nowy element i unieważnisz `itemsContainer`, to żądanie AJAX zwróci również nowy snippet, ale handler javascript nie może go nigdzie przypisać. W rzeczywistości na stronie nie ma jeszcze elementu HTML o tym identyfikatorze. +Snippety w komponentach +----------------------- -W takim przypadku najłatwiej jest owinąć całą listę jeszcze jednym snippetem i unieważnić całość: +Snippety można tworzyć również w [komponentach|components], a Nette będzie je automatycznie przerysowywać. Istnieje jednak pewne ograniczenie: do przerysowania snippetów wywoływana jest metoda `render()` bez parametrów. Oznacza to, że przekazywanie parametrów w szablonie nie będzie działać: ```latte -{snippet wholeList} -
      - {foreach $list as $id => $item} -
    • {$item} update
    • - {/foreach} -
    -{/snippet} -Add +OK +{control productGrid} + +nie będzie działać: +{control productGrid $arg, $arg} +{control productGrid:paginator} ``` + +Wysyłanie danych użytkownika +---------------------------- + +Wraz ze snippetami możesz wysłać klientowi dowolne inne dane. Wystarczy je zapisać w obiekcie `payload`: + ```php -public function handleAdd(): void +public function actionDelete(int $id): void { - $this->template->list = $this->getTheWholeList(); - $this->template->list[] = 'New one'; - $this->redrawControl('wholeList'); + // ... + if ($this->isAjax()) { + $this->payload->message = 'Sukces'; + } } ``` -To samo tyczy się usuwania. Można by jakoś wysłać pusty snippet, ale w praktyce większość list jest paginowana i byłoby to zbyt skomplikowane, aby bardziej ekonomicznie usunąć jeden plus ewentualnie załadować inny (który nie pasował wcześniej). - -Wysyłanie parametrów do komponentu .[#toc-sending-parameters-to-component] -========================================================================== +Przekazywanie parametrów +======================== -Jeśli wysyłamy parametry do komponentu za pomocą żądania AJAX, zarówno parametry sygnałowe, jak i parametry trwałe, musimy określić ich globalną nazwę w żądaniu, które zawiera nazwę komponentu. Pełna nazwa parametru jest zwracana przez metodę `getParameterId()`. +Jeśli za pomocą żądania AJAX wysyłamy parametry do komponentu, czy to parametry sygnału, czy parametry persistentne, musimy w żądaniu podać ich globalną nazwę, która zawiera również nazwę komponentu. Pełną nazwę parametru zwraca metoda `getParameterId()`. ```js -$.getJSON( - {link changeCountBasket!}, - { - {$control->getParameterId('id')}: id, - {$control->getParameterId('count')}: count - } -}); +let url = new URL({link //foo!}); +url.searchParams.set({$control->getParameterId('bar')}, bar); + +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) ``` -Metoda handle z odpowiednimi parametrami w komponencie. +A metoda handle z odpowiednimi parametrami w komponencie: ```php -public function handleChangeCountBasket(int $id, int $count): void +public function handleFoo(int $bar): void { - } ``` diff --git a/application/pl/bootstrap.texy b/application/pl/bootstrap.texy deleted file mode 100644 index 1a1ef76bbc..0000000000 --- a/application/pl/bootstrap.texy +++ /dev/null @@ -1,233 +0,0 @@ -Bootstrap -********* - -
    - -Bootstrap to kod startowy, który inicjalizuje środowisko, tworzy kontener wtrysku zależności (DI) i uruchamia aplikację. Powiedzmy: - -- jak skonfigurować przy użyciu plików NEON -- jak odróżnić tryb produkcyjny od deweloperskiego -- jak stworzyć kontener DI - -
    - - -Aplikacje, niezależnie od tego, czy są to aplikacje internetowe, czy skrypty wiersza poleceń, rozpoczynają swój czas działania od pewnej formy inicjalizacji środowiska. W dawnych czasach robił to plik o nazwie na przykład `include.inc.php`, który inkubował początkowy plik. -W nowoczesnych aplikacjach Nette zostało to zastąpione klasą `Bootstrap`, która jako część aplikacji znajduje się w pliku `app/Bootstrap.php` Może to wyglądać tak: - -```php -use Nette\Bootstrap\Configurator; - -class Bootstrap -{ - public static function boot(): Configurator - { - $appDir = dirname(__DIR__); - $configurator = new Configurator; - //$configurator->setDebugMode('secret@23.75.345.200'); - $configurator->enableTracy($appDir . '/log'); - $configurator->setTempDirectory($appDir . '/temp'); - $configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); - $configurator->addConfig($appDir . '/config/common.neon'); - return $configurator; - } -} -``` - - -index.php .[#toc-index-php] -=========================== - -Podstawowym plikiem w przypadku aplikacji internetowych jest `index.php`, który znajduje się w katalogu publicznym `www/`. Spowoduje to, że klasa Bootstrap zainicjuje środowisko i zwróci `$configurator`, a następnie wyprodukuje kontener DI. Następnie pobiera z niego usługę `Application`, która uruchamia aplikację internetową: - -```php -// inicjalizacja środowiska + uzyskanie obiektu Configurator -$configurator = App\Bootstrap::boot(); -// tworzenie kontenera DI -$container = $configurator->createContainer(); -// Kontener DI tworzy obiekt "Nette -$application = $container->getByType(Nette\Application\Application::class); -// uruchomienie aplikacji Nette -$application->run(); -``` - -Jak widać, klasa [api:Nette\Bootstrap\Configurator] pomaga w konfiguracji środowiska i tworzeniu kontenera dependency injection (DI), któremu teraz przyjrzymy się bliżej. - - -Tryb deweloperski a produkcyjny .[#toc-development-vs-production-mode] -====================================================================== - -Nette rozróżnia dwa podstawowe tryby, w których realizowane jest żądanie: deweloperski i produkcyjny. Tryb deweloperski ma na celu maksymalną wygodę dla programisty, wyświetlana jest Tracy, pamięć podręczna jest automatycznie aktualizowana, gdy zmieniają się szablony lub konfiguracje kontenerów DI itp. Produkcja skupia się na wydajności i rześkim wdrożeniu, Tracy tylko loguje błędy, a zmiany w szablonach i innych plikach nie są testowane. - -Wybór trybu odbywa się poprzez autodetekcję, więc zazwyczaj nie ma potrzeby konfigurowania czy ręcznego przełączania czegokolwiek. Trybem deweloperskim jest sytuacja, kiedy aplikacja jest uruchomiona na localhoście (czyli na adresie IP `127.0.0.1` lub `::1`) i nie ma proxy (czyli jego nagłówka HTTP). W przeciwnym razie działa w trybie produkcyjnym. - -Jeśli chcemy włączyć tryb deweloperski w innych przypadkach, takich jak programiści uzyskujący dostęp z określonego adresu IP, używamy `setDebugMode()`: - -```php -$configurator->setDebugMode('23.75.345.200'); // można również określić pole adresu IP -``` - -Zdecydowanie zalecamy połączenie adresu IP z plikiem cookie. W pliku cookie `nette-debug` przechowujemy tajny token, np. `secret1234`, i w ten sposób umożliwiamy tryb deweloperski dla programistów uzyskujących dostęp z określonego adresu IP i posiadających token w pliku cookie: - -```php -$configurator->setDebugMode('secret1234@23.75.345.200'); -``` - -Możemy również całkowicie wyłączyć tryb deweloperski, nawet dla localhost: - -```php -$configurator->setDebugMode(false); -``` - -Uwaga, wartość `true` domyślnie włącza tryb deweloperski, co nigdy nie może mieć miejsca na serwerze produkcyjnym. - - -Narzędzie do debugowania Tracy .[#toc-debugging-tool-tracy] -=========================================================== - -Aby ułatwić debugowanie, włączmy wspaniałe narzędzie [Tracy |tracy:]. Wizualizuje błędy w trybie deweloperskim i loguje błędy w trybie produkcyjnym do podanego katalogu: - -```php -$configurator->enableTracy($appDir . '/log'); -``` - - -Pliki tymczasowe .[#toc-temporary-files] -======================================== - -Nette używa buforowania dla kontenera DI, RobotLoader, szablonów itp. Dlatego musisz ustawić ścieżkę do katalogu, w którym będzie przechowywany cache: - -```php -$configurator->setTempDirectory($appDir . '/temp'); -``` - -W systemach Linux lub macOS ustaw katalogi `log/` i `temp/` na uprawnienia do [zapisu |nette:troubleshooting#Setting-Directory-Permissions]. - - -RobotLoader .[#toc-robotloader] -=============================== - -Zazwyczaj będziemy chcieli automatycznie załadować klasy za pomocą [RobotLoader |robot-loader:], więc musimy go uruchomić i kazać mu załadować klasy z katalogu, w którym znajduje się `Bootstrap.php` (czyli `__DIR__`), oraz z wszelkich podkatalogów: - -```php -$configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); -``` - -Alternatywnym podejściem jest pozwolić mu załadować klasy tylko poprzez [Composer |best-practices:composer], jednocześnie podążając za PSR-4. - - -Strefa czasowa .[#toc-timezone] -=============================== - -Domyślną strefę czasową można ustawić za pośrednictwem konfiguratora. - -```php -$configurator->setTimeZone('Europe/Prague'); -``` - - -Konfiguracja kontenera DI .[#toc-di-container-configuration] -============================================================ - -Częścią procesu uruchamiania jest stworzenie kontenera DI, czyli fabryki obiektów, która jest sercem aplikacji. Jest to właściwie klasa PHP, która jest generowana przez Nette i przechowywana w katalogu cache. Fabryka produkuje kluczowe obiekty aplikacji, a my za pomocą plików konfiguracyjnych instruujemy ją, jak ma je tworzyć i ustawiać, wpływając tym samym na zachowanie całej aplikacji. - -Pliki konfiguracyjne są zazwyczaj zapisane w formacie [NEON |neon:format]. Zobacz osobny rozdział, aby dowiedzieć się, co [można skonfigurować |nette:configuring]. - -.[tip] -W trybie deweloperskim kontener jest automatycznie aktualizowany przy każdej zmianie kodu lub plików konfiguracyjnych. W trybie produkcyjnym jest on generowany tylko raz, a zmiany nie są sprawdzane w celu uzyskania maksymalnej wydajności. - -Pliki konfiguracyjne są ładowane za pomocą `addConfig()`: - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -``` - -Jeśli chcemy dodać więcej plików konfiguracyjnych, możemy wywołać funkcję `addConfig()` wielokrotnie. - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -$configurator->addConfig($appDir . '/config/local.neon'); -if (PHP_SAPI === 'cli') { - $configurator->addConfig($appDir . '/config/cli.php'); -} -``` - -Nazwa `cli.php` nie jest literówką, konfiguracja może być również zapisana w pliku PHP, który zwraca ją jako tablicę. - -Możemy również dodać inne pliki konfiguracyjne w [sekcji `includes` |dependency-injection:configuration#including-files]. - -Jeśli w plikach konfiguracyjnych pojawią się elementy o takich samych kluczach, zostaną one nadpisane, lub [scalone |dependency-injection:configuration#merging] w przypadku pól. Plik dodany później ma wyższy priorytet niż poprzedni. Plik, w którym wymieniona jest sekcja `includes` ma wyższy priorytet niż pliki w niej zawarte. - - -Parametry statyczne .[#toc-static-parameters] ---------------------------------------------- - -Parametry wykorzystywane w plikach konfiguracyjnych można zdefiniować [w sekcji `parameters` |dependency-injection:configuration#parameters], a także przekazać (lub nadpisać) za pomocą metody `addStaticParameters()` (posiada ona alias `addParameters()`). Co ważne, różne wartości parametrów spowodują wygenerowanie dodatkowych kontenerów DI, czyli dodatkowych klas. - -```php -$configurator->addStaticParameters([ - 'projectId' => 23, -]); -``` - -Do parametru `projectId` można się odwołać w konfiguracji za pomocą zwykłej notacji `%projectId%`. Klasa Configurator automatycznie dodaje parametry `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` oraz `consoleMode`. - - -Parametry dynamiczne .[#toc-dynamic-parameters] ------------------------------------------------ - -Do kontenera możemy również dodać parametry dynamiczne, których różne wartości, w przeciwieństwie do parametrów statycznych, nie będą powodowały generowania nowych kontenerów DI. - -```php -$configurator->addDynamicParameters([ - 'remoteIp' => $_SERVER['REMOTE_ADDR'], -]); -``` - -Możemy po prostu dodać np. zmienne środowiskowe, do których następnie możemy się odwołać w konfiguracji pisząc `%env.variable%`. - -```php -$configurator->addDynamicParameters([ - 'env' => getenv(), -]); -``` - - -Usługi importowane .[#toc-imported-services] --------------------------------------------- - -Teraz wchodzimy głębiej. Chociaż punktem kontenera DI jest wytwarzanie obiektów, w rzadkich przypadkach może zaistnieć potrzeba wstawienia do kontenera istniejącego obiektu. Robimy to poprzez zdefiniowanie usługi z flagą `imported: true`. - -```neon -services: - myservice: - type: App\Model\MyCustomService - imported: true -``` - -A w bootstrapie wstawiamy obiekt do kontenera: - -```php -$configurator->addServices([ - 'myservice' => new App\Model\MyCustomService('foobar'), -]); -``` - - -Różne środowiska .[#toc-different-environments] -=============================================== - -Zapraszamy do dostosowania klasy Bootstrap do swoich potrzeb. Możesz dodać parametry do metody `boot()`, aby odróżnić projekty internetowe, lub dodać inne metody, takie jak `bootForTests()`, która inicjalizuje środowisko dla testów jednostkowych, `bootForCli()` dla skryptów wywoływanych z linii poleceń itp. - -```php -public static function bootForTests(): Configurator -{ - $configurator = self::boot(); - Tester\Environment::setup(); // inicializace Nette Testeru - return $configurator; -} -``` diff --git a/application/pl/bootstrapping.texy b/application/pl/bootstrapping.texy new file mode 100644 index 0000000000..4cc015bed0 --- /dev/null +++ b/application/pl/bootstrapping.texy @@ -0,0 +1,297 @@ +Bootstrapping +************* + +
    + +Bootstrapping to proces inicjalizacji środowiska aplikacji, tworzenia kontenera dependency injection (DI) i uruchamiania aplikacji. Omówimy: + +- jak klasa Bootstrap inicjalizuje środowisko +- jak aplikacje są konfigurowane przy użyciu plików NEON +- jak rozróżnić tryb produkcyjny i deweloperski +- jak utworzyć i skonfigurować kontener DI + +
    + + +Aplikacje, czy to webowe, czy skrypty uruchamiane z wiersza poleceń, rozpoczynają swoje działanie od pewnej formy inicjalizacji środowiska. W dawnych czasach odpowiadał za to plik o nazwie np. `include.inc.php`, który był dołączany przez plik początkowy. W nowoczesnych aplikacjach Nette zastąpiła go klasa `Bootstrap`, którą jako część aplikacji znajdziesz w pliku `app/Bootstrap.php`. Może wyglądać na przykład tak: + +```php +use Nette\Bootstrap\Configurator; + +class Bootstrap +{ + private Configurator $configurator; + private string $rootDir; + + public function __construct() + { + $this->rootDir = dirname(__DIR__); + // Konfigurator jest odpowiedzialny za ustawienie środowiska aplikacji i usług. + $this->configurator = new Configurator; + // Ustawia katalog dla plików tymczasowych generowanych przez Nette (np. skompilowane szablony) + $this->configurator->setTempDirectory($this->rootDir . '/temp'); + } + + public function bootWebApplication(): Nette\DI\Container + { + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); + } + + private function initializeEnvironment(): void + { + // Nette jest sprytne i tryb deweloperski włącza się automatycznie, + // lub możesz go włączyć dla konkretnego adresu IP, odkomentowując poniższą linię: + // $this->configurator->setDebugMode('secret@23.75.345.200'); + + // Aktywuje Tracy: ostateczny "szwajcarski scyzoryk" do debugowania. + $this->configurator->enableTracy($this->rootDir . '/log'); + + // RobotLoader: automatycznie ładuje wszystkie klasy w wybranym katalogu + $this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); + } + + private function setupContainer(): void + { + // Ładuje pliki konfiguracyjne + $this->configurator->addConfig($this->rootDir . '/config/common.neon'); + } +} +``` + + +index.php +========= + +Plikiem początkowym w przypadku aplikacji webowych jest `index.php`, który znajduje się w [katalogu publicznym |directory-structure#Katalog publiczny www] `www/`. Ten plik zleca klasie Bootstrap inicjalizację środowiska i utworzenie kontenera DI. Następnie pobiera z niego usługę `Application`, która uruchamia aplikację webową: + +```php +$bootstrap = new App\Bootstrap; +// Inicjalizacja środowiska + utworzenie kontenera DI +$container = $bootstrap->bootWebApplication(); +// Kontener DI tworzy obiekt Nette\Application\Application +$application = $container->getByType(Nette\Application\Application::class); +// Uruchomienie aplikacji Nette i przetworzenie przychodzącego żądania +$application->run(); +``` + +Jak widać, w ustawieniu środowiska i tworzeniu kontenera dependency injection (DI) pomaga klasa [api:Nette\Bootstrap\Configurator], którą teraz bliżej przedstawimy. + + +Tryb deweloperski vs produkcyjny +================================ + +Nette zachowuje się różnie w zależności od tego, czy działa na serwerze deweloperskim czy produkcyjnym: + +🛠️ Tryb deweloperski (Development): + - Wyświetla pasek debugowania Tracy z użytecznymi informacjami (zapytania SQL, czas wykonania, użyta pamięć) + - W przypadku błędu wyświetla szczegółową stronę błędu z wywołaniami funkcji i zawartością zmiennych + - Automatycznie odświeża cache przy zmianie szablonów Latte, modyfikacji plików konfiguracyjnych itp. + + +🚀 Tryb produkcyjny (Production): + - Nie wyświetla żadnych informacji debugowania, wszystkie błędy zapisuje do logu + - W przypadku błędu wyświetla ErrorPresenter lub ogólną stronę "Server Error" + - Cache nigdy nie jest automatycznie odświeżany! + - Zoptymalizowany pod kątem szybkości i bezpieczeństwa + + +Wybór trybu odbywa się przez autodetekcję, więc zazwyczaj nie trzeba niczego konfigurować ani ręcznie przełączać: + +- tryb deweloperski: na localhost (adres IP `127.0.0.1` lub `::1`) jeśli nie ma proxy (tj. jego nagłówka HTTP) +- tryb produkcyjny: wszędzie indziej + +Jeśli chcemy włączyć tryb deweloperski również w innych przypadkach, na przykład dla programistów łączących się z konkretnego adresu IP, użyjemy `setDebugMode()`: + +```php +$this->configurator->setDebugMode('23.75.345.200'); // można podać również tablicę adresów IP +``` + +Zdecydowanie zalecamy łączenie adresu IP z ciasteczkiem (cookie). W ciasteczku `nette-debug` zapisujemy tajny token, np. `secret1234`, i w ten sposób aktywujemy tryb deweloperski dla programistów łączących się z konkretnego adresu IP i jednocześnie posiadających w ciasteczku wspomniany token: + +```php +$this->configurator->setDebugMode('secret1234@23.75.345.200'); +``` + +Tryb deweloperski możemy również całkowicie wyłączyć, nawet dla localhost: + +```php +$this->configurator->setDebugMode(false); +``` + +Uwaga, wartość `true` włącza tryb deweloperski na stałe, co nigdy nie powinno mieć miejsca na serwerze produkcyjnym. + + +Narzędzie debugujące Tracy +========================== + +Dla łatwego debugowania włączymy jeszcze świetne narzędzie [Tracy |tracy:]. W trybie deweloperskim wizualizuje błędy, a w trybie produkcyjnym loguje błędy do podanego katalogu: + +```php +$this->configurator->enableTracy($this->rootDir . '/log'); +``` + + +Pliki tymczasowe +================ + +Nette wykorzystuje cache dla kontenera DI, RobotLoadera, szablonów itp. Dlatego konieczne jest ustawienie ścieżki do katalogu, w którym będzie przechowywany cache: + +```php +$this->configurator->setTempDirectory($this->rootDir . '/temp'); +``` + +Na Linuksie lub macOS ustaw katalogom `log/` i `temp/` [uprawnienia do zapisu |nette:troubleshooting#Ustawianie uprawnień do katalogów]. + + +RobotLoader +=========== + +Zazwyczaj będziemy chcieli automatycznie ładować klasy za pomocą [RobotLoadera |robot-loader:], musimy go więc uruchomić i pozwolić mu ładować klasy z katalogu, w którym znajduje się `Bootstrap.php` (tj. `__DIR__`), oraz wszystkich podkatalogów: + +```php +$this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); +``` + +Alternatywnym podejściem jest ładowanie klas wyłącznie przez [Composer |best-practices:composer] przy zachowaniu PSR-4. + + +Strefa czasowa +============== + +Za pomocą konfiguratora można ustawić domyślną strefę czasową. + +```php +$this->configurator->setTimeZone('Europe/Prague'); +``` + + +Konfiguracja kontenera DI +========================= + +Częścią procesu startowego jest utworzenie kontenera DI, czyli fabryki obiektów, która jest sercem całej aplikacji. Jest to właściwie klasa PHP, którą generuje Nette i zapisuje w katalogu z cache. Fabryka tworzy kluczowe obiekty aplikacji, a za pomocą plików konfiguracyjnych instruujemy ją, jak ma je tworzyć i ustawiać, co wpływa na zachowanie całej aplikacji. + +Pliki konfiguracyjne zazwyczaj zapisuje się w formacie [NEON |neon:format]. W osobnym rozdziale dowiesz się, [co można skonfigurować |nette:configuring]. + +.[tip] +W trybie deweloperskim kontener automatycznie aktualizuje się przy każdej zmianie kodu lub plików konfiguracyjnych. W trybie produkcyjnym generowany jest tylko raz, a zmiany nie są sprawdzane w celu maksymalizacji wydajności. + +Pliki konfiguracyjne ładujemy za pomocą `addConfig()`: + +```php +$this->configurator->addConfig($this->rootDir . '/config/common.neon'); +``` + +Jeśli chcemy dodać więcej plików konfiguracyjnych, możemy wywołać funkcję `addConfig()` wielokrotnie. + +```php +$configDir = $this->rootDir . '/config'; +$this->configurator->addConfig($configDir . '/common.neon'); +$this->configurator->addConfig($configDir . '/services.neon'); +if (PHP_SAPI === 'cli') { + $this->configurator->addConfig($configDir . '/cli.php'); +} +``` + +Nazwa `cli.php` nie jest pomyłką, konfiguracja może być zapisana również w pliku PHP, który zwraca ją jako tablicę. + +Możemy również dodać inne pliki konfiguracyjne w [sekcji `includes` |dependency-injection:configuration#Dołączanie plików]. + +Jeśli w plikach konfiguracyjnych pojawią się elementy o tych samych kluczach, zostaną one nadpisane lub w przypadku [tablic połączone |dependency-injection:configuration#Łączenie]. Później dołączony plik ma wyższy priorytet niż poprzedni. Plik, w którym znajduje się sekcja `includes`, ma wyższy priorytet niż pliki w nim zawarte. + + +Parametry statyczne +------------------- + +Parametry używane w plikach konfiguracyjnych możemy zdefiniować [w sekcji `parameters` |dependency-injection:configuration#Parametry], a także przekazywać (lub nadpisywać) metodą `addStaticParameters()` (ma alias `addParameters()`). Ważne jest, że różne wartości parametrów spowodują wygenerowanie kolejnych kontenerów DI, czyli kolejnych klas. + +```php +$this->configurator->addStaticParameters([ + 'projectId' => 23, +]); +``` + +Do parametru `projectId` można odwołać się w konfiguracji za pomocą zwykłego zapisu `%projectId%`. + + +Parametry dynamiczne +-------------------- + +Do kontenera możemy dodać również parametry dynamiczne, których różne wartości, w przeciwieństwie do parametrów statycznych, nie spowodują generowania nowych kontenerów DI. + +```php +$this->configurator->addDynamicParameters([ + 'remoteIp' => $_SERVER['REMOTE_ADDR'], +]); +``` + +W prosty sposób możemy dodać np. zmienne środowiskowe, do których można się następnie odwołać w konfiguracji za pomocą zapisu `%env.variable%`. + +```php +$this->configurator->addDynamicParameters([ + 'env' => getenv(), +]); +``` + + +Parametry domyślne +------------------ + +W plikach konfiguracyjnych można używać następujących parametrów statycznych: + +- `%appDir%` to ścieżka absolutna do katalogu z plikiem `Bootstrap.php` +- `%wwwDir%` to ścieżka absolutna do katalogu z plikiem wejściowym `index.php` +- `%tempDir%` to ścieżka absolutna do katalogu dla plików tymczasowych +- `%vendorDir%` to ścieżka absolutna do katalogu, w którym Composer instaluje biblioteki +- `%rootDir%` to ścieżka absolutna do katalogu głównego projektu +- `%debugMode%` wskazuje, czy aplikacja jest w trybie debugowania +- `%consoleMode%` wskazuje, czy żądanie przyszło przez wiersz poleceń + + +Usługi importowane +------------------ + +Teraz zagłębiamy się bardziej. Chociaż celem kontenera DI jest tworzenie obiektów, wyjątkowo może zaistnieć potrzeba wstawienia istniejącego obiektu do kontenera. Robimy to, definiując usługę z flagą `imported: true`. + +```neon +services: + myservice: + type: App\Model\MyCustomService + imported: true +``` + +A w bootstrapie wstawiamy obiekt do kontenera: + +```php +$this->configurator->addServices([ + 'myservice' => new App\Model\MyCustomService('foobar'), +]); +``` + + +Odmienne środowisko +=================== + +Nie bój się modyfikować klasy Bootstrap według własnych potrzeb. Do metody `bootWebApplication()` możesz dodać parametry do rozróżniania projektów webowych. Możemy też dodać inne metody, na przykład `bootTestEnvironment()`, która inicjalizuje środowisko dla testów jednostkowych, `bootConsoleApplication()` dla skryptów wywoływanych z wiersza poleceń itp. + +```php +public function bootTestEnvironment(): Nette\DI\Container +{ + Tester\Environment::setup(); // inicjalizacja Nette Testera + $this->setupContainer(); + return $this->configurator->createContainer(); +} + +public function bootConsoleApplication(): Nette\DI\Container +{ + $this->configurator->setDebugMode(false); + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); +} +``` diff --git a/application/pl/components.texy b/application/pl/components.texy index 94d619c41e..0ddc2e7e66 100644 --- a/application/pl/components.texy +++ b/application/pl/components.texy @@ -1,29 +1,29 @@ -Elementy interaktywne -********************* +Komponenty interaktywne +***********************
    -Komponenty to samodzielne obiekty wielokrotnego użytku, które wstawiamy do stron. Mogą to być formularze, datagridy, ankiety, w rzeczywistości wszystko, co ma sens, aby używać wielokrotnie. Zobaczmy: +Komponenty to samodzielne obiekty wielokrotnego użytku, które wstawiamy na strony. Mogą to być formularze, datagridy, ankiety, właściwie wszystko, co ma sens używać wielokrotnie. Pokażemy: - jak używać komponentów? -- jak je napisać? -- co to są sygnały? +- jak je pisać? +- czym są sygnały?
    -Nette posiada wbudowany system komponentów. Memordziści mogą znać coś podobnego z Delphi lub ASP.NET Web Forms, a React lub Vue.js jest zbudowany na czymś zdalnie podobnym. W świecie frameworków PHP jest to jednak ewenement. +Nette ma wbudowany system komponentów. Coś podobnego mogą pamiętać weterani z Delphi lub ASP.NET Web Forms, na czymś zdalnie podobnym opiera się React czy Vue.js. Jednak w świecie frameworków PHP jest to unikalna sprawa. -Jednak komponenty w zasadniczy sposób wpływają na podejście do tworzenia aplikacji. W rzeczywistości można komponować strony z gotowych jednostek. Czy potrzebujesz datagridu w swojej administracji? Można go znaleźć na [Componette |https://componette.org/search/component], repozytorium open-source'owych dodatków (nie tylko komponentów) dla Nette, i po prostu wstawić do prezentera. +Przy tym komponenty zasadniczo wpływają na podejście do tworzenia aplikacji. Możesz bowiem składać strony z gotowych jednostek. Potrzebujesz w administracji datagrid? Znajdziesz go na [Componette |https://componette.org/search/component], repozytorium open-source dodatków (czyli nie tylko komponentów) dla Nette i po prostu wstawisz do presentera. -W prezenterze można zawrzeć dowolną liczbę komponentów. A do niektórych komponentów można wstawić inne komponenty. Tworzy to drzewo komponentów z prezenterem jako korzeniem. +Do presentera możesz włączyć dowolną liczbę komponentów. A do niektórych komponentów możesz wstawiać kolejne komponenty. Powstaje w ten sposób drzewo komponentów, którego korzeniem jest presenter. -Metody fabryczne .[#toc-factory-methods] -======================================== +Metody fabrykujące +================== -Jak komponenty są wstawiane do prezentera, a następnie wykorzystywane? Zazwyczaj z wykorzystaniem metod fabrycznych. +Jak wstawiać komponenty do presentera i następnie ich używać? Zazwyczaj za pomocą metod fabrykujących. -Fabryka komponentów jest eleganckim sposobem tworzenia komponentów tylko wtedy, gdy są one rzeczywiście potrzebne (leniwe / na żądanie). Cała magia tkwi w implementacji metody o nazwie `createComponent()`gdzie `` jest nazwą tworzonego komponentu, który tworzy i zwraca komponent. +Fabryka komponentów stanowi elegancki sposób na tworzenie komponentów dopiero w chwili, gdy są rzeczywiście potrzebne (lazy / on demand). Cały urok polega na implementacji metody o nazwie `createComponent()`, gdzie `` to nazwa tworzonego komponentu, która tworzy i zwraca komponent. ```php .{file:DefaultPresenter.php} class DefaultPresenter extends Nette\Application\UI\Presenter @@ -37,43 +37,43 @@ class DefaultPresenter extends Nette\Application\UI\Presenter } ``` -Ponieważ wszystkie komponenty są tworzone w osobnych metodach, kod zyskuje na przejrzystości. +Dzięki temu, że wszystkie komponenty są tworzone w osobnych metodach, kod zyskuje na przejrzystości. .[note] -Nazwy komponentów zawsze zaczynają się od małej litery, nawet jeśli w nazwie metody są pisane wielką literą. +Nazwy komponentów zawsze zaczynają się małą literą, mimo że w nazwie metody pisane są z dużej. -Nigdy nie wywołujemy fabryk bezpośrednio, dzwonią one do siebie przy pierwszym użyciu komponentu. Dzięki temu komponent jest tworzony w odpowiednim czasie i tylko wtedy, gdy jest rzeczywiście potrzebny. Jeśli nie korzystamy z komponentu (np. podczas żądania AJAX, gdy przekazywana jest tylko część strony, lub podczas buforowania szablonu), nie jest on w ogóle tworzony i oszczędzamy wydajność serwera. +Fabryk nigdy nie wywołujemy bezpośrednio, wywołują się same w chwili, gdy komponent użyjemy po raz pierwszy. Dzięki temu komponent jest tworzony we właściwym momencie i tylko wtedy, gdy jest rzeczywiście potrzebny. Jeśli komponentu nie użyjemy (np. przy żądaniu AJAX, gdy przesyłana jest tylko część strony, lub przy cachowaniu szablonu), nie zostanie on w ogóle utworzony i oszczędzimy wydajność serwera. ```php .{file:DefaultPresenter.php} -// przechodzimy do komponentu i jeśli był to pierwszy raz, -// wywołaj funkcję createComponentPoll(), aby go stworzyć +// uzyskujemy dostęp do komponentu i jeśli to było po raz pierwszy, +// wywołuje się createComponentPoll(), która go tworzy $poll = $this->getComponent('poll'); // alternatywna składnia: $poll = $this['poll']; ``` -Możliwe jest renderowanie komponentu w szablonie za pomocą znacznika [{control} |#Rendering], dlatego nie ma potrzeby ręcznego przekazywania komponentów do szablonu. +W szablonie można wyrenderować komponent za pomocą znacznika [{control} |#Renderowanie]. Nie ma więc potrzeby ręcznego przekazywania komponentów do szablonu. ```latte -

    Please Vote

    +

    Głosuj

    {control poll} ``` -Styl hollywoodzki .[#toc-hollywood-style] -========================================= +Styl Hollywood +============== -Komponenty powszechnie wykorzystują jedną świeżą technikę, którą lubimy nazywać stylem hollywoodzkim. Na pewno znacie skrzydlate zdanie, które tak często słyszą osoby biorące udział w przesłuchaniach do filmów: "Nie dzwoń do nas, my zadzwonimy do ciebie". I właśnie o to chodzi. +Komponenty często używają jednej świeżej techniki, którą lubimy nazywać stylem Hollywood. Na pewno znasz skrzydlate zdanie, które tak często słyszą uczestnicy castingów filmowych: „Nie dzwońcie do nas, my zadzwonimy do was”. I właśnie o to chodzi. -Bo w Nette, zamiast konieczności ciągłego zadawania pytań ("czy formularz został przesłany?", "czy był ważny?" lub "czy użytkownik nacisnął ten przycisk?"), mówisz frameworkowi "kiedy to się stanie, wywołaj tę metodę" i pozostawiasz mu resztę pracy. Jeśli programujesz w JavaScript, jesteś zaznajomiony z tym stylem programowania. Piszesz funkcje, które są wywoływane w momencie wystąpienia zdarzenia. A język przekazuje im odpowiednie parametry. +W Nette bowiem, zamiast ciągle pytać („czy formularz został wysłany?”, „czy był poprawny?” lub „czy użytkownik nacisnął ten przycisk?”), mówisz frameworkowi „kiedy to się stanie, wywołaj tę metodę” i zostawiasz dalszą pracę jemu. Jeśli programujesz w JavaScript, ten styl programowania jest Ci dobrze znany. Piszesz funkcje, które są wywoływane, gdy nastąpi określone zdarzenie. A język przekazuje im odpowiednie parametry. -To całkowicie zmienia sposób myślenia o pisaniu aplikacji. Im więcej zadań możesz zostawić ramom, tym mniej pracy musisz wykonać. I tym mniej można pominąć, np. +To całkowicie zmienia spojrzenie na pisanie aplikacji. Im więcej zadań możesz zostawić frameworkowi, tym mniej masz pracy. I tym mniej możesz czegoś np. pominąć. -Pisanie komponentu .[#toc-how-to-write-a-component] -=================================================== +Pisanie komponentu +================== -Przez komponent zwykle rozumiemy potomka klasy [api:Nette\Application\UI\Control]. (Dokładniej byłoby użyć terminu "kontrole", ale "kontrole" mają zupełnie inne znaczenie w języku angielskim i "komponenty" przejęły je). Sam prezenter [api:Nette\Application\UI\Presenter] zresztą też jest potomkiem klasy `Control`. +Pod pojęciem komponent zazwyczaj rozumiemy potomka klasy [api:Nette\Application\UI\Control]. (Dokładniej byłoby więc używać terminu „controls”, ale „kontrolki” mają w języku polskim zupełnie inne znaczenie i raczej przyjęły się „komponenty”.) Sam presenter [api:Nette\Application\UI\Presenter] jest zresztą również potomkiem klasy `Control`. ```php .{file:PollControl.php} use Nette\Application\UI\Control; @@ -84,17 +84,17 @@ class PollControl extends Control ``` -Rendering .[#toc-rendering] -=========================== +Renderowanie +============ -Wiemy już, że znacznik `{control componentName}` służy do renderowania komponentu. W rzeczywistości wywołuje to metodę `render()` komponentu, w której wykonamy renderowanie. Mamy, podobnie jak w Presenterze, szablon [Latte |latte:] w zmiennej `$this->template`, do którego przekazujemy parametry. W przeciwieństwie do prezentera, musimy podać plik szablonu i zlecić jego renderowanie: +Już wiemy, że do renderowania komponentu używa się znacznika `{control componentName}`. Ten znacznik właściwie wywołuje metodę `render()` komponentu, w której dbamy o renderowanie. Do dyspozycji mamy, tak samo jak w presenterze, [szablon Latte|templates] w zmiennej `$this->template`, do której przekazujemy parametry. W przeciwieństwie do presentera musimy podać plik z szablonem i zlecić jego wyrenderowanie: ```php .{file:PollControl.php} public function render(): void { - // wstawiamy kilka parametrów do szablonu + // wstawiamy do szablonu jakieś parametry $this->template->param = $value; - // i renderować go + // i renderujemy go $this->template->render(__DIR__ . '/poll.latte'); } ``` @@ -112,7 +112,7 @@ public function render(int $id, string $message): void } ``` -Czasami komponent może składać się z kilku części, które chcemy renderować osobno. Dla każdego z nich tworzymy własną metodę renderowania, tutaj w przykładzie dla przykładu `renderPaginator()`: +Czasami komponent może składać się z kilku części, które chcemy renderować oddzielnie. Dla każdej z nich tworzymy własną metodę renderującą, tutaj w przykładzie np. `renderPaginator()`: ```php .{file:PollControl.php} public function renderPaginator(): void @@ -121,30 +121,30 @@ public function renderPaginator(): void } ``` -A następnie wywołaj go w szablonie za pomocą: +A w szablonie wywołujemy ją za pomocą: ```latte {control poll:paginator} ``` -Dla lepszego zrozumienia dobrze jest wiedzieć, jak przetłumaczyć ten tag na język PHP. +Dla lepszego zrozumienia warto wiedzieć, jak ten znacznik jest tłumaczony na PHP. ```latte {control poll} {control poll:paginator 123, 'hello'} ``` -tłumaczy się jako: +zostanie przetłumaczone jako: ```php $control->getComponent('poll')->render(); $control->getComponent('poll')->renderPaginator(123, 'hello'); ``` -Metoda `getComponent()` zwraca komponent `poll` i wywołuje metodę `render()`, lub `renderPaginator()` nad tym komponentem, jeśli w znaczniku po dwukropku określono inną metodę renderowania. +Metoda `getComponent()` zwraca komponent `poll` i na tym komponencie wywołuje metodę `render()`, lub `renderPaginator()`, jeśli inny sposób renderowania jest podany w znaczniku za dwukropkiem. .[caution] -Zauważ, że jeśli **`=>`** pojawia się gdziekolwiek w parametrach, wszystkie parametry zostaną zawinięte w tablicę i przekazane jako pierwszy argument: +Uwaga, jeśli gdziekolwiek w parametrach pojawi się **`=>`**, wszystkie parametry zostaną zapakowane do tablicy i przekazane jako pierwszy argument: ```latte {control poll, id: 123, message: 'hello'} @@ -162,28 +162,28 @@ Renderowanie podkomponentu: {control cartControl-someForm} ``` -tłumaczy się jako: +zostanie przetłumaczone jako: ```php $control->getComponent("cartControl-someForm")->render(); ``` -Komponenty, podobnie jak prezentery, przekazują automatycznie kilka przydatnych zmiennych do szablonów: +Komponenty, podobnie jak presentery, automatycznie przekazują do szablonów kilka użytecznych zmiennych: -- `$basePath` to bezwzględna ścieżka URL do katalogu głównego (np. `/eshop`) -- `$baseUrl` to bezwzględny adres URL do katalogu głównego (np. `http://localhost/eshop`) -- `$user` jest obiektem [reprezentującym użytkownika |security:authentication] -- `$presenter` jest obecnym prezenterem -- `$control` to aktualny składnik -- `$flashes` jest tablicą [komunikatów |#Flash-Messages] wysyłanych przez funkcję `flashMessage()` +- `$basePath` to absolutna ścieżka URL do katalogu głównego (np. `/eshop`) +- `$baseUrl` to absolutny URL do katalogu głównego (np. `http://localhost/eshop`) +- `$user` to obiekt [reprezentujący użytkownika |security:authentication] +- `$presenter` to aktualny presenter +- `$control` to aktualny komponent +- `$flashes` to tablica [wiadomości |#Wiadomości flash] wysłanych przez funkcję `flashMessage()` -Sygnał .[#toc-signal] -===================== +Sygnał +====== -Wiemy już, że nawigacja w aplikacji Nette polega na łączeniu lub przekierowaniu do par `Presenter:action`. Ale co jeśli chcemy wykonać akcję tylko na **bieżącej stronie**? Na przykład zmienić kolejność kolumn w tabeli; usunąć element; przełączyć tryb jasny/ciemny; przesłać formularz; głosować w ankiecie; itp. +Już wiemy, że nawigacja w aplikacji Nette polega na linkowaniu lub przekierowywaniu do par `Presenter:action`. Ale co, jeśli chcemy tylko wykonać akcję na **aktualnej stronie**? Na przykład zmienić sortowanie kolumn w tabeli; usunąć pozycję; przełączyć tryb jasny/ciemny; wysłać formularz; zagłosować w ankiecie; itp. -Tego typu żądania nazywane są sygnałami. I tak jak akcje, wywołują one metody `action()` lub `render()`, sygnały wywołują metody `handle()`. O ile pojęcie akcji (lub widoku) dotyczy wyłącznie prezenterów, o tyle sygnały odnoszą się do wszystkich komponentów. A zatem także prezenterów, gdyż `UI\Presenter` jest potomkiem `UI\Control`. +Ten rodzaj żądań nazywa się sygnałami. I podobnie jak akcje wywołują metody `action()` lub `render()`, sygnały wywołują metody `handle()`. Podczas gdy pojęcie akcji (lub widoku) jest związane wyłącznie z presenterami, sygnały dotyczą wszystkich komponentów. A więc także presenterów, ponieważ `UI\Presenter` jest potomkiem `UI\Control`. ```php public function handleClick(int $x, int $y): void @@ -192,36 +192,36 @@ public function handleClick(int $x, int $y): void } ``` -Link wywołujący sygnał tworzymy w zwykły sposób, czyli w szablonie za pomocą atrybutu `n:href` lub znacznika `{link}`, a w kodzie za pomocą metody `link()` Więcej informacji znajdziesz w rozdziale [Tworzenie linków URL |creating-links#Links-to-Signal]. +Link, który wywoła sygnał, tworzymy w zwykły sposób, czyli w szablonie za pomocą atrybutu `n:href` lub znacznika `{link}`, w kodzie za pomocą metody `link()`. Więcej w rozdziale [Tworzenie linków URL |creating-links#Linki do sygnału]. ```latte -click here +kliknij tutaj ``` -Sygnał jest zawsze wywoływany na bieżącym prezenterze i widoku, więc nie można go wywołać na innym prezenterze lub widoku. +Sygnał zawsze jest wywoływany na aktualnym presenterze i akcji, nie można go wywołać na innym presenterze lub innej akcji. -Tak więc sygnał powoduje przeładowanie strony dokładnie w taki sam sposób, jak oryginalne żądanie, ale wywołuje metodę obsługi sygnału z odpowiednimi parametrami. Jeśli metoda nie istnieje, rzucany jest wyjątek [api:Nette\Application\UI\BadSignalException], który jest wyświetlany użytkownikowi jako strona błędu 403 Forbidden. +Sygnał powoduje więc ponowne załadowanie strony dokładnie tak samo, jak przy pierwotnym żądaniu, tylko dodatkowo wywołuje metodę obsługującą sygnał z odpowiednimi parametrami. Jeśli metoda nie istnieje, rzucany jest wyjątek [api:Nette\Application\UI\BadSignalException], który użytkownikowi wyświetla się jako strona błędu 403 Forbidden. -Snippety i AJAX .[#toc-snippets-and-ajax] -========================================= +Snippety i AJAX +=============== -Snippety mogą przypominać nieco AJAX: handlery, które są wywoływane na bieżącej stronie. I masz rację, sygnały są rzeczywiście często wywoływane za pomocą AJAX-a, a następnie tylko zmienione części strony są przekazywane do przeglądarki. Albo tzw. snippety. Więcej informacji można znaleźć na [stronie poświęconej AJAX-owi |ajax]. +Sygnały mogą trochę przypominać AJAX: handlery, które są wywoływane na aktualnej stronie. I masz rację, sygnały naprawdę często są wywoływane za pomocą AJAXu, a następnie przesyłamy do przeglądarki tylko zmienione części strony. Czyli tzw. snippety. Więcej informacji znajdziesz na [stronie poświęconej AJAX |ajax]. -Wiadomości błyskowe .[#toc-flash-messages] -========================================== +Wiadomości flash +================ -Komponent posiada własne, niezależne od prezentera repozytorium wiadomości flash. Są to komunikaty, które np. informują o wyniku operacji. Ważną cechą wiadomości flash jest to, że są one dostępne w szablonie nawet po przekierowaniu. Nawet po ich wyświetleniu pozostaną na żywo przez kolejne 30 sekund - na przykład w przypadku, gdy użytkownik odświeży stronę z powodu błędu transmisji - więc komunikat nie zniknie natychmiast. +Komponent ma własny magazyn wiadomości flash, niezależny od presentera. Są to wiadomości, które np. informują o wyniku operacji. Ważną cechą wiadomości flash jest to, że są dostępne w szablonie nawet po przekierowaniu. Nawet po wyświetleniu pozostają aktywne przez kolejne 30 sekund – na przykład na wypadek, gdyby z powodu błędnego transferu użytkownik odświeżył stronę - wiadomość mu więc od razu nie zniknie. -Wysyłanie jest obsługiwane przez metodę [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Pierwszym parametrem jest tekst wiadomości lub obiekt `stdClass` reprezentujący wiadomość. Opcjonalnym drugim parametrem jest jego typ (błąd, ostrzeżenie, info, itd.). Metoda `flashMessage()` zwraca instancję wiadomości flash jako obiekt `stdClass`, do którego można dodać dodatkowe informacje. +Wysyłanie zapewnia metoda [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Pierwszym parametrem jest tekst wiadomości lub obiekt `stdClass` reprezentujący wiadomość. Opcjonalnym drugim parametrem jest jej typ (error, warning, info itp.). Metoda `flashMessage()` zwraca instancję wiadomości flash jako obiekt `stdClass`, do którego można dodawać kolejne informacje. ```php -$this->flashMessage('Item has been deleted.'); -$this->redirect(/* ... */); // i przekierować +$this->flashMessage('Pozycja została usunięta.'); +$this->redirect(/* ... */); // i przekierowujemy ``` -Do szablonu wiadomości te są dostępne w zmiennej `$flashes` jako obiekty `stdClass`, które zawierają właściwości `message` (tekst wiadomości), `type` (typ wiadomości) i mogą zawierać wspomniane już informacje o użytkowniku. Na przykład wyrenderujmy je w następujący sposób: +W szablonie te wiadomości są dostępne w zmiennej `$flashes` jako obiekty `stdClass`, które zawierają właściwości `message` (tekst wiadomości), `type` (typ wiadomości) i mogą zawierać już wspomniane informacje użytkownika. Wyrenderujemy je na przykład tak: ```latte {foreach $flashes as $flash} @@ -230,44 +230,66 @@ Do szablonu wiadomości te są dostępne w zmiennej `$flashes` jako obiekty `std ``` -Trwałe parametry .[#toc-persistent-parameters] -============================================== +Przekierowanie po sygnale +========================= + +Po przetworzeniu sygnału komponentu często następuje przekierowanie. Jest to podobna sytuacja jak w przypadku formularzy - po ich wysłaniu również przekierowujemy, aby przy odświeżeniu strony w przeglądarce nie doszło do ponownego wysłania danych. + +```php +$this->redirect('this') // przekierowuje na aktualny presenter i akcję +``` + +Ponieważ komponent jest elementem wielokrotnego użytku i zazwyczaj nie powinien mieć bezpośredniego powiązania z konkretnymi presenterami, metody `redirect()` i `link()` automatycznie interpretują parametr jako sygnał komponentu: + +```php +$this->redirect('click') // przekierowuje na sygnał 'click' tego samego komponentu +``` + +Jeśli potrzebujesz przekierować na inny presenter lub akcję, możesz to zrobić za pośrednictwem presentera: + +```php +$this->getPresenter()->redirect('Product:show'); // przekierowuje na inny presenter/akcję +``` + + +Parametry persistentne +====================== -Trwałe parametry są używane do utrzymania stanu w komponentach pomiędzy różnymi żądaniami. Ich wartość pozostaje taka sama nawet po kliknięciu linku. W przeciwieństwie do danych sesji, są one przekazywane w adresie URL. I są przekazywane automatycznie, łącznie z linkami utworzonymi w innych komponentach na tej samej stronie. +Parametry persistentne służą do utrzymywania stanu w komponentach między różnymi żądaniami. Ich wartość pozostaje taka sama nawet po kliknięciu na link. W przeciwieństwie do danych w sesji, są one przesyłane w URL. I to całkowicie automatycznie, w tym w linkach tworzonych w innych komponentach na tej samej stronie. -Na przykład, masz komponent stronicowania treści. Na stronie może być kilka takich komponentów. I chcesz, aby wszystkie komponenty pozostały na bieżącej stronie, gdy użytkownik kliknie na link. Dlatego czynimy numer strony (`page`) trwałym parametrem. +Masz np. komponent do paginacji treści. Takich komponentów może być na stronie kilka. I chcemy, aby po kliknięciu na link wszystkie komponenty pozostały na swojej aktualnej stronie. Dlatego z numeru strony (`page`) zrobimy parametr persistentny. -Tworzenie trwałych parametrów jest w Nette niezwykle proste. Wystarczy stworzyć właściwość publiczną i oznaczyć ją atrybutem: (poprzednio użyto `/** @persistent */` ) +Tworzenie parametru persistentnego w Nette jest niezwykle proste. Wystarczy utworzyć publiczną właściwość i oznaczyć ją atrybutem: (wcześniej używano `/** @persistent */`) ```php -use Nette\Application\Attributes\Persistent; // ta linia jest ważna +use Nette\Application\Attributes\Persistent; // ta linia jest ważna class PaginatingControl extends Control { #[Persistent] - public int $page = 1; // musi być publiczny + public int $page = 1; // musi być public } ``` -Zalecamy dołączenie typu danych (np. `int`) do właściwości, możesz także dołączyć wartość domyślną. Wartości parametrów mogą być [walidowane |#Validation of Persistent Parameters]. +Przy właściwości zalecamy podanie również typu danych (np. `int`) i można podać również wartość domyślną. Wartości parametrów można [walidować |#Walidacja parametrów persistentnych]. -Możesz zmienić wartość trwałego parametru podczas tworzenia linku: +Podczas tworzenia linku można zmienić wartość parametru persistentnego: ```latte -next +następna ``` -Można też go *resetować*, czyli usunąć z adresu URL. Przyjmie on wtedy swoją domyślną wartość: +Lub można go *zresetować*, tj. usunąć z URL. Wtedy przyjmie swoją wartość domyślną: ```latte -reset +resetuj ``` -Trwałe komponenty .[#toc-persistent-components] -=============================================== +Komponenty persistentne +======================= -Nie tylko parametry, ale także komponenty mogą być trwałe. Dla takiego komponentu, jego trwałe parametry są również przekazywane pomiędzy różnymi akcjami prezentera lub pomiędzy wieloma prezenterami. Oznaczamy trwałe komponenty poprzez adnotację klasy prezentera. Na przykład w ten sposób oznaczamy składniki `calendar` i `poll`: +Nie tylko parametry, ale także komponenty mogą być persistentne. W przypadku takiego komponentu jego parametry persistentne są przenoszone również między różnymi akcjami presentera lub między wieloma presenterami. Komponenty persistentne oznaczamy adnotacją przy klasie presentera. Na przykład tak oznaczymy komponenty `calendar` i `poll`: ```php /** @@ -278,9 +300,9 @@ class DefaultPresenter extends Nette\Application\UI\Presenter } ``` -Podkomponenty wewnątrz tych komponentów nie muszą być oznaczone, stają się również trwałe. +Podkomponentów wewnątrz tych komponentów nie trzeba oznaczać, staną się również persistentne. -W PHP 8 możesz również używać atrybutów do oznaczania trwałych komponentów: +W PHP 8 można również użyć atrybutów do oznaczenia komponentów persistentnych: ```php use Nette\Application\Attributes\Persistent; @@ -292,35 +314,35 @@ class DefaultPresenter extends Nette\Application\UI\Presenter ``` -Komponenty z zależnościami .[#toc-components-with-dependencies] -=============================================================== +Komponenty z zależnościami +========================== -Jak tworzyć komponenty z zależnościami, nie "brudząc" prezenterów, które będą z nich korzystać? Dzięki sprytnym funkcjom kontenera DI w Nette, podobnie jak w przypadku korzystania z tradycyjnych usług, możemy większość pracy pozostawić frameworkowi. +Jak tworzyć komponenty z zależnościami, nie „zaśmiecając” sobie presenterów, które będą ich używać? Dzięki sprytnym właściwościom kontenera DI w Nette, podobnie jak przy używaniu klasycznych usług, można pozostawić większość pracy frameworkowi. -Weźmy jako przykład komponent, który ma zależność od serwisu `PollFacade`: +Weźmy jako przykład komponent, który ma zależność od usługi `PollFacade`: ```php class PollControl extends Control { public function __construct( - private int $id, // Id ankiety, za pomocą której można tworzyć komponenty + private int $id, // Id ankiety dla której tworzymy komponent private PollFacade $facade, ) { } public function handleVote(int $voteId): void { - $this->facade->vote($id, $voteId); + $this->facade->vote($this->id, $voteId); // ... } } ``` -Gdybyśmy pisali klasyczny serwis, nie byłoby się czym przejmować. Kontener DI w niewidoczny sposób zająłby się przekazaniem wszystkich zależności. Ale zazwyczaj obsługujemy komponenty tworząc ich nową instancję bezpośrednio w prezenterze w [metodach fabrycznych |#Factory-Methods] `createComponent…()`. Ale przekazanie wszystkich zależności wszystkich komponentów do prezentera, aby następnie przekazać je do komponentów, jest uciążliwe. A ilość napisanego kodu... +Gdybyśmy pisali klasyczną usługę, nie byłoby problemu. O przekazanie wszystkich zależności zadbałby niewidocznie kontener DI. Jednak z komponentami zazwyczaj postępujemy tak, że ich nową instancję tworzymy bezpośrednio w presenterze w [metodach fabrykujących |#Metody fabrykujące] `createComponent…()`. Ale przekazywanie wszystkich zależności wszystkich komponentów do presentera, aby je następnie przekazać komponentom, jest uciążliwe. I tyle napisanego kodu… -Logicznym pytaniem jest, dlaczego po prostu nie zarejestrujemy komponentu jako klasycznej usługi, przekażemy go do prezentera, a następnie zwrócimy go w metodzie `createComponent…()`? Ale to podejście jest nieodpowiednie, ponieważ chcemy mieć możliwość wielokrotnego tworzenia komponentu. +Logicznym pytaniem jest, dlaczego po prostu nie zarejestrujemy komponentu jako klasycznej usługi, nie przekażemy go do presentera, a następnie w metodzie `createComponent…()` nie zwrócimy? Takie podejście jest jednak nieodpowiednie, ponieważ chcemy mieć możliwość tworzenia komponentu nawet wielokrotnie. -Poprawnym rozwiązaniem jest napisanie fabryki komponentu, czyli klasy, która tworzy za nas komponent: +Prawidłowym rozwiązaniem jest napisanie dla komponentu fabryki, czyli klasy, która nam komponent utworzy: ```php class PollControlFactory @@ -337,17 +359,17 @@ class PollControlFactory } ``` -W ten sposób rejestrujemy fabrykę w naszym kontenerze w konfiguracji: +Taką fabrykę zarejestrujemy w naszym kontenerze w konfiguracji: ```neon services: - PollControlFactory ``` -i w końcu użyć go w naszym prezenterze: +a na koniec użyjemy jej w naszym presenterze: ```php -class PollPresenter extends Nette\UI\Application\Presenter +class PollPresenter extends Nette\Application\UI\Presenter { public function __construct( private PollControlFactory $pollControlFactory, @@ -362,7 +384,7 @@ class PollPresenter extends Nette\UI\Application\Presenter } ``` -Świetną rzeczą jest to, że Nette DI potrafi [generować |dependency-injection:factory] takie proste fabryki, więc zamiast pisać cały kod, wystarczy napisać jego interfejs: +Świetne jest to, że Nette DI potrafi takie proste fabryki [generować |dependency-injection:factory], więc zamiast całego jej kodu wystarczy napisać tylko jej interfejs: ```php interface PollControlFactory @@ -371,21 +393,21 @@ interface PollControlFactory } ``` -I to jest właśnie to. Nette wewnętrznie implementuje ten interfejs i przekazuje go do prezentera, gdzie możemy go użyć. W magiczny sposób dodaje również do naszego komponentu parametr `$id` oraz instancję klasy `PollFacade`. +I to wszystko. Nette wewnętrznie zaimplementuje ten interfejs i przekaże go do presentera, gdzie już możemy go używać. Magicznie doda nam do naszego komponentu również parametr `$id` i instancję klasy `PollFacade`. -Komponenty w głębi .[#toc-advanced-use-of-components] -===================================================== +Komponenty dogłębnie +==================== -Komponenty w aplikacji Nette to części aplikacji internetowej wielokrotnego użytku, które osadzamy w stronach, co jest tematem tego rozdziału. Jakie dokładnie są możliwości takiego komponentu? +Komponenty w Nette Application stanowią części aplikacji internetowej wielokrotnego użytku, które wstawiamy na strony i którym poświęcony jest cały ten rozdział. Jakie dokładnie możliwości ma taki komponent? -1) Jest renderowalny w szablonie -2) wie, która część siebie ma być renderowana podczas [żądania AJAX |ajax#Invalidation] (snippety) -3) ma możliwość przechowywania swojego stanu w URL (trwałe parametry) -4) posiada zdolność do reagowania na działania (sygnały) użytkownika -5) tworzy strukturę hierarchiczną (gdzie korzeniem jest prezenter) +1) jest renderowalny w szablonie +2) wie, [którą swoją część |ajax#Snippety] ma wyrenderować przy żądaniu AJAX (snippety) +3) ma możliwość zapisywania swojego stanu w URL (parametry persistentne) +4) ma możliwość reagowania na akcje użytkownika (sygnały) +5) tworzy strukturę hierarchiczną (gdzie korzeniem jest presenter) -Każda z tych funkcji jest obsługiwana przez jedną z klas linii dziedziczenia. Renderingiem (1 + 2) zajmuje się [api:Nette\Application\UI\Control], włączaniem do [cyklu życia |presenters#life-cycle-of-presenter] (3, 4) klasa [api:Nette\Application\UI\Component], a tworzeniem struktury hierarchicznej (5) klasy [Container i Component |component-model:]. +Każdą z tych funkcji obsługuje któraś z klas linii dziedziczenia. Renderowanie (1 + 2) obsługuje [api:Nette\Application\UI\Control], włączenie do [cyklu życia |presenters#Cykl życia presentera] (3, 4) klasa [api:Nette\Application\UI\Component], a tworzenie struktury hierarchicznej (5) klasy [Container i Component |component-model:]. ``` Nette\ComponentModel\Component { IComponent } @@ -400,18 +422,18 @@ Nette\ComponentModel\Component { IComponent } ``` -Cykl życia komponentów .[#toc-zivotni-cyklus-componenty] --------------------------------------------------------- +Cykl życia komponentu +--------------------- -[* lifecycle-component.svg *] *** *Cykl życia składników* .<> +[* lifecycle-component.svg *] *** *Cykl życia komponentu* .<> -Walidacja stałych parametrów .[#toc-validation-of-persistent-parameters] ------------------------------------------------------------------------- +Walidacja parametrów persistentnych +----------------------------------- -Wartości [trwałych parametrów |#persistent parameters] otrzymanych z adresów URL są zapisywane do właściwości przez metodę `loadState()`. Sprawdza ona również, czy typ danych określony dla właściwości pasuje, w przeciwnym razie odpowie błędem 404 i strona nie zostanie wyświetlona. +Wartości [parametrów persistentnych |#Parametry persistentne] otrzymanych z URL zapisuje do właściwości metoda `loadState()`. Sprawdza ona również, czy odpowiada typ danych podany przy właściwości, w przeciwnym razie odpowiada błędem 404 i strona się nie wyświetla. -Nigdy ślepo nie ufaj trwałym parametrom, ponieważ mogą one zostać łatwo nadpisane przez użytkownika w adresie URL. Na przykład, w ten sposób sprawdzamy, czy numer strony `$this->page` jest większy niż 0. Dobrym sposobem na to jest nadpisanie metody `loadState()` wspomnianej powyżej: +Nigdy ślepo nie wierz parametrom persistentnym, ponieważ mogą być łatwo nadpisane przez użytkownika w URL. W ten sposób na przykład sprawdzimy, czy numer strony `$this->page` jest większy niż 0. Odpowiednią drogą jest nadpisanie wspomnianej metody `loadState()`: ```php class PaginatingControl extends Control @@ -421,8 +443,8 @@ class PaginatingControl extends Control public function loadState(array $params): void { - parent::loadState($params); // tutaj jest ustawione $this->page - // następuje sprawdzenie wartości użytkownika: + parent::loadState($params); // tutaj ustawia się $this->page + // następuje własna kontrola wartości: if ($this->page < 1) { $this->error(); } @@ -430,27 +452,27 @@ class PaginatingControl extends Control } ``` -Procesem przeciwnym, czyli pobieraniem wartości z persistent properites, zajmuje się metoda `saveState()`. +Proces odwrotny, czyli zebranie wartości z właściwości persistentnych, obsługuje metoda `saveState()`. -Sygnały w głąb .[#toc-signaly-do-hloubky] ------------------------------------------ +Sygnały dogłębnie +----------------- -Sygnał ten powoduje przeładowanie strony dokładnie tak samo jak oryginalne żądanie (z wyjątkiem wywołania przez AJAX) i wywołuje metodę `signalReceived($signal)`, której domyślna implementacja w klasie `Nette\Application\UI\Component` próbuje wywołać metodę składającą się ze słów `handle{signal}`. Dalsze przetwarzanie należy do obiektu. Obiekty dziedziczące po `Component` (czyli `Control` i `Presenter`) odpowiadają próbując wywołać metodę `handle{signal}` z odpowiednimi parametrami. +Sygnał powoduje ponowne załadowanie strony dokładnie tak samo, jak przy pierwotnym żądaniu (z wyjątkiem przypadku, gdy jest wywoływany przez AJAX) i wywołuje metodę `signalReceived($signal)`, której domyślna implementacja w klasie `Nette\Application\UI\Component` próbuje wywołać metodę złożoną ze słów `handle{signal}`. Dalsze przetwarzanie zależy od danego obiektu. Obiekty dziedziczące po `Component` (tj. `Control` i `Presenter`) reagują tak, że próbują wywołać metodę `handle{signal}` z odpowiednimi parametrami. -Innymi słowy, bierze definicję funkcji `handle{signal}` i wszystkie parametry, które przyszły z żądaniem, dołącza parametry z adresu URL do argumentów po nazwie i próbuje wywołać metodę. Na przykład wartość z parametru `id` w adresie URL jest przekazywana jako źródło `$id`, `something` z adresu URL jest przekazywany jako `$something` itd. A jeśli metoda nie istnieje, metoda `signalReceived` podnosi [wyjątek |api:Nette\Application\UI\BadSignalException]. +Innymi słowy: bierze się definicję funkcji `handle{signal}` i wszystkie parametry, które przyszły z żądaniem, a do argumentów według nazwy dopasowuje się parametry z URL i próbuje wywołać daną metodę. Np. jako parametr `$id` przekazuje się wartość z parametru `id` w URL, jako `$something` przekazuje się `something` z URL, itd. A jeśli metoda nie istnieje, metoda `signalReceived` rzuca [wyjątek |api:Nette\Application\UI\BadSignalException]. -Sygnał może odebrać każdy komponent, prezenter lub obiekt, który implementuje interfejs `SignalReceiver` i jest podłączony do drzewa komponentów. +Sygnał może odbierać dowolny komponent, presenter lub obiekt, który implementuje interfejs `SignalReceiver` i jest podłączony do drzewa komponentów. -Głównymi odbiorcami sygnałów będą `Presentery` oraz komponenty wizualne dziedziczące po `Control`. Sygnał ma służyć jako sygnał dla obiektu, że powinien coś zrobić - ankieta powinna zliczyć głos od użytkownika, blok wiadomości powinien się rozwinąć i wyświetlić dwa razy więcej wiadomości, formularz został przesłany i powinien przetworzyć dane, i tak dalej. +Głównymi odbiorcami sygnałów będą `Presentery` i komponenty wizualne dziedziczące po `Control`. Sygnał ma służyć jako znak dla obiektu, że ma coś zrobić – ankieta ma zliczyć głos od użytkownika, blok z nowościami ma się rozwinąć i wyświetlić dwa razy więcej nowości, formularz został wysłany i ma przetworzyć dane itp. -Tworzymy adres URL dla sygnału za pomocą metody [Component::link() |api:Nette\Application\UI\Component::link()]. Przekazujemy ciąg `{signal}!` jako parametr `$destination` oraz tablicę argumentów, które chcemy przekazać do sygnału jako `$args`. Sygnał jest zawsze wywoływany na bieżącym widoku z bieżącymi parametrami, parametry sygnału są po prostu dodawane. Dodatkowo na samym początku dodawany jest **parametr `?do`, który określa sygnał**. +URL dla sygnału tworzymy za pomocą metody [Component::link() |api:Nette\Application\UI\Component::link()]. Jako parametr `$destination` przekazujemy ciąg `{signal}!` a jako `$args` tablicę argumentów, które chcemy przekazać sygnałowi. Sygnał zawsze jest wywoływany na aktualnym presenterze i akcji z aktualnymi parametrami, parametry sygnału są tylko dodawane. Dodatkowo na początku dodawany jest **parametr `?do`, który określa sygnał**. -Jego format to albo `{signal}`, albo `{signalReceiver}-{signal}`. `{signalReceiver}` to nazwa komponentu w prezenterze. Dlatego w nazwie komponentu nie może być myślnika - służy on do oddzielenia nazwy komponentu od sygnału, ale możliwe jest osadzenie w ten sposób kilku komponentów. +Jego format to albo `{signal}`, albo `{signalReceiver}-{signal}`. `{signalReceiver}` to nazwa komponentu w presenterze. Dlatego w nazwie komponentu nie może być myślnika – używa się go do oddzielenia nazwy komponentu i sygnału, jednak można w ten sposób zagnieździć kilka komponentów. -Metoda [isSignalReceiver() |api:Nette\Application\UI\Presenter::isSignalReceiver()] sprawdza, czy komponent (pierwszy argument) jest odbiornikiem sygnału (drugi argument). Drugi argument może być pominięty - wtedy sprawdza, czy dany komponent jest odbiornikiem jakiegokolwiek sygnału. Drugi parametr może być `true`, aby sprawdzić, czy nie tylko określony komponent jest odbiornikiem, ale także dowolny z jego potomków. +Metoda [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] sprawdza, czy komponent (pierwszy argument) jest odbiorcą sygnału (drugi argument). Drugi argument możemy pominąć – wtedy sprawdza, czy komponent jest odbiorcą jakiegokolwiek sygnału. Jako drugi parametr można podać `true` i tym samym sprawdzić, czy odbiorcą jest nie tylko podany komponent, ale także którykolwiek jego potomek. -Na dowolnym etapie przed `handle{signal}` możemy wykonać sygnał ręcznie, wywołując metodę [processSignal() |api:Nette\Application\UI\Presenter::processSignal()], która zajmuje się obsługą sygnału - bierze komponent, który określił się jako odbiorca sygnału (jeśli nie określono odbiorcy sygnału, jest nim sam prezenter) i wysyła mu sygnał. +W dowolnej fazie poprzedzającej `handle{signal}` możemy wykonać sygnał ręcznie, wywołując metodę [processSignal()|api:Nette\Application\UI\Presenter::processSignal()], która zajmuje się obsługą sygnału – bierze komponent, który został określony jako odbiorca sygnału (jeśli nie jest określony odbiorca sygnału, jest to sam presenter) i wysyła mu sygnał. Przykład: @@ -460,4 +482,4 @@ if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, ' } ``` -Oznacza to, że sygnał jest przedwcześnie wykonany i nie zostanie ponownie wywołany. +Tym samym sygnał jest wykonany przedwcześnie i nie będzie już ponownie wywoływany. diff --git a/application/pl/configuration.texy b/application/pl/configuration.texy index 3209488156..01f37a6477 100644 --- a/application/pl/configuration.texy +++ b/application/pl/configuration.texy @@ -2,57 +2,70 @@ Konfiguracja aplikacji ********************** .[perex] -Przegląd opcji konfiguracyjnych dla Nette Applications. +Przegląd opcji konfiguracyjnych dla aplikacji Nette. -Aplikacja .[#toc-application] -============================= +Application +=========== ```neon application: - # pokazać panel "Nette Application" w Tracy BlueScreen? - debugger: ... # (bool) domyślnie jest true + # wyświetlić panel "Nette Application" w Tracy BlueScreen? + debugger: ... # (bool) domyślnie true - # czy error-presenter zostanie wywołany na błędzie? - catchExceptions: ... # (bool) domyślnie przyjmuje wartość true w trybie produkcyjnym + # czy przy błędzie będzie wywoływany error-presenter? + # ma efekt tylko w trybie deweloperskim + catchExceptions: ... # (bool) domyślnie true - # error-presenter name - errorPresenter: Error # (string) domyślnie 'Nette:Error' + # nazwa error-presentera + errorPresenter: Error # (string|array) domyślnie 'Nette:Error' - # definiuje zasady rozwiązywania nazwy prezentera do klasy + # definiuje aliasy dla presenterów i akcji + aliases: ... + + # definiuje reguły tłumaczenia nazwy presentera na klasę mapping: ... - # błędne linki nie generują ostrzeżenia? - # działa tylko w trybie deweloperskim - silentLinks: ... # (bool) domyślnie jest false + # nieprawidłowe linki nie generują ostrzeżeń? + # ma efekt tylko w trybie deweloperskim + silentLinks: ... # (bool) domyślnie false ``` -Ponieważ prezentery błędów nie są domyślnie wywoływane w trybie deweloperskim, a błąd jest wyświetlany tylko przez Tracy, zmiana wartości `catchExceptions` na `true` pozwala nam zweryfikować ich poprawną funkcjonalność w trakcie rozwoju. +Od wersji `nette/application` 3.2 można zdefiniować parę error-presenterów: -Opcja `silentLinks` określa, jak Nette zachowuje się w trybie rozwoju, gdy generowanie linków nie powiedzie się (np. z powodu braku prezentera itp.). Domyślna wartość `false` oznacza, że Nette rzuci błąd `E_USER_WARNING`. Ustawienie go na `true` spowoduje wyeliminowanie tego komunikatu o błędzie. W środowisku produkcyjnym, `E_USER_WARNING` jest zawsze podniesiony. Na to zachowanie można również wpłynąć poprzez ustawienie zmiennej prezentera [$invalidLinkMode |creating-links#Invalid-Links]. +```neon +application: + errorPresenter: + 4xx: Error4xx # dla wyjątku Nette\Application\BadRequestException + 5xx: Error5xx # dla pozostałych wyjątków +``` -[Odwzorowanie określa zasady |modules#Mapping], według których nazwa klasy jest wyprowadzana z nazwy prezentera. +Opcja `silentLinks` określa, jak Nette zachowa się w trybie deweloperskim, gdy generowanie linku nie powiodło się (np. dlatego, że presenter nie istnieje itp.). Domyślna wartość `false` oznacza, że Nette zgłosi błąd `E_USER_WARNING`. Ustawienie na `true` spowoduje stłumienie tego komunikatu błędu. W środowisku produkcyjnym `E_USER_WARNING` jest zawsze zgłaszany. To zachowanie możemy również kontrolować, ustawiając zmienną presentera [$invalidLinkMode |creating-links#Nieprawidłowe linki]. +[Aliasy upraszczają linkowanie |creating-links#Aliasy] do często używanych presenterów. -Automatyczna rejestracja prezenterów .[#toc-automatic-registration-of-presenters] ---------------------------------------------------------------------------------- +[Mapowanie definiuje reguły |directory-structure#Mapowanie presenterów], według których z nazwy presentera wyprowadzana jest nazwa klasy. -Nette automatycznie dodaje prezentery jako usługi do kontenera DI, co radykalnie przyspieszy ich tworzenie. Sposób śledzenia prezenterów przez Nette można skonfigurować: + +Automatyczna rejestracja presenterów +------------------------------------ + +Nette automatycznie dodaje presentery jako usługi do kontenera DI, co znacząco przyspiesza ich tworzenie. Sposób, w jaki Nette wyszukuje presentery, można skonfigurować: ```neon -zastosowanie: - # search presentery in Composer class map? - scanComposer: ... # (bool) domyślnie jest true +application: + # szukać presenterów w Composer class map? + scanComposer: ... # (bool) domyślnie true - # maska, która musi pasować do nazwy klasy i pliku - scanFilter: ... # (string) domyślnie '*Prezenter' + # maska, której musi odpowiadać nazwa klasy i pliku + scanFilter: ... # (string) domyślnie '*Presenter' - # w jakich katalogach szukać prezenterów? + # w których katalogach szukać presenterów? scanDirs: # (string[]|false) domyślnie '%appDir%' - - %vendorDir%/mymoduł + - %vendorDir%/mymodule ``` -Katalogi wymienione w `scanDirs` nie nadpisują domyślnej wartości `%appDir%`, ale dodają do niej, więc `scanDirs` będzie zawierał zarówno ścieżki `%appDir%` jak i `%vendorDir%/mymodule`. Jeśli chcielibyśmy pominąć domyślny katalog, używamy [wykrzyknika |dependency-injection:configuration#merging], aby nadpisać wartość: +Katalogi podane w `scanDirs` nie nadpisują wartości domyślnej `%appDir%`, ale uzupełniają ją, więc `scanDirs` będzie zawierać obie ścieżki `%appDir%` i `%vendorDir%/mymodule`. Jeśli chcielibyśmy pominąć katalog domyślny, użyjemy [wykrzyknika |dependency-injection:configuration#Łączenie], który nadpisze wartość: ```neon application: @@ -60,64 +73,73 @@ application: - %vendorDir%/mymodule ``` -Skanowanie katalogów można wyłączyć podając wartość false. Nie zaleca się całkowitego wyłączenia automatycznego dodawania prezenterów, w przeciwnym razie wydajność aplikacji ulegnie pogorszeniu. +Skanowanie katalogów można wyłączyć, podając wartość `false`. Nie zalecamy całkowitego wyłączania automatycznego dodawania presenterów, ponieważ w przeciwnym razie dojdzie do obniżenia wydajności aplikacji. -Szablony Latte .[#toc-latte] -============================ +Szablony Latte +============== -To ustawienie może być używane do globalnego wpływania na zachowanie Latte w komponentach i prezenterach. +Tym ustawieniem można globalnie wpłynąć na zachowanie Latte w komponentach i presenterach. ```neon latte: - # show Latte bar in Tracy Bar for main template (true) or all components (all)? - debugger: ... # (true|false|'all') domyślnie jest true + # wyświetlić panel Latte w Tracy Bar dla głównego szablonu (true) lub wszystkich komponentów (all)? + debugger: ... # (true|false|'all') domyślnie true + + # generuje szablony z nagłówkiem declare(strict_types=1) + strictTypes: ... # (bool) domyślnie false + + # włącza tryb [ścisłego parsera |latte:develop#striktní režim] + strictParsing: ... # (bool) domyślnie false + + # aktywuje [kontrolę wygenerowanego kodu |latte:develop#Kontrola vygenerovaného kódu] + phpLinter: ... # (string) domyślnie null - # generowanie szablonów z nagłówkami declare(strict_types=1) - strictTypes: ... # (bool) domyślnie jest false + # ustawia locale + locale: cs_CZ # (string) domyślnie null - # klasa obiektów $this->template - templateClass: AppMyTemplateClass # domyślnie jest Nette\Bridges\ApplicationLatte\DefaultTemplate + # klasa obiektu $this->template + templateClass: App\MyTemplateClass # domyślnie Nette\Bridges\ApplicationLatte\DefaultTemplate ``` -Jeśli używasz Latte w wersji 3, możesz dodać nowe [rozszerzenia |latte:creating-extension] za pomocą: +Jeśli używasz Latte w wersji 3, możesz dodawać nowe [rozszerzenia |latte:extending-latte#Latte Extension] za pomocą: ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -/--comment - - - +Jeśli używasz Latte w wersji 2, możesz rejestrować nowe tagi, podając nazwę klasy lub referencję do usługi. Domyślnie wywoływana jest metoda `install()`, ale można to zmienić, podając nazwę innej metody: +```neon +latte: + # rejestracja niestandardowych znaczników Latte + macros: + - App\MyLatteMacros::register # metoda statyczna, nazwa klasy lub callable + - @App\MyLatteMacrosFactory # usługa z metodą install() + - @App\MyLatteMacrosFactory::register # usługa z metodą register() + +services: + - App\MyLatteMacrosFactory +``` +Routing +======= - - - - -\-- - - -Routing .[#toc-routing] -======================= - -Ustawienia podstawowe: +Podstawowe ustawienia: ```neon routing: - # show routing bar in Tracy Bar? - debugger: ... # (bool) domyślnie jest true + # wyświetlić panel routingu w Tracy Bar? + debugger: ... # (bool) domyślnie true # serializuje router do kontenera DI - cache: ... # (bool) domyślnie jest false + cache: ... # (bool) domyślnie false ``` -Routing jest zwykle definiowany w klasie [RouterFactory |routing#Route-Collection]. Alternatywnie, routery mogą być również zdefiniowane w konfiguracji przy użyciu par `maska: akce`, ale ta metoda nie oferuje tak szerokiego spektrum ustawień: +Routing zazwyczaj definiujemy w klasie [RouterFactory |routing#Kolekcja tras]. Alternatywnie trasy można definiować również w konfiguracji za pomocą par `maska: akcja`, ale ten sposób nie oferuje tak szerokiej zmienności w ustawieniach: ```neon routing: @@ -127,8 +149,8 @@ routing: ``` -Stałe .[#toc-constants] -======================= +Stałe +===== Tworzenie stałych PHP. @@ -140,15 +162,30 @@ constants: Po uruchomieniu aplikacji zostanie utworzona stała `Foobar`. .[note] -Stałe nie powinny być używane jako zmienne dostępne globalnie. Użyj [zastrzyku zależności |dependency-injection:passing-dependencies], aby przekazać wartości do obiektów. +Stałe nie powinny służyć jako swego rodzaju globalnie dostępne zmienne. Do przekazywania wartości do obiektów wykorzystaj [dependency injection |dependency-injection:passing-dependencies]. PHP === -Ustawianie dyrektyw PHP. Przegląd wszystkich dyrektyw można znaleźć na stronie [php.net |https://www.php.net/manual/en/ini.list.php]. +Ustawienia dyrektyw PHP. Przegląd wszystkich dyrektyw znajdziesz na [php.net |https://www.php.net/manual/en/ini.list.php]. ```neon php: date.timezone: Europe/Prague ``` + + +Usługi DI +========= + +Te usługi są dodawane do kontenera DI: + +| Nazwa | Typ | Opis +|---------------------------------------------------------- +| `application.application` | [api:Nette\Application\Application] | [uruchamiacz całej aplikacji |how-it-works#Nette Application] +| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] +| `application.presenterFactory` | [api:Nette\Application\PresenterFactory] | fabryka presenterów +| `application.###` | [api:Nette\Application\UI\Presenter] | poszczególne presentery +| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | fabryka obiektu `Latte\Engine` +| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | fabryka dla [`$this->template` |templates] diff --git a/application/pl/creating-links.texy b/application/pl/creating-links.texy index 5c9126dba0..40b1445c2c 100644 --- a/application/pl/creating-links.texy +++ b/application/pl/creating-links.texy @@ -3,158 +3,158 @@ Tworzenie linków URL
    -Tworzenie linków w Nette jest tak proste jak wskazanie palcem. Wystarczy wskazać, a framework wykona całą pracę za Ciebie. Zobaczmy: +Tworzenie linków w Nette jest proste jak wskazywanie palcem. Wystarczy tylko wycelować, a framework już za Ciebie wykona całą pracę. Pokażemy: -- jak tworzyć linki w szablonach i innych miejscach -- jak wyróżnić link do bieżącej strony -- co zrobić z nieważnymi linkami +- jak tworzyć linki w szablonach i gdzie indziej +- jak odróżnić link do aktualnej strony +- co z nieprawidłowymi linkami
    -Dzięki [dwukierunkowemu routingowi |routing] nigdy nie będziesz musiał pisać adresów URL aplikacji w szablonach lub kodzie, który może się zmienić lub być skomplikowany do skomponowania później. Wystarczy określić prezentera i akcję w linku, przekazać dowolne parametry, a framework sam wygeneruje adres URL. W rzeczywistości jest to bardzo podobne do wywołania funkcji. Spodoba ci się to. +Dzięki [dwukierunkowemu routingowi |routing] nigdy nie będziesz musiał w szablonach czy kodzie zapisywać na sztywno adresów URL Twojej aplikacji, które mogą się później zmienić, lub skomplikowanie je składać. W linku wystarczy podać presenter i akcję, przekazać ewentualne parametry, a framework już sam wygeneruje URL. Właściwie jest to bardzo podobne do wywoływania funkcji. Spodoba Ci się to. -W szablonie prezentera .[#toc-in-the-presenter-template] -======================================================== +W szablonie presentera +====================== -Najczęściej tworzymy linki w szablonach i dużą pomocą jest atrybut `n:href`: +Najczęściej tworzymy linki w szablonach, a świetnym pomocnikiem jest atrybut `n:href`: ```latte -detail +szczegóły ``` -Zauważ, że zamiast atrybutu HTML `href` użyliśmy atrybutu [n: |latte:syntax#n-attributes] `n:href`. Jego wartością nie jest wtedy adres URL, jak miałoby to miejsce w przypadku atrybutu `href`, ale nazwa prezentera i akcja. +Zauważ, że zamiast atrybutu HTML `href` użyliśmy [n:atrybutu |latte:syntax#n:atrybuty] `n:href`. Jego wartością nie jest URL, jak by to było w przypadku atrybutu `href`, ale nazwa presentera i akcji. -Kliknięcie na link jest, najprościej mówiąc, jak wywołanie metody `ProductPresenter::renderShow()`. A jeśli ma parametry w swoim podpisie, możemy go wywołać z argumentami: +Kliknięcie na link jest, upraszczając, czymś w rodzaju wywołania metody `ProductPresenter::renderShow()`. A jeśli ma w swojej sygnaturze parametry, możemy ją wywołać z argumentami: ```latte -detail produktu +szczegóły produktu ``` -Możliwe jest również przekazywanie nazwanych parametrów. Poniższy link przekazuje parametr `lang` z wartością `cs`: +Możliwe jest również przekazywanie parametrów nazwanych. Poniższy link przekazuje parametr `lang` o wartości `cs`: ```latte -detail produktu +szczegóły produktu ``` -Jeśli metoda `ProductPresenter::renderShow()` nie ma `$lang` w swojej sygnaturze, może pobrać wartość parametru za pomocą `$lang = $this->getParameter('lang')`. +Jeśli metoda `ProductPresenter::renderShow()` nie ma `$lang` w swojej sygnaturze, może uzyskać wartość parametru za pomocą `$lang = $this->getParameter('lang')` lub z [właściwości |presenters#Parametry żądania]. -Jeśli parametry są przechowywane w tablicy, można je rozwinąć za pomocą operatora `...` (w Latte 2.x operator `(expand)`): +Jeśli parametry są przechowywane w tablicy, można je rozwinąć operatorem `...` (w Latte 2.x operatorem `(expand)`): ```latte {var $args = [$product->id, lang => cs]} -detail produktu +szczegóły produktu ``` -Tak zwane [trwałe parametry |presenters#Persistent-Parameters] są również automatycznie przekazywane w referencjach. +W linkach automatycznie przekazywane są również tzw. [parametry persistentne |presenters#Parametry trwałe]. -Atrybut `n:href` jest bardzo przydatny dla znaczników HTML ``. Jeśli chcemy wymienić link w innym miejscu, na przykład w tekście, używamy `{link}`: +Atrybut `n:href` jest bardzo przydatny dla znaczników HTML ``. Jeśli chcemy wypisać link gdzie indziej, na przykład w tekście, użyjemy `{link}`: ```latte -Adresa je: {link Home:default} +Adres to: {link Home:default} ``` -W kodzie .[#toc-in-the-code] -============================ +W kodzie +======== -Metoda `link()` służy do tworzenia linku w prezenterze: +Do tworzenia linku w presenterze służy metoda `link()`: ```php $url = $this->link('Product:show', $product->id); ``` -Parametry mogą być również przekazywane za pomocą tablicy, gdzie można również określić parametry nazwane: +Parametry można przekazać również za pomocą tablicy, gdzie można podać również parametry nazwane: ```php $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); ``` -Linki można tworzyć bez prezentera, do tego służy [LinkGenerator |#LinkGenerator] i jego metoda `link()`. +Linki można tworzyć również bez presentera, do tego służy [#LinkGenerator] i jego metoda `link()`. -Linki do prezentera .[#toc-links-to-presenter] -============================================== +Linki do presentera +=================== -Jeśli celem linku jest prezenter i akcja, to ma on taką składnię: +Jeśli celem linku jest presenter i akcja, ma on następującą składnię: ``` [//] [[[[:]module:]presenter:]action | this] [#fragment] ``` -Format jest obsługiwany przez wszystkie znaczniki Latte oraz wszystkie metody prezentera, które pracują z linkami, czyli `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` a także [LinkGenerator |#LinkGenerator]. Więc nawet jeśli `n:href` jest używany w przykładach, każda z funkcji może tam być. +Format ten obsługują wszystkie znaczniki Latte i wszystkie metody presentera, które pracują z linkami, czyli `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` oraz [#LinkGenerator]. Więc nawet jeśli w przykładach użyto `n:href`, mogłaby tam być dowolna z tych funkcji. Podstawową formą jest więc `Presenter:action`: ```latte -úvodní stránka +strona główna ``` -Jeśli odnosimy się do działania bieżącego prezentera, możemy pominąć nazwę prezentera: +Jeśli linkujemy do akcji aktualnego presentera, możemy pominąć jego nazwę: ```latte -úvodní stránka +strona główna ``` -Jeśli celem działania jest `default`, możemy go pominąć, ale dwukropek musi pozostać: +Jeśli celem jest akcja `default`, możemy ją pominąć, ale dwukropek musi pozostać: ```latte -úvodní stránka +strona główna ``` -Linki mogą również wskazywać na inne [moduły |modules]. Tutaj linki są rozróżniane jako względne do zagnieżdżonego podmodułu lub bezwzględne. Zasada działania jest analogiczna do ścieżek dyskowych, ale z dwukropkami zamiast ukośników. Załóżmy, że aktualny prezenter jest częścią modułu `Front`, wtedy piszemy: +Linki mogą również prowadzić do innych [modułów |directory-structure#Presentery i szablony]. Tutaj linki dzielą się na względne do zagnieżdżonego podmodułu lub absolutne. Zasada jest analogiczna do ścieżek na dysku, tylko zamiast ukośników są dwukropki. Załóżmy, że aktualny presenter jest częścią modułu `Front`, wtedy zapiszemy: ```latte -odkaz na Front:Shop:Product:show -odkaz na Admin:Product:show +link do Front:Shop:Product:show +link do Admin:Product:show ``` -Szczególnym przypadkiem jest [autoreferencja |#Link-to-Current-Page], w której jako miejsce docelowe podajemy `this`. +Specjalnym przypadkiem jest link [do siebie samego |#Link do aktualnej strony], gdy jako cel podamy `this`. ```latte -refresh +odśwież ``` -Możemy linkować do określonej części strony poprzez fragment po znaku siatki `#`: +Możemy linkować do określonej części strony za pomocą tzw. fragmentu za znakiem kratki `#`: ```latte -odkaz na Home:default a fragment #main +link do Home:default i fragmentu #main ``` -Ścieżki bezwzględne .[#toc-absolute-paths] -========================================== +Ścieżki absolutne +================= -Linki generowane przez `link()` lub `n:href` są zawsze ścieżkami bezwzględnymi (tj. zaczynają się od `/`), ale nie bezwzględnymi adresami URL z protokołem i domeną jak `https://domain`. +Linki generowane za pomocą `link()` lub `n:href` są zawsze ścieżkami absolutnymi (tj. zaczynają się znakiem `/`), ale nie są absolutnymi URL z protokołem i domeną, jak `https://domain`. -Aby wygenerować bezwzględny adres URL, dodaj dwa ukośniki na początku (np. `n:href="//Home:"`). Możesz też przełączyć prezenter, aby generował tylko bezwzględne linki, ustawiając `$this->absoluteUrls = true`. +Aby wygenerować absolutny URL, dodaj na początku dwie ukośniki (np. `n:href="//Home:"`). Lub można przełączyć presenter, aby generował tylko linki absolutne, ustawiając `$this->absoluteUrls = true`. -Link do bieżącej strony .[#toc-link-to-current-page] -==================================================== +Link do aktualnej strony +======================== -Cel `this` tworzy link do bieżącej strony: +Cel `this` tworzy link do aktualnej strony: ```latte -refresh +odśwież ``` -Wszystkie parametry określone w podpisie są również przekazywane `render()` lub `action()` metody. Jeśli więc jesteśmy na `Product:show` i `id: 123`, to link do `this` również przekaże ten parametr. +Jednocześnie przekazywane są wszystkie parametry podane w sygnaturze metody `action()` lub `render()`, jeśli `action()` nie jest zdefiniowana. Więc jeśli jesteśmy na stronie `Product:show` i `id: 123`, link do `this` przekaże również ten parametr. -Oczywiście istnieje możliwość bezpośredniego podania parametrów: +Oczywiście można parametry specyfikować bezpośrednio: ```latte -refresh +odśwież ``` -Funkcja `isLinkCurrent()` sprawdza, czy miejsce docelowe linku jest takie samo jak bieżąca strona. Można to wykorzystać np. w szablonie do wyróżnienia linków itp. +Funkcja `isLinkCurrent()` sprawdza, czy cel linku jest zgodny z aktualną stroną. Można tego użyć na przykład w szablonie do odróżnienia linków itp. -Parametry są takie same jak w przypadku metody `link()`, ale dodatkowo można zamiast konkretnej akcji podać symbol wieloznaczny `*`, aby wskazać dowolną akcję prezentera. +Parametry są takie same jak w metodzie `link()`, dodatkowo jednak można zamiast konkretnej akcji podać symbol wieloznaczny `*`, który oznacza dowolną akcję danego presentera. ```latte {if !isLinkCurrent('Admin:login')} - Přihlaste se + Zaloguj się {/if}
  • @@ -162,15 +162,15 @@ Parametry są takie same jak w przypadku metody `link()`, ale dodatkowo można z
  • ``` -Forma krótka może być stosowana w połączeniu z `n:href` w jednym elemencie: +W połączeniu z `n:href` w jednym elemencie można użyć skróconej formy: ```latte ... ``` -Symbol wieloznaczny `*` może być użyty tylko w miejsce akcji, nie prezentera. +Symbol wieloznaczny `*` można użyć tylko zamiast akcji, a nie presentera. -Aby określić, czy jesteśmy w module lub jego podmodule, używamy metody `isModuleCurrent(moduleName)`. +Aby sprawdzić, czy jesteśmy w określonym module lub jego podmodule, użyjemy metody `isModuleCurrent(moduleName)`. ```latte
  • @@ -179,41 +179,41 @@ Aby określić, czy jesteśmy w module lub jego podmodule, używamy metody `isMo ``` -Odniesienia sygnałów .[#toc-links-to-signal] -============================================ +Linki do sygnału +================ -Celem linku może być nie tylko prezenter i akcja, ale także [sygnał |components#Signal] (wywołują metodę `handle()`). Wtedy składnia jest następująca: +Celem linku nie musi być tylko presenter i akcja, ale także [sygnał |components#Sygnał] (wywołują metodę `handle()`). Wtedy składnia jest następująca: ``` [//] [sub-component:]signal! [#fragment] ``` -Sygnał ten jest więc wyróżniony wykrzyknikiem: +Sygnał odróżnia więc wykrzyknik: ```latte -signal +sygnał ``` -Można również utworzyć odniesienie do sygnału podrzędnego (lub podrzędnego): +Można również utworzyć link do sygnału podkomponentu (lub pod-podkomponentu): ```latte -signal +sygnał ``` -Odniesienia w komponencie .[#toc-links-in-component] -==================================================== +Linki w komponencie +=================== -Ponieważ [komponenty |components] są samodzielnymi, wielokrotnego użytku jednostkami, które nie powinny mieć żadnych powiązań z otaczającymi prezenterami, linki działają tutaj nieco inaczej. Atrybut Latte `n:href` oraz znacznik `{link}`, a także metody składowe, takie jak `link()` i inne, traktują cel łącza **zawsze jako nazwę sygnału**. Dlatego nie ma nawet potrzeby umieszczania wykrzyknika: +Ponieważ [komponenty|components] są samodzielnymi jednostkami wielokrotnego użytku, które nie powinny mieć żadnych powiązań z otaczającymi presenterami, linki działają tu trochę inaczej. Atrybut Latte `n:href` i znacznik `{link}` oraz metody komponentu takie jak `link()` i inne uważają cel linku **zawsze za nazwę sygnału**. Dlatego nie jest nawet konieczne podawanie wykrzyknika: ```latte -signál, nikoliv akce +sygnał, a nie akcja ``` -Gdybyśmy chcieli odwołać się do prezenterów w szablonie komponentu, użylibyśmy do tego celu tagu `{plink}`: +Jeśli chcielibyśmy w szablonie komponentu linkować do presenterów, użyjemy do tego znacznika `{plink}`: ```latte -úvod +główna ``` lub w kodzie @@ -223,17 +223,41 @@ $this->getPresenter()->link('Home:default') ``` -Nieprawidłowe linki .[#toc-invalid-links] -========================================= +Aliasy .{data-version:v3.2.2} +============================= -Może się zdarzyć, że utworzymy nieprawidłowy link - albo dlatego, że prowadzi do nieistniejącego prezentera, albo dlatego, że przekazuje więcej parametrów niż docelowa metoda akceptuje w swojej sygnaturze, albo gdy nie można wygenerować adresu URL dla docelowej akcji. O tym, jak obsłużyć nieprawidłowe linki, decyduje zmienna statyczna `Presenter::$invalidLinkMode`. Może przyjmować kombinację następujących wartości (stałych): +Czasami może się przydać przypisanie parze Presenter:akcja łatwo zapamiętywalnego aliasu. Na przykład stronę główną `Front:Home:default` nazwać po prostu `home` lub `Admin:Dashboard:default` jako `admin`. -- `Presenter::InvalidLinkSilent` - tryb cichy, adres URL zwróci znak #. -- `Presenter::InvalidLinkWarning` - wyświetlane jest ostrzeżenie E_USER_WARNING, które będzie rejestrowane w trybie produkcyjnym, ale nie spowoduje przerwania skryptu -- `Presenter::InvalidLinkTextual` - ostrzeżenie wizualne, drukuje błąd bezpośrednio do linku -- `Presenter::InvalidLinkException` - rzuca wyjątek InvalidLinkException +Aliasy definiuje się w [konfiguracji|configuration] pod kluczem `application › aliases`: -Domyślnie jest to `InvalidLinkWarning` w trybie produkcyjnym i `InvalidLinkWarning | InvalidLinkTextual` w trybie deweloperskim. `InvalidLinkWarning` w trybie produkcyjnym nie spowoduje przerwania skryptu, ale ostrzeżenie zostanie zapisane w dzienniku. W środowisku programistycznym [Tracy |tracy:] złapie to i wyświetli bluescreen. `InvalidLinkTextual` działa poprzez zwrócenie komunikatu o błędzie, który zaczyna się od `#error:` jako adresu URL. Aby takie linki były widoczne na pierwszy rzut oka, dodamy trochę CSS: +```neon +application: + aliases: + home: Front:Home:default + admin: Admin:Dashboard:default + sign: Front:Sign:in +``` + +W linkach zapisuje się je za pomocą znaku @, na przykład: + +```latte +administracja +``` + +Są one obsługiwane również we wszystkich metodach pracujących z linkami, takich jak `redirect()` i podobnych. + + +Nieprawidłowe linki +=================== + +Może się zdarzyć, że utworzymy nieprawidłowy link - albo dlatego, że prowadzi do nieistniejącego presentera, albo dlatego, że przekazuje więcej parametrów, niż akceptuje metoda docelowa w swojej sygnaturze, albo gdy dla akcji docelowej nie można wygenerować URL. Sposób postępowania z nieprawidłowymi linkami określa zmienna statyczna `Presenter::$invalidLinkMode`. Może ona przyjmować kombinację tych wartości (stałych): + +- `Presenter::InvalidLinkSilent` - tryb cichy, jako URL zwracany jest znak # +- `Presenter::InvalidLinkWarning` - zgłaszane jest ostrzeżenie E_USER_WARNING, które w trybie produkcyjnym zostanie zalogowane, ale nie spowoduje przerwania działania skryptu +- `Presenter::InvalidLinkTextual` - ostrzeżenie wizualne, wypisuje błąd bezpośrednio w linku +- `Presenter::InvalidLinkException` - rzucany jest wyjątek InvalidLinkException + +Domyślne ustawienie to `InvalidLinkWarning` w trybie produkcyjnym i `InvalidLinkWarning | InvalidLinkTextual` w trybie deweloperskim. `InvalidLinkWarning` w środowisku produkcyjnym nie powoduje przerwania skryptu, ale ostrzeżenie zostanie zalogowane. W środowisku deweloperskim zostanie przechwycone przez [Tracy |tracy:] i wyświetli bluescreen. `InvalidLinkTextual` działa tak, że jako URL zwraca komunikat błędu zaczynający się od znaków `#error:`. Aby takie linki były od razu widoczne, dodajmy do CSS: ```css a[href^="#error:"] { @@ -242,7 +266,7 @@ a[href^="#error:"] { } ``` -Jeśli nie chcemy, aby środowisko programistyczne generowało ostrzeżenia, możemy ustawić tryb silent bezpośrednio w [konfiguracji |configuration]. +Jeśli nie chcemy, aby w środowisku deweloperskim generowane były ostrzeżenia, możemy ustawić tryb cichy bezpośrednio w [konfiguracji|configuration]. ```neon application: @@ -250,13 +274,13 @@ application: ``` -LinkGenerator .[#toc-linkgenerator] -=================================== +LinkGenerator +============= -Jak tworzyć linki z podobną wygodą jak metoda `link()`, ale bez obecności prezentera? Po to właśnie jest [api:Nette\Application\LinkGenerator]. +Jak tworzyć linki z podobnym komfortem jak metoda `link()`, ale bez obecności presentera? Do tego służy [api:Nette\Application\LinkGenerator]. -LinkGenerator to usługa, którą można mieć przekazaną przez konstruktor, a następnie tworzyć linki za pomocą jego metody `link()`. +LinkGenerator to usługa, którą można sobie przekazać przez konstruktor, a następnie tworzyć linki za pomocą jej metody `link()`. -W porównaniu z prezenterami jest różnica. LinkGenerator tworzy wszystkie linki bezpośrednio jako bezwzględne adresy URL. Nie ma również "rzeczywistego prezentera", więc nie możesz po prostu podać nazwy akcji `link('default')` jako celu lub podać względnych ścieżek do [modułów |modules]. +W porównaniu do presenterów jest tu różnica. LinkGenerator tworzy wszystkie linki od razu jako absolutne URL. Ponadto nie istnieje żaden "aktualny presenter", więc nie można jako celu podać tylko nazwy akcji `link('default')` ani podawać ścieżek względnych do modułów. -Nieważne linki zawsze wyrzucają `Nette\Application\UI\InvalidLinkException`. +Nieprawidłowe linki zawsze rzucają `Nette\Application\UI\InvalidLinkException`. diff --git a/application/pl/directory-structure.texy b/application/pl/directory-structure.texy new file mode 100644 index 0000000000..f3a84e2f67 --- /dev/null +++ b/application/pl/directory-structure.texy @@ -0,0 +1,526 @@ +Struktura katalogów aplikacji +***************************** + +
    + +Jak zaprojektować przejrzystą i skalowalną strukturę katalogów dla projektów w Nette Framework? Pokażemy sprawdzone praktyki, które pomogą w organizacji kodu. Dowiesz się: + +- jak **logicznie podzielić** aplikację na katalogi +- jak zaprojektować strukturę tak, aby **dobrze skalowała się** wraz ze wzrostem projektu +- jakie są **możliwe alternatywy** i ich zalety czy wady + +
    + + +Ważne jest, aby wspomnieć, że sam Nette Framework nie narzuca żadnej konkretnej struktury. Jest zaprojektowany tak, aby można go było łatwo dostosować do wszelkich potrzeb i preferencji. + + +Podstawowa struktura projektu +============================= + +Chociaż Nette Framework nie dyktuje żadnej sztywnej struktury katalogów, istnieje sprawdzony domyślny układ w postaci [Web Project|https://github.com/nette/web-project]: + +/--pre +web-project/ +├── app/ ← katalog z aplikacją +├── assets/ ← pliki SCSS, JS, obrazy..., alternatywnie resources/ +├── bin/ ← skrypty dla wiersza poleceń +├── config/ ← konfiguracja +├── log/ ← zalogowane błędy +├── temp/ ← pliki tymczasowe, cache +├── tests/ ← testy +├── vendor/ ← biblioteki zainstalowane przez Composer +└── www/ ← katalog publiczny (document-root) +\-- + +Tę strukturę można dowolnie modyfikować zgodnie z własnymi potrzebami - zmieniać nazwy lub przenosić foldery. Następnie wystarczy tylko zmodyfikować ścieżki względne do katalogów w pliku `Bootstrap.php` i ewentualnie `composer.json`. Nic więcej nie jest potrzebne, żadna skomplikowana rekonfiguracja, żadne zmiany stałych. Nette dysponuje inteligentną autodetekcją i automatycznie rozpozna lokalizację aplikacji, w tym jej bazę URL. + + +Zasady organizacji kodu +======================= + +Kiedy po raz pierwszy eksplorujesz nowy projekt, powinieneś szybko się w nim zorientować. Wyobraź sobie, że rozwijasz katalog `app/Model/` i widzisz taką strukturę: + +/--pre +app/Model/ +├── Services/ +├── Repositories/ +└── Entities/ +\-- + +Z niej wyczytasz tylko to, że projekt używa jakichś usług, repozytoriów i encji. O rzeczywistym celu aplikacji nie dowiesz się absolutnie nic. + +Spójrzmy na inne podejście - **organizację według domen**: + +/--pre +app/Model/ +├── Cart/ +├── Payment/ +├── Order/ +└── Product/ +\-- + +Tutaj jest inaczej - na pierwszy rzut oka widać, że chodzi o e-sklep. Już same nazwy katalogów zdradzają, co aplikacja potrafi - pracuje z płatnościami, zamówieniami i produktami. + +Pierwsze podejście (organizacja według typu klas) przynosi w praktyce szereg problemów: kod, który jest ze sobą logicznie powiązany, jest rozproszony w różnych folderach i trzeba między nimi przeskakiwać. Dlatego będziemy organizować według domen. + + +Przestrzenie nazw +----------------- + +Jest zwyczajem, że struktura katalogów odpowiada przestrzeniom nazw w aplikacji. Oznacza to, że fizyczna lokalizacja plików odpowiada ich namespace. Na przykład klasa umieszczona w `app/Model/Product/ProductRepository.php` powinna mieć namespace `App\Model\Product`. Ta zasada pomaga w orientacji w kodzie i upraszcza autoloading. + + +Liczba pojedyncza vs mnoga w nazwach +------------------------------------ + +Zauważ, że w głównych katalogach aplikacji używamy liczby pojedynczej: `app`, `config`, `log`, `temp`, `www`. Podobnie wewnątrz aplikacji: `Model`, `Core`, `Presentation`. Dzieje się tak dlatego, że każdy z nich reprezentuje jeden spójny koncept. + +Podobnie np. `app/Model/Product` reprezentuje wszystko związane z produktami. Nie nazwiemy tego `Products`, ponieważ nie jest to folder pełen produktów (byłyby tam pliki `nokia.php`, `samsung.php`). Jest to namespace zawierający klasy do pracy z produktami - `ProductRepository.php`, `ProductService.php`. + +Folder `app/Tasks` jest w liczbie mnogiej, ponieważ zawiera zestaw samodzielnych skryptów wykonywalnych - `CleanupTask.php`, `ImportTask.php`. Każdy z nich jest samodzielną jednostką. + +Dla spójności zalecamy używanie: +- Liczby pojedynczej dla namespace reprezentującego funkcjonalną całość (nawet jeśli pracuje z wieloma encjami) +- Liczby mnogiej dla kolekcji samodzielnych jednostek +- W przypadku niepewności lub jeśli nie chcesz się nad tym zastanawiać, wybierz liczbę pojedynczą + + +Katalog publiczny `www/` +======================== + +Ten katalog jest jedynym dostępnym z sieci (tzw. document-root). Często można spotkać się również z nazwą `public/` zamiast `www/` - jest to tylko kwestia konwencji i nie ma wpływu na funkcjonalność. Katalog zawiera: +- [Punkt wejściowy |bootstrapping#index.php] aplikacji `index.php` +- Plik `.htaccess` z regułami dla mod_rewrite (w Apache) +- Pliki statyczne (CSS, JavaScript, obrazy) +- Przesłane pliki + +Dla prawidłowego zabezpieczenia aplikacji kluczowe jest posiadanie poprawnie [skonfigurowanego document-root |nette:troubleshooting#Jak zmienić lub usunąć katalog www z adresu URL]. + +.[note] +Nigdy nie umieszczaj w tym katalogu folderu `node_modules/` - zawiera tysiące plików, które mogą być wykonywalne i nie powinny być publicznie dostępne. + + +Katalog aplikacji `app/` +======================== + +To jest główny katalog z kodem aplikacji. Podstawowa struktura: + +/--pre +app/ +├── Core/ ← kwestie infrastrukturalne +├── Model/ ← logika biznesowa +├── Presentation/ ← presentery i szablony +├── Tasks/ ← skrypty poleceń +└── Bootstrap.php ← klasa startowa aplikacji +\-- + +`Bootstrap.php` to [klasa startowa aplikacji|bootstrapping], która inicjalizuje środowisko, ładuje konfigurację i tworzy kontener DI. + +Przyjrzyjmy się teraz poszczególnym podkatalogom bardziej szczegółowo. + + +Presentery i szablony +===================== + +Część prezentacyjną aplikacji mamy w katalogu `app/Presentation`. Alternatywą jest krótkie `app/UI`. Jest to miejsce dla wszystkich presenterów, ich szablonów i ewentualnych klas pomocniczych. + +Tę warstwę organizujemy według domen. W złożonym projekcie, który łączy e-sklep, blog i API, struktura wyglądałaby tak: + +/--pre +app/Presentation/ +├── Shop/ ← frontend e-sklepu +│ ├── Product/ +│ ├── Cart/ +│ └── Order/ +├── Blog/ ← blog +│ ├── Home/ +│ └── Post/ +├── Admin/ ← administracja +│ ├── Dashboard/ +│ └── Products/ +└── Api/ ← endpointy API + └── V1/ +\-- + +Natomiast w prostym blogu użylibyśmy podziału: + +/--pre +app/Presentation/ +├── Front/ ← frontend strony +│ ├── Home/ +│ └── Post/ +├── Admin/ ← administracja +│ ├── Dashboard/ +│ └── Posts/ +├── Error/ +└── Export/ ← RSS, mapy strony itp. +\-- + +Foldery takie jak `Home/` czy `Dashboard/` zawierają presentery i szablony. Foldery takie jak `Front/`, `Admin/` czy `Api/` nazywamy **modułami**. Technicznie są to zwykłe katalogi, które służą do logicznego podziału aplikacji. + +Każdy folder z presenterem zawiera tak samo nazwany presenter i jego szablony. Na przykład folder `Dashboard/` zawiera: + +/--pre +Dashboard/ +├── DashboardPresenter.php ← presenter +└── default.latte ← szablon +\-- + +Ta struktura katalogów odzwierciedla się w przestrzeniach nazw klas. Na przykład `DashboardPresenter` znajduje się w przestrzeni nazw `App\Presentation\Admin\Dashboard` (zobacz [#Mapowanie presenterów]): + +```php +namespace App\Presentation\Admin\Dashboard; + +class DashboardPresenter extends Nette\Application\UI\Presenter +{ + // ... +} +``` + +Do presentera `Dashboard` wewnątrz modułu `Admin` odwołujemy się w aplikacji za pomocą notacji dwukropkowej jako `Admin:Dashboard`. Do jego akcji `default` następnie jako `Admin:Dashboard:default`. W przypadku zagnieżdżonych modułów używamy więcej dwukropków, na przykład `Shop:Order:Detail:default`. + + +Elastyczny rozwój struktury +--------------------------- + +Jedną z wielkich zalet tej struktury jest to, jak elegancko dostosowuje się do rosnących potrzeb projektu. Jako przykład weźmy część generującą kanały XML. Na początku mamy prostą postać: + +/--pre +Export/ +├── ExportPresenter.php ← jeden presenter dla wszystkich eksportów +├── sitemap.latte ← szablon dla mapy strony +└── feed.latte ← szablon dla kanału RSS +\-- + +Z czasem pojawią się kolejne typy kanałów i będziemy potrzebować dla nich więcej logiki... Żaden problem! Folder `Export/` po prostu stanie się modułem: + +/--pre +Export/ +├── Sitemap/ +│ ├── SitemapPresenter.php +│ └── sitemap.latte +└── Feed/ + ├── FeedPresenter.php + ├── zbozi.latte ← kanał dla Zboží.cz + └── heureka.latte ← kanał dla Heureka.cz +\-- + +Ta transformacja jest całkowicie płynna - wystarczy utworzyć nowe podfoldery, podzielić do nich kod i zaktualizować linki (np. z `Export:feed` na `Export:Feed:zbozi`). Dzięki temu możemy strukturę stopniowo rozszerzać w miarę potrzeb, poziom zagnieżdżenia nie jest w żaden sposób ograniczony. + +Jeśli na przykład w administracji masz wiele presenterów dotyczących zarządzania zamówieniami, takich jak `OrderDetail`, `OrderEdit`, `OrderDispatch` itp., możesz dla lepszej organizacji w tym miejscu utworzyć moduł (folder) `Order`, w którym będą (foldery dla) presenterów `Detail`, `Edit`, `Dispatch` i inne. + + +Lokalizacja szablonów +--------------------- + +W poprzednich przykładach widzieliśmy, że szablony są umieszczone bezpośrednio w folderze z presenterem: + +/--pre +Dashboard/ +├── DashboardPresenter.php ← presenter +├── DashboardTemplate.php ← opcjonalna klasa dla szablonu +└── default.latte ← szablon +\-- + +Ta lokalizacja w praktyce okazuje się najwygodniejsza - wszystkie powiązane pliki masz od razu pod ręką. + +Alternatywnie możesz umieścić szablony w podfolderze `templates/`. Nette wspiera obie warianty. Możesz nawet umieścić szablony całkowicie poza folderem `Presentation/`. Wszystko o możliwościach umieszczania szablonów znajdziesz w rozdziale [Wyszukiwanie szablonów |templates#Wyszukiwanie szablonów]. + + +Klasy pomocnicze i komponenty +----------------------------- + +Do presenterów i szablonów często należą również inne pliki pomocnicze. Umieszczamy je logicznie według ich zakresu: + +1. **Bezpośrednio przy presenterze** w przypadku specyficznych komponentów dla danego presentera: + +/--pre +Product/ +├── ProductPresenter.php +├── ProductGrid.php ← komponent do listowania produktów +└── FilterForm.php ← formularz do filtrowania +\-- + +2. **Dla modułu** - zalecamy wykorzystanie folderu `Accessory`, który umieści się przejrzyście na początku alfabetu: + +/--pre +Front/ +├── Accessory/ +│ ├── NavbarControl.php ← komponenty dla frontendu +│ └── TemplateFilters.php +├── Product/ +└── Cart/ +\-- + +3. **Dla całej aplikacji** - w `Presentation/Accessory/`: +/--pre +app/Presentation/ +├── Accessory/ +│ ├── LatteExtension.php +│ └── TemplateFilters.php +├── Front/ +└── Admin/ +\-- + +Lub możesz umieścić klasy pomocnicze takie jak `LatteExtension.php` czy `TemplateFilters.php` w folderze infrastruktury `app/Core/Latte/`. A komponenty w `app/Components`. Wybór zależy od zwyczajów zespołu. + + +Model - serce aplikacji +======================= + +Model zawiera całą logikę biznesową aplikacji. Dla jego organizacji obowiązuje ponownie zasada - strukturyzujemy według domen: + +/--pre +app/Model/ +├── Payment/ ← wszystko związane z płatnościami +│ ├── PaymentFacade.php ← główny punkt wejściowy +│ ├── PaymentRepository.php +│ ├── Payment.php ← encja +├── Order/ ← wszystko związane z zamówieniami +│ ├── OrderFacade.php +│ ├── OrderRepository.php +│ ├── Order.php +└── Shipping/ ← wszystko związane z wysyłką +\-- + +W modelu typowo spotkasz się z tymi typami klas: + +**Fasady**: reprezentują główny punkt wejściowy do konkretnej domeny w aplikacji. Działają jako orkiestrator, który koordynuje współpracę między różnymi usługami w celu implementacji kompletnych przypadków użycia (jak "utwórz zamówienie" lub "przetwórz płatność"). Pod swoją warstwą orkiestracji fasada ukrywa szczegóły implementacyjne przed resztą aplikacji, dostarczając czysty interfejs do pracy z daną domeną. + +```php +class OrderFacade +{ + public function createOrder(Cart $cart): Order + { + // walidacja + // utworzenie zamówienia + // wysłanie e-maila + // zapisanie do statystyk + } +} +``` + +**Usługi**: koncentrują się na specyficznej operacji biznesowej w ramach domeny. W przeciwieństwie do fasady, która orkiestruje całe przypadki użycia, usługa implementuje konkretną logikę biznesową (jak kalkulacje cen lub przetwarzanie płatności). Usługi są typowo bezstanowe i mogą być używane albo przez fasady jako bloki budulcowe dla bardziej złożonych operacji, albo bezpośrednio przez inne części aplikacji dla prostszych zadań. + +```php +class PricingService +{ + public function calculateTotal(Order $order): Money + { + // obliczenie ceny + } +} +``` + +**Repozytoria**: zapewniają całą komunikację z magazynem danych, typowo bazą danych. Jego zadaniem jest wczytywanie i zapisywanie encji oraz implementacja metod do ich wyszukiwania. Repozytorium odizolowuje resztę aplikacji od szczegółów implementacyjnych bazy danych i dostarcza interfejs zorientowany obiektowo do pracy z danymi. + +```php +class OrderRepository +{ + public function find(int $id): ?Order + { + } + + public function findByCustomer(int $customerId): array + { + } +} +``` + +**Encje**: obiekty reprezentujące główne koncepty biznesowe w aplikacji, które mają swoją tożsamość i zmieniają się w czasie. Typowo są to klasy mapowane na tabele bazy danych za pomocą ORM (jak Nette Database Explorer lub Doctrine). Encje mogą zawierać reguły biznesowe dotyczące ich danych oraz logikę walidacji. + +```php +// Encja mapowana na tabelę bazy danych orders +class Order extends Nette\Database\Table\ActiveRow +{ + public function addItem(Product $product, int $quantity): void + { + $this->related('order_items')->insert([ + 'product_id' => $product->id, + 'quantity' => $quantity, + 'unit_price' => $product->price, + ]); + } +} +``` + +**Obiekty wartości**: niemutowalne obiekty reprezentujące wartości bez własnej tożsamości - na przykład kwota pieniężna lub adres e-mail. Dwie instancje obiektu wartości z tymi samymi wartościami są uważane za identyczne. + + +Kod infrastruktury +================== + +Folder `Core/` (lub także `Infrastructure/`) jest domem dla technicznego fundamentu aplikacji. Kod infrastruktury typowo obejmuje: + +/--pre +app/Core/ +├── Router/ ← routing i zarządzanie URL +│ └── RouterFactory.php +├── Security/ ← uwierzytelnianie i autoryzacja +│ ├── Authenticator.php +│ └── Authorizator.php +├── Logging/ ← logowanie i monitorowanie +│ ├── SentryLogger.php +│ └── FileLogger.php +├── Cache/ ← warstwa cache +│ └── FullPageCache.php +└── Integration/ ← integracja z usługami zewnętrznymi + ├── Slack/ + └── Stripe/ +\-- + +W mniejszych projektach oczywiście wystarczy płaska struktura: + +/--pre +Core/ +├── RouterFactory.php +├── Authenticator.php +└── QueueMailer.php +\-- + +Chodzi o kod, który: + +- Rozwiązuje problemy techniczne infrastruktury (routing, logowanie, cachowanie) +- Integruje usługi zewnętrzne (Sentry, Elasticsearch, Redis) +- Dostarcza podstawowe usługi dla całej aplikacji (mail, baza danych) +- Jest zazwyczaj niezależny od konkretnej domeny - cache lub logger działa tak samo dla e-sklepu czy bloga. + +Wahasz się, czy dana klasa należy tutaj, czy do modelu? Kluczowa różnica polega na tym, że kod w `Core/`: + +- Nie wie nic o domenie (produkty, zamówienia, artykuły) +- Jest zazwyczaj możliwe przeniesienie go do innego projektu +- Rozwiązuje "jak to działa" (jak wysłać maila), a nie "co to robi" (jakiego maila wysłać) + +Przykład dla lepszego zrozumienia: + +- `App\Core\MailerFactory` - tworzy instancje klasy do wysyłania e-maili, obsługuje ustawienia SMTP +- `App\Model\OrderMailer` - używa `MailerFactory` do wysyłania e-maili o zamówieniach, zna ich szablony i wie, kiedy mają być wysłane + + +Skrypty poleceń +=============== + +Aplikacje często potrzebują wykonywać czynności poza zwykłymi żądaniami HTTP - czy to chodzi o przetwarzanie danych w tle, konserwację, czy zadania okresowe. Do uruchamiania służą proste skrypty w katalogu `bin/`, samą logikę implementacyjną umieszczamy w `app/Tasks/` (ewentualnie `app/Commands/`). + +Przykład: + +/--pre +app/Tasks/ +├── Maintenance/ ← skrypty konserwacyjne +│ ├── CleanupCommand.php ← usuwanie starych danych +│ └── DbOptimizeCommand.php ← optymalizacja bazy danych +├── Integration/ ← integracja z systemami zewnętrznymi +│ ├── ImportProducts.php ← import z systemu dostawcy +│ └── SyncOrders.php ← synchronizacja zamówień +└── Scheduled/ ← zadania regularne + ├── NewsletterCommand.php ← wysyłanie newsletterów + └── ReminderCommand.php ← powiadomienia dla klientów +\-- + +Co należy do modelu, a co do skryptów poleceń? Na przykład logika do wysłania jednego e-maila jest częścią modelu, masowa wysyłka tysięcy e-maili już należy do `Tasks/`. + +Zadania zazwyczaj [uruchamiamy z wiersza poleceń |https://blog.nette.org/en/cli-scripts-in-nette-application] lub przez cron. Można je uruchamiać również przez żądanie HTTP, ale trzeba pamiętać o bezpieczeństwie. Presenter, który uruchomi zadanie, trzeba zabezpieczyć, na przykład tylko dla zalogowanych użytkowników lub silnym tokenem i dostępem z dozwolonych adresów IP. W przypadku długich zadań trzeba zwiększyć limit czasu skryptu i użyć `session_write_close()`, aby nie blokować sesji. + + +Inne możliwe katalogi +===================== + +Oprócz wspomnianych podstawowych katalogów można w zależności od potrzeb projektu dodać inne specjalistyczne foldery. Spójrzmy na najczęstsze z nich i ich zastosowanie: + +/--pre +app/ +├── Api/ ← logika dla API niezależna od warstwy prezentacji +├── Database/ ← skrypty migracyjne i seedery dla danych testowych +├── Components/ ← współdzielone komponenty wizualne w całej aplikacji +├── Event/ ← przydatne jeśli używasz architektury sterowanej zdarzeniami +├── Mail/ ← szablony e-mail i powiązana logika +└── Utils/ ← klasy pomocnicze +\-- + +Dla współdzielonych komponentów wizualnych używanych w presenterach w całej aplikacji można użyć folderu `app/Components` lub `app/Controls`: + +/--pre +app/Components/ +├── Form/ ← współdzielone komponenty formularzy +│ ├── SignInForm.php +│ └── UserForm.php +├── Grid/ ← komponenty do listowania danych +│ └── DataGrid.php +└── Navigation/ ← elementy nawigacyjne + ├── Breadcrumbs.php + └── Menu.php +\-- + +Tutaj należą komponenty, które mają bardziej złożoną logikę. Jeśli chcesz współdzielić komponenty między wieloma projektami, wskazane jest wydzielenie ich do osobnego pakietu composera. + +Do katalogu `app/Mail` można umieścić zarządzanie komunikacją e-mail: + +/--pre +app/Mail/ +├── templates/ ← szablony e-mail +│ ├── order-confirmation.latte +│ └── welcome.latte +└── OrderMailer.php +\-- + + +Mapowanie presenterów +===================== + +Mapowanie definiuje reguły wnioskowania nazwy klasy z nazwy presentera. Specyfikujemy je w [konfiguracji|configuration] pod kluczem `application › mapping`. + +Na tej stronie pokazaliśmy, że presentery umieszczamy w folderze `app/Presentation` (ewentualnie `app/UI`). Tę konwencję musimy przekazać Nette w pliku konfiguracyjnym. Wystarczy jedna linia: + +```neon +application: + mapping: App\Presentation\*\**Presenter +``` + +Jak działa mapowanie? Dla lepszego zrozumienia najpierw wyobraźmy sobie aplikację bez modułów. Chcemy, aby klasy presenterów należały do przestrzeni nazw `App\Presentation`, aby presenter `Home` mapował się na klasę `App\Presentation\HomePresenter`. Co osiągniemy tą konfiguracją: + +```neon +application: + mapping: App\Presentation\*Presenter +``` + +Mapowanie działa tak, że nazwa presentera `Home` zastępuje gwiazdkę w masce `App\Presentation\*Presenter`, przez co uzyskujemy wynikową nazwę klasy `App\Presentation\HomePresenter`. Proste! + +Jak jednak widać w przykładach w tym i innych rozdziałach, klasy presenterów umieszczamy w eponimicznych podkatalogach, na przykład presenter `Home` mapuje się na klasę `App\Presentation\Home\HomePresenter`. Osiągniemy to przez podwojenie dwukropka (wymaga Nette Application 3.2): + +```neon +application: + mapping: App\Presentation\**Presenter +``` + +Teraz przystąpimy do mapowania presenterów do modułów. Dla każdego modułu możemy zdefiniować specyficzne mapowanie: + +```neon +application: + mapping: + Front: App\Presentation\Front\**Presenter + Admin: App\Presentation\Admin\**Presenter + Api: App\Api\*Presenter +``` + +Zgodnie z tą konfiguracją presenter `Front:Home` mapuje się na klasę `App\Presentation\Front\Home\HomePresenter`, podczas gdy presenter `Api:OAuth` na klasę `App\Api\OAuthPresenter`. + +Ponieważ moduły `Front` i `Admin` mają podobny sposób mapowania i takich modułów będzie prawdopodobnie więcej, możliwe jest utworzenie ogólnej reguły, która je zastąpi. Do maski klasy dojdzie więc nowa gwiazdka dla modułu: + +```neon +application: + mapping: + *: App\Presentation\*\**Presenter + Api: App\Api\*Presenter +``` + +Działa to również dla głębiej zagnieżdżonych struktur katalogów, jak na przykład presenter `Admin:User:Edit`, segment z gwiazdką powtarza się dla każdego poziomu, a wynikiem jest klasa `App\Presentation\Admin\User\Edit\EditPresenter`. + +Alternatywnym zapisem jest użycie zamiast stringa tablicy składającej się z trzech segmentów. Ten zapis jest ekwiwalentny z poprzednim: + +```neon +application: + mapping: + *: [App\Presentation, *, **Presenter] + Api: [App\Api, '', *Presenter] +``` diff --git a/application/pl/how-it-works.texy b/application/pl/how-it-works.texy index 84c129dd41..b0a9e60584 100644 --- a/application/pl/how-it-works.texy +++ b/application/pl/how-it-works.texy @@ -3,99 +3,101 @@ Jak działają aplikacje?
    -Czytasz właśnie dokumentację rdzenia Nette. Poznasz całą zasadę działania aplikacji internetowych. Od A do Z, od momentu narodzin do ostatniego tchnienia skryptu PHP. Po przeczytaniu tego, będziesz wiedział: +Właśnie czytasz podstawowy dokument dokumentacji Nette. Poznasz całą zasadę działania aplikacji internetowych. Krok po kroku, od A do Z, od momentu powstania aż do ostatniego tchnienia skryptu PHP. Po przeczytaniu będziesz wiedzieć: - jak to wszystko działa -- czym jest Bootstrap, Presenter i kontener DI +- co to jest Bootstrap, Presenter i kontener DI - jak wygląda struktura katalogów
    -Struktura katalogu .[#toc-directory-structure] -============================================== +Struktura katalogów +=================== -Otwórz szkieletowy przykład aplikacji internetowej o nazwie [WebProject |https://github.com/nette/web-project] i w trakcie czytania możesz przyjrzeć się omawianym plikom. +Otwórz przykład szkieletu aplikacji internetowej o nazwie [WebProject|https://github.com/nette/web-project] i podczas czytania możesz patrzeć na pliki, o których jest mowa. Struktura katalogów wygląda mniej więcej tak: /--pre web-project/ -├── app/ ← adresář s aplikací -│ ├── Presenters/ ← presentery a šablony -│ │ ├── HomePresenter.php ← třída presenteru Home -│ │ └── templates/ ← adresář se šablonami -│ │ ├── @layout.latte ← šablona layoutu -│ │ └── Home/ ← šablony presenteru Home -│ │ └── default.latte ← šablona akce 'default' -│ ├── Router/ ← konfigurace URL adres -│ └── Bootstrap.php ← zaváděcí třída Bootstrap -├── bin/ ← skripty spouštěné z příkazové řádky -├── config/ ← konfigurační soubory +├── app/ ← katalog z aplikacją +│ ├── Core/ ← podstawowe klasy niezbędne do działania +│ │ └── RouterFactory.php ← konfiguracja adresów URL +│ ├── Presentation/ ← presentery, szablony & spółka. +│ │ ├── @layout.latte ← szablon layoutu +│ │ └── Home/ ← katalog presentera Home +│ │ ├── HomePresenter.php ← klasa presentera Home +│ │ └── default.latte ← szablon akcji default +│ └── Bootstrap.php ← klasa startowa Bootstrap +├── assets/ ← zasoby (SCSS, TypeScript, obrazy źródłowe) +├── bin/ ← skrypty uruchamiane z wiersza poleceń +├── config/ ← pliki konfiguracyjne │ ├── common.neon -│ └── local.neon -├── log/ ← logované chyby -├── temp/ ← dočasné soubory, cache, … -├── vendor/ ← knihovny instalované Composerem +│ └── services.neon +├── log/ ← logowane błędy +├── temp/ ← pliki tymczasowe, cache, … +├── vendor/ ← biblioteki zainstalowane przez Composer │ ├── ... -│ └── autoload.php ← autoloading všech nainstalovaných balíčků -├── www/ ← veřejný adresář neboli document-root projektu -│ ├── .htaccess ← pravidla mod_rewrite -│ └── index.php ← prvotní soubor, kterým se aplikace spouští -└── .htaccess ← zakazuje přístup do všech adresářů krom www +│ └── autoload.php ← autoloading wszystkich zainstalowanych pakietów +├── www/ ← katalog publiczny czyli document-root projektu +│ ├── assets/ ← skompilowane pliki statyczne (CSS, JS, obrazy, ...) +│ ├── .htaccess ← reguły mod_rewrite +│ └── index.php ← pierwszy plik, którym uruchamia się aplikacja +└── .htaccess ← zabrania dostępu do wszystkich katalogów oprócz www \-- -Możesz zmienić strukturę katalogów w dowolny sposób, zmienić nazwę lub przenieść foldery, a następnie po prostu edytować ścieżki do `log/` i `temp/` w pliku `Bootstrap.php`, a następnie ścieżkę do tego pliku w `composer.json` w sekcji `autoload`. Nic więcej, żadnych skomplikowanych rekonfiguracji, żadnych zmian w stałych. Nette posiada [sprytną |bootstrap#Development-vs-Production-Mode] funkcję [autodetekcji |bootstrap#Development-vs-Production-Mode]. +Strukturę katalogów można dowolnie zmieniać, foldery przemianować lub przenieść, jest całkowicie elastyczna. Nette dodatkowo dysponuje inteligentną autodetekcją i automatycznie rozpozna lokalizację aplikacji, w tym jej bazę URL. -W przypadku nieco większych aplikacji możemy podzielić foldery prezentera i szablonu na dysku na podkatalogi, a klasy na przestrzenie nazw, które nazywamy [modułami |modules]. +W nieco większych aplikacjach możemy foldery z presenterami i szablonami [podzielić na podkatalogi |directory-structure#Presentery i szablony] i klasy na przestrzenie nazw, które nazywamy modułami. -Katalog `www/` jest tzw. katalogiem publicznym lub document-root projektu. Możesz zmienić jego nazwę bez konieczności ustawiania czegokolwiek innego po stronie aplikacji. Wystarczy [skonfigurować hosting |nette:troubleshooting#How-to-change-or-remove-www-directory-from-URL] tak, aby document-root trafił do tego katalogu. +Katalog `www/` reprezentuje tzw. katalog publiczny czyli document-root projektu. Można go przemianować bez konieczności ustawiania czegokolwiek dodatkowego po stronie aplikacji. Trzeba tylko [skonfigurować hosting |nette:troubleshooting#Jak zmienić lub usunąć katalog www z adresu URL] tak, aby document-root wskazywał na ten katalog. -Możesz również pobrać WebProject bezpośrednio, w tym Nette, używając [Composera |best-practices:composer]: +WebProject można również od razu pobrać wraz z Nette za pomocą [Composera |best-practices:composer]: ```shell composer create-project nette/web-project ``` -W systemach Linux lub macOS ustaw katalogi `log/` i `temp/` na uprawnienia do [zapisu |nette:troubleshooting#Setting-Directory-Permissions]. +Na Linuksie lub macOS ustaw katalogom `log/` i `temp/` [prawa do zapisu |nette:troubleshooting#Ustawianie uprawnień do katalogów]. -Aplikacja WebProject jest gotowa do uruchomienia, nie jest wymagana żadna konfiguracja i można ją obejrzeć bezpośrednio w przeglądarce, wchodząc do folderu `www/`. +Aplikacja WebProject jest gotowa do uruchomienia, nie trzeba niczego konfigurować i można ją od razu wyświetlić w przeglądarce, uzyskując dostęp do folderu `www/`. -Żądanie HTTP .[#toc-http-request] -================================= +Żądanie HTTP +============ -Wszystko zaczyna się w momencie, gdy użytkownik otwiera stronę w przeglądarce. Czyli wtedy, gdy przeglądarka kliknie na serwer z żądaniem HTTP. Żądanie jest kierowane do pojedynczego pliku PHP znajdującego się w katalogu publicznym `www/`, czyli do `index.php`. Załóżmy, że żądanie dotyczy `https://example.com/product/123` i jest wykonywany. +Wszystko zaczyna się w chwili, gdy użytkownik w przeglądarce otwiera stronę. Czyli gdy przeglądarka puka do serwera z żądaniem HTTP. Żądanie kieruje się na jeden plik PHP, który znajduje się w publicznym katalogu `www/`, a jest nim `index.php`. Powiedzmy, że chodzi o żądanie na adres `https://example.com/product/123`. Dzięki odpowiedniemu [ustawieniu serwera |nette:troubleshooting#Jak skonfigurować serwer dla przyjaznych adresów URL] również ten URL mapuje się na plik `index.php` i ten się wykonuje. -Jego celem jest: +Jego zadaniem jest: -1) inicjalizacja środowiska -2) zdobyć fabrykę -3) uruchomić aplikację Nette, która będzie obsługiwać żądanie +1) zainicjalizowanie środowiska +2) uzyskanie fabryki +3) uruchomienie aplikacji Nette, która obsłuży żądanie -Jaka fabryka? Nie robimy traktorów, robimy strony internetowe! Trzymaj się, za chwilę się to wyjaśni. +Jaką fabrykę? Przecież nie produkujemy traktorów, ale strony internetowe! Poczekajcie, zaraz się to wyjaśni. -Przez "inicjalizację środowiska" rozumiemy na przykład, że włączona jest [Tracy |tracy:], która jest niesamowitym narzędziem do logowania lub wizualizacji błędów. Rejestruje błędy na serwerze produkcyjnym, a wyświetla je na serwerze deweloperskim. Tak więc inicjalizacja obejmuje podjęcie decyzji, czy witryna działa w trybie produkcyjnym czy rozwojowym. Aby to zrobić, Nette używa autodetekcji: jeśli uruchamiasz stronę na localhost, działa ona w trybie deweloperskim. Nie musisz niczego konfigurować, a aplikacja jest gotowa zarówno do rozwoju, jak i wdrożenia na żywo. Czynności te są wykonywane i szczegółowo opisane w rozdziale poświęconym [klasie Bootstrap |bootstrap]. +Słowami „inicjalizacja środowiska” rozumiemy na przykład to, że aktywuje się [Tracy|tracy:], co jest niesamowitym narzędziem do logowania lub wizualizacji błędów. Na serwerze produkcyjnym błędy loguje, na deweloperskim od razu wyświetla. Dlatego do inicjalizacji należy również decyzja, czy strona działa w trybie produkcyjnym czy deweloperskim. Do tego Nette używa [inteligentnej autodetekcji |bootstrapping#Tryb deweloperski vs produkcyjny]: jeśli stronę uruchamiasz na localhost, działa w trybie deweloperskim. Nie musisz więc nic konfigurować, a aplikacja jest od razu gotowa zarówno do rozwoju, jak i ostrego wdrożenia. Te kroki są wykonywane i szczegółowo opisane w rozdziale o [klasie Bootstrap|bootstrapping]. -Trzeci punkt (tak, pominęliśmy drugi, ale do niego wrócimy) to uruchomienie aplikacji. Obsługą żądań HTTP zajmuje się w Nette klasa `Nette\Application\Application` (dalej `Application`), więc mówiąc o uruchomieniu aplikacji, mamy na myśli w szczególności wywołanie metody o odpowiedniej nazwie `run()` na obiekcie tej klasy. +Trzecim punktem (tak, drugi pominęliśmy, ale wrócimy do niego) jest uruchomienie aplikacji. Obsługą żądań HTTP w Nette zajmuje się klasa `Nette\Application\Application` (dalej `Application`), więc gdy mówimy uruchomić aplikację, mamy na myśli konkretnie wywołanie metody o wymownej nazwie `run()` na obiekcie tej klasy. -Nette jest mentorem, który prowadzi Cię do pisania czystych aplikacji według sprawdzonych metodologii. A jeden z absolutnie najbardziej sprawdzonych nazywa się **dependency injection**, w skrócie DI. W tym momencie nie chcemy obarczać Cię wyjaśnianiem DI, jest na to [osobny rozdział |dependency-injection:introduction], sedno sprawy jest takie, że kluczowe obiekty będą zazwyczaj tworzone przez naszą fabrykę obiektów, która nazywa się **DI container** (w skrócie DIC). Tak, to jest ta fabryka, o której była mowa przed chwilą. A także uczyni nas obiektem `Application`, dlatego najpierw potrzebujemy kontenera. Uzyskamy go za pomocą klasy `Configurator` i każemy mu wytworzyć obiekt `Application`, wywołać na nim metodę `run()` i to uruchomi aplikację Nette. Dokładnie to samo dzieje się w pliku [index.php |bootstrap#index-php]. +Nette jest mentorem, który prowadzi Cię do pisania czystych aplikacji według sprawdzonych metodyk. A jedna z tych absolutnie najsprawdzonych nazywa się **dependency injection**, w skrócie DI. W tej chwili nie chcemy Cię obciążać wyjaśnianiem DI, od tego jest [osobny rozdział|dependency-injection:introduction], istotny jest skutek, że kluczowe obiekty będzie nam zazwyczaj tworzyć fabryka obiektów, której mówi się **kontener DI** (w skrócie DIC). Tak, to ta fabryka, o której była przed chwilą mowa. I wyprodukuje nam również obiekt `Application`, dlatego potrzebujemy najpierw kontenera. Uzyskamy go za pomocą klasy `Configurator` i pozwolimy mu wyprodukować obiekt `Application`, wywołamy na nim metodę `run()` i tym samym uruchomi się aplikacja Nette. Dokładnie to dzieje się w pliku [index.php |bootstrapping#index.php]. -Aplikacja Nette .[#toc-nette-application] -========================================= +Nette Application +================= -Klasa Application ma tylko jedno zadanie: odpowiedzieć na żądanie HTTP. +Klasa `Application` ma jedno zadanie: odpowiedzieć na żądanie HTTP. -Aplikacje napisane w Nette są podzielone na wiele prezenterów (w innych frameworkach możesz spotkać się z terminem controller, to to samo), czyli klas, z których każda reprezentuje konkretną stronę witryny: np. stronę główną; produkt w sklepie internetowym; formularz logowania; sitemap feed itp. Aplikacja może mieć od jednego do tysięcy prezenterów. +Aplikacje pisane w Nette dzielą się na mnóstwo tzw. presenterów (w innych frameworkach można spotkać się z terminem controller, chodzi o to samo), które są klasami, z których każda reprezentuje jakąś konkretną stronę internetową: np. stronę główną; produkt w e-sklepie; formularz logowania; kanał sitemap itp. Aplikacja może mieć od jednego do tysięcy presenterów. -Aplikacja rozpoczyna się od zapytania tzw. routera o decyzję, do którego prezentera przekazać bieżące żądanie do przetworzenia. Router decyduje o tym, czyja to odpowiedzialność. Patrzy na wejściowy URL `https://example.com/product/123`, którego poprosi o wyświetlenie (`show`) produktu z `id: 123` jako **akcji** . Dobrą praktyką jest zapisanie pary prezenter + akcja oddzielonej dwukropkiem jako `Product:show`. +`Application` zaczyna od tego, że prosi tzw. router, aby zdecydował, któremu z presenterów przekazać aktualne żądanie do obsłużenia. Router decyduje, czyja to odpowiedzialność. Spogląda na wejściowy URL `https://example.com/product/123` i na podstawie tego, jak jest ustawiony, decyduje, że to praca np. dla **presentera** `Product`, od którego będzie chciał jako **akcję** wyświetlenie (`show`) produktu o `id: 123`. Parę presenter + akcja jest dobrym zwyczajem zapisywać oddzieloną dwukropkiem jako `Product:show`. -W ten sposób router przekształca adres URL w parę `Presenter:action` + parametry, w naszym przypadku `Product:show` + `id: 123`. Jak wygląda taki router, można zobaczyć w pliku `app/Router/RouterFactory.php`, a szczegółowo opisujemy go w rozdziale [Routing |Routing]. +Zatem router przekształcił URL na parę `Presenter:action` + parametry, w naszym przypadku `Product:show` + `id: 123`. Jak taki router wygląda, można zobaczyć w pliku `app/Core/RouterFactory.php` i szczegółowo opisujemy go w rozdziale [Routingu |Routing]. -Ruszajmy dalej. Aplikacja zna teraz nazwę prezentera i może przejść dalej. Produkując obiekt klasy `ProductPresenter`, który jest kodem prezentera `Product`. Dokładniej, prosi kontener DI o wyprodukowanie prezentera, ponieważ jest tam, aby go wyprodukować. +Idźmy dalej. `Application` już zna nazwę presentera i może kontynuować. Tworząc obiekt klasy `ProductPresenter`, który jest kodem presentera `Product`. Dokładniej mówiąc, prosi kontener DI, aby wyprodukował presenter, ponieważ od produkowania jest on. -Prezenter może wyglądać tak: +Presenter może wyglądać na przykład tak: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -107,98 +109,92 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function renderShow(int $id): void { - // získáme dane z modelu a předáme do šablony + // pobieramy dane z modelu i przekazujemy do szablonu $this->template->product = $this->repository->getProduct($id); } } ``` -Prezenter przejmuje obsługę żądania. A zadanie jest jasne: wykonać akcję `show` z `id: 123`. Co w języku prezenterów oznacza wywołanie metody `renderShow()` i uzyskanie `123` w parametrze `$id`. +Obsługę żądania przejmuje presenter. A zadanie brzmi jasno: wykonaj akcję `show` z `id: 123`. Co w języku presenterów oznacza, że wywołana zostanie metoda `renderShow()` i w parametrze `$id` otrzyma `123`. -Prezenter może obsługiwać wiele akcji, czyli mieć wiele metod `render()`. Zalecamy jednak projektowanie prezenterów z jedną lub jak najmniejszą ilością akcji. +Presenter może obsługiwać więcej akcji, czyli mieć więcej metod `render()`. Ale zalecamy projektowanie presenterów z jedną lub jak najmniejszą liczbą akcji. -Wywołana jest więc metoda `renderShow(123)`, której kod jest fikcyjnym przykładem, ale widać, jak przekazać dane do szablonu, czyli pisząc do `$this->template`. +Zatem, wywołano metodę `renderShow(123)`, której kod jest wprawdzie wymyślonym przykładem, ale można na nim zobaczyć, jak przekazuje się dane do szablonu, czyli zapisem do `$this->template`. -Następnie prezenter zwraca odpowiedź. Może to być strona HTML, obraz, dokument XML, wysłanie pliku z dysku, JSON, a nawet przekierowanie na inną stronę. Co ważne, o ile nie powiemy wprost prezenterowi, jak ma odpowiedzieć (co ma miejsce w przypadku `ProductPresenter`), odpowiedzią będzie renderowanie szablonu ze stroną HTML. Dlaczego? Ponieważ w 99% przypadków chcemy renderować szablon, więc prezenter przyjmuje to zachowanie jako domyślne i chce ułatwić nam pracę. O to właśnie chodzi w Nette. +Następnie presenter zwraca odpowiedź. Może to być strona HTML, obrazek, dokument XML, wysłanie pliku z dysku, JSON lub na przykład przekierowanie na inną stronę. Ważne jest, że jeśli jawnie nie powiemy, jak ma odpowiedzieć (co jest przypadkiem `ProductPresenter`), odpowiedzią będzie wyrenderowanie szablonu ze stroną HTML. Dlaczego? Ponieważ w 99% przypadków chcemy wyrenderować szablon, dlatego presenter to zachowanie traktuje jako domyślne i chce nam ułatwić pracę. To jest sens Nette. -Nie musimy nawet określać, jaki szablon ma być renderowany, sam wywnioskuje ścieżkę do niego za pomocą prostej logiki. W przypadku prezentera `Product` i akcji `show`, spróbuje sprawdzić, czy istnieje jeden z tych plików szablonów przechowywanych względnie z katalogu klasy `ProductPresenter`: +Nie musimy nawet podawać, jaki szablon wyrenderować, ścieżkę do niego wywnioskuje sam. W przypadku akcji `show` po prostu spróbuje załadować szablon `show.latte` w katalogu z klasą `ProductPresenter`. Podobnie spróbuje odnaleźć layout w pliku `@layout.latte` (więcej o [wyszukiwaniu szablonów |templates#Wyszukiwanie szablonów]). -- `templates/Product/show.latte` -- `templates/Product.show.latte` - -Spróbuje również prześledzić układ w pliku `@layout.latte`, a następnie wyrenderować szablon. W ten sposób kończy się zadanie prezentera i aplikacji, a praca zostaje zakończona. Jeśli szablon nie istnieje, zwrócona zostanie strona błędu 404. Więcej o prezenterach można przeczytać na stronie [Prezenterzy |presenters]. +A następnie szablony wyrenderuje. Tym samym zadanie presentera i całej aplikacji jest zakończone, a dzieło jest ukończone. Jeśli szablon by nie istniał, zwrócona zostanie strona z błędem 404. Więcej o presenterach przeczytasz na stronie [Presentery |presenters]. [* request-flow.svg *] -Aby być bezpiecznym, spróbujmy podsumować cały proces z nieco innym adresem URL: +Dla pewności, spróbujmy podsumować cały proces z nieco innym URL: -1) Adres URL będzie następujący `https://example.com` -2) uruchamiamy aplikację, tworzymy kontener i uruchamiamy `Application::run()` -3) router dekoduje adres URL jako parę `Home:default` -4) tworzony jest obiekt klasy `HomePresenter` +1) URL będzie `https://example.com` +2) uruchamiamy aplikację, tworzy się kontener i uruchamia `Application::run()` +3) router dekoduje URL jako parę `Home:default` +4) tworzy się obiekt klasy `HomePresenter` 5) wywoływana jest metoda `renderDefault()` (jeśli istnieje) -6) wyrenderować szablon np. `templates/Home/default.latte` z układem np. `templates/@layout.latte` +6) renderowany jest szablon np. `default.latte` z layoutem np. `@layout.latte` -Teraz być może spotkałeś się z wieloma nowymi pojęciami, ale wierzymy, że mają one sens. Tworzenie aplikacji w Nette to ogromna bułka z masłem. +Być może spotkałeś się teraz z dużą ilością nowych pojęć, ale wierzymy, że mają sens. Tworzenie aplikacji w Nette to ogromna przyjemność. -Szablony .[#toc-templates] -========================== +Szablony +======== -Mówiąc o szablonach, Nette używa systemu szablonów [Latte |latte:]. Stąd `.latte` zakończenie dla szablonów. Latte jest używany częściowo dlatego, że jest to najbezpieczniejszy system szablonowania dla PHP, a także najbardziej intuicyjny. Nie musisz uczyć się wielu nowych rzeczy, możesz sobie poradzić z PHP i kilkoma tagami. Wszystkiego można się dowiedzieć [w dokumentacji |latte:]. +Skoro już mowa o szablonach, w Nette używa się systemu szablonów [Latte |latte:]. Dlatego też te końcówki `.latte` przy szablonach. Latte używa się po pierwsze dlatego, że jest to najlepiej zabezpieczony system szablonów dla PHP, a jednocześnie system najbardziej intuicyjny. Nie musisz uczyć się wielu nowych rzeczy, wystarczy znajomość PHP i kilku znaczników. Wszystko dowiesz się [w dokumentacji |templates]. -Szablon łączy się [z |creating-links] innymi prezenterami i wydarzeniami w następujący sposób: +W szablonie [tworzy się linki |creating-links] do innych presenterów i akcji w ten sposób: ```latte -detail produktu +szczegóły produktu ``` -Wystarczy wpisać znaną parę `Presenter:action` zamiast prawdziwego adresu URL i podać dowolne parametry. Sztuczka to `n:href`, która mówi, że ten atrybut jest obsługiwany przez Nette. I wygenerować ją: +Po prostu zamiast rzeczywistego URL wpisujesz znaną parę `Presenter:action` i podajesz ewentualne parametry. Sztuczka tkwi w `n:href`, które mówi, że ten atrybut przetworzy Nette. I wygeneruje: ```latte -detail produktu +szczegóły produktu ``` -Za generowanie adresu URL odpowiada wspomniany wcześniej router. Routery w Nette są wyjątkowe, ponieważ mogą nie tylko wykonywać transformacje z URL do pary prezenter:akcja, ale także w drugą stronę, czyli z nazwy prezentera + akcji + parametrów do wygenerowania URL. -Dzięki temu Nette może całkowicie zmienić kształty adresów URL w całej gotowej aplikacji, nie zmieniając ani jednego znaku w szablonie lub prezenterze. Wystarczy zmodyfikować router. -Sprawia również, że działa tak zwana kanonizacja, kolejna unikalna funkcja Nette, która przyczynia się do lepszego SEO (optymalizacji pod kątem wyszukiwarek) poprzez automatyczne zapobieganie istnieniu zduplikowanych treści na różnych adresach URL. -Wielu programistów uważa to za przytłaczające. +Generowaniem URL zajmuje się już wcześniej wspomniany router. Otóż routery w Nette są wyjątkowe tym, że potrafią wykonywać nie tylko transformacje z URL na parę presenter:action, ale także odwrotnie, czyli z nazwy presentera + akcji + parametrów wygenerować URL. Dzięki temu w Nette możesz całkowicie zmienić kształty URL w całej gotowej aplikacji, nie zmieniając ani jednego znaku w szablonie czy presenterze. Tylko przez modyfikację routera. Również dzięki temu działa tzw. kanonizacja, co jest kolejną unikalną cechą Nette, która przyczynia się do lepszego SEO (optymalizacji dla wyszukiwarek internetowych) przez automatyczne zapobieganie istnieniu duplikatów treści pod różnymi URL. Wielu programistów uważa to za niesamowite. -Elementy interaktywne .[#toc-interactive-components] -==================================================== +Komponenty interaktywne +======================= -O prezenterach można powiedzieć jeszcze jedno: mają wbudowany system komponentów. Możesz pamiętać coś podobnego z Delphi lub ASP.NET Web Forms, a React lub Vue.js opiera się na czymś zdalnie podobnym. W świecie frameworków PHP jest to rzecz zupełnie wyjątkowa. +O presenterach musimy Ci jeszcze zdradzić jedną rzecz: mają w sobie wbudowany system komponentów. Coś podobnego mogą pamiętać weterani z Delphi lub ASP.NET Web Forms, na czymś zdalnie podobnym opiera się React czy Vue.js. W świecie frameworków PHP jest to absolutnie unikalna sprawa. -Komponenty to samodzielne jednostki wielokrotnego użytku, które osadzamy na stronach (czyli prezenterach). Mogą to być [formularze |forms:in-presenter], [datagridy |https://componette.org/contributte/datagrid/], menu, ankiety, w rzeczywistości wszystko, co ma sens, aby używać wielokrotnie. Możemy stworzyć własne komponenty lub skorzystać z któregoś z [ogromnej gamy |https://componette.org] komponentów open source. +Komponenty to samodzielne, wielokrotnego użytku całości, które wstawiamy do stron (czyli presenterów). Mogą to być [formularze |forms:in-presenter], [siatki danych |https://componette.org/contributte/datagrid/], menu, ankiety, właściwie cokolwiek, co ma sens używać wielokrotnie. Możemy tworzyć własne komponenty lub używać niektórych z [ogromnej oferty |https://componette.org] komponentów open source. -Komponenty w zasadniczy sposób wpływają na podejście do budowania aplikacji. Otwierają one nowe możliwości komponowania stron z gotowych jednostek. Do tego mają coś wspólnego z [Hollywood |components#Hollywood-Style]. +Komponenty zasadniczo wpływają na podejście do tworzenia aplikacji. Otworzą Ci nowe możliwości składania stron z gotowych jednostek. A ponadto mają coś wspólnego z [Hollywoodem |components#Styl Hollywood]. -Kontener DI i konfiguracja .[#toc-di-container-and-configuration] -================================================================= +Kontener DI i konfiguracja +========================== -Kontener DI, czyli fabryka obiektów, to serce aplikacji. +Kontener DI czyli fabryka obiektów jest sercem całej aplikacji. -Nie martw się, nie jest to magiczna czarna skrzynka, jak mogłoby się wydawać z poprzednich wierszy. Jest to właściwie jedna dość nudna klasa PHP, którą Nette generuje i przechowuje w katalogu cache. Ma garść metod nazwanych jak `createServiceAbcd()` i każda z nich może wytworzyć i zwrócić obiekt. Tak, istnieje metoda `createServiceApplication()`, która wyprodukuje `Nette\Application\Application`, który był nam potrzebny w pliku `index.php` do uruchomienia aplikacji. A są metody, które produkują indywidualnych prezenterów. I tak dalej. +Nie obawiaj się, nie jest to żaden magiczny black box, jak mogłoby się wydawać z poprzednich linijek. Właściwie jest to jedna dość nudna klasa PHP, którą generuje Nette i zapisuje w katalogu z cache. Ma mnóstwo metod nazwanych jak `createServiceAbcd()` i każda z nich potrafi wyprodukować i zwrócić jakiś obiekt. Tak, jest tam również metoda `createServiceApplication()`, która wyprodukuje `Nette\Application\Application`, którego potrzebowaliśmy w pliku `index.php` do uruchomienia aplikacji. I są tam metody produkujące poszczególne presentery. I tak dalej. -Obiekty, które tworzy kontener DI, nie bez powodu nazywane są usługami. +Obiektom, które tworzy kontener DI, z jakiegoś powodu mówi się usługi. -To co jest naprawdę wyjątkowe w tej klasie to fakt, że nie programujesz jej, robi to framework. W rzeczywistości generuje on kod PHP i przechowuje go na dysku. Po prostu instruujesz, jakie obiekty kontener powinien być w stanie wyprodukować i jak dokładnie. A instrukcje te zapisywane są w [plikach konfiguracyjnych |bootstrap#DI-Container-Configuration], dla których wykorzystywany jest format [NEON |neon:format], a więc mają rozszerzenie `.neon`. +Co jest w tej klasie naprawdę specjalnego, to że nie programujesz jej Ty, ale framework. On rzeczywiście generuje kod PHP i zapisuje go na dysku. Ty tylko dajesz instrukcje, jakie obiekty ma umieć kontener produkować i jak dokładnie. A te instrukcje są zapisane w [plikach konfiguracyjnych |bootstrapping#Konfiguracja kontenera DI], dla których używa się formatu [NEON|neon:format] i dlatego mają też rozszerzenie `.neon`. -Pliki konfiguracyjne służą wyłącznie do instruowania kontenera DI. Tak więc, na przykład, jeśli określę opcję `expiration: 14 days` w sekcji [sesji |http:configuration#Session], kontener DI wywoła swoją metodę `setExpiration('14 days')`, gdy utworzy obiekt `Nette\Http\Session` reprezentujący sesję, urzeczywistniając konfigurację. +Pliki konfiguracyjne służą czysto do instruowania kontenera DI. Więc gdy na przykład podam w sekcji [sesji |http:configuration#Sesja] opcję `expiration: 14 days`, to kontener DI przy tworzeniu obiektu `Nette\Http\Session` reprezentującego sesję wywoła jego metodę `setExpiration('14 days')` i tym samym konfiguracja stanie się rzeczywistością. -Jest cały rozdział opisujący, co można [skonfigurować |nette:configuring] i jak [zdefiniować niestandardowe usługi |dependency-injection:services]. +Jest tu dla Ciebie przygotowany cały rozdział opisujący, co wszystko można [konfigurować |nette:configuring] i jak [definiować własne usługi |dependency-injection:services]. -Gdy zagłębimy się nieco w tworzenie usług, natrafimy na słowo [autowiring |dependency-injection:autowiring]. To gadżet, który w niesamowity sposób uprości Twoje życie. Może automatycznie przekazywać obiekty tam, gdzie ich potrzebujesz (jak w konstruktorach twoich klas) bez konieczności robienia czegokolwiek. Przekonasz się, że kontener DI w Nette to małe cudo. +Jak tylko trochę zagłębisz się w tworzenie usług, natkniesz się na słowo [autowiring |dependency-injection:autowiring]. To jest bajer, który niesamowicie uprości Ci życie. Potrafi automatycznie przekazywać obiekty tam, gdzie ich potrzebujesz (na przykład w konstruktorach Twoich klas), bez konieczności robienia czegokolwiek. Odkryjesz, że kontener DI w Nette to mały cud. -Gdzie dalej? .[#toc-what-next] -============================== +Dokąd dalej? +============ -Przeszliśmy przez podstawowe zasady działania aplikacji Nette. Jak na razie bardzo powierzchownie, ale wkrótce dostaniesz się do nitty gritty i ostatecznie zbudujesz kilka wspaniałych aplikacji internetowych. Gdzie dalej? Czy próbowałeś tutoriala [Pisanie pierwszej aplikacji |quickstart:]? +Przeszliśmy przez podstawowe zasady aplikacji w Nette. Na razie bardzo powierzchownie, ale wkrótce zagłębisz się w temat i z czasem stworzysz wspaniałe aplikacje internetowe. Dokąd kontynuować dalej? Czy wypróbowałeś już tutorial [Pisanie pierwszej aplikacji|quickstart:]? -Oprócz powyższego Nette posiada cały arsenał [przydatnych klas |utils:], [warstwę bazodanową |database:] itp. Spróbuj celowo przeklikać się przez dokumentację. Albo [blog |https://blog.nette.org]. Odkryjesz wiele ciekawych rzeczy. +Oprócz wyżej opisanego, Nette dysponuje całym arsenałem [przydatnych klas|utils:], [warstwą bazy danych|database:], itd. Spróbuj sobie po prostu przeklikać dokumentację. Lub [blog|https://blog.nette.org]. Odkryjesz mnóstwo ciekawych rzeczy. -Niech ramy przyniosą Wam wiele radości 💙 +Niech framework przynosi Ci mnóstwo radości 💙 diff --git a/application/pl/modules.texy b/application/pl/modules.texy deleted file mode 100644 index a2a4480132..0000000000 --- a/application/pl/modules.texy +++ /dev/null @@ -1,148 +0,0 @@ -Moduły -****** - -.[perex] -W Nette moduły reprezentują logiczne jednostki, które tworzą aplikację. Należą do nich prezentery, szablony, ewentualnie komponenty i klasy modeli. - -Jeden komponent dla prezenterów i jeden dla szablonów nie wystarczyłby dla prawdziwych projektów. Posiadanie kilkudziesięciu plików w jednym folderze jest co najmniej niezorganizowane. Jak się z niego wydostać? Po prostu dzielimy je na podkatalogi na dysku i na przestrzenie nazw w kodzie. I właśnie to robią moduły Nette. - -Zapomnijmy więc o jednym folderze dla prezenterów i szablonów, a zamiast tego stwórzmy moduły, na przykład `Admin` i `Front`. - -/--pre -app/ -├── Presenters/ -├── Modules/ ← adresář s moduly -│ ├── Admin/ ← modul Admin -│ │ ├── Presenters/ ← jeho presentery -│ │ │ ├── DashboardPresenter.php -│ │ │ └── templates/ -│ └── Front/ ← modul Front -│ └── Presenters/ ← jeho presentery -│ └── ... -\-- - -Ta struktura katalogów będzie odzwierciedlona w przestrzeni nazw klas, więc na przykład `DashboardPresenter` będzie w przestrzeni `App\Modules\Admin\Presenters`: - -```php -namespace App\Modules\Admin\Presenters; - -class DashboardPresenter extends Nette\Application\UI\Presenter -{ - // ... -} -``` - -Prezenter `Dashboard` wewnątrz modułu `Admin` jest określany w aplikacji za pomocą notacji z podwójną kropką jako `Admin:Dashboard`, a jego działanie `default` jest określane jako `Admin:Dashboard:default`. -A skąd Nette own wie, że `Admin:Dashboard` reprezentuje klasę `App\Modules\Admin\Presenters\DashboardPresenter`? Mówimy to za pomocą [mapowania |#Mappings] w konfiguracji. -Tak więc podana struktura nie jest stała i można ją modyfikować według potrzeb. - -Moduły mogą oczywiście zawierać wszystkie inne części oprócz prezenterów i szablonów, jak komponenty, klasy modeli itp. - - -Moduły zagnieżdżone .[#toc-nested-modules] ------------------------------------------- - -Moduły nie muszą tworzyć tylko płaskiej struktury, można też tworzyć np. submoduły: - -/--pre -app/ -├── Modules/ ← adresář s moduly -│ ├── Blog/ ← modul Blog -│ │ ├── Admin/ ← submodul Admin -│ │ │ ├── Presenters/ -│ │ │ └── ... -│ │ └── Front/ ← submodul Front -│ │ ├── Presenters/ -│ │ └── ... -│ ├── Forum/ ← modul Forum -│ │ └── ... -\-- - -Tak więc moduł `Blog` jest podzielony na podmoduły `Admin` i `Front`. I znowu będzie to odzwierciedlone w przestrzeni nazw, która będzie `App\Modules\Blog\Admin\Presenters` itd. Prezenter `Dashboard` wewnątrz submodułu jest określany jako `Blog:Admin:Dashboard`. - -Rozgałęzienie może iść tak głęboko, jak chcesz, więc możesz tworzyć podmoduły. - - -Tworzenie linków .[#toc-creating-links] ---------------------------------------- - -Linki w szablonach prezenterów są względne do bieżącego modułu. Tak więc link `Foo:default` prowadzi do prezentera `Foo` w tym samym module co aktualny prezenter. Na przykład, jeśli bieżący moduł to `Front`, to link idzie tak: - -```latte -odkaz na Front:Product:show -``` - -Link jest względny, nawet jeśli nazwa modułu jest jego częścią, jest wtedy uważany za submoduł: - -```latte -odkaz na Front:Shop:Product:show -``` - -Odwołania bezwzględne są zapisywane analogicznie do ścieżek bezwzględnych na dysku, ale z dwukropkami zamiast ukośników. Tak więc link bezwzględny zaczyna się od dwukropka: - -```latte -odkaz na Admin:Product:show -``` - -Aby dowiedzieć się, czy jesteśmy w danym module lub submodule, korzystamy z funkcji `isModuleCurrent(moduleName)`. - -```latte -
  • - ... -
  • -``` - - -Routing .[#toc-routing] ------------------------ - -Patrz [rozdział dotyczący routingu |routing#Modules]. - - -Mapowanie .[#toc-mapping] -------------------------- - -Określa zasady, według których nazwa klasy jest wyprowadzana z nazwy prezentera. Zapisujemy je w [konfiguracji |configuration] pod kluczem `application › mapping`. - -Zacznijmy od próbki, która nie korzysta z modułów. Będziemy chcieli, aby klasy prezentera miały przestrzeń nazw `App\Presenters`. To znaczy, będziemy chcieli, aby prezenter, na przykład, `Home` mapował do klasy `App\Presenters\HomePresenter`. Można to osiągnąć dzięki następującej konfiguracji: - -```neon -application: - mapping: - *: App\Presenters\*Presenter -``` - -Zastąp nazwę prezentera gwiazdką w masce klasy, a wynikiem będzie nazwa klasy. Spokojnie! - -Jeśli złamiemy prezenterów na moduły, możemy mieć niestandardowe mapowanie dla każdego modułu: - -```neon -application: - mapping: - Front: App\Modules\Front\Presenters\*Presenter - Admin: App\Modules\Admin\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -Teraz prezenter `Front:Home` mapuje do klasy `App\Modules\Front\Presenters\HomePresenter`, a prezenter `Admin:Dashboard` mapuje do klasy `App\Modules\Admin\Presenters\DashboardPresenter`. - -Bardziej praktyczne będzie stworzenie ogólnej (gwiazdkowej) reguły, która zastąpi pierwsze dwie. W masce klasy zostanie dodana dodatkowa gwiazdka tylko dla tego modułu: - -```neon -application: - mapping: - *: App\Modules\*\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -Ale co w przypadku, gdy korzystamy z wielu zagnieżdżonych modułów i mamy np. prezentera `Admin:User:Edit`? W takim przypadku segment z gwiazdką reprezentujący moduł dla każdego poziomu zostanie po prostu powtórzony, a wynikiem będzie klasa `App\Modules\Admin\User\Presenters\EditPresenter`. - -Alternatywną notacją jest użycie tablicy składającej się z trzech segmentów zamiast ciągu. Ta notacja jest równoważna z poprzednią: - -```neon -application: - mapping: - *: [App\Modules, *, Presenters\*Presenter] -``` - -Wartość domyślna to `*: *Module\*Presenter`. diff --git a/application/pl/multiplier.texy b/application/pl/multiplier.texy index 4c29ccf902..68e68e1852 100644 --- a/application/pl/multiplier.texy +++ b/application/pl/multiplier.texy @@ -1,30 +1,32 @@ -Mnożnik: składniki dynamiczne -***************************** +Multiplier: dynamiczne komponenty +********************************* -Narzędzie do dynamicznego tworzenia interaktywnych komponentów .[perex] +.[perex] +Narzędzie do dynamicznego tworzenia interaktywnych komponentów -Zacznijmy od typowego przykładu: miejmy listę przedmiotów w sklepie internetowym, a dla każdego przedmiotu chcemy wypisać formularz dodawania przedmiotu do koszyka. Jedną z możliwych opcji jest zawinięcie całego listingu w jeden formularz. Jednak znacznie wygodniejszy sposób oferuje [api:Nette\Application\UI\Multiplier]. +Wyjdźmy od typowego przykładu: mamy listę towarów w sklepie internetowym, przy czym przy każdym będziemy chcieli wyświetlić formularz do dodania towaru do koszyka. Jedną z możliwych opcji jest opakowanie całego listingu w jeden formularz. Znacznie wygodniejszy sposób oferuje nam jednak [api:Nette\Application\UI\Multiplier]. -Mnożnik pozwala na wygodne zdefiniowanie fabryki dla wielu komponentów. Działa na zasadzie zagnieżdżonych komponentów - każdy komponent dziedziczący po [api:Nette\ComponentModel\Container] może zawierać inne komponenty. +Multiplier umożliwia wygodne definiowanie fabryczki dla wielu komponentów. Działa na zasadzie zagnieżdżonych komponentów - każdy komponent dziedziczący po [api:Nette\ComponentModel\Container] może zawierać kolejne komponenty. -Zobacz rozdział o [modelu komponentów |components#Advanced-Use-of-Components] w dokumentacji lub [wykład Honzy Tvrdíka |https://www.youtube.com/watch?v=8y3LLexWu-I]. .[tip] +.[tip] +Zobacz rozdział o [modelu komponentowym |components#Komponenty dogłębnie] w dokumentacji lub [wykład Honzy Tvrdíka|https://www.youtube.com/watch?v=8y3LLexWu-I]. -Istotą Multiplier jest to, że działa jako rodzic, który może dynamicznie tworzyć swoje dzieci za pomocą wywołania zwrotnego przekazanego w konstruktorze. Zobacz przykład: +Istotą Multipliera jest to, że występuje w pozycji rodzica, który potrafi dynamicznie tworzyć swoje potomstwo za pomocą callbacku przekazanego w konstruktorze. Zobacz przykład: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function () { $form = new Nette\Application\UI\Form; - $form->addInteger('count', 'Počet zboží:') + $form->addInteger('count', 'Ilość towaru:') ->setRequired(); - $form->addSubmit('send', 'Přidat do košíku'); + $form->addSubmit('send', 'Dodaj do koszyka'); return $form; }); } ``` -Teraz możemy po prostu mieć formularz renderowany dla każdego elementu w szablonie - i każdy będzie naprawdę unikalnym komponentem. +Teraz możemy w szablonie po prostu przy każdym towarze wyświetlić formularz - i każdy będzie rzeczywiście unikalnym komponentem. ```latte {foreach $items as $item} @@ -35,26 +37,26 @@ Teraz możemy po prostu mieć formularz renderowany dla każdego elementu w szab {/foreach} ``` -Argument przekazywany w znaczniku `{control}` ma format mówiący: +Argument przekazany w znaczniku `{control}` jest w formacie, który mówi: -1. zdobądź komponent `shopForm` -2. i uzyskać od niej dziecko `$item->id` +1. pobierz komponent `shopForm` +2. a z niego pobierz potomka `$item->id` -Przy pierwszym wywołaniu punktu **1.**, `shopForm` jeszcze nie istnieje, więc wywoływana jest jego fabryka `createComponentShopForm` Fabryka danej formy - którą jest anonimowa funkcja, którą przekazaliśmy Multiplierowi w konstruktorze - jest następnie wywoływana na uzyskanym komponencie (instancji Multiplera). +Przy pierwszym wywołaniu punktu **1.** `shopForm` jeszcze nie istnieje, więc wywołana zostanie jego fabryka `createComponentShopForm`. Na uzyskanym komponencie (instancji Multipliera) jest następnie wywoływana fabryka konkretnego formularza - co jest anonimową funkcją, którą przekazaliśmy Multiplierowi w konstruktorze. -W kolejnej iteracji foreache metoda `createComponentShopForm` nie zostanie już wywołana (komponent istnieje), ale ponieważ szukamy jego drugiego potomka (`$item->id` będzie inny w każdej iteracji), funkcja anonimowa zostanie ponownie wywołana i zwróci nowy formularz. +W kolejnej iteracji foreache już metoda `createComponentShopForm` nie będzie wywoływana (komponent istnieje), ale ponieważ szukamy jego innego potomka (`$item->id` będzie w każdej iteracji inne), ponownie zostanie wywołana anonimowa funkcja i zwróci nam nowy formularz. -Pozostaje tylko zadbać o to, aby formularz faktycznie dodawał do naszego koszyka przedmioty, które posiada - obecnie formularz jest dokładnie taki sam dla każdego przedmiotu. Cechą Multiplier (i w ogóle każdej fabryki komponentów w Nette Framework), która nam pomaga jest to, że każda fabryka dostaje jako pierwszy argument nazwę tworzonego komponentu. W naszym przypadku będzie to `$item->id`, czyli dokładnie taka informacja, jakiej potrzebujemy. Musimy więc tylko lekko zmodyfikować tworzenie formularza: +Jedyne, co pozostaje, to zapewnić, aby formularz dodał do koszyka rzeczywiście ten towar, który ma - obecnie formularz przy każdym towarze jest całkowicie identyczny. Pomoże nam właściwość Multipliera (i ogólnie każdej fabryki komponentu w Nette Framework), a mianowicie ta, że każda fabryka jako swój pierwszy argument otrzymuje nazwę tworzonego komponentu. W naszym przypadku będzie to `$item->id`, co jest dokładnie tą informacją, której potrzebujemy. Wystarczy więc lekko zmodyfikować tworzenie formularza: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function ($itemId) { $form = new Nette\Application\UI\Form; - $form->addInteger('count', 'Počet zboží:') + $form->addInteger('count', 'Ilość towaru:') ->setRequired(); $form->addHidden('itemId', $itemId); - $form->addSubmit('send', 'Přidat do košíku'); + $form->addSubmit('send', 'Dodaj do koszyka'); return $form; }); } diff --git a/application/pl/presenters.texy b/application/pl/presenters.texy index 43ec3f3ee2..c450af53eb 100644 --- a/application/pl/presenters.texy +++ b/application/pl/presenters.texy @@ -1,39 +1,39 @@ -Prezenterzy -*********** +Presentery +**********
    -Dowiedz się jak pisać prezentery i szablony w Nette. Po przeczytaniu będziesz wiedział: +Zapoznamy się z tym, jak w Nette pisze się presentery i szablony. Po przeczytaniu będziesz wiedzieć: -- jak działa prezenter -- jakie są trwałe parametry -- jak rysować szablony +- jak działa presenter +- co to są parametry trwałe +- jak rysuje się szablony
    -[Wiemy już |how-it-works#Nette-Application], że prezenter to klasa, która reprezentuje konkretną stronę aplikacji internetowej, np. stronę główną; produkt w sklepie internetowym; formularz logowania; sitemap feed itp. Aplikacja może mieć od jednego do tysięcy prezenterów. W innych frameworkach są one również nazywane kontrolerami. +[Już wiemy |how-it-works#Nette Application], że presenter to klasa, która reprezentuje jakąś konkretną stronę aplikacji internetowej, np. stronę główną; produkt w e-sklepie; formularz logowania; kanał sitemap itp. Aplikacja może mieć od jednego do tysięcy presenterów. W innych frameworkach nazywa się je również kontrolerami. -Zazwyczaj termin prezenter odnosi się do potomka klasy [api:Nette\Application\UI\Presenter], która jest przydatna do generowania interfejsów internetowych i którą omówimy w dalszej części tego rozdziału. W ogólnym sensie prezenterem jest dowolny obiekt, który implementuje interfejs [api:Nette\Application\IPresenter]. +Zazwyczaj pod pojęciem presenter rozumie się potomka klasy [api:Nette\Application\UI\Presenter], który jest odpowiedni do generowania interfejsów internetowych i któremu poświęcimy resztę tego rozdziału. W ogólnym sensie presenter to dowolny obiekt implementujący interfejs [api:Nette\Application\IPresenter]. -Cykl życia prezentera .[#toc-life-cycle-of-presenter] -===================================================== +Cykl życia presentera +===================== -Zadaniem prezentera jest obsługa żądania i zwrócenie odpowiedzi (która może być stroną HTML, obrazem, przekierowaniem itp.) +Zadaniem presentera jest obsłużenie żądania i zwrócenie odpowiedzi (co może być stroną HTML, obrazkiem, przekierowaniem itp.). -W ten sposób na początku przekazywane jest do niego żądanie. Nie jest to bezpośrednio żądanie HTTP, lecz obiekt [api:Nette\Application\Request], na który przy pomocy routera zostało przekształcone żądanie HTTP. Zwykle nie mamy styczności z tym obiektem, ponieważ prezenter sprytnie deleguje przetwarzanie żądania do innych metod, które teraz pokażemy. +Zatem na początku przekazywane jest mu żądanie. Nie jest to bezpośrednio żądanie HTTP, ale obiekt [api:Nette\Application\Request], na który zostało przekształcone żądanie HTTP za pomocą routera. Z tym obiektem zazwyczaj nie mamy do czynienia, ponieważ presenter inteligentnie deleguje przetwarzanie żądania do innych metod, które teraz pokażemy. -[* lifecycle.svg *] *** *Cykl życia prezentera* .<> +[* lifecycle.svg *] *** *Cykl życia presentera* .<> -Rysunek przedstawia listę metod, które są wywoływane sekwencyjnie od góry do dołu, jeśli istnieją. Żaden z nich nie musi istnieć, możemy mieć całkowicie pusty prezenter bez ani jednej metody i zbudować na nim prostą statyczną stronę. +Obrazek przedstawia listę metod, które są kolejno wywoływane od góry do dołu, jeśli istnieją. Żadna z nich nie musi istnieć, możemy mieć całkowicie pusty presenter bez ani jednej metody i zbudować na nim prostą statyczną stronę internetową. `__construct()` --------------- -Konstruktor tak naprawdę nie należy do cyklu życia prezentera, ponieważ jest wywoływany w momencie tworzenia obiektu. Ale wspominamy o tym ze względu na jego znaczenie. Konstruktor (wraz z [metodą inject |best-practices:inject-method-attribute]) służy do przekazywania zależności. +Konstruktor nie należy tak do końca do cyklu życia presentera, ponieważ jest wywoływany w momencie tworzenia obiektu. Ale podajemy go ze względu na ważność. Konstruktor (wraz z [metodą inject|best-practices:inject-method-attribute]) służy do przekazywania zależności. -Prezenter nie powinien zaspokajać logiki biznesowej aplikacji, zapisywania do i odczytywania z bazy danych, wykonywania obliczeń itp. Do tego właśnie służą klasy z warstwy, którą nazywamy modelem. Na przykład klasa `ArticleRepository` może być odpowiedzialna za wyszukiwanie i przechowywanie artykułów. Aby prezenter mógł z nim pracować, będzie miał go [przekazanego do niego za pomocą zastrzyku zależności |dependency-injection:passing-dependencies]: +Presenter nie powinien zajmować się logiką biznesową aplikacji, zapisywać i czytać z bazy danych, wykonywać obliczeń itp. Od tego są klasy z warstwy, którą określamy jako model. Na przykład klasa `ArticleRepository` może odpowiadać za ładowanie i zapisywanie artykułów. Aby presenter mógł z nią pracować, pozwoli sobie ją [przekazać za pomocą dependency injection |dependency-injection:passing-dependencies]: ```php @@ -50,44 +50,44 @@ class ArticlePresenter extends Nette\Application\UI\Presenter `startup()` ----------- -Natychmiast po otrzymaniu żądania wywoływana jest metoda `startup()`. Możesz go użyć do inicjalizacji właściwości, sprawdzenia uprawnień użytkowników itp. Wymagane jest, aby metoda zawsze wywoływała przodka `parent::startup()`. +Natychmiast po otrzymaniu żądania wywoływana jest metoda `startup()`. Można jej użyć do inicjalizacji właściwości, weryfikacji uprawnień użytkownika itp. Wymagane jest, aby metoda zawsze wywoływała przodka `parent::startup()`. `action(args...)` .{toc: action()} -------------------------------------------------- -Podobna metoda `render()`. Podczas gdy `render()` Metoda ma na celu przygotowanie danych dla konkretnego szablonu, który jest następnie renderowany. `action()` obsługuje żądanie bez podążania za renderowaniem szablonu. Na przykład, przetwarza dane, loguje użytkownika w lub z i tak dalej, a następnie [przekierowuje |#Redirection] w inne miejsce. +Odpowiednik metody `render()`. Podczas gdy `render()` jest przeznaczona do przygotowania danych dla konkretnego szablonu, który następnie zostanie wyrenderowany, to w `action()` przetwarza się żądanie bez związku z renderowaniem szablonu. Na przykład przetwarza się dane, loguje lub wylogowuje użytkownika i tak dalej, a następnie [przekierowuje gdzie indziej |#Przekierowanie]. -Ważne jest to, że `action()` jest wywoływany przed `render()`więc możemy w nim potencjalnie zmienić dalszy bieg historii, czyli zmienić szablon do wylosowania, a także metodę `render()`który zostanie wywołany. Odbywa się to za pomocą strony `setView('jineView')`. +Ważne jest, że `action()` jest wywoływana wcześniej niż `render()`, więc możemy w niej ewentualnie zmienić dalszy bieg wydarzeń, tj. zmienić szablon, który będzie rysowany, a także metodę `render()`, która będzie wywoływana. A to za pomocą `setView('innyView')`. -Do metody przekazywane są parametry z żądania. Możliwe i zalecane jest podawanie typów do parametrów, np. `actionShow(int $id, string $slug = null)` - jeśli w parametrze `id` zabraknie lub nie będzie on liczbą całkowitą, prezenter zwróci [błąd 404 |#Error-404-etc] i wyjdzie. +Metodzie przekazywane są parametry z żądania. Możliwe i zalecane jest podanie typów parametrów, np. `actionShow(int $id, ?string $slug = null)` - jeśli parametr `id` będzie brakował lub jeśli nie będzie liczbą całkowitą, presenter zwróci [błąd 404 |#Błąd 404 i spółka] i zakończy działanie. `handle(args...)` .{toc: handle()} -------------------------------------------------- -Metoda ta przetwarza tzw. sygnały, z którymi zapoznamy się w rozdziale poświęconym [komponentom |components#Signal]. Jest on przeznaczony głównie do komponentów i przetwarzania żądań AJAX. +Metoda przetwarza tzw. sygnały, z którymi zapoznamy się w rozdziale poświęconym [komponentom |components#Sygnał]. Jest bowiem przeznaczona głównie dla komponentów i przetwarzania żądań AJAX. -Parametry z żądania są przekazywane do metody, tak jak w przypadku `action()`, w tym sprawdzanie typów. +Metodzie przekazywane są parametry z żądania, jak w przypadku `action()`, w tym kontrola typów. `beforeRender()` ---------------- -Metoda `beforeRender`, jak sama nazwa wskazuje, jest wywoływana przed każdą metodą `render()`. Jest używany do wspólnej konfiguracji szablonów, przekazywania zmiennych układu i tak dalej. +Metoda `beforeRender`, jak sama nazwa wskazuje, jest wywoływana przed każdą metodą `render()`. Używa się jej do wspólnej konfiguracji szablonu, przekazania zmiennych dla layoutu i podobnych. `render(args...)` .{toc: render()} ---------------------------------------------- -Miejsce, w którym przygotowujemy szablon do późniejszego renderowania, przekazujemy do niego dane itp. +Miejsce, gdzie przygotowujemy szablon do późniejszego wyrenderowania, przekazujemy mu dane itp. -Parametry z żądania są przekazywane do metody, tak jak w przypadku `action()`, w tym sprawdzanie typów. +Metodzie przekazywane są parametry z żądania, jak w przypadku `action()`, w tym kontrola typów. ```php public function renderShow(int $id): void { - // pobieramy dane z modelu i przekazujemy je do szablonu + // pobieramy dane z modelu i przekazujemy do szablonu $this->template->article = $this->articles->getById($id); } ``` @@ -96,39 +96,39 @@ public function renderShow(int $id): void `afterRender()` --------------- -Metoda `afterRender`, jak sama nazwa wskazuje, jest wywoływana po każdej metodzie `render()`. Jest on raczej rzadko używany. +Metoda `afterRender`, jak nazwa ponownie wskazuje, jest wywoływana po każdej metodzie `render()`. Używa się jej raczej wyjątkowo. `shutdown()` ------------ -Jest on wywoływany na końcu cyklu życia prezentera. +Wywoływana na końcu cyklu życia presentera. -**Dobra rada, zanim ruszymy dalej**. Presenter jak widać może obsługiwać wiele akcji/widoków, więc może mieć wiele metod `render()`. Zalecamy jednak projektowanie prezenterów z jedną lub jak najmniejszą ilością akcji. +**Dobra rada, zanim pójdziemy dalej**. Presenter, jak widać, może obsługiwać więcej akcji/view, czyli mieć więcej metod `render()`. Ale zalecamy projektowanie presenterów z jedną lub jak najmniejszą liczbą akcji. -Wysyłanie odpowiedzi .[#toc-sending-a-response] -=============================================== +Wysłanie odpowiedzi +=================== -Odpowiedź prezentera to zazwyczaj [render szablonu ze stroną HTML |templates], ale może to być również przesłanie pliku, JSON lub nawet przekierowanie do innej strony. +Odpowiedzią presentera jest zazwyczaj [wyrenderowanie szablonu ze stroną HTML|templates], ale może nią być również wysłanie pliku, JSON lub na przykład przekierowanie na inną stronę. -W dowolnym momencie cyklu życia możemy użyć dowolnej z poniższych metod, aby wysłać odpowiedź i jednocześnie wyjść z prezentera: +W dowolnym momencie cyklu życia możemy za pomocą jednej z poniższych metod wysłać odpowiedź i jednocześnie zakończyć działanie presentera: -- `redirect()`, `redirectPermanent()`, `redirectUrl()` oraz `forward()` [przekierowania |#Redirection] -- `error()` kończy pracę prezentera z [powodu błędu |#Error-404-etc] -- `sendJson($data)` prezenter kończy pracę i [wysyła dane |#Sending-JSON] w formacie JSON -- `sendTemplate()` prezenter kończy pracę i natychmiast [renderuje szablon |templates] -- `sendResponse($response)` prezenter wychodzi i wysyła [swoją własną odpowiedź |#Responses] -- `terminate()` prezenter wychodzi bez odpowiedzi +- `redirect()`, `redirectPermanent()`, `redirectUrl()` i `forward()` [przekierowuje |#Przekierowanie] +- `error()` kończy presenter [z powodu błędu |#Błąd 404 i spółka] +- `sendJson($data)` kończy presenter i [wysyła dane |#Wysłanie JSON] w formacie JSON +- `sendTemplate()` kończy presenter i natychmiast [renderuje szablon |templates] +- `sendResponse($response)` kończy presenter i wysyła [własną odpowiedź |#Odpowiedzi] +- `terminate()` kończy presenter bez odpowiedzi -Jeśli nie wywołasz żadnej z tych metod, prezenter automatycznie przejdzie do renderowania szablonu. Dlaczego? Ponieważ w 99% przypadków chcemy renderować szablon, więc presenter przyjmuje to zachowanie jako domyślne i chce ułatwić nam pracę. +Jeśli nie wywołasz żadnej z tych metod, presenter automatycznie przystąpi do renderowania szablonu. Dlaczego? Ponieważ w 99% przypadków chcemy wyrenderować szablon, dlatego presenter to zachowanie traktuje jako domyślne i chce nam ułatwić pracę. -Tworzenie linków .[#toc-creating-links] -======================================= +Tworzenie linków +================ -Presenter ma metodę `link()`, która może być użyta do tworzenia linków URL do innych prezenterów. Pierwszym parametrem jest docelowy prezenter & akcja, a następnie przekazane argumenty, które mogą być określone jako tablica: +Presenter dysponuje metodą `link()`, za pomocą której można tworzyć linki URL do innych presenterów. Pierwszym parametrem jest docelowy presenter & akcja, następnie przekazywane argumenty, które mogą być podane jako tablica: ```php $url = $this->link('Product:show', $id); @@ -136,64 +136,64 @@ $url = $this->link('Product:show', $id); $url = $this->link('Product:show', [$id, 'lang' => 'cs']); ``` -W szablonie linki do innych prezenterów i wydarzeń tworzone są w następujący sposób: +W szablonie tworzy się linki do innych presenterów & akcji w ten sposób: ```latte -detail produktu +szczegóły produktu ``` -Wystarczy wpisać znaną parę `Presenter:action` zamiast prawdziwego adresu URL i podać dowolne parametry. Sztuczka to `n:href`, która mówi, że Latte obsłuży ten atrybut i wygeneruje prawdziwy adres URL. Więc w Nette nie musisz w ogóle myśleć o adresach URL, tylko o prezenterach i wydarzeniach. +Po prostu zamiast rzeczywistego URL wpisujesz znaną parę `Presenter:action` i podajesz ewentualne parametry. Sztuczka tkwi w `n:href`, które mówi, że ten atrybut przetworzy Latte i wygeneruje rzeczywisty URL. W Nette więc w ogóle nie musisz zastanawiać się nad URL, tylko nad presenterami i akcjami. -Więcej informacji na ten temat znajduje się w rozdziale [Tworzenie linków URL |creating-links]. +Więcej informacji znajdziesz w rozdziale [Tworzenie linków URL|creating-links]. -Przekierowanie .[#toc-redirection] -================================== +Przekierowanie +============== -Aby przekierować do innego prezentera, użyj metod `redirect()` i `forward()`, które mają bardzo podobną składnię do metody [link() |#Creating-Links]. +Do przejścia na inny presenter służą metody `redirect()` i `forward()`, które mają bardzo podobną składnię jak metoda [link() |#Tworzenie linków]. -Metoda `forward()` przechodzi natychmiast do nowego prezentera bez przekierowania HTTP: +Metoda `forward()` przechodzi na nowy presenter natychmiast bez przekierowania HTTP: ```php $this->forward('Product:show'); ``` -Przykład tymczasowego przekierowania z kodem HTTP 302 lub 303: +Przykład tzw. tymczasowego przekierowania z kodem HTTP 302 (lub 303, jeśli metodą aktualnego żądania jest POST): ```php $this->redirect('Product:show', $id); ``` -Aby uzyskać trwałe przekierowanie z kodem HTTP 301, wykonaj następujące czynności: +Stałe przekierowanie z kodem HTTP 301 osiągniesz w ten sposób: ```php $this->redirectPermanent('Product:show', $id); ``` -Możesz przekierować do innego adresu URL poza aplikacją za pomocą metody `redirectUrl()`: +Na inny URL poza aplikacją można przekierować metodą `redirectUrl()`. Jako drugi parametr można podać kod HTTP, domyślny to 302 (lub 303, jeśli metodą aktualnego żądania jest POST): ```php $this->redirectUrl('https://nette.org'); ``` -Przekierowanie natychmiast kończy prezentera, rzucając cichy wyjątek zakończenia `Nette\Application\AbortException`. +Przekierowanie natychmiast kończy działanie presentera przez wyrzucenie tzw. cichego wyjątku kończącego `Nette\Application\AbortException`. -Przed przekierowaniem można wysłać [wiadomości flash |#Flash-Messages], czyli takie, które będą wyświetlane w szablonie po przekierowaniu. +Przed przekierowaniem można wysłać [flash message |#Wiadomości flash], czyli wiadomości, które zostaną po przekierowaniu wyświetlone w szablonie. -Wiadomości błyskowe .[#toc-flash-messages] -========================================== +Wiadomości flash +================ -Są to komunikaty informujące zazwyczaj o wyniku jakiejś operacji. Ważną cechą wiadomości flash jest to, że są one dostępne w szablonie nawet po przekierowaniu. Nawet po ich wyświetleniu pozostaną na żywo przez kolejne 30 sekund - na przykład w przypadku, gdy użytkownik odświeży stronę z powodu błędu transmisji - więc komunikat nie zniknie natychmiast. +Są to wiadomości zazwyczaj informujące o wyniku jakiejś operacji. Ważną cechą wiadomości flash jest to, że są dostępne w szablonie również po przekierowaniu. Nawet po wyświetleniu pozostają aktywne jeszcze przez 30 sekund – na przykład na wypadek, gdyby z powodu błędnego transferu użytkownik odświeżył stronę - wiadomość mu więc od razu nie zniknie. -Wystarczy wywołać metodę [flashMessage() |api:Nette\Application\UI\Control::flashMessage()], a prezenter zajmie się przekazaniem jej do szablonu. Pierwszy parametr to tekst komunikatu, a opcjonalny drugi to jego typ (błąd, ostrzeżenie, info itp.). Metoda `flashMessage()` zwraca instancję wiadomości flash, do której można dodać dodatkowe informacje. +Wystarczy wywołać metodę [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] a o przekazanie do szablonu zadba presenter. Pierwszym parametrem jest tekst wiadomości, a opcjonalnym drugim parametrem jej typ (error, warning, info itp.). Metoda `flashMessage()` zwraca instancję wiadomości flash, której można dodawać dalsze informacje. ```php -$this->flashMessage('Item has been deleted.'); -$this->redirect(/* ... */); // i przekierować +$this->flashMessage('Pozycja została usunięta.'); +$this->redirect(/* ... */); // i przekierowujemy ``` -Wiadomości te są dostępne dla szablonu w zmiennej `$flashes` jako obiekty `stdClass`, które zawierają właściwości `message` (tekst wiadomości), `type` (typ wiadomości) oraz mogą zawierać wspomniane już informacje o użytkowniku. Na przykład wyrenderujmy je w następujący sposób: +W szablonie te wiadomości są dostępne w zmiennej `$flashes` jako obiekty `stdClass`, które zawierają właściwości `message` (tekst wiadomości), `type` (typ wiadomości) i mogą zawierać już wspomniane informacje użytkownika. Wyrenderujemy je na przykład tak: ```latte {foreach $flashes as $flash} @@ -202,10 +202,10 @@ Wiadomości te są dostępne dla szablonu w zmiennej `$flashes` jako obiekty `st ``` -Error 404 i co. .[#toc-error-404-etc] -===================================== +Błąd 404 i spółka. +================== -Jeśli żądanie nie może zostać spełnione, na przykład dlatego, że artykuł, który chcemy wyświetlić, nie istnieje w bazie danych, rzucamy błąd 404 za pomocą metody `error(string $message = null, int $httpCode = 404)`. +Jeśli nie można spełnić żądania, na przykład z powodu, że artykuł, który chcemy wyświetlić, nie istnieje w bazie danych, wyrzucamy błąd 404 metodą `error(?string $message = null, int $httpCode = 404)`. ```php public function renderShow(int $id): void @@ -218,14 +218,13 @@ public function renderShow(int $id): void } ``` -Kod błędu HTTP może być przekazany jako drugi parametr, domyślnie jest to 404. Metoda działa poprzez rzucenie wyjątku `Nette\Application\BadRequestException`, po którym `Application` przekazuje kontrolę do error-presenter. Który jest prezenterem, którego zadaniem jest wyświetlenie strony informującej o błędzie. -Ustawienie prezentera błędów odbywa się w [konfiguracji aplikacji |configuration]. +Kod HTTP błędu można przekazać jako drugi parametr, domyślny to 404. Metoda działa tak, że wyrzuca wyjątek `Nette\Application\BadRequestException`, po czym `Application` przekazuje sterowanie do error-presentera. Co jest presenterem, którego zadaniem jest wyświetlenie strony informującej o zaistniałym błędzie. Ustawienie error-preseteru dokonuje się w [konfiguracji application|configuration]. -Wysyłanie JSON .[#toc-sending-json] -=================================== +Wysłanie JSON +============= -Przykład akcji-metody, która wysyła dane w formacie JSON i wychodzi z prezentera: +Przykład metody action, która wysyła dane w formacie JSON i kończy presenter: ```php public function actionData(): void @@ -236,33 +235,59 @@ public function actionData(): void ``` -Trwałe parametry .[#toc-persistent-parameters] -============================================== +Parametry żądania .{data-version:3.1.14} +======================================== -Trwałe parametry są używane do utrzymania stanu pomiędzy różnymi żądaniami. Ich wartość pozostaje taka sama nawet po kliknięciu linku. W przeciwieństwie do danych sesji, są one przekazywane w adresie URL. Dzieje się to całkowicie automatycznie, więc nie ma potrzeby wyraźnego ich podawania `link()` lub `n:href`. +Presenter, a także każdy komponent, uzyskuje z żądania HTTP swoje parametry. Ich wartość można uzyskać metodą `getParameter($name)` lub `getParameters()`. Wartości są ciągami znaków lub tablicami ciągów znaków, są to w zasadzie surowe dane uzyskane bezpośrednio z URL. -Przykład użycia? Masz wielojęzyczną aplikację. Rzeczywisty język jest parametrem, który musi być częścią adresu URL przez cały czas. Ale byłoby to niewiarygodnie żmudne, aby zawrzeć go w każdym linku. Więc robisz z niego trwały parametr o nazwie `lang` i będzie się on sam przenosił. Fajnie! +Dla większej wygody zalecamy udostępnianie parametrów przez właściwości. Wystarczy oznaczyć je atrybutem `#[Parameter]`: + +```php +use Nette\Application\Attributes\Parameter; // ta linia jest ważna + +class HomePresenter extends Nette\Application\UI\Presenter +{ + #[Parameter] + public string $theme; // musi być public +} +``` + +Przy właściwości zalecamy podanie również typu danych (np. `string`), a Nette na jego podstawie automatycznie przeliczy wartość. Wartości parametrów można również [walidować |#Walidacja parametrów]. + +Przy tworzeniu linku można parametrom wartość ustawić bezpośrednio: + +```latte +kliknij +``` -Tworzenie trwałych parametrów jest niezwykle proste w Nette. Wystarczy stworzyć właściwość publiczną i oznaczyć ją atrybutem: (poprzednio używano `/** @persistent */` ) + +Parametry trwałe +================ + +Parametry trwałe służą do utrzymywania stanu między różnymi żądaniami. Ich wartość pozostaje taka sama nawet po kliknięciu na link. W przeciwieństwie do danych w sesji, są one przekazywane w URL. I to całkowicie automatycznie, nie trzeba ich więc jawnie podawać w `link()` lub `n:href`. + +Przykład użycia? Masz aplikację wielojęzyczną. Aktualny język jest parametrem, który musi być stale częścią URL. Ale byłoby niesamowicie męczące podawanie go w każdym linku. Więc zrobisz z niego parametr trwały `lang` i będzie się przenosił sam. Parada! + +Tworzenie parametru trwałego jest w Nette niezwykle proste. Wystarczy utworzyć publiczną właściwość i oznaczyć ją atrybutem: (wcześniej używano `/** @persistent */`) ```php -use Nette\Application\Attributes\Persistent; // ta linia jest ważna +use Nette\Application\Attributes\Persistent; // ta linia jest ważna class ProductPresenter extends Nette\Application\UI\Presenter { #[Persistent] - public string $lang; // musi być publiczny + public string $lang; // musi być public } ``` -Jeśli `$this->lang` ma wartość taką jak `'en'`, to linki utworzone przy użyciu `link()` lub `n:href` będą zawierały również parametr `lang=en`. A gdy link zostanie kliknięty, ponownie będzie to `$this->lang = 'en'`. +Jeśli `$this->lang` będzie miał wartość na przykład `'en'`, to również linki utworzone za pomocą `link()` lub `n:href` będą zawierać parametr `lang=en`. A po kliknięciu na link ponownie `$this->lang = 'en'`. -Dla właściwości zalecamy dołączenie typu danych (np. `string`) i można również dołączyć wartość domyślną. Wartości parametrów mogą być [walidowane |#Validation of Persistent Parameters]. +Przy właściwości zalecamy podanie również typu danych (np. `string`) i można podać również wartość domyślną. Wartości parametrów można [walidować |#Walidacja parametrów]. -Trwałe parametry są domyślnie przekazywane pomiędzy wszystkimi akcjami danego prezentera. Aby przekazać je pomiędzy wieloma prezenterami, musisz je zdefiniować: +Parametry trwałe standardowo przenoszą się między wszystkimi akcjami danego presentera. Aby przenosiły się również między wieloma presenterami, trzeba je zdefiniować albo: -- we wspólnym przodku, po którym dziedziczą prezentery -- w cechach, które są używane przez prezenterów: +- we wspólnym przodku, od którego dziedziczą presentery +- w trait, którego użyją presentery: ```php trait LanguageAware @@ -277,48 +302,42 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -Możesz zmienić wartość trwałego parametru podczas tworzenia łącza: +Przy tworzeniu linku można parametrowi trwałemu zmienić wartość: ```latte -detail in Czech +szczegóły po czesku ``` -Można też go *resetować*, czyli usunąć z adresu URL. Przyjmie on wtedy swoją domyślną wartość: +Lub można go *zresetować*, tj. usunąć z URL. Wtedy przyjmie swoją wartość domyślną: ```latte -click +kliknij ``` -Komponenty interaktywne .[#toc-interactive-components] -====================================================== +Komponenty interaktywne +======================= -Prezenterzy mają wbudowany system komponentów. Komponenty to samodzielne jednostki wielokrotnego użytku, które wstawiamy do prezenterów. Mogą to być [formularze |forms:in-presenter], datagridy, menu, w rzeczywistości wszystko, co ma sens, aby używać wielokrotnie. +Presentery mają wbudowany system komponentów. Komponenty to samodzielne, wielokrotnego użytku całości, które wstawiamy do presenterów. Mogą to być [formularze |forms:in-presenter], siatki danych, menu, właściwie cokolwiek, co ma sens używać wielokrotnie. -Jak komponenty są wstawiane do prezentera, a następnie wykorzystywane? Dowiesz się tego w rozdziale [Komponenty |components]. Dowiesz się nawet, co łączy ich z Hollywood. +Jak wstawia się komponenty do presentera i następnie używa? Dowiesz się tego w rozdziale [Komponenty |components]. Nawet dowiesz się, co mają wspólnego z Hollywoodem. -A gdzie mogę dostać komponenty? Na stronie [Componette |https://componette.org/search/component] można znaleźć komponenty open-source, a także szereg innych dodatków dla Nette, umieszczonych tu przez wolontariuszy ze społeczności skupionej wokół frameworka. +A gdzie mogę zdobyć komponenty? Na stronie [Componette |https://componette.org/search/component] znajdziesz komponenty open-source oraz wiele innych dodatków do Nette, które umieścili tu wolontariusze ze społeczności wokół frameworka. -Sięgając głębiej .[#toc-going-deeper] -===================================== +Idziemy do hloubky +================== .[tip] -Z tym, co do tej pory omówiliśmy w tym rozdziale, jesteś prawdopodobnie całkowicie zadowolony. Kolejne wiersze są dla tych, którzy interesują się prezenterami dogłębnie i chcą wiedzieć wszystko. - +Z tym, co do tej pory pokazaliśmy w tym rozdziale, prawdopodobnie w zupełności sobie poradzisz. Poniższe linijki są przeznaczone dla tych, którzy interesują się presenterami dogłębnie i chcą wiedzieć absolutnie wszystko. -Wymagania i parametry .[#toc-requirement-and-parameters] --------------------------------------------------------- -Żądanie obsługiwane przez prezentera ma postać obiektu [api:Nette\Application\Request] i jest zwracane przez metodę prezentera `getRequest()`. Zawiera ona tablicę parametrów, a każdy z nich należy albo do któregoś z komponentów, albo bezpośrednio do prezentera (który w rzeczywistości też jest komponentem, choć specjalnym). Nette dokonuje więc redystrybucji parametrów i przechodzi między poszczególnymi komponentami (i prezenterem), wywołując metodę `loadState(array $params)`. Parametry można uzyskać za pomocą metody `getParameters(): array`, indywidualnie za pomocą `getParameter($name)`. Wartości parametrów są ciągami lub tablicami ciągów, są to w zasadzie surowe dane uzyskane bezpośrednio z adresu URL. +Walidacja parametrów +-------------------- +Wartości [parametrów żądania |#Parametry żądania] i [parametrów trwałych |#Parametry trwałe] otrzymanych z URL zapisuje do właściwości metoda `loadState()`. Ta również kontroluje, czy odpowiada typ danych podany przy właściwości, w przeciwnym razie odpowie błędem 404 i strona się nie wyświetli. -Walidacja trwałych parametrów .[#toc-validation-of-persistent-parameters] -------------------------------------------------------------------------- - -Wartości [trwałych parametrów |#persistent parameters] otrzymanych z adresów URL są zapisywane do właściwości przez metodę `loadState()`. Sprawdza ona również, czy typ danych określony we właściwości pasuje, w przeciwnym razie odpowie błędem 404 i strona nie zostanie wyświetlona. - -Nigdy ślepo nie ufaj trwałym parametrom, ponieważ mogą one zostać łatwo nadpisane przez użytkownika w adresie URL. Na przykład w ten sposób sprawdzamy, czy `$this->lang` jest wśród obsługiwanych języków. Dobrym sposobem na to jest nadpisanie metody `loadState()` wspomnianej powyżej: +Nigdy ślepo nie wierz parametrom, ponieważ mogą być łatwo przez użytkownika nadpisane w URL. W ten sposób na przykład zweryfikujemy, czy język `$this->lang` jest wśród wspieranych. Odpowiednią drogą jest nadpisanie wspomnianej metody `loadState()`: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -328,8 +347,8 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function loadState(array $params): void { - parent::loadState($params); // tutaj jest ustawiony $this->lang - // następuje sprawdzenie wartości użytkownika: + parent::loadState($params); // tutaj ustawia się $this->lang + // następuje własna kontrola wartości: if (!in_array($this->lang, ['en', 'cs'])) { $this->error(); } @@ -338,43 +357,45 @@ class ProductPresenter extends Nette\Application\UI\Presenter ``` -Zapisywanie i przywracanie wniosku .[#toc-save-and-restore-the-request] ------------------------------------------------------------------------ +Zapisanie i odtworzenie żądania +------------------------------- -Bieżące żądanie można zapisać do sesji lub przywrócić z niej i pozwolić prezenterowi wykonać je ponownie. Jest to przydatne, na przykład, gdy użytkownik wypełnia formularz i jego login wygasa. Aby uniknąć utraty danych, przed przekierowaniem na stronę logowania zapisujemy bieżące żądanie do sesji za pomocą `$reqId = $this->storeRequest()`, która zwraca jej identyfikator jako krótki łańcuch i przekazuje go jako parametr do prezentera logowania. +Żądanie, które obsługuje presenter, jest obiektem [api:Nette\Application\Request] i zwraca go metoda presentera `getRequest()`. -Po zalogowaniu się wywołujemy metodę `$this->restoreRequest($reqId)`, która pobiera żądanie z sesji i przekazuje je dalej. Ta metoda sprawdza, czy żądanie zostało utworzone przez tego samego użytkownika, który jest teraz zalogowany. Jeśli zalogował się inny użytkownik lub klucz był nieważny, nie robi nic i program kontynuuje. +Aktualne żądanie można zapisać do sesji lub odwrotnie, odtworzyć z niej i pozwolić presenterowi ponownie je wykonać. Przydaje się to na przykład w sytuacji, gdy użytkownik wypełnia formularz i wygaśnie mu sesja logowania. Aby nie stracił danych, przed przekierowaniem na stronę logowania aktualne żądanie zapisujemy do sesji za pomocą `$reqId = $this->storeRequest()`, które zwraca jego identyfikator w postaci krótkiego ciągu znaków, a ten przekazujemy jako parametr do presentera logowania. -Zobacz poradnik [Jak powrócić do wcześniejszej strony |best-practices:restore-request]. +Po zalogowaniu wywołujemy metodę `$this->restoreRequest($reqId)`, która pobiera żądanie z sesji i forwarduje na nie. Metoda przy tym weryfikuje, czy żądanie utworzył ten sam użytkownik, który się teraz zalogował. Jeśli zalogowałby się inny użytkownik lub klucz byłby nieprawidłowy, nie zrobi nic i program kontynuuje dalej. +Zobacz poradnik [Jak wrócić do poprzedniej strony |best-practices:restore-request]. -Canonicalization .[#toc-canonization] -------------------------------------- -Prezentery mają jedną naprawdę fajną funkcję, która przyczynia się do lepszego SEO (optymalizacji możliwości znalezienia użytkownika w internecie). Automatycznie zapobiegają one istnieniu zduplikowanej treści na różnych adresach URL. Jeśli wiele adresów URL prowadzi do określonego miejsca docelowego, np. `/index` i `/index?page=1`, framework określa jeden z nich jako główny (kanoniczny) i przekierowuje do niego pozostałe za pomocą kodu HTTP 301. Dzięki temu wyszukiwarki nie będą podwójnie indeksować Twojej witryny i rozcieńczać jej page rank. +Kanonizacja +----------- -Proces ten nazywany jest kanonikalizacją. Kanoniczny adres URL to ten wygenerowany przez [router |routing], zwykle pierwszy pasujący router w kolekcji. +Presentery mają jedną naprawdę świetną cechę, która przyczynia się do lepszego SEO (optymalizacji dla wyszukiwarek internetowych). Automatycznie zapobiegają istnieniu duplikatów treści pod różnymi URL. Jeśli do określonego celu prowadzi więcej adresów URL, np. `/index` i `/index?page=1`, framework określa jeden z nich jako podstawowy (kanoniczny) i pozostałe na niego przekierowuje za pomocą kodu HTTP 301. Dzięki temu wyszukiwarki nie indeksują stron dwukrotnie i nie rozdrabniają ich page rank. -Kanonizacja jest domyślnie włączona i można ją wyłączyć poprzez `$this->autoCanonicalize = false`. +Ten proces nazywa się kanonizacją. Kanonicznym URL jest ten, który generuje [router|routing], zazwyczaj więc pierwsza pasująca trasa w kolekcji. -Przekierowanie nie nastąpi w przypadku żądań AJAX lub POST, ponieważ spowodowałoby to utratę danych lub nie przyniosłoby wartości dodanej z perspektywy SEO. +Kanonizacja jest domyślnie włączona i można ją wyłączyć przez `$this->autoCanonicalize = false`. -Możesz również wywołać kanonizację ręcznie za pomocą metody `canonicalize()`, która przekaże prezentera, akcję i parametry podobnie jak w przypadku metody `link()`. Tworzy link i porównuje go z bieżącym adresem URL. Jeśli się różni, przekierowuje na wygenerowany link. +Do przekierowania nie dochodzi przy żądaniu AJAX lub POST, ponieważ doszłoby do utraty danych lub nie miałoby to wartości dodanej z punktu widzenia SEO. + +Kanonizację można wywołać również manualnie za pomocą metody `canonicalize()`, której podobnie jak metodzie `link()` przekazuje się presenter, akcję i parametry. Tworzy link i porównuje go z aktualnym adresem URL. Jeśli się różnią, przekierowuje na wygenerowany link. ```php -public function actionShow(int $id, string $slug = null): void +public function actionShow(int $id, ?string $slug = null): void { $realSlug = $this->facade->getSlugForId($id); - // přesměruje, pokud $slug se liší od $realSlug + // przekierowuje, jeśli $slug różni się od $realSlug $this->canonicalize('Product:show', [$id, $realSlug]); } ``` -Wydarzenia .[#toc-events] -------------------------- +Zdarzenia +--------- -Oprócz metod `startup()`, `beforeRender()` i `shutdown()`, które są wywoływane w ramach cyklu życia prezentera, można zdefiniować dodatkowe funkcje, które będą wywoływane automatycznie. Presenter definiuje tzw. [zdarzenia |nette:glossary#Events], których handlery dodajesz do pól `$onStartup`, `$onRender` oraz `$onShutdown`. +Oprócz metod `startup()`, `beforeRender()` i `shutdown()`, które są wywoływane jako część cyklu życia presentera, można zdefiniować jeszcze inne funkcje, które mają być automatycznie wywoływane. Presenter definiuje tzw. [zdarzenia |nette:glossary#Eventy zdarzenia], których handlery dodasz do tablic `$onStartup`, `$onRender` i `$onShutdown`. ```php class ArticlePresenter extends Nette\Application\UI\Presenter @@ -388,23 +409,23 @@ class ArticlePresenter extends Nette\Application\UI\Presenter } ``` -Handlery w polu `$onStartup` są wywoływane tuż przed metodą `startup()`, następnie `$onRender` pomiędzy `beforeRender()` i `render()` i wreszcie `$onShutdown` tuż przed `shutdown()`. +Handlery w tablicy `$onStartup` są wywoływane tuż przed metodą `startup()`, dalej `$onRender` między `beforeRender()` a `render()` i na końcu `$onShutdown` tuż przed `shutdown()`. -Odpowiedzi .[#toc-responses] ----------------------------- +Odpowiedzi +---------- -Odpowiedź zwracana przez prezentera jest obiektem implementującym interfejs [api:Nette\Application\Response]. Dostępnych jest wiele gotowych odpowiedzi: +Odpowiedź, którą zwraca presenter, jest obiektem implementującym interfejs [api:Nette\Application\Response]. Dostępnych jest szereg gotowych odpowiedzi: -- [api:Nette\Application\Responses\CallbackResponse] - wysyła wywołanie zwrotne +- [api:Nette\Application\Responses\CallbackResponse] - wysyła callback - [api:Nette\Application\Responses\FileResponse] - wysyła plik - [api:Nette\Application\Responses\ForwardResponse] - forward() - [api:Nette\Application\Responses\JsonResponse] - wysyła JSON - [api:Nette\Application\Responses\RedirectResponse] - przekierowanie -- [api:Nette\Application\Responses\TextResponse] - wyślij tekst +- [api:Nette\Application\Responses\TextResponse] - wysyła tekst - [api:Nette\Application\Responses\VoidResponse] - pusta odpowiedź -Odpowiedzi wysyłane są za pomocą metody `sendResponse()`: +Odpowiedzi wysyła się metodą `sendResponse()`: ```php use Nette\Application\Responses; @@ -415,7 +436,7 @@ $this->sendResponse(new Responses\TextResponse('Hello Nette!')); // Wysyła plik $this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf')); -// Wysyła wywołanie zwrotne +// Odpowiedzią będzie callback $callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse) { if ($httpResponse->getHeader('Content-Type') === 'text/html') { echo '

    Hello

    '; @@ -425,10 +446,55 @@ $this->sendResponse(new Responses\CallbackResponse($callback)); ``` -Dalsza lektura .[#toc-further-reading] -====================================== +Ograniczenie dostępu za pomocą `#[Requires]` .{data-version:3.2.2} +------------------------------------------------------------------ + +Atrybut `#[Requires]` zapewnia zaawansowane możliwości ograniczania dostępu do presenterów i ich metod. Można go użyć do specyfikacji metod HTTP, wymagania żądania AJAX, ograniczenia do tego samego pochodzenia (same origin) oraz dostępu tylko przez forwardowanie. Atrybut można stosować zarówno do klas presenterów, jak i do poszczególnych metod `action()`, `render()`, `handle()` i `createComponent()`. + +Można określić te ograniczenia: +- na metody HTTP: `#[Requires(methods: ['GET', 'POST'])]` +- wymaganie żądania AJAX: `#[Requires(ajax: true)]` +- dostęp tylko z tego samego pochodzenia: `#[Requires(sameOrigin: true)]` +- dostęp tylko przez forward: `#[Requires(forward: true)]` +- ograniczenie do konkretnych akcji: `#[Requires(actions: 'default')]` + +Szczegóły znajdziesz w poradniku [Jak używać atrybutu Requires |best-practices:attribute-requires]. + + +Kontrola metody HTTP +-------------------- + +Presentery w Nette automatycznie weryfikują metodę HTTP każdego przychodzącego żądania. Powodem tej kontroli jest przede wszystkim bezpieczeństwo. Standardowo dozwolone są metody `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH`. + +Jeśli chcesz dodatkowo zezwolić na przykład na metodę `OPTIONS`, użyj do tego atrybutu `#[Requires]` (od Nette Application v3.2): + +```php +#[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])] +class MyPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +W wersji 3.1 weryfikacja odbywa się w `checkHttpMethod()`, która sprawdza, czy metoda określona w żądaniu jest zawarta w tablicy `$presenter->allowedMethods`. Dodanie metody wykonaj w ten sposób: + +```php +class MyPresenter extends Nette\Application\UI\Presenter +{ + protected function checkHttpMethod(): void + { + $this->allowedMethods[] = 'OPTIONS'; + parent::checkHttpMethod(); + } +} +``` + +Ważne jest podkreślenie, że jeśli zezwolisz na metodę `OPTIONS`, musisz ją następnie również odpowiednio obsłużyć w ramach swojego presentera. Metoda jest często używana jako tzw. preflight request, który przeglądarka automatycznie wysyła przed rzeczywistym żądaniem, gdy trzeba sprawdzić, czy żądanie jest dozwolone z punktu widzenia polityki CORS (Cross-Origin Resource Sharing). Jeśli zezwolisz na metodę, ale nie zaimplementujesz prawidłowej odpowiedzi, może to prowadzić do niespójności i potencjalnych problemów bezpieczeństwa. + + +Dalsza lektura +============== -- [Wstrzykiwanie metod i atrybutów |best-practices:inject-method-attribute] -- [Składanie prezenterów z cech |best-practices:presenter-traits] -- [Przekazywanie ustawień do prezent |best-practices:passing-settings-to-presenters]erów -- [Jak wrócić do wcześniejszej strony |best-practices:restore-request] +- [Metody i atrybuty inject |best-practices:inject-method-attribute] +- [Składanie presenterów z trait |best-practices:presenter-traits] +- [Przekazywanie ustawień do presenterów |best-practices:passing-settings-to-presenters] +- [Jak wrócić do poprzedniej strony |best-practices:restore-request] diff --git a/application/pl/routing.texy b/application/pl/routing.texy index 1cc006af6e..34577ae078 100644 --- a/application/pl/routing.texy +++ b/application/pl/routing.texy @@ -1,29 +1,28 @@ -Routing -******* +Routowanie +**********
    -Router zajmuje się wszystkim wokół adresów URL, więc nie musisz już o nich myśleć. Zobaczmy: +Router odpowiada za wszystko związane z adresami URL, abyś Ty już nie musiał się nad nimi zastanawiać. Pokażemy: -- jak skonfigurować router, aby adresy URL były takie, jak chcesz. -- porozmawiamy o SEO i przekierowaniu -- a my pokażemy ci, jak napisać własny router +- jak ustawić router, aby URL były zgodne z oczekiwaniami +- powiemy o SEO i przekierowaniach +- i pokażemy, jak napisać własny router
    -Bardziej ludzkie adresy URL (lub fajne lub ładne adresy URL) są bardziej użyteczne, bardziej zapamiętywane i przyczyniają się pozytywnie do SEO. Nette ma to na uwadze i w pełni odpowiada na potrzeby deweloperów. Możesz zaprojektować dokładnie taką strukturę adresu URL, jaką chcesz dla swojej aplikacji. -Możesz nawet zaprojektować go po tym, jak aplikacja jest gotowa, ponieważ można to zrobić bez interwencji kodu lub szablonu. Dzieje się tak dlatego, że jest on zdefiniowany w elegancki sposób w jednym [miejscu |#Integration], w routerze, a więc nie jest rozproszony w postaci adnotacji we wszystkich prezenterach. +Bardziej ludzkie URL (lub też cool czy pretty URL) są bardziej użyteczne, łatwiejsze do zapamiętania i pozytywnie wpływają na SEO. Nette o tym myśli i w pełni wychodzi naprzeciw deweloperom. Możesz dla swojej aplikacji zaprojektować dokładnie taką strukturę adresów URL, jaką będziesz chciał. Możesz ją zaprojektować nawet w chwili, gdy aplikacja jest już gotowa, ponieważ obejdzie się to bez ingerencji w kod czy szablony. Definiuje się ją bowiem w elegancki sposób w jednym [jedynym miejscu |#Włączenie do aplikacji], w routerze, i nie jest rozproszona w formie adnotacji we wszystkich presenterach. -Router w Nette jest wyjątkowy, ponieważ jest **dwukierunkowy.** Może zarówno dekodować adresy URL w żądaniu HTTP, jak i tworzyć linki. Odgrywa więc istotną rolę w [aplikacji Nette |how-it-works#Nette-Application], ponieważ decyduje, który prezenter i akcja wykonają bieżące żądanie, ale jest również używany do [generowania adresów URL |creating-links] w szablonie itp. +Router w Nette jest wyjątkowy tym, że jest **dwukierunkowy.** Potrafi zarówno dekodować URL w żądaniu HTTP, jak i tworzyć linki. Odgrywa więc kluczową rolę w [Nette Application |how-it-works#Nette Application], ponieważ nie tylko decyduje o tym, który presenter i akcja będzie wykonywać aktualne żądanie, ale także wykorzystuje się go do [generowania URL |creating-links] w szablonie itp. -Jednak router nie jest ograniczony do tego zastosowania, możesz go użyć w aplikacjach, w których prezentery nie są w ogóle używane, dla interfejsów API REST itp. Więcej szczegółów można znaleźć w sekcji [osobnego |#Standalone-Use] przypadku [użycia |#Standalone-Use]. +Jednak router nie jest ograniczony tylko do tego zastosowania, można go używać w aplikacjach, gdzie w ogóle nie używa się presenterów, dla REST API, itd. Więcej w części [#Samostatné použití]. -Kolekcja routerów .[#toc-route-collection] -========================================== +Kolekcja tras +============= -Najwygodniejszy sposób definiowania postaci adresów URL w aplikacji zapewnia klasa [api:Nette\Application\Routers\RouteList] Definicja składa się z listy tzw. rout, czyli masek adresów URL oraz powiązanych z nimi prezenterów i akcji wykorzystujących proste API. Nie musimy w żaden sposób nazywać tras. +Najprzyjemniejszy sposób definiowania postaci adresów URL w aplikacji oferuje klasa [api:Nette\Application\Routers\RouteList]. Definicja składa się z listy tzw. tras (routes), czyli masek adresów URL i przypisanych do nich presenterów i akcji za pomocą prostego API. Tras nie musimy w żaden sposób nazywać. ```php $router = new Nette\Application\Routers\RouteList; @@ -32,122 +31,122 @@ $router->addRoute('article/', 'Article:view'); // ... ``` -Przykład mówi, że jeśli otworzymy w przeglądarce stronę `https://domain.com/rss.xml` zostanie wyświetlony z akcją `rss`, jeśli `https://domain.com/article/12` zostanie wyświetlony z akcją `view`, itd. Jeśli odpowiednia trasa nie zostanie znaleziona, aplikacja Nette odpowiada rzuceniem [wyjątku BadRequestException |api:Nette\Application\BadRequestException], który jest wyświetlany użytkownikowi jako strona błędu 404 Not Found. +Przykład mówi, że jeśli w przeglądarce otworzymy `https://domain.com/rss.xml`, wyświetli się presenter `Feed` z akcją `rss`, jeśli `https://domain.com/article/12`, wyświetli się presenter `Article` z akcją `view` itd. W przypadku nieznalezienia odpowiedniej trasy Nette Application reaguje wyrzuceniem wyjątku [BadRequestException |api:Nette\Application\BadRequestException], który użytkownikowi wyświetli się jako strona błędu 404 Not Found. -Kolejność tras .[#toc-order-of-routes] --------------------------------------- +Kolejność tras +-------------- -Kolejność**, w jakiej trasy są wymienione, jest całkowicie **kluczowa**, ponieważ są one oceniane kolejno od góry do dołu. Zasada jest taka, że deklarujemy trasy **od konkretnej do ogólnej**: +Absolutnie **kluczowa jest kolejność**, w jakiej są wymienione poszczególne trasy, ponieważ są one ewaluowane kolejno od góry do dołu. Obowiązuje zasada, że trasy deklarujemy **od szczegółowych do ogólnych**: ```php -// BŁĄD: 'rss.xml' łapie pierwszą rutę i traktuje ten ciąg jako +// ŹLE: 'rss.xml' przechwyci pierwsza trasa i rozumie ten ciąg jako $router->addRoute('', 'Article:view'); $router->addRoute('rss.xml', 'Feed:rss'); -// OK +// DOBRZE $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('', 'Article:view'); ``` -Trasy są również oceniane od góry do dołu podczas generowania linków: +Trasy są ewaluowane od góry do dołu również przy generowaniu linków: ```php -// ŠPATNĚ: odkaz na 'Feed:rss' vygeneruje jako 'admin/feed/rss' +// ŹLE: link do 'Feed:rss' wygeneruje jako 'admin/feed/rss' $router->addRoute('admin//', 'Admin:default'); $router->addRoute('rss.xml', 'Feed:rss'); -// DOBŘE +// DOBRZE $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('admin//', 'Admin:default'); ``` -Nie będziemy ukrywać przed Tobą, że prawidłowe trasowanie wymaga pewnych umiejętności. Dopóki nie wejdziesz w to, [panel rout |#Debugging-Router]ingu będzie przydatnym narzędziem. +Nie będziemy przed Tobą ukrywać, że prawidłowe zestawienie tras wymaga pewnej wprawy. Zanim ją opanujesz, użytecznym pomocnikiem będzie [panel routingu |#Debugowanie routera]. -Maska i parametry .[#toc-mask-and-parameters] ---------------------------------------------- +Maska i parametry +----------------- -Maska opisuje względną ścieżkę od katalogu głównego witryny. Najprostszą maską jest statyczny adres URL: +Maska opisuje ścieżkę względną od katalogu głównego strony internetowej. Najprostszą maską jest statyczny URL: ```php $router->addRoute('products', 'Products:default'); ``` -Często maski zawierają tak zwane **parametry**. Są one wymienione w nawiasach spiczastych (np. ``) i są przekazywane do docelowego prezentera, na przykład do metody `renderShow(int $year)` lub do trwałego parametru `$year`: +Często maski zawierają tzw. **parametry**. Są one podane w nawiasach ostrych (np. ``) i są przekazywane do docelowego presentera, na przykład do metody `renderShow(int $year)` lub do trwałego parametru `$year`: ```php $router->addRoute('chronicle/', 'History:show'); ``` -Przykład mówi, że jeśli otworzymy stronę `https://example.com/chronicle/2020` z akcją `show` i parametrem `year: 2020`. +Przykład mówi, że jeśli w przeglądarce otworzymy `https://example.com/chronicle/2020`, wyświetli się presenter `History` z akcją `show` i parametrem `year: 2020`. -Możemy określić domyślną wartość parametrów bezpośrednio w masce, czyniąc je opcjonalnymi: +Parametrom możemy określić wartość domyślną bezpośrednio w masce i tym samym stają się one opcjonalne: ```php $router->addRoute('chronicle/', 'History:show'); ``` -Trasa będzie teraz akceptować również adres URL `https://example.com/chronicle/` z parametrem `year: 2020`. +Trasa będzie teraz akceptować również URL `https://example.com/chronicle/`, które ponownie wyświetli `History:show` z parametrem `year: 2020`. -Oczywiście parametrem może być również prezenter i nazwa wydarzenia. Na przykład: +Parametrem może być oczywiście również nazwa presentera i akcji. Na przykład tak: ```php $router->addRoute('/', 'Home:default'); ``` -Podana trasa przyjmuje np. adresy URL o postaci `/article/edit` lub również `/catalog/list` i rozumie je jako prezentery i wydarzenia `Article:edit` i `Catalog:list`. +Podana trasa akceptuje np. URL w postaci `/article/edit` lub także `/catalog/list` i rozumie je jako presentery i akcje `Article:edit` i `Catalog:list`. -Jednocześnie nadaje parametrom `presenter` i `action` wartości domyślne `Home` i `default`, a zatem są one również opcjonalne. Tak więc router akceptuje również adresy URL w postaci `/article` i traktuje je jako `Article:default`. Lub odwrotnie, link do `Product:default` wygeneruje ścieżkę `/product`, link do domyślnego `Home:default` wygeneruje ścieżkę `/`. +Jednocześnie nadaje parametrom `presenter` i `action` wartości domyślne `Home` i `default`, a zatem są one również opcjonalne. Tak więc trasa akceptuje również URL w postaci `/article` i rozumie go jako `Article:default`. Lub odwrotnie, link do `Product:default` wygeneruje ścieżkę `/product`, link do domyślnego `Home:default` ścieżkę `/`. -Maska może opisywać nie tylko ścieżkę względną z korzenia strony, ale także ścieżkę bezwzględną, jeśli zaczyna się od ukośnika, a nawet pełny bezwzględny adres URL, jeśli zaczyna się od dwóch ukośników: +Maska może opisywać nie tylko ścieżkę względną od katalogu głównego strony internetowej, ale także ścieżkę absolutną, jeśli zaczyna się od ukośnika, lub nawet cały absolutny URL, jeśli zaczyna się od dwóch ukośników: ```php -// w stosunku do głównej części dokumentu +// względnie do document root $router->addRoute('/', /* ... */); -// ścieżka bezwzględna (względem domeny) +// ścieżka absolutna (względna do domeny) $router->addRoute('//', /* ... */); -// bezwzględny adres URL zawierający domenę (względny w stosunku do schematu) +// absolutny URL włącznie z domeną (względny do schematu) $router->addRoute('//.example.com//', /* ... */); -// bezwzględny adres URL z uwzględnieniem schematu +// absolutny URL włącznie ze schematem $router->addRoute('https://.example.com//', /* ... */); ``` -Wyrażenia walidacyjne .[#toc-validation-expressions] ----------------------------------------------------- +Wyrażenia walidacyjne +--------------------- -Warunek walidacji może być określony dla każdego parametru przy użyciu [wyrażenia regularnego |https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Na przykład określamy, że parametr `id` może przyjmować tylko cyfry za pomocą wyrażenia regularnego `\d+`: +Dla każdego parametru można ustalić warunek walidacyjny za pomocą [wyrażenia regularnego|https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Na przykład dla parametru `id` określimy, że może przyjmować tylko cyfry za pomocą wyrażenia regularnego `\d+`: ```php $router->addRoute('/[/]', /* ... */); ``` -Domyślnym wyrażeniem regularnym dla wszystkich parametrów jest `[^/]+`, czyli wszystko poza ukośnikiem. Jeśli parametr ma akceptować ukośniki, to podajemy wyrażenie `.+`: +Domyślnym wyrażeniem regularnym dla wszystkich parametrów jest `[^/]+`, tj. wszystko oprócz ukośnika. Jeśli parametr ma akceptować również ukośniki, podamy wyrażenie `.+`: ```php -// akceptuje https://example.com/a/b/c, path bude 'a/b/c' +// akceptuje https://example.com/a/b/c, path będzie 'a/b/c' $router->addRoute('', /* ... */); ``` -Sekwencje opcjonalne .[#toc-optional-sequences] ------------------------------------------------ +Sekwencje opcjonalne +-------------------- -Części opcjonalne w masce można zaznaczyć za pomocą nawiasów kwadratowych. Każda część maski może być opcjonalna, można też dołączyć parametry: +W masce można oznaczać opcjonalne części za pomocą nawiasów kwadratowych. Opcjonalna może być dowolna część maski, mogą się w niej znajdować również parametry: ```php $router->addRoute('[/]', /* ... */); -// Akceptuje cesty: -// /en/download => lang => cs, name => download +// Akceptuje ścieżki: +// /cs/download => lang => cs, name => download // /download => lang => null, name => download ``` -Kiedy parametr jest częścią sekwencji opcjonalnej, naturalnie staje się również opcjonalny. Jeśli nie ma wartości domyślnej, będzie to wartość null. +Gdy parametr jest częścią sekwencji opcjonalnej, staje się oczywiście również opcjonalny. Jeśli nie ma podanej wartości domyślnej, będzie miał wartość null. Opcjonalne części mogą być również w domenie: @@ -155,7 +154,7 @@ Opcjonalne części mogą być również w domenie: $router->addRoute('//[.]example.com//', /* ... */); ``` -Sekwencje mogą być dowolnie osadzane i łączone: +Sekwencje można dowolnie zagnieżdżać i kombinować: ```php $router->addRoute( @@ -163,14 +162,14 @@ $router->addRoute( 'Home:default', ); -// Akceptuje cesty: -// /en/hello +// Akceptuje ścieżki: +// /cs/hello // /en-us/hello // /hello // /hello/page-12 ``` -Podczas generowania adresów URL poszukiwany jest najkrótszy wariant, więc wszystko, co można pominąć, jest pomijane. Dlatego też np. routa `index[.html]` generuje ścieżkę `/index`. Możliwe jest odwrócenie zachowania poprzez umieszczenie wykrzyknika po lewym nawiasie kwadratowym: +Przy generowaniu URL dąży się do najkrótszej warianty, więc wszystko, co można pominąć, jest pomijane. Dlatego na przykład trasa `index[.html]` generuje ścieżkę `/index`. Odwrócić zachowanie można przez podanie wykrzyknika za lewym nawiasem kwadratowym: ```php // akceptuje /hello i /hello.html, generuje /hello @@ -180,29 +179,29 @@ $router->addRoute('[.html]', /* ... */); $router->addRoute('[!.html]', /* ... */); ``` -Parametry opcjonalne (tzn. parametry posiadające wartość domyślną) bez nawiasów kwadratowych zachowują się zasadniczo tak, jakby były objęte nawiasami, jak poniżej: +Parametry opcjonalne (tj. parametry mające wartość domyślną) bez nawiasów kwadratowych zachowują się w zasadzie tak, jakby były ujęte w nawiasy w następujący sposób: ```php $router->addRoute('//', /* ... */); -// odpovídá tomuto: +// odpowiada temu: $router->addRoute('[/[/[]]]', /* ... */); ``` -Jeśli chcemy wpłynąć na zachowanie ukośnika spiczastego tak, aby np. zamiast `/home/` generowany był tylko `/home`, można to zrobić w następujący sposób: +Jeśli chcielibyśmy wpłynąć na zachowanie końcowego ukośnika, aby np. zamiast `/home/` generowało się tylko `/home`, można to osiągnąć w ten sposób: ```php $router->addRoute('[[/[/]]]', /* ... */); ``` -Żbiki .[#toc-wildcards] ------------------------ +Symbole wieloznaczne +-------------------- -Możesz użyć następujących symboli wieloznacznych w masce ścieżki absolutnej, aby uniknąć, na przykład, konieczności wpisania domeny do maski, która może się różnić w środowiskach deweloperskich i produkcyjnych: +W masce ścieżki absolutnej możemy użyć następujących symboli wieloznacznych i uniknąć w ten sposób np. konieczności zapisywania w masce domeny, która może się różnić w środowisku deweloperskim i produkcyjnym: -- `%tld%` = domena najwyższego poziomu, np. `com` lub `org` -- `%sld%` = domena drugiego poziomu, np. `example` +- `%tld%` = top level domain, np. `com` lub `org` +- `%sld%` = second level domain, np. `example` - `%domain%` = domena bez subdomen, np. `example.com` - `%host%` = cały host, np. `www.example.com` - `%basePath%` = ścieżka do katalogu głównego @@ -213,10 +212,10 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ @@ -225,7 +224,7 @@ $router->addRoute('/[/]', [ ]); ``` -Możemy też użyć tego formularza, zauważając nadpisanie wyrażenia regularnego walidacji: +Dla bardziej szczegółowej specyfikacji można użyć jeszcze bardziej rozszerzonej formy, gdzie oprócz wartości domyślnych możemy ustawić również inne właściwości parametrów, takie jak na przykład walidacyjne wyrażenie regularne (zobacz parametr `id`): ```php use Nette\Routing\Route; @@ -243,19 +242,19 @@ $router->addRoute('/[/]', [ ]); ``` -Te bardziej verbose formaty są przydatne do dodawania dodatkowych metadanych. +Ważne jest zauważenie, że jeśli parametry zdefiniowane w tablicy nie są wymienione w masce ścieżki, ich wartości nie można zmienić, nawet za pomocą parametrów query podanych za znakiem zapytania w URL. -Filtry i tłumaczenia .[#toc-filters-and-translations] ------------------------------------------------------ +Filtry i tłumaczenia +-------------------- -Kod źródłowy aplikacji piszemy w języku angielskim, ale jeśli strona ma mieć czeski adres URL, to prosty typ routingu: +Kody źródłowe aplikacji piszemy w języku angielskim, ale jeśli strona ma mieć polskie URL, to proste routowanie typu: ```php $router->addRoute('/', 'Home:default'); ``` -wygeneruje angielski adres URL, taki jak `/product/123` lub `/cart`. Jeśli chcemy, aby prezentery i akcje w adresie URL były reprezentowane przez czeskie słowa (np. `/produkt/123` lub `/kosik`), możemy użyć słownika tłumaczeń. Aby go napisać, potrzebujemy bardziej "verbose" wersji drugiego parametru: +będzie generować angielskie URL, takie jak `/product/123` lub `/cart`. Jeśli chcemy mieć presentery i akcje w URL reprezentowane polskimi słowami (np. `/produkt/123` lub `/koszyk`), możemy wykorzystać słownik tłumaczeń. Do jego zapisu potrzebujemy już "bardziej gadatliwej" warianty drugiego parametru: ```php use Nette\Routing\Route; @@ -264,26 +263,26 @@ $router->addRoute('/', [ 'presenter' => [ Route::Value => 'Home', Route::FilterTable => [ - // řetězec v URL => presenter + // ciąg w URL => presenter 'produkt' => 'Product', - 'kosik' => 'Cart', + 'koszyk' => 'Cart', 'katalog' => 'Catalog', ], ], 'action' => [ Route::Value => 'default', Route::FilterTable => [ - 'seznam' => 'list', + 'lista' => 'list', ], ], ]); ``` -Wiele kluczy słownika tłumaczeń może prowadzić do tego samego prezentera. Dzięki temu zostaną utworzone różne aliasy do niego. Ostatni klucz jest traktowany jako wariant kanoniczny (czyli ten, który znajdzie się w wygenerowanym adresie URL). +Wiele kluczy słownika tłumaczeń może prowadzić do tego samego presentera. W ten sposób tworzy się dla niego różne aliasy. Za wariant kanoniczny (czyli ten, który będzie w wygenerowanym URL) uważa się ostatni klucz. -Tablicę translacji można w ten sposób zastosować do każdego parametru. Jeśli nie istnieje tłumaczenie, przyjmowana jest wartość oryginalna. To zachowanie można zmienić, dodając `Route::FilterStrict => true`, a następnie router odrzuci adres URL, jeśli wartość nie znajduje się w słowniku. +Tabelę tłumaczeń można w ten sposób użyć dla dowolnego parametru. Przy czym jeśli tłumaczenie nie istnieje, bierze się pierwotną wartość. To zachowanie możemy zmienić dodając `Route::FilterStrict => true` i trasa wtedy odrzuci URL, jeśli wartość nie jest w słowniku. -Oprócz słownika tłumaczeń w postaci tablicy można wdrożyć niestandardowe funkcje tłumaczeniowe. +Oprócz słownika tłumaczeń w postaci tablicy można zastosować również własne funkcje tłumaczące. ```php use Nette\Routing\Route; @@ -299,15 +298,15 @@ $router->addRoute('//', [ ]); ``` -Funkcja `Route::FilterIn` dokonuje translacji pomiędzy parametrem w adresie URL a ciągiem znaków, który następnie jest przekazywany do prezentera, funkcja `FilterOut` dokonuje konwersji w przeciwnym kierunku. +Funkcja `Route::FilterIn` konwertuje między parametrem w URL a ciągiem, który następnie jest przekazywany do presentera, funkcja `FilterOut` zapewnia konwersję w przeciwnym kierunku. -Parametry `presenter`, `action` i `module` mają już predefiniowane filtry, które konwertują pomiędzy stylami PascalCase i camelCase używanymi w URL. Domyślna wartość parametrów jest już zapisana w formie przekształconej, więc np. w przypadku prezentera piszemy ``nie ``. +Parametry `presenter`, `action` i `module` już mają predefiniowane filtry, które konwertują między stylem PascalCase resp. camelCase a kebab-case używanym w URL. Wartość domyślna parametrów zapisuje się już w przekształconej postaci, więc na przykład w przypadku presentera piszemy ``, a nie ``. -Filtry ogólne .[#toc-general-filters] -------------------------------------- +Filtry ogólne +------------- -Oprócz filtrów specyficznych dla parametrów, możemy również zdefiniować filtry ogólne, które otrzymują tablicę asocjacyjną wszystkich parametrów, które mogą w dowolny sposób modyfikować, a następnie zwracają je. Definiujemy ogólne filtry pod kluczem `null`. +Oprócz filtrów przeznaczonych dla konkretnych parametrów możemy zdefiniować również filtry ogólne, które otrzymają tablicę asocjacyjną wszystkich parametrów, które mogą dowolnie modyfikować, a następnie je zwrócą. Filtry ogólne definiujemy pod kluczem `null`. ```php use Nette\Routing\Route; @@ -315,42 +314,65 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => 'Home', 'action' => 'default', - null => [ + '' => [ Route::FilterIn => function (array $params): array { /* ... */ }, Route::FilterOut => function (array $params): array { /* ... */ }, ], ]); ``` -Filtry generyczne dają możliwość modyfikowania zachowania routera w absolutnie dowolny sposób. Możemy na przykład użyć ich do modyfikacji parametrów na podstawie innych parametrów. Na przykład, tłumaczenie `` a `` na podstawie aktualnej wartości parametru ``. +Filtry ogólne dają możliwość modyfikacji zachowania trasy w absolutnie dowolny sposób. Możemy je użyć na przykład do modyfikacji parametrów na podstawie innych parametrów. Na przykład tłumaczenie `` i `` na podstawie aktualnej wartości parametru ``. -Jeśli parametr ma zdefiniowany filtr niestandardowy i jednocześnie istnieje filtr ogólny, niestandardowy `FilterIn` jest wykonywany przed ogólnym i odwrotnie, ogólny `FilterOut` jest wykonywany przed niestandardowym. Tak więc wewnątrz filtra ogólnego wartości parametrów `presenter` i `action` zapisywane są odpowiednio w stylu PascalCase i camelCase. +Jeśli parametr ma zdefiniowany własny filtr i jednocześnie istnieje filtr ogólny, wykonuje się własny `FilterIn` przed ogólnym i odwrotnie ogólny `FilterOut` przed własnym. Zatem wewnątrz filtra ogólnego wartości parametrów `presenter` resp. `action` są zapisane w stylu PascalCase resp. camelCase. -OneWay .[#toc-oneway-flag] --------------------------- +Jednokierunkowe OneWay +---------------------- -Jednokierunkowe trasy są używane do zachowania funkcjonalności starych adresów URL, których aplikacja już nie generuje, ale nadal akceptuje. Oznaczamy je flagą `OneWay`: +Trasy jednokierunkowe używa się do zachowania funkcjonalności starych URL, których aplikacja już nie generuje, ale nadal akceptuje. Oznaczamy je flagą `OneWay`: ```php -// staré URL /product-info?id=123 +// stare URL /product-info?id=123 $router->addRoute('product-info', 'Product:detail', $router::ONE_WAY); -// nové URL /product/123 +// nowe URL /product/123 $router->addRoute('product/', 'Product:detail'); ``` -Kiedy użytkownik uzyskuje dostęp do starego adresu URL, prezenter automatycznie przekierowuje na nowy adres URL, aby wyszukiwarki nie indeksowały tych stron podwójnie (patrz [SEO i kanoniczność |#SEO-and-Canonization]). +Przy dostępie do starego URL presenter automatycznie przekierowuje na nowy URL, dzięki czemu wyszukiwarki nie zaindeksują tych stron dwukrotnie (zobacz [#SEO i kanonizacja]). -Moduły .[#toc-modules] ----------------------- +Dynamiczne routowanie z callbackami +----------------------------------- + +Dynamiczne routowanie z callbackami pozwala przypisać trasom bezpośrednio funkcje (callbacki), które zostaną wykonane, gdy dana ścieżka zostanie odwiedzona. Ta elastyczna funkcjonalność pozwala szybko i efektywnie tworzyć różne punkty końcowe (endpoints) dla Twojej aplikacji: + +```php +$router->addRoute('test', function () { + echo 'jesteś pod adresem /test'; +}); +``` + +Możesz również zdefiniować w masce parametry, które zostaną automatycznie przekazane do Twojego callbacku: -Jeśli mamy wiele tras, które wpadają do wspólnego [modułu |modules], używamy `withModule()`: +```php +$router->addRoute('', function (string $lang) { + echo match ($lang) { + 'cs' => 'Witaj na czeskiej wersji naszej strony!', + 'en' => 'Welcome to the English version of our website!', + }; +}); +``` + + +Moduły +------ + +Jeśli mamy więcej tras, które należą do wspólnego [modułu |directory-structure#Presentery i szablony], wykorzystamy `withModule()`: ```php $router = new RouteList; $router->withModule('Forum') // następujące trasy są częścią modułu Forum - ->addRoute('rss', 'Feed:rss') // prezenterem będzie Forum:Feed + ->addRoute('rss', 'Feed:rss') // presenter będzie Forum:Feed ->addRoute('/') ->withModule('Admin') // następujące trasy są częścią modułu Forum:Admin @@ -360,17 +382,17 @@ $router->withModule('Forum') // następujące trasy są częścią modułu Forum Alternatywą jest użycie parametru `module`: ```php -// URL manage/dashboard/default mapuje na prezenter Admin:Dashboard +// URL manage/dashboard/default mapuje się na presenter Admin:Dashboard $router->addRoute('manage//', [ 'module' => 'Admin', ]); ``` -Subdomeny .[#toc-subdomains] ----------------------------- +Subdomeny +--------- -Kolekcje tras mogą być podzielone na subdomeny: +Kolekcje tras możemy dzielić według subdomen: ```php $router = new RouteList; @@ -379,7 +401,7 @@ $router->withDomain('example.com') ->addRoute('/'); ``` -W nazwie domeny można również stosować symbole [wieloznaczne |#Excluding]: +W nazwie domeny można użyć również [#Symbole wieloznaczne]: ```php $router = new RouteList; @@ -388,23 +410,23 @@ $router->withDomain('example.%tld%') ``` -Prefiks ścieżki .[#toc-path-prefix] ------------------------------------ +Prefiks ścieżki +--------------- -Kolekcje tras mogą być podzielone według ścieżki w adresie URL: +Kolekcje tras możemy dzielić według ścieżki w URL: ```php $router = new RouteList; $router->withPath('eshop') - ->addRoute('rss', 'Feed:rss') // chytry URL /eshop/rss - ->addRoute('/'); // chytry URL /eshop// + ->addRoute('rss', 'Feed:rss') // łapie URL /eshop/rss + ->addRoute('/'); // łapie URL /eshop// ``` -Kombinacja .[#toc-combinations] -------------------------------- +Kombinacje +---------- -Powyższe podziały można ze sobą łączyć: +Powyższe podziały możemy wzajemnie kombinować: ```php $router = (new RouteList) @@ -424,40 +446,40 @@ $router = (new RouteList) ``` -Parametry zapytań .[#toc-query-parameters] ------------------------------------------- +Parametry Query +--------------- -Maski mogą również zawierać parametry zapytania (parametry po znaku zapytania w adresie URL). Nie można zdefiniować dla nich wyrażenia walidacyjnego, ale można zmienić nazwę, pod którą są przekazywane do prezentera: +Maski mogą również zawierać parametry query (parametry za znakiem zapytania w URL). Nie można im zdefiniować wyrażenia walidacyjnego, ale można zmienić nazwę, pod którą zostaną przekazane do presentera: ```php -// parametr zapytania 'cat', który chcemy wykorzystać w aplikacji pod nazwą 'categoryId' +// parametr query 'cat' chcemy w aplikacji użyć pod nazwą 'categoryId' $router->addRoute('product ? id= & cat=', /* ... */); ``` -Parametry Foo .[#toc-foo-parameters] ------------------------------------- +Parametry Foo +------------- -Teraz wchodzimy głębiej. Parametry Foo są w zasadzie nienazwanymi parametrami, które pozwalają na dopasowanie wyrażenia regularnego. Przykładem jest rutyna, która akceptuje `/index`, `/index.html`, `/index.htm`, i `/index.php`: +Teraz już idziemy głębiej. Parametry Foo to w zasadzie nienazwane parametry, które umożliwiają dopasowanie wyrażenia regularnego. Przykładem jest trasa akceptująca `/index`, `/index.html`, `/index.htm` i `/index.php`: ```php $router->addRoute('index', /* ... */); ``` -Możesz również jawnie określić ciąg znaków, który ma być użyty podczas generowania adresu URL. Ciąg musi być umieszczony bezpośrednio po znaku zapytania. Poniższa procedura jest podobna do poprzedniej, ale generuje `/index.html` zamiast `/index`, ponieważ łańcuch `.html` jest ustawiony jako wartość generowania: +Można również jawnie zdefiniować ciąg, który będzie użyty przy generowaniu URL. Ciąg musi być umieszczony bezpośrednio za znakiem zapytania. Następująca trasa jest podobna do poprzedniej, ale generuje `/index.html` zamiast `/index`, ponieważ ciąg `.html` jest ustawiony jako wartość generująca: ```php $router->addRoute('index', /* ... */); ``` -Integracja aplikacji .[#toc-integration] -======================================== +Włączenie do aplikacji +====================== -Aby zintegrować stworzony router z aplikacją, musimy powiedzieć o nim kontenerowi DI. Najprostszym sposobem na to jest przygotowanie fabryki tworzącej obiekt routera i powiedzenie konfiguracji kontenera, aby go użyć. Załóżmy, że w tym celu napiszemy metodę `App\Router\RouterFactory::createRouter()`: +Aby włączyć utworzony router do aplikacji, musimy o nim powiedzieć kontenerowi DI. Najłatwiejszą drogą jest przygotowanie fabryki, która wyprodukuje obiekt routera, i poinformowanie w konfiguracji kontenera, że ma jej użyć. Powiedzmy, że w tym celu napiszemy metodę `App\Core\RouterFactory::createRouter()`: ```php -namespace App\Router; +namespace App\Core; use Nette\Application\Routers\RouteList; @@ -472,14 +494,14 @@ class RouterFactory } ``` -Następnie wpisujemy do [konfiguracji |dependency-injection:services]: +Do [konfiguracji |dependency-injection:services] następnie zapiszemy: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter ``` -Wszelkie zależności, na przykład od bazy danych itp, są przekazywane do metody fabrycznej jako jej parametry przez [autowiring |dependency-injection:autowiring]: +Wszelkie zależności, na przykład od bazy danych itp., zostaną przekazane do metody fabrycznej jako jej parametry za pomocą [autowiringu|dependency-injection:autowiring]: ```php public static function createRouter(Nette\Database\Connection $db): RouteList @@ -489,25 +511,25 @@ public static function createRouter(Nette\Database\Connection $db): RouteList ``` -SimpleRouter .[#toc-simplerouter] -================================= +SimpleRouter +============ -Znacznie prostszym routerem od kolekcji rout jest [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Używamy go, jeśli nie mamy specjalnych wymagań co do kształtu adresu URL, jeśli `mod_rewrite` (lub jego alternatywy) nie jest dostępny, lub jeśli nie chcemy jeszcze zajmować się ładnymi adresami URL. +Znacznie prostszym routerem niż kolekcja tras jest [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Użyjemy go wtedy, gdy nie mamy szczególnych wymagań co do kształtu URL, gdy nie jest dostępny `mod_rewrite` (lub jego alternatywy) lub gdy na razie nie chcemy zajmować się ładnymi URL. -Generuje on adresy URL w mniej więcej następującej formie: +Generuje adresy mniej więcej w tym kształcie: ``` http://example.com/?presenter=Product&action=detail&id=123 ``` -Parametrem konstruktora SimpleRouter jest domyślny prezenter & akcja, do której zostaniemy skierowani, jeśli otworzymy stronę bez parametrów, np. `http://example.com/`. +Parametrem konstruktora SimpleRoutera jest domyślny presenter & akcja, na który ma kierować, jeśli otworzymy stronę bez parametrów, np. `http://example.com/`. ```php -// výchozím presenterem bude 'Home' a akce 'default' +// domyślnym presenterem będzie 'Home' a akcja 'default' $router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` -Zalecane jest zdefiniowanie SimpleRoutera bezpośrednio w [konfiguracji |dependency-injection:services]: +Zalecamy SimpleRouter bezpośrednio zdefiniować w [konfiguracji |dependency-injection:services]: ```neon services: @@ -515,22 +537,22 @@ services: ``` -SEO i kanonizacja .[#toc-seo-and-canonization] -============================================== +SEO i kanonizacja +================= -Framework przyczynia się do SEO (optymalizacja możliwości znalezienia w Internecie) poprzez zapobieganie duplikacji treści w różnych adresach URL. Jeśli wiele adresów URL prowadzi do określonego miejsca docelowego, np. `/index` i `/index.html`, framework wyznacza pierwszy z nich jako główny (kanoniczny) adres URL i przekierowuje do niego pozostałe przy użyciu kodu HTTP 301. W ten sposób wyszukiwarki nie będą podwójnie indeksować Twojej witryny i rozcieńczać jej page rank. +Framework przyczynia się do SEO (optymalizacji dla wyszukiwarek internetowych) przez zapobieganie duplikacji treści pod różnymi URL. Jeśli do określonego celu prowadzi więcej adresów, np. `/index` i `/index.html`, framework pierwszy z nich określa jako podstawowy (kanoniczny) i pozostałe na niego przekierowuje za pomocą kodu HTTP 301. Dzięki temu wyszukiwarki nie indeksują stron dwukrotnie i nie rozdrabniają ich page rank. -Proces ten nazywany jest kanonikalizacją. Kanoniczny adres URL to ten wygenerowany przez router, czyli pierwszy pasujący router w kolekcji bez flagi OneWay. Dlatego w kolekcji wymieniamy **pierwsze trasy**. +Ten proces nazywa się kanonizacją. Kanonicznym URL jest ten, który generuje router, tj. pierwsza pasująca trasa w kolekcji bez flagi OneWay. Dlatego w kolekcji podajemy **podstawowe trasy jako pierwsze**. -Kanonizacja jest wykonywana przez prezentera, zobacz rozdział [Kanonizacja |presenters#Canonization], aby uzyskać więcej szczegółów. +Kanonizację przeprowadza presenter, więcej w rozdziale [kanonizacja |presenters#Kanonizacja]. -HTTPS .[#toc-https] -=================== +HTTPS +===== -Aby korzystać z protokołu HTTPS, należy włączyć go na swoim hostingu i poprawnie skonfigurować swój serwer. +Aby móc używać protokołu HTTPS, konieczne jest jego włączenie na hostingu i prawidłowe skonfigurowanie serwera. -Przekierowanie całej strony na HTTPS należy ustawić na poziomie serwera, na przykład za pomocą pliku .htaccess w katalogu głównym naszej aplikacji, z kodem HTTP 301. Ustawienia mogą się różnić w zależności od hostingu i wyglądają coś takiego: +Przekierowanie całej strony na HTTPS należy ustawić na poziomie serwera, na przykład za pomocą pliku .htaccess w katalogu głównym naszej aplikacji, i to z kodem HTTP 301. Ustawienie może się różnić w zależności od hostingu i wygląda mniej więcej tak: ``` @@ -542,40 +564,40 @@ Przekierowanie całej strony na HTTPS należy ustawić na poziomie serwera, na p ``` -Router generuje adres URL z tym samym protokołem, z którym strona została załadowana, więc nie ma potrzeby ustawiania niczego innego. +Router generuje URL z tym samym protokołem, z jakim została załadowana strona, więc nic więcej nie trzeba ustawiać. -Jeśli jednak wyjątkowo potrzebujemy, aby różne routery działały pod różnymi protokołami, określamy to w masce routera: +Jeśli jednak wyjątkowo potrzebujemy, aby różne trasy działały pod różnymi protokołami, podamy go w masce trasy: ```php -// Bude generovat adresu s HTTP +// Będzie generować adres z HTTP $router->addRoute('http://%host%//', /* ... */); -// Bude generovat adresu s HTTPs +// Będzie generować adres z HTTPS $router->addRoute('https://%host%//', /* ... */); ``` -Debugowanie routera .[#toc-debugging-router] -============================================ +Debugowanie routera +=================== -Pasek routingu wyświetlany w [Tracy Bar |tracy:] to przydatne narzędzie, które wyświetla listę tras, a także parametry, które router uzyskał z adresu URL. +Panel routingu wyświetlający się w [Tracy Bar |tracy:] jest użytecznym pomocnikiem, który wyświetla listę tras oraz parametrów, które router uzyskał z URL. -Zielony pasek z symbolem ✓ reprezentuje router, który przetworzył bieżący adres URL, natomiast niebieski pasek i symbol ≈ wskazują routery, które również przetworzyłyby adres URL, gdyby zielony pasek ich nie wyprzedził. Następnie widzimy aktualnego prezentera & akcję. +Zielony pasek z symbolem ✓ reprezentuje trasę, która przetworzyła aktualny URL, niebieskim kolorem i symbolem ≈ są oznaczone trasy, które również przetworzyłyby URL, gdyby zielona ich nie wyprzedziła. Dalej widzimy aktualny presenter & akcję. [* routing-debugger.webp *] -Jednocześnie, jeśli nastąpi nieoczekiwane przekierowanie z powodu [kanonizacji |#SEO-and-Canonization], warto zajrzeć do paska *redirect*, aby zobaczyć, jak router pierwotnie zrozumiał adres URL i dlaczego dokonał przekierowania. +Jednocześnie jeśli dojdzie do nieoczekiwanego przekierowania z powodu [kanonizacji |#SEO i kanonizacja], warto spojrzeć do panelu w pasku *redirect*, gdzie dowiesz się, jak router pierwotnie zrozumiał URL i dlaczego przekierował. .[note] -Podczas debugowania routera zalecamy otwarcie Developer Tools w przeglądarce (Ctrl+Shift+I lub Cmd+Option+I) i wyłączenie pamięci podręcznej w panelu Network, aby nie były w niej zapisywane przekierowania. +Podczas debugowania routera zalecamy otwarcie w przeglądarce Developer Tools (Ctrl+Shift+I lub Cmd+Option+I) i w panelu Network wyłączenie cache, aby nie zapisywały się w niej przekierowania. -Wydajność .[#toc-performance] -============================= +Wydajność +========= -Liczba tras wpływa na szybkość działania routera. Liczba ta zdecydowanie nie powinna przekraczać kilkudziesięciu. Jeśli Twoja witryna ma zbyt skomplikowaną strukturę adresów URL, możesz napisać [własny niestandardowy router |#Custom-Router]. +Liczba tras ma wpływ na szybkość routera. Ich liczba zdecydowanie nie powinna przekraczać kilkudziesięciu. Jeśli Twoja strona ma zbyt skomplikowaną strukturę URL, możesz napisać na miarę [#Własny router]. -Jeśli router nie ma żadnych zależności, na przykład od bazy danych, a jego fabryka nie przyjmuje żadnych argumentów, możemy serializować jego skompilowaną postać bezpośrednio do kontenera DI i w ten sposób nieco przyspieszyć działanie aplikacji. +Jeśli router nie ma żadnych zależności, na przykład od bazy danych, a jego fabryka nie przyjmuje żadnych argumentów, możemy jego skompilowaną postać zserializować bezpośrednio do kontenera DI i tym samym nieznacznie przyspieszyć aplikację. ```neon routing: @@ -583,10 +605,10 @@ routing: ``` -Router na zamówienie .[#toc-custom-router] -========================================== +Własny router +============= -Poniższe linie przeznaczone są dla bardzo zaawansowanych użytkowników. Możesz stworzyć własny router i naturalnie zintegrować go ze swoją kolekcją routingu. Router jest implementacją interfejsu [api:Nette\Routing\Router] z dwoma metodami: +Poniższe linijki są przeznaczone dla bardzo zaawansowanych użytkowników. Możesz stworzyć własny router i całkowicie naturalnie włączyć go do kolekcji tras. Router jest implementacją interfejsu [api:Nette\Routing\Router] z dwiema metodami: ```php use Nette\Http\IRequest as HttpRequest; @@ -606,8 +628,7 @@ class MyRouter implements Nette\Routing\Router } ``` -Metoda `match` przetwarza bieżące żądanie [$httpRequest |http:request], z którego można uzyskać nie tylko adres URL, ale także nagłówki itp. w tablicy zawierającej nazwę prezentera i jego parametry. Jeśli nie może przetworzyć żądania, zwróci null. -Podczas przetwarzania żądania musimy zwrócić co najmniej prezentera i akcję. Nazwa prezentera jest kompletna i zawiera wszelkie moduły: +Metoda `match` przetwarza aktualne żądanie [$httpRequest |http:request], z którego można uzyskać nie tylko URL, ale i nagłówki itp., do tablicy zawierającej nazwę presentera i jego parametry. Jeśli nie potrafi przetworzyć żądania, zwraca null. Przy przetwarzaniu żądania musimy zwrócić co najmniej presenter i akcję. Nazwa presentera jest pełna i zawiera również ewentualne moduły: ```php [ @@ -616,9 +637,9 @@ Podczas przetwarzania żądania musimy zwrócić co najmniej prezentera i akcję ] ``` -Metoda `constructUrl`, z drugiej strony, konstruuje wynikowy bezwzględny adres URL z tablicy parametrów. Aby to zrobić, może użyć informacji z tablicy [`$refUrl` |api:Nette\Http\UrlScript], czyli aktualny adres URL. +Metoda `constructUrl` odwrotnie, składa z tablicy parametrów wynikowy absolutny URL. Do tego może wykorzystać informacje z parametru [`$refUrl`|api:Nette\Http\UrlScript], który jest aktualnym URL. -Aby dodać go do kolekcji rout, użyj `add()`: +Do kolekcji tras dodasz go za pomocą `add()`: ```php $router = new Nette\Application\Routers\RouteList; @@ -628,19 +649,19 @@ $router->addRoute(/* ... */); ``` -Użytkowanie samodzielne .[#toc-separated-usage] -=============================================== +Samostatné použití +================== -Przez użycie samodzielne rozumiemy wykorzystanie możliwości routera w aplikacji, która nie korzysta z Nette Application i prezenterów. Dotyczy go prawie wszystko, co pokazaliśmy w tym rozdziale, z następującymi różnicami: +Samodzielnym użyciem rozumiemy wykorzystanie możliwości routera w aplikacji, która nie wykorzystuje Nette Application i presenterów. Dotyczy go prawie wszystko, co pokazaliśmy w tym rozdziale, z tymi różnicami: -- dla kolekcji rutynowych używamy klasy [api:Nette\Routing\RouteList] -- jako klasę prostego routera [api:Nette\Routing\SimpleRouter] -- ponieważ nie istnieje para `Presenter:action`, używamy [rozszerzonej notacji |#Advanced-Notation] +- dla kolekcji tras używamy klasy [api:Nette\Routing\RouteList] +- jako simple router klasy [api:Nette\Routing\SimpleRouter] +- ponieważ nie istnieje para `Presenter:action`, używamy [#Zapis rozszerzony] -Czyli znowu tworzymy metodę, która buduje dla nas np. router: +Więc ponownie tworzymy metodę, która nam zbuduje router, np.: ```php -namespace App\Router; +namespace App\Core; use Nette\Routing\RouteList; @@ -661,35 +682,35 @@ class RouterFactory } ``` -Jeśli używasz kontenera DI, który zalecamy, ponownie dodajemy metodę do konfiguracji, a następnie otrzymujemy router wraz z żądaniem HTTP z kontenera: +Jeśli używasz kontenera DI, co zalecamy, ponownie dodamy metodę do konfiguracji, a następnie router wraz z żądaniem HTTP uzyskamy z kontenera: ```php $router = $container->getByType(Nette\Routing\Router::class); $httpRequest = $container->getByType(Nette\Http\IRequest::class); ``` -Możemy też wykonać obiekty bezpośrednio: +Albo obiekty bezpośrednio wyprodukujemy: ```php -$router = App\Router\RouterFactory::createRouter(); +$router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` -Teraz pozostaje nam tylko doprowadzić router do stanu używalności: +Teraz już pozostaje puścić router do pracy: ```php $params = $router->match($httpRequest); if ($params === null) { - // nie znaleziono pasującego routera, wyślij błąd 404 + // nie znaleziono pasującej trasy, wysyłamy błąd 404 exit; } -// przetwarzanie uzyskanych parametrów +// przetwarzamy uzyskane parametry $controller = $params['controller']; // ... ``` -I w odwrotnym kierunku, wykorzystujemy router do budowy łącza: +I odwrotnie użyjemy routera do zbudowania linku: ```php $params = ['controller' => 'ArticleController', 'id' => 123]; diff --git a/application/pl/templates.texy b/application/pl/templates.texy index 9a9d31a92e..496242c1d0 100644 --- a/application/pl/templates.texy +++ b/application/pl/templates.texy @@ -2,15 +2,15 @@ Szablony ******** .[perex] -Nette wykorzystuje system szablonów [Latte |latte:]. Po pierwsze dlatego, że jest to najbezpieczniejszy system szablonowania dla PHP, a także najbardziej intuicyjny. Nie musisz uczyć się wielu nowych rzeczy, wystarczy znajomość PHP i kilka tagów. +Nette używa systemu szablonów [Latte |latte:]. Po pierwsze dlatego, że jest to najlepiej zabezpieczony system szablonów dla PHP, a jednocześnie system najbardziej intuicyjny. Nie musisz uczyć się wielu nowych rzeczy, wystarczy znajomość PHP i kilku znaczników. -Często zdarza się, że strona składa się z szablonu układu + szablonu akcji. Na przykład tak może wyglądać szablon układu, zauważ bloki `{block}` i znacznik `{include}`: +Jest typowe, że strona składa się z szablonu layoutu + szablonu danej akcji. Tak na przykład może wyglądać szablon layoutu, zwróć uwagę na bloki `{block}` i znacznik `{include}`: ```latte - {block title}My App{/block} + {block title}Moja Aplikacja{/block}
    ...
    @@ -20,61 +20,109 @@ Często zdarza się, że strona składa się z szablonu układu + szablonu akcji ``` -I to będzie szablon akcji: +A to będzie szablon akcji: ```latte -{block title}Homepage{/block} +{block title}Strona główna{/block} {block content} -

    Homepage

    +

    Strona główna

    ... {/block} ``` -Definiuje blok `content`, który zostanie wstawiony w miejsce `{include content}` w układzie, a także redefiniuje blok `title`, który zastąpi `{block title}` w układzie. Spróbujcie sobie wyobrazić ten rezultat. +Definiuje on blok `content`, który zostanie wstawiony w miejsce `{include content}` w layoucie, a także redefiniuje blok `title`, którym nadpisze `{block title}` w layoucie. Spróbuj sobie wyobrazić wynik. -Wyszukiwanie szablonów .[#toc-search-for-templates] ---------------------------------------------------- +Wyszukiwanie szablonów +---------------------- -Ścieżka do szablonów jest wyprowadzana przez prezentera za pomocą prostej logiki. Spróbuje sprawdzić, czy jeden z tych plików znajduje się relatywnie od katalogu klasy prezentera, gdzie `` to nazwa aktualnego prezentera, a `` jest nazwą bieżącego zdarzenia: +Nie musisz w presenterach podawać, jaki szablon ma być wyrenderowany, framework sam wywnioskuje ścieżkę i oszczędzi Ci pisania. -- `templates//.latte` -- `templates/..latte` +Jeśli używasz struktury katalogów, gdzie każdy presenter ma własny katalog, po prostu umieść szablon w tym katalogu pod nazwą akcji (resp. view), tj. dla akcji `default` użyj szablonu `default.latte`: -Jeśli szablon nie zostanie znaleziony, odpowiedzią jest [błąd 404 |presenters#Error-404-etc]. +/--pre +app/ +└── Presentation/ + └── Home/ + ├── HomePresenter.php + └── default.latte +\-- -Widok można również zmienić za pomocą strony `$this->setView('jineView')`. Lub, zamiast szukać bezpośrednio, określ nazwę pliku szablonu za pomocą `$this->template->setFile('/path/to/template.latte')`. +Jeśli używasz struktury, gdzie presentery są razem w jednym katalogu, a szablony w folderze `templates`, zapisz go albo w pliku `..latte` albo `/.latte`: + +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── Home.default.latte ← 1. wariant + └── Home/ + └── default.latte ← 2. wariant +\-- + +Katalog `templates` może być umieszczony również o poziom wyżej, tj. na tym samym poziomie, co katalog z klasami presenterów. + +Jeśli szablon nie zostanie znaleziony, presenter odpowie [błędem 404 - page not found |presenters#Błąd 404 i spółka]. + +View zmienisz za pomocą `$this->setView('innyView')`. Można również bezpośrednio określić plik z szablonem za pomocą `$this->template->setFile('/path/to/template.latte')`. .[note] -Pliki, w których wyszukiwane są szablony można zmienić nakładając na metodę [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], która zwraca tablicę możliwych nazw plików. +Pliki, w których wyszukiwane są szablony, można zmienić przez nadpisanie metody [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], która zwraca tablicę możliwych nazw plików. + + +Wyszukiwanie szablonu layoutu +----------------------------- + +Nette również automatycznie wyszukuje plik z layoutem. + +Jeśli używasz struktury katalogów, gdzie każdy presenter ma własny katalog, umieść layout albo w folderze z presenterem, jeśli jest specyficzny tylko dla niego, albo o poziom wyżej, jeśli jest wspólny dla wielu presenterów: + +/--pre +app/ +└── Presentation/ + ├── @layout.latte ← wspólny layout + └── Home/ + ├── @layout.latte ← tylko dla presentera Home + ├── HomePresenter.php + └── default.latte +\-- + +Jeśli używasz struktury, gdzie presentery są razem w jednym katalogu, a szablony w folderze `templates`, layout będzie oczekiwany w tych miejscach: -W tych plikach spodziewany jest układ: +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── @layout.latte ← wspólny layout + ├── Home.@layout.latte ← tylko dla Home, 1. wariant + └── Home/ + └── @layout.latte ← tylko dla Home, 2. wariant +\-- -- `templates//@.latte` -- `templates/.@.latte` -- `templates/@.latte` układ wspólny dla wielu prezenterów +Jeśli presenter znajduje się w module, będzie wyszukiwany również o kolejne poziomy katalogów wyżej, zgodnie z zagnieżdżeniem modułu. -Gdzie `` to nazwa aktualnego prezentera, a `` jest nazwą układu, którą domyślnie jest `'layout'`. Nazwę można zmienić za pomocą `$this->setLayout('jinyLayout')`, więc wypróbowane zostaną pliki `@jinyLayout.latte`. +Nazwę layoutu można zmienić za pomocą `$this->setLayout('layoutAdmin')`, a wtedy będzie oczekiwany w pliku `@layoutAdmin.latte`. Można również bezpośrednio określić plik z szablonem layoutu za pomocą `$this->setLayout('/path/to/template.latte')`. -Możesz również bezpośrednio określić nazwę pliku szablonu układu, używając `$this->setLayout('/path/to/template.latte')`. Użycie `$this->setLayout(false)` wyłączy śledzenie układu. +Za pomocą `$this->setLayout(false)` lub znacznika `{layout none}` wewnątrz szablonu wyszukiwanie layoutu zostanie wyłączone. .[note] -Pliki, w których wyszukiwane są szablony układów można zmienić nakładając na metodę [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], która zwraca tablicę możliwych nazw plików. +Pliki, w których wyszukiwane są szablony layoutu, można zmienić przez nadpisanie metody [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], która zwraca tablicę możliwych nazw plików. -Zmienne w szablonie .[#toc-variables-in-the-template] ------------------------------------------------------ +Zmienne w szablonie +------------------- -Zmienne są przekazywane do szablonu poprzez zapisanie ich do `$this->template`, a następnie są dostępne w szablonie jako zmienne lokalne: +Zmienne do szablonu przekazujemy tak, że zapisujemy je do `$this->template`, a potem mamy je dostępne w szablonie jako zmienne lokalne: ```php $this->template->article = $this->articles->getById($id); ``` -W ten sposób możemy łatwo przekazać dowolne zmienne do szablonów. Jednak podczas tworzenia solidnych aplikacji często bardziej przydatne jest ograniczenie się. Na przykład poprzez jawne zdefiniowanie listy zmiennych, których oczekuje szablon i ich typów. Pozwoli to PHP na sprawdzanie typu, IDE na prawidłowe szeptanie, a analiza statyczna na wykrywanie błędów. +W ten prosty sposób możemy przekazać do szablonów dowolne zmienne. Jednak przy tworzeniu solidnych aplikacji bywa bardziej użyteczne ograniczenie się. Na przykład tak, że jawnie zdefiniujemy wykaz zmiennych, których oczekuje szablon, oraz ich typów. Dzięki temu PHP będzie mógł kontrolować typy, IDE poprawnie podpowiadać, a analiza statyczna wykrywać błędy. -A jak zdefiniować taką wyliczankę? Po prostu w postaci klasy i jej właściwości. Nazwiemy go jak presenter, ale z `Template` na końcu: +A jak taki wykaz zdefiniujemy? Po prostu w postaci klasy i jej właściwości. Nazwiemy ją podobnie jak presenter, tylko z `Template` na końcu: ```php /** @@ -89,26 +137,26 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template public Model\Article $article; public Nette\Security\User $user; - // a další proměnné + // i inne zmienne } ``` -Obiekt `$this->template` w prezenterze będzie teraz instancją klasy `ArticleTemplate`. Tak więc PHP będzie sprawdzać zadeklarowane typy podczas pisania. A począwszy od PHP 8.2, będzie również ostrzegać przy zapisie do nieistniejącej zmiennej; w poprzednich wersjach to samo można osiągnąć za pomocą cechy [Nette\SmartObject |utils:smartobject]. +Obiekt `$this->template` w presenterze będzie teraz instancją klasy `ArticleTemplate`. Więc PHP podczas zapisu będzie kontrolował zadeklarowane typy. A począwszy od wersji PHP 8.2 powiadomi również o zapisie do nieistniejącej zmiennej, w poprzednich wersjach tego samego można osiągnąć używając traity [Nette\SmartObject |utils:smartobject]. -Adnotacja `@property-read` jest dla IDE i analizy statycznej, sprawi, że szeptanie będzie działać, zobacz "PhpStorm i uzupełnianie kodu dla $this->template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. +Adnotacja `@property-read` jest przeznaczona dla IDE i analizy statycznej, dzięki niej będzie działać podpowiadanie, zobacz "PhpStorm and code completion for $this⁠-⁠>⁠template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. [* phpstorm-completion.webp *] -Możesz również mieć luksus szeptania w szablonach, wystarczy zainstalować wtyczkę Latte w PhpStorm i umieścić nazwę klasy na początku szablonu, zobacz artykuł "Latte: jak wpisać system":https://blog.nette.org/pl/latte-jak-korzystac-z-systemu-typow, aby uzyskać więcej informacji: +Luksusu podpowiadania możesz sobie pozwolić również w szablonach, wystarczy zainstalować w PhpStorm wtyczkę dla Latte i podać na początku szablonu nazwę klasy, więcej w artykule "Latte: jak na typowy system":https://blog.nette.org/pl/latte-how-to-use-type-system: ```latte -{templateType App\Presenters\ArticleTemplate} +{templateType App\Presentation\Article\ArticleTemplate} ... ``` -W ten sam sposób działają szablony w komponentach, wystarczy zastosować konwencję nazewnictwa i stworzyć klasę szablonu `FifteenTemplate` dla komponentu np. `FifteenControl`. +Tak działają również szablony w komponentach, wystarczy tylko przestrzegać konwencji nazewnictwa i dla komponentu np. `FifteenControl` utworzyć klasę szablonu `FifteenTemplate`. -Jeśli potrzebujesz stworzyć `$template` jako instancję innej klasy, użyj metody `createTemplate()`: +Jeśli potrzebujesz utworzyć `$template` jako instancję innej klasy, wykorzystaj metodę `createTemplate()`: ```php public function renderDefault(): void @@ -121,60 +169,60 @@ public function renderDefault(): void ``` -Zmienne domyślne .[#toc-default-variables] ------------------------------------------- +Zmienne domyślne +---------------- -Prezentery i komponenty przekazują automatycznie kilka przydatnych zmiennych do szablonów: +Presentery i komponenty przekazują do szablonów kilka użytecznych zmiennych automatycznie: -- `$basePath` to bezwzględna ścieżka URL do katalogu głównego (np. `/eshop`) -- `$baseUrl` to bezwzględny adres URL do katalogu głównego (np. `http://localhost/eshop`) -- `$user` jest obiektem [reprezentującym użytkownika |security:authentication] -- `$presenter` jest obecnym prezenterem -- `$control` jest bieżącym elementem lub prezenterem -- `$flashes` jest tablicą [wiadomości |presenters#Flash-Messages] wysyłanych przez funkcje `flashMessage()` +- `$basePath` to absolutna ścieżka URL do katalogu głównego (np. `/eshop`) +- `$baseUrl` to absolutny URL do katalogu głównego (np. `http://localhost/eshop`) +- `$user` to obiekt [reprezentujący użytkownika |security:authentication] +- `$presenter` to aktualny presenter +- `$control` to aktualny komponent lub presenter +- `$flashes` tablica [wiadomości |presenters#Wiadomości flash] wysłanych funkcją `flashMessage()` -Jeśli używasz niestandardowej klasy szablonu, te zmienne zostaną przekazane, jeśli utworzysz dla nich właściwość. +Jeśli używasz własnej klasy szablonu, te zmienne zostaną przekazane, jeśli utworzysz dla nich właściwość. -Tworzenie linków .[#toc-creating-links] ---------------------------------------- +Tworzenie linków +---------------- -Szablon tworzy w ten sposób linki do innych prezenterów & wydarzeń: +W szablonie tworzy się linki do innych presenterów & akcji w ten sposób: ```latte -detail produktu +szczegóły produktu ``` -Atrybut `n:href` jest bardzo przydatny dla znaczników HTML ``. Jeśli chcemy wymienić link w innym miejscu, na przykład w tekście, używamy `{link}`: +Atrybut `n:href` jest bardzo przydatny dla znaczników HTML ``. Jeśli chcemy link wypisać gdzie indziej, na przykład w tekście, użyjemy `{link}`: ```latte -Adresa je: {link Home:default} +Adres to: {link Home:default} ``` -Aby uzyskać więcej informacji, zobacz [Tworzenie linków URL |creating-links]. +Więcej informacji znajdziesz w rozdziale [Tworzenie linków URL|creating-links]. -Niestandardowe filtry, tagi, itp. .[#toc-custom-filters-tags-etc] ------------------------------------------------------------------ +Własne filtry, znaczniki itp. +----------------------------- -System szablonów Latte może być rozszerzony o własne filtry, funkcje, tagi, itp. Można to zrobić bezpośrednio w metodzie `render` lub `beforeRender()`: +System szablonów Latte można rozszerzyć o własne filtry, funkcje, znaczniki itp. Można to zrobić bezpośrednio w metodzie `render` lub `beforeRender()`: ```php public function beforeRender(): void { - // dodaj filtr + // dodanie filtra $this->template->addFilter('foo', /* ... */); - // lub skonfigurować bezpośrednio obiekt Latte\Engine + // lub konfigurujemy bezpośrednio obiekt Latte\Engine $latte = $this->template->getLatte(); $latte->addFilterLoader(/* ... */); } ``` -Latte w wersji 3 oferuje bardziej zaawansowany sposób tworzenia [rozszerzenia |latte:creating-extension] dla każdego projektu internetowego. Oto krótki przykład takiej klasy: +Latte w wersji 3 oferuje bardziej zaawansowany sposób, a mianowicie utworzenie sobie [extension |latte:extending-latte#Latte Extension] dla każdego projektu internetowego. Przykładowy fragment takiej klasy: ```php -namespace App\Templating; +namespace App\Presentation\Accessory; final class LatteExtension extends Latte\Extension { @@ -207,22 +255,21 @@ final class LatteExtension extends Latte\Extension } ``` -Rejestrujemy go za pomocą [konfiguracji |configuration#Latte]: +Zarejestrujemy ją za pomocą [konfiguracji |configuration#Szablony Latte]: ```neon latte: extensions: - - App\Templating\LatteExtension + - App\Presentation\Accessory\LatteExtension ``` -Tłumaczenie .[#toc-translating] -------------------------------- +Tłumaczenie +----------- -Jeśli programujesz wielojęzyczną aplikację, prawdopodobnie będziesz musiał wyprowadzić część tekstu w szablonie w różnych językach. Aby to zrobić, Nette Framework definiuje interfejs tłumaczący [api:Nette\Localization\Translator], który posiada pojedynczą metodę `translate()`. Przyjmuje ona komunikat `$message`, który zwykle jest ciągiem znaków, oraz dowolne inne parametry. Jej zadaniem jest zwrócenie przetłumaczonego ciągu znaków. -W Nette nie ma domyślnej implementacji, można wybrać według własnych potrzeb spośród kilku gotowych rozwiązań, które można znaleźć na [Componette |https://componette.org/search/localization]. Ich dokumentacja podpowiada, jak skonfigurować translator. +Jeśli programujesz aplikację wielojęzyczną, prawdopodobnie będziesz potrzebować niektóre teksty w szablonie wypisać w różnych językach. Nette Framework w tym celu definiuje interfejs do tłumaczenia [api:Nette\Localization\Translator], który ma jedyną metodę `translate()`. Przyjmuje ona wiadomość `$message`, co zazwyczaj jest ciągiem znaków, oraz dowolne inne parametry. Zadaniem jest zwrócenie przetłumaczonego ciągu. W Nette nie ma żadnej domyślnej implementacji, możesz wybrać według swoich potrzeb spośród kilku gotowych rozwiązań, które znajdziesz na [Componette |https://componette.org/search/localization]. W ich dokumentacji dowiesz się, jak konfigurować translator. -Szablony można skonfigurować z tłumaczem, którego [będziemy mieli przekazanego |dependency-injection:passing-dependencies], za pomocą metody `setTranslator()`: +Szablonom można ustawić translator, który sobie [przekażemy |dependency-injection:passing-dependencies], metodą `setTranslator()`: ```php protected function beforeRender(): void @@ -232,38 +279,38 @@ protected function beforeRender(): void } ``` -Alternatywnie, tłumacz może być ustawiony za pomocą [konfiguracji |configuration#Latte]: +Translator alternatywnie można ustawić za pomocą [konfiguracji |configuration#Szablony Latte]: ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -Translator może być wtedy użyty np. jako filtr `|translate`, z dodatkowymi parametrami przekazywanymi do metody `translate()` (patrz `foo, bar`): +Następnie można używać translatora na przykład jako filtra `|translate`, w tym z dodatkowymi parametrami, które zostaną przekazane metodzie `translate()` (zobacz `foo, bar`): ```latte -{='Basket'|translate} +{='Koszyk'|translate} {$item|translate} {$item|translate, foo, bar} ``` -Albo jako znacznik podkreślenia: +Lub jako znacznika z podkreśleniem: ```latte -{_'Basket'} +{_'Koszyk'} {_$item} {_$item, foo, bar} ``` -Do tłumaczenia sekcji szablonu służy sparowany tag `{translate}` (od wersji Latte 2.11, wcześniej używany był tag `{_}` ): +Do tłumaczenia fragmentu szablonu istnieje parzysty znacznik `{translate}` (od Latte 2.11, wcześniej używano znacznika `{_}`): ```latte -{translate}Order{/translate} -{translate foo, bar}Order{/translate} +{translate}Zamówienie{/translate} +{translate foo, bar}Zamówienie{/translate} ``` -Translator jest domyślnie wywoływany w trybie runtime podczas renderowania szablonu. Latte w wersji 3 może jednak tłumaczyć cały tekst statyczny podczas kompilacji szablonu. Oszczędza to wydajność, ponieważ każdy ciąg jest tłumaczony tylko raz, a wynikowe tłumaczenie jest zapisywane do skompilowanej postaci. Tworzy to wiele skompilowanych wersji szablonu w katalogu cache, po jednej dla każdego języka. Aby to zrobić, musisz tylko określić język jako drugi parametr: +Translator standardowo jest wywoływany w czasie rzeczywistym podczas renderowania szablonu. Latte w wersji 3 jednak potrafi wszystkie statyczne teksty tłumaczyć już podczas kompilacji szablonu. Tym samym oszczędza się wydajność, ponieważ każdy ciąg jest tłumaczony tylko raz, a wynikowe tłumaczenie jest zapisywane w skompilowanej postaci. W katalogu z cache powstaje więc więcej skompilowanych wersji szablonu, jedna dla każdego języka. Do tego wystarczy tylko podać język jako drugi parametr: ```php protected function beforeRender(): void @@ -273,4 +320,4 @@ protected function beforeRender(): void } ``` -Przez tekst statyczny rozumiemy na przykład `{_'hello'}` lub `{translate}hello{/translate}`. Tekst niestatyczny, taki jak `{_$foo}`, będzie nadal kompilowany w locie. +Tekstem statycznym jest na przykład `{_'hello'}` lub `{translate}hello{/translate}`. Teksty niestatyczne, jak na przykład `{_$foo}`, nadal będą tłumaczone w czasie rzeczywistym. diff --git a/application/pt/@home.texy b/application/pt/@home.texy index 68976dc90d..5f79b47fc4 100644 --- a/application/pt/@home.texy +++ b/application/pt/@home.texy @@ -1,36 +1,85 @@ -Aplicação Nette -*************** +Nette Application +***************** .[perex] -O pacote `nette/application` é a base para a criação de aplicações web interativas. - -- [Como funcionam as aplicações? |how-it-works] -- [Bootstrap |Bootstrap] -- [Apresentadores |Presenters] -- [Modelos |Templates] -- [Módulos |Modules] -- [Roteiro |Routing] -- [Criação de links URL |creating-links] -- [Componentes interativos |components] -- [AJAX & Snippets |ajax] -- [Multiplicador |multiplier] -- [Configuração |Configuration] +Nette Application é o núcleo do Nette Framework, que fornece ferramentas poderosas para criar aplicações web modernas. Oferece uma série de recursos excepcionais que facilitam significativamente o desenvolvimento e melhoram a segurança e a manutenção do código. Instalação ---------- -Baixe e instale o pacote usando [o Composer |best-practices:composer]: +Faça o download e instale a biblioteca usando a ferramenta [Composer|best-practices:composer]: ```shell composer require nette/application ``` -| versão | compatível com PHP + +Porquê escolher Nette Application? +---------------------------------- + +Nette sempre foi pioneiro no campo das tecnologias web. + +**Roteador bidirecional:** Nette possui um sistema de roteamento avançado que é único pela sua bidirecionalidade - não só traduz URLs para ações da aplicação, mas também consegue gerar URLs de volta. Isso significa que: +- Pode alterar a estrutura de URLs de toda a aplicação a qualquer momento sem precisar de editar os templates +- As URLs são automaticamente canonizadas, o que melhora o SEO +- O roteamento é definido num único local, em vez de espalhado em anotações + +**Componentes e sinais:** O sistema de componentes integrado, inspirado no Delphi e React.js, é completamente excecional entre os frameworks PHP: +- Permite criar elementos de UI reutilizáveis +- Suporta composição hierárquica de componentes +- Oferece um tratamento elegante de requisições AJAX usando sinais +- Uma vasta biblioteca de componentes prontos em [Componette](https://componette.org) + +**AJAX e snippets:** Nette introduziu uma forma revolucionária de trabalhar com AJAX já em 2009, muito antes de soluções semelhantes como Hotwire para Ruby on Rails ou Symfony UX Turbo: +- Snippets permitem atualizar apenas partes da página sem a necessidade de escrever JavaScript +- Integração automática com o sistema de componentes +- Invalidação inteligente de partes das páginas +- Quantidade mínima de dados transferidos + +**Templates intuitivos [Latte|latte:]:** O sistema de templates mais seguro para PHP com recursos avançados: +- Proteção automática contra XSS com escaping sensível ao contexto +- Extensibilidade através de filtros, funções e tags personalizadas +- Herança de templates e snippets para AJAX +- Excelente suporte a PHP 8.x com sistema de tipos + +**Dependency Injection:** Nette utiliza totalmente a Injeção de Dependência: +- Passagem automática de dependências (autowiring) +- Configuração através do formato claro NEON +- Suporte para fábricas de componentes + + +Principais vantagens +-------------------- + +- **Segurança**: Defesa automática contra [vulnerabilidades|nette:vulnerability-protection] como XSS, CSRF, etc. +- **Produtividade**: Menos escrita, mais funções graças a um design inteligente +- **Depuração**: [Depurador Tracy|tracy:] com painel de roteamento +- **Desempenho**: Cache inteligente, lazy loading de componentes +- **Flexibilidade**: Fácil modificação de URLs mesmo após a conclusão da aplicação +- **Componentes**: Sistema único de elementos de UI reutilizáveis +- **Moderno**: Suporte total a PHP 8.4+ e sistema de tipos + + +Começando +--------- + +1. [Como funcionam as aplicações? |how-it-works] - Compreender a arquitetura básica +2. [Presenters |presenters] - Trabalhar com presenters e ações +3. [Templates |templates] - Criar templates em Latte +4. [Roteamento |routing] - Configurar endereços URL +5. [Componentes interativos |components] - Utilizar o sistema de componentes + + +Compatibilidade com PHP +----------------------- + +| versão | compatível com PHP |-----------|------------------- -| Nette Application 4.0 | PHP 8.0 - 8.2 -| Aplicação Nette 3.1 | PHP 7.2 - 8.2 -| Aplicativo Nette 3.0 | PHP 7.1 - 8.0 -| Aplicação Nette 2.4 | PHP 5.6 - 8.0 +| Nette Application 4.0 | PHP 8.1 – 8.4 +| Nette Application 3.2 | PHP 8.1 – 8.4 +| Nette Application 3.1 | PHP 7.2 – 8.3 +| Nette Application 3.0 | PHP 7.1 – 8.0 +| Nette Application 2.4 | PHP 5.6 – 8.0 -Aplica-se às últimas versões de remendos. +Aplica-se à última versão de patch. diff --git a/application/pt/@left-menu.texy b/application/pt/@left-menu.texy index aef9759b41..9f525b8f0e 100644 --- a/application/pt/@left-menu.texy +++ b/application/pt/@left-menu.texy @@ -1,19 +1,22 @@ -Aplicação Nette -*************** +Nette Application +***************** - [Como funcionam as aplicações? |how-it-works] -- [Bootstrap |Bootstrap] -- [Apresentadores |Presenters] -- [Modelos |Templates] -- [Módulos |Modules] -- [Roteiro |Routing] -- [Criação de links URL |creating-links] +- [Bootstrapping] +- [Presenters |presenters] +- [Templates |templates] +- [Estrutura de diretórios |directory-structure] +- [Roteamento |routing] +- [Criando links URL |creating-links] - [Componentes interativos |components] -- [AJAX & Snippets |ajax] -- [Multiplicador |multiplier] -- [Configuração |Configuration] +- [AJAX & snippets |ajax] +- [Multiplier |multiplier] +- [Configuração |configuration] Leitura adicional ***************** -- [As melhores práticas |best-practices:] +- [Por que usar o Nette? |www:10-reasons-why-nette] +- [Instalação |nette:installation] +- [Escrevendo a primeira aplicação! |quickstart:] +- [Guias e melhores práticas |best-practices:] - [Solução de problemas |nette:troubleshooting] diff --git a/application/pt/@meta.texy b/application/pt/@meta.texy new file mode 100644 index 0000000000..41a853b6aa --- /dev/null +++ b/application/pt/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentação Nette}} diff --git a/application/pt/ajax.texy b/application/pt/ajax.texy index b4cb976fc9..e55e78afce 100644 --- a/application/pt/ajax.texy +++ b/application/pt/ajax.texy @@ -3,36 +3,43 @@ AJAX & Snippets
    -As aplicações web modernas atualmente rodam metade em um servidor e metade em um navegador. O AJAX é um fator de união vital. Que suporte o Nette Framework oferece? -- envio de fragmentos de modelos (os chamados *snippets*) -- passando variáveis entre PHP e JavaScript -- Depuração de aplicações AJAX +Na era das aplicações web modernas, onde a funcionalidade é frequentemente dividida entre o servidor e o navegador, o AJAX é um elemento de ligação essencial. Que opções o Nette Framework nos oferece nesta área? +- envio de partes do template, os chamados snippets +- passagem de variáveis entre PHP e JavaScript +- ferramentas para depuração de requisições AJAX
    -Uma solicitação AJAX pode ser detectada usando um método de um serviço [que encapsula uma solicitação HTTP |http:request] `$httpRequest->isAjax()` (detecta com base no cabeçalho HTTP `X-Requested-With` ). Há também um método abreviado no apresentador: `$this->isAjax()`. -Um pedido AJAX não é diferente de um pedido normal - um apresentador é chamado com uma certa visão e parâmetros. Também depende do apresentador como ele irá reagir: ele pode usar suas rotinas para retornar um fragmento de código HTML (um snippet), um documento XML, um objeto JSON ou um pedaço de código Javascript. +Requisição AJAX +=============== -Há um objeto pré-processado chamado `payload` dedicado ao envio de dados para o navegador no JSON. +Uma requisição AJAX, em princípio, não difere de uma requisição HTTP clássica. Um presenter é chamado com determinados parâmetros. E cabe ao presenter decidir como responder à requisição - ele pode retornar dados em formato JSON, enviar uma parte do código HTML, um documento XML, etc. -```php -public function actionDelete(int $id): void -{ - if ($this->isAjax()) { - $this->payload->message = 'Success'; - } - // ... -} +No lado do navegador, inicializamos a requisição AJAX usando a função `fetch()`: + +```js +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) +.then(response => response.json()) +.then(payload => { + // processamento da resposta +}); ``` -Para um controle total sobre sua saída JSON, utilize o método `sendJson` em seu apresentador. Ele encerra o apresentador imediatamente e você não precisará de um modelo: +No lado do servidor, reconhecemos uma requisição AJAX usando o método `$httpRequest->isAjax()` do serviço [encapsulando a requisição HTTP |http:request]. Para a deteção, ele usa o cabeçalho HTTP `X-Requested-With`, por isso é importante enviá-lo. Dentro do presenter, pode-se usar o método `$this->isAjax()`. + +Se desejar enviar dados em formato JSON, use o método [`sendJson()` |presenters#Envio da resposta]. O método também encerra a atividade do presenter. ```php -$this->sendJson(['key' => 'value', /* ... */]); +public function actionExport(): void +{ + $this->sendJson($this->model->getData); +} ``` -Se quisermos enviar HTML, podemos definir um modelo especial para pedidos AJAX: +Se você planeja responder com um template especial destinado ao AJAX, pode fazê-lo da seguinte forma: ```php public function handleClick($param): void @@ -45,222 +52,198 @@ public function handleClick($param): void ``` -Naja .[#toc-naja] -================= +Snippets +======== + +O recurso mais poderoso que o Nette oferece para conectar o servidor ao cliente são os snippets. Graças a eles, você pode transformar uma aplicação comum em uma aplicação AJAX com esforço mínimo e algumas linhas de código. O exemplo Fifteen demonstra como tudo funciona, e seu código pode ser encontrado no [GitHub |https://github.com/nette-examples/fifteen]. + +Snippets, ou trechos, permitem atualizar apenas partes da página, em vez de recarregar a página inteira. Isso não só é mais rápido e eficiente, mas também proporciona uma experiência de usuário mais confortável. Os snippets podem lembrá-lo do Hotwire para Ruby on Rails ou do Symfony UX Turbo. Curiosamente, o Nette introduziu os snippets 14 anos antes. + +Como os snippets funcionam? No primeiro carregamento da página (requisição não-AJAX), a página inteira é carregada, incluindo todos os snippets. Quando o usuário interage com a página (por exemplo, clica em um botão, envia um formulário, etc.), em vez de carregar a página inteira, uma requisição AJAX é disparada. O código no presenter executa a ação e decide quais snippets precisam ser atualizados. O Nette renderiza esses snippets e os envia como um array em formato JSON. O código de manipulação no navegador insere os snippets recebidos de volta na página. Assim, apenas o código dos snippets alterados é transmitido, economizando largura de banda e acelerando o carregamento em comparação com a transferência do conteúdo da página inteira. + -A [biblioteca Naja |https://naja.js.org] é utilizada para lidar com pedidos AJAX no lado do navegador. [Instale-a |https://naja.js.org/#/guide/01-install-setup-naja] como um pacote node.js (para usar com Webpack, Rollup, Vite, Parcel e mais): +Naja +---- + +Para manipular snippets no lado do navegador, utiliza-se a [biblioteca Naja |https://naja.js.org]. [Instale-a |https://naja.js.org/#/guide/01-install-setup-naja] como um pacote node.js (para uso com aplicações Webpack, Rollup, Vite, Parcel e outras): ```shell npm install naja ``` -...ou inseri-lo diretamente no modelo da página: +…ou insira-a diretamente no template da página: ```html ``` +Primeiro, é necessário [inicializar |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] a biblioteca: -Snippets .[#toc-snippets] -========================= +```js +naja.initialize(); +``` -Há uma ferramenta muito mais poderosa de suporte AJAX incorporado - trechos. O uso deles torna possível transformar uma aplicação regular em AJAX, utilizando apenas algumas linhas de código. Como tudo funciona é demonstrado no exemplo dos Quinze, cujo código também é acessível no build ou no [GitHub |https://github.com/nette-examples/fifteen]. +Para transformar um link comum (sinal) ou o envio de um formulário em uma requisição AJAX, basta marcar o link, formulário ou botão correspondente com a classe `ajax`: -A forma como os trechos funcionam é que a página inteira é transferida durante a solicitação inicial (isto é, não-AJAX) e depois com cada [sub solicitação |components#signal] AJAX (solicitação da mesma visão do mesmo apresentador) apenas o código das partes alteradas é transferido no repositório `payload` mencionado anteriormente. +```html +Go -Snippets podem lembrá-lo da Hotwire para Ruby on Rails ou Symfony UX Turbo, mas a Nette surgiu com eles catorze anos antes. +
    + +
    +ou -Invalidação de Snippets .[#toc-invalidation-of-snippets] -======================================================== +
    + +
    +``` -Cada descendente do [Controle de |components] Classe (que um Apresentador também é) é capaz de lembrar se houve alguma mudança durante um pedido que requeira sua reapresentação. Há um par de métodos para lidar com isso: `redrawControl()` e `isControlInvalid()`. Um exemplo: + +Redesenho de snippets +--------------------- + +Cada objeto da classe [Control |components] (incluindo o próprio Presenter) regista se ocorreram alterações que exigem o seu redesenho. Para isso, serve o método `redrawControl()`: ```php public function handleLogin(string $user): void { - // O objeto tem de ser restituído após o usuário ter feito o login + // após o login, é necessário redesenhar a parte relevante $this->redrawControl(); // ... } ``` -A Nette, entretanto, oferece uma resolução ainda mais fina do que os componentes inteiros. Os métodos listados aceitam o nome do chamado "snippet" como um parâmetro opcional. Um "snippet" é basicamente um elemento em seu modelo marcado para esse fim por uma tag Latte, mais sobre isso depois. Assim, é possível pedir a um componente para redesenhar apenas *partes* de seu gabarito. Se o componente inteiro for invalidado, então todos os seus trechos serão restituídos. Um componente é "inválido" também se qualquer um de seus subcomponentes for inválido. - -```php -$this->isControlInvalid(); // -> false -$this->redrawControl('header'); // invalida o snippet chamado 'header'. -$this->isControlInvalid('header'); // -> true -$this->isControlInvalid('footer'); // -> false -$this->isControlInvalid(); // -> true, at least one snippet is invalid +O Nette permite um controlo ainda mais fino do que deve ser redesenhado. O método mencionado pode receber o nome do snippet como argumento. Assim, é possível invalidar (entenda-se: forçar o redesenho) ao nível das partes do template. Se todo o componente for invalidado, cada um dos seus snippets também será redesenhado: -$this->redrawControl(); // invalida todo o componente, todos os snippet -$this->isControlInvalid('footer'); // -> true +```php +// invalida o snippet 'header' +$this->redrawControl('header'); ``` -Um componente que recebe um sinal é automaticamente marcado para ser redesenhado. - -Graças ao desenho de snippet, sabemos exatamente quais partes de quais elementos devem ser novamente entregues. - - -Tag `{snippet} … {/snippet}` .{toc: Tag snippet} -================================================ -A renderização da página procede de forma muito semelhante a um pedido regular: os mesmos modelos são carregados, etc. A parte vital é, no entanto, deixar de fora as partes que não devem chegar à saída; as outras partes devem ser associadas a um identificador e enviadas ao usuário em um formato compreensível para um manipulador de JavaScript. +Snippets em Latte +----------------- - -Sintaxe .[#toc-syntax] ----------------------- - -Se houver um controle ou um snippet no modelo, temos que embrulhá-lo usando a tag do par `{snippet} ... {/snippet}` - ele assegurará que o snippet renderizado será "cortado" e enviado para o navegador. Ele também o anexará em um helper `
    ` (é possível utilizar uma etiqueta diferente). No exemplo a seguir, um trecho chamado `header` está definido. Ele pode também representar o modelo de um componente: +Usar snippets em Latte é extremamente fácil. Para definir uma parte do template como um snippet, basta envolvê-la com as tags `{snippet}` e `{/snippet}`: ```latte {snippet header} -

    Hello ...

    +

    Olá ...

    {/snippet} ``` -Um fragmento de um tipo diferente de `
    ` ou um snippet com atributos HTML adicionais é obtido usando a variante de atributo: +O snippet cria um elemento `
    ` na página HTML com um `id` especial gerado. Ao redesenhar o snippet, o conteúdo desse elemento é atualizado. Por isso, é necessário que, na renderização inicial da página, todos os snippets também sejam renderizados, mesmo que possam estar vazios no início. + +Você também pode criar um snippet com um elemento diferente de `
    ` usando o n:atributo: ```latte
    -

    Hello ...

    +

    Olá ...

    ``` -Snippets dinâmicos .[#toc-dynamic-snippets] -=========================================== +Áreas de Snippets +----------------- -Em Nette você também pode definir trechos com um nome dinâmico baseado em um parâmetro de tempo de execução. Isto é mais adequado para várias listas onde precisamos mudar apenas uma linha, mas não queremos transferir a lista inteira junto com ela. Um exemplo disto seria: +Os nomes dos snippets também podem ser expressões: ```latte -
      - {foreach $list as $id => $item} -
    • {$item} update
    • - {/foreach} -
    +{foreach $items as $id => $item} +
  • {$item}
  • +{/foreach} ``` -Há um trecho estático chamado `itemsContainer`, contendo vários trechos dinâmicos: `item-0`, `item-1` e assim por diante. +Assim, teremos vários snippets `item-0`, `item-1`, etc. Se invalidássemos diretamente um snippet dinâmico (por exemplo, `item-1`), nada seria redesenhado. A razão é que os snippets funcionam realmente como recortes e apenas eles próprios são renderizados diretamente. No entanto, no template, não existe de facto nenhum snippet chamado `item-1`. Ele só surge com a execução do código ao redor do snippet, ou seja, o ciclo foreach. Portanto, marcamos a parte do template que deve ser executada usando a tag `{snippetArea}`: -Você não pode redesenhar um trecho dinâmico diretamente (o redesenho de `item-1` não tem efeito), você tem que redesenhar seu trecho pai (neste exemplo `itemsContainer`). Isto faz com que o código do snippet pai seja executado, mas então apenas seus sub-snippets são enviados para o navegador. Se você quiser enviar apenas um dos sub-snippets, você tem que modificar a entrada para que o trecho pai não gere os outros sub-snippets. +```latte +
      + {foreach $items as $id => $item} +
    • {$item}
    • + {/foreach} +
    +``` -No exemplo acima você tem que ter certeza de que para um pedido AJAX apenas um item será adicionado à matriz `$list`, portanto o laço `foreach` imprimirá apenas um trecho dinâmico. +E mandamos redesenhar tanto o snippet em si quanto toda a área pai: ```php -class HomePresenter extends Nette\Application\UI\Presenter -{ - /** - * This method returns data for the list. - * Usually this would just request the data from a model. - * For the purpose of this example, the data is hard-coded. - */ - private function getTheWholeList(): array - { - return [ - 'First', - 'Second', - 'Third', - ]; - } - - public function renderDefault(): void - { - if (!isset($this->template->list)) { - $this->template->list = $this->getTheWholeList(); - } - } - - public function handleUpdate(int $id): void - { - $this->template->list = $this->isAjax() - ? [] - : $this->getTheWholeList(); - $this->template->list[$id] = 'Updated item'; - $this->redrawControl('itemsContainer'); - } -} +$this->redrawControl('itemsContainer'); +$this->redrawControl('item-1'); ``` +Ao mesmo tempo, é aconselhável garantir que o array `$items` contenha apenas os itens que devem ser redesenhados. -Snippets em um Modelo Incluído .[#toc-snippets-in-an-included-template] -======================================================================= - -Pode acontecer que o trecho esteja em um modelo que está sendo incluído a partir de um modelo diferente. Nesse caso, precisamos embrulhar o código de inclusão no segundo modelo com a tag `snippetArea`, então redesenhamos tanto o snippetArea quanto o snippet real. - -A tag `snippetArea` assegura que o código interno seja executado, mas apenas o trecho real no modelo incluído é enviado para o navegador. +Se incluirmos outro template que contém snippets no template usando a tag `{include}`, é necessário envolver a inclusão do template novamente em `snippetArea` e invalidá-la junto com o snippet: ```latte -{* parent.latte *} -{snippetArea wrapper} - {include 'child.latte'} +{snippetArea include} + {include 'included.latte'} {/snippetArea} ``` + ```latte -{* child.latte *} +{* included.latte *} {snippet item} -... + ... {/snippet} ``` + ```php -$this->redrawControl('wrapper'); +$this->redrawControl('include'); $this->redrawControl('item'); ``` -Você também pode combiná-lo com trechos dinâmicos. - -Adicionando e excluindo .[#toc-adding-and-deleting] -=================================================== +Snippets em Componentes +----------------------- -Se você acrescentar um novo item à lista e invalidar `itemsContainer`, o pedido AJAX devolve trechos incluindo o novo, mas o manipulador de javascript não será capaz de renderizá-lo. Isto porque não há nenhum elemento HTML com o ID recém-criado. - -Neste caso, a maneira mais simples é envolver toda a lista em mais um trecho e invalidar tudo isso: +Você também pode criar snippets em [componentes|components] e o Nette irá redesenhá-los automaticamente. Mas existe uma certa limitação: para redesenhar os snippets, ele chama o método `render()` sem parâmetros. Portanto, a passagem de parâmetros no template não funcionará: ```latte -{snippet wholeList} -
      - {foreach $list as $id => $item} -
    • {$item} update
    • - {/foreach} -
    -{/snippet} -Add +OK +{control productGrid} + +não funcionará: +{control productGrid $arg, $arg} +{control productGrid:paginator} ``` + +Envio de dados do usuário +------------------------- + +Juntamente com os snippets, você pode enviar quaisquer outros dados para o cliente. Basta escrevê-los no objeto `payload`: + ```php -public function handleAdd(): void +public function actionDelete(int $id): void { - $this->template->list = $this->getTheWholeList(); - $this->template->list[] = 'New one'; - $this->redrawControl('wholeList'); + // ... + if ($this->isAjax()) { + $this->payload->message = 'Sucesso'; + } } ``` -O mesmo vale para a eliminação de um item. Seria possível enviar um trecho vazio, mas geralmente as listas podem ser paginadas e seria complicado implementar a exclusão de um item e o carregamento de outro (que costumava estar em uma página diferente da lista paginada). - -Parâmetros de envio para o componente .[#toc-sending-parameters-to-component] -============================================================================= +Passagem de parâmetros +====================== -Quando enviamos parâmetros para o componente via solicitação AJAX, sejam parâmetros de sinal ou parâmetros persistentes, devemos fornecer seu nome global, que também contém o nome do componente. O nome completo do parâmetro retorna o método `getParameterId()`. +Se enviarmos parâmetros para um componente através de uma requisição AJAX, sejam parâmetros de sinal ou parâmetros persistentes, devemos indicar na requisição o seu nome global, que também inclui o nome do componente. O nome completo do parâmetro é retornado pelo método `getParameterId()`. ```js -$.getJSON( - {link changeCountBasket!}, - { - {$control->getParameterId('id')}: id, - {$control->getParameterId('count')}: count - } -}); +let url = new URL({link //foo!}); +url.searchParams.set({$control->getParameterId('bar')}, bar); + +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) ``` -E método de manuseio com s parâmetros correspondentes em componente. +E o método handle com os parâmetros correspondentes no componente: ```php -public function handleChangeCountBasket(int $id, int $count): void +public function handleFoo(int $bar): void { - } ``` diff --git a/application/pt/bootstrap.texy b/application/pt/bootstrap.texy deleted file mode 100644 index 2fe2d46f10..0000000000 --- a/application/pt/bootstrap.texy +++ /dev/null @@ -1,233 +0,0 @@ -Bootstrap -********* - -
    - -Bootstrap é o código de inicialização que inicializa o ambiente, cria um recipiente de injeção de dependência (DI), e inicia a aplicação. Vamos discutir: - -- como configurar sua aplicação utilizando arquivos NEON -- como lidar com os modos de produção e desenvolvimento -- como criar o recipiente DI - -
    - - -As aplicações, sejam scripts baseados na web ou em linha de comando, começam por alguma forma de inicialização do ambiente. Nos tempos antigos, podia ser um arquivo chamado, por exemplo, `include.inc.php` que estava encarregado disto, e estava incluído no arquivo inicial. -Nas aplicações Nette modernas, ele foi substituído pela classe `Bootstrap`, que como parte da aplicação pode ser encontrada no `app/Bootstrap.php`. Pode ser parecido, por exemplo, com isto: - -```php -use Nette\Bootstrap\Configurator; - -class Bootstrap -{ - public static function boot(): Configurator - { - $appDir = dirname(__DIR__); - $configurator = new Configurator; - //$configurator->setDebugMode('secret@23.75.345.200'); - $configurator->enableTracy($appDir . '/log'); - $configurator->setTempDirectory($appDir . '/temp'); - $configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); - $configurator->addConfig($appDir . '/config/common.neon'); - return $configurator; - } -} -``` - - -index.php .[#toc-index-php] -=========================== - -No caso de aplicações web, o arquivo inicial é `index.php`, que está localizado no diretório público `www/`. Ele permite que a classe `Bootstrap` inicialize o ambiente e devolva o `$configurator` que cria o contêiner DI. Em seguida, obtém o serviço `Application`, que executa a aplicação web: - -```php -// inicializar o ambiente + obter objeto Configurador -$configurator = App\Bootstrap::boot(); -// criar um recipiente DI -$container = $configurator->createContainer(); -// Recipiente DI cria um objeto de aplicação Nette -$application = $container->getByType(Nette\Application\Application::class); -// iniciar a aplicação Nette -$application->run(); -``` - -Como você pode ver, a classe [api:Nette\Bootstrap\Configurator], que agora vamos apresentar mais detalhadamente, ajuda a configurar o ambiente e criar um recipiente de injeção de dependência (DI). - - -Desenvolvimento vs Modo de Produção .[#toc-development-vs-production-mode] -========================================================================== - -Nette distingue dois modos básicos nos quais uma solicitação é executada: desenvolvimento e produção. O modo de desenvolvimento é focado no máximo conforto do programador, Tracy é exibido, o cache é atualizado automaticamente ao alterar os modelos ou a configuração do container DI, etc. O modo de produção é focado no desempenho, Tracy apenas registra erros e mudanças de gabaritos e outros arquivos não são verificados. - -A seleção do modo é feita por autodetecção, de modo que normalmente não há necessidade de configurar ou trocar nada manualmente. O modo é desenvolvimento se a aplicação estiver rodando no localhost (ou seja, endereço IP `127.0.0.1` ou `::1`) e nenhum proxy estiver presente (ou seja, seu cabeçalho HTTP). Caso contrário, ele é executado em modo de produção. - -Se você quiser ativar o modo de desenvolvimento em outros casos, por exemplo, para programadores que acessam de um endereço IP específico, você pode usar `setDebugMode()`: - -```php -$configurator->setDebugMode('23.75.345.200'); // um ou mais endereços IP -``` - -Definitivamente, recomendamos combinar um endereço IP com um cookie. Armazenaremos um token secreto no cookie `nette-debug`, por exemplo `secret1234`, e o modo de desenvolvimento será ativado para programadores com esta combinação de IP e cookie. - -```php -$configurator->setDebugMode('secret1234@23.75.345.200'); -``` - -Também podemos desligar completamente o modo desenvolvedor, mesmo para o localhost: - -```php -$configurator->setDebugMode(false); -``` - -Note que o valor `true` liga o modo desenvolvedor por hard, o que nunca deveria acontecer em um servidor de produção. - - -Ferramenta de depuração Tracy .[#toc-debugging-tool-tracy] -========================================================== - -Para facilitar a depuração, vamos acionar a grande ferramenta [Tracy |tracy:]. No modo desenvolvedor ela visualiza os erros e no modo de produção registra os erros no diretório especificado: - -```php -$configurator->enableTracy($appDir . '/log'); -``` - - -Arquivos temporários .[#toc-temporary-files] -============================================ - -Nette utiliza o cache para contêiner DI, RobotLoader, modelos, etc. Portanto, é necessário definir o caminho para o diretório onde o cache será armazenado: - -```php -$configurator->setTempDirectory($appDir . '/temp'); -``` - -No Linux ou macOS, defina as [permissões de escrita |nette:troubleshooting#Setting directory permissions] para os diretórios `log/` e `temp/`. - - -RobotLoader .[#toc-robotloader] -=============================== - -Normalmente, queremos carregar automaticamente as classes usando [o RobotLoader |robot-loader:], então temos que iniciá-lo e deixá-lo carregar classes do diretório onde se encontra `Bootstrap.php` (ou seja, `__DIR__`) e todos os seus subdiretórios: - -```php -$configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); -``` - -Uma maneira alternativa é usar apenas o [Composer |best-practices:composer] PSR-4 auto-carregamento. - - -Fuso horário .[#toc-timezone] -============================= - -O Configurador permite que você especifique um fuso horário para sua aplicação. - -```php -$configurator->setTimeZone('Europe/Prague'); -``` - - -Configuração de contêineres DI .[#toc-di-container-configuration] -================================================================= - -Parte do processo de inicialização é a criação de um container DI, ou seja, uma fábrica para objetos, que é o coração de toda a aplicação. Na verdade, é uma classe PHP gerada pela Nette e armazenada em um diretório cache. A fábrica produz objetos chave da aplicação e arquivos de configuração instruem-no como criá-los e configurá-los, e assim influenciamos o comportamento de toda a aplicação. - -Os arquivos de configuração são geralmente escritos no [formato NEON |neon:format]. Você pode ler [o que pode ser configurado aqui |nette:configuring]. - -.[tip] -No modo de desenvolvimento, o recipiente é atualizado automaticamente cada vez que você altera o código ou os arquivos de configuração. No modo de produção, ele é gerado apenas uma vez e as mudanças de arquivo não são verificadas para maximizar o desempenho. - -Os arquivos de configuração são carregados usando `addConfig()`: - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -``` - -O método `addConfig()` pode ser chamado várias vezes para adicionar vários arquivos. - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -$configurator->addConfig($appDir . '/config/local.neon'); -if (PHP_SAPI === 'cli') { - $configurator->addConfig($appDir . '/config/cli.php'); -} -``` - -O nome `cli.php` não é um erro de digitação, a configuração também pode ser escrita em um arquivo PHP, que a devolve como um array. - -Alternativamente, podemos usar a [seção`includes` |dependency-injection:configuration#including files] para carregar mais arquivos de configuração. - -Se itens com as mesmas chaves aparecerem dentro dos arquivos de configuração, eles serão [sobrescritos ou fundidos |dependency-injection:configuration#Merging] no caso de arrays. O arquivo incluído posteriormente tem uma prioridade mais alta do que o anterior. O arquivo no qual a seção `includes` é listada tem uma prioridade mais alta do que os arquivos incluídos nela. - - -Parâmetros estáticos .[#toc-static-parameters] ----------------------------------------------- - -Os parâmetros usados nos arquivos de configuração podem ser definidos [na seção `parameters` |dependency-injection:configuration#parameters] e também passados (ou sobrescritos) pelo método `addStaticParameters()` (tem o pseudônimo `addParameters()`). É importante que diferentes valores de parâmetros causem a geração de recipientes DI adicionais, ou seja, classes adicionais. - -```php -$configurator->addStaticParameters([ - 'projectId' => 23, -]); -``` - -Nos arquivos de configuração, podemos escrever a notação usual `%projectId%` para acessar o parâmetro `projectId`. Por padrão, o Configurador preenche os seguintes parâmetros: `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` e `consoleMode`. - - -Parâmetros dinâmicos .[#toc-dynamic-parameters] ------------------------------------------------ - -Também podemos adicionar parâmetros dinâmicos ao recipiente, seus diferentes valores, ao contrário dos parâmetros estáticos, não causarão a geração de novos recipientes DI. - -```php -$configurator->addDynamicParameters([ - 'remoteIp' => $_SERVER['REMOTE_ADDR'], -]); -``` - -As variáveis ambientais poderiam ser facilmente disponibilizadas usando parâmetros dinâmicos. Podemos acessá-las via `%env.variable%` em arquivos de configuração. - -```php -$configurator->addDynamicParameters([ - 'env' => getenv(), -]); -``` - - -Serviços Importados .[#toc-imported-services] ---------------------------------------------- - -Estamos indo mais fundo agora. Embora o propósito de um recipiente DI seja criar objetos, excepcionalmente pode haver a necessidade de inserir um objeto existente no recipiente. Fazemos isso definindo o serviço com o atributo `imported: true`. - -```neon -services: - myservice: - type: App\Model\MyCustomService - imported: true -``` - -Criar uma nova instância e inseri-la no bootstrap: - -```php -$configurator->addServices([ - 'myservice' => new App\Model\MyCustomService('foobar'), -]); -``` - - -Diferentes Ambientes .[#toc-different-environments] -=================================================== - -Sinta-se à vontade para personalizar a classe `Bootstrap` de acordo com suas necessidades. Você pode adicionar parâmetros ao método `boot()` para diferenciar projetos web, ou adicionar outros métodos, tais como `bootForTests()`, que inicializa o ambiente para testes unitários, `bootForCli()` para scripts chamados a partir da linha de comando, e assim por diante. - -```php -public static function bootForTests(): Configurator -{ - $configurator = self::boot(); - Tester\Environment::setup(); // Nette Tester initialization - return $configurator; -} -``` diff --git a/application/pt/bootstrapping.texy b/application/pt/bootstrapping.texy new file mode 100644 index 0000000000..07fbde0ede --- /dev/null +++ b/application/pt/bootstrapping.texy @@ -0,0 +1,297 @@ +Bootstrapping +************* + +
    + +Bootstrapping é o processo de inicialização do ambiente da aplicação, criação de um contêiner de injeção de dependência (DI) e início da aplicação. Vamos discutir: + +- como a classe Bootstrap inicializa o ambiente +- como as aplicações são configuradas usando arquivos NEON +- como distinguir entre modo de produção e desenvolvimento +- como criar e configurar o contêiner DI + +
    + + +Aplicações, sejam elas web ou scripts executados a partir da linha de comando, começam sua execução com alguma forma de inicialização do ambiente. Antigamente, isso era responsabilidade de um arquivo chamado, por exemplo, `include.inc.php`, que o arquivo inicial incluía. Em aplicações Nette modernas, ele foi substituído pela classe `Bootstrap`, que, como parte da aplicação, pode ser encontrada no arquivo `app/Bootstrap.php`. Pode parecer, por exemplo, assim: + +```php +use Nette\Bootstrap\Configurator; + +class Bootstrap +{ + private Configurator $configurator; + private string $rootDir; + + public function __construct() + { + $this->rootDir = dirname(__DIR__); + // O Configurator é responsável por configurar o ambiente da aplicação e os serviços. + $this->configurator = new Configurator; + // Define o diretório para arquivos temporários gerados pelo Nette (por exemplo, templates compilados) + $this->configurator->setTempDirectory($this->rootDir . '/temp'); + } + + public function bootWebApplication(): Nette\DI\Container + { + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); + } + + private function initializeEnvironment(): void + { + // O Nette é inteligente e o modo de desenvolvimento é ativado automaticamente, + // ou você pode habilitá-lo para um endereço IP específico descomentando a linha seguinte: + // $this->configurator->setDebugMode('secret@23.75.345.200'); + + // Ativa o Tracy: o "canivete suíço" definitivo para depuração. + $this->configurator->enableTracy($this->rootDir . '/log'); + + // RobotLoader: carrega automaticamente todas as classes no diretório selecionado + $this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); + } + + private function setupContainer(): void + { + // Carrega os arquivos de configuração + $this->configurator->addConfig($this->rootDir . '/config/common.neon'); + } +} +``` + + +index.php +========= + +O arquivo inicial no caso de aplicações web é `index.php`, localizado no [diretório público |directory-structure#Diretório público www] `www/`. Ele solicita à classe Bootstrap que inicialize o ambiente e crie o contêiner de DI. Em seguida, obtém o serviço `Application` dele, que inicia a aplicação web: + +```php +$bootstrap = new App\Bootstrap; +// Inicialização do ambiente + criação do contêiner de DI +$container = $bootstrap->bootWebApplication(); +// O contêiner de DI cria o objeto Nette\Application\Application +$application = $container->getByType(Nette\Application\Application::class); +// Inicia a aplicação Nette e processa a requisição recebida +$application->run(); +``` + +Como pode ser visto, a classe [api:Nette\Bootstrap\Configurator] ajuda na configuração do ambiente e na criação do contêiner de injeção de dependência (DI), que agora apresentaremos em mais detalhes. + + +Modo de desenvolvimento vs produção +=================================== + +O Nette se comporta de maneira diferente dependendo se está sendo executado em um servidor de desenvolvimento ou de produção: + +🛠️ Modo de desenvolvimento (Development): + - Exibe a barra de depuração do Tracy com informações úteis (consultas SQL, tempo de execução, memória usada) + - Em caso de erro, exibe uma página de erro detalhada com a pilha de chamadas de funções e o conteúdo das variáveis + - Atualiza automaticamente o cache quando os templates Latte são alterados, os arquivos de configuração são modificados, etc. + + +🚀 Modo de produção (Production): + - Não exibe nenhuma informação de depuração, todos os erros são registrados no log + - Em caso de erro, exibe o ErrorPresenter ou uma página genérica "Server Error" + - O cache nunca é atualizado automaticamente! + - Otimizado para velocidade e segurança + + +A seleção do modo é feita por autodeteção, portanto, geralmente não é necessário configurar nada ou alternar manualmente: + +- modo de desenvolvimento: em localhost (endereço IP `127.0.0.1` ou `::1`) se não houver proxy presente (ou seja, seu cabeçalho HTTP) +- modo de produção: em todos os outros lugares + +Se quisermos habilitar o modo de desenvolvimento em outros casos, por exemplo, para programadores acessando de um endereço IP específico, usamos `setDebugMode()`: + +```php +$this->configurator->setDebugMode('23.75.345.200'); // também pode ser fornecido um array de endereços IP +``` + +Recomendamos fortemente combinar o endereço IP com um cookie. Armazenamos um token secreto no cookie `nette-debug`, por exemplo, `secret1234`, e desta forma ativamos o modo de desenvolvimento para programadores acessando de um endereço IP específico e que também possuem o token mencionado no cookie: + +```php +$this->configurator->setDebugMode('secret1234@23.75.345.200'); +``` + +Também podemos desativar completamente o modo de desenvolvimento, mesmo para localhost: + +```php +$this->configurator->setDebugMode(false); +``` + +Atenção, o valor `true` ativa o modo de desenvolvimento permanentemente, o que nunca deve acontecer em um servidor de produção. + + +Ferramenta de depuração Tracy +============================= + +Para facilitar a depuração, ativamos também a excelente ferramenta [Tracy |tracy:]. No modo de desenvolvimento, ela visualiza os erros e, no modo de produção, registra os erros no diretório especificado: + +```php +$this->configurator->enableTracy($this->rootDir . '/log'); +``` + + +Arquivos temporários +==================== + +O Nette utiliza cache para o contêiner de DI, RobotLoader, templates, etc. Portanto, é necessário definir o caminho para o diretório onde o cache será armazenado: + +```php +$this->configurator->setTempDirectory($this->rootDir . '/temp'); +``` + +No Linux ou macOS, defina as [permissões de escrita |nette:troubleshooting#Configurando Permissões de Diretório] para os diretórios `log/` e `temp/`. + + +RobotLoader +=========== + +Geralmente, queremos carregar classes automaticamente usando o [RobotLoader |robot-loader:], então precisamos iniciá-lo e deixá-lo carregar classes do diretório onde `Bootstrap.php` está localizado (ou seja, `__DIR__`), e de todos os subdiretórios: + +```php +$this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); +``` + +Uma abordagem alternativa é deixar as classes serem carregadas apenas através do [Composer |best-practices:composer] seguindo o PSR-4. + + +Fuso horário +============ + +Através do configurator, você pode definir o fuso horário padrão. + +```php +$this->configurator->setTimeZone('Europe/Lisbon'); +``` + + +Configuração do contêiner de DI +=============================== + +Parte do processo de inicialização é a criação do contêiner de DI, ou seja, a fábrica de objetos, que é o coração de toda a aplicação. Na verdade, é uma classe PHP gerada pelo Nette e armazenada no diretório de cache. A fábrica produz os objetos chave da aplicação e, por meio de arquivos de configuração, a instruímos sobre como criá-los e configurá-los, influenciando assim o comportamento de toda a aplicação. + +Os arquivos de configuração são geralmente escritos no formato [NEON |neon:format]. Em um capítulo separado, você aprenderá [o que pode ser configurado |nette:configuring]. + +.[tip] +No modo de desenvolvimento, o contêiner é atualizado automaticamente a cada alteração no código ou nos arquivos de configuração. No modo de produção, ele é gerado apenas uma vez e as alterações não são verificadas para maximizar o desempenho. + +Carregamos os arquivos de configuração usando `addConfig()`: + +```php +$this->configurator->addConfig($this->rootDir . '/config/common.neon'); +``` + +Se quisermos adicionar mais arquivos de configuração, podemos chamar a função `addConfig()` várias vezes. + +```php +$configDir = $this->rootDir . '/config'; +$this->configurator->addConfig($configDir . '/common.neon'); +$this->configurator->addConfig($configDir . '/services.neon'); +if (PHP_SAPI === 'cli') { + $this->configurator->addConfig($configDir . '/cli.php'); +} +``` + +O nome `cli.php` não é um erro de digitação, a configuração também pode ser escrita em um arquivo PHP, que a retorna como um array. + +Também podemos adicionar outros arquivos de configuração na [seção `includes` |dependency-injection:configuration#Inclusão de arquivos]. + +Se elementos com as mesmas chaves aparecerem nos arquivos de configuração, eles serão sobrescritos ou, no caso de [arrays, mesclados |dependency-injection:configuration#Mesclagem]. O arquivo incluído posteriormente tem prioridade maior que o anterior. O arquivo em que a seção `includes` é listada tem prioridade maior do que os arquivos incluídos nele. + + +Parâmetros estáticos +-------------------- + +Parâmetros usados nos arquivos de configuração podem ser definidos [na seção `parameters` |dependency-injection:configuration#Parâmetros] e também podem ser passados (ou sobrescritos) pelo método `addStaticParameters()` (tem o alias `addParameters()`). É importante que diferentes valores de parâmetros causem a geração de contêineres de DI adicionais, ou seja, classes adicionais. + +```php +$this->configurator->addStaticParameters([ + 'projectId' => 23, +]); +``` + +O parâmetro `projectId` pode ser referenciado na configuração usando a notação usual `%projectId%`. + + +Parâmetros dinâmicos +-------------------- + +Também podemos adicionar parâmetros dinâmicos ao contêiner, cujos diferentes valores, ao contrário dos parâmetros estáticos, não causam a geração de novos contêineres de DI. + +```php +$this->configurator->addDynamicParameters([ + 'remoteIp' => $_SERVER['REMOTE_ADDR'], +]); +``` + +Assim, podemos adicionar facilmente, por exemplo, variáveis de ambiente, que podem ser referenciadas na configuração usando a notação `%env.variable%`. + +```php +$this->configurator->addDynamicParameters([ + 'env' => getenv(), +]); +``` + + +Parâmetros padrão +----------------- + +Nos arquivos de configuração, você pode usar estes parâmetros estáticos: + +- `%appDir%` é o caminho absoluto para o diretório com o arquivo `Bootstrap.php` +- `%wwwDir%` é o caminho absoluto para o diretório com o arquivo de entrada `index.php` +- `%tempDir%` é o caminho absoluto para o diretório de arquivos temporários +- `%vendorDir%` é o caminho absoluto para o diretório onde o Composer instala as bibliotecas +- `%rootDir%` é o caminho absoluto para o diretório raiz do projeto +- `%debugMode%` indica se a aplicação está em modo de depuração +- `%consoleMode%` indica se a requisição veio da linha de comando + + +Serviços importados +------------------- + +Agora estamos indo mais a fundo. Embora o propósito do contêiner de DI seja criar objetos, excepcionalmente pode surgir a necessidade de inserir um objeto existente no contêiner. Fazemos isso definindo o serviço com o sinalizador `imported: true`. + +```neon +services: + myservice: + type: App\Model\MyCustomService + imported: true +``` + +E no bootstrap, inserimos o objeto no contêiner: + +```php +$this->configurator->addServices([ + 'myservice' => new App\Model\MyCustomService('foobar'), +]); +``` + + +Ambiente diferente +================== + +Não hesite em modificar a classe Bootstrap de acordo com suas necessidades. Você pode adicionar parâmetros ao método `bootWebApplication()` para distinguir projetos web. Ou podemos adicionar outros métodos, como `bootTestEnvironment()`, que inicializa o ambiente para testes unitários, `bootConsoleApplication()` para scripts chamados da linha de comando, etc. + +```php +public function bootTestEnvironment(): Nette\DI\Container +{ + Tester\Environment::setup(); // inicialização do Nette Tester + $this->setupContainer(); + return $this->configurator->createContainer(); +} + +public function bootConsoleApplication(): Nette\DI\Container +{ + $this->configurator->setDebugMode(false); + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); +} +``` diff --git a/application/pt/components.texy b/application/pt/components.texy index 55d25cc8ce..c6d4d414a4 100644 --- a/application/pt/components.texy +++ b/application/pt/components.texy @@ -3,7 +3,7 @@ Componentes interativos
    -Os componentes são objetos reutilizáveis separados que colocamos em páginas. Eles podem ser formas, datagrids, pesquisas, na verdade, qualquer coisa que faça sentido usar repetidamente. Vamos mostrar: +Componentes são objetos reutilizáveis independentes que inserimos nas páginas. Podem ser formulários, datagrids, enquetes, na verdade, qualquer coisa que faça sentido usar repetidamente. Vamos mostrar: - como usar componentes? - como escrevê-los? @@ -11,19 +11,19 @@ Os componentes são objetos reutilizáveis separados que colocamos em páginas.
    -Nette tem um sistema de componentes incorporado. Os mais antigos de vocês podem se lembrar de algo similar dos formulários Delphi ou ASP.NET Web Forms. Reage ou Vue.js é construído sobre algo remotamente semelhante. Entretanto, no mundo das estruturas PHP, esta é uma característica completamente única. +O Nette possui um sistema de componentes embutido. Algo semelhante pode ser familiar para veteranos do Delphi ou ASP.NET Web Forms, e algo remotamente parecido é a base do React ou Vue.js. No entanto, no mundo dos frameworks PHP, é uma característica única. -Ao mesmo tempo, os componentes mudam fundamentalmente a abordagem para o desenvolvimento de aplicações. Você pode compor páginas a partir de unidades pré-preparadas. Você precisa de dadosagrid na administração? Você pode encontrá-lo na [Componette |https://componette.org/search/component], um repositório de add-ons de código aberto (não apenas componentes) para a Nette, e simplesmente colá-lo no apresentador. +Ao mesmo tempo, os componentes influenciam fundamentalmente a abordagem para a criação de aplicações. Você pode montar páginas a partir de unidades pré-preparadas. Precisa de um datagrid na administração? Encontre-o na [Componette |https://componette.org/search/component], um repositório de add-ons open-source (ou seja, não apenas componentes) para o Nette e simplesmente insira-o no presenter. -Você pode incorporar qualquer número de componentes ao apresentador. E você pode inserir outros componentes em alguns componentes. Isto cria uma árvore de componentes com um apresentador como raiz. +Você pode incorporar qualquer número de componentes em um presenter. E em alguns componentes, você pode inserir outros componentes. Isso cria uma árvore de componentes, cuja raiz é o presenter. -Métodos de Fábrica .[#toc-factory-methods] -========================================== +Métodos de fábrica +================== -Como os componentes são colocados e posteriormente utilizados no apresentador? Geralmente utilizando métodos de fábrica. +Como os componentes são inseridos no presenter e subsequentemente usados? Geralmente através de métodos de fábrica. -A fábrica de componentes é uma maneira elegante de criar componentes somente quando eles são realmente necessários (preguiçosos / sob demanda). Toda a magia está na implementação de um método chamado `createComponent()`onde `` é o nome do componente, que criará e retornará. +A fábrica de componentes representa uma maneira elegante de criar componentes apenas quando eles são realmente necessários (lazy / on demand). Toda a mágica reside na implementação de um método chamado `createComponent()`, onde `` é o nome do componente a ser criado, e que cria e retorna o componente. ```php .{file:DefaultPresenter.php} class DefaultPresenter extends Nette\Application\UI\Presenter @@ -37,43 +37,43 @@ class DefaultPresenter extends Nette\Application\UI\Presenter } ``` -Como todos os componentes são criados em métodos separados, o código é mais limpo e fácil de ler. +Graças ao fato de que todos os componentes são criados em métodos separados, o código ganha clareza. .[note] -Os nomes dos componentes sempre começam com uma letra minúscula, embora sejam capitalizados no nome do método. +Os nomes dos componentes sempre começam com letra minúscula, embora no nome do método sejam escritos com letra maiúscula. -Nunca ligamos diretamente para as fábricas, elas são chamadas automaticamente, quando usamos componentes pela primeira vez. Graças a isso, um componente é criado no momento certo, e somente se for realmente necessário. Se não usássemos o componente (por exemplo, em algum pedido AJAX, onde retornamos apenas parte da página, ou quando as peças estão em cache), ele nem mesmo seria criado e poupamos o desempenho do servidor. +As fábricas nunca são chamadas diretamente; elas são chamadas automaticamente na primeira vez que usamos o componente. Graças a isso, o componente é criado no momento certo e apenas se for realmente necessário. Se não usarmos o componente (por exemplo, durante uma requisição AJAX em que apenas parte da página é transferida, ou ao armazenar o template em cache), ele não será criado de forma alguma e economizaremos o desempenho do servidor. ```php .{file:DefaultPresenter.php} -// temos acesso ao componente e se foi a primeira vez, -// chama createComponentPoll() para criá-lo +// acessamos o componente e, se for a primeira vez, +// createComponentPoll() é chamado para criá-lo $poll = $this->getComponent('poll'); // sintaxe alternativa: $poll = $this['poll']; ``` -No modelo, você pode renderizar um componente usando a etiqueta [{controle} |#Rendering]. Portanto, não há necessidade de passar manualmente os componentes para o modelo. +No template, é possível renderizar o componente usando a tag [{control} |#Renderização]. Portanto, não é necessário passar manualmente os componentes para o template. ```latte -

    Please Vote

    +

    Vote

    {control poll} ``` -Estilo Hollywood .[#toc-hollywood-style] -======================================== +Estilo Hollywood +================ -Os componentes geralmente usam uma técnica legal, que nós gostamos de chamar de estilo Hollywood. Certamente você conhece o clichê que os atores ouvem com freqüência nos casting calls: "Não nos chame, nós o chamaremos". E é disso que se trata. +Os componentes geralmente usam uma técnica inovadora que gostamos de chamar de Estilo Hollywood. Você certamente conhece a frase famosa que os participantes de audições de cinema ouvem com tanta frequência: "Não nos ligue, nós ligaremos para você". E é exatamente disso que se trata. -Em Nette, ao invés de ter que fazer perguntas constantemente ("o formulário foi apresentado?", "ele era válido?" ou "alguém apertou este botão?"), você diz à estrutura "quando isto acontecer, chame este método" e deixa mais trabalho sobre ele. Se você programar em JavaScript, você está familiarizado com este estilo de programação. Você escreve funções que são chamadas quando um determinado evento ocorre. E o motor passa os parâmetros apropriados para elas. +No Nette, em vez de ter que perguntar constantemente ("o formulário foi enviado?", "era válido?" ou "o usuário pressionou este botão?"), você diz ao framework "quando isso acontecer, chame este método" e deixa o resto do trabalho para ele. Se você programa em JavaScript, está intimamente familiarizado com este estilo de programação. Você escreve funções que são chamadas quando um determinado evento ocorre. E a linguagem passa os parâmetros apropriados para elas. -Isto muda completamente a maneira como você escreve as aplicações. Quanto mais tarefas você puder delegar à estrutura, menos trabalho você terá. E quanto menos você puder esquecer. +Isso muda completamente a perspectiva sobre a escrita de aplicações. Quanto mais tarefas você puder deixar para o framework, menos trabalho você terá. E menos coisas você pode esquecer. -Como Escrever um Componente .[#toc-how-to-write-a-component] -============================================================ +Escrevendo um componente +======================== -Por componente entendemos geralmente os descendentes da classe [api:Nette\Application\UI\Control]. O próprio apresentador [api:Nette\Application\UI\Presenter] é também um descendente da classe `Control`. +Sob o termo componente, geralmente entendemos um descendente da classe [api:Nette\Application\UI\Control]. (Seria mais preciso usar o termo "controls", mas "controles" tem um significado diferente em português e "componentes" se tornou mais comum.) O próprio presenter [api:Nette\Application\UI\Presenter] também é, aliás, um descendente da classe `Control`. ```php .{file:PollControl.php} use Nette\Application\UI\Control; @@ -84,17 +84,17 @@ class PollControl extends Control ``` -Renderização .[#toc-rendering] -============================== +Renderização +============ -Já sabemos que a tag `{control componentName}` é usada para desenhar um componente. Na verdade, ela chama o método `render()` do componente, no qual nós cuidamos da renderização. Temos, assim como no apresentador, um modelo [Latte |latte:] na variável `$this->template`, para o qual passamos os parâmetros. Ao contrário do uso em um apresentador, devemos especificar um arquivo de modelo e deixá-lo renderizar: +Já sabemos que para renderizar um componente, usamos a tag `{control componentName}`. Ela basicamente chama o método `render()` do componente, no qual cuidamos da renderização. Temos à nossa disposição, exatamente como no presenter, um [template Latte|templates] na variável `$this->template`, para a qual passamos parâmetros. Ao contrário do presenter, precisamos especificar o arquivo de template e deixá-lo renderizar: ```php .{file:PollControl.php} public function render(): void { - // vamos colocar alguns parâmetros no modelo + // inserimos alguns parâmetros no template $this->template->param = $value; - // e desenhá-la + // e o renderizamos $this->template->render(__DIR__ . '/poll.latte'); } ``` @@ -112,7 +112,7 @@ public function render(int $id, string $message): void } ``` -Às vezes um componente pode consistir de várias partes que queremos renderizar separadamente. Para cada uma delas, criaremos um método próprio de renderização, aqui está, por exemplo, `renderPaginator()`: +Às vezes, um componente pode consistir em várias partes que queremos renderizar separadamente. Para cada uma delas, criamos nosso próprio método de renderização, aqui no exemplo, `renderPaginator()`: ```php .{file:PollControl.php} public function renderPaginator(): void @@ -121,69 +121,69 @@ public function renderPaginator(): void } ``` -E no modelo que então chamamos de usar: +E no template, então a chamamos usando: ```latte {control poll:paginator} ``` -Para melhor compreensão, é bom saber como a tag é compilada para o código PHP. +Para uma melhor compreensão, é bom saber como esta tag é traduzida para PHP. ```latte {control poll} {control poll:paginator 123, 'hello'} ``` -Isto se compila a: +é traduzido como: ```php $control->getComponent('poll')->render(); $control->getComponent('poll')->renderPaginator(123, 'hello'); ``` -`getComponent()` devolve o componente `poll` e depois o método `render()` ou `renderPaginator()`, respectivamente, é chamado sobre ele. +O método `getComponent()` retorna o componente `poll` e chama o método `render()` neste componente, ou `renderPaginator()` se um método de renderização diferente for especificado na tag após os dois pontos. .[caution] -Se em qualquer parte da parte do parâmetro **`=>`** for usado, todos os parâmetros serão envolvidos com uma matriz e passados como o primeiro argumento: +Atenção, se **`=>`** aparecer em qualquer lugar nos parâmetros, todos os parâmetros serão agrupados em um array e passados como o primeiro argumento: ```latte {control poll, id: 123, message: 'hello'} ``` -compila para: +é traduzido como: ```php $control->getComponent('poll')->render(['id' => 123, 'message' => 'hello']); ``` -Renderização de sub-componente: +Renderização de subcomponente: ```latte {control cartControl-someForm} ``` -compila para: +é traduzido como: ```php $control->getComponent("cartControl-someForm")->render(); ``` -Os componentes, como os apresentadores, passam automaticamente várias variáveis úteis para os modelos: +Componentes, assim como presenters, passam automaticamente várias variáveis úteis para os templates: -- `$basePath` é um caminho absoluto de URL para o dir raiz (por exemplo `/CD-collection`) -- `$baseUrl` é um URL absoluto para o dir raiz (por exemplo `http://localhost/CD-collection`) -- `$user` é um objeto [que representa o usuário |security:authentication] -- `$presenter` é o atual apresentador +- `$basePath` é o caminho URL absoluto para o diretório raiz (por exemplo, `/loja`) +- `$baseUrl` é a URL absoluta para o diretório raiz (por exemplo, `http://localhost/loja`) +- `$user` é o objeto [representando o usuário |security:authentication] +- `$presenter` é o presenter atual - `$control` é o componente atual -- `$flashes` lista de [mensagens |#flash-messages] enviadas por método `flashMessage()` +- `$flashes` array de [mensagens |#Mensagens Flash] enviadas pela função `flashMessage()` -Sinal .[#toc-signal] -==================== +Sinal +===== -Já sabemos que a navegação na aplicação Nette consiste em ligar ou redirecionar para pares `Presenter:action`. Mas e se quisermos apenas realizar uma ação na **página atual***? Por exemplo, alterar a ordem de classificação da coluna na tabela; apagar item; mudar o modo luz/escuro; enviar o formulário; votar na pesquisa; etc. +Já sabemos que a navegação em uma aplicação Nette consiste em vincular ou redirecionar para pares `Presenter:action`. Mas e se quisermos apenas executar uma ação na **página atual**? Por exemplo, alterar a ordenação das colunas em uma tabela; excluir um item; alternar entre modo claro/escuro; enviar um formulário; votar em uma enquete; etc. -Este tipo de pedido é chamado de sinal. E como as ações invocam métodos `action()` ou `render()`, sinaliza métodos de chamada `handle()`. Enquanto o conceito de ação (ou visão) se refere apenas aos apresentadores, os sinais se aplicam a todos os componentes. E, portanto, também aos apresentadores, pois `UI\Presenter` é descendente de `UI\Control`. +Esse tipo de requisição é chamado de sinal. E, assim como as ações invocam métodos `action()` ou `render()`, os sinais chamam métodos `handle()`. Enquanto o conceito de ação (ou view) está puramente relacionado aos presenters, os sinais se aplicam a todos os componentes. E, portanto, também aos presenters, porque `UI\Presenter` é um descendente de `UI\Control`. ```php public function handleClick(int $x, int $y): void @@ -192,36 +192,36 @@ public function handleClick(int $x, int $y): void } ``` -O link que chama o sinal é criado da maneira usual, ou seja, no modelo pelo atributo `n:href` ou na tag `{link}`, no código pelo método `link()`. Mais no capítulo [Criação de links URL |creating-links#Links to Signal]. +Criamos o link que chama o sinal da maneira usual, ou seja, no template com o atributo `n:href` ou a tag `{link}`, no código com o método `link()`. Mais no capítulo [Criando Links URL |creating-links#Links para sinal]. ```latte -click here +clique aqui ``` -O sinal é sempre chamado no apresentador e na visão atual, de modo que não é possível fazer a ligação com o sinal em diferentes apresentadores/ações. +O sinal é sempre chamado no presenter e action atuais, não é possível chamá-lo em outro presenter ou outra action. -Assim, o sinal faz com que a página seja recarregada exatamente da mesma forma que no pedido original, só que, além disso, ele chama o método de manuseio do sinal com os parâmetros apropriados. Se o método não existir, é lançada a exceção [api:Nette\Application\UI\BadSignalException], que é exibida ao usuário como erro página 403 Proibida. +Portanto, o sinal causa o recarregamento da página exatamente como na requisição original, mas adicionalmente chama o método de manipulação do sinal com os parâmetros apropriados. Se o método não existir, uma exceção [api:Nette\Application\UI\BadSignalException] é lançada, que é exibida ao usuário como uma página de erro 403 Forbidden. -Snippets e AJAX .[#toc-snippets-and-ajax] -========================================= +Snippets e AJAX +=============== -Os sinais podem lembrar um pouco o AJAX: manipuladores que são chamados na página atual. E você está certo, os sinais são realmente chamados com freqüência usando AJAX, e então nós só transmitimos partes alteradas da página para o navegador. Eles são chamados de snippets. Mais informações podem ser encontradas na [página sobre o AJAX |ajax]. +Sinais podem lembrá-lo um pouco de AJAX: manipuladores que são invocados na página atual. E você está certo, sinais são frequentemente chamados via AJAX e, subsequentemente, apenas as partes alteradas da página são transmitidas para o navegador. Ou seja, os chamados snippets. Mais informações podem ser encontradas na [página dedicada ao AJAX |ajax]. -Mensagens Flash .[#toc-flash-messages] -====================================== +Mensagens Flash +=============== -Um componente tem seu próprio armazenamento de mensagens flash, independente do apresentador. Estas são mensagens que, por exemplo, informam sobre o resultado da operação. Uma característica importante das mensagens flash é que elas estão disponíveis no modelo, mesmo após o redirecionamento. Mesmo após serem exibidas, elas permanecerão vivas por mais 30 segundos - por exemplo, caso o usuário atualize involuntariamente a página - a mensagem não será perdida. +O componente tem seu próprio armazenamento de mensagens flash independente do presenter. São mensagens que, por exemplo, informam sobre o resultado de uma operação. Uma característica importante das mensagens flash é que elas estão disponíveis no template mesmo após um redirecionamento. Mesmo após serem exibidas, elas permanecem ativas por mais 30 segundos - por exemplo, caso o usuário atualize a página devido a um erro de transmissão - a mensagem não desaparecerá imediatamente. -O envio é feito através do método [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. O primeiro parâmetro é o texto da mensagem ou o objeto `stdClass` que representa a mensagem. O segundo parâmetro opcional é seu tipo (erro, aviso, informação, etc.). O método `flashMessage()` retorna uma instância de mensagem flash como objeto stdClass, para a qual você pode passar informações. +O envio é feito pelo método [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. O primeiro parâmetro é o texto da mensagem ou um objeto `stdClass` representando a mensagem. O segundo parâmetro opcional é o seu tipo (erro, aviso, informação, etc.). O método `flashMessage()` retorna uma instância da mensagem flash como um objeto `stdClass`, ao qual informações adicionais podem ser adicionadas. ```php -$this->flashMessage('Item foi excluído'); -$this->redirect(/* ... */); // e redirecionar +$this->flashMessage('O item foi excluído.'); +$this->redirect(/* ... */); // e redirecionamos ``` -No modelo, estas mensagens estão disponíveis na variável `$flashes` como objetos `stdClass`, que contém as propriedades `message` (texto da mensagem), `type` (tipo de mensagem) e podem conter as informações de usuário já mencionadas. Nós as desenhamos da seguinte forma: +No template, essas mensagens estão disponíveis na variável `$flashes` como objetos `stdClass`, que contêm as propriedades `message` (texto da mensagem), `type` (tipo da mensagem) e podem conter as informações do usuário já mencionadas. Nós as renderizamos assim, por exemplo: ```latte {foreach $flashes as $flash} @@ -230,17 +230,39 @@ No modelo, estas mensagens estão disponíveis na variável `$flashes` como obje ``` -Parâmetros Persistentes .[#toc-persistent-parameters] -===================================================== +Redirecionamento após sinal +=========================== -Parâmetros persistentes são usados para manter o estado em componentes entre diferentes solicitações. Seu valor permanece o mesmo, mesmo depois que um link é clicado. Ao contrário dos dados da sessão, eles são transferidos na URL. E eles são transferidos automaticamente, incluindo links criados em outros componentes na mesma página. +Após o processamento de um sinal de componente, frequentemente segue-se um redirecionamento. É uma situação semelhante à dos formulários - após o envio deles, também redirecionamos para que, ao atualizar a página no navegador, os dados não sejam enviados novamente. -Por exemplo, você tem um componente de paginação de conteúdo. Pode haver vários desses componentes em uma página. E você quer que todos os componentes permaneçam em sua página atual quando você clicar no link. Portanto, tornamos o número da página (`page`) um parâmetro persistente. +```php +$this->redirect('this'); // redireciona para o presenter e action atuais +``` + +Como o componente é um elemento reutilizável e geralmente não deve ter um vínculo direto com presenters específicos, os métodos `redirect()` e `link()` interpretam automaticamente o parâmetro como um sinal do componente: + +```php +$this->redirect('click'); // redireciona para o sinal 'click' do mesmo componente +``` + +Se precisar redirecionar para outro presenter ou ação, você pode fazer isso através do presenter: + +```php +$this->getPresenter()->redirect('Product:show'); // redireciona para outro presenter/action +``` + + +Parâmetros persistentes +======================= + +Parâmetros persistentes são usados para manter o estado nos componentes entre diferentes requisições. Seu valor permanece o mesmo mesmo após clicar em um link. Ao contrário dos dados na sessão, eles são transmitidos na URL. E isso de forma totalmente automática, inclusive em links criados em outros componentes na mesma página. + +Por exemplo, você tem um componente para paginação de conteúdo. Pode haver vários desses componentes em uma página. E desejamos que, após clicar em um link, todos os componentes permaneçam em sua página atual. Portanto, transformamos o número da página (`page`) em um parâmetro persistente. -Criar um parâmetro persistente é extremamente fácil em Nette. Basta criar uma propriedade pública e etiquetá-la com o atributo: (anteriormente foi utilizado `/** @persistent */` ) +Criar um parâmetro persistente no Nette é extremamente simples. Basta criar uma propriedade pública e marcá-la com um atributo: (anteriormente usava-se `/** @persistent */`) ```php -use Nette\Application\Attributes\Persistent; // esta linha é importante +use Nette\Application\Attributes\Persistent; // esta linha é importante class PaginatingControl extends Control { @@ -249,25 +271,25 @@ class PaginatingControl extends Control } ``` -Recomendamos que você inclua o tipo de dados (por exemplo `int`) com o imóvel, e você também pode incluir um valor padrão. Os valores dos parâmetros podem ser [validados |#Validation of Persistent Parameters]. +Recomendamos especificar o tipo de dados para a propriedade (por exemplo, `int`) e você também pode especificar um valor padrão. Os valores dos parâmetros podem ser [validados |#Validação de parâmetros persistentes]. -Você pode alterar o valor de um parâmetro persistente ao criar um link: +Ao criar um link, o valor do parâmetro persistente pode ser alterado: ```latte -next +próximo ``` -Ou pode ser *reset*, ou seja, removido do URL. Então, ele tomará seu valor padrão: +Ou pode ser *resetado*, ou seja, removido da URL. Então ele assumirá seu valor padrão: ```latte -reset +resetar ``` -Componentes Persistentes .[#toc-persistent-components] -====================================================== +Componentes persistentes +======================== -Não apenas os parâmetros, mas também os componentes podem ser persistentes. Seus parâmetros persistentes também são transferidos entre diferentes ações ou entre diferentes apresentadores. Marcamos os componentes persistentes com estas anotações para a classe do apresentador. Por exemplo, aqui marcamos os componentes `calendar` e `poll` como a seguir: +Não apenas parâmetros, mas também componentes podem ser persistentes. Em tal componente, seus parâmetros persistentes são transmitidos mesmo entre diferentes ações do presenter ou entre vários presenters. Marcamos componentes persistentes com uma anotação na classe do presenter. Por exemplo, marcamos os componentes `calendar` e `poll` assim: ```php /** @@ -278,7 +300,7 @@ class DefaultPresenter extends Nette\Application\UI\Presenter } ``` -Não é necessário marcar os subcomponentes como persistentes, eles são persistentes automaticamente. +Subcomponentes dentro desses componentes não precisam ser marcados, eles também se tornarão persistentes. No PHP 8, você também pode usar atributos para marcar componentes persistentes: @@ -292,35 +314,35 @@ class DefaultPresenter extends Nette\Application\UI\Presenter ``` -Componentes com Dependências .[#toc-components-with-dependencies] -================================================================= +Componentes com dependências +============================ -Como criar componentes com dependências sem "bagunçar" os apresentadores que irão utilizá-los? Graças às características inteligentes do contêiner DI em Nette, como no uso de serviços tradicionais, podemos deixar a maior parte do trabalho para a estrutura. +Como criar componentes com dependências sem "poluir" os presenters que os usarão? Graças às propriedades inteligentes do contêiner de DI no Nette, assim como no uso de serviços clássicos, podemos deixar a maior parte do trabalho para o framework. -Tomemos como exemplo um componente que depende do serviço `PollFacade`: +Vamos pegar como exemplo um componente que tem dependência do serviço `PollFacade`: ```php class PollControl extends Control { public function __construct( - private int $id, // Id de uma pesquisa, para a qual o componente é criado + private int $id, // ID da enquete para a qual estamos criando o componente private PollFacade $facade, ) { } public function handleVote(int $voteId): void { - $this->facade->vote($id, $voteId); + $this->facade->vote($this->id, $voteId); // ... } } ``` -Se estivéssemos escrevendo um serviço clássico, não haveria nada com que se preocupar. O recipiente DI se encarregaria invisivelmente de passar todas as dependências. Mas normalmente lidamos com componentes criando uma nova instância deles diretamente no apresentador nos [métodos de fábrica |#factory methods] `createComponent...()`. Mas passar todas as dependências de todos os componentes para o apresentador para depois passá-los aos componentes é incômodo. E a quantidade de código escrito... +Se estivéssemos escrevendo um serviço clássico, não haveria problema. O contêiner de DI cuidaria invisivelmente da passagem de todas as dependências. Mas com componentes, geralmente lidamos de forma que criamos sua nova instância diretamente no presenter nos [#métodos de fábrica] `createComponent…()`. Mas passar todas as dependências de todos os componentes para o presenter, para então passá-las aos componentes, é complicado. E a quantidade de código escrito… -A questão lógica é: por que não registramos o componente como um serviço clássico, passamos para o apresentador e depois o devolvemos no método `createComponent...()`? Mas esta abordagem é inapropriada porque queremos ser capazes de criar o componente várias vezes. +A questão lógica é: por que simplesmente não registramos o componente como um serviço clássico, o passamos para o presenter e depois o retornamos no método `createComponent…()`? Essa abordagem, no entanto, é inadequada, porque queremos ter a possibilidade de criar o componente várias vezes, se necessário. -A solução correta é escrever uma fábrica para o componente, ou seja, uma classe que cria o componente para nós: +A solução correta é escrever uma fábrica para o componente, ou seja, uma classe que criará o componente para nós: ```php class PollControlFactory @@ -337,17 +359,17 @@ class PollControlFactory } ``` -Agora registramos nosso serviço de DI container à configuração: +Registramos essa fábrica em nosso contêiner na configuração: ```neon services: - PollControlFactory ``` -Finalmente, utilizaremos esta fábrica em nosso apresentador: +e finalmente a usamos em nosso presenter: ```php -class PollPresenter extends Nette\UI\Application\Presenter +class PollPresenter extends Nette\Application\UI\Presenter { public function __construct( private PollControlFactory $pollControlFactory, @@ -362,7 +384,7 @@ class PollPresenter extends Nette\UI\Application\Presenter } ``` -O ótimo é que a Nette DI pode [gerar |dependency-injection:factory] fábricas tão simples, portanto, em vez de escrever o código inteiro, basta escrever sua interface: +O ótimo é que o Nette DI pode [gerar |dependency-injection:factory] essas fábricas simples, então, em vez de todo o seu código, basta escrever apenas sua interface: ```php interface PollControlFactory @@ -371,21 +393,21 @@ interface PollControlFactory } ``` -Isso é tudo. A Nette implementa internamente esta interface e a injeta em nosso apresentador, onde podemos utilizá-la. Ela também passa magicamente nosso parâmetro `$id` e instância da classe `PollFacade` para nosso componente. +E isso é tudo. O Nette implementará internamente esta interface e a passará para o presenter, onde já podemos usá-la. Ele magicamente adiciona o parâmetro `$id` e a instância da classe `PollFacade` ao nosso componente. -Componentes em profundidade .[#toc-components-in-depth] -======================================================= +Componentes em profundidade +=========================== -Os componentes de uma aplicação Nette são as partes reutilizáveis de uma aplicação web que incorporamos nas páginas, que é o tema deste capítulo. Quais são exatamente as capacidades de um componente desse tipo? +Componentes na Nette Application representam partes reutilizáveis de uma aplicação web que inserimos nas páginas e às quais, aliás, todo este capítulo é dedicado. Quais são exatamente as capacidades de tal componente? -1) é renderizável em um modelo -2) ela sabe qual parte de si mesma deve prestar durante um [pedido AJAX |ajax#invalidation] (trechos) -3) tem a capacidade de armazenar seu estado em uma URL (parâmetros persistentes) -4) tem a capacidade de responder às ações (sinais) do usuário -5) cria uma estrutura hierárquica (onde a raiz é o apresentador) +1) é renderizável no template +2) sabe [qual parte sua |ajax#Snippets] deve ser renderizada durante uma requisição AJAX (snippets) +3) tem a capacidade de armazenar seu estado na URL (parâmetros persistentes) +4) tem a capacidade de reagir a ações do usuário (sinais) +5) cria uma estrutura hierárquica (onde a raiz é o presenter) -Cada uma destas funções é tratada por uma das classes de linhagem de herança. A renderização (1 + 2) é tratada por [api:Nette\Application\UI\Control], a incorporação ao [ciclo de vida |presenters#life-cycle-of-presenter] (3, 4) pela classe [api:Nette\Application\UI\Component] e a criação da estrutura hierárquica (5) pelas classes [Container e Component |component-model:]. +Cada uma dessas funções é cuidada por alguma das classes da linha de herança. A renderização (1 + 2) é responsabilidade de [api:Nette\Application\UI\Control], a integração no [ciclo de vida |presenters#Ciclo de vida do presenter] (3, 4) da classe [api:Nette\Application\UI\Component] e a criação da estrutura hierárquica (5) das classes [Container e Component |component-model:]. ``` Nette\ComponentModel\Component { IComponent } @@ -400,18 +422,18 @@ Nette\ComponentModel\Component { IComponent } ``` -Ciclo de vida do componente .[#toc-life-cycle-of-component] ------------------------------------------------------------ +Ciclo de vida do componente +--------------------------- -[* lifecycle-component.svg *] *** Ciclo de vida do componente* .<> +[* lifecycle-component.svg *] *** *Ciclo de vida do componente* .<> -Validação de Parâmetros Persistentes .[#toc-validation-of-persistent-parameters] --------------------------------------------------------------------------------- +Validação de parâmetros persistentes +------------------------------------ -Os valores de [parâmetros persistentes |#persistent parameters] recebidos de URLs são escritos nas propriedades pelo método `loadState()`. Ele também verifica se o tipo de dados especificado para a propriedade corresponde, caso contrário ele responderá com um erro 404 e a página não será exibida. +Os valores dos [#parâmetros persistentes] recebidos da URL são escritos nas propriedades pelo método `loadState()`. Ele também verifica se o tipo de dados especificado na propriedade corresponde, caso contrário, responde com um erro 404 e a página não é exibida. -Nunca confie cegamente em parâmetros persistentes porque eles podem ser facilmente sobrescritos pelo usuário no URL. Por exemplo, é assim que verificamos se o número da página `$this->page` é maior que 0. Uma boa maneira de fazer isso é sobrescrever o método `loadState()` mencionado acima: +Nunca confie cegamente nos parâmetros persistentes, pois eles podem ser facilmente sobrescritos pelo usuário na URL. Assim, por exemplo, verificamos se o número da página `$this->page` é maior que 0. Uma maneira adequada é sobrescrever o método mencionado `loadState()`: ```php class PaginatingControl extends Control @@ -421,8 +443,8 @@ class PaginatingControl extends Control public function loadState(array $params): void { - parent::loadState($params); // aqui está definido o $this->page - // segue a verificação do valor do usuário: + parent::loadState($params); // aqui $this->page é definido + // segue a verificação personalizada do valor: if ($this->page < 1) { $this->error(); } @@ -430,27 +452,27 @@ class PaginatingControl extends Control } ``` -O processo oposto, ou seja, a coleta de valores de properites persistentes, é tratado pelo método `saveState()`. +O processo oposto, ou seja, coletar valores das propriedades persistentes, é responsabilidade do método `saveState()`. -Sinais em profundidade .[#toc-signals-in-depth] ------------------------------------------------ +Sinais em profundidade +---------------------- -Um sinal causa uma recarga de página como a solicitação original (com exceção do AJAX) e invoca o método `signalReceived($signal)` cuja implementação padrão na classe `Nette\Application\UI\Component` tenta chamar um método composto pelas palavras `handle{Signal}`. O processamento posterior depende do objeto em questão. Os objetos que são descendentes de `Component` (ou seja, `Control` e `Presenter`) tentam chamar `handle{Signal}` com parâmetros relevantes. +Um sinal causa o recarregamento da página exatamente como na requisição original (exceto quando chamado via AJAX) e invoca o método `signalReceived($signal)`, cuja implementação padrão na classe `Nette\Application\UI\Component` tenta chamar um método composto pelas palavras `handle{signal}`. O processamento adicional depende do objeto em questão. Objetos que herdam de `Component` (ou seja, `Control` e `Presenter`) reagem tentando chamar o método `handle{signal}` com os parâmetros apropriados. -Em outras palavras: a definição do método `handle{Signal}` é tomada e todos os parâmetros que foram recebidos no pedido são combinados com os parâmetros do método. Isso significa que o parâmetro `id` da URL é comparado com o parâmetro do método `$id`, `something` a `$something` e assim por diante. E se o método não existir, o método `signalReceived` lança [uma exceção |api:Nette\Application\UI\BadSignalException]. +Em outras palavras: pega-se a definição da função `handle{signal}` e todos os parâmetros que vieram com a requisição, e os parâmetros da URL são atribuídos aos argumentos pelo nome e tenta-se chamar o método dado. Por exemplo, o valor do parâmetro `id` na URL é passado como parâmetro `$id`, `something` da URL é passado como `$something`, etc. E se o método não existir, o método `signalReceived` lança uma [exceção |api:Nette\Application\UI\BadSignalException]. -O sinal pode ser recebido por qualquer componente, apresentador do objeto que implementa a interface `SignalReceiver` se estiver conectado à árvore de componentes. +O sinal pode ser recebido por qualquer componente, presenter ou objeto que implemente a interface `SignalReceiver` e esteja conectado à árvore de componentes. -Os principais receptores de sinais são `Presenters` e componentes visuais que se estendem a `Control`. Um sinal é um sinal para um objeto que tem que fazer algo - a pesquisa conta em um voto do usuário, a caixa com notícias tem que se desdobrar, o formulário foi enviado e tem que processar dados e assim por diante. +Os principais receptores de sinais serão `Presenters` e componentes visuais que herdam de `Control`. O sinal deve servir como um sinal para o objeto de que ele deve fazer algo - a enquete deve contar o voto do usuário, o bloco de notícias deve se expandir e exibir o dobro de notícias, o formulário foi enviado e deve processar os dados, e assim por diante. -A URL para o sinal é criada usando o método [Componente::link() |api:Nette\Application\UI\Component::link()]. Como parâmetro `$destination` passamos a string `{signal}!` e como `$args` uma série de argumentos que queremos passar para o manipulador do sinal. Os parâmetros de sinal são anexados à URL do apresentador/visualizador atual. **O parâmetro `?do` no URL determina o sinal chamado.** +A URL para o sinal é criada usando o método [Component::link() |api:Nette\Application\UI\Component::link()]. Como parâmetro `$destination`, passamos a string `{signal}!` e como `$args`, um array de argumentos que queremos passar para o sinal. O sinal é sempre chamado no presenter e action atuais com os parâmetros atuais, os parâmetros do sinal são apenas adicionados. Além disso, o **parâmetro `?do`, que especifica o sinal**, é adicionado logo no início. -Seu formato é `{signal}` ou `{signalReceiver}-{signal}`. `{signalReceiver}` é o nome do componente no apresentador. É por isso que o hífen (traço inexato) não pode estar presente no nome dos componentes - é usado para dividir o nome do componente e do sinal, mas é possível compor vários componentes. +Seu formato é `{signal}` ou `{signalReceiver}-{signal}`. `{signalReceiver}` é o nome do componente no presenter. É por isso que um hífen não pode estar no nome do componente - ele é usado para separar o nome do componente e o sinal, mas é possível aninhar vários componentes dessa maneira. -O método [éSignalReceiver() |api:Nette\Application\UI\Presenter::isSignalReceiver()] verifica se um componente (primeiro argumento) é um receptor de um sinal (segundo argumento). O segundo argumento pode ser omitido - então ele descobre se o componente é um receptor de qualquer sinal. Se o segundo parâmetro for `true`, verifica se o componente ou seus descendentes são receptores de um sinal. +O método [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] verifica se o componente (primeiro argumento) é o receptor do sinal (segundo argumento). Podemos omitir o segundo argumento - então ele verifica se o componente é o receptor de qualquer sinal. `true` pode ser passado como segundo parâmetro para verificar se não apenas o componente especificado, mas também qualquer um de seus descendentes é o receptor. -Em qualquer fase anterior a `handle{Signal}`, o sinal pode ser executado manualmente chamando o método [ProcessSignal() |api:Nette\Application\UI\Presenter::processSignal()], que assume a responsabilidade pela execução do sinal. Pega o componente receptor (se não for definido, é o próprio apresentador) e lhe envia o sinal. +Em qualquer fase anterior a `handle{signal}`, podemos executar o sinal manualmente chamando o método [processSignal()|api:Nette\Application\UI\Presenter::processSignal()], que se encarrega de tratar o sinal - pega o componente que foi determinado como o receptor do sinal (se nenhum receptor de sinal for especificado, é o próprio presenter) e envia o sinal para ele. Exemplo: @@ -460,4 +482,4 @@ if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, ' } ``` -O sinal é executado prematuramente e não será chamado novamente. +Assim, o sinal é executado prematuramente e não será chamado novamente. diff --git a/application/pt/configuration.texy b/application/pt/configuration.texy index 3fcf68b9b0..e4a50ee6a1 100644 --- a/application/pt/configuration.texy +++ b/application/pt/configuration.texy @@ -1,58 +1,71 @@ -Configuração da aplicação -************************* +Configuração de aplicações +************************** .[perex] -Visão geral das opções de configuração para a Aplicação Nette. +Visão geral das opções de configuração para Aplicações Nette. -Aplicação .[#toc-application] -============================= +Application +=========== ```neon application: - # mostra painel "Aplicação Nette" em Tracy BlueScreen? - debugger: ... # (bool) por omissão + # exibir o painel "Nette Application" no Tracy BlueScreen? + debugger: ... # (bool) padrão é true - # será que o apresentador de erro será chamado por erro? - catchExceptions: ... # (bool) é o padrão verdadeiro no modo de produção + # o error-presenter será chamado em caso de erro? + # tem efeito apenas no modo de desenvolvimento + catchExceptions: ... # (bool) padrão é true - # nome do apresentador de erro - errorPresenter: Error # (string) padrão para 'Nette:Erro'. + # nome do error-presenter + errorPresenter: Error # (string|array) padrão é 'Nette:Error' - # define as regras para resolver o nome do apresentador para uma classe + # define aliases para presenters e ações + aliases: ... + + # define regras para traduzir o nome do presenter para a classe mapping: ... - # os links ruins geram avisos? - # tem efeito somente no modo desenvolvedor - silentLinks: ... # (bool) falha em falso + # links inválidos não geram avisos? + # tem efeito apenas no modo de desenvolvimento + silentLinks: ... # (bool) padrão é false ``` -Como os apresentadores de erros não são chamados por padrão no modo de desenvolvimento e os erros são exibidos por Tracy, alterar o valor `catchExceptions` para `true` ajuda a verificar se os apresentadores de erros funcionam corretamente durante o desenvolvimento. +A partir da versão `nette/application` 3.2, é possível definir um par de error-presenters: -A opção `silentLinks` determina como Nette se comporta em modo desenvolvedor quando a geração de links falha (por exemplo, porque não há apresentador, etc.). O valor padrão `false` significa que a Nette aciona `E_USER_WARNING`. A configuração para `true` suprime esta mensagem de erro. Em um ambiente de produção, `E_USER_WARNING` é sempre invocado. Também podemos influenciar este comportamento definindo a variável apresentadora [$invalidLinkMode |creating-links#Invalid Links]. +```neon +application: + errorPresenter: + 4xx: Error4xx # para a exceção Nette\Application\BadRequestException + 5xx: Error5xx # para outras exceções +``` -O [mapeamento define as regras |modules#mapping] pelas quais o nome da classe é derivado do nome do apresentador. +A opção `silentLinks` determina como o Nette se comporta no modo de desenvolvimento quando a geração de um link falha (por exemplo, porque o presenter não existe, etc.). O valor padrão `false` significa que o Nette lançará um erro `E_USER_WARNING`. Definir como `true` suprimirá esta mensagem de erro. No ambiente de produção, `E_USER_WARNING` é sempre lançado. Este comportamento também pode ser influenciado definindo a variável do presenter [$invalidLinkMode |creating-links#Links inválidos]. +[Aliases simplificam a vinculação |creating-links#Aliases] a presenters frequentemente usados. -Registro automático dos apresentadores .[#toc-automatic-registration-of-presenters] ------------------------------------------------------------------------------------ +[Mapeamento define regras |directory-structure#Mapeamento de presenters], segundo as quais o nome da classe é derivado do nome do presenter. -A Nette adiciona automaticamente apresentadores como serviços ao contêiner DI, o que acelera significativamente sua criação. Como Nette descobre que os apresentadores podem ser configurados: + +Registro automático de presenters +--------------------------------- + +O Nette adiciona automaticamente presenters como serviços ao contêiner de DI, o que acelera significativamente sua criação. Como o Nette localiza os presenters pode ser configurado: ```neon application: - # para procurar apresentadores no mapa da classe Composer? - scanComposer: ... # (bool) por omissão + # procurar presenters no mapa de classes do Composer? + scanComposer: ... # (bool) padrão é true - # uma máscara que deve corresponder à classe e ao nome do arquivo - scanFilter: ... # (string) default to '*Presenter' (string) + # máscara que o nome da classe e do arquivo deve corresponder + scanFilter: ... # (string) padrão é '*Presenter' - # em quais diretórios procurar apresentadores? - scanDirs: # (string[]|false) padrão a '%appDir%'. + # em quais diretórios procurar presenters? + scanDirs: # (string[]|false) padrão é '%appDir%' - %vendorDir%/mymodule ``` -Os diretórios listados em `scanDirs` não substituem o valor padrão `%appDir%`, mas o complementam, portanto `scanDirs` conterá ambos os caminhos `%appDir%` e `%vendorDir%/mymodule`. Se quisermos sobrescrever o diretório padrão, usamos [ponto de exclamação |dependency-injection:configuration#Merging]: +Os diretórios listados em `scanDirs` não sobrescrevem o valor padrão `%appDir%`, mas o complementam, então `scanDirs` conterá ambos os caminhos `%appDir%` e `%vendorDir%/mymodule`. Se quisermos omitir o diretório padrão, usamos [um ponto de exclamação |dependency-injection:configuration#Mesclagem], que sobrescreve o valor: ```neon application: @@ -60,64 +73,73 @@ application: - %vendorDir%/mymodule ``` -A digitalização de diretórios pode ser desativada definindo falso. Não recomendamos a supressão completa da adição automática de apresentadores, caso contrário, o desempenho da aplicação será reduzido. +A varredura de diretórios pode ser desativada especificando o valor false. Não recomendamos suprimir completamente a adição automática de presenters, pois isso resultará em uma redução no desempenho da aplicação. -Latte .[#toc-latte] -=================== +Templates Latte +=============== -Este cenário afeta globalmente o comportamento do Latte em componentes e apresentadores. +Com esta configuração, o comportamento do Latte em componentes e presenters pode ser influenciado globalmente. ```neon latte: - # mostra o painel Latte no Tracy Bar para o modelo principal (true) ou para todos os componentes (all)? - debugger: ... # (true|false|'all') padrão para true + # exibir o painel Latte na Barra Tracy para o template principal (true) ou todos os componentes (all)? + debugger: ... # (true|false|'all') padrão é true + + # gera templates com o cabeçalho declare(strict_types=1) + strictTypes: ... # (bool) padrão é false - # gera modelos com declare(strict_types=1) - strictTypes: ... # (bool) falha em falso + # ativa o modo de [parser estrito |latte:develop#striktní režim] + strictParsing: ... # (bool) padrão é false - # classe de $this->template - templateClass: App\MyTemplateClass # defaults to Nette\Bridges\ApplicationLatte\DefaultTemplate + # ativa a [verificação do código gerado |latte:develop#Kontrola vygenerovaného kódu] + phpLinter: ... # (string) padrão é null + + # define a localidade + locale: pt_BR # (string) padrão é null + + # classe do objeto $this->template + templateClass: App\MyTemplateClass # padrão é Nette\Bridges\ApplicationLatte\DefaultTemplate ``` -Se você estiver usando Latte versão 3, você pode adicionar uma nova [extensão |latte:creating-extension] usando: +Se você estiver usando Latte versão 3, pode adicionar novas [extensões |latte:extending-latte#Latte Extension] usando: ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -/--comment - - - - +Se você estiver usando Latte versão 2, pode registrar novas tags especificando o nome da classe ou uma referência a um serviço. Por padrão, o método `install()` é chamado, mas isso pode ser alterado especificando o nome de outro método: +```neon +latte: + # registro de tags Latte personalizadas + macros: + - App\MyLatteMacros::register # método estático, nome da classe ou callable + - @App\MyLatteMacrosFactory # serviço com método install() + - @App\MyLatteMacrosFactory::register # serviço com método register() + +services: + - App\MyLatteMacrosFactory +``` - - - - -\-- - - -Roteiro .[#toc-routing] -======================= +Roteamento +========== Configurações básicas: ```neon routing: - # mostra painel de roteamento em Tracy Bar? - debugger: ... # (bool) por omissão + # exibir o painel de roteamento na Barra Tracy? + debugger: ... # (bool) padrão é true - # para seriar o roteador para o contêiner DI? - cache: ... # (bool) falha em falso + # serializa o roteador no contêiner DI + cache: ... # (bool) padrão é false ``` -O roteador é normalmente definido na classe [RouterFactory |routing#Route Collection]. Alternativamente, as rotinas também podem ser definidas na configuração usando `mask: action` pares, mas este método não oferece uma variação tão grande nas configurações: +O roteamento geralmente é definido na classe [RouterFactory |routing#Coleção de rotas]. Alternativamente, as rotas também podem ser definidas na configuração usando pares `máscara: ação`, mas este método não oferece tanta variabilidade nas configurações: ```neon routing: @@ -127,8 +149,8 @@ routing: ``` -Constantes .[#toc-constants] -============================ +Constantes +========== Criação de constantes PHP. @@ -137,18 +159,33 @@ constants: Foobar: 'baz' ``` -A constante `Foobar` será criada após o início das operações. +Após iniciar a aplicação, a constante `Foobar` será criada. .[note] -As constantes não devem servir como variáveis disponíveis globalmente. Para passar valores a objetos, use a [injeção de dependência |dependency-injection:passing-dependencies]. +Constantes não devem servir como variáveis globalmente disponíveis. Para passar valores para objetos, use [injeção de dependência |dependency-injection:passing-dependencies]. PHP === -Você pode definir diretrizes PHP. Uma visão geral de todas as diretivas pode ser encontrada em [php.net |https://www.php.net/manual/en/ini.list.php]. +Configuração de diretivas PHP. Uma visão geral de todas as diretivas pode ser encontrada em [php.net |https://www.php.net/manual/en/ini.list.php]. ```neon php: - date.timezone: Europe/Prague + date.timezone: Europe/Lisbon ``` + + +Serviços DI +=========== + +Estes serviços são adicionados ao contêiner de DI: + +| Nome | Tipo | Descrição +|---------------------------------------------------------- +| `application.application` | [api:Nette\Application\Application] | [iniciador de toda a aplicação |how-it-works#Nette Application] +| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] +| `application.presenterFactory` | [api:Nette\Application\PresenterFactory] | fábrica de presenters +| `application.###` | [api:Nette\Application\UI\Presenter] | presenters individuais +| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | fábrica do objeto `Latte\Engine` +| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | fábrica para [`$this->template` |templates] diff --git a/application/pt/creating-links.texy b/application/pt/creating-links.texy index b58d739ab1..67af4a919b 100644 --- a/application/pt/creating-links.texy +++ b/application/pt/creating-links.texy @@ -1,160 +1,160 @@ -Criação de links URL -******************** +Criando Links URL +*****************
    -Criar links em Nette é tão fácil quanto apontar um dedo. Basta apontar e a estrutura fará todo o trabalho por você. Nós mostraremos: +Criar links no Nette é tão simples quanto apontar o dedo. Basta apontar e o framework faz todo o trabalho por você. Vamos mostrar: -- como criar links em modelos e em outros lugares +- como criar links em templates e em outros lugares - como distinguir um link para a página atual -- E os links inválidos? +- o que fazer com links inválidos
    -Graças ao [roteamento bidirecional |routing], você nunca terá que codificar as URLs dos aplicativos nos modelos ou códigos, que podem mudar mais tarde ou ser complicados para compor. Basta especificar o apresentador e a ação no link, passar quaisquer parâmetros e a estrutura gerará a própria URL. Na verdade, é muito semelhante a chamar uma função. Você vai gostar. +Graças ao [roteamento bidirecional |routing], você nunca precisará escrever URLs fixas da sua aplicação em templates ou código, que podem mudar posteriormente, ou montá-las de forma complicada. No link, basta indicar o presenter e a ação, passar quaisquer parâmetros e o framework gerará a URL por si só. Na verdade, é muito semelhante a chamar uma função. Você vai gostar disso. -No modelo do apresentador .[#toc-in-the-presenter-template] -=========================================================== +No template do presenter +======================== -Na maioria das vezes criamos links em modelos e um grande ajudante é o atributo `n:href`: +Mais frequentemente, criamos links em templates e um ótimo auxiliar é o atributo `n:href`: ```latte -detail +detalhe ``` -Note que, em vez do atributo HTML `href`, usamos o [atributo n: |latte:syntax#n:attributes] `n:href`. Seu valor não é uma URL, como você está acostumado com o atributo `href`, mas o nome do apresentador e da ação. +Observe que, em vez do atributo HTML `href`, usamos o [n:atributo |latte:syntax#n:atributos] `n:href`. Seu valor não é uma URL, como seria no caso do atributo `href`, but o nome do presenter e da ação. -Clicando em um link é, simplesmente dito, algo como chamar um método `ProductPresenter::renderShow()`. E se ele tiver parâmetros em sua assinatura, podemos chamá-lo com argumentos: +Clicar no link é, simplificadamente, algo como chamar o método `ProductPresenter::renderShow()`. E se ele tiver parâmetros em sua assinatura, podemos chamá-lo com argumentos: ```latte -detail +detalhe do produto ``` -Também é possível passar parâmetros nomeados. O seguinte link passa o parâmetro `lang` com o valor `en`: +Também é possível passar parâmetros nomeados. O link a seguir passa o parâmetro `lang` com o valor `pt`: ```latte -detail +detalhe do produto ``` -Se o método `ProductPresenter::renderShow()` não tiver `$lang` em sua assinatura, ele pode ler o valor do parâmetro usando `$lang = $this->getParameter('lang')`. +Se o método `ProductPresenter::renderShow()` não tiver `$lang` em sua assinatura, ele pode obter o valor do parâmetro usando `$lang = $this->getParameter('lang')` ou da [propriedade |presenters#Parâmetros da requisição]. -Se os parâmetros estiverem armazenados em uma matriz, eles podem ser expandidos com o operador `...` (ou `(expand)` operador em Latte 2.x): +Se os parâmetros estiverem armazenados em um array, eles podem ser expandidos com o operador `...` (no Latte 2.x, com o operador `(expand)`): ```latte -{var $args = [$product->id, lang => en]} -detail +{var $args = [$product->id, lang => pt]} +detalhe do produto ``` -Os chamados [parâmetros persistentes |presenters#persistent parameters] também são automaticamente passados nos links. +Nos links, os chamados [parâmetros persistentes |presenters#Parâmetros persistentes] também são transmitidos automaticamente. -Atributo `n:href` é muito útil para tags HTML ``. Se quisermos imprimir o link em outro lugar, por exemplo, no texto, usamos `{link}`: +O atributo `n:href` é muito útil para tags HTML ``. Se quisermos exibir o link em outro lugar, por exemplo, no texto, usamos `{link}`: ```latte -URL is: {link Home:default} +O endereço é: {link Home:default} ``` -No Código .[#toc-in-the-code] -============================= +No código +========= -O método `link()` é usado para criar um link no apresentador: +Para criar um link no presenter, usa-se o método `link()`: ```php $url = $this->link('Product:show', $product->id); ``` -Os parâmetros também podem ser passados como uma matriz onde parâmetros nomeados também podem ser especificados: +Os parâmetros também podem ser passados através de um array, onde também podem ser especificados parâmetros nomeados: ```php -$url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); +$url = $this->link('Product:show', [$product->id, 'lang' => 'pt']); ``` -Links também podem ser criados sem apresentador, usando o [LinkGenerator |#LinkGenerator] e seu método `link()`. +Links também podem ser criados sem um presenter, para isso existe o [#LinkGenerator] e seu método `link()`. -Links para o apresentador .[#toc-links-to-presenter] -==================================================== +Links para o presenter +====================== -Se o alvo do link é o apresentador e a ação, ele tem esta sintaxe: +Se o destino do link for um presenter e uma ação, ele tem esta sintaxe: ``` [//] [[[[:]module:]presenter:]action | this] [#fragment] ``` -O formato é suportado por todas as etiquetas Latte e todos os métodos de apresentação que funcionam com links, ou seja `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` e também [LinkGenerator |#LinkGenerator]. Portanto, mesmo que `n:href` seja usado nos exemplos, pode haver qualquer uma das funções. +O formato é suportado por todas as tags Latte e todos os métodos do presenter que trabalham com links, ou seja, `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` e também [#LinkGenerator]. Portanto, mesmo que `n:href` seja usado nos exemplos, qualquer uma das funções poderia estar lá. A forma básica é, portanto, `Presenter:action`: ```latte -home +página inicial ``` -Se nos ligarmos à ação do atual apresentador, podemos omitir seu nome: +Se estivermos vinculando a uma ação do presenter atual, podemos omitir seu nome: ```latte -home +página inicial ``` -Se a ação é `default`, podemos omiti-la, mas o cólon deve permanecer: +Se o destino for a ação `default`, podemos omiti-la, mas os dois pontos devem permanecer: ```latte -home +página inicial ``` -Os links também podem apontar para outros [módulos |modules]. Aqui, os links são diferenciados em relativos aos submódulos, ou absolutos. O princípio é análogo aos caminhos do disco, somente em vez de cortes existem colons. Vamos supor que o apresentador real faça parte do módulo `Front`, então escreveremos: +Links também podem apontar para outros [módulos |directory-structure#Presenters e templates]. Aqui, os links são distinguidos entre relativos para um submódulo aninhado ou absolutos. O princípio é análogo aos caminhos no disco, apenas em vez de barras, são dois pontos. Suponha que o presenter atual faça parte do módulo `Front`, então escrevemos: ```latte -link to Front:Shop:Product:show -link to Admin:Product:show +link para Front:Shop:Product:show +link para Admin:Product:show ``` -Um caso especial está [ligado a si mesmo |#Links to Current Page]. Aqui escreveremos `this` como o alvo. +Um caso especial é um link [para si mesmo |#Link para a página atual], onde especificamos `this` como destino. ```latte -refresh +atualizar ``` -Podemos criar um link para uma determinada parte da página HTML através do chamado fragmento após o símbolo `#` hash: +Podemos vincular a uma parte específica da página através do chamado fragmento após o caractere de cerquilha `#`: ```latte -link to Home:default and fragment #main +link para Home:default e fragmento #main ``` -Caminhos Absolutos .[#toc-absolute-paths] -========================================= +Caminhos absolutos +================== -Links gerados por `link()` ou `n:href` são sempre caminhos absolutos (ou seja, começam com `/`), mas não URLs absolutas com um protocolo e domínio como `https://domain`. +Links gerados usando `link()` ou `n:href` são sempre caminhos absolutos (ou seja, começam com o caractere `/`), mas não URLs absolutas com protocolo e domínio como `https://domain`. -Para gerar uma URL absoluta, acrescente duas barras ao início (por exemplo, `n:href="//Home:"`). Ou você pode mudar o apresentador para gerar apenas links absolutos, definindo `$this->absoluteUrls = true`. +Para gerar uma URL absoluta, adicione duas barras no início (por exemplo, `n:href="//Home:"`). Ou você pode configurar o presenter para gerar apenas links absolutos definindo `$this->absoluteUrls = true`. -Link para a página atual .[#toc-link-to-current-page] -===================================================== +Link para a página atual +======================== -A meta `this` irá criar um link para a página atual: +O destino `this` cria um link para a página atual: ```latte -refresh +atualizar ``` -Ao mesmo tempo, todos os parâmetros especificados na assinatura do `render()` ou `action()` método são transferidos. Portanto, se estivermos nas páginas `Product:show` e `id:123`, o link para `this` também passará este parâmetro. +Ao mesmo tempo, todos os parâmetros especificados na assinatura do método `action()` ou `render()` são transmitidos, se `action()` não estiver definida. Portanto, se estivermos na página `Product:show` e `id: 123`, o link para `this` também passará este parâmetro. -Naturalmente, é possível especificar os parâmetros diretamente: +Claro, é possível especificar os parâmetros diretamente: ```latte -refresh +atualizar ``` -A função `isLinkCurrent()` determina se o alvo do link é o mesmo que a página atual. Isto pode ser usado, por exemplo, em um modelo para diferenciar links, etc. +A função `isLinkCurrent()` verifica se o destino do link é idêntico à página atual. Isso pode ser usado, por exemplo, em um template para diferenciar links, etc. -Os parâmetros são os mesmos do método `link()`, mas também é possível utilizar o curinga `*` em vez de uma ação específica, o que significa qualquer ação do apresentador. +Os parâmetros são os mesmos do método `link()`, mas adicionalmente é possível usar o caractere curinga `*` em vez de uma ação específica, o que significa qualquer ação do presenter dado. ```latte {if !isLinkCurrent('Admin:login')} - Přihlaste se + Faça login {/if}
  • @@ -162,58 +162,58 @@ Os parâmetros são os mesmos do método `link()`, mas também é possível util
  • ``` -Uma forma abreviada pode ser usada em combinação com `n:href` em um único elemento: +Em combinação com `n:href` em um único elemento, uma forma abreviada pode ser usada: ```latte -... +... ``` -O caráter curinga `*' substitui apenas a ação do apresentador, não o próprio apresentador. +O caractere curinga `*` só pode ser usado no lugar da ação, não do presenter. -Para saber se estamos em um determinado módulo ou em seu submódulo, podemos usar a função `isModuleCurrent(moduleName)`. +Para verificar se estamos em um determinado módulo ou seu submódulo, usamos o método `isModuleCurrent(moduleName)`. ```latte -
  • +
  • ...
  • ``` -Links para Sinal .[#toc-links-to-signal] -======================================== +Links para sinal +================ -O alvo do link pode não só ser o apresentador e a ação, mas também o [sinal |components#Signal] (eles chamam o método `handle()`). A sintaxe é a seguinte: +O destino de um link não precisa ser apenas um presenter e uma ação, mas também um [sinal |components#Sinal] (eles chamam o método `handle()`). Então a sintaxe é a seguinte: ``` [//] [sub-component:]signal! [#fragment] ``` -O sinal distingue-se, portanto, pelo ponto de exclamação: +O sinal é, portanto, distinguido por um ponto de exclamação: ```latte -signal +sinal ``` -Você também pode criar um link para o sinal do subcomponente (ou sub-subcomponente): +Também é possível criar um link para o sinal de um subcomponente (ou sub-subcomponente): ```latte -signal +sinal ``` -Links em Componente .[#toc-links-in-component] -============================================== +Links no componente +=================== -Como [os componentes |components] são unidades reutilizáveis separadas que não devem ter nenhuma relação com os apresentadores ao redor, os elos funcionam de forma um pouco diferente. O atributo Latte `n:href` e tag `{link}` e os métodos de componentes como `link()` e outros sempre consideram o alvo ** como o nome do sinal***. Portanto, não é necessário o uso de um ponto de exclamação: +Como os [componentes|components] são unidades reutilizáveis separadas que não devem ter vínculos com os presenters circundantes, os links funcionam um pouco diferente aqui. O atributo Latte `n:href` e a tag `{link}`, bem como os métodos do componente como `link()` e outros, consideram o destino do link **sempre como o nome de um sinal**. Portanto, nem mesmo é necessário incluir o ponto de exclamação: ```latte -signal, not an action +sinal, não ação ``` -Se quisermos fazer um link para apresentadores no modelo de componente, usamos a tag `{plink}`: +Se quiséssemos vincular a presenters no template do componente, usaríamos a tag `{plink}`: ```latte -home +início ``` ou no código @@ -223,17 +223,41 @@ $this->getPresenter()->link('Home:default') ``` -Links inválidos .[#toc-invalid-links] -===================================== +Aliases .{data-version:v3.2.2} +============================== -Pode acontecer de criarmos um link inválido - seja porque ele se refere a um apresentador inexistente, ou porque ele passa mais parâmetros que o método alvo recebe em sua assinatura, ou quando não pode haver uma URL gerada para a ação alvo. O que fazer com links inválidos é determinado pela variável estática `Presenter::$invalidLinkMode`. Ela pode ter um destes valores (constantes): +Às vezes, pode ser útil atribuir um alias fácil de lembrar a um par Presenter:action. Por exemplo, nomear a página inicial `Front:Home:default` simplesmente como `home` ou `Admin:Dashboard:default` como `admin`. -- `Presenter::InvalidLinkSilent` - modo silencioso, retorna o símbolo `#` como URL -- `Presenter::InvalidLinkWarning` - E_USER_WARNING será produzido -- `Presenter::InvalidLinkTextual` - aviso visual, o texto de erro é exibido no link -- `Presenter::InvalidLinkException` - InvalidLinkException será lançada +Aliases são definidos na [configuração|configuration] sob a chave `application › aliases`: -A configuração padrão no modo de produção é `InvalidLinkWarning` e no modo de desenvolvimento é `InvalidLinkWarning | InvalidLinkTextual`. `InvalidLinkWarning` não mata o script no ambiente de produção, mas o aviso será registrado. No ambiente de desenvolvimento, [Tracy |tracy:] interceptará o aviso e exibirá a tela de erro bluescreen. Se o `InvalidLinkTextual` estiver configurado, o apresentador e os componentes retornarão a mensagem de erro como URL, que é estrelada com `#error:`. Para tornar tais links visíveis, podemos adicionar uma regra CSS à nossa folha de estilo: +```neon +application: + aliases: + home: Front:Home:default + admin: Admin:Dashboard:default + sign: Front:Sign:in +``` + +Nos links, eles são então escritos usando um arroba, por exemplo: + +```latte +administração +``` + +Eles também são suportados em todos os métodos que trabalham com links, como `redirect()` e similares. + + +Links inválidos +=============== + +Pode acontecer que criemos um link inválido - seja porque ele leva a um presenter inexistente, ou porque passa mais parâmetros do que o método de destino aceita em sua assinatura, ou quando uma URL não pode ser gerada para a ação de destino. Como lidar com links inválidos é determinado pela variável estática `Presenter::$invalidLinkMode`. Ela pode assumir uma combinação destes valores (constantes): + +- `Presenter::InvalidLinkSilent` - modo silencioso, o caractere # é retornado como URL +- `Presenter::InvalidLinkWarning` - um aviso E_USER_WARNING é lançado, que será registrado no modo de produção, mas não causará a interrupção da execução do script +- `Presenter::InvalidLinkTextual` - aviso visual, exibe o erro diretamente no link +- `Presenter::InvalidLinkException` - a exceção InvalidLinkException é lançada + +A configuração padrão é `InvalidLinkWarning` no modo de produção e `InvalidLinkWarning | InvalidLinkTextual` no modo de desenvolvimento. `InvalidLinkWarning` no ambiente de produção não causa a interrupção do script, mas o aviso será registrado. No ambiente de desenvolvimento, ele é capturado pelo [Tracy |tracy:] e exibe uma bluescreen. `InvalidLinkTextual` funciona retornando uma mensagem de erro como URL, que começa com os caracteres `#error:`. Para tornar esses links visíveis à primeira vista, adicionamos ao CSS: ```css a[href^="#error:"] { @@ -242,7 +266,7 @@ a[href^="#error:"] { } ``` -Se não quisermos que os avisos sejam produzidos no ambiente de desenvolvimento, podemos ativar o modo de link inválido silencioso na [configuração |configuration]. +Se não quisermos que avisos sejam produzidos no ambiente de desenvolvimento, podemos definir o modo silencioso diretamente na [configuração|configuration]. ```neon application: @@ -250,13 +274,13 @@ application: ``` -LinkGenerator .[#toc-linkgenerator] -=================================== +LinkGenerator +============= -Como criar vínculos com o método `link()` conforto, mas sem a presença de um apresentador? É por isso que aqui está [api:Nette\Application\LinkGenerator]. +Como criar links com conforto semelhante ao método `link()`, mas sem a presença de um presenter? Para isso existe a [api:Nette\Application\LinkGenerator]. -O LinkGenerator é um serviço que você pode ter passado pelo construtor e depois criar links usando seu método `link()`. +LinkGenerator é um serviço que você pode solicitar via construtor e, em seguida, criar links usando seu método `link()`. -Há uma diferença em comparação com os apresentadores. O LinkGenerator cria todos os links como URLs absolutos. Além disso, não há "apresentador atual", portanto não é possível especificar apenas o nome da ação `link('default')` ou os caminhos relativos aos [módulos |modules]. +Há uma diferença em relação aos presenters. O LinkGenerator cria todos os links diretamente como URLs absolutas. Além disso, não existe um "presenter atual", então não é possível especificar apenas o nome da ação como destino `link('default')` ou usar caminhos relativos para módulos. Links inválidos sempre lançam `Nette\Application\UI\InvalidLinkException`. diff --git a/application/pt/directory-structure.texy b/application/pt/directory-structure.texy new file mode 100644 index 0000000000..c7f269dfff --- /dev/null +++ b/application/pt/directory-structure.texy @@ -0,0 +1,526 @@ +Estrutura de diretórios da aplicação +************************************ + +
    + +Como projetar uma estrutura de diretórios clara e escalável para projetos no Nette Framework? Mostraremos as melhores práticas que o ajudarão a organizar seu código. Você aprenderá: + +- como **dividir logicamente** a aplicação em diretórios +- como projetar a estrutura para que ela **escale bem** com o crescimento do projeto +- quais são as **alternativas possíveis** e suas vantagens ou desvantagens + +
    + + +É importante mencionar que o próprio Nette Framework não impõe nenhuma estrutura específica. Ele é projetado para ser facilmente adaptável a quaisquer necessidades e preferências. + + +Estrutura básica do projeto +=========================== + +Embora o Nette Framework não dite nenhuma estrutura de diretórios fixa, existe uma organização padrão comprovada na forma do [Web Project|https://github.com/nette/web-project]: + +/--pre +web-project/ +├── app/ ← diretório com a aplicação +├── assets/ ← arquivos SCSS, JS, imagens..., alternativamente resources/ +├── bin/ ← scripts para a linha de comando +├── config/ ← configuração +├── log/ ← erros registrados +├── temp/ ← arquivos temporários, cache +├── tests/ ← testes +├── vendor/ ← bibliotecas instaladas pelo Composer +└── www/ ← diretório público (document-root) +\-- + +Você pode modificar esta estrutura livremente de acordo com suas necessidades - renomear ou mover pastas. Depois, basta apenas ajustar os caminhos relativos aos diretórios no arquivo `Bootstrap.php` e, opcionalmente, `composer.json`. Nada mais é necessário, nenhuma reconfiguração complicada, nenhuma alteração de constantes. O Nette possui uma autodeteção inteligente e reconhece automaticamente a localização da aplicação, incluindo sua base de URL. + + +Princípios de organização do código +=================================== + +Quando você explora um novo projeto pela primeira vez, deve conseguir se orientar rapidamente nele. Imagine que você abre o diretório `app/Model/` e vê esta estrutura: + +/--pre +app/Model/ +├── Services/ +├── Repositories/ +└── Entities/ +\-- + +A partir dela, você só pode deduzir que o projeto usa alguns serviços, repositórios e entidades. Você não aprenderá nada sobre o propósito real da aplicação. + +Vejamos outra abordagem - **organização por domínios**: + +/--pre +app/Model/ +├── Cart/ +├── Payment/ +├── Order/ +└── Product/ +\-- + +Aqui é diferente - à primeira vista, fica claro que se trata de uma loja virtual. Os próprios nomes dos diretórios revelam o que a aplicação faz - trabalha com pagamentos, pedidos e produtos. + +A primeira abordagem (organização por tipo de classe) traz na prática uma série de problemas: o código que está logicamente relacionado é fragmentado em diferentes pastas e você precisa pular entre elas. Portanto, organizaremos por domínios. + + +Namespaces +---------- + +É costume que a estrutura de diretórios corresponda aos namespaces na aplicação. Isso significa que a localização física dos arquivos corresponde ao seu namespace. Por exemplo, uma classe localizada em `app/Model/Product/ProductRepository.php` deve ter o namespace `App\Model\Product`. Este princípio ajuda na orientação no código e simplifica o autoloading. + + +Singular vs. plural nos nomes +----------------------------- + +Observe que para os diretórios principais da aplicação usamos o singular: `app`, `config`, `log`, `temp`, `www`. O mesmo vale para o interior da aplicação: `Model`, `Core`, `Presentation`. Isso ocorre porque cada um deles representa um conceito coeso. + +Da mesma forma, por exemplo, `app/Model/Product` representa tudo relacionado a produtos. Não o chamaremos de `Products`, porque não é uma pasta cheia de produtos (isso significaria que haveria arquivos `nokia.php`, `samsung.php`). É um namespace contendo classes para trabalhar com produtos - `ProductRepository.php`, `ProductService.php`. + +A pasta `app/Tasks` está no plural porque contém um conjunto de scripts executáveis independentes - `CleanupTask.php`, `ImportTask.php`. Cada um deles é uma unidade separada. + +Para consistência, recomendamos usar: +- Singular para namespaces que representam uma unidade funcional (mesmo que trabalhe com múltiplas entidades) +- Plural para coleções de unidades independentes +- Em caso de incerteza ou se você não quiser pensar sobre isso, escolha o singular + + +Diretório público `www/` +======================== + +Este diretório é o único acessível pela web (o chamado document-root). Frequentemente, você pode encontrar o nome `public/` em vez de `www/` - é apenas uma questão de convenção e não afeta a funcionalidade do Nette. O diretório contém: +- [Ponto de entrada |bootstrapping#index.php] da aplicação `index.php` +- Arquivo `.htaccess` com regras para mod_rewrite (no Apache) +- Arquivos estáticos (CSS, JavaScript, imagens) +- Arquivos carregados (uploads) + +Para a segurança adequada da aplicação, é crucial ter o [document-root configurado corretamente |nette:troubleshooting#Como alterar ou remover o diretório www da URL]. + +.[note] +Nunca coloque a pasta `node_modules/` neste diretório - ela contém milhares de arquivos que podem ser executáveis e não devem estar publicamente acessíveis. + + +Diretório da aplicação `app/` +============================= + +Este é o diretório principal com o código da aplicação. Estrutura básica: + +/--pre +app/ +├── Core/ ← questões de infraestrutura +├── Model/ ← lógica de negócios +├── Presentation/ ← presenters e templates +├── Tasks/ ← scripts de comando +└── Bootstrap.php ← classe de inicialização da aplicação +\-- + +`Bootstrap.php` é a [classe de inicialização da aplicação|bootstrapping], que inicializa o ambiente, carrega a configuração e cria o contêiner de DI. + +Vamos agora examinar os subdiretórios individuais com mais detalhes. + + +Presenters e templates +====================== + +A parte de apresentação da aplicação está no diretório `app/Presentation`. Uma alternativa é o curto `app/UI`. É o local para todos os presenters, seus templates e quaisquer classes auxiliares. + +Organizamos esta camada por domínios. Em um projeto complexo que combina uma loja virtual, um blog e uma API, a estrutura seria assim: + +/--pre +app/Presentation/ +├── Shop/ ← frontend da loja virtual +│ ├── Product/ +│ ├── Cart/ +│ └── Order/ +├── Blog/ ← blog +│ ├── Home/ +│ └── Post/ +├── Admin/ ← administração +│ ├── Dashboard/ +│ └── Products/ +└── Api/ ← endpoints da API + └── V1/ +\-- + +Por outro lado, para um blog simples, usaríamos a seguinte divisão: + +/--pre +app/Presentation/ +├── Front/ ← frontend do site +│ ├── Home/ +│ └── Post/ +├── Admin/ ← administração +│ ├── Dashboard/ +│ └── Posts/ +├── Error/ +└── Export/ ← RSS, sitemaps, etc. +\-- + +Pastas como `Home/` ou `Dashboard/` contêm presenters e templates. Pastas como `Front/`, `Admin/` ou `Api/` são chamadas de **módulos**. Tecnicamente, são diretórios comuns que servem para a divisão lógica da aplicação. + +Cada pasta com um presenter contém um presenter de mesmo nome e seus templates. Por exemplo, a pasta `Dashboard/` contém: + +/--pre +Dashboard/ +├── DashboardPresenter.php ← presenter +└── default.latte ← template +\-- + +Esta estrutura de diretórios se reflete nos namespaces das classes. Por exemplo, `DashboardPresenter` está localizado no namespace `App\Presentation\Admin\Dashboard` (veja [#Mapeamento de presenters]): + +```php +namespace App\Presentation\Admin\Dashboard; + +class DashboardPresenter extends Nette\Application\UI\Presenter +{ + // ... +} +``` + +Referimo-nos ao presenter `Dashboard` dentro do módulo `Admin` na aplicação usando a notação de dois pontos como `Admin:Dashboard`. À sua ação `default`, então, como `Admin:Dashboard:default`. No caso de módulos aninhados, usamos mais dois pontos, por exemplo, `Shop:Order:Detail:default`. + + +Desenvolvimento flexível da estrutura +------------------------------------- + +Uma das grandes vantagens desta estrutura é como ela se adapta elegantemente às necessidades crescentes do projeto. Como exemplo, vejamos a parte que gera feeds XML. No início, temos uma forma simples: + +/--pre +Export/ +├── ExportPresenter.php ← um presenter para todas as exportações +├── sitemap.latte ← template para o sitemap +└── feed.latte ← template para o feed RSS +\-- + +Com o tempo, mais tipos de feeds são adicionados e precisamos de mais lógica para eles... Sem problemas! A pasta `Export/` simplesmente se torna um módulo: + +/--pre +Export/ +├── Sitemap/ +│ ├── SitemapPresenter.php +│ └── sitemap.latte +└── Feed/ + ├── FeedPresenter.php + ├── zbozi.latte ← feed para Zboží.cz + └── heureka.latte ← feed para Heureka.cz +\-- + +Esta transformação é completamente fluida - basta criar novas subpastas, dividir o código nelas e atualizar os links (por exemplo, de `Export:feed` para `Export:Feed:zbozi`). Graças a isso, podemos expandir gradualmente a estrutura conforme necessário, o nível de aninhamento não é limitado de forma alguma. + +Se, por exemplo, na administração você tiver muitos presenters relacionados ao gerenciamento de pedidos, como `OrderDetail`, `OrderEdit`, `OrderDispatch`, etc., você pode criar um módulo (pasta) `Order` neste local para melhor organização, que conterá (pastas para) os presenters `Detail`, `Edit`, `Dispatch` e outros. + + +Localização dos templates +------------------------- + +Nos exemplos anteriores, vimos que os templates estão localizados diretamente na pasta com o presenter: + +/--pre +Dashboard/ +├── DashboardPresenter.php ← presenter +├── DashboardTemplate.php ← classe opcional para o template +└── default.latte ← template +\-- + +Esta localização se mostra na prática a mais conveniente - todos os arquivos relacionados estão à mão. + +Alternativamente, você pode colocar os templates em uma subpasta `templates/`. O Nette suporta ambas as variantes. Você pode até colocar os templates completamente fora da pasta `Presentation/`. Tudo sobre as opções de localização de templates pode ser encontrado no capítulo [Procurando templates |templates#Procurando templates]. + + +Classes auxiliares e componentes +-------------------------------- + +Frequentemente, presenters e templates são acompanhados por outros arquivos auxiliares. Nós os colocamos logicamente de acordo com seu escopo: + +1. **Diretamente com o presenter** no caso de componentes específicos para esse presenter: + +/--pre +Product/ +├── ProductPresenter.php +├── ProductGrid.php ← componente para listagem de produtos +└── FilterForm.php ← formulário para filtragem +\-- + +2. **Para o módulo** - recomendamos usar a pasta `Accessory`, que é colocada de forma clara no início do alfabeto: + +/--pre +Front/ +├── Accessory/ +│ ├── NavbarControl.php ← componentes para o frontend +│ └── TemplateFilters.php +├── Product/ +└── Cart/ +\-- + +3. **Para toda a aplicação** - em `Presentation/Accessory/`: +/--pre +app/Presentation/ +├── Accessory/ +│ ├── LatteExtension.php +│ └── TemplateFilters.php +├── Front/ +└── Admin/ +\-- + +Ou você pode colocar classes auxiliares como `LatteExtension.php` ou `TemplateFilters.php` na pasta de infraestrutura `app/Core/Latte/`. E componentes em `app/Components`. A escolha depende dos costumes da equipe. + + +Model - o coração da aplicação +============================== + +O Model contém toda a lógica de negócios da aplicação. Para sua organização, a regra se aplica novamente - estruturamos por domínios: + +/--pre +app/Model/ +├── Payment/ ← tudo sobre pagamentos +│ ├── PaymentFacade.php ← ponto de entrada principal +│ ├── PaymentRepository.php +│ ├── Payment.php ← entidade +├── Order/ ← tudo sobre pedidos +│ ├── OrderFacade.php +│ ├── OrderRepository.php +│ ├── Order.php +└── Shipping/ ← tudo sobre envio +\-- + +No model, você normalmente encontrará estes tipos de classes: + +**Facades**: representam o ponto de entrada principal para um domínio específico na aplicação. Atuam como um orquestrador que coordena a cooperação entre diferentes serviços para implementar casos de uso completos (como "criar pedido" ou "processar pagamento"). Sob sua camada de orquestração, a facade esconde os detalhes de implementação do resto da aplicação, fornecendo assim uma interface limpa para trabalhar com o domínio dado. + +```php +class OrderFacade +{ + public function createOrder(Cart $cart): Order + { + // validação + // criação do pedido + // envio de e-mail + // registro nas estatísticas + } +} +``` + +**Serviços**: focam em uma operação de negócios específica dentro do domínio. Ao contrário da facade, que orquestra casos de uso inteiros, um serviço implementa lógica de negócios específica (como cálculos de preços ou processamento de pagamentos). Os serviços são tipicamente sem estado e podem ser usados por facades como blocos de construção para operações mais complexas, ou diretamente por outras partes da aplicação para tarefas mais simples. + +```php +class PricingService +{ + public function calculateTotal(Order $order): Money + { + // cálculo do preço + } +} +``` + +**Repositórios**: garantem toda a comunicação com o armazenamento de dados, tipicamente um banco de dados. Sua tarefa é carregar e salvar entidades e implementar métodos para sua busca. O repositório isola o resto da aplicação dos detalhes de implementação do banco de dados e fornece uma interface orientada a objetos para trabalhar com dados. + +```php +class OrderRepository +{ + public function find(int $id): ?Order + { + } + + public function findByCustomer(int $customerId): array + { + } +} +``` + +**Entidades**: objetos que representam os principais conceitos de negócios na aplicação, que têm sua identidade e mudam ao longo do tempo. Tipicamente, são classes mapeadas para tabelas de banco de dados usando ORM (como Nette Database Explorer ou Doctrine). As entidades podem conter regras de negócios relacionadas aos seus dados e lógica de validação. + +```php +// Entidade mapeada para a tabela de banco de dados orders +class Order extends Nette\Database\Table\ActiveRow +{ + public function addItem(Product $product, int $quantity): void + { + $this->related('order_items')->insert([ + 'product_id' => $product->id, + 'quantity' => $quantity, + 'unit_price' => $product->price, + ]); + } +} +``` + +**Value objects**: objetos imutáveis que representam valores sem identidade própria - por exemplo, um valor monetário ou um endereço de e-mail. Duas instâncias de um value object com os mesmos valores são consideradas idênticas. + + +Código de infraestrutura +======================== + +A pasta `Core/` (ou também `Infrastructure/`) é o lar da base técnica da aplicação. O código de infraestrutura normalmente inclui: + +/--pre +app/Core/ +├── Router/ ← roteamento e gerenciamento de URL +│ └── RouterFactory.php +├── Security/ ← autenticação e autorização +│ ├── Authenticator.php +│ └── Authorizator.php +├── Logging/ ← logging e monitoramento +│ ├── SentryLogger.php +│ └── FileLogger.php +├── Cache/ ← camada de cache +│ └── FullPageCache.php +└── Integration/ ← integração com serviços ext. + ├── Slack/ + └── Stripe/ +\-- + +Para projetos menores, uma estrutura plana é obviamente suficiente: + +/--pre +Core/ +├── RouterFactory.php +├── Authenticator.php +└── QueueMailer.php +\-- + +É o código que: + +- Lida com a infraestrutura técnica (roteamento, logging, cache) +- Integra serviços externos (Sentry, Elasticsearch, Redis) +- Fornece serviços básicos para toda a aplicação (e-mail, banco de dados) +- É geralmente independente do domínio específico - cache ou logger funciona da mesma forma para uma loja virtual ou blog. + +Está em dúvida se uma determinada classe pertence aqui ou ao model? A diferença crucial é que o código em `Core/`: + +- Não sabe nada sobre o domínio (produtos, pedidos, artigos) +- Geralmente pode ser transferido para outro projeto +- Lida com "como funciona" (como enviar um e-mail), não "o que faz" (qual e-mail enviar) + +Exemplo para melhor compreensão: + +- `App\Core\MailerFactory` - cria instâncias da classe para envio de e-mails, lida com configurações SMTP +- `App\Model\OrderMailer` - usa `MailerFactory` para enviar e-mails sobre pedidos, conhece seus templates e sabe quando devem ser enviados + + +Scripts de comando +================== + +Aplicações frequentemente precisam executar atividades fora das requisições HTTP normais - seja processamento de dados em segundo plano, manutenção ou tarefas periódicas. Scripts simples no diretório `bin/` são usados para execução, enquanto a lógica de implementação é colocada em `app/Tasks/` (ou `app/Commands/`). + +Exemplo: + +/--pre +app/Tasks/ +├── Maintenance/ ← scripts de manutenção +│ ├── CleanupCommand.php ← exclusão de dados antigos +│ └── DbOptimizeCommand.php ← otimização do banco de dados +├── Integration/ ← integração com sistemas externos +│ ├── ImportProducts.php ← importação do sistema do fornecedor +│ └── SyncOrders.php ← sincronização de pedidos +└── Scheduled/ ← tarefas agendadas + ├── NewsletterCommand.php ← envio de newsletters + └── ReminderCommand.php ← notificações para clientes +\-- + +O que pertence ao model e o que pertence aos scripts de comando? Por exemplo, a lógica para enviar um único e-mail faz parte do model, o envio em massa de milhares de e-mails já pertence a `Tasks/`. + +As tarefas são geralmente [executadas a partir da linha de comando |https://blog.nette.org/en/cli-scripts-in-nette-application] ou via cron. Elas também podem ser executadas via requisição HTTP, mas é necessário pensar na segurança. O presenter que inicia a tarefa precisa ser protegido, por exemplo, apenas para usuários logados ou com um token forte e acesso de endereços IP permitidos. Para tarefas longas, é necessário aumentar o limite de tempo do script e usar `session_write_close()` para não bloquear a sessão. + + +Outros diretórios possíveis +=========================== + +Além dos diretórios básicos mencionados, você pode adicionar outras pastas especializadas de acordo com as necessidades do projeto. Vejamos as mais comuns e seus usos: + +/--pre +app/ +├── Api/ ← lógica para API independente da camada de apresentação +├── Database/ ← scripts de migração e seeders para dados de teste +├── Components/ ← componentes visuais compartilhados em toda a aplicação +├── Event/ ← útil se você usa arquitetura orientada a eventos +├── Mail/ ← templates de e-mail e lógica relacionada +└── Utils/ ← classes auxiliares +\-- + +Para componentes visuais compartilhados usados em presenters em toda a aplicação, a pasta `app/Components` ou `app/Controls` pode ser usada: + +/--pre +app/Components/ +├── Form/ ← componentes de formulário compartilhados +│ ├── SignInForm.php +│ └── UserForm.php +├── Grid/ ← componentes para listagens de dados +│ └── DataGrid.php +└── Navigation/ ← elementos de navegação + ├── Breadcrumbs.php + └── Menu.php +\-- + +Aqui pertencem componentes que têm lógica mais complexa. Se você deseja compartilhar componentes entre vários projetos, é aconselhável extraí-los para um pacote composer separado. + +No diretório `app/Mail`, você pode colocar o gerenciamento da comunicação por e-mail: + +/--pre +app/Mail/ +├── templates/ ← templates de e-mail +│ ├── order-confirmation.latte +│ └── welcome.latte +└── OrderMailer.php +\-- + + +Mapeamento de presenters +======================== + +O mapeamento define regras para derivar o nome da classe a partir do nome do presenter. Especificamo-las na [configuração|configuration] sob a chave `application › mapping`. + +Nesta página, mostramos que colocamos os presenters na pasta `app/Presentation` (ou `app/UI`). Precisamos informar esta convenção ao Nette no arquivo de configuração. Basta uma linha: + +```neon +application: + mapping: App\Presentation\*\**Presenter +``` + +Como funciona o mapeamento? Para melhor compreensão, imaginemos primeiro uma aplicação sem módulos. Queremos que as classes dos presenters caiam no namespace `App\Presentation`, para que o presenter `Home` seja mapeado para a classe `App\Presentation\HomePresenter`. O que conseguimos com esta configuração: + +```neon +application: + mapping: App\Presentation\*Presenter +``` + +O mapeamento funciona de forma que o nome do presenter `Home` substitui o asterisco na máscara `App\Presentation\*Presenter`, resultando no nome final da classe `App\Presentation\HomePresenter`. Simples! + +Mas, como você pode ver nos exemplos neste e em outros capítulos, colocamos as classes dos presenters em subdiretórios homônimos, por exemplo, o presenter `Home` é mapeado para a classe `App\Presentation\Home\HomePresenter`. Conseguimos isso duplicando os dois pontos (requer Nette Application 3.2): + +```neon +application: + mapping: App\Presentation\**Presenter +``` + +Agora vamos mapear presenters para módulos. Para cada módulo, podemos definir um mapeamento específico: + +```neon +application: + mapping: + Front: App\Presentation\Front\**Presenter + Admin: App\Presentation\Admin\**Presenter + Api: App\Api\*Presenter +``` + +De acordo com esta configuração, o presenter `Front:Home` é mapeado para a classe `App\Presentation\Front\Home\HomePresenter`, enquanto o presenter `Api:OAuth` para a classe `App\Api\OAuthPresenter`. + +Como os módulos `Front` e `Admin` têm um método de mapeamento semelhante e provavelmente haverá mais módulos assim, é possível criar uma regra geral que os substitua. Um novo asterisco para o módulo é adicionado à máscara da classe: + +```neon +application: + mapping: + *: App\Presentation\*\**Presenter + Api: App\Api\*Presenter +``` + +Isso também funciona para estruturas de diretórios mais profundamente aninhadas, como, por exemplo, o presenter `Admin:User:Edit`, o segmento com asterisco se repete para cada nível e o resultado é a classe `App\Presentation\Admin\User\Edit\EditPresenter`. + +Uma notação alternativa é usar um array composto por três segmentos em vez de uma string. Esta notação é equivalente à anterior: + +```neon +application: + mapping: + *: [App\Presentation, *, **Presenter] + Api: [App\Api, '', *Presenter] +``` diff --git a/application/pt/how-it-works.texy b/application/pt/how-it-works.texy index a4e6915e34..3e6fd2db4b 100644 --- a/application/pt/how-it-works.texy +++ b/application/pt/how-it-works.texy @@ -3,99 +3,101 @@ Como funcionam as aplicações?
    -Atualmente você está lendo o documento básico da documentação Nette. Você aprenderá todo o princípio das aplicações web. Nice de A a Z, desde o momento do nascimento até o último suspiro do script PHP. Após a leitura, você saberá: +Você está lendo o documento fundamental da documentação do Nette. Aprenderá como as aplicações web funcionam. Do início ao fim, desde o momento do nascimento até o último suspiro do script PHP. Após a leitura, você saberá: -- como tudo isso funciona -- o que é Bootstrap, Apresentador e Recipiente DI -- como é a estrutura do diretório +- como tudo funciona +- o que é Bootstrap, Presenter e Contêiner de DI +- como é a estrutura de diretórios
    -Estrutura do diretório .[#toc-directory-structure] -================================================== +Estrutura de diretórios +======================= -Abra um exemplo de esqueleto de uma aplicação web chamada [WebProject |https://github.com/nette/web-project] e você pode assistir aos arquivos que estão sendo escritos. +Abra o exemplo do esqueleto da aplicação web chamado [WebProject|https://github.com/nette/web-project] e, enquanto lê, pode consultar os arquivos sobre os quais estamos falando. -A estrutura do diretório é algo parecido com isto: +A estrutura de diretórios se parece com algo assim: /--pre web-project/ -├── app/ ← directory with application -│ ├── Presenters/ ← presenter classes -│ │ ├── HomePresenter.php ← Home presenter class -│ │ └── templates/ ← templates directory -│ │ ├── @layout.latte ← template of shared layout -│ │ └── Home/ ← templates for Home presenter -│ │ └── default.latte ← template for action `default` -│ ├── Router/ ← configuration of URL addresses -│ └── Bootstrap.php ← booting class Bootstrap -├── bin/ ← scripts for the command line -├── config/ ← configuration files +├── app/ ← diretório da aplicação +│ ├── Core/ ← classes base necessárias para a execução +│ │ └── RouterFactory.php ← configuração de endereços URL +│ ├── Presentation/ ← presenters, templates & cia. +│ │ ├── @layout.latte ← template de layout +│ │ └── Home/ ← diretório do presenter Home +│ │ ├── HomePresenter.php ← classe do presenter Home +│ │ └── default.latte ← template da ação default +│ └── Bootstrap.php ← classe de inicialização Bootstrap +├── assets/ ← recursos (SCSS, TypeScript, imagens de origem) +├── bin/ ← scripts executados a partir da linha de comando +├── config/ ← arquivos de configuração │ ├── common.neon -│ └── local.neon -├── log/ ← error logs -├── temp/ ← temporary files, cache, … -├── vendor/ ← libraries installed by Composer +│ └── services.neon +├── log/ ← erros registrados +├── temp/ ← arquivos temporários, cache, … +├── vendor/ ← bibliotecas instaladas pelo Composer │ ├── ... -│ └── autoload.php ← autoloading of libs installed by Composer -├── www/ ← public directory, document root of project -│ ├── .htaccess ← mod_rewrite rules etc -│ └── index.php ← initial file that launches the application -└── .htaccess ← prohibits access to all directories except www +│ └── autoload.php ← autoloading de todos os pacotes instalados +├── www/ ← diretório público ou document-root do projeto +│ ├── assets/ ← arquivos estáticos compilados (CSS, JS, imagens, ...) +│ ├── .htaccess ← regras mod_rewrite +│ └── index.php ← arquivo inicial pelo qual a aplicação é iniciada +└── .htaccess ← proíbe o acesso a todos os diretórios exceto www \-- -Você pode mudar a estrutura do diretório de qualquer forma, renomear ou mover pastas e depois basta editar os caminhos para `log/` e `temp/` no arquivo `Bootstrap.php` e o caminho para este arquivo em `composer.json` na seção `autoload`. Nada mais, nenhuma reconfiguração complicada, nenhuma mudança constante. A Nette tem uma [autodetecção inteligente |bootstrap#development-vs-production-mode]. +Você pode alterar a estrutura de diretórios de qualquer forma, renomear ou mover pastas, é totalmente flexível. Além disso, o Nette possui uma autodeteção inteligente e reconhece automaticamente a localização da aplicação, incluindo sua base de URL. -Para aplicações um pouco maiores, podemos dividir pastas com apresentadores e modelos em subdiretórios (em disco) e em namespaces (em código), que chamamos de [módulos |modules]. +Para aplicações um pouco maiores, podemos [dividir as pastas com presenters e templates em subdiretórios |directory-structure#Presenters e templates] e as classes em namespaces, que chamamos de módulos. -O diretório `www/` é o diretório público ou a base de documentos do projeto. Você pode renomeá-lo sem ter que definir nada mais no lado da aplicação. Basta [configurar a hospedagem |nette:troubleshooting#How to change or remove www directory from URL] para que a raíz do documento vá para este diretório. +O diretório `www/` representa o chamado diretório público ou document-root do projeto. Você pode renomeá-lo sem a necessidade de configurar mais nada no lado da aplicação. Apenas é necessário [configurar a hospedagem |nette:troubleshooting#Como alterar ou remover o diretório www da URL] para que o document-root aponte para este diretório. -Você também pode baixar o WebProject diretamente, incluindo a Nette, usando o [Composer |best-practices:composer]: +Você também pode baixar o WebProject diretamente, incluindo o Nette, usando o [Composer |best-practices:composer]: ```shell composer create-project nette/web-project ``` -No Linux ou macOS, defina as [permissões de escrita |nette:troubleshooting#Setting directory permissions] para os diretórios `log/` e `temp/`. +No Linux ou macOS, defina as [permissões de escrita |nette:troubleshooting#Configurando Permissões de Diretório] para as pastas `log/` e `temp/`. -O aplicativo WebProject está pronto para rodar, não há necessidade de configurar mais nada e você pode vê-lo diretamente no navegador acessando a pasta `www/`. +A aplicação WebProject está pronta para ser executada, não é necessário configurar absolutamente nada e você pode exibi-la imediatamente no navegador acessando a pasta `www/`. -Solicitação HTTP .[#toc-http-request] -===================================== +Requisição HTTP +=============== -Tudo começa quando um usuário abre a página em um navegador e o navegador bate no servidor com uma solicitação HTTP. A requisição vai para um arquivo PHP localizado no diretório público `www/`, que é `index.php`. Vamos supor que esta seja uma solicitação para `https://example.com/product/123` e será executada. +Tudo começa no momento em que o usuário abre a página no navegador. Ou seja, quando o navegador faz uma requisição HTTP ao servidor. A requisição é direcionada para um único arquivo PHP, localizado no diretório público `www/`, que é `index.php`. Digamos que seja uma requisição para o endereço `https://example.com/product/123`. Graças à [configuração adequada do servidor |nette:troubleshooting#Como configurar o servidor para URLs amigáveis], até mesmo esta URL é mapeada para o arquivo `index.php` e ele é executado. Sua tarefa é: -1) inicializar o meio ambiente +1) inicializar o ambiente 2) obter a fábrica -3) lançar a aplicação Nette que trata do pedido +3) iniciar a aplicação Nette, que tratará da requisição -Que tipo de fábrica? Nós não produzimos tratores, mas websites! Espere, isso será explicado imediatamente. +Que fábrica? Não estamos fabricando tratores, mas sim páginas web! Aguarde, isso será explicado em breve. -Por "inicializar o ambiente" queremos dizer, por exemplo, que [Tracy |tracy:] é ativado, o que é uma ferramenta incrível para registrar ou visualizar erros. Ele registra os erros no servidor de produção e os exibe diretamente no servidor de desenvolvimento. Portanto, a inicialização também precisa decidir se o site está rodando em modo de produção ou de desenvolvimento. Para isso, a Nette usa autodetecção: se você executar o site no localhost, ele roda em modo desenvolvedor. Você não precisa configurar nada e o aplicativo está pronto tanto para o desenvolvimento quanto para a implantação em produção. Estas etapas são executadas e descritas em detalhes no capítulo sobre a [classe Bootstrap |bootstrap]. +Com as palavras "inicialização do ambiente", queremos dizer, por exemplo, que o [Tracy|tracy:] é ativado, que é uma ferramenta incrível para logging ou visualização de erros. No servidor de produção, ele registra os erros; no de desenvolvimento, ele os exibe diretamente. Portanto, a inicialização também inclui a decisão sobre se a web está sendo executada no modo de produção ou de desenvolvimento. Para isso, o Nette usa uma [autodeteção inteligente |bootstrapping#Modo de desenvolvimento vs produção]: se você executar a web em localhost, ela será executada no modo de desenvolvimento. Assim, você não precisa configurar nada e a aplicação está imediatamente pronta tanto para o desenvolvimento quanto para a implantação em produção. Esses passos são realizados e detalhadamente descritos no capítulo sobre a [classe Bootstrap|bootstrapping]. -O terceiro ponto (sim, pulamos o segundo, mas voltaremos a ele) é iniciar a aplicação. O tratamento dos pedidos HTTP em Nette é feito pela classe `Nette\Application\Application` (doravante denominada `Application`), portanto, quando dizemos "executar uma aplicação", queremos chamar um método com o nome `run()` sobre um objeto desta classe. +O terceiro ponto (sim, pulamos o segundo, mas voltaremos a ele) é iniciar a aplicação. O tratamento de requisições HTTP no Nette é responsabilidade da classe `Nette\Application\Application` (doravante `Application`), então quando dizemos iniciar a aplicação, queremos dizer especificamente chamar o método com o nome apropriado `run()` no objeto desta classe. -Nette é um mentor que orienta você a escrever aplicações limpas através de metodologias comprovadas. E a mais comprovada é chamada de **injeção de dependência**, abreviada DI. No momento não queremos sobrecarregá-lo com a explicação de DI, já que existe um [capítulo separado |dependency-injection:introduction], o importante aqui é que os objetos chave serão normalmente criados por uma fábrica para objetos chamados **container de DI** (abreviado DIC). Sim, esta é a fábrica que foi mencionada há um tempo atrás. E também cria o objeto `Application` para nós, portanto precisamos primeiro de um container. Nós o obtemos usando a classe `Configurator` e o deixamos produzir o objeto `Application`, chamamos o método `run()` e isto inicia a aplicação Nette. Isto é exatamente o que acontece no arquivo [index.php |bootstrap#index.php]. +O Nette é um mentor que o guia para escrever aplicações limpas de acordo com metodologias comprovadas. E uma das mais comprovadas é chamada de **injeção de dependência**, abreviada como DI. Neste momento, não queremos sobrecarregá-lo com a explicação da DI, para isso existe um [capítulo separado|dependency-injection:introduction], o resultado essencial é que os objetos chave geralmente serão criados para nós por uma fábrica de objetos, chamada de **Contêiner de DI** (abreviado como DIC). Sim, essa é a fábrica da qual falamos há pouco. E ela também nos fabricará o objeto `Application`, por isso precisamos primeiro do contêiner. Obtemo-lo usando a classe `Configurator` e deixamos que ele fabrique o objeto `Application`, chamamos o método `run()` nele e assim a aplicação Nette é iniciada. É exatamente isso que acontece no arquivo [index.php |bootstrapping#index.php]. -Aplicação Nette .[#toc-nette-application] -========================================= +Nette Application +================= -A classe Application tem uma única tarefa: responder a uma solicitação HTTP. +A classe Application tem uma única tarefa: responder a uma requisição HTTP. -As aplicações escritas em Nette são divididas em muitos dos chamados apresentadores (em outras estruturas você pode encontrar o termo controlador, que é o mesmo), que são classes que representam uma página específica do site: por exemplo, página inicial; produto na loja virtual; formulário de login; feed do mapa do site, etc. A aplicação pode ter de um a milhares de apresentadores. +Aplicações escritas em Nette são divididas em muitos chamados presenters (em outros frameworks, você pode encontrar o termo controller, é a mesma coisa), que são classes, cada uma representando uma página específica do site: por exemplo, a página inicial; um produto em uma loja virtual; um formulário de login; um feed de sitemap, etc. Uma aplicação pode ter de um a milhares de presenters. -A aplicação começa pedindo ao chamado roteador que decida qual dos apresentadores deve passar o atual pedido de processamento. O roteador decide de quem é a responsabilidade. Ele analisa a URL de entrada `https://example.com/product/123`, que quer `show` um produto com `id: 123` como uma ação. É um bom hábito escrever um par de apresentador + ação separado por dois pontos como `Product:show`. +A Application começa pedindo ao chamado roteador (router) para decidir a qual dos presenters a requisição atual deve ser passada para tratamento. O roteador decide de quem é a responsabilidade. Ele olha para a URL de entrada `https://example.com/product/123` e, com base em como está configurado, decide que este é o trabalho, por exemplo, do **presenter** `Product`, do qual ele desejará como **ação** a exibição (`show`) do produto com `id: 123`. É um bom costume escrever o par presenter + ação separados por dois pontos como `Product:show`. -Assim, o roteador transformou a URL em um par `Presenter:action` + parâmetros, em nosso caso `Product:show` + `id: 123`. Você pode ver como é um roteador no arquivo `app/Router/RouterFactory.php` e nós o descreveremos em detalhes no capítulo [Roteamento |Routing]. +Portanto, o roteador transformou a URL no par `Presenter:action` + parâmetros, no nosso caso `Product:show` + `id: 123`. Como tal roteador se parece, você pode ver no arquivo `app/Core/RouterFactory.php` e o descrevemos detalhadamente no capítulo [Roteamento |Routing]. -Vamos em frente. O pedido já conhece o nome do apresentador e pode continuar. Ao criar um objeto `ProductPresenter`, que é o código do apresentador `Product`. Mais precisamente, pede ao contêiner DI para criar o apresentador, pois a produção de objetos é sua função. +Vamos continuar. A Application já conhece o nome do presenter e pode prosseguir. Criando o objeto da classe `ProductPresenter`, que é o código do presenter `Product`. Mais precisamente, ele pede ao Contêiner de DI para fabricar o presenter, porque fabricar é a função dele. -O apresentador pode se parecer com isto: +O presenter pode parecer assim: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -107,98 +109,92 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function renderShow(int $id): void { - // obtemos os dados do modelo e os passamos para o modelo + // obtemos dados do model e passamos para o template $this->template->product = $this->repository->getProduct($id); } } ``` -O pedido é tratado pelo apresentador. E a tarefa é clara: fazer ação `show` com `id: 123`. O que na linguagem dos apresentadores significa que o método `renderShow()` é chamado e no parâmetro `$id` recebe `123`. +O tratamento da requisição é assumido pelo presenter. E a tarefa é clara: execute a ação `show` com `id: 123`. O que, na linguagem dos presenters, significa que o método `renderShow()` é chamado e recebe `123` no parâmetro `$id`. -Um apresentador pode lidar com múltiplas ações, ou seja, ter múltiplos métodos `render()`. Mas recomendamos projetar apresentadores com uma ou o menor número possível de ações. +Um presenter pode atender a várias ações, ou seja, ter vários métodos `render()`. Mas recomendamos projetar presenters com uma ou o mínimo possível de ações. -Então, o método `renderShow(123)` foi chamado, cujo código é um exemplo fictício, mas você pode ver nele como os dados são passados para o modelo, ou seja, escrevendo para `$this->template`. +Então, o método `renderShow(123)` foi chamado, cujo código é um exemplo fictício, mas você pode ver nele como os dados são passados para o template, ou seja, escrevendo em `$this->template`. -Posteriormente, o apresentador retorna a resposta. Pode ser uma página HTML, uma imagem, um documento XML, o envio de um arquivo do disco, JSON ou o redirecionamento para outra página. É importante ressaltar que se não dissermos explicitamente como responder (que é o caso de `ProductPresenter`), a resposta será renderizar o template com uma página HTML. Por quê? Bem, porque em 99% dos casos queremos desenhar um template, então o apresentador toma este comportamento como padrão e quer tornar nosso trabalho mais fácil. Esse é o ponto de vista da Nette. +Posteriormente, o presenter retorna uma resposta. Esta pode ser uma página HTML, uma imagem, um documento XML, o envio de um arquivo do disco, JSON ou talvez um redirecionamento para outra página. O importante é que, se não dissermos explicitamente como ele deve responder (o que é o caso de `ProductPresenter`), a resposta será a renderização de um template com uma página HTML. Por quê? Porque em 99% dos casos queremos renderizar um template, então o presenter considera esse comportamento como padrão e quer facilitar nosso trabalho. Esse é o propósito do Nette. -Não temos nem mesmo que declarar qual modelo desenhar, ele deriva o caminho para ele de acordo com uma lógica simples. No caso do apresentador `Product` e da ação `show`, ele tenta ver se um destes arquivos de modelo existe em relação ao diretório onde se encontra a classe `ProductPresenter`: +Nem precisamos indicar qual template renderizar, ele deduzirá o caminho por si só. No caso da ação `show`, ele simplesmente tentará carregar o template `show.latte` no diretório com a classe `ProductPresenter`. Ele também tentará localizar o layout no arquivo `@layout.latte` (mais detalhes sobre [localização de templates |templates#Procurando templates]). -- `templates/Product/show.latte` -- `templates/Product.show.latte` - -Ele também tentará encontrar o layout no arquivo `@layout.latte` e depois renderiza o modelo. Agora a tarefa do apresentador e de todo o pedido está concluída. Se o modelo não existir, uma página com erro 404 será devolvida. Você pode ler mais sobre os apresentadores na página [Apresentadores |Presenters]. +E então ele renderiza os templates. Com isso, a tarefa do presenter e de toda a aplicação está concluída e o trabalho está finalizado. Se o template não existisse, seria retornada uma página com erro 404. Você pode ler mais sobre presenters na página [Presenters|presenters]. [* request-flow.svg *] -Só para ter certeza, vamos tentar recapitular todo o processo com uma URL ligeiramente diferente: +Para ter certeza, vamos tentar recapitular todo o processo com uma URL ligeiramente diferente: -1) a URL será `https://example.com` -2) iniciamos a aplicação, criamos um container e executamos `Application::run()` -3) o roteador decodifica a URL como um par `Home:default` -4) um objeto `HomePresenter` é criado -5) método `renderDefault()` é chamado (se existir) -6) um modelo `templates/Home/default.latte` com um layout `templates/@layout.latte` é apresentado +1) A URL será `https://example.com` +2) Inicializamos a aplicação, o contêiner é criado e `Application::run()` é iniciado +3) O roteador decodifica a URL como o par `Home:default` +4) O objeto da classe `HomePresenter` é criado +5) O método `renderDefault()` é chamado (se existir) +6) O template, por exemplo, `default.latte` com o layout, por exemplo, `@layout.latte` é renderizado -Você pode ter se deparado com muitos conceitos novos agora, mas acreditamos que eles fazem sentido. Criar aplicações em Nette é uma brisa. +Talvez você tenha encontrado muitos termos novos agora, mas acreditamos que eles fazem sentido. Criar aplicações no Nette é muito fácil. -Modelos .[#toc-templates] -========================= +Templates +========= -Quando se trata dos modelos, a Nette utiliza o sistema de modelos [Latte |latte:]. É por isso que os arquivos com os gabaritos terminam com `.latte`. O Latte é usado porque é o sistema de modelos mais seguro para PHP e, ao mesmo tempo, o sistema mais intuitivo. Você não precisa aprender muito de novo, você só precisa conhecer PHP e algumas tags de Latte. Você descobrirá tudo [na documentação |latte:]. +Já que falamos de templates, no Nette usa-se o sistema de templates [Latte |latte:]. É por isso que as extensões `.latte` nos templates. O Latte é usado, por um lado, porque é o sistema de templates mais seguro para PHP e, ao mesmo tempo, o sistema mais intuitivo. Você não precisa aprender muito de novo, basta o conhecimento de PHP e algumas tags. Você aprenderá tudo na [documentação |templates]. -No modelo, [criamos um link |creating-links] para outros apresentadores e ações da seguinte forma: +No template, [links são criados |creating-links] para outros presenters & ações assim: ```latte -product detail +detalhe do produto ``` -Basta escrever o familiar par `Presenter:action` ao invés da URL real e incluir quaisquer parâmetros. O truque é `n:href`, que diz que este atributo será processado pela Nette. E ele será gerado: +Simplesmente, em vez da URL real, você escreve o par conhecido `Presenter:action` e especifica quaisquer parâmetros. O truque está no `n:href`, que diz que este atributo será processado pelo Nette. E ele gera: ```latte -product detail +detalhe do produto ``` -O roteador mencionado anteriormente está encarregado de gerar a URL. De fato, os roteadores em Nette são únicos na medida em que podem realizar não apenas transformações de uma URL para um par de apresentador:ação, mas também vice-versa gerar uma URL a partir do nome do apresentador + ação + parâmetros. -Graças a isto, em Nette você pode alterar completamente a forma da URL em toda a aplicação finalizada sem alterar um único caractere no modelo ou apresentador apenas modificando o roteador. -E graças a isto, o chamado trabalho de canonização, que é outra característica única da Nette, que melhora o SEO (otimização da capacidade de busca na internet), evitando automaticamente a existência de conteúdo duplicado em diferentes URLs. -Muitos programadores acham isso surpreendente. +A geração de URLs é responsabilidade do já mencionado roteador. De fato, os roteadores no Nette são excepcionais porque podem realizar não apenas transformações de URL para o par presenter:action, mas também o inverso, ou seja, gerar uma URL a partir do nome do presenter + ação + parâmetros. Graças a isso, no Nette, você pode alterar completamente as formas das URLs em toda a aplicação finalizada, sem alterar um único caractere no template ou presenter. Apenas modificando o roteador. Também graças a isso funciona a chamada canonização, que é outra característica única do Nette, que contribui para um melhor SEO (otimização para motores de busca) ao impedir automaticamente a existência de conteúdo duplicado em URLs diferentes. Muitos programadores consideram isso surpreendente. -Componentes interativos .[#toc-interactive-components] -====================================================== +Componentes interativos +======================= -Temos mais uma coisa a lhe dizer sobre os apresentadores: eles têm um sistema de componentes embutido. Os mais antigos de vocês podem se lembrar de algo semelhante dos formulários Delphi ou ASP.NET Web Forms. O React ou Vue.js é construído sobre algo remotamente semelhante. No mundo das estruturas PHP, esta é uma característica completamente única. +Sobre os presenters, precisamos contar mais uma coisa: eles têm um sistema de componentes embutido. Algo semelhante pode ser familiar aos veteranos do Delphi ou ASP.NET Web Forms, algo remotamente parecido é a base do React ou Vue.js. No mundo dos frameworks PHP, é uma característica absolutamente única. -Os componentes são unidades reutilizáveis separadas que colocamos em páginas (ou seja, apresentadores). Eles podem ser [formas |forms:in-presenter], [datagrids |https://componette.org/contributte/datagrid/], menus, enquetes, na verdade, qualquer coisa que faça sentido usar repetidamente. Podemos criar nossos próprios componentes ou usar alguns dos [enormes |https://componette.org] componentes de código aberto. +Componentes são unidades reutilizáveis independentes que inserimos nas páginas (ou seja, presenters). Podem ser [formulários |forms:in-presenter], [datagrids |https://componette.org/contributte/datagrid/], menus, enquetes de votação, na verdade, qualquer coisa que faça sentido usar repetidamente. Podemos criar nossos próprios componentes ou usar alguns da [enorme oferta |https://componette.org] de componentes open source. -Os componentes mudam fundamentalmente a abordagem para o desenvolvimento de aplicações. Eles abrirão novas possibilidades de composição de páginas a partir de unidades pré-definidas. E eles têm algo em comum com [Hollywood |components#Hollywood style]. +Os componentes influenciam fundamentalmente a abordagem para a criação de aplicações. Eles abrirão novas possibilidades para você compor páginas a partir de unidades pré-preparadas. E, além disso, eles têm algo em comum com [Hollywood |components#Estilo Hollywood]. -DI Container e Configuração .[#toc-di-container-and-configuration] -================================================================== +Contêiner de DI e configuração +============================== -O recipiente DI (fábrica para objetos) é o coração de toda a aplicação. +O Contêiner de DI, ou fábrica de objetos, é o coração de toda a aplicação. -Não se preocupe, não é uma caixa preta mágica, como pode parecer das palavras anteriores. Na verdade, é uma classe PHP bastante enfadonha gerada pela Nette e armazenada em um diretório cache. Ela tem muitos métodos nomeados como `createServiceAbcd()` e cada um deles cria e devolve um objeto. Sim, há também um método `createServiceApplication()` que produzirá `Nette\Application\Application`, que precisávamos no arquivo `index.php` para executar a aplicação. E há métodos para a produção de apresentadores individuais. E assim por diante. +Não se preocupe, não é nenhuma caixa preta mágica, como poderia parecer das linhas anteriores. Na verdade, é uma classe PHP bastante comum, que o Nette gera e salva no diretório de cache. Ela tem muitos métodos nomeados como `createServiceAbcd()` e cada um deles sabe como fabricar e retornar algum objeto. Sim, também existe o método `createServiceApplication()`, que fabrica `Nette\Application\Application`, que precisávamos no arquivo `index.php` para iniciar a aplicação. E existem métodos que fabricam os presenters individuais. E assim por diante. -Os objetos que o container DI cria são chamados de serviços por alguma razão. +Objetos que o Contêiner de DI cria são, por algum motivo, chamados de serviços. -O que é realmente especial sobre esta classe é que ela não é programada por você, mas pela estrutura. Ela realmente gera o código PHP e o salva em disco. Você apenas dá instruções sobre quais objetos o recipiente deve ser capaz de produzir e como exatamente. E estas instruções são escritas em [arquivos de configuração |bootstrap#DI Container Configuration] no [formato NEON |neon:format] e, portanto, têm a extensão `.neon`. +O que é realmente especial sobre esta classe é que você não a programa, mas sim o framework. Ele realmente gera o código PHP e o salva no disco. Você apenas dá instruções sobre quais objetos o contêiner deve saber fabricar e como exatamente. E essas instruções são escritas nos [arquivos de configuração |bootstrapping#Configuração do contêiner de DI], para os quais se usa o formato [NEON|neon:format] e, portanto, também têm a extensão `.neon`. -Os arquivos de configuração são usados puramente para instruir o recipiente DI. Assim, por exemplo, se eu especificar a opção `expiration: 14 days` na seção [sessão |http:configuration#Session], o recipiente DI ao criar o objeto `Nette\Http\Session` que representa a sessão chamará seu método `setExpiration('14 days')`, e assim a configuração se tornará uma realidade. +Os arquivos de configuração servem puramente para instruir o Contêiner de DI. Então, por exemplo, se eu especificar na seção [sessão |http:configuration#Sessão] a opção `expiration: 14 days`, o Contêiner de DI, ao criar o objeto `Nette\Http\Session` representando a sessão, chamará seu método `setExpiration('14 days')` e assim a configuração se tornará realidade. -Há um capítulo inteiro pronto para você, descrevendo o que pode ser [configurado |nette:configuring] e como [definir seus próprios serviços |dependency-injection:services]. +Há um capítulo inteiro preparado para você descrevendo tudo o que pode ser [configurado |nette:configuring] e como [definir seus próprios serviços |dependency-injection:services]. -Uma vez que você entre na criação de serviços, você se deparará com a palavra [autoconexão |dependency-injection:autowiring]. Este é um gadget que tornará sua vida incrivelmente mais fácil. Ele pode passar objetos automaticamente onde você precisar (nos construtores de suas aulas, por exemplo) sem ter que fazer nada. Você descobrirá que o recipiente DI em Nette é um pequeno milagre. +Assim que você se aprofundar um pouco na criação de serviços, encontrará a palavra [autowiring |dependency-injection:autowiring]. Esta é uma funcionalidade que simplificará sua vida de maneira incrível. Ela pode passar automaticamente objetos para onde você precisa deles (por exemplo, nos construtores de suas classes), sem que você precise fazer nada. Você descobrirá que o Contêiner de DI no Nette é um pequeno milagre. -E agora? .[#toc-what-next] -========================== +Para onde ir agora? +=================== -Passamos pelos princípios básicos das aplicações em Nette. Até agora, muito superficialmente, mas em breve você mergulhará nas profundezas e eventualmente criará aplicações maravilhosas na web. Onde continuar? Você já tentou o tutorial [Criar sua primeira aplicação |quickstart:]? +Percorremos os princípios básicos das aplicações no Nette. Até agora, muito superficialmente, mas em breve você se aprofundará e, com o tempo, criará aplicações web maravilhosas. Para onde continuar agora? Você já experimentou o tutorial [Escrevendo a primeira aplicação|quickstart:]? -Além do acima exposto, Nette possui todo um arsenal de [classes úteis |utils:], [camada de banco de dados |database:], etc. Tente de propósito apenas clicar na documentação. Ou visite o [blog |https://blog.nette.org]. Você descobrirá um monte de coisas interessantes. +Além do que foi descrito acima, o Nette possui todo um arsenal de [classes úteis|utils:], uma [camada de banco de dados|database:], etc. Tente apenas navegar pela documentação. Ou pelo [blog|https://blog.nette.org]. Você descobrirá muitas coisas interessantes. -Deixe que a estrutura lhe traga muita alegria 💙 +Que o framework lhe traga muita alegria 💙 diff --git a/application/pt/modules.texy b/application/pt/modules.texy deleted file mode 100644 index 35ee3786d3..0000000000 --- a/application/pt/modules.texy +++ /dev/null @@ -1,148 +0,0 @@ -Módulos -******* - -.[perex] -Em Nette, os módulos representam as unidades lógicas que compõem uma aplicação. Eles incluem apresentadores, gabaritos, possivelmente também componentes e classes de modelos. - -Um diretório para apresentadores e um para modelos não seria suficiente para projetos reais. Ter dezenas de arquivos em uma pasta é pelo menos desorganizado. Como sair dela? Nós simplesmente os dividimos em subdiretórios em disco e em namespaces no código. E é exatamente isso que os módulos Nette fazem. - -Portanto, vamos esquecer uma única pasta para apresentadores e modelos e, em vez disso, criar módulos, por exemplo `Admin` e `Front`. - -/--pre -app/ -├── Presenters/ -├── Modules/ ← directory with modules -│ ├── Admin/ ← module Admin -│ │ ├── Presenters/ ← its presenters -│ │ │ ├── DashboardPresenter.php -│ │ │ └── templates/ -│ └── Front/ ← module Front -│ └── Presenters/ ← its presenters -│ └── ... -\-- - -Esta estrutura de diretório será refletida pelos namespaces de classe, portanto, por exemplo `DashboardPresenter` estará no namespace `App\Modules\Admin\Presenters`: - -```php -namespace App\Modules\Admin\Presenters; - -class DashboardPresenter extends Nette\Application\UI\Presenter -{ - // ... -} -``` - -O `Dashboard` apresentador dentro do módulo `Admin` é referenciado dentro da aplicação usando a notação de cólon como `Admin:Dashboard`, e sua ação `default` como `Admin:Dashboard:default`. -E como a Nette propriamente dita sabe que `Admin:Dashboard` representa a classe `App\Modules\Admin\Presenters\DashboardPresenter`? Isto é determinado pelo [mapeamento |#mapping] na configuração. -Assim, a estrutura dada não é difícil de definir e você pode modificá-la de acordo com suas necessidades. - -Os módulos podem naturalmente conter todos os outros itens além de apresentadores e modelos, tais como componentes, classes de modelos, etc. - - -Módulos aninhados .[#toc-nested-modules] ----------------------------------------- - -Os módulos não precisam formar apenas uma estrutura plana, você também pode criar submódulos, por exemplo: - -/--pre -app/ -├── Modules/ ← directory with modules -│ ├── Blog/ ← module Blog -│ │ ├── Admin/ ← submodule Admin -│ │ │ ├── Presenters/ -│ │ │ └── ... -│ │ └── Front/ ← submodule Front -│ │ ├── Presenters/ -│ │ └── ... -│ ├── Forum/ ← module Forum -│ │ └── ... -\-- - -Assim, o módulo `Blog` está dividido em `Admin` e `Front` submódulos. Mais uma vez, isto se refletirá nos namespaces, que serão `App\Modules\Blog\Admin\Presenters` etc. O apresentador `Dashboard` dentro do submódulo é referido como `Blog:Admin:Dashboard`. - -O ninho pode ir tão fundo quanto você desejar, de modo que sub-submódulos podem ser criados. - - -Criação de links .[#toc-creating-links] ---------------------------------------- - -Os links nos modelos de apresentadores são relativos ao módulo atual. Assim, o link `Foo:default` leva ao apresentador `Foo` no mesmo módulo que o apresentador atual. Se o módulo atual é `Front`, por exemplo, então o link vai assim: - -```latte -link to Front:Product:show -``` - -Um link é relativo mesmo que inclua o nome de um módulo, que é então considerado um submódulo: - -```latte -link to Front:Shop:Product:show -``` - -Links absolutos são escritos analogamente a caminhos absolutos em disco, mas com colons ao invés de cortes. Assim, uma ligação absoluta começa com dois-pontos: - -```latte -link to Admin:Product:show -``` - -Para saber se estamos em um determinado módulo ou em seu submódulo, podemos usar a função `isModuleCurrent(moduleName)`. - -```latte -
  • - ... -
  • -``` - - -Roteiro .[#toc-routing] ------------------------ - -Ver [capítulo sobre roteamento |routing#Modules]. - - -Mapeamento .[#toc-mapping] --------------------------- - -Define as regras pelas quais o nome da classe é derivado do nome do apresentador. Nós as escrevemos na [configuração |configuration] sob a chave `application › mapping`. - -Vamos começar com uma amostra que não utiliza módulos. Queremos apenas que as classes de apresentadores tenham o namespace `App\Presenters`. Isso significa que um apresentador como o `Home` deve mapear para a classe `App\Presenters\HomePresenter`. Isto pode ser conseguido através da seguinte configuração: - -```neon -application: - mapping: - *: App\Presenters\*Presenter -``` - -O nome do apresentador é substituído pelo asterisco na máscara da classe e o resultado é o nome da classe. Fácil! - -Se dividirmos os apresentadores em módulos, podemos ter nosso próprio mapeamento para cada módulo: - -```neon -application: - mapping: - Front: App\Modules\Front\Presenters\*Presenter - Admin: App\Modules\Admin\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -Agora o apresentador `Front:Home` mapeia para a classe `App\Modules\Front\Presenters\HomePresenter` e o apresentador `Admin:Dashboard` para a classe `App\Modules\Admin\Presenters\DashboardPresenter`. - -É mais prático criar uma regra geral (estrela) para substituir as duas primeiras. O asterisco extra será adicionado à máscara de classe apenas para o módulo: - -```neon -application: - mapping: - *: App\Modules\*\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -Mas e se usarmos módulos aninhados e tivermos um apresentador `Admin:User:Edit`? Neste caso, o segmento com um asterisco representando o módulo para cada nível é simplesmente repetido e o resultado é a classe `App\Modules\Admin\User\Presenters\EditPresenter`. - -Uma notação alternativa é utilizar um conjunto composto de três segmentos em vez de um fio. Esta notação é equivalente à anterior: - -```neon -application: - mapping: - *: [App\Modules, *, Presenters\*Presenter] -``` - -O valor padrão é `*: *Module\*Presenter`. diff --git a/application/pt/multiplier.texy b/application/pt/multiplier.texy index 35b84493a0..f9f867f5ca 100644 --- a/application/pt/multiplier.texy +++ b/application/pt/multiplier.texy @@ -1,30 +1,32 @@ -Multiplicador: Componentes dinâmicos -************************************ +Multiplier: componentes dinâmicos +********************************* -Uma ferramenta para a criação dinâmica de componentes interativos .[perex] +.[perex] +Ferramenta para criação dinâmica de componentes interativos -Vamos começar com um problema típico: temos uma lista de produtos em um site de comércio eletrônico e queremos acompanhar cada produto com um formulário *add to cart*. Uma das maneiras é envolver toda a lista em um único formulário. Uma maneira mais conveniente é usar [api:Nette\Application\UI\Multiplier]. +Vamos partir de um exemplo típico: temos uma lista de produtos em uma loja virtual, e para cada um queremos exibir um formulário para adicionar o produto ao carrinho. Uma das opções possíveis é envolver toda a listagem em um único formulário. No entanto, um método muito mais conveniente nos é oferecido pelo [api:Nette\Application\UI\Multiplier]. -O multiplicador permite definir uma fábrica para múltiplos componentes. Ele se baseia no princípio de componentes aninhados - cada componente herdado de [api:Nette\ComponentModel\Container] pode conter outros componentes. +O Multiplier permite definir convenientemente uma pequena fábrica para múltiplos componentes. Funciona com base no princípio de componentes aninhados - cada componente que herda de [api:Nette\ComponentModel\Container] pode conter outros componentes. -Veja o [modelo dos componentes |components#Components in Depth] na documentação. .[tip] +.[tip] +Veja o capítulo sobre o [modelo de componentes |components#Componentes em profundidade] na documentação ou a [palestra de Honza Tvrdík|https://www.youtube.com/watch?v=8y3LLexWu-I]. -O multiplicador posa como um componente pai que pode criar dinamicamente seus filhos usando a callback passada no construtor. Veja o exemplo: +A essência do Multiplier é que ele atua na posição de pai, que pode criar seus descendentes dinamicamente usando um callback passado no construtor. Veja o exemplo: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function () { $form = new Nette\Application\UI\Form; - $form->addInteger('amount', 'Amount:') + $form->addInteger('count', 'Quantidade de produtos:') ->setRequired(); - $form->addSubmit('send', 'Add to cart'); + $form->addSubmit('send', 'Adicionar ao carrinho'); return $form; }); } ``` -No modelo podemos apresentar um formulário para cada produto - e cada formulário será de fato um componente único. +Agora podemos, no template, simplesmente deixar renderizar o formulário para cada produto - e cada um será realmente um componente único. ```latte {foreach $items as $item} @@ -35,26 +37,26 @@ No modelo podemos apresentar um formulário para cada produto - e cada formulár {/foreach} ``` -Argumento passado para `{control}` tag diz: +O argumento passado na tag `{control}` está em um formato que diz: -1. obter um componente `shopForm` -2. e devolver seu filho `$item->id` +1. obtenha o componente `shopForm` +2. e dele obtenha o descendente `$item->id` -Durante a primeira chamada de **1.** o componente `shopForm` ainda não existe, portanto o método `createComponentShopForm` é chamado para criá-lo. Uma função anônima passada como parâmetro para o Multiplicador, é então chamada e um formulário é criado. +Na primeira chamada do ponto **1.**, `shopForm` ainda não existe, então sua fábrica `createComponentShopForm` é chamada. No componente obtido (instância do Multiplier), a fábrica do formulário específico é então chamada - que é a função anônima que passamos para o Multiplier no construtor. -Nas iterações subsequentes do `foreach` o método `createComponentShopForm` não é mais chamado porque o componente já existe. Mas como fazemos referência a outra criança (`$item->id` varia entre iterações), uma função anônima é chamada novamente e uma nova forma é criada. +Na próxima iteração do foreach, o método `createComponentShopForm` não será mais chamado (o componente existe), mas como estamos procurando por um descendente diferente dele (`$item->id` será diferente em cada iteração), a função anônima será chamada novamente e nos retornará um novo formulário. -A última coisa é garantir que o formulário realmente acrescente o produto correto ao carrinho porque no estado atual todos os formulários são iguais e não podemos distinguir a que produtos eles pertencem. Para isso, podemos usar a propriedade de Multiplicador (e em geral de qualquer método de fábrica de componentes em Nette Framework) que cada método de fábrica de componentes recebe o nome do componente criado como o primeiro argumento. Em nosso caso, isso seria `$item->id`, que é exatamente o que precisamos para distinguir os produtos individuais. Tudo o que precisamos fazer é modificar o código para criar o formulário: +A única coisa que resta é garantir que o formulário adicione ao carrinho realmente o produto que deve - atualmente, o formulário é completamente idêntico para cada produto. A propriedade do Multiplier (e geralmente de cada fábrica de componentes no Nette Framework) nos ajudará, que é que cada fábrica recebe como seu primeiro argumento o nome do componente sendo criado. No nosso caso, será `$item->id`, que é exatamente a informação que precisamos. Basta, portanto, ajustar ligeiramente a criação do formulário: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function ($itemId) { $form = new Nette\Application\UI\Form; - $form->addInteger('amount', 'Amount:') + $form->addInteger('count', 'Quantidade de produtos:') ->setRequired(); $form->addHidden('itemId', $itemId); - $form->addSubmit('send', 'Add to cart'); + $form->addSubmit('send', 'Adicionar ao carrinho'); return $form; }); } diff --git a/application/pt/presenters.texy b/application/pt/presenters.texy index e5558b4842..c0f77a364e 100644 --- a/application/pt/presenters.texy +++ b/application/pt/presenters.texy @@ -1,39 +1,39 @@ -Apresentadores -************** +Presenters +**********
    -Aprenderemos como escrever apresentadores e modelos em Nette. Após a leitura, você saberá: +Vamos nos familiarizar com como escrever presenters e templates no Nette. Após a leitura, você saberá: -- como funciona o apresentador +- como funciona um presenter - o que são parâmetros persistentes -- como fazer um modelo +- como os templates são renderizados
    -[Já sabemos |how-it-works#nette-application] que um apresentador é uma classe que representa uma página específica de uma aplicação web, como uma página inicial; produto em e-shop; formulário de login; feed de mapa do site, etc. A aplicação pode ter de um a milhares de apresentadores. Em outras estruturas, eles também são conhecidos como controladores. +[Já sabemos |how-it-works#Nette Application], que um presenter é uma classe que representa uma página específica de uma aplicação web, por exemplo, a página inicial; um produto em uma loja virtual; um formulário de login; um feed de sitemap, etc. Uma aplicação pode ter de um a milhares de presenters. Em outros frameworks, eles também são chamados de controllers. -Normalmente, o termo apresentador se refere a um descendente da classe [api:Nette\Application\UI\Presenter], que é adequado para interfaces web e que discutiremos no resto deste capítulo. Em um sentido geral, um apresentador é qualquer objeto que implemente a interface [api:Nette\Application\IPresenter]. +Geralmente, sob o termo presenter, entende-se um descendente da classe [api:Nette\Application\UI\Presenter], que é adequado para gerar interfaces web e ao qual nos dedicaremos no restante deste capítulo. Em um sentido geral, um presenter é qualquer objeto que implementa a interface [api:Nette\Application\IPresenter]. -Ciclo de vida do apresentador .[#toc-life-cycle-of-presenter] -============================================================= +Ciclo de vida do presenter +========================== -O trabalho do apresentador é processar a solicitação e devolver uma resposta (que pode ser uma página HTML, imagem, redirecionamento, etc.). +A tarefa do presenter é processar a requisição e retornar uma resposta (que pode ser uma página HTML, uma imagem, um redirecionamento, etc.). -Portanto, no início é um pedido. Não é diretamente uma solicitação HTTP, mas um objeto [api:Nette\Application\Request] no qual a solicitação HTTP foi transformada usando um roteador. Normalmente não entramos em contato com este objeto, porque o apresentador delega inteligentemente o processamento da solicitação a métodos especiais, o que veremos agora. +Portanto, no início, a requisição é passada a ele. Não é diretamente uma requisição HTTP, mas um objeto [api:Nette\Application\Request], no qual a requisição HTTP foi transformada com a ajuda do roteador. Geralmente não interagimos com este objeto, pois o presenter delega inteligentemente o processamento da requisição para outros métodos, que mostraremos agora. -[* lifecycle.svg *] ***Ciclo de vida do apresentador* .<> +[* lifecycle.svg *] *** *Ciclo de vida do presenter* .<> -A figura mostra uma lista de métodos que são chamados sequencialmente de cima para baixo, se eles existirem. Nenhum deles precisa existir, podemos ter um apresentador completamente vazio sem um único método e construir uma simples teia estática sobre ele. +A imagem representa uma lista de métodos que são chamados sequencialmente de cima para baixo, se existirem. Nenhum deles precisa existir, podemos ter um presenter completamente vazio sem um único método e construir um site estático simples sobre ele. `__construct()` --------------- -O construtor não pertence exatamente ao ciclo de vida do apresentador, pois é chamado no momento da criação do objeto. Mas nós o mencionamos devido à sua importância. O construtor (juntamente com o [método de injeção |best-practices:inject-method-attribute]) é usado para passar dependências. +O construtor não pertence exatamente ao ciclo de vida do presenter, porque é chamado no momento da criação do objeto. Mas o mencionamos devido à sua importância. O construtor (juntamente com o [método inject|best-practices:inject-method-attribute]) serve para passar dependências. -O apresentador não deve cuidar da lógica comercial da aplicação, escrever e ler a partir do banco de dados, realizar cálculos, etc. Esta é a tarefa para as classes de uma camada, que chamamos de modelo. Por exemplo, a classe `ArticleRepository` pode ser responsável por carregar e salvar artigos. Para que o apresentador possa utilizá-la, ela é [passada usando a injeção de dependência |dependency-injection:passing-dependencies]: +O presenter não deve cuidar da lógica de negócios da aplicação, escrever e ler do banco de dados, realizar cálculos, etc. Para isso existem classes da camada que chamamos de model. Por exemplo, a classe `ArticleRepository` pode ser responsável por carregar e salvar artigos. Para que o presenter possa trabalhar com ela, ele a solicita [via injeção de dependência |dependency-injection:passing-dependencies]: ```php @@ -50,44 +50,44 @@ class ArticlePresenter extends Nette\Application\UI\Presenter `startup()` ----------- -Imediatamente após o recebimento do pedido, é invocado o método `startup ()`. Você pode utilizá-lo para inicializar as propriedades, verificar os privilégios do usuário, etc. É necessário chamar sempre o ancestral `parent::startup()`. +Imediatamente após receber a requisição, o método `startup()` é chamado. Você pode usá-lo para inicializar propriedades, verificar permissões de usuário, etc. É necessário que o método sempre chame o ancestral `parent::startup()`. `action(args...)` .{toc: action()} -------------------------------------------------- -Semelhante ao método `render()`. Enquanto `render()` é destinado a preparar dados para um modelo específico, que é posteriormente apresentado, em `action()` uma solicitação é processada sem acompanhamento da apresentação do modelo. Por exemplo, os dados são processados, um usuário entra ou sai, e assim por diante, e depois é [redirecionado para outro lugar |#Redirection]. +Análogo ao método `render()`. Enquanto `render()` se destina a preparar dados para um template específico que será subsequentemente renderizado, em `action()` a requisição é processada sem ligação à renderização do template. Por exemplo, os dados são processados, o usuário é logado ou deslogado, e assim por diante, e então [redireciona para outro lugar |#Redirecionamento]. -É importante que `action()` é chamado antes `render()`Assim, dentro dele podemos possivelmente mudar o próximo ciclo de vida, ou seja, mudar o modelo que será apresentado e também o método `render()` que será chamado, usando `setView('otherView')`. +O importante é que `action()` é chamado antes de `render()`, então nele podemos eventualmente mudar o curso dos eventos, ou seja, mudar o template que será renderizado, e também o método `render()` que será chamado. E isso usando `setView('outroView')`. -Os parâmetros do pedido são passados para o método. É possível e recomendado especificar tipos para os parâmetros, por exemplo `actionShow(int $id, string $slug = null)` - se o parâmetro `id` estiver faltando ou se não for um número inteiro, o apresentador retorna o [erro 404 |#Error 404 etc.] e encerra a operação. +Parâmetros da requisição são passados para o método. É possível e recomendado especificar tipos para os parâmetros, por exemplo, `actionShow(int $id, ?string $slug = null)` - se o parâmetro `id` estiver faltando ou não for um inteiro, o presenter retornará um [erro 404 |#Erro 404 e cia] e encerrará a atividade. `handle(args...)` .{toc: handle()} -------------------------------------------------- -Este método processa os chamados sinais, que discutiremos no capítulo sobre [Componentes |components#Signal]. Ele se destina principalmente a componentes e processamento de pedidos AJAX. +O método processa os chamados sinais, com os quais nos familiarizaremos no capítulo dedicado aos [componentes |components#Sinal]. Ele é destinado principalmente a componentes e ao processamento de requisições AJAX. -Os parâmetros são passados para o método, como no caso de `action()`incluindo a verificação do tipo. +Parâmetros da requisição são passados para o método, como no caso de `action()`, incluindo verificação de tipo. `beforeRender()` ---------------- -O método `beforeRender`, como o nome sugere, é chamado antes de cada método `render()`. É utilizado para configuração de modelos comuns, passando variáveis para layout e assim por diante. +O método `beforeRender`, como o nome sugere, é chamado antes de cada método `render()`. É usado para configuração comum do template, passagem de variáveis para o layout e assim por diante. `render(args...)` .{toc: render()} ---------------------------------------------- -O local onde preparamos o modelo para posterior renderização, passamos dados para ele, etc. +O local onde preparamos o template para a renderização subsequente, passamos dados para ele, etc. -Os parâmetros são passados para o método, como no caso de `action()`incluindo a verificação do tipo. +Parâmetros da requisição são passados para o método, como no caso de `action()`, incluindo verificação de tipo. ```php public function renderShow(int $id): void { - // obtemos os dados do modelo e os passamos para o modelo + // obtemos dados do model e passamos para o template $this->template->article = $this->articles->getById($id); } ``` @@ -96,104 +96,104 @@ public function renderShow(int $id): void `afterRender()` --------------- -O método `afterRender`, como o nome sugere novamente, é chamado depois de cada `render()` método. Ele é usado muito raramente. +O método `afterRender`, como o nome novamente sugere, é chamado após cada método `render()`. É usado de forma bastante excepcional. `shutdown()` ------------ -É chamado no final do ciclo de vida do apresentador. +É chamado no final do ciclo de vida do presenter. -**Bom conselho antes de seguirmos em frente***. Como você pode ver, o apresentador pode lidar com mais ações/visões, ou seja, ter mais métodos `render()`. Mas recomendamos projetar apresentadores com uma ou o menor número possível de ações. +**Um bom conselho antes de prosseguirmos**. Como pode ser visto, um presenter pode atender a várias ações/views, ou seja, ter vários métodos `render()`. Mas recomendamos projetar presenters com uma ou o mínimo possível de ações. -Enviando uma resposta .[#toc-sending-a-response] -================================================ +Envio da resposta +================= -A resposta do apresentador geralmente é [renderizar o modelo com a página HTML |templates], mas também pode estar enviando um arquivo, JSON ou mesmo redirecionando para outra página. +A resposta do presenter geralmente é a [renderização de um template com uma página HTML|templates], mas também pode ser o envio de um arquivo, JSON ou talvez um redirecionamento para outra página. -A qualquer momento durante o ciclo de vida, você pode usar qualquer um dos seguintes métodos para enviar uma resposta e sair do apresentador ao mesmo tempo: +A qualquer momento durante o ciclo de vida, podemos enviar uma resposta usando um dos seguintes métodos e, ao mesmo tempo, encerrar o presenter: -- `redirect()`, `redirectPermanent()`, `redirectUrl()` e `forward()` [redireciona |#Redirection] -- `error()` deixa o apresentador [devido a erro |#Error 404 etc.] -- `sendJson($data)` deixa o apresentador e [envia os dados |#Sending JSON] no formato JSON -- `sendTemplate()` desiste do apresentador e imediatamente [apresenta o modelo |templates] -- `sendResponse($response)` deixa o apresentador e envia [a sua própria resposta |#Responses] -- `terminate()` deixa o apresentador sem resposta +- `redirect()`, `redirectPermanent()`, `redirectUrl()` e `forward()` [redirecionam |#Redirecionamento] +- `error()` encerra o presenter [devido a um erro |#Erro 404 e cia] +- `sendJson($data)` encerra o presenter e [envia dados |#Envio de JSON] no formato JSON +- `sendTemplate()` encerra o presenter e imediatamente [renderiza o template |templates] +- `sendResponse($response)` encerra o presenter e envia uma [resposta personalizada |#Respostas] +- `terminate()` encerra o presenter sem resposta -Se você não chamar nenhum destes métodos, o apresentador procederá automaticamente para renderizar o modelo. Por quê? Bem, porque em 99% dos casos queremos desenhar um modelo, então o apresentador toma este comportamento como padrão e quer tornar nosso trabalho mais fácil. +Se você não chamar nenhum desses métodos, o presenter automaticamente procederá à renderização do template. Por quê? Porque em 99% dos casos queremos renderizar um template, então o presenter considera esse comportamento como padrão e quer facilitar nosso trabalho. -Criação de links .[#toc-creating-links] -======================================= +Criação de links +================ -O apresentador tem um método `link()`, que é usado para criar links de URL para outros apresentadores. O primeiro parâmetro é o apresentador alvo & ação, seguido dos argumentos, que podem ser passados como array: +O presenter possui o método `link()`, com o qual é possível criar links URL para outros presenters. O primeiro parâmetro é o presenter & ação de destino, seguido pelos argumentos passados, que podem ser especificados como um array: ```php $url = $this->link('Product:show', $id); -$url = $this->link('Product:show', [$id, 'lang' => 'en']); +$url = $this->link('Product:show', [$id, 'lang' => 'pt']); ``` -No modelo, criamos links para outros apresentadores e ações da seguinte forma: +No template, links para outros presenters & ações são criados desta forma: ```latte -product detail +detalhe do produto ``` -Basta escrever o familiar par `Presenter:action` ao invés da URL real e incluir quaisquer parâmetros. O truque é `n:href`, que diz que este atributo será processado por Latte e gera uma URL real. Em Nette, você não precisa pensar em URLs de forma alguma, apenas em apresentadores e ações. +Simplesmente, em vez da URL real, você escreve o par conhecido `Presenter:action` e especifica quaisquer parâmetros. O truque está no `n:href`, que diz que este atributo será processado pelo Latte e gerará a URL real. No Nette, você não precisa pensar em URLs, apenas em presenters e ações. -Para mais informações, consulte [Criação de links |Creating Links]. +Mais informações podem ser encontradas no capítulo [Criando Links URL|creating-links]. -Redirecionamento .[#toc-redirection] -==================================== +Redirecionamento +================ -Os métodos `redirect()` e `forward()` são usados para saltar para outro apresentador, que tem uma sintaxe muito semelhante à do [link |#Creating Links] do método. +Para ir para outro presenter, usam-se os métodos `redirect()` e `forward()`, que têm uma sintaxe muito semelhante ao método [link() |#Criação de links]. -O `forward()` muda imediatamente para o novo apresentador sem redirecionamento HTTP: +O método `forward()` vai para o novo presenter imediatamente sem um redirecionamento HTTP: ```php $this->forward('Product:show'); ``` -Exemplo de redirecionamento temporário com o código HTTP 302 ou 303: +Exemplo do chamado redirecionamento temporário com código HTTP 302 (ou 303, se o método da requisição atual for POST): ```php $this->redirect('Product:show', $id); ``` -Para obter um redirecionamento permanente com o uso do código HTTP 301: +O redirecionamento permanente com código HTTP 301 é alcançado assim: ```php $this->redirectPermanent('Product:show', $id); ``` -Você pode redirecionar para outra URL fora da aplicação com o método `redirectUrl()`: +Para redirecionar para outra URL fora da aplicação, pode-se usar o método `redirectUrl()`. O código HTTP pode ser passado como segundo parâmetro, o padrão é 302 (ou 303, se o método da requisição atual for POST): ```php $this->redirectUrl('https://nette.org'); ``` -A redireção encerra imediatamente o ciclo de vida do apresentador, lançando a chamada exceção de terminação silenciosa `Nette\Application\AbortException`. +O redirecionamento encerra imediatamente a atividade do presenter lançando a chamada exceção de terminação silenciosa `Nette\Application\AbortException`. -Antes do redirecionamento, é possível enviar uma [mensagem flash |#Flash Messages], mensagens que serão exibidas no modelo após o redirecionamento. +Antes do redirecionamento, é possível enviar uma [flash message |#Mensagens Flash], ou seja, mensagens que serão exibidas no template após o redirecionamento. -Mensagens Flash .[#toc-flash-messages] -====================================== +Mensagens Flash +=============== -Estas são mensagens que normalmente informam sobre o resultado de uma operação. Uma característica importante das mensagens flash é que elas estão disponíveis no modelo, mesmo após o redirecionamento. Mesmo após serem exibidas, elas permanecerão vivas por mais 30 segundos - por exemplo, caso o usuário atualize involuntariamente a página - a mensagem não será perdida. +São mensagens que geralmente informam sobre o resultado de alguma operação. Uma característica importante das mensagens flash é que elas estão disponíveis no template mesmo após um redirecionamento. Mesmo após serem exibidas, elas permanecem ativas por mais 30 segundos – por exemplo, caso o usuário atualize a página devido a um erro de transmissão - a mensagem não desaparecerá imediatamente. -Basta ligar para o método [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] e o apresentador se encarregará de passar a mensagem para o modelo. O primeiro argumento é o texto da mensagem e o segundo argumento opcional é seu tipo (erro, aviso, informação, etc.). O método `flashMessage()` retorna uma instância de mensagem flash, para nos permitir acrescentar mais informações. +Basta chamar o método [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] e o presenter se encarrega de passá-la para o template. O primeiro parâmetro é o texto da mensagem e o segundo parâmetro opcional é o seu tipo (error, warning, info, etc.). O método `flashMessage()` retorna uma instância da mensagem flash, à qual informações adicionais podem ser adicionadas. ```php -$this->flashMessage('Item was removed.'); -$this->redirect(/* ... */); +$this->flashMessage('O item foi excluído.'); +$this->redirect(/* ... */); // e redirecionamos ``` -No modelo, estas mensagens estão disponíveis na variável `$flashes` como objetos `stdClass`, que contém as propriedades `message` (texto da mensagem), `type` (tipo de mensagem) e podem conter as informações de usuário já mencionadas. Nós as desenhamos da seguinte forma: +No template, essas mensagens estão disponíveis na variável `$flashes` como objetos `stdClass`, que contêm as propriedades `message` (texto da mensagem), `type` (tipo da mensagem) e podem conter as informações do usuário já mencionadas. Nós as renderizamos assim, por exemplo: ```latte {foreach $flashes as $flash} @@ -202,10 +202,10 @@ No modelo, estas mensagens estão disponíveis na variável `$flashes` como obje ``` -Erro 404 etc. .[#toc-error-404-etc] -=================================== +Erro 404 e cia. +=============== -Quando não pudermos atender ao pedido porque, por exemplo, o artigo que queremos exibir não existe no banco de dados, vamos jogar fora o erro 404 usando o método `error(string $message = null, int $httpCode = 404)`, que representa o erro HTTP 404: +Se a requisição não puder ser atendida, por exemplo, porque o artigo que queremos exibir não existe no banco de dados, lançamos um erro 404 com o método `error(?string $message = null, int $httpCode = 404)`. ```php public function renderShow(int $id): void @@ -218,14 +218,13 @@ public function renderShow(int $id): void } ``` -O código de erro HTTP pode ser passado como o segundo parâmetro, o padrão é 404. O método funciona lançando a exceção `Nette\Application\BadRequestException`, após a qual `Application` passa o controle para o apresentador do erro. Que é um apresentador cuja função é exibir uma página informando sobre o erro. -O apresentador de erros é definido na [configuração da aplicação |configuration]. +O código HTTP do erro pode ser passado como segundo parâmetro, o padrão é 404. O método funciona lançando a exceção `Nette\Application\BadRequestException`, após o qual `Application` passa o controle para o error-presenter. Que é um presenter cuja tarefa é exibir uma página informando sobre o erro ocorrido. A configuração do error-presenter é feita na [configuração da aplicação|configuration]. -Enviando o JSON .[#toc-sending-json] -==================================== +Envio de JSON +============= -Exemplo de método de ação que envia dados em formato JSON e sai do apresentador: +Exemplo de um método de ação que envia dados no formato JSON e encerra o presenter: ```php public function actionData(): void @@ -236,17 +235,43 @@ public function actionData(): void ``` -Parâmetros Persistentes .[#toc-persistent-parameters] -===================================================== +Parâmetros da requisição .{data-version:3.1.14} +=============================================== -Parâmetros persistentes são usados para manter o estado entre diferentes solicitações. Seu valor permanece o mesmo mesmo, mesmo depois que um link é clicado. Ao contrário dos dados da sessão, eles são passados na URL. Isto é completamente automático, portanto não há necessidade de declará-los explicitamente em `link()` ou `n:href`. +O presenter e também cada componente obtêm seus parâmetros da requisição HTTP. Você pode descobrir seu valor usando o método `getParameter($name)` ou `getParameters()`. Os valores são strings ou arrays de strings, são basicamente dados brutos obtidos diretamente da URL. -Exemplo de uso? Você tem uma aplicação multilíngüe. O idioma real é um parâmetro que precisa fazer parte da URL o tempo todo. Mas seria incrivelmente entediante incluí-lo em cada link. Portanto, você o torna um parâmetro persistente chamado `lang` e ele se carregará sozinho. Legal! +Para maior conveniência, recomendamos tornar os parâmetros acessíveis através de propriedades. Basta marcá-los com o atributo `#[Parameter]`: + +```php +use Nette\Application\Attributes\Parameter; // esta linha é importante + +class HomePresenter extends Nette\Application\UI\Presenter +{ + #[Parameter] + public string $theme; // deve ser público +} +``` + +Recomendamos especificar o tipo de dados para a propriedade (por exemplo, `string`) e o Nette converterá automaticamente o valor de acordo com ele. Os valores dos parâmetros também podem ser [validados |#Validação de parâmetros]. + +Ao criar um link, o valor dos parâmetros pode ser definido diretamente: + +```latte +clique +``` -Criar um parâmetro persistente é extremamente fácil em Nette. Basta criar uma propriedade pública e etiquetá-la com o atributo: (anteriormente foi utilizado `/** @persistent */` ) + +Parâmetros persistentes +======================= + +Parâmetros persistentes são usados para manter o estado entre diferentes requisições. Seu valor permanece o mesmo mesmo após clicar em um link. Ao contrário dos dados na sessão, eles são transmitidos na URL. E isso de forma totalmente automática, não sendo necessário especificá-los explicitamente em `link()` ou `n:href`. + +Exemplo de uso? Você tem uma aplicação multilíngue. O idioma atual é um parâmetro que deve estar constantemente presente na URL. Mas seria incrivelmente tedioso especificá-lo em cada link. Então você o transforma em um parâmetro persistente `lang` e ele será transmitido por si só. Ótimo! + +Criar um parâmetro persistente no Nette é extremamente simples. Basta criar uma propriedade pública e marcá-la com um atributo: (anteriormente usava-se `/** @persistent */`) ```php -use Nette\Application\Attributes\Persistent; // esta linha é importante +use Nette\Application\Attributes\Persistent; // esta linha é importante class ProductPresenter extends Nette\Application\UI\Presenter { @@ -255,14 +280,14 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -Se `$this->lang` tem um valor como `'en'`, então os links criados usando `link()` ou `n:href` também conterão o parâmetro `lang=en`. E quando o link for clicado, ele será novamente `$this->lang = 'en'`. +Se `$this->lang` tiver o valor, por exemplo, `'en'`, então os links criados usando `link()` ou `n:href` também conterão o parâmetro `lang=en`. E após clicar no link, novamente `$this->lang = 'en'`. -Para propriedades, recomendamos que você inclua o tipo de dados (por exemplo, `string`) e você também pode incluir um valor padrão. Os valores dos parâmetros podem ser [validados |#Validation of Persistent Parameters]. +Recomendamos especificar o tipo de dados para a propriedade (por exemplo, `string`) e você também pode especificar um valor padrão. Os valores dos parâmetros podem ser [validados |#Validação de parâmetros]. -Parâmetros persistentes são passados por padrão entre todas as ações de um determinado apresentador. Para passá-los entre vários apresentadores, você precisa defini-los também: +Parâmetros persistentes são normalmente transmitidos entre todas as ações de um determinado presenter. Para que sejam transmitidos também entre vários presenters, é necessário defini-los: -- em um ancestral comum do qual os apresentadores herdam -- no traço que os apresentadores usam: +- em um ancestral comum do qual os presenters herdam +- em uma trait que os presenters usam: ```php trait LanguageAware @@ -277,48 +302,42 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -Você pode alterar o valor de um parâmetro persistente ao criar um link: +Ao criar um link, o valor do parâmetro persistente pode ser alterado: ```latte -detail in Czech +detalhe em português ``` -Ou pode ser *reset*, ou seja, removido da URL. Então, ele tomará seu valor padrão: +Ou pode ser *resetado*, ou seja, removido da URL. Então ele assumirá seu valor padrão: ```latte -click +clique ``` -Componentes interativos .[#toc-interactive-components] -====================================================== +Componentes interativos +======================= -Os apresentadores têm um sistema de componentes incorporado. Os componentes são unidades reutilizáveis separadas que colocamos nos apresentadores. Eles podem ser [formas |forms:in-presenter], datagrids, menus, na verdade qualquer coisa que faça sentido usar repetidamente. +Presenters têm um sistema de componentes embutido. Componentes são unidades reutilizáveis independentes que inserimos nos presenters. Podem ser [formulários |forms:in-presenter], datagrids, menus, na verdade, qualquer coisa que faça sentido usar repetidamente. -Como os componentes são colocados e posteriormente utilizados no apresentador? Isto é explicado no capítulo [Componentes |Components]. Você descobrirá até mesmo o que eles têm a ver com Hollywood. +Como os componentes são inseridos no presenter e subsequentemente usados? Isso você aprenderá no capítulo [Componentes |components]. Você descobrirá até o que eles têm em comum com Hollywood. -Onde posso obter alguns componentes? Na página [Componente |https://componette.org] você pode encontrar alguns componentes de código aberto e outros addons para Nette que são feitos e compartilhados pela comunidade de Nette Framework. +E onde posso obter componentes? Na página [Componette |https://componette.org/search/component] você encontrará componentes open-source e também uma série de outros add-ons para Nette, que foram colocados lá por voluntários da comunidade em torno do framework. -Indo mais fundo .[#toc-going-deeper] -==================================== +Vamos aprofundar +================ .[tip] -O que mostramos até agora neste capítulo provavelmente será suficiente. As seguintes linhas destinam-se àqueles que estão interessados em apresentadores em profundidade e querem saber tudo. - +Com o que mostramos até agora neste capítulo, você provavelmente se sairá bem. As linhas a seguir são destinadas àqueles que estão interessados em presenters em profundidade e querem saber absolutamente tudo. -Requisitos e parâmetros .[#toc-requirement-and-parameters] ----------------------------------------------------------- -O pedido tratado pelo apresentador é o objeto [api:Nette\Application\Request] e é devolvido pelo método do apresentador `getRequest()`. Ele inclui um conjunto de parâmetros e cada um deles pertence a alguns dos componentes ou diretamente ao apresentador (que na verdade é também um componente, embora especial). Assim, Nette redistribui os parâmetros e passa entre os componentes individuais (e o apresentador), chamando o método `loadState(array $params)`. Os parâmetros podem ser obtidos pelo método `getParameters(): array`, individualmente usando `getParameter($name)`. Os valores dos parâmetros são strings ou matrizes de strings, são basicamente dados brutos obtidos diretamente de uma URL. +Validação de parâmetros +----------------------- +Os valores dos [#parâmetros da requisição] e [#parâmetros persistentes] recebidos da URL são escritos nas propriedades pelo método `loadState()`. Ele também verifica se o tipo de dados especificado na propriedade corresponde, caso contrário, responde com um erro 404 e a página não é exibida. -Validação de Parâmetros Persistentes .[#toc-validation-of-persistent-parameters] --------------------------------------------------------------------------------- - -Os valores de [parâmetros persistentes |#persistent parameters] recebidos de URLs são escritos nas propriedades pelo método `loadState()`. Ele também verifica se o tipo de dados especificado na propriedade corresponde, caso contrário, responderá com um erro 404 e a página não será exibida. - -Nunca confie cegamente em parâmetros persistentes, pois eles podem ser facilmente sobrescritos pelo usuário no URL. Por exemplo, é assim que verificamos se `$this->lang` está entre os idiomas suportados. Uma boa maneira de fazer isso é sobrescrever o método `loadState()` mencionado acima: +Nunca confie cegamente nos parâmetros, pois eles podem ser facilmente sobrescritos pelo usuário na URL. Assim, por exemplo, verificamos se o idioma `$this->lang` está entre os suportados. Uma maneira adequada é sobrescrever o método mencionado `loadState()`: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -328,9 +347,9 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function loadState(array $params): void { - parent::loadState($params); // aqui está definido o $this->lang - // segue a verificação do valor do usuário: - if (!in_array($this->lang, ['en', 'cs'])) { + parent::loadState($params); // aqui $this->lang é definido + // segue a verificação personalizada do valor: + if (!in_array($this->lang, ['en', 'pt'])) { $this->error(); } } @@ -338,31 +357,33 @@ class ProductPresenter extends Nette\Application\UI\Presenter ``` -Salvar e Restaurar o Pedido .[#toc-save-and-restore-the-request] ----------------------------------------------------------------- +Salvar e restaurar requisição +----------------------------- -Você pode salvar o pedido atual para uma sessão ou restaurá-lo da sessão e deixar o apresentador executá-lo novamente. Isto é útil, por exemplo, quando um usuário preenche um formulário e seu login expira. Para não perder dados, antes de redirecionar para a página de login, salvamos a solicitação atual para a sessão usando `$reqId = $this->storeRequest()`, que retorna um identificador na forma de uma seqüência curta e o passa como parâmetro para o apresentador de login. +A requisição que o presenter processa é um objeto [api:Nette\Application\Request] e é retornado pelo método do presenter `getRequest()`. -Após o login, chamamos o método `$this->restoreRequest($reqId)`, que capta o pedido da sessão e o encaminha para ele. O método verifica que a solicitação foi criada pelo mesmo usuário que está agora logado. Se outro usuário faz o login ou a chave é inválida, ele não faz nada e o programa continua. +A requisição atual pode ser salva na sessão ou, inversamente, restaurada dela e deixar o presenter executá-la novamente. Isso é útil, por exemplo, em uma situação em que o usuário está preenchendo um formulário e sua sessão expira. Para não perder os dados, antes de redirecionar para a página de login, salvamos a requisição atual na sessão usando `$reqId = $this->storeRequest()`, que retorna seu identificador na forma de uma string curta e o passamos como parâmetro para o presenter de login. -Veja o livro de receitas [Como voltar a uma página anterior |best-practices:restore-request]. +Após o login, chamamos o método `$this->restoreRequest($reqId)`, que recupera a requisição da sessão e encaminha para ela. O método verifica se a requisição foi criada pelo mesmo usuário que está logado agora. Se outro usuário fizer login ou a chave for inválida, ele não faz nada e o programa continua. +Veja o tutorial [Como retornar à página anterior |best-practices:restore-request]. -Canonização .[#toc-canonization] --------------------------------- -Os apresentadores têm uma característica realmente grande que melhora a SEO (otimização da capacidade de busca na Internet). Eles evitam automaticamente a existência de conteúdo duplicado em diferentes URLs. Se múltiplas URLs levam a um determinado destino, por exemplo, `/index` e `/index?page=1`, a estrutura designa uma delas como a principal (canônica) e redireciona as outras para ela usando o código HTTP 301. Graças a isso, os mecanismos de busca não indexam páginas duas vezes e não enfraquecem sua classificação de páginas. +Canonização +----------- -Este processo é chamado de canonização. A URL canônica é a URL gerada pelo [roteador |routing], geralmente a primeira rota apropriada na coleção. +Presenters têm uma característica realmente ótima que contribui para um melhor SEO (otimização para motores de busca). Eles impedem automaticamente a existência de conteúdo duplicado em URLs diferentes. Se houver várias URLs que levam ao mesmo destino, por exemplo, `/index` e `/index?page=1`, o framework determina uma delas como primária (canônica) e redireciona as outras para ela usando o código HTTP 301. Graças a isso, os motores de busca não indexam suas páginas duas vezes e não diluem seu page rank. -A canonização está ligada por padrão e pode ser desligada via `$this->autoCanonicalize = false`. +Este processo é chamado de canonização. A URL canônica é aquela gerada pelo [roteador|routing], geralmente a primeira rota correspondente na coleção. -O redirecionamento não ocorre com um pedido AJAX ou POST porque resultaria em perda de dados ou sem valor agregado SEO. +A canonização está ativada por padrão e pode ser desativada através de `$this->autoCanonicalize = false`. -Você também pode invocar a canonização manualmente usando o método `canonicalize()`, que, como o método `link()`, recebe o apresentador, ações e parâmetros como argumentos. Ele cria um link e o compara com a URL atual. Se for diferente, ele se redireciona para o link gerado. +O redirecionamento não ocorre durante uma requisição AJAX ou POST, pois isso causaria perda de dados ou não teria valor agregado do ponto de vista de SEO. + +Você também pode invocar a canonização manualmente usando o método `canonicalize()`, ao qual, de forma semelhante ao método `link()`, são passados o presenter, a ação e os parâmetros. Ele cria um link e o compara com a URL atual. Se diferirem, ele redireciona para o link gerado. ```php -public function actionShow(int $id, string $slug = null): void +public function actionShow(int $id, ?string $slug = null): void { $realSlug = $this->facade->getSlugForId($id); // redireciona se $slug for diferente de $realSlug @@ -371,10 +392,10 @@ public function actionShow(int $id, string $slug = null): void ``` -Eventos .[#toc-events] ----------------------- +Eventos +------- -Além dos métodos `startup()`, `beforeRender()` e `shutdown()`, que são chamados como parte do ciclo de vida do apresentador, outras funções podem ser definidas para serem chamadas automaticamente. O apresentador define os chamados [eventos |nette:glossary#events], e você adiciona seus manipuladores às arrays `$onStartup`, `$onRender` e `$onShutdown`. +Além dos métodos `startup()`, `beforeRender()` e `shutdown()`, que são chamados como parte do ciclo de vida do presenter, é possível definir outras funções que devem ser chamadas automaticamente. O presenter define os chamados [eventos |nette:glossary#Eventos], cujos manipuladores você adiciona aos arrays `$onStartup`, `$onRender` e `$onShutdown`. ```php class ArticlePresenter extends Nette\Application\UI\Presenter @@ -388,21 +409,21 @@ class ArticlePresenter extends Nette\Application\UI\Presenter } ``` -Os manipuladores na matriz `$onStartup` são chamados pouco antes do método `startup()`, depois `$onRender` entre `beforeRender()` e `render()` e finalmente `$onShutdown` pouco antes de `shutdown()`. +Os manipuladores no array `$onStartup` são chamados logo antes do método `startup()`, `$onRender` entre `beforeRender()` e `render()` e, finalmente, `$onShutdown` logo antes de `shutdown()`. -Respostas .[#toc-responses] ---------------------------- +Respostas +--------- -A resposta devolvida pelo apresentador é um objeto que implementa a interface [api:Nette\Application\Response]. Há uma série de respostas prontas: +A resposta que o presenter retorna é um objeto que implementa a interface [api:Nette\Application\Response]. Há uma série de respostas prontas disponíveis: -- [api:Nette\Application\Responses\CallbackResponse] - envia uma ligação de retorno -- [api:Nette\Application\Responses\FileResponse] - envia o arquivo -- [api:Nette\Application\Responses\ForwardResponse] - para frente () +- [api:Nette\Application\Responses\CallbackResponse] - envia um callback +- [api:Nette\Application\Responses\FileResponse] - envia um arquivo +- [api:Nette\Application\Responses\ForwardResponse] - forward() - [api:Nette\Application\Responses\JsonResponse] - envia JSON -- [api:Nette\Application\Responses\RedirectResponse] - redirecionar +- [api:Nette\Application\Responses\RedirectResponse] - redirecionamento - [api:Nette\Application\Responses\TextResponse] - envia texto -- [api:Nette\Application\Responses\VoidResponse] - resposta em branco +- [api:Nette\Application\Responses\VoidResponse] - resposta vazia As respostas são enviadas pelo método `sendResponse()`: @@ -410,25 +431,70 @@ As respostas são enviadas pelo método `sendResponse()`: use Nette\Application\Responses; // Texto simples -$this->sendResponse(new Responses\TextResponse('Hello Nette!')); +$this->sendResponse(new Responses\TextResponse('Olá Nette!')); // Envia um arquivo $this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf')); -// Envia uma ligação de retorno +// A resposta será um callback $callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse) { if ($httpResponse->getHeader('Content-Type') === 'text/html') { - echo '

    Hello

    '; + echo '

    Olá

    '; } }; $this->sendResponse(new Responses\CallbackResponse($callback)); ``` -Leitura adicional .[#toc-further-reading] -========================================= +Restrição de acesso usando `#[Requires]` .{data-version:3.2.2} +-------------------------------------------------------------- + +O atributo `#[Requires]` oferece opções avançadas para restringir o acesso a presenters e seus métodos. Pode ser usado para especificar métodos HTTP, exigir requisição AJAX, restringir à mesma origem (same origin) e acesso apenas via encaminhamento (forwarding). O atributo pode ser aplicado tanto a classes de presenters quanto a métodos individuais `action()`, `render()`, `handle()` e `createComponent()`. + +Você pode especificar estas restrições: +- em métodos HTTP: `#[Requires(methods: ['GET', 'POST'])]` +- exigir requisição AJAX: `#[Requires(ajax: true)]` +- acesso apenas da mesma origem: `#[Requires(sameOrigin: true)]` +- acesso apenas via forward: `#[Requires(forward: true)]` +- restrição a ações específicas: `#[Requires(actions: 'default')]` + +Detalhes podem ser encontrados no tutorial [Como usar o atributo Requires |best-practices:attribute-requires]. + + +Verificação do método HTTP +-------------------------- + +Presenters no Nette verificam automaticamente o método HTTP de cada requisição recebida. A razão para esta verificação é principalmente a segurança. Por padrão, os métodos `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH` são permitidos. + +Se você quiser permitir adicionalmente, por exemplo, o método `OPTIONS`, use o atributo `#[Requires]` (a partir do Nette Application v3.2): + +```php +#[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])] +class MyPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Na versão 3.1, a verificação é feita em `checkHttpMethod()`, que verifica se o método especificado na requisição está contido no array `$presenter->allowedMethods`. Adicione o método assim: + +```php +class MyPresenter extends Nette\Application\UI\Presenter +{ + protected function checkHttpMethod(): void + { + $this->allowedMethods[] = 'OPTIONS'; + parent::checkHttpMethod(); + } +} +``` + +É importante enfatizar que, se você permitir o método `OPTIONS`, deverá subsequentemente tratá-lo adequadamente dentro do seu presenter. O método é frequentemente usado como a chamada requisição preflight, que o navegador envia automaticamente antes da requisição real, quando é necessário verificar se a requisição é permitida do ponto de vista da política CORS (Cross-Origin Resource Sharing). Se você permitir o método, mas não implementar a resposta correta, isso pode levar a inconsistências e potenciais problemas de segurança. + + +Leitura adicional +================= -- [Métodos de injeção e atributos |best-practices:inject-method-attribute] -- [Composição dos apresentadores a partir de traços |best-practices:presenter-traits] -- [Passagem de configurações para os apresentadores |best-practices:passing-settings-to-presenters] -- [Como voltar a uma página anterior |best-practices:restore-request] +- [Métodos e atributos inject |best-practices:inject-method-attribute] +- [Compondo presenters a partir de traits |best-practices:presenter-traits] +- [Passando configurações para presenters |best-practices:passing-settings-to-presenters] +- [Como retornar à página anterior |best-practices:restore-request] diff --git a/application/pt/routing.texy b/application/pt/routing.texy index d9ad4462b0..6becfb0d3b 100644 --- a/application/pt/routing.texy +++ b/application/pt/routing.texy @@ -1,29 +1,28 @@ -Roteiro -******* +Roteamento +**********
    -O roteador é responsável por tudo sobre URLs, para que você não tenha mais que pensar nelas. Nós vamos mostrar: +O Roteador cuida de tudo relacionado aos endereços URL, para que você não precise mais pensar neles. Vamos mostrar: -- como configurar o roteador para que as URLs pareçam como você quer -- algumas notas sobre o redirecionamento de SEO -- e nós lhe mostraremos como escrever seu próprio roteador +- como configurar o roteador para que as URLs fiquem como desejado +- falaremos sobre SEO e redirecionamento +- e mostraremos como escrever seu próprio roteador
    -URLs mais humanas (ou mais legais ou bonitas) são mais utilizáveis, mais memoráveis e contribuem positivamente para SEO. Nette tem isto em mente e atende plenamente aos desejos dos desenvolvedores. Você pode projetar sua estrutura de URLs para sua aplicação exatamente do jeito que você quiser. -Você pode até projetá-la depois que o aplicativo estiver pronto, pois pode ser feita sem qualquer mudança de código ou modelo. Ela é definida de forma elegante em [um único lugar |#Integration], no roteador, e não está dispersa na forma de anotações em todos os apresentadores. +URLs mais amigáveis (ou também cool ou pretty URLs) são mais usáveis, memoráveis e contribuem positivamente para o SEO. O Nette pensa nisso e atende plenamente aos desenvolvedores. Você pode projetar para sua aplicação exatamente a estrutura de URLs que desejar. Você pode até projetá-la quando a aplicação já estiver pronta, pois isso pode ser feito sem intervenções no código ou nos templates. É definido de forma elegante em um [único local |#Integração na aplicação], no roteador, e não está espalhado na forma de anotações em todos os presenters. -O roteador em Nette é especial porque é **bidirecional**, ele pode tanto decodificar URLs de solicitação HTTP quanto criar links. Portanto, ele desempenha um papel vital na [Aplicação Nette |how-it-works#Nette Application], pois decide qual apresentador e ação executará a solicitação atual, e também é utilizado para [geração de URLs |creating-links] no modelo, etc. +O Roteador no Nette é extraordinário por ser **bidirecional.** Ele pode tanto decodificar URLs na requisição HTTP quanto criar links. Portanto, desempenha um papel crucial na [Nette Application |how-it-works#Nette Application], pois decide qual presenter e ação executará a requisição atual, mas também é usado para [gerar URLs |creating-links] no template, etc. -Entretanto, o roteador não está limitado a este uso, você pode usá-lo em aplicações onde os apresentadores não são usados, para APIs REST, etc. Mais na seção [uso separado |#separated usage]. +No entanto, o roteador não está limitado apenas a este uso, você pode usá-lo em aplicações onde presenters não são usados de forma alguma, para APIs REST, etc. Mais na seção [#Uso independente]. -Coleta de rotas .[#toc-route-collection] -======================================== +Coleção de rotas +================ -A maneira mais agradável de definir os endereços URL na aplicação é através da classe [api:Nette\Application\Routers\RouteList]. A definição consiste em uma lista das chamadas rotas, ou seja, máscaras de endereços URL e seus apresentadores associados e ações utilizando uma API simples. Não é necessário nomear as rotas. +A maneira mais agradável de definir a aparência das URLs na aplicação é oferecida pela classe [api:Nette\Application\Routers\RouteList]. A definição consiste em uma lista das chamadas rotas, ou seja, máscaras de URLs e seus presenters e ações associados por meio de uma API simples. Não precisamos nomear as rotas de forma alguma. ```php $router = new Nette\Application\Routers\RouteList; @@ -32,130 +31,130 @@ $router->addRoute('article/', 'Article:view'); // ... ``` -O exemplo diz que se abrirmos `https://any-domain.com/rss.xml` com a ação `rss` será exibido, se `https://domain.com/article/12` com a ação `view` será exibido, etc. Se nenhum caminho adequado for encontrado, a Aplicação Nette responde lançando uma exceção [BadRequestException |api:Nette\Application\BadRequestException], que aparece ao usuário como uma página de erro 404 Não encontrado. +O exemplo diz que se abrirmos `https://domain.com/rss.xml` no navegador, o presenter `Feed` com a ação `rss` será exibido, se `https://domain.com/article/12`, o presenter `Article` com a ação `view` será exibido, etc. No caso de não encontrar uma rota adequada, a Nette Application reage lançando a exceção [BadRequestException |api:Nette\Application\BadRequestException], que é exibida ao usuário como uma página de erro 404 Not Found. -Ordem das Rotas .[#toc-order-of-routes] ---------------------------------------- +Ordem das rotas +--------------- -A ordem na qual as rotas são listadas é **muito importante** porque são avaliadas seqüencialmente de cima para baixo. A regra é que declaramos as rotas **de específicas a gerais***: +A **ordem** em que as rotas individuais são listadas é **absolutamente crucial**, pois elas são avaliadas sequencialmente de cima para baixo. A regra é que declaramos as rotas **das mais específicas para as mais gerais**: ```php -// ERRADO: 'rss.xml' corresponde à primeira rota e entende isto mal como +// ERRADO: 'rss.xml' é capturado pela primeira rota e entende esta string como $router->addRoute('', 'Article:view'); $router->addRoute('rss.xml', 'Feed:rss'); -// BOM +// CORRETO $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('', 'Article:view'); ``` -As rotas também são avaliadas de cima para baixo quando os links são gerados: +As rotas também são avaliadas de cima para baixo ao gerar links: ```php -// ERRADO: gera um link para 'Feed:rss' como 'admin/feed/rss'. +// ERRADO: link para 'Feed:rss' gera como 'admin/feed/rss' $router->addRoute('admin//', 'Admin:default'); $router->addRoute('rss.xml', 'Feed:rss'); -// BOM +// CORRETO $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('admin//', 'Admin:default'); ``` -Não vamos esconder de você que é preciso alguma habilidade para construir uma lista corretamente. Até que você entre nela, o [painel de roteamento |#Debugging Router] será uma ferramenta útil. +Não esconderemos de você que a montagem correta das rotas requer alguma habilidade. Antes de dominá-la, o [painel de roteamento |#Depuração do roteador] será um auxiliar útil. -Máscara e Parâmetros .[#toc-mask-and-parameters] ------------------------------------------------- +Máscara e parâmetros +-------------------- -A máscara descreve o caminho relativo com base na raiz do local. A máscara mais simples é uma URL estática: +A máscara descreve o caminho relativo a partir do diretório raiz da web. A máscara mais simples é uma URL estática: ```php $router->addRoute('products', 'Products:default'); ``` -Muitas vezes as máscaras contêm os chamados **parâmetros***. Elas estão entre parênteses angulares (por exemplo ``) e são passadas ao apresentador alvo, por exemplo, ao método `renderShow(int $year)` ou ao parâmetro persistente `$year`: +Frequentemente, as máscaras contêm os chamados **parâmetros**. Eles são indicados entre colchetes angulares (por exemplo, ``) e são passados para o presenter de destino, por exemplo, para o método `renderShow(int $year)` ou para o parâmetro persistente `$year`: ```php $router->addRoute('chronicle/', 'History:show'); ``` -O exemplo diz que se abrirmos `https://any-domain.com/chronicle/2020` e a ação `show` com o parâmetro `year: 2020` serão exibidos. +O exemplo diz que se abrirmos `https://example.com/chronicle/2020` no navegador, o presenter `History` com a ação `show` e o parâmetro `year: 2020` será exibido. -Podemos especificar um valor padrão para os parâmetros diretamente na máscara e assim ele se torna opcional: +Podemos definir um valor padrão para os parâmetros diretamente na máscara, tornando-os opcionais: ```php $router->addRoute('chronicle/', 'History:show'); ``` -A rota agora aceitará a URL `https://any-domain.com/chronicle/` com o parâmetro `year: 2020`. +A rota agora também aceitará a URL `https://example.com/chronicle/`, que novamente exibirá `History:show` com o parâmetro `year: 2020`. -Naturalmente, o nome do apresentador e a ação também podem ser um parâmetro. Por exemplo, o nome do apresentador e a ação também podem ser um parâmetro: +O parâmetro também pode ser, obviamente, o nome do presenter e da ação. Por exemplo, assim: ```php $router->addRoute('/', 'Home:default'); ``` -Esta rota aceita, por exemplo, uma URL no formulário `/article/edit` resp. `/catalog/list` e as traduz para os apresentadores e ações `Article:edit` resp. `Catalog:list`. +A rota especificada aceita, por exemplo, URLs no formato `/article/edit` ou também `/catalog/list` e as entende como presenters e ações `Article:edit` e `Catalog:list`. -Também dá aos parâmetros `presenter` e `action` valores padrão`Home` e `default` e, portanto, são opcionais. Portanto, a rota também aceita uma URL `/article` e a traduz como `Article:default`. Ou vice versa, um link para `Product:default` gera um caminho `/product`, um link para o padrão `Home:default` gera um caminho `/`. +Ao mesmo tempo, ela atribui aos parâmetros `presenter` e `action` os valores padrão `Home` e `default`, tornando-os também opcionais. Portanto, a rota também aceita URLs no formato `/article` e a entende como `Article:default`. Ou vice-versa, um link para `Product:default` gerará o caminho `/product`, um link para o padrão `Home:default` o caminho `/`. -A máscara pode descrever não apenas o caminho relativo baseado na raiz do site, mas também o caminho absoluto quando ele começa com uma barra, ou mesmo todo o URL absoluto quando começa com duas barras: +A máscara pode descrever não apenas o caminho relativo a partir do diretório raiz da web, mas também o caminho absoluto, se começar com uma barra, ou até mesmo a URL absoluta inteira, se começar com duas barras: ```php -// caminho relativo à raiz do documento de aplicação +// relativo ao document root $router->addRoute('/', /* ... */); -// caminho absoluto, relativo ao nome da hostname do servidor +// caminho absoluto (relativo ao domínio) $router->addRoute('//', /* ... */); -// URL absoluto, incluindo o nome da hostname (mas esquema-relativo) +// URL absoluta incluindo domínio (relativa ao esquema) $router->addRoute('//.example.com//', /* ... */); -// URL absoluta, incluindo o esquema +// URL absoluta incluindo esquema $router->addRoute('https://.example.com//', /* ... */); ``` -Expressões de validação .[#toc-validation-expressions] ------------------------------------------------------- +Expressões de validação +----------------------- -Uma condição de validação pode ser especificada para cada parâmetro usando [expressão regular |https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Por exemplo, vamos definir `id` para ser apenas numérico, usando `\d+` regexp: +Para cada parâmetro, pode-se estabelecer uma condição de validação usando uma [expressão regular|https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Por exemplo, para o parâmetro `id`, determinamos que ele só pode conter dígitos usando a regex `\d+`: ```php $router->addRoute('/[/]', /* ... */); ``` -A expressão regular padrão para todos os parâmetros é `[^/]+`ou seja, tudo, exceto a barra. Se um parâmetro deve corresponder a uma barra também, definimos a expressão regular para `.+`. +A expressão regular padrão para todos os parâmetros é `[^/]+`, ou seja, tudo exceto a barra. Se um parâmetro precisar aceitar também barras, especificamos a expressão `.+`: ```php -// aceita https://example.com/a/b/c, o caminho é 'a/b/c'. +// aceita https://example.com/a/b/c, path será 'a/b/c' $router->addRoute('', /* ... */); ``` -Seqüências Opcionais .[#toc-optional-sequences] ------------------------------------------------ +Sequências opcionais +-------------------- -Colchetes quadrados denotam partes opcionais da máscara. Qualquer parte da máscara pode ser definida como opcional, incluindo aquelas que contenham parâmetros: +Na máscara, partes opcionais podem ser marcadas usando colchetes. Qualquer parte da máscara pode ser opcional, e elas também podem conter parâmetros: ```php $router->addRoute('[/]', /* ... */); -// URLs aceitas: Parâmetros: -// /en/download lang => en, name => download -// /download lang => null, name => download +// Aceita caminhos: +// /pt/download => lang => pt, name => download +// /download => lang => null, name => download ``` -Naturalmente, quando um parâmetro faz parte de uma seqüência opcional, ele também se torna opcional. Se não tiver um valor padrão, ele será nulo. +Quando um parâmetro faz parte de uma sequência opcional, ele obviamente também se torna opcional. Se não tiver um valor padrão especificado, será null. -As seções opcionais também podem estar no domínio: +Partes opcionais também podem estar no domínio: ```php $router->addRoute('//[.]example.com//', /* ... */); ``` -As seqüências podem ser livremente aninhadas e combinadas: +As sequências podem ser aninhadas e combinadas livremente: ```php $router->addRoute( @@ -163,48 +162,48 @@ $router->addRoute( 'Home:default', ); -// URLs aceitas: -// /en/hello -// /en-us/hello -// /hello -// /hello/page-12 +// Aceita caminhos: +// /pt/ola +// /en-us/ola +// /ola +// /ola/page-12 ``` -O gerador de URL tenta manter a URL o mais curta possível, de modo que o que pode ser omitido é omitido. Portanto, por exemplo, uma rota `index[.html]` gera um caminho `/index`. Você pode reverter este comportamento escrevendo um ponto de exclamação após o colchete à esquerda: +Ao gerar URLs, busca-se a variante mais curta, então tudo que pode ser omitido, é omitido. Por isso, por exemplo, a rota `index[.html]` gera o caminho `/index`. É possível reverter o comportamento especificando um ponto de exclamação após o colchete esquerdo: ```php -// aceita /hello e /hello.html, gera /hello +// aceita /ola e /ola.html, gera /ola $router->addRoute('[.html]', /* ... */); -// aceita tanto /hello como /hello.html, gera /hello.html +// aceita /ola e /ola.html, gera /ola.html $router->addRoute('[!.html]', /* ... */); ``` -Os parâmetros opcionais (ou seja, parâmetros com valor padrão) sem parênteses rectos comportam-se como se fossem embrulhados desta forma: +Parâmetros opcionais (ou seja, parâmetros com valor padrão) sem colchetes se comportam basicamente como se estivessem entre colchetes da seguinte forma: ```php $router->addRoute('//', /* ... */); -// é igual a: +// corresponde a isto: $router->addRoute('[/[/[]]]', /* ... */); ``` -Para mudar a forma como a barra mais à direita é gerada, ou seja, ao invés de `/home/`, obtenha um `/home`, ajuste a rota desta forma: +Se quiséssemos influenciar o comportamento da barra final, para que, por exemplo, em vez de `/home/` fosse gerado apenas `/home`, poderíamos fazer isso assim: ```php $router->addRoute('[[/[/]]]', /* ... */); ``` -Wildcards .[#toc-wildcards] ---------------------------- +Caracteres curinga +------------------ -Na máscara do caminho absoluto, podemos usar os seguintes wildcards para evitar, por exemplo, a necessidade de escrever um domínio para a máscara, que pode diferir no ambiente de desenvolvimento e produção: +Na máscara de caminho absoluto, podemos usar os seguintes caracteres curinga para evitar, por exemplo, a necessidade de escrever o domínio na máscara, que pode diferir entre os ambientes de desenvolvimento e produção: -- `%tld%` = domínio de primeiro nível, por exemplo `com` ou `org` -- `%sld%` = domínio de segundo nível, por exemplo `example` -- `%domain%` = domínio sem subdomínios, p. ex. `example.com` -- `%host%` = host inteiro, p.ex. `www.example.com` +- `%tld%` = top level domain, por exemplo, `com` ou `org` +- `%sld%` = second level domain, por exemplo, `example` +- `%domain%` = domínio sem subdomínios, por exemplo, `example.com` +- `%host%` = host completo, por exemplo, `www.example.com` - `%basePath%` = caminho para o diretório raiz ```php @@ -213,10 +212,10 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ @@ -225,7 +224,7 @@ $router->addRoute('/[/]', [ ]); ``` -Ou podemos usar este formulário, observando a reescrita da expressão regular de validação: +Para uma especificação mais detalhada, pode-se usar uma forma ainda mais estendida, onde, além dos valores padrão, podemos definir outras propriedades dos parâmetros, como uma expressão regular de validação (veja o parâmetro `id`): ```php use Nette\Routing\Route; @@ -243,19 +242,19 @@ $router->addRoute('/[/]', [ ]); ``` -Estes formatos mais faladores são úteis para adicionar outros metadados. +É importante notar que se os parâmetros definidos no array não estiverem listados na máscara do caminho, seus valores não podem ser alterados, nem mesmo usando parâmetros de consulta especificados após o ponto de interrogação na URL. -Filtros e Traduções .[#toc-filters-and-translations] ----------------------------------------------------- +Filtros e traduções +------------------- -É uma boa prática escrever o código fonte em inglês, mas e se você precisar que seu website tenha o URL traduzido para outro idioma? Rotas simples, como por exemplo: +Escrevemos o código-fonte da aplicação em inglês, mas se o site precisar ter URLs em português, então um roteamento simples do tipo: ```php $router->addRoute('/', 'Home:default'); ``` -gerará URLs em inglês, tais como `/product/123` ou `/cart`. Se quisermos ter apresentadores e ações na URL traduzidas para o Deutsch (por exemplo `/produkt/123` ou `/einkaufswagen`), podemos usar um dicionário de tradução. Para adicioná-lo, já precisamos de uma variante "mais faladora" do segundo parâmetro: +gerará URLs em inglês, como `/product/123` ou `/cart`. Se quisermos ter presenters e ações na URL representados por palavras em português (por exemplo, `/produto/123` ou `/carrinho`), podemos usar um dicionário de tradução. Para escrevê-lo, já precisamos da variante "mais verbosa" do segundo parâmetro: ```php use Nette\Routing\Route; @@ -264,26 +263,26 @@ $router->addRoute('/', [ 'presenter' => [ Route::Value => 'Home', Route::FilterTable => [ - // string na URL => apresentador - 'produkt' => 'Product', - 'einkaufswagen' => 'Cart', - 'katalog' => 'Catalog', + // string na URL => presenter + 'produto' => 'Product', + 'carrinho' => 'Cart', + 'catalogo' => 'Catalog', ], ], 'action' => [ Route::Value => 'default', Route::FilterTable => [ - 'liste' => 'list', + 'lista' => 'list', ], ], ]); ``` -As chaves de múltiplos dicionários podem ser usadas para o mesmo apresentador. Elas criarão vários pseudônimos para ele. A última chave é considerada como a variante canônica (ou seja, aquela que estará na URL gerada). +Várias chaves do dicionário de tradução podem levar ao mesmo presenter. Assim, diferentes aliases são criados para ele. A variante canônica (ou seja, aquela que estará na URL gerada) é considerada a última chave. -A tabela de tradução pode ser aplicada a qualquer parâmetro desta forma. Entretanto, se a tradução não existir, é tomado o valor original. Podemos mudar este comportamento adicionando `Route::FilterStrict => true` e a rota rejeitará a URL se o valor não estiver no dicionário. +A tabela de tradução pode ser usada desta forma para qualquer parâmetro. Se a tradução não existir, o valor original é usado. Podemos alterar esse comportamento adicionando `Route::FilterStrict => true`, e a rota então rejeitará a URL se o valor não estiver no dicionário. -Além do dicionário de tradução na forma de um array, é possível definir funções de tradução próprias: +Além do dicionário de tradução na forma de array, também é possível aplicar funções de tradução personalizadas. ```php use Nette\Routing\Route; @@ -299,15 +298,15 @@ $router->addRoute('//', [ ]); ``` -A função `Route::FilterIn` converte entre o parâmetro na URL e a string, que é então passada para o apresentador, a função `FilterOut` assegura a conversão na direção oposta. +A função `Route::FilterIn` converte entre o parâmetro na URL e a string que é então passada para o presenter, a função `FilterOut` garante a conversão na direção oposta. -Os parâmetros `presenter`, `action` e `module` já possuem filtros predefinidos que convertem entre o PascalCase resp. estilo camelCase e a caixa kebab utilizada na URL. O valor padrão dos parâmetros já está escrito na forma transformada, assim, por exemplo, no caso de um apresentador, nós escrevemos `` em vez de ``. +Os parâmetros `presenter`, `action` e `module` já possuem filtros predefinidos que convertem entre o estilo PascalCase ou camelCase e o kebab-case usado na URL. O valor padrão dos parâmetros já é escrito na forma transformada, então, por exemplo, no caso do presenter, escrevemos ``, não ``. -Filtros gerais .[#toc-general-filters] --------------------------------------- +Filtros gerais +-------------- -Além dos filtros para parâmetros específicos, você também pode definir filtros gerais que recebem uma matriz associativa de todos os parâmetros que podem modificar de qualquer forma e depois retornar. Os filtros gerais são definidos sob a tecla `null`. +Além dos filtros destinados a parâmetros específicos, também podemos definir filtros gerais que recebem um array associativo de todos os parâmetros, que podem modificar de qualquer forma e depois retorná-los. Definimos filtros gerais sob a chave `null`. ```php use Nette\Routing\Route; @@ -315,62 +314,85 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => 'Home', 'action' => 'default', - null => [ + '' => [ Route::FilterIn => function (array $params): array { /* ... */ }, Route::FilterOut => function (array $params): array { /* ... */ }, ], ]); ``` -Os filtros gerais lhe dão a capacidade de ajustar o comportamento da rota de absolutamente qualquer maneira. Podemos usá-los, por exemplo, para modificar parâmetros com base em outros parâmetros. Por exemplo, a tradução `` e `` com base no valor atual do parâmetro ``. +Filtros gerais oferecem a possibilidade de ajustar o comportamento da rota de absolutamente qualquer maneira. Podemos usá-los, por exemplo, para modificar parâmetros com base em outros parâmetros. Por exemplo, traduzir `` e `` com base no valor atual do parâmetro ``. -Se um parâmetro tem um filtro personalizado definido e um filtro geral existe ao mesmo tempo, o personalizado `FilterIn` é executado antes do geral e vice-versa geral `FilterOut` é executado antes do personalizado. Assim, dentro do filtro geral estão os valores dos parâmetros `presenter` resp. `action` escritos em estilo PascalCase resp. camelCase. +Se um parâmetro tiver um filtro próprio definido e, ao mesmo tempo, existir um filtro geral, o `FilterIn` próprio será executado antes do geral e, inversamente, o `FilterOut` geral antes do próprio. Ou seja, dentro do filtro geral, os valores dos parâmetros `presenter` ou `action` estão escritos no estilo PascalCase ou camelCase. -Bandeira OneWay .[#toc-oneway-flag] ------------------------------------ +Rotas de sentido único (OneWay) +------------------------------- -Rotas unidirecionais são usadas para preservar a funcionalidade de URLs antigas que a aplicação não gera mais, mas ainda assim aceita. Nós as sinalizamos com `OneWay`: +Rotas de sentido único são usadas para preservar a funcionalidade de URLs antigas que a aplicação não gera mais, mas ainda aceita. Nós as marcamos com o sinalizador `OneWay`: ```php -// URL antigo /product-info?id=123 +// URL antiga /product-info?id=123 $router->addRoute('product-info', 'Product:detail', $router::ONE_WAY); -// novo URL /produto/123 +// nova URL /product/123 $router->addRoute('product/', 'Product:detail'); ``` -Ao acessar a antiga URL, o apresentador redireciona automaticamente para a nova URL para que os mecanismos de busca não indexem essas páginas duas vezes (ver [SEO e canonização |#SEO and canonization]). +Ao acessar a URL antiga, o presenter redireciona automaticamente para a nova URL, para que os motores de busca não indexem essas páginas duas vezes (veja [#SEO e canonização]). -Módulos .[#toc-modules] ------------------------ +Roteamento dinâmico com callbacks +--------------------------------- + +O roteamento dinâmico com callbacks permite atribuir diretamente funções (callbacks) às rotas, que são executadas quando o caminho correspondente é visitado. Esta funcionalidade flexível permite criar rápida e eficientemente vários endpoints para a sua aplicação: + +```php +$router->addRoute('test', function () { + echo 'você está no endereço /test'; +}); +``` + +Você também pode definir parâmetros na máscara, que são passados automaticamente para o seu callback: -Se temos mais rotas que pertencem a um [módulo |modules], podemos usar `withModule()` para agrupá-las: +```php +$router->addRoute('', function (string $lang) { + echo match ($lang) { + 'pt' => 'Bem-vindo à versão em português do nosso site!', + 'en' => 'Welcome to the English version of our website!', + }; +}); +``` + + +Módulos +------- + +Se tivermos várias rotas que pertencem a um [módulo |directory-structure#Presenters e templates] comum, usamos `withModule()`: ```php $router = new RouteList; -$router->withModule('Forum') // os seguintes roteadores fazem parte do módulo Forum - ->addRoute('rss', 'Feed:rss') // apresentador é Forum:Feed +$router->withModule('Forum') // as rotas seguintes fazem parte do módulo Forum + ->addRoute('rss', 'Feed:rss') // o presenter será Forum:Feed ->addRoute('/') - ->withModule('Admin') // os seguintes roteadores fazem parte do módulo Forum:Admin + ->withModule('Admin') // as rotas seguintes fazem parte do módulo Forum:Admin ->addRoute('sign:in', 'Sign:in'); ``` Uma alternativa é usar o parâmetro `module`: ```php -// Gestão de URLs/ mapas de default para o apresentador Admin:Dashboard +// URL manage/dashboard/default mapeia para o presenter Admin:Dashboard $router->addRoute('manage//', [ 'module' => 'Admin', ]); ``` -Subdomínios .[#toc-subdomains] ------------------------------- +Subdomínios +----------- -As coleções de rotas podem ser agrupadas por subdomínios: +Podemos agrupar coleções de rotas por subdomínios: ```php $router = new RouteList; @@ -379,7 +401,7 @@ $router->withDomain('example.com') ->addRoute('/'); ``` -Você também pode usar [wildcards |#wildcards] em seu nome de domínio: +No nome do domínio, também é possível usar [#Caracteres curinga]: ```php $router = new RouteList; @@ -388,23 +410,23 @@ $router->withDomain('example.%tld%') ``` -Prefixo do caminho .[#toc-path-prefix] --------------------------------------- +Prefixo de caminho +------------------ -As coleções de rotas podem ser agrupadas por caminho na URL: +Podemos agrupar coleções de rotas pelo caminho na URL: ```php $router = new RouteList; -$router->withPath('eshop') - ->addRoute('rss', 'Feed:rss') // combina URL /eshop/rss - ->addRoute('/'); // combina URL /eshop// +$router->withPath('loja') + ->addRoute('rss', 'Feed:rss') // captura URL /loja/rss + ->addRoute('/'); // captura URL /loja// ``` -Combinações .[#toc-combinations] --------------------------------- +Combinações +----------- -O uso acima pode ser combinado: +Podemos combinar as agrupações acima: ```php $router = (new RouteList) @@ -424,40 +446,40 @@ $router = (new RouteList) ``` -Parâmetros de consulta .[#toc-query-parameters] ------------------------------------------------ +Parâmetros de consulta (Query) +------------------------------ -As máscaras também podem conter parâmetros de consulta (parâmetros após o ponto de interrogação na URL). Elas não podem definir uma expressão de validação, mas podem alterar o nome sob o qual são passadas ao apresentador: +As máscaras também podem conter parâmetros de consulta (parâmetros após o ponto de interrogação na URL). Não é possível definir uma expressão de validação para eles, mas pode-se alterar o nome sob o qual são passados para o presenter: ```php -// usar o parâmetro de consulta 'cat' como uma 'categoriaId' na aplicação +// queremos usar o parâmetro de consulta 'cat' na aplicação com o nome 'categoryId' $router->addRoute('product ? id= & cat=', /* ... */); ``` -Parâmetros Foo .[#toc-foo-parameters] -------------------------------------- +Parâmetros Foo +-------------- -Estamos indo mais fundo agora. Os parâmetros Foo são basicamente parâmetros anônimos que permitem corresponder a uma expressão regular. O seguinte roteiro corresponde a `/index`, `/index.html`, `/index.htm` e `/index.php`: +Agora estamos indo mais a fundo. Parâmetros Foo são basicamente parâmetros sem nome que permitem corresponder a uma expressão regular. Um exemplo é uma rota que aceita `/index`, `/index.html`, `/index.htm` e `/index.php`: ```php $router->addRoute('index', /* ... */); ``` -Também é possível definir explicitamente uma cadeia que será usada para a geração de URLs. A cadeia deve ser colocada diretamente após o ponto de interrogação. A seguinte rota é semelhante à anterior, mas gera `/index.html` ao invés de `/index` porque a string `.html` é definida como um "valor gerado". +Também é possível definir explicitamente a string que será usada ao gerar a URL. A string deve ser colocada diretamente após o ponto de interrogação. A seguinte rota é semelhante à anterior, mas gera `/index.html` em vez de `/index`, porque a string `.html` está definida como o valor de geração: ```php $router->addRoute('index', /* ... */); ``` -Integração .[#toc-integration] -============================== +Integração na aplicação +======================= -A fim de conectar nosso roteador à aplicação, devemos informar o recipiente DI sobre isso. A maneira mais fácil é preparar a fábrica que irá construir o objeto roteador e dizer à configuração do contêiner para usá-lo. Portanto, digamos que escrevemos um método para este fim `App\Router\RouterFactory::createRouter()`: +Para integrar o roteador criado na aplicação, precisamos informar o Contêiner de DI sobre ele. O caminho mais fácil é preparar uma fábrica que produzirá o objeto roteador e informar na configuração do contêiner que ele deve usá-la. Digamos que, para esse fim, escrevamos o método `App\Core\RouterFactory::createRouter()`: ```php -namespace App\Router; +namespace App\Core; use Nette\Application\Routers\RouteList; @@ -472,14 +494,14 @@ class RouterFactory } ``` -Em seguida, escrevemos em [configuração |dependency-injection:services]: +Na [configuração |dependency-injection:services], então escrevemos: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter ``` -Quaisquer dependências, tais como uma conexão de banco de dados, etc., são passadas para o método de fábrica como seus parâmetros usando [a fiação automática |dependency-injection:autowiring]: +Quaisquer dependências, como banco de dados, etc., são passadas para o método de fábrica como seus parâmetros usando [autowiring|dependency-injection:autowiring]: ```php public static function createRouter(Nette\Database\Connection $db): RouteList @@ -489,21 +511,21 @@ public static function createRouter(Nette\Database\Connection $db): RouteList ``` -SimpleRouter .[#toc-simplerouter] -================================= +SimpleRouter +============ -Um roteador muito mais simples do que a coleta de rotas é o [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Ele pode ser usado quando não há necessidade de um formato URL específico, quando `mod_rewrite` (ou alternativas) não está disponível ou quando simplesmente não queremos nos preocupar com URLs de fácil utilização ainda. +Um roteador muito mais simples do que a coleção de rotas é o [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Usamo-lo quando não temos requisitos especiais para a forma da URL, se `mod_rewrite` (ou suas alternativas) não estiver disponível, ou se ainda não quisermos lidar com URLs bonitas. -Gera endereços de forma aproximada: +Ele gera endereços aproximadamente neste formato: ``` http://example.com/?presenter=Product&action=detail&id=123 ``` -O parâmetro do construtor `SimpleRouter` é um apresentador e ação padrão, ou seja, ação a ser executada se abrirmos, por exemplo, `http://example.com/` sem parâmetros adicionais. +O parâmetro do construtor SimpleRouter é o presenter & ação padrão para o qual deve ser direcionado se abrirmos a página sem parâmetros, por exemplo, `http://example.com/`. ```php -// default para o apresentador 'Home' e ação 'default +// o presenter padrão será 'Home' e a ação 'default' $router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` @@ -515,22 +537,22 @@ services: ``` -SEO e Canonização .[#toc-seo-and-canonization] -============================================== +SEO e canonização +================= -A estrutura aumenta o SEO (Search Engine Optimization) ao evitar a duplicação de conteúdo em diferentes URLs. Se vários endereços forem ligados a um mesmo destino, por exemplo `/index` e `/index.html`, a estrutura determina o primeiro como primário (canônico) e redireciona os outros para ele usando o código HTTP 301. Graças a isso, os mecanismos de busca não indexarão páginas duas vezes e não quebrarão sua classificação de páginas. . +O framework contribui para o SEO (otimização para motores de busca) ao impedir a duplicação de conteúdo em URLs diferentes. Se houver vários endereços que levam ao mesmo destino, por exemplo, `/index` e `/index.html`, o framework determina o primeiro deles como primário (canônico) e redireciona os outros para ele usando o código HTTP 301. Graças a isso, os motores de busca não indexam suas páginas duas vezes e não diluem seu page rank. -Este processo é chamado de canonização. A URL canônica é aquela gerada pelo roteador, ou seja, pela primeira rota de correspondência na [coleta |#route-collection] sem a bandeira OneWay. Portanto, na coleção, listamos primeiro **caminhos primários**. +Este processo é chamado de canonização. A URL canônica é aquela gerada pelo roteador, ou seja, a primeira rota correspondente na coleção sem o sinalizador OneWay. Por isso, na coleção, listamos as **rotas primárias primeiro**. -A canonização é realizada pelo apresentador, mais no capítulo [canonização |presenters#Canonization]. +A canonização é realizada pelo presenter, mais no capítulo [canonização |presenters#Canonização]. -HTTPS .[#toc-https] -=================== +HTTPS +===== -Para utilizar o protocolo HTTPS, é necessário ativá-lo na hospedagem e configurar o servidor. +Para usar o protocolo HTTPS, é necessário habilitá-lo na hospedagem e configurar corretamente o servidor. -O redirecionamento de todo o site para HTTPS deve ser feito no nível do servidor, por exemplo, usando o arquivo .htaccess no diretório raiz de nossa aplicação, com o código HTTP 301. As configurações podem ser diferentes dependendo da hospedagem e se parece com isto: +O redirecionamento de todo o site para HTTPS deve ser configurado no nível do servidor, por exemplo, usando o arquivo .htaccess no diretório raiz da nossa aplicação, com o código HTTP 301. A configuração pode variar dependendo da hospedagem e se parece aproximadamente com isto: ``` @@ -542,40 +564,40 @@ O redirecionamento de todo o site para HTTPS deve ser feito no nível do servido ``` -O roteador gera uma URL com o mesmo protocolo que a página foi carregada, portanto, não há necessidade de definir mais nada. +O roteador gera URLs com o mesmo protocolo com que a página foi carregada, então nada mais precisa ser configurado. -Entretanto, se excepcionalmente precisarmos de rotas diferentes para operar sob protocolos diferentes, a colocaremos na máscara da rota: +No entanto, se excepcionalmente precisarmos que rotas diferentes sejam executadas sob protocolos diferentes, especificamos isso na máscara da rota: ```php -// Irá gerar um endereço HTTP +// Gerará endereço com HTTP $router->addRoute('http://%host%//', /* ... */); -// Irá gerar um endereço HTTPS +// Gerará endereço com HTTPS $router->addRoute('https://%host%//', /* ... */); ``` -Roteador de depuração .[#toc-debugging-router] -============================================== +Depuração do roteador +===================== -A barra de roteamento exibida na [Tracy Bar |tracy:] é uma ferramenta útil que exibe uma lista de rotas e também os parâmetros que o roteador obteve da URL. +O painel de roteamento exibido na [Barra Tracy |tracy:] é um auxiliar útil que exibe a lista de rotas e também os parâmetros que o roteador obteve da URL. -A barra verde com o símbolo ✓ representa a rota que corresponde à URL atual, as barras azuis com símbolos ≈ indicam as rotas que também corresponderiam à URL se o verde não as ultrapassasse. Vemos o apresentador e a ação atual mais adiante. +A barra verde com o símbolo ✓ representa a rota que processou a URL atual, a cor azul e o símbolo ≈ indicam rotas que também processariam a URL se a verde não as tivesse precedido. Em seguida, vemos o presenter & ação atuais. [* routing-debugger.webp *] -Ao mesmo tempo, se houver um redirecionamento inesperado devido à [canonicalização |#SEO and Canonization], é útil olhar na barra *redireto* para ver como o roteador entendeu originalmente a URL e porque ela foi redirecionada. +Ao mesmo tempo, se ocorrer um redirecionamento inesperado devido à [canonização |#SEO e canonização], é útil olhar para o painel na barra *redirect*, onde você descobrirá como o roteador entendeu originalmente a URL e por que redirecionou. .[note] -Ao depurar o roteador, recomenda-se abrir as Ferramentas do Desenvolvedor no navegador (Ctrl+Shift+I ou Cmd+Option+I) e desabilitar o cache no painel de Rede para que os redirecionamentos não sejam armazenados nele. +Ao depurar o roteador, recomendamos abrir as Ferramentas do Desenvolvedor no navegador (Ctrl+Shift+I ou Cmd+Option+I) e desativar o cache no painel Network, para que os redirecionamentos não sejam armazenados nele. -Desempenho .[#toc-performance] -============================== +Desempenho +========== -O número de rotas afeta a velocidade do roteador. Seu número não deve certamente exceder algumas dúzias. Se seu site tem uma estrutura URL excessivamente complicada, você pode escrever um [roteador personalizado |#custom router]. +O número de rotas afeta a velocidade do roteador. Seu número definitivamente não deve exceder algumas dezenas. Se o seu site tiver uma estrutura de URL muito complicada, você pode escrever um [#Roteador personalizado] personalizado. -Se o roteador não tem dependências, como em um banco de dados, e sua fábrica não tem argumentos, podemos serializar sua forma compilada diretamente em um container DI e assim tornar a aplicação um pouco mais rápida. +Se o roteador não tiver dependências, por exemplo, no banco de dados, e sua fábrica não aceitar argumentos, podemos serializar sua forma compilada diretamente no Contêiner de DI e, assim, acelerar ligeiramente a aplicação. ```neon routing: @@ -583,10 +605,10 @@ routing: ``` -Roteador personalizado .[#toc-custom-router] -============================================ +Roteador personalizado +====================== -As linhas a seguir são destinadas a usuários muito avançados. Você pode criar seu próprio roteador e, naturalmente, adicioná-lo à sua coleção de rotas. O roteador é uma implementação da interface [api:Nette\Routing\Router] com dois métodos: +As linhas a seguir são destinadas a usuários muito avançados. Você pode criar seu próprio roteador e integrá-lo naturalmente à coleção de rotas. O Roteador é uma implementação da interface [api:Nette\Routing\Router] com dois métodos: ```php use Nette\Http\IRequest as HttpRequest; @@ -606,8 +628,7 @@ class MyRouter implements Nette\Routing\Router } ``` -O método `match` processa o atual [$httpRequest |http:request], do qual não apenas a URL, mas também cabeçalhos etc. podem ser recuperados, em uma matriz contendo o nome do apresentador e seus parâmetros. Se não puder processar o pedido, ele retorna nulo. -Ao processar o pedido, devemos retornar pelo menos o apresentador e a ação. O nome do apresentador é completo e inclui quaisquer módulos: +O método `match` processa a requisição atual [$httpRequest |http:request], da qual é possível obter não apenas a URL, mas também cabeçalhos, etc., em um array contendo o nome do presenter e seus parâmetros. Se não puder processar a requisição, retorna null. Ao processar a requisição, devemos retornar pelo menos o presenter e a ação. O nome do presenter é completo e contém também eventuais módulos: ```php [ @@ -616,9 +637,9 @@ Ao processar o pedido, devemos retornar pelo menos o apresentador e a ação. O ] ``` -O método `constructUrl`, por outro lado, gera uma URL absoluta a partir da matriz de parâmetros. Ele pode utilizar as informações do parâmetro `$refUrl`, que é a URL atual. +O método `constructUrl`, por outro lado, monta a URL absoluta final a partir do array de parâmetros. Para isso, pode usar informações do parâmetro [`$refUrl`|api:Nette\Http\UrlScript], que é a URL atual. -Para adicionar roteador personalizado à coleta de rotas, use `add()`: +Você o adiciona à coleção de rotas usando `add()`: ```php $router = new Nette\Application\Routers\RouteList; @@ -628,19 +649,19 @@ $router->addRoute(/* ... */); ``` -Uso separado .[#toc-separated-usage] -==================================== +Uso independente +================ -Por uso separado, entendemos o uso das capacidades do roteador em uma aplicação que não utiliza Nette Application e apresentadores. Quase tudo o que mostramos neste capítulo se aplica a ela, com as seguintes diferenças: +Por uso independente, entendemos a utilização das capacidades do roteador em uma aplicação que não utiliza Nette Application e presenters. Quase tudo o que mostramos neste capítulo se aplica a ele, com estas diferenças: -- para coletas de rotas usamos classe [api:Nette\Routing\RouteList] -- como uma classe de roteador simples [api:Nette\Routing\SimpleRouter] -- porque não há par `Presenter:action`, usamos [a notação avançada |#Advanced notation] +- para coleções de rotas, usamos a classe [api:Nette\Routing\RouteList] +- como simple router, a classe [api:Nette\Routing\SimpleRouter] +- como não existe o par `Presenter:action`, usamos a [#Notação estendida] -Assim, mais uma vez, criaremos um método que construirá um roteador, por exemplo: +Então, novamente, criamos um método que montará o roteador para nós, por exemplo: ```php -namespace App\Router; +namespace App\Core; use Nette\Routing\RouteList; @@ -661,35 +682,35 @@ class RouterFactory } ``` -Se você usar um recipiente DI, que recomendamos, adicione novamente o método à configuração e, em seguida, obtenha o roteador junto com a solicitação HTTP do recipiente: +Se você usa um Contêiner de DI, o que recomendamos, adicionamos novamente o método à configuração e, em seguida, obtemos o roteador juntamente com a requisição HTTP do contêiner: ```php $router = $container->getByType(Nette\Routing\Router::class); $httpRequest = $container->getByType(Nette\Http\IRequest::class); ``` -Ou criaremos objetos diretamente: +Ou fabricamos os objetos diretamente: ```php -$router = App\Router\RouterFactory::createRouter(); +$router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` -Agora temos que deixar o roteador trabalhar: +Agora resta apenas colocar o roteador para trabalhar: ```php $params = $router->match($httpRequest); if ($params === null) { - // não foi encontrada nenhuma rota correspondente, enviaremos um erro 404 + // não foi encontrada uma rota correspondente, enviamos erro 404 exit; } -// nós processamos os parâmetros recebidos +// processamos os parâmetros obtidos $controller = $params['controller']; // ... ``` -E vice-versa, usaremos o roteador para criar o link: +E, inversamente, usamos o roteador para montar um link: ```php $params = ['controller' => 'ArticleController', 'id' => 123]; diff --git a/application/pt/templates.texy b/application/pt/templates.texy index bf8e08a5ef..c5fb40d21d 100644 --- a/application/pt/templates.texy +++ b/application/pt/templates.texy @@ -1,16 +1,16 @@ -Modelos -******* +Templates +********* .[perex] -A Nette utiliza o sistema de modelos [Latte |latte:]. O Latte é usado porque é o sistema de modelo mais seguro para PHP e, ao mesmo tempo, o sistema mais intuitivo. Você não precisa aprender muito de novo, você só precisa conhecer PHP e algumas tags de Latte. +O Nette usa o sistema de templates [Latte |latte:]. Por um lado, porque é o sistema de templates mais seguro para PHP e, ao mesmo tempo, o sistema mais intuitivo. Você não precisa aprender muito de novo, basta o conhecimento de PHP e algumas tags. -É normal que a página seja completada a partir do modelo de layout + o modelo de ação. Isto é o que um modelo de layout pode parecer, observe os blocos `{block}` e a etiqueta `{include}`: +É comum que uma página seja composta por um template de layout + o template da ação específica. Assim pode parecer um template de layout, observe os blocos `{block}` e a tag `{include}`: ```latte - {block title}My App{/block} + {block title}Minha App{/block}
    ...
    @@ -20,61 +20,109 @@ A Nette utiliza o sistema de modelos [Latte |latte:]. O Latte é usado porque é ``` -E este poderia ser o modelo de ação: +E este será o template da ação: ```latte -{block title}Homepage{/block} +{block title}Página Inicial{/block} {block content} -

    Homepage

    +

    Página Inicial

    ... {/block} ``` -Ele define o bloco `content`, que é inserido no lugar de `{include content}` no layout, e também redefine o bloco `title`, que sobrescreve `{block title}` no layout. Tente imaginar o resultado. +Ele define o bloco `content`, que será inserido no lugar de `{include content}` no layout, e também re-define o bloco `title`, que sobrescreverá `{block title}` no layout. Tente imaginar o resultado. -Busca de modelos .[#toc-search-for-templates] ---------------------------------------------- +Procurando templates +-------------------- -O caminho para os modelos é deduzido de acordo com uma lógica simples. Ele tenta ver se um destes arquivos de gabaritos existe em relação ao diretório onde se encontra a classe apresentadora, onde `` é o nome do atual apresentador e `` é o nome da ação atual: +Você não precisa especificar nos presenters qual template deve ser renderizado, o framework deduzirá o caminho por si só e economizará sua digitação. -- `templates//.latte` -- `templates/..latte` +Se você usa uma estrutura de diretórios onde cada presenter tem seu próprio diretório, simplesmente coloque o template neste diretório com o nome da ação (ou view), ou seja, para a ação `default`, use o template `default.latte`: -Se não encontrar o modelo, a resposta é [erro 404 |presenters#Error 404 etc.]. +/--pre +app/ +└── Presentation/ + └── Home/ + ├── HomePresenter.php + └── default.latte +\-- -Você também pode mudar a visão usando `$this->setView('otherView')`. Ou, em vez de procurar, especifique diretamente o nome do arquivo modelo usando `$this->template->setFile('/path/to/template.latte')`. +Se você usa uma estrutura onde os presenters estão juntos em um diretório e os templates na pasta `templates`, salve-o no arquivo `..latte` ou `/.latte`: + +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── Home.default.latte ← 1ª variante + └── Home/ + └── default.latte ← 2ª variante +\-- + +O diretório `templates` também pode estar um nível acima, ou seja, no mesmo nível do diretório com as classes dos presenters. + +Se o template não for encontrado, o presenter responderá com um [erro 404 - página não encontrada |presenters#Erro 404 e cia]. + +A view é alterada usando `$this->setView('outraView')`. Também é possível especificar diretamente o arquivo de template usando `$this->template->setFile('/caminho/para/template.latte')`. .[note] -Você pode alterar os caminhos onde os modelos são pesquisados substituindo o método de [formataçãoTemplateFiles |api:Nette\Application\UI\Presenter::formatTemplateFiles()], que retorna um conjunto de possíveis caminhos de arquivos. +Os arquivos onde os templates são procurados podem ser alterados sobrescrevendo o método [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], que retorna um array de possíveis nomes de arquivos. + + +Procurando o template de layout +------------------------------- + +O Nette também procura automaticamente o arquivo de layout. + +Se você usa uma estrutura de diretórios onde cada presenter tem seu próprio diretório, coloque o layout ou na pasta com o presenter, se for específico apenas para ele, ou um nível acima, se for comum a vários presenters: + +/--pre +app/ +└── Presentation/ + ├── @layout.latte ← layout comum + └── Home/ + ├── @layout.latte ← apenas para o presenter Home + ├── HomePresenter.php + └── default.latte +\-- + +Se você usa uma estrutura onde os presenters estão juntos em um diretório e os templates na pasta `templates`, o layout será esperado nestes locais: -O layout é esperado nos seguintes arquivos: +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── @layout.latte ← layout comum + ├── Home.@layout.latte ← apenas para Home, 1ª variante + └── Home/ + └── @layout.latte ← apenas para Home, 2ª variante +\-- -- `templates//@.latte` -- `templates/.@.latte` -- `templates/@.latte` layout comum a vários apresentadores +Se o presenter estiver em um módulo, a busca também ocorrerá em níveis de diretório superiores, de acordo com o aninhamento do módulo. -`` é o nome do atual apresentador e `` é o nome do layout, que é, por padrão, `'layout'`. O nome pode ser alterado com `$this->setLayout('otherLayout')`, para que os arquivos `@otherLayout.latte` sejam experimentados. +O nome do layout pode ser alterado usando `$this->setLayout('layoutAdmin')` e então será esperado no arquivo `@layoutAdmin.latte`. Também é possível especificar diretamente o arquivo de template de layout usando `$this->setLayout('/caminho/para/template.latte')`. -Você também pode especificar diretamente o nome do arquivo do modelo de layout usando `$this->setLayout('/path/to/template.latte')`. O uso do `$this->setLayout(false)` desabilitará a busca do layout. +Usando `$this->setLayout(false)` ou a tag `{layout none}` dentro do template, a busca por layout é desativada. .[note] -Você pode alterar os caminhos onde os modelos são pesquisados, substituindo o método [formatLayoutTemplateFiles |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], que retorna uma série de possíveis caminhos de arquivos. +Os arquivos onde os templates de layout são procurados podem ser alterados sobrescrevendo o método [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], que retorna um array de possíveis nomes de arquivos. -Variáveis no modelo .[#toc-variables-in-the-template] ------------------------------------------------------ +Variáveis no template +--------------------- -As variáveis são passadas para o modelo, escrevendo-as para `$this->template` e depois estão disponíveis no modelo como variáveis locais: +Passamos variáveis para o template escrevendo-as em `$this->template` e depois as temos disponíveis no template como variáveis locais: ```php $this->template->article = $this->articles->getById($id); ``` -Desta forma, podemos facilmente passar qualquer variável para os modelos. Entretanto, ao desenvolver aplicações robustas, muitas vezes é mais útil nos limitarmos. Por exemplo, ao definir explicitamente uma lista de variáveis que o modelo espera e seus tipos. Isto permitirá que o PHP verifique a digitação, que a IDE se autocomplete corretamente, e que a análise estática detecte erros. +Desta forma simples, podemos passar quaisquer variáveis para os templates. No entanto, no desenvolvimento de aplicações robustas, geralmente é mais útil limitar-se. Por exemplo, definindo explicitamente a lista de variáveis que o template espera e seus tipos. Graças a isso, o PHP poderá verificar os tipos, o IDE sugerirá corretamente e a análise estática revelará erros. -E como definimos tal enumeração? Simplesmente sob a forma de uma classe e suas propriedades. Damos o mesmo nome ao apresentador, mas com `Template` no final: +E como definimos tal lista? Simplesmente na forma de uma classe e suas propriedades. Nomeamo-la de forma semelhante ao presenter, apenas com `Template` no final: ```php /** @@ -93,22 +141,22 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template } ``` -O objeto `$this->template` do apresentador será agora uma instância da classe `ArticleTemplate`. Portanto, o PHP verificará os tipos declarados quando eles forem escritos. E a partir do PHP 8.2 ele também alertará sobre a escrita de uma variável inexistente, nas versões anteriores o mesmo pode ser alcançado usando o traço [Nette\SmartObject |utils:smartobject]. +O objeto `$this->template` no presenter será agora uma instância da classe `ArticleTemplate`. Assim, o PHP verificará os tipos declarados ao escrever. E a partir da versão PHP 8.2, também alertará sobre a escrita em uma variável inexistente; em versões anteriores, o mesmo pode ser alcançado usando a trait [Nette\SmartObject |utils:smartobject]. -A anotação `@property-read` é para IDE e análise estática, fará com que o auto-completar funcione, veja "PhpStorm e preenchimento de código por $this->template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. +A anotação `@property-read` destina-se ao IDE e à análise estática, graças a ela o autocompletar funcionará, veja [PhpStorm and code completion for $this⁠-⁠>⁠template|https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template]. [* phpstorm-completion.webp *] -Você também pode se dar ao luxo de sussurrar nos modelos, basta instalar o plugin Latte no PhpStorm e especificar o nome da classe no início do modelo, veja o artigo "Latte: como digitar sistema":https://blog.nette.org/pt/latte-como-usar-o-sistema-de-tipo: +Você pode desfrutar do luxo do autocompletar também nos templates, basta instalar o plugin para Latte no PhpStorm e indicar o nome da classe no início do template, mais no artigo [Latte: como usar o sistema de tipos|https://blog.nette.org/pt/latte-how-to-use-type-system]: ```latte -{templateType App\Presenters\ArticleTemplate} +{templateType App\Presentation\Article\ArticleTemplate} ... ``` -Também é assim que os modelos funcionam nos componentes, basta seguir a convenção de nomenclatura e criar uma classe de modelos `FifteenTemplate` para o componente, por exemplo `FifteenControl`. +É assim que os templates em componentes também funcionam, basta seguir a convenção de nomenclatura e para um componente, por exemplo, `FifteenControl`, criar uma classe de template `FifteenTemplate`. -Se você precisar criar um `$template` como instância de outra classe, use o método `createTemplate()`: +Se precisar criar `$template` como uma instância de outra classe, use o método `createTemplate()`: ```php public function renderDefault(): void @@ -121,43 +169,43 @@ public function renderDefault(): void ``` -Variáveis padrão .[#toc-default-variables] ------------------------------------------- +Variáveis padrão +---------------- -Apresentadores e componentes passam várias variáveis úteis para os modelos automaticamente: +Presenters e componentes passam automaticamente várias variáveis úteis para os templates: -- `$basePath` é um caminho absoluto de URL para o dir raiz (por exemplo `/CD-collection`) -- `$baseUrl` é um URL absoluto para o dir raiz (por exemplo `http://localhost/CD-collection`) -- `$user` é um objeto [que representa o usuário |security:authentication] -- `$presenter` é o atual apresentador -- `$control` é o atual componente ou apresentador -- `$flashes` lista de [mensagens |presenters#flash-messages] enviadas por método `flashMessage()` +- `$basePath` é o caminho URL absoluto para o diretório raiz (por exemplo, `/loja`) +- `$baseUrl` é a URL absoluta para o diretório raiz (por exemplo, `http://localhost/loja`) +- `$user` é o objeto [representando o usuário |security:authentication] +- `$presenter` é o presenter atual +- `$control` é o componente ou presenter atual +- `$flashes` array de [mensagens |presenters#Mensagens Flash] enviadas pela função `flashMessage()` -Se você usar uma classe de modelo personalizada, estas variáveis são passadas se você criar uma propriedade para elas. +Se você usar sua própria classe de template, essas variáveis serão passadas se você criar uma propriedade para elas. -Criação de links .[#toc-creating-links] ---------------------------------------- +Criação de links +---------------- -No modelo, criamos links para outros apresentadores e ações da seguinte forma: +No template, links para outros presenters & ações são criados desta forma: ```latte -detail +detalhe do produto ``` -O atributo `n:href` é muito útil para tags HTML ``. Se quisermos imprimir o link em outro lugar, por exemplo, no texto, usamos `{link}`: +O atributo `n:href` é muito útil para tags HTML ``. Se quisermos exibir o link em outro lugar, por exemplo, no texto, usamos `{link}`: ```latte -URL is: {link Home:default} +O endereço é: {link Home:default} ``` -Para mais informações, consulte [Criação de links |Creating Links]. +Mais informações podem ser encontradas no capítulo [Criando Links URL|creating-links]. -Filtros personalizados, Etiquetas, etc. .[#toc-custom-filters-tags-etc] ------------------------------------------------------------------------ +Filtros personalizados, tags, etc. +---------------------------------- -O sistema de modelos Latte pode ser ampliado com filtros personalizados, funções, tags, etc. Isto pode ser feito diretamente no `render` ou `beforeRender()` método: +O sistema de templates Latte pode ser estendido com filtros, funções, tags, etc. personalizados. Isso pode ser feito diretamente no método `render` ou `beforeRender()`: ```php public function beforeRender(): void @@ -165,16 +213,16 @@ public function beforeRender(): void // adicionando um filtro $this->template->addFilter('foo', /* ... */); - // ou configurar diretamente o objeto Latte\Engine + // ou configuramos diretamente o objeto Latte\Engine $latte = $this->template->getLatte(); $latte->addFilterLoader(/* ... */); } ``` -A versão 3 do Latte oferece uma maneira mais avançada, criando uma [extensão |latte:creating-extension] para cada projeto web. Aqui está um exemplo rudimentar de tal classe: +O Latte na versão 3 oferece uma maneira mais avançada, que é criar uma [extensão |latte:extending-latte#Latte Extension] para cada projeto web. Um exemplo fragmentado de tal classe: ```php -namespace App\Templating; +namespace App\Presentation\Accessory; final class LatteExtension extends Latte\Extension { @@ -207,22 +255,21 @@ final class LatteExtension extends Latte\Extension } ``` -Registramos usando [a configuração#Latte |configuration#Latte]: +Nós a registramos usando a [configuração |configuration#Templates Latte]: ```neon latte: extensions: - - App\Templating\LatteExtension + - App\Presentation\Accessory\LatteExtension ``` -Traduzindo .[#toc-translating] ------------------------------- +Tradução +-------- -Se você estiver programando uma aplicação multilíngüe, provavelmente precisará produzir parte do texto no modelo em diferentes idiomas. Para fazer isto, o Nette Framework define uma interface de tradução [api:Nette\Localization\Translator], que tem um único método `translate()`. Isto aceita a mensagem `$message`, que geralmente é uma string, e quaisquer outros parâmetros. A tarefa é devolver a string traduzida. -Não há uma implementação padrão em Nette, você pode escolher de acordo com suas necessidades entre várias soluções prontas que podem ser encontradas na [Componette |https://componette.org/search/localization]. Sua documentação lhe diz como configurar o tradutor. +Se você está programando uma aplicação multilíngue, provavelmente precisará exibir alguns textos no template em diferentes idiomas. O Nette Framework define para este propósito uma interface para tradução [api:Nette\Localization\Translator], que tem um único método `translate()`. Ele recebe a mensagem `$message`, que geralmente é uma string, e quaisquer outros parâmetros. A tarefa é retornar a string traduzida. No Nette, não há implementação padrão, você pode escolher de acordo com suas necessidades entre várias soluções prontas que podem ser encontradas na [Componette |https://componette.org/search/localization]. Em sua documentação, você aprenderá como configurar o tradutor. -Os modelos podem ser criados com um tradutor, que [teremos passado para nós |dependency-injection:passing-dependencies], usando o método `setTranslator()`: +É possível definir um tradutor para os templates, que [solicitamos |dependency-injection:passing-dependencies], usando o método `setTranslator()`: ```php protected function beforeRender(): void @@ -232,38 +279,38 @@ protected function beforeRender(): void } ``` -Alternativamente, o tradutor pode ser definido usando a [configuração |configuration#Latte]: +O tradutor também pode ser definido alternativamente através da [configuração |configuration#Templates Latte]: ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -O tradutor pode então ser usado, por exemplo, como um filtro `|translate`, com parâmetros adicionais passados para o método `translate()` (ver `foo, bar`): +Depois, o tradutor pode ser usado, por exemplo, como um filtro `|translate`, incluindo parâmetros adicionais que são passados para o método `translate()` (veja `foo, bar`): ```latte -{='Basket'|translate} +{='Carrinho'|translate} {$item|translate} {$item|translate, foo, bar} ``` -Ou como uma etiqueta de sublinhado: +Ou como uma tag de sublinhado: ```latte -{_'Basket'} +{_'Carrinho'} {_$item} {_$item, foo, bar} ``` -Para a tradução da seção de modelos, há uma etiqueta emparelhada `{translate}` (desde Latte 2.11, anteriormente foi usada a etiqueta `{_}` ): +Para traduzir uma seção do template, existe uma tag de par `{translate}` (a partir do Latte 2.11, anteriormente usava-se a tag `{_}`): ```latte -{translate}Order{/translate} -{translate foo, bar}Order{/translate} +{translate}Pedido{/translate} +{translate foo, bar}Pedido{/translate} ``` -O tradutor é chamado por padrão em tempo de execução ao renderizar o modelo. Latte versão 3, no entanto, pode traduzir todo o texto estático durante a compilação do modelo. Isto economiza desempenho porque cada string é traduzida apenas uma vez e a tradução resultante é escrita na forma compilada. Isto cria múltiplas versões compiladas do modelo no diretório do cache, uma para cada idioma. Para fazer isto, basta especificar o idioma como segundo parâmetro: +O tradutor é chamado por padrão em tempo de execução durante a renderização do template. O Latte versão 3, no entanto, pode traduzir todos os textos estáticos já durante a compilação do template. Isso economiza desempenho, pois cada string é traduzida apenas uma vez e a tradução resultante é escrita na forma compilada. No diretório de cache, são criadas várias versões compiladas do template, uma para cada idioma. Para isso, basta apenas especificar o idioma como segundo parâmetro: ```php protected function beforeRender(): void @@ -273,4 +320,4 @@ protected function beforeRender(): void } ``` -Por texto estático entendemos, por exemplo, `{_'hello'}` ou `{translate}hello{/translate}`. Textos não estáticos, como `{_$foo}`, continuarão a ser compilados em tempo real. +Texto estático significa, por exemplo, `{_'olá'}` ou `{translate}olá{/translate}`. Textos não estáticos, como `{_$foo}`, continuarão a ser traduzidos em tempo de execução. diff --git a/application/ro/@home.texy b/application/ro/@home.texy index 8a8c11fbf4..aad3662fe8 100644 --- a/application/ro/@home.texy +++ b/application/ro/@home.texy @@ -1,36 +1,85 @@ -Aplicație Nette -*************** +Nette Application +***************** .[perex] -Pachetul `nette/application` este baza pentru crearea de aplicații web interactive. - -- [Cum funcționează aplicațiile? |how-it-works] -- [Bootstrap |Bootstrap] -- [Prezentatori |Presenters] -- [Șabloane |Templates] -- [Module |Modules] -- [Rutarea |Routing] -- [Crearea de linkuri URL |creating-links] -- [Componente interactive |components] -- [AJAX & Snippets |ajax] -- [Multiplicator |multiplier] -- [Configurație |Configuration] +Nette Application este nucleul framework-ului Nette, care oferă instrumente puternice pentru crearea de aplicații web moderne. Oferă o serie de caracteristici excepționale care facilitează semnificativ dezvoltarea și îmbunătățesc securitatea și mentenabilitatea codului. Instalare --------- -Descărcați și instalați pachetul folosind [Composer |best-practices:composer]: +Descărcați și instalați biblioteca folosind [Composer|best-practices:composer]: ```shell composer require nette/application ``` -| versiune | compatibil cu PHP + +De ce să alegeți Nette Application? +----------------------------------- + +Nette a fost întotdeauna un pionier în domeniul tehnologiilor web. + +**Router bidirecțional:** Nette dispune de un sistem avansat de rutare, unic prin bidirecționalitatea sa - nu numai că traduce URL-urile în acțiuni ale aplicației, dar poate și genera invers adrese URL. Acest lucru înseamnă că: +- Puteți schimba oricând structura URL a întregii aplicații fără a fi nevoie să modificați șabloanele +- URL-urile sunt canonizate automat, ceea ce îmbunătățește SEO +- Rutarea este definită într-un singur loc, nu dispersată în adnotări + +**Componente și semnale:** Sistemul de componente încorporat, inspirat de Delphi și React.js, este complet excepțional printre framework-urile PHP: +- Permite crearea de elemente UI reutilizabile +- Suportă compunerea ierarhică a componentelor +- Oferă o procesare elegantă a cererilor AJAX folosind semnale +- Bibliotecă bogată de componente gata făcute pe [Componette](https://componette.org) + +**AJAX și snippete:** Nette a introdus un mod revoluționar de lucru cu AJAX încă din 2009, cu mult înainte de soluții similare precum Hotwire pentru Ruby on Rails sau Symfony UX Turbo: +- Snippetele permit actualizarea doar a unor părți ale paginii fără a fi nevoie să scrieți JavaScript +- Integrare automată cu sistemul de componente +- Invalidare inteligentă a părților paginii +- Cantitate minimă de date transferate + +**Șabloane intuitive [Latte|latte:]:** Cel mai sigur sistem de șabloane pentru PHP cu funcții avansate: +- Protecție automată împotriva XSS cu escapare sensibilă la context +- Extensibilitate prin filtre, funcții și tag-uri personalizate +- Moștenirea șabloanelor și snippete pentru AJAX +- Suport excelent pentru PHP 8.x cu sistem de tipuri + +**Dependency Injection:** Nette utilizează pe deplin Dependency Injection: +- Transmiterea automată a dependențelor (autowiring) +- Configurare folosind formatul clar NEON +- Suport pentru fabrici de componente + + +Principalele avantaje +--------------------- + +- **Securitate**: Protecție automată împotriva [vulnerabilităților|nette:vulnerability-protection] precum XSS, CSRF, etc. +- **Productivitate**: Mai puțin cod, mai multe funcții datorită designului inteligent +- **Depanare**: [Tracy debugger|tracy:] cu panou de rutare +- **Performanță**: Cache inteligent, încărcare leneșă a componentelor +- **Flexibilitate**: Modificare ușoară a URL-urilor chiar și după finalizarea aplicației +- **Componente**: Sistem unic de elemente UI reutilizabile +- **Modern**: Suport complet pentru PHP 8.4+ și sistem de tipuri + + +Primii pași +----------- + +1. [Cum funcționează aplicațiile? |how-it-works] - Înțelegerea arhitecturii de bază +2. [Presenters |presenters] - Lucrul cu presenteri și acțiuni +3. [Șabloane |templates] - Crearea șabloanelor în Latte +4. [Rutare |routing] - Configurarea adreselor URL +5. [Componente interactive |components] - Utilizarea sistemului de componente + + +Compatibilitate cu PHP +---------------------- + +| versiune | compatibil cu PHP |-----------|------------------- -| Nette Application 4.0 | PHP 8.0 - 8.2 -| Nette Application 3.1 | PHP 7.2 - 8.2 -| Nette Application 3.0 | PHP 7.1 - 8.0 -| Nette Application 2.4 | PHP 5.6 - 8.0 +| Nette Application 4.0 | PHP 8.1 – 8.4 +| Nette Application 3.2 | PHP 8.1 – 8.4 +| Nette Application 3.1 | PHP 7.2 – 8.3 +| Nette Application 3.0 | PHP 7.1 – 8.0 +| Nette Application 2.4 | PHP 5.6 – 8.0 -Se aplică la cele mai recente versiuni de patch-uri. +Se aplică pentru ultima versiune patch. diff --git a/application/ro/@left-menu.texy b/application/ro/@left-menu.texy index 844756dc21..c281e45b41 100644 --- a/application/ro/@left-menu.texy +++ b/application/ro/@left-menu.texy @@ -1,19 +1,22 @@ -Aplicație Nette -*************** +Nette Application +***************** - [Cum funcționează aplicațiile? |how-it-works] -- [Bootstrap |Bootstrap] -- [Prezentatori |Presenters] -- [Șabloane |Templates] -- [Module |Modules] -- [Rutarea |Routing] -- [Crearea de linkuri URL |creating-links] +- [Bootstrapping] +- [Presenters |presenters] +- [Șabloane |templates] +- [Structura directoarelor |directory-structure] +- [Rutare |routing] +- [Crearea linkurilor URL |creating-links] - [Componente interactive |components] -- [AJAX & Snippets |ajax] -- [Multiplicator |multiplier] -- [Configurație |Configuration] +- [AJAX & snippete |ajax] +- [Multiplier |multiplier] +- [Configurație |configuration] -Lecturi suplimentare +Lectură suplimentară ******************** -- [Cele mai bune practici |best-practices:] +- [De ce să folosiți Nette? |www:10-reasons-why-nette] +- [Instalare |nette:installation] +- [Scriem prima aplicație! |quickstart:] +- [Tutoriale și proceduri |best-practices:] - [Rezolvarea problemelor |nette:troubleshooting] diff --git a/application/ro/@meta.texy b/application/ro/@meta.texy new file mode 100644 index 0000000000..9c744b37d6 --- /dev/null +++ b/application/ro/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentație Nette}} diff --git a/application/ro/ajax.texy b/application/ro/ajax.texy index 78691c1798..9808145f5a 100644 --- a/application/ro/ajax.texy +++ b/application/ro/ajax.texy @@ -1,38 +1,45 @@ -AJAX & Snippets +AJAX & snippety ***************
    -În prezent, aplicațiile web moderne rulează pe jumătate pe un server și pe jumătate în browser. AJAX este un factor vital de unificare. Ce suport oferă Nette Framework? -- trimiterea de fragmente de șabloane (așa-numitele *snippets*) -- transmiterea de variabile între PHP și JavaScript -- depanarea aplicațiilor AJAX +În era aplicațiilor web moderne, unde funcționalitatea este adesea împărțită între server și browser, AJAX este un element de legătură esențial. Ce opțiuni ne oferă Nette Framework în acest domeniu? +- trimiterea unor părți din șablon, așa-numitele snippets +- transmiterea variabilelor între PHP și JavaScript +- instrumente pentru depanarea cererilor AJAX
    -O cerere AJAX poate fi detectată prin intermediul unei metode a unui serviciu care [încapsulează o cerere HTTP |http:request] `$httpRequest->isAjax()` (detectează pe baza antetului HTTP `X-Requested-With` ). Există, de asemenea, o metodă prescurtată în presenter: `$this->isAjax()`. -O solicitare AJAX nu diferă cu nimic de una normală - un prezentator este apelat cu o anumită vizualizare și parametri. Depinde, de asemenea, de prezentator cum va reacționa: acesta își poate folosi rutinele pentru a returna fie un fragment de cod HTML (un snippet), fie un document XML, un obiect JSON sau o bucată de cod Javascript. +Cererea AJAX +============ -Există un obiect preprocesat numit `payload` dedicat trimiterii de date către browser în JSON. +O cerere AJAX nu diferă, în esență, de o cerere HTTP clasică. Se apelează un presenter cu anumiți parametri. Și depinde de presenter cum va reacționa la cerere - poate returna date în format JSON, poate trimite o parte din codul HTML, un document XML etc. -```php -public function actionDelete(int $id): void -{ - if ($this->isAjax()) { - $this->payload->message = 'Success'; - } - // ... -} +Pe partea de browser, inițializăm cererea AJAX folosind funcția `fetch()`: + +```js +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) +.then(response => response.json()) +.then(payload => { + // procesarea răspunsului +}); ``` -Pentru un control complet asupra ieșirii JSON, utilizați metoda `sendJson` în prezentator. Aceasta termină imediat presenterul și vă veți descurca fără șablon: +Pe partea de server, recunoaștem o cerere AJAX prin metoda `$httpRequest->isAjax()` a serviciului [încapsulând cererea HTTP |http:request]. Pentru detectare, utilizează antetul HTTP `X-Requested-With`, de aceea este important să îl trimitem. În cadrul presenterului, se poate utiliza metoda `$this->isAjax()`. + +Dacă doriți să trimiteți date în format JSON, utilizați metoda [`sendJson()` |presenters#Trimiterea răspunsului]. Metoda încheie, de asemenea, activitatea presenterului. ```php -$this->sendJson(['key' => 'value', /* ... */]); +public function actionExport(): void +{ + $this->sendJson($this->model->getData); +} ``` -Dacă dorim să trimitem HTML, putem fie să setăm un șablon special pentru cererile AJAX: +Dacă intenționați să răspundeți folosind un șablon special destinat AJAX, puteți face acest lucru după cum urmează: ```php public function handleClick($param): void @@ -45,74 +52,80 @@ public function handleClick($param): void ``` -Naja .[#toc-naja] -================= +Snippets +======== + +Cel mai puternic instrument pe care Nette îl oferă pentru conectarea serverului cu clientul sunt snippets. Datorită lor, puteți transforma o aplicație obișnuită într-una AJAX cu un efort minim și câteva linii de cod. Exemplul Fifteen, al cărui cod îl găsiți pe [GitHub |https://github.com/nette-examples/fifteen], demonstrează cum funcționează totul. + +Snippets, sau fragmente, permit actualizarea doar a unor părți ale paginii, în loc de a reîncărca întreaga pagină. Acest lucru este nu numai mai rapid și mai eficient, dar oferă și o experiență de utilizare mai confortabilă. Snippets vă pot aminti de Hotwire pentru Ruby on Rails sau Symfony UX Turbo. Interesant este că Nette a introdus snippets cu 14 ani mai devreme. + +Cum funcționează snippets? La prima încărcare a paginii (cerere non-AJAX), se încarcă întreaga pagină, inclusiv toate snippets. Când utilizatorul interacționează cu pagina (de exemplu, face clic pe un buton, trimite un formular etc.), în loc de a încărca întreaga pagină, se declanșează o cerere AJAX. Codul din presenter execută acțiunea și decide ce snippets trebuie actualizate. Nette redă aceste snippets și le trimite sub forma unui array în format JSON. Codul de gestionare din browser inserează snippets primite înapoi în pagină. Astfel, se transferă doar codul snippets modificate, ceea ce economisește lățimea de bandă și accelerează încărcarea în comparație cu transferul conținutului întregii pagini. + + +Naja +---- - [Librăria Naja |https://naja.js.org] este utilizată pentru a gestiona cererile AJAX din partea browserului. [Instalați-o |https://naja.js.org/#/guide/01-install-setup-naja] ca un pachet node.js (pentru a o utiliza cu Webpack, Rollup, Vite, Parcel și altele): +Pentru gestionarea snippets pe partea de browser, se utilizează [biblioteca Naja |https://naja.js.org]. Aceasta se [instalează |https://naja.js.org/#/guide/01-install-setup-naja] ca pachet node.js (pentru utilizare cu aplicații Webpack, Rollup, Vite, Parcel și altele): ```shell npm install naja ``` -...sau inserați-o direct în șablonul de pagină: +…sau se inserează direct în șablonul paginii: ```html ``` +Mai întâi, este necesar să [inițializați |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] biblioteca: -Snippets .[#toc-snippets] -========================= +```js +naja.initialize(); +``` -Există un instrument mult mai puternic de suport AJAX încorporat - snippets. Utilizarea acestora face posibilă transformarea unei aplicații obișnuite într-una AJAX folosind doar câteva linii de cod. Modul în care funcționează totul este demonstrat în exemplul Fifteen, al cărui cod este, de asemenea, accesibil în build sau pe [GitHub |https://github.com/nette-examples/fifteen]. +Pentru a transforma un link obișnuit (semnal) sau trimiterea unui formular într-o cerere AJAX, este suficient să marcați linkul, formularul sau butonul respectiv cu clasa `ajax`: -Modul în care funcționează snippet-urile este că întreaga pagină este transferată în timpul cererii inițiale (adică non-AJAX) și apoi, la fiecare [subcerere |components#signal] AJAX (cerere a aceleiași vizualizări a aceluiași prezentator), doar codul părților modificate este transferat în depozitul `payload` menționat anterior. +```html +Go -Snippets vă poate aminti de Hotwire pentru Ruby on Rails sau de Symfony UX Turbo, dar Nette a venit cu ele cu paisprezece ani mai devreme. +
    + +
    +sau -Invalidarea Snippets .[#toc-invalidation-of-snippets] -===================================================== +
    + +
    +``` -Fiecare descendent al clasei [Control |components] (care este, de asemenea, un prezentator) este capabil să își amintească dacă au existat modificări în timpul unei solicitări care să necesite o nouă redare. Există o pereche de metode pentru a gestiona acest lucru: `redrawControl()` și `isControlInvalid()`. Un exemplu: + +Redesenarea snippetelor +----------------------- + +Fiecare obiect al clasei [Control |components] (inclusiv Presenterul însuși) înregistrează dacă au avut loc modificări care necesită redesenarea sa. Pentru aceasta se utilizează metoda `redrawControl()`: ```php public function handleLogin(string $user): void { - // Obiectul trebuie să se redea după ce utilizatorul s-a logat + // după logare este necesar să redesenăm partea relevantă $this->redrawControl(); // ... } ``` -Nette oferă însă o rezoluție și mai fină decât componentele întregi. Metodele enumerate acceptă ca parametru opțional numele unui așa-numit "snippet". Un "snippet" este practic un element din șablonul dvs. marcat în acest scop de o etichetă Latte, mai multe detalii în continuare. Astfel, este posibil să cereți unei componente să redeseneze doar *părți* din șablonul său. Dacă întreaga componentă este invalidată, atunci toate "snippet-urile" sale sunt reredimensionate. O componentă este "invalidată" și dacă oricare dintre subcomponentele sale este invalidată. - -```php -$this->isControlInvalid(); // -> fals -$this->redrawControl('header'); // invalidează fragmentul numit "header -$this->isControlInvalid('header'); // -> true -$this->isControlInvalid('footer'); // -> false -$this->isControlInvalid(); // -> true, cel puțin un fragment este invalidat +Nette permite un control și mai fin asupra a ceea ce trebuie redesenat. Metoda menționată poate primi ca argument numele snippetului. Astfel, se poate invalida (adică forța redesenarea) la nivelul părților șablonului. Dacă se invalidează întreaga componentă, se va redesena și fiecare snippet al acesteia: -$this->redrawControl(); // invalidează întreaga componentă, fiecare fragment -$this->isControlInvalid('footer'); // -> true +```php +// invalidează snippetul 'header' +$this->redrawControl('header'); ``` -O componentă care primește un semnal este marcată automat pentru redesenare. - -Mulțumită redimensionării fragmentelor, știm exact ce părți din ce elemente trebuie redimensionate. - -Etichetă `{snippet} … {/snippet}` .{toc: Tag snippet} -===================================================== +Snippets în Latte +----------------- -Redarea paginii se desfășoară în mod similar cu o cerere obișnuită: sunt încărcate aceleași șabloane etc. Partea vitală este, totuși, să se lase deoparte părțile care nu trebuie să ajungă la ieșire; celelalte părți trebuie asociate cu un identificator și trimise utilizatorului într-un format inteligibil pentru un manipulator JavaScript. - - -Sintaxa .[#toc-syntax] ----------------------- - -Dacă există un control sau un fragment în șablon, trebuie să îl înfășurăm cu ajutorul etichetei `{snippet} ... {/snippet}` - aceasta se va asigura că fragmentul redat va fi "tăiat" și trimis către browser. De asemenea, acesta va fi inclus într-un helper `
    ` (este posibil să se folosească o altă etichetă). În exemplul următor este definit un fragment numit `header`. Acesta poate reprezenta la fel de bine șablonul unei componente: +Utilizarea snippetelor în Latte este extrem de ușoară. Dacă doriți să definiți o parte a șablonului ca snippet, încadrați-o pur și simplu între tag-urile `{snippet}` și `{/snippet}`: ```latte {snippet header} @@ -120,7 +133,9 @@ Dacă există un control sau un fragment în șablon, trebuie să îl înfășur {/snippet} ``` -Un fragment de alt tip decât `
    ` sau un fragment cu atribute HTML suplimentare se obține prin utilizarea variantei de atribut: +Snippetul creează în pagina HTML un element `
    ` cu un `id` special generat. La redesenarea snippetului, se actualizează conținutul acestui element. De aceea, este necesar ca la redarea inițială a paginii să se redea și toate snippet-urile, chiar dacă acestea pot fi inițial goale. + +Puteți crea și un snippet cu un alt element decât `
    ` folosind n:atributul: ```latte
    @@ -129,138 +144,106 @@ Un fragment de alt tip decât `
    ` sau un fragment cu atribute HTML supliment ``` -Snippets dinamice .[#toc-dynamic-snippets] -========================================== +Zone de snippets +---------------- -În Nette puteți defini, de asemenea, fragmente cu un nume dinamic bazat pe un parametru de execuție. Acest lucru este cel mai potrivit pentru diverse liste în cazul în care trebuie să modificăm doar un singur rând, dar nu dorim să transferăm întreaga listă odată cu el. Un exemplu în acest sens ar fi: +Numele snippetelor pot fi și expresii: ```latte -
      - {foreach $list as $id => $item} -
    • {$item} update
    • - {/foreach} -
    +{foreach $items as $id => $item} +
  • {$item}
  • +{/foreach} ``` -Există un fragment static numit `itemsContainer`, care conține mai multe fragmente dinamice: `item-0`, `item-1` și așa mai departe. +Astfel, vom crea mai multe snippets `item-0`, `item-1` etc. Dacă am invalida direct un snippet dinamic (de exemplu, `item-1`), nu s-ar redesena nimic. Motivul este că snippet-urile funcționează într-adevăr ca fragmente și se redau doar ele însele direct. Însă, în șablon, nu există de fapt niciun snippet numit `item-1`. Acesta apare doar prin executarea codului din jurul snippetului, adică ciclul foreach. Prin urmare, marcăm porțiunea de șablon care trebuie executată folosind tag-ul `{snippetArea}`: -Nu puteți redesena direct un fragment dinamic (redesenarea lui `item-1` nu are niciun efect), ci trebuie să redesenați fragmentul său părinte (în acest exemplu `itemsContainer`). Acest lucru determină executarea codului snippet-ului părinte, dar apoi doar sub-snippet-urile sale sunt trimise către browser. Dacă doriți să trimiteți doar unul dintre sub-snippet-uri, trebuie să modificați intrarea pentru snippet-ul părinte pentru a nu genera celelalte sub-snippet-uri. +```latte +
      + {foreach $items as $id => $item} +
    • {$item}
    • + {/foreach} +
    +``` -În exemplul de mai sus, trebuie să vă asigurați că, pentru o cerere AJAX, doar un singur element va fi adăugat la matricea `$list`, prin urmare, bucla `foreach` va imprima doar un singur fragment dinamic. +Și lăsăm să se redeseneze atât snippetul însuși, cât și întreaga zonă părinte: ```php -class HomePresenter extends Nette\Application\UI\Presenter -{ - /** - * This method returns data for the list. - * Usually this would just request the data from a model. - * For the purpose of this example, the data is hard-coded. - */ - private function getTheWholeList(): array - { - return [ - 'First', - 'Second', - 'Third', - ]; - } - - public function renderDefault(): void - { - if (!isset($this->template->list)) { - $this->template->list = $this->getTheWholeList(); - } - } - - public function handleUpdate(int $id): void - { - $this->template->list = $this->isAjax() - ? [] - : $this->getTheWholeList(); - $this->template->list[$id] = 'Updated item'; - $this->redrawControl('itemsContainer'); - } -} +$this->redrawControl('itemsContainer'); +$this->redrawControl('item-1'); ``` +În același timp, este recomandabil să ne asigurăm că array-ul `$items` conține doar acele elemente care trebuie redesenate. -Fragmente într-un șablon inclus .[#toc-snippets-in-an-included-template] -======================================================================== - -Se poate întâmpla ca fragmentul să se afle într-un șablon care este inclus dintr-un alt șablon. În acest caz, trebuie să înfășurăm codul de includere în cel de-al doilea șablon cu eticheta `snippetArea`, apoi redesenăm atât snippetArea, cât și fragmentul propriu-zis. - -Eticheta `snippetArea` asigură că codul din interior este executat, dar numai fragmentul real din șablonul inclus este trimis către browser. +Dacă includem în șablon, folosind tag-ul `{include}`, un alt șablon care conține snippets, este necesar să includem din nou includerea șablonului în `snippetArea` și să o invalidăm împreună cu snippetul: ```latte -{* parent.latte *} -{snippetArea wrapper} - {include 'child.latte'} +{snippetArea include} + {include 'included.latte'} {/snippetArea} ``` + ```latte -{* copil.latte *} +{* included.latte *} {snippet item} -... + ... {/snippet} ``` + ```php -$this->redrawControl('wrapper'); +$this->redrawControl('include'); $this->redrawControl('item'); ``` -De asemenea, îl puteți combina cu snippet-uri dinamice. - -Adăugarea și ștergerea .[#toc-adding-and-deleting] -================================================== - -Dacă adăugați un nou element în listă și invalidați `itemsContainer`, cererea AJAX returnează fragmente, inclusiv pe cel nou, dar gestionarul javascript nu va putea să îl redea. Acest lucru se datorează faptului că nu există un element HTML cu ID-ul nou creat. +Snippets în componente +---------------------- -În acest caz, cea mai simplă metodă este de a îngloba întreaga listă într-un alt fragment și de a-l invalida pe tot: +Puteți crea snippets și în [componente|components], iar Nette le va redesena automat. Dar există o anumită limitare: pentru redesenarea snippetelor, se apelează metoda `render()` fără parametri. Prin urmare, nu va funcționa transmiterea parametrilor în șablon: ```latte -{snippet wholeList} -
      - {foreach $list as $id => $item} -
    • {$item} update
    • - {/foreach} -
    -{/snippet} -Add +OK +{control productGrid} + +nu va funcționa: +{control productGrid $arg, $arg} +{control productGrid:paginator} ``` + +Trimiterea datelor utilizatorului +--------------------------------- + +Împreună cu snippets, puteți trimite clientului orice alte date. Este suficient să le scrieți în obiectul `payload`: + ```php -public function handleAdd(): void +public function actionDelete(int $id): void { - $this->template->list = $this->getTheWholeList(); - $this->template->list[] = 'New one'; - $this->redrawControl('wholeList'); + // ... + if ($this->isAjax()) { + $this->payload->message = 'Success'; + } } ``` -Același lucru este valabil și pentru ștergerea unui element. Ar fi posibil să se trimită un fragment gol, dar, de obicei, listele pot fi paginate și ar fi complicat să se implementeze ștergerea unui element și încărcarea altuia (care se afla pe o pagină diferită a listei paginate). +Transmiterea parametrilor +========================= -Trimiterea parametrilor către componentă .[#toc-sending-parameters-to-component] -================================================================================ - -Atunci când trimitem parametrii către componentă prin intermediul unei cereri AJAX, fie că este vorba de parametri de semnal sau de parametri persistenți, trebuie să furnizăm numele global al acestora, care conține și numele componentei. Numele complet al parametrului returnează metoda `getParameterId()`. +Dacă trimitem parametri unei componente printr-o cerere AJAX, fie că sunt parametri de semnal sau parametri persistenți, trebuie să specificăm în cerere numele lor global, care include și numele componentei. Numele complet al parametrului este returnat de metoda `getParameterId()`. ```js -$.getJSON( - {link changeCountBasket!}, - { - {$control->getParameterId('id')}: id, - {$control->getParameterId('count')}: count - } -}); +let url = new URL({link //foo!}); +url.searchParams.set({$control->getParameterId('bar')}, bar); + +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) ``` -Și gestionează metoda cu s parametrii corespunzători în componentă. +Și metoda handle cu parametrii corespunzători în componentă: ```php -public function handleChangeCountBasket(int $id, int $count): void +public function handleFoo(int $bar): void { - } ``` diff --git a/application/ro/bootstrap.texy b/application/ro/bootstrap.texy deleted file mode 100644 index 585b6eed26..0000000000 --- a/application/ro/bootstrap.texy +++ /dev/null @@ -1,233 +0,0 @@ -Bootstrap -********* - -
    - -Bootstrap este codul de pornire care inițializează mediul, creează un container de injecție a dependențelor (DI) și pornește aplicația. Vom discuta despre: - -- cum să vă configurați aplicația utilizând fișiere NEON -- cum să gestionați modurile de producție și de dezvoltare -- cum să creați containerul DI - -
    - - -Aplicațiile, fie că sunt bazate pe web sau scripturi în linie de comandă, încep printr-o anumită formă de inițializare a mediului. În vremurile vechi, putea fi un fișier numit eg `include.inc.php` care se ocupa de acest lucru și care era inclus în fișierul inițial. -În aplicațiile moderne Nette, acesta a fost înlocuit de clasa `Bootstrap`, care, ca parte a aplicației, poate fi găsită în fișierul `app/Bootstrap.php`. Acesta ar putea arăta de exemplu astfel: - -```php -use Nette\Bootstrap\Configurator; - -class Bootstrap -{ - public static function boot(): Configurator - { - $appDir = dirname(__DIR__); - $configurator = new Configurator; - //$configurator->setDebugMode('secret@23.75.345.200'); - $configurator->enableTracy($appDir . '/log'); - $configurator->setTempDirectory($appDir . '/temp'); - $configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); - $configurator->addConfig($appDir . '/config/common.neon'); - return $configurator; - } -} -``` - - -index.php .[#toc-index-php] -=========================== - -În cazul aplicațiilor web, fișierul inițial este `index.php`, care se află în directorul public `www/`. Acesta permite clasei `Bootstrap` să inițializeze mediul și să returneze `$configurator` care creează containerul DI. Apoi se obține serviciul `Application`, care execută aplicația web: - -```php -// inițializarea mediului + obținerea obiectului Configurator -$configurator = App\Bootstrap::boot(); -// creați un container DI -$container = $configurator->createContainer(); -// containerul DI creează un obiect Nette\Application\Application -$application = $container->getByType(Nette\Application\Application::class); -// pornește aplicația Nette -$application->run(); -``` - -După cum puteți vedea, clasa [api:Nette\Bootstrap\Configurator], pe care o vom prezenta acum mai în detaliu, ajută la configurarea mediului și la crearea unui container de injecție a dependențelor (DI). - - -Modul de dezvoltare vs. modul de producție .[#toc-development-vs-production-mode] -================================================================================= - -Nette face distincție între două moduri de bază în care este executată o cerere: dezvoltare și producție. Modul de dezvoltare este axat pe confortul maxim al programatorului, Tracy este afișat, memoria cache este actualizată automat atunci când se schimbă șabloanele sau configurația containerului DI etc. Modul de producție este axat pe performanță, Tracy înregistrează doar erorile, iar modificările șabloanelor și ale altor fișiere nu sunt verificate. - -Selectarea modului se face prin autodetecție, astfel încât, de obicei, nu este nevoie să configurați sau să comutați nimic manual. Modul este dezvoltare dacă aplicația rulează pe localhost (adică adresa IP `127.0.0.1` sau `::1`) și nu este prezent niciun proxy (adică antetul său HTTP). În caz contrar, se execută în modul de producție. - -Dacă doriți să activați modul de dezvoltare în alte cazuri, de exemplu, pentru programatorii care accesează de la o anumită adresă IP, puteți utiliza `setDebugMode()`: - -```php -$configurator->setDebugMode('23.75.345.200'); // una sau mai multe adrese IP -``` - -Vă recomandăm cu siguranță să combinați o adresă IP cu un cookie. Vom stoca un token secret în cookie-ul `nette-debug`, de exemplu `secret1234`, iar modul de dezvoltare va fi activat pentru programatorii cu această combinație de IP și cookie. - -```php -$configurator->setDebugMode('secret1234@23.75.345.200'); -``` - -De asemenea, putem dezactiva complet modul de dezvoltare, chiar și pentru localhost: - -```php -$configurator->setDebugMode(false); -``` - -Rețineți că valoarea `true` activează modul dezvoltator din greu, ceea ce nu ar trebui să se întâmple niciodată pe un server de producție. - - -Instrumentul de depanare Tracy .[#toc-debugging-tool-tracy] -=========================================================== - -Pentru o depanare mai ușoară, vom porni minunata unealtă [Tracy |tracy:]. În modul dezvoltator, acesta vizualizează erorile, iar în modul de producție înregistrează erorile în directorul specificat: - -```php -$configurator->enableTracy($appDir . '/log'); -``` - - -Temporary Files .[#toc-temporary-files] -======================================= - -Nette utilizează memoria cache pentru DI container, RobotLoader, șabloane etc. Prin urmare, este necesar să setați calea către directorul în care va fi stocată memoria cache: - -```php -$configurator->setTempDirectory($appDir . '/temp'); -``` - -Pe Linux sau macOS, setați [permisiunile de scriere |nette:troubleshooting#Setting directory permissions] pentru directoarele `log/` și `temp/`. - - -RobotLoader .[#toc-robotloader] -=============================== - -De obicei, vom dori să încărcăm automat clasele folosind [RobotLoader |robot-loader:], așa că trebuie să îl pornim și să îl lăsăm să încarce clasele din directorul în care se află `Bootstrap.php` (adică `__DIR__`) și din toate subdirectoarele sale: - -```php -$configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); -``` - -O modalitate alternativă este de a utiliza doar încărcarea automată [Composer |best-practices:composer] PSR-4. - - -Fusul orar .[#toc-timezone] -=========================== - -Configurator vă permite să specificați un fus orar pentru aplicația dumneavoastră. - -```php -$configurator->setTimeZone('Europe/Prague'); -``` - - -Configurarea containerului DI .[#toc-di-container-configuration] -================================================================ - -O parte a procesului de pornire constă în crearea unui container DI, adică a unei fabrici de obiecte, care reprezintă inima întregii aplicații. Acesta este de fapt o clasă PHP generată de Nette și stocată într-un director de cache. Fabrica produce obiectele cheie ale aplicației, iar fișierele de configurare o instruiesc cum să le creeze și să le configureze și, astfel, influențăm comportamentul întregii aplicații. - -Fișierele de configurare sunt de obicei scrise în [formatul NEON |neon:format]. Puteți citi [ce poate fi configurat aici |nette:configuring]. - -.[tip] -În modul de dezvoltare, containerul este actualizat automat de fiecare dată când modificați codul sau fișierele de configurare. În modul de producție, acesta este generat o singură dată, iar modificările fișierelor nu sunt verificate pentru a maximiza performanța. - -Fișierele de configurare sunt încărcate utilizând `addConfig()`: - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -``` - -Metoda `addConfig()` poate fi apelată de mai multe ori pentru a adăuga mai multe fișiere. - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -$configurator->addConfig($appDir . '/config/local.neon'); -if (PHP_SAPI === 'cli') { - $configurator->addConfig($appDir . '/config/cli.php'); -} -``` - -Numele `cli.php` nu este o greșeală de tipar, deoarece configurația poate fi scrisă și într-un fișier PHP, care o returnează sub forma unei matrice. - -Alternativ, putem folosi [secțiunea`includes` |dependency-injection:configuration#including files] pentru a încărca mai multe fișiere de configurare. - -Dacă în fișierele de configurare apar elemente cu aceleași chei în cadrul fișierelor de configurare, acestea vor fi [suprascrise sau fuzionate |dependency-injection:configuration#Merging] în cazul array-urilor. Ultimul fișier inclus are o prioritate mai mare decât cel anterior. Fișierul în care este listată secțiunea `includes` are o prioritate mai mare decât fișierele incluse în el. - - -Parametrii statici .[#toc-static-parameters] --------------------------------------------- - -Parametrii utilizați în fișierele de configurare pot fi definiți [în secțiunea `parameters` |dependency-injection:configuration#parameters] și, de asemenea, pot fi trecuți (sau suprascriși) prin metoda `addStaticParameters()` (are pseudonimul `addParameters()`). Este important ca valorile diferite ale parametrilor să determine generarea de containere DI suplimentare, adică de clase suplimentare. - -```php -$configurator->addStaticParameters([ - 'projectId' => 23, -]); -``` - -În fișierele de configurare, putem scrie notația obișnuită `%projectId%` pentru a accesa parametrul numit `projectId`. În mod implicit, configuratorul completează următorii parametri: `appDir`, `wwwDir`, `tempDir`, , `vendorDir`, `debugMode` și `consoleMode`. - - -Parametrii dinamici .[#toc-dynamic-parameters] ----------------------------------------------- - -De asemenea, putem adăuga parametri dinamici la container, valorile lor diferite, spre deosebire de parametrii statici, nu vor determina generarea de noi containere DI. - -```php -$configurator->addDynamicParameters([ - 'remoteIp' => $_SERVER['REMOTE_ADDR'], -]); -``` - -Variabilele de mediu ar putea fi puse cu ușurință la dispoziție prin intermediul parametrilor dinamici. Le putem accesa prin intermediul `%env.variable%` în fișierele de configurare. - -```php -$configurator->addDynamicParameters([ - 'env' => getenv(), -]); -``` - - -Servicii importate .[#toc-imported-services] --------------------------------------------- - -Acum mergem mai departe. Deși scopul unui container DI este acela de a crea obiecte, în mod excepțional poate fi nevoie să se introducă un obiect existent în container. Facem acest lucru prin definirea serviciului cu atributul `imported: true`. - -```neon -services: - myservice: - type: App\Model\MyCustomService - imported: true -``` - -Creați o nouă instanță și inserați-o în bootstrap: - -```php -$configurator->addServices([ - 'myservice' => new App\Model\MyCustomService('foobar'), -]); -``` - - -Diferite medii .[#toc-different-environments] -============================================= - -Nu ezitați să personalizați clasa `Bootstrap` pentru a se potrivi nevoilor dumneavoastră. Puteți adăuga parametri la metoda `boot()` pentru a diferenția proiectele web sau puteți adăuga alte metode, cum ar fi `bootForTests()`, care inițializează mediul pentru testele unitare, `bootForCli()` pentru scripturile apelate din linia de comandă și așa mai departe. - -```php -public static function bootForTests(): Configurator -{ - $configurator = self::boot(); - Tester\Environment::setup(); // Inițializarea Nette Tester - return $configurator; -} -``` diff --git a/application/ro/bootstrapping.texy b/application/ro/bootstrapping.texy new file mode 100644 index 0000000000..929a260190 --- /dev/null +++ b/application/ro/bootstrapping.texy @@ -0,0 +1,297 @@ +Bootstrapping +************* + +
    + +Bootstrapping-ul este procesul de inițializare a mediului aplicației, crearea unui container de dependency injection (DI) și pornirea aplicației. Vom discuta: + +- cum clasa Bootstrap inițializează mediul +- cum sunt configurate aplicațiile folosind fișiere NEON +- cum să distingem între modul de producție și dezvoltare +- cum să creăm și să configurăm containerul DI + +
    + + +Aplicațiile, fie că sunt web sau scripturi rulate din linia de comandă, își încep execuția cu o formă de inițializare a mediului. În trecut, acest lucru era de obicei gestionat de un fișier numit, de exemplu, `include.inc.php`, pe care fișierul inițial îl includea. În aplicațiile Nette moderne, acesta a fost înlocuit de clasa `Bootstrap`, pe care o veți găsi ca parte a aplicației în fișierul `app/Bootstrap.php`. Poate arăta, de exemplu, astfel: + +```php +use Nette\Bootstrap\Configurator; + +class Bootstrap +{ + private Configurator $configurator; + private string $rootDir; + + public function __construct() + { + $this->rootDir = dirname(__DIR__); + // Configuratorul este responsabil pentru setarea mediului aplicației și a serviciilor. + $this->configurator = new Configurator; + // Setează directorul pentru fișierele temporare generate de Nette (de ex., șabloane compilate) + $this->configurator->setTempDirectory($this->rootDir . '/temp'); + } + + public function bootWebApplication(): Nette\DI\Container + { + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); + } + + private function initializeEnvironment(): void + { + // Nette este inteligent și modul de dezvoltare se activează automat, + // sau îl puteți activa pentru o anumită adresă IP decomentând următoarea linie: + // $this->configurator->setDebugMode('secret@23.75.345.200'); + + // Activează Tracy: "briceagul elvețian" suprem pentru depanare. + $this->configurator->enableTracy($this->rootDir . '/log'); + + // RobotLoader: încarcă automat toate clasele din directorul selectat + $this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); + } + + private function setupContainer(): void + { + // Încarcă fișierele de configurare + $this->configurator->addConfig($this->rootDir . '/config/common.neon'); + } +} +``` + + +index.php +========= + +Fișierul inițial în cazul aplicațiilor web este `index.php`, care se află în [directorul public |directory-structure#Director public www] `www/`. Acesta solicită clasei Bootstrap să inițializeze mediul și să creeze containerul DI. Apoi, obține din acesta serviciul `Application`, care pornește aplicația web: + +```php +$bootstrap = new App\Bootstrap; +// Inițializarea mediului + crearea containerului DI +$container = $bootstrap->bootWebApplication(); +// Containerul DI creează obiectul Nette\Application\Application +$application = $container->getByType(Nette\Application\Application::class); +// Pornirea aplicației Nette și procesarea cererii primite +$application->run(); +``` + +După cum se poate vedea, clasa [api:Nette\Bootstrap\Configurator] ajută la setarea mediului și la crearea containerului de dependency injection (DI), pe care o vom prezenta acum mai detaliat. + + +Modul de dezvoltare vs producție +================================ + +Nette se comportă diferit în funcție de dacă rulează pe un server de dezvoltare sau de producție: + +🛠️ Modul de dezvoltare (Development): + - Afișează bara de depanare Tracy cu informații utile (interogări SQL, timp de execuție, memorie utilizată) + - În caz de eroare, afișează o pagină de eroare detaliată cu apelurile de funcții și conținutul variabilelor + - Reînnoiește automat cache-ul la modificarea șabloanelor Latte, editarea fișierelor de configurare etc. + + +🚀 Modul de producție (Production): + - Nu afișează nicio informație de depanare, toate erorile sunt scrise în log + - În caz de eroare, afișează ErrorPresenter sau pagina generică "Server Error" + - Cache-ul nu se reînnoiește niciodată automat! + - Optimizat pentru viteză și securitate + + +Alegerea modului se face prin autodetecție, deci de obicei nu este necesar să configurați sau să comutați manual: + +- modul de dezvoltare: pe localhost (adresa IP `127.0.0.1` sau `::1`) dacă nu este prezent un proxy (adică antetul său HTTP) +- modul de producție: oriunde altundeva + +Dacă dorim să activăm modul de dezvoltare și în alte cazuri, de exemplu pentru programatorii care accesează de la o anumită adresă IP, folosim `setDebugMode()`: + +```php +$this->configurator->setDebugMode('23.75.345.200'); // se poate specifica și un array de adrese IP +``` + +Recomandăm cu tărie combinarea adresei IP cu un cookie. În cookie-ul `nette-debug` salvăm un token secret, de ex. `secret1234`, și astfel activăm modul de dezvoltare pentru programatorii care accesează de la o anumită adresă IP și au în același timp tokenul menționat în cookie: + +```php +$this->configurator->setDebugMode('secret1234@23.75.345.200'); +``` + +Putem, de asemenea, să dezactivăm complet modul de dezvoltare, chiar și pentru localhost: + +```php +$this->configurator->setDebugMode(false); +``` + +Atenție, valoarea `true` activează modul de dezvoltare forțat, ceea ce nu trebuie să se întâmple niciodată pe un server de producție. + + +Instrumentul de depanare Tracy +============================== + +Pentru o depanare ușoară, vom activa și excelentul instrument [Tracy |tracy:]. În modul de dezvoltare, vizualizează erorile, iar în modul de producție, le înregistrează în directorul specificat: + +```php +$this->configurator->enableTracy($this->rootDir . '/log'); +``` + + +Fișiere temporare +================= + +Nette utilizează cache pentru containerul DI, RobotLoader, șabloane etc. Prin urmare, este necesar să setați calea către directorul unde se va stoca cache-ul: + +```php +$this->configurator->setTempDirectory($this->rootDir . '/temp'); +``` + +Pe Linux sau macOS, setați [permisiuni de scriere |nette:troubleshooting#Setarea permisiunilor pentru directoare] pentru directoarele `log/` și `temp/`. + + +RobotLoader +=========== + +De regulă, vom dori să încărcăm automat clasele folosind [RobotLoader |robot-loader:], deci trebuie să îl pornim și să îl lăsăm să încarce clasele din directorul unde este plasat `Bootstrap.php` (adică `__DIR__`), și din toate subdirectoarele: + +```php +$this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); +``` + +O abordare alternativă este să lăsăm clasele să fie încărcate doar prin [Composer |best-practices:composer] respectând PSR-4. + + +Timezone +======== + +Prin intermediul configuratorului puteți seta fusul orar implicit. + +```php +$this->configurator->setTimeZone('Europe/Prague'); +``` + + +Configurarea containerului DI +============================= + +Parte a procesului de inițializare este crearea containerului DI sau a fabricii de obiecte, care este inima întregii aplicații. Este de fapt o clasă PHP, generată de Nette și salvată în directorul de cache. Fabrica produce obiectele cheie ale aplicației și, folosind fișierele de configurare, o instruim cum să le creeze și să le seteze, influențând astfel comportamentul întregii aplicații. + +Fișierele de configurare sunt de obicei scrise în formatul [NEON |neon:format]. Într-un capitol separat veți afla [ce poate fi configurat |nette:configuring]. + +.[tip] +În modul de dezvoltare, containerul se actualizează automat la fiecare modificare a codului sau a fișierelor de configurare. În modul de producție, se generează o singură dată și modificările nu sunt verificate pentru a maximiza performanța. + +Fișierele de configurare le încărcăm folosind `addConfig()`: + +```php +$this->configurator->addConfig($this->rootDir . '/config/common.neon'); +``` + +Dacă dorim să adăugăm mai multe fișiere de configurare, putem apela funcția `addConfig()` de mai multe ori. + +```php +$configDir = $this->rootDir . '/config'; +$this->configurator->addConfig($configDir . '/common.neon'); +$this->configurator->addConfig($configDir . '/services.neon'); +if (PHP_SAPI === 'cli') { + $this->configurator->addConfig($configDir . '/cli.php'); +} +``` + +Numele `cli.php` nu este o greșeală de tipar, configurația poate fi scrisă și într-un fișier PHP, care o returnează ca array. + +De asemenea, putem adăuga alte fișiere de configurare în [secțiunea `includes` |dependency-injection:configuration#Includerea fișierelor]. + +Dacă în fișierele de configurare apar elemente cu aceleași chei, acestea vor fi suprascrise sau, în cazul [array-urilor, combinate |dependency-injection:configuration#Combinare]. Fișierul inclus ulterior are prioritate mai mare decât cel anterior. Fișierul în care este specificată secțiunea `includes` are prioritate mai mare decât fișierele incluse în el. + + +Parametri statici +----------------- + +Parametrii utilizați în fișierele de configurare pot fi definiți [în secțiunea `parameters` |dependency-injection:configuration#Parametri] și, de asemenea, pot fi transmiși (sau suprascriși) prin metoda `addStaticParameters()` (are aliasul `addParameters()`). Este important că valorile diferite ale parametrilor determină generarea altor containere DI, adică a altor clase. + +```php +$this->configurator->addStaticParameters([ + 'projectId' => 23, +]); +``` + +La parametrul `projectId` se poate face referire în configurație prin notația obișnuită `%projectId%`. + + +Parametri dinamici +------------------ + +În container putem adăuga și parametri dinamici, ale căror valori diferite, spre deosebire de parametrii statici, nu determină generarea de noi containere DI. + +```php +$this->configurator->addDynamicParameters([ + 'remoteIp' => $_SERVER['REMOTE_ADDR'], +]); +``` + +Astfel, putem adăuga simplu, de exemplu, variabile de mediu, la care se poate face referire ulterior în configurație prin notația `%env.variable%`. + +```php +$this->configurator->addDynamicParameters([ + 'env' => getenv(), +]); +``` + + +Parametri impliciți +------------------- + +În fișierele de configurare puteți utiliza acești parametri statici: + +- `%appDir%` este calea absolută către directorul cu fișierul `Bootstrap.php` +- `%wwwDir%` este calea absolută către directorul cu fișierul de intrare `index.php` +- `%tempDir%` este calea absolută către directorul pentru fișiere temporare +- `%vendorDir%` este calea absolută către directorul unde Composer instalează bibliotecile +- `%rootDir%` este calea absolută către directorul rădăcină al proiectului +- `%debugMode%` indică dacă aplicația este în modul de depanare +- `%consoleMode%` indică dacă cererea a venit prin linia de comandă + + +Servicii importate +------------------ + +Acum intrăm mai în profunzime. Deși scopul containerului DI este să producă obiecte, în mod excepțional poate apărea nevoia de a introduce un obiect existent în container. Facem acest lucru definind serviciul cu flag-ul `imported: true`. + +```neon +services: + myservice: + type: App\Model\MyCustomService + imported: true +``` + +Și în bootstrap introducem obiectul în container: + +```php +$this->configurator->addServices([ + 'myservice' => new App\Model\MyCustomService('foobar'), +]); +``` + + +Medii diferite +============== + +Nu vă fie teamă să modificați clasa Bootstrap conform nevoilor dvs. Puteți adăuga parametri metodei `bootWebApplication()` pentru a distinge proiectele web. Sau putem completa cu alte metode, de exemplu `bootTestEnvironment()`, care inițializează mediul pentru testele unitare, `bootConsoleApplication()` pentru scripturile apelate din linia de comandă etc. + +```php +public function bootTestEnvironment(): Nette\DI\Container +{ + Tester\Environment::setup(); // inițializarea Nette Tester + $this->setupContainer(); + return $this->configurator->createContainer(); +} + +public function bootConsoleApplication(): Nette\DI\Container +{ + $this->configurator->setDebugMode(false); + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); +} +``` diff --git a/application/ro/components.texy b/application/ro/components.texy index 67cf732f5a..e4959ac9a9 100644 --- a/application/ro/components.texy +++ b/application/ro/components.texy @@ -3,27 +3,27 @@ Componente interactive
    -Componentele sunt obiecte separate reutilizabile pe care le plasăm în pagini. Ele pot fi formulare, datagrids, sondaje, de fapt, orice lucru care are sens să fie folosit în mod repetat. Vom arăta: +Componentele sunt obiecte separate, reutilizabile, pe care le inserăm în pagini. Acestea pot fi formulare, datagrid-uri, sondaje, de fapt, orice are sens să fie folosit în mod repetat. Vom arăta: - cum se utilizează componentele? -- cum să le scriem? +- cum se scriu? - ce sunt semnalele?
    -Nette are un sistem de componente încorporat. Cei mai în vârstă dintre dumneavoastră își amintesc poate ceva similar din Delphi sau ASP.NET Web Forms. React sau Vue.js este construit pe ceva foarte asemănător. Cu toate acestea, în lumea cadrelor PHP, aceasta este o caracteristică complet unică. +Nette are încorporat un sistem de componente. Ceva similar ar putea fi cunoscut de veterani din Delphi sau ASP.NET Web Forms, ceva asemănător stă la baza React sau Vue.js. Cu toate acestea, în lumea framework-urilor PHP, este o caracteristică unică. -În același timp, componentele schimbă în mod fundamental abordarea dezvoltării aplicațiilor. Puteți compune pagini din unități pregatite în prealabil. Aveți nevoie de datagrid în administrație? Îl puteți găsi la [Componette |https://componette.org/search/component], un depozit de add-on-uri (nu doar componente) open-source pentru Nette, și pur și simplu îl puteți lipi în prezentator. +Totuși, componentele influențează în mod fundamental abordarea creării aplicațiilor. Puteți compune paginile din unități pre-pregătite. Aveți nevoie de un datagrid în administrare? Îl găsiți pe [Componette |https://componette.org/search/component], un depozit de add-on-uri open-source (adică nu doar componente) pentru Nette și îl inserați pur și simplu în presenter. -Puteți încorpora orice număr de componente în prezentator. Și puteți insera alte componente în unele componente. Se creează astfel un arbore de componente cu un prezentator ca rădăcină. +Puteți încorpora orice număr de componente într-un presenter. Și în unele componente puteți insera alte componente. Astfel se creează un arbore de componente, a cărui rădăcină este presenterul. -Metode de fabrică .[#toc-factory-methods] -========================================= +Metode factory +============== -Cum sunt plasate și ulterior utilizate componentele în prezentator? De obicei, folosind metode de fabrică. +Cum se inserează componentele în presenter și cum se utilizează ulterior? De obicei, prin metode factory. -Fabrica de componente este o modalitate elegantă de a crea componente numai atunci când sunt cu adevărat necesare (leneș / la cerere). Toată magia constă în implementarea unei metode numite `createComponent()`, unde `` este numele componentei, pe care o va crea și o va returna. +O fabrică de componente reprezintă o modalitate elegantă de a crea componente doar în momentul în care sunt cu adevărat necesare (lazy / on demand). Întreaga magie constă în implementarea unei metode cu numele `createComponent()`, unde `` este numele componentei create, și care creează și returnează componenta. ```php .{file:DefaultPresenter.php} class DefaultPresenter extends Nette\Application\UI\Presenter @@ -37,43 +37,43 @@ class DefaultPresenter extends Nette\Application\UI\Presenter } ``` -Deoarece toate componentele sunt create în metode separate, codul este mai curat și mai ușor de citit. +Datorită faptului că toate componentele sunt create în metode separate, codul devine mai clar. .[note] -Numele componentelor încep întotdeauna cu o literă minusculă, deși sunt scrise cu majuscule în numele metodei. +Numele componentelor încep întotdeauna cu literă mică, chiar dacă în numele metodei se scriu cu literă mare. -Nu apelăm niciodată direct fabricile, acestea sunt apelate automat, atunci când folosim componentele pentru prima dată. Datorită acestui fapt, o componentă este creată la momentul potrivit și numai dacă este cu adevărat necesară. Dacă nu am folosi componenta (de exemplu, în cadrul unor cereri AJAX, în care returnăm doar o parte din pagină sau când anumite părți sunt stocate în memoria cache), aceasta nici măcar nu ar fi creată și am economisi performanța serverului. +Fabricile nu le apelăm niciodată direct, ele se apelează singure în momentul în care folosim componenta pentru prima dată. Datorită acestui fapt, componenta este creată la momentul potrivit și doar în cazul în care este cu adevărat necesară. Dacă nu folosim componenta (de exemplu, într-o cerere AJAX, când se transferă doar o parte a paginii, sau la cache-uirea șablonului), aceasta nu se creează deloc și economisim performanța serverului. ```php .{file:DefaultPresenter.php} // accesăm componenta și dacă a fost prima dată, -// se apelează createComponentPoll() pentru a o crea. +// se apelează createComponentPoll() care o creează $poll = $this->getComponent('poll'); -// sintaxa alternativă: $poll = $this['poll']; +// sintaxă alternativă: $poll = $this['poll']; ``` -În șablon, puteți reda o componentă folosind eticheta [{control} |#Rendering]. Astfel, nu mai este nevoie să treceți manual componentele în șablon. +În șablon, este posibil să redăm componenta folosind tag-ul [{control} |#Redare]. Prin urmare, nu este necesar să transmitem manual componentele către șablon. ```latte -

    Please Vote

    +

    Votați

    {control poll} ``` -Stilul Hollywood .[#toc-hollywood-style] -======================================== +Stilul Hollywood +================ -Componentele folosesc în mod obișnuit o tehnică interesantă, pe care ne place să o numim stilul Hollywood. Cu siguranță cunoașteți clișeul pe care actorii îl aud adesea la apelurile de casting: "Nu ne sunați pe noi, vă sunăm noi pe voi". Și despre asta este vorba. +Componentele folosesc în mod obișnuit o tehnică proaspătă, pe care ne place să o numim stilul Hollywood. Cu siguranță cunoașteți celebra frază pe care participanții la castingurile de film o aud atât de des: „Nu ne sunați, vă vom suna noi”. Și exact despre asta este vorba. -În Nette, în loc să trebuiască să puneți în permanență întrebări ("a fost trimis formularul?", "a fost valid?" sau "a apăsat cineva acest buton?"), îi spuneți framework-ului "când se întâmplă acest lucru, apelați această metodă" și lăsați mai departe să lucreze la ea. Dacă programați în JavaScript, sunteți familiarizat cu acest stil de programare. Scrieți funcții care sunt apelate atunci când apare un anumit eveniment. Iar motorul le trece parametrii corespunzători. +În Nette, în loc să trebuiască să întrebați constant („a fost trimis formularul?”, „a fost valid?” sau „a apăsat utilizatorul acest buton?”), spuneți framework-ului „când se întâmplă asta, apelează această metodă” și lăsați restul muncii pe seama lui. Dacă programați în JavaScript, acest stil de programare vă este familiar. Scrieți funcții care sunt apelate atunci când apare un anumit eveniment. Și limbajul le transmite parametrii corespunzători. -Acest lucru schimbă complet modul în care scrieți aplicații. Cu cât puteți delega mai multe sarcini către framework, cu atât mai puțină muncă aveți de făcut. Și cu atât mai puțin puteți uita. +Acest lucru schimbă complet perspectiva asupra scrierii aplicațiilor. Cu cât puteți lăsa mai multe sarcini pe seama framework-ului, cu atât aveți mai puțină muncă. Și cu atât mai puțin puteți omite. -Cum se scrie o componentă .[#toc-how-to-write-a-component] -========================================================== +Scriem o componentă +=================== -Prin componentă ne referim de obicei la descendenții clasei [api:Nette\Application\UI\Control]. Prezentatorul [api:Nette\Application\UI\Presenter] este, de asemenea, un descendent al clasei `Control`. +Prin termenul componentă înțelegem de obicei un descendent al clasei [api:Nette\Application\UI\Control]. (Mai precis ar fi, așadar, să folosim termenul „controls”, dar „controale” are un sens complet diferit în română și s-a impus mai degrabă „componente”.) Presenterul însuși [api:Nette\Application\UI\Presenter] este, de altfel, tot un descendent al clasei `Control`. ```php .{file:PollControl.php} use Nette\Application\UI\Control; @@ -84,22 +84,22 @@ class PollControl extends Control ``` -Redarea .[#toc-rendering] -========================= +Redare +====== -Știm deja că eticheta `{control componentName}` este utilizată pentru a desena o componentă. Aceasta apelează de fapt metoda `render()` a componentei, în care ne ocupăm de redare. Avem, la fel ca în prezentator, un șablon [Latte |latte:] în variabila `$this->template`, căruia îi transmitem parametrii. Spre deosebire de utilizarea într-un prezentator, trebuie să specificăm un fișier șablon și să-l lăsăm să facă randarea: +Știm deja că pentru redarea unei componente se folosește tag-ul `{control componentName}`. Acesta apelează de fapt metoda `render()` a componentei, în care ne ocupăm de redare. Avem la dispoziție, la fel ca în presenter, [șablonul Latte|templates] în variabila `$this->template`, căreia îi transmitem parametri. Spre deosebire de presenter, trebuie să specificăm fișierul cu șablonul și să îl lăsăm să fie redat: ```php .{file:PollControl.php} public function render(): void { - // vom introduce câțiva parametri în șablon + // inserăm în șablon câțiva parametri $this->template->param = $value; - // și îl vom desena + // și o redăm $this->template->render(__DIR__ . '/poll.latte'); } ``` -Eticheta `{control}` permite trecerea parametrilor către metoda `render()`: +Tag-ul `{control}` permite transmiterea parametrilor către metoda `render()`: ```latte {control poll $id, $message} @@ -112,7 +112,7 @@ public function render(int $id, string $message): void } ``` -Uneori, o componentă poate fi formată din mai multe părți pe care dorim să le redăm separat. Pentru fiecare dintre ele vom crea propria metodă de redare, iată de exemplu `renderPaginator()`: +Uneori, o componentă poate consta din mai multe părți pe care dorim să le redăm separat. Pentru fiecare dintre ele, creăm propria metodă de redare, aici în exemplu, de exemplu, `renderPaginator()`: ```php .{file:PollControl.php} public function renderPaginator(): void @@ -121,36 +121,36 @@ public function renderPaginator(): void } ``` -Iar în șablon o vom apela apoi folosind: +Și în șablon o apelăm apoi folosind: ```latte {control poll:paginator} ``` -Pentru o mai bună înțelegere, este bine de știut cum este compilat tag-ul în cod PHP. +Pentru o mai bună înțelegere, este bine să știm cum se traduce acest tag în PHP. ```latte {control poll} {control poll:paginator 123, 'hello'} ``` -Acesta se compilează la: +se traduce ca: ```php $control->getComponent('poll')->render(); $control->getComponent('poll')->renderPaginator(123, 'hello'); ``` -`getComponent()` returnează componenta `poll` și apoi este apelată metoda `render()` sau `renderPaginator()`, respectiv, este apelată pe aceasta. +Metoda `getComponent()` returnează componenta `poll` și pe această componentă apelează metoda `render()`, respectiv `renderPaginator()` dacă este specificat un alt mod de redare în tag după două puncte. .[caution] -Dacă oriunde în partea parametrilor se folosește **`=>`**, toți parametrii vor fi înfășurați cu o matrice și vor fi trecuți ca prim argument: +Atenție, dacă oriunde în parametri apare **`=>`**, toți parametrii vor fi împachetați într-un array și transmiși ca prim argument: ```latte {control poll, id: 123, message: 'hello'} ``` -se compilează la: +se traduce ca: ```php $control->getComponent('poll')->render(['id' => 123, 'message' => 'hello']); @@ -162,66 +162,66 @@ Redarea sub-componentei: {control cartControl-someForm} ``` -se compilează la: +se traduce ca: ```php $control->getComponent("cartControl-someForm")->render(); ``` -Componentele, precum prezentatorii, transmit automat mai multe variabile utile șabloanelor: +Componentele, la fel ca presenterele, transmit automat către șabloane câteva variabile utile: -- `$basePath` este o cale URL absolută către directorul rădăcină (de exemplu, `/CD-collection`) -- `$baseUrl` este o adresă URL absolută către directorul rădăcină (de exemplu `http://localhost/CD-collection`) -- `$user` este un obiect care [reprezintă utilizatorul |security:authentication] -- `$presenter` este prezentatorul curent +- `$basePath` este calea URL absolută către directorul rădăcină (de ex. `/eshop`) +- `$baseUrl` este URL-ul absolut către directorul rădăcină (de ex. `http://localhost/eshop`) +- `$user` este obiectul [reprezentând utilizatorul |security:authentication] +- `$presenter` este presenterul curent - `$control` este componenta curentă -- `$flashes` lista de [mesaje |#flash-messages] trimise prin metoda `flashMessage()` +- `$flashes` array de [mesaje |#Mesaje flash] trimise de funcția `flashMessage()` -Semnal .[#toc-signal] -===================== +Semnal +====== -Știm deja că navigarea în aplicația Nette constă în crearea de legături sau redirecționarea către perechi `Presenter:action`. Dar ce se întâmplă dacă dorim doar să efectuăm o acțiune pe **pagina curentă**? De exemplu, să schimbăm ordinea de sortare a coloanei din tabel; să ștergem un element; să schimbăm modul lumină/întuneric; să trimitem formularul; să votăm în sondaj; etc. +Știm deja că navigarea într-o aplicație Nette constă în legarea sau redirecționarea către perechi `Presenter:action`. Dar ce se întâmplă dacă vrem doar să executăm o acțiune pe **pagina curentă**? De exemplu, să schimbăm ordonarea coloanelor într-un tabel; să ștergem un element; să comutăm între modul luminos/întunecat; să trimitem un formular; să votăm într-un sondaj; etc. -Acest tip de cerere se numește semnal. Și, la fel ca și acțiunile, invocă metode `action()` sau `render()`, semnalele apelează metode `handle()`. În timp ce conceptul de acțiune (sau vizualizare) se referă doar la prezentatori, semnalele se aplică tuturor componentelor. Și, prin urmare, și prezentatorilor, deoarece `UI\Presenter` este un descendent al `UI\Control`. +Acest tip de cereri se numește semnale. Și la fel cum acțiunile apelează metodele `action()` sau `render()`, semnalele apelează metodele `handle()`. În timp ce conceptul de acțiune (sau view) este legat strict doar de presentere, semnalele se referă la toate componentele. Și, prin urmare, și la presentere, deoarece `UI\Presenter` este un descendent al `UI\Control`. ```php public function handleClick(int $x, int $y): void { - // ... procesarea semnalelor ... + // ... procesarea semnalului ... } ``` -Legătura care apelează semnalul este creată în mod obișnuit, adică în șablon prin atributul `n:href` sau tag-ul `{link}`, în cod prin metoda `link()`. Mai multe informații în capitolul [Crearea de legături URL |creating-links#Links to Signal]. +Un link care apelează un semnal se creează în mod obișnuit, adică în șablon prin atributul `n:href` sau tag-ul `{link}`, în cod prin metoda `link()`. Mai multe în capitolul [Crearea linkurilor URL |creating-links#Linkuri către semnal]. ```latte click here ``` -Semnalul este întotdeauna apelat în prezentatorul și vizualizarea curente, astfel încât nu este posibilă crearea unei legături către semnal în alt prezentator/acțiune. +Semnalul se apelează întotdeauna pe presenterul și acțiunea curentă, nu este posibil să-l apelezi pe alt presenter sau altă acțiune. -Astfel, semnalul determină reîncărcarea paginii exact în același mod ca în cererea inițială, doar că, în plus, apelează metoda de tratare a semnalului cu parametrii corespunzători. În cazul în care metoda nu există, se aruncă excepția [api:Nette\Application\UI\BadSignalException], care este afișată utilizatorului sub forma paginii de eroare 403 Forbidden. +Semnalul provoacă, așadar, reîncărcarea paginii la fel ca la cererea inițială, doar că în plus apelează metoda de gestionare a semnalului cu parametrii corespunzători. Dacă metoda nu există, se aruncă o excepție [api:Nette\Application\UI\BadSignalException], care este afișată utilizatorului ca o pagină de eroare 403 Forbidden. -Fragmente și AJAX .[#toc-snippets-and-ajax] -=========================================== +Snippets și AJAX +================ -Semnalele vă pot aminti puțin de AJAX: gestionari care sunt apelați în pagina curentă. Și aveți dreptate, semnalele sunt foarte des apelate folosind AJAX, iar apoi transmitem doar părțile modificate ale paginii către browser. Acestea se numesc snippets. Mai multe informații pot fi găsite pe [pagina despre AJAX |ajax]. +Semnalele vă pot aminti puțin de AJAX: handlere care sunt apelate pe pagina curentă. Și aveți dreptate, semnalele sunt într-adevăr adesea apelate folosind AJAX și ulterior transmitem către browser doar părțile modificate ale paginii. Adică așa-numitele snippets. Mai multe informații găsiți pe [pagina dedicată AJAX |ajax]. -Mesaje Flash .[#toc-flash-messages] -=================================== +Mesaje flash +============ -O componentă dispune de propria sa memorie de mesaje flash, independent de prezentator. Acestea sunt mesaje care, de exemplu, informează cu privire la rezultatul operațiunii. O caracteristică importantă a mesajelor flash este că acestea sunt disponibile în șablon chiar și după redirecționare. Chiar și după ce au fost afișate, ele vor rămâne în viață încă 30 de secunde - de exemplu, în cazul în care utilizatorul ar reîmprospăta involuntar pagina - mesajul nu se va pierde. +Componenta are propriul său spațiu de stocare pentru mesaje flash, independent de presenter. Acestea sunt mesaje care, de exemplu, informează despre rezultatul unei operațiuni. O caracteristică importantă a mesajelor flash este că sunt disponibile în șablon chiar și după redirecționare. Chiar și după afișare, rămân active încă 30 de secunde – de exemplu, în cazul în care utilizatorul ar reîncărca pagina din cauza unei erori de transmisie - mesajul nu dispare imediat. -Trimiterea se face prin metoda [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Primul parametru este textul mesajului sau obiectul `stdClass` care reprezintă mesajul. Al doilea parametru opțional este tipul acestuia (error, warning, info, etc.). Metoda `flashMessage()` returnează o instanță a mesajului flash ca obiect stdClass căruia i se pot transmite informații. +Trimiterea este gestionată de metoda [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Primul parametru este textul mesajului sau un obiect `stdClass` reprezentând mesajul. Al doilea parametru opțional este tipul său (error, warning, info etc.). Metoda `flashMessage()` returnează instanța mesajului flash ca obiect `stdClass`, căruia i se pot adăuga informații suplimentare. ```php -$this->flashMessage('Item was deleted.'); -$this->redirect(/* ... */); // și redirecționarea +$this->flashMessage('Elementul a fost șters.'); +$this->redirect(/* ... */); // și redirecționăm ``` -În șablon, aceste mesaje sunt disponibile în variabila `$flashes` ca obiecte `stdClass`, care conțin proprietățile `message` (text mesaj), `type` (tip mesaj) și pot conține informațiile deja menționate despre utilizator. Le desenăm după cum urmează: +În șablon, aceste mesaje sunt disponibile în variabila `$flashes` ca obiecte `stdClass`, care conțin proprietățile `message` (textul mesajului), `type` (tipul mesajului) și pot conține informațiile utilizatorului menționate anterior. Le redăm, de exemplu, astfel: ```latte {foreach $flashes as $flash} @@ -230,44 +230,66 @@ $this->redirect(/* ... */); // și redirecționarea ``` -Parametrii persistenți .[#toc-persistent-parameters] -==================================================== +Redirecționare după semnal +========================== + +După procesarea semnalului componentei, urmează adesea o redirecționare. Este o situație similară cu formularele - după trimiterea lor, redirecționăm, de asemenea, pentru ca la reîncărcarea paginii în browser să nu se trimită din nou datele. + +```php +$this->redirect('this') // redirecționează către presenterul și acțiunea curentă +``` + +Deoarece componenta este un element reutilizabil și, de obicei, nu ar trebui să aibă o legătură directă cu presentere specifice, metodele `redirect()` și `link()` interpretează automat parametrul ca un semnal al componentei: + +```php +$this->redirect('click') // redirecționează către semnalul 'click' al aceleiași componente +``` + +Dacă aveți nevoie să redirecționați către un alt presenter sau acțiune, puteți face acest lucru prin intermediul presenterului: + +```php +$this->getPresenter()->redirect('Product:show'); // redirecționează către alt presenter/acțiune +``` + + +Parametri persistenți +===================== -Parametrii persistenți sunt utilizați pentru a menține starea componentelor între diferite cereri. Valoarea lor rămâne aceeași chiar și după ce se face clic pe un link. Spre deosebire de datele de sesiune, aceștia sunt transferați în URL. Și sunt transferați automat, inclusiv legăturile create în alte componente din aceeași pagină. +Parametrii persistenți sunt utilizați pentru a menține starea în componente între diferite cereri. Valoarea lor rămâne aceeași chiar și după ce se face clic pe un link. Spre deosebire de datele din sesiune, acestea sunt transmise în URL. Și acest lucru se întâmplă complet automat, inclusiv pentru linkurile create în alte componente de pe aceeași pagină. -De exemplu, aveți o componentă de paginare a conținutului. Pot exista mai multe astfel de componente pe o pagină. Și doriți ca toate componentele să rămână pe pagina lor curentă atunci când faceți clic pe link. Prin urmare, facem din numărul paginii (`page`) un parametru persistent. +Aveți, de exemplu, o componentă pentru paginarea conținutului. Pot exista mai multe astfel de componente pe o pagină. Și dorim ca, după ce se face clic pe un link, toate componentele să rămână pe pagina lor curentă. De aceea, facem din numărul paginii (`page`) un parametru persistent. -Crearea unui parametru persistent este extrem de ușoară în Nette. Trebuie doar să creați o proprietate publică și să o marcați cu atributul: (anterior se folosea `/** @persistent */` ) +Crearea unui parametru persistent în Nette este extrem de simplă. Este suficient să creați o proprietate publică și să o marcați cu un atribut: (anterior se folosea `/** @persistent */`) ```php -use Nette\Application\Attributes\Persistent; // această linie este importantă +use Nette\Application\Attributes\Persistent; // această linie este importantă class PaginatingControl extends Control { #[Persistent] - public int $page = 1; // trebuie să fie publice + public int $page = 1; // trebuie să fie publică } ``` -Vă recomandăm să includeți tipul de date (de exemplu, `int`) cu proprietatea și puteți include și o valoare implicită. Valorile parametrilor pot fi [validate |#Validation of Persistent Parameters]. +Pentru proprietate, recomandăm să specificați și tipul de date (de ex. `int`) și puteți specifica și o valoare implicită. Valorile parametrilor pot fi [validate |#Validarea parametrilor persistenți]. -Puteți modifica valoarea unui parametru persistent atunci când creați o legătură: +La crearea unui link, valoarea parametrului persistent poate fi modificată: ```latte next ``` -Sau poate fi *restat*, adică eliminat din URL. În acest caz, va lua valoarea implicită: +Sau poate fi *resetat*, adică eliminat din URL. Atunci va lua valoarea sa implicită: ```latte reset ``` -Componente persistente .[#toc-persistent-components] -==================================================== +Componente persistente +====================== -Nu numai parametrii, ci și componentele pot fi persistente. Parametrii persistenți ai acestora sunt, de asemenea, transferați între diferite acțiuni sau între diferiți prezentatori. Marcăm componentele persistente cu această adnotare pentru clasa prezentator. De exemplu, aici marcăm componentele `calendar` și `poll` după cum urmează: +Nu doar parametrii, ci și componentele pot fi persistente. La o astfel de componentă, parametrii săi persistenți sunt transmiși și între diferite acțiuni ale presenterului sau între mai mulți presenteri. Componentele persistente le marcăm cu o adnotare la clasa presenterului. De exemplu, astfel marcăm componentele `calendar` și `poll`: ```php /** @@ -278,9 +300,9 @@ class DefaultPresenter extends Nette\Application\UI\Presenter } ``` -Nu trebuie să marcați subcomponentele ca fiind persistente, acestea sunt persistente în mod automat. +Subcomponentele din interiorul acestor componente nu trebuie marcate, devin și ele persistente. -În PHP 8, puteți utiliza, de asemenea, atribute pentru a marca componentele persistente: +În PHP 8, puteți utiliza și atribute pentru a marca componentele persistente: ```php use Nette\Application\Attributes\Persistent; @@ -292,35 +314,35 @@ class DefaultPresenter extends Nette\Application\UI\Presenter ``` -Componente cu dependențe .[#toc-components-with-dependencies] -============================================================= +Componente cu dependențe +======================== -Cum să creați componente cu dependențe fără a "încurca" prezentatorii care le vor folosi? Datorită caracteristicilor inteligente ale containerului DI din Nette, ca și în cazul utilizării serviciilor tradiționale, putem lăsa cea mai mare parte a muncii în seama cadrului. +Cum să creăm componente cu dependențe fără a ne „murdări” presenterele care le vor utiliza? Datorită proprietăților inteligente ale containerului DI din Nette, la fel ca la utilizarea serviciilor clasice, putem lăsa majoritatea muncii pe seama framework-ului. -Să luăm ca exemplu o componentă care are o dependență față de serviciul `PollFacade`: +Să luăm ca exemplu o componentă care are o dependență de serviciul `PollFacade`: ```php class PollControl extends Control { public function __construct( - private int $id, // Id-ul unui sondaj, pentru care este creată componenta + private int $id, // Id-ul sondajului pentru care creăm componenta private PollFacade $facade, ) { } public function handleVote(int $voteId): void { - $this->facade->vote($id, $voteId); + $this->facade->vote($this->id, $voteId); // ... } } ``` -Dacă am scrie un serviciu clasic, nu ar fi nimic de care să ne facem griji. Containerul DI s-ar ocupa în mod invizibil de trecerea tuturor dependențelor. Dar, de obicei, ne ocupăm de componente prin crearea unei noi instanțe a acestora direct în prezentator, în [metodele factory |#factory methods] `createComponent...()`. Dar transmiterea tuturor dependențelor tuturor componentelor către prezentator, pentru ca apoi să le transmiteți componentelor, este greoaie. Iar cantitatea de cod scrisă... +Dacă am scrie un serviciu clasic, nu ar fi nimic de rezolvat. Containerul DI s-ar ocupa invizibil de transmiterea tuturor dependențelor. Însă, cu componentele, de obicei procedăm astfel încât creăm noua lor instanță direct în presenter în [metodele factory |#Metode factory] `createComponent…()`. Dar transmiterea tuturor dependențelor tuturor componentelor către presenter, pentru a le transmite apoi componentelor, este greoaie. Și cât cod scris… -Întrebarea logică este: de ce să nu înregistrăm pur și simplu componenta ca serviciu clasic, să o transmitem prezentatorului și apoi să o returnăm în metoda `createComponent...()`? Dar această abordare este nepotrivită, deoarece dorim să putem crea componenta de mai multe ori. +Întrebarea logică este, de ce nu înregistrăm pur și simplu componenta ca un serviciu clasic, nu o transmitem către presenter și apoi în metoda `createComponent…()` nu o returnăm? O astfel de abordare este însă nepotrivită, deoarece dorim să avem posibilitatea de a crea componenta chiar și de mai multe ori. -Soluția corectă este să scriem o fabrică pentru componentă, adică o clasă care să creeze componenta pentru noi: +Soluția corectă este să scriem pentru componentă o fabrică, adică o clasă care ne va crea componenta: ```php class PollControlFactory @@ -337,17 +359,17 @@ class PollControlFactory } ``` -Acum înregistrăm serviciul nostru în containerul DI pentru configurare: +Astfel înregistrăm fabrica în containerul nostru în configurație: ```neon services: - PollControlFactory ``` -În cele din urmă, vom utiliza această fabrică în prezentatorul nostru: +și în final o folosim în presenterul nostru: ```php -class PollPresenter extends Nette\UI\Application\Presenter +class PollPresenter extends Nette\Application\UI\Presenter { public function __construct( private PollControlFactory $pollControlFactory, @@ -356,13 +378,13 @@ class PollPresenter extends Nette\UI\Application\Presenter protected function createComponentPollControl(): PollControl { - $pollId = 1; // putem trece parametrul nostru + $pollId = 1; // putem transmite parametrul nostru return $this->pollControlFactory->create($pollId); } } ``` -Lucrul minunat este că Nette DI poate [genera |dependency-injection:factory] astfel de fabrici simple, astfel încât, în loc să scrieți întregul cod, trebuie doar să scrieți interfața sa: +Minunat este că Nette DI poate [genera |dependency-injection:factory] astfel de fabrici simple, așa că în loc de întregul său cod, este suficient să scriem doar interfața sa: ```php interface PollControlFactory @@ -371,21 +393,21 @@ interface PollControlFactory } ``` -Asta e tot. Nette implementează intern această interfață și o injectează în prezentatorul nostru, unde o putem folosi. De asemenea, trece în mod magic parametrul `$id` și instanța clasei `PollFacade` în componenta noastră. +Și asta e tot. Nette implementează intern această interfață și o transmite către presenter, unde o putem deja utiliza. Magic, ne adaugă și parametrul `$id` și instanța clasei `PollFacade` în componenta noastră. -Componente în profunzime .[#toc-components-in-depth] -==================================================== +Componente în profunzime +======================== -Componentele într-o aplicație Nette sunt părțile reutilizabile ale unei aplicații web pe care le încorporăm în pagini, care reprezintă subiectul acestui capitol. Care sunt mai exact capacitățile unei astfel de componente? +Componentele din Nette Application reprezintă părți reutilizabile ale aplicației web, pe care le inserăm în pagini și cărora, de altfel, le este dedicat întregul acest capitol. Ce abilități exacte are o astfel de componentă? -1) este redabilă într-un șablon -2) știe ce parte din ea însăși trebuie să redea în timpul unei [cereri AJAX |ajax#invalidation] (fragmente) -3) are capacitatea de a stoca starea sa într-un URL (parametri persistenți) -4) are capacitatea de a răspunde la acțiunile utilizatorului (semnale) -5) creează o structură ierarhică (în care rădăcina este prezentatorul) +1) este redabilă în șablon +2) știe [ce parte a sa |ajax#Snippets] trebuie să redea la o cerere AJAX (snippets) +3) are capacitatea de a-și salva starea în URL (parametri persistenți) +4) are capacitatea de a reacționa la acțiunile utilizatorului (semnale) +5) creează o structură ierarhică (unde rădăcina este presenterul) -Fiecare dintre aceste funcții este gestionată de una dintre clasele din linia de moștenire. Redarea (1 + 2) este gestionată de [api:Nette\Application\UI\Control], încorporarea în [ciclul de viață |presenters#life-cycle-of-presenter] (3, 4) de către clasa [api:Nette\Application\UI\Component], iar crearea structurii ierarhice (5) de către clasele [Container și Component |component-model:]. +Fiecare dintre aceste funcții este gestionată de una dintre clasele liniei ereditare. Redarea (1 + 2) este responsabilitatea [api:Nette\Application\UI\Control], includerea în [ciclul de viață |presenters#Ciclul de viață al presenterului] (3, 4) a clasei [api:Nette\Application\UI\Component] și crearea structurii ierarhice (5) a claselor [Container și Component |component-model:]. ``` Nette\ComponentModel\Component { IComponent } @@ -400,18 +422,18 @@ Nette\ComponentModel\Component { IComponent } ``` -Ciclul de viață al componentei .[#toc-life-cycle-of-component] --------------------------------------------------------------- +Ciclul de viață al componentei +------------------------------ [* lifecycle-component.svg *] *** *Ciclul de viață al componentei* .<> -Validarea parametrilor persistenți .[#toc-validation-of-persistent-parameters] ------------------------------------------------------------------------------- +Validarea parametrilor persistenți +---------------------------------- -Valorile [parametrilor persistenți |#persistent parameters] primite de la URL-uri sunt scrise în proprietăți prin metoda `loadState()`. Aceasta verifică, de asemenea, dacă tipul de date specificat pentru proprietate se potrivește, în caz contrar se va răspunde cu o eroare 404 și pagina nu va fi afișată. +Valorile [parametrilor persistenți |#Parametri persistenți] primite din URL sunt scrise în proprietăți de către metoda `loadState()`. Aceasta verifică, de asemenea, dacă tipul de date specificat la proprietate corespunde, altfel răspunde cu eroarea 404 și pagina nu se afișează. -Nu vă încredeți niciodată orbește în parametrii persistenți, deoarece aceștia pot fi ușor suprascriși de către utilizator în URL. De exemplu, acesta este modul în care verificăm dacă numărul paginii `$this->page` este mai mare decât 0. O modalitate bună de a face acest lucru este de a suprascrie metoda `loadState()` menționată mai sus: +Nu credeți niciodată orbește în parametrii persistenți, deoarece pot fi ușor suprascriși de utilizator în URL. Astfel, de exemplu, verificăm dacă numărul paginii `$this->page` este mai mare decât 0. O cale potrivită este să suprascriem metoda menționată `loadState()`: ```php class PaginatingControl extends Control @@ -421,8 +443,8 @@ class PaginatingControl extends Control public function loadState(array $params): void { - parent::loadState($params); // aici este setat $this->page - // urmează verificarea valorii utilizatorului: + parent::loadState($params); // aici se setează $this->page + // urmează controlul propriu al valorii: if ($this->page < 1) { $this->error(); } @@ -430,27 +452,27 @@ class PaginatingControl extends Control } ``` -Procesul opus, și anume colectarea valorilor din proprietățile persistente, este gestionat de metoda `saveState()`. +Procesul invers, adică colectarea valorilor din proprietățile persistente, este responsabilitatea metodei `saveState()`. -Semnale în profunzime .[#toc-signals-in-depth] ----------------------------------------------- +Semnale în profunzime +--------------------- -Un semnal determină o reîncărcare a paginii ca și cererea inițială (cu excepția AJAX) și invocă metoda `signalReceived($signal)` a cărei implementare implicită în clasa `Nette\Application\UI\Component` încearcă să apeleze o metodă compusă din cuvintele `handle{Signal}`. Prelucrarea ulterioară se bazează pe obiectul dat. Obiectele care sunt descendente ale `Component` (și anume `Control` și `Presenter`) încearcă să apeleze `handle{Signal}` cu parametrii relevanți. +Un semnal provoacă reîncărcarea paginii exact la fel ca la cererea inițială (cu excepția cazului în care este apelat prin AJAX) și apelează metoda `signalReceived($signal)`, a cărei implementare implicită în clasa `Nette\Application\UI\Component` încearcă să apeleze o metodă compusă din cuvintele `handle{signal}`. Procesarea ulterioară depinde de obiectul respectiv. Obiectele care moștenesc de la `Component` (adică `Control` și `Presenter`) reacționează încercând să apeleze metoda `handle{signal}` cu parametrii corespunzători. -Cu alte cuvinte: se ia definiția metodei `handle{Signal}` și toți parametrii care au fost primiți în cerere sunt potriviți cu parametrii metodei. Aceasta înseamnă că parametrul `id` din URL se potrivește cu parametrul metodei `$id`, `something` cu `$something` și așa mai departe. Iar dacă metoda nu există, metoda `signalReceived` aruncă o [excepție |api:Nette\Application\UI\BadSignalException]. +Cu alte cuvinte: se ia definiția funcției `handle{signal}` și toți parametrii care au venit cu cererea, iar argumentelor li se atribuie parametrii din URL după nume și se încearcă apelarea metodei respective. De exemplu, ca parametru `$id` se transmite valoarea din parametrul `id` din URL, ca `$something` se transmite `something` din URL, etc. Și dacă metoda nu există, metoda `signalReceived` aruncă o [excepție |api:Nette\Application\UI\BadSignalException]. -Semnalul poate fi recepționat de orice componentă, prezentator al unui obiect care implementează interfața `SignalReceiver`, dacă este conectat la arborele de componente. +Semnalul poate fi primit de orice componentă, presenter sau obiect care implementează interfața `SignalReceiver` și este conectat la arborele de componente. -Principalii receptori de semnale sunt `Presenters` și componentele vizuale care extind `Control`. Un semnal este un semn pentru un obiect că trebuie să facă ceva - sondajul contează un vot din partea utilizatorului, caseta cu noutăți trebuie să se desfășoare, formularul a fost trimis și trebuie să proceseze datele și așa mai departe. +Principalii destinatari ai semnalelor vor fi `Presenterele` și componentele vizuale care moștenesc de la `Control`. Semnalul trebuie să servească drept semn pentru obiect că trebuie să facă ceva – sondajul trebuie să numere votul utilizatorului, blocul cu știri trebuie să se extindă și să afișeze de două ori mai multe știri, formularul a fost trimis și trebuie să proceseze datele și așa mai departe. -URL-ul pentru semnal este creat cu ajutorul metodei [Component::link() |api:Nette\Application\UI\Component::link()]. Ca parametru `$destination` transmitem șirul de caractere `{signal}!`, iar ca `$args` o matrice de argumente pe care dorim să le transmitem gestionarului de semnal. Parametrii semnalului sunt atașați la URL-ul prezentatorului/vederea curentă. **Parametrul `?do` din URL determină semnalul apelat.** +URL-ul pentru semnal îl creăm folosind metoda [Component::link() |api:Nette\Application\UI\Component::link()]. Ca parametru `$destination` transmitem șirul `{signal}!` și ca `$args` un array de argumente pe care dorim să le transmitem semnalului. Semnalul se apelează întotdeauna pe presenterul și acțiunea curentă cu parametrii curenți, parametrii semnalului se adaugă doar. În plus, se adaugă chiar la început **parametrul `?do`, care specifică semnalul**. -Formatul său este `{signal}` sau `{signalReceiver}-{signal}`. `{signalReceiver}` este numele componentei din prezentator. Acesta este motivul pentru care cratima (inexact liniuță) nu poate fi prezentă în numele componentelor - este utilizată pentru a diviza numele componentei și al semnalului, dar este posibilă compunerea mai multor componente. +Formatul său este fie `{signal}`, fie `{signalReceiver}-{signal}`. `{signalReceiver}` este numele componentei în presenter. De aceea, în numele componentei nu poate exista cratimă – se folosește pentru a separa numele componentei și semnalul, însă este posibil să se imbricheze astfel mai multe componente. -Metoda [isSignalReceiver() |api:Nette\Application\UI\Presenter::isSignalReceiver()] verifică dacă o componentă (primul argument) este un receptor al unui semnal (al doilea argument). Al doilea argument poate fi omis - atunci se află dacă componenta este un receptor al oricărui semnal. În cazul în care al doilea parametru este `true`, se verifică dacă componenta sau descendenții săi sunt receptorii unui semnal. +Metoda [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] verifică dacă componenta (primul argument) este destinatarul semnalului (al doilea argument). Al doilea argument poate fi omis – atunci verifică dacă componenta este destinatarul oricărui semnal. Ca al doilea parametru se poate specifica `true` și astfel se verifică dacă destinatarul este nu numai componenta specificată, ci și oricare dintre descendenții săi. -În orice fază care precede `handle{Signal}` poate fi semnalul executat manual prin apelarea metodei [processSignal() |api:Nette\Application\UI\Presenter::processSignal()] care își asumă responsabilitatea pentru executarea semnalului. Preia componenta receptoare (dacă nu este setată este chiar prezentatorul) și îi trimite semnalul. +În orice fază anterioară `handle{signal}` putem executa semnalul manual apelând metoda [processSignal()|api:Nette\Application\UI\Presenter::processSignal()], care se ocupă de gestionarea semnalului – ia componenta care a fost desemnată ca destinatar al semnalului (dacă nu este specificat un destinatar al semnalului, acesta este presenterul însuși) și îi trimite semnalul. Exemplu: @@ -460,4 +482,4 @@ if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, ' } ``` -Semnalul este executat prematur și nu va mai fi apelat din nou. +Astfel, semnalul este executat prematur și nu va mai fi apelat din nou. diff --git a/application/ro/configuration.texy b/application/ro/configuration.texy index 4acdb8af67..1645a4868b 100644 --- a/application/ro/configuration.texy +++ b/application/ro/configuration.texy @@ -1,58 +1,71 @@ -Configurarea aplicației -*********************** +Configurația aplicațiilor +************************* .[perex] -Prezentare generală a opțiunilor de configurare pentru aplicația Nette. +Prezentare generală a opțiunilor de configurare pentru aplicațiile Nette. -Aplicație .[#toc-application] -============================= +Application +=========== ```neon application: # afișează panoul "Nette Application" în Tracy BlueScreen? - debugger: ... # (bool) valoarea implicită este true + debugger: ... # (bool) implicit este true - # error-presenter va fi apelat în caz de eroare? - catchExceptions: ... # (bool) implicit la true în modul de producție + # se va apela error-presenter în caz de eroare? + # are efect doar în modul de dezvoltare + catchExceptions: ... # (bool) implicit este true - # numele prezentatorului de erori - errorPresenter: Error # (string) valoarea implicită este "Nette:Error" (Nette:Error) + # numele error-presenterului + errorPresenter: Error # (string|array) implicit este 'Nette:Error' - # definește regulile de rezolvare a numelui prezentatorului la o clasă + # definește aliasuri pentru presentere și acțiuni + aliases: ... + + # definește reguli pentru traducerea numelui presenterului în clasă mapping: ... - # legăturile proaste generează avertismente? - # are efect numai în modul dezvoltator - silentLinks: ... # (bool) valoarea implicită este false + # linkurile invalide nu generează avertismente? + # are efect doar în modul de dezvoltare + silentLinks: ... # (bool) implicit este false ``` -Deoarece error-presenters nu este apelat în mod implicit în modul de dezvoltare, iar erorile sunt afișate de Tracy, schimbarea valorii `catchExceptions` în `true` ajută la verificarea funcționării corecte a error-presenters în timpul dezvoltării. +De la versiunea `nette/application` 3.2 se poate defini o pereche de error-presentere: -Opțiunea `silentLinks` determină modul în care se comportă Nette în modul de dezvoltare atunci când generarea legăturilor eșuează (de exemplu, pentru că nu există un prezentator etc.). Valoarea implicită `false` înseamnă că Nette declanșează `E_USER_WARNING`. Setarea la `true` suprimă acest mesaj de eroare. Într-un mediu de producție, `E_USER_WARNING` este întotdeauna invocat. De asemenea, putem influența acest comportament prin setarea variabilei presenter [$invalidLinkMode |creating-links#Invalid Links]. +```neon +application: + errorPresenter: + 4xx: Error4xx # pentru excepția Nette\Application\BadRequestException + 5xx: Error5xx # pentru celelalte excepții +``` - [Cartografierea definește regulile |modules#mapping] prin care numele clasei este derivat din numele prezentatorului. +Opțiunea `silentLinks` determină cum se comportă Nette în modul de dezvoltare când generarea unui link eșuează (de exemplu, pentru că nu există presenterul etc.). Valoarea implicită `false` înseamnă că Nette va arunca o eroare `E_USER_WARNING`. Setarea la `true` va suprima acest mesaj de eroare. În mediul de producție, `E_USER_WARNING` este întotdeauna aruncat. Acest comportament poate fi, de asemenea, influențat prin setarea variabilei presenterului [$invalidLinkMode |creating-links#Linkuri invalide]. +[Aliasurile simplifică legarea |creating-links#Aliasuri] la presenterele utilizate frecvent. -Înregistrarea automată a prezentatorilor .[#toc-automatic-registration-of-presenters] -------------------------------------------------------------------------------------- +[Maparea definește reguli |directory-structure#Maparea presenterelor], conform cărora din numele presenterului se deduce numele clasei. -Nette adaugă automat prezentatorii ca servicii în containerul DI, ceea ce accelerează semnificativ crearea acestora. Modul în care Nette găsește prezentatorii poate fi configurat: + +Înregistrarea automată a presenterelor +-------------------------------------- + +Nette adaugă automat presenterele ca servicii în containerul DI, ceea ce accelerează semnificativ crearea lor. Modul în care Nette localizează presenterele poate fi configurat: ```neon application: - # pentru a căuta prezentatori în harta clasei Composer? - scanComposer: ... # (bool) valoarea implicită este true + # caută presentere în Composer class map? + scanComposer: ... # (bool) implicit este true - # o mască care trebuie să se potrivească cu numele clasei și al fișierului - scanFilter: ... # (string) valoarea implicită este "*Presenter". + # masca pe care trebuie să o respecte numele clasei și al fișierului + scanFilter: ... # (string) implicit este '*Presenter' - # în ce directoare să se caute prezentatorii? - scanDirs: # (string[]|false) valoarea implicită este "%appDir%". + # în ce directoare să caute presentere? + scanDirs: # (string[]|false) implicit este '%appDir%' - %vendorDir%/mymodule ``` -Directoarele listate în `scanDirs` nu înlocuiesc valoarea implicită `%appDir%`, ci o completează, astfel încât `scanDirs` va conține atât căile `%appDir%` cât și `%vendorDir%/mymodule`. Dacă dorim să suprascriem directorul implicit, folosim [semnul exclamării |dependency-injection:configuration#Merging]: +Directoarele specificate în `scanDirs` nu suprascriu valoarea implicită `%appDir%`, ci o completează, deci `scanDirs` va conține ambele căi `%appDir%` și `%vendorDir%/mymodule`. Dacă dorim să omitem directorul implicit, folosim [semnul exclamării |dependency-injection:configuration#Combinare], care suprascrie valoarea: ```neon application: @@ -60,64 +73,73 @@ application: - %vendorDir%/mymodule ``` -Scanarea directoarelor poate fi dezactivată prin setarea false. Nu recomandăm suprimarea completă a adăugării automate a prezentatorilor, în caz contrar performanța aplicației va fi redusă. +Scanarea directoarelor poate fi dezactivată specificând valoarea false. Nu recomandăm suprimarea completă a adăugării automate a presenterelor, deoarece altfel performanța aplicației va scădea. -Latte .[#toc-latte] -=================== +Șabloane Latte +============== -Această setare afectează în mod global comportamentul lui Latte în componente și prezentatori. +Prin această setare se poate influența global comportamentul Latte în componente și presentere. ```neon latte: - # afișează panoul Latte în bara Tracy pentru șablonul principal (true) sau pentru toate componentele (all)? - debugger: ... # (true|false|'all') valoarea implicită este true + # afișează panoul Latte în Tracy Bar pentru șablonul principal (true) sau toate componentele (all)? + debugger: ... # (true|false|'all') implicit este true + + # generează șabloane cu antetul declare(strict_types=1) + strictTypes: ... # (bool) implicit este false - # generează șabloane cu declare(strict_types=1) - strictTypes: ... # (bool) valoarea implicită este false + # activează modul [parser strict |latte:develop#striktní režim] + strictParsing: ... # (bool) implicit este false - # clasa lui $this->template - templateClass: App\MyTemplateClass # valoarea implicită este Nette\Bridges\ApplicationLatte\DefaultTemplate + # activează [verificarea codului generat |latte:develop#Kontrola vygenerovaného kódu] + phpLinter: ... # (string) implicit este null + + # setează locale + locale: cs_CZ # (string) implicit este null + + # clasa obiectului $this->template + templateClass: App\MyTemplateClass # implicit este Nette\Bridges\ApplicationLatte\DefaultTemplate ``` -Dacă utilizați Latte versiunea 3, puteți adăuga o nouă [extensie |latte:creating-extension] utilizând: +Dacă utilizați Latte versiunea 3, puteți adăuga noi [extensii |latte:extending-latte#Latte Extension] folosind: ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -/--comment - - - - +Dacă utilizați Latte versiunea 2, puteți înregistra noi tag-uri fie specificând numele clasei, fie o referință la serviciu. Implicit, se apelează metoda `install()`, dar acest lucru poate fi schimbat specificând numele altei metode: +```neon +latte: + # înregistrarea tag-urilor Latte personalizate + macros: + - App\MyLatteMacros::register # metodă statică, classname sau callable + - @App\MyLatteMacrosFactory # serviciu cu metoda install() + - @App\MyLatteMacrosFactory::register # serviciu cu metoda register() + +services: + - App\MyLatteMacrosFactory +``` - - - - -\-- - - -Rutarea .[#toc-routing] -======================= +Rutare +====== Setări de bază: ```neon routing: # afișează panoul de rutare în Tracy Bar? - debugger: ... # (bool) valoarea implicită este true + debugger: ... # (bool) implicit este true - # pentru a serializa routerul în containerul DI? - cache: ... # (bool) valoarea implicită este false + # serializează routerul în containerul DI + cache: ... # (bool) implicit este false ``` -Routerul este de obicei definit în clasa [RouterFactory |routing#Route Collection]. Alternativ, rutele pot fi definite și în configurație, utilizând perechile `mask: action`, dar această metodă nu oferă o variație atât de mare de setări: +Rutarea o definim de obicei în clasa [RouterFactory |routing#Colecție de rute]. Alternativ, rutele pot fi definite și în configurație folosind perechi `mască: acțiune`, dar această metodă nu oferă o varietate atât de largă în setări: ```neon routing: @@ -127,8 +149,8 @@ routing: ``` -Constantele .[#toc-constants] -============================= +Constante +========= Crearea constantelor PHP. @@ -137,18 +159,33 @@ constants: Foobar: 'baz' ``` -Constanta `Foobar` va fi creată după pornire. +După pornirea aplicației, va fi creată constanta `Foobar`. .[note] -Constantele nu trebuie să servească drept variabile disponibile la nivel global. Pentru a transmite valori obiectelor, utilizați [injecția de dependență |dependency-injection:passing-dependencies]. +Constantele nu ar trebui să servească drept variabile disponibile global. Pentru transmiterea valorilor către obiecte, utilizați [dependency injection |dependency-injection:passing-dependencies]. PHP === -Puteți seta directive PHP. O prezentare generală a tuturor directivelor poate fi găsită la [php.net |https://www.php.net/manual/en/ini.list.php]. +Setarea directivelor PHP. O prezentare generală a tuturor directivelor o găsiți pe [php.net |https://www.php.net/manual/en/ini.list.php]. ```neon php: date.timezone: Europe/Prague ``` + + +Servicii DI +=========== + +Aceste servicii sunt adăugate în containerul DI: + +| Nume | Tip | Descriere +|---------------------------------------------------------- +| `application.application` | [api:Nette\Application\Application] | [lansatorul întregii aplicații |how-it-works#Nette Application] +| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] +| `application.presenterFactory` | [api:Nette\Application\PresenterFactory] | fabrică de presentere +| `application.###` | [api:Nette\Application\UI\Presenter] | presentere individuale +| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | fabrică a obiectului `Latte\Engine` +| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | fabrică pentru [`$this->template` |templates] diff --git a/application/ro/creating-links.texy b/application/ro/creating-links.texy index 659cc88a08..e44ffb9998 100644 --- a/application/ro/creating-links.texy +++ b/application/ro/creating-links.texy @@ -1,160 +1,160 @@ -Crearea de legături URL -*********************** +Crearea linkurilor URL +**********************
    -Crearea de linkuri în Nette este la fel de simplă ca și cum ați arăta cu degetul. Trebuie doar să arătați cu degetul, iar cadrul va face toată munca în locul dumneavoastră. Vă vom arăta: +Crearea linkurilor în Nette este simplă, ca și cum ai arăta cu degetul. Trebuie doar să țintești și framework-ul va face toată munca pentru tine. Vom arăta: -- cum să creați legături în șabloane și în alte părți -- cum să distingem un link de pagina curentă -- ce se întâmplă cu legăturile invalide +- cum să creezi linkuri în șabloane și în altă parte +- cum să distingi un link către pagina curentă +- ce să faci cu linkurile invalide
    -Datorită [rutei bidirecționale |routing], nu va trebui niciodată să codificați URL-urile aplicațiilor în șabloane sau în cod, care se pot schimba ulterior sau pot fi complicate de compus. Trebuie doar să specificați prezentatorul și acțiunea în link, să treceți orice parametru, iar framework-ul va genera singur URL-ul. De fapt, este foarte asemănător cu apelarea unei funcții. Vă va plăcea. +Datorită [rutării bidirecționale |routing], nu va trebui niciodată să scrieți manual adresele URL ale aplicației dvs. în șabloane sau cod, adrese care s-ar putea schimba ulterior, sau să le compuneți complicat. În link este suficient să specificați presenterul și acțiunea, să transmiteți eventualii parametri și framework-ul va genera URL-ul singur. De fapt, este foarte asemănător cu apelarea unei funcții. Acest lucru vă va plăcea. -În șablonul de prezentator .[#toc-in-the-presenter-template] -============================================================ +În șablonul presenterului +========================= -De cele mai multe ori creăm linkuri în șabloane, iar un mare ajutor este atributul `n:href`: +Cel mai adesea creăm linkuri în șabloane, iar un ajutor excelent este atributul `n:href`: ```latte -detail +detaliu ``` -Rețineți că, în loc de atributul HTML `href`, am folosit [n:attribute |latte:syntax#n:attributes] `n:href`. Valoarea acestuia nu este un URL, așa cum sunteți obișnuiți cu atributul `href`, ci numele prezentatorului și acțiunea. +Observați că în loc de atributul HTML `href`, am folosit [n:atributul |latte:syntax#n:atribute] `n:href`. Valoarea sa nu este apoi URL-ul, așa cum ar fi în cazul atributului `href`, ci numele presenterului și al acțiunii. -A face clic pe un link este, pur și simplu, ceva de genul apelării unei metode `ProductPresenter::renderShow()`. Iar dacă aceasta are parametri în semnătura sa, o putem apela cu argumente: +Click-ul pe link este, simplificat spus, ceva asemănător cu apelarea metodei `ProductPresenter::renderShow()`. Și dacă are parametri în semnătura sa, o putem apela cu argumente: ```latte -detail +detaliu produs ``` -De asemenea, este posibil să se treacă parametri numiți. Următoarea legătură trece parametrul `lang` cu valoarea `en`: +Este posibil să se transmită și parametri numiți. Următorul link transmite parametrul `lang` cu valoarea `cs`: ```latte -detail +detaliu produs ``` -În cazul în care metoda `ProductPresenter::renderShow()` nu are `$lang` în semnătura sa, aceasta poate citi valoarea parametrului folosind `$lang = $this->getParameter('lang')`. +Dacă metoda `ProductPresenter::renderShow()` nu are `$lang` în semnătura sa, poate afla valoarea parametrului folosind `$lang = $this->getParameter('lang')` sau din [proprietate |presenters#Parametrii cererii]. -În cazul în care parametrii sunt stocați într-o matrice, aceștia pot fi dezvoltați cu operatorul `...` (sau cu operatorul `(expand)` în Latte 2.x): +Dacă parametrii sunt stocați într-un array, aceștia pot fi expandați cu operatorul `...` (în Latte 2.x cu operatorul `(expand)`): ```latte -{var $args = [$product->id, lang => en]} -detail +{var $args = [$product->id, lang => cs]} +detaliu produs ``` -Așa-numiții [parametri persistenți |presenters#persistent parameters] sunt, de asemenea, trecuți automat în legături. +În linkuri se transmit automat și așa-numiții [parametri persistenți |presenters#Parametri persistenți]. -Atributul `n:href` este foarte util pentru etichetele HTML ``. Dacă dorim să imprimăm link-ul în altă parte, de exemplu în text, folosim `{link}`: +Atributul `n:href` este foarte util pentru tag-urile HTML ``. Dacă dorim să afișăm linkul în altă parte, de exemplu în text, folosim `{link}`: ```latte -URL is: {link Home:default} +Adresa este: {link Home:default} ``` -În cod .[#toc-in-the-code] -========================== +În cod +====== -Metoda `link()` este utilizată pentru a crea o legătură în prezentator: +Pentru a crea un link în presenter se folosește metoda `link()`: ```php $url = $this->link('Product:show', $product->id); ``` -Parametrii pot fi, de asemenea, trecuți ca o matrice în care pot fi specificați și parametri cu nume: +Parametrii pot fi transmiși și printr-un array, unde se pot specifica și parametri numiți: ```php $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); ``` -Legăturile pot fi create și fără un prezentator, utilizând [LinkGenerator |#LinkGenerator] și metoda sa `link()`. +Linkurile pot fi create și fără presenter, pentru asta există [#LinkGenerator] și metoda sa `link()`. -Legături către prezentator .[#toc-links-to-presenter] -===================================================== +Linkuri către presenter +======================= -În cazul în care ținta legăturii este prezentatorul și acțiunea, aceasta are următoarea sintaxă: +Dacă ținta linkului este un presenter și o acțiune, are această sintaxă: ``` [//] [[[[:]module:]presenter:]action | this] [#fragment] ``` -Formatul este acceptat de toate etichetele Latte și de toate metodele presenter care lucrează cu linkuri, adică `n:href`, `{link}`, `{plink}`, , `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` și, de asemenea, [LinkGenerator |#LinkGenerator]. Deci, chiar dacă în exemple se folosește `n:href`, ar putea exista oricare dintre aceste funcții. +Formatul este suportat de toate tag-urile Latte și toate metodele presenterului care lucrează cu linkuri, adică `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` și, de asemenea, [#LinkGenerator]. Deci, chiar dacă în exemple este folosit `n:href`, ar putea fi oricare dintre funcții. -Forma de bază este, prin urmare, `Presenter:action`: +Forma de bază este deci `Presenter:action`: ```latte -home +pagina principală ``` -Dacă facem legătura cu acțiunea prezentatorului curent, putem omite numele acestuia: +Dacă facem referire la acțiunea presenterului curent, putem omite numele acestuia: ```latte -home +pagina principală ``` -Dacă acțiunea este `default`, putem omite numele, dar trebuie să păstrăm două puncte: +Dacă ținta este acțiunea `default`, o putem omite, dar două puncte trebuie să rămână: ```latte -home +pagina principală ``` -Legăturile pot indica și alte [module |modules]. Aici, legăturile se disting în relative la submodule sau absolute. Principiul este analog cu cel al căilor de acces pe disc, doar că în loc de bară oblică se folosesc două puncte. Să presupunem că actualul prezentator face parte din modulul `Front`, atunci vom scrie: +Linkurile pot, de asemenea, să direcționeze către alte [module |directory-structure#Presentere și șabloane]. Aici, linkurile se disting între cele relative către un submodul imbricat și cele absolute. Principiul este analog cu căile de pe disc, doar că în loc de slash-uri sunt două puncte. Presupunem că presenterul curent face parte din modulul `Front`, atunci scriem: ```latte -link to Front:Shop:Product:show -link to Admin:Product:show +link către Front:Shop:Product:show +link către Admin:Product:show ``` -Un caz special este [legătura către sine |#Links to Current Page]. Aici vom scrie `this` ca țintă. +Un caz special este linkul [către sine însuși |#Link către pagina curentă], când specificăm `this` ca țintă. ```latte refresh ``` -Putem crea o legătură către o anumită parte a paginii HTML prin intermediul unui așa-numit fragment după simbolul hash `#`: +Putem face referire la o anumită parte a paginii prin așa-numitul fragment după semnul diez `#`: ```latte -link to Home:default and fragment #main +link către Home:default și fragmentul #main ``` -Căi de acces absolute .[#toc-absolute-paths] -============================================ +Căi absolute +============ -Legăturile generate de `link()` sau `n:href` sunt întotdeauna căi de acces absolute (adică încep cu `/`), dar nu și URL-uri absolute cu un protocol și domeniu, cum ar fi `https://domain`. +Linkurile generate folosind `link()` sau `n:href` sunt întotdeauna căi absolute (adică încep cu caracterul `/`), dar nu URL-uri absolute cu protocol și domeniu precum `https://domain`. -Pentru a genera o adresă URL absolută, adăugați două bariere la început (de exemplu, `n:href="//Home:"`). Sau puteți comuta prezentatorul pentru a genera numai link-uri absolute, setând `$this->absoluteUrls = true`. +Pentru a genera un URL absolut, adăugați două slash-uri la început (de ex. `n:href="//Home:"`). Sau puteți comuta presenterul să genereze doar linkuri absolute setând `$this->absoluteUrls = true`. -Legătură către pagina curentă .[#toc-link-to-current-page] -========================================================== +Link către pagina curentă +========================= -Obiectivul `this` va crea un link către pagina curentă: +Ținta `this` creează un link către pagina curentă: ```latte refresh ``` -În același timp, toți parametrii specificați în semnătura de la `render()` sau `action()` sunt transferați. Astfel, dacă ne aflăm pe paginile `Product:show` și `id:123`, legătura către `this` va transfera și acest parametru. +În același timp, se transmit și toți parametrii specificați în semnătura metodei `action()` sau `render()`, dacă `action()` nu este definită. Deci, dacă suntem pe pagina `Product:show` și `id: 123`, linkul către `this` va transmite și acest parametru. -Desigur, este posibil să se specifice parametrii direct: +Desigur, este posibil să specificați parametrii direct: ```latte refresh ``` -Funcția `isLinkCurrent()` determină dacă ținta linkului este aceeași cu pagina curentă. Acest lucru poate fi utilizat, de exemplu, într-un șablon pentru a diferenția legăturile etc. +Funcția `isLinkCurrent()` verifică dacă ținta linkului este identică cu pagina curentă. Acest lucru poate fi utilizat, de exemplu, în șablon pentru a distinge linkurile etc. -Parametrii sunt aceiași ca pentru metoda `link()`, dar este posibilă și utilizarea caracterului wildcard `*` în locul unei acțiuni specifice, ceea ce înseamnă orice acțiune a prezentatorului. +Parametrii sunt aceiași ca la metoda `link()`, dar în plus este posibil să se specifice un wildcard `*` în loc de o acțiune specifică, ceea ce înseamnă orice acțiune a presenterului respectiv. ```latte {if !isLinkCurrent('Admin:login')} - Přihlaste se + Conectați-vă {/if}
  • @@ -162,58 +162,58 @@ Parametrii sunt aceiași ca pentru metoda `link()`, dar este posibilă și utili
  • ``` -O formă prescurtată poate fi utilizată în combinație cu `n:href` într-un singur element: +În combinație cu `n:href` într-un singur element, se poate folosi o formă prescurtată: ```latte -... +... ``` -Caracterul wildcard `*` înlocuiește doar acțiunea prezentatorului, nu și prezentatorul însuși. +Wildcard-ul `*` poate fi folosit doar în locul acțiunii, nu și al presenterului. -Pentru a afla dacă ne aflăm într-un anumit modul sau într-un submodul al acestuia, putem utiliza funcția `isModuleCurrent(moduleName)`. +Pentru a verifica dacă ne aflăm într-un anumit modul sau submodul al acestuia, folosim metoda `isModuleCurrent(moduleName)`. ```latte -
  • +
  • ...
  • ``` -Legături către semnal .[#toc-links-to-signal] -============================================= +Linkuri către semnal +==================== -Ținta legăturii poate fi nu numai prezentatorul și acțiunea, ci și [semnalul |components#Signal] (acestea apelează metoda `handle()`). Sintaxa este următoarea: +Ținta linkului nu trebuie să fie doar un presenter și o acțiune, ci și un [semnal |components#Semnal] (apelează metoda `handle()`). Atunci sintaxa este următoarea: ``` [//] [sub-component:]signal! [#fragment] ``` -Semnalul se distinge astfel prin semnul exclamării: +Semnalul este deci distins prin semnul exclamării: ```latte -signal +semnal ``` -De asemenea, puteți crea o legătură către semnalul subcomponentei (sau sub-componentei): +Se poate crea și un link către semnalul unei subcomponente (sau sub-subcomponente): ```latte -signal +semnal ``` -Legături în componentă .[#toc-links-in-component] -================================================= +Linkuri în componentă +===================== -Deoarece [componentele |components] sunt unități separate reutilizabile care nu ar trebui să aibă relații cu prezentatorii din jur, legăturile funcționează puțin diferit. Atributul Latte `n:href` și tag-ul `{link}` și metodele componentelor, cum ar fi `link()` și altele, consideră întotdeauna ținta **ca nume de semnal**. Prin urmare, nu este necesar să se utilizeze un semn de exclamare: +Deoarece [componentele|components] sunt unități separate, reutilizabile, care nu ar trebui să aibă nicio legătură cu presenterele din jur, linkurile funcționează aici puțin diferit. Atributul Latte `n:href` și tag-ul `{link}`, precum și metodele componentei precum `link()` și altele consideră ținta linkului **întotdeauna ca fiind numele semnalului**. De aceea, nu este necesar nici măcar să se specifice semnul exclamării: ```latte -signal, not an action +semnal, nu acțiune ``` -Dacă dorim să facem legătura cu prezentatorii din șablonul componentei, folosim eticheta `{plink}`: +Dacă am dori să facem referire la presentere în șablonul componentei, folosim tag-ul `{plink}`: ```latte -home +introducere ``` sau în cod @@ -223,17 +223,41 @@ $this->getPresenter()->link('Home:default') ``` -Legături nevalabile .[#toc-invalid-links] -========================================= +Aliasuri .{data-version:v3.2.2} +=============================== -Se poate întâmpla să creăm o legătură invalidă - fie pentru că se referă la un prezentator inexistent, fie pentru că trece mai mulți parametri decât primește metoda țintă în semnătura sa, fie când nu poate exista un URL generat pentru acțiunea vizată. Ce trebuie făcut cu legăturile invalide este determinat de variabila statică `Presenter::$invalidLinkMode`. Aceasta poate avea una dintre aceste valori (constante): +Uneori poate fi util să atribuiți perechii Presenter:acțiune un alias ușor de reținut. De exemplu, pagina de start `Front:Home:default` să o numiți simplu `home` sau `Admin:Dashboard:default` ca `admin`. -- `Presenter::InvalidLinkSilent` - mod silențios, returnează simbolul `#` ca URL. -- `Presenter::InvalidLinkWarning` - se va produce E_USER_WARNING -- `Presenter::InvalidLinkTextual` - avertizare vizuală, textul erorii este afișat în legătură -- `Presenter::InvalidLinkException` - se va produce InvalidLinkException +Aliasurile se definesc în [configurație|configuration] sub cheia `application › aliases`: -Configurația implicită în modul de producție este `InvalidLinkWarning`, iar în modul de dezvoltare este `InvalidLinkWarning | InvalidLinkTextual`. `InvalidLinkWarning` nu omoară scriptul în mediul de producție, dar avertismentul va fi înregistrat. În mediul de dezvoltare, [Tracy |tracy:] va intercepta avertismentul și va afișa ecranul albastru de eroare. Dacă `InvalidLinkTextual` este setat, prezentatorul și componentele returnează mesajul de eroare ca URL care începe cu `#error:`. Pentru a face vizibile astfel de linkuri, putem adăuga o regulă CSS la foaia noastră de stil: +```neon +application: + aliases: + home: Front:Home:default + admin: Admin:Dashboard:default + sign: Front:Sign:in +``` + +În linkuri se scriu apoi folosind arondul, de exemplu: + +```latte +administrare +``` + +Sunt suportate și în toate metodele care lucrează cu linkuri, cum ar fi `redirect()` și altele asemenea. + + +Linkuri invalide +================ + +Se poate întâmpla să creăm un link invalid - fie pentru că duce la un presenter inexistent, fie pentru că transmite mai mulți parametri decât acceptă metoda țintă în semnătura sa, sau când nu se poate genera un URL pentru acțiunea țintă. Cum să tratăm linkurile invalide este determinat de variabila statică `Presenter::$invalidLinkMode`. Aceasta poate lua o combinație a acestor valori (constante): + +- `Presenter::InvalidLinkSilent` - mod silențios, ca URL se returnează caracterul # +- `Presenter::InvalidLinkWarning` - se aruncă o avertizare E_USER_WARNING, care va fi înregistrată în modul de producție, dar nu va cauza întreruperea execuției scriptului +- `Presenter::InvalidLinkTextual` - avertizare vizuală, afișează eroarea direct în link +- `Presenter::InvalidLinkException` - se aruncă excepția InvalidLinkException + +Setarea implicită este `InvalidLinkWarning` în modul de producție și `InvalidLinkWarning | InvalidLinkTextual` în modul de dezvoltare. `InvalidLinkWarning` în mediul de producție nu cauzează întreruperea scriptului, dar avertizarea va fi înregistrată. În mediul de dezvoltare, [Tracy |tracy:] o va captura și va afișa un bluescreen. `InvalidLinkTextual` funcționează astfel încât returnează ca URL un mesaj de eroare care începe cu caracterele `#error:`. Pentru ca astfel de linkuri să fie vizibile la prima vedere, adăugăm în CSS: ```css a[href^="#error:"] { @@ -242,7 +266,7 @@ a[href^="#error:"] { } ``` -Dacă nu dorim ca avertismentele să fie produse în mediul de dezvoltare, putem activa modul silent invalid link mode în [configurație |configuration]. +Dacă nu dorim să se producă avertizări în mediul de dezvoltare, putem seta modul silențios direct în [configurație|configuration]. ```neon application: @@ -250,13 +274,13 @@ application: ``` -LinkGenerator .[#toc-linkgenerator] -=================================== +LinkGenerator +============= -Cum se pot crea legături cu metoda `link()` comfort, dar fără prezența unui prezentator? De aceea, iată [api:Nette\Application\LinkGenerator]. +Cum să creăm linkuri cu un confort similar cu cel al metodei `link()`, dar fără prezența unui presenter? Pentru asta există [api:Nette\Application\LinkGenerator]. -LinkGenerator este un serviciu pe care îl puteți avea trecut prin constructor și apoi să creați link-uri folosind metoda sa `link()`. +LinkGenerator este un serviciu pe care îl puteți primi prin constructor și apoi crea linkuri folosind metoda sa `link()`. -Există o diferență față de prezentatori. LinkGenerator creează toate legăturile ca URL-uri absolute. În plus, nu există un "prezentator curent", deci nu este posibil să se specifice doar numele acțiunii `link('default')` sau căile relative către [module |modules]. +Spre deosebire de presentere, există o diferență. LinkGenerator creează toate linkurile direct ca URL-uri absolute. Și, în plus, nu există niciun "presenter curent", deci nu se poate specifica doar numele acțiunii `link('default')` ca țintă sau specifica căi relative către module. -Legăturile invalide aruncă întotdeauna `Nette\Application\UI\InvalidLinkException`. +Linkurile invalide aruncă întotdeauna `Nette\Application\UI\InvalidLinkException`. diff --git a/application/ro/directory-structure.texy b/application/ro/directory-structure.texy new file mode 100644 index 0000000000..bd9646acc6 --- /dev/null +++ b/application/ro/directory-structure.texy @@ -0,0 +1,526 @@ +Structura directoarelor aplicației +********************************** + +
    + +Cum să proiectăm o structură de directoare clară și scalabilă pentru proiectele în Nette Framework? Vom arăta practici dovedite care vă vor ajuta cu organizarea codului. Veți afla: + +- cum să **împărțiți logic** aplicația în directoare +- cum să proiectați structura astfel încât să **scaleze bine** odată cu creșterea proiectului +- care sunt **alternativele posibile** și avantajele sau dezavantajele lor + +
    + + +Este important de menționat că Nette Framework însuși nu impune nicio structură specifică. Este proiectat astfel încât să poată fi ușor adaptat la orice nevoi și preferințe. + + +Structura de bază a proiectului +=============================== + +Deși Nette Framework nu dictează nicio structură de directoare fixă, există o aranjare implicită dovedită sub forma [Web Project|https://github.com/nette/web-project]: + +/--pre +web-project/ +├── app/ ← director cu aplicația +├── assets/ ← fișiere SCSS, JS, imagini..., alternativ resources/ +├── bin/ ← scripturi pentru linia de comandă +├── config/ ← configurație +├── log/ ← erori înregistrate +├── temp/ ← fișiere temporare, cache +├── tests/ ← teste +├── vendor/ ← biblioteci instalate de Composer +└── www/ ← director public (document-root) +\-- + +Această structură poate fi modificată liber în funcție de nevoile dvs. - folderele pot fi redenumite sau mutate. Apoi este suficient doar să modificați căile relative către directoare în fișierul `Bootstrap.php` și eventual `composer.json`. Nimic mai mult nu este necesar, nicio reconfigurare complicată, nicio modificare a constantelor. Nette dispune de autodetecție inteligentă și recunoaște automat locația aplicației, inclusiv baza sa URL. + + +Principii de organizare a codului +================================= + +Când explorați pentru prima dată un proiect nou, ar trebui să vă orientați rapid în el. Imaginați-vă că deschideți directorul `app/Model/` și vedeți această structură: + +/--pre +app/Model/ +├── Services/ +├── Repositories/ +└── Entities/ +\-- + +Din aceasta deduceți doar că proiectul folosește niște servicii, depozite și entități. Despre scopul real al aplicației nu aflați absolut nimic. + +Să ne uităm la o altă abordare - **organizarea pe domenii**: + +/--pre +app/Model/ +├── Cart/ +├── Payment/ +├── Order/ +└── Product/ +\-- + +Aici este altfel - la prima vedere este clar că este vorba despre un magazin online. Chiar și numele directoarelor dezvăluie ce poate face aplicația - lucrează cu plăți, comenzi și produse. + +Prima abordare (organizarea după tipul claselor) aduce în practică o serie de probleme: codul care este logic legat este fragmentat în diferite foldere și trebuie să săriți între ele. De aceea, vom organiza pe domenii. + + +Spații de nume +-------------- + +Este obișnuit ca structura directoarelor să corespundă spațiilor de nume din aplicație. Aceasta înseamnă că locația fizică a fișierelor corespunde namespace-ului lor. De exemplu, o clasă situată în `app/Model/Product/ProductRepository.php` ar trebui să aibă namespace-ul `App\Model\Product`. Acest principiu ajută la orientarea în cod și simplifică autoloading-ul. + + +Singular vs plural în nume +-------------------------- + +Observați că pentru directoarele principale ale aplicației folosim singularul: `app`, `config`, `log`, `temp`, `www`. La fel și în interiorul aplicației: `Model`, `Core`, `Presentation`. Acest lucru se datorează faptului că fiecare dintre ele reprezintă un concept unitar. + +Similar, de exemplu, `app/Model/Product` reprezintă totul legat de produse. Nu îl vom numi `Products`, deoarece nu este un folder plin de produse (acolo ar fi fișiere `nokia.php`, `samsung.php`). Este un namespace care conține clase pentru lucrul cu produse - `ProductRepository.php`, `ProductService.php`. + +Folderul `app/Tasks` este la plural deoarece conține un set de scripturi executabile separate - `CleanupTask.php`, `ImportTask.php`. Fiecare dintre ele este o unitate separată. + +Pentru consistență, recomandăm utilizarea: +- Singularului pentru namespace-ul care reprezintă un ansamblu funcțional (chiar dacă lucrează cu mai multe entități) +- Pluralului pentru colecții de unități separate +- În caz de incertitudine sau dacă nu doriți să vă gândiți la asta, alegeți singularul + + +Director public `www/` +====================== + +Acest director este singurul accesibil de pe web (așa-numitul document-root). Adesea puteți întâlni și numele `public/` în loc de `www/` - este doar o chestiune de convenție și nu are nicio influență asupra funcționalității aplicației. Directorul conține: +- [Punctul de intrare |bootstrapping#index.php] al aplicației `index.php` +- Fișierul `.htaccess` cu reguli pentru mod_rewrite (pentru Apache) +- Fișiere statice (CSS, JavaScript, imagini) +- Fișiere încărcate + +Pentru securitatea corectă a aplicației, este esențial să aveți [configurat corect document-root |nette:troubleshooting#Cum să schimbați sau să eliminați directorul www din URL]. + +.[note] +Nu plasați niciodată folderul `node_modules/` în acest director - conține mii de fișiere care pot fi executabile și nu ar trebui să fie accesibile public. + + +Director aplicație `app/` +========================= + +Acesta este directorul principal cu codul aplicației. Structura de bază: + +/--pre +app/ +├── Core/ ← aspecte de infrastructură +├── Model/ ← logica de business +├── Presentation/ ← presentere și șabloane +├── Tasks/ ← scripturi de comandă +└── Bootstrap.php ← clasa de inițializare a aplicației +\-- + +`Bootstrap.php` este [clasa de pornire a aplicației|bootstrapping], care inițializează mediul, încarcă configurația și creează containerul DI. + +Să ne uităm acum mai detaliat la subdirectoarele individuale. + + +Presentere și șabloane +====================== + +Partea de prezentare a aplicației o avem în directorul `app/Presentation`. O alternativă este scurtul `app/UI`. Este locul pentru toți presenterele, șabloanele lor și eventualele clase ajutătoare. + +Acest strat îl organizăm pe domenii. Într-un proiect complex, care combină un magazin online, un blog și un API, structura ar arăta astfel: + +/--pre +app/Presentation/ +├── Shop/ ← frontend magazin online +│ ├── Product/ +│ ├── Cart/ +│ └── Order/ +├── Blog/ ← blog +│ ├── Home/ +│ └── Post/ +├── Admin/ ← administrare +│ ├── Dashboard/ +│ └── Products/ +└── Api/ ← endpoint-uri API + └── V1/ +\-- + +Pe de altă parte, pentru un blog simplu, am folosi o împărțire: + +/--pre +app/Presentation/ +├── Front/ ← frontend web +│ ├── Home/ +│ └── Post/ +├── Admin/ ← administrare +│ ├── Dashboard/ +│ └── Posts/ +├── Error/ +└── Export/ ← RSS, sitemap-uri etc. +\-- + +Foldere precum `Home/` sau `Dashboard/` conțin presentere și șabloane. Foldere precum `Front/`, `Admin/` sau `Api/` le numim **module**. Tehnic, sunt directoare obișnuite care servesc la împărțirea logică a aplicației. + +Fiecare folder cu un presenter conține un presenter cu același nume și șabloanele sale. De exemplu, folderul `Dashboard/` conține: + +/--pre +Dashboard/ +├── DashboardPresenter.php ← presenter +└── default.latte ← șablon +\-- + +Această structură de directoare se reflectă în spațiile de nume ale claselor. De exemplu, `DashboardPresenter` se află în spațiul de nume `App\Presentation\Admin\Dashboard` (vezi [#Maparea presenterelor]): + +```php +namespace App\Presentation\Admin\Dashboard; + +class DashboardPresenter extends Nette\Application\UI\Presenter +{ + // ... +} +``` + +La presenterul `Dashboard` din interiorul modulului `Admin` facem referire în aplicație folosind notația cu două puncte ca `Admin:Dashboard`. La acțiunea sa `default` apoi ca `Admin:Dashboard:default`. În cazul modulelor imbricate, folosim mai multe două puncte, de exemplu `Shop:Order:Detail:default`. + + +Dezvoltare flexibilă a structurii +--------------------------------- + +Unul dintre marile avantaje ale acestei structuri este cât de elegant se adaptează la nevoile în creștere ale proiectului. Ca exemplu, să luăm partea care generează feed-uri XML. La început avem o formă simplă: + +/--pre +Export/ +├── ExportPresenter.php ← un singur presenter pentru toate exporturile +├── sitemap.latte ← șablon pentru sitemap +└── feed.latte ← șablon pentru feed RSS +\-- + +Cu timpul, apar noi tipuri de feed-uri și avem nevoie de mai multă logică pentru ele... Nicio problemă! Folderul `Export/` devine pur și simplu un modul: + +/--pre +Export/ +├── Sitemap/ +│ ├── SitemapPresenter.php +│ └── sitemap.latte +└── Feed/ + ├── FeedPresenter.php + ├── zbozi.latte ← feed pentru Zboží.cz + └── heureka.latte ← feed pentru Heureka.cz +\-- + +Această transformare este complet fluidă - este suficient să creați noi subfoldere, să împărțiți codul în ele și să actualizați linkurile (de ex. de la `Export:feed` la `Export:Feed:zbozi`). Datorită acestui fapt, putem extinde treptat structura după necesități, nivelul de imbricare nu este limitat în niciun fel. + +Dacă, de exemplu, în administrare aveți mulți presenteri referitori la gestionarea comenzilor, cum ar fi `OrderDetail`, `OrderEdit`, `OrderDispatch` etc., puteți crea pentru o mai bună organizare în acest loc un modul (folder) `Order`, în care vor fi (foldere pentru) presenterele `Detail`, `Edit`, `Dispatch` și altele. + + +Amplasarea șabloanelor +---------------------- + +În exemplele anterioare am văzut că șabloanele sunt plasate direct în folderul cu presenterul: + +/--pre +Dashboard/ +├── DashboardPresenter.php ← presenter +├── DashboardTemplate.php ← clasă opțională pentru șablon +└── default.latte ← șablon +\-- + +Această amplasare se dovedește în practică a fi cea mai convenabilă - aveți toate fișierele aferente la îndemână. + +Alternativ, puteți plasa șabloanele într-un subfolder `templates/`. Nette suportă ambele variante. Puteți chiar plasa șabloanele complet în afara folderului `Presentation/`. Totul despre posibilitățile de amplasare a șabloanelor găsiți în capitolul [Căutarea șabloanelor |templates#Căutarea șabloanelor]. + + +Clase ajutătoare și componente +------------------------------ + +Presenterelor și șabloanelor le aparțin adesea și alte fișiere ajutătoare. Le plasăm logic în funcție de domeniul lor de aplicare: + +1. **Direct lângă presenter** în cazul componentelor specifice pentru presenterul respectiv: + +/--pre +Product/ +├── ProductPresenter.php +├── ProductGrid.php ← componentă pentru listarea produselor +└── FilterForm.php ← formular pentru filtrare +\-- + +2. **Pentru modul** - recomandăm utilizarea folderului `Accessory`, care se plasează convenabil chiar la începutul alfabetului: + +/--pre +Front/ +├── Accessory/ +│ ├── NavbarControl.php ← componente pentru frontend +│ └── TemplateFilters.php +├── Product/ +└── Cart/ +\-- + +3. **Pentru întreaga aplicație** - în `Presentation/Accessory/`: +/--pre +app/Presentation/ +├── Accessory/ +│ ├── LatteExtension.php +│ └── TemplateFilters.php +├── Front/ +└── Admin/ +\-- + +Sau puteți plasa clase ajutătoare precum `LatteExtension.php` sau `TemplateFilters.php` în folderul de infrastructură `app/Core/Latte/`. Și componentele în `app/Components`. Alegerea depinde de obiceiurile echipei. + + +Model - inima aplicației +======================== + +Modelul conține întreaga logică de business a aplicației. Pentru organizarea sa se aplică din nou regula - structurăm pe domenii: + +/--pre +app/Model/ +├── Payment/ ← totul despre plăți +│ ├── PaymentFacade.php ← principalul punct de intrare +│ ├── PaymentRepository.php +│ ├── Payment.php ← entitate +├── Order/ ← totul despre comenzi +│ ├── OrderFacade.php +│ ├── OrderRepository.php +│ ├── Order.php +└── Shipping/ ← totul despre transport +\-- + +În model veți întâlni de obicei aceste tipuri de clase: + +**Facade**: reprezintă principalul punct de intrare într-un domeniu specific al aplicației. Acționează ca un orchestrator care coordonează colaborarea între diferite servicii în scopul implementării cazurilor de utilizare complete (cum ar fi "creează comandă" sau "procesează plată"). Sub stratul său de orchestrator, fațada ascunde detaliile de implementare de restul aplicației, oferind astfel o interfață curată pentru lucrul cu domeniul respectiv. + +```php +class OrderFacade +{ + public function createOrder(Cart $cart): Order + { + // validare + // creare comandă + // trimitere e-mail + // înregistrare în statistici + } +} +``` + +**Servicii**: se concentrează pe o operațiune specifică de business în cadrul domeniului. Spre deosebire de fațadă, care orchestrează cazuri de utilizare întregi, serviciul implementează o logică de business specifică (cum ar fi calcule de prețuri sau procesarea plăților). Serviciile sunt de obicei fără stare și pot fi utilizate fie de fațade ca blocuri de construcție pentru operațiuni mai complexe, fie direct de alte părți ale aplicației pentru sarcini mai simple. + +```php +class PricingService +{ + public function calculateTotal(Order $order): Money + { + // calcul preț + } +} +``` + +**Depozite**: asigură întreaga comunicare cu stocarea de date, de obicei o bază de date. Sarcina sa este de a încărca și salva entități și de a implementa metode pentru căutarea lor. Depozitul izolează restul aplicației de detaliile de implementare ale bazei de date și oferă o interfață orientată pe obiecte pentru lucrul cu datele. + +```php +class OrderRepository +{ + public function find(int $id): ?Order + { + } + + public function findByCustomer(int $customerId): array + { + } +} +``` + +**Entități**: obiecte care reprezintă principalele concepte de business în aplicație, care au identitatea lor și se schimbă în timp. De obicei, sunt clase mapate pe tabele de baze de date folosind ORM (cum ar fi Nette Database Explorer sau Doctrine). Entitățile pot conține reguli de business referitoare la datele lor și logică de validare. + +```php +// Entitate mapată pe tabela de bază de date orders +class Order extends Nette\Database\Table\ActiveRow +{ + public function addItem(Product $product, int $quantity): void + { + $this->related('order_items')->insert([ + 'product_id' => $product->id, + 'quantity' => $quantity, + 'unit_price' => $product->price, + ]); + } +} +``` + +**Obiecte valoare**: obiecte imuabile care reprezintă valori fără identitate proprie - de exemplu, o sumă de bani sau o adresă de e-mail. Două instanțe ale unui obiect valoare cu aceleași valori sunt considerate identice. + + +Cod de infrastructură +===================== + +Folderul `Core/` (sau și `Infrastructure/`) este casa pentru baza tehnică a aplicației. Codul de infrastructură include de obicei: + +/--pre +app/Core/ +├── Router/ ← rutare și management URL +│ └── RouterFactory.php +├── Security/ ← autentificare și autorizare +│ ├── Authenticator.php +│ └── Authorizator.php +├── Logging/ ← logare și monitorizare +│ ├── SentryLogger.php +│ └── FileLogger.php +├── Cache/ ← strat de cache +│ └── FullPageCache.php +└── Integration/ ← integrare cu servicii ext. + ├── Slack/ + └── Stripe/ +\-- + +Pentru proiecte mai mici, este suficientă, desigur, o structură plată: + +/--pre +Core/ +├── RouterFactory.php +├── Authenticator.php +└── QueueMailer.php +\-- + +Este vorba despre cod care: + +- Rezolvă infrastructura tehnică (rutare, logare, cache) +- Integrează servicii externe (Sentry, Elasticsearch, Redis) +- Oferă servicii de bază pentru întreaga aplicație (mail, bază de date) +- Este în mare parte independent de domeniul specific - cache-ul sau loggerul funcționează la fel pentru magazinul online sau blog. + +Ezitați dacă o anumită clasă aparține aici sau în model? Diferența cheie este că codul din `Core/`: + +- Nu știe nimic despre domeniu (produse, comenzi, articole) +- Este în mare parte posibil să fie transferat într-un alt proiect +- Rezolvă "cum funcționează" (cum se trimite un mail), nu "ce face" (ce mail să trimită) + +Exemplu pentru o mai bună înțelegere: + +- `App\Core\MailerFactory` - creează instanțe ale clasei pentru trimiterea e-mailurilor, rezolvă setările SMTP +- `App\Model\OrderMailer` - folosește `MailerFactory` pentru a trimite e-mailuri despre comenzi, cunoaște șabloanele lor și știe când trebuie trimise + + +Scripturi de comandă +==================== + +Aplicațiile au adesea nevoie să execute activități în afara cererilor HTTP obișnuite - fie că este vorba de procesarea datelor în fundal, întreținere sau sarcini periodice. Pentru rulare se folosesc scripturi simple în directorul `bin/`, logica de implementare propriu-zisă o plasăm apoi în `app/Tasks/` (eventual `app/Commands/`). + +Exemplu: + +/--pre +app/Tasks/ +├── Maintenance/ ← scripturi de întreținere +│ ├── CleanupCommand.php ← ștergerea datelor vechi +│ └── DbOptimizeCommand.php ← optimizarea bazei de date +├── Integration/ ← integrare cu sisteme externe +│ ├── ImportProducts.php ← import din sistemul furnizorului +│ └── SyncOrders.php ← sincronizarea comenzilor +└── Scheduled/ ← sarcini regulate + ├── NewsletterCommand.php ← trimiterea newsletterelor + └── ReminderCommand.php ← notificări clienți +\-- + +Ce aparține modelului și ce scripturilor de comandă? De exemplu, logica pentru trimiterea unui singur e-mail face parte din model, trimiterea în masă a mii de e-mailuri aparține deja `Tasks/`. + +Sarcinile le [rulăm de obicei din linia de comandă |https://blog.nette.org/en/cli-scripts-in-nette-application] sau prin cron. Pot fi rulate și prin cerere HTTP, dar trebuie să ne gândim la securitate. Presenterul care rulează sarcina trebuie securizat, de exemplu, doar pentru utilizatorii conectați sau cu un token puternic și acces de la adrese IP permise. Pentru sarcinile lungi, este necesar să se mărească limita de timp a scriptului și să se folosească `session_write_close()`, pentru a nu bloca sesiunea. + + +Alte directoare posibile +======================== + +Pe lângă directoarele de bază menționate, puteți adăuga, în funcție de nevoile proiectului, alte foldere specializate. Să ne uităm la cele mai frecvente dintre ele și la utilizarea lor: + +/--pre +app/ +├── Api/ ← logica pentru API independentă de stratul de prezentare +├── Database/ ← scripturi de migrare și seedere pentru date de test +├── Components/ ← componente vizuale partajate în întreaga aplicație +├── Event/ ← util dacă utilizați arhitectura bazată pe evenimente +├── Mail/ ← șabloane de e-mail și logica aferentă +└── Utils/ ← clase ajutătoare +\-- + +Pentru componentele vizuale partajate utilizate în presentere în întreaga aplicație, se poate folosi folderul `app/Components` sau `app/Controls`: + +/--pre +app/Components/ +├── Form/ ← componente de formular partajate +│ ├── SignInForm.php +│ └── UserForm.php +├── Grid/ ← componente pentru listări de date +│ └── DataGrid.php +└── Navigation/ ← elemente de navigație + ├── Breadcrumbs.php + └── Menu.php +\-- + +Aici aparțin componentele care au o logică mai complexă. Dacă doriți să partajați componente între mai multe proiecte, este recomandabil să le extrageți într-un pachet composer separat. + +În directorul `app/Mail` puteți plasa gestionarea comunicării prin e-mail: + +/--pre +app/Mail/ +├── templates/ ← șabloane de e-mail +│ ├── order-confirmation.latte +│ └── welcome.latte +└── OrderMailer.php +\-- + + +Maparea presenterelor +===================== + +Maparea definește reguli pentru derivarea numelui clasei din numele presenterului. Le specificăm în [configurație|configuration] sub cheia `application › mapping`. + +Pe această pagină am arătat că plasăm presenterele în folderul `app/Presentation` (eventual `app/UI`). Această convenție trebuie să o comunicăm lui Nette în fișierul de configurare. Este suficientă o singură linie: + +```neon +application: + mapping: App\Presentation\*\**Presenter +``` + +Cum funcționează maparea? Pentru o mai bună înțelegere, să ne imaginăm mai întâi o aplicație fără module. Dorim ca clasele presenterelor să se încadreze în spațiul de nume `App\Presentation`, astfel încât presenterul `Home` să fie mapat pe clasa `App\Presentation\HomePresenter`. Ceea ce realizăm cu această configurație: + +```neon +application: + mapping: App\Presentation\*Presenter +``` + +Maparea funcționează astfel încât numele presenterului `Home` înlocuiește asteriscul din masca `App\Presentation\*Presenter`, obținând astfel numele final al clasei `App\Presentation\HomePresenter`. Simplu! + +Dar, după cum vedeți în exemplele din acest capitol și din altele, plasăm clasele presenterelor în subdirectoare eponime, de exemplu, presenterul `Home` se mapează pe clasa `App\Presentation\Home\HomePresenter`. Acest lucru se realizează prin dublarea celor două puncte (necesită Nette Application 3.2): + +```neon +application: + mapping: App\Presentation\**Presenter +``` + +Acum trecem la maparea presenterelor în module. Pentru fiecare modul putem defini o mapare specifică: + +```neon +application: + mapping: + Front: App\Presentation\Front\**Presenter + Admin: App\Presentation\Admin\**Presenter + Api: App\Api\*Presenter +``` + +Conform acestei configurații, presenterul `Front:Home` se mapează pe clasa `App\Presentation\Front\Home\HomePresenter`, în timp ce presenterul `Api:OAuth` pe clasa `App\Api\OAuthPresenter`. + +Deoarece modulele `Front` și `Admin` au un mod similar de mapare și probabil vor exista mai multe astfel de module, este posibil să se creeze o regulă generală care să le înlocuiască. Astfel, în masca clasei va apărea un nou asterisc pentru modul: + +```neon +application: + mapping: + *: App\Presentation\*\**Presenter + Api: App\Api\*Presenter +``` + +Funcționează și pentru structuri de directoare mai adânc imbricate, cum ar fi, de exemplu, presenterul `Admin:User:Edit`, segmentul cu asterisc se repetă pentru fiecare nivel și rezultatul este clasa `App\Presentation\Admin\User\Edit\EditPresenter`. + +O notație alternativă este să folosim un array format din trei segmente în loc de un șir de caractere. Această notație este echivalentă cu cea anterioară: + +```neon +application: + mapping: + *: [App\Presentation, *, **Presenter] + Api: [App\Api, '', *Presenter] +``` diff --git a/application/ro/how-it-works.texy b/application/ro/how-it-works.texy index eeb65bb9f7..63df4130f0 100644 --- a/application/ro/how-it-works.texy +++ b/application/ro/how-it-works.texy @@ -3,99 +3,101 @@ Cum funcționează aplicațiile?
    -În prezent citiți documentul de bază al documentației Nette. Veți învăța toate principiile aplicațiilor web. Nisa de la A la Z, de la momentul nașterii până la ultima suflare a scriptului PHP. După ce veți citi veți ști: +Tocmai citiți documentul de bază al documentației Nette. Veți afla întregul principiu de funcționare al aplicațiilor web. De la A la Z, de la momentul nașterii până la ultima suflare a scriptului PHP. După citire, veți ști: - cum funcționează totul -- ce este Bootstrap, Presenter și containerul DI +- ce sunt Bootstrap, Presenter și containerul DI - cum arată structura directoarelor
    -Structura directoarelor .[#toc-directory-structure] -=================================================== +Structura directoarelor +======================= -Deschideți un exemplu de schelet al unei aplicații web numit [WebProject |https://github.com/nette/web-project] și puteți urmări fișierele despre care se scrie. +Deschideți exemplul de schelet al unei aplicații web numit [WebProject|https://github.com/nette/web-project] și, în timp ce citiți, puteți privi fișierele despre care este vorba. Structura directoarelor arată cam așa: /--pre web-project/ -├── app/ ← directory with application -│ ├── Presenters/ ← presenter classes -│ │ ├── HomePresenter.php ← Home presenter class -│ │ └── templates/ ← templates directory -│ │ ├── @layout.latte ← template of shared layout -│ │ └── Home/ ← templates for Home presenter -│ │ └── default.latte ← template for action `default` -│ ├── Router/ ← configuration of URL addresses -│ └── Bootstrap.php ← booting class Bootstrap -├── bin/ ← scripts for the command line -├── config/ ← configuration files +├── app/ ← director cu aplicația +│ ├── Core/ ← clase de bază necesare pentru funcționare +│ │ └── RouterFactory.php ← configurarea adreselor URL +│ ├── Presentation/ ← presentere, șabloane & co. +│ │ ├── @layout.latte ← șablon de layout +│ │ └── Home/ ← directorul presenterului Home +│ │ ├── HomePresenter.php ← clasa presenterului Home +│ │ └── default.latte ← șablonul acțiunii default +│ └── Bootstrap.php ← clasa de inițializare Bootstrap +├── assets/ ← resurse (SCSS, TypeScript, imagini sursă) +├── bin/ ← scripturi rulate din linia de comandă +├── config/ ← fișiere de configurare │ ├── common.neon -│ └── local.neon -├── log/ ← error logs -├── temp/ ← temporary files, cache, … -├── vendor/ ← libraries installed by Composer +│ └── services.neon +├── log/ ← erori înregistrate +├── temp/ ← fișiere temporare, cache, … +├── vendor/ ← biblioteci instalate de Composer │ ├── ... -│ └── autoload.php ← autoloading of libs installed by Composer -├── www/ ← public directory, document root of project -│ ├── .htaccess ← mod_rewrite rules etc -│ └── index.php ← initial file that launches the application -└── .htaccess ← prohibits access to all directories except www +│ └── autoload.php ← autoloading pentru toate pachetele instalate +├── www/ ← director public sau document-root al proiectului +│ ├── assets/ ← fișiere statice compilate (CSS, JS, imagini, ...) +│ ├── .htaccess ← reguli mod_rewrite +│ └── index.php ← fișierul inițial prin care se lansează aplicația +└── .htaccess ← interzice accesul la toate directoarele, cu excepția www \-- -Puteți modifica structura directoarelor în orice mod, puteți redenumi sau muta folderele și apoi puteți edita doar căile de acces la `log/` și `temp/` în fișierul `Bootstrap.php` și calea de acces la acest fișier în `composer.json` în secțiunea `autoload`. Nimic mai mult, fără reconfigurări complicate, fără schimbări constante. Nette are o [autodetecție inteligentă |bootstrap#development-vs-production-mode]. +Structura directoarelor poate fi modificată oricum, folderele pot fi redenumite sau mutate, este complet flexibilă. Nette dispune, în plus, de autodetecție inteligentă și recunoaște automat locația aplicației, inclusiv baza sa URL. -Pentru aplicații ceva mai mari, putem împărți dosarele cu prezentatori și șabloane în subdirectoare (pe disc) și în spații de nume (în cod), pe care le numim [module |modules]. +Pentru aplicații puțin mai mari, putem [împărți folderele cu presentere și șabloane în subdirectoare |directory-structure#Presentere și șabloane] și clasele în spații de nume, pe care le numim module. -Directorul `www/` este directorul public sau documentul-rădăcină al proiectului. Îl puteți redenumi fără a fi nevoie să setați nimic altceva pe partea de aplicație. Trebuie doar să [configurați găzduirea |nette:troubleshooting#How to change or remove www directory from URL] astfel încât documentul-root să ajungă în acest director. +Directorul `www/` reprezintă așa-numitul director public sau document-root al proiectului. Îl puteți redenumi fără a fi nevoie să setați altceva în partea de aplicație. Este necesar doar să [configurați hostingul |nette:troubleshooting#Cum să schimbați sau să eliminați directorul www din URL] astfel încât document-root să indice către acest director. -De asemenea, puteți descărca direct proiectul WebProject, inclusiv Nette, folosind [Composer |best-practices:composer]: +WebProject poate fi, de asemenea, descărcat direct, inclusiv Nette, folosind [Composer |best-practices:composer]: ```shell composer create-project nette/web-project ``` -Pe Linux sau macOS, setați [permisiunile de scriere |nette:troubleshooting#Setting directory permissions] pentru directoarele `log/` și `temp/`. +Pe Linux sau macOS, setați [permisiunile de scriere |nette:troubleshooting#Setarea permisiunilor pentru directoare] pentru directoarele `log/` și `temp/`. -Aplicația WebProject este gata să ruleze, nu mai este nevoie să configurați absolut nimic altceva și o puteți vizualiza direct în browser accesând folderul `www/`. +Aplicația WebProject este gata de rulare, nu este nevoie să configurați absolut nimic și o puteți afișa direct în browser accesând folderul `www/`. -Cerere HTTP .[#toc-http-request] -================================ +Cerere HTTP +=========== -Totul începe atunci când un utilizator deschide o pagină într-un browser și browserul bate la server cu o cerere HTTP. Cererea merge la un fișier PHP aflat în directorul public `www/`, care este `index.php`. Să presupunem că este vorba de o cerere către `https://example.com/product/123` și va fi executat. +Totul începe în momentul în care utilizatorul deschide pagina în browser. Adică atunci când browserul bate la ușa serverului cu o cerere HTTP. Cererea vizează un singur fișier PHP, care se află în directorul public `www/`, și acesta este `index.php`. Să presupunem că este vorba despre o cerere pentru adresa `https://example.com/product/123`. Datorită [setărilor adecvate ale serverului |nette:troubleshooting#Cum să configurați serverul pentru URL-uri prietenoase], chiar și acest URL este mapat pe fișierul `index.php` și acesta se execută. Sarcina sa este: -1) să inițializeze mediul -2) să obțină fabrica -3) lansarea aplicației Nette care gestionează cererea +1) inițializarea mediului +2) obținerea fabricii +3) pornirea aplicației Nette, care va gestiona cererea -Ce fel de fabrică? Noi nu producem tractoare, ci site-uri web! Stați puțin, vă explicăm imediat. +Ce fel de fabrică? Nu producem tractoare, ci pagini web! Aveți răbdare, se va explica imediat. -Prin "inițializarea mediului" înțelegem, de exemplu, că este activat [Tracy |tracy:], care este un instrument uimitor pentru înregistrarea sau vizualizarea erorilor. Acesta înregistrează erorile de pe serverul de producție și le afișează direct pe serverul de dezvoltare. Prin urmare, inițializarea trebuie, de asemenea, să decidă dacă site-ul rulează în modul de producție sau de dezvoltare. Pentru a face acest lucru, Nette folosește autodetecția: dacă executați site-ul pe localhost, acesta rulează în modul dezvoltator. Nu trebuie să configurați nimic, iar aplicația este pregătită atât pentru dezvoltare, cât și pentru implementarea în producție. Acești pași sunt realizați și descriși în detaliu în capitolul despre [clasa Bootstrap |bootstrap]. +Prin „inițializarea mediului” ne referim, de exemplu, la faptul că se activează [Tracy|tracy:], care este un instrument uimitor pentru înregistrarea sau vizualizarea erorilor. Pe serverul de producție, înregistrează erorile, pe cel de dezvoltare le afișează direct. Prin urmare, inițializarea include și decizia dacă site-ul rulează în modul de producție sau de dezvoltare. Pentru aceasta, Nette utilizează [autodetecția inteligentă |bootstrapping#Modul de dezvoltare vs producție]: dacă rulați site-ul pe localhost, rulează în modul de dezvoltare. Nu trebuie să configurați nimic și aplicația este direct pregătită atât pentru dezvoltare, cât și pentru implementarea live. Acești pași se efectuează și sunt descriși detaliat în capitolul despre [clasa Bootstrap|bootstrapping]. -Al treilea punct (da, l-am sărit pe al doilea, dar vom reveni la el) este să pornim aplicația. Gestionarea cererilor HTTP în Nette este realizată de clasa `Nette\Application\Application` (denumită în continuare `Application`), astfel încât atunci când spunem "a rula o aplicație", ne referim la apelarea unei metode cu numele `run()` pe un obiect din această clasă. +Al treilea punct (da, am sărit peste al doilea, dar vom reveni la el) este pornirea aplicației. Gestionarea cererilor HTTP este responsabilitatea clasei `Nette\Application\Application` (în continuare `Application`), așa că atunci când spunem pornirea aplicației, ne referim în mod specific la apelarea metodei cu numele sugestiv `run()` pe obiectul acestei clase. -Nette este un mentor care vă ghidează să scrieți aplicații curate prin metodologii dovedite. Iar cea mai dovedită se numește **Injecție de dependență**, prescurtat DI. Deocamdată nu vrem să vă împovărăm cu explicații despre DI, deoarece există un [capitol separat |dependency-injection:introduction], important aici este că obiectele cheie vor fi create de obicei de o fabrică de obiecte numită **DI container** (abreviat DIC). Da, aceasta este fabrica care a fost menționată cu ceva timp în urmă. Și tot ea creează pentru noi obiectul `Application`, deci avem nevoie mai întâi de un container. Îl obținem cu ajutorul clasei `Configurator` și îl lăsăm să producă obiectul `Application`, apelăm metoda `run()` și astfel pornim aplicația Nette. Este exact ceea ce se întâmplă în fișierul [index.php |bootstrap#index.php]. +Nette este un mentor care vă ghidează să scrieți aplicații curate conform metodologiilor dovedite. Și una dintre cele absolut cele mai dovedite se numește **dependency injection**, prescurtat DI. În acest moment, nu vrem să vă încărcăm cu explicații despre DI, pentru asta există [un capitol separat|dependency-injection:introduction], esențial este rezultatul că obiectele cheie ne vor fi de obicei create de o fabrică de obiecte, care se numește **container DI** (prescurtat DIC). Da, aceasta este fabrica despre care am vorbit mai devreme. Și ne va produce și obiectul `Application`, de aceea avem nevoie mai întâi de container. Îl obținem folosind clasa `Configurator` și îl lăsăm să producă obiectul `Application`, apelăm pe el metoda `run()` și astfel pornește aplicația Nette. Exact acest lucru se întâmplă în fișierul [index.php |bootstrapping#index.php]. -Aplicația Nette .[#toc-nette-application] -========================================= +Nette Application +================= -Clasa Application are o singură sarcină: să răspundă la o cerere HTTP. +Clasa Application are o singură sarcină: să răspundă la cererea HTTP. -Aplicațiile scrise în Nette sunt împărțite în mai multe așa-numite prezentări (în alte framework-uri este posibil să întâlniți termenul de controler, care este același), care sunt clase care reprezintă o anumită pagină de site web: de exemplu, pagina de start; produs în magazinul electronic; formular de înregistrare; feed sitemap etc. Aplicația poate avea de la unul până la mii de prezentatori. +Aplicațiile scrise în Nette sunt împărțite în multe așa-numite presentere (în alte framework-uri puteți întâlni termenul controller, este același lucru), care sunt clase, fiecare reprezentând o anumită pagină specifică a site-ului: de ex. homepage; produs într-un magazin online; formular de conectare; feed sitemap etc. Aplicația poate avea de la unul la mii de presentere. -Aplicația începe prin a cere așa-numitului router să decidă care dintre prezentatori să transmită cererea curentă pentru procesare. Routerul decide a cui este responsabilitatea. Acesta se uită la URL-ul de intrare `https://example.com/product/123`, care dorește să `show` un produs cu `id: 123` ca acțiune. Este un bun obicei să se scrie perechile prezentator + acțiune separate de două puncte ca `Product:show`. +Application începe prin a solicita așa-numitului router să decidă căruia dintre presentere să îi transmită cererea curentă pentru gestionare. Routerul decide a cui este responsabilitatea. Se uită la URL-ul de intrare `https://example.com/product/123` și, pe baza modului în care este setat, decide că aceasta este treaba, de ex., a **presenterului** `Product`, de la care va dori ca **acțiune** afișarea (`show`) produsului cu `id: 123`. Perechea presenter + acțiune este o bună practică să fie scrisă separată prin două puncte ca `Product:show`. -Astfel, routerul a transformat URL-ul într-o pereche `Presenter:action` + parametri, în cazul nostru `Product:show` + `id: 123`. Puteți vedea cum arată un router în fișierul `app/Router/RouterFactory.php` și îl vom descrie în detaliu în capitolul [Routing |Routing]. +Deci, routerul a transformat URL-ul în perechea `Presenter:action` + parametri, în cazul nostru `Product:show` + `id: 123`. Cum arată un astfel de router puteți vedea în fișierul `app/Core/RouterFactory.php` și îl descriem detaliat în capitolul [Rutare |Routing]. -Să mergem mai departe. Aplicația cunoaște deja numele prezentatorului și poate continua. Prin crearea unui obiect `ProductPresenter`, care este codul prezentatorului `Product`. Mai exact, solicită containerului DI crearea prezentatorului, deoarece producerea de obiecte este sarcina acestuia. +Să mergem mai departe. Application cunoaște deja numele presenterului și poate continua. Prin crearea obiectului clasei `ProductPresenter`, care este codul presenterului `Product`. Mai precis, solicită containerului DI să creeze presenterul, deoarece crearea este treaba lui. -Prezentatorul ar putea arăta astfel: +Presenterul poate arăta, de exemplu, așa: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -113,92 +115,86 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -Cererea este gestionată de prezentator. Iar sarcina este clară: efectuați acțiunea `show` cu `id: 123`. Ceea ce în limbajul prezentatorilor înseamnă că metoda `renderShow()` este apelată și în parametrul `$id` se obține `123`. +Gestionarea cererii este preluată de presenter. Și sarcina este clară: execută acțiunea `show` cu `id: 123`. Ceea ce, în limbajul presenterelor, înseamnă că se apelează metoda `renderShow()` și în parametrul `$id` primește `123`. -Un prezentator poate gestiona mai multe acțiuni, adică are mai multe metode `render()`. Dar noi recomandăm proiectarea de prezentatoare cu una sau cât mai puține acțiuni posibile. +Presenterul poate gestiona mai multe acțiuni, adică poate avea mai multe metode `render()`. Dar recomandăm proiectarea presenterelor cu una sau cât mai puține acțiuni. -Așadar, a fost apelată metoda `renderShow(123)`, al cărei cod este un exemplu fictiv, dar se poate vedea pe ea cum sunt transmise datele către șablon, adică prin scrierea la `$this->template`. +Deci, s-a apelat metoda `renderShow(123)`, al cărei cod este un exemplu fictiv, dar puteți vedea pe el cum se transmit datele către șablon, adică prin scrierea în `$this->template`. -Ulterior, prezentatorul returnează răspunsul. Acesta poate fi o pagină HTML, o imagine, un document XML, trimiterea unui fișier de pe disc, JSON sau redirecționarea către o altă pagină. Este important de reținut că, dacă nu spunem în mod explicit cum să răspundem (ceea ce este cazul `ProductPresenter`), răspunsul va fi redarea șablonului cu o pagină HTML. De ce? Ei bine, pentru că în 99% din cazuri dorim să desenăm un șablon, așa că prezentatorul ia acest comportament ca fiind implicit și dorește să ne ușureze munca. Acesta este punctul de vedere al lui Nette. +Ulterior, presenterul returnează un răspuns. Acesta poate fi o pagină HTML, o imagine, un document XML, trimiterea unui fișier de pe disc, JSON sau chiar o redirecționare către o altă pagină. Important este că, dacă nu spunem explicit cum să răspundă (ceea ce este cazul `ProductPresenter`), răspunsul va fi redarea șablonului cu pagina HTML. De ce? Deoarece în 99% din cazuri dorim să redăm un șablon, prin urmare presenterul consideră acest comportament ca fiind implicit și vrea să ne ușureze munca. Acesta este scopul Nette. -Nici măcar nu trebuie să precizăm ce șablon să desenăm, el derivă calea către acesta conform unei logici simple. În cazul prezentatorului `Product` și al acțiunii `show`, el încearcă să vadă dacă unul dintre aceste fișiere șablon există în raport cu directorul în care se află clasa `ProductPresenter`: +Nu trebuie nici măcar să specificăm ce șablon să redăm, calea către acesta o deduce singur. În cazul acțiunii `show`, încearcă pur și simplu să încarce șablonul `show.latte` din directorul cu clasa `ProductPresenter`. De asemenea, încearcă să găsească layout-ul în fișierul `@layout.latte` (mai detaliat despre [găsirea șabloanelor |templates#Căutarea șabloanelor]). -- `templates/Product/show.latte` -- `templates/Product.show.latte` - -De asemenea, va încerca să găsească aspectul în fișierul `@layout.latte` și apoi va reda șablonul. Acum sarcina prezentatorului și a întregii aplicații este finalizată. În cazul în care șablonul nu există, va fi returnată o pagină cu eroarea 404. Puteți citi mai multe despre prezentatori pe pagina [Prezentatori |Presenters]. +Și ulterior redă șabloanele. Astfel, sarcina presenterului și a întregii aplicații este finalizată și lucrarea este încheiată. Dacă șablonul nu ar exista, s-ar returna o pagină cu eroarea 404. Mai multe despre presentere puteți citi pe pagina [Presentere |presenters]. [* request-flow.svg *] -Doar pentru a fi siguri, să încercăm să recapitulăm întregul proces cu un URL ușor diferit: +Pentru siguranță, să încercăm să recapitulăm întregul proces cu un URL puțin diferit: 1) URL-ul va fi `https://example.com` -2) vom porni aplicația, vom crea un container și vom rula `Application::run()` -3) routerul decodifică URL-ul ca o pereche `Home:default` -4) este creat un obiect `HomePresenter` +2) inițializăm aplicația, se creează containerul și se rulează `Application::run()` +3) routerul decodează URL-ul ca perechea `Home:default` +4) se creează obiectul clasei `HomePresenter` 5) se apelează metoda `renderDefault()` (dacă există) -6) este redat un șablon `templates/Home/default.latte` cu un layout `templates/@layout.latte` +6) se redă șablonul, de ex. `default.latte` cu layout-ul, de ex. `@layout.latte` -Este posibil să fi întâlnit acum o mulțime de concepte noi, dar noi credem că acestea au sens. Crearea de aplicații în Nette este o joacă de copii. +Poate că v-ați întâlnit acum cu o mulțime de termeni noi, dar credem că au sens. Crearea aplicațiilor în Nette este o adevărată plăcere. -Șabloane .[#toc-templates] -========================== +Șabloane +======== -În ceea ce privește șabloanele, Nette folosește sistemul de șabloane [Latte |latte:]. De aceea, fișierele cu șabloane se termină cu `.latte`. Latte este utilizat deoarece este cel mai sigur sistem de șabloane pentru PHP și, în același timp, cel mai intuitiv sistem. Nu trebuie să învățați multe lucruri noi, trebuie doar să cunoașteți PHP și câteva etichete Latte. Veți afla totul [în documentație |latte:]. +Deoarece am ajuns la subiectul șabloanelor, în Nette se utilizează sistemul de șabloane [Latte |latte:]. De aceea și extensiile `.latte` la șabloane. Latte se utilizează, pe de o parte, pentru că este cel mai sigur sistem de șabloane pentru PHP și, pe de altă parte, și cel mai intuitiv sistem. Nu trebuie să învățați multe lucruri noi, vă descurcați cu cunoștințele de PHP și câteva tag-uri. Totul veți afla [în documentație |templates]. -În șablon [creăm un link |creating-links] către alți prezentatori & acțiuni după cum urmează: +În șablon se [creează linkuri |creating-links] către alți presenteri & acțiuni astfel: ```latte -product detail +detaliu produs ``` -Pur și simplu scrieți binomul cunoscut `Presenter:action` în locul URL-ului real și includeți orice parametru. Trucul este `n:href`, care spune că acest atribut va fi procesat de Nette. Și acesta va genera: +Pur și simplu, în loc de URL-ul real, scrieți perechea cunoscută `Presenter:action` și specificați eventualii parametri. Trucul este în `n:href`, care spune că acest atribut va fi procesat de Nette. Și va genera: ```latte -product detail +detaliu produs ``` -Routerul menționat anterior se ocupă de generarea URL-ului. De fapt, routerele din Nette sunt unice prin faptul că pot efectua nu numai transformări de la un URL la o pereche prezentator:acțiune, ci și invers, generează un URL din numele prezentatorului + acțiune + parametri. -Datorită acestui fapt, în Nette puteți schimba complet forma URL-ului în întreaga aplicație finalizată fără a schimba nici măcar un singur caracter din șablon sau prezentator, doar prin modificarea routerului. -Și datorită acestui lucru, funcționează așa-numita canonizare, care este o altă caracteristică unică a Nette, care îmbunătățește SEO (optimizarea capacității de căutare pe internet) prin prevenirea automată a existenței conținutului duplicat la diferite URL-uri. -Mulți programatori consideră acest lucru uimitor. +Generarea URL-ului este responsabilitatea routerului menționat anterior. De fapt, routerele din Nette sunt excepționale prin faptul că pot efectua nu numai transformări din URL în perechea presenter:action, ci și invers, adică din numele presenterului + acțiunii + parametrilor să genereze un URL. Datorită acestui fapt, în Nette puteți schimba complet formele URL-urilor în întreaga aplicație finalizată, fără a schimba un singur caracter în șablon sau presenter. Doar prin modificarea routerului. De asemenea, datorită acestui fapt funcționează așa-numita canonizare, care este o altă caracteristică unică a Nette, care contribuie la un SEO mai bun (optimizarea găsirii pe internet) prin prevenirea automată a existenței conținutului duplicat la URL-uri diferite. Mulți programatori consideră acest lucru uimitor. -Componente interactive .[#toc-interactive-components] -===================================================== +Componente interactive +====================== -Mai avem un lucru de spus despre prezentatori: aceștia au un sistem de componente încorporat. Cei mai în vârstă dintre dumneavoastră își amintesc poate ceva similar din Delphi sau ASP.NET Web Forms. React sau Vue.js este construit pe ceva foarte asemănător. În lumea cadrelor PHP, aceasta este o caracteristică complet unică. +Despre presentere trebuie să vă mai spunem un lucru: au încorporat un sistem de componente. Ceva similar ar putea fi cunoscut de veterani din Delphi sau ASP.NET Web Forms, ceva asemănător stă la baza React sau Vue.js. În lumea framework-urilor PHP, este o caracteristică complet unică. -Componentele sunt unități separate reutilizabile pe care le plasăm în pagini (adică în prezentatori). Ele pot fi [formulare |forms:in-presenter], [datagrids |https://componette.org/contributte/datagrid/], meniuri, sondaje, de fapt, orice are sens să fie folosit în mod repetat. Putem să ne creăm propriile componente sau să folosim o parte din [gama imensă |https://componette.org] de componente opensource. +Componentele sunt unități separate, reutilizabile, pe care le inserăm în pagini (adică presentere). Acestea pot fi [formulare |forms:in-presenter], [datagrid-uri |https://componette.org/contributte/datagrid/], meniuri, sondaje de votare, de fapt, orice are sens să fie folosit în mod repetat. Putem crea propriile componente sau putem folosi unele din [oferta imensă |https://componette.org] de componente open source. -Componentele schimbă în mod fundamental abordarea dezvoltării aplicațiilor. Ele vor deschide noi posibilități de a compune pagini din unități predefinite. Și au ceva în comun cu [Hollywood-ul |components#Hollywood style]. +Componentele influențează fundamental abordarea creării aplicațiilor. Vă vor deschide noi posibilități de compunere a paginilor din unități pre-pregătite. Și, în plus, au ceva în comun cu [Hollywood-ul |components#Stilul Hollywood]. -Containerul și configurația DI .[#toc-di-container-and-configuration] -===================================================================== +Container DI și configurare +=========================== -Containerul DI (fabrica de obiecte) este inima întregii aplicații. +Containerul DI sau fabrica de obiecte este inima întregii aplicații. -Nu vă faceți griji, nu este o cutie neagră magică, așa cum ar putea părea din cuvintele anterioare. De fapt, este o clasă PHP destul de plictisitoare generată de Nette și stocată într-un director de cache. Are o mulțime de metode numite `createServiceAbcd()` și fiecare dintre ele creează și returnează un obiect. Da, există, de asemenea, o metodă `createServiceApplication()` care va produce `Nette\Application\Application`, de care aveam nevoie în fișierul `index.php` pentru a rula aplicația. Și există metode pentru a produce prezentatori individuali. Și așa mai departe. +Nu vă faceți griji, nu este nicio cutie neagră magică, așa cum ar putea părea din rândurile anterioare. De fapt, este o clasă PHP destul de plictisitoare, pe care Nette o generează și o salvează în directorul de cache. Are o mulțime de metode numite precum `createServiceAbcd()` și fiecare dintre ele știe să creeze și să returneze un anumit obiect. Da, există și metoda `createServiceApplication()`, care creează `Nette\Application\Application`, de care aveam nevoie în fișierul `index.php` pentru a porni aplicația. Și există metode care creează presentere individuale. Și așa mai departe. -Obiectele pe care le creează containerul DI se numesc servicii dintr-un anumit motiv. +Obiectelor pe care le creează containerul DI li se spune, din anumite motive, servicii. -Ceea ce este cu adevărat special la această clasă este faptul că nu este programată de dumneavoastră, ci de către cadru. Acesta generează de fapt codul PHP și îl salvează pe disc. Dumneavoastră doar dați instrucțiuni cu privire la obiectele pe care containerul ar trebui să fie capabil să le producă și cum anume. Iar aceste instrucțiuni sunt scrise în [fișiere de configurare |bootstrap#DI Container Configuration] în [format NEON |neon:format] și, prin urmare, au extensia `.neon`. +Ceea ce este cu adevărat special la această clasă este că nu o programați voi, ci framework-ul. El generează efectiv codul PHP și îl salvează pe disc. Voi doar dați instrucțiuni despre ce obiecte ar trebui să știe să creeze containerul și cum anume. Iar aceste instrucțiuni sunt scrise în [fișierele de configurare |bootstrapping#Configurarea containerului DI], pentru care se utilizează formatul [NEON|neon:format] și, prin urmare, au și extensia `.neon`. -Fișierele de configurare sunt utilizate exclusiv pentru a da instrucțiuni containerului DI. Astfel, de exemplu, dacă precizez opțiunea `expiration: 14 days` în secțiunea [sesiune |http:configuration#Session], containerul DI, atunci când creează obiectul `Nette\Http\Session` care reprezintă sesiunea, va apela metoda sa `setExpiration('14 days')`, și astfel configurația devine realitate. +Fișierele de configurare servesc exclusiv pentru a instrui containerul DI. Deci, dacă, de exemplu, specific în secțiunea [session |http:configuration#Sesiune] opțiunea `expiration: 14 days`, atunci containerul DI, la crearea obiectului `Nette\Http\Session` reprezentând sesiunea, va apela metoda sa `setExpiration('14 days')` și astfel configurația devine realitate. -Există un întreg capitol pregătit pentru dumneavoastră, în care se descrie ce poate fi [configurat |nette:configuring] și cum să [vă definiți propriile servicii |dependency-injection:services]. +Există un capitol întreg pregătit pentru voi, care descrie ce totul poate fi [configurat |nette:configuring] și cum să [definiți propriile servicii |dependency-injection:services]. -Odată ce intrați în crearea serviciilor, veți da peste cuvântul [autowiring |dependency-injection:autowiring]. Acesta este un gadget care vă va face viața incredibil de ușoară. Acesta poate trece automat obiectele acolo unde aveți nevoie de ele (în constructorii claselor dvs., de exemplu), fără să trebuiască să faceți nimic. Veți descoperi că containerul DI din Nette este un mic miracol. +Odată ce pătrundeți puțin în crearea serviciilor, veți întâlni cuvântul [autowiring |dependency-injection:autowiring]. Acesta este un truc care vă va simplifica viața într-un mod incredibil. Poate transmite automat obiectele acolo unde aveți nevoie de ele (de exemplu, în constructorii claselor voastre), fără a fi nevoie să faceți nimic. Veți descoperi că containerul DI din Nette este un mic miracol. -Ce urmează? .[#toc-what-next] -============================= +Unde să mergem mai departe? +=========================== -Am trecut în revistă principiile de bază ale aplicațiilor în Nette. Până acum, foarte superficial, dar în curând veți pătrunde în profunzime și, în cele din urmă, veți crea aplicații web minunate. Unde să continuăm? Ați încercat tutorialul [Creează-ți prima aplicație |quickstart:]? +Am parcurs principiile de bază ale aplicațiilor în Nette. Deocamdată foarte superficial, dar în curând veți pătrunde în profunzime și, în timp, veți crea aplicații web minunate. Unde să continuăm? Ați încercat deja tutorialul [Scriem prima aplicație|quickstart:]? -Pe lângă cele de mai sus, Nette are un întreg arsenal de [clase utile |utils:], un [strat de baze de date |database:], etc. Încercați în mod intenționat doar să faceți clic prin documentație. Sau vizitați [blogul |https://blog.nette.org]. Veți descoperi o mulțime de lucruri interesante. +Pe lângă cele descrise mai sus, Nette dispune de un întreg arsenal de [clase utile|utils:], [un strat de baze de date|database:], etc. Încercați să răsfoiți documentația. Sau [blogul|https://blog.nette.org]. Veți descoperi o mulțime de lucruri interesante. -Lăsați cadrul să vă aducă multă bucurie 💙. +Sperăm ca framework-ul să vă aducă multă bucurie 💙 diff --git a/application/ro/modules.texy b/application/ro/modules.texy deleted file mode 100644 index bc8e11b0d2..0000000000 --- a/application/ro/modules.texy +++ /dev/null @@ -1,148 +0,0 @@ -Module -****** - -.[perex] -În Nette, modulele reprezintă unitățile logice care alcătuiesc o aplicație. Acestea includ prezentatori, șabloane, eventual și componente și clase de model. - -Un singur director pentru prezentatori și unul pentru șabloane nu ar fi suficient pentru proiectele reale. A avea zeci de fișiere într-un singur dosar este cel puțin neorganizat. Cum se poate ieși din asta? Pur și simplu le împărțim în subdirectoare pe disc și în spații de nume în cod. Și asta este exact ceea ce fac modulele Nette. - -Așadar, să uităm de un singur dosar pentru prezentatori și șabloane și să creăm în schimb module, de exemplu `Admin` și `Front`. - -/--pre -app/ -├── Presenters/ -├── Modules/ ← directory with modules -│ ├── Admin/ ← module Admin -│ │ ├── Presenters/ ← its presenters -│ │ │ ├── DashboardPresenter.php -│ │ │ └── templates/ -│ └── Front/ ← module Front -│ └── Presenters/ ← its presenters -│ └── ... -\-- - -Această structură de directoare va fi reflectată de spațiile de nume ale claselor, astfel încât, de exemplu, `DashboardPresenter` va fi în spațiul de nume `App\Modules\Admin\Presenters`: - -```php -namespace App\Modules\Admin\Presenters; - -class DashboardPresenter extends Nette\Application\UI\Presenter -{ - // ... -} -``` - -Prezentatorul `Dashboard` din cadrul modulului `Admin` este referit în cadrul aplicației folosind notația de două puncte ca `Admin:Dashboard`, iar acțiunea `default` ca `Admin:Dashboard:default`. -Și de unde știe Nette proper că `Admin:Dashboard` reprezintă clasa `App\Modules\Admin\Presenters\DashboardPresenter`? Acest lucru este determinat de [cartografierea |#mapping] din configurație. -Așadar, structura dată nu este prestabilită și o puteți modifica în funcție de nevoile dumneavoastră. - -Modulele pot conține, bineînțeles, toate celelalte elemente în afară de prezentatori și șabloane, cum ar fi componente, clase de modele etc. - - -Module imbricate .[#toc-nested-modules] ---------------------------------------- - -Modulele nu trebuie să formeze doar o structură plată, ci puteți crea și submodule, de exemplu: - -/--pre -app/ -├── Modules/ ← directory with modules -│ ├── Blog/ ← module Blog -│ │ ├── Admin/ ← submodule Admin -│ │ │ ├── Presenters/ -│ │ │ └── ... -│ │ └── Front/ ← submodule Front -│ │ ├── Presenters/ -│ │ └── ... -│ ├── Forum/ ← module Forum -│ │ └── ... -\-- - -Astfel, modulul `Blog` este împărțit în submodulele `Admin` și `Front`. Din nou, acest lucru se va reflecta în spațiile de nume, care vor fi `App\Modules\Blog\Admin\Presenters` etc. Prezentatorul `Dashboard` din interiorul submodulului este denumit `Blog:Admin:Dashboard`. - -Anveloparea poate merge cât de adânc doriți, astfel încât pot fi create sub-submodule. - - -Crearea de legături .[#toc-creating-links] ------------------------------------------- - -Legăturile din șabloanele de prezentator sunt relative la modulul curent. Astfel, legătura `Foo:default` duce la prezentatorul `Foo` din același modul ca și prezentatorul curent. Dacă modulul curent este `Front`, de exemplu, atunci legătura se prezintă astfel: - -```latte -link to Front:Product:show -``` - -O legătură este relativă chiar dacă include numele unui modul, care este considerat atunci un submodul: - -```latte -link to Front:Shop:Product:show -``` - -Legăturile absolute sunt scrise în mod analog cu căile absolute de acces de pe disc, dar cu două puncte în loc de bară oblică. Astfel, o legătură absolută începe cu două puncte: - -```latte -link to Admin:Product:show -``` - -Pentru a afla dacă ne aflăm într-un anumit modul sau într-un submodul al acestuia, putem utiliza funcția `isModuleCurrent(moduleName)`. - -```latte -
  • - ... -
  • -``` - - -Rutarea .[#toc-routing] ------------------------ - -A se vedea [capitolul privind rutarea |routing#Modules]. - - -Cartografiere .[#toc-mapping] ------------------------------ - -Definește regulile prin care numele clasei este derivat din numele prezentatorului. Le scriem în [configurație |configuration] sub cheia `application › mapping`. - -Să începem cu un exemplu care nu folosește module. Vom dori doar ca clasele de prezentator să aibă spațiul de nume `App\Presenters`. Aceasta înseamnă că un prezentator precum `Home` ar trebui să se mapeze la clasa `App\Presenters\HomePresenter`. Acest lucru poate fi realizat prin următoarea configurație: - -```neon -application: - mapping: - *: App\Presenters\*Presenter -``` - -Numele prezentatorului este înlocuit cu un asterisc în masca clasei, iar rezultatul este numele clasei. Ușor! - -Dacă împărțim prezentatorii în module, putem avea propria mapare pentru fiecare modul: - -```neon -application: - mapping: - Front: App\Modules\Front\Presenters\*Presenter - Admin: App\Modules\Admin\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -Acum, prezentatorul `Front:Home` se referă la clasa `App\Modules\Front\Presenters\HomePresenter` și prezentatorul `Admin:Dashboard` la clasa `App\Modules\Admin\Presenters\DashboardPresenter`. - -Este mai practic să creăm o regulă generală (stea) care să le înlocuiască pe primele două. Asteriscul suplimentar va fi adăugat la masca clasei doar pentru modul: - -```neon -application: - mapping: - *: App\Modules\*\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -Dar ce se întâmplă dacă folosim module imbricate și avem un prezentator `Admin:User:Edit`? În acest caz, segmentul cu un asterisc care reprezintă modulul pentru fiecare nivel se repetă pur și simplu, iar rezultatul este clasa `App\Modules\Admin\User\Presenters\EditPresenter`. - -O notație alternativă este utilizarea unui array format din trei segmente în loc de un șir de caractere. Această notație este echivalentă cu cea anterioară: - -```neon -application: - mapping: - *: [App\Modules, *, Presenters\*Presenter] -``` - -Valoarea implicită este `*: *Module\*Presenter`. diff --git a/application/ro/multiplier.texy b/application/ro/multiplier.texy index 039322805c..c564bd5532 100644 --- a/application/ro/multiplier.texy +++ b/application/ro/multiplier.texy @@ -1,30 +1,32 @@ -Multiplicator: Componente dinamice -********************************** +Multiplier: componente dinamice +******************************* -Un instrument pentru crearea dinamică de componente interactive .[perex] +.[perex] +Instrument pentru crearea dinamică a componentelor interactive -Să începem cu o problemă tipică: avem o listă de produse pe un site de comerț electronic și dorim să însoțim fiecare produs cu un formular *add to cart*. O modalitate este de a îngloba întreaga listă într-un singur formular. O modalitate mai convenabilă este să folosim [api:Nette\Application\UI\Multiplier]. +Să pornim de la un exemplu tipic: avem o listă de produse într-un magazin online, iar pentru fiecare dorim să afișăm un formular pentru adăugarea produsului în coș. Una dintre variantele posibile este să încapsulăm întreaga listă într-un singur formular. O modalitate mult mai convenabilă ne oferă însă [api:Nette\Application\UI\Multiplier]. -Multiplicatorul vă permite să definiți o fabrică pentru mai multe componente. Se bazează pe principiul componentelor imbricate - fiecare componentă care moștenește din [api:Nette\ComponentModel\Container] poate conține alte componente. +Multiplier permite definirea convenabilă a unei fabrici pentru mai multe componente. Funcționează pe principiul componentelor imbricate - fiecare componentă care moștenește de la [api:Nette\ComponentModel\Container] poate conține alte componente. -Consultați [modelul de componente |components#Components in Depth] în documentație. .[tip] +.[tip] +Vezi capitolul despre [modelul de componente |components#Componente în profunzime] în documentație sau [prezentarea lui Honza Tvrdík|https://www.youtube.com/watch?v=8y3LLexWu-I]. -Multiplier se prezintă ca o componentă părinte care își poate crea în mod dinamic copiii folosind callback-ul transmis în constructor. A se vedea exemplul: +Esența Multiplierului este că acționează în poziția de părinte, care își poate crea descendenții dinamic folosind un callback transmis în constructor. Vezi exemplul: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function () { $form = new Nette\Application\UI\Form; - $form->addInteger('amount', 'Amount:') + $form->addInteger('count', 'Număr produse:') ->setRequired(); - $form->addSubmit('send', 'Add to cart'); + $form->addSubmit('send', 'Adaugă în coș'); return $form; }); } ``` -În șablon putem reda un formular pentru fiecare produs - și fiecare formular va fi într-adevăr o componentă unică. +Acum putem, în șablon, să lăsăm pur și simplu să se redea formularul pentru fiecare produs - și fiecare va fi într-adevăr o componentă unică. ```latte {foreach $items as $item} @@ -35,26 +37,26 @@ protected function createComponentShopForm(): Multiplier {/foreach} ``` -Argumentul transmis la eticheta `{control}` spune: +Argumentul transmis în tag-ul `{control}` este în formatul care spune: -1. obține o componentă `shopForm` -2. și returnează copilul său `$item->id` +1. obține componenta `shopForm` +2. și din ea obține descendentul `$item->id` -În timpul primului apel al **1.**, componenta `shopForm` nu există încă, astfel încât metoda `createComponentShopForm` este apelată pentru a o crea. O funcție anonimă transmisă ca parametru la Multiplicator, este apoi apelată și se creează un formular. +La prima apelare a punctului **1.** `shopForm` încă nu există, așa că se apelează fabrica sa `createComponentShopForm`. Pe componenta obținută (instanța Multiplierului) este apoi apelată fabrica formularului specific - care este funcția anonimă pe care am transmis-o Multiplierului în constructor. -În iterațiile ulterioare ale `foreach`, metoda `createComponentShopForm` nu mai este apelată deoarece componenta există deja. Dar, deoarece facem referire la un alt copil (`$item->id` variază între iterații), este apelată din nou o funcție anonimă și este creat un nou formular. +În următoarea iterație a foreach-ului, metoda `createComponentShopForm` nu va mai fi apelată (componenta există), dar deoarece căutăm un alt descendent al său (`$item->id` va fi diferit în fiecare iterație), funcția anonimă va fi apelată din nou și ne va returna un nou formular. -Ultimul lucru este să ne asigurăm că formularul adaugă efectiv produsul corect în coș, deoarece în starea actuală toate formularele sunt egale și nu putem distinge căror produse aparțin. Pentru aceasta putem folosi proprietatea Multiplicator (și în general a oricărei metode de fabrică de componente din Nette Framework) ca fiecare metodă de fabrică de componente să primească ca prim argument numele componentei create. În cazul nostru, acesta ar fi `$item->id`, care este exact ceea ce avem nevoie pentru a distinge produsele individuale. Tot ce trebuie să faceți este să modificați codul de creare a formularului: +Singurul lucru care rămâne de făcut este să ne asigurăm că formularul ne adaugă în coș într-adevăr produsul pe care trebuie - în prezent, formularul este complet identic pentru fiecare produs. Ne ajută proprietatea Multiplierului (și, în general, a fiecărei fabrici de componente din Nette Framework), și anume că fiecare fabrică primește ca prim argument numele componentei create. În cazul nostru, acesta va fi `$item->id`, care este exact informația de care avem nevoie. Este suficient, așadar, să modificăm ușor crearea formularului: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function ($itemId) { $form = new Nette\Application\UI\Form; - $form->addInteger('amount', 'Amount:') + $form->addInteger('count', 'Număr produse:') ->setRequired(); $form->addHidden('itemId', $itemId); - $form->addSubmit('send', 'Add to cart'); + $form->addSubmit('send', 'Adaugă în coș'); return $form; }); } diff --git a/application/ro/presenters.texy b/application/ro/presenters.texy index cf977be9a2..d14383c97a 100644 --- a/application/ro/presenters.texy +++ b/application/ro/presenters.texy @@ -1,39 +1,39 @@ -Prezentatori -************ +Presentere +**********
    -Vom învăța cum să scriem prezentări și șabloane în Nette. După ce veți citi veți ști: +Vom face cunoștință cu modul în care se scriu presenterele și șabloanele în Nette. După citire, veți ști: -- cum funcționează prezentatorul +- cum funcționează un presenter - ce sunt parametrii persistenți -- cum se redă un șablon +- cum se redau șabloanele
    -[Știm deja |how-it-works#nette-application] că un prezentator este o clasă care reprezintă o anumită pagină a unei aplicații web, cum ar fi o pagină de pornire; un produs în magazinul electronic; un formular de înregistrare; un feed de hartă a site-ului etc. Aplicația poate avea de la unul până la mii de prezentatori. În alte framework-uri, aceștia sunt cunoscuți și sub denumirea de controlori. +[Știm deja |how-it-works#Nette Application] că presenterul este o clasă care reprezintă o anumită pagină specifică a aplicației web, de ex. pagina de start; un produs într-un magazin online; formularul de conectare; feed-ul sitemap etc. Aplicația poate avea de la unul la mii de presentere. În alte framework-uri li se mai spune și controllere. -De obicei, termenul de prezentator se referă la un descendent al clasei [api:Nette\Application\UI\Presenter], care este potrivită pentru interfețele web și pe care o vom discuta în restul acestui capitol. În sens general, un prezentator este orice obiect care implementează interfața [api:Nette\Application\IPresenter]. +De obicei, prin termenul presenter se înțelege un descendent al clasei [api:Nette\Application\UI\Presenter], care este potrivit pentru generarea interfețelor web și căruia ne vom dedica în restul acestui capitol. În sens general, un presenter este orice obiect care implementează interfața [api:Nette\Application\IPresenter]. -Ciclul de viață al unui prezentator .[#toc-life-cycle-of-presenter] -=================================================================== +Ciclul de viață al presenterului +================================ -Sarcina prezentatorului este de a procesa cererea și de a returna un răspuns (care poate fi o pagină HTML, o imagine, o redirecționare etc.). +Sarcina presenterului este de a gestiona cererea și de a returna un răspuns (care poate fi o pagină HTML, o imagine, o redirecționare etc.). -Așadar, la început este o cerere. Nu este direct o cerere HTTP, ci un obiect [api:Nette\Application\Request] în care a fost transformată cererea HTTP cu ajutorul unui router. De obicei, nu intrăm în contact cu acest obiect, deoarece prezentatorul deleagă în mod inteligent procesarea cererii către metode speciale, pe care le vom vedea acum. +Deci, la început i se transmite cererea. Nu este direct o cerere HTTP, ci obiectul [api:Nette\Application\Request], în care a fost transformată cererea HTTP cu ajutorul routerului. Cu acest obiect, de obicei, nu intrăm în contact, deoarece presenterul deleagă inteligent procesarea cererii către alte metode, pe care le vom prezenta acum. -[* lifecycle.svg *] *** *Ciclul de viață al prezentatorului* .<> +[* lifecycle.svg *] *** Ciclul de viață al presenterului .<> -Figura prezintă o listă de metode care sunt apelate secvențial de sus în jos, dacă există. Niciuna dintre ele nu trebuie să existe neapărat, putem avea un presenter complet gol, fără nicio metodă, și să construim un simplu web static pe el. +Imaginea reprezintă lista metodelor care sunt apelate succesiv de sus în jos, dacă există. Niciuna dintre ele nu trebuie să existe, putem avea un presenter complet gol, fără nicio metodă, și să construim pe el un site web static simplu. `__construct()` --------------- -Constructorul nu face parte exact din ciclul de viață al prezentatorului, deoarece este apelat în momentul creării obiectului. Dar îl menționăm datorită importanței sale. Constructorul (împreună cu [metoda inject |best-practices:inject-method-attribute]) este utilizat pentru a trece dependențele. +Constructorul nu face parte în totalitate din ciclul de viață al presenterului, deoarece este apelat în momentul creării obiectului. Dar îl menționăm datorită importanței sale. Constructorul (împreună cu [metoda inject|best-practices:inject-method-attribute]) servește la transmiterea dependențelor. -Prezentatorul nu trebuie să se ocupe de logica de afaceri a aplicației, să scrie și să citească din baza de date, să efectueze calcule etc. Aceasta este sarcina pentru clasele dintr-un strat, pe care îl numim model. De exemplu, clasa `ArticleRepository` poate fi responsabilă de încărcarea și salvarea articolelor. Pentru ca prezentatorul să o folosească, aceasta este [transmisă folosind injecția de dependență |dependency-injection:passing-dependencies]: +Presenterul nu ar trebui să se ocupe de logica de business a aplicației, să scrie și să citească din baza de date, să efectueze calcule etc. Pentru asta există clase din stratul pe care îl numim model. De exemplu, clasa `ArticleRepository` poate avea responsabilitatea de a încărca și salva articole. Pentru ca presenterul să poată lucra cu ea, o primește [transmisă prin dependency injection |dependency-injection:passing-dependencies]: ```php @@ -50,39 +50,39 @@ class ArticlePresenter extends Nette\Application\UI\Presenter `startup()` ----------- -Imediat după primirea cererii, se invocă metoda `startup ()`. Puteți să o utilizați pentru a inițializa proprietățile, a verifica privilegiile utilizatorilor etc. Este necesar să se apeleze întotdeauna strămoșul `parent::startup()`. +Imediat după primirea cererii, se apelează metoda `startup()`. O puteți utiliza pentru inițializarea proprietăților, verificarea permisiunilor utilizatorului etc. Este necesar ca metoda să apeleze întotdeauna părintele `parent::startup()`. `action(args...)` .{toc: action()} -------------------------------------------------- -Similar cu metoda `render()`. În timp ce `render()` este destinată să pregătească datele pentru un anumit șablon, care este ulterior redat, în `action()` o solicitare este procesată fără a fi urmată de redarea șablonului. De exemplu, datele sunt procesate, un utilizator este logat sau deconectat și așa mai departe, iar apoi se [redirecționează în altă parte |#Redirection]. +Similară cu metoda `render()`. În timp ce `render()` este destinată pregătirii datelor pentru un șablon specific care urmează să fie redat, în `action()` se procesează cererea fără legătură cu redarea șablonului. De exemplu, se procesează date, se conectează sau deconectează utilizatorul, și așa mai departe, și apoi [se redirecționează în altă parte |#Redirecționare]. -Este important ca `action()` este apelat înainte de `render()`, astfel încât în interiorul acesteia să putem eventual să schimbăm următorul curs al ciclului de viață, adică să schimbăm șablonul care va fi redat și, de asemenea, metoda `render()` care va fi apelată, folosind `setView('otherView')`. +Important este că `action()` se apelează înainte de `render()`, deci în ea putem eventual schimba cursul ulterior al evenimentelor, adică să schimbăm șablonul care va fi redat, și, de asemenea, metoda `render()` care va fi apelată. Și asta folosind `setView('jineView')`. -Parametrii din cerere sunt trecuți către metodă. Este posibil și recomandat să se precizeze tipurile pentru parametri, de exemplu `actionShow(int $id, string $slug = null)` - dacă parametrul `id` lipsește sau dacă nu este un număr întreg, prezentatorul returnează [eroarea 404 |#Error 404 etc.] și încheie operațiunea. +Metodei i se transmit parametri din cerere. Este posibil și recomandat să se specifice tipurile parametrilor, de ex. `actionShow(int $id, ?string $slug = null)` - dacă parametrul `id` lipsește sau dacă nu este un integer, presenterul va returna [eroarea 404 |#Eroare 404 etc] și va încheia activitatea. `handle(args...)` .{toc: handle()} -------------------------------------------------- -Această metodă procesează așa-numitele semnale, pe care le vom discuta în capitolul despre [componente |components#Signal]. Ea este destinată în principal componentelor și procesării cererilor AJAX. +Metoda procesează așa-numitele semnale, cu care ne vom familiariza în capitolul dedicat [componentelor |components#Semnal]. Este destinată în special componentelor și procesării cererilor AJAX. -Parametrii sunt pasați metodei, ca în cazul lui `action()`, inclusiv verificarea tipului. +Metodei i se transmit parametri din cerere, ca în cazul `action()`, inclusiv verificarea tipului. `beforeRender()` ---------------- -Metoda `beforeRender`, după cum sugerează și numele, este apelată înainte de fiecare metodă `render()`. Este utilizată pentru configurarea obișnuită a șabloanelor, trecerea variabilelor pentru aspect și așa mai departe. +Metoda `beforeRender`, așa cum sugerează și numele, se apelează înainte de fiecare metodă `render()`. Se utilizează pentru configurarea comună a șablonului, transmiterea variabilelor pentru layout și altele asemenea. `render(args...)` .{toc: render()} ---------------------------------------------- -Locul în care pregătim șablonul pentru redarea ulterioară, îi transmitem date etc. +Locul unde pregătim șablonul pentru redarea ulterioară, îi transmitem date etc. -Parametrii sunt trecuți la metodă, ca în cazul lui `action()`, inclusiv verificarea tipului. +Metodei i se transmit parametri din cerere, ca în cazul `action()`, inclusiv verificarea tipului. ```php public function renderShow(int $id): void @@ -96,104 +96,104 @@ public function renderShow(int $id): void `afterRender()` --------------- -Metoda `afterRender`, după cum sugerează și numele, este apelată după fiecare `render()` metodă. Ea este utilizată destul de rar. +Metoda `afterRender`, așa cum sugerează din nou numele, se apelează după fiecare metodă `render()`. Se utilizează mai degrabă excepțional. `shutdown()` ------------ -Este apelată la sfârșitul ciclului de viață al prezentatorului. +Se apelează la sfârșitul ciclului de viață al presenterului. -**Bun sfat înainte de a merge mai departe**. După cum vedeți, prezentatorul poate gestiona mai multe acțiuni/viziuni, adică poate avea mai multe metode. `render()`. Dar recomandăm proiectarea de prezentatoare cu una sau cât mai puține acțiuni. +**Un sfat bun, înainte de a merge mai departe**. Presenterul, după cum se vede, poate gestiona mai multe acțiuni/view-uri, adică poate avea mai multe metode `render()`. Dar recomandăm proiectarea presenterelor cu una sau cât mai puține acțiuni. -Trimiterea unui răspuns .[#toc-sending-a-response] -================================================== +Trimiterea răspunsului +====================== -Răspunsul prezentatorului este, de obicei, [redarea șablonului cu pagina HTML |templates], dar poate fi, de asemenea, trimiterea unui fișier, JSON sau chiar redirecționarea către o altă pagină. +Răspunsul presenterului este, de regulă, [redarea unui șablon cu o pagină HTML|templates], dar poate fi și trimiterea unui fișier, JSON sau chiar o redirecționare către o altă pagină. -În orice moment în timpul ciclului de viață, puteți utiliza oricare dintre următoarele metode pentru a trimite un răspuns și a ieși din prezentator în același timp: +Oricând în timpul ciclului de viață putem trimite un răspuns folosind una dintre următoarele metode și, în același timp, să încheiem presenterul: -- `redirect()`, `redirectPermanent()`, `redirectUrl()` și `forward()` [redirecționări |#Redirection]. -- `error()` părăsește prezentatorul din cauza unei [erori |#Error 404 etc.] -- `sendJson($data)` părăsește prezentatorul și [trimite datele |#Sending JSON] în format JSON -- `sendTemplate()` părăsește prezentatorul și redă imediat [șablonul |templates] -- `sendResponse($response)` părăsește prezentatorul și trimite [propriul răspuns |#Responses] -- `terminate()` părăsește prezentatorul fără răspuns +- `redirect()`, `redirectPermanent()`, `redirectUrl()` și `forward()` [redirecționează |#Redirecționare] +- `error()` încheie presenterul [din cauza unei erori |#Eroare 404 etc] +- `sendJson($data)` încheie presenterul și [trimite date |#Trimiterea JSON] în format JSON +- `sendTemplate()` încheie presenterul și imediat [redă șablonul |templates] +- `sendResponse($response)` încheie presenterul și trimite [un răspuns propriu |#Răspunsuri] +- `terminate()` încheie presenterul fără răspuns -Dacă nu apelați niciuna dintre aceste metode, prezentatorul va proceda automat la redarea șablonului. De ce? Ei bine, pentru că în 99% din cazuri dorim să desenăm un șablon, așa că prezentatorul ia acest comportament ca fiind implicit și dorește să ne ușureze munca. +Dacă nu apelați niciuna dintre aceste metode, presenterul va trece automat la redarea șablonului. De ce? Deoarece în 99% din cazuri dorim să redăm un șablon, prin urmare presenterul consideră acest comportament ca fiind implicit și vrea să ne ușureze munca. -Crearea legăturilor .[#toc-creating-links] -========================================== +Crearea linkurilor +================== -Prezentatorul are o metodă `link()`, care este utilizată pentru a crea legături URL către alți prezentatori. Primul parametru este prezentatorul și acțiunea țintă, urmat de argumente, care pot fi transmise sub formă de matrice: +Presenterul dispune de metoda `link()`, cu ajutorul căreia se pot crea linkuri URL către alți presenteri. Primul parametru este presenterul & acțiunea țintă, urmate de argumentele transmise, care pot fi specificate ca array: ```php $url = $this->link('Product:show', $id); -$url = $this->link('Product:show', [$id, 'lang' => 'en']); +$url = $this->link('Product:show', [$id, 'lang' => 'cs']); ``` -În șablon, creăm legături către alți prezentatori și acțiuni după cum urmează: +În șablon se creează linkuri către alți presenteri & acțiuni în acest mod: ```latte -product detail +detaliu produs ``` -Pur și simplu scrieți perechea cunoscută `Presenter:action` în locul URL-ului real și includeți orice parametru. Trucul este `n:href`, care spune că acest atribut va fi procesat de Latte și generează un URL real. În Nette, nu trebuie să vă gândiți deloc la URL-uri, ci doar la prezentatori și acțiuni. +Pur și simplu, în loc de URL-ul real, scrieți perechea cunoscută `Presenter:action` și specificați eventualii parametri. Trucul este în `n:href`, care spune că acest atribut va fi procesat de Latte și va genera URL-ul real. În Nette, nu trebuie să vă gândiți deloc la URL-uri, ci doar la presentere și acțiuni. -Pentru mai multe informații, consultați [Crearea de legături |Creating Links]. +Mai multe informații găsiți în capitolul [Crearea linkurilor URL|creating-links]. -Redirecționare .[#toc-redirection] -================================== +Redirecționare +============== -Metodele `redirect()` și `forward()` sunt utilizate pentru a trece la un alt prezentator, care au o sintaxă foarte asemănătoare cu cea a metodei [link() |#Creating Links]. +Pentru a trece la un alt presenter se utilizează metodele `redirect()` și `forward()`, care au o sintaxă foarte similară cu metoda [link() |#Crearea linkurilor]. -Metoda `forward()` trece imediat la noul prezentator, fără redirecționare HTTP: +Metoda `forward()` trece imediat la noul presenter fără redirecționare HTTP: ```php $this->forward('Product:show'); ``` -Exemplu de redirecționare temporară cu codul HTTP 302 sau 303: +Exemplu de așa-numită redirecționare temporară cu codul HTTP 302 (sau 303, dacă metoda cererii curente este POST): ```php $this->redirect('Product:show', $id); ``` -Pentru a obține o redirecționare permanentă cu codul HTTP 301, utilizați: +Redirecționarea permanentă cu codul HTTP 301 se realizează astfel: ```php $this->redirectPermanent('Product:show', $id); ``` -Puteți redirecționa către un alt URL din afara aplicației cu metoda `redirectUrl()`: +Către un alt URL în afara aplicației se poate redirecționa cu metoda `redirectUrl()`. Ca al doilea parametru se poate specifica codul HTTP, implicit este 302 (sau 303, dacă metoda cererii curente este POST): ```php $this->redirectUrl('https://nette.org'); ``` -Redirecționarea încheie imediat ciclul de viață al prezentatorului prin aruncarea așa-numitei excepții de terminare silențioasă `Nette\Application\AbortException`. +Redirecționarea încheie imediat activitatea presenterului prin aruncarea așa-numitei excepții de terminare silențioasă `Nette\Application\AbortException`. -Înainte de redirecționare, este posibil să se trimită un [mesaj flash |#Flash Messages], mesaje care vor fi afișate în șablon după redirecționare. +Înainte de redirecționare se poate trimite un [mesaj flash |#Mesaje flash], adică mesaje care vor fi afișate în șablon după redirecționare. -Mesaje flash .[#toc-flash-messages] -=================================== +Mesaje flash +============ -Acestea sunt mesaje care, de obicei, informează despre rezultatul unei operații. O caracteristică importantă a mesajelor flash este că acestea sunt disponibile în șablon chiar și după redirecționare. Chiar și după ce au fost afișate, ele vor rămâne în viață încă 30 de secunde - de exemplu, în cazul în care utilizatorul ar reîmprospăta involuntar pagina - mesajul nu se va pierde. +Acestea sunt mesaje care informează de obicei despre rezultatul unei operațiuni. O caracteristică importantă a mesajelor flash este că sunt disponibile în șablon chiar și după redirecționare. Chiar și după afișare, rămân active încă 30 de secunde – de exemplu, în cazul în care utilizatorul ar reîncărca pagina din cauza unei erori de transmisie - mesajul nu dispare imediat. -Trebuie doar să apelați metoda [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] și presenter se va ocupa de transmiterea mesajului către șablon. Primul argument este textul mesajului, iar al doilea argument opțional este tipul acestuia (error, warning, info etc.). Metoda `flashMessage()` returnează o instanță de mesaj flash, pentru a ne permite să adăugăm mai multe informații. +Este suficient să apelați metoda [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] și presenterul se va ocupa de transmiterea către șablon. Primul parametru este textul mesajului și al doilea parametru opțional este tipul său (error, warning, info etc.). Metoda `flashMessage()` returnează instanța mesajului flash, căreia i se pot adăuga informații suplimentare. ```php -$this->flashMessage('Item was removed.'); -$this->redirect(/* ... */); +$this->flashMessage('Elementul a fost șters.'); +$this->redirect(/* ... */); // și redirecționăm ``` -În șablon, aceste mesaje sunt disponibile în variabila `$flashes` sub forma unor obiecte `stdClass`, care conțin proprietățile `message` (textul mesajului), `type` (tipul mesajului) și pot conține informațiile despre utilizator deja menționate. Le desenăm după cum urmează: +În șablon, aceste mesaje sunt disponibile în variabila `$flashes` ca obiecte `stdClass`, care conțin proprietățile `message` (textul mesajului), `type` (tipul mesajului) și pot conține informațiile utilizatorului menționate anterior. Le redăm, de exemplu, astfel: ```latte {foreach $flashes as $flash} @@ -202,10 +202,10 @@ $this->redirect(/* ... */); ``` -Eroare 404 etc. .[#toc-error-404-etc] -===================================== +Eroare 404 etc. +=============== -Atunci când nu putem îndeplini cererea deoarece, de exemplu, articolul pe care dorim să îl afișăm nu există în baza de date, vom arunca eroarea 404 folosind metoda `error(string $message = null, int $httpCode = 404)`, care reprezintă eroarea HTTP 404: +Dacă cererea nu poate fi îndeplinită, de exemplu, pentru că articolul pe care dorim să îl afișăm nu există în baza de date, aruncăm eroarea 404 cu metoda `error(?string $message = null, int $httpCode = 404)`. ```php public function renderShow(int $id): void @@ -218,14 +218,13 @@ public function renderShow(int $id): void } ``` -Codul de eroare HTTP poate fi trecut ca al doilea parametru, valoarea implicită fiind 404. Metoda funcționează prin aruncarea excepției `Nette\Application\BadRequestException`, după care `Application` transmite controlul către prezentatorul de erori. Care este un prezentator a cărui sarcină este de a afișa o pagină de informare cu privire la eroare. -Error-preseter este setat în [configurația aplicației |configuration]. +Codul HTTP al erorii poate fi transmis ca al doilea parametru, implicit este 404. Metoda funcționează aruncând excepția `Nette\Application\BadRequestException`, după care `Application` predă controlul error-presenterului. Acesta este un presenter a cărui sarcină este să afișeze o pagină care informează despre eroarea apărută. Setarea error-presenterului se face în [configurația application|configuration]. -Trimiterea JSON .[#toc-sending-json] -==================================== +Trimiterea JSON +=============== -Exemplu de metodă de acțiune care trimite date în format JSON și iese din prezentator: +Exemplu de metodă-acțiune care trimite date în format JSON și încheie presenterul: ```php public function actionData(): void @@ -236,33 +235,59 @@ public function actionData(): void ``` -Parametrii persistenți .[#toc-persistent-parameters] -==================================================== +Parametrii cererii .{data-version:3.1.14} +========================================= + +Presenterul și, de asemenea, fiecare componentă obțin parametrii săi din cererea HTTP. Valoarea lor o aflați cu metoda `getParameter($name)` sau `getParameters()`. Valorile sunt șiruri de caractere sau array-uri de șiruri de caractere, sunt în esență date brute obținute direct din URL. + +Pentru mai mult confort, recomandăm accesarea parametrilor prin proprietăți. Este suficient să le marcați cu atributul `#[Parameter]`: + +```php +use Nette\Application\Attributes\Parameter; // această linie este importantă + +class HomePresenter extends Nette\Application\UI\Presenter +{ + #[Parameter] + public string $theme; // trebuie să fie publică +} +``` + +Pentru proprietate, recomandăm să specificați și tipul de date (de ex. `string`), iar Nette va converti automat valoarea conform acestuia. Valorile parametrilor pot fi, de asemenea, [validate |#Validarea parametrilor]. + +La crearea unui link, valoarea parametrilor poate fi setată direct: + +```latte +click +``` -Parametrii persistenți sunt utilizați pentru a menține starea între diferite cereri. Valoarea lor rămâne aceeași chiar și după ce se face clic pe un link. Spre deosebire de datele de sesiune, aceștia sunt trecuți în URL. Acest lucru este complet automat, astfel încât nu este nevoie să îi menționăm în mod explicit în `link()` sau `n:href`. -Exemplu de utilizare? Aveți o aplicație multilingvă. Limba actuală este un parametru care trebuie să facă parte în permanență din URL. Dar ar fi incredibil de anevoios să îl includeți în fiecare link. Așa că îl transformați într-un parametru persistent numit `lang` și se va păstra singur. E foarte bine! +Parametri persistenți +===================== -Crearea unui parametru persistent este extrem de ușoară în Nette. Trebuie doar să creați o proprietate publică și să o marcați cu atributul: (anterior se folosea `/** @persistent */` ) +Parametrii persistenți sunt utilizați pentru a menține starea între diferite cereri. Valoarea lor rămâne aceeași chiar și după ce se face clic pe un link. Spre deosebire de datele din sesiune, acestea sunt transmise în URL. Și acest lucru se întâmplă complet automat, deci nu este necesar să le specificați explicit în `link()` sau `n:href`. + +Exemplu de utilizare? Aveți o aplicație multilingvă. Limba curentă este un parametru care trebuie să fie constant parte a URL-ului. Dar ar fi incredibil de obositor să îl specificați în fiecare link. Așa că îl faceți un parametru persistent `lang` și se va transmite singur. Minunat! + +Crearea unui parametru persistent în Nette este extrem de simplă. Este suficient să creați o proprietate publică și să o marcați cu un atribut: (anterior se folosea `/** @persistent */`) ```php -use Nette\Application\Attributes\Persistent; // această linie este importantă +use Nette\Application\Attributes\Persistent; // această linie este importantă class ProductPresenter extends Nette\Application\UI\Presenter { #[Persistent] - public string $lang; // trebuie să fie publice + public string $lang; // trebuie să fie publică } ``` -Dacă `$this->lang` are o valoare, cum ar fi `'en'`, atunci legăturile create folosind `link()` sau `n:href` vor conține și parametrul `lang=en`. Iar atunci când se face clic pe link, acesta va fi din nou `$this->lang = 'en'`. +Dacă `$this->lang` va avea valoarea, de exemplu, `'en'`, atunci și linkurile create folosind `link()` sau `n:href` vor conține parametrul `lang=en`. Și după ce se face clic pe link, va fi din nou `$this->lang = 'en'`. -Pentru proprietăți, vă recomandăm să includeți tipul de date (de exemplu, `string`) și puteți include și o valoare implicită. Valorile parametrilor pot fi [validate |#Validation of Persistent Parameters]. +Pentru proprietate, recomandăm să specificați și tipul de date (de ex. `string`) și puteți specifica și o valoare implicită. Valorile parametrilor pot fi [validate |#Validarea parametrilor]. -Parametrii persistenți sunt trecuți în mod implicit între toate acțiunile unui anumit prezentator. Pentru a-i transmite între mai mulți prezentatori, trebuie să îi definiți fie: +Parametrii persistenți sunt transmiși standard între toate acțiunile presenterului respectiv. Pentru a se transmite și între mai mulți presenteri, este necesar să fie definiți fie: -- într-un strămoș comun din care moștenesc prezentatorii -- în trăsătura pe care o utilizează prezentatorii: +- într-un strămoș comun, de la care moștenesc presenterele +- într-un trait, pe care presenterele îl utilizează: ```php trait LanguageAware @@ -277,48 +302,42 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -Puteți modifica valoarea unui parametru persistent atunci când creați o legătură: +La crearea unui link, valoarea parametrului persistent poate fi modificată: ```latte -detail in Czech +detaliu în cehă ``` -Sau poate fi *restat*, adică eliminat din URL. În acest caz, va lua valoarea implicită: +Sau poate fi *resetat*, adică eliminat din URL. Atunci va lua valoarea sa implicită: ```latte -click +click aici ``` -Componente interactive .[#toc-interactive-components] -===================================================== +Componente interactive +====================== -Prezentatorii au un sistem de componente încorporat. Componentele sunt unități separate reutilizabile pe care le introducem în prezentatoare. Ele pot fi [formulare |forms:in-presenter], datagrids, meniuri, de fapt orice lucru care are sens să fie folosit în mod repetat. +Presenterele au încorporat un sistem de componente. Componentele sunt unități separate, reutilizabile, pe care le inserăm în presentere. Acestea pot fi [formulare |forms:in-presenter], datagrid-uri, meniuri, de fapt, orice are sens să fie folosit în mod repetat. -Cum se plasează componentele și cum sunt utilizate ulterior în prezentator? Acest lucru este explicat în capitolul [Componente |Components]. Veți afla chiar și ce legătură au acestea cu Hollywood. +Cum se inserează componentele în presenter și cum se utilizează ulterior? Acest lucru îl veți afla în capitolul [Componente |components]. Veți descoperi chiar și ce au în comun cu Hollywood-ul. -De unde pot obține niște componente? La pagina [Componette |https://componette.org] puteți găsi câteva componente open-source și alte add-on-uri pentru Nette, care sunt realizate și partajate de comunitatea Nette Framework. +Și de unde pot obține componente? Pe pagina [Componette |https://componette.org/search/component] găsiți componente open-source și, de asemenea, o serie de alte add-on-uri pentru Nette, pe care le-au plasat aici voluntari din comunitatea din jurul framework-ului. -Aprofundare .[#toc-going-deeper] -================================ +Intrăm în profunzime +==================== .[tip] -Ceea ce am arătat până acum în acest capitol va fi probabil suficient. Rândurile următoare sunt destinate celor care sunt interesați de prezentatori în profunzime și doresc să știe totul. +Cu ceea ce am arătat până acum în acest capitol, probabil vă veți descurca complet. Următoarele rânduri sunt destinate celor care sunt interesați de presentere în profunzime și doresc să știe absolut totul. -Cerință și parametri .[#toc-requirement-and-parameters] -------------------------------------------------------- +Validarea parametrilor +---------------------- -Cererea gestionată de prezentator este obiectul [api:Nette\Application\Request] și este returnată de metoda prezentatorului `getRequest()`. Acesta include o matrice de parametri și fiecare dintre ei aparține fie unora dintre componente, fie direct prezentatorului (care este de fapt tot o componentă, deși una specială). Astfel, Nette redistribuie parametrii și pasează între componentele individuale (și prezentator) prin apelarea metodei `loadState(array $params)`. Parametrii pot fi obținuți prin metoda `getParameters(): array`, folosind individual `getParameter($name)`. Valorile parametrilor sunt șiruri de caractere sau array-uri de șiruri de caractere, acestea fiind practic date brute obținute direct de la un URL. +Valorile [parametrilor cererii |#Parametrii cererii] și ale [parametrilor persistenți |#Parametri persistenți] primite din URL sunt scrise în proprietăți de către metoda `loadState()`. Aceasta verifică, de asemenea, dacă tipul de date specificat la proprietate corespunde, altfel răspunde cu eroarea 404 și pagina nu se afișează. - -Validarea parametrilor persistenți .[#toc-validation-of-persistent-parameters] ------------------------------------------------------------------------------- - -Valorile [parametrilor persistenți |#persistent parameters] primite de la URL-uri sunt scrise în proprietăți prin metoda `loadState()`. Aceasta verifică, de asemenea, dacă tipul de date specificat în proprietate se potrivește, în caz contrar se va răspunde cu o eroare 404 și pagina nu va fi afișată. - -Nu vă încredeți niciodată orbește în parametrii persistenți, deoarece aceștia pot fi ușor suprascriși de către utilizator în URL. De exemplu, iată cum verificăm dacă `$this->lang` se află printre limbile acceptate. O modalitate bună de a face acest lucru este să suprascrieți metoda `loadState()` menționată mai sus: +Nu credeți niciodată orbește în parametri, deoarece pot fi ușor suprascriși de utilizator în URL. Astfel, de exemplu, verificăm dacă limba `$this->lang` se află printre cele suportate. O cale potrivită este să suprascriem metoda menționată `loadState()`: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -328,8 +347,8 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function loadState(array $params): void { - parent::loadState($params); // aici este setat $this->lang - // urmează verificarea valorii utilizatorului: + parent::loadState($params); // aici se setează $this->lang + // urmează controlul propriu al valorii: if (!in_array($this->lang, ['en', 'cs'])) { $this->error(); } @@ -338,43 +357,45 @@ class ProductPresenter extends Nette\Application\UI\Presenter ``` -Salvarea și restaurarea cererii .[#toc-save-and-restore-the-request] --------------------------------------------------------------------- +Salvarea și restaurarea cererii +------------------------------- -Puteți salva cererea curentă într-o sesiune sau o puteți restaura din sesiune și permite prezentatorului să o execute din nou. Acest lucru este util, de exemplu, atunci când un utilizator completează un formular și login-ul său expiră. Pentru a nu pierde date, înainte de redirecționarea către pagina de autentificare, salvăm cererea curentă în sesiune folosind `$reqId = $this->storeRequest()`, care returnează un identificator sub forma unui șir scurt și îl transmite ca parametru către prezentatorul de autentificare. +Cererea pe care o gestionează presenterul este obiectul [api:Nette\Application\Request] și este returnată de metoda presenterului `getRequest()`. -După conectare, apelăm metoda `$this->restoreRequest($reqId)`, care preia cererea din sesiune și o transmite acesteia. Metoda verifică dacă cererea a fost creată de același utilizator ca și cel care s-a logat acum este. Dacă un alt utilizator se conectează sau dacă cheia nu este validă, nu face nimic și programul continuă. +Cererea curentă poate fi salvată în sesiune sau, invers, restaurată din ea și lăsată presenterului să o execute din nou. Acest lucru este util, de exemplu, în situația în care utilizatorul completează un formular și îi expiră sesiunea de conectare. Pentru a nu pierde datele, înainte de redirecționarea către pagina de conectare, salvăm cererea curentă în sesiune folosind `$reqId = $this->storeRequest()`, care returnează identificatorul său sub forma unui șir scurt și îl transmitem ca parametru presenterului de conectare. -Consultați cartea de bucate [Cum să vă întoarceți la o pagină anterioară |best-practices:restore-request]. +După conectare, apelăm metoda `$this->restoreRequest($reqId)`, care preia cererea din sesiune și face forward către ea. Metoda verifică, în același timp, că cererea a fost creată de același utilizator care s-a conectat acum. Dacă s-ar conecta un alt utilizator sau cheia ar fi invalidă, nu face nimic și programul continuă. +Consultați ghidul [Cum să reveniți la pagina anterioară |best-practices:restore-request]. -Canonizare .[#toc-canonization] -------------------------------- -Prezentatorii au o caracteristică foarte bună care îmbunătățește SEO (optimizarea capacității de căutare pe Internet). Acestea previn în mod automat existența conținutului duplicat la diferite URL-uri. Dacă mai multe URL-uri duc la o anumită destinație, de exemplu `/index` și `/index?page=1`, cadrul desemnează unul dintre ele ca fiind primar (canonic) și le redirecționează pe celelalte către acesta folosind codul HTTP 301. Datorită acestui lucru, motoarele de căutare nu indexează paginile de două ori și nu le slăbește page rank-ul. +Canonizare +---------- -Acest proces se numește canonizare. URL-ul canonic este URL-ul generat de [router |routing], de obicei prima rută adecvată din colecție. +Presenterele au o caracteristică cu adevărat grozavă, care contribuie la un SEO mai bun (optimizarea găsirii pe internet). Previn automat existența conținutului duplicat la URL-uri diferite. Dacă către o anumită țintă duc mai multe adrese URL, de ex. `/index` și `/index?page=1`, framework-ul o determină pe una dintre ele ca fiind primară (canonică) și le redirecționează pe celelalte către ea folosind codul HTTP 301. Datorită acestui fapt, motoarele de căutare nu vă indexează paginile de două ori și nu le diluează page rank-ul. -Canonizarea este activată în mod implicit și poate fi dezactivată prin intermediul `$this->autoCanonicalize = false`. +Acest proces se numește canonizare. URL-ul canonic este cel generat de [router|routing], de regulă deci prima rută corespunzătoare din colecție. -Redirecționarea nu are loc cu o cerere AJAX sau POST, deoarece ar duce la pierderea de date sau nu ar avea nicio valoare adăugată SEO. +Canonizarea este activată implicit și poate fi dezactivată prin `$this->autoCanonicalize = false`. -De asemenea, puteți invoca manual canonizarea folosind metoda `canonicalize()`, care, ca și metoda `link()`, primește ca argumente prezentatorul, acțiunile și parametrii. Aceasta creează o legătură și o compară cu URL-ul curent. Dacă este diferit, redirecționează către link-ul generat. +Redirecționarea nu are loc la o cerere AJAX sau POST, deoarece s-ar pierde date sau nu ar avea valoare adăugată din punct de vedere SEO. + +Canonizarea poate fi invocată și manual folosind metoda `canonicalize()`, căreia i se transmit, similar metodei `link()`, presenterul, acțiunea și parametrii. Creează un link și îl compară cu URL-ul curent. Dacă diferă, redirecționează către linkul generat. ```php -public function actionShow(int $id, string $slug = null): void +public function actionShow(int $id, ?string $slug = null): void { $realSlug = $this->facade->getSlugForId($id); - // redirecționează dacă $slug este diferit de $realSlug + // redirecționează, dacă $slug diferă de $realSlug $this->canonicalize('Product:show', [$id, $realSlug]); } ``` -Evenimente .[#toc-events] -------------------------- +Evenimente +---------- -Pe lângă metodele `startup()`, `beforeRender()` și `shutdown()`, care sunt apelate ca parte a ciclului de viață al prezentatorului, pot fi definite și alte funcții care să fie apelate automat. Prezentatorul definește așa-numitele [evenimente |nette:glossary#events], iar dumneavoastră adăugați gestionarii acestora la array-urile `$onStartup`, `$onRender` și `$onShutdown`. +Pe lângă metodele `startup()`, `beforeRender()` și `shutdown()`, care sunt apelate ca parte a ciclului de viață al presenterului, pot fi definite și alte funcții care să fie apelate automat. Presenterul definește așa-numitele [evenimente |nette:glossary#Evenimente], ale căror handlere le adăugați în array-urile `$onStartup`, `$onRender` și `$onShutdown`. ```php class ArticlePresenter extends Nette\Application\UI\Presenter @@ -388,19 +409,19 @@ class ArticlePresenter extends Nette\Application\UI\Presenter } ``` -Manipulatorii din array-ul `$onStartup` sunt apelați chiar înainte de metoda `startup()`, apoi `$onRender` între `beforeRender()` și `render()` și, în sfârșit, `$onShutdown` chiar înainte de `shutdown()`. +Handlerele din array-ul `$onStartup` sunt apelate chiar înainte de metoda `startup()`, apoi `$onRender` între `beforeRender()` și `render()` și în final `$onShutdown` chiar înainte de `shutdown()`. -Răspunsuri .[#toc-responses] ----------------------------- +Răspunsuri +---------- -Răspunsul returnat de către prezentator este un obiect care implementează interfața [api:Nette\Application\Response]. Există o serie de răspunsuri gata făcute: +Răspunsul returnat de presenter este un obiect care implementează interfața [api:Nette\Application\Response]. Există o serie de răspunsuri pregătite disponibile: - [api:Nette\Application\Responses\CallbackResponse] - trimite un callback -- [api:Nette\Application\Responses\FileResponse] - trimite fișierul -- [api:Nette\Application\Responses\ForwardResponse] - transmite () +- [api:Nette\Application\Responses\FileResponse] - trimite un fișier +- [api:Nette\Application\Responses\ForwardResponse] - forward() - [api:Nette\Application\Responses\JsonResponse] - trimite JSON -- [api:Nette\Application\Responses\RedirectResponse] - redirecționează +- [api:Nette\Application\Responses\RedirectResponse] - redirecționare - [api:Nette\Application\Responses\TextResponse] - trimite text - [api:Nette\Application\Responses\VoidResponse] - răspuns gol @@ -412,10 +433,10 @@ use Nette\Application\Responses; // Text simplu $this->sendResponse(new Responses\TextResponse('Hello Nette!')); -// Trimite un fișier +// Trimite fișier $this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf')); -// Trimite un callback +// Răspunsul va fi un callback $callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse) { if ($httpResponse->getHeader('Content-Type') === 'text/html') { echo '

    Hello

    '; @@ -425,10 +446,55 @@ $this->sendResponse(new Responses\CallbackResponse($callback)); ``` -Lecturi suplimentare .[#toc-further-reading] -============================================ +Restricționarea accesului folosind `#[Requires]` .{data-version:3.2.2} +---------------------------------------------------------------------- + +Atributul `#[Requires]` oferă opțiuni avansate pentru restricționarea accesului la presentere și metodele lor. Poate fi utilizat pentru specificarea metodelor HTTP, solicitarea unei cereri AJAX, restricționarea la aceeași origine (same origin) și accesul doar prin forward. Atributul poate fi aplicat atât claselor presenterelor, cât și metodelor individuale `action()`, `render()`, `handle()` și `createComponent()`. + +Puteți specifica aceste restricții: +- pentru metode HTTP: `#[Requires(methods: ['GET', 'POST'])]` +- solicitarea unei cereri AJAX: `#[Requires(ajax: true)]` +- acces doar din aceeași origine: `#[Requires(sameOrigin: true)]` +- acces doar prin forward: `#[Requires(forward: true)]` +- restricționare la acțiuni specifice: `#[Requires(actions: 'default')]` + +Detalii găsiți în ghidul [Cum se utilizează atributul Requires |best-practices:attribute-requires]. + + +Verificarea metodei HTTP +------------------------ + +Presenterele din Nette verifică automat metoda HTTP a fiecărei cereri primite. Motivul acestei verificări este în principal securitatea. Standard, sunt permise metodele `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH`. + +Dacă doriți să permiteți în plus, de exemplu, metoda `OPTIONS`, utilizați atributul `#[Requires]` (de la Nette Application v3.2): + +```php +#[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])] +class MyPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +În versiunea 3.1, verificarea se face în `checkHttpMethod()`, care verifică dacă metoda specificată în cerere este inclusă în array-ul `$presenter->allowedMethods`. Adăugarea metodei se face astfel: + +```php +class MyPresenter extends Nette\Application\UI\Presenter +{ + protected function checkHttpMethod(): void + { + $this->allowedMethods[] = 'OPTIONS'; + parent::checkHttpMethod(); + } +} +``` + +Este important de subliniat că, dacă permiteți metoda `OPTIONS`, trebuie ulterior să o gestionați corespunzător în cadrul presenterului dvs. Metoda este adesea utilizată ca așa-numită cerere preflight, pe care browserul o trimite automat înainte de cererea reală, când este necesar să se afle dacă cererea este permisă din punctul de vedere al politicii CORS (Cross-Origin Resource Sharing). Dacă permiteți metoda, dar nu implementați un răspuns corect, acest lucru poate duce la inconsecvențe și potențiale probleme de securitate. + + +Lectură suplimentară +==================== -- [Injectarea metodelor și atributelor |best-practices:inject-method-attribute] -- [Compunerea prezentatorilor din trăsături |best-practices:presenter-traits] -- [Transmiterea de setări către prezentatori |best-practices:passing-settings-to-presenters] -- [Cum să vă întoarceți la o pagină anterioară |best-practices:restore-request] +- [Metode și atribute inject |best-practices:inject-method-attribute] +- [Compunerea presenterelor din trait-uri |best-practices:presenter-traits] +- [Transmiterea setărilor către presentere |best-practices:passing-settings-to-presenters] +- [Cum să reveniți la pagina anterioară |best-practices:restore-request] diff --git a/application/ro/routing.texy b/application/ro/routing.texy index 0879cd0109..0d580d0565 100644 --- a/application/ro/routing.texy +++ b/application/ro/routing.texy @@ -1,29 +1,28 @@ -Rutarea -******* +Rutare +******
    -Routerul este responsabil pentru tot ceea ce ține de URL-uri, astfel încât nu mai trebuie să vă gândiți la ele. Vom arăta: +Routerul se ocupă de tot ce ține de adresele URL, astfel încât să nu mai trebuiască să vă gândiți la ele. Vom arăta: -- cum să configurați routerul astfel încât URL-urile să arate așa cum doriți. -- câteva note despre redirecționarea SEO -- și vă vom arăta cum să vă scrieți propriul router +- cum să setați routerul pentru ca URL-urile să fie conform așteptărilor +- vom vorbi despre SEO și redirecționare +- și vom arăta cum să scrieți propriul router
    -URL-urile mai umane (sau URL-urile cool sau frumoase) sunt mai ușor de utilizat, mai ușor de memorat și contribuie pozitiv la SEO. Nette are în vedere acest lucru și satisface pe deplin dorințele dezvoltatorilor. Puteți proiecta structura URL pentru aplicația dumneavoastră exact așa cum doriți. -O puteți proiecta chiar și după ce aplicația este gata, deoarece se poate face fără modificări de cod sau de șablon. Aceasta este definită într-un mod elegant într-un [singur loc |#Integration], în router, și nu este împrăștiată sub formă de adnotări în toate prezentatoarele. +URL-urile mai prietenoase pentru oameni (sau și cool ori pretty URL) sunt mai utilizabile, mai ușor de reținut și contribuie pozitiv la SEO. Nette se gândește la asta și vine în întâmpinarea dezvoltatorilor. Puteți proiecta pentru aplicația dvs. exact structura de adrese URL pe care o doriți. Puteți chiar să o proiectați abia în momentul în care aplicația este deja finalizată, deoarece acest lucru se face fără intervenții în cod sau șabloane. Se definește într-un mod elegant într-un [singur loc |#Integrarea în aplicație], în router, și nu este astfel împrăștiat sub formă de adnotări în toți presenterele. -Routerul din Nette este special pentru că este **bidirecțional**, poate atât decoda URL-uri de cerere HTTP, cât și să creeze legături. Astfel, acesta joacă un rol vital în [aplicația Nette |how-it-works#Nette Application], deoarece decide ce prezentator și ce acțiune va executa cererea curentă și este, de asemenea, utilizat pentru [generarea de URL-uri |creating-links] în șablon etc. +Routerul din Nette este extraordinar prin faptul că este **bidirecțional.** Poate atât să decodeze URL-ul din cererea HTTP, cât și să creeze linkuri. Joacă, așadar, un rol esențial în [Nette Application |how-it-works#Nette Application], deoarece decide ce presenter și acțiune vor executa cererea curentă, dar este utilizat și pentru [generarea URL-urilor |creating-links] în șablon etc. -Cu toate acestea, routerul nu se limitează la această utilizare, îl puteți utiliza în aplicații în care nu se folosesc deloc prezentatorii, pentru API-uri REST etc. Mai multe în secțiunea [utilizare separată |#separated usage]. +Totuși, routerul nu este limitat doar la această utilizare, îl puteți folosi în aplicații unde nu se utilizează deloc presentere, pentru API-uri REST etc. Mai multe în secțiunea [#utilizare independentă]. -Colecția de rute .[#toc-route-collection] -========================================= +Colecție de rute +================ -Cel mai plăcut mod de a defini adresele URL în aplicație este prin intermediul clasei [api:Nette\Application\Routers\RouteList]. Definiția constă într-o listă de așa-numite rute, adică măști de adrese URL și prezentatorii și acțiunile asociate acestora, folosind un API simplu. Nu trebuie să denumim rutele. +Cel mai plăcut mod de a defini forma adreselor URL în aplicație îl oferă clasa [api:Nette\Application\Routers\RouteList]. Definiția este formată dintr-o listă de așa-numite rute, adică măști de adrese URL și presenterele și acțiunile asociate acestora, folosind un API simplu. Rutele nu trebuie denumite în niciun fel. ```php $router = new Nette\Application\Routers\RouteList; @@ -32,130 +31,130 @@ $router->addRoute('article/', 'Article:view'); // ... ``` -Exemplul spune că, dacă deschidem `https://any-domain.com/rss.xml` cu acțiunea `rss`, dacă `https://domain.com/article/12` cu acțiunea `view` etc. Dacă nu se găsește nicio rută potrivită, aplicația Nette răspunde prin aruncarea unei excepții [BadRequestException |api:Nette\Application\BadRequestException], care apare utilizatorului ca o pagină de eroare 404 Not Found. +Exemplul spune că dacă deschidem în browser `https://domain.com/rss.xml`, se va afișa presenterul `Feed` cu acțiunea `rss`, dacă `https://domain.com/article/12`, se va afișa presenterul `Article` cu acțiunea `view` etc. În cazul în care nu se găsește o rută potrivită, Nette Application reacționează aruncând excepția [BadRequestException |api:Nette\Application\BadRequestException], care este afișată utilizatorului ca o pagină de eroare 404 Not Found. -Ordinea rutelor .[#toc-order-of-routes] ---------------------------------------- +Ordinea rutelor +--------------- -Ordinea în care sunt enumerate rutele este **foarte importantă**, deoarece acestea sunt evaluate secvențial, de sus în jos. Regula este că declarăm rutele **de la specific la general**: +**Ordinea** în care sunt specificate rutele individuale este **absolut crucială**, deoarece acestea sunt evaluate secvențial de sus în jos. Se aplică regula conform căreia declarăm rutele **de la cele specifice la cele generale**: ```php -// GREȘIT: "rss.xml" se potrivește cu prima rută și înțelege greșit acest lucru ca fiind +// GREȘIT: 'rss.xml' este capturat de prima rută și înțelege acest șir ca $router->addRoute('', 'Article:view'); $router->addRoute('rss.xml', 'Feed:rss'); -// BUNĂ +// CORECT $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('', 'Article:view'); ``` -Rutele sunt, de asemenea, evaluate de sus în jos atunci când se generează legături: +Rutele sunt evaluate de sus în jos și la generarea linkurilor: ```php -// WRONG: generează un link către 'Feed:rss' ca 'admin/feed/rss' +// GREȘIT: linkul către 'Feed:rss' va fi generat ca 'admin/feed/rss' $router->addRoute('admin//', 'Admin:default'); $router->addRoute('rss.xml', 'Feed:rss'); -// BUNĂ +// CORECT $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('admin//', 'Admin:default'); ``` -Nu vă vom ascunde că este nevoie de o anumită îndemânare pentru a construi o listă în mod corect. Până când vă veți familiariza cu ea, [panoul de rutare |#Debugging Router] va fi un instrument util. +Nu vom ascunde faptul că asamblarea corectă a rutelor necesită o anumită abilitate. Până când veți pătrunde în ea, [panoul de rutare |#Depanarea routerului] vă va fi un ajutor util. -Mască și parametri .[#toc-mask-and-parameters] ----------------------------------------------- +Mască și parametri +------------------ -Masca descrie calea relativă bazată pe rădăcina site-ului. Cea mai simplă mască este un URL static: +Masca descrie calea relativă de la directorul rădăcină al site-ului. Cea mai simplă mască este un URL static: ```php $router->addRoute('products', 'Products:default'); ``` -Adesea, măștile conțin așa-numiții **parametri**. Aceștia sunt incluși între paranteze unghiulare (de ex. ``) și sunt transmise prezentatorului țintă, de exemplu, metodei `renderShow(int $year)` sau parametrului persistent `$year`: +Adesea, măștile conțin așa-numiții **parametri**. Aceștia sunt specificați între paranteze unghiulare (de ex. ``) și sunt transmiși către presenterul țintă, de exemplu metodei `renderShow(int $year)` sau parametrului persistent `$year`: ```php $router->addRoute('chronicle/', 'History:show'); ``` -Exemplul spune că, dacă deschidem `https://any-domain.com/chronicle/2020` și acțiunea `show` cu parametrul `year: 2020`. +Exemplul spune că dacă deschidem în browser `https://example.com/chronicle/2020`, se va afișa presenterul `History` cu acțiunea `show` și parametrul `year: 2020`. -Putem specifica o valoare implicită pentru parametrii direct în mască și, astfel, aceasta devine opțională: +Parametrilor le putem specifica o valoare implicită direct în mască și astfel devin opționali: ```php $router->addRoute('chronicle/', 'History:show'); ``` -Ruta va accepta acum URL-ul `https://any-domain.com/chronicle/` cu parametrul `year: 2020`. +Ruta va accepta acum și URL-ul `https://example.com/chronicle/`, care va afișa din nou `History:show` cu parametrul `year: 2020`. -Bineînțeles, numele prezentatorului și al acțiunii pot fi, de asemenea, un parametru. De exemplu: +Parametrul poate fi, desigur, și numele presenterului și al acțiunii. De exemplu, așa: ```php $router->addRoute('/', 'Home:default'); ``` -Această rută acceptă, de exemplu, un URL de forma `/article/edit` resp. `/catalog/list` și le traduce în prezentatorii și acțiunile `Article:edit` resp. `Catalog:list`. +Ruta specificată acceptă, de ex., URL-uri de forma `/article/edit` sau `/catalog/list` și le înțelege ca presentere și acțiuni `Article:edit` și `Catalog:list`. -De asemenea, oferă parametrilor `presenter` și `action` valori implicite`Home` și `default` și, prin urmare, aceștia sunt opționali. Astfel, ruta acceptă și un URL `/article` și îl traduce ca `Article:default`. Sau invers, un link către `Product:default` generează o cale `/product`, un link către `Home:default` implicit generează o cale `/`. +În același timp, atribuie parametrilor `presenter` și `action` valorile implicite `Home` și `default` și sunt, prin urmare, și opționali. Deci, ruta acceptă și URL-uri de forma `/article` și le înțelege ca `Article:default`. Sau invers, un link către `Product:default` va genera calea `/product`, un link către `Home:default` implicit va genera calea `/`. -Masca poate descrie nu numai calea relativă bazată pe rădăcina site-ului, ci și calea absolută atunci când începe cu o bară oblică, sau chiar întregul URL absolut atunci când începe cu două bare oblice: +Masca poate descrie nu numai calea relativă de la directorul rădăcină al site-ului, ci și calea absolută, dacă începe cu un slash, sau chiar întregul URL absolut, dacă începe cu două slash-uri: ```php -// calea relativă către rădăcina documentului aplicației +// relativ la document root $router->addRoute('/', /* ... */); -// cale absolută, relativă la numele de gazdă al serverului +// cale absolută (relativă la domeniu) $router->addRoute('//', /* ... */); -// URL absolut, inclusiv numele de gazdă (dar relativ la schemă) +// URL absolut inclusiv domeniul (relativ la schemă) $router->addRoute('//.example.com//', /* ... */); -// URL absolut, inclusiv schema +// URL absolut inclusiv schema $router->addRoute('https://.example.com//', /* ... */); ``` -Expresii de validare .[#toc-validation-expressions] ---------------------------------------------------- +Expresii de validare +-------------------- -Se poate specifica o condiție de validare pentru fiecare parametru folosind [expresii regulate |https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. De exemplu, să setați `id` să fie doar numeric, folosind `\d+` regexp: +Pentru fiecare parametru se poate stabili o condiție de validare folosind o [expresie regulată|https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. De exemplu, pentru parametrul `id` specificăm că poate lua doar cifre folosind expresia regulată `\d+`: ```php $router->addRoute('/[/]', /* ... */); ``` -Expresia regulată implicită pentru toți parametrii este `[^/]+`, adică totul cu excepția slash-ului. Dacă un parametru trebuie să se potrivească și cu o bară oblică, setează expresia regulată la `.+`. +Expresia regulată implicită pentru toți parametrii este `[^/]+`, adică totul cu excepția slash-ului. Dacă un parametru trebuie să accepte și slash-uri, specificăm expresia `.+`: ```php -// acceptă https://example.com/a/b/c, calea este 'a/b/c' +// acceptă https://example.com/a/b/c, path va fi 'a/b/c' $router->addRoute('', /* ... */); ``` -Secvențe opționale .[#toc-optional-sequences] ---------------------------------------------- +Secvențe opționale +------------------ -Parantezele pătrate denotă părțile opționale ale măștii. Orice parte a măștii poate fi stabilită ca opțională, inclusiv cele care conțin parametri: +În mască se pot marca părți opționale folosind paranteze drepte. Orice parte a măștii poate fi opțională, în ea se pot afla și parametri: ```php $router->addRoute('[/]', /* ... */); -// URL-uri acceptate: Parametrii: -// /en/download lang => en, name => download -// /download lang => null, name => download +// Acceptă căi: +// /cs/download => lang => cs, name => download +// /download => lang => null, name => download ``` -Bineînțeles, atunci când un parametru face parte dintr-o secvență opțională, acesta devine, de asemenea, opțional. Dacă nu are o valoare implicită, acesta va fi nul. +Când un parametru face parte dintr-o secvență opțională, devine, desigur, și el opțional. Dacă nu are specificată o valoare implicită, atunci va fi null. -Secțiunile opționale pot fi, de asemenea, în domeniu: +Părțile opționale pot fi și în domeniu: ```php $router->addRoute('//[.]example.com//', /* ... */); ``` -Secvențele pot fi liber imbricate și combinate: +Secvențele pot fi imbricate și combinate liber: ```php $router->addRoute( @@ -163,48 +162,48 @@ $router->addRoute( 'Home:default', ); -// URL-uri acceptate: -// /en/hello -// /en-us/hello -// /hello -// /hello/hello/page-12 +// Acceptă căi: +// /cs/hello +// /en-us/hello +// /hello +// /hello/page-12 ``` -Generatorul de URL-uri încearcă să mențină URL-ul cât mai scurt posibil, astfel încât ceea ce poate fi omis este omis. Prin urmare, de exemplu, o rută `index[.html]` generează o cale `/index`. Puteți inversa acest comportament scriind un semn de exclamare după paranteza pătrată din stânga: +La generarea URL-ului, se urmărește varianta cea mai scurtă, deci tot ce poate fi omis se omite. De aceea, de exemplu, ruta `index[.html]` generează calea `/index`. Inversarea comportamentului este posibilă prin specificarea unui semn de exclamare după paranteza dreaptă de deschidere: ```php -// acceptă atât /hello cât și /hello.html, generează /hello +// acceptă /hello și /hello.html, generează /hello $router->addRoute('[.html]', /* ... */); -// acceptă atât /hello cât și /hello.html, generează /hello.html +// acceptă /hello și /hello.html, generează /hello.html $router->addRoute('[!.html]', /* ... */); ``` -Parametrii opționali (adică parametrii cu valoare implicită) fără paranteze pătrate se comportă ca și cum ar fi înfășurați astfel: +Parametrii opționali (adică parametrii care au o valoare implicită) fără paranteze drepte se comportă în esență ca și cum ar fi încadrați în paranteze în felul următor: ```php $router->addRoute('//', /* ... */); -// este egal cu: +// corespunde acestuia: $router->addRoute('[/[/[]]]', /* ... */); ``` -Pentru a schimba modul în care este generată cea mai din dreapta bară oblică, adică în loc de `/home/` obțineți un `/home`, ajustați traseul în acest fel: +Dacă am dori să influențăm comportamentul slash-ului final, astfel încât, de ex., în loc de `/home/` să se genereze doar `/home`, acest lucru se poate realiza astfel: ```php $router->addRoute('[[/[/]]]', /* ... */); ``` -Wildcards .[#toc-wildcards] ---------------------------- +Substituenți +------------ -În masca de cale absolută, putem utiliza următoarele caractere wildcards pentru a evita, de exemplu, necesitatea de a scrie un domeniu în mască, care poate fi diferit în mediul de dezvoltare și în cel de producție: +În masca căii absolute putem folosi următorii substituenți și evita astfel, de ex., necesitatea de a scrie în mască domeniul, care se poate diferenția în mediul de dezvoltare și cel de producție: -- `%tld%` = domeniu de nivel superior, de exemplu, `com` sau `org` -- `%sld%` = domeniu de al doilea nivel, de exemplu `example` +- `%tld%` = top level domain, de ex. `com` sau `org` +- `%sld%` = second level domain, de ex. `example` - `%domain%` = domeniu fără subdomenii, de ex. `example.com` -- `%host%` = întreaga gazdă, de ex. `www.example.com` +- `%host%` = întregul host, de ex. `www.example.com` - `%basePath%` = calea către directorul rădăcină ```php @@ -213,10 +212,10 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ @@ -225,7 +224,7 @@ $router->addRoute('/[/]', [ ]); ``` -Sau putem folosi această formă, observați rescrierea expresiei regulate de validare: +Pentru o specificație mai detaliată, se poate utiliza o formă și mai extinsă, unde, pe lângă valorile implicite, putem seta și alte proprietăți ale parametrilor, cum ar fi expresia regulată de validare (vezi parametrul `id`): ```php use Nette\Routing\Route; @@ -243,19 +242,19 @@ $router->addRoute('/[/]', [ ]); ``` -Aceste formate mai vorbărețe sunt utile pentru adăugarea altor metadate. +Este important de menționat că, dacă parametrii definiți în array nu sunt specificați în masca căii, valorile lor nu pot fi modificate, nici prin parametrii query specificați după semnul întrebării în URL. -Filtre și traduceri .[#toc-filters-and-translations] ----------------------------------------------------- +Filtre și traduceri +------------------- -Este o bună practică să scrieți codul sursă în limba engleză, dar ce se întâmplă dacă aveți nevoie ca site-ul dvs. să aibă URL-ul tradus în altă limbă? Rute simple, cum ar fi: +Codul sursă al aplicației îl scriem în engleză, dar dacă site-ul trebuie să aibă URL-uri în română, atunci rutarea simplă de tipul: ```php $router->addRoute('/', 'Home:default'); ``` -va genera URL-uri în limba engleză, cum ar fi `/product/123` sau `/cart`. Dacă dorim ca prezentatorii și acțiunile din URL să fie traduse în limba germană (de exemplu, `/produkt/123` sau `/einkaufswagen`), putem utiliza un dicționar de traducere. Pentru a-l adăuga, avem deja nevoie de o variantă "mai vorbăreață" a celui de-al doilea parametru: +va genera URL-uri în engleză, cum ar fi `/product/123` sau `/cart`. Dacă dorim ca presenterele și acțiunile să fie reprezentate în URL prin cuvinte românești (de ex. `/produs/123` sau `/cos`), putem utiliza un dicționar de traducere. Pentru scrierea sa avem nevoie deja de varianta "mai vorbăreață" a celui de-al doilea parametru: ```php use Nette\Routing\Route; @@ -264,26 +263,26 @@ $router->addRoute('/', [ 'presenter' => [ Route::Value => 'Home', Route::FilterTable => [ - // șir de caractere în URL => prezentator - 'produkt' => 'Product', - 'einkaufswagen' => 'Cart', - 'katalog' => 'Catalog', + // șir în URL => presenter + 'produs' => 'Product', + 'cos' => 'Cart', + 'catalog' => 'Catalog', ], ], 'action' => [ Route::Value => 'default', Route::FilterTable => [ - 'liste' => 'list', + 'lista' => 'list', ], ], ]); ``` -Se pot utiliza mai multe chei de dicționar pentru același prezentator. Acestea vor crea diverse alias-uri pentru acesta. Ultima cheie este considerată a fi varianta canonică (adică cea care se va regăsi în URL-ul generat). +Mai multe chei ale dicționarului de traducere pot duce la același presenter. Astfel se creează diferite aliasuri pentru acesta. Ca variantă canonică (adică cea care va fi în URL-ul generat) se consideră ultima cheie. -Tabelul de traducere poate fi aplicat în acest mod oricărui parametru. Cu toate acestea, dacă traducerea nu există, se ia valoarea originală. Putem schimba acest comportament adăugând `Route::FilterStrict => true`, iar ruta va respinge atunci URL-ul dacă valoarea nu se află în dicționar. +Tabelul de traducere poate fi utilizat în acest mod pentru orice parametru. În același timp, dacă traducerea nu există, se ia valoarea originală. Acest comportament poate fi schimbat prin adăugarea `Route::FilterStrict => true` și ruta va respinge apoi URL-ul dacă valoarea nu se află în dicționar. -În plus față de dicționarul de traduceri sub forma unui array, este posibil să setați propriile funcții de traducere: +Pe lângă dicționarul de traducere sub formă de array, se pot implementa și funcții de traducere proprii. ```php use Nette\Routing\Route; @@ -299,15 +298,15 @@ $router->addRoute('//', [ ]); ``` -Funcția `Route::FilterIn` face conversia între parametrul din URL și șirul de caractere, care este apoi transmis către prezentator, funcția `FilterOut` asigură conversia în sens invers. +Funcția `Route::FilterIn` convertește între parametrul din URL și șirul care este apoi transmis presenterului, funcția `FilterOut` asigură conversia în sens invers. -Parametrii `presenter`, `action` și `module` au deja filtre predefinite care convertesc între stilul PascalCase resp. camelCase și kebab-case utilizat în URL. Valoarea implicită a parametrilor este deja scrisă în forma transformată, astfel încât, de exemplu, în cazul unui prezentator, se scrie `` în loc de ``. +Parametrii `presenter`, `action` și `module` au deja filtre predefinite care convertesc între stilul PascalCase respectiv camelCase și kebab-case utilizat în URL. Valoarea implicită a parametrilor se scrie deja în forma transformată, deci, de exemplu, în cazul presenterului scriem ``, nu ``. -Filtre generale .[#toc-general-filters] ---------------------------------------- +Filtre generale +--------------- -În afară de filtrele pentru parametri specifici, puteți defini, de asemenea, filtre generale care primesc un tablou asociativ al tuturor parametrilor pe care îi pot modifica în orice mod și pe care apoi îi pot returna. Filtrele generale se definesc sub tasta `null`. +Pe lângă filtrele destinate parametrilor specifici, putem defini și filtre generale, care primesc un array asociativ al tuturor parametrilor, pe care îi pot modifica în orice mod și apoi îi returnează. Filtrele generale le definim sub cheia `null`. ```php use Nette\Routing\Route; @@ -315,62 +314,85 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => 'Home', 'action' => 'default', - null => [ + '' => [ Route::FilterIn => function (array $params): array { /* ... */ }, Route::FilterOut => function (array $params): array { /* ... */ }, ], ]); ``` -Filtrele generale vă oferă posibilitatea de a ajusta comportamentul traseului în absolut orice mod. Le putem folosi, de exemplu, pentru a modifica parametrii pe baza altor parametri. De exemplu, traducerea `` și `` pe baza valorii curente a parametrului ``. +Filtrele generale oferă posibilitatea de a modifica comportamentul rutei în absolut orice mod. Le putem folosi, de exemplu, pentru modificarea parametrilor pe baza altor parametri. De exemplu, traducerea `` și `` pe baza valorii curente a parametrului ``. -În cazul în care un parametru are definit un filtru personalizat și există în același timp un filtru general, filtrul personalizat `FilterIn` este executat înaintea celui general și, invers, filtrul general `FilterOut` este executat înaintea celui personalizat. Astfel, în interiorul filtrului general se află valorile parametrilor `presenter` și `action` scrise în stilul PascalCase și respectiv camelCase. +Dacă un parametru are definit un filtru propriu și, în același timp, există un filtru general, se execută filtrul propriu `FilterIn` înainte de cel general și, invers, filtrul general `FilterOut` înainte de cel propriu. Deci, în interiorul filtrului general, valorile parametrilor `presenter` respectiv `action` sunt scrise în stilul PascalCase respectiv camelCase. -Indicator unidirecțional .[#toc-oneway-flag] --------------------------------------------- +Rute unidirecționale (OneWay) +----------------------------- -Rutele unidirecționale sunt utilizate pentru a păstra funcționalitatea vechilor URL-uri pe care aplicația nu le mai generează, dar pe care le acceptă în continuare. Le marcăm cu `OneWay`: +Rutele unidirecționale sunt utilizate pentru a menține funcționalitatea URL-urilor vechi, pe care aplicația nu le mai generează, dar le acceptă în continuare. Le marcăm cu flag-ul `OneWay`: ```php -// vechiul URL /product-info?id=123 +// URL vechi /product-info?id=123 $router->addRoute('product-info', 'Product:detail', $router::ONE_WAY); -// noul URL /product/123 +// URL nou /product/123 $router->addRoute('product/', 'Product:detail'); ``` -La accesarea vechiului URL, prezentatorul redirecționează automat către noul URL, astfel încât motoarele de căutare să nu indexeze aceste pagini de două ori (a se vedea [SEO și canonizarea |#SEO and canonization]). +La accesarea URL-ului vechi, presenterul redirecționează automat către noul URL, astfel încât motoarele de căutare nu vă vor indexa aceste pagini de două ori (vezi [#SEO și canonizare]). + + +Rutare dinamică cu callback-uri +------------------------------- + +Rutarea dinamică cu callback-uri vă permite să atribuiți rutelor direct funcții (callback-uri), care se execută atunci când calea respectivă este vizitată. Această funcționalitate flexibilă vă permite să creați rapid și eficient diferite puncte finale (endpoints) pentru aplicația dvs.: + +```php +$router->addRoute('test', function () { + echo 'sunteți la adresa /test'; +}); +``` + +Puteți defini, de asemenea, parametri în mască, care se vor transmite automat către callback-ul dvs.: + +```php +$router->addRoute('', function (string $lang) { + echo match ($lang) { + 'cs' => 'Bun venit la versiunea română a site-ului nostru!', + 'en' => 'Welcome to the English version of our website!', + }; +}); +``` -Module .[#toc-modules] ----------------------- +Module +------ -Dacă avem mai multe rute care aparțin unui singur [modul |modules], putem folosi `withModule()` pentru a le grupa: +Dacă avem mai multe rute care aparțin unui [modul |directory-structure#Presentere și șabloane] comun, utilizăm `withModule()`: ```php $router = new RouteList; -$router->withModule('Forum') // următoarele routere fac parte din modulul Forum - ->addRoute('rss', 'Feed:rss') // prezentatorul este Forum:Feed +$router->withModule('Forum') // următoarele rute fac parte din modulul Forum + ->addRoute('rss', 'Feed:rss') // presenterul va fi Forum:Feed ->addRoute('/') - ->withModule('Admin') // următoarele routere fac parte din modulul Forum:Admin + ->withModule('Admin') // următoarele rute fac parte din modulul Forum:Admin ->addRoute('sign:in', 'Sign:in'); ``` O alternativă este utilizarea parametrului `module`: ```php -// URL manage/dashboard/default se referă la prezentator Admin:Dashboard +// URL manage/dashboard/default se mapează pe presenterul Admin:Dashboard $router->addRoute('manage//', [ 'module' => 'Admin', ]); ``` -Subdomenii .[#toc-subdomains] ------------------------------ +Subdomenii +---------- -Colecțiile de rute pot fi grupate pe subdomenii: +Colecțiile de rute le putem împărți după subdomenii: ```php $router = new RouteList; @@ -379,7 +401,7 @@ $router->withDomain('example.com') ->addRoute('/'); ``` -De asemenea, puteți utiliza [caractere sălbatice |#wildcards] în numele de domeniu: +În numele domeniului se pot folosi și [#substituenți]: ```php $router = new RouteList; @@ -388,23 +410,23 @@ $router->withDomain('example.%tld%') ``` -Prefix de cale .[#toc-path-prefix] ----------------------------------- +Prefix de cale +-------------- -Colecțiile de rute pot fi grupate în funcție de calea din URL: +Colecțiile de rute le putem împărți după calea din URL: ```php $router = new RouteList; $router->withPath('eshop') - ->addRoute('rss', 'Feed:rss') // se potrivește cu URL /eshop/rss - ->addRoute('/'); // se potrivește cu URL-ul /eshop// + ->addRoute('rss', 'Feed:rss') // prinde URL /eshop/rss + ->addRoute('/'); // prinde URL /eshop// ``` -Combinații .[#toc-combinations] -------------------------------- +Combinații +---------- -Utilizarea de mai sus poate fi combinată: +Împărțirile menționate mai sus pot fi combinate între ele: ```php $router = (new RouteList) @@ -424,40 +446,40 @@ $router = (new RouteList) ``` -Parametrii de interogare .[#toc-query-parameters] -------------------------------------------------- +Parametri query +--------------- -Măștile pot conține, de asemenea, parametri de interogare (parametri după semnul întrebării din URL). Acestea nu pot defini o expresie de validare, dar pot modifica numele sub care sunt transmise prezentatorului: +Măștile pot conține și parametri query (parametri după semnul întrebării în URL). Acestora nu li se poate defini o expresie de validare, dar li se poate schimba numele sub care sunt transmiși presenterului: ```php -// utilizarea parametrului de interogare "cat" ca "categoryId" în aplicație +// parametrul query 'cat' dorim să îl folosim în aplicație sub numele 'categoryId' $router->addRoute('product ? id= & cat=', /* ... */); ``` -Parametrii Foo .[#toc-foo-parameters] -------------------------------------- +Parametri Foo +------------- -Acum mergem mai departe. Parametrii Foo sunt, în principiu, parametri fără nume care permit să se potrivească cu o expresie regulată. Următoarea rută se potrivește cu `/index`, `/index.html`, `/index.htm` și `/index.php`: +Acum intrăm mai în profunzime. Parametrii Foo sunt în esență parametri nedenumiți care permit potrivirea unei expresii regulate. Un exemplu este o rută care acceptă `/index`, `/index.html`, `/index.htm` și `/index.php`: ```php $router->addRoute('index', /* ... */); ``` -De asemenea, este posibil să se definească în mod explicit un șir de caractere care va fi utilizat pentru generarea URL-urilor. Șirul trebuie să fie plasat direct după semnul întrebării. Următorul traseu este similar cu cel anterior, dar generează `/index.html` în loc de `/index` deoarece șirul `.html` este definit ca "valoare generată". +Se poate, de asemenea, defini explicit șirul care va fi utilizat la generarea URL-ului. Șirul trebuie plasat direct după semnul întrebării. Următoarea rută este similară cu cea anterioară, dar generează `/index.html` în loc de `/index`, deoarece șirul `.html` este setat ca valoare de generare: ```php $router->addRoute('index', /* ... */); ``` -Integrare .[#toc-integration] -============================= +Integrarea în aplicație +======================= -Pentru a conecta routerul nostru la aplicație, trebuie să informăm containerul DI despre acesta. Cel mai simplu este să pregătim fabrica care va construi obiectul router și să spunem configurației containerului să o folosească. Să spunem că scriem o metodă în acest scop `App\Router\RouterFactory::createRouter()`: +Pentru a integra routerul creat în aplicație, trebuie să îi spunem despre el containerului DI. Cea mai ușoară cale este să pregătim o fabrică care va produce obiectul routerului și să comunicăm în configurația containerului că trebuie să o folosească. Să presupunem că în acest scop scriem metoda `App\Core\RouterFactory::createRouter()`: ```php -namespace App\Router; +namespace App\Core; use Nette\Application\Routers\RouteList; @@ -472,14 +494,14 @@ class RouterFactory } ``` -Apoi scriem în [configurație |dependency-injection:services]: +În [configurație |dependency-injection:services] scriem apoi: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter ``` -Orice dependență, cum ar fi o conexiune la o bază de date etc., este transmisă metodei factory ca parametru al acesteia folosind [autowiring |dependency-injection:autowiring]: +Orice dependențe, de exemplu de baza de date etc., sunt transmise metodei fabricii ca parametri ai săi folosind [autowiring-ul|dependency-injection:autowiring]: ```php public static function createRouter(Nette\Database\Connection $db): RouteList @@ -489,25 +511,25 @@ public static function createRouter(Nette\Database\Connection $db): RouteList ``` -SimpleRouter .[#toc-simplerouter] -================================= +SimpleRouter +============ -Un router mult mai simplu decât Route Collection este [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Acesta poate fi utilizat atunci când nu este nevoie de un format URL specific, când `mod_rewrite` (sau alternative) nu este disponibil sau când pur și simplu nu vrem să ne deranjăm încă cu URL-uri ușor de utilizat. +Un router mult mai simplu decât colecția de rute este [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Îl folosim atunci când nu avem cerințe speciale privind forma URL-ului, dacă nu este disponibil `mod_rewrite` (sau alternativele sale) sau dacă deocamdată nu dorim să ne ocupăm de URL-uri frumoase. -Generează adrese în aproximativ această formă: +Generează adrese aproximativ în această formă: ``` http://example.com/?presenter=Product&action=detail&id=123 ``` -Parametrul constructorului `SimpleRouter` este un prezentator și o acțiune implicită, adică acțiunea care va fi executată dacă deschidem, de exemplu, `http://example.com/` fără parametri suplimentari. +Parametrul constructorului SimpleRouter este presenterul & acțiunea implicită către care trebuie direcționat dacă deschidem pagina fără parametri, de ex. `http://example.com/`. ```php -// implicit la prezentatorul "Home" și acțiunea "default +// presenterul implicit va fi 'Home' și acțiunea 'default' $router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` -Vă recomandăm să definiți SimpleRouter direct în [configurare |dependency-injection:services]: +Recomandăm definirea directă a SimpleRouter în [configurație |dependency-injection:services]: ```neon services: @@ -515,22 +537,22 @@ services: ``` -SEO și canonizare .[#toc-seo-and-canonization] -============================================== +SEO și canonizare +================= -Cadrul crește SEO (optimizarea pentru motoarele de căutare) prin prevenirea duplicării conținutului la diferite URL-uri. În cazul în care mai multe adrese au legătură cu aceeași destinație, de exemplu `/index` și `/index.html`, cadrul o determină pe prima ca fiind principală (canonică) și le redirecționează pe celelalte către aceasta folosind codul HTTP 301. Datorită acestui lucru, motoarele de căutare nu vor indexa paginile de două ori și nu le strică page rank-ul. . +Framework-ul contribuie la SEO (optimizarea găsirii pe internet) prin prevenirea duplicării conținutului la URL-uri diferite. Dacă către o anumită țintă duc mai multe adrese, de ex. `/index` și `/index.html`, framework-ul o determină pe prima dintre ele ca fiind primară (canonică) și le redirecționează pe celelalte către ea folosind codul HTTP 301. Datorită acestui fapt, motoarele de căutare nu vă indexează paginile de două ori și nu le diluează page rank-ul. -Acest proces se numește canonizare. URL-ul canonic este cel generat de router, adică de prima rută corespunzătoare din [colecția |#route-collection] fără indicatorul OneWay. Prin urmare, în colecție, enumerăm mai întâi **rutele primare**. +Acest proces se numește canonizare. URL-ul canonic este cel generat de router, adică prima rută corespunzătoare din colecție fără flag-ul OneWay. De aceea, în colecție specificăm **rutele primare primele**. -Canonizarea este realizată de către prezentator, mai multe în capitolul [canonizare |presenters#Canonization]. +Canonizarea este efectuată de presenter, mai multe în capitolul [canonizare |presenters#Canonizare]. -HTTPS .[#toc-https] -=================== +HTTPS +===== -Pentru a utiliza protocolul HTTPS, este necesar să îl activați la găzduire și să configurați serverul. +Pentru a putea utiliza protocolul HTTPS, este necesar să îl activați pe hosting și să configurați corect serverul. -Redirecționarea întregului site către HTTPS trebuie efectuată la nivelul serverului, de exemplu, folosind fișierul .htaccess din directorul rădăcină al aplicației noastre, cu codul HTTP 301. Setările pot fi diferite în funcție de găzduire și arată cam așa: +Redirecționarea întregului site către HTTPS trebuie setată la nivelul serverului, de exemplu folosind fișierul .htaccess în directorul rădăcină al aplicației noastre, și anume cu codul HTTP 301. Setarea poate varia în funcție de hosting și arată aproximativ așa: ``` @@ -542,40 +564,40 @@ Redirecționarea întregului site către HTTPS trebuie efectuată la nivelul ser ``` -Routerul generează un URL cu același protocol cu cel cu care a fost încărcată pagina, deci nu este nevoie să setați nimic altceva. +Routerul generează URL-uri cu același protocol cu care a fost încărcată pagina, deci nu este nevoie să setați nimic în plus. -Cu toate acestea, dacă avem nevoie în mod excepțional de rute diferite pentru a rula sub protocoale diferite, vom pune acest lucru în masca de rută: +Dacă însă, în mod excepțional, avem nevoie ca diferite rute să ruleze sub protocoale diferite, îl specificăm în masca rutei: ```php -// Va genera o adresă HTTP +// Va genera adresa cu HTTP $router->addRoute('http://%host%//', /* ... */); -// Va genera o adresă HTTPS +// Va genera adresa cu HTTPs $router->addRoute('https://%host%//', /* ... */); ``` -Debugging Router .[#toc-debugging-router] -========================================= +Depanarea routerului +==================== -Bara de rutare afișată în [Tracy Bar |tracy:] este un instrument util care afișează o listă de rute și, de asemenea, parametrii pe care routerul i-a obținut din URL. +Panoul de rutare afișat în [Tracy Bar |tracy:] este un ajutor util, care afișează lista rutelor și, de asemenea, parametrii pe care routerul i-a obținut din URL. -Bara verde cu simbolul ✓ reprezintă ruta care a corespuns URL-ului curent, iar barele albastre cu simbolurile ≈ indică rutele care ar corespunde, de asemenea, URL-ului, dacă verdele nu le-ar depăși. Vedem prezentatorul curent & acțiunea în continuare. +Bara verde cu simbolul ✓ reprezintă ruta care a procesat URL-ul curent, cu albastru și simbolul ≈ sunt marcate rutele care ar procesa și ele URL-ul, dacă cea verde nu le-ar fi devansat. În continuare vedem presenterul & acțiunea curentă. [* routing-debugger.webp *] -În același timp, dacă există o redirecționare neașteptată din cauza [canonicalizării |#SEO and Canonization], este util să ne uităm în bara *redirect* pentru a vedea cum a înțeles inițial routerul URL-ul și de ce a redirecționat. +În același timp, dacă are loc o redirecționare neașteptată din cauza [canonizării |#SEO și canonizare], este util să vă uitați în panoul din bara *redirect*, unde veți afla cum a înțeles routerul inițial URL-ul și de ce a redirecționat. .[note] -Atunci când depanați routerul, se recomandă să deschideți Instrumente pentru dezvoltatori în browser (Ctrl+Shift+I sau Cmd+Option+I) și să dezactivați memoria cache din panoul Rețea, astfel încât redirecționările să nu fie stocate în ea. +La depanarea routerului, recomandăm deschiderea în browser a Developer Tools (Ctrl+Shift+I sau Cmd+Option+I) și în panoul Network dezactivarea cache-ului, pentru a nu se salva în el redirecționările. -Performanță .[#toc-performance] -=============================== +Performanță +=========== -Numărul de rute afectează viteza routerului. Cu siguranță, numărul acestora nu trebuie să depășească câteva zeci. Dacă site-ul dumneavoastră are o structură URL prea complicată, puteți scrie un [router personalizat |#custom router]. +Numărul de rute influențează viteza routerului. Numărul lor nu ar trebui să depășească în niciun caz câteva zeci. Dacă site-ul dvs. are o structură URL prea complicată, puteți scrie un [#router personalizat]. -Dacă routerul nu are dependențe, cum ar fi de o bază de date, iar fabrica sa nu are argumente, putem serializa forma sa compilată direct într-un container DI și, astfel, să facem aplicația puțin mai rapidă. +Dacă routerul nu are dependențe, de exemplu de baza de date, și fabrica sa nu acceptă niciun argument, putem serializa forma sa compilată direct în containerul DI și astfel accelera ușor aplicația. ```neon routing: @@ -583,10 +605,10 @@ routing: ``` -Router personalizat .[#toc-custom-router] -========================================= +Router personalizat +=================== -Următoarele linii sunt destinate utilizatorilor foarte avansați. Vă puteți crea propriul router și îl puteți adăuga în mod natural în colecția de rute. Routerul este o implementare a interfeței [api:Nette\Routing\Router] cu două metode: +Următoarele rânduri sunt destinate utilizatorilor foarte avansați. Puteți crea un router propriu și să îl integrați complet natural în colecția de rute. Routerul este o implementare a interfeței [api:Nette\Routing\Router] cu două metode: ```php use Nette\Http\IRequest as HttpRequest; @@ -606,8 +628,7 @@ class MyRouter implements Nette\Routing\Router } ``` -Metoda `match` procesează [$httpRequest |http:request] curent, din care pot fi preluate nu numai URL-ul, ci și antetele etc., într-un array care conține numele prezentatorului și parametrii acestuia. În cazul în care nu poate procesa cererea, se returnează null. -Atunci când procesăm cererea, trebuie să returnăm cel puțin prezentatorul și acțiunea. Numele prezentatorului este complet și include toate modulele: +Metoda `match` procesează cererea curentă [$httpRequest |http:request], din care se poate obține nu numai URL-ul, ci și antetele etc., într-un array care conține numele presenterului și parametrii săi. Dacă nu poate procesa cererea, returnează null. La procesarea cererii, trebuie să returnăm cel puțin presenterul și acțiunea. Numele presenterului este complet și conține și eventualele module: ```php [ @@ -616,9 +637,9 @@ Atunci când procesăm cererea, trebuie să returnăm cel puțin prezentatorul ] ``` -Metoda `constructUrl`, pe de altă parte, generează un URL absolut din matricea de parametri. Aceasta poate utiliza informațiile din parametrul `$refUrl`, care este URL-ul curent. +Metoda `constructUrl`, dimpotrivă, asamblează din array-ul de parametri URL-ul absolut rezultat. Pentru aceasta poate utiliza informații din parametrul [`$refUrl`|api:Nette\Http\UrlScript], care este URL-ul curent. -Pentru a adăuga un router personalizat la colecția de rute, utilizați `add()`: +În colecția de rute îl adăugați folosind `add()`: ```php $router = new Nette\Application\Routers\RouteList; @@ -628,19 +649,19 @@ $router->addRoute(/* ... */); ``` -Utilizare separată .[#toc-separated-usage] -========================================== +Utilizare independentă +====================== -Prin utilizare separată se înțelege utilizarea capacităților routerului într-o aplicație care nu utilizează aplicația Nette și prezentatorii. Aproape tot ceea ce am arătat în acest capitol i se aplică, cu următoarele diferențe: +Prin utilizare independentă înțelegem utilizarea capacităților routerului într-o aplicație care nu utilizează Nette Application și presentere. Se aplică aproape tot ce am arătat în acest capitol, cu aceste diferențe: -- pentru colecțiile de rute folosim clasa [api:Nette\Routing\RouteList] -- ca o clasă simplă de router [api:Nette\Routing\SimpleRouter] -- deoarece nu există o pereche `Presenter:action`, folosim [notația Advanced |#Advanced notation] +- pentru colecții de rute folosim clasa [api:Nette\Routing\RouteList] +- ca simple router clasa [api:Nette\Routing\SimpleRouter] +- deoarece nu există perechea `Presenter:action`, folosim [notația extinsă |#Notație extinsă] -Deci, din nou, vom crea o metodă care va construi un router, de exemplu: +Deci, din nou, creăm o metodă care ne va asambla routerul, de ex.: ```php -namespace App\Router; +namespace App\Core; use Nette\Routing\RouteList; @@ -661,35 +682,35 @@ class RouterFactory } ``` -Dacă folosiți un container DI, ceea ce vă recomandăm, adăugați din nou metoda la configurație și apoi obțineți routerul împreună cu cererea HTTP din container: +Dacă utilizați un container DI, ceea ce recomandăm, din nou adăugăm metoda în configurație și apoi obținem routerul împreună cu cererea HTTP din container: ```php $router = $container->getByType(Nette\Routing\Router::class); $httpRequest = $container->getByType(Nette\Http\IRequest::class); ``` -Sau vom crea obiecte direct: +Sau creăm obiectele direct: ```php -$router = App\Router\RouterFactory::createRouter(); +$router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` -Acum trebuie să lăsăm routerul să funcționeze: +Acum rămâne doar să punem routerul la treabă: ```php $params = $router->match($httpRequest); if ($params === null) { - // nu s-a găsit nicio rută corespunzătoare, vom trimite o eroare 404 + // nu a fost găsită o rută corespunzătoare, trimitem eroarea 404 exit; } -// procesăm parametrii primiți +// procesăm parametrii obținuți $controller = $params['controller']; // ... ``` -Și viceversa, vom folosi routerul pentru a crea legătura: +Și invers, folosim routerul pentru a asambla un link: ```php $params = ['controller' => 'ArticleController', 'id' => 123]; diff --git a/application/ro/templates.texy b/application/ro/templates.texy index 4e058192af..206ecd3fa6 100644 --- a/application/ro/templates.texy +++ b/application/ro/templates.texy @@ -2,9 +2,9 @@ ******** .[perex] -Nette utilizează sistemul de șabloane [Latte |latte:]. Latte este utilizat deoarece este cel mai sigur sistem de șabloane pentru PHP și, în același timp, cel mai intuitiv sistem. Nu trebuie să învățați prea multe lucruri noi, trebuie doar să cunoașteți PHP și câteva etichete Latte. +Nette utilizează sistemul de șabloane [Latte |latte:]. Pe de o parte, pentru că este cel mai sigur sistem de șabloane pentru PHP și, pe de altă parte, și cel mai intuitiv sistem. Nu trebuie să învățați multe lucruri noi, vă descurcați cu cunoștințele de PHP și câteva tag-uri. -Este obișnuit ca pagina să fie completată din șablonul layout + șablonul de acțiune. Iată cum ar putea arăta un șablon layout, observați blocurile `{block}` și tag-ul `{include}`: +Este obișnuit ca pagina să fie compusă dintr-un șablon de layout + șablonul acțiunii respective. Așa poate arăta, de exemplu, un șablon de layout, observați blocurile `{block}` și tag-ul `{include}`: ```latte @@ -20,7 +20,7 @@ Este obișnuit ca pagina să fie completată din șablonul layout + șablonul de ``` -Iar acesta ar putea fi șablonul de acțiune: +Și acesta va fi șablonul acțiunii: ```latte {block title}Homepage{/block} @@ -31,50 +31,98 @@ Iar acesta ar putea fi șablonul de acțiune: {/block} ``` -Acesta definește blocul `content`, care este inserat în locul lui `{include content}` în machetă și, de asemenea, redefinește blocul `title`, care suprascrie `{block title}` în machetă. Încercați să vă imaginați rezultatul. +Acesta definește blocul `content`, care se va insera în locul `{include content}` din layout, și, de asemenea, re-definește blocul `title`, care va suprascrie `{block title}` din layout. Încercați să vă imaginați rezultatul. -Căutați șabloane .[#toc-search-for-templates] ---------------------------------------------- +Căutarea șabloanelor +-------------------- -Calea către șabloane este dedusă conform unei logici simple. Se încearcă să se vadă dacă unul dintre aceste fișiere șablon există în raport cu directorul în care se află clasa presenter, unde `` este numele prezentatorului curent și `` este numele acțiunii curente: +Nu trebuie să specificați în presentere ce șablon trebuie redat, framework-ul deduce singur calea și vă scutește de scris. -- `templates//.latte` -- `templates/..latte` +Dacă utilizați o structură de directoare unde fiecare presenter are propriul director, plasați pur și simplu șablonul în acest director sub numele acțiunii (resp. view), adică pentru acțiunea `default` utilizați șablonul `default.latte`: -În cazul în care nu găsește șablonul, răspunsul este [eroarea 404 |presenters#Error 404 etc.]. +/--pre +app/ +└── Presentation/ + └── Home/ + ├── HomePresenter.php + └── default.latte +\-- -De asemenea, puteți schimba vizualizarea folosind `$this->setView('otherView')`. Sau, în loc să căutați, specificați direct numele fișierului șablon folosind `$this->template->setFile('/path/to/template.latte')`. +Dacă utilizați o structură unde presenterele sunt împreună într-un singur director și șabloanele în folderul `templates`, salvați-l fie în fișierul `..latte`, fie `/.latte`: + +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── Home.default.latte ← prima variantă + └── Home/ + └── default.latte ← a doua variantă +\-- + +Directorul `templates` poate fi plasat și cu un nivel mai sus, adică la același nivel cu directorul cu clasele presenterelor. + +Dacă șablonul nu este găsit, presenterul răspunde cu [eroarea 404 - page not found |presenters#Eroare 404 etc]. + +View-ul îl schimbați folosind `$this->setView('jineView')`. De asemenea, se poate specifica direct fișierul cu șablonul folosind `$this->template->setFile('/path/to/template.latte')`. .[note] -Puteți modifica căile în care sunt căutate șabloanele prin suprascrierea metodei [formatTemplateFiles |api:Nette\Application\UI\Presenter::formatTemplateFiles()], care returnează o matrice de posibile căi de acces la fișiere. +Fișierele unde se caută șabloanele pot fi modificate prin suprascrierea metodei [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], care returnează un array de nume posibile de fișiere. + + +Căutarea șablonului de layout +----------------------------- + +Nette caută automat și fișierul cu layout-ul. + +Dacă utilizați o structură de directoare unde fiecare presenter are propriul director, plasați layout-ul fie în folderul cu presenterul, dacă este specific doar pentru el, fie cu un nivel mai sus, dacă este comun pentru mai mulți presenteri: + +/--pre +app/ +└── Presentation/ + ├── @layout.latte ← layout comun + └── Home/ + ├── @layout.latte ← doar pentru presenterul Home + ├── HomePresenter.php + └── default.latte +\-- + +Dacă utilizați o structură unde presenterele sunt împreună într-un singur director și șabloanele în folderul `templates`, layout-ul va fi așteptat în aceste locații: -Prezentarea este așteptată în următoarele fișiere: +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── @layout.latte ← layout comun + ├── Home.@layout.latte ← doar pentru Home, prima variantă + └── Home/ + └── @layout.latte ← doar pentru Home, a doua variantă +\-- -- `templates//@.latte` -- `templates/.@.latte` -- `templates/@.latte` aspect comun pentru mai mulți prezentatori +Dacă presenterul se află într-un modul, se va căuta și la niveluri de directoare superioare, în funcție de imbricarea modulului. -`` este numele prezentatorului curent și `` este numele structurii, care este în mod implicit `'layout'`. Numele poate fi schimbat cu `$this->setLayout('otherLayout')`, astfel încât să se încerce să se utilizeze fișierele `@otherLayout.latte`. +Numele layout-ului poate fi schimbat folosind `$this->setLayout('layoutAdmin')` și atunci se va aștepta în fișierul `@layoutAdmin.latte`. De asemenea, se poate specifica direct fișierul cu șablonul layout-ului folosind `$this->setLayout('/path/to/template.latte')`. -De asemenea, puteți specifica direct numele de fișier al șablonului de machetă cu `$this->setLayout('/path/to/template.latte')`. Utilizarea `$this->setLayout(false)` va dezactiva căutarea de machete. +Folosind `$this->setLayout(false)` sau tag-ul `{layout none}` în interiorul șablonului, căutarea layout-ului se dezactivează. .[note] -Puteți modifica căile de căutare a șabloanelor prin suprascrierea metodei [formatLayoutTemplateFiles |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], care returnează o matrice de posibile căi de acces la fișiere. +Fișierele unde se caută șabloanele de layout pot fi modificate prin suprascrierea metodei [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], care returnează un array de nume posibile de fișiere. -Variabilele din șablon .[#toc-variables-in-the-template] --------------------------------------------------------- +Variabile în șablon +------------------- -Variabilele sunt transmise șablonului prin scrierea lor pe `$this->template` și apoi sunt disponibile în șablon ca variabile locale: +Variabilele le transmitem șablonului scriindu-le în `$this->template` și apoi le avem disponibile în șablon ca variabile locale: ```php $this->template->article = $this->articles->getById($id); ``` -În acest fel, putem trece cu ușurință orice variabile în șabloane. Cu toate acestea, atunci când dezvoltăm aplicații robuste, este adesea mai util să ne limităm. De exemplu, prin definirea explicită a unei liste de variabile pe care șablonul le așteaptă și a tipurilor acestora. Acest lucru va permite PHP să verifice tipul, IDE-ului să se autocompleteze corect, iar analizei statice să detecteze erorile. +Astfel de simplu putem transmite șabloanelor orice variabile. Însă, la dezvoltarea aplicațiilor robuste, este mai util să ne limităm. De exemplu, prin definirea explicită a listei de variabile pe care șablonul le așteaptă și a tipurilor lor. Datorită acestui fapt, PHP ne va putea verifica tipurile, IDE-ul ne va sugera corect și analiza statică va dezvălui erorile. -Și cum definim o astfel de enumerare? Pur și simplu sub forma unei clase și a proprietăților sale. O denumim în mod similar cu presenter, dar cu `Template` la sfârșit: +Și cum definim o astfel de listă? Simplu, sub forma unei clase și a proprietăților sale. O numim similar cu presenterul, doar cu `Template` la sfârșit: ```php /** @@ -93,22 +141,22 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template } ``` -Obiectul `$this->template` din prezentator va fi acum o instanță a clasei `ArticleTemplate`. Astfel, PHP va verifica tipurile declarate atunci când acestea sunt scrise. Începând cu PHP 8.2, PHP va avertiza, de asemenea, în cazul în care se scrie pe o variabilă inexistentă; în versiunile anterioare, același lucru poate fi realizat cu ajutorul trăsăturii [Nette\SmartObject |utils:smartobject]. +Obiectul `$this->template` din presenter va fi acum o instanță a clasei `ArticleTemplate`. Deci, PHP va verifica tipurile declarate la scriere. Și începând cu versiunea PHP 8.2 va avertiza și la scrierea într-o variabilă inexistentă, în versiunile anterioare se poate obține același lucru folosind trait-ul [Nette\SmartObject |utils:smartobject]. -Adnotarea `@property-read` este pentru IDE și analiza statică, va face ca autocompletarea să funcționeze, a se vedea "PhpStorm și finalizarea codului pentru $this->template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. +Adnotarea `@property-read` este destinată IDE-ului și analizei statice, datorită ei va funcționa sugerarea, vezi "PhpStorm and code completion for $this⁠-⁠>⁠template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. [* phpstorm-completion.webp *] -Vă puteți permite și luxul de a șopti în șabloane, trebuie doar să instalați pluginul în PhpStorm și să specificați numele clasei la începutul șablonului, consultați articolul "Latte: cum se tastează sistemul":https://blog.nette.org/ro/latte-cum-se-utilizeaza-sistemul-de-tipuri: +De luxul sugerării vă puteți bucura și în șabloane, este suficient să instalați în PhpStorm plugin-ul pentru Latte și să specificați la începutul șablonului numele clasei, mai multe în articolul "Latte: cum să folosiți sistemul de tipuri":https://blog.nette.org/ro/latte-how-to-use-type-system: ```latte -{templateType App\Presenters\ArticleTemplate} +{templateType App\Presentation\Article\ArticleTemplate} ... ``` -Acesta este, de asemenea, modul în care funcționează șabloanele în componente, trebuie doar să urmați convenția de denumire și să creați o clasă șablon `FifteenTemplate` pentru componentă, de exemplu `FifteenControl`. +Astfel funcționează și șabloanele în componente, este suficient doar să respectați convenția de nume și pentru componenta, de ex. `FifteenControl`, să creați clasa șablonului `FifteenTemplate`. -Dacă aveți nevoie să creați un `$template` ca instanță a unei alte clase, utilizați metoda `createTemplate()`: +Dacă aveți nevoie să creați `$template` ca instanță a altei clase, utilizați metoda `createTemplate()`: ```php public function renderDefault(): void @@ -121,43 +169,43 @@ public function renderDefault(): void ``` -Variabile implicite .[#toc-default-variables] ---------------------------------------------- +Variabile implicite +------------------- -Prezentatorii și componentele transmit automat mai multe variabile utile către șabloane: +Presenterele și componentele transmit automat către șabloane câteva variabile utile: -- `$basePath` este o cale URL absolută către directorul rădăcină (de exemplu, `/CD-collection`) -- `$baseUrl` este o adresă URL absolută către directorul rădăcină (de exemplu `http://localhost/CD-collection`) -- `$user` este un obiect [care reprezintă utilizatorul |security:authentication] -- `$presenter` este prezentatorul curent -- `$control` este componenta sau prezentatorul curent -- `$flashes` lista [mesajelor |presenters#flash-messages] trimise prin metoda `flashMessage()` +- `$basePath` este calea URL absolută către directorul rădăcină (de ex. `/eshop`) +- `$baseUrl` este URL-ul absolut către directorul rădăcină (de ex. `http://localhost/eshop`) +- `$user` este obiectul [reprezentând utilizatorul |security:authentication] +- `$presenter` este presenterul curent +- `$control` este componenta sau presenterul curent +- `$flashes` array de [mesaje |presenters#Mesaje flash] trimise de funcția `flashMessage()` -Dacă utilizați o clasă de șablon personalizată, aceste variabile sunt transmise dacă creați o proprietate pentru ele. +Dacă utilizați propria clasă de șablon, aceste variabile se transmit dacă creați proprietăți pentru ele. -Crearea legăturilor .[#toc-creating-links] ------------------------------------------- +Crearea linkurilor +------------------ -În șablon, creăm legături către alți prezentatori și acțiuni după cum urmează: +În șablon se creează linkuri către alți presenteri & acțiuni în acest mod: ```latte -detail +detaliu produs ``` -Atributul `n:href` este foarte util pentru etichetele HTML ``. Dacă dorim să imprimăm link-ul în altă parte, de exemplu în text, folosim `{link}`: +Atributul `n:href` este foarte util pentru tag-urile HTML ``. Dacă dorim să afișăm linkul în altă parte, de exemplu în text, folosim `{link}`: ```latte -URL is: {link Home:default} +Adresa este: {link Home:default} ``` -Pentru mai multe informații, consultați [Crearea de linkuri |Creating Links]. +Mai multe informații găsiți în capitolul [Crearea linkurilor URL|creating-links]. -Filtre personalizate, etichete etc. .[#toc-custom-filters-tags-etc] -------------------------------------------------------------------- +Filtre personalizate, tag-uri etc. +---------------------------------- -Sistemul de modelare Latte poate fi extins cu filtre, funcții, etichete etc. personalizate. Acest lucru se poate face direct în `render` sau `beforeRender()`: +Sistemul de șabloane Latte poate fi extins cu filtre, funcții, tag-uri etc. personalizate. Acest lucru se poate face direct în metoda `render` sau `beforeRender()`: ```php public function beforeRender(): void @@ -165,16 +213,16 @@ public function beforeRender(): void // adăugarea unui filtru $this->template->addFilter('foo', /* ... */); - // sau configurați direct obiectul Latte\Engine + // sau configurăm direct obiectul Latte\Engine $latte = $this->template->getLatte(); $latte->addFilterLoader(/* ... */); } ``` -Latte versiunea 3 oferă o metodă mai avansată prin crearea unei [extensii |latte:creating-extension] pentru fiecare proiect web. Iată un exemplu aproximativ al unei astfel de clase: +Latte în versiunea 3 oferă o modalitate mai avansată și anume crearea unei [extensii |latte:extending-latte#Latte Extension] pentru fiecare proiect web. Un exemplu fragmentar al unei astfel de clase: ```php -namespace App\Templating; +namespace App\Presentation\Accessory; final class LatteExtension extends Latte\Extension { @@ -207,22 +255,21 @@ final class LatteExtension extends Latte\Extension } ``` -O înregistrăm folosind [configuration#Latte]: +O înregistrăm folosind [configurația |configuration#Șabloane Latte]: ```neon latte: extensions: - - App\Templating\LatteExtension + - App\Presentation\Accessory\LatteExtension ``` -Traducerea .[#toc-translating] ------------------------------- +Traducere +--------- -Dacă programați o aplicație multilingvă, probabil că va trebui să scoateți o parte din textul din șablon în diferite limbi. Pentru a face acest lucru, Nette Framework definește o interfață de traducere [api:Nette\Localization\Translator], care are o singură metodă `translate()`. Aceasta acceptă mesajul `$message`, care este de obicei un șir de caractere, și orice alți parametri. Sarcina este de a returna șirul tradus. -Nu există o implementare implicită în Nette, puteți alege în funcție de nevoile dumneavoastră din mai multe soluții gata făcute care pot fi găsite pe [Componette |https://componette.org/search/localization]. Documentația acestora vă spune cum să configurați traducătorul. +Dacă programați o aplicație multilingvă, probabil veți avea nevoie să afișați unele texte din șablon în diferite limbi. Nette Framework definește în acest scop o interfață pentru traducere [api:Nette\Localization\Translator], care are o singură metodă `translate()`. Aceasta primește mesajul `$message`, care de obicei este un șir de caractere, și orice alți parametri. Sarcina este de a returna șirul tradus. În Nette nu există nicio implementare implicită, puteți alege în funcție de nevoile dvs. dintre mai multe soluții gata făcute, pe care le găsiți pe [Componette |https://componette.org/search/localization]. În documentația lor veți afla cum să configurați translatorul. -Șabloanele pot fi configurate cu un traducător, pe care ni-l [vom trece |dependency-injection:passing-dependencies], folosind metoda `setTranslator()`: +Șabloanelor li se poate seta un traducător, pe care îl [primim transmis |dependency-injection:passing-dependencies], prin metoda `setTranslator()`: ```php protected function beforeRender(): void @@ -232,38 +279,38 @@ protected function beforeRender(): void } ``` -Alternativ, traducătorul poate fi setat cu ajutorul [configurației |configuration#Latte]: +Translatorul poate fi setat alternativ folosind [configurația |configuration#Șabloane Latte]: ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -Traducătorul poate fi apoi utilizat, de exemplu, ca filtru `|translate`, cu parametri suplimentari trecuți la metoda `translate()` (a se vedea `foo, bar`): +Apoi, traducătorul poate fi utilizat, de exemplu, ca filtru `|translate`, inclusiv cu parametri suplimentari, care sunt transmiși metodei `translate()` (vezi `foo, bar`): ```latte -{='Basket'|translate} +{='Coș'|translate} {$item|translate} {$item|translate, foo, bar} ``` -Sau ca o etichetă de subliniere: +Sau ca tag cu underscore: ```latte -{_'Basket'} +{_'Coș'} {_$item} {_$item, foo, bar} ``` -Pentru traducerea secțiunii de șablon, există o etichetă pereche `{translate}` (de la Latte 2.11, anterior se folosea eticheta `{_}` ): +Pentru traducerea unei secțiuni a șablonului există un tag pereche `{translate}` (de la Latte 2.11, anterior se folosea tag-ul `{_}`): ```latte -{translate}Order{/translate} -{translate foo, bar}Order{/translate} +{translate}Comandă{/translate} +{translate foo, bar}Comandă{/translate} ``` -Traducătorul este apelat în mod implicit în timpul execuției la redarea șablonului. Cu toate acestea, versiunea 3 a Latte poate traduce tot textul static în timpul compilării șablonului. Acest lucru economisește performanță, deoarece fiecare șir de caractere este tradus o singură dată, iar traducerea rezultată este scrisă în formularul compilat. Astfel, se creează mai multe versiuni compilate ale șablonului în directorul cache, una pentru fiecare limbă. Pentru a face acest lucru, trebuie doar să specificați limba ca al doilea parametru: +Translatorul este apelat standard în timpul rulării la redarea șablonului. Latte versiunea 3, însă, poate traduce toate textele statice deja în timpul compilării șablonului. Astfel se economisește performanță, deoarece fiecare șir se traduce o singură dată și traducerea rezultată se scrie în forma compilată. În directorul cu cache se creează astfel mai multe versiuni compilate ale șablonului, una pentru fiecare limbă. Pentru aceasta este suficient doar să specificați limba ca al doilea parametru: ```php protected function beforeRender(): void @@ -273,4 +320,4 @@ protected function beforeRender(): void } ``` -Prin text static înțelegem, de exemplu, `{_'hello'}` sau `{translate}hello{/translate}`. Textul nestatic, cum ar fi `{_$foo}`, va continua să fie compilat din mers. +Prin text static se înțelege, de exemplu, `{_'hello'}` sau `{translate}hello{/translate}`. Textele nestatice, cum ar fi `{_$foo}`, se vor traduce în continuare în timpul rulării. diff --git a/application/ru/@home.texy b/application/ru/@home.texy index 6ba4943c53..eee8a946c1 100644 --- a/application/ru/@home.texy +++ b/application/ru/@home.texy @@ -1,36 +1,85 @@ -Приложение Nette -**************** +Nette Application +***************** .[perex] -Пакет `nette/application` является основой для создания интерактивных веб-приложений. - -- [Как работают приложения? |how-it-works] -- [Bootstrap] -- [Презентеры |presenters] -- [Шаблоны |templates] -- [Модули |modules] -- [Маршрутизация |routing] -- [Создание URL-ссылок |creating-links] -- [Интерактивные компоненты |components] -- [AJAX и сниппеты |ajax] -- [Multiplier] -- [Конфигурация |configuration] +Nette Application является ядром фреймворка Nette, предоставляя мощные инструменты для создания современных веб-приложений. Оно предлагает ряд исключительных возможностей, которые значительно упрощают разработку и повышают безопасность и поддерживаемость кода. Установка --------- -Загрузите и установите пакет с помощью [Composer|best-practices:composer]: +Скачать и установить библиотеку можно с помощью [Composer|best-practices:composer]: ```shell composer require nette/application ``` -| версия пакета | совместимая версия PHP -|-----------------------|----------------------- -| Nette Application 4.0 | PHP 8.0 – 8.1 -| Nette Application 3.1 | PHP 7.2 – 8.1 + +Почему стоит выбрать Nette Application? +--------------------------------------- + +Nette всегда был пионером в области веб-технологий. + +**Двунаправленный маршрутизатор:** Nette обладает продвинутой системой маршрутизации, уникальной своей двунаправленностью — она не только преобразует URL в действия (actions) приложения, но и способна генерировать URL-адреса в обратную сторону. Это означает, что: +- Вы можете в любое время изменить структуру URL всего приложения без необходимости редактировать шаблоны +- URL автоматически канонизируются, что улучшает SEO +- Маршрутизация определяется в одном месте, а не разбросана по аннотациям + +**Компоненты и сигналы:** Встроенная компонентная система, вдохновленная Delphi и React.js, является совершенно уникальной среди PHP-фреймворков: +- Позволяет создавать повторно используемые элементы UI +- Поддерживает иерархическую композицию компонентов +- Предлагает элегантную обработку AJAX-запросов с помощью сигналов +- Богатая библиотека готовых компонентов на [Componette](https://componette.org) + +**AJAX и сниппеты:** Nette представил революционный способ работы с AJAX еще в 2009 году, задолго до появления аналогичных решений, таких как Hotwire для Ruby on Rails или Symfony UX Turbo: +- Сниппеты позволяют обновлять только части страницы без необходимости писать JavaScript +- Автоматическая интеграция с компонентной системой +- Умная инвалидация частей страниц +- Минимальное количество передаваемых данных + +**Интуитивные шаблоны [Latte|latte:]:** Самая безопасная система шаблонов для PHP с расширенными функциями: +- Автоматическая защита от XSS с контекстно-зависимым экранированием +- Расширяемость с помощью пользовательских фильтров, функций и тегов +- Наследование шаблонов и сниппеты для AJAX +- Отличная поддержка PHP 8.x с системой типов + +**Dependency Injection:** Nette полностью использует Dependency Injection: +- Автоматическая передача зависимостей (autowiring) +- Конфигурация с помощью понятного формата NEON +- Поддержка фабрик компонентов + + +Основные преимущества +--------------------- + +- **Безопасность**: Автоматическая защита от [уязвимостей|nette:vulnerability-protection], таких как XSS, CSRF и т. д. +- **Продуктивность**: Меньше кода, больше функций благодаря умному дизайну +- **Отладка**: [отладчик Tracy|tracy:] с панелью маршрутизации +- **Производительность**: Умный кеш, ленивая загрузка компонентов +- **Гибкость**: Легкое изменение URL даже после завершения приложения +- **Компоненты**: Уникальная система повторно используемых элементов UI +- **Современность**: Полная поддержка PHP 8.4+ и системы типов + + +Начало работы +------------- + +1. [Как работают приложения? |how-it-works] - Понимание базовой архитектуры +2. [Презентеры |presenters] - Работа с презентерами и действиями +3. [Шаблоны |templates] - Создание шаблонов в Latte +4. [Маршрутизация |routing] - Конфигурация URL-адресов +5. [Интерактивные компоненты |components] - Использование компонентной системы + + +Совместимость с PHP +------------------- + +| версия | совместим с PHP +|-----------|------------------- +| Nette Application 4.0 | PHP 8.1 – 8.4 +| Nette Application 3.2 | PHP 8.1 – 8.4 +| Nette Application 3.1 | PHP 7.2 – 8.3 | Nette Application 3.0 | PHP 7.1 – 8.0 | Nette Application 2.4 | PHP 5.6 – 8.0 -Применяется к последним версиям патчей. +Действительно для последних патч-версий. diff --git a/application/ru/@left-menu.texy b/application/ru/@left-menu.texy index d00f4e17e2..bcf2cfd383 100644 --- a/application/ru/@left-menu.texy +++ b/application/ru/@left-menu.texy @@ -1,19 +1,22 @@ -Приложение Nette -**************** +Nette Application +***************** - [Как работают приложения? |how-it-works] -- [Bootstrap] +- [Bootstrapping] - [Презентеры |presenters] - [Шаблоны |templates] -- [Модули |modules] +- [Структура каталогов |directory-structure] - [Маршрутизация |routing] - [Создание URL-ссылок |creating-links] - [Интерактивные компоненты |components] - [AJAX и сниппеты |ajax] -- [Multiplier] +- [Multiplier |multiplier] - [Конфигурация |configuration] -Дальнейшее чтение -***************** -- [Лучшие практики |best-practices:] +Дополнительное чтение +********************* +- [Зачем использовать Nette? |www:10-reasons-why-nette] +- [Установка |nette:installation] +- [Пишем первое приложение! |quickstart:] +- [Руководства и лучшие практики |best-practices:] - [Устранение неполадок |nette:troubleshooting] diff --git a/application/ru/@meta.texy b/application/ru/@meta.texy new file mode 100644 index 0000000000..7f329adfce --- /dev/null +++ b/application/ru/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документация Nette}} diff --git a/application/ru/ajax.texy b/application/ru/ajax.texy index 05cb303614..a377e3d954 100644 --- a/application/ru/ajax.texy +++ b/application/ru/ajax.texy @@ -3,36 +3,43 @@ AJAX и сниппеты
    -Современные веб-приложения сегодня работают наполовину на сервере, а наполовину в браузере. AJAX является жизненно важным объединяющим фактором. Какую поддержку предлагает фреймворк Nette? -- отправка фрагментов шаблонов (так называемых *сниппетов*) +В эпоху современных веб-приложений, где функциональность часто распределяется между сервером и браузером, AJAX является необходимым связующим элементом. Какие возможности предлагает нам Nette Framework в этой области? +- отправка частей шаблона, так называемых сниппетов - передача переменных между PHP и JavaScript -- Отладка приложений AJAX +- инструменты для отладки AJAX-запросов
    -AJAX-запрос может быть обнаружен с помощью метода сервиса [инкапсуляция HTTP-запроса |http:request] `$httpRequest->isAjax()` (определяет на основе HTTP-заголовка `X-Requested-With`). Существует также сокращенный метод в презентере: `$this->isAjax()`. -AJAX-запрос ничем не отличается от обычного — вызывается презентер с определенным представлением и параметрами. От презентера также зависит, как он отреагирует: он может использовать свои процедуры для возврата фрагмента HTML-кода (сниппета), XML-документа, объекта JSON или фрагмента кода Javascript. +AJAX-запрос +=========== -Существует предварительно обработанный объект `payload`, предназначенный для отправки данных в браузер в формате JSON. +AJAX-запрос в принципе не отличается от классического HTTP-запроса. Вызывается презентер (presenter) с определенными параметрами. И презентер решает, как реагировать на запрос — он может вернуть данные в формате JSON, отправить часть HTML-кода, XML-документ и т. д. -```php -public function actionDelete(int $id): void -{ - if ($this->isAjax()) { - $this->payload->message = 'Успешно'; - } - // ... -} +На стороне браузера мы инициализируем AJAX-запрос с помощью функции `fetch()`: + +```js +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) +.then(response => response.json()) +.then(payload => { + // обработка ответа +}); ``` -Для полного контроля над выводом JSON используйте метод `sendJson` в презентере. Это немедленно прервет работу презентера, и вы обойдетесь без шаблона: +На стороне сервера мы распознаем AJAX-запрос с помощью метода `$httpRequest->isAjax()` сервиса [инкапсулирующего HTTP-запрос |http:request]. Для обнаружения используется HTTP-заголовок `X-Requested-With`, поэтому важно его отправлять. В рамках презентера можно использовать метод `$this->isAjax()`. + +Если вы хотите отправить данные в формате JSON, используйте метод [`sendJson()` |presenters#Отправка ответа]. Метод также завершает работу презентера. ```php -$this->sendJson(['key' => 'value', /* ... */]); +public function actionExport(): void +{ + $this->sendJson($this->model->getData); +} ``` -Если мы хотим отправить HTML, мы можем установить специальный шаблон для AJAX-запросов: +Если вы планируете ответить с помощью специального шаблона, предназначенного для AJAX, вы можете сделать это следующим образом: ```php public function handleClick($param): void @@ -45,222 +52,198 @@ public function handleClick($param): void ``` +Сниппеты +======== + +Самый мощный инструмент, который предлагает Nette для связи сервера с клиентом, — это сниппеты. Благодаря им вы можете превратить обычное приложение в AJAX-приложение с минимальными усилиями и несколькими строками кода. Как все это работает, демонстрирует пример Fifteen, код которого вы найдете на [GitHub |https://github.com/nette-examples/fifteen]. + +Сниппеты, или фрагменты, позволяют обновлять только части страницы, вместо того чтобы перезагружать всю страницу целиком. Это не только быстрее и эффективнее, но и обеспечивает более комфортный пользовательский опыт. Сниппеты могут напомнить вам Hotwire для Ruby on Rails или Symfony UX Turbo. Интересно, что Nette представила сниппеты на 14 лет раньше. + +Как работают сниппеты? При первой загрузке страницы (не-AJAX запросе) загружается вся страница, включая все сниппеты. Когда пользователь взаимодействует со страницей (например, нажимает кнопку, отправляет форму и т. д.), вместо загрузки всей страницы вызывается AJAX-запрос. Код в презентере выполняет действие (action) и решает, какие сниппеты необходимо обновить. Nette отрисовывает эти сниппеты и отправляет их в виде массива в формате JSON. Обслуживающий код в браузере вставляет полученные сниппеты обратно в страницу. Таким образом, передается только код измененных сниппетов, что экономит пропускную способность и ускоряет загрузку по сравнению с передачей всего содержимого страницы. + + Naja -==== +---- -[Библиотека Naja |https://naja.js.org] используется для обработки AJAX-запросов на стороне браузера. [Установите |https://naja.js.org/#/guide/01-install-setup-naja] его как пакет node.js (для использования с Webpack, Rollup, Vite, Parcel и другими): +Для обработки сниппетов на стороне браузера используется [библиотека Naja |https://naja.js.org]. Ее [установите |https://naja.js.org/#/guide/01-install-setup-naja] как пакет node.js (для использования с приложениями Webpack, Rollup, Vite, Parcel и другими): ```shell npm install naja ``` -...или вставить непосредственно в шаблон страницы: +…или прямо вставьте в шаблон страницы: ```html ``` +Сначала необходимо [инициализировать |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] библиотеку: -Сниппеты -======== +```js +naja.initialize(); +``` + +Чтобы превратить обычную ссылку (сигнал) или отправку формы в AJAX-запрос, достаточно пометить соответствующую ссылку, форму или кнопку классом `ajax`: + +```html +Go -Однако существует гораздо более мощный инструмент встроенной поддержки AJAX — сниппеты. Их использование позволяет превратить обычное приложение в AJAX-приложение с помощью всего нескольких строк кода. Как всё это работает, показано в примере Fifteen, код которого также доступен в сборке или на [GitHub |https://github.com/nette-examples/fifteen]. +
    + +
    -Принцип работы сниппетов заключается в том, что вся страница передается во время начального (т. е. не-AJAX) запроса и затем с каждым AJAX [subrequest |components#Signal] (запрос того же представления того же презентера) только код измененных частей передается в хранилище `payload`, упомянутое ранее. +или -Сниппеты могут напомнить вам Hotwire для Ruby on Rails или Symfony UX Turbo, но Nette придумал их четырнадцатью годами раньше. +
    + +
    +``` -Инвалидация .[#toc-invalidation-of-snippets] -============================================ +Перерисовка сниппетов +--------------------- -Каждый потомок класса [Control |components] (которым является и Presenter) способен помнить, были ли какие-либо изменения во время запроса, требующие повторного отображения. Существует несколько способов справиться с этим: `redrawControl()` и `isControlInvalid()`. Пример: +Каждый объект класса [Control |components] (включая сам Presenter) отслеживает, произошли ли изменения, требующие его перерисовки. Для этого используется метод `redrawControl()`: ```php public function handleLogin(string $user): void { - // Объект должен повторно отображаться после того, как пользователь вошел в систему + // после входа необходимо перерисовать соответствующую часть $this->redrawControl(); // ... } ``` -Однако Nette обеспечивает ещё более тонкое разрешение, чем целые компоненты. Перечисленные методы принимают имя так называемого «фрагмента» в качестве необязательного параметра. «Фрагмет» это, по сути, элемент в вашем шаблоне, помеченный для этой цели макросом Latte, подробнее об этом позже. Таким образом, можно попросить компонент перерисовать только *часть* своего шаблона. Если весь компонент недействителен, то все его фрагменты отображаются заново. Компонент является «недействительным», если любой из его субкомпонентов является недействительным. - -```php -$this->isControlInvalid(); // -> false -$this->redrawControl('header'); // аннулирует фрагмент с именем 'header' -$this->isControlInvalid('header'); // -> true -$this->isControlInvalid('footer'); // -> false -$this->isControlInvalid(); // -> true, по крайней мере один фрагмент недействителен +Nette позволяет еще более тонко контролировать то, что нужно перерисовать. Указанный метод может принимать в качестве аргумента имя сниппета. Таким образом, можно инвалидировать (то есть: принудительно перерисовать) на уровне частей шаблона. Если инвалидируется весь компонент, то перерисовывается и каждый его сниппет: -$this->redrawControl(); // делает недействительным весь компонент, каждый фрагмент -$this->isControlInvalid('footer'); // -> true +```php +// инвалидирует сниппет 'header' +$this->redrawControl('header'); ``` -Компонент, получивший сигнал, автоматически помечается для перерисовки. - -Благодаря перерисовке фрагментов мы точно знаем, какие части каких элементов должны быть перерисованы. - - -Тег `{snippet} … {/snippet}` .{toc: Tag snippet} -================================================ - -Рендеринг страницы происходит точно так же, как и при обычном запросе: загружаются одни и те же шаблоны и т. д. Однако самое важное — это не допустить попадания в выходной сигнал тех частей, которые не должны попасть в выходной сигнал; остальные части должны быть связаны с идентификатором и отправлены пользователю в формате, понятном для обработчика JavaScript. - -Синтаксис ---------- +Сниппеты в Latte +---------------- -Если в шаблоне есть элемент управления или фрагмент, мы должны обернуть его с помощью парного тега `{snippet} ... {/snippet}` — отрисованный фрагмент будет «вырезан» и отправится в браузер. Он также заключит его в вспомогательный тег `
    ` (можно использовать другой). В следующем примере определен сниппет с именем `header`. Он также может представлять собой шаблон компонента: +Использование сниппетов в Latte невероятно просто. Чтобы определить часть шаблона как сниппет, просто оберните ее тегами `{snippet}` и `{/snippet}`: ```latte {snippet header} -

    Hello ...

    +

    Привет ...

    {/snippet} ``` -Если вы хотите создать сниппет с другим содержащим элементом, отличным от `
    `, или добавить пользовательские атрибуты к элементу, вы можете использовать следующее определение: +Сниппет создает в HTML-странице элемент `
    ` со специальным сгенерированным `id`. При перерисовке сниппета обновляется содержимое этого элемента. Поэтому необходимо, чтобы при первоначальной отрисовке страницы отрисовывались также все сниппеты, даже если они могут быть пустыми вначале. + +Вы можете создать сниппет с другим элементом, отличным от `
    `, с помощью n:атрибута: ```latte
    -

    Hello ...

    +

    Привет ...

    ``` -Динамические сниппеты -===================== +Области сниппетов +----------------- -В Nette вы также можете определить сниппеты с динамическим именем, основанным на параметре времени выполнения. Это наиболее подходит для различных списков, где нам нужно изменить только одну строку, но мы не хотим переносить весь список вместе с ней. Примером этого может быть: +Имена сниппетов также могут быть выражениями: ```latte - +{foreach $items as $id => $item} +
  • {$item}
  • +{/foreach} ``` -Существует один статический сниппет `itemsContainer`, содержащий несколько динамических сниппетов: `пункт-0`, `пункт-1` и так далее. +Таким образом, у нас получится несколько сниппетов `item-0`, `item-1` и т. д. Если бы мы напрямую инвалидировали динамический сниппет (например, `item-1`), ничего бы не перерисовалось. Причина в том, что сниппеты действительно работают как фрагменты и отрисовываются только они сами. Однако в шаблоне фактически нет сниппета с именем `item-1`. Он создается только при выполнении кода вокруг сниппета, то есть цикла foreach. Поэтому мы помечаем часть шаблона, которая должна быть выполнена, с помощью тега `{snippetArea}`: -Вы не можете перерисовать динамический фрагмент напрямую (перерисовка `item-1` не имеет эффекта), вы должны перерисовать его родительский фрагмент (в данном примере `itemsContainer`). При этом выполняется код родительского сниппета, но браузеру передаются только его вложенные сниппеты. Если вы хотите передать только один из вложенных сниппетов, вам нужно изменить ввод для родительского сниппета, чтобы не генерировать другие вложенные сниппеты. +```latte +
      + {foreach $items as $id => $item} +
    • {$item}
    • + {/foreach} +
    +``` -В приведенном примере необходимо убедиться, что при AJAX-запросе в массив `$list` будет добавлен только один элемент, поэтому цикл `foreach` будет выводить только один динамический фрагмент. +И заставляем перерисовать как сам сниппет, так и всю родительскую область: ```php -class HomePresenter extends Nette\Application\UI\Presenter -{ - /** - * Этот метод возвращает данные для списка. - * Обычно это просто запрос данных из модели. - * Для целей этого примера данные жёстко закодированы. - */ - private function getTheWholeList(): array - { - return [ - 'First', - 'Second', - 'Third', - ]; - } - - public function renderDefault(): void - { - if (!isset($this->template->list)) { - $this->template->list = $this->getTheWholeList(); - } - } - - public function handleUpdate(int $id): void - { - $this->template->list = $this->isAjax() - ? [] - : $this->getTheWholeList(); - $this->template->list[$id] = 'Updated item'; - $this->redrawControl('itemsContainer'); - } -} +$this->redrawControl('itemsContainer'); +$this->redrawControl('item-1'); ``` +В то же время желательно убедиться, что массив `$items` содержит только те элементы, которые должны быть перерисованы. -Сниппеты во включенном шаблоне -============================== - -Может случиться так, что сниппет находится в шаблоне, который включается из другого шаблона. В этом случае необходимо обернуть код включения во втором шаблоне макросом `snippetArea`, затем перерисовать как snippetArea, так и сам сниппет. - -Макрос `snippetArea` гарантирует, что код внутри него будет выполнен, но браузеру будет отправлен только фактический фрагмент включенного шаблона. +Если мы вставляем в шаблон с помощью тега `{include}` другой шаблон, содержащий сниппеты, необходимо включение шаблона снова обернуть в `snippetArea` и инвалидировать его вместе со сниппетом: ```latte -{* parent.latte *} -{snippetArea wrapper} - {include 'child.latte'} +{snippetArea include} + {include 'included.latte'} {/snippetArea} ``` + ```latte -{* child.latte *} +{* included.latte *} {snippet item} -... + ... {/snippet} ``` + ```php -$this->redrawControl('wrapper'); +$this->redrawControl('include'); $this->redrawControl('item'); ``` -Вы также можете сочетать его с динамическими сниппетами. - -Добавление и удаление -===================== +Сниппеты в компонентах +---------------------- -Если добавить новый элемент в список и аннулировать `itemsContainer`, AJAX-запрос вернет фрагменты, включая новый, но javascript-обработчик не сможет его отобразить. Это происходит потому, что нет HTML-элемента с вновь созданным ID. - -В этом случае самый простой способ — обернуть весь список в ещё один сниппет и признать его недействительным: +Вы можете создавать сниппеты и в [компонентах |components], и Nette будет автоматически их перерисовывать. Но есть определенное ограничение: для перерисовки сниппетов вызывается метод `render()` без параметров. То есть передача параметров в шаблоне не будет работать: ```latte -{snippet wholeList} - -{/snippet} -Добавить +OK +{control productGrid} + +не будет работать: +{control productGrid $arg, $arg} +{control productGrid:paginator} ``` + +Отправка пользовательских данных +-------------------------------- + +Вместе со сниппетами вы можете отправить клиенту любые другие данные. Достаточно записать их в объект `payload`: + ```php -public function handleAdd(): void +public function actionDelete(int $id): void { - $this->template->list = $this->getTheWholeList(); - $this->template->list[] = 'New one'; - $this->redrawControl('wholeList'); + // ... + if ($this->isAjax()) { + $this->payload->message = 'Успешно'; + } } ``` -То же самое относится и к удалению элемента. Можно было бы отправить пустой сниппет, но обычно списки могут быть постраничными, и было бы сложно реализовать удаление одного элемента и загрузку другого (который раньше находился на другой странице постраничного списка). - -Отправка параметров компоненту -============================== +Передача параметров +=================== -Когда мы отправляем параметры компоненту через AJAX-запрос, будь то сигнальные или постоянные параметры, мы должны предоставить их глобальное имя, которое также содержит имя компонента. Полное имя параметра возвращает метод `getParameterId()`. +Если мы отправляем компоненту параметры с помощью AJAX-запроса, будь то параметры сигнала или персистентные параметры, мы должны указать в запросе их глобальное имя, которое также содержит имя компонента. Полное имя параметра возвращает метод `getParameterId()`. ```js -$.getJSON( - {link changeCountBasket!}, - { - {$control->getParameterId('id')}: id, - {$control->getParameterId('count')}: count - } -}); +let url = new URL({link //foo!}); +url.searchParams.set({$control->getParameterId('bar')}, bar); + +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) ``` -И обработать метод с соответствующими параметрами в компоненте. +И метод handle с соответствующими параметрами в компоненте: ```php -public function handleChangeCountBasket(int $id, int $count): void +public function handleFoo(int $bar): void { - } ``` diff --git a/application/ru/bootstrap.texy b/application/ru/bootstrap.texy deleted file mode 100644 index fb4b949974..0000000000 --- a/application/ru/bootstrap.texy +++ /dev/null @@ -1,233 +0,0 @@ -Bootstrap -********* - -
    - -Bootstrap — это загрузочный код, который инициализирует среду, создает контейнер внедрения зависимостей (DI) и запускает приложение. Мы обсудим: - -- как настроить приложение с помощью файлов NEON -- как работать с режимами производства и разработки -- как создать контейнер DI - -
    - - -Приложения, будь то веб-приложения или скрипты командной строки, начинаются с инициализации среды в той или иной форме. В древние времена за это мог отвечать файл с именем, например, `include.inc.php`, который включался в исходный файл. -В современных приложениях Nette он заменен классом `Bootstrap`, который как часть приложения находится в файле `app/Bootstrap.php`. Это может выглядеть, например, так: - -```php -use Nette\Bootstrap\Configurator; - -class Bootstrap -{ - public static function boot(): Configurator - { - $appDir = dirname(__DIR__); - $configurator = new Configurator; - //$configurator->setDebugMode('secret@23.75.345.200'); - $configurator->enableTracy($appDir . '/log'); - $configurator->setTempDirectory($appDir . '/temp'); - $configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); - $configurator->addConfig($appDir . '/config/common.neon'); - return $configurator; - } -} -``` - - -index.php -========= - -В случае веб-приложений начальным файлом является `index.php`, который находится в общедоступном каталоге `www/`. Он позволяет классу `Bootstrap` инициализировать среду и возвращает `$configurator`, который создает контейнер DI. Затем он получает сервис `Application`, запускающий веб-приложение: - -```php -// инициализируем среду + получаем объект Configurator -$configurator = App\Bootstrap::boot(); -// создаем DI-контейнер -$container = $configurator->createContainer(); -// DI-контейнер создайет объект Nette\Application\Application -$application = $container->getByType(Nette\Application\Application::class); -// запускаем приложение Nette -$application->run(); -``` - -Как вы видите, класс [api:Nette\Bootstrap\Configurator], который мы сейчас представим более подробно, помогает в настройке окружения и создании контейнера внедрения зависимостей (DI). - - -Режим разработки и режим производства .[#toc-development-vs-production-mode] -============================================================================ - -Nette различает два основных режима, в которых выполняется запрос: разработка и производство. Режим разработки ориентирован на максимальное удобство программиста, отображается Tracy, кэш автоматически обновляется при изменении шаблонов или конфигурации DI контейнера и т. д. Режим производства ориентирован на производительность, Tracy только регистрирует ошибки, а изменения шаблонов и других файлов не проверяются. - -Выбор режима осуществляется путем автоопределения, поэтому обычно нет необходимости настраивать или переключать что-либо вручную. Режим разработки используется, если приложение запущено на localhost (т. е. IP-адрес `127.0.0.1` или `::1`) и отсутствует прокси-сервер (т. е. его HTTP-заголовок). В противном случае приложение работает в производственном режиме. - -Если вы хотите включить режим разработки в других случаях, например, для программистов, получающих доступ с определенного IP-адреса, вы можете использовать `setDebugMode()`: - -```php -$configurator->setDebugMode('23.75.345.200'); // один или более IP-адресов -``` - -Мы определенно рекомендуем сочетать IP-адрес с файлом cookie. Мы будем хранить секретный токен в cookie `nette-debug', например, `secret1234`, и режим разработки будет активирован для программистов с такой комбинацией IP и cookie. - -```php -$configurator->setDebugMode('secret1234@23.75.345.200'); -``` - -Можно полностью отключить режим разработчика, даже для localhost: - -```php -$configurator->setDebugMode(false); -``` - -Обратите внимание, что значение `true` жестко включает режим разработчика, чего никогда не должно происходить на рабочем сервере. - - -Отладочный инструмент Tracy .[#toc-debugging-tool-tracy] -======================================================== - -Для облегчения отладки мы включим замечательный инструмент [Tracy |tracy:]. В режиме разработчика он визуализирует ошибки, а в режиме производства — записывает ошибки в указанный каталог: - -```php -$configurator->enableTracy($appDir . '/log'); -``` - - -Временные файлы .[#toc-temporary-files] -======================================= - -Nette использует кэш для DI-контейнера, RobotLoader, шаблонов и т. д. Поэтому необходимо задать путь к директории, где будет храниться кэш: - -```php -$configurator->setTempDirectory($appDir . '/temp'); -``` - -В Linux или macOS установите [права на запись |nette:troubleshooting#Setting-Directory-Permissions] для каталогов `log/` и `temp/`. - - -RobotLoader -=========== - -Обычно мы хотим автоматически загружать классы с помощью [RobotLoader |robot-loader:], поэтому мы должны запустить его и позволить ему загрузить классы из каталога, в котором находится `Bootstrap.php` (т. е. `__DIR__`) и все его подкаталоги: - -```php -$configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); -``` - -Альтернативный способ — использовать только автозагрузку PSR-4 [Composer |best-practices:composer]. - - -Часовой пояс .[#toc-timezone] -============================= - -Configurator позволяет указать часовой пояс для вашего приложения. - -```php -$configurator->setTimeZone('Europe/Prague'); -``` - - -Конфигурация DI-контейнера .[#toc-di-container-configuration] -============================================================= - -Частью процесса загрузки является создание DI-контейнера, то есть фабрики объектов, которая является сердцем всего приложения. На самом деле это PHP-класс, созданный Nette и хранящийся в каталоге кэша. Фабрика создает ключевые объекты приложения, а конфигурационные файлы инструктируют её, как их создавать и настраивать, и таким образом мы влияем на поведение всего приложения. - -Файлы конфигурации обычно записываются в формате [NEON|neon:format]. Вы можете прочитать [что можно настроить здесь|nette:configuring]. - -.[tip] -В режиме разработки контейнер автоматически обновляется каждый раз, когда вы изменяете код или конфигурационные файлы. В производственном режиме он генерируется только один раз, а изменения файлов не проверяются для достижения максимальной производительности. - -Файлы конфигурации загружаются с помощью `addConfig()`: - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -``` - -Метод `addConfig()` может быть вызван несколько раз для добавления нескольких файлов. - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -$configurator->addConfig($appDir . '/config/local.neon'); -if (PHP_SAPI === 'cli') { - $configurator->addConfig($appDir . '/config/cli.php'); -} -``` - -Подключение `cli.php` не является опечаткой, конфигурация также может быть записана в PHP-файле, который возвращает ее в виде массива. - -Альтернативно, мы можем использовать [секцию `includes`|dependency-injection:configuration#Including-Files] для загрузки конфигурационных файлов. - -Если элементы с одинаковыми ключами отображаются в файлах конфигурации, они будут [перезаписаны или объединены |dependency-injection:configuration#Merging] в случае массивов. Позже включенный файл имеет более высокий приоритет, чем предыдущие. Файл, указанный в секции `includes`, имеет более высокий приоритет, чем файлы, включенные в него. - - -Статические параметры .[#toc-static-parameters] ------------------------------------------------ - -Параметры, используемые в файлах конфигурации, могут быть определены [в секции `parameters`|dependency-injection:configuration#parameters] и подхвачены (или перезаписаны) методом `addStaticParameters()` (у него есть алиас `addParameters()`). Важно, что разные значения параметров вызывают генерацию дополнительных DI-контейнеров, то есть дополнительных классов. - -```php -$configurator->addStaticParameters([ - 'projectId' => 23, -]); -``` - -В конфигурационных файлах можно использовать обычную нотацию `%projectId%` для доступа к параметру с именем `projectId`. По умолчанию конфигуратор заполняет следующие параметры: `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` и `consoleMode`. - - -Динамические параметры .[#toc-dynamic-parameters] -------------------------------------------------- - -Можно также добавить динамические параметры в контейнер. Их разные значения, в отличие от статических параметров, не приведут к генерации новых DI-контейнеров. - -```php -$configurator->addDynamicParameters([ - 'remoteIp' => $_SERVER['REMOTE_ADDR'], -]); -``` - -Переменные среды могут быть легко доступны с использованием динамических параметров. Мы можем получить доступ к ним через `%env.variable%` в файлах конфигурации. - -```php -$configurator->addDynamicParameters([ - 'env' => getenv(), -]); -``` - - -Импортированные сервисы .[#toc-imported-services] -------------------------------------------------- - -Углубимся дальше. Хотя цель DI-контейнера в создании объектов, может возникнуть необходимость вставить существующий объект в контейнер. Это делается определением сервиса с атрибутом `imported: true`: - -```neon -services: - myservice: - type: App\Model\MyCustomService - imported: true -``` - -Создаём новый экземпляр и вставляем его в Bootstrap: - -```php -$configurator->addServices([ - 'myservice' => new App\Model\MyCustomService('foobar'), -]); -``` - - -Разные среды .[#toc-different-environments] -=========================================== - -Не стесняйтесь настроить класс `Bootstrap` в соответствии с вашими потребностями. Вы можете добавлять параметры в метод `boot()` для разделения веб-проектов, или добавлять другие методы, такие как `bootForTests()`, которые инициализируют среду для модульных тестов, `bootForCli()` для скриптов, вызываемых из командной строки, и так далее. - -```php -public static function bootForTests(): Configurator -{ - $configurator = self::boot(); - Tester\Environment::setup(); // Инициализация Nette Tester - return $configurator; -} -``` diff --git a/application/ru/bootstrapping.texy b/application/ru/bootstrapping.texy new file mode 100644 index 0000000000..58aeb4f9b0 --- /dev/null +++ b/application/ru/bootstrapping.texy @@ -0,0 +1,297 @@ +Загрузка +******** + +
    + +Загрузка — это процесс инициализации среды приложения, создания контейнера внедрения зависимостей (DI) и запуска приложения. Мы обсудим: + +- как класс Bootstrap инициализирует среду +- как приложения настраиваются с помощью NEON файлов +- как различать режим производства и разработки +- как создать и настроить DI контейнер + +
    + + +Приложения, будь то веб-приложения или скрипты, запускаемые из командной строки, начинают свою работу с некоторой формы инициализации среды. В давние времена за это отвечал файл с именем, например, `include.inc.php`, который включался в первоначальный файл. В современных приложениях Nette его заменил класс `Bootstrap`, который как часть приложения находится в файле `app/Bootstrap.php`. Он может выглядеть, например, так: + +```php +use Nette\Bootstrap\Configurator; + +class Bootstrap +{ + private Configurator $configurator; + private string $rootDir; + + public function __construct() + { + $this->rootDir = dirname(__DIR__); + // Конфигуратор отвечает за настройку среды приложения и сервисов. + $this->configurator = new Configurator; + // Устанавливает каталог для временных файлов, генерируемых Nette (например, скомпилированных шаблонов) + $this->configurator->setTempDirectory($this->rootDir . '/temp'); + } + + public function bootWebApplication(): Nette\DI\Container + { + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); + } + + private function initializeEnvironment(): void + { + // Nette умный, и режим разработки включается автоматически, + // или вы можете включить его для конкретного IP-адреса, раскомментировав следующую строку: + // $this->configurator->setDebugMode('secret@23.75.345.200'); + + // Активирует Tracy: ультимативный "швейцарский нож" для отладки. + $this->configurator->enableTracy($this->rootDir . '/log'); + + // RobotLoader: автоматически загружает все классы в выбранном каталоге + $this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); + } + + private function setupContainer(): void + { + // Загружает конфигурационные файлы + $this->configurator->addConfig($this->rootDir . '/config/common.neon'); + } +} +``` + + +index.php +========= + +Первоначальным файлом для веб-приложений является `index.php`, который находится в [публичном каталоге |directory-structure#Публичный каталог www] `www/`. Он запрашивает у класса Bootstrap инициализацию среды и создание DI-контейнера. Затем он получает из него сервис `Application`, который запускает веб-приложение: + +```php +$bootstrap = new App\Bootstrap; +// Инициализация среды + создание DI-контейнера +$container = $bootstrap->bootWebApplication(); +// DI-контейнер создает объект Nette\Application\Application +$application = $container->getByType(Nette\Application\Application::class); +// Запуск приложения Nette и обработка входящего запроса +$application->run(); +``` + +Как видно, с настройкой среды и созданием контейнера внедрения зависимостей (DI) помогает класс [api:Nette\Bootstrap\Configurator], который мы сейчас рассмотрим подробнее. + + +Режим разработки vs режим production +==================================== + +Nette ведет себя по-разному в зависимости от того, работает ли он на сервере разработки или production: + +🛠️ Режим разработки (Development): + - Отображает панель отладки Tracy с полезной информацией (SQL-запросы, время выполнения, использованная память) + - При ошибке отображает подробную страницу ошибки с вызовами функций и содержимым переменных + - Автоматически обновляет кеш при изменении шаблонов Latte, редактировании конфигурационных файлов и т. д. + + +🚀 Режим production (Production): + - Не отображает никакой отладочной информации, все ошибки записывает в лог + - При ошибке отображает ErrorPresenter или общую страницу "Server Error" + - Кеш никогда автоматически не обновляется! + - Оптимизирован для скорости и безопасности + + +Выбор режима осуществляется автоопределением, поэтому обычно не требуется ничего настраивать или вручную переключать: + +- режим разработки: на localhost (IP-адрес `127.0.0.1` или `::1`), если нет прокси (т. е. его HTTP-заголовка) +- режим production: везде в остальных случаях + +Если мы хотим включить режим разработки и в других случаях, например, для программистов, обращающихся с конкретного IP-адреса, используем `setDebugMode()`: + +```php +$this->configurator->setDebugMode('23.75.345.200'); // можно указать и массив IP-адресов +``` + +Настоятельно рекомендуем комбинировать IP-адрес с cookie. В cookie `nette-debug` сохраним секретный токен, например `secret1234`, и таким образом активируем режим разработки для программистов, обращающихся с конкретного IP-адреса и одновременно имеющих в cookie упомянутый токен: + +```php +$this->configurator->setDebugMode('secret1234@23.75.345.200'); +``` + +Режим разработки можно также полностью отключить, даже для localhost: + +```php +$this->configurator->setDebugMode(false); +``` + +Внимание, значение `true` включает режим разработки принудительно, что никогда не должно происходить на production-сервере. + + +Инструмент отладки Tracy +======================== + +Для легкой отладки мы также включим отличный инструмент [Tracy |tracy:]. В режиме разработки он визуализирует ошибки, а в режиме production записывает ошибки в указанный каталог: + +```php +$this->configurator->enableTracy($this->rootDir . '/log'); +``` + + +Временные файлы +=============== + +Nette использует кеш для DI-контейнера, RobotLoader, шаблонов и т. д. Поэтому необходимо установить путь к каталогу, куда будет сохраняться кеш: + +```php +$this->configurator->setTempDirectory($this->rootDir . '/temp'); +``` + +На Linux или macOS установите для каталогов `log/` и `temp/` [права на запись |nette:troubleshooting#Настройка прав доступа к каталогам]. + + +RobotLoader +=========== + +Как правило, мы захотим автоматически загружать классы с помощью [RobotLoader |robot-loader:], поэтому мы должны его запустить и позволить ему загружать классы из каталога, где находится `Bootstrap.php` (т. е. `__DIR__`), и всех подкаталогов: + +```php +$this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); +``` + +Альтернативный подход — позволить загружать классы только через [Composer |best-practices:composer] при соблюдении PSR-4. + + +Часовой пояс +============ + +Через конфигуратор можно установить часовой пояс по умолчанию. + +```php +$this->configurator->setTimeZone('Europe/Prague'); +``` + + +Конфигурация DI-контейнера +========================== + +Частью процесса загрузки является создание DI-контейнера, или фабрики объектов, которая является сердцем всего приложения. Это фактически PHP-класс, который генерирует Nette и сохраняет в каталоге кеша. Фабрика производит ключевые объекты приложения, и с помощью конфигурационных файлов мы инструктируем ее, как их создавать и настраивать, тем самым влияя на поведение всего приложения. + +Конфигурационные файлы обычно записываются в формате [NEON |neon:format]. В отдельной главе вы узнаете, [что все можно настроить |nette:configuring]. + +.[tip] +В режиме разработки контейнер автоматически обновляется при каждом изменении кода или конфигурационных файлов. В режиме production он генерируется только один раз, и изменения не проверяются для максимальной производительности. + +Конфигурационные файлы загружаем с помощью `addConfig()`: + +```php +$this->configurator->addConfig($this->rootDir . '/config/common.neon'); +``` + +Если мы хотим добавить несколько конфигурационных файлов, мы можем вызвать функцию `addConfig()` несколько раз. + +```php +$configDir = $this->rootDir . '/config'; +$this->configurator->addConfig($configDir . '/common.neon'); +$this->configurator->addConfig($configDir . '/services.neon'); +if (PHP_SAPI === 'cli') { + $this->configurator->addConfig($configDir . '/cli.php'); +} +``` + +Имя `cli.php` — не опечатка, конфигурация может быть записана и в PHP-файле, который вернет ее в виде массива. + +Также мы можем добавить другие конфигурационные файлы в [секции `includes` |dependency-injection:configuration#Включение файлов]. + +Если в конфигурационных файлах появляются элементы с одинаковыми ключами, они будут перезаписаны или, в случае [массивов, объединены |dependency-injection:configuration#Слияние]. Позже включенный файл имеет более высокий приоритет, чем предыдущий. Файл, в котором указана секция `includes`, имеет более высокий приоритет, чем включенные в нем файлы. + + +Статические параметры +--------------------- + +Параметры, используемые в конфигурационных файлах, можно определить [в секции `parameters` |dependency-injection:configuration#Параметры], а также передавать (или перезаписывать) методом `addStaticParameters()` (имеет псевдоним `addParameters()`). Важно, что разные значения параметров приводят к генерации дополнительных DI-контейнеров, то есть дополнительных классов. + +```php +$this->configurator->addStaticParameters([ + 'projectId' => 23, +]); +``` + +На параметр `projectId` можно ссылаться в конфигурации обычным способом `%projectId%`. + + +Динамические параметры +---------------------- + +В контейнер можно добавить и динамические параметры, различные значения которых, в отличие от статических параметров, не приводят к генерации новых DI-контейнеров. + +```php +$this->configurator->addDynamicParameters([ + 'remoteIp' => $_SERVER['REMOTE_ADDR'], +]); +``` + +Таким образом, мы можем легко добавить, например, переменные среды, на которые затем можно ссылаться в конфигурации с помощью записи `%env.variable%`. + +```php +$this->configurator->addDynamicParameters([ + 'env' => getenv(), +]); +``` + + +Параметры по умолчанию +---------------------- + +В конфигурационных файлах вы можете использовать эти статические параметры: + +- `%appDir%` — абсолютный путь к каталогу с файлом `Bootstrap.php` +- `%wwwDir%` — абсолютный путь к каталогу с входным файлом `index.php` +- `%tempDir%` — абсолютный путь к каталогу для временных файлов +- `%vendorDir%` — абсолютный путь к каталогу, куда Composer устанавливает библиотеки +- `%rootDir%` — абсолютный путь к корневому каталогу проекта +- `%debugMode%` — указывает, находится ли приложение в режиме отладки +- `%consoleMode%` — указывает, пришел ли запрос через командную строку + + +Импортированные сервисы +----------------------- + +Теперь мы углубляемся. Хотя смысл DI-контейнера заключается в создании объектов, в исключительных случаях может возникнуть необходимость вставить существующий объект в контейнер. Мы делаем это, определяя сервис с флагом `imported: true`. + +```neon +services: + myservice: + type: App\Model\MyCustomService + imported: true +``` + +И в bootstrap вставляем объект в контейнер: + +```php +$this->configurator->addServices([ + 'myservice' => new App\Model\MyCustomService('foobar'), +]); +``` + + +Различные среды +=============== + +Не бойтесь изменять класс Bootstrap в соответствии со своими потребностями. Методу `bootWebApplication()` можно добавить параметры для различения веб-проектов. Или мы можем добавить другие методы, например `bootTestEnvironment()`, который инициализирует среду для модульных тестов, `bootConsoleApplication()` для скриптов, вызываемых из командной строки, и т. д. + +```php +public function bootTestEnvironment(): Nette\DI\Container +{ + Tester\Environment::setup(); // инициализация Nette Tester + $this->setupContainer(); + return $this->configurator->createContainer(); +} + +public function bootConsoleApplication(): Nette\DI\Container +{ + $this->configurator->setDebugMode(false); + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); +} +``` diff --git a/application/ru/components.texy b/application/ru/components.texy index b4e0f8a4d4..be5ba317a5 100644 --- a/application/ru/components.texy +++ b/application/ru/components.texy @@ -3,7 +3,7 @@
    -Компоненты — это отдельные объекты многократного использования, которые мы помещаем на страницы. Это могут быть формы, сетки данных, опросы, в общем, всё, что имеет смысл использовать многократно. Далее мы узнаем: +Компоненты — это отдельные повторно используемые объекты, которые мы вставляем на страницы. Это могут быть формы, датагриды, опросы, в общем, все, что имеет смысл использовать повторно. Мы покажем: - как использовать компоненты? - как их писать? @@ -11,19 +11,19 @@
    -Nette имеет встроенную систему компонентов. Те, кто постарше, могут помнить нечто подобное из Delphi или ASP.NET Web Forms. React или Vue.js построены на чём-то отдаленно похожем. Однако в мире PHP-фреймворков это совершенно уникальная функция. +Nette имеет встроенную систему компонентов. Что-то подобное могут помнить старожилы из Delphi или ASP.NET Web Forms, на чем-то отдаленно похожем построены React или Vue.js. Однако в мире PHP-фреймворков это уникальное явление. -В то же время компоненты в корне меняют подход к разработке приложений. Вы можете составлять страницы из заранее подготовленных блоков. Нужна ли вам сетка данных в администрировании? Вы можете найти её на [Componette |https://componette.org/search/component], репозитории открытых дополнений (не только компонентов) для Nette, и просто вставить в презентер. +При этом компоненты кардинально влияют на подход к созданию приложений. Вы можете собирать страницы из готовых блоков. Нужен датагрид в админке? Найдите его на [Componette |https://componette.org/search/component], репозитории open-source дополнений (то есть не только компонентов) для Nette, и просто вставьте в презентер. -Вы можете включить в презентер любое количество компонентов. И вы можете вставлять другие компоненты в некоторые компоненты. Это создает дерево компонентов с презентером в качестве корня. +В презентер можно включить любое количество компонентов. А в некоторые компоненты можно вставлять другие компоненты. Таким образом, создается дерево компонентов, корнем которого является презентер. -Фабричные методы .[#toc-factory-methods] -======================================== +Фабричные методы +================ -Как размещаются и впоследствии используются компоненты в презентере? Обычно с использованием фабричных методов. +Как компоненты вставляются в презентер и затем используются? Обычно с помощью фабричных методов. -Фабрика компонентов — это элегантный способ создавать компоненты только тогда, когда они действительно нужны (лениво / по требованию). Вся магия заключается в реализации метода `createComponent()`, где `` — имя компонента, который будет создан и возвращен. +Фабрика компонентов представляет собой элегантный способ создания компонентов только тогда, когда они действительно необходимы (lazy / on demand). Все волшебство заключается в реализации метода с именем `createComponent()`, где `` — это имя создаваемого компонента, и который создает и возвращает компонент. ```php .{file:DefaultPresenter.php} class DefaultPresenter extends Nette\Application\UI\Presenter @@ -37,43 +37,43 @@ class DefaultPresenter extends Nette\Application\UI\Presenter } ``` -Поскольку все компоненты создаются в отдельных методах, код чище и легче читается. +Благодаря тому, что все компоненты создаются в отдельных методах, код становится более понятным. .[note] -Имена компонентов всегда начинаются со строчной буквы, хотя в имени метода они пишутся с заглавной. +Имена компонентов всегда начинаются с маленькой буквы, хотя в названии метода они пишутся с большой. -Мы никогда не вызываем фабрики напрямую, они вызываются автоматически, когда мы впервые используем компоненты. Благодаря этому компонент создается в нужный момент, и только если он действительно необходим. Если мы не будем использовать компонент (например, при AJAX-запросе, когда мы возвращаем только часть страницы, или когда части кэшируются), он даже не будет создан, и мы сэкономим производительность сервера. +Фабрики никогда не вызываются напрямую, они вызываются сами в тот момент, когда мы впервые используем компонент. Благодаря этому компонент создается в нужный момент и только в том случае, когда он действительно необходим. Если мы не используем компонент (например, при AJAX-запросе, когда передается только часть страницы, или при кешировании шаблона), он вообще не создается, и мы экономим ресурсы сервера. ```php .{file:DefaultPresenter.php} -// мы обращаемся к компоненту, и если это было впервые, -// он вызывает createComponentPoll(), чтобы создать его +// обращаемся к компоненту, и если это было впервые, +// вызывается createComponentPoll(), который его создает $poll = $this->getComponent('poll'); // альтернативный синтаксис: $poll = $this['poll']; ``` -В шаблоне вы можете визуализировать компонент с помощью тега [{control} |#Rendering]. Поэтому нет необходимости вручную передавать компоненты в шаблон. +В шаблоне можно отрисовать компонент с помощью тега [{control} |#Отрисовка]. Поэтому нет необходимости вручную передавать компоненты в шаблон. ```latte -

    Проголосуйте, пожалуйста

    +

    Голосуйте

    {control poll} ``` -Голливудский стиль .[#toc-hollywood-style] -========================================== +Стиль Голливуда +=============== -Компоненты обычно используют один классный прием, который мы любим называть голливудским стилем. Наверняка вы знаете это клише, которое актёры часто слышат на кастингах: «Не звоните нам, мы позвоним вам». И это то, о чём идёт речь. +Компоненты обычно используют одну свежую технику, которую мы любим называть стилем Голливуда. Вы наверняка знаете крылатую фразу, которую так часто слышат участники кинопроб: «Не звоните нам, мы вам позвоним». Именно об этом и идет речь. -В Nette, вместо того, чтобы постоянно задавать вопросы («была ли форма отправлена?», «была ли она действительна?», или «нажал ли кто-нибудь на эту кнопку?»), вы говорите фреймворку «когда это произойдет, вызовите этот метод» и оставьте дальнейшую работу над ним. Если вы программируете на JavaScript, вы знакомы с этим стилем программирования. Вы пишете функции, которые вызываются при наступлении определенного события. А движок передает им соответствующие параметры. +В Nette вместо того, чтобы постоянно спрашивать («была ли отправлена форма?», «было ли это валидно?» или «нажал ли пользователь эту кнопку?»), вы говорите фреймворку «когда это произойдет, вызови этот метод» и оставляете дальнейшую работу ему. Если вы программируете на JavaScript, этот стиль программирования вам хорошо знаком. Вы пишете функции, которые вызываются, когда происходит определенное событие. И язык передает им соответствующие параметры. -Это полностью меняет способ написания приложений. Чем больше задач вы можете делегировать фреймворку, тем меньше у вас работы. И тем меньше вы можете забыть. +Это полностью меняет взгляд на написание приложений. Чем больше задач вы можете оставить фреймворку, тем меньше у вас работы. И тем меньше вы можете что-то упустить. -Как написать компонент .[#toc-how-to-write-a-component] -======================================================= +Пишем компонент +=============== -Под компонентом мы обычно подразумеваем потомков класса [api:Nette\Application\UI\Control]. Сам презентер [api:Nette\Application\UI\Presenter] также является потомком класса `Control`. +Под понятием компонент обычно подразумевается потомок класса [api:Nette\Application\UI\Control]. (Точнее было бы использовать термин «controls», но «контролы» в русском языке имеют совершенно другое значение, и скорее прижились «компоненты».) Сам презентер [api:Nette\Application\UI\Presenter] также является потомком класса `Control`. ```php .{file:PollControl.php} use Nette\Application\UI\Control; @@ -84,22 +84,22 @@ class PollControl extends Control ``` -Рендеринг .[#toc-rendering] -=========================== +Отрисовка +========= -Мы уже знаем, что тег `{control componentName}` используется для рисования компонента. Он фактически вызывает метод `render()` компонента, в котором мы берем на себя заботу о рендеринге. У нас есть, как и в презентере, шаблон [Latte |latte:] в переменной `$this->template`, которому мы передаем параметры. В отличие от использования в презентере, мы должны указать файл шаблона и позволить ему отрисоваться: +Мы уже знаем, что для отрисовки компонента используется тег `{control componentName}`. Он фактически вызывает метод `render()` компонента, в котором мы заботимся об отрисовке. В нашем распоряжении, точно так же, как и в презентере, есть [шаблон Latte |templates] в переменной `$this->template`, в который мы передаем параметры. В отличие от презентера, мы должны указать файл с шаблоном и заставить его отрисоваться: ```php .{file:PollControl.php} public function render(): void { - // мы поместим некоторые параметры в шаблон + // вставляем в шаблон какие-то параметры $this->template->param = $value; - // и отобразим его + // и отрисовываем его $this->template->render(__DIR__ . '/poll.latte'); } ``` -Тег `{control}` позволяет передавать параметры в метод `render()`: +Тег `{control}` позволяет передать параметры в метод `render()`: ```latte {control poll $id, $message} @@ -112,7 +112,7 @@ public function render(int $id, string $message): void } ``` -Иногда компонент может состоять из нескольких частей, которые мы хотим отобразить отдельно. Для каждого из них мы создадим свой метод рендеринга, например, `renderPaginator()`: +Иногда компонент может состоять из нескольких частей, которые мы хотим отрисовывать отдельно. Для каждой из них мы создадим собственный метод отрисовки, здесь в примере, например, `renderPaginator()`: ```php .{file:PollControl.php} public function renderPaginator(): void @@ -121,69 +121,69 @@ public function renderPaginator(): void } ``` -А в шаблоне мы затем вызываем его с помощью: +А в шаблоне мы затем вызовем его с помощью: ```latte {control poll:paginator} ``` -Для лучшего понимания полезно знать, как тег компилируется в PHP-код. +Для лучшего понимания полезно знать, как этот тег переводится в PHP. ```latte {control poll} {control poll:paginator 123, 'hello'} ``` -Это компилируется в: +переводится как: ```php $control->getComponent('poll')->render(); $control->getComponent('poll')->renderPaginator(123, 'hello'); ``` -Метод `getComponent()` возвращает компонент `poll` и затем для него вызывается метод `render()` или `renderPaginator()` соответственно. +Метод `getComponent()` возвращает компонент `poll`, и над этим компонентом вызывается метод `render()`, соответственно `renderPaginator()`, если в теге после двоеточия указан другой способ рендеринга. .[caution] -Если где-либо в части параметров используется **`=>`**, все параметры будут обернуты в массив и переданы в качестве первого аргумента: +Внимание, если где-либо в параметрах появится **`=>`**, все параметры будут упакованы в массив и переданы как первый аргумент: ```latte -{control poll, id => 123, message => 'hello'} +{control poll, id: 123, message: 'hello'} ``` -компилируется в: +переводится как: ```php $control->getComponent('poll')->render(['id' => 123, 'message' => 'hello']); ``` -Рендеринг вложенного компонента: +Отрисовка суб-компонента: ```latte {control cartControl-someForm} ``` -компилируется в: +переводится как: ```php $control->getComponent("cartControl-someForm")->render(); ``` -Компоненты, как и презентеры, автоматически передают шаблонам несколько полезных переменных: +Компоненты, так же как и презентеры, автоматически передают в шаблоны несколько полезных переменных: -- `$basePath` — абсолютный URL путь к корневому каталогу (например, `/CD-collection`). -- `$baseUrl` — абсолютный URL к корневому каталогу (например, `http://localhost/CD-collection`) -- `$user` — это объект, [представляющий пользователя |security:authentication]. +- `$basePath` — абсолютный URL-путь к корневому каталогу (например, `/eshop`) +- `$baseUrl` — абсолютный URL к корневому каталогу (например, `http://localhost/eshop`) +- `$user` — объект, [представляющий пользователя |security:authentication] - `$presenter` — текущий презентер - `$control` — текущий компонент -- `$flashes` список [сообщений |#flash-сообщений], отправленных методом `flashMessage()`. +- `$flashes` — массив [сообщений |#Flash-сообщения], отправленных функцией `flashMessage()` -Сигнал .[#toc-signal] -===================== +Сигнал +====== -Мы уже знаем, что навигация в приложении Nette состоит из ссылок или перенаправления на пары `Presenter:action`. Но что если мы просто хотим выполнить действие на **текущей странице**? Например, изменить порядок сортировки столбцов в таблице; удалить элемент; переключить режим light/dark; отправить форму; проголосовать в опросе; и т. д. +Мы уже знаем, что навигация в приложении Nette заключается в ссылках или перенаправлениях на пары `Presenter:action`. Но что, если мы просто хотим выполнить действие на **текущей странице**? Например, изменить сортировку столбцов в таблице; удалить элемент; переключить светлый/темный режим; отправить форму; проголосовать в опросе; и т. д. -Такой тип запроса называется сигналом. И как действия вызывают методы `action()` или `render()`, сигналы вызывают методы `handle()`. В то время как понятие действия (или просмотра) относится только к презентерам, сигналы относятся ко всем компонентам. А значит и к презентерам, потому что `UI\Presenter` является потомком `UI\Control`. +Этот тип запросов называется сигналами. И подобно тому, как действия вызывают методы `action()` или `render()`, сигналы вызывают методы `handle()`. В то время как понятие действия (или view) связано исключительно с презентерами, сигналы относятся ко всем компонентам. И, следовательно, и к презентерам, потому что `UI\Presenter` является потомком `UI\Control`. ```php public function handleClick(int $x, int $y): void @@ -192,36 +192,36 @@ public function handleClick(int $x, int $y): void } ``` -Ссылка, вызывающая сигнал, создается обычным способом, т. е. в шаблоне атрибутом `n:href` или тегом `{link}`, в коде методом `link()`. Подробнее в главе [Создание ссылок URL |creating-links#Links-to-Signal]. +Ссылку, которая вызывает сигнал, мы создаем обычным способом, то есть в шаблоне атрибутом `n:href` или тегом `{link}`, в коде методом `link()`. Подробнее в главе [Создание URL-ссылок |creating-links#Ссылки на сигнал]. ```latte -нажмите сюда +нажмите здесь ``` -Сигнал всегда вызывается на текущем презентере и представлении, поэтому невозможно связать сигнал с другим презентером/действием. +Сигнал всегда вызывается на текущем презентере и action, невозможно вызвать его на другом презентере или другом action. -Таким образом, сигнал вызывает перезагрузку страницы точно так же, как и в исходном запросе, только дополнительно он вызывает метод обработки сигнала с соответствующими параметрами. Если метод не существует, выбрасывается исключение [api:Nette\Application\UI\BadSignalException], которое отображается пользователю в виде страницы ошибки 403 Forbidden. +Таким образом, сигнал вызывает перезагрузку страницы точно так же, как при первоначальном запросе, только дополнительно вызывает метод обработки сигнала с соответствующими параметрами. Если метод не существует, выбрасывается исключение [api:Nette\Application\UI\BadSignalException], которое отображается пользователю как страница ошибки 403 Forbidden. -Сниппеты и AJAX .[#toc-snippets-and-ajax] -========================================= +Сниппеты и AJAX +=============== -Сигналы могут немного напомнить вам AJAX: обработчики, которые вызываются на текущей странице. И вы правы, сигналы действительно часто вызываются с помощью AJAX, и тогда мы передаем браузеру только измененные части страницы. Они называются сниппетами. Более подробную информацию можно найти на [странице об AJAX |ajax]. +Сигналы могут немного напомнить вам AJAX: обработчики, которые вызываются на текущей странице. И вы правы, сигналы действительно часто вызываются с помощью AJAX, и затем мы передаем в браузер только измененные части страницы. То есть так называемые сниппеты. Дополнительную информацию можно найти на [странице, посвященной AJAX |ajax]. -Флэш-сообщения .[#toc-flash-messages] -===================================== +Flash-сообщения +=============== -Компонент имеет собственное хранилище флэш-сообщений, не зависящее от презентера. Это сообщения, которые, например, информируют о результате операции. Важной особенностью флэш-сообщений является то, что они доступны в шаблоне даже после перенаправления. Даже после отображения они будут оставаться живыми ещё 30 секунд — например, на случай, если пользователь непреднамеренно обновит страницу — сообщение не будет потеряно. +Компонент имеет собственное хранилище flash-сообщений, независимое от презентера. Это сообщения, которые, например, информируют о результате операции. Важной особенностью flash-сообщений является то, что они доступны в шаблоне даже после перенаправления. Даже после отображения они остаются активными еще 30 секунд — например, на случай, если из-за ошибки передачи пользователь обновит страницу — сообщение не исчезнет сразу. -Отправка осуществляется методом [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Первым параметром является текст сообщения или объект `stdClass`, представляющий сообщение. Необязательный второй параметр — это его тип (ошибка, предупреждение, информация и т. д.). Метод `flashMessage()` возвращает экземпляр flash-сообщения как объект stdClass, которому можно передать информацию. +Отправку обеспечивает метод [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Первым параметром является текст сообщения или объект `stdClass`, представляющий сообщение. Необязательным вторым параметром является его тип (error, warning, info и т. д.). Метод `flashMessage()` возвращает экземпляр flash-сообщения как объект `stdClass`, к которому можно добавлять дополнительную информацию. ```php -$this->flashMessage('Элемент был удалён.'); -$this->redirect(/* ... */); // делаем редирект +$this->flashMessage('Элемент был удален.'); +$this->redirect(/* ... */); // и перенаправляем ``` -В шаблоне эти сообщения доступны в переменной `$flashes` как объекты `stdClass`, которые содержат свойства `message` (текст сообщения), `type` (тип сообщения) и могут содержать уже упомянутую информацию о пользователе. Мы отображаем их следующим образом: +В шаблоне эти сообщения доступны в переменной `$flashes` как объекты `stdClass`, которые содержат свойства `message` (текст сообщения), `type` (тип сообщения) и могут содержать уже упомянутую пользовательскую информацию. Отрисуем их, например, так: ```latte {foreach $flashes as $flash} @@ -230,44 +230,66 @@ $this->redirect(/* ... */); // делаем редирект ``` -Постоянные параметры .[#toc-persistent-parameters] -================================================== +Перенаправление после сигнала +============================= -Постоянные параметры используются для сохранения состояния компонентов между различными запросами. Их значение остается неизменным даже после нажатия на ссылку. В отличие от данных сессии, они передаются в URL. И они передаются автоматически, включая ссылки, созданные в других компонентах на той же странице. +После обработки сигнала компонента часто следует перенаправление. Это похожая ситуация, как с формами — после их отправки мы также перенаправляем, чтобы при обновлении страницы в браузере не произошло повторной отправки данных. -Например, у вас есть компонент пейджинга контента. На странице может быть несколько таких компонентов. И вы хотите, чтобы при нажатии на ссылку все компоненты оставались на текущей странице. Поэтому мы делаем номер страницы (`page`) постоянным параметром. +```php +$this->redirect('this') // перенаправляет на текущий презентер и action +``` + +Поскольку компонент является повторно используемым элементом и обычно не должен иметь прямой связи с конкретными презентерами, методы `redirect()` и `link()` автоматически интерпретируют параметр как сигнал компонента: + +```php +$this->redirect('click') // перенаправляет на сигнал 'click' того же компонента +``` + +Если вам нужно перенаправить на другой презентер или действие, вы можете сделать это через презентер: + +```php +$this->getPresenter()->redirect('Product:show'); // перенаправляет на другой презентер/action +``` + + +Персистентные параметры +======================= + +Персистентные параметры служат для поддержания состояния в компонентах между различными запросами. Их значение остается неизменным даже после нажатия на ссылку. В отличие от данных в сессии, они передаются в URL. И это происходит полностью автоматически, включая ссылки, созданные в других компонентах на той же странице. + +Например, у вас есть компонент для постраничной навигации контента. Таких компонентов на странице может быть несколько. И мы хотим, чтобы после нажатия на ссылку все компоненты остались на своей текущей странице. Поэтому мы сделаем номер страницы (`page`) персистентным параметром. -Создать постоянный параметр в Nette очень просто. Просто создайте публичное свойство и пометьте его атрибутом: (ранее использовалось `/** @persistent */` ). +Создание персистентного параметра в Nette невероятно просто. Достаточно создать публичное свойство и пометить его атрибутом: (ранее использовалось `/** @persistent */`) ```php -use Nette\Application\Attributes\Persistent; // эта строка важна +use Nette\Application\Attributes\Persistent; // эта строка важна class PaginatingControl extends Control { #[Persistent] - public int $page = 1; // должны быть публичными + public int $page = 1; // должно быть public } ``` -Мы рекомендуем указывать тип данных (например, `int`) вместе со свойством, а также можно указать значение по умолчанию. Значения параметров могут быть [проверены |#Validation of Persistent Parameters]. +У свойства рекомендуется указывать тип данных (например, `int`), и вы можете указать значение по умолчанию. Значения параметров можно [валидировать |#Валидация персистентных параметров]. -Значение постоянного параметра можно изменить при создании ссылки: +При создании ссылки можно изменить значение персистентного параметра: ```latte -next +следующая ``` -Или его можно *сбросить*, т.е. удалить из URL. Тогда он примет значение по умолчанию: +Или его можно *сбросить*, то есть удалить из URL. Тогда он примет свое значение по умолчанию: ```latte -reset +сбросить ``` -Постоянные компоненты .[#toc-persistent-components] -=================================================== +Персистентные компоненты +======================== -Постоянными могут быть не только параметры, но и компоненты. Их постоянные параметры также передаются между различными действиями или между различными презентерами. Мы помечаем постоянные компоненты этой аннотацией для класса презентера. Например, здесь мы помечаем компоненты `calendar` и `poll` следующим образом: +Не только параметры, но и компоненты могут быть персистентными. У такого компонента его персистентные параметры передаются и между различными действиями презентера, или между несколькими презентерами. Персистентные компоненты помечаются аннотацией у класса презентера. Например, так мы пометим компоненты `calendar` и `poll`: ```php /** @@ -278,9 +300,9 @@ class DefaultPresenter extends Nette\Application\UI\Presenter } ``` -Вам не нужно помечать подкомпоненты как постоянные, они становятся постоянными автоматически. +Подкомпоненты внутри этих компонентов помечать не нужно, они также станут персистентными. -В PHP 8 вы также можете использовать атрибуты для маркировки постоянных компонентов: +В PHP 8 вы можете использовать атрибуты для обозначения персистентных компонентов: ```php use Nette\Application\Attributes\Persistent; @@ -292,35 +314,35 @@ class DefaultPresenter extends Nette\Application\UI\Presenter ``` -Компоненты с зависимостями .[#toc-components-with-dependencies] -=============================================================== +Компоненты с зависимостями +========================== -Как создать компоненты с зависимостями, не "запутав" ведущих, которые будут их использовать? Благодаря продуманным возможностям DI-контейнера в Nette, как и при использовании традиционных сервисов, мы можем оставить большую часть работы фреймворку. +Как создавать компоненты с зависимостями, не «засоряя» презентеры, которые их будут использовать? Благодаря умным свойствам DI-контейнера в Nette, так же как при использовании классических сервисов, можно оставить большую часть работы фреймворку. -Возьмем в качестве примера компонент, имеющий зависимость от сервиса `PollFacade`: +Возьмем в качестве примера компонент, который имеет зависимость от сервиса `PollFacade`: ```php class PollControl extends Control { public function __construct( - private int $id, // Id опроса, для которого создается компонент + private int $id, // Id опроса, для которого мы создаем компонент private PollFacade $facade, ) { } public function handleVote(int $voteId): void { - $this->facade->vote($id, $voteId); + $this->facade->vote($this->id, $voteId); // ... } } ``` -Если бы мы писали классический сервис, то беспокоиться было бы не о чем. Контейнер DI незаметно позаботился бы о передаче всех зависимостей. Но мы обычно работаем с компонентами, создавая их новый экземпляр непосредственно в презентере в [#factory methods] `createComponent...()`. Но передача всех зависимостей всех компонентов в презентер, чтобы затем передать их компонентам, громоздка. И количество написанного кода... +Если бы мы писали классический сервис, проблем бы не было. О передаче всех зависимостей невидимо позаботился бы DI-контейнер. Однако с компонентами мы обычно обращаемся так, что создаем их новый экземпляр прямо в презентере в [фабричных методах |#Фабричные методы] `createComponent…()`. Но передавать все зависимости всех компонентов в презентер, чтобы затем передать их компонентам, громоздко. И сколько написанного кода… -Логичный вопрос: почему бы нам просто не зарегистрировать компонент как классический сервис, передать его ведущему, а затем вернуть его в методе `createComponent...()`? Но такой подход неуместен, поскольку мы хотим иметь возможность создавать компонент многократно. +Логичный вопрос: почему бы просто не зарегистрировать компонент как классический сервис, не передать его в презентер и затем в методе `createComponent…()` не возвращать? Такой подход, однако, неуместен, потому что мы хотим иметь возможность создавать компонент даже несколько раз. -Правильное решение - написать фабрику для компонента, т.е. класс, который создает компонент за нас: +Правильным решением является написание для компонента фабрики, то есть класса, который нам создаст компонент: ```php class PollControlFactory @@ -337,17 +359,17 @@ class PollControlFactory } ``` -Теперь мы регистрируем наш сервис в DI-контейнере для конфигурации: +Так мы зарегистрируем фабрику в нашем контейнере в конфигурации: ```neon services: - PollControlFactory ``` -Наконец, мы будем использовать эту фабрику в нашем презентере: +и, наконец, используем ее в нашем презентере: ```php -class PollPresenter extends Nette\UI\Application\Presenter +class PollPresenter extends Nette\Application\UI\Presenter { public function __construct( private PollControlFactory $pollControlFactory, @@ -356,13 +378,13 @@ class PollPresenter extends Nette\UI\Application\Presenter protected function createComponentPollControl(): PollControl { - $pollId = 1; // мы можем передать наш параметр + $pollId = 1; // можем передать наш параметр return $this->pollControlFactory->create($pollId); } } ``` -К счастью, Nette может генерировать эти простые фабрики, поэтому мы можем написать просто интерфейс этой фабрики, а DI-контейнер сгенерирует реализацию: +Замечательно то, что Nette DI умеет [генерировать |dependency-injection:factory] такие простые фабрики, поэтому вместо всего ее кода достаточно написать только ее интерфейс: ```php interface PollControlFactory @@ -371,21 +393,21 @@ interface PollControlFactory } ``` -Вот и всё. Nette внутренне реализует этот интерфейс и передает его нашему презентеру, где мы можем его использовать. Он также магическим образом передает наш параметр `$id` и экземпляр класса `PollFacade` в наш компонент. +И это все. Nette внутренне реализует этот интерфейс и передаст его в презентер, где мы уже можем его использовать. Он волшебным образом добавит в наш компонент и параметр `$id`, и экземпляр класса `PollFacade`. -Компоненты в глубину .[#toc-components-in-depth] -================================================ +Компоненты в глубину +==================== -Компоненты в Nette Application - это многократно используемые части веб-приложения, которые мы встраиваем в страницы, о чем и пойдет речь в этой главе. Каковы возможности такого компонента? +Компоненты в Nette Application представляют собой повторно используемые части веб-приложения, которые мы вставляем на страницы и которым, собственно, посвящена вся эта глава. Какие именно возможности имеет такой компонент? -1) он может быть отображен в шаблоне -2) он знает, какую часть себя отображать во время [AJAX-запроса |ajax#invalidation] (сниппеты) -3) он имеет возможность хранить свое состояние в URL (постоянные параметры) -4) имеет возможность реагировать на действия пользователя (сигналы) -5) создает иерархическую структуру (где корнем является ведущий) +1) он может быть отрисован в шаблоне +2) он знает, [какую свою часть |ajax#Сниппеты] нужно отрисовать при AJAX-запросе (сниппеты) +3) он имеет возможность сохранять свое состояние в URL (персистентные параметры) +4) он имеет возможность реагировать на действия пользователя (сигналы) +5) он создает иерархическую структуру (где корнем является презентер) -Каждая из этих функций обрабатывается одним из классов линии наследования. Рендеринг (1 + 2) обрабатывается [api:Nette\Application\UI\Control], включение в [жизненный цикл |presenters#life-cycle-of-presenter] (3, 4) - классом [api:Nette\Application\UI\Component], а создание иерархической структуры (5) - классами [Container и Component |component-model:]. +Каждую из этих функций обеспечивает один из классов иерархии наследования. За отрисовку (1 + 2) отвечает [api:Nette\Application\UI\Control], за включение в [жизненный цикл |presenters#Жизненный цикл презентера] (3, 4) — класс [api:Nette\Application\UI\Component], а за создание иерархической структуры (5) — классы [Container и Component |component-model:]. ``` Nette\ComponentModel\Component { IComponent } @@ -400,18 +422,18 @@ Nette\ComponentModel\Component { IComponent } ``` -Жизненный цикл компонента .[#toc-life-cycle-of-component] ---------------------------------------------------------- +Жизненный цикл компонента +------------------------- [* lifecycle-component.svg *] *** *Жизненный цикл компонента* .<> -Валидация постоянных параметров .[#toc-validation-of-persistent-parameters] ---------------------------------------------------------------------------- +Валидация персистентных параметров +---------------------------------- -Значения [постоянных параметров |#persistent parameters], полученных из URL, записываются в свойства методом `loadState()`. Он также проверяет, соответствует ли тип данных, указанный для свойства, в противном случае выдается ошибка 404 и страница не отображается. +Значения [персистентных параметров |#Персистентные параметры], полученные из URL, записываются в свойства методом `loadState()`. Он также проверяет, соответствует ли тип данных, указанный у свойства, иначе отвечает ошибкой 404, и страница не отображается. -Никогда не доверяйте слепо постоянным параметрам, так как они могут быть легко перезаписаны пользователем в URL. Например, так мы проверяем, больше ли номер страницы `$this->page`, чем 0. Хороший способ сделать это - переопределить метод `loadState()`, упомянутый выше: +Никогда слепо не доверяйте персистентным параметрам, потому что они могут быть легко изменены пользователем в URL. Так, например, мы проверим, что номер страницы `$this->page` больше 0. Подходящий способ — переопределить упомянутый метод `loadState()`: ```php class PaginatingControl extends Control @@ -422,7 +444,7 @@ class PaginatingControl extends Control public function loadState(array $params): void { parent::loadState($params); // здесь устанавливается $this->page - // после проверки значения пользователем: + // следует собственная проверка значения: if ($this->page < 1) { $this->error(); } @@ -430,27 +452,27 @@ class PaginatingControl extends Control } ``` -Противоположный процесс, то есть сбор значений из постоянных свойств, обрабатывается методом `saveState()`. +Обратный процесс, то есть сбор значений из персистентных свойств, отвечает метод `saveState()`. -Сигналы в глубину .[#toc-signals-in-depth] ------------------------------------------- +Сигналы в глубину +----------------- -Сигнал вызывает перезагрузку страницы подобно исходному запросу (за исключением AJAX) и вызывает метод `signalReceived($signal)`, реализация которого по умолчанию в классе `Nette\Application\UI\Component` пытается вызвать метод, состоящий из слов `handle{Signal}`. Дальнейшая обработка зависит от данного объекта. Объекты, являющиеся потомками `Component` (т. е. `Control` и `Presenter`), пытаются вызвать `handle{Signal}` с соответствующими параметрами. +Сигнал вызывает перезагрузку страницы точно так же, как при первоначальном запросе (кроме случая, когда он вызывается AJAX-ом), и вызывает метод `signalReceived($signal)`, чья реализация по умолчанию в классе `Nette\Application\UI\Component` пытается вызвать метод, составленный из слов `handle{signal}`. Дальнейшая обработка зависит от данного объекта. Объекты, наследующие от `Component` (т. е. `Control` и `Presenter`), реагируют так, что пытаются вызвать метод `handle{signal}` с соответствующими параметрами. -Другими словами: берется определение метода `handle{Signal}` и все параметры, которые были получены в запросе, сопоставляются с параметрами метода. Это означает, что параметр `id` из URL сопоставляется с параметром метода `$id`, `something` — с `$something` и так далее. А если метод не существует, то метод `signalReceived` выбрасывает [исключение |api:Nette\Application\UI\BadSignalException]. +Другими словами: берется определение функции `handle{signal}` и все параметры, пришедшие с запросом, и к аргументам по имени подставляются параметры из URL, и делается попытка вызвать данный метод. Например, в качестве параметра `$id` передается значение из параметра `id` в URL, в качестве `$something` передается `something` из URL и т. д. И если метод не существует, метод `signalReceived` выбрасывает [исключение |api:Nette\Application\UI\BadSignalException]. -Сигнал может быть получен любым компонентом, ведущим объектом, реализующим интерфейс `SignalReceiver`, если он подключен к дереву компонентов. +Сигнал может принимать любой компонент, презентер или объект, который реализует интерфейс `SignalReceiver` и подключен к дереву компонентов. -Основными получателями сигналов являются презентеры и визуальные компоненты, расширяющие `Control`. Сигнал — это знак для объекта, что он должен что-то сделать — опрос засчитывает голос пользователя, ящик с новостями должен развернуться, форма была отправлена и должна обработать данные и так далее. +Основными получателями сигналов будут `Presenters` и визуальные компоненты, наследующие от `Control`. Сигнал должен служить знаком для объекта, что он должен что-то сделать — опрос должен засчитать голос пользователя, блок с новостями должен развернуться и показать в два раза больше новостей, форма была отправлена и должна обработать данные и т. п. -URL для сигнала создается с помощью метода [Component::link() |api:Nette\Application\UI\Component::link()]. В качестве параметра `$destination` передается строка `{signal}!`, а в качестве `$args` — массив аргументов, которые мы хотим передать обработчику сигнала. Параметры сигнала привязываются к URL текущего презентера/представления. **Параметр `?do` в URL определяет вызываемый сигнал.** +URL для сигнала создаем с помощью метода [Component::link() |api:Nette\Application\UI\Component::link()]. В качестве параметра `$destination` передаем строку `{signal}!` и в качестве `$args` — массив аргументов, которые мы хотим передать сигналу. Сигнал всегда вызывается на текущем презентере и action с текущими параметрами, параметры сигнала просто добавляются. Кроме того, в самом начале добавляется **параметр `?do`, который определяет сигнал**. -Его формат — `{signal}` или `{signalReceiver}-{signal}`. `{signalReceiver}` — это имя компонента в презентере. Именно поэтому дефис (неточно тире) не может присутствовать в имени компонентов — он используется для разделения имени компонента и сигнала, но можно составить несколько компонентов. +Его формат — либо `{signal}`, либо `{signalReceiver}-{signal}`. `{signalReceiver}` — это имя компонента в презентере. Поэтому в имени компонента не может быть дефиса — он используется для разделения имени компонента и сигнала, однако таким образом можно вложить несколько компонентов. -Метод [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] проверяет, является ли компонент (первый аргумент) приемником сигнала (второй аргумент). Второй аргумент может быть опущен — тогда выясняется, является ли компонент приемником какого-либо сигнала. Если второй параметр равен `true`, то проверяется, является ли компонент или его потомки приемниками сигнала. +Метод [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] проверяет, является ли компонент (первый аргумент) получателем сигнала (второй аргумент). Второй аргумент можно опустить — тогда проверяется, является ли компонент получателем любого сигнала. В качестве второго параметра можно указать `true`, чтобы проверить, является ли получателем не только указанный компонент, но и любой его потомок. -В любой фазе, предшествующей `handle{Signal}`, сигнал можно выполнить вручную, вызвав метод [processSignal()|api:Nette\Application\UI\Presenter::processSignal()], который берет на себя ответственность за выполнение сигнала. Принимает компонент-приемник (если он не установлен, то это сам презентер) и посылает ему сигнал. +На любом этапе, предшествующем `handle{signal}`, мы можем выполнить сигнал вручную, вызвав метод [processSignal()|api:Nette\Application\UI\Presenter::processSignal()], который берет на себя обработку сигнала — берет компонент, который определен как получатель сигнала (если получатель сигнала не указан, это сам презентер), и отправляет ему сигнал. Пример: @@ -460,4 +482,4 @@ if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, ' } ``` -Сигнал выполняется преждевременно и больше не будет вызван. +Таким образом, сигнал выполняется преждевременно и больше не будет вызываться. diff --git a/application/ru/configuration.texy b/application/ru/configuration.texy index 1f4e950d11..4f2aa91381 100644 --- a/application/ru/configuration.texy +++ b/application/ru/configuration.texy @@ -1,50 +1,63 @@ -Настройка приложения -******************** +Конфигурация приложений +*********************** .[perex] -Обзор вариантов конфигурации приложения Nette. +Обзор опций конфигурации для приложений Nette. -Приложение .[#toc-application] -============================== +Application +=========== ```neon application: - # отображает вкладку "Nette Application" на синем экране Tracy? + # отображать панель "Nette Application" в Tracy BlueScreen? debugger: ... # (bool) по умолчанию true - # будет ли вызываться презентер ошибок при ошибке? - catchExceptions: ... # (bool) по умолчанию true на «боевом» сервере + # будет ли при ошибке вызываться error-presenter? + # имеет эффект только в режиме разработки + catchExceptions: ... # (bool) по умолчанию true - # имя презентера ошибок - errorPresenter: Error # (string) по умолчанию 'Nette:Error' + # имя error-presenter + errorPresenter: Error # (string|array) по умолчанию 'Nette:Error' - # определяет правила для преобразования имени ведущего в класс + # определяет псевдонимы для презентеров и действий + aliases: ... + + # определяет правила для перевода имени презентера в класс mapping: ... - # выдают ли плохие ссылки предупреждения? - # имеет силу только в режиме разработки + # неверные ссылки не генерируют предупреждения? + # имеет эффект только в режиме разработки silentLinks: ... # (bool) по умолчанию false ``` -Поскольку в режиме разработки презентеры ошибок по умолчанию не вызываются, а ошибки отображаются Tracy, изменение значения `catchExceptions` на `true` помогает проверить корректность работы презентеров ошибок во время разработки. +Начиная с версии `nette/application` 3.2, можно определить пару error-presenter'ов: -Опция `silentLinks` определяет, как Nette ведет себя в режиме разработчика, когда генерация ссылок не удается (например, из-за отсутствия презентера и т. д.). Значение по умолчанию `false` означает, что Nette запускает `E_USER_WARNING`. Установка значения `true` подавляет это сообщение об ошибке. В производственной среде всегда вызывается `E_USER_WARNING`. Мы также можем повлиять на это поведение, установив переменную презентера [$invalidLinkMode |creating-links#Invalid-Links]. +```neon +application: + errorPresenter: + 4xx: Error4xx # для исключения Nette\Application\BadRequestException + 5xx: Error5xx # для остальных исключений +``` -Карта [mapping определяет правила |modules#Mapping], по которым имя класса выводится из имени ведущего. +Опция `silentLinks` определяет, как Nette поведет себя в режиме разработки, если генерация ссылки не удалась (например, потому что презентер не существует и т. д.). Значение по умолчанию `false` означает, что Nette выбросит ошибку `E_USER_WARNING`. Установка на `true` подавит это сообщение об ошибке. В production-среде `E_USER_WARNING` всегда будет вызываться. Это поведение также можно контролировать, установив переменную презентера [$invalidLinkMode |creating-links#Недействительные ссылки]. +[Псевдонимы упрощают создание ссылок |creating-links#Псевдонимы] на часто используемые презентеры. -Авторегистрация презентеров .[#toc-automatic-registration-of-presenters] ------------------------------------------------------------------------- +[Маппинг определяет правила |directory-structure#Маппинг презентеров], по которым из имени презентера выводится имя класса. -Nette автоматически добавляет презентеры как сервисы в контейнер DI, что значительно ускоряет их создание. Как Nette узнает презентеры, можно настроить: + +Автоматическая регистрация презентеров +-------------------------------------- + +Nette автоматически добавляет презентеры как сервисы в DI-контейнер, что значительно ускоряет их создание. Как Nette находит презентеры, можно настроить: ```neon application: - # для поиска презентеров в карте классов Composer? + # искать презентеры в Composer class map? scanComposer: ... # (bool) по умолчанию true - # маска, которая должна соответствовать классу и имени файла + # маска, которой должно соответствовать имя класса и файла scanFilter: ... # (string) по умолчанию '*Presenter' # в каких каталогах искать презентеры? @@ -52,7 +65,7 @@ application: - %vendorDir%/mymodule ``` -Каталоги, перечисленные в `scanDirs`, не отменяют значение по умолчанию `%appDir%`, а дополняют его, поэтому `scanDirs` будет содержать оба пути `%appDir%` и `%vendorDir%/mymodule`. Чтобы перезаписать каталог по умолчанию, мы используем [восклицательный знак |dependency-injection:configuration#Merging]: +Каталоги, указанные в `scanDirs`, не перезаписывают значение по умолчанию `%appDir%`, а дополняют его, `scanDirs` таким образом будет содержать оба пути `%appDir%` и `%vendorDir%/mymodule`. Если мы хотим исключить каталог по умолчанию, используем [восклицательный знак |dependency-injection:configuration#Слияние], который перезапишет значение: ```neon application: @@ -60,64 +73,73 @@ application: - %vendorDir%/mymodule ``` -Сканирование каталога можно отключить, задав значение `false`. Мы не рекомендуем полностью подавлять автоматическое добавление презентеров, иначе производительность приложения снизится. +Сканирование каталогов можно отключить, указав значение false. Не рекомендуем полностью подавлять автоматическое добавление презентеров, так как это приведет к снижению производительности приложения. -Latte -===== +Шаблоны Latte +============= -Эта настройка глобально влияет на поведение Latte в компонентах и презентерах. +С помощью этой настройки можно глобально повлиять на поведение Latte в компонентах и презентерах. ```neon latte: - # отображает вкладку Latte на панели Tracy для основного шаблона (true) или для всех компонентов (all)? + # отображать панель Latte в Tracy Bar для главного шаблона (true) или всех компонентов (all)? debugger: ... # (true|false|'all') по умолчанию true - # генерирует шаблоны с declare(strict_types=1) + # генерирует шаблоны с заголовком declare(strict_types=1) strictTypes: ... # (bool) по умолчанию false - # класс $this->template + # включает режим [строгого парсера |latte:develop#strict-mode] + strictParsing: ... # (bool) по умолчанию false + + # активирует [проверку сгенерированного кода |latte:develop#checking-generated-code] + phpLinter: ... # (string) по умолчанию null + + # устанавливает локаль + locale: ru_RU # (string) по умолчанию null + + # класс объекта $this->template templateClass: App\MyTemplateClass # по умолчанию Nette\Bridges\ApplicationLatte\DefaultTemplate ``` -Если вы используете Latte версии 3, вы можете добавить новое [расширение |latte:creating-extension], используя: +Если вы используете Latte версии 3, вы можете добавлять новые [расширения |latte:extending-latte#Latte Extension] с помощью: ```neon latte: - расширения: - - Latte\Essential\TranslatorExtension + extensions: + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -/--comment - - - - +Если вы используете Latte версии 2, вы можете регистрировать новые теги, указав имя класса или ссылку на сервис. По умолчанию вызывается метод `install()`, но это можно изменить, указав имя другого метода: +```neon +latte: + # регистрация пользовательских тегов Latte + macros: + - App\MyLatteMacros::register # статический метод, classname или callable + - @App\MyLatteMacrosFactory # сервис с методом install() + - @App\MyLatteMacrosFactory::register # сервис с методом register() + +services: + - App\MyLatteMacrosFactory +``` - - - - -\-- - - -Маршрутизация .[#toc-routing] -============================= +Маршрутизация +============= Основные настройки: ```neon routing: - # отображает вкладку Routing на панели Tracy? + # отображать панель маршрутизации в Tracy Bar? debugger: ... # (bool) по умолчанию true - # осуществлять сериализацию маршрутов в DI-контейнере? + # сериализует маршрутизатор в DI-контейнер cache: ... # (bool) по умолчанию false ``` -Маршруты обычно определяются в классе RouterFactory. Альтернативно, простые правила маршрутизации можно определить в конфигурации с помощью пар `маска: действие`: +Маршрутизацию обычно определяем в классе [RouterFactory |routing#Коллекция маршрутов]. Альтернативно, маршруты можно определить также в конфигурации с помощью пар `маска: действие`, но этот способ не предлагает такой широкой вариативности в настройке: ```neon routing: @@ -127,28 +149,43 @@ routing: ``` -Константы .[#toc-constants] -=========================== +Константы +========= -Создание констант PHP: +Создание PHP-констант. ```neon constants: Foobar: 'baz' ``` -Константа `Foobar` будет создана после запуска. +После запуска приложения будет создана константа `Foobar`. .[note] -Константы не должны служить в качестве глобально доступных переменных. Для передачи значений объектам используйте [dependency injection |dependency-injection:passing-dependencies]. +Константы не должны служить некими глобально доступными переменными. Для передачи значений в объекты используйте [внедрение зависимостей |dependency-injection:passing-dependencies]. PHP === -Вы можете устанавливать директивы PHP. Обзор всех директив можно найти на сайте [php.net |https://www.php.net/manual/ru/ini.list.php]. +Настройка директив PHP. Обзор всех директив можно найти на [php.net |https://www.php.net/manual/en/ini.list.php]. ```neon php: date.timezone: Europe/Prague ``` + + +Сервисы DI +========== + +Эти сервисы добавляются в DI-контейнер: + +| Имя | Тип | Описание +|---------------------------------------------------------- +| `application.application` | [api:Nette\Application\Application] | [запускающий все приложение |how-it-works#Nette Application] +| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] +| `application.presenterFactory` | [api:Nette\Application\PresenterFactory] | фабрика презентеров +| `application.###` | [api:Nette\Application\UI\Presenter] | отдельные презентеры +| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | фабрика объекта `Latte\Engine` +| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | фабрика для [`$this->template` |templates] diff --git a/application/ru/creating-links.texy b/application/ru/creating-links.texy index 3a4dbb4805..99e54aa1ff 100644 --- a/application/ru/creating-links.texy +++ b/application/ru/creating-links.texy @@ -3,89 +3,89 @@
    -Создавать ссылки в Nette так же просто, как тыкать пальцем. Просто наведите курсор, и система сделает всю работу за вас. Мы покажем: +Создавать ссылки в Nette просто, как указывать пальцем. Достаточно просто направить, и фреймворк уже сделает всю работу за вас. Мы покажем: -- как создавать ссылки в шаблонах и других местах -- как выделить ссылку на текущую страницу +- как создавать ссылки в шаблонах и в других местах +- как отличить ссылку на текущую страницу - что делать с недействительными ссылками
    -Благодаря [двунаправленной маршрутизации|routing], вам никогда не придется жестко кодировать URL приложения в шаблонах или коде, которые могут измениться позже или быть сложными для составления. Просто укажите презентера и действие в ссылке, передайте любые параметры, и фреймворк сам сгенерирует URL. Фактически, это очень похоже на вызов функции. Вам понравится. +Благодаря [двусторонней маршрутизации |routing] вам никогда не придется жестко прописывать URL-адреса вашего приложения в шаблонах или коде, которые могут позже измениться, или сложно их составлять. В ссылке достаточно указать презентер и действие, передать возможные параметры, и фреймворк сам сгенерирует URL. На самом деле, это очень похоже на вызов функции. Вам это понравится. -В шаблоне презентера .[#toc-in-the-presenter-template] -====================================================== +В шаблоне презентера +==================== Чаще всего мы создаем ссылки в шаблонах, и отличным помощником является атрибут `n:href`: ```latte -подробнее +деталь ``` -Обратите внимание, что вместо HTML-атрибута `href` мы использовали [n:attribute |latte:syntax#n:attributes] `n:href`. Его значением является не URL, как вы привыкли видеть в атрибуте `href`, а имя презентера и действие. +Обратите внимание, что вместо HTML-атрибута `href` мы использовали [n:атрибут |latte:syntax#n:атрибуты] `n:href`. Его значением является не URL, как это было бы в случае атрибута `href`, а имя презентера и действия. -Нажатие на ссылку, проще говоря, является чем-то вроде вызова метода `ProductPresenter::renderShow()`. И если в его сигнатуре есть параметры, мы можем вызвать его с аргументами: +Нажатие на ссылку, упрощенно говоря, похоже на вызов метода `ProductPresenter::renderShow()`. И если у него в сигнатуре есть параметры, мы можем вызвать его с аргументами: ```latte -подробнее +деталь продукта ``` -Также можно передавать именованные параметры. Следующая ссылка передает параметр `lang` со значением `en`: +Можно передавать и именованные параметры. Следующая ссылка передает параметр `lang` со значением `cs`: ```latte -подробнее +деталь продукта ``` -Если метод `ProductPresenter::renderShow()` не имеет `$lang` в своей сигнатуре, он может прочитать значение параметра, используя `$lang = $this->getParameter('lang')`. +Если метод `ProductPresenter::renderShow()` не имеет `$lang` в своей сигнатуре, он может получить значение параметра с помощью `$lang = $this->getParameter('lang')` или из [свойства |presenters#Параметры запроса]. -Если параметры хранятся в массиве, их можно расширить с помощью оператора `(expand)` (что-то вроде оператора `...` в PHP, но работает с ассоциативными массивами): +Если параметры хранятся в массиве, их можно развернуть с помощью оператора `...` (в Latte 2.x оператором `(expand)`): ```latte -{var $args = [$product->id, lang => en]} -подробнее +{var $args = [$product->id, lang => cs]} +деталь продукта ``` -Так называемые [постоянные параметры|presenters#Persistent-Parameters] также автоматически передаются в ссылках. +В ссылках также автоматически передаются так называемые [персистентные параметры |presenters#Персистентные параметры]. -Атрибут `n:href` очень удобен для HTML-тегов ``. Если мы хотим вывести ссылку в другом месте, например, в тексте, мы используем `{link}`: +Атрибут `n:href` очень удобен для HTML-тегов ``. Если мы хотим вывести ссылку в другом месте, например, в тексте, используем `{link}`: ```latte -URL: {link Home:default} +Адрес: {link Home:default} ``` -В коде .[#toc-in-the-code] -========================== +В коде +====== -Метод `link()` используется для создания ссылки в презентере: +Для создания ссылки в презентере служит метод `link()`: ```php $url = $this->link('Product:show', $product->id); ``` -Параметры также могут быть переданы в виде массива, в котором также могут быть указаны именованные параметры: +Параметры можно передать также с помощью массива, где можно указать и именованные параметры: ```php $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); ``` -Ссылки можно создавать и без презентера, используя [#LinkGenerator] и его метод `link()`. +Ссылки можно создавать и без презентера, для этого есть [#LinkGenerator] и его метод `link()`. -Ссылки на презентер .[#toc-links-to-presenter] -============================================== +Ссылки на презентер +=================== -Если целью ссылки является презентер и действие, она имеет такой синтаксис: +Если целью ссылки является презентер и действие, используется следующий синтаксис: ``` [//] [[[[:]module:]presenter:]action | this] [#fragment] ``` -Формат поддерживается всеми тегами Latte и всеми методами презентера, которые работают со ссылками, т. е. `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()`, а также [#LinkGenerator]. Поэтому, даже если в примерах используется `n:href`, здесь может быть любая из функций. +Формат поддерживается всеми тегами Latte и всеми методами презентера, работающими со ссылками, то есть `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()`, а также [#LinkGenerator]. Так что, хотя в примерах используется `n:href`, там могла бы быть любая из этих функций. -Поэтому основной формой является `Presenter:action`: +Основной формой является `Presenter:action`: ```latte главная страница @@ -97,42 +97,42 @@ $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); главная страница ``` -Если действие является `default`, мы можем опустить его, но двоеточие должно остаться: +Если целью является действие `default`, мы можем его опустить, но двоеточие должно остаться: ```latte главная страница ``` -Ссылки могут также указывать на другие [модули |modules]. Здесь ссылки различаются на относительные по отношению к подмодулям или абсолютные. Принцип аналогичен дисковым путям, только вместо косых черт стоят двоеточия. Предположим, что настоящий презентер является частью модуля `Front`, тогда мы напишем: +Ссылки также могут указывать на другие [модули |directory-structure#Презентеры и шаблоны]. Здесь ссылки делятся на относительные к вложенному подмодулю или абсолютные. Принцип аналогичен путям на диске, только вместо слешей используются двоеточия. Предположим, что текущий презентер является частью модуля `Front`, тогда запишем: ```latte ссылка на Front:Shop:Product:show ссылка на Admin:Product:show ``` -Особым случаем является [ссылка на себя|#Link-to-Current-Page]. Здесь мы напишем `this` в качестве цели. +Особым случаем является ссылка [на себя |#Ссылка на текущую страницу], когда в качестве цели указываем `this`. ```latte -refresh +обновить ``` -Мы можем ссылаться на определенную часть HTML-страницы через так называемый фрагмент после хэш-символа `#`: +Мы можем ссылаться на определенную часть страницы через так называемый фрагмент после символа решетки `#`: ```latte ссылка на Home:default и фрагмент #main ``` -Абсолютные пути .[#toc-absolute-paths] -====================================== +Абсолютные пути +=============== -Ссылки, генерируемые `link()` или `n:href`, всегда являются абсолютными путями (т.е. начинаются с `/`), но не абсолютными URL с протоколом и доменом, как `https://domain`. +Ссылки, генерируемые с помощью `link()` или `n:href`, всегда являются абсолютными путями (т. е. начинаются с символа `/`), но не абсолютными URL с протоколом и доменом, как `https://domain`. -Чтобы создать абсолютный URL, добавьте две косые черты в начало (например, `n:href="//Home:"`). Или вы можете переключить презентатор на генерацию только абсолютных ссылок, установив `$this->absoluteUrls = true`. +Для генерации абсолютного URL добавьте в начало два слеша (например, `n:href="//Home:"`). Или можно переключить презентер, чтобы он генерировал только абсолютные ссылки, установив `$this->absoluteUrls = true`. -Ссылка на текущую страницу .[#toc-link-to-current-page] -======================================================= +Ссылка на текущую страницу +========================== Цель `this` создаст ссылку на текущую страницу: @@ -140,7 +140,7 @@ $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); обновить ``` -При этом передаются все параметры, указанные в сигнатуре метода `render()` или `action()`. Таким образом, если мы находимся на страницах `Product:show` и `id:123`, ссылка на `this` также будет передавать этот параметр. +При этом передаются все параметры, указанные в сигнатуре метода `action()` или `render()`, если `action()` не определена. Так что если мы находимся на странице `Product:show` и `id: 123`, ссылка на `this` передаст и этот параметр. Конечно, можно указать параметры напрямую: @@ -148,92 +148,116 @@ $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); обновить ``` -Метод презентера `isLinkCurrent()` определяет, совпадает ли цель ссылки с текущей страницей. Это можно использовать, например, в шаблоне для разграничения ссылок и т. д. +Функция `isLinkCurrent()` проверяет, совпадает ли цель ссылки с текущей страницей. Это можно использовать, например, в шаблоне для выделения ссылок и т. п. -Параметры те же, что и для метода `link()`, но также можно использовать подстановочный знак `*` вместо конкретного действия, что означает любое действие презентера. +Параметры такие же, как у метода `link()`, но дополнительно можно вместо конкретного действия указать подстановочный знак `*`, который означает любое действие данного презентера. ```latte -{if !$presenter->isLinkCurrent('Admin:login')} - Войти +{if !isLinkCurrent('Admin:login')} + Войдите {/if} -
  • +
  • ...
  • ``` -Сокращенная форма может использоваться в сочетании с `n:href` в одном элементе: +В сочетании с `n:href` в одном элементе можно использовать сокращенную форму: ```latte -... +... ``` -Символ подстановки `*` заменяет только действие презентера, но не сам презентер. +Подстановочный знак `*` можно использовать только вместо действия, а не презентера. -Чтобы узнать, находимся ли мы в определенном модуле или его подмодуле, мы можем использовать метод `$presenter->isModuleCurrent(moduleName)`. +Для проверки, находимся ли мы в определенном модуле или его подмодуле, используем метод `isModuleCurrent(moduleName)`. ```latte -
  • +
  • ...
  • ``` -Ссылки на сигнал .[#toc-links-to-signal] -======================================== +Ссылки на сигнал +================ -Целью ссылки может быть не только презентер и действие, но и [сигнал |components#Signal] (они вызывают метод `handle()`). Синтаксис следующий: +Целью ссылки может быть не только презентер и действие, но и [сигнал |components#Сигнал] (вызывают метод `handle()`). Тогда синтаксис следующий: ``` [//] [sub-component:]signal! [#fragment] ``` -Поэтому сигнал выделяется восклицательным знаком: +Сигнал отличает восклицательный знак: ```latte -signal +сигнал ``` -Вы также можете создать ссылку на сигнал подкомпонента (или подсубкомпонента): +Можно создать и ссылку на сигнал подкомпонента (или под-подкомпонента): ```latte -signal +сигнал ``` -Ссылки на компонент .[#toc-links-in-component] -============================================== +Ссылки в компоненте +=================== -Поскольку [компоненты|components] — это отдельные многократно используемые единицы, которые не должны иметь никаких отношений с окружающими презентерами, ссылки работают немного по-другому. Атрибут Latte `n:href` и тег `{link}`, а также методы компонентов, такие как `link()` и другие, всегда рассматривают цель **как имя сигнала**. Поэтому нет необходимости использовать восклицательный знак: +Поскольку [компоненты |components] являются отдельными повторно используемыми единицами, которые не должны иметь никаких связей с окружающими презентерами, ссылки здесь работают немного иначе. Атрибут Latte `n:href` и тег `{link}`, а также методы компонента, такие как `link()` и другие, считают цель ссылки **всегда именем сигнала**. Поэтому не нужно даже указывать восклицательный знак: ```latte -сигнал, не действие +сигнал, а не действие ``` -Если мы хотим сделать ссылку на презентеры в шаблоне компонента, мы используем тег `{plink}`: +Если бы мы хотели в шаблоне компонента ссылаться на презентеры, мы бы использовали для этого тег `{plink}`: ```latte -главная страница +введение ``` -or in the code +или в коде ```php $this->getPresenter()->link('Home:default') ``` -Недействительные ссылки .[#toc-invalid-links] -============================================= +Псевдонимы .{data-version:v3.2.2} +================================= + +Иногда может быть полезно присвоить паре Presenter:action легко запоминающийся псевдоним. Например, главную страницу `Front:Home:default` назвать просто `home` или `Admin:Dashboard:default` как `admin`. + +Псевдонимы определяются в [конфигурации |configuration] под ключом `application › aliases`: + +```neon +application: + aliases: + home: Front:Home:default + admin: Admin:Dashboard:default + sign: Front:Sign:in +``` + +В ссылках они затем записываются с помощью символа @, например: + +```latte +администрирование +``` + +Они также поддерживаются во всех методах, работающих со ссылками, таких как `redirect()` и подобных. + + +Недействительные ссылки +======================= -Может случиться так, что мы создадим некорректную ссылку — либо потому, что она ссылается на несуществующий презентер, либо потому, что она передает больше параметров, чем целевой метод получает в своей сигнатуре, либо когда не может быть сгенерирован URL для целевого действия. Что делать с недействительными ссылками, определяется статической переменной `Presenter::$invalidLinkMode`. Она может иметь одно из этих значений (констант): +Может случиться, что мы создадим недействительную ссылку — либо потому, что она ведет на несуществующий презентер, либо потому, что передает больше параметров, чем принимает целевой метод в своей сигнатуре, либо когда для целевого действия невозможно сгенерировать URL. Как обращаться с недействительными ссылками, определяет статическая переменная `Presenter::$invalidLinkMode`. Она может принимать комбинацию следующих значений (констант): -- `Presenter::InvalidLinkSilent` — тихий режим, возвращает символ `#` в качестве URL-адреса -- `Presenter::InvalidLinkWarning` — будет выдано сообщение E_USER_WARNING -- `Presenter::InvalidLinkTextual` — визуальное предупреждение, текст ошибки отображается в ссылке -- `Presenter::InvalidLinkException` — будет выброшено исключение InvalidLinkException +- `Presenter::InvalidLinkSilent` — тихий режим, в качестве URL возвращается символ # +- `Presenter::InvalidLinkWarning` — выбрасывается предупреждение E_USER_WARNING, которое в режиме production будет залогировано, но не вызовет прерывания выполнения скрипта +- `Presenter::InvalidLinkTextual` — визуальное предупреждение, выводит ошибку прямо в ссылку +- `Presenter::InvalidLinkException` — выбрасывается исключение InvalidLinkException -По умолчанию в рабочем режиме используется параметр `InvalidLinkWarning`, а в режиме разработки — `InvalidLinkWarning | InvalidLinkTextual`. `InvalidLinkWarning` не убивает сценарий в рабочей среде, но предупреждение будет зарегистрировано в журнале. В среде разработки [Tracy |tracy:] перехватит предупреждение и отобразит синюю страницу ошибки. Если установлен `InvalidLinkTextual`, презентер и компоненты возвращают сообщение об ошибке в виде URL-адреса, который отмечен `#error:`. Чтобы сделать такие ссылки видимыми, мы можем добавить правило CSS в нашу таблицу стилей: +Настройка по умолчанию — `InvalidLinkWarning` в режиме production и `InvalidLinkWarning | InvalidLinkTextual` в режиме разработки. `InvalidLinkWarning` в production-среде не вызывает прерывания скрипта, но предупреждение будет залогировано. В среде разработки его перехватывает [Tracy |tracy:] и отображает синий экран. `InvalidLinkTextual` работает так, что в качестве URL возвращает сообщение об ошибке, которое начинается символами `#error:`. Чтобы такие ссылки были заметны с первого взгляда, добавим в CSS: ```css a[href^="#error:"] { @@ -242,7 +266,7 @@ a[href^="#error:"] { } ``` -Если мы не хотим, чтобы предупреждения создавались в среде разработки, мы можем включить режим автоматической недопустимой связи в [конфигурации|configuration]. +Если мы не хотим, чтобы в среде разработки генерировались предупреждения, мы можем установить тихий режим прямо в [конфигурации |configuration]. ```neon application: @@ -253,10 +277,10 @@ application: LinkGenerator ============= -Как создавать ссылки с таким же комфортом, как с методом `link()`, но без презентера? Для этого у нас есть [api:Nette\Application\LinkGenerator]. +Как создавать ссылки с таким же удобством, как у метода `link()`, но без присутствия презентера? Для этого существует [api:Nette\Application\LinkGenerator]. -LinkGenerator — это сервис, который можно передать через конструктор, а затем создать ссылки с помощью метода 'link()'. +LinkGenerator — это сервис, который вы можете получить через конструктор и затем создавать ссылки его методом `link()`. -Есть разница по сравнению с презентерами. LinkGenerator создает все ссылки как абсолютные URL-адреса. Кроме того, нет «текущего презентера», поэтому невозможно указать только имя действия 'link('default')' или относительные пути к модулям. +По сравнению с презентерами есть разница. LinkGenerator создает все ссылки сразу как абсолютные URL. И далее не существует "текущего презентера", поэтому нельзя в качестве цели указать только имя действия `link('default')` или указывать относительные пути к модулям. -Недопустимые ссылки всегда выбросывают исключение `Nette\Application\UI\InvalidLinkException`. +Недействительные ссылки всегда выбрасывают `Nette\Application\UI\InvalidLinkException`. diff --git a/application/ru/directory-structure.texy b/application/ru/directory-structure.texy new file mode 100644 index 0000000000..9c1abc41af --- /dev/null +++ b/application/ru/directory-structure.texy @@ -0,0 +1,526 @@ +Структура каталогов приложения +****************************** + +
    + +Как спроектировать понятную и масштабируемую структуру каталогов для проектов на Nette Framework? Мы покажем проверенные практики, которые помогут вам организовать код. Вы узнаете: + +- как **логически разделить** приложение на каталоги +- как спроектировать структуру так, чтобы она **хорошо масштабировалась** с ростом проекта +- какие существуют **возможные альтернативы** и их преимущества или недостатки + +
    + + +Важно отметить, что сам Nette Framework не привязан к какой-либо конкретной структуре. Он разработан так, чтобы его можно было легко адаптировать к любым потребностям и предпочтениям. + + +Базовая структура проекта +========================= + +Хотя Nette Framework не диктует никакой жесткой структуры каталогов, существует проверенное стандартное расположение в виде [Web Project |https://github.com/nette/web-project]: + +/--pre +web-project/ +├── app/ ← каталог с приложением +├── assets/ ← файлы SCSS, JS, изображения..., альтернативно resources/ +├── bin/ ← скрипты для командной строки +├── config/ ← конфигурация +├── log/ ← логируемые ошибки +├── temp/ ← временные файлы, кеш +├── tests/ ← тесты +├── vendor/ ← библиотеки, установленные Composer +└── www/ ← публичный каталог (document-root) +\-- + +Эту структуру можно произвольно изменять в соответствии с вашими потребностями - папки можно переименовывать или перемещать. Затем достаточно лишь изменить относительные пути к каталогам в файле `Bootstrap.php` и, возможно, `composer.json`. Больше ничего не требуется, никакой сложной реконфигурации, никаких изменений констант. Nette обладает умным автоопределением и автоматически распознает расположение приложения, включая его базовый URL. + + +Принципы организации кода +========================= + +Когда вы впервые изучаете новый проект, вы должны быстро в нем сориентироваться. Представьте, что вы открываете каталог `app/Model/` и видите следующую структуру: + +/--pre +app/Model/ +├── Services/ +├── Repositories/ +└── Entities/ +\-- + +Из нее вы узнаете только то, что проект использует какие-то сервисы, репозитории и сущности. О реальном назначении приложения вы не узнаете абсолютно ничего. + +Посмотрим на другой подход - **организацию по доменам**: + +/--pre +app/Model/ +├── Cart/ +├── Payment/ +├── Order/ +└── Product/ +\-- + +Здесь все иначе - с первого взгляда ясно, что это интернет-магазин. Уже сами названия каталогов говорят о том, что умеет приложение - работает с платежами, заказами и продуктами. + +Первый подход (организация по типу классов) на практике приносит ряд проблем: код, который логически связан, разбросан по разным папкам, и вам приходится переключаться между ними. Поэтому мы будем организовывать по доменам. + + +Пространства имен +----------------- + +Принято, чтобы структура каталогов соответствовала пространствам имен в приложении. Это означает, что физическое расположение файлов соответствует их пространству имен. Например, класс, расположенный в `app/Model/Product/ProductRepository.php`, должен иметь пространство имен `App\Model\Product`. Этот принцип помогает ориентироваться в коде и упрощает автозагрузку. + + +Единственное vs множественное число в названиях +----------------------------------------------- + +Обратите внимание, что для основных каталогов приложения мы используем единственное число: `app`, `config`, `log`, `temp`, `www`. Точно так же и внутри приложения: `Model`, `Core`, `Presentation`. Это потому, что каждый из них представляет собой единую целостную концепцию. + +Аналогично, например, `app/Model/Product` представляет все, что связано с продуктами. Мы не назовем это `Products`, потому что это не папка, полная продуктов (там были бы файлы `nokia.php`, `samsung.php`). Это пространство имен, содержащее классы для работы с продуктами - `ProductRepository.php`, `ProductService.php`. + +Папка `app/Tasks` находится во множественном числе, потому что она содержит набор отдельных исполняемых скриптов - `CleanupTask.php`, `ImportTask.php`. Каждый из них является отдельной единицей. + +Для согласованности рекомендуем использовать: +- Единственное число для пространства имен, представляющего функциональное целое (даже если оно работает с несколькими сущностями) +- Множественное число для коллекций отдельных единиц +- В случае неопределенности или если вы не хотите об этом думать, выберите единственное число + + +Публичный каталог `www/` +======================== + +Этот каталог является единственным доступным из веба (так называемый document-root). Часто можно встретить название `public/` вместо `www/` - это всего лишь вопрос соглашения и на функциональность не влияет. Каталог содержит: +- [Точку входа |bootstrapping#index.php] приложения `index.php` +- Файл `.htaccess` с правилами для mod_rewrite (для Apache) +- Статические файлы (CSS, JavaScript, изображения) +- Загруженные файлы + +Для правильной защиты приложения крайне важно иметь правильно [настроенный document-root |nette:troubleshooting#Как изменить или удалить каталог www из URL]. + +.[note] +Никогда не размещайте в этом каталоге папку `node_modules/` - она содержит тысячи файлов, которые могут быть исполняемыми и не должны быть общедоступными. + + +Каталог приложения `app/` +========================= + +Это основной каталог с кодом приложения. Базовая структура: + +/--pre +app/ +├── Core/ ← инфраструктурные вопросы +├── Model/ ← бизнес-логика +├── Presentation/ ← презентеры и шаблоны +├── Tasks/ ← командные скрипты +└── Bootstrap.php ← загрузочный класс приложения +\-- + +`Bootstrap.php` — это [стартовый класс приложения |bootstrapping], который инициализирует среду, загружает конфигурацию и создает DI-контейнер. + +Давайте теперь рассмотрим отдельные подкаталоги подробнее. + + +Презентеры и шаблоны +==================== + +Презентационная часть приложения находится в каталоге `app/Presentation`. Альтернативой является короткое `app/UI`. Это место для всех презентеров, их шаблонов и возможных вспомогательных классов. + +Этот слой мы организуем по доменам. В сложном проекте, который сочетает в себе интернет-магазин, блог и API, структура выглядела бы так: + +/--pre +app/Presentation/ +├── Shop/ ← фронтенд интернет-магазина +│ ├── Product/ +│ ├── Cart/ +│ └── Order/ +├── Blog/ ← блог +│ ├── Home/ +│ └── Post/ +├── Admin/ ← администрирование +│ ├── Dashboard/ +│ └── Products/ +└── Api/ ← конечные точки API + └── V1/ +\-- + +Напротив, для простого блога мы бы использовали разделение: + +/--pre +app/Presentation/ +├── Front/ ← фронтенд сайта +│ ├── Home/ +│ └── Post/ +├── Admin/ ← администрирование +│ ├── Dashboard/ +│ └── Posts/ +├── Error/ +└── Export/ ← RSS, карты сайта и т. д. +\-- + +Папки, такие как `Home/` или `Dashboard/`, содержат презентеры и шаблоны. Папки, такие как `Front/`, `Admin/` или `Api/`, называются **модулями**. Технически это обычные каталоги, которые служат для логического разделения приложения. + +Каждая папка с презентером содержит одноименный презентер и его шаблоны. Например, папка `Dashboard/` содержит: + +/--pre +Dashboard/ +├── DashboardPresenter.php ← презентер +└── default.latte ← шаблон +\-- + +Эта структура каталогов отражается в пространствах имен классов. Например, `DashboardPresenter` находится в пространстве имен `App\Presentation\Admin\Dashboard` (см. [#маппинг презентеров]): + +```php +namespace App\Presentation\Admin\Dashboard; + +class DashboardPresenter extends Nette\Application\UI\Presenter +{ + // ... +} +``` + +На презентер `Dashboard` внутри модуля `Admin` мы ссылаемся в приложении с помощью двоеточия как на `Admin:Dashboard`. На его действие `default` затем как на `Admin:Dashboard:default`. В случае вложенных модулей мы используем больше двоеточий, например `Shop:Order:Detail:default`. + + +Гибкое развитие структуры +------------------------- + +Одним из больших преимуществ этой структуры является то, как элегантно она адаптируется к растущим потребностям проекта. В качестве примера возьмем часть, генерирующую XML-фиды. В начале у нас простая форма: + +/--pre +Export/ +├── ExportPresenter.php ← один презентер для всех экспортов +├── sitemap.latte ← шаблон для карты сайта +└── feed.latte ← шаблон для RSS-фида +\-- + +Со временем появляются новые типы фидов, и для них требуется больше логики... Нет проблем! Папка `Export/` просто становится модулем: + +/--pre +Export/ +├── Sitemap/ +│ ├── SitemapPresenter.php +│ └── sitemap.latte +└── Feed/ + ├── FeedPresenter.php + ├── zbozi.latte ← фид для Zboží.cz + └── heureka.latte ← фид для Heureka.cz +\-- + +Эта трансформация абсолютно плавная - достаточно создать новые подпапки, распределить в них код и обновить ссылки (например, с `Export:feed` на `Export:Feed:zbozi`). Благодаря этому мы можем постепенно расширять структуру по мере необходимости, уровень вложенности никак не ограничен. + +Если, например, в администрировании у вас много презентеров, связанных с управлением заказами, таких как `OrderDetail`, `OrderEdit`, `OrderDispatch` и т. д., вы можете для лучшей организации в этом месте создать модуль (папку) `Order`, в котором будут (папки для) презентеров `Detail`, `Edit`, `Dispatch` и другие. + + +Расположение шаблонов +--------------------- + +В предыдущих примерах мы видели, что шаблоны размещаются прямо в папке с презентером: + +/--pre +Dashboard/ +├── DashboardPresenter.php ← презентер +├── DashboardTemplate.php ← необязательный класс для шаблона +└── default.latte ← шаблон +\-- + +Это расположение на практике оказывается наиболее удобным - все связанные файлы у вас под рукой. + +Альтернативно, вы можете разместить шаблоны в подпапке `templates/`. Nette поддерживает оба варианта. Вы даже можете разместить шаблоны совершенно вне папки `Presentation/`. Все о возможностях размещения шаблонов вы найдете в главе [Поиск шаблонов |templates#Поиск шаблонов]. + + +Вспомогательные классы и компоненты +----------------------------------- + +К презентерам и шаблонам часто относятся и другие вспомогательные файлы. Мы разместим их логически в соответствии с их областью действия: + +1. **Прямо у презентера** в случае специфических компонентов для данного презентера: + +/--pre +Product/ +├── ProductPresenter.php +├── ProductGrid.php ← компонент для вывода продуктов +└── FilterForm.php ← форма для фильтрации +\-- + +2. **Для модуля** - рекомендуем использовать папку `Accessory`, которая размещается удобно в начале алфавита: + +/--pre +Front/ +├── Accessory/ +│ ├── NavbarControl.php ← компоненты для фронтенда +│ └── TemplateFilters.php +├── Product/ +└── Cart/ +\-- + +3. **Для всего приложения** - в `Presentation/Accessory/`: +/--pre +app/Presentation/ +├── Accessory/ +│ ├── LatteExtension.php +│ └── TemplateFilters.php +├── Front/ +└── Admin/ +\-- + +Или вы можете разместить вспомогательные классы, такие как `LatteExtension.php` или `TemplateFilters.php`, в инфраструктурной папке `app/Core/Latte/`. А компоненты в `app/Components`. Выбор зависит от привычек команды. + + +Модель - сердце приложения +========================== + +Модель содержит всю бизнес-логику приложения. Для ее организации снова действует правило - структурируем по доменам: + +/--pre +app/Model/ +├── Payment/ ← все, что связано с платежами +│ ├── PaymentFacade.php ← главная точка входа +│ ├── PaymentRepository.php +│ ├── Payment.php ← сущность +├── Order/ ← все, что связано с заказами +│ ├── OrderFacade.php +│ ├── OrderRepository.php +│ ├── Order.php +└── Shipping/ ← все, что связано с доставкой +\-- + +В модели обычно встречаются следующие типы классов: + +**Фасады**: представляют собой главную точку входа в конкретный домен приложения. Они действуют как оркестратор, координирующий взаимодействие между различными сервисами для реализации полных сценариев использования (например, "создать заказ" или "обработать платеж"). Под своим оркестрационным слоем фасад скрывает детали реализации от остальной части приложения, предоставляя чистый интерфейс для работы с данным доменом. + +```php +class OrderFacade +{ + public function createOrder(Cart $cart): Order + { + // валидация + // создание заказа + // отправка электронной почты + // запись в статистику + } +} +``` + +**Сервисы**: фокусируются на специфической бизнес-операции в рамках домена. В отличие от фасада, который оркеструет целые сценарии использования, сервис реализует конкретную бизнес-логику (например, расчет цен или обработку платежей). Сервисы обычно не имеют состояния и могут использоваться либо фасадами как строительные блоки для более сложных операций, либо напрямую другими частями приложения для более простых задач. + +```php +class PricingService +{ + public function calculateTotal(Order $order): Money + { + // расчет цены + } +} +``` + +**Репозитории**: обеспечивают все взаимодействие с хранилищем данных, обычно базой данных. Их задача - загрузка и сохранение сущностей и реализация методов для их поиска. Репозиторий изолирует остальную часть приложения от деталей реализации базы данных и предоставляет объектно-ориентированный интерфейс для работы с данными. + +```php +class OrderRepository +{ + public function find(int $id): ?Order + { + } + + public function findByCustomer(int $customerId): array + { + } +} +``` + +**Сущности**: объекты, представляющие основные бизнес-концепции в приложении, которые имеют свою идентичность и изменяются со временем. Обычно это классы, отображаемые на таблицы базы данных с помощью ORM (например, Nette Database Explorer или Doctrine). Сущности могут содержать бизнес-правила, касающиеся их данных, и логику валидации. + +```php +// Сущность, отображаемая на таблицу базы данных orders +class Order extends Nette\Database\Table\ActiveRow +{ + public function addItem(Product $product, int $quantity): void + { + $this->related('order_items')->insert([ + 'product_id' => $product->id, + 'quantity' => $quantity, + 'unit_price' => $product->price, + ]); + } +} +``` + +**Объекты-значения**: неизменяемые объекты, представляющие значения без собственной идентичности - например, денежная сумма или адрес электронной почты. Два экземпляра объекта-значения с одинаковыми значениями считаются идентичными. + + +Инфраструктурный код +==================== + +Папка `Core/` (или также `Infrastructure/`) является домом для технической основы приложения. Инфраструктурный код обычно включает: + +/--pre +app/Core/ +├── Router/ ← маршрутизация и управление URL +│ └── RouterFactory.php +├── Security/ ← аутентификация и авторизация +│ ├── Authenticator.php +│ └── Authorizator.php +├── Logging/ ← логирование и мониторинг +│ ├── SentryLogger.php +│ └── FileLogger.php +├── Cache/ ← слой кеширования +│ └── FullPageCache.php +└── Integration/ ← интеграция с внешними сервисами + ├── Slack/ + └── Stripe/ +\-- + +Для небольших проектов, разумеется, достаточно плоского разделения: + +/--pre +Core/ +├── RouterFactory.php +├── Authenticator.php +└── QueueMailer.php +\-- + +Это код, который: + +- Решает техническую инфраструктуру (маршрутизация, логирование, кеширование) +- Интегрирует внешние сервисы (Sentry, Elasticsearch, Redis) +- Предоставляет базовые сервисы для всего приложения (почта, база данных) +- В основном не зависит от конкретного домена - кеш или логгер работает одинаково для интернет-магазина или блога. + +Сомневаетесь, принадлежит ли определенный класс сюда или к модели? Ключевое различие в том, что код в `Core/`: + +- Ничего не знает о домене (продукты, заказы, статьи) +- В основном его можно перенести в другой проект +- Решает "как это работает" (как отправить письмо), а не "что это делает" (какое письмо отправить) + +Пример для лучшего понимания: + +- `App\Core\MailerFactory` - создает экземпляры класса для отправки электронной почты, решает настройки SMTP +- `App\Model\OrderMailer` - использует `MailerFactory` для отправки электронных писем о заказах, знает их шаблоны и когда их нужно отправлять + + +Командные скрипты +================= + +Приложения часто нуждаются в выполнении действий вне обычных HTTP-запросов - будь то обработка данных в фоновом режиме, обслуживание или периодические задачи. Для запуска служат простые скрипты в каталоге `bin/`, саму логику реализации мы размещаем в `app/Tasks/` (или `app/Commands/`). + +Пример: + +/--pre +app/Tasks/ +├── Maintenance/ ← скрипты обслуживания +│ ├── CleanupCommand.php ← удаление старых данных +│ └── DbOptimizeCommand.php ← оптимизация базы данных +├── Integration/ ← интеграция с внешними системами +│ ├── ImportProducts.php ← импорт из системы поставщика +│ └── SyncOrders.php ← синхронизация заказов +└── Scheduled/ ← регулярные задачи + ├── NewsletterCommand.php ← рассылка новостей + └── ReminderCommand.php ← уведомления клиентам +\-- + +Что относится к модели, а что к командным скриптам? Например, логика отправки одного электронного письма является частью модели, массовая рассылка тысяч писем уже относится к `Tasks/`. + +Задачи обычно [запускаем из командной строки |https://blog.nette.org/en/cli-scripts-in-nette-application] или через cron. Их можно запускать и через HTTP-запрос, но необходимо помнить о безопасности. Презентер, который запускает задачу, нужно защитить, например, только для вошедших пользователей или сильным токеном и доступом с разрешенных IP-адресов. Для длительных задач необходимо увеличить временной лимит скрипта и использовать `session_write_close()`, чтобы сессия не блокировалась. + + +Другие возможные каталоги +========================= + +Кроме упомянутых базовых каталогов, вы можете в соответствии с потребностями проекта добавить другие специализированные папки. Посмотрим на наиболее частые из них и их использование: + +/--pre +app/ +├── Api/ ← логика для API, независимая от презентационного слоя +├── Database/ ← миграционные скрипты и сидеры для тестовых данных +├── Components/ ← общие визуальные компоненты для всего приложения +├── Event/ ← полезно, если вы используете событийно-ориентированную архитектуру +├── Mail/ ← шаблоны электронной почты и связанная логика +└── Utils/ ← вспомогательные классы +\-- + +Для общих визуальных компонентов, используемых в презентерах по всему приложению, можно использовать папку `app/Components` или `app/Controls`: + +/--pre +app/Components/ +├── Form/ ← общие компоненты форм +│ ├── SignInForm.php +│ └── UserForm.php +├── Grid/ ← компоненты для вывода данных +│ └── DataGrid.php +└── Navigation/ ← навигационные элементы + ├── Breadcrumbs.php + └── Menu.php +\-- + +Сюда относятся компоненты, имеющие более сложную логику. Если вы хотите делиться компонентами между несколькими проектами, рекомендуется выделить их в отдельный composer-пакет. + +В каталог `app/Mail` можно поместить управление электронной почтой: + +/--pre +app/Mail/ +├── templates/ ← шаблоны электронной почты +│ ├── order-confirmation.latte +│ └── welcome.latte +└── OrderMailer.php +\-- + + +Маппинг презентеров +=================== + +Маппинг определяет правила для вывода имени класса из имени презентера. Мы указываем их в [конфигурации |configuration] под ключом `application › mapping`. + +На этой странице мы показали, что презентеры размещаем в папке `app/Presentation` (или `app/UI`). Эту конвенцию мы должны сообщить Nette в конфигурационном файле. Достаточно одной строки: + +```neon +application: + mapping: App\Presentation\*\**Presenter +``` + +Как работает маппинг? Для лучшего понимания сначала представим приложение без модулей. Мы хотим, чтобы классы презентеров попадали в пространство имен `App\Presentation`, чтобы презентер `Home` отображался на класс `App\Presentation\HomePresenter`. Этого мы достигнем с помощью следующей конфигурации: + +```neon +application: + mapping: App\Presentation\*Presenter +``` + +Маппинг работает так, что имя презентера `Home` заменяет звездочку в маске `App\Presentation\*Presenter`, тем самым мы получаем итоговое имя класса `App\Presentation\HomePresenter`. Просто! + +Однако, как вы видите в примерах в этой и других главах, классы презентеров мы размещаем в одноименных подкаталогах, например, презентер `Home` отображается на класс `App\Presentation\Home\HomePresenter`. Этого мы достигнем удвоением звездочки: + +```neon +application: + mapping: App\Presentation\**Presenter +``` + +Теперь перейдем к маппингу презентеров в модули. Для каждого модуля можно определить специфический маппинг: + +```neon +application: + mapping: + Front: App\Presentation\Front\**Presenter + Admin: App\Presentation\Admin\**Presenter + Api: App\Api\*Presenter +``` + +Согласно этой конфигурации, презентер `Front:Home` отображается на класс `App\Presentation\Front\Home\HomePresenter`, в то время как презентер `Api:OAuth` на класс `App\Api\OAuthPresenter`. + +Поскольку модули `Front` и `Admin` имеют схожий способ маппинга, и таких модулей, скорее всего, будет больше, можно создать общее правило, которое их заменит. В маску класса так добавится новая звездочка для модуля: + +```neon +application: + mapping: + *: App\Presentation\*\**Presenter + Api: App\Api\*Presenter +``` + +Это работает и для более глубоко вложенных структур каталогов, таких как, например, презентер `Admin:User:Edit`, сегмент со звездочкой повторяется для каждого уровня, и результатом является класс `App\Presentation\Admin\User\Edit\EditPresenter`. + +Альтернативной записью является использование массива, состоящего из трех сегментов, вместо строки. Эта запись эквивалентна предыдущей: + +```neon +application: + mapping: + *: [App\Presentation, *, **Presenter] + Api: [App\Api, '', *Presenter] +``` diff --git a/application/ru/how-it-works.texy b/application/ru/how-it-works.texy index b5b91aa685..684db1a876 100644 --- a/application/ru/how-it-works.texy +++ b/application/ru/how-it-works.texy @@ -3,99 +3,101 @@
    -Сейчас вы читаете основной документ документации Nette. Вы узнаете все принципы работы веб-приложений. Все мелочи от А до Я, от момента рождения до последнего вздоха PHP-скрипта. После прочтения вы будете знать: +Вы читаете основной документ документации Nette. Вы узнаете весь принцип работы веб-приложений. От А до Я, с момента рождения до последнего вздоха PHP-скрипта. После прочтения вы будете знать: - как все это работает -- что такое Bootstrap, Presenter и DI контейнер +- что такое Bootstrap, Presenter и DI-контейнер - как выглядит структура каталогов
    -Структура каталога .[#toc-directory-structure] -============================================== +Структура каталогов +=================== -Откройте скелетный пример веб-приложения под названием [WebProject|https://github.com/nette/web-project], и вы сможете наблюдать, как происходит запись файлов. +Откройте пример скелета веб-приложения под названием [WebProject |https://github.com/nette/web-project] и во время чтения вы можете смотреть на файлы, о которых идет речь. Структура каталогов выглядит примерно так: /--pre web-project/ ├── app/ ← каталог с приложением -│ ├── Presenters/ ← классы презентеров -│ │ ├── HomePresenter.php ← Класс презентера главной страницы -│ │ └── templates/ ← директория шаблонов -│ │ ├── @layout.latte ← шаблон общего макета -│ │ └── Home/ ← шаблоны презентера главной страницы -│ │ └── default.latte ← шаблон действия `default` -│ ├── Router/ ← конфигурация URL-адресов +│ ├── Core/ ← базовые классы, необходимые для работы +│ │ └── RouterFactory.php ← конфигурация URL-адресов +│ ├── Presentation/ ← презентеры, шаблоны и т.п. +│ │ ├── @layout.latte ← шаблон макета +│ │ └── Home/ ← каталог презентера Home +│ │ ├── HomePresenter.php ← класс презентера Home +│ │ └── default.latte ← шаблон действия default │ └── Bootstrap.php ← загрузочный класс Bootstrap -├── bin/ ← скрипты командной строки -├── config/ ← файлы конфигурации +├── assets/ ← ресурсы (SCSS, TypeScript, исходные изображения) +├── bin/ ← скрипты, запускаемые из командной строки +├── config/ ← конфигурационные файлы │ ├── common.neon -│ └── local.neon -├── log/ ← журналы ошибок -├── temp/ ← временные файлы, кэш, … -├── vendor/ ← библиотеки, установленные через Composer +│ └── services.neon +├── log/ ← логируемые ошибки +├── temp/ ← временные файлы, кеш, … +├── vendor/ ← библиотеки, установленные Composer │ ├── ... -│ └── autoload.php ← автозагрузчик библиотек, установленных через Composer -├── www/ ← публичный корневой каталог проекта -│ ├── .htaccess ← правила mod_rewrite и т. д. -│ └── index.php ← начальный файл, запускающий приложение +│ └── autoload.php ← автозагрузка всех установленных пакетов +├── www/ ← публичный каталог или document-root проекта +│ ├── assets/ ← скомпилированные статические файлы (CSS, JS, изображения, ...) +│ ├── .htaccess ← правила mod_rewrite +│ └── index.php ← первоначальный файл, которым запускается приложение └── .htaccess ← запрещает доступ ко всем каталогам, кроме www \-- -Вы можете изменить структуру каталогов любым способом, переименовать или переместить папки, а затем просто отредактировать пути к `log/` и `temp/` в файле `Bootstrap.php` и путь к этому файлу в `composer.json` в секции `autoload`. Ничего больше, никакой сложной перенастройки, никаких постоянных изменений. Nette имеет [интеллектуальное автоопределение|bootstrap#development-vs-production-mode]. +Структуру каталогов можно изменять как угодно, папки переименовывать или перемещать, она абсолютно гибкая. Nette, кроме того, обладает умным автоопределением и автоматически распознает расположение приложения, включая его базовый URL. -Для немного больших приложений мы можем разделить папки с ведущими и шаблонами на подкаталоги (на диске) и на пространства имен (в коде), которые мы называем [модулями|modules]. +Для немного больших приложений мы можем [разделить папки с презентерами и шаблонами на подкаталоги |directory-structure#Презентеры и шаблоны] и классы на пространства имен, которые мы называем модулями. -Публичный каталог `www/` может быть изменен без необходимости устанавливать что-либо ещё. На самом деле, часто бывает, что из-за специфики вашего хостинга вам придется переименовать его или, наоборот, установить так называемый document-root на этот каталог в конфигурации хостинга. Если ваш хостинг не позволяет создавать папки на один уровень выше публичного каталога, советуем вам поискать другой хостинг. В противном случае вы подвергнете себя значительному риску безопасности. +Каталог `www/` представляет собой так называемый публичный каталог или document-root проекта. Вы можете его переименовать без необходимости что-либо еще настраивать на стороне приложения. Нужно только [настроить хостинг |nette:troubleshooting#Как изменить или удалить каталог www из URL] так, чтобы document-root указывал на этот каталог. -Вы также можете загрузить WebProject напрямую, включая Nette, используя [Composer |best-practices:composer]: +WebProject можно также сразу скачать вместе с Nette с помощью [Composer |best-practices:composer]: ```shell composer create-project nette/web-project ``` -В Linux или macOS установите [разрешения на запись |nette:troubleshooting#Setting-Directory-Permissions] для каталогов `log/` и `temp/`. +На Linux или macOS установите для каталогов `log/` и `temp/` [права на запись |nette:troubleshooting#Настройка прав доступа к каталогам]. -Приложение WebProject готово к запуску, больше ничего настраивать не нужно, и вы можете просмотреть его прямо в браузере, обратившись к папке `www/`. +Приложение WebProject готово к запуску, не нужно ничего настраивать, и его можно сразу отобразить в браузере, обратившись к папке `www/`. -HTTP-запрос .[#toc-http-request] -================================ +HTTP-запрос +=========== -Все начинается с того, что пользователь открывает страницу в браузере, а браузер стучится на сервер с HTTP-запросом. Запрос идет к PHP-файлу, расположенному в публичном каталоге `www/`, который называется `index.php`. Предположим, что это запрос на `https://example.com/product/123`. Благодаря соответствующим настройкам [server settings |nette:troubleshooting#how-to-configure-a-server-for-nice-urls], этот URL также сопоставлен с файлом `index.php` и будет выполнен. +Все начинается в тот момент, когда пользователь в браузере открывает страницу. То есть когда браузер стучится на сервер с HTTP-запросом. Запрос направляется на единственный PHP-файл, который находится в публичном каталоге `www/`, и это `index.php`. Допустим, это запрос на адрес `https://example.com/product/123`. Благодаря подходящей [настройке сервера |nette:troubleshooting#Как настроить сервер для красивых URL] и этот URL отображается на файл `index.php`, и он выполняется. -Его задача состоит в следующем: +Его задача: -1) инициализация среды -2) получение фабрики -3) запуск приложения Nette, которое обрабатывает запрос +1) инициализировать среду +2) получить фабрику +3) запустить приложение Nette, которое обработает запрос -Что за фабрика? Мы производим не тракторы, а веб-сайты! Подождите, сейчас всё будет объяснено. +Какую фабрику? Мы же не тракторы производим, а веб-страницы! Потерпите, сейчас все объяснится. -Под «инициализацией среды» подразумевается, например, что активирован сервис [Tracy |tracy:], который является удивительным инструментом для регистрации или визуализации ошибок. Он регистрирует ошибки на рабочем сервере и отображает их непосредственно на сервере разработки. Поэтому при инициализации также необходимо решить, работает ли сайт в производственном режиме или в режиме разработчика. Для этого Nette использует автоопределение: если вы запускаете сайт на localhost, он работает в режиме разработчика. Вам не нужно ничего настраивать, и приложение готово как для разработки, так и для производственного развертывания. Эти шаги выполняются и подробно описываются в главе [Bootstrap |bootstrap]. +Под словами «инициализация среды» мы подразумеваем, например, активацию [Tracy |tracy:], что является замечательным инструментом для логирования или визуализации ошибок. На production-сервере он логирует ошибки, на сервере разработки сразу отображает. Следовательно, к инициализации относится и решение, работает ли веб в режиме production или разработки. Для этого Nette использует [умное автоопределение |bootstrapping#Режим разработки vs режим production]: если вы запускаете веб на localhost, он работает в режиме разработки. Вам не нужно ничего настраивать, и приложение сразу готово как для разработки, так и для реального развертывания. Эти шаги выполняются и подробно описаны в главе о [классе Bootstrap |bootstrapping]. -Третий пункт (да, мы пропустили второй, но мы к нему вернемся) — это запуск приложения. Обработкой HTTP-запросов в Nette занимается класс `Nette\Application\Application` (далее `Application`), поэтому когда мы говорим «запустить приложение», мы подразумеваем вызов метода с именем `run()` на объекте этого класса. +Третьим пунктом (да, второй мы пропустили, но вернемся к нему) является запуск приложения. Обработкой HTTP-запросов в Nette занимается класс `Nette\Application\Application` (далее `Application`), поэтому когда мы говорим запустить приложение, мы имеем в виду конкретно вызов метода с характерным названием `run()` на объекте этого класса. -Nette — это наставник, который направляет вас к написанию чистых приложений по проверенным методологиям. И самая проверенная из них называется **внедрение зависимостей**, сокращенно DI. В данный момент мы не хотим обременять вас объяснением DI, поскольку этому посвящена [отдельная глава |dependency-injection:introduction], здесь важно то, что ключевые объекты обычно создаются фабрикой объектов, называемой **DI-контейнер** (сокращенно DIC). Да, это та самая фабрика, о которой говорилось некоторое время назад. И она также создает для нас объект `Application`, поэтому сначала нам нужен контейнер. Мы получаем его с помощью класса `Configurator` и позволяем ему создать объект `Application`, вызываем метод `run()` и это запускает приложение Nette. Именно это и происходит в файле [index.php |bootstrap#index-php]. +Nette — это наставник, который ведет вас к написанию чистых приложений по проверенным методикам. И одна из самых проверенных называется **dependency injection**, сокращенно DI. В данный момент мы не хотим загружать вас объяснением DI, для этого есть [отдельная глава |dependency-injection:introduction], важен результат, что ключевые объекты нам обычно будет создавать фабрика объектов, которая называется **DI-контейнер** (сокращенно DIC). Да, это та самая фабрика, о которой шла речь минуту назад. И она создаст нам и объект `Application`, поэтому нам сначала нужен контейнер. Мы получаем его с помощью класса `Configurator` и позволяем ему создать объект `Application`, вызываем на нем метод `run()`, и тем самым запускается приложение Nette. Именно это происходит в файле [index.php |bootstrapping#index.php]. -Приложение Nette .[#toc-nette-application] -========================================== +Nette Application +================= -Класс Application имеет единственную задачу: для ответа на HTTP-запрос. +У класса Application одна задача: ответить на HTTP-запрос. -Приложения, написанные на Nette, разделены на множество так называемых презентеров (в других фреймворках вы можете встретить термин *контроллер*, что одно и то же), которые являются классами, представляющими конкретную страницу сайта: например, домашняя страница; товар в электронном магазине; регистрационная форма; rss-карта и т. д. В приложении может быть от одного до тысячи презентеров. +Приложения, написанные на Nette, делятся на множество так называемых презентеров (в других фреймворках вы можете встретить термин controller, это одно и то же), которые представляют собой классы, каждый из которых представляет какую-то конкретную страницу сайта: например, главную страницу; продукт в интернет-магазине; форму входа; фид карты сайта и т. д. Приложение может иметь от одного до тысяч презентеров. -Приложение начинает работу с того, что просит так называемый маршрутизатор решить, какому из презентеров передать текущий запрос на обработку. Маршрутизатор решает, чья это ответственность. Он просматривает входной URL `https://example.com/product/123` и, основываясь на том, как он настроен, решает, что это задание, например, для **презентера** `Product`, который хочет `показать` продукт с `id: 123` как действие. Хорошей привычкой является написание пар презентер + действие, разделенных двоеточием: `Продукт:показать`. +Application начинает с того, что запрашивает у так называемого маршрутизатора (router), чтобы он решил, какому из презентеров передать текущий запрос для обработки. Маршрутизатор решает, чья это ответственность. Он смотрит на входной URL `https://example.com/product/123` и на основе того, как он настроен, решает, что это работа, например, для **презентера** `Product`, от которого потребуется в качестве **действия** отображение (`show`) продукта с `id: 123`. Пару презентер + действие принято записывать, разделяя двоеточием, как `Product:show`. -Поэтому маршрутизатор преобразовал URL в пару `Presenter:action` + параметры, в нашем случае `Product:show` + `id`: 123`. Вы можете увидеть, как выглядит маршрутизатор в файле `app/Router/RouterFactory.php`, и мы подробно опишем его в главе [Маршрутизация |routing]. +Таким образом, маршрутизатор преобразовал URL в пару `Presenter:action` + параметры, в нашем случае `Product:show` + `id: 123`. Как выглядит такой маршрутизатор, вы можете посмотреть в файле `app/Core/RouterFactory.php`, и мы подробно описываем его в главе [Маршрутизация |Routing]. -Давайте двигаться дальше. Приложение уже знает имя презентера и может продолжить работу. Путем создания объекта `ProductPresenter`, который является кодом презентера `Product`. Точнее, он просит контейнер DI создать презентера, потому что создание объектов — это его работа. +Идем дальше. Application уже знает имя презентера и может продолжать. Создавая объект класса `ProductPresenter`, который является кодом презентера `Product`. Точнее говоря, он просит DI-контейнер создать презентер, потому что создание — это его работа. -Презентер может выглядеть следующим образом: +Презентер может выглядеть примерно так: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -107,98 +109,92 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function renderShow(int $id): void { - // we obtain data from the model and pass it to the template + // получаем данные из модели и передаем в шаблон $this->template->product = $this->repository->getProduct($id); } } ``` -Запрос обрабатывается презентером. И задача ясна: выполнить действие `show` с `id: 123`. Что на языке презентеров означает — вызвать метод `renderShow()` с параметрем `$id` равным `123`. +Обработку запроса берет на себя презентер. И задача ясна: выполнить действие `show` с `id: 123`. Что на языке презентеров означает, что вызывается метод `renderShow()`, и в параметре `$id` он получает `123`. -Презентер может обрабатывать несколько действий, то есть иметь несколько методов `render()`. Но мы рекомендуем разрабатывать презентеры с одним или как можно меньшим количеством действий. +Презентер может обслуживать несколько действий, то есть иметь несколько методов `render()`. Но мы рекомендуем проектировать презентеры с одним или как можно меньшим количеством действий. -Итак, был вызван метод `renderShow(123)`, код которого является вымышленным примером, но на нем можно увидеть, как данные передаются в шаблон, то есть путем записи в `$this->template`. +Итак, вызвался метод `renderShow(123)`, код которого, хотя и является вымышленным примером, но на нем вы можете увидеть, как передаются данные в шаблон, то есть записью в `$this->template`. -После этого презентер возвращает ответ. Это может быть HTML-страница, изображение, XML-документ, отправка файла с диска, JSON или перенаправление на другую страницу. Важно отметить, что если мы явно не указываем, как реагировать (что имеет место в случае с `ProductPresenter`), ответом будет отображение шаблона с HTML-страницей. Почему? Ну, потому что в 99% случаев мы хотим отобразить шаблон, поэтому презентер принимает такое поведение по умолчанию и хочет облегчить нашу работу. Это точка зрения Nette. +Затем презентер возвращает ответ. Это может быть HTML-страница, изображение, XML-документ, отправка файла с диска, JSON или, например, перенаправление на другую страницу. Важно, что если мы явно не скажем, как он должен ответить (что и происходит в случае `ProductPresenter`), ответом будет отрисовка шаблона с HTML-страницей. Почему? Потому что в 99% случаев мы хотим отрисовать шаблон, поэтому презентер воспринимает это поведение как стандартное и хочет облегчить нам работу. В этом смысл Nette. -Нам даже не нужно указывать, какой шаблон нужно вывести, он сам выводит путь к нему в соответствии с простой логикой. В случае с презентером `Product` и действием `show`, он пытается проверить, существует ли один из этих файлов шаблонов относительно каталога, в котором находится класс `ProductPresenter`: +Нам даже не нужно указывать, какой шаблон отрисовать, путь к нему он выведет сам. В случае действия `show` он просто попытается загрузить шаблон `show.latte` в каталоге с классом `ProductPresenter`. Также он попытается найти макет в файле `@layout.latte` (подробнее о [поиске шаблонов |templates#Поиск шаблонов]). -- `templates/Product/show.latte` -- `templates/Product.show.latte` - -И затем он отображает шаблон. Теперь задача презентера и всего приложения выполнена. Если шаблон не существует, будет возвращена страница с ошибкой 404. Подробнее о презентерах вы можете прочитать на странице [Презентеры |presenters]. +И затем шаблоны отрисовываются. На этом задача презентера и всего приложения завершена, и работа выполнена. Если бы шаблон не существовал, вернулась бы страница с ошибкой 404. Больше о презентерах вы можете прочитать на странице [Презентеры |presenters]. [* request-flow.svg *] -Чтобы убедиться в этом, давайте попробуем повторить весь процесс, используя немного другой URL: +На всякий случай, попробуем повторить весь процесс с немного другим URL: 1) URL будет `https://example.com` -2) мы загружаем приложение, создаем контейнер и запускаем `Application::run()` +2) загружаем приложение, создается контейнер и запускается `Application::run()` 3) маршрутизатор декодирует URL как пару `Home:default` -4) создается объект `HomePresenter` +4) создается объект класса `HomePresenter` 5) вызывается метод `renderDefault()` (если существует) -6) шаблон `templates/Home/default.latte` с макетом `templates/@layout.latte` отрисован +6) отрисовывается шаблон, например, `default.latte` с макетом, например, `@layout.latte` -Возможно, сейчас вы столкнулись с множеством новых понятий, но мы считаем, что они имеют смысл. Создавать приложения в Nette — проще простого. +Возможно, вы сейчас столкнулись с большим количеством новых понятий, но мы верим, что они имеют смысл. Создание приложений в Nette — это огромное удовольствие. -Шаблоны .[#toc-templates] -========================= +Шаблоны +======= -Что касается шаблонов, Nette использует систему шаблонов [Latte |latte:]. Поэтому файлы с шаблонами заканчиваются на `.latte`. Latte используется потому, что это самая безопасная система шаблонов для PHP, и в то же время самая интуитивно понятная. Вам не нужно изучать много нового, достаточно знать PHP и несколько тегов Latte. Вы узнаете всё [в документации |latte:]. +Раз уж речь зашла о шаблонах, в Nette используется система шаблонов [Latte |latte:]. Поэтому и расширения `.latte` у шаблонов. Latte используется, во-первых, потому что это самая безопасная система шаблонов для PHP, а во-вторых, самая интуитивно понятная. Вам не нужно учить много нового, достаточно знания PHP и нескольких тегов. Все вы узнаете [в документации |templates]. -В шаблоне мы [создаем ссылки |creating-links] на других презентеров и действия следующим образом: +В шаблоне [создаются ссылки |creating-links] на другие презентеры и действия так: ```latte -страница товара +деталь продукта ``` -Просто напишите знакомую пару `Presenter:action` вместо реального URL и включите любые параметры. Хитрость заключается в `n:href`, который говорит, что этот атрибут будет обрабатываться Nette. И он будет генерировать: +Просто вместо реального URL вы пишете известную пару `Presenter:action` и указываете возможные параметры. Хитрость в `n:href`, которое говорит, что этот атрибут обработает Nette. И сгенерирует: ```latte -страница товара +деталь продукта ``` -Ранее упомянутый маршрутизатор отвечает за генерацию URL. Фактически, маршрутизаторы в Nette уникальны тем, что они могут выполнять не только преобразования из URL в пару презентер:действие, но и наоборот — генерировать URL из имени презентер + действие + параметры. -Благодаря этому в Nette вы можете полностью изменить форму URL во всем готовом приложении, не меняя ни одного символа в шаблоне или презентере, просто модифицировав маршрутизатор. -И благодаря этому работает так называемая канонизация — ещё одна уникальная особенность Nette, которая улучшает SEO путем автоматического предотвращения существования дублированного контента на разных URL. -Многие программисты находят это удивительным. +Генерацией URL занимается уже упомянутый маршрутизатор. Дело в том, что маршрутизаторы в Nette уникальны тем, что они могут выполнять не только преобразование из URL в пару presenter:action, но и наоборот, то есть из имени презентера + действия + параметров генерировать URL. Благодаря этому в Nette вы можете полностью изменить формы URL во всем готовом приложении, не изменяя ни одного символа в шаблоне или презентере. Просто изменив маршрутизатор. Также благодаря этому работает так называемая канонизация, что является еще одной уникальной особенностью Nette, которая способствует лучшему SEO (оптимизации для поисковых систем), автоматически предотвращая существование дублирующегося контента на разных URL. Многие программисты считают это потрясающим. -Интерактивные компоненты .[#toc-interactive-components] -======================================================= +Интерактивные компоненты +======================== -Мы хотим рассказать вам ещё кое-что о презентерах: они имеют встроенную систему компонентов. Те, кто постарше, могут помнить нечто подобное из Delphi или ASP.NET Web Forms. React или Vue.js построены на чем-то отдаленно похожем. В мире PHP-фреймворков это совершенно уникальная функция. +О презентерах мы должны вам рассказать еще одну вещь: у них есть встроенная система компонентов. Что-то подобное могут помнить старожилы из Delphi или ASP.NET Web Forms, на чем-то отдаленно похожем построены React или Vue.js. В мире PHP-фреймворков это совершенно уникальное явление. -Компоненты — это отдельные многократно используемые блоки, которые мы помещаем в страницы (т. е. в презентеры). Это могут быть [формы |forms:in-presenter], [сетки данных |https://componette.org/contributte/datagrid/], меню, опросы, в общем, всё, что имеет смысл использовать многократно. Мы можем создавать собственные компоненты или использовать некоторые из [огромного количества |https://componette.org] компонентов с открытым исходным кодом. +Компоненты — это отдельные повторно используемые единицы, которые мы вставляем на страницы (то есть в презентеры). Это могут быть [формы |forms:in-presenter], [датагриды |https://componette.org/contributte/datagrid/], меню, опросы, в общем, все, что имеет смысл использовать повторно. Мы можем создавать собственные компоненты или использовать некоторые из [огромного выбора |https://componette.org] open source компонентов. -Компоненты в корне меняют подход к разработке приложений. Они откроют новые возможности для создания страниц из заранее заданных блоков. И у них есть что-то общее с [Голливудом|components#Hollywood-Style]. +Компоненты кардинально влияют на подход к созданию приложений. Они откроют вам новые возможности сборки страниц из готовых блоков. И к тому же у них есть что-то общее с [Голливудом |components#Стиль Голливуда]. -Контейнер DI и конфигурация .[#toc-di-container-and-configuration] -================================================================== +DI-контейнер и конфигурация +=========================== -DI-контейнер (фабрика для объектов) — это сердце всего приложения. +DI-контейнер или фабрика объектов — это сердце всего приложения. -Не волнуйтесь, это не магический черный ящик, как может показаться из предыдущих слов. На самом деле, это один довольно скучный PHP-класс, сгенерированный Nette и хранящийся в каталоге кэша. Он имеет множество методов, названных `createServiceAbcd()`, и каждый из них создает и возвращает объект. Да, есть также метод `createServiceApplication()`, который создаёт `Nette\Application\Application`, который нам понадобился в файле `index.php` для запуска приложения. Существуют также методы подготовки индивидуальных презентеров. И так далее. +Не беспокойтесь, это не какой-то магический черный ящик, как могло показаться из предыдущих строк. На самом деле, это один довольно скучный PHP-класс, который генерирует Nette и сохраняет в каталоге кеша. У него много методов, названных как `createServiceAbcd()`, и каждый из них умеет создавать и возвращать какой-то объект. Да, там есть и метод `createServiceApplication()`, который создает `Nette\Application\Application`, который нам был нужен в файле `index.php` для запуска приложения. И там есть методы, создающие отдельные презентеры. И так далее. -Объекты, которые создает контейнер DI, по какой-то причине называются сервисами. +Объектам, которые создает DI-контейнер, по какой-то причине говорят сервисы. -Особенность этого класса в том, что он программируется не вами, а фреймворком. Он фактически генерирует PHP-код и сохраняет его на диске. Вы просто даете инструкции о том, какие объекты и как именно должен производить контейнер. И эти инструкции записаны в [конфигурационных файлах |bootstrap#di-container-configuration] в [формате NEON|neon:format] и поэтому имеют расширение `.neon`. +Что действительно особенного в этом классе, так это то, что его программируете не вы, а фреймворк. Он действительно генерирует PHP-код и сохраняет его на диск. Вы только даете инструкции, какие объекты должен уметь создавать контейнер и как именно. И эти инструкции записаны в [конфигурационных файлах |bootstrapping#Конфигурация DI-контейнера], для которых используется формат [NEON |neon:format], и поэтому они имеют расширение `.neon`. -Конфигурационные файлы используются исключительно для обучения DI-контейнера. Так, например, если я укажу `expiration: 14 days` в секции [session|http:configuration#session], контейнер DI при создании объекта `Nette\Http\Session`, представляющего сессию, вызовет его метод `setExpiration('14 days')`, и таким образом конфигурация станет реальностью. +Конфигурационные файлы служат исключительно для инструктирования DI-контейнера. Так что, например, если я укажу в секции [session |http:configuration#Сессия] опцию `expiration: 14 days`, то DI-контейнер при создании объекта `Nette\Http\Session`, представляющего сессию, вызовет его метод `setExpiration('14 days')`, и тем самым конфигурация станет реальностью. -Для вас подготовлена целая глава, описывающая, что можно [настроить |nette:configuring] и как [определить свои собственные сервисы |dependency-injection:services]. +Для вас подготовлена целая глава, описывающая, что все можно [настроить |nette:configuring] и как [определить собственные сервисы |dependency-injection:services]. -Как только вы перейдете к созданию сервисов, вы столкнетесь со словом *autowiring* (*автоподключение*). Это гаджет, который невероятно облегчит вашу жизнь. Он может автоматически передавать объекты туда, куда вам нужно (например, в конструкторы ваших классов) без необходимости что-либо делать. Вы увидите, что контейнер DI в Nette — это маленькое чудо. +Как только вы немного разберетесь в создании сервисов, вы столкнетесь со словом [autowiring |dependency-injection:autowiring]. Это фишка, которая невероятным образом упростит вам жизнь. Она умеет автоматически передавать объекты туда, где они вам нужны (например, в конструкторах ваших классов), без необходимости что-либо делать. Вы обнаружите, что DI-контейнер в Nette — это маленькое чудо. -Что дальше? .[#toc-what-next] -============================= +Куда дальше? +============ -Мы рассмотрели основные принципы работы приложений в Nette. Пока очень поверхностно, но вскоре вы погрузитесь в глубины и в итоге создадите замечательные веб-приложения. Где продолжить? Вы пробовали изучить учебник [Создайте свое первое приложение |quickstart:]? +Мы рассмотрели основные принципы приложений в Nette. Пока очень поверхностно, но скоро вы проникнете в глубину и со временем создадите замечательные веб-приложения. Куда двигаться дальше? Вы уже пробовали учебник [Пишем первое приложение |quickstart:]? -В дополнение к вышеперечисленному, Nette имеет целый арсенал [полезных классов |utils:], [слой базы данных |database:] и т. д. Попробуйте целенаправленно просто просмотреть документацию. Или посетите [блог |https://blog.nette.org]. Вы откроете для себя много интересного. +Кроме вышеописанного, Nette располагает целым арсеналом [полезных классов |utils:], [слоем базы данных |database:], и т. д. Попробуйте просто пролистать документацию. Или [блог |https://blog.nette.org]. Вы откроете много интересного. -Пусть этот фреймворк принесёт вам много радости 💙. +Пусть фреймворк приносит вам много радости 💙 diff --git a/application/ru/modules.texy b/application/ru/modules.texy deleted file mode 100644 index b8d3e626c2..0000000000 --- a/application/ru/modules.texy +++ /dev/null @@ -1,148 +0,0 @@ -Модули -****** - -.[perex] -В Nette модули представляют собой логические единицы, из которых состоит приложение. Они включают ведущие, шаблоны, возможно, компоненты и классы моделей. - -Одного компонента для презентаторов и одного для шаблонов будет недостаточно для реальных проектов. Наличие десятков файлов в одной папке по меньшей мере неорганизованно. Как выйти из этого положения? Мы просто разделяем их на подкаталоги на диске и на пространства имен в коде. И это именно то, что делают модули Nette. - -Поэтому давайте забудем о единой папке для ведущих и шаблонов и вместо этого создадим модули, например, `Admin` и `Front`. - -/--pre -app/ -├── Presenters/ -├── Modules/ ← директория с модулями -│ ├── Admin/ ← модуль Admin -│ │ ├── Presenters/ ← его презентеры -│ │ │ ├── DashboardPresenter.php -│ │ │ └── templates/ -│ └── Front/ ← модуль Front -│ └── Presenters/ ← его презентеры -│ └── ... -\-- - -Эта структура каталогов будет отражена в пространствах имен классов, так, например, `DashboardPresenter` будет находиться в пространстве `App\Modules\Admin\Presenters`: - -```php -namespace App\Modules\Admin\Presenters; - -class DashboardPresenter extends Nette\Application\UI\Presenter -{ - // ... -} -``` - -Ведущий `Dashboard` внутри модуля `Admin` обозначается в приложении с помощью двойной точечной нотации как `Admin:Dashboard`, а его действие `default` обозначается как `Admin:Dashboard:default`. -А откуда Nette знает, что `Admin:Dashboard` представляет класс `App\Modules\Admin\Presenters\DashboardPresenter`? Мы говорим об этом, используя [отображение |#Mapping] в конфигурации. -Таким образом, приведенная структура не является фиксированной, и вы можете изменять ее по своему усмотрению. - -Модули, конечно, могут содержать все другие части, помимо презентаторов и шаблонов, такие как компоненты, классы моделей и т.д. - - -Вложенные модули .[#toc-nested-modules] ---------------------------------------- - -Модули не обязательно должны формировать только плоскую структуру, вы также можете создавать, например, подмодули: - -/--pre -app/ -├── Modules/ ← директория с модулями -│ ├── Blog/ ← модуль Blog -│ │ ├── Admin/ ← подмодуль Admin -│ │ │ ├── Presenters/ -│ │ │ └── ... -│ │ └── Front/ ← подмодуль Front -│ │ ├── Presenters/ -│ │ └── ... -│ ├── Forum/ ← модуль Forum -│ │ └── ... -\-- - -Таким образом, модуль `Blog` разбивается на подмодули `Admin` и `Front`. И опять же это будет отражено в пространствах имен, которые будут `App\Modules\Blog\Admin\Presenters` и т.д. Ведущий `Dashboard` внутри подмодуля называется `Blog:Admin:Dashboard`. - -Ветвление может быть настолько глубоким, насколько вы захотите, поэтому вы можете создавать подмодули. - - -Создание ссылок .[#toc-creating-links] --------------------------------------- - -Ссылки в шаблонах ведущего являются относительными по отношению к текущему модулю. Таким образом, ссылка `Foo:default` ведет к ведущему `Foo` в том же модуле, что и текущий ведущий. Например, если текущим модулем является `Front`, то ссылка выглядит следующим образом: - -```latte -odkaz na Front:Product:show -``` - -Ссылка является относительной, даже если имя модуля является ее частью, тогда он считается подмодулем: - -```latte -odkaz na Front:Shop:Product:show -``` - -Абсолютные ссылки записываются аналогично абсолютным путям на диске, но с двоеточиями вместо косых черт. Таким образом, абсолютная ссылка начинается с двоеточия: - -```latte -odkaz na Admin:Product:show -``` - -Чтобы узнать, находимся ли мы в определенном модуле или подмодуле, мы используем функцию `isModuleCurrent(moduleName)`. - -```latte -
  • - ... -
  • -``` - - -Маршрутизация .[#toc-routing] ------------------------------ - -См. [главу о маршрутизации |routing#modules]. - - -Составление карты .[#toc-mapping] ---------------------------------- - -Определяет правила, по которым имя класса выводится из имени ведущего. Мы записываем их в [конфигурацию |configuration] под ключом `application › mapping`. - -Начнем с примера, в котором не используются модули. Мы просто хотим, чтобы классы ведущего имели пространство имен `App\Presenters`. То есть мы хотим, чтобы ведущий, например, `Home` отображался на класс `App\Presenters\HomePresenter`. Этого можно достичь с помощью следующей конфигурации: - -```neon -application: - mapping: - *: App\Presenters\*Presenter -``` - -Имя презентера заменяется звездочкой, и в результате получается название класса. Легко! - -Если мы разделим докладчиков на модули, то для каждого модуля у нас может быть свой маппинг: - -```neon -application: - mapping: - Front: App\Modules\Front\Presenters\*Presenter - Admin: App\Modules\Admin\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -Теперь презентер `Front:Home` определяется классом `App\Modules\Front\HomePresenter`, а презентер `Admin:Dashboard` — `App\AdminModule\DashboardPresenter`. - -Удобнее будет создать общее правило (звездочка), которое заменит первые два правила и добавит дополнительную звездочку только для модуля: - -```neon -application: - mapping: - *: App\Modules\*\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -Но что если мы используем несколько вложенных модулей и у нас есть, например, ведущий `Admin:User:Edit`? В этом случае сегмент со звездочкой, представляющий модуль для каждого уровня, будет просто повторяться, и результатом будет класс `App\Modules\Admin\User\Presenters\EditPresenter`. - -Альтернативной нотацией является использование массива, состоящего из трех сегментов, вместо строки. Эта нотация эквивалентна предыдущей: - -```neon -application: - mapping: - *: [App\Modules, *, Presenters\*Presenter] -``` - -Значение по умолчанию - `*: *Module\*Presenter`. diff --git a/application/ru/multiplier.texy b/application/ru/multiplier.texy index 8a8a77ff7f..15e753817b 100644 --- a/application/ru/multiplier.texy +++ b/application/ru/multiplier.texy @@ -1,30 +1,32 @@ -Multiplier: Динамические компоненты +Multiplier: динамические компоненты *********************************** -Инструмент для динамического создания интерактивных компонентов. +.[perex] +Инструмент для динамического создания интерактивных компонентов -Давайте начнем с типичной проблемы: у нас есть список товаров на сайте электронной коммерции, и мы хотим сопроводить каждый товар формой *добавить в корзину*. Один из способов — обернуть весь листинг в одну форму. Более удобным способом является использование [api:Nette\Application\UI\Multiplier]. +Начнем с типичного примера: у нас есть список товаров в интернет-магазине, и для каждого мы хотим вывести форму для добавления товара в корзину. Один из возможных вариантов — обернуть весь список в одну форму. Однако гораздо более удобный способ предлагает нам [api:Nette\Application\UI\Multiplier]. -Multiplier позволяет определить фабрику для нескольких компонентов. Он основан на принципе вложенных компонентов — каждый компонент, наследующий от [api:Nette\ComponentModel\Container], может содержать другие компоненты. +Multiplier позволяет удобно определить фабрику для нескольких компонентов. Он работает по принципу вложенных компонентов — каждый компонент, наследующий от [api:Nette\ComponentModel\Container], может содержать другие компоненты. -См. [модель компонента|components#Components in Depth] в документации. +.[tip] +См. главу о [модели компонентов |components#Компоненты в глубину] в документации или [лекцию Яна Тврдика |https://www.youtube.com/watch?v=8y3LLexWu-I]. -Multiplier представляет собой родительский компонент, который может динамически создавать свои дочерние компоненты, используя обратный вызов, переданный в конструкторе. См. пример: +Суть Multiplier заключается в том, что он выступает в роли родителя, который может динамически создавать своих потомков с помощью callback-функции, переданной в конструкторе. См. пример: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function () { $form = new Nette\Application\UI\Form; - $form->addInteger('amount', 'Amount:') + $form->addInteger('count', 'Количество товара:') ->setRequired(); - $form->addSubmit('send', 'Add to cart'); + $form->addSubmit('send', 'Добавить в корзину'); return $form; }); } ``` -В шаблоне мы можем отобразить форму для каждого товара — и каждая форма действительно будет уникальным компонентом. +Теперь мы можем в шаблоне просто для каждого товара отрисовать форму — и каждая будет действительно уникальным компонентом. ```latte {foreach $items as $item} @@ -35,23 +37,23 @@ protected function createComponentShopForm(): Multiplier {/foreach} ``` -Аргумент, переданный в тег `{control}`, гласит: +Аргумент, переданный в теге `{control}`, имеет формат, который говорит: -1. получить компонент `shopForm` -2. вернуть своему потомку `$item->id` +1. получи компонент `shopForm` +2. и из него получи потомка `$item->id` -При первом вызове **1.** компонент `shopForm` ещё не существует, поэтому для его создания вызывается метод `createComponentShopForm`. Затем вызывается анонимная функция, переданная в качестве параметра в Multiplier, и создается форма. +При первом вызове пункта **1.** `shopForm` еще не существует, поэтому вызывается его фабрика `createComponentShopForm`. На полученном компоненте (экземпляре Multiplier) затем вызывается фабрика конкретной формы — это анонимная функция, которую мы передали Multiplier в конструкторе. -В последующих итерациях `foreach` метод `createComponentShopForm` больше не вызывается, поскольку компонент уже существует. Но поскольку мы ссылаемся на другого потомка (`$item->id` варьируется между итерациями), анонимная функция вызывается снова и создается новая форма. +В следующей итерации foreach метод `createComponentShopForm` уже не будет вызван (компонент существует), но поскольку мы ищем другого его потомка (`$item->id` будет разным в каждой итерации), снова будет вызвана анонимная функция и вернет нам новую форму. -Последнее, что нужно сделать, это убедиться, что форма действительно добавляет правильный товар в корзину, потому что в текущем состоянии все формы равны, и мы не можем различить, к каким продуктам они принадлежат. Для этого мы можем использовать свойство класса Multiplier (и вообще любого метода фабрики компонентов в фреймворке Nette), что каждый метод фабрики компонентов получает имя созданного компонента в качестве первого аргумента. В нашем случае это будет `$item->id`, что именно то, что нам нужно, чтобы различать отдельные товары. Всё, что вам нужно сделать, это изменить код для создания формы: +Единственное, что остается, — это убедиться, что форма добавит в корзину действительно тот товар, который нужно — в настоящее время форма для каждого товара абсолютно одинакова. Нам поможет свойство Multiplier (и вообще любой фабрики компонентов в Nette Framework), а именно то, что каждая фабрика в качестве своего первого аргумента получает имя создаваемого компонента. В нашем случае это будет `$item->id`, что является именно той информацией, которая нам нужна. Достаточно немного изменить создание формы: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function ($itemId) { $form = new Nette\Application\UI\Form; - $form->addInteger('amount', 'Количество:') + $form->addInteger('count', 'Количество товара:') ->setRequired(); $form->addHidden('itemId', $itemId); $form->addSubmit('send', 'Добавить в корзину'); diff --git a/application/ru/presenters.texy b/application/ru/presenters.texy index 2b382c7c88..0c75055dae 100644 --- a/application/ru/presenters.texy +++ b/application/ru/presenters.texy @@ -3,37 +3,37 @@
    -Научимся создавать презентеры и шаблоны в Nette. После прочтения этой статьи вы узнаете: +Мы познакомимся с тем, как в Nette пишутся презентеры и шаблоны. После прочтения вы будете знать: - как работает презентер -- что такое постоянные параметры -- как отрендерить шаблон +- что такое персистентные параметры +- как отрисовываются шаблоны
    -[Мы уже знаем |how-it-works#Nette-Application], что презентер это класс, который представляет конкретную страницу веб-приложения, такую как главная страница; страница товара в интернет-магазине; форма авторизации; карта сайта и т. д. Приложение может иметь от одного до тысячи презентеров. В других фреймворках они также известны как контроллеры. +[Мы уже знаем |how-it-works#Nette Application], что презентер — это класс, представляющий какую-либо конкретную страницу веб-приложения, например, главную страницу; товар в интернет-магазине; форму входа; фид карты сайта и т. д. Приложение может иметь от одного до тысяч презентеров. В других фреймворках их также называют контроллерами. -Обычно термин *презентер* соотносится с потомком класса [api:Nette\Application\UI\Presenter], который подходит для веб-интерфейсов. Мы обсудим этот класс в оставшейся части этой главы. В общем смысле, презентер — это любой объект, реализующий интерфейс [api:Nette\Application\IPresenter]. +Обычно под понятием презентер подразумевается потомок класса [api:Nette\Application\UI\Presenter], который подходит для генерации веб-интерфейсов и которому мы будем уделять внимание в оставшейся части этой главы. В общем смысле презентер — это любой объект, реализующий интерфейс [api:Nette\Application\IPresenter]. -Жизненный цикл презентера .[#toc-life-cycle-of-presenter] -========================================================= +Жизненный цикл презентера +========================= Задача презентера — обработать запрос и вернуть ответ (это может быть HTML-страница, изображение, перенаправление и т. д.). -Итак, в начале — запрос. Это не непосредственно HTTP-запрос, а объект [api:Nette\Application\Request], в который HTTP-запрос был преобразован с помощью маршрутизатора. Обычно мы не сталкиваемся с этим объектом, потому что презентер ловко делегирует обработку запроса специальным методам, которые мы сейчас увидим. +То есть вначале ему передается запрос. Это не непосредственно HTTP-запрос, а объект [api:Nette\Application\Request], в который был преобразован HTTP-запрос с помощью маршрутизатора. С этим объектом мы обычно не сталкиваемся, так как презентер умно делегирует обработку запроса другим методам, которые мы сейчас покажем. -[* lifecycle.svg *] *** *Жизненный цикл презентера* .<> +[* lifecycle.svg *] *** Жизненный цикл презентера .<> -На рисунке показан список методов, которые вызываются последовательно сверху вниз, если они существуют. Все они необязательные, мы можем иметь совершенно пустой презентер без единого метода и построить на нем простой статический веб. +Изображение представляет список методов, которые последовательно вызываются сверху вниз, если они существуют. Ни один из них не обязан существовать, у нас может быть совершенно пустой презентер без единого метода, и на нем можно построить простой статический сайт. `__construct()` --------------- -Конструктор не совсем относится к жизненному циклу презентера, поскольку вызывается в момент создания объекта. Но мы упоминаем его из-за важности, поскольку он используется для передачи зависимостей. +Конструктор не совсем относится к жизненному циклу презентера, так как вызывается в момент создания объекта. Но мы упоминаем его из-за важности. Конструктор (вместе с [методом inject |best-practices:inject-method-attribute]) служит для передачи зависимостей. -Презентер не должен заботиться о бизнес-логике приложения, писать и читать из базы данных, выполнять вычисления и т. д. Это задача для классов из слоя, который мы называем моделью. Например, класс `ArticleRepository` может отвечать за загрузку и сохранение статей. Для того чтобы презентер мог его использовать, он [передается с помощью внедрения зависимостей |dependency-injection:passing-dependencies]: +Презентер не должен заниматься бизнес-логикой приложения, записывать и читать из базы данных, выполнять вычисления и т. д. Для этого существуют классы из слоя, который мы называем моделью. Например, класс `ArticleRepository` может отвечать за загрузку и сохранение статей. Чтобы презентер мог с ним работать, он получает его [через внедрение зависимостей |dependency-injection:passing-dependencies]: ```php @@ -50,44 +50,44 @@ class ArticlePresenter extends Nette\Application\UI\Presenter `startup()` ----------- -Сразу после получения запроса вызывается метод `startup()`. Вы можете использовать его для инициализации свойств, проверки привилегий пользователя и т. д. Требуется всегда вызывать предка `parent::startup()`. +Сразу после получения запроса вызывается метод `startup()`. Вы можете использовать его для инициализации свойств, проверки прав пользователя и т. д. Требуется, чтобы метод всегда вызывал предка `parent::startup()`. `action(args...)` .{toc: action()} -------------------------------------------------- -Аналогичен методу `render()`. В то время как `render()` предназначена для подготовки данных для определенного шаблона, который впоследствии рендерится, в `action()` запрос обрабатывается без последующего рендеринга шаблона. Например, данные обрабатываются, пользователь входит или выходит из системы и так далее, а затем [перенаправляется в другое место |#Redirection]. +Аналог метода `render()`. В то время как `render()` предназначен для подготовки данных для конкретного шаблона, который затем будет отрисован, в `action()` обрабатывается запрос без связи с отрисовкой шаблона. Например, обрабатываются данные, пользователь входит в систему или выходит из нее, и так далее, а затем [перенаправляется в другое место |#Перенаправление]. -Важно, что `action()` вызывается перед `render()`, поэтому внутри него мы можем, возможно, изменить следующий ход жизненного цикла, т. е. изменить шаблон, который будет отображаться, а также метод `render()`, который будет вызываться, используя `setView('otherView')`. +Важно, что `action()` вызывается раньше, чем `render()`, поэтому в нем мы можем при необходимости изменить дальнейший ход событий, т. е. изменить шаблон, который будет отрисовываться, а также метод `render()`, который будет вызываться. Это делается с помощью `setView('jineView')`. -В метод передаются параметры из запроса. Можно и рекомендуется указывать типы для параметров, например `actionShow(int $id, string $slug = null)` — если параметр `id` отсутствует или если он не является целым числом, презентер возвращает [ошибку 404|#Error-404-etc] и завершает операцию. +Методу передаются параметры из запроса. Возможно и рекомендуется указывать типы параметров, например, `actionShow(int $id, ?string $slug = null)` — если параметр `id` будет отсутствовать или не будет целым числом, презентер вернет [ошибку 404 |#Ошибка 404 и т.п] и завершит работу. `handle(args...)` .{toc: handle()} -------------------------------------------------- -Этот метод обрабатывает так называемые сигналы, о которых мы поговорим в главе о [Компонентах |components#Signal]. Он предназначен в основном для компонентов и обработки AJAX-запросов. +Метод обрабатывает так называемые сигналы, с которыми мы познакомимся в главе, посвященной [компонентам |components#Сигнал]. Он предназначен в основном для компонентов и обработки AJAX-запросов. -Параметры передаются методу, как в случае `action()`, включая проверку типа. +Методу передаются параметры из запроса, как и в случае `action()`, включая проверку типов. `beforeRender()` ---------------- -Метод `beforeRender`, как следует из названия, вызывается перед каждым методом `render()`. Используется для общей настройки шаблона, передачи переменных для верстки и так далее. +Метод `beforeRender`, как следует из названия, вызывается перед каждым методом `render()`. Используется для общей конфигурации шаблона, передачи переменных для макета и т. п. `render(args...)` .{toc: render()} ---------------------------------------------- -Место, где мы готовим шаблон к последующему рендерингу, передаем ему данные и т. д. +Место, где мы подготавливаем шаблон к последующей отрисовке, передаем ему данные и т. д. -Параметры передаются методу, как в случае `action()`, включая проверку типа. +Методу передаются параметры из запроса, как и в случае `action()`, включая проверку типов. ```php public function renderShow(int $id): void { - // мы получаем данные из модели и передаем их в шаблон + // получаем данные из модели и передаем в шаблон $this->template->article = $this->articles->getById($id); } ``` @@ -96,7 +96,7 @@ public function renderShow(int $id): void `afterRender()` --------------- -Метод `afterRender`, как следует из названия, вызывается после каждого метода `render()`. Он используется довольно редко. +Метод `afterRender`, как снова следует из названия, вызывается после каждого метода `render()`. Используется довольно редко. `shutdown()` @@ -105,95 +105,95 @@ public function renderShow(int $id): void Вызывается в конце жизненного цикла презентера. -**Хороший совет, прежде чем мы продолжим**. Как вы видите, презентер может обрабатывать больше действий/просмотров, т. е. имеют больше методов `render()`. Но мы рекомендуем разрабатывать презентеры с одним или как можно меньшим количеством действий. +**Хороший совет, прежде чем идти дальше**. Презентер, как видно, может обслуживать несколько действий/представлений, то есть иметь несколько методов `render()`. Но мы рекомендуем проектировать презентеры с одним или как можно меньшим количеством действий. -Отправка ответа .[#toc-sending-a-response] -========================================== +Отправка ответа +=============== -Обычно ответом ведущего является [рендеринг шаблона с HTML-страницей|templates], но это также может быть отправка файла, JSON или даже перенаправление на другую страницу. +Ответом презентера обычно является [отрисовка шаблона с HTML-страницей |templates], но это также может быть отправка файла, JSON или, например, перенаправление на другую страницу. -В любой момент жизненного цикла мы можем использовать один из следующих методов для отправки ответа и завершения работы презентера: +В любой момент жизненного цикла мы можем одним из следующих методов отправить ответ и одновременно завершить работу презентера: -- [Перенаправления |#Redirection] `redirect()`, `redirectPermanent()`, `redirectUrl()` и `forward()`. -- `error()` завершает работу презентера [из-за ошибки |#Error-404-etc]. -- `sendJson($data)` выходит из презентера и отправляет данные в формате JSON -- `sendTemplate()` завершает работу презентера и сразу же выполняет рендеринг шаблона -- `sendResponse($response)` выходит из презентера и отправляет [собственный ответ |#Ответы]. -- `terminate()` завершает работу презентера без ответа +- `redirect()`, `redirectPermanent()`, `redirectUrl()` и `forward()` [перенаправляют |#Перенаправление] +- `error()` завершает презентер [из-за ошибки |#Ошибка 404 и т.п] +- `sendJson($data)` завершает презентер и [отправляет данные |#Отправка JSON] в формате JSON +- `sendTemplate()` завершает презентер и немедленно [отрисовывает шаблон |templates] +- `sendResponse($response)` завершает презентер и отправляет [собственный ответ |#Ответы] +- `terminate()` завершает презентер без ответа -**Сейчас что-то важное**: если мы явно не говорим, какой ответ должен отправить презентер, ответом будет [рендеринг шаблонов |#Рендеринг шаблонов] HTML. Почему? Ну, потому что в 99% случаев мы хотим отрендерить шаблон, поэтому презентер принимает такое поведение по умолчанию и хочет облегчить нашу работу. +Если вы не вызовете ни один из этих методов, презентер автоматически приступит к отрисовке шаблона. Почему? Потому что в 99% случаев мы хотим отрисовать шаблон, поэтому презентер воспринимает это поведение как стандартное и хочет облегчить нам работу. -Создание ссылок .[#toc-creating-links] -====================================== +Создание ссылок +=============== -У презентера есть метод `link()`, который используется для создания URL-ссылок на другие презентеры. Первым параметром является целевой презентер и действие, затем следуют аргументы, которые могут быть переданы в виде массива: +Презентер располагает методом `link()`, с помощью которого можно создавать URL-ссылки на другие презентеры. Первым параметром является целевой презентер и действие, за ним следуют передаваемые аргументы, которые могут быть указаны как массив: ```php $url = $this->link('Product:show', $id); -$url = $this->link('Product:show', [$id, 'lang' => 'en']); +$url = $this->link('Product:show', [$id, 'lang' => 'cs']); ``` -В шаблоне мы создаем ссылки на другие презентеры и действия следующим образом: +В шаблоне ссылки на другие презентеры и действия создаются следующим образом: ```latte -страница товара +деталь продукта ``` -Просто напишите знакомую пару `Presenter:action` вместо реального URL и включите любые параметры. Хитрость заключается в `n:href`, который говорит, что этот атрибут будет обработан Latte и сгенерирует настоящий URL. В Nette вам вообще не нужно думать об URL-адресах, только о презентерах и действиях. +Просто вместо реального URL вы пишете известную пару `Presenter:action` и указываете возможные параметры. Хитрость в `n:href`, которое говорит, что этот атрибут обработает Latte и сгенерирует реальный URL. В Nette вам вообще не нужно думать об URL, только о презентерах и действиях. -Для получения дополнительной информации см. [Создание ссылок |creating-links]. +Дополнительную информацию можно найти в главе [Создание URL-ссылок |creating-links]. -Перенаправление .[#toc-redirection] -=================================== +Перенаправление +=============== -Для перехода к другому презентеру используются методы `redirect()` и `forward()`, которые имеют очень похожий синтаксис с методом [link() |#Creating-Links]. +Для перехода на другой презентер служат методы `redirect()` и `forward()`, которые имеют очень похожий синтаксис, как и метод [link() |#Создание ссылок]. -Функция `forward()` переключает на новый презентер немедленно без перенаправления HTTP: +Метод `forward()` переходит на новый презентер немедленно без HTTP-перенаправления: ```php $this->forward('Product:show'); ``` -Пример временного перенаправления с HTTP-кодом 302 или 303: +Пример так называемого временного перенаправления с HTTP-кодом 302 (или 303, если метод текущего запроса POST): ```php $this->redirect('Product:show', $id); ``` -Для достижения постоянного перенаправления с HTTP-кодом 301 используйте: +Постоянное перенаправление с HTTP-кодом 301 достигается так: ```php $this->redirectPermanent('Product:show', $id); ``` -Вы можете перенаправить на другой URL вне приложения с помощью метода `redirectUrl()`: +На другой URL вне приложения можно перенаправить методом `redirectUrl()`. В качестве второго параметра можно указать HTTP-код, по умолчанию 302 (или 303, если метод текущего запроса POST): ```php $this->redirectUrl('https://nette.org'); ``` -Перенаправление немедленно завершает жизненный цикл презентера, выбрасывая так называемое исключение молчаливого завершения `Nette\Application\AbortException`. +Перенаправление немедленно завершает работу презентера, выбрасывая так называемое тихое завершающее исключение `Nette\Application\AbortException`. -Перед перенаправлением можно отправить [флэш-сообщение |#Flash-Messages], которое будет отображаться в шаблоне после перенаправления. +Перед перенаправлением можно отправить [flash-сообщение |#Flash-сообщения], то есть сообщения, которые будут отображены в шаблоне после перенаправления. -Флэш-сообщения .[#toc-flash-messages] -===================================== +Flash-сообщения +=============== -Это сообщения, которые обычно информируют о результате операции. Важной особенностью флэш-сообщений является то, что они доступны в шаблоне даже после перенаправления. Даже после отображения они будут оставаться живыми еще 30 секунд — например, на случай, если пользователь непреднамеренно обновит страницу — сообщение не будет потеряно. +Это сообщения, обычно информирующие о результате какой-либо операции. Важной особенностью flash-сообщений является то, что они доступны в шаблоне даже после перенаправления. Даже после отображения они остаются активными еще 30 секунд — например, на случай, если из-за ошибки передачи пользователь обновит страницу — сообщение не исчезнет сразу. -Просто вызовите метод [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] и презентер позаботится о передаче сообщения в шаблон. Первый аргумент — текст сообщения, а второй необязательный аргумент — его тип (ошибка, предупреждение, информация и т. д.). Метод `flashMessage()` возвращает экземпляр флэш-сообщения, чтобы мы могли добавить дополнительную информацию. +Достаточно вызвать метод [flashMessage() |api:Nette\Application\UI\Control::flashMessage()], и о передаче в шаблон позаботится презентер. Первым параметром является текст сообщения, а необязательным вторым параметром — его тип (error, warning, info и т. п.). Метод `flashMessage()` возвращает экземпляр flash-сообщения, которому можно добавлять дополнительную информацию. ```php -$this->flashMessage('Item was removed.'); -$this->redirect(/* ... */); +$this->flashMessage('Элемент был удален.'); +$this->redirect(/* ... */); // и перенаправляем ``` -В шаблоне эти сообщения доступны в переменной `$flashes` как объекты `stdClass`, которые содержат свойства `message` (текст сообщения), `type` (тип сообщения) и могут содержать уже упомянутую информацию о пользователе. Мы выводим их следующим образом: +В шаблоне эти сообщения доступны в переменной `$flashes` как объекты `stdClass`, которые содержат свойства `message` (текст сообщения), `type` (тип сообщения) и могут содержать уже упомянутую пользовательскую информацию. Отрисуем их, например, так: ```latte {foreach $flashes as $flash} @@ -202,10 +202,10 @@ $this->redirect(/* ... */); ``` -Ошибка 404 и т. д. .[#toc-error-404-etc] -======================================== +Ошибка 404 и т.п. +================= -Когда мы не можем выполнить запрос, потому что, например, статья, которую мы хотим отобразить, не существует в базе данных, мы выбросим ошибку 404, используя метод `error(string $message = null, int $httpCode = 404)`, который представляет HTTP-ошибку 404: +Если невозможно выполнить запрос, например, из-за того, что статья, которую мы хотим отобразить, не существует в базе данных, мы выбрасываем ошибку 404 методом `error(?string $message = null, int $httpCode = 404)`. ```php public function renderShow(int $id): void @@ -218,14 +218,13 @@ public function renderShow(int $id): void } ``` -Код ошибки HTTP может быть передан в качестве второго параметра, по умолчанию это 404. Метод работает, выбрасывая исключение `Nette\Application\BadRequestException`, после чего `Application` передает управление презентеру ошибки. Его задача — отобразить страницу, информирующую об ошибке. -Преселектор ошибок устанавливается в [конфигурация приложения |configuration]. +HTTP-код ошибки можно передать вторым параметром, по умолчанию 404. Метод работает так, что выбрасывает исключение `Nette\Application\BadRequestException`, после чего `Application` передает управление error-презентеру. Это презентер, задачей которого является отображение страницы, информирующей о возникшей ошибке. Настройка error-презентера выполняется в [конфигурации application |configuration]. -Отправка JSON .[#toc-sending-json] -================================== +Отправка JSON +============= -Пример действия-метода, который отправляет данные в формате JSON и выходит из ведущего: +Пример action-метода, который отправляет данные в формате JSON и завершает работу презентера: ```php public function actionData(): void @@ -236,33 +235,59 @@ public function actionData(): void ``` -Постоянные параметры .[#toc-persistent-parameters] -================================================== +Параметры запроса .{data-version:3.1.14} +======================================== + +Презентер, а также каждый компонент, получает свои параметры из HTTP-запроса. Их значение можно узнать методами `getParameter($name)` или `getParameters()`. Значениями являются строки или массивы строк, это, по сути, необработанные данные, полученные непосредственно из URL. + +Для большего удобства рекомендуем сделать параметры доступными через свойства. Достаточно пометить их атрибутом `#[Parameter]`: + +```php +use Nette\Application\Attributes\Parameter; // эта строка важна + +class HomePresenter extends Nette\Application\UI\Presenter +{ + #[Parameter] + public string $theme; // должно быть public +} +``` + +У свойства рекомендуется указывать тип данных (например, `string`), и Nette автоматически преобразует значение в соответствии с ним. Значения параметров также можно [валидировать |#Валидация параметров]. + +При создании ссылки можно напрямую установить значение параметра: + +```latte +нажмите +``` + + +Персистентные параметры +======================= -Постоянные параметры используются для сохранения состояния между различными запросами. Их значение остается неизменным даже после нажатия на ссылку. В отличие от данных сессии, они передаются в URL. Это происходит полностью автоматически, поэтому нет необходимости явно указывать их в `link()` или `n:href`. +Персистентные параметры служат для поддержания состояния между различными запросами. Их значение остается неизменным даже после нажатия на ссылку. В отличие от данных в сессии, они передаются в URL. И это происходит полностью автоматически, то есть нет необходимости явно указывать их в `link()` или `n:href`. -Пример использования? У вас есть многоязычное приложение. Фактический язык - это параметр, который всегда должен быть частью URL. Но было бы невероятно утомительно указывать его в каждой ссылке. Поэтому вы сделаете его постоянным параметром с именем `lang`, и он сам себя перенесет. Круто! +Пример использования? У вас многоязычное приложение. Текущий язык — это параметр, который должен постоянно присутствовать в URL. Но было бы невероятно утомительно указывать его в каждой ссылке. Так что вы делаете его персистентным параметром `lang`, и он будет передаваться сам. Отлично! -Создать постоянный параметр в Nette очень просто. Просто создайте публичное свойство и пометьте его атрибутом: (ранее использовалось `/** @persistent */` ). +Создание персистентного параметра в Nette невероятно просто. Достаточно создать публичное свойство и пометить его атрибутом: (ранее использовалось `/** @persistent */`) ```php -use Nette\Application\Attributes\Persistent; // эта строка важна +use Nette\Application\Attributes\Persistent; // эта строка важна class ProductPresenter extends Nette\Application\UI\Presenter { #[Persistent] - public string $lang; // должны быть публичными + public string $lang; // должно быть public } ``` -Если `$this->lang` имеет значение, например, `'en'`, то ссылки, созданные с помощью `link()` или `n:href`, также будут содержать параметр `lang=en`. И когда ссылка будет щелкнута, она снова станет `$this->lang = 'en'`. +Если `$this->lang` будет иметь значение, например, `'en'`, то и ссылки, созданные с помощью `link()` или `n:href`, будут содержать параметр `lang=en`. И после нажатия на ссылку снова будет `$this->lang = 'en'`. -Для свойств рекомендуется указывать тип данных (например, `string`), а также можно указать значение по умолчанию. Значения параметров могут быть [проверены |#Validation of Persistent Parameters]. +У свойства рекомендуется указывать тип данных (например, `string`), и вы можете указать значение по умолчанию. Значения параметров можно [валидировать |#Валидация параметров]. -Постоянные параметры по умолчанию передаются между всеми действиями данного ведущего. Чтобы передать их между несколькими ведущими, необходимо определить их либо: +Персистентные параметры по умолчанию передаются между всеми действиями данного презентера. Чтобы они передавались и между несколькими презентерами, их нужно определить либо: -- в общем предке, от которого наследуют ведущие -- в трейте, который используют ведущие: +- в общем предке, от которого наследуют презентеры +- в трейте, который используют презентеры: ```php trait LanguageAware @@ -277,48 +302,42 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -Вы можете изменить значение постоянного параметра при создании ссылки: +При создании ссылки можно изменить значение персистентного параметра: ```latte -detail in Czech +деталь на чешском ``` -Или его можно *сбросить*, т.е. удалить из URL. Тогда он примет значение по умолчанию: +Или его можно *сбросить*, то есть удалить из URL. Тогда он примет свое значение по умолчанию: ```latte -click +нажмите ``` -Интерактивные компоненты .[#toc-interactive-components] -======================================================= +Интерактивные компоненты +======================== -У презентеров есть встроенная система компонентов. Компоненты — это отдельные многократно используемые единицы, которые мы помещаем в презентеры. Это могут быть [формы|forms:in-presenter], сетки данных, меню, в общем, всё, что имеет смысл использовать многократно. +Презентеры имеют встроенную систему компонентов. Компоненты — это отдельные повторно используемые единицы, которые мы вставляем в презентеры. Это могут быть [формы |forms:in-presenter], датагриды, меню, в общем, все, что имеет смысл использовать повторно. -Как размещаются и впоследствии используются компоненты в презентере? Это объясняется в главе [компоненты|components]. Вы даже узнаете, какое отношение они имеют к Голливуду. +Как компоненты вставляются в презентер и затем используются? Это вы узнаете в главе [Компоненты |components]. Вы даже узнаете, что у них общего с Голливудом. -Где можно приобрести некоторые компоненты? На странице [Componette |https://componette.org] вы можете найти некоторые компоненты с открытым исходным кодом и другие дополнения для Nette, которые создаются и распространяются сообществом фреймворка Nette. +А где я могу получить компоненты? На странице [Componette |https://componette.org/search/component] вы найдете open-source компоненты, а также множество других дополнений для Nette, которые разместили добровольцы из сообщества вокруг фреймворка. -Углубляемся .[#toc-going-deeper] -================================ +Идем в глубину +============== .[tip] -Того, что мы показали до сих пор в этой главе, вероятно, будет достаточно. Следующие строки предназначены для тех, кто интересуется презентерами досконально и хочет знать всё. +Того, что мы до сих пор показали в этой главе, вам, скорее всего, будет вполне достаточно. Следующие строки предназначены для тех, кто интересуется презентерами в глубину и хочет знать абсолютно все. -Требования и параметры .[#toc-requirement-and-parameters] ---------------------------------------------------------- +Валидация параметров +-------------------- -Запрос, обрабатываемый ведущим, представляет собой объект [api:Nette\Application\Request] и возвращается методом ведущего `getRequest()`. Он включает в себя массив параметров, и каждый из них принадлежит либо какому-то из компонентов, либо непосредственно ведущему (который на самом деле тоже является компонентом, хотя и специальным). Поэтому Nette перераспределяет параметры и передает их между отдельными компонентами (и ведущим), вызывая метод `loadState(array $params)`. Параметры могут быть получены методом `getParameters(): array`, индивидуально с помощью `getParameter($name)`. Значения параметров представляют собой строки или массивы строк, в основном это необработанные данные, полученные непосредственно из URL. +Значения [параметров запроса |#Параметры запроса] и [персистентных параметров |#Персистентные параметры], полученные из URL, записываются в свойства методом `loadState()`. Он также проверяет, соответствует ли тип данных, указанный у свойства, иначе отвечает ошибкой 404, и страница не отображается. - -Валидация постоянных параметров .[#toc-validation-of-persistent-parameters] ---------------------------------------------------------------------------- - -Значения [постоянных параметров |#persistent parameters], полученных из URL, записываются в свойства методом `loadState()`. Он также проверяет, соответствует ли тип данных, указанный в свойстве, в противном случае выдается ошибка 404 и страница не отображается. - -Никогда не доверяйте слепо постоянным параметрам, так как они могут быть легко перезаписаны пользователем в URL. Например, так мы проверяем, входит ли `$this->lang` в число поддерживаемых языков. Хороший способ сделать это - переопределить метод `loadState()`, упомянутый выше: +Никогда слепо не доверяйте параметрам, потому что они могут быть легко изменены пользователем в URL. Так, например, мы проверим, что язык `$this->lang` находится среди поддерживаемых. Подходящий способ — переопределить упомянутый метод `loadState()`: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -329,7 +348,7 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function loadState(array $params): void { parent::loadState($params); // здесь устанавливается $this->lang - // после проверки значения пользователя: + // следует собственная проверка значения: if (!in_array($this->lang, ['en', 'cs'])) { $this->error(); } @@ -338,31 +357,33 @@ class ProductPresenter extends Nette\Application\UI\Presenter ``` -Сохранение и восстановление запроса .[#toc-save-and-restore-the-request] ------------------------------------------------------------------------- +Сохранение и восстановление запроса +----------------------------------- -Вы можете сохранить текущий запрос в сессии или восстановить его из сессии и позволить презентеру выполнить его снова. Это полезно, например, когда пользователь заполняет форму, а срок действия его логина истекает. Чтобы не потерять данные, перед перенаправлением на страницу регистрации мы сохраняем текущий запрос в сессию с помощью функции `$reqId = $this->storeRequest()`, которая возвращает идентификатор в виде короткой строки и передает его в качестве параметра презентеру для регистрации. +Запрос, который обрабатывает презентер, является объектом [api:Nette\Application\Request] и возвращается методом презентера `getRequest()`. -После входа в систему мы вызываем метод `$this->restoreRequest($reqId)`, который забирает запрос у сессии и пересылает его ей. Метод проверяет, что запрос был создан тем же пользователем, который сейчас вошел в систему. Если другой пользователь вошел в систему или ключ недействителен, он ничего не делает, и программа продолжает работу. +Текущий запрос можно сохранить в сессии или, наоборот, восстановить из нее и позволить презентеру снова его выполнить. Это полезно, например, в ситуации, когда пользователь заполняет форму, и у него истекает сессия. Чтобы не потерять данные, перед перенаправлением на страницу входа мы сохраняем текущий запрос в сессию с помощью `$reqId = $this->storeRequest()`, который возвращает его идентификатор в виде короткой строки, и передаем его как параметр презентеру входа. -См. главу [Как вернуться на предыдущую страницу |best-practices:restore-request]. +После входа мы вызываем метод `$this->restoreRequest($reqId)`, который извлекает запрос из сессии и перенаправляет на него. Метод при этом проверяет, что запрос создал тот же пользователь, который сейчас вошел в систему. Если вошел другой пользователь или ключ недействителен, он ничего не делает, и программа продолжает работу. +Посмотрите руководство [Как вернуться к предыдущей странице |best-practices:restore-request]. -Канонизация .[#toc-canonization] --------------------------------- -У презентеров есть одна действительно замечательная функция, которая улучшает SEO. Они автоматически предотвращают существование дублирующего контента на разных URL-адресах. Если несколько URL-адресов ведут к определенному месту назначения, например. `/index` и `/index?page=1`, фреймворк назначает один из них основным (каноническим) и перенаправляет на него остальные с помощью HTTP-кода 301. Благодаря этому поисковые системы не индексируют страницы дважды и не ослабляют их рейтинг. +Канонизация +----------- + +Презентеры обладают одной действительно замечательной особенностью, которая способствует лучшему SEO (оптимизации для поисковых систем). Они автоматически предотвращают существование дублирующегося контента на разных URL. Если к определенной цели ведет несколько URL-адресов, например, `/index` и `/index?page=1`, фреймворк определяет один из них как основной (канонический) и остальные перенаправляет на него с помощью HTTP-кода 301. Благодаря этому поисковые системы не индексируют страницы дважды и не размывают их page rank. -Этот процесс называется канонизацией. Канонический URL — это URL, сгенерированный [маршрутом |routing], обычно первый подходящий маршрут в коллекции. +Этот процесс называется канонизацией. Каноническим URL является тот, который генерирует [маршрутизатор |routing], как правило, это первый соответствующий маршрут в коллекции. -Канонизация включена по умолчанию и может быть отключена с помощью `$this->autoCanonicalize = false`. +Канонизация включена по умолчанию и может быть отключена через `$this->autoCanonicalize = false`. -Перенаправление не происходит при запросе AJAX или POST, так как это приведет к потере данных или не принесет никакой пользы для SEO. +Перенаправление не происходит при AJAX- или POST-запросе, так как это привело бы к потере данных или не имело бы дополнительной ценности с точки зрения SEO. -Вы также можете вызвать канонизацию вручную с помощью метода `canonicalize()`, который, как и метод `link()`, получает в качестве аргументов презентера, действия и параметры. Он создает ссылку и сравнивает её с текущим URL. Если они отличаются, то происходит перенаправление на сгенерированную ссылку. +Канонизацию можно вызвать и вручную с помощью метода `canonicalize()`, которому, подобно методу `link()`, передается презентер, действие и параметры. Он создает ссылку и сравнивает ее с текущим URL-адресом. Если они отличаются, он перенаправляет на сгенерированную ссылку. ```php -public function actionShow(int $id, string $slug = null): void +public function actionShow(int $id, ?string $slug = null): void { $realSlug = $this->facade->getSlugForId($id); // перенаправляет, если $slug отличается от $realSlug @@ -371,10 +392,10 @@ public function actionShow(int $id, string $slug = null): void ``` -События .[#toc-events] ----------------------- +События +------- -Помимо методов `startup()`, `beforeRender()` и `shutdown()`, которые вызываются в рамках жизненного цикла презентера, можно определить другие функции, которые будут вызываться автоматически. Презентер определяет так называемые [события |nette:glossary#Events], а вы добавляете их обработчики в массивы `$onStartup`, `$onRender` и `$onShutdown`. +Кроме методов `startup()`, `beforeRender()` и `shutdown()`, которые вызываются как часть жизненного цикла презентера, можно определить еще другие функции, которые должны вызываться автоматически. Презентер определяет так называемые [события |nette:glossary#События Events], обработчики которых вы добавляете в массивы `$onStartup`, `$onRender` и `$onShutdown`. ```php class ArticlePresenter extends Nette\Application\UI\Presenter @@ -388,34 +409,34 @@ class ArticlePresenter extends Nette\Application\UI\Presenter } ``` -Обработчики в массиве `$onStartup` вызываются непосредственно перед методом `startup()`, затем `$onRender` между `beforeRender()` и `render()` и, наконец, `$onShutdown` непосредственно перед `shutdown()`. +Обработчики в массиве `$onStartup` вызываются непосредственно перед методом `startup()`, далее `$onRender` между `beforeRender()` и `render()`, и наконец `$onShutdown` непосредственно перед `shutdown()`. -Ответы .[#toc-responses] ------------------------- +Ответы +------ -Ответ, возвращаемый презентером, представляет собой объект, реализующий интерфейс [api:Nette\Application\Response]. Есть несколько готовых ответов: +Ответ, который возвращает презентер, является объектом, реализующим интерфейс [api:Nette\Application\Response]. Доступно несколько готовых ответов: -- [api:Nette\Application\Responses\CallbackResponse] — посылает обратный вызов -- [api:Nette\Application\Responses\FileResponse] — посылает файл -- [api:Nette\Application\Responses\ForwardResponse] — forward () -- [api:Nette\Application\Responses\JsonResponse] — посылает JSON -- [api:Nette\Application\Responses\RedirectResponse] — перенаправляет -- [api:Nette\Application\Responses\TextResponse] — посылает текст -- [api:Nette\Application\Responses\VoidResponse] — пустой ответ +- [api:Nette\Application\Responses\CallbackResponse] - отправляет callback +- [api:Nette\Application\Responses\FileResponse] - отправляет файл +- [api:Nette\Application\Responses\ForwardResponse] - forward() +- [api:Nette\Application\Responses\JsonResponse] - отправляет JSON +- [api:Nette\Application\Responses\RedirectResponse] - перенаправление +- [api:Nette\Application\Responses\TextResponse] - отправляет текст +- [api:Nette\Application\Responses\VoidResponse] - пустой ответ Ответы отправляются методом `sendResponse()`: ```php use Nette\Application\Responses; -// Обычный текст +// Простой текст $this->sendResponse(new Responses\TextResponse('Hello Nette!')); // Отправляет файл $this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf')); -// Отправляет обратный вызов +// Ответом будет callback $callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse) { if ($httpResponse->getHeader('Content-Type') === 'text/html') { echo '

    Hello

    '; @@ -425,10 +446,55 @@ $this->sendResponse(new Responses\CallbackResponse($callback)); ``` -Дальнейшее чтение .[#toc-further-reading] -========================================= +Ограничение доступа с помощью `#[Requires]` .{data-version:3.2.2} +----------------------------------------------------------------- + +Атрибут `#[Requires]` предоставляет расширенные возможности для ограничения доступа к презентерам и их методам. Его можно использовать для указания HTTP-методов, требования AJAX-запроса, ограничения на тот же источник (same origin) и доступа только через переадресацию (forwarding). Атрибут можно применять как к классам презентеров, так и к отдельным методам `action()`, `render()`, `handle()` и `createComponent()`. + +Вы можете указать следующие ограничения: +- на HTTP-методы: `#[Requires(methods: ['GET', 'POST'])]` +- требование AJAX-запроса: `#[Requires(ajax: true)]` +- доступ только с того же источника: `#[Requires(sameOrigin: true)]` +- доступ только через forward: `#[Requires(forward: true)]` +- ограничение на конкретные действия: `#[Requires(actions: 'default')]` + +Подробности можно найти в руководстве [Как использовать атрибут Requires |best-practices:attribute-requires]. + + +Проверка HTTP-метода +-------------------- + +Презентеры в Nette автоматически проверяют HTTP-метод каждого входящего запроса. Причиной этой проверки является прежде всего безопасность. По умолчанию разрешены методы `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH`. + +Если вы хотите дополнительно разрешить, например, метод `OPTIONS`, используйте для этого атрибут `#[Requires]` (начиная с Nette Application v3.2): + +```php +#[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])] +class MyPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +В версии 3.1 проверка выполняется в `checkHttpMethod()`, которая проверяет, содержится ли метод, указанный в запросе, в массиве `$presenter->allowedMethods`. Добавление метода сделайте так: + +```php +class MyPresenter extends Nette\Application\UI\Presenter +{ + protected function checkHttpMethod(): void + { + $this->allowedMethods[] = 'OPTIONS'; + parent::checkHttpMethod(); + } +} +``` + +Важно подчеркнуть, что если вы разрешите метод `OPTIONS`, вы должны затем также соответствующим образом обработать его в рамках своего презентера. Метод часто используется как так называемый preflight request, который браузер автоматически отправляет перед фактическим запросом, когда необходимо выяснить, разрешен ли запрос с точки зрения политики CORS (Cross-Origin Resource Sharing). Если вы разрешите метод, но не реализуете правильный ответ, это может привести к несоответствиям и потенциальным проблемам безопасности. + + +Дальнейшее чтение +================= -- [Инжектирование методов и атрибутов |best-practices:inject-method-attribute] -- [Составление презентеров из признаков |best-practices:presenter-traits] -- [Передача параметров в презентеры |best-practices:passing-settings-to-presenters] -- [Как вернуться на предыдущую страницу |best-practices:restore-request] +- [Методы и атрибуты inject |best-practices:inject-method-attribute] +- [Сборка презентеров из трейтов |best-practices:presenter-traits] +- [Передача настроек в презентеры |best-practices:passing-settings-to-presenters] +- [Как вернуться к предыдущей странице |best-practices:restore-request] diff --git a/application/ru/routing.texy b/application/ru/routing.texy index 5bff8b8035..4e2896a131 100644 --- a/application/ru/routing.texy +++ b/application/ru/routing.texy @@ -3,27 +3,26 @@
    -Маршрутизатор позаботится обо всём, что связано с URL-адресами, так что вам больше не придется о них думать. Давайте покажем: +Маршрутизатор отвечает за все, что связано с URL-адресами, чтобы вам больше не приходилось о них думать. Мы покажем: -- как настроить маршрутизатор так, чтобы URL-адреса были такими, какими вы хотите их видеть -- как настроить SEO и перенаправления -- как написать свой собственный маршрутизатор +- как настроить маршрутизатор, чтобы URL были такими, как вы хотите +- расскажем о SEO и перенаправлении +- и покажем, как написать собственный маршрутизатор
    -Более человеческие URL (или крутые или красивые URL) более удобны для использования, лучше запоминаются и положительно влияют на SEO. Nette учитывает это и полностью удовлетворяет желания разработчиков. Вы можете разработать структуру URL для вашего приложения именно так, как вы хотите. -Вы можете даже разработать ее после того, как приложение будет готово, поскольку это можно сделать без каких-либо изменений кода или шаблона. Она определяется элегантным образом в [одном единственном месте |#Integration], в маршрутизаторе, и не разбросана в виде аннотаций по всем презентаторам. +Более человечные URL (или также крутые или красивые URL) более удобны в использовании, запоминаемы и положительно влияют на SEO. Nette учитывает это и полностью идет навстречу разработчикам. Вы можете спроектировать для своего приложения именно такую структуру URL-адресов, какую захотите. Вы можете спроектировать ее даже тогда, когда приложение уже готово, потому что это не потребует изменений в коде или шаблонах. Она определяется элегантным способом в одном [единственном месте |#Включение в приложение], в маршрутизаторе, и таким образом не разбросана в виде аннотаций во всех презентерах. -Маршрутизатор в Nette особенный, потому что он **двунаправленный**, он может как декодировать URL HTTP-запросов, так и создавать ссылки. Поэтому он играет важную роль в [Nette Application |how-it-works#Nette-Application], поскольку он решает, какой ведущий и действие будут выполнять текущий запрос, а также используется для [генерации URL |creating-links] в шаблоне и т.д. +Маршрутизатор в Nette уникален тем, что он **двусторонний.** Он умеет как декодировать URL в HTTP-запросе, так и создавать ссылки. Таким образом, он играет ключевую роль в [Nette Application |how-it-works#Nette Application], поскольку не только решает, какой презентер и действие будут выполнять текущий запрос, но также используется для [генерации URL |creating-links] в шаблоне и т. д. -Однако маршрутизатор не ограничивается этим применением, вы можете использовать его в приложениях, где ведущие вообще не используются, для REST API и т.д. Подробнее в разделе [раздельное использование|#separated-usage]. +Однако маршрутизатор не ограничен только этим использованием, вы можете использовать его в приложениях, где презентеры вообще не используются, для REST API и т. д. Подробнее в разделе [#Самостоятельное использование]. -Коллекция маршрутов .[#toc-route-collection] -============================================ +Коллекция маршрутов +=================== -Наиболее приятным способом определения URL-адресов в приложении является класс [api:Nette\Application\Routers\RouteList]. Определение состоит из списка так называемых маршрутов, то есть масок URL-адресов и связанных с ними презентеров и действий с помощью простого API. Нам не нужно называть маршруты. +Самый приятный способ определения вида URL-адресов в приложении предлагает класс [api:Nette\Application\Routers\RouteList]. Определение состоит из списка так называемых маршрутов, то есть масок URL-адресов и связанных с ними презентеров и действий, с помощью простого API. Маршруты не нужно никак именовать. ```php $router = new Nette\Application\Routers\RouteList; @@ -32,145 +31,145 @@ $router->addRoute('article/', 'Article:view'); // ... ``` -В примере говорится, что если мы откроем `https://any-domain.com/rss.xml` в браузере, то будет отображен презентер `Feed` с действием `rss` и т. д. Если подходящий маршрут не найден, приложение Nette отвечает исключением [BadRequestException |api:Nette\Application\BadRequestException], которое отображается для пользователя как страница ошибки 404 Not Found. +Пример говорит, что если в браузере открыть `https://domain.com/rss.xml`, отобразится презентер `Feed` с действием `rss`, если `https://domain.com/article/12`, отобразится презентер `Article` с действием `view` и т. д. В случае ненахождения подходящего маршрута Nette Application реагирует выбрасыванием исключения [BadRequestException |api:Nette\Application\BadRequestException], которое отображается пользователю как страница ошибки 404 Not Found. -Порядок маршрутов .[#toc-order-of-routes] ------------------------------------------ +Порядок маршрутов +----------------- -Порядок, в котором перечислены маршруты, **очень важен**, потому что они оцениваются последовательно сверху вниз. Правило заключается в том, что мы объявляем маршруты **от конкретных к общим**: +**Ключевым является порядок**, в котором перечислены отдельные маршруты, поскольку они оцениваются последовательно сверху вниз. Действует правило, что маршруты объявляются **от специфических к общим**: ```php -// ОШИБКА: 'rss.xml' соответствует первому маршруту и неправильно воспринимает его как . +// НЕПРАВИЛЬНО: 'rss.xml' перехватит первый маршрут и поймет эту строку как $router->addRoute('', 'Article:view'); $router->addRoute('rss.xml', 'Feed:rss'); -// ПОДРОБНЕЕ +// ПРАВИЛЬНО $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('', 'Article:view'); ``` -Маршруты также оцениваются сверху вниз при генерации ссылок: +Маршруты оцениваются сверху вниз также при генерации ссылок: ```php -// ОШИБКА: генерирует ссылку на 'Feed:rss' как 'admin/feed/rss' +// НЕПРАВИЛЬНО: ссылка на 'Feed:rss' сгенерируется как 'admin/feed/rss' $router->addRoute('admin//', 'Admin:default'); $router->addRoute('rss.xml', 'Feed:rss'); -// ПОДРОБНЕЕ +// ПРАВИЛЬНО $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('admin//', 'Admin:default'); ``` -Мы не будем скрывать от вас, что для правильного построения списка требуется определенный навык. Пока вы не научитесь, панель [routing panel |#Debugging-Router] будет полезным инструментом. +Мы не будем скрывать от вас, что правильное составление маршрутов требует определенного навыка. Пока вы не освоите его, полезным помощником будет [панель маршрутизации |#Отладка маршрутизатора]. -Маска и параметры .[#toc-mask-and-parameters] ---------------------------------------------- +Маска и параметры +----------------- -Маска описывает относительный путь, основанный на корне сайта. Самая простая маска — это статический URL: +Маска описывает относительный путь от корневого каталога сайта. Самой простой маской является статический URL: ```php $router->addRoute('products', 'Products:default'); ``` -Часто маски содержат так называемые **параметры**. Они заключены в угловые скобки (например, ``) и передаются в целевой презентер, например, в метод `renderShow(int $year)` или в постоянный параметр `$year`: +Часто маски содержат так называемые **параметры**. Они указываются в угловых скобках (например, ``) и передаются в целевой презентер, например, методу `renderShow(int $year)` или в персистентный параметр `$year`: ```php $router->addRoute('chronicle/', 'History:show'); ``` -В примере говорится, что если мы откроем `https://any-domain.com/chronicle/2020` в браузере, презентер `History` и действие `show` с параметром `year: 2020` будет отображаться. +Пример говорит, что если в браузере открыть `https://example.com/chronicle/2020`, отобразится презентер `History` с действием `show` и параметром `year: 2020`. -Мы можем указать значение по умолчанию для параметров непосредственно в маске, и таким образом она становится необязательной: +Параметрам можно определить значение по умолчанию прямо в маске, и тем самым они станут необязательными: ```php $router->addRoute('chronicle/', 'History:show'); ``` -Теперь маршрут будет принимать URL `https://any-domain.com/chronicle/`, который снова отобразит `History:show` с параметром `year`: 2020`. +Маршрут теперь будет принимать и URL `https://example.com/chronicle/`, который снова отобразит `History:show` с параметром `year: 2020`. -Конечно, имя презентера и действие также могут быть параметрами. Например: +Параметром может быть, конечно, и имя презентера и действия. Например, так: ```php $router->addRoute('/', 'Home:default'); ``` -Этот маршрут принимает, например, URL в форме `/article/edit` и `/catalog/list` соответственно, и переводит их в презентеры и действия `Article:edit` и `Catalog:list` соответственно. +Указанный маршрут принимает, например, URL вида `/article/edit` или `/catalog/list` и понимает их как презентеры и действия `Article:edit` и `Catalog:list`. -Он также придает параметрам `presenter` и `action` значения по умолчанию `Home` и `default` и поэтому они являются необязательными. Поэтому маршрут также принимает URL `/article` и переводит его как `Article:default`. Или наоборот, ссылка на `Product:default` генерирует путь `/product`, ссылка на стандартную `Home:default` генерирует путь `/`. +Одновременно он дает параметрам `presenter` и `action` значения по умолчанию `Home` и `default`, и они, следовательно, также необязательны. Так что маршрут принимает и URL вида `/article` и понимает его как `Article:default`. Или наоборот, ссылка на `Product:default` сгенерирует путь `/product`, ссылка на стандартный `Home:default` путь `/`. -Маска может описывать не только относительный путь, основанный на корне сайта, но и абсолютный путь, если он начинается со слэша, или даже весь абсолютный URL, если он начинается с двух слэшей: +Маска может описывать не только относительный путь от корневого каталога сайта, но и абсолютный путь, если начинается со слеша, или даже полный абсолютный URL, если начинается с двух слешей: ```php -// относительный путь к корню приложения +// относительно document root $router->addRoute('/', /* ... */); -// абсолютный путь, относительно имени хоста сервера +// абсолютный путь (относительно домена) $router->addRoute('//', /* ... */); -// абсолютный URL, включая имя хоста (относительно схемы) +// абсолютный URL включая домен (относительно схемы) $router->addRoute('//.example.com//', /* ... */); -// абсолютный URL, включая схему +// абсолютный URL включая схему $router->addRoute('https://.example.com//', /* ... */); ``` -Валидационные выражения .[#toc-validation-expressions] ------------------------------------------------------- +Выражения валидации +------------------- -Для каждого параметра можно задать условие проверки с помощью [регулярного выражения |https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Например, давайте зададим `id` только числовым, используя `\d+` regexp: +Для каждого параметра можно установить условие валидации с помощью [регулярного выражения |https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Например, для параметра `id` мы укажем, что он может принимать только цифры с помощью регулярного выражения `\d+`: ```php $router->addRoute('/[/]', /* ... */); ``` -По умолчанию регулярным выражением для всех параметров является `[^/]+`, то есть всё, кроме слэша. Если параметр должен соответствовать также косой черте, мы задаем регулярное выражение `.+`. +Стандартным регулярным выражением для всех параметров является `[^/]+`, т. е. все, кроме слеша. Если параметр должен принимать и слеши, укажем выражение `.+`: ```php -// принимает https://example.com/a/b/c, path — 'a/b/c' +// принимает https://example.com/a/b/c, path будет 'a/b/c' $router->addRoute('', /* ... */); ``` -Необязательные последовательности .[#toc-optional-sequences] ------------------------------------------------------------- +Необязательные последовательности +--------------------------------- -Квадратные скобки обозначают необязательные части маски. Любая часть маски может быть задана как необязательная, включая те, которые содержат параметры: +В маске можно обозначать необязательные части с помощью квадратных скобок. Необязательной может быть любая часть маски, в ней могут находиться и параметры: ```php $router->addRoute('[/]', /* ... */); -// Принимаемые URL-адреса: Параметры: -// /en/download lang => en, name => download -// /download lang => null, name => download +// Принимает пути: +// /cs/download => lang => cs, name => download +// /download => lang => null, name => download ``` -Конечно, когда параметр является частью необязательной последовательности, он также становится необязательным. Если у него нет значения по умолчанию, он будет равен null. +Когда параметр является частью необязательной последовательности, он, разумеется, также становится необязательным. Если у него нет указанного значения по умолчанию, то он будет null. -Необязательные части также могут быть в домене: +Необязательные части могут быть и в домене: ```php $router->addRoute('//[.]example.com//', /* ... */); ``` -Последовательности могут быть свободно вложены и объединены: +Последовательности можно произвольно вкладывать и комбинировать: ```php $router->addRoute( - '[[-]/][page-]', + '[[-]/][/page-]', 'Home:default', ); -// Принимаемые URL-адреса: -// /ru/hello -// /en-us/hello -// /hello -// /hello/page-12 +// Принимает пути: +// /cs/hello +// /en-us/hello +// /hello +// /hello/page-12 ``` -Генератор URL старается сделать URL как можно короче, поэтому то, что можно опустить, опускается. Поэтому, например, маршрут `index[.html]` генерирует путь `/index`. Вы можете изменить это поведение, написав восклицательный знак после левой квадратной скобки: +При генерации URL стремимся к кратчайшему варианту, поэтому все, что можно опустить, опускается. Поэтому, например, маршрут `index[.html]` генерирует путь `/index`. Изменить поведение можно, указав восклицательный знак после левой квадратной скобки: ```php // принимает /hello и /hello.html, генерирует /hello @@ -180,31 +179,31 @@ $router->addRoute('[.html]', /* ... */); $router->addRoute('[!.html]', /* ... */); ``` -Необязательные параметры (т. е. параметры, имеющие значение по умолчанию) без квадратных скобок ведут себя так, как если бы они были обернуты таким образом: +Необязательные параметры (т. е. параметры, имеющие значение по умолчанию) без квадратных скобок ведут себя по сути так, как если бы они были заключены в скобки следующим образом: ```php $router->addRoute('//', /* ... */); -// equals to: +// соответствует этому: $router->addRoute('[/[/[]]]', /* ... */); ``` -Чтобы изменить способ генерации самой правой косой черты, т. е. вместо `/home/` получить `/home`, настройте маршрут таким образом: +Если мы хотим повлиять на поведение конечного слеша, чтобы, например, вместо `/home/` генерировалось только `/home`, этого можно достичь так: ```php $router->addRoute('[[/[/]]]', /* ... */); ``` -Символы подстановки .[#toc-wildcards] -------------------------------------- +Подстановочные знаки +-------------------- -В маске абсолютного пути мы можем использовать следующие подстановочные знаки, чтобы избежать, например, необходимости записывать в маску домен, который может отличаться в среде разработки и производственной среде: +В маске абсолютного пути мы можем использовать следующие подстановочные знаки и избежать, например, необходимости записывать в маску домен, который может отличаться в среде разработки и production: -- `%tld%` = домен верхнего уровня, например, `com` или `org` -- `%sld%` = домен второго уровня, например, `example` -- `%domain%` = домен без поддоменов, например, `example.com` -- `%host%` = весь хост, например, `www.example.com` +- `%tld%` = домен верхнего уровня, например `com` или `org` +- `%sld%` = домен второго уровня, например `example` +- `%domain%` = домен без поддоменов, например `example.com` +- `%host%` = весь хост, например `www.example.com` - `%basePath%` = путь к корневому каталогу ```php @@ -213,10 +212,10 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ @@ -225,7 +224,7 @@ $router->addRoute('/[/]', [ ]); ``` -Или мы можем использовать эту форму, обратите внимание на переписывание регулярного выражения проверки: +Для более детальной спецификации можно использовать еще более расширенную форму, где кроме значений по умолчанию можно настроить и другие свойства параметров, например, валидационное регулярное выражение (см. параметр `id`): ```php use Nette\Routing\Route; @@ -243,19 +242,19 @@ $router->addRoute('/[/]', [ ]); ``` -Эти более подробные форматы полезны для добавления дополнительных метаданных. +Важно отметить, что если параметры, определенные в массиве, не указаны в маске пути, их значения нельзя изменить, даже с помощью query-параметров, указанных после вопросительного знака в URL. -Фильтры и переводы .[#toc-filters-and-translations] ---------------------------------------------------- +Фильтры и переводы +------------------ -Хорошей практикой является написание исходного кода на английском языке, но что если вам нужно, чтобы URL вашего сайта был переведен на другой язык? +Исходные коды приложения мы пишем на английском языке, но если сайт должен иметь русские URL, то простая маршрутизация типа: ```php $router->addRoute('/', 'Home:default'); ``` -будет генерировать английские URL, такие как `/product/123` или `/cart`. Если мы хотим, чтобы презентеры и действия в URL были переведены на немецкий язык (например, `/produkt/123` или `/einkaufswagen`), мы можем использовать словарь переводов. Чтобы добавить его, нам уже нужен «более понятный» вариант второго параметра: +будет генерировать английские URL, например `/product/123` или `/cart`. Если мы хотим, чтобы презентеры и действия в URL были представлены русскими словами (например, `/продукт/123` или `/корзина`), мы можем использовать словарь перевода. Для его записи уже нужна "более многословная" версия второго параметра: ```php use Nette\Routing\Route; @@ -264,26 +263,26 @@ $router->addRoute('/', [ 'presenter' => [ Route::Value => 'Home', Route::FilterTable => [ - // строка в URL => ведущий + // строка в URL => презентер 'produkt' => 'Product', - 'einkaufswagen' => 'Cart', + 'korzina' => 'Cart', 'katalog' => 'Catalog', ], ], 'action' => [ Route::Value => 'default', Route::FilterTable => [ - 'liste' => 'list', + 'spisok' => 'list', ], ], ]); ``` -Для одного и того же презентера можно использовать несколько ключей словаря. Они будут создавать для него различные псевдонимы. Последний ключ считается каноническим вариантом (т. е. тот, который будет в сгенерированном URL). +Несколько ключей словаря перевода могут вести на один и тот же презентер. Таким образом, к нему создаются различные псевдонимы. Каноническим вариантом (то есть тем, который будет в сгенерированном URL) считается последний ключ. -Таблица перевода может быть применена к любому параметру таким образом. Однако если перевода не существует, берется исходное значение. Мы можем изменить это поведение, добавив `Route::FilterStrict => true`, и тогда маршрут будет отклонять URL, если значение отсутствует в словаре. +Таблицу перевода можно таким образом использовать для любого параметра. При этом, если перевод не существует, берется исходное значение. Это поведение можно изменить, добавив `Route::FilterStrict => true`, и маршрут тогда отклонит URL, если значение отсутствует в словаре. -В дополнение к словарю перевода в виде массива можно задать собственные функции перевода: +Кроме словаря перевода в виде массива, можно применить и собственные функции перевода. ```php use Nette\Routing\Route; @@ -299,15 +298,15 @@ $router->addRoute('//', [ ]); ``` -Функция `Route::FilterIn` осуществляет преобразование между параметром в URL и строкой, которая затем передается презентеру, функция `FilterOut` обеспечивает преобразование в обратном направлении. +Функция `Route::FilterIn` преобразует параметр в URL в строку, которая затем передается в презентер, функция `FilterOut` обеспечивает преобразование в обратном направлении. -Параметры `presenter`, `action` и `module` уже имеют предопределенные фильтры, которые конвертируют между PascalCase, camelCase и kebab-case соответственно, используемые в URL. Значение параметров по умолчанию уже записано в преобразованной форме, поэтому, например, в случае с презентером мы пишем `` вместо ``. +Параметры `presenter`, `action` и `module` уже имеют предопределенные фильтры, которые преобразуют между стилем PascalCase или camelCase и kebab-case, используемым в URL. Значение по умолчанию параметров записывается уже в преобразованном виде, поэтому, например, в случае презентера пишем ``, а не ``. -Общие фильтры .[#toc-general-filters] -------------------------------------- +Общие фильтры +------------- -Помимо фильтров для конкретных параметров, вы также можете определить общие фильтры, получающие ассоциативный массив всех параметров, которые они могут изменять любым способом, а затем возвращать. Общие фильтры определяются по ключу `null`. +Помимо фильтров, предназначенных для конкретных параметров, мы можем определить также общие фильтры, которые получают ассоциативный массив всех параметров, которые могут как угодно модифицировать и затем вернуть. Общие фильтры определяем под ключом `null`. ```php use Nette\Routing\Route; @@ -315,22 +314,22 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => 'Home', 'action' => 'default', - null => [ + '' => [ Route::FilterIn => function (array $params): array { /* ... */ }, Route::FilterOut => function (array $params): array { /* ... */ }, ], ]); ``` -Общие фильтры дают вам возможность настроить поведение маршрута абсолютно любым способом. Мы можем использовать их, например, для изменения параметров на основе других параметров. Например, перевод `` и `` на основе текущего значения параметра ``. +Общие фильтры дают возможность настроить поведение маршрута абсолютно любым способом. Мы можем использовать их, например, для модификации параметров на основе других параметров. Например, перевод `` и `` на основе текущего значения параметра ``. -Если для параметра определен пользовательский фильтр и одновременно существует общий фильтр, пользовательский `FilterIn` выполняется перед общим, и наоборот, общий `FilterOut` выполняется перед пользовательским. Таким образом, внутри общего фильтра находятся значения параметров `presenter` и `action` соответственно, написанное на языке PascalCase и camelCase соответственно. +Если у параметра определен собственный фильтр и одновременно существует общий фильтр, выполняется собственный `FilterIn` перед общим и, наоборот, общий `FilterOut` перед собственным. То есть внутри общего фильтра значения параметров `presenter` или `action` записаны в стиле PascalCase или camelCase. -Флаг OneWay .[#toc-oneway-flag] -------------------------------- +Односторонние маршруты OneWay +----------------------------- -Односторонние маршруты используются для сохранения функциональности старых URL, которые приложение больше не генерирует, но всё ещё принимает. Мы отмечаем их флагом `OneWay`: +Односторонние маршруты используются для сохранения функциональности старых URL, которые приложение уже не генерирует, но все еще принимает. Мы помечаем их флагом `OneWay`: ```php // старый URL /product-info?id=123 @@ -339,38 +338,61 @@ $router->addRoute('product-info', 'Product:detail', $router::ONE_WAY); $router->addRoute('product/', 'Product:detail'); ``` -При обращении к старому URL-адресу презентер автоматически перенаправляет на новый URL-адрес, чтобы поисковые системы не индексировали эти страницы дважды (см. [SEO и канонизация|#seo-and-canonization]). +При доступе к старому URL презентер автоматически перенаправляет на новый URL, так что поисковые системы не проиндексируют эти страницы дважды (см. [#SEO и канонизация]). -Модули .[#toc-modules] ----------------------- +Динамическая маршрутизация с callback-функциями +----------------------------------------------- -Если у нас есть несколько маршрутов, принадлежащих одному модулю, мы можем использовать `withModule()` для их группировки: +Динамическая маршрутизация с callback-функциями позволяет вам напрямую назначать маршрутам функции (callback), которые будут выполнены при посещении данного пути. Эта гибкая функциональность позволяет быстро и эффективно создавать различные конечные точки (endpoints) для вашего приложения: + +```php +$router->addRoute('test', function () { + echo 'вы находитесь по адресу /test'; +}); +``` + +Вы также можете определить в маске параметры, которые автоматически передадутся в ваш callback: + +```php +$router->addRoute('', function (string $lang) { + echo match ($lang) { + 'cs' => 'Добро пожаловать на чешскую версию нашего сайта!', + 'en' => 'Welcome to the English version of our website!', + }; +}); +``` + + +Модули +------ + +Если у нас есть несколько маршрутов, относящихся к общему [модулю |directory-structure#Презентеры и шаблоны], мы используем `withModule()`: ```php $router = new RouteList; -$router->withModule('Forum') // следующие маршрутизаторы входят в состав модуля Forum - ->addRoute('rss', 'Feed:rss') // презентер Forum:Feed +$router->withModule('Forum') // следующие маршруты являются частью модуля Forum + ->addRoute('rss', 'Feed:rss') // презентер будет Forum:Feed ->addRoute('/') - ->withModule('Admin') // следующие маршрутизаторы являются частью модуля Forum:Admin + ->withModule('Admin') // следующие маршруты являются частью модуля Forum:Admin ->addRoute('sign:in', 'Sign:in'); ``` Альтернативой является использование параметра `module`: ```php -// URL manage/dashboard/default отображается на ведущего Admin:Dashboard +// URL manage/dashboard/default отображается на презентер Admin:Dashboard $router->addRoute('manage//', [ 'module' => 'Admin', ]); ``` -Субдомены .[#toc-subdomains] ----------------------------- +Поддомены +--------- -Коллекции маршрутов могут быть сгруппированы по поддоменам: +Коллекции маршрутов можно разделять по поддоменам: ```php $router = new RouteList; @@ -379,7 +401,7 @@ $router->withDomain('example.com') ->addRoute('/'); ``` -Вы также можете использовать [Символы подстановки |#Wildcards] в своем доменном имени: +В имени домена можно использовать и [#Подстановочные знаки]: ```php $router = new RouteList; @@ -388,23 +410,23 @@ $router->withDomain('example.%tld%') ``` -Префикс пути .[#toc-path-prefix] --------------------------------- +Префикс пути +------------ -Коллекции маршрутов могут быть сгруппированы по пути в URL: +Коллекции маршрутов можно разделять по пути в URL: ```php $router = new RouteList; $router->withPath('eshop') - ->addRoute('rss', 'Feed:rss') // соответствует URL /eshop/rss - ->addRoute('/'); // соответствует URL /eshop// + ->addRoute('rss', 'Feed:rss') // ловит URL /eshop/rss + ->addRoute('/'); // ловит URL /eshop// ``` -Комбинации .[#toc-combinations] -------------------------------- +Комбинации +---------- -Вышеуказанные варианты использования можно комбинировать: +Вышеупомянутые разделения можно взаимно комбинировать: ```php $router = (new RouteList) @@ -424,40 +446,40 @@ $router = (new RouteList) ``` -Параметры запроса .[#toc-query-parameters] ------------------------------------------- +Query-параметры +--------------- -Маски также могут содержать параметры запроса (параметры после знака вопроса в URL). Они не могут определить выражение проверки, но они могут изменить имя, под которым они передаются презентеру: +Маски могут также содержать query-параметры (параметры после вопросительного знака в URL). Для них нельзя определить валидационное выражение, но можно изменить имя, под которым они передадутся в презентер: ```php -// используем параметр запроса 'cat' в качестве 'categoryId' в приложении +// query-параметр 'cat' мы хотим использовать в приложении под именем 'categoryId' $router->addRoute('product ? id= & cat=', /* ... */); ``` -Параметры Foo .[#toc-foo-parameters] ------------------------------------- +Foo-параметры +------------- -Теперь мы идем глубже. Параметры Foo — это, по сути, неименованные параметры, которые позволяют сопоставить регулярное выражение. Следующий маршрут соответствует `/index`, `/index.html`, `/index.htm` и `/index.php`: +Теперь мы углубляемся. Foo-параметры — это, по сути, безымянные параметры, которые позволяют сопоставлять регулярное выражение. Примером является маршрут, принимающий `/index`, `/index.html`, `/index.htm` и `/index.php`: ```php $router->addRoute('index', /* ... */); ``` -Также можно явно задать строку, которая будет использоваться для генерации URL. Строка должна располагаться непосредственно после знака вопроса. Следующий маршрут похож на предыдущий, но генерирует `/index.html` вместо `/index`, потому что строка `.html` установлена как «генерируемое значение». +Можно также явно определить строку, которая будет использоваться при генерации URL. Строка должна быть размещена непосредственно за вопросительным знаком. Следующий маршрут похож на предыдущий, но генерирует `/index.html` вместо `/index`, потому что строка `.html` установлена как генерируемое значение: ```php $router->addRoute('index', /* ... */); ``` -Интеграция .[#toc-integration] -============================== +Включение в приложение +====================== -Чтобы подключить наш маршрутизатор к приложению, мы должны сообщить о нем контейнеру DI. Самый простой способ - это подготовить фабрику, которая будет создавать объект маршрутизатора, и сообщить конфигурации контейнера, чтобы она его использовала. Допустим, мы напишем для этого метод `App\Router\RouterFactory::createRouter()`: +Чтобы подключить созданный маршрутизатор к приложению, мы должны сообщить о нем DI-контейнеру. Самый простой способ — подготовить фабрику, которая создаст объект маршрутизатора, и сообщить в конфигурации контейнера, что ее нужно использовать. Допустим, для этой цели мы напишем метод `App\Core\RouterFactory::createRouter()`: ```php -namespace App\Router; +namespace App\Core; use Nette\Application\Routers\RouteList; @@ -472,14 +494,14 @@ class RouterFactory } ``` -Затем мы пишем в [configuration |dependency-injection:services]: +В [конфигурацию |dependency-injection:services] затем запишем: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter ``` -Любые зависимости, такие как подключение к базе данных и т.д., передаются методу фабрики в качестве параметров с помощью [autowiring |dependency-injection:autowiring]: +Любые зависимости, например, от базы данных и т. д., передаются фабричному методу как его параметры с помощью [autowiring |dependency-injection:autowiring]: ```php public static function createRouter(Nette\Database\Connection $db): RouteList @@ -492,22 +514,22 @@ public static function createRouter(Nette\Database\Connection $db): RouteList SimpleRouter ============ -Гораздо более простым маршрутизатором, чем коллекция маршрутов, является [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Его можно использовать, когда нет необходимости в определенном формате URL, когда `mod_rewrite` (или альтернативы) недоступен или когда мы просто не хотим пока возиться с удобными для пользователя URL. +Гораздо более простым маршрутизатором, чем коллекция маршрутов, является [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Мы используем его тогда, когда у нас нет особых требований к форме URL, если недоступен `mod_rewrite` (или его альтернативы) или если мы пока не хотим заниматься красивыми URL. -Генерирует адреса примерно такой формы: +Генерирует адреса примерно в таком виде: ``` http://example.com/?presenter=Product&action=detail&id=123 ``` -Параметром конструктора `SimpleRouter` является презентер и действие по умолчанию, т. е. действие, которое будет выполнено, если мы откроем, например, `http://example.com/` без дополнительных параметров. +Параметром конструктора SimpleRouter является стандартный презентер и действие, на который нужно направлять, если мы открываем страницу без параметров, например, `http://example.com/`. ```php -// используем презентер 'Home' и действие 'default' +// стандартным презентером будет 'Home' и действие 'default' $router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` -Мы рекомендуем определять SimpleRouter непосредственно в [конфигурации |dependency-injection:services]: +Рекомендуем SimpleRouter напрямую определить в [конфигурации |dependency-injection:services]: ```neon services: @@ -515,22 +537,22 @@ services: ``` -SEO и канонизация .[#toc-seo-and-canonization] -============================================== +SEO и канонизация +================= -Фреймворк улучшает SEO, предотвращая дублирование контента на разных URL. Если несколько адресов ссылаются на одно и то же место назначения, например `/index` и `/index.html`, фреймворк определяет первый из них как основной (канонический) и перенаправляет на него остальные с помощью HTTP-кода 301. Благодаря этому поисковые системы не будут индексировать страницы дважды и не нарушат их страничный рейтинг. +Фреймворк способствует SEO (оптимизации для поисковых систем), предотвращая дублирование контента на разных URL. Если к определенной цели ведет несколько адресов, например, `/index` и `/index.html`, фреймворк определяет первый из них как основной (канонический) и остальные перенаправляет на него с помощью HTTP-кода 301. Благодаря этому поисковые системы не индексируют страницы дважды и не размывают их page rank. -Этот процесс называется канонизацией. Канонический URL — это URL, сгенерированный маршрутизатором, т. е. первым подходящим маршрутом в [коллекции |#Route-Collection] без флага OneWay. Поэтому в коллекции мы перечисляем **первичные маршруты первыми**. +Этот процесс называется канонизацией. Каноническим URL является тот, который генерирует маршрутизатор, то есть первый подходящий маршрут в коллекции без флага OneWay. Поэтому в коллекции мы указываем **основные маршруты первыми**. -Канонизация осуществляется презентером, подробнее в главе [Канонизация |presenters#canonization]. +Канонизацию выполняет презентер, подробнее в главе [канонизация |presenters#Канонизация]. HTTPS ===== -Для того чтобы использовать протокол HTTPS, необходимо активировать его на хостинге и настроить сервер. +Чтобы использовать протокол HTTPS, необходимо включить его на хостинге и правильно настроить сервер. -Перенаправление всего сайта на HTTPS должно быть выполнено на уровне сервера, например, с помощью файла .htaccess в корневом каталоге нашего приложения, с HTTP-кодом 301. Настройки могут отличаться в зависимости от хостинга и выглядят примерно так: +Перенаправление всего сайта на HTTPS необходимо настроить на уровне сервера, например, с помощью файла .htaccess в корневом каталоге нашего приложения, и это с HTTP-кодом 301. Настройки могут отличаться в зависимости от хостинга и выглядят примерно так: ``` @@ -542,40 +564,40 @@ HTTPS ``` -Маршрутизатор генерирует URL с тем же протоколом, по которому была загружена страница, поэтому нет необходимости задавать что-либо ещё. +Маршрутизатор генерирует URL с тем же протоколом, с которым была загружена страница, так что больше ничего настраивать не нужно. -Однако, если нам исключительно нужно, чтобы разные маршруты работали под разными протоколами, мы поместим это в маску маршрута: +Однако, если нам в исключительных случаях нужно, чтобы разные маршруты работали под разными протоколами, мы укажем его в маске маршрута: ```php -// Сгенерирует HTTP адрес +// Будет генерировать адрес с HTTP $router->addRoute('http://%host%//', /* ... */); -// Сгенерирует HTTPS адрес +// Будет генерировать адрес с HTTPS $router->addRoute('https://%host%//', /* ... */); ``` -Отладчик маршрутизации .[#toc-debugging-router] -=============================================== +Отладка маршрутизатора +====================== -Полоса маршрутизации, показанная в [Tracy Bar |tracy:], является полезным инструментом, который отображает список маршрутов, а также параметры, которые маршрутизатор получил из URL. +Панель маршрутизации, отображаемая в [Tracy Bar |tracy:], является полезным помощником, который показывает список маршрутов, а также параметры, которые маршрутизатор получил из URL. -Зеленая полоса с символом ✓ представляет маршрут, который соответствует текущему URL, синие полосы с символами ≈ указывают на маршруты, которые также соответствовали бы URL, если бы зеленый цвет не обогнал их. Далее мы видим текущего ведущего и действия. +Зеленая полоса с символом ✓ представляет маршрут, который обработал текущий URL, синим цветом и символом ≈ обозначены маршруты, которые также обработали бы URL, если бы зеленый их не опередил. Далее мы видим текущий презентер и действие. [* routing-debugger.webp *] -В то же время, если происходит неожиданное перенаправление из-за [каноникализации |#seo-and-canonization], полезно заглянуть в панель *redirect*, чтобы узнать, как маршрутизатор изначально понял URL и почему он перенаправил. +Одновременно, если происходит неожиданное перенаправление из-за [канонизации |#SEO и канонизация], полезно посмотреть в панель в строке *redirect*, где вы узнаете, как маршрутизатор изначально понял URL и почему перенаправил. .[note] -При отладке маршрутизатора рекомендуется открыть Developer Tools в браузере (Ctrl+Shift+I или Cmd+Option+I) и отключить кэш в панели Network, чтобы перенаправления не сохранялись в нем. +При отладке маршрутизатора рекомендуем открыть в браузере Developer Tools (Ctrl+Shift+I или Cmd+Option+I) и в панели Network отключить кеш, чтобы в него не сохранялись перенаправления. -Производительность .[#toc-performance] -====================================== +Производительность +================== -Количество маршрутов влияет на скорость маршрутизатора. Их количество, конечно, не должно превышать нескольких десятков. Если ваш сайт имеет слишком сложную структуру URL, вы можете написать [Пользовательский маршрутизатор|#custom-router]. +Количество маршрутов влияет на скорость маршрутизатора. Их число определенно не должно превышать нескольких десятков. Если у вашего сайта слишком сложная структура URL, вы можете написать собственный [#Собственный маршрутизатор] под свои нужды. -Если маршрутизатор не имеет зависимостей, например, от базы данных, и его фабрика не имеет аргументов, мы можем сериализовать его скомпилированную форму непосредственно в DI-контейнер и тем самым сделать приложение немного быстрее. +Если маршрутизатор не имеет зависимостей, например, от базы данных, и его фабрика не принимает никаких аргументов, мы можем его скомпилированную форму сериализовать прямо в DI-контейнер и тем самым немного ускорить приложение. ```neon routing: @@ -583,10 +605,10 @@ routing: ``` -Пользовательский маршрутизатор .[#toc-custom-router] -==================================================== +Собственный маршрутизатор +========================= -Следующие строки предназначены для очень опытных пользователей. Вы можете создать свой собственный маршрутизатор и, естественно, добавить его в коллекцию маршрутов. Маршрутизатор представляет собой реализацию интерфейса [Router |api:Nette\Routing\Router] с двумя методами: +Следующие строки предназначены для очень продвинутых пользователей. Вы можете создать собственный маршрутизатор и совершенно естественно включить его в коллекцию маршрутов. Маршрутизатор — это реализация интерфейса [api:Nette\Routing\Router] с двумя методами: ```php use Nette\Http\IRequest as HttpRequest; @@ -606,8 +628,7 @@ class MyRouter implements Nette\Routing\Router } ``` -Метод `match` обрабатывает текущий [$httpRequest |http:request], из которого может быть извлечен не только URL, но и заголовки и т.д., в массив, содержащий имя ведущего и его параметры. Если он не может обработать запрос, то возвращает null. -При обработке запроса мы должны вернуть как минимум ведущего и действие. Имя ведущего является полным и включает любые модули: +Метод `match` обрабатывает текущий запрос [$httpRequest |http:request], из которого можно получить не только URL, но и заголовки и т. д., в массив, содержащий имя презентера и его параметры. Если он не может обработать запрос, возвращает null. При обработке запроса мы должны вернуть как минимум презентер и действие. Имя презентера является полным и содержит также возможные модули: ```php [ @@ -616,31 +637,31 @@ class MyRouter implements Nette\Routing\Router ] ``` -Метод `constructUrl`, с другой стороны, генерирует абсолютный URL из массива параметров. Он может использовать информацию из параметра `$refUrl`, который является текущим URL. +Метод `constructUrl`, наоборот, составляет из массива параметров итоговый абсолютный URL. Для этого он может использовать информацию из параметра [`$refUrl` |api:Nette\Http\UrlScript], который является текущим URL. -Чтобы добавить пользовательский маршрутизатор в коллекцию маршрутов, используйте `add()`: +В коллекцию маршрутов его добавите с помощью `add()`: ```php $router = new Nette\Application\Routers\RouteList; -$router->add(new MyRouter); +$router->add($myRouter); $router->addRoute(/* ... */); // ... ``` -Раздельное использование .[#toc-separated-usage] -================================================ +Самостоятельное использование +============================= -Под раздельным использованием подразумевается использование возможностей маршрутизатора в приложении, которое не использует приложение Nette и презентеры. К нему применимо почти всё, что мы показали в этой главе, со следующими отличиями: +Под самостоятельным использованием мы подразумеваем использование возможностей маршрутизатора в приложении, которое не использует Nette Application и презентеры. Для него действует почти все, что мы показали в этой главе, со следующими отличиями: -- для коллекций маршрутов мы используем класс [api:Nette\Routing\RouteList]. -- как класс простого маршрутизатора [api:Nette\Routing\SimpleRouter]. -- поскольку нет пары `Presenter:action`, мы используем [Расширенная нотация|#advanced-notation]. +- для коллекций маршрутов мы используем класс [api:Nette\Routing\RouteList] +- в качестве простого маршрутизатора класс [api:Nette\Routing\SimpleRouter] +- поскольку не существует пары `Presenter:action`, мы используем [#Расширенная запись] -Итак, мы снова добавим метод, который будет создавать, например, маршрутизатор: +Итак, мы снова создаем метод, который нам составит маршрутизатор, например: ```php -namespace App\Router; +namespace App\Core; use Nette\Routing\RouteList; @@ -661,26 +682,26 @@ class RouterFactory } ``` -Если вы используете DI-контейнер, как мы рекомендуем, добавьте метод в конфигурацию ещё раз, а затем получите маршрутизатор вместе с HTTP-запросом из контейнера: +Если вы используете DI-контейнер, что мы рекомендуем, снова добавляем метод в конфигурацию, а затем получаем маршрутизатор вместе с HTTP-запросом из контейнера: ```php $router = $container->getByType(Nette\Routing\Router::class); $httpRequest = $container->getByType(Nette\Http\IRequest::class); ``` -Или мы будем создавать объекты напрямую: +Или объекты создаем напрямую: ```php -$router = App\Router\RouterFactory::createRouter(); +$router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` -Теперь нужно дать маршрутизатору поработать: +Теперь остается только запустить маршрутизатор в работу: ```php $params = $router->match($httpRequest); if ($params === null) { - // не найден совпадающий маршрут, отправим ошибку 404 + // не найден подходящий маршрут, отправляем ошибку 404 exit; } @@ -689,7 +710,7 @@ $controller = $params['controller']; // ... ``` -И наоборот, мы будем использовать маршрутизатор для создания ссылки: +И наоборот, используем маршрутизатор для составления ссылки: ```php $params = ['controller' => 'ArticleController', 'id' => 123]; diff --git a/application/ru/templates.texy b/application/ru/templates.texy index 25333ec9a1..c399dcdc7a 100644 --- a/application/ru/templates.texy +++ b/application/ru/templates.texy @@ -2,15 +2,15 @@ ******* .[perex] -Nette использует систему шаблонов [Latte |latte:]. Latte используется потому, что это самая безопасная система шаблонов для PHP, и в то же время самая интуитивно понятная. Вам не нужно изучать много нового, достаточно знать PHP и несколько тегов Latte. +Nette использует систему шаблонов [Latte |latte:]. Во-первых, потому что это самая безопасная система шаблонов для PHP, а во-вторых, самая интуитивно понятная. Вам не нужно учить много нового, достаточно знания PHP и нескольких тегов. -Обычно страница заполняется из шаблона макета + шаблона действия. Вот как может выглядеть шаблон макета, обратите внимание на блоки `{block}` и тег `{include}`: +Обычно страница состоит из шаблона макета + шаблона данного действия. Так, например, может выглядеть шаблон макета, обратите внимание на блоки `{block}` и тег `{include}`: ```latte - {block title}Мое приложение{/block} + {block title}My App{/block}
    ...
    @@ -20,61 +20,109 @@ Nette использует систему шаблонов [Latte |latte:]. Latt ``` -А это может быть шаблоном действий: +А это будет шаблон действия: ```latte -{block title}Главная страница{/block} +{block title}Homepage{/block} {block content} -

    Главная страница

    +

    Homepage

    ... {/block} ``` -Он определяет блок `content`, который вставляется вместо `{include content}` в макете, а также переопределяет блок `title`, который перезаписывает `{block title}` в макете. Попытайтесь представить себе результат. +Он определяет блок `content`, который вставляется на место `{include content}` в макете, а также переопределяет блок `title`, которым перезаписывает `{block title}` в макете. Попробуйте представить результат. -Поиск шаблонов .[#toc-search-for-templates] -------------------------------------------- +Поиск шаблонов +-------------- -Путь к шаблонам определяется ведущим с помощью простой логики. Он попытается проверить, есть ли один из этих файлов, расположенный относительно каталога класса ведущего, где `` это имя текущего ведущего, а `` это имя текущего события: +Вам не нужно указывать в презентерах, какой шаблон должен быть отрисован, фреймворк сам выведет путь и сэкономит вам написание. -- `templates//.latte` -- `templates/..latte` +Если вы используете структуру каталогов, где каждый презентер имеет собственный каталог, просто поместите шаблон в этот каталог под именем действия (соответственно, view), т. е. для действия `default` используйте шаблон `default.latte`: -Если шаблон не найден, ответом будет [ошибка 404 |presenters#error-404-etc]. +/--pre +app/ +└── Presentation/ + └── Home/ + ├── HomePresenter.php + └── default.latte +\-- -Вы также можете изменить вид с помощью `$this->setView('jineView')`. Или, вместо прямого поиска, укажите имя файла шаблона с помощью `$this->template->setFile('/path/to/template.latte')`. +Если вы используете структуру, где презентеры находятся вместе в одном каталоге, а шаблоны — в папке `templates`, сохраните его либо в файле `..latte`, либо `/.latte`: + +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── Home.default.latte ← 1-й вариант + └── Home/ + └── default.latte ← 2-й вариант +\-- + +Каталог `templates` может быть расположен также на уровень выше, т. е. на том же уровне, что и каталог с классами презентеров. + +Если шаблон не найден, презентер отвечает [ошибкой 404 - страница не найдена |presenters#Ошибка 404 и т.п]. + +View можно изменить с помощью `$this->setView('jineView')`. Также можно напрямую указать файл с шаблоном с помощью `$this->template->setFile('/path/to/template.latte')`. .[note] -Файлы, в которых производится поиск шаблонов, можно изменить, наложив метод [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], который возвращает массив возможных имен файлов. +Файлы, где ищутся шаблоны, можно изменить, переопределив метод [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], который возвращает массив возможных имен файлов. + + +Поиск шаблона макета +-------------------- + +Nette также автоматически ищет файл с макетом. + +Если вы используете структуру каталогов, где каждый презентер имеет собственный каталог, поместите макет либо в папку с презентером, если он специфичен только для него, либо на уровень выше, если он общий для нескольких презентеров: + +/--pre +app/ +└── Presentation/ + ├── @layout.latte ← общий макет + └── Home/ + ├── @layout.latte ← только для презентера Home + ├── HomePresenter.php + └── default.latte +\-- + +Если вы используете структуру, где презентеры находятся вместе в одном каталоге, а шаблоны — в папке `templates`, макет будет ожидаться в следующих местах: -В этих файлах ожидается компоновка: +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── @layout.latte ← общий макет + ├── Home.@layout.latte ← только для Home, 1-й вариант + └── Home/ + └── @layout.latte ← только для Home, 2-й вариант +\-- -- `templates//@.latte` -- `templates/.@.latte` -- `templates/@.latte` макет, общий для нескольких докладчиков +Если презентер находится в модуле, поиск будет производиться и на более высоких уровнях каталогов, в соответствии с вложенностью модуля. -Где `` это имя текущего ведущего и `` это имя макета, которое по умолчанию равно `'layout'`. Имя может быть изменено с помощью `$this->setLayout('jinyLayout')`, поэтому будут опробованы файлы `@jinyLayout.latte`. +Название макета можно изменить с помощью `$this->setLayout('layoutAdmin')`, и тогда он будет ожидаться в файле `@layoutAdmin.latte`. Также можно напрямую указать файл с шаблоном макета с помощью `$this->setLayout('/path/to/template.latte')`. -Вы также можете напрямую указать имя файла шаблона макета с помощью `$this->setLayout('/path/to/template.latte')`. Использование `$this->setLayout(false)` отключает отслеживание макета. +С помощью `$this->setLayout(false)` или тега `{layout none}` внутри шаблона поиск макета отключается. .[note] -Файлы, в которых производится поиск шаблонов макета, можно изменить, наложив метод [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], который возвращает массив возможных имен файлов. +Файлы, где ищутся шаблоны макета, можно изменить, переопределив метод [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], который возвращает массив возможных имен файлов. -Переменные в шаблоне .[#toc-variables-in-the-template] ------------------------------------------------------- +Переменные в шаблоне +-------------------- -Переменные передаются в шаблон путем записи их в `$this->template`, а затем они доступны в шаблоне как локальные переменные: +Переменные в шаблон передаем, записывая их в `$this->template`, и затем они доступны в шаблоне как локальные переменные: ```php $this->template->article = $this->articles->getById($id); ``` -Таким образом, мы можем легко передавать любые переменные в шаблоны. Однако при разработке надежных приложений часто полезнее ограничить себя. Например, путем явного определения списка переменных, которые ожидает шаблон, и их типов. Это позволит PHP проверять типы, IDE - правильно шептать, а статический анализ - обнаруживать ошибки. +Таким простым способом мы можем передать в шаблоны любые переменные. Однако при разработке надежных приложений полезнее ограничиться. Например, явно определив перечень переменных, которые ожидает шаблон, и их типы. Благодаря этому PHP сможет проверять типы, IDE правильно подсказывать, а статический анализ выявлять ошибки. -И как определить такое перечисление? Просто в виде класса и его свойств. Мы назовем его как presenter, но с `Template` в конце: +А как такой перечень определить? Просто в виде класса и его свойств. Назовем его похоже на презентер, только с `Template` на конце: ```php /** @@ -93,20 +141,20 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template } ``` -Объект `$this->template` в presenter теперь будет экземпляром класса `ArticleTemplate`. Таким образом, PHP будет проверять объявленные типы при записи. А начиная с PHP 8.2, он также будет предупреждать при записи в несуществующую переменную; в предыдущих версиях этого можно добиться с помощью свойства [Nette\SmartObject |utils:smartobject]. +Объект `$this->template` в презентере теперь будет экземпляром класса `ArticleTemplate`. Так что PHP при записи будет проверять объявленные типы. А начиная с версии PHP 8.2 предупредит и о записи в несуществующую переменную, в предыдущих версиях того же можно достичь с помощью трейта [Nette\SmartObject |utils:smartobject]. -Аннотация `@property-read` предназначена для IDE и статического анализа, она заставит работать шепот, см. "PhpStorm и завершение кода для $this->template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. +Аннотация `@property-read` предназначена для IDE и статического анализа, благодаря ей будет работать автодополнение, см. "PhpStorm and code completion for $this⁠-⁠>⁠template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. [* phpstorm-completion.webp *] -Вы также можете позволить себе роскошь шептать в шаблонах, просто установите плагин Latte в PhpStorm и поместите имя класса в начало шаблона, более подробную информацию смотрите в статье "Latte: как набирать систему":https://blog.nette.org/ru/latte-kak-ispol-zovat-sistemu-tipov: +Роскошью автодополнения можно наслаждаться и в шаблонах, достаточно установить в PhpStorm плагин для Latte и указать в начале шаблона имя класса, подробнее в статье "Latte: как работать с системой типов":https://blog.nette.org/ru/latte-how-to-use-type-system: ```latte -{templateType App\Presenters\ArticleTemplate} +{templateType App\Presentation\Article\ArticleTemplate} ... ``` -Таким же образом шаблоны работают в компонентах, просто следуйте соглашению об именовании и создайте класс шаблона `FifteenTemplate` для компонента, например, `FifteenControl`. +Так же работают и шаблоны в компонентах, достаточно лишь соблюдать соглашение об именах и для компонента, например, `FifteenControl` создать класс шаблона `FifteenTemplate`. Если вам нужно создать `$template` как экземпляр другого класса, используйте метод `createTemplate()`: @@ -121,60 +169,60 @@ public function renderDefault(): void ``` -Переменные по умолчанию .[#toc-default-variables] -------------------------------------------------- +Переменные по умолчанию +----------------------- -Презентаторы и компоненты автоматически передают шаблонам несколько полезных переменных: +Презентеры и компоненты автоматически передают в шаблоны несколько полезных переменных: -- `$basePath` - абсолютный URL-путь к корневому каталогу (например, `/eshop`). -- `$baseUrl` - это абсолютный URL-адрес корневого каталога (например. `http://localhost/eshop`) -- `$user` - это объект, [представляющий пользователя |security:authentication]. -- `$presenter` - нынешний ведущий -- `$control` - текущий компонент или ведущий -- `$flashes` - это массив [сообщений |presenters#flash-messages], отправленных функциями `flashMessage()` +- `$basePath` — абсолютный URL-путь к корневому каталогу (например, `/eshop`) +- `$baseUrl` — абсолютный URL к корневому каталогу (например, `http://localhost/eshop`) +- `$user` — объект, [представляющий пользователя |security:authentication] +- `$presenter` — текущий презентер +- `$control` — текущий компонент или презентер +- `$flashes` — массив [сообщений |presenters#Flash-сообщения], отправленных функцией `flashMessage()` -Если вы используете пользовательский класс шаблона, эти переменные будут переданы, если вы создадите для них свойство. +Если вы используете собственный класс шаблона, эти переменные передадутся, если вы создадите для них свойства. -Создание ссылок .[#toc-creating-links] --------------------------------------- +Создание ссылок +--------------- -Шаблон создает таким образом ссылки на других ведущих и мероприятия: +В шаблоне ссылки на другие презентеры и действия создаются следующим образом: ```latte -detail produktu +деталь продукта ``` -Атрибут `n:href` очень удобен для HTML-тегов. ``. Если мы хотим указать ссылку в другом месте, например, в тексте, мы используем `{link}`: +Атрибут `n:href` очень удобен для HTML-тегов ``. Если мы хотим вывести ссылку в другом месте, например, в тексте, используем `{link}`: ```latte -Adresa je: {link Home:default} +Адрес: {link Home:default} ``` -Дополнительные сведения см. в разделе [Создание ссылок URL |creating-links]. +Дополнительную информацию можно найти в главе [Создание URL-ссылок |creating-links]. -Пользовательские фильтры, теги и т.д. .[#toc-custom-filters-tags-etc] ---------------------------------------------------------------------- +Собственные фильтры, теги и т.п. +-------------------------------- -Система шаблонов Latte может быть расширена с помощью пользовательских фильтров, функций, тегов и т.д. Это можно сделать непосредственно в методе `render` или `beforeRender()`: +Систему шаблонов Latte можно расширить собственными фильтрами, функциями, тегами и т. п. Это можно сделать прямо в методе `render` или `beforeRender()`: ```php public function beforeRender(): void { - // přidání filtru + // добавление фильтра $this->template->addFilter('foo', /* ... */); - // nebo konfigurujeme přímo objekt Latte\Engine + // или конфигурируем непосредственно объект Latte\Engine $latte = $this->template->getLatte(); $latte->addFilterLoader(/* ... */); } ``` -Latte версии 3 предлагает более продвинутый способ создания [расширения |latte:creating-extension] для каждого веб-проекта. Вот краткий пример такого класса: +Latte версии 3 предлагает более продвинутый способ — создание [расширения |latte:extending-latte#Latte Extension] для каждого веб-проекта. Пример такого класса: ```php -namespace App\Templating; +namespace App\Presentation\Accessory; final class LatteExtension extends Latte\Extension { @@ -207,22 +255,21 @@ final class LatteExtension extends Latte\Extension } ``` -Мы регистрируем его с помощью [конфигурации |configuration#Latte]: +Зарегистрируем его с помощью [конфигурации |configuration#Шаблоны Latte]: ```neon latte: extensions: - - App\Templating\LatteExtension + - App\Presentation\Accessory\LatteExtension ``` -Перевод .[#toc-translating] ---------------------------- +Перевод +------- -Если вы программируете многоязычное приложение, вам, вероятно, потребуется вывести часть текста в шаблоне на разных языках. Для этого в Nette Framework определен интерфейс перевода [api:Nette\Localization\Translator], который имеет единственный метод `translate()`. Он принимает сообщение `$message`, которое обычно является строкой, и любые другие параметры. Задача состоит в том, чтобы вернуть переведенную строку. -В Nette нет реализации по умолчанию, вы можете выбрать в соответствии с вашими потребностями из нескольких готовых решений, которые можно найти на [Componette |https://componette.org/search/localization]. В их документации описано, как настроить транслятор. +Если вы программируете многоязычное приложение, вам, скорее всего, потребуется выводить некоторые тексты в шаблоне на разных языках. Nette Framework для этой цели определяет интерфейс для перевода [api:Nette\Localization\Translator], который имеет единственный метод `translate()`. Он принимает сообщение `$message`, которое обычно является строкой, и любые другие параметры. Задача — вернуть переведенную строку. В Nette нет реализации по умолчанию, вы можете выбрать из нескольких готовых решений, которые найдете на [Componette |https://componette.org/search/localization], в соответствии со своими потребностями. В их документации вы узнаете, как настроить транслятор. -Шаблоны могут быть настроены с переводчиком, который [мы передадим нам |dependency-injection:passing-dependencies], используя метод `setTranslator()`: +Шаблонам можно установить переводчик, который мы [получим |dependency-injection:passing-dependencies], методом `setTranslator()`: ```php protected function beforeRender(): void @@ -232,38 +279,38 @@ protected function beforeRender(): void } ``` -В качестве альтернативы переводчик можно установить с помощью [конфигурации |configuration#Latte]: +Транслятор альтернативно можно настроить с помощью [конфигурации |configuration#Шаблоны Latte]: ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -Транслятор можно использовать, например, в качестве фильтра `|translate`, передав дополнительные параметры в метод `translate()` (см. `foo, bar`): +Затем переводчик можно использовать, например, как фильтр `|translate`, включая дополнительные параметры, которые передаются методу `translate()` (см. `foo, bar`): ```latte -{='Basket'|translate} +{='Корзина'|translate} {$item|translate} {$item|translate, foo, bar} ``` -Или как тег подчеркивания: +Или как тег с подчеркиванием: ```latte -{_'Basket'} +{_'Корзина'} {_$item} {_$item, foo, bar} ``` -Для перевода раздела шаблона существует парный тег `{translate}` (начиная с версии Latte 2.11, ранее использовался тег `{_}` ): +Для перевода участка шаблона существует парный тег `{translate}` (начиная с Latte 2.11, ранее использовался тег `{_}`): ```latte -{translate}Order{/translate} -{translate foo, bar}Order{/translate} +{translate}Заказ{/translate} +{translate foo, bar}Заказ{/translate} ``` -Translator по умолчанию вызывается во время выполнения при рендеринге шаблона. Latte версии 3, однако, может переводить весь статический текст во время компиляции шаблона. Это экономит производительность, поскольку каждая строка переводится только один раз, а результирующий перевод записывается в скомпилированную форму. При этом в каталоге кэша создается несколько скомпилированных версий шаблона, по одной для каждого языка. Для этого достаточно указать язык в качестве второго параметра: +Транслятор по умолчанию вызывается во время выполнения при отрисовке шаблона. Однако Latte версии 3 умеет переводить все статические тексты уже во время компиляции шаблона. Это экономит производительность, так как каждая строка переводится только один раз, и итоговый перевод записывается в скомпилированную форму. В каталоге кеша таким образом создается несколько скомпилированных версий шаблона, по одной для каждого языка. Для этого достаточно лишь указать язык вторым параметром: ```php protected function beforeRender(): void @@ -273,4 +320,4 @@ protected function beforeRender(): void } ``` -Под статическим текстом подразумевается, например, `{_'hello'}` или `{translate}hello{/translate}`. Нестатический текст, такой как `{_$foo}`, будет продолжать компилироваться на лету. +Статическим текстом считается, например, `{_'hello'}` или `{translate}hello{/translate}`. Нестатические тексты, такие как `{_$foo}`, по-прежнему будут переводиться во время выполнения. diff --git a/application/sl/@home.texy b/application/sl/@home.texy index e78407b83e..ead8f24c66 100644 --- a/application/sl/@home.texy +++ b/application/sl/@home.texy @@ -1,36 +1,85 @@ -Nette aplikacija -**************** +Nette Application +***************** .[perex] -Paket `nette/application` je osnova za ustvarjanje interaktivnih spletnih aplikacij. - -- [Kako delujejo aplikacije |how-it-works]? -- [Bootstrap |Bootstrap] -- [Predstavitve |Presenters] -- [Predloge |Templates] -- [Moduli |Modules] -- [Usmerjanje |Routing] -- [Ustvarjanje povezav URL |creating-links] -- [Interaktivne komponente |components] -- [AJAX in sličice |ajax] -- [Multiplikator |multiplier] -- [Konfiguracija |Configuration] +Nette Application je jedro ogrodja Nette, ki prinaša zmogljiva orodja za ustvarjanje sodobnih spletnih aplikacij. Ponuja vrsto izjemnih lastnosti, ki znatno olajšajo razvoj ter izboljšajo varnost in vzdržljivost kode. Namestitev ---------- -Prenesite in namestite paket s [programom Composer |best-practices:composer]: +Knjižnico prenesete in namestite z orodjem [Composer|best-practices:composer]: ```shell composer require nette/application ``` -| različica | združljivo s PHP + +Zakaj izbrati Nette Application? +-------------------------------- + +Nette je bil vedno pionir na področju spletnih tehnologij. + +**Dvosmerni usmerjevalnik (Router):** Nette ima napreden sistem usmerjanja, ki je edinstven po svoji dvosmernosti - ne samo da prevaja URL-je v akcije aplikacije, ampak lahko tudi povratno generira URL naslove. To pomeni, da: +- Lahko kadarkoli spremenite strukturo URL-jev celotne aplikacije brez potrebe po urejanju predlog +- URL-ji so samodejno kanonizirani, kar izboljšuje SEO +- Usmerjanje je definirano na enem mestu, ne pa razpršeno v anotacijah + +**Komponente in signali:** Vgrajen komponentni sistem, navdihnjen z Delphi in React.js, je med PHP ogrodji popolnoma izjemen: +- Omogoča ustvarjanje ponovno uporabnih UI elementov +- Podpira hierarhično sestavljanje komponent +- Ponuja elegantno obdelavo AJAX zahtev s pomočjo signalov +- Bogata knjižnica pripravljenih komponent na [Componette](https://componette.org) + +**AJAX in odrezki (snippets):** Nette je predstavil revolucionaren način dela z AJAX-om že leta 2009, dolgo pred podobnimi rešitvami, kot sta Hotwire za Ruby on Rails ali Symfony UX Turbo: +- Odrezki omogočajo posodabljanje samo delov strani brez potrebe po pisanju JavaScripta +- Samodejna integracija s komponentnim sistemom +- Pametna invalidacija delov strani +- Minimalna količina prenesenih podatkov + +**Intuitivne predloge [Latte|latte:]:** Najvarnejši sistem predlog za PHP z naprednimi funkcijami: +- Samodejna zaščita pred XSS s kontekstno občutljivim ubežanjem znakov +- Razširljivost s pomočjo lastnih filtrov, funkcij in značk +- Dedovanje predlog in odrezki za AJAX +- Odlična podpora za PHP 8.x s sistemom tipov + +**Dependency Injection:** Nette v celoti izkorišča Dependency Injection: +- Samodejno posredovanje odvisnosti (autowiring) +- Konfiguracija s pomočjo preglednega formata NEON +- Podpora za tovarne komponent + + +Glavne prednosti +---------------- + +- **Varnost**: Samodejna obramba pred [ranljivostmi|nette:vulnerability-protection] kot so XSS, CSRF itd. +- **Produktivnost**: Manj pisanja, več funkcij zahvaljujoč pametnemu načrtovanju +- **Razhroščevanje**: [Tracy razhroščevalnik|tracy:] z usmerjevalno ploščo +- **Zmogljivost**: Pameten predpomnilnik, leno nalaganje komponent +- **Fleksibilnost**: Enostavna prilagoditev URL-jev tudi po zaključku aplikacije +- **Komponente**: Edinstven sistem ponovno uporabnih UI elementov +- **Sodobno**: Polna podpora za PHP 8.4+ in sistem tipov + + +Prvi koraki +----------- + +1. [Kako delujejo aplikacije? |how-it-works] - Razumevanje osnovne arhitekture +2. [Presenterji |presenters] - Delo s presenterji in akcijami +3. [Predloge |templates] - Ustvarjanje predlog v Latte +4. [Usmerjanje |routing] - Konfiguracija URL naslovov +5. [Interaktivne komponente |components] - Uporaba komponentnega sistema + + +Združljivost s PHP +------------------ + +| različica | združljivo s PHP |-----------|------------------- -| Nette Application 4.0 | PHP 8.0 - 8.2 -| Nette Application 3.1 | PHP 7.2 - 8.2 -| Nette Application 3.0 | PHP 7.1 - 8.0 -| Nette Application 2.4 | PHP 5.6 - 8.0 +| Nette Application 4.0 | PHP 8.1 – 8.4 +| Nette Application 3.2 | PHP 8.1 – 8.4 +| Nette Application 3.1 | PHP 7.2 – 8.3 +| Nette Application 3.0 | PHP 7.1 – 8.0 +| Nette Application 2.4 | PHP 5.6 – 8.0 -Velja za najnovejše različice popravkov. +Velja za zadnjo patch različico. diff --git a/application/sl/@left-menu.texy b/application/sl/@left-menu.texy index de4606eaba..5e3ed359c7 100644 --- a/application/sl/@left-menu.texy +++ b/application/sl/@left-menu.texy @@ -1,19 +1,22 @@ -Nette aplikacija -**************** +Nette Application +***************** - [Kako delujejo aplikacije? |how-it-works] -- [Bootstrap |Bootstrap] -- [Predstavniki |Presenters] -- [Predloge |Templates] -- [Moduli |Modules] -- [Usmerjanje |Routing] -- [Ustvarjanje povezav URL |creating-links] +- [Bootstrapping] +- [Presenterji |presenters] +- [Predloge |templates] +- [Struktura imenikov |directory-structure] +- [Usmerjanje |routing] +- [Ustvarjanje URL povezav |creating-links] - [Interaktivne komponente |components] -- [AJAX in sličice |ajax] -- [Multiplikator |multiplier] -- [Konfiguracija |Configuration] +- [AJAX & odrezki |ajax] +- [Multiplier |multiplier] +- [Konfiguracija |configuration] Nadaljnje branje **************** -- [Najboljše prakse |best-practices:] -- [Odpravljanje težav |nette:troubleshooting] +- [Zakaj uporabljati Nette? |www:10-reasons-why-nette] +- [Namestitev |nette:installation] +- [Napišimo prvo aplikacijo! |quickstart:] +- [Navodila in postopki |best-practices:] +- [Reševanje težav |nette:troubleshooting] diff --git a/application/sl/@meta.texy b/application/sl/@meta.texy new file mode 100644 index 0000000000..724324bee5 --- /dev/null +++ b/application/sl/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokumentacija}} diff --git a/application/sl/ajax.texy b/application/sl/ajax.texy index a30819a9a5..d0152d84cd 100644 --- a/application/sl/ajax.texy +++ b/application/sl/ajax.texy @@ -1,38 +1,45 @@ -AJAX in sličice -*************** +AJAX & odrezki +**************
    -Sodobne spletne aplikacije danes tečejo pol na strežniku in pol v brskalniku. AJAX je ključni povezovalni dejavnik. Kakšno podporo ponuja ogrodje Nette? -- pošiljanje fragmentov predlog (tako imenovanih *snippets*) +V dobi sodobnih spletnih aplikacij, kjer je funkcionalnost pogosto razdeljena med strežnikom in brskalnikom, je AJAX nujen povezovalni element. Kakšne možnosti nam na tem področju ponuja Nette Framework? +- pošiljanje delov predloge, t.i. odrezkov - posredovanje spremenljivk med PHP in JavaScriptom -- razhroščevanje aplikacij AJAX +- orodja za razhroščevanje AJAX zahtevkov
    -Zahtevo AJAX je mogoče zaznati z metodo storitve, ki [enkapsulira zahtevo HTTP |http:request] `$httpRequest->isAjax()` (zaznava na podlagi glave `X-Requested-With` HTTP). Obstaja tudi skrajšana metoda v programu presenter: `$this->isAjax()`. -Zahteva AJAX se v ničemer ne razlikuje od običajne zahteve - presenter se pokliče z določenim pogledom in parametri. Tudi od predstavnika je odvisno, kako se bo odzval: s svojimi rutinami lahko vrne fragment kode HTML (snippet), dokument XML, objekt JSON ali del kode Javascript. +AJAX zahtevek +============= -Na voljo je vnaprej obdelan objekt, imenovan `payload`, namenjen pošiljanju podatkov brskalniku v obliki JSON. +AJAX zahtevek se v bistvu ne razlikuje od klasičnega HTTP zahtevka. Pokliče se presenter z določenimi parametri. Od presenterja pa je odvisno, kako se bo na zahtevek odzval - lahko vrne podatke v formatu JSON, pošlje del HTML kode, XML dokument itd. -```php -public function actionDelete(int $id): void -{ - if ($this->isAjax()) { - $this->payload->message = 'Success'; - } - // ... -} +Na strani brskalnika inicializiramo AJAX zahtevek s funkcijo `fetch()`: + +```js +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) +.then(response => response.json()) +.then(payload => { + // obdelava odgovora +}); ``` -Za popoln nadzor nad izpisom JSON uporabite metodo `sendJson` v svojem predstavitvenem programu. S tem se takoj zaključi predstavitveni program, vi pa boste opravili brez predloge: +Na strani strežnika prepoznamo AJAX zahtevek z metodo `$httpRequest->isAjax()` storitve [enkapsulirajoč HTTP zahtevek |http:request]. Za zaznavanje uporablja HTTP glavo `X-Requested-With`, zato je pomembno, da jo pošiljamo. V okviru presenterja lahko uporabimo metodo `$this->isAjax()`. + +Če želite poslati podatke v formatu JSON, uporabite metodo [`sendJson()` |presenters#Pošiljanje odgovora]. Metoda prav tako zaključi delovanje presenterja. ```php -$this->sendJson(['key' => 'value', /* ... */]); +public function actionExport(): void +{ + $this->sendJson($this->model->getData); +} ``` -Če želimo poslati HTML, lahko nastavimo posebno predlogo za zahteve AJAX: +Če nameravate odgovoriti s posebno predlogo, namenjeno za AJAX, lahko to storite na naslednji način: ```php public function handleClick($param): void @@ -45,74 +52,80 @@ public function handleClick($param): void ``` -Naja .[#toc-naja] -================= +Odrezki +======= + +Najmočnejše sredstvo, ki ga Nette ponuja za povezovanje strežnika s klientom, so odrezki. Zahvaljujoč njim lahko iz navadne aplikacije naredite AJAX aplikacijo z minimalnim naporom in nekaj vrsticami kode. Kako vse skupaj deluje, prikazuje primer Fifteen, katerega kodo najdete na [GitHubu |https://github.com/nette-examples/fifteen]. + +Odrezki omogočajo posodabljanje samo delov strani, namesto da bi se celotna stran ponovno nalagala. To ni samo hitrejše in učinkovitejše, ampak zagotavlja tudi udobnejšo uporabniško izkušnjo. Odrezki vas lahko spominjajo na Hotwire za Ruby on Rails ali Symfony UX Turbo. Zanimivo je, da je Nette predstavil odrezke že 14 let prej. + +Kako odrezki delujejo? Ob prvem nalaganju strani (ne-AJAX zahtevek) se naloži celotna stran, vključno z vsemi odrezki. Ko uporabnik interagira s stranjo (npr. klikne na gumb, pošlje obrazec itd.), se namesto nalaganja celotne strani sproži AJAX zahtevek. Koda v presenterju izvede akcijo in odloči, katere odrezke je treba posodobiti. Nette te odrezke izriše in jih pošlje v obliki polja v formatu JSON. Obdelovalna koda v brskalniku prejete odrezke vstavi nazaj v stran. Prenaša se torej samo koda spremenjenih odrezkov, kar prihrani pasovno širino in pospeši nalaganje v primerjavi s prenosom vsebine celotne strani. + - [Knjižnica Naja |https://naja.js.org] se uporablja za obdelavo zahtevkov AJAX na strani brskalnika. [Namestite |https://naja.js.org/#/guide/01-install-setup-naja] jo kot paket node.js (za uporabo s programi Webpack, Rollup, Vite, Parcel in drugimi): +Naja +---- + +Za obdelavo odrezkov na strani brskalnika služi [knjižnica Naja |https://naja.js.org]. To [namestite |https://naja.js.org/#/guide/01-install-setup-naja] kot node.js paket (za uporabo z aplikacijami Webpack, Rollup, Vite, Parcel in drugimi): ```shell npm install naja ``` -...ali pa jo vstavite neposredno v predlogo strani: +…ali pa jo neposredno vstavite v predlogo strani: ```html ``` +Najprej je treba knjižnico [inicializirati |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization]: -. .[#toc-snippets] -================== +```js +naja.initialize(); +``` -Obstaja veliko močnejše orodje vgrajene podpore AJAX - snippets. Z njihovo uporabo je mogoče navadno aplikacijo spremeniti v aplikacijo AJAX z uporabo le nekaj vrstic kode. Kako vse to deluje, je prikazano v primeru Fifteen, katerega koda je na voljo tudi v sestavi ali na [GitHubu |https://github.com/nette-examples/fifteen]. +Da bi se iz navadne povezave (signala) ali pošiljanja obrazca ustvaril AJAX zahtevek, je dovolj označiti ustrezno povezavo, obrazec ali gumb z razredom `ajax`: -Snippets delujejo tako, da se med začetno zahtevo (tj. zahtevo brez AJAX-a) prenese celotna stran, nato pa se pri vsaki [podpovprašitvi |components#signal] AJAX-a (zahteva istega pogleda istega predstavnika) v prej omenjeno shrambo `payload` prenese le koda spremenjenih delov. +```html +Go -Snippets vas morda spominjajo na Hotwire za Ruby on Rails ali Symfony UX Turbo, vendar jih je Nette zasnoval že štirinajst let prej. +
    + +
    +ali -Neveljavnost nizov (Snippets) .[#toc-invalidation-of-snippets] -============================================================== +
    + +
    +``` -Vsak potomec razreda [Control |components] (kar je tudi Presenter) si lahko zapomni, ali je med zahtevo prišlo do kakšnih sprememb, zaradi katerih je treba ponovno prikazati. Za to obstaja par metod: `redrawControl()` in `isControlInvalid()`. Primer: + +Prekresljevanje odrezkov +------------------------ + +Vsak objekt razreda [Control |components] (vključno s samim Presenterjem) beleži, ali so se zgodile spremembe, ki zahtevajo njegovo prekreslitev. Za to služi metoda `redrawControl()`: ```php public function handleLogin(string $user): void { - // Objekt se mora ponovno prikazati, ko se uporabnik prijavi + // po prijavi je treba prekresliti relevantni del $this->redrawControl(); // ... } ``` -Nette pa ponuja še natančnejšo ločljivost kot celotne komponente. Navedeni metodi kot neobvezni parameter sprejmeta ime tako imenovanega "snippeta". "Snippet" je v bistvu element v vaši predlogi, ki je v ta namen označen z oznako Latte, več o tem pozneje. Tako je mogoče od komponente zahtevati, da na novo nariše samo *delčke* svoje predloge. Če je celotna komponenta razveljavljena, se ponovno izrišejo vse njene sličice. Komponenta je "neveljavna" tudi, če je neveljavna katera koli njena podkomponenta. - -```php -$this->isControlInvalid(); // -> false -$this->redrawControl('header'); // razveljavi odlomek z imenom 'header' -$this->isControlInvalid('header'); // -> true -$this->isControlInvalid('footer'); // -> false -$this->isControlInvalid(); // -> true, vsaj en delček je neveljaven +Nette omogoča še natančnejši nadzor nad tem, kaj se mora prekresliti. Navedena metoda namreč lahko kot argument sprejme ime odrezka. Tako lahko razveljavimo (razumite: prisilimo prekreslitev) na ravni delov predloge. Če se razveljavi celotna komponenta, se prekresli tudi vsak njen odrezek: -$this->redrawControl(); // razveljavi celotno komponento, vsak odlomek -$this->isControlInvalid('footer'); // -> true +```php +// razveljavi odrezek 'header' +$this->redrawControl('header'); ``` -Komponenta, ki prejme signal, je samodejno označena za ponovno izrisovanje. - -Zahvaljujoč ponovnemu izrisu snippetov natančno vemo, katere dele katerih elementov je treba ponovno izrisati. - - -Oznaka `{snippet} … {/snippet}` .{toc: Tag snippet} -=================================================== -Prikazovanje strani poteka zelo podobno kot pri običajni zahtevi: naložijo se iste predloge itd. Bistveno pa je, da se izpustijo deli, ki naj ne bi dosegli izpisa; drugi deli se povežejo z identifikatorjem in pošljejo uporabniku v obliki, razumljivi za obdelovalnik JavaScript. +Odrezki v Latte +--------------- - -Sintaksa .[#toc-syntax] ------------------------ - -Če je v predlogi kontrolni element ali izsek, ga moramo oviti z uporabo oznake `{snippet} ... {/snippet}` pair - ta bo poskrbela, da bo izrisani izsek "izrezan" in poslan brskalniku. Prav tako ga bo zaprla v pomožno vrstico `
    ` (možno je uporabiti tudi drugačno oznako). V naslednjem primeru je opredeljen izsek z imenom `header`. Prav tako lahko predstavlja predlogo komponente: +Uporaba odrezkov v Latte je izjemno enostavna. Če želite definirati del predloge kot odrezek, ga preprosto ovijte z značkama `{snippet}` in `{/snippet}`: ```latte {snippet header} @@ -120,7 +133,9 @@ Sintaksa .[#toc-syntax] {/snippet} ``` -Snippet tipa, ki ni `
    ` ali izsek z dodatnimi atributi HTML se doseže z uporabo različice atributa: +Odrezek ustvari v HTML strani element `
    ` s posebnim generiranim `id`. Pri prekreslitvi odrezka se nato posodobi vsebina tega elementa. Zato je nujno, da se ob prvotnem izrisu strani izrišejo tudi vsi odrezki, čeprav so lahko na začetku prazni. + +Lahko ustvarite tudi odrezek z drugim elementom kot `
    ` s pomočjo n:atributa: ```latte
    @@ -129,138 +144,106 @@ Snippet tipa, ki ni `
    ` ali izsek z dodatnimi atributi HTML se doseže z upo ``` -Dinamični utrinki .[#toc-dynamic-snippets] -========================================== +Območja odrezkov +---------------- -V programu Nette lahko na podlagi parametra v času izvajanja določite tudi dinamično ime snippetov. To je najbolj primerno za različne sezname, kjer moramo spremeniti samo eno vrstico, vendar ne želimo skupaj z njo prenesti celotnega seznama. Primer tega je: +Imena odrezkov so lahko tudi izrazi: ```latte -
      - {foreach $list as $id => $item} -
    • {$item} update
    • - {/foreach} -
    +{foreach $items as $id => $item} +
  • {$item}
  • +{/foreach} ``` -Obstaja en statični niz z imenom `itemsContainer`, ki vsebuje več dinamičnih nizov: `item-0`, `item-1` in tako naprej. +Tako dobimo več odrezkov `item-0`, `item-1` itd. Če bi neposredno razveljavili dinamični odrezek (na primer `item-1`), se ne bi prekreslilo nič. Razlog je ta, da odrezki res delujejo kot izrezki in se izrisujejo samo neposredno oni sami. Vendar v predlogi dejansko ni nobenega odrezka z imenom `item-1`. Ta nastane šele z izvajanjem kode v okolici odrezka, torej zanke foreach. Zato označimo del predloge, ki se mora izvesti, s pomočjo značke `{snippetArea}`: -Dinamičnega utrinka ne morete narisati neposredno (ponovno narisanje `item-1` nima učinka), temveč morate narisati njegov nadrejeni utrinek (v tem primeru `itemsContainer`). To povzroči, da se izvede koda nadrejenega odlomka, vendar se nato brskalniku pošljejo samo njegovi pododlomki. Če želite poslati samo enega od podnapisov, morate spremeniti vnos za nadrejeni odlomek, da ne bo ustvaril drugih podnapisov. +```latte +
      + {foreach $items as $id => $item} +
    • {$item}
    • + {/foreach} +
    +``` -V zgornjem primeru morate poskrbeti, da bo za zahtevo AJAX v polje `$list` dodan samo en element, zato bo zanka `foreach` izpisala samo en dinamični odlomek. +In pustimo prekresliti tako sam odrezek kot tudi celotno nadrejeno območje: ```php -class HomePresenter extends Nette\Application\UI\Presenter -{ - /** - * This method returns data for the list. - * Usually this would just request the data from a model. - * For the purpose of this example, the data is hard-coded. - */ - private function getTheWholeList(): array - { - return [ - 'First', - 'Second', - 'Third', - ]; - } - - public function renderDefault(): void - { - if (!isset($this->template->list)) { - $this->template->list = $this->getTheWholeList(); - } - } - - public function handleUpdate(int $id): void - { - $this->template->list = $this->isAjax() - ? [] - : $this->getTheWholeList(); - $this->template->list[$id] = 'Updated item'; - $this->redrawControl('itemsContainer'); - } -} +$this->redrawControl('itemsContainer'); +$this->redrawControl('item-1'); ``` +Hkrati je priporočljivo zagotoviti, da polje `$items` vsebuje samo tiste elemente, ki se morajo prekresliti. -Utrinki v vključeni predlogi .[#toc-snippets-in-an-included-template] -===================================================================== - -Lahko se zgodi, da je snippet v predlogi, ki je vključena iz druge predloge. V tem primeru moramo vključitveno kodo v drugi predlogi oviti z oznako `snippetArea`, nato pa ponovno narišemo območje snippetArea in dejanski snippet. - -Oznaka `snippetArea` zagotavlja, da se koda v njej izvrši, vendar se brskalniku pošlje le dejanski odlomek iz vključene predloge. +Če v predlogo s pomočjo značke `{include}` vključujemo drugo predlogo, ki vsebuje odrezke, je treba vključitev predloge ponovno vključiti v `snippetArea` in jo razveljaviti skupaj z odrezkom: ```latte -{* parent.latte *} -{snippetArea wrapper} - {include 'child.latte'} +{snippetArea include} + {include 'included.latte'} {/snippetArea} ``` + ```latte -{* child.latte *} +{* included.latte *} {snippet item} -... + ... {/snippet} ``` + ```php -$this->redrawControl('wrapper'); +$this->redrawControl('include'); $this->redrawControl('item'); ``` -Kombinirate jo lahko tudi z dinamičnimi utrinki. - -Dodajanje in brisanje .[#toc-adding-and-deleting] -================================================= +Odrezki v komponentah +--------------------- -Če na seznam dodate nov element in razveljavite `itemsContainer`, zahteva AJAX vrne odlomke, vključno z novim elementom, vendar ga obdelovalnik javascript ne bo mogel prikazati. Razlog za to je, da ni elementa HTML z novo ustvarjenim ID. - -V tem primeru je najpreprostejši način, da celoten seznam zavijete v še en izsek in ga razveljavite: +Odrezke lahko ustvarjate tudi v [komponentah|components] in Nette jih bo samodejno prekresljeval. Vendar obstaja določena omejitev: za prekreslitev odrezkov kliče metodo `render()` brez parametrov. Torej posredovanje parametrov v predlogi ne bo delovalo: ```latte -{snippet wholeList} -
      - {foreach $list as $id => $item} -
    • {$item} update
    • - {/foreach} -
    -{/snippet} -Add +OK +{control productGrid} + +ne bo delovalo: +{control productGrid $arg, $arg} +{control productGrid:paginator} ``` + +Pošiljanje uporabniških podatkov +-------------------------------- + +Skupaj z odrezki lahko klientu pošljete poljubne druge podatke. Dovolj je, da jih zapišete v objekt `payload`: + ```php -public function handleAdd(): void +public function actionDelete(int $id): void { - $this->template->list = $this->getTheWholeList(); - $this->template->list[] = 'New one'; - $this->redrawControl('wholeList'); + // ... + if ($this->isAjax()) { + $this->payload->message = 'Uspeh'; + } } ``` -Enako velja za brisanje elementa. Možno bi bilo poslati prazen snippet, vendar so običajno seznami lahko oštevilčeni in bi bilo zapleteno izvesti brisanje enega elementa in nalaganje drugega (ki je bil prej na drugi strani oštevilčenega seznama). - -Pošiljanje parametrov sestavini .[#toc-sending-parameters-to-component] -======================================================================= +Posredovanje parametrov +======================= -Ko komponenti prek zahteve AJAX pošiljamo parametre, bodisi signalne bodisi trajne, moramo navesti njihovo globalno ime, ki vsebuje tudi ime komponente. Polno ime parametra vrne metoda `getParameterId()`. +Če komponenti s pomočjo AJAX zahtevka pošiljamo parametre, bodisi parametre signala ali persistentne parametre, moramo pri zahtevku navesti njihovo globalno ime, ki vsebuje tudi ime komponente. Celotno ime parametra vrne metoda `getParameterId()`. ```js -$.getJSON( - {link changeCountBasket!}, - { - {$control->getParameterId('id')}: id, - {$control->getParameterId('count')}: count - } -}); +let url = new URL({link //foo!}); +url.searchParams.set({$control->getParameterId('bar')}, bar); + +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) ``` -In obdela metodo z ustreznimi parametri v komponenti. +In `handle` metoda z ustreznimi parametri v komponenti: ```php -public function handleChangeCountBasket(int $id, int $count): void +public function handleFoo(int $bar): void { - } ``` diff --git a/application/sl/bootstrap.texy b/application/sl/bootstrap.texy deleted file mode 100644 index ba8eb0b587..0000000000 --- a/application/sl/bootstrap.texy +++ /dev/null @@ -1,233 +0,0 @@ -Bootstrap -********* - -
    - -Bootstrap je zagonska koda, ki inicializira okolje, ustvari vsebnik za vbrizgavanje odvisnosti (DI) in zažene aplikacijo. Obravnavali bomo: - -- kako konfigurirati aplikacijo z datotekami NEON -- kako ravnati s produkcijskim in razvojnim načinom -- kako ustvariti vsebnik DI - -
    - - -Aplikacije, ne glede na to, ali temeljijo na spletu ali skripti ukazne vrstice, se začnejo z določeno obliko inicializacije okolja. V starih časih je bila za to lahko odgovorna datoteka z imenom npr. `include.inc.php`, ki je bila vključena v začetno datoteko. -V sodobnih aplikacijah Nette jo je nadomestil razred `Bootstrap`, ki se kot del aplikacije nahaja v datoteki `app/Bootstrap.php`. Izgleda lahko na primer takole: - -```php -use Nette\Bootstrap\Configurator; - -class Bootstrap -{ - public static function boot(): Configurator - { - $appDir = dirname(__DIR__); - $configurator = new Configurator; - //$configurator->setDebugMode('secret@23.75.345.200'); - $configurator->enableTracy($appDir . '/log'); - $configurator->setTempDirectory($appDir . '/temp'); - $configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); - $configurator->addConfig($appDir . '/config/common.neon'); - return $configurator; - } -} -``` - - -index.php .[#toc-index-php] -=========================== - -Pri spletnih aplikacijah je začetna datoteka `index.php`, ki se nahaja v javnem imeniku `www/`. Omogoča, da razred `Bootstrap` inicializira okolje in vrne `$configurator`, ki ustvari vsebnik DI. Nato pridobi storitev `Application`, ki izvaja spletno aplikacijo: - -```php -// inicializacija okolja + pridobitev predmeta Configurator -$configurator = App\Bootstrap::boot(); -// ustvarite vsebnik DI -$container = $configurator->createContainer(); -// vsebnik DI ustvari objekt Nette\Application\Application -$application = $container->getByType(Nette\Application\Application::class); -// zaženite aplikacijo Nette -$application->run(); -``` - -Kot vidite, razred [api:Nette\Bootstrap\Configurator], ki ga bomo zdaj podrobneje predstavili, pomaga pri vzpostavljanju okolja in ustvarjanju vsebnika za vbrizgavanje odvisnosti (DI). - - -Razvojni in produkcijski način .[#toc-development-vs-production-mode] -===================================================================== - -Nette razlikuje med dvema osnovnima načinoma izvajanja zahtevka: razvojnim in produkcijskim. Razvojni način je osredotočen na čim večje udobje programerja, prikazan je Tracy, predpomnilnik se samodejno posodablja ob spreminjanju predlog ali konfiguracije vsebnika DI itd. Produkcijski način je osredotočen na zmogljivost, Tracy beleži le napake, spremembe predlog in drugih datotek pa se ne preverjajo. - -Izbira načina poteka s samodejnim zaznavanjem, zato običajno ni treba ničesar ročno konfigurirati ali preklapljati. Način je razvojni, če aplikacija teče na lokalnem gostitelju (tj. naslov IP `127.0.0.1` ali `::1`) in ni prisoten posrednik (tj. njegova glavička HTTP). V nasprotnem primeru deluje v produkcijskem načinu. - -Če želite omogočiti razvojni način v drugih primerih, na primer za programerje, ki dostopajo z določenega naslova IP, lahko uporabite `setDebugMode()`: - -```php -$configurator->setDebugMode('23.75.345.200'); // enega ali več naslovov IP. -``` - -Vsekakor priporočamo kombinacijo naslova IP s piškotkom. V piškotek `nette-debug` bomo shranili tajni žeton, npr. `secret1234`, razvojni način pa bo aktiviran za programerje s to kombinacijo IP in piškotka. - -```php -$configurator->setDebugMode('secret1234@23.75.345.200'); -``` - -Razvojni način lahko tudi popolnoma izklopimo, tudi za lokalni gostitelj: - -```php -$configurator->setDebugMode(false); -``` - -Vrednost `true` vklopi način za razvijalce, kar se na produkcijskem strežniku ne bi smelo zgoditi. - - -Orodje za razhroščevanje Tracy .[#toc-debugging-tool-tracy] -=========================================================== - -Za lažje razhroščevanje bomo vklopili odlično orodje [Tracy |tracy:]. V načinu za razvijalce vizualizira napake, v produkcijskem načinu pa napake beleži v določen imenik: - -```php -$configurator->enableTracy($appDir . '/log'); -``` - - -Začasne datoteke .[#toc-temporary-files] -======================================== - -Nette uporablja predpomnilnik za vsebnik DI, RobotLoader, predloge itd. Zato je treba nastaviti pot do imenika, v katerem bo shranjen predpomnilnik: - -```php -$configurator->setTempDirectory($appDir . '/temp'); -``` - -V operacijskem sistemu Linux ali macOS nastavite [dovoljenja za pisanje za |nette:troubleshooting#Setting directory permissions] imenike `log/` in `temp/`. - - -RobotLoader .[#toc-robotloader] -=============================== - -Običajno bomo želeli samodejno naložiti razrede z [RobotLoaderjem |robot-loader:], zato ga moramo zagnati in mu omogočiti, da naloži razrede iz imenika, kjer se nahaja `Bootstrap.php` (tj. `__DIR__`), in vseh njegovih podimenikov: - -```php -$configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); -``` - -Druga možnost je, da uporabimo samo samodejno nalaganje [Composer |best-practices:composer] PSR-4. - - -Časovni pas .[#toc-timezone] -============================ - -Configurator vam omogoča, da določite časovni pas za svojo aplikacijo. - -```php -$configurator->setTimeZone('Europe/Prague'); -``` - - -Konfiguracija zabojnika DI .[#toc-di-container-configuration] -============================================================= - -Del zagonskega postopka je ustvarjanje vsebnika DI, tj. tovarne za predmete, ki je srce celotne aplikacije. To je pravzaprav razred PHP, ki ga ustvari Nette in je shranjen v imeniku predpomnilnika. Tovarna izdeluje ključne objekte aplikacije, konfiguracijske datoteke pa ji dajejo navodila, kako naj jih ustvari in konfigurira, s čimer vplivamo na obnašanje celotne aplikacije. - -Konfiguracijske datoteke so običajno zapisane v [formatu NEON |neon:format]. [Kaj vse je mogoče konfigurirati |nette:configuring], si lahko preberete [tukaj |nette:configuring]. - -.[tip] -V razvojnem načinu se vsebnik samodejno posodobi vsakič, ko spremenite kodo ali konfiguracijske datoteke. V produkcijskem načinu se ustvari samo enkrat, spremembe datotek pa se ne preverjajo, da bi povečali zmogljivost. - -Konfiguracijske datoteke se naložijo z uporabo `addConfig()`: - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -``` - -Metodo `addConfig()` lahko za dodajanje več datotek pokličete večkrat. - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -$configurator->addConfig($appDir . '/config/local.neon'); -if (PHP_SAPI === 'cli') { - $configurator->addConfig($appDir . '/config/cli.php'); -} -``` - -Ime `cli.php` ni tiskarska napaka, konfiguracijo lahko zapišete tudi v datoteko PHP, ki jo vrne kot polje. - -Druga možnost je, da z [razdelkom`includes` |dependency-injection:configuration#including files] naložimo več konfiguracijskih datotek. - -Če se v konfiguracijskih datotekah pojavijo elementi z enakimi ključi, se bodo [prepisali ali združili |dependency-injection:configuration#Merging] v primeru polj. Kasneje vključena datoteka ima višjo prioriteto kot prejšnja. Datoteka, v kateri je naveden razdelek `includes`, ima višjo prednost kot datoteke, ki so vanjo vključene. - - -Statični parametri .[#toc-static-parameters] --------------------------------------------- - -Parametre, ki se uporabljajo v konfiguracijskih datotekah, je mogoče opredeliti [v razdelku `parameters` |dependency-injection:configuration#parameters] in jih tudi posredovati (ali prepisati) z metodo `addStaticParameters()` (ima vzdevek `addParameters()`). Pomembno je, da različne vrednosti parametrov povzročijo generiranje dodatnih vsebnikov DI, tj. dodatnih razredov. - -```php -$configurator->addStaticParameters([ - 'projectId' => 23, -]); -``` - -V konfiguracijskih datotekah lahko zapišemo običajni zapis `%projectId%` za dostop do parametra z imenom `projectId`. Privzeto konfigurator izpolni naslednje parametre: `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` in `consoleMode`. - - -Dinamični parametri .[#toc-dynamic-parameters] ----------------------------------------------- - -Kontejnerju lahko dodamo tudi dinamične parametre, katerih različne vrednosti za razliko od statičnih parametrov ne bodo povzročile generiranja novih DI kontejnerjev. - -```php -$configurator->addDynamicParameters([ - 'remoteIp' => $_SERVER['REMOTE_ADDR'], -]); -``` - -Spremenljivke okolja bi lahko preprosto dali na voljo z dinamičnimi parametri. Do njih lahko dostopamo prek spletne strani `%env.variable%` v konfiguracijskih datotekah. - -```php -$configurator->addDynamicParameters([ - 'env' => getenv(), -]); -``` - - -Uvožene storitve .[#toc-imported-services] ------------------------------------------- - -Zdaj se bomo poglobili. Čeprav je namen vsebnika DI ustvarjanje objektov, se lahko izjemoma pojavi potreba po vstavitvi obstoječega objekta v vsebnik. To storimo tako, da definiramo storitev z atributom `imported: true`. - -```neon -services: - myservice: - type: App\Model\MyCustomService - imported: true -``` - -Ustvarite nov primerek in ga vstavite v bootstrap: - -```php -$configurator->addServices([ - 'myservice' => new App\Model\MyCustomService('foobar'), -]); -``` - - -Različna okolja .[#toc-different-environments] -============================================== - -Razred `Bootstrap` lahko prilagodite svojim potrebam. Metodi `boot()` lahko dodate parametre za razlikovanje med spletnimi projekti ali dodate druge metode, kot so `bootForTests()`, ki inicializira okolje za teste enote, `bootForCli()` za skripte, ki se kličejo iz ukazne vrstice, in tako naprej. - -```php -public static function bootForTests(): Configurator -{ - $configurator = self::boot(); - Tester\Environment::setup(); // Inicializacija Nette Testerja - return $configurator; -} -``` diff --git a/application/sl/bootstrapping.texy b/application/sl/bootstrapping.texy new file mode 100644 index 0000000000..c529fd023f --- /dev/null +++ b/application/sl/bootstrapping.texy @@ -0,0 +1,297 @@ +Bootstrapping +************* + +
    + +Bootstrapping je proces inicializacije okolja aplikacije, ustvarjanja vsebnika za vstavljanje odvisnosti (DI) in zagona aplikacije. Razpravljali bomo o: + +- kako razred Bootstrap inicializira okolje +- kako so aplikacije konfigurirane z uporabo NEON datotek +- kako razlikovati med produkcijskim in razvojnim načinom +- kako ustvariti in konfigurirati DI vsebnik + +
    + + +Aplikacije, bodisi spletne ali skripti, zagnani iz ukazne vrstice, začnejo svoje delovanje z neko obliko inicializacije okolja. V davnih časih je za to skrbel datoteka z imenom, na primer `include.inc.php`, ki jo je prvotna datoteka vključila. V sodobnih Nette aplikacijah jo je nadomestil razred `Bootstrap`, ki ga kot del aplikacije najdete v datoteki `app/Bootstrap.php`. Lahko izgleda na primer takole: + +```php +use Nette\Bootstrap\Configurator; + +class Bootstrap +{ + private Configurator $configurator; + private string $rootDir; + + public function __construct() + { + $this->rootDir = dirname(__DIR__); + // Konfigurator je odgovoren za nastavitev okolja aplikacije in storitev. + $this->configurator = new Configurator; + // Nastavi mapo za začasne datoteke, ki jih generira Nette (npr. prevedene predloge) + $this->configurator->setTempDirectory($this->rootDir . '/temp'); + } + + public function bootWebApplication(): Nette\DI\Container + { + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); + } + + private function initializeEnvironment(): void + { + // Nette je pameten in razvojni način se vklopi samodejno, + // ali pa ga lahko omogočite za določen IP naslov z odkomentiranjem naslednje vrstice: + // $this->configurator->setDebugMode('secret@23.75.345.200'); + + // Aktivira Tracy: ultimativni "švicarski nož" za razhroščevanje. + $this->configurator->enableTracy($this->rootDir . '/log'); + + // RobotLoader: samodejno naloži vse razrede v izbrani mapi + $this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); + } + + private function setupContainer(): void + { + // Naloži konfiguracijske datoteke + $this->configurator->addConfig($this->rootDir . '/config/common.neon'); + } +} +``` + + +index.php +========= + +Prvotna datoteka je v primeru spletnih aplikacij `index.php`, ki se nahaja v [javni mapi |directory-structure#Javna mapa www] `www/`. Ta si pusti od razreda Bootstrap inicializirati okolje in izdelati DI vsebnik. Nato iz njega pridobi storitev `Application`, ki zažene spletno aplikacijo: + +```php +$bootstrap = new App\Bootstrap; +// Inicializacija okolja + ustvarjanje DI vsebnika +$container = $bootstrap->bootWebApplication(); +// DI vsebnik ustvari objekt Nette\Application\Application +$application = $container->getByType(Nette\Application\Application::class); +// Zagon aplikacije Nette in obdelava dohodnega zahtevka +$application->run(); +``` + +Kot je vidno, pri nastavitvi okolja in ustvarjanju dependency injection (DI) vsebnika pomaga razred [api:Nette\Bootstrap\Configurator], ki si ga bomo zdaj podrobneje predstavili. + + +Razvojni vs produkcijski način +============================== + +Nette se obnaša različno glede na to, ali teče na razvojnem ali produkcijskem strežniku: + +🛠️ Razvojni način (Development): + - Prikazuje Tracy debugbar z uporabnimi informacijami (SQL poizvedbe, čas izvajanja, uporabljeni pomnilnik) + - Ob napaki prikaže podrobno stran z napako s klici funkcij in vsebino spremenljivk + - Samodejno obnavlja predpomnilnik ob spremembi Latte predlog, urejanju konfiguracijskih datotek itd. + + +🚀 Produkcijski način (Production): + - Ne prikazuje nobenih informacij za razhroščevanje, vse napake zapisuje v dnevnik + - Ob napaki prikaže ErrorPresenter ali splošno stran "Server Error" + - Predpomnilnik se nikoli samodejno ne obnavlja! + - Optimiziran za hitrost in varnost + + +Izbira načina se izvaja s samodejnim zaznavanjem, zato običajno ni treba ničesar konfigurirati ali ročno preklapljati: + +- razvojni način: na localhostu (IP naslov `127.0.0.1` ali `::1`) če ni prisoten proxy (tj. njegova HTTP glava) +- produkcijski način: povsod drugje + +Če želimo razvojni način omogočiti tudi v drugih primerih, na primer programerjem, ki dostopajo z določenega IP naslova, uporabimo `setDebugMode()`: + +```php +$this->configurator->setDebugMode('23.75.345.200'); // lahko navedemo tudi polje IP naslovov +``` + +Vsekakor priporočamo kombiniranje IP naslova s piškotkom. V piškotek `nette-debug` shranimo skrivni žeton, npr. `secret1234`, in na ta način aktiviramo razvojni način za programerje, ki dostopajo z določenega IP naslova in imajo hkrati v piškotku omenjeni žeton: + +```php +$this->configurator->setDebugMode('secret1234@23.75.345.200'); +``` + +Razvojni način lahko tudi popolnoma izklopimo, tudi za localhost: + +```php +$this->configurator->setDebugMode(false); +``` + +Pozor, vrednost `true` vklopi razvojni način na trdo, kar se nikoli ne sme zgoditi na produkcijskem strežniku. + + +Orodje za razhroščevanje Tracy +============================== + +Za enostavno razhroščevanje še vklopimo odlično orodje [Tracy |tracy:]. V razvojnem načinu vizualizira napake in v produkcijskem načinu napake beleži v navedeno mapo: + +```php +$this->configurator->enableTracy($this->rootDir . '/log'); +``` + + +Začasne datoteke +================ + +Nette uporablja predpomnilnik za DI vsebnik, RobotLoader, predloge itd. Zato je treba nastaviti pot do mape, kamor se bo predpomnilnik shranjeval: + +```php +$this->configurator->setTempDirectory($this->rootDir . '/temp'); +``` + +Na Linuxu ali macOS nastavite mapama `log/` in `temp/` [pravice za pisanje |nette:troubleshooting#Nastavitev pravic map]. + + +RobotLoader +=========== + +Praviloma bomo želeli samodejno nalagati razrede s pomočjo [RobotLoaderja |robot-loader:], zato ga moramo zagnati in mu pustiti, da nalaga razrede iz mape, kjer se nahaja `Bootstrap.php` (tj. `__DIR__`), in vseh podmap: + +```php +$this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); +``` + +Alternativni pristop je, da pustimo razrede nalagati samo prek [Composerja |best-practices:composer] ob upoštevanju PSR-4. + + +Časovni pas +=========== + +Prek konfiguratorja lahko nastavite privzeti časovni pas. + +```php +$this->configurator->setTimeZone('Europe/Prague'); +``` + + +Konfiguracija DI vsebnika +========================= + +Del zagonskega procesa je ustvarjanje DI vsebnika ali tovarne objektov, kar je srce celotne aplikacije. Gre pravzaprav za PHP razred, ki ga Nette generira in shrani v mapo s predpomnilnikom. Tovarna izdeluje ključne objekte aplikacije in s pomočjo konfiguracijskih datotek ji naročamo, kako naj jih ustvarja in nastavlja, s čimer vplivamo na obnašanje celotne aplikacije. + +Konfiguracijske datoteke se običajno zapisujejo v formatu [NEON |neon:format]. V ločenem poglavju boste izvedeli, [kaj vse je mogoče konfigurirati |nette:configuring]. + +.[tip] +V razvojnem načinu se vsebnik samodejno posodablja ob vsaki spremembi kode ali konfiguracijskih datotek. V produkcijskem načinu se generira samo enkrat in spremembe se zaradi maksimizacije zmogljivosti ne preverjajo. + +Konfiguracijske datoteke naložimo s pomočjo `addConfig()`: + +```php +$this->configurator->addConfig($this->rootDir . '/config/common.neon'); +``` + +Če želimo dodati več konfiguracijskih datotek, lahko funkcijo `addConfig()` pokličemo večkrat. + +```php +$configDir = $this->rootDir . '/config'; +$this->configurator->addConfig($configDir . '/common.neon'); +$this->configurator->addConfig($configDir . '/services.neon'); +if (PHP_SAPI === 'cli') { + $this->configurator->addConfig($configDir . '/cli.php'); +} +``` + +Ime `cli.php` ni napaka, konfiguracija je lahko zapisana tudi v PHP datoteki, ki jo vrne kot polje. + +Prav tako lahko dodamo druge konfiguracijske datoteke v [odsek `includes` |dependency-injection:configuration#Vključevanje datotek]. + +Če se v konfiguracijskih datotekah pojavijo elementi z enakimi ključi, bodo prepisani ali v primeru [polj združeni |dependency-injection:configuration#Združevanje]. Kasneje vključena datoteka ima višjo prioriteto kot prejšnja. Datoteka, v kateri je naveden odsek `includes`, ima višjo prioriteto kot v njej vključene datoteke. + + +Statični parametri +------------------ + +Parametre, uporabljene v konfiguracijskih datotekah, lahko definiramo [v odseku `parameters` |dependency-injection:configuration#Parametri] in jih tudi posredujemo (ali prepišemo) z metodo `addStaticParameters()` (ima alias `addParameters()`). Pomembno je, da različne vrednosti parametrov povzročijo generiranje dodatnih DI vsebnikov, torej dodatnih razredov. + +```php +$this->configurator->addStaticParameters([ + 'projectId' => 23, +]); +``` + +Na parameter `projectId` se lahko v konfiguraciji sklicujemo z običajnim zapisom `%projectId%`. + + +Dinamični parametri +------------------- + +V vsebnik lahko dodamo tudi dinamične parametre, katerih različne vrednosti za razliko od statičnih parametrov ne povzročijo generiranja novih DI vsebnikov. + +```php +$this->configurator->addDynamicParameters([ + 'remoteIp' => $_SERVER['REMOTE_ADDR'], +]); +``` + +Preprosto lahko tako dodamo npr. okoljske spremenljivke, na katere se nato lahko v konfiguraciji sklicujemo z zapisom `%env.variable%`. + +```php +$this->configurator->addDynamicParameters([ + 'env' => getenv(), +]); +``` + + +Privzeti parametri +------------------ + +V konfiguracijskih datotekah lahko uporabite te statične parametre: + +- `%appDir%` je absolutna pot do mape z datoteko `Bootstrap.php` +- `%wwwDir%` je absolutna pot do mape z vhodno datoteko `index.php` +- `%tempDir%` je absolutna pot do mape za začasne datoteke +- `%vendorDir%` je absolutna pot do mape, kamor Composer namešča knjižnice +- `%rootDir%` je absolutna pot do korenskega direktorija projekta +- `%debugMode%` označuje, ali je aplikacija v načinu za razhroščevanje +- `%consoleMode%` označuje, ali je zahtevek prišel prek ukazne vrstice + + +Uvožene storitve +---------------- + +Zdaj gremo globlje. Čeprav je smisel DI vsebnika izdelovati objekte, lahko izjemoma nastane potreba, da v vsebnik vstavimo obstoječi objekt. To storimo tako, da storitev definiramo z zastavico `imported: true`. + +```neon +services: + myservice: + type: App\Model\MyCustomService + imported: true +``` + +In v bootstrapu v vsebnik vstavimo objekt: + +```php +$this->configurator->addServices([ + 'myservice' => new App\Model\MyCustomService('foobar'), +]); +``` + + +Različna okolja +=============== + +Ne bojte se prilagoditi razreda Bootstrap svojim potrebam. Metodi `bootWebApplication()` lahko dodate parametre za razlikovanje spletnih projektov. Ali pa lahko dopolnimo druge metode, na primer `bootTestEnvironment()`, ki inicializira okolje za enotne teste, `bootConsoleApplication()` za skripte, klicane iz ukazne vrstice itd. + +```php +public function bootTestEnvironment(): Nette\DI\Container +{ + Tester\Environment::setup(); // inicializacija Nette Testerja + $this->setupContainer(); + return $this->configurator->createContainer(); +} + +public function bootConsoleApplication(): Nette\DI\Container +{ + $this->configurator->setDebugMode(false); + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); +} +``` diff --git a/application/sl/components.texy b/application/sl/components.texy index 91bb598e94..71001e3566 100644 --- a/application/sl/components.texy +++ b/application/sl/components.texy @@ -3,27 +3,27 @@ Interaktivne komponente
    -Komponente so ločeni predmeti za večkratno uporabo, ki jih namestimo na strani. To so lahko obrazci, podatkovne mreže, ankete, pravzaprav vse, kar je smiselno uporabljati večkrat. Prikazali bomo: +Komponente so samostojni ponovno uporabni objekti, ki jih vstavljamo v strani. Lahko so obrazci, podatkovne mreže, ankete, pravzaprav karkoli, kar ima smisel uporabljati večkrat. Pokazali si bomo: - kako uporabljati komponente? -- kako jih napisati? +- kako jih pisati? - kaj so signali?
    -Nette ima vgrajen sistem komponent. Starejši med vami se morda spomnite nečesa podobnega iz Delphija ali ASP.NET Web Forms. React ali Vue.js sta zgrajena na nečem zelo podobnem. Vendar pa je v svetu ogrodij PHP to povsem edinstvena lastnost. +Nette ima vgrajen komponentni sistem. Nekaj podobnega se lahko spomnijo veterani iz Delphi ali ASP.NET Web Forms, na nečem oddaljeno podobnem temeljita React ali Vue.js. Vendar pa je v svetu PHP ogrodij to edinstvena zadeva. -Hkrati pa komponente temeljito spremenijo pristop k razvoju aplikacij. Strani lahko sestavite iz vnaprej pripravljenih enot. Ali v administraciji potrebujete podatkovno mrežo? Najdete jo lahko v [Componette |https://componette.org/search/component], skladišču odprtokodnih dodatkov (ne le komponent) za Nette, in jo preprosto prilepite v predstavnik. +Pri tem komponente bistveno vplivajo na pristop k ustvarjanju aplikacij. Strani lahko namreč sestavljate iz vnaprej pripravljenih enot. Potrebujete v administraciji podatkovno mrežo? Najdete jo na [Componette |https://componette.org/search/component], repozitoriju odprtokodnih dodatkov (torej ne samo komponent) za Nette in jo preprosto vstavite v presenter. -V predstavitveni program lahko vključite poljubno število komponent. V nekatere komponente pa lahko vstavite druge komponente. Tako nastane drevo komponent, katerega koren je predstavnik. +V presenter lahko vključite poljubno število komponent. In v nekatere komponente lahko vstavljate druge komponente. Tako nastane komponentno drevo, katerega koren je presenter. -Tovarniške metode .[#toc-factory-methods] -========================================= +Tovarniške metode +================= -Kako se komponente namestijo in nato uporabijo v predstavitvenem programu? Običajno z uporabo tovarniških metod. +Kako se komponente vstavljajo v presenter in nato uporabljajo? Običajno s pomočjo tovarniških metod. -Tovarna komponent je eleganten način za ustvarjanje komponent samo takrat, ko jih resnično potrebujemo (lenoba / na zahtevo). Celotna čarovnija je v izvajanju metode, imenovane `createComponent()`, kjer `` je ime komponente, ki se ustvari in vrne. +Tovarna komponent predstavlja eleganten način, kako komponente ustvarjati šele takrat, ko so dejansko potrebne (lazy / on demand). Celotna čarovnija temelji na implementaciji metode z imenom `createComponent()`, kjer je `` ime ustvarjene komponente, in ki komponento ustvari ter vrne. ```php .{file:DefaultPresenter.php} class DefaultPresenter extends Nette\Application\UI\Presenter @@ -37,43 +37,43 @@ class DefaultPresenter extends Nette\Application\UI\Presenter } ``` -Ker so vse komponente ustvarjene v ločenih metodah, je koda čistejša in lažje berljiva. +Zahvaljujoč temu, da so vse komponente ustvarjene v ločenih metodah, koda pridobi na preglednosti. .[note] -Imena komponent se vedno začnejo z malo črko, čeprav so v imenu metode zapisana z veliko začetnico. +Imena komponent se vedno začnejo z malo začetnico, čeprav se v imenu metode pišejo z veliko. -Nikoli ne kličemo tovarn neposredno, pokličejo se samodejno, ko prvič uporabimo komponente. Zaradi tega se komponenta ustvari v pravem trenutku in le, če jo resnično potrebujemo. Če komponente ne bi uporabili (na primer pri kakšni zahtevi AJAX, kjer vrnemo le del strani, ali ko so deli v predpomnilniku), se sploh ne bi ustvarila in prihranili bi zmogljivost strežnika. +Tovarn nikoli ne kličemo neposredno, pokličejo se same takrat, ko komponento prvič uporabimo. Zahvaljujoč temu je komponenta ustvarjena v pravem trenutku in samo v primeru, ko je dejansko potrebna. Če komponente ne uporabimo (na primer pri AJAX zahtevku, ko se prenaša samo del strani, ali pri predpomnjenju predloge), se sploh ne ustvari in prihranimo zmogljivost strežnika. ```php .{file:DefaultPresenter.php} -// dostopamo do komponente in ali je bilo to prvič, -// pokliče createComponentPoll(), da jo ustvari +// dostopimo do komponente in če je bilo to prvič, +// se pokliče createComponentPoll(), ki jo ustvari $poll = $this->getComponent('poll'); // alternativna sintaksa: $poll = $this['poll']; ``` -V predlogi lahko komponento prikažete z uporabo oznake [{control} |#Rendering]. Tako ni potrebe po ročnem posredovanju komponent predlogi. +V predlogi je mogoče izrisati komponento s pomočjo značke [{control} |#Izrisovanje]. Zato ni potrebno ročno posredovati komponent v predlogo. ```latte -

    Please Vote

    +

    Glasujte

    {control poll} ``` -Hollywoodski slog .[#toc-hollywood-style] -========================================= +Hollywood style +=============== -Sestavni deli pogosto uporabljajo kul tehniko, ki jo radi imenujemo hollywoodski slog. Zagotovo poznate kliše, ki ga igralci pogosto slišijo na kastingih: "Ne kličite nas, mi bomo poklicali vas." In prav za to gre v tem primeru. +Komponente običajno uporabljajo eno svežo tehniko, ki ji radi rečemo Hollywood style. Zagotovo poznate krilatico, ki jo tako pogosto slišijo udeleženci filmskih avdicij: "Ne kličite nas, mi bomo poklicali vas." In prav za to gre. -V Nette, namesto da bi nenehno postavljali vprašanja ("je bil obrazec oddan?", "je bil veljaven?" ali "je kdo pritisnil ta gumb?"), ogrodju poveste "ko se to zgodi, pokliči to metodo" in mu prepustite nadaljnje delo. Če programirate v jeziku JavaScript, ste seznanjeni s tem slogom programiranja. Napišete funkcije, ki se pokličejo, ko se zgodi določen dogodek. In gonilo jim posreduje ustrezne parametre. +V Nette namreč namesto tega, da bi se morali nenehno spraševati ("je bil obrazec poslan?", "je bil veljaven?" ali "je uporabnik pritisnil ta gumb?"), poveste ogrodju "ko se to zgodi, pokliči to metodo" in nadaljnje delo prepustite njemu. Če programirate v JavaScriptu, ta slog programiranja dobro poznate. Pišete funkcije, ki se kličejo, ko nastopi določen dogodek. In jezik jim posreduje ustrezne parametre. -To popolnoma spremeni način pisanja aplikacij. Več nalog, kot jih lahko prenesete na ogrodje, manj dela imate. In manj lahko pozabite. +To popolnoma spremeni pogled na pisanje aplikacij. Več nalog kot lahko prepustite ogrodju, manj dela imate vi. In manj stvari lahko na primer pozabite. -Kako napisati komponento .[#toc-how-to-write-a-component] -========================================================= +Pišemo komponento +================= -S komponento običajno mislimo na potomce razreda [api:Nette\Application\UI\Control]. Tudi sam predstavnik [api:Nette\Application\UI\Presenter] je potomec razreda `Control`. +Pod pojmom komponenta običajno mislimo na potomca razreda [api:Nette\Application\UI\Control]. (Natančneje bi bilo torej uporabljati izraz "controls", vendar "kontrole" imajo v slovenščini popolnoma drugačen pomen in se je bolj uveljavil izraz "komponente".) Sam presenter [api:Nette\Application\UI\Presenter] je mimogrede tudi potomec razreda `Control`. ```php .{file:PollControl.php} use Nette\Application\UI\Control; @@ -84,22 +84,22 @@ class PollControl extends Control ``` -Prikazovanje .[#toc-rendering] -============================== +Izrisovanje +=========== -Vemo že, da se oznaka `{control componentName}` uporablja za risanje komponente. Pravzaprav kliče metodo `render()` komponente, v kateri poskrbimo za upodabljanje. Podobno kot v predstavitvenem programu imamo tudi tu v spremenljivki `$this->template` predlogo [Latte |latte:], ki ji posredujemo parametre. Za razliko od uporabe v predstavitvenem programu moramo določiti datoteko predloge in ji omogočiti, da se izrisuje: +Že vemo, da se za izris komponente uporablja značka `{control componentName}`. Ta pravzaprav pokliče metodo `render()` komponente, v kateri poskrbimo za izris. Na voljo imamo, popolnoma enako kot v presenterju, [Latte predlogo|templates] v spremenljivki `$this->template`, v katero posredujemo parametre. Za razliko od presenterja moramo navesti datoteko s predlogo in jo pustiti izrisati: ```php .{file:PollControl.php} public function render(): void { - // v predlogo bomo vnesli nekaj parametrov + // vstavimo v predlogo nekaj parametrov $this->template->param = $value; - // in jo narisali + // in jo izrišemo $this->template->render(__DIR__ . '/poll.latte'); } ``` -Z oznako `{control}` lahko metodi `render()` posredujemo parametre: +Značka `{control}` omogoča posredovanje parametrov v metodo `render()`: ```latte {control poll $id, $message} @@ -112,7 +112,7 @@ public function render(int $id, string $message): void } ``` -Včasih je lahko komponenta sestavljena iz več delov, ki jih želimo prikazati ločeno. Za vsakega od njih bomo ustvarili svojo metodo upodabljanja, tu je na primer `renderPaginator()`: +Včasih se lahko komponenta sestoji iz več delov, ki jih želimo izrisovati ločeno. Za vsakega od njih si ustvarimo lastno metodo za izris, tukaj v primeru na primer `renderPaginator()`: ```php .{file:PollControl.php} public function renderPaginator(): void @@ -121,107 +121,107 @@ public function renderPaginator(): void } ``` -V predlogi jo nato pokličemo z uporabo: +In v predlogi jo nato pokličemo s pomočjo: ```latte {control poll:paginator} ``` -Za boljše razumevanje je dobro vedeti, kako je oznaka sestavljena v kodo PHP. +Za boljše razumevanje je dobro vedeti, kako se ta značka prevede v PHP. ```latte {control poll} {control poll:paginator 123, 'hello'} ``` -To se sestavi v: +se prevede kot: ```php $control->getComponent('poll')->render(); $control->getComponent('poll')->renderPaginator(123, 'hello'); ``` -`getComponent()` metoda vrne komponento `poll`, nato pa se na njej kliče metoda `render()` oziroma `renderPaginator()`. +Metoda `getComponent()` vrne komponento `poll` in nad to komponento kliče metodo `render()`, oz. `renderPaginator()`, če je drugačen način izrisovanja naveden v znački za dvopičjem. .[caution] -Če se kjer koli v delu parametrov uporabi **`=>`**, se vsi parametri zavijejo v polje in posredujejo kot prvi argument: +Pozor, če se kjerkoli v parametrih pojavi **`=>`**, bodo vsi parametri zapakirani v polje in posredovani kot prvi argument: ```latte {control poll, id: 123, message: 'hello'} ``` -se pretvori v: +se prevede kot: ```php $control->getComponent('poll')->render(['id' => 123, 'message' => 'hello']); ``` - +Izris podkomponente: ```latte {control cartControl-someForm} ``` -sestavi na: +se prevede kot: ```php $control->getComponent("cartControl-someForm")->render(); ``` -Komponente, kot so predstavniki, predlogam samodejno posredujejo več uporabnih spremenljivk: +Komponente, enako kot presenterji, samodejno posredujejo v predloge nekaj uporabnih spremenljivk: -- `$basePath` je absolutna pot URL do korenskega dirja (na primer `/CD-collection`) -- `$baseUrl` je absolutna pot URL do korenskega dirja (na primer `http://localhost/CD-collection`) -- `$user` je objekt, ki [predstavlja uporabnika |security:authentication] -- `$presenter` je trenutni predstavnik +- `$basePath` je absolutna URL pot do korenskega direktorija (npr. `/eshop`) +- `$baseUrl` je absolutni URL do korenskega direktorija (npr. `http://localhost/eshop`) +- `$user` je objekt [ki predstavlja uporabnika |security:authentication] +- `$presenter` je trenutni presenter - `$control` je trenutna komponenta -- `$flashes` seznam [sporočil, |#flash-messages] poslanih z metodo `flashMessage()` +- `$flashes` polje [sporočil |#Flash sporočila] poslanih s funkcijo `flashMessage()` -Signal .[#toc-signal] -===================== +Signal +====== -Vemo že, da je navigacija v aplikaciji Nette sestavljena iz povezovanja ali preusmerjanja na pare `Presenter:action`. Kaj pa, če želimo izvesti dejanje samo na **tekoči strani**? Na primer, spremeniti vrstni red razvrščanja stolpca v tabeli; izbrisati element; preklopiti svetlobni/temni način; oddati obrazec; glasovati v anketi; itd. +Že vemo, da navigacija v Nette aplikaciji temelji na povezovanju ali preusmerjanju na pare `Presenter:action`. Kaj pa, če želimo samo izvesti akcijo na **trenutni strani**? Na primer spremeniti razvrščanje stolpcev v tabeli; izbrisati element; preklopiti svetel/temen način; poslati obrazec; glasovati v anketi; itd. -Tovrstna zahteva se imenuje signal. In tako kot akcije kličejo metode `action()` ali `render()`, signali kličejo metode `handle()`. Medtem ko se koncept akcije (ali pogleda) nanaša samo na predstavnike, signali veljajo za vse komponente. Torej tudi za predstavnike, saj je `UI\Presenter` potomec `UI\Control`. +Tej vrsti zahtevkov rečemo signali. In podobno kot akcije sprožijo metode `action()` ali `render()`, signali kličejo metode `handle()`. Medtem ko je pojem akcije (ali view) povezan izključno s presenterji, se signali nanašajo na vse komponente. In torej tudi na presenterje, ker je `UI\Presenter` potomec `UI\Control`. ```php public function handleClick(int $x, int $y): void { - // ... obdelava signalov ... + // ... obdelava signala ... } ``` -Povezava, ki kliče signal, se ustvari na običajen način, tj. v predlogi z atributom `n:href` ali oznako `{link}`, v kodi pa z metodo `link()`. Več v poglavju [Ustvarjanje povezav URL |creating-links#Links to Signal]. +Povezavo, ki pokliče signal, ustvarimo na običajen način, torej v predlogi z atributom `n:href` ali značko `{link}`, v kodi z metodo `link()`. Več v poglavju [Ustvarjanje URL povezav |creating-links#Povezave na signal]. ```latte -click here +kliknite tukaj ``` -Signal se vedno pokliče v trenutnem predstavniku in pogledu, zato povezave do signala ni mogoče vzpostaviti v drugem predstavniku/ukrepu. +Signal se vedno kliče na trenutnem presenterju in akciji, ni ga mogoče poklicati na drugem presenterju ali drugi akciji. -Tako signal povzroči ponovno nalaganje strani na popolnoma enak način kot v prvotni zahtevi, le da dodatno pokliče metodo za obdelavo signala z ustreznimi parametri. Če metoda ne obstaja, se vrže izjema [api:Nette\Application\UI\BadSignalException], ki se uporabniku prikaže kot stran z napako 403 Forbidden. +Signal torej povzroči ponovno nalaganje strani popolnoma enako kot pri prvotnem zahtevku, le da dodatno pokliče obdelovalno metodo signala z ustreznimi parametri. Če metoda ne obstaja, se sproži izjema [api:Nette\Application\UI\BadSignalException], ki se uporabniku prikaže kot stran z napako 403 Forbidden. -Utrinki in AJAX .[#toc-snippets-and-ajax] -========================================= +Odrezki in AJAX +=============== -Signali vas morda malce spominjajo na AJAX: obdelave, ki se kličejo na trenutni strani. In prav imate, signale res pogosto kličemo z uporabo AJAX-a, nato pa brskalniku posredujemo le spremenjene dele strani. Imenujejo se odlomki (snippets). Več informacij lahko najdete na [strani o AJAXu |ajax]. +Signali vas morda nekoliko spominjajo na AJAX: obdelovalci, ki se kličejo na trenutni strani. In imate prav, signali se res pogosto kličejo s pomočjo AJAX-a in nato v brskalnik prenesemo samo spremenjene dele strani. Ali t.i. odrezke. Več informacij najdete na [strani, namenjeni AJAX-u |ajax]. -Sporočila Flash .[#toc-flash-messages] -====================================== +Flash sporočila +=============== -Komponenta ima lastno shrambo sporočil flash, ki je neodvisna od predstavnika. To so sporočila, ki na primer obveščajo o rezultatu operacije. Pomembna lastnost bliskovnih sporočil je, da so na voljo v predlogi tudi po preusmeritvi. Tudi po prikazu bodo ostala živa še 30 sekund - na primer, če bi uporabnik nenamerno osvežil stran - sporočilo se ne bo izgubilo. +Komponenta ima svoje lastno shrambo flash sporočil, neodvisno od presenterja. Gre za sporočila, ki na primer obveščajo o rezultatu operacije. Pomembna značilnost flash sporočil je, da so v predlogi na voljo tudi po preusmeritvi. Tudi po prikazu ostanejo živa še nadaljnjih 30 sekund – na primer za primer, če bi zaradi napačnega prenosa uporabnik osvežil stran - sporočilo mu torej ne izgine takoj. -Pošiljanje se izvede z metodo [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Prvi parameter je besedilo sporočila ali objekt `stdClass`, ki predstavlja sporočilo. Neobvezni drugi parameter je njegova vrsta (napaka, opozorilo, informacija itd.). Metoda `flashMessage()` vrne primerek sporočila flash kot objekt stdClass, ki mu lahko posredujete informacije. +Pošiljanje zagotavlja metoda [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Prvi parameter je besedilo sporočila ali objekt `stdClass`, ki predstavlja sporočilo. Neobvezni drugi parameter je njegov tip (error, warning, info ipd.). Metoda `flashMessage()` vrne instanco flash sporočila kot objekt `stdClass`, kateremu je mogoče dodajati dodatne informacije. ```php -$this->flashMessage('Item was deleted.'); -$this->redirect(/* ... */); // in preusmerite +$this->flashMessage('Element je bil izbrisan.'); +$this->redirect(/* ... */); // in preusmerimo ``` -V predlogi so ta sporočila na voljo v spremenljivki `$flashes` kot objekti `stdClass`, ki vsebujejo lastnosti `message` (besedilo sporočila), `type` (vrsta sporočila) in lahko vsebujejo že omenjene podatke o uporabniku. Narišemo jih na naslednji način: +Predlogi so ta sporočila na voljo v spremenljivki `$flashes` kot objekti `stdClass`, ki vsebujejo lastnosti `message` (besedilo sporočila), `type` (tip sporočila) in lahko vsebujejo že omenjene uporabniške informacije. Izrišemo jih na primer takole: ```latte {foreach $flashes as $flash} @@ -230,44 +230,66 @@ V predlogi so ta sporočila na voljo v spremenljivki `$flashes` kot objekti `std ``` -Trajni parametri .[#toc-persistent-parameters] -============================================== +Preusmeritev po signalu +======================= + +Po obdelavi signala komponente pogosto sledi preusmeritev. To je podobna situacija kot pri obrazcih - po njihovem pošiljanju prav tako preusmerjamo, da ob osvežitvi strani v brskalniku ne pride do ponovnega pošiljanja podatkov. + +```php +$this->redirect('this'); // preusmeri na trenutni presenter in akcijo +``` + +Ker je komponenta ponovno uporaben element in običajno ne bi smela imeti neposredne povezave s konkretnimi presenterji, metodi `redirect()` in `link()` samodejno interpretirata parameter kot signal komponente: + +```php +$this->redirect('click'); // preusmeri na signal 'click' iste komponente +``` + +Če potrebujete preusmeriti na drug presenter ali akcijo, lahko to storite prek presenterja: + +```php +$this->getPresenter()->redirect('Product:show'); // preusmeri na drug presenter/akcijo +``` + + +Persistentni parametri +====================== -Trajni parametri se uporabljajo za ohranjanje stanja v komponentah med različnimi zahtevami. Njihova vrednost ostane enaka tudi po kliku na povezavo. Za razliko od podatkov seje se prenesejo v naslovu URL. In se prenesejo samodejno, vključno s povezavami, ustvarjenimi v drugih komponentah na isti strani. +Persistentni parametri služijo za ohranjanje stanja v komponentah med različnimi zahtevki. Njihova vrednost ostane enaka tudi po kliku na povezavo. Za razliko od podatkov v seji se prenašajo v URL-ju. In to popolnoma samodejno, vključno s povezavami, ustvarjenimi v drugih komponentah na isti strani. -Na primer, imate komponento za listanje vsebine. Na strani je lahko več takšnih komponent. Želite, da vse komponente ostanejo na trenutni strani, ko kliknete povezavo. Zato je številka strani (`page`) trajni parameter. +Imate na primer komponento za paginacijo vsebine. Takšnih komponent je lahko na strani več. In želimo si, da po kliku na povezavo ostanejo vse komponente na svoji trenutni strani. Zato iz številke strani (`page`) naredimo persistentni parameter. -Ustvarjanje trajnega parametra je v programu Nette zelo enostavno. Ustvarite le javno lastnost in jo označite z atributom: (prej je bila uporabljena `/** @persistent */` ) +Ustvarjanje persistentnega parametra je v Nette izjemno enostavno. Dovolj je ustvariti javno lastnost in jo označiti z atributom: (prej se je uporabljalo `/** @persistent */`) ```php -use Nette\Application\Attributes\Persistent; // ta vrstica je pomembna +use Nette\Application\Attributes\Persistent; // ta vrstica je pomembna class PaginatingControl extends Control { #[Persistent] - public int $page = 1; // morajo biti javni. + public int $page = 1; // mora biti public } ``` -Priporočamo, da pri lastnosti navedete podatkovno vrsto (npr. `int`), vključite pa lahko tudi privzeto vrednost. Vrednosti parametrov je mogoče [potrditi |#Validation of Persistent Parameters]. +Pri lastnosti priporočamo navedbo tudi podatkovnega tipa (npr. `int`) in lahko navedete tudi privzeto vrednost. Vrednosti parametrov je mogoče [validirati |#Validacija persistentnih parametrov]. -Pri ustvarjanju povezave lahko spremenite vrednost trajnega parametra: +Pri ustvarjanju povezave lahko persistentnemu parametru spremenite vrednost: ```latte -next +naslednja ``` -Lahko ga tudi *resetirate*, tj. odstranite iz URL-ja. Nato bo prevzel privzeto vrednost: +Ali pa ga lahko *ponastavite*, tj. odstranite iz URL-ja. Potem bo prevzel svojo privzeto vrednost: ```latte -reset +ponastavi ``` -Trajne komponente .[#toc-persistent-components] -=============================================== +Persistentne komponente +======================= -Ne le parametri, tudi komponente so lahko trajne. Njihovi trajni parametri se prenašajo tudi med različnimi akcijami ali med različnimi predstavniki. Trajne komponente označimo s temi opombami za razred presenter. Na primer, tukaj komponente `calendar` in `poll` označimo na naslednji način: +Ne samo parametri, tudi komponente so lahko persistentne. Pri takšni komponenti se njeni persistentni parametri prenašajo tudi med različnimi akcijami presenterja ali med več presenterji. Persistentne komponente označimo z anotacijo pri razredu presenterja. Na primer, tako označimo komponente `calendar` in `poll`: ```php /** @@ -278,9 +300,9 @@ class DefaultPresenter extends Nette\Application\UI\Presenter } ``` -Podsestav ni treba označiti kot trajne, saj so trajne samodejno. +Podkomponent znotraj teh komponent ni treba označevati, postale bodo persistentne tudi one. -V PHP 8 lahko za označevanje trajnih komponent uporabite tudi atribute: +V PHP 8 lahko za označevanje persistentnih komponent uporabite tudi atribute: ```php use Nette\Application\Attributes\Persistent; @@ -292,35 +314,35 @@ class DefaultPresenter extends Nette\Application\UI\Presenter ``` -Komponente z odvisnostmi .[#toc-components-with-dependencies] -============================================================= +Komponente z odvisnostmi +======================== -Kako ustvariti komponente z odvisnostmi, ne da bi "zmotili" predstavnike, ki jih bodo uporabljali? Zahvaljujoč pametnim funkcijam vsebnika DI v Nette, lahko tako kot pri uporabi tradicionalnih storitev večino dela prepustimo ogrodju. +Kako ustvarjati komponente z odvisnostmi, ne da bi si "onesnažili" presenterje, ki jih bodo uporabljali? Zahvaljujoč pametnim lastnostim DI vsebnika v Nette lahko, enako kot pri uporabi klasičnih storitev, večino dela prepustimo ogrodju. -Kot primer vzemimo komponento, ki je odvisna od storitve `PollFacade`: +Vzemimo za primer komponento, ki ima odvisnost od storitve `PollFacade`: ```php class PollControl extends Control { public function __construct( - private int $id, // Id ankete, za katero je ustvarjena komponenta + private int $id, // Id ankete, za katero ustvarjamo komponento private PollFacade $facade, ) { } public function handleVote(int $voteId): void { - $this->facade->vote($id, $voteId); + $this->facade->vote($this->id, $voteId); // ... } } ``` -Če bi pisali klasično storitev, nam ne bi bilo treba skrbeti. Kontejner DI bi nevidno poskrbel za posredovanje vseh odvisnosti. Vendar pa komponente običajno obravnavamo tako, da ustvarimo njihov nov primerek neposredno v predstavniku v [tovarniških metodah |#factory methods] `createComponent...()`. Toda posredovanje vseh odvisnosti vseh komponent predstavniku, da bi jih nato posredoval komponentam, je okorno. In količina kode, ki je napisana... +Če bi pisali klasično storitev, ne bi bilo kaj reševati. Za posredovanje vseh odvisnosti bi nevidno poskrbel DI vsebnik. Vendar pa s komponentami običajno ravnamo tako, da njihovo novo instanco ustvarjamo neposredno v presenterju v [tovarniških metodah |#Tovarniške metode] `createComponent…()`. Toda posredovanje vseh odvisnosti vseh komponent v presenter, da bi jih nato posredovali komponentam, je okorno. In toliko napisane kode… -Logično vprašanje je, zakaj ne bi komponente preprosto registrirali kot klasično storitev, jo posredovali predstavniku in jo nato vrnili v metodi `createComponent...()`? Toda ta pristop je neprimeren, saj želimo imeti možnost, da komponento ustvarimo večkrat. +Logično vprašanje je, zakaj preprosto ne registriramo komponente kot klasične storitve, je ne posredujemo v presenter in nato v metodi `createComponent…()` ne vračamo? Takšen pristop pa je neprimeren, ker želimo imeti možnost komponento ustvariti tudi večkrat. -Pravilna rešitev je, da za komponento napišemo tovarno, tj. razred, ki za nas ustvari komponento: +Pravilna rešitev je napisati za komponento tovarno, torej razred, ki nam bo komponento ustvaril: ```php class PollControlFactory @@ -337,17 +359,17 @@ class PollControlFactory } ``` -Zdaj našo storitev registriramo v vsebniku DI za konfiguracijo: +Tako tovarno registriramo v naš vsebnik v konfiguraciji: ```neon services: - PollControlFactory ``` -Na koncu bomo to tovarno uporabili v našem predstavitvenem programu: +in na koncu jo uporabimo v našem presenterju: ```php -class PollPresenter extends Nette\UI\Application\Presenter +class PollPresenter extends Nette\Application\UI\Presenter { public function __construct( private PollControlFactory $pollControlFactory, @@ -362,7 +384,7 @@ class PollPresenter extends Nette\UI\Application\Presenter } ``` -Odlično je, da lahko Nette DI [ustvari |dependency-injection:factory] tako preproste tovarne, tako da morate namesto celotne kode napisati le njen vmesnik: +Odlično je, da Nette DI takšne preproste tovarne zna [generirati |dependency-injection:factory], tako da namesto njene celotne kode zadostuje napisati samo njen vmesnik: ```php interface PollControlFactory @@ -371,21 +393,21 @@ interface PollControlFactory } ``` -To je vse. Nette interno implementira ta vmesnik in ga vbrizga v naš predstavnik, kjer ga lahko uporabimo. Prav tako čudežno posreduje naš parameter `$id` in primerek razreda `PollFacade` v našo komponento. +In to je vse. Nette notranje ta vmesnik implementira in ga posreduje v presenter, kjer ga že lahko uporabljamo. Čarobno nam prav v našo komponento doda tudi parameter `$id` in instanco razreda `PollFacade`. -Komponente poglobljeno .[#toc-components-in-depth] -================================================== +Komponente v globino +==================== -Komponente v aplikaciji Nette so ponovno uporabni deli spletne aplikacije, ki jih vgrajujemo v strani, kar je predmet tega poglavja. Kakšne natančno so zmožnosti takšne komponente? +Komponente v Nette Application predstavljajo ponovno uporabne dele spletne aplikacije, ki jih vstavljamo v strani in katerim je posvečeno celotno to poglavje. Kakšne natančno sposobnosti ima takšna komponenta? -1) mogoče jo je izrisati v predlogi -2) ve, kateri del sebe naj prikaže med [zahtevo AJAX |ajax#invalidation] (odlomki) -3) ima možnost shranjevanja svojega stanja v naslovu URL (trajni parametri). -4) ima sposobnost odzivanja na dejanja uporabnika (signali) -5) ustvarja hierarhično strukturo (kjer je korenski element predstavnik) +1) je izrisljiva v predlogi +2) ve, [kateri svoj del |ajax#Odrezki] mora izrisati pri AJAX zahtevku (odrezki) +3) ima sposobnost shranjevanja svojega stanja v URL (persistentni parametri) +4) ima sposobnost odzivanja na uporabniške akcije (signali) +5) ustvarja hierarhično strukturo (kjer je koren presenter) -Za vsako od teh funkcij skrbi eden od razredov dedne linije. Za upodabljanje (1 + 2) skrbi razred [api:Nette\Application\UI\Control], za vključitev v [življenjski cikel |presenters#life-cycle-of-presenter] (3, 4) razred [api:Nette\Application\UI\Component], za ustvarjanje hierarhične strukture (5) pa razreda [Container in Component |component-model:]. +Vsako od teh funkcij zagotavlja kateri od razredov dedne linije. Za izrisovanje (1 + 2) skrbi [api:Nette\Application\UI\Control], za vključitev v [življenjski cikel |presenters#Življenjski cikel presenterja] (3, 4) razred [api:Nette\Application\UI\Component] in za ustvarjanje hierarhične strukture (5) razreda [Container in Component |component-model:]. ``` Nette\ComponentModel\Component { IComponent } @@ -400,18 +422,18 @@ Nette\ComponentModel\Component { IComponent } ``` -Življenjski cikel komponente .[#toc-life-cycle-of-component] ------------------------------------------------------------- +Življenjski cikel komponente +---------------------------- [* lifecycle-component.svg *] *** *Življenjski cikel komponente* .<> -Potrjevanje trajnih parametrov .[#toc-validation-of-persistent-parameters] --------------------------------------------------------------------------- +Validacija persistentnih parametrov +----------------------------------- -Vrednosti [trajnih parametrov |#persistent parameters], prejetih z naslovov URL, se zapišejo v lastnosti z metodo `loadState()`. Preveri tudi, ali se podatkovna vrsta, določena za lastnost, ujema, sicer se odzove z napako 404 in stran se ne prikaže. +Vrednosti [persistentnih parametrov |#Persistentni parametri], prejetih iz URL-ja, zapisuje v lastnosti metoda `loadState()`. Ta tudi preverja, ali ustreza podatkovni tip, naveden pri lastnosti, sicer odgovori z napako 404 in stran se ne prikaže. -Nikoli ne zaupajte slepo trajnim parametrom, saj jih lahko uporabnik zlahka prepiše v naslovu URL. Tako na primer preverimo, ali je številka strani `$this->page` večja od 0. Dober način za to je, da prekrijemo zgoraj omenjeno metodo `loadState()`: +Nikoli slepo ne verjemite persistentnim parametrom, ker jih lahko uporabnik enostavno prepiše v URL-ju. Tako na primer preverimo, ali je številka strani `$this->page` večja od 0. Primerna pot je prepisati omenjeno metodo `loadState()`: ```php class PaginatingControl extends Control @@ -421,8 +443,8 @@ class PaginatingControl extends Control public function loadState(array $params): void { - parent::loadState($params); // tukaj je nastavljen $this->page - // sledi preverjanju uporabniške vrednosti: + parent::loadState($params); // tukaj se nastavi $this->page + // sledi lastno preverjanje vrednosti: if ($this->page < 1) { $this->error(); } @@ -430,27 +452,27 @@ class PaginatingControl extends Control } ``` -Nasprotni postopek, to je zbiranje vrednosti iz trajnih lastnosti, je obdelan z metodo `saveState()`. +Nasprotni proces, torej zbiranje vrednosti iz persistentnih lastnosti, ima na skrbi metoda `saveState()`. -Signali v globino .[#toc-signals-in-depth] ------------------------------------------- +Signali v globino +----------------- -Signal povzroči ponovno nalaganje strani kot prvotna zahteva (z izjemo AJAX) in kliče metodo `signalReceived($signal)`, katere privzeta implementacija v razredu `Nette\Application\UI\Component` poskuša poklicati metodo, sestavljeno iz besed `handle{Signal}`. Nadaljnja obdelava je odvisna od danega predmeta. Objekti, ki so potomci `Component` (tj. `Control` in `Presenter`), poskušajo poklicati `handle{Signal}` z ustreznimi parametri. +Signal povzroči ponovno nalaganje strani popolnoma enako kot pri prvotnem zahtevku (razen v primeru, ko je klican z AJAX-om) in pokliče metodo `signalReceived($signal)`, katere privzeta implementacija v razredu `Nette\Application\UI\Component` poskuša poklicati metodo, sestavljeno iz besed `handle{signal}`. Nadaljnja obdelava je odvisna od danega objekta. Objekti, ki dedujejo od `Component` (tzn. `Control` in `Presenter`), se odzovejo tako, da poskušajo poklicati metodo `handle{signal}` z ustreznimi parametri. -Z drugimi besedami: vzame se definicija metode `handle{Signal}` in vsi parametri, ki so bili prejeti v zahtevi, se ujemajo s parametri metode. To pomeni, da se parameter `id` iz naslova URL ujema s parametrom metode `$id`, `something` s parametrom `$something` in tako naprej. In če metoda ne obstaja, metoda `signalReceived` vrže [izjemo |api:Nette\Application\UI\BadSignalException]. +Z drugimi besedami: vzame se definicija funkcije `handle{signal}` in vsi parametri, ki so prišli z zahtevkom, ter se argumentom glede na ime dodelijo parametri iz URL-ja in poskuša poklicati dano metodo. Npr. kot parameter `$id` se posreduje vrednost iz parametra `id` v URL-ju, kot `$something` se posreduje `something` iz URL-ja itd. In če metoda ne obstaja, metoda `signalReceived` sproži [izjemo |api:Nette\Application\UI\BadSignalException]. -Signal lahko prejme vsaka komponenta, predstavnik objekta, ki implementira vmesnik `SignalReceiver`, če je povezan z drevesom komponent. +Signal lahko sprejme katerakoli komponenta, presenter ali objekt, ki implementira vmesnik `SignalReceiver` in je priključen v drevo komponent. -Glavni prejemniki signalov so `Presenters` in vizualne komponente, ki razširjajo `Control`. Signal je znak za objekt, da mora nekaj storiti - anketa šteje glas uporabnika, polje z novicami se mora razviti, obrazec je bil poslan in mora obdelati podatke itd. +Med glavne prejemnike signalov bodo spadali `Presenterji` in vizualne komponente, ki dedujejo od `Control`. Signal naj bi služil kot znak za objekt, da mora nekaj narediti – anketa si mora zabeležiti glas od uporabnika, blok z novicami se mora razširiti in prikazati dvakrat toliko novic, obrazec je bil poslan in mora obdelati podatke in podobno. -URL za signal se ustvari z metodo [Component::link() |api:Nette\Application\UI\Component::link()]. Kot parameter `$destination` posredujemo niz `{signal}!`, kot `$args` pa polje argumentov, ki jih želimo posredovati izvajalcu signala. Parametri signala so priključeni na naslov URL trenutnega predstavnika/ogleda. **Parameter `?do` v naslovu URL določa klicani signal.** +URL za signal ustvarimo s pomočjo metode [Component::link() |api:Nette\Application\UI\Component::link()]. Kot parameter `$destination` posredujemo niz `{signal}!` in kot `$args` polje argumentov, ki jih želimo signalu posredovati. Signal se vedno kliče na trenutnem presenterju in akciji s trenutnimi parametri, parametri signala se samo dodajo. Poleg tega se takoj na začetku doda **parameter `?do`, ki določa signal**. -Njegova oblika je `{signal}` ali `{signalReceiver}-{signal}`. `{signalReceiver}` je ime komponente v predstavniku. Zato pomišljaj (nenatančno pomišljaj) ne sme biti prisoten v imenu komponent - uporablja se za razdelitev imena komponente in signala, vendar je mogoče sestaviti več komponent. +Njegov format je bodisi `{signal}` ali `{signalReceiver}-{signal}`. `{signalReceiver}` je ime komponente v presenterju. Zato v imenu komponente ne sme biti vezaja – uporablja se za ločevanje imena komponente in signala, vendar je mogoče tako ugnezditi več komponent. -Metoda [isSignalReceiver() |api:Nette\Application\UI\Presenter::isSignalReceiver()] preveri, ali je komponenta (prvi argument) sprejemnik signala (drugi argument). Drugi argument lahko izpustite - takrat ugotovi, ali je komponenta sprejemnik katerega koli signala. Če je drugi parameter `true`, preveri, ali je komponenta ali njeni potomci sprejemniki signala. +Metoda [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] preveri, ali je komponenta (prvi argument) prejemnik signala (drugi argument). Drugi argument lahko izpustimo – potem ugotavlja, ali je komponenta prejemnik kateregakoli signala. Kot drugi parameter lahko navedemo `true` in s tem preverimo, ali je prejemnik ne samo navedena komponenta, ampak tudi katerikoli njen potomec. -V kateri koli fazi pred `handle{Signal}` se lahko signal izvede ročno s klicem metode [processSignal() |api:Nette\Application\UI\Presenter::processSignal()], ki prevzame odgovornost za izvedbo signala. Vzame komponento prejemnika (če ni nastavljena, je to sam predvajalnik) in ji pošlje signal. +V katerikoli fazi pred `handle{signal}` lahko signal izvedemo ročno s klicem metode [processSignal()|api:Nette\Application\UI\Presenter::processSignal()], ki prevzame skrb za obdelavo signala – vzame komponento, ki se je določila kot prejemnik signala (če ni določen prejemnik signala, je to presenter sam) in ji pošlje signal. Primer: @@ -460,4 +482,4 @@ if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, ' } ``` -Signal se izvede predčasno in ne bo več poklican. +S tem je signal izveden predčasno in se ne bo več ponovno klical. diff --git a/application/sl/configuration.texy b/application/sl/configuration.texy index f28b876a6a..4f92a881d4 100644 --- a/application/sl/configuration.texy +++ b/application/sl/configuration.texy @@ -1,58 +1,71 @@ -Konfiguriranje aplikacije -************************* +Konfiguracija aplikacij +*********************** .[perex] -Pregled možnosti konfiguracije za aplikacijo Nette. +Pregled konfiguracijskih možnosti za Nette Aplikacije. -Aplikacija .[#toc-application] -============================== +Application +=========== ```neon application: - # prikazuje ploščo "Nette Application" v programu Tracy BlueScreen? + # prikazati ploščo "Nette Application" v Tracy BlueScreen? debugger: ... # (bool) privzeto je true - # ali se ob napaki pokliče error-presenter? - catchExceptions: ... # (bool) privzeto true v produkcijskem načinu + # ali se bo ob napaki klical error-presenter? + # učinkuje samo v razvojnem načinu + catchExceptions: ... # (bool) privzeto je true - # ime programa error-presenter - errorPresenter: Error # (string) privzeto 'Nette:Error' + # ime error-presenterja + errorPresenter: Error # (string|array) privzeto je 'Nette:Error' - # določa pravila za razrešitev imena predstavnika na razred + # definira aliase za presenterje in akcije + aliases: ... + + # definira pravila za prevajanje imena presenterja v razred mapping: ... - # ali slabe povezave ustvarjajo opozorila? - # učinkuje samo v načinu za razvijalce + # ali napačne povezave ne generirajo opozoril? + # učinkuje samo v razvojnem načinu silentLinks: ... # (bool) privzeto je false ``` -Ker se v razvojnem načinu predstavniki napak privzeto ne kličejo, napake pa prikazuje Tracy, sprememba vrednosti `catchExceptions` v `true` pomaga preveriti, ali predstavniki napak med razvojem delujejo pravilno. +Od `nette/application` različice 3.2 je mogoče definirati par error-presenterjev: -Možnost `silentLinks` določa, kako se Nette obnaša v razvojnem načinu, ko generiranje povezav ne uspe (na primer ker ni predstavnika itd.). Privzeta vrednost `false` pomeni, da Nette sproži `E_USER_WARNING`. Nastavitev na `true` to sporočilo o napaki odpravi. V produkcijskem okolju se vedno sproži `E_USER_WARNING`. Na to obnašanje lahko vplivamo tudi z nastavitvijo spremenljivke presenterja [$invalidLinkMode |creating-links#Invalid Links]. +```neon +application: + errorPresenter: + 4xx: Error4xx # za izjemo Nette\Application\BadRequestException + 5xx: Error5xx # za ostale izjeme +``` - [Prikazovanje določa pravila, |modules#mapping] po katerih se ime razreda izpelje iz imena predstavnika. +Možnost `silentLinks` določa, kako se Nette obnaša v razvojnem načinu, ko generiranje povezave ne uspe (na primer zato, ker presenter ne obstaja itd.). Privzeta vrednost `false` pomeni, da Nette sproži napako `E_USER_WARNING`. Nastavitev na `true` bo to sporočilo o napaki potlačila. V produkcijskem okolju se `E_USER_WARNING` vedno sproži. Na to obnašanje lahko vplivamo tudi z nastavitvijo spremenljivke presenterja [$invalidLinkMode |creating-links#Neveljavne povezave]. +[Aliasi poenostavljajo povezovanje |creating-links#Aliasi] na pogosto uporabljene presenterje. -Samodejna registracija predavateljev .[#toc-automatic-registration-of-presenters] ---------------------------------------------------------------------------------- +[Mapiranje definira pravila |directory-structure#Mapiranje presenterjev], po katerih se iz imena presenterja izpelje ime razreda. -Nette samodejno doda predstavnike kot storitve v vsebnik DI, kar znatno pospeši njihovo ustvarjanje. Kako Nette najde predstavnike, lahko nastavite: + +Samodejna registracija presenterjev +----------------------------------- + +Nette samodejno dodaja presenterje kot storitve v DI vsebnik, kar bistveno pospeši njihovo ustvarjanje. Kako Nette presenterje išče, je mogoče konfigurirati: ```neon application: - # za iskanje predavateljev na zemljevidu razredov programa Composer? - scanComposer: ... # (bool) privzeto true + # iskati presenterje v Composer class map? + scanComposer: ... # (bool) privzeto je true - # maska, ki se mora ujemati z imenom razreda in datoteke - scanFilter: ... # (string) privzeto '*Presenter' + # maska, ki ji morata ustrezati ime razreda in datoteke + scanFilter: ... # (string) privzeto je '*Presenter' - # v katerih imenikih iskati predstavnike? - scanDirs: # (string[]|false) privzeto '%appDir%' + # v katerih mapah iskati presenterje? + scanDirs: # (string[]|false) privzeto je '%appDir%' - %vendorDir%/mymodule ``` -Imeniki, navedeni v `scanDirs`, ne nadomeščajo privzete vrednosti `%appDir%`, temveč jo dopolnjujejo, tako da bo `scanDirs` vseboval poti `%appDir%` in `%vendorDir%/mymodule`. Če želimo prepisati privzeti imenik, uporabimo [izklicaj |dependency-injection:configuration#Merging]: +Mape, navedene v `scanDirs`, ne prepišejo privzete vrednosti `%appDir%`, ampak jo dopolnjujejo, `scanDirs` bo torej vseboval obe poti `%appDir%` in `%vendorDir%/mymodule`. Če bi želeli privzeto mapo izpustiti, uporabimo [klicaj |dependency-injection:configuration#Združevanje], ki vrednost prepiše: ```neon application: @@ -60,64 +73,73 @@ application: - %vendorDir%/mymodule ``` -Pregledovanje imenikov lahko izklopimo z nastavitvijo false. Ne priporočamo popolne ukinitve samodejnega dodajanja predstavnikov, sicer se bo zmanjšalo delovanje aplikacije. +Skeniranje map lahko izklopimo z navedbo vrednosti false. Ne priporočamo popolne potlačitve samodejnega dodajanja presenterjev, ker sicer pride do zmanjšanja zmogljivosti aplikacije. -Latte .[#toc-latte] -=================== +Predloge Latte +============== -Ta nastavitev globalno vpliva na obnašanje programa Latte v komponentah in predstavitvah. +S to nastavitvijo lahko globalno vplivamo na obnašanje Latte v komponentah in presenterjih. ```neon latte: - # prikaže ploščo Latte v Tracy Baru za glavno predlogo (true) ali za vse komponente (all)? + # prikazati ploščo Latte v Tracy Baru za glavno predlogo (true) ali vse komponente (all)? debugger: ... # (true|false|'all') privzeto je true - # generira predloge z declare(strict_types=1) + # generira predloge z glavo declare(strict_types=1) strictTypes: ... # (bool) privzeto je false - # razred $this->template + # vklopi način [strogega razčlenjevalnika |latte:develop#striktní režim] + strictParsing: ... # (bool) privzeto je false + + # aktivira [preverjanje generirane kode |latte:develop#Kontrola vygenerovaného kódu] + phpLinter: ... # (string) privzeto je null + + # nastavi locale + locale: cs_CZ # (string) privzeto je null + + # razred objekta $this->template templateClass: App\MyTemplateClass # privzeto je Nette\Bridges\ApplicationLatte\DefaultTemplate ``` -Če uporabljate različico Latte 3, lahko novo [razširitev |latte:creating-extension] dodate z uporabo: +Če uporabljate Latte različice 3, lahko dodajate nove [razširitve |latte:extending-latte#Latte Extension] s pomočjo: ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -/--comment - - - - +Če uporabljate Latte različice 2, lahko registrirate nove značke bodisi z navedbo imena razreda ali s sklicem na storitev. Kot privzeta se kliče metoda `install()`, vendar to lahko spremenite tako, da navedete ime druge metode: +```neon +latte: + # registracija uporabniških Latte značk + macros: + - App\MyLatteMacros::register # statična metoda, ime razreda ali klicna funkcija + - @App\MyLatteMacrosFactory # storitev z metodo install() + - @App\MyLatteMacrosFactory::register # storitev z metodo register() + +services: + - App\MyLatteMacrosFactory +``` - - - - -\-- - - -Usmerjanje .[#toc-routing] -========================== +Usmerjanje +========== Osnovne nastavitve: ```neon routing: - # prikazuje usmerjevalno ploščo v vrstici Tracy Bar? + # prikazati usmerjevalno ploščo v Tracy Baru? debugger: ... # (bool) privzeto je true - # serializirati usmerjevalnik v vsebnik DI? - cache: ... # (bool) privzeto false + # serializira usmerjevalnik v DI vsebnik + cache: ... # (bool) privzeto je false ``` -Usmerjevalnik je običajno definiran v razredu [RouterFactory |routing#Route Collection]. Alternativno lahko usmerjevalnike določimo tudi v konfiguraciji z uporabo parov `mask: action`, vendar ta način ne ponuja tako široke variabilnosti nastavitev: +Usmerjanje običajno definiramo v razredu [RouterFactory |routing#Zbirka poti]. Alternativno lahko poti definiramo tudi v konfiguraciji s pomočjo parov `maska: akcija`, vendar ta način ne ponuja tako široke variabilnosti v nastavitvah: ```neon routing: @@ -127,28 +149,43 @@ routing: ``` -Konstante .[#toc-constants] -=========================== +Konstante +========= -Ustvarjanje konstant PHP. +Ustvarjanje PHP konstant. ```neon constants: Foobar: 'baz' ``` -Konstanta `Foobar` bo ustvarjena po zagonu. +Po zagonu aplikacije bo ustvarjena konstanta `Foobar`. .[note] -Konstante ne smejo služiti kot globalno dostopne spremenljivke. Za posredovanje vrednosti objektom uporabite [vbrizgavanje odvisnosti |dependency-injection:passing-dependencies]. +Konstante ne bi smele služiti kot nekakšne globalno dostopne spremenljivke. Za posredovanje vrednosti v objekte uporabite [dependency injection |dependency-injection:passing-dependencies]. PHP === -Nastavite lahko direktive PHP. Pregled vseh direktiv je na voljo na [php.net |https://www.php.net/manual/en/ini.list.php]. +Nastavitev direktiv PHP. Pregled vseh direktiv najdete na [php.net |https://www.php.net/manual/en/ini.list.php]. ```neon php: date.timezone: Europe/Prague ``` + + +Storitve DI +=========== + +Te storitve se dodajajo v DI vsebnik: + +| Ime | Tip | Opis +|---------------------------------------------------------- +| `application.application` | [api:Nette\Application\Application] | [zaganjalnik celotne aplikacije |how-it-works#Nette Application] +| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] +| `application.presenterFactory` | [api:Nette\Application\PresenterFactory] | tovarna za presenterje +| `application.###` | [api:Nette\Application\UI\Presenter] | posamezni presenterji +| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | tovarna objekta `Latte\Engine` +| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | tovarna za [`$this->template` |templates] diff --git a/application/sl/creating-links.texy b/application/sl/creating-links.texy index 4943feeb98..0de3e87efa 100644 --- a/application/sl/creating-links.texy +++ b/application/sl/creating-links.texy @@ -1,160 +1,160 @@ -Ustvarjanje povezav URL +Ustvarjanje URL povezav ***********************
    -Ustvarjanje povezav v Nette je tako enostavno, kot da pokažete s prstom. Samo pokažite in ogrodje bo opravilo vse delo namesto vas. Prikazali bomo: +Ustvarjanje povezav v Nette je preprosto, kot kazanje s prstom. Dovolj je le nameriti in ogrodje bo že samo opravilo vse delo. Pokazali si bomo: -- kako ustvariti povezave v predlogah in drugje -- kako razlikovati povezavo do trenutne strani -- kako je z neveljavnimi povezavami +- kako ustvarjati povezave v predlogah in drugje +- kako razlikovati povezavo na trenutno stran +- kaj storiti z neveljavnimi povezavami
    -Zaradi [dvosmernega usmerjanja |routing] vam nikoli več ne bo treba v predlogah ali kodi trdo kodirati URL-jev aplikacij, ki se lahko pozneje spremenijo ali jih je zapleteno sestaviti. V povezavi samo določite predstavnika in dejanje, podajte morebitne parametre in ogrodje bo samo ustvarilo naslov URL. Pravzaprav je to zelo podobno klicanju funkcije. Všeč vam bo. +Zahvaljujoč [dvosmernemu usmerjanju |routing] vam nikoli ne bo treba v predloge ali kodo trdo kodirati URL naslovov vaše aplikacije, ki se lahko kasneje spremenijo, ali jih zapleteno sestavljati. V povezavi je dovolj navesti presenter in akcijo, posredovati morebitne parametre in ogrodje bo že samo generiralo URL. Pravzaprav je to zelo podobno, kot ko kličete funkcijo. To vam bo všeč. -V predlogi za predstavitelja .[#toc-in-the-presenter-template] -============================================================== +V predlogi presenterja +====================== Najpogosteje ustvarjamo povezave v predlogah in odličen pomočnik je atribut `n:href`: ```latte -detail +podrobnosti ``` -Upoštevajte, da smo namesto atributa HTML `href` uporabili [n:atribut |latte:syntax#n:attributes] `n:href`. Njegova vrednost ni naslov URL, kot ste vajeni pri atributu `href`, temveč ime predstavnika in dejanja. +Opazite, da smo namesto HTML atributa `href` uporabili [n:atribut |latte:syntax#n:atributi] `n:href`. Njegova vrednost potem ni URL, kot bi bilo v primeru atributa `href`, ampak ime presenterja in akcije. -Klik na povezavo je, preprosto povedano, nekaj podobnega kot klic metode `ProductPresenter::renderShow()`. In če ima v svojem podpisu parametre, jo lahko pokličemo z argumenti: +Klik na povezavo je, poenostavljeno rečeno, nekaj takega kot klicanje metode `ProductPresenter::renderShow()`. In če ima v svoji signaturi parametre, jo lahko kličemo z argumenti: ```latte -detail +podrobnosti izdelka ``` -Lahko ji posredujemo tudi poimenovane parametre. Naslednja povezava posreduje parameter `lang` z vrednostjo `en`: +Možno je posredovati tudi imenovane parametre. Naslednja povezava posreduje parameter `lang` z vrednostjo `cs`: ```latte -detail +podrobnosti izdelka ``` -Če metoda `ProductPresenter::renderShow()` v svojem podpisu nima zapisa `$lang`, lahko vrednost parametra prebere z uporabo `$lang = $this->getParameter('lang')`. +Če metoda `ProductPresenter::renderShow()` nima `$lang` v svoji signaturi, lahko vrednost parametra ugotovi s pomočjo `$lang = $this->getParameter('lang')` ali iz [lastnosti |presenters#Parametri zahtevka]. -Če so parametri shranjeni v polju, jih je mogoče razširiti z operatorjem `...` (ali `(expand)` v Latte 2.x): +Če so parametri shranjeni v polju, jih lahko razvijemo z operatorjem `...` (v Latte 2.x z operatorjem `(expand)`): ```latte -{var $args = [$product->id, lang => en]} -detail +{var $args = [$product->id, lang => cs]} +podrobnosti izdelka ``` -V povezavah se samodejno posredujejo tudi tako imenovani [trajni parametri |presenters#persistent parameters]. +V povezavah se samodejno prenašajo tudi t.i. [persistentni parametri |presenters#Persistentni parametri]. -Atribut `n:href` je zelo priročen za oznake HTML ``. Če želimo povezavo izpisati drugje, na primer v besedilu, uporabimo `{link}`: +Atribut `n:href` je zelo priročen za HTML značke ``. Če želimo povezavo izpisati drugje, na primer v besedilu, uporabimo `{link}`: ```latte -URL is: {link Home:default} +Naslov je: {link Home:default} ``` -V kodi .[#toc-in-the-code] -========================== +V kodi +====== -Metoda `link()` se uporablja za ustvarjanje povezave v predstavitvenem programu: +Za ustvarjanje povezave v presenterju služi metoda `link()`: ```php $url = $this->link('Product:show', $product->id); ``` -Parametri se lahko posredujejo tudi kot polje, v katerem se lahko določijo tudi poimenovani parametri: +Parametre lahko posredujemo tudi s pomočjo polja, kjer lahko navedemo tudi imenovane parametre: ```php $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); ``` -Povezave je mogoče ustvariti tudi brez predstavnika z uporabo [LinkGeneratorja |#LinkGenerator] in njegove metode `link()`. +Povezave lahko ustvarjamo tudi brez presenterja, za to je tu [##LinkGenerator] in njegova metoda `link()`. -Povezave do predstavnika .[#toc-links-to-presenter] -=================================================== +Povezave na presenter +===================== -Če je cilj povezave predvajalnik in akcija, ima povezava naslednjo sintakso: +Če je cilj povezave presenter in akcija, ima to sintakso: ``` [//] [[[[:]module:]presenter:]action | this] [#fragment] ``` -To obliko podpirajo vse oznake Latte in vse metode presenterja, ki delajo s povezavami, tj. `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` in tudi [LinkGenerator |#LinkGenerator]. Torej tudi če je v primerih uporabljena `n:href`, je lahko uporabljena katera koli od teh funkcij. +Format podpirajo vse značke Latte in vse metode presenterja, ki delajo s povezavami, torej `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` in tudi [##LinkGenerator]. Torej, čeprav je v primerih uporabljen `n:href`, bi lahko bila tam katerakoli od funkcij. Osnovna oblika je torej `Presenter:action`: ```latte -home +domača stran ``` -Če se povežemo z dejanjem trenutnega predstavnika, lahko izpustimo njegovo ime: +Če povezujemo na akcijo trenutnega presenterja, lahko njegovo ime izpustimo: ```latte -home +domača stran ``` -Če je dejanje `default`, ga lahko izpustimo, vendar mora dvopičje ostati: +Če je cilj akcija `default`, jo lahko izpustimo, vendar dvopičje mora ostati: ```latte -home +domača stran ``` -Povezave lahko kažejo tudi na druge [module |modules]. Tu se povezave razlikujejo na relativne na podmodule ali absolutne. Načelo je podobno kot pri diskovnih poteh, le da so namesto poševnic dvopičja. Predpostavimo, da je dejanski predstavnik del modula `Front`, potem bomo zapisali: +Povezave lahko vodijo tudi v druge [module |directory-structure#Presenterji in predloge]. Tukaj se povezave razlikujejo na relativne v ugnezden podmodul ali absolutne. Princip je analogen potem na disku, le da so namesto poševnic dvopičja. Predpostavimo, da je trenutni presenter del modula `Front`, potem zapišemo: ```latte -link to Front:Shop:Product:show -link to Admin:Product:show +povezava na Front:Shop:Product:show +povezava na Admin:Product:show ``` -Poseben primer je [povezovanje na samega sebe |#Links to Current Page]. V tem primeru bomo kot cilj zapisali `this`. +Poseben primer je povezava [nase |#Povezava na trenutno stran], ko kot cilj navedemo `this`. ```latte -refresh +osveži ``` -Na določen del strani HTML se lahko povežemo s tako imenovanim fragmentom za simbolom `#` hash: +Povezovati lahko na določen del strani prek t.i. fragmenta za znakom lojtre `#`: ```latte -link to Home:default and fragment #main +povezava na Home:default in fragment #main ``` -Absolutne poti .[#toc-absolute-paths] -===================================== +Absolutne poti +============== -Povezave, ki jih generirata `link()` ali `n:href`, so vedno absolutne poti (tj. začnejo se z `/`), ne pa tudi absolutni naslovi URL s protokolom in domeno, kot `https://domain`. +Povezave, generirane s pomočjo `link()` ali `n:href`, so vedno absolutne poti (tj. začnejo se z znakom `/`), vendar ne absolutni URL-ji s protokolom in domeno kot `https://domain`. -Če želite ustvariti absolutni naslov URL, na začetek dodajte dve poševnici (npr. `n:href="//Home:"`). Lahko pa tudi preklopite predstavnik, da ustvarja samo absolutne povezave, tako da nastavite `$this->absoluteUrls = true`. +Za generiranje absolutnega URL-ja dodajte na začetek dve poševnici (npr. `n:href="//Home:"`). Ali pa lahko preklopite presenter, da generira samo absolutne povezave z nastavitvijo `$this->absoluteUrls = true`. -Povezava na trenutno stran .[#toc-link-to-current-page] -======================================================= +Povezava na trenutno stran +========================== -Ciljna stran `this` bo ustvarila povezavo do trenutne strani: +Cilj `this` ustvari povezavo na trenutno stran: ```latte -refresh +osveži ``` -Hkrati se vsi parametri, navedeni v podpisu `render()` ali `action()` se prenesejo. Če smo torej na straneh `Product:show` in `id:123`, bo tudi povezava do strani `this` prenesla ta parameter. +Hkrati se prenašajo tudi vsi parametri, navedeni v signaturi metode `action()` ali `render()`, če `action()` ni definirana. Torej, če smo na strani `Product:show` in `id: 123`, povezava na `this` prenese tudi ta parameter. -Seveda je mogoče parametre navesti tudi neposredno: +Seveda je mogoče parametre specificirati neposredno: ```latte -refresh +osveži ``` -Funkcija `isLinkCurrent()` določi, ali je cilj povezave enak kot trenutna stran. To lahko na primer uporabimo v predlogi za razlikovanje povezav itd. +Funkcija `isLinkCurrent()` ugotavlja, ali je cilj povezave enak trenutni strani. To lahko uporabimo na primer v predlogi za razlikovanje povezav ipd. -Parametri so enaki kot pri metodi `link()`, vendar je mogoče namesto določenega dejanja uporabiti tudi nadomestni znak `*`, kar pomeni katero koli dejanje predstavnika. +Parametri so enaki kot pri metodi `link()`, poleg tega pa je mogoče namesto konkretne akcije navesti nadomestni znak `*`, ki pomeni katerokoli akcijo danega presenterja. ```latte {if !isLinkCurrent('Admin:login')} - Přihlaste se + Prijavite se {/if}
  • @@ -162,58 +162,58 @@ Parametri so enaki kot pri metodi `link()`, vendar je mogoče namesto določeneg
  • ``` -Skrajšana oblika se lahko uporablja v kombinaciji s `n:href` v enem samem elementu: +V kombinaciji z `n:href` v enem elementu se da uporabiti skrajšana oblika: ```latte -... +... ``` -Zaščitni znak `*` nadomesti samo dejanje predavatelja, ne pa tudi samega predavatelja. +Nadomestni znak `*` lahko uporabimo samo namesto akcije, ne pa presenterja. -Če želimo ugotoviti, ali smo v določenem modulu ali njegovem podmodulu, lahko uporabimo funkcijo `isModuleCurrent(moduleName)`. +Za ugotavljanje, ali smo v določenem modulu ali njegovem podmodulu, uporabimo metodo `isModuleCurrent(moduleName)`. ```latte -
  • +
  • ...
  • ``` -Povezave do signala .[#toc-links-to-signal] -=========================================== +Povezave na signal +================== -Cilj povezave nista lahko le predstavnik in dejanje, temveč tudi [signal |components#Signal] (kličeta metodo `handle()`). Sintaksa je naslednja: +Cilj povezave ni nujno samo presenter in akcija, ampak tudi [signal |components#Signal] (kličejo metodo `handle()`). Potem je sintaksa naslednja: ``` [//] [sub-component:]signal! [#fragment] ``` -Signal se torej razlikuje z vzklikalnikom: +Signal torej loči klicaj: ```latte signal ``` -Ustvarite lahko tudi povezavo do signala podkomponente (ali podkomponente): +Lahko ustvarimo tudi povezavo na signal podkomponente (ali pod-podkomponente): ```latte signal ``` -Povezave v komponenti .[#toc-links-in-component] -================================================ +Povezave v komponenti +===================== -Ker so [komponente |components] ločene enote za večkratno uporabo, ki naj ne bi imele nobenih povezav z okoliškimi predstavniki, povezave delujejo nekoliko drugače. Latte atribut `n:href` in oznaka `{link}` ter metode komponente, kot so `link()` in druge, vedno upoštevajo cilj **kot ime signala**. Zato ni treba uporabljati vzklika: +Ker so [komponente|components] samostojne ponovno uporabne enote, ki ne bi smele imeti nobenih povezav z okoliškimi presenterji, tukaj povezave delujejo nekoliko drugače. Atribut Latte `n:href` in značka `{link}` ter metode komponent, kot je `link()` in druge, obravnavajo cilj povezave **vedno kot ime signala**. Zato ni treba niti navajati klicaja: ```latte -signal, not an action +signal, ne akcija ``` -Če se želimo v predlogi komponente povezati s predstavniki, uporabimo oznako `{plink}`: +Če bi želeli v predlogi komponente povezovati na presenterje, uporabimo za to značko `{plink}`: ```latte -home +domov ``` ali v kodi @@ -223,17 +223,41 @@ $this->getPresenter()->link('Home:default') ``` -Neveljavne povezave .[#toc-invalid-links] -========================================= +Aliasi .{data-version:v3.2.2} +============================= + +Včasih se lahko zgodi, da je koristno paru Presenter:akcija dodeliti lahko zapomnljiv alias. Na primer, domačo stran `Front:Home:default` poimenovati preprosto kot `home` ali `Admin:Dashboard:default` kot `admin`. + +Aliasi se definirajo v [konfiguraciji|configuration] pod ključem `application › aliases`: + +```neon +application: + aliases: + home: Front:Home:default + admin: Admin:Dashboard:default + sign: Front:Sign:in +``` + +V povezavah se nato zapisujejo s pomočjo afne, na primer: + +```latte +administracija +``` + +Podprti so tudi v vseh metodah, ki delajo s povezavami, kot je `redirect()` in podobno. + + +Neveljavne povezave +=================== -Lahko se zgodi, da ustvarimo neveljavno povezavo - bodisi ker se sklicuje na neobstoječega predstavnika, bodisi ker posreduje več parametrov, kot jih prejme ciljna metoda v svojem podpisu, bodisi kadar ni mogoče ustvariti URL za ciljno dejanje. Kaj storiti z neveljavnimi povezavami, določa statična spremenljivka `Presenter::$invalidLinkMode`. Ima lahko eno od teh vrednosti (konstant): +Lahko se zgodi, da ustvarimo neveljavno povezavo - bodisi zato, ker vodi na neobstoječ presenter, ali zato, ker posreduje več parametrov, kot jih ciljna metoda sprejema v svoji signaturi, ali ko za ciljno akcijo ni mogoče generirati URL-ja. Kako ravnati z neveljavnimi povezavami, določa statična spremenljivka `Presenter::$invalidLinkMode`. Ta lahko prevzame kombinacijo teh vrednosti (konstant): -- `Presenter::InvalidLinkSilent` - tihi način, vrne simbol `#` kot URL -- `Presenter::InvalidLinkWarning` - ustvari se E_USER_WARNING -- `Presenter::InvalidLinkTextual` - vizualno opozorilo, besedilo napake se prikaže v povezavi -- `Presenter::InvalidLinkException` - Izključitev InvalidLinkException se vrže +- `Presenter::InvalidLinkSilent` - tihi način, kot URL se vrne znak # +- `Presenter::InvalidLinkWarning` - sproži se opozorilo E_USER_WARNING, ki bo v produkcijskem načinu zabeleženo, vendar ne bo povzročilo prekinitve izvajanja skripta +- `Presenter::InvalidLinkTextual` - vizualno opozorilo, napako izpiše neposredno v povezavo +- `Presenter::InvalidLinkException` - sproži se izjema InvalidLinkException -Privzeta nastavitev v produkcijskem načinu je `InvalidLinkWarning`, v razvojnem načinu pa `InvalidLinkWarning | InvalidLinkTextual`. `InvalidLinkWarning` v produkcijskem okolju ne uniči skripte, vendar se bo opozorilo zabeležilo. V razvojnem okolju bo program [Tracy |tracy:] prestregel opozorilo in prikazal modri zaslon za napake. Če je nastavljen `InvalidLinkTextual`, predstavnik in komponente vrnejo sporočilo o napaki kot naslov URL, ki se zvezdica z `#error:`. Če želimo, da so takšne povezave vidne, lahko v naš niz slogov dodamo pravilo CSS: +Privzeta nastavitev je `InvalidLinkWarning` v produkcijskem načinu in `InvalidLinkWarning | InvalidLinkTextual` v razvojnem. `InvalidLinkWarning` v produkcijskem okolju ne povzroči prekinitve skripta, vendar bo opozorilo zabeleženo. V razvojnem okolju ga ujame [Tracy |tracy:] in prikaže bluescreen. `InvalidLinkTextual` deluje tako, da kot URL vrne sporočilo o napaki, ki se začne z znaki `#error:`. Da bi bile takšne povezave na prvi pogled očitne, si dodamo v CSS: ```css a[href^="#error:"] { @@ -242,7 +266,7 @@ a[href^="#error:"] { } ``` -Če ne želimo, da se v razvojnem okolju ustvarjajo opozorila, lahko v [konfiguraciji |configuration] vklopimo način tihe neveljavne povezave. +Če ne želimo, da se v razvojnem okolju producirajo opozorila, lahko nastavimo tihi način neposredno v [konfiguraciji|configuration]. ```neon application: @@ -250,13 +274,13 @@ application: ``` -LinkGenerator .[#toc-linkgenerator] -=================================== +LinkGenerator +============= -Kako ustvariti povezave z metodo `link()` comfort, vendar brez prisotnosti predstavnika? Zato je tu [api:Nette\Application\LinkGenerator]. +Kako ustvarjati povezave s podobnim udobjem kot ima metoda `link()`, vendar brez prisotnosti presenterja? Za to je tu [api:Nette\Application\LinkGenerator]. -LinkGenerator je storitev, ki jo lahko imate posredovano skozi konstruktor in nato ustvarite povezave z njeno metodo `link()`. +LinkGenerator je storitev, ki si jo lahko pustite posredovati prek konstruktorja in nato ustvarjate povezave z njegovo metodo `link()`. -V primerjavi s predstavniki obstaja razlika. LinkGenerator ustvari vse povezave kot absolutne naslove URL. Poleg tega ni "trenutnega predstavnika", zato ni mogoče določiti samo imena akcije `link('default')` ali relativnih poti do [modulov |modules]. +V primerjavi s presenterji je tu razlika. LinkGenerator ustvarja vse povezave takoj kot absolutne URL-je. In nadalje ne obstaja noben "trenutni presenter", zato ni mogoče kot cilj navesti samo ime akcije `link('default')` ali navajati relativne poti do modulov. -Neveljavne povezave vedno vrže `Nette\Application\UI\InvalidLinkException`. +Neveljavne povezave vedno sprožijo `Nette\Application\UI\InvalidLinkException`. diff --git a/application/sl/directory-structure.texy b/application/sl/directory-structure.texy new file mode 100644 index 0000000000..fcf4a9205d --- /dev/null +++ b/application/sl/directory-structure.texy @@ -0,0 +1,526 @@ +Struktura mape aplikacije +************************* + +
    + +Kako zasnovati pregledno in razširljivo strukturo map za projekte v Nette Framework? Pokazali si bomo preverjene prakse, ki vam bodo pomagale pri organizaciji kode. Izvedeli boste: + +- kako **logično razčleniti** aplikacijo v mape +- kako strukturo zasnovati tako, da **dobro skalira** z rastjo projekta +- kakšne so **možne alternative** in njihove prednosti ali slabosti + +
    + + +Pomembno je omeniti, da Nette Framework sam po sebi ne vztraja pri nobeni konkretni strukturi. Zasnovan je tako, da se ga da enostavno prilagoditi kakršnimkoli potrebam in preferencam. + + +Osnovna struktura projekta +========================== + +Čeprav Nette Framework ne narekuje nobene fiksne strukture map, obstaja preverjena privzeta ureditev v obliki [Web Project|https://github.com/nette/web-project]: + +/--pre +web-project/ +├── app/ ← mapa z aplikacijo +├── assets/ ← datoteke SCSS, JS, slike..., alternativno resources/ +├── bin/ ← skripti za ukazno vrstico +├── config/ ← konfiguracija +├── log/ ← zabeležene napake +├── temp/ ← začasne datoteke, predpomnilnik +├── tests/ ← testi +├── vendor/ ← knjižnice, nameščene s Composerjem +└── www/ ← javna mapa (document-root) +\-- + +To strukturo lahko poljubno urejate glede na svoje potrebe - mape preimenujete ali premaknete. Nato je dovolj le urediti relativne poti do map v datoteki `Bootstrap.php` in po potrebi `composer.json`. Nič več ni potrebno, nobene zapletene rekonfiguracije, nobenih sprememb konstant. Nette razpolaga s pametnim samodejnim zaznavanjem in samodejno prepozna lokacijo aplikacije, vključno z njeno osnovno URL. + + +Principi organizacije kode +========================== + +Ko prvič raziskujete nov projekt, bi se morali v njem hitro znajti. Predstavljajte si, da odprete mapo `app/Model/` in vidite to strukturo: + +/--pre +app/Model/ +├── Services/ +├── Repositories/ +└── Entities/ +\-- + +Iz nje razberete le to, da projekt uporablja neke storitve, repozitorije in entitete. O dejanskem namenu aplikacije ne izveste ničesar. + +Poglejmo si drugačen pristop - **organizacijo po domenah**: + +/--pre +app/Model/ +├── Cart/ +├── Payment/ +├── Order/ +└── Product/ +\-- + +Tukaj je drugače - na prvi pogled je jasno, da gre za spletno trgovino. Že sama imena map razkrivajo, kaj aplikacija zna - dela s plačili, naročili in izdelki. + +Prvi pristop (organizacija po tipu razredov) v praksi prinaša vrsto težav: koda, ki logično sodi skupaj, je razdrobljena v različne mape in morate med njimi preskakovati. Zato bomo organizirali po domenah. + + +Imenski prostori +---------------- + +Običajno je, da struktura map ustreza imenskim prostorom v aplikaciji. To pomeni, da fizična lokacija datotek ustreza njihovemu imenskemu prostoru. Na primer, razred, ki se nahaja v `app/Model/Product/ProductRepository.php`, bi moral imeti imenski prostor `App\Model\Product`. Ta princip pomaga pri orientaciji v kodi in poenostavlja samodejno nalaganje. + + +Ednina vs množina v imenih +-------------------------- + +Opazite, da pri glavnih mapah aplikacije uporabljamo ednino: `app`, `config`, `log`, `temp`, `www`. Enako tudi znotraj aplikacije: `Model`, `Core`, `Presentation`. To je zato, ker vsaka od njih predstavlja en celovit koncept. + +Podobno na primer `app/Model/Product` predstavlja vse v zvezi z izdelki. Ne bomo ga poimenovali `Products`, ker ne gre za mapo, polno izdelkov (to bi bile tam datoteke `nokia.php`, `samsung.php`). To je imenski prostor, ki vsebuje razrede za delo z izdelki - `ProductRepository.php`, `ProductService.php`. + +Mapa `app/Tasks` je v množini zato, ker vsebuje nabor samostojnih izvedljivih skriptov - `CleanupTask.php`, `ImportTask.php`. Vsak od njih je samostojna enota. + +Za doslednost priporočamo uporabo: +- Ednine za imenski prostor, ki predstavlja funkcionalno celoto (čeprav dela z več entitetami) +- Množine za zbirke samostojnih enot +- V primeru negotovosti ali če o tem ne želite razmišljati, izberite ednino + + +Javna mapa `www/` +================= + +Ta mapa je edina dostopna s spleta (t.i. document-root). Pogosto se lahko srečate tudi z imenom `public/` namesto `www/` - to je le vprašanje konvencije in na funkcionalnost aplikacije nima vpliva. Mapa vsebuje: +- [Vstopno točko |bootstrapping#index.php] aplikacije `index.php` +- Datoteko `.htaccess` s pravili za mod_rewrite (pri Apache) +- Statične datoteke (CSS, JavaScript, slike) +- Naložene datoteke + +Za pravilno varnost aplikacije je ključno imeti pravilno [konfiguriran document-root |nette:troubleshooting#Kako spremeniti ali odstraniti mapo www iz URL-ja]. + +.[note] +Nikoli ne postavljajte v to mapo mape `node_modules/` - vsebuje na tisoče datotek, ki so lahko izvedljive in ne bi smele biti javno dostopne. + + +Aplikacijska mapa `app/` +======================== + +To je glavna mapa z aplikacijsko kodo. Osnovna struktura: + +/--pre +app/ +├── Core/ ← infrastrukturne zadeve +├── Model/ ← poslovna logika +├── Presentation/ ← presenterji in predloge +├── Tasks/ ← ukazni skripti +└── Bootstrap.php ← zagonski razred aplikacije +\-- + +`Bootstrap.php` je [zagonski razred aplikacije|bootstrapping], ki inicializira okolje, nalaga konfiguracijo in ustvarja DI vsebnik. + +Poglejmo si zdaj posamezne podmape podrobneje. + + +Presenterji in predloge +======================= + +Predstavitveni del aplikacije imamo v mapi `app/Presentation`. Alternativa je kratko `app/UI`. To je mesto za vse presenterje, njihove predloge in morebitne pomožne razrede. + +To plast organiziramo po domenah. V kompleksnem projektu, ki združuje spletno trgovino, blog in API, bi struktura izgledala takole: + +/--pre +app/Presentation/ +├── Shop/ ← spletna trgovina frontend +│ ├── Product/ +│ ├── Cart/ +│ └── Order/ +├── Blog/ ← blog +│ ├── Home/ +│ └── Post/ +├── Admin/ ← administracija +│ ├── Dashboard/ +│ └── Products/ +└── Api/ ← API končne točke + └── V1/ +\-- + +Nasprotno pa bi pri preprostem blogu uporabili členitev: + +/--pre +app/Presentation/ +├── Front/ ← frontend spletnega mesta +│ ├── Home/ +│ └── Post/ +├── Admin/ ← administracija +│ ├── Dashboard/ +│ └── Posts/ +├── Error/ +└── Export/ ← RSS, sitemaps itd. +\-- + +Mape kot `Home/` ali `Dashboard/` vsebujejo presenterje in predloge. Mape kot `Front/`, `Admin/` ali `Api/` imenujemo **moduli**. Tehnično gre za običajne mape, ki služijo za logično členitev aplikacije. + +Vsaka mapa s presenterjem vsebuje enako poimenovan presenter in njegove predloge. Na primer, mapa `Dashboard/` vsebuje: + +/--pre +Dashboard/ +├── DashboardPresenter.php ← presenter +└── default.latte ← predloga +\-- + +Ta struktura map se odraža v imenskih prostorih razredov. Na primer, `DashboardPresenter` se nahaja v imenskem prostoru `App\Presentation\Admin\Dashboard` (glej [##mapiranje presenterjev]): + +```php +namespace App\Presentation\Admin\Dashboard; + +class DashboardPresenter extends Nette\Application\UI\Presenter +{ + // ... +} +``` + +Na presenter `Dashboard` znotraj modula `Admin` se v aplikaciji sklicujemo s pomočjo dvopične notacije kot na `Admin:Dashboard`. Na njegovo akcijo `default` potem kot na `Admin:Dashboard:default`. V primeru ugnezdenih modulov uporabljamo več dvopičij, na primer `Shop:Order:Detail:default`. + + +Fleksibilen razvoj strukture +---------------------------- + +Ena od velikih prednosti te strukture je, kako elegantno se prilagaja rastočim potrebam projekta. Kot primer si vzemimo del, ki generira XML vire. Na začetku imamo preprosto obliko: + +/--pre +Export/ +├── ExportPresenter.php ← en presenter za vse izvoze +├── sitemap.latte ← predloga za sitemap +└── feed.latte ← predloga za RSS vir +\-- + +Sčasoma se dodajo nove vrste virov in zanje potrebujemo več logike... Noben problem! Mapa `Export/` preprosto postane modul: + +/--pre +Export/ +├── Sitemap/ +│ ├── SitemapPresenter.php +│ └── sitemap.latte +└── Feed/ + ├── FeedPresenter.php + ├── zbozi.latte ← vir za Zboží.cz + └── heureka.latte ← vir za Heureka.cz +\-- + +Ta transformacija je popolnoma gladka - dovolj je ustvariti nove podmape, razdeliti kodo vanje in posodobiti povezave (npr. iz `Export:feed` na `Export:Feed:zbozi`). Zahvaljujoč temu lahko strukturo postopoma širimo glede na potrebe, raven gnezdenja ni nikakor omejena. + +Če na primer v administraciji imate veliko presenterjev, ki se nanašajo na upravljanje naročil, kot so `OrderDetail`, `OrderEdit`, `OrderDispatch` itd., lahko za boljšo organiziranost na tem mestu ustvarite modul (mapo) `Order`, v katerem bodo (mape za) presenterje `Detail`, `Edit`, `Dispatch` in drugi. + + +Lokacija predlog +---------------- + +V prejšnjih primerih smo videli, da so predloge nameščene neposredno v mapi s presenterjem: + +/--pre +Dashboard/ +├── DashboardPresenter.php ← presenter +├── DashboardTemplate.php ← izbirni razred za predlogo +└── default.latte ← predloga +\-- + +Ta lokacija se v praksi izkaže za najudobnejšo - vse povezane datoteke imate takoj pri roki. + +Alternativno lahko predloge namestite v podmapo `templates/`. Nette podpira obe varianti. Celo predloge lahko namestite tudi popolnoma izven mape `Presentation/`. Vse o možnostih lokacije predlog najdete v poglavju [Iskanje predlog |templates#Iskanje predlog]. + + +Pomožni razredi in komponente +----------------------------- + +K presenterjem in predlogam pogosto spadajo tudi druge pomožne datoteke. Namestimo jih logično glede na njihovo področje delovanja: + +1. **Neposredno pri presenterju** v primeru specifičnih komponent za dani presenter: + +/--pre +Product/ +├── ProductPresenter.php +├── ProductGrid.php ← komponenta za izpis izdelkov +└── FilterForm.php ← obrazec za filtriranje +\-- + +2. **Za modul** - priporočamo uporabo mape `Accessory`, ki se namesti pregledno takoj na začetku abecede: + +/--pre +Front/ +├── Accessory/ +│ ├── NavbarControl.php ← komponente za frontend +│ └── TemplateFilters.php +├── Product/ +└── Cart/ +\-- + +3. **Za celotno aplikacijo** - v `Presentation/Accessory/`: +/--pre +app/Presentation/ +├── Accessory/ +│ ├── LatteExtension.php +│ └── TemplateFilters.php +├── Front/ +└── Admin/ +\-- + +Ali pa lahko pomožne razrede kot `LatteExtension.php` ali `TemplateFilters.php` namestite v infrastrukturno mapo `app/Core/Latte/`. In komponente v `app/Components`. Izbira je odvisna od navad ekipe. + + +Model - srce aplikacije +======================= + +Model vsebuje vso poslovno logiko aplikacije. Za njegovo organizacijo velja spet pravilo - strukturiramo po domenah: + +/--pre +app/Model/ +├── Payment/ ← vse v zvezi s plačili +│ ├── PaymentFacade.php ← glavna vstopna točka +│ ├── PaymentRepository.php +│ ├── Payment.php ← entiteta +├── Order/ ← vse v zvezi z naročili +│ ├── OrderFacade.php +│ ├── OrderRepository.php +│ ├── Order.php +└── Shipping/ ← vse v zvezi z dostavo +\-- + +V modelu se tipično srečate s temi tipi razredov: + +**Fasade**: predstavljajo glavno vstopno točko v konkretno domeno v aplikaciji. Delujejo kot orkestrator, ki koordinira sodelovanje med različnimi storitvami za namen implementacije celotnih primerov uporabe (kot "ustvari naročilo" ali "obdelaj plačilo"). Pod svojo orkestracijsko plastjo fasada skriva implementacijske podrobnosti pred preostankom aplikacije, s čimer zagotavlja čist vmesnik za delo z dano domeno. + +```php +class OrderFacade +{ + public function createOrder(Cart $cart): Order + { + // validacija + // ustvarjanje naročila + // pošiljanje e-pošte + // zapisovanje v statistiko + } +} +``` + +**Storitve**: osredotočajo se na specifično poslovno operacijo znotraj domene. Za razliko od fasade, ki orkestrira celotne primere uporabe, storitev implementira konkretno poslovno logiko (kot izračuni cen ali obdelava plačil). Storitve so tipično brez stanja in jih lahko uporabljajo bodisi fasade kot gradniki za kompleksnejše operacije ali neposredno drugi deli aplikacije za enostavnejše naloge. + +```php +class PricingService +{ + public function calculateTotal(Order $order): Money + { + // izračun cene + } +} +``` + +**Repozitoriji**: zagotavljajo vso komunikacijo s podatkovnim skladiščem, tipično podatkovno bazo. Njegova naloga je nalaganje in shranjevanje entitet ter implementacija metod za njihovo iskanje. Repozitorij loči preostanek aplikacije od implementacijskih podrobnosti podatkovne baze in zagotavlja objektno usmerjen vmesnik za delo s podatki. + +```php +class OrderRepository +{ + public function find(int $id): ?Order + { + } + + public function findByCustomer(int $customerId): array + { + } +} +``` + +**Entitete**: objekti, ki predstavljajo glavne poslovne koncepte v aplikaciji, ki imajo svojo identiteto in se spreminjajo s časom. Tipično gre za razrede, preslikane na tabele podatkovne baze s pomočjo ORM (kot Nette Database Explorer ali Doctrine). Entitete lahko vsebujejo poslovna pravila, ki se nanašajo na njihove podatke, in validacijsko logiko. + +```php +// Entiteta, preslikana na tabelo podatkovne baze orders +class Order extends Nette\Database\Table\ActiveRow +{ + public function addItem(Product $product, int $quantity): void + { + $this->related('order_items')->insert([ + 'product_id' => $product->id, + 'quantity' => $quantity, + 'unit_price' => $product->price, + ]); + } +} +``` + +**Vrednostni objekti**: nespremenljivi objekti, ki predstavljajo vrednosti brez lastne identitete - na primer denarni znesek ali e-poštni naslov. Dve instanci vrednostnega objekta z enakimi vrednostmi se štejeta za identični. + + +Infrastrukturna koda +==================== + +Mapa `Core/` (ali tudi `Infrastructure/`) je dom za tehnično osnovo aplikacije. Infrastrukturna koda tipično vključuje: + +/--pre +app/Core/ +├── Router/ ← usmerjanje in upravljanje URL-jev +│ └── RouterFactory.php +├── Security/ ← avtentikacija in avtorizacija +│ ├── Authenticator.php +│ └── Authorizator.php +├── Logging/ ← dnevniško beleženje in nadzor +│ ├── SentryLogger.php +│ └── FileLogger.php +├── Cache/ ← plast predpomnjenja +│ └── FullPageCache.php +└── Integration/ ← integracija z zunanjimi storitvami + ├── Slack/ + └── Stripe/ +\-- + +Pri manjših projektih seveda zadostuje ravna členitev: + +/--pre +Core/ +├── RouterFactory.php +├── Authenticator.php +└── QueueMailer.php +\-- + +Gre za kodo, ki: + +- Rešuje tehnično infrastrukturo (usmerjanje, beleženje, predpomnjenje) +- Integrira zunanje storitve (Sentry, Elasticsearch, Redis) +- Zagotavlja osnovne storitve za celotno aplikacijo (pošta, podatkovna baza) +- Je večinoma neodvisna od konkretne domene - predpomnilnik ali logger deluje enako za spletno trgovino ali blog. + +Se sprašujete, ali določen razred spada sem ali v model? Ključna razlika je v tem, da koda v `Core/`: + +- Ne ve nič o domeni (izdelki, naročila, članki) +- Je večinoma mogoče prenesti v drug projekt +- Rešuje "kako deluje" (kako poslati pošto), ne pa "kaj dela" (kakšno pošto poslati) + +Primer za boljše razumevanje: + +- `App\Core\MailerFactory` - ustvarja instance razreda za pošiljanje e-pošte, rešuje SMTP nastavitve +- `App\Model\OrderMailer` - uporablja `MailerFactory` za pošiljanje e-pošte o naročilih, pozna njihove predloge in ve, kdaj se morajo poslati + + +Ukazni skripti +============== + +Aplikacije pogosto potrebujejo izvajanje dejavnosti izven običajnih HTTP zahtevkov - bodisi gre za obdelavo podatkov v ozadju, vzdrževanje ali periodične naloge. Za zagon služijo preprosti skripti v mapi `bin/`, samo implementacijsko logiko pa namestimo v `app/Tasks/` (po potrebi `app/Commands/`). + +Primer: + +/--pre +app/Tasks/ +├── Maintenance/ ← vzdrževalni skripti +│ ├── CleanupCommand.php ← brisanje starih podatkov +│ └── DbOptimizeCommand.php ← optimizacija podatkovne baze +├── Integration/ ← integracija z zunanjimi sistemi +│ ├── ImportProducts.php ← uvoz iz dobaviteljskega sistema +│ └── SyncOrders.php ← sinhronizacija naročil +└── Scheduled/ ← redne naloge + ├── NewsletterCommand.php ← pošiljanje novičnikov + └── ReminderCommand.php ← obvestila strankam +\-- + +Kaj spada v model in kaj v ukazne skripte? Na primer, logika za pošiljanje enega e-poštnega sporočila je del modela, množično pošiljanje tisočev e-poštnih sporočil pa že spada v `Tasks/`. + +Naloge običajno [zaženemo iz ukazne vrstice |https://blog.nette.org/en/cli-scripts-in-nette-application] ali prek crona. Lahko jih zaženemo tudi prek HTTP zahtevka, vendar je treba misliti na varnost. Presenter, ki nalogo zažene, je treba zavarovati, na primer samo za prijavljene uporabnike ali z močnim žetonom in dostopom z dovoljenih IP naslovov. Pri dolgih nalogah je treba povečati časovno omejitev skripta in uporabiti `session_write_close()`, da se ne zaklene seja. + + +Druge možne mape +================ + +Poleg omenjenih osnovnih map lahko glede na potrebe projekta dodate druge specializirane mape. Poglejmo si najpogostejše izmed njih in njihovo uporabo: + +/--pre +app/ +├── Api/ ← logika za API, neodvisna od predstavitvene plasti +├── Database/ ← migracijski skripti in sejalci za testne podatke +├── Components/ ← deljene vizualne komponente po celotni aplikaciji +├── Event/ ← uporabno, če uporabljate arhitekturo, vodeno z dogodki +├── Mail/ ← e-poštne predloge in povezana logika +└── Utils/ ← pomožni razredi +\-- + +Za deljene vizualne komponente, uporabljene v presenterjih po celotni aplikaciji, lahko uporabite mapo `app/Components` ali `app/Controls`: + +/--pre +app/Components/ +├── Form/ ← deljene komponente obrazcev +│ ├── SignInForm.php +│ └── UserForm.php +├── Grid/ ← komponente za izpise podatkov +│ └── DataGrid.php +└── Navigation/ ← navigacijski elementi + ├── Breadcrumbs.php + └── Menu.php +\-- + +Sem spadajo komponente, ki imajo kompleksnejšo logiko. Če želite komponente deliti med več projekti, je priporočljivo, da jih izločite v samostojen composer paket. + +V mapo `app/Mail` lahko namestite upravljanje e-poštne komunikacije: + +/--pre +app/Mail/ +├── templates/ ← e-poštne predloge +│ ├── order-confirmation.latte +│ └── welcome.latte +└── OrderMailer.php +\-- + + +Mapiranje presenterjev +====================== + +Mapiranje definira pravila za izpeljavo imena razreda iz imena presenterja. Specificiramo jih v [konfiguraciji|configuration] pod ključem `application › mapping`. + +Na tej strani smo si pokazali, da presenterje nameščamo v mapo `app/Presentation` (po potrebi `app/UI`). To konvencijo moramo Nette sporočiti v konfiguracijski datoteki. Dovolj je ena vrstica: + +```neon +application: + mapping: App\Presentation\*\**Presenter +``` + +Kako mapiranje deluje? Za boljše razumevanje si najprej predstavljajmo aplikacijo brez modulov. Želimo, da razredi presenterjev spadajo v imenski prostor `App\Presentation`, da se presenter `Home` preslika na razred `App\Presentation\HomePresenter`. Kar dosežemo s to konfiguracijo: + +```neon +application: + mapping: App\Presentation\*Presenter +``` + +Mapiranje deluje tako, da ime presenterja `Home` nadomesti zvezdico v maski `App\Presentation\*Presenter`, s čimer dobimo končno ime razreda `App\Presentation\HomePresenter`. Preprosto! + +Kot pa vidite v primerih v tem in drugih poglavjih, razrede presenterjev nameščamo v istoimenske podmape, na primer presenter `Home` se preslika na razred `App\Presentation\Home\HomePresenter`. To dosežemo z podvojitvijo dvopičja (zahteva Nette Application 3.2): + +```neon +application: + mapping: App\Presentation\**Presenter +``` + +Zdaj pristopimo k mapiranju presenterjev v module. Za vsak modul lahko definiramo specifično mapiranje: + +```neon +application: + mapping: + Front: App\Presentation\Front\**Presenter + Admin: App\Presentation\Admin\**Presenter + Api: App\Api\*Presenter +``` + +Glede na to konfiguracijo se presenter `Front:Home` preslika na razred `App\Presentation\Front\Home\HomePresenter`, medtem ko se presenter `Api:OAuth` na razred `App\Api\OAuthPresenter`. + +Ker imata modula `Front` in `Admin` podoben način mapiranja in takšnih modulov bo najverjetneje več, je mogoče ustvariti splošno pravilo, ki jih nadomesti. V masko razreda tako pride nova zvezdica za modul: + +```neon +application: + mapping: + *: App\Presentation\*\**Presenter + Api: App\Api\*Presenter +``` + +Deluje tudi za globlje ugnezdene strukture map, kot je na primer presenter `Admin:User:Edit`, se segment z zvezdico ponovi za vsako raven in rezultat je razred `App\Presentation\Admin\User\Edit\EditPresenter`. + +Alternativni zapis je namesto niza uporabiti polje, sestavljeno iz treh segmentov. Ta zapis je ekvivalenten prejšnjemu: + +```neon +application: + mapping: + *: [App\Presentation, *, **Presenter] + Api: [App\Api, '', *Presenter] +``` diff --git a/application/sl/how-it-works.texy b/application/sl/how-it-works.texy index ed6745881b..e472bbcea2 100644 --- a/application/sl/how-it-works.texy +++ b/application/sl/how-it-works.texy @@ -3,99 +3,101 @@ Kako delujejo aplikacije?
    -Trenutno berete osnovni dokument dokumentacije Nette. Spoznali boste vsa načela delovanja spletnih aplikacij. Lepo od A do Ž, od trenutka rojstva do zadnjega diha skripte PHP. Po branju boste vedeli: +Pravkar berete osnovno listino dokumentacije Nette. Spoznali boste celoten princip delovanja spletnih aplikacij. Lepo od A do Ž, od trenutka nastanka do zadnjega izdiha skripta PHP. Po branju boste vedeli: -- kako vse to deluje -- kaj so Bootstrap, Presenter in DI kontejner -- kako je videti struktura imenikov +- kako vse skupaj deluje +- kaj so Bootstrap, Presenter in DI vsebnik +- kako izgleda struktura map
    -Struktura imenika .[#toc-directory-structure] -============================================= +Struktura map +============= -Odprite skeletni primer spletne aplikacije z imenom [WebProject |https://github.com/nette/web-project] in si oglejte datoteke, o katerih se piše. +Odpri si primer ogrodja spletne aplikacije imenovane [WebProject|https://github.com/nette/web-project] in med branjem lahko gledaš datoteke, o katerih je govora. -Struktura imenikov je videti nekako takole: +Struktura map izgleda nekako takole: /--pre web-project/ -├── app/ ← directory with application -│ ├── Presenters/ ← presenter classes -│ │ ├── HomePresenter.php ← Home presenter class -│ │ └── templates/ ← templates directory -│ │ ├── @layout.latte ← template of shared layout -│ │ └── Home/ ← templates for Home presenter -│ │ └── default.latte ← template for action `default` -│ ├── Router/ ← configuration of URL addresses -│ └── Bootstrap.php ← booting class Bootstrap -├── bin/ ← scripts for the command line -├── config/ ← configuration files +├── app/ ← mapa z aplikacijo +│ ├── Core/ ← osnovni razredi, potrebni za delovanje +│ │ └── RouterFactory.php ← konfiguracija URL naslovov +│ ├── Presentation/ ← presenterji, predloge & co. +│ │ ├── @layout.latte ← predloga postavitve +│ │ └── Home/ ← mapa presenterja Home +│ │ ├── HomePresenter.php ← razred presenterja Home +│ │ └── default.latte ← predloga akcije default +│ └── Bootstrap.php ← zagonski razred Bootstrap +├── assets/ ← viri (SCSS, TypeScript, izvorne slike) +├── bin/ ← skripti, zagnani iz ukazne vrstice +├── config/ ← konfiguracijske datoteke │ ├── common.neon -│ └── local.neon -├── log/ ← error logs -├── temp/ ← temporary files, cache, … -├── vendor/ ← libraries installed by Composer +│ └── services.neon +├── log/ ← zabeležene napake +├── temp/ ← začasne datoteke, predpomnilnik, … +├── vendor/ ← knjižnice, nameščene s Composerjem │ ├── ... -│ └── autoload.php ← autoloading of libs installed by Composer -├── www/ ← public directory, document root of project -│ ├── .htaccess ← mod_rewrite rules etc -│ └── index.php ← initial file that launches the application -└── .htaccess ← prohibits access to all directories except www +│ └── autoload.php ← samodejno nalaganje vseh nameščenih paketov +├── www/ ← javna mapa ali document-root projekta +│ ├── assets/ ← sestavljene statične datoteke (CSS, JS, slike, ...) +│ ├── .htaccess ← pravila mod_rewrite +│ └── index.php ← začetna datoteka, s katero se aplikacija zažene +└── .htaccess ← prepoveduje dostop do vseh map razen www \-- -Lahko kakor koli spremenite imeniško strukturo, preimenujete ali premaknete mape, nato pa samo uredite poti do `log/` in `temp/` v datoteki `Bootstrap.php` ter pot do te datoteke v `composer.json` v razdelku `autoload`. Nič več, nobene zapletene ponovne konfiguracije, nobenih stalnih sprememb. Nette ima [pametno samodejno zaznavanje |bootstrap#development-vs-production-mode]. +Strukturo map lahko kakorkoli spreminjate, mape preimenujete ali premaknete, je popolnoma fleksibilna. Nette poleg tega razpolaga s pametnim samodejnim zaznavanjem in samodejno prepozna lokacijo aplikacije, vključno z njeno osnovno URL. -Za nekoliko večje aplikacije lahko mape s predvajalniki in predlogami razdelimo v podimenike (na disku) in v imenske prostore (v kodi), ki jih imenujemo [moduli |modules]. +Pri nekoliko večjih aplikacijah lahko mape s presenterji in predlogami [razčlenimo v podmape |directory-structure#Presenterji in predloge] in razrede v imenske prostore, ki jim rečemo moduli. -Imenik `www/` je javni imenik ali dokumentni koren projekta. Lahko ga preimenujete, ne da bi vam bilo treba na strani aplikacije nastaviti kar koli drugega. [Gostovanje |nette:troubleshooting#How to change or remove www directory from URL] morate le [konfigurirati |nette:troubleshooting#How to change or remove www directory from URL] tako, da bo korenina dokumentov prešla v ta imenik. +Mapa `www/` predstavlja t.i. javno mapo ali document-root projekta. Lahko jo preimenujete brez potrebe po kakršnemkoli dodatnem nastavljanju na strani aplikacije. Le potrebno je [konfigurirati gostovanje |nette:troubleshooting#Kako spremeniti ali odstraniti mapo www iz URL-ja] tako, da document-root kaže v to mapo. -WebProject lahko prenesete tudi neposredno, vključno z Nette, z uporabo [Composerja |best-practices:composer]: +WebProject si lahko tudi takoj prenesete vključno z Nette in to s pomočjo [Composerja |best-practices:composer]: ```shell composer create-project nette/web-project ``` -V operacijskem sistemu Linux ali macOS nastavite [dovoljenja za pisanje za |nette:troubleshooting#Setting directory permissions] imenike `log/` in `temp/`. +Na Linuxu ali macOS nastavite mapama `log/` in `temp/` [pravice za pisanje |nette:troubleshooting#Nastavitev pravic map]. -Aplikacija WebProject je pripravljena za zagon, ničesar več ni treba nastavljati, ogledate pa si jo lahko neposredno v brskalniku z dostopom do mape `www/`. +Aplikacija WebProject je pripravljena za zagon, ni treba ničesar konfigurirati in jo lahko takoj prikažete v brskalniku z dostopom do mape `www/`. -Zahteva HTTP .[#toc-http-request] -================================= +HTTP zahtevek +============= -Vse se začne, ko uporabnik odpre stran v brskalniku in brskalnik z zahtevo HTTP zaklepa strežnik. Zahteva gre v datoteko PHP, ki se nahaja v javnem imeniku `www/`, ki je `index.php`. Predpostavimo, da je to zahteva na naslov `https://example.com/product/123` in se bo izvršil. +Vse se začne v trenutku, ko uporabnik v brskalniku odpre stran. Torej, ko brskalnik potrka na strežnik s HTTP zahtevkom. Zahtevek cilja na eno samo PHP datoteko, ki se nahaja v javni mapi `www/`, in to je `index.php`. Recimo, da gre za zahtevek na naslov `https://example.com/product/123`. Zahvaljujoč primerni [nastavitvi strežnika |nette:troubleshooting#Kako nastaviti strežnik za lepe URL-je] se tudi ta URL preslika na datoteko `index.php` in ta se izvede. Njegova naloga je: -1) inicializacija okolja +1) inicializirati okolje 2) pridobiti tovarno -3) zagnati aplikacijo Nette, ki obravnava zahtevo +3) zagnati Nette aplikacijo, ki bo obdelala zahtevek -Katere vrste je tovarna? Ne proizvajamo traktorjev, temveč spletne strani! Počakajte, takoj vam bo razloženo. +Kakšno tovarno? Saj ne izdelujemo traktorjev, ampak spletne strani! Počakajte, takoj se bo pojasnilo. -Z "inicializacijo okolja" mislimo na primer na to, da se aktivira [Tracy, |tracy:] ki je izjemno orodje za beleženje ali vizualizacijo napak. Beleži napake v produkcijskem strežniku in jih prikazuje neposredno v razvojnem strežniku. Zato je treba pri inicializaciji odločiti tudi, ali spletno mesto deluje v produkcijskem ali razvojnem načinu. Za to Nette uporablja samodejno zaznavanje: če zaženete spletno mesto na lokalnem gostitelju, se zažene v razvijalskem načinu. Ničesar vam ni treba konfigurirati in aplikacija je pripravljena tako za razvojno kot produkcijsko namestitev. Ti koraki so izvedeni in podrobno opisani v poglavju o [razredu Bootstrap |bootstrap]. +Z besedami »inicializacija okolja« mislimo na primer to, da se aktivira [Tracy|tracy:], kar je čudovito orodje za beleženje ali vizualizacijo napak. Na produkcijskem strežniku napake beleži, na razvojnem jih takoj prikaže. Zato k inicializaciji spada tudi odločitev, ali spletno mesto teče v produkcijskem ali razvojnem načinu. Za to Nette uporablja [pametno samodejno zaznavanje |bootstrapping#Razvojni vs produkcijski način]: če spletno mesto zaženete na localhostu, teče v razvojnem načinu. Ni vam treba ničesar konfigurirati in aplikacija je takoj pripravljena tako za razvoj kot za ostro uporabo. Ti koraki se izvajajo in so podrobno opisani v poglavju o [razredu Bootstrap|bootstrapping]. -Tretja točka (da, drugo smo preskočili, vendar se bomo k njej vrnili) je zagon aplikacije. Obravnavo zahtevkov HTTP v Nette izvaja razred `Nette\Application\Application` (v nadaljevanju `Application`), zato ko rečemo "zagnati aplikacijo", imamo v mislih klic metode z imenom `run()` na objektu tega razreda. +Tretja točka (da, drugo smo preskočili, a se bomo vrnili k njej) je zagon aplikacije. Obdelavo HTTP zahtevkov ima v Nette na skrbi razred `Nette\Application\Application` (v nadaljevanju `Application`), zato ko rečemo zagnati aplikacijo, mislimo konkretno na klicanje metode s primernim imenom `run()` na objektu tega razreda. -Nette je mentor, ki vas vodi pri pisanju čistih aplikacij po preverjenih metodologijah. Najbolj preizkušena se imenuje **vbrizgavanje odvisnosti**, skrajšano DI. Trenutno vas ne želimo obremenjevati z razlago DI, saj je za to namenjeno [posebno poglavje |dependency-injection:introduction], pomembno pa je, da bodo ključni objekti običajno ustvarjeni s tovarno za objekte, imenovano **kontejner DI** (skrajšano DIC). Da, to je tista tovarna, ki smo jo omenili pred kratkim. Ta nam ustvari tudi objekt `Application`, zato najprej potrebujemo vsebnik. Dobimo ga z uporabo razreda `Configurator` in mu pustimo, da izdela objekt `Application`, pokličemo metodo `run()` in s tem zaženemo aplikacijo Nette. Natanko to se zgodi v datoteki [index.php |bootstrap#index.php]. +Nette je mentor, ki vas vodi k pisanju čistih aplikacij po preverjenih metodologijah. In ena od tistih popolnoma najbolj preverjenih se imenuje **dependency injection**, skrajšano DI. V tem trenutku vas ne želimo obremenjevati z razlago DI, za to je tu [ločeno poglavje|dependency-injection:introduction], bistven je posledica, da nam bo ključne objekte običajno ustvarjala tovarna objektov, ki se ji reče **DI vsebnik** (skrajšano DIC). Da, to je tista tovarna, o kateri je bila pred kratkim govora. In izdelala nam bo tudi objekt `Application`, zato potrebujemo najprej vsebnik. Pridobimo ga s pomočjo razreda `Configurator` in mu pustimo izdelati objekt `Application`, na njem pokličemo metodo `run()` in s tem se zažene Nette aplikacija. Točno to se dogaja v datoteki [index.php |bootstrapping#index.php]. -Aplikacija Nette .[#toc-nette-application] -========================================== +Nette Application +================= -Razred Application ima eno samo nalogo: odgovoriti na zahtevo HTTP. +Razred Application ima eno samo nalogo: odgovoriti na HTTP zahtevek. -Aplikacije, napisane v okolju Nette, so razdeljene na številne tako imenovane predstavnike (v drugih ogrodjih lahko naletite na izraz controller, ki je enak), ki so razredi, ki predstavljajo določeno spletno stran: npr. domača stran; izdelek v e-trgovini; prijavni obrazec; vir zemljevida spletnega mesta itd. Aplikacija ima lahko od enega do več tisoč predstavnikov. +Aplikacije, napisane v Nette, se členijo v veliko t.i. presenterjev (v drugih ogrodjih se lahko srečate z izrazom controller, gre za isto stvar), kar so razredi, od katerih vsak predstavlja neko konkretno stran spletnega mesta: npr. domačo stran; izdelek v spletni trgovini; prijavni obrazec; sitemap vir itd. Aplikacija lahko ima od enega do tisoč presenterjev. -Aplikacija se začne tako, da zahteva od tako imenovanega usmerjevalnika, da se odloči, kateremu od predstavnikov bo posredoval trenutno zahtevo v obdelavo. Usmerjevalnik se odloči, čigava je to odgovornost. Pregleda vhodni naslov URL `https://example.com/product/123`, ki želi `show` izdelek z `id: 123` kot dejanjem. Dobra navada je, da se pari predstavnik + akcija, ločeni z dvopičjem, zapišejo kot `Product:show`. +Application začne tako, da prosi t.i. usmerjevalnik (router), naj odloči, kateremu od presenterjev predati trenutni zahtevek v obdelavo. Usmerjevalnik odloči, čigava je to odgovornost. Pogleda vhodni URL `https://example.com/product/123` in na podlagi tega, kako je nastavljen, odloči, da je to delo npr. za **presenter** `Product`, od katerega bo želel kot **akcijo** prikaz (`show`) izdelka z `id: 123`. Par presenter + akcija je dobra navada zapisovati ločeno z dvopičjem kot `Product:show`. -Usmerjevalnik je torej pretvoril naslov URL v par `Presenter:action` + parametri, v našem primeru `Product:show` + `id: 123`. Kako je videti usmerjevalnik, si lahko ogledate v datoteki `app/Router/RouterFactory.php`, podrobno pa ga bomo opisali v poglavju [Usmerjanje |Routing]. +Torej je usmerjevalnik transformiral URL v par `Presenter:action` + parametri, v našem primeru `Product:show` + `id: 123`. Kako takšen usmerjevalnik izgleda, si lahko pogledate v datoteki `app/Core/RouterFactory.php` in ga podrobno opisujemo v poglavju [Usmerjanje |routing]. -Pojdimo naprej. Aplikacija že pozna ime predavatelja in lahko nadaljuje. Z ustvarjanjem predmeta `ProductPresenter`, ki je koda predstavnika `Product`. Natančneje, za ustvarjanje predstavnika zaprosi vsebnik DI, saj je izdelovanje objektov njegova naloga. +Pojdimo naprej. Application že pozna ime presenterja in lahko nadaljuje naprej. S tem, da izdela objekt razreda `ProductPresenter`, kar je koda presenterja `Product`. Natančneje rečeno, prosi DI vsebnik, naj presenter izdela, ker je za izdelovanje tu on. -Predstavnik je lahko videti takole: +Presenter lahko izgleda na primer takole: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -113,92 +115,86 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -Zahtevo obravnava predstavnik. In naloga je jasna: izvedite dejanje `show` s `id: 123`. Kar v jeziku predstavnikov pomeni, da se pokliče metoda `renderShow()` in v parametru `$id` dobi `123`. +Obdelavo zahtevka prevzame presenter. In naloga je jasna: izvedi akcijo `show` z `id: 123`. Kar v jeziku presenterjev pomeni, da se pokliče metoda `renderShow()` in v parametru `$id` dobi `123`. -Predstavnik lahko obravnava več dejanj, tj. ima več metod `render()`. Vendar priporočamo, da predstavnike oblikujete z eno ali čim manj akcijami. +Presenter lahko obravnava več akcij, torej ima več metod `render()`. Vendar priporočamo načrtovanje presenterjev z eno ali čim manj akcijami. -Tako je bila klicana metoda `renderShow(123)`, katere koda je izmišljen primer, vendar lahko na njej vidite, kako se podatki posredujejo predlogi, tj. z zapisom v `$this->template`. +Torej, poklicala se je metoda `renderShow(123)`, katere koda je sicer izmišljen primer, vendar lahko na njej vidite, kako se posredujejo podatki v predlogo, torej z zapisom v `$this->template`. -Nato predstavnik vrne odgovor. To je lahko stran HTML, slika, dokument XML, pošiljanje datoteke z diska, JSON ali preusmeritev na drugo stran. Pomembno je, da če izrecno ne navedemo, kako odgovoriti (kar je primer `ProductPresenter`), bo odgovor prikaz predloge s stranjo HTML. Zakaj? No, ker v 99 % primerov želimo izrisati predlogo, zato predstavnik to vedenje sprejme kot privzeto in nam želi olajšati delo. To je Nettejeva poanta. +Nato presenter vrne odgovor. Ta je lahko HTML stran, slika, XML dokument, pošiljanje datoteke z diska, JSON ali pa preusmeritev na drugo stran. Pomembno je, da če eksplicitno ne povemo, kako naj odgovori (kar je primer `ProductPresenter`), bo odgovor izris predloge s HTML stranjo. Zakaj? Ker v 99 % primerov želimo izrisati predlogo, zato presenter to obnašanje jemlje kot privzeto in nam želi olajšati delo. To je smisel Nette. -Ni nam treba niti navesti, katero predlogo želimo narisati, on pot do nje izpelje po preprosti logiki. V primeru predstavnika `Product` in akcije `show`, poskuša preveriti, ali obstaja ena od teh datotek s predlogami glede na imenik, v katerem se nahaja razred `ProductPresenter`: +Ni nam treba niti navajati, katero predlogo izrisati, pot do nje si izpelje sam. V primeru akcije `show` preprosto poskusi naložiti predlogo `show.latte` v mapi z razredom `ProductPresenter`. Prav tako poskusi poiskati postavitev v datoteki `@layout.latte` (podrobneje o [iskanju predlog |templates#Iskanje predlog]). -- `templates/Product/show.latte` -- `templates/Product.show.latte` - -Prav tako poskuša poiskati postavitev v datoteki `@layout.latte` in nato upodobi predlogo. Zdaj je naloga predstavnika in celotne aplikacije končana. Če predloga ne obstaja, se vrne stran z napako 404. Več o predstavitvah si lahko preberete na strani [Predstavitve |Presenters]. +In nato predloge izriše. S tem je naloga presenterja in celotne aplikacije končana in delo je zaključeno. Če predloga ne bi obstajala, se vrne stran z napako 404. Več o presenterjih preberite na strani [Presenterji |presenters]. [* request-flow.svg *] -Da bi se prepričali, poskusite celoten postopek ponoviti z nekoliko drugačnim naslovom URL: +Za vsak slučaj, poskusimo si ponoviti celoten proces z nekoliko drugačnim URL-jem: -1) naslov URL bo `https://example.com` -2) zaženemo aplikacijo, ustvarimo vsebnik in zaženemo `Application::run()` -3) usmerjevalnik dekodira naslov URL kot par `Home:default` -4) ustvari se objekt `HomePresenter` -5) kličemo metodo `renderDefault()` (če obstaja) -6) prikaže se predloga `templates/Home/default.latte` z razporeditvijo `templates/@layout.latte` +1) URL bo `https://example.com` +2) zaženemo aplikacijo, ustvari se vsebnik in zažene `Application::run()` +3) usmerjevalnik dekodira URL kot par `Home:default` +4) ustvari se objekt razreda `HomePresenter` +5) pokliče se metoda `renderDefault()` (če obstaja) +6) izriše se predloga npr. `default.latte` s postavitvijo npr. `@layout.latte` -Morda ste zdaj naleteli na veliko novih konceptov, vendar verjamemo, da so smiselni. Ustvarjanje aplikacij v programu Nette je zelo enostavno. +Morda ste se zdaj srečali z veliko novimi pojmi, vendar verjamemo, da imajo smisel. Ustvarjanje aplikacij v Nette je izjemno prijetno. -Predloge .[#toc-templates] -========================== +Predloge +======== -Pri predlogah Nette uporablja sistem predlog [Latte |latte:]. Zato se datoteke s predlogami končajo s `.latte`. Latte se uporablja, ker je najbolj varen sistem predlog za PHP in hkrati najbolj intuitiven sistem. Ni se vam treba naučiti veliko novega, poznati morate le PHP in nekaj oznak Latte. Vse boste našli v [dokumentaciji |latte:]. +Ko smo že pri predlogah, v Nette se uporablja sistem predlog [Latte |latte:]. Zato tudi končnice `.latte` pri predlogah. Latte se uporablja delsno zato, ker gre za najbolj varen sistem predlog za PHP, in hkrati tudi najbolj intuitiven sistem. Ni se vam treba učiti veliko novega, zadostuje znanje PHP in nekaj značk. Vse boste izvedeli [v dokumentaciji |templates]. -V predlogi [ustvarimo povezave do |creating-links] drugih predstavnikov in akcij, kot sledi: +V predlogi se [ustvarjajo povezave |creating-links] na druge presenterje & akcije takole: ```latte -product detail +podrobnosti izdelka ``` -Preprosto zapišite znani par `Presenter:action` namesto pravega URL in vključite vse parametre. Trik je `n:href`, ki pove, da bo ta atribut obdelal program Nette. In ustvaril bo: +Preprosto namesto realnega URL-ja napišete znani par `Presenter:action` in navedete morebitne parametre. Trik je v `n:href`, ki pravi, da ta atribut obdela Nette. In generira: ```latte -product detail +podrobnosti izdelka ``` -Za generiranje naslova URL je odgovoren prej omenjeni usmerjevalnik. Pravzaprav so usmerjevalniki v Nette edinstveni, saj lahko izvajajo ne le pretvorbe iz naslova URL v par predstavnik:dejanje, temveč tudi obratno, generirajo naslov URL iz imena predstavnika + dejanja + parametrov. -Zahvaljujoč temu lahko v Nette v celoti spremenite obliko URL v celotni dokončani aplikaciji, ne da bi spremenili en sam znak v predlogi ali predstavniku, samo s spremembo usmerjevalnika. -Zahvaljujoč temu pa deluje tudi tako imenovana kanonizacija, ki je še ena edinstvena lastnost sistema Nette, ki izboljša SEO (optimizacijo iskanja v internetu), saj samodejno preprečuje obstoj podvojene vsebine na različnih URL-jih. -Številnim programerjem se to zdi neverjetno. +Generiranje URL-jev ima na skrbi že prej omenjeni usmerjevalnik. Namreč usmerjevalniki v Nette so izjemni s tem, da znajo izvajati ne samo transformacije iz URL-ja v par presenter:action, ampak tudi obratno, torej iz imena presenterja + akcije + parametrov generirati URL. Zahvaljujoč temu lahko v Nette popolnoma spremenite oblike URL-jev v celotni končani aplikaciji, ne da bi spremenili en sam znak v predlogi ali presenterju. Samo s tem, da uredite usmerjevalnik. Prav tako zahvaljujoč temu deluje t.i. kanonizacija, kar je še ena edinstvena lastnost Nette, ki prispeva k boljšemu SEO (optimizaciji najdljivosti na internetu) s tem, da samodejno preprečuje obstoj podvojene vsebine na različnih URL-jih. Veliko programerjev to šteje za osupljivo. -Interaktivne komponente .[#toc-interactive-components] -====================================================== +Interaktivne komponente +======================= -O predstavitvah vam moramo povedati še nekaj: imajo vgrajen sistem komponent. Starejši med vami se morda spomnite nečesa podobnega iz Delphija ali ASP.NET Web Forms. React ali Vue.js sta zgrajena na nečem zelo podobnem. V svetu ogrodij PHP je to povsem edinstvena lastnost. +O presenterjih vam moramo povedati še eno stvar: imajo vgrajen komponentni sistem. Nekaj podobnega se lahko spomnijo veterani iz Delphi ali ASP.NET Web Forms, na nečem oddaljeno podobnem temeljita React ali Vue.js. V svetu PHP ogrodij gre za popolnoma edinstveno zadevo. -Komponente so ločene enote za večkratno uporabo, ki jih namestimo v strani (tj. predstavnike). To so lahko [obrazci |forms:in-presenter], [podatkovne mreže |https://componette.org/contributte/datagrid/], meniji, ankete, pravzaprav vse, kar je smiselno uporabiti večkrat. Ustvarimo lahko lastne komponente ali uporabimo katero od [številnih |https://componette.org] odprtokodnih komponent. +Komponente so samostojne ponovno uporabne celote, ki jih vstavljamo v strani (torej presenterje). Lahko so [obrazci |forms:in-presenter], [podatkovne mreže |https://componette.org/contributte/datagrid/], meniji, glasovalne ankete, pravzaprav karkoli, kar ima smisel uporabljati večkrat. Lahko ustvarjamo lastne komponente ali uporabljamo nekatere iz [ogromne ponudbe |https://componette.org] odprtokodnih komponent. -Komponente bistveno spremenijo pristop k razvoju aplikacij. Odprle bodo nove možnosti za sestavljanje strani iz vnaprej določenih enot. In imajo nekaj skupnega s [Hollywoodom |components#Hollywood style]. +Komponente bistveno vplivajo na pristop k ustvarjanju aplikacij. Odprle vam bodo nove možnosti sestavljanja strani iz vnaprej pripravljenih enot. In poleg tega imajo nekaj skupnega s [Hollywoodom |components#Hollywood style]. -Kontejner DI in konfiguracija .[#toc-di-container-and-configuration] -==================================================================== +DI vsebnik in konfiguracija +=========================== -Kontejner DI (tovarna predmetov) je srce celotne aplikacije. +DI vsebnik ali tovarna objektov je srce celotne aplikacije. -Ne skrbite, to ni magična črna škatla, kot se morda zdi iz prejšnjih besed. Pravzaprav gre za en precej dolgočasen razred PHP, ki ga ustvari Nette in je shranjen v imeniku predpomnilnika. Ima veliko metod, poimenovanih kot `createServiceAbcd()`, in vsaka od njih ustvari in vrne objekt. Da, obstaja tudi metoda `createServiceApplication()`, ki ustvari `Nette\Application\Application`, ki smo jo potrebovali v datoteki `index.php` za zagon aplikacije. Obstajajo pa tudi metode za izdelavo posameznih predstavnikov. In tako naprej. +Ne skrbite, ni to nobena čarobna črna škatla, kot bi se morda lahko zdelo iz prejšnjih vrstic. Pravzaprav je to ena precej dolgočasna PHP klasa, ki jo generira Nette in shrani v mapo s predpomnilnikom. Ima veliko metod, poimenovanih kot `createServiceAbcd()`, in vsaka od njih zna izdelati in vrniti nek objekt. Da, tam je tudi metoda `createServiceApplication()`, ki izdela `Nette\Application\Application`, ki smo ga potrebovali v datoteki `index.php` za zagon aplikacije. In tam so metode, ki izdelujejo posamezne presenterje. In tako naprej. -Predmeti, ki jih ustvarja vsebnik DI, se iz nekega razloga imenujejo storitve. +Objektom, ki jih DI vsebnik ustvarja, se iz nekega razloga reče storitve. -Posebnost tega razreda je, da ga ne programirate vi, temveč ogrodje. Pravzaprav ustvari kodo PHP in jo shrani na disk. Vi samo podate navodila o tem, katere predmete naj bi vsebnik znal ustvariti in kako natančno. Ta navodila so zapisana v [konfiguracijskih datotekah |bootstrap#DI Container Configuration] v [formatu NEON |neon:format] in imajo zato končnico `.neon`. +Kar je pri tem razredu resnično posebno, je to, da ga ne programirate vi, ampak ogrodje. On dejansko generira PHP kodo in jo shrani na disk. Vi samo dajete navodila, kakšne objekte naj zna vsebnik izdelovati in kako natančno. In ta navodila so zapisana v [konfiguracijskih datotekah |bootstrapping#Konfiguracija DI vsebnika], za katere se uporablja format [NEON|neon:format] in zato imajo tudi končnico `.neon`. -Konfiguracijske datoteke se uporabljajo izključno za dajanje navodil vsebniku DI. Tako bo na primer, če v razdelku [seje |http:configuration#Session] navedem možnost `expiration: 14 days`, vsebnik DI pri ustvarjanju objekta `Nette\Http\Session`, ki predstavlja sejo, poklical njegovo metodo `setExpiration('14 days')`, s čimer bo konfiguracija postala resničnost. +Konfiguracijske datoteke služijo izključno za navodila DI vsebnika. Torej, ko na primer navedem v sekciji [session |http:configuration#Seja] možnost `expiration: 14 days`, DI vsebnik pri ustvarjanju objekta `Nette\Http\Session`, ki predstavlja sejo, pokliče njegovo metodo `setExpiration('14 days')` in s tem konfiguracija postane resničnost. -Za vas je pripravljeno celotno poglavje, v katerem je opisano, kaj je mogoče [konfigurirati |nette:configuring] in kako [opredeliti lastne storitve |dependency-injection:services]. +Tu je za vas pripravljeno celo poglavje, ki opisuje, kaj vse je mogoče [konfigurirati |nette:configuring] in kako [definirati lastne storitve |dependency-injection:services]. -Ko se boste lotili ustvarjanja storitev, boste naleteli na besedo [samodejno povezovanje |dependency-injection:autowiring]. To je pripomoček, ki vam bo neverjetno olajšal življenje. Z njim lahko samodejno posredujete predmete, kjer jih potrebujete (na primer v konstruktorjih vaših razredov), ne da bi vam bilo treba kar koli storiti. Ugotovili boste, da je zabojnik DI v Netteu majhen čudež. +Ko se malo poglobite v ustvarjanje storitev, boste naleteli na besedo [autowiring |dependency-injection:autowiring]. To je iznajdba, ki vam bo na neverjeten način poenostavila življenje. Zna samodejno posredovati objekte tja, kjer jih potrebujete (na primer v konstruktorjih vaših razredov), ne da bi morali karkoli narediti. Ugotovili boste, da je DI vsebnik v Nette mali čudež. -Kaj naprej? .[#toc-what-next] -============================= +Kam naprej? +=========== -Pregledali smo osnovna načela aplikacij v programu Nette. Zaenkrat zelo površno, vendar se boste kmalu poglobili v globino in sčasoma ustvarili čudovite spletne aplikacije. Kje nadaljevati? Ste že poskusili z učbenikom [Ustvarite svojo prvo aplikacijo |quickstart:]? +Prešli smo osnovne principe aplikacij v Nette. Zaenkrat zelo površno, vendar boste kmalu prodrli v globino in sčasoma ustvarili čudovite spletne aplikacije. Kam nadaljevati? Ste že preizkusili vadnico [Pišemo prvo aplikacijo|quickstart:]? -Poleg zgoraj navedenega ima Nette še cel arzenal [uporabnih razredov |utils:], [sloj podatkovnih zbirk |database:] itd. Poskusite namenoma samo klikniti po dokumentaciji. Ali pa obiščite [blog |https://blog.nette.org]. Odkrili boste veliko zanimivih stvari. +Poleg zgoraj opisanega Nette razpolaga s celim arzenalom [uporabnih razredov|utils:], [podatkovno plastjo|database:], itd. Poskusite si samo tako preklikati dokumentacijo. Ali [blog|https://blog.nette.org]. Odkrili boste veliko zanimivega. Naj vam ogrodje prinese veliko veselja 💙 diff --git a/application/sl/modules.texy b/application/sl/modules.texy deleted file mode 100644 index d37ea342b1..0000000000 --- a/application/sl/modules.texy +++ /dev/null @@ -1,148 +0,0 @@ -Moduli -****** - -.[perex] -V Nette moduli predstavljajo logične enote, ki sestavljajo aplikacijo. Vključujejo predstavnike, predloge, lahko tudi komponente in razrede modelov. - -En imenik za predstavnike in en imenik za predloge za prave projekte ne bi bil dovolj. Če je v eni mapi na desetine datotek, je to vsaj neorganizirano. Kako to odpraviti? Preprosto jih razdelimo v podimenike na disku in v imenske prostore v kodi. In točno to naredijo moduli Nette. - -Pozabimo torej na eno mapo za predstavnike in predloge in namesto tega ustvarimo module, na primer `Admin` in `Front`. - -/--pre -app/ -├── Presenters/ -├── Modules/ ← directory with modules -│ ├── Admin/ ← module Admin -│ │ ├── Presenters/ ← its presenters -│ │ │ ├── DashboardPresenter.php -│ │ │ └── templates/ -│ └── Front/ ← module Front -│ └── Presenters/ ← its presenters -│ └── ... -\-- - -Ta struktura imenikov se bo odražala v imenskih prostorih razredov, tako da bo na primer `DashboardPresenter` v imenskem prostoru `App\Modules\Admin\Presenters`: - -```php -namespace App\Modules\Admin\Presenters; - -class DashboardPresenter extends Nette\Application\UI\Presenter -{ - // ... -} -``` - -Na predvajalnik `Dashboard` znotraj modula `Admin` se v aplikaciji sklicujemo z uporabo zapisa v dvopičju kot na `Admin:Dashboard`, na njegovo akcijo `default` pa kot na `Admin:Dashboard:default`. -In kako Nette pravilno ve, da `Admin:Dashboard` predstavlja razred `App\Modules\Admin\Presenters\DashboardPresenter`? To je določeno s [preslikavo |#mapping] v konfiguraciji. -Podana struktura torej ni trdno določena in jo lahko spreminjate glede na svoje potrebe. - -Moduli seveda lahko poleg predstavnikov in predlog vsebujejo tudi vse druge elemente, kot so komponente, razredi modelov itd. - - -Vgnezdeni moduli .[#toc-nested-modules] ---------------------------------------- - -Ni nujno, da moduli tvorijo le ravno strukturo, ustvarite lahko tudi podmodule, na primer: - -/--pre -app/ -├── Modules/ ← directory with modules -│ ├── Blog/ ← module Blog -│ │ ├── Admin/ ← submodule Admin -│ │ │ ├── Presenters/ -│ │ │ └── ... -│ │ └── Front/ ← submodule Front -│ │ ├── Presenters/ -│ │ └── ... -│ ├── Forum/ ← module Forum -│ │ └── ... -\-- - -Tako je modul `Blog` razdeljen na podmodula `Admin` in `Front`. Tudi to se bo odražalo v imenskih prostorih, ki bodo `App\Modules\Blog\Admin\Presenters` itd. Predstavnik `Dashboard` znotraj podmodula se imenuje `Blog:Admin:Dashboard`. - -Gnezdenje je lahko tako globoko, kot želite, zato lahko ustvarite podmodule. - - -Ustvarjanje povezav .[#toc-creating-links] ------------------------------------------- - -Povezave v predlogah za predstavitev so relativne glede na trenutni modul. Tako povezava `Foo:default` vodi do predstavitvene predloge `Foo` v istem modulu kot trenutna predstavitvena predloga. Če je trenutni modul na primer `Front`, potem povezava poteka takole: - -```latte -link to Front:Product:show -``` - -Povezava je relativna tudi, če vključuje ime modula, ki se potem šteje za podmodul: - -```latte -link to Front:Shop:Product:show -``` - -Absolutne povezave se zapišejo podobno kot absolutne poti na disku, vendar s podpičjem namesto šumnikov. Tako se absolutna povezava začne z dvopičjem: - -```latte -link to Admin:Product:show -``` - -Če želimo ugotoviti, ali smo v določenem modulu ali njegovem podmodulu, lahko uporabimo funkcijo `isModuleCurrent(moduleName)`. - -```latte -
  • - ... -
  • -``` - - -Usmerjanje .[#toc-routing] --------------------------- - -Glejte [poglavje o usmerjanju |routing#Modules]. - - -Kartiranje .[#toc-mapping] --------------------------- - -Določa pravila, po katerih se ime razreda izpelje iz imena predstavnika. Zapišemo jih v [konfiguracijo |configuration] pod ključ `application › mapping`. - -Začnimo z vzorcem, ki ne uporablja modulov. Želeli bomo le, da imajo razredi predstavnikov imenski prostor `App\Presenters`. To pomeni, da mora biti predstavnik, kot je `Home`, preslikan v razred `App\Presenters\HomePresenter`. To lahko dosežemo z naslednjo konfiguracijo: - -```neon -application: - mapping: - *: App\Presenters\*Presenter -``` - -V maski razreda se ime predvajalnika nadomesti z zvezdico, rezultat pa je ime razreda. Enostavno! - -Če voditelje razdelimo na module, lahko za vsak modul pripravimo lastno preslikavo: - -```neon -application: - mapping: - Front: App\Modules\Front\Presenters\*Presenter - Admin: App\Modules\Admin\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -Sedaj je predstavnik `Front:Home` preslikan v razred `App\Modules\Front\Presenters\HomePresenter`, predstavnik `Admin:Dashboard` pa v razred `App\Modules\Admin\Presenters\DashboardPresenter`. - -Bolj praktično je ustvariti splošno (zvezdno) pravilo, ki bo nadomestilo prvi dve. Dodatna zvezdica bo dodana maski razreda samo za ta modul: - -```neon -application: - mapping: - *: App\Modules\*\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -Kaj pa, če uporabljamo vgnezdene module in imamo predvajalnik `Admin:User:Edit`? V tem primeru se segment z zvezdico, ki predstavlja modul za vsako raven, preprosto ponovi in rezultat je razred `App\Modules\Admin\User\Presenters\EditPresenter`. - -Alternativni zapis je, da namesto niza uporabimo polje, sestavljeno iz treh segmentov. Ta zapis je enakovreden prejšnjemu: - -```neon -application: - mapping: - *: [App\Modules, *, Presenters\*Presenter] -``` - -Privzeta vrednost je `*: *Module\*Presenter`. diff --git a/application/sl/multiplier.texy b/application/sl/multiplier.texy index cca2d67b9b..90d9b2588d 100644 --- a/application/sl/multiplier.texy +++ b/application/sl/multiplier.texy @@ -1,30 +1,32 @@ -Multiplikator: Dinamične komponente -*********************************** +Multiplier: dinamične komponente +******************************** -Orodje za dinamično ustvarjanje interaktivnih komponent .[perex] +.[perex] +Orodje za dinamično ustvarjanje interaktivnih komponent -Začnimo s tipičnim problemom: imamo seznam izdelkov na spletni strani e-trgovine in vsak izdelek želimo opremiti z obrazcem *Dodaj v košarico*. Eden od načinov je, da celoten seznam zapakiramo v en sam obrazec. Priročnejši način je uporaba [api:Nette\Application\UI\Multiplier]. +Izhajajmo iz tipičnega primera: imejmo seznam blaga v spletni trgovini, pri čemer bomo pri vsakem želeli izpisati obrazec za dodajanje blaga v košarico. Ena od možnih variant je oviti celoten izpis v en obrazec. Veliko udobnejši način pa nam ponuja [api:Nette\Application\UI\Multiplier]. -Funkcija Multiplier vam omogoča, da določite tovarno za več sestavnih delov. Temelji na načelu vgnezdenih komponent - vsaka komponenta, ki podeduje od [api:Nette\ComponentModel\Container], lahko vsebuje druge komponente. +Multiplier omogoča udobno definiranje tovarniške metode za več komponent. Deluje na principu ugnezdenih komponent - vsaka komponenta, ki deduje od [api:Nette\ComponentModel\Container], lahko vsebuje druge komponente. -Oglejte si [model komponent |components#Components in Depth] v dokumentaciji. .[tip] +.[tip] +Glej poglavje o [komponentnem modelu |components#Komponente v globino] v dokumentaciji ali [predavanje Honze Tvrdíka|https://www.youtube.com/watch?v=8y3LLexWu-I]. -Multiplier nastopa kot starševska komponenta, ki lahko dinamično ustvari svoje otroke z uporabo povratnega klica, posredovanega v konstruktorju. Oglejte si primer: +Bistvo Multiplierja je, da nastopa v vlogi starša, ki si svoje potomce lahko ustvarja dinamično s pomočjo povratnega klica (callback), predanega v konstruktorju. Glej primer: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function () { $form = new Nette\Application\UI\Form; - $form->addInteger('amount', 'Amount:') + $form->addInteger('count', 'Število kosov:') ->setRequired(); - $form->addSubmit('send', 'Add to cart'); + $form->addSubmit('send', 'Dodaj v košarico'); return $form; }); } ``` -V predlogi lahko prikažemo obrazec za vsak izdelek - in vsak obrazec bo dejansko edinstvena komponenta. +Zdaj lahko v predlogi enostavno pri vsakem blagu pustimo izrisati obrazec - in vsak bo resnično edinstvena komponenta. ```latte {foreach $items as $item} @@ -35,26 +37,26 @@ V predlogi lahko prikažemo obrazec za vsak izdelek - in vsak obrazec bo dejansk {/foreach} ``` -Argument, posredovan oznaki `{control}`, pravi: +Argument, predan v znački `{control}`, je v formatu, ki pravi: 1. pridobi komponento `shopForm` -2. in vrni njenega otroka `$item->id` +2. in iz nje pridobi potomca `$item->id` -Med prvim klicem **1.** komponenta `shopForm` še ne obstaja, zato se za njeno ustvarjanje pokliče metoda `createComponentShopForm`. Nato se pokliče anonimna funkcija, ki je Multiplierju posredovana kot parameter, in ustvari se obrazec. +Pri prvem klicu točke **1.** `shopForm` še ne obstaja, zato se pokliče njegova tovarna `createComponentShopForm`. Na pridobljeni komponenti (instanci Multiplierja) je nato poklicana tovarna konkretnega obrazca - kar je anonimna funkcija, ki smo jo Multiplierju v konstruktorju predali. -V naslednjih iteracijah `foreach` se metoda `createComponentShopForm` ne kliče več, ker komponenta že obstaja. Ker pa se sklicujemo na drugega otroka (`$item->id` se med iteracijami spreminja), se ponovno pokliče anonimna funkcija in ustvari se nov obrazec. +V naslednji iteraciji foreacha metoda `createComponentShopForm` ne bo več klicana (komponenta obstaja), ker pa iščemo njenega drugega potomca (`$item->id` bo v vsaki iteraciji drugačen), bo ponovno poklicana anonimna funkcija in nam vrnila nov obrazec. -Zadnja stvar je zagotoviti, da obrazec dejansko doda pravi izdelek v košarico, saj so v trenutnem stanju vsi obrazci enaki in ne moremo razlikovati, katerim izdelkom pripadajo. Za to lahko uporabimo lastnost Multiplierja (in na splošno vsake metode tovarne komponent v Nette Framework), da vsaka metoda tovarne komponent kot prvi argument prejme ime ustvarjene komponente. V našem primeru je to `$item->id`, kar je natanko tisto, kar potrebujemo za razlikovanje posameznih izdelkov. Vse, kar morate storiti, je, da spremenite kodo za ustvarjanje obrazca: +Edino, kar preostane, je zagotoviti, da nam obrazec v košarico doda resnično tisto blago, ki ga mora - trenutno je obrazec pri vsakem blagu popolnoma enak. Pomagala nam bo lastnost Multiplierja (in na splošno vsake tovarne na komponento v Nette Frameworku), in sicer ta, da vsaka tovarna kot svoj prvi argument dobi ime tvořené komponenty. V našem primeru bo to `$item->id`, kar je točno tisti podatek, ki ga potrebujemo. Dovolj je torej rahlo prilagoditi tvorbu obrazca: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function ($itemId) { $form = new Nette\Application\UI\Form; - $form->addInteger('amount', 'Amount:') + $form->addInteger('count', 'Število kosov:') ->setRequired(); $form->addHidden('itemId', $itemId); - $form->addSubmit('send', 'Add to cart'); + $form->addSubmit('send', 'Dodaj v košarico'); return $form; }); } diff --git a/application/sl/presenters.texy b/application/sl/presenters.texy index 3096521e5c..aea7c3d715 100644 --- a/application/sl/presenters.texy +++ b/application/sl/presenters.texy @@ -1,39 +1,39 @@ -Predavatelji -************ +Presenterji +***********
    -Naučili se bomo pisati predstavnike in predloge v Nette. Po branju boste vedeli: +Spoznali bomo, kako se v Nette pišejo presenterji in predloge. Po branju boste vedeli: -- kako deluje predstavnik -- kaj so trajni parametri -- kako izrisati predlogo +- kako deluje presenter +- kaj so persistentni parametri +- kako se rišejo predloge
    -[Vemo že, |how-it-works#nette-application] da je predstavnik razred, ki predstavlja določeno stran spletne aplikacije, na primer domačo stran, izdelek v e-trgovini, obrazec za prijavo, vir zemljevida spletne strani itd. Aplikacija ima lahko od enega do več tisoč predstavnikov. V drugih ogrodjih so znani tudi kot krmilniki. +[Že vemo |how-it-works#Nette Application], da je presenter razred, ki predstavlja neko konkretno stran spletne aplikacije, npr. domačo stran; izdelek v spletni trgovini; prijavni obrazec; sitemap vir itd. Aplikacija lahko ima od enega do tisoč presenterjev. V drugih ogrodjih jim rečejo tudi kontrolerji. -Običajno se izraz presenter nanaša na potomca razreda [api:Nette\Application\UI\Presenter], ki je primeren za spletne vmesnike in ga bomo obravnavali v nadaljevanju tega poglavja. V splošnem smislu je predstavnik vsak objekt, ki implementira vmesnik [api:Nette\Application\IPresenter]. +Običajno se pod pojmom presenter misli na potomca razreda [api:Nette\Application\UI\Presenter], ki je primeren za generiranje spletnih vmesnikov in kateremu se bomo posvetili v preostanku tega poglavja. V splošnem smislu je presenter katerikoli objekt, ki implementira vmesnik [api:Nette\Application\IPresenter]. -Življenjski cikel predstavnika .[#toc-life-cycle-of-presenter] -============================================================== +Življenjski cikel presenterja +============================= -Naloga predstavnika je obdelati zahtevo in vrniti odgovor (ki je lahko stran HTML, slika, preusmeritev itd.). +Naloga presenterja je obdelati zahtevek in vrniti odgovor (kar je lahko HTML stran, slika, preusmeritev itd.). -Na začetku je torej zahteva. To ni neposredno zahteva HTTP, temveč predmet [api:Nette\Application\Request], v katerega je bila zahteva HTTP pretvorjena s pomočjo usmerjevalnika. S tem objektom običajno ne pridemo v stik, saj predstavnik obdelavo zahteve spretno prenese na posebne metode, ki si jih bomo zdaj ogledali. +Torej na začetku mu je predan zahtevek. To ni neposredno HTTP zahtevek, ampak objekt [api:Nette\Application\Request], v katerega je bil HTTP zahtevek preoblikovan s pomočjo usmerjevalnika. S tem objektom običajno ne pridemo v stik, saj presenter obdelavo zahtevka pametno delegira v druge metode, ki si jih bomo zdaj pokazali. -[* lifecycle.svg *] ***Življenjski cikel predstavnika* .<> +[* lifecycle.svg *] *** *Življenjski cikel presenterja* .<> -Na sliki je prikazan seznam metod, ki se kličejo zaporedno od zgoraj navzdol, če obstajajo. Nobeni od njih ni treba obstajati, lahko imamo popolnoma prazen predstavnik brez ene same metode in na njem zgradimo preprost statični splet. +Slika predstavlja seznam metod, ki se postopoma od zgoraj navzdol kličejo, če obstajajo. Nobena od njih ni nujno, da obstaja, lahko imamo popolnoma prazen presenter brez ene same metode in na njem zgradimo preprosto statično spletno stran. `__construct()` --------------- -Konstruktor ne sodi ravno v življenjski cikel predstavnika, saj se pokliče v trenutku ustvarjanja predmeta. Vendar ga omenjamo zaradi njegove pomembnosti. Konstruktor (skupaj z [metodo inject |best-practices:inject-method-attribute]) se uporablja za posredovanje odvisnosti. +Konstruktor ne spada povsem v življenjski cikel presenterja, ker se kliče v trenutku ustvarjanja objekta. Vendar ga navajamo zaradi pomembnosti. Konstruktor (skupaj z [metodo inject|best-practices:inject-method-attribute]) služi za posredovanje odvisnosti. -Predstavnik ne sme skrbeti za poslovno logiko aplikacije, pisati in brati iz podatkovne zbirke, izvajati izračunov itd. To je naloga za razrede iz plasti, ki jo imenujemo model. Na primer, razred `ArticleRepository` je lahko odgovoren za nalaganje in shranjevanje člankov. Da bi ga lahko predstavnik uporabljal, ga [posredujemo z uporabo vbrizgavanja odvisnosti |dependency-injection:passing-dependencies]: +Presenter ne bi smel opravljati poslovne logike aplikacije, pisati in brati iz podatkovne baze, izvajati izračunov itd. Za to so razredi iz plasti, ki jo označujemo kot model. Na primer, razred `ArticleRepository` lahko skrbi za nalaganje in shranjevanje člankov. Da bi lahko presenter z njim delal, si ga pusti [posredovati s pomočjo dependency injection |dependency-injection:passing-dependencies]: ```php @@ -50,39 +50,39 @@ class ArticlePresenter extends Nette\Application\UI\Presenter `startup()` ----------- -Takoj po prejemu zahteve se sproži metoda `startup ()`. Uporabite jo lahko za inicializacijo lastnosti, preverjanje uporabniških pravic itd. Vedno je treba poklicati prednika `parent::startup()`. +Takoj po prejemu zahtevka se pokliče metoda `startup()`. Lahko jo uporabite za inicializacijo lastnosti, preverjanje uporabniških dovoljenj itd. Zahtevano je, da metoda vedno pokliče prednika `parent::startup()`. `action(args...)` .{toc: action()} -------------------------------------------------- -Podobno kot pri metodi `render()`. Medtem ko `render()` je namenjena pripravi podatkov za določeno predlogo, ki se nato izriše, v `action()` se zahteva obdela brez naknadnega upodabljanja predloge. Tako se na primer obdelajo podatki, uporabnik se prijavi ali odjavi in podobno, nato pa [se preusmeri drugam |#Redirection]. +Podobno metodi `render()`. Medtem ko je `render()` namenjena pripravi podatkov za konkretno predlogo, ki se nato izriše, se v `action()` obdeluje zahtevek brez povezave z izrisovanjem predloge. Na primer, obdelajo se podatki, prijavi ali odjavi uporabnik, in tako naprej, nato pa [preusmeri drugam |#Preusmerjanje]. -Pomembno je, da `action()` se pokliče pred `render()`, tako da lahko znotraj njega morebiti spremenimo naslednji potek življenjskega cikla, tj. spremenimo predlogo, ki se bo izrisala, in tudi metodo `render()` ki bo poklicana, z uporabo `setView('otherView')`. +Pomembno je, da se `action()` kliče prej kot `render()`, tako da lahko v njej morebiti spremenimo nadaljnji potek dogodkov, tj. spremenimo predlogo, ki se bo risala, in tudi metodo `render()`, ki se bo klicala. In to s pomočjo `setView('jineView')`. -Parametri iz zahteve se posredujejo metodi. Za parametre je mogoče in priporočljivo določiti tipe, npr. `actionShow(int $id, string $slug = null)` - če parameter `id` manjka ali če ni celo število, predstavnik vrne [napako 404 |#Error 404 etc.] in zaključi operacijo. +Metodi se posredujejo parametri iz zahtevka. Možno in priporočljivo je navesti tipe parametrov, npr. `actionShow(int $id, ?string $slug = null)` - če bo parameter `id` manjkal ali če ne bo integer, bo presenter vrnil [napako 404 |#Napaka 404 in podobno] in zaključil delovanje. `handle(args...)` .{toc: handle()} -------------------------------------------------- -Ta metoda obdeluje tako imenovane signale, ki jih bomo obravnavali v poglavju o [komponentah |components#Signal]. Namenjena je predvsem komponentam in obdelavi zahtevkov AJAX. +Metoda obdeluje t.i. signale, s katerimi se bomo seznanili v poglavju, posvečenem [komponentam |components#Signal]. Namenjena je namreč predvsem komponentam in obdelavi AJAX zahtevkov. -Metodi se posredujejo parametri, tako kot v primeru `action()`, vključno s preverjanjem tipa. +Metodi se posredujejo parametri iz zahtevka, kot v primeru `action()`, vključno s tipsko kontrolo. `beforeRender()` ---------------- -Metoda `beforeRender`, kot pove že ime, se pokliče pred vsako metodo `render()`. Uporablja se za skupno konfiguracijo predloge, posredovanje spremenljivk za postavitev in tako naprej. +Metoda `beforeRender`, kot že ime pove, se kliče pred vsako metodo `render()`. Uporablja se za skupno konfiguracijo predloge, posredovanje spremenljivk za postavitev in podobno. `render(args...)` .{toc: render()} ---------------------------------------------- -Mesto, kjer pripravimo predlogo za nadaljnje upodabljanje, ji posredujemo podatke itd. +Mesto, kjer pripravljamo predlogo za nadaljnje izrisovanje, ji posredujemo podatke itd. -Parametri se posredujejo metodi, kot v primeru `action()`, vključno s preverjanjem tipa. +Metodi se posredujejo parametri iz zahtevka, kot v primeru `action()`, vključno s tipsko kontrolo. ```php public function renderShow(int $id): void @@ -96,104 +96,104 @@ public function renderShow(int $id): void `afterRender()` --------------- -Metoda `afterRender`, kot je razvidno iz imena, se pokliče po vsakem `render()` metodi. Uporablja se precej redko. +Metoda `afterRender`, kot ime spet pove, se kliče za vsako metodo `render()`. Uporablja se bolj izjemoma. `shutdown()` ------------ -Pokliče se na koncu življenjskega cikla predstavnika. +Kliče se na koncu življenjskega cikla presenterja. -**Dober nasvet, preden se premaknemo naprej**. Kot lahko vidite, lahko predstavnik obdeluje več akcij/ogledov, tj. ima več metod `render()`. Vendar priporočamo, da predstavnike oblikujete z enim dejanjem ali čim manjšim številom dejanj. +**Dober nasvet, preden gremo naprej**. Presenter, kot je vidno, lahko obravnava več akcij/view, torej ima več metod `render()`. Vendar priporočamo načrtovanje presenterjev z eno ali čim manj akcijami. -Pošiljanje odziva .[#toc-sending-a-response] -============================================ +Pošiljanje odgovora +=================== -Odziv predstavnika je običajno [prikaz predloge s stranjo HTML |templates], lahko pa je tudi pošiljanje datoteke, JSON ali celo preusmeritev na drugo stran. +Odgovor presenterja je praviloma [izris predloge s HTML stranjo|templates], lahko pa je tudi pošiljanje datoteke, JSON ali pa preusmeritev na drugo stran. -Kadar koli med življenjskim ciklom lahko uporabite katero koli od naslednjih metod za pošiljanje odziva in hkratni izhod iz predstavitvenega programa: +Kadarkoli med življenjskim ciklom lahko z eno od naslednjih metod pošljemo odgovor in hkrati zaključimo presenter: -- `redirect()`, `redirectPermanent()`, `redirectUrl()` in `forward()` [preusmeritve |#Redirection] -- `error()` [zaradi napake |#Error 404 etc.] zaključi predstavitveni program -- `sendJson($data)` zapusti predstavitveno orodje in [pošlje podatke |#Sending JSON] v obliki JSON -- `sendTemplate()` zapusti predstavitveno orodje in takoj [izriše predlogo |templates]. -- `sendResponse($response)` zaključi predstavitev in pošlje [lasten odgovor |#Responses] -- `terminate()` brez odgovora zapusti predstavitveni program +- `redirect()`, `redirectPermanent()`, `redirectUrl()` in `forward()` [preusmeri |#Preusmerjanje] +- `error()` zaključi presenter [zaradi napake |#Napaka 404 in podobno] +- `sendJson($data)` presenter zaključi in [pošlje podatke |#Pošiljanje JSON] v formatu JSON +- `sendTemplate()` presenter zaključi in takoj [izriše predlogo |templates] +- `sendResponse($response)` presenter zaključi in pošlje [lastni odgovor |#Odgovori] +- `terminate()` presenter zaključi brez odgovora -Če ne pokličete nobene od teh metod, bo predstavitveni program samodejno nadaljeval z upodabljanjem predloge. Zakaj? No, ker v 99 % primerov želimo izrisati predlogo, zato predstavnik to obnašanje vzame kot privzeto in nam želi olajšati delo. +Če nobene od teh metod ne pokličete, bo presenter samodejno pristopil k izrisu predloge. Zakaj? Ker v 99 % primerov želimo izrisati predlogo, zato presenter to obnašanje jemlje kot privzeto in nam želi olajšati delo. -Ustvarjanje povezav .[#toc-creating-links] -========================================== +Ustvarjanje povezav +=================== -Presenter ima metodo `link()`, ki se uporablja za ustvarjanje povezav URL do drugih presenterjev. Prvi parameter je ciljni predstavnik in dejanje, sledijo pa mu argumenti, ki so lahko posredovani kot polje: +Presenter razpolaga z metodo `link()`, s pomočjo katere lahko ustvarjamo URL povezave na druge presenterje. Prvi parameter je ciljni presenter & akcija, sledijo posredovani argumenti, ki so lahko navedeni kot polje: ```php $url = $this->link('Product:show', $id); -$url = $this->link('Product:show', [$id, 'lang' => 'en']); +$url = $this->link('Product:show', [$id, 'lang' => 'sl']); ``` -V predlogi ustvarimo povezave do drugih predstavnikov in akcij na naslednji način: +V predlogi se ustvarjajo povezave na druge presenterje & akcije na ta način: ```latte -product detail +podrobnosti izdelka ``` -Preprosto zapišite znani par `Presenter:action` namesto pravega URL in vključite vse parametre. Trik je `n:href`, ki pove, da bo ta atribut obdelal Latte in ustvaril pravi URL. V programu Nette vam sploh ni treba razmišljati o naslovih URL, temveč le o predstavnikih in akcijah. +Preprosto namesto realnega URL-ja napišete znani par `Presenter:action` in navedete morebitne parametre. Trik je v `n:href`, ki pravi, da ta atribut obdela Latte in generira realni URL. V Nette tako sploh ni treba razmišljati o URL-jih, samo o presenterjih in akcijah. -Za več informacij glejte [Ustvarjanje povezav |Creating Links]. +Več informacij najdete v poglavju [Ustvarjanje URL povezav |creating-links]. -Preusmeritev .[#toc-redirection] -================================ +Preusmerjanje +============= -Metodi `redirect()` in `forward()` se uporabljata za preskok na drug predvajalnik in imata zelo podobno sintakso kot metoda [link() |#Creating Links]. +Za prehod na drug presenter služita metodi `redirect()` in `forward()`, ki imata zelo podobno sintakso kot metoda [link() |#Ustvarjanje povezav]. -Metoda `forward()` takoj preklopi na novi predstavnik brez preusmeritve HTTP: +Metoda `forward()` preide na nov presenter takoj brez HTTP preusmeritve: ```php $this->forward('Product:show'); ``` -Primer začasne preusmeritve s kodo HTTP 302 ali 303: +Primer t.i. začasne preusmeritve s HTTP kodo 302 (ali 303, če je metoda trenutnega zahtevka POST): ```php $this->redirect('Product:show', $id); ``` -Za trajno preusmeritev s kodo HTTP 301 uporabite: +Trajno preusmeritev s HTTP kodo 301 dosežete takole: ```php $this->redirectPermanent('Product:show', $id); ``` -Z metodo `redirectUrl()` lahko preusmerite na drug URL zunaj aplikacije: +Na drug URL izven aplikacije lahko preusmerite z metodo `redirectUrl()`. Kot drugi parameter lahko navedete HTTP kodo, privzeta je 302 (ali 303, če je metoda trenutnega zahtevka POST): ```php $this->redirectUrl('https://nette.org'); ``` -Preusmeritev nemudoma prekine življenjski cikel predvajalnika, tako da vrže tako imenovano izjemo tihega zaključka `Nette\Application\AbortException`. +Preusmeritev takoj zaključi delovanje presenterja s sprožitvijo t.i. tihe zaključne izjeme `Nette\Application\AbortException`. -Pred preusmeritvijo je mogoče poslati [bliskovito sporočilo |#Flash Messages], sporočila, ki bodo prikazana v predlogi po preusmeritvi. +Pred preusmeritvijo lahko pošljete [flash message |#Flash sporočila], torej sporočila, ki bodo po preusmeritvi prikazana v predlogi. -Bliskovita sporočila .[#toc-flash-messages] -=========================================== +Flash sporočila +=============== -To so sporočila, ki običajno obveščajo o rezultatu operacije. Pomembna lastnost bliskovitih sporočil je, da so v predlogi na voljo tudi po preusmeritvi. Tudi po prikazu bodo ostala živa še 30 sekund - na primer, če bi uporabnik nenamerno osvežil stran - sporočilo se ne bo izgubilo. +Gre za sporočila, ki običajno obveščajo o rezultatu neke operacije. Pomembna značilnost flash sporočil je, da so v predlogi na voljo tudi po preusmeritvi. Tudi po prikazu ostanejo živa še nadaljnjih 30 sekund – na primer za primer, če bi zaradi napačnega prenosa uporabnik osvežil stran - sporočilo mu torej ne izgine takoj. -Samo pokličite metodo [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] in presenter bo poskrbel za posredovanje sporočila predlogi. Prvi argument je besedilo sporočila, drugi neobvezni argument pa je njegova vrsta (napaka, opozorilo, informacija itd.). Metoda `flashMessage()` vrne primerek sporočila flash, da lahko dodamo več informacij. +Dovolj je poklicati metodo [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] in za posredovanje v predlogo poskrbi presenter. Prvi parameter je besedilo sporočila in neobvezni drugi parameter je njegov tip (error, warning, info ipd.). Metoda `flashMessage()` vrne instanco flash sporočila, kateremu je mogoče dodajati dodatne informacije. ```php -$this->flashMessage('Item was removed.'); -$this->redirect(/* ... */); +$this->flashMessage('Element je bil izbrisan.'); +$this->redirect(/* ... */); // in preusmerimo ``` -V predlogi so ta sporočila na voljo v spremenljivki `$flashes` kot objekti `stdClass`, ki vsebujejo lastnosti `message` (besedilo sporočila), `type` (vrsta sporočila) in lahko vsebujejo že omenjene podatke o uporabniku. Narišemo jih na naslednji način: +Predlogi so ta sporočila na voljo v spremenljivki `$flashes` kot objekti `stdClass`, ki vsebujejo lastnosti `message` (besedilo sporočila), `type` (tip sporočila) in lahko vsebujejo že omenjene uporabniške informacije. Izrišemo jih na primer takole: ```latte {foreach $flashes as $flash} @@ -202,10 +202,10 @@ V predlogi so ta sporočila na voljo v spremenljivki `$flashes` kot objekti `std ``` -Napaka 404 itd. .[#toc-error-404-etc] -===================================== +Napaka 404 in podobno +===================== -Kadar ne moremo izpolniti zahteve, ker na primer članek, ki ga želimo prikazati, ne obstaja v zbirki podatkov, bomo z metodo `error(string $message = null, int $httpCode = 404)`, ki predstavlja napako HTTP 404, vrgli napako 404: +Če zahteve ni mogoče izpolniti, na primer zato, ker članek, ki ga želimo prikazati, ne obstaja v podatkovni bazi, sprožimo napako 404 z metodo `error(?string $message = null, int $httpCode = 404)`. ```php public function renderShow(int $id): void @@ -218,14 +218,13 @@ public function renderShow(int $id): void } ``` -Koda napake HTTP se lahko posreduje kot drugi parameter, privzeta vrednost je 404. Metoda deluje tako, da vrže izjemo `Nette\Application\BadRequestException`, nakar `Application` preda nadzor predstavniku napake. To je predstavnik, katerega naloga je prikazati stran, ki obvešča o napaki. -Predvajalnik napak je nastavljen v [konfiguraciji aplikacije |configuration]. +HTTP kodo napake lahko predamo kot drugi parameter, privzeta je 404. Metoda deluje tako, da sproži izjemo `Nette\Application\BadRequestException`, nato pa `Application` preda nadzor error-presenterju. Kar je presenter, katerega naloga je prikazati stran, ki obvešča o nastali napaki. Nastavitev error-preseterja se izvaja v [konfiguraciji application|configuration]. -Pošiljanje JSON .[#toc-sending-json] -==================================== +Pošiljanje JSON +=============== -Primer akcijske metode, ki pošlje podatke v obliki JSON in zaključi predstavitev: +Primer action-metode, ki pošlje podatke v formatu JSON in zaključi presenter: ```php public function actionData(): void @@ -236,33 +235,59 @@ public function actionData(): void ``` -Trajni parametri .[#toc-persistent-parameters] -============================================== +Parametri zahtevka .{data-version:3.1.14} +========================================= -Trajni parametri se uporabljajo za ohranjanje stanja med različnimi zahtevami. Njihova vrednost ostane enaka tudi po kliku na povezavo. Za razliko od podatkov seje se posredujejo v naslovu URL. To poteka povsem samodejno, zato jih ni treba izrecno navesti v `link()` ali `n:href`. +Presenter in tudi vsaka komponenta pridobiva iz HTTP zahtevka svoje parametre. Njihovo vrednost ugotovite z metodo `getParameter($name)` ali `getParameters()`. Vrednosti so nizi ali polja nizov, gre v bistvu za surove podatke, pridobljene neposredno iz URL-ja. -Primer uporabe? Imate večjezično aplikacijo. Dejanski jezik je parameter, ki mora biti vedno del naslova URL. Vendar bi bilo izjemno zamudno, če bi ga vključili v vsako povezavo. Zato ga naredite za trajni parameter z imenom `lang`, ki se bo prenašal sam. Super! +Za večje udobje priporočamo, da parametre zpřístupnite prek lastnosti. Dovolj je, da jih označite z atributom `#[Parameter]`: + +```php +use Nette\Application\Attributes\Parameter; // ta vrstica je pomembna + +class HomePresenter extends Nette\Application\UI\Presenter +{ + #[Parameter] + public string $theme; // mora biti public +} +``` + +Pri lastnosti priporočamo navedbo tudi podatkovnega tipa (npr. `string`) in Nette glede na to vrednost samodejno preoblikuje. Vrednosti parametrov lahko tudi [validirate |#Validacija parametrov]. + +Pri ustvarjanju povezave lahko parametrom vrednost neposredno nastavite: + +```latte +klikni +``` -Ustvarjanje trajnega parametra je v Nette izjemno enostavno. Preprosto ustvarite javno lastnost in jo označite z atributom: (prej se je uporabljal `/** @persistent */` ). + +Persistentni parametri +====================== + +Persistentni parametri služijo za ohranjanje stanja med različnimi zahtevki. Njihova vrednost ostane enaka tudi po kliku na povezavo. Za razliko od podatkov v seji se prenašajo v URL-ju. In to popolnoma samodejno, ni jih torej treba eksplicitno navajati v `link()` ali `n:href`. + +Primer uporabe? Imate večjezično aplikacijo. Trenutni jezik je parameter, ki mora biti nenehno del URL-ja. Vendar bi bilo izjemno utrujajoče ga v vsaki povezavi navajati. Zato ga naredite za persistentni parameter `lang` in se bo prenašal sam. Odlično! + +Ustvarjanje persistentnega parametra je v Nette izjemno enostavno. Dovolj je ustvariti javno lastnost in jo označiti z atributom: (prej se je uporabljalo `/** @persistent */`) ```php -use Nette\Application\Attributes\Persistent; // ta vrstica je pomembna +use Nette\Application\Attributes\Persistent; // ta vrstica je pomembna class ProductPresenter extends Nette\Application\UI\Presenter { #[Persistent] - public string $lang; // morajo biti javni. + public string $lang; // mora biti public } ``` -Če ima `$this->lang` vrednost, kot je `'en'`, bodo povezave, ustvarjene z uporabo `link()` ali `n:href`, vsebovale tudi parameter `lang=en`. In ko boste povezavo kliknili, bo ta spet imela vrednost `$this->lang = 'en'`. +Če bo `$this->lang` imel vrednost na primer `'en'`, bodo tudi povezave, ustvarjene s pomočjo `link()` ali `n:href`, vsebovale parameter `lang=en`. In po kliku na povezavo bo spet `$this->lang = 'en'`. -Za lastnosti priporočamo, da vključite podatkovno vrsto (npr. `string`), vključite pa lahko tudi privzeto vrednost. Vrednosti parametrov je mogoče [potrditi |#Validation of Persistent Parameters]. +Pri lastnosti priporočamo navedbo tudi podatkovnega tipa (npr. `string`) in lahko navedete tudi privzeto vrednost. Vrednosti parametrov lahko [validirate |#Validacija parametrov]. -Trajni parametri se privzeto posredujejo med vsemi dejanji določenega predstavnika. Če jih želite posredovati med več predstavniki, jih morate opredeliti: +Persistentni parametri se standardno prenašajo med vsemi akcijami danega presenterja. Da bi se prenašali tudi med več presenterji, jih je treba definirati bodisi: -- v skupnem predniku, od katerega predstavniki dedujejo -- v lastnosti, ki jo uporabljajo predstavniki: +- v skupnem predniku, od katerega presenterji dedujejo +- v traiti, ki jo presenterji uporabijo: ```php trait LanguageAware @@ -277,48 +302,42 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -Pri ustvarjanju povezave lahko spremenite vrednost trajnega parametra: +Pri ustvarjanju povezave lahko persistentnemu parametru spremenite vrednost: ```latte -detail in Czech +podrobnosti v slovenščini ``` -Lahko ga tudi *resetirate*, tj. odstranite iz URL-ja. Nato bo prevzel privzeto vrednost: +Nebo jej lze *vyresetovat*, tj. odstranit z URL. Pak bude nabývat svou výchozí hodnotu: ```latte -click +klikni ``` -Interaktivne komponente .[#toc-interactive-components] -====================================================== +Interaktivne komponente +======================= -Predstavitveni programi imajo vgrajen sistem komponent. Komponente so ločene enote za večkratno uporabo, ki jih namestimo v predstavnike. To so lahko [obrazci |forms:in-presenter], podatkovne mreže, meniji, pravzaprav vse, kar je smiselno večkrat uporabiti. +Presenterji imajo vgrajen komponentni sistem. Komponente so samostojne ponovno uporabne celote, ki jih vstavljamo v presenterje. Lahko so [obrazci |forms:in-presenter], podatkovne mreže, meniji, pravzaprav karkoli, kar ima smisel uporabljati večkrat. -Kako se komponente namestijo in nato uporabljajo v predstavitvenem programu? To je razloženo v poglavju [Komponente |Components]. Izvedeli boste celo, kaj imajo skupnega s Hollywoodom. +Kako se komponente vstavljajo v presenter in nato uporabljajo? To boste izvedeli v poglavju [Komponente |components]. Celo ugotovili boste, kaj imajo skupnega s Hollywoodom. -Kje lahko dobim komponente? Na strani [Komponente |https://componette.org] lahko najdete nekaj odprtokodnih komponent in drugih dodatkov za Nette, ki jih je izdelala in delila skupnost ogrodja Nette. +In kje lahko dobim komponente? Na strani [Componette |https://componette.org/search/component] najdete odprtokodne komponente in tudi vrsto drugih dodatkov za Nette, ki so jih sem postavili prostovoljci iz skupnosti okoli ogrodja. -Poglobitev .[#toc-going-deeper] -=============================== +Gremo v globino +=============== .[tip] -To, kar smo doslej prikazali v tem poglavju, bo verjetno zadostovalo. Naslednje vrstice so namenjene tistim, ki jih predstavniki zanimajo poglobljeno in želijo vedeti vse. - +S tem, kar smo si doslej v tem poglavju pokazali, si boste najverjetneje popolnoma zadostovali. Naslednje vrstice so namenjene tistim, ki se zanimajo za presenterje v globino in želijo vedeti popolnoma vse. -Zahteve in parametri .[#toc-requirement-and-parameters] -------------------------------------------------------- -Zahteva, ki jo obravnava predstavnik, je objekt [api:Nette\Application\Request] in jo vrne predstavnikova metoda `getRequest()`. Vključuje polje parametrov, vsak od njih pa pripada bodisi kateri od komponent bodisi neposredno predstavniku (ki je pravzaprav tudi komponenta, čeprav posebna). Nette torej parametre prerazporedi in posreduje med posameznimi komponentami (in predstavnikom) tako, da pokliče metodo `loadState(array $params)`. Parametre lahko pridobimo z metodo `getParameters(): array`, posamično pa z metodo `getParameter($name)`. Vrednosti parametrov so nizi ali polja nizov, v bistvu so surovi podatki, pridobljeni neposredno iz naslova URL. +Validacija parametrov +--------------------- +Vrednosti [parametrov zahtevka |#Parametri zahtevka] in [persistentnih parametrov |#Persistentni parametri], prejetih iz URL-ja, zapisuje v lastnosti metoda `loadState()`. Ta tudi kontroluje, zda odpovídá datový typ uvedený u property, jinak odpoví chybou 404 a stránka se nezobrazí. -Potrjevanje trajnih parametrov .[#toc-validation-of-persistent-parameters] --------------------------------------------------------------------------- - -Vrednosti [trajnih parametrov |#persistent parameters], prejetih z naslovov URL, se zapišejo v lastnosti z metodo `loadState()`. Preveri tudi, ali se podatkovna vrsta, navedena v lastnosti, ujema, sicer se odzove z napako 404 in stran se ne prikaže. - -Nikoli ne zaupajte slepo trajnim parametrom, saj jih lahko uporabnik zlahka prepiše v naslovu URL. Tako na primer preverimo, ali je `$this->lang` med podprtimi jeziki. Dober način za to je, da prekrijete zgoraj omenjeno metodo `loadState()`: +Nikoli slepo ne verjemite parametrom, saj jih lahko uporabnik enostavno prepiše v URL-ju. Tako na primer preverimo, ali je jezik `$this->lang` med podprtimi. Primerna pot je prepisati omenjeno metodo `loadState()`: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -328,9 +347,9 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function loadState(array $params): void { - parent::loadState($params); // tukaj je nastavljen $this->lang - // sledi preverjanju uporabniške vrednosti: - if (!in_array($this->lang, ['en', 'cs'])) { + parent::loadState($params); // tukaj se nastavi $this->lang + // sledi lastno preverjanje vrednosti: + if (!in_array($this->lang, ['en', 'sl'])) { // 'cs' spremenjeno v 'sl' $this->error(); } } @@ -338,31 +357,33 @@ class ProductPresenter extends Nette\Application\UI\Presenter ``` -Shranjevanje in obnavljanje zahtevka .[#toc-save-and-restore-the-request] -------------------------------------------------------------------------- +Shranjevanje in obnovitev zahtevka +---------------------------------- -Trenutni zahtevek lahko shranite v sejo ali pa ga obnovite iz seje in omogočite predavatelju, da ga ponovno izvede. To je na primer uporabno, ko uporabnik izpolni obrazec in mu poteče prijava. Da ne bi izgubili podatkov, pred preusmeritvijo na stran za prijavo shranimo trenutno zahtevo v sejo z uporabo `$reqId = $this->storeRequest()`, ki vrne identifikator v obliki kratkega niza in ga kot parameter posreduje predstavniku za prijavo. +Zahtevek, ki ga obravnava presenter, je objekt [api:Nette\Application\Request] in ga vrača metoda presenterja `getRequest()`. -Po prijavi pokličemo metodo `$this->restoreRequest($reqId)`, ki prevzame zahtevo iz seje in ji jo posreduje naprej. Metoda preveri, ali je zahtevo ustvaril isti uporabnik, kot je zdaj prijavljeni. Če se prijavi drug uporabnik ali je ključ neveljaven, ne stori ničesar in program se nadaljuje. +Trenutni zahtevek lahko shranimo v sejo ali pa ga iz nje obnovimo in pustimo, da ga presenter ponovno izvede. To je koristno na primer v situaciji, ko uporabnik izpolnjuje obrazec in mu poteče prijava. Da ne bi izgubil podatkov, pred preusmeritvijo na prijavno stran trenutni zahtevek shranimo v sejo s pomočjo `$reqId = $this->storeRequest()`, ki vrne njegov identifikator v obliki kratkega niza in ga predamo kot parameter prijavnemu presenterju. -Oglejte si kuharsko knjigo [Kako se vrniti na prejšnjo stran |best-practices:restore-request]. +Po prijavi pokličemo metodo `$this->restoreRequest($reqId)`, ki zahtevek prevzame iz seje in preusmeri nanj. Metoda pri tem preveri, da je zahtevek ustvaril isti uporabnik, kot se je zdaj prijavil. Če bi se prijavil drug uporabnik ali bi bil ključ neveljaven, ne naredi nič in program nadaljuje naprej. +Poglejte si navodilo [Kako se vrniti na prejšnjo stran |best-practices:restore-request]. -Kanonizacija .[#toc-canonization] ---------------------------------- -Predstavniki imajo eno res odlično funkcijo, ki izboljšuje SEO (optimizacijo iskanja na internetu). Samodejno preprečujejo obstoj podvojene vsebine na različnih naslovih URL. Če na določen cilj vodi več naslovov URL, npr. `/index` in `/index?page=1`, ogrodje enega od njih označi kot primarnega (kanoničnega) in nanj preusmeri druge z uporabo kode HTTP 301. Zaradi tega iskalniki strani ne indeksirajo dvakrat in ne oslabijo njihovega ranga. +Kanonizacija +------------ -Ta postopek se imenuje kanonizacija. Kanonični URL je URL, ki ga ustvari [usmerjevalnik |routing], običajno prva ustrezna pot v zbirki. +Presenterji imajo eno resnično odlično lastnost, ki prispeva k boljšemu SEO (optimizaciji najdljivosti na internetu). Samodejno preprečujejo obstoj podvojene vsebine na različnih URL-jih. Če do določenega cilja vodi več URL naslovov, npr. `/index` in `/index?page=1`, ogrodje določi enega od njih za primarnega (kanoničnega) in ostale nanj preusmeri s pomočjo HTTP kode 301. Zahvaljujoč temu vam iskalniki strani ne indeksirajo dvakrat in ne razpršijo njihovega page ranka. -Kanonizacija je privzeto vklopljena in jo lahko izklopite prek spletne strani `$this->autoCanonicalize = false`. +Temu procesu rečemo kanonizacija. Kanonični URL je tisti, ki ga generira [usmerjevalnik |routing], praviloma torej prva ustrezna pot v zbirki. -Preusmeritev se ne izvede pri zahtevi AJAX ali POST, ker bi povzročila izgubo podatkov ali ne bi imela dodane vrednosti SEO. +Kanonizacija je privzeto vklopljena in jo lahko izklopite prek `$this->autoCanonicalize = false`. -Kanonizacijo lahko sprožite tudi ročno z metodo `canonicalize()`, ki tako kot metoda `link()` kot argumente prejme predstavnika, dejanja in parametre. Ustvari povezavo in jo primerja s trenutnim naslovom URL. Če se razlikuje, preusmeri na ustvarjeno povezavo. +Do preusmeritve ne pride pri AJAX ali POST zahtevku, ker bi prišlo do izgube podatkov ali pa to ne bi imelo dodane vrednosti z vidika SEO. + +Kanonizacijo lahko sprožite tudi ročno s pomočjo metode `canonicalize()`, kateri se podobno kot metodi `link()` predajo presenter, akcija in parametri. Izdelala bo povezavo in jo primerjala s trenutnim URL naslovom. Če se razlikujeta, preusmeri na generirano povezavo. ```php -public function actionShow(int $id, string $slug = null): void +public function actionShow(int $id, ?string $slug = null): void { $realSlug = $this->facade->getSlugForId($id); // preusmeri, če se $slug razlikuje od $realSlug @@ -371,10 +392,10 @@ public function actionShow(int $id, string $slug = null): void ``` -Dogodki .[#toc-events] ----------------------- +Dogodki +------- -Poleg metod `startup()`, `beforeRender()` in `shutdown()`, ki se kličejo kot del življenjskega cikla predstavitve, lahko določite tudi druge funkcije, ki se kličejo samodejno. Predstavitelj definira tako imenovane [dogodke |nette:glossary#events], njihove izvajalce pa dodate v polja `$onStartup`, `$onRender` in `$onShutdown`. +Poleg metod `startup()`, `beforeRender()` in `shutdown()`, ki se kličejo kot del življenjskega cikla presenterja, lahko definiramo še druge funkcije, ki naj se samodejno pokličejo. Presenter definira t.i. [dogodke |nette:glossary#Dogodki eventi], katerih obdelovalce dodate v polja `$onStartup`, `$onRender` in `$onShutdown`. ```php class ArticlePresenter extends Nette\Application\UI\Presenter @@ -388,34 +409,34 @@ class ArticlePresenter extends Nette\Application\UI\Presenter } ``` -Obdelovalci v polju `$onStartup` se pokličejo tik pred metodo `startup()`, nato `$onRender` med `beforeRender()` in `render()` in nazadnje `$onShutdown` tik pred `shutdown()`. +Obdelovalci v polju `$onStartup` se kličejo tik pred metodo `startup()`, nato `$onRender` med `beforeRender()` in `render()` in na koncu `$onShutdown` tik pred `shutdown()`. -Odzivi .[#toc-responses] ------------------------- +Odgovori +-------- -Odziv, ki ga vrne predstavitelj, je objekt, ki implementira vmesnik [api:Nette\Application\Response]. Na voljo je več pripravljenih odgovorov: +Odgovor, ki ga vrača presenter, je objekt, ki implementira vmesnik [api:Nette\Application\Response]. Na voljo je vrsta pripravljenih odgovorov: - [api:Nette\Application\Responses\CallbackResponse] - pošlje povratni klic - [api:Nette\Application\Responses\FileResponse] - pošlje datoteko -- [api:Nette\Application\Responses\ForwardResponse] - posreduje () +- [api:Nette\Application\Responses\ForwardResponse] - forward() - [api:Nette\Application\Responses\JsonResponse] - pošlje JSON -- [api:Nette\Application\Responses\RedirectResponse] - preusmeri +- [api:Nette\Application\Responses\RedirectResponse] - preusmeritev - [api:Nette\Application\Responses\TextResponse] - pošlje besedilo - [api:Nette\Application\Responses\VoidResponse] - prazen odgovor -Odgovori se pošljejo z metodo `sendResponse()`: +Odgovori se pošiljajo z metodo `sendResponse()`: ```php use Nette\Application\Responses; -// Preprosto besedilo +// Navadno besedilo $this->sendResponse(new Responses\TextResponse('Hello Nette!')); // Pošlje datoteko $this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf')); -// Pošlje povratni klic +// Odgovor bo povratni klic $callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse) { if ($httpResponse->getHeader('Content-Type') === 'text/html') { echo '

    Hello

    '; @@ -425,10 +446,55 @@ $this->sendResponse(new Responses\CallbackResponse($callback)); ``` -Nadaljnje branje .[#toc-further-reading] -======================================== +Omejitev dostopa s pomočjo `#[Requires]` .{data-version:3.2.2} +-------------------------------------------------------------- + +Atribut `#[Requires]` ponuja napredne možnosti za omejevanje dostopa do presenterjev in njihovih metod. Lahko ga uporabite za specifikacijo HTTP metod, zahtevanje AJAX zahtevka, omejitev na isti izvor (same origin), in dostop samo prek posredovanja (forwarding). Atribut lahko uporabite tako za razrede presenterjev kot za posamezne metode `action()`, `render()`, `handle()` in `createComponent()`. + +Lahko določite te omejitve: +- na HTTP metode: `#[Requires(methods: ['GET', 'POST'])]` +- zahtevanje AJAX zahtevka: `#[Requires(ajax: true)]` +- dostop samo iz istega izvora: `#[Requires(sameOrigin: true)]` +- dostop samo prek posredovanja: `#[Requires(forward: true)]` +- omejitev na konkretne akcije: `#[Requires(actions: 'default')]` + +Podrobnosti najdete v navodilu [Kako uporabljati atribut Requires |best-practices:attribute-requires]. + + +Preverjanje HTTP metode +----------------------- + +Presenterji v Nette samodejno preverjajo HTTP metodo vsakega dohodnega zahtevka. Razlog za to preverjanje je predvsem varnost. Standardno so dovoljene metode `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH`. + +Če želite dovoliti dodatno na primer metodo `OPTIONS`, uporabite za to atribut `#[Requires]` (od Nette Application v3.2): + +```php +#[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])] +class MyPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +V različici 3.1 se preverjanje izvaja v `checkHttpMethod()`, ki ugotavlja, ali je metoda, specificirana v zahtevku, vsebovana v polju `$presenter->allowedMethods`. Dodajanje metode naredite takole: + +```php +class MyPresenter extends Nette\Application\UI\Presenter +{ + protected function checkHttpMethod(): void + { + $this->allowedMethods[] = 'OPTIONS'; + parent::checkHttpMethod(); + } +} +``` + +Pomembno je poudariti, da če dovolite metodo `OPTIONS`, jo morate nato tudi ustrezno obravnavati znotraj svojega presenterja. Metoda se pogosto uporablja kot t.i. preflight request, ki ga brskalnik samodejno pošlje pred dejanskim zahtevkom, ko je treba ugotoviti, ali je zahtevek dovoljen z vidika CORS (Cross-Origin Resource Sharing) politike. Če metodo dovolite, vendar ne implementirate pravilnega odgovora, lahko to vodi do neskladij in potencialnih varnostnih težav. + + +Nadaljnje branje +================ -- [Vbrizgavanje metod in atributov |best-practices:inject-method-attribute] -- [Sestavljanje predstavnikov iz lastnosti |best-practices:presenter-traits] -- [Posredovanje nastavitev predstavnikom |best-practices:passing-settings-to-presenters] +- [Metode in atributi inject |best-practices:inject-method-attribute] +- [Sestavljanje presenterjev iz trait |best-practices:presenter-traits] +- [Posredovanje nastavitev v presenterje |best-practices:passing-settings-to-presenters] - [Kako se vrniti na prejšnjo stran |best-practices:restore-request] diff --git a/application/sl/routing.texy b/application/sl/routing.texy index 046c205488..06c2ccee69 100644 --- a/application/sl/routing.texy +++ b/application/sl/routing.texy @@ -3,27 +3,26 @@ Usmerjanje
    -Usmerjevalnik je odgovoren za vse, kar zadeva naslove URL, tako da vam o njih ni treba več razmišljati. Prikazali bomo: +Usmerjevalnik (Router) skrbi za vse v zvezi z URL naslovi, da vam nad njimi ne bi bilo treba več razmišljati. Pokazali si bomo: -- kako nastaviti usmerjevalnik, da bodo naslovi URL videti tako, kot želite -- nekaj opomb o preusmeritvi SEO -- in pokazali vam bomo, kako lahko napišete svoj usmerjevalnik +- kako nastaviti usmerjevalnik, da bodo URL-ji po želji +- povedali si bomo o SEO in preusmeritvah +- in pokazali si bomo, kako napisati lasten usmerjevalnik
    -Bolj človeški URL-ji (ali kul ali lepi URL-ji) so bolj uporabni, bolj zapomnljivi in pozitivno prispevajo k SEO. Nette to upošteva in v celoti izpolnjuje želje razvijalcev. Strukturo URL za svojo aplikacijo lahko oblikujete točno tako, kot želite. -Oblikujete jo lahko celo po tem, ko je aplikacija že pripravljena, saj to lahko storite brez kakršnih koli sprememb kode ali predloge. Opredeljena je na eleganten način na [enem samem mestu |#Integration], v usmerjevalniku, in ni razpršena v obliki opomb v vseh predstavitvah. +Bolj človeški URL-ji (ali tudi kul ali lepi URL-ji) so bolj uporabni, lažje zapomnljivi in pozitivno prispevajo k SEO. Nette na to misli in razvijalcem popolnoma ustreza. Za svojo aplikacijo si lahko zasnujete točno takšno strukturo URL naslovov, kakršno boste želeli. Lahko jo zasnujete celo šele takrat, ko je aplikacija že končana, saj se to izvede brez posegov v kodo ali predloge. Definira se namreč na eleganten način na enem [samem mestu |#Vključitev v aplikacijo], v usmerjevalniku, in ni tako razpršena v obliki anotacij v vseh presenterjih. -Usmerjevalnik v programu Nette je poseben, ker je **dvosmeren**, saj lahko tako dekodira URL-je zahtevkov HTTP kot tudi ustvarja povezave. Zato ima v [aplikaciji Nette |how-it-works#Nette Application] pomembno vlogo, saj odloča, kateri predstavnik in dejanje bosta izvedla trenutno zahtevo, uporablja pa se tudi za [ustvarjanje URL-jev |creating-links] v predlogi itd. +Usmerjevalnik v Nette je izjemen s tem, da je **dvosmeren.** Zna tako dekodirati URL v HTTP zahtevku kot tudi ustvarjati povezave. Igra torej ključno vlogo v [Nette Application |how-it-works#Nette Application], saj delsno odloča o tem, kateri presenter in akcija bosta izvajala trenutni zahtevek, delsno pa se uporablja za [generiranje URL-jev |creating-links] v predlogi itd. -Vendar usmerjevalnik ni omejen na to uporabo, uporabite ga lahko v aplikacijah, kjer se predstavniki sploh ne uporabljajo, za API REST itd. Več v razdelku [ločena uporaba |#separated usage]. +Vendar usmerjevalnik ni omejen samo na to uporabo, lahko ga uporabite v aplikacijah, kjer se presenterji sploh ne uporabljajo, za REST API itd. Več v delu [#Samostojna uporaba]. -Zbiranje poti .[#toc-route-collection] -====================================== +Zbirka poti +=========== -Najprijetnejši način za določanje naslovov URL v aplikaciji je prek razreda [api:Nette\Application\Routers\RouteList]. Opredelitev je sestavljena iz seznama tako imenovanih poti, tj. mask naslovov URL in z njimi povezanih predstavnikov ter akcij z uporabo preprostega API. Poti nam ni treba poimenovati. +Najprijetnejši način, kako definirati obliko URL naslovov v aplikaciji, ponuja razred [api:Nette\Application\Routers\RouteList]. Definicija je sestavljena iz seznama t.i. poti (routes), torej mask URL naslovov in k njim pridruženih presenterjev in akcij s pomočjo preprostega API-ja. Poti ni treba poimenovati. ```php $router = new Nette\Application\Routers\RouteList; @@ -32,16 +31,16 @@ $router->addRoute('article/', 'Article:view'); // ... ``` -Primer pravi, da če v brskalniku odpremo `https://any-domain.com/rss.xml` z akcijo `rss`, če `https://domain.com/article/12` z akcijo `view` itd. Če ne najdemo ustrezne poti, se aplikacija Nette Application odzove tako, da vrže izjemo [BadRequestException |api:Nette\Application\BadRequestException], ki se uporabniku prikaže kot stran z napako 404 Not Found. +Primer pravi, da če v brskalniku odpremo `https://domain.com/rss.xml`, se prikaže presenter `Feed` z akcijo `rss`, če `https://domain.com/article/12`, se prikaže presenter `Article` z akcijo `view` itd. V primeru nenajdene primerne poti Nette Application reagira s sprožitvijo izjeme [BadRequestException |api:Nette\Application\BadRequestException], ki se uporabniku prikaže kot stran z napako 404 Not Found. -Vrstni red poti .[#toc-order-of-routes] ---------------------------------------- +Vrstni red poti +--------------- -Vrstni red, v katerem so poti navedene, je **zelo pomemben**, saj se ocenjujejo zaporedno od zgoraj navzdol. Velja pravilo, da poti prijavljamo **od posebnih do splošnih**: +Popolnoma **ključni je vrstni red**, v katerem so posamezne poti navedene, ker se vrednotijo postopoma od zgoraj navzdol. Velja pravilo, da poti deklariramo **od specifičnih k splošnim**: ```php -// Napačno: 'rss.xml' ustreza prvi poti in to napačno razume kot +// SLABO: 'rss.xml' ujame prva pot in ta niz razume kot $router->addRoute('', 'Article:view'); $router->addRoute('rss.xml', 'Feed:rss'); @@ -50,10 +49,10 @@ $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('', 'Article:view'); ``` -Tudi pri ustvarjanju povezav se poti ocenjujejo od zgoraj navzdol: +Poti se vrednotijo od zgoraj navzdol tudi pri generiranju povezav: ```php -// NAPAKA: ustvari povezavo do 'Feed:rss' kot 'admin/feed/rss' +// SLABO: povezava na 'Feed:rss' generira kot 'admin/feed/rss' $router->addRoute('admin//', 'Admin:default'); $router->addRoute('rss.xml', 'Feed:rss'); @@ -62,100 +61,100 @@ $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('admin//', 'Admin:default'); ``` -Ne bomo vam skrivali, da je za pravilno oblikovanje seznama potrebno nekaj spretnosti. Dokler se v to ne boste spoprijeli, bo [plošča za usmerjanje |#Debugging Router] uporabno orodje. +Ne bomo vam skrivali, da pravilno sestavljanje poti zahteva določeno spretnost. Preden se vanjo poglobite, vam bo koristen pomočnik [usmerjevalna plošča |#Razhroščevanje usmerjevalnika]. -Maska in parametri .[#toc-mask-and-parameters] ----------------------------------------------- +Maska in parametri +------------------ -Maska opisuje relativno pot, ki temelji na korenu spletnega mesta. Najpreprostejša maska je statični URL: +Maska opisuje relativno pot od korenskega direktorija spletnega mesta. Najenostavnejša maska je statični URL: ```php $router->addRoute('products', 'Products:default'); ``` -Maske pogosto vsebujejo tako imenovane **parametre**. Ti so zaprti v oglatih oklepajih (npr. ``) in se posredujejo ciljnemu predstavniku, na primer metodi `renderShow(int $year)` ali trajnemu parametru `$year`: +Pogosto maske vsebujejo t.i. **parametre**. Ti so navedeni v ostrih oklepajih (npr. ``) in so posredovani v ciljni presenter, na primer metodi `renderShow(int $year)` ali v persistentni parameter `$year`: ```php $router->addRoute('chronicle/', 'History:show'); ``` -Primer pravi, da če v brskalniku odpremo `https://any-domain.com/chronicle/2020` in dejanje `show` s parametrom `year: 2020`. +Primer pravi, da če v brskalniku odpremo `https://example.com/chronicle/2020`, se prikaže presenter `History` z akcijo `show` in parametrom `year: 2020`. -Neposredno v maski lahko določimo privzeto vrednost parametrov in tako postane neobvezna: +Parametrom lahko določimo privzeto vrednost neposredno v maski in s tem postanejo izbirni: ```php $router->addRoute('chronicle/', 'History:show'); ``` -Pot bo zdaj sprejela naslov URL `https://any-domain.com/chronicle/` s parametrom `year: 2020`. +Pot bo zdaj sprejela tudi URL `https://example.com/chronicle/`, ki spet prikaže `History:show` s parametrom `year: 2020`. -Seveda sta lahko parametra tudi ime predstavnika in akcija. Na primer: +Parameter je lahko seveda tudi ime presenterja in akcije. Na primer tako: ```php $router->addRoute('/', 'Home:default'); ``` -Ta pot sprejme na primer naslov URL v obliki `/article/edit` oz. `/catalog/list` in ga prevede v predstavnike in dejanja `Article:edit` oz. `Catalog:list`. +Navedena pot sprejema npr. URL v obliki `/article/edit` ali tudi `/catalog/list` in jih razume kot presenterje in akcije `Article:edit` in `Catalog:list`. -Parametroma `presenter` in `action` daje tudi privzete vrednosti`Home` in `default`, zato sta neobvezna. Tako pot sprejme tudi naslov URL `/article` in ga prevede kot `Article:default`. Ali obratno, povezava na `Product:default` ustvari pot `/product`, povezava na privzeto `Home:default` pa ustvari pot `/`. +Hkrati daje parametroma `presenter` in `action` privzeti vrednosti `Home` in `default` in sta torej tudi izbirna. Tako pot sprejema tudi URL v obliki `/article` in ga razume kot `Article:default`. Ali obratno, povezava na `Product:default` generira pot `/product`, povezava na privzeti `Home:default` pot `/`. -Maska lahko opiše ne le relativno pot na podlagi korena spletnega mesta, temveč tudi absolutno pot, kadar se začne s poševnico, ali celo celoten absolutni naslov URL, kadar se začne z dvema poševnicama: +Maska lahko opisuje ne samo relativno pot od korenskega direktorija spletnega mesta, ampak tudi absolutno pot, če se začne s poševnico, ali celo celoten absolutni URL, če se začne z dvema poševnicama: ```php -// relativna pot do korena dokumenta aplikacije +// relativno glede na document root $router->addRoute('/', /* ... */); -// absolutna pot, relativna glede na gostiteljsko ime strežnika +// absolutna pot (relativna glede na domeno) $router->addRoute('//', /* ... */); -// absolutni naslov URL, vključno z imenom gostitelja (vendar relativno glede na shemo) +// absolutni URL vključno z domeno (relativen glede na shemo) $router->addRoute('//.example.com//', /* ... */); -// absolutni URL, vključno s shemo +// absolutni URL vključno s shemo $router->addRoute('https://.example.com//', /* ... */); ``` -Izrazi za potrjevanje .[#toc-validation-expressions] ----------------------------------------------------- +Validacijski izrazi +------------------- -Za vsak parameter lahko določite pogoj za preverjanje z uporabo [regularnega izraza |https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Na primer, z uporabo regexp `\d+` določimo, da je `id` samo številski parameter: +Za vsak parameter lahko določimo validacijski pogoj s pomočjo [regularnega izraza|https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Na primer, parametru `id` določimo, da lahko vsebuje samo števke s pomočjo regularnega izraza `\d+`: ```php $router->addRoute('/[/]', /* ... */); ``` -Privzet regularni izraz za vse parametre je `[^/]+`tj. vse razen poševnice. Če naj bi se parameter ujemal tudi s poševnico, nastavimo regularni izraz na `.+`. +Privzeti regularni izraz za vse parametre je `[^/]+`, tj. vse razen poševnice. Če mora parameter sprejemati tudi poševnice, navedemo izraz `.+`: ```php -// sprejme https://example.com/a/b/c, pot je 'a/b/c' +// sprejema https://example.com/a/b/c, path bo 'a/b/c' $router->addRoute('', /* ... */); ``` -Neobvezna zaporedja .[#toc-optional-sequences] ----------------------------------------------- +Izbirne sekvence +---------------- -V oglatih oklepajih so označeni neobvezni deli maske. Vsak del maske je lahko nastavljen kot neobvezen, vključno s tistimi, ki vsebujejo parametre: +V maski lahko označujemo izbirne dele s pomočjo oglatih oklepajev. Izbirni je lahko katerikoli del maske, lahko se v njem nahajajo tudi parametri: ```php $router->addRoute('[/]', /* ... */); -// Sprejeti naslovi URL: Parametri: -// /en/download lang => en, name => download -// /download lang => null, ime => download +// Sprejema poti: +// /sl/download => lang => sl, name => download +// /download => lang => null, name => download ``` -Če je parameter del neobveznega zaporedja, seveda postane tudi neobvezen. Če nima privzete vrednosti, bo nič. +Ko je parameter del izbirne sekvence, postane seveda tudi izbiren. Če nima navedene privzete vrednosti, bo null. -Neobvezni odseki so lahko tudi v domeni: +Izbirni deli so lahko tudi v domeni: ```php $router->addRoute('//[.]example.com//', /* ... */); ``` -Sekvence se lahko poljubno gnezdijo in kombinirajo: +Sekvence je mogoče poljubno gnezditi in kombinirati: ```php $router->addRoute( @@ -163,49 +162,49 @@ $router->addRoute( 'Home:default', ); -// Sprejeti naslovi URL: -// /en/hello -// /en-us/hello -// /hello -// /hello/stran-12 +// Sprejema poti: +// /sl/hello +// /en-us/hello +// /hello +// /hello/page-12 ``` -generator URL poskuša ohraniti čim krajši URL, zato je tisto, kar je mogoče izpustiti, izpuščeno. Zato je na primer pot `index[.html]` ustvari pot `/index`. To vedenje lahko obrnete tako, da za levim oglatim oklepajem napišete vzklikalnik: +Pri generiranju URL-jev se stremi k najkrajši varianti, zato se vse, kar je mogoče izpustiti, izpusti. Zato na primer pot `index[.html]` generira pot `/index`. Obrniti obnašanje je mogoče z navedbo klicaja za levim oglatim oklepajem: ```php -// sprejme tako /hello kot /hello.html in ustvari /hello +// sprejema /hello in /hello.html, generira /hello $router->addRoute('[.html]', /* ... */); -// sprejme tako /hello kot /hello.html in ustvari /hello.html +// sprejema /hello in /hello.html, generira /hello.html $router->addRoute('[!.html]', /* ... */); ``` -Izbirni parametri (tj. parametri s privzeto vrednostjo) brez oglatih oklepajev se obnašajo, kot da bi bili zaviti na ta način: +Izbirni parametri (tj. parametri, ki imajo privzeto vrednost) brez oglatih oklepajev se obnašajo v bistvu tako, kot da bi bili oklepajeni na naslednji način: ```php $router->addRoute('//', /* ... */); -// je enako: +// ustreza temu: $router->addRoute('[/[/[]]]', /* ... */); ``` -Če želite spremeniti način generiranja skrajne desne poševnice, tj. namesto `/home/` dobite `/home`, prilagodite pot na ta način: +Če bi želeli vplivati na obnašanje končne poševnice, da bi se npr. namesto `/home/` generiralo samo `/home`, lahko to dosežemo takole: ```php $router->addRoute('[[/[/]]]', /* ... */); ``` -Zaščitne črke .[#toc-wildcards] -------------------------------- +Nadomestni znaki +---------------- -V maski absolutne poti lahko uporabimo naslednje nadomestne črke, da se na primer izognemo potrebi po zapisu domene v masko, ki se lahko razlikuje v razvojnem in produkcijskem okolju: +V maski absolutne poti lahko uporabimo naslednje nadomestne znake in se tako izognemo npr. potrebi po zapisovanju domene v masko, ki se lahko razlikuje v razvojnem in produkcijskem okolju: -- `%tld%` = domena najvišje ravni, npr. `com` ali `org` -- `%sld%` = domena druge ravni, npr. `example` +- `%tld%` = top level domain, npr. `com` ali `org` +- `%sld%` = second level domain, npr. `example` - `%domain%` = domena brez poddomen, npr. `example.com` - `%host%` = celoten gostitelj, npr. `www.example.com` -- `%basePath%` = pot do korenskega imenika +- `%basePath%` = pot do korenskega direktorija ```php $router->addRoute('//www.%domain%/%basePath%//', /* ... */); @@ -213,10 +212,10 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ @@ -225,7 +224,7 @@ $router->addRoute('/[/]', [ ]); ``` -Lahko pa uporabimo tudi to obliko, pri čemer opazimo prepisovanje regularnega izraza za preverjanje: +Za podrobnejšo specifikacijo lahko uporabimo še razširjenejšo obliko, kjer poleg privzetih vrednosti lahko nastavimo tudi druge lastnosti parametrov, kot na primer validacijski regularni izraz (glej parameter `id`): ```php use Nette\Routing\Route; @@ -243,19 +242,19 @@ $router->addRoute('/[/]', [ ]); ``` -Te bolj zgovorne oblike so uporabne za dodajanje drugih metapodatkov. +Pomembno je opozoriti, da če parametri, definirani v polju, niso navedeni v maski poti, njihovih vrednosti ni mogoče spremeniti, niti s pomočjo poizvedbenih parametrov, navedenih za vprašajem v URL-ju. -Filtri in prevodi .[#toc-filters-and-translations] --------------------------------------------------- +Filtri in prevodi +----------------- -Dobra praksa je, da izvorno kodo pišete v angleščini, a kaj, če potrebujete, da je URL vašega spletnega mesta preveden v drug jezik? Preproste poti, kot so npr: +Izvorne kode aplikacije pišemo v angleščini, vendar če naj ima spletno mesto slovenske URL-je, potem preprosto usmerjanje tipa: ```php $router->addRoute('/', 'Home:default'); ``` -bodo ustvarile angleške naslove URL, kot sta `/product/123` ali `/cart`. Če želimo, da so predstavniki in dejanja v naslovu URL prevedeni v nemščino (npr. `/produkt/123` ali `/einkaufswagen`), lahko uporabimo prevajalski slovar. Za njegovo dodajanje že potrebujemo "bolj zgovorno" različico drugega parametra: +bo generiralo angleške URL-je, kot na primer `/product/123` ali `/cart`. Če želimo imeti presenterje in akcije v URL-ju predstavljene s slovenskimi besedami (npr. `/izdelek/123` ali `/kosarica`), lahko uporabimo prevodni slovar. Za njegov zapis že potrebujemo »bolj zgovorno« varianto drugega parametra: ```php use Nette\Routing\Route; @@ -264,26 +263,26 @@ $router->addRoute('/', [ 'presenter' => [ Route::Value => 'Home', Route::FilterTable => [ - // niz v naslovu URL => presenter - 'produkt' => 'Product', - 'einkaufswagen' => 'Cart', + // niz v URL => presenter + 'izdelek' => 'Product', + 'kosarica' => 'Cart', 'katalog' => 'Catalog', ], ], 'action' => [ Route::Value => 'default', Route::FilterTable => [ - 'liste' => 'list', + 'seznam' => 'list', ], ], ]); ``` -Za isti predstavnik lahko uporabimo več ključev slovarja. Z njimi bodo zanj ustvarjeni različni vzdevki. Zadnji ključ velja za kanonično različico (tj. tisto, ki bo v ustvarjenem naslovu URL). +Več ključev prevodnega slovarja lahko vodi na isti presenter. S tem se zanj ustvarijo različni aliasi. Za kanonično varianto (torej tisto, ki bo v generiranem URL-ju) se šteje zadnji ključ. -Na ta način je mogoče prevajalno tabelo uporabiti za kateri koli parameter. Če prevod ne obstaja, se vzame izvirna vrednost. To obnašanje lahko spremenimo tako, da dodamo `Route::FilterStrict => true` in pot bo nato zavrnila URL, če vrednosti ni v slovarju. +Prevodno tabelo lahko na ta način uporabimo za katerikoli parameter. Pri čemer, če prevod ne obstaja, se vzame prvotna vrednost. To obnašanje lahko spremenimo z dopolnitvijo `Route::FilterStrict => true` in pot potem zavrne URL, če vrednost ni v slovarju. -Poleg slovarja prevodov v obliki polja je mogoče nastaviti tudi lastne prevajalske funkcije: +Poleg prevodnega slovarja v obliki polja lahko uporabimo tudi lastne prevodne funkcije. ```php use Nette\Routing\Route; @@ -299,15 +298,15 @@ $router->addRoute('//', [ ]); ``` -Funkcija `Route::FilterIn` pretvori parameter v naslovu URL v niz, ki se nato posreduje predstavniku, funkcija `FilterOut` pa poskrbi za pretvorbo v nasprotni smeri. +Funkcija `Route::FilterIn` pretvarja med parametrom v URL-ju in nizom, ki se nato posreduje v presenter, funkcija `FilterOut` zagotavlja pretvorbo v nasprotno smer. -Parametri `presenter`, `action` in `module` imajo že vnaprej določene filtre, ki pretvarjajo med slogom PascalCase oz. camelCase in kebab-case, uporabljenim v naslovu URL. Privzeta vrednost parametrov je že zapisana v pretvorjeni obliki, tako da na primer v primeru predvajalnika zapišemo `` namesto ``. +Parametri `presenter`, `action` in `module` že imajo preddefinirane filtre, ki pretvarjajo med slogom PascalCase oz. camelCase in kebab-case, uporabljenim v URL-ju. Privzeta vrednost parametrov se zapisuje že v transformirani obliki, tako da na primer v primeru presenterja pišemo ``, ne pa ``. -Splošni filtri .[#toc-general-filters] --------------------------------------- +Splošni filtri +-------------- -Poleg filtrov za določene parametre lahko določite tudi splošne filtre, ki prejmejo asociativno polje vseh parametrov, ki jih lahko poljubno spremenijo in nato vrnejo. Splošni filtri so opredeljeni pod ključem `null`. +Poleg filtrov, namenjenih konkretnim parametrom, lahko definiramo tudi splošne filtre, ki prejmejo asociativno polje vseh parametrov, ki jih lahko kakorkoli modificirajo in nato vrnejo. Splošne filtre definiramo pod ključem `null`. ```php use Nette\Routing\Route; @@ -315,22 +314,22 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => 'Home', 'action' => 'default', - null => [ + '' => [ Route::FilterIn => function (array $params): array { /* ... */ }, Route::FilterOut => function (array $params): array { /* ... */ }, ], ]); ``` -Splošni filtri vam dajejo možnost, da prilagodite obnašanje poti na popolnoma poljuben način. Uporabimo jih lahko na primer za spreminjanje parametrov na podlagi drugih parametrov. Na primer, prevod `` in `` na podlagi trenutne vrednosti parametra ``. +Splošni filtri dajejo možnost prilagoditi obnašanje poti na popolnoma kakršenkoli način. Lahko jih uporabimo na primer za modifikacijo parametrov na podlagi drugih parametrov. Na primer, prevajanje `` in `` na podlagi trenutne vrednosti parametra ``. -Če ima parameter opredeljen filter po meri in hkrati obstaja splošni filter, se filter po meri `FilterIn` izvede pred splošnim in obratno, splošni `FilterOut` se izvede pred filtrom po meri. Tako so znotraj splošnega filtra vrednosti parametrov `presenter` oz. `action` zapisane v slogu PascalCase oz. camelCase. +Če ima parameter definiran lasten filter in hkrati obstaja splošni filter, se izvede lastni `FilterIn` pred splošnim in obratno splošni `FilterOut` pred lastnim. Torej znotraj splošnega filtra so vrednosti parametrov `presenter` oz. `action` zapisane v slogu PascalCase oz. camelCase. -Enosmerna zastavica .[#toc-oneway-flag] ---------------------------------------- +Enosmerne poti OneWay +--------------------- -Enosmerne poti se uporabljajo za ohranjanje funkcionalnosti starih URL-jev, ki jih aplikacija ne ustvarja več, vendar jih še vedno sprejema. Označimo jih z `OneWay`: +Enosmerne poti se uporabljajo za ohranjanje funkcionalnosti starih URL-jev, ki jih aplikacija ne generira več, vendar jih še vedno sprejema. Označimo jih z zastavico `OneWay`: ```php // stari URL /product-info?id=123 @@ -339,38 +338,61 @@ $router->addRoute('product-info', 'Product:detail', $router::ONE_WAY); $router->addRoute('product/', 'Product:detail'); ``` -Pri dostopu do starega naslova URL predstavnik samodejno preusmeri na novi naslov URL, tako da iskalniki teh strani ne indeksirajo dvakrat (glejte [SEO in kanonizacijo |#SEO and canonization]). +Pri dostopu do starega URL-ja presenter samodejno preusmeri na nov URL, tako da vam te strani iskalniki ne indeksirajo dvakrat (glej [#SEO in kanonizacija]). -Moduli .[#toc-modules] ----------------------- +Dinamično usmerjanje s povratnimi klici +--------------------------------------- -Če imamo več poti, ki pripadajo enemu [modulu |modules], jih lahko z uporabo `withModule()` združimo v skupine: +Dinamično usmerjanje s povratnimi klici (callbacks) vam omogoča, da potem dodelite neposredno funkcije (callbacke), ki se izvedejo, ko je dana pot obiskana. Ta fleksibilna funkcionalnost vam omogoča hitro in učinkovito ustvarjanje različnih končnih točk (endpoints) za vašo aplikacijo: + +```php +$router->addRoute('test', function () { + echo 'ste na naslovu /test'; +}); +``` + +Lahko tudi definirate v maski parametre, ki se samodejno posredujejo v vaš callback: + +```php +$router->addRoute('', function (string $lang) { + echo match ($lang) { + 'sl' => 'Dobrodošli na slovenski različici našega spletnega mesta!', + 'en' => 'Welcome to the English version of our website!', + }; +}); +``` + + +Moduli +------ + +Če imamo več poti, ki spadajo v skupni [modul |directory-structure#Presenterji in predloge], uporabimo `withModule()`: ```php $router = new RouteList; -$router->withModule('Forum') // naslednji usmerjevalniki so del modula Forum - ->addRoute('rss', 'Feed:rss') // predstavnik je Forum:Feed +$router->withModule('Forum') // naslednje poti so del modula Forum + ->addRoute('rss', 'Feed:rss') // presenter bo Forum:Feed ->addRoute('/') - ->withModule('Admin') // Naslednji usmerjevalniki so del modula Forum:Admin + ->withModule('Admin') // naslednje poti so del modula Forum:Admin ->addRoute('sign:in', 'Sign:in'); ``` -Druga možnost je uporaba parametra `module`: +Alternativa je uporaba parametra `module`: ```php -// URL upravljam/dashboard/default se prikaže v predstavniku Admin:Dashboard +// URL manage/dashboard/default se preslika na presenter Admin:Dashboard $router->addRoute('manage//', [ 'module' => 'Admin', ]); ``` -Poddomene .[#toc-subdomains] ----------------------------- +Poddomene +--------- -Zbirke poti lahko združite po poddomenah: +Zbirke poti lahko členimo po poddomenah: ```php $router = new RouteList; @@ -379,7 +401,7 @@ $router->withDomain('example.com') ->addRoute('/'); ``` -V imenu domene lahko uporabite tudi [nadomestne znake |#wildcards]: +V imenu domene lahko uporabimo tudi [#Nadomestni znaki]: ```php $router = new RouteList; @@ -388,23 +410,23 @@ $router->withDomain('example.%tld%') ``` -Predpona poti .[#toc-path-prefix] ---------------------------------- +Predpona poti +------------- -Zbirke poti lahko združite po poti v URL: +Zbirke poti lahko členimo po poti v URL-ju: ```php $router = new RouteList; $router->withPath('eshop') - ->addRoute('rss', 'Feed:rss') // ustreza naslovu URL /eshop/rss - ->addRoute('/'); // ustreza naslovu URL /eshop// + ->addRoute('rss', 'Feed:rss') // ujame URL /eshop/rss + ->addRoute('/'); // ujame URL /eshop// ``` -Kombinacije .[#toc-combinations] --------------------------------- +Kombinacije +----------- -Zgornje načine uporabe je mogoče kombinirati: +Zgoraj navedeno členjenje lahko medsebojno kombiniramo: ```php $router = (new RouteList) @@ -424,40 +446,40 @@ $router = (new RouteList) ``` -Parametri poizvedbe .[#toc-query-parameters] --------------------------------------------- +Poizvedbeni parametri +--------------------- -Maske lahko vsebujejo tudi parametre poizvedbe (parametri za vprašalnim znakom v naslovu URL). Ne morejo opredeliti izraza za preverjanje, lahko pa spremenijo ime, pod katerim so posredovani predstavniku: +Maske lahko vsebujejo tudi poizvedbene parametre (parametre za vprašajem v URL-ju). Tem ni mogoče definirati validacijskega izraza, vendar lahko spremenimo ime, pod katerim se posredujejo v presenter: ```php -// uporabite parameter poizvedbe 'cat' kot 'categoryId' v aplikaciji +// poizvedbeni parameter 'cat' želimo v aplikaciji uporabiti pod imenom 'categoryId' $router->addRoute('product ? id= & cat=', /* ... */); ``` -Foo Parametri .[#toc-foo-parameters] ------------------------------------- +Foo parametri +------------- -Zdaj se bomo poglobili. Parametri Foo so pravzaprav neimenovani parametri, ki omogočajo ujemanje z regularnim izrazom. Naslednja pot ustreza `/index`, `/index.html`, `/index.htm` in `/index.php`: +Zdaj gremo že globlje. Foo parametri so v bistvu neimenovani parametri, ki omogočajo ujemanje regularnega izraza. Primer je pot, ki sprejema `/index`, `/index.html`, `/index.htm` in `/index.php`: ```php $router->addRoute('index', /* ... */); ``` -Prav tako je mogoče izrecno določiti niz, ki bo uporabljen za generiranje URL-jev. Niz mora biti nameščen neposredno za vprašalnim znakom. Naslednja pot je podobna prejšnji, vendar generira `/index.html` namesto `/index`, ker je niz `.html` nastavljen kot "generirana vrednost". +Lahko tudi eksplicitno definiramo niz, ki bo uporabljen pri generiranju URL-ja. Niz mora biti umeščen neposredno za vprašajem. Naslednja pot je podobna prejšnji, vendar generira `/index.html` namesto `/index`, ker je niz `.html` nastavljen kot generacijska vrednost: ```php $router->addRoute('index', /* ... */); ``` -Integracija .[#toc-integration] -=============================== +Vključitev v aplikacijo +======================= -Da bi naš usmerjevalnik povezali z aplikacijo, moramo o tem obvestiti vsebnik DI. Najlažje je pripraviti tovarno, ki bo zgradila objekt usmerjevalnika, in povedati konfiguraciji vsebnika, naj jo uporabi. Recimo, da v ta namen napišemo metodo `App\Router\RouterFactory::createRouter()`: +Da bi ustvarjeni usmerjevalnik vključili v aplikacijo, moramo o njem povedati DI vsebnika. Najlažja pot je pripraviti tovarno, ki bo objekt usmerjevalnika izdelala, in sporočiti v konfiguraciji vsebnika, da jo naj uporabi. Recimo, da za ta namen napišemo metodo `App\Core\RouterFactory::createRouter()`: ```php -namespace App\Router; +namespace App\Core; use Nette\Application\Routers\RouteList; @@ -472,14 +494,14 @@ class RouterFactory } ``` -Nato zapišemo v [konfiguracijo |dependency-injection:services]: +V [konfiguracijo |dependency-injection:services] nato zapišemo: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter ``` -Vse odvisnosti, kot je povezava s podatkovno bazo itd., se metodi tovarne posredujejo kot njeni parametri z uporabo [samodejnega povezovanja |dependency-injection:autowiring]: +Kakršnekoli odvisnosti, na primer od podatkovne baze itd., se posredujejo tovarniški metodi kot njeni parametri s pomočjo [autowiringa |dependency-injection:autowiring]: ```php public static function createRouter(Nette\Database\Connection $db): RouteList @@ -489,25 +511,25 @@ public static function createRouter(Nette\Database\Connection $db): RouteList ``` -SimpleRouter .[#toc-simplerouter] -================================= +SimpleRouter +============ -Precej preprostejši usmerjevalnik od zbirke poti je [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Uporabimo ga lahko, kadar ni potrebe po posebni obliki URL, kadar `mod_rewrite` (ali alternative) ni na voljo ali kadar se preprosto še ne želimo ukvarjati z uporabniku prijaznimi URL-ji. +Veliko enostavnejši usmerjevalnik kot zbirka poti je [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Uporabimo ga takrat, ko nimamo posebnih zahtev glede oblike URL-ja, če ni na voljo `mod_rewrite` (ali njegove alternative) ali če zaenkrat ne želimo reševati lepih URL-jev. -Generira naslove v približno takšni obliki: +Generira naslove približno v tej obliki: ``` http://example.com/?presenter=Product&action=detail&id=123 ``` -Parameter konstruktorja `SimpleRouter` je privzeti predstavnik in dejanje, tj. dejanje, ki se izvede, če odpremo npr. `http://example.com/` brez dodatnih parametrov. +Parameter konstruktorja SimpleRouterja je privzeti presenter & akcija, na katerega naj se usmerja, če odpremo stran brez parametrov, npr. `http://example.com/`. ```php -// privzeto za predstavitelja 'Home' in akcijo 'default' +// privzeti presenter bo 'Home' in akcija 'default' $router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` -Priporočamo, da se SimpleRouter opredeli neposredno v [konfiguraciji |dependency-injection:services]: +Priporočamo, da SimpleRouter neposredno definirate v [konfiguraciji |dependency-injection:services]: ```neon services: @@ -515,22 +537,22 @@ services: ``` -SEO in kanonizacija .[#toc-seo-and-canonization] -================================================ +SEO in kanonizacija +=================== -Okvir izboljša SEO (optimizacijo za iskalnike), saj preprečuje podvajanje vsebine na različnih URL-jih. Če je več naslovov povezanih z istim ciljem, npr. `/index` in `/index.html`, ogrodje določi prvega kot primarnega (kanoničnega) in nanj preusmeri druge z uporabo kode HTTP 301. Zaradi tega iskalniki strani ne indeksirajo dvakrat in ne porušijo njihovega ranga strani. . +Ogrodje prispeva k SEO (optimizaciji najdljivosti na internetu) s tem, da preprečuje podvojenost vsebine na različnih URL-jih. Če do določenega cilja vodi več naslovov, npr. `/index` in `/index.html`, ogrodje prvega od njih določi za primarnega (kanoničnega) in ostale nanj preusmeri s pomočjo HTTP kode 301. Zahvaljujoč temu vam iskalniki strani ne indeksirajo dvakrat in ne razpršijo njihovega page ranka. -Ta postopek se imenuje kanonizacija. Kanonični URL je tisti, ki ga ustvari usmerjevalnik, tj. prva ustrezna pot v [zbirki |#route-collection] brez oznake OneWay. Zato v zbirki najprej navedemo **primarne poti**. +Temu procesu rečemo kanonizacija. Kanonični URL je tisti, ki ga generira usmerjevalnik, tj. prva ustrezna pot v zbirki brez zastavice OneWay. Zato v zbirki navajamo **primarne poti kot prve**. -Kanonizacijo izvede predstavnik, več v poglavju [Kanonizacija |presenters#Canonization]. +Kanonizacijo izvaja presenter, več v poglavju [kanonizacija |presenters#Kanonizacija]. -HTTPS .[#toc-https] -=================== +HTTPS +===== -Za uporabo protokola HTTPS ga je treba aktivirati na gostovanju in konfigurirati strežnik. +Da bi lahko uporabljali HTTPS protokol, ga je treba omogočiti na gostovanju in pravilno konfigurirati strežnik. -Preusmeritev celotnega spletnega mesta na protokol HTTPS je treba izvesti na ravni strežnika, na primer z datoteko .htaccess v korenskem imeniku naše aplikacije, s kodo HTTP 301. Nastavitve se lahko razlikujejo glede na gostovanje in so videti nekako takole: +Preusmeritev celotnega spletnega mesta na HTTPS je treba nastaviti na ravni strežnika, na primer s pomočjo datoteke .htaccess v korenskem direktoriju naše aplikacije, in to s HTTP kodo 301. Nastavitev se lahko razlikuje glede na gostovanje in izgleda približno takole: ``` @@ -542,40 +564,40 @@ Preusmeritev celotnega spletnega mesta na protokol HTTPS je treba izvesti na rav ``` -Usmerjevalnik ustvari naslov URL z enakim protokolom, kot je bila naložena stran, zato ni treba nastavljati ničesar drugega. +Usmerjevalnik generira URL z istim protokolom, s katerim je bila stran naložena, zato ni treba ničesar več nastavljati. -Če pa izjemoma potrebujemo, da se različne poti izvajajo pod različnimi protokoli, bomo to določili v maski poti: +Če pa izjemoma potrebujemo, da različne poti tečejo pod različnimi protokoli, ga navedemo v maski poti: ```php -// Ustvari naslov HTTP +// Generiral bo naslov s HTTP $router->addRoute('http://%host%//', /* ... */); -// Ustvari naslov HTTPS +// Generiral bo naslov s HTTPS $router->addRoute('https://%host%//', /* ... */); ``` -Razhroščevanje usmerjevalnika .[#toc-debugging-router] -====================================================== +Razhroščevanje usmerjevalnika +============================= -Vrstica za usmerjanje, prikazana v [Tracy Bar |tracy:], je uporabno orodje, ki prikazuje seznam poti in tudi parametre, ki jih je usmerjevalnik pridobil iz URL-ja. +Usmerjevalna plošča, ki se prikazuje v [Tracy Baru |tracy:], je koristen pomočnik, ki prikazuje seznam poti in tudi parametrov, ki jih je usmerjevalnik pridobil iz URL-ja. -Zelena vrstica s simbolom ✓ predstavlja pot, ki je ustrezala trenutnemu naslovu URL, modre vrstice s simboli ≈ pa označujejo poti, ki bi prav tako ustrezale naslovu URL, če jih zelena ne bi prehitela. V nadaljevanju vidimo trenutni predstavnik in dejanje. +Zelena vrstica s simbolom ✓ predstavlja pot, ki je obdelala trenutni URL, z modro barvo in simbolom ≈ so označene poti, ki bi prav tako obdelale URL, če jih zelena ne bi prehitela. Nato vidimo trenutni presenter & akcijo. [* routing-debugger.webp *] -Hkrati je v primeru nepričakovane preusmeritve zaradi [kanonizacije |#SEO and Canonization] koristno pogledati v vrstico *preusmeritev*, da vidimo, kako je usmerjevalnik prvotno razumel naslov URL in zakaj ga je preusmeril. +Hkrati, če pride do nepričakovane preusmeritve zaradi [kanonizacije |#SEO in kanonizacija], je koristno pogledati v ploščo v vrstici *redirect*, kjer ugotovite, kako je usmerjevalnik URL prvotno razumel in zakaj je preusmeril. .[note] -Pri odpravljanju napak usmerjevalnika je priporočljivo, da v brskalniku odprete programerska orodja (Ctrl+Shift+I ali Cmd+Option+I) in onemogočite predpomnilnik na plošči Omrežje, da se vanj ne shranjujejo preusmeritve. +Pri razhroščevanju usmerjevalnika priporočamo, da v brskalniku odprete Developer Tools (Ctrl+Shift+I ali Cmd+Option+I) in v plošči Network izklopite predpomnilnik, da se vanj ne shranjujejo preusmeritve. -Uspešnost .[#toc-performance] -============================= +Zmogljivost +=========== -Število poti vpliva na hitrost usmerjevalnika. Njihovo število vsekakor ne sme presegati nekaj deset. Če ima vaše spletno mesto preveč zapleteno strukturo URL, lahko napišete [usmerjevalnik po meri |#custom router]. +Število poti vpliva na hitrost usmerjevalnika. Njihovo število zagotovo ne bi smelo preseči nekaj deset. Če ima vaše spletno mesto preveč zapleteno strukturo URL-jev, si lahko napišete po meri [#Lasten usmerjevalnik]. -Če usmerjevalnik nima odvisnosti, na primer od podatkovne zbirke, in njegova tovarna nima argumentov, lahko njegovo sestavljeno obliko serializiramo neposredno v vsebnik DI in tako aplikacijo nekoliko pohitrimo. +Če usmerjevalnik nima nobenih odvisnosti, na primer od podatkovne baze, in njegova tovarna ne sprejema nobenih argumentov, lahko njegovo sestavljeno obliko serializiramo neposredno v DI vsebnik in s tem aplikacijo nekoliko pospešimo. ```neon routing: @@ -583,10 +605,10 @@ routing: ``` -Usmerjevalnik po meri .[#toc-custom-router] -=========================================== +Lasten usmerjevalnik +==================== -Naslednje vrstice so namenjene zelo naprednim uporabnikom. Ustvarite lahko svoj usmerjevalnik in ga seveda dodate v zbirko poti. Usmerjevalnik je implementacija vmesnika [api:Nette\Routing\Router] z dvema metodama: +Naslednje vrstice so namenjene zelo naprednim uporabnikom. Lahko si ustvarite lasten usmerjevalnik in ga popolnoma naravno vključite v zbirko poti. Usmerjevalnik je implementacija vmesnika [api:Nette\Routing\Router] z dvema metodama: ```php use Nette\Http\IRequest as HttpRequest; @@ -606,8 +628,7 @@ class MyRouter implements Nette\Routing\Router } ``` -Metoda `match` obdela trenutni [zahtevek $httpRequest |http:request], iz katerega je mogoče pridobiti ne le naslov URL, temveč tudi glave itd., v polje, ki vsebuje ime predstavnika in njegove parametre. Če zahteve ne more obdelati, vrne ničlo. -Pri obdelavi zahteve moramo vrniti vsaj predstavnika in akcijo. Ime predstavnika je popolno in vključuje vse module: +Metoda `match` obdela trenutni zahtevek [$httpRequest |http:request], iz katerega lahko pridobimo ne samo URL, ampak tudi glave itd., v polje, ki vsebuje ime presenterja in njegove parametre. Če zahtevka ne zna obdelati, vrne null. Pri obdelavi zahtevka moramo vrniti vsaj presenter in akcijo. Ime presenterja je popolno in vsebuje tudi morebitne module: ```php [ @@ -616,9 +637,9 @@ Pri obdelavi zahteve moramo vrniti vsaj predstavnika in akcijo. Ime predstavnika ] ``` -Metoda `constructUrl`, po drugi strani pa iz niza parametrov ustvari absolutni naslov URL. Uporabi lahko podatke iz parametra `$refUrl`, ki je trenutni naslov URL. +Metoda `constructUrl` nasprotno sestavi iz polja parametrov končni absolutni URL. Pri tem lahko uporabi informacije iz parametra [`$refUrl`|api:Nette\Http\UrlScript], kar je trenutni URL. -Če želite zbirki poti dodati usmerjevalnik po meri, uporabite `add()`: +V zbirko poti ga dodate s pomočjo `add()`: ```php $router = new Nette\Application\Routers\RouteList; @@ -628,19 +649,19 @@ $router->addRoute(/* ... */); ``` -Ločena uporaba .[#toc-separated-usage] -====================================== +Samostojna uporaba +================== -Z ločeno uporabo mislimo na uporabo zmožnosti usmerjevalnika v aplikaciji, ki ne uporablja aplikacije Nette in predstavnikov. Zanjo velja skoraj vse, kar smo prikazali v tem poglavju, z naslednjimi razlikami: +Samostojna uporaba pomeni uporabo sposobnosti usmerjevalnika v aplikaciji, ki ne uporablja Nette Application in presenterjev. Zanj velja skoraj vse, kar smo si v tem poglavju pokazali, s temi razlikami: - za zbirke poti uporabljamo razred [api:Nette\Routing\RouteList] -- kot preprost razred usmerjevalnika [api:Nette\Routing\SimpleRouter] -- ker ni para `Presenter:action`, uporabljamo [zapis Advanced |#Advanced notation] +- kot preprost usmerjevalnik razred [api:Nette\Routing\SimpleRouter] +- ker ne obstaja par `Presenter:action`, uporabljamo [#Razširjeni zapis] -Tako bomo ponovno ustvarili metodo, ki bo zgradila usmerjevalnik, na primer: +Torej spet ustvarimo metodo, ki nam bo sestavila usmerjevalnik, npr.: ```php -namespace App\Router; +namespace App\Core; use Nette\Routing\RouteList; @@ -661,35 +682,35 @@ class RouterFactory } ``` -Če uporabljate vsebnik DI, kar priporočamo, ponovno dodajte metodo v konfiguracijo in nato skupaj z zahtevo HTTP iz vsebnika pridobite usmerjevalnik: +Če uporabljate DI vsebnik, kar priporočamo, spet metodo dodamo v konfiguracijo in nato usmerjevalnik skupaj s HTTP zahtevkom pridobimo iz vsebnika: ```php $router = $container->getByType(Nette\Routing\Router::class); $httpRequest = $container->getByType(Nette\Http\IRequest::class); ``` -Ali pa bomo predmete ustvarili neposredno: +Ali pa objekte neposredno izdelamo: ```php -$router = App\Router\RouterFactory::createRouter(); +$router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` -Zdaj moramo omogočiti delovanje usmerjevalnika: +Zdaj preostane le še, da usmerjevalnik spustimo k delu: ```php $params = $router->match($httpRequest); if ($params === null) { - // ni bila najdena ustrezna pot, pošljemo napako 404. + // ni bila najdena ustrezna pot, pošljemo napako 404 exit; } -// obdelamo prejete parametre +// obdelamo pridobljene parametre $controller = $params['controller']; // ... ``` -In obratno, usmerjevalnik bomo uporabili za vzpostavitev povezave: +In obratno uporabimo usmerjevalnik za sestavljanje povezave: ```php $params = ['controller' => 'ArticleController', 'id' => 123]; diff --git a/application/sl/templates.texy b/application/sl/templates.texy index 2c34a4a33b..394f2baae2 100644 --- a/application/sl/templates.texy +++ b/application/sl/templates.texy @@ -2,15 +2,15 @@ Predloge ******** .[perex] -Nette uporablja sistem predlog [Latte |latte:]. Latte se uporablja, ker je najbolj varen sistem predlog za PHP in hkrati najbolj intuitiven. Ni se vam treba naučiti veliko novega, poznati morate le PHP in nekaj oznak Latte. +Nette uporablja sistem predlog [Latte |latte:]. Delsno zato, ker gre za najbolj varen sistem predlog za PHP, in hkrati tudi najbolj intuitiven sistem. Ni se vam treba učiti veliko novega, zadostuje znanje PHP in nekaj značk. -Običajno je stran dokončana iz predloge za postavitev + predloge za akcijo. Tako je lahko videti predloga za postavitev, opazite bloke `{block}` in oznake `{include}`: +Običajno je, da se stran sestavi iz predloge postavitve + predloge dane akcije. Takole na primer lahko izgleda predloga postavitve, opazite bloke `{block}` in značko `{include}`: ```latte - {block title}My App{/block} + {block title}Moja Aplikacija{/block}
    ...
    @@ -20,61 +20,109 @@ Običajno je stran dokončana iz predloge za postavitev + predloge za akcijo. Ta ``` -To pa je lahko predloga za dejanja: +In tole bo predloga akcije: ```latte -{block title}Homepage{/block} +{block title}Domača stran{/block} {block content} -

    Homepage

    +

    Domača stran

    ... {/block} ``` -V njej je opredeljen blok `content`, ki se v postavitev vstavi namesto bloka `{include content}`, prav tako pa je na novo opredeljen blok `title`, ki v postavitvi prepiše blok `{block title}`. Poskusite si predstavljati rezultat. +Ta definira blok `content`, ki se vstavi na mesto `{include content}` v postavitvi, in tudi ponovno definira blok `title`, s katerim prepiše `{block title}` v postavitvi. Poskusite si predstavljati rezultat. -Iskanje predlog .[#toc-search-for-templates] --------------------------------------------- +Iskanje predlog +--------------- -Pot do predlog se določi po preprosti logiki. Poskusi preveriti, ali obstaja ena od teh datotek s predlogami glede na imenik, v katerem se nahaja razred presenter, kjer `` je ime trenutnega predstavnika in `` je ime trenutnega dejanja: +Ni vam treba v presenterjih navajati, katera predloga naj se izriše, ogrodje pot izpelje samo in vam prihrani pisanje. -- `templates//.latte` -- `templates/..latte` +Če uporabljate strukturo map, kjer ima vsak presenter svojo mapo, preprosto namestite predlogo v to mapo pod imenom akcije (oz. view), tj. za akcijo `default` uporabite predlogo `default.latte`: -Če predloge ne najde, je odgovor [napaka 404 |presenters#Error 404 etc.]. +/--pre +app/ +└── Presentation/ + └── Home/ + ├── HomePresenter.php + └── default.latte +\-- -Pogled lahko spremenite tudi z uporabo `$this->setView('otherView')`. Lahko pa namesto iskanja neposredno določite ime datoteke s predlogo z uporabo `$this->template->setFile('/path/to/template.latte')`. +Če uporabljate strukturo, kjer so skupaj presenterji v eni mapi in predloge v mapi `templates`, jo shranite bodisi v datoteko `..latte` ali `/.latte`: + +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── Home.default.latte ← 1. varianta + └── Home/ + └── default.latte ← 2. varianta +\-- + +Mapa `templates` je lahko nameščena tudi eno raven višje, tj. na isti ravni, kot je mapa z razredi presenterjev. + +Če predloga ni najdena, presenter odgovori z [napako 404 - stran ni najdena |presenters#Napaka 404 in podobno]. + +View spremenite s pomočjo `$this->setView('jineView')`. Prav tako lahko neposredno določite datoteko s predlogo s pomočjo `$this->template->setFile('/path/to/template.latte')`. .[note] -Poti, po katerih se iščejo predloge, lahko spremenite tako, da nadgradite metodo [formatTemplateFiles |api:Nette\Application\UI\Presenter::formatTemplateFiles()], ki vrne polje možnih poti do datotek. +Datoteke, kjer se iščejo predloge, lahko spremenite s prepisom metode [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], ki vrne polje možnih imen datotek. + + +Iskanje predloge postavitve +--------------------------- + +Nette tudi samodejno išče datoteko s postavitvijo. + +Če uporabljate strukturo map, kjer ima vsak presenter svojo mapo, namestite postavitev bodisi v mapo s presenterjem, če je specifična samo zanj, ali eno raven višje, če je skupna za več presenterjev: + +/--pre +app/ +└── Presentation/ + ├── @layout.latte ← skupna postavitev + └── Home/ + ├── @layout.latte ← samo za presenter Home + ├── HomePresenter.php + └── default.latte +\-- + +Če uporabljate strukturo, kjer so skupaj presenterji v eni mapi in predloge v mapi `templates`, se bo postavitev pričakovala na teh mestih: -Postavitev se pričakuje v naslednjih datotekah: +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── @layout.latte ← skupna postavitev + ├── Home.@layout.latte ← samo za Home, 1. varianta + └── Home/ + └── @layout.latte ← samo za Home, 2. varianta +\-- -- `templates//@.latte` -- `templates/.@.latte` -- `templates/@.latte` postavitev, ki je skupna več predstavnikom +Če se presenter nahaja v modulu, se bo iskalo tudi na višjih ravneh map, glede na gnezdenje modula. -`` je ime trenutnega predavatelja in `` je ime postavitve, ki je privzeto `'layout'`. Ime lahko spremenite s `$this->setLayout('otherLayout')`, tako da se bodo poskušale uporabiti datoteke `@otherLayout.latte`. +Ime postavitve lahko spremenite s pomočjo `$this->setLayout('layoutAdmin')` in potem se bo pričakovalo v datoteki `@layoutAdmin.latte`. Prav tako lahko neposredno določite datoteko s predlogo postavitve s pomočjo `$this->setLayout('/path/to/template.latte')`. -Ime datoteke za predlogo postavitve lahko določite tudi neposredno z uporabo `$this->setLayout('/path/to/template.latte')`. Z uporabo spletne strani `$this->setLayout(false)` bo iskanje postavitve onemogočeno. +S pomočjo `$this->setLayout(false)` ali značke `{layout none}` znotraj predloge se iskanje postavitve izklopi. .[note] -Poti, po katerih se iščejo predloge, lahko spremenite tako, da nadgradite metodo [formatLayoutTemplateFiles |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], ki vrne polje možnih poti do datotek. +Datoteke, kjer se iščejo predloge postavitve, lahko spremenite s prepisom metode [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], ki vrne polje možnih imen datotek. -Spremenljivke v predlogi .[#toc-variables-in-the-template] ----------------------------------------------------------- +Spremenljivke v predlogi +------------------------ -Spremenljivke posredujemo predlogi tako, da jih zapišemo na naslov `$this->template`, nato pa so v predlogi na voljo kot lokalne spremenljivke: +Spremenljivke v predlogo posredujemo tako, da jih zapišemo v `$this->template` in potem jih imamo na voljo v predlogi kot lokalne spremenljivke: ```php $this->template->article = $this->articles->getById($id); ``` -Na ta način lahko predlogi enostavno posredujemo poljubne spremenljivke. Vendar je pri razvoju robustnih aplikacij pogosto bolj koristno, da se omejimo. Na primer tako, da izrecno določimo seznam spremenljivk, ki jih predloga pričakuje, in njihove vrste. To bo PHP omogočilo preverjanje tipov, IDE pravilno samodejno dopolnjevanje, statična analiza pa odkrivanje napak. +Tako enostavno lahko v predloge posredujemo kakršnekoli spremenljivke. Pri razvoju robustnih aplikacij pa je običajno bolj koristno se omejiti. Na primer tako, da eksplicitno definiramo seznam spremenljivk, ki jih predloga pričakuje, in njihovih tipov. Zahvaljujoč temu nam bo lahko PHP preverjal tipe, IDE pravilno predlagal in statična analiza odkrivala napake. -In kako definiramo takšen seznam? Preprosto v obliki razreda in njegovih lastnosti. Poimenujemo ga podobno kot presenter, vendar s `Template` na koncu: +In kako takšen seznam definiramo? Preprosto v obliki razreda in njegovih lastnosti. Poimenujemo ga podobno kot presenter, le s `Template` na koncu: ```php /** @@ -93,22 +141,22 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template } ``` -Objekt `$this->template` v predstavniku bo zdaj primerek razreda `ArticleTemplate`. PHP bo torej preveril deklarirane tipe, ko bodo zapisani. Od različice PHP 8.2 pa bo tudi opozoril na zapisovanje v neobstoječo spremenljivko; v prejšnjih različicah lahko enako dosežemo z uporabo lastnosti [Nette\SmartObject |utils:smartobject]. +Objekt `$this->template` v presenterju bo zdaj instanca razreda `ArticleTemplate`. Tako bo PHP pri zapisu preverjal deklarirane tipe. In od različice PHP 8.2 naprej bo opozoril tudi na zapis v neobstoječo spremenljivko, v prejšnjih različicah lahko isto dosežemo z uporabo traite [Nette\SmartObject |utils:smartobject]. -Opomba `@property-read` je namenjena IDE in statični analizi, zaradi nje bo delovalo samodejno dokončanje, glejte "PhpStorm and code completion for $this->template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. +Anotacija `@property-read` je namenjena za IDE in statično analizo, zahvaljujoč njej bo delovalo predlaganje, glej "PhpStorm and code completion for $this⁠-⁠>⁠template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. [* phpstorm-completion.webp *] -Privoščite si lahko tudi razkošje šepetanja v predlogah, samo namestite vtičnik Latte v PhpStorm in na začetku predloge navedite ime razreda, glejte članek "Latte: kako vtipkati sistem":https://blog.nette.org/sl/latte-kako-uporabljati-sistem-tipov: +Luksuza predlaganja si lahko privoščite tudi v predlogah, dovolj je v PhpStorm namestiti vtičnik za Latte in navesti na začetek predloge ime razreda, več v članku "Latte: kako do tipskega sistema":https://blog.nette.org/sl/latte-how-to-use-type-system: ```latte -{templateType App\Presenters\ArticleTemplate} +{templateType App\Presentation\Article\ArticleTemplate} ... ``` -Tako delujejo tudi predloge v komponentah, samo upoštevajte konvencijo poimenovanja in ustvarite razred predloge `FifteenTemplate` za komponento, npr. `FifteenControl`. +Tako delujejo tudi predloge v komponentah, dovolj je le upoštevati imensko konvencijo in za komponento npr. `FifteenControl` ustvariti razred predloge `FifteenTemplate`. -Če morate ustvariti `$template` kot primerek drugega razreda, uporabite metodo `createTemplate()`: +Če potrebujete ustvariti `$template` kot instanco drugega razreda, uporabite metodo `createTemplate()`: ```php public function renderDefault(): void @@ -121,43 +169,43 @@ public function renderDefault(): void ``` -Privzete spremenljivke .[#toc-default-variables] ------------------------------------------------- +Privzete spremenljivke +---------------------- -Predstavniki in komponente predlogam samodejno posredujejo več uporabnih spremenljivk: +Presenterji in komponente samodejno posredujejo v predloge nekaj uporabnih spremenljivk: -- `$basePath` je absolutna pot URL do korenskega dirja (na primer `/CD-collection`) -- `$baseUrl` je absolutna pot URL do korenskega dirja (na primer `http://localhost/CD-collection`) -- `$user` je objekt, ki [predstavlja uporabnika |security:authentication] -- `$presenter` je trenutni predavatelj -- `$control` je trenutna komponenta ali predvajalnik -- `$flashes` seznam [sporočil, |presenters#flash-messages] poslanih z metodo `flashMessage()` +- `$basePath` je absolutna URL pot do korenskega direktorija (npr. `/eshop`) +- `$baseUrl` je absolutni URL do korenskega direktorija (npr. `http://localhost/eshop`) +- `$user` je objekt [ki predstavlja uporabnika |security:authentication] +- `$presenter` je trenutni presenter +- `$control` je trenutna komponenta ali presenter +- `$flashes` polje [sporočil |presenters#Flash sporočila] poslanih s funkcijo `flashMessage()` -Če uporabljate razred predloge po meri, se te spremenljivke posredujejo, če zanje ustvarite lastnost. +Če uporabljate lasten razred predloge, se te spremenljivke posredujejo, če zanje ustvarite lastnost. -Ustvarjanje povezav .[#toc-creating-links] ------------------------------------------- +Ustvarjanje povezav +------------------- -V predlogi ustvarimo povezave do drugih predstavnikov in akcij na naslednji način: +V predlogi se ustvarjajo povezave na druge presenterje & akcije na ta način: ```latte -detail +podrobnosti izdelka ``` -Atribut `n:href` je zelo priročen za oznake HTML ``. Če želimo povezavo natisniti drugje, na primer v besedilu, uporabimo `{link}`: +Atribut `n:href` je zelo priročen za HTML značke ``. Če želimo povezavo izpisati drugje, na primer v besedilu, uporabimo `{link}`: ```latte -URL is: {link Home:default} +Naslov je: {link Home:default} ``` -Za več informacij glejte [Ustvarjanje povezav |Creating Links]. +Več informacij najdete v poglavju [Ustvarjanje URL povezav |creating-links]. -Filtri po meri, oznake itd. .[#toc-custom-filters-tags-etc] ------------------------------------------------------------ +Lastni filtri, značke ipd. +-------------------------- -Sistem predlog Latte je mogoče razširiti s filtri po meri, funkcijami, oznakami itd. To lahko storite neposredno v `render` ali `beforeRender()`: +Sistem predlog Latte lahko razširimo z lastnimi filtri, funkcijami, značkami ipd. To lahko storimo neposredno v metodi `render` ali `beforeRender()`: ```php public function beforeRender(): void @@ -165,16 +213,16 @@ public function beforeRender(): void // dodajanje filtra $this->template->addFilter('foo', /* ... */); - // ali neposredno konfigurirajte objekt Latte\Engine + // ali konfiguriramo neposredno objekt Latte\Engine $latte = $this->template->getLatte(); $latte->addFilterLoader(/* ... */); } ``` -Različica Latte 3 ponuja naprednejši način z ustvarjanjem [razširitve |latte:creating-extension] za vsak spletni projekt. Tukaj je približni primer takega razreda: +Latte v različici 3 ponuja naprednejši način in to je ustvarjanje si [extension |latte:extending-latte#Latte Extension] za vsak spletni projekt. Primer takšnega razreda: ```php -namespace App\Templating; +namespace App\Presentation\Accessory; final class LatteExtension extends Latte\Extension { @@ -207,22 +255,21 @@ final class LatteExtension extends Latte\Extension } ``` -Registriramo ga z uporabo [konfiguracije|configuration#Latte]: +Registriramo jo s pomočjo [konfiguracije |configuration#Predloge Latte]: ```neon latte: extensions: - - App\Templating\LatteExtension + - App\Presentation\Accessory\LatteExtension ``` -Prevajanje .[#toc-translating] ------------------------------- +Prevajanje +---------- -Če programirate večjezično aplikacijo, boste verjetno morali del besedila v predlogi izpisati v različnih jezikih. V ta namen je v ogrodju Nette opredeljen vmesnik za prevajanje [api:Nette\Localization\Translator], ki ima eno samo metodo `translate()`. Ta sprejme sporočilo `$message`, ki je običajno niz, in morebitne druge parametre. Naloga je vrniti prevedeni niz. -Privzete implementacije v Nette ni, glede na svoje potrebe lahko izbirate med več pripravljenimi rešitvami, ki jih najdete na [Componette |https://componette.org/search/localization]. V njihovi dokumentaciji je opisano, kako konfigurirati prevajalnik. +Če programirate večjezično aplikacijo, boste najverjetneje potrebovali nekatera besedila v predlogi izpisati v različnih jezikih. Nette Framework za ta namen definira vmesnik za prevajanje [api:Nette\Localization\Translator], ki ima eno samo metodo `translate()`. Ta sprejema sporočilo `$message`, kar je praviloma niz, in poljubne druge parametre. Naloga je vrniti preveden niz. V Nette ni nobene privzete implementacije, lahko si izberete glede na svoje potrebe iz več pripravljenih rešitev, ki jih najdete na [Componette |https://componette.org/search/localization]. V njihovi dokumentaciji boste izvedeli, kako prevajalnik konfigurirati. -Predloge lahko nastavimo s prevajalnikom, ki nam ga [bomo posredovali |dependency-injection:passing-dependencies], s pomočjo metode `setTranslator()`: +Predlogam lahko nastavimo prevajalnik, ki si ga [pustimo posredovati |dependency-injection:passing-dependencies], z metodo `setTranslator()`: ```php protected function beforeRender(): void @@ -232,38 +279,38 @@ protected function beforeRender(): void } ``` -Prevajalnik lahko nastavimo tudi s [konfiguracijo |configuration#Latte]: +Prevajalnik je alternativno mogoče nastaviti s pomočjo [konfiguracije |configuration#Predloge Latte]: ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -Prevajalnik lahko nato uporabimo na primer kot filter `|translate`, pri čemer metodi `translate()` posredujemo dodatne parametre (glej `foo, bar`): +Nato lahko prevajalnik uporabljamo na primer kot filter `|translate`, in to vključno z dopolnilnimi parametri, ki se posredujejo metodi `translate()` (glej `foo, bar`): ```latte -{='Basket'|translate} +{='Košarica'|translate} {$item|translate} {$item|translate, foo, bar} ``` -ali kot podčrtanka: +Ali kot podčrtajno značko: ```latte -{_'Basket'} +{_'Košarica'} {_$item} {_$item, foo, bar} ``` -Za prevajanje razdelkov predlog je na voljo parna oznaka `{translate}` (od različice Latte 2.11, prej se je uporabljala oznaka `{_}` ): +Za prevod odseka predloge obstaja parna značka `{translate}` (od Latte 2.11, prej se je uporabljala značka `{_}`): ```latte -{translate}Order{/translate} -{translate foo, bar}Order{/translate} +{translate}Naročilo{/translate} +{translate foo, bar}Naročilo{/translate} ``` -Prevajalnik se privzeto prikliče med izvajanjem, ko se izrisuje predloga. Različica Latte 3 pa lahko prevede vse statično besedilo med sestavljanjem predloge. To prihrani zmogljivost, saj se vsak niz prevede samo enkrat, dobljeni prevod pa se zapiše v sestavljeno obliko. Tako se v imeniku predpomnilnika ustvari več sestavljenih različic predloge, po ena za vsak jezik. Za to morate kot drugi parameter navesti le jezik: +Prevajalnik se standardno kliče med izvajanjem pri izrisovanju predloge. Latte različice 3 pa zna vsa statična besedila prevajati že med kompilacijo predloge. S tem se prihrani zmogljivost, ker se vsak niz prevede samo enkrat in končni prevod se zapiše v prevedeno obliko. V mapi s predpomnilnikom tako nastane več prevedenih različic predloge, ena za vsak jezik. Za to je dovolj le navesti jezik kot drugi parameter: ```php protected function beforeRender(): void @@ -273,4 +320,4 @@ protected function beforeRender(): void } ``` -S statičnim besedilom mislimo na primer na `{_'hello'}` ali `{translate}hello{/translate}`. Nestatično besedilo, kot je `{_$foo}`, se bo še naprej sestavljalo sproti. +Statično besedilo je mišljeno na primer `{_'hello'}` ali `{translate}hello{/translate}`. Nestatična besedila, kot na primer `{_$foo}`, se bodo še naprej prevajala med izvajanjem. diff --git a/application/tr/@home.texy b/application/tr/@home.texy index bb31500e7c..be4ca80d0f 100644 --- a/application/tr/@home.texy +++ b/application/tr/@home.texy @@ -2,35 +2,84 @@ Nette Application ***************** .[perex] -`nette/application` paketi, etkileşimli web uygulamaları oluşturmak için temel oluşturur. - -- [Uygulamalar nasıl çalışır? |how-it-works] -- [Bootstrap |Bootstrap] -- [Sunum Yapanlar |Presenters] -- [Şablonlar |Templates] -- [Modüller |Modules] -- [Yönlendirme |Routing] -- [URL Bağlantıları Oluşturma |creating-links] -- [İnteraktif Bileşenler |components] -- [AJAX ve Snippet'ler |ajax] -- [Çarpan |multiplier] -- [Konfigürasyon |Configuration] +Nette Application, modern web uygulamaları oluşturmak için güçlü araçlar sunan Nette framework'ünün çekirdeğidir. Geliştirmeyi önemli ölçüde kolaylaştıran ve kodun güvenliğini ve sürdürülebilirliğini artıran bir dizi olağanüstü özellik sunar. Kurulum ------- -[Composer'ı |best-practices:composer] kullanarak paketi indirin ve yükleyin: +Kütüphaneyi [Composer|best-practices:composer] aracını kullanarak indirip kurabilirsiniz: ```shell composer require nette/application ``` -| sürüm | PHP ile uyumlu + +Neden Nette Application'ı Seçmelisiniz? +--------------------------------------- + +Nette, web teknolojileri alanında her zaman öncü olmuştur. + +**Çift Yönlü Yönlendirici:** Nette, benzersiz çift yönlülüğü ile gelişmiş bir yönlendirme sistemine sahiptir - yalnızca URL'leri uygulama eylemlerine çevirmekle kalmaz, aynı zamanda geriye dönük olarak URL adresleri de oluşturabilir. Bu şu anlama gelir: +- Şablonları düzenlemeye gerek kalmadan tüm uygulamanın URL yapısını istediğiniz zaman değiştirebilirsiniz +- URL'ler otomatik olarak standartlaştırılır, bu da SEO'yu iyileştirir +- Yönlendirme, ek açıklamalara dağılmış olarak değil, tek bir yerde tanımlanır + +**Bileşenler ve Sinyaller:** Delphi ve React.js'den ilham alan yerleşik bileşen sistemi, PHP framework'leri arasında tamamen benzersizdir: +- Yeniden kullanılabilir UI öğeleri oluşturmanıza olanak tanır +- Hiyerarşik bileşen kompozisyonunu destekler +- Sinyalleri kullanarak AJAX isteklerinin zarif bir şekilde işlenmesini sunar +- [Componette](https://componette.org) üzerinde zengin hazır bileşen kütüphanesi + +**AJAX ve Snippet'ler:** Nette, Ruby on Rails için Hotwire veya Symfony UX Turbo gibi benzer çözümlerden çok önce, 2009'da AJAX ile çalışmanın devrim niteliğinde bir yolunu tanıttı: +- Snippet'ler, JavaScript yazmaya gerek kalmadan sayfanın yalnızca bölümlerini güncellemenizi sağlar +- Bileşen sistemiyle otomatik entegrasyon +- Sayfa bölümlerinin akıllıca geçersizleştirilmesi +- Minimum miktarda aktarılan veri + +**Sezgisel Şablonlar [Latte|latte:]:** Gelişmiş özelliklere sahip PHP için en güvenli şablonlama sistemi: +- Bağlama duyarlı kaçış (escaping) ile XSS'ye karşı otomatik koruma +- Özel filtreler, fonksiyonlar ve etiketler aracılığıyla genişletilebilirlik +- AJAX için şablon kalıtımı ve snippet'ler +- Tip sistemi ile PHP 8.x için mükemmel destek + +**Dependency Injection:** Nette, Dependency Injection'ı tam olarak kullanır: +- Bağımlılıkların otomatik olarak geçirilmesi (autowiring) +- Anlaşılır NEON formatı kullanılarak yapılandırma +- Bileşen fabrikaları için destek + + +Başlıca Avantajlar +------------------ + +- **Güvenlik**: XSS, CSRF vb. gibi [güvenlik açıklarına|nette:vulnerability-protection] karşı otomatik koruma. +- **Verimlilik**: Akıllı tasarım sayesinde daha az yazma, daha fazla işlev. +- **Hata Ayıklama**: Yönlendirme panelli [Tracy hata ayıklayıcı|tracy:]. +- **Performans**: Akıllı önbellek, bileşenlerin geç yüklenmesi (lazy loading). +- **Esneklik**: Uygulama tamamlandıktan sonra bile URL'lerin kolayca değiştirilmesi. +- **Bileşenler**: Yeniden kullanılabilir UI öğelerinin benzersiz sistemi. +- **Modern**: PHP 8.4+ ve tip sistemi için tam destek. + + +Başlarken +--------- + +1. [Uygulamalar nasıl çalışır? |how-it-works] - Temel mimariyi anlama +2. [Presenter'lar |presenters] - Presenter'lar ve eylemlerle çalışma +3. [Şablonlar |templates] - Latte'de şablon oluşturma +4. [Yönlendirme |routing] - URL adreslerini yapılandırma +5. [Etkileşimli bileşenler |components] - Bileşen sistemini kullanma + + +PHP ile Uyumluluk +----------------- + +| sürüm | PHP ile uyumlu |-----------|------------------- -| Nette Uygulama 4.0 | PHP 8.0 - 8.2 -| Nette Uygulama 3.1 | PHP 7.2 - 8.2 -| Nette Uygulama 3.0 | PHP 7.1 - 8.0 -| Nette Uygulama 2.4 | PHP 5.6 - 8.0 +| Nette Application 4.0 | PHP 8.1 – 8.4 +| Nette Application 3.2 | PHP 8.1 – 8.4 +| Nette Application 3.1 | PHP 7.2 – 8.3 +| Nette Application 3.0 | PHP 7.1 – 8.0 +| Nette Application 2.4 | PHP 5.6 – 8.0 -En son yama sürümleri için geçerlidir. +Son yama sürümü için geçerlidir. diff --git a/application/tr/@left-menu.texy b/application/tr/@left-menu.texy index 96ce183d38..05dd6798ff 100644 --- a/application/tr/@left-menu.texy +++ b/application/tr/@left-menu.texy @@ -1,19 +1,22 @@ -Nette Uygulama -************** +Nette Application +***************** - [Uygulamalar nasıl çalışır? |how-it-works] -- [Bootstrap |Bootstrap] -- [Sunum Yapanlar |Presenters] -- [Şablonlar |Templates] -- [Modüller |Modules] -- [Yönlendirme |Routing] +- [Bootstrapping] +- [Presenter'lar |presenters] +- [Şablonlar |templates] +- [Dizin yapısı |directory-structure] +- [Yönlendirme |routing] - [URL Bağlantıları Oluşturma |creating-links] -- [İnteraktif Bileşenler |components] -- [AJAX ve Snippet'ler |ajax] -- [Çarpan |multiplier] -- [Konfigürasyon |Configuration] +- [Etkileşimli bileşenler |components] +- [AJAX & snippet'ler |ajax] +- [Multiplier |Multiplier] +- [Yapılandırma |configuration] Daha Fazla Okuma **************** -- [En iyi uygulamalar |best-practices:] +- [Neden Nette kullanmalı? |www:10-reasons-why-nette] +- [Kurulum |nette:installation] +- [İlk uygulamamızı yazıyoruz! |quickstart:] +- [Kılavuzlar ve yöntemler |best-practices:] - [Sorun Giderme |nette:troubleshooting] diff --git a/application/tr/@meta.texy b/application/tr/@meta.texy new file mode 100644 index 0000000000..8dfe82f311 --- /dev/null +++ b/application/tr/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokümantasyonu}} diff --git a/application/tr/ajax.texy b/application/tr/ajax.texy index e40b00fd9a..1b680135c7 100644 --- a/application/tr/ajax.texy +++ b/application/tr/ajax.texy @@ -1,38 +1,45 @@ -AJAX ve Snippet'ler -******************* +AJAX & Snippet'ler +******************
    -Günümüzde modern web uygulamalarının yarısı sunucuda, yarısı da tarayıcıda çalışmaktadır. AJAX hayati bir birleştirici faktördür. Nette Framework ne gibi destekler sunuyor? -- Şablon parçaları gönderme (*snippet* olarak adlandırılır) -- PHP ve JavaScript arasında değişken aktarımı -- AJAX uygulamaları hata ayıklama +Sunucu ve tarayıcı arasında işlevselliğin sıklıkla bölündüğü modern web uygulamaları çağında, AJAX vazgeçilmez bir bağlantı elemanıdır. Nette Framework bize bu alanda hangi seçenekleri sunuyor? +- şablonun parçalarını, yani snippet'leri gönderme +- PHP ve JavaScript arasında değişkenleri iletme +- AJAX isteklerinin hatalarını ayıklama araçları
    -Bir AJAX isteği, `$httpRequest->isAjax()` [HTTP isteğini kapsülleyen |http:request] bir hizmet yöntemi kullanılarak algılanabilir ( `X-Requested-With` HTTP başlığına göre algılar). Ayrıca presenter'da steno bir yöntem de vardır: `$this->isAjax()`. -Bir AJAX isteği normal bir istekten farklı değildir - bir sunucu belirli bir görünüm ve parametrelerle çağrılır. Nasıl tepki vereceği de sunucuya bağlıdır: rutinlerini bir HTML kodu parçası (snippet), bir XML belgesi, bir JSON nesnesi veya bir Javascript kodu parçası döndürmek için kullanabilir. +AJAX İsteği +=========== -JSON'da tarayıcıya veri göndermeye adanmış `payload` adında önceden işlenmiş bir nesne vardır. +Bir AJAX isteği, temelde klasik bir HTTP isteğinden farklı değildir. Belirli parametrelerle bir presenter çağrılır. Ve isteğe nasıl yanıt vereceği presenter'a bağlıdır - JSON formatında veri döndürebilir, HTML kodunun bir kısmını, bir XML belgesini vb. gönderebilir. -```php -public function actionDelete(int $id): void -{ - if ($this->isAjax()) { - $this->payload->message = 'Success'; - } - // ... -} +Tarayıcı tarafında, `fetch()` fonksiyonunu kullanarak bir AJAX isteği başlatırız: + +```js +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) +.then(response => response.json()) +.then(payload => { + // yanıtın işlenmesi +}); ``` -JSON çıktınız üzerinde tam kontrol sahibi olmak için sunumunuzda `sendJson` yöntemini kullanın. Sunucuyu hemen sonlandırır ve şablon olmadan yaparsınız: +Sunucu tarafında, [HTTP isteğini kapsayan |http:request] servisin `$httpRequest->isAjax()` metoduyla bir AJAX isteğini tanırız. Algılama için `X-Requested-With` HTTP başlığını kullanır, bu yüzden onu göndermek önemlidir. Presenter içinde `$this->isAjax()` metodunu kullanabilirsiniz. + +Verileri JSON formatında göndermek istiyorsanız, [`sendJson()` |presenters#Yanıt Gönderme] metodunu kullanın. Metot ayrıca presenter'ın etkinliğini de sonlandırır. ```php -$this->sendJson(['key' => 'value', /* ... */]); +public function actionExport(): void +{ + $this->sendJson($this->model->getData); +} ``` -HTML göndermek istiyorsak, AJAX istekleri için özel bir şablon ayarlayabiliriz: +AJAX için tasarlanmış özel bir şablonla yanıt vermeyi planlıyorsanız, bunu aşağıdaki gibi yapabilirsiniz: ```php public function handleClick($param): void @@ -45,222 +52,198 @@ public function handleClick($param): void ``` -Naja .[#toc-naja] -================= +Snippet'ler +=========== + +Nette'nin sunucuyu istemciyle bağlamak için sunduğu en güçlü araç snippet'lerdir. Onlar sayesinde, sıradan bir uygulamayı minimum çaba ve birkaç satır kodla AJAX uygulamasına dönüştürebilirsiniz. Tüm bunların nasıl çalıştığını, kodunu [GitHub'da |https://github.com/nette-examples/fifteen] bulabileceğiniz Fifteen örneği göstermektedir. + +Snippet'ler veya kesitler, tüm sayfanın yeniden yüklenmesi yerine sayfanın yalnızca bölümlerini güncellemenize olanak tanır. Bu sadece daha hızlı ve daha verimli olmakla kalmaz, aynı zamanda daha rahat bir kullanıcı deneyimi de sağlar. Snippet'ler size Ruby on Rails için Hotwire'ı veya Symfony UX Turbo'yu hatırlatabilir. İlginç bir şekilde, Nette snippet'leri 14 yıl önce tanıttı. + +Snippet'ler nasıl çalışır? Sayfa ilk yüklendiğinde (AJAX olmayan istek), tüm snippet'ler dahil olmak üzere tüm sayfa yüklenir. Kullanıcı sayfayla etkileşime girdiğinde (örneğin, bir düğmeye tıkladığında, bir form gönderdiğinde vb.), tüm sayfayı yüklemek yerine bir AJAX isteği tetiklenir. Presenter'daki kod eylemi gerçekleştirir ve hangi snippet'lerin güncellenmesi gerektiğine karar verir. Nette bu snippet'leri oluşturur ve JSON formatında bir dizi olarak gönderir. Tarayıcıdaki işleyici kod, alınan snippet'leri sayfaya geri ekler. Bu nedenle, yalnızca değiştirilen snippet'lerin kodu aktarılır, bu da bant genişliğinden tasarruf sağlar ve tüm sayfanın içeriğini aktarmaya kıyasla yüklemeyi hızlandırır. + + +Naja +---- -[Naja kütüphanesi |https://naja.js.org], tarayıcı tarafında AJAX isteklerini işlemek için kullanılır. Bir node.js paketi olarak [yükleyin |https://naja.js.org/#/guide/01-install-setup-naja] (Webpack, Rollup, Vite, Parcel ve daha fazlası ile kullanmak için): +Snippet'leri tarayıcı tarafında işlemek için [Naja kütüphanesi |https://naja.js.org] kullanılır. Bunu bir node.js paketi olarak [kurun |https://naja.js.org/#/guide/01-install-setup-naja] (Webpack, Rollup, Vite, Parcel ve diğer uygulamalarla kullanım için): ```shell npm install naja ``` -...veya doğrudan sayfa şablonuna ekleyin: +…veya doğrudan sayfa şablonuna ekleyin: ```html ``` +Önce kütüphaneyi [başlatmanız |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] gerekir: -Parçacıklar .[#toc-snippets] -============================ +```js +naja.initialize(); +``` -Yerleşik AJAX desteğinin çok daha güçlü bir aracı vardır - snippet'ler. Bunları kullanmak, normal bir uygulamayı yalnızca birkaç satır kod kullanarak bir AJAX uygulamasına dönüştürmeyi mümkün kılar. Tüm bunların nasıl çalıştığı, koduna derlemede veya [GitHub'da |https://github.com/nette-examples/fifteen] da erişilebilen Fifteen örneğinde gösterilmiştir. +Sıradan bir bağlantıdan (sinyal) veya form gönderiminden bir AJAX isteği oluşturmak için, ilgili bağlantıyı, formu veya düğmeyi `ajax` sınıfıyla işaretlemek yeterlidir: -Snippet'lerin çalışma şekli, ilk (yani AJAX olmayan) istek sırasında tüm sayfanın aktarılması ve ardından her AJAX [alt |components#signal] isteğinde (aynı sunucunun aynı görünümünün isteği) yalnızca değiştirilen parçaların kodunun daha önce bahsedilen `payload` deposuna aktarılmasıdır. +```html +Git -Snippet'ler size Ruby on Rails için Hotwire veya Symfony UX Turbo'yu hatırlatabilir, ancak Nette bunları on dört yıl önce buldu. +
    + +
    +veya + +
    + +
    +``` -Snippet'lerin Geçersiz Kılınması .[#toc-invalidation-of-snippets] -================================================================= -[Control |components] sınıfının her bir torunu (ki bir Presenter da öyledir), bir istek sırasında yeniden oluşturmasını gerektiren herhangi bir değişiklik olup olmadığını hatırlayabilir. Bunu işlemek için bir çift yöntem vardır: `redrawControl()` ve `isControlInvalid()`. Bir örnek: +Snippet'lerin Yeniden Çizilmesi +------------------------------- + +[Control |components] sınıfının her nesnesi (Presenter'ın kendisi dahil), yeniden çizilmesini gerektiren değişiklikler olup olmadığını takip eder. Bunun için `redrawControl()` metodu kullanılır: ```php public function handleLogin(string $user): void { - // Nesne, kullanıcı giriş yaptıktan sonra yeniden oluşturulmalıdır + // giriş yaptıktan sonra ilgili bölümü yeniden çizmek gerekir $this->redrawControl(); // ... } ``` -Ancak Nette, tüm bileşenlerden daha da ince bir çözünürlük sunar. Listelenen yöntemler, isteğe bağlı bir parametre olarak "snippet" olarak adlandırılan bir ismi kabul eder. Bir "snippet" temel olarak şablonunuzdaki bir Latte makrosu tarafından bu amaç için işaretlenmiş bir öğedir, daha sonra bu konuda daha fazla bilgi verilecektir. Böylece bir bileşenden şablonunun sadece *parçalarını* yeniden çizmesini istemek mümkündür. Bileşenin tamamı geçersiz kılınırsa, tüm parçacıkları yeniden oluşturulur. Bir bileşen, alt bileşenlerinden herhangi birinin geçersiz olması durumunda da "geçersiz" olur. - -```php -$this->isControlInvalid(); // -> false -$this->redrawControl('header'); // 'header' adlı parçacığı geçersiz kılar -$this->isControlInvalid('header'); // -> true -$this->isControlInvalid('footer'); // -> false -$this->isControlInvalid(); // -> true, en az bir snippet geçersiz +Nette, neyin yeniden çizileceği konusunda daha da hassas kontrol sağlar. Bahsedilen metot, argüman olarak snippet adını alabilir. Böylece, şablonun bölümleri düzeyinde geçersiz kılma (yani yeniden çizmeyi zorlama) mümkündür. Tüm bileşen geçersiz kılınırsa, her bir snippet'i de yeniden çizilir: -$this->redrawControl(); // tüm bileşeni, her parçacığı geçersiz kılar -$this->isControlInvalid('footer'); // -> true +```php +// 'header' snippet'ini geçersiz kılar +$this->redrawControl('header'); ``` -Sinyal alan bir bileşen otomatik olarak yeniden çizilmek üzere işaretlenir. -Snippet yeniden çizimi sayesinde hangi öğelerin hangi kısımlarının yeniden işlenmesi gerektiğini tam olarak biliyoruz. +Latte'de Snippet'ler +-------------------- - -Etiket `{snippet} … {/snippet}` .{toc: Tag snippet} -=================================================== - -Sayfanın oluşturulması normal bir istekle çok benzer şekilde ilerler: aynı şablonlar yüklenir, vb. Ancak önemli olan, çıktıya ulaşmaması gereken kısımların dışarıda bırakılmasıdır; diğer kısımlar bir tanımlayıcı ile ilişkilendirilmeli ve bir JavaScript işleyicisi için anlaşılabilir bir biçimde kullanıcıya gönderilmelidir. - - -Sözdizimi .[#toc-syntax] ------------------------- - -Şablonda bir kontrol veya snippet varsa, bunu `{snippet} ... {/snippet}` pair etiketini kullanarak sarmalıyız - bu, işlenen snippet'in "kesilip çıkarılmasını" ve tarayıcıya gönderilmesini sağlayacaktır. Ayrıca onu bir yardımcı içine alacaktır `
    ` etiketi (farklı bir etiket kullanmak mümkündür). Aşağıdaki örnekte `header` adında bir snippet tanımlanmıştır. Bir bileşenin şablonunu da temsil edebilir: +Latte'de snippet kullanmak son derece kolaydır. Şablonun bir bölümünü snippet olarak tanımlamak için, onu `{snippet}` ve `{/snippet}` etiketleriyle sarmanız yeterlidir: ```latte {snippet header} -

    Hello ...

    +

    Merhaba ...

    {/snippet} ``` -dışındaki bir türden bir snippet `
    ` veya ek HTML öznitelikleri içeren bir snippet, öznitelik varyantı kullanılarak elde edilir: +Snippet, HTML sayfasında özel olarak oluşturulmuş bir `id` ile bir `
    ` öğesi oluşturur. Snippet yeniden çizildiğinde, bu öğenin içeriği güncellenir. Bu nedenle, sayfanın ilk oluşturulmasında, başlangıçta boş olsalar bile tüm snippet'lerin de oluşturulması gerekir. + +n:attribute kullanarak `
    ` dışında bir öğeyle de bir snippet oluşturabilirsiniz: ```latte
    -

    Hello ...

    +

    Merhaba ...

    ``` -Dinamik Parçacıklar .[#toc-dynamic-snippets] -============================================ +Snippet Alanları +---------------- -Nette'de ayrıca bir çalışma zamanı parametresine dayalı olarak dinamik bir adla snippet'ler tanımlayabilirsiniz. Bu, sadece bir satırı değiştirmemiz gereken ancak tüm listeyi onunla birlikte aktarmak istemediğimiz çeşitli listeler için en uygun olanıdır. Bunun bir örneği şöyle olabilir: +Snippet adları ifadeler de olabilir: ```latte -
      - {foreach $list as $id => $item} -
    • {$item} update
    • - {/foreach} -
    +{foreach $items as $id => $item} +
  • {$item}
  • +{/foreach} ``` -Birkaç dinamik snippet içeren `itemsContainer` adında bir statik snippet vardır: `item-0`, `item-1` ve benzeri. +Bu şekilde birkaç snippet oluşturulur: `item-0`, `item-1` vb. Dinamik bir snippet'i doğrudan geçersiz kılsaydık (örneğin `item-1`), hiçbir şey yeniden çizilmezdi. Bunun nedeni, snippet'lerin gerçekten kesitler gibi çalışması ve yalnızca kendilerinin doğrudan oluşturulmasıdır. Ancak şablonda aslında `item-1` adında bir snippet yoktur. Bu, yalnızca snippet'in etrafındaki kodun, yani foreach döngüsünün yürütülmesiyle ortaya çıkar. Bu nedenle, yürütülmesi gereken şablon bölümünü `{snippetArea}` etiketiyle işaretleriz: -Dinamik bir snippet'i doğrudan yeniden çizemezsiniz ( `item-1` adresinin yeniden çizilmesinin hiçbir etkisi yoktur), üst snippet'ini yeniden çizmeniz gerekir (bu örnekte `itemsContainer`). Bu, üst snippet'in kodunun yürütülmesine neden olur, ancak daha sonra tarayıcıya yalnızca alt snippet'leri gönderilir. Alt snippet'lerden yalnızca birini göndermek istiyorsanız, diğer alt snippet'leri oluşturmamak için ana snippet'in girdisini değiştirmeniz gerekir. +```latte +
      + {foreach $items as $id => $item} +
    • {$item}
    • + {/foreach} +
    +``` -Yukarıdaki örnekte, bir AJAX isteği için `$list` dizisine yalnızca bir öğe ekleneceğinden emin olmanız gerekir, bu nedenle `foreach` döngüsü yalnızca bir dinamik parçacık yazdıracaktır. +Ve hem snippet'in kendisini hem de tüm üst alanı yeniden çizeriz: ```php -class HomePresenter extends Nette\Application\UI\Presenter -{ - /** - * This method returns data for the list. - * Usually this would just request the data from a model. - * For the purpose of this example, the data is hard-coded. - */ - private function getTheWholeList(): array - { - return [ - 'First', - 'Second', - 'Third', - ]; - } - - public function renderDefault(): void - { - if (!isset($this->template->list)) { - $this->template->list = $this->getTheWholeList(); - } - } - - public function handleUpdate(int $id): void - { - $this->template->list = $this->isAjax() - ? [] - : $this->getTheWholeList(); - $this->template->list[$id] = 'Updated item'; - $this->redrawControl('itemsContainer'); - } -} +$this->redrawControl('itemsContainer'); +$this->redrawControl('item-1'); ``` +Aynı zamanda, `$items` dizisinin yalnızca yeniden çizilmesi gereken öğeleri içermesini sağlamak uygundur. -Dahil Edilen Bir Şablondaki Parçacıklar .[#toc-snippets-in-an-included-template] -================================================================================ - -Snippet, farklı bir şablondan dahil edilen bir şablonda olabilir. Bu durumda, ikinci şablondaki dahil etme kodunu `snippetArea` makrosuyla sarmamız gerekir, ardından hem snippetArea'yı hem de gerçek snippet'i yeniden çizeriz. - -Makro `snippetArea`, içindeki kodun yürütülmesini sağlar, ancak tarayıcıya yalnızca dahil edilen şablondaki gerçek parçacık gönderilir. +Şablona `{include}` etiketi kullanarak snippet'ler içeren başka bir şablon eklersek, şablon eklemesini tekrar `snippetArea` içine almalı ve onu snippet ile birlikte geçersiz kılmalıyız: ```latte -{* parent.latte *} -{snippetArea wrapper} - {include 'child.latte'} +{snippetArea include} + {include 'included.latte'} {/snippetArea} ``` + ```latte -{* child.latte *} +{* included.latte *} {snippet item} -... + ... {/snippet} ``` + ```php -$this->redrawControl('wrapper'); +$this->redrawControl('include'); $this->redrawControl('item'); ``` -Ayrıca dinamik snippet'lerle de birleştirebilirsiniz. - - -Ekleme ve Silme .[#toc-adding-and-deleting] -=========================================== -Listeye yeni bir öğe ekler ve `itemsContainer` adresini geçersiz kılarsanız, AJAX isteği yenisini de içeren parçacıkları döndürür, ancak javascript işleyicisi bunu işleyemez. Bunun nedeni, yeni oluşturulan ID'ye sahip bir HTML öğesi olmamasıdır. +Bileşenlerde Snippet'ler +------------------------ -Bu durumda, en basit yol tüm listeyi bir parçacığa daha sarmak ve hepsini geçersiz kılmaktır: +[Bileşenlerde|components] de snippet'ler oluşturabilirsiniz ve Nette bunları otomatik olarak yeniden çizer. Ancak burada belirli bir sınırlama vardır: snippet'leri yeniden çizmek için `render()` metodunu parametresiz çağırır. Yani, şablonda parametreleri iletmek işe yaramaz: ```latte -{snippet wholeList} -
      - {foreach $list as $id => $item} -
    • {$item} update
    • - {/foreach} -
    -{/snippet} -Add +OK +{control productGrid} + +işe yaramayacak: +{control productGrid $arg, $arg} +{control productGrid:paginator} ``` + +Kullanıcı Verilerini Gönderme +----------------------------- + +Snippet'lerle birlikte istemciye herhangi bir ek veri gönderebilirsiniz. Bunları `payload` nesnesine yazmanız yeterlidir: + ```php -public function handleAdd(): void +public function actionDelete(int $id): void { - $this->template->list = $this->getTheWholeList(); - $this->template->list[] = 'New one'; - $this->redrawControl('wholeList'); + // ... + if ($this->isAjax()) { + $this->payload->message = 'Başarılı'; + } } ``` -Aynı şey bir öğeyi silmek için de geçerlidir. Boş snippet göndermek mümkün olabilir, ancak genellikle listeler sayfalandırılabilir ve bir öğeyi silip diğerini (sayfalandırılmış listenin farklı bir sayfasında bulunan) yüklemek karmaşık olacaktır. - -Bileşene Parametre Gönderme .[#toc-sending-parameters-to-component] -=================================================================== +Parametreleri İletme +==================== -AJAX isteği aracılığıyla bileşene parametreler gönderdiğimizde, ister sinyal parametreleri ister kalıcı parametreler olsun, bileşenin adını da içeren global adlarını sağlamalıyız. Parametrenin tam adı `getParameterId()` yöntemini döndürür. +Bir bileşene AJAX isteği ile parametreler gönderirsek, bunlar ister sinyal parametreleri ister kalıcı parametreler olsun, istekte bileşenin adını da içeren global adlarını belirtmemiz gerekir. Parametrenin tam adını `getParameterId()` metodu döndürür. ```js -$.getJSON( - {link changeCountBasket!}, - { - {$control->getParameterId('id')}: id, - {$control->getParameterId('count')}: count - } -}); +let url = new URL({link //foo!}); +url.searchParams.set({$control->getParameterId('bar')}, bar); + +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) ``` -Ve bileşende karşılık gelen parametrelerle yöntemi işleyin. +Ve bileşendeki karşılık gelen parametrelerle handle metodu: ```php -public function handleChangeCountBasket(int $id, int $count): void +public function handleFoo(int $bar): void { - } ``` diff --git a/application/tr/bootstrap.texy b/application/tr/bootstrap.texy deleted file mode 100644 index c2829b9f89..0000000000 --- a/application/tr/bootstrap.texy +++ /dev/null @@ -1,233 +0,0 @@ -Bootstrap -********* - -
    - -Bootstrap, ortamı başlatan, bir bağımlılık enjeksiyonu (DI) konteyneri oluşturan ve uygulamayı başlatan önyükleme kodudur. Tartışacağız: - -- NEON dosyalarını kullanarak uygulamanızı nasıl yapılandırırsınız -- üretim ve geliştirme modlarının nasıl ele alınacağı -- DI konteyneri nasıl oluşturulur - -
    - - -İster web tabanlı ister komut satırı komut dosyaları olsun, uygulamalar bir tür ortam başlatma ile başlar. Eski zamanlarda, bundan sorumlu olan ve başlangıç dosyasına dahil edilen eg `include.inc.php` adlı bir dosya olabilirdi. -Modern Nette uygulamalarında bunun yerini, uygulamanın bir parçası olarak `app/Bootstrap.php` adresinde bulunabilen `Bootstrap` sınıfı almıştır. Örneğin aşağıdaki gibi görünebilir: - -```php -use Nette\Bootstrap\Configurator; - -class Bootstrap -{ - public static function boot(): Configurator - { - $appDir = dirname(__DIR__); - $configurator = new Configurator; - //$configurator->setDebugMode('secret@23.75.345.200'); - $configurator->enableTracy($appDir . '/log'); - $configurator->setTempDirectory($appDir . '/temp'); - $configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); - $configurator->addConfig($appDir . '/config/common.neon'); - return $configurator; - } -} -``` - - -index.php .[#toc-index-php] -=========================== - -Web uygulamaları söz konusu olduğunda, başlangıç dosyası `index.php` olup `www/` genel dizininde bulunur. Ortamı başlatmak için `Bootstrap` sınıfına izin verir ve DI konteynerini oluşturan `$configurator` sınıfını döndürür. Daha sonra web uygulamasını çalıştıran `Application` hizmetini elde eder: - -```php -// ortamı başlat + Configurator nesnesini al -$configurator = App\Bootstrap::boot(); -// bir DI konteyneri oluşturun -$container = $configurator->createContainer(); -// DI container bir Nette\Application\Application nesnesi oluşturur -$application = $container->getByType(Nette\Application\Application::class); -// Nette uygulamasını başlat -$application->run(); -``` - -Gördüğünüz gibi, şimdi daha ayrıntılı olarak tanıtacağımız [api:Nette\Bootstrap\Configurator] sınıfı, ortamın ayarlanmasına ve bir bağımlılık enjeksiyonu (DI) konteyneri oluşturulmasına yardımcı olur. - - -Geliştirme ve Üretim Modu .[#toc-development-vs-production-mode] -================================================================ - -Nette, bir talebin yürütüldüğü iki temel mod arasında ayrım yapar: geliştirme ve üretim. Geliştirme modu programcının maksimum konforuna odaklanır, Tracy görüntülenir, şablonlar veya DI konteyner yapılandırması değiştirilirken önbellek otomatik olarak güncellenir, vb. Üretim modu performansa odaklanır, Tracy yalnızca hataları günlüğe kaydeder ve şablonların ve diğer dosyaların değişiklikleri kontrol edilmez. - -Mod seçimi otomatik algılama ile yapılır, bu nedenle genellikle herhangi bir şeyi manuel olarak yapılandırmaya veya değiştirmeye gerek yoktur. Uygulama localhost üzerinde çalışıyorsa (yani IP adresi `127.0.0.1` veya `::1`) ve proxy yoksa (yani HTTP başlığı) mod geliştirmedir. Aksi takdirde, üretim modunda çalışır. - -Geliştirme modunu diğer durumlarda, örneğin belirli bir IP adresinden erişen programcılar için etkinleştirmek istiyorsanız, `setDebugMode()` adresini kullanabilirsiniz: - -```php -$configurator->setDebugMode('23.75.345.200'); // bir veya daha fazla IP adresi -``` - -Bir IP adresini bir çerezle birleştirmenizi kesinlikle öneririz. `nette-debug` çerezine gizli bir belirteç depolayacağız, örneğin `secret1234` ve geliştirme modu, bu IP ve çerez kombinasyonuna sahip programcılar için etkinleştirilecektir. - -```php -$configurator->setDebugMode('secret1234@23.75.345.200'); -``` - -Ayrıca localhost için bile geliştirici modunu tamamen kapatabiliriz: - -```php -$configurator->setDebugMode(false); -``` - -`true` değerinin, bir üretim sunucusunda asla gerçekleşmemesi gereken geliştirici modunu zorlayarak açtığını unutmayın. - - -Hata Ayıklama Aracı Tracy .[#toc-debugging-tool-tracy] -====================================================== - -Kolay hata ayıklama için harika araç [Tracy'yi |tracy:] açacağız. Geliştirici modunda hataları görselleştirir ve üretim modunda hataları belirtilen dizine kaydeder: - -```php -$configurator->enableTracy($appDir . '/log'); -``` - - -Geçici Dosyalar .[#toc-temporary-files] -======================================= - -Nette, DI konteyneri, RobotLoader, şablonlar vb. için önbelleği kullanır. Bu nedenle, önbelleğin depolanacağı dizinin yolunu ayarlamak gerekir: - -```php -$configurator->setTempDirectory($appDir . '/temp'); -``` - -Linux veya macOS üzerinde, `log/` ve `temp/` dizinleri için [yazma izinlerini |nette:troubleshooting#Setting directory permissions] ayarlayın. - - -RobotLoader .[#toc-robotloader] -=============================== - -Genellikle, [RobotLoader'ı |robot-loader:] kullanarak sınıfları otomatik olarak yüklemek isteyeceğiz, bu yüzden onu başlatmalı ve `Bootstrap.php` 'un bulunduğu dizinden (yani `__DIR__`) ve tüm alt dizinlerinden sınıfları yüklemesine izin vermeliyiz: - -```php -$configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); -``` - -Alternatif bir yol da sadece [Composer |best-practices:composer] PSR-4 otomatik yüklemeyi kullanmaktır. - - -Zaman Dilimi .[#toc-timezone] -============================= - -Yapılandırıcı, uygulamanız için bir saat dilimi belirlemenize olanak tanır. - -```php -$configurator->setTimeZone('Europe/Prague'); -``` - - -DI Konteyner Yapılandırması .[#toc-di-container-configuration] -============================================================== - -Önyükleme sürecinin bir parçası, tüm uygulamanın kalbi olan bir DI konteynerinin, yani nesneler için bir fabrikanın oluşturulmasıdır. Bu aslında Nette tarafından oluşturulan ve bir önbellek dizininde saklanan bir PHP sınıfıdır. Fabrika, temel uygulama nesnelerini üretir ve yapılandırma dosyaları, bunların nasıl oluşturulacağı ve yapılandırılacağı konusunda talimat verir ve böylece tüm uygulamanın davranışını etkileriz. - -Konfigürasyon dosyaları genellikle [NEON formatında |neon:format] yazılır. [Nelerin yapılandırılabileceğini buradan okuy abilirsiniz|nette:configuring]. - -.[tip] -Geliştirme modunda, kodu veya yapılandırma dosyalarını her değiştirdiğinizde kapsayıcı otomatik olarak güncellenir. Üretim modunda, yalnızca bir kez oluşturulur ve performansı en üst düzeye çıkarmak için dosya değişiklikleri kontrol edilmez. - -Yapılandırma dosyaları `addConfig()` kullanılarak yüklenir: - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -``` - -Birden fazla dosya eklemek için `addConfig()` yöntemi birden fazla kez çağrılabilir. - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -$configurator->addConfig($appDir . '/config/local.neon'); -if (PHP_SAPI === 'cli') { - $configurator->addConfig($appDir . '/config/cli.php'); -} -``` - -`cli.php` adı bir yazım hatası değildir, yapılandırma bir dizi olarak döndüren bir PHP dosyasına da yazılabilir. - -Alternatif olarak, daha fazla yapılandırma dosyası yüklemek için [`includes` bölümünü |dependency-injection:configuration#including files] kullanabiliriz. - -Aynı anahtarlara sahip öğeler yapılandırma dosyalarında görünürse, diziler söz konusu olduğunda bunların [üzerine yazılır veya birleştirilir |dependency-injection:configuration#Merging]. Daha sonra dahil edilen dosyanın bir öncekinden daha yüksek önceliği vardır. `includes` bölümünün listelendiği dosya, içine dahil edilen dosyalardan daha yüksek önceliğe sahiptir. - - -Statik Parametreler .[#toc-static-parameters] ---------------------------------------------- - -Yapılandırma dosyalarında kullanılan parametreler [`parameters` bölümünde |dependency-injection:configuration#parameters] tanımlanabilir ve ayrıca `addStaticParameters()` yöntemi ( `addParameters()` takma adı vardır) tarafından geçirilebilir (veya üzerine yazılabilir). Farklı parametre değerlerinin ek DI konteynerlerinin, yani ek sınıfların oluşturulmasına neden olması önemlidir. - -```php -$configurator->addStaticParameters([ - 'projectId' => 23, -]); -``` - -Yapılandırma dosyalarında, `projectId` adlı parametreye erişmek için `%projectId%` normal gösterimini yazabiliriz. Yapılandırıcı varsayılan olarak aşağıdaki parametreleri doldurur: `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` ve `consoleMode`. - - -Dinamik Parametreler .[#toc-dynamic-parameters] ------------------------------------------------ - -Konteynere dinamik parametreler de ekleyebiliriz, statik parametrelerin aksine farklı değerleri yeni DI konteynerlerinin oluşturulmasına neden olmaz. - -```php -$configurator->addDynamicParameters([ - 'remoteIp' => $_SERVER['REMOTE_ADDR'], -]); -``` - -Ortam değişkenleri dinamik parametreler kullanılarak kolayca kullanılabilir hale getirilebilir. Bunlara yapılandırma dosyalarındaki `%env.variable%` adresinden erişebiliriz. - -```php -$configurator->addDynamicParameters([ - 'env' => getenv(), -]); -``` - - -İthal Hizmetler .[#toc-imported-services] ------------------------------------------ - -Şimdi daha derine iniyoruz. Bir DI konteynerinin amacı nesneler oluşturmak olsa da, istisnai olarak mevcut bir nesneyi konteynere ekleme ihtiyacı olabilir. Bunu, servisi `imported: true` niteliği ile tanımlayarak yaparız. - -```neon -services: - myservice: - type: App\Model\MyCustomService - imported: true -``` - -Yeni bir örnek oluşturun ve bootstrap'e ekleyin: - -```php -$configurator->addServices([ - 'myservice' => new App\Model\MyCustomService('foobar'), -]); -``` - - -Farklı Ortamlar .[#toc-different-environments] -============================================== - -`Bootstrap` sınıfını ihtiyaçlarınıza göre özelleştirmekten çekinmeyin. Web projelerini farklılaştırmak için `boot()` yöntemine parametreler ekleyebilir veya birim testleri için ortamı başlatan `bootForTests()`, komut satırından çağrılan komut dosyaları için `bootForCli()` gibi başka yöntemler ekleyebilirsiniz. - -```php -public static function bootForTests(): Configurator -{ - $configurator = self::boot(); - Tester\Environment::setup(); // Nette Tester initialization - return $configurator; -} -``` diff --git a/application/tr/bootstrapping.texy b/application/tr/bootstrapping.texy new file mode 100644 index 0000000000..6900984407 --- /dev/null +++ b/application/tr/bootstrapping.texy @@ -0,0 +1,297 @@ +Bootstrapping +************* + +
    + +Bootstrapping, uygulama ortamının başlatılması, bir dependency injection (DI) konteynerinin oluşturulması ve uygulamanın başlatılması sürecidir. Şunları tartışacağız: + +- Bootstrap sınıfının ortamı nasıl başlattığı +- uygulamaların NEON dosyaları kullanılarak nasıl yapılandırıldığı +- üretim ve geliştirme modları arasında nasıl ayrım yapılacağı +- DI konteynerinin nasıl oluşturulacağı ve yapılandırılacağı + +
    + + +Uygulamalar, ister web uygulamaları ister komut satırından çalıştırılan betikler olsun, çalışmalarına bir tür ortam başlatma ile başlarlar. Eski zamanlarda, bu genellikle ilk dosyanın dahil ettiği `include.inc.php` gibi bir dosyanın sorumluluğundaydı. Modern Nette uygulamalarında, bunun yerini uygulamanın bir parçası olarak `app/Bootstrap.php` dosyasında bulunan `Bootstrap` sınıfı almıştır. Örneğin şöyle görünebilir: + +```php +use Nette\Bootstrap\Configurator; + +class Bootstrap +{ + private Configurator $configurator; + private string $rootDir; + + public function __construct() + { + $this->rootDir = dirname(__DIR__); + // Yapılandırıcı, uygulama ortamını ve servisleri ayarlamaktan sorumludur. + $this->configurator = new Configurator; + // Nette tarafından oluşturulan geçici dosyalar için dizini ayarlar (örn. derlenmiş şablonlar) + $this->configurator->setTempDirectory($this->rootDir . '/temp'); + } + + public function bootWebApplication(): Nette\DI\Container + { + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); + } + + private function initializeEnvironment(): void + { + // Nette akıllıdır ve geliştirme modu otomatik olarak açılır, + // veya aşağıdaki satırın yorumunu kaldırarak belirli bir IP adresi için etkinleştirebilirsiniz: + // $this->configurator->setDebugMode('secret@23.75.345.200'); + + // Tracy'yi etkinleştirir: hata ayıklama için nihai "İsviçre çakısı". + $this->configurator->enableTracy($this->rootDir . '/log'); + + // RobotLoader: seçilen dizindeki tüm sınıfları otomatik olarak yükler + $this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); + } + + private function setupContainer(): void + { + // Yapılandırma dosyalarını yükler + $this->configurator->addConfig($this->rootDir . '/config/common.neon'); + } +} +``` + + +index.php +========= + +Web uygulamaları durumunda ilk dosya, [genel dizinde |directory-structure#Genel Dizin www] `www/` bulunan `index.php` dosyasıdır. Bu dosya, Bootstrap sınıfından ortamı başlatmasını ve DI konteynerini oluşturmasını ister. Ardından, web uygulamasını başlatan `Application` servisini ondan alır: + +```php +$bootstrap = new App\Bootstrap; +// Ortamı başlat + DI konteynerini oluştur +$container = $bootstrap->bootWebApplication(); +// DI konteyneri Nette\Application\Application nesnesini oluşturur +$application = $container->getByType(Nette\Application\Application::class); +// Nette uygulamasını başlat ve gelen isteği işle +$application->run(); +``` + +Gördüğünüz gibi, ortamı ayarlamaya ve bağımlılık enjeksiyonu (DI) konteynerini oluşturmaya [api:Nette\Bootstrap\Configurator] sınıfı yardımcı olur, şimdi onu daha yakından tanıyacağız. + + +Geliştirme vs Üretim Modu +========================= + +Nette, geliştirme veya üretim sunucusunda çalışıp çalışmadığına bağlı olarak farklı davranır: + +🛠️ Geliştirme Modu (Development): + - Yararlı bilgilerle (SQL sorguları, yürütme süresi, kullanılan bellek) Tracy hata ayıklama çubuğunu gösterir + - Bir hata durumunda, fonksiyon çağrıları ve değişken içerikleriyle ayrıntılı bir hata sayfası gösterir + - Latte şablonları değiştiğinde, yapılandırma dosyaları düzenlendiğinde vb. önbelleği otomatik olarak yeniler + + +🚀 Üretim Modu (Production): + - Hata ayıklama bilgisi göstermez, tüm hataları günlüğe yazar + - Bir hata durumunda, ErrorPresenter'ı veya genel "Server Error" sayfasını gösterir + - Önbellek asla otomatik olarak yenilenmez! + - Hız ve güvenlik için optimize edilmiştir + + +Mod seçimi otomatik algılama ile yapılır, bu nedenle genellikle herhangi bir şeyi yapılandırmaya veya manuel olarak değiştirmeye gerek yoktur: + +- geliştirme modu: localhost'ta (IP adresi `127.0.0.1` veya `::1`) proxy mevcut değilse (yani HTTP başlığı yoksa) +- üretim modu: her yerde + +Geliştirme modunu diğer durumlarda da etkinleştirmek istersek, örneğin belirli bir IP adresinden erişen programcılar için, `setDebugMode()` kullanırız: + +```php +$this->configurator->setDebugMode('23.75.345.200'); // IP adresleri dizisi de belirtilebilir +``` + +Kesinlikle IP adresini bir çerezle birleştirmenizi öneririz. `nette-debug` çerezine gizli bir belirteç, örneğin `secret1234` kaydederiz ve bu şekilde belirli bir IP adresinden erişen ve aynı zamanda çerezde belirtilen belirtece sahip olan programcılar için geliştirme modunu etkinleştiririz: + +```php +$this->configurator->setDebugMode('secret1234@23.75.345.200'); +``` + +Geliştirme modunu tamamen kapatabiliriz, localhost için bile: + +```php +$this->configurator->setDebugMode(false); +``` + +Dikkat, `true` değeri geliştirme modunu zorla açar, bu üretim sunucusunda asla olmamalıdır. + + +Hata Ayıklama Aracı Tracy +========================= + +Kolay hata ayıklama için harika [Tracy |tracy:] aracını da etkinleştireceğiz. Geliştirme modunda hataları görselleştirir ve üretim modunda hataları belirtilen dizine günlüğe kaydeder: + +```php +$this->configurator->enableTracy($this->rootDir . '/log'); +``` + + +Geçici Dosyalar +=============== + +Nette, DI konteyneri, RobotLoader, şablonlar vb. için önbellek kullanır. Bu nedenle, önbelleğin depolanacağı dizinin yolunu ayarlamak gerekir: + +```php +$this->configurator->setTempDirectory($this->rootDir . '/temp'); +``` + +Linux veya macOS'ta, `log/` ve `temp/` dizinlerine [yazma izinlerini |nette:troubleshooting#Dizin İzinlerini Ayarlama] ayarlayın. + + +RobotLoader +=========== + +Genellikle [RobotLoader |robot-loader:] kullanarak sınıfları otomatik olarak yüklemek isteyeceğiz, bu yüzden onu başlatmalı ve `Bootstrap.php` dosyasının bulunduğu dizinden (yani `__DIR__`) ve tüm alt dizinlerden sınıfları yüklemesine izin vermeliyiz: + +```php +$this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); +``` + +Alternatif bir yaklaşım, PSR-4'e uyarak sınıfları yalnızca [Composer |best-practices:composer] aracılığıyla yüklemektir. + + +Zaman Dilimi +============ + +Yapılandırıcı aracılığıyla varsayılan zaman dilimini ayarlayabilirsiniz. + +```php +$this->configurator->setTimeZone('Europe/Prague'); +``` + + +DI Konteyner Yapılandırması +=========================== + +Başlatma sürecinin bir parçası, tüm uygulamanın kalbi olan nesneler için bir fabrika olan DI konteynerinin oluşturulmasıdır. Aslında bu, Nette tarafından oluşturulan ve önbellek dizinine kaydedilen bir PHP sınıfıdır. Fabrika, uygulamanın temel nesnelerini üretir ve yapılandırma dosyaları aracılığıyla ona nasıl oluşturulacağını ve ayarlanacağını bildiririz, böylece tüm uygulamanın davranışını etkileriz. + +Yapılandırma dosyaları genellikle [NEON |neon:format] formatında yazılır. Ayrı bir bölümde, [nelerin yapılandırılabileceğini |nette:configuring] öğreneceksiniz. + +.[tip] +Geliştirme modunda, kod veya yapılandırma dosyaları her değiştiğinde konteyner otomatik olarak güncellenir. Üretim modunda, yalnızca bir kez oluşturulur ve performansı en üst düzeye çıkarmak için değişiklikler kontrol edilmez. + +Yapılandırma dosyalarını `addConfig()` kullanarak yükleriz: + +```php +$this->configurator->addConfig($this->rootDir . '/config/common.neon'); +``` + +Daha fazla yapılandırma dosyası eklemek istiyorsak, `addConfig()` fonksiyonunu birden çok kez çağırabiliriz. + +```php +$configDir = $this->rootDir . '/config'; +$this->configurator->addConfig($configDir . '/common.neon'); +$this->configurator->addConfig($configDir . '/services.neon'); +if (PHP_SAPI === 'cli') { + $this->configurator->addConfig($configDir . '/cli.php'); +} +``` + +`cli.php` adı bir yazım hatası değildir, yapılandırma bir dizi olarak döndüren bir PHP dosyasında da yazılabilir. + +Ayrıca [`includes` bölümünde |dependency-injection:configuration#Dosya Dahil Etme] başka yapılandırma dosyaları da ekleyebiliriz. + +Yapılandırma dosyalarında aynı anahtarlara sahip öğeler görünürse, bunlar üzerine yazılır veya [diziler durumunda birleştirilir |dependency-injection:configuration#Birleştirme]. Daha sonra eklenen dosya, öncekinden daha yüksek önceliğe sahiptir. `includes` bölümünün belirtildiği dosya, içine dahil edilen dosyalardan daha yüksek önceliğe sahiptir. + + +Statik Parametreler +------------------- + +Yapılandırma dosyalarında kullanılan parametreleri [`parameters` bölümünde |dependency-injection:configuration#Parametreler] tanımlayabilir ve ayrıca `addStaticParameters()` metoduyla (diğer adı `addParameters()`) iletebilir (veya üzerine yazabiliriz). Önemli olan, farklı parametre değerlerinin ek DI konteynerlerinin, yani ek sınıfların oluşturulmasına neden olmasıdır. + +```php +$this->configurator->addStaticParameters([ + 'projectId' => 23, +]); +``` + +`projectId` parametresine yapılandırmada normal `%projectId%` gösterimiyle başvurulabilir. + + +Dinamik Parametreler +-------------------- + +Konteynere dinamik parametreler de ekleyebiliriz; bunların farklı değerleri, statik parametrelerin aksine, yeni DI konteynerlerinin oluşturulmasına neden olmaz. + +```php +$this->configurator->addDynamicParameters([ + 'remoteIp' => $_SERVER['REMOTE_ADDR'], +]); +``` + +Bu şekilde, örneğin ortam değişkenlerini kolayca ekleyebiliriz, bunlara daha sonra yapılandırmada `%env.variable%` gösterimiyle başvurulabilir. + +```php +$this->configurator->addDynamicParameters([ + 'env' => getenv(), +]); +``` + + +Varsayılan Parametreler +----------------------- + +Yapılandırma dosyalarında şu statik parametreleri kullanabilirsiniz: + +- `%appDir%`, `Bootstrap.php` dosyasını içeren dizine mutlak yoldur +- `%wwwDir%`, giriş dosyası `index.php` dosyasını içeren dizine mutlak yoldur +- `%tempDir%`, geçici dosyalar için dizine mutlak yoldur +- `%vendorDir%`, Composer'ın kütüphaneleri kurduğu dizine mutlak yoldur +- `%rootDir%`, projenin kök dizinine mutlak yoldur +- `%debugMode%`, uygulamanın hata ayıklama modunda olup olmadığını belirtir +- `%consoleMode%`, isteğin komut satırından gelip gelmediğini belirtir + + +İçe Aktarılan Servisler +----------------------- + +Şimdi daha derine iniyoruz. DI konteynerinin amacı nesneleri üretmek olsa da, istisnai olarak mevcut bir nesneyi konteynere ekleme ihtiyacı doğabilir. Bunu, servisi `imported: true` bayrağıyla tanımlayarak yaparız. + +```neon +services: + myservice: + type: App\Model\MyCustomService + imported: true +``` + +Ve bootstrap'ta nesneyi konteynere ekleriz: + +```php +$this->configurator->addServices([ + 'myservice' => new App\Model\MyCustomService('foobar'), +]); +``` + + +Farklı Ortam +============ + +Bootstrap sınıfını ihtiyaçlarınıza göre değiştirmekten çekinmeyin. Web projelerini ayırt etmek için `bootWebApplication()` metoduna parametreler ekleyebilirsiniz. Veya birim testleri için ortamı başlatan `bootTestEnvironment()`, komut satırından çağrılan betikler için `bootConsoleApplication()` gibi başka metotlar ekleyebiliriz. + +```php +public function bootTestEnvironment(): Nette\DI\Container +{ + Tester\Environment::setup(); // Nette Tester'ı başlat + $this->setupContainer(); + return $this->configurator->createContainer(); +} + +public function bootConsoleApplication(): Nette\DI\Container +{ + $this->configurator->setDebugMode(false); + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); +} +``` diff --git a/application/tr/components.texy b/application/tr/components.texy index 030924af19..68661abfc4 100644 --- a/application/tr/components.texy +++ b/application/tr/components.texy @@ -1,9 +1,9 @@ -İnteraktif Bileşenler -********************* +Etkileşimli Bileşenler +**********************
    -Bileşenler, sayfalara yerleştirdiğimiz ayrı yeniden kullanılabilir nesnelerdir. Bunlar formlar, datagridler, anketler, aslında tekrar tekrar kullanılması mantıklı olan her şey olabilir. Göstereceğiz: +Bileşenler, sayfalara eklediğimiz bağımsız, yeniden kullanılabilir nesnelerdir. Bunlar formlar, veri ızgaraları, anketler, aslında tekrar tekrar kullanılması mantıklı olan her şey olabilir. Şunları göstereceğiz: - bileşenler nasıl kullanılır? - nasıl yazılır? @@ -11,19 +11,19 @@ Bileşenler, sayfalara yerleştirdiğimiz ayrı yeniden kullanılabilir nesneler
    -Nette yerleşik bir bileşen sistemi vardır. Daha yaşlılarınız Delphi veya ASP.NET Web Forms'dan benzer bir şeyi hatırlayabilir. React veya Vue.js uzaktan benzer bir şey üzerine inşa edilmiştir. Ancak, PHP çerçeveleri dünyasında bu tamamen benzersiz bir özelliktir. +Nette'nin yerleşik bir bileşen sistemi vardır. Delphi veya ASP.NET Web Forms'tan aşina olanlar benzer bir şey hatırlayabilir, React veya Vue.js de uzaktan benzer bir şeye dayanmaktadır. Ancak, PHP framework dünyasında bu benzersiz bir özelliktir. -Aynı zamanda bileşenler, uygulama geliştirme yaklaşımını temelden değiştirir. Önceden hazırlanmış birimlerden sayfalar oluşturabilirsiniz. Yönetimde datagrid'e mi ihtiyacınız var? Nette için açık kaynaklı eklentilerin (sadece bileşenlerin değil) bir deposu olan [Componette'de |https://componette.org/search/component] bulabilir ve basitçe sunucuya yapıştırabilirsiniz. +Bununla birlikte, bileşenler uygulama geliştirme yaklaşımını temelden etkiler. Sayfaları önceden hazırlanmış birimlerden oluşturabilirsiniz. Yönetimde bir veri ızgarasına mı ihtiyacınız var? Onu Nette için açık kaynaklı eklentilerin (yani sadece bileşenlerin değil) deposu olan [Componette |https://componette.org/search/component] adresinde bulabilir ve presenter'a kolayca ekleyebilirsiniz. -Sunucuya istediğiniz sayıda bileşen ekleyebilirsiniz. Ve bazı bileşenlerin içine başka bileşenler ekleyebilirsiniz. Bu, kök olarak sunum yapan bir bileşen ağacı oluşturur. +Presenter'a istediğiniz sayıda bileşen ekleyebilirsiniz. Ve bazı bileşenlere başka bileşenler ekleyebilirsiniz. Bu, kökü presenter olan bir bileşen ağacı oluşturur. -Fabrika Yöntemleri .[#toc-factory-methods] -========================================== +Fabrika Metotları +================= -Bileşenler sunucuya nasıl yerleştirilir ve daha sonra nasıl kullanılır? Genellikle fabrika yöntemleri kullanılarak. +Bileşenler presenter'a nasıl eklenir ve ardından kullanılır? Genellikle fabrika metotları aracılığıyla. -Bileşen fabrikası, bileşenleri yalnızca gerçekten ihtiyaç duyulduklarında (tembel / isteğe bağlı) oluşturmanın zarif bir yoludur. Tüm sihir, aşağıdaki gibi adlandırılan bir yöntemin uygulanmasındadır `createComponent()`, nerede `` oluşturacak ve geri dönecek bileşenin adıdır. +Bileşen fabrikası, bileşenleri yalnızca gerçekten ihtiyaç duyulduğunda (lazy / on demand) oluşturmanın zarif bir yoludur. Tüm sihir, `` öğesinin oluşturulan bileşenin adı olduğu ve bileşeni oluşturup döndüren `createComponent()` adlı bir metodun uygulanmasında yatar. ```php .{file:DefaultPresenter.php} class DefaultPresenter extends Nette\Application\UI\Presenter @@ -37,43 +37,43 @@ class DefaultPresenter extends Nette\Application\UI\Presenter } ``` -Tüm bileşenler ayrı yöntemlerle oluşturulduğu için kod daha temizdir ve okunması daha kolaydır. +Tüm bileşenlerin ayrı metotlarda oluşturulması sayesinde kod daha okunaklı hale gelir. .[note] -Bileşen adları her zaman küçük harfle başlar, ancak yöntem adında büyük harfle yazılır. +Bileşen adları, metot adında büyük harfle yazılsa bile her zaman küçük harfle başlar. -Fabrikaları asla doğrudan çağırmayız, bileşenleri ilk kez kullandığımızda otomatik olarak çağrılırlar. Bu sayede, bir bileşen doğru zamanda ve yalnızca gerçekten ihtiyaç duyulduğunda oluşturulur. Bileşeni kullanmayacaksak (örneğin sayfanın yalnızca bir kısmını döndürdüğümüz bazı AJAX isteklerinde veya parçalar önbelleğe alındığında), bileşen oluşturulmaz bile ve sunucunun performansından tasarruf ederiz. +Fabrikaları asla doğrudan çağırmayız, bileşeni ilk kullandığımızda kendiliğinden çağrılırlar. Bu sayede bileşen doğru zamanda ve yalnızca gerçekten ihtiyaç duyulduğunda oluşturulur. Bileşeni kullanmazsak (örneğin, sayfanın yalnızca bir kısmının aktarıldığı bir AJAX isteğinde veya şablon önbelleğe alınırken), hiç oluşturulmaz ve sunucu performansından tasarruf ederiz. ```php .{file:DefaultPresenter.php} -// bileşene erişiyoruz ve eğer bu ilk sefer ise, -// oluşturmak için createComponentPoll() işlevini çağırır +// bileşene erişiriz ve eğer ilk kez ise, +// onu oluşturan createComponentPoll() çağrılır $poll = $this->getComponent('poll'); // alternatif sözdizimi: $poll = $this['poll']; ``` -Şablonda, [{control} |#Rendering] etiketini kullanarak bir bileşen oluşturabilirsiniz. Böylece bileşenleri şablona manuel olarak geçirmeye gerek kalmaz. +Şablonda, bir bileşeni [{control} |#Oluşturma] etiketi kullanarak oluşturmak mümkündür. Bu nedenle bileşenleri şablona manuel olarak iletmeye gerek yoktur. ```latte -

    Please Vote

    +

    Oy Verin

    {control poll} ``` -Hollywood Tarzı .[#toc-hollywood-style] -======================================= +Hollywood Tarzı +=============== -Bileşenler genellikle Hollywood tarzı olarak adlandırdığımız harika bir teknik kullanır. Oyuncu seçmelerinde oyuncuların sık sık duyduğu klişeyi biliyorsunuzdur: "Siz bizi aramayın, biz sizi ararız." İşte bu da bununla ilgili. +Bileşenler genellikle Hollywood tarzı dediğimiz yeni bir tekniği kullanır. Film seçmelerine katılanların sıkça duyduğu şu meşhur cümleyi mutlaka bilirsiniz: "Bizi aramayın, biz sizi ararız." İşte tam olarak bundan bahsediyoruz. -Nette, sürekli soru sormak zorunda kalmak yerine ("form gönderildi mi?", "geçerli miydi?" veya "bu düğmeye basan oldu mu?"), framework'e "bu olduğunda, bu yöntemi çağır" der ve daha fazla çalışmayı bırakırsınız. JavaScript'te programlama yapıyorsanız, bu tarz programlamaya aşinasınızdır. Belirli bir olay gerçekleştiğinde çağrılan fonksiyonlar yazarsınız. Ve motor bunlara uygun parametreleri aktarır. +Nette'de, sürekli bir şeyler sormak yerine ("form gönderildi mi?", "geçerli miydi?" veya "kullanıcı bu düğmeye bastı mı?"), framework'e "bu olduğunda, şu metodu çağır" dersiniz ve geri kalan işi ona bırakırsınız. JavaScript ile programlama yapıyorsanız, bu programlama tarzına aşinasınızdır. Belirli bir olay gerçekleştiğinde çağrılan fonksiyonlar yazarsınız. Ve dil onlara ilgili parametreleri iletir. -Bu, uygulama yazma şeklinizi tamamen değiştirir. Framework'e ne kadar çok görev devredebilirseniz, o kadar az işiniz olur. Ve o kadar az unutabilirsiniz. +Bu, uygulama yazma şeklini tamamen değiştirir. Framework'e ne kadar çok görev bırakabilirseniz, o kadar az işiniz olur. Ve dolayısıyla gözden kaçırabileceğiniz şeyler de o kadar azalır. -Bir Bileşen Nasıl Yazılır .[#toc-how-to-write-a-component] -========================================================== +Bileşen Yazma +============= -Bileşen derken genellikle [api:Nette\Application\UI\Control] sınıfının torunlarını kastediyoruz. Sunucu [api:Nette\Application\UI\Presenter] 'un kendisi de `Control` sınıfının bir torunudur. +Bileşen terimiyle genellikle [api:Nette\Application\UI\Control] sınıfının bir alt sınıfını kastederiz. (Dolayısıyla "kontroller" terimini kullanmak daha doğru olurdu, ancak "kontroller" Türkçede tamamen farklı bir anlama sahiptir ve "bileşenler" daha çok yerleşmiştir.) Bu arada, presenter [api:Nette\Application\UI\Presenter] kendisi de `Control` sınıfının bir alt sınıfıdır. ```php .{file:PollControl.php} use Nette\Application\UI\Control; @@ -84,22 +84,22 @@ class PollControl extends Control ``` -Rendering .[#toc-rendering] -=========================== +Oluşturma +========= -`{control componentName}` etiketinin bir bileşeni çizmek için kullanıldığını zaten biliyoruz. Aslında `render()` bileşeninin render işlemini gerçekleştirdiğimiz metodunu çağırır. Tıpkı sunucuda olduğu gibi, parametreleri aktardığımız `$this->template` değişkeninde bir [Latte |latte:] şablonumuz var. Sunucudaki kullanımdan farklı olarak, bir şablon dosyası belirtmeli ve render edilmesine izin vermeliyiz: +Biliyoruz ki bir bileşeni oluşturmak için `{control componentName}` etiketi kullanılır. Bu aslında bileşenin `render()` metodunu çağırır ve burada oluşturmayı biz hallederiz. Tıpkı presenter'da olduğu gibi, `$this->template` değişkeninde [Latte şablonuna|templates] sahibiz ve ona parametreleri iletiriz. Presenter'dan farklı olarak, şablon dosyasını belirtmeli ve oluşturulmasını sağlamalıyız: ```php .{file:PollControl.php} public function render(): void { - // şablona bazı parametreler koyacağız + // şablona bazı parametreler ekleriz $this->template->param = $value; - // ve çiz + // ve onu oluştururuz $this->template->render(__DIR__ . '/poll.latte'); } ``` -`{control}` etiketi, `render()` yöntemine parametre aktarılmasına olanak tanır: +`{control}` etiketi, `render()` metoduna parametreler iletmeyi sağlar: ```latte {control poll $id, $message} @@ -112,7 +112,7 @@ public function render(int $id, string $message): void } ``` -Bazen bir bileşen, ayrı ayrı render etmek istediğimiz birkaç parçadan oluşabilir. Her biri için kendi render yöntemini oluşturacağız, örneğin `renderPaginator()`: +Bazen bir bileşen, ayrı ayrı oluşturmak istediğimiz birkaç bölümden oluşabilir. Her biri için kendi oluşturma metodumuzu oluştururuz, buradaki örnekte örneğin `renderPaginator()`: ```php .{file:PollControl.php} public function renderPaginator(): void @@ -121,69 +121,69 @@ public function renderPaginator(): void } ``` -Ve şablonda daha sonra bunu kullanarak çağırıyoruz: +Ve şablonda onu şu şekilde çağırırız: ```latte {control poll:paginator} ``` -Daha iyi anlamak için etiketin PHP koduna nasıl derlendiğini bilmek iyi olacaktır. +Daha iyi anlamak için, bu etiketin PHP'ye nasıl çevrildiğini bilmek iyidir. ```latte {control poll} {control poll:paginator 123, 'hello'} ``` -Bu şu anlama geliyor: +şu şekilde çevrilir: ```php $control->getComponent('poll')->render(); $control->getComponent('poll')->renderPaginator(123, 'hello'); ``` -`getComponent()` yöntemi `poll` bileşenini döndürür ve ardından bu bileşen üzerinde sırasıyla `render()` veya `renderPaginator()` yöntemi çağrılır. +`getComponent()` metodu `poll` bileşenini döndürür ve bu bileşen üzerinde `render()` metodunu veya etikette iki noktadan sonra farklı bir oluşturma yöntemi belirtilmişse `renderPaginator()` metodunu çağırır. .[caution] -Parametre kısmının herhangi bir yerinde **`=>`** kullanılırsa, tüm parametreler bir dizi ile sarılır ve ilk bağımsız değişken olarak geçirilir: +Dikkat, parametrelerin herhangi bir yerinde **`=>`** görünürse, tüm parametreler bir diziye paketlenir ve ilk argüman olarak iletilir: ```latte {control poll, id: 123, message: 'hello'} ``` -derlenir: +şu şekilde çevrilir: ```php $control->getComponent('poll')->render(['id' => 123, 'message' => 'hello']); ``` -Alt bileşenin render edilmesi: +Alt bileşenin oluşturulması: ```latte {control cartControl-someForm} ``` -derlenir: +şu şekilde çevrilir: ```php $control->getComponent("cartControl-someForm")->render(); ``` -Sunucular gibi bileşenler de şablonlara otomatik olarak çeşitli yararlı değişkenler aktarır: +Bileşenler, presenter'lar gibi, şablonlara otomatik olarak birkaç yararlı değişken iletir: -- `$basePath` kök dizine giden mutlak bir URL yoludur (örneğin `/CD-collection`) -- `$baseUrl` kök dizine mutlak bir URL'dir (örneğin `http://localhost/CD-collection`) -- `$user` [kullanıcıyı temsil eden bir nesnedir|security:authentication] -- `$presenter` şu anki sunucu -- `$control` mevcut bileşendir -- `$flashes` yöntem tarafından gönderilen [mesajların |#flash-messages] listesi `flashMessage()` +- `$basePath`, kök dizine mutlak URL yoludur (örn. `/eshop`) +- `$baseUrl`, kök dizine mutlak URL'dir (örn. `http://localhost/eshop`) +- `$user`, [kullanıcıyı temsil eden |security:authentication] nesnedir +- `$presenter`, mevcut presenter'dır +- `$control`, mevcut bileşendir +- `$flashes`, `flashMessage()` fonksiyonu tarafından gönderilen [mesajlar |#Flash Mesajları] dizisidir -Sinyal .[#toc-signal] -===================== +Sinyal +====== -Nette uygulamasında gezinmenin `Presenter:action` çiftlerine bağlantı vermek veya yönlendirmekten oluştuğunu zaten biliyoruz. Peki ya sadece **mevcut sayfa** üzerinde bir eylem gerçekleştirmek istiyorsak? Örneğin, tablodaki sütunun sıralama düzenini değiştirmek; öğeyi silmek; aydınlık/karanlık modunu değiştirmek; formu göndermek; ankette oy kullanmak vb. +Nette uygulamasında gezinmenin `Presenter:action` çiftlerine bağlantı verme veya yönlendirme yapmaktan ibaret olduğunu zaten biliyoruz. Peki ya sadece **mevcut sayfada** bir eylem gerçekleştirmek istersek? Örneğin, bir tablodaki sütunların sıralamasını değiştirmek; bir öğeyi silmek; açık/koyu modu değiştirmek; bir form göndermek; bir ankette oy kullanmak; vb. -Bu tür isteklere sinyal adı verilir. Ve eylemlerin yöntemleri çağırması gibi `action()` veya `render()`, sinyaller yöntemleri çağırır `handle()`. Eylem (veya görünüm) kavramı yalnızca sunum yapanlarla ilgiliyken, sinyaller tüm bileşenler için geçerlidir. Ve dolayısıyla sunucular için de geçerlidir, çünkü `UI\Presenter`, `UI\Control`'un soyundan gelmektedir. +Bu tür isteklere sinyal denir. Ve eylemlerin `action()` veya `render()` metotlarını tetiklemesi gibi, sinyaller de `handle()` metotlarını çağırır. Eylem (veya view) kavramı yalnızca presenter'larla ilgiliyken, sinyaller tüm bileşenlerle ilgilidir. Ve dolayısıyla presenter'larla da, çünkü `UI\Presenter`, `UI\Control`'un bir alt sınıfıdır. ```php public function handleClick(int $x, int $y): void @@ -192,36 +192,36 @@ public function handleClick(int $x, int $y): void } ``` -Sinyali çağıran bağlantı olağan şekilde oluşturulur, yani şablonda `n:href` niteliği veya `{link}` etiketi ile, kodda `link()` yöntemi ile. Daha fazlası [URL bağlantıları oluşturma |creating-links#Links to Signal] bölümünde. +Sinyali çağıran bir bağlantıyı normal şekilde oluştururuz, yani şablonda `n:href` niteliğiyle veya `{link}` etiketiyle, kodda `link()` metoduyla. Daha fazla bilgi için [URL Bağlantıları Oluşturma |creating-links#Sinyale Bağlantılar] bölümüne bakın. ```latte -click here +buraya tıkla ``` -Sinyal her zaman geçerli sunumcu ve görünümde çağrılır, bu nedenle farklı sunumcu / eylemde sinyale bağlanmak mümkün değildir. +Sinyal her zaman mevcut presenter ve eylem üzerinde çağrılır, başka bir presenter veya başka bir eylem üzerinde çağrılamaz. -Böylece sinyal, sayfanın orijinal istekle tamamen aynı şekilde yeniden yüklenmesine neden olur, sadece ek olarak sinyal işleme yöntemini uygun parametrelerle çağırır. Yöntem mevcut değilse, kullanıcıya 403 Forbidden hata sayfası olarak görüntülenen [api:Nette\Application\UI\BadSignalException] istisnası atılır. +Dolayısıyla sinyal, sayfanın orijinal istekteki gibi tamamen yeniden yüklenmesine neden olur, ancak ek olarak ilgili parametrelerle sinyal işleyici metodunu çağırır. Metot mevcut değilse, kullanıcıya 403 Forbidden hata sayfası olarak gösterilen [api:Nette\Application\UI\BadSignalException] istisnası atılır. -Snippet'ler ve AJAX .[#toc-snippets-and-ajax] -============================================= +Snippet'ler ve AJAX +=================== -Sinyaller size biraz AJAX'ı hatırlatabilir: mevcut sayfada çağrılan işleyiciler. Ve haklısınız, sinyaller genellikle AJAX kullanılarak çağrılır ve daha sonra sayfanın yalnızca değişen kısımlarını tarayıcıya iletiriz. Bunlara snippet adı verilir. [AJAX ile ilgili sayfada |ajax] daha fazla bilgi bulabilirsiniz. +Sinyaller size biraz AJAX'ı hatırlatabilir: mevcut sayfada çağrılan işleyiciler. Ve haklısınız, sinyaller gerçekten de sık sık AJAX kullanılarak çağrılır ve ardından sayfanın yalnızca değiştirilmiş bölümleri tarayıcıya aktarılır. Yani sözde snippet'ler. Daha fazla bilgi için [AJAX'a ayrılmış sayfada |ajax] bulabilirsiniz. -Flaş Mesajlar .[#toc-flash-messages] -==================================== +Flash Mesajları +=============== -Bir bileşen, sunum yapan kişiden bağımsız olarak kendi flaş mesaj deposuna sahiptir. Bunlar, örneğin işlemin sonucu hakkında bilgi veren mesajlardır. Flaş mesajların önemli bir özelliği, yeniden yönlendirmeden sonra bile şablonda mevcut olmalarıdır. Görüntülendikten sonra bile 30 saniye daha canlı kalırlar - örneğin, kullanıcının istemeden sayfayı yenilemesi durumunda - mesaj kaybolmaz. +Bileşenin, presenter'dan bağımsız kendi flash mesaj deposu vardır. Bunlar, örneğin bir işlemin sonucunu bildiren mesajlardır. Flash mesajlarının önemli bir özelliği, yönlendirmeden sonra bile şablonda kullanılabilir olmalarıdır. Görüntülendikten sonra bile 30 saniye daha canlı kalırlar - örneğin, hatalı bir aktarım nedeniyle kullanıcının sayfayı yenilemesi durumunda mesaj hemen kaybolmaz. -Gönderme işlemi [flashMessage |api:Nette\Application\UI\Control::flashMessage()] yöntemi ile yapılır. İlk parametre mesaj metni veya mesajı temsil eden `stdClass` nesnesidir. İsteğe bağlı ikinci parametre mesajın türüdür (hata, uyarı, bilgi, vb.). `flashMessage()` yöntemi, bilgi aktarabileceğiniz stdClass nesnesi olarak bir flash message örneği döndürür. +Gönderme işlemi [flashMessage |api:Nette\Application\UI\Control::flashMessage()] metodu tarafından gerçekleştirilir. İlk parametre mesaj metni veya mesajı temsil eden bir `stdClass` nesnesidir. İsteğe bağlı ikinci parametre türüdür (error, warning, info vb.). `flashMessage()` metodu, flash mesajının bir örneğini `stdClass` nesnesi olarak döndürür ve buna ek bilgiler eklenebilir. ```php -$this->flashMessage('Item was deleted.'); -$this->redirect(/* ... */); // ve yeniden yönlendir +$this->flashMessage('Öğe silindi.'); +$this->redirect(/* ... */); // ve yönlendiririz ``` -Şablonda, bu mesajlar `$flashes` değişkeninde `message` (mesaj metni), `type` (mesaj türü) özelliklerini içeren ve daha önce bahsedilen kullanıcı bilgilerini içerebilen `stdClass` nesneleri olarak mevcuttur. Bunları aşağıdaki gibi çiziyoruz: +Şablonda bu mesajlar `$flashes` değişkeninde `stdClass` nesneleri olarak bulunur ve `message` (mesaj metni), `type` (mesaj türü) özelliklerini içerir ve daha önce bahsedilen kullanıcı bilgilerini içerebilir. Onları örneğin şu şekilde oluştururuz: ```latte {foreach $flashes as $flash} @@ -230,44 +230,66 @@ $this->redirect(/* ... */); // ve yeniden yönlendir ``` -Kalıcı Parametreler .[#toc-persistent-parameters] -================================================= +Sinyal Sonrası Yönlendirme +========================== -Kalıcı parametreler, farklı istekler arasında bileşenlerdeki durumu korumak için kullanılır. Bir bağlantı tıklandıktan sonra bile değerleri aynı kalır. Oturum verilerinin aksine, URL içinde aktarılırlar. Ve aynı sayfadaki diğer bileşenlerde oluşturulan bağlantılar da dahil olmak üzere otomatik olarak aktarılırlar. +Bileşen sinyallerinin işlenmesinden sonra genellikle bir yönlendirme takip eder. Bu, formlardaki duruma benzer - gönderildikten sonra da yönlendiririz, böylece tarayıcıda sayfa yenilendiğinde veriler tekrar gönderilmez. -Örneğin, bir içerik sayfalama bileşeniniz var. Bir sayfada bu tür birkaç bileşen olabilir. Ve bağlantıya tıkladığınızda tüm bileşenlerin geçerli sayfalarında kalmasını istiyorsunuz. Bu nedenle, sayfa numarasını (`page`) kalıcı bir parametre haline getiriyoruz. +```php +$this->redirect('this') // mevcut presenter ve eyleme yönlendirir +``` + +Bileşen yeniden kullanılabilir bir öğe olduğundan ve genellikle belirli presenter'larla doğrudan bir bağlantısı olmaması gerektiğinden, `redirect()` ve `link()` metotları parametreyi otomatik olarak bileşen sinyali olarak yorumlar: + +```php +$this->redirect('click') // aynı bileşenin 'click' sinyaline yönlendirir +``` + +Başka bir presenter'a veya eyleme yönlendirmeniz gerekiyorsa, bunu presenter aracılığıyla yapabilirsiniz: + +```php +$this->getPresenter()->redirect('Product:show'); // başka bir presenter/eyleme yönlendirir +``` + + +Kalıcı Parametreler +=================== + +Kalıcı parametreler, farklı istekler arasında bileşenlerde durumu korumak için kullanılır. Değerleri, bir bağlantıya tıklandıktan sonra bile aynı kalır. Oturumdaki verilerin aksine, URL'de aktarılırlar. Ve bu tamamen otomatiktir, aynı sayfadaki diğer bileşenlerde oluşturulan bağlantılar dahil. + +Örneğin, içeriği sayfalandırmak için bir bileşeniniz var. Sayfada bu tür birkaç bileşen olabilir. Ve bir bağlantıya tıklandığında tüm bileşenlerin mevcut sayfalarında kalmasını istiyoruz. Bu nedenle, sayfa numarasını (`page`) kalıcı bir parametre yaparız. -Kalıcı bir parametre oluşturmak Nette son derece kolaydır. Sadece bir public özellik oluşturun ve şu nitelikle etiketleyin: (daha önce `/** @persistent */` kullanılıyordu) +Nette'de kalıcı bir parametre oluşturmak son derece basittir. Sadece genel bir özellik oluşturmanız ve onu bir nitelikle işaretlemeniz yeterlidir: (eskiden `/** @persistent */` kullanılırdı) ```php -use Nette\Application\Attributes\Persistent; // bu satır önemlidir +use Nette\Application\Attributes\Persistent; // bu satır önemlidir class PaginatingControl extends Control { #[Persistent] - public int $page = 1; // halka açık olmalı + public int $page = 1; // public olmalı } ``` -Veri türünü (örn. `int`) özellikle birlikte eklemenizi öneririz ve ayrıca varsayılan bir değer de ekleyebilirsiniz. Parametre değerleri [doğrulanabilir |#Validation of Persistent Parameters]. +Özellik için veri türünü (örn. `int`) belirtmenizi öneririz ve varsayılan bir değer de belirtebilirsiniz. Parametre değerleri [doğrulanabilir |#Kalıcı Parametrelerin Doğrulanması]. -Bir bağlantı oluştururken kalıcı bir parametrenin değerini değiştirebilirsiniz: +Bir bağlantı oluştururken, kalıcı parametrenin değeri değiştirilebilir: ```latte -next +sonraki ``` -Ya da *reset* edilebilir, yani URL'den kaldırılabilir. Daha sonra varsayılan değerini alacaktır: +Veya *sıfırlanabilir*, yani URL'den kaldırılabilir. O zaman varsayılan değerini alacaktır: ```latte -reset +sıfırla ``` -Kalıcı Bileşenler .[#toc-persistent-components] -=============================================== +Kalıcı Bileşenler +================= -Sadece parametreler değil, bileşenler de kalıcı olabilir. Kalıcı parametreleri de farklı eylemler arasında veya farklı sunucular arasında aktarılır. Kalıcı bileşenleri sunum yapan sınıf için bu ek açıklamalarla işaretleriz. Örneğin burada `calendar` ve `poll` bileşenlerini aşağıdaki gibi işaretliyoruz: +Sadece parametreler değil, bileşenler de kalıcı olabilir. Böyle bir bileşenin kalıcı parametreleri, presenter'ın farklı eylemleri arasında veya birden fazla presenter arasında da aktarılır. Kalıcı bileşenleri presenter sınıfındaki bir ek açıklama ile işaretleriz. Örneğin, `calendar` ve `poll` bileşenlerini şu şekilde işaretleriz: ```php /** @@ -278,9 +300,9 @@ class DefaultPresenter extends Nette\Application\UI\Presenter } ``` -Alt bileşenleri kalıcı olarak işaretlemenize gerek yoktur, otomatik olarak kalıcı olurlar. +Bu bileşenlerin içindeki alt bileşenleri işaretlemeye gerek yoktur, onlar da kalıcı hale gelirler. -PHP 8'de, kalıcı bileşenleri işaretlemek için öznitelikleri de kullanabilirsiniz: +PHP 8'de, kalıcı bileşenleri işaretlemek için nitelikleri de kullanabilirsiniz: ```php use Nette\Application\Attributes\Persistent; @@ -292,35 +314,35 @@ class DefaultPresenter extends Nette\Application\UI\Presenter ``` -Bağımlılıkları Olan Bileşenler .[#toc-components-with-dependencies] -=================================================================== +Bağımlılıklara Sahip Bileşenler +=============================== -Bağımlılıkları olan bileşenleri, bunları kullanacak sunumcuları "karıştırmadan" nasıl oluşturabiliriz? Nette'deki DI konteynerinin akıllı özellikleri sayesinde, geleneksel servisleri kullanırken olduğu gibi, işin çoğunu çerçeveye bırakabiliriz. +Bağımlılıklara sahip bileşenleri, onları kullanacak presenter'ları "kirletmeden" nasıl oluşturabiliriz? Nette'deki DI konteynerinin akıllı özellikleri sayesinde, klasik servisleri kullanırken olduğu gibi, işin çoğunu framework'e bırakabiliriz. -Örnek olarak `PollFacade` hizmetine bağımlılığı olan bir bileşeni ele alalım: +Örnek olarak, `PollFacade` servisine bağımlılığı olan bir bileşeni ele alalım: ```php class PollControl extends Control { public function __construct( - private int $id, // Bileşenin oluşturulduğu anketin kimliği + private int $id, // Bileşeni oluşturduğumuz anketin kimliği private PollFacade $facade, ) { } public function handleVote(int $voteId): void { - $this->facade->vote($id, $voteId); + $this->facade->vote($this->id, $voteId); // ... } } ``` -Klasik bir hizmet yazıyor olsaydık, endişelenecek bir şey olmazdı. DI konteyneri görünmez bir şekilde tüm bağımlılıkları aktarmakla ilgilenirdi. Ancak bileşenleri genellikle [fabrika yöntemleriyle |#factory methods] doğrudan sunucuda yeni bir örneğini oluşturarak ele alırız `createComponent...()`. Ancak tüm bileşenlerin tüm bağımlılıklarını sunucuya aktarmak ve daha sonra bunları bileşenlere aktarmak zahmetlidir. Ve yazılan kod miktarı... +Klasik bir servis yazıyor olsaydık, çözülecek bir şey olmazdı. Tüm bağımlılıkların iletilmesi DI konteyneri tarafından görünmez bir şekilde halledilirdi. Ancak bileşenlerle genellikle, yeni örneklerini doğrudan presenter'da [fabrika metotlarında |#Fabrika Metotları] `createComponent…()` oluşturacak şekilde çalışırız. Ancak tüm bileşenlerin tüm bağımlılıklarını presenter'a iletmek, sonra onları bileşenlere iletmek hantaldır. Ve yazılan kod miktarı… -Mantıklı soru şu: Neden bileşeni klasik bir hizmet olarak kaydetmiyor, sunucuya aktarmıyor ve ardından `createComponent...()` yönteminde döndürmüyoruz? Ancak bu yaklaşım uygun değildir çünkü bileşeni birden çok kez oluşturabilmek istiyoruz. +Mantıksal soru şudur: neden bileşeni basitçe klasik bir servis olarak kaydetmiyor, presenter'a iletmiyor ve sonra `createComponent…()` metodunda döndürmüyoruz? Ancak bu yaklaşım uygunsuzdur, çünkü bileşeni birden çok kez oluşturabilmek istiyoruz. -Doğru çözüm, bileşen için bir fabrika, yani bileşeni bizim için oluşturan bir sınıf yazmaktır: +Doğru çözüm, bileşen için bir fabrika yazmaktır, yani bize bileşeni oluşturacak bir sınıf: ```php class PollControlFactory @@ -337,17 +359,17 @@ class PollControlFactory } ``` -Şimdi servisimizi DI konteynerine yapılandırma için kaydediyoruz: +Bu fabrikayı yapılandırmamızda konteynerimize kaydederiz: ```neon services: - PollControlFactory ``` -Son olarak, bu fabrikayı sunucumuzda kullanacağız: +ve son olarak onu presenter'ımızda kullanırız: ```php -class PollPresenter extends Nette\UI\Application\Presenter +class PollPresenter extends Nette\Application\UI\Presenter { public function __construct( private PollControlFactory $pollControlFactory, @@ -356,13 +378,13 @@ class PollPresenter extends Nette\UI\Application\Presenter protected function createComponentPollControl(): PollControl { - $pollId = 1; // parametremizi geçebiliriz + $pollId = 1; // parametremizi iletebiliriz return $this->pollControlFactory->create($pollId); } } ``` -İşin en güzel yanı, Nette DI'nın bu kadar basit fabrikalar [üretebilmesidir |dependency-injection:factory], bu nedenle tüm kodu yazmak yerine sadece arayüzünü yazmanız yeterlidir: +Harika olan şey, Nette DI'nin bu tür basit fabrikaları [oluşturabilmesidir |dependency-injection:factory], bu yüzden tüm kodunu yazmak yerine sadece arayüzünü yazmak yeterlidir: ```php interface PollControlFactory @@ -371,21 +393,21 @@ interface PollControlFactory } ``` -Hepsi bu kadar. Nette bu arayüzü dahili olarak uygular ve onu kullanabileceğimiz sunucumuza enjekte eder. Ayrıca `$id` parametremizi ve `PollFacade` sınıfının örneğini sihirli bir şekilde bileşenimize aktarır. +Ve hepsi bu. Nette bu arayüzü dahili olarak uygular ve presenter'a iletir, burada onu zaten kullanabiliriz. Sihirli bir şekilde `$id` parametresini ve `PollFacade` sınıfının bir örneğini bileşenimize ekler. -Derinlemesine Bileşenler .[#toc-components-in-depth] -==================================================== +Bileşenler Derinlemesine +======================== -Bir Nette Uygulamasındaki bileşenler, bu bölümün konusu olan sayfalara gömdüğümüz bir web uygulamasının yeniden kullanılabilir parçalarıdır. Böyle bir bileşenin yetenekleri tam olarak nelerdir? +Nette Application'daki bileşenler, web uygulamasının yeniden kullanılabilir parçalarıdır, bunları sayfalara ekleriz ve bu bölümün tamamı onlara ayrılmıştır. Böyle bir bileşenin tam olarak hangi yetenekleri vardır? -1) bir şablon içinde oluşturulabilir -2) [AJAX isteği |ajax#invalidation] sırasında hangi bölümünün işleneceğini bilir (snippet'ler) -3) durumunu bir URL'de saklama yeteneğine sahiptir (kalıcı parametreler) -4) kullanıcı eylemlerine (sinyallere) yanıt verme yeteneğine sahiptir -5) hiyerarşik bir yapı oluşturur (kökün sunum yapan kişi olduğu) +1) şablonda oluşturulabilir +2) AJAX isteğinde [hangi bölümünün |ajax#Snippet ler] oluşturulacağını bilir (snippet'ler) +3) durumunu URL'de saklama yeteneğine sahiptir (kalıcı parametreler) +4) kullanıcı eylemlerine yanıt verme yeteneğine sahiptir (sinyaller) +5) hiyerarşik bir yapı oluşturur (kökü presenter'dır) -Bu işlevlerin her biri kalıtım soyu sınıflarından biri tarafından yerine getirilir. Oluşturma (1 + 2) [api:Nette\Application\UI\Control] tarafından, [yaşam döngüsüne |presenters#life-cycle-of-presenter] dahil etme (3, 4) [api:Nette\Application\UI\Component] sınıfı tarafından ve hiyerarşik yapının oluşturulması (5) [Container ve Component |component-model:] sınıfları tarafından gerçekleştirilir. +Bu fonksiyonların her biri kalıtım çizgisindeki sınıflardan biri tarafından sağlanır. Oluşturma (1 + 2) [api:Nette\Application\UI\Control] tarafından, [yaşam döngüsüne |presenters#Presenter Yaşam Döngüsü] dahil etme (3, 4) [api:Nette\Application\UI\Component] sınıfı tarafından ve hiyerarşik yapı oluşturma (5) [Container ve Component |component-model:] sınıfları tarafından halledilir. ``` Nette\ComponentModel\Component { IComponent } @@ -400,18 +422,18 @@ Nette\ComponentModel\Component { IComponent } ``` -Bileşenin Yaşam Döngüsü .[#toc-life-cycle-of-component] -------------------------------------------------------- +Bileşenin Yaşam Döngüsü +----------------------- [* lifecycle-component.svg *] *** *Bileşenin yaşam döngüsü* .<> -Kalıcı Parametrelerin Doğrulanması .[#toc-validation-of-persistent-parameters] ------------------------------------------------------------------------------- +Kalıcı Parametrelerin Doğrulanması +---------------------------------- -URL'lerden alınan [kalıcı parametrelerin |#persistent parameters] değerleri `loadState()` metodu tarafından özelliklere yazılır. Ayrıca, özellik için belirtilen veri türünün eşleşip eşleşmediğini kontrol eder, aksi takdirde 404 hatasıyla yanıt verir ve sayfa görüntülenmez. +URL'den alınan [kalıcı parametrelerin |#Kalıcı Parametreler] değerleri `loadState()` metodu tarafından özelliklere yazılır. Bu metot ayrıca özellikte belirtilen veri türünün eşleşip eşleşmediğini de kontrol eder, aksi takdirde 404 hatasıyla yanıt verir ve sayfa görüntülenmez. -Kalıcı parametrelere asla körü körüne güvenmeyin çünkü bunlar URL'de kullanıcı tarafından kolayca üzerine yazılabilir. Örneğin, `$this->page` sayfa numarasının 0'dan büyük olup olmadığını bu şekilde kontrol ederiz. Bunu yapmanın iyi bir yolu, yukarıda bahsedilen `loadState()` yöntemini geçersiz kılmaktır: +Kalıcı parametrelere asla körü körüne güvenmeyin, çünkü kullanıcı tarafından URL'de kolayca üzerine yazılabilirler. Örneğin, `$this->page` sayfa numarasının 0'dan büyük olup olmadığını bu şekilde doğrularız. Uygun yol, bahsedilen `loadState()` metodunu geçersiz kılmaktır: ```php class PaginatingControl extends Control @@ -422,7 +444,7 @@ class PaginatingControl extends Control public function loadState(array $params): void { parent::loadState($params); // burada $this->page ayarlanır - // kullanıcı değeri kontrolünü takip eder: + // ardından kendi değer kontrolümüz gelir: if ($this->page < 1) { $this->error(); } @@ -430,27 +452,27 @@ class PaginatingControl extends Control } ``` -Tersi işlem, yani kalıcı özelliklerden değerlerin toplanması, `saveState()` yöntemi tarafından gerçekleştirilir. +Ters işlem, yani kalıcı özelliklerden değerlerin toplanması, `saveState()` metodunun sorumluluğundadır. -Derinlemesine Sinyaller .[#toc-signals-in-depth] ------------------------------------------------- +Sinyaller Derinlemesine +----------------------- -Bir sinyal, orijinal istek gibi bir sayfanın yeniden yüklenmesine neden olur (AJAX hariç) ve `Nette\Application\UI\Component` sınıfındaki varsayılan uygulaması `handle{Signal}` sözcüklerinden oluşan bir yöntemi çağırmaya çalışan `signalReceived($signal)` yöntemini çağırır. Daha sonraki işlemler verilen nesneye dayanır. `Component` 'un torunları olan nesneler (yani `Control` ve `Presenter`) `handle{Signal}` 'u ilgili parametrelerle çağırmaya çalışır. +Bir sinyal, sayfanın orijinal istekteki gibi tamamen yeniden yüklenmesine neden olur (AJAX ile çağrıldığı durumlar hariç) ve `signalReceived($signal)` metodunu çağırır; bu metodun `Nette\Application\UI\Component` sınıfındaki varsayılan uygulaması, `handle{signal}` kelimelerinden oluşan bir metodu çağırmaya çalışır. Daha sonraki işlemler ilgili nesneye bağlıdır. `Component`'ten (yani `Control` ve `Presenter`) miras alan nesneler, ilgili parametrelerle `handle{signal}` metodunu çağırmaya çalışarak yanıt verirler. -Başka bir deyişle: `handle{Signal}` metodunun tanımı alınır ve istekte alınan tüm parametreler metodun parametreleriyle eşleştirilir. Yani URL'den gelen `id` parametresi metodun `$id` parametresiyle, `something` parametresi `$something` parametresiyle eşleştirilir ve bu böyle devam eder. Ve eğer metot mevcut değilse, `signalReceived` metodu [bir istisna |api:Nette\Application\UI\BadSignalException] atar. +Başka bir deyişle: `handle{signal}` fonksiyonunun tanımı alınır ve istekle birlikte gelen tüm parametreler alınır ve argümanlara URL'den parametreler ada göre atanır ve ilgili metot çağrılmaya çalışılır. Örneğin, `$id` parametresi olarak URL'deki `id` parametresinin değeri, `$something` olarak URL'deki `something` vb. iletilir. Ve metot mevcut değilse, `signalReceived` metodu [bir istisna |api:Nette\Application\UI\BadSignalException] atar. -Sinyal, bileşen ağacına bağlıysa `SignalReceiver` arayüzünü uygulayan herhangi bir bileşen, nesne sunucusu tarafından alınabilir. +Sinyal, `SignalReceiver` arayüzünü uygulayan ve bileşen ağacına bağlı olan herhangi bir bileşen, presenter veya nesne tarafından alınabilir. -Sinyallerin ana alıcıları `Presenters` ve `Control` adresini genişleten görsel bileşenlerdir. Sinyal, bir nesne için bir şey yapması gerektiğine dair bir işarettir - anket kullanıcıdan gelen bir oyu sayar, haber kutusu açılmalıdır, form gönderilmiştir ve verileri işlemelidir vb. +Sinyallerin ana alıcıları `Presenter`'lar ve `Control`'dan miras alan görsel bileşenler olacaktır. Sinyal, nesneye bir şey yapması gerektiğine dair bir işaret görevi görmelidir - anket kullanıcının oyunu saymalı, haber bloğu genişlemeli ve iki kat daha fazla haber göstermeli, form gönderildi ve verileri işlemeli vb. -Sinyal için URL [Component::link() |api:Nette\Application\UI\Component::link()] yöntemi kullanılarak oluşturulur. `$destination` parametresi olarak `{signal}!` dizesini ve `$args` olarak sinyal işleyicisine iletmek istediğimiz argümanların bir dizisini iletiriz. Sinyal parametreleri geçerli sunucunun/görüntünün URL'sine eklenir. **URL'deki `?do` parametresi çağrılan sinyali belirler.** +Sinyal için URL, [Component::link() |api:Nette\Application\UI\Component::link()] metodu kullanılarak oluşturulur. `$destination` parametresi olarak `{signal}!` dizesini ve `$args` olarak sinyale iletmek istediğimiz argümanlar dizisini iletiriz. Sinyal her zaman mevcut presenter ve eylem üzerinde mevcut parametrelerle çağrılır, sinyal parametreleri yalnızca eklenir. Ek olarak, en başta **sinyali belirten `?do` parametresi** eklenir. -Biçimi `{signal}` veya `{signalReceiver}-{signal}` şeklindedir. `{signalReceiver}`, sunum programındaki bileşenin adıdır. Bu nedenle bileşenlerin adında kısa çizgi (yanlış olarak tire) bulunamaz - bileşenin adını ve sinyali bölmek için kullanılır, ancak birkaç bileşen oluşturmak mümkündür. +Formatı ya `{signal}` ya da `{signalReceiver}-{signal}` şeklindedir. `{signalReceiver}`, presenter'daki bileşenin adıdır. Bu nedenle, bileşen adında tire olamaz - bileşen adını ve sinyali ayırmak için kullanılır, ancak bu şekilde birkaç bileşeni iç içe geçirmek mümkündür. -[isSignalReceiver() |api:Nette\Application\UI\Presenter::isSignalReceiver()] yöntemi, bir bileşenin (ilk bağımsız değişken) bir sinyalin (ikinci bağımsız değişken) alıcısı olup olmadığını doğrular. İkinci bağımsız değişken atlanabilir - o zaman bileşenin herhangi bir sinyalin alıcısı olup olmadığını bulur. İkinci parametre `true` ise, bileşenin veya torunlarının bir sinyalin alıcısı olup olmadığını doğrular. +[isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] metodu, bileşenin (ilk argüman) sinyalin (ikinci argüman) alıcısı olup olmadığını doğrular. İkinci argümanı atlayabiliriz - o zaman bileşenin herhangi bir sinyalin alıcısı olup olmadığını kontrol eder. İkinci parametre olarak `true` belirtilebilir ve böylece yalnızca belirtilen bileşenin değil, aynı zamanda herhangi bir alt öğesinin de alıcı olup olmadığını doğrular. -`handle{Signal}` adresinden önceki herhangi bir aşamada, sinyal yürütme sorumluluğunu üstlenen [processSignal() |api:Nette\Application\UI\Presenter::processSignal()] yöntemi çağrılarak manuel olarak sinyal gerçekleştirilebilir. Alıcı bileşeni alır (ayarlanmamışsa sunucunun kendisidir) ve sinyali gönderir. +`handle{signal}`'den önceki herhangi bir aşamada, sinyali manuel olarak [processSignal()|api:Nette\Application\UI\Presenter::processSignal()] metodunu çağırarak yürütebiliriz; bu metot sinyalin işlenmesini üstlenir - sinyalin alıcısı olarak belirlenen bileşeni alır (sinyal alıcısı belirtilmemişse, presenter'ın kendisidir) ve ona sinyali gönderir. Örnek: @@ -460,4 +482,4 @@ if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, ' } ``` -Sinyal zamanından önce yürütülür ve bir daha çağrılmaz. +Böylece sinyal erken yürütülür ve tekrar çağrılmaz. diff --git a/application/tr/configuration.texy b/application/tr/configuration.texy index be7d92db7c..c933edfdd1 100644 --- a/application/tr/configuration.texy +++ b/application/tr/configuration.texy @@ -1,58 +1,71 @@ -Uygulama Yapılandırma -********************* +Uygulama Yapılandırması +*********************** .[perex] -Nette Uygulaması için yapılandırma seçeneklerine genel bakış. +Nette Uygulamaları için yapılandırma seçeneklerine genel bakış. -Uygulama .[#toc-application] -============================ +Application +=========== ```neon application: - # Tracy BlueScreen'de "Nette Application" panelini gösterir? - debugger: ... # (bool) varsayılan olarak true + # Tracy BlueScreen'de "Nette Application" panelini göster? + debugger: ... # (bool) varsayılan true - # error-presenter hata durumunda çağrılacak mı? - catchExceptions: ... # (bool) üretim modunda varsayılan olarak true + # hata durumunda error-presenter çağrılacak mı? + # yalnızca geliştirme modunda etkilidir + catchExceptions: ... # (bool) varsayılan true - # hata sunucusunun adı - errorPresenter: Error # (string) varsayılan olarak 'Nette:Error' + # error-presenter adı + errorPresenter: Error # (string|array) varsayılan 'Nette:Error' - # sunum yapan kişinin adını bir sınıfa çözümlemek için kuralları tanımlar + # presenter'lar ve eylemler için takma adları tanımlar + aliases: ... + + # presenter adını sınıfa çevirme kurallarını tanımlar mapping: ... - # kötü bağlantılar uyarı oluşturuyor mu? - # sadece geliştirici modunda etkilidir - silentLinks: ... # (bool) varsayılan değer false + # hatalı bağlantılar uyarı oluşturmaz mı? + # yalnızca geliştirme modunda etkilidir + silentLinks: ... # (bool) varsayılan false ``` -Hata sunucuları geliştirme modunda varsayılan olarak çağrılmadığından ve hatalar Tracy tarafından görüntülendiğinden, `catchExceptions` değerini `true` olarak değiştirmek, hata sunucularının geliştirme sırasında doğru çalıştığını doğrulamaya yardımcı olur. +`nette/application` sürüm 3.2'den itibaren bir çift error-presenter tanımlanabilir: -`silentLinks` seçeneği, bağlantı oluşturma başarısız olduğunda (örneğin, sunum yapan kişi olmadığından vb.) Nette'in geliştirici modunda nasıl davranacağını belirler. Varsayılan değer olan `false`, Nette'in `E_USER_WARNING` adresini tetikleyeceği anlamına gelir. `true` olarak ayarlanması bu hata mesajını bastırır. Bir üretim ortamında, `E_USER_WARNING` her zaman çağrılır. Bu davranışı, sunum yapan değişken [$invalidLinkMode'u |creating-links#Invalid Links] ayarlayarak da etkileyebiliriz. +```neon +application: + errorPresenter: + 4xx: Error4xx # Nette\Application\BadRequestException istisnası için + 5xx: Error5xx # diğer istisnalar için +``` -[Eşleme |modules#mapping], sınıf adının sunum yapan kişinin adından türetildiği [kuralları tanımlar |modules#mapping]. +`silentLinks` seçeneği, Nette'nin geliştirme modunda bir bağlantı oluşturma başarısız olduğunda (örneğin, presenter mevcut olmadığı için vb.) nasıl davranacağını belirler. Varsayılan `false` değeri, Nette'nin bir `E_USER_WARNING` hatası atacağı anlamına gelir. `true` olarak ayarlamak bu hata mesajını bastırır. Üretim ortamında `E_USER_WARNING` her zaman tetiklenir. Bu davranışı, presenter değişkeni [$invalidLinkMode |creating-links#Geçersiz Bağlantılar] ayarlayarak da etkileyebiliriz. +[Takma adlar, sık kullanılan presenter'lara bağlantı vermeyi basitleştirir |creating-links#Takma Adlar Alias]. -Sunum Yapanların Otomatik Kaydı .[#toc-automatic-registration-of-presenters] ----------------------------------------------------------------------------- +[Eşleme, presenter adından sınıf adının nasıl türetileceğine ilişkin kuralları tanımlar |directory-structure#Presenter Eşlemesi]. -Nette, sunum yapan kişileri otomatik olarak DI konteynerine hizmet olarak ekler, bu da onların oluşturulmasını önemli ölçüde hızlandırır. Nette'in sunucuları nasıl bulacağı yapılandırılabilir: + +Presenter'ların Otomatik Kaydı +------------------------------ + +Nette, presenter'ları otomatik olarak DI konteynerine servis olarak ekler, bu da oluşturulmalarını önemli ölçüde hızlandırır. Nette'nin presenter'ları nasıl bulduğu yapılandırılabilir: ```neon application: - # Composer sınıf haritasında sunum yapan kişileri aramak için? - scanComposer: ... # (bool) varsayılan değer true + # Composer sınıf haritasında presenter'ları ara? + scanComposer: ... # (bool) varsayılan true - # sınıf ve dosya adıyla eşleşmesi gereken bir maske - scanFilter: ... # (string) varsayılan olarak '*Presenter' + # sınıf ve dosya adının uyması gereken maske + scanFilter: ... # (string) varsayılan '*Presenter' - # sunum yapan kişileri hangi dizinlerde arayalım? - scanDirs: # (string[]|false) varsayılan değer '%appDir%' + # presenter'lar hangi dizinlerde aranacak? + scanDirs: # (string[]|false) varsayılan '%appDir%' - %vendorDir%/mymodule ``` -`scanDirs` adresinde listelenen dizinler `%appDir%` varsayılan değerini geçersiz kılmaz, ancak onu tamamlar, bu nedenle `scanDirs` hem `%appDir%` hem de `%vendorDir%/mymodule` yollarını içerecektir. Varsayılan dizinin üzerine yazmak istiyorsak [ünlem işareti |dependency-injection:configuration#Merging] kullanırız: +`scanDirs` içinde belirtilen dizinler, varsayılan `%appDir%` değerinin üzerine yazmaz, ancak onu tamamlar, bu nedenle `scanDirs` hem `%appDir%` hem de `%vendorDir%/mymodule` yollarını içerecektir. Varsayılan dizini atlamak istiyorsak, değeri üzerine yazan [ünlem işaretini |dependency-injection:configuration#Birleştirme] kullanırız: ```neon application: @@ -60,64 +73,73 @@ application: - %vendorDir%/mymodule ``` -Dizin taraması false ayarı yapılarak kapatılabilir. Sunucuların otomatik olarak eklenmesinin tamamen engellenmesini önermiyoruz, aksi takdirde uygulama performansı düşecektir. +Dizin taraması, false değeri belirtilerek kapatılabilir. Presenter'ların otomatik olarak eklenmesini tamamen bastırmanızı önermiyoruz, çünkü aksi takdirde uygulama performansı düşer. -Latte .[#toc-latte] -=================== +Latte Şablonları +================ -Bu ayar, Latte'nin bileşenler ve sunuculardaki davranışını genel olarak etkiler. +Bu ayar, Latte'nin bileşenlerdeki ve presenter'lardaki davranışını genel olarak etkilemenizi sağlar. ```neon latte: - # Latte panelini Tracy Bar'da ana şablon için mi (true) yoksa tüm bileşenler için mi (all) gösteriyor? - debugger: ... # (true|false|'all') varsayılan olarak true + # Ana şablon için (true) veya tüm bileşenler için (all) Tracy Bar'da Latte panelini göster? + debugger: ... # (true|false|'all') varsayılan true + + # declare(strict_types=1) başlığıyla şablonlar oluşturur + strictTypes: ... # (bool) varsayılan false - # declare(strict_types=1) ile şablonlar oluşturur - strictTypes: ... # (bool) varsayılan değer false + # [katı ayrıştırıcı |latte:develop#striktní režim] modunu açar + strictParsing: ... # (bool) varsayılan false - # $this->template sınıfı - templateClass: App\MyTemplateClass # varsayılan olarak Nette\Bridges\ApplicationLatte\DefaultTemplate + # [oluşturulan kodun kontrolünü |latte:develop#Kontrola vygenerovaného kódu] etkinleştirir + phpLinter: ... # (string) varsayılan null + + # yerel ayarı ayarlar + locale: cs_CZ # (string) varsayılan null + + # $this->template nesnesinin sınıfı + templateClass: App\MyTemplateClass # varsayılan Nette\Bridges\ApplicationLatte\DefaultTemplate ``` -Latte sürüm 3 kullanıyorsanız, kullanarak yeni [uzantı |latte:creating-extension] ekleyebilirsiniz: +Latte sürüm 3 kullanıyorsanız, yeni [uzantıları |latte:extending-latte#Latte Extension] şu şekilde ekleyebilirsiniz: ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -/--comment - - - - - +Latte sürüm 2 kullanıyorsanız, yeni etiketleri sınıf adını belirterek veya bir servise referans vererek kaydedebilirsiniz. Varsayılan olarak `install()` metodu çağrılır, ancak bu, başka bir metodun adını belirterek değiştirilebilir: +```neon +latte: + # özel Latte etiketlerini kaydet + macros: + - App\MyLatteMacros::register # statik metot, sınıf adı veya çağrılabilir + - @App\MyLatteMacrosFactory # install() metoduna sahip servis + - @App\MyLatteMacrosFactory::register # register() metoduna sahip servis + +services: + - App\MyLatteMacrosFactory +``` - - - -\-- - - -Yönlendirme .[#toc-routing] -=========================== +Yönlendirme +=========== Temel ayarlar: ```neon -yönlendirme: - # Tracy Bar'da yönlendirme panelini gösterir mi? - debugger: ... # (bool) varsayılan değer true +routing: + # Tracy Bar'da yönlendirme panelini göster? + debugger: ... # (bool) varsayılan true - # yönlendiriciyi DI konteynerine serileştirmek için? - cache: ... # (bool) varsayılan değer false + # yönlendiriciyi DI konteynerine serileştirir + cache: ... # (bool) varsayılan false ``` -Yönlendirici genellikle [RouterFactory |routing#Route Collection] sınıfında tanımlanır. Alternatif olarak, yönlendiriciler `mask: action` çiftleri kullanılarak yapılandırmada da tanımlanabilir, ancak bu yöntem ayarlarda bu kadar geniş bir çeşitlilik sunmaz: +Yönlendirme genellikle [RouterFactory |routing#Rota Koleksiyonu] sınıfında tanımlanır. Alternatif olarak, rotalar yapılandırmada `maske: eylem` çiftleri kullanılarak da tanımlanabilir, ancak bu yöntem ayar konusunda o kadar geniş bir çeşitlilik sunmaz: ```neon routing: @@ -127,8 +149,8 @@ routing: ``` -Sabitler .[#toc-constants] -========================== +Sabitler +======== PHP sabitleri oluşturma. @@ -137,18 +159,33 @@ constants: Foobar: 'baz' ``` -`Foobar` sabiti başlangıçtan sonra oluşturulacaktır. +Uygulama başlatıldıktan sonra `Foobar` sabiti oluşturulacaktır. .[note] -Sabitler global olarak kullanılabilir değişkenler olarak kullanılmamalıdır. Nesnelere değer aktarmak için [bağımlılık enjeksiyonu |dependency-injection:passing-dependencies] kullanın. +Sabitler, genel olarak erişilebilir değişkenler gibi kullanılmamalıdır. Nesnelere değer iletmek için [bağımlılık enjeksiyonunu |dependency-injection:passing-dependencies] kullanın. PHP === -PHP yönergelerini ayarlayabilirsiniz. Tüm yönergelere genel bir bakış [php.net |https://www.php.net/manual/en/ini.list.php] adresinde bulunabilir. +PHP yönergelerinin ayarlanması. Tüm yönergelerin bir listesini [php.net |https://www.php.net/manual/en/ini.list.php] adresinde bulabilirsiniz. ```neon php: date.timezone: Europe/Prague ``` + + +DI Servisleri +============= + +Bu servisler DI konteynerine eklenir: + +| Ad | Tip | Açıklama +|---------------------------------------------------------- +| `application.application` | [api:Nette\Application\Application] | [tüm uygulamanın başlatıcısı |how-it-works#Nette Application] +| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] +| `application.presenterFactory` | [api:Nette\Application\PresenterFactory] | presenter'lar için fabrika +| `application.###` | [api:Nette\Application\UI\Presenter] | bireysel presenter'lar +| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | `Latte\Engine` nesnesi için fabrika +| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | [`$this->template` |templates] için fabrika diff --git a/application/tr/creating-links.texy b/application/tr/creating-links.texy index 1b8325a446..ff2a3d3020 100644 --- a/application/tr/creating-links.texy +++ b/application/tr/creating-links.texy @@ -3,158 +3,158 @@ URL Bağlantıları Oluşturma
    -Nette'de bağlantı oluşturmak parmakla işaret etmek kadar kolaydır. Sadece işaret edin ve çerçeve sizin için tüm işi yapacaktır. Biz göstereceğiz: +Nette'de bağlantı oluşturmak parmakla göstermek kadar kolaydır. Sadece işaret etmeniz yeterlidir ve framework tüm işi sizin için yapar. Şunları göstereceğiz: -- şablonlarda ve başka yerlerde nasıl bağlantı oluşturulur +- şablonlarda ve başka yerlerde bağlantılar nasıl oluşturulur - mevcut sayfaya bir bağlantı nasıl ayırt edilir -- geçersiz bağlantılar ne olacak +- geçersiz bağlantılarla ne yapılmalı
    -[Çift yönlü yönlendirme |routing] sayesinde, daha sonra değişebilecek veya oluşturulması karmaşık olabilecek uygulama URL'lerini şablonlarda veya kodda sabit kodlamak zorunda kalmazsınız. Sadece bağlantıdaki sunucuyu ve eylemi belirtin, herhangi bir parametre geçirin ve çerçeve URL'yi kendisi oluşturacaktır. Aslında, bir fonksiyon çağırmaya çok benzer. Bunu seveceksiniz. +[Çift yönlü yönlendirme |routing] sayesinde, şablonlarınıza veya kodunuza daha sonra değişebilecek veya karmaşık bir şekilde birleştirilmesi gereken uygulamanızın URL adreslerini asla sabit kodlamanız gerekmeyecektir. Bağlantıda presenter'ı ve eylemi belirtmeniz, olası parametreleri iletmeniz yeterlidir ve framework URL'yi kendisi oluşturacaktır. Aslında, bir fonksiyonu çağırmaya çok benzer. Bunu seveceksiniz. -Sunucu Şablonunda .[#toc-in-the-presenter-template] -=================================================== +Presenter şablonunda +==================== -Çoğu zaman şablonlarda bağlantılar oluştururuz ve `n:href` niteliği harika bir yardımcıdır: +En sık olarak şablonlarda bağlantılar oluştururuz ve `n:href` niteliği harika bir yardımcıdır: ```latte -detail +detay ``` -`href` HTML özniteliği yerine `n:href`[n: |latte:syntax#n:attributes] özniteliğini kullandığımıza dikkat edin. Değeri, `href` özniteliğinde alıştığınız gibi bir URL değil, sunum yapan kişinin ve eylemin adıdır. +HTML niteliği `href` yerine [n:niteliği |latte:syntax#n:nitelikler] `n:href` kullandığımıza dikkat edin. Değeri, `href` niteliğinde olduğu gibi bir URL değil, presenter'ın ve eylemin adıdır. -Bir bağlantıya tıklamak, basitçe söylemek gerekirse, bir metodu çağırmak gibidir `ProductPresenter::renderShow()`. Ve eğer imzasında parametreler varsa, onu argümanlarla çağırabiliriz: +Bir bağlantıya tıklamak, basitleştirilmiş bir ifadeyle, `ProductPresenter::renderShow()` metodunu çağırmak gibidir. Ve eğer imzasında parametreler varsa, onu argümanlarla çağırabiliriz: ```latte -detail +ürün detayı ``` -Adlandırılmış parametreleri geçmek de mümkündür. Aşağıdaki bağlantı `lang` parametresini `en` değeriyle geçirir: +Adlandırılmış parametreleri iletmek de mümkündür. Aşağıdaki bağlantı, `lang` parametresini `cs` değeriyle iletir: ```latte -detail +ürün detayı ``` -`ProductPresenter::renderShow()` yönteminin imzasında `$lang` yoksa, parametrenin değerini `$lang = $this->getParameter('lang')` kullanarak okuyabilir. +Eğer `ProductPresenter::renderShow()` metodu imzasında `$lang` içermiyorsa, parametrenin değerini `$lang = $this->getParameter('lang')` kullanarak veya [özellikten |presenters#İstek Parametreleri] öğrenebilir. -Parametreler bir dizide saklanıyorsa, `...` operatörü (veya Latte 2.x'te `(expand)` operatörü) ile genişletilebilirler: +Parametreler bir dizide saklanıyorsa, `...` operatörü (Latte 2.x'te `(expand)` operatörü) ile genişletilebilirler: ```latte -{var $args = [$product->id, lang => en]} -detail +{var $args = [$product->id, lang => cs]} +ürün detayı ``` -[Kalıcı parametreler |presenters#persistent parameters] olarak adlandırılan [parametreler |presenters#persistent parameters] de bağlantılarda otomatik olarak aktarılır. +Bağlantılarda sözde [kalıcı parametreler |presenters#Kalıcı Parametreler] de otomatik olarak iletilir. -Öznitelik `n:href` HTML etiketleri için çok kullanışlıdır ``. Bağlantıyı başka bir yere, örneğin metnin içine yazdırmak istiyorsak `{link}` adresini kullanırız: +`n:href` niteliği HTML `` etiketleri için çok kullanışlıdır. Bağlantıyı başka bir yerde, örneğin metinde yazdırmak istiyorsak, `{link}` kullanırız: ```latte -URL is: {link Home:default} +Adres: {link Home:default} ``` -Kod'da .[#toc-in-the-code] -========================== +Kodda +===== -Sunucuda bir bağlantı oluşturmak için `link()` yöntemi kullanılır: +Presenter'da bir bağlantı oluşturmak için `link()` metodu kullanılır: ```php $url = $this->link('Product:show', $product->id); ``` -Parametreler, adlandırılmış parametrelerin de belirtilebildiği bir dizi olarak da aktarılabilir: +Parametreler, adlandırılmış parametrelerin de belirtilebildiği bir dizi kullanılarak da iletilebilir: ```php $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); ``` -[Linkler, LinkGenerator |#LinkGenerator] ve onun `link()` yöntemi kullanılarak bir sunucu olmadan da oluşturulabilir. +Bağlantılar presenter olmadan da oluşturulabilir, bunun için [#LinkGenerator] ve onun `link()` metodu vardır. -Sunucu Bağlantıları .[#toc-links-to-presenter] -============================================== +Presenter'a Bağlantılar +======================= -Bağlantının hedefi sunum yapan ve eylem ise, bu sözdizimine sahiptir: +Bağlantının hedefi bir presenter ve eylem ise, şu sözdizimine sahiptir: ``` [//] [[[[:]module:]presenter:]action | this] [#fragment] ``` -Bu format tüm Latte etiketleri ve bağlantılarla çalışan tüm sunum yöntemleri tarafından desteklenmektedir, yani `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` ve ayrıca [LinkGenerator |#LinkGenerator]. Yani örneklerde `n:href` kullanılmış olsa bile, fonksiyonlardan herhangi biri olabilir. +Format, tüm Latte etiketleri ve bağlantılarla çalışan tüm presenter metotları tarafından desteklenir, yani `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` ve ayrıca [#LinkGenerator]. Dolayısıyla, örneklerde `n:href` kullanılmış olsa bile, fonksiyonlardan herhangi biri orada olabilirdi. -Bu nedenle temel form `Presenter:action` şeklindedir: +Temel form bu nedenle `Presenter:action` şeklindedir: ```latte -home +ana sayfa ``` -Geçerli sunucunun eylemine bağlantı verirsek, adını atlayabiliriz: +Mevcut presenter'ın bir eylemine bağlantı veriyorsak, adını atlayabiliriz: ```latte -home +ana sayfa ``` -Eylem `default` ise, bunu atlayabiliriz, ancak iki nokta üst üste kalmalıdır: +Hedef eylem `default` ise, onu atlayabiliriz, ancak iki nokta üst üste kalmalıdır: ```latte -home +ana sayfa ``` -Bağlantılar diğer [modüllere |modules] de işaret edebilir. Burada, bağlantılar alt modüllere göreli veya mutlak olarak ayırt edilir. Prensip disk yollarına benzer, sadece eğik çizgiler yerine iki nokta üst üste vardır. Gerçek sunucunun `Front` modülünün bir parçası olduğunu varsayalım, o zaman yazacağız: +Bağlantılar ayrıca diğer [modüllere |directory-structure#Presenter lar ve Şablonlar] de yönlendirebilir. Burada bağlantılar, iç içe geçmiş alt modüle göreceli veya mutlak olarak ayırt edilir. Prensip, diskteki yollara benzer, yalnızca eğik çizgiler yerine iki nokta üst üste kullanılır. Mevcut presenter'ın `Front` modülünün bir parçası olduğunu varsayalım, o zaman şunu yazarız: ```latte -link to Front:Shop:Product:show -link to Admin:Product:show +Front:Shop:Product:show bağlantısı +Admin:Product:show bağlantısı ``` -Özel bir durum, [kendine bağlantı |#Links to Current Page] vermektir. Burada hedef olarak `this` yazacağız. +Özel bir durum, hedef olarak `this` belirttiğimiz [kendine bağlantıdır |#Mevcut Sayfaya Bağlantı]. ```latte -refresh +yenile ``` -HTML sayfasının belirli bir bölümüne `#` hash sembolünden sonra gelen fragment adı verilen bir parça aracılığıyla bağlantı verebiliriz: +Izgara işareti `#` sonrasındaki bir parça (fragment) aracılığıyla sayfanın belirli bir bölümüne bağlantı verebiliriz: ```latte -link to Home:default and fragment #main +Home:default ve #main parçasına bağlantı ``` -Mutlak Yollar .[#toc-absolute-paths] -==================================== +Mutlak Yollar +============= -`link()` veya `n:href` tarafından oluşturulan bağlantılar her zaman mutlak yollardır (yani `/` ile başlarlar), ancak `https://domain` gibi bir protokol ve etki alanı içeren mutlak URL'ler değildir. +`link()` veya `n:href` kullanılarak oluşturulan bağlantılar her zaman mutlak yollardır (yani `/` karakteriyle başlarlar), ancak `https://domain` gibi protokol ve alan adı içeren mutlak URL'ler değildir. -Mutlak bir URL oluşturmak için başına iki eğik çizgi ekleyin (örneğin, `n:href="//Home:"`). Ya da `$this->absoluteUrls = true` adresini ayarlayarak sunucuyu yalnızca mutlak bağlantılar oluşturacak şekilde değiştirebilirsiniz. +Mutlak bir URL oluşturmak için başına iki eğik çizgi ekleyin (örn. `n:href="//Home:"`). Veya presenter'ı yalnızca mutlak bağlantılar oluşturacak şekilde `$this->absoluteUrls = true` ayarlayarak değiştirebilirsiniz. -Güncel Sayfaya Bağlantı .[#toc-link-to-current-page] -==================================================== +Mevcut Sayfaya Bağlantı +======================= -Hedef `this` geçerli sayfaya bir bağlantı oluşturacaktır: +`this` hedefi mevcut sayfaya bir bağlantı oluşturur: ```latte -refresh +yenile ``` -Aynı zamanda, imzasında belirtilen tüm parametreler `render()` veya `action()` yöntemi aktarılır. Yani `Product:show` ve `id:123` sayfalarındaysak, `this` bağlantısı da bu parametreyi geçirecektir. +Aynı zamanda, `action()` veya `render()` metotlarının imzasında belirtilen tüm parametreler de, `action()` tanımlanmamışsa iletilir. Dolayısıyla, `Product:show` sayfasındaysak ve `id: 123` ise, `this` bağlantısı bu parametreyi de iletecektir. Elbette parametreleri doğrudan belirtmek de mümkündür: ```latte -refresh +yenile ``` -İşlev `isLinkCurrent()` bağlantının hedefinin geçerli sayfayla aynı olup olmadığını belirler. Bu, örneğin bir şablonda bağlantıları vb. ayırt etmek için kullanılabilir. +`isLinkCurrent()` fonksiyonu, bağlantı hedefinin mevcut sayfayla aynı olup olmadığını kontrol eder. Bu, örneğin şablonda bağlantıları ayırt etmek vb. için kullanılabilir. -Parametreler `link()` yöntemiyle aynıdır, ancak belirli bir eylem yerine `*` joker karakterini kullanmak da mümkündür, bu da sunum yapan kişinin herhangi bir eylemi anlamına gelir. +Parametreler `link()` metoduyla aynıdır, ancak ek olarak belirli bir eylem yerine, söz konusu presenter'ın herhangi bir eylemi anlamına gelen `*` joker karakterini belirtmek mümkündür. ```latte {if !isLinkCurrent('Admin:login')} - Přihlaste se + Giriş Yap {/if}
  • @@ -162,58 +162,58 @@ Parametreler `link()` yöntemiyle aynıdır, ancak belirli bir eylem yerine `*`
  • ``` -Kısaltılmış bir form, tek bir öğede `n:href` ile birlikte kullanılabilir: +Tek bir öğede `n:href` ile birlikte kullanıldığında, kısaltılmış bir form kullanılabilir: ```latte -... +... ``` -Joker karakter `*` yalnızca sunum yapan kişinin eylemini değiştirir, sunum yapan kişinin kendisini değiştirmez. +`*` joker karakteri yalnızca eylem yerine kullanılabilir, presenter yerine kullanılamaz. -Belirli bir modülde mi yoksa onun alt modülünde mi olduğumuzu öğrenmek için `isModuleCurrent(moduleName)` fonksiyonunu kullanabiliriz. +Belirli bir modülde veya alt modülünde olup olmadığımızı kontrol etmek için `isModuleCurrent(moduleName)` metodunu kullanırız. ```latte -
  • +
  • ...
  • ``` -Sinyal Bağlantıları .[#toc-links-to-signal] -=========================================== +Sinyale Bağlantılar +=================== -Bağlantının hedefi yalnızca sunum yapan kişi ve eylem değil, aynı zamanda [sinyal |components#Signal] de olabilir (metodu çağırırlar `handle()`). Sözdizimi aşağıdaki gibidir: +Bağlantının hedefi yalnızca bir presenter ve eylem olmak zorunda değildir, aynı zamanda bir [sinyal |components#Sinyal] de olabilir (`handle()` metodunu çağırırlar). O zaman sözdizimi aşağıdaki gibidir: ``` [//] [sub-component:]signal! [#fragment] ``` -Bu nedenle sinyal ünlem işareti ile ayırt edilir: +Sinyal bu nedenle bir ünlem işaretiyle ayırt edilir: ```latte -signal +sinyal ``` -Ayrıca alt bileşenin (veya alt alt bileşenin) sinyaline bir bağlantı da oluşturabilirsiniz: +Bir alt bileşenin (veya alt-alt bileşenin) sinyaline bir bağlantı oluşturmak da mümkündür: ```latte -signal +sinyal ``` -Bileşendeki Bağlantılar .[#toc-links-in-component] -================================================== +Bileşendeki Bağlantılar +======================= -[Bileşenler |components], çevrelerindeki sunucularla hiçbir ilişkisi olmaması gereken ayrı yeniden kullanılabilir birimler olduğundan, bağlantılar biraz farklı çalışır. Latte niteliği `n:href` ve etiketi `{link}` ve `link()` ve diğerleri gibi bileşen yöntemleri her zaman hedefi **sinyal adı** olarak kabul eder. Bu nedenle ünlem işareti kullanmak gerekli değildir: +[Bileşenler|components] bağımsız, yeniden kullanılabilir birimler olduğundan ve çevreleyen presenter'larla herhangi bir bağlantısı olmaması gerektiğinden, bağlantılar burada biraz farklı çalışır. Latte niteliği `n:href` ve `{link}` etiketi ile `link()` gibi bileşen metotları ve diğerleri, bağlantı hedefini **her zaman sinyal adı olarak** kabul eder. Bu nedenle ünlem işareti belirtmek bile gerekli değildir: ```latte -signal, not an action +sinyal, eylem değil ``` -Bileşen şablonunda sunum yapanlara bağlantı vermek istiyorsak `{plink}` etiketini kullanırız: +Bileşen şablonunda presenter'lara bağlantı vermek isteseydik, bunun için `{plink}` etiketini kullanırdık: ```latte -home +giriş ``` veya kodda @@ -223,17 +223,41 @@ $this->getPresenter()->link('Home:default') ``` -Geçersiz Bağlantılar .[#toc-invalid-links] +Takma Adlar (Alias) .{data-version:v3.2.2} ========================================== -Geçersiz bir bağlantı oluşturabiliriz - ya mevcut olmayan bir sunucuya atıfta bulunduğu için ya da hedef yöntemin imzasında aldığından daha fazla parametre geçtiği için ya da hedeflenen eylem için oluşturulmuş bir URL olamadığında. Geçersiz bağlantılarla ne yapılacağı `Presenter::$invalidLinkMode` statik değişkeni tarafından belirlenir. Bu değerlerden (sabitler) birine sahip olabilir: +Bazen Presenter:eylem çiftine kolayca hatırlanabilir bir takma ad atamak yararlı olabilir. Örneğin, `Front:Home:default` giriş sayfasını basitçe `home` olarak veya `Admin:Dashboard:default` sayfasını `admin` olarak adlandırmak. -- `Presenter::InvalidLinkSilent` - sessiz mod, URL olarak `#` sembolünü döndürür -- `Presenter::InvalidLinkWarning` - E_USER_WARNING üretilecektir -- `Presenter::InvalidLinkTextual` - görsel uyarı, hata metni bağlantıda görüntülenir -- `Presenter::InvalidLinkException` - InvalidLinkException fırlatılacaktır +Takma adlar, [yapılandırmada|configuration] `application › aliases` anahtarı altında tanımlanır: -Üretim modunda varsayılan kurulum `InvalidLinkWarning` ve geliştirme modunda `InvalidLinkWarning | InvalidLinkTextual` şeklindedir. `InvalidLinkWarning` üretim ortamında betiği öldürmez, ancak uyarı günlüğe kaydedilir. Geliştirme ortamında, [Tracy |tracy:] uyarıyı kesecek ve hata mavi ekranını görüntüleyecektir. Eğer `InvalidLinkTextual` ayarlanmışsa, sunucu ve bileşenler hata mesajını `#error:` ile başlayan URL olarak döndürür. Bu tür bağlantıları görünür kılmak için stil sayfamıza bir CSS kuralı ekleyebiliriz: +```neon +application: + aliases: + home: Front:Home:default + admin: Admin:Dashboard:default + sign: Front:Sign:in +``` + +Bağlantılarda, örneğin bir at işareti kullanılarak yazılırlar: + +```latte +yönetim +``` + +`redirect()` ve benzeri gibi bağlantılarla çalışan tüm metotlarda da desteklenirler. + + +Geçersiz Bağlantılar +==================== + +Geçersiz bir bağlantı oluşturmamız olabilir - ya var olmayan bir presenter'a yönlendirdiği için, ya hedef metodun imzasında kabul ettiğinden daha fazla parametre ilettiği için ya da hedef eylem için bir URL oluşturulamadığı için. Geçersiz bağlantılarla nasıl başa çıkılacağını statik değişken `Presenter::$invalidLinkMode` belirler. Bu, şu değerlerin bir kombinasyonunu alabilir (sabitler): + +- `Presenter::InvalidLinkSilent` - sessiz mod, URL olarak # karakteri döndürülür +- `Presenter::InvalidLinkWarning` - E_USER_WARNING uyarısı atılır, bu üretim modunda günlüğe kaydedilir, ancak betiğin çalışmasını kesintiye uğratmaz +- `Presenter::InvalidLinkTextual` - görsel uyarı, hatayı doğrudan bağlantıya yazar +- `Presenter::InvalidLinkException` - InvalidLinkException istisnası atılır + +Varsayılan ayar, üretim modunda `InvalidLinkWarning` ve geliştirme modunda `InvalidLinkWarning | InvalidLinkTextual` şeklindedir. Üretim ortamındaki `InvalidLinkWarning`, betiğin kesintiye uğramasına neden olmaz, ancak uyarı günlüğe kaydedilir. Geliştirme ortamında, [Tracy |tracy:] tarafından yakalanır ve bir mavi ekran görüntüler. `InvalidLinkTextual`, `#error:` karakterleriyle başlayan bir hata mesajını URL olarak döndürerek çalışır. Bu tür bağlantıların ilk bakışta belirgin olmasını sağlamak için CSS'imize ekleriz: ```css a[href^="#error:"] { @@ -242,7 +266,7 @@ a[href^="#error:"] { } ``` -Geliştirme ortamında uyarıların üretilmesini istemiyorsak, [yapılandırmada |configuration] sessiz geçersiz bağlantı modunu açabiliriz. +Geliştirme ortamında uyarıların üretilmesini istemiyorsak, sessiz modu doğrudan [yapılandırmada|configuration] ayarlayabiliriz. ```neon application: @@ -250,13 +274,13 @@ application: ``` -LinkGenerator .[#toc-linkgenerator] -=================================== +LinkGenerator +============= -`link()` comfort yöntemiyle, ancak bir sunum yapan kişi olmadan nasıl bağlantı oluşturulur? İşte bu yüzden [api:Nette\Application\LinkGenerator]. +`link()` metodunun sunduğu benzer konforla, ancak presenter olmadan bağlantılar nasıl oluşturulur? Bunun için [api:Nette\Application\LinkGenerator] vardır. -LinkGenerator, kurucudan geçirebileceğiniz ve ardından `link()` yöntemini kullanarak bağlantılar oluşturabileceğiniz bir hizmettir. +LinkGenerator, kurucu aracılığıyla size iletilmesini isteyebileceğiniz ve ardından `link()` metoduyla bağlantılar oluşturabileceğiniz bir servistir. -Sunuculara kıyasla bir fark vardır. LinkGenerator tüm bağlantıları mutlak URL'ler olarak oluşturur. Ayrıca, "geçerli sunucu" yoktur, bu nedenle yalnızca `link('default')` eyleminin adını veya [modüllere |modules] göreli yolları belirtmek mümkün değildir. +Presenter'lara kıyasla burada bir fark vardır. LinkGenerator tüm bağlantıları doğrudan mutlak URL'ler olarak oluşturur. Ayrıca, "mevcut presenter" diye bir şey yoktur, bu nedenle hedef olarak yalnızca `link('default')` eylem adını belirtmek veya modüllere göreceli yollar belirtmek mümkün değildir. -Geçersiz bağlantılar her zaman `Nette\Application\UI\InvalidLinkException` adresini atar. +Geçersiz bağlantılar her zaman `Nette\Application\UI\InvalidLinkException` atar. diff --git a/application/tr/directory-structure.texy b/application/tr/directory-structure.texy new file mode 100644 index 0000000000..16b6298815 --- /dev/null +++ b/application/tr/directory-structure.texy @@ -0,0 +1,526 @@ +Uygulama Dizin Yapısı +********************* + +
    + +Nette Framework projeleri için anlaşılır ve ölçeklenebilir bir dizin yapısı nasıl tasarlanır? Kodunuzu düzenlemenize yardımcı olacak kanıtlanmış en iyi uygulamaları göstereceğiz. Şunları öğreneceksiniz: + +- uygulamayı dizinlere **mantıksal olarak nasıl bölersiniz** +- yapıyı projenin büyümesiyle **iyi ölçeklenecek** şekilde nasıl tasarlarsınız +- **olası alternatifler** ve avantajları veya dezavantajları nelerdir + +
    + + +Nette Framework'ün kendisinin herhangi bir belirli yapıya bağlı olmadığını belirtmek önemlidir. Herhangi bir ihtiyaca ve tercihe kolayca uyarlanabilecek şekilde tasarlanmıştır. + + +Temel Proje Yapısı +================== + +Nette Framework herhangi bir sabit dizin yapısı dikte etmese de, [Web Project|https://github.com/nette/web-project] şeklinde kanıtlanmış bir varsayılan düzenleme vardır: + +/--pre +web-project/ +├── app/ ← uygulama dizini +├── assets/ ← SCSS, JS dosyaları, resimler..., alternatif olarak resources/ +├── bin/ ← komut satırı betikleri +├── config/ ← yapılandırma +├── log/ ← günlüğe kaydedilen hatalar +├── temp/ ← geçici dosyalar, önbellek +├── tests/ ← testler +├── vendor/ ← Composer tarafından kurulan kütüphaneler +└── www/ ← genel dizin (document-root) +\-- + +Bu yapıyı ihtiyaçlarınıza göre serbestçe değiştirebilirsiniz - klasörleri yeniden adlandırabilir veya taşıyabilirsiniz. Ardından, yalnızca `Bootstrap.php` dosyasındaki ve muhtemelen `composer.json` dosyasındaki dizinlere giden göreceli yolları ayarlamanız yeterlidir. Başka hiçbir şeye gerek yoktur, karmaşık yeniden yapılandırma yok, sabitlerde değişiklik yok. Nette akıllı otomatik algılamaya sahiptir ve URL tabanı da dahil olmak üzere uygulamanın konumunu otomatik olarak tanır. + + +Kod Organizasyon Prensipleri +============================ + +Yeni bir projeyi ilk kez incelerken, içinde hızla yönünüzü bulabilmelisiniz. `app/Model/` dizinini açtığınızı ve şu yapıyı gördüğünüzü hayal edin: + +/--pre +app/Model/ +├── Services/ +├── Repositories/ +└── Entities/ +\-- + +Bundan yalnızca projenin bazı servisler, depolar ve varlıklar kullandığını anlarsınız. Uygulamanın gerçek amacı hakkında hiçbir şey öğrenemezsiniz. + +Başka bir yaklaşıma bakalım - **alanlara göre organizasyon**: + +/--pre +app/Model/ +├── Cart/ +├── Payment/ +├── Order/ +└── Product/ +\-- + +Burada durum farklı - ilk bakışta bunun bir e-ticaret sitesi olduğu açık. Dizin adlarının kendisi uygulamanın neler yapabildiğini ortaya koyuyor - ödemeler, siparişler ve ürünlerle çalışıyor. + +İlk yaklaşım (sınıf türüne göre organizasyon) pratikte bir dizi sorun getirir: mantıksal olarak birbiriyle ilişkili kod farklı klasörlere dağılmıştır ve aralarında atlamanız gerekir. Bu nedenle alanlara göre organize edeceğiz. + + +İsim Alanları (Namespaces) +-------------------------- + +Dizin yapısının uygulamadaki isim alanlarıyla örtüşmesi adettendir. Bu, dosyaların fiziksel konumunun isim alanlarına karşılık geldiği anlamına gelir. Örneğin, `app/Model/Product/ProductRepository.php` içinde bulunan bir sınıfın `App\Model\Product` isim alanına sahip olması gerekir. Bu prensip, kodda gezinmeye yardımcı olur ve otomatik yüklemeyi basitleştirir. + + +Adlarda Tekil vs Çoğul Sayı +--------------------------- + +Uygulamanın ana dizinlerinde tekil sayı kullandığımıza dikkat edin: `app`, `config`, `log`, `temp`, `www`. Aynı şekilde uygulama içinde de: `Model`, `Core`, `Presentation`. Bunun nedeni, her birinin bütün bir kavramı temsil etmesidir. + +Benzer şekilde, örneğin `app/Model/Product`, ürünlerle ilgili her şeyi temsil eder. Buna `Products` demeyiz, çünkü ürünlerle dolu bir klasör değildir (orada `nokia.php`, `samsung.php` dosyaları olurdu). Ürünlerle çalışmak için sınıflar içeren bir isim alanıdır - `ProductRepository.php`, `ProductService.php`. + +`app/Tasks` klasörü çoğul sayıdadır çünkü bir dizi bağımsız yürütülebilir betik içerir - `CleanupTask.php`, `ImportTask.php`. Her biri bağımsız bir birimdir. + +Tutarlılık için şunları kullanmanızı öneririz: +- İşlevsel bir bütünü temsil eden isim alanı için tekil sayı (birden fazla varlıkla çalışsa bile) +- Bağımsız birimlerin koleksiyonları için çoğul sayı +- Emin olmadığınızda veya bunun hakkında düşünmek istemiyorsanız, tekil sayıyı seçin + + +Genel Dizin `www/` +================== + +Bu dizin, web'den erişilebilen tek dizindir (document-root olarak da bilinir). `www/` yerine `public/` adıyla da sıkça karşılaşabilirsiniz - bu sadece bir gelenek meselesidir ve uygulamanın işlevselliği üzerinde hiçbir etkisi yoktur. Dizin şunları içerir: +- Uygulamanın [Giriş noktası |bootstrapping#index.php] `index.php` +- mod_rewrite (Apache için) kuralları içeren `.htaccess` dosyası +- Statik dosyalar (CSS, JavaScript, resimler) +- Yüklenen dosyalar + +Uygulamanın doğru güvenliği için [document-root'un doğru şekilde yapılandırılması |nette:troubleshooting#URL den www Dizini Nasıl Değiştirilir veya Kaldırılır] esastır. + +.[note] +`node_modules/` klasörünü asla bu dizine yerleştirmeyin - yürütülebilir olabilecek ve genel olarak erişilebilir olmaması gereken binlerce dosya içerir. + + +Uygulama Dizini `app/` +====================== + +Bu, uygulama kodunu içeren ana dizindir. Temel yapı: + +/--pre +app/ +├── Core/ ← altyapısal konular +├── Model/ ← iş mantığı +├── Presentation/ ← presenter'lar ve şablonlar +├── Tasks/ ← komut betikleri +└── Bootstrap.php ← uygulamanın başlatma sınıfı +\-- + +`Bootstrap.php`, ortamı başlatan, yapılandırmayı yükleyen ve DI konteynerini oluşturan [uygulamanın başlangıç sınıfıdır|bootstrapping]. + +Şimdi bireysel alt dizinlere daha ayrıntılı bakalım. + + +Presenter'lar ve Şablonlar +========================== + +Uygulamanın sunum kısmı `app/Presentation` dizinindedir. Alternatif olarak kısa `app/UI` da kullanılabilir. Bu, tüm presenter'lar, şablonları ve olası yardımcı sınıflar için yerdir. + +Bu katmanı alanlara göre organize ederiz. E-ticaret, blog ve API'yi birleştiren karmaşık bir projede yapı şöyle görünürdü: + +/--pre +app/Presentation/ +├── Shop/ ← e-ticaret ön yüzü +│ ├── Product/ +│ ├── Cart/ +│ └── Order/ +├── Blog/ ← blog +│ ├── Home/ +│ └── Post/ +├── Admin/ ← yönetim +│ ├── Dashboard/ +│ └── Products/ +└── Api/ ← API uç noktaları + └── V1/ +\-- + +Buna karşılık, basit bir blog için şu bölümlemeyi kullanırdık: + +/--pre +app/Presentation/ +├── Front/ ← web sitesi ön yüzü +│ ├── Home/ +│ └── Post/ +├── Admin/ ← yönetim +│ ├── Dashboard/ +│ └── Posts/ +├── Error/ +└── Export/ ← RSS, site haritaları vb. +\-- + +`Home/` veya `Dashboard/` gibi klasörler presenter'ları ve şablonları içerir. `Front/`, `Admin/` veya `Api/` gibi klasörlere **modüller** diyoruz. Teknik olarak bunlar, uygulamayı mantıksal olarak bölmek için kullanılan normal dizinlerdir. + +Presenter içeren her klasör, aynı adı taşıyan bir presenter ve şablonlarını içerir. Örneğin, `Dashboard/` klasörü şunları içerir: + +/--pre +Dashboard/ +├── DashboardPresenter.php ← presenter +└── default.latte ← şablon +\-- + +Bu dizin yapısı, sınıfların isim alanlarına yansır. Örneğin, `DashboardPresenter`, `App\Presentation\Admin\Dashboard` isim alanında bulunur ([#Presenter Eşlemesi] bölümüne bakın): + +```php +namespace App\Presentation\Admin\Dashboard; + +class DashboardPresenter extends Nette\Application\UI\Presenter +{ + // ... +} +``` + +Uygulamada `Admin` modülü içindeki `Dashboard` presenter'ına iki nokta üst üste gösterimiyle `Admin:Dashboard` olarak başvururuz. `default` eylemine ise `Admin:Dashboard:default` olarak başvururuz. İç içe geçmiş modüller durumunda, daha fazla iki nokta üst üste kullanırız, örneğin `Shop:Order:Detail:default`. + + +Esnek Yapı Geliştirme +--------------------- + +Bu yapının büyük avantajlarından biri, projenin artan ihtiyaçlarına ne kadar zarif bir şekilde uyum sağladığıdır. Örnek olarak, XML beslemeleri oluşturan bölümü ele alalım. Başlangıçta basit bir formumuz var: + +/--pre +Export/ +├── ExportPresenter.php ← tüm dışa aktarımlar için tek bir presenter +├── sitemap.latte ← site haritası için şablon +└── feed.latte ← RSS beslemesi için şablon +\-- + +Zamanla başka besleme türleri eklenir ve onlar için daha fazla mantığa ihtiyacımız olur... Sorun değil! `Export/` klasörü basitçe bir modül haline gelir: + +/--pre +Export/ +├── Sitemap/ +│ ├── SitemapPresenter.php +│ └── sitemap.latte +└── Feed/ + ├── FeedPresenter.php + ├── zbozi.latte ← Zboží.cz için besleme + └── heureka.latte ← Heureka.cz için besleme +\-- + +Bu dönüşüm tamamen sorunsuzdur - sadece yeni alt klasörler oluşturmanız, kodu bunlara bölmeniz ve bağlantıları güncellemeniz yeterlidir (örneğin, `Export:feed` yerine `Export:Feed:zbozi`). Bu sayede yapıyı ihtiyaçlara göre kademeli olarak genişletebiliriz, iç içe geçme seviyesi sınırlı değildir. + +Örneğin, yönetimde sipariş yönetimiyle ilgili `OrderDetail`, `OrderEdit`, `OrderDispatch` vb. gibi birçok presenter'ınız varsa, daha iyi organizasyon için bu noktada (klasörler için) `Detail`, `Edit`, `Dispatch` ve diğer presenter'ları içerecek bir `Order` modülü (klasörü) oluşturabilirsiniz. + + +Şablonların Konumu +------------------ + +Önceki örneklerde, şablonların doğrudan presenter içeren klasörde bulunduğunu gördük: + +/--pre +Dashboard/ +├── DashboardPresenter.php ← presenter +├── DashboardTemplate.php ← şablon için isteğe bağlı sınıf +└── default.latte ← şablon +\-- + +Bu konum pratikte en uygun olanıdır - tüm ilgili dosyalarınız hemen elinizin altındadır. + +Alternatif olarak, şablonları `templates/` alt klasörüne yerleştirebilirsiniz. Nette her iki seçeneği de destekler. Hatta şablonları tamamen `Presentation/` klasörünün dışına bile yerleştirebilirsiniz. Şablonların yerleştirilme seçenekleri hakkında her şeyi [Şablonları Bulma |templates#Şablonları Bulma] bölümünde bulabilirsiniz. + + +Yardımcı Sınıflar ve Bileşenler +------------------------------- + +Presenter'lara ve şablonlara genellikle başka yardımcı dosyalar da eşlik eder. Bunları etki alanlarına göre mantıksal olarak yerleştiririz: + +1. **Doğrudan presenter yanında**, belirli bir presenter için özel bileşenler durumunda: + +/--pre +Product/ +├── ProductPresenter.php +├── ProductGrid.php ← ürün listeleme için bileşen +└── FilterForm.php ← filtreleme için form +\-- + +2. **Modül için** - alfabenin hemen başında düzgün bir şekilde yerleştirilecek olan `Accessory` klasörünü kullanmanızı öneririz: + +/--pre +Front/ +├── Accessory/ +│ ├── NavbarControl.php ← ön yüz için bileşenler +│ └── TemplateFilters.php +├── Product/ +└── Cart/ +\-- + +3. **Tüm uygulama için** - `Presentation/Accessory/` içinde: +/--pre +app/Presentation/ +├── Accessory/ +│ ├── LatteExtension.php +│ └── TemplateFilters.php +├── Front/ +└── Admin/ +\-- + +Veya `LatteExtension.php` veya `TemplateFilters.php` gibi yardımcı sınıfları altyapısal `app/Core/Latte/` klasörüne yerleştirebilirsiniz. Ve bileşenleri `app/Components` içine. Seçim, ekibin alışkanlıklarına bağlıdır. + + +Model - Uygulamanın Kalbi +========================= + +Model, uygulamanın tüm iş mantığını içerir. Organizasyonu için yine kural geçerlidir - alanlara göre yapılandırırız: + +/--pre +app/Model/ +├── Payment/ ← ödemelerle ilgili her şey +│ ├── PaymentFacade.php ← ana giriş noktası +│ ├── PaymentRepository.php +│ ├── Payment.php ← varlık +├── Order/ ← siparişlerle ilgili her şey +│ ├── OrderFacade.php +│ ├── OrderRepository.php +│ ├── Order.php +└── Shipping/ ← kargoyla ilgili her şey +\-- + +Modelde tipik olarak şu tür sınıflarla karşılaşırsınız: + +**Fasadlar (Facades)**: Uygulamadaki belirli bir alana ana giriş noktasını temsil ederler. Tam kullanım senaryolarını (use-cases) uygulamak için farklı servisler arasındaki işbirliğini koordine eden bir orkestratör görevi görürler (örneğin "sipariş oluştur" veya "ödemeyi işle"). Orkestrasyon katmanının altında, fasad uygulama ayrıntılarını uygulamanın geri kalanından gizler, böylece söz konusu alanla çalışmak için temiz bir arayüz sağlar. + +```php +class OrderFacade +{ + public function createOrder(Cart $cart): Order + { + // doğrulama + // sipariş oluşturma + // e-posta gönderme + // istatistiklere yazma + } +} +``` + +**Servisler**: Alan içindeki belirli bir iş operasyonuna odaklanırlar. Tüm kullanım senaryolarını düzenleyen bir fasadın aksine, bir servis belirli bir iş mantığını uygular (fiyat hesaplamaları veya ödeme işlemleri gibi). Servisler tipik olarak durumsuzdur ve daha karmaşık operasyonlar için yapı taşları olarak fasadlar tarafından veya daha basit görevler için doğrudan uygulamanın diğer bölümleri tarafından kullanılabilirler. + +```php +class PricingService +{ + public function calculateTotal(Order $order): Money + { + // fiyat hesaplama + } +} +``` + +**Depolar (Repositories)**: Veri deposuyla, tipik olarak veritabanıyla tüm iletişimi sağlarlar. Görevi, varlıkları yüklemek ve kaydetmek ve bunları aramak için metotlar uygulamaktır. Depo, uygulamanın geri kalanını veritabanının uygulama ayrıntılarından soyutlar ve verilerle çalışmak için nesne yönelimli bir arayüz sağlar. + +```php +class OrderRepository +{ + public function find(int $id): ?Order + { + } + + public function findByCustomer(int $customerId): array + { + } +} +``` + +**Varlıklar (Entities)**: Uygulamadaki ana iş kavramlarını temsil eden, kendi kimlikleri olan ve zamanla değişen nesnelerdir. Tipik olarak bunlar, ORM (Nette Database Explorer veya Doctrine gibi) kullanılarak veritabanı tablolarına eşlenen sınıflardır. Varlıklar, verileriyle ilgili iş kurallarını ve doğrulama mantığını içerebilir. + +```php +// orders veritabanı tablosuna eşlenen varlık +class Order extends Nette\Database\Table\ActiveRow +{ + public function addItem(Product $product, int $quantity): void + { + $this->related('order_items')->insert([ + 'product_id' => $product->id, + 'quantity' => $quantity, + 'unit_price' => $product->price, + ]); + } +} +``` + +**Değer Nesneleri (Value Objects)**: Kendi kimlikleri olmayan değerleri temsil eden değişmez nesnelerdir - örneğin bir para tutarı veya bir e-posta adresi. Aynı değerlere sahip iki değer nesnesi örneği özdeş kabul edilir. + + +Altyapısal Kod +============== + +`Core/` (veya `Infrastructure/`) klasörü, uygulamanın teknik temelinin evidir. Altyapısal kod tipik olarak şunları içerir: + +/--pre +app/Core/ +├── Router/ ← yönlendirme ve URL yönetimi +│ └── RouterFactory.php +├── Security/ ← kimlik doğrulama ve yetkilendirme +│ ├── Authenticator.php +│ └── Authorizator.php +├── Logging/ ← günlükleme ve izleme +│ ├── SentryLogger.php +│ └── FileLogger.php +├── Cache/ ← önbellekleme katmanı +│ └── FullPageCache.php +└── Integration/ ← harici servislerle entegrasyon + ├── Slack/ + └── Stripe/ +\-- + +Daha küçük projelerde, elbette düz bir bölümleme yeterlidir: + +/--pre +Core/ +├── RouterFactory.php +├── Authenticator.php +└── QueueMailer.php +\-- + +Bu, şu kodu ifade eder: + +- Teknik altyapıyı çözer (yönlendirme, günlükleme, önbellekleme) +- Harici servisleri entegre eder (Sentry, Elasticsearch, Redis) +- Tüm uygulama için temel servisleri sağlar (posta, veritabanı) +- Çoğunlukla belirli bir alandan bağımsızdır - önbellek veya günlükleyici e-ticaret veya blog için aynı şekilde çalışır. + +Belirli bir sınıfın buraya mı yoksa modele mi ait olduğundan emin değil misiniz? Anahtar fark, `Core/` içindeki kodun: + +- Alan hakkında hiçbir şey bilmemesi (ürünler, siparişler, makaleler) +- Çoğunlukla başka bir projeye taşınabilmesi +- "Nasıl çalıştığını" (bir e-posta nasıl gönderilir) çözmesi, "ne yaptığını" (hangi e-postanın gönderileceği) değil + +Daha iyi anlamak için bir örnek: + +- `App\Core\MailerFactory` - e-posta göndermek için sınıf örnekleri oluşturur, SMTP ayarlarını çözer +- `App\Model\OrderMailer` - siparişlerle ilgili e-postaları göndermek için `MailerFactory` kullanır, şablonlarını bilir ve ne zaman gönderilmeleri gerektiğini bilir + + +Komut Betikleri +=============== + +Uygulamaların genellikle normal HTTP istekleri dışında etkinlikler gerçekleştirmesi gerekir - ister arka planda veri işleme, ister bakım, ister periyodik görevler olsun. Çalıştırma için `bin/` dizinindeki basit betikler kullanılır, uygulama mantığının kendisi ise `app/Tasks/` (veya `app/Commands/`) içine yerleştirilir. + +Örnek: + +/--pre +app/Tasks/ +├── Maintenance/ ← bakım betikleri +│ ├── CleanupCommand.php ← eski verileri silme +│ └── DbOptimizeCommand.php ← veritabanı optimizasyonu +├── Integration/ ← harici sistemlerle entegrasyon +│ ├── ImportProducts.php ← tedarikçi sisteminden içe aktarma +│ └── SyncOrders.php ← sipariş senkronizasyonu +└── Scheduled/ ← düzenli görevler + ├── NewsletterCommand.php ← bülten gönderme + └── ReminderCommand.php ← müşteri bildirimleri +\-- + +Modele ne aittir ve komut betiklerine ne aittir? Örneğin, tek bir e-posta gönderme mantığı modelin bir parçasıdır, binlerce e-postanın toplu gönderimi zaten `Tasks/` içine aittir. + +Görevler genellikle [komut satırından |https://blog.nette.org/en/cli-scripts-in-nette-application] veya cron aracılığıyla çalıştırılır. HTTP isteği aracılığıyla da çalıştırılabilirler, ancak güvenliği göz önünde bulundurmak gerekir. Görevi başlatan presenter'ın güvenliğini sağlamak gerekir, örneğin yalnızca oturum açmış kullanıcılar için veya güçlü bir belirteç ve izin verilen IP adreslerinden erişimle. Uzun görevler için betik zaman aşımını artırmak ve oturumun kilitlenmemesi için `session_write_close()` kullanmak gerekir. + + +Diğer Olası Dizinler +==================== + +Bahsedilen temel dizinlere ek olarak, proje ihtiyaçlarına göre başka özel klasörler de ekleyebilirsiniz. En yaygın olanlarına ve kullanımlarına bakalım: + +/--pre +app/ +├── Api/ ← sunum katmanından bağımsız API mantığı +├── Database/ ← test verileri için geçiş betikleri ve tohumlayıcılar +├── Components/ ← tüm uygulama genelinde paylaşılan görsel bileşenler +├── Event/ ← olay odaklı mimari kullanıyorsanız yararlıdır +├── Mail/ ← e-posta şablonları ve ilgili mantık +└── Utils/ ← yardımcı sınıflar +\-- + +Uygulama genelinde presenter'larda kullanılan paylaşılan görsel bileşenler için `app/Components` veya `app/Controls` klasörünü kullanabilirsiniz: + +/--pre +app/Components/ +├── Form/ ← paylaşılan form bileşenleri +│ ├── SignInForm.php +│ └── UserForm.php +├── Grid/ ← veri listeleri için bileşenler +│ └── DataGrid.php +└── Navigation/ ← gezinme öğeleri + ├── Breadcrumbs.php + └── Menu.php +\-- + +Buraya daha karmaşık mantığa sahip bileşenler aittir. Bileşenleri birden fazla proje arasında paylaşmak istiyorsanız, bunları ayrı bir composer paketine ayırmak uygundur. + +E-posta iletişiminin yönetimini `app/Mail` dizinine yerleştirebilirsiniz: + +/--pre +app/Mail/ +├── templates/ ← e-posta şablonları +│ ├── order-confirmation.latte +│ └── welcome.latte +└── OrderMailer.php +\-- + + +Presenter Eşlemesi +================== + +Eşleme, presenter adından sınıf adını türetme kurallarını tanımlar. Bunları [yapılandırmada|configuration] `application › mapping` anahtarı altında belirtiriz. + +Bu sayfada, presenter'ları `app/Presentation` (veya `app/UI`) klasörüne yerleştirdiğimizi gösterdik. Bu geleneği Nette'ye yapılandırma dosyasında bildirmeliyiz. Tek bir satır yeterlidir: + +```neon +application: + mapping: App\Presentation\*\**Presenter +``` + +Eşleme nasıl çalışır? Daha iyi anlamak için önce modülsüz bir uygulama hayal edelim. Presenter sınıflarının `App\Presentation` isim alanına düşmesini istiyoruz, böylece `Home` presenter'ı `App\Presentation\HomePresenter` sınıfına eşlenir. Bunu şu yapılandırmayla başarırız: + +```neon +application: + mapping: App\Presentation\*Presenter +``` + +Eşleme, `Home` presenter adının `App\Presentation\*Presenter` maskesindeki yıldız işaretini değiştirmesiyle çalışır, böylece sonuçta `App\Presentation\HomePresenter` sınıf adını elde ederiz. Basit! + +Ancak bu ve diğer bölümlerdeki örneklerde gördüğünüz gibi, presenter sınıflarını aynı adlı alt dizinlere yerleştiririz, örneğin `Home` presenter'ı `App\Presentation\Home\HomePresenter` sınıfına eşlenir. Bunu iki nokta üst üste işaretini iki katına çıkararak başarırız (Nette Application 3.2 gerektirir): + +```neon +application: + mapping: App\Presentation\**Presenter +``` + +Şimdi presenter'ları modüllere eşlemeye geçelim. Her modül için belirli bir eşleme tanımlayabiliriz: + +```neon +application: + mapping: + Front: App\Presentation\Front\**Presenter + Admin: App\Presentation\Admin\**Presenter + Api: App\Api\*Presenter +``` + +Bu yapılandırmaya göre, `Front:Home` presenter'ı `App\Presentation\Front\Home\HomePresenter` sınıfına eşlenirken, `Api:OAuth` presenter'ı `App\Api\OAuthPresenter` sınıfına eşlenir. + +`Front` ve `Admin` modülleri benzer bir eşleme yöntemine sahip olduğundan ve muhtemelen bu türden daha fazla modül olacağından, bunları değiştirecek genel bir kural oluşturmak mümkündür. Sınıf maskesine modül için yeni bir yıldız işareti eklenir: + +```neon +application: + mapping: + *: App\Presentation\*\**Presenter + Api: App\Api\*Presenter +``` + +Bu, örneğin `Admin:User:Edit` presenter'ı gibi daha derinlemesine iç içe geçmiş dizin yapıları için de çalışır, yıldız işaretli segment her seviye için tekrarlanır ve sonuç `App\Presentation\Admin\User\Edit\EditPresenter` sınıfıdır. + +Alternatif bir gösterim, bir dize yerine üç segmentten oluşan bir dizi kullanmaktır. Bu gösterim öncekiyle eşdeğerdir: + +```neon +application: + mapping: + *: [App\Presentation, *, **Presenter] + Api: [App\Api, '', *Presenter] +``` diff --git a/application/tr/how-it-works.texy b/application/tr/how-it-works.texy index 35bbb16a50..6b64f1a29a 100644 --- a/application/tr/how-it-works.texy +++ b/application/tr/how-it-works.texy @@ -3,99 +3,101 @@ Uygulamalar Nasıl Çalışır?
    -Şu anda Nette dokümantasyonunun temel belgesini okumaktasınız. Web uygulamalarının tüm prensiplerini öğreneceksiniz. A'dan Z'ye, doğum anından PHP betiğinin son nefesine kadar güzel. Okuduktan sonra bileceksiniz: +Şu anda Nette dokümantasyonunun temel sayfasını okuyorsunuz. Web uygulamalarının çalışma prensibini öğreneceksiniz. A'dan Z'ye, başlangıç anından PHP betiğinin son nefesine kadar. Okuduktan sonra şunları bileceksiniz: -- her şey nasil i̇şli̇yor -- Bootstrap, Presenter ve DI konteyner nedir -- dizin yapısının neye benzediği +- her şey nasıl çalışır +- Bootstrap, Presenter ve DI konteynerinin ne olduğu +- dizin yapısının nasıl göründüğü
    -Dizin Yapısı .[#toc-directory-structure] -======================================== +Dizin Yapısı +============ -[WebProject |https://github.com/nette/web-project] adlı bir web uygulamasının iskelet örneğini açın ve hakkında yazılan dosyaları izleyebilirsiniz. +[WebProject|https://github.com/nette/web-project] adlı web uygulaması iskelet örneğini açın ve okurken bahsedilen dosyalara bakabilirsiniz. -Dizin yapısı şuna benzer: +Dizin yapısı aşağı yukarı şöyle görünür: /--pre web-project/ -├── app/ ← directory with application -│ ├── Presenters/ ← presenter classes -│ │ ├── HomePresenter.php ← Home presenter class -│ │ └── templates/ ← templates directory -│ │ ├── @layout.latte ← template of shared layout -│ │ └── Home/ ← templates for Home presenter -│ │ └── default.latte ← template for action `default` -│ ├── Router/ ← configuration of URL addresses -│ └── Bootstrap.php ← booting class Bootstrap -├── bin/ ← scripts for the command line -├── config/ ← configuration files +├── app/ ← uygulama dizini +│ ├── Core/ ← çalışması için gerekli temel sınıflar +│ │ └── RouterFactory.php ← URL adreslerinin yapılandırılması +│ ├── Presentation/ ← presenter'lar, şablonlar ve ilgili dosyalar +│ │ ├── @layout.latte ← düzen şablonu +│ │ └── Home/ ← Home presenter dizini +│ │ ├── HomePresenter.php ← Home presenter sınıfı +│ │ └── default.latte ← default eyleminin şablonu +│ └── Bootstrap.php ← Bootstrap başlatma sınıfı +├── assets/ ← kaynaklar (SCSS, TypeScript, kaynak görüntüler) +├── bin/ ← komut satırından çalıştırılan betikler +├── config/ ← yapılandırma dosyaları │ ├── common.neon -│ └── local.neon -├── log/ ← error logs -├── temp/ ← temporary files, cache, … -├── vendor/ ← libraries installed by Composer +│ └── services.neon +├── log/ ← günlüğe kaydedilen hatalar +├── temp/ ← geçici dosyalar, önbellek, … +├── vendor/ ← Composer tarafından kurulan kütüphaneler │ ├── ... -│ └── autoload.php ← autoloading of libs installed by Composer -├── www/ ← public directory, document root of project -│ ├── .htaccess ← mod_rewrite rules etc -│ └── index.php ← initial file that launches the application -└── .htaccess ← prohibits access to all directories except www +│ └── autoload.php ← kurulan tüm paketlerin otomatik yüklenmesi +├── www/ ← genel dizin veya projenin document-root'u +│ ├── assets/ ← derlenmiş statik dosyalar (CSS, JS, resimler, ...) +│ ├── .htaccess ← mod_rewrite kuralları +│ └── index.php ← uygulamanın başlatıldığı ilk dosya +└── .htaccess ← www dışındaki tüm dizinlere erişimi yasaklar \-- -Dizin yapısını herhangi bir şekilde değiştirebilir, klasörleri yeniden adlandırabilir veya taşıyabilir ve ardından `Bootstrap.php` dosyasındaki `log/` ve `temp/` yollarını ve `autoload` bölümündeki `composer.json` içindeki bu dosyanın yolunu düzenleyebilirsiniz. Başka bir şey yok, karmaşık yeniden yapılandırma yok, sürekli değişiklik yok. Nette [akıllı |bootstrap#development-vs-production-mode] bir [otomatik algılamaya |bootstrap#development-vs-production-mode] sahiptir. +Dizin yapısını istediğiniz gibi değiştirebilir, klasörleri yeniden adlandırabilir veya taşıyabilirsiniz, tamamen esnektir. Nette ayrıca akıllı otomatik algılamaya sahiptir ve URL tabanı da dahil olmak üzere uygulamanın konumunu otomatik olarak tanır. -Biraz daha büyük uygulamalar için, sunum ve şablon içeren klasörleri alt dizinlere (diskte) ve [modül |modules] olarak adlandırdığımız ad alanlarına (kodda) bölebiliriz. +Biraz daha büyük uygulamalarda, presenter ve şablon klasörlerini [alt dizinlere ayırabilir |directory-structure#Presenter lar ve Şablonlar] ve sınıfları modül dediğimiz isim alanlarına bölebiliriz. -`www/` dizini, projenin genel dizini veya belge köküdür. Uygulama tarafında başka bir şey ayarlamanıza gerek kalmadan yeniden adlandırabilirsiniz. Sadece [barındırmayı |nette:troubleshooting#How to change or remove www directory from URL], belge kökünün bu dizine gideceği şekilde [yapılandırmanız |nette:troubleshooting#How to change or remove www directory from URL] gerekir. +`www/` dizini, projenin sözde genel dizinini veya document-root'unu temsil eder. Uygulama tarafında başka bir şey ayarlamaya gerek kalmadan yeniden adlandırabilirsiniz. Yalnızca [hosting'i yapılandırmak |nette:troubleshooting#URL den www Dizini Nasıl Değiştirilir veya Kaldırılır] gerekir, böylece document-root bu dizine işaret eder. -[Composer'ı |best-practices:composer] kullanarak Nette dahil olmak üzere WebProject'i doğrudan da indirebilirsiniz: +WebProject'i Nette dahil olmak üzere doğrudan [Composer|best-practices:composer] kullanarak da indirebilirsiniz: ```shell composer create-project nette/web-project ``` -Linux veya macOS üzerinde, `log/` ve `temp/` dizinleri için [yazma izinlerini |nette:troubleshooting#Setting directory permissions] ayarlayın. +Linux veya macOS'ta, `log/` ve `temp/` dizinlerine [yazma izinlerini |nette:troubleshooting#Dizin İzinlerini Ayarlama] ayarlayın. -WebProject uygulaması çalışmaya hazırdır, başka bir şey yapılandırmaya gerek yoktur ve `www/` klasörüne erişerek doğrudan tarayıcıda görüntüleyebilirsiniz. +WebProject uygulaması çalışmaya hazırdır, hiçbir şey yapılandırmaya gerek yoktur ve `www/` klasörüne erişerek doğrudan tarayıcıda görüntüleyebilirsiniz. -HTTP İsteği .[#toc-http-request] -================================ +HTTP İsteği +=========== -Her şey bir kullanıcının sayfayı tarayıcıda açması ve tarayıcının sunucuya bir HTTP isteği göndermesiyle başlar. İstek, `index.php` olan `www/` genel dizininde bulunan bir PHP dosyasına gider. Bunun `https://example.com/product/123` dosyasına eşlenir ve çalıştırılır. +Her şey, kullanıcının tarayıcıda bir sayfa açmasıyla başlar. Yani tarayıcı bir HTTP isteği ile sunucuya dokunduğunda. İstek, genel `www/` dizininde bulunan tek bir PHP dosyasına, yani `index.php`'ye yönlendirilir. Diyelim ki istek `https://example.com/product/123` adresine yapıldı. Uygun [sunucu yapılandırması |nette:troubleshooting#Sunucu Kullanıcı Dostu URL ler İçin Nasıl Ayarlanır] sayesinde, bu URL bile `index.php` dosyasına eşlenir ve yürütülür. -Görevi: +Görevi şudur: -1) ortamı başlatın -2) fabrikayı alın -3) talebi işleyen Nette uygulamasını başlatın +1) ortamı başlatmak +2) fabrikayı almak +3) isteği işleyecek Nette uygulamasını başlatmak -Ne tür bir fabrika? Traktör değil, web sitesi üretiyoruz! Bekleyin, hemen açıklayacağım. +Hangi fabrika? Traktör üretmiyoruz, web sayfaları üretiyoruz! Sabırlı olun, hemen açıklanacak. -"Ortamı başlatmak" derken, örneğin, hataları günlüğe kaydetmek veya görselleştirmek için harika bir araç olan [Tracy |tracy:] 'nin etkinleştirilmesini kastediyoruz. Üretim sunucusundaki hataları günlüğe kaydeder ve bunları doğrudan geliştirme sunucusunda görüntüler. Bu nedenle, başlatmanın sitenin üretim modunda mı yoksa geliştirici modunda mı çalıştığına da karar vermesi gerekir. Bunu yapmak için Nette otomatik algılama kullanır: siteyi localhost üzerinde çalıştırırsanız, geliştirici modunda çalışır. Hiçbir şey yapılandırmanız gerekmez ve uygulama hem geliştirme hem de üretim dağıtımı için hazırdır. Bu adımlar [Bootstrap sınıfı |bootstrap] ile ilgili bölümde ayrıntılı olarak açıklanmıştır. +"Ortamı başlatmak" ifadesiyle, örneğin hataları günlüğe kaydetmek veya görselleştirmek için harika bir araç olan [Tracy|tracy:]'nin etkinleştirilmesini kastediyoruz. Üretim sunucusunda hataları günlüğe kaydeder, geliştirme sunucusunda doğrudan görüntüler. Dolayısıyla başlatma, web sitesinin üretim veya geliştirme modunda çalışıp çalışmadığına karar vermeyi de içerir. Bunun için Nette [akıllı otomatik algılama |bootstrapping#Geliştirme vs Üretim Modu] kullanır: web sitesini localhost'ta çalıştırırsanız, geliştirme modunda çalışır. Bu nedenle hiçbir şey yapılandırmanıza gerek yoktur ve uygulama hem geliştirme hem de canlı dağıtım için hemen hazırdır. Bu adımlar [Bootstrap sınıfı|bootstrapping] hakkındaki bölümde gerçekleştirilir ve ayrıntılı olarak açıklanır. -Üçüncü nokta (evet, ikinciyi atladık, ancak ona geri döneceğiz) uygulamayı başlatmaktır. Nette HTTP isteklerinin işlenmesi `Nette\Application\Application` sınıfı (bundan sonra `Application` olarak anılacaktır) tarafından yapılır, bu nedenle "bir uygulamayı çalıştır" dediğimizde, bu sınıfın bir nesnesi üzerinde `run()` adlı bir yöntemi çağırmayı kastediyoruz. +Üçüncü nokta (evet, ikincisini atladık, ama ona geri döneceğiz) uygulamayı başlatmaktır. Nette'de HTTP isteklerini işlemekten `Nette\Application\Application` sınıfı (bundan sonra `Application` olarak anılacaktır) sorumludur, bu yüzden uygulamayı başlatmak dediğimizde, özellikle bu sınıfın nesnesinde anlamlı bir ada sahip `run()` metodunu çağırmayı kastediyoruz. -Nette, kanıtlanmış metodolojilerle temiz uygulamalar yazmanız için size rehberlik eden bir akıl hocasıdır. Ve en kanıtlanmış olanı **bağımlılık enjeksiyonu**, kısaltılmış DI olarak adlandırılır. Şu anda sizi DI'yi açıklamakla meşgul etmek istemiyoruz, çünkü [ayrı bir bölüm |dependency-injection:introduction] var, burada önemli olan şey, anahtar nesnelerin genellikle **DI container** (kısaltılmış DIC) adı verilen nesneler için bir fabrika tarafından oluşturulacağıdır. Evet, bu bir süre önce bahsedilen fabrikadır. Ayrıca bizim için `Application` nesnesini de oluşturur, bu yüzden önce bir konteynere ihtiyacımız var. Bunu `Configurator` sınıfını kullanarak elde ediyoruz ve `Application` nesnesini üretmesine izin veriyoruz, `run()` yöntemini çağırıyoruz ve bu Nette uygulamasını başlatıyor. Bu tam olarak [index.php |bootstrap#index.php] dosyasında olan şeydir. +Nette, sizi kanıtlanmış metodolojilere göre temiz uygulamalar yazmaya yönlendiren bir akıl hocasıdır. Ve bu kesinlikle en kanıtlanmış olanlardan biri **dependency injection** (bağımlılık enjeksiyonu), kısaca DI olarak adlandırılır. Şu anda sizi DI'yi açıklamakla yormak istemiyoruz, bunun için [ayrı bir bölüm|dependency-injection:introduction] var, önemli olan sonuç, temel nesnelerin genellikle **DI konteyner** (kısaca DIC) olarak adlandırılan bir nesne fabrikası tarafından oluşturulacağıdır. Evet, bu az önce bahsedilen fabrika. Ve bize `Application` nesnesini de üretecek, bu yüzden önce konteynere ihtiyacımız var. Onu `Configurator` sınıfını kullanarak alırız ve `Application` nesnesini üretmesine izin veririz, üzerinde `run()` metodunu çağırırız ve böylece Nette uygulaması başlar. Tam olarak bu, [index.php |bootstrapping#index.php] dosyasında olur. -Nette Uygulama .[#toc-nette-application] -======================================== +Nette Application +================= Application sınıfının tek bir görevi vardır: HTTP isteğine yanıt vermek. -Nette'de yazılan uygulamalar, belirli bir web sitesi sayfasını temsil eden sınıflar olan birçok sözde sunucuya (diğer çerçevelerde aynı olan denetleyici terimiyle karşılaşabilirsiniz) ayrılır: örneğin ana sayfa; e-mağazadaki ürün; oturum açma formu; site haritası beslemesi vb. Uygulama bir ila binlerce sunucuya sahip olabilir. +Nette'de yazılan uygulamalar, her biri web sitesinin belirli bir sayfasını temsil eden birçok sözde presenter'a (diğer framework'lerde controller terimiyle karşılaşabilirsiniz, aynı şeydir) bölünür: örn. ana sayfa; e-ticaretteki bir ürün; giriş formu; site haritası beslemesi vb. Bir uygulamanın bir ila binlerce presenter'ı olabilir. -Uygulama, yönlendirici olarak adlandırılan kişiden mevcut talebin işlenmek üzere hangi sunuculara iletileceğine karar vermesini isteyerek başlar. Yönlendirici bunun kimin sorumluluğunda olduğuna karar verir. `https://example.com/product/123` ile bir ürünü `id: 123` eylem olarak isteyen **sunucu** `Product` için bir iş olduğuna karar verir. Sunucu + eylem çiftlerini iki nokta üst üste ile ayırarak `Product:show` şeklinde yazmak iyi bir alışkanlıktır. +Application, mevcut isteği işlemek için hangi presenter'a ileteceğine karar vermesi için sözde yönlendiriciye (router) sorarak başlar. Yönlendirici, bunun kimin sorumluluğu olduğuna karar verir. Giriş URL'si `https://example.com/product/123`'e bakar ve nasıl ayarlandığına bağlı olarak, bunun örneğin `id: 123` olan ürünü görüntüleme (**eylem**) isteyeceği **presenter** `Product`'ın işi olduğuna karar verir. Presenter + eylem çiftini iki nokta üst üste ile `Product:show` olarak yazmak iyi bir alışkanlıktır. -Böylece yönlendirici URL'yi bir `Presenter:action` + parametreler çiftine dönüştürdü, bizim durumumuzda `Product:show` + `id: 123`. Bir yönlendiricinin nasıl göründüğünü `app/Router/RouterFactory.php` dosyasında görebilirsiniz ve bunu [Yönlendirme |Routing] bölümünde ayrıntılı olarak açıklayacağız. +Yani yönlendirici, URL'yi `Presenter:action` + parametreler çiftine dönüştürdü, bizim durumumuzda `Product:show` + `id: 123`. Böyle bir yönlendiricinin nasıl göründüğünü `app/Core/RouterFactory.php` dosyasında görebilirsiniz ve onu [Yönlendirme|Routing] bölümünde ayrıntılı olarak açıklıyoruz. -Devam edelim. Uygulama zaten sunucunun adını biliyor ve devam edebilir. Sunum yapan kişinin kodu olan `ProductPresenter` nesnesini oluşturarak `Product`. Daha doğrusu, DI konteynerinden sunucuyu yaratmasını ister, çünkü nesneleri üretmek onun işidir. +Devam edelim. Application artık presenter'ın adını biliyor ve devam edebilir. `ProductPresenter` sınıfının nesnesini üreterek, ki bu presenter `Product`'ın kodudur. Daha doğrusu, presenter'ı üretmesi için DI konteynerine sorar, çünkü üretmek onun işidir. -Sunucu şöyle görünebilir: +Presenter şöyle görünebilir: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -107,98 +109,92 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function renderShow(int $id): void { - // modelden veri alıyoruz ve bunu şablona aktarıyoruz + // modelden verileri alır ve şablona iletiriz $this->template->product = $this->repository->getProduct($id); } } ``` -Talep, sunum yapan kişi tarafından ele alınır. Ve görev açıktır: `id: 123` ile `show` eylemini gerçekleştirin. Sunucuların dilinde bu, `renderShow()` yönteminin çağrıldığı ve `$id` parametresinde `123` aldığı anlamına gelir. +İsteğin işlenmesini presenter devralır. Ve görev açıktır: `id: 123` ile `show` eylemini gerçekleştirin. Bu, presenter'ların dilinde, `renderShow()` metodunun çağrılacağı ve `$id` parametresinde `123` alacağı anlamına gelir. -Bir sunum yapan kişi birden fazla eylemi işleyebilir, yani birden fazla yönteme sahip olabilir `render()`. Ancak sunum yapanların bir veya mümkün olduğunca az eylemle tasarlanmasını öneriyoruz. +Presenter birden fazla eylemi işleyebilir, yani birden fazla `render()` metoduna sahip olabilir. Ancak, bir veya mümkün olduğunca az eyleme sahip presenter'lar tasarlamanızı öneririz. -Böylece, kodu kurgusal bir örnek olan `renderShow(123)` yöntemi çağrıldı, ancak verilerin şablona nasıl aktarıldığını, yani `$this->template` adresine yazarak görebilirsiniz. +Yani, `renderShow(123)` metodu çağrıldı, kodu hayali bir örnek olsa da, verilerin şablona nasıl iletildiğini, yani `$this->template`'e yazılarak görebilirsiniz. -Daha sonra, sunum yapan kişi yanıtı döndürür. Bu bir HTML sayfası, bir resim, bir XML belgesi, diskten bir dosya gönderme, JSON veya başka bir sayfaya yönlendirme olabilir. Daha da önemlisi, nasıl yanıt verileceğini açıkça belirtmezsek ( `ProductPresenter` adresinde olduğu gibi), yanıt şablonu bir HTML sayfası ile işlemek olacaktır. Neden mi? Çünkü vakaların %99'unda bir şablon çizmek isteriz, dolayısıyla sunum yapan kişi bu davranışı varsayılan olarak kabul eder ve işimizi kolaylaştırmak ister. Nette'in amacı da bu. +Ardından presenter yanıtı döndürür. Bu bir HTML sayfası, bir resim, bir XML belgesi, diskten bir dosya gönderimi, JSON veya başka bir sayfaya yönlendirme olabilir. Önemli olan, açıkça nasıl yanıt vereceğini söylemezsek (ki bu `ProductPresenter` durumudur), yanıtın bir HTML sayfasıyla şablonun oluşturulması olacağıdır. Neden? Çünkü vakaların %99'unda bir şablon oluşturmak istiyoruz, bu yüzden presenter bu davranışı varsayılan olarak kabul eder ve işimizi kolaylaştırmak ister. Nette'nin amacı budur. -Hangi şablonun çizileceğini belirtmemize bile gerek yok, basit bir mantıkla o şablona giden yolu türetiyor. Sunucu `Product` ve eylem `show` durumunda, bu şablon dosyalarından birinin `ProductPresenter` sınıfının bulunduğu dizine göre var olup olmadığını görmeye çalışır: +Hangi şablonun oluşturulacağını bile belirtmemize gerek yok, yolunu kendisi türetir. `show` eylemi durumunda, `ProductPresenter` sınıfıyla aynı dizindeki `show.latte` şablonunu basitçe yüklemeye çalışır. Ayrıca `@layout.latte` dosyasındaki düzeni bulmaya çalışır ([şablonları bulma |templates#Şablonları Bulma] hakkında daha fazla bilgi). -- `templates/Product/show.latte` -- `templates/Product.show.latte` - -Ayrıca `@layout.latte` dosyasında düzeni bulmaya çalışacak ve ardından şablonu oluşturacaktır. Artık sunucunun ve tüm uygulamanın görevi tamamlanmıştır. Şablon mevcut değilse, 404 hatalı bir sayfa döndürülecektir. Sunumcular hakkında daha fazla bilgiyi [Sunumcular |Presenters] sayfasında bulabilirsiniz. +Ve ardından şablonları oluşturur. Böylece presenter'ın ve tüm uygulamanın görevi tamamlanır ve iş biter. Şablon mevcut değilse, 404 hata sayfası döndürülür. Presenter'lar hakkında daha fazla bilgiyi [Presenter'lar|presenters] sayfasında okuyabilirsiniz. [* request-flow.svg *] -Sadece emin olmak için, tüm süreci biraz farklı bir URL ile özetlemeye çalışalım: +Emin olmak için, tüm süreci biraz farklı bir URL ile özetleyelim: -1) URL şu şekilde olacaktır `https://example.com` -2) uygulamayı önyüklüyoruz, bir konteyner oluşturuyoruz ve `Application::run()` -3) yönlendirici URL'yi bir çift olarak çözer `Home:default` -4) bir `HomePresenter` nesnesi oluşturulur -5) `renderDefault()` yöntemi çağrılır (eğer varsa) -6) `templates/@layout.latte` düzenine sahip bir `templates/Home/default.latte` şablonu oluşturulur +1) URL `https://example.com` olacak +2) uygulamayı başlatırız, konteyner oluşturulur ve `Application::run()` çalıştırılır +3) yönlendirici URL'yi `Home:default` çifti olarak kodlar +4) `HomePresenter` sınıfının nesnesi oluşturulur +5) `renderDefault()` metodu çağrılır (varsa) +6) örneğin `@layout.latte` düzeniyle `default.latte` şablonu oluşturulur -Şu anda birçok yeni kavramla karşılaşmış olabilirsiniz, ancak bunların anlamlı olduğuna inanıyoruz. Nette'de uygulama oluşturmak çocuk oyuncağı. +Şimdi birçok yeni terimle karşılaşmış olabilirsiniz, ancak anlamlı olduklarına inanıyoruz. Nette'de uygulama oluşturmak son derece keyiflidir. -Şablonlar .[#toc-templates] -=========================== +Şablonlar +========= -Şablonlar söz konusu olduğunda, Nette [Latte |latte:] şablon sistemini kullanır. Bu yüzden şablon içeren dosyalar `.latte` ile biter. Latte, PHP için en güvenli şablon sistemi ve aynı zamanda en sezgisel sistem olduğu için kullanılır. Yeni bir şey öğrenmenize gerek yok, sadece PHP ve birkaç Latte etiketi bilmeniz yeterli. Her şeyi [dokümantasyonda |latte:] bulacaksınız. +Şablonlardan bahsetmişken, Nette'de [Latte |latte:] şablonlama sistemi kullanılır. Bu nedenle şablonlarda `.latte` uzantıları bulunur. Latte kullanılır çünkü hem PHP için en güvenli şablonlama sistemidir hem de en sezgisel sistemdir. Çok fazla yeni şey öğrenmenize gerek yok, PHP bilginiz ve birkaç etiket yeterlidir. Her şeyi [belgelerde |templates] öğreneceksiniz. -Şablonda diğer sunumculara ve eylemlere aşağıdaki gibi [bir bağlantı oluşturuyoruz |creating-links]: +Şablonda, diğer presenter'lara ve eylemlere [bağlantılar oluşturulur |creating-links] şu şekilde: ```latte -product detail +ürün detayı ``` -Gerçek URL yerine tanıdık `Presenter:action` çiftini yazmanız ve parametreleri eklemeniz yeterlidir. İşin püf noktası, bu özelliğin Nette tarafından işleneceğini söyleyen `n:href` adresidir. Ve oluşturacaktır: +Sadece gerçek URL yerine bilinen `Presenter:action` çiftini yazın ve olası parametreleri belirtin. İşin püf noktası, bu niteliğin Nette tarafından işleneceğini söyleyen `n:href`'tir. Ve şunu oluşturur: ```latte -product detail +ürün detayı ``` -Daha önce bahsedilen yönlendirici URL'nin oluşturulmasından sorumludur. Aslında, Nette'deki yönlendiriciler, yalnızca bir URL'den bir sunumcu:eylem çiftine dönüşüm gerçekleştirebilmeleri açısından değil, aynı zamanda sunumcu + eylem + parametrelerin adından bir URL oluşturabilmeleri açısından da benzersizdir. -Bu sayede Nette'de, şablonda veya sunum yapan kişide tek bir karakteri bile değiştirmeden, sadece yönlendiriciyi değiştirerek tüm bitmiş uygulamadaki URL'nin şeklini tamamen değiştirebilirsiniz. -Ve bu sayede, Nette'nin bir başka benzersiz özelliği olan ve farklı URL'lerde yinelenen içeriğin varlığını otomatik olarak önleyerek SEO'yu (internette aranabilirliğin optimizasyonu) geliştiren sözde kanonizasyon çalışır. -Birçok programcı bunu şaşırtıcı buluyor. +URL oluşturmaktan daha önce bahsedilen yönlendirici sorumludur. Aslında, Nette'deki yönlendiriciler olağanüstüdür çünkü yalnızca URL'den presenter:action çiftine dönüşümler yapmakla kalmaz, aynı zamanda tersini de yapabilirler, yani presenter adı + eylem + parametrelerden bir URL oluşturabilirler. Bu sayede Nette'de, şablonda veya presenter'da tek bir karakteri bile değiştirmeden tüm hazır uygulamanın URL şekillerini tamamen değiştirebilirsiniz. Sadece yönlendiriciyi düzenleyerek. Ayrıca bu sayede, Nette'nin başka bir benzersiz özelliği olan ve farklı URL'lerde yinelenen içeriğin varlığını otomatik olarak önleyerek daha iyi SEO'ya (arama motoru optimizasyonu) katkıda bulunan sözde kanonikleştirme çalışır. Birçok programcı bunu şaşırtıcı buluyor. -İnteraktif Bileşenler .[#toc-interactive-components] -==================================================== +Interaktif Bileşenler +===================== -Sunucular hakkında size söylememiz gereken bir şey daha var: yerleşik bir bileşen sistemine sahipler. Daha yaşlılarınız Delphi veya ASP.NET Web Forms'dan benzer bir şeyi hatırlayabilir. React veya Vue.js uzaktan benzer bir şey üzerine inşa edilmiştir. PHP çerçeveleri dünyasında bu tamamen benzersiz bir özelliktir. +Presenter'lar hakkında size bir şey daha söylemeliyiz: yerleşik bir bileşen sistemleri vardır. Delphi veya ASP.NET Web Forms'tan aşina olanlar benzer bir şey hatırlayabilir, React veya Vue.js de uzaktan benzer bir şeye dayanmaktadır. PHP framework dünyasında bu tamamen benzersiz bir özelliktir. -Bileşenler, sayfalara (yani sunumlara) yerleştirdiğimiz ayrı yeniden kullanılabilir birimlerdir. Bunlar [formlar |forms:in-presenter], [datagridler |https://componette.org/contributte/datagrid/], menüler, anketler, aslında tekrar tekrar kullanılması mantıklı olan her şey olabilir. Kendi bileşenlerimizi oluşturabilir veya [çok çeşitli |https://componette.org] açık kaynak bileşenlerinden bazılarını kullanabiliriz. +Bileşenler, sayfalara (yani presenter'lara) eklediğimiz bağımsız, yeniden kullanılabilir birimlerdir. Bunlar [formlar |forms:in-presenter], [veri ızgaraları |https://componette.org/contributte/datagrid/], menüler, oylama anketleri, aslında tekrar tekrar kullanılması mantıklı olan her şey olabilir. Kendi bileşenlerimizi oluşturabilir veya [geniş yelpazedeki |https://componette.org] açık kaynaklı bileşenlerden bazılarını kullanabiliriz. -Bileşenler, uygulama geliştirme yaklaşımını temelden değiştirmektedir. Önceden tanımlanmış birimlerden sayfalar oluşturmak için yeni olanaklar sunacaklar. Ve [Hollywood |components#Hollywood style] ile ortak bir yönleri var. +Bileşenler, uygulama oluşturma yaklaşımını temelden etkiler. Size sayfaları önceden hazırlanmış birimlerden oluşturma konusunda yeni olanaklar sunarlar. Ve ayrıca [Hollywood |components#Hollywood Tarzı] ile ortak bir yanları vardır. -DI Konteyneri ve Yapılandırması .[#toc-di-container-and-configuration] -====================================================================== +DI Konteyneri ve Yapılandırma +============================= -DI konteyneri (nesneler için fabrika) tüm uygulamanın kalbidir. +DI konteyneri veya nesne fabrikası, tüm uygulamanın kalbidir. -Merak etmeyin, önceki kelimelerden anlaşılabileceği gibi sihirli bir kara kutu değil. Aslında, Nette tarafından oluşturulan ve bir önbellek dizininde saklanan oldukça sıkıcı bir PHP sınıfıdır. `createServiceAbcd()` şeklinde adlandırılan ve her biri bir nesne yaratan ve döndüren çok sayıda metodu vardır. Evet, uygulamayı çalıştırmak için `index.php` dosyasında ihtiyaç duyduğumuz `Nette\Application\Application` nesnesini üretecek bir `createServiceApplication()` yöntemi de var. Ve tek tek sunum yapan kişileri üretmek için yöntemler var. Ve böyle devam eder. +Endişelenmeyin, önceki satırlardan görünebileceği gibi sihirli bir kara kutu değildir. Aslında, Nette tarafından oluşturulan ve önbellek dizinine kaydedilen oldukça sıkıcı bir PHP sınıfıdır. `createServiceAbcd()` gibi adlandırılmış birçok metodu vardır ve her biri belirli bir nesneyi üretebilir ve döndürebilir. Evet, uygulamayı başlatmak için `index.php` dosyasında ihtiyaç duyduğumuz `Nette\Application\Application`'ı üreten `createServiceApplication()` metodu da vardır. Ve bireysel presenter'ları üreten metotlar da vardır. Ve bu böyle devam eder. -DI konteynerinin oluşturduğu nesneler bir nedenden dolayı servis olarak adlandırılır. +DI konteynerinin oluşturduğu nesnelere bir nedenden dolayı servis denir. -Bu sınıfla ilgili gerçekten özel olan şey, sizin tarafınızdan değil, çerçeve tarafından programlanmış olmasıdır. Aslında PHP kodunu üretir ve diske kaydeder. Siz sadece konteynerin hangi nesneleri tam olarak nasıl üretmesi gerektiğine dair talimatlar verirsiniz. Ve bu talimatlar [NEON formatındaki |neon:format] [yapılandırma dosyalarına |bootstrap#DI Container Configuration] yazılır ve bu nedenle `.neon` uzantısına sahiptir. +Bu sınıfın gerçekten özel olan yanı, onu sizin değil, framework'ün programlamasıdır. Gerçekten PHP kodunu oluşturur ve diske kaydeder. Siz sadece konteynerin hangi nesneleri üretebileceği ve tam olarak nasıl üreteceği konusunda talimatlar verirsiniz. Ve bu talimatlar, [NEON|neon:format] formatının kullanıldığı ve dolayısıyla `.neon` uzantısına sahip olan [yapılandırma dosyalarında|bootstrapping] yazılır. -Yapılandırma dosyaları yalnızca DI konteynerine talimat vermek için kullanılır. Örneğin, [oturum |http:configuration#Session] bölümünde `expiration: 14 days` seçeneğini belirtirsem, DI konteyneri oturumu temsil eden `Nette\Http\Session` nesnesini oluştururken `setExpiration('14 days')` yöntemini çağırır ve böylece yapılandırma gerçek olur. +Yapılandırma dosyaları yalnızca DI konteynerini bilgilendirmek için kullanılır. Yani örneğin, [session |http:configuration#Oturum Session] bölümünde `expiration: 14 days` seçeneğini belirtirsem, DI konteyneri oturumu temsil eden `Nette\Http\Session` nesnesini oluştururken onun `setExpiration('14 days')` metodunu çağırır ve böylece yapılandırma gerçeğe dönüşür. -Nelerin [yapılandırılabileceğini |nette:configuring] ve [kendi servislerinizi |dependency-injection:services] nasıl [tanımlayacağınızı |dependency-injection:services] anlatan koca bir bölüm sizin için hazır. +Nelerin [yapılandırılabileceğini |nette:configuring] ve kendi [servislerinizi nasıl tanımlayacağınızı |dependency-injection:services] açıklayan tam bir bölüm sizin için hazırlanmıştır. -Hizmetlerin oluşturulmasına girdiğinizde, [otomatik kab |dependency-injection:autowiring] lolama kelimesiyle karşılaşacaksınız. Bu, hayatınızı inanılmaz derecede kolaylaştıracak bir araçtır. Hiçbir şey yapmanıza gerek kalmadan nesneleri ihtiyaç duyduğunuz yerlere (örneğin sınıflarınızın kurucularına) otomatik olarak aktarabilir. Nette'deki DI konteynerinin küçük bir mucize olduğunu göreceksiniz. +Servis oluşturmaya biraz daldığınızda, [autowiring |dependency-injection:autowiring] kelimesiyle karşılaşırsınız. Bu, hayatınızı inanılmaz derecede basitleştiren bir özelliktir. Nesneleri ihtiyaç duyduğunuz yerlere (örneğin sınıflarınızın kurucularına) otomatik olarak iletebilir, hiçbir şey yapmanıza gerek kalmadan. Nette'deki DI konteynerinin küçük bir mucize olduğunu keşfedeceksiniz. -Sırada Ne Var? .[#toc-what-next] -================================ +Nereye Devam Edelim? +==================== -Nette uygulamaların temel prensiplerini gözden geçirdik. Şimdiye kadar çok yüzeysel olarak, ancak yakında derinliklere inecek ve sonunda harika web uygulamaları oluşturacaksınız. Nereden devam edelim? [İlk Uygulamanızı Oluşturun |quickstart:] eğitimini denediniz mi? +Nette'deki uygulamaların temel prensiplerini gözden geçirdik. Henüz çok yüzeysel, ancak yakında derinlemesine dalacak ve zamanla harika web uygulamaları oluşturacaksınız. Nereye devam etmeli? [İlk Uygulamamızı Yazıyoruz|quickstart:] eğitimini denediniz mi? -Yukarıdakilere ek olarak, Nette [yararlı sınıflardan |utils:], [veritabanı katmanından |database:] vb. oluşan bir cephaneliğe sahiptir. Kasıtlı olarak sadece belgelere tıklamayı deneyin. Veya [blogu |https://blog.nette.org] ziyaret edin. Birçok ilginç şey keşfedeceksiniz. +Yukarıda açıklananlara ek olarak, Nette [kullanışlı sınıflar|utils:], [veritabanı katmanı|database:] vb. içeren tam bir cephaneliğe sahiptir. Sadece belgeleri veya [blogu|https://blog.nette.org] gözden geçirmeyi deneyin. Birçok ilginç şey keşfedeceksiniz. -Çerçeve size bolca neşe getirsin 💙 +Framework'ün size bolca neşe getirmesini dileriz 💙 diff --git a/application/tr/modules.texy b/application/tr/modules.texy deleted file mode 100644 index e447d581bd..0000000000 --- a/application/tr/modules.texy +++ /dev/null @@ -1,148 +0,0 @@ -Modüller -******** - -.[perex] -Nette'de modüller bir uygulamayı oluşturan mantıksal birimleri temsil eder. Sunucuları, şablonları, muhtemelen bileşenleri ve model sınıflarını içerirler. - -Sunucular için bir dizin ve şablonlar için bir dizin gerçek projeler için yeterli olmayacaktır. Bir klasörde düzinelerce dosya olması en azından düzensizdir. Bundan nasıl kurtulabiliriz? Basitçe onları diskteki alt dizinlere ve koddaki ad alanlarına böleriz. Nette modüllerinin yaptığı da tam olarak budur. - -Sunucular ve şablonlar için tek bir klasör oluşturmayı unutalım ve bunun yerine örneğin `Admin` ve `Front` gibi modüller oluşturalım. - -/--pre -app/ -├── Presenters/ -├── Modules/ ← directory with modules -│ ├── Admin/ ← module Admin -│ │ ├── Presenters/ ← its presenters -│ │ │ ├── DashboardPresenter.php -│ │ │ └── templates/ -│ └── Front/ ← module Front -│ └── Presenters/ ← its presenters -│ └── ... -\-- - -Bu dizin yapısı sınıf ad alanları tarafından yansıtılacaktır, bu nedenle örneğin `DashboardPresenter` `App\Modules\Admin\Presenters` ad alanında olacaktır: - -```php -namespace App\Modules\Admin\Presenters; - -class DashboardPresenter extends Nette\Application\UI\Presenter -{ - // ... -} -``` - -`Admin` modülü içindeki `Dashboard` sunumcusuna uygulama içinde iki nokta üst üste gösterimi kullanılarak `Admin:Dashboard` şeklinde ve `default` eylemine `Admin:Dashboard:default` şeklinde referans verilir. -Peki Nette proper `Admin:Dashboard` 'un `App\Modules\Admin\Presenters\DashboardPresenter` sınıfını temsil ettiğini nasıl biliyor? Bu, yapılandırmadaki [eşleme |#mapping] ile belirlenir. -Bu nedenle, verilen yapı sabit değildir ve ihtiyaçlarınıza göre değiştirebilirsiniz. - -Modüller elbette sunum yapanların ve şablonların yanı sıra bileşenler, model sınıfları vb. gibi diğer tüm öğeleri de içerebilir. - - -İç İçe Modüller .[#toc-nested-modules] --------------------------------------- - -Modüller sadece düz bir yapı oluşturmak zorunda değildir, örneğin alt modüller de oluşturabilirsiniz: - -/--pre -app/ -├── Modules/ ← directory with modules -│ ├── Blog/ ← module Blog -│ │ ├── Admin/ ← submodule Admin -│ │ │ ├── Presenters/ -│ │ │ └── ... -│ │ └── Front/ ← submodule Front -│ │ ├── Presenters/ -│ │ └── ... -│ ├── Forum/ ← module Forum -│ │ └── ... -\-- - -Böylece, `Blog` modülü `Admin` ve `Front` alt modüllerine bölünmüştür. Yine, bu durum `App\Modules\Blog\Admin\Presenters` vb. isim alanlarına da yansıyacaktır. Alt modülün içindeki `Dashboard` sunucusu `Blog:Admin:Dashboard` olarak adlandırılır. - -İç içe geçme istediğiniz kadar derin olabilir, böylece alt alt modüller oluşturulabilir. - - -Bağlantı Oluşturma .[#toc-creating-links] ------------------------------------------ - -Sunucu şablonlarındaki bağlantılar geçerli modüle görelidir. Bu nedenle, `Foo:default` bağlantısı geçerli sunum yapan kişiyle aynı modülde bulunan `Foo` sunum yapan kişiye yönlendirir. Örneğin, geçerli modül `Front` ise, bağlantı şu şekilde olur: - -```latte -link to Front:Product:show -``` - -Bir bağlantı, bir modülün adını içerse bile görelidir ve bu durumda bir alt modül olarak kabul edilir: - -```latte -link to Front:Shop:Product:show -``` - -Mutlak bağlantılar diskteki mutlak yollara benzer şekilde yazılır, ancak eğik çizgiler yerine iki nokta üst üste konur. Böylece, mutlak bir bağlantı iki nokta üst üste ile başlar: - -```latte -link to Admin:Product:show -``` - -Belirli bir modülde mi yoksa onun alt modülünde mi olduğumuzu öğrenmek için `isModuleCurrent(moduleName)` fonksiyonunu kullanabiliriz. - -```latte -
  • - ... -
  • -``` - - -Yönlendirme .[#toc-routing] ---------------------------- - -[Yönlendirme ile ilgili bölüme |routing#Modules] bakın. - - -Haritalama .[#toc-mapping] --------------------------- - -Sınıf adının sunum yapan kişinin adından türetildiği kuralları tanımlar. Bunları [yapılandırmada |configuration] `application › mapping` anahtarının altına yazıyoruz. - -Modül kullanmayan bir örnekle başlayalım. Sadece sunum yapan sınıfların `App\Presenters` ad alanına sahip olmasını isteyeceğiz. Bu, `Home` gibi bir sunucunun `App\Presenters\HomePresenter` sınıfıyla eşleşmesi gerektiği anlamına gelir. Bu, aşağıdaki yapılandırma ile gerçekleştirilebilir: - -```neon -application: - mapping: - *: App\Presenters\*Presenter -``` - -Sunum yapan kişinin adı sınıf maskesindeki yıldız işaretiyle değiştirilir ve sonuç sınıf adı olur. Kolay! - -Sunum yapanları modüllere ayırırsak, her modül için kendi eşlememizi yapabiliriz: - -```neon -application: - mapping: - Front: App\Modules\Front\Presenters\*Presenter - Admin: App\Modules\Admin\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -Şimdi `Front:Home` sunucusu `App\Modules\Front\Presenters\HomePresenter` sınıfıyla ve `Admin:Dashboard` sunucusu `App\Modules\Admin\Presenters\DashboardPresenter` sınıfıyla eşleşir. - -İlk ikisini değiştirmek için genel bir (yıldız) kural oluşturmak daha pratiktir. Ekstra yıldız işareti sadece modül için sınıf maskesine eklenecektir: - -```neon -application: - mapping: - *: App\Modules\*\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -Peki ya iç içe modüller kullanıyorsak ve bir sunumcumuz varsa `Admin:User:Edit`? Bu durumda, her seviye için modülü temsil eden yıldız işaretli bölüm basitçe tekrarlanır ve sonuç `App\Modules\Admin\User\Presenters\EditPresenter` sınıfı olur. - -Alternatif bir gösterim, bir dize yerine üç segmentten oluşan bir dizi kullanmaktır. Bu gösterim bir öncekine eşdeğerdir: - -```neon -application: - mapping: - *: [App\Modules, *, Presenters\*Presenter] -``` - -Varsayılan değer `*: *Module\*Presenter`'dur. diff --git a/application/tr/multiplier.texy b/application/tr/multiplier.texy index 69c83c08f0..746642ce3e 100644 --- a/application/tr/multiplier.texy +++ b/application/tr/multiplier.texy @@ -1,30 +1,32 @@ -Çarpan: Dinamik Bileşenler -************************** +Multiplier: Dinamik Bileşenler +****************************** -Etkileşimli bileşenlerin dinamik olarak oluşturulmasına yönelik bir araç .[perex] +.[perex] +Etkileşimli bileşenlerin dinamik olarak oluşturulması için bir araç -Tipik bir sorunla başlayalım: bir e-ticaret sitesinde bir ürün listemiz var ve her ürüne bir *sepete ekle* formuyla eşlik etmek istiyoruz. Bir yol, tüm listeyi tek bir forma sarmaktır. Daha kullanışlı bir yol ise [api:Nette\Application\UI\Multiplier] kullanmaktır. +Tipik bir örnekten başlayalım: bir e-ticaret sitesinde ürün listemiz var ve her biri için sepete ürün eklemek için bir form yazdırmak istiyoruz. Olası seçeneklerden biri, tüm listeyi tek bir formla sarmaktır. Ancak, [api:Nette\Application\UI\Multiplier] bize çok daha uygun bir yol sunar. -Multiplier, birden fazla bileşen için bir fabrika tanımlamanıza olanak tanır. İç içe geçmiş bileşenler prensibine dayanır - [api:Nette\ComponentModel\Container] adresinden miras alınan her bileşen başka bileşenler içerebilir. +Multiplier, birden fazla bileşen için bir fabrika tanımlamayı kolaylaştırır. İç içe geçmiş bileşenler prensibine göre çalışır - [api:Nette\ComponentModel\Container]'dan miras alan her bileşen başka bileşenler içerebilir. -Belgelerdeki [bileşen modeline |components#Components in Depth] bakın. .[tip] +.[tip] +Belgelerdeki [bileşen modeli |components#Bileşenler Derinlemesine] bölümüne veya [Honza Tvrdík'in sunumuna|https://www.youtube.com/watch?v=8y3LLexWu-I] bakın. -Çarpan, yapıcıda geçirilen geri çağırmayı kullanarak çocuklarını dinamik olarak oluşturabilen bir üst bileşen olarak konumlanır. Örneğe bakın: +Multiplier'ın özü, kurucuda iletilen bir geri çağırma (callback) kullanarak yavrularını dinamik olarak oluşturabilen bir ebeveyn konumunda hareket etmesidir. Örneğe bakın: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function () { $form = new Nette\Application\UI\Form; - $form->addInteger('amount', 'Amount:') + $form->addInteger('count', 'Ürün sayısı:') ->setRequired(); - $form->addSubmit('send', 'Add to cart'); + $form->addSubmit('send', 'Sepete ekle'); return $form; }); } ``` -Şablonda her ürün için bir form oluşturabiliriz - ve her form gerçekten de benzersiz bir bileşen olacaktır. +Şimdi şablonda her ürün için formu kolayca oluşturabiliriz - ve her biri gerçekten benzersiz bir bileşen olacaktır. ```latte {foreach $items as $item} @@ -35,26 +37,26 @@ protected function createComponentShopForm(): Multiplier {/foreach} ``` -`{control}` etiketine aktarılan argüman şöyle der: +`{control}` etiketinde iletilen argüman şu formatta: -1. bir bileşen edinin `shopForm` -2. ve çocuğunu döndürür `$item->id` +1. `shopForm` bileşenini al +2. ve ondan `$item->id` yavrusunu al -İlk **1.** çağrısı sırasında `shopForm` bileşeni henüz mevcut değildir, bu nedenle onu oluşturmak için `createComponentShopForm` yöntemi çağrılır. Daha sonra Çarpan'a parametre olarak aktarılan anonim bir fonksiyon çağrılır ve bir form oluşturulur. +**1.** noktasının ilk çağrısında `shopForm` henüz mevcut değildir, bu yüzden fabrikası `createComponentShopForm` çağrılır. Alınan bileşen (Multiplier örneği) üzerinde daha sonra belirli formun fabrikası çağrılır - ki bu, Multiplier'a kurucuda ilettiğimiz anonim fonksiyondur. -`foreach` adresinin sonraki yinelemelerinde `createComponentShopForm` yöntemi artık çağrılmaz çünkü bileşen zaten mevcuttur. Ancak başka bir çocuğa referans verdiğimiz için (`$item->id` iterasyonlar arasında değişir), anonim bir fonksiyon tekrar çağrılır ve yeni bir form oluşturulur. +Foreach'in bir sonraki yinelemesinde, `createComponentShopForm` metodu artık çağrılmaz (bileşen mevcuttur), ancak farklı bir yavrusunu aradığımız için (`$item->id` her yinelemede farklı olacaktır), anonim fonksiyon tekrar çağrılır ve bize yeni bir form döndürür. -Son olarak, formun sepete gerçekten doğru ürünü eklediğinden emin olmak gerekir çünkü mevcut durumda tüm formlar eşittir ve hangi ürünlere ait olduklarını ayırt edemeyiz. Bunun için Multiplier'ın (ve genel olarak Nette Framework'teki herhangi bir bileşen fabrika yönteminin) her bileşen fabrika yönteminin ilk argüman olarak oluşturulan bileşenin adını alması özelliğini kullanabiliriz. Bizim durumumuzda bu `$item->id` olacaktır, ki bu tam olarak bireysel ürünleri ayırt etmek için ihtiyacımız olan şeydir. Tek yapmanız gereken formu oluşturmak için kodu değiştirmektir: +Geriye kalan tek şey, formun sepete gerçekten eklemesi gereken ürünü eklemesini sağlamaktır - şu anda her ürün için form tamamen aynıdır. Multiplier'ın (ve genel olarak Nette Framework'teki her bileşen fabrikasının) özelliği bize yardımcı olur, yani her fabrikanın ilk argümanı olarak oluşturulan bileşenin adını almasıdır. Bizim durumumuzda bu `$item->id` olacaktır, ki bu tam olarak ihtiyacımız olan bilgidir. Bu nedenle, form oluşturmayı hafifçe ayarlamak yeterlidir: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function ($itemId) { $form = new Nette\Application\UI\Form; - $form->addInteger('amount', 'Amount:') + $form->addInteger('count', 'Ürün sayısı:') ->setRequired(); $form->addHidden('itemId', $itemId); - $form->addSubmit('send', 'Add to cart'); + $form->addSubmit('send', 'Sepete ekle'); return $form; }); } diff --git a/application/tr/presenters.texy b/application/tr/presenters.texy index 101d447cd2..2e9d6aa932 100644 --- a/application/tr/presenters.texy +++ b/application/tr/presenters.texy @@ -1,39 +1,39 @@ -Sunum Yapanlar -************** +Presenter'lar +*************
    -Nette sunumların ve şablonların nasıl yazılacağını öğreneceğiz. Okuduktan sonra öğreneceksiniz: +Nette'de presenter'ların ve şablonların nasıl yazıldığını öğreneceğiz. Okuduktan sonra şunları bileceksiniz: -- sunum yapan ki̇şi̇ nasil çalişir +- presenter nasıl çalışır - kalıcı parametreler nedir -- şablon nasıl oluşturulur +- şablonlar nasıl oluşturulur
    -Bir sunucunun, ana sayfa; e-mağazadaki ürün; oturum açma formu; site haritası beslemesi vb. gibi bir web uygulamasının belirli bir sayfasını temsil eden bir sınıf [olduğunuzaten biliyoruz |how-it-works#nette-application]. Uygulama bir ila binlerce sunucuya sahip olabilir. Diğer çerçevelerde, kontrolörler olarak da bilinirler. +[Zaten biliyoruz |how-it-works#Nette Application], presenter'ın bir web uygulamasının belirli bir sayfasını temsil eden bir sınıf olduğunu, örn. ana sayfa; e-ticaretteki bir ürün; giriş formu; site haritası beslemesi vb. Bir uygulamanın bir ila binlerce presenter'ı olabilir. Diğer framework'lerde bunlara controller da denir. -Genellikle, sunucu terimi, web arayüzleri için uygun olan ve bu bölümün geri kalanında tartışacağımız [api:Nette\Application\UI\Presenter] sınıfının bir torununu ifade eder. Genel anlamda, bir sunucu [api:Nette\Application\IPresenter] arayüzünü uygulayan herhangi bir nesnedir. +Genellikle presenter terimiyle, web arayüzleri oluşturmak için uygun olan ve bu bölümün geri kalanında ele alacağımız [api:Nette\Application\UI\Presenter] sınıfının bir alt sınıfı kastedilir. Genel anlamda, presenter [api:Nette\Application\IPresenter] arayüzünü uygulayan herhangi bir nesnedir. -Sunucunun Yaşam Döngüsü .[#toc-life-cycle-of-presenter] -======================================================= +Presenter Yaşam Döngüsü +======================= -Sunucunun görevi, isteği işlemek ve bir yanıt (bir HTML sayfası, görüntü, yönlendirme vb. olabilir) döndürmektir. +Presenter'ın görevi, isteği işlemek ve bir yanıt döndürmektir (bu bir HTML sayfası, bir resim, bir yönlendirme vb. olabilir). -Yani başlangıçta bir istek var. Bu doğrudan bir HTTP isteği değil, HTTP isteğinin bir yönlendirici kullanılarak dönüştürüldüğü bir [api:Nette\Application\Request] nesnesidir. Genellikle bu nesneyle temas etmeyiz, çünkü sunucu isteğin işlenmesini akıllıca özel yöntemlere devreder, şimdi bunları göreceğiz. +Yani başlangıçta ona bir istek iletilir. Bu doğrudan bir HTTP isteği değil, HTTP isteğinin yönlendirici yardımıyla dönüştürüldüğü [api:Nette\Application\Request] nesnesidir. Bu nesneyle genellikle temas etmeyiz, çünkü presenter isteğin işlenmesini akıllıca şimdi göstereceğimiz diğer metotlara devreder. -[* lifecycle.svg *] *** *Sunucunun yaşam döngüsü* .<> +[* lifecycle.svg *] *** *Presenter yaşam döngüsü* .<> -Şekilde, eğer varsa, yukarıdan aşağıya doğru sırayla çağrılan yöntemlerin bir listesi gösterilmektedir. Hiçbirinin var olmasına gerek yoktur, tek bir yöntem olmadan tamamen boş bir sunucuya sahip olabilir ve üzerinde basit bir statik web oluşturabiliriz. +Resim, mevcutsa yukarıdan aşağıya sırayla çağrılan metotların bir listesini temsil eder. Hiçbirinin mevcut olması gerekmez, tek bir metodu olmayan tamamen boş bir presenter'a sahip olabilir ve üzerine basit bir statik web sitesi kurabiliriz. `__construct()` --------------- -Yapıcı tam olarak sunucunun yaşam döngüsüne ait değildir, çünkü nesnenin yaratıldığı anda çağrılır. Ancak öneminden dolayı bundan bahsediyoruz. Yapıcı ( [inject yöntemiyle |best-practices:inject-method-attribute] birlikte) bağımlılıkları aktarmak için kullanılır. +Kurucu, nesnenin oluşturulma anında çağrıldığı için tam olarak presenter yaşam döngüsüne ait değildir. Ancak önemi nedeniyle bahsediyoruz. Kurucu ([inject metodu|best-practices:inject-method-attribute] ile birlikte) bağımlılıkları iletmek için kullanılır. -Sunucu, uygulamanın iş mantığıyla ilgilenmemeli, veritabanına yazmamalı ve veritabanından okumamalı, hesaplamalar yapmamalı vb. Bu, model olarak adlandırdığımız bir katmandaki sınıfların görevidir. Örneğin `ArticleRepository` sınıfı makalelerin yüklenmesinden ve kaydedilmesinden sorumlu olabilir. Sunucunun bunu kullanabilmesi için [bağımlılık enjeksiyonu kullanılarak geçirilir |dependency-injection:passing-dependencies]: +Presenter, uygulamanın iş mantığını işlememeli, veritabanından yazıp okumamalı, hesaplamalar yapmamalı vb. Bunun için model olarak adlandırdığımız katmandan sınıflar vardır. Örneğin, `ArticleRepository` sınıfı makaleleri yüklemek ve kaydetmekten sorumlu olabilir. Presenter'ın onunla çalışabilmesi için, onu [bağımlılık enjeksiyonu |dependency-injection:passing-dependencies] aracılığıyla iletmesini ister: ```php @@ -50,44 +50,44 @@ class ArticlePresenter extends Nette\Application\UI\Presenter `startup()` ----------- -İstek alındıktan hemen sonra `startup ()` yöntemi çağrılır. Bunu özellikleri başlatmak, kullanıcı ayrıcalıklarını kontrol etmek vb. için kullanabilirsiniz. Her zaman `parent::startup()` atasının çağrılması gerekir. +İstek alındıktan hemen sonra `startup()` metodu çağrılır. Özellikleri başlatmak, kullanıcı izinlerini doğrulamak vb. için kullanabilirsiniz. Metodun her zaman atası `parent::startup()`'ı çağırması gerekir. `action(args...)` .{toc: action()} -------------------------------------------------- -Yönteme benzer şekilde `render()`. O sırada `render()` 'de daha sonra işlenecek olan belirli bir şablon için veri hazırlamak üzere tasarlanmıştır. `action()` Bir istek, şablon oluşturmayı takip etmeden işlenir. Örneğin, veriler işlenir, bir kullanıcı oturum açar veya kapatır vb. ve ardından [başka bir yere yönlendirir |#Redirection]. +`render()` metodunun bir benzeri. `render()` belirli bir şablon için veri hazırlamak ve ardından onu oluşturmak için tasarlanmışken, `action()`'da istek şablon oluşturmaya bağlı kalmadan işlenir. Örneğin, veriler işlenir, kullanıcı giriş yapar veya çıkış yapar vb. ve ardından [başka bir yere yönlendirilir |#Yönlendirme]. -Bu önemlidir `action()` daha önce çağrılır `render()`Bu nedenle, içinde muhtemelen yaşam döngüsünün bir sonraki seyrini değiştirebiliriz, yani oluşturulacak şablonu ve ayrıca yöntemi değiştirebiliriz `render()` `setView('otherView')` kullanılarak çağrılacaktır. +Önemli olan, `action()`'ın `render()`'dan önce çağrılmasıdır, bu yüzden içinde olayların sonraki seyrini değiştirebiliriz, yani oluşturulacak şablonu ve ayrıca çağrılacak `render()` metodunu değiştirebiliriz. Ve bunu `setView('jineView')` kullanarak yaparız. -İstekten gelen parametreler yönteme aktarılır. Parametreler için tür belirtmek mümkündür ve önerilir, örneğin `actionShow(int $id, string $slug = null)` - `id` parametresi eksikse veya tamsayı değilse, sunum yapan kişi [404 hatası |#Error 404 etc.] döndürür ve işlemi sonlandırır. +Metoda istekten parametreler iletilir. Parametrelere tür belirtmek mümkündür ve önerilir, örn. `actionShow(int $id, ?string $slug = null)` - eğer `id` parametresi eksikse veya tamsayı değilse, presenter [404 hatası |#Hata 404 ve Benzerleri] döndürür ve çalışmayı sonlandırır. `handle(args...)` .{toc: handle()} -------------------------------------------------- -Bu yöntem, [Bileşenler |components#Signal] bölümünde tartışacağımız sözde sinyalleri işler. Esas olarak bileşenler ve AJAX isteklerinin işlenmesi için tasarlanmıştır. +Metot, [bileşenler |components#Sinyal] bölümünde tanışacağımız sözde sinyalleri işler. Aslında özellikle bileşenler ve AJAX isteklerinin işlenmesi için tasarlanmıştır. -Parametreler, aşağıdaki örnekte olduğu gibi yönteme geçirilir `action()`tip kontrolü de dahil olmak üzere. +Metoda, `action()` durumunda olduğu gibi, tür kontrolü dahil olmak üzere istekten parametreler iletilir. `beforeRender()` ---------------- -`beforeRender` yöntemi, adından da anlaşılacağı gibi, her yöntemden önce çağrılır `render()`. Ortak şablon yapılandırması, düzen için değişkenlerin geçirilmesi vb. için kullanılır. +`beforeRender` metodu, adından da anlaşılacağı gibi, her `render()` metodundan önce çağrılır. Şablonun ortak yapılandırması, layout için değişkenlerin iletilmesi vb. için kullanılır. `render(args...)` .{toc: render()} ---------------------------------------------- -Şablonu sonraki işleme için hazırladığımız, ona veri aktardığımız vb. yer. +Şablonu sonraki oluşturma için hazırladığımız, ona veri ilettiğimiz vb. yer. -Parametreler, aşağıdaki örnekte olduğu gibi yönteme geçirilir `action()`tip kontrolü de dahil olmak üzere. +Metoda, `action()` durumunda olduğu gibi, tür kontrolü dahil olmak üzere istekten parametreler iletilir. ```php public function renderShow(int $id): void { - // modelden veri alıyoruz ve bunu şablona aktarıyoruz + // modelden verileri alır ve şablona iletiriz $this->template->article = $this->articles->getById($id); } ``` @@ -96,104 +96,104 @@ public function renderShow(int $id): void `afterRender()` --------------- -`afterRender` yöntemi, adından da anlaşılacağı gibi, her `render()` yöntem. Oldukça nadiren kullanılır. +`afterRender` metodu, adından da anlaşılacağı gibi, her `render()` metodundan sonra çağrılır. Daha çok istisnai durumlarda kullanılır. `shutdown()` ------------ -Sunucunun yaşam döngüsünün sonunda çağrılır. +Presenter yaşam döngüsünün sonunda çağrılır. -**Devam etmeden önce iyi bir tavsiye**. Gördüğünüz gibi, sunum yapan kişi daha fazla eylemi/görüntüyü işleyebilir, yani daha fazla metoda sahip olabilir `render()`. Ancak sunum yapanların bir veya mümkün olduğunca az eylemle tasarlanmasını öneriyoruz. +**Devam etmeden önce iyi bir tavsiye**. Gördüğünüz gibi presenter birden fazla eylemi/view'i işleyebilir, yani birden fazla `render()` metoduna sahip olabilir. Ancak, bir veya mümkün olduğunca az eyleme sahip presenter'lar tasarlamanızı öneririz. -Yanıt Gönderme .[#toc-sending-a-response] -========================================= +Yanıt Gönderme +============== -Sunucunun yanıtı genellikle [HTML sayfasıyla şablonu işlemektir |templates], ancak bir dosya, JSON göndermek veya hatta başka bir sayfaya yönlendirmek de olabilir. +Presenter'ın yanıtı genellikle [bir HTML sayfasıyla şablonun oluşturulmasıdır|templates], ancak bir dosya gönderimi, JSON veya başka bir sayfaya yönlendirme de olabilir. -Yaşam döngüsü sırasında herhangi bir zamanda, bir yanıt göndermek ve aynı zamanda sunum yapan kişiden çıkmak için aşağıdaki yöntemlerden herhangi birini kullanabilirsiniz: +Yaşam döngüsünün herhangi bir anında, aşağıdaki metotlardan biriyle bir yanıt gönderebilir ve aynı zamanda presenter'ı sonlandırabiliriz: -- `redirect()`, `redirectPermanent()`, `redirectUrl()` ve `forward()` [yönlendirmeleri |#Redirection] -- `error()` [hata nedeniyle sunucuyu bıraktı|#Error 404 etc.] -- `sendJson($data)` sunumcudan çıkar ve [verileri |#Sending JSON] JSON biçiminde [gönderir |#Sending JSON] -- `sendTemplate()` sunumcudan çıkar ve [şablonu hemen oluşturur|templates] -- `sendResponse($response)` sunucuyu bıraktı ve [kendi yanıtını gönderdi|#Responses] -- `terminate()` cevap vermeden sunuculuğu bıraktı +- `redirect()`, `redirectPermanent()`, `redirectUrl()` ve `forward()` [yönlendirir |#Yönlendirme] +- `error()` presenter'ı [bir hata nedeniyle |#Hata 404 ve Benzerleri] sonlandırır +- `sendJson($data)` presenter'ı sonlandırır ve verileri JSON formatında [gönderir |#JSON Gönderme] +- `sendTemplate()` presenter'ı sonlandırır ve hemen [şablonu oluşturur |templates] +- `sendResponse($response)` presenter'ı sonlandırır ve [özel bir yanıt |#Yanıtlar] gönderir +- `terminate()` presenter'ı yanıtsız sonlandırır -Bu yöntemlerden herhangi birini çağırmazsanız, sunum yapan kişi otomatik olarak şablonu oluşturmaya devam edecektir. Neden mi? Çünkü vakaların %99'unda bir şablon çizmek isteriz, bu nedenle sunum yapan kişi bu davranışı varsayılan olarak alır ve işimizi kolaylaştırmak ister. +Bu metotlardan hiçbirini çağırmazsanız, presenter otomatik olarak şablonu oluşturmaya devam eder. Neden? Çünkü vakaların %99'unda bir şablon oluşturmak istiyoruz, bu yüzden presenter bu davranışı varsayılan olarak kabul eder ve işimizi kolaylaştırmak ister. -Bağlantı Oluşturma .[#toc-creating-links] -========================================= +Bağlantı Oluşturma +================== -Presenter, diğer presenter'lara URL bağlantıları oluşturmak için kullanılan `link()` yöntemine sahiptir. İlk parametre hedef sunumcu ve eylemdir, ardından dizi olarak geçirilebilen argümanlar gelir: +Presenter, diğer presenter'lara URL bağlantıları oluşturmak için kullanılabilecek `link()` metoduna sahiptir. İlk parametre hedef presenter ve eylemdir, ardından bir dizi olarak belirtilebilecek iletilen argümanlar gelir: ```php $url = $this->link('Product:show', $id); -$url = $this->link('Product:show', [$id, 'lang' => 'en']); +$url = $this->link('Product:show', [$id, 'lang' => 'tr']); ``` -Şablonda diğer sunumculara ve eylemlere aşağıdaki gibi bağlantılar oluşturuyoruz: +Şablonda, diğer presenter'lara ve eylemlere bağlantılar şu şekilde oluşturulur: ```latte -product detail +ürün detayı ``` -Gerçek URL yerine tanıdık `Presenter:action` çiftini yazmanız ve parametreleri eklemeniz yeterlidir. İşin püf noktası, bu özelliğin Latte tarafından işleneceğini ve gerçek bir URL oluşturacağını söyleyen `n:href` adresidir. Nette'de URL'ler hakkında hiç düşünmek zorunda değilsiniz, sadece sunucular ve eylemler hakkında. +Sadece gerçek URL yerine bilinen `Presenter:action` çiftini yazın ve olası parametreleri belirtin. İşin püf noktası, bu niteliğin Latte tarafından işleneceğini ve gerçek bir URL oluşturacağını söyleyen `n:href`'tir. Nette'de URL'leri hiç düşünmenize gerek yoktur, sadece presenter'ları ve eylemleri düşünmeniz yeterlidir. -Daha fazla bilgi için [Bağlantı Oluşturma |Creating Links] bölümüne bakın. +Daha fazla bilgi için [URL Bağlantıları Oluşturma|creating-links] bölümünde bulabilirsiniz. -Yeniden Yönlendirme .[#toc-redirection] -======================================= +Yönlendirme +=========== -`redirect()` ve `forward()` yöntemleri, [link() |#Creating Links] yöntemine çok benzer bir sözdizimine sahip olan başka bir sunucuya atlamak için kullanılır. +Başka bir presenter'a geçmek için, [link() |#Bağlantı Oluşturma] metoduyla çok benzer bir sözdizimine sahip olan `redirect()` ve `forward()` metotları kullanılır. -`forward()` HTTP yeniden yönlendirmesi olmadan hemen yeni sunucuya geçer: +`forward()` metodu, HTTP yönlendirmesi olmadan hemen yeni presenter'a geçer: ```php $this->forward('Product:show'); ``` -HTTP kodu 302 veya 303 ile geçici yeniden yönlendirme örneği: +HTTP kodu 302 (veya mevcut isteğin metodu POST ise 303) ile sözde geçici yönlendirme örneği: ```php $this->redirect('Product:show', $id); ``` -HTTP kodu 301 ile kalıcı yeniden yönlendirme elde etmek için kullanın: +HTTP kodu 301 ile kalıcı yönlendirmeyi şu şekilde başarırsınız: ```php $this->redirectPermanent('Product:show', $id); ``` -`redirectUrl()` yöntemi ile uygulama dışında başka bir URL'ye yönlendirme yapabilirsiniz: +Uygulama dışındaki başka bir URL'ye `redirectUrl()` metoduyla yönlendirilebilir. İkinci parametre olarak HTTP kodu belirtilebilir, varsayılan 302'dir (veya mevcut isteğin metodu POST ise 303): ```php $this->redirectUrl('https://nette.org'); ``` -Yeniden yönlendirme, `Nette\Application\AbortException` sessiz sonlandırma istisnasını fırlatarak sunum yapan kişinin yaşam döngüsünü derhal sonlandırır. +Yönlendirme, sözde sessiz sonlandırma istisnası `Nette\Application\AbortException` atarak presenter'ın etkinliğini hemen sonlandırır. -Yönlendirmeden önce, yönlendirmeden sonra şablonda görüntülenecek bir [flash mesaj |#Flash Messages], mesajlar göndermek mümkündür. +Yönlendirmeden önce, yönlendirmeden sonra şablonda görüntülenecek olan [#flash mesajları] (geçici mesajlar) gönderilebilir. -Flaş Mesajlar .[#toc-flash-messages] -==================================== +Flash Mesajları +=============== -Bunlar genellikle bir işlemin sonucu hakkında bilgi veren mesajlardır. Flash mesajların önemli bir özelliği, yeniden yönlendirmeden sonra bile şablonda mevcut olmalarıdır. Görüntülendikten sonra bile 30 saniye daha canlı kalırlar - örneğin, kullanıcının istemeden sayfayı yenilemesi durumunda - mesaj kaybolmaz. +Bunlar genellikle bir işlemin sonucunu bildiren mesajlardır. Flash mesajlarının önemli bir özelliği, yönlendirmeden sonra bile şablonda kullanılabilir olmalarıdır. Görüntülendikten sonra bile 30 saniye daha canlı kalırlar - örneğin, hatalı bir aktarım nedeniyle kullanıcının sayfayı yenilemesi durumunda mesaj hemen kaybolmaz. -Sadece [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] yöntemini çağırın ve presenter mesajı şablona aktarmakla ilgilenecektir. İlk bağımsız değişken mesajın metnidir ve isteğe bağlı ikinci bağımsız değişken mesajın türüdür (hata, uyarı, bilgi vb.). `flashMessage()` yöntemi, daha fazla bilgi eklememize izin vermek için bir flash mesajı örneği döndürür. +Sadece [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] metodunu çağırmanız yeterlidir ve presenter şablona iletilmesini halleder. İlk parametre mesaj metni ve isteğe bağlı ikinci parametre türüdür (error, warning, info vb.). `flashMessage()` metodu, flash mesajının bir örneğini döndürür ve buna ek bilgiler eklenebilir. ```php -$this->flashMessage('Item was removed.'); -$this->redirect(/* ... */); +$this->flashMessage('Öğe silindi.'); +$this->redirect(/* ... */); // ve yönlendiririz ``` -Şablonda, bu mesajlar `$flashes` değişkeninde `message` (mesaj metni), `type` (mesaj türü) özelliklerini içeren ve daha önce bahsedilen kullanıcı bilgilerini içerebilen `stdClass` nesneleri olarak mevcuttur. Bunları aşağıdaki gibi çiziyoruz: +Şablonda bu mesajlar `$flashes` değişkeninde `stdClass` nesneleri olarak bulunur ve `message` (mesaj metni), `type` (mesaj türü) özelliklerini içerir ve daha önce bahsedilen kullanıcı bilgilerini içerebilir. Onları örneğin şu şekilde oluştururuz: ```latte {foreach $flashes as $flash} @@ -202,10 +202,10 @@ $this->redirect(/* ... */); ``` -Hata 404 vb. .[#toc-error-404-etc] -================================== +Hata 404 ve Benzerleri +====================== -Örneğin görüntülemek istediğimiz makale veritabanında bulunmadığı için isteği yerine getiremediğimizde, HTTP hatası 404'ü temsil eden `error(string $message = null, int $httpCode = 404)` yöntemini kullanarak 404 hatasını atacağız: +İstek yerine getirilemiyorsa, örneğin görüntülemek istediğimiz makale veritabanında mevcut olmadığı için, `error(?string $message = null, int $httpCode = 404)` metoduyla 404 hatası atarız. ```php public function renderShow(int $id): void @@ -218,14 +218,13 @@ public function renderShow(int $id): void } ``` -HTTP hata kodu ikinci parametre olarak geçirilebilir, varsayılan 404'tür. Yöntem, `Nette\Application\BadRequestException` istisnasını fırlatarak çalışır ve ardından `Application` kontrolü hata sunucusuna geçirir. Bu, görevi hata hakkında bilgi veren bir sayfa görüntülemek olan bir sunumcudur. -Hata sunucusu [uygulama yapılandırmasında |configuration] ayarlanır. +Hatanın HTTP kodu ikinci parametre olarak iletilebilir, varsayılan 404'tür. Metot, `Nette\Application\BadRequestException` istisnası atarak çalışır, bunun üzerine `Application` kontrolü error-presenter'a devreder. Bu, meydana gelen hatayı bildiren bir sayfa görüntülemekle görevli bir presenter'dır. Error-presenter ayarı [application yapılandırmasında|configuration] yapılır. -JSON Gönderme .[#toc-sending-json] -================================== +JSON Gönderme +============= -JSON biçiminde veri gönderen ve sunum yapan kişiden çıkan eylem yöntemi örneği: +Verileri JSON formatında gönderen ve presenter'ı sonlandıran bir action-metodu örneği: ```php public function actionData(): void @@ -236,33 +235,59 @@ public function actionData(): void ``` -Kalıcı Parametreler .[#toc-persistent-parameters] -================================================= +İstek Parametreleri .{data-version:3.1.14} +========================================== -Kalıcı parametreler, farklı istekler arasında durumu korumak için kullanılır. Bir bağlantı tıklandıktan sonra bile değerleri aynı kalır. Oturum verilerinin aksine, URL'de aktarılırlar. Bu tamamen otomatiktir, bu nedenle bunları `link()` veya `n:href` adresinde açıkça belirtmeye gerek yoktur. +Presenter ve ayrıca her bileşen, HTTP isteğinden parametrelerini alır. Değerlerini `getParameter($name)` veya `getParameters()` metoduyla öğrenebilirsiniz. Değerler dizeler veya dize dizileridir, aslında doğrudan URL'den alınan ham verilerdir. -Kullanım örneği? Çok dilli bir uygulamanız var. Gerçek dil, her zaman URL'nin bir parçası olması gereken bir parametredir. Ancak bunu her bağlantıya dahil etmek inanılmaz derecede sıkıcı olacaktır. Bu yüzden onu `lang` adında kalıcı bir parametre yaparsınız ve kendi kendini taşır. Harika! +Daha fazla kolaylık için parametreleri özellikler aracılığıyla erişilebilir hale getirmenizi öneririz. Bunları `#[Parameter]` niteliğiyle işaretlemeniz yeterlidir: + +```php +use Nette\Application\Attributes\Parameter; // bu satır önemlidir + +class HomePresenter extends Nette\Application\UI\Presenter +{ + #[Parameter] + public string $theme; // public olmalı +} +``` + +Özellik için veri türünü (örn. `string`) belirtmenizi öneririz ve Nette değeri buna göre otomatik olarak dönüştürür. Parametre değerleri ayrıca [doğrulanabilir |#Parametre Doğrulaması]. + +Bir bağlantı oluştururken, parametrelere doğrudan değer atanabilir: + +```latte +tıkla +``` -Kalıcı bir parametre oluşturmak Nette son derece kolaydır. Sadece bir public özellik oluşturun ve şu nitelikle etiketleyin: (daha önce `/** @persistent */` kullanılıyordu) + +Kalıcı Parametreler +=================== + +Kalıcı parametreler, farklı istekler arasında durumu korumak için kullanılır. Değerleri, bir bağlantıya tıklandıktan sonra bile aynı kalır. Oturumdaki verilerin aksine, URL'de aktarılırlar. Ve bu tamamen otomatiktir, bu yüzden onları `link()` veya `n:href` içinde açıkça belirtmeye gerek yoktur. + +Kullanım örneği? Çok dilli bir uygulamanız var. Mevcut dil, sürekli olarak URL'nin bir parçası olması gereken bir parametredir. Ancak her bağlantıda onu belirtmek inanılmaz derecede yorucu olurdu. Bu yüzden onu kalıcı bir `lang` parametresi yaparsınız ve kendi kendine aktarılır. Harika! + +Nette'de kalıcı bir parametre oluşturmak son derece basittir. Sadece genel bir özellik oluşturmanız ve onu bir nitelikle işaretlemeniz yeterlidir: (eskiden `/** @persistent */` kullanılırdı) ```php -use Nette\Application\Attributes\Persistent; // bu satır önemlidir +use Nette\Application\Attributes\Persistent; // bu satır önemlidir class ProductPresenter extends Nette\Application\UI\Presenter { #[Persistent] - public string $lang; // halka açık olmalı + public string $lang; // public olmalı } ``` - `$this->lang`, `'en'` gibi bir değere sahipse, `link()` veya `n:href` kullanılarak oluşturulan bağlantılar da `lang=en` parametresini içerecektir. Ve bağlantı tıklandığında, yine `$this->lang = 'en'` olacaktır. +Eğer `$this->lang` değeri örneğin `'tr'` ise, `link()` veya `n:href` kullanılarak oluşturulan bağlantılar da `lang=tr` parametresini içerecektir. Ve bağlantıya tıklandıktan sonra yine `$this->lang = 'tr'` olacaktır. -Özellikler için veri türünü eklemenizi öneririz (örn. `string`) ve ayrıca varsayılan bir değer de ekleyebilirsiniz. Parametre değerleri [doğrulanabilir |#Validation of Persistent Parameters]. +Özellik için veri türünü (örn. `string`) belirtmenizi öneririz ve varsayılan bir değer de belirtebilirsiniz. Parametre değerleri [doğrulanabilir |#Parametre Doğrulaması]. -Kalıcı parametreler varsayılan olarak belirli bir sunum yapan kişinin tüm eylemleri arasında aktarılır. Bunları birden fazla sunum yapan kişi arasında geçirmek için tanımlamanız gerekir: +Kalıcı parametreler standart olarak söz konusu presenter'ın tüm eylemleri arasında aktarılır. Birden fazla presenter arasında da aktarılmaları için, ya: -- Sunum yapanların miras aldığı ortak bir atada -- sunum yapanların kullandığı özellikte: +- presenter'ların miras aldığı ortak bir atada tanımlanmaları gerekir +- presenter'ların kullandığı bir trait'te tanımlanmaları gerekir: ```php trait LanguageAware @@ -277,48 +302,42 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -Bir bağlantı oluştururken kalıcı bir parametrenin değerini değiştirebilirsiniz: +Bir bağlantı oluştururken, kalıcı parametrenin değeri değiştirilebilir: ```latte -detail in Czech +İngilizce detay ``` -Ya da *reset* edilebilir, yani URL'den kaldırılabilir. Daha sonra varsayılan değerini alacaktır: +Veya *sıfırlanabilir*, yani URL'den kaldırılabilir. O zaman varsayılan değerini alacaktır: ```latte -click +tıkla ``` -İnteraktif Bileşenler .[#toc-interactive-components] -==================================================== +İnteraktif Bileşenler +===================== -Sunucular yerleşik bir bileşen sistemine sahiptir. Bileşenler, sunucuların içine yerleştirdiğimiz ayrı yeniden kullanılabilir birimlerdir. Bunlar [formlar |forms:in-presenter], datagridler, menüler, aslında tekrar tekrar kullanılması mantıklı olan her şey olabilir. +Presenter'ların yerleşik bir bileşen sistemi vardır. Bileşenler, presenter'lara eklediğimiz bağımsız, yeniden kullanılabilir birimlerdir. Bunlar [formlar |forms:in-presenter], veri ızgaraları, menüler, aslında tekrar tekrar kullanılması mantıklı olan her şey olabilir. -Bileşenler sunum aracına nasıl yerleştirilir ve daha sonra nasıl kullanılır? Bu konu [Bileşenler |Components] bölümünde açıklanmaktadır. Hatta bunların Hollywood ile ne ilgisi olduğunu bile öğreneceksiniz. +Bileşenler presenter'a nasıl eklenir ve ardından kullanılır? Bunu [Bileşenler |components] bölümünde öğreneceksiniz. Hatta Hollywood ile ortak yanlarının ne olduğunu bile keşfedeceksiniz. -Bazı Bileşenleri Nereden Alabilirim? [Componette |https://componette.org] sayfasında, Nette Framework topluluğu tarafından yapılan ve paylaşılan bazı açık kaynaklı bileşenleri ve Nette için diğer eklentileri bulabilirsiniz. +Ve bileşenleri nereden alabilirim? [Componette |https://componette.org/search/component] sayfasında, framework etrafındaki topluluktan gönüllülerin buraya yerleştirdiği açık kaynaklı bileşenleri ve Nette için bir dizi başka eklentiyi bulabilirsiniz. -Daha Derine Gitmek .[#toc-going-deeper] -======================================= +Derinlemesine İnceliyoruz +========================= .[tip] -Bu bölümde şimdiye kadar gösterdiklerimiz muhtemelen yeterli olacaktır. Aşağıdaki satırlar, sunum yapanlarla derinlemesine ilgilenen ve her şeyi bilmek isteyenlere yöneliktir. - +Bu bölümde şimdiye kadar gösterdiklerimizle muhtemelen tamamen idare edeceksiniz. Aşağıdaki satırlar, presenter'ları derinlemesine merak eden ve kesinlikle her şeyi bilmek isteyenler içindir. -Gereksinim ve Parametreler .[#toc-requirement-and-parameters] -------------------------------------------------------------- -Sunum yapan kişi tarafından ele alınan istek [api:Nette\Application\Request] nesnesidir ve sunum yapan kişinin `getRequest()` yöntemi tarafından döndürülür. Bir dizi parametre içerir ve bunların her biri ya bazı bileşenlere ya da doğrudan sunum yapan kişiye aittir (aslında bu da özel bir bileşen olsa da bir bileşendir). Dolayısıyla Nette parametreleri yeniden dağıtır ve `loadState(array $params)` yöntemini çağırarak tek tek bileşenler (ve sunum yapan kişi) arasında geçiş yapar. Parametreler `getParameters(): array` yöntemiyle, ayrı ayrı `getParameter($name)` kullanılarak elde edilebilir. Parametre değerleri dizeler veya dizelerin dizileridir, temelde doğrudan bir URL'den elde edilen ham verilerdir. +Parametre Doğrulaması +--------------------- +URL'den alınan [istek parametrelerinin |#İstek Parametreleri] ve [kalıcı parametrelerin |#Kalıcı Parametreler] değerleri `loadState()` metodu tarafından özelliklere yazılır. Bu metot ayrıca özellikte belirtilen veri türünün eşleşip eşleşmediğini de kontrol eder, aksi takdirde 404 hatasıyla yanıt verir ve sayfa görüntülenmez. -Kalıcı Parametrelerin Doğrulanması .[#toc-validation-of-persistent-parameters] ------------------------------------------------------------------------------- - -URL'lerden alınan [kalıcı parametrelerin |#persistent parameters] değerleri `loadState()` metodu tarafından özelliklere yazılır. Ayrıca özellikte belirtilen veri türünün eşleşip eşleşmediğini kontrol eder, aksi takdirde 404 hatası ile yanıt verir ve sayfa görüntülenmez. - -Kalıcı parametrelere asla körü körüne güvenmeyin, çünkü bunlar URL'de kullanıcı tarafından kolayca üzerine yazılabilir. Örneğin, `$this->lang` adresinin desteklenen diller arasında olup olmadığını bu şekilde kontrol ediyoruz. Bunu yapmanın iyi bir yolu, yukarıda bahsedilen `loadState()` yöntemini geçersiz kılmaktır: +Parametrelere asla körü körüne güvenmeyin, çünkü kullanıcı tarafından URL'de kolayca üzerine yazılabilirler. Örneğin, `$this->lang` dilinin desteklenenler arasında olup olmadığını bu şekilde doğrularız. Uygun yol, bahsedilen `loadState()` metodunu geçersiz kılmaktır: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -329,8 +348,8 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function loadState(array $params): void { parent::loadState($params); // burada $this->lang ayarlanır - // kullanıcı değeri kontrolünü takip eder: - if (!in_array($this->lang, ['en', 'cs'])) { + // ardından kendi değer kontrolümüz gelir: + if (!in_array($this->lang, ['en', 'tr'])) { $this->error(); } } @@ -338,43 +357,45 @@ class ProductPresenter extends Nette\Application\UI\Presenter ``` -Talebi Kaydetme ve Geri Yükleme .[#toc-save-and-restore-the-request] --------------------------------------------------------------------- +İsteği Kaydetme ve Geri Yükleme +------------------------------- -Geçerli isteği bir oturuma kaydedebilir veya oturumdan geri yükleyebilir ve sunucunun tekrar yürütmesine izin verebilirsiniz. Bu, örneğin bir kullanıcı bir formu doldurduğunda ve oturum açma süresi dolduğunda kullanışlıdır. Veri kaybetmemek için, oturum açma sayfasına yönlendirmeden önce, kısa bir dize biçiminde bir tanımlayıcı döndüren ve oturum açma sunucusuna parametre olarak ileten `$reqId = $this->storeRequest()` adresini kullanarak geçerli isteği oturuma kaydederiz. +Presenter'ın işlediği istek [api:Nette\Application\Request] nesnesidir ve presenter'ın `getRequest()` metodu tarafından döndürülür. -Oturum açtıktan sonra, isteği oturumdan alan ve ona ileten `$this->restoreRequest($reqId)` yöntemini çağırıyoruz. Yöntem, isteğin şu anda oturum açmış olan kullanıcıyla aynı kullanıcı tarafından oluşturulduğunu doğrular. Başka bir kullanıcı giriş yaparsa veya anahtar geçersizse, hiçbir şey yapmaz ve program devam eder. +Mevcut istek oturuma kaydedilebilir veya tam tersi, ondan geri yüklenebilir ve presenter'ın onu tekrar yürütmesi sağlanabilir. Bu, örneğin kullanıcı bir form doldururken ve oturumu sona erdiğinde kullanışlıdır. Verileri kaybetmemek için, giriş sayfasına yönlendirmeden önce mevcut isteği `$reqId = $this->storeRequest()` kullanarak oturuma kaydederiz, bu da kısa bir dize şeklinde kimliğini döndürür ve bunu giriş presenter'ına parametre olarak iletiriz. -[Daha önceki bir sayfaya nasıl dönülür |best-practices:restore-request] yemek kitabına bakın. +Giriş yaptıktan sonra, isteği oturumdan alan ve ona yönlendiren `$this->restoreRequest($reqId)` metodunu çağırırız. Metot bu arada isteği oluşturan kullanıcının şimdi giriş yapanla aynı olduğunu doğrular. Farklı bir kullanıcı giriş yaparsa veya anahtar geçersizse, hiçbir şey yapmaz ve program devam eder. +[Önceki sayfaya nasıl geri dönülür |best-practices:restore-request] kılavuzuna bakın. -Kanonizasyon .[#toc-canonization] ---------------------------------- -Sunucular SEO'yu (internette aranabilirliğin optimizasyonu) geliştiren gerçekten harika bir özelliğe sahiptir. Farklı URL'lerde yinelenen içeriğin varlığını otomatik olarak önlerler. Birden fazla URL belirli bir hedefe yönlendiriyorsa, örneğin `/index` ve `/index?page=1`, çerçeve bunlardan birini birincil (kanonik) olarak belirler ve diğerlerini HTTP kodu 301 kullanarak ona yönlendirir. Bu sayede arama motorları sayfaları iki kez indekslemez ve sayfa sıralamalarını zayıflatmaz. +Kanonikleştirme +--------------- -Bu işleme kanonizasyon denir. Kanonik URL, genellikle koleksiyondaki ilk uygun [rota olan yönlendirici |routing] tarafından oluşturulan URL'dir. +Presenter'ların SEO'ya (arama motoru optimizasyonu) katkıda bulunan gerçekten harika bir özelliği vardır. Farklı URL'lerde yinelenen içeriğin varlığını otomatik olarak önlerler. Belirli bir hedefe birden fazla URL adresi yönlendiriyorsa, örn. `/index` ve `/index?page=1`, framework bunlardan birini birincil (kanonik) olarak belirler ve diğerlerini HTTP kodu 301 ile ona yönlendirir. Bu sayede arama motorları sayfalarınızı iki kez indekslemez ve sayfa sıralamalarını seyreltmez. -Kanonizasyon varsayılan olarak açıktır ve `$this->autoCanonicalize = false` adresinden kapatılabilir. +Bu sürece kanonikleştirme denir. Kanonik URL, [yönlendirici|routing] tarafından oluşturulan URL'dir, yani genellikle koleksiyondaki ilk eşleşen rotadır. -Yeniden yönlendirme bir AJAX veya POST isteği ile gerçekleşmez çünkü veri kaybına neden olur veya SEO katma değeri olmaz. +Kanonikleştirme varsayılan olarak açıktır ve `$this->autoCanonicalize = false` aracılığıyla kapatılabilir. -Ayrıca, `link()` yöntemi gibi sunum yapan kişiyi, eylemleri ve parametreleri bağımsız değişken olarak alan `canonicalize()` yöntemini kullanarak kanonlaştırmayı manuel olarak da çağırabilirsiniz. Bir bağlantı oluşturur ve bunu geçerli URL ile karşılaştırır. Farklıysa, oluşturulan bağlantıya yönlendirir. +AJAX veya POST isteği sırasında yönlendirme gerçekleşmez, çünkü veri kaybına neden olur veya SEO açısından ek bir değeri olmaz. + +Kanonikleştirmeyi manuel olarak `canonicalize()` metoduyla da tetikleyebilirsiniz; bu metoda `link()` metoduna benzer şekilde presenter, eylem ve parametreler iletilir. Bir bağlantı oluşturur ve onu mevcut URL adresiyle karşılaştırır. Farklıysa, oluşturulan bağlantıya yönlendirir. ```php -public function actionShow(int $id, string $slug = null): void +public function actionShow(int $id, ?string $slug = null): void { $realSlug = $this->facade->getSlugForId($id); - // eğer $slug, $realSlug'dan farklıysa yönlendirir + // $slug, $realSlug'dan farklıysa yönlendirir $this->canonicalize('Product:show', [$id, $realSlug]); } ``` -Etkinlikler .[#toc-events] --------------------------- +Olaylar +------- -Sunum yapan kişinin yaşam döngüsünün bir parçası olarak çağrılan `startup()`, `beforeRender()` ve `shutdown()` yöntemlerine ek olarak, otomatik olarak çağrılacak başka işlevler de tanımlanabilir. Sunum yapan kişi sözde [olayları |nette:glossary#events] tanımlar ve siz de bunların işleyicilerini `$onStartup`, `$onRender` ve `$onShutdown` dizilerine eklersiniz. +Presenter yaşam döngüsünün bir parçası olarak çağrılan `startup()`, `beforeRender()` ve `shutdown()` metotlarına ek olarak, otomatik olarak çağrılması gereken başka fonksiyonlar da tanımlanabilir. Presenter, işleyicilerini `$onStartup`, `$onRender` ve `$onShutdown` dizilerine ekleyeceğiniz sözde [olayları |nette:glossary#Olaylar Events] tanımlar. ```php class ArticlePresenter extends Nette\Application\UI\Presenter @@ -388,47 +409,92 @@ class ArticlePresenter extends Nette\Application\UI\Presenter } ``` -`$onStartup` dizisindeki işleyiciler `startup()` yönteminden hemen önce, ardından `$onRender` ile `beforeRender()` arasında çağrılır. `render()` ve son olarak `shutdown()` adresinden hemen önce `$onShutdown`. +`$onStartup` dizisindeki işleyiciler `startup()` metodundan hemen önce, `$onRender` `beforeRender()` ile `render()` arasında ve son olarak `$onShutdown` `shutdown()` metodundan hemen önce çağrılır. -Yanıtlar .[#toc-responses] --------------------------- +Yanıtlar +-------- -Sunucu tarafından döndürülen yanıt, [api:Nette\Application\Response] arayüzünü uygulayan bir nesnedir. Bir dizi hazır yanıt vardır: +Presenter'ın döndürdüğü yanıt, [api:Nette\Application\Response] arayüzünü uygulayan bir nesnedir. Bir dizi hazır yanıt mevcuttur: -- [api:Nette\Application\Responses\CallbackResponse] - bir geri arama gönderir -- [api:Nette\Application\Responses\FileResponse] - dosyayı gönderir -- [api:Nette\Application\Responses\ForwardResponse] - ileri () +- [api:Nette\Application\Responses\CallbackResponse] - bir geri çağırma gönderir +- [api:Nette\Application\Responses\FileResponse] - bir dosya gönderir +- [api:Nette\Application\Responses\ForwardResponse] - forward() - [api:Nette\Application\Responses\JsonResponse] - JSON gönderir -- [api:Nette\Application\Responses\RedirectResponse] - yönlendi̇rme +- [api:Nette\Application\Responses\RedirectResponse] - yönlendirme - [api:Nette\Application\Responses\TextResponse] - metin gönderir - [api:Nette\Application\Responses\VoidResponse] - boş yanıt -Yanıtlar `sendResponse()` yöntemi ile gönderilir: +Yanıtlar `sendResponse()` metoduyla gönderilir: ```php use Nette\Application\Responses; // Düz metin -$this->sendResponse(new Responses\TextResponse('Hello Nette!')); +$this->sendResponse(new Responses\TextResponse('Merhaba Nette!')); // Bir dosya gönderir $this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf')); -// Bir geri arama gönderir +// Yanıt bir geri çağırma olacak $callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse) { if ($httpResponse->getHeader('Content-Type') === 'text/html') { - echo '

    Hello

    '; + echo '

    Merhaba

    '; } }; $this->sendResponse(new Responses\CallbackResponse($callback)); ``` -Daha Fazla Okuma .[#toc-further-reading] -======================================== +`#[Requires]` ile Erişim Kısıtlaması .{data-version:3.2.2} +---------------------------------------------------------- + +`#[Requires]` niteliği, presenter'lara ve metotlarına erişimi kısıtlamak için gelişmiş seçenekler sunar. HTTP metotlarını belirtmek, AJAX isteği gerektirmek, aynı kaynağa (same origin) kısıtlamak ve yalnızca yönlendirme (forwarding) yoluyla erişime izin vermek için kullanılabilir. Nitelik, hem presenter sınıflarına hem de `action()`, `render()`, `handle()` ve `createComponent()` bireysel metotlarına uygulanabilir. + +Şu kısıtlamaları belirleyebilirsiniz: +- HTTP metotlarına: `#[Requires(methods: ['GET', 'POST'])]` +- AJAX isteği gerektirme: `#[Requires(ajax: true)]` +- yalnızca aynı kaynaktan erişim: `#[Requires(sameOrigin: true)]` +- yalnızca yönlendirme (forward) yoluyla erişim: `#[Requires(forward: true)]` +- belirli eylemlere kısıtlama: `#[Requires(actions: 'default')]` + +Ayrıntılar için [#Requires Niteliği Nasıl Kullanılır |best-practices:attribute-requires] kılavuzuna bakın. + + +HTTP Metodu Kontrolü +-------------------- + +Nette'deki presenter'lar, her gelen isteğin HTTP metodunu otomatik olarak doğrular. Bu kontrolün nedeni öncelikle güvenliktir. Standart olarak `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH` metotlarına izin verilir. + +Örneğin `OPTIONS` metoduna ek olarak izin vermek istiyorsanız, `#[Requires]` niteliğini kullanın (Nette Application v3.2'den itibaren): + +```php +#[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])] +class MyPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Sürüm 3.1'de doğrulama, istekte belirtilen metodun `$presenter->allowedMethods` dizisinde bulunup bulunmadığını kontrol eden `checkHttpMethod()` içinde yapılır. Metodu şu şekilde ekleyin: + +```php +class MyPresenter extends Nette\Application\UI\Presenter +{ + protected function checkHttpMethod(): void + { + $this->allowedMethods[] = 'OPTIONS'; + parent::checkHttpMethod(); + } +} +``` + +`OPTIONS` metoduna izin verirseniz, onu daha sonra presenter'ınız içinde uygun şekilde işlemeniz gerektiğini vurgulamak önemlidir. Metot genellikle, isteğin CORS (Cross-Origin Resource Sharing) politikası açısından izinli olup olmadığını belirlemek gerektiğinde tarayıcının gerçek istekten önce otomatik olarak gönderdiği sözde bir preflight isteği olarak kullanılır. Metoda izin verir ancak doğru yanıtı uygulamazsanız, bu tutarsızlıklara ve potansiyel güvenlik sorunlarına yol açabilir. + + +Daha Fazla Okuma +================ -- [Yöntemleri ve öznitelikleri enjekte |best-practices:inject-method-attribute]etme -- [Özelliklerden sunum yapanların oluşturulması |best-practices:presenter-traits] -- [Ayarların sunum yapanlara aktarılması |best-practices:passing-settings-to-presenters] -- [Önceki bir sayfaya nasıl dönülür |best-practices:restore-request]? +- [Inject Metotları ve Nitelikleri |best-practices:inject-method-attribute] +- [Trait'lerden Presenter Oluşturma |best-practices:presenter-traits] +- [Ayarları Presenter'lara İletme |best-practices:passing-settings-to-presenters] +- [Önceki Sayfaya Nasıl Geri Dönülür |best-practices:restore-request] diff --git a/application/tr/routing.texy b/application/tr/routing.texy index b1812614ab..8fccd6c0b1 100644 --- a/application/tr/routing.texy +++ b/application/tr/routing.texy @@ -1,29 +1,28 @@ -Yönlendirme -*********** +Yönlendirme (Routing) +*********************
    -Yönlendirici, URL'lerle ilgili her şeyden sorumludur, böylece artık bunları düşünmek zorunda kalmazsınız. Biz göstereceğiz: +Yönlendirici (Router), URL adresleriyle ilgili her şeyden sorumludur, böylece artık onlar hakkında düşünmek zorunda kalmazsınız. Şunları göstereceğiz: -- URL'lerin istediğiniz gibi görünmesi için yönlendiricinin nasıl ayarlanacağı -- SEO yönlendirmesi hakkında birkaç not -- ve size kendi yönlendiricinizi nasıl yazacağınızı göstereceğiz +- URL'lerin istediğiniz gibi olması için yönlendirici nasıl ayarlanır +- SEO ve yönlendirme hakkında konuşacağız +- ve kendi yönlendiricinizi nasıl yazacağınızı göstereceğiz
    -Daha insani URL'ler (veya havalı veya güzel URL'ler) daha kullanılabilir, daha akılda kalıcıdır ve SEO'ya olumlu katkıda bulunur. Nette bunu göz önünde bulundurur ve geliştiricilerin isteklerini tam olarak karşılar. Uygulamanız için URL yapınızı tam olarak istediğiniz şekilde tasarlayabilirsiniz. -Hatta herhangi bir kod veya şablon değişikliği olmadan yapılabildiği için uygulama hazır olduktan sonra bile tasarlayabilirsiniz. Tek bir [yerde |#Integration], yönlendiricide zarif bir şekilde tanımlanır ve tüm sunumcularda ek açıklamalar şeklinde dağınık değildir. +Daha insancıl URL'ler (veya havalı ya da güzel URL'ler) daha kullanışlı, daha akılda kalıcıdır ve SEO'ya olumlu katkıda bulunurlar. Nette bunu düşünür ve geliştiricilere tam destek verir. Uygulamanız için tam olarak istediğiniz URL adresi yapısını tasarlayabilirsiniz. Hatta uygulama bittiğinde bile tasarlayabilirsiniz, çünkü kodda veya şablonlarda herhangi bir müdahale gerektirmez. Zarif bir şekilde tek bir [yerde |#Uygulamaya Dahil Etme], yönlendiricide tanımlanır ve böylece tüm presenter'lara anotasyonlar şeklinde dağılmaz. -Nette'deki yönlendirici özeldir çünkü **çift yönlüdür**, hem HTTP istek URL'lerinin kodunu çözebilir hem de bağlantılar oluşturabilir. Bu nedenle Nette [Uygulamasında |how-it-works#Nette Application] hayati bir rol oynar, çünkü mevcut isteği hangi sunumcu ve eylemin yürüteceğine karar verir ve ayrıca şablonda [URL |creating-links] oluşturma vb. için kullanılır. +Nette'deki yönlendirici, **çift yönlü** olmasıyla olağanüstüdür. Hem HTTP isteğindeki URL'yi çözebilir hem de bağlantılar oluşturabilir. Bu nedenle [Nette Application |how-it-works#Nette Application]'da hayati bir rol oynar, çünkü hem mevcut isteği hangi presenter ve eylemin yürüteceğine karar verir hem de şablonda vb. [URL oluşturmak |creating-links] için kullanılır. -Ancak, yönlendirici bu kullanımla sınırlı değildir, sunum yapanların hiç kullanılmadığı uygulamalarda, REST API'leri vb. için kullanabilirsiniz. [Ayrılmış kullanım |#separated usage] bölümünde daha fazlası. +Ancak yönlendirici yalnızca bu kullanımla sınırlı değildir, presenter'ların hiç kullanılmadığı uygulamalarda, REST API'ler için vb. kullanabilirsiniz. Daha fazla bilgi [#Bağımsız Kullanım] bölümünde. -Rota Koleksiyonu .[#toc-route-collection] -========================================= +Rota Koleksiyonu +================ -Uygulamadaki URL adreslerini tanımlamanın en hoş yolu [api:Nette\Application\Routers\RouteList] sınıfıdır. Tanım, basit bir API kullanarak sözde rotaların bir listesinden, yani URL adreslerinin maskelerinden ve bunlarla ilişkili sunucu ve eylemlerden oluşur. Rotalara isim vermek zorunda değiliz. +Uygulamadaki URL adreslerinin şeklini tanımlamanın en hoş yolu [api:Nette\Application\Routers\RouteList] sınıfını sunar. Tanım, sözde rotaların, yani URL adresi maskelerinin ve bunlarla ilişkili presenter'ların ve eylemlerin basit bir API kullanılarak bir listesinden oluşur. Rotaları herhangi bir şekilde adlandırmamız gerekmez. ```php $router = new Nette\Application\Routers\RouteList; @@ -32,84 +31,84 @@ $router->addRoute('article/', 'Article:view'); // ... ``` -Örnek, tarayıcıda `https://any-domain.com/rss.xml` eylemi ile `Feed` sunucusunun görüntüleneceğini, `https://domain.com/article/12` eylemi ile `Article` 'un görüntüleneceğini vb. söylüyor. Uygun bir rota bulunamazsa, Nette Application, kullanıcıya 404 Not Found hata sayfası olarak görünen bir [BadRequestException |api:Nette\Application\BadRequestException] istisnası atarak yanıt verir. +Örnek, tarayıcıda `https://domain.com/rss.xml` açarsak, `Feed` presenter'ının `rss` eylemiyle görüntüleneceğini, `https://domain.com/article/12` açarsak, `Article` presenter'ının `view` eylemiyle görüntüleneceğini vb. söyler. Uygun bir rota bulunamazsa, Nette Application [BadRequestException |api:Nette\Application\BadRequestException] istisnası atarak yanıt verir, bu da kullanıcıya 404 Not Found hata sayfası olarak gösterilir. -Rotaların Sırası .[#toc-order-of-routes] ----------------------------------------- +Rotaların Sırası +---------------- -Rotaların listelenme sırası **çok önemlidir** çünkü yukarıdan aşağıya doğru sırayla değerlendirilirler. Kural, rotaları **özelden genele** doğru beyan etmemizdir: +Bireysel rotaların listelendiği sıra **kesinlikle anahtar** öneme sahiptir, çünkü yukarıdan aşağıya doğru sırayla değerlendirilirler. Rotaları **özgülden genele** doğru bildirme kuralı geçerlidir: ```php -// YANLIŞ: 'rss.xml' ilk rota ile eşleşir ve bunu olarak yanlış anlar +// YANLIŞ: 'rss.xml' ilk rota tarafından yakalanır ve bu dizeyi olarak anlar $router->addRoute('', 'Article:view'); $router->addRoute('rss.xml', 'Feed:rss'); -// İYİ +// DOĞRU $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('', 'Article:view'); ``` -Bağlantılar oluşturulurken rotalar da yukarıdan aşağıya doğru değerlendirilir: +Rotalar, bağlantılar oluşturulurken de yukarıdan aşağıya doğru değerlendirilir: ```php -// YANLIŞ: 'Feed:rss' için 'admin/feed/rss' şeklinde bir bağlantı oluşturur +// YANLIŞ: 'Feed:rss' bağlantısı 'admin/feed/rss' olarak oluşturulur $router->addRoute('admin//', 'Admin:default'); $router->addRoute('rss.xml', 'Feed:rss'); -// İYİ +// DOĞRU $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('admin//', 'Admin:default'); ``` -Doğru bir liste oluşturmanın biraz beceri gerektirdiğini sizden saklamayacağız. İşin içine girene kadar, [yönlendirme paneli |#Debugging Router] faydalı bir araç olacaktır. +Doğru rota derlemesinin belirli bir beceri gerektirdiğini sizden saklamayacağız. Buna hakim olana kadar, [yönlendirme paneli |#Yönlendirici Hata Ayıklaması] sizin için yararlı bir yardımcı olacaktır. -Maske ve Parametreler .[#toc-mask-and-parameters] -------------------------------------------------- +Maske ve Parametreler +--------------------- -Maske, site kökünü temel alan göreli yolu tanımlar. En basit maske statik bir URL'dir: +Maske, web sitesinin kök dizininden göreceli yolu tanımlar. En basit maske statik bir URL'dir: ```php $router->addRoute('products', 'Products:default'); ``` -Genellikle maskeler **parametreler** olarak adlandırılan parametreler içerir. Bunlar açılı parantezler içine alınır (örn. ``) ve hedef sunucuya, örneğin `renderShow(int $year)` yöntemine veya `$year` kalıcı parametresine aktarılır: +Genellikle maskeler sözde **parametreler** içerir. Bunlar sivri parantez içinde belirtilir (örn. ``) ve hedef presenter'a, örneğin `renderShow(int $year)` metoduna veya kalıcı parametre `$year`'a iletilir: ```php $router->addRoute('chronicle/', 'History:show'); ``` -Örnek, tarayıcıda `https://any-domain.com/chronicle/2020` sunumcusunun ve `year: 2020` parametreli `show` eyleminin görüntüleneceğini söylüyor. +Örnek, tarayıcıda `https://example.com/chronicle/2020` açarsak, `History` presenter'ının `show` eylemiyle ve `year: 2020` parametresiyle görüntüleneceğini söyler. -Parametreler için doğrudan maske içinde varsayılan bir değer belirleyebiliriz ve böylece isteğe bağlı hale gelir: +Parametrelere doğrudan maskede varsayılan bir değer atayabiliriz ve böylece isteğe bağlı hale gelirler: ```php $router->addRoute('chronicle/', 'History:show'); ``` -Rota şimdi `https://any-domain.com/chronicle/` parametresiyle birlikte `History:show` adresini tekrar görüntüleyecektir. +Rota şimdi `https://example.com/chronicle/` URL'sini de kabul edecektir, bu da yine `History:show`'u `year: 2020` parametresiyle görüntüler. -Elbette, sunum yapan kişinin adı ve eylem de bir parametre olabilir. Örneğin: +Parametre elbette presenter ve eylem adı da olabilir. Örneğin şöyle: ```php $router->addRoute('/', 'Home:default'); ``` -Bu rota, örneğin `/article/edit` veya `/catalog/list` şeklinde bir URL'yi kabul eder ve bunları `Article:edit` veya `Catalog:list` sunucularına ve eylemlerine çevirir. +Belirtilen rota, örn. `/article/edit` veya `/catalog/list` şeklindeki URL'leri kabul eder ve bunları `Article:edit` ve `Catalog:list` presenter'ları ve eylemleri olarak anlar. -Ayrıca `presenter` ve `action` parametrelerine`Home` ve `default` varsayılan değerlerini verir ve bu nedenle bunlar isteğe bağlıdır. Böylece rota `/article` URL'sini de kabul eder ve bunu `Article:default` olarak çevirir. Ya da tam tersi, `Product:default` adresine bir bağlantı `/product` yolunu oluşturur, varsayılan `Home:default` adresine bir bağlantı `/` yolunu oluşturur. +Aynı zamanda `presenter` ve `action` parametrelerine `Home` ve `default` varsayılan değerlerini verir ve dolayısıyla bunlar da isteğe bağlıdır. Yani rota, `/article` şeklindeki URL'yi de kabul eder ve onu `Article:default` olarak anlar. Veya tersi, `Product:default` bağlantısı `/product` yolunu, varsayılan `Home:default` bağlantısı `/` yolunu oluşturur. -Maske yalnızca site köküne dayalı göreli yolu değil, aynı zamanda eğik çizgi ile başladığında mutlak yolu, hatta iki eğik çizgi ile başladığında tüm mutlak URL'yi de tanımlayabilir: +Maske yalnızca web sitesinin kök dizininden göreceli yolu değil, aynı zamanda eğik çizgiyle başlıyorsa mutlak yolu veya hatta iki eğik çizgiyle başlıyorsa tüm mutlak URL'yi tanımlayabilir: ```php -// uygulama belgesi kök dizinine göreli yol +// document root'a göreceli $router->addRoute('/', /* ... */); -// mutlak yol, sunucu ana bilgisayar adına göreli +// mutlak yol (alan adına göreceli) $router->addRoute('//', /* ... */); -// ana bilgisayar adı dahil mutlak URL (ancak şema göreli) +// alan adı dahil mutlak URL (şemaya göreceli) $router->addRoute('//.example.com//', /* ... */); // şema dahil mutlak URL @@ -117,45 +116,45 @@ $router->addRoute('https://.example.com//', /* ... */); ``` -Doğrulama İfadeleri .[#toc-validation-expressions] --------------------------------------------------- +Doğrulama İfadeleri +------------------- -[Düzenli ifade |https://www.php.net/manual/en/reference.pcre.pattern.syntax.php] kullanılarak her parametre için bir doğrulama koşulu belirtilebilir. Örneğin, `\d+` regexp kullanarak `id` adresini yalnızca sayısal olacak şekilde ayarlayalım: +Her parametre için [düzenli ifade|https://www.php.net/manual/en/reference.pcre.pattern.syntax.php] kullanarak bir doğrulama koşulu belirlenebilir. Örneğin, `id` parametresinin yalnızca rakamlardan oluşabileceğini `\d+` düzenli ifadesiyle belirleriz: ```php $router->addRoute('/[/]', /* ... */); ``` -Tüm parametreler için varsayılan düzenli ifade `[^/]+`yani eğik çizgi dışındaki her şey. Bir parametrenin eğik çizgiyle de eşleşmesi gerekiyorsa, düzenli ifadeyi `.+` olarak ayarlarız. +Tüm parametreler için varsayılan düzenli ifade `[^/]+`'dır, yani eğik çizgi dışındaki her şey. Parametrenin eğik çizgileri de kabul etmesi gerekiyorsa, `.+` ifadesini belirtiriz: ```php -// https://example.com/a/b/c adresini kabul eder, yol 'a/b/c' şeklindedir +// https://example.com/a/b/c kabul eder, path 'a/b/c' olur $router->addRoute('', /* ... */); ``` -İsteğe Bağlı Diziler .[#toc-optional-sequences] ------------------------------------------------ +İsteğe Bağlı Diziler +-------------------- -Köşeli parantezler maskenin isteğe bağlı kısımlarını belirtir. Parametreleri içerenler de dahil olmak üzere maskenin herhangi bir kısmı isteğe bağlı olarak ayarlanabilir: +Maskede isteğe bağlı bölümleri köşeli parantez kullanarak işaretleyebilirsiniz. Maskenin herhangi bir bölümü isteğe bağlı olabilir, içinde parametreler de bulunabilir: ```php $router->addRoute('[/]', /* ... */); -// Kabul edilen URL'ler: Parametreler: -// /en/download lang => en, name => download -// /download lang => null, name => download +// Yolları kabul eder: +// /tr/download => lang => tr, name => download +// /download => lang => null, name => download ``` -Elbette, bir parametre isteğe bağlı bir dizinin parçası olduğunda, o da isteğe bağlı hale gelir. Varsayılan bir değeri yoksa, null olacaktır. +Parametre isteğe bağlı bir dizinin parçası olduğunda, doğal olarak isteğe bağlı hale gelir. Varsayılan bir değeri belirtilmemişse, null olur. -İsteğe bağlı bölümler de etki alanında olabilir: +İsteğe bağlı bölümler alan adında da olabilir: ```php $router->addRoute('//[.]example.com//', /* ... */); ``` -Diziler serbestçe iç içe geçirilebilir ve birleştirilebilir: +Diziler istenildiği gibi iç içe geçirilebilir ve birleştirilebilir: ```php $router->addRoute( @@ -163,48 +162,48 @@ $router->addRoute( 'Home:default', ); -// Kabul edilen URL'ler: -// /en/hello -// /en-us/hello -// /hello -// /hello/page-12 +// Yolları kabul eder: +// /tr/hello +// /en-us/hello +// /hello +// /hello/page-12 ``` -URL oluşturucu URL'yi mümkün olduğunca kısa tutmaya çalışır, bu nedenle atlanabilecek şeyler atlanır. Bu nedenle, örneğin, bir rota `index[.html]` bir yol oluşturur `/index`. Sol köşeli ayraçtan sonra bir ünlem işareti yazarak bu davranışı tersine çevirebilirsiniz: +URL oluşturulurken en kısa varyant hedeflenir, bu nedenle atlanabilecek her şey atlanır. Bu yüzden örneğin `index[.html]` rotası `/index` yolunu oluşturur. Sol köşeli parantezden sonra bir ünlem işareti belirterek davranışı tersine çevirmek mümkündür: ```php -// hem /hello hem de /hello.html kabul eder, /hello oluşturur +// /hello ve /hello.html kabul eder, /hello oluşturur $router->addRoute('[.html]', /* ... */); -// hem /hello hem de /hello.html kabul eder, /hello.html oluşturur +// /hello ve /hello.html kabul eder, /hello.html oluşturur $router->addRoute('[!.html]', /* ... */); ``` -Köşeli parantezler olmadan isteğe bağlı parametreler (yani varsayılan değere sahip parametreler) bu şekilde sarılmış gibi davranır: +Köşeli parantez olmadan isteğe bağlı parametreler (yani varsayılan değere sahip parametreler) aslında aşağıdaki gibi parantez içine alınmış gibi davranırlar: ```php $router->addRoute('//', /* ... */); -// eşittir: +// şuna karşılık gelir: $router->addRoute('[/[/[]]]', /* ... */); ``` -En sağdaki eğik çizginin nasıl oluşturulduğunu değiştirmek için, yani `/home/` yerine bir `/home` almak için, rotayı bu şekilde ayarlayın: +Eğer bitiş eğik çizgisinin davranışını etkilemek isteseydik, örneğin `/home/` yerine sadece `/home` oluşturulsun, bunu şu şekilde başarabiliriz: ```php $router->addRoute('[[/[/]]]', /* ... */); ``` -Wildcards .[#toc-wildcards] ---------------------------- +Joker Karakterler +----------------- -Mutlak yol maskesinde, örneğin geliştirme ve üretim ortamında farklılık gösterebilecek maskeye bir etki alanı yazma ihtiyacından kaçınmak için aşağıdaki joker karakterleri kullanabiliriz: +Mutlak yol maskesinde aşağıdaki joker karakterleri kullanabilir ve böylece örneğin geliştirme ve üretim ortamlarında farklı olabilen alan adını maskeye yazma zorunluluğundan kaçınabiliriz: -- `%tld%` = üst düzey alan adı, örneğin `com` veya `org` -- `%sld%` = ikinci seviye alan adı, örn. `example` -- `%domain%` = alt alan adları olmayan alan adı, örn. `example.com` -- `%host%` = tüm ana bilgisayar, örn. `www.example.com` +- `%tld%` = üst düzey alan adı, örn. `com` veya `org` +- `%sld%` = ikinci düzey alan adı, örn. `example` +- `%domain%` = alt alan adları olmadan alan adı, örn. `example.com` +- `%host%` = tüm ana bilgisayar adı, örn. `www.example.com` - `%basePath%` = kök dizine giden yol ```php @@ -213,10 +212,10 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ @@ -225,7 +224,7 @@ $router->addRoute('/[/]', [ ]); ``` -Ya da bu formu kullanabiliriz, doğrulama düzenli ifadesinin yeniden yazıldığına dikkat edin: +Daha ayrıntılı belirtim için, varsayılan değerlere ek olarak parametrelerin diğer özelliklerini, örneğin doğrulama düzenli ifadesini (bkz. `id` parametresi) ayarlayabileceğimiz daha da genişletilmiş bir form kullanılabilir: ```php use Nette\Routing\Route; @@ -243,19 +242,19 @@ $router->addRoute('/[/]', [ ]); ``` -Bu daha konuşkan formatlar diğer meta verileri eklemek için kullanışlıdır. +Dizide tanımlanan parametreler yol maskesinde belirtilmemişse, değerlerinin URL'deki soru işaretinden sonra belirtilen sorgu parametreleri kullanılarak bile değiştirilemeyeceğini belirtmek önemlidir. -Filtreler ve Çeviriler .[#toc-filters-and-translations] -------------------------------------------------------- +Filtreler ve Çeviriler +---------------------- -Kaynak kodunu İngilizce yazmak iyi bir uygulamadır, ancak web sitenizin URL'sinin farklı bir dile çevrilmesi gerekiyorsa ne olacak? Gibi basit rotalar: +Uygulamanın kaynak kodlarını İngilizce yazıyoruz, ancak web sitesinin Türkçe URL'lere sahip olması gerekiyorsa, o zaman basit yönlendirme türü: ```php $router->addRoute('/', 'Home:default'); ``` -`/product/123` veya `/cart` gibi İngilizce URL'ler oluşturacaktır. URL'deki sunucuların ve eylemlerin Almanca'ya çevrilmesini istiyorsak (örneğin `/produkt/123` veya `/einkaufswagen`), bir çeviri sözlüğü kullanabiliriz. Bunu eklemek için, ikinci parametrenin "daha konuşkan" bir varyantına zaten ihtiyacımız var: +`/product/123` veya `/cart` gibi İngilizce URL'ler üretecektir. URL'deki presenter'ların ve eylemlerin Türkçe kelimelerle temsil edilmesini istiyorsak (örn. `/urun/123` veya `/sepet`), bir çeviri sözlüğü kullanabiliriz. Yazımı için zaten ikinci parametrenin "daha konuşkan" varyantına ihtiyacımız var: ```php use Nette\Routing\Route; @@ -265,8 +264,8 @@ $router->addRoute('/', [ Route::Value => 'Home', Route::FilterTable => [ // URL'deki dize => presenter - 'produkt' => 'Product', - 'einkaufswagen' => 'Cart', + 'urun' => 'Product', + 'sepet' => 'Cart', 'katalog' => 'Catalog', ], ], @@ -279,11 +278,11 @@ $router->addRoute('/', [ ]); ``` -Aynı sunum yapan kişi için birden fazla sözlük anahtarı kullanılabilir. Bunun için çeşitli takma adlar oluşturacaklardır. Son anahtar kanonik varyant olarak kabul edilir (yani oluşturulan URL'de yer alacak olan). +Çeviri sözlüğünün birden fazla anahtarı aynı presenter'a yol açabilir. Böylece ona farklı takma adlar oluşturulur. Kanonik varyant (yani oluşturulan URL'de olacak olan) olarak son anahtar kabul edilir. -Çeviri tablosu bu şekilde herhangi bir parametreye uygulanabilir. Ancak, çeviri mevcut değilse, orijinal değer alınır. Bu davranışı `Route::FilterStrict => true` ekleyerek değiştirebiliriz ve değer sözlükte yoksa rota URL'yi reddedecektir. +Çeviri tablosu bu şekilde herhangi bir parametreye uygulanabilir. Çeviri mevcut değilse, orijinal değer alınır. Bu davranışı `Route::FilterStrict => true` ekleyerek değiştirebiliriz ve rota daha sonra değer sözlükte yoksa URL'yi reddeder. -Bir dizi biçimindeki çeviri sözlüğüne ek olarak, kendi çeviri işlevlerini ayarlamak da mümkündür: +Dizi şeklindeki çeviri sözlüğüne ek olarak, kendi çeviri fonksiyonlarımızı da dağıtabiliriz. ```php use Nette\Routing\Route; @@ -299,15 +298,15 @@ $router->addRoute('//', [ ]); ``` -`Route::FilterIn` işlevi, URL'deki parametre ile daha sonra sunum yapan kişiye aktarılan dize arasında dönüşüm yapar, `FilterOut` işlevi ise dönüşümü ters yönde sağlar. +`Route::FilterIn` fonksiyonu, URL'deki parametre ile daha sonra presenter'a iletilen dize arasında dönüştürme yapar, `FilterOut` fonksiyonu ters yönde dönüştürmeyi sağlar. -`presenter`, `action` ve `module` parametreleri, URL'de kullanılan PascalCase resp. camelCase stili ile kebab-case arasında dönüşüm yapan önceden tanımlanmış filtrelere sahiptir. Parametrelerin varsayılan değeri dönüştürülmüş formda zaten yazılmıştır, Bu nedenle, örneğin, bir sunum yapan kişi söz konusu olduğunda şunları yazarız `` yerine ``. +`presenter`, `action` ve `module` parametrelerinin zaten URL'de kullanılan PascalCase veya camelCase ve kebab-case stilleri arasında dönüştürme yapan önceden tanımlanmış filtreleri vardır. Parametrelerin varsayılan değeri zaten dönüştürülmüş biçimde yazılır, bu yüzden örneğin presenter durumunda `` yazarız, `` değil. -Genel Filtreler .[#toc-general-filters] ---------------------------------------- +Genel Filtreler +--------------- -Belirli parametreler için filtrelerin yanı sıra, herhangi bir şekilde değiştirebilecekleri tüm parametrelerin ilişkisel bir dizisini alan ve ardından geri dönen genel filtreler de tanımlayabilirsiniz. Genel filtreler `null` tuşu altında tanımlanır. +Belirli parametrelere yönelik filtrelere ek olarak, tüm parametrelerin ilişkisel bir dizisini alan, bunları herhangi bir şekilde değiştirebilen ve sonra döndüren genel filtreler de tanımlayabiliriz. Genel filtreleri `null` anahtarı altında tanımlarız. ```php use Nette\Routing\Route; @@ -315,22 +314,22 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => 'Home', 'action' => 'default', - null => [ + '' => [ Route::FilterIn => function (array $params): array { /* ... */ }, Route::FilterOut => function (array $params): array { /* ... */ }, ], ]); ``` -Genel filtreler size rotanın davranışını kesinlikle herhangi bir şekilde ayarlama olanağı verir. Örneğin bunları, parametreleri diğer parametrelere göre değiştirmek için kullanabiliriz. Örneğin, çeviri `` ve `` parametresinin geçerli değerine göre ``. +Genel filtreler, rotanın davranışını kesinlikle herhangi bir şekilde değiştirme yeteneği verir. Bunları örneğin parametreleri diğer parametrelere göre değiştirmek için kullanabiliriz. Örneğin, `` ve ``'ın mevcut `` parametresinin değerine göre çevrilmesi. -Bir parametrede özel bir filtre tanımlanmışsa ve aynı zamanda genel bir filtre varsa, özel `FilterIn` genelden önce çalıştırılır ve bunun tersi olarak genel `FilterOut` özelden önce çalıştırılır. Böylece, genel filtrenin içinde `presenter` ve `action` parametrelerinin değerleri PascalCase ve camelCase stilinde yazılır. +Bir parametrenin kendi filtresi tanımlanmışsa ve aynı zamanda genel bir filtre varsa, kendi `FilterIn` filtresi genel filtreden önce ve tersine genel `FilterOut` filtresi kendi filtresinden önce yürütülür. Yani genel filtre içinde, `presenter` veya `action` parametrelerinin değerleri PascalCase veya camelCase stilinde yazılır. -OneWay Bayrağı .[#toc-oneway-flag] ----------------------------------- +Tek Yönlüler (OneWay) +--------------------- -Tek yönlü rotalar, uygulamanın artık üretmediği ancak hala kabul ettiği eski URL'lerin işlevselliğini korumak için kullanılır. Bunları `OneWay` ile işaretleriz: +Tek yönlü rotalar, uygulamanın artık oluşturmadığı ancak hala kabul ettiği eski URL'lerin işlevselliğini korumak için kullanılır. Bunları `OneWay` bayrağıyla işaretleriz: ```php // eski URL /product-info?id=123 @@ -339,38 +338,61 @@ $router->addRoute('product-info', 'Product:detail', $router::ONE_WAY); $router->addRoute('product/', 'Product:detail'); ``` -Eski URL'ye erişildiğinde, sunucu otomatik olarak yeni URL'ye yönlendirir, böylece arama motorları bu sayfaları iki kez dizine eklemez (bkz. [SEO ve kanonizasyon |#SEO and canonization]). +Eski URL'ye erişildiğinde, presenter otomatik olarak yeni URL'ye yönlendirir, böylece arama motorları bu sayfaları iki kez indekslemez ([#SEO ve kanonikleştirme] bölümüne bakın). -Modüller .[#toc-modules] ------------------------- +Geri Çağrılarla Dinamik Yönlendirme +----------------------------------- + +Geri çağırmalarla dinamik yönlendirme, rotalara doğrudan, ilgili yol ziyaret edildiğinde yürütülecek fonksiyonlar (geri çağırmalar) atamanıza olanak tanır. Bu esnek işlevsellik, uygulamanız için çeşitli uç noktaları (endpoints) hızlı ve verimli bir şekilde oluşturmanıza olanak tanır: + +```php +$router->addRoute('test', function () { + echo '/test adresindesiniz'; +}); +``` + +Ayrıca maskede, geri çağırmanıza otomatik olarak iletilecek parametreler tanımlayabilirsiniz: + +```php +$router->addRoute('', function (string $lang) { + echo match ($lang) { + 'tr' => 'Web sitemizin Türkçe versiyonuna hoş geldiniz!', + 'en' => 'Welcome to the English version of our website!', + }; +}); +``` -Bir [modüle |modules] ait daha fazla rotamız varsa, bunları gruplamak için `withModule()` adresini kullanabiliriz: + +Modüller +-------- + +Ortak bir [modüle |directory-structure#Presenter lar ve Şablonlar] ait birden fazla rotamız varsa, `withModule()` kullanırız: ```php $router = new RouteList; -$router->withModule('Forum') // aşağıdaki yönlendiriciler Forum modülünün bir parçasıdır - ->addRoute('rss', 'Feed:rss') // Sunucu Forum:Feed +$router->withModule('Forum') // aşağıdaki rotalar Forum modülünün bir parçasıdır + ->addRoute('rss', 'Feed:rss') // presenter Forum:Feed olacak ->addRoute('/') - ->withModule('Admin') // aşağıdaki yönlendiriciler Forum:Admin modülünün bir parçasıdır + ->withModule('Admin') // aşağıdaki rotalar Forum:Admin modülünün bir parçasıdır ->addRoute('sign:in', 'Sign:in'); ``` -Bir alternatif de `module` parametresini kullanmaktır: +Alternatif olarak `module` parametresini kullanmaktır: ```php -// URL manage/dashboard/default presenter Admin:Dashboard ile eşleşir +// URL manage/dashboard/default, Admin:Dashboard presenter'ına eşlenir $router->addRoute('manage//', [ 'module' => 'Admin', ]); ``` -Alt Alanlar .[#toc-subdomains] ------------------------------- +Alt Alan Adları +--------------- -Rota koleksiyonları alt alan adlarına göre gruplandırılabilir: +Rota koleksiyonlarını alt alan adlarına göre bölebiliriz: ```php $router = new RouteList; @@ -379,7 +401,7 @@ $router->withDomain('example.com') ->addRoute('/'); ``` -Alan adınızda [joker karakter |#wildcards] ler de kullanabilirsiniz: +Alan adında [#joker karakterler] de kullanılabilir: ```php $router = new RouteList; @@ -388,23 +410,23 @@ $router->withDomain('example.%tld%') ``` -Yol Öneki .[#toc-path-prefix] ------------------------------ +Yol Öneki +--------- -Rota koleksiyonları URL'deki yola göre gruplandırılabilir: +Rota koleksiyonlarını URL'deki yola göre bölebiliriz: ```php $router = new RouteList; $router->withPath('eshop') - ->addRoute('rss', 'Feed:rss') // /eshop/rss URL'si ile eşleşir - ->addRoute('/'); // /eshop// URL'si ile eşleşir + ->addRoute('rss', 'Feed:rss') // /eshop/rss URL'sini yakalar + ->addRoute('/'); // /eshop// URL'sini yakalar ``` -Kombinasyonlar .[#toc-combinations] ------------------------------------ +Kombinasyonlar +-------------- -Yukarıdaki kullanım birleştirilebilir: +Yukarıdaki bölümlemeyi birbirleriyle birleştirebiliriz: ```php $router = (new RouteList) @@ -424,40 +446,40 @@ $router = (new RouteList) ``` -Sorgu Parametreleri .[#toc-query-parameters] --------------------------------------------- +Sorgu Parametreleri +------------------- -Maskeler sorgu parametreleri de içerebilir (URL'de soru işaretinden sonraki parametreler). Bir doğrulama ifadesi tanımlayamazlar, ancak sunum yapan kişiye iletildikleri adı değiştirebilirler: +Maskeler ayrıca sorgu parametrelerini (URL'deki soru işaretinden sonraki parametreler) de içerebilir. Bunlar için bir doğrulama ifadesi tanımlanamaz, ancak presenter'a iletilecekleri adı değiştirebilirsiniz: ```php -// uygulamada 'cat' sorgu parametresini 'categoryId' olarak kullan +// 'cat' sorgu parametresini uygulamada 'categoryId' adıyla kullanmak istiyoruz $router->addRoute('product ? id= & cat=', /* ... */); ``` -Foo Parametreleri .[#toc-foo-parameters] ----------------------------------------- +Foo Parametreleri +----------------- -Şimdi daha derine iniyoruz. Foo parametreleri temel olarak düzenli bir ifadeyle eşleşmeye izin veren isimsiz parametrelerdir. Aşağıdaki rota `/index`, `/index.html`, `/index.htm` ve `/index.php` ile eşleşir: +Şimdi daha derine iniyoruz. Foo parametreleri aslında düzenli bir ifadeyle eşleşmeyi sağlayan isimsiz parametrelerdir. Örnek olarak `/index`, `/index.html`, `/index.htm` ve `/index.php` kabul eden bir rota verilebilir: ```php $router->addRoute('index', /* ... */); ``` -URL oluşturmak için kullanılacak bir dizeyi açıkça tanımlamak da mümkündür. Dize, soru işaretinden hemen sonra yerleştirilmelidir. Aşağıdaki rota bir öncekine benzer, ancak `/index` yerine `/index.html` oluşturur, çünkü `.html` dizesi "oluşturulan değer" olarak ayarlanmıştır. +URL oluşturulurken kullanılacak dizeyi açıkça tanımlamak da mümkündür. Dize doğrudan soru işaretinden sonra yerleştirilmelidir. Aşağıdaki rota öncekine benzer, ancak `/index` yerine `/index.html` oluşturur, çünkü `.html` dizesi oluşturma değeri olarak ayarlanmıştır: ```php $router->addRoute('index', /* ... */); ``` -Entegrasyon .[#toc-integration] -=============================== +Uygulamaya Dahil Etme +===================== -Yönlendiricimizi uygulamaya bağlamak için DI konteynerine bunu söylemeliyiz. Bunun en kolay yolu router nesnesini oluşturacak fabrikayı hazırlamak ve container konfigürasyonuna bunu kullanmasını söylemektir. Diyelim ki bu amaçla bir metot yazdık `App\Router\RouterFactory::createRouter()`: +Oluşturulan yönlendiriciyi uygulamaya dahil etmek için DI konteynerine ondan bahsetmeliyiz. En kolay yol, yönlendirici nesnesini üretecek bir fabrika hazırlamak ve konteyner yapılandırmasında onu kullanmasını söylemektir. Bu amaçla `App\Core\RouterFactory::createRouter()` metodunu yazdığımızı varsayalım: ```php -namespace App\Router; +namespace App\Core; use Nette\Application\Routers\RouteList; @@ -472,14 +494,14 @@ class RouterFactory } ``` -Daha sonra [yapılandırmaya |dependency-injection:services] yazıyoruz: +[Yapılandırmaya |dependency-injection:services] şunu yazarız: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter ``` -Veritabanı bağlantısı vb. gibi tüm bağımlılıklar, [otomatik |dependency-injection:autowiring] bağlantı kullanılarak fabrika yöntemine parametreleri olarak aktarılır: +Veritabanı vb. gibi herhangi bir bağımlılık, fabrika metoduna parametreleri olarak [autowiring|dependency-injection:autowiring] kullanılarak iletilir: ```php public static function createRouter(Nette\Database\Connection $db): RouteList @@ -489,21 +511,21 @@ public static function createRouter(Nette\Database\Connection $db): RouteList ``` -SimpleRouter .[#toc-simplerouter] -================================= +SimpleRouter +============ -Route Collection'dan çok daha basit bir yönlendirici [SimpleRouter'dır |api:Nette\Application\Routers\SimpleRouter]. Belirli bir URL formatına ihtiyaç duyulmadığında, `mod_rewrite` (veya alternatifleri) mevcut olmadığında veya henüz kullanıcı dostu URL'lerle uğraşmak istemediğimizde kullanılabilir. +Rota koleksiyonundan çok daha basit bir yönlendirici [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]'dır. URL şekli konusunda özel gereksinimlerimiz olmadığında, `mod_rewrite` (veya alternatifleri) mevcut olmadığında veya henüz güzel URL'lerle uğraşmak istemediğimizde kullanırız. -Adresleri kabaca bu biçimde oluşturur: +Kabaca şu şekilde adresler üretir: ``` http://example.com/?presenter=Product&action=detail&id=123 ``` -`SimpleRouter` kurucusunun parametresi varsayılan bir sunumcu ve eylemdir, yani örneğin `http://example.com/` adresini ek parametreler olmadan açarsak yürütülecek eylemdir. +SimpleRouter'ın kurucu parametresi, parametresiz bir sayfa açtığımızda, örn. `http://example.com/`, yönlendirilmesi gereken varsayılan presenter ve eylemdir. ```php -// varsayılan olarak sunucu 'Anasayfa' ve eylem 'varsayılan' +// varsayılan presenter 'Home' ve eylem 'default' olacak $router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` @@ -515,22 +537,22 @@ services: ``` -SEO ve Kanonizasyon .[#toc-seo-and-canonization] -================================================ +SEO ve Kanonikleştirme +====================== -Çerçeve, farklı URL'lerde içeriğin tekrarlanmasını önleyerek SEO'yu (arama motoru optimizasyonu) artırır. Birden fazla adres aynı hedefe bağlanırsa, örneğin `/index` ve `/index.html`, çerçeve ilkini birincil (kanonik) olarak belirler ve diğerlerini HTTP kodu 301 kullanarak ona yönlendirir. Bu sayede arama motorları sayfaları iki kez indekslemeyecek ve sayfa sıralamalarını bozmayacaktır. . +Framework, farklı URL'lerde yinelenen içeriği önleyerek SEO'ya (arama motoru optimizasyonu) katkıda bulunur. Belirli bir hedefe birden fazla adres yönlendiriyorsa, örn. `/index` ve `/index.html`, framework bunlardan ilkini birincil (kanonik) olarak belirler ve diğerlerini HTTP kodu 301 ile ona yönlendirir. Bu sayede arama motorları sayfalarınızı iki kez indekslemez ve sayfa sıralamalarını seyreltmez. -Bu işlem kanonizasyon olarak adlandırılır. Kanonik URL, yönlendirici tarafından, yani OneWay bayrağı olmadan [koleksiyondaki |#route-collection] ilk eşleşen rota tarafından oluşturulan URL'dir. Bu nedenle, koleksiyonda önce **birincil rotaları** listeleriz. +Bu sürece kanonikleştirme denir. Kanonik URL, yönlendirici tarafından oluşturulan URL'dir, yani koleksiyondaki OneWay bayrağı olmayan ilk uygun rotadır. Bu nedenle koleksiyonda **birincil rotaları ilk olarak** belirtiriz. -Kanonizasyon, [kanonizasyon |presenters#Canonization] bölümünde daha çok sunum yapan kişi tarafından gerçekleştirilir. +Kanonikleştirme presenter tarafından yapılır, daha fazla bilgi [kanonikleştirme |presenters#Kanonikleştirme] bölümünde. -HTTPS .[#toc-https] -=================== +HTTPS +===== -HTTPS protokolünü kullanmak için, hosting üzerinde etkinleştirmek ve sunucuyu yapılandırmak gerekir. +HTTPS protokolünü kullanabilmek için barındırmada etkinleştirmek ve sunucuyu doğru şekilde yapılandırmak gerekir. -Tüm sitenin HTTPS'ye yeniden yönlendirilmesi sunucu düzeyinde, örneğin uygulamamızın kök dizinindeki .htaccess dosyası kullanılarak HTTP kodu 301 ile gerçekleştirilmelidir. Ayarlar barındırmaya bağlı olarak farklılık gösterebilir ve şuna benzer: +Tüm web sitesinin HTTPS'ye yönlendirilmesi sunucu düzeyinde ayarlanmalıdır, örneğin uygulamamızın kök dizinindeki .htaccess dosyası kullanılarak ve HTTP kodu 301 ile. Ayar barındırmaya göre değişebilir ve kabaca şöyle görünür: ``` @@ -542,40 +564,40 @@ Tüm sitenin HTTPS'ye yeniden yönlendirilmesi sunucu düzeyinde, örneğin uygu ``` -Yönlendirici, sayfanın yüklendiği protokolle aynı protokole sahip bir URL oluşturur, bu nedenle başka bir şey ayarlamaya gerek yoktur. +Yönlendirici, sayfanın yüklendiği protokolle aynı protokole sahip URL'ler üretir, bu yüzden başka bir şey ayarlamaya gerek yoktur. -Ancak, istisnai olarak farklı protokoller altında çalışmak için farklı rotalara ihtiyacımız varsa, bunu rota maskesine koyacağız: +Ancak istisnai olarak farklı rotaların farklı protokoller altında çalışması gerekiyorsa, bunu rota maskesinde belirtiriz: ```php -// Bir HTTP adresi oluşturacaktır +// HTTP ile bir adres üretecek $router->addRoute('http://%host%//', /* ... */); -// Bir HTTPS adresi oluşturacaktır +// HTTPS ile bir adres üretecek $router->addRoute('https://%host%//', /* ... */); ``` -Yönlendirici Hata Ayıklama .[#toc-debugging-router] -=================================================== +Yönlendirici Hata Ayıklaması +============================ -[Tracy Bar |tracy:] 'da görüntülenen yönlendirme çubuğu, rotaların bir listesini ve ayrıca yönlendiricinin URL'den elde ettiği parametreleri görüntüleyen kullanışlı bir araçtır. +[Tracy Bar |tracy:]'da görüntülenen yönlendirme paneli, rotaların listesini ve yönlendiricinin URL'den aldığı parametreleri gösteren yararlı bir yardımcıdır. -Sembolü ✓ olan yeşil çubuk mevcut URL ile eşleşen rotayı temsil eder, sembolü ≈ olan mavi çubuklar ise yeşil onları geçmeseydi URL ile eşleşecek rotaları gösterir. Mevcut sunucuyu ve eylemi daha fazla görüyoruz. +Yeşil çubuk ve ✓ sembolü, mevcut URL'yi işleyen rotayı temsil eder; mavi renk ve ≈ sembolü, yeşil olan onları geçmeseydi URL'yi de işleyecek olan rotaları gösterir. Ayrıca mevcut presenter ve eylemi de görürüz. [* routing-debugger.webp *] -Aynı zamanda, [kanonikleştirme |#SEO and Canonization] nedeniyle beklenmedik bir yönlendirme varsa, yönlendiricinin URL'yi başlangıçta nasıl anladığını ve neden yönlendirdiğini görmek için *redirect* çubuğuna bakmak yararlıdır. +Aynı zamanda, [kanonikleştirme |#SEO ve Kanonikleştirme] nedeniyle beklenmedik bir yönlendirme olursa, yönlendiricinin URL'yi başlangıçta nasıl anladığını ve neden yönlendirdiğini öğrenmek için *redirect* çubuğundaki panele bakmak yararlıdır. .[note] -Yönlendiricide hata ayıklarken, tarayıcıda Geliştirici Araçları'nı açmanız (Ctrl+Shift+I veya Cmd+Option+I) ve yönlendirmelerin depolanmaması için Ağ panelindeki önbelleği devre dışı bırakmanız önerilir. +Yönlendiriciyi hata ayıklarken, tarayıcıda Geliştirici Araçları'nı (Ctrl+Shift+I veya Cmd+Option+I) açmanızı ve Ağ panelinde önbelleği devre dışı bırakmanızı öneririz, böylece yönlendirmeler orada saklanmaz. -Performans .[#toc-performance] -============================== +Performans +========== -Rota sayısı yönlendiricinin hızını etkiler. Sayıları kesinlikle birkaç düzineyi geçmemelidir. Siteniz aşırı karmaşık bir URL yapısına sahipse, [özel |#custom router] bir yönlendirici yazabilirsiniz. +Rotaların sayısı yönlendiricinin hızını etkiler. Sayıları kesinlikle birkaç düzineyi geçmemelidir. Web sitenizin çok karmaşık bir URL yapısı varsa, özel bir [#Özel Yönlendirici] yazabilirsiniz. -Yönlendiricinin veritabanı gibi bir bağımlılığı yoksa ve fabrikasının argümanı yoksa, derlenmiş halini doğrudan bir DI konteynerine serileştirebilir ve böylece uygulamayı biraz daha hızlı hale getirebiliriz. +Yönlendiricinin örneğin veritabanı gibi herhangi bir bağımlılığı yoksa ve fabrikası herhangi bir argüman kabul etmiyorsa, derlenmiş formunu doğrudan DI konteynerine serileştirebilir ve böylece uygulamayı biraz hızlandırabiliriz. ```neon routing: @@ -583,10 +605,10 @@ routing: ``` -Özel Yönlendirici .[#toc-custom-router] -======================================= +Özel Yönlendirici +================= -Aşağıdaki satırlar çok ileri düzey kullanıcılar için tasarlanmıştır. Kendi yönlendiricinizi oluşturabilir ve doğal olarak rota koleksiyonunuza ekleyebilirsiniz. Yönlendirici, [api:Nette\Routing\Router] arayüzünün iki yöntemli bir uygulamasıdır: +Aşağıdaki satırlar çok ileri düzey kullanıcılar içindir. Kendi yönlendiricinizi oluşturabilir ve onu tamamen doğal bir şekilde rota koleksiyonuna dahil edebilirsiniz. Yönlendirici, iki metoda sahip [api:Nette\Routing\Router] arayüzünün bir uygulamasıdır: ```php use Nette\Http\IRequest as HttpRequest; @@ -606,8 +628,7 @@ class MyRouter implements Nette\Routing\Router } ``` -`match` yöntemi, yalnızca URL'nin değil, başlıkların vb. de alınabildiği geçerli [$httpRequest'i |http:request], sunum yapan kişinin adını ve parametrelerini içeren bir diziye dönüştürür. Eğer isteği işleyemezse null döndürür. -İsteği işlerken, en azından sunum yapan kişiyi ve eylemi döndürmeliyiz. Sunucu adı eksiksizdir ve tüm modülleri içerir: +`match` metodu, yalnızca URL'yi değil, aynı zamanda başlıkları vb. de alabileceğiniz mevcut isteği [$httpRequest |http:request] işler ve presenter adını ve parametrelerini içeren bir diziye dönüştürür. İsteği işleyemezse, null döndürür. İsteği işlerken en azından presenter ve eylemi döndürmeliyiz. Presenter adı tamdır ve olası modülleri de içerir: ```php [ @@ -616,9 +637,9 @@ class MyRouter implements Nette\Routing\Router ] ``` -Öte yandan `constructUrl` yöntemi, parametre dizisinden mutlak bir URL oluşturur. Geçerli URL olan `$refUrl` parametresindeki bilgileri kullanabilir. +`constructUrl` metodu ise tam tersine, parametreler dizisinden sonuçta ortaya çıkan mutlak URL'yi oluşturur. Bunun için mevcut URL olan [`$refUrl`|api:Nette\Http\UrlScript] parametresindeki bilgileri kullanabilir. -Rota koleksiyonuna özel yönlendirici eklemek için `add()` adresini kullanın: +`add()` kullanarak rota koleksiyonuna eklersiniz: ```php $router = new Nette\Application\Routers\RouteList; @@ -628,19 +649,19 @@ $router->addRoute(/* ... */); ``` -Ayrılmış Kullanım .[#toc-separated-usage] -========================================= +Bağımsız Kullanım +================= -Ayrı kullanımla, yönlendiricinin yeteneklerinin Nette Uygulaması ve sunucuları kullanmayan bir uygulamada kullanılmasını kastediyoruz. Bu bölümde gösterdiğimiz hemen hemen her şey, aşağıdaki farklarla birlikte bu uygulama için de geçerlidir: +Bağımsız kullanım derken, Nette Application ve presenter'ları kullanmayan bir uygulamada yönlendiricinin yeteneklerini kullanmayı kastediyoruz. Bu bölümde gösterdiğimiz hemen hemen her şey onun için geçerlidir, şu farklılıklarla: -- sınıfını kullandığımız rota koleksiyonları için [api:Nette\Routing\RouteList] -- basit bir yönlendirici sınıfı olarak [api:Nette\Routing\SimpleRouter] -- `Presenter:action` çifti olmadığı için [Gelişmiş gösterimini kullanıyoruz|#Advanced notation] +- rota koleksiyonları için [api:Nette\Routing\RouteList] sınıfını kullanırız +- basit yönlendirici olarak [api:Nette\Routing\SimpleRouter] sınıfını kullanırız +- `Presenter:eylem` çifti olmadığı için, [#Genişletilmiş Gösterim] kullanırız -Bu yüzden yine örneğin bir yönlendirici oluşturacak bir yöntem oluşturacağız: +Yani yine bize yönlendiriciyi oluşturacak bir metot oluştururuz, örn.: ```php -namespace App\Router; +namespace App\Core; use Nette\Routing\RouteList; @@ -661,35 +682,35 @@ class RouterFactory } ``` -Önerdiğimiz gibi bir DI konteyneri kullanıyorsanız, yöntemi yapılandırmaya tekrar ekleyin ve ardından yönlendiriciyi konteynerden HTTP isteği ile birlikte alın: +DI konteyneri kullanıyorsanız, ki bunu öneririz, metodu tekrar yapılandırmaya ekleriz ve ardından yönlendiriciyi HTTP isteğiyle birlikte konteynerden alırız: ```php $router = $container->getByType(Nette\Routing\Router::class); $httpRequest = $container->getByType(Nette\Http\IRequest::class); ``` -Ya da doğrudan nesneler oluşturacağız: +Veya nesneleri doğrudan üretiriz: ```php -$router = App\Router\RouterFactory::createRouter(); +$router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` -Şimdi yönlendiricinin çalışmasına izin vermeliyiz: +Şimdi geriye sadece yönlendiriciyi işe koymak kalıyor: ```php $params = $router->match($httpRequest); if ($params === null) { - // eşleşen rota bulunamadı, 404 hatası göndereceğiz + // uygun bir rota bulunamadı, 404 hatası göndeririz exit; } -// alınan parametreleri işliyoruz +// alınan parametreleri işleriz $controller = $params['controller']; // ... ``` -Ve tam tersi, bağlantıyı oluşturmak için yönlendiriciyi kullanacağız: +Ve tersine, bir bağlantı oluşturmak için yönlendiriciyi kullanırız: ```php $params = ['controller' => 'ArticleController', 'id' => 123]; diff --git a/application/tr/templates.texy b/application/tr/templates.texy index 42aadf4b20..b335c961a8 100644 --- a/application/tr/templates.texy +++ b/application/tr/templates.texy @@ -2,15 +2,15 @@ ********* .[perex] -Nette [Latte |latte:] şablon sistemini kullanır. Latte, PHP için en güvenli şablon sistemi ve aynı zamanda en sezgisel sistem olduğu için kullanılır. Yeni bir şey öğrenmenize gerek yok, sadece PHP ve birkaç Latte etiketi bilmeniz yeterli. +Nette, [Latte |latte:] şablonlama sistemini kullanır. Bunun nedeni, hem PHP için en güvenli şablonlama sistemi olması hem de en sezgisel sistem olmasıdır. Çok fazla yeni şey öğrenmenize gerek yoktur, PHP bilginiz ve birkaç etiket yeterlidir. -Sayfanın düzen şablonu + eylem şablonundan tamamlanması olağandır. Bir düzen şablonu böyle görünebilir, `{block}` bloklarına ve `{include}` etiketine dikkat edin: +Bir sayfanın bir layout şablonu + ilgili eylemin şablonundan oluşması yaygındır. Örneğin bir layout şablonu şöyle görünebilir, `{block}` bloklarına ve `{include}` etiketine dikkat edin: ```latte - {block title}My App{/block} + {block title}Uygulamam{/block}
    ...
    @@ -20,61 +20,109 @@ Sayfanın düzen şablonu + eylem şablonundan tamamlanması olağandır. Bir d ``` -Bu da eylem şablonu olabilir: +Ve bu da eylemin şablonu olacaktır: ```latte -{block title}Homepage{/block} +{block title}Ana Sayfa{/block} {block content} -

    Homepage

    +

    Ana Sayfa

    ... {/block} ``` -Düzende `{include content}` yerine eklenen `content` bloğunu tanımlar ve ayrıca düzende `{block title}` 'un üzerine yazılan `title` bloğunu yeniden tanımlar. Sonucu hayal etmeye çalışın. +Bu, layout'taki `{include content}` yerine eklenecek olan `content` bloğunu tanımlar ve ayrıca layout'taki `{block title}` öğesinin üzerine yazacak olan `title` bloğunu yeniden tanımlar. Sonucu hayal etmeye çalışın. -Şablon Arama .[#toc-search-for-templates] ------------------------------------------ +Şablonları Bulma +---------------- -Şablonların yolu basit bir mantığa göre çıkarılır. Sunucu sınıfının bulunduğu dizine göre bu şablon dosyalarından birinin var olup olmadığına bakılır, burada `` geçerli sunum yapan kişinin adı ve `` geçerli eylemin adıdır: +Presenter'larda hangi şablonun oluşturulacağını belirtmeniz gerekmez, framework yolu kendisi türetir ve size yazmaktan tasarruf sağlar. -- `templates//.latte` -- `templates/..latte` +Her presenter'ın kendi dizinine sahip olduğu bir dizin yapısı kullanıyorsanız, şablonu basitçe bu dizine eylemin (veya view'in) adıyla yerleştirin, yani `default` eylemi için `default.latte` şablonunu kullanın: -Şablonu bulamazsa, yanıt [404 hatası |presenters#Error 404 etc.] olur. +/--pre +app/ +└── Presentation/ + └── Home/ + ├── HomePresenter.php + └── default.latte +\-- -Ayrıca `$this->setView('otherView')` adresini kullanarak görünümü değiştirebilirsiniz. Ya da arama yapmak yerine `$this->template->setFile('/path/to/template.latte')` adresini kullanarak şablon dosyasının adını doğrudan belirtin. +Presenter'ların birlikte tek bir dizinde ve şablonların `templates` klasöründe olduğu bir yapı kullanıyorsanız, onu ya `..latte` dosyasına ya da `/.latte` dosyasına kaydedin: + +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── Home.default.latte ← 1. seçenek + └── Home/ + └── default.latte ← 2. seçenek +\-- + +`templates` dizini bir seviye yukarıda da yer alabilir, yani presenter sınıflarını içeren dizinle aynı seviyede. + +Şablon bulunamazsa, presenter [404 - sayfa bulunamadı hatası |presenters#Hata 404 ve Benzerleri] ile yanıt verir. + +View'i `$this->setView('jineView')` kullanarak değiştirirsiniz. Ayrıca şablon dosyasını doğrudan `$this->template->setFile('/path/to/template.latte')` kullanarak belirtebilirsiniz. .[note] -Olası dosya yollarından oluşan bir dizi döndüren [formatTemplateFiles |api:Nette\Application\UI\Presenter::formatTemplateFiles()] yöntemini geçersiz kılarak şablonların arandığı yolları değiştirebilirsiniz. +Şablonların arandığı dosyalar, olası dosya adları dizisini döndüren [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()] metodunu geçersiz kılarak değiştirilebilir. + + +Layout Şablonunu Bulma +---------------------- + +Nette ayrıca layout dosyasını otomatik olarak bulur. + +Her presenter'ın kendi dizinine sahip olduğu bir dizin yapısı kullanıyorsanız, layout'u ya yalnızca ona özelse presenter içeren klasöre ya da birden fazla presenter için ortaksa bir seviye yukarıya yerleştirin: + +/--pre +app/ +└── Presentation/ + ├── @layout.latte ← ortak layout + └── Home/ + ├── @layout.latte ← yalnızca Home presenter için + ├── HomePresenter.php + └── default.latte +\-- + +Presenter'ların birlikte tek bir dizinde ve şablonların `templates` klasöründe olduğu bir yapı kullanıyorsanız, layout şu yerlerde beklenecektir: -Düzen aşağıdaki dosyalarda beklenmektedir: +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── @layout.latte ← ortak layout + ├── Home.@layout.latte ← yalnızca Home için, 1. seçenek + └── Home/ + └── @layout.latte ← yalnızca Home için, 2. seçenek +\-- -- `templates//@.latte` -- `templates/.@.latte` -- `templates/@.latte` birden fazla sunumcu için ortak düzen +Presenter bir modülde bulunuyorsa, modülün iç içe geçme durumuna göre daha üst dizin seviyelerinde de aranacaktır. -`` geçerli sunum yapan kişinin adı ve `` varsayılan olarak `'layout'` olan düzenin adıdır. İsim `$this->setLayout('otherLayout')` ile değiştirilebilir, böylece `@otherLayout.latte` dosyaları denenecektir. +Layout adı `$this->setLayout('layoutAdmin')` kullanılarak değiştirilebilir ve ardından `@layoutAdmin.latte` dosyasında beklenecektir. Ayrıca layout şablonu dosyasını doğrudan `$this->setLayout('/path/to/template.latte')` kullanarak belirtebilirsiniz. -Düzen şablonunun dosya adını `$this->setLayout('/path/to/template.latte')` adresini kullanarak doğrudan da belirtebilirsiniz. `$this->setLayout(false)` adresini kullanmak düzen aramayı devre dışı bırakacaktır. +`$this->setLayout(false)` veya şablon içindeki `{layout none}` etiketi kullanılarak layout araması kapatılır. .[note] -Olası dosya yollarından oluşan bir dizi döndüren [formatLayoutTemplateFiles |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()] yöntemini geçersiz kılarak şablonların arandığı yolları değiştirebilirsiniz. +Layout şablonlarının arandığı dosyalar, olası dosya adları dizisini döndüren [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()] metodunu geçersiz kılarak değiştirilebilir. -Şablondaki Değişkenler .[#toc-variables-in-the-template] --------------------------------------------------------- +Şablondaki Değişkenler +---------------------- -Değişkenler `$this->template` adresine yazılarak şablona aktarılır ve daha sonra yerel değişkenler olarak şablonda kullanılabilir: +Değişkenleri şablona `$this->template`'e yazarak iletiriz ve ardından şablonda yerel değişkenler olarak kullanılabilirler: ```php $this->template->article = $this->articles->getById($id); ``` -Bu şekilde herhangi bir değişkeni şablonlara kolayca aktarabiliriz. Ancak, sağlam uygulamalar geliştirirken, kendimizi sınırlamak genellikle daha yararlıdır. Örneğin, şablonun beklediği değişkenlerin ve türlerinin bir listesini açıkça tanımlayarak. Bu, PHP'nin tip kontrolü yapmasına, IDE'nin doğru şekilde otomatik tamamlama yapmasına ve statik analizin hataları tespit etmesine olanak tanıyacaktır. +Bu şekilde herhangi bir değişkeni şablonlara kolayca iletebiliriz. Ancak sağlam uygulamalar geliştirirken kendimizi sınırlamak daha yararlı olur. Örneğin, şablonun beklediği değişkenlerin listesini ve türlerini açıkça tanımlayarak. Bu sayede PHP türleri kontrol edebilir, IDE doğru şekilde öneride bulunabilir ve statik analiz hataları ortaya çıkarabilir. -Peki böyle bir numaralandırmayı nasıl tanımlarız? Basitçe bir sınıf ve onun özellikleri şeklinde. Bunu presenter'a benzer şekilde adlandırıyoruz, ancak sonunda `Template` var: +Ve böyle bir listeyi nasıl tanımlarız? Basitçe bir sınıf ve onun özellikleri şeklinde. Onu presenter'a benzer şekilde adlandırırız, ancak sonunda `Template` ile: ```php /** @@ -93,22 +141,22 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template } ``` -Sunucudaki `$this->template` nesnesi artık `ArticleTemplate` sınıfının bir örneği olacaktır. Böylece PHP, yazıldıklarında bildirilen türleri kontrol edecektir. PHP 8.2'den başlayarak, varolmayan bir değişkene yazma konusunda da uyaracaktır, önceki sürümlerde aynı şey [Nette\SmartObject |utils:smartobject] özelliği kullanılarak elde edilebilir. +Presenter'daki `$this->template` nesnesi artık `ArticleTemplate` sınıfının bir örneği olacaktır. Böylece PHP yazarken bildirilen türleri kontrol edecektir. Ve PHP 8.2 sürümünden itibaren, mevcut olmayan bir değişkene yazma konusunda da uyaracaktır, önceki sürümlerde aynı şeye [Nette\SmartObject |utils:smartobject] trait'ini kullanarak ulaşılabilir. -`@property-read` ek açıklaması IDE ve statik analiz içindir, otomatik tamamlamanın çalışmasını sağlar, "PhpStorm ve $this->template için kod tamamlama":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template bölümüne bakın. +`@property-read` ek açıklaması IDE ve statik analiz içindir, sayesinde öneri işlevi çalışacaktır, bkz. "PhpStorm and code completion for $this⁠-⁠>⁠template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. [* phpstorm-completion.webp *] -Şablonlarda da fısıldama lüksünün tadını çıkarabilirsiniz, sadece PhpStorm'da Latte eklentisini kurun ve şablonun başında sınıf adını belirtin, "Latte: sistem nasıl yazılır":https://blog.nette.org/tr/latte-tip-sistemi-nasil-kullanilir makalesine bakın: +Öneri lüksünün tadını şablonlarda da çıkarabilirsiniz, sadece PhpStorm'a Latte eklentisini kurmanız ve şablonun başına sınıf adını belirtmeniz yeterlidir, daha fazla bilgi için "Latte: tür sistemi nasıl kullanılır":https://blog.nette.org/tr/latte-how-to-use-type-system makalesine bakın: ```latte -{templateType App\Presenters\ArticleTemplate} +{templateType App\Presentation\Article\ArticleTemplate} ... ``` -Şablonlar bileşenlerde de bu şekilde çalışır, sadece adlandırma kuralını takip edin ve bileşen için bir şablon sınıfı `FifteenTemplate` oluşturun, örneğin `FifteenControl`. +Bu şekilde bileşenlerdeki şablonlar da çalışır, sadece adlandırma kuralına uymanız ve örneğin `FifteenControl` bileşeni için `FifteenTemplate` şablon sınıfını oluşturmanız yeterlidir. -Başka bir sınıfın örneği olarak bir `$template` oluşturmanız gerekiyorsa, `createTemplate()` yöntemini kullanın: +`$template`'i farklı bir sınıfın örneği olarak oluşturmanız gerekiyorsa, `createTemplate()` metodunu kullanın: ```php public function renderDefault(): void @@ -121,60 +169,60 @@ public function renderDefault(): void ``` -Varsayılan Değişkenler .[#toc-default-variables] ------------------------------------------------- +Varsayılan Değişkenler +---------------------- -Sunucular ve bileşenler çeşitli faydalı değişkenleri şablonlara otomatik olarak aktarır: +Presenter'lar ve bileşenler, şablonlara otomatik olarak birkaç yararlı değişken iletir: -- `$basePath` kök dizine giden mutlak bir URL yoludur (örneğin `/CD-collection`) -- `$baseUrl` kök dizine mutlak bir URL'dir (örneğin `http://localhost/CD-collection`) -- `$user` [kullanıcıyı temsil eden bir nesnedir|security:authentication] -- `$presenter` şu anki sunucu -- `$control` geçerli bileşen veya sunum yapan kişidir -- `$flashes` yöntem tarafından gönderilen [mesajların |presenters#flash-messages] listesi `flashMessage()` +- `$basePath`, kök dizine mutlak URL yoludur (örn. `/eshop`) +- `$baseUrl`, kök dizine mutlak URL'dir (örn. `http://localhost/eshop`) +- `$user`, [kullanıcıyı temsil eden |security:authentication] nesnedir +- `$presenter`, mevcut presenter'dır +- `$control`, mevcut bileşen veya presenter'dır +- `$flashes`, `flashMessage()` fonksiyonu tarafından gönderilen [mesajlar |presenters#Flash Mesajları] dizisidir -Özel bir şablon sınıfı kullanıyorsanız, bu değişkenler için bir özellik oluşturduğunuzda bu değişkenler geçirilir. +Kendi şablon sınıfınızı kullanıyorsanız, bu değişkenler için bir özellik oluşturursanız iletilirler. -Bağlantı Oluşturma .[#toc-creating-links] ------------------------------------------ +Bağlantı Oluşturma +------------------ -Şablonda diğer sunumculara ve eylemlere aşağıdaki gibi bağlantılar oluşturuyoruz: +Şablonda, diğer presenter'lara ve eylemlere bağlantılar şu şekilde oluşturulur: ```latte -detail +ürün detayı ``` -Öznitelik `n:href` HTML etiketleri için çok kullanışlıdır ``. Bağlantıyı başka bir yere, örneğin metnin içine yazdırmak istiyorsak `{link}` adresini kullanırız: +`n:href` niteliği HTML `` etiketleri için çok kullanışlıdır. Bağlantıyı başka bir yerde, örneğin metinde yazdırmak istiyorsak, `{link}` kullanırız: ```latte -URL is: {link Home:default} +Adres: {link Home:default} ``` -Daha fazla bilgi için [Bağlantı Oluşturma |Creating Links] bölümüne bakın. +Daha fazla bilgi için [URL Bağlantıları Oluşturma|creating-links] bölümünde bulabilirsiniz. -Özel Filtreler, Etiketler vb. .[#toc-custom-filters-tags-etc] -------------------------------------------------------------- +Özel Filtreler, Etiketler vb. +----------------------------- -Latte şablonlama sistemi özel filtreler, fonksiyonlar, etiketler vb. ile genişletilebilir. Bu işlem doğrudan `render` veya `beforeRender()` yöntemini kullanabilirsiniz: +Latte şablonlama sistemi özel filtreler, fonksiyonlar, etiketler vb. ile genişletilebilir. Bu, doğrudan `render` veya `beforeRender()` metodunda yapılabilir: ```php public function beforeRender(): void { - // bir filtre ekleme + // filtre ekleme $this->template->addFilter('foo', /* ... */); - // veya Latte\Engine nesnesini doğrudan yapılandırın + // veya doğrudan Latte\Engine nesnesini yapılandırırız $latte = $this->template->getLatte(); $latte->addFilterLoader(/* ... */); } ``` -Latte sürüm 3, her web projesi için bir [uzantı |latte:creating-extension] oluşturarak daha gelişmiş bir yol sunar. İşte böyle bir sınıfın kabaca bir örneği: +Latte sürüm 3, her web projesi için bir [extension |latte:extending-latte#Latte Extension] oluşturarak daha gelişmiş bir yol sunar. Böyle bir sınıfın kaba bir örneği: ```php -namespace App\Templating; +namespace App\Presentation\Accessory; final class LatteExtension extends Latte\Extension { @@ -207,22 +255,21 @@ final class LatteExtension extends Latte\Extension } ``` -[configuration |configuration#Latte] kullanarak kaydediyoruz: +Onu [yapılandırma |configuration#Latte Şablonları] kullanarak kaydederiz: ```neon latte: extensions: - - App\Templating\LatteExtension + - App\Presentation\Accessory\LatteExtension ``` -Çeviri .[#toc-translating] --------------------------- +Çeviri +------ -Çok dilli bir uygulama programlıyorsanız, muhtemelen şablondaki bazı metinlerin çıktısını farklı dillerde almanız gerekecektir. Bunu yapmak için Nette Framework, `translate()` tek bir yöntemi olan bir çeviri arayüzü [api:Nette\Localization\Translator] tanımlar. Bu, genellikle bir dize olan `$message` mesajını ve diğer parametreleri kabul eder. Görev, çevrilmiş dizeyi döndürmektir. -Nette'de varsayılan bir uygulama yoktur, [Componette |https://componette.org/search/localization]'de bulunabilecek çeşitli hazır çözümler arasından ihtiyaçlarınıza göre seçim yapabilirsiniz. Belgeleri size çevirmeni nasıl yapılandıracağınızı anlatır. +Çok dilli bir uygulama programlıyorsanız, muhtemelen şablondaki bazı metinleri farklı dillerde yazdırmanız gerekecektir. Nette Framework bu amaçla tek bir metodu `translate()` olan [api:Nette\Localization\Translator] çeviri arayüzünü tanımlar. Bu metot, genellikle bir dize olan `$message` mesajını ve isteğe bağlı diğer parametreleri alır. Görevi, çevrilmiş dizeyi döndürmektir. Nette'de varsayılan bir uygulama yoktur, ihtiyaçlarınıza göre [Componette |https://componette.org/search/localization] adresinde bulabileceğiniz birkaç hazır çözüm arasından seçim yapabilirsiniz. Belgelerinde çevirmeni nasıl yapılandıracağınızı öğreneceksiniz. -Şablonlar, `setTranslator()` yöntemi kullanılarak [bize iletilecek |dependency-injection:passing-dependencies] bir çevirmen ile kurulabilir: +Şablonlara, `setTranslator()` metoduyla [bize iletilmesini istediğimiz |dependency-injection:passing-dependencies] bir çevirmen ayarlanabilir: ```php protected function beforeRender(): void @@ -232,38 +279,38 @@ protected function beforeRender(): void } ``` -Alternatif olarak, çevirmen [yapılandırma |configuration#Latte] kullanılarak ayarlanabilir: +Çevirmen alternatif olarak [yapılandırma |configuration#Latte Şablonları] kullanılarak ayarlanabilir: ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -Çevirmen daha sonra örneğin `|translate` filtresi olarak kullanılabilir ve ek parametreler `translate()` yöntemine aktarılabilir (bkz. `foo, bar`): +Ardından çevirmen örneğin `|translate` filtresi olarak kullanılabilir ve `translate()` metoduna iletilen ek parametreler dahil (bkz. `foo, bar`): ```latte -{='Basket'|translate} +{='Sepet'|translate} {$item|translate} {$item|translate, foo, bar} ``` -Veya bir alt çizgi etiketi olarak: +Veya alt çizgi etiketi olarak: ```latte -{_'Basket'} +{_'Sepet'} {_$item} {_$item, foo, bar} ``` -Şablon bölüm çevirisi için eşleştirilmiş bir `{translate}` etiketi vardır (Latte 2.11'den beri, daha önce `{_}` etiketi kullanılıyordu): +Şablonun bir bölümünü çevirmek için eşli `{translate}` etiketi vardır (Latte 2.11'den itibaren, daha önce `{_}` etiketi kullanılırdı): ```latte -{translate}Order{/translate} -{translate foo, bar}Order{/translate} +{translate}Sipariş{/translate} +{translate foo, bar}Sipariş{/translate} ``` -Çevirmen, şablon oluşturulurken varsayılan olarak çalışma zamanında çağrılır. Ancak Latte sürüm 3, şablon derleme sırasında tüm statik metni çevirebilir. Bu performans tasarrufu sağlar çünkü her dize yalnızca bir kez çevrilir ve sonuçta ortaya çıkan çeviri derlenmiş forma yazılır. Bu, önbellek dizininde şablonun her dil için bir tane olmak üzere birden fazla derlenmiş sürümünü oluşturur. Bunu yapmak için yalnızca ikinci parametre olarak dili belirtmeniz gerekir: +Çevirmen standart olarak şablon oluşturulurken çalışma zamanında çağrılır. Ancak Latte sürüm 3, tüm statik metinleri şablon derlemesi sırasında çevirebilir. Bu, her dize yalnızca bir kez çevrildiği ve sonuçtaki çeviri derlenmiş forma yazıldığı için performanstan tasarruf sağlar. Önbellek dizininde, her dil için bir tane olmak üzere şablonun birden fazla derlenmiş sürümü oluşturulur. Bunun için dili ikinci parametre olarak belirtmek yeterlidir: ```php protected function beforeRender(): void @@ -273,4 +320,4 @@ protected function beforeRender(): void } ``` -Statik metin ile örneğin `{_'hello'}` veya `{translate}hello{/translate}` kastedilmektedir. `{_$foo}` gibi statik olmayan metinler anında derlenmeye devam edecektir. +Statik metin derken örneğin `{_'merhaba'}` veya `{translate}merhaba{/translate}` kastedilir. `{_$foo}` gibi statik olmayan metinler çalışma zamanında çevrilmeye devam edecektir. diff --git a/application/uk/@home.texy b/application/uk/@home.texy index c1bd9272d7..5627e56f5d 100644 --- a/application/uk/@home.texy +++ b/application/uk/@home.texy @@ -2,35 +2,84 @@ Nette Application ***************** .[perex] -Пакет `nette/application` є основою для створення інтерактивних веб-додатків. - -- [Як працюють додатки? |how-it-works] -- [Bootstrap |Bootstrap] -- [Презентери |presenters] -- [Шаблони |templates] -- [Модулі |modules] -- [Маршрутизація |routing] -- [Створення URL-посилань |creating-links] -- [Інтерактивні компоненти |components] -- [AJAX і сніпети |ajax] -- [Multiplier |Multiplier] -- [Конфігурація |configuration] +Nette Application є ядром фреймворку Nette, яке надає потужні інструменти для створення сучасних веб-застосунків. Воно пропонує низку виняткових функцій, які значно полегшують розробку та покращують безпеку й підтримуваність коду. Встановлення ------------ -Завантажте та встановіть пакет за допомогою [Composer |best-practices:composer]: +Бібліотеку можна завантажити та встановити за допомогою інструменту [Composer|best-practices:composer]: ```shell composer require nette/application ``` -| версія пакета | сумісна версія PHP -|-----------------------|----------------------- -| Nette Application 4.0 | PHP 8.0 - 8.1 -| Nette Application 3.1 | PHP 7.2 - 8.1 -| Nette Application 3.0 | PHP 7.1 - 8.0 -| Nette Application 2.4 | PHP 5.6 - 8.0 -Застосовується до останніх версій патчів. +Чому варто обрати Nette Application? +------------------------------------ + +Nette завжди був піонером у галузі веб-технологій. + +**Двосторонній роутер:** Nette має вдосконалену систему маршрутизації, яка є унікальною завдяки своїй двосторонності — вона не тільки перетворює URL-адреси на дії застосунку, але й може генерувати URL-адреси у зворотному напрямку. Це означає, що: +- Ви можете будь-коли змінити структуру URL-адрес усього застосунку без необхідності редагувати шаблони +- URL-адреси автоматично канонізуються, що покращує SEO +- Маршрутизація визначається в одному місці, а не розкидана по анотаціях + +**Компоненти та сигнали:** Вбудована система компонентів, натхненна Delphi та React.js, є абсолютно унікальною серед PHP-фреймворків: +- Дозволяє створювати багаторазові UI-елементи +- Підтримує ієрархічне складання компонентів +- Пропонує елегантну обробку AJAX-запитів за допомогою сигналів +- Багата бібліотека готових компонентів на [Componette](https://componette.org) + +**AJAX та сніпети:** Nette представив революційний спосіб роботи з AJAX ще у 2009 році, задовго до появи подібних рішень, таких як Hotwire для Ruby on Rails або Symfony UX Turbo: +- Сніпети дозволяють оновлювати лише частини сторінки без необхідності писати JavaScript +- Автоматична інтеграція з компонентною системою +- Розумна інвалідація частин сторінок +- Мінімальна кількість переданих даних + +**Інтуїтивні шаблони [Latte|latte:]:** Найбезпечніша система шаблонів для PHP з розширеними функціями: +- Автоматичний захист від XSS за допомогою контекстно-залежного екранування +- Розширюваність за допомогою власних фільтрів, функцій та тегів +- Спадкування шаблонів та сніпети для AJAX +- Відмінна підтримка PHP 8.x з системою типів + +**Dependency Injection:** Nette повністю використовує Dependency Injection: +- Автоматична передача залежностей (autowiring) +- Конфігурація за допомогою зрозумілого формату NEON +- Підтримка фабрик для компонентів + + +Основні переваги +---------------- + +- **Безпека**: Автоматичний захист від [вразливостей|nette:vulnerability-protection], таких як XSS, CSRF тощо. +- **Продуктивність**: Менше коду, більше функцій завдяки розумному дизайну +- **Налагодження**: [Tracy debugger|tracy:] з панеллю маршрутизації +- **Швидкодія**: Розумний кеш, ліниве завантаження компонентів +- **Гнучкість**: Легка зміна URL-адрес навіть після завершення розробки застосунку +- **Компоненти**: Унікальна система багаторазових UI-елементів +- **Сучасність**: Повна підтримка PHP 8.4+ та системи типів + + +Починаємо +--------- + +1. [Як працюють застосунки? |how-it-works] - Розуміння базової архітектури +2. [Presenters |presenters] - Робота з презентерами та діями +3. [Шаблони |templates] - Створення шаблонів у Latte +4. [Маршрутизація |routing] - Конфігурація URL-адрес +5. [Інтерактивні компоненти |components] - Використання компонентної системи + + +Сумісність з PHP +---------------- + +| версія | сумісна з PHP +|-----------|------------------- +| Nette Application 4.0 | PHP 8.1 – 8.4 +| Nette Application 3.2 | PHP 8.1 – 8.4 +| Nette Application 3.1 | PHP 7.2 – 8.3 +| Nette Application 3.0 | PHP 7.1 – 8.0 +| Nette Application 2.4 | PHP 5.6 – 8.0 + +Застосовується до останньої версії патчу. diff --git a/application/uk/@left-menu.texy b/application/uk/@left-menu.texy index dfefb7aa58..5ad8904c1a 100644 --- a/application/uk/@left-menu.texy +++ b/application/uk/@left-menu.texy @@ -1,19 +1,22 @@ -Додаток Nette -************* -- [Як працюють додатки? |how-it-works] -- [Bootstrap |Bootstrap] -- [Презентери |presenters] +Nette Application +***************** +- [Як працюють застосунки? |how-it-works] +- [Bootstrapping] +- [Presenters |presenters] - [Шаблони |templates] -- [Модулі |modules] +- [Структура каталогів |directory-structure] - [Маршрутизація |routing] -- [Створення URL-посилань |creating-links] +- [Створення посилань URL |creating-links] - [Інтерактивні компоненти |components] -- [AJAX і сніпети |ajax] +- [AJAX & сніпети |ajax] - [Multiplier |Multiplier] - [Конфігурація |configuration] -Подальше читання -**************** -- [Кращі практики |best-practices:] -- [Усунення неполадок |nette:troubleshooting] +Додаткове читання +***************** +- [Чому варто використовувати Nette? |www:10-reasons-why-nette] +- [Встановлення |nette:installation] +- [Пишемо перший застосунок! |quickstart:] +- [Посібники та практики |best-practices:] +- [Вирішення проблем |nette:troubleshooting] diff --git a/application/uk/@meta.texy b/application/uk/@meta.texy new file mode 100644 index 0000000000..96e2d9752a --- /dev/null +++ b/application/uk/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документація Nette}} diff --git a/application/uk/ajax.texy b/application/uk/ajax.texy index a0f7c17300..f07569adaf 100644 --- a/application/uk/ajax.texy +++ b/application/uk/ajax.texy @@ -1,38 +1,45 @@ -AJAX і сніпети +AJAX & сніпети **************
    -Сучасні веб-додатки сьогодні працюють наполовину на сервері, а наполовину в браузері. AJAX є життєво важливим об'єднуючим фактором. Яку підтримку пропонує фреймворк Nette? -- надсилання фрагментів шаблонів (так званих *сніпетів*) +В епоху сучасних веб-застосунків, де функціональність часто розподілена між сервером і браузером, AJAX є необхідним сполучним елементом. Які можливості пропонує нам Nette Framework у цій галузі? +- надсилання частин шаблону, так званих сніпетів - передача змінних між PHP і JavaScript -- Налагодження додатків AJAX +- інструменти для налагодження AJAX-запитів
    -AJAX-запит може бути виявлений за допомогою методу сервісу [інкапсуляція HTTP-запиту |http:request] `$httpRequest->isAjax()` (визначає на основі HTTP-заголовка `X-Requested-With`). Існує також скорочений метод у презентері: `$this->isAjax()`. -AJAX-запит нічим не відрізняється від звичайного - викликається презентер з певним поданням і параметрами. Від презентера також залежить, як він відреагує: він може використати свої процедури для повернення фрагмента HTML-коду (сніпету), XML-документа, об'єкта JSON або фрагмента коду Javascript. +AJAX-запит +========== -Існує попередньо оброблений об'єкт `payload`, призначений для надсилання даних у браузер у форматі JSON. +AJAX-запит, по суті, не відрізняється від класичного HTTP-запиту. Викликається presenter із певними параметрами. І від presenter'а залежить, як він реагуватиме на запит - він може повернути дані у форматі JSON, надіслати частину HTML-коду, XML-документ тощо. -```php -public function actionDelete(int $id): void -{ - if ($this->isAjax()) { - $this->payload->message = 'Успішно'; - } - // ... -} +На стороні браузера ми ініціюємо AJAX-запит за допомогою функції `fetch()`: + +```js +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) +.then(response => response.json()) +.then(payload => { + // обробка відповіді +}); ``` -Для повного контролю над виведенням JSON використовуйте метод `sendJson` у презентері. Це негайно перерве роботу презентера, і ви обійдетеся без шаблону: +На стороні сервера ми розпізнаємо AJAX-запит за допомогою методу `$httpRequest->isAjax()` сервісу [що інкапсулює HTTP-запит |http:request]. Для виявлення він використовує HTTP-заголовок `X-Requested-With`, тому важливо його надсилати. У presenter'і можна використовувати метод `$this->isAjax()`. + +Якщо ви хочете надіслати дані у форматі JSON, використовуйте метод [`sendJson()` |presenters#Надсилання відповіді]. Метод також завершує роботу presenter'а. ```php -$this->sendJson(['key' => 'value', /* ... */]); +public function actionExport(): void +{ + $this->sendJson($this->model->getData); +} ``` -Якщо ми хочемо надіслати HTML, ми можемо встановити спеціальний шаблон для AJAX-запитів: +Якщо ви плануєте відповісти за допомогою спеціального шаблону, призначеного для AJAX, ви можете зробити це так: ```php public function handleClick($param): void @@ -45,222 +52,198 @@ public function handleClick($param): void ``` -Naja .[#toc-naja] -================= +Сніпети +======= + +Найпотужнішим засобом, який пропонує Nette для зв'язку сервера з клієнтом, є сніпети. Завдяки їм ви можете перетворити звичайний застосунок на AJAX-застосунок з мінімальними зусиллями та кількома рядками коду. Як це все працює, демонструє приклад Fifteen, код якого ви знайдете на [GitHub |https://github.com/nette-examples/fifteen]. + +Сніпети, або фрагменти, дозволяють оновлювати лише частини сторінки, замість того, щоб перезавантажувати всю сторінку. Це не тільки швидше та ефективніше, але й забезпечує більш комфортний користувацький досвід. Сніпети можуть нагадувати вам Hotwire для Ruby on Rails або Symfony UX Turbo. Цікаво, що Nette представило сніпети на 14 років раніше. + +Як працюють сніпети? При першому завантаженні сторінки (не AJAX-запит) завантажується вся сторінка, включно з усіма сніпетами. Коли користувач взаємодіє зі сторінкою (наприклад, натискає кнопку, надсилає форму тощо), замість завантаження всієї сторінки викликається AJAX-запит. Код у presenter'і виконує дію і вирішує, які сніпети потрібно оновити. Nette рендерить ці сніпети та надсилає їх у вигляді масиву у форматі JSON. Обробний код у браузері отримує сніпети та вставляє їх назад у сторінку. Таким чином, передається лише код змінених сніпетів, що економить пропускну здатність і прискорює завантаження порівняно з передачею вмісту всієї сторінки. + -[Бібліотека Naja |https://naja.js.org] використовується для обробки AJAX-запитів на стороні браузера. [Встановіть |https://naja.js.org/#/guide/01-install-setup-naja] його як пакет node.js (для використання з Webpack, Rollup, Vite, Parcel та іншими): +Naja +---- + +Для обробки сніпетів на стороні браузера використовується [бібліотека Naja |https://naja.js.org]. Її [встановіть |https://naja.js.org/#/guide/01-install-setup-naja] як пакет node.js (для використання з застосунками Webpack, Rollup, Vite, Parcel та іншими): ```shell npm install naja ``` -...або вставити безпосередньо в шаблон сторінки: +…або безпосередньо вставте в шаблон сторінки: ```html ``` +Спочатку потрібно бібліотеку [ініціалізувати |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization]: -Сніпети .[#toc-snippety] -======================== +```js +naja.initialize(); +``` -Однак існує набагато потужніший інструмент вбудованої підтримки AJAX - сніпети. Їхнє використання дає змогу перетворити звичайний застосунок на AJAX-додаток за допомогою лише кількох рядків коду. Як усе це працює, показано в прикладі Fifteen, код якого також доступний у збірці або на [GitHub |https://github.com/nette-examples/fifteen]. +Щоб перетворити звичайне посилання (сигнал) або надсилання форми на AJAX-запит, достатньо позначити відповідне посилання, форму або кнопку класом `ajax`: -Принцип роботи сніпетів полягає в тому, що всю сторінку передають під час початкового (тобто не-AJAX) запиту, а потім із кожним AJAX [subrequest |components#Signal] (запит до того самого подання того самого презентера) тільки код змінених частин передають до сховища `payload`, згаданого раніше. +```html +Перейти -Сніпети можуть нагадати вам Hotwire для Ruby on Rails або Symfony UX Turbo, але Nette придумав їх чотирнадцятьма роками раніше. +
    + +
    +або -Інвалідація .[#toc-invalidation-of-snippets] -============================================ +
    + +
    +``` -Кожен нащадок класу [Control |components] (яким є і Presenter) здатний пам'ятати, чи були якісь зміни під час запиту, що вимагають повторного відображення. Існує кілька способів впоратися з цим: `redrawControl()` і `isControlInvalid()`. Приклад: + +Перемальовування сніпетів +------------------------- + +Кожен об'єкт класу [Control |components] (включно з самим Presenter'ом) відстежує, чи відбулися зміни, що вимагають його перемальовування. Для цього використовується метод `redrawControl()`: ```php public function handleLogin(string $user): void { - // Об'єкт має повторно відображатися після того, як користувач увійшов у систему + // після входу потрібно перемалювати відповідну частину $this->redrawControl(); // ... } ``` -Однак Nette забезпечує ще більш тонкий дозвіл, ніж цілі компоненти. Перераховані методи приймають ім'я так званого "фрагмента" як необов'язковий параметр. "Фрагмет" це, по суті, елемент у вашому шаблоні, позначений для цієї мети макросом Latte, докладніше про це пізніше. Таким чином, можна попросити компонент перемалювати тільки *частину* свого шаблону. Якщо весь компонент недійсний, то всі його фрагменти відображаються заново. Компонент є "недійсним", якщо будь-який з його субкомпонентів є недійсним. - -```php -$this->isControlInvalid(); // -> false -$this->redrawControl('header'); // анулює фрагмент з ім'ям 'header' -$this->isControlInvalid('header'); // -> true -$this->isControlInvalid('footer'); // -> false -$this->isControlInvalid(); // -> true, принаймні один фрагмент недійсний +Nette дозволяє ще більш точно контролювати, що саме потрібно перемалювати. Згаданий метод може приймати як аргумент назву сніпета. Таким чином, можна інвалідувати (тобто: змусити перемалювати) на рівні частин шаблону. Якщо інвалідується весь компонент, то перемальовується і кожен його сніпет: -$this->redrawControl(); // робить недійсним весь компонент, кожен фрагмент -$this->isControlInvalid('footer'); // -> true +```php +// інвалідує сніпет 'header' +$this->redrawControl('header'); ``` -Компонент, який отримав сигнал, автоматично позначається для перемальовування. - -Завдяки перемальовуванню фрагментів ми точно знаємо, які частини яких елементів мають бути перемальовані. - - -Тег `{snippet} … {/snippet}` .{toc: Tag snippet} -================================================ -Рендеринг сторінки відбувається так само, як і під час звичайного запиту: завантажуються одні й ті самі шаблони тощо. Однак найважливіше - це не допустити потрапляння до вихідного сигналу тих частин, які не повинні потрапити до вихідного сигналу; інші частини мають бути пов'язані з ідентифікатором і надіслані користувачеві у форматі, зрозумілому для обробника JavaScript. +Сніпети в Latte +--------------- - -Синтаксис .[#toc-sintaksis] ---------------------------- - -Якщо в шаблоні є елемент управління або фрагмент, ми повинні обернути його за допомогою парного тега `{snippet} ... {/snippet}` - відмальований фрагмент буде "вирізаний" і відправиться в браузер. Він також укладе його в допоміжний тег `
    ` (можна використовувати інший). У наступному прикладі визначено сніппет з ім'ям `header`. Він також може являти собою шаблон компонента: +Використання сніпетів у Latte надзвичайно просте. Щоб визначити частину шаблону як сніпет, просто оберніть її тегами `{snippet}` та `{/snippet}`: ```latte {snippet header} -

    Hello ...

    +

    Привіт ...

    {/snippet} ``` -Якщо ви хочете створити сніппет з іншим елементом, що містить, відмінним від `
    `, або додати користувацькі атрибути до елемента, ви можете використовувати таке визначення: +Сніпет створює в HTML-сторінці елемент `
    ` зі спеціальним згенерованим `id`. При перемальовуванні сніпета оновлюється вміст цього елемента. Тому необхідно, щоб при первинному відображенні сторінки відображалися також усі сніпети, навіть якщо вони спочатку можуть бути порожніми. + +Ви можете створити сніпет з іншим елементом, ніж `
    `, за допомогою n:атрибута: ```latte
    -

    Hello ...

    +

    Привіт ...

    ``` -Динамічні сніпети .[#toc-dinamiceskie-snippety] -=============================================== +Області сніпетів +---------------- -У Nette ви також можете визначити сніпети з динамічним ім'ям, заснованим на параметрі часу виконання. Це найбільше підходить для різних списків, де нам потрібно змінити лише один рядок, але ми не хочемо переносити весь список разом із ним. Прикладом цього може бути: +Назви сніпетів також можуть бути виразами: ```latte - +{foreach $items as $id => $item} +
  • {$item}
  • +{/foreach} ``` -Існує один статичний сніппет `itemsContainer`, що містить кілька динамічних сніпетів: `пункт-0`, `пункт-1` і так далі. +Таким чином, у нас виникне кілька сніпетів `item-0`, `item-1` тощо. Якщо ми безпосередньо інвалідуємо динамічний сніпет (наприклад, `item-1`), нічого не перемалюється. Причина в тому, що сніпети справді працюють як вирізки і відображаються лише безпосередньо вони самі. Але в шаблоні фактично немає жодного сніпета з назвою `item-1`. Він виникає лише при виконанні коду навколо сніпета, тобто циклу foreach. Тому позначимо частину шаблону, яка має виконатися, за допомогою тегу `{snippetArea}`: -Ви не можете перемалювати динамічний фрагмент безпосередньо (перемальовування `item-1` не має ефекту), ви маєте перемалювати його батьківський фрагмент (у цьому прикладі `itemsContainer`). При цьому виконується код батьківського сніпета, але браузеру передаються тільки його вкладені сніпети. Якщо ви хочете передати тільки один із вкладених сніпетів, вам потрібно змінити введення для батьківського сніпета, щоб не генерувати інші вкладені сніпети. +```latte +
      + {foreach $items as $id => $item} +
    • {$item}
    • + {/foreach} +
    +``` -У наведеному прикладі необхідно переконатися, що під час AJAX-запиту до масиву `$list` буде додано тільки один елемент, тому цикл `foreach` виводитиме тільки один динамічний фрагмент. +І змусимо перемалювати як сам сніпет, так і всю батьківську область: ```php -class HomePresenter extends Nette\Application\UI\Presenter -{ - /** - * Этот метод возвращает данные для списка. - * Обычно это просто запрос данных из модели. - * Для целей этого примера данные жёстко закодированы. - */ - private function getTheWholeList(): array - { - return [ - 'First', - 'Second', - 'Third', - ]; - } - - public function renderDefault(): void - { - if (!isset($this->template->list)) { - $this->template->list = $this->getTheWholeList(); - } - } - - public function handleUpdate(int $id): void - { - $this->template->list = $this->isAjax() - ? [] - : $this->getTheWholeList(); - $this->template->list[$id] = 'Updated item'; - $this->redrawControl('itemsContainer'); - } -} +$this->redrawControl('itemsContainer'); +$this->redrawControl('item-1'); ``` +Водночас бажано забезпечити, щоб масив `$items` містив лише ті елементи, які потрібно перемалювати. -Сніпети в увімкненому шаблоні .[#toc-snippety-vo-vklyucennom-sablone] -===================================================================== - -Може трапитися так, що сніппет міститься в шаблоні, який вмикається з іншого шаблону. У цьому разі необхідно обернути код включення в другому шаблоні макросом `snippetArea`, потім перемалювати як snippetArea, так і сам сніппет. - -Макрос `snippetArea` гарантує, що код усередині нього буде виконано, але браузеру буде надіслано тільки фактичний фрагмент включеного шаблону. +Якщо ми вставляємо в шаблон за допомогою тегу `{include}` інший шаблон, який містить сніпети, необхідно вставлення шаблону знову включити в `snippetArea` і інвалідувати його разом зі сніпетом: ```latte -{* parent.latte *} -{snippetArea wrapper} - {include 'child.latte'} +{snippetArea include} + {include 'included.latte'} {/snippetArea} ``` + ```latte -{* child.latte *} +{* included.latte *} {snippet item} -... + ... {/snippet} ``` + ```php -$this->redrawControl('wrapper'); +$this->redrawControl('include'); $this->redrawControl('item'); ``` -Ви також можете поєднувати його з динамічними сніпетами. - - -Додавання та видалення .[#toc-dobavlenie-i-udalenie] -==================================================== -Якщо додати новий елемент у список і анулювати `itemsContainer`, AJAX-запит поверне фрагменти, включно з новим, але javascript-обробник не зможе його відобразити. Це відбувається тому, що немає HTML-елемента з новоствореним ID. +Сніпети в компонентах +--------------------- -У цьому випадку найпростіший спосіб - обернути весь список у ще один сніпет і визнати його недійсним: +Ви можете створювати сніпети і в [компонентах|components], і Nette буде автоматично їх перемальовувати. Але тут є певне обмеження: для перемальовування сніпетів викликається метод `render()` без параметрів. Тобто передача параметрів у шаблоні не працюватиме: ```latte -{snippet wholeList} - -{/snippet} -Добавить +OK +{control productGrid} + +не працюватиме: +{control productGrid $arg, $arg} +{control productGrid:paginator} ``` + +Надсилання користувацьких даних +------------------------------- + +Разом зі сніпетами ви можете надсилати клієнту будь-які інші дані. Достатньо записати їх в об'єкт `payload`: + ```php -public function handleAdd(): void +public function actionDelete(int $id): void { - $this->template->list = $this->getTheWholeList(); - $this->template->list[] = 'New one'; - $this->redrawControl('wholeList'); + // ... + if ($this->isAjax()) { + $this->payload->message = 'Успішно'; + } } ``` -Те ж саме стосується і видалення елемента. Можна було б надіслати порожній сніппет, але зазвичай списки можуть бути посторінковими, і було б складно реалізувати видалення одного елемента і завантаження іншого (який раніше перебував на іншій сторінці посторінкового списку). - -Надсилання параметрів компоненту .[#toc-otpravka-parametrov-komponentu] -======================================================================= +Передача параметрів +=================== -Коли ми надсилаємо параметри компоненту через AJAX-запит, чи то сигнальні, чи постійні параметри, ми повинні надати їхнє глобальне ім'я, яке також містить ім'я компонента. Повне ім'я параметра повертає метод `getParameterId()`. +Якщо ми надсилаємо компоненту параметри за допомогою AJAX-запиту, чи то параметри сигналу, чи персистентні параметри, ми повинні вказати у запиті їхню глобальну назву, яка містить також ім'я компонента. Повну назву параметра повертає метод `getParameterId()`. ```js -$.getJSON( - {link changeCountBasket!}, - { - {$control->getParameterId('id')}: id, - {$control->getParameterId('count')}: count - } -}); +let url = new URL({link //foo!}); +url.searchParams.set({$control->getParameterId('bar')}, bar); + +fetch(url, { + headers: {'X-Requested-With': 'XMLHttpRequest'}, +}) ``` -І обробити метод з відповідними параметрами в компоненті. +І метод handle з відповідними параметрами в компоненті: ```php -public function handleChangeCountBasket(int $id, int $count): void +public function handleFoo(int $bar): void { - } ``` diff --git a/application/uk/bootstrap.texy b/application/uk/bootstrap.texy deleted file mode 100644 index f4872269e9..0000000000 --- a/application/uk/bootstrap.texy +++ /dev/null @@ -1,233 +0,0 @@ -Bootstrap -********* - -
    - -Bootstrap - це завантажувальний код, який ініціалізує середовище, створює контейнер впровадження залежностей (DI) і запускає додаток. Ми обговоримо: - -- як налаштувати застосунок за допомогою файлів NEON -- як працювати з режимами виробництва та розробки -- як створити контейнер DI - -
    - - -Додатки, чи то веб-додатки, чи то скрипти командного рядка, починаються з ініціалізації середовища в тій чи іншій формі. У стародавні часи за це міг відповідати файл з ім'ям, наприклад, `include.inc.php`, який включався у вихідний файл. -У сучасних додатках Nette він замінений класом `Bootstrap`, який як частина додатка знаходиться у файлі `app/Bootstrap.php`. Це може виглядати, наприклад, так: - -```php -use Nette\Bootstrap\Configurator; - -class Bootstrap -{ - public static function boot(): Configurator - { - $appDir = dirname(__DIR__); - $configurator = new Configurator; - //$configurator->setDebugMode('secret@23.75.345.200'); - $configurator->enableTracy($appDir . '/log'); - $configurator->setTempDirectory($appDir . '/temp'); - $configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); - $configurator->addConfig($appDir . '/config/common.neon'); - return $configurator; - } -} -``` - - -index.php .[#toc-index-php] -=========================== - -У випадку веб-додатків початковим файлом є `index.php`, який знаходиться у загальнодоступному каталозі `www/`. Він дозволяє класу `Bootstrap` ініціалізувати середовище і повертає `$configurator`, який створює контейнер DI. Потім він отримує сервіс `Application`, який запускає веб-додаток: - -```php -// ініціалізуємо середовище + отримуємо об'єкт Configurator -$configurator = App\Bootstrap::boot(); -// створюємо DI-контейнер -$container = $configurator->createContainer(); -// DI-контейнер створює об'єкт Nette\Application\Application -$application = $container->getByType(Nette\Application\Application::class); -// запускаємо додаток Nette -$application->run(); -``` - -Як ви бачите, клас [api:Nette\Bootstrap\Configurator], який ми зараз представимо детальніше, допомагає в налаштуванні середовища та створенні контейнера впровадження залежностей (DI). - - -Режим розробки та режим виробництва .[#toc-development-vs-production-mode] -========================================================================== - -Nette розрізняє два основні режими, в яких виконується запит: розробка і виробництво. Режим розробки орієнтований на максимальну зручність програміста, відображається Tracy, кеш автоматично оновлюється у разі зміни шаблонів або конфігурації DI контейнера тощо. Режим виробництва орієнтований на продуктивність, Tracy тільки реєструє помилки, а зміни шаблонів та інших файлів не перевіряються. - -Вибір режиму здійснюється шляхом автовизначення, тому зазвичай немає необхідності налаштовувати або перемикати що-небудь вручну. Режим розробки використовується, якщо застосунок запущено на localhost (тобто IP-адресу `127.0.0.1` або `::1`) і відсутній проксі-сервер (тобто його HTTP-заголовок). В іншому разі застосунок працює у виробничому режимі. - -Якщо ви хочете ввімкнути режим розробки в інших випадках, наприклад, для програмістів, які отримують доступ з певної IP-адреси, ви можете використовувати `setDebugMode()`: - -```php -$configurator->setDebugMode('23.75.345.200'); // одна або більше IP-адрес -``` - -Ми безумовно рекомендуємо поєднувати IP-адресу з файлом cookie. Ми зберігатимемо секретний токен у cookie `nette-debug', например, `secret1234`, і режим розробки буде активовано для програмістів із такою комбінацією IP і cookie. - -```php -$configurator->setDebugMode('secret1234@23.75.345.200'); -``` - -Можна повністю вимкнути режим розробника, навіть для localhost: - -```php -$configurator->setDebugMode(false); -``` - -Зверніть увагу, що значення `true` жорстко вмикає режим розробника, чого ніколи не повинно відбуватися на робочому сервері. - - -Налагоджувальний інструмент Tracy .[#toc-debugging-tool-tracy] -============================================================== - -Для полегшення налагодження ми увімкнемо чудовий інструмент [Tracy |tracy:]. У режимі розробника він візуалізує помилки, а в режимі виробництва - записує помилки в зазначений каталог: - -```php -$configurator->enableTracy($appDir . '/log'); -``` - - -Тимчасові файли .[#toc-temporary-files] -======================================= - -Nette використовує кеш для DI-контейнера, RobotLoader, шаблонів тощо. Тому необхідно задати шлях до директорії, де зберігатиметься кеш: - -```php -$configurator->setTempDirectory($appDir . '/temp'); -``` - -У Linux або macOS встановіть [права на запис |nette:troubleshooting#Setting-Directory-Permissions] для каталогів `log/` і `temp/`. - - -RobotLoader .[#toc-robotloader] -=============================== - -Зазвичай ми хочемо автоматично завантажувати класи за допомогою [RobotLoader |robot-loader:], тому ми повинні запустити його і дозволити йому завантажити класи з каталогу, в якому знаходиться `Bootstrap.php` (тобто `__DIR__`) і всі його підкаталоги: - -```php -$configurator->createRobotLoader() - ->addDirectory(__DIR__) - ->register(); -``` - -Альтернативний спосіб - використовувати лише автозавантаження PSR-4 [Composer |best-practices:composer]. - - -Часовий пояс .[#toc-timezone] -============================= - -Configurator дає змогу вказати часовий пояс для вашого застосунку. - -```php -$configurator->setTimeZone('Europe/Prague'); -``` - - -Конфігурація DI-контейнера .[#toc-di-container-configuration] -============================================================= - -Частиною процесу завантаження є створення DI-контейнера, тобто фабрики об'єктів, яка є серцем усього додатка. Насправді це PHP-клас, створений Nette, який зберігається в каталозі кешу. Фабрика створює ключові об'єкти застосунку, а конфігураційні файли інструктують її, як їх створювати та налаштовувати, і таким чином ми впливаємо на поведінку всього застосунку. - -Файли конфігурації зазвичай записуються у форматі [NEON |neon:format]. Ви можете прочитати [що можна налаштувати тут |nette:configuring]. - -.[tip] -У режимі розробки контейнер автоматично оновлюється щоразу, коли ви змінюєте код або конфігураційні файли. У виробничому режимі він генерується лише один раз, а зміни файлів не перевіряються для досягнення максимальної продуктивності. - -Файли конфігурації завантажуються за допомогою `addConfig()`: - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -``` - -Метод `addConfig()` може бути викликаний кілька разів для додавання декількох файлів. - -```php -$configurator->addConfig($appDir . '/config/common.neon'); -$configurator->addConfig($appDir . '/config/local.neon'); -if (PHP_SAPI === 'cli') { - $configurator->addConfig($appDir . '/config/cli.php'); -} -``` - -Підключення `cli.php` не є друкарською помилкою, конфігурацію також можна записати в PHP-файлі, який повертає її у вигляді масиву. - -Альтернативно, ми можемо використовувати [секцію `includes` |dependency-injection:configuration#Including-Files] для завантаження конфігураційних файлів. - -Якщо елементи з однаковими ключами відображаються у файлах конфігурації, вони будуть [перезаписані або об'єднані |dependency-injection:configuration#Merging] у випадку масивів. Пізніше включений файл має вищий пріоритет, ніж попередні. Файл, зазначений у секції `includes`, має вищий пріоритет, ніж файли, включені в нього. - - -Статичні параметри .[#toc-static-parameters] --------------------------------------------- - -Параметри, що використовуються у файлах конфігурації, можуть бути визначені [в секції `parameters` |dependency-injection:configuration#parameters] і підхоплені (або перезаписані) методом `addStaticParameters()` (у нього є аліас `addParameters()`). Важливо, що різні значення параметрів викликають генерацію додаткових DI-контейнерів, тобто додаткових класів. - -```php -$configurator->addStaticParameters([ - 'projectId' => 23, -]); -``` - -У конфігураційних файлах можна використовувати звичайну нотацію `%projectId%` для доступу до параметра з ім'ям `projectId`. За замовчуванням конфігуратор заповнює такі параметри: `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` і `consoleMode`. - - -Динамічні параметри .[#toc-dynamic-parameters] ----------------------------------------------- - -Можна також додати динамічні параметри в контейнер. Їхні різні значення, на відміну від статичних параметрів, не призведуть до генерації нових DI-контейнерів. - -```php -$configurator->addDynamicParameters([ - 'remoteIp' => $_SERVER['REMOTE_ADDR'], -]); -``` - -Змінні середовища можуть бути легко доступні з використанням динамічних параметрів. Ми можемо отримати доступ до них через `%env.variable%` у файлах конфігурації. - -```php -$configurator->addDynamicParameters([ - 'env' => getenv(), -]); -``` - - -Імпортовані сервіси .[#toc-imported-services] ---------------------------------------------- - -Заглибимося далі. Хоча мета DI-контейнера у створенні об'єктів, може виникнути необхідність вставити наявний об'єкт у контейнер. Це робиться визначенням сервісу з атрибутом `imported: true`: - -```neon -services: - myservice: - type: App\Model\MyCustomService - imported: true -``` - -Створюємо новий екземпляр і вставляємо його в Bootstrap: - -```php -$configurator->addServices([ - 'myservice' => new App\Model\MyCustomService('foobar'), -]); -``` - - -Різні середовища .[#toc-different-environments] -=============================================== - -Не соромтеся налаштувати клас `Bootstrap` відповідно до ваших потреб. Ви можете додавати параметри до методу `boot()` для розділення веб-проєктів, або додавати інші методи, як-от `bootForTests()`, які ініціалізують середовище для модульних тестів, `bootForCli()` для скриптів, що викликаються з командного рядка, і так далі. - -```php -public static function bootForTests(): Configurator -{ - $configurator = self::boot(); - Tester\Environment::setup(); // Инициализация Nette Tester - return $configurator; -} -``` diff --git a/application/uk/bootstrapping.texy b/application/uk/bootstrapping.texy new file mode 100644 index 0000000000..593350fa04 --- /dev/null +++ b/application/uk/bootstrapping.texy @@ -0,0 +1,297 @@ +Завантаження +************ + +
    + +Завантаження — це процес ініціалізації середовища додатка, створення контейнера впровадження залежностей (DI) та запуску додатка. Ми обговоримо: + +- як клас Bootstrap ініціалізує середовище +- як додатки налаштовуються за допомогою NEON файлів +- як розрізняти режим виробництва та розробки +- як створити та налаштувати DI контейнер + +
    + + +Застосунки, чи то веб-застосунки, чи скрипти, що запускаються з командного рядка, починають свою роботу з певної форми ініціалізації середовища. У давні часи за це відповідав файл з назвою, наприклад, `include.inc.php`, який включався первинним файлом. У сучасних застосунках Nette його замінив клас `Bootstrap`, який як частину застосунку ви знайдете у файлі `app/Bootstrap.php`. Він може виглядати, наприклад, так: + +```php +use Nette\Bootstrap\Configurator; + +class Bootstrap +{ + private Configurator $configurator; + private string $rootDir; + + public function __construct() + { + $this->rootDir = dirname(__DIR__); + // Configurator відповідає за налаштування середовища застосунку та сервісів. + $this->configurator = new Configurator; + // Встановлює каталог для тимчасових файлів, що генеруються Nette (наприклад, скомпільовані шаблони) + $this->configurator->setTempDirectory($this->rootDir . '/temp'); + } + + public function bootWebApplication(): Nette\DI\Container + { + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); + } + + private function initializeEnvironment(): void + { + // Nette розумний, і режим розробки вмикається автоматично, + // або ви можете ввімкнути його для конкретної IP-адреси, розкоментувавши наступний рядок: + // $this->configurator->setDebugMode('secret@23.75.345.200'); + + // Активує Tracy: неперевершений "швейцарський ніж" для налагодження. + $this->configurator->enableTracy($this->rootDir . '/log'); + + // RobotLoader: автоматично завантажує всі класи у вибраному каталозі + $this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); + } + + private function setupContainer(): void + { + // Завантажує конфігураційні файли + $this->configurator->addConfig($this->rootDir . '/config/common.neon'); + } +} +``` + + +index.php +========= + +Первинним файлом у випадку веб-застосунків є `index.php`, який знаходиться у [публічному каталозі |directory-structure#Публічний каталог www] `www/`. Він отримує від класу Bootstrap ініціалізацію середовища та створення DI-контейнера. Потім з нього отримує сервіс `Application`, який запускає веб-застосунок: + +```php +$bootstrap = new App\Bootstrap; +// Ініціалізація середовища + створення DI-контейнера +$container = $bootstrap->bootWebApplication(); +// DI-контейнер створює об'єкт Nette\Application\Application +$application = $container->getByType(Nette\Application\Application::class); +// Запуск застосунку Nette та обробка вхідного запиту +$application->run(); +``` + +Як бачимо, з налаштуванням середовища та створенням DI-контейнера (впровадження залежностей) допомагає клас [api:Nette\Bootstrap\Configurator], який ми зараз детальніше розглянемо. + + +Режим розробки проти робочого режиму +==================================== + +Nette поводиться по-різному залежно від того, чи працює він на сервері розробки чи на робочому сервері: + +🛠️ Режим розробки (Development): + - Показує панель налагодження Tracy з корисною інформацією (SQL-запити, час виконання, використана пам'ять) + - У разі помилки показує детальну сторінку помилки з викликами функцій та вмістом змінних + - Автоматично оновлює кеш при зміні шаблонів Latte, редагуванні конфігураційних файлів тощо. + + +🚀 Робочий режим (Production): + - Не показує жодної налагоджувальної інформації, всі помилки записує в лог + - У разі помилки показує ErrorPresenter або загальну сторінку "Server Error" + - Кеш ніколи автоматично не оновлюється! + - Оптимізований для швидкості та безпеки + + +Вибір режиму здійснюється автовизначенням, тому зазвичай не потрібно нічого налаштовувати або вручну перемикати: + +- режим розробки: на localhost (IP-адреса `127.0.0.1` або `::1`), якщо немає проксі (тобто її HTTP-заголовка) +- робочий режим: скрізь в інших місцях + +Якщо ми хочемо ввімкнути режим розробки і в інших випадках, наприклад, для програмістів, що підключаються з конкретної IP-адреси, використовуємо `setDebugMode()`: + +```php +$this->configurator->setDebugMode('23.75.345.200'); // можна вказати і масив IP-адрес +``` + +Однозначно рекомендуємо комбінувати IP-адресу з cookie. У cookie `nette-debug` збережемо секретний токен, наприклад, `secret1234`, і таким чином активуємо режим розробки для програмістів, що підключаються з конкретної IP-адреси та мають у cookie згаданий токен: + +```php +$this->configurator->setDebugMode('secret1234@23.75.345.200'); +``` + +Режим розробки можна також повністю вимкнути, навіть для localhost: + +```php +$this->configurator->setDebugMode(false); +``` + +Увага, значення `true` вмикає режим розробки примусово, що ніколи не повинно статися на робочому сервері. + + +Інструмент налагодження Tracy +============================= + +Для легкого налагодження ще ввімкнемо чудовий інструмент [Tracy |tracy:]. У режимі розробки він візуалізує помилки, а в робочому режимі помилки логує до вказаного каталогу: + +```php +$this->configurator->enableTracy($this->rootDir . '/log'); +``` + + +Тимчасові файли +=============== + +Nette використовує кеш для DI-контейнера, RobotLoader, шаблонів тощо. Тому необхідно встановити шлях до каталогу, куди буде зберігатися кеш: + +```php +$this->configurator->setTempDirectory($this->rootDir . '/temp'); +``` + +На Linux або macOS встановіть для каталогів `log/` та `temp/` [права на запис |nette:troubleshooting#Налаштування прав доступу до каталогів]. + + +RobotLoader +=========== + +Зазвичай ми захочемо автоматично завантажувати класи за допомогою [RobotLoader |robot-loader:], тому ми повинні його запустити і дозволити йому завантажувати класи з каталогу, де знаходиться `Bootstrap.php` (тобто `__DIR__`), та всіх підкаталогів: + +```php +$this->configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->register(); +``` + +Альтернативний підхід — дозволити завантажувати класи лише через [Composer |best-practices:composer], дотримуючись PSR-4. + + +Часовий пояс +============ + +За допомогою конфігуратора ви можете встановити стандартний часовий пояс. + +```php +$this->configurator->setTimeZone('Europe/Kyiv'); +``` + + +Конфігурація DI-контейнера +========================== + +Частиною процесу завантаження є створення DI-контейнера, або фабрики об'єктів, що є серцем усього застосунку. Це фактично PHP-клас, який генерує Nette і зберігає в каталозі з кешем. Фабрика виробляє ключові об'єкти застосунку, і за допомогою конфігураційних файлів ми інструктуємо її, як їх створювати та налаштовувати, чим впливаємо на поведінку всього застосунку. + +Конфігураційні файли зазвичай записуються у форматі [NEON |neon:format]. В окремому розділі ви дізнаєтеся, [що можна налаштувати |nette:configuring]. + +.[tip] +У режимі розробки контейнер автоматично оновлюється при кожній зміні коду або конфігураційних файлів. У робочому режимі він генерується лише один раз, і зміни не перевіряються для максимальної продуктивності. + +Конфігураційні файли завантажуємо за допомогою `addConfig()`: + +```php +$this->configurator->addConfig($this->rootDir . '/config/common.neon'); +``` + +Якщо ми хочемо додати більше конфігураційних файлів, ми можемо викликати функцію `addConfig()` кілька разів. + +```php +$configDir = $this->rootDir . '/config'; +$this->configurator->addConfig($configDir . '/common.neon'); +$this->configurator->addConfig($configDir . '/services.neon'); +if (PHP_SAPI === 'cli') { + $this->configurator->addConfig($configDir . '/cli.php'); +} +``` + +Назва `cli.php` не є помилкою, конфігурація може бути записана також у PHP-файлі, який повертає її як масив. + +Також ми можемо додати інші конфігураційні файли в [секції `includes` |dependency-injection:configuration#Включення файлів]. + +Якщо в конфігураційних файлах з'являються елементи з однаковими ключами, вони будуть перезаписані, або у випадку [масивів об'єднані |dependency-injection:configuration#Об єднання]. Файл, що завантажується пізніше, має вищий пріоритет, ніж попередній. Файл, у якому вказана секція `includes`, має вищий пріоритет, ніж файли, що в ньому включені. + + +Статичні параметри +------------------ + +Параметри, що використовуються в конфігураційних файлах, ми можемо визначити [у секції `parameters` |dependency-injection:configuration#Параметри], а також передавати (чи перезаписувати) їх методом `addStaticParameters()` (має псевдонім `addParameters()`). Важливо, що різні значення параметрів спричинять генерацію додаткових DI-контейнерів, тобто додаткових класів. + +```php +$this->configurator->addStaticParameters([ + 'projectId' => 23, +]); +``` + +На параметр `projectId` можна посилатися в конфігурації звичайним записом `%projectId%`. + + +Динамічні параметри +------------------- + +До контейнера ми можемо додати й динамічні параметри, різні значення яких, на відміну від статичних параметрів, не спричиняють генерації нових DI-контейнерів. + +```php +$this->configurator->addDynamicParameters([ + 'remoteIp' => $_SERVER['REMOTE_ADDR'], +]); +``` + +Таким чином, ми можемо легко додати, наприклад, змінні середовища, на які потім можна посилатися в конфігурації записом `%env.variable%`. + +```php +$this->configurator->addDynamicParameters([ + 'env' => getenv(), +]); +``` + + +Стандартні параметри +-------------------- + +У конфігураційних файлах ви можете використовувати ці статичні параметри: + +- `%appDir%` — абсолютний шлях до каталогу з файлом `Bootstrap.php` +- `%wwwDir%` — абсолютний шлях до каталогу з вхідним файлом `index.php` +- `%tempDir%` — абсолютний шлях до каталогу для тимчасових файлів +- `%vendorDir%` — абсолютний шлях до каталогу, куди Composer встановлює бібліотеки +- `%rootDir%` — абсолютний шлях до кореневого каталогу проєкту +- `%debugMode%` — вказує, чи перебуває застосунок у режимі налагодження +- `%consoleMode%` — вказує, чи прийшов запит через командний рядок + + +Імпортовані сервіси +------------------- + +Тепер ми заглиблюємося. Хоча сенс DI-контейнера полягає у створенні об'єктів, винятково може виникнути потреба вставити в контейнер існуючий об'єкт. Ми робимо це, визначаючи сервіс з прапорцем `imported: true`. + +```neon +services: + myservice: + type: App\Model\MyCustomService + imported: true +``` + +І в bootstrap ми вставляємо об'єкт у контейнер: + +```php +$this->configurator->addServices([ + 'myservice' => new App\Model\MyCustomService('foobar'), +]); +``` + + +Різне середовище +================ + +Не бійтеся змінювати клас Bootstrap відповідно до ваших потреб. Методу `bootWebApplication()` ви можете додати параметри для розрізнення веб-проектів. Або ми можемо додати інші методи, наприклад `bootTestEnvironment()`, який ініціалізує середовище для юніт-тестів, `bootConsoleApplication()` для скриптів, що викликаються з командного рядка, тощо. + +```php +public function bootTestEnvironment(): Nette\DI\Container +{ + Tester\Environment::setup(); // ініціалізація Nette Tester + $this->setupContainer(); + return $this->configurator->createContainer(); +} + +public function bootConsoleApplication(): Nette\DI\Container +{ + $this->configurator->setDebugMode(false); + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); +} +``` diff --git a/application/uk/components.texy b/application/uk/components.texy index 097773c6ba..cda44c9ffb 100644 --- a/application/uk/components.texy +++ b/application/uk/components.texy @@ -3,7 +3,7 @@
    -Компоненти - це окремі об'єкти багаторазового використання, які ми поміщаємо на сторінки. Це можуть бути форми, сітки даних, опитування, загалом, усе, що має сенс використовувати багаторазово. Далі ми дізнаємося: +Компоненти — це окремі об'єкти, що використовуються повторно, які ми вставляємо на сторінки. Це можуть бути форми, таблиці даних, опитування, власне все, що має сенс використовувати повторно. Ми покажемо: - як використовувати компоненти? - як їх писати? @@ -11,19 +11,19 @@
    -Nette має вбудовану систему компонентів. Ті, хто старший, можуть пам'ятати щось подібне з Delphi або ASP.NET Web Forms. React або Vue.js побудовані на чомусь віддалено схожому. Однак у світі PHP-фреймворків це абсолютно унікальна функція. +Nette має вбудовану систему компонентів. Щось подібне можуть пам'ятати ті, хто працював з Delphi або ASP.NET Web Forms, на чомусь віддалено схожому побудовані React або Vue.js. Однак у світі PHP-фреймворків це унікальна річ. -Водночас компоненти докорінно змінюють підхід до розробки застосунків. Ви можете складати сторінки із заздалегідь підготовлених блоків. Чи потрібна вам сітка даних в адмініструванні? Ви можете знайти її на [Componette |https://componette.org/search/component], репозиторії відкритих доповнень (не тільки компонентів) для Nette, і просто вставити в презентер. +При цьому компоненти суттєво впливають на підхід до створення застосунків. Ви можете складати сторінки з готових блоків. Потрібна таблиця даних в адміністративній панелі? Знайдіть її на [Componette |https://componette.org/search/component], репозиторії доповнень з відкритим кодом (тобто не тільки компонентів) для Nette, і просто вставте в presenter. -Ви можете включити в презентер будь-яку кількість компонентів. І ви можете вставляти інші компоненти в деякі компоненти. Це створює дерево компонентів із презентером як коренем. +До presenter'а можна включити будь-яку кількість компонентів. А в деякі компоненти можна вставляти інші компоненти. Таким чином створюється дерево компонентів, коренем якого є presenter. -Фабричні методи .[#toc-factory-methods] -======================================= +Фабричні методи +=============== -Як розміщуються і згодом використовуються компоненти в презентері? Зазвичай з використанням фабричних методів. +Як компоненти вставляються в presenter і потім використовуються? Зазвичай за допомогою фабричних методів. -Фабрика компонентів - це елегантний спосіб створювати компоненти тільки тоді, коли вони справді потрібні (ліньки / на вимогу). Уся магія полягає в реалізації методу `createComponent()`, де `` - ім'я компонента, який буде створено та повернуто. +Фабрика компонентів — це елегантний спосіб створювати компоненти лише тоді, коли вони дійсно потрібні (lazy / on demand). Вся магія полягає в реалізації методу з назвою `createComponent()`, де `` — це назва створюваного компонента, який створює та повертає компонент. ```php .{file:DefaultPresenter.php} class DefaultPresenter extends Nette\Application\UI\Presenter @@ -37,43 +37,43 @@ class DefaultPresenter extends Nette\Application\UI\Presenter } ``` -Оскільки всі компоненти створюються в окремих методах, код чистіший і легше читається. +Завдяки тому, що всі компоненти створюються в окремих методах, код стає більш зрозумілим. .[note] -Імена компонентів завжди починаються з малої літери, хоча в імені методу вони пишуться із великої. +Назви компонентів завжди починаються з малої літери, хоча в назві методу вони пишуться з великої. -Ми ніколи не викликаємо фабрики безпосередньо, вони викликаються автоматично, коли ми вперше використовуємо компоненти. Завдяки цьому компонент створюється в потрібний момент, і тільки якщо він дійсно необхідний. Якщо ми не використовуватимемо компонент (наприклад, під час AJAX-запиту, коли ми повертаємо тільки частину сторінки, або коли частини кешуються), він навіть не буде створений, і ми заощадимо продуктивність сервера. +Фабрики ніколи не викликаються безпосередньо, вони викликаються самі в момент першого використання компонента. Завдяки цьому компонент створюється в потрібний момент і лише тоді, коли він дійсно потрібен. Якщо ми не використовуємо компонент (наприклад, при AJAX-запиті, коли передається лише частина сторінки, або при кешуванні шаблону), він взагалі не створюється, і ми економимо ресурси сервера. ```php .{file:DefaultPresenter.php} -// ми звертаємося до компонента, і якщо це було вперше, -// він викликає createComponentPoll(), щоб створити його +// звертаємося до компонента, і якщо це вперше, +// викликається createComponentPoll(), який його створює $poll = $this->getComponent('poll'); // альтернативний синтаксис: $poll = $this['poll']; ``` -У шаблоні ви можете візуалізувати компонент за допомогою тега [{control} |#Rendering]. Тому немає необхідності вручну передавати компоненти в шаблон. +У шаблоні можна відобразити компонент за допомогою тегу [{control} |#Відображення]. Тому не потрібно вручну передавати компоненти в шаблон. ```latte -

    Проголосуйте, пожалуйста

    +

    Голосуйте

    {control poll} ``` -Голлівудський стиль .[#toc-hollywood-style] -=========================================== +Голлівудський стиль +=================== -Компоненти зазвичай використовують один класний прийом, який ми любимо називати голлівудським стилем. Напевно ви знаєте це кліше, яке актори часто чують на кастингах: "Не телефонуйте нам, ми зателефонуємо вам". І це те, про що йдеться. +Компоненти зазвичай використовують одну свіжу техніку, яку ми любимо називати Голлівудським стилем. Ви напевно знаєте крилату фразу, яку так часто чують учасники кінопроб: "Не дзвоніть нам, ми вам зателефонуємо". Саме про це йдеться. -У Nette, замість того, щоб постійно ставити запитання ("чи була форма надіслана?", "чи була вона дійсною?", чи "чи натиснув хто-небудь на цю кнопку?"), ви кажете фреймворку "коли це станеться, викличте цей метод" і залиште подальшу роботу над ним. Якщо ви програмуєте на JavaScript, ви знайомі з цим стилем програмування. Ви пишете функції, які викликаються при настанні певної події. А движок передає їм відповідні параметри. +У Nette замість того, щоб постійно щось запитувати ("чи була надіслана форма?", "чи була вона валідною?" або "чи натиснув користувач цю кнопку?"), ви кажете фреймворку "коли це станеться, виклич цей метод" і залишаєте подальшу роботу йому. Якщо ви програмуєте на JavaScript, цей стиль програмування вам добре знайомий. Ви пишете функції, які викликаються, коли настає певна подія. І мова передає їм відповідні параметри. -Це повністю змінює спосіб написання додатків. Що більше завдань ви можете делегувати фреймворку, то менше у вас роботи. І тим менше ви можете забути. +Це повністю змінює погляд на написання застосунків. Чим більше завдань ви можете залишити фреймворку, тим менше роботи у вас. І тим менше ви можете щось пропустити. -Як написати компонент .[#toc-how-to-write-a-component] -====================================================== +Пишемо компонент +================ -Під компонентом ми зазвичай маємо на увазі нащадків класу [api:Nette\Application\UI\Control]. Сам презентер [api:Nette\Application\UI\Presenter] також є нащадком класу `Control`. +Під поняттям компонент зазвичай мається на увазі нащадок класу [api:Nette\Application\UI\Control]. (Точніше було б використовувати термін "controls", але "контроли" мають в українській мові зовсім інше значення, і скоріше прижилися "компоненти".) Сам presenter [api:Nette\Application\UI\Presenter] є, до речі, також нащадком класу `Control`. ```php .{file:PollControl.php} use Nette\Application\UI\Control; @@ -84,22 +84,22 @@ class PollControl extends Control ``` -Рендеринг .[#toc-rendering] -=========================== +Відображення +============ -Ми вже знаємо, що тег `{control componentName}` використовується для малювання компонента. Він фактично викликає метод `render()` компонента, в якому ми беремо на себе турботу про рендеринг. У нас є, як і в презентері, шаблон [Latte |latte:] у змінній `$this->template`, якому ми передаємо параметри. На відміну від використання в презентері, ми повинні вказати файл шаблону і дозволити йому відмалюватися: +Ми вже знаємо, що для відображення компонента використовується тег `{control componentName}`. Він фактично викликає метод `render()` компонента, в якому ми дбаємо про відображення. У нас є, так само як і в presenter'і, [Latte шаблон|templates] у змінній `$this->template`, куди ми передаємо параметри. На відміну від presenter'а, ми повинні вказати файл із шаблоном і змусити його відобразитися: ```php .{file:PollControl.php} public function render(): void { - // ми помістимо деякі параметри в шаблон + // вставляємо в шаблон деякі параметри $this->template->param = $value; - // і відобразимо його + // і відображаємо його $this->template->render(__DIR__ . '/poll.latte'); } ``` -Тег `{control}` дозволяє передавати параметри в метод `render()`: +Тег `{control}` дозволяє передати параметри в метод `render()`: ```latte {control poll $id, $message} @@ -112,7 +112,7 @@ public function render(int $id, string $message): void } ``` -Іноді компонент може складатися з декількох частин, які ми хочемо відобразити окремо. Для кожного з них ми створимо свій метод візуалізації, наприклад, `renderPaginator()`: +Іноді компонент може складатися з кількох частин, які ми хочемо відображати окремо. Для кожної з них ми створюємо власний метод відображення, тут у прикладі, наприклад, `renderPaginator()`: ```php .{file:PollControl.php} public function renderPaginator(): void @@ -127,101 +127,101 @@ public function renderPaginator(): void {control poll:paginator} ``` -Для кращого розуміння корисно знати, як тег компілюється в PHP-код. +Для кращого розуміння добре знати, як цей тег перекладається в PHP. ```latte {control poll} {control poll:paginator 123, 'hello'} ``` -Це компілюється в: +перекладається як: ```php $control->getComponent('poll')->render(); $control->getComponent('poll')->renderPaginator(123, 'hello'); ``` -Метод `getComponent()` повертає компонент `poll` і потім для нього викликається метод `render()` або `renderPaginator()` відповідно. +Метод `getComponent()` повертає компонент `poll` і над цим компонентом викликає метод `render()`, відповідно `renderPaginator()`, якщо в тезі після двокрапки вказано інший спосіб рендерингу. .[caution] -Якщо деінде в частині параметрів використовується **`=>`**, усі параметри будуть обгорнуті в масив і передані як перший аргумент: +Увага, якщо десь у параметрах з'явиться **`=>`**, усі параметри будуть упаковані в масив і передані як перший аргумент: ```latte -{control poll, id => 123, message => 'hello'} +{control poll, id: 123, message: 'hello'} ``` -компілюється в: +перекладається як: ```php $control->getComponent('poll')->render(['id' => 123, 'message' => 'hello']); ``` -Рендеринг вкладеного компонента: +Відображення підкомпонента: ```latte {control cartControl-someForm} ``` -компілюється в: +перекладається як: ```php $control->getComponent("cartControl-someForm")->render(); ``` -Компоненти, як і презентери, автоматично передають шаблонам кілька корисних змінних: +Компоненти, так само як і presenter'и, автоматично передають у шаблони кілька корисних змінних: -- `$basePath` - абсолютний URL шлях до кореневого каталогу (наприклад, `/CD-collection`). -- `$baseUrl` - абсолютний URL до кореневого каталогу (наприклад, `http://localhost/CD-collection`) -- `$user` - це об'єкт, [що представляє користувача |security:authentication]. -- `$presenter` - поточний презентер -- `$control` - поточний компонент -- `$flashes` - список [повідомлень |#flash-сообщений], надісланих методом `flashMessage()`. +- `$basePath` — абсолютний URL-шлях до кореневого каталогу (наприклад, `/eshop`) +- `$baseUrl` — абсолютний URL до кореневого каталогу (наприклад, `http://localhost/eshop`) +- `$user` — об'єкт [що представляє користувача |security:authentication] +- `$presenter` — поточний presenter +- `$control` — поточний компонент +- `$flashes` — масив [повідомлень |#Flash-повідомлення], надісланих функцією `flashMessage()` -Сигнал .[#toc-signal] -===================== +Сигнал +====== -Ми вже знаємо, що навігація в додатку Nette складається з посилань або перенаправлення на пари `Presenter:action`. Але що якщо ми просто хочемо виконати дію на **поточній сторінці**? Наприклад, змінити порядок сортування стовпців у таблиці; видалити елемент; перемкнути режим light/dark; надіслати форму; проголосувати в опитуванні; тощо. +Ми вже знаємо, що навігація в застосунку Nette полягає у посиланні або перенаправленні на пари `Presenter:action`. Але що, якщо ми просто хочемо виконати дію на **поточній сторінці**? Наприклад, змінити сортування стовпців у таблиці; видалити елемент; перемкнути світлий/темний режим; надіслати форму; проголосувати в опитуванні тощо. -Такий тип запиту називається сигналом. І як дії викликають методи `action()` або `render()`, сигнали викликають методи `handle()`. У той час як поняття дії (або перегляду) стосується лише презентерів, сигнали стосуються всіх компонентів. А отже, і до презентерів, бо `UI\Presenter` є нащадком `UI\Control`. +Цей тип запитів називається сигналами. І подібно до того, як дії викликають методи `action()` або `render()`, сигнали викликають методи `handle()`. У той час як поняття дії (або view) пов'язане виключно з presenter'ами, сигнали стосуються всіх компонентів. А отже, й presenter'ів, оскільки `UI\Presenter` є нащадком `UI\Control`. ```php public function handleClick(int $x, int $y): void { - // ... обробка сигналів ... + // ... обробка сигналу ... } ``` -Посилання, що викликає сигнал, створюється звичайним способом, тобто в шаблоні атрибутом `n:href` або тегом `{link}`, у коді методом `link()`. Докладніше в розділі [Створення посилань URL |creating-links#Links-to-Signal]. +Посилання, що викликає сигнал, створюється звичайним способом, тобто в шаблоні атрибутом `n:href` або тегом `{link}`, у коді методом `link()`. Більше в розділі [Створення URL-посилань |creating-links#Посилання на сигнал]. ```latte -нажмите сюда +натисніть тут ``` -Сигнал завжди викликається на поточному презентері та поданні, тому неможливо пов'язати сигнал з іншим презентером/дією. +Сигнал завжди викликається на поточному presenter'і та action, його неможливо викликати на іншому presenter'і або іншому action. -Таким чином, сигнал викликає перезавантаження сторінки точно так само, як і у вихідному запиті, тільки додатково він викликає метод обробки сигналу з відповідними параметрами. Якщо метод не існує, викидається виняток [api:Nette\Application\UI\BadSignalException], який відображається користувачеві у вигляді сторінки помилки 403 Forbidden. +Сигнал, отже, спричиняє перезавантаження сторінки так само, як і при початковому запиті, лише додатково викликає метод обробки сигналу з відповідними параметрами. Якщо метод не існує, викидається виняток [api:Nette\Application\UI\BadSignalException], який користувачеві відображається як сторінка помилки 403 Forbidden. -Сніпети та AJAX .[#toc-snippets-and-ajax] -========================================= +Сніпети та AJAX +=============== -Сигнали можуть трохи нагадати вам AJAX: обробники, які викликаються на поточній сторінці. І ви маєте рацію, сигнали дійсно часто викликаються за допомогою AJAX, і тоді ми передаємо браузеру тільки змінені частини сторінки. Вони називаються сніпетами. Більш детальну інформацію можна знайти на [сторінці про AJAX |ajax]. +Сигнали вам, можливо, трохи нагадують AJAX: обробники, які викликаються на поточній сторінці. І ви маєте рацію, сигнали дійсно часто викликаються за допомогою AJAX, і потім ми передаємо в браузер лише змінені частини сторінки. Тобто так звані сніпети. Більше інформації ви знайдете на [сторінці, присвяченій AJAX |ajax]. -Флеш-повідомлення .[#toc-flash-messages] -======================================== +Flash-повідомлення +================== -Компонент має власне сховище флеш-повідомлень, яке не залежить від презентера. Це повідомлення, які, наприклад, інформують про результат операції. Важливою особливістю флеш-повідомлень є те, що вони доступні в шаблоні навіть після перенаправлення. Навіть після відображення вони залишатимуться живими ще 30 секунд - наприклад, на випадок, якщо користувач ненавмисно оновить сторінку, повідомлення не буде втрачено. +Компонент має власне сховище flash-повідомлень, незалежне від presenter'а. Це повідомлення, які, наприклад, інформують про результат операції. Важливою особливістю flash-повідомлень є те, що вони доступні в шаблоні навіть після перенаправлення. Навіть після відображення вони залишаються активними ще 30 секунд – наприклад, на випадок, якщо через помилку передачі користувач оновить сторінку - повідомлення йому одразу не зникне. -Надсилання здійснюється методом [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Першим параметром є текст повідомлення або об'єкт `stdClass`, що представляє повідомлення. Необов'язковий другий параметр - це його тип (помилка, попередження, інформація тощо). Метод `flashMessage()` повертає екземпляр flash-повідомлення як об'єкт stdClass, якому можна передати інформацію. +Надсилання забезпечує метод [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Першим параметром є текст повідомлення або об'єкт `stdClass`, що представляє повідомлення. Необов'язковим другим параметром є його тип (error, warning, info тощо). Метод `flashMessage()` повертає екземпляр flash-повідомлення як об'єкт `stdClass`, до якого можна додавати додаткову інформацію. ```php $this->flashMessage('Елемент було видалено.'); -$this->redirect(/* ... */); // робимо редирект +$this->redirect(/* ... */); // і перенаправляємо ``` -У шаблоні ці повідомлення доступні у змінній `$flashes` як об'єкти `stdClass`, які містять властивості `message` (текст повідомлення), `type` (тип повідомлення) і можуть містити вже згадану інформацію про користувача. Ми відображаємо їх таким чином: +У шаблоні ці повідомлення доступні у змінній `$flashes` як об'єкти `stdClass`, які містять властивості `message` (текст повідомлення), `type` (тип повідомлення) і можуть містити вже згадану користувацьку інформацію. Відобразимо їх, наприклад, так: ```latte {foreach $flashes as $flash} @@ -230,44 +230,66 @@ $this->redirect(/* ... */); // робимо редирект ``` -Постійні параметри .[#toc-persistent-parameters] -================================================ +Перенаправлення після сигналу +============================= + +Після обробки сигналу компонента часто відбувається перенаправлення. Це схожа ситуація, як з формами - після їх надсилання ми також перенаправляємо, щоб при оновленні сторінки в браузері не відбулося повторного надсилання даних. + +```php +$this->redirect('this') // перенаправляє на поточний presenter та action +``` + +Оскільки компонент є елементом, що використовується повторно, і зазвичай не повинен мати прямого зв'язку з конкретними presenter'ами, методи `redirect()` та `link()` автоматично інтерпретують параметр як сигнал компонента: + +```php +$this->redirect('click') // перенаправляє на сигнал 'click' того ж компонента +``` + +Якщо вам потрібно перенаправити на інший presenter чи дію, ви можете зробити це через presenter: + +```php +$this->getPresenter()->redirect('Product:show'); // перенаправляє на інший presenter/action +``` + + +Персистентні параметри +====================== -Постійні параметри використовуються для збереження стану компонентів між різними запитами. Їх значення залишається незмінним навіть після переходу за посиланням. На відміну від сесійних даних, вони передаються в URL-адресі. І вони передаються автоматично, включаючи посилання, створені в інших компонентах на тій же сторінці. +Персистентні параметри служать для підтримки стану в компонентах між різними запитами. Їхнє значення залишається незмінним навіть після натискання на посилання. На відміну від даних у сесії, вони передаються в URL. І це відбувається повністю автоматично, включно з посиланнями, створеними в інших компонентах на тій самій сторінці. -Наприклад, у вас є компонент підкачки контенту. Таких компонентів на сторінці може бути декілька. І ви хочете, щоб при переході за посиланням всі компоненти залишалися на своїй поточній сторінці. Тому ми робимо номер сторінки (`page`) постійним параметром. +Наприклад, у вас є компонент для пагінації вмісту. Таких компонентів на сторінці може бути кілька. І ми хочемо, щоб після натискання на посилання всі компоненти залишалися на своїй поточній сторінці. Тому ми зробимо номер сторінки (`page`) персистентним параметром. -Створити постійний параметр в Nette надзвичайно просто. Просто створіть загальнодоступну властивість і позначте її атрибутом: (раніше використовувалося `/** @persistent */` ) +Створення персистентного параметра в Nette надзвичайно просте. Достатньо створити публічну властивість і позначити її атрибутом: (раніше використовувалося `/** @persistent */`) ```php -use Nette\Application\Attributes\Persistent; // цей рядок важливий +use Nette\Application\Attributes\Persistent; // цей рядок важливий class PaginatingControl extends Control { #[Persistent] - public int $page = 1; // повинні бути публічними + public int $page = 1; // має бути public } ``` -Ми рекомендуємо вам вказати тип даних (наприклад, `int`) разом з властивістю, а також ви можете вказати значення за замовчуванням. Значення параметрів можуть бути [перевірені |#Validation of Persistent Parameters]. +Для властивості рекомендуємо вказувати тип даних (наприклад, `int`) і ви можете вказати значення за замовчуванням. Значення параметрів можна [валідувати |#Валідація персистентних параметрів]. -Ви можете змінити значення постійного параметра під час створення посилання: +При створенні посилання можна змінити значення персистентного параметра: ```latte -next +наступна ``` -Або ж його можна *скинути*, тобто видалити з URL-адреси. Тоді він прийме значення за замовчуванням: +Або його можна *скинути*, тобто видалити з URL. Тоді він набуде свого значення за замовчуванням: ```latte -reset +скинути ``` -Постійні компоненти .[#toc-persistent-components] -================================================= +Персистентні компоненти +======================= -Постійними можуть бути не тільки параметри, а й компоненти. Їхні постійні параметри також передаються між різними діями або між різними презентерами. Ми позначаємо постійні компоненти цією анотацією для класу презентера. Наприклад, тут ми позначаємо компоненти `calendar` і `poll` таким чином: +Не тільки параметри, але й компоненти можуть бути персистентними. У такого компонента його персистентні параметри передаються і між різними діями presenter'а, або між кількома presenter'ами. Персистентні компоненти позначаємо анотацією біля класу presenter'а. Наприклад, так позначимо компоненти `calendar` та `poll`: ```php /** @@ -278,9 +300,9 @@ class DefaultPresenter extends Nette\Application\UI\Presenter } ``` -Вам не потрібно позначати підкомпоненти як постійні, вони стають постійними автоматично. +Підкомпоненти всередині цих компонентів не потрібно позначати, вони також стануть персистентними. -У PHP 8 ви також можете використовувати атрибути для маркування постійних компонентів: +У PHP 8 ви можете для позначення персистентних компонентів використовувати також атрибути: ```php use Nette\Application\Attributes\Persistent; @@ -292,35 +314,35 @@ class DefaultPresenter extends Nette\Application\UI\Presenter ``` -Компоненти із залежностями .[#toc-components-with-dependencies] -=============================================================== +Компоненти із залежностями +========================== -Як створити компоненти із залежностями, не "заплутавши" ведучих, які будуть їх використовувати? Завдяки продуманим можливостям DI-контейнера в Nette, як і під час використання традиційних сервісів, ми можемо залишити більшу частину роботи фреймворку. +Як створювати компоненти із залежностями, не "забруднюючи" presenter'ів, які їх використовуватимуть? Завдяки розумним властивостям DI-контейнера в Nette можна, так само як при використанні класичних сервісів, залишити більшу частину роботи фреймворку. -Візьмемо як приклад компонент, що має залежність від сервісу `PollFacade`: +Візьмемо як приклад компонент, який має залежність від сервісу `PollFacade`: ```php class PollControl extends Control { public function __construct( - private int $id, // Id опитування, для якого створюється компонент + private int $id, // Id опитування, для якого ми створюємо компонент private PollFacade $facade, ) { } public function handleVote(int $voteId): void { - $this->facade->vote($id, $voteId); + $this->facade->vote($this->id, $voteId); // ... } } ``` -Якби ми писали класичний сервіс, то турбуватися було б нема про що. Контейнер DI непомітно подбав би про передачу всіх залежностей. Але ми зазвичай працюємо з компонентами, створюючи їхній новий екземпляр безпосередньо в презентері в [factory methods |#factory methods] `createComponent...()`. Але передача всіх залежностей усіх компонентів у презентер, щоб потім передати їх компонентам, громіздка. І кількість написаного коду... +Якби ми писали класичний сервіс, не було б чого вирішувати. Про передачу всіх залежностей невидимо подбав би DI-контейнер. Але з компонентами ми зазвичай поводимося так, що їхній новий екземпляр створюємо безпосередньо в presenter'і в [фабричних методах |#Фабричні методи] `createComponent…()`. Але передавати всі залежності всіх компонентів у presenter, щоб потім передати їх компонентам, незручно. І стільки написаного коду… -Логічне запитання: чому б нам просто не зареєструвати компонент як класичний сервіс, передати його ведучому, а потім повернути його в методі `createComponent...()`? Але такий підхід недоречний, оскільки ми хочемо мати можливість створювати компонент багаторазово. +Логічним питанням є, чому б просто не зареєструвати компонент як класичний сервіс, не передати його в presenter і потім у методі `createComponent…()` не повертати? Такий підхід, однак, недоречний, оскільки ми хочемо мати можливість створювати компонент навіть кілька разів. -Правильне рішення - написати фабрику для компонента, тобто клас, який створює компонент за нас: +Правильним рішенням є написати для компонента фабрику, тобто клас, який нам створить компонент: ```php class PollControlFactory @@ -337,17 +359,17 @@ class PollControlFactory } ``` -Тепер ми реєструємо наш сервіс у DI-контейнері для конфігурації: +Таким чином, фабрику зареєструємо в нашому контейнері в конфігурації: ```neon services: - PollControlFactory ``` -Нарешті, ми будемо використовувати цю фабрику в нашому презентері: +і нарешті використаємо її в нашому presenter'і: ```php -class PollPresenter extends Nette\UI\Application\Presenter +class PollPresenter extends Nette\Application\UI\Presenter { public function __construct( private PollControlFactory $pollControlFactory, @@ -356,13 +378,13 @@ class PollPresenter extends Nette\UI\Application\Presenter protected function createComponentPollControl(): PollControl { - $pollId = 1; // ми можемо передати наш параметр + $pollId = 1; // можемо передати наш параметр return $this->pollControlFactory->create($pollId); } } ``` -На щастя, Nette може генерувати ці прості фабрики, тому ми можемо написати просто інтерфейс цієї фабрики, а DI-контейнер згенерує реалізацію: +Чудово те, що Nette DI такі прості фабрики вміє [генерувати |dependency-injection:factory], тому замість її повного коду достатньо написати лише її інтерфейс: ```php interface PollControlFactory @@ -371,21 +393,21 @@ interface PollControlFactory } ``` -Ось і все. Nette внутрішньо реалізує цей інтерфейс і передає його нашому презентеру, де ми можемо його використовувати. Він також магічним чином передає наш параметр `$id` і екземпляр класу `PollFacade` у наш компонент. +І це все. Nette внутрішньо реалізує цей інтерфейс і передасть його в presenter, де ми вже можемо його використовувати. Магічно він додасть до нашого компонента і параметр `$id`, і екземпляр класу `PollFacade`. -Компоненти в глибину .[#toc-components-in-depth] -================================================ +Компоненти до глибини +===================== -Компоненти в Nette Application - це багаторазово використовувані частини веб-додатка, які ми вбудовуємо в сторінки, про що і піде мова в цьому розділі. Які можливості такого компонента? +Компоненти в Nette Application представляють собою повторно використовувані частини веб-застосунку, які ми вставляємо на сторінки і яким, власне, присвячена вся ця глава. Які саме можливості має такий компонент? -1) він може бути відображений у шаблоні -2) він знає, яку частину себе відображати під час [AJAX-запиту |ajax#invalidation] (сніпети) -3) має можливість зберігати свій стан в URL (постійні параметри) -4) має можливість реагувати на дії користувача (сигнали) -5) створює ієрархічну структуру (де коренем є ведучий) +1) його можна відобразити в шаблоні +2) він знає, [яку свою частину |ajax#Сніпети] має відобразити при AJAX-запиті (сніпети) +3) він має можливість зберігати свій стан в URL (персистентні параметри) +4) він має можливість реагувати на дії користувача (сигнали) +5) він створює ієрархічну структуру (де коренем є presenter) -Кожна з цих функцій обробляється одним із класів лінії успадкування. Рендеринг (1 + 2) обробляється [api:Nette\Application\UI\Control], включення в [життєвий цикл |presenters#life-cycle-of-presenter] (3, 4) - класом [api:Nette\Application\UI\Component], а створення ієрархічної структури (5) - класами [Container і Component |component-model:]. +Кожну з цих функцій забезпечує певний клас спадкової лінії. За відображення (1 + 2) відповідає [api:Nette\Application\UI\Control], за включення в [життєвий цикл |presenters#Життєвий цикл презентера] (3, 4) — клас [api:Nette\Application\UI\Component], а за створення ієрархічної структури (5) — класи [Container та Component |component-model:]. ``` Nette\ComponentModel\Component { IComponent } @@ -400,18 +422,18 @@ Nette\ComponentModel\Component { IComponent } ``` -Життєвий цикл компонента .[#toc-life-cycle-of-component] --------------------------------------------------------- +Життєвий цикл компонента +------------------------ [* lifecycle-component.svg *] *** *Життєвий цикл компонента* .<> -Перевірка постійних параметрів .[#toc-validation-of-persistent-parameters] --------------------------------------------------------------------------- +Валідація персистентних параметрів +---------------------------------- -Значення [постійних параметрів |#persistent parameters], отримані з URL-адрес, записуються у властивості методом `loadState()`. Також перевіряється, чи збігається тип даних, вказаний для властивості, інакше буде видано помилку 404, і сторінка не буде відображена. +Значення [персистентних параметрів |#Персистентні параметри], отримані з URL, записує у властивості метод `loadState()`. Він також перевіряє, чи відповідає тип даних, вказаний у властивості, інакше відповідає помилкою 404 і сторінка не відображається. -Ніколи не довіряйте сліпо постійним параметрам, оскільки вони можуть бути легко перезаписані користувачем в URL. Наприклад, так ми перевіряємо, чи номер сторінки `$this->page` більший за 0. Хорошим способом зробити це є перевизначення методу `loadState()`, згаданого вище: +Ніколи сліпо не довіряйте персистентним параметрам, оскільки їх може легко перезаписати користувач в URL. Таким чином, наприклад, перевіримо, чи номер сторінки `$this->page` більший за 0. Підходящим способом є перезапис згаданого методу `loadState()`: ```php class PaginatingControl extends Control @@ -421,8 +443,8 @@ class PaginatingControl extends Control public function loadState(array $params): void { - parent::loadState($params); // тут задається $this->page - // слідує перевірка користувацького значення: + parent::loadState($params); // тут встановлюється $this->page + // далі йде власна перевірка значення: if ($this->page < 1) { $this->error(); } @@ -430,27 +452,27 @@ class PaginatingControl extends Control } ``` -Протилежний процес, тобто збір значень з постійних властивостей, обробляється методом `saveState()`. +Зворотний процес, тобто збір значень з персистентних властивостей, відповідає метод `saveState()`. -Сигнали в глибину .[#toc-signals-in-depth] ------------------------------------------- +Сигнали до глибини +------------------ -Сигнал викликає перезавантаження сторінки подібно до вихідного запиту (за винятком AJAX) і викликає метод `signalReceived($signal)`, реалізація якого за замовчуванням у класі `Nette\Application\UI\Component` намагається викликати метод, що складається зі слів `handle{Signal}`. Подальша обробка залежить від цього об'єкта. Об'єкти, які є нащадками `Component` (тобто `Control` і `Presenter`), намагаються викликати `handle{Signal}` з відповідними параметрами. +Сигнал спричиняє перезавантаження сторінки так само, як і при початковому запиті (крім випадку, коли він викликаний AJAX) і викликає метод `signalReceived($signal)`, стандартна реалізація якого в класі `Nette\Application\UI\Component` намагається викликати метод, складений зі слів `handle{signal}`. Подальша обробка залежить від конкретного об'єкта. Об'єкти, що успадковують від `Component` (тобто `Control` і `Presenter`), реагують так, що намагаються викликати метод `handle{signal}` з відповідними параметрами. -Іншими словами: береться визначення методу `handle{Signal}` і всі параметри, які були отримані в запиті, зіставляються з параметрами методу. Це означає, що параметр `id` з URL зіставляється з параметром методу `$id`, `something` - з `$something` і так далі. А якщо метод не існує, то метод `signalReceived` викидає [виняток |api:Nette\Application\UI\BadSignalException]. +Іншими словами: береться визначення функції `handle{signal}` та всі параметри, що прийшли із запитом, і до аргументів за іменем підставляються параметри з URL, і намагається викликати даний метод. Наприклад, як параметр `$id` передається значення з параметра `id` в URL, як `$something` передається `something` з URL тощо. І якщо метод не існує, метод `signalReceived` викидає [виняток |api:Nette\Application\UI\BadSignalException]. -Сигнал може бути отриманий будь-яким компонентом, провідним об'єктом, що реалізує інтерфейс `SignalReceiver`, якщо він підключений до дерева компонентів. +Сигнал може приймати будь-який компонент, presenter або об'єкт, який реалізує інтерфейс `SignalReceiver` і підключений до дерева компонентів. -Основними одержувачами сигналів є презентери та візуальні компоненти, що розширюють `Control`. Сигнал - це знак для об'єкта, що він має щось зробити - опитування зараховує голос користувача, скринька з новинами має розгорнутися, форму було відправлено, і вона має обробити дані тощо. +Основними одержувачами сигналів будуть `Presenter`'и та візуальні компоненти, що успадковують від `Control`. Сигнал має служити знаком для об'єкта, що він має щось зробити – опитування має зарахувати голос від користувача, блок з новинами має розгорнутися і показати вдвічі більше новин, форма була надіслана і має обробити дані тощо. -URL для сигналу створюється за допомогою методу [Component::link() |api:Nette\Application\UI\Component::link()]. Як параметр `$destination` передається рядок `{signal}!`, а як `$args` - масив аргументів, які ми хочемо передати обробнику сигналу. Параметри сигналу прив'язуються до URL поточного презентера/представлення. **Параметр `?do` в URL визначає сигнал, що викликається.** +URL для сигналу створюємо за допомогою методу [Component::link() |api:Nette\Application\UI\Component::link()]. Як параметр `$destination` передаємо рядок `{signal}!` і як `$args` масив аргументів, які ми хочемо передати сигналу. Сигнал завжди викликається на поточному presenter'і та action з поточними параметрами, параметри сигналу лише додаються. Крім того, на самому початку додається **параметр `?do`, який визначає сигнал**. -Його формат - `{signal}` або `{signalReceiver}-{signal}`. `{signalReceiver}` - це ім'я компонента в презентері. Саме тому дефіс (неточно тире) не може бути присутнім в імені компонентів - він використовується для розділення імені компонента і сигналу, але можна скласти кілька компонентів. +Його формат — або `{signal}`, або `{signalReceiver}-{signal}`. `{signalReceiver}` — це назва компонента в presenter'і. Тому в назві компонента не може бути дефіса — він використовується для розділення назви компонента і сигналу, однак таким чином можна вкладати кілька компонентів. -Метод [isSignalReceiver() |api:Nette\Application\UI\Presenter::isSignalReceiver()] перевіряє, чи є компонент (перший аргумент) приймачем сигналу (другий аргумент). Другий аргумент може бути опущений - тоді з'ясовується, чи є компонент приймачем будь-якого сигналу. Якщо другий параметр дорівнює `true`, то перевіряється, чи є компонент або його нащадки приймачами сигналу. +Метод [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] перевіряє, чи є компонент (перший аргумент) одержувачем сигналу (другий аргумент). Другий аргумент можна опустити — тоді він з'ясовує, чи є компонент одержувачем будь-якого сигналу. Як другий параметр можна вказати `true`, і цим перевірити, чи є одержувачем не тільки вказаний компонент, але й будь-який його нащадок. -У будь-якій фазі, що передує `handle{Signal}`, сигнал можна виконати вручну, викликавши метод [processSignal() |api:Nette\Application\UI\Presenter::processSignal()], який бере на себе відповідальність за виконання сигналу. Приймає компонент-приймач (якщо він не встановлений, то це сам презентер) і посилає йому сигнал. +На будь-якому етапі, що передує `handle{signal}`, ми можемо виконати сигнал вручну, викликавши метод [processSignal()|api:Nette\Application\UI\Presenter::processSignal()], який бере на себе обробку сигналу — бере компонент, який визначено як одержувача сигналу (якщо одержувач сигналу не вказаний, це сам presenter) і надсилає йому сигнал. Приклад: @@ -460,4 +482,4 @@ if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, ' } ``` -Сигнал виконується передчасно і більше не буде викликаний. +Таким чином, сигнал виконано передчасно і більше не буде викликатися. diff --git a/application/uk/configuration.texy b/application/uk/configuration.texy index ad70721b40..6cdade511b 100644 --- a/application/uk/configuration.texy +++ b/application/uk/configuration.texy @@ -1,50 +1,63 @@ -Налаштування програми -********************* +Конфігурація застосунків +************************ .[perex] -Огляд варіантів конфігурації застосунку Nette. +Огляд конфігураційних опцій для застосунків Nette. -Додаток .[#toc-application] -=========================== +Application +=========== ```neon application: - # відображає вкладку "Nette Application" на синьому екрані Tracy? + # показувати панель "Nette Application" у Tracy BlueScreen? debugger: ... # (bool) за замовчуванням true - # чи буде викликатися презентер помилок у разі помилки? - catchExceptions: ... # (bool) за замовчуванням true на "бойовому" сервері + # чи буде при помилці викликатися error-presenter? + # має ефект лише в режимі розробки + catchExceptions: ... # (bool) за замовчуванням true - # ім'я презентера помилок - errorPresenter: Error # (string) за замовчуванням 'Nette:Error' + # назва error-presenter + errorPresenter: Error # (string|array) за замовчуванням 'Nette:Error' - # визначає правила для перетворення імені ведучого в клас + # визначає аліаси для презентерів та дій + aliases: ... + + # визначає правила для перекладу назви presenter на клас mapping: ... - # чи видають погані посилання попередження? - # має силу тільки в режимі розробки + # неправильні посилання не генерують попередження? + # має ефект лише в режимі розробки silentLinks: ... # (bool) за замовчуванням false ``` -Оскільки в режимі розробки презентери помилок за замовчуванням не викликаються, а помилки відображаються Tracy, зміна значення `catchExceptions` на `true` допомагає перевірити коректність роботи презентерів помилок під час розробки. +Починаючи з версії `nette/application` 3.2, можна визначити пару error-presenter'ів: -Опція `silentLinks` визначає, як Nette поводиться в режимі розробника, коли генерація посилань не вдається (наприклад, через відсутність презентера тощо). Значення за замовчуванням `false` означає, що Nette запускає `E_USER_WARNING`. Встановлення значення `true` пригнічує це повідомлення про помилку. У виробничому середовищі завжди викликається `E_USER_WARNING`. Ми також можемо вплинути на цю поведінку, встановивши змінну презентера [$invalidLinkMode |creating-links#Invalid-Links]. +```neon +application: + errorPresenter: + 4xx: Error4xx # для винятку Nette\Application\BadRequestException + 5xx: Error5xx # для інших винятків +``` -Карта [mapping визначає правила |modules#Mapping], за якими ім'я класу виводиться з імені ведучого. +Опція `silentLinks` визначає, як Nette поводитиметься в режимі розробки, коли генерація посилання зазнає невдачі (наприклад, тому що не існує presenter тощо). Стандартне значення `false` означає, що Nette викине помилку `E_USER_WARNING`. Встановлення на `true` призведе до придушення цього повідомлення про помилку. У робочому середовищі `E_USER_WARNING` викликається завжди. Цю поведінку можна також контролювати, встановивши змінну presenter [$invalidLinkMode |creating-links#Недійсні посилання]. +[Аліаси спрощують посилання |creating-links#Аліаси] на часто використовувані презентери. -Автореєстрація презентерів .[#toc-automatic-registration-of-presenters] ------------------------------------------------------------------------ +[Мапінг визначає правила |directory-structure#Мапінг presenter ів], за якими з назви presenter виводиться назва класу. -Nette автоматично додає презентери як сервіси в контейнер DI, що значно прискорює їхнє створення. Як Nette впізнає презентери, можна налаштувати: + +Автоматична реєстрація презентерів +---------------------------------- + +Nette автоматично додає презентери як сервіси до DI-контейнера, що суттєво прискорює їхнє створення. Як Nette знаходить презентери, можна налаштувати: ```neon application: - # для пошуку презентерів у карті класів Composer? + # шукати презентери в Composer class map? scanComposer: ... # (bool) за замовчуванням true - # маска, яка має відповідати класу та імені файлу + # маска, якій має відповідати назва класу та файлу scanFilter: ... # (string) за замовчуванням '*Presenter' # у яких каталогах шукати презентери? @@ -52,7 +65,7 @@ application: - %vendorDir%/mymodule ``` -Каталоги, перераховані в `scanDirs`, не скасовують значення за замовчуванням `%appDir%`, а доповнюють його, тому `scanDirs` міститиме обидва шляхи `%appDir%` і `%vendorDir%/mymodule`. Щоб перезаписати каталог за замовчуванням, ми використовуємо [знак оклику |dependency-injection:configuration#Merging]: +Каталоги, зазначені в `scanDirs`, не перезаписують стандартне значення `%appDir%`, а доповнюють його, отже `scanDirs` міститиме обидва шляхи `%appDir%` та `%vendorDir%/mymodule`. Якщо ми хочемо виключити стандартний каталог, використаємо [знак оклику |dependency-injection:configuration#Об єднання], який перезапише значення: ```neon application: @@ -60,64 +73,73 @@ application: - %vendorDir%/mymodule ``` -Сканування каталогу можна відключити, задавши значення `false`. Ми не рекомендуємо повністю пригнічувати автоматичне додавання презентерів, інакше продуктивність програми знизиться. +Сканування каталогів можна вимкнути, вказавши значення false. Не рекомендуємо повністю придушувати автоматичне додавання презентерів, оскільки інакше це призведе до зниження швидкодії застосунку. -Latte .[#toc-latte] -=================== +Шаблони Latte +============= -Це налаштування глобально впливає на поведінку Latte в компонентах і презентерах. +За допомогою цього налаштування можна глобально вплинути на поведінку Latte в компонентах та презентерах. ```neon latte: - # відображає вкладку Latte на панелі Tracy для основного шаблону (true) або для всіх компонентів (all)? + # показувати панель Latte в Tracy Bar для головного шаблону (true) або всіх компонентів (all)? debugger: ... # (true|false|'all') за замовчуванням true - # генерує шаблони з declare(strict_types=1) + # генерує шаблони із заголовком declare(strict_types=1) strictTypes: ... # (bool) за замовчуванням false - # клас $this->template + # вмикає режим [суворого парсера |latte:develop#striktní režim] + strictParsing: ... # (bool) за замовчуванням false + + # активує [перевірку згенерованого коду |latte:develop#Kontrola vygenerovaného kódu] + phpLinter: ... # (string) за замовчуванням null + + # встановлює локаль + locale: cs_CZ # (string) за замовчуванням null + + # клас об'єкта $this->template templateClass: App\MyTemplateClass # за замовчуванням Nette\Bridges\ApplicationLatte\DefaultTemplate ``` -Якщо ви використовуєте Latte версії 3, ви можете додати нове [розширення |latte:creating-extension], використовуючи: +Якщо ви використовуєте Latte версії 3, ви можете додавати нові [розширення |latte:extending-latte#Latte Extension] за допомогою: ```neon latte: - расширения: - - Latte\Essential\TranslatorExtension + extensions: + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -/--comment - - - - +Якщо ви використовуєте Latte версії 2, ви можете реєструвати нові теги, вказавши ім'я класу або посилання на сервіс. За замовчуванням викликається метод `install()`, але це можна змінити, вказавши ім'я іншого методу: +```neon +latte: + # реєстрація користувацьких тегів Latte + macros: + - App\MyLatteMacros::register # статичний метод, назва класу або callable + - @App\MyLatteMacrosFactory # сервіс з методом install() + - @App\MyLatteMacrosFactory::register # сервіс з методом register() + +services: + - App\MyLatteMacrosFactory +``` - - - - -\-- - - -Маршрутизація .[#toc-routing] -============================= +Маршрутизація +============= Основні налаштування: ```neon routing: - # відображає вкладку Routing на панелі Tracy? + # показувати панель маршрутизації в Tracy Bar? debugger: ... # (bool) за замовчуванням true - # здійснювати серіалізацію маршрутів у DI-контейнері? + # серіалізує маршрутизатор до DI-контейнера cache: ... # (bool) за замовчуванням false ``` -Маршрути зазвичай визначаються в класі RouterFactory. Альтернативно, прості правила маршрутизації можна визначити в конфігурації за допомогою пар `маска: действие`: +Маршрутизацію зазвичай визначаємо в класі [RouterFactory |routing#Колекція маршрутів]. Альтернативно, маршрути можна визначити також у конфігурації за допомогою пар `маска: дія`, але цей спосіб не пропонує такої широкої варіативності в налаштуваннях: ```neon routing: @@ -127,28 +149,43 @@ routing: ``` -Константи .[#toc-constants] -=========================== +Константи +========= -Створення констант PHP: +Створення PHP-констант. ```neon constants: Foobar: 'baz' ``` -Константа `Foobar` буде створена після запуску. +Після запуску застосунку буде створена константа `Foobar`. .[note] -Константи не повинні слугувати як глобально доступні змінні. Для передачі значень об'єктам використовуйте [dependency injection |dependency-injection:passing-dependencies]. +Константи не повинні слугувати як якісь глобально доступні змінні. Для передачі значень в об'єкти використовуйте [впровадження залежностей |dependency-injection:passing-dependencies]. PHP === -Ви можете встановлювати директиви PHP. Огляд усіх директив можна знайти на сайті [php.net |https://www.php.net/manual/ru/ini.list.php]. +Налаштування директив PHP. Огляд усіх директив ви знайдете на [php.net |https://www.php.net/manual/en/ini.list.php]. ```neon php: date.timezone: Europe/Prague ``` + + +Сервіси DI +========== + +Ці сервіси додаються до DI-контейнера: + +| Назва | Тип | Опис +|---------------------------------------------------------- +| `application.application` | [api:Nette\Application\Application] | [запускач усього застосунку |how-it-works#Nette Application] +| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] +| `application.presenterFactory` | [api:Nette\Application\PresenterFactory] | фабрика презентерів +| `application.###` | [api:Nette\Application\UI\Presenter] | окремі презентери +| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | фабрика об'єкта `Latte\Engine` +| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | фабрика для [`$this->template` |templates] diff --git a/application/uk/creating-links.texy b/application/uk/creating-links.texy index 34ec0457fb..361bc0c8ab 100644 --- a/application/uk/creating-links.texy +++ b/application/uk/creating-links.texy @@ -3,237 +3,261 @@
    -Створювати посилання в Nette так само просто, як тикати пальцем. Просто наведіть курсор, і система зробить усю роботу за вас. Ми покажемо: +Створювати посилання в Nette просто, як показувати пальцем. Достатньо лише вказати напрямок, і фреймворк зробить усю роботу за вас. Ми покажемо: - як створювати посилання в шаблонах та інших місцях -- як виділити посилання на поточну сторінку +- як відрізнити посилання на поточну сторінку - що робити з недійсними посиланнями
    -Завдяки [двонаправленій маршрутизації |routing], вам ніколи не доведеться жорстко кодувати URL додатка в шаблонах або коді, які можуть змінитися пізніше або бути складними для складання. Просто вкажіть презентера і дію в посиланні, передайте будь-які параметри, і фреймворк сам згенерує URL. Фактично, це дуже схоже на виклик функції. Вам сподобається. +Завдяки [двосторонньому роутингу |routing] вам ніколи не доведеться вписувати URL-адреси вашого застосунку вручну в шаблони чи код, оскільки вони можуть згодом змінитися, або складно їх складати. У посиланні достатньо вказати presenter та дію, передати можливі параметри, і фреймворк сам згенерує URL. Власне, це дуже схоже на виклик функції. Вам це сподобається. -У шаблоні презентера .[#toc-in-the-presenter-template] -====================================================== +У шаблоні presenter'а +===================== Найчастіше ми створюємо посилання в шаблонах, і чудовим помічником є атрибут `n:href`: ```latte -подробнее +деталі ``` -Зверніть увагу, що замість HTML-атрибута `href` ми використовували [n:attribute |latte:syntax#n:attributes] `n:href`. Його значенням є не URL, як ви звикли бачити в атрибуті `href`, а ім'я презентера та дія. +Зверніть увагу, що замість HTML-атрибута `href` ми використали [n:атрибут |latte:syntax#n:атрибути] `n:href`. Його значенням є не URL, як це було б у випадку атрибута `href`, а назва presenter'а та дії. -Натискання на посилання, простіше кажучи, є чимось на зразок виклику методу `ProductPresenter::renderShow()`. І якщо в його сигнатурі є параметри, ми можемо викликати його з аргументами: +Натискання на посилання, спрощено кажучи, схоже на виклик методу `ProductPresenter::renderShow()`. І якщо він має параметри у своїй сигнатурі, ми можемо викликати його з аргументами: ```latte -подробнее +деталі продукту ``` -Також можна передавати іменовані параметри. Наступне посилання передає параметр `lang` зі значенням `en`: +Можна передавати й іменовані параметри. Наступне посилання передає параметр `lang` зі значенням `cs`: ```latte -подробнее +деталі продукту ``` -Якщо метод `ProductPresenter::renderShow()` не має `$lang` у своїй сигнатурі, він може прочитати значення параметра, використовуючи `$lang = $this->getParameter('lang')`. +Якщо метод `ProductPresenter::renderShow()` не має `$lang` у своїй сигнатурі, він може отримати значення параметра за допомогою `$lang = $this->getParameter('lang')` або з [властивості |presenters#Параметри запиту]. -Якщо параметри зберігаються в масиві, їх можна розширити за допомогою оператора `(expand)` (щось на зразок оператора `...` у PHP, але працює з асоціативними масивами): +Якщо параметри зберігаються в масиві, їх можна розгорнути оператором `...` (в Latte 2.x оператором `(expand)`): ```latte -{var $args = [$product->id, lang => en]} -подробнее +{var $args = [$product->id, lang => cs]} +деталі продукту ``` -Так звані [постійні параметри |presenters#Persistent-Parameters] також автоматично передаються в посиланнях. +У посиланнях також автоматично передаються так звані [персистентні параметри |presenters#Персистентні параметри]. -Атрибут `n:href` дуже зручний для HTML-тегів ``. Якщо ми хочемо вивести посилання в іншому місці, наприклад, у тексті, ми використовуємо `{link}`: +Атрибут `n:href` дуже зручний для HTML-тегів ``. Якщо ми хочемо вивести посилання в іншому місці, наприклад, у тексті, використовуємо `{link}`: ```latte -URL: {link Home:default} +Адреса: {link Home:default} ``` -У коді .[#toc-in-the-code] -========================== +У коді +====== -Метод `link()` використовується для створення посилання в презентері: +Для створення посилання в presenter'і служить метод `link()`: ```php $url = $this->link('Product:show', $product->id); ``` -Параметри також можуть бути передані у вигляді масиву, в якому також можуть бути вказані іменовані параметри: +Параметри можна передати також за допомогою масиву, де можна вказати й іменовані параметри: ```php $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); ``` -Посилання можна створювати і без презентера, використовуючи [LinkGenerator |#LinkGenerator] і його метод `link()`. +Посилання можна створювати і без presenter'а, для цього існує [#LinkGenerator] та його метод `link()`. -Посилання на презентер .[#toc-links-to-presenter] -================================================= +Посилання на presenter +====================== -Якщо метою посилання є презентер і дія, воно має такий синтаксис: +Якщо ціллю посилання є presenter та дія, воно має такий синтаксис: ``` [//] [[[[:]module:]presenter:]action | this] [#fragment] ``` -Формат підтримується всіма тегами Latte і всіма методами презентера, які працюють із посиланнями, тобто `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()`, а також [LinkGenerator |#LinkGenerator]. Тому, навіть якщо в прикладах використовується `n:href`, тут може бути будь-яка з функцій. +Формат підтримують усі теги Latte та всі методи presenter'а, які працюють з посиланнями, тобто `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()`, а також [#LinkGenerator]. Тому, хоча в прикладах використано `n:href`, там могла б бути будь-яка з функцій. -Тому основною формою є `Presenter:action`: +Основною формою є `Presenter:action`: ```latte -главная страница +головна сторінка ``` -Якщо ми посилаємося на дію поточного презентера, ми можемо опустити його ім'я: +Якщо ми посилаємося на дію поточного presenter'а, ми можемо опустити його назву: ```latte -главная страница +головна сторінка ``` -Якщо дія є `default`, ми можемо опустити її, але двокрапка має залишитися: +Якщо ціллю є дія `default`, ми можемо її опустити, але двокрапка має залишитися: ```latte -главная страница +головна сторінка ``` -Посилання можуть також вказувати на інші [модулі |modules]. Тут посилання розрізняються на відносні по відношенню до підмодулів або абсолютні. Принцип аналогічний дисковим шляхам, тільки замість косих рисок стоять двокрапки. Припустимо, що справжній презентер є частиною модуля `Front`, тоді ми напишемо: +Посилання також можуть вказувати на інші [модулі |directory-structure#Presenter и та шаблони]. Тут посилання розрізняються на відносні до вкладеного підмодуля або абсолютні. Принцип аналогічний до шляхів на диску, тільки замість слешів використовуються двокрапки. Припустимо, що поточний presenter є частиною модуля `Front`, тоді запишемо: ```latte -ссылка на Front:Shop:Product:show -ссылка на Admin:Product:show +посилання на Front:Shop:Product:show +посилання на Admin:Product:show ``` -Особливим випадком є [посилання на себе |#Link-to-Current-Page]. Тут ми напишемо `this` як ціль. +Особливим випадком є посилання [на себе |#Посилання на поточну сторінку], коли як ціль вказуємо `this`. ```latte -refresh +оновити ``` -Ми можемо посилатися на певну частину HTML-сторінки через так званий фрагмент після хеш-символу `#`: +Ми можемо посилатися на певну частину сторінки через так званий фрагмент за знаком решітки `#`: ```latte -ссылка на Home:default и фрагмент #main +посилання на Home:default та фрагмент #main ``` -Абсолютні шляхи .[#toc-absolute-paths] -====================================== +Абсолютні шляхи +=============== -Посилання, що генеруються `link()` або `n:href`, завжди є абсолютними шляхами (тобто починаються з `/`), але не абсолютними URL з протоколом і доменом, як `https://domain`. +Посилання, згенеровані за допомогою `link()` або `n:href`, завжди є абсолютними шляхами (тобто починаються зі знака `/`), але не абсолютними URL з протоколом та доменом, як `https://domain`. -Щоб створити абсолютний URL, додайте дві косі риски на початок (наприклад, `n:href="//Home:"`). Або ви можете перемкнути презентатор на генерацію тільки абсолютних посилань, встановивши `$this->absoluteUrls = true`. +Для генерації абсолютного URL додайте на початок два слеші (наприклад, `n:href="//Home:"`). Або можна перемкнути presenter, щоб він генерував лише абсолютні посилання, встановивши `$this->absoluteUrls = true`. -Посилання на поточну сторінку .[#toc-link-to-current-page] -========================================================== +Посилання на поточну сторінку +============================= Ціль `this` створить посилання на поточну сторінку: ```latte -обновить +оновити ``` -При цьому передаються всі параметри, зазначені в сигнатурі методу `render()` або `action()`. Таким чином, якщо ми перебуваємо на сторінках `Product:show` і `id:123`, посилання на `this` також передаватиме цей параметр. +Водночас передаються всі параметри, зазначені в сигнатурі методу `action()` або `render()`, якщо `action()` не визначено. Отже, якщо ми на сторінці `Product:show` і `id: 123`, посилання на `this` передасть і цей параметр. Звичайно, можна вказати параметри безпосередньо: ```latte -обновить +оновити ``` -Метод презентера `isLinkCurrent()` визначає, чи збігається мета посилання з поточною сторінкою. Це можна використовувати, наприклад, у шаблоні для розмежування посилань тощо. +Функція `isLinkCurrent()` перевіряє, чи ціль посилання збігається з поточною сторінкою. Це можна використати, наприклад, у шаблоні для розрізнення посилань тощо. -Параметри ті самі, що й для методу `link()`, але також можна використовувати підстановний знак `*` замість конкретної дії, що означає будь-яку дію презентера. +Параметри такі ж, як у методі `link()`, але додатково можна замість конкретної дії вказати заступний знак `*`, який означає будь-яку дію даного presenter'а. ```latte -{if !$presenter->isLinkCurrent('Admin:login')} - Войти +{if !isLinkCurrent('Admin:login')} + Увійдіть {/if} -
  • +
  • ...
  • ``` -Скорочена форма може використовуватися в поєднанні з `n:href` в одному елементі: +У комбінації з `n:href` в одному елементі можна використовувати скорочену форму: ```latte -... +... ``` -Символ підстановки `*` замінює тільки дію презентера, але не сам презентер. +Заступний знак `*` можна використовувати лише замість дії, а не presenter'а. -Щоб дізнатися, чи перебуваємо ми в певному модулі або його підмодулі, ми можемо використовувати метод `$presenter->isModuleCurrent(moduleName)`. +Для перевірки, чи ми знаходимося в певному модулі або його підмодулі, використовуємо метод `isModuleCurrent(moduleName)`. ```latte -
  • +
  • ...
  • ``` -Посилання на сигнал .[#toc-links-to-signal] -=========================================== +Посилання на сигнал +=================== -Метою посилання може бути не тільки презентер і дія, а й [сигнал |components#Signal] (вони викликають метод `handle()`). Синтаксис наступний: +Ціллю посилання може бути не тільки presenter та дія, але й [сигнал |components#Сигнал] (викликають метод `handle()`). Тоді синтаксис такий: ``` [//] [sub-component:]signal! [#fragment] ``` -Тому сигнал виділяється знаком оклику: +Сигнал, отже, відрізняється знаком оклику: ```latte -signal +сигнал ``` -Ви також можете створити посилання на сигнал підкомпонента (або підсубкомпонента): +Можна створити й посилання на сигнал підкомпонента (або під-підкомпонента): ```latte -signal +сигнал ``` -Посилання на компонент .[#toc-links-in-component] -================================================= +Посилання в компоненті +====================== -Оскільки [компоненти |components] є окремими багаторазово використовуваними одиницями, які не повинні мати жодних стосунків з оточуючими презентерами, посилання працюють дещо по-іншому. Атрибут Latte `n:href` і тег `{link}`, а також методи компонентів, такі як `link()` та інші, завжди розглядають ціль **як ім'я сигналу**. Тому немає необхідності використовувати знак оклику: +Оскільки [компоненти|components] є окремими повторно використовуваними одиницями, які не повинні мати жодних зв'язків з навколишніми presenter'ами, посилання тут працюють трохи інакше. Атрибут Latte `n:href` та тег `{link}`, а також методи компонента, такі як `link()` та інші, розглядають ціль посилання **завжди як назву сигналу**. Тому навіть не потрібно вказувати знак оклику: ```latte -сигнал, не действие +сигнал, а не дія ``` -Якщо ми хочемо зробити посилання на презентери в шаблоні компонента, ми використовуємо тег `{plink}`: +Якщо ми хочемо в шаблоні компонента посилатися на presenter'ів, використовуємо для цього тег `{plink}`: ```latte -главная страница +вступ ``` -or in the code +або в коді ```php $this->getPresenter()->link('Home:default') ``` -Недійсні посилання .[#toc-invalid-links] -======================================== +Аліаси .{data-version:v3.2.2} +============================= -Може трапитися так, що ми створимо некоректне посилання - або через те, що воно посилається на неіснуючий презентер, або через те, що воно передає більше параметрів, ніж цільовий метод отримує у своїй сигнатурі, або коли не може бути згенеровано URL для цільової дії. Що робити з недійсними посиланнями, визначається статичною змінною `Presenter::$invalidLinkMode`. Вона може мати одне з цих значень (констант): +Іноді може бути корисно призначити парі Presenter:дія легко запам'ятовуваний псевдонім. Наприклад, головну сторінку `Front:Home:default` назвати просто `home` або `Admin:Dashboard:default` як `admin`. -- `Presenter::InvalidLinkSilent` - тихий режим, повертає символ `#` як URL-адресу -- `Presenter::InvalidLinkWarning` - буде видано повідомлення E_USER_WARNING -- `Presenter::InvalidLinkTextual` - візуальне попередження, текст помилки відображається в посиланні -- `Presenter::InvalidLinkException` - буде викинуто виняток InvalidLinkException +Аліаси визначаються в [конфігурації|configuration] під ключем `application › aliases`: -За замовчуванням у робочому режимі використовується параметр `InvalidLinkWarning`, а в режимі розробки - `InvalidLinkWarning | InvalidLinkTextual`. `InvalidLinkWarning` не вбиває сценарій у робочому середовищі, але попередження буде зареєстровано в журналі. У середовищі розробки [Tracy |tracy:] перехопить попередження і відобразить синю сторінку помилки. Якщо встановлено `InvalidLinkTextual`, презентер і компоненти повертають повідомлення про помилку у вигляді URL-адреси, яку позначено `#error:`. Щоб зробити такі посилання видимими, ми можемо додати правило CSS до нашої таблиці стилів: +```neon +application: + aliases: + home: Front:Home:default + admin: Admin:Dashboard:default + sign: Front:Sign:in +``` + +У посиланнях вони потім записуються за допомогою символу @, наприклад: + +```latte +адміністрація +``` + +Вони також підтримуються у всіх методах, що працюють з посиланнями, таких як `redirect()` тощо. + + +Недійсні посилання +================== + +Може статися, що ми створимо недійсне посилання - або тому, що воно веде на неіснуючий presenter, або тому, що передає більше параметрів, ніж цільовий метод приймає у своїй сигнатурі, або коли для цільової дії неможливо згенерувати URL. Як поводитися з недійсними посиланнями, визначає статична змінна `Presenter::$invalidLinkMode`. Вона може набувати комбінації таких значень (констант): + +- `Presenter::InvalidLinkSilent` - тихий режим, як URL повертається знак # +- `Presenter::InvalidLinkWarning` - викидається попередження E_USER_WARNING, яке в робочому режимі буде залоговано, але не спричинить переривання виконання скрипта +- `Presenter::InvalidLinkTextual` - візуальне попередження, виводить помилку безпосередньо в посиланні +- `Presenter::InvalidLinkException` - викидається виняток InvalidLinkException + +Стандартне налаштування — `InvalidLinkWarning` у робочому режимі та `InvalidLinkWarning | InvalidLinkTextual` у режимі розробки. `InvalidLinkWarning` у робочому середовищі не спричиняє переривання скрипта, але попередження буде залоговано. У середовищі розробки його перехопить [Tracy |tracy:] і відобразить блюскрін. `InvalidLinkTextual` працює так, що як URL повертає повідомлення про помилку, яке починається символами `#error:`. Щоб такі посилання були помітні з першого погляду, доповнимо CSS: ```css a[href^="#error:"] { @@ -242,7 +266,7 @@ a[href^="#error:"] { } ``` -Якщо ми не хочемо, щоб попередження створювалися в середовищі розробки, ми можемо ввімкнути режим автоматичного неприпустимого зв'язку в [конфігурації |configuration]. +Якщо ми не хочемо, щоб у середовищі розробки генерувалися попередження, можемо встановити тихий режим безпосередньо в [конфігурації|configuration]. ```neon application: @@ -250,13 +274,13 @@ application: ``` -LinkGenerator .[#toc-linkgenerator] -=================================== +LinkGenerator +============= -Як створювати посилання з таким же комфортом, як з методом `link()`, але без презентера? Для цього в нас є [api:Nette\Application\LinkGenerator]. +Як створювати посилання з такою ж зручністю, як метод `link()`, але без наявності presenter'а? Для цього існує [api:Nette\Application\LinkGenerator]. -LinkGenerator - це сервіс, який можна передати через конструктор, а потім створити посилання за допомогою методу 'link()'. +LinkGenerator — це сервіс, який ви можете отримати через конструктор, а потім створювати посилання його методом `link()`. -Є різниця порівняно з презентерами. LinkGenerator створює всі посилання як абсолютні URL-адреси. Крім того, немає "поточного презентера", тому неможливо вказати тільки ім'я дії 'link('default')' або відносні шляхи до модулів. +Порівняно з presenter'ами є відмінність. LinkGenerator створює всі посилання одразу як абсолютні URL. Також не існує "поточного presenter'а", тому не можна як ціль вказати лише назву дії `link('default')` або вказувати відносні шляхи до модулів. -Неприпустимі посилання завжди викидають виняток `Nette\Application\UI\InvalidLinkException`. +Недійсні посилання завжди викидають `Nette\Application\UI\InvalidLinkException`. diff --git a/application/uk/directory-structure.texy b/application/uk/directory-structure.texy new file mode 100644 index 0000000000..2643f4f031 --- /dev/null +++ b/application/uk/directory-structure.texy @@ -0,0 +1,526 @@ +Структура каталогів застосунку +****************************** + +
    + +Як спроектувати зрозумілу та масштабовану структуру каталогів для проектів на Nette Framework? Ми покажемо перевірені практики, які допоможуть вам організувати код. Ви дізнаєтеся: + +- як **логічно розділити** застосунок на каталоги +- як спроектувати структуру так, щоб вона **добре масштабувалася** зі зростанням проекту +- які є **можливі альтернативи** та їхні переваги чи недоліки + +
    + + +Важливо зазначити, що сам Nette Framework не наполягає на жодній конкретній структурі. Він розроблений так, щоб його можна було легко адаптувати до будь-яких потреб та уподобань. + + +Базова структура проекту +======================== + +Хоча Nette Framework не диктує жодної жорсткої структури каталогів, існує перевірене стандартне розташування у вигляді [Web Project|https://github.com/nette/web-project]: + +/--pre +web-project/ +├── app/ ← каталог із застосунком +├── assets/ ← файли SCSS, JS, зображення..., альтернативно resources/ +├── bin/ ← скрипти для командного рядка +├── config/ ← конфігурація +├── log/ ← залоговані помилки +├── temp/ ← тимчасові файли, кеш +├── tests/ ← тести +├── vendor/ ← бібліотеки, встановлені Composer +└── www/ ← публічний каталог (document-root) +\-- + +Цю структуру ви можете вільно змінювати відповідно до своїх потреб - папки перейменовувати чи переміщувати. Потім достатньо лише змінити відносні шляхи до каталогів у файлі `Bootstrap.php` та, можливо, `composer.json`. Більше нічого не потрібно, жодної складної реконфігурації, жодних змін констант. Nette має розумне автовизначення і автоматично розпізнає розташування застосунку, включно з його базовим URL. + + +Принципи організації коду +========================= + +Коли ви вперше досліджуєте новий проект, ви повинні швидко в ньому зорієнтуватися. Уявіть, що ви розкриваєте каталог `app/Model/` і бачите таку структуру: + +/--pre +app/Model/ +├── Services/ +├── Repositories/ +└── Entities/ +\-- + +З неї ви дізнаєтеся лише те, що проект використовує якісь сервіси, репозиторії та сутності. Про справжнє призначення застосунку ви не дізнаєтеся абсолютно нічого. + +Розглянемо інший підхід - **організацію за доменами**: + +/--pre +app/Model/ +├── Cart/ +├── Payment/ +├── Order/ +└── Product/ +\-- + +Тут все інакше - з першого погляду зрозуміло, що це інтернет-магазин. Вже самі назви каталогів розкривають, що вміє застосунок - працює з платежами, замовленнями та продуктами. + +Перший підхід (організація за типом класів) на практиці спричиняє низку проблем: код, який логічно пов'язаний, розкиданий по різних папках, і вам доводиться між ними перескакувати. Тому ми будемо організовувати за доменами. + + +Простори імен +------------- + +Зазвичай структура каталогів відповідає просторам імен у застосунку. Це означає, що фізичне розташування файлів відповідає їхньому namespace. Наприклад, клас, розташований у `app/Model/Product/ProductRepository.php`, повинен мати namespace `App\Model\Product`. Цей принцип допомагає орієнтуватися в коді та спрощує автозавантаження. + + +Однина проти множини в назвах +----------------------------- + +Зверніть увагу, що для основних каталогів застосунку ми використовуємо однину: `app`, `config`, `log`, `temp`, `www`. Так само і всередині застосунку: `Model`, `Core`, `Presentation`. Це тому, що кожен з них представляє одну цілісну концепцію. + +Подібно, наприклад, `app/Model/Product` представляє все, що стосується продуктів. Ми не назвемо це `Products`, оскільки це не папка, повна продуктів (там були б файли `nokia.php`, `samsung.php`). Це namespace, що містить класи для роботи з продуктами - `ProductRepository.php`, `ProductService.php`. + +Папка `app/Tasks` у множині, оскільки містить набір окремих виконуваних скриптів - `CleanupTask.php`, `ImportTask.php`. Кожен з них є окремою одиницею. + +Для послідовності рекомендуємо використовувати: +- Однину для namespace, що представляє функціональну одиницю (хоча й працює з кількома сутностями) +- Множину для колекцій окремих одиниць +- У разі невизначеності або якщо ви не хочете над цим замислюватися, вибирайте однину + + +Публічний каталог `www/` +======================== + +Цей каталог є єдиним доступним з вебу (так званий document-root). Часто можна зустріти назву `public/` замість `www/` - це лише питання конвенції і на функціональність це не впливає. Каталог містить: +- [Точка входу |bootstrapping#index.php] застосунку `index.php` +- Файл `.htaccess` з правилами для mod_rewrite (для Apache) +- Статичні файли (CSS, JavaScript, зображення) +- Завантажені файли + +Для належного захисту застосунку важливо мати правильно [налаштований document-root |nette:troubleshooting#Як змінити або видалити каталог www з URL]. + +.[note] +Ніколи не розміщуйте в цьому каталозі папку `node_modules/` - вона містить тисячі файлів, які можуть бути виконуваними і не повинні бути публічно доступними. + + +Каталог застосунку `app/` +========================= + +Це головний каталог з кодом застосунку. Базова структура: + +/--pre +app/ +├── Core/ ← інфраструктурні питання +├── Model/ ← бізнес-логіка +├── Presentation/ ← presenter'и та шаблони +├── Tasks/ ← скрипти командного рядка +└── Bootstrap.php ← завантажувальний клас застосунку +\-- + +`Bootstrap.php` — це [стартовий клас застосунку|bootstrapping], який ініціалізує середовище, завантажує конфігурацію та створює DI-контейнер. + +Тепер розглянемо окремі підкаталоги детальніше. + + +Presenter'и та шаблони +====================== + +Презентаційна частина застосунку знаходиться в каталозі `app/Presentation`. Альтернативою є коротке `app/UI`. Це місце для всіх presenter'ів, їхніх шаблонів та можливих допоміжних класів. + +Цей шар ми організовуємо за доменами. У складному проекті, який поєднує інтернет-магазин, блог та API, структура виглядала б так: + +/--pre +app/Presentation/ +├── Shop/ ← фронтенд інтернет-магазину +│ ├── Product/ +│ ├── Cart/ +│ └── Order/ +├── Blog/ ← блог +│ ├── Home/ +│ └── Post/ +├── Admin/ ← адміністрація +│ ├── Dashboard/ +│ └── Products/ +└── Api/ ← кінцеві точки API + └── V1/ +\-- + +Навпаки, для простого блогу ми б використали такий поділ: + +/--pre +app/Presentation/ +├── Front/ ← фронтенд сайту +│ ├── Home/ +│ └── Post/ +├── Admin/ ← адміністрація +│ ├── Dashboard/ +│ └── Posts/ +├── Error/ +└── Export/ ← RSS, sitemaps тощо. +\-- + +Папки, такі як `Home/` або `Dashboard/`, містять presenter'и та шаблони. Папки, такі як `Front/`, `Admin/` або `Api/`, називаємо **модулями**. Технічно це звичайні каталоги, які служать для логічного поділу застосунку. + +Кожна папка з presenter'ом містить однойменний presenter та його шаблони. Наприклад, папка `Dashboard/` містить: + +/--pre +Dashboard/ +├── DashboardPresenter.php ← presenter +└── default.latte ← шаблон +\-- + +Ця структура каталогів відображається в просторах імен класів. Наприклад, `DashboardPresenter` знаходиться в просторі імен `App\Presentation\Admin\Dashboard` (див. [#Мапінг presenter ів]): + +```php +namespace App\Presentation\Admin\Dashboard; + +class DashboardPresenter extends Nette\Application\UI\Presenter +{ + // ... +} +``` + +На presenter `Dashboard` всередині модуля `Admin` ми посилаємося в застосунку за допомогою двокрапкової нотації як на `Admin:Dashboard`. На його дію `default` — як на `Admin:Dashboard:default`. У випадку вкладених модулів використовуємо більше двокрапок, наприклад `Shop:Order:Detail:default`. + + +Гнучкий розвиток структури +-------------------------- + +Однією з великих переваг цієї структури є те, як елегантно вона адаптується до зростаючих потреб проекту. Як приклад візьмемо частину, що генерує XML-фіди. На початку маємо просту форму: + +/--pre +Export/ +├── ExportPresenter.php ← один presenter для всіх експортів +├── sitemap.latte ← шаблон для sitemap +└── feed.latte ← шаблон для RSS-фіду +\-- + +З часом з'являться інші типи фідів, і нам знадобиться для них більше логіки... Жодних проблем! Папка `Export/` просто стає модулем: + +/--pre +Export/ +├── Sitemap/ +│ ├── SitemapPresenter.php +│ └── sitemap.latte +└── Feed/ + ├── FeedPresenter.php + ├── zbozi.latte ← фід для Zboží.cz + └── heureka.latte ← фід для Heureka.cz +\-- + +Ця трансформація абсолютно плавна - достатньо створити нові підпапки, розділити в них код і оновити посилання (наприклад, з `Export:feed` на `Export:Feed:zbozi`). Завдяки цьому ми можемо структуру поступово розширювати за потребою, рівень вкладеності ніяк не обмежений. + +Якщо, наприклад, в адміністрації у вас багато presenter'ів, що стосуються управління замовленнями, таких як `OrderDetail`, `OrderEdit`, `OrderDispatch` тощо, ви можете для кращої організації в цьому місці створити модуль (папку) `Order`, в якому будуть (папки для) presenter'ів `Detail`, `Edit`, `Dispatch` та інші. + + +Розташування шаблонів +--------------------- + +У попередніх прикладах ми бачили, що шаблони розташовані безпосередньо в папці з presenter'ом: + +/--pre +Dashboard/ +├── DashboardPresenter.php ← presenter +├── DashboardTemplate.php ← необов'язковий клас для шаблону +└── default.latte ← шаблон +\-- + +Це розташування на практиці виявляється найзручнішим - усі пов'язані файли у вас одразу під рукою. + +Альтернативно, ви можете розмістити шаблони в підпапці `templates/`. Nette підтримує обидва варіанти. Ви навіть можете розмістити шаблони повністю поза папкою `Presentation/`. Все про можливості розташування шаблонів ви знайдете в розділі [Пошук шаблонів |templates#Пошук шаблонів]. + + +Допоміжні класи та компоненти +----------------------------- + +До presenter'ів та шаблонів часто належать й інші допоміжні файли. Розмістимо їх логічно відповідно до їхньої сфери дії: + +1. **Безпосередньо біля presenter'а** у випадку специфічних компонентів для даного presenter'а: + +/--pre +Product/ +├── ProductPresenter.php +├── ProductGrid.php ← компонент для виведення продуктів +└── FilterForm.php ← форма для фільтрації +\-- + +2. **Для модуля** - рекомендуємо використовувати папку `Accessory`, яка розміщується зручно на початку алфавіту: + +/--pre +Front/ +├── Accessory/ +│ ├── NavbarControl.php ← компоненти для фронтенду +│ └── TemplateFilters.php +├── Product/ +└── Cart/ +\-- + +3. **Для всього застосунку** - в `Presentation/Accessory/`: +/--pre +app/Presentation/ +├── Accessory/ +│ ├── LatteExtension.php +│ └── TemplateFilters.php +├── Front/ +└── Admin/ +\-- + +Або ви можете розмістити допоміжні класи, такі як `LatteExtension.php` або `TemplateFilters.php`, в інфраструктурній папці `app/Core/Latte/`. А компоненти — в `app/Components`. Вибір залежить від звичок команди. + + +Модель - серце застосунку +========================= + +Модель містить усю бізнес-логіку застосунку. Для її організації знову діє правило - структуруємо за доменами: + +/--pre +app/Model/ +├── Payment/ ← все, що стосується платежів +│ ├── PaymentFacade.php ← головна точка входу +│ ├── PaymentRepository.php +│ ├── Payment.php ← сутність +├── Order/ ← все, що стосується замовлень +│ ├── OrderFacade.php +│ ├── OrderRepository.php +│ ├── Order.php +└── Shipping/ ← все, що стосується доставки +\-- + +У моделі зазвичай зустрічаються такі типи класів: + +**Фасади**: представляють головну точку входу до конкретної домени в застосунку. Діють як оркестратор, який координує співпрацю між різними сервісами з метою реалізації повних use-cases (як "створити замовлення" або "обробити платіж"). Під своїм оркестраційним шаром фасад приховує деталі реалізації від решти застосунку, чим надає чистий інтерфейс для роботи з даною доменою. + +```php +class OrderFacade +{ + public function createOrder(Cart $cart): Order + { + // валідація + // створення замовлення + // надсилання електронного листа + // запис у статистику + } +} +``` + +**Сервіси**: зосереджуються на специфічній бізнес-операції в межах домени. На відміну від фасаду, який оркеструє цілі use-cases, сервіс реалізує конкретну бізнес-логіку (як розрахунки цін або обробка платежів). Сервіси зазвичай без стану і можуть бути використані або фасадами як будівельні блоки для складніших операцій, або безпосередньо іншими частинами застосунку для простіших завдань. + +```php +class PricingService +{ + public function calculateTotal(Order $order): Money + { + // розрахунок ціни + } +} +``` + +**Репозиторії**: забезпечують усю комунікацію з сховищем даних, зазвичай базою даних. Його завданням є завантаження та збереження сутностей та реалізація методів для їх пошуку. Репозиторій відокремлює решту застосунку від деталей реалізації бази даних і надає об'єктно-орієнтований інтерфейс для роботи з даними. + +```php +class OrderRepository +{ + public function find(int $id): ?Order + { + } + + public function findByCustomer(int $customerId): array + { + } +} +``` + +**Сутності**: об'єкти, що представляють основні бізнес-концепції в застосунку, які мають свою ідентичність і змінюються з часом. Зазвичай це класи, що мапуються на таблиці бази даних за допомогою ORM (як Nette Database Explorer або Doctrine). Сутності можуть містити бізнес-правила, що стосуються їхніх даних, та логіку валідації. + +```php +// Сутність, мапована на таблицю бази даних orders +class Order extends Nette\Database\Table\ActiveRow +{ + public function addItem(Product $product, int $quantity): void + { + $this->related('order_items')->insert([ + 'product_id' => $product->id, + 'quantity' => $quantity, + 'unit_price' => $product->price, + ]); + } +} +``` + +**Об'єкти значень**: незмінні об'єкти, що представляють значення без власної ідентичності - наприклад, грошова сума або адреса електронної пошти. Два екземпляри об'єкта значення з однаковими значеннями вважаються ідентичними. + + +Інфраструктурний код +==================== + +Папка `Core/` (або також `Infrastructure/`) є домом для технічної основи застосунку. Інфраструктурний код зазвичай включає: + +/--pre +app/Core/ +├── Router/ ← маршрутизація та управління URL +│ └── RouterFactory.php +├── Security/ ← автентифікація та авторизація +│ ├── Authenticator.php +│ └── Authorizator.php +├── Logging/ ← логування та моніторинг +│ ├── SentryLogger.php +│ └── FileLogger.php +├── Cache/ ← шар кешування +│ └── FullPageCache.php +└── Integration/ ← інтеграція з зовнішніми сервісами + ├── Slack/ + └── Stripe/ +\-- + +Для менших проектів, звісно, достатньо плоского поділу: + +/--pre +Core/ +├── RouterFactory.php +├── Authenticator.php +└── QueueMailer.php +\-- + +Це код, який: + +- Вирішує технічну інфраструктуру (маршрутизація, логування, кешування) +- Інтегрує зовнішні сервіси (Sentry, Elasticsearch, Redis) +- Надає базові сервіси для всього застосунку (пошта, база даних) +- Здебільшого незалежний від конкретної домени - кеш або логер працює однаково для інтернет-магазину чи блогу. + +Вагаєтеся, чи певний клас належить сюди, чи до моделі? Ключова відмінність полягає в тому, що код у `Core/`: + +- Нічого не знає про домену (продукти, замовлення, статті) +- Здебільшого можна перенести в інший проект +- Вирішує "як це працює" (як надіслати лист), а не "що це робить" (який лист надіслати) + +Приклад для кращого розуміння: + +- `App\Core\MailerFactory` - створює екземпляри класу для надсилання електронних листів, вирішує налаштування SMTP +- `App\Model\OrderMailer` - використовує `MailerFactory` для надсилання електронних листів про замовлення, знає їхні шаблони та коли їх потрібно надіслати + + +Скрипти командного рядка +======================== + +Застосунки часто потребують виконання дій поза звичайними HTTP-запитами - чи то обробка даних у фоновому режимі, обслуговування, чи періодичні завдання. Для запуску служать прості скрипти в каталозі `bin/`, саму логіку реалізації ми розміщуємо в `app/Tasks/` (або `app/Commands/`). + +Приклад: + +/--pre +app/Tasks/ +├── Maintenance/ ← скрипти обслуговування +│ ├── CleanupCommand.php ← видалення старих даних +│ └── DbOptimizeCommand.php ← оптимізація бази даних +├── Integration/ ← інтеграція з зовнішніми системами +│ ├── ImportProducts.php ← імпорт із системи постачальника +│ └── SyncOrders.php ← синхронізація замовлень +└── Scheduled/ ← регулярні завдання + ├── NewsletterCommand.php ← розсилка новин + └── ReminderCommand.php ← сповіщення клієнтам +\-- + +Що належить до моделі, а що до скриптів командного рядка? Наприклад, логіка для надсилання одного електронного листа є частиною моделі, масова розсилка тисяч електронних листів вже належить до `Tasks/`. + +Завдання зазвичай [запускаємо з командного рядка |https://blog.nette.org/en/cli-scripts-in-nette-application] або через cron. Їх можна запускати і через HTTP-запит, але потрібно пам'ятати про безпеку. Presenter, який запускає завдання, потрібно захистити, наприклад, лише для зареєстрованих користувачів або сильним токеном та доступом з дозволених IP-адрес. Для тривалих завдань потрібно збільшити часовий ліміт скрипта та використовувати `session_write_close()`, щоб не блокувалася сесія. + + +Інші можливі каталоги +===================== + +Крім згаданих базових каталогів, ви можете за потребою проекту додати інші спеціалізовані папки. Розглянемо найпоширеніші з них та їхнє використання: + +/--pre +app/ +├── Api/ ← логіка для API, незалежна від презентаційного шару +├── Database/ ← міграційні скрипти та сідери для тестових даних +├── Components/ ← спільні візуальні компоненти для всього застосунку +├── Event/ ← корисно, якщо використовуєте подієво-орієнтовану архітектуру +├── Mail/ ← шаблони електронних листів та пов'язана логіка +└── Utils/ ← допоміжні класи +\-- + +Для спільних візуальних компонентів, що використовуються в presenter'ах по всьому застосунку, можна використовувати папку `app/Components` або `app/Controls`: + +/--pre +app/Components/ +├── Form/ ← спільні компоненти форм +│ ├── SignInForm.php +│ └── UserForm.php +├── Grid/ ← компоненти для виведення даних +│ └── DataGrid.php +└── Navigation/ ← елементи навігації + ├── Breadcrumbs.php + └── Menu.php +\-- + +Сюди належать компоненти, які мають складнішу логіку. Якщо ви хочете ділитися компонентами між кількома проектами, доцільно виділити їх в окремий composer пакет. + +До каталогу `app/Mail` ви можете розмістити управління електронною поштою: + +/--pre +app/Mail/ +├── templates/ ← шаблони електронних листів +│ ├── order-confirmation.latte +│ └── welcome.latte +└── OrderMailer.php +\-- + + +Мапінг presenter'ів +=================== + +Мапінг визначає правила для виведення назви класу з назви presenter'а. Ми вказуємо їх у [конфігурації|configuration] під ключем `application › mapping`. + +На цій сторінці ми показали, що presenter'и розміщуємо в папці `app/Presentation` (або `app/UI`). Цю конвенцію ми повинні повідомити Nette в конфігураційному файлі. Достатньо одного рядка: + +```neon +application: + mapping: App\Presentation\*\**Presenter +``` + +Як працює мапінг? Для кращого розуміння спочатку уявимо застосунок без модулів. Ми хочемо, щоб класи presenter'ів належали до простору імен `App\Presentation`, щоб presenter `Home` мапувався на клас `App\Presentation\HomePresenter`. Цього досягнемо такою конфігурацією: + +```neon +application: + mapping: App\Presentation\*Presenter +``` + +Мапінг працює так, що назва presenter'а `Home` замінює зірочку в масці `App\Presentation\*Presenter`, чим отримуємо кінцеву назву класу `App\Presentation\HomePresenter`. Просто! + +Але, як ви бачите в прикладах у цьому та інших розділах, класи presenter'ів ми розміщуємо в однойменних підкаталогах, наприклад, presenter `Home` мапується на клас `App\Presentation\Home\HomePresenter`. Цього досягнемо подвоєнням двокрапки (вимагає Nette Application 3.2): + +```neon +application: + mapping: App\Presentation\**Presenter +``` + +Тепер перейдемо до мапінгу presenter'ів у модулі. Для кожного модуля ми можемо визначити специфічний мапінг: + +```neon +application: + mapping: + Front: App\Presentation\Front\**Presenter + Admin: App\Presentation\Admin\**Presenter + Api: App\Api\*Presenter +``` + +Згідно з цією конфігурацією, presenter `Front:Home` мапується на клас `App\Presentation\Front\Home\HomePresenter`, тоді як presenter `Api:OAuth` на клас `App\Api\OAuthPresenter`. + +Оскільки модулі `Front` та `Admin` мають схожий спосіб мапінгу, і таких модулів, ймовірно, буде більше, можна створити загальне правило, яке їх замінить. До маски класу так додасться нова зірочка для модуля: + +```neon +application: + mapping: + *: App\Presentation\*\**Presenter + Api: App\Api\*Presenter +``` + +Це працює і для глибше вкладених структур каталогів, як, наприклад, presenter `Admin:User:Edit`, сегмент із зірочкою повторюється для кожного рівня, і результатом є клас `App\Presentation\Admin\User\Edit\EditPresenter`. + +Альтернативним записом є використання замість рядка масиву, що складається з трьох сегментів. Цей запис еквівалентний попередньому: + +```neon +application: + mapping: + *: [App\Presentation, *, **Presenter] + Api: [App\Api, '', *Presenter] +``` diff --git a/application/uk/how-it-works.texy b/application/uk/how-it-works.texy index 903ddc003d..ab074c7c17 100644 --- a/application/uk/how-it-works.texy +++ b/application/uk/how-it-works.texy @@ -1,101 +1,103 @@ -Як працюють додатки? -******************** +Як працюють застосунки? +***********************
    -Зараз ви читаєте основний документ документації Nette. Ви дізнаєтеся всі принципи роботи веб-додатків. Усі дрібниці від А до Я, від моменту народження до останнього подиху PHP-скрипта. Після прочитання ви будете знати: +Ви читаєте основний документ документації Nette. Ви дізнаєтеся весь принцип роботи веб-застосунків. Гарно від А до Я, від моменту народження до останнього подиху PHP-скрипта. Після прочитання ви будете знати: -- як усе це працює -- що таке Bootstrap, Presenter і DI контейнер -- який вигляд має структура каталогів +- як це все працює +- що таке Bootstrap, Presenter та DI-контейнер +- як виглядає структура каталогів
    -Структура каталогу .[#toc-directory-structure] -============================================== +Структура каталогів +=================== -Відкрийте скелетний приклад веб-додатка під назвою [WebProject |https://github.com/nette/web-project], і ви зможете спостерігати, як відбувається запис файлів. +Відкрийте приклад скелета веб-застосунку під назвою [WebProject|https://github.com/nette/web-project] і під час читання можете дивитися на файли, про які йдеться. Структура каталогів виглядає приблизно так: /--pre web-project/ -├── app/ ← каталог с приложением -│ ├── Presenters/ ← классы презентеров -│ │ ├── HomePresenter.php ← Класс презентера главной страницы -│ │ └── templates/ ← директория шаблонов -│ │ ├── @layout.latte ← шаблон общего макета -│ │ └── Home/ ← шаблоны презентера главной страницы -│ │ └── default.latte ← шаблон действия `default` -│ ├── Router/ ← конфигурация URL-адресов -│ └── Bootstrap.php ← загрузочный класс Bootstrap -├── bin/ ← скрипты командной строки -├── config/ ← файлы конфигурации +├── app/ ← каталог із застосунком +│ ├── Core/ ← базові класи, необхідні для роботи +│ │ └── RouterFactory.php ← конфігурація URL-адрес +│ ├── Presentation/ ← презентери, шаблони та ін. +│ │ ├── @layout.latte ← шаблон layout +│ │ └── Home/ ← каталог презентера Home +│ │ ├── HomePresenter.php ← клас презентера Home +│ │ └── default.latte ← шаблон дії default +│ └── Bootstrap.php ← завантажувальний клас Bootstrap +├── assets/ ← ресурси (SCSS, TypeScript, вихідні зображення) +├── bin/ ← скрипти, що запускаються з командного рядка +├── config/ ← конфігураційні файли │ ├── common.neon -│ └── local.neon -├── log/ ← журналы ошибок -├── temp/ ← временные файлы, кэш, … -├── vendor/ ← библиотеки, установленные через Composer +│ └── services.neon +├── log/ ← залоговані помилки +├── temp/ ← тимчасові файли, кеш, … +├── vendor/ ← бібліотеки, встановлені Composer │ ├── ... -│ └── autoload.php ← автозагрузчик библиотек, установленных через Composer -├── www/ ← публичный корневой каталог проекта -│ ├── .htaccess ← правила mod_rewrite и т. д. -│ └── index.php ← начальный файл, запускающий приложение -└── .htaccess ← запрещает доступ ко всем каталогам, кроме www +│ └── autoload.php ← автозавантаження всіх встановлених пакетів +├── www/ ← публічний каталог або document-root проекту +│ ├── assets/ ← скомпільовані статичні файли (CSS, JS, зображення, ...) +│ ├── .htaccess ← правила mod_rewrite +│ └── index.php ← первинний файл, яким запускається застосунок +└── .htaccess ← забороняє доступ до всіх каталогів, крім www \-- -Ви можете змінити структуру каталогів будь-яким способом, перейменувати або перемістити папки, а потім просто відредагувати шляхи до `log/` і `temp/` у файлі `Bootstrap.php` і шлях до цього файлу в `composer.json` у секції `autoload`. Нічого більше, жодного складного переналаштування, жодних постійних змін. Nette має [інтелектуальне автовизначення |bootstrap#development-vs-production-mode]. +Структуру каталогів можна будь-як змінювати, папки перейменовувати чи переміщувати, вона абсолютно гнучка. Nette, крім того, має розумне автовизначення і автоматично розпізнає розташування застосунку, включно з його базовим URL. -Для трохи більших додатків ми можемо розділити папки з ведучими і шаблонами на підкаталоги (на диску) і на простори імен (у коді), які ми називаємо [модулями |modules]. +Для трохи більших застосунків ми можемо папки з презентерами та шаблонами [розділити на підкаталоги |directory-structure#Presenter и та шаблони] та класи на простори імен, які називаємо модулями. -Публічний каталог `www/` може бути змінений без необхідності встановлювати що-небудь ще. Насправді, часто буває, що через специфіку вашого хостингу вам доведеться перейменувати його або, навпаки, встановити так званий document-root на цей каталог у конфігурації хостингу. Якщо ваш хостинг не дозволяє створювати папки на один рівень вище публічного каталогу, радимо вам пошукати інший хостинг. В іншому разі ви піддасте себе значному ризику безпеки. +Каталог `www/` представляє так званий публічний каталог або document-root проекту. Ви можете його перейменувати без необхідності щось додатково налаштовувати на стороні застосунку. Лише потрібно [налаштувати хостинг |nette:troubleshooting#Як змінити або видалити каталог www з URL] так, щоб document-root вказував на цей каталог. -Ви також можете завантажити WebProject безпосередньо, включно з Nette, використовуючи [Composer |best-practices:composer]: +WebProject ви можете також одразу завантажити разом з Nette за допомогою [Composer |best-practices:composer]: ```shell composer create-project nette/web-project ``` -У Linux або macOS встановіть [дозволи на запис |nette:troubleshooting#Setting-Directory-Permissions] для каталогів `log/` і `temp/`. +На Linux або macOS встановіть для каталогів `log/` та `temp/` [права на запис |nette:troubleshooting#Налаштування прав доступу до каталогів]. -Додаток WebProject готовий до запуску, більше нічого налаштовувати не потрібно, і ви можете переглянути його прямо в браузері, звернувшись до папки `www/`. +Застосунок WebProject готовий до запуску, не потрібно взагалі нічого налаштовувати, і ви можете одразу відобразити його в браузері, звернувшись до папки `www/`. -HTTP-запит .[#toc-http-request] -=============================== +HTTP-запит +========== -Усе починається з того, що користувач відкриває сторінку в браузері, а браузер стукає на сервер із HTTP-запитом. Запит іде до PHP-файлу, розташованого в публічному каталозі `www/`, який називається `index.php`. Припустимо, що це запит на `https://example.com/product/123` і буде виконано. +Все починається в той момент, коли користувач у браузері відкриває сторінку. Тобто коли браузер стукає на сервер з HTTP-запитом. Запит спрямований на єдиний PHP-файл, який знаходиться в публічному каталозі `www/`, і це `index.php`. Припустимо, що йдеться про запит на адресу `https://example.com/product/123`. Завдяки відповідному [налаштуванню сервера |nette:troubleshooting#Як налаштувати сервер для гарних URL] навіть цей URL мапується на файл `index.php`, і він виконується. -Його завдання полягає в наступному: +Його завдання: -1) ініціалізація середовища -2) отримання фабрики -3) запуск програми Nette, яка обробляє запит +1) ініціалізувати середовище +2) отримати фабрику +3) запустити застосунок Nette, який обробить запит -Що за фабрика? Ми виробляємо не трактори, а веб-сайти! Зачекайте, зараз усе буде пояснено. +Яку ж фабрику? Ми ж не виробляємо трактори, а веб-сторінки! Зачекайте, зараз все поясниться. -Під "ініціалізацією середовища" мається на увазі, наприклад, що активовано сервіс [Tracy |tracy:], який є дивовижним інструментом для реєстрації або візуалізації помилок. Він реєструє помилки на робочому сервері та відображає їх безпосередньо на сервері розробки. Тому під час ініціалізації також необхідно вирішити, чи працює сайт у виробничому режимі або в режимі розробника. Для цього Nette використовує автовизначення: якщо ви запускаєте сайт на localhost, він працює в режимі розробника. Вам не потрібно нічого налаштовувати, і додаток готовий як для розробки, так і для виробничого розгортання. Ці кроки виконуються і детально описуються в розділі [Bootstrap |bootstrap]. +Словами "ініціалізація середовища" ми маємо на увазі, наприклад, те, що активується [Tracy|tracy:], що є чудовим інструментом для логування або візуалізації помилок. На робочому сервері він логує помилки, на сервері розробки одразу їх відображає. Отже, до ініціалізації належить і рішення, чи працює веб-сайт у робочому чи розробницькому режимі. Для цього Nette використовує [розумне автовизначення |bootstrapping#Режим розробки проти робочого режиму]: якщо ви запускаєте веб-сайт на localhost, він працює в режимі розробки. Вам не потрібно нічого налаштовувати, і застосунок одразу готовий як для розробки, так і для реального розгортання. Ці кроки виконуються і детально описані в розділі про [клас Bootstrap|bootstrapping]. -Третій пункт (так, ми пропустили другий, але ми до нього повернемося) - це запуск програми. Обробкою HTTP-запитів у Nette займається клас `Nette\Application\Application` (далі `Application`), тому коли ми говоримо "запустити застосунок", ми маємо на увазі виклик методу з ім'ям `run()` на об'єкті цього класу. +Третім пунктом (так, другий ми пропустили, але повернемося до нього) є запуск застосунку. Обробкою HTTP-запитів у Nette займається клас `Nette\Application\Application` (далі `Application`), тому, коли ми говоримо запустити застосунок, ми маємо на увазі конкретно виклик методу з характерною назвою `run()` на об'єкті цього класу. -Nette - це наставник, який спрямовує вас до написання чистих додатків за перевіреними методологіями. І найбільш перевірена з них називається **впровадження залежностей**, скорочено DI. Наразі ми не хочемо обтяжувати вас поясненням DI, оскільки цьому присвячено [окремий розділ |dependency-injection:introduction], тут важливим є те, що ключові об'єкти зазвичай створюються фабрикою об'єктів, яка називається **DI-контейнер** (скорочено DIC). Так, це та сама фабрика, про яку йшлося деякий час тому. І вона також створює для нас об'єкт `Application`, тому спочатку нам потрібен контейнер. Ми отримуємо його за допомогою класу `Configurator` і дозволяємо йому створити об'єкт `Application`, викликаємо метод `run()` і це запускає додаток Nette. Саме це і відбувається у файлі [index.php |bootstrap#index-php]. +Nette — це наставник, який веде вас до написання чистих застосунків за перевіреними методиками. І одна з тих абсолютно найперевіреніших називається **dependency injection**, скорочено DI. На даний момент ми не хочемо обтяжувати вас поясненням DI, для цього є [окремий розділ|dependency-injection:introduction], важливим є наслідок, що ключові об'єкти нам зазвичай створюватиме фабрика об'єктів, яка називається **DI-контейнер** (скорочено DIC). Так, це та фабрика, про яку йшлося нещодавно. І вона створить нам і об'єкт `Application`, тому нам спочатку потрібен контейнер. Отримаємо його за допомогою класу `Configurator` і змусимо його створити об'єкт `Application`, викличемо на ньому метод `run()`, і тим самим запуститься застосунок Nette. Саме це відбувається у файлі [index.php |bootstrapping#index.php]. -Додаток Nette .[#toc-nette-application] -======================================= +Nette Application +================= -Клас Application має єдине завдання: для відповіді на HTTP-запит. +Клас Application має єдине завдання: відповісти на HTTP-запит. -Додатки, написані на Nette, розділені на безліч так званих презентерів (в інших фреймворках ви можете зустріти термін *контролер*, що те саме), які є класами, що представляють конкретну сторінку сайту: наприклад, домашня сторінка; товар в електронному магазині; реєстраційна форма; rss-карта і т. д. У додатку може бути від одного до тисячі презентерів. +Застосунки, написані на Nette, поділяються на безліч так званих презентерів (в інших фреймворках ви можете зустріти термін контролер, це те саме), що є класами, кожен з яких представляє якусь конкретну сторінку веб-сайту: наприклад, головну сторінку; продукт в інтернет-магазині; форму входу; sitemap feed тощо. Застосунок може мати від одного до тисяч презентерів. -Додаток починає роботу з того, що просить так званий маршрутизатор вирішити, якому з презентерів передати поточний запит на обробку. Маршрутизатор вирішує, чия це відповідальність. Він переглядає вхідний URL `https://example.com/product/123`, який хоче `показать` продукт із `id: 123` як дію. Доброю звичкою є написання пар презентер + дія, розділених двокрапкою: `Продукт:показать`. +Application починає з того, що запитує так званий маршрутизатор, щоб вирішити, якому з презентерів передати поточний запит для обробки. Маршрутизатор вирішує, чия це відповідальність. Він дивиться на вхідний URL `https://example.com/product/123` і на основі того, як він налаштований, вирішує, що це робота, наприклад, для **презентера** `Product`, від якого він захоче як **дію** відображення (`show`) продукту з `id: 123`. Пару презентер + дія прийнято записувати, розділяючи двокрапкою, як `Product:show`. -Тому маршрутизатор перетворив URL у пару `Presenter:action` + параметри, у нашому випадку `Product:show` + `id`: 123`. Вы можете увидеть, как выглядит маршрутизатор в файле `app/Router/RouterFactory.php`, і ми детально опишемо його в розділі [Маршрутизація |routing]. +Отже, маршрутизатор перетворив URL на пару `Presenter:action` + параметри, у нашому випадку `Product:show` + `id: 123`. Як виглядає такий маршрутизатор, ви можете побачити у файлі `app/Core/RouterFactory.php`, і ми детально його описуємо в розділі [Маршрутизація |Routing]. -Давайте рухатися далі. Додаток уже знає ім'я презентера і може продовжити роботу. Шляхом створення об'єкта `ProductPresenter`, який є кодом презентера `Product`. Точніше, він просить контейнер DI створити презентера, тому що створення об'єктів - це його робота. +Йдемо далі. Application вже знає ім'я презентера і може продовжувати. Тим, що створить об'єкт класу `ProductPresenter`, що є кодом презентера `Product`. Точніше кажучи, він попросить DI-контейнер створити презентер, оскільки для створення існує він. -Презентер може виглядати наступним чином: +Презентер може виглядати приблизно так: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -107,98 +109,92 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function renderShow(int $id): void { - // отримуємо дані з моделі та передаємо їх шаблону + // отримуємо дані з моделі та передаємо в шаблон $this->template->product = $this->repository->getProduct($id); } } ``` -Запит обробляється презентером. І завдання зрозуміле: виконати дію `show` з `id: 123`. Що мовою презентерів означає - викликати метод `renderShow()` з параметром `$id` рівним `123`. +Обробку запиту перебирає презентер. І завдання звучить чітко: виконай дію `show` з `id: 123`. Що мовою презентерів означає, що викликається метод `renderShow()`, і в параметрі `$id` він отримує `123`. -Презентер може обробляти кілька дій, тобто мати кілька методів `render()`. Але ми рекомендуємо розробляти презентери з однією або якомога меншою кількістю дій. +Презентер може обслуговувати кілька дій, тобто мати кілька методів `render()`. Але ми рекомендуємо проектувати презентери з однією або якомога меншою кількістю дій. -Отже, було викликано метод `renderShow(123)`, код якого є вигаданим прикладом, але на ньому можна побачити, як дані передаються в шаблон, тобто шляхом запису в `$this->template`. +Отже, викликався метод `renderShow(123)`, код якого є вигаданим прикладом, але ви можете на ньому побачити, як передаються дані в шаблон, тобто записом у `$this->template`. -Після цього презентер повертає відповідь. Це може бути HTML-сторінка, зображення, XML-документ, надсилання файлу з диска, JSON або перенаправлення на іншу сторінку. Важливо зазначити, що якщо ми явно не вказуємо, як реагувати (що має місце у випадку з `ProductPresenter`), відповіддю буде відображення шаблону з HTML-сторінкою. Чому? Ну, тому що в 99% випадків ми хочемо відобразити шаблон, тому презентер приймає таку поведінку за замовчуванням і хоче полегшити нашу роботу. Це точка зору Nette. +Потім презентер повертає відповідь. Це може бути HTML-сторінка, зображення, XML-документ, надсилання файлу з диска, JSON або, наприклад, перенаправлення на іншу сторінку. Важливо, що якщо ми явно не скажемо, як він має відповісти (що є випадком `ProductPresenter`), відповіддю буде відображення шаблону з HTML-сторінкою. Чому? Тому що в 99% випадків ми хочемо відобразити шаблон, тому презентер таку поведінку вважає стандартною і хоче полегшити нам роботу. У цьому сенс Nette. -Нам навіть не потрібно вказувати, який шаблон потрібно вивести, він сам виводить шлях до нього відповідно до простої логіки. У випадку з презентером `Product` і дією `show`, він намагається перевірити, чи існує один із цих файлів шаблонів відносно каталогу, в якому знаходиться клас `ProductPresenter`: +Нам навіть не потрібно вказувати, який шаблон відобразити, шлях до нього він виведе сам. У випадку дії `show` він просто спробує завантажити шаблон `show.latte` в каталозі з класом `ProductPresenter`. Також він спробує знайти layout у файлі `@layout.latte` (детальніше про [пошук шаблонів |templates#Пошук шаблонів]). -- `templates/Product/show.latte` -- `templates/Product.show.latte` - -І потім він відображає шаблон. Тепер завдання презентера і всієї програми виконано. Якщо шаблону не існує, буде повернуто сторінку з помилкою 404. Детальніше про презентери ви можете прочитати на сторінці [Презентери |presenters]. +І потім шаблони відобразить. Тим самим завдання презентера та всього застосунку виконано, і робота завершена. Якби шаблон не існував, повернулася б сторінка з помилкою 404. Більше про презентери ви дізнаєтеся на сторінці [Презентери|presenters]. [* request-flow.svg *] -Щоб переконатися в цьому, давайте спробуємо повторити весь процес, використовуючи трохи інший URL: +Для певності, спробуймо підсумувати весь процес з трохи іншим URL: 1) URL буде `https://example.com` -2) ми завантажуємо додаток, створюємо контейнер і запускаємо `Application::run()` +2) завантажуємо застосунок, створюється контейнер і запускається `Application::run()` 3) маршрутизатор декодує URL як пару `Home:default` -4) створюється об'єкт `HomePresenter` +4) створюється об'єкт класу `HomePresenter` 5) викликається метод `renderDefault()` (якщо існує) -6) шаблон `templates/Home/default.latte` з макетом `templates/@layout.latte` відмальований +6) відображається шаблон, наприклад, `default.latte` з layout, наприклад, `@layout.latte` -Можливо, зараз ви зіткнулися з безліччю нових понять, але ми вважаємо, що вони мають сенс. Створювати додатки в Nette - простіше простого. +Можливо, ви зараз зіткнулися з великою кількістю нових понять, але ми віримо, що вони мають сенс. Створення застосунків у Nette — це величезне задоволення. -Шаблони .[#toc-templates] -========================= +Шаблони +======= -Що стосується шаблонів, Nette використовує систему шаблонів [Latte |latte:]. Тому файли з шаблонами закінчуються на `.latte`. Latte використовується тому, що це найбезпечніша система шаблонів для PHP, і водночас найінтуїтивніша та найзрозуміліша. Вам не потрібно вивчати багато нового, достатньо знати PHP і кілька тегів Latte. Ви дізнаєтеся все [в документації |latte:]. +Коли вже зайшла мова про шаблони, у Nette використовується система шаблонів [Latte |latte:]. Тому й такі розширення `.latte` у шаблонів. Latte використовується, по-перше, тому що це найбільш захищена система шаблонів для PHP, а по-друге, також система найбільш інтуїтивно зрозуміла. Вам не потрібно вчити багато нового, достатньо знання PHP та кількох тегів. Все ви дізнаєтеся [у документації |templates]. -У шаблоні ми [створюємо посилання |creating-links] на інших презентерів і дії таким чином: +У шаблоні [створюються посилання |creating-links] на інші презентери та дії так: ```latte -страница товара +деталі продукту ``` -Просто напишіть знайому пару `Presenter:action` замість реального URL і ввімкніть будь-які параметри. Хитрість полягає в `n:href`, який говорить, що цей атрибут буде оброблятися Nette. І він буде генерувати: +Просто замість реального URL ви пишете відому пару `Presenter:action` і вказуєте можливі параметри. Трюк полягає в `n:href`, яке говорить, що цей атрибут обробить Nette. І згенерує: ```latte -страница товара +деталі продукту ``` -Раніше згаданий маршрутизатор відповідає за генерацію URL. Фактично, маршрутизатори в Nette унікальні тим, що вони можуть виконувати не тільки перетворення з URL у пару презентер:дія, а й навпаки - генерувати URL з імені презентер + дія + параметри. -Завдяки цьому в Nette ви можете повністю змінити форму URL у всьому готовому застосунку, не змінюючи жодного символу в шаблоні або презентері, просто модифікувавши маршрутизатор. -І завдяки цьому працює так звана канонізація - ще одна унікальна особливість Nette, яка покращує SEO шляхом автоматичного запобігання існування дубльованого контенту на різних URL. -Багато програмістів знаходять це дивовижним. +Генерацією URL займається вже згаданий маршрутизатор. Справа в тому, що маршрутизатори в Nette виняткові тим, що вміють виконувати не тільки перетворення з URL на пару presenter:action, але й навпаки, тобто з назви презентера + дії + параметрів генерувати URL. Завдяки цьому в Nette ви можете повністю змінити форми URL у всьому готовому застосунку, не змінюючи жодного символу в шаблоні чи презентері. Лише тим, що зміните маршрутизатор. Також завдяки цьому працює так звана канонізація, що є ще однією унікальною властивістю Nette, яка сприяє кращому SEO (оптимізації знаходження в Інтернеті), автоматично запобігаючи існуванню дубльованого контенту на різних URL. Багато програмістів вважають це вражаючим. -Інтерактивні компоненти .[#toc-interactive-components] -====================================================== +Інтерактивні компоненти +======================= -Ми хочемо розповісти вам ще дещо про презентери: вони мають вбудовану систему компонентів. Ті, хто старший, можуть пам'ятати щось подібне з Delphi або ASP.NET Web Forms. React або Vue.js побудовані на чомусь віддалено схожому. У світі PHP-фреймворків це абсолютно унікальна функція. +Про презентери ми повинні розповісти вам ще одну річ: вони мають вбудовану систему компонентів. Щось подібне можуть пам'ятати ті, хто працював з Delphi або ASP.NET Web Forms, на чомусь віддалено схожому побудовані React або Vue.js. У світі PHP-фреймворків це абсолютно унікальна річ. -Компоненти - це окремі багаторазово використовувані блоки, які ми поміщаємо в сторінки (тобто в презентери). Це можуть бути [форми |forms:in-presenter], [сітки даних |https://componette.org/contributte/datagrid/], меню, опитування, загалом, усе, що має сенс використовувати багаторазово. Ми можемо створювати власні компоненти або використовувати деякі з [величезної кількості |https://componette.org] компонентів з відкритим вихідним кодом. +Компоненти — це окремі повторно використовувані одиниці, які ми вставляємо на сторінки (тобто презентери). Це можуть бути [форми |forms:in-presenter], [datagrid |https://componette.org/contributte/datagrid/], меню, опитування, власне все, що має сенс використовувати повторно. Ми можемо створювати власні компоненти або використовувати деякі з [величезної пропозиції |https://componette.org] компонентів з відкритим кодом. -Компоненти докорінно змінюють підхід до розробки додатків. Вони відкриють нові можливості для створення сторінок із заздалегідь заданих блоків. І в них є щось спільне з [Голлівудом |components#Hollywood-Style]. +Компоненти суттєво впливають на підхід до створення застосунків. Вони відкриють вам нові можливості складання сторінок з готових одиниць. І до того ж мають щось спільне з [Голлівудом |components#Голлівудський стиль]. -Контейнер DI та конфігурація .[#toc-di-container-and-configuration] -=================================================================== +DI-контейнер та конфігурація +============================ -DI-контейнер (фабрика для об'єктів) - це серце всього застосунку. +DI-контейнер, або фабрика об'єктів, є серцем усього застосунку. -Не хвилюйтеся, це не магічний чорний ящик, як може здатися з попередніх слів. Насправді, це один доволі нудний PHP-клас, згенерований Nette, який зберігається в каталозі кешу. Він має безліч методів, названих `createServiceAbcd()`, і кожен з них створює і повертає об'єкт. Так, є також метод `createServiceApplication()`, який створює `Nette\Application\Application`, який нам знадобився у файлі `index.php` для запуску програми. Існують також методи підготовки індивідуальних презентерів. І так далі. +Не хвилюйтеся, це не якийсь магічний чорний ящик, як могло б здатися з попередніх рядків. Власне, це один досить нудний PHP-клас, який генерує Nette і зберігає в каталозі з кешем. Він має багато методів, названих як `createServiceAbcd()`, і кожен з них вміє створити та повернути якийсь об'єкт. Так, там є і метод `createServiceApplication()`, який створить `Nette\Application\Application`, який нам був потрібен у файлі `index.php` для запуску застосунку. І є методи, що створюють окремі презентери. І так далі. -Об'єкти, які створює контейнер DI, з якоїсь причини називаються сервісами. +Об'єктам, які створює DI-контейнер, з якоїсь причини називають сервісами. -Особливість цього класу в тому, що він програмується не вами, а фреймворком. Він фактично генерує PHP-код і зберігає його на диску. Ви просто даєте інструкції про те, які об'єкти і як саме має виробляти контейнер. І ці інструкції записані в [конфігураційних файлах |bootstrap#di-container-configuration] у [форматі NEON |neon:format] і тому мають розширення `.neon`. +Що в цьому класі справді особливого, так це те, що його програмуєте не ви, а фреймворк. Він дійсно генерує PHP-код і зберігає його на диску. Ви лише даєте інструкції, які об'єкти має вміти створювати контейнер і як саме. І ці інструкції записані в [конфігураційних файлах |bootstrapping#Конфігурація DI-контейнера], для яких використовується формат [NEON|neon:format], і тому вони мають розширення `.neon`. -Конфігураційні файли використовуються виключно для навчання DI-контейнера. Так, наприклад, якщо я вкажу `expiration: 14 days` у секції [session |http:configuration#session], контейнер DI при створенні об'єкта `Nette\Http\Session`, що представляє сесію, викличе його метод `setExpiration('14 days')`, і таким чином конфігурація стане реальністю. +Конфігураційні файли служать виключно для інструктування DI-контейнера. Отже, коли, наприклад, я вказую в секції [session |http:configuration#Сесія] опцію `expiration: 14 days`, то DI-контейнер при створенні об'єкта `Nette\Http\Session`, що представляє сесію, викличе його метод `setExpiration('14 days')`, і тим самим конфігурація стане реальністю. -Для вас підготовлено цілий розділ, який описує, що можна [налаштувати |nette:configuring] і як [визначити свої власні сервіси |dependency-injection:services]. +Для вас підготовлено цілий розділ, що описує, що все можна [налаштувати |nette:configuring] та як [визначити власні сервіси |dependency-injection:services]. -Щойно ви перейдете до створення сервісів, ви зіткнетеся зі словом *autowiring* (*автопідключення*). Це гаджет, який неймовірно полегшить ваше життя. Він може автоматично передавати об'єкти туди, куди вам потрібно (наприклад, до конструкторів ваших класів), не маючи потреби що-небудь робити. Ви побачите, що контейнер DI в Nette - це маленьке диво. +Як тільки ви трохи заглибитеся у створення сервісів, ви натрапите на слово [autowiring |dependency-injection:autowiring]. Це фішка, яка неймовірним чином спростить вам життя. Вона вміє автоматично передавати об'єкти туди, де вони вам потрібні (наприклад, у конструкторах ваших класів), не вимагаючи від вас нічого робити. Ви дізнаєтеся, що DI-контейнер у Nette — це маленьке диво. -Що далі? .[#toc-what-next] -========================== +Куди далі? +========== -Ми розглянули основні принципи роботи додатків у Nette. Поки що дуже поверхнево, але незабаром ви зануритеся в глибини і зрештою створите чудові веб-додатки. Де продовжити? Ви пробували вивчити підручник [Створіть свій перший додаток |quickstart:]? +Ми пройшлися по основних принципах застосунків у Nette. Поки що дуже поверхнево, але скоро ви заглибитеся глибше і з часом створите чудові веб-застосунки. Куди йти далі? Ви вже спробували підручник [Пишемо перший застосунок|quickstart:]? -На додаток до перерахованого вище, Nette має цілий арсенал [корисних класів |utils:], [шар бази даних |database:] тощо. Спробуйте цілеспрямовано просто переглянути документацію. Або відвідайте [блог |https://blog.nette.org]. Ви відкриєте для себе багато цікавого. +Крім вищеописаного, Nette має цілий арсенал [корисних класів|utils:], [шар бази даних|database:], тощо. Спробуйте просто проклацати документацію. Або [блог|https://blog.nette.org]. Ви відкриєте багато цікавого. -Нехай цей фреймворк принесе вам багато радості 💙. +Нехай фреймворк приносить вам багато радості 💙 diff --git a/application/uk/modules.texy b/application/uk/modules.texy deleted file mode 100644 index 5503142028..0000000000 --- a/application/uk/modules.texy +++ /dev/null @@ -1,148 +0,0 @@ -Модулі -****** - -.[perex] -У Nette модулі являють собою логічні одиниці, з яких складається додаток. Вони включають ведучі, шаблони, можливо, компоненти та класи моделей. - -Одного компонента для презентаторів і одного для шаблонів буде недостатньо для реальних проектів. Наявність десятків файлів в одній папці щонайменше неорганізована. Як вийти з цього становища? Ми просто розділяємо їх на підкаталоги на диску і на простори імен у коді. І це саме те, що роблять модулі Nette. - -Тому давайте забудемо про єдину папку для ведучих і шаблонів і натомість створимо модулі, наприклад, `Admin` і `Front`. - -/--pre -app/ -├── Presenters/ -├── Modules/ ← директория с модулями -│ ├── Admin/ ← модуль Admin -│ │ ├── Presenters/ ← его презентеры -│ │ │ ├── DashboardPresenter.php -│ │ │ └── templates/ -│ └── Front/ ← модуль Front -│ └── Presenters/ ← его презентеры -│ └── ... -\-- - -Ця структура каталогів буде відображена в просторах імен класів, так, наприклад, `DashboardPresenter` буде знаходитися в просторі `App\Modules\Admin\Presenters`: - -```php -namespace App\Modules\Admin\Presenters; - -class DashboardPresenter extends Nette\Application\UI\Presenter -{ - // ... -} -``` - -Ведучий `Dashboard` усередині модуля `Admin` позначається в додатку за допомогою подвійної точкової нотації як `Admin:Dashboard`, а його дія `default` позначається як `Admin:Dashboard:default`. -А звідки Nette знає, що `Admin:Dashboard` представляє клас `App\Modules\Admin\Presenters\DashboardPresenter`? Ми говоримо про це, використовуючи [відображення |#Mapping] в конфігурації. -Таким чином, наведена структура не є фіксованою, і ви можете змінювати її на свій розсуд. - -Модулі, звісно, можуть містити всі інші частини, крім презентаторів і шаблонів, такі як компоненти, класи моделей тощо. - - -Вкладені модулі .[#toc-nested-modules] --------------------------------------- - -Модулі не обов'язково повинні формувати тільки плоску структуру, ви також можете створювати, наприклад, підмодулі: - -/--pre -app/ -├── Modules/ ← директория с модулями -│ ├── Blog/ ← модуль Blog -│ │ ├── Admin/ ← подмодуль Admin -│ │ │ ├── Presenters/ -│ │ │ └── ... -│ │ └── Front/ ← подмодуль Front -│ │ ├── Presenters/ -│ │ └── ... -│ ├── Forum/ ← модуль Forum -│ │ └── ... -\-- - -Таким чином, модуль `Blog` розбивається на підмодулі `Admin` і `Front`. І знову ж таки це буде відображено в просторах імен, які будуть `App\Modules\Blog\Admin\Presenters` тощо. Ведучий `Dashboard` всередині підмодуля називається `Blog:Admin:Dashboard`. - -Розгалуження може бути настільки глибоким, наскільки ви захочете, тому ви можете створювати підмодулі. - - -Створення посилань .[#toc-creating-links] ------------------------------------------ - -Посилання в шаблонах ведучого є відносними щодо поточного модуля. Таким чином, посилання `Foo:default` веде до ведучого `Foo` у тому ж модулі, що й поточний ведучий. Наприклад, якщо поточним модулем є `Front`, то посилання має такий вигляд: - -```latte -odkaz na Front:Product:show -``` - -Посилання є відносним, навіть якщо ім'я модуля є його частиною, тоді він вважається підмодулем: - -```latte -odkaz na Front:Shop:Product:show -``` - -Абсолютні посилання записуються аналогічно абсолютним шляхам на диску, але з двокрапками замість косих рисок. Таким чином, абсолютне посилання починається з двокрапки: - -```latte -odkaz na Admin:Product:show -``` - -Щоб дізнатися, чи перебуваємо ми в певному модулі або підмодулі, ми використовуємо функцію `isModuleCurrent(moduleName)`. - -```latte -
  • - ... -
  • -``` - - -Маршрутизація .[#toc-routing] ------------------------------ - -Див. [розділ про маршрутизацію |routing#modules]. - - -Складання карти .[#toc-mapping] -------------------------------- - -Визначає правила, за якими ім'я класу виводиться з імені ведучого. Ми записуємо їх у [конфігурацію |configuration] під ключем `application › mapping`. - -Почнемо з прикладу, в якому не використовуються модулі. Ми просто хочемо, щоб класи ведучого мали простір імен `App\Presenters`. Тобто ми хочемо, щоб ведучий, наприклад, `Home` відображався на клас `App\Presenters\HomePresenter`. Цього можна досягти за допомогою такої конфігурації: - -```neon -application: - mapping: - *: App\Presenters\*Presenter -``` - -Ім'я презентера замінюється зірочкою, і в результаті виходить назва класу. Легко! - -Якщо ми розділимо доповідачів на модулі, то для кожного модуля в нас може бути свій маппінг: - -```neon -application: - mapping: - Front: App\Modules\Front\Presenters\*Presenter - Admin: App\Modules\Admin\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -Тепер презентер `Front:Home` визначається класом `App\Modules\Front\HomePresenter`, а презентер `Admin:Dashboard` ` - `App\AdminModule\DashboardPresenter`. - -Зручніше буде створити загальне правило (зірочка), яке замінить перші два правила і додасть додаткову зірочку тільки для модуля: - -```neon -application: - mapping: - *: App\Modules\*\Presenters\*Presenter - Api: App\Api\*Presenter -``` - -Але що якщо ми використовуємо кілька вкладених модулів і в нас є, наприклад, провідний `Admin:User:Edit`? У цьому випадку сегмент із зірочкою, що представляє модуль для кожного рівня, буде просто повторюватися, і результатом буде клас `App\Modules\Admin\User\Presenters\EditPresenter`. - -Альтернативною нотацією є використання масиву, що складається з трьох сегментів, замість рядка. Ця нотація еквівалентна попередній: - -```neon -application: - mapping: - *: [App\Modules, *, Presenters\*Presenter] -``` - -Значення за замовчуванням - `*: *Module\*Presenter`. diff --git a/application/uk/multiplier.texy b/application/uk/multiplier.texy index b49ec16f65..4b882fd4d4 100644 --- a/application/uk/multiplier.texy +++ b/application/uk/multiplier.texy @@ -1,30 +1,32 @@ -Multiplier: Динамічні компоненти +Multiplier: динамічні компоненти ******************************** -Інструмент для динамічного створення інтерактивних компонентів. +.[perex] +Інструмент для динамічного створення інтерактивних компонентів -Давайте почнемо з типової проблеми: у нас є список товарів на сайті електронної комерції, і ми хочемо супроводити кожен товар формою *додати до кошика*. Один зі способів - обернути весь лістинг в одну форму. Більш зручним способом є використання [api:Nette\Application\UI\Multiplier]. +Почнемо з типового прикладу: маємо список товарів в інтернет-магазині, причому біля кожного ми хочемо вивести форму для додавання товару в кошик. Одним з можливих варіантів є обгортання всього списку в одну форму. Набагато зручніший спосіб нам пропонує [api:Nette\Application\UI\Multiplier]. -Multiplier дозволяє визначити фабрику для декількох компонентів. Він заснований на принципі вкладених компонентів - кожен компонент, що успадковує від [api:Nette\ComponentModel\Container], може містити інші компоненти. +Multiplier дозволяє зручно визначити фабрику для кількох компонентів. Він працює за принципом вкладених компонентів - кожен компонент, що успадковує від [api:Nette\ComponentModel\Container], може містити інші компоненти. -Див. [модель компонента |components#Components in Depth] в документації. +.[tip] +Див. розділ про [модель компонентів |components#Компоненти до глибини] у документації або [лекцію від Honza Tvrdík|https://www.youtube.com/watch?v=8y3LLexWu-I]. -Multiplier являє собою батьківський компонент, який може динамічно створювати свої дочірні компоненти, використовуючи зворотний виклик, переданий у конструкторі. Див. приклад: +Суть Multiplier полягає в тому, що він виступає в ролі батька, який може динамічно створювати своїх нащадків за допомогою callback-функції, переданої в конструкторі. Див. приклад: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function () { $form = new Nette\Application\UI\Form; - $form->addInteger('amount', 'Amount:') + $form->addInteger('count', 'Кількість товару:') ->setRequired(); - $form->addSubmit('send', 'Add to cart'); + $form->addSubmit('send', 'Додати в кошик'); return $form; }); } ``` -У шаблоні ми можемо відобразити форму для кожного товару - і кожна форма дійсно буде унікальним компонентом. +Тепер ми можемо в шаблоні просто біля кожного товару відобразити форму - і кожна буде дійсно унікальним компонентом. ```latte {foreach $items as $item} @@ -35,26 +37,26 @@ protected function createComponentShopForm(): Multiplier {/foreach} ``` -Аргумент, переданий у тег `{control}`, свідчить: +Аргумент, переданий у тезі `{control}`, має формат, який говорить: -1. отримати компонент `shopForm` -2. повернути своєму нащадку `$item->id` +1. отримай компонент `shopForm` +2. і з нього отримай нащадка `$item->id` -При першому виклику **1.** компонент `shopForm` ще не існує, тому для його створення викликається метод `createComponentShopForm`. Потім викликається анонімна функція, передана як параметр у Multiplier, і створюється форма. +При першому виклику пункту **1.** `shopForm` ще не існує, тому викликається його фабрика `createComponentShopForm`. На отриманому компоненті (екземплярі Multiplier) потім викликається фабрика конкретної форми - це анонімна функція, яку ми передали Multiplier у конструкторі. -У наступних ітераціях `foreach` метод `createComponentShopForm` більше не викликається, оскільки компонент уже існує. Але оскільки ми посилаємося на іншого нащадка (`$item->id` варіюється між ітераціями), анонімна функція викликається знову і створюється нова форма. +У наступній ітерації foreach метод `createComponentShopForm` вже не буде викликаний (компонент існує), але оскільки ми шукаємо іншого його нащадка (`$item->id` буде різним у кожній ітерації), знову буде викликана анонімна функція і поверне нам нову форму. -Останнє, що потрібно зробити, це переконатися, що форма справді додає правильний товар до кошика, тому що в поточному стані всі форми рівні, і ми не можемо розрізнити, до яких продуктів вони належать. Для цього ми можемо використовувати властивість класу Multiplier (і взагалі будь-якого методу фабрики компонентів у фреймворку Nette), що кожен метод фабрики компонентів отримує ім'я створеного компонента як перший аргумент. У нашому випадку це буде `$item->id`, що саме те, що нам потрібно, щоб розрізняти окремі товари. Все, що вам потрібно зробити, це змінити код для створення форми: +Єдине, що залишається, - це забезпечити, щоб форма додала в кошик дійсно той товар, який потрібно - наразі форма біля кожного товару абсолютно однакова. Допоможе нам властивість Multiplier (і загалом кожної фабрики компонентів у Nette Framework), а саме те, що кожна фабрика як свій перший аргумент отримує назву створюваного компонента. У нашому випадку це буде `$item->id`, що є саме тим даними, які нам потрібні. Достатньо лише трохи змінити створення форми: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function ($itemId) { $form = new Nette\Application\UI\Form; - $form->addInteger('amount', 'Количество:') + $form->addInteger('count', 'Кількість товару:') ->setRequired(); $form->addHidden('itemId', $itemId); - $form->addSubmit('send', 'Добавить в корзину'); + $form->addSubmit('send', 'Додати в кошик'); return $form; }); } diff --git a/application/uk/presenters.texy b/application/uk/presenters.texy index 5e17528f27..8284bc3a31 100644 --- a/application/uk/presenters.texy +++ b/application/uk/presenters.texy @@ -3,37 +3,37 @@
    -Навчимося створювати презентери та шаблони в Nette. Після прочитання цієї статті ви дізнаєтеся: +Ми ознайомимося з тим, як у Nette пишуться презентери та шаблони. Після прочитання ви будете знати: - як працює презентер -- що таке постійні параметри -- як відрендерити шаблон +- що таке персистентні параметри +- як відображаються шаблони
    -[Ми вже знаємо |how-it-works#Nette-Application], що презентер - це клас, який представляє конкретну сторінку веб-додатка, як-от головна сторінка; сторінка товару в інтернет-магазині; форма авторизації; мапа сайту тощо. Додаток може мати від одного до тисячі презентерів. В інших фреймворках вони також відомі як контролери. +[Ми вже знаємо |how-it-works#Nette Application], що презентер — це клас, який представляє певну конкретну сторінку веб-застосунку, наприклад, головну сторінку; продукт в інтернет-магазині; форму входу; стрічку sitemap тощо. Застосунок може мати від одного до тисяч презентерів. В інших фреймворках їх також називають контролерами. -Зазвичай термін *презентер* співвідноситься з нащадком класу [api:Nette\Application\UI\Presenter], який підходить для веб-інтерфейсів. Ми обговоримо цей клас у решті частини цього розділу. У загальному сенсі, презентер - це будь-який об'єкт, що реалізує інтерфейс [api:Nette\Application\IPresenter]. +Зазвичай під поняттям презентер мається на увазі нащадок класу [api:Nette\Application\UI\Presenter], який підходить для генерації веб-інтерфейсів і якому ми присвятимо решту цього розділу. У загальному сенсі презентер — це будь-який об'єкт, що реалізує інтерфейс [api:Nette\Application\IPresenter]. -Життєвий цикл презентера .[#toc-life-cycle-of-presenter] -======================================================== +Життєвий цикл презентера +======================== -Завдання ведучого - обробити запит і повернути відповідь (яка може бути HTML-сторінкою, зображенням, редиректом тощо). +Завданням презентера є обробити запит і повернути відповідь (це може бути HTML-сторінка, зображення, перенаправлення тощо). -Отже, на початку - запит. Це не безпосередньо HTTP-запит, а об'єкт [api:Nette\Application\Request], у який HTTP-запит було перетворено за допомогою маршрутизатора. Зазвичай ми не стикаємося з цим об'єктом, тому що презентер спритно делегує обробку запиту спеціальним методам, які ми зараз побачимо. +Отже, на початку йому передається запит. Це не безпосередньо HTTP-запит, а об'єкт [api:Nette\Application\Request], в який був перетворений HTTP-запит за допомогою маршрутизатора. З цим об'єктом ми зазвичай не стикаємося, оскільки презентер розумно делегує обробку запиту іншим методам, які ми зараз розглянемо. [* lifecycle.svg *] *** *Життєвий цикл презентера* .<> -На малюнку показано список методів, які викликаються послідовно зверху вниз, якщо вони існують. Усі вони необов'язкові, ми можемо мати абсолютно порожній презентер без жодного методу і побудувати на ньому простий статичний веб. +Зображення представляє список методів, які послідовно викликаються зверху вниз, якщо вони існують. Жоден з них не обов'язковий, ми можемо мати абсолютно порожній презентер без жодного методу і побудувати на ньому простий статичний веб-сайт. `__construct()` --------------- -Конструктор не зовсім належить до життєвого циклу презентера, оскільки викликається в момент створення об'єкта. Але ми згадуємо його через важливість, оскільки він використовується для передачі залежностей. +Конструктор не зовсім належить до життєвого циклу презентера, оскільки викликається в момент створення об'єкта. Але ми згадуємо його через важливість. Конструктор (разом з [методом inject|best-practices:inject-method-attribute]) служить для передачі залежностей. -Презентер не повинен піклуватися про бізнес-логіку застосунку, писати і читати з бази даних, виконувати обчислення тощо. Це завдання для класів із шару, який ми називаємо моделлю. Наприклад, клас `ArticleRepository` може відповідати за завантаження і збереження статей. Для того щоб презентер міг його використовувати, він [передається за допомогою впровадження залежностей |dependency-injection:passing-dependencies]: +Презентер не повинен займатися бізнес-логікою застосунку, записувати та читати з бази даних, виконувати обчислення тощо. Для цього існують класи з шару, який ми називаємо моделлю. Наприклад, клас `ArticleRepository` може відповідати за завантаження та збереження статей. Щоб презентер міг з ним працювати, він отримує його [передачею за допомогою dependency injection |dependency-injection:passing-dependencies]: ```php @@ -50,44 +50,44 @@ class ArticlePresenter extends Nette\Application\UI\Presenter `startup()` ----------- -Відразу після отримання запиту викликається метод `startup()`. Ви можете використовувати його для ініціалізації властивостей, перевірки привілеїв користувача тощо. Потрібно завжди викликати предка `parent::startup()`. +Одразу після отримання запиту викликається метод `startup()`. Ви можете використовувати його для ініціалізації властивостей, перевірки прав користувача тощо. Вимагається, щоб метод завжди викликав батьківський `parent::startup()`. `action(args...)` .{toc: action()} -------------------------------------------------- -Аналогічний методу `render()`. У той час як `render()` призначена для підготовки даних для певного шаблону, який згодом рендериться, в `action()` запит обробляється без подальшого рендерингу шаблону. Наприклад, дані обробляються, користувач входить або виходить із системи тощо, а потім [перенаправляється в інше місце |#Redirection]. +Аналог методу `render()`. У той час як `render()` призначений для підготовки даних для конкретного шаблону, який потім відображається, то в `action()` обробляється запит без зв'язку з відображенням шаблону. Наприклад, обробляються дані, користувач входить або виходить з системи, тощо, а потім [перенаправляється в інше місце |#Перенаправлення]. -Важливо, що `action()` викликається перед `render()`, тому всередині нього ми можемо, можливо, змінити наступний хід життєвого циклу, тобто змінити шаблон, який буде відображатися, а також метод `render()`, який буде викликатися, використовуючи `setView('otherView')`. +Важливо, що `action()` викликається раніше, ніж `render()`, тому в ньому ми можемо, за потреби, змінити подальший хід подій, тобто змінити шаблон, який буде відображатися, а також метод `render()`, який буде викликатися. Це робиться за допомогою `setView('іншийView')`. -У метод передаються параметри із запиту. Можна і рекомендується вказувати типи для параметрів, наприклад `actionShow(int $id, string $slug = null)` - якщо параметр `id` відсутній або якщо він не є цілим числом, презентер повертає [помилку 404 |#Error-404-etc] і завершує операцію. +Методу передаються параметри із запиту. Можна і рекомендується вказувати типи параметрів, наприклад, `actionShow(int $id, ?string $slug = null)` - якщо параметр `id` буде відсутній або якщо він не буде цілим числом, презентер поверне [помилку 404 |#Помилка 404 тощо] і завершить роботу. `handle(args...)` .{toc: handle()} -------------------------------------------------- -Цей метод обробляє так звані сигнали, про які ми поговоримо в розділі про [Компоненти |components#Signal]. Він призначений в основному для компонентів і обробки AJAX-запитів. +Метод обробляє так звані сигнали, з якими ми познайомимося в розділі, присвяченому [компонентам |components#Сигнал]. Він призначений переважно для компонентів та обробки AJAX-запитів. -Параметри передаються методу, як у випадку `action()`включно з перевіркою типу. +Методу передаються параметри із запиту, як у випадку `action()`, включно з перевіркою типів. `beforeRender()` ---------------- -Метод `beforeRender`, як випливає з назви, викликається перед кожним методом `render()`. Використовується для загального налаштування шаблону, передачі змінних для верстки тощо. +Метод `beforeRender`, як випливає з назви, викликається перед кожним методом `render()`. Використовується для спільної конфігурації шаблону, передачі змінних для layout тощо. `render(args...)` .{toc: render()} ---------------------------------------------- -Місце, де ми готуємо шаблон до подальшого рендерингу, передаємо йому дані тощо. +Місце, де ми готуємо шаблон до подальшого відображення, передаємо йому дані тощо. -Параметри передаються методу, як у випадку `action()`, включно з перевіркою типу. +Методу передаються параметри із запиту, як у випадку `action()`, включно з перевіркою типів. ```php public function renderShow(int $id): void { - // ми отримуємо дані з моделі та передаємо їх у шаблон + // отримуємо дані з моделі та передаємо в шаблон $this->template->article = $this->articles->getById($id); } ``` @@ -96,104 +96,104 @@ public function renderShow(int $id): void `afterRender()` --------------- -Метод `afterRender`, як випливає з назви, викликається після кожного методу `render()`. Він використовується досить рідко. +Метод `afterRender`, як знову ж таки випливає з назви, викликається після кожного методу `render()`. Використовується досить рідко. `shutdown()` ------------ -Викликається наприкінці життєвого циклу презентера. +Викликається в кінці життєвого циклу презентера. -**Хороша порада, перш ніж ми продовжимо**. Як ви бачите, презентер може обробляти більше дій/переглядів, тобто мають більше методів `render()`. Але ми рекомендуємо розробляти презентери з однією або якомога меншою кількістю дій. +**Добра порада, перш ніж йти далі**. Презентер, як бачимо, може обслуговувати кілька дій/view, тобто мати кілька методів `render()`. Але ми рекомендуємо проектувати презентери з однією або якомога меншою кількістю дій. -Надсилання відповіді .[#toc-sending-a-response] -=============================================== +Надсилання відповіді +==================== -Зазвичай відповіддю ведучого є [рендеринг шаблону з HTML-сторінкою |templates], але це також може бути надсилання файлу, JSON або навіть перенаправлення на іншу сторінку. +Відповіддю презентера зазвичай є [відображення шаблону з HTML-сторінкою|templates], але це може бути також надсилання файлу, JSON або, наприклад, перенаправлення на іншу сторінку. -У будь-який момент життєвого циклу ми можемо використовувати один із таких методів для надсилання відповіді та завершення роботи презентера: +У будь-який момент життєвого циклу ми можемо одним з наступних методів надіслати відповідь і одночасно завершити роботу презентера: -- [Перенаправлення |#Redirection] `redirect()`, `redirectPermanent()`, `redirectUrl()` і `forward()`. -- `error()` завершує роботу презентера [через помилку |#Error-404-etc]. -- `sendJson($data)` виходить із презентера і надсилає дані у форматі JSON -- `sendTemplate()` завершує роботу презентера і відразу ж виконує рендеринг шаблону -- `sendResponse($response)` виходить із презентера та надсилає [власну відповідь |#Ответы]. -- `terminate()` завершує роботу презентера без відповіді +- `redirect()`, `redirectPermanent()`, `redirectUrl()` та `forward()` [перенаправляє |#Перенаправлення] +- `error()` завершує презентер [через помилку |#Помилка 404 тощо] +- `sendJson($data)` завершує презентер і [надсилає дані |#Надсилання JSON] у форматі JSON +- `sendTemplate()` завершує презентер і негайно [відображає шаблон |templates] +- `sendResponse($response)` завершує презентер і надсилає [власну відповідь |#Відповіді] +- `terminate()` завершує презентер без відповіді -**Зараз щось важливе**: якщо ми явно не говоримо, яку відповідь має надіслати презентер, відповіддю буде [рендеринг шаблонів |#Рендеринг шаблонов] HTML. Чому? Ну, тому що в 99% випадків ми хочемо відрендерити шаблон, тому презентер приймає таку поведінку за замовчуванням і хоче полегшити нашу роботу. +Якщо ви не викличете жоден з цих методів, презентер автоматично перейде до відображення шаблону. Чому? Тому що в 99% випадків ми хочемо відобразити шаблон, тому презентер таку поведінку вважає стандартною і хоче полегшити нам роботу. -Створення посилань .[#toc-creating-links] -========================================= +Створення посилань +================== -У презентера є метод `link()`, який використовується для створення URL-посилань на інші презентери. Першим параметром є цільовий презентер і дія, потім йдуть аргументи, які можуть бути передані у вигляді масиву: +Презентер має метод `link()`, за допомогою якого можна створювати URL-посилання на інші презентери. Першим параметром є цільовий презентер та дія, далі йдуть передані аргументи, які можуть бути вказані як масив: ```php $url = $this->link('Product:show', $id); -$url = $this->link('Product:show', [$id, 'lang' => 'en']); +$url = $this->link('Product:show', [$id, 'lang' => 'cs']); ``` -У шаблоні ми створюємо посилання на інші презентери та дії таким чином: +У шаблоні створюються посилання на інші презентери та дії таким чином: ```latte -страница товара +деталі продукту ``` -Просто напишіть знайому пару `Presenter:action` замість реального URL і включіть будь-які параметри. Хитрість полягає в `n:href`, який говорить, що цей атрибут буде оброблений Latte і згенерує справжній URL. У Nette вам взагалі не потрібно думати про URL-адреси, тільки про презентери та дії. +Просто замість реального URL ви пишете відому пару `Presenter:action` і вказуєте можливі параметри. Трюк полягає в `n:href`, яке говорить, що цей атрибут обробить Latte і згенерує реальний URL. У Nette вам взагалі не потрібно думати про URL, лише про презентери та дії. -Для отримання додаткової інформації див. [Створення посилань |creating-links]. +Більше інформації ви знайдете в розділі [Створення URL-посилань|creating-links]. -Перенаправлення .[#toc-redirection] -=================================== +Перенаправлення +=============== -Для переходу до іншого презентера використовуються методи `redirect()` і `forward()`, які мають дуже схожий синтаксис із методом [link() |#Creating-Links]. +Для переходу на інший презентер служать методи `redirect()` та `forward()`, які мають дуже схожий синтаксис, як метод [link() |#Створення посилань]. -Функція `forward()` перемикає на новий презентер негайно без перенаправлення HTTP: +Метод `forward()` переходить на новий презентер негайно без HTTP-перенаправлення: ```php $this->forward('Product:show'); ``` -Приклад тимчасового перенаправлення з HTTP-кодом 302 або 303: +Приклад так званого тимчасового перенаправлення з HTTP-кодом 302 (або 303, якщо метод поточного запиту POST): ```php $this->redirect('Product:show', $id); ``` -Для досягнення постійного перенаправлення з HTTP-кодом 301 використовуйте: +Постійне перенаправлення з HTTP-кодом 301 досягається так: ```php $this->redirectPermanent('Product:show', $id); ``` -Ви можете перенаправити на інший URL поза додатком за допомогою методу `redirectUrl()`: +На інший URL поза застосунком можна перенаправити методом `redirectUrl()`. Як другий параметр можна вказати HTTP-код, стандартний — 302 (або 303, якщо метод поточного запиту POST): ```php $this->redirectUrl('https://nette.org'); ``` -Перенаправлення негайно завершує життєвий цикл презентера, викидаючи так зване виключення мовчазного завершення `Nette\Application\AbortException`. +Перенаправлення негайно завершує роботу презентера, викидаючи так званий тихий завершальний виняток `Nette\Application\AbortException`. -Перед перенаправленням можна надіслати [флеш-повідомлення |#Flash-Messages], яке відображатиметься в шаблоні після перенаправлення. +Перед перенаправленням можна надіслати [#flash-повідомлення], тобто повідомлення, які будуть відображені в шаблоні після перенаправлення. -Флеш-повідомлення .[#toc-flash-messages] -======================================== +Flash-повідомлення +================== -Це повідомлення, які зазвичай інформують про результат операції. Важливою особливістю флеш-повідомлень є те, що вони доступні в шаблоні навіть після перенаправлення. Навіть після відображення вони залишатимуться живими ще 30 секунд - наприклад, на випадок, якщо користувач ненавмисно оновить сторінку - повідомлення не буде загублено. +Це повідомлення, які зазвичай інформують про результат якоїсь операції. Важливою особливістю flash-повідомлень є те, що вони доступні в шаблоні навіть після перенаправлення. Навіть після відображення вони залишаються активними ще 30 секунд – наприклад, на випадок, якщо через помилку передачі користувач оновить сторінку - повідомлення йому одразу не зникне. -Просто викличте метод [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] і презентер подбає про передачу повідомлення в шаблон. Перший аргумент - текст повідомлення, а другий необов'язковий аргумент - його тип (помилка, попередження, інформація тощо). Метод `flashMessage()` повертає екземпляр флеш-повідомлення, щоб ми могли додати додаткову інформацію. +Достатньо викликати метод [flashMessage() |api:Nette\Application\UI\Control::flashMessage()], і про передачу в шаблон подбає презентер. Першим параметром є текст повідомлення, а необов'язковим другим параметром — його тип (error, warning, info тощо). Метод `flashMessage()` повертає екземпляр flash-повідомлення, до якого можна додавати додаткову інформацію. ```php -$this->flashMessage('Item was removed.'); -$this->redirect(/* ... */); +$this->flashMessage('Елемент було видалено.'); +$this->redirect(/* ... */); // і перенаправляємо ``` -У шаблоні ці повідомлення доступні у змінній `$flashes` як об'єкти `stdClass`, які містять властивості `message` (текст повідомлення), `type` (тип повідомлення) і можуть містити вже згадану інформацію про користувача. Ми виводимо їх таким чином: +У шаблоні ці повідомлення доступні у змінній `$flashes` як об'єкти `stdClass`, які містять властивості `message` (текст повідомлення), `type` (тип повідомлення) і можуть містити вже згадану користувацьку інформацію. Відобразимо їх, наприклад, так: ```latte {foreach $flashes as $flash} @@ -202,10 +202,10 @@ $this->redirect(/* ... */); ``` -Помилка 404 тощо. .[#toc-error-404-etc] -======================================= +Помилка 404 тощо. +================= -Коли ми не можемо виконати запит, тому що, наприклад, стаття, яку ми хочемо відобразити, не існує в базі даних, ми викинемо помилку 404, використовуючи метод `error(string $message = null, int $httpCode = 404)`, який представляє HTTP-помилку 404: +Якщо неможливо виконати запит, наприклад, через те, що стаття, яку ми хочемо відобразити, не існує в базі даних, ми викидаємо помилку 404 методом `error(?string $message = null, int $httpCode = 404)`. ```php public function renderShow(int $id): void @@ -218,14 +218,13 @@ public function renderShow(int $id): void } ``` -Код помилки HTTP може бути переданий як другий параметр, за замовчуванням це 404. Метод працює, викидаючи виняток `Nette\Application\BadRequestException`, після чого `Application` передає управління презентуванню помилки. Його завдання - відобразити сторінку, що інформує про помилку. -Преселектор помилок встановлюється в [конфігураціях програми |configuration]. +HTTP-код помилки можна передати як другий параметр, стандартний — 404. Метод працює так, що викидає виняток `Nette\Application\BadRequestException`, після чого `Application` передає управління error-презентеру. Це презентер, завданням якого є відобразити сторінку, що інформує про помилку. Налаштування error-презентера здійснюється в [конфігурації application|configuration]. -Надсилання JSON .[#toc-sending-json] -==================================== +Надсилання JSON +=============== -Приклад дії-методу, який надсилає дані у форматі JSON і виходить із ведучого: +Приклад action-методу, який надсилає дані у форматі JSON і завершує презентер: ```php public function actionData(): void @@ -236,33 +235,59 @@ public function actionData(): void ``` -Постійні параметри .[#toc-persistent-parameters] -================================================ +Параметри запиту .{data-version:3.1.14} +======================================= + +Презентер, а також кожен компонент, отримує з HTTP-запиту свої параметри. Їхнє значення ви можете дізнатися методом `getParameter($name)` або `getParameters()`. Значення є рядками або масивами рядків, це, по суті, сирі дані, отримані безпосередньо з URL. + +Для більшої зручності рекомендуємо зробити параметри доступними через властивості. Достатньо позначити їх атрибутом `#[Parameter]`: + +```php +use Nette\Application\Attributes\Parameter; // цей рядок важливий + +class HomePresenter extends Nette\Application\UI\Presenter +{ + #[Parameter] + public string $theme; // має бути public +} +``` + +Для властивості рекомендуємо вказувати тип даних (наприклад, `string`), і Nette автоматично перетворить значення відповідно до нього. Значення параметрів також можна [валідувати |#Валідація параметрів]. + +При створенні посилання можна безпосередньо встановити значення параметрів: + +```latte +натисніть +``` -Постійні параметри використовуються для збереження стану між різними запитами. Їх значення залишається незмінним навіть після переходу за посиланням. На відміну від сесійних даних, вони передаються в URL-адресі. Це відбувається повністю автоматично, тому немає необхідності явно вказувати їх в `link()` або `n:href`. -Приклад використання? У вас є багатомовний додаток. Фактична мова - це параметр, який завжди повинен бути частиною URL-адреси. Але було б неймовірно нудно включати його в кожне посилання. Тому ви робите його постійним параметром з іменем `lang`, і він сам себе переноситиме. Круто! +Персистентні параметри +====================== -Створити постійний параметр у Nette надзвичайно просто. Просто створіть загальнодоступну властивість і позначте її атрибутом: (раніше використовували `/** @persistent */` ) +Персистентні параметри служать для підтримки стану між різними запитами. Їхнє значення залишається незмінним навіть після натискання на посилання. На відміну від даних у сесії, вони передаються в URL. І це відбувається повністю автоматично, тому не потрібно їх явно вказувати в `link()` або `n:href`. + +Приклад використання? У вас багатомовний застосунок. Поточна мова — це параметр, який повинен постійно бути частиною URL. Але було б надзвичайно втомливо вказувати його в кожному посиланні. Тож ви робите його персистентним параметром `lang`, і він буде передаватися сам. Чудово! + +Створення персистентного параметра в Nette надзвичайно просте. Достатньо створити публічну властивість і позначити її атрибутом: (раніше використовувалося `/** @persistent */`) ```php -use Nette\Application\Attributes\Persistent; // цей рядок важливий +use Nette\Application\Attributes\Persistent; // цей рядок важливий class ProductPresenter extends Nette\Application\UI\Presenter { #[Persistent] - public string $lang; // повинні бути публічними + public string $lang; // має бути public } ``` -Якщо `$this->lang` має значення `'en'`, то посилання, створені за допомогою `link()` або `n:href`, також будуть містити параметр `lang=en`. І коли посилання буде натиснуто, воно знову стане `$this->lang = 'en'`. +Якщо `$this->lang` матиме значення, наприклад, `'en'`, то й посилання, створені за допомогою `link()` або `n:href`, міститимуть параметр `lang=en`. І після натискання на посилання знову буде `$this->lang = 'en'`. -Для властивостей рекомендується вказувати тип даних (наприклад, `string`), а також можна вказати значення за замовчуванням. Значення параметрів можна [перевіряти |#Validation of Persistent Parameters]. +Для властивості рекомендуємо вказувати тип даних (наприклад, `string`) і ви можете вказати значення за замовчуванням. Значення параметрів можна [валідувати |#Валідація параметрів]. -Постійні параметри за замовчуванням передаються між усіма діями даного доповідача. Щоб передати їх між кількома доповідачами, вам також потрібно їх визначити: +Персистентні параметри стандартно передаються між усіма діями даного презентера. Щоб вони передавалися і між кількома презентерами, їх потрібно визначити або: -- у спільному предку, від якого успадковуються ведучі -- в ознаці, яку ведучі використовують: +- у спільному предку, від якого успадковують презентери +- у трейті, який використовують презентери: ```php trait LanguageAware @@ -277,48 +302,42 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -Ви можете змінити значення постійного параметра при створенні посилання: +При створенні посилання можна змінити значення персистентного параметра: ```latte -detail in Czech +деталі українською ``` -Або ж його можна *скинути*, тобто видалити з URL-адреси. Тоді він прийме значення за замовчуванням: +Або його можна *скинути*, тобто видалити з URL. Тоді він набуде свого значення за замовчуванням: ```latte -click +натисніть ``` -Інтерактивні компоненти .[#toc-interactive-components] -====================================================== +Інтерактивні компоненти +======================= -У презентерів є вбудована система компонентів. Компоненти - це окремі багаторазово використовувані одиниці, які ми поміщаємо в презентери. Це можуть бути [форми |forms:in-presenter], сітки даних, меню, загалом, усе, що має сенс використовувати багаторазово. +Презентери мають вбудовану систему компонентів. Компоненти — це окремі повторно використовувані одиниці, які ми вставляємо в презентери. Це можуть бути [форми |forms:in-presenter], datagrid, меню, власне все, що має сенс використовувати повторно. -Як розміщуються і згодом використовуються компоненти в презентері? Це пояснюється в розділі [компоненти |components]. Ви навіть дізнаєтеся, який стосунок вони мають до Голлівуду. +Як компоненти вставляються в презентер і потім використовуються? Це ви дізнаєтеся в розділі [Компоненти |components]. Ви навіть дізнаєтеся, що вони мають спільного з Голлівудом. -Де можна придбати деякі компоненти? На сторінці [Componette |https://componette.org] ви можете знайти деякі компоненти з відкритим вихідним кодом та інші доповнення для Nette, що створюються та розповсюджуються спільнотою фреймворку Nette. +А де я можу отримати компоненти? На сторінці [Componette |https://componette.org/search/component] ви знайдете компоненти з відкритим кодом, а також багато інших доповнень для Nette, які сюди розмістили добровольці зі спільноти навколо фреймворку. -Заглиблюємося .[#toc-going-deeper] -================================== +Заглиблюємося +============= .[tip] -Того, що ми показали досі в цьому розділі, ймовірно, буде достатньо. Наступні рядки призначені для тих, хто цікавиться презентерами досконально і хоче знати все. - +З тим, що ми досі показали в цьому розділі, ви, ймовірно, цілком впораєтеся. Наступні рядки призначені для тих, хто цікавиться презентерами до глибини і хоче знати абсолютно все. -Вимоги та параметри .[#toc-requirement-and-parameters] ------------------------------------------------------- -Запит, який обробляє доповідач, є об'єктом [api:Nette\Application\Request] і повертається методом доповідача `getRequest()`. Він містить масив параметрів, кожен з яких належить або якомусь з компонентів, або безпосередньо доповідачу (який, власне, теж є компонентом, хоча й особливим). Тож Nette перерозподіляє параметри та передачі між окремими компонентами (та доповідачем) за допомогою виклику методу `loadState(array $params)`. Параметри можна отримати за допомогою методу `getParameters(): array`, окремо за допомогою `getParameter($name)`. Значення параметрів - це рядки або масиви рядків, в основному це необроблені дані, отримані безпосередньо з URL-адреси. - - -Перевірка постійних параметрів .[#toc-validation-of-persistent-parameters] --------------------------------------------------------------------------- +Валідація параметрів +-------------------- -Значення [постійних параметрів |#persistent parameters], отриманих з URL-адрес, записуються у властивості методом `loadState()`. Також перевіряється, чи збігається тип даних, вказаний у властивості, інакше буде видано помилку 404, і сторінка не буде відображена. +Значення [параметрів запиту |#Параметри запиту] та [персистентних параметрів |#Персистентні параметри], отримані з URL, записує у властивості метод `loadState()`. Він також перевіряє, чи відповідає тип даних, вказаний у властивості, інакше відповідає помилкою 404 і сторінка не відображається. -Ніколи не довіряйте сліпо постійним параметрам, оскільки вони можуть бути легко перезаписані користувачем в URL. Наприклад, так ми перевіряємо, чи є `$this->lang` серед підтримуваних мов. Хороший спосіб зробити це - перевизначити метод `loadState()`, згаданий вище: +Ніколи сліпо не довіряйте параметрам, оскільки їх може легко перезаписати користувач в URL. Таким чином, наприклад, перевіримо, чи мова `$this->lang` є серед підтримуваних. Підходящим способом є перезапис згаданого методу `loadState()`: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -328,8 +347,8 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function loadState(array $params): void { - parent::loadState($params); // тут задається значення $this->lang - // слідує за перевіркою користувацького значення: + parent::loadState($params); // тут встановлюється $this->lang + // далі йде власна перевірка значення: if (!in_array($this->lang, ['en', 'cs'])) { $this->error(); } @@ -338,31 +357,33 @@ class ProductPresenter extends Nette\Application\UI\Presenter ``` -Збереження та відновлення запиту .[#toc-save-and-restore-the-request] ---------------------------------------------------------------------- +Збереження та відновлення запиту +-------------------------------- -Ви можете зберегти поточний запит у сесії або відновити його із сесії та дозволити презентеру виконати його знову. Це корисно, наприклад, коли користувач заповнює форму, а термін дії його логіна закінчується. Щоб не втратити дані, перед перенаправленням на сторінку реєстрації ми зберігаємо поточний запит у сесію за допомогою функції `$reqId = $this->storeRequest()`, яка повертає ідентифікатор у вигляді короткого рядка і передає його як параметр презенту для реєстрації. +Запит, який обробляє презентер, є об'єктом [api:Nette\Application\Request] і повертає його метод презентера `getRequest()`. -Після входу в систему ми викликаємо метод `$this->restoreRequest($reqId)`, який забирає запит у сесії та пересилає його їй. Метод перевіряє, що запит був створений тим самим користувачем, який зараз увійшов у систему. Якщо інший користувач увійшов у систему або ключ недійсний, він нічого не робить, і програма продовжує роботу. +Поточний запит можна зберегти в сесію або, навпаки, відновити з неї і змусити презентер знову його виконати. Це корисно, наприклад, у ситуації, коли користувач заповнює форму, і його сесія закінчується. Щоб не втратити дані, перед перенаправленням на сторінку входу поточний запит зберігаємо в сесію за допомогою `$reqId = $this->storeRequest()`, яке повертає його ідентифікатор у вигляді короткого рядка, і передаємо його як параметр презентеру входу. -Див. розділ [Як повернутися на попередню сторінку |best-practices:restore-request]. +Після входу викликаємо метод `$this->restoreRequest($reqId)`, який витягує запит із сесії та перенаправляє на нього. Метод при цьому перевіряє, що запит створив той самий користувач, який зараз увійшов. Якщо увійшов інший користувач або ключ недійсний, він нічого не робить, і програма продовжує роботу. +Подивіться на інструкцію [Як повернутися на попередню сторінку |best-practices:restore-request]. -Канонізація .[#toc-canonization] --------------------------------- -У презентерів є одна справді чудова функція, яка покращує SEO. Вони автоматично запобігають існуванню дублюючого контенту на різних URL-адресах. Якщо кілька URL-адрес ведуть до певного місця призначення, наприклад, `/index` і `/index?page=1`, фреймворк призначає одну з них основною (канонічною) і перенаправляє на неї решту за допомогою HTTP-коду 301. Завдяки цьому пошукові системи не індексують сторінки двічі і не послаблюють їхній рейтинг. +Канонізація +----------- -Цей процес називається канонізацією. Канонічний URL - це URL, згенерований [маршрутом |routing], зазвичай перший відповідний маршрут у колекції. +Презентери мають одну справді чудову властивість, яка сприяє кращому SEO (оптимізації знаходження в Інтернеті). Вони автоматично запобігають існуванню дубльованого контенту на різних URL. Якщо до певної цілі веде кілька URL-адрес, наприклад, `/index` та `/index?page=1`, фреймворк визначає одну з них як первинну (канонічну) і решту на неї перенаправляє за допомогою HTTP-коду 301. Завдяки цьому пошукові системи не індексують ваші сторінки двічі і не розмивають їхній page rank. -Канонізація ввімкнена за замовчуванням і може бути вимкнена за допомогою `$this->autoCanonicalize = false`. +Цей процес називається канонізацією. Канонічним URL є той, який генерує [маршрутизатор|routing], зазвичай це перший відповідний маршрут у колекції. -Перенаправлення не відбувається під час запиту AJAX або POST, оскільки це призведе до втрати даних або не принесе жодної користі для SEO. +Канонізація стандартно ввімкнена і її можна вимкнути через `$this->autoCanonicalize = false`. -Ви також можете викликати канонізацію вручну за допомогою методу `canonicalize()`, який, як і метод `link()`, отримує як аргументи презентера, дії та параметри. Він створює посилання і порівнює його з поточним URL. Якщо вони відрізняються, то відбувається перенаправлення на згенероване посилання. +Перенаправлення не відбувається при AJAX- або POST-запиті, оскільки це призвело б до втрати даних або не мало б доданої вартості з точки зору SEO. + +Канонізацію можна викликати й вручну за допомогою методу `canonicalize()`, якому, подібно до методу `link()`, передається презентер, дія та параметри. Він створює посилання і порівнює його з поточною URL-адресою. Якщо вони відрізняються, то перенаправляє на згенероване посилання. ```php -public function actionShow(int $id, string $slug = null): void +public function actionShow(int $id, ?string $slug = null): void { $realSlug = $this->facade->getSlugForId($id); // перенаправляє, якщо $slug відрізняється від $realSlug @@ -371,10 +392,10 @@ public function actionShow(int $id, string $slug = null): void ``` -Події .[#toc-events] --------------------- +Події +----- -Крім методів `startup()`, `beforeRender()` і `shutdown()`, які викликаються в рамках життєвого циклу презентера, можна визначити інші функції, які будуть викликатися автоматично. Презентер визначає так звані [події |nette:glossary#Events], а ви додаєте їхні обробники в масиви `$onStartup`, `$onRender` і `$onShutdown`. +Крім методів `startup()`, `beforeRender()` та `shutdown()`, які викликаються як частина життєвого циклу презентера, можна визначити ще інші функції, які мають автоматично викликатися. Презентер визначає так звану [подію |nette:glossary#Події události], обробники якої ви додаєте до масивів `$onStartup`, `$onRender` та `$onShutdown`. ```php class ArticlePresenter extends Nette\Application\UI\Presenter @@ -388,19 +409,19 @@ class ArticlePresenter extends Nette\Application\UI\Presenter } ``` -Обробники в масиві `$onStartup` викликаються безпосередньо перед методом `startup()`, потім `$onRender` між `beforeRender()` і `render()` і, нарешті, `$onShutdown` безпосередньо перед `shutdown()`. +Обробники в масиві `$onStartup` викликаються безпосередньо перед методом `startup()`, далі `$onRender` між `beforeRender()` та `render()`, і нарешті `$onShutdown` безпосередньо перед `shutdown()`. -Відповіді .[#toc-responses] ---------------------------- +Відповіді +--------- -Відповідь, яку повертає презентер, являє собою об'єкт, що реалізує інтерфейс [api:Nette\Application\Response]. Є кілька готових відповідей: +Відповідь, яку повертає презентер, є об'єктом, що реалізує інтерфейс [api:Nette\Application\Response]. Доступно багато готових відповідей: -- [api:Nette\Application\Responses\CallbackResponse] - надсилає зворотний виклик +- [api:Nette\Application\Responses\CallbackResponse] - надсилає callback - [api:Nette\Application\Responses\FileResponse] - надсилає файл -- [api:Nette\Application\Responses\ForwardResponse] - forward () +- [api:Nette\Application\Responses\ForwardResponse] - forward() - [api:Nette\Application\Responses\JsonResponse] - надсилає JSON -- [api:Nette\Application\Responses\RedirectResponse] - перенаправляє +- [api:Nette\Application\Responses\RedirectResponse] - перенаправлення - [api:Nette\Application\Responses\TextResponse] - надсилає текст - [api:Nette\Application\Responses\VoidResponse] - порожня відповідь @@ -412,23 +433,68 @@ use Nette\Application\Responses; // Простий текст $this->sendResponse(new Responses\TextResponse('Hello Nette!')); -// Відправляє файл +// Надсилає файл $this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf')); -// Відправляє зворотній виклик +// Відповіддю буде callback $callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse) { if ($httpResponse->getHeader('Content-Type') === 'text/html') { - echo '

    Привіт

    '; + echo '

    Hello

    '; } }; $this->sendResponse(new Responses\CallbackResponse($callback)); ``` -Читати далі .[#toc-further-reading] -=================================== +Обмеження доступу за допомогою `#[Requires]` .{data-version:3.2.2} +------------------------------------------------------------------ + +Атрибут `#[Requires]` надає розширені можливості для обмеження доступу до презентерів та їхніх методів. Його можна використовувати для специфікації HTTP-методів, вимоги AJAX-запиту, обмеження на той самий походження (same origin) та доступу лише через переадресацію. Атрибут можна застосовувати як до класів презентерів, так і до окремих методів `action()`, `render()`, `handle()` та `createComponent()`. + +Ви можете визначити такі обмеження: +- на HTTP-методи: `#[Requires(methods: ['GET', 'POST'])]` +- вимога AJAX-запиту: `#[Requires(ajax: true)]` +- доступ лише з того самого походження: `#[Requires(sameOrigin: true)]` +- доступ лише через forward: `#[Requires(forward: true)]` +- обмеження на конкретні дії: `#[Requires(actions: 'default')]` + +Деталі ви знайдете в інструкції [Як використовувати атрибут Requires |best-practices:attribute-requires]. + + +Перевірка HTTP-методу +--------------------- + +Презентери в Nette автоматично перевіряють HTTP-метод кожного вхідного запиту. Причиною цієї перевірки є насамперед безпека. Стандартно дозволені методи `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH`. + +Якщо ви хочете додатково дозволити, наприклад, метод `OPTIONS`, використовуйте для цього атрибут `#[Requires]` (з Nette Application v3.2): + +```php +#[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])] +class MyPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +У версії 3.1 перевірка проводиться в `checkHttpMethod()`, яка з'ясовує, чи міститься метод, вказаний у запиті, в масиві `$presenter->allowedMethods`. Додавання методу зробіть так: + +```php +class MyPresenter extends Nette\Application\UI\Presenter +{ + protected function checkHttpMethod(): void + { + $this->allowedMethods[] = 'OPTIONS'; + parent::checkHttpMethod(); + } +} +``` + +Важливо підкреслити, що якщо ви дозволите метод `OPTIONS`, ви повинні потім також належним чином обробити його в рамках свого презентера. Метод часто використовується як так званий preflight request, який браузер автоматично надсилає перед фактичним запитом, коли потрібно з'ясувати, чи дозволений запит з точки зору політики CORS (Cross-Origin Resource Sharing). Якщо ви дозволите метод, але не реалізуєте правильну відповідь, це може призвести до невідповідностей та потенційних проблем безпеки. + + +Подальше читання +================ -- Введення [методів та атрибутів |best-practices:inject-method-attribute] -- Складання презентерів [з |best-practices:presenter-traits]атрибутів -- Передача [налаштувань |best-practices:passing-settings-to-presenters]презентерам +- [Методи та атрибути inject |best-practices:inject-method-attribute] +- [Компонування презентерів з трейтів |best-practices:presenter-traits] +- [Передача налаштувань у презентери |best-practices:passing-settings-to-presenters] - [Як повернутися на попередню сторінку |best-practices:restore-request] diff --git a/application/uk/routing.texy b/application/uk/routing.texy index 6295ad4828..ab3b132ccd 100644 --- a/application/uk/routing.texy +++ b/application/uk/routing.texy @@ -3,27 +3,26 @@
    -Маршрутизатор подбає про все, що пов'язано з URL-адресами, так що вам більше не доведеться про них думати. Давайте покажемо: +Маршрутизатор відповідає за все, що стосується URL-адрес, щоб вам більше не доводилося над ними замислюватися. Ми покажемо: -- як налаштувати маршрутизатор так, щоб URL-адреси були такими, якими ви хочете їх бачити -- як налаштувати SEO та перенаправлення -- як написати свій власний маршрутизатор +- як налаштувати маршрутизатор, щоб URL були такими, як ви хочете +- поговоримо про SEO та перенаправлення +- і покажемо, як написати власний маршрутизатор
    -Більш людські URL (або круті або красиві URL) зручніші для використання, краще запам'ятовуються і позитивно впливають на SEO. Nette враховує це і повністю задовольняє бажання розробників. Ви можете розробити структуру URL для вашого застосунку саме так, як ви хочете. -Ви можете навіть розробити її після того, як застосунок буде готовий, оскільки це можна зробити без будь-яких змін коду або шаблону. Вона визначається елегантним чином в [одному єдиному місці |#Integration], у маршрутизаторі, і не розкидана у вигляді анотацій по всіх презентаторах. +Більш людські URL (або також cool чи pretty URL) є більш зручними для використання, легше запам'ятовуються та позитивно впливають на SEO. Nette про це думає і повністю йде назустріч розробникам. Ви можете для свого застосунку розробити саме таку структуру URL-адрес, яку захочете. Ви можете її розробити навіть тоді, коли застосунок вже готовий, оскільки це обійдеться без втручань у код чи шаблони. Визначається це елегантним способом в одному [єдиному місці |#Включення в застосунок], у маршрутизаторі, і таким чином не розкидано у вигляді анотацій у всіх презентерах. -Маршрутизатор у Nette особливий, бо він **двонаправлений**, він може як декодувати URL HTTP-запитів, так і створювати посилання. Тому він відіграє важливу роль у [Nette Application |how-it-works#Nette-Application], оскільки він вирішує, який ведучий і дія виконуватимуть поточний запит, а також використовується для [генерації URL |creating-links] у шаблоні тощо. +Маршрутизатор у Nette є винятковим тим, що він **двосторонній.** Він вміє як декодувати URL в HTTP-запиті, так і створювати посилання. Отже, він відіграє ключову роль у [Nette Application |how-it-works#Nette Application], оскільки не тільки вирішує, який презентер та дія виконуватимуть поточний запит, але також використовується для [генерування URL |creating-links] у шаблоні тощо. -Однак маршрутизатор не обмежується цим застосуванням, ви можете використовувати його в додатках, де ведучі взагалі не використовуються, для REST API тощо. Докладніше в розділі [роздільне використання |#separated-usage]. +Однак маршрутизатор не обмежений лише цим використанням, ви можете його використовувати в застосунках, де взагалі не використовуються презентери, для REST API тощо. Більше в частині [#самостійне використання]. -Колекція маршрутів .[#toc-route-collection] -=========================================== +Колекція маршрутів +================== -Найбільш приємним способом визначення URL-адрес у додатку є клас [api:Nette\Application\Routers\RouteList]. Визначення складається зі списку так званих маршрутів, тобто масок URL-адрес і пов'язаних із ними презентерів і дій за допомогою простого API. Нам не потрібно називати маршрути. +Найприємніший спосіб визначити вигляд URL-адрес у застосунку пропонує клас [api:Nette\Application\Routers\RouteList]. Визначення складається зі списку так званих маршрутів, тобто масок URL-адрес та пов'язаних з ними презентерів та дій за допомогою простого API. Маршрути не потрібно якось називати. ```php $router = new Nette\Application\Routers\RouteList; @@ -32,178 +31,178 @@ $router->addRoute('article/', 'Article:view'); // ... ``` -У прикладі йдеться про те, що якщо ми відкриємо `https://any-domain.com/rss.xml` з дією `rss` і т. д. Якщо відповідний маршрут не знайдено, застосунок Nette відповідає винятком [BadRequestException |api:Nette\Application\BadRequestException], який відображається для користувача як сторінка помилки 404 Not Found. +Приклад говорить, що якщо в браузері відкрити `https://domain.com/rss.xml`, відобразиться презентер `Feed` з дією `rss`, якщо `https://domain.com/article/12`, відобразиться презентер `Article` з дією `view` тощо. У разі не знаходження відповідного маршруту Nette Application реагує викиданням винятку [BadRequestException |api:Nette\Application\BadRequestException], який користувачеві відображається як сторінка помилки 404 Not Found. -Порядок маршрутів .[#toc-order-of-routes] ------------------------------------------ +Порядок маршрутів +----------------- -Порядок, у якому перелічено маршрути, **дуже важливий**, тому що вони оцінюються послідовно зверху вниз. Правило полягає в тому, що ми оголошуємо маршрути **від конкретних до загальних**: +Абсолютно **ключовим є порядок**, у якому вказані окремі маршрути, оскільки вони оцінюються послідовно зверху вниз. Діє правило, що маршрути декларуємо **від специфічних до загальних**: ```php -// ПОМИЛКА: 'rss.xml' відповідає першому маршруту і неправильно сприймає його як . +// ПОГАНО: 'rss.xml' перехопить перший маршрут і розуміє цей рядок як $router->addRoute('', 'Article:view'); $router->addRoute('rss.xml', 'Feed:rss'); -// ДЕТАЛЬНІШЕ +// ДОБРЕ $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('', 'Article:view'); ``` -Маршрути також оцінюються зверху вниз під час генерації посилань: +Маршрути оцінюються зверху вниз також при генерації посилань: ```php -// ПОМИЛКА: генерує посилання на 'Feed:rss' як 'admin/feed/rss' +// ПОГАНО: посилання на 'Feed:rss' згенерує як 'admin/feed/rss' $router->addRoute('admin//', 'Admin:default'); $router->addRoute('rss.xml', 'Feed:rss'); -// ДЕТАЛЬНІШЕ +// ДОБРЕ $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('admin//', 'Admin:default'); ``` -Ми не будемо приховувати від вас, що для правильної побудови списку потрібна певна навичка. Поки ви не навчитеся, панель [routing panel |#Debugging-Router] буде корисним інструментом. +Ми не будемо приховувати від вас, що правильне складання маршрутів вимагає певної вправності. Перш ніж ви в неї проникнете, вам буде корисним помічником [панель маршрутизації |#Налагодження маршрутизатора]. -Маска і параметри .[#toc-mask-and-parameters] ---------------------------------------------- +Маска та параметри +------------------ -Маска описує відносний шлях, заснований на корені сайту. Найпростіша маска - це статичний URL: +Маска описує відносний шлях від кореневого каталогу веб-сайту. Найпростішою маскою є статичний URL: ```php $router->addRoute('products', 'Products:default'); ``` -Часто маски містять так звані **параметри**. Вони укладені в кутові дужки (наприклад, ``) і передаються в цільовий презентер, наприклад, у метод `renderShow(int $year)` або в постійний параметр `$year`: +Часто маски містять так звані **параметри**. Вони вказані в кутових дужках (наприклад, ``) і передаються до цільового презентера, наприклад, методу `renderShow(int $year)` або до персистентного параметра `$year`: ```php $router->addRoute('chronicle/', 'History:show'); ``` -У прикладі йдеться про те, що якщо ми відкриємо `https://any-domain.com/chronicle/2020` і дія `show` з параметром `year: 2020` відображатиметься. +Приклад говорить, що якщо в браузері відкрити `https://example.com/chronicle/2020`, відобразиться презентер `History` з дією `show` та параметром `year: 2020`. -Ми можемо вказати значення за замовчуванням для параметрів безпосередньо в масці, і таким чином вона стає необов'язковою: +Параметрам можна визначити значення за замовчуванням безпосередньо в масці, і тим самим вони стануть необов'язковими: ```php $router->addRoute('chronicle/', 'History:show'); ``` -Тепер маршрут прийматиме URL `https://any-domain.com/chronicle/` з параметром `year`: 2020`. +Маршрут тепер прийматиме й URL `https://example.com/chronicle/`, який знову відобразить `History:show` з параметром `year: 2020`. -Звісно, ім'я презентера та дія також можуть бути параметрами. Наприклад: +Параметром може бути, звичайно, й ім'я презентера та дії. Наприклад, так: ```php $router->addRoute('/', 'Home:default'); ``` -Цей маршрут приймає, наприклад, URL у формі `/article/edit` і `/catalog/list` відповідно, і переводить їх у презентери та дії `Article:edit` і `Catalog:list` відповідно. +Зазначений маршрут приймає, наприклад, URL у вигляді `/article/edit` або також `/catalog/list` і розуміє їх як презентери та дії `Article:edit` та `Catalog:list`. -Він також надає параметрам `presenter` і `action` значення за замовчуванням `Home` і `default` і тому вони є необов'язковими. Тому маршрут також приймає URL `/article` і переводить його як `Article:default`. Або навпаки, посилання на `Product:default` генерує шлях `/product`, посилання на стандартну `Home:default` генерує шлях `/`. +Водночас він надає параметрам `presenter` та `action` значення за замовчуванням `Home` та `default`, і вони, отже, також є необов'язковими. Тому маршрут приймає й URL у вигляді `/article` і розуміє його як `Article:default`. Або навпаки, посилання на `Product:default` згенерує шлях `/product`, посилання на стандартний `Home:default` шлях `/`. -Маска може описувати не тільки відносний шлях, що базується на корені сайту, а й абсолютний шлях, якщо він починається зі слеша, або навіть увесь абсолютний URL, якщо він починається з двох слешів: +Маска може описувати не тільки відносний шлях від кореневого каталогу веб-сайту, але й абсолютний шлях, якщо починається зі слеша, або навіть цілий абсолютний URL, якщо починається з двох слешів: ```php -// відносний шлях до кореня програми +// відносно до document root $router->addRoute('/', /* ... */); -// абсолютний шлях, відносно імені хоста сервера +// абсолютний шлях (відносно до домену) $router->addRoute('//', /* ... */); -// абсолютний URL, включно з ім'ям хоста (щодо схеми) +// абсолютний URL включно з доменом (відносно до схеми) $router->addRoute('//.example.com//', /* ... */); -// абсолютний URL, включно зі схемою +// абсолютний URL включно зі схемою $router->addRoute('https://.example.com//', /* ... */); ``` -Валідаційні вирази .[#toc-validation-expressions] -------------------------------------------------- +Валідаційні вирази +------------------ -Для кожного параметра можна задати умову перевірки за допомогою [регулярного виразу |https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Наприклад, давайте задамо `id` тільки числовим, використовуючи `\d+` regexp: +Для кожного параметра можна встановити умову валідації за допомогою [регулярного виразу|https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Наприклад, параметру `id` визначимо, що він може набувати лише цифр за допомогою регулярного виразу `\d+`: ```php $router->addRoute('/[/]', /* ... */); ``` -За замовчуванням регулярним виразом для всіх параметрів є `[^/]+`, тобто все, крім слеша. Якщо параметр має відповідати також косій рисі, ми задаємо регулярний вираз `.+`. +Стандартним регулярним виразом для всіх параметрів є `[^/]+`, тобто все, крім слеша. Якщо параметр має приймати й слеші, вкажемо вираз `.+`: ```php -// приймає https://example.com/a/b/c, path - 'a/b/c' +// приймає https://example.com/a/b/c, path буде 'a/b/c' $router->addRoute('', /* ... */); ``` -Необов'язкові послідовності .[#toc-optional-sequences] ------------------------------------------------------- +Необов'язкові послідовності +--------------------------- -Квадратні дужки позначають необов'язкові частини маски. Будь-яка частина маски може бути задана як необов'язкова, включно з тими, які містять параметри: +У масці можна позначати необов'язкові частини за допомогою квадратних дужок. Необов'язковою може бути будь-яка частина маски, в ній можуть знаходитися й параметри: ```php $router->addRoute('[/]', /* ... */); -// URL-адреси, що приймаються: Параметри: -// /en/download lang => en, name => download -// /download lang => null, name => download +// Приймає шляхи: +// /cs/download => lang => cs, name => download +// /download => lang => null, name => download ``` -Звичайно, коли параметр є частиною необов'язкової послідовності, він також стає необов'язковим. Якщо у нього немає значення за замовчуванням, він дорівнюватиме null. +Коли параметр є частиною необов'язкової послідовності, він, зрозуміло, також стає необов'язковим. Якщо він не має вказаного значення за замовчуванням, то буде null. -Необов'язкові частини також можуть бути в домені: +Необов'язкові частини можуть бути й у домені: ```php $router->addRoute('//[.]example.com//', /* ... */); ``` -Послідовності можуть бути вільно вкладені та об'єднані: +Послідовності можна довільно вкладати та комбінувати: ```php $router->addRoute( - '[[-]/][page-]', + '[[-]/][/page-]', 'Home:default', ); -// URL-адреси, що приймаються: -// /ru/hello -// /en-us/hello -// /hello -// /hello/page-12 +// Приймає шляхи: +// /cs/hello +// /en-us/hello +// /hello +// /hello/page-12 ``` -Генератор URL намагається зробити URL якомога коротшим, тому те, що можна опустити, опускається. Тому, наприклад, маршрут `index[.html]` генерує шлях `/index`. Ви можете змінити цю поведінку, написавши знак оклику після лівої квадратної дужки: +При генерації URL прагнуть до найкоротшого варіанту, тому все, що можна пропустити, пропускається. Тому, наприклад, маршрут `index[.html]` генерує шлях `/index`. Змінити поведінку можна, вказавши знак оклику за лівою квадратною дужкою: ```php -// приймає /hello і /hello.html, генерує /hello +// приймає /hello та /hello.html, генерує /hello $router->addRoute('[.html]', /* ... */); -// приймає /hello і /hello.html, генерує /hello.html +// приймає /hello та /hello.html, генерує /hello.html $router->addRoute('[!.html]', /* ... */); ``` -Необов'язкові параметри (тобто параметри, що мають значення за замовчуванням) без квадратних дужок поводяться так, як якщо б вони були обгорнуті таким чином: +Необов'язкові параметри (тобто параметри, що мають значення за замовчуванням) без квадратних дужок поводяться, по суті, так, ніби вони були взяті в дужки таким чином: ```php $router->addRoute('//', /* ... */); -// дорівнює: +// відповідає цьому: $router->addRoute('[/[/[]]]', /* ... */); ``` -Щоб змінити спосіб генерації самої правої косої риски, тобто замість `/home/` отримати `/home`, налаштуйте маршрут таким чином: +Якщо ми хочемо вплинути на поведінку кінцевого слеша, щоб, наприклад, замість `/home/` генерувалося лише `/home`, цього можна досягти так: ```php $router->addRoute('[[/[/]]]', /* ... */); ``` -Символи підстановки .[#toc-wildcards] -------------------------------------- +Заступні знаки +-------------- -У масці абсолютного шляху ми можемо використовувати такі підстановні знаки, щоб уникнути, наприклад, необхідності записувати в маску домен, який може відрізнятися в середовищі розроблення та виробничому середовищі: +У масці абсолютного шляху ми можемо використовувати наступні заступні знаки і уникнути так, наприклад, необхідності записувати в маску домен, який може відрізнятися в середовищі розробки та робочому середовищі: - `%tld%` = домен верхнього рівня, наприклад, `com` або `org` - `%sld%` = домен другого рівня, наприклад, `example` -- `%domain%` = домен без піддоменів, наприклад, `example.com` +- `%domain%` = домен без субдоменів, наприклад, `example.com` - `%host%` = весь хост, наприклад, `www.example.com` - `%basePath%` = шлях до кореневого каталогу @@ -213,10 +212,10 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ @@ -225,7 +224,7 @@ $router->addRoute('/[/]', [ ]); ``` -Або ми можемо використовувати цю форму, зверніть увагу на переписування регулярного виразу перевірки: +Для більш детальної специфікації можна використовувати ще більш розширену форму, де крім значень за замовчуванням ми можемо встановити й інші властивості параметрів, як-от валідаційний регулярний вираз (див. параметр `id`): ```php use Nette\Routing\Route; @@ -243,19 +242,19 @@ $router->addRoute('/[/]', [ ]); ``` -Ці детальніші формати корисні для додавання додаткових метаданих. +Важливо зазначити, що якщо параметри, визначені в масиві, не вказані в масці шляху, їхні значення не можна змінити, навіть за допомогою query-параметрів, зазначених після знака питання в URL. -Фільтри та переклади .[#toc-filters-and-translations] ------------------------------------------------------ +Фільтри та переклади +-------------------- -Доброю практикою є написання вихідного коду англійською мовою, але що якщо вам потрібно, щоб URL вашого сайту було перекладено іншою мовою? +Вихідні коди застосунку ми пишемо англійською, але якщо веб-сайт повинен мати українські URL, то просте маршрутування типу: ```php $router->addRoute('/', 'Home:default'); ``` -буде генерувати англійські URL, такі як `/product/123` або `/cart`. Якщо ми хочемо, щоб презентери та дії в URL були перекладені німецькою мовою (наприклад, `/produkt/123` або `/einkaufswagen`), ми можемо використовувати словник перекладів. Щоб додати його, нам уже потрібен "більш зрозумілий" варіант другого параметра: +буде генерувати англійські URL, як-от `/product/123` або `/cart`. Якщо ми хочемо, щоб презентери та дії в URL були представлені українськими словами (наприклад, `/produkt/123` або `/koshyk`), ми можемо використати перекладацький словник. Для його запису вже потрібен "багатослівніший" варіант другого параметра: ```php use Nette\Routing\Route; @@ -264,26 +263,26 @@ $router->addRoute('/', [ 'presenter' => [ Route::Value => 'Home', Route::FilterTable => [ - // строка в URL => ведущий + // рядок в URL => презентер 'produkt' => 'Product', - 'einkaufswagen' => 'Cart', + 'koshyk' => 'Cart', 'katalog' => 'Catalog', ], ], 'action' => [ Route::Value => 'default', Route::FilterTable => [ - 'liste' => 'list', + 'spysok' => 'list', ], ], ]); ``` -Для одного і того ж презентера можна використовувати кілька ключів словника. Вони створюватимуть для нього різні псевдоніми. Останній ключ вважається канонічним варіантом (тобто той, який буде в згенерованому URL). +Кілька ключів перекладацького словника можуть вести на той самий презентер. Тим самим для нього створюються різні псевдоніми. За канонічний варіант (тобто той, який буде у згенерованому URL) вважається останній ключ. -Таблиця перекладу може бути застосована до будь-якого параметра таким чином. Однак якщо перекладу не існує, береться вихідне значення. Ми можемо змінити цю поведінку, додавши `Route::FilterStrict => true`, і тоді маршрут відхилятиме URL, якщо значення відсутнє в словнику. +Перекладацьку таблицю можна таким чином використовувати для будь-якого параметра. При цьому, якщо переклад не існує, береться початкове значення. Цю поведінку можна змінити, доповнивши `Route::FilterStrict => true`, і маршрут тоді відхилить URL, якщо значення немає в словнику. -На додаток до словника перекладу у вигляді масиву можна задати власні функції перекладу: +Крім перекладацького словника у вигляді масиву, можна застосувати й власні функції перекладу. ```php use Nette\Routing\Route; @@ -299,15 +298,15 @@ $router->addRoute('//', [ ]); ``` -Функція `Route::FilterIn` здійснює перетворення між параметром в URL і рядком, який потім передають презентувальнику, функція `FilterOut` забезпечує перетворення у зворотному напрямку. +Функція `Route::FilterIn` перетворює параметр в URL на рядок, який потім передається до презентера, функція `FilterOut` забезпечує перетворення у зворотному напрямку. -Параметри `presenter`, `action` і `module` вже мають зумовлені фільтри, які конвертують між PascalCase, camelCase і kebab-case відповідно, що використовуються в URL. Значення параметрів за замовчуванням уже записано в перетвореній формі, тому, наприклад, у випадку з презентером ми пишемо `` замість ``. +Параметри `presenter`, `action` та `module` вже мають передвизначені фільтри, які перетворюють між стилем PascalCase або camelCase та kebab-case, що використовується в URL. Значення параметрів за замовчуванням записується вже в трансформованому вигляді, тому, наприклад, у випадку презентера пишемо ``, а не ``. -Загальні фільтри .[#toc-general-filters] ----------------------------------------- +Загальні фільтри +---------------- -Крім фільтрів для конкретних параметрів, ви також можете визначити загальні фільтри, які отримують асоціативний масив усіх параметрів, які вони можуть змінювати будь-яким способом, а потім повертати. Загальні фільтри визначаються за ключем `null`. +Крім фільтрів, призначених для конкретних параметрів, ми можемо визначити також загальні фільтри, які отримають асоціативний масив усіх параметрів, які можуть будь-яким чином модифікувати, а потім їх повернути. Загальні фільтри визначаємо під ключем `null`. ```php use Nette\Routing\Route; @@ -315,62 +314,85 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => 'Home', 'action' => 'default', - null => [ + '' => [ Route::FilterIn => function (array $params): array { /* ... */ }, Route::FilterOut => function (array $params): array { /* ... */ }, ], ]); ``` -Загальні фільтри дають вам можливість налаштувати поведінку маршруту абсолютно будь-яким способом. Ми можемо використовувати їх, наприклад, для зміни параметрів на основі інших параметрів. Наприклад, переведення `` и `` на основі поточного значення параметра ``. +Загальні фільтри дають можливість змінити поведінку маршруту абсолютно будь-яким способом. Ми можемо їх використовувати, наприклад, для модифікації параметрів на основі інших параметрів. Наприклад, переклад `` та `` на основі поточного значення параметра ``. -Якщо для параметра визначено користувацький фільтр і одночасно існує загальний фільтр, користувацький `FilterIn` виконується перед загальним, і навпаки, загальний `FilterOut` виконується перед користувацьким. Таким чином, усередині загального фільтра знаходяться значення параметрів `presenter` і `action` відповідно, написані мовою PascalCase і camelCase відповідно. +Якщо параметр має визначений власний фільтр і одночасно існує загальний фільтр, виконується власний `FilterIn` перед загальним і, навпаки, загальний `FilterOut` перед власним. Тобто всередині загального фільтра значення параметрів `presenter` або `action` записані в стилі PascalCase або camelCase. -Прапор OneWay .[#toc-oneway-flag] ---------------------------------- +Односторонні OneWay +------------------- -Односторонні маршрути використовуються для збереження функціональності старих URL, які застосунок більше не генерує, але все ще приймає. Ми позначаємо їх прапором `OneWay`: +Односторонні маршрути використовуються для збереження функціональності старих URL, які застосунок вже не генерує, але все ще приймає. Позначимо їх прапорцем `OneWay`: ```php // старий URL /product-info?id=123 $router->addRoute('product-info', 'Product:detail', $router::ONE_WAY); -// нова URL-адреса /product/123 +// новий URL /product/123 $router->addRoute('product/', 'Product:detail'); ``` -У разі звернення до старої URL-адреси презентер автоматично перенаправляє на нову URL-адресу, щоб пошукові системи не індексували ці сторінки двічі (див. [SEO та канонізація |#seo-and-canonization]). +При доступі до старого URL презентер автоматично перенаправляє на новий URL, тому ці сторінки пошукові системи не проіндексують двічі (див. [#SEO та канонізація]). -Модулі .[#toc-modules] ----------------------- +Динамічна маршрутизація з callback-функціями +-------------------------------------------- -Якщо у нас є кілька маршрутів, що належать одному модулю, ми можемо використовувати `withModule()` для їхнього групування: +Динамічна маршрутизація з callback-функціями дозволяє вам призначати маршрутам безпосередньо функції (callback-функції), які виконуються, коли відвідується відповідний шлях. Ця гнучка функціональність дозволяє швидко та ефективно створювати різні кінцеві точки (endpoints) для вашого застосунку: + +```php +$router->addRoute('test', function () { + echo 'ви на адресі /test'; +}); +``` + +Ви також можете визначити в масці параметри, які автоматично передадуться до вашого callback: + +```php +$router->addRoute('', function (string $lang) { + echo match ($lang) { + 'cs' => 'Ласкаво просимо на українську версію нашого сайту!', + 'en' => 'Welcome to the English version of our website!', + }; +}); +``` + + +Модулі +------ + +Якщо у нас є кілька маршрутів, які належать до спільного [модуля |directory-structure#Presenter и та шаблони], використаємо `withModule()`: ```php $router = new RouteList; -$router->withModule('Forum') // наступні маршрутизатори входять до складу модуля Forum - ->addRoute('rss', 'Feed:rss') // презентер Forum:Feed +$router->withModule('Forum') // наступні маршрути є частиною модуля Forum + ->addRoute('rss', 'Feed:rss') // презентер буде Forum:Feed ->addRoute('/') - ->withModule('Admin') // наступні маршрутизатори є частиною модуля Forum:Admin + ->withModule('Admin') // наступні маршрути є частиною модуля Forum:Admin ->addRoute('sign:in', 'Sign:in'); ``` Альтернативою є використання параметра `module`: ```php -// URL manage/dashboard/default відображається на ведучого Admin:Dashboard +// URL manage/dashboard/default мапується на презентер Admin:Dashboard $router->addRoute('manage//', [ 'module' => 'Admin', ]); ``` -Субдомени .[#toc-subdomains] ----------------------------- +Субдомени +--------- -Колекції маршрутів можуть бути згруповані за піддоменами: +Колекції маршрутів ми можемо розділяти за субдоменами: ```php $router = new RouteList; @@ -379,7 +401,7 @@ $router->withDomain('example.com') ->addRoute('/'); ``` -Ви також можете використовувати [Символи підстановки |#Wildcards] у своєму доменному імені: +У назві домену можна використовувати й [#заступні знаки]: ```php $router = new RouteList; @@ -388,23 +410,23 @@ $router->withDomain('example.%tld%') ``` -Префікс шляху .[#toc-path-prefix] ---------------------------------- +Префікс шляху +------------- -Колекції маршрутів можуть бути згруповані за шляхом в URL: +Колекції маршрутів ми можемо розділяти за шляхом в URL: ```php $router = new RouteList; $router->withPath('eshop') - ->addRoute('rss', 'Feed:rss') // відповідає URL /eshop/rss - ->addRoute('/'); // відповідає URL /eshop// + ->addRoute('rss', 'Feed:rss') // ловить URL /eshop/rss + ->addRoute('/'); // ловить URL /eshop// ``` -Комбінації .[#toc-combinations] -------------------------------- +Комбінації +---------- -Вищевказані варіанти використання можна комбінувати: +Вищезгаданий поділ можна взаємно комбінувати: ```php $router = (new RouteList) @@ -424,40 +446,40 @@ $router = (new RouteList) ``` -Параметри запиту .[#toc-query-parameters] ------------------------------------------ +Query-параметри +--------------- -Маски також можуть містити параметри запиту (параметри після знака питання в URL). Вони не можуть визначити вираз перевірки, але вони можуть змінити ім'я, під яким вони передаються презентувальнику: +Маски можуть також містити query-параметри (параметри після знака питання в URL). Для них не можна визначити валідаційний вираз, але можна змінити назву, під якою вони передаються до презентера: ```php -// використовуємо параметр запиту 'cat' як 'categoryId' у застосунку +// query-параметр 'cat' ми хочемо в застосунку використовувати під назвою 'categoryId' $router->addRoute('product ? id= & cat=', /* ... */); ``` -Параметри Foo .[#toc-foo-parameters] ------------------------------------- +Foo-параметри +------------- -Тепер ми йдемо глибше. Параметри Foo - це, по суті, неіменовані параметри, які дозволяють зіставити регулярний вираз. Наступний маршрут відповідає `/index`, `/index.html`, `/index.htm` і `/index.php`: +Тепер ми заглиблюємося. Foo-параметри — це, по суті, неіменовані параметри, які дозволяють зіставляти регулярний вираз. Прикладом є маршрут, що приймає `/index`, `/index.html`, `/index.htm` та `/index.php`: ```php $router->addRoute('index', /* ... */); ``` -Також можна явно задати рядок, який буде використовуватися для генерації URL. Рядок має розташовуватися безпосередньо після знака запитання. Наступний маршрут схожий на попередній, але генерує `/index.html` замість `/index`, тому що рядок `.html` встановлено як "значення, що генерується". +Можна також явно визначити рядок, який буде використаний при генерації URL. Рядок повинен бути розміщений безпосередньо за знаком питання. Наступний маршрут схожий на попередній, але генерує `/index.html` замість `/index`, оскільки рядок `.html` встановлено як значення для генерації: ```php $router->addRoute('index', /* ... */); ``` -Інтеграція .[#toc-integration] -============================== +Включення в застосунок +====================== -Щоб підключити наш маршрутизатор до застосунку, ми повинні повідомити про нього контейнер DI. Найпростіший спосіб - це підготувати фабрику, яка буде створювати об'єкт маршрутизатора, і повідомити конфігурацію контейнера, щоб вона його використовувала. Припустимо, ми напишемо для цього метод `App\Router\RouterFactory::createRouter()`: +Щоб створений маршрутизатор підключити до застосунку, ми повинні про нього повідомити DI-контейнер. Найпростіший шлях — підготувати фабрику, яка створить об'єкт маршрутизатора, і повідомити в конфігурації контейнера, що її потрібно використовувати. Припустимо, що для цієї мети ми напишемо метод `App\Core\RouterFactory::createRouter()`: ```php -namespace App\Router; +namespace App\Core; use Nette\Application\Routers\RouteList; @@ -472,14 +494,14 @@ class RouterFactory } ``` -Потім ми пишемо в [configuration |dependency-injection:services]: +До [конфігурації |dependency-injection:services] потім запишемо: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter ``` -Будь-які залежності, такі як підключення до бази даних тощо, передаються методу фабрики як параметри за допомогою [autowiring |dependency-injection:autowiring]: +Будь-які залежності, наприклад, від бази даних тощо, передаються фабричному методу як його параметри за допомогою [autowiring|dependency-injection:autowiring]: ```php public static function createRouter(Nette\Database\Connection $db): RouteList @@ -489,25 +511,25 @@ public static function createRouter(Nette\Database\Connection $db): RouteList ``` -SimpleRouter .[#toc-simplerouter] -================================= +SimpleRouter +============ -Набагато простішим маршрутизатором, ніж колекція маршрутів, є [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Його можна використовувати, коли немає потреби в певному форматі URL, коли `mod_rewrite` (або альтернативи) недоступний або коли ми просто не хочемо поки що возитися зі зручними для користувача URL. +Набагато простішим маршрутизатором, ніж колекція маршрутів, є [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Ми використовуємо його тоді, коли не маємо особливих вимог до форми URL, якщо недоступний `mod_rewrite` (або його альтернативи) або якщо поки що не хочемо займатися гарними URL. -Генерує адреси приблизно такої форми: +Генерує адреси приблизно в такому вигляді: ``` http://example.com/?presenter=Product&action=detail&id=123 ``` -Параметром конструктора `SimpleRouter` є презентер і дія за замовчуванням, тобто дія, яку буде виконано, якщо ми відкриємо, наприклад, `http://example.com/` без додаткових параметрів. +Параметром конструктора SimpleRouter є стандартний презентер та дія, на який слід спрямовувати, якщо відкрити сторінку без параметрів, наприклад, `http://example.com/`. ```php -// використовуємо презентер 'Home' і дію 'default' +// стандартним презентером буде 'Home' та дія 'default' $router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` -Ми рекомендуємо визначати SimpleRouter безпосередньо в [конфігурації |dependency-injection:services]: +Рекомендуємо SimpleRouter безпосередньо визначати в [конфігурації |dependency-injection:services]: ```neon services: @@ -515,22 +537,22 @@ services: ``` -SEO та канонізація .[#toc-seo-and-canonization] -=============================================== +SEO та канонізація +================== -Фреймворк покращує SEO, запобігаючи дублюванню контенту на різних URL. Якщо кілька адрес посилаються на одне й те саме місце призначення, наприклад `/index` і `/index.html`, фреймворк визначає першу з них як основну (канонічну) і перенаправляє на неї решту за допомогою HTTP-коду 301. Завдяки цьому пошукові системи не будуть індексувати сторінки двічі і не порушать їхній сторінковий рейтинг. +Фреймворк сприяє SEO (оптимізації знаходження в Інтернеті), запобігаючи дублюванню контенту на різних URL. Якщо до певної цілі веде кілька адрес, наприклад, `/index` та `/index.html`, фреймворк першу з них визначає як первинну (канонічну) і решту на неї перенаправляє за допомогою HTTP-коду 301. Завдяки цьому пошукові системи не індексують ваші сторінки двічі і не розмивають їхній page rank. -Цей процес називається канонізацією. Канонічний URL - це URL, згенерований маршрутизатором, тобто першим відповідним маршрутом у [колекції |#Route-Collection] без прапора OneWay. Тому в колекції ми перераховуємо **первинні маршрути першими**. +Цей процес називається канонізацією. Канонічним URL є той, який генерує маршрутизатор, тобто перший відповідний маршрут у колекції без прапорця OneWay. Тому в колекції вказуємо **первинні маршрути першими**. -Канонізація здійснюється презентером, докладніше в розділі [Канонізація |presenters#canonization]. +Канонізацію виконує презентер, більше в розділі [канонізація |presenters#Канонізація]. -HTTPS .[#toc-https] -=================== +HTTPS +===== -Для того щоб використовувати протокол HTTPS, необхідно активувати його на хостингу та налаштувати сервер. +Щоб мати можливість використовувати протокол HTTPS, необхідно його увімкнути на хостингу та правильно налаштувати сервер. -Перенаправлення всього сайту на HTTPS має бути виконано на рівні сервера, наприклад, за допомогою файлу .htaccess у кореневому каталозі нашого застосунку, з HTTP-кодом 301. Налаштування можуть відрізнятися залежно від хостингу і виглядають приблизно так: +Перенаправлення всього сайту на HTTPS необхідно налаштувати на рівні сервера, наприклад, за допомогою файлу .htaccess у кореневому каталозі нашого застосунку, і це з HTTP-кодом 301. Налаштування може відрізнятися залежно від хостингу і виглядає приблизно так: ``` @@ -542,40 +564,40 @@ HTTPS .[#toc-https] ``` -Маршрутизатор генерує URL з тим самим протоколом, за яким було завантажено сторінку, тому немає необхідності задавати що-небудь ще. +Маршрутизатор генерує URL з тим самим протоколом, з яким була завантажена сторінка, тому нічого більше налаштовувати не потрібно. -Однак, якщо нам виключно потрібно, щоб різні маршрути працювали під різними протоколами, ми помістимо це в маску маршруту: +Але якщо винятково потрібно, щоб різні маршрути працювали під різними протоколами, вкажемо його в масці маршруту: ```php -// Згенерує HTTP-адресу +// Буде генерувати адресу з HTTP $router->addRoute('http://%host%//', /* ... */); -// Згенерує HTTPS адресу +// Буде генерувати адресу з HTTPS $router->addRoute('https://%host%//', /* ... */); ``` -Налагоджувач маршрутизації .[#toc-debugging-router] -=================================================== +Налагодження маршрутизатора +=========================== -Смуга маршрутизації, показана в [Tracy Bar |tracy:], є корисним інструментом, який відображає список маршрутів, а також параметри, які маршрутизатор отримав з URL. +Панель маршрутизації, що відображається в [Tracy Bar |tracy:], є корисним помічником, який показує список маршрутів, а також параметрів, які маршрутизатор отримав з URL. -Зелена смуга з символом ✓ представляє маршрут, який відповідає поточному URL, сині смуги з символами ≈ вказують на маршрути, які також відповідали б URL, якби зелений колір не обігнав їх. Далі ми бачимо поточного ведучого та дії. +Зелена смуга із символом ✓ представляє маршрут, який обробив поточний URL, синім кольором та символом ≈ позначені маршрути, які також обробили б URL, якби їх не випередив зелений. Далі бачимо поточний презентер та дію. [* routing-debugger.webp *] -Водночас, якщо відбувається несподіване перенаправлення через [канонікалізацію |#seo-and-canonization], корисно зазирнути в панель *redirect*, щоб дізнатися, як маршрутизатор спочатку зрозумів URL і чому він перенаправив. +Водночас, якщо відбувається неочікуване перенаправлення через [канонізацію |#SEO та канонізація], корисно подивитися на панель у рядку *redirect*, де ви дізнаєтеся, як маршрутизатор спочатку зрозумів URL і чому перенаправив. .[note] -Під час налагодження маршрутизатора рекомендується відкрити Developer Tools у браузері (Ctrl+Shift+I або Cmd+Option+I) і вимкнути кеш у панелі Network, щоб перенаправлення не зберігалися в ньому. +При налагодженні маршрутизатора рекомендуємо відкрити в браузері Developer Tools (Ctrl+Shift+I або Cmd+Option+I) і в панелі Network вимкнути кеш, щоб у ньому не зберігалися перенаправлення. -Продуктивність .[#toc-performance] -================================== +Продуктивність +============== -Кількість маршрутів впливає на швидкість маршрутизатора. Їхня кількість, звісно, не повинна перевищувати кількох десятків. Якщо ваш сайт має занадто складну структуру URL, ви можете написати [Користувацький маршрутизатор |#custom-router]. +Кількість маршрутів впливає на швидкість маршрутизатора. Їхня кількість точно не повинна перевищувати кілька десятків. Якщо ваш сайт має занадто складну структуру URL, ви можете написати на замовлення [#власний маршрутизатор]. -Якщо маршрутизатор не має залежностей, наприклад, від бази даних, і його фабрика не має аргументів, ми можемо серіалізувати його скомпільовану форму безпосередньо в DI-контейнер і таким чином зробити додаток трохи швидшим. +Якщо маршрутизатор не має жодних залежностей, наприклад, від бази даних, і його фабрика не приймає жодних аргументів, ми можемо його зібрану форму серіалізувати безпосередньо в DI-контейнер і тим самим трохи прискорити застосунок. ```neon routing: @@ -583,10 +605,10 @@ routing: ``` -Користувацький маршрутизатор .[#toc-custom-router] -================================================== +Власний маршрутизатор +===================== -Наступні рядки призначені для дуже досвідчених користувачів. Ви можете створити свій власний маршрутизатор і, природно, додати його до колекції маршрутів. Маршрутизатор являє собою реалізацію інтерфейсу [Router |api:Nette\Routing\Router] з двома методами: +Наступні рядки призначені для дуже досвідчених користувачів. Ви можете створити власний маршрутизатор і цілком природно включити його до колекції маршрутів. Маршрутизатор є реалізацією інтерфейсу [api:Nette\Routing\Router] з двома методами: ```php use Nette\Http\IRequest as HttpRequest; @@ -606,8 +628,7 @@ class MyRouter implements Nette\Routing\Router } ``` -Метод `match` обробляє поточний [$httpRequest |http:request], з якого може бути витягнуто не тільки URL, а й заголовки тощо, у масив, що містить ім'я ведучого і його параметри. Якщо він не може обробити запит, то повертає null. -Під час обробки запиту ми повинні повернути щонайменше ведучого і дію. Ім'я ведучого є повним і включає будь-які модулі: +Метод `match` обробляє поточний запит [$httpRequest |http:request], з якого можна отримати не тільки URL, але й заголовки тощо, до масиву, що містить назву презентера та його параметри. Якщо запит обробити не може, повертає null. При обробці запиту ми повинні повернути щонайменше презентер та дію. Назва презентера є повною і містить також можливі модулі: ```php [ @@ -616,31 +637,31 @@ class MyRouter implements Nette\Routing\Router ] ``` -Метод `constructUrl`, з іншого боку, генерує абсолютний URL з масиву параметрів. Він може використовувати інформацію з параметра `$refUrl`, який є поточним URL. +Метод `constructUrl` навпаки складає з масиву параметрів кінцевий абсолютний URL. Для цього він може використовувати інформацію з параметра [`$refUrl`|api:Nette\Http\UrlScript], що є поточним URL. -Щоб додати користувацький маршрутизатор до колекції маршрутів, використовуйте `add()`: +До колекції маршрутів його додасте за допомогою `add()`: ```php $router = new Nette\Application\Routers\RouteList; -$router->add(new MyRouter); +$router->add($myRouter); $router->addRoute(/* ... */); // ... ``` -Роздільне використання .[#toc-separated-usage] -============================================== +Самостійне використання +======================= -Під роздільним використанням мається на увазі використання можливостей маршрутизатора в додатку, який не використовує додаток Nette і презентери. До нього можна застосувати майже все, що ми показали в цій главі, з такими відмінностями: +Самостійним використанням ми маємо на увазі використання можливостей маршрутизатора в застосунку, який не використовує Nette Application та презентери. Для нього діє майже все, що ми показали в цьому розділі, з такими відмінностями: -- для колекцій маршрутів ми використовуємо клас [api:Nette\Routing\RouteList]. -- як клас простого маршрутизатора [api:Nette\Routing\SimpleRouter]. -- оскільки немає пари `Presenter:action`, ми використовуємо [Розширену нотацію |#advanced-notation]. +- для колекцій маршрутів використовуємо клас [api:Nette\Routing\RouteList] +- як простий маршрутизатор клас [api:Nette\Routing\SimpleRouter] +- оскільки не існує пари `Presenter:action`, використовуємо [#розширений запис] -Отже, ми знову додамо метод, який буде створювати, наприклад, маршрутизатор: +Отже, знову створимо метод, який нам складе маршрутизатор, наприклад: ```php -namespace App\Router; +namespace App\Core; use Nette\Routing\RouteList; @@ -661,26 +682,26 @@ class RouterFactory } ``` -Якщо ви використовуєте DI-контейнер, як ми рекомендуємо, додайте метод у конфігурацію ще раз, а потім отримайте маршрутизатор разом із HTTP-запитом із контейнера: +Якщо ви використовуєте DI-контейнер, що ми рекомендуємо, знову додамо метод до конфігурації, а потім маршрутизатор разом з HTTP-запитом отримаємо з контейнера: ```php $router = $container->getByType(Nette\Routing\Router::class); $httpRequest = $container->getByType(Nette\Http\IRequest::class); ``` -Або ми будемо створювати об'єкти безпосередньо: +Або об'єкти безпосередньо створимо: ```php -$router = App\Router\RouterFactory::createRouter(); +$router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` -Тепер потрібно дати маршрутизатору попрацювати: +Тепер залишається лише запустити маршрутизатор до роботи: ```php $params = $router->match($httpRequest); if ($params === null) { - // не знайдено співпадаючого маршруту, надішлемо помилку 404 + // не знайдено відповідного маршруту, надсилаємо помилку 404 exit; } @@ -689,7 +710,7 @@ $controller = $params['controller']; // ... ``` -І навпаки, ми будемо використовувати маршрутизатор для створення посилання: +І навпаки, використаємо маршрутизатор для складання посилання: ```php $params = ['controller' => 'ArticleController', 'id' => 123]; diff --git a/application/uk/templates.texy b/application/uk/templates.texy index 6190a04b3f..ea1f4480c3 100644 --- a/application/uk/templates.texy +++ b/application/uk/templates.texy @@ -2,15 +2,15 @@ ******* .[perex] -Nette використовує систему шаблонів [Latte |latte:]. Latte використовується тому, що це найбезпечніша система шаблонів для PHP, і водночас найінтуїтивніша та найзрозуміліша. Вам не потрібно вивчати багато нового, достатньо знати PHP і кілька тегів Latte. +Nette використовує систему шаблонів [Latte |latte:]. По-перше, тому що це найбезпечніша система шаблонів для PHP, а по-друге, вона також є найінтуїтивнішою. Вам не потрібно вивчати багато нового, достатньо знань PHP та кількох тегів. -Зазвичай сторінка заповнюється з шаблону макета + шаблону дії. Ось як може виглядати шаблон макета, зверніть увагу на блоки `{block}` і тег `{include}`: +Зазвичай сторінка складається з шаблону layout + шаблону для конкретної дії. Ось як може виглядати шаблон layout, зверніть увагу на блоки `{block}` та тег `{include}`: ```latte - {block title}Мое приложение{/block} + {block title}My App{/block}
    ...
    @@ -20,61 +20,109 @@ Nette використовує систему шаблонів [Latte |latte:]. ``` -А це може бути шаблоном дій: +А це буде шаблон дії: ```latte -{block title}Главная страница{/block} +{block title}Homepage{/block} {block content} -

    Главная страница

    +

    Homepage

    ... {/block} ``` -Він визначає блок `content`, який вставляється замість `{include content}` у макеті, а також перевизначає блок `title`, який перезаписує `{block title}` у макеті. Спробуйте уявити собі результат. +Він визначає блок `content`, який буде вставлено замість `{include content}` у layout, а також перевизначає блок `title`, який перезапише `{block title}` у layout. Спробуйте уявити результат. -Пошук шаблонів .[#toc-search-for-templates] -------------------------------------------- +Пошук шаблонів +-------------- -Шлях до шаблонів визначається ведучим за допомогою простої логіки. Він спробує перевірити, чи є один із цих файлів, розташований відносно каталогу класу ведучого, де `` це ім'я поточного ведучого, а `` це ім'я поточної події: +Вам не потрібно вказувати в presenter'ах, який шаблон потрібно відобразити, фреймворк сам визначить шлях і заощадить вам час на написання коду. -- `templates//.latte` -- `templates/..latte` +Якщо ви використовуєте структуру каталогів, де кожен presenter має власний каталог, просто розмістіть шаблон у цьому каталозі під назвою дії (або view), тобто для дії `default` використовуйте шаблон `default.latte`: -Якщо шаблон не знайдено, відповіддю буде [помилка 404 |presenters#error-404-etc]. +/--pre +app/ +└── Presentation/ + └── Home/ + ├── HomePresenter.php + └── default.latte +\-- -Ви також можете змінити вигляд за допомогою `$this->setView('jineView')`. Або, замість прямого пошуку, вкажіть ім'я файлу шаблону за допомогою `$this->template->setFile('/path/to/template.latte')`. +Якщо ви використовуєте структуру, де presenter'и знаходяться разом в одному каталозі, а шаблони — у папці `templates`, збережіть його або у файлі `..latte`, або `/.latte`: + +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── Home.default.latte ← 1-й варіант + └── Home/ + └── default.latte ← 2-й варіант +\-- + +Каталог `templates` також може знаходитись на рівень вище, тобто на тому ж рівні, що й каталог із класами presenter'ів. + +Якщо шаблон не знайдено, presenter відповість [помилкою 404 - сторінку не знайдено |presenters#Помилка 404 тощо]. + +View можна змінити за допомогою `$this->setView('іншийView')`. Також можна безпосередньо вказати файл шаблону за допомогою `$this->template->setFile('/path/to/template.latte')`. .[note] -Файли, в яких здійснюється пошук шаблонів, можна змінити, наклавши метод [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], який повертає масив можливих імен файлів. +Файли, де шукаються шаблони, можна змінити, перевизначивши метод [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], який повертає масив можливих імен файлів. + + +Пошук шаблону layout +-------------------- + +Nette також автоматично шукає файл layout. + +Якщо ви використовуєте структуру каталогів, де кожен presenter має власний каталог, розмістіть layout або в папці з presenter'ом, якщо він специфічний лише для нього, або на рівень вище, якщо він спільний для кількох presenter'ів: + +/--pre +app/ +└── Presentation/ + ├── @layout.latte ← спільний layout + └── Home/ + ├── @layout.latte ← тільки для presenter'а Home + ├── HomePresenter.php + └── default.latte +\-- + +Якщо ви використовуєте структуру, де presenter'и знаходяться разом в одному каталозі, а шаблони — у папці `templates`, layout очікуватиметься в таких місцях: -У цих файлах очікується компонування: +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── @layout.latte ← спільний layout + ├── Home.@layout.latte ← тільки для Home, 1-й варіант + └── Home/ + └── @layout.latte ← тільки для Home, 2-й варіант +\-- -- `templates//@.latte` -- `templates/.@.latte` -- `templates/@.latte` макет, спільний для кількох доповідачів +Якщо presenter знаходиться в модулі, пошук буде здійснюватися також на вищих рівнях каталогів, відповідно до вкладеності модуля. -Де `` це ім'я поточного ведучого і `` це ім'я макета, яке за замовчуванням дорівнює `'layout'`. Ім'я може бути змінено за допомогою `$this->setLayout('jinyLayout')`, тому будуть випробувані файли `@jinyLayout.latte`. +Назву layout можна змінити за допомогою `$this->setLayout('layoutAdmin')`, і тоді він очікуватиметься у файлі `@layoutAdmin.latte`. Також можна безпосередньо вказати файл шаблону layout за допомогою `$this->setLayout('/path/to/template.latte')`. -Ви також можете безпосередньо вказати ім'я файлу шаблону макета за допомогою `$this->setLayout('/path/to/template.latte')`. Використання `$this->setLayout(false)` відключає відстеження макета. +За допомогою `$this->setLayout(false)` або тегу `{layout none}` всередині шаблону пошук layout вимикається. .[note] -Файли, в яких здійснюється пошук шаблонів макета, можна змінити, наклавши метод [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], який повертає масив можливих імен файлів. +Файли, де шукаються шаблони layout, можна змінити, перевизначивши метод [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], який повертає масив можливих імен файлів. -Змінні в шаблоні .[#toc-variables-in-the-template] --------------------------------------------------- +Змінні в шаблоні +---------------- -Змінні передаються в шаблон шляхом запису їх у `$this->template`, а потім вони доступні в шаблоні як локальні змінні: +Змінні передаються в шаблон шляхом запису їх у `$this->template`, після чого вони стають доступними в шаблоні як локальні змінні: ```php $this->template->article = $this->articles->getById($id); ``` -Таким чином, ми можемо легко передавати будь-які змінні в шаблони. Однак при розробці надійних додатків часто корисніше обмежити себе. Наприклад, шляхом явного визначення списку змінних, які очікує шаблон, і їхніх типів. Це дозволить PHP перевіряти типи, IDE - правильно шепотіти, а статичний аналіз - виявляти помилки. +Таким чином, ми можемо легко передавати будь-які змінні в шаблони. Однак при розробці надійних додатків корисніше обмежити себе. Наприклад, явно визначивши перелік змінних, які очікує шаблон, та їхні типи. Завдяки цьому PHP зможе перевіряти типи, IDE правильно підказуватиме, а статичний аналіз виявлятиме помилки. -І як визначити таке перерахування? Просто у вигляді класу і його властивостей. Ми назвемо його як presenter, але з `Template` в кінці: +А як визначити такий перелік? Просто у вигляді класу та його властивостей. Назвемо його подібно до presenter'а, але з `Template` на кінці: ```php /** @@ -93,20 +141,20 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template } ``` -Об'єкт `$this->template` в presenter тепер буде екземпляром класу `ArticleTemplate`. Таким чином, PHP перевірятиме оголошені типи під час запису. А починаючи з PHP 8.2, він також буде попереджати при записі в неіснуючу змінну; в попередніх версіях цього можна домогтися за допомогою властивості [Nette\SmartObject |utils:smartobject]. +Об'єкт `$this->template` у presenter'і тепер буде екземпляром класу `ArticleTemplate`. Таким чином, PHP перевірятиме оголошені типи під час запису. А починаючи з версії PHP 8.2, він також попереджатиме про запис у неіснуючу змінну; у попередніх версіях цього можна досягти за допомогою трейту [Nette\SmartObject |utils:smartobject]. -Анотація `@property-read` призначена для IDE і статичного аналізу, вона змусить працювати шепіт, див. "PhpStorm і завершення коду для $this->template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. +Анотація `@property-read` призначена для IDE та статичного аналізу, завдяки їй працюватиме автодоповнення, див. "PhpStorm and code completion for $this⁠-⁠>⁠template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. [* phpstorm-completion.webp *] -Ви також можете дозволити собі розкіш шепотіти в шаблонах, просто встановіть плагін Latte в PhpStorm і помістіть ім'я класу на початок шаблону, докладнішу інформацію дивіться в статті "Latte: як набирати систему":https://blog.nette.org/uk/latte-yak-koristuvatisya-sistemoyu-tipiv: +Розкішшю автодоповнення можна насолоджуватися і в шаблонах, достатньо встановити плагін для Latte в PhpStorm та вказати на початку шаблону назву класу, більше в статті "Latte: як працювати з системою типів":https://blog.nette.org/uk/latte-how-to-use-type-system: ```latte -{templateType App\Presenters\ArticleTemplate} +{templateType App\Presentation\Article\ArticleTemplate} ... ``` -Таким же чином шаблони працюють у компонентах, просто дотримуйтесь угоди про іменування і створіть клас шаблону `FifteenTemplate` для компонента, наприклад, `FifteenControl`. +Так само працюють і шаблони в компонентах, достатньо дотримуватися конвенції іменування і для компонента, наприклад, `FifteenControl` створити клас шаблону `FifteenTemplate`. Якщо вам потрібно створити `$template` як екземпляр іншого класу, використовуйте метод `createTemplate()`: @@ -121,60 +169,60 @@ public function renderDefault(): void ``` -Змінні за замовчуванням .[#toc-default-variables] -------------------------------------------------- +Змінні за замовчуванням +----------------------- -Презентатори та компоненти автоматично передають шаблонам кілька корисних змінних: +Presenter'и та компоненти автоматично передають у шаблони кілька корисних змінних: -- `$basePath` - абсолютний URL-шлях до кореневого каталогу (наприклад, `/eshop`). -- `$baseUrl` - це абсолютна URL-адреса кореневого каталогу (наприклад. `http://localhost/eshop`) -- `$user` - це об'єкт, [що представляє користувача |security:authentication]. -- `$presenter` - нинішній ведучий -- `$control` - поточний компонент або ведучий -- `$flashes` - це масив [повідомлень |presenters#flash-messages], надісланих функціями `flashMessage()` +- `$basePath` — це абсолютний URL-шлях до кореневого каталогу (наприклад, `/eshop`) +- `$baseUrl` — це абсолютний URL до кореневого каталогу (наприклад, `http://localhost/eshop`) +- `$user` — це об'єкт, [що представляє користувача |security:authentication] +- `$presenter` — це поточний presenter +- `$control` — це поточний компонент або presenter +- `$flashes` — масив [повідомлень |presenters#Flash-повідомлення], надісланих функцією `flashMessage()` -Якщо ви використовуєте користувацький клас шаблону, ці змінні будуть передані, якщо ви створите для них властивість. +Якщо ви використовуєте власний клас шаблону, ці змінні будуть передані, якщо ви створите для них властивості. -Створення посилань .[#toc-creating-links] ------------------------------------------ +Створення посилань +------------------ -Шаблон створює таким чином посилання на інших ведучих і заходи: +У шаблоні посилання на інші presenter'и та дії створюються таким чином: ```latte -detail produktu +деталі продукту ``` -Атрибут `n:href` дуже зручний для HTML-тегів. ``. Якщо ми хочемо вказати посилання в іншому місці, наприклад, у тексті, ми використовуємо `{link}`: +Атрибут `n:href` дуже зручний для HTML-тегів ``. Якщо ми хочемо вивести посилання в іншому місці, наприклад, у тексті, використовуємо `{link}`: ```latte -Adresa je: {link Home:default} +Адреса: {link Home:default} ``` -Додаткові відомості див. у розділі [Створення посилань URL |creating-links]. +Більше інформації ви знайдете в розділі [Створення URL-посилань|creating-links]. -Користувацькі фільтри, теги тощо. .[#toc-custom-filters-tags-etc] ------------------------------------------------------------------ +Власні фільтри, теги тощо. +-------------------------- -Система шаблонів Latte може бути розширена за допомогою користувацьких фільтрів, функцій, тегів тощо. Це можна зробити безпосередньо в методі `render` або `beforeRender()`: +Систему шаблонів Latte можна розширити власними фільтрами, функціями, тегами тощо. Це можна зробити безпосередньо в методі `render` або `beforeRender()`: ```php public function beforeRender(): void { - // додати фільтр + // додавання фільтра $this->template->addFilter('foo', /* ... */); - // або налаштувати об'єкт Latte\Engine безпосередньо + // або конфігуруємо безпосередньо об'єкт Latte\Engine $latte = $this->template->getLatte(); $latte->addFilterLoader(/* ... */); } ``` -Latte версії 3 пропонує більш просунутий спосіб створення [розширення |latte:creating-extension] для кожного веб-проекту. Ось короткий приклад такого класу: +Latte версії 3 пропонує більш просунутий спосіб, а саме створення [extension |latte:extending-latte#Latte Extension] для кожного веб-проекту. Приклад такого класу: ```php -namespace App\Templating; +namespace App\Presentation\Accessory; final class LatteExtension extends Latte\Extension { @@ -207,22 +255,21 @@ final class LatteExtension extends Latte\Extension } ``` -Ми реєструємо його за допомогою [конфігурації |configuration#Latte]: +Зареєструємо його за допомогою [конфігурації |configuration#Шаблони Latte]: ```neon latte: extensions: - - App\Templating\LatteExtension + - App\Presentation\Accessory\LatteExtension ``` -Перекладати .[#toc-translating] -------------------------------- +Переклад +-------- -Якщо ви програмуєте багатомовний додаток, вам, ймовірно, знадобиться виводити частину тексту в шаблоні різними мовами. Для цього в Nette Framework визначено інтерфейс перекладу [api:Nette\Localization\Translator], який має єдиний метод `translate()`. Він приймає повідомлення `$message`, яке зазвичай є рядком, і будь-які інші параметри. Завдання полягає у поверненні перекладеного рядка. -У Nette немає реалізації за замовчуванням, ви можете вибрати відповідно до своїх потреб з декількох готових рішень, які можна знайти на [Componette |https://componette.org/search/localization]. Їх документація підкаже вам, як налаштувати перекладач. +Якщо ви програмуєте багатомовний додаток, вам, швидше за все, знадобиться виводити деякі тексти в шаблоні різними мовами. Для цього Nette Framework визначає інтерфейс для перекладу [api:Nette\Localization\Translator], який має єдиний метод `translate()`. Він приймає повідомлення `$message`, яке зазвичай є рядком, та будь-які інші параметри. Завдання полягає в тому, щоб повернути перекладений рядок. У Nette немає реалізації за замовчуванням, ви можете вибрати відповідно до своїх потреб з кількох готових рішень, які можна знайти на [Componette |https://componette.org/search/localization]. У їхній документації ви дізнаєтеся, як налаштувати перекладач. -Шаблони можна налаштувати за допомогою перекладача, який [нам переда |dependency-injection:passing-dependencies]дуть, за допомогою методу `setTranslator()`: +Шаблонам можна встановити перекладач, який ми [передамо |dependency-injection:passing-dependencies], за допомогою методу `setTranslator()`: ```php protected function beforeRender(): void @@ -232,38 +279,38 @@ protected function beforeRender(): void } ``` -Крім того, перекладач можна встановити за допомогою [конфігурації |configuration#Latte]: +Перекладач також можна налаштувати за допомогою [конфігурації |configuration#Шаблони Latte]: ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -Тоді перекладач можна використовувати, наприклад, як фільтр `|translate`, з додатковими параметрами, переданими методу `translate()` (див. `foo, bar`): +Після цього перекладач можна використовувати, наприклад, як фільтр `|translate`, включаючи додаткові параметри, які передаються методу `translate()` (див. `foo, bar`): ```latte -{='Basket'|translate} +{='Кошик'|translate} {$item|translate} {$item|translate, foo, bar} ``` -Або як тег підкреслення: +Або як тег з підкресленням: ```latte -{_'Basket'} +{_'Кошик'} {_$item} {_$item, foo, bar} ``` -Для перекладу шаблонних розділів існує парний тег `{translate}` (починаючи з версії Latte 2.11, раніше використовувався тег `{_}` ): +Для перекладу частини шаблону існує парний тег `{translate}` (з Latte 2.11, раніше використовувався тег `{_}`): ```latte -{translate}Order{/translate} -{translate foo, bar}Order{/translate} +{translate}Замовлення{/translate} +{translate foo, bar}Замовлення{/translate} ``` -Перекладач викликається за замовчуванням під час виконання шаблону під час рендерингу. Однак у версії 3 Latte може перекладати весь статичний текст під час компіляції шаблону. Це економить продуктивність, оскільки кожен рядок перекладається лише один раз, а результат перекладу записується до скомпільованої форми. Це створює кілька скомпільованих версій шаблону в кеш-пам'яті, по одній для кожної мови. Для цього вам потрібно лише вказати мову як другий параметр: +Перекладач зазвичай викликається під час виконання при рендерингу шаблону. Однак Latte версії 3 може перекладати всі статичні тексти вже під час компіляції шаблону. Це економить продуктивність, оскільки кожен рядок перекладається лише один раз, а результат перекладу записується в скомпільовану форму. У каталозі кешу таким чином створюється кілька скомпільованих версій шаблону, по одній для кожної мови. Для цього достатньо лише вказати мову як другий параметр: ```php protected function beforeRender(): void @@ -273,4 +320,4 @@ protected function beforeRender(): void } ``` -Під статичним текстом мається на увазі, наприклад, `{_'hello'}` або `{translate}hello{/translate}`. Нестатичний текст, такий як `{_$foo}`, буде продовжувати компілюватися на льоту. +Статичним текстом мається на увазі, наприклад, `{_'hello'}` або `{translate}hello{/translate}`. Нестатичні тексти, такі як `{_$foo}`, продовжуватимуть перекладатися під час виконання. diff --git a/assets/bg/@home.texy b/assets/bg/@home.texy new file mode 100644 index 0000000000..255db2e9c3 --- /dev/null +++ b/assets/bg/@home.texy @@ -0,0 +1,432 @@ +Nette Assets +************ + +
    + +Омръзна ли ви ръчното управление на статични файлове във вашите уеб приложения? Забравете за хардкодиране на пътища, справяне с инвалидиране на кеша или притеснения относно версиирането на файлове. Nette Assets трансформира начина, по който работите с изображения, стилови таблици, скриптове и други статични ресурси. + +- **Интелигентно версииране** гарантира, че браузърите винаги зареждат най-новите файлове +- **Автоматично откриване** на типове файлове и размери +- **Безпроблемна Latte интеграция** с интуитивни тагове +- **Гъвкава архитектура**, поддържаща файлови системи, CDN и Vite +- **Лениво зареждане** за оптимална производителност + +
    + + +Защо Nette Assets? +================== + +Работата със статични файлове често означава повтарящ се, податлив на грешки код. Ръчно конструирате URL адреси, добавяте параметри за версии за кеш изчистване и обработвате различни типове файлове по различен начин. Това води до код като: + +```html +Logo + +``` + +С Nette Assets цялата тази сложност изчезва: + +```latte +{* Всичко автоматизирано - URL, версииране, размери *} + + + +{* Или просто *} +{asset 'css/style.css'} +``` + +Това е! Библиотеката автоматично: +- Добавя параметри за версии въз основа на времето на последна модификация на файла +- Открива размерите на изображението и ги включва в HTML +- Генерира правилния HTML елемент за всеки тип файл +- Обработва както развойна, така и продукционна среда + + +Инсталация +========== + +Инсталирайте Nette Assets с помощта на [Composer|best-practices:composer]: + +```shell +composer require nette/assets +``` + +Изисква PHP 8.1 или по-нова и работи перфектно с Nette Framework, но може да се използва и самостоятелно. + + +Първи стъпки +============ + +Nette Assets работи веднага без никаква конфигурация. Поставете статичните си файлове в директорията `www/assets/` и започнете да ги използвате: + +```latte +{* Показва изображение с автоматични размери *} +{asset 'logo.png'} + +{* Включва стилова таблица с версииране *} +{asset 'style.css'} + +{* Зарежда JavaScript модул *} +{asset 'app.js'} +``` + +За повече контрол върху генерирания HTML, използвайте атрибута `n:asset` или функцията `asset()`. + + +Как работи +========== + +Nette Assets е изграден около три основни концепции, които го правят мощен, но лесен за използване: + + +Активи - Вашите файлове стават интелигентни +------------------------------------------- + +**Актив** представлява всеки статичен файл във вашето приложение. Всеки файл става обект с полезни свойства само за четене: + +```php +$image = $assets->getAsset('photo.jpg'); +echo $image->url; // '/assets/photo.jpg?v=1699123456' +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' +``` + +Различните типове файлове предоставят различни свойства: +- **Изображения**: ширина, височина, алтернативен текст, лениво зареждане +- **Скриптове**: тип модул, хешове за цялост, crossorigin +- **Стилови таблици**: медийни заявки, цялост +- **Аудио/Видео**: продължителност, размери +- **Шрифтове**: правилно предварително зареждане с CORS + +Библиотеката автоматично открива типовете файлове и създава подходящия клас актив. + + +Мапъри - Откъде идват файловете +------------------------------- + +**Мапърът** знае как да намира файлове и да създава URL адреси за тях. Можете да имате множество мапъри за различни цели - локални файлове, CDN, облачно хранилище или инструменти за изграждане (всеки от тях има име). Вграденият `FilesystemMapper` обработва локални файлове, докато `ViteMapper` се интегрира с модерни инструменти за изграждане. + +Мапърите се дефинират в [Конфигурация |Configuration]. + + +Регистър - Вашият основен интерфейс +----------------------------------- + +**Регистърът** управлява всички мапъри и предоставя основния API: + +```php +// Инжектирайте регистъра във вашата услуга +public function __construct( + private Nette\Assets\Registry $assets +) {} + +// Вземете активи от различни мапъри +$logo = $this->assets->getAsset('images:logo.png'); // мапър 'image' +$app = $this->assets->getAsset('app:main.js'); // мапър 'app' +$style = $this->assets->getAsset('style.css'); // използва мапъра по подразбиране +``` + +Регистърът автоматично избира правилния мапър и кешира резултатите за производителност. + + +Работа с активи в PHP +===================== + +Регистърът предоставя два метода за извличане на активи: + +```php +// Хвърля Nette\Assets\AssetNotFoundException, ако файлът не съществува +$logo = $assets->getAsset('logo.png'); + +// Връща null, ако файлът не съществува +$banner = $assets->tryGetAsset('banner.jpg'); +if ($banner) { + echo $banner->url; +} +``` + + +Указване на мапъри +------------------ + +Можете изрично да изберете кой мапър да използвате: + +```php +// Използвайте мапъра по подразбиране +$file = $assets->getAsset('document.pdf'); + +// Използвайте конкретен мапър с префикс +$image = $assets->getAsset('images:photo.jpg'); + +// Използвайте конкретен мапър със синтаксис на масив +$script = $assets->getAsset(['scripts', 'app.js']); +``` + + +Свойства и типове активи +------------------------ + +Всеки тип актив предоставя съответните свойства само за четене: + +```php +// Свойства на изображение +$image = $assets->getAsset('photo.jpg'); +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' + +// Свойства на скрипт +$script = $assets->getAsset('app.js'); +echo $script->type; // 'module' или null + +// Свойства на аудио +$audio = $assets->getAsset('song.mp3'); +echo $audio->duration; // продължителност в секунди + +// Всички активи могат да бъдат преобразувани в низ (връща URL) +$url = (string) $assets->getAsset('document.pdf'); +``` + +.[note] +Свойства като размери или продължителност се зареждат лениво само при достъп, поддържайки библиотеката бърза. + + +Използване на активи в Latte шаблони +==================================== + +Nette Assets предоставя интуитивна [Latte|latte:] интеграция с тагове и функции. + + +`{asset}` +--------- + +Тагът `{asset}` рендира пълни HTML елементи: + +```latte +{* Рендира: *} +{asset 'hero.jpg'} + +{* Рендира: *} +{asset 'app.js'} + +{* Рендира: *} +{asset 'style.css'} +``` + +Тагът автоматично: +- Открива типа актив и генерира подходящ HTML +- Включва версииране за кеш изчистване +- Добавя размери за изображения +- Задава правилни атрибути (тип, медия и т.н.) + +Когато се използва вътре в HTML атрибути, той извежда само URL адреса: + +```latte +
    + +``` + + +`n:asset` +--------- + +За пълен контрол върху HTML атрибутите: + +```latte +{* Атрибутът n:asset попълва src, размери и т.н. *} +Product + +{* Работи с всеки подходящ елемент *} + + + +``` + +Използвайте променливи и мапъри: + +```latte +{* Променливите работят естествено *} + + +{* Укажете мапър с къдрави скоби *} + + +{* Укажете мапър с нотация на масив *} + +``` + + +`asset()` +--------- + +За максимална гъвкавост, използвайте функцията `asset()`: + +```latte +{var $logo = asset('logo.png')} +width} height={$logo->height}> + +{* Или директно *} +Logo +``` + + +Опционални активи +----------------- + +Обработвайте липсващи активи елегантно с `{asset?}`, `n:asset?` и `tryAsset()`: + +```latte +{* Опционален таг - не рендира нищо, ако активът липсва *} +{asset? 'optional-banner.jpg'} + +{* Опционален атрибут - пропуска, ако активът липсва *} +Avatar + +{* С резервен вариант *} +{var $avatar = tryAsset('user-avatar.jpg') ?? asset('default-avatar.jpg')} +Avatar +``` + + +`{preload}` +----------- + +Подобрете производителността на зареждане на страницата: + +```latte +{* Във вашата секция *} +{preload 'critical.css'} +{preload 'important-font.woff2'} +{preload 'hero-image.jpg'} +``` + +Генерира подходящи preload връзки: + +```html + + + +``` + + +Разширени функции +================= + + +Автоматично откриване на разширения +----------------------------------- + +Автоматично обработвайте множество формати: + +```neon +assets: + mapping: + images: + path: img + extension: [webp, jpg, png] # Опитайте по ред +``` + +Сега можете да изисквате без разширение: + +```latte +{* Намира logo.webp, logo.jpg или logo.png автоматично *} +{asset 'images:logo'} +``` + +Перфектно за прогресивно подобрение с модерни формати. + + +Интелигентно версииране +----------------------- + +Файловете автоматично се версиират въз основа на времето на модификация: + +```latte +{asset 'style.css'} +{* Изход: *} +``` + +Когато актуализирате файла, времевият печат се променя, принуждавайки опресняване на кеша на браузъра. + +Контролирайте версиирането за всеки актив: + +```php +// Деактивирайте версиирането за конкретен актив +$asset = $assets->getAsset('style.css', ['version' => false]); + +// В Latte +{asset 'style.css', version: false} +``` + + +Шрифтови активи +--------------- + +Шрифтовете получават специално отношение с правилен CORS: + +```latte +{* Правилно предварително зареждане с crossorigin *} +{preload 'fonts:OpenSans-Regular.woff2'} + +{* Използвайте в CSS *} + +``` + + +Персонализирани мапъри +====================== + +Създайте персонализирани мапъри за специални нужди като облачно хранилище или динамично генериране: + +```php +use Nette\Assets\Mapper; +use Nette\Assets\Asset; +use Nette\Assets\Helpers; + +class CloudStorageMapper implements Mapper +{ + public function __construct( + private CloudClient $client, + private string $bucket, + ) {} + + public function getAsset(string $reference, array $options = []): Asset + { + if (!$this->client->exists($this->bucket, $reference)) { + throw new Nette\Assets\AssetNotFoundException("Asset '$reference' not found"); + } + + $url = $this->client->getPublicUrl($this->bucket, $reference); + return Helpers::createAssetFromUrl($url); + } +} +``` + +Регистрирайте в конфигурацията: + +```neon +assets: + mapping: + cloud: CloudStorageMapper(@cloudClient, 'my-bucket') +``` + +Използвайте като всеки друг мапър: + +```latte +{asset 'cloud:user-uploads/photo.jpg'} +``` + +Методът `Helpers::createAssetFromUrl()` автоматично създава правилния тип актив въз основа на разширението на файла. + + +Допълнително четене +=================== + +- [Нетни активи: Най-накрая унифициран API за всичко - от изображения до Vite |https://blog.nette.org/en/introducing-nette-assets] diff --git a/assets/bg/@left-menu.texy b/assets/bg/@left-menu.texy new file mode 100644 index 0000000000..5b04a76bdb --- /dev/null +++ b/assets/bg/@left-menu.texy @@ -0,0 +1,5 @@ +Nette Assets +************ +- [Преглед |@home] +- [Vite |vite] +- [Конфигурация |Configuration] diff --git a/assets/bg/@meta.texy b/assets/bg/@meta.texy new file mode 100644 index 0000000000..57804a1127 --- /dev/null +++ b/assets/bg/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документация на Nette}} diff --git a/assets/bg/configuration.texy b/assets/bg/configuration.texy new file mode 100644 index 0000000000..666a7ec7a1 --- /dev/null +++ b/assets/bg/configuration.texy @@ -0,0 +1,188 @@ +Конфигурация на активи +********************** + +.[perex] +Преглед на опциите за конфигурация за Nette Assets. + + +```neon +assets: + # базов път за разрешаване на относителни пътища на мапъри + basePath: ... # (string) по подразбиране е %wwwDir% + + # базов URL за разрешаване на относителни URL адреси на мапъри + baseUrl: ... # (string) по подразбиране е %baseUrl% + + # активиране на версииране на активи глобално? + versioning: ... # (bool) по подразбиране е true + + # дефинира мапъри на активи + mapping: ... # (array) по подразбиране е път 'assets' +``` + +`basePath` задава директорията на файловата система по подразбиране за разрешаване на относителни пътища в мапъри. По подразбиране използва уеб директорията (`%wwwDir%`). + +`baseUrl` задава URL префикса по подразбиране за разрешаване на относителни URL адреси в мапъри. По подразбиране използва основния URL адрес (`%baseUrl%`). + +Опцията `versioning` глобално контролира дали параметрите за версии се добавят към URL адресите на активи за изчистване на кеша. Отделните мапъри могат да презапишат тази настройка. + + +Мапъри +------ + +Мапърите могат да бъдат конфигурирани по три начина: проста нотация на низ, подробна нотация на масив или като препратка към услуга. + +Най-простият начин за дефиниране на мапър: + +```neon +assets: + mapping: + default: assets # Създава мапър на файлова система за %wwwDir%/assets/ + images: img # Създава мапър на файлова система за %wwwDir%/img/ + scripts: js # Създава мапър на файлова система за %wwwDir%/js/ +``` + +Всеки мапър създава `FilesystemMapper`, който: +- Търси файлове в `%wwwDir%/` +- Генерира URL адреси като `%baseUrl%/` +- Наследява глобалната настройка за версииране + + +За повече контрол, използвайте подробната нотация: + +```neon +assets: + mapping: + images: + # директория, където се съхраняват файловете + path: ... # (string) опционално, по подразбиране е '' + + # URL префикс за генерирани връзки + url: ... # (string) опционално, по подразбиране е path + + # активиране на версииране за този мапър? + versioning: ... # (bool) опционално, наследява глобалната настройка + + # автоматично добавяне на разширение(я) при търсене на файлове + extension: ... # (string|array) опционално, по подразбиране е null +``` + +Разбиране как се разрешават стойностите на конфигурацията: + +Разрешаване на пътя: + - Относителните пътища се разрешават от `basePath` (или `%wwwDir%`, ако `basePath` не е зададен) + - Абсолютните пътища се използват такива, каквито са + +Разрешаване на URL: + - Относителните URL адреси се разрешават от `baseUrl` (или `%baseUrl%`, ако `baseUrl` не е зададен) + - Абсолютните URL адреси (със схема или `//`) се използват такива, каквито са + - Ако `url` не е указан, той използва стойността на `path` + + +```neon +assets: + basePath: /var/www/project/www + baseUrl: https://example.com/assets + + mapping: + # Относителен път и URL + images: + path: img # Разрешено до: /var/www/project/www/img + url: images # Разрешено до: https://example.com/assets/images + + # Абсолютен път и URL + uploads: + path: /var/shared/uploads # Използва се както е: /var/shared/uploads + url: https://cdn.example.com # Използва се както е: https://cdn.example.com + + # Указан е само пътят + styles: + path: css # Път: /var/www/project/www/css + # URL: https://example.com/assets/css +``` + + +Персонализирани мапъри +---------------------- + +За персонализирани мапъри, препратете или дефинирайте услуга: + +```neon +services: + s3mapper: App\Assets\S3Mapper(%s3.bucket%) + +assets: + mapping: + cloud: @s3mapper + database: App\Assets\DatabaseMapper(@database.connection) +``` + + +Vite Mapper +----------- + +Vite мапърът изисква само да добавите `type: vite`. Това е пълен списък с опции за конфигурация: + +```neon +assets: + mapping: + default: + # тип мапър (задължителен за Vite) + type: vite # (string) задължителен, трябва да е 'vite' + + # директория за изход на Vite build + path: ... # (string) опционално, по подразбиране е '' + + # URL префикс за изградени активи + url: ... # (string) опционално, по подразбиране е path + + # местоположение на Vite manifest файл + manifest: ... # (string) опционално, по подразбиране е /.vite/manifest.json + + # конфигурация на Vite dev сървър + devServer: ... # (bool|string) опционално, по подразбиране е true + + # версииране за файлове в публична директория + versioning: ... # (bool) опционално, наследява глобалната настройка + + # автоматично разширение за файлове в публична директория + extension: ... # (string|array) опционално, по подразбиране е null +``` + +Опцията `devServer` контролира как се зареждат активи по време на разработка: + +- `true` (по подразбиране) - Автоматично открива Vite dev сървъра на текущия хост и порт. Ако dev сървърът работи **и вашето приложение е в режим на отстраняване на грешки**, активите се зареждат от него с поддръжка на гореща подмяна на модули. Ако dev сървърът не работи, активите се зареждат от изградените файлове в публичната директория. +- `false` - Напълно деактивира интеграцията на dev сървъра. Активите винаги се зареждат от изградените файлове. +- Персонализиран URL (напр. `https://localhost:5173`) - Ръчно указва URL адреса на dev сървъра, включително протокол и порт. Полезно, когато dev сървърът работи на различен хост или порт. + +Опциите `versioning` и `extension` се прилагат само за файлове в публичната директория на Vite, които не се обработват от Vite. + + +Ръчна конфигурация +------------------ + +Когато не използвате Nette DI, конфигурирайте мапърите ръчно: + +```php +use Nette\Assets\Registry; +use Nette\Assets\FilesystemMapper; +use Nette\Assets\ViteMapper; + +$registry = new Registry; + +// Добавяне на мапър на файлова система +$registry->addMapper('images', new FilesystemMapper( + baseUrl: 'https://example.com/img', + basePath: __DIR__ . '/www/img', + extensions: ['webp', 'jpg', 'png'], + versioning: true, +)); + +// Добавяне на Vite мапър +$registry->addMapper('app', new ViteMapper( + baseUrl: '/build', + basePath: __DIR__ . '/www/build', + manifestPath: __DIR__ . '/www/build/.vite/manifest.json', + devServer: 'https://localhost:5173', +)); +``` diff --git a/assets/bg/vite.texy b/assets/bg/vite.texy new file mode 100644 index 0000000000..45c188b3e3 --- /dev/null +++ b/assets/bg/vite.texy @@ -0,0 +1,508 @@ +Vite интеграция +*************** + +
    + +Модерните JavaScript приложения изискват сложни инструменти за изграждане. Nette Assets предоставя първокласна интеграция с [Vite |https://vitejs.dev/], инструментът за изграждане на фронтенд от следващо поколение. Получете светкавично бързо развитие с Hot Module Replacement (HMR) и оптимизирани продукционни компилации без никакви проблеми с конфигурацията. + +- **Нулева конфигурация** - автоматичен мост между Vite и PHP шаблони +- **Пълно управление на зависимостите** - един таг обработва всички активи +- **Hot Module Replacement** - незабавни JavaScript и CSS актуализации +- **Оптимизирани продукционни компилации** - разделяне на кода и tree shaking + +
    + + +Nette Assets се интегрира безпроблемно с Vite, така че получавате всички тези предимства, докато пишете шаблоните си както обикновено. + + +Настройка на Vite +================= + +Нека настроим Vite стъпка по стъпка. Не се притеснявайте, ако сте нов в инструментите за изграждане - ще обясним всичко! + + +Стъпка 1: Инсталирайте Vite +--------------------------- + +Първо, инсталирайте Vite и Nette плъгина във вашия проект: + +```shell +npm install -D vite @nette/vite-plugin +``` + +Това инсталира Vite и специален плъгин, който помага на Vite да работи перфектно с Nette. + + +Стъпка 2: Структура на проекта +------------------------------ + +Стандартният подход е да поставите изходните файлове на активи в папка `assets/` в корена на проекта, а компилираните версии в `www/assets/`: + +/--pre +web-project/ +├── assets/ ← изходни файлове (SCSS, TypeScript, изходни изображения) +│ ├── public/ ← статични файлове (копират се както са) +│ │ └── favicon.ico +│ ├── images/ +│ │ └── logo.png +│ ├── app.js ← основна входна точка +│ └── style.css ← вашите стилове +└── www/ ← публична директория (документен корен) + ├── assets/ ← компилираните файлове ще отидат тук + └── index.php +\-- + +Папката `assets/` съдържа вашите изходни файлове - кода, който пишете. Vite ще обработи тези файлове и ще постави компилираните версии в `www/assets/`. + + +Стъпка 3: Конфигурирайте Vite +----------------------------- + +Създайте файл `vite.config.ts` в корена на проекта. Този файл казва на Vite къде да намери вашите изходни файлове и къде да постави компилираните. + +Плъгинът Nette Vite идва с интелигентни настройки по подразбиране, които опростяват конфигурацията. Той предполага, че вашите изходни фронтенд файлове са в директорията `assets/` (опция `root`) и компилираните файлове отиват в `www/assets/` (опция `outDir`). Трябва само да укажете [Входни точки |#Entry Points]: + +```js +import { defineConfig } from 'vite'; +import nette from '@nette/vite-plugin'; + +export default defineConfig({ + plugins: [ + nette({ + entry: 'app.js', + }), + ], +}); +``` + +Ако искате да укажете друго име на директория за изграждане на вашите активи, ще трябва да промените няколко опции: + +```js +export default defineConfig({ + root: 'assets', // основна директория на изходни активи + + build: { + outDir: '../www/assets', // къде отиват компилираните файлове + }, + + // ... друга конфигурация ... +}); +``` + +.[note] +Пътят `outDir` се счита за относителен спрямо `root`, поради което има `../` в началото. + + +Стъпка 4: Конфигурирайте Nette +------------------------------ + +Кажете на Nette Assets за Vite във вашия `common.neon`: + +```neon +assets: + mapping: + default: + type: vite # казва на Nette да използва ViteMapper + path: assets +``` + + +Стъпка 5: Добавете скриптове +---------------------------- + +Добавете тези скриптове към вашия `package.json`: + +```json +{ + "scripts": { + "dev": "vite", + "build": "vite build" + } +} +``` + +Сега можете: +- `npm run dev` - стартирайте сървър за разработка с горещо презареждане +- `npm run build` - създайте оптимизирани продукционни файлове + + +Входни точки +============ + +**Входна точка** е основният файл, от който започва вашето приложение. От този файл импортирате други файлове (CSS, JavaScript модули, изображения), създавайки дърво на зависимостите. Vite следва тези импорти и пакетира всичко заедно. + +Примерна входна точка `assets/app.js`: + +```js +// Импортиране на стилове +import './style.css' + +// Импортиране на JavaScript модули +import netteForms from 'nette-forms'; +import naja from 'naja'; + +// Инициализиране на вашето приложение +netteForms.initOnLoad(); +naja.initialize(); +``` + +В шаблона можете да вмъкнете входна точка, както следва: + +```latte +{asset 'app.js'} +``` + +Nette Assets автоматично генерира всички необходими HTML тагове - JavaScript, CSS и всякакви други зависимости. + + +Множество входни точки +---------------------- + +По-големите приложения често се нуждаят от отделни входни точки: + +```js +export default defineConfig({ + plugins: [ + nette({ + entry: [ + 'app.js', // публични страници + 'admin.js', // административен панел + ], + }), + ], +}); +``` + +Използвайте ги в различни шаблони: + +```latte +{* В публични страници *} +{asset 'app.js'} + +{* В административен панел *} +{asset 'admin.js'} +``` + + +Важно: Изходни срещу компилирани файлове +---------------------------------------- + +Ключово е да се разбере, че в продукция можете да зареждате само: + +1. **Входни точки**, дефинирани в `entry` +2. **Файлове от директорията `assets/public/`** + +Не можете да зареждате с `{asset}` произволни файлове от `assets/` - само активи, реферирани от JavaScript или CSS файлове. Ако вашият файл не е рефериран никъде, той няма да бъде компилиран. Ако искате да направите Vite наясно с други активи, можете да ги преместите в [Публична папка |#public folder]. + +Моля, имайте предвид, че по подразбиране Vite ще вгради всички активи, по-малки от 4KB, така че няма да можете да реферирате тези файлове директно. (Вижте [документацията на Vite |https://vite.dev/guide/assets.html]). + +```latte +{* ✓ Това работи - това е входна точка *} +{asset 'app.js'} + +{* ✓ Това работи - това е в assets/public/ *} +{asset 'favicon.ico'} + +{* ✗ Това няма да работи - произволен файл в assets/ *} +{asset 'components/button.js'} +``` + + +Режим на разработка +=================== + +Режимът на разработка е напълно опционален, но предоставя значителни предимства, когато е активиран. Основното предимство е **Hot Module Replacement (HMR)** - вижте промените незабавно, без да губите състоянието на приложението, което прави процеса на разработка много по-плавен и бърз. + +Vite е модерен инструмент за изграждане, който прави разработката невероятно бърза. За разлика от традиционните пакетиращи инструменти, Vite обслужва вашия код директно на браузъра по време на разработка, което означава незабавен старт на сървъра, независимо колко голям е вашият проект, и светкавично бързи актуализации. + + +Стартиране на сървър за разработка +---------------------------------- + +Стартирайте сървъра за разработка: + +```shell +npm run dev +``` + +Ще видите: + +``` + ➜ Local: http://localhost:5173/ + ➜ Network: use --host to expose +``` + +Дръжте този терминал отворен, докато разработвате. + +Плъгинът Nette Vite автоматично открива кога: +1. Vite dev сървърът работи +2. Вашето Nette приложение е в режим на отстраняване на грешки + +Когато и двете условия са изпълнени, Nette Assets зарежда файлове от Vite dev сървъра вместо от компилираната директория: + +```latte +{asset 'app.js'} +{* В разработка: *} +{* В продукция: *} +``` + +Не е необходима конфигурация - просто работи! + + +Работа на различни домейни +-------------------------- + +Ако вашият сървър за разработка работи на нещо различно от `localhost` (като `myapp.local`), може да срещнете проблеми с CORS (Cross-Origin Resource Sharing). CORS е функция за сигурност в уеб браузърите, която по подразбиране блокира заявки между различни домейни. Когато вашето PHP приложение работи на `myapp.local`, но Vite работи на `localhost:5173`, браузърът ги вижда като различни домейни и блокира заявките. + +Имате две опции за решаване на това: + +**Опция 1: Конфигурирайте CORS** + +Най-простото решение е да разрешите заявки от различни източници от вашето PHP приложение: + +```js +export default defineConfig({ + // ... друга конфигурация ... + + server: { + cors: { + origin: 'http://myapp.local', // URL на вашето PHP приложение + }, + }, +}); +``` +**Опция 2: Пуснете Vite на вашия домейн** + +Другото решение е да накарате Vite да работи на същия домейн като вашето PHP приложение. + +```js +export default defineConfig({ + // ... друга конфигурация ... + + server: { + host: 'myapp.local', // същото като вашето PHP приложение + }, +}); +``` + +Всъщност, дори в този случай, трябва да конфигурирате CORS, защото dev сървърът работи на същия хост, но на различен порт. Въпреки това, в този случай CORS се конфигурира автоматично от плъгина Nette Vite. + + +HTTPS разработка +---------------- + +Ако разработвате на HTTPS, имате нужда от сертификати за вашия Vite сървър за разработка. Най-лесният начин е да използвате плъгин, който генерира сертификати автоматично: + +```shell +npm install -D vite-plugin-mkcert +``` + +Ето как да го конфигурирате във `vite.config.ts`: + +```js +import mkcert from 'vite-plugin-mkcert'; + +export default defineConfig({ + // ... друга конфигурация ... + + plugins: [ + mkcert(), // генерира сертификати автоматично и активира https + nette(), + ], +}); +``` + +Имайте предвид, че ако използвате CORS конфигурацията (Опция 1 отгоре), трябва да актуализирате URL адреса на източника, за да използва `https://` вместо `http://`. + + +Продукционни компилации +======================= + +Създайте оптимизирани продукционни файлове: + +```shell +npm run build +``` + +Vite ще: +- Минифицира целия JavaScript и CSS +- Раздели кода на оптимални части +- Генерира хеширани имена на файлове за кеш-изчистване +- Създаде манифест файл за Nette Assets + +Примерен изход: + +``` +www/assets/ +├── app-4f3a2b1c.js # Вашият основен JavaScript (минифициран) +├── app-7d8e9f2a.css # Извлечен CSS (минифициран) +├── vendor-8c4b5e6d.js # Споделени зависимости +└── .vite/ + └── manifest.json # Мапиране за Nette Assets +``` + +Хешираните имена на файлове гарантират, че браузърите винаги зареждат най-новата версия. + + +Публична папка +============== + +Файловете в директорията `assets/public/` се копират в изхода без обработка: + +``` +assets/ +├── public/ +│ ├── favicon.ico +│ ├── robots.txt +│ └── images/ +│ └── og-image.jpg +├── app.js +└── style.css +``` + +Реферирайте ги нормално: + +```latte +{* Тези файлове се копират както са *} + + +``` + +За публични файлове можете да използвате функциите на FilesystemMapper: + +```neon +assets: + mapping: + default: + type: vite + path: assets + extension: [webp, jpg, png] # Първо опитайте WebP + versioning: true # Добавете cache-busting +``` + +В конфигурацията `vite.config.ts` можете да промените публичната папка, като използвате опцията `publicDir`. + + +Динамични импорти +================= + +Vite автоматично разделя кода за оптимално зареждане. Динамичните импорти ви позволяват да зареждате код само когато е наистина необходим, намалявайки първоначалния размер на пакета: + +```js +// Зареждане на тежки компоненти при поискване +button.addEventListener('click', async () => { + let { Chart } = await import('./components/chart.js') + new Chart(data) +}) +``` + +Динамичните импорти създават отделни части, които се зареждат само когато е необходимо. Това се нарича "разделяне на кода" и е една от най-мощните функции на Vite. Когато използвате динамични импорти, Vite автоматично създава отделни JavaScript файлове за всеки динамично импортиран модул. + +Тагът `{asset 'app.js'}` **не** зарежда автоматично тези динамични части. Това е умишлено поведение - не искаме да изтегляме код, който може никога да не бъде използван. Частите се изтеглят само когато динамичният импорт бъде изпълнен. + +Въпреки това, ако знаете, че определени динамични импорти са критични и ще са необходими скоро, можете да ги предварително заредите: + +```latte +{* Основна входна точка *} +{asset 'app.js'} + +{* Предварително зареждане на критични динамични импорти *} +{preload 'components/chart.js'} +``` + +Това казва на браузъра да изтегли компонента на диаграмата във фонов режим, така че да е готов веднага, когато е необходим. + + +Поддръжка на TypeScript +======================= + +TypeScript работи веднага: + +```ts +// assets/main.ts +interface User { + name: string + email: string +} + +export function greetUser(user: User): void { + console.log(`Hello, ${user.name}!`) +} +``` + +Реферирайте TypeScript файлове нормално: + +```latte +{asset 'main.ts'} +``` + +За пълна поддръжка на TypeScript, инсталирайте го: + +```shell +npm install -D typescript +``` + + +Допълнителна конфигурация на Vite +================================= + +Ето някои полезни опции за конфигурация на Vite с подробни обяснения: + +```js +export default defineConfig({ + // Основна директория, съдържаща изходни активи + root: 'assets', + + // Папка, чието съдържание се копира в изходната директория както е + // По подразбиране: 'public' (относително спрямо 'root') + publicDir: 'public', + + build: { + // Къде да се поставят компилираните файлове (относително спрямо 'root') + outDir: '../www/assets', + + // Изчистване на изходната директория преди изграждане? + // Полезно за премахване на стари файлове от предишни компилации + emptyOutDir: true, + + // Поддиректория в outDir за генерирани части и активи + // Това помага да се организира изходната структура + assetsDir: 'static', + + rollupOptions: { + // Входна(и) точка(и) - може да бъде един файл или масив от файлове + // Всяка входна точка става отделен пакет + input: [ + 'app.js', // основно приложение + 'admin.js', // административен панел + ], + }, + }, + + server: { + // Хост, към който да се свърже сървърът за разработка + // Използвайте '0.0.0.0', за да изложите на мрежата + host: 'localhost', + + // Порт за сървъра за разработка + port: 5173, + + // CORS конфигурация за заявки от различни източници + cors: { + origin: 'http://myapp.local', + }, + }, + + css: { + // Активиране на CSS source maps в разработка + devSourcemap: true, + }, + + plugins: [ + nette(), + ], +}); +``` + +Това е! Вече имате модерна система за изграждане, интегрирана с Nette Assets. diff --git a/assets/cs/@home.texy b/assets/cs/@home.texy new file mode 100644 index 0000000000..266aab1028 --- /dev/null +++ b/assets/cs/@home.texy @@ -0,0 +1,432 @@ +Nette Assets +************ + +
    + +Už vás nebaví ručně spravovat statické soubory ve vašich webových aplikacích? Zapomeňte na pevné kódování cest, řešení zneplatnění cache nebo starosti s verzováním souborů. Nette Assets transformuje způsob, jakým pracujete s obrázky, styly, skripty a dalšími statickými zdroji. + +- **Chytré verzování** zajistí, že prohlížeče vždy načtou nejnovější soubory +- **Automatická detekce** typů a rozměrů souborů +- **Bezproblémová integrace** s Latte pomocí intuitivních tagů +- **Flexibilní architektura** podporující souborové systémy, CDN a Vite +- **Líné načítání** pro optimální výkon + +
    + + +Proč Nette Assets? +================== + +Práce se statickými soubory často znamená opakující se, chybový kód. Ručně konstruujete URL, přidáváte parametry verzí pro zrušení cache a řešíte různé typy souborů odlišně. To vede ke kódu jako: + +```html +Logo + +``` + +S Nette Assets veškerá tato složitost mizí: + +```latte +{* Vše automatizováno - URL, verzování, rozměry *} + + + +{* Nebo jen *} +{asset 'css/style.css'} +``` + +To je vše! Knihovna automaticky: +- Přidává parametry verzí na základě času poslední úpravy souboru +- Detekuje rozměry obrázků a zahrnuje je do HTML +- Generuje správný HTML element pro každý typ souboru +- Zpracovává vývojové i produkční prostředí + + +Instalace +========= + +Nainstalujte Nette Assets pomocí [Composeru|best-practices:composer]: + +```shell +composer require nette/assets +``` + +Vyžaduje PHP 8.1 nebo vyšší a perfektně funguje s Nette Frameworkem, ale lze jej použít i samostatně. + + +První kroky +=========== + +Nette Assets funguje ihned po instalaci bez jakékékoli konfigurace. Umístěte své statické soubory do adresáře `www/assets/` a začněte je používat: + +```latte +{* Zobrazí obrázek s automatickými rozměry *} +{asset 'logo.png'} + +{* Zahrne šablonu stylů s verzováním *} +{asset 'style.css'} + +{* Načte JavaScript modul *} +{asset 'app.js'} +``` + +Pro větší kontrolu nad generovaným HTML použijte atribut `n:asset` nebo funkci `asset()`. + + +Jak to funguje +============== + +Nette Assets je postaveno na třech základních konceptech, které jej činí výkonným, ale zároveň jednoduchým na použití: + + +Assets – vaše soubory chytře +---------------------------- + +**Asset** představuje jakýkoli statický soubor ve vaší aplikaci. Každý soubor se stává objektem s užitečnými vlastnostmi jen pro čtení: + +```php +$image = $assets->getAsset('photo.jpg'); +echo $image->url; // '/assets/photo.jpg?v=1699123456' +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' +``` + +Různé typy souborů poskytují různé vlastnosti: +- **Obrázky**: šířka, výška, alternativní text, líné načítání +- **Skripty**: typ modulu, integrity hashe, crossorigin +- **Styly**: media queries, integrity +- **Audio/Video**: délka, rozměry +- **Fonty**: správné přednačítání s CORS + +Knihovna automaticky detekuje typy souborů a vytváří odpovídající třídu assetu. + + +Mappery – odkud soubory pocházejí +--------------------------------- + +**Mapper** ví, jak najít soubory a vytvořit pro ně URL. Můžete mít více mapperů pro různé účely – lokální soubory, CDN, cloudové úložiště nebo build nástroje (každý z nich má jméno). Vestavěný `FilesystemMapper` zpracovává lokální soubory, zatímco `ViteMapper` se integruje s moderními build nástroji. + +Mappery jsou definovány v [konfiguraci]. + + +Registry – vaše hlavní rozhraní +------------------------------- + +**Registry** spravuje všechny mappery a poskytuje hlavní API: + +```php +// Vstříkněte registry do vaší služby +public function __construct( + private Nette\Assets\Registry $assets +) {} + +// Získejte assety z různých mapperů +$logo = $this->assets->getAsset('images:logo.png'); // 'image' mapper +$app = $this->assets->getAsset('app:main.js'); // 'app' mapper +$style = $this->assets->getAsset('style.css'); // používá výchozí mapper +``` + +Registry automaticky vybírá správný mapper a kešuje výsledky pro výkon. + + +Práce s Assets v PHP +==================== + +Registry poskytuje dvě metody pro získávání assetů: + +```php +// Vyvolá Nette\Assets\AssetNotFoundException, pokud soubor neexistuje +$logo = $assets->getAsset('logo.png'); + +// Vrátí null, pokud soubor neexistuje +$banner = $assets->tryGetAsset('banner.jpg'); +if ($banner) { + echo $banner->url; +} +``` + + +Určení mapperů +-------------- + +Můžete explicitně zvolit, který mapper použít: + +```php +// Použít výchozí mapper +$file = $assets->getAsset('document.pdf'); + +// Použít konkrétní mapper s prefixem +$image = $assets->getAsset('images:photo.jpg'); + +// Použít konkrétní mapper se syntaxí pole +$script = $assets->getAsset(['scripts', 'app.js']); +``` + + +Vlastnosti a typy assetů +------------------------ + +Každý typ assetu poskytuje relevantní vlastnosti jen pro čtení: + +```php +// Vlastnosti obrázku +$image = $assets->getAsset('photo.jpg'); +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' + +// Vlastnosti skriptu +$script = $assets->getAsset('app.js'); +echo $script->type; // 'module' or null + +// Vlastnosti audia +$audio = $assets->getAsset('song.mp3'); +echo $audio->duration; // délka v sekundách + +// Všechny assety lze přetypovat na řetězec (vrací URL) +$url = (string) $assets->getAsset('document.pdf'); +``` + +.[note] +Vlastnosti jako rozměry nebo délka se načítají líně pouze při přístupu, což udržuje knihovnu rychlou. + + +Použití Assets v Latte šablonách +================================ + +Nette Assets poskytuje intuitivní integraci s [Latte|latte:] pomocí tagů a funkcí. + + +`{asset}` +--------- + +Tag `{asset}` vykresluje kompletní HTML elementy: + +```latte +{* Vykreslí: *} +{asset 'hero.jpg'} + +{* Vykreslí: *} +{asset 'app.js'} + +{* Vykreslí: *} +{asset 'style.css'} +``` + +Tag automaticky: +- Detekuje typ assetu a generuje odpovídající HTML +- Zahrnuje verzování pro zrušení cache +- Přidává rozměry pro obrázky +- Nastavuje správné atributy (typ, media atd.) + +Při použití uvnitř HTML atributů vypíše pouze URL: + +```latte +
    + +``` + + +`n:asset` +--------- + +Pro plnou kontrolu nad HTML atributy: + +```latte +{* Atribut n:asset vyplní src, rozměry atd. *} +Product + +{* Funguje s jakýmkoli relevantním elementem *} + + + +``` + +Použijte proměnné a mappery: + +```latte +{* Proměnné fungují přirozeně *} + + +{* Určete mapper pomocí složených závorek *} + + +{* Určete mapper pomocí zápisu pole *} + +``` + + +`asset()` +--------- + +Pro maximální flexibilitu použijte funkci `asset()`: + +```latte +{var $logo = asset('logo.png')} +width} height={$logo->height}> + +{* Nebo přímo *} +Logo +``` + + +Volitelné Assets +---------------- + +Elegantně zpracujte chybějící assety pomocí `{asset?}`, `n:asset?` a `tryAsset()`: + +```latte +{* Volitelný tag – nevykreslí nic, pokud asset chybí *} +{asset? 'optional-banner.jpg'} + +{* Volitelný atribut – přeskočí, pokud asset chybí *} +Avatar + +{* S fallbackem *} +{var $avatar = tryAsset('user-avatar.jpg') ?? asset('default-avatar.jpg')} +Avatar +``` + + +`{preload}` +----------- + +Zlepšete výkon načítání stránky: + +```latte +{* Ve vaší sekci *} +{preload 'critical.css'} +{preload 'important-font.woff2'} +{preload 'hero-image.jpg'} +``` + +Generuje odpovídající preload odkazy: + +```html + + + +``` + + +Pokročilé funkce +================ + + +Automatická detekce přípon +-------------------------- + +Automaticky zpracovává více formátů: + +```neon +assets: + mapping: + images: + path: img + extension: [webp, jpg, png] # Zkusit v pořadí +``` + +Nyní můžete požadovat bez přípony: + +```latte +{* Automaticky najde logo.webp, logo.jpg nebo logo.png *} +{asset 'images:logo'} +``` + +Ideální pro progresivní vylepšení s moderními formáty. + + +Chytré verzování +---------------- + +Soubory jsou automaticky verzovány na základě času poslední úpravy: + +```latte +{asset 'style.css'} +{* Výstup: *} +``` + +Když soubor aktualizujete, časové razítko se změní, což vynutí obnovení cache prohlížeče. + +Kontrola verzování pro každý asset: + +```php +// Zakázat verzování pro konkrétní asset +$asset = $assets->getAsset('style.css', ['version' => false]); + +// V Latte +{asset 'style.css', version: false} +``` + + +Assety fontů +------------ + +Fonty získávají speciální zacházení se správným CORS: + +```latte +{* Správné přednačtení s crossorigin *} +{preload 'fonts:OpenSans-Regular.woff2'} + +{* Použití v CSS *} + +``` + + +Vlastní mappery +=============== + +Vytvořte vlastní mappery pro speciální potřeby, jako je cloudové úložiště nebo dynamické generování: + +```php +use Nette\Assets\Mapper; +use Nette\Assets\Asset; +use Nette\Assets\Helpers; + +class CloudStorageMapper implements Mapper +{ + public function __construct( + private CloudClient $client, + private string $bucket, + ) {} + + public function getAsset(string $reference, array $options = []): Asset + { + if (!$this->client->exists($this->bucket, $reference)) { + throw new Nette\Assets\AssetNotFoundException("Asset '$reference' not found"); + } + + $url = $this->client->getPublicUrl($this->bucket, $reference); + return Helpers::createAssetFromUrl($url); + } +} +``` + +Registrace v konfiguraci: + +```neon +assets: + mapping: + cloud: CloudStorageMapper(@cloudClient, 'my-bucket') +``` + +Použijte jako jakýkoli jiný mapper: + +```latte +{asset 'cloud:user-uploads/photo.jpg'} +``` + +Metoda `Helpers::createAssetFromUrl()` automaticky vytvoří správný typ assetu na základě přípony souboru. + + +Další četba +=========== + +- [Nette Assets: Konečně jednotné API pro vše od obrázků po Vite |https://blog.nette.org/cs/predstaveni-nette-assets] diff --git a/assets/cs/@left-menu.texy b/assets/cs/@left-menu.texy new file mode 100644 index 0000000000..2dfe55222e --- /dev/null +++ b/assets/cs/@left-menu.texy @@ -0,0 +1,5 @@ +Nette Assets +************ +- [Úvod |@home] +- [Vite |vite] +- [Konfigurace |Configuration] diff --git a/assets/cs/@meta.texy b/assets/cs/@meta.texy new file mode 100644 index 0000000000..462d9add80 --- /dev/null +++ b/assets/cs/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokumentace}} diff --git a/assets/cs/configuration.texy b/assets/cs/configuration.texy new file mode 100644 index 0000000000..b1470d68a2 --- /dev/null +++ b/assets/cs/configuration.texy @@ -0,0 +1,188 @@ +Konfigurace Assets +****************** + +.[perex] +Přehled možností konfigurace pro Nette Assets. + + +```neon +assets: + # základní cesta pro řešení relativních cest mapperů + basePath: ... # (string) výchozí hodnota je %wwwDir% + + # základní URL pro řešení relativních URL mapperů + baseUrl: ... # (string) výchozí hodnota je %baseUrl% + + # povolit globální verzování assetů? + versioning: ... # (bool) výchozí hodnota je true + + # definuje mappery assetů + mapping: ... # (array) výchozí hodnota je cesta 'assets' +``` + +`basePath` nastavuje výchozí adresář souborového systému pro řešení relativních cest v mapperech. Ve výchozím nastavení používá webový adresář (`%wwwDir%`). + +`baseUrl` nastavuje výchozí URL prefix pro řešení relativních URL v mapperech. Ve výchozím nastavení používá kořenovou URL (`%baseUrl%`). + +Možnost `versioning` globálně řídí, zda jsou k URL assetů přidávány parametry verzí pro zrušení cache. Jednotlivé mappery mohou toto nastavení přepsat. + + +Mappery +------- + +Mappery lze konfigurovat třemi způsoby: jednoduchou řetězcovou notací, detailní notací pole nebo jako odkaz na službu. + +Nejjednodušší způsob, jak definovat mapper: + +```neon +assets: + mapping: + default: assets # Vytvoří filesystem mapper pro %wwwDir%/assets/ + images: img # Vytvoří filesystem mapper pro %wwwDir%/img/ + scripts: js # Vytvoří filesystem mapper pro %wwwDir%/js/ +``` + +Každý mapper vytvoří `FilesystemMapper`, který: +- Hledá soubory v `%wwwDir%/` +- Generuje URL jako `%baseUrl%/` +- Dědí globální nastavení verzování + + +Pro větší kontrolu použijte detailní notaci: + +```neon +assets: + mapping: + images: + # adresář, kde jsou soubory uloženy + path: ... # (string) volitelné, výchozí hodnota je '' + + # URL prefix pro generované odkazy + url: ... # (string) volitelné, výchozí hodnota je cesta + + # povolit verzování pro tento mapper? + versioning: ... # (bool) volitelné, dědí globální nastavení + + # automaticky přidat příponu(y) při hledání souborů + extension: ... # (string|array) volitelné, výchozí hodnota je null +``` + +Pochopení, jak se řeší konfigurační hodnoty: + +Řešení cest: + - Relativní cesty jsou řešeny z `basePath` (nebo `%wwwDir%`, pokud `basePath` není nastaveno) + - Absolutní cesty jsou použity tak, jak jsou + +Řešení URL: + - Relativní URL jsou řešeny z `baseUrl` (nebo `%baseUrl%`, pokud `baseUrl` není nastaveno) + - Absolutní URL (se schématem nebo `//`) jsou použity tak, jak jsou + - Pokud `url` není specifikováno, použije hodnotu `path` + + +```neon +assets: + basePath: /var/www/project/www + baseUrl: https://example.com/assets + + mapping: + # Relativní cesta a URL + images: + path: img # Vyřešeno na: /var/www/project/www/img + url: images # Vyřešeno na: https://example.com/assets/images + + # Absolutní cesta a URL + uploads: + path: /var/shared/uploads # Použito tak, jak je: /var/shared/uploads + url: https://cdn.example.com # Použito tak, jak je: https://cdn.example.com + + # Pouze cesta specifikována + styles: + path: css # Cesta: /var/www/project/www/css + # URL: https://example.com/assets/css +``` + + +Vlastní mappery +--------------- + +Pro vlastní mappery odkazujte nebo definujte službu: + +```neon +services: + s3mapper: App\Assets\S3Mapper(%s3.bucket%) + +assets: + mapping: + cloud: @s3mapper + database: App\Assets\DatabaseMapper(@database.connection) +``` + + +Vite Mapper +----------- + +Vite mapper vyžaduje pouze přidání `type: vite`. Zde je kompletní seznam konfiguračních možností: + +```neon +assets: + mapping: + default: + # typ mapperu (vyžadováno pro Vite) + type: vite # (string) vyžadováno, musí být 'vite' + + # výstupní adresář Vite buildu + path: ... # (string) volitelné, výchozí hodnota je '' + + # URL prefix pro sestavené assety + url: ... # (string) volitelné, výchozí hodnota je cesta + + # umístění souboru manifestu Vite + manifest: ... # (string) volitelné, výchozí hodnota je /.vite/manifest.json + + # konfigurace Vite dev serveru + devServer: ... # (bool|string) volitelné, výchozí hodnota je true + + # verzování souborů ve veřejném adresáři + versioning: ... # (bool) volitelné, dědí globální nastavení + + # automatická přípona pro soubory ve veřejném adresáři + extension: ... # (string|array) volitelné, výchozí hodnota je null +``` + +Možnost `devServer` řídí, jak se assety načítají během vývoje: + +- `true` (výchozí) – Automaticky detekuje Vite dev server na aktuálním hostiteli a portu. Pokud je dev server spuštěn **a vaše aplikace je v debug režimu**, assety se z něj načítají s podporou hot module replacement. Pokud dev server neběží, assety se načítají ze sestavených souborů ve veřejném adresáři. +- `false` – Zcela zakáže integraci dev serveru. Assety se vždy načítají ze sestavených souborů. +- Vlastní URL (např. `https://localhost:5173`) – Ručně zadejte URL dev serveru včetně protokolu a portu. Užitečné, když dev server běží na jiném hostiteli nebo portu. + +Možnosti `versioning` a `extension` se vztahují pouze na soubory ve veřejném adresáři Vite, které nejsou zpracovány Vite. + + +Ruční konfigurace +----------------- + +Pokud nepoužíváte Nette DI, nakonfigurujte mappery ručně: + +```php +use Nette\Assets\Registry; +use Nette\Assets\FilesystemMapper; +use Nette\Assets\ViteMapper; + +$registry = new Registry; + +// Přidat filesystem mapper +$registry->addMapper('images', new FilesystemMapper( + baseUrl: 'https://example.com/img', + basePath: __DIR__ . '/www/img', + extensions: ['webp', 'jpg', 'png'], + versioning: true, +)); + +// Přidat Vite mapper +$registry->addMapper('app', new ViteMapper( + baseUrl: '/build', + basePath: __DIR__ . '/www/build', + manifestPath: __DIR__ . '/www/build/.vite/manifest.json', + devServer: 'https://localhost:5173', +)); +``` diff --git a/assets/cs/vite.texy b/assets/cs/vite.texy new file mode 100644 index 0000000000..6fe1ddf17a --- /dev/null +++ b/assets/cs/vite.texy @@ -0,0 +1,508 @@ +Integrace s Vite +**************** + +
    + +Moderní JavaScriptové aplikace vyžadují sofistikované build nástroje. Nette Assets poskytuje prvotřídní integraci s [Vite |https://vitejs.dev/], nástrojem pro frontend build nové generace. Získejte bleskově rychlý vývoj s Hot Module Replacement (HMR) a optimalizované produkční buildy bez potíží s konfigurací. + +- **Nulová konfigurace** – automatické propojení mezi Vite a PHP šablonami +- **Kompletní správa závislostí** – jeden tag zpracovává všechny assety +- **Hot Module Replacement** – okamžité aktualizace JavaScriptu a CSS +- **Optimalizované produkční buildy** – code splitting a tree shaking + +
    + + +Nette Assets se bezproblémově integruje s Vite, takže získáte všechny tyto výhody, zatímco budete psát své šablony jako obvykle. + + +Nastavení Vite +============== + +Pojďme nastavit Vite krok za krokem. Nebojte se, pokud jste v build nástrojích noví – vše vysvětlíme! + + +Krok 1: Instalace Vite +---------------------- + +Nejprve nainstalujte Vite a Nette plugin do vašeho projektu: + +```shell +npm install -D vite @nette/vite-plugin +``` + +Tím se nainstaluje Vite a speciální plugin, který pomáhá Vite perfektně fungovat s Nette. + + +Krok 2: Struktura projektu +-------------------------- + +Standardní přístup je umístit zdrojové soubory assetů do složky `assets/` v kořenovém adresáři projektu a zkompilované verze do `www/assets/`: + +/--pre +web-project/ +├── assets/ ← zdrojové soubory (SCSS, TypeScript, zdrojové obrázky) +│ ├── public/ ← statické soubory (kopírovány tak, jak jsou) +│ │ └── favicon.ico +│ ├── images/ +│ │ └── logo.png +│ ├── app.js ← hlavní vstupní bod +│ └── style.css ← vaše styly +└── www/ ← veřejný adresář (document root) + ├── assets/ ← zde budou zkompilované soubory + └── index.php +\-- + +Složka `assets/` obsahuje vaše zdrojové soubory – kód, který píšete. Vite tyto soubory zpracuje a umístí zkompilované verze do `www/assets/`. + + +Krok 3: Konfigurace Vite +------------------------ + +Vytvořte soubor `vite.config.ts` v kořenovém adresáři projektu. Tento soubor říká Vite, kde má najít vaše zdrojové soubory a kam má umístit zkompilované. + +Nette Vite plugin přichází s chytrými výchozími nastaveními, která zjednodušují konfiguraci. Předpokládá, že vaše front-end zdrojové soubory jsou v adresáři `assets/` (možnost `root`) a zkompilované soubory jdou do `www/assets/` (možnost `outDir`). Potřebujete pouze specifikovat [vstupní bod|#Entry Points]: + +```js +import { defineConfig } from 'vite'; +import nette from '@nette/vite-plugin'; + +export default defineConfig({ + plugins: [ + nette({ + entry: 'app.js', + }), + ], +}); +``` + +Pokud chcete zadat jiný název adresáře pro sestavení vašich assetů, budete muset změnit několik možností: + +```js +export default defineConfig({ + root: 'assets', // kořenový adresář zdrojových assetů + + build: { + outDir: '../www/assets', // kam jdou zkompilované soubory + }, + + // ... další konfigurace ... +}); +``` + +.[note] +Cesta `outDir` je považována za relativní k `root`, proto je na začátku `../`. + + +Krok 4: Konfigurace Nette +------------------------- + +Řekněte Nette Assets o Vite ve vašem `common.neon`: + +```neon +assets: + mapping: + default: + type: vite # říká Nette, aby použilo ViteMapper + path: assets +``` + + +Krok 5: Přidání skriptů +----------------------- + +Přidejte tyto skripty do vašeho `package.json`: + +```json +{ + "scripts": { + "dev": "vite", + "build": "vite build" + } +} +``` + +Nyní můžete: +- `npm run dev` – spustí vývojový server s hot reloadingem +- `npm run build` – vytvoří optimalizované produkční soubory + + +Vstupní body +============ + +**Vstupní bod** je hlavní soubor, kde začíná vaše aplikace. Z tohoto souboru importujete další soubory (CSS, JavaScript moduly, obrázky), čímž vytváříte strom závislostí. Vite sleduje tyto importy a vše sváže dohromady. + +Příklad vstupního bodu `assets/app.js`: + +```js +// Importovat styly +import './style.css' + +// Importovat JavaScript moduly +import netteForms from 'nette-forms'; +import naja from 'naja'; + +// Inicializovat vaši aplikaci +netteForms.initOnLoad(); +naja.initialize(); +``` + +V šabloně můžete vložit vstupní bod následovně: + +```latte +{asset 'app.js'} +``` + +Nette Assets automaticky generuje všechny potřebné HTML tagy – JavaScript, CSS a jakékoli další závislosti. + + +Více vstupních bodů +------------------- + +Větší aplikace často potřebují samostatné vstupní body: + +```js +export default defineConfig({ + plugins: [ + nette({ + entry: [ + 'app.js', // veřejné stránky + 'admin.js', // administrační panel + ], + }), + ], +}); +``` + +Použijte je v různých šablonách: + +```latte +{* Na veřejných stránkách *} +{asset 'app.js'} + +{* V administračním panelu *} +{asset 'admin.js'} +``` + + +Důležité: Zdrojové vs. zkompilované soubory +------------------------------------------- + +Je klíčové pochopit, že v produkci můžete načíst pouze: + +1. Vstupní body definované v `entry` +2. Soubory z adresáře `assets/public/` + +Nemůžete načítat pomocí `{asset}` libovolné soubory z `assets/` – pouze assety odkazované JavaScriptovými nebo CSS soubory. Pokud váš soubor není nikde odkazován, nebude zkompilován. Pokud chcete, aby Vite věděl o dalších assetech, můžete je přesunout do [veřejné složky|#public folder]. + +Vezměte prosím na vědomí, že Vite ve výchozím nastavení vloží všechny assety menší než 4KB, takže tyto soubory nebudete moci přímo odkazovat. (Viz [dokumentace Vite |https://vite.dev/guide/assets.html]). + +```latte +{* ✓ Toto funguje – je to vstupní bod *} +{asset 'app.js'} + +{* ✓ Toto funguje – je to v assets/public/ *} +{asset 'favicon.ico'} + +{* ✗ Toto nebude fungovat – náhodný soubor v assets/ *} +{asset 'components/button.js'} +``` + + +Vývojový režim +============== + +Vývojový režim je zcela volitelný, ale po aktivaci poskytuje značné výhody. Hlavní výhodou je **Hot Module Replacement (HMR)** – okamžitě vidíte změny bez ztráty stavu aplikace, což činí vývoj mnohem plynulejším a rychlejším. + +Vite je moderní build nástroj, který činí vývoj neuvěřitelně rychlým. Na rozdíl od tradičních bundlerů, Vite během vývoje servíruje váš kód přímo do prohlížeče, což znamená okamžitý start serveru bez ohledu na velikost vašeho projektu a bleskově rychlé aktualizace. + + +Spuštění vývojového serveru +--------------------------- + +Spusťte vývojový server: + +```shell +npm run dev +``` + +Uvidíte: + +``` + ➜ Local: http://localhost:5173/ + ➜ Network: use --host to expose +``` + +Nechte tento terminál otevřený během vývoje. + +Nette Vite plugin automaticky detekuje, když: +1. Vite dev server běží +2. Vaše Nette aplikace je v debug režimu + +Když jsou splněny obě podmínky, Nette Assets načítá soubory z Vite dev serveru namísto zkompilovaného adresáře: + +```latte +{asset 'app.js'} +{* Ve vývoji: *} +{* V produkci: *} +``` + +Není potřeba žádná konfigurace – prostě to funguje! + + +Práce na různých doménách +------------------------- + +Pokud váš vývojový server běží na něčem jiném než `localhost` (například `myapp.local`), můžete narazit na problémy s CORS (Cross-Origin Resource Sharing). CORS je bezpečnostní funkce ve webových prohlížečích, která ve výchozím nastavení blokuje požadavky mezi různými doménami. Když vaše PHP aplikace běží na `myapp.local`, ale Vite běží na `localhost:5173`, prohlížeč je vnímá jako různé domény a blokuje požadavky. + +Máte dvě možnosti, jak to vyřešit: + +**Možnost 1: Konfigurace CORS** + +Nejjednodušší řešení je povolit cross-origin požadavky z vaší PHP aplikace: + +```js +export default defineConfig({ + // ... další konfigurace ... + + server: { + cors: { + origin: 'http://myapp.local', // URL vaší PHP aplikace + }, + }, +}); +``` +**Možnost 2: Spusťte Vite na vaší doméně** + +Dalším řešením je nechat Vite běžet na stejné doméně jako vaše PHP aplikace. + +```js +export default defineConfig({ + // ... další konfigurace ... + + server: { + host: 'myapp.local', // stejné jako vaše PHP aplikace + }, +}); +``` + +Ve skutečnosti i v tomto případě musíte nakonfigurovat CORS, protože dev server běží na stejném hostiteli, ale na jiném portu. V tomto případě je však CORS automaticky konfigurován Nette Vite pluginem. + + +Vývoj s HTTPS +------------- + +Pokud vyvíjíte na HTTPS, potřebujete certifikáty pro váš Vite vývojový server. Nejjednodušší způsob je použití pluginu, který automaticky generuje certifikáty: + +```shell +npm install -D vite-plugin-mkcert +``` + +Zde je, jak to nakonfigurovat v `vite.config.ts`: + +```js +import mkcert from 'vite-plugin-mkcert'; + +export default defineConfig({ + // ... další konfigurace ... + + plugins: [ + mkcert(), // automaticky generuje certifikáty a povolí https + nette(), + ], +}); +``` + +Všimněte si, že pokud používáte konfiguraci CORS (možnost 1 výše), musíte aktualizovat URL původu tak, aby používala `https://` namísto `http://`. + + +Produkční buildy +================ + +Vytvořte optimalizované produkční soubory: + +```shell +npm run build +``` + +Vite bude: +- Minifikovat veškerý JavaScript a CSS +- Rozdělit kód do optimálních chunků +- Generovat hashované názvy souborů pro cache-busting +- Vytvořit soubor manifestu pro Nette Assets + +Příklad výstupu: + +``` +www/assets/ +├── app-4f3a2b1c.js # Váš hlavní JavaScript (minifikovaný) +├── app-7d8e9f2a.css # Extrahované CSS (minifikované) +├── vendor-8c4b5e6d.js # Sdílené závislosti +└── .vite/ + └── manifest.json # Mapování pro Nette Assets +``` + +Hashované názvy souborů zajišťují, že prohlížeče vždy načtou nejnovější verzi. + + +Veřejná složka +============== + +Soubory v adresáři `assets/public/` jsou kopírovány do výstupu bez zpracování: + +``` +assets/ +├── public/ +│ ├── favicon.ico +│ ├── robots.txt +│ └── images/ +│ └── og-image.jpg +├── app.js +└── style.css +``` + +Odkazujte na ně normálně: + +```latte +{* Tyto soubory jsou kopírovány tak, jak jsou *} + + +``` + +Pro veřejné soubory můžete použít funkce FilesystemMapperu: + +```neon +assets: + mapping: + default: + type: vite + path: assets + extension: [webp, jpg, png] # Zkusit WebP jako první + versioning: true # Přidat cache-busting +``` + +V konfiguraci `vite.config.ts` můžete změnit veřejnou složku pomocí možnosti `publicDir`. + + +Dynamické importy +================= + +Vite automaticky rozděluje kód pro optimální načítání. Dynamické importy vám umožňují načítat kód pouze tehdy, když je skutečně potřeba, čímž se snižuje počáteční velikost balíčku: + +```js +// Načíst náročné komponenty na vyžádání +button.addEventListener('click', async () => { + let { Chart } = await import('./components/chart.js') + new Chart(data) +}) +``` + +Dynamické importy vytvářejí samostatné chunky, které se načítají pouze v případě potřeby. Tomu se říká "code splitting" a je to jedna z nejvýkonnějších funkcí Vite. Když používáte dynamické importy, Vite automaticky vytváří samostatné JavaScriptové soubory pro každý dynamicky importovaný modul. + +Tag `{asset 'app.js'}` **automaticky nepřednačítá** tyto dynamické chunky. Toto je záměrné chování – nechceme stahovat kód, který by se nikdy nemusel použít. Chunky se stahují pouze při provedení dynamického importu. + +Pokud však víte, že určité dynamické importy jsou kritické a budou brzy potřeba, můžete je přednačíst: + +```latte +{* Hlavní vstupní bod *} +{asset 'app.js'} + +{* Přednačíst kritické dynamické importy *} +{preload 'components/chart.js'} +``` + +To říká prohlížeči, aby stáhl komponentu grafu na pozadí, takže je okamžitě připravena, když je potřeba. + + +Podpora TypeScriptu +=================== + +TypeScript funguje ihned po instalaci: + +```ts +// assets/main.ts +interface User { + name: string + email: string +} + +export function greetUser(user: User): void { + console.log(`Hello, ${user.name}!`) +} +``` + +Odkazujte na soubory TypeScriptu normálně: + +```latte +{asset 'main.ts'} +``` + +Pro plnou podporu TypeScriptu jej nainstalujte: + +```shell +npm install -D typescript +``` + + +Další konfigurace Vite +====================== + +Zde jsou některé užitečné konfigurační možnosti Vite s podrobnými vysvětleními: + +```js +export default defineConfig({ + // Kořenový adresář obsahující zdrojové assety + root: 'assets', + + // Složka, jejíž obsah je kopírován do výstupního adresáře tak, jak je + // Výchozí: 'public' (relativně k 'root') + publicDir: 'public', + + build: { + // Kam umístit zkompilované soubory (relativně k 'root') + outDir: '../www/assets', + + // Vyprázdnit výstupní adresář před sestavením? + // Užitečné pro odstranění starých souborů z předchozích buildů + emptyOutDir: true, + + // Podadresář uvnitř outDir pro generované chunky a assety + // To pomáhá organizovat výstupní strukturu + assetsDir: 'static', + + rollupOptions: { + // Vstupní bod(y) – může být jeden soubor nebo pole souborů + // Každý vstupní bod se stane samostatným balíčkem + input: [ + 'app.js', // hlavní aplikace + 'admin.js', // administrační panel + ], + }, + }, + + server: { + // Hostitel, na který se má dev server navázat + // Použijte '0.0.0.0' pro vystavení do sítě + host: 'localhost', + + // Port pro dev server + port: 5173, + + // Konfigurace CORS pro cross-origin požadavky + cors: { + origin: 'http://myapp.local', + }, + }, + + css: { + // Povolit CSS source mapy ve vývoji + devSourcemap: true, + }, + + plugins: [ + nette(), + ], +}); +``` + +To je vše! Nyní máte moderní build systém integrovaný s Nette Assets. diff --git a/assets/de/@home.texy b/assets/de/@home.texy new file mode 100644 index 0000000000..a29023976d --- /dev/null +++ b/assets/de/@home.texy @@ -0,0 +1,432 @@ +Nette Assets +************ + +
    + +Sind Sie es leid, statische Dateien in Ihren Webanwendungen manuell zu verwalten? Vergessen Sie das Hardcodieren von Pfaden, die Cache-Invalidierung oder die Sorge um die Dateiversionierung. Nette Assets verändert die Art und Weise, wie Sie mit Bildern, Stylesheets, Skripten und anderen statischen Ressourcen arbeiten. + +- **Intelligente Versionierung** stellt sicher, dass Browser immer die neuesten Dateien laden +- **Automatische Erkennung** von Dateitypen und Dimensionen +- **Nahtlose Latte-Integration** mit intuitiven Tags +- **Flexible Architektur**, die Dateisysteme, CDNs und Vite unterstützt +- **Lazy Loading** für optimale Leistung + +
    + + +Warum Nette Assets? +=================== + +Die Arbeit mit statischen Dateien bedeutet oft sich wiederholenden, fehleranfälligen Code. Sie konstruieren URLs manuell, fügen Versionsparameter zur Cache-Busting hinzu und behandeln verschiedene Dateitypen unterschiedlich. Dies führt zu Code wie: + +```html +Logo + +``` + +Mit Nette Assets verschwindet all diese Komplexität: + +```latte +{* Alles automatisiert - URL, Versionierung, Dimensionen *} + + + +{* Oder einfach *} +{asset 'css/style.css'} +``` + +Das war's! Die Bibliothek erledigt automatisch: +- Fügt Versionsparameter basierend auf der Dateimodifikationszeit hinzu +- Erkennt Bilddimensionen und fügt sie in das HTML ein +- Generiert das korrekte HTML-Element für jeden Dateityp +- Handhabt sowohl Entwicklungs- als auch Produktionsumgebungen + + +Installation +============ + +Installieren Sie Nette Assets mit [Composer|best-practices:composer]: + +```shell +composer require nette/assets +``` + +Es erfordert PHP 8.1 oder höher und funktioniert perfekt mit dem Nette Framework, kann aber auch eigenständig verwendet werden. + + +Erste Schritte +============== + +Nette Assets funktioniert sofort und ohne Konfiguration. Legen Sie Ihre statischen Dateien im Verzeichnis `www/assets/` ab und beginnen Sie mit der Verwendung: + +```latte +{* Zeigt ein Bild mit automatischen Dimensionen an *} +{asset 'logo.png'} + +{* Fügt ein Stylesheet mit Versionierung ein *} +{asset 'style.css'} + +{* Lädt ein JavaScript-Modul *} +{asset 'app.js'} +``` + +Für mehr Kontrolle über das generierte HTML verwenden Sie das Attribut `n:asset` oder die Funktion `asset()`. + + +Wie es funktioniert +=================== + +Nette Assets basiert auf drei Kernkonzepten, die es leistungsstark und dennoch einfach zu bedienen machen: + + +Assets - Ihre Dateien intelligent gemacht +----------------------------------------- + +Ein **Asset** repräsentiert jede statische Datei in Ihrer Anwendung. Jede Datei wird zu einem Objekt mit nützlichen schreibgeschützten Eigenschaften: + +```php +$image = $assets->getAsset('photo.jpg'); +echo $image->url; // '/assets/photo.jpg?v=1699123456' +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' +``` + +Verschiedene Dateitypen bieten unterschiedliche Eigenschaften: +- **Bilder**: Breite, Höhe, Alternativtext, Lazy Loading +- **Skripte**: Modultyp, Integritäts-Hashes, Cross-Origin +- **Stylesheets**: Media Queries, Integrität +- **Audio/Video**: Dauer, Dimensionen +- **Schriftarten**: korrektes Preloading mit CORS + +Die Bibliothek erkennt Dateitypen automatisch und erstellt die entsprechende Asset-Klasse. + + +Mapper - Woher Dateien kommen +----------------------------- + +Ein **Mapper** weiß, wie Dateien gefunden und URLs für sie erstellt werden. Sie können mehrere Mapper für verschiedene Zwecke haben – lokale Dateien, CDN, Cloud-Speicher oder Build-Tools (jeder von ihnen hat einen Namen). Der eingebaute `FilesystemMapper` verarbeitet lokale Dateien, während `ViteMapper` sich in moderne Build-Tools integriert. + +Mapper werden in der [Konfiguration |Configuration] definiert. + + +Registry - Ihre Hauptschnittstelle +---------------------------------- + +Die **Registry** verwaltet alle Mapper und stellt die Haupt-API bereit: + +```php +// Injizieren Sie die Registry in Ihren Dienst +public function __construct( + private Nette\Assets\Registry $assets +) {} + +// Assets von verschiedenen Mappern abrufen +$logo = $this->assets->getAsset('images:logo.png'); // 'image' mapper +$app = $this->assets->getAsset('app:main.js'); // 'app' mapper +$style = $this->assets->getAsset('style.css'); // verwendet den Standard-Mapper +``` + +Die Registry wählt automatisch den richtigen Mapper aus und speichert die Ergebnisse zur Leistungsverbesserung im Cache. + + +Arbeiten mit Assets in PHP +========================== + +Die Registry bietet zwei Methoden zum Abrufen von Assets: + +```php +// Wirft Nette\Assets\AssetNotFoundException, wenn die Datei nicht existiert +$logo = $assets->getAsset('logo.png'); + +// Gibt null zurück, wenn die Datei nicht existiert +$banner = $assets->tryGetAsset('banner.jpg'); +if ($banner) { + echo $banner->url; +} +``` + + +Mapper angeben +-------------- + +Sie können explizit auswählen, welchen Mapper Sie verwenden möchten: + +```php +// Standard-Mapper verwenden +$file = $assets->getAsset('document.pdf'); + +// Spezifischen Mapper mit Präfix verwenden +$image = $assets->getAsset('images:photo.jpg'); + +// Spezifischen Mapper mit Array-Syntax verwenden +$script = $assets->getAsset(['scripts', 'app.js']); +``` + + +Asset-Eigenschaften und -Typen +------------------------------ + +Jeder Asset-Typ bietet relevante schreibgeschützte Eigenschaften: + +```php +// Bildeigenschaften +$image = $assets->getAsset('photo.jpg'); +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' + +// Skript-Eigenschaften +$script = $assets->getAsset('app.js'); +echo $script->type; // 'module' or null + +// Audio-Eigenschaften +$audio = $assets->getAsset('song.mp3'); +echo $audio->duration; // duration in seconds + +// Alle Assets können in einen String umgewandelt werden (gibt URL zurück) +$url = (string) $assets->getAsset('document.pdf'); +``` + +.[note] +Eigenschaften wie Dimensionen oder Dauer werden nur bei Zugriff verzögert geladen, um die Bibliothek schnell zu halten. + + +Verwenden von Assets in Latte-Templates +======================================= + +Nette Assets bietet intuitive [Latte|latte:]-Integration mit Tags und Funktionen. + + +`{asset}` +--------- + +Der `{asset}`-Tag rendert vollständige HTML-Elemente: + +```latte +{* Rendert: *} +{asset 'hero.jpg'} + +{* Rendert: *} +{asset 'app.js'} + +{* Rendert: *} +{asset 'style.css'} +``` + +Der Tag erledigt automatisch: +- Erkennt den Asset-Typ und generiert das passende HTML +- Fügt Versionierung für Cache-Busting hinzu +- Fügt Dimensionen für Bilder hinzu +- Setzt korrekte Attribute (Typ, Medien usw.) + +Bei Verwendung innerhalb von HTML-Attributen gibt es nur die URL aus: + +```latte +
    + +``` + + +`n:asset` +--------- + +Für die volle Kontrolle über HTML-Attribute: + +```latte +{* Das n:asset Attribut füllt src, Dimensionen usw. aus. *} +Product + +{* Funktioniert mit jedem relevanten Element *} + + + +``` + +Verwenden Sie Variablen und Mapper: + +```latte +{* Variablen funktionieren natürlich *} + + +{* Mapper mit geschweiften Klammern angeben *} + + +{* Mapper mit Array-Notation angeben *} + +``` + + +`asset()` +--------- + +Für maximale Flexibilität verwenden Sie die Funktion `asset()`: + +```latte +{var $logo = asset('logo.png')} +width} height={$logo->height}> + +{* Oder direkt *} +Logo +``` + + +Optionale Assets +---------------- + +Behandeln Sie fehlende Assets elegant mit `{asset?}`, `n:asset?` und `tryAsset()`: + +```latte +{* Optionaler Tag - rendert nichts, wenn Asset fehlt *} +{asset? 'optional-banner.jpg'} + +{* Optionales Attribut - überspringt, wenn Asset fehlt *} +Avatar + +{* Mit Fallback *} +{var $avatar = tryAsset('user-avatar.jpg') ?? asset('default-avatar.jpg')} +Avatar +``` + + +`{preload}` +----------- + +Verbessern Sie die Seitenladeleistung: + +```latte +{* Im -Bereich *} +{preload 'critical.css'} +{preload 'important-font.woff2'} +{preload 'hero-image.jpg'} +``` + +Generiert entsprechende Preload-Links: + +```html + + + +``` + + +Erweiterte Funktionen +===================== + + +Erweiterungs-Automatik-Erkennung +-------------------------------- + +Verarbeitet mehrere Formate automatisch: + +```neon +assets: + mapping: + images: + path: img + extension: [webp, jpg, png] # In dieser Reihenfolge versuchen +``` + +Jetzt können Sie ohne Erweiterung anfordern: + +```latte +{* Findet logo.webp, logo.jpg oder logo.png automatisch *} +{asset 'images:logo'} +``` + +Perfekt für progressive Verbesserung mit modernen Formaten. + + +Intelligente Versionierung +-------------------------- + +Dateien werden automatisch basierend auf der Änderungszeit versioniert: + +```latte +{asset 'style.css'} +{* Ausgabe: *} +``` + +Wenn Sie die Datei aktualisieren, ändert sich der Zeitstempel, was eine Aktualisierung des Browser-Caches erzwingt. + +Steuern Sie die Versionierung pro Asset: + +```php +// Versionierung für bestimmtes Asset deaktivieren +$asset = $assets->getAsset('style.css', ['version' => false]); + +// In Latte +{asset 'style.css', version: false} +``` + + +Schriftarten-Assets +------------------- + +Schriftarten erhalten eine spezielle Behandlung mit korrektem CORS: + +```latte +{* Korrektes Preload mit Crossorigin *} +{preload 'fonts:OpenSans-Regular.woff2'} + +{* Verwendung in CSS *} + +``` + + +Benutzerdefinierte Mapper +========================= + +Erstellen Sie benutzerdefinierte Mapper für spezielle Anforderungen wie Cloud-Speicher oder dynamische Generierung: + +```php +use Nette\Assets\Mapper; +use Nette\Assets\Asset; +use Nette\Assets\Helpers; + +class CloudStorageMapper implements Mapper +{ + public function __construct( + private CloudClient $client, + private string $bucket, + ) {} + + public function getAsset(string $reference, array $options = []): Asset + { + if (!$this->client->exists($this->bucket, $reference)) { + throw new Nette\Assets\AssetNotFoundException("Asset '$reference' nicht gefunden"); + } + + $url = $this->client->getPublicUrl($this->bucket, $reference); + return Helpers::createAssetFromUrl($url); + } +} +``` + +Registrieren Sie in der Konfiguration: + +```neon +assets: + mapping: + cloud: CloudStorageMapper(@cloudClient, 'my-bucket') +``` + +Verwenden Sie wie jeden anderen Mapper: + +```latte +{asset 'cloud:user-uploads/photo.jpg'} +``` + +Die Methode `Helpers::createAssetFromUrl()` erstellt automatisch den korrekten Asset-Typ basierend auf der Dateierweiterung. + + +Weitere Lektüre +=============== + +- [Nette Assets: Endlich eine einheitliche API für alles von Bildern bis Vite |https://blog.nette.org/en/introducing-nette-assets] diff --git a/assets/de/@left-menu.texy b/assets/de/@left-menu.texy new file mode 100644 index 0000000000..7376817c1d --- /dev/null +++ b/assets/de/@left-menu.texy @@ -0,0 +1,5 @@ +Nette Assets +************ +- [Erste Schritte |@home] +- [Vite |vite] +- [Konfiguration |Configuration] diff --git a/assets/de/@meta.texy b/assets/de/@meta.texy new file mode 100644 index 0000000000..b3b806b2ca --- /dev/null +++ b/assets/de/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokumentation}} diff --git a/assets/de/configuration.texy b/assets/de/configuration.texy new file mode 100644 index 0000000000..26bc89785a --- /dev/null +++ b/assets/de/configuration.texy @@ -0,0 +1,188 @@ +Assets Konfiguration +******************** + +.[perex] +Übersicht der Konfigurationsoptionen für Nette Assets. + + +```neon +assets: + # Basispfad zum Auflösen relativer Mapper-Pfade + basePath: ... # (string) Standardwert ist %wwwDir% + + # Basis-URL zum Auflösen relativer Mapper-URLs + baseUrl: ... # (string) Standardwert ist %baseUrl% + + # Asset-Versionierung global aktivieren? + versioning: ... # (bool) Standardwert ist true + + # definiert Asset-Mapper + mapping: ... # (array) Standardwert ist der Pfad 'assets' +``` + +Der `basePath` legt das Standard-Dateisystemverzeichnis zum Auflösen relativer Pfade in Mappern fest. Standardmäßig wird das Webverzeichnis (`%wwwDir%`) verwendet. + +Der `baseUrl` legt das Standard-URL-Präfix zum Auflösen relativer URLs in Mappern fest. Standardmäßig wird die Root-URL (`%baseUrl%`) verwendet. + +Die Option `versioning` steuert global, ob Versionsparameter zu Asset-URLs für Cache-Busting hinzugefügt werden. Einzelne Mapper können diese Einstellung überschreiben. + + +Mapper +------ + +Mapper können auf drei Arten konfiguriert werden: einfache String-Notation, detaillierte Array-Notation oder als Referenz auf einen Dienst. + +Die einfachste Art, einen Mapper zu definieren: + +```neon +assets: + mapping: + default: assets # Erstellt einen Dateisystem-Mapper für %wwwDir%/assets/ + images: img # Erstellt einen Dateisystem-Mapper für %wwwDir%/img/ + scripts: js # Erstellt einen Dateisystem-Mapper für %wwwDir%/js/ +``` + +Jeder Mapper erstellt einen `FilesystemMapper`, der: +- Sucht nach Dateien in `%wwwDir%/` +- Generiert URLs wie `%baseUrl%/` +- Erbt die globale Versionierungseinstellung + + +Für mehr Kontrolle verwenden Sie die detaillierte Notation: + +```neon +assets: + mapping: + images: + # Verzeichnis, in dem Dateien gespeichert sind + path: ... # (string) optional, Standardwert ist '' + + # URL-Präfix für generierte Links + url: ... # (string) optional, Standardwert ist path + + # Versionierung für diesen Mapper aktivieren? + versioning: ... # (bool) optional, erbt globale Einstellung + + # Erweiterung(en) beim Suchen nach Dateien automatisch hinzufügen + extension: ... # (string|array) optional, Standardwert ist null +``` + +Verständnis, wie Konfigurationswerte aufgelöst werden: + +Pfadauflösung: + - Relative Pfade werden von `basePath` (oder `%wwwDir%`, wenn `basePath` nicht gesetzt ist) aufgelöst + - Absolute Pfade werden unverändert verwendet + +URL-Auflösung: + - Relative URLs werden von `baseUrl` (oder `%baseUrl%`, wenn `baseUrl` nicht gesetzt ist) aufgelöst + - Absolute URLs (mit Schema oder `//`) werden unverändert verwendet + - Wenn `url` nicht angegeben ist, wird der Wert von `path` verwendet + + +```neon +assets: + basePath: /var/www/project/www + baseUrl: https://example.com/assets + + mapping: + # Relativer Pfad und URL + images: + path: img # Aufgelöst zu: /var/www/project/www/img + url: images # Aufgelöst zu: https://example.com/assets/images + + # Absoluter Pfad und URL + uploads: + path: /var/shared/uploads # Unverändert verwendet: /var/shared/uploads + url: https://cdn.example.com # Unverändert verwendet: https://cdn.example.com + + # Nur Pfad angegeben + styles: + path: css # Pfad: /var/www/project/www/css + # URL: https://example.com/assets/css +``` + + +Benutzerdefinierte Mapper +------------------------- + +Für benutzerdefinierte Mapper verweisen oder definieren Sie einen Dienst: + +```neon +services: + s3mapper: App\Assets\S3Mapper(%s3.bucket%) + +assets: + mapping: + cloud: @s3mapper + database: App\Assets\DatabaseMapper(@database.connection) +``` + + +Vite Mapper +----------- + +Der Vite-Mapper erfordert lediglich, dass Sie `type: vite` hinzufügen. Dies ist eine vollständige Liste der Konfigurationsoptionen: + +```neon +assets: + mapping: + default: + # Mapper-Typ (erforderlich für Vite) + type: vite # (string) erforderlich, muss 'vite' sein + + # Vite Build-Ausgabeverzeichnis + path: ... # (string) optional, Standardwert ist '' + + # URL-Präfix für gebaute Assets + url: ... # (string) optional, Standardwert ist path + + # Speicherort der Vite-Manifestdatei + manifest: ... # (string) optional, Standardwert ist /.vite/manifest.json + + # Vite Dev-Server-Konfiguration + devServer: ... # (bool|string) optional, Standardwert ist true + + # Versionierung für Dateien im öffentlichen Verzeichnis + versioning: ... # (bool) optional, erbt globale Einstellung + + # Auto-Erweiterung für Dateien im öffentlichen Verzeichnis + extension: ... # (string|array) optional, Standardwert ist null +``` + +Die Option `devServer` steuert, wie Assets während der Entwicklung geladen werden: + +- `true` (Standard) - Erkennt den Vite Dev-Server auf dem aktuellen Host und Port automatisch. Wenn der Dev-Server läuft **und Ihre Anwendung im Debug-Modus ist**, werden Assets von dort mit Hot Module Replacement-Unterstützung geladen. Wenn der Dev-Server nicht läuft, werden Assets aus den gebauten Dateien im öffentlichen Verzeichnis geladen. +- `false` - Deaktiviert die Dev-Server-Integration vollständig. Assets werden immer aus den gebauten Dateien geladen. +- Benutzerdefinierte URL (z.B. `https://localhost:5173`) - Geben Sie die Dev-Server-URL manuell an, einschließlich Protokoll und Port. Nützlich, wenn der Dev-Server auf einem anderen Host oder Port läuft. + +Die Optionen `versioning` und `extension` gelten nur für Dateien im öffentlichen Verzeichnis von Vite, die nicht von Vite verarbeitet werden. + + +Manuelle Konfiguration +---------------------- + +Wenn Sie Nette DI nicht verwenden, konfigurieren Sie Mapper manuell: + +```php +use Nette\Assets\Registry; +use Nette\Assets\FilesystemMapper; +use Nette\Assets\ViteMapper; + +$registry = new Registry; + +// Dateisystem-Mapper hinzufügen +$registry->addMapper('images', new FilesystemMapper( + baseUrl: 'https://example.com/img', + basePath: __DIR__ . '/www/img', + extensions: ['webp', 'jpg', 'png'], + versioning: true, +)); + +// Vite-Mapper hinzufügen +$registry->addMapper('app', new ViteMapper( + baseUrl: '/build', + basePath: __DIR__ . '/www/build', + manifestPath: __DIR__ . '/www/build/.vite/manifest.json', + devServer: 'https://localhost:5173', +)); +``` diff --git a/assets/de/vite.texy b/assets/de/vite.texy new file mode 100644 index 0000000000..50a716c1bb --- /dev/null +++ b/assets/de/vite.texy @@ -0,0 +1,508 @@ +Vite Integration +**************** + +
    + +Moderne JavaScript-Anwendungen erfordern ausgeklügelte Build-Tools. Nette Assets bietet erstklassige Integration mit [Vite |https://vitejs.dev/], dem Frontend-Build-Tool der nächsten Generation. Erhalten Sie blitzschnelle Entwicklung mit Hot Module Replacement (HMR) und optimierte Produktions-Builds mit null Konfigurationsaufwand. + +- **Keine Konfiguration** - automatische Brücke zwischen Vite und PHP-Templates +- **Vollständiges Abhängigkeitsmanagement** - ein Tag verarbeitet alle Assets +- **Hot Module Replacement** - sofortige JavaScript- und CSS-Updates +- **Optimierte Produktions-Builds** - Code-Splitting und Tree Shaking + +
    + + +Nette Assets integriert sich nahtlos in Vite, sodass Sie all diese Vorteile nutzen können, während Sie Ihre Templates wie gewohnt schreiben. + + +Vite einrichten +=============== + +Lassen Sie uns Vite Schritt für Schritt einrichten. Keine Sorge, wenn Sie neu bei Build-Tools sind – wir erklären alles! + + +Schritt 1: Vite installieren +---------------------------- + +Installieren Sie zuerst Vite und das Nette-Plugin in Ihrem Projekt: + +```shell +npm install -D vite @nette/vite-plugin +``` + +Dies installiert Vite und ein spezielles Plugin, das Vite hilft, perfekt mit Nette zusammenzuarbeiten. + + +Schritt 2: Projektstruktur +-------------------------- + +Der Standardansatz ist, Quell-Asset-Dateien in einem `assets/`-Ordner im Projekt-Root zu platzieren und die kompilierten Versionen in `www/assets/`: + +/--pre +web-project/ +├── assets/ ← Quell-Dateien (SCSS, TypeScript, Quell-Bilder) +│ ├── public/ ← statische Dateien (unverändert kopiert) +│ │ └── favicon.ico +│ ├── images/ +│ │ └── logo.png +│ ├── app.js ← Haupt-Einstiegspunkt +│ └── style.css ← Ihre Styles +└── www/ ← öffentliches Verzeichnis (Dokument-Root) + ├── assets/ ← kompilierte Dateien werden hier abgelegt + └── index.php +\-- + +Der Ordner `assets/` enthält Ihre Quelldateien – den Code, den Sie schreiben. Vite wird diese Dateien verarbeiten und die kompilierten Versionen in `www/assets/` ablegen. + + +Schritt 3: Vite konfigurieren +----------------------------- + +Erstellen Sie eine Datei `vite.config.ts` im Projekt-Root. Diese Datei teilt Vite mit, wo Ihre Quelldateien zu finden sind und wohin die kompilierten Dateien abgelegt werden sollen. + +Das Nette Vite-Plugin kommt mit intelligenten Standardeinstellungen, die die Konfiguration vereinfachen. Es geht davon aus, dass Ihre Frontend-Quelldateien im Verzeichnis `assets/` (Option `root`) liegen und kompilierte Dateien nach `www/assets/` (Option `outDir`) gehen. Sie müssen nur den [Einstiegspunkt|#Entry Points] angeben: + +```js +import { defineConfig } from 'vite'; +import nette from '@nette/vite-plugin'; + +export default defineConfig({ + plugins: [ + nette({ + entry: 'app.js', + }), + ], +}); +``` + +Wenn Sie einen anderen Verzeichnisnamen zum Erstellen Ihrer Assets angeben möchten, müssen Sie einige Optionen ändern: + +```js +export default defineConfig({ + root: 'assets', // Root-Verzeichnis der Quell-Assets + + build: { + outDir: '../www/assets', // wohin kompilierte Dateien gehen + }, + + // ... andere Konfiguration ... +}); +``` + +.[note] +Der `outDir`-Pfad wird relativ zu `root` betrachtet, weshalb am Anfang `../` steht. + + +Schritt 4: Nette konfigurieren +------------------------------ + +Informieren Sie Nette Assets über Vite in Ihrer `common.neon`: + +```neon +assets: + mapping: + default: + type: vite # weist Nette an, den ViteMapper zu verwenden + path: assets +``` + + +Schritt 5: Skripte hinzufügen +----------------------------- + +Fügen Sie diese Skripte zu Ihrer `package.json` hinzu: + +```json +{ + "scripts": { + "dev": "vite", + "build": "vite build" + } +} +``` + +Jetzt können Sie: +- `npm run dev` - Entwicklungs-Server mit Hot Reloading starten +- `npm run build` - optimierte Produktionsdateien erstellen + + +Einstiegspunkte +=============== + +Ein **Einstiegspunkt** ist die Hauptdatei, in der Ihre Anwendung startet. Von dieser Datei importieren Sie andere Dateien (CSS, JavaScript-Module, Bilder), wodurch ein Abhängigkeitsbaum entsteht. Vite folgt diesen Importen und bündelt alles zusammen. + +Beispiel-Einstiegspunkt `assets/app.js`: + +```js +// Styles importieren +import './style.css' + +// JavaScript-Module importieren +import netteForms from 'nette-forms'; +import naja from 'naja'; + +// Ihre Anwendung initialisieren +netteForms.initOnLoad(); +naja.initialize(); +``` + +Im Template können Sie einen Einstiegspunkt wie folgt einfügen: + +```latte +{asset 'app.js'} +``` + +Nette Assets generiert automatisch alle notwendigen HTML-Tags – JavaScript, CSS und alle anderen Abhängigkeiten. + + +Mehrere Einstiegspunkte +----------------------- + +Größere Anwendungen benötigen oft separate Einstiegspunkte: + +```js +export default defineConfig({ + plugins: [ + nette({ + entry: [ + 'app.js', // öffentliche Seiten + 'admin.js', // Admin-Panel + ], + }), + ], +}); +``` + +Verwenden Sie sie in verschiedenen Templates: + +```latte +{* Auf öffentlichen Seiten *} +{asset 'app.js'} + +{* Im Admin-Panel *} +{asset 'admin.js'} +``` + + +Wichtig: Quell- vs. kompilierte Dateien +--------------------------------------- + +Es ist entscheidend zu verstehen, dass Sie in der Produktion nur laden können: + +1. **Einstiegspunkte**, die in `entry` definiert sind +2. **Dateien aus dem Verzeichnis `assets/public/`** + +Sie können **nicht** beliebige Dateien aus `assets/` mit `{asset}` laden – nur Assets, die von JavaScript- oder CSS-Dateien referenziert werden. Wenn Ihre Datei nirgendwo referenziert wird, wird sie nicht kompiliert. Wenn Sie Vite auf andere Assets aufmerksam machen möchten, können Sie diese in den [#public folder] verschieben. + +Bitte beachten Sie, dass Vite standardmäßig alle Assets, die kleiner als 4KB sind, inline einfügt, sodass Sie diese Dateien nicht direkt referenzieren können. (Siehe [Vite-Dokumentation |https://vite.dev/guide/assets.html]). + +```latte +{* ✓ Dies funktioniert - es ist ein Einstiegspunkt *} +{asset 'app.js'} + +{* ✓ Dies funktioniert - es ist in assets/public/ *} +{asset 'favicon.ico'} + +{* ✗ Dies funktioniert nicht - zufällige Datei in assets/ *} +{asset 'components/button.js'} +``` + + +Entwicklungsmodus +================= + +Der Entwicklungsmodus ist völlig optional, bietet aber erhebliche Vorteile, wenn er aktiviert ist. Der Hauptvorteil ist **Hot Module Replacement (HMR)** – sehen Sie Änderungen sofort, ohne den Anwendungszustand zu verlieren, was die Entwicklungserfahrung viel reibungsloser und schneller macht. + +Vite ist ein modernes Build-Tool, das die Entwicklung unglaublich schnell macht. Im Gegensatz zu traditionellen Bundlern serviert Vite Ihren Code während der Entwicklung direkt an den Browser, was einen sofortigen Serverstart unabhängig von der Größe Ihres Projekts und blitzschnelle Updates bedeutet. + + +Entwicklungs-Server starten +--------------------------- + +Starten Sie den Entwicklungs-Server: + +```shell +npm run dev +``` + +Sie werden sehen: + +``` + ➜ Local: http://localhost:5173/ + ➜ Network: use --host to expose +``` + +Lassen Sie dieses Terminal während der Entwicklung geöffnet. + +Das Nette Vite-Plugin erkennt automatisch, wann: +1. Der Vite Dev-Server läuft +2. Ihre Nette-Anwendung im Debug-Modus ist + +Wenn beide Bedingungen erfüllt sind, lädt Nette Assets Dateien vom Vite Dev-Server anstelle des kompilierten Verzeichnisses: + +```latte +{asset 'app.js'} +{* In der Entwicklung: *} +{* In der Produktion: *} +``` + +Keine Konfiguration erforderlich – es funktioniert einfach! + + +Arbeiten auf verschiedenen Domains +---------------------------------- + +Wenn Ihr Entwicklungs-Server auf etwas anderem als `localhost` läuft (z.B. `myapp.local`), könnten Sie auf CORS (Cross-Origin Resource Sharing)-Probleme stoßen. CORS ist eine Sicherheitsfunktion in Webbrowsern, die Anfragen zwischen verschiedenen Domains standardmäßig blockiert. Wenn Ihre PHP-Anwendung auf `myapp.local` läuft, Vite aber auf `localhost:5173`, betrachtet der Browser diese als verschiedene Domains und blockiert die Anfragen. + +Sie haben zwei Optionen, um dies zu lösen: + +**Option 1: CORS konfigurieren** + +Die einfachste Lösung ist, Cross-Origin-Anfragen von Ihrer PHP-Anwendung zuzulassen: + +```js +export default defineConfig({ + // ... andere Konfiguration ... + + server: { + cors: { + origin: 'http://myapp.local', // Ihre PHP-App-URL + }, + }, +}); +``` +**Option 2: Vite auf Ihrer Domain ausführen** + +Die andere Lösung ist, Vite auf derselben Domain wie Ihre PHP-Anwendung laufen zu lassen. + +```js +export default defineConfig({ + // ... andere Konfiguration ... + + server: { + host: 'myapp.local', // wie Ihre PHP-App + }, +}); +``` + +Tatsächlich müssen Sie auch in diesem Fall CORS konfigurieren, da der Dev-Server auf demselben Hostnamen, aber auf einem anderen Port läuft. In diesem Fall wird CORS jedoch automatisch vom Nette Vite-Plugin konfiguriert. + + +HTTPS-Entwicklung +----------------- + +Wenn Sie unter HTTPS entwickeln, benötigen Sie Zertifikate für Ihren Vite-Entwicklungs-Server. Der einfachste Weg ist die Verwendung eines Plugins, das Zertifikate automatisch generiert: + +```shell +npm install -D vite-plugin-mkcert +``` + +So konfigurieren Sie es in `vite.config.ts`: + +```js +import mkcert from 'vite-plugin-mkcert'; + +export default defineConfig({ + // ... andere Konfiguration ... + + plugins: [ + mkcert(), // generiert Zertifikate automatisch und aktiviert https + nette(), + ], +}); +``` + +Beachten Sie, dass Sie, wenn Sie die CORS-Konfiguration (Option 1 von oben) verwenden, die Ursprungs-URL aktualisieren müssen, um `https://` anstelle von `http://` zu verwenden. + + +Produktions-Builds +================== + +Erstellen Sie optimierte Produktionsdateien: + +```shell +npm run build +``` + +Vite wird: +- Alle JavaScript und CSS minifizieren +- Code in optimale Chunks aufteilen +- Gehashte Dateinamen für Cache-Busting generieren +- Eine Manifest-Datei für Nette Assets erstellen + +Beispielausgabe: + +``` +www/assets/ +├── app-4f3a2b1c.js # Ihr Haupt-JavaScript (minifiziert) +├── app-7d8e9f2a.css # Extrahiertes CSS (minifiziert) +├── vendor-8c4b5e6d.js # Gemeinsame Abhängigkeiten +└── .vite/ + └── manifest.json # Mapping für Nette Assets +``` + +Die gehashten Dateinamen stellen sicher, dass Browser immer die neueste Version laden. + + +Public-Ordner +============= + +Dateien im Verzeichnis `assets/public/` werden ohne Verarbeitung in die Ausgabe kopiert: + +``` +assets/ +├── public/ +│ ├── favicon.ico +│ ├── robots.txt +│ └── images/ +│ └── og-image.jpg +├── app.js +└── style.css +``` + +Referenzieren Sie sie normal: + +```latte +{* Diese Dateien werden unverändert kopiert *} + + +``` + +Für öffentliche Dateien können Sie FilesystemMapper-Funktionen verwenden: + +```neon +assets: + mapping: + default: + type: vite + path: assets + extension: [webp, jpg, png] # Zuerst WebP versuchen + versioning: true # Cache-Busting hinzufügen +``` + +In der `vite.config.ts`-Konfiguration können Sie den öffentlichen Ordner mit der Option `publicDir` ändern. + + +Dynamische Importe +================== + +Vite teilt Code automatisch für optimales Laden auf. Dynamische Importe ermöglichen es Ihnen, Code nur dann zu laden, wenn er tatsächlich benötigt wird, wodurch die anfängliche Bundle-Größe reduziert wird: + +```js +// Schwere Komponenten bei Bedarf laden +button.addEventListener('click', async () => { + let { Chart } = await import('./components/chart.js') + new Chart(data) +}) +``` + +Dynamische Importe erstellen separate Chunks, die nur bei Bedarf geladen werden. Dies wird "Code-Splitting" genannt und ist eine der leistungsstärksten Funktionen von Vite. Wenn Sie dynamische Importe verwenden, erstellt Vite automatisch separate JavaScript-Dateien für jedes dynamisch importierte Modul. + +Der Tag `{asset 'app.js'}` lädt diese dynamischen Chunks **nicht** automatisch vor. Dies ist beabsichtigtes Verhalten – wir möchten keinen Code herunterladen, der möglicherweise nie verwendet wird. Die Chunks werden nur heruntergeladen, wenn der dynamische Import ausgeführt wird. + +Wenn Sie jedoch wissen, dass bestimmte dynamische Importe kritisch sind und bald benötigt werden, können Sie diese vorladen: + +```latte +{* Haupt-Einstiegspunkt *} +{asset 'app.js'} + +{* Kritische dynamische Importe vorladen *} +{preload 'components/chart.js'} +``` + +Dies weist den Browser an, die Chart-Komponente im Hintergrund herunterzuladen, sodass sie sofort bereit ist, wenn sie benötigt wird. + + +TypeScript-Unterstützung +======================== + +TypeScript funktioniert sofort: + +```ts +// assets/main.ts +interface User { + name: string + email: string +} + +export function greetUser(user: User): void { + console.log(`Hello, ${user.name}!`) +} +``` + +Referenzieren Sie TypeScript-Dateien normal: + +```latte +{asset 'main.ts'} +``` + +Für vollständige TypeScript-Unterstützung installieren Sie es: + +```shell +npm install -D typescript +``` + + +Zusätzliche Vite-Konfiguration +============================== + +Hier sind einige nützliche Vite-Konfigurationsoptionen mit detaillierten Erklärungen: + +```js +export default defineConfig({ + // Root-Verzeichnis mit Quell-Assets + root: 'assets', + + // Ordner, dessen Inhalt unverändert in das Ausgabeverzeichnis kopiert wird + // Standard: 'public' (relativ zu 'root') + publicDir: 'public', + + build: { + // Wohin kompilierte Dateien gelegt werden sollen (relativ zu 'root') + outDir: '../www/assets', + + // Ausgabeverzeichnis vor dem Build leeren? + // Nützlich, um alte Dateien von früheren Builds zu entfernen + emptyOutDir: true, + + // Unterverzeichnis innerhalb von outDir für generierte Chunks und Assets + // Dies hilft, die Ausgabestruktur zu organisieren + assetsDir: 'static', + + rollupOptions: { + // Einstiegspunkt(e) - kann eine einzelne Datei oder ein Array von Dateien sein + // Jeder Einstiegspunkt wird zu einem separaten Bundle + input: [ + 'app.js', // Hauptanwendung + 'admin.js', // Admin-Panel + ], + }, + }, + + server: { + // Host, an den der Dev-Server gebunden werden soll + // Verwenden Sie '0.0.0.0', um im Netzwerk sichtbar zu sein + host: 'localhost', + + // Port für den Dev-Server + port: 5173, + + // CORS-Konfiguration für Cross-Origin-Anfragen + cors: { + origin: 'http://myapp.local', + }, + }, + + css: { + // CSS-Sourcemaps in der Entwicklung aktivieren + devSourcemap: true, + }, + + plugins: [ + nette(), + ], +}); +``` + +Das war's! Sie haben jetzt ein modernes Build-System, das in Nette Assets integriert ist. diff --git a/assets/el/@home.texy b/assets/el/@home.texy new file mode 100644 index 0000000000..6fef2aa5ca --- /dev/null +++ b/assets/el/@home.texy @@ -0,0 +1,432 @@ +Nette Assets +************ + +
    + +Έχετε κουραστεί να διαχειρίζεστε χειροκίνητα στατικά αρχεία στις εφαρμογές ιστού σας; Ξεχάστε την ενσωμάτωση σκληρών διαδρομών, την αντιμετώπιση της ακύρωσης της κρυφής μνήμης ή την ανησυχία για την έκδοση αρχείων. Το Nette Assets μεταμορφώνει τον τρόπο που εργάζεστε με εικόνες, φύλλα στυλ, σενάρια και άλλους στατικούς πόρους. + +- **Έξυπνη έκδοση** διασφαλίζει ότι τα προγράμματα περιήγησης φορτώνουν πάντα τα πιο πρόσφατα αρχεία +- **Αυτόματη ανίχνευση** τύπων αρχείων και διαστάσεων +- **Απρόσκοπτη ενσωμάτωση Latte** με διαισθητικές ετικέτες +- **Ευέλικτη αρχιτεκτονική** που υποστηρίζει συστήματα αρχείων, CDN και Vite +- **Lazy loading** για βέλτιστη απόδοση + +
    + + +Γιατί Nette Assets; +=================== + +Η εργασία με στατικά αρχεία συχνά σημαίνει επαναλαμβανόμενο κώδικα επιρρεπή σε σφάλματα. Κατασκευάζετε χειροκίνητα διευθύνσεις URL, προσθέτετε παραμέτρους έκδοσης για την εκκαθάριση της κρυφής μνήμης και χειρίζεστε διαφορετικούς τύπους αρχείων με διαφορετικό τρόπο. Αυτό οδηγεί σε κώδικα όπως: + +```html +Logo + +``` + +Με το Nette Assets, όλη αυτή η πολυπλοκότητα εξαφανίζεται: + +```latte +{* Everything automated - URL, versioning, dimensions *} + + + +{* Or just *} +{asset 'css/style.css'} +``` + +Αυτό είναι όλο! Η βιβλιοθήκη αυτόματα: +- Προσθέτει παραμέτρους έκδοσης με βάση την ώρα τροποποίησης του αρχείου +- Ανιχνεύει τις διαστάσεις της εικόνας και τις συμπεριλαμβάνει στο HTML +- Δημιουργεί το σωστό στοιχείο HTML για κάθε τύπο αρχείου +- Χειρίζεται τόσο το περιβάλλον ανάπτυξης όσο και το περιβάλλον παραγωγής + + +Εγκατάσταση +=========== + +Εγκαταστήστε το Nette Assets χρησιμοποιώντας το [Composer|best-practices:composer]: + +```shell +composer require nette/assets +``` + +Απαιτεί PHP 8.1 ή νεότερο και λειτουργεί τέλεια με το Nette Framework, αλλά μπορεί επίσης να χρησιμοποιηθεί αυτόνομα. + + +Πρώτα Βήματα +============ + +Το Nette Assets λειτουργεί άμεσα χωρίς καμία ρύθμιση. Τοποθετήστε τα στατικά σας αρχεία στον κατάλογο `www/assets/` και αρχίστε να τα χρησιμοποιείτε: + +```latte +{* Display an image with automatic dimensions *} +{asset 'logo.png'} + +{* Include a stylesheet with versioning *} +{asset 'style.css'} + +{* Load a JavaScript module *} +{asset 'app.js'} +``` + +Για περισσότερο έλεγχο στο παραγόμενο HTML, χρησιμοποιήστε το χαρακτηριστικό `n:asset` ή τη συνάρτηση `asset()`. + + +Πώς Λειτουργεί +============== + +Το Nette Assets βασίζεται σε τρεις βασικές έννοιες που το καθιστούν ισχυρό αλλά απλό στη χρήση: + + +Assets - Τα Αρχεία σας Έγιναν Έξυπνα +------------------------------------ + +Ένα **asset** αντιπροσωπεύει οποιοδήποτε στατικό αρχείο στην εφαρμογή σας. Κάθε αρχείο γίνεται ένα αντικείμενο με χρήσιμες ιδιότητες μόνο για ανάγνωση: + +```php +$image = $assets->getAsset('photo.jpg'); +echo $image->url; // '/assets/photo.jpg?v=1699123456' +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' +``` + +Διαφορετικοί τύποι αρχείων παρέχουν διαφορετικές ιδιότητες: +- **Εικόνες**: πλάτος, ύψος, εναλλακτικό κείμενο, lazy loading +- **Σενάρια**: τύπος ενότητας (module), hashes ακεραιότητας, crossorigin +- **Φύλλα στυλ**: media queries, ακεραιότητα +- **Ήχος/Βίντεο**: διάρκεια, διαστάσεις +- **Γραμματοσειρές**: σωστή προφόρτωση με CORS + +Η βιβλιοθήκη ανιχνεύει αυτόματα τους τύπους αρχείων και δημιουργεί την κατάλληλη κλάση asset. + + +Mappers - Από Πού Προέρχονται τα Αρχεία +--------------------------------------- + +Ένας **mapper** γνωρίζει πώς να βρίσκει αρχεία και να δημιουργεί διευθύνσεις URL για αυτά. Μπορείτε να έχετε πολλούς mappers για διαφορετικούς σκοπούς - τοπικά αρχεία, CDN, αποθήκευση στο cloud ή εργαλεία δημιουργίας (το καθένα από αυτά έχει ένα όνομα). Ο ενσωματωμένος `FilesystemMapper` χειρίζεται τα τοπικά αρχεία, ενώ ο `ViteMapper` ενσωματώνεται με σύγχρονα εργαλεία δημιουργίας. + +Οι mappers ορίζονται στην [Διαμόρφωση |Configuration]. + + +Registry - Η Κύρια Διεπαφή σας +------------------------------ + +Το **registry** διαχειρίζεται όλους τους mappers και παρέχει το κύριο API: + +```php +// Inject the registry in your service +public function __construct( + private Nette\Assets\Registry $assets +) {} + +// Get assets from different mappers +$logo = $this->assets->getAsset('images:logo.png'); // 'image' mapper +$app = $this->assets->getAsset('app:main.js'); // 'app' mapper +$style = $this->assets->getAsset('style.css'); // uses default mapper +``` + +Το registry επιλέγει αυτόματα τον σωστό mapper και αποθηκεύει τα αποτελέσματα στην κρυφή μνήμη για καλύτερη απόδοση. + + +Εργασία με Assets σε PHP +======================== + +Το Registry παρέχει δύο μεθόδους για την ανάκτηση assets: + +```php +// Throws Nette\Assets\AssetNotFoundException if file doesn't exist +$logo = $assets->getAsset('logo.png'); + +// Returns null if file doesn't exist +$banner = $assets->tryGetAsset('banner.jpg'); +if ($banner) { + echo $banner->url; +} +``` + + +Καθορισμός Mappers +------------------ + +Μπορείτε να επιλέξετε ρητά ποιον mapper θα χρησιμοποιήσετε: + +```php +// Use default mapper +$file = $assets->getAsset('document.pdf'); + +// Use specific mapper with prefix +$image = $assets->getAsset('images:photo.jpg'); + +// Use specific mapper with array syntax +$script = $assets->getAsset(['scripts', 'app.js']); +``` + + +Ιδιότητες και Τύποι Asset +------------------------- + +Κάθε τύπος asset παρέχει σχετικές ιδιότητες μόνο για ανάγνωση: + +```php +// Image properties +$image = $assets->getAsset('photo.jpg'); +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' + +// Script properties +$script = $assets->getAsset('app.js'); +echo $script->type; // 'module' or null + +// Audio properties +$audio = $assets->getAsset('song.mp3'); +echo $audio->duration; // duration in seconds + +// All assets can be cast to string (returns URL) +$url = (string) $assets->getAsset('document.pdf'); +``` + +.[note] +Ιδιότητες όπως διαστάσεις ή διάρκεια φορτώνονται με lazy loading μόνο όταν προσπελαστούν, διατηρώντας τη βιβλιοθήκη γρήγορη. + + +Χρήση Assets σε Πρότυπα Latte +============================= + +Το Nette Assets παρέχει διαισθητική ενσωμάτωση [Latte|latte:] με ετικέτες και συναρτήσεις. + + +`{asset}` +--------- + +Η ετικέτα `{asset}` αποδίδει πλήρη στοιχεία HTML: + +```latte +{* Renders: *} +{asset 'hero.jpg'} + +{* Renders: *} +{asset 'app.js'} + +{* Renders: *} +{asset 'style.css'} +``` + +Η ετικέτα αυτόματα: +- Ανιχνεύει τον τύπο asset και δημιουργεί το κατάλληλο HTML +- Περιλαμβάνει έκδοση για την εκκαθάριση της κρυφής μνήμης +- Προσθέτει διαστάσεις για εικόνες +- Ορίζει τα σωστά χαρακτηριστικά (type, media, κ.λπ.) + +Όταν χρησιμοποιείται μέσα σε χαρακτηριστικά HTML, εξάγει μόνο τη διεύθυνση URL: + +```latte +
    + +``` + + +`n:asset` +--------- + +Για πλήρη έλεγχο των χαρακτηριστικών HTML: + +```latte +{* The n:asset attribute fills in src, dimensions, etc. *} +Product + +{* Works with any relevant element *} + + + +``` + +Χρησιμοποιήστε μεταβλητές και mappers: + +```latte +{* Variables work naturally *} + + +{* Specify mapper with curly brackets *} + + +{* Specify mapper with array notation *} + +``` + + +`asset()` +--------- + +Για μέγιστη ευελιξία, χρησιμοποιήστε τη συνάρτηση `asset()`: + +```latte +{var $logo = asset('logo.png')} +width} height={$logo->height}> + +{* Or directly *} +Logo +``` + + +Προαιρετικά Assets +------------------ + +Χειριστείτε τα ελλείποντα assets με χάρη με `{asset?}`, `n:asset?` και `tryAsset()`: + +```latte +{* Optional tag - renders nothing if asset missing *} +{asset? 'optional-banner.jpg'} + +{* Optional attribute - skips if asset missing *} +Avatar + +{* With fallback *} +{var $avatar = tryAsset('user-avatar.jpg') ?? asset('default-avatar.jpg')} +Avatar +``` + + +`{preload}` +----------- + +Βελτιώστε την απόδοση φόρτωσης σελίδας: + +```latte +{* In your section *} +{preload 'critical.css'} +{preload 'important-font.woff2'} +{preload 'hero-image.jpg'} +``` + +Δημιουργεί κατάλληλους συνδέσμους προφόρτωσης: + +```html + + + +``` + + +Προηγμένες Λειτουργίες +====================== + + +Αυτόματη Ανίχνευση Επέκτασης +---------------------------- + +Χειριστείτε αυτόματα πολλαπλές μορφές: + +```neon +assets: + mapping: + images: + path: img + extension: [webp, jpg, png] # Try in order +``` + +Τώρα μπορείτε να ζητήσετε χωρίς επέκταση: + +```latte +{* Finds logo.webp, logo.jpg, or logo.png automatically *} +{asset 'images:logo'} +``` + +Ιδανικό για προοδευτική βελτίωση με σύγχρονες μορφές. + + +Έξυπνη Έκδοση +------------- + +Τα αρχεία εκδίδονται αυτόματα με βάση την ώρα τροποποίησης: + +```latte +{asset 'style.css'} +{* Output: *} +``` + +Όταν ενημερώνετε το αρχείο, η χρονοσφραγίδα αλλάζει, αναγκάζοντας την ανανέωση της κρυφής μνήμης του προγράμματος περιήγησης. + +Έλεγχος έκδοσης ανά asset: + +```php +// Disable versioning for specific asset +$asset = $assets->getAsset('style.css', ['version' => false]); + +// In Latte +{asset 'style.css', version: false} +``` + + +Assets Γραμματοσειρών +--------------------- + +Οι γραμματοσειρές λαμβάνουν ειδική μεταχείριση με σωστό CORS: + +```latte +{* Proper preload with crossorigin *} +{preload 'fonts:OpenSans-Regular.woff2'} + +{* Use in CSS *} + +``` + + +Προσαρμοσμένοι Mappers +====================== + +Δημιουργήστε προσαρμοσμένους mappers για ειδικές ανάλογες ανάγκες όπως αποθήκευση στο cloud ή δυναμική δημιουργία: + +```php +use Nette\Assets\Mapper; +use Nette\Assets\Asset; +use Nette\Assets\Helpers; + +class CloudStorageMapper implements Mapper +{ + public function __construct( + private CloudClient $client, + private string $bucket, + ) {} + + public function getAsset(string $reference, array $options = []): Asset + { + if (!$this->client->exists($this->bucket, $reference)) { + throw new Nette\Assets\AssetNotFoundException("Asset '$reference' not found"); + } + + $url = $this->client->getPublicUrl($this->bucket, $reference); + return Helpers::createAssetFromUrl($url); + } +} +``` + +Καταχωρήστε στη διαμόρφωση: + +```neon +assets: + mapping: + cloud: CloudStorageMapper(@cloudClient, 'my-bucket') +``` + +Χρησιμοποιήστε όπως οποιονδήποτε άλλο mapper: + +```latte +{asset 'cloud:user-uploads/photo.jpg'} +``` + +Η μέθοδος `Helpers::createAssetFromUrl()` δημιουργεί αυτόματα τον σωστό τύπο asset με βάση την επέκταση αρχείου. + + +Περαιτέρω ανάγνωση +================== + +- [Nette Assets: για τα πάντα, από εικόνες έως Vite |https://blog.nette.org/en/introducing-nette-assets] diff --git a/assets/el/@left-menu.texy b/assets/el/@left-menu.texy new file mode 100644 index 0000000000..4e74c3d9a0 --- /dev/null +++ b/assets/el/@left-menu.texy @@ -0,0 +1,5 @@ +Nette Assets +************ +- [Ξεκινώντας |@home] +- [Vite |vite] +- [Διαμόρφωση |Configuration] diff --git a/assets/el/@meta.texy b/assets/el/@meta.texy new file mode 100644 index 0000000000..88e29852c7 --- /dev/null +++ b/assets/el/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Τεκμηρίωση}} diff --git a/assets/el/configuration.texy b/assets/el/configuration.texy new file mode 100644 index 0000000000..8ec9f2944c --- /dev/null +++ b/assets/el/configuration.texy @@ -0,0 +1,188 @@ +Διαμόρφωση Assets +***************** + +.[perex] +Επισκόπηση των επιλογών διαμόρφωσης για το Nette Assets. + + +```neon +assets: + # base path for resolving relative mapper paths + basePath: ... # (string) defaults to %wwwDir% + + # base URL for resolving relative mapper URLs + baseUrl: ... # (string) defaults to %baseUrl% + + # enable asset versioning globally? + versioning: ... # (bool) defaults to true + + # defines asset mappers + mapping: ... # (array) defaults to path 'assets' +``` + +Το `basePath` ορίζει τον προεπιλεγμένο κατάλογο συστήματος αρχείων για την επίλυση σχετικών διαδρομών σε mappers. Από προεπιλογή, χρησιμοποιεί τον κατάλογο web (`%wwwDir%`). + +Το `baseUrl` ορίζει το προεπιλεγμένο πρόθεμα URL για την επίλυση σχετικών URL σε mappers. Από προεπιλογή, χρησιμοποιεί το root URL (`%baseUrl%`). + +Η επιλογή `versioning` ελέγχει καθολικά εάν προστίθενται παράμετροι έκδοσης στις διευθύνσεις URL των assets για την εκκαθάριση της κρυφής μνήμης. Οι μεμονωμένοι mappers μπορούν να παρακάμψουν αυτήν τη ρύθμιση. + + +Mappers +------- + +Οι Mappers μπορούν να διαμορφωθούν με τρεις τρόπους: απλή σύνταξη συμβολοσειράς, λεπτομερής σύνταξη πίνακα ή ως αναφορά σε μια υπηρεσία. + +Ο απλούστερος τρόπος για να ορίσετε έναν mapper: + +```neon +assets: + mapping: + default: assets # Creates filesystem mapper for %wwwDir%/assets/ + images: img # Creates filesystem mapper for %wwwDir%/img/ + scripts: js # Creates filesystem mapper for %wwwDir%/js/ +``` + +Κάθε mapper δημιουργεί έναν `FilesystemMapper` που: +- Αναζητά αρχεία στο `%wwwDir%/` +- Δημιουργεί διευθύνσεις URL όπως `%baseUrl%/` +- Κληρονομεί την καθολική ρύθμιση έκδοσης + + +Για περισσότερο έλεγχο, χρησιμοποιήστε τη λεπτομερή σύνταξη: + +```neon +assets: + mapping: + images: + # directory where files are stored + path: ... # (string) optional, defaults to '' + + # URL prefix for generated links + url: ... # (string) optional, defaults to path + + # enable versioning for this mapper? + versioning: ... # (bool) optional, inherits global setting + + # auto-add extension(s) when searching for files + extension: ... # (string|array) optional, defaults to null +``` + +Κατανόηση του τρόπου επίλυσης των τιμών διαμόρφωσης: + +Επίλυση Διαδρομής: + - Οι σχετικές διαδρομές επιλύονται από το `basePath` (ή `%wwwDir%` εάν το `basePath` δεν έχει οριστεί) + - Οι απόλυτες διαδρομές χρησιμοποιούνται ως έχουν + +Επίλυση URL: + - Οι σχετικές διευθύνσεις URL επιλύονται από το `baseUrl` (ή `%baseUrl%` εάν το `baseUrl` δεν έχει οριστεί) + - Οι απόλυτες διευθύνσεις URL (με σχήμα ή `//`) χρησιμοποιούνται ως έχουν + - Εάν το `url` δεν έχει καθοριστεί, χρησιμοποιεί την τιμή του `path` + + +```neon +assets: + basePath: /var/www/project/www + baseUrl: https://example.com/assets + + mapping: + # Relative path and URL + images: + path: img # Resolved to: /var/www/project/www/img + url: images # Resolved to: https://example.com/assets/images + + # Absolute path and URL + uploads: + path: /var/shared/uploads # Used as-is: /var/shared/uploads + url: https://cdn.example.com # Used as-is: https://cdn.example.com + + # Only path specified + styles: + path: css # Path: /var/www/project/www/css + # URL: https://example.com/assets/css +``` + + +Προσαρμοσμένοι Mappers +---------------------- + +Για προσαρμοσμένους mappers, αναφέρετε ή ορίστε μια υπηρεσία: + +```neon +services: + s3mapper: App\Assets\S3Mapper(%s3.bucket%) + +assets: + mapping: + cloud: @s3mapper + database: App\Assets\DatabaseMapper(@database.connection) +``` + + +Vite Mapper +----------- + +Ο Vite mapper απαιτεί μόνο να προσθέσετε `type: vite`. Αυτή είναι μια πλήρης λίστα επιλογών διαμόρφωσης: + +```neon +assets: + mapping: + default: + # mapper type (required for Vite) + type: vite # (string) required, must be 'vite' + + # Vite build output directory + path: ... # (string) optional, defaults to '' + + # URL prefix for built assets + url: ... # (string) optional, defaults to path + + # location of Vite manifest file + manifest: ... # (string) optional, defaults to /.vite/manifest.json + + # Vite dev server configuration + devServer: ... # (bool|string) optional, defaults to true + + # versioning for public directory files + versioning: ... # (bool) optional, inherits global setting + + # auto-extension for public directory files + extension: ... # (string|array) optional, defaults to null +``` + +Η επιλογή `devServer` ελέγχει τον τρόπο φόρτωσης των assets κατά την ανάπτυξη: + +- `true` (προεπιλογή) - Ανιχνεύει αυτόματα τον Vite dev server στον τρέχοντα host και port. Εάν ο dev server εκτελείται **και η εφαρμογή σας είναι σε λειτουργία debug**, τα assets φορτώνονται από αυτόν με υποστήριξη hot module replacement. Εάν ο dev server δεν εκτελείται, τα assets φορτώνονται από τα δημιουργημένα αρχεία στον δημόσιο κατάλογο. +- `false` - Απενεργοποιεί πλήρως την ενσωμάτωση του dev server. Τα assets φορτώνονται πάντα από τα δημιουργημένα αρχεία. +- Προσαρμοσμένη διεύθυνση URL (π.χ., `https://localhost:5173`) - Καθορίστε χειροκίνητα τη διεύθυνση URL του dev server συμπεριλαμβανομένου του πρωτοκόλλου και του port. Χρήσιμο όταν ο dev server εκτελείται σε διαφορετικό host ή port. + +Οι επιλογές `versioning` και `extension` ισχύουν μόνο για αρχεία στον δημόσιο κατάλογο του Vite που δεν επεξεργάζονται από το Vite. + + +Μη Αυτόματη Διαμόρφωση +---------------------- + +Όταν δεν χρησιμοποιείτε το Nette DI, διαμορφώστε τους mappers χειροκίνητα: + +```php +use Nette\Assets\Registry; +use Nette\Assets\FilesystemMapper; +use Nette\Assets\ViteMapper; + +$registry = new Registry; + +// Add filesystem mapper +$registry->addMapper('images', new FilesystemMapper( + baseUrl: 'https://example.com/img', + basePath: __DIR__ . '/www/img', + extensions: ['webp', 'jpg', 'png'], + versioning: true, +)); + +// Add Vite mapper +$registry->addMapper('app', new ViteMapper( + baseUrl: '/build', + basePath: __DIR__ . '/www/build', + manifestPath: __DIR__ . '/www/build/.vite/manifest.json', + devServer: 'https://localhost:5173', +)); +``` diff --git a/assets/el/vite.texy b/assets/el/vite.texy new file mode 100644 index 0000000000..d526f68d5e --- /dev/null +++ b/assets/el/vite.texy @@ -0,0 +1,508 @@ +Ενσωμάτωση Vite +*************** + +
    + +Οι σύγχρονες εφαρμογές JavaScript απαιτούν εξελιγμένα εργαλεία δημιουργίας. Το Nette Assets παρέχει ενσωμάτωση πρώτης κατηγορίας με το [Vite |https://vitejs.dev/], το εργαλείο δημιουργίας frontend επόμενης γενιάς. Αποκτήστε αστραπιαία ανάπτυξη με Hot Module Replacement (HMR) και βελτιστοποιημένες εκδόσεις παραγωγής χωρίς προβλήματα διαμόρφωσης. + +- **Μηδενική διαμόρφωση** - αυτόματη γέφυρα μεταξύ Vite και προτύπων PHP +- **Πλήρης διαχείριση εξαρτήσεων** - μία ετικέτα χειρίζεται όλα τα assets +- **Hot Module Replacement** - άμεσες ενημερώσεις JavaScript και CSS +- **Βελτιστοποιημένες εκδόσεις παραγωγής** - code splitting και tree shaking + +
    + + +Το Nette Assets ενσωματώνεται απρόσκοπτα με το Vite, οπότε έχετε όλα αυτά τα οφέλη ενώ γράφετε τα πρότυπά σας ως συνήθως. + + +Ρύθμιση του Vite +================ + +Ας ρυθμίσουμε το Vite βήμα προς βήμα. Μην ανησυχείτε αν είστε νέοι στα εργαλεία δημιουργίας - θα εξηγήσουμε τα πάντα! + + +Βήμα 1: Εγκατάσταση του Vite +---------------------------- + +Πρώτα, εγκαταστήστε το Vite και το Nette plugin στο έργο σας: + +```shell +npm install -D vite @nette/vite-plugin +``` + +Αυτό εγκαθιστά το Vite και ένα ειδικό plugin που βοηθά το Vite να λειτουργεί τέλεια με το Nette. + + +Βήμα 2: Δομή Έργου +------------------ + +Η τυπική προσέγγιση είναι να τοποθετήσετε τα αρχεία asset πηγής σε έναν φάκελο `assets/` στον ριζικό κατάλογο του έργου σας και τις μεταγλωττισμένες εκδόσεις στο `www/assets/`: + +/--pre +web-project/ +├── assets/ ← αρχεία πηγής (SCSS, TypeScript, εικόνες πηγής) +│ ├── public/ ← στατικά αρχεία (αντιγράφονται ως έχουν) +│ │ └── favicon.ico +│ ├── images/ +│ │ └── logo.png +│ ├── app.js ← κύριο σημείο εισόδου +│ └── style.css ← τα στυλ σας +└── www/ ← δημόσιος κατάλογος (document root) + ├── assets/ ← τα μεταγλωττισμένα αρχεία θα πάνε εδώ + └── index.php +\-- + +Ο φάκελος `assets/` περιέχει τα αρχεία πηγής σας - τον κώδικα που γράφετε. Το Vite θα επεξεργαστεί αυτά τα αρχεία και θα τοποθετήσει τις μεταγλωττισμένες εκδόσεις στο `www/assets/`. + + +Βήμα 3: Διαμόρφωση του Vite +--------------------------- + +Δημιουργήστε ένα αρχείο `vite.config.ts` στον ριζικό κατάλογο του έργου σας. Αυτό το αρχείο λέει στο Vite πού να βρει τα αρχεία πηγής σας και πού να τοποθετήσει τα μεταγλωττισμένα. + +Το Nette Vite plugin έρχεται με έξυπνες προεπιλογές που κάνουν τη διαμόρφωση απλή. Υποθέτει ότι τα αρχεία πηγής frontend βρίσκονται στον κατάλογο `assets/` (επιλογή `root`) και τα μεταγλωττισμένα αρχεία πηγαίνουν στο `www/assets/` (επιλογή `outDir`). Χρειάζεται μόνο να καθορίσετε το [σημείο εισόδου|#Entry Points]: + +```js +import { defineConfig } from 'vite'; +import nette from '@nette/vite-plugin'; + +export default defineConfig({ + plugins: [ + nette({ + entry: 'app.js', + }), + ], +}); +``` + +Εάν θέλετε να καθορίσετε άλλο όνομα καταλόγου για να δημιουργήσετε τα assets σας, θα χρειαστεί να αλλάξετε μερικές επιλογές: + +```js +export default defineConfig({ + root: 'assets', // root directory of source assets + + build: { + outDir: '../www/assets', // where compiled files go + }, + + // ... other config ... +}); +``` + +.[note] +Η διαδρομή `outDir` θεωρείται σχετική με το `root`, γι' αυτό υπάρχει το `../` στην αρχή. + + +Βήμα 4: Διαμόρφωση του Nette +---------------------------- + +Ενημερώστε το Nette Assets για το Vite στο `common.neon` σας: + +```neon +assets: + mapping: + default: + type: vite # tells Nette to use the ViteMapper + path: assets +``` + + +Βήμα 5: Προσθήκη σεναρίων +------------------------- + +Προσθέστε αυτά τα σενάρια στο `package.json` σας: + +```json +{ + "scripts": { + "dev": "vite", + "build": "vite build" + } +} +``` + +Τώρα μπορείτε: +- `npm run dev` - εκκίνηση του development server με hot reloading +- `npm run build` - δημιουργία βελτιστοποιημένων αρχείων παραγωγής + + +Σημεία Εισόδου +============== + +Ένα **σημείο εισόδου** είναι το κύριο αρχείο από όπου ξεκινά η εφαρμογή σας. Από αυτό το αρχείο, εισάγετε άλλα αρχεία (CSS, μονάδες JavaScript, εικόνες), δημιουργώντας ένα δέντρο εξαρτήσεων. Το Vite ακολουθεί αυτές τις εισαγωγές και ομαδοποιεί τα πάντα μαζί. + +Παράδειγμα σημείου εισόδου `assets/app.js`: + +```js +// Import styles +import './style.css' + +// Import JavaScript modules +import netteForms from 'nette-forms'; +import naja from 'naja'; + +// Initialize your application +netteForms.initOnLoad(); +naja.initialize(); +``` + +Στο πρότυπο μπορείτε να εισάγετε ένα σημείο εισόδου ως εξής: + +```latte +{asset 'app.js'} +``` + +Το Nette Assets δημιουργεί αυτόματα όλες τις απαραίτητες ετικέτες HTML - JavaScript, CSS και οποιεσδήποτε άλλες εξαρτήσεις. + + +Πολλαπλά Σημεία Εισόδου +----------------------- + +Μεγαλύτερες εφαρμογές συχνά χρειάζονται ξεχωριστά σημεία εισόδου: + +```js +export default defineConfig({ + plugins: [ + nette({ + entry: [ + 'app.js', // public pages + 'admin.js', // admin panel + ], + }), + ], +}); +``` + +Χρησιμοποιήστε τα σε διαφορετικά πρότυπα: + +```latte +{* In public pages *} +{asset 'app.js'} + +{* In admin panel *} +{asset 'admin.js'} +``` + + +Σημαντικό: Αρχεία Πηγής έναντι Μεταγλωττισμένων Αρχείων +------------------------------------------------------- + +Είναι κρίσιμο να κατανοήσετε ότι στην παραγωγή μπορείτε να φορτώσετε μόνο: + +1. **Σημεία εισόδου** που ορίζονται στο `entry` +2. **Αρχεία από τον κατάλογο `assets/public/`** + +Δεν μπορείτε να φορτώσετε χρησιμοποιώντας `{asset}` αυθαίρετα αρχεία από το `assets/` - μόνο assets που αναφέρονται από αρχεία JavaScript ή CSS. Εάν το αρχείο σας δεν αναφέρεται πουθενά, δεν θα μεταγλωττιστεί. Εάν θέλετε να κάνετε το Vite να γνωρίζει άλλα assets, μπορείτε να τα μετακινήσετε στον [δημόσιο φάκελο|#public folder]. + +Λάβετε υπόψη ότι από προεπιλογή, το Vite θα ενσωματώσει όλα τα assets μικρότερα από 4KB, οπότε δεν θα μπορείτε να αναφέρετε αυτά τα αρχεία απευθείας. (Δείτε την [τεκμηρίωση του Vite |https://vite.dev/guide/assets.html]). + +```latte +{* ✓ This works - it's an entry point *} +{asset 'app.js'} + +{* ✓ This works - it's in assets/public/ *} +{asset 'favicon.ico'} + +{* ✗ This won't work - random file in assets/ *} +{asset 'components/button.js'} +``` + + +Λειτουργία Ανάπτυξης +==================== + +Η λειτουργία ανάπτυξης είναι εντελώς προαιρετική, αλλά παρέχει σημαντικά οφέλη όταν είναι ενεργοποιημένη. Το κύριο πλεονέκτημα είναι το **Hot Module Replacement (HMR)** - δείτε τις αλλαγές άμεσα χωρίς να χάσετε την κατάσταση της εφαρμογής, κάνοντας την εμπειρία ανάπτυξης πολύ πιο ομαλή και ταχύτερη. + +Το Vite είναι ένα σύγχρονο εργαλείο δημιουργίας που κάνει την ανάπτυξη απίστευτα γρήγορη. Σε αντίθεση με τους παραδοσιακούς bundlers, το Vite εξυπηρετεί τον κώδικά σας απευθείας στο πρόγραμμα περιήγησης κατά την ανάπτυξη, πράγμα που σημαίνει άμεση εκκίνηση του server ανεξάρτητα από το μέγεθος του έργου σας και αστραπιαίες ενημερώσεις. + + +Εκκίνηση του Development Server +------------------------------- + +Εκτελέστε τον development server: + +```shell +npm run dev +``` + +Θα δείτε: + +``` + ➜ Local: http://localhost:5173/ + ➜ Network: use --host to expose +``` + +Κρατήστε αυτό το τερματικό ανοιχτό κατά την ανάπτυξη. + +Το Nette Vite plugin ανιχνεύει αυτόματα όταν: +1. Ο Vite dev server εκτελείται +2. Η εφαρμογή Nette σας είναι σε λειτουργία debug + +Όταν πληρούνται και οι δύο προϋποθέσεις, το Nette Assets φορτώνει αρχεία από τον Vite dev server αντί από τον μεταγλωττισμένο κατάλογο: + +```latte +{asset 'app.js'} +{* In development: *} +{* In production: *} +``` + +Δεν απαιτείται διαμόρφωση - απλά λειτουργεί! + + +Εργασία σε Διαφορετικούς Τομείς (Domains) +----------------------------------------- + +Εάν ο development server σας εκτελείται σε κάτι άλλο εκτός από το `localhost` (όπως `myapp.local`), ενδέχεται να αντιμετωπίσετε προβλήματα CORS (Cross-Origin Resource Sharing). Το CORS είναι ένα χαρακτηριστικό ασφαλείας στα προγράμματα περιήγησης ιστού που μπλοκάρει τις αιτήσεις μεταξύ διαφορετικών τομέων από προεπιλογή. Όταν η εφαρμογή PHP σας εκτελείται στο `myapp.local` αλλά το Vite εκτελείται στο `localhost:5173`, το πρόγραμμα περιήγησης τα βλέπει ως διαφορετικούς τομείς και μπλοκάρει τις αιτήσεις. + +Έχετε δύο επιλογές για να το λύσετε: + +**Επιλογή 1: Διαμόρφωση CORS** + +Η απλούστερη λύση είναι να επιτρέψετε αιτήσεις cross-origin από την εφαρμογή PHP σας: + +```js +export default defineConfig({ + // ... other config ... + + server: { + cors: { + origin: 'http://myapp.local', // your PHP app URL + }, + }, +}); +``` +**Επιλογή 2: Εκτελέστε το Vite στον τομέα σας** + +Η άλλη λύση είναι να κάνετε το Vite να εκτελείται στον ίδιο τομέα με την εφαρμογή PHP σας. + +```js +export default defineConfig({ + // ... other config ... + + server: { + host: 'myapp.local', // same as your PHP app + }, +}); +``` + +Πράγματι, ακόμη και σε αυτή την περίπτωση, πρέπει να διαμορφώσετε το CORS επειδή ο dev server εκτελείται στον ίδιο hostname αλλά σε διαφορετικό port. Ωστόσο, σε αυτή την περίπτωση, το CORS διαμορφώνεται αυτόματα από το Nette Vite plugin. + + +Ανάπτυξη HTTPS +-------------- + +Εάν αναπτύσσετε σε HTTPS, χρειάζεστε πιστοποιητικά για τον Vite development server σας. Ο ευκολότερος τρόπος είναι να χρησιμοποιήσετε ένα plugin που δημιουργεί αυτόματα πιστοποιητικά: + +```shell +npm install -D vite-plugin-mkcert +``` + +Δείτε πώς να το διαμορφώσετε στο `vite.config.ts`: + +```js +import mkcert from 'vite-plugin-mkcert'; + +export default defineConfig({ + // ... other config ... + + plugins: [ + mkcert(), // generates certificates automatically and enables https + nette(), + ], +}); +``` + +Σημειώστε ότι εάν χρησιμοποιείτε τη διαμόρφωση CORS (Επιλογή 1 από παραπάνω), πρέπει να ενημερώσετε τη διεύθυνση URL προέλευσης για να χρησιμοποιήσετε `https://` αντί για `http://`. + + +Εκδόσεις Παραγωγής +================== + +Δημιουργήστε βελτιστοποιημένα αρχεία παραγωγής: + +```shell +npm run build +``` + +Το Vite θα: +- Συμπιέσει (minify) όλα τα JavaScript και CSS +- Χωρίσει τον κώδικα σε βέλτιστα τμήματα (chunks) +- Δημιουργήσει ονόματα αρχείων με hash για cache-busting +- Δημιουργήσει ένα αρχείο manifest για το Nette Assets + +Παράδειγμα εξόδου: + +``` +www/assets/ +├── app-4f3a2b1c.js # Your main JavaScript (minified) +├── app-7d8e9f2a.css # Extracted CSS (minified) +├── vendor-8c4b5e6d.js # Shared dependencies +└── .vite/ + └── manifest.json # Mapping for Nette Assets +``` + +Τα ονόματα αρχείων με hash διασφαλίζουν ότι τα προγράμματα περιήγησης φορτώνουν πάντα την τελευταία έκδοση. + + +Δημόσιος Φάκελος +================ + +Τα αρχεία στον κατάλογο `assets/public/` αντιγράφονται στην έξοδο χωρίς επεξεργασία: + +``` +assets/ +├── public/ +│ ├── favicon.ico +│ ├── robots.txt +│ └── images/ +│ └── og-image.jpg +├── app.js +└── style.css +``` + +Αναφερθείτε σε αυτά κανονικά: + +```latte +{* These files are copied as-is *} + + +``` + +Για δημόσια αρχεία, μπορείτε να χρησιμοποιήσετε τις λειτουργίες του FilesystemMapper: + +```neon +assets: + mapping: + default: + type: vite + path: assets + extension: [webp, jpg, png] # Try WebP first + versioning: true # Add cache-busting +``` + +Στη διαμόρφωση `vite.config.ts` μπορείτε να αλλάξετε τον δημόσιο φάκελο χρησιμοποιώντας την επιλογή `publicDir`. + + +Δυναμικές Εισαγωγές +=================== + +Το Vite χωρίζει αυτόματα τον κώδικα για βέλτιστη φόρτωση. Οι δυναμικές εισαγωγές σάς επιτρέπουν να φορτώνετε κώδικα μόνο όταν είναι πραγματικά απαραίτητος, μειώνοντας το αρχικό μέγεθος του bundle: + +```js +// Load heavy components on demand +button.addEventListener('click', async () => { + let { Chart } = await import('./components/chart.js') + new Chart(data) +}) +``` + +Οι δυναμικές εισαγωγές δημιουργούν ξεχωριστά τμήματα (chunks) που φορτώνονται μόνο όταν χρειάζονται. Αυτό ονομάζεται "code splitting" και είναι μία από τις πιο ισχυρές λειτουργίες του Vite. Όταν χρησιμοποιείτε δυναμικές εισαγωγές, το Vite δημιουργεί αυτόματα ξεχωριστά αρχεία JavaScript για κάθε δυναμικά εισαγόμενη ενότητα (module). + +Η ετικέτα `{asset 'app.js'}` **δεν** προφορτώνει αυτόματα αυτά τα δυναμικά τμήματα. Αυτή είναι σκόπιμη συμπεριφορά - δεν θέλουμε να κατεβάσουμε κώδικα που μπορεί να μην χρησιμοποιηθεί ποτέ. Τα τμήματα κατεβάζονται μόνο όταν εκτελείται η δυναμική εισαγωγή. + +Ωστόσο, εάν γνωρίζετε ότι ορισμένες δυναμικές εισαγωγές είναι κρίτιμες και θα χρειαστούν σύντομα, μπορείτε να τις προφορτώσετε: + +```latte +{* Main entry point *} +{asset 'app.js'} + +{* Preload critical dynamic imports *} +{preload 'components/chart.js'} +``` + +Αυτό λέει στο πρόγραμμα περιήγησης να κατεβάσει το στοιχείο του γραφήματος στο παρασκήνιο, ώστε να είναι άμεσα διαθέσιμο όταν χρειαστεί. + + +Υποστήριξη TypeScript +===================== + +Το TypeScript λειτουργεί άμεσα: + +```ts +// assets/main.ts +interface User { + name: string + email: string +} + +export function greetUser(user: User): void { + console.log(`Hello, ${user.name}!`) +} +``` + +Αναφερθείτε στα αρχεία TypeScript κανονικά: + +```latte +{asset 'main.ts'} +``` + +Για πλήρη υποστήριξη TypeScript, εγκαταστήστε το: + +```shell +npm install -D typescript +``` + + +Πρόσθετη Διαμόρφωση Vite +======================== + +Ακολουθούν ορισμένες χρήσιμες επιλογές διαμόρφωσης Vite με λεπτομερείς επεξηγήσεις: + +```js +export default defineConfig({ + // Root directory containing source assets + root: 'assets', + + // Folder whose contents are copied to output directory as-is + // Default: 'public' (relative to 'root') + publicDir: 'public', + + build: { + // Where to put compiled files (relative to 'root') + outDir: '../www/assets', + + // Empty output directory before building? + // Useful to remove old files from previous builds + emptyOutDir: true, + + // Subdirectory within outDir for generated chunks and assets + // This helps organize the output structure + assetsDir: 'static', + + rollupOptions: { + // Entry point(s) - can be a single file or array of files + // Each entry point becomes a separate bundle + input: [ + 'app.js', // main application + 'admin.js', // admin panel + ], + }, + }, + + server: { + // Host to bind the dev server to + // Use '0.0.0.0' to expose to network + host: 'localhost', + + // Port for the dev server + port: 5173, + + // CORS configuration for cross-origin requests + cors: { + origin: 'http://myapp.local', + }, + }, + + css: { + // Enable CSS source maps in development + devSourcemap: true, + }, + + plugins: [ + nette(), + ], +}); +``` + +Αυτό είναι όλο! Έχετε τώρα ένα σύγχρονο σύστημα δημιουργίας ενσωματωμένο με το Nette Assets. diff --git a/assets/en/@home.texy b/assets/en/@home.texy new file mode 100644 index 0000000000..1d95ebe05c --- /dev/null +++ b/assets/en/@home.texy @@ -0,0 +1,432 @@ +Nette Assets +************ + +
    + +Tired of manually managing static files in your web applications? Forget about hardcoding paths, dealing with cache invalidation, or worrying about file versioning. Nette Assets transforms how you work with images, stylesheets, scripts, and other static resources. + +- **Smart versioning** ensures browsers always load the latest files +- **Automatic detection** of file types and dimensions +- **Seamless Latte integration** with intuitive tags +- **Flexible architecture** supporting filesystems, CDNs, and Vite +- **Lazy loading** for optimal performance + +
    + + +Why Nette Assets? +================= + +Working with static files often means repetitive, error-prone code. You manually construct URLs, add version parameters for cache busting, and handle different file types differently. This leads to code like: + +```html +Logo + +``` + +With Nette Assets, all this complexity disappears: + +```latte +{* Everything automated - URL, versioning, dimensions *} + + + +{* Or just *} +{asset 'css/style.css'} +``` + +That's it! The library automatically: +- Adds version parameters based on file modification time +- Detects image dimensions and includes them in the HTML +- Generates the correct HTML element for each file type +- Handles both development and production environments + + +Installation +============ + +Install Nette Assets using [Composer|best-practices:composer]: + +```shell +composer require nette/assets +``` + +It requires PHP 8.1 or higher and works perfectly with Nette Framework, but can also be used standalone. + + +First Steps +=========== + +Nette Assets works out of the box with zero configuration. Place your static files in the `www/assets/` directory and start using them: + +```latte +{* Display an image with automatic dimensions *} +{asset 'logo.png'} + +{* Include a stylesheet with versioning *} +{asset 'style.css'} + +{* Load a JavaScript module *} +{asset 'app.js'} +``` + +For more control over the generated HTML, use the `n:asset` attribute or the `asset()` function. + + +How It Works +============ + +Nette Assets is built around three core concepts that make it powerful yet simple to use: + + +Assets - Your Files Made Smart +------------------------------ + +An **asset** represents any static file in your application. Each file becomes an object with useful readonly properties: + +```php +$image = $assets->getAsset('photo.jpg'); +echo $image->url; // '/assets/photo.jpg?v=1699123456' +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' +``` + +Different file types provide different properties: +- **Images**: width, height, alternative text, lazy loading +- **Scripts**: module type, integrity hashes, crossorigin +- **Stylesheets**: media queries, integrity +- **Audio/Video**: duration, dimensions +- **Fonts**: proper preloading with CORS + +The library automatically detects file types and creates the appropriate asset class. + + +Mappers - Where Files Come From +------------------------------- + +A **mapper** knows how to find files and create URLs for them. You can have multiple mappers for different purposes - local files, CDN, cloud storage, or build tools (each of them has a name). The built-in `FilesystemMapper` handles local files, while `ViteMapper` integrates with modern build tools. + +Mappers are defined in the [configuration]. + + +Registry - Your Main Interface +------------------------------ + +The **registry** manages all mappers and provides the main API: + +```php +// Inject the registry in your service +public function __construct( + private Nette\Assets\Registry $assets +) {} + +// Get assets from different mappers +$logo = $this->assets->getAsset('images:logo.png'); // 'image' mapper +$app = $this->assets->getAsset('app:main.js'); // 'app' mapper +$style = $this->assets->getAsset('style.css'); // uses default mapper +``` + +The registry automatically selects the right mapper and caches results for performance. + + +Working with Assets in PHP +========================== + +The Registry provides two methods for retrieving assets: + +```php +// Throws Nette\Assets\AssetNotFoundException if file doesn't exist +$logo = $assets->getAsset('logo.png'); + +// Returns null if file doesn't exist +$banner = $assets->tryGetAsset('banner.jpg'); +if ($banner) { + echo $banner->url; +} +``` + + +Specifying Mappers +------------------ + +You can explicitly choose which mapper to use: + +```php +// Use default mapper +$file = $assets->getAsset('document.pdf'); + +// Use specific mapper with prefix +$image = $assets->getAsset('images:photo.jpg'); + +// Use specific mapper with array syntax +$script = $assets->getAsset(['scripts', 'app.js']); +``` + + +Asset Properties and Types +-------------------------- + +Each asset type provides relevant readonly properties: + +```php +// Image properties +$image = $assets->getAsset('photo.jpg'); +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' + +// Script properties +$script = $assets->getAsset('app.js'); +echo $script->type; // 'module' or null + +// Audio properties +$audio = $assets->getAsset('song.mp3'); +echo $audio->duration; // duration in seconds + +// All assets can be cast to string (returns URL) +$url = (string) $assets->getAsset('document.pdf'); +``` + +.[note] +Properties like dimensions or duration are loaded lazily only when accessed, keeping the library fast. + + +Using Assets in Latte Templates +=============================== + +Nette Assets provides intuitive [Latte|latte:] integration with tags and functions. + + +`{asset}` +--------- + +The `{asset}` tag renders complete HTML elements: + +```latte +{* Renders: *} +{asset 'hero.jpg'} + +{* Renders: *} +{asset 'app.js'} + +{* Renders: *} +{asset 'style.css'} +``` + +The tag automatically: +- Detects asset type and generates appropriate HTML +- Includes versioning for cache busting +- Adds dimensions for images +- Sets correct attributes (type, media, etc.) + +When used inside HTML attributes, it outputs just the URL: + +```latte +
    + +``` + + +`n:asset` +--------- + +For full control over HTML attributes: + +```latte +{* The n:asset attribute fills in src, dimensions, etc. *} +Product + +{* Works with any relevant element *} + + + +``` + +Use variables and mappers: + +```latte +{* Variables work naturally *} + + +{* Specify mapper with curly brackets *} + + +{* Specify mapper with array notation *} + +``` + + +`asset()` +--------- + +For maximum flexibility, use the `asset()` function: + +```latte +{var $logo = asset('logo.png')} +width} height={$logo->height}> + +{* Or directly *} +Logo +``` + + +Optional Assets +--------------- + +Handle missing assets gracefully with `{asset?}`, `n:asset?` and `tryAsset()`: + +```latte +{* Optional tag - renders nothing if asset missing *} +{asset? 'optional-banner.jpg'} + +{* Optional attribute - skips if asset missing *} +Avatar + +{* With fallback *} +{var $avatar = tryAsset('user-avatar.jpg') ?? asset('default-avatar.jpg')} +Avatar +``` + + +`{preload}` +----------- + +Improve page load performance: + +```latte +{* In your section *} +{preload 'critical.css'} +{preload 'important-font.woff2'} +{preload 'hero-image.jpg'} +``` + +Generates appropriate preload links: + +```html + + + +``` + + +Advanced Features +================= + + +Extension Auto-Detection +------------------------ + +Handle multiple formats automatically: + +```neon +assets: + mapping: + images: + path: img + extension: [webp, jpg, png] # Try in order +``` + +Now you can request without extension: + +```latte +{* Finds logo.webp, logo.jpg, or logo.png automatically *} +{asset 'images:logo'} +``` + +Perfect for progressive enhancement with modern formats. + + +Smart Versioning +---------------- + +Files are automatically versioned based on modification time: + +```latte +{asset 'style.css'} +{* Output: *} +``` + +When you update the file, the timestamp changes, forcing browser cache refresh. + +Control versioning per asset: + +```php +// Disable versioning for specific asset +$asset = $assets->getAsset('style.css', ['version' => false]); + +// In Latte +{asset 'style.css', version: false} +``` + + +Font Assets +----------- + +Fonts get special treatment with proper CORS: + +```latte +{* Proper preload with crossorigin *} +{preload 'fonts:OpenSans-Regular.woff2'} + +{* Use in CSS *} + +``` + + +Custom Mappers +============== + +Create custom mappers for special needs like cloud storage or dynamic generation: + +```php +use Nette\Assets\Mapper; +use Nette\Assets\Asset; +use Nette\Assets\Helpers; + +class CloudStorageMapper implements Mapper +{ + public function __construct( + private CloudClient $client, + private string $bucket, + ) {} + + public function getAsset(string $reference, array $options = []): Asset + { + if (!$this->client->exists($this->bucket, $reference)) { + throw new Nette\Assets\AssetNotFoundException("Asset '$reference' not found"); + } + + $url = $this->client->getPublicUrl($this->bucket, $reference); + return Helpers::createAssetFromUrl($url); + } +} +``` + +Register in configuration: + +```neon +assets: + mapping: + cloud: CloudStorageMapper(@cloudClient, 'my-bucket') +``` + +Use like any other mapper: + +```latte +{asset 'cloud:user-uploads/photo.jpg'} +``` + +The `Helpers::createAssetFromUrl()` method automatically creates the correct asset type based on file extension. + + +Further Reading +=============== + +- [Nette Assets: Finally unified API for everything from images to Vite |https://blog.nette.org/en/introducing-nette-assets] diff --git a/assets/en/@left-menu.texy b/assets/en/@left-menu.texy new file mode 100644 index 0000000000..ba3656522b --- /dev/null +++ b/assets/en/@left-menu.texy @@ -0,0 +1,5 @@ +Nette Assets +************ +- [Getting Started |@home] +- [Vite |vite] +- [Configuration] diff --git a/assets/en/@meta.texy b/assets/en/@meta.texy new file mode 100644 index 0000000000..42471908b0 --- /dev/null +++ b/assets/en/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Documentation}} diff --git a/assets/en/configuration.texy b/assets/en/configuration.texy new file mode 100644 index 0000000000..de021652b2 --- /dev/null +++ b/assets/en/configuration.texy @@ -0,0 +1,188 @@ +Assets Configuration +******************** + +.[perex] +Overview of configuration options for Nette Assets. + + +```neon +assets: + # base path for resolving relative mapper paths + basePath: ... # (string) defaults to %wwwDir% + + # base URL for resolving relative mapper URLs + baseUrl: ... # (string) defaults to %baseUrl% + + # enable asset versioning globally? + versioning: ... # (bool) defaults to true + + # defines asset mappers + mapping: ... # (array) defaults to path 'assets' +``` + +The `basePath` sets the default filesystem directory for resolving relative paths in mappers. By default, it uses the web directory (`%wwwDir%`). + +The `baseUrl` sets the default URL prefix for resolving relative URLs in mappers. By default, it uses the root URL (`%baseUrl%`). + +The `versioning` option globally controls whether version parameters are added to asset URLs for cache busting. Individual mappers can override this setting. + + +Mappers +------- + +Mappers can be configured in three ways: simple string notation, detailed array notation, or as a reference to a service. + +The simplest way to define a mapper: + +```neon +assets: + mapping: + default: assets # Creates filesystem mapper for %wwwDir%/assets/ + images: img # Creates filesystem mapper for %wwwDir%/img/ + scripts: js # Creates filesystem mapper for %wwwDir%/js/ +``` + +Each mapper creates a `FilesystemMapper` that: +- Looks for files in `%wwwDir%/` +- Generates URLs like `%baseUrl%/` +- Inherits global versioning setting + + +For more control, use the detailed notation: + +```neon +assets: + mapping: + images: + # directory where files are stored + path: ... # (string) optional, defaults to '' + + # URL prefix for generated links + url: ... # (string) optional, defaults to path + + # enable versioning for this mapper? + versioning: ... # (bool) optional, inherits global setting + + # auto-add extension(s) when searching for files + extension: ... # (string|array) optional, defaults to null +``` + +Understanding how configuration values are resolved: + +Path Resolution: + - Relative paths are resolved from `basePath` (or `%wwwDir%` if `basePath` is not set) + - Absolute paths are used as-is + +URL Resolution: + - Relative URLs are resolved from `baseUrl` (or `%baseUrl%` if `baseUrl` is not set) + - Absolute URLs (with scheme or `//`) are used as-is + - If `url` is not specified, it uses the value of `path` + + +```neon +assets: + basePath: /var/www/project/www + baseUrl: https://example.com/assets + + mapping: + # Relative path and URL + images: + path: img # Resolved to: /var/www/project/www/img + url: images # Resolved to: https://example.com/assets/images + + # Absolute path and URL + uploads: + path: /var/shared/uploads # Used as-is: /var/shared/uploads + url: https://cdn.example.com # Used as-is: https://cdn.example.com + + # Only path specified + styles: + path: css # Path: /var/www/project/www/css + # URL: https://example.com/assets/css +``` + + +Custom Mappers +-------------- + +For custom mappers, reference or define a service: + +```neon +services: + s3mapper: App\Assets\S3Mapper(%s3.bucket%) + +assets: + mapping: + cloud: @s3mapper + database: App\Assets\DatabaseMapper(@database.connection) +``` + + +Vite Mapper +----------- + +The Vite mapper only requires you to add `type: vite`. This is a complete list of configuration options: + +```neon +assets: + mapping: + default: + # mapper type (required for Vite) + type: vite # (string) required, must be 'vite' + + # Vite build output directory + path: ... # (string) optional, defaults to '' + + # URL prefix for built assets + url: ... # (string) optional, defaults to path + + # location of Vite manifest file + manifest: ... # (string) optional, defaults to /.vite/manifest.json + + # Vite dev server configuration + devServer: ... # (bool|string) optional, defaults to true + + # versioning for public directory files + versioning: ... # (bool) optional, inherits global setting + + # auto-extension for public directory files + extension: ... # (string|array) optional, defaults to null +``` + +The `devServer` option controls how assets are loaded during development: + +- `true` (default) - Automatically detects the Vite dev server on the current host and port. If the dev server is running **and your application is in debug mode**, assets are loaded from it with hot module replacement support. If the dev server is not running, assets are loaded from the built files in the public directory. +- `false` - Completely disables the dev server integration. Assets are always loaded from the built files. +- Custom URL (e.g., `https://localhost:5173`) - Manually specify the dev server URL including protocol and port. Useful when the dev server runs on a different host or port. + +Options `versioning` and `extension` apply only to files in Vite's public directory that aren't processed by Vite. + + +Manual Configuration +-------------------- + +When not using Nette DI, configure mappers manually: + +```php +use Nette\Assets\Registry; +use Nette\Assets\FilesystemMapper; +use Nette\Assets\ViteMapper; + +$registry = new Registry; + +// Add filesystem mapper +$registry->addMapper('images', new FilesystemMapper( + baseUrl: 'https://example.com/img', + basePath: __DIR__ . '/www/img', + extensions: ['webp', 'jpg', 'png'], + versioning: true, +)); + +// Add Vite mapper +$registry->addMapper('app', new ViteMapper( + baseUrl: '/build', + basePath: __DIR__ . '/www/build', + manifestPath: __DIR__ . '/www/build/.vite/manifest.json', + devServer: 'https://localhost:5173', +)); +``` diff --git a/assets/en/vite.texy b/assets/en/vite.texy new file mode 100644 index 0000000000..781e62016a --- /dev/null +++ b/assets/en/vite.texy @@ -0,0 +1,508 @@ +Vite Integration +**************** + +
    + +Modern JavaScript applications require sophisticated build tools. Nette Assets provides first-class integration with [Vite |https://vitejs.dev/], the next-generation frontend build tool. Get lightning-fast development with Hot Module Replacement (HMR) and optimized production builds with zero configuration hassle. + +- **Zero configuration** - automatic bridge between Vite and PHP templates +- **Complete dependency management** - one tag handles all assets +- **Hot Module Replacement** - instant JavaScript and CSS updates +- **Optimized production builds** - code splitting and tree shaking + +
    + + +Nette Assets integrates seamlessly with Vite, so you get all these benefits while writing your templates as usual. + + +Setting Up Vite +=============== + +Let's set up Vite step by step. Don't worry if you're new to build tools - we'll explain everything! + + +Step 1: Install Vite +-------------------- + +First, install Vite and the Nette plugin in your project: + +```shell +npm install -D vite @nette/vite-plugin +``` + +This installs Vite and a special plugin that helps Vite work perfectly with Nette. + + +Step 2: Project Structure +------------------------- + +The standard approach is to place source asset files in an `assets/` folder in your project root, and the compiled versions in `www/assets/`: + +/--pre +web-project/ +├── assets/ ← source files (SCSS, TypeScript, source images) +│ ├── public/ ← static files (copied as-is) +│ │ └── favicon.ico +│ ├── images/ +│ │ └── logo.png +│ ├── app.js ← main entry point +│ └── style.css ← your styles +└── www/ ← public directory (document root) + ├── assets/ ← compiled files will go here + └── index.php +\-- + +The `assets/` folder contains your source files - the code you write. Vite will process these files and put the compiled versions in `www/assets/`. + + +Step 3: Configure Vite +---------------------- + +Create a `vite.config.ts` file in your project root. This file tells Vite where to find your source files and where to put the compiled ones. + +The Nette Vite plugin comes with smart defaults that make configuration simple. It assumes your front-end source files are in the `assets/` directory (option `root`) and compiled files go to `www/assets/` (option `outDir`). You only need to specify the [entry point|#Entry Points]: + +```js +import { defineConfig } from 'vite'; +import nette from '@nette/vite-plugin'; + +export default defineConfig({ + plugins: [ + nette({ + entry: 'app.js', + }), + ], +}); +``` + +If you want to specify another directory name to build your assets, you will need to change a few options: + +```js +export default defineConfig({ + root: 'assets', // root directory of source assets + + build: { + outDir: '../www/assets', // where compiled files go + }, + + // ... other config ... +}); +``` + +.[note] +The `outDir` path is considered relative to `root`, which is why there's `../` at the beginning. + + +Step 4: Configure Nette +----------------------- + +Tell Nette Assets about Vite in your `common.neon`: + +```neon +assets: + mapping: + default: + type: vite # tells Nette to use the ViteMapper + path: assets +``` + + +Step 5: Add scripts +------------------- + +Add these scripts to your `package.json`: + +```json +{ + "scripts": { + "dev": "vite", + "build": "vite build" + } +} +``` + +Now you can: +- `npm run dev` - start development server with hot reloading +- `npm run build` - create optimized production files + + +Entry Points +============ + +An **entry point** is the main file where your application starts. From this file, you import other files (CSS, JavaScript modules, images), creating a dependency tree. Vite follows these imports and bundles everything together. + +Example entry point `assets/app.js`: + +```js +// Import styles +import './style.css' + +// Import JavaScript modules +import netteForms from 'nette-forms'; +import naja from 'naja'; + +// Initialize your application +netteForms.initOnLoad(); +naja.initialize(); +``` + +In the template you can insert an entry point as follows: + +```latte +{asset 'app.js'} +``` + +Nette Assets automatically generates all necessary HTML tags - JavaScript, CSS, and any other dependencies. + + +Multiple Entry Points +--------------------- + +Larger applications often need separate entry points: + +```js +export default defineConfig({ + plugins: [ + nette({ + entry: [ + 'app.js', // public pages + 'admin.js', // admin panel + ], + }), + ], +}); +``` + +Use them in different templates: + +```latte +{* In public pages *} +{asset 'app.js'} + +{* In admin panel *} +{asset 'admin.js'} +``` + + +Important: Source vs Compiled Files +----------------------------------- + +It's crucial to understand that on production you can only load: + +1. **Entry points** defined in `entry` +2. **Files from the `assets/public/` directory** + +You **cannot** load using `{asset}` arbitrary files from `assets/` - only assets referenced by JavaScript or CSS files. If your file is not referenced anywhere it will not be compiled. If you want to make Vite aware of other assets, you can move them to the [#public folder]. + +Please note that by default, Vite will inline all assets smaller than 4KB, so you will not be able to reference these files directly. (See [Vite documentation |https://vite.dev/guide/assets.html]). + +```latte +{* ✓ This works - it's an entry point *} +{asset 'app.js'} + +{* ✓ This works - it's in assets/public/ *} +{asset 'favicon.ico'} + +{* ✗ This won't work - random file in assets/ *} +{asset 'components/button.js'} +``` + + +Development Mode +================ + +Development mode is completely optional but provides significant benefits when enabled. The main advantage is **Hot Module Replacement (HMR)** - see changes instantly without losing application state, making the development experience much smoother and faster. + +Vite is a modern build tool that makes development incredibly fast. Unlike traditional bundlers, Vite serves your code directly to the browser during development, which means instant server start no matter how large your project and lightning-fast updates. + + +Starting Development Server +--------------------------- + +Run the development server: + +```shell +npm run dev +``` + +You'll see: + +``` + ➜ Local: http://localhost:5173/ + ➜ Network: use --host to expose +``` + +Keep this terminal open while developing. + +The Nette Vite plugin automatically detects when: +1. Vite dev server is running +2. Your Nette application is in debug mode + +When both conditions are met, Nette Assets loads files from the Vite dev server instead of the compiled directory: + +```latte +{asset 'app.js'} +{* In development: *} +{* In production: *} +``` + +No configuration needed - it just works! + + +Working on Different Domains +---------------------------- + +If your development server runs on something other than `localhost` (like `myapp.local`), you might encounter CORS (Cross-Origin Resource Sharing) issues. CORS is a security feature in web browsers that blocks requests between different domains by default. When your PHP application runs on `myapp.local` but Vite runs on `localhost:5173`, the browser sees these as different domains and blocks the requests. + +You have two options to solve this: + +**Option 1: Configure CORS** + +The simplest solution is to allow cross-origin requests from your PHP application: + +```js +export default defineConfig({ + // ... other config ... + + server: { + cors: { + origin: 'http://myapp.local', // your PHP app URL + }, + }, +}); +``` +**Option 2: Run Vite on your domain** + +The other solution is to make Vite run on the same domain as your PHP application. + +```js +export default defineConfig({ + // ... other config ... + + server: { + host: 'myapp.local', // same as your PHP app + }, +}); +``` + +Actually, even in this case, you need to configure CORS because the dev server runs on the same hostname but on a different port. However, in this case, CORS is automatically configured by the Nette Vite plugin. + + +HTTPS Development +----------------- + +If you develop on HTTPS, you need certificates for your Vite development server. The easiest way is using a plugin that generates certificates automatically: + +```shell +npm install -D vite-plugin-mkcert +``` + +Here's how to configure it in `vite.config.ts`: + +```js +import mkcert from 'vite-plugin-mkcert'; + +export default defineConfig({ + // ... other config ... + + plugins: [ + mkcert(), // generates certificates automatically and enables https + nette(), + ], +}); +``` + +Note that if you're using the CORS configuration (Option 1 from above), you need to update the origin URL to use `https://` instead of `http://`. + + +Production Builds +================= + +Create optimized production files: + +```shell +npm run build +``` + +Vite will: +- Minify all JavaScript and CSS +- Split code into optimal chunks +- Generate hashed filenames for cache-busting +- Create a manifest file for Nette Assets + +Example output: + +``` +www/assets/ +├── app-4f3a2b1c.js # Your main JavaScript (minified) +├── app-7d8e9f2a.css # Extracted CSS (minified) +├── vendor-8c4b5e6d.js # Shared dependencies +└── .vite/ + └── manifest.json # Mapping for Nette Assets +``` + +The hashed filenames ensure browsers always load the latest version. + + +Public Folder +============= + +Files in `assets/public/` directory are copied to the output without processing: + +``` +assets/ +├── public/ +│ ├── favicon.ico +│ ├── robots.txt +│ └── images/ +│ └── og-image.jpg +├── app.js +└── style.css +``` + +Reference them normally: + +```latte +{* These files are copied as-is *} + + +``` + +For public files, you can use FilesystemMapper features: + +```neon +assets: + mapping: + default: + type: vite + path: assets + extension: [webp, jpg, png] # Try WebP first + versioning: true # Add cache-busting +``` + +In the `vite.config.ts` configuration you can change the public folder using the `publicDir` option. + + +Dynamic Imports +=============== + +Vite automatically splits code for optimal loading. Dynamic imports allow you to load code only when it's actually needed, reducing the initial bundle size: + +```js +// Load heavy components on demand +button.addEventListener('click', async () => { + let { Chart } = await import('./components/chart.js') + new Chart(data) +}) +``` + +Dynamic imports create separate chunks that are loaded only when needed. This is called "code splitting" and it's one of Vite's most powerful features. When you use dynamic imports, Vite automatically creates separate JavaScript files for each dynamically imported module. + +The `{asset 'app.js'}` tag does **not** automatically preload these dynamic chunks. This is intentional behavior - we don't want to download code that might never be used. The chunks are downloaded only when the dynamic import is executed. + +However, if you know that certain dynamic imports are critical and will be needed soon, you can preload them: + +```latte +{* Main entry point *} +{asset 'app.js'} + +{* Preload critical dynamic imports *} +{preload 'components/chart.js'} +``` + +This tells the browser to download the chart component in the background, so it's ready immediately when needed. + + +TypeScript Support +================== + +TypeScript works out of the box: + +```ts +// assets/main.ts +interface User { + name: string + email: string +} + +export function greetUser(user: User): void { + console.log(`Hello, ${user.name}!`) +} +``` + +Reference TypeScript files normally: + +```latte +{asset 'main.ts'} +``` + +For full TypeScript support, install it: + +```shell +npm install -D typescript +``` + + +Additional Vite Configuration +============================= + +Here are some useful Vite configuration options with detailed explanations: + +```js +export default defineConfig({ + // Root directory containing source assets + root: 'assets', + + // Folder whose contents are copied to output directory as-is + // Default: 'public' (relative to 'root') + publicDir: 'public', + + build: { + // Where to put compiled files (relative to 'root') + outDir: '../www/assets', + + // Empty output directory before building? + // Useful to remove old files from previous builds + emptyOutDir: true, + + // Subdirectory within outDir for generated chunks and assets + // This helps organize the output structure + assetsDir: 'static', + + rollupOptions: { + // Entry point(s) - can be a single file or array of files + // Each entry point becomes a separate bundle + input: [ + 'app.js', // main application + 'admin.js', // admin panel + ], + }, + }, + + server: { + // Host to bind the dev server to + // Use '0.0.0.0' to expose to network + host: 'localhost', + + // Port for the dev server + port: 5173, + + // CORS configuration for cross-origin requests + cors: { + origin: 'http://myapp.local', + }, + }, + + css: { + // Enable CSS source maps in development + devSourcemap: true, + }, + + plugins: [ + nette(), + ], +}); +``` + +That's it! You now have a modern build system integrated with Nette Assets. diff --git a/assets/es/@home.texy b/assets/es/@home.texy new file mode 100644 index 0000000000..e17c3e7455 --- /dev/null +++ b/assets/es/@home.texy @@ -0,0 +1,432 @@ +Nette Assets +************ + +
    + +¿Cansado de gestionar manualmente archivos estáticos en sus aplicaciones web? Olvídese de codificar rutas, lidiar con la invalidación de la caché o preocuparse por el versionado de archivos. Nette Assets transforma la forma en que trabaja con imágenes, hojas de estilo, scripts y otros recursos estáticos. + +- El **versionado inteligente** garantiza que los navegadores siempre carguen los archivos más recientes +- **Detección automática** de tipos de archivo y dimensiones +- **Integración perfecta con Latte** con etiquetas intuitivas +- **Arquitectura flexible** que soporta sistemas de archivos, CDNs y Vite +- **Carga diferida** para un rendimiento óptimo + +
    + + +¿Por qué Nette Assets? +====================== + +Trabajar con archivos estáticos a menudo significa código repetitivo y propenso a errores. Usted construye URLs manualmente, añade parámetros de versión para la eliminación de caché y maneja diferentes tipos de archivos de manera distinta. Esto lleva a un código como: + +```html +Logo + +``` + +Con Nette Assets, toda esta complejidad desaparece: + +```latte +{* Todo automatizado - URL, versionado, dimensiones *} + + + +{* O simplemente *} +{asset 'css/style.css'} +``` + +¡Eso es todo! La librería automáticamente: +- Añade parámetros de versión basados en la hora de modificación del archivo +- Detecta las dimensiones de la imagen y las incluye en el HTML +- Genera el elemento HTML correcto para cada tipo de archivo +- Maneja tanto entornos de desarrollo como de producción + + +Instalación +=========== + +Instale Nette Assets usando [Composer|best-practices:composer]: + +```shell +composer require nette/assets +``` + +Requiere PHP 8.1 o superior y funciona perfectamente con Nette Framework, pero también puede usarse de forma independiente. + + +Primeros Pasos +============== + +Nette Assets funciona de forma inmediata sin configuración. Coloque sus archivos estáticos en el directorio `www/assets/` y empiece a usarlos: + +```latte +{* Muestra una imagen con dimensiones automáticas *} +{asset 'logo.png'} + +{* Incluye una hoja de estilo con versionado *} +{asset 'style.css'} + +{* Carga un módulo JavaScript *} +{asset 'app.js'} +``` + +Para un mayor control sobre el HTML generado, use el atributo `n:asset` o la función `asset()`. + + +Cómo Funciona +============= + +Nette Assets se construye alrededor de tres conceptos fundamentales que lo hacen potente y, al mismo tiempo, sencillo de usar: + + +Assets - Sus Archivos Hechos Inteligentes +----------------------------------------- + +Un **asset** representa cualquier archivo estático en su aplicación. Cada archivo se convierte en un objeto con propiedades de solo lectura útiles: + +```php +$image = $assets->getAsset('photo.jpg'); +echo $image->url; // '/assets/photo.jpg?v=1699123456' +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' +``` + +Diferentes tipos de archivo proporcionan diferentes propiedades: +- **Imágenes**: ancho, alto, texto alternativo, carga diferida +- **Scripts**: tipo de módulo, hashes de integridad, crossorigin +- **Hojas de estilo**: media queries, integridad +- **Audio/Video**: duración, dimensiones +- **Fuentes**: precarga adecuada con CORS + +La librería detecta automáticamente los tipos de archivo y crea la clase de asset apropiada. + + +Mappers - De Dónde Vienen los Archivos +-------------------------------------- + +Un **mapper** sabe cómo encontrar archivos y crear URLs para ellos. Puede tener varios mappers para diferentes propósitos: archivos locales, CDN, almacenamiento en la nube o herramientas de construcción (cada uno de ellos tiene un nombre). El `FilesystemMapper` incorporado maneja los archivos locales, mientras que `ViteMapper` se integra con las herramientas de construcción modernas. + +Los mappers se definen en la [configuración |Configuration]. + + +Registry - Su Interfaz Principal +-------------------------------- + +El **registry** gestiona todos los mappers y proporciona la API principal: + +```php +// Inyecta el registro en su servicio +public function __construct( + private Nette\Assets\Registry $assets +) {} + +// Obtiene assets de diferentes mappers +$logo = $this->assets->getAsset('images:logo.png'); // mapper 'image' +$app = $this->assets->getAsset('app:main.js'); // mapper 'app' +$style = $this->assets->getAsset('style.css'); // usa el mapper predeterminado +``` + +El registro selecciona automáticamente el mapper correcto y almacena en caché los resultados para mejorar el rendimiento. + + +Trabajando con Assets en PHP +============================ + +El Registry proporciona dos métodos para recuperar assets: + +```php +// Lanza Nette\Assets\AssetNotFoundException si el archivo no existe +$logo = $assets->getAsset('logo.png'); + +// Devuelve null si el archivo no existe +$banner = $assets->tryGetAsset('banner.jpg'); +if ($banner) { + echo $banner->url; +} +``` + + +Especificando Mappers +--------------------- + +Puede elegir explícitamente qué mapper usar: + +```php +// Usar el mapper predeterminado +$file = $assets->getAsset('document.pdf'); + +// Usar un mapper específico con prefijo +$image = $assets->getAsset('images:photo.jpg'); + +// Usar un mapper específico con sintaxis de array +$script = $assets->getAsset(['scripts', 'app.js']); +``` + + +Propiedades y Tipos de Assets +----------------------------- + +Cada tipo de asset proporciona propiedades de solo lectura relevantes: + +```php +// Propiedades de la imagen +$image = $assets->getAsset('photo.jpg'); +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' + +// Propiedades del script +$script = $assets->getAsset('app.js'); +echo $script->type; // 'module' o null + +// Propiedades del audio +$audio = $assets->getAsset('song.mp3'); +echo $audio->duration; // duración en segundos + +// Todos los assets pueden convertirse a string (devuelve la URL) +$url = (string) $assets->getAsset('document.pdf'); +``` + +.[note] +Las propiedades como las dimensiones o la duración se cargan de forma diferida solo cuando se acceden, manteniendo la librería rápida. + + +Usando Assets en Plantillas Latte +================================= + +Nette Assets proporciona una integración intuitiva con [Latte|latte:] mediante etiquetas y funciones. + + +`{asset}` +--------- + +La etiqueta `{asset}` renderiza elementos HTML completos: + +```latte +{* Renderiza: *} +{asset 'hero.jpg'} + +{* Renderiza: *} +{asset 'app.js'} + +{* Renderiza: *} +{asset 'style.css'} +``` + +La etiqueta automáticamente: +- Detecta el tipo de asset y genera el HTML apropiado +- Incluye versionado para la eliminación de caché +- Añade dimensiones para las imágenes +- Establece los atributos correctos (type, media, etc.) + +Cuando se usa dentro de atributos HTML, solo genera la URL: + +```latte +
    + +``` + + +`n:asset` +--------- + +Para un control total sobre los atributos HTML: + +```latte +{* El atributo n:asset rellena src, dimensiones, etc. *} +Producto + +{* Funciona con cualquier elemento relevante *} + + + +``` + +Use variables y mappers: + +```latte +{* Las variables funcionan de forma natural *} + + +{* Especificar mapper con llaves *} + + +{* Especificar mapper con notación de array *} + +``` + + +`asset()` +--------- + +Para una máxima flexibilidad, use la función `asset()`: + +```latte +{var $logo = asset('logo.png')} +width} height={$logo->height}> + +{* O directamente *} +Logo +``` + + +Assets Opcionales +----------------- + +Maneje los assets que faltan con elegancia usando `{asset?}`, `n:asset?` y `tryAsset()`: + +```latte +{* Etiqueta opcional - no renderiza nada si falta el asset *} +{asset? 'optional-banner.jpg'} + +{* Atributo opcional - se omite si falta el asset *} +Avatar + +{* Con fallback *} +{var $avatar = tryAsset('user-avatar.jpg') ?? asset('default-avatar.jpg')} +Avatar +``` + + +`{preload}` +----------- + +Mejore el rendimiento de carga de la página: + +```latte +{* En su sección *} +{preload 'critical.css'} +{preload 'important-font.woff2'} +{preload 'hero-image.jpg'} +``` + +Genera enlaces de precarga apropiados: + +```html + + + +``` + + +Características Avanzadas +========================= + + +Detección Automática de Extensión +--------------------------------- + +Maneje múltiples formatos automáticamente: + +```neon +assets: + mapping: + images: + path: img + extension: [webp, jpg, png] # Intentar en orden +``` + +Ahora puede solicitar sin extensión: + +```latte +{* Encuentra logo.webp, logo.jpg o logo.png automáticamente *} +{asset 'images:logo'} +``` + +Perfecto para la mejora progresiva con formatos modernos. + + +Versionado Inteligente +---------------------- + +Los archivos se versionan automáticamente según la hora de modificación: + +```latte +{asset 'style.css'} +{* Salida: *} +``` + +Cuando actualiza el archivo, la marca de tiempo cambia, forzando la actualización de la caché del navegador. + +Controle el versionado por asset: + +```php +// Deshabilitar el versionado para un asset específico +$asset = $assets->getAsset('style.css', ['version' => false]); + +// En Latte +{asset 'style.css', version: false} +``` + + +Assets de Fuentes +----------------- + +Las fuentes reciben un tratamiento especial con CORS adecuado: + +```latte +{* Precarga adecuada con crossorigin *} +{preload 'fonts:OpenSans-Regular.woff2'} + +{* Usar en CSS *} + +``` + + +Mappers Personalizados +====================== + +Cree mappers personalizados para necesidades especiales como almacenamiento en la nube o generación dinámica: + +```php +use Nette\Assets\Mapper; +use Nette\Assets\Asset; +use Nette\Assets\Helpers; + +class CloudStorageMapper implements Mapper +{ + public function __construct( + private CloudClient $client, + private string $bucket, + ) {} + + public function getAsset(string $reference, array $options = []): Asset + { + if (!$this->client->exists($this->bucket, $reference)) { + throw new Nette\Assets\AssetNotFoundException("Asset '$reference' no encontrado"); + } + + $url = $this->client->getPublicUrl($this->bucket, $reference); + return Helpers::createAssetFromUrl($url); + } +} +``` + +Registre en la configuración: + +```neon +assets: + mapping: + cloud: CloudStorageMapper(@cloudClient, 'my-bucket') +``` + +Use como cualquier otro mapper: + +```latte +{asset 'cloud:user-uploads/photo.jpg'} +``` + +El método `Helpers::createAssetFromUrl()` crea automáticamente el tipo de asset correcto basándose en la extensión del archivo. + + +Más información +=============== + +- [Activos Nette: Por fin una API unificada para todo, desde imágenes hasta Vite |https://blog.nette.org/en/introducing-nette-assets] diff --git a/assets/es/@left-menu.texy b/assets/es/@left-menu.texy new file mode 100644 index 0000000000..8b8a4573d7 --- /dev/null +++ b/assets/es/@left-menu.texy @@ -0,0 +1,5 @@ +Nette Assets +************ +- [Primeros Pasos |@home] +- [Vite |vite] +- [Configuración |Configuration] diff --git a/assets/es/@meta.texy b/assets/es/@meta.texy new file mode 100644 index 0000000000..1670b124ad --- /dev/null +++ b/assets/es/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Documentación}} diff --git a/assets/es/configuration.texy b/assets/es/configuration.texy new file mode 100644 index 0000000000..f07585f39c --- /dev/null +++ b/assets/es/configuration.texy @@ -0,0 +1,188 @@ +Configuración de Assets +*********************** + +.[perex] +Resumen de las opciones de configuración para Nette Assets. + + +```neon +assets: + # ruta base para resolver rutas relativas del mapper + basePath: ... # (string) por defecto %wwwDir% + + # URL base para resolver URLs relativas del mapper + baseUrl: ... # (string) por defecto %baseUrl% + + # ¿habilitar el versionado de assets globalmente? + versioning: ... # (bool) por defecto true + + # define los mappers de assets + mapping: ... # (array) por defecto la ruta 'assets' +``` + +`basePath` establece el directorio del sistema de archivos predeterminado para resolver rutas relativas en los mappers. Por defecto, usa el directorio web (`%wwwDir%`). + +`baseUrl` establece el prefijo de URL predeterminado para resolver URLs relativas en los mappers. Por defecto, usa la URL raíz (`%baseUrl%`). + +La opción `versioning` controla globalmente si se añaden parámetros de versión a las URLs de los assets para la eliminación de caché. Los mappers individuales pueden anular esta configuración. + + +Mappers +------- + +Los mappers se pueden configurar de tres maneras: notación de cadena simple, notación de array detallada o como referencia a un servicio. + +La forma más sencilla de definir un mapper: + +```neon +assets: + mapping: + default: assets # Crea un mapper de sistema de archivos para %wwwDir%/assets/ + images: img # Crea un mapper de sistema de archivos para %wwwDir%/img/ + scripts: js # Crea un mapper de sistema de archivos para %wwwDir%/js/ +``` + +Cada mapper crea un `FilesystemMapper` que: +- Busca archivos en `%wwwDir%/` +- Genera URLs como `%baseUrl%/` +- Hereda la configuración global de versionado + + +Para un mayor control, use la notación detallada: + +```neon +assets: + mapping: + images: + # directorio donde se almacenan los archivos + path: ... # (string) opcional, por defecto '' + + # prefijo de URL para los enlaces generados + url: ... # (string) opcional, por defecto path + + # ¿habilitar el versionado para este mapper? + versioning: ... # (bool) opcional, hereda la configuración global + + # añadir automáticamente extensión(es) al buscar archivos + extension: ... # (string|array) opcional, por defecto null +``` + +Entendiendo cómo se resuelven los valores de configuración: + +Resolución de Rutas: + - Las rutas relativas se resuelven desde `basePath` (o `%wwwDir%` si `basePath` no está configurado) + - Las rutas absolutas se usan tal cual + +Resolución de URLs: + - Las URLs relativas se resuelven desde `baseUrl` (o `%baseUrl%` si `baseUrl` no está configurado) + - Las URLs absolutas (con esquema o `//`) se usan tal cual + - Si `url` no se especifica, usa el valor de `path` + + +```neon +assets: + basePath: /var/www/project/www + baseUrl: https://example.com/assets + + mapping: + # Ruta y URL relativas + images: + path: img # Resuelto a: /var/www/project/www/img + url: images # Resuelto a: https://example.com/assets/images + + # Ruta y URL absolutas + uploads: + path: /var/shared/uploads # Usado tal cual: /var/shared/uploads + url: https://cdn.example.com # Usado tal cual: https://cdn.example.com + + # Solo la ruta especificada + styles: + path: css # Ruta: /var/www/project/www/css + # URL: https://example.com/assets/css +``` + + +Mappers Personalizados +---------------------- + +Para mappers personalizados, referencie o defina un servicio: + +```neon +services: + s3mapper: App\Assets\S3Mapper(%s3.bucket%) + +assets: + mapping: + cloud: @s3mapper + database: App\Assets\DatabaseMapper(@database.connection) +``` + + +Vite Mapper +----------- + +El mapper de Vite solo requiere que añada `type: vite`. Esta es una lista completa de opciones de configuración: + +```neon +assets: + mapping: + default: + # tipo de mapper (requerido para Vite) + type: vite # (string) requerido, debe ser 'vite' + + # directorio de salida de la construcción de Vite + path: ... # (string) opcional, por defecto '' + + # prefijo de URL para los assets construidos + url: ... # (string) opcional, por defecto path + + # ubicación del archivo manifest de Vite + manifest: ... # (string) opcional, por defecto /.vite/manifest.json + + # configuración del servidor de desarrollo de Vite + devServer: ... # (bool|string) opcional, por defecto true + + # versionado para archivos del directorio público + versioning: ... # (bool) opcional, hereda la configuración global + + # auto-extensión para archivos del directorio público + extension: ... # (string|array) opcional, por defecto null +``` + +La opción `devServer` controla cómo se cargan los assets durante el desarrollo: + +- `true` (predeterminado) - Detecta automáticamente el servidor de desarrollo de Vite en el host y puerto actuales. Si el servidor de desarrollo está ejecutándose **y su aplicación está en modo depuración**, los assets se cargan desde él con soporte para Hot Module Replacement. Si el servidor de desarrollo no está ejecutándose, los assets se cargan desde los archivos construidos en el directorio público. +- `false` - Deshabilita completamente la integración del servidor de desarrollo. Los assets siempre se cargan desde los archivos construidos. +- URL personalizada (ej. `https://localhost:5173`) - Especifique manualmente la URL del servidor de desarrollo, incluyendo el protocolo y el puerto. Útil cuando el servidor de desarrollo se ejecuta en un host o puerto diferente. + +Las opciones `versioning` y `extension` solo se aplican a los archivos del directorio público de Vite que no son procesados por Vite. + + +Configuración Manual +-------------------- + +Cuando no use Nette DI, configure los mappers manualmente: + +```php +use Nette\Assets\Registry; +use Nette\Assets\FilesystemMapper; +use Nette\Assets\ViteMapper; + +$registry = new Registry; + +// Añadir mapper de sistema de archivos +$registry->addMapper('images', new FilesystemMapper( + baseUrl: 'https://example.com/img', + basePath: __DIR__ . '/www/img', + extensions: ['webp', 'jpg', 'png'], + versioning: true, +)); + +// Añadir mapper de Vite +$registry->addMapper('app', new ViteMapper( + baseUrl: '/build', + basePath: __DIR__ . '/www/build', + manifestPath: __DIR__ . '/www/build/.vite/manifest.json', + devServer: 'https://localhost:5173', +)); +``` diff --git a/assets/es/vite.texy b/assets/es/vite.texy new file mode 100644 index 0000000000..880b80626b --- /dev/null +++ b/assets/es/vite.texy @@ -0,0 +1,508 @@ +Integración con Vite +******************** + +
    + +Las aplicaciones JavaScript modernas requieren herramientas de construcción sofisticadas. Nette Assets proporciona una integración de primera clase con [Vite |https://vitejs.dev/], la herramienta de construcción frontend de próxima generación. Obtenga un desarrollo ultrarrápido con Hot Module Replacement (HMR) y construcciones de producción optimizadas sin problemas de configuración. + +- **Cero configuración** - puente automático entre Vite y las plantillas PHP +- **Gestión completa de dependencias** - una etiqueta maneja todos los assets +- **Hot Module Replacement** - actualizaciones instantáneas de JavaScript y CSS +- **Construcciones de producción optimizadas** - división de código y tree shaking + +
    + + +Nette Assets se integra perfectamente con Vite, por lo que obtiene todos estos beneficios mientras escribe sus plantillas como de costumbre. + + +Configurando Vite +================= + +Vamos a configurar Vite paso a paso. No se preocupe si es nuevo en las herramientas de construcción, ¡lo explicaremos todo! + + +Paso 1: Instalar Vite +--------------------- + +Primero, instale Vite y el plugin de Nette en su proyecto: + +```shell +npm install -D vite @nette/vite-plugin +``` + +Esto instala Vite y un plugin especial que ayuda a Vite a funcionar perfectamente con Nette. + + +Paso 2: Estructura del Proyecto +------------------------------- + +El enfoque estándar es colocar los archivos de assets fuente en una carpeta `assets/` en la raíz de su proyecto, y las versiones compiladas en `www/assets/`: + +/--pre +web-project/ +├── assets/ ← archivos fuente (SCSS, TypeScript, imágenes fuente) +│ ├── public/ ← archivos estáticos (copiados tal cual) +│ │ └── favicon.ico +│ ├── images/ +│ │ └── logo.png +│ ├── app.js ← punto de entrada principal +│ └── style.css ← sus estilos +└── www/ ← directorio público (document root) + ├── assets/ ← los archivos compilados irán aquí + └── index.php +\-- + +La carpeta `assets/` contiene sus archivos fuente, el código que usted escribe. Vite procesará estos archivos y colocará las versiones compiladas en `www/assets/`. + + +Paso 3: Configurar Vite +----------------------- + +Cree un archivo `vite.config.ts` en la raíz de su proyecto. Este archivo le dice a Vite dónde encontrar sus archivos fuente y dónde colocar los compilados. + +El plugin Nette Vite viene con valores predeterminados inteligentes que simplifican la configuración. Asume que sus archivos fuente front-end están en el directorio `assets/` (opción `root`) y los archivos compilados van a `www/assets/` (opción `outDir`). Solo necesita especificar el [punto de entrada |#Entry Points]: + +```js +import { defineConfig } from 'vite'; +import nette from '@nette/vite-plugin'; + +export default defineConfig({ + plugins: [ + nette({ + entry: 'app.js', + }), + ], +}); +``` + +Si desea especificar otro nombre de directorio para construir sus assets, deberá cambiar algunas opciones: + +```js +export default defineConfig({ + root: 'assets', // directorio raíz de los assets fuente + + build: { + outDir: '../www/assets', // dónde van los archivos compilados + }, + + // ... otra configuración ... +}); +``` + +.[note] +La ruta `outDir` se considera relativa a `root`, por eso hay `../` al principio. + + +Paso 4: Configurar Nette +------------------------ + +Informe a Nette Assets sobre Vite en su `common.neon`: + +```neon +assets: + mapping: + default: + type: vite # le dice a Nette que use ViteMapper + path: assets +``` + + +Paso 5: Añadir scripts +---------------------- + +Añada estos scripts a su `package.json`: + +```json +{ + "scripts": { + "dev": "vite", + "build": "vite build" + } +} +``` + +Ahora puede: +- `npm run dev` - iniciar el servidor de desarrollo con recarga en caliente +- `npm run build` - crear archivos de producción optimizados + + +Puntos de Entrada +================= + +Un **punto de entrada** es el archivo principal donde comienza su aplicación. Desde este archivo, importa otros archivos (CSS, módulos JavaScript, imágenes), creando un árbol de dependencias. Vite sigue estas importaciones y agrupa todo. + +Ejemplo de punto de entrada `assets/app.js`: + +```js +// Importar estilos +import './style.css' + +// Importar módulos JavaScript +import netteForms from 'nette-forms'; +import naja from 'naja'; + +// Inicializar su aplicación +netteForms.initOnLoad(); +naja.initialize(); +``` + +En la plantilla puede insertar un punto de entrada de la siguiente manera: + +```latte +{asset 'app.js'} +``` + +Nette Assets genera automáticamente todas las etiquetas HTML necesarias: JavaScript, CSS y cualquier otra dependencia. + + +Múltiples Puntos de Entrada +--------------------------- + +Las aplicaciones más grandes a menudo necesitan puntos de entrada separados: + +```js +export default defineConfig({ + plugins: [ + nette({ + entry: [ + 'app.js', // páginas públicas + 'admin.js', // panel de administración + ], + }), + ], +}); +``` + +Úselos en diferentes plantillas: + +```latte +{* En páginas públicas *} +{asset 'app.js'} + +{* En el panel de administración *} +{asset 'admin.js'} +``` + + +Importante: Archivos Fuente vs Compilados +----------------------------------------- + +Es crucial entender que en producción solo se puede cargar: + +1. **Puntos de entrada** definidos en `entry` +2. **Archivos del directorio `assets/public/`** + +Usted **no puede** cargar usando `{asset}` archivos arbitrarios de `assets/` - solo assets referenciados por archivos JavaScript o CSS. Si su archivo no es referenciado en ningún lugar, no se compilará. Si desea que Vite sea consciente de otros assets, puede moverlos a la [carpeta pública |#public folder]. + +Tenga en cuenta que, por defecto, Vite incrustará todos los assets de menos de 4KB, por lo que no podrá referenciar estos archivos directamente. (Consulte la [documentación de Vite |https://vite.dev/guide/assets.html]). + +```latte +{* ✓ Esto funciona - es un punto de entrada *} +{asset 'app.js'} + +{* ✓ Esto funciona - está en assets/public/ *} +{asset 'favicon.ico'} + +{* ✗ Esto no funcionará - archivo aleatorio en assets/ *} +{asset 'components/button.js'} +``` + + +Modo de Desarrollo +================== + +El modo de desarrollo es completamente opcional, pero proporciona beneficios significativos cuando está habilitado. La principal ventaja es el **Hot Module Replacement (HMR)**: vea los cambios instantáneamente sin perder el estado de la aplicación, lo que hace que la experiencia de desarrollo sea mucho más fluida y rápida. + +Vite es una herramienta de construcción moderna que hace que el desarrollo sea increíblemente rápido. A diferencia de los bundlers tradicionales, Vite sirve su código directamente al navegador durante el desarrollo, lo que significa un inicio instantáneo del servidor sin importar cuán grande sea su proyecto y actualizaciones ultrarrápidas. + + +Iniciando el Servidor de Desarrollo +----------------------------------- + +Ejecute el servidor de desarrollo: + +```shell +npm run dev +``` + +Verá: + +``` + ➜ Local: http://localhost:5173/ + ➜ Network: use --host to expose +``` + +Mantenga esta terminal abierta mientras desarrolla. + +El plugin Nette Vite detecta automáticamente cuándo: +1. El servidor de desarrollo de Vite está ejecutándose +2. Su aplicación Nette está en modo depuración + +Cuando se cumplen ambas condiciones, Nette Assets carga los archivos desde el servidor de desarrollo de Vite en lugar del directorio compilado: + +```latte +{asset 'app.js'} +{* En desarrollo: *} +{* En producción: *} +``` + +No se necesita configuración, ¡simplemente funciona! + + +Trabajando en Diferentes Dominios +--------------------------------- + +Si su servidor de desarrollo se ejecuta en algo diferente a `localhost` (como `myapp.local`), podría encontrarse con problemas de CORS (Cross-Origin Resource Sharing). CORS es una característica de seguridad en los navegadores web que bloquea las solicitudes entre diferentes dominios por defecto. Cuando su aplicación PHP se ejecuta en `myapp.local` pero Vite se ejecuta en `localhost:5173`, el navegador los ve como dominios diferentes y bloquea las solicitudes. + +Tiene dos opciones para resolver esto: + +**Opción 1: Configurar CORS** + +La solución más sencilla es permitir solicitudes de origen cruzado desde su aplicación PHP: + +```js +export default defineConfig({ + // ... otra configuración ... + + server: { + cors: { + origin: 'http://myapp.local', // la URL de su aplicación PHP + }, + }, +}); +``` +**Opción 2: Ejecutar Vite en su dominio** + +La otra solución es hacer que Vite se ejecute en el mismo dominio que su aplicación PHP. + +```js +export default defineConfig({ + // ... otra configuración ... + + server: { + host: 'myapp.local', // igual que su aplicación PHP + }, +}); +``` + +De hecho, incluso en este caso, necesita configurar CORS porque el servidor de desarrollo se ejecuta en el mismo nombre de host pero en un puerto diferente. Sin embargo, en este caso, CORS se configura automáticamente por el plugin Nette Vite. + + +Desarrollo HTTPS +---------------- + +Si desarrolla en HTTPS, necesita certificados para su servidor de desarrollo Vite. La forma más sencilla es usar un plugin que genere certificados automáticamente: + +```shell +npm install -D vite-plugin-mkcert +``` + +Así es como se configura en `vite.config.ts`: + +```js +import mkcert from 'vite-plugin-mkcert'; + +export default defineConfig({ + // ... otra configuración ... + + plugins: [ + mkcert(), // genera certificados automáticamente y habilita https + nette(), + ], +}); +``` + +Tenga en cuenta que si está utilizando la configuración de CORS (Opción 1 de arriba), necesita actualizar la URL de origen para usar `https://` en lugar de `http://`. + + +Construcciones de Producción +============================ + +Cree archivos de producción optimizados: + +```shell +npm run build +``` + +Vite hará lo siguiente: +- Minificará todo el JavaScript y CSS +- Dividirá el código en trozos óptimos +- Generará nombres de archivo con hash para la eliminación de caché +- Creará un archivo manifest para Nette Assets + +Ejemplo de salida: + +``` +www/assets/ +├── app-4f3a2b1c.js # Su JavaScript principal (minificado) +├── app-7d8e9f2a.css # CSS extraído (minificado) +├── vendor-8c4b5e6d.js # Dependencias compartidas +└── .vite/ + └── manifest.json # Mapeo para Nette Assets +``` + +Los nombres de archivo con hash garantizan que los navegadores siempre carguen la última versión. + + +Carpeta Pública +=============== + +Los archivos en el directorio `assets/public/` se copian a la salida sin procesar: + +``` +assets/ +├── public/ +│ ├── favicon.ico +│ ├── robots.txt +│ └── images/ +│ └── og-image.jpg +├── app.js +└── style.css +``` + +Referéncielos normalmente: + +```latte +{* Estos archivos se copian tal cual *} + + +``` + +Para archivos públicos, puede usar las características de FilesystemMapper: + +```neon +assets: + mapping: + default: + type: vite + path: assets + extension: [webp, jpg, png] # Probar WebP primero + versioning: true # Añadir eliminación de caché +``` + +En la configuración `vite.config.ts` puede cambiar la carpeta pública usando la opción `publicDir`. + + +Importaciones Dinámicas +======================= + +Vite divide automáticamente el código para una carga óptima. Las importaciones dinámicas le permiten cargar código solo cuando realmente se necesita, reduciendo el tamaño inicial del paquete: + +```js +// Cargar componentes pesados bajo demanda +button.addEventListener('click', async () => { + let { Chart } = await import('./components/chart.js') + new Chart(data) +}) +``` + +Las importaciones dinámicas crean fragmentos separados que se cargan solo cuando son necesarios. Esto se llama "división de código" y es una de las características más potentes de Vite. Cuando utiliza importaciones dinámicas, Vite crea automáticamente archivos JavaScript separados para cada módulo importado dinámicamente. + +La etiqueta `{asset 'app.js'}` **no** precarga automáticamente estos fragmentos dinámicos. Este es un comportamiento intencional, no queremos descargar código que quizás nunca se use. Los fragmentos se descargan solo cuando se ejecuta la importación dinámica. + +Sin embargo, si sabe que ciertas importaciones dinámicas son críticas y se necesitarán pronto, puede precargarlas: + +```latte +{* Punto de entrada principal *} +{asset 'app.js'} + +{* Precargar importaciones dinámicas críticas *} +{preload 'components/chart.js'} +``` + +Esto le dice al navegador que descargue el componente del gráfico en segundo plano, para que esté listo inmediatamente cuando sea necesario. + + +Soporte de TypeScript +===================== + +TypeScript funciona de forma inmediata: + +```ts +// assets/main.ts +interface User { + name: string + email: string +} + +export function greetUser(user: User): void { + console.log(`Hello, ${user.name}!`) +} +``` + +Referencie los archivos TypeScript normalmente: + +```latte +{asset 'main.ts'} +``` + +Para un soporte completo de TypeScript, instálelo: + +```shell +npm install -D typescript +``` + + +Configuración Adicional de Vite +=============================== + +Aquí tiene algunas opciones de configuración útiles de Vite con explicaciones detalladas: + +```js +export default defineConfig({ + // Directorio raíz que contiene los assets fuente + root: 'assets', + + // Carpeta cuyo contenido se copia al directorio de salida tal cual + // Por defecto: 'public' (relativo a 'root') + publicDir: 'public', + + build: { + // Dónde colocar los archivos compilados (relativo a 'root') + outDir: '../www/assets', + + // ¿Vaciar el directorio de salida antes de construir? + // Útil para eliminar archivos antiguos de construcciones anteriores + emptyOutDir: true, + + // Subdirectorio dentro de outDir para los fragmentos y assets generados + // Esto ayuda a organizar la estructura de salida + assetsDir: 'static', + + rollupOptions: { + // Punto(s) de entrada - puede ser un solo archivo o un array de archivos + // Cada punto de entrada se convierte en un paquete separado + input: [ + 'app.js', // aplicación principal + 'admin.js', // panel de administración + ], + }, + }, + + server: { + // Host al que enlazar el servidor de desarrollo + // Usar '0.0.0.0' para exponer a la red + host: 'localhost', + + // Puerto para el servidor de desarrollo + port: 5173, + + // Configuración CORS para solicitudes de origen cruzado + cors: { + origin: 'http://myapp.local', + }, + }, + + css: { + // Habilitar mapas de origen CSS en desarrollo + devSourcemap: true, + }, + + plugins: [ + nette(), + ], +}); +``` + +¡Eso es todo! Ahora tiene un sistema de construcción moderno integrado con Nette Assets. diff --git a/assets/fr/@home.texy b/assets/fr/@home.texy new file mode 100644 index 0000000000..86e519921a --- /dev/null +++ b/assets/fr/@home.texy @@ -0,0 +1,432 @@ +Nette Assets +************ + +
    + +Fatigué de gérer manuellement les fichiers statiques dans vos applications web ? Oubliez le codage en dur des chemins, la gestion de l'invalidation du cache ou les soucis de versioning des fichiers. Nette Assets transforme la façon dont vous travaillez avec les images, les feuilles de style, les scripts et autres ressources statiques. + +- Le **versioning intelligent** garantit que les navigateurs chargent toujours les derniers fichiers +- La **détection automatique** des types de fichiers et des dimensions +- L'**intégration transparente de Latte** avec des balises intuitives +- Une **architecture flexible** supportant les systèmes de fichiers, les CDN et Vite +- Le **chargement paresseux** pour des performances optimales + +
    + + +Pourquoi Nette Assets ? +======================= + +Travailler avec des fichiers statiques signifie souvent un code répétitif et sujet aux erreurs. Vous construisez manuellement des URL, ajoutez des paramètres de version pour le cache busting et gérez différemment les différents types de fichiers. Cela conduit à un code comme : + +```html +Logo + +``` + +Avec Nette Assets, toute cette complexité disparaît : + +```latte +{* Tout est automatisé - URL, versioning, dimensions *} + + + +{* Ou simplement *} +{asset 'css/style.css'} +``` + +C'est tout ! La bibliothèque automatiquement : +- Ajoute des paramètres de version basés sur l'heure de modification du fichier +- Détecte les dimensions des images et les inclut dans le HTML +- Génère l'élément HTML correct pour chaque type de fichier +- Gère les environnements de développement et de production + + +Installation +============ + +Installez Nette Assets en utilisant [Composer|best-practices:composer] : + +```shell +composer require nette/assets +``` + +Il nécessite PHP 8.1 ou supérieur et fonctionne parfaitement avec Nette Framework, mais peut également être utilisé de manière autonome. + + +Premiers pas +============ + +Nette Assets fonctionne dès la première utilisation sans aucune configuration. Placez vos fichiers statiques dans le répertoire `www/assets/` et commencez à les utiliser : + +```latte +{* Affiche une image avec des dimensions automatiques *} +{asset 'logo.png'} + +{* Inclut une feuille de style avec versioning *} +{asset 'style.css'} + +{* Charge un module JavaScript *} +{asset 'app.js'} +``` + +Pour plus de contrôle sur le HTML généré, utilisez l'attribut `n:asset` ou la fonction `asset()`. + + +Comment ça marche +================= + +Nette Assets est construit autour de trois concepts fondamentaux qui le rendent puissant et simple à utiliser : + + +Assets - Vos fichiers rendus intelligents +----------------------------------------- + +Un **asset** représente tout fichier statique dans votre application. Chaque fichier devient un objet avec des propriétés en lecture seule utiles : + +```php +$image = $assets->getAsset('photo.jpg'); +echo $image->url; // '/assets/photo.jpg?v=1699123456' +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' +``` + +Différents types de fichiers fournissent différentes propriétés : +- **Images** : largeur, hauteur, texte alternatif, chargement paresseux +- **Scripts** : type de module, hachages d'intégrité, crossorigin +- **Feuilles de style** : requêtes média, intégrité +- **Audio/Vidéo** : durée, dimensions +- **Polices** : préchargement correct avec CORS + +La bibliothèque détecte automatiquement les types de fichiers et crée la classe d'asset appropriée. + + +Mappers - D'où viennent les fichiers +------------------------------------ + +Un **mapper** sait comment trouver des fichiers et créer des URL pour eux. Vous pouvez avoir plusieurs mappers à des fins différentes - fichiers locaux, CDN, stockage cloud, ou outils de build (chacun d'eux a un nom). Le `FilesystemMapper` intégré gère les fichiers locaux, tandis que `ViteMapper` s'intègre aux outils de build modernes. + +Les mappers sont définis dans la [configuration]. + + +Registry - Votre interface principale +------------------------------------- + +Le **registry** gère tous les mappers et fournit l'API principale : + +```php +// Injecte le registry dans votre service +public function __construct( + private Nette\Assets\Registry $assets +) {} + +// Obtient les assets de différents mappers +$logo = $this->assets->getAsset('images:logo.png'); // mapper 'image' +$app = $this->assets->getAsset('app:main.js'); // mapper 'app' +$style = $this->assets->getAsset('style.css'); // utilise le mapper par défaut +``` + +Le registry sélectionne automatiquement le bon mapper et met en cache les résultats pour des raisons de performance. + + +Travailler avec les Assets en PHP +================================= + +Le Registry fournit deux méthodes pour récupérer les assets : + +```php +// Lance Nette\Assets\AssetNotFoundException si le fichier n'existe pas +$logo = $assets->getAsset('logo.png'); + +// Retourne null si le fichier n'existe pas +$banner = $assets->tryGetAsset('banner.jpg'); +if ($banner) { + echo $banner->url; +} +``` + + +Spécifier les Mappers +--------------------- + +Vous pouvez choisir explicitement quel mapper utiliser : + +```php +// Utilise le mapper par défaut +$file = $assets->getAsset('document.pdf'); + +// Utilise un mapper spécifique avec un préfixe +$image = $assets->getAsset('images:photo.jpg'); + +// Utilise un mapper spécifique avec la syntaxe de tableau +$script = $assets->getAsset(['scripts', 'app.js']); +``` + + +Propriétés et Types d'Asset +--------------------------- + +Chaque type d'asset fournit des propriétés en lecture seule pertinentes : + +```php +// Propriétés de l'image +$image = $assets->getAsset('photo.jpg'); +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' + +// Propriétés du script +$script = $assets->getAsset('app.js'); +echo $script->type; // 'module' ou null + +// Propriétés audio +$audio = $assets->getAsset('song.mp3'); +echo $audio->duration; // durée en secondes + +// Tous les assets peuvent être convertis en chaîne (retourne l'URL) +$url = (string) $assets->getAsset('document.pdf'); +``` + +.[note] +Les propriétés comme les dimensions ou la durée ne sont chargées paresseusement que lorsqu'elles sont accédées, ce qui maintient la bibliothèque rapide. + + +Utilisation des Assets dans les Templates Latte +=============================================== + +Nette Assets offre une intégration intuitive de [Latte|latte:] avec des balises et des fonctions. + + +`{asset}` +--------- + +La balise `{asset}` rend des éléments HTML complets : + +```latte +{* Rend : *} +{asset 'hero.jpg'} + +{* Rend : *} +{asset 'app.js'} + +{* Rend : *} +{asset 'style.css'} +``` + +La balise automatiquement : +- Détecte le type d'asset et génère le HTML approprié +- Inclut le versioning pour le cache busting +- Ajoute les dimensions pour les images +- Définit les attributs corrects (type, media, etc.) + +Lorsqu'elle est utilisée à l'intérieur d'attributs HTML, elle ne produit que l'URL : + +```latte +
    + +``` + + +`n:asset` +--------- + +Pour un contrôle total sur les attributs HTML : + +```latte +{* L'attribut n:asset remplit src, les dimensions, etc. *} +Product + +{* Fonctionne avec tout élément pertinent *} + + + +``` + +Utilisez des variables et des mappers : + +```latte +{* Les variables fonctionnent naturellement *} + + +{* Spécifiez le mapper avec des accolades *} + + +{* Spécifiez le mapper avec la notation de tableau *} + +``` + + +`asset()` +--------- + +Pour une flexibilité maximale, utilisez la fonction `asset()` : + +```latte +{var $logo = asset('logo.png')} +width} height={$logo->height}> + +{* Ou directement *} +Logo +``` + + +Assets optionnels +----------------- + +Gérez les assets manquants avec `{asset?}`, `n:asset?` et `tryAsset()` : + +```latte +{* Balise optionnelle - ne rend rien si l'asset est manquant *} +{asset? 'optional-banner.jpg'} + +{* Attribut optionnel - saute si l'asset est manquant *} +Avatar + +{* Avec un fallback *} +{var $avatar = tryAsset('user-avatar.jpg') ?? asset('default-avatar.jpg')} +Avatar +``` + + +`{preload}` +----------- + +Améliorez les performances de chargement de page : + +```latte +{* Dans votre section *} +{preload 'critical.css'} +{preload 'important-font.woff2'} +{preload 'hero-image.jpg'} +``` + +Génère les liens de préchargement appropriés : + +```html + + + +``` + + +Fonctionnalités avancées +======================== + + +Détection automatique d'extension +--------------------------------- + +Gérez automatiquement plusieurs formats : + +```neon +assets: + mapping: + images: + path: img + extension: [webp, jpg, png] # Essayer dans l'ordre +``` + +Maintenant, vous pouvez demander sans extension : + +```latte +{* Trouve logo.webp, logo.jpg, ou logo.png automatiquement *} +{asset 'images:logo'} +``` + +Parfait pour l'amélioration progressive avec les formats modernes. + + +Versioning intelligent +---------------------- + +Les fichiers sont automatiquement versionnés en fonction de l'heure de modification : + +```latte +{asset 'style.css'} +{* Sortie : *} +``` + +Lorsque vous mettez à jour le fichier, l'horodatage change, forçant le rafraîchissement du cache du navigateur. + +Contrôlez le versioning par asset : + +```php +// Désactive le versioning pour un asset spécifique +$asset = $assets->getAsset('style.css', ['version' => false]); + +// Dans Latte +{asset 'style.css', version: false} +``` + + +Assets de police +---------------- + +Les polices bénéficient d'un traitement spécial avec un CORS approprié : + +```latte +{* Préchargement correct avec crossorigin *} +{preload 'fonts:OpenSans-Regular.woff2'} + +{* Utilisation dans CSS *} + +``` + + +Mappers personnalisés +===================== + +Créez des mappers personnalisés pour des besoins spéciaux comme le stockage cloud ou la génération dynamique : + +```php +use Nette\Assets\Mapper; +use Nette\Assets\Asset; +use Nette\Assets\Helpers; + +class CloudStorageMapper implements Mapper +{ + public function __construct( + private CloudClient $client, + private string $bucket, + ) {} + + public function getAsset(string $reference, array $options = []): Asset + { + if (!$this->client->exists($this->bucket, $reference)) { + throw new Nette\Assets\AssetNotFoundException("Asset '$reference' not found"); + } + + $url = $this->client->getPublicUrl($this->bucket, $reference); + return Helpers::createAssetFromUrl($url); + } +} +``` + +Enregistrez dans la configuration : + +```neon +assets: + mapping: + cloud: CloudStorageMapper(@cloudClient, 'my-bucket') +``` + +Utilisez comme tout autre mapper : + +```latte +{asset 'cloud:user-uploads/photo.jpg'} +``` + +La méthode `Helpers::createAssetFromUrl()` crée automatiquement le type d'asset correct en fonction de l'extension du fichier. + + +Pour en savoir plus +=================== + +- [Nette Assets : Enfin une API unifiée pour tout, des images à Vite |https://blog.nette.org/en/introducing-nette-assets] diff --git a/assets/fr/@left-menu.texy b/assets/fr/@left-menu.texy new file mode 100644 index 0000000000..0b9d2f0212 --- /dev/null +++ b/assets/fr/@left-menu.texy @@ -0,0 +1,5 @@ +Nette Assets +************ +- [Démarrage rapide |@home] +- [Vite |vite] +- [Configuration |Configuration] diff --git a/assets/fr/@meta.texy b/assets/fr/@meta.texy new file mode 100644 index 0000000000..72ae4b8db8 --- /dev/null +++ b/assets/fr/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentation Nette}} diff --git a/assets/fr/configuration.texy b/assets/fr/configuration.texy new file mode 100644 index 0000000000..dc9645645d --- /dev/null +++ b/assets/fr/configuration.texy @@ -0,0 +1,188 @@ +Configuration des Assets +************************ + +.[perex] +Aperçu des options de configuration pour Nette Assets. + + +```neon +assets: + # chemin de base pour la résolution des chemins relatifs des mappers + basePath: ... # (string) par défaut à %wwwDir% + + # URL de base pour la résolution des URL relatives des mappers + baseUrl: ... # (string) par défaut à %baseUrl% + + # activer le versioning des assets globalement ? + versioning: ... # (bool) par défaut à true + + # définit les mappers d'assets + mapping: ... # (array) par défaut au chemin 'assets' +``` + +Le `basePath` définit le répertoire de système de fichiers par défaut pour la résolution des chemins relatifs dans les mappers. Par défaut, il utilise le répertoire web (`%wwwDir%`). + +Le `baseUrl` définit le préfixe d'URL par défaut pour la résolution des URL relatives dans les mappers. Par défaut, il utilise l'URL racine (`%baseUrl%`). + +L'option `versioning` contrôle globalement si les paramètres de version sont ajoutés aux URL des assets pour l'invalidation du cache. Les mappers individuels peuvent outrepasser ce paramètre. + + +Mappers +------- + +Les mappers peuvent être configurés de trois manières : notation de chaîne simple, notation de tableau détaillée, ou comme référence à un service. + +La manière la plus simple de définir un mapper : + +```neon +assets: + mapping: + default: assets # Crée un mapper de système de fichiers pour %wwwDir%/assets/ + images: img # Crée un mapper de système de fichiers pour %wwwDir%/img/ + scripts: js # Crée un mapper de système de fichiers pour %wwwDir%/js/ +``` + +Chaque mapper crée un `FilesystemMapper` qui : +- Cherche les fichiers dans `%wwwDir%/` +- Génère des URL comme `%baseUrl%/` +- Hérite du paramètre de versioning global + + +Pour plus de contrôle, utilisez la notation détaillée : + +```neon +assets: + mapping: + images: + # répertoire où les fichiers sont stockés + path: ... # (string) optionnel, par défaut à '' + + # préfixe d'URL pour les liens générés + url: ... # (string) optionnel, par défaut à path + + # activer le versioning pour ce mapper ? + versioning: ... # (bool) optionnel, hérite du paramètre global + + # ajouter automatiquement l'extension (les extensions) lors de la recherche de fichiers + extension: ... # (string|array) optionnel, par défaut à null +``` + +Comprendre comment les valeurs de configuration sont résolues : + +Résolution de chemin : + - Les chemins relatifs sont résolus à partir de `basePath` (ou `%wwwDir%` si `basePath` n'est pas défini) + - Les chemins absolus sont utilisés tels quels + +Résolution d'URL : + - Les URL relatives sont résolues à partir de `baseUrl` (ou `%baseUrl%` si `baseUrl` n'est pas défini) + - Les URL absolues (avec schéma ou `//`) sont utilisées telles quelles + - Si `url` n'est pas spécifié, il utilise la valeur de `path` + + +```neon +assets: + basePath: /var/www/project/www + baseUrl: https://example.com/assets + + mapping: + # Chemin et URL relatifs + images: + path: img # Résolu en : /var/www/project/www/img + url: images # Résolu en : https://example.com/assets/images + + # Chemin et URL absolus + uploads: + path: /var/shared/uploads # Utilisé tel quel : /var/shared/uploads + url: https://cdn.example.com # Utilisé tel quel : https://cdn.example.com + + # Seul le chemin est spécifié + styles: + path: css # Chemin : /var/www/project/www/css + # URL : https://example.com/assets/css +``` + + +Mappers personnalisés +--------------------- + +Pour les mappers personnalisés, référencez ou définissez un service : + +```neon +services: + s3mapper: App\Assets\S3Mapper(%s3.bucket%) + +assets: + mapping: + cloud: @s3mapper + database: App\Assets\DatabaseMapper(@database.connection) +``` + + +Vite Mapper +----------- + +Le mapper Vite ne nécessite que l'ajout de `type: vite`. Voici une liste complète des options de configuration : + +```neon +assets: + mapping: + default: + # type de mapper (obligatoire pour Vite) + type: vite # (string) obligatoire, doit être 'vite' + + # répertoire de sortie de la build Vite + path: ... # (string) optionnel, par défaut à '' + + # préfixe d'URL pour les assets construits + url: ... # (string) optionnel, par défaut à path + + # emplacement du fichier manifest de Vite + manifest: ... # (string) optionnel, par défaut à /.vite/manifest.json + + # configuration du serveur de développement Vite + devServer: ... # (bool|string) optionnel, par défaut à true + + # versioning pour les fichiers du répertoire public + versioning: ... # (bool) optionnel, hérite du paramètre global + + # auto-extension pour les fichiers du répertoire public + extension: ... # (string|array) optionnel, par défaut à null +``` + +L'option `devServer` contrôle la manière dont les assets sont chargés pendant le développement : + +- `true` (par défaut) - Détecte automatiquement le serveur de développement Vite sur l'hôte et le port actuels. Si le serveur de développement est en cours d'exécution **et que votre application est en mode débogage**, les assets sont chargés à partir de celui-ci avec le support du rechargement à chaud des modules (HMR). Si le serveur de développement n'est pas en cours d'exécution, les assets sont chargés à partir des fichiers construits dans le répertoire public. +- `false` - Désactive complètement l'intégration du serveur de développement. Les assets sont toujours chargés à partir des fichiers construits. +- URL personnalisée (par exemple, `https://localhost:5173`) - Spécifiez manuellement l'URL du serveur de développement, y compris le protocole et le port. Utile lorsque le serveur de développement s'exécute sur un hôte ou un port différent. + +Les options `versioning` et `extension` s'appliquent uniquement aux fichiers du répertoire public de Vite qui ne sont pas traités par Vite. + + +Configuration manuelle +---------------------- + +Lorsque vous n'utilisez pas Nette DI, configurez les mappers manuellement : + +```php +use Nette\Assets\Registry; +use Nette\Assets\FilesystemMapper; +use Nette\Assets\ViteMapper; + +$registry = new Registry; + +// Ajoute un mapper de système de fichiers +$registry->addMapper('images', new FilesystemMapper( + baseUrl: 'https://example.com/img', + basePath: __DIR__ . '/www/img', + extensions: ['webp', 'jpg', 'png'], + versioning: true, +)); + +// Ajoute un mapper Vite +$registry->addMapper('app', new ViteMapper( + baseUrl: '/build', + basePath: __DIR__ . '/www/build', + manifestPath: __DIR__ . '/www/build/.vite/manifest.json', + devServer: 'https://localhost:5173', +)); +``` diff --git a/assets/fr/vite.texy b/assets/fr/vite.texy new file mode 100644 index 0000000000..49be69d96e --- /dev/null +++ b/assets/fr/vite.texy @@ -0,0 +1,508 @@ +Intégration de Vite +******************* + +
    + +Les applications JavaScript modernes nécessitent des outils de build sophistiqués. Nette Assets offre une intégration de première classe avec [Vite |https://vitejs.dev/], l'outil de build frontend de nouvelle génération. Obtenez un développement ultra-rapide avec le Hot Module Replacement (HMR) et des builds de production optimisées sans tracas de configuration. + +- **Zéro configuration** - pont automatique entre Vite et les templates PHP +- **Gestion complète des dépendances** - une seule balise gère tous les assets +- **Hot Module Replacement** - mises à jour instantanées de JavaScript et CSS +- **Builds de production optimisées** - code splitting et tree shaking + +
    + + +Nette Assets s'intègre parfaitement à Vite, vous bénéficiez donc de tous ces avantages tout en écrivant vos templates comme d'habitude. + + +Configuration de Vite +===================== + +Configurons Vite étape par étape. Ne vous inquiétez pas si vous débutez avec les outils de build - nous allons tout vous expliquer ! + + +Étape 1 : Installer Vite +------------------------ + +Tout d'abord, installez Vite et le plugin Nette dans votre projet : + +```shell +npm install -D vite @nette/vite-plugin +``` + +Ceci installe Vite et un plugin spécial qui aide Vite à fonctionner parfaitement avec Nette. + + +Étape 2 : Structure du projet +----------------------------- + +L'approche standard consiste à placer les fichiers d'assets source dans un dossier `assets/` à la racine de votre projet, et les versions compilées dans `www/assets/` : + +/--pre +web-project/ +├── assets/ ← fichiers source (SCSS, TypeScript, images source) +│ ├── public/ ← fichiers statiques (copiés tels quels) +│ │ └── favicon.ico +│ ├── images/ +│ │ └── logo.png +│ ├── app.js ← point d'entrée principal +│ └── style.css ← vos styles +└── www/ ← répertoire public (racine du document) + ├── assets/ ← les fichiers compilés iront ici + └── index.php +\-- + +Le dossier `assets/` contient vos fichiers source - le code que vous écrivez. Vite traitera ces fichiers et placera les versions compilées dans `www/assets/`. + + +Étape 3 : Configurer Vite +------------------------- + +Créez un fichier `vite.config.ts` à la racine de votre projet. Ce fichier indique à Vite où trouver vos fichiers source et où placer les fichiers compilés. + +Le plugin Nette Vite est livré avec des valeurs par défaut intelligentes qui simplifient la configuration. Il suppose que vos fichiers source front-end se trouvent dans le répertoire `assets/` (option `root`) et que les fichiers compilés vont dans `www/assets/` (option `outDir`). Vous n'avez qu'à spécifier le [point d'entrée|#Points d'entrée] : + +```js +import { defineConfig } from 'vite'; +import nette from '@nette/vite-plugin'; + +export default defineConfig({ + plugins: [ + nette({ + entry: 'app.js', + }), + ], +}); +``` + +Si vous souhaitez spécifier un autre nom de répertoire pour construire vos assets, vous devrez modifier quelques options : + +```js +export default defineConfig({ + root: 'assets', // répertoire racine des assets source + + build: { + outDir: '../www/assets', // où vont les fichiers compilés + }, + + // ... autre configuration ... +}); +``` + +.[note] +Le chemin `outDir` est considéré comme relatif à `root`, c'est pourquoi il y a `../` au début. + + +Étape 4 : Configurer Nette +-------------------------- + +Indiquez à Nette Assets l'intégration de Vite dans votre `common.neon` : + +```neon +assets: + mapping: + default: + type: vite # indique à Nette d'utiliser le ViteMapper + path: assets +``` + + +Étape 5 : Ajouter des scripts +----------------------------- + +Ajoutez ces scripts à votre `package.json` : + +```json +{ + "scripts": { + "dev": "vite", + "build": "vite build" + } +} +``` + +Maintenant, vous pouvez : +- `npm run dev` - démarrer le serveur de développement avec rechargement à chaud +- `npm run build` - créer des fichiers de production optimisés + + +Points d'entrée +=============== + +Un **point d'entrée** est le fichier principal où votre application démarre. À partir de ce fichier, vous importez d'autres fichiers (CSS, modules JavaScript, images), créant ainsi un arbre de dépendances. Vite suit ces importations et regroupe tout. + +Exemple de point d'entrée `assets/app.js` : + +```js +// Importe les styles +import './style.css' + +// Importe les modules JavaScript +import netteForms from 'nette-forms'; +import naja from 'naja'; + +// Initialise votre application +netteForms.initOnLoad(); +naja.initialize(); +``` + +Dans le template, vous pouvez insérer un point d'entrée comme suit : + +```latte +{asset 'app.js'} +``` + +Nette Assets génère automatiquement toutes les balises HTML nécessaires - JavaScript, CSS et toutes les autres dépendances. + + +Points d'entrée multiples +------------------------- + +Les applications plus grandes ont souvent besoin de points d'entrée séparés : + +```js +export default defineConfig({ + plugins: [ + nette({ + entry: [ + 'app.js', // pages publiques + 'admin.js', // panneau d'administration + ], + }), + ], +}); +``` + +Utilisez-les dans différents templates : + +```latte +{* Dans les pages publiques *} +{asset 'app.js'} + +{* Dans le panneau d'administration *} +{asset 'admin.js'} +``` + + +Important : Fichiers source vs compilés +--------------------------------------- + +Il est crucial de comprendre qu'en production, vous ne pouvez charger que : + +1. Les **points d'entrée** définis dans `entry` +2. Les **fichiers du répertoire `assets/public/`** + +Vous **ne pouvez pas** charger en utilisant `{asset}` des fichiers arbitraires depuis `assets/` - seulement les assets référencés par des fichiers JavaScript ou CSS. Si votre fichier n'est référencé nulle part, il ne sera pas compilé. Si vous voulez que Vite soit conscient d'autres assets, vous pouvez les déplacer vers le [dossier public|#Dossier public]. + +Veuillez noter que par défaut, Vite intégrera tous les assets de moins de 4 Ko, vous ne pourrez donc pas référencer ces fichiers directement. (Voir la [documentation Vite |https://vite.dev/guide/assets.html]). + +```latte +{* ✓ Cela fonctionne - c'est un point d'entrée *} +{asset 'app.js'} + +{* ✓ Cela fonctionne - c'est dans assets/public/ *} +{asset 'favicon.ico'} + +{* ✗ Cela ne fonctionnera pas - fichier aléatoire dans assets/ *} +{asset 'components/button.js'} +``` + + +Mode développement +================== + +Le mode développement est entièrement optionnel mais offre des avantages significatifs lorsqu'il est activé. Le principal avantage est le **Hot Module Replacement (HMR)** - voyez les changements instantanément sans perdre l'état de l'application, rendant l'expérience de développement beaucoup plus fluide et rapide. + +Vite est un outil de build moderne qui rend le développement incroyablement rapide. Contrairement aux bundlers traditionnels, Vite sert votre code directement au navigateur pendant le développement, ce qui signifie un démarrage instantané du serveur quelle que soit la taille de votre projet et des mises à jour ultra-rapides. + + +Démarrage du serveur de développement +------------------------------------- + +Lancez le serveur de développement : + +```shell +npm run dev +``` + +Vous verrez : + +``` + ➜ Local: http://localhost:5173/ + ➜ Network: use --host to expose +``` + +Gardez ce terminal ouvert pendant le développement. + +Le plugin Nette Vite détecte automatiquement quand : +1. Le serveur de développement Vite est en cours d'exécution +2. Votre application Nette est en mode débogage + +Lorsque ces deux conditions sont remplies, Nette Assets charge les fichiers depuis le serveur de développement Vite au lieu du répertoire compilé : + +```latte +{asset 'app.js'} +{* En développement : *} +{* En production : *} +``` + +Aucune configuration nécessaire - ça marche tout seul ! + + +Travailler sur différents domaines +---------------------------------- + +Si votre serveur de développement s'exécute sur autre chose que `localhost` (comme `myapp.local`), vous pourriez rencontrer des problèmes de CORS (Cross-Origin Resource Sharing). CORS est une fonctionnalité de sécurité des navigateurs web qui bloque par défaut les requêtes entre différents domaines. Lorsque votre application PHP s'exécute sur `myapp.local` mais que Vite s'exécute sur `localhost:5173`, le navigateur les considère comme des domaines différents et bloque les requêtes. + +Vous avez deux options pour résoudre ce problème : + +**Option 1 : Configurer CORS** + +La solution la plus simple est d'autoriser les requêtes cross-origin depuis votre application PHP : + +```js +export default defineConfig({ + // ... autre configuration ... + + server: { + cors: { + origin: 'http://myapp.local', // l'URL de votre application PHP + }, + }, +}); +``` +**Option 2 : Exécuter Vite sur votre domaine** + +L'autre solution est de faire en sorte que Vite s'exécute sur le même domaine que votre application PHP. + +```js +export default defineConfig({ + // ... autre configuration ... + + server: { + host: 'myapp.local', // le même que votre application PHP + }, +}); +``` + +En fait, même dans ce cas, vous devez configurer CORS car le serveur de développement s'exécute sur le même nom d'hôte mais sur un port différent. Cependant, dans ce cas, CORS est automatiquement configuré par le plugin Nette Vite. + + +Développement HTTPS +------------------- + +Si vous développez en HTTPS, vous avez besoin de certificats pour votre serveur de développement Vite. Le moyen le plus simple est d'utiliser un plugin qui génère automatiquement des certificats : + +```shell +npm install -D vite-plugin-mkcert +``` + +Voici comment le configurer dans `vite.config.ts` : + +```js +import mkcert from 'vite-plugin-mkcert'; + +export default defineConfig({ + // ... autre configuration ... + + plugins: [ + mkcert(), // génère automatiquement des certificats et active https + nette(), + ], +}); +``` + +Notez que si vous utilisez la configuration CORS (Option 1 ci-dessus), vous devez mettre à jour l'URL d'origine pour utiliser `https://` au lieu de `http://`. + + +Builds de production +==================== + +Créez des fichiers de production optimisés : + +```shell +npm run build +``` + +Vite va : +- Minifier tout le JavaScript et le CSS +- Diviser le code en morceaux optimaux +- Générer des noms de fichiers hachés pour le cache-busting +- Créer un fichier manifest pour Nette Assets + +Exemple de sortie : + +``` +www/assets/ +├── app-4f3a2b1c.js # Votre JavaScript principal (minifié) +├── app-7d8e9f2a.css # CSS extrait (minifié) +├── vendor-8c4b5e6d.js # Dépendances partagées +└── .vite/ + └── manifest.json # Mappage pour Nette Assets +``` + +Les noms de fichiers hachés garantissent que les navigateurs chargent toujours la dernière version. + + +Dossier public +============== + +Les fichiers du répertoire `assets/public/` sont copiés dans la sortie sans traitement : + +``` +assets/ +├── public/ +│ ├── favicon.ico +│ ├── robots.txt +│ └── images/ +│ └── og-image.jpg +├── app.js +└── style.css +``` + +Référencez-les normalement : + +```latte +{* Ces fichiers sont copiés tels quels *} + + +``` + +Pour les fichiers publics, vous pouvez utiliser les fonctionnalités de FilesystemMapper : + +```neon +assets: + mapping: + default: + type: vite + path: assets + extension: [webp, jpg, png] # Essayer WebP en premier + versioning: true # Ajouter le cache-busting +``` + +Dans la configuration `vite.config.ts`, vous pouvez modifier le dossier public en utilisant l'option `publicDir`. + + +Imports dynamiques +================== + +Vite divise automatiquement le code pour un chargement optimal. Les imports dynamiques vous permettent de charger du code uniquement lorsqu'il est réellement nécessaire, réduisant ainsi la taille initiale du bundle : + +```js +// Charge les composants lourds à la demande +button.addEventListener('click', async () => { + let { Chart } = await import('./components/chart.js') + new Chart(data) +}) +``` + +Les imports dynamiques créent des chunks séparés qui ne sont chargés que lorsque nécessaire. C'est ce qu'on appelle le "code splitting" et c'est l'une des fonctionnalités les plus puissantes de Vite. Lorsque vous utilisez des imports dynamiques, Vite crée automatiquement des fichiers JavaScript séparés pour chaque module importé dynamiquement. + +La balise `{asset 'app.js'}` ne précharge **pas** automatiquement ces chunks dynamiques. C'est un comportement intentionnel - nous ne voulons pas télécharger du code qui pourrait ne jamais être utilisé. Les chunks ne sont téléchargés que lorsque l'import dynamique est exécuté. + +Cependant, si vous savez que certains imports dynamiques sont critiques et seront nécessaires bientôt, vous pouvez les précharger : + +```latte +{* Point d'entrée principal *} +{asset 'app.js'} + +{* Précharge les imports dynamiques critiques *} +{preload 'components/chart.js'} +``` + +Cela indique au navigateur de télécharger le composant de graphique en arrière-plan, afin qu'il soit prêt immédiatement lorsque nécessaire. + + +Support TypeScript +================== + +TypeScript fonctionne dès la première utilisation : + +```ts +// assets/main.ts +interface User { + name: string + email: string +} + +export function greetUser(user: User): void { + console.log(`Hello, ${user.name}!`) +} +``` + +Référencez les fichiers TypeScript normalement : + +```latte +{asset 'main.ts'} +``` + +Pour un support TypeScript complet, installez-le : + +```shell +npm install -D typescript +``` + + +Configuration Vite additionnelle +================================ + +Voici quelques options de configuration Vite utiles avec des explications détaillées : + +```js +export default defineConfig({ + // Répertoire racine contenant les assets source + root: 'assets', + + // Dossier dont le contenu est copié dans le répertoire de sortie tel quel + // Par défaut : 'public' (relatif à 'root') + publicDir: 'public', + + build: { + // Où placer les fichiers compilés (relatif à 'root') + outDir: '../www/assets', + + // Vider le répertoire de sortie avant la construction ? + // Utile pour supprimer les anciens fichiers des builds précédentes + emptyOutDir: true, + + // Sous-répertoire dans outDir pour les chunks et assets générés + // Cela aide à organiser la structure de sortie + assetsDir: 'static', + + rollupOptions: { + // Point(s) d'entrée - peut être un seul fichier ou un tableau de fichiers + // Chaque point d'entrée devient un bundle séparé + input: [ + 'app.js', // application principale + 'admin.js', // panneau d'administration + ], + }, + }, + + server: { + // Hôte sur lequel lier le serveur de développement + // Utilisez '0.0.0.0' pour exposer au réseau + host: 'localhost', + + // Port pour le serveur de développement + port: 5173, + + // Configuration CORS pour les requêtes cross-origin + cors: { + origin: 'http://myapp.local', + }, + }, + + css: { + // Activer les source maps CSS en développement + devSourcemap: true, + }, + + plugins: [ + nette(), + ], +}); +``` + +C'est tout ! Vous avez maintenant un système de build moderne intégré à Nette Assets. diff --git a/assets/hu/@home.texy b/assets/hu/@home.texy new file mode 100644 index 0000000000..03bd6ca91a --- /dev/null +++ b/assets/hu/@home.texy @@ -0,0 +1,432 @@ +Nette Assets +************ + +
    + +Eleged van a statikus fájlok manuális kezeléséből a webalkalmazásaidban? Felejtsd el a hardkódolt útvonalakat, a gyorsítótár érvénytelenítésével kapcsolatos problémákat vagy a fájlverziózással kapcsolatos aggodalmakat. A Nette Assets átalakítja a képekkel, stíluslapokkal, szkriptekkel és más statikus erőforrásokkal való munkát. + +- **Intelligens verziózás** biztosítja, hogy a böngészők mindig a legfrissebb fájlokat töltsék be +- Fájltípusok és dimenziók **automatikus felismerése** +- **Zökkenőmentes Latte integráció** intuitív tagekkel +- **Rugalmas architektúra** fájlrendszerek, CDN-ek és Vite támogatásával +- **Lusta betöltés** az optimális teljesítmény érdekében + +
    + + +Miért a Nette Assets? +===================== + +A statikus fájlokkal való munka gyakran ismétlődő, hibára hajlamos kódot jelent. Manuálisan konstruálsz URL-eket, verzióparamétereket adsz hozzá a gyorsítótár törléséhez, és különböző fájltípusokat eltérően kezelsz. Ez olyan kódhoz vezet, mint: + +```html +Logo + +``` + +A Nette Assets segítségével mindez a bonyolultság eltűnik: + +```latte +{* Minden automatizált - URL, verziózás, dimenziók *} + + + +{* Vagy csak *} +{asset 'css/style.css'} +``` + +Ennyi! A könyvtár automatikusan: +- Hozzáadja a verzióparamétereket a fájl módosítási ideje alapján +- Felismeri a kép dimenzióit és beilleszti azokat a HTML-be +- Létrehozza a megfelelő HTML elemet minden fájltípushoz +- Kezeli a fejlesztői és éles környezeteket is + + +Telepítés +========= + +Telepítsd a Nette Assets-et a [Composer |best-practices:composer] segítségével: + +```shell +composer require nette/assets +``` + +PHP 8.1 vagy újabb verziót igényel, és tökéletesen működik a Nette Frameworkkel, de önállóan is használható. + + +Első lépések +============ + +A Nette Assets konfiguráció nélkül azonnal működik. Helyezd a statikus fájlokat a `www/assets/` könyvtárba, és kezdd el használni őket: + +```latte +{* Kép megjelenítése automatikus dimenziókkal *} +{asset 'logo.png'} + +{* Stíluslap beillesztése verziózással *} +{asset 'style.css'} + +{* JavaScript modul betöltése *} +{asset 'app.js'} +``` + +A generált HTML feletti nagyobb kontroll érdekében használd az `n:asset` attribútumot vagy az `asset()` függvényt. + + +Hogyan működik +============== + +A Nette Assets három alapvető koncepcióra épül, amelyek erőteljessé, mégis egyszerűvé teszik a használatát: + + +Assets - Intelligens fájljaid +----------------------------- + +Az **asset** az alkalmazásodban található bármely statikus fájlt jelenti. Minden fájl egy objektummá válik hasznos csak olvasható tulajdonságokkal: + +```php +$image = $assets->getAsset('photo.jpg'); +echo $image->url; // '/assets/photo.jpg?v=1699123456' +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' +``` + +Különböző fájltípusok különböző tulajdonságokat biztosítanak: +- **Képek**: szélesség, magasság, alternatív szöveg, lusta betöltés +- **Szkriptek**: modul típusa, integritás hash-ek, crossorigin +- **Stíluslapok**: média lekérdezések, integritás +- **Audió/Videó**: időtartam, dimenziók +- **Betűtípusok**: megfelelő előbetöltés CORS-szal + +A könyvtár automatikusan felismeri a fájltípusokat és létrehozza a megfelelő asset osztályt. + + +Mapperek - Honnan jönnek a fájlok +--------------------------------- + +Egy **mapper** tudja, hogyan találja meg a fájlokat és hogyan hozzon létre URL-eket számukra. Több mapper is lehet különböző célokra - helyi fájlok, CDN, felhőtárhely vagy build eszközök (mindegyiknek van neve). A beépített `FilesystemMapper` a helyi fájlokat kezeli, míg a `ViteMapper` integrálódik a modern build eszközökkel. + +A mapperek a [konfigurációban |Configuration] vannak definiálva. + + +Registry - A fő interfészed +--------------------------- + +A **registry** kezeli az összes mappert és biztosítja a fő API-t: + +```php +// Injektáld a registry-t a szolgáltatásodba +public function __construct( + private Nette\Assets\Registry $assets +) {} + +// Assetek lekérése különböző mapperekből +$logo = $this->assets->getAsset('images:logo.png'); // 'image' mapper +$app = $this->assets->getAsset('app:main.js'); // 'app' mapper +$style = $this->assets->getAsset('style.css'); // az alapértelmezett mappert használja +``` + +A registry automatikusan kiválasztja a megfelelő mappert és gyorsítótárazza az eredményeket a teljesítmény érdekében. + + +Assetek használata PHP-ban +========================== + +A Registry két módszert biztosít az assetek lekérésére: + +```php +// Nette\Assets\AssetNotFoundException-t dob, ha a fájl nem létezik +$logo = $assets->getAsset('logo.png'); + +// null-t ad vissza, ha a fájl nem létezik +$banner = $assets->tryGetAsset('banner.jpg'); +if ($banner) { + echo $banner->url; +} +``` + + +Mapperek megadása +----------------- + +Explicit módon kiválaszthatod, melyik mappert használd: + +```php +// Alapértelmezett mapper használata +$file = $assets->getAsset('document.pdf'); + +// Specifikus mapper használata prefixszel +$image = $assets->getAsset('images:photo.jpg'); + +// Specifikus mapper használata tömb szintaxissal +$script = $assets->getAsset(['scripts', 'app.js']); +``` + + +Asset tulajdonságok és típusok +------------------------------ + +Minden asset típus releváns csak olvasható tulajdonságokat biztosít: + +```php +// Kép tulajdonságok +$image = $assets->getAsset('photo.jpg'); +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' + +// Szkript tulajdonságok +$script = $assets->getAsset('app.js'); +echo $script->type; // 'module' vagy null + +// Audió tulajdonságok +$audio = $assets->getAsset('song.mp3'); +echo $audio->duration; // időtartam másodpercben + +// Minden asset stringgé konvertálható (URL-t ad vissza) +$url = (string) $assets->getAsset('document.pdf'); +``` + +.[note] +Az olyan tulajdonságok, mint a dimenziók vagy az időtartam, csak akkor töltődnek be lustán, ha hozzáférnek hozzájuk, így a könyvtár gyors marad. + + +Assetek használata Latte sablonokban +==================================== + +A Nette Assets intuitív [Latte |latte:] integrációt biztosít tagekkel és függvényekkel. + + +`{asset}` +--------- + +Az `{asset}` tag teljes HTML elemeket renderel: + +```latte +{* Renderel: *} +{asset 'hero.jpg'} + +{* Renderel: *} +{asset 'app.js'} + +{* Renderel: *} +{asset 'style.css'} +``` + +A tag automatikusan: +- Felismeri az asset típusát és megfelelő HTML-t generál +- Tartalmazza a verziózást a gyorsítótár törléséhez +- Hozzáadja a dimenziókat a képekhez +- Beállítja a megfelelő attribútumokat (típus, média stb.) + +Ha HTML attribútumokon belül használják, csak az URL-t adja ki: + +```latte +
    + +``` + + +`n:asset` +--------- + +A HTML attribútumok teljes ellenőrzéséhez: + +```latte +{* Az n:asset attribútum kitölti a src-t, dimenziókat stb. *} +Product + +{* Bármely releváns elemmel működik *} + + + +``` + +Használj változókat és mappereket: + +```latte +{* A változók természetesen működnek *} + + +{* Mapper megadása kapcsos zárójelekkel *} + + +{* Mapper megadása tömb jelöléssel *} + +``` + + +`asset()` +--------- + +A maximális rugalmasság érdekében használd az `asset()` függvényt: + +```latte +{var $logo = asset('logo.png')} +width} height={$logo->height}> + +{* Vagy közvetlenül *} +Logo +``` + + +Opcionális assetek +------------------ + +Kezeld a hiányzó asseteket elegánsan a `{asset?}`, `n:asset?` és `tryAsset()` segítségével: + +```latte +{* Opcionális tag - semmit sem renderel, ha az asset hiányzik *} +{asset? 'optional-banner.jpg'} + +{* Opcionális attribútum - kihagyja, ha az asset hiányzik *} +Avatar + +{* Tartalék opcióval *} +{var $avatar = tryAsset('user-avatar.jpg') ?? asset('default-avatar.jpg')} +Avatar +``` + + +`{preload}` +----------- + +Javítsd az oldalbetöltési teljesítményt: + +```latte +{* A szekcióban *} +{preload 'critical.css'} +{preload 'important-font.woff2'} +{preload 'hero-image.jpg'} +``` + +Megfelelő preload linkeket generál: + +```html + + + +``` + + +Haladó funkciók +=============== + + +Kiterjesztés automatikus felismerése +------------------------------------ + +Több formátum kezelése automatikusan: + +```neon +assets: + mapping: + images: + path: img + extension: [webp, jpg, png] # Próbálja sorrendben +``` + +Mostantól kiterjesztés nélkül is kérhetsz: + +```latte +{* Automatikusan megtalálja a logo.webp, logo.jpg vagy logo.png fájlt *} +{asset 'images:logo'} +``` + +Tökéletes a progresszív fejlesztéshez modern formátumokkal. + + +Intelligens verziózás +--------------------- + +A fájlok automatikusan verziózódnak a módosítási idő alapján: + +```latte +{asset 'style.css'} +{* Kimenet: *} +``` + +Amikor frissíted a fájlt, az időbélyeg megváltozik, ami a böngésző gyorsítótárának frissítését kényszeríti. + +Verziózás szabályozása assetenként: + +```php +// Verziózás letiltása specifikus assethez +$asset = $assets->getAsset('style.css', ['version' => false]); + +// Latte-ban +{asset 'style.css', version: false} +``` + + +Betűtípus assetek +----------------- + +A betűtípusok különleges kezelést kapnak megfelelő CORS-szal: + +```latte +{* Megfelelő preload crossorigin-nel *} +{preload 'fonts:OpenSans-Regular.woff2'} + +{* Használat CSS-ben *} + +``` + + +Egyedi mapperek +=============== + +Hozzon létre egyedi mappereket különleges igényekhez, mint például felhőtárhely vagy dinamikus generálás: + +```php +use Nette\Assets\Mapper; +use Nette\Assets\Asset; +use Nette\Assets\Helpers; + +class CloudStorageMapper implements Mapper +{ + public function __construct( + private CloudClient $client, + private string $bucket, + ) {} + + public function getAsset(string $reference, array $options = []): Asset + { + if (!$this->client->exists($this->bucket, $reference)) { + throw new Nette\Assets\AssetNotFoundException("Az asset '$reference' nem található"); + } + + $url = $this->client->getPublicUrl($this->bucket, $reference); + return Helpers::createAssetFromUrl($url); + } +} +``` + +Regisztrálja a konfigurációban: + +```neon +assets: + mapping: + cloud: CloudStorageMapper(@cloudClient, 'my-bucket') +``` + +Használja, mint bármely más mappert: + +```latte +{asset 'cloud:user-uploads/photo.jpg'} +``` + +A `Helpers::createAssetFromUrl()` metódus automatikusan létrehozza a megfelelő asset típust a fájlkiterjesztés alapján. + + +További olvasnivalók +==================== + +- [Nette Assets: Végre egységes API a képektől a Vite-ig mindenhez |https://blog.nette.org/en/introducing-nette-assets] diff --git a/assets/hu/@left-menu.texy b/assets/hu/@left-menu.texy new file mode 100644 index 0000000000..143719af1e --- /dev/null +++ b/assets/hu/@left-menu.texy @@ -0,0 +1,5 @@ +Nette Assets +************ +- [Első lépések |@home] +- [Vite |vite] +- [Konfiguráció |Configuration] diff --git a/assets/hu/@meta.texy b/assets/hu/@meta.texy new file mode 100644 index 0000000000..c172d1cda5 --- /dev/null +++ b/assets/hu/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette dokumentáció}} diff --git a/assets/hu/configuration.texy b/assets/hu/configuration.texy new file mode 100644 index 0000000000..a4c3bac847 --- /dev/null +++ b/assets/hu/configuration.texy @@ -0,0 +1,188 @@ +Assets Konfiguráció +******************* + +.[perex] +A Nette Assets konfigurációs lehetőségeinek áttekintése. + + +```neon +assets: + # alapútvonal a relatív mapper útvonalak feloldásához + basePath: ... # (string) alapértelmezés szerint %wwwDir% + + # alap URL a relatív mapper URL-ek feloldásához + baseUrl: ... # (string) alapértelmezés szerint %baseUrl% + + # asset verziózás engedélyezése globálisan? + versioning: ... # (bool) alapértelmezés szerint true + + # asset mapperek definiálása + mapping: ... # (array) alapértelmezés szerint 'assets' útvonal +``` + +A `basePath` beállítja az alapértelmezett fájlrendszer könyvtárat a mapperek relatív útvonalainak feloldásához. Alapértelmezés szerint a webkönyvtárat (`%wwwDir%`) használja. + +A `baseUrl` beállítja az alapértelmezett URL prefixet a mapperek relatív URL-einek feloldásához. Alapértelmezés szerint a gyökér URL-t (`%baseUrl%`) használja. + +A `versioning` opció globálisan szabályozza, hogy a verzióparaméterek hozzáadódnak-e az asset URL-ekhez a gyorsítótár törléséhez. Az egyes mapperek felülírhatják ezt a beállítást. + + +Mapperek +-------- + +A mapperek háromféleképpen konfigurálhatók: egyszerű string jelöléssel, részletes tömb jelöléssel, vagy egy szolgáltatásra való hivatkozással. + +A mapper definiálásának legegyszerűbb módja: + +```neon +assets: + mapping: + default: assets # Fájlrendszer mappert hoz létre a %wwwDir%/assets/ számára + images: img # Fájlrendszer mappert hoz létre a %wwwDir%/img/ számára + scripts: js # Fájlrendszer mappert hoz létre a %wwwDir%/js/ számára +``` + +Minden mapper létrehoz egy `FilesystemMapper`-t, amely: +- Fájlokat keres a `%wwwDir%/`-ban +- URL-eket generál, mint `%baseUrl%/` +- Örökli a globális verziózási beállítást + + +A nagyobb kontroll érdekében használd a részletes jelölést: + +```neon +assets: + mapping: + images: + # könyvtár, ahol a fájlok tárolódnak + path: ... # (string) opcionális, alapértelmezés szerint '' + + # URL prefix a generált linkekhez + url: ... # (string) opcionális, alapértelmezés szerint path + + # verziózás engedélyezése ehhez a mapperhez? + versioning: ... # (bool) opcionális, örökli a globális beállítást + + # automatikus kiterjesztés(ek) hozzáadása fájlok keresésekor + extension: ... # (string|array) opcionális, alapértelmezés szerint null +``` + +A konfigurációs értékek feloldásának megértése: + +Útvonal feloldás: + - A relatív útvonalak a `basePath`-ból (vagy `%wwwDir%`, ha a `basePath` nincs beállítva) oldódnak fel + - Az abszolút útvonalak változatlanul használatosak + +URL feloldás: + - A relatív URL-ek a `baseUrl`-ből (vagy `%baseUrl%`, ha a `baseUrl` nincs beállítva) oldódnak fel + - Az abszolút URL-ek (sémával vagy `//`) változatlanul használatosak + - Ha az `url` nincs megadva, akkor a `path` értékét használja + + +```neon +assets: + basePath: /var/www/project/www + baseUrl: https://example.com/assets + + mapping: + # Relatív útvonal és URL + images: + path: img # Feloldva: /var/www/project/www/img + url: images # Feloldva: https://example.com/assets/images + + # Abszolút útvonal és URL + uploads: + path: /var/shared/uploads # Változatlanul használva: /var/shared/uploads + url: https://cdn.example.com # Változatlanul használva: https://cdn.example.com + + # Csak az útvonal megadva + styles: + path: css # Útvonal: /var/www/project/www/css + # URL: https://example.com/assets/css +``` + + +Egyedi mapperek +--------------- + +Egyedi mapperek esetén hivatkozzon vagy definiáljon egy szolgáltatást: + +```neon +services: + s3mapper: App\Assets\S3Mapper(%s3.bucket%) + +assets: + mapping: + cloud: @s3mapper + database: App\Assets\DatabaseMapper(@database.connection) +``` + + +Vite Mapper +----------- + +A Vite mapperhez csak a `type: vite` hozzáadása szükséges. Ez a konfigurációs lehetőségek teljes listája: + +```neon +assets: + mapping: + default: + # mapper típus (kötelező a Vite-hez) + type: vite # (string) kötelező, 'vite' kell legyen + + # Vite build kimeneti könyvtár + path: ... # (string) opcionális, alapértelmezés szerint '' + + # URL prefix a beépített assetekhez + url: ... # (string) opcionális, alapértelmezés szerint path + + # Vite manifest fájl helye + manifest: ... # (string) opcionális, alapértelmezés szerint /.vite/manifest.json + + # Vite dev szerver konfiguráció + devServer: ... # (bool|string) opcionális, alapértelmezés szerint true + + # verziózás a public könyvtár fájljaihoz + versioning: ... # (bool) opcionális, örökli a globális beállítást + + # automatikus kiterjesztés a public könyvtár fájljaihoz + extension: ... # (string|array) opcionális, alapértelmezés szerint null +``` + +A `devServer` opció szabályozza, hogyan töltődnek be az assetek fejlesztés közben: + +- `true` (alapértelmezett) - Automatikusan felismeri a Vite dev szervert az aktuális hoston és porton. Ha a dev szerver fut **és az alkalmazásod debug módban van**, az assetek onnan töltődnek be hot module replacement támogatással. Ha a dev szerver nem fut, az assetek a buildelt fájlokból töltődnek be a public könyvtárból. +- `false` - Teljesen letiltja a dev szerver integrációt. Az assetek mindig a buildelt fájlokból töltődnek be. +- Egyedi URL (pl. `https://localhost:5173`) - Manuálisan adja meg a dev szerver URL-jét, beleértve a protokollt és a portot. Hasznos, ha a dev szerver más hoston vagy porton fut. + +Az `versioning` és `extension` opciók csak a Vite public könyvtárában lévő olyan fájlokra vonatkoznak, amelyeket a Vite nem dolgoz fel. + + +Manuális konfiguráció +--------------------- + +Ha nem használja a Nette DI-t, konfigurálja a mappereket manuálisan: + +```php +use Nette\Assets\Registry; +use Nette\Assets\FilesystemMapper; +use Nette\Assets\ViteMapper; + +$registry = new Registry; + +// Fájlrendszer mapper hozzáadása +$registry->addMapper('images', new FilesystemMapper( + baseUrl: 'https://example.com/img', + basePath: __DIR__ . '/www/img', + extensions: ['webp', 'jpg', 'png'], + versioning: true, +)); + +// Vite mapper hozzáadása +$registry->addMapper('app', new ViteMapper( + baseUrl: '/build', + basePath: __DIR__ . '/www/build', + manifestPath: __DIR__ . '/www/build/.vite/manifest.json', + devServer: 'https://localhost:5173', +)); +``` diff --git a/assets/hu/vite.texy b/assets/hu/vite.texy new file mode 100644 index 0000000000..eefb77fdaa --- /dev/null +++ b/assets/hu/vite.texy @@ -0,0 +1,508 @@ +Vite Integráció +*************** + +
    + +A modern JavaScript alkalmazások kifinomult build eszközöket igényelnek. A Nette Assets első osztályú integrációt biztosít a [Vite |https://vitejs.dev/] nevű, következő generációs frontend build eszközzel. Villámgyors fejlesztést érhet el Hot Module Replacement (HMR) funkcióval és optimalizált éles build-ekkel, nulla konfigurációs gonddal. + +- **Nulla konfiguráció** - automatikus híd a Vite és a PHP sablonok között +- **Teljes függőségkezelés** - egyetlen tag kezeli az összes assetet +- **Hot Module Replacement** - azonnali JavaScript és CSS frissítések +- **Optimalizált éles build-ek** - kód felosztás és tree shaking + +
    + + +A Nette Assets zökkenőmentesen integrálódik a Vite-tel, így az összes előnyét élvezheti, miközben a sablonokat a szokásos módon írja. + + +Vite beállítása +=============== + +Állítsuk be a Vite-et lépésről lépésre. Ne aggódj, ha még új vagy a build eszközök terén - mindent elmagyarázunk! + + +1. lépés: Vite telepítése +------------------------- + +Először telepítsd a Vite-et és a Nette plugint a projektedbe: + +```shell +npm install -D vite @nette/vite-plugin +``` + +Ez telepíti a Vite-et és egy speciális plugint, amely segít a Vite-nek tökéletesen működni a Nette-tel. + + +2. lépés: Projektstruktúra +-------------------------- + +A standard megközelítés az, hogy a forrás asset fájlokat a projekt gyökerében lévő `assets/` mappába helyezzük, a fordított verziókat pedig a `www/assets/` mappába: + +/--pre +web-project/ +├── assets/ ← forrásfájlok (SCSS, TypeScript, forrásképek) +│ ├── public/ ← statikus fájlok (változatlanul másolva) +│ │ └── favicon.ico +│ ├── images/ +│ │ └── logo.png +│ ├── app.js ← fő belépési pont +│ └── style.css ← a stíluslapjaid +└── www/ ← nyilvános könyvtár (dokumentum gyökér) + ├── assets/ ← ide kerülnek a fordított fájlok + └── index.php +\-- + +Az `assets/` mappa tartalmazza a forrásfájljaidat - a kódot, amit írsz. A Vite feldolgozza ezeket a fájlokat, és a fordított verziókat a `www/assets/` mappába helyezi. + + +3. lépés: Vite konfigurálása +---------------------------- + +Hozzon létre egy `vite.config.ts` fájlt a projekt gyökerében. Ez a fájl megmondja a Vite-nek, hol találja a forrásfájlokat és hova tegye a fordított fájlokat. + +A Nette Vite plugin intelligens alapértelmezett beállításokkal érkezik, amelyek leegyszerűsítik a konfigurációt. Feltételezi, hogy a frontend forrásfájlok az `assets/` könyvtárban vannak (`root` opció), és a fordított fájlok a `www/assets/` mappába kerülnek (`outDir` opció). Csak a [belépési pontot |#Entry Points] kell megadnia: + +```js +import { defineConfig } from 'vite'; +import nette from '@nette/vite-plugin'; + +export default defineConfig({ + plugins: [ + nette({ + entry: 'app.js', + }), + ], +}); +``` + +Ha másik könyvtárnevet szeretne megadni az assetek buildeléséhez, néhány opciót módosítania kell: + +```js +export default defineConfig({ + root: 'assets', // forrás assetek gyökérkönyvtára + + build: { + outDir: '../www/assets', // ahova a fordított fájlok kerülnek + }, + + // ... egyéb konfiguráció ... +}); +``` + +.[note] +Az `outDir` útvonal a `root`-hoz képest relatív, ezért van `../` az elején. + + +4. lépés: Nette konfigurálása +----------------------------- + +Mondja meg a Nette Assets-nek a Vite-ről a `common.neon` fájlban: + +```neon +assets: + mapping: + default: + type: vite # megmondja a Nette-nek, hogy a ViteMapper-t használja + path: assets +``` + + +5. lépés: Szkriptek hozzáadása +------------------------------ + +Add hozzá ezeket a szkripteket a `package.json` fájlhoz: + +```json +{ + "scripts": { + "dev": "vite", + "build": "vite build" + } +} +``` + +Most már tudsz: +- `npm run dev` - fejlesztői szerver indítása hot reloading-gal +- `npm run build` - optimalizált éles fájlok létrehozása + + +Belépési pontok +=============== + +A **belépési pont** az a fő fájl, ahol az alkalmazásod elindul. Ebből a fájlból importálsz más fájlokat (CSS, JavaScript modulok, képek), létrehozva egy függőségi fát. A Vite követi ezeket az importokat és mindent egybe csomagol. + +Példa belépési pont `assets/app.js`: + +```js +// Stílusok importálása +import './style.css' + +// JavaScript modulok importálása +import netteForms from 'nette-forms'; +import naja from 'naja'; + +// Alkalmazás inicializálása +netteForms.initOnLoad(); +naja.initialize(); +``` + +A sablonban a belépési pontot a következőképpen illesztheti be: + +```latte +{asset 'app.js'} +``` + +A Nette Assets automatikusan generálja az összes szükséges HTML taget - JavaScript, CSS és bármely más függőség. + + +Több belépési pont +------------------ + +Nagyobb alkalmazásoknak gyakran külön belépési pontokra van szükségük: + +```js +export default defineConfig({ + plugins: [ + nette({ + entry: [ + 'app.js', // nyilvános oldalak + 'admin.js', // admin panel + ], + }), + ], +}); +``` + +Használd őket különböző sablonokban: + +```latte +{* Nyilvános oldalakon *} +{asset 'app.js'} + +{* Admin panelen *} +{asset 'admin.js'} +``` + + +Fontos: Forrás vs. fordított fájlok +----------------------------------- + +Fontos megérteni, hogy éles környezetben csak a következőket töltheti be: + +1. A `entry` fájlban definiált **belépési pontok** +2. Fájlok az `assets/public/` könyvtárból + +Nem tölthet be `{asset}` segítségével tetszőleges fájlokat az `assets/` könyvtárból - csak azokat az asseteket, amelyekre JavaScript vagy CSS fájlok hivatkoznak. Ha a fájlra sehol sem hivatkoznak, az nem lesz fordítva. Ha más asseteket is tudatosítani szeretne a Vite-tel, áthelyezheti őket a [public mappa |#public folder]-be. + +Kérjük, vegye figyelembe, hogy alapértelmezés szerint a Vite az összes 4KB-nál kisebb assetet beágyazza, így ezekre a fájlokra nem hivatkozhat közvetlenül. (Lásd [Vite dokumentáció |https://vite.dev/guide/assets.html]). + +```latte +{* ✓ Ez működik - ez egy belépési pont *} +{asset 'app.js'} + +{* ✓ Ez működik - az assets/public/ mappában van *} +{asset 'favicon.ico'} + +{* ✗ Ez nem fog működni - véletlenszerű fájl az assets/ mappában *} +{asset 'components/button.js'} +``` + + +Fejlesztői mód +============== + +A fejlesztői mód teljesen opcionális, de jelentős előnyökkel jár, ha engedélyezve van. A fő előny a **Hot Module Replacement (HMR)** - azonnal láthatja a változásokat az alkalmazás állapotának elvesztése nélkül, ami sokkal simábbá és gyorsabbá teszi a fejlesztési élményt. + +A Vite egy modern build eszköz, amely hihetetlenül gyorssá teszi a fejlesztést. A hagyományos bundlerekkel ellentétben a Vite közvetlenül a böngészőnek szolgálja ki a kódot fejlesztés közben, ami azt jelenti, hogy azonnali szerverindítás történik, függetlenül a projekt méretétől, és villámgyors frissítések. + + +Fejlesztői szerver indítása +--------------------------- + +Futtassa a fejlesztői szervert: + +```shell +npm run dev +``` + +Látni fogja: + +``` + ➜ Local: http://localhost:5173/ + ➜ Network: use --host to expose +``` + +Tartsa nyitva ezt a terminált a fejlesztés során. + +A Nette Vite plugin automatikusan felismeri, ha: +1. A Vite dev szerver fut +2. A Nette alkalmazás debug módban van + +Ha mindkét feltétel teljesül, a Nette Assets a Vite dev szerverről tölti be a fájlokat a fordított könyvtár helyett: + +```latte +{asset 'app.js'} +{* Fejlesztésben: *} +{* Éles környezetben: *} +``` + +Nincs szükség konfigurációra - egyszerűen működik! + + +Különböző domaineken való munka +------------------------------- + +Ha a fejlesztői szervered nem `localhost`-on (például `myapp.local`-on) fut, akkor CORS (Cross-Origin Resource Sharing) problémákkal találkozhatsz. A CORS egy biztonsági funkció a webböngészőkben, amely alapértelmezés szerint blokkolja a különböző domainek közötti kéréseket. Amikor a PHP alkalmazásod `myapp.local`-on fut, de a Vite `localhost:5173`-on, a böngésző ezeket különböző domaineknek tekinti, és blokkolja a kéréseket. + +Két lehetőséged van ennek megoldására: + +**1. opció: CORS konfigurálása** + +A legegyszerűbb megoldás, ha engedélyezi a cross-origin kéréseket a PHP alkalmazásából: + +```js +export default defineConfig({ + // ... egyéb konfiguráció ... + + server: { + cors: { + origin: 'http://myapp.local', // a PHP alkalmazásod URL-je + }, + }, +}); +``` +**2. opció: Futtassa a Vite-et a domainjén** + +A másik megoldás, ha a Vite-et ugyanazon a domainen futtatja, mint a PHP alkalmazását. + +```js +export default defineConfig({ + // ... egyéb konfiguráció ... + + server: { + host: 'myapp.local', // ugyanaz, mint a PHP alkalmazásod + }, +}); +``` + +Valójában ebben az esetben is konfigurálnia kell a CORS-t, mert a dev szerver ugyanazon a hostnéven, de más porton fut. Azonban ebben az esetben a CORS-t a Nette Vite plugin automatikusan konfigurálja. + + +HTTPS fejlesztés +---------------- + +Ha HTTPS-en fejlesztesz, tanúsítványokra lesz szükséged a Vite fejlesztői szerveredhez. A legegyszerűbb módja egy olyan plugin használata, amely automatikusan generál tanúsítványokat: + +```shell +npm install -D vite-plugin-mkcert +``` + +Így konfigurálhatja a `vite.config.ts` fájlban: + +```js +import mkcert from 'vite-plugin-mkcert'; + +export default defineConfig({ + // ... egyéb konfiguráció ... + + plugins: [ + mkcert(), // automatikusan generál tanúsítványokat és engedélyezi a https-t + nette(), + ], +}); +``` + +Ne feledje, hogy ha a CORS konfigurációt használja (az 1. opciót fentebb), akkor frissítenie kell az origin URL-t `https://` használatára `http://` helyett. + + +Éles build-ek +============= + +Hozzon létre optimalizált éles fájlokat: + +```shell +npm run build +``` + +A Vite: +- Minifikálja az összes JavaScriptet és CSS-t +- Optimális részekre osztja a kódot +- Hash-elt fájlneveket generál a gyorsítótár törléséhez +- Létrehoz egy manifest fájlt a Nette Assets számára + +Példa kimenet: + +``` +www/assets/ +├── app-4f3a2b1c.js # A fő JavaScripted (minifikált) +├── app-7d8e9f2a.css # Kinyert CSS (minifikált) +├── vendor-8c4b5e6d.js # Megosztott függőségek +└── .vite/ + └── manifest.json # Leképezés a Nette Assets számára +``` + +A hash-elt fájlnevek biztosítják, hogy a böngészők mindig a legújabb verziót töltsék be. + + +Nyilvános mappa +=============== + +Az `assets/public/` könyvtárban lévő fájlok feldolgozás nélkül másolódnak a kimenetbe: + +``` +assets/ +├── public/ +│ ├── favicon.ico +│ ├── robots.txt +│ └── images/ +│ └── og-image.jpg +├── app.js +└── style.css +``` + +Hivatkozzon rájuk normálisan: + +```latte +{* Ezek a fájlok változatlanul másolódnak *} + + +``` + +Nyilvános fájlokhoz használhatja a FilesystemMapper funkcióit: + +```neon +assets: + mapping: + default: + type: vite + path: assets + extension: [webp, jpg, png] # Először a WebP-t próbálja + versioning: true # Gyorsítótár törlés hozzáadása +``` + +A `vite.config.ts` konfigurációban a `publicDir` opcióval módosíthatja a nyilvános mappát. + + +Dinamikus importok +================== + +A Vite automatikusan felosztja a kódot az optimális betöltés érdekében. A dinamikus importok lehetővé teszik, hogy a kódot csak akkor töltse be, amikor arra ténylegesen szükség van, csökkentve az kezdeti csomagméretet: + +```js +// Nehéz komponensek betöltése igény szerint +button.addEventListener('click', async () => { + let { Chart } = await import('./components/chart.js') + new Chart(data) +}) +``` + +A dinamikus importok külön chunkokat hoznak létre, amelyek csak akkor töltődnek be, amikor ténylegesen szükség van rájuk. Ezt "kód felosztásnak" nevezik, és ez a Vite egyik legerősebb funkciója. Amikor dinamikus importokat használ, a Vite automatikusan külön JavaScript fájlokat hoz létre minden dinamikusan importált modulhoz. + +Az `{asset 'app.js'}` tag **nem** tölti be automatikusan ezeket a dinamikus chunkokat. Ez szándékos viselkedés - nem akarunk olyan kódot letölteni, amelyet esetleg soha nem használnak. A chunkok csak akkor töltődnek le, amikor a dinamikus import végrehajtásra kerül. + +Azonban, ha tudja, hogy bizonyos dinamikus importok kritikusak, és hamarosan szükség lesz rájuk, előtöltheti őket: + +```latte +{* Fő belépési pont *} +{asset 'app.js'} + +{* Kritikus dinamikus importok előtöltése *} +{preload 'components/chart.js'} +``` + +Ez azt mondja a böngészőnek, hogy töltse le a diagramkomponenst a háttérben, így azonnal készen áll, amikor szükség van rá. + + +TypeScript támogatás +==================== + +A TypeScript azonnal működik: + +```ts +// assets/main.ts +interface User { + name: string + email: string +} + +export function greetUser(user: User): void { + console.log(`Hello, ${user.name}!`) +} +``` + +Hivatkozzon a TypeScript fájlokra normálisan: + +```latte +{asset 'main.ts'} +``` + +A teljes TypeScript támogatáshoz telepítse: + +```shell +npm install -D typescript +``` + + +További Vite konfiguráció +========================= + +Íme néhány hasznos Vite konfigurációs opció részletes magyarázattal: + +```js +export default defineConfig({ + // A forrás asseteket tartalmazó gyökérkönyvtár + root: 'assets', + + // Az a mappa, amelynek tartalma változatlanul másolódik a kimeneti könyvtárba + // Alapértelmezett: 'public' (a 'root'-hoz képest relatív) + publicDir: 'public', + + build: { + // Hova kerüljenek a fordított fájlok (a 'root'-hoz képest relatív) + outDir: '../www/assets', + + // Ürítse ki a kimeneti könyvtárat a buildelés előtt? + // Hasznos a régi fájlok eltávolításához az előző buildekből + emptyOutDir: true, + + // Alkönvtár az outDir-en belül a generált chunkok és assetek számára + // Ez segít a kimeneti struktúra rendezésében + assetsDir: 'static', + + rollupOptions: { + // Belépési pont(ok) - lehet egyetlen fájl vagy fájltömb + // Minden belépési pont külön csomaggá válik + input: [ + 'app.js', // fő alkalmazás + 'admin.js', // admin panel + ], + }, + }, + + server: { + // Host, amelyhez a dev szerver kötődik + // Használja a '0.0.0.0'-t a hálózaton való közzétételhez + host: 'localhost', + + // Port a dev szerverhez + port: 5173, + + // CORS konfiguráció a cross-origin kérésekhez + cors: { + origin: 'http://myapp.local', + }, + }, + + css: { + // CSS forrástérképek engedélyezése fejlesztésben + devSourcemap: true, + }, + + plugins: [ + nette(), + ], +}); +``` + +Ennyi! Most már van egy modern build rendszered, amely integrálva van a Nette Assets-szel. diff --git a/assets/it/@home.texy b/assets/it/@home.texy new file mode 100644 index 0000000000..fff3a307ef --- /dev/null +++ b/assets/it/@home.texy @@ -0,0 +1,432 @@ +Nette Assets +************ + +
    + +Stanco di gestire manualmente i file statici nelle tue applicazioni web? Dimentica la codifica manuale dei percorsi, la gestione dell'invalidazione della cache o la preoccupazione per il versioning dei file. Nette Assets trasforma il modo in cui lavori con immagini, fogli di stile, script e altre risorse statiche. + +- **Versioning intelligente** assicura che i browser carichino sempre i file più recenti +- **Rilevamento automatico** dei tipi di file e delle dimensioni +- **Integrazione Latte senza soluzione di continuità** con tag intuitivi +- **Architettura flessibile** che supporta filesystem, CDN e Vite +- **Caricamento pigro (Lazy loading)** per prestazioni ottimali + +
    + + +Perché Nette Assets? +==================== + +Lavorare con i file statici spesso significa codice ripetitivo e soggetto a errori. Costruisci manualmente URL, aggiungi parametri di versione per il cache busting e gestisci diversi tipi di file in modo diverso. Questo porta a codice come: + +```html +Logo + +``` + +Con Nette Assets, tutta questa complessità scompare: + +```latte +{* Tutto automatizzato - URL, versioning, dimensioni *} + + + +{* O semplicemente *} +{asset 'css/style.css'} +``` + +Questo è tutto! La libreria automaticamente: +- Aggiunge parametri di versione basati sull'ora di modifica del file +- Rileva le dimensioni dell'immagine e le include nell'HTML +- Genera l'elemento HTML corretto per ogni tipo di file +- Gestisce sia gli ambienti di sviluppo che di produzione + + +Installazione +============= + +Installa Nette Assets usando [Composer|best-practices:composer]: + +```shell +composer require nette/assets +``` + +Richiede PHP 8.1 o superiore e funziona perfettamente con Nette Framework, ma può essere usato anche in modo standalone. + + +Primi Passi +=========== + +Nette Assets funziona subito senza alcuna configurazione. Posiziona i tuoi file statici nella directory `www/assets/` e inizia ad usarli: + +```latte +{* Visualizza un'immagine con dimensioni automatiche *} +{asset 'logo.png'} + +{* Includi un foglio di stile con versioning *} +{asset 'style.css'} + +{* Carica un modulo JavaScript *} +{asset 'app.js'} +``` + +Per un maggiore controllo sull'HTML generato, usa l'attributo `n:asset` o la funzione `asset()`. + + +Come Funziona +============= + +Nette Assets è costruito attorno a tre concetti fondamentali che lo rendono potente ma semplice da usare: + + +Assets - I Tuoi File Resi Intelligenti +-------------------------------------- + +Un **asset** rappresenta qualsiasi file statico nella tua applicazione. Ogni file diventa un oggetto con utili proprietà di sola lettura: + +```php +$image = $assets->getAsset('photo.jpg'); +echo $image->url; // '/assets/photo.jpg?v=1699123456' +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' +``` + +Diversi tipi di file forniscono proprietà diverse: +- **Immagini**: larghezza, altezza, testo alternativo, caricamento pigro +- **Script**: tipo di modulo, hash di integrità, crossorigin +- **Fogli di stile**: media queries, integrità +- **Audio/Video**: durata, dimensioni +- **Font**: precaricamento corretto con CORS + +La libreria rileva automaticamente i tipi di file e crea la classe asset appropriata. + + +Mappers - Da Dove Vengono i File +-------------------------------- + +Un **mapper** sa come trovare i file e creare URL per essi. Puoi avere più mapper per scopi diversi - file locali, CDN, cloud storage o strumenti di build (ognuno di essi ha un nome). Il `FilesystemMapper` integrato gestisce i file locali, mentre `ViteMapper` si integra con i moderni strumenti di build. + +I mapper sono definiti nella [Configurazione | Configuration]. + + +Registry - La Tua Interfaccia Principale +---------------------------------------- + +Il **registry** gestisce tutti i mapper e fornisce l'API principale: + +```php +// Inietta il registry nel tuo servizio +public function __construct( + private Nette\Assets\Registry $assets +) {} + +// Ottieni assets da diversi mapper +$logo = $this->assets->getAsset('images:logo.png'); // mapper 'image' +$app = $this->assets->getAsset('app:main.js'); // mapper 'app' +$style = $this->assets->getAsset('style.css'); // usa il mapper predefinito +``` + +Il registry seleziona automaticamente il mapper corretto e memorizza i risultati nella cache per le prestazioni. + + +Lavorare con gli Assets in PHP +============================== + +Il Registry fornisce due metodi per recuperare gli asset: + +```php +// Lancia Nette\Assets\AssetNotFoundException se il file non esiste +$logo = $assets->getAsset('logo.png'); + +// Restituisce null se il file non esiste +$banner = $assets->tryGetAsset('banner.jpg'); +if ($banner) { + echo $banner->url; +} +``` + + +Specificare i Mapper +-------------------- + +Puoi scegliere esplicitamente quale mapper usare: + +```php +// Usa il mapper predefinito +$file = $assets->getAsset('document.pdf'); + +// Usa un mapper specifico con prefisso +$image = $assets->getAsset('images:photo.jpg'); + +// Usa un mapper specifico con sintassi array +$script = $assets->getAsset(['scripts', 'app.js']); +``` + + +Proprietà e Tipi di Asset +------------------------- + +Ogni tipo di asset fornisce proprietà di sola lettura rilevanti: + +```php +// Proprietà dell'immagine +$image = $assets->getAsset('photo.jpg'); +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' + +// Proprietà dello script +$script = $assets->getAsset('app.js'); +echo $script->type; // 'module' o null + +// Proprietà audio +$audio = $assets->getAsset('song.mp3'); +echo $audio->duration; // durata in secondi + +// Tutti gli asset possono essere convertiti in stringa (restituisce URL) +$url = (string) $assets->getAsset('document.pdf'); +``` + +.[note] +Le proprietà come le dimensioni o la durata vengono caricate pigramente solo quando vi si accede, mantenendo la libreria veloce. + + +Uso degli Assets nei Template Latte +=================================== + +Nette Assets fornisce un'integrazione [Latte|latte:] intuitiva con tag e funzioni. + + +`{asset}` +--------- + +Il tag `{asset}` renderizza elementi HTML completi: + +```latte +{* Renderizza: *} +{asset 'hero.jpg'} + +{* Renderizza: *} +{asset 'app.js'} + +{* Renderizza: *} +{asset 'style.css'} +``` + +Il tag automaticamente: +- Rileva il tipo di asset e genera l'HTML appropriato +- Include il versioning per il cache busting +- Aggiunge le dimensioni per le immagini +- Imposta gli attributi corretti (type, media, ecc.) + +Quando usato all'interno di attributi HTML, produce solo l'URL: + +```latte +
    + +``` + + +`n:asset` +--------- + +Per un controllo completo sugli attributi HTML: + +```latte +{* L'attributo n:asset riempie src, dimensioni, ecc. *} +Prodotto + +{* Funziona con qualsiasi elemento rilevante *} + + + +``` + +Usa variabili e mapper: + +```latte +{* Le variabili funzionano naturalmente *} + + +{* Specifica il mapper con le parentesi graffe *} + + +{* Specifica il mapper con la notazione array *} + +``` + + +`asset()` +--------- + +Per la massima flessibilità, usa la funzione `asset()`: + +```latte +{var $logo = asset('logo.png')} +width} height={$logo->height}> + +{* O direttamente *} +Logo +``` + + +Assets Opzionali +---------------- + +Gestisci gli asset mancanti con `{asset?}`, `n:asset?` e `tryAsset()`: + +```latte +{* Tag opzionale - non renderizza nulla se l'asset manca *} +{asset? 'optional-banner.jpg'} + +{* Attributo opzionale - salta se l'asset manca *} +Avatar + +{* Con fallback *} +{var $avatar = tryAsset('user-avatar.jpg') ?? asset('default-avatar.jpg')} +Avatar +``` + + +`{preload}` +----------- + +Migliora le prestazioni di caricamento della pagina: + +```latte +{* Nella tua sezione *} +{preload 'critical.css'} +{preload 'important-font.woff2'} +{preload 'hero-image.jpg'} +``` + +Genera i link di precaricamento appropriati: + +```html + + + +``` + + +Funzionalità Avanzate +===================== + + +Rilevamento Automatico dell'Estensione +-------------------------------------- + +Gestisci automaticamente più formati: + +```neon +assets: + mapping: + images: + path: img + extension: [webp, jpg, png] # Prova in ordine +``` + +Ora puoi richiedere senza estensione: + +```latte +{* Trova logo.webp, logo.jpg, o logo.png automaticamente *} +{asset 'images:logo'} +``` + +Perfetto per il progressive enhancement con formati moderni. + + +Versioning Intelligente +----------------------- + +I file vengono automaticamente versionati in base all'ora di modifica: + +```latte +{asset 'style.css'} +{* Output: *} +``` + +Quando aggiorni il file, il timestamp cambia, forzando l'aggiornamento della cache del browser. + +Controlla il versioning per ogni asset: + +```php +// Disabilita il versioning per un asset specifico +$asset = $assets->getAsset('style.css', ['version' => false]); + +// In Latte +{asset 'style.css', version: false} +``` + + +Assets Font +----------- + +I font ricevono un trattamento speciale con CORS appropriato: + +```latte +{* Precaricamento corretto con crossorigin *} +{preload 'fonts:OpenSans-Regular.woff2'} + +{* Usa in CSS *} + +``` + + +Mappers Personalizzati +====================== + +Crea mapper personalizzati per esigenze speciali come l'archiviazione cloud o la generazione dinamica: + +```php +use Nette\Assets\Mapper; +use Nette\Assets\Asset; +use Nette\Assets\Helpers; + +class CloudStorageMapper implements Mapper +{ + public function __construct( + private CloudClient $client, + private string $bucket, + ) {} + + public function getAsset(string $reference, array $options = []): Asset + { + if (!$this->client->exists($this->bucket, $reference)) { + throw new Nette\Assets\AssetNotFoundException("Asset '$reference' not found"); + } + + $url = $this->client->getPublicUrl($this->bucket, $reference); + return Helpers::createAssetFromUrl($url); + } +} +``` + +Registra nella configurazione: + +```neon +assets: + mapping: + cloud: CloudStorageMapper(@cloudClient, 'my-bucket') +``` + +Usa come qualsiasi altro mapper: + +```latte +{asset 'cloud:user-uploads/photo.jpg'} +``` + +Il metodo `Helpers::createAssetFromUrl()` crea automaticamente il tipo di asset corretto in base all'estensione del file. + + +Ulteriori letture +================= + +- [Nette Assets: Finalmente un'API unificata per tutto, dalle immagini a Vite |https://blog.nette.org/en/introducing-nette-assets] diff --git a/assets/it/@left-menu.texy b/assets/it/@left-menu.texy new file mode 100644 index 0000000000..e5b9271e3a --- /dev/null +++ b/assets/it/@left-menu.texy @@ -0,0 +1,5 @@ +Nette Assets +************ +- [Per iniziare |@home] +- [Vite |vite] +- [Configurazione | Configuration] diff --git a/assets/it/@meta.texy b/assets/it/@meta.texy new file mode 100644 index 0000000000..4647d0c8a2 --- /dev/null +++ b/assets/it/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentazione Nette}} diff --git a/assets/it/configuration.texy b/assets/it/configuration.texy new file mode 100644 index 0000000000..4ba24a8345 --- /dev/null +++ b/assets/it/configuration.texy @@ -0,0 +1,188 @@ +Configurazione degli Assets +*************************** + +.[perex] +Panoramica delle opzioni di configurazione per Nette Assets. + + +```neon +assets: + # percorso base per la risoluzione dei percorsi relativi del mapper + basePath: ... # (string) predefinito a %wwwDir% + + # URL base per la risoluzione degli URL relativi del mapper + baseUrl: ... # (string) predefinito a %baseUrl% + + # abilitare il versioning degli asset globalmente? + versioning: ... # (bool) predefinito a true + + # definisce i mapper degli asset + mapping: ... # (array) predefinito al percorso 'assets' +``` + +`basePath` imposta la directory del filesystem predefinita per la risoluzione dei percorsi relativi nei mapper. Per impostazione predefinita, usa la directory web (`%wwwDir%`). + +`baseUrl` imposta il prefisso URL predefinito per la risoluzione degli URL relativi nei mapper. Per impostazione predefinita, usa l'URL radice (`%baseUrl%`). + +L'opzione `versioning` controlla globalmente se i parametri di versione vengono aggiunti agli URL degli asset per il cache busting. I singoli mapper possono sovrascrivere questa impostazione. + + +Mappers +------- + +I mapper possono essere configurati in tre modi: notazione stringa semplice, notazione array dettagliata o come riferimento a un servizio. + +Il modo più semplice per definire un mapper: + +```neon +assets: + mapping: + default: assets # Crea un mapper del filesystem per %wwwDir%/assets/ + images: img # Crea un mapper del filesystem per %wwwDir%/img/ + scripts: js # Crea un mapper del filesystem per %wwwDir%/js/ +``` + +Ogni mapper crea un `FilesystemMapper` che: +- Cerca i file in `%wwwDir%/` +- Genera URL come `%baseUrl%/` +- Eredita l'impostazione di versioning globale + + +Per un maggiore controllo, usa la notazione dettagliata: + +```neon +assets: + mapping: + images: + # directory dove sono memorizzati i file + path: ... # (string) opzionale, predefinito a '' + + # prefisso URL per i link generati + url: ... # (string) opzionale, predefinito a path + + # abilitare il versioning per questo mapper? + versioning: ... # (bool) opzionale, eredita l'impostazione globale + + # aggiungi automaticamente estensione(i) durante la ricerca di file + extension: ... # (string|array) opzionale, predefinito a null +``` + +Comprendere come vengono risolti i valori di configurazione: + +Risoluzione del percorso: + - I percorsi relativi vengono risolti da `basePath` (o `%wwwDir%` se `basePath` non è impostato) + - I percorsi assoluti vengono usati così come sono + +URL Risoluzione: + - Gli URL relativi vengono risolti da `baseUrl` (o `%baseUrl%` se `baseUrl` non è impostato) + - Gli URL assoluti (con schema o `//`) vengono usati così come sono + - Se `url` non è specificato, usa il valore di `path` + + +```neon +assets: + basePath: /var/www/project/www + baseUrl: https://example.com/assets + + mapping: + # Percorso e URL relativi + images: + path: img # Risolto in: /var/www/project/www/img + url: images # Risolto in: https://example.com/assets/images + + # Percorso e URL assoluti + uploads: + path: /var/shared/uploads # Usato così come è: /var/shared/uploads + url: https://cdn.example.com # Usato così come è: https://cdn.example.com + + # Solo percorso specificato + styles: + path: css # Percorso: /var/www/project/www/css + # URL: https://example.com/assets/css +``` + + +Mappers Personalizzati +---------------------- + +Per i mapper personalizzati, fai riferimento o definisci un servizio: + +```neon +services: + s3mapper: App\Assets\S3Mapper(%s3.bucket%) + +assets: + mapping: + cloud: @s3mapper + database: App\Assets\DatabaseMapper(@database.connection) +``` + + +Vite Mapper +----------- + +Il mapper Vite richiede solo di aggiungere `type: vite`. Questa è una lista completa delle opzioni di configurazione: + +```neon +assets: + mapping: + default: + # tipo di mapper (richiesto per Vite) + type: vite # (string) richiesto, deve essere 'vite' + + # directory di output della build di Vite + path: ... # (string) opzionale, predefinito a '' + + # prefisso URL per gli asset costruiti + url: ... # (string) opzionale, predefinito a path + + # posizione del file manifest di Vite + manifest: ... # (string) opzionale, predefinito a /.vite/manifest.json + + # configurazione del server di sviluppo Vite + devServer: ... # (bool|string) opzionale, predefinito a true + + # versioning per i file della directory pubblica + versioning: ... # (bool) opzionale, eredita l'impostazione globale + + # estensione automatica per i file della directory pubblica + extension: ... # (string|array) opzionale, predefinito a null +``` + +L'opzione `devServer` controlla come vengono caricati gli asset durante lo sviluppo: + +- `true` (predefinito) - Rileva automaticamente il server di sviluppo Vite sull'host e la porta attuali. Se il server di sviluppo è in esecuzione **e la tua applicazione è in modalità debug**, gli asset vengono caricati da esso con supporto per l'Hot Module Replacement. Se il server di sviluppo non è in esecuzione, gli asset vengono caricati dai file costruiti nella directory pubblica. +- `false` - Disabilita completamente l'integrazione del server di sviluppo. Gli asset vengono sempre caricati dai file costruiti. +- URL personalizzato (es. `https://localhost:5173`) - Specifica manualmente l'URL del server di sviluppo inclusi protocollo e porta. Utile quando il server di sviluppo è in esecuzione su un host o una porta diversa. + +Le opzioni `versioning` ed `extension` si applicano solo ai file nella directory pubblica di Vite che non vengono elaborati da Vite. + + +Configurazione Manuale +---------------------- + +Quando non si usa Nette DI, configura i mapper manualmente: + +```php +use Nette\Assets\Registry; +use Nette\Assets\FilesystemMapper; +use Nette\Assets\ViteMapper; + +$registry = new Registry; + +// Aggiungi il mapper del filesystem +$registry->addMapper('images', new FilesystemMapper( + baseUrl: 'https://example.com/img', + basePath: __DIR__ . '/www/img', + extensions: ['webp', 'jpg', 'png'], + versioning: true, +)); + +// Aggiungi il mapper Vite +$registry->addMapper('app', new ViteMapper( + baseUrl: '/build', + basePath: __DIR__ . '/www/build', + manifestPath: __DIR__ . '/www/build/.vite/manifest.json', + devServer: 'https://localhost:5173', +)); +``` diff --git a/assets/it/vite.texy b/assets/it/vite.texy new file mode 100644 index 0000000000..4c917d2431 --- /dev/null +++ b/assets/it/vite.texy @@ -0,0 +1,508 @@ +Integrazione Vite +***************** + +
    + +Le moderne applicazioni JavaScript richiedono strumenti di build sofisticati. Nette Assets fornisce un'integrazione di prima classe con [Vite |https://vitejs.dev/], lo strumento di build frontend di nuova generazione. Ottieni uno sviluppo fulmineo con Hot Module Replacement (HMR) e build di produzione ottimizzate senza problemi di configurazione. + +- **Zero configurazione** - ponte automatico tra Vite e i template PHP +- **Gestione completa delle dipendenze** - un solo tag gestisce tutti gli asset +- **Hot Module Replacement** - aggiornamenti istantanei di JavaScript e CSS +- **Build di produzione ottimizzate** - code splitting e tree shaking + +
    + + +Nette Assets si integra perfettamente con Vite, così ottieni tutti questi vantaggi mentre scrivi i tuoi template come al solito. + + +Configurazione di Vite +====================== + +Configuriamo Vite passo dopo passo. Non preoccuparti se sei nuovo agli strumenti di build - spiegheremo tutto! + + +Passo 1: Installa Vite +---------------------- + +Per prima cosa, installa Vite e il plugin Nette nel tuo progetto: + +```shell +npm install -D vite @nette/vite-plugin +``` + +Questo installa Vite e un plugin speciale che aiuta Vite a funzionare perfettamente con Nette. + + +Passo 2: Struttura del Progetto +------------------------------- + +L'approccio standard è quello di posizionare i file sorgente degli asset in una cartella `assets/` nella radice del tuo progetto, e le versioni compilate in `www/assets/`: + +/--pre +web-project/ +├── assets/ ← file sorgente (SCSS, TypeScript, immagini sorgente) +│ ├── public/ ← file statici (copiati così come sono) +│ │ └── favicon.ico +│ ├── images/ +│ │ └── logo.png +│ ├── app.js ← punto di ingresso principale +│ └── style.css ← i tuoi stili +└── www/ ← directory pubblica (document root) + ├── assets/ ← i file compilati andranno qui + └── index.php +\-- + +La cartella `assets/` contiene i tuoi file sorgente - il codice che scrivi. Vite elaborerà questi file e metterà le versioni compilate in `www/assets/`. + + +Passo 3: Configura Vite +----------------------- + +Crea un file `vite.config.ts` nella radice del tuo progetto. Questo file dice a Vite dove trovare i tuoi file sorgente e dove mettere quelli compilati. + +Il plugin Nette Vite viene fornito con impostazioni predefinite intelligenti che semplificano la configurazione. Presuppone che i tuoi file sorgente front-end si trovino nella directory `assets/` (opzione `root`) e che i file compilati vadano in `www/assets/` (opzione `outDir`). Devi solo specificare il [punto di ingresso|#Entry Points]: + +```js +import { defineConfig } from 'vite'; +import nette from '@nette/vite-plugin'; + +export default defineConfig({ + plugins: [ + nette({ + entry: 'app.js', + }), + ], +}); +``` + +Se vuoi specificare un altro nome di directory per costruire i tuoi asset, dovrai cambiare alcune opzioni: + +```js +export default defineConfig({ + root: 'assets', // directory radice degli asset sorgente + + build: { + outDir: '../www/assets', // dove vanno i file compilati + }, + + // ... altra configurazione ... +}); +``` + +.[note] +Il percorso `outDir` è considerato relativo a `root`, ecco perché c'è `../` all'inizio. + + +Passo 4: Configura Nette +------------------------ + +Dì a Nette Assets di Vite nel tuo `common.neon`: + +```neon +assets: + mapping: + default: + type: vite # dice a Nette di usare il ViteMapper + path: assets +``` + + +Passo 5: Aggiungi script +------------------------ + +Aggiungi questi script al tuo `package.json`: + +```json +{ + "scripts": { + "dev": "vite", + "build": "vite build" + } +} +``` + +Ora puoi: +- `npm run dev` - avvia il server di sviluppo con hot reloading +- `npm run build` - crea file di produzione ottimizzati + + +Punti di Ingresso +================= + +Un **punto di ingresso** è il file principale da cui la tua applicazione inizia. Da questo file, importi altri file (CSS, moduli JavaScript, immagini), creando un albero di dipendenze. Vite segue queste importazioni e raggruppa tutto insieme. + +Esempio di punto di ingresso `assets/app.js`: + +```js +// Importa stili +import './style.css' + +// Importa moduli JavaScript +import netteForms from 'nette-forms'; +import naja from 'naja'; + +// Inizializza la tua applicazione +netteForms.initOnLoad(); +naja.initialize(); +``` + +Nel template puoi inserire un punto di ingresso come segue: + +```latte +{asset 'app.js'} +``` + +Nette Assets genera automaticamente tutti i tag HTML necessari - JavaScript, CSS e qualsiasi altra dipendenza. + + +Punti di Ingresso Multipli +-------------------------- + +Applicazioni più grandi spesso necessitano di punti di ingresso separati: + +```js +export default defineConfig({ + plugins: [ + nette({ + entry: [ + 'app.js', // pagine pubbliche + 'admin.js', // pannello di amministrazione + ], + }), + ], +}); +``` + +Usali in template diversi: + +```latte +{* Nelle pagine pubbliche *} +{asset 'app.js'} + +{* Nel pannello di amministrazione *} +{asset 'admin.js'} +``` + + +Importante: File Sorgente vs Compilati +-------------------------------------- + +È fondamentale capire che in produzione puoi caricare solo: + +1. **Punti di ingresso** definiti in `entry` +2. **File dalla directory `assets/public/`** + +Non puoi caricare usando `{asset}` file arbitrari da `assets/` - solo asset a cui si fa riferimento da file JavaScript o CSS. Se il tuo file non è referenziato da nessuna parte non verrà compilato. Se vuoi che Vite sia a conoscenza di altri asset, puoi spostarli nella [cartella pubblica|#public folder]. + +Si prega di notare che per impostazione predefinita, Vite inlinerà tutti gli asset più piccoli di 4KB, quindi non sarai in grado di fare riferimento a questi file direttamente. (Vedi [documentazione di Vite |https://vite.dev/guide/assets.html]). + +```latte +{* ✓ Questo funziona - è un punto di ingresso *} +{asset 'app.js'} + +{* ✓ Questo funziona - è in assets/public/ *} +{asset 'favicon.ico'} + +{* ✗ Questo non funzionerà - file casuale in assets/ *} +{asset 'components/button.js'} +``` + + +Modalità di Sviluppo +==================== + +La modalità di sviluppo è completamente opzionale ma offre vantaggi significativi quando abilitata. Il vantaggio principale è l'**Hot Module Replacement (HMR)** - vedi i cambiamenti istantaneamente senza perdere lo stato dell'applicazione, rendendo l'esperienza di sviluppo molto più fluida e veloce. + +Vite è uno strumento di build moderno che rende lo sviluppo incredibilmente veloce. A differenza dei bundler tradizionali, Vite serve il tuo codice direttamente al browser durante lo sviluppo, il che significa avvio istantaneo del server, indipendentemente dalle dimensioni del tuo progetto, e aggiornamenti fulminei. + + +Avvio del Server di Sviluppo +---------------------------- + +Avvia il server di sviluppo: + +```shell +npm run dev +``` + +Vedrai: + +``` + ➜ Local: http://localhost:5173/ + ➜ Network: use --host to expose +``` + +Mantieni questo terminale aperto durante lo sviluppo. + +Il plugin Nette Vite rileva automaticamente quando: +1. Il server di sviluppo Vite è in esecuzione +2. La tua applicazione Nette è in modalità debug + +Quando entrambe le condizioni sono soddisfatte, Nette Assets carica i file dal server di sviluppo Vite invece che dalla directory compilata: + +```latte +{asset 'app.js'} +{* In sviluppo: *} +{* In produzione: *} +``` + +Nessuna configurazione necessaria - funziona e basta! + + +Lavorare su Domini Diversi +-------------------------- + +Se il tuo server di sviluppo è in esecuzione su qualcosa di diverso da `localhost` (come `myapp.local`), potresti incontrare problemi di CORS (Cross-Origin Resource Sharing). CORS è una funzionalità di sicurezza nei browser web che blocca le richieste tra domini diversi per impostazione predefinita. Quando la tua applicazione PHP è in esecuzione su `myapp.local` ma Vite è in esecuzione su `localhost:5173`, il browser li vede come domini diversi e blocca le richieste. + +Hai due opzioni per risolvere questo problema: + +**Opzione 1: Configura CORS** + +La soluzione più semplice è consentire le richieste cross-origin dalla tua applicazione PHP: + +```js +export default defineConfig({ + // ... altra configurazione ... + + server: { + cors: { + origin: 'http://myapp.local', // l'URL della tua app PHP + }, + }, +}); +``` +**Opzione 2: Esegui Vite sul tuo dominio** + +L'altra soluzione è far sì che Vite sia in esecuzione sullo stesso dominio della tua applicazione PHP. + +```js +export default defineConfig({ + // ... altra configurazione ... + + server: { + host: 'myapp.local', // lo stesso della tua app PHP + }, +}); +``` + +In realtà, anche in questo caso, è necessario configurare CORS perché il server di sviluppo è in esecuzione sullo stesso hostname ma su una porta diversa. Tuttavia, in questo caso, CORS viene configurato automaticamente dal plugin Nette Vite. + + +Sviluppo HTTPS +-------------- + +Se sviluppi su HTTPS, hai bisogno di certificati per il tuo server di sviluppo Vite. Il modo più semplice è usare un plugin che genera automaticamente i certificati: + +```shell +npm install -D vite-plugin-mkcert +``` + +Ecco come configurarlo in `vite.config.ts`: + +```js +import mkcert from 'vite-plugin-mkcert'; + +export default defineConfig({ + // ... altra configurazione ... + + plugins: [ + mkcert(), // genera automaticamente i certificati e abilita https + nette(), + ], +}); +``` + +Nota che se stai usando la configurazione CORS (Opzione 1 da sopra), devi aggiornare l'URL di origine per usare `https://` invece di `http://`. + + +Build di Produzione +=================== + +Crea file di produzione ottimizzati: + +```shell +npm run build +``` + +Vite: +- Minifica tutto il JavaScript e il CSS +- Divide il codice in chunk ottimali +- Genera nomi di file con hash per il cache-busting +- Crea un file manifest per Nette Assets + +Esempio di output: + +``` +www/assets/ +├── app-4f3a2b1c.js # Il tuo JavaScript principale (minificato) +├── app-7d8e9f2a.css # CSS estratto (minificato) +├── vendor-8c4b5e6d.js # Dipendenze condivise +└── .vite/ + └── manifest.json # Mappatura per Nette Assets +``` + +I nomi di file con hash assicurano che i browser carichino sempre la versione più recente. + + +Cartella Pubblica +================= + +I file nella directory `assets/public/` vengono copiati nell'output senza elaborazione: + +``` +assets/ +├── public/ +│ ├── favicon.ico +│ ├── robots.txt +│ └── images/ +│ └── og-image.jpg +├── app.js +└── style.css +``` + +Fai riferimento ad essi normalmente: + +```latte +{* Questi file vengono copiati così come sono *} + + +``` + +Per i file pubblici, puoi usare le funzionalità di FilesystemMapper: + +```neon +assets: + mapping: + default: + type: vite + path: assets + extension: [webp, jpg, png] # Prova prima WebP + versioning: true # Aggiungi cache-busting +``` + +Nella configurazione `vite.config.ts` puoi cambiare la cartella pubblica usando l'opzione `publicDir`. + + +Importazioni Dinamiche +====================== + +Vite divide automaticamente il codice per un caricamento ottimale. Le importazioni dinamiche ti consentono di caricare il codice solo quando è effettivamente necessario, riducendo la dimensione iniziale del bundle: + +```js +// Carica componenti pesanti su richiesta +button.addEventListener('click', async () => { + let { Chart } = await import('./components/chart.js') + new Chart(data) +}) +``` + +Le importazioni dinamiche creano chunk separati che vengono caricati solo quando necessario. Questo è chiamato "code splitting" ed è una delle funzionalità più potenti di Vite. Quando usi le importazioni dinamiche, Vite crea automaticamente file JavaScript separati per ogni modulo importato dinamicamente. + +Il tag `{asset 'app.js'}` **non** precarica automaticamente questi chunk dinamici. Questo è un comportamento intenzionale - non vogliamo scaricare codice che potrebbe non essere mai usato. I chunk vengono scaricati solo quando l'importazione dinamica viene eseguita. + +Tuttavia, se sai che alcune importazioni dinamiche sono critiche e saranno necessarie a breve, puoi precaricarle: + +```latte +{* Punto di ingresso principale *} +{asset 'app.js'} + +{* Precarica importazioni dinamiche critiche *} +{preload 'components/chart.js'} +``` + +Questo dice al browser di scaricare il componente del grafico in background, in modo che sia pronto immediatamente quando necessario. + + +Supporto TypeScript +=================== + +TypeScript funziona subito: + +```ts +// assets/main.ts +interface User { + name: string + email: string +} + +export function greetUser(user: User): void { + console.log(`Hello, ${user.name}!`) +} +``` + +Fai riferimento ai file TypeScript normalmente: + +```latte +{asset 'main.ts'} +``` + +Per il supporto completo di TypeScript, installalo: + +```shell +npm install -D typescript +``` + + +Configurazione Aggiuntiva di Vite +================================= + +Ecco alcune utili opzioni di configurazione di Vite con spiegazioni dettagliate: + +```js +export default defineConfig({ + // Directory radice contenente gli asset sorgente + root: 'assets', + + // Cartella il cui contenuto viene copiato nella directory di output così com'è + // Predefinito: 'public' (relativo a 'root') + publicDir: 'public', + + build: { + // Dove mettere i file compilati (relativo a 'root') + outDir: '../www/assets', + + // Svuotare la directory di output prima della build? + // Utile per rimuovere i vecchi file dalle build precedenti + emptyOutDir: true, + + // Sottodirectory all'interno di outDir per chunk e asset generati + // Questo aiuta a organizzare la struttura di output + assetsDir: 'static', + + rollupOptions: { + // Punto(i) di ingresso - può essere un singolo file o un array di file + // Ogni punto di ingresso diventa un bundle separato + input: [ + 'app.js', // applicazione principale + 'admin.js', // pannello di amministrazione + ], + }, + }, + + server: { + // Host a cui associare il server di sviluppo + // Usa '0.0.0.0' per esporre alla rete + host: 'localhost', + + // Porta per il server di sviluppo + port: 5173, + + // Configurazione CORS per richieste cross-origin + cors: { + origin: 'http://myapp.local', + }, + }, + + css: { + // Abilita le source map CSS in sviluppo + devSourcemap: true, + }, + + plugins: [ + nette(), + ], +}); +``` + +Questo è tutto! Ora hai un sistema di build moderno integrato con Nette Assets. diff --git a/assets/ja/@home.texy b/assets/ja/@home.texy new file mode 100644 index 0000000000..f9f55f4999 --- /dev/null +++ b/assets/ja/@home.texy @@ -0,0 +1,432 @@ +Nette Assets +************ + +
    + +Webアプリケーションで静的ファイルの管理を手動で行うことにうんざりしていませんか?パスのハードコーディング、キャッシュの無効化、ファイルバージョニングの心配はもう必要ありません。Nette Assetsは、画像、スタイルシート、スクリプト、その他の静的リソースの作業方法を変革します。 + +- **スマートバージョニング**により、ブラウザは常に最新のファイルをロードします +- ファイルの種類と寸法の**自動検出** +- 直感的なタグによる**シームレスなLatte連携** +- ファイルシステム、CDN、Viteをサポートする**柔軟なアーキテクチャ** +- 最適なパフォーマンスのための**遅延ロード** + +
    + + +Nette Assetsを使用する理由 +=================== + +静的ファイルの操作は、多くの場合、反復的でエラーが発生しやすいコードを意味します。URLを手動で構築し、キャッシュバストのためにバージョンパラメータを追加し、異なるファイルタイプを異なる方法で処理します。これにより、次のようなコードになります。 + +```html +Logo + +``` + +Nette Assetsを使用すると、このすべての複雑さが解消されます。 + +```latte +{* URL、バージョニング、寸法がすべて自動化されます *} + + + +{* または単に *} +{asset 'css/style.css'} +``` + +それだけです!ライブラリは自動的に次のことを行います。 +- ファイル変更時間に基づいてバージョンパラメータを追加します +- 画像の寸法を検出し、HTMLに含めます +- 各ファイルタイプに適切なHTML要素を生成します +- 開発環境と本番環境の両方を処理します + + +インストール +====== + +Nette Assetsを[Composer|best-practices:composer]を使用してインストールします。 + +```shell +composer require nette/assets +``` + +PHP 8.1以降が必要で、Nette Frameworkと完全に連携しますが、スタンドアロンでも使用できます。 + + +最初のステップ +======= + +Nette Assetsは設定なしでそのまま動作します。静的ファイルを`www/assets/`ディレクトリに配置し、使用を開始します。 + +```latte +{* 自動寸法で画像を表示 *} +{asset 'logo.png'} + +{* バージョニング付きのスタイルシートを含める *} +{asset 'style.css'} + +{* JavaScriptモジュールをロード *} +{asset 'app.js'} +``` + +生成されるHTMLをより細かく制御するには、`n:asset`属性または`asset()`関数を使用します。 + + +動作原理 +==== + +Nette Assetsは、強力でありながら使いやすい3つのコアコンセプトに基づいて構築されています。 + + +アセット - スマートになったファイル +------------------- + +**アセット**は、アプリケーション内の静的ファイルを表します。各ファイルは、便利な読み取り専用プロパティを持つオブジェクトになります。 + +```php +$image = $assets->getAsset('photo.jpg'); +echo $image->url; // '/assets/photo.jpg?v=1699123456' +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' +``` + +異なるファイルタイプは異なるプロパティを提供します。 +- **画像**: 幅、高さ、代替テキスト、遅延ロード +- **スクリプト**: モジュールタイプ、整合性ハッシュ、クロスオリジン +- **スタイルシート**: メディアクエリ、整合性 +- **オーディオ/ビデオ**: 期間、寸法 +- **フォント**: CORSを使用した適切なプリロード + +ライブラリは自動的にファイルタイプを検出し、適切なアセットクラスを作成します。 + + +マッパー - ファイルの取得元 +--------------- + +**マッパー**は、ファイルの検索方法とそれらのURLの作成方法を知っています。ローカルファイル、CDN、クラウドストレージ、またはビルドツールなど、さまざまな目的のために複数のマッパーを持つことができます(それぞれに名前があります)。組み込みの`FilesystemMapper`はローカルファイルを処理し、`ViteMapper`は最新のビルドツールと連携します。 + +マッパーは[設定|Configuration]で定義されます。 + + +レジストリ - メインインターフェース +------------------- + +**レジストリ**はすべてのマッパーを管理し、メインAPIを提供します。 + +```php +// サービスにレジストリを注入 +public function __construct( + private Nette\Assets\Registry $assets +) {} + +// 異なるマッパーからアセットを取得 +$logo = $this->assets->getAsset('images:logo.png'); // 'image' マッパー +$app = $this->assets->getAsset('app:main.js'); // 'app' マッパー +$style = $this->assets->getAsset('style.css'); // デフォルトマッパーを使用 +``` + +レジストリは自動的に正しいマッパーを選択し、パフォーマンスのために結果をキャッシュします。 + + +PHPでアセットを操作する +============= + +レジストリは、アセットを取得するための2つのメソッドを提供します。 + +```php +// ファイルが存在しない場合、Nette\Assets\AssetNotFoundException をスローします +$logo = $assets->getAsset('logo.png'); + +// ファイルが存在しない場合、null を返します +$banner = $assets->tryGetAsset('banner.jpg'); +if ($banner) { + echo $banner->url; +} +``` + + +マッパーの指定 +------- + +使用するマッパーを明示的に選択できます。 + +```php +// デフォルトマッパーを使用 +$file = $assets->getAsset('document.pdf'); + +// プレフィックス付きの特定のマッパーを使用 +$image = $assets->getAsset('images:photo.jpg'); + +// 配列構文で特定のマッパーを使用 +$script = $assets->getAsset(['scripts', 'app.js']); +``` + + +アセットのプロパティとタイプ +-------------- + +各アセットタイプは関連する読み取り専用プロパティを提供します。 + +```php +// 画像のプロパティ +$image = $assets->getAsset('photo.jpg'); +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' + +// スクリプトのプロパティ +$script = $assets->getAsset('app.js'); +echo $script->type; // 'module' または null + +// オーディオのプロパティ +$audio = $assets->getAsset('song.mp3'); +echo $audio->duration; // 秒単位の期間 + +// すべてのアセットは文字列にキャスト可能 (URLを返します) +$url = (string) $assets->getAsset('document.pdf'); +``` + +.[note] +寸法や期間などのプロパティは、アクセスされたときにのみ遅延ロードされるため、ライブラリは高速に動作します。 + + +Latteテンプレートでアセットを使用する +===================== + +Nette Assetsは、タグと関数による直感的な[Latte|latte:]連携を提供します。 + + +`{asset}` +--------- + +`{asset}`タグは完全なHTML要素をレンダリングします。 + +```latte +{* レンダリング: *} +{asset 'hero.jpg'} + +{* レンダリング: *} +{asset 'app.js'} + +{* レンダリング: *} +{asset 'style.css'} +``` + +このタグは自動的に次のことを行います。 +- アセットタイプを検出し、適切なHTMLを生成します +- キャッシュバストのためにバージョニングを含めます +- 画像の寸法を追加します +- 正しい属性(type、mediaなど)を設定します + +HTML属性内で使用すると、URLのみを出力します。 + +```latte +
    + +``` + + +`n:asset` +--------- + +HTML属性を完全に制御する場合: + +```latte +{* n:asset 属性は src、寸法などを埋めます *} +Product + +{* 関連する任意の要素で動作します *} + + + +``` + +変数とマッパーを使用します。 + +```latte +{* 変数は自然に動作します *} + + +{* 波括弧でマッパーを指定します *} + + +{* 配列表記でマッパーを指定します *} + +``` + + +`asset()` +--------- + +最大限の柔軟性を得るには、`asset()`関数を使用します。 + +```latte +{var $logo = asset('logo.png')} +width} height={$logo->height}> + +{* または直接 *} +Logo +``` + + +オプションのアセット +---------- + +`{asset?}`、`n:asset?`、`tryAsset()`を使用して、不足しているアセットを適切に処理します。 + +```latte +{* オプションのタグ - アセットがない場合は何もレンダリングしません *} +{asset? 'optional-banner.jpg'} + +{* オプションの属性 - アセットがない場合はスキップします *} +Avatar + +{* フォールバック付き *} +{var $avatar = tryAsset('user-avatar.jpg') ?? asset('default-avatar.jpg')} +Avatar +``` + + +`{preload}` +----------- + +ページ読み込みパフォーマンスを向上させます。 + +```latte +{* セクション内 *} +{preload 'critical.css'} +{preload 'important-font.woff2'} +{preload 'hero-image.jpg'} +``` + +適切なプリロードリンクを生成します。 + +```html + + + +``` + + +高度な機能 +===== + + +拡張子の自動検出 +-------- + +複数のフォーマットを自動的に処理します。 + +```neon +assets: + mapping: + images: + path: img + extension: [webp, jpg, png] # この順序で試行 +``` + +これで、拡張子なしでリクエストできます。 + +```latte +{* logo.webp、logo.jpg、または logo.png を自動的に検索します *} +{asset 'images:logo'} +``` + +最新のフォーマットによるプログレッシブエンハンスメントに最適です。 + + +スマートバージョニング +----------- + +ファイルは変更時間に基づいて自動的にバージョニングされます。 + +```latte +{asset 'style.css'} +{* 出力: *} +``` + +ファイルを更新すると、タイムスタンプが変更され、ブラウザのキャッシュが強制的に更新されます。 + +アセットごとにバージョニングを制御します。 + +```php +// 特定のアセットのバージョニングを無効にする +$asset = $assets->getAsset('style.css', ['version' => false]); + +// Latteで +{asset 'style.css', version: false} +``` + + +フォントアセット +-------- + +フォントは適切なCORSで特別に扱われます。 + +```latte +{* crossorigin 付きの適切なプリロード *} +{preload 'fonts:OpenSans-Regular.woff2'} + +{* CSSでの使用 *} + +``` + + +カスタムマッパー +======== + +クラウドストレージや動的生成などの特別なニーズに合わせてカスタムマッパーを作成します。 + +```php +use Nette\Assets\Mapper; +use Nette\Assets\Asset; +use Nette\Assets\Helpers; + +class CloudStorageMapper implements Mapper +{ + public function __construct( + private CloudClient $client, + private string $bucket, + ) {} + + public function getAsset(string $reference, array $options = []): Asset + { + if (!$this->client->exists($this->bucket, $reference)) { + throw new Nette\Assets\AssetNotFoundException("アセット '$reference' が見つかりません"); + } + + $url = $this->client->getPublicUrl($this->bucket, $reference); + return Helpers::createAssetFromUrl($url); + } +} +``` + +設定に登録します。 + +```neon +assets: + mapping: + cloud: CloudStorageMapper(@cloudClient, 'my-bucket') +``` + +他のマッパーと同様に使用します。 + +```latte +{asset 'cloud:user-uploads/photo.jpg'} +``` + +`Helpers::createAssetFromUrl()`メソッドは、ファイル拡張子に基づいて正しいアセットタイプを自動的に作成します。 + + +参考文献 +==== + +- [Nette Assets:画像からViteまで、ついに統一APIが登場 |https://blog.nette.org/en/introducing-nette-assets] diff --git a/assets/ja/@left-menu.texy b/assets/ja/@left-menu.texy new file mode 100644 index 0000000000..fc278aeb3b --- /dev/null +++ b/assets/ja/@left-menu.texy @@ -0,0 +1,5 @@ +Nette Assets +************ +- [はじめに|@home] +- [Vite|vite] +- [設定|Configuration] diff --git a/assets/ja/@meta.texy b/assets/ja/@meta.texy new file mode 100644 index 0000000000..d3c41dc3d7 --- /dev/null +++ b/assets/ja/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette ドキュメンテーション}} diff --git a/assets/ja/configuration.texy b/assets/ja/configuration.texy new file mode 100644 index 0000000000..fa2cb1dae9 --- /dev/null +++ b/assets/ja/configuration.texy @@ -0,0 +1,188 @@ +アセット設定 +****** + +.[perex] +Nette Assetsの設定オプションの概要。 + + +```neon +assets: + # 相対マッパーパスを解決するためのベースパス + basePath: ... # (string) デフォルトは %wwwDir% + + # 相対マッパーURLを解決するためのベースURL + baseUrl: ... # (string) デフォルトは %baseUrl% + + # アセットのバージョニングをグローバルに有効にするか? + versioning: ... # (bool) デフォルトは true + + # アセットマッパーを定義 + mapping: ... # (array) デフォルトはパス 'assets' +``` + +`basePath`は、マッパー内の相対パスを解決するためのデフォルトのファイルシステムディレクトリを設定します。デフォルトでは、ウェブディレクトリ(`%wwwDir%`)を使用します。 + +`baseUrl`は、マッパー内の相対URLを解決するためのデフォルトのURLプレフィックスを設定します。デフォルトでは、ルートURL(`%baseUrl%`)を使用します。 + +`versioning`オプションは、キャッシュバストのためにアセットURLにバージョンパラメータを追加するかどうかをグローバルに制御します。個々のマッパーはこの設定を上書きできます。 + + +マッパー +---- + +マッパーは、単純な文字列表記、詳細な配列表記、またはサービスへの参照の3つの方法で設定できます。 + +マッパーを定義する最も簡単な方法: + +```neon +assets: + mapping: + default: assets # %wwwDir%/assets/ のファイルシステムマッパーを作成 + images: img # %wwwDir%/img/ のファイルシステムマッパーを作成 + scripts: js # %wwwDir%/js/ のファイルシステムマッパーを作成 +``` + +各マッパーは`FilesystemMapper`を作成し、次のことを行います。 +- `%wwwDir%/`内のファイルを検索します +- `%baseUrl%/`のようなURLを生成します +- グローバルなバージョニング設定を継承します + + +より詳細な制御が必要な場合は、詳細な表記を使用します。 + +```neon +assets: + mapping: + images: + # ファイルが保存されているディレクトリ + path: ... # (string) オプション、デフォルトは '' + + # 生成されるリンクのURLプレフィックス + url: ... # (string) オプション、デフォルトは path + + # このマッパーのバージョニングを有効にするか? + versioning: ... # (bool) オプション、グローバル設定を継承 + + # ファイルを検索する際に拡張子を自動追加するか? + extension: ... # (string|array) オプション、デフォルトは null +``` + +設定値がどのように解決されるかを理解する: + +パス解決: + - 相対パスは`basePath`(または`basePath`が設定されていない場合は`%wwwDir%`)から解決されます + - 絶対パスはそのまま使用されます + +URL解決: + - 相対URLは`baseUrl`(または`baseUrl`が設定されていない場合は`%baseUrl%`)から解決されます + - 絶対URL(スキームまたは`//`を含む)はそのまま使用されます + - `url`が指定されていない場合、`path`の値が使用されます + + +```neon +assets: + basePath: /var/www/project/www + baseUrl: https://example.com/assets + + mapping: + # 相対パスとURL + images: + path: img # 解決済み: /var/www/project/www/img + url: images # 解決済み: https://example.com/assets/images + + # 絶対パスとURL + uploads: + path: /var/shared/uploads # そのまま使用: /var/shared/uploads + url: https://cdn.example.com # そのまま使用: https://cdn.example.com + + # パスのみ指定 + styles: + path: css # パス: /var/www/project/www/css + # URL: https://example.com/assets/css +``` + + +カスタムマッパー +-------- + +カスタムマッパーの場合は、サービスを参照するか定義します。 + +```neon +services: + s3mapper: App\Assets\S3Mapper(%s3.bucket%) + +assets: + mapping: + cloud: @s3mapper + database: App\Assets\DatabaseMapper(@database.connection) +``` + + +Viteマッパー +-------- + +Viteマッパーは`type: vite`を追加するだけで済みます。以下は設定オプションの完全なリストです。 + +```neon +assets: + mapping: + default: + # マッパータイプ (Viteに必須) + type: vite # (string) 必須、'vite'でなければならない + + # Viteビルド出力ディレクトリ + path: ... # (string) オプション、デフォルトは '' + + # ビルドされたアセットのURLプレフィックス + url: ... # (string) オプション、デフォルトは path + + # Viteマニフェストファイルの場所 + manifest: ... # (string) オプション、デフォルトは /.vite/manifest.json + + # Vite開発サーバー設定 + devServer: ... # (bool|string) オプション、デフォルトは true + + # publicディレクトリファイルのバージョニング + versioning: ... # (bool) オプション、グローバル設定を継承 + + # publicディレクトリファイルの自動拡張子 + extension: ... # (string|array) オプション、デフォルトは null +``` + +`devServer`オプションは、開発中にアセットがどのようにロードされるかを制御します。 + +- `true`(デフォルト) - 現在のホストとポートでVite開発サーバーを自動的に検出します。開発サーバーが実行中で**アプリケーションがデバッグモードの場合**、アセットはホットモジュールリプレイスメント(HMR)をサポートしてそこからロードされます。開発サーバーが実行されていない場合、アセットはpublicディレクトリ内のビルド済みファイルからロードされます。 +- `false` - 開発サーバーの連携を完全に無効にします。アセットは常にビルド済みファイルからロードされます。 +- カスタムURL(例: `https://localhost:5173`) - プロトコルとポートを含む開発サーバーのURLを手動で指定します。開発サーバーが異なるホストまたはポートで実行されている場合に便利です。 + +`versioning`と`extension`オプションは、Viteによって処理されないViteのpublicディレクトリ内のファイルにのみ適用されます。 + + +手動設定 +---- + +Nette DIを使用しない場合、マッパーを手動で設定します。 + +```php +use Nette\Assets\Registry; +use Nette\Assets\FilesystemMapper; +use Nette\Assets\ViteMapper; + +$registry = new Registry; + +// ファイルシステムマッパーを追加 +$registry->addMapper('images', new FilesystemMapper( + baseUrl: 'https://example.com/img', + basePath: __DIR__ . '/www/img', + extensions: ['webp', 'jpg', 'png'], + versioning: true, +)); + +// Viteマッパーを追加 +$registry->addMapper('app', new ViteMapper( + baseUrl: '/build', + basePath: __DIR__ . '/www/build', + manifestPath: __DIR__ . '/www/build/.vite/manifest.json', + devServer: 'https://localhost:5173', +)); +``` diff --git a/assets/ja/vite.texy b/assets/ja/vite.texy new file mode 100644 index 0000000000..1fa0143c36 --- /dev/null +++ b/assets/ja/vite.texy @@ -0,0 +1,508 @@ +Vite連携 +****** + +
    + +最新のJavaScriptアプリケーションには、洗練されたビルドツールが必要です。Nette Assetsは、次世代のフロントエンドビルドツールである[Vite|https://vitejs.dev/]とのファーストクラスの連携を提供します。設定の手間なしで、ホットモジュールリプレイスメント(HMR)による超高速開発と最適化された本番ビルドを実現します。 + +- **ゼロ設定** - ViteとPHPテンプレート間の自動ブリッジ +- **完全な依存関係管理** - 1つのタグですべてのアセットを処理 +- **ホットモジュールリプレイスメント** - JavaScriptとCSSの即時更新 +- **最適化された本番ビルド** - コード分割とツリーシェイキング + +
    + + +Nette AssetsはViteとシームレスに連携するため、テンプレートを通常通り記述しながら、これらすべての恩恵を受けることができます。 + + +Viteのセットアップ +=========== + +Viteをステップバイステップでセットアップしましょう。ビルドツールに慣れていなくても心配ありません。すべて説明します! + + +ステップ1: Viteをインストールする +-------------------- + +まず、プロジェクトにViteとNetteプラグインをインストールします。 + +```shell +npm install -D vite @nette/vite-plugin +``` + +これにより、Viteと、ViteがNetteと完全に連携するのに役立つ特別なプラグインがインストールされます。 + + +ステップ2: プロジェクト構造 +--------------- + +標準的なアプローチは、ソースアセットファイルをプロジェクトルートの`assets/`フォルダーに配置し、コンパイルされたバージョンを`www/assets/`に配置することです。 + +/--pre +web-project/ +├── assets/ ← ソースファイル (SCSS, TypeScript, ソース画像) +│ ├── public/ ← 静的ファイル (そのままコピーされる) +│ │ └── favicon.ico +│ ├── images/ +│ │ └── logo.png +│ ├── app.js ← メインエントリポイント +│ └── style.css ← スタイル +└── www/ ← public ディレクトリ (ドキュメントルート) + ├── assets/ ← コンパイルされたファイルがここに入る + └── index.php +\-- + +`assets/`フォルダーにはソースファイル、つまり記述するコードが含まれています。Viteはこれらのファイルを処理し、コンパイルされたバージョンを`www/assets/`に配置します。 + + +ステップ3: Viteを設定する +---------------- + +プロジェクトルートに`vite.config.ts`ファイルを作成します。このファイルは、Viteにソースファイルの場所とコンパイルされたファイルの出力先を指示します。 + +Nette Viteプラグインには、設定を簡素化するスマートなデフォルトが付属しています。フロントエンドのソースファイルは`assets/`ディレクトリ(`root`オプション)にあり、コンパイルされたファイルは`www/assets/`(`outDir`オプション)に配置されると想定しています。必要なのは[エントリポイント|#Entry Points]を指定することだけです。 + +```js +import { defineConfig } from 'vite'; +import nette from '@nette/vite-plugin'; + +export default defineConfig({ + plugins: [ + nette({ + entry: 'app.js', + }), + ], +}); +``` + +アセットをビルドするために別のディレクトリ名を指定したい場合は、いくつかのオプションを変更する必要があります。 + +```js +export default defineConfig({ + root: 'assets', // ソースアセットのルートディレクトリ + + build: { + outDir: '../www/assets', // コンパイルされたファイルの出力先 + }, + + // ... その他の設定 ... +}); +``` + +.[note] +`outDir`パスは`root`からの相対パスと見なされるため、先頭に`../`があります。 + + +ステップ4: Netteを設定する +----------------- + +`common.neon`でNette AssetsにViteについて伝えます。 + +```neon +assets: + mapping: + default: + type: vite # NetteにViteMapperを使用するよう指示 + path: assets +``` + + +ステップ5: スクリプトを追加する +----------------- + +これらのスクリプトを`package.json`に追加します。 + +```json +{ + "scripts": { + "dev": "vite", + "build": "vite build" + } +} +``` + +これで次のことができます。 +- `npm run dev` - ホットリロード付き開発サーバーを開始 +- `npm run build` - 最適化された本番ファイルを作成 + + +エントリポイント +======== + +**エントリポイント**は、アプリケーションが開始するメインファイルです。このファイルから、他のファイル(CSS、JavaScriptモジュール、画像)をインポートし、依存関係ツリーを作成します。Viteはこれらのインポートを追跡し、すべてをバンドルします。 + +エントリポイント`assets/app.js`の例: + +```js +// スタイルをインポート +import './style.css' + +// JavaScriptモジュールをインポート +import netteForms from 'nette-forms'; +import naja from 'naja'; + +// アプリケーションを初期化 +netteForms.initOnLoad(); +naja.initialize(); +``` + +テンプレートでは、エントリポイントを次のように挿入できます。 + +```latte +{asset 'app.js'} +``` + +Nette Assetsは、必要なすべてのHTMLタグ(JavaScript、CSS、およびその他の依存関係)を自動的に生成します。 + + +複数のエントリポイント +----------- + +大規模なアプリケーションでは、個別のエントリポイントが必要になることがよくあります。 + +```js +export default defineConfig({ + plugins: [ + nette({ + entry: [ + 'app.js', // public ページ + 'admin.js', // 管理パネル + ], + }), + ], +}); +``` + +異なるテンプレートで使用します。 + +```latte +{* public ページで *} +{asset 'app.js'} + +{* 管理パネルで *} +{asset 'admin.js'} +``` + + +重要: ソースファイルとコンパイル済みファイル +----------------------- + +本番環境では、次のもののみをロードできることを理解することが重要です。 + +1. `entry`で定義された**エントリポイント** +2. `assets/public/`ディレクトリの**ファイル** + +`{asset}`を使用して`assets/`から任意のファイルをロードすることは**できません**。JavaScriptまたはCSSファイルによって参照されているアセットのみです。ファイルがどこからも参照されていない場合、コンパイルされません。Viteに他のアセットを認識させたい場合は、[public フォルダー|#public folder]に移動できます。 + +デフォルトでは、Viteは4KB未満のすべてのアセットをインライン化するため、これらのファイルを直接参照できないことに注意してください。([Vite ドキュメント|https://vitejs.dev/guide/assets.html]を参照)。 + +```latte +{* ✓ これは動作します - エントリポイントです *} +{asset 'app.js'} + +{* ✓ これは動作します - assets/public/ にあります *} +{asset 'favicon.ico'} + +{* ✗ これは動作しません - assets/ 内のランダムなファイルです *} +{asset 'components/button.js'} +``` + + +開発モード +===== + +開発モードは完全にオプションですが、有効にすると大きなメリットがあります。主な利点は**ホットモジュールリプレイスメント (HMR)**です。アプリケーションの状態を失うことなく変更を即座に確認できるため、開発エクスペリエンスがはるかにスムーズで高速になります。 + +Viteは、開発を信じられないほど高速にする最新のビルドツールです。従来のバンドラーとは異なり、Viteは開発中にコードをブラウザに直接提供するため、プロジェクトの規模に関係なくサーバーの起動が瞬時に行われ、更新も超高速です。 + + +開発サーバーの起動 +--------- + +開発サーバーを実行します。 + +```shell +npm run dev +``` + +次のように表示されます。 + +``` + ➜ Local: http://localhost:5173/ + ➜ Network: use --host to expose +``` + +開発中は、このターミナルを開いたままにしてください。 + +Nette Viteプラグインは、次の条件が満たされたときに自動的に検出します。 +1. Vite開発サーバーが実行中である +2. Netteアプリケーションがデバッグモードである + +両方の条件が満たされると、Nette Assetsはコンパイルされたディレクトリからではなく、Vite開発サーバーからファイルをロードします。 + +```latte +{asset 'app.js'} +{* 開発時: *} +{* 本番時: *} +``` + +設定は不要です。ただ動作します! + + +異なるドメインでの作業 +----------- + +開発サーバーが`localhost`以外のもの(例: `myapp.local`)で実行されている場合、CORS(Cross-Origin Resource Sharing)の問題が発生する可能性があります。CORSは、ウェブブラウザのセキュリティ機能であり、デフォルトでは異なるドメイン間のリクエストをブロックします。PHPアプリケーションが`myapp.local`で実行されているが、Viteが`localhost:5173`で実行されている場合、ブラウザはこれらを異なるドメインと見なし、リクエストをブロックします。 + +これを解決するには2つのオプションがあります。 + +**オプション1: CORSを設定する** + +最も簡単な解決策は、PHPアプリケーションからのクロスオリジンリクエストを許可することです。 + +```js +export default defineConfig({ + // ... その他の設定 ... + + server: { + cors: { + origin: 'http://myapp.local', // PHPアプリのURL + }, + }, +}); +``` +**オプション2: Viteを同じドメインで実行する** + +もう1つの解決策は、ViteをPHPアプリケーションと同じドメインで実行することです。 + +```js +export default defineConfig({ + // ... その他の設定 ... + + server: { + host: 'myapp.local', // PHPアプリと同じ + }, +}); +``` + +実際、この場合でも、開発サーバーは同じホスト名ですが異なるポートで実行されるため、CORSを設定する必要があります。ただし、この場合、CORSはNette Viteプラグインによって自動的に設定されます。 + + +HTTPS開発 +------- + +HTTPSで開発する場合、Vite開発サーバーには証明書が必要です。最も簡単な方法は、証明書を自動的に生成するプラグインを使用することです。 + +```shell +npm install -D vite-plugin-mkcert +``` + +`vite.config.ts`での設定方法は次のとおりです。 + +```js +import mkcert from 'vite-plugin-mkcert'; + +export default defineConfig({ + // ... その他の設定 ... + + plugins: [ + mkcert(), // 証明書を自動生成し、httpsを有効にする + nette(), + ], +}); +``` + +CORS設定(上記のオプション1)を使用している場合、オリジンURLを`http://`ではなく`https://`を使用するように更新する必要があることに注意してください。 + + +本番ビルド +===== + +最適化された本番ファイルを作成します。 + +```shell +npm run build +``` + +Viteは次のことを行います。 +- すべてのJavaScriptとCSSをミニファイします +- コードを最適なチャンクに分割します +- キャッシュバストのためにハッシュ化されたファイル名を生成します +- Nette Assets用のマニフェストファイルを作成します + +出力例: + +``` +www/assets/ +├── app-4f3a2b1c.js # メインJavaScript (ミニファイ済み) +├── app-7d8e9f2a.css # 抽出されたCSS (ミニファイ済み) +├── vendor-8c4b5e6d.js # 共有依存関係 +└── .vite/ + └── manifest.json # Nette Assetsのマッピング +``` + +ハッシュ化されたファイル名により、ブラウザは常に最新バージョンをロードします。 + + +Public フォルダー +============ + +`assets/public/`ディレクトリ内のファイルは、処理されずにそのまま出力にコピーされます。 + +``` +assets/ +├── public/ +│ ├── favicon.ico +│ ├── robots.txt +│ └── images/ +│ └── og-image.jpg +├── app.js +└── style.css +``` + +通常通り参照します。 + +```latte +{* これらのファイルはそのままコピーされます *} + + +``` + +publicファイルの場合、FilesystemMapperの機能を使用できます。 + +```neon +assets: + mapping: + default: + type: vite + path: assets + extension: [webp, jpg, png] # 最初にWebPを試す + versioning: true # キャッシュバストを追加 +``` + +`vite.config.ts`設定では、`publicDir`オプションを使用してpublicフォルダーを変更できます。 + + +動的インポート +======= + +Viteは最適なロードのためにコードを自動的に分割します。動的インポートを使用すると、実際に必要なときにのみコードをロードできるため、初期バンドルサイズを削減できます。 + +```js +// 重いコンポーネントをオンデマンドでロード +button.addEventListener('click', async () => { + let { Chart } = await import('./components/chart.js') + new Chart(data) +}) +``` + +動的インポートは、必要なときにのみロードされる個別のチャンクを作成します。これは「コード分割」と呼ばれ、Viteの最も強力な機能の1つです。動的インポートを使用すると、Viteは動的にインポートされたモジュールごとに個別のJavaScriptファイルを自動的に作成します。 + +`{asset 'app.js'}`タグは、これらの動的チャンクを自動的にプリロード**しません**。これは意図的な動作です。使用されない可能性のあるコードをダウンロードしたくありません。チャンクは、動的インポートが実行されたときにのみダウンロードされます。 + +ただし、特定の動的インポートが重要であり、すぐに必要になることがわかっている場合は、それらをプリロードできます。 + +```latte +{* メインエントリポイント *} +{asset 'app.js'} + +{* 重要な動的インポートをプリロード *} +{preload 'components/chart.js'} +``` + +これにより、ブラウザはチャートコンポーネントをバックグラウンドでダウンロードし、必要になったときにすぐに使用できるようにします。 + + +TypeScriptサポート +============== + +TypeScriptはそのまま動作します。 + +```ts +// assets/main.ts +interface User { + name: string + email: string +} + +export function greetUser(user: User): void { + console.log(`Hello, ${user.name}!`) +} +``` + +TypeScriptファイルを通常通り参照します。 + +```latte +{asset 'main.ts'} +``` + +完全なTypeScriptサポートには、インストールが必要です。 + +```shell +npm install -D typescript +``` + + +追加のVite設定 +========= + +以下に、詳細な説明付きの便利なVite設定オプションをいくつか示します。 + +```js +export default defineConfig({ + // ソースアセットを含むルートディレクトリ + root: 'assets', + + // 内容がそのまま出力ディレクトリにコピーされるフォルダー + // デフォルト: 'public' ('root'からの相対パス) + publicDir: 'public', + + build: { + // コンパイルされたファイルの出力先 ('root'からの相対パス) + outDir: '../www/assets', + + // ビルド前に出力ディレクトリを空にするか? + // 以前のビルドからの古いファイルを削除するのに便利 + emptyOutDir: true, + + // outDir内の生成されたチャンクとアセットのサブディレクトリ + // 出力構造を整理するのに役立つ + assetsDir: 'static', + + rollupOptions: { + // エントリポイント - 単一のファイルまたはファイルの配列 + // 各エントリポイントは個別のバンドルになる + input: [ + 'app.js', // メインアプリケーション + 'admin.js', // 管理パネル + ], + }, + }, + + server: { + // 開発サーバーをバインドするホスト + // ネットワークに公開するには '0.0.0.0' を使用 + host: 'localhost', + + // 開発サーバーのポート + port: 5173, + + // クロスオリジンリクエストのCORS設定 + cors: { + origin: 'http://myapp.local', + }, + }, + + css: { + // 開発時にCSSソースマップを有効にする + devSourcemap: true, + }, + + plugins: [ + nette(), + ], +}); +``` + +これで完了です!Nette Assetsと連携した最新のビルドシステムが手に入りました。 diff --git a/assets/meta.json b/assets/meta.json new file mode 100644 index 0000000000..5cdc50207b --- /dev/null +++ b/assets/meta.json @@ -0,0 +1,5 @@ +{ + "version": "1.0", + "repo": "nette/assets", + "composer": "nette/assets" +} diff --git a/assets/pl/@home.texy b/assets/pl/@home.texy new file mode 100644 index 0000000000..21e33f18ee --- /dev/null +++ b/assets/pl/@home.texy @@ -0,0 +1,432 @@ +Nette Assets +************ + +
    + +Masz dość ręcznego zarządzania plikami statycznymi w swoich aplikacjach webowych? Zapomnij o kodowaniu ścieżek na stałe, problemach z unieważnianiem pamięci podręcznej czy martwieniu się o wersjonowanie plików. Nette Assets zmienia sposób, w jaki pracujesz z obrazami, arkuszami stylów, skryptami i innymi zasobami statycznymi. + +- **Inteligentne wersjonowanie** zapewnia, że przeglądarki zawsze ładują najnowsze pliki +- **Automatyczne wykrywanie** typów plików i wymiarów +- **Bezproblemowa integracja z Latte** dzięki intuicyjnym tagom +- **Elastyczna architektura** wspierająca systemy plików, CDN i Vite +- **Leniwe ładowanie** dla optymalnej wydajności + +
    + + +Dlaczego Nette Assets? +====================== + +Praca z plikami statycznymi często oznacza powtarzalny, podatny na błędy kod. Ręcznie konstruujesz adresy URL, dodajesz parametry wersji dla unieważniania pamięci podręcznej i obsługujesz różne typy plików w różny sposób. Prowadzi to do kodu takiego jak: + +```html +Logo + +``` + +Z Nette Assets cała ta złożoność znika: + +```latte +{* Wszystko zautomatyzowane - URL, wersjonowanie, wymiary *} + + + +{* Albo po prostu *} +{asset 'css/style.css'} +``` + +To wszystko! Biblioteka automatycznie: +- Dodaje parametry wersji na podstawie czasu modyfikacji pliku +- Wykrywa wymiary obrazu i uwzględnia je w HTML +- Generuje prawidłowy element HTML dla każdego typu pliku +- Obsługuje zarówno środowiska deweloperskie, jak i produkcyjne + + +Instalacja +========== + +Zainstaluj Nette Assets za pomocą [Composera|best-practices:composer]: + +```shell +composer require nette/assets +``` + +Wymaga PHP 8.1 lub nowszego i działa idealnie z Nette Framework, ale może być również używany samodzielnie. + + +Pierwsze kroki +============== + +Nette Assets działa od razu po wyjęciu z pudełka bez żadnej konfiguracji. Umieść swoje pliki statyczne w katalogu `www/assets/` i zacznij ich używać: + +```latte +{* Wyświetl obraz z automatycznymi wymiarami *} +{asset 'logo.png'} + +{* Dołącz arkusz stylów z wersjonowaniem *} +{asset 'style.css'} + +{* Załaduj moduł JavaScript *} +{asset 'app.js'} +``` + +Aby mieć większą kontrolę nad generowanym HTML, użyj atrybutu `n:asset` lub funkcji `asset()`. + + +Jak to działa +============= + +Nette Assets opiera się na trzech kluczowych koncepcjach, które sprawiają, że jest potężny, a jednocześnie prosty w użyciu: + + +Zasoby - Twoje pliki stają się inteligentne +------------------------------------------- + +**Zasób** reprezentuje dowolny plik statyczny w Twojej aplikacji. Każdy plik staje się obiektem z przydatnymi właściwościami tylko do odczytu: + +```php +$image = $assets->getAsset('photo.jpg'); +echo $image->url; // '/assets/photo.jpg?v=1699123456' +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' +``` + +Różne typy plików udostępniają różne właściwości: +- **Obrazy**: szerokość, wysokość, tekst alternatywny, leniwe ładowanie +- **Skrypty**: typ modułu, hashe integralności, crossorigin +- **Arkusze stylów**: zapytania mediów, integralność +- **Audio/Wideo**: czas trwania, wymiary +- **Czcionki**: prawidłowe wstępne ładowanie z CORS + +Biblioteka automatycznie wykrywa typy plików i tworzy odpowiednią klasę zasobów. + + +Mappery - Skąd pochodzą pliki +----------------------------- + +**Mapper** wie, jak znaleźć pliki i utworzyć dla nich adresy URL. Możesz mieć wiele mapperów do różnych celów – plików lokalnych, CDN, przechowywania w chmurze lub narzędzi do budowania (każdy z nich ma nazwę). Wbudowany `FilesystemMapper` obsługuje pliki lokalne, natomiast `ViteMapper` integruje się z nowoczesnymi narzędziami do budowania. + +Mappery są definiowane w [konfiguracji |Configuration]. + + +Rejestr - Twój główny interfejs +------------------------------- + +**Rejestr** zarządza wszystkimi mapperami i udostępnia główne API: + +```php +// Wstrzyknij rejestr do swojej usługi +public function __construct( + private Nette\Assets\Registry $assets +) {} + +// Pobierz zasoby z różnych mapperów +$logo = $this->assets->getAsset('images:logo.png'); // mapper 'image' +$app = $this->assets->getAsset('app:main.js'); // mapper 'app' +$style = $this->assets->getAsset('style.css'); // używa domyślnego mappera +``` + +Rejestr automatycznie wybiera odpowiedni mapper i buforuje wyniki dla zwiększenia wydajności. + + +Praca z zasobami w PHP +====================== + +Rejestr udostępnia dwie metody pobierania zasobów: + +```php +// Rzuca Nette\Assets\AssetNotFoundException, jeśli plik nie istnieje +$logo = $assets->getAsset('logo.png'); + +// Zwraca null, jeśli plik nie istnieje +$banner = $assets->tryGetAsset('banner.jpg'); +if ($banner) { + echo $banner->url; +} +``` + + +Określanie mapperów +------------------- + +Możesz jawnie wybrać, którego mappera użyć: + +```php +// Użyj domyślnego mappera +$file = $assets->getAsset('document.pdf'); + +// Użyj konkretnego mappera z prefiksem +$image = $assets->getAsset('images:photo.jpg'); + +// Użyj konkretnego mappera z składnią tablicową +$script = $assets->getAsset(['scripts', 'app.js']); +``` + + +Właściwości i typy zasobów +-------------------------- + +Każdy typ zasobu udostępnia odpowiednie właściwości tylko do odczytu: + +```php +// Właściwości obrazu +$image = $assets->getAsset('photo.jpg'); +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' + +// Właściwości skryptu +$script = $assets->getAsset('app.js'); +echo $script->type; // 'module' or null + +// Właściwości audio +$audio = $assets->getAsset('song.mp3'); +echo $audio->duration; // duration in seconds + +// Wszystkie zasoby mogą być rzutowane na ciąg znaków (zwraca URL) +$url = (string) $assets->getAsset('document.pdf'); +``` + +.[note] +Właściwości takie jak wymiary czy czas trwania są ładowane leniwie tylko przy dostępie, co utrzymuje bibliotekę szybką. + + +Używanie zasobów w szablonach Latte +=================================== + +Nette Assets zapewnia intuicyjną integrację [Latte|latte:] z tagami i funkcjami. + + +`{asset}` +--------- + +Tag `{asset}` renderuje kompletne elementy HTML: + +```latte +{* Renderuje: *} +{asset 'hero.jpg'} + +{* Renderuje: *} +{asset 'app.js'} + +{* Renderuje: *} +{asset 'style.css'} +``` + +Tag automatycznie: +- Wykrywa typ zasobu i generuje odpowiedni HTML +- Włącza wersjonowanie dla unieważniania pamięci podręcznej +- Dodaje wymiary dla obrazów +- Ustawia prawidłowe atrybuty (typ, media itp.) + +Użyty wewnątrz atrybutów HTML, wyprowadza tylko URL: + +```latte +
    + +``` + + +`n:asset` +--------- + +Dla pełnej kontroli nad atrybutami HTML: + +```latte +{* Atrybut n:asset wypełnia src, wymiary itp. *} +Product + +{* Działa z każdym odpowiednim elementem *} + + + +``` + +Użyj zmiennych i mapperów: + +```latte +{* Zmienne działają naturalnie *} + + +{* Określ mapper za pomocą nawiasów klamrowych *} + + +{* Określ mapper za pomocą notacji tablicowej *} + +``` + + +`asset()` +--------- + +Dla maksymalnej elastyczności użyj funkcji `asset()`: + +```latte +{var $logo = asset('logo.png')} +width} height={$logo->height}> + +{* Albo bezpośrednio *} +Logo +``` + + +Opcjonalne zasoby +----------------- + +Obsługuj brakujące zasoby elegancko za pomocą `{asset?}`, `n:asset?` i `tryAsset()`: + +```latte +{* Opcjonalny tag - nie renderuje niczego, jeśli zasób brakuje *} +{asset? 'optional-banner.jpg'} + +{* Opcjonalny atrybut - pomija, jeśli zasób brakuje *} +Avatar + +{* Z fallbackiem *} +{var $avatar = tryAsset('user-avatar.jpg') ?? asset('default-avatar.jpg')} +Avatar +``` + + +`{preload}` +----------- + +Popraw wydajność ładowania strony: + +```latte +{* W sekcji *} +{preload 'critical.css'} +{preload 'important-font.woff2'} +{preload 'hero-image.jpg'} +``` + +Generuje odpowiednie linki preload: + +```html + + + +``` + + +Zaawansowane funkcje +==================== + + +Automatyczne wykrywanie rozszerzeń +---------------------------------- + +Automatycznie obsługuj wiele formatów: + +```neon +assets: + mapping: + images: + path: img + extension: [webp, jpg, png] # Spróbuj w kolejności +``` + +Teraz możesz żądać bez rozszerzenia: + +```latte +{* Automatycznie znajduje logo.webp, logo.jpg lub logo.png *} +{asset 'images:logo'} +``` + +Idealne do progresywnego ulepszania z nowoczesnymi formatami. + + +Inteligentne wersjonowanie +-------------------------- + +Pliki są automatycznie wersjonowane na podstawie czasu modyfikacji: + +```latte +{asset 'style.css'} +{* Wyjście: *} +``` + +Po zaktualizowaniu pliku, znacznik czasu zmienia się, wymuszając odświeżenie pamięci podręcznej przeglądarki. + +Kontroluj wersjonowanie dla każdego zasobu: + +```php +// Wyłącz wersjonowanie dla konkretnego zasobu +$asset = $assets->getAsset('style.css', ['version' => false]); + +// W Latte +{asset 'style.css', version: false} +``` + + +Zasoby czcionek +--------------- + +Czcionki są traktowane specjalnie z prawidłowym CORS: + +```latte +{* Prawidłowe wstępne ładowanie z crossorigin *} +{preload 'fonts:OpenSans-Regular.woff2'} + +{* Użyj w CSS *} + +``` + + +Niestandardowe mappery +====================== + +Twórz niestandardowe mappery dla specjalnych potrzeb, takich jak przechowywanie w chmurze lub dynamiczne generowanie: + +```php +use Nette\Assets\Mapper; +use Nette\Assets\Asset; +use Nette\Assets\Helpers; + +class CloudStorageMapper implements Mapper +{ + public function __construct( + private CloudClient $client, + private string $bucket, + ) {} + + public function getAsset(string $reference, array $options = []): Asset + { + if (!$this->client->exists($this->bucket, $reference)) { + throw new Nette\Assets\AssetNotFoundException("Asset '$reference' not found"); + } + + $url = $this->client->getPublicUrl($this->bucket, $reference); + return Helpers::createAssetFromUrl($url); + } +} +``` + +Zarejestruj w konfiguracji: + +```neon +assets: + mapping: + cloud: CloudStorageMapper(@cloudClient, 'my-bucket') +``` + +Użyj jak każdego innego mappera: + +```latte +{asset 'cloud:user-uploads/photo.jpg'} +``` + +Metoda `Helpers::createAssetFromUrl()` automatycznie tworzy prawidłowy typ zasobu na podstawie rozszerzenia pliku. + + +Więcej informacji +================= + +- [Nette Assets: Wreszcie ujednolicone API dla wszystkiego, od obrazów po Vite |https://blog.nette.org/en/introducing-nette-assets] diff --git a/assets/pl/@left-menu.texy b/assets/pl/@left-menu.texy new file mode 100644 index 0000000000..9e642b6cfe --- /dev/null +++ b/assets/pl/@left-menu.texy @@ -0,0 +1,5 @@ +Nette Assets +************ +- [Rozpoczęcie pracy |@home] +- [Vite |vite] +- [Konfiguracja |Configuration] diff --git a/assets/pl/@meta.texy b/assets/pl/@meta.texy new file mode 100644 index 0000000000..61ac92d1af --- /dev/null +++ b/assets/pl/@meta.texy @@ -0,0 +1 @@ +{{sitename: Dokumentacja Nette}} diff --git a/assets/pl/configuration.texy b/assets/pl/configuration.texy new file mode 100644 index 0000000000..fbefdbf1e3 --- /dev/null +++ b/assets/pl/configuration.texy @@ -0,0 +1,188 @@ +Konfiguracja zasobów +******************** + +.[perex] +Przegląd opcji konfiguracyjnych dla Nette Assets. + + +```neon +assets: + # ścieżka bazowa do rozwiązywania względnych ścieżek mappera + basePath: ... # (string) domyślnie %wwwDir% + + # bazowy URL do rozwiązywania względnych URL mappera + baseUrl: ... # (string) domyślnie %baseUrl% + + # włączyć globalne wersjonowanie zasobów? + versioning: ... # (bool) domyślnie true + + # definiuje mappery zasobów + mapping: ... # (array) domyślnie ścieżka 'assets' +``` + +Opcja `basePath` ustawia domyślny katalog systemu plików do rozwiązywania ścieżek względnych w mapperach. Domyślnie używa katalogu webowego (`%wwwDir%`). + +Opcja `baseUrl` ustawia domyślny prefiks URL do rozwiązywania względnych adresów URL w mapperach. Domyślnie używa głównego URL (`%baseUrl%`). + +Opcja `versioning` globalnie kontroluje, czy parametry wersji są dodawane do adresów URL zasobów w celu unieważnienia pamięci podręcznej. Poszczególne mappery mogą nadpisać to ustawienie. + + +Mappery +------- + +Mappery można konfigurować na trzy sposoby: prostą notacją stringową, szczegółową notacją tablicową lub jako odwołanie do usługi. + +Najprostszy sposób zdefiniowania mappera: + +```neon +assets: + mapping: + default: assets # Tworzy mapper systemu plików dla %wwwDir%/assets/ + images: img # Tworzy mapper systemu plików dla %wwwDir%/img/ + scripts: js # Tworzy mapper systemu plików dla %wwwDir%/js/ +``` + +Każdy mapper tworzy `FilesystemMapper`, który: +- Szuka plików w `%wwwDir%/` +- Generuje adresy URL takie jak `%baseUrl%/` +- Dziedziczy globalne ustawienie wersjonowania + + +Dla większej kontroli użyj szczegółowej notacji: + +```neon +assets: + mapping: + images: + # katalog, w którym przechowywane są pliki + path: ... # (string) opcjonalnie, domyślnie '' + + # prefiks URL dla generowanych linków + url: ... # (string) opcjonalnie, domyślnie ścieżka + + # włączyć wersjonowanie dla tego mappera? + versioning: ... # (bool) opcjonalnie, dziedziczy ustawienie globalne + + # automatycznie dodawaj rozszerzenie(a) podczas wyszukiwania plików + extension: ... # (string|array) opcjonalnie, domyślnie null +``` + +Zrozumienie, jak rozwiązywane są wartości konfiguracyjne: + +Rozwiązywanie ścieżek: + - Ścieżki względne są rozwiązywane z `basePath` (lub `%wwwDir%`, jeśli `basePath` nie jest ustawione) + - Ścieżki bezwzględne są używane bez zmian + +Rozwiązywanie URL: + - Względne adresy URL są rozwiązywane z `baseUrl` (lub `%baseUrl%`, jeśli `baseUrl` nie jest ustawione) + - Bezwzględne adresy URL (ze schematem lub `//`) są używane bez zmian + - Jeśli `url` nie jest określone, używa wartości `path` + + +```neon +assets: + basePath: /var/www/project/www + baseUrl: https://example.com/assets + + mapping: + # Względna ścieżka i URL + images: + path: img # Rozwiązane do: /var/www/project/www/img + url: images # Rozwiązane do: https://example.com/assets/images + + # Bezwzględna ścieżka i URL + uploads: + path: /var/shared/uploads # Użyte bez zmian: /var/shared/uploads + url: https://cdn.example.com # Użyte bez zmian: https://cdn.example.com + + # Określono tylko ścieżkę + styles: + path: css # Ścieżka: /var/www/project/www/css + # URL: https://example.com/assets/css +``` + + +Niestandardowe mappery +---------------------- + +Dla niestandardowych mapperów, odwołaj się lub zdefiniuj usługę: + +```neon +services: + s3mapper: App\Assets\S3Mapper(%s3.bucket%) + +assets: + mapping: + cloud: @s3mapper + database: App\Assets\DatabaseMapper(@database.connection) +``` + + +Vite Mapper +----------- + +Mapper Vite wymaga jedynie dodania `type: vite`. Poniżej znajduje się pełna lista opcji konfiguracyjnych: + +```neon +assets: + mapping: + default: + # typ mappera (wymagany dla Vite) + type: vite # (string) wymagany, musi być 'vite' + + # katalog wyjściowy kompilacji Vite + path: ... # (string) opcjonalnie, domyślnie '' + + # prefiks URL dla zbudowanych zasobów + url: ... # (string) opcjonalnie, domyślnie ścieżka + + # lokalizacja pliku manifestu Vite + manifest: ... # (string) opcjonalnie, domyślnie /.vite/manifest.json + + # konfiguracja serwera deweloperskiego Vite + devServer: ... # (bool|string) opcjonalnie, domyślnie true + + # wersjonowanie dla plików z katalogu publicznego + versioning: ... # (bool) opcjonalnie, dziedziczy ustawienie globalne + + # automatyczne rozszerzenie dla plików z katalogu publicznego + extension: ... # (string|array) opcjonalnie, domyślnie null +``` + +Opcja `devServer` kontroluje, jak zasoby są ładowane podczas developmentu: + +- `true` (domyślnie) - Automatycznie wykrywa serwer deweloperski Vite na bieżącym hoście i porcie. Jeśli serwer deweloperski jest uruchomiony **i Twoja aplikacja jest w trybie debugowania**, zasoby są ładowane z niego z obsługą Hot Module Replacement. Jeśli serwer deweloperski nie jest uruchomiony, zasoby są ładowane ze zbudowanych plików w katalogu publicznym. +- `false` - Całkowicie wyłącza integrację z serwerem deweloperskim. Zasoby są zawsze ładowane ze zbudowanych plików. +- Niestandardowy URL (np. `https://localhost:5173`) - Ręcznie określ URL serwera deweloperskiego, włączając protokół i port. Przydatne, gdy serwer deweloperski działa na innym hoście lub porcie. + +Opcje `versioning` i `extension` dotyczą tylko plików w katalogu publicznym Vite, które nie są przetwarzane przez Vite. + + +Konfiguracja ręczna +------------------- + +Gdy nie używasz Nette DI, skonfiguruj mappery ręcznie: + +```php +use Nette\Assets\Registry; +use Nette\Assets\FilesystemMapper; +use Nette\Assets\ViteMapper; + +$registry = new Registry; + +// Dodaj mapper systemu plików +$registry->addMapper('images', new FilesystemMapper( + baseUrl: 'https://example.com/img', + basePath: __DIR__ . '/www/img', + extensions: ['webp', 'jpg', 'png'], + versioning: true, +)); + +// Dodaj mapper Vite +$registry->addMapper('app', new ViteMapper( + baseUrl: '/build', + basePath: __DIR__ . '/www/build', + manifestPath: __DIR__ . '/www/build/.vite/manifest.json', + devServer: 'https://localhost:5173', +)); +``` diff --git a/assets/pl/vite.texy b/assets/pl/vite.texy new file mode 100644 index 0000000000..d392857399 --- /dev/null +++ b/assets/pl/vite.texy @@ -0,0 +1,508 @@ +Integracja z Vite +***************** + +
    + +Nowoczesne aplikacje JavaScript wymagają zaawansowanych narzędzi do budowania. Nette Assets zapewnia pierwszorzędną integrację z [Vite |https://vitejs.dev/], narzędziem do budowania frontendowego nowej generacji. Uzyskaj błyskawiczne środowisko deweloperskie z Hot Module Replacement (HMR) i zoptymalizowane kompilacje produkcyjne bez problemów z konfiguracją. + +- **Zero konfiguracji** - automatyczny most między Vite a szablonami PHP +- **Kompletne zarządzanie zależnościami** - jeden tag obsługuje wszystkie zasoby +- **Hot Module Replacement** - natychmiastowe aktualizacje JavaScript i CSS +- **Zoptymalizowane kompilacje produkcyjne** - dzielenie kodu i tree shaking + +
    + + +Nette Assets integruje się bezproblemowo z Vite, dzięki czemu uzyskujesz wszystkie te korzyści, pisząc swoje szablony jak zwykle. + + +Konfigurowanie Vite +=================== + +Skonfigurujmy Vite krok po kroku. Nie martw się, jeśli jesteś nowy w narzędziach do budowania - wyjaśnimy wszystko! + + +Krok 1: Zainstaluj Vite +----------------------- + +Najpierw zainstaluj Vite i wtyczkę Nette w swoim projekcie: + +```shell +npm install -D vite @nette/vite-plugin +``` + +To instaluje Vite i specjalną wtyczkę, która pomaga Vite doskonale współpracować z Nette. + + +Krok 2: Struktura projektu +-------------------------- + +Standardowe podejście to umieszczenie plików źródłowych zasobów w folderze `assets/` w katalogu głównym projektu, a skompilowanych wersji w `www/assets/`: + +/--pre +web-project/ +├── assets/ ← pliki źródłowe (SCSS, TypeScript, obrazy źródłowe) +│ ├── public/ ← pliki statyczne (kopiowane bez zmian) +│ │ └── favicon.ico +│ ├── images/ +│ │ └── logo.png +│ ├── app.js ← główny punkt wejścia +│ └── style.css ← Twoje style +└── www/ ← katalog publiczny (root dokumentu) + ├── assets/ ← tutaj trafią skompilowane pliki + └── index.php +\-- + +Folder `assets/` zawiera Twoje pliki źródłowe - kod, który piszesz. Vite przetworzy te pliki i umieści skompilowane wersje w `www/assets/`. + + +Krok 3: Skonfiguruj Vite +------------------------ + +Utwórz plik `vite.config.ts` w katalogu głównym projektu. Ten plik informuje Vite, gdzie znaleźć Twoje pliki źródłowe i gdzie umieścić skompilowane. + +Wtyczka Nette Vite ma inteligentne wartości domyślne, które upraszczają konfigurację. Zakłada, że Twoje pliki źródłowe front-end znajdują się w katalogu `assets/` (opcja `root`), a skompilowane pliki trafiają do `www/assets/` (opcja `outDir`). Musisz tylko określić [punkt wejścia |#Entry Points]: + +```js +import { defineConfig } from 'vite'; +import nette from '@nette/vite-plugin'; + +export default defineConfig({ + plugins: [ + nette({ + entry: 'app.js', + }), + ], +}); +``` + +Jeśli chcesz określić inną nazwę katalogu do budowania swoich zasobów, będziesz musiał zmienić kilka opcji: + +```js +export default defineConfig({ + root: 'assets', // katalog główny zasobów źródłowych + + build: { + outDir: '../www/assets', // gdzie trafiają skompilowane pliki + }, + + // ... inna konfiguracja ... +}); +``` + +.[note] +Ścieżka `outDir` jest traktowana jako względna do `root`, dlatego na początku znajduje się `../`. + + +Krok 4: Skonfiguruj Nette +------------------------- + +Poinformuj Nette Assets o Vite w swoim `common.neon`: + +```neon +assets: + mapping: + default: + type: vite # informuje Nette, aby użyło ViteMapper + path: assets +``` + + +Krok 5: Dodaj skrypty +--------------------- + +Dodaj te skrypty do swojego `package.json`: + +```json +{ + "scripts": { + "dev": "vite", + "build": "vite build" + } +} +``` + +Teraz możesz: +- `npm run dev` - uruchom serwer deweloperski z hot reloadingiem +- `npm run build` - utwórz zoptymalizowane pliki produkcyjne + + +Punkty wejścia +============== + +**Punkt wejścia** to główny plik, w którym uruchamia się Twoja aplikacja. Z tego pliku importujesz inne pliki (CSS, moduły JavaScript, obrazy), tworząc drzewo zależności. Vite śledzi te importy i łączy wszystko razem. + +Przykładowy punkt wejścia `assets/app.js`: + +```js +// Importuj style +import './style.css' + +// Importuj moduły JavaScript +import netteForms from 'nette-forms'; +import naja from 'naja'; + +// Zainicjuj swoją aplikację +netteForms.initOnLoad(); +naja.initialize(); +``` + +W szablonie możesz wstawić punkt wejścia w następujący sposób: + +```latte +{asset 'app.js'} +``` + +Nette Assets automatycznie generuje wszystkie niezbędne tagi HTML - JavaScript, CSS i wszelkie inne zależności. + + +Wiele punktów wejścia +--------------------- + +Większe aplikacje często potrzebują oddzielnych punktów wejścia: + +```js +export default defineConfig({ + plugins: [ + nette({ + entry: [ + 'app.js', // strony publiczne + 'admin.js', // panel administracyjny + ], + }), + ], +}); +``` + +Używaj ich w różnych szablonach: + +```latte +{* Na stronach publicznych *} +{asset 'app.js'} + +{* W panelu administracyjnym *} +{asset 'admin.js'} +``` + + +Ważne: Pliki źródłowe vs skompilowane +------------------------------------- + +Kluczowe jest zrozumienie, że w środowisku produkcyjnym możesz ładować tylko: + +1. **Punkty wejścia** zdefiniowane w `entry` +2. **Pliki z katalogu `assets/public/`** + +Nie **możesz** ładować za pomocą `{asset}` dowolnych plików z `assets/` - tylko zasoby, do których odwołują się pliki JavaScript lub CSS. Jeśli Twój plik nie jest nigdzie odwołany, nie zostanie skompilowany. Jeśli chcesz, aby Vite był świadomy innych zasobów, możesz przenieść je do [folderu publicznego |#public folder]. + +Należy pamiętać, że domyślnie Vite wbuduje wszystkie zasoby mniejsze niż 4KB, więc nie będziesz mógł odwoływać się do tych plików bezpośrednio. (Zobacz [dokumentację Vite |https://vite.dev/guide/assets.html]). + +```latte +{* ✓ To działa - to punkt wejścia *} +{asset 'app.js'} + +{* ✓ To działa - to jest w assets/public/ *} +{asset 'favicon.ico'} + +{* ✗ To nie zadziała - losowy plik w assets/ *} +{asset 'components/button.js'} +``` + + +Tryb deweloperski +================= + +Tryb deweloperski jest całkowicie opcjonalny, ale zapewnia znaczące korzyści po włączeniu. Główną zaletą jest **Hot Module Replacement (HMR)** - natychmiastowe widzenie zmian bez utraty stanu aplikacji, co sprawia, że doświadczenie deweloperskie jest znacznie płynniejsze i szybsze. + +Vite to nowoczesne narzędzie do budowania, które sprawia, że rozwój jest niewiarygodnie szybki. W przeciwieństwie do tradycyjnych bundlerów, Vite serwuje Twój kod bezpośrednio do przeglądarki podczas developmentu, co oznacza natychmiastowe uruchomienie serwera niezależnie od wielkości projektu i błyskawiczne aktualizacje. + + +Uruchamianie serwera deweloperskiego +------------------------------------ + +Uruchom serwer deweloperski: + +```shell +npm run dev +``` + +Zobaczysz: + +``` + ➜ Local: http://localhost:5173/ + ➜ Network: use --host to expose +``` + +Pozostaw ten terminal otwarty podczas developmentu. + +Wtyczka Nette Vite automatycznie wykrywa, kiedy: +1. Serwer deweloperski Vite jest uruchomiony +2. Twoja aplikacja Nette jest w trybie debugowania + +Gdy oba warunki są spełnione, Nette Assets ładuje pliki z serwera deweloperskiego Vite zamiast z katalogu skompilowanego: + +```latte +{asset 'app.js'} +{* W trybie deweloperskim: *} +{* W trybie produkcyjnym: *} +``` + +Nie potrzeba konfiguracji - po prostu działa! + + +Praca na różnych domenach +------------------------- + +Jeśli Twój serwer deweloperski działa na czymś innym niż `localhost` (np. `myapp.local`), możesz napotkać problemy z CORS (Cross-Origin Resource Sharing). CORS to funkcja bezpieczeństwa w przeglądarkach internetowych, która domyślnie blokuje żądania między różnymi domenami. Gdy Twoja aplikacja PHP działa na `myapp.local`, a Vite na `localhost:5173`, przeglądarka traktuje je jako różne domeny i blokuje żądania. + +Masz dwie opcje rozwiązania tego problemu: + +**Opcja 1: Skonfiguruj CORS** + +Najprostszym rozwiązaniem jest zezwolenie na żądania cross-origin z Twojej aplikacji PHP: + +```js +export default defineConfig({ + // ... inna konfiguracja ... + + server: { + cors: { + origin: 'http://myapp.local', // URL Twojej aplikacji PHP + }, + }, +}); +``` +**Opcja 2: Uruchom Vite na swojej domenie** + +Innym rozwiązaniem jest uruchomienie Vite na tej samej domenie co Twoja aplikacja PHP. + +```js +export default defineConfig({ + // ... inna konfiguracja ... + + server: { + host: 'myapp.local', // to samo co Twoja aplikacja PHP + }, +}); +``` + +W rzeczywistości, nawet w tym przypadku, musisz skonfigurować CORS, ponieważ serwer deweloperski działa na tej samej nazwie hosta, ale na innym porcie. Jednak w tym przypadku CORS jest automatycznie konfigurowany przez wtyczkę Nette Vite. + + +Development HTTPS +----------------- + +Jeśli rozwijasz na HTTPS, potrzebujesz certyfikatów dla swojego serwera deweloperskiego Vite. Najprostszym sposobem jest użycie wtyczki, która automatycznie generuje certyfikaty: + +```shell +npm install -D vite-plugin-mkcert +``` + +Oto jak skonfigurować to w `vite.config.ts`: + +```js +import mkcert from 'vite-plugin-mkcert'; + +export default defineConfig({ + // ... inna konfiguracja ... + + plugins: [ + mkcert(), // automatycznie generuje certyfikaty i włącza https + nette(), + ], +}); +``` + +Zauważ, że jeśli używasz konfiguracji CORS (Opcja 1 powyżej), musisz zaktualizować adres URL źródła, aby używał `https://` zamiast `http://`. + + +Kompilacje produkcyjne +====================== + +Utwórz zoptymalizowane pliki produkcyjne: + +```shell +npm run build +``` + +Vite będzie: +- Minifikować cały JavaScript i CSS +- Dzielić kod na optymalne części +- Generować nazwy plików z hashami dla unieważniania pamięci podręcznej +- Tworzyć plik manifestu dla Nette Assets + +Przykładowe wyjście: + +``` +www/assets/ +├── app-4f3a2b1c.js # Twój główny JavaScript (zminifikowany) +├── app-7d8e9f2a.css # Wyodrębniony CSS (zminifikowany) +├── vendor-8c4b5e6d.js # Wspólne zależności +└── .vite/ + └── manifest.json # Mapowanie dla Nette Assets +``` + +Nazwy plików z hashami zapewniają, że przeglądarki zawsze ładują najnowszą wersję. + + +Folder publiczny +================ + +Pliki w katalogu `assets/public/` są kopiowane do wyjścia bez przetwarzania: + +``` +assets/ +├── public/ +│ ├── favicon.ico +│ ├── robots.txt +│ └── images/ +│ └── og-image.jpg +├── app.js +└── style.css +``` + +Odwołuj się do nich normalnie: + +```latte +{* Te pliki są kopiowane bez zmian *} + + +``` + +Dla plików publicznych możesz użyć funkcji FilesystemMapper: + +```neon +assets: + mapping: + default: + type: vite + path: assets + extension: [webp, jpg, png] # Spróbuj najpierw WebP + versioning: true # Dodaj unieważnianie pamięci podręcznej +``` + +W konfiguracji `vite.config.ts` możesz zmienić folder publiczny za pomocą opcji `publicDir`. + + +Dynamiczne importy +================== + +Vite automatycznie dzieli kod dla optymalnego ładowania. Dynamiczne importy pozwalają ładować kod tylko wtedy, gdy jest faktycznie potrzebny, zmniejszając początkowy rozmiar pakietu: + +```js +// Ładuj ciężkie komponenty na żądanie +button.addEventListener('click', async () => { + let { Chart } = await import('./components/chart.js') + new Chart(data) +}) +``` + +Dynamiczne importy tworzą oddzielne części kodu, które są ładowane tylko wtedy, gdy są potrzebne. Nazywa się to "dzieleniem kodu" i jest to jedna z najpotężniejszych funkcji Vite. Kiedy używasz dynamicznych importów, Vite automatycznie tworzy oddzielne pliki JavaScript dla każdego dynamicznie importowanego modułu. + +Tag `{asset 'app.js'}` **nie** ładuje automatycznie tych dynamicznych części kodu. Jest to zamierzone zachowanie - nie chcemy pobierać kodu, który może nigdy nie zostać użyty. Części kodu są pobierane tylko wtedy, gdy dynamiczny import jest wykonywany. + +Jednakże, jeśli wiesz, że pewne dynamiczne importy są krytyczne i będą potrzebne wkrótce, możesz je wstępnie załadować: + +```latte +{* Główny punkt wejścia *} +{asset 'app.js'} + +{* Wstępnie załaduj krytyczne dynamiczne importy *} +{preload 'components/chart.js'} +``` + +To instruuje przeglądarkę, aby pobrała komponent wykresu w tle, dzięki czemu jest on natychmiast gotowy, gdy będzie potrzebny. + + +Obsługa TypeScript +================== + +TypeScript działa od razu po wyjęciu z pudełka: + +```ts +// assets/main.ts +interface User { + name: string + email: string +} + +export function greetUser(user: User): void { + console.log(`Hello, ${user.name}!`) +} +``` + +Odwołuj się do plików TypeScript normalnie: + +```latte +{asset 'main.ts'} +``` + +Dla pełnej obsługi TypeScript, zainstaluj go: + +```shell +npm install -D typescript +``` + + +Dodatkowa konfiguracja Vite +=========================== + +Oto kilka przydatnych opcji konfiguracyjnych Vite ze szczegółowymi wyjaśnieniami: + +```js +export default defineConfig({ + // Katalog główny zawierający zasoby źródłowe + root: 'assets', + + // Folder, którego zawartość jest kopiowana do katalogu wyjściowego bez zmian + // Domyślnie: 'public' (względnie do 'root') + publicDir: 'public', + + build: { + // Gdzie umieścić skompilowane pliki (względnie do 'root') + outDir: '../www/assets', + + // Opróżnić katalog wyjściowy przed budowaniem? + // Przydatne do usuwania starych plików z poprzednich kompilacji + emptyOutDir: true, + + // Podkatalog w outDir dla generowanych części kodu i zasobów + // Pomaga to zorganizować strukturę wyjściową + assetsDir: 'static', + + rollupOptions: { + // Punkt(y) wejścia - może być pojedynczym plikiem lub tablicą plików + // Każdy punkt wejścia staje się oddzielnym pakietem + input: [ + 'app.js', // główna aplikacja + 'admin.js', // panel administracyjny + ], + }, + }, + + server: { + // Host do powiązania serwera deweloperskiego + // Użyj '0.0.0.0', aby udostępnić w sieci + host: 'localhost', + + // Port dla serwera deweloperskiego + port: 5173, + + // Konfiguracja CORS dla żądań cross-origin + cors: { + origin: 'http://myapp.local', + }, + }, + + css: { + // Włącz mapy źródłowe CSS w trybie deweloperskim + devSourcemap: true, + }, + + plugins: [ + nette(), + ], +}); +``` + +To wszystko! Masz teraz nowoczesny system budowania zintegrowany z Nette Assets. diff --git a/assets/pt/@home.texy b/assets/pt/@home.texy new file mode 100644 index 0000000000..0bb4e7dd24 --- /dev/null +++ b/assets/pt/@home.texy @@ -0,0 +1,432 @@ +Nette Assets +************ + +
    + +Cansado de gerenciar manualmente arquivos estáticos em suas aplicações web? Esqueça a codificação de caminhos, a invalidação de cache ou a preocupação com o versionamento de arquivos. Nette Assets transforma a maneira como você trabalha com imagens, folhas de estilo, scripts e outros recursos estáticos. + +- **Versionamento inteligente** garante que os navegadores sempre carreguem os arquivos mais recentes +- **Detecção automática** de tipos e dimensões de arquivos +- **Integração perfeita com Latte** com tags intuitivas +- **Arquitetura flexível** suportando sistemas de arquivos, CDNs e Vite +- **Carregamento preguiçoso** para desempenho ideal + +
    + + +Por que Nette Assets? +===================== + +Trabalhar com arquivos estáticos geralmente significa código repetitivo e propenso a erros. Você constrói URLs manualmente, adiciona parâmetros de versão para cache busting e lida com diferentes tipos de arquivos de forma diferente. Isso leva a um código como: + +```html +Logo + +``` + +Com Nette Assets, toda essa complexidade desaparece: + +```latte +{* Tudo automatizado - URL, versionamento, dimensões *} + + + +{* Ou simplesmente *} +{asset 'css/style.css'} +``` + +É isso! A biblioteca automaticamente: +- Adiciona parâmetros de versão com base na hora de modificação do arquivo +- Detecta dimensões da imagem e as inclui no HTML +- Gera o elemento HTML correto para cada tipo de arquivo +- Lida com ambientes de desenvolvimento e produção + + +Instalação +========== + +Instale Nette Assets usando [Composer|best-practices:composer]: + +```shell +composer require nette/assets +``` + +Requer PHP 8.1 ou superior e funciona perfeitamente com Nette Framework, mas também pode ser usado de forma autônoma. + + +Primeiros Passos +================ + +Nette Assets funciona de imediato com configuração zero. Coloque seus arquivos estáticos no diretório `www/assets/` e comece a usá-los: + +```latte +{* Exibe uma imagem com dimensões automáticas *} +{asset 'logo.png'} + +{* Inclui uma folha de estilo com versionamento *} +{asset 'style.css'} + +{* Carrega um módulo JavaScript *} +{asset 'app.js'} +``` + +Para mais controle sobre o HTML gerado, use o atributo `n:asset` ou a função `asset()`. + + +Como Funciona +============= + +Nette Assets é construído em torno de três conceitos centrais que o tornam poderoso e simples de usar: + + +Assets - Seus Arquivos Inteligentes +----------------------------------- + +Um **asset** representa qualquer arquivo estático em sua aplicação. Cada arquivo se torna um objeto com propriedades somente leitura úteis: + +```php +$image = $assets->getAsset('photo.jpg'); +echo $image->url; // '/assets/photo.jpg?v=1699123456' +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' +``` + +Diferentes tipos de arquivo fornecem diferentes propriedades: +- **Imagens**: largura, altura, texto alternativo, carregamento preguiçoso +- **Scripts**: tipo de módulo, hashes de integridade, crossorigin +- **Folhas de estilo**: media queries, integridade +- **Áudio/Vídeo**: duração, dimensões +- **Fontes**: pré-carregamento adequado com CORS + +A biblioteca detecta automaticamente os tipos de arquivo e cria a classe de asset apropriada. + + +Mappers - De Onde Vêm os Arquivos +--------------------------------- + +Um **mapper** sabe como encontrar arquivos e criar URLs para eles. Você pode ter vários mappers para diferentes propósitos - arquivos locais, CDN, armazenamento em nuvem ou ferramentas de construção (cada um deles tem um nome). O `FilesystemMapper` integrado lida com arquivos locais, enquanto o `ViteMapper` se integra com ferramentas de construção modernas. + +Mappers são definidos na [configuração|Configuration]. + + +Registry - Sua Interface Principal +---------------------------------- + +O **registry** gerencia todos os mappers e fornece a API principal: + +```php +// Injeta o registry em seu serviço +public function __construct( + private Nette\Assets\Registry $assets +) {} + +// Obtém assets de diferentes mappers +$logo = $this->assets->getAsset('images:logo.png'); // mapper 'image' +$app = $this->assets->getAsset('app:main.js'); // mapper 'app' +$style = $this->assets->getAsset('style.css'); // usa o mapper padrão +``` + +O registry seleciona automaticamente o mapper correto e armazena os resultados em cache para desempenho. + + +Trabalhando com Assets em PHP +============================= + +O Registry fornece dois métodos para recuperar assets: + +```php +// Lança Nette\Assets\AssetNotFoundException se o arquivo não existir +$logo = $assets->getAsset('logo.png'); + +// Retorna null se o arquivo não existir +$banner = $assets->tryGetAsset('banner.jpg'); +if ($banner) { + echo $banner->url; +} +``` + + +Especificando Mappers +--------------------- + +Você pode escolher explicitamente qual mapper usar: + +```php +// Usa o mapper padrão +$file = $assets->getAsset('document.pdf'); + +// Usa um mapper específico com prefixo +$image = $assets->getAsset('images:photo.jpg'); + +// Usa um mapper específico com sintaxe de array +$script = $assets->getAsset(['scripts', 'app.js']); +``` + + +Propriedades e Tipos de Asset +----------------------------- + +Cada tipo de asset fornece propriedades somente leitura relevantes: + +```php +// Propriedades da imagem +$image = $assets->getAsset('photo.jpg'); +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' + +// Propriedades do script +$script = $assets->getAsset('app.js'); +echo $script->type; // 'module' ou null + +// Propriedades de áudio +$audio = $assets->getAsset('song.mp3'); +echo $audio->duration; // duração em segundos + +// Todos os assets podem ser convertidos para string (retorna URL) +$url = (string) $assets->getAsset('document.pdf'); +``` + +.[note] +Propriedades como dimensões ou duração são carregadas preguiçosamente apenas quando acessadas, mantendo a biblioteca rápida. + + +Usando Assets em Templates Latte +================================ + +Nette Assets fornece integração intuitiva com [Latte|latte:] com tags e funções. + + +`{asset}` +--------- + +A tag `{asset}` renderiza elementos HTML completos: + +```latte +{* Renderiza: *} +{asset 'hero.jpg'} + +{* Renderiza: *} +{asset 'app.js'} + +{* Renderiza: *} +{asset 'style.css'} +``` + +A tag automaticamente: +- Detecta o tipo de asset e gera o HTML apropriado +- Inclui versionamento para cache busting +- Adiciona dimensões para imagens +- Define atributos corretos (tipo, mídia, etc.) + +Quando usado dentro de atributos HTML, ele gera apenas a URL: + +```latte +
    + +``` + + +`n:asset` +--------- + +Para controle total sobre os atributos HTML: + +```latte +{* O atributo n:asset preenche src, dimensões, etc. *} +Product + +{* Funciona com qualquer elemento relevante *} + + + +``` + +Use variáveis e mappers: + +```latte +{* Variáveis funcionam naturalmente *} + + +{* Especifique o mapper com chaves *} + + +{* Especifique o mapper com notação de array *} + +``` + + +`asset()` +--------- + +Para máxima flexibilidade, use a função `asset()`: + +```latte +{var $logo = asset('logo.png')} +width} height={$logo->height}> + +{* Ou diretamente *} +Logo +``` + + +Assets Opcionais +---------------- + +Lide com assets ausentes graciosamente com `{asset?}`, `n:asset?` e `tryAsset()`: + +```latte +{* Tag opcional - não renderiza nada se o asset estiver ausente *} +{asset? 'optional-banner.jpg'} + +{* Atributo opcional - ignora se o asset estiver ausente *} +Avatar + +{* Com fallback *} +{var $avatar = tryAsset('user-avatar.jpg') ?? asset('default-avatar.jpg')} +Avatar +``` + + +`{preload}` +----------- + +Melhore o desempenho de carregamento da página: + +```latte +{* Na sua seção *} +{preload 'critical.css'} +{preload 'important-font.woff2'} +{preload 'hero-image.jpg'} +``` + +Gera links de pré-carregamento apropriados: + +```html + + + +``` + + +Recursos Avançados +================== + + +Detecção Automática de Extensão +------------------------------- + +Lida com múltiplos formatos automaticamente: + +```neon +assets: + mapping: + images: + path: img + extension: [webp, jpg, png] # Tenta nesta ordem +``` + +Agora você pode requisitar sem extensão: + +```latte +{* Encontra logo.webp, logo.jpg ou logo.png automaticamente *} +{asset 'images:logo'} +``` + +Perfeito para aprimoramento progressivo com formatos modernos. + + +Versionamento Inteligente +------------------------- + +Os arquivos são automaticamente versionados com base na hora de modificação: + +```latte +{asset 'style.css'} +{* Saída: *} +``` + +Quando você atualiza o arquivo, o timestamp muda, forçando a atualização do cache do navegador. + +Controle o versionamento por asset: + +```php +// Desativa o versionamento para um asset específico +$asset = $assets->getAsset('style.css', ['version' => false]); + +// No Latte +{asset 'style.css', version: false} +``` + + +Assets de Fonte +--------------- + +As fontes recebem tratamento especial com CORS adequado: + +```latte +{* Pré-carregamento adequado com crossorigin *} +{preload 'fonts:OpenSans-Regular.woff2'} + +{* Uso em CSS *} + +``` + + +Mappers Personalizados +====================== + +Crie mappers personalizados para necessidades especiais como armazenamento em nuvem ou geração dinâmica: + +```php +use Nette\Assets\Mapper; +use Nette\Assets\Asset; +use Nette\Assets\Helpers; + +class CloudStorageMapper implements Mapper +{ + public function __construct( + private CloudClient $client, + private string $bucket, + ) {} + + public function getAsset(string $reference, array $options = []): Asset + { + if (!$this->client->exists($this->bucket, $reference)) { + throw new Nette\Assets\AssetNotFoundException("Asset '$reference' not found"); + } + + $url = $this->client->getPublicUrl($this->bucket, $reference); + return Helpers::createAssetFromUrl($url); + } +} +``` + +Registre na configuração: + +```neon +assets: + mapping: + cloud: CloudStorageMapper(@cloudClient, 'my-bucket') +``` + +Use como qualquer outro mapper: + +```latte +{asset 'cloud:user-uploads/photo.jpg'} +``` + +O método `Helpers::createAssetFromUrl()` cria automaticamente o tipo de asset correto com base na extensão do arquivo. + + +Leitura adicional +================= + +- [Nette Assets: Finalmente uma API unificada para tudo, desde imagens até o Vite |https://blog.nette.org/en/introducing-nette-assets] diff --git a/assets/pt/@left-menu.texy b/assets/pt/@left-menu.texy new file mode 100644 index 0000000000..67ea06e0d2 --- /dev/null +++ b/assets/pt/@left-menu.texy @@ -0,0 +1,5 @@ +Nette Assets +************ +- [Primeiros Passos |@home] +- [Vite |vite] +- [Configuração |Configuration] diff --git a/assets/pt/@meta.texy b/assets/pt/@meta.texy new file mode 100644 index 0000000000..41a853b6aa --- /dev/null +++ b/assets/pt/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentação Nette}} diff --git a/assets/pt/configuration.texy b/assets/pt/configuration.texy new file mode 100644 index 0000000000..7bca5174a1 --- /dev/null +++ b/assets/pt/configuration.texy @@ -0,0 +1,188 @@ +Configuração de Assets +********************** + +.[perex] +Visão geral das opções de configuração para Nette Assets. + + +```neon +assets: + # caminho base para resolver caminhos de mapper relativos + basePath: ... # (string) padrão para %wwwDir% + + # URL base para resolver URLs de mapper relativas + baseUrl: ... # (string) padrão para %baseUrl% + + # habilitar versionamento de asset globalmente? + versioning: ... # (bool) padrão para true + + # define os mappers de asset + mapping: ... # (array) padrão para o caminho 'assets' +``` + +O `basePath` define o diretório padrão do sistema de arquivos para resolver caminhos relativos em mappers. Por padrão, ele usa o diretório web (`%wwwDir%`). + +A `baseUrl` define o prefixo de URL padrão para resolver URLs relativas em mappers. Por padrão, ele usa a URL raiz (`%baseUrl%`). + +A opção `versioning` controla globalmente se os parâmetros de versão são adicionados às URLs dos assets para cache busting. Mappers individuais podem substituir essa configuração. + + +Mappers +------- + +Mappers podem ser configurados de três maneiras: notação de string simples, notação de array detalhada ou como uma referência a um serviço. + +A maneira mais simples de definir um mapper: + +```neon +assets: + mapping: + default: assets # Cria um mapper de sistema de arquivos para %wwwDir%/assets/ + images: img # Cria um mapper de sistema de arquivos para %wwwDir%/img/ + scripts: js # Cria um mapper de sistema de arquivos para %wwwDir%/js/ +``` + +Cada mapper cria um `FilesystemMapper` que: +- Procura arquivos em `%wwwDir%/` +- Gera URLs como `%baseUrl%/` +- Herda a configuração de versionamento global + + +Para mais controle, use a notação detalhada: + +```neon +assets: + mapping: + images: + # diretório onde os arquivos são armazenados + path: ... # (string) opcional, padrão para '' + + # prefixo de URL para links gerados + url: ... # (string) opcional, padrão para path + + # habilitar versionamento para este mapper? + versioning: ... # (bool) opcional, herda a configuração global + + # adicionar automaticamente extensão(ões) ao procurar arquivos + extension: ... # (string|array) opcional, padrão para null +``` + +Entendendo como os valores de configuração são resolvidos: + +Resolução de Caminho: + - Caminhos relativos são resolvidos a partir de `basePath` (ou `%wwwDir%` se `basePath` não estiver definido) + - Caminhos absolutos são usados como estão + +Resolução de URL: + - URLs relativas são resolvidas a partir de `baseUrl` (ou `%baseUrl%` se `baseUrl` não estiver definido) + - URLs absolutas (com esquema ou `//`) são usadas como estão + - Se `url` não for especificado, ele usa o valor de `path` + + +```neon +assets: + basePath: /var/www/project/www + baseUrl: https://example.com/assets + + mapping: + # Caminho e URL relativos + images: + path: img # Resolvido para: /var/www/project/www/img + url: images # Resolvido para: https://example.com/assets/images + + # Caminho e URL absolutos + uploads: + path: /var/shared/uploads # Usado como está: /var/shared/uploads + url: https://cdn.example.com # Usado como está: https://cdn.example.com + + # Apenas o caminho especificado + styles: + path: css # Caminho: /var/www/project/www/css + # URL: https://example.com/assets/css +``` + + +Mappers Personalizados +---------------------- + +Para mappers personalizados, faça referência ou defina um serviço: + +```neon +services: + s3mapper: App\Assets\S3Mapper(%s3.bucket%) + +assets: + mapping: + cloud: @s3mapper + database: App\Assets\DatabaseMapper(@database.connection) +``` + + +Vite Mapper +----------- + +O mapper Vite exige apenas que você adicione `type: vite`. Esta é uma lista completa de opções de configuração: + +```neon +assets: + mapping: + default: + # tipo de mapper (obrigatório para Vite) + type: vite # (string) obrigatório, deve ser 'vite' + + # diretório de saída de construção do Vite + path: ... # (string) opcional, padrão para '' + + # prefixo de URL para assets construídos + url: ... # (string) opcional, padrão para path + + # localização do arquivo de manifesto do Vite + manifest: ... # (string) opcional, padrão para /.vite/manifest.json + + # configuração do servidor de desenvolvimento do Vite + devServer: ... # (bool|string) opcional, padrão para true + + # versionamento para arquivos do diretório público + versioning: ... # (bool) opcional, herda a configuração global + + # auto-extensão para arquivos do diretório público + extension: ... # (string|array) opcional, padrão para null +``` + +A opção `devServer` controla como os assets são carregados durante o desenvolvimento: + +- `true` (padrão) - Detecta automaticamente o servidor de desenvolvimento Vite no host e porta atuais. Se o servidor de desenvolvimento estiver em execução **e sua aplicação estiver em modo de depuração**, os assets são carregados dele com suporte a hot module replacement. Se o servidor de desenvolvimento não estiver em execução, os assets são carregados dos arquivos construídos no diretório público. +- `false` - Desativa completamente a integração do servidor de desenvolvimento. Os assets são sempre carregados dos arquivos construídos. +- URL personalizada (por exemplo, `https://localhost:5173`) - Especifique manualmente a URL do servidor de desenvolvimento, incluindo protocolo e porta. Útil quando o servidor de desenvolvimento é executado em um host ou porta diferente. + +As opções `versioning` e `extension` aplicam-se apenas a arquivos no diretório público do Vite que não são processados pelo Vite. + + +Configuração Manual +------------------- + +Quando não estiver usando Nette DI, configure os mappers manualmente: + +```php +use Nette\Assets\Registry; +use Nette\Assets\FilesystemMapper; +use Nette\Assets\ViteMapper; + +$registry = new Registry; + +// Adiciona o mapper de sistema de arquivos +$registry->addMapper('images', new FilesystemMapper( + baseUrl: 'https://example.com/img', + basePath: __DIR__ . '/www/img', + extensions: ['webp', 'jpg', 'png'], + versioning: true, +)); + +// Adiciona o mapper Vite +$registry->addMapper('app', new ViteMapper( + baseUrl: '/build', + basePath: __DIR__ . '/www/build', + manifestPath: __DIR__ . '/www/build/.vite/manifest.json', + devServer: 'https://localhost:5173', +)); +``` diff --git a/assets/pt/vite.texy b/assets/pt/vite.texy new file mode 100644 index 0000000000..542aae02f8 --- /dev/null +++ b/assets/pt/vite.texy @@ -0,0 +1,508 @@ +Integração com Vite +******************* + +
    + +Aplicações JavaScript modernas exigem ferramentas de construção sofisticadas. Nette Assets oferece integração de primeira classe com [Vite |https://vitejs.dev/], a ferramenta de construção frontend de próxima geração. Obtenha desenvolvimento ultrarrápido com Hot Module Replacement (HMR) e construções de produção otimizadas com zero complicações de configuração. + +- **Zero configuração** - ponte automática entre Vite e templates PHP +- **Gerenciamento completo de dependências** - uma tag lida com todos os assets +- **Hot Module Replacement** - atualizações instantâneas de JavaScript e CSS +- **Construções de produção otimizadas** - code splitting e tree shaking + +
    + + +Nette Assets se integra perfeitamente com Vite, para que você obtenha todos esses benefícios enquanto escreve seus templates como de costume. + + +Configurando o Vite +=================== + +Vamos configurar o Vite passo a passo. Não se preocupe se você é novo em ferramentas de construção - vamos explicar tudo! + + +Passo 1: Instalar o Vite +------------------------ + +Primeiro, instale o Vite e o plugin Nette em seu projeto: + +```shell +npm install -D vite @nette/vite-plugin +``` + +Isso instala o Vite e um plugin especial que ajuda o Vite a funcionar perfeitamente com o Nette. + + +Passo 2: Estrutura do Projeto +----------------------------- + +A abordagem padrão é colocar os arquivos de asset de origem em uma pasta `assets/` na raiz do seu projeto, e as versões compiladas em `www/assets/`: + +/--pre +web-project/ +├── assets/ ← arquivos de origem (SCSS, TypeScript, imagens de origem) +│ ├── public/ ← arquivos estáticos (copiados como estão) +│ │ └── favicon.ico +│ ├── images/ +│ │ └── logo.png +│ ├── app.js ← ponto de entrada principal +│ └── style.css ← seus estilos +└── www/ ← diretório público (document root) + ├── assets/ ← arquivos compilados irão para cá + └── index.php +\-- + +A pasta `assets/` contém seus arquivos de origem - o código que você escreve. O Vite processará esses arquivos e colocará as versões compiladas em `www/assets/`. + + +Passo 3: Configurar o Vite +-------------------------- + +Crie um arquivo `vite.config.ts` na raiz do seu projeto. Este arquivo informa ao Vite onde encontrar seus arquivos de origem e onde colocar os compilados. + +O plugin Nette Vite vem com padrões inteligentes que simplificam a configuração. Ele assume que seus arquivos de origem frontend estão no diretório `assets/` (opção `root`) e os arquivos compilados vão para `www/assets/` (opção `outDir`). Você só precisa especificar o [ponto de entrada|#Entry Points]: + +```js +import { defineConfig } from 'vite'; +import nette from '@nette/vite-plugin'; + +export default defineConfig({ + plugins: [ + nette({ + entry: 'app.js', + }), + ], +}); +``` + +Se você quiser especificar outro nome de diretório para construir seus assets, precisará alterar algumas opções: + +```js +export default defineConfig({ + root: 'assets', // diretório raiz dos assets de origem + + build: { + outDir: '../www/assets', // onde os arquivos compilados vão + }, + + // ... outras configurações ... +}); +``` + +.[note] +O caminho `outDir` é considerado relativo a `root`, por isso há `../` no início. + + +Passo 4: Configurar o Nette +--------------------------- + +Informe ao Nette Assets sobre o Vite em seu `common.neon`: + +```neon +assets: + mapping: + default: + type: vite # informa ao Nette para usar o ViteMapper + path: assets +``` + + +Passo 5: Adicionar scripts +-------------------------- + +Adicione estes scripts ao seu `package.json`: + +```json +{ + "scripts": { + "dev": "vite", + "build": "vite build" + } +} +``` + +Agora você pode: +- `npm run dev` - iniciar o servidor de desenvolvimento com hot reloading +- `npm run build` - criar arquivos de produção otimizados + + +Pontos de Entrada +================= + +Um **ponto de entrada** é o arquivo principal onde sua aplicação começa. A partir deste arquivo, você importa outros arquivos (CSS, módulos JavaScript, imagens), criando uma árvore de dependências. O Vite segue essas importações e agrupa tudo. + +Exemplo de ponto de entrada `assets/app.js`: + +```js +// Importa estilos +import './style.css' + +// Importa módulos JavaScript +import netteForms from 'nette-forms'; +import naja from 'naja'; + +// Inicializa sua aplicação +netteForms.initOnLoad(); +naja.initialize(); +``` + +No template você pode inserir um ponto de entrada da seguinte forma: + +```latte +{asset 'app.js'} +``` + +Nette Assets gera automaticamente todas as tags HTML necessárias - JavaScript, CSS e quaisquer outras dependências. + + +Múltiplos Pontos de Entrada +--------------------------- + +Aplicações maiores geralmente precisam de pontos de entrada separados: + +```js +export default defineConfig({ + plugins: [ + nette({ + entry: [ + 'app.js', // páginas públicas + 'admin.js', // painel de administração + ], + }), + ], +}); +``` + +Use-os em diferentes templates: + +```latte +{* Em páginas públicas *} +{asset 'app.js'} + +{* No painel de administração *} +{asset 'admin.js'} +``` + + +Importante: Arquivos de Origem vs. Compilados +--------------------------------------------- + +É crucial entender que em produção você só pode carregar: + +1. **Pontos de entrada** definidos em `entry` +2. **Arquivos do diretório `assets/public/`** + +Você **não pode** carregar usando `{asset}` arquivos arbitrários de `assets/` - apenas assets referenciados por arquivos JavaScript ou CSS. Se seu arquivo não for referenciado em nenhum lugar, ele não será compilado. Se você quiser que o Vite esteja ciente de outros assets, você pode movê-los para a [pasta pública|#Public Folder]. + +Observe que, por padrão, o Vite incorporará todos os assets menores que 4KB, então você não poderá referenciar esses arquivos diretamente. (Consulte a [documentação do Vite |https://vite.dev/guide/assets.html]). + +```latte +{* ✓ Isso funciona - é um ponto de entrada *} +{asset 'app.js'} + +{* ✓ Isso funciona - está em assets/public/ *} +{asset 'favicon.ico'} + +{* ✗ Isso não funcionará - arquivo aleatório em assets/ *} +{asset 'components/button.js'} +``` + + +Modo de Desenvolvimento +======================= + +O modo de desenvolvimento é completamente opcional, mas oferece benefícios significativos quando ativado. A principal vantagem é o **Hot Module Replacement (HMR)** - veja as mudanças instantaneamente sem perder o estado da aplicação, tornando a experiência de desenvolvimento muito mais suave e rápida. + +Vite é uma ferramenta de construção moderna que torna o desenvolvimento incrivelmente rápido. Ao contrário dos bundlers tradicionais, o Vite serve seu código diretamente para o navegador durante o desenvolvimento, o que significa um início instantâneo do servidor, não importa o tamanho do seu projeto, e atualizações ultrarrápidas. + + +Iniciando o Servidor de Desenvolvimento +--------------------------------------- + +Execute o servidor de desenvolvimento: + +```shell +npm run dev +``` + +Você verá: + +``` + ➜ Local: http://localhost:5173/ + ➜ Network: use --host to expose +``` + +Mantenha este terminal aberto durante o desenvolvimento. + +O plugin Nette Vite detecta automaticamente quando: +1. O servidor de desenvolvimento Vite está em execução +2. Sua aplicação Nette está em modo de depuração + +Quando ambas as condições são atendidas, o Nette Assets carrega os arquivos do servidor de desenvolvimento Vite em vez do diretório compilado: + +```latte +{asset 'app.js'} +{* Em desenvolvimento: *} +{* Em produção: *} +``` + +Nenhuma configuração necessária - simplesmente funciona! + + +Trabalhando em Diferentes Domínios +---------------------------------- + +Se o seu servidor de desenvolvimento estiver sendo executado em algo diferente de `localhost` (como `myapp.local`), você pode encontrar problemas de CORS (Cross-Origin Resource Sharing). CORS é um recurso de segurança em navegadores da web que bloqueia solicitações entre diferentes domínios por padrão. Quando sua aplicação PHP é executada em `myapp.local`, mas o Vite é executado em `localhost:5173`, o navegador os vê como domínios diferentes e bloqueia as solicitações. + +Você tem duas opções para resolver isso: + +**Opção 1: Configurar CORS** + +A solução mais simples é permitir solicitações cross-origin de sua aplicação PHP: + +```js +export default defineConfig({ + // ... outras configurações ... + + server: { + cors: { + origin: 'http://myapp.local', // URL da sua aplicação PHP + }, + }, +}); +``` +**Opção 2: Executar o Vite em seu domínio** + +A outra solução é fazer com que o Vite seja executado no mesmo domínio da sua aplicação PHP. + +```js +export default defineConfig({ + // ... outras configurações ... + + server: { + host: 'myapp.local', // o mesmo da sua aplicação PHP + }, +}); +``` + +Na verdade, mesmo neste caso, você precisa configurar o CORS porque o servidor de desenvolvimento é executado no mesmo hostname, mas em uma porta diferente. No entanto, neste caso, o CORS é configurado automaticamente pelo plugin Nette Vite. + + +Desenvolvimento HTTPS +--------------------- + +Se você desenvolve em HTTPS, precisa de certificados para o seu servidor de desenvolvimento Vite. A maneira mais fácil é usar um plugin que gera certificados automaticamente: + +```shell +npm install -D vite-plugin-mkcert +``` + +Veja como configurá-lo em `vite.config.ts`: + +```js +import mkcert from 'vite-plugin-mkcert'; + +export default defineConfig({ + // ... outras configurações ... + + plugins: [ + mkcert(), // gera certificados automaticamente e habilita https + nette(), + ], +}); +``` + +Observe que, se você estiver usando a configuração CORS (Opção 1 acima), precisará atualizar a URL de origem para usar `https://` em vez de `http://`. + + +Construções de Produção +======================= + +Crie arquivos de produção otimizados: + +```shell +npm run build +``` + +O Vite irá: +- Minificar todo o JavaScript e CSS +- Dividir o código em chunks ideais +- Gerar nomes de arquivo com hash para cache-busting +- Criar um arquivo de manifesto para Nette Assets + +Exemplo de saída: + +``` +www/assets/ +├── app-4f3a2b1c.js # Seu JavaScript principal (minificado) +├── app-7d8e9f2a.css # CSS extraído (minificado) +├── vendor-8c4b5e6d.js # Dependências compartilhadas +└── .vite/ + └── manifest.json # Mapeamento para Nette Assets +``` + +Os nomes de arquivo com hash garantem que os navegadores sempre carreguem a versão mais recente. + + +Pasta Pública +============= + +Os arquivos no diretório `assets/public/` são copiados para a saída sem processamento: + +``` +assets/ +├── public/ +│ ├── favicon.ico +│ ├── robots.txt +│ └── images/ +│ └── og-image.jpg +├── app.js +└── style.css +``` + +Referencie-os normalmente: + +```latte +{* Estes arquivos são copiados como estão *} + + +``` + +Para arquivos públicos, você pode usar os recursos do FilesystemMapper: + +```neon +assets: + mapping: + default: + type: vite + path: assets + extension: [webp, jpg, png] # Tenta WebP primeiro + versioning: true # Adiciona cache-busting +``` + +Na configuração `vite.config.ts` você pode alterar a pasta pública usando a opção `publicDir`. + + +Importações Dinâmicas +===================== + +O Vite divide automaticamente o código para carregamento ideal. As importações dinâmicas permitem que você carregue o código apenas quando ele é realmente necessário, reduzindo o tamanho inicial do bundle: + +```js +// Carrega componentes pesados sob demanda +button.addEventListener('click', async () => { + let { Chart } = await import('./components/chart.js') + new Chart(data) +}) +``` + +As importações dinâmicas criam chunks separados que são carregados apenas quando necessário. Isso é chamado de "code splitting" e é um dos recursos mais poderosos do Vite. Quando você usa importações dinâmicas, o Vite cria automaticamente arquivos JavaScript separados para cada módulo importado dinamicamente. + +A tag `{asset 'app.js'}` **não** pré-carrega automaticamente esses chunks dinâmicos. Este é um comportamento intencional - não queremos baixar código que talvez nunca seja usado. Os chunks são baixados apenas quando a importação dinâmica é executada. + +No entanto, se você souber que certas importações dinâmicas são críticas e serão necessárias em breve, você pode pré-carregá-las: + +```latte +{* Ponto de entrada principal *} +{asset 'app.js'} + +{* Pré-carrega importações dinâmicas críticas *} +{preload 'components/chart.js'} +``` + +Isso informa ao navegador para baixar o componente do gráfico em segundo plano, para que esteja pronto imediatamente quando necessário. + + +Suporte a TypeScript +==================== + +TypeScript funciona de imediato: + +```ts +// assets/main.ts +interface User { + name: string + email: string +} + +export function greetUser(user: User): void { + console.log(`Hello, ${user.name}!`) +} +``` + +Referencie arquivos TypeScript normalmente: + +```latte +{asset 'main.ts'} +``` + +Para suporte completo a TypeScript, instale-o: + +```shell +npm install -D typescript +``` + + +Configuração Adicional do Vite +============================== + +Aqui estão algumas opções úteis de configuração do Vite com explicações detalhadas: + +```js +export default defineConfig({ + // Diretório raiz contendo os assets de origem + root: 'assets', + + // Pasta cujo conteúdo é copido para o diretório de saída como está + // Padrão: 'public' (relativo a 'root') + publicDir: 'public', + + build: { + // Onde colocar os arquivos compilados (relativo a 'root') + outDir: '../www/assets', + + // Esvaziar o diretório de saída antes de construir? + // Útil para remover arquivos antigos de construções anteriores + emptyOutDir: true, + + // Subdiretório dentro de outDir para chunks e assets gerados + // Isso ajuda a organizar a estrutura de saída + assetsDir: 'static', + + rollupOptions: { + // Ponto(s) de entrada - pode ser um único arquivo ou array de arquivos + // Cada ponto de entrada se torna um bundle separado + input: [ + 'app.js', // aplicação principal + 'admin.js', // painel de administração + ], + }, + }, + + server: { + // Host para o qual o servidor de desenvolvimento deve se vincular + // Use '0.0.0.0' para expor à rede + host: 'localhost', + + // Porta para o servidor de desenvolvimento + port: 5173, + + // Configuração CORS para solicitações cross-origin + cors: { + origin: 'http://myapp.local', + }, + }, + + css: { + // Habilitar sourcemaps CSS em desenvolvimento + devSourcemap: true, + }, + + plugins: [ + nette(), + ], +}); +``` + +É isso! Agora você tem um sistema de construção moderno integrado com Nette Assets. diff --git a/assets/ro/@home.texy b/assets/ro/@home.texy new file mode 100644 index 0000000000..aaa637a694 --- /dev/null +++ b/assets/ro/@home.texy @@ -0,0 +1,432 @@ +Nette Assets +************ + +
    + +V-ați săturat să gestionați manual fișierele statice în aplicațiile dumneavoastră web? Uitați de codificarea manuală a căilor, de gestionarea invalidării cache-ului sau de îngrijorarea legată de versionarea fișierelor. Nette Assets transformă modul în care lucrați cu imagini, foi de stil, scripturi și alte resurse statice. + +- **Versionare inteligentă** asigură că browserele încarcă întotdeauna cele mai recente fișiere +- **Detecție automată** a tipurilor și dimensiunilor fișierelor +- **Integrare perfectă cu Latte** cu tag-uri intuitive +- **Arhitectură flexibilă** care suportă sisteme de fișiere, CDN-uri și Vite +- **Încărcare leneșă** pentru performanță optimă + +
    + + +De ce Nette Assets? +=================== + +Lucrul cu fișiere statice înseamnă adesea cod repetitiv, predispus la erori. Construiți manual URL-uri, adăugați parametri de versiune pentru invalidarea cache-ului și gestionați diferit tipurile de fișiere. Acest lucru duce la cod de genul: + +```html +Logo + +``` + +Cu Nette Assets, toată această complexitate dispare: + +```latte +{* Totul automatizat - URL, versionare, dimensiuni *} + + + +{* Sau pur și simplu *} +{asset 'css/style.css'} +``` + +Asta e tot! Biblioteca automat: +- Adaugă parametri de versiune bazat pe timpul de modificare al fișierului +- Detectează dimensiunile imaginii și le include în HTML +- Generează elementul HTML corect pentru fiecare tip de fișier +- Gestionează atât mediile de dezvoltare, cât și cele de producție + + +Instalare +========= + +Instalați Nette Assets folosind [Composer|best-practices:composer]: + +```shell +composer require nette/assets +``` + +Necesită PHP 8.1 sau o versiune superioară și funcționează perfect cu Nette Framework, dar poate fi folosit și independent. + + +Primii Pași +=========== + +Nette Assets funcționează imediat, fără configurare. Plasați fișierele statice în directorul `www/assets/` și începeți să le utilizați: + +```latte +{* Afișează o imagine cu dimensiuni automate *} +{asset 'logo.png'} + +{* Include o foaie de stil cu versionare *} +{asset 'style.css'} + +{* Încarcă un modul JavaScript *} +{asset 'app.js'} +``` + +Pentru mai mult control asupra HTML-ului generat, utilizați atributul `n:asset` sau funcția `asset()`. + + +Cum Funcționează +================ + +Nette Assets este construit în jurul a trei concepte cheie care îl fac puternic, dar simplu de utilizat: + + +Asset-uri - Fișierele Dumneavoastră Făcute Inteligente +------------------------------------------------------ + +Un **asset** reprezintă orice fișier static din aplicația dumneavoastră. Fiecare fișier devine un obiect cu proprietăți utile, doar pentru citire: + +```php +$image = $assets->getAsset('photo.jpg'); +echo $image->url; // '/assets/photo.jpg?v=1699123456' +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' +``` + +Diferite tipuri de fișiere oferă proprietăți diferite: +- **Imagini**: lățime, înălțime, text alternativ, încărcare leneșă +- **Scripturi**: tip modul, hash-uri de integritate, crossorigin +- **Foi de stil**: interogări media, integritate +- **Audio/Video**: durată, dimensiuni +- **Fonturi**: preîncărcare corectă cu CORS + +Biblioteca detectează automat tipurile de fișiere și creează clasa de asset corespunzătoare. + + +Mapperi - De unde provin fișierele +---------------------------------- + +Un **mapper** știe cum să găsească fișiere și să creeze URL-uri pentru ele. Puteți avea mai mulți mapperi pentru diferite scopuri - fișiere locale, CDN, stocare în cloud sau instrumente de construire (fiecare dintre ele are un nume). FilesystemMapper-ul încorporat gestionează fișierele locale, în timp ce ViteMapper se integrează cu instrumente moderne de construire. + +Mapperii sunt definiți în [Configurare |Configuration]. + + +Registrul - Interfața dumneavoastră principală +---------------------------------------------- + +**Registrul** gestionează toți mapperii și oferă API-ul principal: + +```php +// Injectați registrul în serviciul dumneavoastră +public function __construct( + private Nette\Assets\Registry $assets +) {} + +// Obțineți asset-uri de la diferiți mapperi +$logo = $this->assets->getAsset('images:logo.png'); // mapper 'image' +$app = $this->assets->getAsset('app:main.js'); // mapper 'app' +$style = $this->assets->getAsset('style.css'); // utilizează mapper-ul implicit +``` + +Registrul selectează automat mapper-ul potrivit și memorează în cache rezultatele pentru performanță. + + +Lucrul cu Asset-uri în PHP +========================== + +Registrul oferă două metode pentru recuperarea asset-urilor: + +```php +// Aruncă Nette\Assets\AssetNotFoundException dacă fișierul nu există +$logo = $assets->getAsset('logo.png'); + +// Returnează null dacă fișierul nu există +$banner = $assets->tryGetAsset('banner.jpg'); +if ($banner) { + echo $banner->url; +} +``` + + +Specificarea Mapper-ilor +------------------------ + +Puteți alege explicit ce mapper să utilizați: + +```php +// Utilizează mapper-ul implicit +$file = $assets->getAsset('document.pdf'); + +// Utilizează mapper-ul specific cu prefix +$image = $assets->getAsset('images:photo.jpg'); + +// Utilizează mapper-ul specific cu sintaxă de array +$script = $assets->getAsset(['scripts', 'app.js']); +``` + + +Proprietăți și Tipuri de Asset-uri +---------------------------------- + +Fiecare tip de asset oferă proprietăți relevante, doar pentru citire: + +```php +// Proprietăți imagine +$image = $assets->getAsset('photo.jpg'); +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' + +// Proprietăți script +$script = $assets->getAsset('app.js'); +echo $script->type; // 'module' or null + +// Proprietăți audio +$audio = $assets->getAsset('song.mp3'); +echo $audio->duration; // duration in seconds + +// Toate asset-urile pot fi convertite la șir (returnează URL) +$url = (string) $assets->getAsset('document.pdf'); +``` + +.[note] +Proprietățile precum dimensiunile sau durata sunt încărcate leneș, doar la accesare, menținând biblioteca rapidă. + + +Utilizarea Asset-urilor în Șabloanele Latte +=========================================== + +Nette Assets oferă o integrare intuitivă cu [Latte|latte:] prin tag-uri și funcții. + + +`{asset}` +--------- + +Tag-ul `{asset}` randează elemente HTML complete: + +```latte +{* Randează: *} +{asset 'hero.jpg'} + +{* Randează: *} +{asset 'app.js'} + +{* Randează: *} +{asset 'style.css'} +``` + +Tag-ul automat: +- Detectează tipul asset-ului și generează HTML-ul corespunzător +- Include versionare pentru invalidarea cache-ului +- Adaugă dimensiuni pentru imagini +- Setează atributele corecte (tip, media, etc.) + +Când este utilizat în interiorul atributelor HTML, acesta afișează doar URL-ul: + +```latte +
    + +``` + + +`n:asset` +--------- + +Pentru control complet asupra atributelor HTML: + +```latte +{* Atributul n:asset completează src, dimensiuni etc. *} +Product + +{* Funcționează cu orice element relevant *} + + + +``` + +Utilizați variabile și mapperi: + +```latte +{* Variabilele funcționează natural *} + + +{* Specificați mapper-ul cu acolade *} + + +{* Specificați mapper-ul cu notație de array *} + +``` + + +`asset()` +--------- + +Pentru flexibilitate maximă, utilizați funcția `asset()`: + +```latte +{var $logo = asset('logo.png')} +width} height={$logo->height}> + +{* Sau direct *} +Logo +``` + + +Asset-uri Opționale +------------------- + +Gestionați asset-urile lipsă în mod elegant cu `{asset?}`, `n:asset?` și `tryAsset()`: + +```latte +{* Tag opțional - nu randează nimic dacă asset-ul lipsește *} +{asset? 'optional-banner.jpg'} + +{* Atribut opțional - sare peste dacă asset-ul lipsește *} +Avatar + +{* Cu fallback *} +{var $avatar = tryAsset('user-avatar.jpg') ?? asset('default-avatar.jpg')} +Avatar +``` + + +`{preload}` +----------- + +Îmbunătățiți performanța de încărcare a paginii: + +```latte +{* În secțiunea *} +{preload 'critical.css'} +{preload 'important-font.woff2'} +{preload 'hero-image.jpg'} +``` + +Generează link-uri de preîncărcare adecvate: + +```html + + + +``` + + +Funcționalități Avansate +======================== + + +Auto-Detecția Extensiilor +------------------------- + +Gestionați automat mai multe formate: + +```neon +assets: + mapping: + images: + path: img + extension: [webp, jpg, png] # Încearcă în ordine +``` + +Acum puteți solicita fără extensie: + +```latte +{* Găsește automat logo.webp, logo.jpg sau logo.png *} +{asset 'images:logo'} +``` + +Perfect pentru îmbunătățirea progresivă cu formate moderne. + + +Versionare Inteligentă +---------------------- + +Fișierele sunt versionate automat pe baza timpului de modificare: + +```latte +{asset 'style.css'} +{* Ieșire: *} +``` + +Când actualizați fișierul, timestamp-ul se modifică, forțând reîmprospătarea cache-ului browserului. + +Controlați versionarea per asset: + +```php +// Dezactivează versionarea pentru un asset specific +$asset = $assets->getAsset('style.css', ['version' => false]); + +// În Latte +{asset 'style.css', version: false} +``` + + +Asset-uri Font +-------------- + +Fonturile beneficiază de un tratament special cu CORS adecvat: + +```latte +{* Preîncărcare corectă cu crossorigin *} +{preload 'fonts:OpenSans-Regular.woff2'} + +{* Utilizați în CSS *} + +``` + + +Mapperi Personalizați +===================== + +Creați mapperi personalizați pentru nevoi speciale, cum ar fi stocarea în cloud sau generarea dinamică: + +```php +use Nette\Assets\Mapper; +use Nette\Assets\Asset; +use Nette\Assets\Helpers; + +class CloudStorageMapper implements Mapper +{ + public function __construct( + private CloudClient $client, + private string $bucket, + ) {} + + public function getAsset(string $reference, array $options = []): Asset + { + if (!$this->client->exists($this->bucket, $reference)) { + throw new Nette\Assets\AssetNotFoundException("Asset '$reference' not found"); + } + + $url = $this->client->getPublicUrl($this->bucket, $reference); + return Helpers::createAssetFromUrl($url); + } +} +``` + +Înregistrați în configurare: + +```neon +assets: + mapping: + cloud: CloudStorageMapper(@cloudClient, 'my-bucket') +``` + +Utilizați ca orice alt mapper: + +```latte +{asset 'cloud:user-uploads/photo.jpg'} +``` + +Metoda `Helpers::createAssetFromUrl()` creează automat tipul corect de asset pe baza extensiei fișierului. + + +Lectură suplimentară +==================== + +- [Nette Assets: În sfârșit, API unificat pentru orice, de la imagini la Vite |https://blog.nette.org/en/introducing-nette-assets] diff --git a/assets/ro/@left-menu.texy b/assets/ro/@left-menu.texy new file mode 100644 index 0000000000..654f12eacf --- /dev/null +++ b/assets/ro/@left-menu.texy @@ -0,0 +1,5 @@ +Nette Assets +************ +- [Noțiuni de bază |@home] +- [Vite |vite] +- [Configurare |Configuration] diff --git a/assets/ro/@meta.texy b/assets/ro/@meta.texy new file mode 100644 index 0000000000..9c744b37d6 --- /dev/null +++ b/assets/ro/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentație Nette}} diff --git a/assets/ro/configuration.texy b/assets/ro/configuration.texy new file mode 100644 index 0000000000..75d20c374a --- /dev/null +++ b/assets/ro/configuration.texy @@ -0,0 +1,188 @@ +Configurarea Asset-urilor +************************* + +.[perex] +Prezentare generală a opțiunilor de configurare pentru Nette Assets. + + +```neon +assets: + # cale de bază pentru rezolvarea căilor relative ale mapper-ilor + basePath: ... # (șir de caractere) implicit %wwwDir% + + # URL de bază pentru rezolvarea URL-urilor relative ale mapper-ilor + baseUrl: ... # (șir de caractere) implicit %baseUrl% + + # activează versionarea asset-urilor global? + versioning: ... # (boolean) implicit true + + # definește mapper-ii de asset-uri + mapping: ... # (array) implicit cale 'assets' +``` + +`basePath` setează directorul implicit al sistemului de fișiere pentru rezolvarea căilor relative în mapperi. Implicit, utilizează directorul web (`%wwwDir%`). + +`baseUrl` setează prefixul URL implicit pentru rezolvarea URL-urilor relative în mapperi. Implicit, utilizează URL-ul rădăcină (`%baseUrl%`). + +Opțiunea `versioning` controlează global dacă parametrii de versiune sunt adăugați la URL-urile asset-urilor pentru invalidarea cache-ului. Mapperii individuali pot suprascrie această setare. + + +Mapperi +------- + +Mapperii pot fi configurați în trei moduri: notație simplă de șir, notație detaliată de array sau ca referință la un serviciu. + +Cel mai simplu mod de a defini un mapper: + +```neon +assets: + mapping: + default: assets # Creează un mapper de sistem de fișiere pentru %wwwDir%/assets/ + images: img # Creează un mapper de sistem de fișiere pentru %wwwDir%/img/ + scripts: js # Creează un mapper de sistem de fișiere pentru %wwwDir%/js/ +``` + +Fiecare mapper creează un `FilesystemMapper` care: +- Caută fișiere în `%wwwDir%/` +- Generează URL-uri precum `%baseUrl%/` +- Moștenește setarea globală de versionare + + +Pentru mai mult control, utilizați notația detaliată: + +```neon +assets: + mapping: + images: + # directorul unde sunt stocate fișierele + path: ... # (șir de caractere) opțional, implicit '' + + # prefix URL pentru link-urile generate + url: ... # (șir de caractere) opțional, implicit cale + + # activează versionarea pentru acest mapper? + versioning: ... # (boolean) opțional, moștenește setarea globală + + # adaugă automat extensie(i) la căutarea fișierelor + extension: ... # (șir de caractere|array) opțional, implicit null +``` + +Înțelegerea modului în care valorile de configurare sunt rezolvate: + +Rezolvarea Căii: + - Căile relative sunt rezolvate din `basePath` (sau `%wwwDir%` dacă `basePath` nu este setat) + - Căile absolute sunt utilizate ca atare + +Rezolvarea URL-ului: + - URL-urile relative sunt rezolvate din `baseUrl` (sau `%baseUrl%` dacă `baseUrl` nu este setat) + - URL-urile absolute (cu schemă sau `//`) sunt utilizate ca atare + - Dacă `url` nu este specificat, utilizează valoarea `path` + + +```neon +assets: + basePath: /var/www/project/www + baseUrl: https://example.com/assets + + mapping: + # Cale și URL relativ + images: + path: img # Rezolvat la: /var/www/project/www/img + url: images # Rezolvat la: https://example.com/assets/images + + # Cale și URL absolut + uploads: + path: /var/shared/uploads # Utilizat ca atare: /var/shared/uploads + url: https://cdn.example.com # Utilizat ca atare: https://cdn.example.com + + # Doar calea specificată + styles: + path: css # Cale: /var/www/project/www/css + # URL: https://example.com/assets/css +``` + + +Mapperi Personalizați +--------------------- + +Pentru mapperi personalizați, referențiați sau definiți un serviciu: + +```neon +services: + s3mapper: App\Assets\S3Mapper(%s3.bucket%) + +assets: + mapping: + cloud: @s3mapper + database: App\Assets\DatabaseMapper(@database.connection) +``` + + +Vite Mapper +----------- + +Mapper-ul Vite necesită doar adăugarea `type: vite`. Aceasta este o listă completă de opțiuni de configurare: + +```neon +assets: + mapping: + default: + # tip mapper (obligatoriu pentru Vite) + type: vite # (șir de caractere) obligatoriu, trebuie să fie 'vite' + + # directorul de ieșire al construirii Vite + path: ... # (șir de caractere) opțional, implicit '' + + # prefix URL pentru asset-urile construite + url: ... # (șir de caractere) opțional, implicit cale + + # locația fișierului manifest Vite + manifest: ... # (șir de caractere) opțional, implicit /.vite/manifest.json + + # configurare server de dezvoltare Vite + devServer: ... # (boolean|șir de caractere) opțional, implicit true + + # versionare pentru fișierele din directorul public + versioning: ... # (boolean) opțional, moștenește setarea globală + + # auto-extensie pentru fișierele din directorul public + extension: ... # (șir de caractere|array) opțional, implicit null +``` + +Opțiunea `devServer` controlează modul în care asset-urile sunt încărcate în timpul dezvoltării: + +- `true` (implicit) - Detectează automat serverul de dezvoltare Vite pe gazda și portul curente. Dacă serverul de dezvoltare rulează **și aplicația dumneavoastră este în modul de depanare**, asset-urile sunt încărcate de la acesta cu suport pentru înlocuirea la cald a modulelor (HMR). Dacă serverul de dezvoltare nu rulează, asset-urile sunt încărcate din fișierele construite din directorul public. +- `false` - Dezactivează complet integrarea serverului de dezvoltare. Asset-urile sunt întotdeauna încărcate din fișierele construite. +- URL personalizat (de ex., `https://localhost:5173`) - Specificați manual URL-ul serverului de dezvoltare, inclusiv protocolul și portul. Util atunci când serverul de dezvoltare rulează pe o altă gazdă sau port. + +Opțiunile `versioning` și `extension` se aplică doar fișierelor din directorul public al Vite care nu sunt procesate de Vite. + + +Configurare Manuală +------------------- + +Când nu utilizați Nette DI, configurați mapperii manual: + +```php +use Nette\Assets\Registry; +use Nette\Assets\FilesystemMapper; +use Nette\Assets\ViteMapper; + +$registry = new Registry; + +// Adaugă mapper de sistem de fișiere +$registry->addMapper('images', new FilesystemMapper( + baseUrl: 'https://example.com/img', + basePath: __DIR__ . '/www/img', + extensions: ['webp', 'jpg', 'png'], + versioning: true, +)); + +// Adaugă mapper Vite +$registry->addMapper('app', new ViteMapper( + baseUrl: '/build', + basePath: __DIR__ . '/www/build', + manifestPath: __DIR__ . '/www/build/.vite/manifest.json', + devServer: 'https://localhost:5173', +)); +``` diff --git a/assets/ro/vite.texy b/assets/ro/vite.texy new file mode 100644 index 0000000000..3415ffd29f --- /dev/null +++ b/assets/ro/vite.texy @@ -0,0 +1,508 @@ +Integrare Vite +************** + +
    + +Aplicațiile JavaScript moderne necesită instrumente de construire sofisticate. Nette Assets oferă o integrare de primă clasă cu [Vite |https://vitejs.dev/], instrumentul de construire frontend de ultimă generație. Obțineți o dezvoltare ultra-rapidă cu Hot Module Replacement (HMR) și build-uri de producție optimizate, fără bătăi de cap legate de configurare. + +- **Zero configurare** - punte automată între Vite și șabloanele PHP +- **Gestionare completă a dependențelor** - un singur tag gestionează toate asset-urile +- **Hot Module Replacement** - actualizări instantanee JavaScript și CSS +- **Build-uri de producție optimizate** - împărțirea codului și tree shaking + +
    + + +Nette Assets se integrează perfect cu Vite, astfel încât obțineți toate aceste beneficii în timp ce scrieți șabloanele ca de obicei. + + +Configurarea Vite +================= + +Să configurăm Vite pas cu pas. Nu vă faceți griji dacă sunteți nou în lumea instrumentelor de construire - vom explica totul! + + +Pasul 1: Instalarea Vite +------------------------ + +Mai întâi, instalați Vite și plugin-ul Nette în proiectul dumneavoastră: + +```shell +npm install -D vite @nette/vite-plugin +``` + +Aceasta instalează Vite și un plugin special care ajută Vite să funcționeze perfect cu Nette. + + +Pasul 2: Structura Proiectului +------------------------------ + +Abordarea standard este de a plasa fișierele asset sursă într-un folder `assets/` în rădăcina proiectului, iar versiunile compilate în `www/assets/`: + +/--pre +web-project/ +├── assets/ ← fișiere sursă (SCSS, TypeScript, imagini sursă) +│ ├── public/ ← fișiere statice (copiate ca atare) +│ │ └── favicon.ico +│ ├── images/ +│ │ └── logo.png +│ ├── app.js ← punct de intrare principal +│ └── style.css ← stilurile dumneavoastră +└── www/ ← director public (rădăcina documentului) + ├── assets/ ← fișierele compilate vor ajunge aici + └── index.php +\-- + +Folderul `assets/` conține fișierele dumneavoastră sursă - codul pe care îl scrieți. Vite va procesa aceste fișiere și va plasa versiunile compilate în `www/assets/`. + + +Pasul 3: Configurarea Vite +-------------------------- + +Creați un fișier `vite.config.ts` în rădăcina proiectului dumneavoastră. Acest fișier îi spune lui Vite unde să găsească fișierele sursă și unde să plaseze cele compilate. + +Plugin-ul Nette Vite vine cu setări implicite inteligente care simplifică configurarea. Presupune că fișierele dumneavoastră sursă de frontend se află în directorul `assets/` (opțiunea `root`) și că fișierele compilate ajung în `www/assets/` (opțiunea `outDir`). Trebuie doar să specificați [punctul de intrare|#Entry Points]: + +```js +import { defineConfig } from 'vite'; +import nette from '@nette/vite-plugin'; + +export default defineConfig({ + plugins: [ + nette({ + entry: 'app.js', + }), + ], +}); +``` + +Dacă doriți să specificați un alt nume de director pentru a construi asset-urile, va trebui să modificați câteva opțiuni: + +```js +export default defineConfig({ + root: 'assets', // directorul rădăcină al asset-urilor sursă + + build: { + outDir: '../www/assets', // unde ajung fișierele compilate + }, + + // ... alte configurații ... +}); +``` + +.[note] +Calea `outDir` este considerată relativă la `root`, de aceea există `../` la început. + + +Pasul 4: Configurarea Nette +--------------------------- + +Spuneți Nette Assets despre Vite în fișierul dumneavoastră `common.neon`: + +```neon +assets: + mapping: + default: + type: vite # îi spune lui Nette să utilizeze ViteMapper + path: assets +``` + + +Pasul 5: Adăugați scripturi +--------------------------- + +Adăugați aceste scripturi în `package.json`: + +```json +{ + "scripts": { + "dev": "vite", + "build": "vite build" + } +} +``` + +Acum puteți: +- `npm run dev` - pornește serverul de dezvoltare cu reîncărcare la cald +- `npm run build` - creează fișiere de producție optimizate + + +Puncte de Intrare +================= + +Un **punct de intrare** este fișierul principal de unde pornește aplicația dumneavoastră. Din acest fișier, importați alte fișiere (CSS, module JavaScript, imagini), creând un arbore de dependențe. Vite urmărește aceste importuri și le grupează pe toate împreună. + +Exemplu de punct de intrare `assets/app.js`: + +```js +// Importă stiluri +import './style.css' + +// Importă module JavaScript +import netteForms from 'nette-forms'; +import naja from 'naja'; + +// Inițializează aplicația dumneavoastră +netteForms.initOnLoad(); +naja.initialize(); +``` + +În șablon puteți insera un punct de intrare după cum urmează: + +```latte +{asset 'app.js'} +``` + +Nette Assets generează automat toate tag-urile HTML necesare - JavaScript, CSS și orice alte dependențe. + + +Puncte de Intrare Multiple +-------------------------- + +Aplicațiile mai mari necesită adesea puncte de intrare separate: + +```js +export default defineConfig({ + plugins: [ + nette({ + entry: [ + 'app.js', // pagini publice + 'admin.js', // panou de administrare + ], + }), + ], +}); +``` + +Utilizați-le în diferite șabloane: + +```latte +{* În pagini publice *} +{asset 'app.js'} + +{* În panoul de administrare *} +{asset 'admin.js'} +``` + + +Important: Fișiere Sursă vs. Fișiere Compilate +---------------------------------------------- + +Este crucial să înțelegeți că în producție puteți încărca doar: + +1. **Puncte de intrare** definite în `entry` +2. **Fișiere din directorul `assets/public/`** + +Nu **puteți** încărca utilizând `{asset}` fișiere arbitrare din `assets/` - doar asset-uri referențiate de fișiere JavaScript sau CSS. Dacă fișierul dumneavoastră nu este referențiat nicăieri, nu va fi compilat. Dacă doriți ca Vite să fie conștient de alte asset-uri, le puteți muta în [folderul public |#public-folder]. + +Vă rugăm să rețineți că, implicit, Vite va încorpora toate asset-urile mai mici de 4KB, deci nu veți putea referenția aceste fișiere direct. (Vezi [documentația Vite |https://vite.dev/guide/assets.html]). + +```latte +{* ✓ Acesta funcționează - este un punct de intrare *} +{asset 'app.js'} + +{* ✓ Acesta funcționează - este în assets/public/ *} +{asset 'favicon.ico'} + +{* ✗ Acesta nu va funcționa - fișier aleatoriu în assets/ *} +{asset 'components/button.js'} +``` + + +Modul de Dezvoltare +=================== + +Modul de dezvoltare este complet opțional, dar oferă beneficii semnificative atunci când este activat. Principalul avantaj este **Hot Module Replacement (HMR)** - vedeți modificările instantaneu fără a pierde starea aplicației, făcând experiența de dezvoltare mult mai fluidă și mai rapidă. + +Vite este un instrument modern de construire care face dezvoltarea incredibil de rapidă. Spre deosebire de bundler-ele tradiționale, Vite servește codul dumneavoastră direct browserului în timpul dezvoltării, ceea ce înseamnă pornire instantanee a serverului indiferent de mărimea proiectului și actualizări ultra-rapide. + + +Pornirea Serverului de Dezvoltare +--------------------------------- + +Rulați serverul de dezvoltare: + +```shell +npm run dev +``` + +Veți vedea: + +``` + ➜ Local: http://localhost:5173/ + ➜ Network: use --host to expose +``` + +Țineți acest terminal deschis în timpul dezvoltării. + +Plugin-ul Nette Vite detectează automat când: +1. Serverul de dezvoltare Vite rulează +2. Aplicația dumneavoastră Nette este în modul de depanare + +Când ambele condiții sunt îndeplinite, Nette Assets încarcă fișierele de la serverul de dezvoltare Vite în loc de directorul compilat: + +```latte +{asset 'app.js'} +{* În dezvoltare: *} +{* În producție: *} +``` + +Nu este necesară configurare - pur și simplu funcționează! + + +Lucrul pe Domenii Diferite +-------------------------- + +Dacă serverul dumneavoastră de dezvoltare rulează pe altceva decât `localhost` (cum ar fi `myapp.local`), s-ar putea să întâmpinați probleme CORS (Cross-Origin Resource Sharing). CORS este o funcționalitate de securitate în browserele web care blochează implicit cererile între domenii diferite. Când aplicația dumneavoastră PHP rulează pe `myapp.local`, dar Vite rulează pe `localhost:5173`, browserul le consideră domenii diferite și blochează cererile. + +Aveți două opțiuni pentru a rezolva acest lucru: + +**Opțiunea 1: Configurați CORS** + +Cea mai simplă soluție este să permiteți cererile cross-origin din aplicația dumneavoastră PHP: + +```js +export default defineConfig({ + // ... alte configurații ... + + server: { + cors: { + origin: 'http://myapp.local', // URL-ul aplicației dumneavoastră PHP + }, + }, +}); +``` +**Opțiunea 2: Rulați Vite pe domeniul dumneavoastră** + +Cealaltă soluție este să faceți Vite să ruleze pe același domeniu ca aplicația dumneavoastră PHP. + +```js +export default defineConfig({ + // ... alte configurații ... + + server: { + host: 'myapp.local', // la fel ca aplicația dumneavoastră PHP + }, +}); +``` + +De fapt, chiar și în acest caz, trebuie să configurați CORS deoarece serverul de dezvoltare rulează pe același hostname, dar pe un port diferit. Totuși, în acest caz, CORS este configurat automat de plugin-ul Nette Vite. + + +Dezvoltare HTTPS +---------------- + +Dacă dezvoltați pe HTTPS, aveți nevoie de certificate pentru serverul dumneavoastră de dezvoltare Vite. Cel mai simplu mod este utilizarea unui plugin care generează certificate automat: + +```shell +npm install -D vite-plugin-mkcert +``` + +Iată cum să-l configurați în `vite.config.ts`: + +```js +import mkcert from 'vite-plugin-mkcert'; + +export default defineConfig({ + // ... alte configurații ... + + plugins: [ + mkcert(), // generează certificate automat și activează https + nette(), + ], +}); +``` + +Rețineți că dacă utilizați configurația CORS (Opțiunea 1 de mai sus), trebuie să actualizați URL-ul de origine pentru a utiliza `https://` în loc de `http://`. + + +Build-uri de Producție +====================== + +Creați fișiere de producție optimizate: + +```shell +npm run build +``` + +Vite va: +- Minifica tot JavaScript-ul și CSS-ul +- Împărți codul în bucăți optime +- Genera nume de fișiere hash-uite pentru invalidarea cache-ului +- Crea un fișier manifest pentru Nette Assets + +Exemplu de ieșire: + +``` +www/assets/ +├── app-4f3a2b1c.js # JavaScript-ul dumneavoastră principal (minificat) +├── app-7d8e9f2a.css # CSS extras (minificat) +├── vendor-8c4b5e6d.js # Dependențe partajate +└── .vite/ + └── manifest.json # Mapare pentru Nette Assets +``` + +Numele de fișiere hash-uite asigură că browserele încarcă întotdeauna cea mai recentă versiune. + + +Folder Public +============= + +Fișierele din directorul `assets/public/` sunt copiate în ieșire fără procesare: + +``` +assets/ +├── public/ +│ ├── favicon.ico +│ ├── robots.txt +│ └── images/ +│ └── og-image.jpg +├── app.js +└── style.css +``` + +Referențiați-le în mod normal: + +```latte +{* Aceste fișiere sunt copiate ca atare *} + + +``` + +Pentru fișierele publice, puteți utiliza funcționalitățile FilesystemMapper: + +```neon +assets: + mapping: + default: + type: vite + path: assets + extension: [webp, jpg, png] # Încearcă WebP mai întâi + versioning: true # Adaugă invalidare cache +``` + +În configurația `vite.config.ts` puteți schimba folderul public utilizând opțiunea `publicDir`. + + +Importuri Dinamice +================== + +Vite împarte automat codul pentru o încărcare optimă. Importurile dinamice vă permit să încărcați codul doar atunci când este efectiv necesar, reducând dimensiunea inițială a bundle-ului: + +```js +// Încarcă componente grele la cerere +button.addEventListener('click', async () => { + let { Chart } = await import('./components/chart.js') + new Chart(data) +}) +``` + +Importurile dinamice creează bucăți separate care sunt încărcate doar atunci când este necesar. Acesta se numește "code splitting" și este una dintre cele mai puternice funcționalități ale Vite. Când utilizați importuri dinamice, Vite creează automat fișiere JavaScript separate pentru fiecare modul importat dinamic. + +Tag-ul `{asset 'app.js'}` **nu** preîncarcă automat aceste bucăți dinamice. Acesta este un comportament intenționat - nu dorim să descărcăm cod care s-ar putea să nu fie folosit niciodată. Bucățile sunt descărcate doar atunci când importul dinamic este executat. + +Totuși, dacă știți că anumite importuri dinamice sunt critice și vor fi necesare în curând, le puteți preîncărca: + +```latte +{* Punct de intrare principal *} +{asset 'app.js'} + +{* Preîncarcă importuri dinamice critice *} +{preload 'components/chart.js'} +``` + +Acest lucru îi spune browserului să descarce componenta grafic în fundal, astfel încât să fie gata imediat când este necesar. + + +Suport TypeScript +================= + +TypeScript funcționează imediat: + +```ts +// assets/main.ts +interface User { + name: string + email: string +} + +export function greetUser(user: User): void { + console.log(`Hello, ${user.name}!`) +} +``` + +Referențiați fișierele TypeScript în mod normal: + +```latte +{asset 'main.ts'} +``` + +Pentru suport complet TypeScript, instalați-l: + +```shell +npm install -D typescript +``` + + +Configurație Suplimentară Vite +============================== + +Iată câteva opțiuni utile de configurare Vite cu explicații detaliate: + +```js +export default defineConfig({ + // Directorul rădăcină care conține asset-urile sursă + root: 'assets', + + // Folderul al cărui conținut este copiat în directorul de ieșire ca atare + // Implicit: 'public' (relativ la 'root') + publicDir: 'public', + + build: { + // Unde să plasezi fișierele compilate (relativ la 'root') + outDir: '../www/assets', + + // Golește directorul de ieșire înainte de construire? + // Util pentru a elimina fișierele vechi din build-urile anterioare + emptyOutDir: true, + + // Subdirector în outDir pentru bucățile și asset-urile generate + // Acest lucru ajută la organizarea structurii de ieșire + assetsDir: 'static', + + rollupOptions: { + // Punct(e) de intrare - poate fi un singur fișier sau un array de fișiere + // Fiecare punct de intrare devine un bundle separat + input: [ + 'app.js', // aplicația principală + 'admin.js', // panoul de administrare + ], + }, + }, + + server: { + // Gazda la care să se lege serverul de dezvoltare + // Utilizați '0.0.0.0' pentru a expune la rețea + host: 'localhost', + + // Port pentru serverul de dezvoltare + port: 5173, + + // Configurare CORS pentru cererile cross-origin + cors: { + origin: 'http://myapp.local', + }, + }, + + css: { + // Activează hărțile sursă CSS în dezvoltare + devSourcemap: true, + }, + + plugins: [ + nette(), + ], +}); +``` + +Asta e tot! Acum aveți un sistem de construire modern integrat cu Nette Assets. diff --git a/assets/ru/@home.texy b/assets/ru/@home.texy new file mode 100644 index 0000000000..d9117708fc --- /dev/null +++ b/assets/ru/@home.texy @@ -0,0 +1,432 @@ +Nette Assets +************ + +
    + +Устали вручную управлять статическими файлами в ваших веб-приложениях? Забудьте о жестком кодировании путей, проблемах с инвалидацией кэша или беспокойстве о версионировании файлов. Nette Assets преобразует ваш способ работы с изображениями, таблицами стилей, скриптами и другими статическими ресурсами. + +- **Умное версионирование** гарантирует, что браузеры всегда загружают последние версии файлов +- **Автоматическое определение** типов и размеров файлов +- **Бесшовная интеграция с Latte** с интуитивно понятными тегами +- **Гибкая архитектура**, поддерживающая файловые системы, CDN и Vite +- **Ленивая загрузка** для оптимальной производительности + +
    + + +Зачем Nette Assets? +=================== + +Работа со статическими файлами часто означает повторяющийся, подверженный ошибкам код. Вы вручную конструируете URL, добавляете параметры версии для обхода кэша и по-разному обрабатываете различные типы файлов. Это приводит к такому коду: + +```html +Logo + +``` + +С Nette Assets вся эта сложность исчезает: + +```latte +{* Everything automated - URL, versioning, dimensions *} + + + +{* Or just *} +{asset 'css/style.css'} +``` + +Вот и все! Библиотека автоматически: +- Добавляет параметры версии на основе времени модификации файла +- Определяет размеры изображения и включает их в HTML +- Генерирует правильный HTML-элемент для каждого типа файла +- Обрабатывает как среды разработки, так и производственные среды + + +Установка +========= + +Установите Nette Assets с помощью [Composer|best-practices:composer]: + +```shell +composer require nette/assets +``` + +Требуется PHP 8.1 или выше, и он отлично работает с Nette Framework, но также может использоваться автономно. + + +Первые шаги +=========== + +Nette Assets работает из коробки без какой-либо конфигурации. Разместите свои статические файлы в каталоге `www/assets/` и начните их использовать: + +```latte +{* Display an image with automatic dimensions *} +{asset 'logo.png'} + +{* Include a stylesheet with versioning *} +{asset 'style.css'} + +{* Load a JavaScript module *} +{asset 'app.js'} +``` + +Для большего контроля над генерируемым HTML используйте атрибут `n:asset` или функцию `asset()`. + + +Как это работает +================ + +Nette Assets построен на трех основных концепциях, которые делают его мощным, но простым в использовании: + + +Активы - Ваши файлы стали умнее +------------------------------- + +**Актив** представляет собой любой статический файл в вашем приложении. Каждый файл становится объектом с полезными свойствами только для чтения: + +```php +$image = $assets->getAsset('photo.jpg'); +echo $image->url; // '/assets/photo.jpg?v=1699123456' +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' +``` + +Различные типы файлов предоставляют различные свойства: +- **Изображения**: ширина, высота, альтернативный текст, ленивая загрузка +- **Скрипты**: тип модуля, хеши целостности, crossorigin +- **Таблицы стилей**: медиа-запросы, целостность +- **Аудио/Видео**: продолжительность, размеры +- **Шрифты**: правильная предварительная загрузка с CORS + +Библиотека автоматически определяет типы файлов и создает соответствующий класс актива. + + +Сопоставители - Откуда берутся файлы +------------------------------------ + +**Сопоставитель** знает, как находить файлы и создавать для них URL. У вас может быть несколько сопоставителей для разных целей - локальные файлы, CDN, облачное хранилище или инструменты сборки (каждый из них имеет имя). Встроенный `FilesystemMapper` обрабатывает локальные файлы, а `ViteMapper` интегрируется с современными инструментами сборки. + +Сопоставители определяются в [конфигурации |Configuration]. + + +Реестр - Ваш основной интерфейс +------------------------------- + +**Реестр** управляет всеми сопоставителями и предоставляет основной API: + +```php +// Inject the registry in your service +public function __construct( + private Nette\Assets\Registry $assets +) {} + +// Get assets from different mappers +$logo = $this->assets->getAsset('images:logo.png'); // 'image' mapper +$app = $this->assets->getAsset('app:main.js'); // 'app' mapper +$style = $this->assets->getAsset('style.css'); // uses default mapper +``` + +Реестр автоматически выбирает правильный сопоставитель и кэширует результаты для повышения производительности. + + +Работа с активами в PHP +======================= + +Реестр предоставляет два метода для получения активов: + +```php +// Throws Nette\Assets\AssetNotFoundException if file doesn't exist +$logo = $assets->getAsset('logo.png'); + +// Returns null if file doesn't exist +$banner = $assets->tryGetAsset('banner.jpg'); +if ($banner) { + echo $banner->url; +} +``` + + +Указание сопоставителей +----------------------- + +Вы можете явно выбрать, какой сопоставитель использовать: + +```php +// Use default mapper +$file = $assets->getAsset('document.pdf'); + +// Use specific mapper with prefix +$image = $assets->getAsset('images:photo.jpg'); + +// Use specific mapper with array syntax +$script = $assets->getAsset(['scripts', 'app.js']); +``` + + +Свойства и типы активов +----------------------- + +Каждый тип актива предоставляет соответствующие свойства только для чтения: + +```php +// Image properties +$image = $assets->getAsset('photo.jpg'); +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' + +// Script properties +$script = $assets->getAsset('app.js'); +echo $script->type; // 'module' or null + +// Audio properties +$audio = $assets->getAsset('song.mp3'); +echo $audio->duration; // duration in seconds + +// All assets can be cast to string (returns URL) +$url = (string) $assets->getAsset('document.pdf'); +``` + +.[note] +Свойства, такие как размеры или продолжительность, загружаются лениво только при обращении к ним, что обеспечивает быструю работу библиотеки. + + +Использование активов в Latte-шаблонах +====================================== + +Nette Assets обеспечивает интуитивно понятную [интеграцию с Latte|latte:] с помощью тегов и функций. + + +`{asset}` +--------- + +Тег `{asset}` отображает полные HTML-элементы: + +```latte +{* Renders: *} +{asset 'hero.jpg'} + +{* Renders: *} +{asset 'app.js'} + +{* Renders: *} +{asset 'style.css'} +``` + +Тег автоматически: +- Определяет тип актива и генерирует соответствующий HTML +- Включает версионирование для обхода кэша +- Добавляет размеры для изображений +- Устанавливает правильные атрибуты (type, media и т.д.) + +При использовании внутри HTML-атрибутов он выводит только URL: + +```latte +
    + +``` + + +`n:asset` +--------- + +Для полного контроля над HTML-атрибутами: + +```latte +{* The n:asset attribute fills in src, dimensions, etc. *} +Product + +{* Works with any relevant element *} + + + +``` + +Используйте переменные и сопоставители: + +```latte +{* Variables work naturally *} + + +{* Specify mapper with curly brackets *} + + +{* Specify mapper with array notation *} + +``` + + +`asset()` +--------- + +Для максимальной гибкости используйте функцию `asset()`: + +```latte +{var $logo = asset('logo.png')} +width} height={$logo->height}> + +{* Or directly *} +Logo +``` + + +Опциональные активы +------------------- + +Избегайте ошибок при отсутствии активов с помощью `{asset?}`, `n:asset?` и `tryAsset()`: + +```latte +{* Optional tag - renders nothing if asset missing *} +{asset? 'optional-banner.jpg'} + +{* Optional attribute - skips if asset missing *} +Avatar + +{* With fallback *} +{var $avatar = tryAsset('user-avatar.jpg') ?? asset('default-avatar.jpg')} +Avatar +``` + + +`{preload}` +----------- + +Улучшите производительность загрузки страницы: + +```latte +{* In your section *} +{preload 'critical.css'} +{preload 'important-font.woff2'} +{preload 'hero-image.jpg'} +``` + +Генерирует соответствующие ссылки предварительной загрузки: + +```html + + + +``` + + +Расширенные возможности +======================= + + +Автоматическое определение расширения +------------------------------------- + +Автоматическая обработка нескольких форматов: + +```neon +assets: + mapping: + images: + path: img + extension: [webp, jpg, png] # Try in order +``` + +Теперь вы можете запрашивать без расширения: + +```latte +{* Finds logo.webp, logo.jpg, or logo.png automatically *} +{asset 'images:logo'} +``` + +Идеально подходит для прогрессивного улучшения с помощью современных форматов. + + +Умное версионирование +--------------------- + +Файлы автоматически версионируются на основе времени модификации: + +```latte +{asset 'style.css'} +{* Output: *} +``` + +При обновлении файла метка времени изменяется, что принудительно обновляет кэш браузера. + +Управление версионированием для каждого актива: + +```php +// Disable versioning for specific asset +$asset = $assets->getAsset('style.css', ['version' => false]); + +// In Latte +{asset 'style.css', version: false} +``` + + +Активы шрифтов +-------------- + +Шрифты получают специальную обработку с правильным CORS: + +```latte +{* Proper preload with crossorigin *} +{preload 'fonts:OpenSans-Regular.woff2'} + +{* Use in CSS *} + +``` + + +Пользовательские сопоставители +============================== + +Создавайте пользовательские сопоставители для особых нужд, таких как облачное хранилище или динамическая генерация: + +```php +use Nette\Assets\Mapper; +use Nette\Assets\Asset; +use Nette\Assets\Helpers; + +class CloudStorageMapper implements Mapper +{ + public function __construct( + private CloudClient $client, + private string $bucket, + ) {} + + public function getAsset(string $reference, array $options = []): Asset + { + if (!$this->client->exists($this->bucket, $reference)) { + throw new Nette\Assets\AssetNotFoundException("Asset '$reference' not found"); + } + + $url = $this->client->getPublicUrl($this->bucket, $reference); + return Helpers::createAssetFromUrl($url); + } +} +``` + +Зарегистрируйте в конфигурации: + +```neon +assets: + mapping: + cloud: CloudStorageMapper(@cloudClient, 'my-bucket') +``` + +Используйте как любой другой сопоставитель: + +```latte +{asset 'cloud:user-uploads/photo.jpg'} +``` + +Метод `Helpers::createAssetFromUrl()` автоматически создает правильный тип актива на основе расширения файла. + + +Дальнейшее чтение +================= + +- [Nette Assets: Наконец-то единый API для всего, от изображений до Vite |https://blog.nette.org/en/introducing-nette-assets] diff --git a/assets/ru/@left-menu.texy b/assets/ru/@left-menu.texy new file mode 100644 index 0000000000..7cb7d975a2 --- /dev/null +++ b/assets/ru/@left-menu.texy @@ -0,0 +1,5 @@ +Nette Assets +************ +- [Начало работы |@home] +- [Vite |vite] +- [Конфигурация |Configuration] diff --git a/assets/ru/@meta.texy b/assets/ru/@meta.texy new file mode 100644 index 0000000000..7f329adfce --- /dev/null +++ b/assets/ru/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документация Nette}} diff --git a/assets/ru/configuration.texy b/assets/ru/configuration.texy new file mode 100644 index 0000000000..684cb661af --- /dev/null +++ b/assets/ru/configuration.texy @@ -0,0 +1,188 @@ +Конфигурация активов +******************** + +.[perex] +Обзор параметров конфигурации для Nette Assets. + + +```neon +assets: + # base path for resolving relative mapper paths + basePath: ... # (string) defaults to %wwwDir% + + # base URL for resolving relative mapper URLs + baseUrl: ... # (string) defaults to %baseUrl% + + # enable asset versioning globally? + versioning: ... # (bool) defaults to true + + # defines asset mappers + mapping: ... # (array) defaults to path 'assets' +``` + +`basePath` устанавливает базовый каталог файловой системы для разрешения относительных путей в сопоставителях. По умолчанию используется веб-каталог (`%wwwDir%`). + +`baseUrl` устанавливает базовый префикс URL для разрешения относительных URL в сопоставителях. По умолчанию используется корневой URL (`%baseUrl%`). + +Опция `versioning` глобально управляет тем, добавляются ли параметры версии к URL активов для обхода кэша. Отдельные сопоставители могут переопределить этот параметр. + + +Сопоставители +------------- + +Сопоставители могут быть настроены тремя способами: простая строковая нотация, подробная нотация массива или ссылка на сервис. + +Самый простой способ определить сопоставитель: + +```neon +assets: + mapping: + default: assets # Creates filesystem mapper for %wwwDir%/assets/ + images: img # Creates filesystem mapper for %wwwDir%/img/ + scripts: js # Creates filesystem mapper for %wwwDir%/js/ +``` + +Каждый сопоставитель создает `FilesystemMapper`, который: +- Ищет файлы в `%wwwDir%/` +- Генерирует URL, такие как `%baseUrl%/` +- Наследует глобальные настройки версионирования + + +Для большего контроля используйте подробную нотацию: + +```neon +assets: + mapping: + images: + # directory where files are stored + path: ... # (string) optional, defaults to '' + + # URL prefix for generated links + url: ... # (string) optional, defaults to path + + # enable versioning for this mapper? + versioning: ... # (bool) optional, inherits global setting + + # auto-add extension(s) when searching for files + extension: ... # (string|array) optional, defaults to null +``` + +Понимание того, как разрешаются значения конфигурации: + +Разрешение пути: + - Относительные пути разрешаются из `basePath` (или `%wwwDir%`, если `basePath` не установлен) + - Абсолютные пути используются как есть + +Разрешение URL: + - Относительные URL разрешаются из `baseUrl` (или `%baseUrl%`, если `baseUrl` не установлен) + - Абсолютные URL (со схемой или `//`) используются как есть + - Если `url` не указан, используется значение `path` + + +```neon +assets: + basePath: /var/www/project/www + baseUrl: https://example.com/assets + + mapping: + # Relative path and URL + images: + path: img # Resolved to: /var/www/project/www/img + url: images # Resolved to: https://example.com/assets/images + + # Absolute path and URL + uploads: + path: /var/shared/uploads # Used as-is: /var/shared/uploads + url: https://cdn.example.com # Used as-is: https://cdn.example.com + + # Only path specified + styles: + path: css # Path: /var/www/project/www/css + # URL: https://example.com/assets/css +``` + + +Пользовательские сопоставители +------------------------------ + +Для пользовательских сопоставителей укажите ссылку или определите сервис: + +```neon +services: + s3mapper: App\Assets\S3Mapper(%s3.bucket%) + +assets: + mapping: + cloud: @s3mapper + database: App\Assets\DatabaseMapper(@database.connection) +``` + + +Сопоставитель Vite +------------------ + +Сопоставитель Vite требует только добавления `type: vite`. Ниже приведен полный список параметров конфигурации: + +```neon +assets: + mapping: + default: + # mapper type (required for Vite) + type: vite # (string) required, must be 'vite' + + # Vite build output directory + path: ... # (string) optional, defaults to '' + + # URL prefix for built assets + url: ... # (string) optional, defaults to path + + # location of Vite manifest file + manifest: ... # (string) optional, defaults to /.vite/manifest.json + + # Vite dev server configuration + devServer: ... # (bool|string) optional, defaults to true + + # versioning for public directory files + versioning: ... # (bool) optional, inherits global setting + + # auto-extension for public directory files + extension: ... # (string|array) optional, defaults to null +``` + +Опция `devServer` управляет тем, как активы загружаются во время разработки: + +- `true` (по умолчанию) - Автоматически определяет сервер разработки Vite на текущем хосте и порту. Если сервер разработки запущен **и ваше приложение находится в режиме отладки**, активы загружаются с него с поддержкой горячей замены модулей. Если сервер разработки не запущен, активы загружаются из скомпилированных файлов в публичном каталоге. +- `false` - Полностью отключает интеграцию с сервером разработки. Активы всегда загружаются из скомпилированных файлов. +- Пользовательский URL (например, `https://localhost:5173`) - Вручную укажите URL сервера разработки, включая протокол и порт. Полезно, когда сервер разработки работает на другом хосте или порту. + +Опции `versioning` и `extension` применяются только к файлам в публичном каталоге Vite, которые не обрабатываются Vite. + + +Ручная конфигурация +------------------- + +При неиспользовании Nette DI настройте сопоставители вручную: + +```php +use Nette\Assets\Registry; +use Nette\Assets\FilesystemMapper; +use Nette\Assets\ViteMapper; + +$registry = new Registry; + +// Add filesystem mapper +$registry->addMapper('images', new FilesystemMapper( + baseUrl: 'https://example.com/img', + basePath: __DIR__ . '/www/img', + extensions: ['webp', 'jpg', 'png'], + versioning: true, +)); + +// Add Vite mapper +$registry->addMapper('app', new ViteMapper( + baseUrl: '/build', + basePath: __DIR__ . '/www/build', + manifestPath: __DIR__ . '/www/build/.vite/manifest.json', + devServer: 'https://localhost:5173', +)); +``` diff --git a/assets/ru/vite.texy b/assets/ru/vite.texy new file mode 100644 index 0000000000..7206b344b1 --- /dev/null +++ b/assets/ru/vite.texy @@ -0,0 +1,508 @@ +Интеграция с Vite +***************** + +
    + +Современные JavaScript-приложения требуют сложных инструментов сборки. Nette Assets обеспечивает первоклассную интеграцию с [Vite |https://vitejs.dev/], инструментом сборки фронтенда нового поколения. Получите молниеносную разработку с горячей заменой модулей (HMR) и оптимизированные производственные сборки без проблем с конфигурацией. + +- **Нулевая конфигурация** - автоматический мост между Vite и PHP-шаблонами +- **Полное управление зависимостями** - один тег обрабатывает все активы +- **Горячая замена модулей** - мгновенные обновления JavaScript и CSS +- **Оптимизированные производственные сборки** - разделение кода и удаление неиспользуемого кода (tree shaking) + +
    + + +Nette Assets бесшовно интегрируется с Vite, поэтому вы получаете все эти преимущества, при этом как обычно пишите свои шаблоны. + + +Настройка Vite +============== + +Давайте настроим Vite шаг за шагом. Не беспокойтесь, если вы новичок в инструментах сборки - мы все объясним! + + +Шаг 1: Установите Vite +---------------------- + +Сначала установите Vite и плагин Nette в ваш проект: + +```shell +npm install -D vite @nette/vite-plugin +``` + +Это устанавливает Vite и специальный плагин, который помогает Vite отлично работать с Nette. + + +Шаг 2: Структура проекта +------------------------ + +Стандартный подход заключается в размещении исходных файлов активов в папке `assets/` в корне вашего проекта, а скомпилированных версий - в `www/assets/`: + +/--pre +web-project/ +├── assets/ ← исходные файлы (SCSS, TypeScript, исходные изображения) +│ ├── public/ ← статические файлы (копируются как есть) +│ │ └── favicon.ico +│ ├── images/ +│ │ └── logo.png +│ ├── app.js ← основная точка входа +│ └── style.css ← ваши стили +└── www/ ← публичный каталог (корневой каталог документа) + ├── assets/ ← сюда будут помещены скомпилированные файлы + └── index.php +\-- + +Папка `assets/` содержит ваши исходные файлы - код, который вы пишете. Vite обработает эти файлы и поместит скомпилированные версии в `www/assets/`. + + +Шаг 3: Настройте Vite +--------------------- + +Создайте файл `vite.config.ts` в корне вашего проекта. Этот файл сообщает Vite, где найти ваши исходные файлы и куда поместить скомпилированные. + +Плагин Nette Vite поставляется с умными значениями по умолчанию, которые упрощают настройку. Он предполагает, что ваши исходные файлы фронтенда находятся в каталоге `assets/` (опция `root`), а скомпилированные файлы попадают в `www/assets/` (опция `outDir`). Вам нужно только указать [точку входа|#Точки входа]: + +```js +import { defineConfig } from 'vite'; +import nette from '@nette/vite-plugin'; + +export default defineConfig({ + plugins: [ + nette({ + entry: 'app.js', + }), + ], +}); +``` + +Если вы хотите указать другое имя каталога для сборки ваших активов, вам нужно будет изменить несколько опций: + +```js +export default defineConfig({ + root: 'assets', // root directory of source assets + + build: { + outDir: '../www/assets', // where compiled files go + }, + + // ... other config ... +}); +``` + +.[note] +Путь `outDir` считается относительным к `root`, поэтому в начале есть `../`. + + +Шаг 4: Настройте Nette +---------------------- + +Сообщите Nette Assets о Vite в вашем `common.neon`: + +```neon +assets: + mapping: + default: + type: vite # tells Nette to use the ViteMapper + path: assets +``` + + +Шаг 5: Добавьте скрипты +----------------------- + +Добавьте эти скрипты в ваш `package.json`: + +```json +{ + "scripts": { + "dev": "vite", + "build": "vite build" + } +} +``` + +Теперь вы можете: +- `npm run dev` - запустить сервер разработки с горячей перезагрузкой +- `npm run build` - создать оптимизированные файлы для продакшена + + +Точки входа +=========== + +**Точка входа** - это основной файл, с которого начинается ваше приложение. Из этого файла вы импортируете другие файлы (CSS, JavaScript-модули, изображения), создавая дерево зависимостей. Vite следует этим импортам и объединяет все вместе. + +Пример точки входа `assets/app.js`: + +```js +// Import styles +import './style.css' + +// Import JavaScript modules +import netteForms from 'nette-forms'; +import naja from 'naja'; + +// Initialize your application +netteForms.initOnLoad(); +naja.initialize(); +``` + +В шаблоне вы можете вставить точку входа следующим образом: + +```latte +{asset 'app.js'} +``` + +Nette Assets автоматически генерирует все необходимые HTML-теги - JavaScript, CSS и любые другие зависимости. + + +Несколько точек входа +--------------------- + +Крупные приложения часто нуждаются в отдельных точках входа: + +```js +export default defineConfig({ + plugins: [ + nette({ + entry: [ + 'app.js', // public pages + 'admin.js', // admin panel + ], + }), + ], +}); +``` + +Используйте их в разных шаблонах: + +```latte +{* In public pages *} +{asset 'app.js'} + +{* In admin panel *} +{asset 'admin.js'} +``` + + +Важно: исходные и скомпилированные файлы +---------------------------------------- + +Крайне важно понимать, что на продакшене вы можете загружать только: + +1. **Точки входа**, определенные в `entry` +2. **Файлы из каталога `assets/public/`** + +Вы **не можете** загружать с помощью `{asset}` произвольные файлы из `assets/` - только активы, на которые ссылаются файлы JavaScript или CSS. Если ваш файл нигде не ссылается, он не будет скомпилирован. Если вы хотите, чтобы Vite знал о других активах, вы можете переместить их в [публичную папку |#Публичная папка]. + +Обратите внимание, что по умолчанию Vite будет встраивать все активы размером менее 4 КБ, поэтому вы не сможете ссылаться на эти файлы напрямую. (См. [документацию Vite |https://vite.dev/guide/assets.html]). + +```latte +{* ✓ This works - it's an entry point *} +{asset 'app.js'} + +{* ✓ This works - it's in assets/public/ *} +{asset 'favicon.ico'} + +{* ✗ This won't work - random file in assets/ *} +{asset 'components/button.js'} +``` + + +Режим разработки +================ + +Режим разработки полностью опционален, но предоставляет значительные преимущества при включении. Главное преимущество - это **горячая замена модулей (HMR)** - мгновенно просматривайте изменения без потери состояния приложения, что делает процесс разработки намного более плавным и быстрым. + +Vite - это современный инструмент сборки, который делает разработку невероятно быстрой. В отличие от традиционных сборщиков, Vite обслуживает ваш код непосредственно в браузере во время разработки, что означает мгновенный запуск сервера независимо от размера вашего проекта и молниеносные обновления. + + +Запуск сервера разработки +------------------------- + +Запустите сервер разработки: + +```shell +npm run dev +``` + +Вы увидите: + +``` + ➜ Local: http://localhost:5173/ + ➜ Network: use --host to expose +``` + +Держите этот терминал открытым во время разработки. + +Плагин Nette Vite автоматически определяет, когда: +1. Сервер разработки Vite запущен +2. Ваше приложение Nette находится в режиме отладки + +Когда оба условия выполнены, Nette Assets загружает файлы с сервера разработки Vite вместо скомпилированного каталога: + +```latte +{asset 'app.js'} +{* In development: *} +{* In production: *} +``` + +Никакой настройки не требуется - просто работает! + + +Работа на разных доменах +------------------------ + +Если ваш сервер разработки работает на чем-то другом, кроме `localhost` (например, `myapp.local`), вы можете столкнуться с проблемами CORS (Cross-Origin Resource Sharing). CORS - это функция безопасности в веб-браузерах, которая по умолчанию блокирует запросы между разными доменами. Когда ваше PHP-приложение работает на `myapp.local`, а Vite - на `localhost:5173`, браузер видит их как разные домены и блокирует запросы. + +У вас есть два варианта решения этой проблемы: + +**Вариант 1: Настройте CORS** + +Самое простое решение - разрешить кросс-доменные запросы из вашего PHP-приложения: + +```js +export default defineConfig({ + // ... other config ... + + server: { + cors: { + origin: 'http://myapp.local', // your PHP app URL + }, + }, +}); +``` +**Вариант 2: Запустите Vite на своем домене** + +Другое решение - запустить Vite на том же домене, что и ваше PHP-приложение. + +```js +export default defineConfig({ + // ... other config ... + + server: { + host: 'myapp.local', // same as your PHP app + }, +}); +``` + +На самом деле, даже в этом случае вам нужно настроить CORS, потому что сервер разработки работает на том же имени хоста, но на другом порту. Однако в этом случае CORS автоматически настраивается плагином Nette Vite. + + +Разработка HTTPS +---------------- + +Если вы разрабатываете по HTTPS, вам нужны сертификаты для вашего сервера разработки Vite. Самый простой способ - использовать плагин, который автоматически генерирует сертификаты: + +```shell +npm install -D vite-plugin-mkcert +``` + +Вот как настроить его в `vite.config.ts`: + +```js +import mkcert from 'vite-plugin-mkcert'; + +export default defineConfig({ + // ... other config ... + + plugins: [ + mkcert(), // generates certificates automatically and enables https + nette(), + ], +}); +``` + +Обратите внимание, что если вы используете конфигурацию CORS (Вариант 1 выше), вам нужно обновить URL источника, чтобы использовать `https://` вместо `http://`. + + +Производственные сборки +======================= + +Создайте оптимизированные файлы для продакшена: + +```shell +npm run build +``` + +Vite будет: +- Минифицировать весь JavaScript и CSS +- Разделять код на оптимальные чанки +- Генерировать хэшированные имена файлов для обхода кэша +- Создавать файл манифеста для Nette Assets + +Пример вывода: + +``` +www/assets/ +├── app-4f3a2b1c.js # Your main JavaScript (minified) +├── app-7d8e9f2a.css # Extracted CSS (minified) +├── vendor-8c4b5e6d.js # Shared dependencies +└── .vite/ + └── manifest.json # Mapping for Nette Assets +``` + +Хэшированные имена файлов гарантируют, что браузеры всегда загружают последнюю версию. + + +Публичная папка +=============== + +Файлы в каталоге `assets/public/` копируются в выходной каталог без обработки: + +``` +assets/ +├── public/ +│ ├── favicon.ico +│ ├── robots.txt +│ └── images/ +│ └── og-image.jpg +├── app.js +└── style.css +``` + +Ссылайтесь на них как обычно: + +```latte +{* These files are copied as-is *} + + +``` + +Для публичных файлов вы можете использовать функции FilesystemMapper: + +```neon +assets: + mapping: + default: + type: vite + path: assets + extension: [webp, jpg, png] # Try WebP first + versioning: true # Add cache-busting +``` + +В конфигурации `vite.config.ts` вы можете изменить публичную папку с помощью опции `publicDir`. + + +Динамические импорты +==================== + +Vite автоматически разделяет код для оптимальной загрузки. Динамические импорты позволяют загружать код только тогда, когда он действительно нужен, уменьшая начальный размер бандла: + +```js +// Load heavy components on demand +button.addEventListener('click', async () => { + let { Chart } = await import('./components/chart.js') + new Chart(data) +}) +``` + +Динамические импорты создают отдельные чанки, которые загружаются только при необходимости. Это называется "разделением кода", и это одна из самых мощных функций Vite. Когда вы используете динамические импорты, Vite автоматически создает отдельные JavaScript-файлы для каждого динамически импортированного модуля. + +Тег `{asset 'app.js'}` **не** автоматически предварительно загружает эти динамические чанки. Это преднамеренное поведение - мы не хотим загружать код, который может никогда не быть использован. Чанки загружаются только тогда, когда выполняется динамический импорт. + +Однако, если вы знаете, что определенные динамические импорты критически важны и потребуются в ближайшее время, вы можете предварительно загрузить их: + +```latte +{* Main entry point *} +{asset 'app.js'} + +{* Preload critical dynamic imports *} +{preload 'components/chart.js'} +``` + +Это сообщает браузеру загрузить компонент диаграммы в фоновом режиме, чтобы он был готов немедленно, когда потребуется. + + +Поддержка TypeScript +==================== + +TypeScript работает из коробки: + +```ts +// assets/main.ts +interface User { + name: string + email: string +} + +export function greetUser(user: User): void { + console.log(`Hello, ${user.name}!`) +} +``` + +Ссылайтесь на файлы TypeScript как обычно: + +```latte +{asset 'main.ts'} +``` + +Для полной поддержки TypeScript установите его: + +```shell +npm install -D typescript +``` + + +Дополнительная конфигурация Vite +================================ + +Вот некоторые полезные параметры конфигурации Vite с подробными объяснениями: + +```js +export default defineConfig({ + // Root directory containing source assets + root: 'assets', + + // Folder whose contents are copied to output directory as-is + // Default: 'public' (relative to 'root') + publicDir: 'public', + + build: { + // Where to put compiled files (relative to 'root') + outDir: '../www/assets', + + // Empty output directory before building? + // Useful to remove old files from previous builds + emptyOutDir: true, + + // Subdirectory within outDir for generated chunks and assets + // This helps organize the output structure + assetsDir: 'static', + + rollupOptions: { + // Entry point(s) - can be a single file or array of files + // Each entry point becomes a separate bundle + input: [ + 'app.js', // main application + 'admin.js', // admin panel + ], + }, + }, + + server: { + // Host to bind the dev server to + // Use '0.0.0.0' to expose to network + host: 'localhost', + + // Port for the dev server + port: 5173, + + // CORS configuration for cross-origin requests + cors: { + origin: 'http://myapp.local', + }, + }, + + css: { + // Enable CSS source maps in development + devSourcemap: true, + }, + + plugins: [ + nette(), + ], +}); +``` + +Вот и все! Теперь у вас есть современная система сборки, интегрированная с Nette Assets. diff --git a/assets/sl/@home.texy b/assets/sl/@home.texy new file mode 100644 index 0000000000..435ee0a70b --- /dev/null +++ b/assets/sl/@home.texy @@ -0,0 +1,432 @@ +Nette Assets +************ + +
    + +Už vás unavuje manuálna správa statických súborov vo vašich webových aplikáciách? Zabudnite na pevne zakódované cesty, problémy s zneplatnením cache alebo starosti s verzovaním súborov. Nette Assets mení spôsob, akým pracujete s obrázkami, štýlmi, skriptami a inými statickými zdrojmi. + +- **Inteligentné verzovanie** zaisťuje, že prehliadače vždy načítajú najnovšie súbory +- **Automatická detekcia** typov súborov a rozmerov +- **Bezproblémová integrácia s Latte** s intuitívnymi tagmi +- **Flexibilná architektúra** podporujúca súborové systémy, CDN a Vite +- **Lazy loading** pre optimálny výkon + +
    + + +Prečo Nette Assets? +=================== + +Práca so statickými súbormi často znamená opakujúci sa kód náchylný na chyby. Manuálne konštruujete URL adresy, pridávate parametre verzie pre cache busting a rôzne typy súborov spracovávate odlišne. To vedie ku kódu ako: + +```html +Logo + +``` + +S Nette Assets všetka táto zložitosť zmizne: + +```latte +{* Všetko automatizované - URL, verzovanie, rozmery *} + + + +{* Alebo len *} +{asset 'css/style.css'} +``` + +To je všetko! Knižnica automaticky: +- Pridáva parametre verzie na základe času poslednej úpravy súboru +- Detekuje rozmery obrázka a zahrnie ich do HTML +- Generuje správny HTML element pre každý typ súboru +- Spracováva vývojové aj produkčné prostredia + + +Inštalácia +========== + +Nainštalujte Nette Assets pomocou [Composer|best-practices:composer]: + +```shell +composer require nette/assets +``` + +Vyžaduje PHP 8.1 alebo vyššie a funguje perfektne s Nette Frameworkom, ale môže byť použitá aj samostatne. + + +Prvé kroky +========== + +Nette Assets funguje hneď po vybalení bez akejkoľvek konfigurácie. Umiestnite svoje statické súbory do adresára `www/assets/` a začnite ich používať: + +```latte +{* Zobrazí obrázok s automatickými rozmermi *} +{asset 'logo.png'} + +{* Zahrnie štýl s verzovaním *} +{asset 'style.css'} + +{* Načíta JavaScript modul *} +{asset 'app.js'} +``` + +Pre väčšiu kontrolu nad generovaným HTML použite atribút `n:asset` alebo funkciu `asset()`. + + +Ako to funguje +============== + +Nette Assets je postavený na troch základných konceptoch, ktoré ho robia výkonným a zároveň jednoduchým na používanie: + + +Assets – Vaše súbory sú inteligentné +------------------------------------ + +**Asset** predstavuje akýkoľvek statický súbor vo vašej aplikácii. Každý súbor sa stáva objektom s užitočnými readonly vlastnosťami: + +```php +$image = $assets->getAsset('photo.jpg'); +echo $image->url; // '/assets/photo.jpg?v=1699123456' +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' +``` + +Rôzne typy súborov poskytujú rôzne vlastnosti: +- **Obrázky**: šírka, výška, alternatívny text, lazy loading +- **Skripty**: typ modulu, integrity hashe, crossorigin +- **Štýly**: media queries, integrity +- **Audio/Video**: trvanie, rozmery +- **Fonty**: správne preloading s CORS + +Knižnica automaticky detekuje typy súborov a vytvára príslušnú triedu assetu. + + +Mappery – Odkiaľ súbory pochádzajú +---------------------------------- + +**Mapper** vie, ako nájsť súbory a vytvoriť pre ne URL adresy. Môžete mať viacero mapperov na rôzne účely – lokálne súbory, CDN, cloudové úložisko alebo build nástroje (každý z nich má názov). Vstavaný `FilesystemMapper` spracováva lokálne súbory, zatiaľ čo `ViteMapper` sa integruje s modernými build nástrojmi. + +Mappery sú definované v [konfigurácii]. + + +Registry – Vaše hlavné rozhranie +-------------------------------- + +**Registry** spravuje všetky mappery a poskytuje hlavné API: + +```php +// Vložte registry do vašej služby +public function __construct( + private Nette\Assets\Registry $assets +) {} + +// Získajte assets z rôznych mapperov +$logo = $this->assets->getAsset('images:logo.png'); // 'image' mapper +$app = $this->assets->getAsset('app:main.js'); // 'app' mapper +$style = $this->assets->getAsset('style.css'); // používa predvolený mapper +``` + +Registry automaticky vyberie správny mapper a cachuje výsledky pre výkon. + + +Práca s Assets v PHP +==================== + +Registry poskytuje dve metódy na získanie assetov: + +```php +// Vyhodí Nette\Assets\AssetNotFoundException, ak súbor neexistuje +$logo = $assets->getAsset('logo.png'); + +// Vráti null, ak súbor neexistuje +$banner = $assets->tryGetAsset('banner.jpg'); +if ($banner) { + echo $banner->url; +} +``` + + +Špecifikácia Mapperov +--------------------- + +Môžete explicitne zvoliť, ktorý mapper použiť: + +```php +// Použite predvolený mapper +$file = $assets->getAsset('document.pdf'); + +// Použite špecifický mapper s prefixom +$image = $assets->getAsset('images:photo.jpg'); + +// Použite špecifický mapper so syntaxou poľa +$script = $assets->getAsset(['scripts', 'app.js']); +``` + + +Vlastnosti a typy Assetov +------------------------- + +Každý typ assetu poskytuje relevantné readonly vlastnosti: + +```php +// Vlastnosti obrázka +$image = $assets->getAsset('photo.jpg'); +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' + +// Vlastnosti skriptu +$script = $assets->getAsset('app.js'); +echo $script->type; // 'module' alebo null + +// Vlastnosti audia +$audio = $assets->getAsset('song.mp3'); +echo $audio->duration; // trvanie v sekundách + +// Všetky assets môžu byť pretypované na string (vráti URL) +$url = (string) $assets->getAsset('document.pdf'); +``` + +.[note] +Vlastnosti ako rozmery alebo trvanie sú načítané len lenivo, keď sú prvýkrát prístupné, čo udržuje knižnicu rýchlu. + + +Používanie Assets v Latte šablónach +=================================== + +Nette Assets poskytuje intuitívnu [Latte|latte:] integráciu s tagmi a funkciami. + + +`{asset}` +--------- + +Tag `{asset}` vykresľuje kompletné HTML elementy: + +```latte +{* Vykreslí: *} +{asset 'hero.jpg'} + +{* Vykreslí: *} +{asset 'app.js'} + +{* Vykreslí: *} +{asset 'style.css'} +``` + +Tag automaticky: +- Detekuje typ assetu a generuje príslušné HTML +- Zahrnie verzovanie pre cache busting +- Pridá rozmery pre obrázky +- Nastaví správne atribúty (typ, media atď.) + +Pri použití vo vnútri HTML atribútov výstupom je len URL: + +```latte +
    + +``` + + +`n:asset` +--------- + +Pre úplnú kontrolu nad HTML atribútmi: + +```latte +{* Atribút n:asset dopĺňa src, rozmery atď. *} +Produkt + +{* Funguje s akýmkoľvek relevantným elementom *} + + + +``` + +Použite premenné a mappery: + +```latte +{* Premenné fungujú prirodzene *} + + +{* Špecifikujte mapper s kučeravými zátvorkami *} + + +{* Špecifikujte mapper s notáciou poľa *} + +``` + + +`asset()` +--------- + +Pre maximálnu flexibilitu použite funkciu `asset()`: + +```latte +{var $logo = asset('logo.png')} +width} height={$logo->height}> + +{* Alebo priamo *} +Logo +``` + + +Voliteľné Assets +---------------- + +Spracujte chýbajúce assets elegantne pomocou `{asset?}`, `n:asset?` a `tryAsset()`: + +```latte +{* Voliteľný tag - nevykreslí nič, ak asset chýba *} +{asset? 'optional-banner.jpg'} + +{* Voliteľný atribút - preskočí, ak asset chýba *} +Avatar + +{* S fallbackom *} +{var $avatar = tryAsset('user-avatar.jpg') ?? asset('default-avatar.jpg')} +Avatar +``` + + +`{preload}` +----------- + +Zlepšite výkon načítania stránky: + +```latte +{* Vo vašej sekcii *} +{preload 'critical.css'} +{preload 'important-font.woff2'} +{preload 'hero-image.jpg'} +``` + +Generuje príslušné preload odkazy: + +```html + + + +``` + + +Pokročilé funkcie +================= + + +Automatická detekcia prípon +--------------------------- + +Automaticky spracujte viacero formátov: + +```neon +assets: + mapping: + images: + path: img + extension: [webp, jpg, png] # Skúšajte v poradí +``` + +Teraz môžete požiadať bez prípony: + +```latte +{* Automaticky nájde logo.webp, logo.jpg alebo logo.png *} +{asset 'images:logo'} +``` + +Ideálne pre progresívne vylepšenie s modernými formátmi. + + +Inteligentné verzovanie +----------------------- + +Súbory sú automaticky verzované na základe času poslednej úpravy: + +```latte +{asset 'style.css'} +{* Výstup: *} +``` + +Keď aktualizujete súbor, časová pečiatka sa zmení, čo vynúti obnovenie cache prehliadača. + +Kontrola verzovania pre jednotlivé assets: + +```php +// Zakázať verzovanie pre konkrétny asset +$asset = $assets->getAsset('style.css', ['version' => false]); + +// V Latte +{asset 'style.css', version: false} +``` + + +Font Assets +----------- + +Fonty dostávajú špeciálne zaobchádzanie so správnym CORS: + +```latte +{* Správne preload s crossorigin *} +{preload 'fonts:OpenSans-Regular.woff2'} + +{* Použite v CSS *} + +``` + + +Vlastné Mappery +=============== + +Vytvorte vlastné mappery pre špeciálne potreby, ako je cloudové úložisko alebo dynamické generovanie: + +```php +use Nette\Assets\Mapper; +use Nette\Assets\Asset; +use Nette\Assets\Helpers; + +class CloudStorageMapper implements Mapper +{ + public function __construct( + private CloudClient $client, + private string $bucket, + ) {} + + public function getAsset(string $reference, array $options = []): Asset + { + if (!$this->client->exists($this->bucket, $reference)) { + throw new Nette\Assets\AssetNotFoundException("Asset '$reference' not found"); + } + + $url = $this->client->getPublicUrl($this->bucket, $reference); + return Helpers::createAssetFromUrl($url); + } +} +``` + +Zaregistrujte v konfigurácii: + +```neon +assets: + mapping: + cloud: CloudStorageMapper(@cloudClient, 'my-bucket') +``` + +Použite ako akýkoľvek iný mapper: + +```latte +{asset 'cloud:user-uploads/photo.jpg'} +``` + +Metóda `Helpers::createAssetFromUrl()` automaticky vytvorí správny typ assetu na základe prípony súboru. + + +Nadaljnje branje +================ + +- [Nette Assets: Končno poenoten API za vse, od slik do Vite |https://blog.nette.org/en/introducing-nette-assets] diff --git a/assets/sl/@left-menu.texy b/assets/sl/@left-menu.texy new file mode 100644 index 0000000000..d7dd6293b8 --- /dev/null +++ b/assets/sl/@left-menu.texy @@ -0,0 +1,5 @@ +Nette Assets +************ +- [Začíname |@home] +- [Vite |vite] +- [Konfigurácia |Configuration] diff --git a/assets/sl/@meta.texy b/assets/sl/@meta.texy new file mode 100644 index 0000000000..724324bee5 --- /dev/null +++ b/assets/sl/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokumentacija}} diff --git a/assets/sl/configuration.texy b/assets/sl/configuration.texy new file mode 100644 index 0000000000..dd0d7fb301 --- /dev/null +++ b/assets/sl/configuration.texy @@ -0,0 +1,188 @@ +Konfigurácia Assets +******************* + +.[perex] +Prehľad možností konfigurácie pre Nette Assets. + + +```neon +assets: + # základná cesta pre rozlíšenie relatívnych ciest mapperov + basePath: ... # (string) predvolené na %wwwDir% + + # základná URL pre rozlíšenie relatívnych URL mapperov + baseUrl: ... # (string) predvolené na %baseUrl% + + # povoliť globálne verzovanie assetov? + versioning: ... # (bool) predvolené na true + + # definuje asset mappery + mapping: ... # (array) predvolené na cestu 'assets' +``` + +`basePath` nastavuje predvolený adresár súborového systému pre rozlíšenie relatívnych ciest v mapperoch. Východiskovo používa webový adresár (`%wwwDir%`). + +`baseUrl` nastavuje predvolený URL prefix pre rozlíšenie relatívnych URL v mapperoch. Východiskovo používa koreňovú URL (`%baseUrl%`). + +Možnosť `versioning` globálne riadi, či sa do URL adries assetov pridávajú parametre verzie pre cache busting. Jednotlivé mappery môžu toto nastavenie prepísať. + + +Mappery +------- + +Mappery môžu byť konfigurované tromi spôsobmi: jednoduchou reťazcovou notáciou, detailnou notáciou poľa alebo ako odkaz na službu. + +Najjednoduchší spôsob definovania mappera: + +```neon +assets: + mapping: + default: assets # Vytvorí filesystem mapper pre %wwwDir%/assets/ + images: img # Vytvorí filesystem mapper pre %wwwDir%/img/ + scripts: js # Vytvorí filesystem mapper pre %wwwDir%/js/ +``` + +Každý mapper vytvorí `FilesystemMapper`, ktorý: +- Hľadá súbory v `%wwwDir%/` +- Generuje URL adresy ako `%baseUrl%/` +- Dedí globálne nastavenie verzovania + + +Pre väčšiu kontrolu použite detailnú notáciu: + +```neon +assets: + mapping: + images: + # adresár, kde sú súbory uložené + path: ... # (string) voliteľné, predvolené na '' + + # URL prefix pre generované odkazy + url: ... # (string) voliteľné, predvolené na path + + # povoliť verzovanie pre tento mapper? + versioning: ... # (bool) voliteľné, dedí globálne nastavenie + + # automaticky pridať príponu(y) pri hľadaní súborov + extension: ... # (string|array) voliteľné, predvolené na null +``` + +Pochopenie, ako sa riešia konfiguračné hodnoty: + +Riešenie ciest: + - Relatívne cesty sa riešia z `basePath` (alebo `%wwwDir%`, ak `basePath` nie je nastavená) + - Absolútne cesty sa používajú tak, ako sú + +Riešenie URL: + - Relatívne URL sa riešia z `baseUrl` (alebo `%baseUrl%`, ak `baseUrl` nie je nastavená) + - Absolútne URL (so schémou alebo `//`) sa používajú tak, ako sú + - Ak `url` nie je špecifikovaná, použije sa hodnota `path` + + +```neon +assets: + basePath: /var/www/project/www + baseUrl: https://example.com/assets + + mapping: + # Relatívna cesta a URL + images: + path: img # Rozlíšené na: /var/www/project/www/img + url: images # Rozlíšené na: https://example.com/assets/images + + # Absolútna cesta a URL + uploads: + path: /var/shared/uploads # Použité tak, ako je: /var/shared/uploads + url: https://cdn.example.com # Použité tak, ako je: https://cdn.example.com + + # Špecifikovaná len cesta + styles: + path: css # Cesta: /var/www/project/www/css + # URL: https://example.com/assets/css +``` + + +Vlastné Mappery +--------------- + +Pre vlastné mappery, odkážte alebo definujte službu: + +```neon +services: + s3mapper: App\Assets\S3Mapper(%s3.bucket%) + +assets: + mapping: + cloud: @s3mapper + database: App\Assets\DatabaseMapper(@database.connection) +``` + + +Vite Mapper +----------- + +Vite mapper vyžaduje iba pridanie `type: vite`. Toto je kompletný zoznam konfiguračných možností: + +```neon +assets: + mapping: + default: + # typ mappera (povinný pre Vite) + type: vite # (string) povinné, musí byť 'vite' + + # výstupný adresár Vite buildu + path: ... # (string) voliteľné, predvolené na '' + + # URL prefix pre vybudované assets + url: ... # (string) voliteľné, predvolené na path + + # umiestnenie súboru Vite manifestu + manifest: ... # (string) voliteľné, predvolené na /.vite/manifest.json + + # konfigurácia dev servera Vite + devServer: ... # (bool|string) voliteľné, predvolené na true + + # verzovanie pre súbory vo verejnom adresári + versioning: ... # (bool) voliteľné, dedí globálne nastavenie + + # auto-prípona pre súbory vo verejnom adresári + extension: ... # (string|array) voliteľné, predvolené na null +``` + +Možnosť `devServer` riadi, ako sa assets načítavajú počas vývoja: + +- `true` (predvolené) – Automaticky detekuje Vite dev server na aktuálnom hostiteľovi a porte. Ak dev server beží **a vaša aplikácia je v režime ladenia**, assets sa z neho načítavajú s podporou hot module replacement. Ak dev server nebeží, assets sa načítavajú z vybudovaných súborov vo verejnom adresári. +- `false` – Úplne zakáže integráciu dev servera. Assets sa vždy načítavajú z vybudovaných súborov. +- Vlastná URL (napr. `https://localhost:5173`) – Manuálne špecifikujte URL dev servera vrátane protokolu a portu. Užitočné, keď dev server beží na inom hostiteľovi alebo porte. + +Možnosti `versioning` a `extension` sa vzťahujú iba na súbory vo verejnom adresári Vite, ktoré nie sú spracované Vite. + + +Manuálna konfigurácia +--------------------- + +Ak nepoužívate Nette DI, nakonfigurujte mappery manuálne: + +```php +use Nette\Assets\Registry; +use Nette\Assets\FilesystemMapper; +use Nette\Assets\ViteMapper; + +$registry = new Registry; + +// Pridajte filesystem mapper +$registry->addMapper('images', new FilesystemMapper( + baseUrl: 'https://example.com/img', + basePath: __DIR__ . '/www/img', + extensions: ['webp', 'jpg', 'png'], + versioning: true, +)); + +// Pridajte Vite mapper +$registry->addMapper('app', new ViteMapper( + baseUrl: '/build', + basePath: __DIR__ . '/www/build', + manifestPath: __DIR__ . '/www/build/.vite/manifest.json', + devServer: 'https://localhost:5173', +)); +``` diff --git a/assets/sl/vite.texy b/assets/sl/vite.texy new file mode 100644 index 0000000000..39fa7d687d --- /dev/null +++ b/assets/sl/vite.texy @@ -0,0 +1,508 @@ +Integrácia Vite +*************** + +
    + +Moderné JavaScript aplikácie vyžadujú sofistikované build nástroje. Nette Assets poskytuje prvotriednu integráciu s [Vite |https://vitejs.dev/], nástrojom na tvorbu frontendu novej generácie. Získajte bleskurýchly vývoj s Hot Module Replacement (HMR) a optimalizované produkčné buildy bez problémov s konfiguráciou. + +- **Nulová konfigurácia** – automatický most medzi Vite a PHP šablónami +- **Kompletná správa závislostí** – jeden tag spracuje všetky assets +- **Hot Module Replacement** – okamžité aktualizácie JavaScriptu a CSS +- **Optimalizované produkčné buildy** – code splitting a tree shaking + +
    + + +Nette Assets sa bezproblémovo integruje s Vite, takže získate všetky tieto výhody, zatiaľ čo svoje šablóny píšete ako obvykle. + + +Nastavenie Vite +=============== + +Poďme nastaviť Vite krok za krokom. Nebojte sa, ak ste nováčik v build nástrojoch – všetko vysvetlíme! + + +Krok 1: Inštalácia Vite +----------------------- + +Najprv nainštalujte Vite a Nette plugin do vášho projektu: + +```shell +npm install -D vite @nette/vite-plugin +``` + +Tým sa nainštaluje Vite a špeciálny plugin, ktorý pomáha Vite perfektne fungovať s Nette. + + +Krok 2: Štruktúra projektu +-------------------------- + +Štandardný prístup je umiestniť zdrojové súbory assetov do priečinka `assets/` v koreni vášho projektu a kompilované verzie do `www/assets/`: + +/--pre +web-project/ +├── assets/ ← zdrojové súbory (SCSS, TypeScript, zdrojové obrázky) +│ ├── public/ ← statické súbory (kopírované tak, ako sú) +│ │ └── favicon.ico +│ ├── images/ +│ │ └── logo.png +│ ├── app.js ← hlavný vstupný bod +│ └── style.css ← vaše štýly +└── www/ ← verejný adresár (document root) + ├── assets/ ← sem pôjdu kompilované súbory + └── index.php +\-- + +Priečinok `assets/` obsahuje vaše zdrojové súbory – kód, ktorý píšete. Vite spracuje tieto súbory a umiestni kompilované verzie do `www/assets/`. + + +Krok 3: Konfigurácia Vite +------------------------- + +Vytvorte súbor `vite.config.ts` v koreni vášho projektu. Tento súbor hovorí Vite, kde nájsť vaše zdrojové súbory a kam umiestniť kompilované súbory. + +Nette Vite plugin prichádza s inteligentnými predvolenými nastaveniami, ktoré zjednodušujú konfiguráciu. Predpokladá, že vaše front-end zdrojové súbory sú v adresári `assets/` (možnosť `root`) a kompilované súbory idú do `www/assets/` (možnosť `outDir`). Potrebujete špecifikovať iba [vstupný bod|#Entry Points]: + +```js +import { defineConfig } from 'vite'; +import nette from '@nette/vite-plugin'; + +export default defineConfig({ + plugins: [ + nette({ + entry: 'app.js', + }), + ], +}); +``` + +Ak chcete špecifikovať iný názov adresára pre build vašich assetov, budete musieť zmeniť niekoľko možností: + +```js +export default defineConfig({ + root: 'assets', // koreňový adresár zdrojových assetov + + build: { + outDir: '../www/assets', // kam idú kompilované súbory + }, + + // ... iná konfigurácia ... +}); +``` + +.[note] +Cesta `outDir` sa považuje za relatívnu k `root`, preto je na začiatku `../`. + + +Krok 4: Konfigurácia Nette +-------------------------- + +Povedzte Nette Assets o Vite vo vašom `common.neon`: + +```neon +assets: + mapping: + default: + type: vite # hovorí Nette, aby použilo ViteMapper + path: assets +``` + + +Krok 5: Pridajte skripty +------------------------ + +Pridajte tieto skripty do vášho `package.json`: + +```json +{ + "scripts": { + "dev": "vite", + "build": "vite build" + } +} +``` + +Teraz môžete: +- `npm run dev` – spustiť vývojový server s hot reloadingom +- `npm run build` – vytvoriť optimalizované produkčné súbory + + +Vstupné body +============ + +**Vstupný bod** je hlavný súbor, kde sa spúšťa vaša aplikácia. Z tohto súboru importujete ďalšie súbory (CSS, JavaScript moduly, obrázky), čím vytvárate strom závislostí. Vite sleduje tieto importy a všetko zbalí dohromady. + +Príklad vstupného bodu `assets/app.js`: + +```js +// Import štýlov +import './style.css' + +// Import JavaScript modulov +import netteForms from 'nette-forms'; +import naja from 'naja'; + +// Inicializujte vašu aplikáciu +netteForms.initOnLoad(); +naja.initialize(); +``` + +V šablóne môžete vložiť vstupný bod nasledovne: + +```latte +{asset 'app.js'} +``` + +Nette Assets automaticky generuje všetky potrebné HTML tagy – JavaScript, CSS a akékoľvek iné závislosti. + + +Viacero vstupných bodov +----------------------- + +Väčšie aplikácie často potrebujú samostatné vstupné body: + +```js +export default defineConfig({ + plugins: [ + nette({ + entry: [ + 'app.js', // verejné stránky + 'admin.js', // administrátorský panel + ], + }), + ], +}); +``` + +Použite ich v rôznych šablónach: + +```latte +{* Na verejných stránkach *} +{asset 'app.js'} + +{* V administrátorskom paneli *} +{asset 'admin.js'} +``` + + +Dôležité: Zdrojové vs. kompilované súbory +----------------------------------------- + +Je kľúčové pochopiť, že v produkcii môžete načítať iba: + +1. **Vstupné body** definované v `entry` +2. **Súbory z adresára `assets/public/`** + +**Nemôžete** načítať pomocou `{asset}` ľubovoľné súbory z `assets/` – iba assets odkazované JavaScriptovými alebo CSS súbormi. Ak váš súbor nie je nikde odkazovaný, nebude skompilovaný. Ak chcete, aby Vite vedelo o iných assets, môžete ich presunúť do [verejného priečinka |#public folder]. + +Upozorňujeme, že predvolene Vite vloží všetky assets menšie ako 4KB, takže tieto súbory nebudete môcť odkazovať priamo. (Pozri [dokumentáciu Vite |https://vite.dev/guide/assets.html]). + +```latte +{* ✓ Toto funguje - je to vstupný bod *} +{asset 'app.js'} + +{* ✓ Toto funguje - je to v assets/public/ *} +{asset 'favicon.ico'} + +{* ✗ Toto nebude fungovať - náhodný súbor v assets/ *} +{asset 'components/button.js'} +``` + + +Vývojový režim +============== + +Vývojový režim je úplne voliteľný, ale pri jeho povolením poskytuje značné výhody. Hlavnou výhodou je **Hot Module Replacement (HMR)** – okamžité zobrazenie zmien bez straty stavu aplikácie, čo robí vývoj oveľa plynulejším a rýchlejším. + +Vite je moderný build nástroj, ktorý robí vývoj neuveriteľne rýchlym. Na rozdiel od tradičných bundlerov, Vite počas vývoja servíruje váš kód priamo do prehliadača, čo znamená okamžitý štart servera bez ohľadu na veľkosť vášho projektu a bleskurýchle aktualizácie. + + +Spustenie vývojového servera +---------------------------- + +Spustite vývojový server: + +```shell +npm run dev +``` + +Uvidíte: + +``` + ➜ Local: http://localhost:5173/ + ➜ Network: use --host to expose +``` + +Tento terminál nechajte otvorený počas vývoja. + +Nette Vite plugin automaticky detekuje, keď: +1. Vite dev server beží +2. Vaša Nette aplikácia je v režime ladenia + +Keď sú splnené obe podmienky, Nette Assets načíta súbory z Vite dev servera namiesto kompilovaného adresára: + +```latte +{asset 'app.js'} +{* Vo vývoji: *} +{* V produkcii: *} +``` + +Nie je potrebná žiadna konfigurácia – jednoducho to funguje! + + +Práca na rôznych doménach +------------------------- + +Ak váš vývojový server beží na niečom inom ako `localhost` (napríklad `myapp.local`), môžete naraziť na problémy s CORS (Cross-Origin Resource Sharing). CORS je bezpečnostná funkcia vo webových prehliadačoch, ktorá predvolene blokuje požiadavky medzi rôznymi doménami. Keď vaša PHP aplikácia beží na `myapp.local`, ale Vite beží na `localhost:5173`, prehliadač ich považuje za rôzne domény a blokuje požiadavky. + +Máte dve možnosti, ako to vyriešiť: + +**Možnosť 1: Konfigurácia CORS** + +Najjednoduchším riešením je povoliť cross-origin požiadavky z vašej PHP aplikácie: + +```js +export default defineConfig({ + // ... iná konfigurácia ... + + server: { + cors: { + origin: 'http://myapp.local', // URL vašej PHP aplikácie + }, + }, +}); +``` +**Možnosť 2: Spustite Vite na vašej doméne** + +Ďalším riešením je spustiť Vite na rovnakej doméne ako vaša PHP aplikácia. + +```js +export default defineConfig({ + // ... iná konfigurácia ... + + server: { + host: 'myapp.local', // rovnaké ako vaša PHP aplikácia + }, +}); +``` + +V skutočnosti aj v tomto prípade musíte nakonfigurovať CORS, pretože dev server beží na rovnakom hostname, ale na inom porte. V tomto prípade však CORS automaticky konfiguruje Nette Vite plugin. + + +Vývoj s HTTPS +------------- + +Ak vyvíjate na HTTPS, potrebujete certifikáty pre váš Vite vývojový server. Najjednoduchší spôsob je použiť plugin, ktorý automaticky generuje certifikáty: + +```shell +npm install -D vite-plugin-mkcert +``` + +Tu je návod, ako ho nakonfigurovať v `vite.config.ts`: + +```js +import mkcert from 'vite-plugin-mkcert'; + +export default defineConfig({ + // ... iná konfigurácia ... + + plugins: [ + mkcert(), // automaticky generuje certifikáty a povolí https + nette(), + ], +}); +``` + +Upozorňujeme, že ak používate konfiguráciu CORS (možnosť 1 z vyššie uvedených), musíte aktualizovať URL pôvodu, aby používala `https://` namiesto `http://`. + + +Produkčné buildy +================ + +Vytvorte optimalizované produkčné súbory: + +```shell +npm run build +``` + +Vite bude: +- Minifikovať všetok JavaScript a CSS +- Rozdeliť kód na optimálne časti +- Generovať hashované názvy súborov pre cache-busting +- Vytvoriť manifest súbor pre Nette Assets + +Príklad výstupu: + +``` +www/assets/ +├── app-4f3a2b1c.js # Váš hlavný JavaScript (minifikovaný) +├── app-7d8e9f2a.css # Extrahovaný CSS (minifikovaný) +├── vendor-8c4b5e6d.js # Zdieľané závislosti +└── .vite/ + └── manifest.json # Mapovanie pre Nette Assets +``` + +Hashované názvy súborov zaisťujú, že prehliadače vždy načítajú najnovšiu verziu. + + +Verejný priečinok +================= + +Súbory v adresári `assets/public/` sú kopírované do výstupu bez spracovania: + +``` +assets/ +├── public/ +│ ├── favicon.ico +│ ├── robots.txt +│ └── images/ +│ └── og-image.jpg +├── app.js +└── style.css +``` + +Odkazujte na ne normálne: + +```latte +{* Tieto súbory sú kopírované tak, ako sú *} + + +``` + +Pre verejné súbory môžete použiť funkcie FilesystemMapper: + +```neon +assets: + mapping: + default: + type: vite + path: assets + extension: [webp, jpg, png] # Skúste najprv WebP + versioning: true # Pridajte cache-busting +``` + +V konfigurácii `vite.config.ts` môžete zmeniť verejný priečinok pomocou možnosti `publicDir`. + + +Dynamické importy +================= + +Vite automaticky rozdeľuje kód pre optimálne načítanie. Dynamické importy vám umožňujú načítať kód iba vtedy, keď je skutočne potrebný, čím sa znižuje počiatočná veľkosť balíka: + +```js +// Načítajte ťažké komponenty na požiadanie +button.addEventListener('click', async () => { + let { Chart } = await import('./components/chart.js') + new Chart(data) +}) +``` + +Dynamické importy vytvárajú samostatné časti, ktoré sa načítavajú iba vtedy, keď sú potrebné. Toto sa nazýva „code splitting“ a je to jedna z najvýkonnejších funkcií Vite. Keď použijete dynamické importy, Vite automaticky vytvorí samostatné JavaScript súbory pre každý dynamicky importovaný modul. + +Tag `{asset 'app.js'}` automaticky **neprednačítava** tieto dynamické časti. Toto je zámerné správanie – nechceme sťahovať kód, ktorý sa možno nikdy nepoužije. Časti sa sťahujú iba vtedy, keď sa vykoná dynamický import. + +Ak však viete, že určité dynamické importy sú kritické a budú čoskoro potrebné, môžete ich prednačítať: + +```latte +{* Hlavný vstupný bod *} +{asset 'app.js'} + +{* Prednačítajte kritické dynamické importy *} +{preload 'components/chart.js'} +``` + +Týmto sa prehliadaču povie, aby stiahol komponent grafu na pozadí, takže je okamžite pripravený, keď je potrebný. + + +Podpora TypeScriptu +=================== + +TypeScript funguje hneď po vybalení: + +```ts +// assets/main.ts +interface User { + name: string + email: string +} + +export function greetUser(user: User): void { + console.log(`Hello, ${user.name}!`) +} +``` + +Odkazujte na TypeScript súbory normálne: + +```latte +{asset 'main.ts'} +``` + +Pre plnú podporu TypeScriptu ho nainštalujte: + +```shell +npm install -D typescript +``` + + +Dodatočná konfigurácia Vite +=========================== + +Tu sú niektoré užitočné možnosti konfigurácie Vite s podrobnými vysvetleniami: + +```js +export default defineConfig({ + // Koreňový adresár obsahujúci zdrojové assets + root: 'assets', + + // Priečinok, ktorého obsah sa kopíruje do výstupného adresára tak, ako je + // Predvolené: 'public' (relatívne k 'root') + publicDir: 'public', + + build: { + // Kam umiestniť skompilované súbory (relatívne k 'root') + outDir: '../www/assets', + + // Vyprázdniť výstupný adresár pred buildom? + // Užitočné na odstránenie starých súborov z predchádzajúcich buildov + emptyOutDir: true, + + // Podadresár v rámci outDir pre generované časti a assets + // To pomáha organizovať výstupnú štruktúru + assetsDir: 'static', + + rollupOptions: { + // Vstupný(é) bod(y) - môže byť jeden súbor alebo pole súborov + // Každý vstupný bod sa stáva samostatným balíkom + input: [ + 'app.js', // hlavná aplikácia + 'admin.js', // administrátorský panel + ], + }, + }, + + server: { + // Hostiteľ, na ktorý sa má naviazať dev server + // Použite '0.0.0.0' na vystavenie do siete + host: 'localhost', + + // Port pre dev server + port: 5173, + + // Konfigurácia CORS pre cross-origin požiadavky + cors: { + origin: 'http://myapp.local', + }, + }, + + css: { + // Povoliť CSS source mapy vo vývoji + devSourcemap: true, + }, + + plugins: [ + nette(), + ], +}); +``` + +To je všetko! Teraz máte moderný build systém integrovaný s Nette Assets. diff --git a/assets/tr/@home.texy b/assets/tr/@home.texy new file mode 100644 index 0000000000..1eb50d116d --- /dev/null +++ b/assets/tr/@home.texy @@ -0,0 +1,432 @@ +Nette Assets +************ + +
    + +Web uygulamalarınızdaki statik dosyaları manuel olarak yönetmekten yoruldunuz mu? Yolları elle kodlamayı, önbellek geçersiz kılma sorunlarıyla uğraşmayı veya dosya sürümlemeyi dert etmeyi unutun. Nette Assets, görseller, stil sayfaları, betikler ve diğer statik kaynaklarla çalışma şeklinizi dönüştürür. + +- **Akıllı sürümleme** tarayıcıların her zaman en güncel dosyaları yüklemesini sağlar +- Dosya türlerinin ve boyutlarının **otomatik algılanması** +- Sezgisel etiketlerle **sorunsuz Latte entegrasyonu** +- Dosya sistemlerini, CDN'leri ve Vite'ı destekleyen **esnek mimari** +- Optimal performans için **tembel yükleme** + +
    + + +Neden Nette Assets? +=================== + +Statik dosyalarla çalışmak genellikle tekrarlayan, hataya açık kod anlamına gelir. URL'leri manuel olarak oluşturur, önbelleği temizlemek için sürüm parametreleri eklersiniz ve farklı dosya türlerini farklı şekilde ele alırsınız. Bu, şöyle bir koda yol açar: + +```html +Logo + +``` + +Nette Assets ile tüm bu karmaşıklık ortadan kalkar: + +```latte +{* Her şey otomatik - URL, sürümleme, boyutlar *} + + + +{* Veya sadece *} +{asset 'css/style.css'} +``` + +Hepsi bu kadar! Kütüphane otomatik olarak: +- Dosya değiştirme zamanına göre sürüm parametreleri ekler +- Görsel boyutlarını algılar ve bunları HTML'ye dahil eder +- Her dosya türü için doğru HTML öğesini oluşturur +- Hem geliştirme hem de üretim ortamlarını yönetir + + +Kurulum +======= + +Nette Assets'i [Composer|best-practices:composer] kullanarak kurun: + +```shell +composer require nette/assets +``` + +PHP 8.1 veya daha yüksek bir sürüm gerektirir ve Nette Framework ile mükemmel çalışır, ancak bağımsız olarak da kullanılabilir. + + +İlk Adımlar +=========== + +Nette Assets, sıfır yapılandırmayla kutudan çıktığı gibi çalışır. Statik dosyalarınızı `www/assets/` dizinine yerleştirin ve kullanmaya başlayın: + +```latte +{* Otomatik boyutlarla bir görseli göster *} +{asset 'logo.png'} + +{* Sürümlemeli bir stil sayfası dahil et *} +{asset 'style.css'} + +{* Bir JavaScript modülünü yükle *} +{asset 'app.js'} +``` + +Oluşturulan HTML üzerinde daha fazla kontrol için `n:asset` niteliğini veya `asset()` fonksiyonunu kullanın. + + +Nasıl Çalışır +============= + +Nette Assets, güçlü ama kullanımı basit olmasını sağlayan üç temel konsept üzerine kuruludur: + + +Varlıklar (Assets) - Dosyalarınız Akıllandı +------------------------------------------- + +Bir **varlık (asset)**, uygulamanızdaki herhangi bir statik dosyayı temsil eder. Her dosya, kullanışlı salt okunur özelliklere sahip bir nesne haline gelir: + +```php +$image = $assets->getAsset('photo.jpg'); +echo $image->url; // '/assets/photo.jpg?v=1699123456' +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' +``` + +Farklı dosya türleri farklı özellikler sağlar: +- **Görseller**: genişlik, yükseklik, alternatif metin, tembel yükleme +- **Betikler**: modül türü, bütünlük hash'leri, crossorigin +- **Stil sayfaları**: medya sorguları, bütünlük +- **Ses/Video**: süre, boyutlar +- **Fontlar**: uygun CORS ile ön yükleme + +Kütüphane, dosya türlerini otomatik olarak algılar ve uygun varlık sınıfını oluşturur. + + +Eşleştiriciler (Mappers) - Dosyalar Nereden Geliyor +--------------------------------------------------- + +Bir **eşleştirici (mapper)**, dosyaları nasıl bulacağını ve onlar için URL'leri nasıl oluşturacağını bilir. Farklı amaçlar için birden fazla eşleştiriciniz olabilir - yerel dosyalar, CDN, bulut depolama veya derleme araçları (her birinin bir adı vardır). Yerleşik `FilesystemMapper` yerel dosyaları yönetirken, `ViteMapper` modern derleme araçlarıyla entegre olur. + +Eşleştiriciler [yapılandırma |Configuration] içinde tanımlanır. + + +Kayıt Defteri (Registry) - Ana Arayüzünüz +----------------------------------------- + +**Kayıt defteri (registry)**, tüm eşleştiricileri yönetir ve ana API'yi sağlar: + +```php +// Kayıt defterini servisinizde enjekte edin +public function __construct( + private Nette\Assets\Registry $assets +) {} + +// Farklı eşleştiricilerden varlıkları al +$logo = $this->assets->getAsset('images:logo.png'); // 'image' eşleştiricisi +$app = $this->assets->getAsset('app:main.js'); // 'app' eşleştiricisi +$style = $this->assets->getAsset('style.css'); // varsayılan eşleştiriciyi kullanır +``` + +Kayıt defteri, doğru eşleştiriciyi otomatik olarak seçer ve performans için sonuçları önbelleğe alır. + + +PHP'de Varlıklarla Çalışma +========================== + +Kayıt Defteri, varlıkları almak için iki metot sağlar: + +```php +// Dosya yoksa Nette\Assets\AssetNotFoundException fırlatır +$logo = $assets->getAsset('logo.png'); + +// Dosya yoksa null döndürür +$banner = $assets->tryGetAsset('banner.jpg'); +if ($banner) { + echo $banner->url; +} +``` + + +Eşleştiricileri Belirtme +------------------------ + +Hangi eşleştiricinin kullanılacağını açıkça seçebilirsiniz: + +```php +// Varsayılan eşleştiriciyi kullan +$file = $assets->getAsset('document.pdf'); + +// Önekle belirli bir eşleştiriciyi kullan +$image = $assets->getAsset('images:photo.jpg'); + +// Dizi sözdizimiyle belirli bir eşleştiriciyi kullan +$script = $assets->getAsset(['scripts', 'app.js']); +``` + + +Varlık Özellikleri ve Türleri +----------------------------- + +Her varlık türü ilgili salt okunur özellikler sağlar: + +```php +// Görsel özellikleri +$image = $assets->getAsset('photo.jpg'); +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' + +// Betik özellikleri +$script = $assets->getAsset('app.js'); +echo $script->type; // 'module' veya null + +// Ses özellikleri +$audio = $assets->getAsset('song.mp3'); +echo $audio->duration; // saniye cinsinden süre + +// Tüm varlıklar dizeye dönüştürülebilir (URL döndürür) +$url = (string) $assets->getAsset('document.pdf'); +``` + +.[note] +Boyutlar veya süre gibi özellikler, kütüphaneyi hızlı tutmak için yalnızca erişildiğinde tembelce yüklenir. + + +Latte Şablonlarında Varlıkları Kullanma +======================================= + +Nette Assets, etiketler ve fonksiyonlarla sezgisel [Latte|latte:] entegrasyonu sağlar. + + +`{asset}` +--------- + +`{asset}` etiketi, eksiksiz HTML öğeleri oluşturur: + +```latte +{* Oluşturur: *} +{asset 'hero.jpg'} + +{* Oluşturur: *} +{asset 'app.js'} + +{* Oluşturur: *} +{asset 'style.css'} +``` + +Etiket otomatik olarak: +- Varlık türünü algılar ve uygun HTML'yi oluşturur +- Önbellek temizleme için sürümlemeyi dahil eder +- Görseller için boyutları ekler +- Doğru nitelikleri (tür, medya vb.) ayarlar + +HTML nitelikleri içinde kullanıldığında, yalnızca URL'yi çıktı verir: + +```latte +
    + +``` + + +`n:asset` +--------- + +HTML nitelikleri üzerinde tam kontrol için: + +```latte +{* n:asset niteliği src, boyutlar vb. doldurur. *} +Ürün + +{* İlgili herhangi bir öğeyle çalışır *} + + + +``` + +Değişkenleri ve eşleştiricileri kullanın: + +```latte +{* Değişkenler doğal olarak çalışır *} + + +{* Süslü parantezlerle eşleştiriciyi belirt *} + + +{* Dizi gösterimiyle eşleştiriciyi belirt *} + +``` + + +`asset()` +--------- + +Maksimum esneklik için `asset()` fonksiyonunu kullanın: + +```latte +{var $logo = asset('logo.png')} +width} height={$logo->height}> + +{* Veya doğrudan *} +Logo +``` + + +İsteğe Bağlı Varlıklar +---------------------- + +Eksik varlıkları `{asset?}`, `n:asset?` ve `tryAsset()` ile zarifçe ele alın: + +```latte +{* İsteğe bağlı etiket - varlık eksikse hiçbir şey oluşturmaz *} +{asset? 'optional-banner.jpg'} + +{* İsteğe bağlı nitelik - varlık eksikse atlar *} +Avatar + +{* Geri dönüş ile *} +{var $avatar = tryAsset('user-avatar.jpg') ?? asset('default-avatar.jpg')} +Avatar +``` + + +`{preload}` +----------- + +Sayfa yükleme performansını iyileştirin: + +```latte +{* bölümünüzde *} +{preload 'critical.css'} +{preload 'important-font.woff2'} +{preload 'hero-image.jpg'} +``` + +Uygun ön yükleme bağlantılarını oluşturur: + +```html + + + +``` + + +Gelişmiş Özellikler +=================== + + +Uzantı Otomatik Algılama +------------------------ + +Birden çok formatı otomatik olarak yönetin: + +```neon +assets: + mapping: + images: + path: img + extension: [webp, jpg, png] # Sırayla dene +``` + +Şimdi uzantısız olarak isteyebilirsiniz: + +```latte +{* logo.webp, logo.jpg veya logo.png'yi otomatik olarak bulur *} +{asset 'images:logo'} +``` + +Modern formatlarla aşamalı geliştirme için mükemmeldir. + + +Akıllı Sürümleme +---------------- + +Dosyalar, değiştirme zamanına göre otomatik olarak sürümlenir: + +```latte +{asset 'style.css'} +{* Çıktı: *} +``` + +Dosyayı güncellediğinizde, zaman damgası değişir ve tarayıcı önbelleğinin yenilenmesini zorlar. + +Varlık başına sürümlemeyi kontrol edin: + +```php +// Belirli bir varlık için sürümlemeyi devre dışı bırak +$asset = $assets->getAsset('style.css', ['version' => false]); + +// Latte'de +{asset 'style.css', version: false} +``` + + +Font Varlıkları +--------------- + +Fontlar, uygun CORS ile özel muamele görür: + +```latte +{* crossorigin ile uygun ön yükleme *} +{preload 'fonts:OpenSans-Regular.woff2'} + +{* CSS'de kullan *} + +``` + + +Özel Eşleştiriciler (Custom Mappers) +==================================== + +Bulut depolama veya dinamik üretim gibi özel ihtiyaçlar için özel eşleştiriciler oluşturun: + +```php +use Nette\Assets\Mapper; +use Nette\Assets\Asset; +use Nette\Assets\Helpers; + +class CloudStorageMapper implements Mapper +{ + public function __construct( + private CloudClient $client, + private string $bucket, + ) {} + + public function getAsset(string $reference, array $options = []): Asset + { + if (!$this->client->exists($this->bucket, $reference)) { + throw new Nette\Assets\AssetNotFoundException("Varlık '$reference' bulunamadı"); + } + + $url = $this->client->getPublicUrl($this->bucket, $reference); + return Helpers::createAssetFromUrl($url); + } +} +``` + +Yapılandırmada kaydolun: + +```neon +assets: + mapping: + cloud: CloudStorageMapper(@cloudClient, 'my-bucket') +``` + +Diğer eşleştiriciler gibi kullanın: + +```latte +{asset 'cloud:user-uploads/photo.jpg'} +``` + +`Helpers::createAssetFromUrl()` metodu, dosya uzantısına göre doğru varlık türünü otomatik olarak oluşturur. + + +Daha Fazla Okuma +================ + +- [Nette Varlıkları: Nihayet görüntülerden Vite'a kadar her şey için birleşik API |https://blog.nette.org/en/introducing-nette-assets] diff --git a/assets/tr/@left-menu.texy b/assets/tr/@left-menu.texy new file mode 100644 index 0000000000..7d5065f0da --- /dev/null +++ b/assets/tr/@left-menu.texy @@ -0,0 +1,5 @@ +Nette Assets +************ +- [Başlarken |@home] +- [Vite |vite] +- [Yapılandırma |Configuration] diff --git a/assets/tr/@meta.texy b/assets/tr/@meta.texy new file mode 100644 index 0000000000..8dfe82f311 --- /dev/null +++ b/assets/tr/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokümantasyonu}} diff --git a/assets/tr/configuration.texy b/assets/tr/configuration.texy new file mode 100644 index 0000000000..65d8436244 --- /dev/null +++ b/assets/tr/configuration.texy @@ -0,0 +1,188 @@ +Varlıklar Yapılandırması +************************ + +.[perex] +Nette Assets için yapılandırma seçeneklerine genel bakış. + + +```neon +assets: + # göreli eşleştirici yollarını çözümlemek için temel yol + basePath: ... # (string) varsayılan olarak %wwwDir% + + # göreli eşleştirici URL'lerini çözümlemek için temel URL + baseUrl: ... # (string) varsayılan olarak %baseUrl% + + # varlık sürümlemeyi global olarak etkinleştir? + versioning: ... # (bool) varsayılan olarak true + + # varlık eşleştiricilerini tanımlar + mapping: ... # (array) varsayılan olarak 'assets' yolu +``` + +`basePath`, eşleştiricilerdeki göreli yolları çözümlemek için varsayılan dosya sistemi dizinini ayarlar. Varsayılan olarak, web dizinini (`%wwwDir%`) kullanır. + +`baseUrl`, eşleştiricilerdeki göreli URL'leri çözümlemek için varsayılan URL önekini ayarlar. Varsayılan olarak, kök URL'yi (`%baseUrl%`) kullanır. + +`versioning` seçeneği, önbellek temizleme için varlık URL'lerine sürüm parametrelerinin eklenip eklenmeyeceğini global olarak kontrol eder. Bireysel eşleştiriciler bu ayarı geçersiz kılabilir. + + +Eşleştiriciler (Mappers) +------------------------ + +Eşleştiriciler üç şekilde yapılandırılabilir: basit dize gösterimi, ayrıntılı dizi gösterimi veya bir servise referans olarak. + +Bir eşleştirici tanımlamanın en basit yolu: + +```neon +assets: + mapping: + default: assets # %wwwDir%/assets/ için dosya sistemi eşleştiricisi oluşturur + images: img # %wwwDir%/img/ için dosya sistemi eşleştiricisi oluşturur + scripts: js # %wwwDir%/js/ için dosya sistemi eşleştiricisi oluşturur +``` + +Her eşleştirici bir `FilesystemMapper` oluşturur: +- `%wwwDir%/` içinde dosyaları arar +- `%baseUrl%/` gibi URL'ler oluşturur +- Global sürümleme ayarını miras alır + + +Daha fazla kontrol için ayrıntılı gösterimi kullanın: + +```neon +assets: + mapping: + images: + # dosyaların depolandığı dizin + path: ... # (string) isteğe bağlı, varsayılan olarak '' + + # oluşturulan bağlantılar için URL öneki + url: ... # (string) isteğe bağlı, varsayılan olarak yol + + # bu eşleştirici için sürümlemeyi etkinleştir? + versioning: ... # (bool) isteğe bağlı, global ayarı miras alır + + # dosyaları ararken uzantıyı/uzantıları otomatik ekle + extension: ... # (string|array) isteğe bağlı, varsayılan olarak null +``` + +Yapılandırma değerlerinin nasıl çözüldüğünü anlama: + +Yol Çözümlemesi: + - Göreli yollar `basePath`'ten (veya `basePath` ayarlanmamışsa `%wwwDir%`'den) çözümlenir + - Mutlak yollar olduğu gibi kullanılır + +URL Çözümlemesi: + - Göreli URL'ler `baseUrl`'den (veya `baseUrl` ayarlanmamışsa `%baseUrl%`'den) çözümlenir + - Mutlak URL'ler (şema veya `//` ile) olduğu gibi kullanılır + - `url` belirtilmezse, `path` değeri kullanılır + + +```neon +assets: + basePath: /var/www/project/www + baseUrl: https://example.com/assets + + mapping: + # Göreli yol ve URL + images: + path: img # Çözümlenir: /var/www/project/www/img + url: images # Çözümlenir: https://example.com/assets/images + + # Mutlak yol ve URL + uploads: + path: /var/shared/uploads # Olduğu gibi kullanılır: /var/shared/uploads + url: https://cdn.example.com # Olduğu gibi kullanılır: https://cdn.example.com + + # Yalnızca yol belirtildi + styles: + path: css # Yol: /var/www/project/www/css + # URL: https://example.com/assets/css +``` + + +Özel Eşleştiriciler (Custom Mappers) +------------------------------------ + +Özel eşleştiriciler için bir servise referans verin veya bir servis tanımlayın: + +```neon +services: + s3mapper: App\Assets\S3Mapper(%s3.bucket%) + +assets: + mapping: + cloud: @s3mapper + database: App\Assets\DatabaseMapper(@database.connection) +``` + + +Vite Eşleştiricisi +------------------ + +Vite eşleştiricisi yalnızca `type: vite` eklemenizi gerektirir. Bu, yapılandırma seçeneklerinin tam listesidir: + +```neon +assets: + mapping: + default: + # eşleştirici türü (Vite için gerekli) + type: vite # (string) gerekli, 'vite' olmalı + + # Vite derleme çıktı dizini + path: ... # (string) isteğe bağlı, varsayılan olarak '' + + # derlenmiş varlıklar için URL öneki + url: ... # (string) isteğe bağlı, varsayılan olarak yol + + # Vite manifest dosyasının konumu + manifest: ... # (string) isteğe bağlı, varsayılan olarak /.vite/manifest.json + + # Vite dev sunucusu yapılandırması + devServer: ... # (bool|string) isteğe bağlı, varsayılan olarak true + + # public dizin dosyaları için sürümleme + versioning: ... # (bool) isteğe bağlı, global ayarı miras alır + + # public dizin dosyaları için otomatik uzantı + extension: ... # (string|array) isteğe bağlı, varsayılan olarak null +``` + +`devServer` seçeneği, geliştirme sırasında varlıkların nasıl yüklendiğini kontrol eder: + +- `true` (varsayılan) - Mevcut ana bilgisayar ve bağlantı noktasındaki Vite geliştirme sunucusunu otomatik olarak algılar. Geliştirme sunucusu çalışıyorsa **ve uygulamanız hata ayıklama modundaysa**, varlıklar sıcak modül değiştirme (HMR) desteğiyle oradan yüklenir. Geliştirme sunucusu çalışmıyorsa, varlıklar derlenmiş dosyalardan public dizininden yüklenir. +- `false` - Geliştirme sunucusu entegrasyonunu tamamen devre dışı bırakır. Varlıklar her zaman derlenmiş dosyalardan yüklenir. +- Özel URL (örneğin, `https://localhost:5173`) - Geliştirme sunucusu URL'sini protokol ve bağlantı noktası dahil manuel olarak belirtin. Geliştirme sunucusu farklı bir ana bilgisayarda veya bağlantı noktasında çalıştığında kullanışlıdır. + +`versioning` ve `extension` seçenekleri yalnızca Vite tarafından işlenmeyen Vite'ın public dizinindeki dosyalar için geçerlidir. + + +Manuel Yapılandırma +------------------- + +Nette DI kullanmadığınızda, eşleştiricileri manuel olarak yapılandırın: + +```php +use Nette\Assets\Registry; +use Nette\Assets\FilesystemMapper; +use Nette\Assets\ViteMapper; + +$registry = new Registry; + +// Dosya sistemi eşleştiricisi ekle +$registry->addMapper('images', new FilesystemMapper( + baseUrl: 'https://example.com/img', + basePath: __DIR__ . '/www/img', + extensions: ['webp', 'jpg', 'png'], + versioning: true, +)); + +// Vite eşleştiricisi ekle +$registry->addMapper('app', new ViteMapper( + baseUrl: '/build', + basePath: __DIR__ . '/www/build', + manifestPath: __DIR__ . '/www/build/.vite/manifest.json', + devServer: 'https://localhost:5173', +)); +``` diff --git a/assets/tr/vite.texy b/assets/tr/vite.texy new file mode 100644 index 0000000000..8219015d20 --- /dev/null +++ b/assets/tr/vite.texy @@ -0,0 +1,508 @@ +Vite Entegrasyonu +***************** + +
    + +Modern JavaScript uygulamaları gelişmiş derleme araçları gerektirir. Nette Assets, yeni nesil ön uç derleme aracı olan [Vite |https://vitejs.dev/] ile birinci sınıf entegrasyon sağlar. Sıfır yapılandırma zahmetiyle Hot Module Replacement (HMR) ile ışık hızında geliştirme ve optimize edilmiş üretim derlemeleri elde edin. + +- **Sıfır yapılandırma** - Vite ve PHP şablonları arasında otomatik köprü +- **Tam bağımlılık yönetimi** - tek bir etiket tüm varlıkları yönetir +- **Hot Module Replacement** - anında JavaScript ve CSS güncellemeleri +- **Optimize edilmiş üretim derlemeleri** - kod bölme ve ağaç sallama + +
    + + +Nette Assets, Vite ile sorunsuz bir şekilde entegre olur, böylece şablonlarınızı her zamanki gibi yazarken tüm bu avantajlardan yararlanırsınız. + + +Vite Kurulumu +============= + +Vite'ı adım adım kuralım. Derleme araçlarına yeni başlıyorsanız endişelenmeyin - her şeyi açıklayacağız! + + +Adım 1: Vite'ı Kurun +-------------------- + +Önce, Vite'ı ve Nette eklentisini projenize kurun: + +```shell +npm install -D vite @nette/vite-plugin +``` + +Bu, Vite'ı ve Vite'ın Nette ile mükemmel çalışmasına yardımcı olan özel bir eklentiyi kurar. + + +Adım 2: Proje Yapısı +-------------------- + +Standart yaklaşım, kaynak varlık dosyalarını proje kökünüzdeki bir `assets/` klasörüne ve derlenmiş sürümlerini `www/assets/`'ye yerleştirmektir: + +/--pre +web-project/ +├── assets/ ← kaynak dosyalar (SCSS, TypeScript, kaynak görseller) +│ ├── public/ ← statik dosyalar (olduğu gibi kopyalanır) +│ │ └── favicon.ico +│ ├── images/ +│ │ └── logo.png +│ ├── app.js ← ana giriş noktası +│ └── style.css ← stilleriniz +└── www/ ← public dizini (belge kökü) + ├── assets/ ← derlenmiş dosyalar buraya gidecek + └── index.php +\-- + +`assets/` klasörü, kaynak dosyalarınızı - yazdığınız kodu - içerir. Vite bu dosyaları işleyecek ve derlenmiş sürümlerini `www/assets/`'ye koyacaktır. + + +Adım 3: Vite'ı Yapılandırın +--------------------------- + +Proje kökünüzde bir `vite.config.ts` dosyası oluşturun. Bu dosya, Vite'a kaynak dosyalarınızı nerede bulacağını ve derlenmiş dosyaları nereye koyacağını söyler. + +Nette Vite eklentisi, yapılandırmayı basitleştiren akıllı varsayılan ayarlarla gelir. Ön uç kaynak dosyalarınızın `assets/` dizininde (`root` seçeneği) olduğunu ve derlenmiş dosyaların `www/assets/`'ye gittiğini (`outDir` seçeneği) varsayar. Yalnızca [giriş noktasını|#Entry Points] belirtmeniz gerekir: + +```js +import { defineConfig } from 'vite'; +import nette from '@nette/vite-plugin'; + +export default defineConfig({ + plugins: [ + nette({ + entry: 'app.js', + }), + ], +}); +``` + +Varlıklarınızı derlemek için başka bir dizin adı belirtmek isterseniz, birkaç seçeneği değiştirmeniz gerekecektir: + +```js +export default defineConfig({ + root: 'assets', // kaynak varlıkların kök dizini + + build: { + outDir: '../www/assets', // derlenmiş dosyaların gideceği yer + }, + + // ... diğer yapılandırma ... +}); +``` + +.[note] +`outDir` yolu, `root`'a göreli olarak kabul edilir, bu yüzden başında `../` vardır. + + +Adım 4: Nette'i Yapılandırın +---------------------------- + +`common.neon` dosyanızda Nette Assets'e Vite hakkında bilgi verin: + +```neon +assets: + mapping: + default: + type: vite # Nette'e ViteMapper'ı kullanmasını söyler + path: assets +``` + + +Adım 5: Betikleri Ekleyin +------------------------- + +Bu betikleri `package.json` dosyanıza ekleyin: + +```json +{ + "scripts": { + "dev": "vite", + "build": "vite build" + } +} +``` + +Şimdi şunları yapabilirsiniz: +- `npm run dev` - sıcak yeniden yükleme ile geliştirme sunucusunu başlat +- `npm run build` - optimize edilmiş üretim dosyaları oluştur + + +Giriş Noktaları +=============== + +Bir **giriş noktası**, uygulamanızın başladığı ana dosyadır. Bu dosyadan diğer dosyaları (CSS, JavaScript modülleri, görseller) içe aktararak bir bağımlılık ağacı oluşturursunuz. Vite bu içe aktarmaları takip eder ve her şeyi bir araya getirir. + +Örnek giriş noktası `assets/app.js`: + +```js +// Stilleri içe aktar +import './style.css' + +// JavaScript modüllerini içe aktar +import netteForms from 'nette-forms'; +import naja from 'naja'; + +// Uygulamanızı başlat +netteForms.initOnLoad(); +naja.initialize(); +``` + +Şablonda bir giriş noktasını şu şekilde ekleyebilirsiniz: + +```latte +{asset 'app.js'} +``` + +Nette Assets, tüm gerekli HTML etiketlerini (JavaScript, CSS ve diğer bağımlılıklar) otomatik olarak oluşturur. + + +Birden Çok Giriş Noktası +------------------------ + +Daha büyük uygulamalar genellikle ayrı giriş noktalarına ihtiyaç duyar: + +```js +export default defineConfig({ + plugins: [ + nette({ + entry: [ + 'app.js', // public sayfalar + 'admin.js', // yönetici paneli + ], + }), + ], +}); +``` + +Bunları farklı şablonlarda kullanın: + +```latte +{* Public sayfalarda *} +{asset 'app.js'} + +{* Yönetici panelinde *} +{asset 'admin.js'} +``` + + +Önemli: Kaynak vs. Derlenmiş Dosyalar +------------------------------------- + +Üretimde yalnızca şunları yükleyebileceğinizi anlamak çok önemlidir: + +1. `entry` içinde tanımlanan **Giriş noktaları** +2. **`assets/public/` dizinindeki dosyalar** + +`{asset}` kullanarak `assets/` içindeki rastgele dosyaları yükleyemezsiniz - yalnızca JavaScript veya CSS dosyaları tarafından referans verilen varlıkları yükleyebilirsiniz. Dosyanız hiçbir yerde referans verilmiyorsa derlenmeyecektir. Vite'ın diğer varlıklardan haberdar olmasını istiyorsanız, onları [public klasörüne |#public folder] taşıyabilirsiniz. + +Varsayılan olarak, Vite'ın 4KB'den küçük tüm varlıkları satır içine alacağını unutmayın, bu nedenle bu dosyalara doğrudan referans veremeyeceksiniz. (Bkz. [Vite dokümantasyonu |https://vite.dev/guide/assets.html]). + +```latte +{* ✓ Bu çalışır - bir giriş noktasıdır *} +{asset 'app.js'} + +{* ✓ Bu çalışır - assets/public/ içinde *} +{asset 'favicon.ico'} + +{* ✗ Bu çalışmaz - assets/ içinde rastgele bir dosya *} +{asset 'components/button.js'} +``` + + +Geliştirme Modu +=============== + +Geliştirme modu tamamen isteğe bağlıdır ancak etkinleştirildiğinde önemli faydalar sağlar. Ana avantajı **Hot Module Replacement (HMR)**'dır - uygulama durumunu kaybetmeden anında değişiklikleri görün, bu da geliştirme deneyimini çok daha pürüzsüz ve hızlı hale getirir. + +Vite, geliştirmeyi inanılmaz hızlı hale getiren modern bir derleme aracıdır. Geleneksel paketleyicilerin aksine, Vite geliştirme sırasında kodunuzu doğrudan tarayıcıya sunar, bu da projenizin ne kadar büyük olursa olsun anında sunucu başlangıcı ve ışık hızında güncellemeler anlamına gelir. + + +Geliştirme Sunucusunu Başlatma +------------------------------ + +Geliştirme sunucusunu çalıştırın: + +```shell +npm run dev +``` + +Şunları göreceksiniz: + +``` + ➜ Local: http://localhost:5173/ + ➜ Network: use --host to expose +``` + +Geliştirme yaparken bu terminali açık tutun. + +Nette Vite eklentisi otomatik olarak şunları algılar: +1. Vite geliştirme sunucusu çalışıyor +2. Nette uygulamanız hata ayıklama modunda + +Her iki koşul da karşılandığında, Nette Assets dosyaları derlenmiş dizin yerine Vite geliştirme sunucusundan yükler: + +```latte +{asset 'app.js'} +{* Geliştirmede: *} +{* Üretimde: *} +``` + +Yapılandırmaya gerek yok - sadece çalışır! + + +Farklı Alan Adlarında Çalışma +----------------------------- + +Geliştirme sunucunuz `localhost` dışında bir şey üzerinde çalışıyorsa (örneğin `myapp.local`), CORS (Cross-Origin Resource Sharing) sorunlarıyla karşılaşabilirsiniz. CORS, web tarayıcılarında varsayılan olarak farklı alan adları arasındaki istekleri engelleyen bir güvenlik özelliğidir. PHP uygulamanız `myapp.local` üzerinde çalışırken Vite `localhost:5173` üzerinde çalıştığında, tarayıcı bunları farklı alan adları olarak görür ve istekleri engeller. + +Bu sorunu çözmek için iki seçeneğiniz var: + +**Seçenek 1: CORS'u Yapılandırın** + +En basit çözüm, PHP uygulamanızdan çapraz kaynak isteklerine izin vermektir: + +```js +export default defineConfig({ + // ... diğer yapılandırma ... + + server: { + cors: { + origin: 'http://myapp.local', // PHP uygulamanızın URL'si + }, + }, +}); +``` +**Seçenek 2: Vite'ı alan adınızda çalıştırın** + +Diğer çözüm, Vite'ı PHP uygulamanızla aynı alan adında çalıştırmaktır. + +```js +export default defineConfig({ + // ... diğer yapılandırma ... + + server: { + host: 'myapp.local', // PHP uygulamanızla aynı + }, +}); +``` + +Aslında, bu durumda bile CORS'u yapılandırmanız gerekir çünkü geliştirme sunucusu aynı ana bilgisayar adında ancak farklı bir bağlantı noktasında çalışır. Ancak, bu durumda CORS, Nette Vite eklentisi tarafından otomatik olarak yapılandırılır. + + +HTTPS Geliştirme +---------------- + +HTTPS üzerinde geliştirme yapıyorsanız, Vite geliştirme sunucunuz için sertifikalara ihtiyacınız vardır. En kolay yol, sertifikaları otomatik olarak oluşturan bir eklenti kullanmaktır: + +```shell +npm install -D vite-plugin-mkcert +``` + +İşte `vite.config.ts`'de nasıl yapılandırılacağı: + +```js +import mkcert from 'vite-plugin-mkcert'; + +export default defineConfig({ + // ... diğer yapılandırma ... + + plugins: [ + mkcert(), // sertifikaları otomatik olarak oluşturur ve https'yi etkinleştirir + nette(), + ], +}); +``` + +CORS yapılandırmasını (yukarıdaki Seçenek 1) kullanıyorsanız, kaynak URL'yi `http://` yerine `https://` kullanacak şekilde güncellemeniz gerektiğini unutmayın. + + +Üretim Derlemeleri +================== + +Optimize edilmiş üretim dosyaları oluşturun: + +```shell +npm run build +``` + +Vite şunları yapacaktır: +- Tüm JavaScript ve CSS'yi küçültür +- Kodu optimal parçalara böler +- Önbellek temizleme için karma adlandırılmış dosyalar oluşturur +- Nette Assets için bir manifest dosyası oluşturur + +Örnek çıktı: + +``` +www/assets/ +├── app-4f3a2b1c.js # Ana JavaScript'iniz (küçültülmüş) +├── app-7d8e9f2a.css # Çıkarılan CSS (küçültülmüş) +├── vendor-8c4b5e6d.js # Paylaşılan bağımlılıklar +└── .vite/ + └── manifest.json # Nette Assets için eşleştirme +``` + +Karma adlandırılmış dosyalar, tarayıcıların her zaman en son sürümü yüklemesini sağlar. + + +Public Klasörü +============== + +`assets/public/` dizinindeki dosyalar, işlenmeden çıktı dizinine kopyalanır: + +``` +assets/ +├── public/ +│ ├── favicon.ico +│ ├── robots.txt +│ └── images/ +│ └── og-image.jpg +├── app.js +└── style.css +``` + +Onlara normal şekilde referans verin: + +```latte +{* Bu dosyalar olduğu gibi kopyalanır *} + + +``` + +Public dosyalar için FilesystemMapper özelliklerini kullanabilirsiniz: + +```neon +assets: + mapping: + default: + type: vite + path: assets + extension: [webp, jpg, png] # Önce WebP'yi dene + versioning: true # Önbellek temizleme ekle +``` + +`vite.config.ts` yapılandırmasında `publicDir` seçeneğini kullanarak public klasörünü değiştirebilirsiniz. + + +Dinamik İçe Aktarmalar +====================== + +Vite, optimal yükleme için kodu otomatik olarak böler. Dinamik içe aktarmalar, kodu yalnızca gerçekten ihtiyaç duyulduğunda yüklemenize olanak tanır, bu da başlangıç paketi boyutunu azaltır: + +```js +// Ağır bileşenleri talep üzerine yükle +button.addEventListener('click', async () => { + let { Chart } = await import('./components/chart.js') + new Chart(data) +}) +``` + +Dinamik içe aktarmalar, yalnızca gerektiğinde yüklenen ayrı yığınlar oluşturur. Buna "kod bölme" denir ve Vite'ın en güçlü özelliklerinden biridir. Dinamik içe aktarmaları kullandığınızda, Vite her dinamik olarak içe aktarılan modül için otomatik olarak ayrı JavaScript dosyaları oluşturur. + +`{asset 'app.js'}` etiketi bu dinamik yığınları otomatik olarak ön yüklemez. Bu kasıtlı bir davranıştır - asla kullanılmayabilecek kodu indirmek istemeyiz. Yığınlar yalnızca dinamik içe aktarma yürütüldüğünde indirilir. + +Ancak, belirli dinamik içe aktarmaların kritik olduğunu ve yakında ihtiyaç duyulacağını biliyorsanız, bunları ön yükleyebilirsiniz: + +```latte +{* Ana giriş noktası *} +{asset 'app.js'} + +{* Kritik dinamik içe aktarmaları ön yükle *} +{preload 'components/chart.js'} +``` + +Bu, tarayıcıya grafik bileşenini arka planda indirmesini söyler, böylece ihtiyaç duyulduğunda hemen hazır olur. + + +TypeScript Desteği +================== + +TypeScript kutudan çıktığı gibi çalışır: + +```ts +// assets/main.ts +interface User { + name: string + email: string +} + +export function greetUser(user: User): void { + console.log(`Merhaba, ${user.name}!`) +} +``` + +TypeScript dosyalarına normal şekilde referans verin: + +```latte +{asset 'main.ts'} +``` + +Tam TypeScript desteği için kurun: + +```shell +npm install -D typescript +``` + + +Ek Vite Yapılandırması +====================== + +İşte ayrıntılı açıklamalarla bazı kullanışlı Vite yapılandırma seçenekleri: + +```js +export default defineConfig({ + // Kaynak varlıkları içeren kök dizin + root: 'assets', + + // İçeriği çıktı dizinine olduğu gibi kopyalanan klasör + // Varsayılan: 'public' ('root'a göreli) + publicDir: 'public', + + build: { + // Derlenmiş dosyaları nereye koymalı ('root'a göreli) + outDir: '../www/assets', + + // Derlemeden önce çıktı dizinini boşaltmalı mı? + // Önceki derlemelerden kalan eski dosyaları kaldırmak için kullanışlıdır + emptyOutDir: true, + + // Oluşturulan yığınlar ve varlıklar için outDir içindeki alt dizin + // Bu, çıktı yapısını düzenlemeye yardımcı olur + assetsDir: 'static', + + rollupOptions: { + // Giriş noktası/noktaları - tek bir dosya veya dosya dizisi olabilir + // Her giriş noktası ayrı bir paket haline gelir + input: [ + 'app.js', // ana uygulama + 'admin.js', // yönetici paneli + ], + }, + }, + + server: { + // Geliştirme sunucusunu bağlamak için ana bilgisayar + // Ağa açmak için '0.0.0.0' kullanın + host: 'localhost', + + // Geliştirme sunucusu için bağlantı noktası + port: 5173, + + // Çapraz kaynak istekleri için CORS yapılandırması + cors: { + origin: 'http://myapp.local', + }, + }, + + css: { + // Geliştirmede CSS kaynak haritalarını etkinleştir + devSourcemap: true, + }, + + plugins: [ + nette(), + ], +}); +``` + +Hepsi bu kadar! Artık Nette Assets ile entegre modern bir derleme sisteminiz var. diff --git a/assets/uk/@home.texy b/assets/uk/@home.texy new file mode 100644 index 0000000000..01b0d357c4 --- /dev/null +++ b/assets/uk/@home.texy @@ -0,0 +1,432 @@ +Nette Assets +************ + +
    + +Втомилися вручну керувати статичними файлами у своїх веб-додатках? Забудьте про жорстке кодування шляхів, проблеми з інвалідацією кешу або турботи про версіонування файлів. Nette Assets змінює спосіб роботи з зображеннями, таблицями стилів, скриптами та іншими статичними ресурсами. + +- **Розумне версіонування** гарантує, що браузери завжди завантажують найновіші файли +- **Автоматичне визначення** типів файлів та розмірів +- **Безшовна інтеграція Latte** з інтуїтивно зрозумілими тегами +- **Гнучка архітектура**, що підтримує файлові системи, CDN та Vite +- **Ледаче завантаження** для оптимальної продуктивності + +
    + + +Чому Nette Assets? +================== + +Робота зі статичними файлами часто означає повторюваний, схильний до помилок код. Ви вручну створюєте URL-адреси, додаєте параметри версії для обходу кешу та по-різному обробляєте різні типи файлів. Це призводить до такого коду: + +```html +Logo + +``` + +З Nette Assets вся ця складність зникає: + +```latte +{* Everything automated - URL, versioning, dimensions *} + + + +{* Or just *} +{asset 'css/style.css'} +``` + +Ось і все! Бібліотека автоматично: +- Додає параметри версії на основі часу модифікації файлу +- Визначає розміри зображень та включає їх у HTML +- Генерує правильний HTML-елемент для кожного типу файлу +- Обробляє як середовища розробки, так і виробничі середовища + + +Встановлення +============ + +Встановіть Nette Assets за допомогою [Composer|best-practices:composer]: + +```shell +composer require nette/assets +``` + +Він вимагає PHP 8.1 або вище та чудово працює з Nette Framework, але також може використовуватися автономно. + + +Перші кроки +=========== + +Nette Assets працює "з коробки" без жодної конфігурації. Розмістіть свої статичні файли в каталозі `www/assets/` і почніть їх використовувати: + +```latte +{* Display an image with automatic dimensions *} +{asset 'logo.png'} + +{* Include a stylesheet with versioning *} +{asset 'style.css'} + +{* Load a JavaScript module *} +{asset 'app.js'} +``` + +Для більшого контролю над згенерованим HTML використовуйте атрибут `n:asset` або функцію `asset()`. + + +Як це працює +============ + +Nette Assets побудовано навколо трьох основних концепцій, які роблять його потужним, але простим у використанні: + + +Активи – Ваші файли стали розумними +----------------------------------- + +**Актив** представляє будь-який статичний файл у вашому додатку. Кожен файл стає об'єктом з корисними властивостями тільки для читання: + +```php +$image = $assets->getAsset('photo.jpg'); +echo $image->url; // '/assets/photo.jpg?v=1699123456' +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' +``` + +Різні типи файлів надають різні властивості: +- **Зображення**: ширина, висота, альтернативний текст, ледаче завантаження +- **Скрипти**: тип модуля, хеші цілісності, crossorigin +- **Таблиці стилів**: медіа-запити, цілісність +- **Аудіо/Відео**: тривалість, розміри +- **Шрифти**: правильне попереднє завантаження з CORS + +Бібліотека автоматично визначає типи файлів та створює відповідний клас активу. + + +Мапери – Звідки беруться файли +------------------------------ + +**Мапер** знає, як знаходити файли та створювати для них URL-адреси. Ви можете мати кілька маперів для різних цілей – локальні файли, CDN, хмарне сховище або інструменти збірки (кожен з них має назву). Вбудований `FilesystemMapper` обробляє локальні файли, тоді як `ViteMapper` інтегрується з сучасними інструментами збірки. + +Мапери визначаються в [конфігурації |Configuration]. + + +Реєстр – Ваш основний інтерфейс +------------------------------- + +**Реєстр** керує всіма маперами та надає основний API: + +```php +// Inject the registry in your service +public function __construct( + private Nette\Assets\Registry $assets +) {} + +// Get assets from different mappers +$logo = $this->assets->getAsset('images:logo.png'); // 'image' mapper +$app = $this->assets->getAsset('app:main.js'); // 'app' mapper +$style = $this->assets->getAsset('style.css'); // uses default mapper +``` + +Реєстр автоматично вибирає правильний мапер та кешує результати для підвищення продуктивності. + + +Робота з активами в PHP +======================= + +Реєстр надає два методи для отримання активів: + +```php +// Throws Nette\Assets\AssetNotFoundException if file doesn't exist +$logo = $assets->getAsset('logo.png'); + +// Returns null if file doesn't exist +$banner = $assets->tryGetAsset('banner.jpg'); +if ($banner) { + echo $banner->url; +} +``` + + +Зазначення маперів +------------------ + +Ви можете явно вибрати, який мапер використовувати: + +```php +// Use default mapper +$file = $assets->getAsset('document.pdf'); + +// Use specific mapper with prefix +$image = $assets->getAsset('images:photo.jpg'); + +// Use specific mapper with array syntax +$script = $assets->getAsset(['scripts', 'app.js']); +``` + + +Властивості та типи активів +--------------------------- + +Кожен тип активу надає відповідні властивості тільки для читання: + +```php +// Image properties +$image = $assets->getAsset('photo.jpg'); +echo $image->width; // 1920 +echo $image->height; // 1080 +echo $image->mimeType; // 'image/jpeg' + +// Script properties +$script = $assets->getAsset('app.js'); +echo $script->type; // 'module' or null + +// Audio properties +$audio = $assets->getAsset('song.mp3'); +echo $audio->duration; // duration in seconds + +// All assets can be cast to string (returns URL) +$url = (string) $assets->getAsset('document.pdf'); +``` + +.[note] +Властивості, такі як розміри або тривалість, завантажуються ледаче лише при доступі, що забезпечує швидку роботу бібліотеки. + + +Використання активів у шаблонах Latte +===================================== + +Nette Assets надає інтуїтивно зрозумілу інтеграцію [Latte|latte:] з тегами та функціями. + + +`{asset}` +--------- + +Тег `{asset}` рендерить повні HTML-елементи: + +```latte +{* Renders: *} +{asset 'hero.jpg'} + +{* Renders: *} +{asset 'app.js'} + +{* Renders: *} +{asset 'style.css'} +``` + +Тег автоматично: +- Визначає тип активу та генерує відповідний HTML +- Включає версіонування для обходу кешу +- Додає розміри для зображень +- Встановлює правильні атрибути (type, media тощо) + +При використанні всередині HTML-атрибутів він виводить лише URL-адресу: + +```latte +
    + +``` + + +`n:asset` +--------- + +Для повного контролю над HTML-атрибутами: + +```latte +{* The n:asset attribute fills in src, dimensions, etc. *} +Product + +{* Works with any relevant element *} + + + +``` + +Використовуйте змінні та мапери: + +```latte +{* Variables work naturally *} + + +{* Specify mapper with curly brackets *} + + +{* Specify mapper with array notation *} + +``` + + +`asset()` +--------- + +Для максимальної гнучкості використовуйте функцію `asset()`: + +```latte +{var $logo = asset('logo.png')} +width} height={$logo->height}> + +{* Or directly *} +Logo +``` + + +Необов'язкові активи +-------------------- + +Обробляйте відсутні активи елегантно за допомогою `{asset?}`, `n:asset?` та `tryAsset()`: + +```latte +{* Optional tag - renders nothing if asset missing *} +{asset? 'optional-banner.jpg'} + +{* Optional attribute - skips if asset missing *} +Avatar + +{* With fallback *} +{var $avatar = tryAsset('user-avatar.jpg') ?? asset('default-avatar.jpg')} +Avatar +``` + + +`{preload}` +----------- + +Покращити продуктивність завантаження сторінки: + +```latte +{* In your section *} +{preload 'critical.css'} +{preload 'important-font.woff2'} +{preload 'hero-image.jpg'} +``` + +Генерує відповідні посилання для попереднього завантаження: + +```html + + + +``` + + +Розширені можливості +==================== + + +Автоматичне визначення розширень +-------------------------------- + +Автоматично обробляти кілька форматів: + +```neon +assets: + mapping: + images: + path: img + extension: [webp, jpg, png] # Try in order +``` + +Тепер ви можете запитувати без розширення: + +```latte +{* Finds logo.webp, logo.jpg, or logo.png automatically *} +{asset 'images:logo'} +``` + +Ідеально підходить для прогресивного покращення з сучасними форматами. + + +Розумне версіонування +--------------------- + +Файли автоматично версіонуються на основі часу модифікації: + +```latte +{asset 'style.css'} +{* Output: *} +``` + +Коли ви оновлюєте файл, мітка часу змінюється, що примушує браузер оновити кеш. + +Контролювати версіонування для кожного активу: + +```php +// Disable versioning for specific asset +$asset = $assets->getAsset('style.css', ['version' => false]); + +// In Latte +{asset 'style.css', version: false} +``` + + +Активи шрифтів +-------------- + +Шрифти отримують особливу обробку з правильним CORS: + +```latte +{* Proper preload with crossorigin *} +{preload 'fonts:OpenSans-Regular.woff2'} + +{* Use in CSS *} + +``` + + +Користувацькі мапери +==================== + +Створюйте користувацькі мапери для особливих потреб, таких як хмарне сховище або динамічна генерація: + +```php +use Nette\Assets\Mapper; +use Nette\Assets\Asset; +use Nette\Assets\Helpers; + +class CloudStorageMapper implements Mapper +{ + public function __construct( + private CloudClient $client, + private string $bucket, + ) {} + + public function getAsset(string $reference, array $options = []): Asset + { + if (!$this->client->exists($this->bucket, $reference)) { + throw new Nette\Assets\AssetNotFoundException("Asset '$reference' not found"); + } + + $url = $this->client->getPublicUrl($this->bucket, $reference); + return Helpers::createAssetFromUrl($url); + } +} +``` + +Зареєструвати в конфігурації: + +```neon +assets: + mapping: + cloud: CloudStorageMapper(@cloudClient, 'my-bucket') +``` + +Використовувати як будь-який інший мапер: + +```latte +{asset 'cloud:user-uploads/photo.jpg'} +``` + +Метод `Helpers::createAssetFromUrl()` автоматично створює правильний тип активу на основі розширення файлу. + + +Читати далі +=========== + +- [Nette Assets: Нарешті уніфікований API для всього, від зображень до Vite |https://blog.nette.org/en/introducing-nette-assets] diff --git a/assets/uk/@left-menu.texy b/assets/uk/@left-menu.texy new file mode 100644 index 0000000000..2461434065 --- /dev/null +++ b/assets/uk/@left-menu.texy @@ -0,0 +1,5 @@ +Nette Assets +************ +- [Початок роботи |@home] +- [Vite |vite] +- [Конфігурація |Configuration] diff --git a/assets/uk/@meta.texy b/assets/uk/@meta.texy new file mode 100644 index 0000000000..96e2d9752a --- /dev/null +++ b/assets/uk/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документація Nette}} diff --git a/assets/uk/configuration.texy b/assets/uk/configuration.texy new file mode 100644 index 0000000000..712ddbf2b7 --- /dev/null +++ b/assets/uk/configuration.texy @@ -0,0 +1,188 @@ +Конфігурація активів +******************** + +.[perex] +Огляд опцій конфігурації для Nette Assets. + + +```neon +assets: + # base path for resolving relative mapper paths + basePath: ... # (string) defaults to %wwwDir% + + # base URL for resolving relative mapper URLs + baseUrl: ... # (string) defaults to %baseUrl% + + # enable asset versioning globally? + versioning: ... # (bool) defaults to true + + # defines asset mappers + mapping: ... # (array) defaults to path 'assets' +``` + +`basePath` встановлює типовий каталог файлової системи для розв'язання відносних шляхів у маперах. За замовчуванням він використовує веб-каталог (`%wwwDir%`). + +`baseUrl` встановлює типовий префікс URL для розв'язання відносних URL у маперах. За замовчуванням він використовує кореневий URL (`%baseUrl%`). + +Опція `versioning` глобально контролює, чи додаються параметри версії до URL-адрес активів для обходу кешу. Окремі мапери можуть перевизначати це налаштування. + + +Мапери +------ + +Мапери можуть бути налаштовані трьома способами: проста рядкова нотація, детальна масивна нотація або як посилання на сервіс. + +Найпростіший спосіб визначити мапер: + +```neon +assets: + mapping: + default: assets # Creates filesystem mapper for %wwwDir%/assets/ + images: img # Creates filesystem mapper for %wwwDir%/img/ + scripts: js # Creates filesystem mapper for %wwwDir%/js/ +``` + +Кожен мапер створює `FilesystemMapper`, який: +- Шукає файли в `%wwwDir%/` +- Генерує URL-адреси, такі як `%baseUrl%/` +- Успадковує глобальні налаштування версіонування + + +Для більшого контролю використовуйте детальну нотацію: + +```neon +assets: + mapping: + images: + # directory where files are stored + path: ... # (string) optional, defaults to '' + + # URL prefix for generated links + url: ... # (string) optional, defaults to path + + # enable versioning for this mapper? + versioning: ... # (bool) optional, inherits global setting + + # auto-add extension(s) when searching for files + extension: ... # (string|array) optional, defaults to null +``` + +Розуміння того, як розв'язуються значення конфігурації: + +Розв'язання шляхів: + - Відносні шляхи розв'язуються з `basePath` (або `%wwwDir%`, якщо `basePath` не встановлено) + - Абсолютні шляхи використовуються як є + +Розв'язання URL: + - Відносні URL-адреси розв'язуються з `baseUrl` (або `%baseUrl%`, якщо `baseUrl` не встановлено) + - Абсолютні URL-адреси (зі схемою або `//`) використовуються як є + - Якщо `url` не вказано, використовується значення `path` + + +```neon +assets: + basePath: /var/www/project/www + baseUrl: https://example.com/assets + + mapping: + # Relative path and URL + images: + path: img # Resolved to: /var/www/project/www/img + url: images # Resolved to: https://example.com/assets/images + + # Absolute path and URL + uploads: + path: /var/shared/uploads # Used as-is: /var/shared/uploads + url: https://cdn.example.com # Used as-is: https://cdn.example.com + + # Only path specified + styles: + path: css # Path: /var/www/project/www/css + # URL: https://example.com/assets/css +``` + + +Користувацькі мапери +-------------------- + +Для користувацьких маперів посилайтеся або визначте сервіс: + +```neon +services: + s3mapper: App\Assets\S3Mapper(%s3.bucket%) + +assets: + mapping: + cloud: @s3mapper + database: App\Assets\DatabaseMapper(@database.connection) +``` + + +Vite Mapper +----------- + +Мапер Vite вимагає лише додати `type: vite`. Це повний список опцій конфігурації: + +```neon +assets: + mapping: + default: + # mapper type (required for Vite) + type: vite # (string) required, must be 'vite' + + # Vite build output directory + path: ... # (string) optional, defaults to '' + + # URL prefix for built assets + url: ... # (string) optional, defaults to path + + # location of Vite manifest file + manifest: ... # (string) optional, defaults to /.vite/manifest.json + + # Vite dev server configuration + devServer: ... # (bool|string) optional, defaults to true + + # versioning for public directory files + versioning: ... # (bool) optional, inherits global setting + + # auto-extension for public directory files + extension: ... # (string|array) optional, defaults to null +``` + +Опція `devServer` контролює, як активи завантажуються під час розробки: + +- `true` (за замовчуванням) - Автоматично виявляє dev-сервер Vite на поточному хості та порту. Якщо dev-сервер запущений **і ваш додаток знаходиться в режимі налагодження**, активи завантажуються з нього з підтримкою гарячої заміни модулів. Якщо dev-сервер не запущений, активи завантажуються з збудованих файлів у публічному каталозі. +- `false` - Повністю вимикає інтеграцію dev-сервера. Активи завжди завантажуються з збудованих файлів. +- Користувацький URL (наприклад, `https://localhost:5173`) - Вручну вказує URL dev-сервера, включаючи протокол та порт. Корисно, коли dev-сервер працює на іншому хості або порту. + +Опції `versioning` та `extension` застосовуються лише до файлів у публічному каталозі Vite, які не обробляються Vite. + + +Ручна конфігурація +------------------ + +Якщо не використовуєте Nette DI, налаштуйте мапери вручну: + +```php +use Nette\Assets\Registry; +use Nette\Assets\FilesystemMapper; +use Nette\Assets\ViteMapper; + +$registry = new Registry; + +// Add filesystem mapper +$registry->addMapper('images', new FilesystemMapper( + baseUrl: 'https://example.com/img', + basePath: __DIR__ . '/www/img', + extensions: ['webp', 'jpg', 'png'], + versioning: true, +)); + +// Add Vite mapper +$registry->addMapper('app', new ViteMapper( + baseUrl: '/build', + basePath: __DIR__ . '/www/build', + manifestPath: __DIR__ . '/www/build/.vite/manifest.json', + devServer: 'https://localhost:5173', +)); +``` diff --git a/assets/uk/vite.texy b/assets/uk/vite.texy new file mode 100644 index 0000000000..fe44373505 --- /dev/null +++ b/assets/uk/vite.texy @@ -0,0 +1,508 @@ +Інтеграція Vite +*************** + +
    + +Сучасні JavaScript-додатки вимагають складних інструментів збірки. Nette Assets надає першокласну інтеграцію з [Vite |https://vitejs.dev/], інструментом збірки фронтенду нового покоління. Отримайте блискавично швидку розробку з Hot Module Replacement (HMR) та оптимізовані виробничі збірки без проблем з конфігурацією. + +- **Нульова конфігурація** – автоматичний міст між Vite та PHP-шаблонами +- **Повне управління залежностями** – один тег обробляє всі активи +- **Гаряча заміна модулів** – миттєві оновлення JavaScript та CSS +- **Оптимізовані виробничі збірки** – розділення коду та tree shaking + +
    + + +Nette Assets бездоганно інтегрується з Vite, тому ви отримуєте всі ці переваги, пишучи свої шаблони як зазвичай. + + +Налаштування Vite +================= + +Давайте налаштуємо Vite крок за кроком. Не хвилюйтеся, якщо ви новачок в інструментах збірки – ми все пояснимо! + + +Крок 1: Встановіть Vite +----------------------- + +Спершу встановіть Vite та плагін Nette у вашому проекті: + +```shell +npm install -D vite @nette/vite-plugin +``` + +Це встановлює Vite та спеціальний плагін, який допомагає Vite ідеально працювати з Nette. + + +Крок 2: Структура проекту +------------------------- + +Стандартний підхід полягає в розміщенні вихідних файлів активів у папці `assets/` у корені вашого проекту, а скомпільованих версій – у `www/assets/`: + +/--pre +web-project/ +├── assets/ ← source files (SCSS, TypeScript, source images) +│ ├── public/ ← static files (copied as-is) +│ │ └── favicon.ico +│ ├── images/ +│ │ └── logo.png +│ ├── app.js ← main entry point +│ └── style.css ← your styles +└── www/ ← public directory (document root) + ├── assets/ ← compiled files will go here + └── index.php +\-- + +Папка `assets/` містить ваші вихідні файли – код, який ви пишете. Vite обробить ці файли та помістить скомпільовані версії в `www/assets/`. + + +Крок 3: Налаштуйте Vite +----------------------- + +Створіть файл `vite.config.ts` у корені вашого проекту. Цей файл вказує Vite, де шукати ваші вихідні файли та куди поміщати скомпільовані. + +Плагін Nette Vite поставляється з розумними значеннями за замовчуванням, які спрощують конфігурацію. Він припускає, що ваші вихідні файли фронтенду знаходяться в каталозі `assets/` (опція `root`), а скомпільовані файли потрапляють до `www/assets/` (опція `outDir`). Вам потрібно лише вказати [точку входу |#Entry Points]: + +```js +import { defineConfig } from 'vite'; +import nette from '@nette/vite-plugin'; + +export default defineConfig({ + plugins: [ + nette({ + entry: 'app.js', + }), + ], +}); +``` + +Якщо ви хочете вказати іншу назву каталогу для збірки ваших активів, вам потрібно буде змінити кілька опцій: + +```js +export default defineConfig({ + root: 'assets', // root directory of source assets + + build: { + outDir: '../www/assets', // where compiled files go + }, + + // ... other config ... +}); +``` + +.[note] +Шлях `outDir` вважається відносним до `root`, тому на початку є `../`. + + +Крок 4: Налаштуйте Nette +------------------------ + +Повідомте Nette Assets про Vite у вашому `common.neon`: + +```neon +assets: + mapping: + default: + type: vite # tells Nette to use the ViteMapper + path: assets +``` + + +Крок 5: Додайте скрипти +----------------------- + +Додайте ці скрипти до вашого `package.json`: + +```json +{ + "scripts": { + "dev": "vite", + "build": "vite build" + } +} +``` + +Тепер ви можете: +- `npm run dev` - запустити dev-сервер з гарячою перезавантаженням +- `npm run build` - створити оптимізовані файли для продакшену + + +Точки входу +=========== + +**Точка входу** – це головний файл, з якого починається ваш додаток. З цього файлу ви імпортуєте інші файли (CSS, модулі JavaScript, зображення), створюючи дерево залежностей. Vite слідує цим імпортам і об'єднує все разом. + +Приклад точки входу `assets/app.js`: + +```js +// Import styles +import './style.css' + +// Import JavaScript modules +import netteForms from 'nette-forms'; +import naja from 'naja'; + +// Initialize your application +netteForms.initOnLoad(); +naja.initialize(); +``` + +У шаблоні ви можете вставити точку входу наступним чином: + +```latte +{asset 'app.js'} +``` + +Nette Assets автоматично генерує всі необхідні HTML-теги – JavaScript, CSS та будь-які інші залежності. + + +Кілька точок входу +------------------ + +Більші додатки часто потребують окремих точок входу: + +```js +export default defineConfig({ + plugins: [ + nette({ + entry: [ + 'app.js', // public pages + 'admin.js', // admin panel + ], + }), + ], +}); +``` + +Використовуйте їх у різних шаблонах: + +```latte +{* In public pages *} +{asset 'app.js'} + +{* In admin panel *} +{asset 'admin.js'} +``` + + +Важливо: Вихідні проти скомпільованих файлів +-------------------------------------------- + +Важливо розуміти, що на продакшені ви можете завантажувати лише: + +1. **Точки входу**, визначені в `entry` +2. **Файли з каталогу `assets/public/`** + +Ви **не можете** завантажувати за допомогою `{asset}` довільні файли з `assets/` – лише активи, на які посилаються файли JavaScript або CSS. Якщо на ваш файл ніде немає посилання, він не буде скомпільований. Якщо ви хочете, щоб Vite знав про інші активи, ви можете перемістити їх до [публічної папки |#public folder]. + +Зверніть увагу, що за замовчуванням Vite вбудовуватиме всі активи розміром менше 4 КБ, тому ви не зможете посилатися на ці файли безпосередньо. (Див. [документацію Vite |https://vite.dev/guide/assets.html]). + +```latte +{* ✓ This works - it's an entry point *} +{asset 'app.js'} + +{* ✓ This works - it's in assets/public/ *} +{asset 'favicon.ico'} + +{* ✗ This won't work - random file in assets/ *} +{asset 'components/button.js'} +``` + + +Режим розробки +============== + +Режим розробки є повністю необов'язковим, але надає значні переваги при увімкненні. Головна перевага – це **Гаряча заміна модулів (HMR)** – миттєве відображення змін без втрати стану програми, що робить процес розробки набагато плавніше та швидше. + +Vite – це сучасний інструмент збірки, який робить розробку неймовірно швидкою. На відміну від традиційних бандлерів, Vite під час розробки подає ваш код безпосередньо в браузер, що означає миттєвий запуск сервера незалежно від розміру вашого проекту та блискавичні оновлення. + + +Запуск dev-сервера +------------------ + +Запустіть dev-сервер: + +```shell +npm run dev +``` + +Ви побачите: + +``` + ➜ Local: http://localhost:5173/ + ➜ Network: use --host to expose +``` + +Залишайте цей термінал відкритим під час розробки. + +Плагін Nette Vite автоматично виявляє, коли: +1. Vite dev-сервер запущений +2. Ваш Nette-додаток знаходиться в режимі налагодження + +Коли обидві умови виконані, Nette Assets завантажує файли з dev-сервера Vite замість скомпільованого каталогу: + +```latte +{asset 'app.js'} +{* In development: *} +{* In production: *} +``` + +Конфігурація не потрібна – просто працює! + + +Робота на різних доменах +------------------------ + +Якщо ваш dev-сервер працює не на `localhost` (наприклад, `myapp.local`), ви можете зіткнутися з проблемами CORS (Cross-Origin Resource Sharing). CORS – це функція безпеки у веб-браузерах, яка за замовчуванням блокує запити між різними доменами. Коли ваш PHP-додаток працює на `myapp.local`, а Vite – на `localhost:5173`, браузер розглядає їх як різні домени та блокує запити. + +У вас є два варіанти вирішення цієї проблеми: + +**Варіант 1: Налаштуйте CORS** + +Найпростіше рішення – дозволити крос-доменні запити з вашого PHP-додатку: + +```js +export default defineConfig({ + // ... other config ... + + server: { + cors: { + origin: 'http://myapp.local', // URL вашого PHP-додатку + }, + }, +}); +``` +**Варіант 2: Запустіть Vite на вашому домені** + +Інше рішення – змусити Vite працювати на тому ж домені, що й ваш PHP-додаток. + +```js +export default defineConfig({ + // ... other config ... + + server: { + host: 'myapp.local', // те саме, що й ваш PHP-додаток + }, +}); +``` + +Насправді, навіть у цьому випадку вам потрібно налаштувати CORS, оскільки dev-сервер працює на тому ж хості, але на іншому порту. Однак у цьому випадку CORS автоматично налаштовується плагіном Nette Vite. + + +Розробка HTTPS +-------------- + +Якщо ви розробляєте на HTTPS, вам потрібні сертифікати для вашого dev-сервера Vite. Найпростіший спосіб – використовувати плагін, який автоматично генерує сертифікати: + +```shell +npm install -D vite-plugin-mkcert +``` + +Ось як налаштувати його в `vite.config.ts`: + +```js +import mkcert from 'vite-plugin-mkcert'; + +export default defineConfig({ + // ... other config ... + + plugins: [ + mkcert(), // generates certificates automatically and enables https + nette(), + ], +}); +``` + +Зверніть увагу, що якщо ви використовуєте конфігурацію CORS (Варіант 1 вище), вам потрібно оновити URL походження, щоб використовувати `https://` замість `http://`. + + +Продакшен збірки +================ + +Створіть оптимізовані файли для продакшену: + +```shell +npm run build +``` + +Vite зробить: +- Мініфікувати весь JavaScript та CSS +- Розділити код на оптимальні чанки +- Згенерувати хешовані імена файлів для обходу кешу +- Створити файл маніфесту для Nette Assets + +Приклад виводу: + +``` +www/assets/ +├── app-4f3a2b1c.js # Your main JavaScript (minified) +├── app-7d8e9f2a.css # Extracted CSS (minified) +├── vendor-8c4b5e6d.js # Shared dependencies +└── .vite/ + └── manifest.json # Mapping for Nette Assets +``` + +Хешовані імена файлів гарантують, що браузери завжди завантажують найновішу версію. + + +Публічна папка +============== + +Файли в каталозі `assets/public/` копіюються у вихідний каталог без обробки: + +``` +assets/ +├── public/ +│ ├── favicon.ico +│ ├── robots.txt +│ └── images/ +│ └── og-image.jpg +├── app.js +└── style.css +``` + +Посилайтеся на них звичайно: + +```latte +{* These files are copied as-is *} + + +``` + +Для публічних файлів можна використовувати функції FilesystemMapper: + +```neon +assets: + mapping: + default: + type: vite + path: assets + extension: [webp, jpg, png] # Try WebP first + versioning: true # Add cache-busting +``` + +У конфігурації `vite.config.ts` ви можете змінити публічну папку за допомогою опції `publicDir`. + + +Динамічні імпорти +================= + +Vite автоматично розділяє код для оптимального завантаження. Динамічні імпорти дозволяють завантажувати код лише тоді, коли він дійсно потрібен, зменшуючи початковий розмір бандлу: + +```js +// Load heavy components on demand +button.addEventListener('click', async () => { + let { Chart } = await import('./components/chart.js') + new Chart(data) +}) +``` + +Динамічні імпорти створюють окремі чанки, які завантажуються лише за потреби. Це називається "розділення коду" (code splitting) і є однією з найпотужніших функцій Vite. Коли ви використовуєте динамічні імпорти, Vite автоматично створює окремі файли JavaScript для кожного динамічно імпортованого модуля. + +Тег `{asset 'app.js'}` **не** попередньо завантажує ці динамічні чанки автоматично. Це навмисна поведінка – ми не хочемо завантажувати код, який може ніколи не використовуватися. Чанки завантажуються лише тоді, коли виконується динамічний імпорт. + +Однак, якщо ви знаєте, що певні динамічні імпорти є критичними і знадобляться незабаром, ви можете попередньо завантажити їх: + +```latte +{* Main entry point *} +{asset 'app.js'} + +{* Preload critical dynamic imports *} +{preload 'components/chart.js'} +``` + +Це вказує браузеру завантажити компонент діаграми у фоновому режимі, щоб він був готовий негайно, коли це знадобиться. + + +Підтримка TypeScript +==================== + +TypeScript працює "з коробки": + +```ts +// assets/main.ts +interface User { + name: string + email: string +} + +export function greetUser(user: User): void { + console.log(`Hello, ${user.name}!`) +} +``` + +Посилайтеся на файли TypeScript звичайно: + +```latte +{asset 'main.ts'} +``` + +Для повної підтримки TypeScript встановіть його: + +```shell +npm install -D typescript +``` + + +Додаткова конфігурація Vite +=========================== + +Ось деякі корисні опції конфігурації Vite з детальними поясненнями: + +```js +export default defineConfig({ + // Root directory containing source assets + root: 'assets', + + // Folder whose contents are copied to output directory as-is + // Default: 'public' (relative to 'root') + publicDir: 'public', + + build: { + // Where to put compiled files (relative to 'root') + outDir: '../www/assets', + + // Empty output directory before building? + // Useful to remove old files from previous builds + emptyOutDir: true, + + // Subdirectory within outDir for generated chunks and assets + // This helps organize the output structure + assetsDir: 'static', + + rollupOptions: { + // Entry point(s) - can be a single file or array of files + // Each entry point becomes a separate bundle + input: [ + 'app.js', // main application + 'admin.js', // admin panel + ], + }, + }, + + server: { + // Host to bind the dev server to + // Use '0.0.0.0' to expose to network + host: 'localhost', + + // Port for the dev server + port: 5173, + + // CORS configuration for cross-origin requests + cors: { + origin: 'http://myapp.local', + }, + }, + + css: { + // Enable CSS source maps in development + devSourcemap: true, + }, + + plugins: [ + nette(), + ], +}); +``` + +Ось і все! Тепер у вас є сучасна система збірки, інтегрована з Nette Assets. diff --git a/best-practices/bg/@home.texy b/best-practices/bg/@home.texy index c3d00d8610..808c4d31b7 100644 --- a/best-practices/bg/@home.texy +++ b/best-practices/bg/@home.texy @@ -1,68 +1,69 @@ -Най-добри практики -****************** +Ръководства и процедури +*********************** .[perex] -Уроци, решения на често срещани проблеми и най-добри практики за Nette. +Ръководства, решения на често срещани задачи и *добри практики* за Nette.
    -Приложение Nette +Nette Приложения ---------------- -- [Инжектиране на методи и атрибути |inject-method-attribute] -- [Генериране на преентири от атрибути |presenter-traits] -- [Предаване на параметри в preenters |passing-settings-to-presenters] -- [Как да се върнете към предишна страница |restore-request] -- [Страница на резултатите от базата данни |Pagination] -- [Динамични фрагменти |dynamic-snippets] +- [Методи и атрибути inject |inject-method-attribute] +- [Съставяне на презентери от trait |presenter-traits] +- [Предаване на настройки към презентери |passing-settings-to-presenters] +- [Как да се върнем към предишна страница |restore-request] +- [Странициране на резултати от база данни |pagination] +- [Динамични снипети |dynamic-snippets] +- [Как да използваме атрибута #Requires |attribute-requires] +- [Как правилно да използваме POST връзки |post-links]
    -Формуляри ---------- -- [Повторна употреба на формуляри |form-reuse] -- [Формуляр за създаване и редактиране на запис |creating-editing-form] -- [Да създадем формуляр за контакт |lets-create-contact-form] -- [Зависими полета за избор |https://blog.nette.org/bg/zavisimi-selektirasi-poleta-elegantno-v-nette-i-cist-js] +Форми +----- +- [Повторно използване на форми |form-reuse] +- [Форма за създаване и редактиране на запис |creating-editing-form] +- [Създаваме контактна форма |lets-create-contact-form] +- [Зависими селектбокси |https://blog.nette.org/bg/dependent-selectboxes-elegantly-in-nette-and-pure-js]
    -Обща +Общи ---- - [Как да заредим конфигурационен файл |bootstrap:] -- [Защо Nette използва константна нотация PascalCase? |https://blog.nette.org/bg/za-po-malko-kresene-v-koda] -- [Защо Nette не използва суфикса Interface? |https://blog.nette.org/bg/prefiksite-i-sufiksite-ne-prinadlezat-na-imenata-na-interfejsite] -- [Съвети за използване на Composer |composer] -- [Съвети за редактори и инструменти |editors-and-tools] +- [Как да пишем микро-уебсайтове |microsites] +- [Защо Nette използва PascalCase нотация за константи? |https://blog.nette.org/bg/for-less-screaming-in-the-code] +- [Защо Nette не използва суфикс Interface? |https://blog.nette.org/bg/prefixes-and-suffixes-do-not-belong-in-interface-names] +- [Composer: съвети за използване |composer] +- [Съвети за редактори & инструменти |editors-and-tools] +- [Въведение в обектно-ориентираното програмиране |nette:introduction-to-object-oriented-programming]
    -Пример за решение ------------------ -- [Примерите на Nette |https://github.com/nette-examples] -- [Доктрина и Нете |https://contributte.org/nettrine/] -- [Примери за Contributte |https://contributte.org/examples.html] -- [Уебсайт на Doctrine ORM |https://github.com/MinecordNetwork/Website] +Примерни решения +---------------- +- [Nette examples |https://github.com/nette-examples] +- [Doctrine & Nette |https://contributte.org/nettrine/] +- [Contributte examples |https://contributte.org/examples.html] +- [Doctrine ORM Website |https://github.com/MinecordNetwork/Website] - [Бърз старт |quickstart:]
    -Видео +Видеа ----- -В канала "Nette Framework YouTube":https://www.youtube.com/user/NetteFramework можете да намерите стотици записи от Posobota и видеоклипове за Nette под един покрив. +Стотици записи от Posledních sobot и видеа за Nette можете да намерите под един покрив в "Youtube канала на Nette Framework":https://www.youtube.com/user/NetteFramework.
    - -{{sitename: Най-добри практики}} -{{leftbar: www:@menu-common}} diff --git a/best-practices/bg/@meta.texy b/best-practices/bg/@meta.texy new file mode 100644 index 0000000000..dc4e6c5b2b --- /dev/null +++ b/best-practices/bg/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Ръководства и процедури}} +{{leftbar: www:@menu-common}} diff --git a/best-practices/bg/attribute-requires.texy b/best-practices/bg/attribute-requires.texy new file mode 100644 index 0000000000..c779502d44 --- /dev/null +++ b/best-practices/bg/attribute-requires.texy @@ -0,0 +1,177 @@ +Как да използваме атрибута `#[Requires]` +**************************************** + +.[perex] +Когато пишете уеб приложение, често се сблъсквате с необходимостта да ограничите достъпа до определени части от вашето приложение. Може би искате някои заявки да могат да изпращат данни само чрез формуляр (т.е. с метод POST), или да бъдат достъпни само за AJAX извиквания. В Nette Framework 3.2 се появи нов инструмент, който ви позволява да настроите такива ограничения много елегантно и прегледно: атрибутът `#[Requires]`. + +Атрибутът е специална маркировка в PHP, която добавяте преди дефиницията на клас или метод. Тъй като всъщност е клас, за да работят следващите примери, е необходимо да се посочи клаузата use: + +```php +use Nette\Application\Attributes\Requires; +``` + +Атрибутът `#[Requires]` можете да използвате при самия клас на презентера, както и на тези методи: + +- `action()` +- `render()` +- `handle()` +- `createComponent()` + +Последните два метода се отнасят и до компоненти, т.е. атрибутът можете да използвате и при тях. + +Ако не са изпълнени условията, които атрибутът посочва, ще се предизвика HTTP грешка 4xx. + + +Методи HTTP +----------- + +Можете да специфицирате кои HTTP методи (като GET, POST и т.н.) са разрешени за достъп. Например, ако искате да разрешите достъп само чрез изпращане на формуляр, настройте: + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +Защо трябва да използвате POST вместо GET за действия, променящи състоянието, и как да го направите? [Прочетете ръководството |post-links]. + +Можете да посочите метод или масив от методи. Специален случай е стойността `'*'`, която разрешава всички методи, което стандартно презентерите [от съображения за сигурност не позволяват |application:presenters#Проверка на HTTP метода]. + + +AJAX извикване +-------------- + +Ако искате презентерът или методът да бъдат достъпни само за AJAX заявки, използвайте: + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Същият произход +--------------- + +За повишаване на сигурността можете да изисквате заявката да бъде направена от същия домейн. С това предотвратявате [уязвимостта CSRF |nette:vulnerability-protection#Cross-Site Request Forgery CSRF]: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +При методите `handle()` достъпът от същия домейн се изисква автоматично. Така че, ако обратно искате да разрешите достъп от всеки домейн, посочете: + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Достъп чрез forward +------------------- + +Понякога е полезно да се ограничи достъпът до презентера така, че да бъде достъпен само непряко, например с използването на метода `forward()` или `switch()` от друг презентер. Така например се защитават error-presenter-ите, за да не могат да бъдат извикани от URL: + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +На практика често е необходимо да се маркират определени views, до които може да се стигне едва въз основа на логиката в презентера. Тоест отново, за да не могат да бъдат отворени директно: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = $this->facade->getProduct($id); + if (!$product) { + $this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Конкретни действия +------------------ + +Можете също така да ограничите, че определен код, например създаване на компонент, ще бъде достъпен само за специфични действия в презентера: + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +В случай на едно действие не е необходимо да се записва масив: `#[Requires(actions: 'default')]` + + +Собствени атрибути +------------------ + +Ако искате да използвате атрибута `#[Requires]` многократно със същите настройки, можете да си създадете собствен атрибут, който ще наследява `#[Requires]` и ще го настрои според нуждите. + +Например `#[SingleAction]` ще позволи достъп само чрез действието `default`: + +```php +#[\Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Или `#[RestMethods]` ще позволи достъп чрез всички HTTP методи, използвани за REST API: + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Заключение +---------- + +Атрибутът `#[Requires]` ви дава голяма гъвкавост и контрол върху това как са достъпни вашите уеб страници. С помощта на прости, но мощни правила можете да повишите сигурността и правилното функциониране на вашето приложение. Както виждате, използването на атрибути в Nette може не само да улесни вашата работа, но и да я обезопаси. diff --git a/best-practices/bg/composer.texy b/best-practices/bg/composer.texy index b8a26ebb20..2346c9485f 100644 --- a/best-practices/bg/composer.texy +++ b/best-practices/bg/composer.texy @@ -1,44 +1,44 @@ -Съвети за използване на Composer -******************************** +Composer: съвети за употреба +****************************
    -Composer е инструмент за управление на зависимостите в PHP. Тя ви позволява да декларирате библиотеките, от които зависи проектът ви, и ще ги инсталира и актуализира вместо вас. Научаваме: +Composer е инструмент за управление на зависимости в PHP. Позволява ни да изброим библиотеките, от които зависи нашият проект, и ще ги инсталира и актуализира вместо нас. Ще покажем: -- как да инсталирате Composer -- да го използвате в нов или съществуващ проект +- как да инсталираме Composer +- неговото използване в нов или съществуващ проект
    -Инсталиране на .[#toc-installation] -=================================== +Инсталация +========== -Composer е изпълним файл `.phar`, който можете да изтеглите и инсталирате, както следва +Composer е изпълним `.phar` файл, който изтегляте и инсталирате по следния начин: -Windows .[#toc-windows] ------------------------ +Windows +------- Използвайте официалния инсталатор [Composer-Setup.exe |https://getcomposer.org/Composer-Setup.exe]. -Linux, MacOS .[#toc-linux-macos] --------------------------------- +Linux, macOS +------------ -Необходими са ви само 4 команди, които можете да копирате от [тази страница |https://getcomposer.org/download/]. +Достатъчни са 4 команди, които копирайте от [тази страница |https://getcomposer.org/download/]. -Освен това, когато се копира в папка, разположена в системата `PATH`, Composer става глобално достъпен: +Освен това, като го поставите в папка, която е в системния `PATH`, Composer става достъпен глобално: ```shell $ mv ./composer.phar ~/bin/composer # или /usr/local/bin/composer ``` -Използване в съществуващ проект .[#toc-use-in-project] -====================================================== +Използване в проект +=================== -За да започнете да използвате Composer в проекта си, се нуждаете само от файла `composer.json`. Този файл описва зависимостите на вашия проект и може да съдържа други метаданни. Най-простият `composer.json` може да изглежда по следния начин: +За да можем да започнем да използваме Composer в нашия проект, се нуждаем само от файл `composer.json`. Той описва зависимостите на нашия проект и може също да съдържа други метаданни. Основният `composer.json` следователно може да изглежда така: ```js { @@ -48,17 +48,17 @@ $ mv ./composer.phar ~/bin/composer # или /usr/local/bin/composer } ``` -Тук казваме, че нашето приложение (или библиотека) зависи от пакета `nette/database` (името на пакета се състои от името на доставчика и името на проекта) и се нуждае от версия, която отговаря на ограничението `^3.0`. +Тук казваме, че нашето приложение (или библиотека) изисква пакета `nette/database` (името на пакета се състои от името на организацията и името на проекта) и иска версия, която отговаря на условието `^3.0` (т.е. най-новата версия 3). -Така че, когато имаме файла `composer.json` в корена на проекта и стартираме +Имаме следователно в корена на проекта файл `composer.json` и стартираме инсталацията: ```shell composer update ``` -Composer ще зареди изходните файлове на Nette в директорията `vendor`. Създава се и файл `composer.lock`, който съдържа информация за това кои версии на библиотеките са инсталирани. +Composer ще изтегли Nette Database в папката `vendor/`. Освен това ще създаде файл `composer.lock`, който съдържа информация за това кои версии на библиотеките точно е инсталирал. -Composer генерира файла `vendor/autoload.php`. Можете просто да включите този файл и да започнете да използвате класовете, които тези библиотеки предоставят, без да е необходима допълнителна работа: +Composer генерира файл `vendor/autoload.php`, който можем лесно да включим и да започнем да използваме библиотеките без никаква друга работа: ```php require __DIR__ . '/vendor/autoload.php'; @@ -67,52 +67,52 @@ $db = new Nette\Database\Connection('sqlite::memory:'); ``` -Актуализиране на пакетите до най-новите версии .[#toc-update-packages-to-the-latest-versions] -============================================================================================= +Актуализация на пакетите до най-новите версии +============================================= -За да обновите всички използвани пакети до най-новата версия в съответствие с ограниченията на версиите, определени в `composer.json`, използвайте командата `composer update`. Например зависимостта `"nette/database": "^3.0"` ще инсталира най-новата версия 3.x.x, но не и версия 4. +Актуализацията на използваните библиотеки до най-новите версии според условията, дефинирани в `composer.json`, се извършва от командата `composer update`. Напр. при зависимост `"nette/database": "^3.0"` ще инсталира най-новата версия 3.x.x, но не и версия 4. -За да актуализирате ограниченията на версията във файла `composer.json` например до "nette/database": "^4.1"`, для установки последней версии, используйте команду `composer require nette/database`. +За актуализация на условията във файла `composer.json`, например на `"nette/database": "^4.1"`, за да може да се инсталира най-новата версия, използвайте командата `composer require nette/database`. -За да актуализирате всички използвани пакети Nette, трябва да ги изпишете в командния ред, напр: +За актуализация на всички използвани пакети на Nette би било необходимо всички те да се изброят в командния ред, напр.: ```shell composer require nette/application nette/forms latte/latte tracy/tracy ... ``` -Това не е практично. Затова използвайте прост скрипт "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff, който ще направи това вместо вас: +Което е непрактично. Затова използвайте простия скрипт "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff, който ще го направи вместо вас: ```shell php composer-frontline.php ``` -Създаване на нов проект .[#toc-creating-new-project] -==================================================== +Създаване на нов проект +======================= -Нов проект на Nette може да бъде създаден чрез изпълнение на проста команда: +Нов проект на Nette създавате с една-единствена команда: ```shell -composer create-project nette/web-project name-of-the-project +composer create-project nette/web-project име-на-проекта ``` -Вместо `name-of-the-project` посочете името на директорията на проекта и изпълнете командата. Composer ще изтегли хранилището `nette/web-project` от GitHub, което вече съдържа файла `composer.json`, и веднага след това ще инсталира самата рамка Nette. Остава само да [проверите разрешенията за запис на |nette:troubleshooting#Setting-Directory-Permissions] директориите `temp/` и `log/`, и сте готови да започнете работа. +Като `име-на-проекта` въведете името на директорията за вашия проект и потвърдете. Composer ще изтегли хранилището `nette/web-project` от GitHub, което вече съдържа файл `composer.json`, и веднага след това Nette Framework. Трябва вече да е достатъчно само да [настроите правата |nette:troubleshooting#Настройка на правата на директориите] за запис в папките `temp/` и `log/` и проектът трябва да оживее. -Ако знаете на каква версия на PHP ще бъде хостван проектът, не забравяйте да [я настро |#PHP Version]ите. +Ако знаете на коя версия на PHP ще бъде хостван проектът, не забравяйте [да я настроите |#Версия на PHP]. -Версия на PHP .[#toc-php-version] -================================= +Версия на PHP +============= -Composer винаги инсталира версиите на пакетите, които са съвместими с версията на PHP, която използвате в момента (или по-скоро с версията на PHP, използвана в командния ред при стартиране на Composer). Вероятно това не е същата версия, която използва вашият уеб хост. Ето защо е много важно да добавите информация за версията на PHP на вашия хостинг във файла `composer.json`. След това ще бъдат инсталирани само версии на пакети, съвместими с хоста. +Composer винаги инсталира тези версии на пакетите, които са съвместими с версията на PHP, която в момента използвате (по-точно с версията на PHP, използвана в командния ред при стартиране на Composer). Което обаче най-вероятно не е същата версия, която използва вашият хостинг. Затова е много важно да добавите в файла `composer.json` информация за версията на PHP на хостинга. След това ще се инсталират само версии на пакетите, съвместими с хостинга. -Например, за да настроите проекта да работи с PHP 8.2.3, използвайте командата: +Това, че проектът ще работи например на PHP 8.2.3, настройваме с командата: ```shell composer config platform.php 8.2.3 ``` -По този начин версията се записва във файла `composer.json`: +Така версията се записва във файла `composer.json`: ```js { @@ -124,8 +124,7 @@ composer config platform.php 8.2.3 } ``` -Номерът на версията на PHP обаче е посочен и на друго място във файла, в раздела `require`. Докато първият номер определя версията, за която ще бъдат инсталирани пакетите, вторият номер казва за каква версия е написано самото приложение. -(Разбира се, няма смисъл тези версии да се различават, така че двойното вписване е излишно.) Тази версия се задава с командата: +Въпреки това, номерът на версията на PHP се посочва и на друго място във файла, а именно в секцията `require`. Докато първото число определя за коя версия ще се инсталират пакетите, второто число казва за коя версия е написано самото приложение. И според него например PhpStorm настройва *PHP language level*. (Разбира се, няма смисъл тези версии да се различават, така че двойният запис е недомислица.) Тази версия настройвате с командата: ```shell composer require php 8.2.3 --no-update @@ -142,66 +141,72 @@ composer require php 8.2.3 --no-update ``` -Фалшиви доклади .[#toc-false-reports] -===================================== +Игнориране на версията на PHP +============================= + +Пакетите обикновено имат посочена както най-ниската версия на PHP, с която са съвместими, така и най-високата, с която са тествани. Ако се готвите да използвате версия на PHP още по-нова, например с цел тестване, Composer ще откаже да инсталира такъв пакет. Решението е опцията `--ignore-platform-req=php+`, която кара Composer да игнорира горните граници на изискваната версия на PHP. + -При надграждане на пакети или промяна на номера на версиите се случват конфликти. Един пакет има изисквания, които са в конфликт с друг и т.н. Понякога обаче Composer отпечатва фалшиви съобщения. Той съобщава за конфликт, който в действителност не съществува. В този случай помага да изтриете файла `composer.lock` и да опитате отново. +Фалшиви съобщения +================= -Ако съобщението за грешка продължава, то е мислено сериозно и трябва да прочетете от него какво и как да промените. +При надграждане на пакети или промени в номерата на версиите се случва да възникне конфликт. Един пакет има изисквания, които са в противоречие с друг и подобни. Composer обаче понякога изписва фалшиви съобщения. Съобщава за конфликт, който реално не съществува. В такъв случай помага да се изтрие файлът `composer.lock` и да се опита отново. +Ако съобщението за грешка продължава, тогава е сериозно и трябва да се разчете от него какво и как да се промени. -Packagist.org - глобално хранилище .[#toc-packagist-org-global-repository] -========================================================================== -[Packagist |https://packagist.org] е основното хранилище на пакети, в което Composer се опитва да търси пакети, освен ако не е посочено друго. Тук можете да публикувате и свои собствени пакети. +Packagist.org - централно хранилище +=================================== + +[Packagist |https://packagist.org] е основното хранилище, в което Composer се опитва да търси пакети, ако не му кажем друго. Тук можем да публикуваме и собствени пакети. -Какво да правим, ако нямаме нужда от централно хранилище .[#toc-what-if-we-don-t-want-the-central-repository] -------------------------------------------------------------------------------------------------------------- +Какво, ако не искаме да използваме централното хранилище? +--------------------------------------------------------- -Ако компанията ни има вътрешни приложения или библиотеки, които не могат да бъдат публично хоствани в Packagist, можем да създадем собствени хранилища за тези проекти. +Ако имаме вътрешнофирмени приложения, които просто не можем да хостваме публично, тогава ще си създадем фирмено хранилище за тях. -Прочетете повече за хранилищата в [официалната документация |https://getcomposer.org/doc/05-repositories.md#Репозитории]. +Повече по темата за хранилищата [в официалната документация |https://getcomposer.org/doc/05-repositories.md#repositories]. -Автоматично зареждане .[#toc-autoloading] -========================================= +Autoloading +=========== -Ключова характеристика на Composer е, че той предоставя автозареждаща програма за всички инсталируеми класове, която стартирате, като включите файла `vendor/autoload.php`. +Ключова характеристика на Composer е, че предоставя autoloading за всички инсталирани от него класове, който стартирате, като включите файла `vendor/autoload.php`. -Възможно е обаче да използвате Composer и за зареждане на други класове извън папката `vendor`. Първият вариант е да позволите на Composer да сканира определени папки и подпапки, да намери всички класове и да ги включи в автоматичния модул. За тази цел задайте `autoload > classmap` във файла `composer.json`: +Въпреки това е възможно да използвате Composer и за зареждане на други класове и извън папката `vendor`. Първата възможност е да оставите Composer да претърси дефинираните папки и подпапки, да намери всички класове и да ги включи в autoloader-а. Това постигате, като настроите `autoload > classmap` в `composer.json`: ```js { "autoload": { "classmap": [ - "src/", # включает папку src/ со всеми вложенными директориями + "src/", # включва папката src/ и нейните подпапки ] } } ``` -Впоследствие трябва да стартирате командата `composer dumpautoload` при всяка промяна и да позволите на таблиците на автоматичния модул да се възстановят. Това е изключително неудобно и е много по-добре да оставите тази задача на [RobotLoader |robot-loader:], който върши същата работа автоматично във фонов режим и много по-бързо. +След това е необходимо при всяка промяна да се стартира командата `composer dumpautoload` и да се оставят autoloading таблиците да се прегенерират. Това е изключително неудобно и далеч по-добре е тази задача да се повери на [RobotLoader|robot-loader:], който извършва същата дейност автоматично във фонов режим и много по-бързо. -Вторият вариант е да следвате [PSR-4 |https://www.php-fig.org/psr/psr-4/]. Казано по-просто, това е система, в която пространствата от имена и имената на класовете съответстват на структурата на директориите и имената на файловете, т.е. `App\Router\RouterFactory` се намира в `/path/to/App/Router/RouterFactory.php`. Примерна конфигурация: +Втората възможност е да се спазва [PSR-4|https://www.php-fig.org/psr/psr-4/]. Опростено казано, става въпрос за система, при която именните пространства и имената на класовете съответстват на директорийната структура и имената на файловете, т.е. напр. `App\Core\RouterFactory` ще бъде във файла `/path/to/App/Core/RouterFactory.php`. Пример за конфигурация: ```js { "autoload": { "psr-4": { - "App\\": "app/" # пространство имён App\ указывает на директорию app/ + "App\\": "app/" # именното пространство App\ е в директорията app/ } } } ``` -Вижте [документацията на Composer |https://getcomposer.org/doc/04-schema.md#PSR-4] за това как точно да конфигурирате това поведение. +Как точно да конфигурирате поведението ще научите в [документацията на Composer|https://getcomposer.org/doc/04-schema.md#psr-4]. -Тестване на нови версии .[#toc-testing-new-versions] -==================================================== +Тестване на нови версии +======================= -Искате да тествате нова версия на даден пакет. Как го правите? Първо, добавете тази двойка опции към `composer.json`, която ще ви позволи да инсталирате версии на пакети за разработка, но ще го направи само ако няма стабилна версия, която да отговаря на изискванията: +Искате да тествате нова разработваща се версия на пакет. Как да го направите? Първо в файла `composer.json` добавете тази двойка опции, която позволява инсталиране на разработващи се версии на пакети, но прибягва до това само в случай, че не съществува никаква комбинация от стабилни версии, която да удовлетворява изискванията: ```js { @@ -210,9 +215,9 @@ Packagist.org - глобално хранилище .[#toc-packagist-org-global- } ``` -Препоръчваме също така да изтриете файла `composer.lock`, защото понякога Composer неразбираемо отказва да се инсталира и това ще реши проблема. +Освен това препоръчваме да изтриете файла `composer.lock`, понякога Composer необяснимо отказва инсталацията и това решава проблема. -Да предположим, че пакетът е `nette/utils`, а новата версия е 4.0. Инсталирате го с помощта на командата: +Да кажем, че става въпрос за пакет `nette/utils` и новата версия има номер 4.0. Инсталирате я с командата: ```shell composer require nette/utils:4.0.x-dev @@ -224,20 +229,19 @@ composer require nette/utils:4.0.x-dev composer require nette/utils:4.0.0-RC2 ``` -Ако друг пакет зависи от библиотеката и е заключен към по-стара версия (напр. `^3.1`), би било идеално пакетът да се актуализира, за да работи с новата версия. -Ако обаче искате просто да заобиколите ограничението и да накарате Composer да инсталира версията за разработка и да се преструва, че е по-стара версия (например 3.1.6), можете да използвате ключовата дума `as`: +Но ако от библиотеката зависи друг пакет, който е заключен на по-стара версия (напр. `^3.1`), тогава е идеално пакетът да се актуализира, за да работи с новата версия. Ако обаче искате само да заобиколите ограничението и да принудите Composer да инсталира разработващата се версия и да се преструва, че става въпрос за по-стара версия (напр. 3.1.6), можете да използвате ключовата дума `as`: ```shell composer require nette/utils "4.0.x-dev as 3.1.6" ``` -Команди за повикване .[#toc-calling-commands] -============================================= +Извикване на команди +==================== -Можете да извиквате собствени персонализирани команди и скриптове чрез Composer, сякаш те са местни команди на Composer. Не е необходимо в тази папка да се посочват скриптове, разположени в папката `vendor/bin`. +Чрез Composer могат да се извикват собствени предварително подготвени команди и скриптове, сякаш става въпрос за нативни команди на Composer. При скриптове, които се намират в папката `vendor/bin`, не е необходимо тази папка да се посочва. -Като пример дефинираме скрипт във файла `composer.json`, който [Nette Tester |tester:] използва за изпълнение на тестове: +Като пример ще дефинираме във файла `composer.json` скрипт, който с помощта на [Nette Tester|tester:] стартира тестове: ```js { @@ -247,19 +251,19 @@ composer require nette/utils "4.0.x-dev as 3.1.6" } ``` -След това стартираме тестовете, като използваме `composer tester`. Можем да извикаме командата, дори ако не се намираме в главната папка на проекта, а в поддиректория. +Тестовете след това стартираме с помощта на `composer tester`. Командата можем да извикаме и в случай, че не сме в коренната папка на проекта, а в някоя поддиректория. -Изпращане на благодарствена бележка .[#toc-send-thanks] -======================================================= +Изпратете благодарност +====================== -Ще ви покажем един трик, с който ще зарадвате авторите на софтуер с отворен код. Можете лесно да присвоите звезда в GitHub на библиотеките, които използват вашия проект. Просто инсталирайте библиотеката `symfony/thanks`: +Ще ви покажем трик, с който ще зарадвате авторите на open source. По прост начин ще дадете звездичка в GitHub на библиотеките, които вашият проект използва. Достатъчно е да инсталирате библиотеката `symfony/thanks`: ```shell composer global require symfony/thanks ``` -След това въведете: +И след това да стартирате: ```shell composer thanks @@ -268,13 +272,11 @@ composer thanks Опитайте! -Конфигурация .[#toc-configuration] -================================== +Конфигурация +============ -Composer е тясно интегриран с инструмента за контрол на версиите [Git |https://git-scm.com]. Ако не използвате Git, трябва да информирате Composer: +Composer е тясно свързан с инструмента за версиониране [Git |https://git-scm.com]. Ако не го имате инсталиран, трябва да кажете на Composer да не го използва: ```shell composer -g config preferred-install dist ``` - -{{sitename: Най-добри практики}} diff --git a/best-practices/bg/creating-editing-form.texy b/best-practices/bg/creating-editing-form.texy index 19fc660421..5f4f84a06c 100644 --- a/best-practices/bg/creating-editing-form.texy +++ b/best-practices/bg/creating-editing-form.texy @@ -1,16 +1,16 @@ -Формуляр за създаване и редактиране на запис -******************************************** +Форма за създаване и редактиране на запис +***************************************** .[perex] -Как правилно да въведем добавянето и редактирането на запис в Nette, като използваме една и съща форма и за двете? +Как правилно да се реализира добавяне и редактиране на запис в Nette, като се използва една и съща форма и за двете? -В много случаи формулярите за добавяне и редактиране на запис са едни и същи, като се различават само по етикета на бутона. Ще покажем примери за прости презентатори, в които използваме формуляра първо за добавяне на запис, след това за редактирането му и накрая комбинираме двете решения. +В много случаи формите за добавяне и редактиране на записи са еднакви, като се различават само по етикета на бутона. Ще покажем примери за прости презентери, където ще използваме формата първо за добавяне на запис, след това за редактиране и накрая ще комбинираме двете решения. -Добавяне на запис .[#toc-adding-a-record] ------------------------------------------ +Добавяне на запис +----------------- -Пример за презентатор, използван за добавяне на запис. Обработката на базата данни ще оставим на класа `Facade`, чийто код не е от значение за този пример. +Пример за презентер, използван за добавяне на запис. Ще оставим действителната работа с базата данни на класа `Facade`, чийто код не е от съществено значение за примера. ```php @@ -27,7 +27,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $form = new Form; - // ... добавляем поля форма ... + // ... добавяме полета към формата ... $form->onSuccess[] = [$this, 'recordFormSucceeded']; return $form; @@ -35,7 +35,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter public function recordFormSucceeded(Form $form, array $data): void { - $this->facade->add($data); // добавляем запис в базата данни + $this->facade->add($data); // добавяне на запис в базата данни $this->flashMessage('Успешно добавено'); $this->redirect('...'); } @@ -48,10 +48,10 @@ class RecordPresenter extends Nette\Application\UI\Presenter ``` -Редактиране на запис .[#toc-editing-a-record] ---------------------------------------------- +Редактиране на запис +-------------------- -Сега нека видим как ще изглежда презентаторът, използван за редактиране на записи: +Сега ще покажем как би изглеждал презентер, използван за редактиране на запис: ```php @@ -70,10 +70,10 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $record = $this->facade->get($id); if ( - !$record // проверка на съществуването на записа - || !$this->facade->isEditAllowed(/*...*/) // проверка на правата за достъп + !$record // проверка за съществуване на запис + || !$this->facade->isEditAllowed(/*...*/) // проверка на правата ) { - $this->error(); // Грешка 404 + $this->error(); // грешка 404 } $this->record = $record; @@ -81,32 +81,32 @@ class RecordPresenter extends Nette\Application\UI\Presenter protected function createComponentRecordForm(): Form { - // проверете дали е избрано действието "редактиране + // проверяваме дали действието е 'edit' if ($this->getAction() !== 'edit') { $this->error(); } $form = new Form; - // ... добавяне на полета за формуляри ... + // ... добавяме полета към формата ... - $form->setDefaults($this->record); // задаване на настройки по подразбиране + $form->setDefaults($this->record); // задаване на стойности по подразбиране $form->onSuccess[] = [$this, 'recordFormSucceeded']; return $form; } public function recordFormSucceeded(Form $form, array $data): void { - $this->facade->update($this->record->id, $data); // актуализиране на записа + $this->facade->update($this->record->id, $data); // актуализиране на запис $this->flashMessage('Успешно актуализирано'); $this->redirect('...'); } } ``` -В метода *action*, който се извиква в самото начало на жизнения цикъл на [презентатора |application:presenters#Life-Cycle-of-Presenter], се проверява съществуването на записа и разрешението на потребителя да го редактира. +В метода *action*, който се стартира в самото начало на [жизнения цикъл на презентера |application:presenters#Жизнен цикъл на презентера], проверяваме съществуването на записа и правата на потребителя да го редактира. -Съхраняваме записа в свойството `$record`, така че той да е достъпен в метода `createComponentRecordForm()` за задаване на стойности по подразбиране и `recordFormSucceeded()` за идентификатор. Алтернативно решение би било да се зададат стойностите по подразбиране директно в `actionEdit()` и стойността на ID, която е част от URL адреса и се извлича с `getParameter('id')`: +Запазваме записа в свойството `$record`, за да го имаме на разположение в метода `createComponentRecordForm()` за задаване на стойности по подразбиране и в `recordFormSucceeded()` заради ID. Алтернативно решение би било да зададем стойностите по подразбиране директно в `actionEdit()` и да получим стойността на ID, която е част от URL адреса, като използваме `getParameter('id')`: ```php @@ -114,12 +114,12 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $record = $this->facade->get($id); if ( - // проверка на съществуването и правата на достъп + // проверка за съществуване и проверка на правата ) { $this->error(); } - // задаване на стойности по подразбиране за полетата на формуляра + // задаване на стойности по подразбиране на формата $this->getComponent('recordForm') ->setDefaults($record); } @@ -133,13 +133,13 @@ class RecordPresenter extends Nette\Application\UI\Presenter } ``` -Въпреки това, и това би трябвало да е **най-важният извод от целия код**, трябва да се уверим, че действието наистина е `edit`, когато създаваме формата. Защото в противен случай валидирането в метода `actionEdit()` изобщо няма да се случи! +Въпреки това, и това трябва да бъде **най-важният извод от целия код**, трябва да се уверим при създаването на формата, че действието наистина е `edit`. В противен случай проверката в метода `actionEdit()` изобщо няма да се извърши! -Една и съща форма за добавяне и редактиране .[#toc-same-form-for-adding-and-editing] ------------------------------------------------------------------------------------- +Същата форма за добавяне и редактиране +-------------------------------------- -Сега ще обединим двамата водещи в един. Или можем да разграничим кое действие е включено в метода `createComponentRecordForm()` и да конфигурираме формуляра по съответния начин, или можем да го оставим директно на методите на действие и да се отървем от условието: +И сега комбинираме двата презентера в един. Можем или да разграничим в метода `createComponentRecordForm()` кое е действието и да конфигурираме формата съответно, или можем да го оставим директно на action-методите и да се отървем от условието: ```php @@ -160,42 +160,42 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $record = $this->facade->get($id); if ( - !$record // проверка на съществуването на записа - || !$this->facade->isEditAllowed(/*...*/) // проверка на правата за достъп + !$record // проверка за съществуване на запис + || !$this->facade->isEditAllowed(/*...*/) // проверка на правата ) { - $this->error(); // Грешка 404 + $this->error(); // грешка 404 } $form = $this->getComponent('recordForm'); - $form->setDefaults($record); // задаване на настройки по подразбиране + $form->setDefaults($record); // задаване на стойности по подразбиране $form->onSuccess[] = [$this, 'editingFormSucceeded']; } protected function createComponentRecordForm(): Form { - // проверява дали текущото действие е 'add' или 'edit' + // проверяваме дали действието е 'add' или 'edit' if (!in_array($this->getAction(), ['add', 'edit'])) { $this->error(); } $form = new Form; - // ... добавяне на полета за формуляри ... + // ... добавяме полета към формата ... return $form; } public function addingFormSucceeded(Form $form, array $data): void { - $this->facade->add($data); // добавете този запис в базата данни - $this->flashMessage('Successfully added'); + $this->facade->add($data); // добавяне на запис в базата данни + $this->flashMessage('Успешно добавено'); $this->redirect('...'); } public function editingFormSucceeded(Form $form, array $data): void { $id = (int) $this->getParameter('id'); - $this->facade->update($id, $data); // актуализиране на записа + $this->facade->update($id, $data); // актуализиране на запис $this->flashMessage('Успешно актуализирано'); $this->redirect('...'); } @@ -203,4 +203,3 @@ class RecordPresenter extends Nette\Application\UI\Presenter ``` {{priority: -1}} -{{sitename: Най-добри практики}} diff --git a/best-practices/bg/dynamic-snippets.texy b/best-practices/bg/dynamic-snippets.texy index b927d707e4..c8baa990be 100644 --- a/best-practices/bg/dynamic-snippets.texy +++ b/best-practices/bg/dynamic-snippets.texy @@ -1,7 +1,7 @@ -Динамични фрагменти -******************* +Динамични снипети +***************** -Много често при разработването на приложения е необходимо да се извършват AJAX операции, например върху отделни редове на таблици или елементи на списъци. Като пример можем да изберем списък със статии, като позволим на влезлия потребител да избере "харесвам/не харесвам" за всяка от тях. Кодът за водещия и съответния шаблон без AJAX би изглеждал по следния начин (изброявам най-важните фрагменти, кодът предполага услуга за маркиране на оценки и извличане на колекция от статии - конкретната реализация не е важна за целите на това ръководство): +Доста често при разработването на приложения възниква необходимостта от извършване на AJAX операции, например върху отделни редове на таблица или елементи от списък. Като пример можем да вземем списък със статии, като за всяка от тях ще позволим на влезлия потребител да избере оценка "харесвам/не харесвам". Кодът на презентера и съответният шаблон без AJAX ще изглеждат приблизително по следния начин (представям най-важните части, кодът предполага съществуването на сървис за маркиране на оценките и получаване на колекция от статии - конкретната реализация не е важна за целите на това ръководство): ```php public function handleLike(int $articleId): void @@ -24,26 +24,26 @@ public function handleUnlike(int $articleId): void

    {$article->title}

    {$article->content}
    {if !$article->liked} - Мне нравится + харесвам {else} - Мне не нравится + вече не ми харесва {/if}
    ``` -Ajaxisation .[#toc-ajaxization] -=============================== +Ajaxизация +========== -Сега нека въведем AJAX в това просто приложение. Промяната на класацията на дадена статия не е достатъчно важна, за да изисква HTTP заявка с пренасочване, така че в идеалния случай това трябва да се прави с AJAX във фонов режим. Ще използваме [скрипта на манипулатора от добавките |https://componette.org/vojtech-dobes/nette.ajax.js/] с обичайната конвенция, че връзките AJAX имат CSS клас `ajax`. +Нека сега оборудваме това просто приложение с AJAX. Промяната на оценката на статията не е толкова важна, че да изисква пренасочване, затова в идеалния случай тя трябва да се извършва чрез AJAX във фонов режим. Ще използваме [обслужващия скрипт от добавките |application:ajax#Naja] с обичайната конвенция, че AJAX връзките имат CSS клас `ajax`. -Как точно да го направим обаче? Nette предлага 2 начина: метода на динамичните фрагменти и метода на компонентите. И двете имат своите плюсове и минуси, затова ще ги покажем последователно. +Но как да го направим конкретно? Nette предлага 2 начина: пътя на т.нар. динамични снипети и пътя на компонентите. И двата имат своите плюсове и минуси, затова ще ги покажем един по един. -Начинът за динамичен фрагмент .[#toc-the-dynamic-snippets-way] -============================================================== +Пътят на динамичните снипети +============================ -В терминологията на Latte динамичният фрагмент е специален случай на използване на тага `{snippet}`, когато в името на фрагмента се използва променлива. Такъв фрагмент не може да бъде намерен просто навсякъде в шаблона - той трябва да бъде обвит от статичен фрагмент, т.е. обикновен фрагмент, или вътре в `{snippetArea}`. Можем да променим шаблона си по следния начин. +Динамичен снипет в терминологията на Latte означава специфичен случай на използване на макроса `{snippet}`, при който в името на снипета се използва променлива. Такъв снипет не може да се намира навсякъде в шаблона - той трябва да бъде обвит в статичен снипет, т.е. обикновен, или вътре в `{snippetArea}`. Можем да модифицираме нашия шаблон по следния начин. ```latte @@ -53,18 +53,18 @@ Ajaxisation .[#toc-ajaxization]
    {$article->content}
    {snippet article-{$article->id}} {if !$article->liked} - Мне нравится + харесвам {else} - Мне не нравится + вече не ми харесва {/if} {/snippet}
    {/snippet} ``` -Всяка статия вече дефинира един фрагмент, който има идентификатор на статията в заглавието. След това всички тези фрагменти се обединяват в един фрагмент, наречен `articlesContainer`. Ако пропуснем този фрагмент на обвивката, Latte ще ни предупреди за изключение. +Всяка статия сега дефинира един снипет, който има ID на статията в името си. Всички тези снипети след това са обвити заедно в един снипет с име `articlesContainer`. Ако пропуснем този обвиващ снипет, Latte ще ни предупреди с изключение. -Остава само да добавите прерисуване на презентатора - просто прерисувайте статичната обвивка. +Остава ни да добавим прерисуването в презентера - достатъчно е да прерисуваме статичната обвивка. ```php public function handleLike(int $articleId): void @@ -72,18 +72,18 @@ public function handleLike(int $articleId): void $this->ratingService->saveLike($articleId, $this->user->id); if ($this->isAjax()) { $this->redrawControl('articlesContainer'); - // $this->redrawControl('article-' . $articleId); -- нет необходимости + // $this->redrawControl('article-' . $articleId); -- не е необходимо } else { $this->redirect('this'); } } ``` -Променете свързания метод `handleUnlike()` по същия начин и AJAX ще работи! +По същия начин модифицираме и сестринския метод `handleUnlike()`, и AJAX работи! -Това решение обаче има и недостатък. Ако разгледаме по-отблизо начина на работа на заявката AJAX, ще установим, че въпреки че приложението изглежда ефективно отвън (връща само един фрагмент за дадена статия), то всъщност показва всички фрагменти на сървъра. Той постави правилния фрагмент в нашия полезен товар и изхвърли останалите (така че съвсем ненужно ги изтегли и от базата данни). +Решението обаче има и една тъмна страна. Ако разгледаме по-подробно как протича AJAX заявката, ще открием, че въпреки че приложението изглежда икономично отвън (връща само един снипет за дадената статия), всъщност на сървъра то е рендирало всички снипети. Желаният снипет е поставен в payload-а, а останалите са изхвърлени (следователно са били извлечени от базата данни напълно ненужно). -За да оптимизираме този процес, се нуждаем от действие, при което да предадем колекцията `$articles` на шаблона (например в метода `renderDefault()`). Ще се възползваме от факта, че обработката на сигнала се извършва преди методите `render`: +За да оптимизираме този процес, ще трябва да се намесим там, където предаваме колекцията `$articles` към шаблона (да речем в метода `renderDefault()`). Ще използваме факта, че обработката на сигналите се извършва преди методите `render`: ```php public function handleLike(int $articleId): void @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connection->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,18 +101,18 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connection->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` -Сега, когато сигналът се обработва, вместо колекция с всички статии, на шаблона се предава само масив с една статия - тази, която искаме да покажем и да изпратим в полезния товар на браузъра. По този начин `{foreach}` ще бъде изпълнен само веднъж и няма да бъдат показвани допълнителни фрагменти. +Сега, при обработката на сигнала, вместо колекция с всички статии, към шаблона се предава само масив с една статия - тази, която искаме да рендираме и изпратим в payload-а към браузъра. Следователно `{foreach}` ще се изпълни само веднъж и няма да се рендират допълнителни снипети. -Метод на компонентите .[#toc-component-way] -=========================================== +Пътят на компонентите +===================== -Напълно различно решение използва различен подход за избягване на динамичните фрагменти. Трикът е да се премести цялата логика в отделен компонент - от сега нататък нямаме презентатор, който да се грижи за въвеждането на оценката, а специален `LikeControl`. Класът ще изглежда по следния начин (освен това ще съдържа и методи `render`, `handleUnlike` и т.н.) +Напълно различен начин на решаване избягва динамичните снипети. Трикът се състои в прехвърлянето на цялата логика в отделен компонент - отсега нататък въвеждането на оценки няма да се обработва от презентера, а от специализирания `LikeControl`. Класът ще изглежда по следния начин (освен това ще съдържа и методите `render`, `handleUnlike` и т.н.): ```php class LikeControl extends Nette\Application\UI\Control @@ -139,26 +139,26 @@ class LikeControl extends Nette\Application\UI\Control ```latte {snippet} {if !$article->liked} - Мне нравится + харесвам {else} - Мне не нравится + вече не ми харесва {/if} {/snippet} ``` -Разбира се, ще променим шаблона на презентацията и ще трябва да добавим фабрика към презентатора. Тъй като ще създаваме компонента толкова пъти, колкото статии получаваме от базата данни, ще използваме класа [Multiplier |application:Multiplier] за тази цел: +Разбира се, шаблонът на изгледа ще се промени и ще трябва да добавим фабрика към презентера. Тъй като ще създадем компонента толкова пъти, колкото статии получим от базата данни, ще използваме класа [application:Multiplier] за неговото "размножаване". ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); } ``` -Шаблонът на изгледа е сведен до необходимия минимум (и е напълно свободен от фрагменти!): +Шаблонът на изгледа се свежда до необходимия минимум (и е напълно лишен от снипети!): ```latte
    @@ -168,7 +168,6 @@ protected function createComponentLikeControl()
    ``` -Почти сме готови: сега приложението ще работи в AJAX. Приложението трябва да бъде оптимизирано и тук, тъй като поради използването на базата данни Nette обработката на сигнала ненужно ще зареди всички статии от базата данни, вместо само една. Предимството обаче е, че няма да има рендиране, тъй като всъщност се рендира само нашият компонент. +Почти сме готови: приложението вече ще работи с AJAX. И тук трябва да оптимизираме приложението, защото поради използването на Nette Database, при обработката на сигнала ненужно се зареждат всички статии от базата данни вместо само една. Предимството обаче е, че те няма да бъдат рендирани, тъй като ще се рендира само нашият компонент. {{priority: -1}} -{{sitename: Най-добри практики}} diff --git a/best-practices/bg/editors-and-tools.texy b/best-practices/bg/editors-and-tools.texy index 86ee4a91f8..89751ce259 100644 --- a/best-practices/bg/editors-and-tools.texy +++ b/best-practices/bg/editors-and-tools.texy @@ -1,40 +1,40 @@ -Редактори и инструменти +Редактори & инструменти *********************** .[perex] -Можете да сте опитен програмист, но само с добри инструменти ще станете майстор. В тази глава ще намерите съвети за важни инструменти, редактори и приставки. +Може да сте опитен програмист, но само с добри инструменти ще станете майстор. В тази глава ще намерите съвети за важни инструменти, редактори и плъгини. -Редактор на IDE .[#toc-ide-editor] -================================== +IDE редактор +============ -Силно препоръчваме да използвате пълнофункционална IDE среда за разработка, например PhpStorm, NetBeans, VS Code, а не просто текстов редактор с поддръжка на PHP. Разликата е наистина съществена. Няма причина да се задоволявате с класически редактор с подчертаване на синтаксиса, защото той не достига възможностите на IDE с точно предложение за код, който може да рефакторира кода и др. Някои IDE са платени, а други са безплатни. +Определено препоръчваме да използвате пълнофункционално IDE за разработка, като PhpStorm, NetBeans, VS Code, а не само текстов редактор с поддръжка на PHP. Разликата е наистина съществена. Няма причина да се задоволявате само с редактор, който може да оцветява синтаксиса, но не достига възможностите на водещо IDE, което точно подсказва, следи за грешки, може да рефакторира код и много повече. Някои IDE са платени, други дори безплатни. **NetBeans IDE** има вградена поддръжка за Nette, Latte и NEON. -**PhpStorm**: инсталирайте тези приставки в `Settings > Plugins > Marketplace`: -- Помощни средства за рамката Nette +**PhpStorm**: инсталирайте тези плъгини в `Settings > Plugins > Marketplace` +- Nette framework helpers - Latte -- Поддръжка на NEON -- Тестер на Nette +- NEON support +- Nette Tester -**VS код**: намерете плъгина "Nette Latte + Neon" на пазара. +**VS Code**: намерете плъгина "Nette Latte + Neon" в marketplace. -Също така свържете Трейси с редактора. Когато се покаже страницата с грешки, можете да щракнете върху имената на файловете и те ще се отворят в редактора с курсор на съответния ред. Научете [как да конфигурирате системата |tracy:open-files-in-ide]. +Свържете също Tracy с редактора си. Когато се покаже страница с грешка, ще можете да кликнете върху имената на файловете и те ще се отворят в редактора с курсор на съответния ред. Прочетете [как да конфигурирате системата |tracy:open-files-in-ide]. -PHPStan .[#toc-phpstan] -======================= +PHPStan +======= -PHPStan е инструмент, който открива логически грешки в кода ви, преди да го стартирате. +PHPStan е инструмент, който открива логически грешки в кода, преди да го стартирате. -Инсталирайте го чрез Composer: +Инсталираме го с помощта на Composer: ```shell composer require --dev phpstan/phpstan-nette ``` -Създайте конфигурационен файл в проекта `phpstan.neon`: +Създаваме конфигурационен файл `phpstan.neon` в проекта: ```neon includes: @@ -47,40 +47,38 @@ parameters: level: 5 ``` -И след това го оставете да анализира класовете в папката `app/`: +И след това го оставяме да анализира класовете в папката `app/`: ```shell vendor/bin/phpstan analyse app ``` -Можете да намерите подробна документация директно на уебсайта на [PHPStan |https://phpstan.org]. +Изчерпателна документация можете да намерите директно на [уебсайта на PHPStan |https://phpstan.org]. -Проверка на кода .[#toc-code-checker] -===================================== +Code Checker +============ -[Проверката на кода |code-checker:] проверява и евентуално поправя някои формални грешки в изходния ви код. +[Code Checker|code-checker:] проверява и евентуално коригира някои от формалните грешки във вашия изходен код: -- Премахва [BOM |nette:glossary#bom]. -- Проверява валидността на шаблоните [Latte |latte:]. -- Проверява валидността на `.neon`, `.php` и `.json`. -- проверява за [контролни знаци |nette:glossary#Control-Characters]. -- проверява дали файлът е кодиран в UTF-8. -- проверява дали `/* @annotations */` е изписан правилно (липсва втората звездичка) -- премахва таговете за край на PHP `?>` в PHP файлове -- премахва белите полета и ненужните празни редове от края на файла -- нормализира окончанията на редовете до системните по подразбиране (с `-l`) +- премахва [BOM |nette:glossary#BOM] +- проверява валидността на [Latte |latte:] шаблоните +- проверява валидността на файловете `.neon`, `.php` и `.json` +- проверява за наличие на [контролни знаци |nette:glossary#Контролни знаци] +- проверява дали файлът е кодиран в UTF-8 +- проверява за неправилно записани `/* @anotace */` (липсва звездичка) +- премахва затварящия таг `?>` от PHP файловете +- премахва интервалите в края на реда и ненужните редове в края на файла +- нормализира разделителите на редове до системните (ако посочите опцията `-l`) -Композитор .[#toc-composer] -=========================== +Composer +======== -[Composer |Composer] е инструмент за управление на зависимостите в PHP. Тя ни позволява да декларираме зависимостите на библиотеките и ги инсталира вместо нас в нашия проект. +[Composer |Composer] е инструмент за управление на зависимости в PHP. Позволява ни да декларираме произволно сложни зависимости на отделни библиотеки и след това ги инсталира вместо нас в нашия проект. -Скрипт за проверка на изискванията .[#toc-requirements-checker] -=============================================================== +Requirements Checker +==================== -Това беше инструмент, който тестваше средата на сървъра и съобщаваше дали (и до каква степен) рамката може да се използва. Nette вече може да се използва на всеки сървър, на който е инсталирана минималната необходима версия на PHP. - -{{sitename: Най-добри практики}} +Това беше инструмент, който тестваше средата за изпълнение на сървъра и информираше дали (и до каква степен) е възможно да се използва framework-ът. В момента Nette може да се използва на всеки сървър, който има минималната изисквана версия на PHP. diff --git a/best-practices/bg/form-reuse.texy b/best-practices/bg/form-reuse.texy index 9470a63875..1af34cdd6f 100644 --- a/best-practices/bg/form-reuse.texy +++ b/best-practices/bg/form-reuse.texy @@ -1,16 +1,16 @@ -Повторно използване на формуляри на различни места -************************************************** +Повторно използване на форми на няколко места +********************************************* .[perex] -В Nette имате няколко възможности за повторно използване на една и съща форма на няколко места, без да дублирате код. В тази статия ще разгледаме различните решения, включително и тези, които трябва да избягвате. +В Nette имате на разположение няколко опции как да използвате една и съща форма на няколко места и да не дублирате код. В тази статия ще покажем различни решения, включително тези, които трябва да избягвате. -Фабрика за формуляри .[#toc-form-factory] -========================================= +Фабрика за форми +================ -Един от основните подходи за използване на един и същ компонент на няколко места е да се създаде метод или клас, който генерира компонента, и след това да се извика този метод на различни места в приложението. Такъв метод или клас се нарича *фабрика*. Моля, не бъркайте с шаблона за проектиране *фабричен метод*, който описва специфичен начин за използване на фабрики и не е свързан с тази тема. +Един от основните подходи за използване на един и същ компонент на няколко места е създаването на метод или клас, който генерира този компонент, и последващото извикване на този метод на различни места в приложението. Такъв метод или клас се нарича *фабрика*. Моля, не го бъркайте с дизайн патърна *factory method*, който описва специфичен начин за използване на фабрики и не е свързан с тази тема. -Като пример, нека създадем фабрика, която ще изгради форма за редактиране: +Като пример ще създадем фабрика, която ще изгражда форма за редактиране: ```php use Nette\Application\UI\Form; @@ -20,22 +20,22 @@ class FormFactory public function createEditForm(): Form { $form = new Form; - $form->addText('title', 'Title:'); - // тук се добавят допълнителни полета за формуляри - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Заглавие:'); + // тук се добавят други полета на формата + $form->addSubmit('send', 'Изпрати'); return $form; } } ``` -Сега можете да използвате тази фабрика на различни места в приложението си, например в презентатори или компоненти. Това става, като [я заявяваме като зависимост |dependency-injection:passing-dependencies]. Затова първо ще запишем класа в конфигурационния файл: +Сега можете да използвате тази фабрика на различни места във вашето приложение, например в презентери или компоненти. И това става, като я [поискаме като зависимост |dependency-injection:passing-dependencies]. Първо, записваме класа в конфигурационния файл: ```neon services: - FormFactory ``` -И след това ще го използваме в презентатора: +И след това я използваме в презентера: ```php @@ -50,14 +50,14 @@ class MyPresenter extends Nette\Application\UI\Presenter { $form = $this->formFactory->createEditForm(); $form->onSuccess[] = function () { - // обработка на изпратени данни + // обработка на изпратените данни }; return $form; } } ``` -Можете да разширите фабриката за формуляри с допълнителни методи, за да създадете други видове формуляри, подходящи за вашето приложение. И, разбира се, можете да добавите метод, който създава основна форма без елементи, която другите методи ще използват: +Можете да разширите фабриката за форми с допълнителни методи за създаване на други видове форми според нуждите на вашето приложение. И разбира се, можем да добавим и метод, който създава основна форма без елементи, и този метод ще бъде използван от другите методи: ```php class FormFactory @@ -71,21 +71,21 @@ class FormFactory public function createEditForm(): Form { $form = $this->createForm(); - $form->addText('title', 'Title:'); - // тук се добавят допълнителни полета за формуляри - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Заглавие:'); + // тук се добавят други полета на формата + $form->addSubmit('send', 'Изпрати'); return $form; } } ``` -Методът `createForm()` все още не прави нищо полезно, но това бързо ще се промени. +Методът `createForm()` засега не прави нищо полезно, но това бързо ще се промени. -Зависимости на фабриката .[#toc-factory-dependencies] -===================================================== +Зависимости на фабриката +======================== -С течение на времето ще стане ясно, че е необходимо формулярите да бъдат многоезични. Това означава, че трябва да настроим [преводач за |forms:rendering#Translating] всички форми. За да направим това, модифицираме класа `FormFactory`, за да приеме обекта `Translator` като зависимост в конструктора и да го предаде на формата: +С времето ще се окаже, че се нуждаем формите да бъдат многоезични. Това означава, че трябва да зададем т.нар. [translator |forms:rendering#Превод] на всички форми. За тази цел ще модифицираме класа `FormFactory`, така че да приема обект `Translator` като зависимост в конструктора и ще го предадем на формата: ```php use Nette\Localization\Translator; @@ -104,18 +104,17 @@ class FormFactory return $form; } - //... + // ... } ``` -Тъй като методът `createForm()` се извиква и от други методи, които създават конкретни форми, трябва да зададем преводача само в този метод. И сме готови. Не е необходимо да променяме какъвто и да е код на презентатора или компонента, което е чудесно. +Тъй като методът `createForm()` се извиква и от другите методи, създаващи специфични форми, е достатъчно да зададем translator-а само в него. И сме готови. Няма нужда да променяме кода на нито един презентер или компонент, което е страхотно. -Още фабрични класове .[#toc-more-factory-classes] -================================================= +Множество фабрични класове +========================== -Като алтернатива можете да създадете няколко класа за всяка форма, която искате да използвате в приложението си. -Този подход може да увеличи четимостта на кода и да улесни управлението на формулярите. Оставете оригиналния `FormFactory` за създаване само на чиста форма с основна конфигурация (например с поддръжка на превод) и създайте нова фабрика `EditFormFactory` за формата за редактиране. +Алтернативно, можете да създадете множество класове за всяка форма, която искате да използвате във вашето приложение. Този подход може да увеличи четимостта на кода и да улесни управлението на формите. Ще оставим оригиналната `FormFactory` да създава само чиста форма с основна конфигурация (например с поддръжка на преводи) и ще създадем нова фабрика `EditFormFactory` за формата за редактиране. ```php class FormFactory @@ -134,7 +133,7 @@ class FormFactory } -// ✅ използване на състава +// ✅ използване на композиция class EditFormFactory { public function __construct( @@ -145,40 +144,39 @@ class EditFormFactory public function create(): Form { $form = $this->formFactory->create(); - // тук се добавят допълнителни полета на формуляра - $form->addSubmit('send', 'Save'); + // тук се добавят други полета на формата + $form->addSubmit('send', 'Изпрати'); return $form; } } ``` -Много е важно обвързването между класовете `FormFactory` и `EditFormFactory` да се реализира чрез композиция, а не чрез наследяване на обекти: +Много е важно връзката между класовете `FormFactory` и `EditFormFactory` да се реализира чрез [композиция |nette:introduction-to-object-oriented-programming#Композиция], а не чрез [обектно наследяване |nette:introduction-to-object-oriented-programming#Наследяване]: ```php -// ⛔ НЕ! НАСЛЕДСТВОТО НЕ ПРИНАДЛЕЖИ ТУК +// ⛔ НЕ ТАКА! НАСЛЕДЯВАНЕТО НЕ Е ЗА ТУК class EditFormFactory extends FormFactory { public function create(): Form { $form = parent::create(); - $form->addText('title', 'Title:'); - // тук се добавят допълнителни полета на формуляра - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Заглавие:'); + // тук се добавят други полета на формата + $form->addSubmit('send', 'Изпрати'); return $form; } } ``` -Използването на наследяване в този случай би било напълно непродуктивно. Много бързо ще се сблъскате с проблеми. Например, ако искате да добавите параметри към метода `create()`; PHP ще отчете грешка, че сигнатурата му е различна от тази на родителя. -Или при предаване на зависимост на класа `EditFormFactory` чрез конструктора. Това би довело до това, което наричаме " [ад на конструкторите" |dependency-injection:passing-dependencies#Constructor hell]. +Използването на наследяване в този случай би било напълно контрапродуктивно. Много бързо ще се сблъскате с проблеми. Например, в момента, в който искате да добавите параметри към метода `create()`; PHP ще съобщи за грешка, че неговата сигнатура се различава от родителската. Или при предаване на зависимост към класа `EditFormFactory` чрез конструктора. Ще възникне ситуация, която наричаме [constructor hell |dependency-injection:passing-dependencies#Адът на конструктора]. -Като цяло е по-добре да се предпочита композицията пред наследяването. +Като цяло е по-добре да се дава предимство на [композицията пред наследяването |dependency-injection:faq#Защо се предпочита композиция пред наследяването]. -Обработка на формуляри .[#toc-form-handling] -============================================ +Обработка на формата +==================== -Обработчикът на формуляри, който се извиква след успешно изпращане, може също да бъде част от фабричен клас. Той ще работи, като предава изпратените данни на модела за обработка. Той ще предаде всички грешки [обратно към |forms:validation#Processing Errors] формата. Моделът в следващия пример е представен от класа `Facade`: +Обработката на формата, която се извиква след успешно изпращане, също може да бъде част от фабричния клас. Тя ще работи, като предава изпратените данни на модела за обработка. Евентуални грешки [ще предаде обратно |forms:validation#Грешки при обработка] на формата. Моделът в следващия пример е представен от класа `Facade`: ```php class EditFormFactory @@ -192,9 +190,9 @@ class EditFormFactory public function create(): Form { $form = $this->formFactory->create(); - $form->addText('title', 'Title:'); - // тук се добавят допълнителни полета за формуляри - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Заглавие:'); + // тук се добавят други полета на формата + $form->addSubmit('send', 'Изпрати'); $form->onSuccess[] = [$this, 'processForm']; return $form; } @@ -202,7 +200,7 @@ class EditFormFactory public function processForm(Form $form, array $data): void { try { - // обработка на подадените данни + // обработка на изпратените данни $this->facade->process($data); } catch (AnyModelException $e) { @@ -212,7 +210,7 @@ class EditFormFactory } ``` -Нека водещият сам се справи с пренасочването. Той ще добави друг обработващ към събитието `onSuccess`, който ще извърши пренасочването. Това ще позволи формулярът да се използва в различни презентатори, като всеки от тях може да пренасочва към различно място. +Самото пренасочване обаче ще оставим на презентера. Той ще добави към събитието `onSuccess` допълнителен handler, който ще извърши пренасочването. Благодарение на това ще бъде възможно да се използва формата в различни презентери и във всеки да се пренасочва към различно място. ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -226,7 +224,7 @@ class MyPresenter extends Nette\Application\UI\Presenter { $form = $this->formFactory->create(); $form->onSuccess[] = function () { - $this->flashMessage('Záznam byl uložen'); + $this->flashMessage('Записът е запазен'); $this->redirect('Homepage:'); }; return $form; @@ -234,39 +232,38 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Това решение се възползва от свойството на формите, че когато се извика `addError()` на форма или неин елемент, не се извиква следващият обработващ `onSuccess`. +Това решение използва свойството на формите, че когато се извика `addError()` върху формата или неин елемент, следващият handler `onSuccess` вече не се извиква. -Наследяване от класа Form .[#toc-inheriting-from-the-form-class] -================================================================ +Наследяване от класа Form +========================= -Вградената форма не трябва да бъде дете на форма. С други думи, не използвайте това решение: +Изградената форма не трябва да бъде наследник на формата. С други думи, не използвайте това решение: ```php -// ⛔ НЕ! НАСЛЕДСТВОТО НЕ ПРИНАДЛЕЖИ ТУК +// ⛔ НЕ ТАКА! НАСЛЕДЯВАНЕТО НЕ Е ЗА ТУК class EditForm extends Form { public function __construct(Translator $translator) { parent::__construct(); - $form->addText('title', 'Title:'); - // тук се добавят допълнителни полета на формуляра - $form->addSubmit('send', 'Save'); - $form->setTranslator($translator); + $this->addText('title', 'Заглавие:'); + // тук се добавят други полета на формата + $this->addSubmit('send', 'Изпрати'); + $this->setTranslator($translator); } } ``` -Вместо да изграждате формата в конструктора, използвайте фабриката. +Вместо да изграждате формата в конструктора, използвайте фабрика. -Важно е да осъзнаете, че класът `Form` е преди всичко инструмент за сглобяване на формуляр, т.е. конструктор на формуляри. А сглобената форма може да се счита за негов продукт. Продуктът обаче не е специфичен случай на конструктора; между тях няма *има* връзка, която е в основата на наследяването. +Трябва да се осъзнае, че класът `Form` е преди всичко инструмент за изграждане на форма, т.е. *form builder*. А изградената форма може да се разглежда като неин продукт. Но продуктът не е специфичен случай на builder-а, между тях няма връзка *is a*, която е основата на наследяването. -Компонент на формата .[#toc-form-component] -=========================================== +Компонент с форма +================= -Съвсем различен подход е да създадете [компонент |application:components], който включва формуляр. Това дава нови възможности, например да визуализирате формуляра по определен начин, тъй като компонентът включва шаблон. -Или пък могат да се използват сигнали за AJAX комуникация и зареждане на информация във формата, например за подсказване и т.н. +Напълно различен подход представлява създаването на [компонент |application:components], чиято част е форма. Това дава нови възможности, например да се рендира формата по специфичен начин, тъй като компонентът включва и шаблон. Или могат да се използват сигнали за AJAX комуникация и дозареждане на информация във формата, например за подсказки и т.н. ```php @@ -284,9 +281,9 @@ class EditControl extends Nette\Application\UI\Control protected function createComponentForm(): Form { $form = new Form; - $form->addText('title', 'Title:'); - // тук се добавят допълнителни полета за формуляри - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Заглавие:'); + // тук се добавят други полета на формата + $form->addSubmit('send', 'Изпрати'); $form->onSuccess[] = [$this, 'processForm']; return $form; @@ -295,7 +292,7 @@ class EditControl extends Nette\Application\UI\Control public function processForm(Form $form, array $data): void { try { - // обработка на подадените данни + // обработка на изпратените данни $this->facade->process($data); } catch (AnyModelException $e) { @@ -309,7 +306,7 @@ class EditControl extends Nette\Application\UI\Control } ``` -Нека да създадем фабрика, която ще произвежда този компонент. Достатъчно е да [напишем нейния интерфейс |application:components#Components with Dependencies]: +Ще създадем и фабрика, която ще произвежда този компонент. Достатъчно е [да запишем нейния интерфейс |application:components#Компоненти със зависимости]: ```php interface EditControlFactory @@ -318,14 +315,14 @@ interface EditControlFactory } ``` -И да го добавим към конфигурационния файл: +И да добавим в конфигурационния файл: ```neon services: - EditControlFactory ``` -И сега можем да поискаме фабриката и да я използваме в презентатора: +И сега вече можем да поискаме фабриката и да я използваме в презентера: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -335,13 +332,13 @@ class MyPresenter extends Nette\Application\UI\Presenter ) { } - protected function createComponentEditForm(): Form + protected function createComponentEditForm(): EditControl { $control = $this->controlFactory->create(); $control->onSave[] = function (EditControl $control, $data) { $this->redirect('this'); - // или пренасочете към резултата от редактирането, например: + // или пренасочваме към резултата от редактирането, напр.: // $this->redirect('detail', ['id' => $data->id]); }; @@ -349,5 +346,3 @@ class MyPresenter extends Nette\Application\UI\Presenter } } ``` - -{{sitename: Най-добри практики}} diff --git a/best-practices/bg/inject-method-attribute.texy b/best-practices/bg/inject-method-attribute.texy index fa3e46916a..2d72ead00c 100644 --- a/best-practices/bg/inject-method-attribute.texy +++ b/best-practices/bg/inject-method-attribute.texy @@ -1,21 +1,18 @@ -Методи и атрибути за инжектиране -******************************** +Методи и атрибути inject +************************ .[perex] -В тази статия ще се съсредоточим върху различните начини за предаване на зависимости на презентатори в рамката Nette. Ще сравним предпочитания метод, който е конструкторът, с други възможности, като например методи и атрибути на `inject`. +В тази статия ще разгледаме различните начини за предаване на зависимости към презентерите в Nette framework. Ще сравним предпочитания начин, който е конструкторът, с други възможности като методите и атрибутите `inject`. -И за презентаторите предаването на зависимости чрез [конструктора |dependency-injection:passing-dependencies#Constructor Injection] е предпочитаният начин. -Въпреки това, ако създадете общ предшественик, от който другите презентатори наследяват (например BasePresenter), и този предшественик също има зависимости, възниква проблем, който наричаме [конструкторски ад |dependency-injection:passing-dependencies#Constructor hell]. -Той може да бъде заобиколен с помощта на алтернативни методи, които включват инжектиране на методи и атрибути (анотации). +Също и за презентерите важи, че предаването на зависимости чрез [конструктор |dependency-injection:passing-dependencies#Предаване чрез конструктор] е предпочитаният път. Но ако създавате общ родител, от който наследяват други презентери (напр. `BasePresenter`), и този родител също има зависимости, възниква проблем, който наричаме [constructor hell |dependency-injection:passing-dependencies#Адът на конструктора]. Той може да бъде заобиколен чрез алтернативни пътища, които представляват методите и атрибутите (анотациите) `inject`. -`inject*()` Методи .[#toc-inject-methods] -========================================= +Методи `inject*()` +================== -Това е форма на предаване на зависимости с помощта на [задаващи елементи |dependency-injection:passing-dependencies#Setter Injection]. Имената на тези задаващи елементи започват с префикса inject. -Nette DI автоматично извиква тези методи веднага след създаването на инстанцията на презентатора и им предава всички необходими зависимости. Следователно те трябва да бъдат декларирани като публични. +Това е форма на предаване на зависимост чрез [setter |dependency-injection:passing-dependencies#Предаване чрез сетър]. Името на тези сетъри започва с префикса `inject`. Nette DI автоматично извиква така наречените методи веднага след създаването на инстанцията на презентера и им предава всички необходими зависимости. Следователно те трябва да бъдат декларирани като public. -`inject*()` Методите могат да се разглеждат като вид разширение на конструктора в множество методи. Благодарение на това `BasePresenter` може да приема зависимости чрез друг метод и да остави конструктора свободен за своите наследници: +Методите `inject*()` могат да се разглеждат като вид разширение на конструктора в няколко метода. Благодарение на това `BasePresenter` може да поеме зависимости чрез друг метод и да остави конструктора свободен за своите наследници: ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -39,18 +36,18 @@ class MyPresenter extends BasePresenter } ``` -Презентаторът може да съдържа произволен брой методи `inject*()` и всеки от тях може да има произволен брой параметри. Това е чудесно и за случаите, когато презентаторът е [съставен от черти |presenter-traits] и всяка от тях изисква своя собствена зависимост. +Презентерът може да съдържа произволен брой методи `inject*()` и всеки може да има произволен брой параметри. Те са чудесни и в случаите, когато презентерът е [съставен от trait |presenter-traits] и всеки от тях изисква собствена зависимост. -`Inject` Атрибути .[#toc-inject-attributes] -=========================================== +Атрибути `Inject` +================= -Това е форма на [инжектиране в свойствата |dependency-injection:passing-dependencies#Property Injection]. Достатъчно е да посочите кои свойства трябва да бъдат инжектирани и Nette DI автоматично предава зависимостите веднага след създаването на инстанцията на презентатора. За да ги вмъкнете, е необходимо да ги декларирате като публични. +Това е форма на [инжектиране в свойство |dependency-injection:passing-dependencies#Чрез задаване на променлива]. Достатъчно е да се обозначи в кои променливи трябва да се инжектира и Nette DI автоматично ще предаде зависимостите веднага след създаването на инстанцията на презентера. За да може да ги вмъкне, е необходимо те да бъдат декларирани като public. -Свойствата се маркират с атрибут: (преди се използваше анотацията `/** @inject */`) +Означаваме свойствата с атрибут: (преди се използваше анотацията `/** @inject */`) ```php -use Nette\DI\Attributes\Inject; // този ред е важен +use Nette\DI\Attributes\Inject; // този ред е важен class MyPresenter extends Nette\Application\UI\Presenter { @@ -59,9 +56,6 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Предимството на този метод за предаване на зависимостите е, че той е много икономичен за записване. С въвеждането на [промоцията на свойствата на конструктора |https://blog.nette.org/bg/php-8-0-p-len-pregled-na-novostite#toc-constructor-property-promotion] обаче използването на конструктора изглежда по-лесно. +Предимството на този начин на предаване на зависимости беше много икономичната форма на запис. Въпреки това, с появата на [constructor property promotion |https://blog.nette.org/bg/php-8-0-complete-overview-of-news#toc-constructor-property-promotion], изглежда по-лесно да се използва конструктор. -От друга страна, този метод страда от същите недостатъци като предаването на зависимости в свойства като цяло: нямаме контрол върху промените в променливата, а в същото време променливата става част от публичния интерфейс на класа, което е нежелателно. - - -{{sitename: Най-добри практики}} +От друга страна, този начин страда от същите недостатъци като предаването на зависимости към свойства като цяло: нямаме контрол над промените в променливата и същевременно променливата става част от публичния интерфейс на класа, което е нежелателно. diff --git a/best-practices/bg/lets-create-contact-form.texy b/best-practices/bg/lets-create-contact-form.texy index b0f3d7ccf6..9070371fe2 100644 --- a/best-practices/bg/lets-create-contact-form.texy +++ b/best-practices/bg/lets-create-contact-form.texy @@ -1,12 +1,12 @@ -Да създадем формуляр за контакт -******************************* +Създаваме контактна форма +************************* .[perex] -Нека разгледаме как да създадем формуляр за контакт в Nette, включително изпращането му на имейл. Така че нека го направим! +Ще разгледаме как да създадем контактна форма в Nette, включително изпращане на имейл. И така, да започваме! -Първо трябва да създадем нов проект. Както е обяснено в страницата " [Започване" |nette:installation]. А след това можем да започнем създаването на формуляра. +Първо трябва да създадем нов проект. Как да го направите е обяснено на страницата [Първи стъпки |nette:installation]. И след това можем да започнем със създаването на формата. -Най-лесният начин е да създадете [формуляра директно в Presenter |forms:in-presenter]. Можем да използваме предварително създадената страница `HomePresenter`. Ще добавим компонента `contactForm`, представляващ формата. Ще направим това, като напишем фабричния метод на `createComponentContactForm()` в кода, който ще създава компонента: +Най-лесният начин е да създадете [форма директно в презентера |forms:in-presenter]. Можем да използваме предварително подготвения `HomePresenter`. В него ще добавим компонент `contactForm`, представляващ формата. Ще направим това, като напишем в кода фабричен метод `createComponentContactForm()`, който ще произведе компонента: ```php use Nette\Application\UI\Form; @@ -17,37 +17,35 @@ class HomePresenter extends Presenter protected function createComponentContactForm(): Form { $form = new Form; - $form->addText('name', 'Name:') - ->setRequired('Enter your name'); + $form->addText('name', 'Име:') + ->setRequired('Въведете име'); $form->addEmail('email', 'E-mail:') - ->setRequired('Enter your e-mail'); - $form->addTextarea('message', 'Message:') - ->setRequired('Enter message'); - $form->addSubmit('send', 'Send'); + ->setRequired('Въведете e-mail'); + $form->addTextarea('message', 'Съобщение:') + ->setRequired('Въведете съобщение'); + $form->addSubmit('send', 'Изпрати'); $form->onSuccess[] = [$this, 'contactFormSucceeded']; return $form; } public function contactFormSucceeded(Form $form, $data): void { - // sending an email + // изпращане на имейл } } ``` -Както можете да видите, създадохме два метода. Първият метод `createComponentContactForm()` създава нова форма. Тя има полета за име, имейл и съобщение, които добавяме с помощта на методите `addText()`, `addEmail()` и `addTextArea()`. Добавихме и бутон за изпращане на формуляра. -Но какво ще стане, ако потребителят не попълни някои полета? В такъв случай трябва да го уведомим, че това е задължително поле. Направихме това с метода `setRequired()`. -Накрая добавихме и [събитие |nette:glossary#events] `onSuccess`, което се задейства, ако формулярът е изпратен успешно. В нашия случай то извиква метода `contactFormSucceeded`, който се грижи за обработката на изпратения формуляр. След малко ще добавим това към кода. +Както виждате, създадохме два метода. Първият метод `createComponentContactForm()` създава нова форма. Тя има полета за име, имейл и съобщение, които добавяме с методите `addText()`, `addEmail()` и `addTextArea()`. Също така добавихме бутон за изпращане на формата. Но какво ще стане, ако потребителят не попълни някое поле? В такъв случай трябва да му съобщим, че това е задължително поле. Постигнахме това с метода `setRequired()`. Накрая добавихме и [събитие |nette:glossary#Събития events] `onSuccess`, което се задейства, ако формата е успешно изпратена. В нашия случай извиква метода `contactFormSucceeded`, който ще се погрижи за обработката на изпратената форма. Ще добавим това в кода след малко. -Нека компонентът `contantForm` бъде визуализиран в шаблона `templates/Home/default.latte`: +Ще оставим компонента `contactForm` да се рендира в шаблона `Home/default.latte`: ```latte {block content} -

    Contant Form

    +

    Контактна форма

    {control contactForm} ``` -За да изпратим самия имейл, създаваме нов клас, наречен `ContactFacade`, и го поставяме във файла `app/Model/ContactFacade.php`: +За самото изпращане на имейл ще създадем нов клас, който ще наречем `ContactFacade` и ще го поставим във файла `app/Model/ContactFacade.php`: ```php addTo('admin@example.com') // your email + $mail->addTo('admin@example.com') // вашият имейл ->setFrom($email, $name) - ->setSubject('Message from the contact form') + ->setSubject('Съобщение от контактната форма') ->setBody($message); $this->mailer->send($mail); @@ -78,9 +76,9 @@ class ContactFacade } ``` -Методът `sendMessage()` ще създаде и изпрати имейла. За целта той използва така наречения mailer, който предава като зависимост чрез конструктора. Прочетете повече за [изпращането на имейли |mail:]. +Методът `sendMessage()` създава и изпраща имейл. За целта използва т.нар. mailer, който получава като зависимост чрез конструктора. Прочетете повече за [изпращане на имейли |mail:]. -Сега ще се върнем към презентатора и ще завършим метода `contactFormSucceeded()`. Той извиква метода `sendMessage()` на класа `ContactFacade` и му предава данните от формуляра. А как ще получим обекта `ContactFacade`? Той ще ни бъде предаден от конструктора: +Сега ще се върнем към презентера и ще завършим метода `contactFormSucceeded()`. Той ще извика метода `sendMessage()` на класа `ContactFacade` и ще му предаде данните от формата. А как ще получим обекта `ContactFacade`? Ще го получим чрез конструктора: ```php use App\Model\ContactFacade; @@ -102,36 +100,36 @@ class HomePresenter extends Presenter public function contactFormSucceeded(stdClass $data): void { $this->facade->sendMessage($data->email, $data->name, $data->message); - $this->flashMessage('The message has been sent'); + $this->flashMessage('Съобщението беше изпратено'); $this->redirect('this'); } } ``` -След като имейлът бъде изпратен, показваме на потребителя т.нар. [флаш съобщение |application:components#flash-messages], с което потвърждаваме, че съобщението е изпратено, и след това пренасочваме към следващата страница, така че формулярът да не може да бъде изпратен отново с помощта на *refresh* в браузъра. +След като имейлът бъде изпратен, ще покажем на потребителя т.нар. [flash съобщение |application:components#Flash съобщения], потвърждаващо, че съобщението е изпратено, и след това ще пренасочим към следващата страница, за да не може формата да бъде повторно изпратена чрез *refresh* в браузъра. -Е, ако всичко е наред, би трябвало да можете да изпратите имейл от формуляра си за контакт. Поздравления! +Така, и ако всичко работи, трябва да можете да изпратите имейл от вашата контактна форма. Поздравления! -HTML шаблон за електронна поща .[#toc-html-email-template] ----------------------------------------------------------- +HTML шаблон на имейл +-------------------- -Засега се изпраща имейл с обикновен текст, съдържащ само съобщението, изпратено от формуляра. Но можем да използваме HTML в имейла и да го направим по-привлекателен. Ще създадем шаблон за него в Latte, който ще запазим в `app/Model/contactEmail.latte`: +Засега се изпраща обикновен текстов имейл, съдържащ само съобщението, изпратено от формата. Но в имейла можем да използваме HTML и да направим вида му по-атрактивен. Ще създадем за него шаблон в Latte, който ще запишем в `app/Model/contactEmail.latte`: ```latte - Message from the contact form + Съобщение от контактната форма -

    Name: {$name}

    +

    Име: {$name}

    E-mail: {$email}

    -

    Message: {$message}

    +

    Съобщение: {$message}

    ``` -Остава да модифицираме `ContactFacade`, за да използваме този шаблон. В конструктора заявяваме класа `LatteFactory`, който може да създаде обекта `Latte\Engine`, [рендер на шаблона Latte |latte:develop#how-to-render-a-template]. Използваме метода `renderToString()`, за да визуализираме шаблона във файл, като първият параметър е пътят до шаблона, а вторият - променливите. +Остава да променим `ContactFacade`, за да използва този шаблон. В конструктора ще изискаме класа `LatteFactory`, който може да произведе обект `Latte\Engine`, т.е. [рендериращ механизъм за Latte шаблони |latte:develop#Как да рендираме шаблон]. С помощта на метода `renderToString()` ще рендираме шаблона във файл, първият параметър е пътят до шаблона, а вторият са променливите. ```php namespace App\Model; @@ -158,7 +156,7 @@ class ContactFacade ]); $mail = new Message; - $mail->addTo('admin@example.com') // your email + $mail->addTo('admin@example.com') // вашият имейл ->setFrom($email, $name) ->setHtmlBody($body); @@ -167,15 +165,15 @@ class ContactFacade } ``` -След това предаваме генерирания HTML имейл на метода `setHtmlBody()` вместо на оригиналния `setBody()`. Също така не е необходимо да посочваме темата на имейла в `setSubject()`, защото библиотеката я взема от елемента `` в шаблона. +Генерирания HTML имейл след това ще предадем на метода `setHtmlBody()` вместо оригиналния `setBody()`. Също така не е необходимо да посочваме темата на имейла в `setSubject()`, тъй като библиотеката ще я вземе от елемента `<title>` на шаблона. -Конфигуриране на .[#toc-configuring] ------------------------------------- +Конфигурация +------------ -В кода на класа `ContactFacade` нашият имейл адрес на администратора `admin@example.com` все още е твърдо кодиран. По-добре би било да го преместите в конфигурационния файл. Как да го направим? +В кода на класа `ContactFacade` все още е твърдо кодиран нашият администраторски имейл `admin@example.com`. Би било по-добре да го преместим в конфигурационния файл. Как да го направим? -Първо, модифицираме класа `ContactFacade` и заменяме низът за имейл с променлива, предадена от конструктора: +Първо ще променим класа `ContactFacade` и ще заменим низа с имейла с променлива, предадена чрез конструктора: ```php class ContactFacade @@ -199,28 +197,25 @@ class ContactFacade } ``` -Втората стъпка е да въведем стойността на тази променлива в конфигурацията. Във файла `app/config/services.neon` добавяме: +А втората стъпка е да посочим стойността на тази променлива в конфигурацията. Във файла `app/config/services.neon` ще запишем: ```neon services: - App\Model\ContactFacade(adminEmail: admin@example.com) ``` -И това е всичко. Ако в раздела `services` има много елементи и ви се струва, че имейлът се губи сред тях, можем да го направим променлива. Ще променим записа на: +И това е. Ако елементите в секцията `services` са много и имате чувството, че имейлът се губи сред тях, можем да го превърнем в променлива. Ще променим записа на: ```neon services: - App\Model\ContactFacade(adminEmail: %adminEmail%) ``` -И ще дефинираме тази променлива във файла `app/config/common.neon`: +И във файла `app/config/common.neon` ще дефинираме тази променлива: ```neon parameters: adminEmail: admin@example.com ``` -И готово! - - -{{sitename: Най-добри практики}} +И е готово! diff --git a/best-practices/bg/microsites.texy b/best-practices/bg/microsites.texy new file mode 100644 index 0000000000..e29edcb2ed --- /dev/null +++ b/best-practices/bg/microsites.texy @@ -0,0 +1,63 @@ +Как да пишем микро-уебсайтове +***************************** + +Представете си, че трябва бързо да създадете малък уебсайт за предстоящо събитие на вашата фирма. Трябва да е просто, бързо и без излишни усложнения. Може би си мислите, че за такъв малък проект не ви е необходим стабилен framework. Но какво ще стане, ако използването на Nette framework може значително да опрости и ускори този процес? + +Все пак, дори при създаването на прости уебсайтове, не искате да се отказвате от удобството. Не искате да измисляте това, което вече е решено. Бъдете спокойно мързеливи и се оставете да ви глезят. Nette Framework може отлично да се използва и като micro framework. + +Как може да изглежда такъв микросайт? Например така, че целият код на уебсайта да се постави в един файл `index.php` в публичната папка: + +```php +<?php + +require __DIR__ . '/../vendor/autoload.php'; + +$configurator = new Nette\Bootstrap\Configurator; +$configurator->enableTracy(__DIR__ . '/../log'); +$configurator->setTempDirectory(__DIR__ . '/../temp'); + +// създаване на DI контейнер въз основа на конфигурацията в config.neon +$configurator->addConfig(__DIR__ . '/../app/config.neon'); +$container = $configurator->createContainer(); + +// настройване на маршрутизацията +$router = new Nette\Application\Routers\RouteList; +$container->addService('router', $router); + +// маршрут за URL https://example.com/ +$router->addRoute('', function ($presenter, Nette\Http\Request $httpRequest) { + // откриване на езика на браузъра и пренасочване към URL /en или /de и т.н. + $supportedLangs = ['en', 'de', 'cs']; + $lang = $httpRequest->detectLanguage($supportedLangs) ?: reset($supportedLangs); + $presenter->redirectUrl("/$lang"); +}); + +// маршрут за URL https://example.com/cs или https://example.com/en +$router->addRoute('<lang cs|en>', function ($presenter, string $lang) { + // показване на съответния шаблон, например ../templates/en.latte + $template = $presenter->createTemplate() + ->setFile(__DIR__ . '/../templates/' . $lang . '.latte'); + return $template; +}); + +// стартиране на приложението! +$container->getByType(Nette\Application\Application::class)->run(); +``` + +Всичко останало ще бъдат шаблони, съхранени в родителската папка `/templates`. + +PHP кодът в `index.php` първо [подготвя средата |bootstrap:], след това дефинира [маршрутите |application:routing#Динамично маршрутизиране с callback-ове] и накрая стартира приложението. Предимството е, че вторият параметър на функцията `addRoute()` може да бъде callable, който се изпълнява след отваряне на съответната страница. + + +Защо да използвате Nette за микросайт? +-------------------------------------- + +- Програмистите, които някога са опитвали [Tracy|tracy:], днес не могат да си представят да програмират нещо без нея. +- Преди всичко обаче ще използвате системата за шаблони [Latte|latte:], защото още от 2 страници ще искате да имате отделен [лейаут и съдържание|latte:template-inheritance]. +- И определено искате да разчитате на [автоматично екраниране |latte:safety-first], за да не възникне уязвимост XSS +- Nette също така гарантира, че при грешка никога няма да се покажат програмни съобщения за грешки на PHP, а разбираема за потребителя страница. +- Ако искате да получавате обратна връзка от потребителите, например под формата на контактна форма, тогава ще добавите и [форми|forms:] и [база данни|database:]. +- Попълнените формуляри можете лесно да [изпращате по имейл|mail:]. +- Понякога може да ви е полезно [кеширането|caching:], например ако изтегляте и показвате фийдове. + +В днешно време, когато скоростта и ефективността са ключови, е важно да имате инструменти, които ви позволяват да постигнете резултати без излишно забавяне. Nette framework ви предлага точно това - бърза разработка, сигурност и широк набор от инструменти, като Tracy и Latte, които опростяват процеса. Достатъчно е да инсталирате няколко Nette пакета и изграждането на такъв микросайт изведнъж става напълно лесно. И знаете, че никъде не се крие никаква дупка в сигурността. diff --git a/best-practices/bg/pagination.texy b/best-practices/bg/pagination.texy index be7883dffc..15a33b739e 100644 --- a/best-practices/bg/pagination.texy +++ b/best-practices/bg/pagination.texy @@ -1,17 +1,16 @@ -Страница на резултатите от заявките за база данни -************************************************* +Пагиниране на резултати от база данни +************************************* .[perex] -Когато разработвате уеб приложения, често се сблъсквате с изискването за извеждане на ограничен брой записи на една страница. +При създаването на уеб приложения много често ще се сблъскате с изискването за ограничаване на броя на изведените елементи на страница. -Излизаме от състояние, в което изписваме всички данни без страниране. За да изберем данни от базата данни, разполагаме с класа ArticleRepository, който съдържа конструктор и метода `findPublishedArticles`, който връща всички публикувани статии, подредени в низходящ ред по дата на публикуване. +Ще изходим от състояние, в което извеждаме всички данни без пагиниране. За избор на данни от базата данни имаме клас ArticleRepository, който освен конструктор съдържа метод `findPublishedArticles`, който връща всички публикувани статии, сортирани низходящо по дата на публикуване. ```php namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -31,10 +30,10 @@ class ArticleRepository } ``` -След това въвеждаме класа на модела в презентатора и в метода `render` правим справка за публикуваните статии, които предаваме на шаблона: +В презентера след това инжектираме моделния клас и в render метода изискваме публикуваните статии, които предаваме на шаблона: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -53,11 +52,11 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -В шаблона се грижим за извеждането на списъка със статии: +В шаблона `default.latte` след това се грижим за извеждането на статиите: ```latte {block content} -<h1>Статьи</h1> +<h1>Статии</h1> <div class="articles"> {foreach $articles as $article} @@ -68,11 +67,11 @@ class HomePresenter extends Nette\Application\UI\Presenter ``` -По този начин можем да напишем всички статии, но това ще доведе до проблеми, когато броят на статиите нарасне. В този момент ще бъде полезно да се приложи механизъм за страниране. +По този начин можем да изведем всички статии, което обаче започва да създава проблеми в момента, когато броят на статиите нарасне. В този момент е подходящо да се внедри механизъм за пагиниране. -Това ще гарантира, че всички статии са разделени на няколко страници и ще показваме само статиите от една текуща страница. Общият брой на страниците и разпределението на статиите се изчислява от самия [Paginator |utils:Paginator] в зависимост от това колко статии имаме общо и колко статии искаме да покажем на страницата. +Той гарантира, че всички статии ще бъдат разделени на няколко страници и ние ще покажем само статиите от една текуща страница. [utils:Paginator] сам ще изчисли общия брой страници и разпределението на статиите според това колко статии общо имаме и колко статии на страница искаме да покажем. -Първата стъпка е да променим метода, който използваме за получаване на статии в класа на хранилището, така че да връща само статии от една страница. Ще добавим и нов метод за получаване на общия брой статии в базата данни, който ще ни е необходим, за да инсталираме Paginator: +В първата стъпка ще променим метода за получаване на статии в класа на repository така, че да може да връща само статии за една страница. Също така ще добавим метод за установяване на общия брой статии в базата данни, който ще ни е необходим за настройка на Paginator: ```php namespace App\Model; @@ -100,7 +99,7 @@ class ArticleRepository } /** - * Returns the total number of published articles + * Връща общия брой публикувани статии */ public function getPublishedArticlesCount(): int { @@ -109,12 +108,12 @@ class ArticleRepository } ``` -Следващата стъпка е да редактирате водещия. Ще предадем номера на текущо показваната страница на метода `render`. В случай че този номер не е част от URL адреса, трябва да зададем стойност по подразбиране за първата страница. +След това ще се заемем с промените в презентера. В render метода ще предаваме номера на текущо показваната страница. За случая, когато този номер не е част от URL, ще зададем стойност по подразбиране за първата страница. -Също така разширяваме метода `render`, за да получим инстанцията Paginator, да я конфигурираме и да изберем желаните статии, които да се показват в шаблона. HomePresenter ще изглежда по следния начин: +Освен това ще разширим render метода с получаване на инстанция на Paginator, неговата настройка и избор на правилните статии за показване в шаблона. HomePresenter след промените ще изглежда така: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -128,31 +127,31 @@ class HomePresenter extends Nette\Application\UI\Presenter public function renderDefault(int $page = 1): void { - // намиране на общия брой публикувани статии + // Ще установим общия брой публикувани статии $articlesCount = $this->articleRepository->getPublishedArticlesCount(); - // Ще създадем инстанция на Paginator и ще я конфигурираме + // Ще създадем инстанция на Paginator и ще го настроим $paginator = new Nette\Utils\Paginator; $paginator->setItemCount($articlesCount); // общ брой статии - $paginator->setItemsPerPage(10); // елементи на страница - $paginator->setPage($page); // действителен номер на страницата + $paginator->setItemsPerPage(10); // брой елементи на страница + $paginator->setPage($page); // номер на текущата страница - // Извличаме ограничен набор от статии от базата данни въз основа на изчисленията на Paginator - $articles = $this->articleRepository->findPublishedArticles($paginator->getLength(), $paginator->getOffset()) + // От базата данни ще изтеглим ограничено множество статии според изчислението на Paginator + $articles = $this->articleRepository->findPublishedArticles($paginator->getLength(), $paginator->getOffset()); - // които предаваме в шаблона + // което ще предадем на шаблона $this->template->articles = $articles; - //а също и самия Paginator за показване на опциите за страниране + // и също така самия Paginator за показване на възможностите за пагиниране $this->template->paginator = $paginator; } } ``` -Шаблонът вече повтаря статиите на една страница, просто добавете връзки за страниране: +Шаблонът ни вече итерира само върху статиите от една страница, достатъчно е да добавим връзките за пагиниране: ```latte {block content} -<h1>Articles</h1> +<h1>Статии</h1> <div class="articles"> {foreach $articles as $article} @@ -163,34 +162,33 @@ class HomePresenter extends Nette\Application\UI\Presenter <div class="pagination"> {if !$paginator->isFirst()} - <a n:href="default, 1">Первая</a> + <a n:href="default, 1">Първа</a>  |  - <a n:href="default, $paginator->page-1">Предыдущая</a> + <a n:href="default, $paginator->page-1">Предишна</a>  |  {/if} - Страница {$paginator->getPage()} из {$paginator->getPageCount()} + Страница {$paginator->getPage()} от {$paginator->getPageCount()} {if !$paginator->isLast()}  |  - <a n:href="default, $paginator->getPage() + 1">Следующая</a> + <a n:href="default, $paginator->getPage() + 1">Следваща</a>  |  - <a n:href="default, $paginator->getPageCount()">Последняя</a> + <a n:href="default, $paginator->getPageCount()">Последна</a> {/if} </div> ``` -Ето как добавихме страниране с помощта на Paginator. Ако използваме [Nette Database Core |database:core] като слой на базата данни вместо [Nette Database Explorer |database:explorer], можем да реализираме странициране дори без Paginator. Класът `Nette\Database\Table\Selection` съдържа метод [page |api:Nette\Database\Table\Selection::_ page] с логика за страниране, взета от Paginator. +Така допълнихме страницата с възможност за пагиниране с помощта на Paginator. В случай, че вместо [Nette Database Core |database:sql-way] като слой за база данни използваме [Nette Database Explorer |database:explorer], можем да внедрим пагиниране и без използване на Paginator. Класът `Nette\Database\Table\Selection` съдържа метод [page |api:Nette\Database\Table\Selection::_page] с логика за пагиниране, взета от Paginator. -Хранилището ще изглежда по следния начин: +Repository при този начин на внедряване ще изглежда така: ```php namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -198,7 +196,6 @@ class ArticleRepository ) { } - public function findPublishedArticles(): Nette\Database\Table\Selection { return $this->database->table('articles') @@ -208,10 +205,10 @@ class ArticleRepository } ``` -Не е необходимо да създаваме Paginator в презентатора, вместо това ще използваме метода на обекта `Selection`, върнат от хранилището: +В презентера не е необходимо да създаваме Paginator, вместо него ще използваме метода на класа `Selection`, който ни връща repository: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -225,25 +222,25 @@ class HomePresenter extends Nette\Application\UI\Presenter public function renderDefault(int $page = 1): void { - // намиране на публикувани статии + // Ще изтеглим публикуваните статии $articles = $this->articleRepository->findPublishedArticles(); - // и тяхната част, ограничена до изчисляването на метода на страницата, която ще предадем в шаблона + // и в шаблона ще изпратим само тяхната част, ограничена според изчислението на метода page $lastPage = 0; $this->template->articles = $articles->page($page, 10, $lastPage); - //както и необходимите данни за показване на опциите за страниране + // и също така необходимите данни за показване на възможностите за пагиниране $this->template->page = $page; $this->template->lastPage = $lastPage; } } ``` -Тъй като не използваме Paginator, трябва да редактираме раздела, показващ връзките за страниране: +Тъй като в шаблона сега не изпращаме Paginator, ще променим частта, показваща връзките за пагиниране: ```latte {block content} -<h1>Статьи</h1> +<h1>Статии</h1> <div class="articles"> {foreach $articles as $article} @@ -254,24 +251,23 @@ class HomePresenter extends Nette\Application\UI\Presenter <div class="pagination"> {if $page > 1} - <a n:href="default, 1">Первая</a> + <a n:href="default, 1">Първа</a>  |  - <a n:href="default, $page - 1">Предыдущая</a> + <a n:href="default, $page - 1">Предишна</a>  |  {/if} - Страница {$page} из {$lastPage} + Страница {$page} от {$lastPage} {if $page < $lastPage}  |  - <a n:href="default, $page + 1">Следующая</a> + <a n:href="default, $page + 1">Следваща</a>  |  - <a n:href="default, $lastPage">Последняя</a> + <a n:href="default, $lastPage">Последна</a> {/if} </div> ``` -По този начин реализирахме механизъм за страниране, без да използваме пейджинатора. +По този начин внедрихме механизъм за пагиниране без използване на Paginator. {{priority: -1}} -{{sitename: Най-добри практики}} diff --git a/best-practices/bg/passing-settings-to-presenters.texy b/best-practices/bg/passing-settings-to-presenters.texy index f3e3c78576..076be18938 100644 --- a/best-practices/bg/passing-settings-to-presenters.texy +++ b/best-practices/bg/passing-settings-to-presenters.texy @@ -1,10 +1,10 @@ -Предаване на параметри на презентатори -************************************** +Предаване на настройки към презентерите +*************************************** .[perex] -Имате ли нужда да предавате аргументи на презентаторите, които не са обекти (напр. информация за това дали се изпълнява в режим на отстраняване на грешки, пътища до директории и т.н.) и следователно не могат да бъдат предадени автоматично с помощта на autoconnect? Решението е да ги капсулирате в обект `Settings`. +Трябва ли да предавате аргументи към презентерите, които не са обекти (напр. информация дали работят в debug режим, пътища до директории и т.н.), и следователно не могат да бъдат предадени автоматично чрез autowiring? Решението е да ги капсулирате в обект `Settings`. -Услугата `Settings` е много прост и полезен начин за предоставяне на информация за работещото приложение на говорителите. Конкретният му вид зависи изцяло от конкретните ви нужди. Пример: +Сървисът `Settings` представлява много лесен и същевременно полезен начин за предоставяне на информация за работещото приложение на презентерите. Конкретният му вид зависи изцяло от вашите конкретни нужди. Пример: ```php namespace App; @@ -12,7 +12,7 @@ namespace App; class Settings { public function __construct( - // от PHP 8.1 е възможно да се зададе readonly + // от PHP 8.1 е възможно да се посочи readonly public bool $debugMode, public string $appDir, // и така нататък @@ -20,7 +20,7 @@ class Settings } ``` -Пример за регистрация в конфигурация: +Пример за регистрация в конфигурацията: ```neon services: @@ -30,7 +30,7 @@ services: ) ``` -Когато водещият се нуждае от информацията, предоставяна от тази услуга, той просто я изисква от конструктора: +Когато презентерът се нуждае от информация, предоставена от този сървис, той просто я изисква в конструктора: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -47,5 +47,3 @@ class MyPresenter extends Nette\Application\UI\Presenter } } ``` - -{{sitename: Най-добри практики}} diff --git a/best-practices/bg/post-links.texy b/best-practices/bg/post-links.texy new file mode 100644 index 0000000000..452bb3b18a --- /dev/null +++ b/best-practices/bg/post-links.texy @@ -0,0 +1,56 @@ +Как правилно да използваме POST връзки +************************************** + +.[perex] +В уеб приложенията, особено в административните интерфейси, основно правило трябва да бъде, че действията, променящи състоянието на сървъра, не трябва да се извършват чрез HTTP метода GET. Както подсказва името на метода, GET трябва да служи само за получаване на данни, а не за тяхната промяна. За действия като изтриване на записи е по-подходящо да се използва методът POST. Въпреки че идеалният би бил методът DELETE, но той не може да бъде извикан без JavaScript, затова исторически се използва POST. + +Как да го направим на практика? Използвайте този прост трик. В началото на шаблона си създайте помощна форма с идентификатор `postForm`, която след това ще използвате за бутоните за изтриване: + +```latte .{file:@layout.latte} +<form method="post" id="postForm"></form> +``` + +Благодарение на тази форма можете вместо класическа връзка `<a>` да използвате бутон `<button>`, който може да бъде визуално оформен така, че да изглежда като обикновена връзка. Например CSS framework Bootstrap предлага класове `btn btn-link`, с които ще постигнете това, че бутонът няма да се различава визуално от останалите връзки. С помощта на атрибута `form="postForm"` го свързваме с предварително подготвената форма: + +```latte .{file:admin.latte} +<table> + <tr n:foreach="$posts as $post"> + <td>{$post->title}</td> + <td> + <button class="btn btn-link" form="postForm" formaction="{link delete $post->id}">delete</button> + <!-- вместо <a n:href="delete $post->id">delete</a> --> + </td> + </tr> +</table> +``` + +При кликване върху връзката сега се извиква действието `delete`. За да се гарантира, че заявките ще бъдат приемани само чрез метода POST и от същия домейн (което е ефективна защита срещу CSRF атаки), използвайте атрибута `#[Requires]`: + +```php .{file:AdminPresenter.php} +use Nette\Application\Attributes\Requires; + +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST', sameOrigin: true)] + public function actionDelete(int $id): void + { + $this->facade->deletePost($id); // хипотетичен код, изтриващ запис + $this->redirect('default'); + } +} +``` + +Атрибутът съществува от Nette Application 3.2 и повече за неговите възможности ще научите на страницата [Как да използваме атрибута #Requires |attribute-requires]. + +Ако вместо действието `actionDelete()` използвате сигнал `handleDelete()`, не е необходимо да посочвате `sameOrigin: true`, тъй като сигналите имат тази защита зададена имплицитно: + +```php .{file:AdminPresenter.php} +#[Requires(methods: 'POST')] +public function handleDelete(int $id): void +{ + $this->facade->deletePost($id); + $this->redirect('this'); +} +``` + +Този подход не само подобрява сигурността на вашето приложение, но също така допринася за спазването на правилните уеб стандарти и практики. Чрез използването на методи POST за действия, променящи състоянието, ще постигнете по-стабилно и по-сигурно приложение. diff --git a/best-practices/bg/presenter-traits.texy b/best-practices/bg/presenter-traits.texy index 0c4c5e8fed..c2cf88f074 100644 --- a/best-practices/bg/presenter-traits.texy +++ b/best-practices/bg/presenter-traits.texy @@ -1,14 +1,14 @@ -Измисляне на водещи от черти -**************************** +Композиране на презентери от trait +********************************** .[perex] -Ако трябва да приложим един и същ код в няколко презентатора (например проверка дали потребителят е влязъл в системата), изкушаващо е да поставим този код в общ предшественик. Вторият вариант е да се създадат едноцелеви признаци. +Ако трябва да внедрим един и същ код в няколко презентера (напр. проверка дали потребителят е влязъл), предлага се да поставим кода в общ родител. Втората възможност е създаването на едноцелеви [trait |nette:introduction-to-object-oriented-programming#Traits]. -Предимството на това решение е, че всеки презентатор може да използва само чертите, от които действително се нуждае, докато в PHP не е възможно многократно наследяване. +Предимството на това решение е, че всеки от презентерите може да използва точно тези trait, които наистина са му необходими, докато множественото наследяване не е възможно в PHP. -Тези черти могат да се възползват от факта, че всички [методи |inject-method-attribute#inject-Methods] се извикват последователно при създаването на главния модул. Трябва само да се уверите, че името на всеки метод за инжектиране е уникално. +Тези trait могат да използват факта, че при създаването на презентера последователно се извикват всички [inject методи |inject-method-attribute#Методи inject]. Необходимо е само да се гарантира, че името на всеки inject метод е уникално. -Чертите могат да закачат код за инициализация в събитията [onStartup или onRender |application:presenters#Events]. +Trait могат да прикачат инициализационен код към събитията [onStartup или onRender |application:presenters#Събития]. Примери: @@ -36,7 +36,7 @@ trait StandardTemplateFilters } ``` -След това водещият просто използва тези черти: +Презентерът след това просто използва тези trait: ```php class ArticlePresenter extends Nette\Application\UI\Presenter @@ -45,6 +45,3 @@ class ArticlePresenter extends Nette\Application\UI\Presenter use RequireLoggedUser; } ``` - - -{{sitename: Най-добри практики}} diff --git a/best-practices/bg/restore-request.texy b/best-practices/bg/restore-request.texy index d65a20ca9e..191350748d 100644 --- a/best-practices/bg/restore-request.texy +++ b/best-practices/bg/restore-request.texy @@ -1,17 +1,16 @@ -Как да се върна към предишната страница? -**************************************** +Как да се върнем към предишна страница? +*************************************** .[perex] -Какво трябва да направя, ако даден потребител е попълнил формуляра, но срокът му на влизане е изтекъл? За да избегнем загубата на данни, ги записваме в сесия, преди да пренасочим към страницата за вход. В Nette това е лесно да се направи. +Какво ще стане, ако потребителят попълва формуляр и сесията му изтече? За да не загуби данните, преди пренасочването към страницата за вход ще запазим данните в сесията. В Nette това е напълно лесно. -Текущата заявка може да бъде запазена в сесията с помощта на метода `storeRequest()`, който връща нейния идентификатор като кратък низ. Методът записва името на текущия водещ, изгледа и неговите параметри. -Ако формулярът също е бил изпратен, стойностите на полетата (с изключение на качените файлове) също се запазват. +Текущата заявка може да бъде запазена в сесията с помощта на метода `storeRequest()`, който връща нейния идентификатор под формата на кратък низ. Методът запазва името на текущия презентер, изгледа и неговите параметри. В случай, че е изпратен и формуляр, се запазва и съдържанието на полетата (с изключение на качените файлове). -Заявката се извлича чрез метода `restoreRequest($key)`, на който предаваме извлечения идентификатор. Това пренасочва към първоначалния водещ и изглед. Ако обаче запазената заявка съдържа формуляр за подаване, тя ще бъде пренасочена към оригиналния презентатор, като се използва методът `forward()`, ще се предадат предварително попълнените стойности на формуляра и ще се позволи той да бъде прерисуван. Това позволява на потребителя да изпрати формуляра отново, без да се губят данни. +Възстановяването на заявката се извършва от метода `restoreRequest($key)`, на който предаваме получения идентификатор. Той пренасочва към оригиналния презентер и изглед. Ако обаче запазената заявка съдържа изпращане на формуляр, към оригиналния презентер се преминава с метода `forward()`, на формуляра се предават предишно попълнените стойности и той се рендира отново. По този начин потребителят има възможност да изпрати формуляра отново и никакви данни не се губят. -Важно е да се отбележи, че `restoreRequest()` проверява дали новорегистрираният потребител е същият потребител, който първоначално е попълнил формуляра. Ако не, той отхвърля заявката и не прави нищо. +Важно е, че `restoreRequest()` проверява дали нововъведеният потребител е същият, който първоначално е попълнил формуляра. Ако не е, заявката се отхвърля и нищо не се прави. -Нека демонстрираме това с пример. Да предположим, че имаме презентатор `AdminPresenter`, в който се редактират данни и чийто метод `startup()` проверява дали потребителят е влязъл в системата. Ако това не е така, го пренасочваме към `SignPresenter`. В същото време запазваме текущата заявка и изпращаме нейния ключ на `SignPresenter`. +Ще покажем всичко на пример. Нека имаме презентер `AdminPresenter`, в който се редактират данни и в чийто метод `startup()` проверяваме дали потребителят е влязъл. Ако не е, го пренасочваме към `SignPresenter`. Същевременно запазваме текущата заявка и нейния ключ изпращаме до `SignPresenter`. ```php class AdminPresenter extends Nette\Application\UI\Presenter @@ -27,7 +26,7 @@ class AdminPresenter extends Nette\Application\UI\Presenter } ``` -Презентаторът `SignPresenter` ще съдържа постоянен параметър `$backlink`, в който се записва ключът, в допълнение към формата за вход. Тъй като параметърът е постоянен, той ще бъде пренесен дори и след като формулярът за вход бъде изпратен. +Презентерът `SignPresenter` освен формуляра за вход ще съдържа и персистентен параметър `$backlink`, в който се записва ключът. Тъй като параметърът е персистентен, той ще се пренася и след изпращане на формуляра за вход. ```php @@ -41,14 +40,14 @@ class SignPresenter extends Nette\Application\UI\Presenter protected function createComponentSignInForm() { $form = new Nette\Application\UI\Form; - // ... добавляем поля форма ... + // ... добавяме полета на формуляра ... $form->onSuccess[] = [$this, 'signInFormSubmitted']; return $form; } public function signInFormSubmitted($form) { - // ... тук ние регистрираме потребителя ... + // ... тук вписваме потребителя ... $this->restoreRequest($this->backlink); $this->redirect('Admin:'); @@ -56,9 +55,8 @@ class SignPresenter extends Nette\Application\UI\Presenter } ``` -Предаваме ключа на запазената заявка на метода `restoreRequest()`, а той пренасочва (или препраща) към оригиналния водещ. +На метода `restoreRequest()` предаваме ключа на запазената заявка и той пренасочва (или преминава) към оригиналния презентер. -Ако обаче ключът е невалиден (напр. вече не съществува в сесията), методът не прави нищо. Следователно следващото повикване е `$this->redirect('Admin:')`, което пренасочва към `AdminPresenter`. +Ако обаче ключът е невалиден (например вече не съществува в сесията), методът не прави нищо. Следователно следва извикването на `$this->redirect('Admin:')`, което пренасочва към `AdminPresenter`. {{priority: -1}} -{{sitename: Най-добри практики}} diff --git a/best-practices/cs/@home.texy b/best-practices/cs/@home.texy index 5466d6d7fc..612d75048f 100644 --- a/best-practices/cs/@home.texy +++ b/best-practices/cs/@home.texy @@ -17,6 +17,8 @@ Nette Aplikace - [Jak se vrátit k dřívější stránce |restore-request] - [Stránkování výsledků databáze |pagination] - [Dynamické snippety |dynamic-snippets] +- [Jak používat atribut #Requires |attribute-requires] +- [Jak správně používat POST odkazy |post-links] </div> <div> @@ -36,10 +38,12 @@ Formuláře Obecné ------ - [Jak načíst konfigurační soubor |bootstrap:] +- [Jak psát mikro-weby |microsites] - [Proč Nette používá PascalCase notaci konstant? |https://blog.nette.org/cs/za-mene-kriku-v-kodu] - [Proč Nette nepoužívá příponu Interface? |https://blog.nette.org/cs/predpony-a-pripony-do-nazvu-rozhrani-nepatri] - [Composer: tipy pro použití |composer] - [Tipy na editory & nástroje |editors-and-tools] +- [Úvod do objektově orientovaného programování |nette:introduction-to-object-oriented-programming] </div> <div> @@ -63,6 +67,3 @@ Stovky záznamů z Posledních sobot a videí o Nette naleznete pod jednou stře </div> </div> - -{{sitename: Best Practices}} -{{leftbar: www:@menu-common}} diff --git a/best-practices/cs/@meta.texy b/best-practices/cs/@meta.texy new file mode 100644 index 0000000000..0c9a1e9689 --- /dev/null +++ b/best-practices/cs/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Návody a postupy}} +{{leftbar: www:@menu-common}} diff --git a/best-practices/cs/attribute-requires.texy b/best-practices/cs/attribute-requires.texy new file mode 100644 index 0000000000..682d1640a5 --- /dev/null +++ b/best-practices/cs/attribute-requires.texy @@ -0,0 +1,177 @@ +Jak používat atribut `#[Requires]` +********************************** + +.[perex] +Když píšete webovou aplikaci, často se setkáte s potřebou omezit přístup k určitým částem vaší aplikace. Možná chcete, aby některé požadavky mohly odesílat data pouze pomocí formuláře (tedy metodou POST), nebo aby byly přístupné pouze pro AJAXové volání. V Nette Frameworku 3.2 se objevil nový nástroj, který vám umožní taková omezení nastavit velmi elegantně a přehledně: atribut `#[Requires]`. + +Atribut je speciální značka v PHP, kterou přidáte před definici třídy nebo metody. Protože jde vlastně o třídu, aby vám následující příklady fungovaly, je nutné uvést klauzuli use: + +```php +use Nette\Application\Attributes\Requires; +``` + +Atribut `#[Requires]` můžete použít u samotné třídy presenteru a také na těchto metodách: + +- `action<Action>()` +- `render<View>()` +- `handle<Signal>()` +- `createComponent<Name>()` + +Poslední dvě metody se týkají i komponent, tedy atribut můžete používat i u nich. + +Pokud nejsou splněny podmínky, které atribut uvádí, dojde k vyvolání HTTP chyby 4xx. + + +Metody HTTP +----------- + +Můžete specifikovat, které HTTP metody (jako GET, POST atd.) jsou pro přístup povolené. Například, pokud chcete povolit přístup pouze odesíláním formuláře, nastavíte: + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +Proč byste měli používat POST místo GET pro akce měnící stav a jak na to? [Přečtěte si návod |post-links]. + +Můžete uvést metodu nebo pole metod. Speciálním případem je hodnota `'*'`, která povolí všechny metody, což standardně presentery z [bezpečnostních důvodů nedovolují |application:presenters#Kontrola HTTP metody]. + + +AJAXové volání +-------------- + +Pokud chcete, aby byl presenter nebo metoda dostupná pouze pro AJAXové požadavky, použijte: + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Stejný původ +------------ + +Pro zvýšení bezpečnosti můžete vyžadovat, aby byl požadavek učiněn ze stejné domény. Tím zabráníte [zranitelnosti CSRF |nette:vulnerability-protection#Cross-Site Request Forgery CSRF]: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +U metod `handle<Signal>()` je přístup ze stejné domény vyžadován automaticky. Takže pokud naopak chcete povolit přístup z jakékoliv domény, uveďte: + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Přístup přes forward +-------------------- + +Někdy je užitečné omezit přístup k presenteru tak, aby byl dostupný pouze nepřímo, například použitím metody `forward()` nebo `switch()` z jiného presenteru. Takto se třeba chrání error-presentery, aby je nebylo možné vyvolat z URL: + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +V praxi bývá často potřeba označit určité views, ke kterým se lze dostat až na základě logiky v presenteru. Tedy opět, aby je nebylo možné otevřít přímo: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = $this->facade->getProduct($id); + if (!$product) { + $this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Konkrétní akce +-------------- + +Můžete také omezit, že určitý kód, třeba vytvoření komponenty, bude dostupné pouze pro specifické akce v presenteru: + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +V případě jedné akce není potřeba zapisovat pole: `#[Requires(actions: 'default')]` + + +Vlastní atributy +---------------- + +Pokud chcete použít atribut `#[Requires]` opakovaně s týmž nastavením, můžete si vytvořit vlastní atribut, který bude dědit `#[Requires]` a nastaví ho podle potřeb. + +Například `#[SingleAction]` umožní přístup pouze přes akci `default`: + +```php +#[\Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Nebo `#[RestMethods]` umožní přístup přes všechny HTTP metody používané pro REST API: + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Závěr +----- + +Atribut `#[Requires]` vám dává velkou flexibilitu a kontrolu nad tím, jak jsou vaše webové stránky přístupné. Pomocí jednoduchých, ale mocných pravidel můžete zvýšit bezpečnost a správné fungování vaší aplikace. Jak vidíte, použití atributů v Nette může vaši práci nejen usnadnit, ale i zabezpečit. diff --git a/best-practices/cs/composer.texy b/best-practices/cs/composer.texy index 2f0c503870..aebe726bbc 100644 --- a/best-practices/cs/composer.texy +++ b/best-practices/cs/composer.texy @@ -58,7 +58,7 @@ composer update Composer stáhne Nette Database do složky `vendor/`. Dále vytvoří soubor `composer.lock`, který obsahuje informace o tom, které verze knihoven přesně nainstaloval. -Composer vygeneruje soubor `vendor/autoload.php`, který můžeme jednoduše zainkludovat a začít používat knihovny bez jakékoli další práce: +Composer vygeneruje soubor `vendor/autoload.php`, který můžeme jednoduše inkludovat a začít používat knihovny bez jakékoli další práce: ```php require __DIR__ . '/vendor/autoload.php'; @@ -96,7 +96,7 @@ Nový projekt na Nette vytvoříte pomocí jediného příkazu: composer create-project nette/web-project nazev-projektu ``` -Jako `nazev-projektu` vložte název adresáře pro svůj projekt a potvrďte. Composer stáhne repozitář `nette/web-project` z GitHubu, který už obsahuje soubor `composer.json`, a hned potom Nette Framework. Mělo by již stačit pouze [nastavit oprávnění |nette:troubleshooting#nastaveni-prav-adresaru] na zápis do složek `temp/` a `log/` a projekt by měl ožít. +Jako `nazev-projektu` vložte název adresáře pro svůj projekt a potvrďte. Composer stáhne repozitář `nette/web-project` z GitHubu, který už obsahuje soubor `composer.json`, a hned potom Nette Framework. Mělo by již stačit pouze [nastavit oprávnění |nette:troubleshooting#Nastavení práv adresářů] na zápis do složek `temp/` a `log/` a projekt by měl ožít. Pokud víte, na jaké verzi bude PHP projekt hostován, nezapomeňte [ji nastavit |#Verze PHP]. @@ -124,8 +124,7 @@ Takto se verze zapíše do souboru `composer.json`: } ``` -Nicméně číslo verze PHP se uvádí ještě na jiném místě souboru, a to v sekci `require`. Zatímco první číslo určuje, pro jakou verzi se budou instalovat balíčky, druhé číslo říká, pro jakou verzi je napsaná samotná aplikace. -A podle něj například PhpStorm nastavuje *PHP language level*. (Samozřejmě nedává smysl, aby se tyto verze lišily, takže dvojí zápis je nedomyšlenost.) Tuto verzi nastavíte příkazem: +Nicméně číslo verze PHP se uvádí ještě na jiném místě souboru, a to v sekci `require`. Zatímco první číslo určuje, pro jakou verzi se budou instalovat balíčky, druhé číslo říká, pro jakou verzi je napsaná samotná aplikace. A podle něj například PhpStorm nastavuje *PHP language level*. (Samozřejmě nedává smysl, aby se tyto verze lišily, takže dvojí zápis je nedomyšlenost.) Tuto verzi nastavíte příkazem: ```shell composer require php 8.2.3 --no-update @@ -142,6 +141,12 @@ Nebo přímo v souboru `composer.json`: ``` +Ignorování verze PHP +==================== + +Balíčky zpravidla mívají uvedenou jak nejnižší verzi PHP, se kterou jsou kompatibilní, tak i nejvyšší, se kterou jsou testované. Pokud se chystáte používat verzi PHP ještě novější, třeba z důvodu testování, Composer odmítne takový balíček nainstalovat. Řešením je volba `--ignore-platform-req=php+`, která způsobí, že Composer bude ignorovat horní limity požadované verze PHP. + + Planá hlášení ============= @@ -183,7 +188,7 @@ Nicméně je možné používat Composer i pro načítání dalších tříd i m Následně je potřeba při každé změně spustit příkaz `composer dumpautoload` a nechat autoloadovací tabulky přegenerovat. To je nesmírně nepohodlné a daleko lepší je tento úkol svěřit [RobotLoaderu|robot-loader:], který stejnou činnost provádí automaticky na pozadí a mnohem rychleji. -Druhou možností je dodržovat [PSR-4|https://www.php-fig.org/psr/psr-4/]. Zjednodušeně řečeno jde o systém, kdy jmenné prostory a názvy tříd odpovídají adresářové struktuře a názvům souborů, tedy např. `App\Router\RouterFactory` bude v souboru `/path/to/App/Router/RouterFactory.php`. Příklad konfigurace: +Druhou možností je dodržovat [PSR-4|https://www.php-fig.org/psr/psr-4/]. Zjednodušeně řečeno jde o systém, kdy jmenné prostory a názvy tříd odpovídají adresářové struktuře a názvům souborů, tedy např. `App\Core\RouterFactory` bude v souboru `/path/to/App/Core/RouterFactory.php`. Příklad konfigurace: ```js { @@ -224,8 +229,7 @@ Nebo můžete nainstalovat konkrétní verzi, například 4.0.0-RC2: composer require nette/utils:4.0.0-RC2 ``` -Když ale na knihovně závisí jiný balíček, který je uzamčený na starší verzi (např. `^3.1`), tak je ideální balík zaktualizovat, aby s novou verzí fungoval. -Pokud však chcete omezení jen obejít a donutit Composer nainstalovat vývojovou verzi a předstírat, že jde o verzi starší (např. 3.1.6), můžete použít klíčové slovo `as`: +Když ale na knihovně závisí jiný balíček, který je uzamčený na starší verzi (např. `^3.1`), tak je ideální balík zaktualizovat, aby s novou verzí fungoval. Pokud však chcete omezení jen obejít a donutit Composer nainstalovat vývojovou verzi a předstírat, že jde o verzi starší (např. 3.1.6), můžete použít klíčové slovo `as`: ```shell composer require nette/utils "4.0.x-dev as 3.1.6" @@ -276,5 +280,3 @@ Composer je úzce propojený s verzovacím nástrojem [Git |https://git-scm.com] ```shell composer -g config preferred-install dist ``` - -{{sitename: Best Practices}} diff --git a/best-practices/cs/creating-editing-form.texy b/best-practices/cs/creating-editing-form.texy index 1b3bb7f2c6..4530f060d4 100644 --- a/best-practices/cs/creating-editing-form.texy +++ b/best-practices/cs/creating-editing-form.texy @@ -29,11 +29,11 @@ class RecordPresenter extends Nette\Application\UI\Presenter // ... přidáme políčka formuláře ... - $form->onSuccess[] = [$this, 'recordFormSucceeded']; + $form->onSuccess[] = $this->recordFormSucceeded(...); return $form; } - public function recordFormSucceeded(Form $form, array $data): void + private function recordFormSucceeded(Form $form, array $data): void { $this->facade->add($data); // přidání záznamu do databáze $this->flashMessage('Successfully added'); @@ -91,11 +91,11 @@ class RecordPresenter extends Nette\Application\UI\Presenter // ... přidáme políčka formuláře ... $form->setDefaults($this->record); // nastavení výchozích hodnot - $form->onSuccess[] = [$this, 'recordFormSucceeded']; + $form->onSuccess[] = $this->recordFormSucceeded(...); return $form; } - public function recordFormSucceeded(Form $form, array $data): void + private function recordFormSucceeded(Form $form, array $data): void { $this->facade->update($this->record->id, $data); // aktualizace záznamu $this->flashMessage('Successfully updated'); @@ -104,7 +104,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter } ``` -V metodě *action*, která se spouští hned na začátku [životního cyklu presenteru|application:presenters#zivotni-cyklus-presenteru], ověříme existenci záznamu a oprávnění uživatele jej editovat. +V metodě *action*, která se spouští hned na začátku [životního cyklu presenteru |application:presenters#Životní cyklus presenteru], ověříme existenci záznamu a oprávnění uživatele jej editovat. Záznam si uložíme do property `$record`, abychom jej měli k dispozici v metodě `createComponentRecordForm()` kvůli nastavení výchozích hodnot, a `recordFormSucceeded()` kvůli ID. Alternativním řešením by bylo nastavit výchozí hodnoty přímo v `actionEdit()` a hodnotu ID, která je součástí URL, získat pomocí `getParameter('id')`: @@ -153,7 +153,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter public function actionAdd(): void { $form = $this->getComponent('recordForm'); - $form->onSuccess[] = [$this, 'addingFormSucceeded']; + $form->onSuccess[] = $this->addingFormSucceeded(...); } public function actionEdit(int $id): void @@ -168,7 +168,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter $form = $this->getComponent('recordForm'); $form->setDefaults($record); // nastavení výchozích hodnot - $form->onSuccess[] = [$this, 'editingFormSucceeded']; + $form->onSuccess[] = $this->editingFormSucceeded(...); } protected function createComponentRecordForm(): Form @@ -185,14 +185,14 @@ class RecordPresenter extends Nette\Application\UI\Presenter return $form; } - public function addingFormSucceeded(Form $form, array $data): void + private function addingFormSucceeded(Form $form, array $data): void { $this->facade->add($data); // přidání záznamu do databáze $this->flashMessage('Successfully added'); $this->redirect('...'); } - public function editingFormSucceeded(Form $form, array $data): void + private function editingFormSucceeded(Form $form, array $data): void { $id = (int) $this->getParameter('id'); $this->facade->update($id, $data); // aktualizace záznamu @@ -203,4 +203,3 @@ class RecordPresenter extends Nette\Application\UI\Presenter ``` {{priority: -1}} -{{sitename: Best Practices}} diff --git a/best-practices/cs/dynamic-snippets.texy b/best-practices/cs/dynamic-snippets.texy index 600dc4b9bd..104844e59c 100644 --- a/best-practices/cs/dynamic-snippets.texy +++ b/best-practices/cs/dynamic-snippets.texy @@ -35,7 +35,7 @@ public function handleUnlike(int $articleId): void Ajaxizace ========= -Pojďme nyní tuto jednoduchou aplikaci vybavit AJAXem. Změna hodnocení článku není natolik důležitá, aby muselo dojít k přesměrování, a proto by ideálně měla probíhat AJAXem na pozadí. Využijeme [obslužného skriptu z doplňků |https://componette.org/vojtech-dobes/nette.ajax.js/] s obvyklou konvencí, že AJAXové odkazy mají CSS třídu `ajax`. +Pojďme nyní tuto jednoduchou aplikaci vybavit AJAXem. Změna hodnocení článku není natolik důležitá, aby muselo dojít k přesměrování, a proto by ideálně měla probíhat AJAXem na pozadí. Využijeme [obslužného skriptu z doplňků |application:ajax#Naja] s obvyklou konvencí, že AJAXové odkazy mají CSS třídu `ajax`. Nicméně jak na to konkrétně? Nette nabízí 2 cesty: cestu tzv. dynamických snippetů a cestu komponent. Obě dvě mají svá pro a proti, a proto si je ukážeme jednu po druhé. @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connection->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,7 +101,7 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connection->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` @@ -151,7 +151,7 @@ Samozřejmě se nám změní šablona view a do presenteru budeme muset doplnit ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); @@ -171,4 +171,3 @@ protected function createComponentLikeControl() Máme téměř hotovo: aplikace nyní bude fungovat AJAXově. I zde nás čeká aplikaci optimalizovat, protože vzhledem k použití Nette Database se při zpracování signálu zbytečně načtou všechny články z databáze namísto jednoho. Výhodou však je, že nedojde k jejich vykreslování, protože se vyrenderuje skutečně jen naše komponenta. {{priority: -1}} -{{sitename: Best Practices}} diff --git a/best-practices/cs/editors-and-tools.texy b/best-practices/cs/editors-and-tools.texy index e65c322423..13e3d10138 100644 --- a/best-practices/cs/editors-and-tools.texy +++ b/best-practices/cs/editors-and-tools.texy @@ -61,10 +61,10 @@ Code Checker [Code Checker|code-checker:] zkontroluje a případně opraví některé z formálních chyb ve vašich zdrojových kódech: -- odstraňuje [BOM |nette:glossary#bom] +- odstraňuje [BOM |nette:glossary#BOM] - kontroluje validitu [Latte |latte:] šablon - kontroluje validitu souborů `.neon`, `.php` a `.json` -- kontroluje výskyt [kontrolních znaků |nette:glossary#kontrolní znaky] +- kontroluje výskyt [kontrolních znaků |nette:glossary#Kontrolní znaky] - kontroluje, zda je soubor kódován v UTF-8 - kontroluje chybně zapsané `/* @anotace */` (chybí hvězdička) - odstraňuje ukončovací `?>` u PHP souborů @@ -82,5 +82,3 @@ Requirements Checker ==================== Šlo o nástroj, který testoval běhové prostředí serveru a informoval, zda (a do jaké míry) je možné framework používat. V současnosti je Nette možné používat na každém serveru, který má minimální požadovanou verzi PHP. - -{{sitename: Best Practices}} diff --git a/best-practices/cs/form-reuse.texy b/best-practices/cs/form-reuse.texy index c659b8bcb4..ac95ca2617 100644 --- a/best-practices/cs/form-reuse.texy +++ b/best-practices/cs/form-reuse.texy @@ -85,7 +85,7 @@ Metoda `createForm()` zatím nedělá nic užitečného, ale to se rychle změn Závislosti továrny ================== -Časem se ukáže, že potřebujeme, aby formuláře byly multijazyčné. To znamená, že všem formulářům musíme nastavit tzv. [translator|forms:rendering#Překládání]. Za tím účelem upravíme třídu `FormFactory` tak, aby přijímala objekt `Translator` jako závislost v konstruktoru, a předáme jej formuláři: +Časem se ukáže, že potřebujeme, aby formuláře byly multijazyčné. To znamená, že všem formulářům musíme nastavit tzv. [translator |forms:rendering#Překládání]. Za tím účelem upravíme třídu `FormFactory` tak, aby přijímala objekt `Translator` jako závislost v konstruktoru, a předáme jej formuláři: ```php use Nette\Localization\Translator; @@ -114,8 +114,7 @@ Jelikož metodu `createForm()` volají i ostatní metody tvořící specifické Více továrních tříd =================== -Alternativně můžete vytvořit více tříd pro každý formulář, který chcete použít ve vaší aplikaci. -Tento přístup může zvýšit čitelnost kódu a usnadnit správu formulářů. Původní `FormFactory` necháme vytvářet jen čistý formulář se základní konfigurací (například s podporou překladů) a pro editační formulář vytvoříme novou továrnu `EditFormFactory`. +Alternativně můžete vytvořit více tříd pro každý formulář, který chcete použít ve vaší aplikaci. Tento přístup může zvýšit čitelnost kódu a usnadnit správu formulářů. Původní `FormFactory` necháme vytvářet jen čistý formulář se základní konfigurací (například s podporou překladů) a pro editační formulář vytvoříme novou továrnu `EditFormFactory`. ```php class FormFactory @@ -152,7 +151,7 @@ class EditFormFactory } ``` -Velmi důležité je, aby vazba mezi třídami `FormFactory` a `EditFormFactory` byla realizována kompozicí, nikoliv objektovou dědičností: +Velmi důležité je, aby vazba mezi třídami `FormFactory` a `EditFormFactory` byla realizována [kompozicí |nette:introduction-to-object-oriented-programming#Kompozice], nikoliv [objektovou dědičností |nette:introduction-to-object-oriented-programming#Dědičnost]: ```php // ⛔ TAKHLE NE! SEM DĚDIČNOST NEPATŘÍ @@ -169,10 +168,9 @@ class EditFormFactory extends FormFactory } ``` -Použití dedičnosti by bylo v tomto případě zcela kontraproduktivní. Na problémy byste narazili velmi rychle. Třeba ve chvíli, kdybyste chtěli přidat metodě `create()` parametry; PHP by zahlásilo chybu, že se její signatura liší od rodičovské. -Nebo při předávání závislosti do třídy `EditFormFactory` přes konstruktor. Nastala by situace, které říkáme [constructor hell |dependency-injection:passing-dependencies#Constructor hell]. +Použití dedičnosti by bylo v tomto případě zcela kontraproduktivní. Na problémy byste narazili velmi rychle. Třeba ve chvíli, kdybyste chtěli přidat metodě `create()` parametry; PHP by zahlásilo chybu, že se její signatura liší od rodičovské. Nebo při předávání závislosti do třídy `EditFormFactory` přes konstruktor. Nastala by situace, které říkáme [constructor hell |dependency-injection:passing-dependencies#Constructor hell]. -Obecně je lepší dávat přednost kompozici před dědičností. +Obecně je lepší dávat přednost [kompozici před dědičností |dependency-injection:faq#Proč se upřednostňuje kompozice před dědičností]. Obsluha formuláře @@ -195,11 +193,11 @@ class EditFormFactory $form->addText('title', 'Titulek:'); // zde se přidávají další formulářová pole $form->addSubmit('send', 'Odeslat'); - $form->onSuccess[] = [$this, 'processForm']; + $form->onSuccess[] = $this->processForm(...); return $form; } - public function processForm(Form $form, array $data): void + private function processForm(Form $form, array $data): void { try { // zpracování odeslaných dat @@ -249,10 +247,10 @@ class EditForm extends Form public function __construct(Translator $translator) { parent::__construct(); - $form->addText('title', 'Titulek:'); + $this->addText('title', 'Titulek:'); // zde se přidávají další formulářová pole - $form->addSubmit('send', 'Odeslat'); - $form->setTranslator($translator); + $this->addSubmit('send', 'Odeslat'); + $this->setTranslator($translator); } } ``` @@ -265,8 +263,7 @@ Je potřeba si uvědomit, že třída `Form` je v první řadě nástrojem pro s Komponenta s formulářem ======================= -Zcela jiný přístup představuje tvorba [komponenty|application:components], jejíž součástí je formulář. To dává nové možnosti, například renderovat formulář specifickým způsobem, neboť součástí komponenty je i šablona. -Nebo lze využít signály pro AJAXovou komunikaci a donačítání informací do formuláře, například pro napovídání, atd. +Zcela jiný přístup představuje tvorba [komponenty|application:components], jejíž součástí je formulář. To dává nové možnosti, například renderovat formulář specifickým způsobem, neboť součástí komponenty je i šablona. Nebo lze využít signály pro AJAXovou komunikaci a donačítání informací do formuláře, například pro napovídání, atd. ```php @@ -287,12 +284,12 @@ class EditControl extends Nette\Application\UI\Control $form->addText('title', 'Titulek:'); // zde se přidávají další formulářová pole $form->addSubmit('send', 'Odeslat'); - $form->onSuccess[] = [$this, 'processForm']; + $form->onSuccess[] = $this->processForm(...); return $form; } - public function processForm(Form $form, array $data): void + private function processForm(Form $form, array $data): void { try { // zpracování odeslaných dat @@ -309,7 +306,7 @@ class EditControl extends Nette\Application\UI\Control } ``` -Ještě vytvoříme továrnu, která bude tuto komponentu vyrábět. Stačí [zapsat její rozhraní|application:components#Komponenty se závislostmi]: +Ještě vytvoříme továrnu, která bude tuto komponentu vyrábět. Stačí [zapsat její rozhraní |application:components#Komponenty se závislostmi]: ```php interface EditControlFactory @@ -335,7 +332,7 @@ class MyPresenter extends Nette\Application\UI\Presenter ) { } - protected function createComponentEditForm(): Form + protected function createComponentEditForm(): EditControl { $control = $this->controlFactory->create(); @@ -349,5 +346,3 @@ class MyPresenter extends Nette\Application\UI\Presenter } } ``` - -{{sitename: Best Practices}} diff --git a/best-practices/cs/inject-method-attribute.texy b/best-practices/cs/inject-method-attribute.texy index 9bd667a09f..f831f0535f 100644 --- a/best-practices/cs/inject-method-attribute.texy +++ b/best-practices/cs/inject-method-attribute.texy @@ -4,16 +4,13 @@ Metody a atributy inject .[perex] V tomto článku se zaměříme na různé způsoby předávání závislostí do presenterů v Nette frameworku. Porovnáme preferovaný způsob, kterým je konstruktor, s dalšími možnostmi, jako jsou metody a atributy `inject`. -I pro presentery platí, že předání závislostí pomocí [konstruktoru |dependency-injection:passing-dependencies#Předávání konstruktorem] je preferovaná cesta. -Pokud ale vytváříte společného předka, od kterého dědí ostatní presentery (např. `BasePresenter`), a tento předek má také závislosti, nastane problém, kterému říkáme [constructor hell |dependency-injection:passing-dependencies#Constructor hell]. -Ten lze obejít pomocí alternativních cest, které představují metody a atributy (anotace) `inject`. +I pro presentery platí, že předání závislostí pomocí [konstruktoru |dependency-injection:passing-dependencies#Předávání konstruktorem] je preferovaná cesta. Pokud ale vytváříte společného předka, od kterého dědí ostatní presentery (např. `BasePresenter`), a tento předek má také závislosti, nastane problém, kterému říkáme [constructor hell |dependency-injection:passing-dependencies#Constructor hell]. Ten lze obejít pomocí alternativních cest, které představují metody a atributy (anotace) `inject`. Metody `inject*()` ================== -Jde o formu předávání závislosti [setterem |dependency-injection:passing-dependencies#Předávání setterem]. Název těchto setterů začíná předponou `inject`. -Nette DI takto pojmenované metody automaticky zavolá hned po vytvoření instance presenteru a předá jim všechny požadované závislosti. Musí být tudíž deklarované jako public. +Jde o formu předávání závislosti [setterem |dependency-injection:passing-dependencies#Předávání setterem]. Název těchto setterů začíná předponou `inject`. Nette DI takto pojmenované metody automaticky zavolá hned po vytvoření instance presenteru a předá jim všechny požadované závislosti. Musí být tudíž deklarované jako public. Metody `inject*()` lze považovat za jakési rozšíření konstruktoru do více metod. Díky tomu může `BasePresenter` převzít závislosti přes jinou metodu a ponechat konstruktor volný pro své potomky: @@ -61,7 +58,4 @@ class MyPresenter extends Nette\Application\UI\Presenter Výhodou tohoto způsobu předávání závislostí byla velice úsporná podoba zápisu. Nicméně s příchodem [constructor property promotion |https://blog.nette.org/cs/php-8-0-kompletni-prehled-novinek#toc-constructor-property-promotion] se jeví snazší použít konstruktor. -Naopak tento způsob trpí stejnými nedostatky, jako předávání závislosti do properites obecně: nemáme kontrolu nad změnami v proměnné a zároveň se proměnná stává součástí veřejného rozhraní třídy, což je nežádnoucí. - - -{{sitename: Best Practices}} +Naopak tento způsob trpí stejnými nedostatky, jako předávání závislosti do properties obecně: nemáme kontrolu nad změnami v proměnné a zároveň se proměnná stává součástí veřejného rozhraní třídy, což je nežádnoucí. diff --git a/best-practices/cs/lets-create-contact-form.texy b/best-practices/cs/lets-create-contact-form.texy index 78f2f92886..23fde0f0c5 100644 --- a/best-practices/cs/lets-create-contact-form.texy +++ b/best-practices/cs/lets-create-contact-form.texy @@ -24,22 +24,20 @@ class HomePresenter extends Presenter $form->addTextarea('message', 'Zpráva:') ->setRequired('Zadejte zprávu'); $form->addSubmit('send', 'Odeslat'); - $form->onSuccess[] = [$this, 'contactFormSucceeded']; + $form->onSuccess[] = $this->contactFormSucceeded(...); return $form; } - public function contactFormSucceeded(Form $form, $data): void + private function contactFormSucceeded(Form $form, $data): void { // odeslání emailu } } ``` -Jak vidíte, vytvořili jsme dvě metody. První metoda `createComponentContactForm()` vytváří nový formulář. Ten má políčka pro jméno, email a zprávu, která přidáváme metodami `addText()`, `addEmail()` a `addTextArea()`. Také jsme přidali tlačítko pro odeslání formuláře. -Ale co když uživatel nevyplní nějaké pole? V takovém případě bychom mu měli dát vědět, že je to povinné pole. Toho jsme docílili metodou `setRequired()`. -Nakonec jsme přidali také [událost |nette:glossary#Události] `onSuccess`, která se spustí, pokud je formulář úspěšně odeslán. V našem případě zavolá metodu `contactFormSucceeded`, která se postará o zpracování odeslaného formuláře. To do kódu doplníme za okamžik. +Jak vidíte, vytvořili jsme dvě metody. První metoda `createComponentContactForm()` vytváří nový formulář. Ten má políčka pro jméno, email a zprávu, která přidáváme metodami `addText()`, `addEmail()` a `addTextArea()`. Také jsme přidali tlačítko pro odeslání formuláře. Ale co když uživatel nevyplní nějaké pole? V takovém případě bychom mu měli dát vědět, že je to povinné pole. Toho jsme docílili metodou `setRequired()`. Nakonec jsme přidali také [událost |nette:glossary#události] `onSuccess`, která se spustí, pokud je formulář úspěšně odeslán. V našem případě zavolá metodu `contactFormSucceeded`, která se postará o zpracování odeslaného formuláře. To do kódu doplníme za okamžik. -Komponentu `contantForm` necháme vykreslit v šabloně `templates/Home/default.latte`: +Komponentu `contactForm` necháme vykreslit v šabloně `Home/default.latte`: ```latte {block content} @@ -50,9 +48,6 @@ Komponentu `contantForm` necháme vykreslit v šabloně `templates/Home/default. Pro samotné odeslání emailu vytvoříme novou třídu, kterou nazveme `ContactFacade` a umístíme ji do souboru `app/Model/ContactFacade.php`: ```php -<?php -declare(strict_types=1); - namespace App\Model; use Nette\Mail\Mailer; @@ -108,7 +103,7 @@ class HomePresenter extends Presenter } ``` -Poté, co se email odešle, ještě zobrazíme uživateli tzv. [flash message |application:components#flash-zpravy], potvrzující, že zpráva se odeslala, a poté přesměrujeme na další stránku, aby nebylo možné formulář opakovaně odeslat pomocí *refresh* v prohlížeči. +Poté, co se email odešle, ještě zobrazíme uživateli tzv. [flash message |application:components#Flash zprávy], potvrzující, že zpráva se odeslala, a poté přesměrujeme na další stránku, aby nebylo možné formulář opakovaně odeslat pomocí *refresh* v prohlížeči. Tak, a pokud všechno funguje, měli byste být schopni odeslat email z vašeho kontaktního formuláře. Gratuluji! @@ -131,7 +126,7 @@ Zatím se odesílá prostý textový email obsahující pouze zprávu odeslanou </html> ``` -Zbývá upravit `ContactFacade`, aby tuto šablonu používal. V konstruktoru si vyžádáme třídu `LatteFactory`, která umí vyrobit objekt `Latte\Engine`, tedy [vykreslovač Latte šablon |latte:develop#jak-vykreslit-sablonu]. Pomocí metody `renderToString()` šablonu vykreslíme do souboru, prvním parametrem je cesta k šabloně a druhým jsou proměnné. +Zbývá upravit `ContactFacade`, aby tuto šablonu používal. V konstruktoru si vyžádáme třídu `LatteFactory`, která umí vyrobit objekt `Latte\Engine`, tedy [vykreslovač Latte šablon |latte:develop#Jak vykreslit šablonu]. Pomocí metody `renderToString()` šablonu vykreslíme do souboru, prvním parametrem je cesta k šabloně a druhým jsou proměnné. ```php namespace App\Model; @@ -221,6 +216,3 @@ parameters: ``` A je hotovo! - - -{{sitename: Best Practices}} diff --git a/best-practices/cs/microsites.texy b/best-practices/cs/microsites.texy new file mode 100644 index 0000000000..b2b0c92b0a --- /dev/null +++ b/best-practices/cs/microsites.texy @@ -0,0 +1,63 @@ +Jak psát mikro-weby +******************* + +Představte si, že potřebujete rychle vytvořit malý web pro nadcházející akci vaší firmy. Má to být jednoduché, rychlé a bez zbytečných komplikací. Možná si myslíte, že pro tak malý projekt nepotřebujete robustní framework. Ale co když použití Nette frameworku může tento proces zásadně zjednodušit a zrychlit? + +Přece i při tvorbě jednoduchých webů se nechcete vzdát pohodlí. Nechcete vymýšlet to, co už bylo jednou vyřešené. Buďte klidně líný a nechte se rozmazlovat. Nette Framework lze skvěle využít i jako micro framework. + +Jak takový microsite může vypadat? Například tak, že celý kód webu umístíme do jediného souboru `index.php` ve veřejné složce: + +```php +<?php + +require __DIR__ . '/../vendor/autoload.php'; + +$configurator = new Nette\Bootstrap\Configurator; +$configurator->enableTracy(__DIR__ . '/../log'); +$configurator->setTempDirectory(__DIR__ . '/../temp'); + +// vytvoř DI kontejner na základě konfigurace v config.neon +$configurator->addConfig(__DIR__ . '/../app/config.neon'); +$container = $configurator->createContainer(); + +// nastavíme routing +$router = new Nette\Application\Routers\RouteList; +$container->addService('router', $router); + +// routa pro URL https://example.com/ +$router->addRoute('', function ($presenter, Nette\Http\Request $httpRequest) { + // detekujeme jazyk prohlížeče a přesměrujeme na URL /en nebo /de atd. + $supportedLangs = ['en', 'de', 'cs']; + $lang = $httpRequest->detectLanguage($supportedLangs) ?: reset($supportedLangs); + $presenter->redirectUrl("/$lang"); +}); + +// routa pro URL https://example.com/cs nebo https://example.com/en +$router->addRoute('<lang cs|en>', function ($presenter, string $lang) { + // zobrazíme příslušnou šablonu, například ../templates/en.latte + $template = $presenter->createTemplate() + ->setFile(__DIR__ . '/../templates/' . $lang . '.latte'); + return $template; +}); + +// spusť aplikaci! +$container->getByType(Nette\Application\Application::class)->run(); +``` + +Vše ostatní budou šablony uložené v nadřazené složce `/templates`. + +PHP kód v `index.php` nejprve [připraví prostředí |bootstrap:], poté definuje [routy |application:routing#Dynamické routování s callbacky] a nakonec spustí aplikaci. Výhodou je, že druhý parametr funkce `addRoute()` může být callable, který se po otevření odpovídající stránky vykoná. + + +Proč používat Nette pro microsite? +---------------------------------- + +- Programátoři, kteří někdy vyzkoušeli [Tracy|tracy:], si dnes neumí představit, že by něco programovali bez ní. +- Především ale využijete šablonovací systém [Latte|latte:], protože už od 2 stránek budete chtít mít oddělený [layout a obsah|latte:template-inheritance]. +- A rozhodně se chcete spolehout na [automatické escapování |latte:safety-first], aby nevznikla zranitelnost XSS +- Nette taky zajistí, že se při chybě nikdy neobrazí programátorské chybové hlášky PHP, ale uživateli srozumitelná stránka. +- Pokud chcete získávat zpětnou vazbu od uživatelů, například v podobě kontaktního formuláře, tak ještě přidáte [formuláře|forms:] a [databázi|database:]. +- Vyplněné formuláře si taktéž můžete nechat snadno [odesílat emailem|mail:]. +- Někdy se vám může hodit [kešování|caching:], například pokud stahujete a zobrazujete feedy. + +V dnešní době, kdy je rychlost a efektivita klíčová, je důležité mít nástroje, které vám umožní dosáhnout výsledků bez zbytečného zdržování. Nette framework vám nabízí právě to - rychlý vývoj, bezpečnost a širokou škálu nástrojů, jako je Tracy a Latte, které zjednodušují proces. Stačí nainstalovat pár Nette balíčků a vybudovat takovou microsite je najednou úplná hračka. A víte, že se nikde neskrývá žádná bezpečnostní díra. diff --git a/best-practices/cs/pagination.texy b/best-practices/cs/pagination.texy index 46a00cd256..cbf0a7798d 100644 --- a/best-practices/cs/pagination.texy +++ b/best-practices/cs/pagination.texy @@ -11,7 +11,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -34,7 +33,7 @@ class ArticleRepository V presenteru si pak injectujeme modelovou třídu a v render metodě si vyžádáme publikované články, které předáme do šablony: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -53,7 +52,7 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -V šabloně se pak postaráme o výpis článků: +V šabloně `default.latte` se pak postaráme o výpis článků: ```latte {block content} @@ -114,7 +113,7 @@ Následně se pustíme do úprav presenteru. Do render metody budeme předávat Dále také render metodu rozšíříme o získání instance Paginatoru, jeho nastavení a výběru správných článků pro zobrazení v šabloně. HomePresenter bude po úpravách vypadat takto: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -181,7 +180,7 @@ class HomePresenter extends Nette\Application\UI\Presenter ``` -Takto jsme doplnili stránku o možnost stránkování pomocí Paginatoru. V případě, kdy namísto [Nette Database Core |database:core] jako databázovou vrstvu použijeme [Nette Database Explorer |database:explorer], jsme schopni implementovat stránkování i bez použití Paginatoru. Třída `Nette\Database\Table\Selection` totiž obsahuje metodu [page |api:Nette\Database\Table\Selection::_page] s logikou stránkování převzatou z Paginatoru. +Takto jsme doplnili stránku o možnost stránkování pomocí Paginatoru. V případě, kdy namísto [Nette Database Core |database:sql-way] jako databázovou vrstvu použijeme [Nette Database Explorer |database:explorer], jsme schopni implementovat stránkování i bez použití Paginatoru. Třída `Nette\Database\Table\Selection` totiž obsahuje metodu [page |api:Nette\Database\Table\Selection::_page] s logikou stránkování převzatou z Paginatoru. Repozitář bude při tomto způsobu implementace vypadat takto: @@ -190,7 +189,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -198,7 +196,6 @@ class ArticleRepository ) { } - public function findPublishedArticles(): Nette\Database\Table\Selection { return $this->database->table('articles') @@ -211,7 +208,7 @@ class ArticleRepository V presenteru nemusíme vytvářet Paginator, použijeme místo něj metodu třídy `Selection`, kterou nám vrací repositář: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -274,4 +271,3 @@ Protože do šablony nyní neposíláme Paginator, upravíme část zobrazujíc Tímto způsobem jsme implementovali stránkovací mechanismus bez použití Paginatoru. {{priority: -1}} -{{sitename: Best Practices}} diff --git a/best-practices/cs/passing-settings-to-presenters.texy b/best-practices/cs/passing-settings-to-presenters.texy index 197d4c78ce..b510a65c1a 100644 --- a/best-practices/cs/passing-settings-to-presenters.texy +++ b/best-practices/cs/passing-settings-to-presenters.texy @@ -47,5 +47,3 @@ class MyPresenter extends Nette\Application\UI\Presenter } } ``` - -{{sitename: Best Practices}} diff --git a/best-practices/cs/post-links.texy b/best-practices/cs/post-links.texy new file mode 100644 index 0000000000..2d24e00a67 --- /dev/null +++ b/best-practices/cs/post-links.texy @@ -0,0 +1,56 @@ +Jak správně používat POST odkazy +******************************** + +.[perex] +Ve webových aplikacích, zejména v administrativních rozhraních, by mělo být základním pravidlem, že akce měnící stav serveru by neměly být prováděny prostřednictvím HTTP metody GET. Jak název metody napovídá, GET by měl sloužit pouze k získání dat, nikoli k jejich změně. Pro akce jako třeba mazání záznamů je vhodnější použít metodu POST. I když ideální by byla metoda DELETE, ale tu nelze bez JavaScriptu vyvolat, proto se historicky používá POST. + +Jak na to v praxi? Využijte tento jednoduchý trik. Na začátku šablony si vytvoříte pomocný formulář s identifikátorem `postForm`, který následně použijete pro mazací tlačítka: + +```latte .{file:@layout.latte} +<form method="post" id="postForm"></form> +``` + +Díky tomuto formuláři můžete místo klasického odkazu `<a>` použít tlačítko `<button>`, které lze vizuálně upravit tak, aby vypadalo jako běžný odkaz. Například CSS framework Bootstrap nabízí třídy `btn btn-link` se kterými dosáhnete toho, že tlačítko nebude vizuálně odlišné od ostatních odkazů. Pomocí atributu `form="postForm"` ho provážeme s předpřipraveným formulářem: + +```latte .{file:admin.latte} +<table> + <tr n:foreach="$posts as $post"> + <td>{$post->title}</td> + <td> + <button class="btn btn-link" form="postForm" formaction="{link delete $post->id}">delete</button> + <!-- instead of <a n:href="delete $post->id">delete</a> --> + </td> + </tr> +</table> +``` + +Při kliknutí na odkaz se nyní vyvolá akce `delete`. Pro zajištění, že požadavky budou přijímány pouze prostřednictvím metody POST a z téže domény (což je účinná obrana proti CSRF útokům), použijte atribut `#[Requires]`: + +```php .{file:AdminPresenter.php} +use Nette\Application\Attributes\Requires; + +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST', sameOrigin: true)] + public function actionDelete(int $id): void + { + $this->facade->deletePost($id); // hypotetický kód mazající záznam + $this->redirect('default'); + } +} +``` + +Atribut existuje od Nette Application 3.2 a více o jeho možnostech se dozvíte na stránce [Jak používat atribut #Requires |attribute-requires]. + +Pokud byste místo akce `actionDelete()` používali signál `handleDelete()`, není nutné uvádět `sameOrigin: true`, protože signály mají tuto ochranu nastavenou implicitně: + +```php .{file:AdminPresenter.php} +#[Requires(methods: 'POST')] +public function handleDelete(int $id): void +{ + $this->facade->deletePost($id); + $this->redirect('this'); +} +``` + +Tento přístup nejenže zlepšuje bezpečnost vaší aplikace, ale také přispívá k dodržování správných webových standardů a praxe. Využitím metod POST pro akce měnící stav dosáhnete robustnější a bezpečnější aplikace. diff --git a/best-practices/cs/presenter-traits.texy b/best-practices/cs/presenter-traits.texy index 56c6ba8dfd..2e5091ce00 100644 --- a/best-practices/cs/presenter-traits.texy +++ b/best-practices/cs/presenter-traits.texy @@ -2,11 +2,11 @@ Skládání presenterů z trait *************************** .[perex] -Pokud potřebujeme ve více presenterech implementovat stejný kód (např. ověření, že je uživatel přihlášen), nabízí se umístit kód do společného předka. Druhou možností je vytvoření jednoúčelových trait. +Pokud potřebujeme ve více presenterech implementovat stejný kód (např. ověření, že je uživatel přihlášen), nabízí se umístit kód do společného předka. Druhou možností je vytvoření jednoúčelových [trait |nette:introduction-to-object-oriented-programming#Traity]. Výhoda tohoto řešení je, že každý z presenterů může použít právě ty traity, které skutečně potřebuje, zatímco vícenásobná dědičnost není v PHP možná. -Tyto traity mohou využívat skutečnosti, že při vytvoření presenteru se postupně zavolají všechny [inject metody|inject-method-attribute#Metody inject]. Jen je nutné dohlédnout na to, aby název každé inject metody byl unikátní. +Tyto traity mohou využívat skutečnosti, že při vytvoření presenteru se postupně zavolají všechny [inject metody |inject-method-attribute#Metody inject]. Jen je nutné dohlédnout na to, aby název každé inject metody byl unikátní. Traity mohou navěsit inicializační kód do událostí [onStartup nebo onRender |application:presenters#Události]. @@ -45,6 +45,3 @@ class ArticlePresenter extends Nette\Application\UI\Presenter use RequireLoggedUser; } ``` - - -{{sitename: Best Practices}} diff --git a/best-practices/cs/restore-request.texy b/best-practices/cs/restore-request.texy index 1a3562ccea..05a54331ae 100644 --- a/best-practices/cs/restore-request.texy +++ b/best-practices/cs/restore-request.texy @@ -4,8 +4,7 @@ Jak se vrátit k dřívější stránce? .[perex] Co když uživatel vyplňuje formulář a vyprší mu přihlášení? Aby o data nepřišel, před přesměrováním na přihlašovací stránku data uložíme do session. V Nette to je úplná hračka. -Aktuální požadavek lze uložit do session pomocí metody `storeRequest()`, která vrátí jeho identifikátor v podobě krátkého řetězce. Metoda uloží název aktuálního presenteru, view a jeho parametry. -V případě, že byl odeslán i formulář, uloží se také obsah políček (s výjimkou uploadovaných souborů). +Aktuální požadavek lze uložit do session pomocí metody `storeRequest()`, která vrátí jeho identifikátor v podobě krátkého řetězce. Metoda uloží název aktuálního presenteru, view a jeho parametry. V případě, že byl odeslán i formulář, uloží se také obsah políček (s výjimkou uploadovaných souborů). Obnovení požadavku provádí metoda `restoreRequest($key)`, které předáme získaný identifikátor. Ta přesměruje na původní presenter a view. Pokud však uložený požadavek obsahuje odeslání formuláře, na původní presenter přejde metodou `forward()`, formuláři předá dříve vyplněné hodnoty a nechá jej znovu vykreslit. Uživatel tak má možnost formulář opětovně odeslat a žádná data se neztratí. @@ -42,11 +41,11 @@ class SignPresenter extends Nette\Application\UI\Presenter { $form = new Nette\Application\UI\Form; // ... přidáme políčka formuláře ... - $form->onSuccess[] = [$this, 'signInFormSubmitted']; + $form->onSuccess[] = $this->signInFormSubmitted(...); return $form; } - public function signInFormSubmitted($form) + private function signInFormSubmitted($form) { // ... tady uživatele přihlásíme ... @@ -61,4 +60,3 @@ Metodě `restoreRequest()` předáme klíč uloženého požadavku a ona přesm Pokud je ale klíč neplatný (například už v session neexistuje), metoda neudělá nic. Následuje tedy volání `$this->redirect('Admin:')`, které přesměruje na `AdminPresenter`. {{priority: -1}} -{{sitename: Best Practices}} diff --git a/best-practices/de/@home.texy b/best-practices/de/@home.texy index 9a057ba919..d855293435 100644 --- a/best-practices/de/@home.texy +++ b/best-practices/de/@home.texy @@ -1,22 +1,24 @@ -Bewährte Praktiken -****************** +Anleitungen und Verfahren +************************* .[perex] -Anleitungen, Lösungen für allgemeine Probleme und bewährte Verfahren für Nette. +Anleitungen, Lösungen für häufige Aufgaben und *Best Practices* für Nette. <div class=documentation> <div> -Nette Bewerbung +Nette-Anwendung --------------- -- [Methoden und Attribute injizieren |inject-method-attribute] -- [Presenter aus Traits zusammensetzen |presenter-traits] -- [Übergabe von Einstellungen an Presenter |passing-settings-to-presenters] +- [Methoden und Attribute inject |inject-method-attribute] +- [Zusammensetzen von Presentern aus Traits |presenter-traits] +- [Übergeben von Einstellungen an Presenter |passing-settings-to-presenters] - [Wie man zu einer früheren Seite zurückkehrt |restore-request] -- [Paginieren von Datenbankergebnissen |Pagination] -- [Dynamische Schnipsel |dynamic-snippets] +- [Paginierung von Datenbankergebnissen |pagination] +- [Dynamische Snippets |dynamic-snippets] +- [Wie man das Attribut #Requires verwendet |attribute-requires] +- [Wie man POST-Links korrekt verwendet |post-links] </div> <div> @@ -26,32 +28,34 @@ Formulare --------- - [Wiederverwendung von Formularen |form-reuse] - [Formular zum Erstellen und Bearbeiten von Datensätzen |creating-editing-form] -- [Erstellen wir ein Kontakt-Formular |lets-create-contact-form] -- [Abhängige Selectboxen |https://blog.nette.org/de/abhaengige-selectboxen-elegant-in-nette-und-purem-js] +- [Erstellen eines Kontaktformulars |lets-create-contact-form] +- [Abhängige Selectboxen |https://blog.nette.org/de/dependent-selectboxes-elegantly-in-nette-and-pure-js] </div> <div> -Allgemein ---------- +Allgemeines +----------- - [Wie man eine Konfigurationsdatei lädt |bootstrap:] -- [Warum verwendet Nette die Konstantenschreibweise PascalCase? |https://blog.nette.org/de/fuer-weniger-geschrei-im-code] -- [Warum verwendet Nette nicht das Suffix Interface? |https://blog.nette.org/de/praefixe-und-suffixe-gehoeren-nicht-in-schnittstellennamen] -- [Tipps zur Verwendung des Composers |composer] -- [Tipps zu Editoren und Tools |editors-and-tools] +- [Wie man Micro-Websites schreibt |microsites] +- [Warum Nette die PascalCase-Notation für Konstanten verwendet |https://blog.nette.org/de/for-less-screaming-in-the-code] +- [Warum Nette das Interface-Suffix nicht verwendet |https://blog.nette.org/de/prefixes-and-suffixes-do-not-belong-in-interface-names] +- [Composer: Tipps zur Verwendung |composer] +- [Tipps für Editoren & Werkzeuge |editors-and-tools] +- [Einführung in die objektorientierte Programmierung |nette:introduction-to-object-oriented-programming] </div> <div> -Musterlösung ------------- +Beispiellösungen +---------------- - [Nette Beispiele |https://github.com/nette-examples] -- [Doktrin & Nette |https://contributte.org/nettrine/] -- [Contributte-Beispiele |https://contributte.org/examples.html] -- [Doctrine ORM-Website |https://github.com/MinecordNetwork/Website] -- [Schnellstart |quickstart:] +- [Doctrine & Nette |https://contributte.org/nettrine/] +- [Contributte Beispiele |https://contributte.org/examples.html] +- [Doctrine ORM Website |https://github.com/MinecordNetwork/Website] +- [Quickstart |quickstart:] </div> <div> @@ -59,10 +63,7 @@ Musterlösung Videos ------ -Hunderte von Aufnahmen aus Posobota und Videos über Nette finden Sie unter einem Dach auf dem "Nette Framework YouTube Channel":https://www.youtube.com/user/NetteFramework. +Hunderte von Aufzeichnungen von "Poslední sobota" und Videos über Nette finden Sie unter einem Dach auf dem [Youtube-Kanal des Nette Frameworks |https://www.youtube.com/user/NetteFramework]. </div> </div> - -{{sitename: Bewährte Praktiken}} -{{leftbar: www:@menu-common}} diff --git a/best-practices/de/@meta.texy b/best-practices/de/@meta.texy new file mode 100644 index 0000000000..c3bc189de7 --- /dev/null +++ b/best-practices/de/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Anleitungen und Verfahren}} +{{leftbar: www:@menu-common}} diff --git a/best-practices/de/attribute-requires.texy b/best-practices/de/attribute-requires.texy new file mode 100644 index 0000000000..b6a5b9f6c2 --- /dev/null +++ b/best-practices/de/attribute-requires.texy @@ -0,0 +1,177 @@ +Wie man das Attribut `#[Requires]` verwendet +******************************************** + +.[perex] +Beim Schreiben einer Webanwendung stoßen Sie oft auf die Notwendigkeit, den Zugriff auf bestimmte Teile Ihrer Anwendung zu beschränken. Vielleicht möchten Sie, dass einige Anfragen nur über ein Formular (also mit der POST-Methode) Daten senden können oder nur für AJAX-Aufrufe zugänglich sind. Im Nette Framework 3.2 gibt es ein neues Werkzeug, mit dem Sie solche Einschränkungen sehr elegant und übersichtlich festlegen können: das Attribut `#[Requires]`. + +Ein Attribut ist eine spezielle Markierung in PHP, die Sie vor die Definition einer Klasse oder Methode hinzufügen. Da es sich eigentlich um eine Klasse handelt, müssen Sie für die Funktion der folgenden Beispiele die use-Klausel angeben: + +```php +use Nette\Application\Attributes\Requires; +``` + +Das Attribut `#[Requires]` können Sie bei der Presenter-Klasse selbst und auch bei diesen Methoden verwenden: + +- `action<Action>()` +- `render<View>()` +- `handle<Signal>()` +- `createComponent<Name>()` + +Die letzten beiden Methoden betreffen auch Komponenten, das Attribut können Sie also auch bei ihnen verwenden. + +Wenn die im Attribut angegebenen Bedingungen nicht erfüllt sind, wird ein HTTP-Fehler 4xx ausgelöst. + + +HTTP-Methoden +------------- + +Sie können angeben, welche HTTP-Methoden (wie GET, POST usw.) für den Zugriff erlaubt sind. Wenn Sie beispielsweise den Zugriff nur durch das Absenden eines Formulars erlauben möchten, stellen Sie ein: + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +Warum sollten Sie POST anstelle von GET für Aktionen verwenden, die den Zustand ändern, und wie geht das? [Lesen Sie die Anleitung |post-links]. + +Sie können eine Methode oder ein Array von Methoden angeben. Ein Sonderfall ist der Wert `'*'`, der alle Methoden erlaubt, was Presenter standardmäßig aus [Sicherheitsgründen nicht zulassen |application:presenters#Überprüfung der HTTP-Methode]. + + +AJAX-Aufrufe +------------ + +Wenn Sie möchten, dass ein Presenter oder eine Methode nur für AJAX-Anfragen verfügbar ist, verwenden Sie: + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Gleicher Ursprung +----------------- + +Zur Erhöhung der Sicherheit können Sie verlangen, dass die Anfrage von derselben Domain stammt. Dadurch verhindern Sie [CSRF-Schwachstellen |nette:vulnerability-protection#Cross-Site Request Forgery CSRF]: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Bei `handle<Signal>()`-Methoden wird der Zugriff von derselben Domain automatisch verlangt. Wenn Sie also den Zugriff von jeder beliebigen Domain erlauben möchten, geben Sie an: + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Zugriff über forward +-------------------- + +Manchmal ist es nützlich, den Zugriff auf einen Presenter so zu beschränken, dass er nur indirekt verfügbar ist, beispielsweise durch Verwendung der Methode `forward()` oder `switch()` aus einem anderen Presenter. So werden beispielsweise Error-Presenter geschützt, damit sie nicht über die URL aufgerufen werden können: + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +In der Praxis ist es oft notwendig, bestimmte Views zu markieren, auf die erst aufgrund der Logik im Presenter zugegriffen werden kann. Also wieder, damit sie nicht direkt geöffnet werden können: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = $this->facade->getProduct($id); + if (!$product) { + $this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Konkrete Aktionen +----------------- + +Sie können auch einschränken, dass bestimmter Code, wie das Erstellen einer Komponente, nur für spezifische Aktionen im Presenter verfügbar ist: + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +Im Falle einer einzelnen Aktion ist es nicht notwendig, ein Array zu schreiben: `#[Requires(actions: 'default')]` + + +Eigene Attribute +---------------- + +Wenn Sie das Attribut `#[Requires]` wiederholt mit denselben Einstellungen verwenden möchten, können Sie ein eigenes Attribut erstellen, das `#[Requires]` erbt und es nach Bedarf konfiguriert. + +Beispielsweise ermöglicht `#[SingleAction]` den Zugriff nur über die Aktion `default`: + +```php +#[\Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Oder `#[RestMethods]` erlaubt den Zugriff über alle HTTP-Methoden, die für REST-APIs verwendet werden: + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Fazit +----- + +Das Attribut `#[Requires]` gibt Ihnen große Flexibilität und Kontrolle darüber, wie auf Ihre Webseiten zugegriffen wird. Mit einfachen, aber leistungsstarken Regeln können Sie die Sicherheit und die korrekte Funktion Ihrer Anwendung erhöhen. Wie Sie sehen, kann die Verwendung von Attributen in Nette Ihre Arbeit nicht nur erleichtern, sondern auch sicherer machen. diff --git a/best-practices/de/composer.texy b/best-practices/de/composer.texy index eaaa86a842..554def763b 100644 --- a/best-practices/de/composer.texy +++ b/best-practices/de/composer.texy @@ -1,44 +1,44 @@ -Tipps zur Verwendung von Composer -********************************* +Composer: Tipps zur Verwendung +****************************** <div class=perex> -Composer ist ein Werkzeug zur Verwaltung von Abhängigkeiten in PHP. Es erlaubt Ihnen, die Bibliotheken, von denen Ihr Projekt abhängt, zu deklarieren, und es wird sie für Sie installieren und aktualisieren. Wir werden lernen: +Composer ist ein Werkzeug zur Verwaltung von Abhängigkeiten in PHP. Es ermöglicht uns, die Bibliotheken aufzulisten, von denen unser Projekt abhängt, und wird sie für uns installieren und aktualisieren. Wir zeigen Ihnen: - wie man Composer installiert -- wie man ihn in einem neuen oder bestehenden Projekt verwendet +- seine Verwendung in einem neuen oder bestehenden Projekt </div> -Einrichtung .[#toc-installation] -================================ +Installation +============ -Composer ist eine ausführbare `.phar` Datei, die Sie wie folgt herunterladen und installieren. +Composer ist eine ausführbare `.phar`-Datei, die Sie herunterladen und wie folgt installieren: -Windows .[#toc-windows] ------------------------ +Windows +------- -Verwenden Sie das offizielle Installationsprogramm [Composer-Setup.exe |https://getcomposer.org/Composer-Setup.exe]. +Verwenden Sie den offiziellen Installer [Composer-Setup.exe |https://getcomposer.org/Composer-Setup.exe]. -Linux, macOS .[#toc-linux-macos] --------------------------------- +Linux, macOS +------------ -Alles was Sie brauchen sind 4 Befehle, die Sie von [dieser Seite |https://getcomposer.org/download/] kopieren können. +Es genügen 4 Befehle, die Sie von [dieser Seite |https://getcomposer.org/download/] kopieren können. -Außerdem wird Composer durch Kopieren in einen Ordner im `PATH` global zugänglich: +Durch das Ablegen im Ordner, der im System-`PATH` enthalten ist, wird Composer global zugänglich: ```shell -$ mv ./composer.phar ~/bin/composer # or /usr/local/bin/composer +$ mv ./composer.phar ~/bin/composer # oder /usr/local/bin/composer ``` -Verwendung im Projekt .[#toc-use-in-project] -============================================ +Verwendung im Projekt +===================== -Um Composer in Ihrem Projekt verwenden zu können, benötigen Sie lediglich eine `composer.json` Datei. Diese Datei beschreibt die Abhängigkeiten Ihres Projekts und kann auch andere Metadaten enthalten. Die einfachste `composer.json` kann wie folgt aussehen: +Um Composer in Ihrem Projekt verwenden zu können, benötigen Sie lediglich die Datei `composer.json`. Diese beschreibt die Abhängigkeiten Ihres Projekts und kann auch weitere Metadaten enthalten. Eine grundlegende `composer.json` kann also so aussehen: ```js { @@ -48,17 +48,17 @@ Um Composer in Ihrem Projekt verwenden zu können, benötigen Sie lediglich eine } ``` -Wir sagen hier, dass unsere Anwendung (oder Bibliothek) von dem Paket `nette/database` abhängt (der Name des Pakets besteht aus dem Namen des Herstellers und dem Namen des Projekts) und die Version benötigt, die der Versionsbeschränkung `^3.0` entspricht. +Hier geben wir an, dass unsere Anwendung (oder Bibliothek) das Paket `nette/database` benötigt (der Paketname setzt sich aus dem Organisationsnamen und dem Projektnamen zusammen) und eine Version wünscht, die der Bedingung `^3.0` entspricht (d.h. die neueste Version 3). -Wenn wir also die Datei `composer.json` im Projektstamm haben und sie ausführen: +Wir haben also im Projektstamm die Datei `composer.json` und starten die Installation mit: ```shell composer update ``` -Der Composer lädt die Nette-Datenbank in das Verzeichnis `vendor` herunter. Außerdem wird eine Datei `composer.lock` erstellt, die Informationen darüber enthält, welche Bibliotheksversionen genau installiert wurden. +Composer lädt Nette Database in den Ordner `vendor/`. Außerdem erstellt er die Datei `composer.lock`, die Informationen darüber enthält, welche Versionen der Bibliotheken genau installiert wurden. -Composer erzeugt eine `vendor/autoload.php` Datei. Sie können diese Datei einfach einbinden und die Klassen, die diese Bibliotheken bereitstellen, ohne zusätzliche Arbeit verwenden: +Composer generiert die Datei `vendor/autoload.php`, die wir einfach einbinden können und sofort mit der Verwendung der Bibliotheken beginnen können, ohne weitere Arbeit: ```php require __DIR__ . '/vendor/autoload.php'; @@ -67,46 +67,46 @@ $db = new Nette\Database\Connection('sqlite::memory:'); ``` -Pakete auf die neuesten Versionen aktualisieren .[#toc-update-packages-to-the-latest-versions] -============================================================================================== +Aktualisierung der Pakete auf die neuesten Versionen +==================================================== -Um alle verwendeten Pakete auf die neueste Version gemäß den in `composer.json` definierten Versionsbeschränkungen zu aktualisieren, verwenden Sie den Befehl `composer update`. Zum Beispiel wird für die Abhängigkeit `"nette/database": "^3.0"` die neueste Version 3.x.x installiert, aber nicht Version 4. +Die Aktualisierung der verwendeten Bibliotheken auf die neuesten Versionen gemäß den in `composer.json` definierten Bedingungen übernimmt der Befehl `composer update`. Z.B. bei der Abhängigkeit `"nette/database": "^3.0"` installiert er die neueste Version 3.x.x, aber nicht mehr Version 4. -Um die Versionseinschränkungen in der Datei `composer.json` z.B. auf `"nette/database": "^4.1"` zu aktualisieren, damit die neueste Version installiert werden kann, verwenden Sie den Befehl `composer require nette/database`. +Um die Bedingungen in der Datei `composer.json` beispielsweise auf `"nette/database": "^4.1"` zu aktualisieren, damit die neueste Version installiert werden kann, verwenden Sie den Befehl `composer require nette/database`. -Um alle verwendeten Nette-Pakete zu aktualisieren, müssen Sie diese in der Befehlszeile auflisten, z. B: +Um alle verwendeten Nette-Pakete zu aktualisieren, müssten Sie sie alle in der Befehlszeile auflisten, z.B.: ```shell composer require nette/application nette/forms latte/latte tracy/tracy ... ``` -Das ist unpraktisch. Verwenden Sie daher ein einfaches Skript "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff, das diese Aufgabe für Sie übernimmt: +Was unpraktisch ist. Verwenden Sie stattdessen das einfache Skript "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff, das dies für Sie erledigt: ```shell php composer-frontline.php ``` -Neues Projekt erstellen .[#toc-creating-new-project] -==================================================== +Erstellung eines neuen Projekts +=============================== -Ein neues Nette-Projekt kann durch Ausführen eines einfachen Befehls erstellt werden: +Ein neues Nette-Projekt erstellen Sie mit einem einzigen Befehl: ```shell -composer create-project nette/web-project name-of-the-project +composer create-project nette/web-project projektname ``` -Anstelle von `name-of-the-project` sollten Sie den Namen des Verzeichnisses für Ihr Projekt angeben und den Befehl ausführen. Composer wird das Repository `nette/web-project` von GitHub holen, das bereits die Datei `composer.json` enthält, und gleich danach das Nette Framework selbst installieren. Nun müssen nur noch die [Schreibrechte |nette:troubleshooting#setting-directory-permissions] für die Verzeichnisse `temp/` und `log/` [überprüft |nette:troubleshooting#setting-directory-permissions] werden und schon kann es losgehen. +Geben Sie als `projektname` den Namen des Verzeichnisses für Ihr Projekt ein und bestätigen Sie. Composer lädt das Repository `nette/web-project` von GitHub herunter, das bereits die Datei `composer.json` enthält, und gleich danach das Nette Framework. Es sollte nur noch notwendig sein, die [Berechtigungen |nette:troubleshooting#Einstellung der Verzeichnisberechtigungen] für das Schreiben in die Ordner `temp/` und `log/` festzulegen, und das Projekt sollte zum Leben erweckt werden. -Wenn Sie wissen, mit welcher PHP-Version das Projekt gehostet werden soll, sollten Sie [diese |#PHP Version] unbedingt einrichten. +Wenn Sie wissen, auf welcher PHP-Version das Projekt gehostet wird, vergessen Sie nicht, [sie einzustellen |#PHP-Version]. -PHP-Version .[#toc-php-version] -=============================== +PHP-Version +=========== -Composer installiert immer die Versionen der Pakete, die mit der PHP-Version kompatibel sind, die Sie gerade verwenden (oder besser gesagt, die PHP-Version, die auf der Kommandozeile verwendet wird, wenn Sie Composer starten). Das ist wahrscheinlich nicht die Version, die Ihr Webhost verwendet. Deshalb ist es sehr wichtig, dass Sie Informationen über die PHP-Version Ihres Hosts in Ihre Datei `composer.json` aufnehmen. Danach werden nur noch die Versionen der Pakete installiert, die mit dem Host kompatibel sind. +Composer installiert immer die Versionen von Paketen, die mit der PHP-Version kompatibel sind, die Sie gerade verwenden (genauer gesagt, mit der PHP-Version, die in der Befehlszeile beim Ausführen von Composer verwendet wird). Dies ist jedoch wahrscheinlich nicht die gleiche Version, die Ihr Hosting verwendet. Daher ist es sehr wichtig, der Datei `composer.json` Informationen über die PHP-Version auf dem Hosting hinzuzufügen. Danach werden nur noch Versionen von Paketen installiert, die mit dem Hosting kompatibel sind. -Um zum Beispiel das Projekt auf PHP 8.2.3 einzustellen, verwenden Sie den Befehl: +Dass das Projekt beispielsweise auf PHP 8.2.3 laufen wird, legen wir mit dem Befehl fest: ```shell composer config platform.php 8.2.3 @@ -124,14 +124,13 @@ So wird die Version in die Datei `composer.json` geschrieben: } ``` -Die PHP-Versionsnummer wird jedoch auch an anderer Stelle in der Datei aufgeführt, im Abschnitt `require`. Während die erste Zahl die Version angibt, für die Pakete installiert werden, sagt die zweite Zahl, für welche Version die Anwendung selbst geschrieben wurde. -(Natürlich macht es keinen Sinn, dass diese Versionen unterschiedlich sind, daher ist die doppelte Angabe eine Redundanz). Sie setzen diese Version mit dem Befehl: +Die PHP-Versionsnummer wird jedoch noch an einer anderen Stelle der Datei angegeben, und zwar im Abschnitt `require`. Während die erste Zahl angibt, für welche Version Pakete installiert werden, gibt die zweite Zahl an, für welche Version die Anwendung selbst geschrieben ist. Und danach stellt beispielsweise PhpStorm das *PHP language level* ein. (Natürlich macht es keinen Sinn, dass sich diese Versionen unterscheiden, daher ist die doppelte Angabe eine Ungeschicklichkeit.) Diese Version stellen Sie mit dem Befehl ein: ```shell composer require php 8.2.3 --no-update ``` -Oder direkt in der Datei "Composer.json": +Oder direkt in der Datei `composer.json`: ```js { @@ -142,66 +141,72 @@ Oder direkt in der Datei "Composer.json": ``` -False Berichte .[#toc-false-reports] -==================================== +Ignorieren der PHP-Version +========================== + +Pakete geben in der Regel sowohl die niedrigste PHP-Version an, mit der sie kompatibel sind, als auch die höchste, mit der sie getestet wurden. Wenn Sie eine noch neuere PHP-Version verwenden möchten, beispielsweise zum Testen, wird Composer die Installation eines solchen Pakets verweigern. Die Lösung ist die Option `--ignore-platform-req=php+`, die bewirkt, dass Composer die Obergrenzen der erforderlichen PHP-Version ignoriert. -Bei der Aktualisierung von Paketen oder der Änderung von Versionsnummern kommt es zu Konflikten. Ein Paket hat Anforderungen, die mit einem anderen in Konflikt stehen, und so weiter. Composer gibt jedoch gelegentlich eine falsche Meldung aus. Er meldet einen Konflikt, der nicht wirklich existiert. In diesem Fall hilft es, die Datei `composer.lock` zu löschen und es erneut zu versuchen. -Bleibt die Fehlermeldung bestehen, dann ist sie ernst gemeint und Sie müssen ihr entnehmen, was Sie wie ändern müssen. +Falsche Meldungen +================= +Beim Upgrade von Paketen oder Änderungen der Versionsnummern kommt es vor, dass ein Konflikt auftritt. Ein Paket hat Anforderungen, die im Widerspruch zu einem anderen stehen und ähnliches. Composer gibt jedoch manchmal falsche Meldungen aus. Er meldet einen Konflikt, der real nicht existiert. In diesem Fall hilft es, die Datei `composer.lock` zu löschen und es erneut zu versuchen. -Packagist.org - Globales Repository .[#toc-packagist-org-global-repository] -=========================================================================== +Wenn die Fehlermeldung bestehen bleibt, ist sie ernst gemeint und Sie müssen daraus entnehmen, was und wie Sie es anpassen müssen. -[Packagist |https://packagist.org] ist das Hauptpaket-Repository, in dem Composer versucht, Pakete zu suchen, wenn nicht anders angegeben. Sie können hier auch Ihre eigenen Pakete veröffentlichen. +Packagist.org - zentrales Repository +==================================== -Was, wenn wir das zentrale Repository nicht wollen? .[#toc-what-if-we-don-t-want-the-central-repository] --------------------------------------------------------------------------------------------------------- +[Packagist |https://packagist.org] ist das Haupt-Repository, in dem Composer versucht, Pakete zu finden, wenn wir ihm nichts anderes sagen. Wir können hier auch eigene Pakete veröffentlichen. -Wenn wir interne Anwendungen oder Bibliotheken in unserem Unternehmen haben, die nicht öffentlich auf Packagist gehostet werden können, können wir unsere eigenen Repositories für diese Projekte erstellen. -Mehr über Repositories finden Sie in der [offiziellen Dokumentation |https://getcomposer.org/doc/05-repositories.md#repositories]. +Was, wenn wir kein zentrales Repository verwenden möchten? +---------------------------------------------------------- +Wenn wir firmeninterne Anwendungen haben, die wir einfach nicht öffentlich hosten können, erstellen wir dafür ein Firmen-Repository. + +Mehr zum Thema Repositories finden Sie [in der offiziellen Dokumentation |https://getcomposer.org/doc/05-repositories.md#repositories]. -Autoloading .[#toc-autoloading] -=============================== -Eine Schlüsselfunktion von Composer ist das automatische Laden aller installierten Klassen, das Sie durch Einfügen einer Datei `vendor/autoload.php` starten. +Autoloading +=========== -Es ist jedoch auch möglich, Composer zu verwenden, um andere Klassen außerhalb des Ordners `vendor` zu laden. Die erste Möglichkeit besteht darin, Composer die definierten Ordner und Unterordner durchsuchen zu lassen, alle Klassen zu finden und sie in den Autoloader aufzunehmen. Dazu setzen Sie `autoload > classmap` in `composer.json`: +Eine wesentliche Eigenschaft von Composer ist, dass er Autoloading für alle von ihm installierten Klassen bereitstellt, das Sie durch Einbinden der Datei `vendor/autoload.php` starten. + +Es ist jedoch auch möglich, Composer zum Laden weiterer Klassen auch außerhalb des Ordners `vendor` zu verwenden. Die erste Möglichkeit ist, Composer definierte Ordner und Unterordner durchsuchen zu lassen, alle Klassen zu finden und sie in den Autoloader aufzunehmen. Dies erreichen Sie durch die Einstellung `autoload > classmap` in `composer.json`: ```js { "autoload": { "classmap": [ - "src/", # includes the src/ folder and its subfolders + "src/", # beinhaltet den Ordner src/ und seine Unterordner ] } } ``` -Anschließend müssen Sie bei jeder Änderung den Befehl `composer dumpautoload` ausführen und die Autoloader-Tabellen neu generieren lassen. Dies ist äußerst lästig, und es ist weitaus besser, diese Aufgabe [RobotLoader |robot-loader:] anzuvertrauen, der dieselbe Tätigkeit automatisch im Hintergrund und viel schneller durchführt. +Anschließend muss bei jeder Änderung der Befehl `composer dumpautoload` ausgeführt und die Autoloading-Tabellen neu generiert werden. Das ist äußerst unpraktisch, und es ist viel besser, diese Aufgabe dem [RobotLoader|robot-loader:] zu überlassen, der dieselbe Tätigkeit automatisch im Hintergrund und viel schneller erledigt. -Die zweite Möglichkeit ist, [PSR-4 |https://www.php-fig.org/psr/psr-4/] zu folgen. Einfach gesagt handelt es sich um ein System, bei dem die Namensräume und Klassennamen der Verzeichnisstruktur und den Dateinamen entsprechen, d. h. `App\Router\RouterFactory` befindet sich in der Datei `/path/to/App/Router/RouterFactory.php`. Beispiel für eine Konfiguration: +Die zweite Möglichkeit ist, [PSR-4|https://www.php-fig.org/psr/psr-4/] einzuhalten. Vereinfacht gesagt handelt es sich um ein System, bei dem Namensräume und Klassennamen der Verzeichnisstruktur und den Dateinamen entsprechen, d.h. z.B. `App\Core\RouterFactory` befindet sich in der Datei `/path/to/App/Core/RouterFactory.php`. Beispielkonfiguration: ```js { "autoload": { "psr-4": { - "App\\": "app/" # the App\ namespace is in the app/ directory + "App\\": "app/" # Der Namensraum App\ befindet sich im Verzeichnis app/ } } } ``` -In der [Composer-Dokumentation |https://getcomposer.org/doc/04-schema.md#psr-4] finden Sie eine genaue Beschreibung, wie Sie dieses Verhalten konfigurieren können. +Wie genau das Verhalten konfiguriert wird, erfahren Sie in der [Composer-Dokumentation|https://getcomposer.org/doc/04-schema.md#psr-4]. -Testen neuer Versionen .[#toc-testing-new-versions] -=================================================== +Testen neuer Versionen +====================== -Sie möchten eine neue Entwicklungsversion eines Pakets testen. Wie kann man das tun? Fügen Sie zunächst dieses Paar von Optionen in die Datei `composer.json` ein, das es Ihnen ermöglicht, Entwicklungsversionen von Paketen zu installieren, aber nur, wenn es keine Kombination aus stabiler Version gibt, die den Anforderungen entspricht: +Sie möchten eine neue Entwicklungsversion eines Pakets testen. Wie gehen Sie vor? Fügen Sie zunächst dieses Optionspaar zur Datei `composer.json` hinzu, das die Installation von Entwicklungsversionen von Paketen erlaubt, aber nur darauf zurückgreift, wenn keine Kombination von stabilen Versionen existiert, die den Anforderungen entspricht: ```js { @@ -210,9 +215,9 @@ Sie möchten eine neue Entwicklungsversion eines Pakets testen. Wie kann man das } ``` -Wir empfehlen auch, die Datei `composer.lock` zu löschen, da Composer manchmal unverständlicherweise die Installation verweigert und dies das Problem lösen wird. +Weiterhin empfehlen wir, die Datei `composer.lock` zu löschen, da Composer manchmal unverständlicherweise die Installation verweigert und dies das Problem löst. -Nehmen wir an, das Paket heißt `nette/utils` und die neue Version ist 4.0. Sie installieren es mit dem Befehl: +Nehmen wir an, es handelt sich um das Paket `nette/utils` und die neue Version hat die Nummer 4.0. Sie installieren sie mit dem Befehl: ```shell composer require nette/utils:4.0.x-dev @@ -224,20 +229,19 @@ Oder Sie können eine bestimmte Version installieren, zum Beispiel 4.0.0-RC2: composer require nette/utils:4.0.0-RC2 ``` -Wenn ein anderes Paket von der Bibliothek abhängt und auf eine ältere Version beschränkt ist (z. B. `^3.1`), ist es ideal, das Paket zu aktualisieren, damit es mit der neuen Version funktioniert. -Wenn Sie jedoch nur die Beschränkung umgehen und Composer zwingen wollen, die Entwicklungsversion zu installieren und so zu tun, als ob es sich um eine ältere Version handelt (z. B. 3.1.6), können Sie das Schlüsselwort `as` verwenden: +Wenn jedoch ein anderes Paket von der Bibliothek abhängt, das auf eine ältere Version gesperrt ist (z.B. `^3.1`), ist es ideal, das Paket zu aktualisieren, damit es mit der neuen Version funktioniert. Wenn Sie jedoch die Einschränkung nur umgehen und Composer zwingen möchten, die Entwicklungsversion zu installieren und so zu tun, als wäre es eine ältere Version (z.B. 3.1.6), können Sie das Schlüsselwort `as` verwenden: ```shell composer require nette/utils "4.0.x-dev as 3.1.6" ``` -Aufrufen von Befehlen .[#toc-calling-commands] -============================================== +Aufruf von Befehlen +=================== -Sie können Ihre eigenen benutzerdefinierten Befehle und Skripte über Composer so aufrufen, als wären es native Composer-Befehle. Skripte, die sich im Ordner `vendor/bin` befinden, müssen diesen Ordner nicht angeben. +Über Composer können eigene vorbereitete Befehle und Skripte aufgerufen werden, als wären es native Composer-Befehle. Bei Skripten, die sich im Ordner `vendor/bin` befinden, muss dieser Ordner nicht angegeben werden. -Als Beispiel definieren wir ein Skript in der Datei `composer.json`, das [Nette Tester |tester:] verwendet, um Tests auszuführen: +Als Beispiel definieren wir in der Datei `composer.json` ein Skript, das mit dem [Nette Tester|tester:] Tests startet: ```js { @@ -247,34 +251,32 @@ Als Beispiel definieren wir ein Skript in der Datei `composer.json`, das [Nette } ``` -Wir führen die Tests dann mit `composer tester` aus. Wir können den Befehl auch dann aufrufen, wenn wir uns nicht im Stammverzeichnis des Projekts, sondern in einem Unterverzeichnis befinden. +Die Tests starten wir dann mit `composer tester`. Den Befehl können wir auch aufrufen, wenn wir uns nicht im Stammverzeichnis des Projekts, sondern in einem Unterverzeichnis befinden. -Dank senden .[#toc-send-thanks] -=============================== +Senden Sie ein Dankeschön +========================= -Wir werden Ihnen einen Trick zeigen, der Open-Source-Autoren glücklich machen wird. Sie können den Bibliotheken, die Ihr Projekt verwendet, ganz einfach einen Stern auf GitHub geben. Installieren Sie einfach die Bibliothek `symfony/thanks`: +Wir zeigen Ihnen einen Trick, mit dem Sie die Autoren von Open Source erfreuen können. Geben Sie auf GitHub den Bibliotheken, die Ihr Projekt verwendet, auf einfache Weise einen Stern. Installieren Sie einfach die Bibliothek `symfony/thanks`: ```shell composer global require symfony/thanks ``` -Und dann ausführen: +Und führen Sie dann aus: ```shell composer thanks ``` -Versuchen Sie es! +Probieren Sie es aus! -Konfiguration .[#toc-configuration] -=================================== +Konfiguration +============= -Composer ist eng mit dem Versionskontrollwerkzeug [Git |https://git-scm.com] integriert. Wenn Sie Git nicht verwenden, müssen Sie dies dem Composer mitteilen: +Composer ist eng mit dem Versionierungswerkzeug [Git |https://git-scm.com] verbunden. Wenn Sie es nicht installiert haben, müssen Sie Composer mitteilen, es nicht zu verwenden: ```shell composer -g config preferred-install dist ``` - -{{sitename: Bewährte Praktiken}} diff --git a/best-practices/de/creating-editing-form.texy b/best-practices/de/creating-editing-form.texy index 5fc927b7d7..4238bf13fa 100644 --- a/best-practices/de/creating-editing-form.texy +++ b/best-practices/de/creating-editing-form.texy @@ -1,16 +1,16 @@ -Formular zum Erstellen und Bearbeiten eines Datensatzes -******************************************************* +Formular zum Erstellen und Bearbeiten von Datensätzen +***************************************************** .[perex] -Wie kann man das Hinzufügen und Bearbeiten eines Datensatzes in Nette richtig umsetzen, indem man für beides das gleiche Formular verwendet? +Wie implementiert man in Nette das Hinzufügen und Bearbeiten von Datensätzen korrekt, sodass für beides dasselbe Formular verwendet wird? -In vielen Fällen sind die Formulare zum Hinzufügen und Bearbeiten eines Datensatzes identisch und unterscheiden sich nur durch die Beschriftung der Schaltfläche. Wir werden Beispiele für einfache Präsentationen zeigen, bei denen wir das Formular zuerst zum Hinzufügen eines Datensatzes und dann zum Bearbeiten verwenden und schließlich beide Lösungen kombinieren. +In vielen Fällen sind die Formulare zum Hinzufügen und Bearbeiten von Datensätzen identisch, sie unterscheiden sich vielleicht nur durch die Beschriftung auf dem Button. Wir zeigen Beispiele für einfache Presenter, in denen wir das Formular zunächst zum Hinzufügen eines Datensatzes verwenden, dann zum Bearbeiten und schließlich beide Lösungen zusammenführen. -Hinzufügen eines Datensatzes .[#toc-adding-a-record] ----------------------------------------------------- +Hinzufügen eines Datensatzes +---------------------------- -Ein Beispiel für einen Präsentator, der zum Hinzufügen eines Datensatzes verwendet wird. Die eigentliche Datenbankarbeit überlassen wir der Klasse `Facade`, deren Code für dieses Beispiel nicht relevant ist. +Beispiel eines Presenters zum Hinzufügen eines Datensatzes. Die eigentliche Arbeit mit der Datenbank überlassen wir der Klasse `Facade`, deren Code für das Beispiel nicht wesentlich ist. ```php @@ -36,7 +36,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter public function recordFormSucceeded(Form $form, array $data): void { $this->facade->add($data); // Datensatz zur Datenbank hinzufügen - $this->flashMessage("Erfolgreich hinzugefügt"); + $this->flashMessage('Erfolgreich hinzugefügt'); $this->redirect('...'); } @@ -48,10 +48,10 @@ class RecordPresenter extends Nette\Application\UI\Presenter ``` -Bearbeiten eines Datensatzes .[#toc-editing-a-record] ------------------------------------------------------ +Bearbeiten eines Datensatzes +---------------------------- -Sehen wir uns nun an, wie ein Presenter zum Bearbeiten eines Datensatzes aussehen würde: +Nun zeigen wir, wie ein Presenter zum Bearbeiten eines Datensatzes aussehen würde: ```php @@ -70,10 +70,10 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $record = $this->facade->get($id); if ( - !$record // die Existenz des Datensatzes zu überprüfen - || !$this->facade->isEditAllowed(/*...*/) // Berechtigungen prüfen + !$record // Überprüfung der Existenz des Datensatzes + || !$this->facade->isEditAllowed(/*...*/) // Berechtigungsprüfung ) { - $this->error(); // 404-Fehler + $this->error(); // Fehler 404 } $this->record = $record; @@ -81,7 +81,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter protected function createComponentRecordForm(): Form { - // Überprüfen, ob die Aktion "Bearbeiten" ist + // Überprüfen, ob die Aktion 'edit' ist if ($this->getAction() !== 'edit') { $this->error(); } @@ -98,15 +98,15 @@ class RecordPresenter extends Nette\Application\UI\Presenter public function recordFormSucceeded(Form $form, array $data): void { $this->facade->update($this->record->id, $data); // Datensatz aktualisieren - $this->flashMessage("Erfolgreich aktualisiert"); + $this->flashMessage('Erfolgreich aktualisiert'); $this->redirect('...'); } } ``` -In der *Action*-Methode, die gleich zu Beginn des [Presenter-Lebenszyklus |application:presenters#Life Cycle of Presenter] aufgerufen wird, überprüfen wir die Existenz des Datensatzes und die Berechtigung des Benutzers, ihn zu bearbeiten. +In der `actionEdit()`-Methode, die gleich zu Beginn des [Presenter-Lebenszyklus |application:presenters#Lebenszyklus des Presenters] ausgeführt wird, überprüfen wir die Existenz des Datensatzes und die Berechtigung des Benutzers, ihn zu bearbeiten. -Wir speichern den Datensatz in der Eigenschaft `$record`, so dass er in der Methode `createComponentRecordForm()` zum Festlegen von Standardwerten und `recordFormSucceeded()` für die ID zur Verfügung steht. Eine alternative Lösung wäre, die Standardwerte direkt in `actionEdit()` zu setzen und den ID-Wert, der Teil der URL ist, mit `getParameter('id')` abzurufen: +Wir speichern den Datensatz in der Eigenschaft `$record`, um ihn in der Methode `createComponentRecordForm()` zum Setzen der Standardwerte und in `recordFormSucceeded()` für die ID zur Verfügung zu haben. Eine alternative Lösung wäre, die Standardwerte direkt in `actionEdit()` zu setzen und den Wert der ID, der Teil der URL ist, mit `getParameter('id')` abzurufen: ```php @@ -114,12 +114,12 @@ Wir speichern den Datensatz in der Eigenschaft `$record`, so dass er in der Meth { $record = $this->facade->get($id); if ( - // Existenz überprüfen und Berechtigungen prüfen + // Überprüfung der Existenz und Berechtigungsprüfung ) { $this->error(); } - // Standardwerte für das Formular festlegen + // Standardwerte des Formulars setzen $this->getComponent('recordForm') ->setDefaults($record); } @@ -133,13 +133,13 @@ Wir speichern den Datensatz in der Eigenschaft `$record`, so dass er in der Meth } ``` -Allerdings, und das sollte **die wichtigste Erkenntnis aus dem ganzen Code** sein, müssen wir sicherstellen, dass die Aktion tatsächlich `edit` ist, wenn wir das Formular erstellen. Denn sonst würde die Validierung in der Methode `actionEdit()` überhaupt nicht stattfinden! +Jedoch, und das sollte **die wichtigste Erkenntnis des gesamten Codes** sein, müssen wir beim Erstellen des Formulars sicherstellen, dass die Aktion tatsächlich `edit` ist. Denn andernfalls würde die Überprüfung in der Methode `actionEdit()` überhaupt nicht stattfinden! -Dasselbe Formular zum Hinzufügen und Bearbeiten .[#toc-same-form-for-adding-and-editing] ----------------------------------------------------------------------------------------- +Dasselbe Formular zum Hinzufügen und Bearbeiten +----------------------------------------------- -Und nun werden wir beide Präsentatoren in einem kombinieren. Entweder wir unterscheiden, welche Aktion an der Methode `createComponentRecordForm()` beteiligt ist und konfigurieren das Formular entsprechend, oder wir überlassen es direkt den Aktionsmethoden und lassen die Bedingung weg: +Und nun führen wir beide Presenter zu einem zusammen. Entweder könnten wir in der Methode `createComponentRecordForm()` unterscheiden, um welche Aktion es sich handelt und das Formular entsprechend konfigurieren, oder wir können dies direkt den Action-Methoden überlassen und die Bedingung entfernen: ```php @@ -160,20 +160,20 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $record = $this->facade->get($id); if ( - !$record // die Existenz des Datensatzes zu überprüfen - || !$this->facade->isEditAllowed(/*...*/) // Berechtigungen prüfen + !$record // Überprüfung der Existenz des Datensatzes + || !$this->facade->isEditAllowed(/*...*/) // Berechtigungsprüfung ) { - $this->error(); // 404-Fehler + $this->error(); // Fehler 404 } $form = $this->getComponent('recordForm'); - $form->setDefaults($record); // Standardeinstellungen festlegen + $form->setDefaults($record); // Standardwerte setzen $form->onSuccess[] = [$this, 'editingFormSucceeded']; } protected function createComponentRecordForm(): Form { - // Überprüfen, ob die Aktion "Hinzufügen" oder "Bearbeiten" ist + // Überprüfen, ob die Aktion 'add' oder 'edit' ist if (!in_array($this->getAction(), ['add', 'edit'])) { $this->error(); } @@ -188,7 +188,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter public function addingFormSucceeded(Form $form, array $data): void { $this->facade->add($data); // Datensatz zur Datenbank hinzufügen - $this->flashMessage("Erfolgreich hinzugefügt"); + $this->flashMessage('Erfolgreich hinzugefügt'); $this->redirect('...'); } @@ -196,11 +196,10 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $id = (int) $this->getParameter('id'); $this->facade->update($id, $data); // Datensatz aktualisieren - $this->flashMessage("Erfolgreich aktualisiert"); + $this->flashMessage('Erfolgreich aktualisiert'); $this->redirect('...'); } } ``` {{priority: -1}} -{{sitename: Bewährte Praktiken}} diff --git a/best-practices/de/dynamic-snippets.texy b/best-practices/de/dynamic-snippets.texy index c75affd1e4..b4ea7a67d5 100644 --- a/best-practices/de/dynamic-snippets.texy +++ b/best-practices/de/dynamic-snippets.texy @@ -1,7 +1,7 @@ -Dynamische Schnipsel -******************** +Dynamische Snippets +******************* -Bei der Anwendungsentwicklung besteht häufig die Notwendigkeit, AJAX-Operationen durchzuführen, z. B. in einzelnen Zeilen einer Tabelle oder in Listenelementen. So können wir beispielsweise Artikel auflisten und dem angemeldeten Benutzer die Möglichkeit geben, für jeden dieser Artikel eine "Gefällt mir"- oder "Gefällt mir nicht"-Bewertung auszuwählen. Der Code des Presenters und der entsprechenden Vorlage ohne AJAX sieht etwa so aus (ich führe die wichtigsten Schnipsel auf, der Code setzt die Existenz eines Dienstes zur Kennzeichnung der Bewertungen und zum Abrufen einer Sammlung von Artikeln voraus - die spezifische Implementierung ist für die Zwecke dieses Tutorials nicht wichtig): +Bei der Entwicklung von Anwendungen entsteht relativ häufig die Notwendigkeit, AJAX-Operationen beispielsweise auf einzelnen Zeilen einer Tabelle oder Elementen einer Liste durchzuführen. Als Beispiel können wir die Auflistung von Artikeln wählen, wobei wir jedem angemeldeten Benutzer ermöglichen, eine Bewertung "gefällt mir/gefällt mir nicht" abzugeben. Der Code des Presenters und des entsprechenden Templates ohne AJAX wird ungefähr wie folgt aussehen (ich gebe die wichtigsten Ausschnitte an, der Code rechnet mit der Existenz eines Dienstes zum Markieren von Bewertungen und dem Abrufen einer Artikelsammlung - die konkrete Implementierung ist für die Zwecke dieser Anleitung nicht wichtig): ```php public function handleLike(int $articleId): void @@ -17,33 +17,33 @@ public function handleUnlike(int $articleId): void } ``` -Vorlage: +Template: ```latte <article n:foreach="$articles as $article"> <h2>{$article->title}</h2> <div class="content">{$article->content}</div> {if !$article->liked} - <a n:href="like! $article->id" class=ajax>I like it</a> + <a n:href="like! $article->id" class=ajax>Gefällt mir</a> {else} - <a n:href="unlike! $article->id" class=ajax>I don't like it anymore</a> + <a n:href="unlike! $article->id" class=ajax>Gefällt mir nicht mehr</a> {/if} </article> ``` -Ajaxisierung .[#toc-ajaxization] -================================ +Ajaxifizierung +============== -Bringen wir nun AJAX in diese einfache Anwendung. Das Ändern der Bewertung eines Artikels ist nicht wichtig genug, um eine HTTP-Anfrage mit Redirect zu erfordern, also sollte es idealerweise mit AJAX im Hintergrund geschehen. Wir werden das [Handler-Skript aus add-ons |https://componette.org/vojtech-dobes/nette.ajax.js/] verwenden, mit der üblichen Konvention, dass AJAX-Links die CSS-Klasse `ajax` haben. +Lassen Sie uns nun diese einfache Anwendung mit AJAX ausstatten. Die Änderung der Artikelbewertung ist nicht so wichtig, dass eine Weiterleitung erfolgen muss, daher sollte sie idealerweise im Hintergrund per AJAX erfolgen. Wir verwenden [das Hilfsskript aus den Add-ons |application:ajax#Naja] mit der üblichen Konvention, dass AJAX-Links die CSS-Klasse `ajax` haben. -Aber wie macht man das konkret? Nette bietet 2 Wege an: den dynamischen Snippet-Weg und den Komponenten-Weg. Beide haben ihre Vor- und Nachteile, daher werden wir sie nacheinander vorstellen. +Aber wie genau geht das? Nette bietet 2 Wege: den Weg der sogenannten dynamischen Snippets und den Weg der Komponenten. Beide haben ihre Vor- und Nachteile, daher werden wir sie nacheinander vorstellen. -Der Weg der dynamischen Snippets .[#toc-the-dynamic-snippets-way] -================================================================= +Der Weg der dynamischen Snippets +================================ -In der Latte-Terminologie ist ein dynamisches Snippet ein spezieller Anwendungsfall des `{snippet}` -Tags, bei dem eine Variable im Namen des Snippets verwendet wird. Ein solches Snippet kann nicht einfach irgendwo in der Vorlage gefunden werden - es muss von einem statischen Snippet, d. h. einem regulären Snippet, oder innerhalb einer `{snippetArea}` umschlossen werden. Wir könnten unsere Vorlage wie folgt ändern. +Ein dynamisches Snippet bedeutet in der Latte-Terminologie einen spezifischen Anwendungsfall des `{snippet}`-Tags, bei dem im Snippetnamen eine Variable verwendet wird. Ein solches Snippet kann nicht einfach irgendwo im Template stehen - es muss von einem statischen Snippet, d.h. einem gewöhnlichen, oder innerhalb von `{snippetArea}` umschlossen sein. Unser Template könnten wir wie folgt anpassen. ```latte @@ -53,18 +53,18 @@ In der Latte-Terminologie ist ein dynamisches Snippet ein spezieller Anwendungsf <div class="content">{$article->content}</div> {snippet article-{$article->id}} {if !$article->liked} - <a n:href="like! $article->id" class=ajax>I like it</a> + <a n:href="like! $article->id" class=ajax>Gefällt mir</a> {else} - <a n:href="unlike! $article->id" class=ajax>I don't like it anymore</a> + <a n:href="unlike! $article->id" class=ajax>Gefällt mir nicht mehr</a> {/if} {/snippet} </article> {/snippet} ``` -Jeder Artikel definiert nun ein einzelnes Snippet, das eine Artikel-ID im Titel hat. Alle diese Snippets werden dann in einem einzigen Snippet namens `articlesContainer` zusammengefasst. Wenn wir dieses Snippet weglassen, wird Latte uns mit einer Ausnahme warnen. +Jeder Artikel definiert nun ein Snippet, das die ID des Artikels im Namen trägt. Alle diese Snippets sind dann zusammen in einem Snippet mit dem Namen `articlesContainer` verpackt. Wenn wir dieses umschließende Snippet weglassen würden, würde uns Latte mit einer Ausnahme darauf hinweisen. -Alles, was noch zu tun ist, ist, den Presenter neu zu zeichnen - einfach den statischen Wrapper neu zu zeichnen. +Es bleibt uns übrig, das Neuzeichnen im Presenter zu ergänzen - es genügt, die statische Hülle neu zu zeichnen. ```php public function handleLike(int $articleId): void @@ -72,18 +72,18 @@ public function handleLike(int $articleId): void $this->ratingService->saveLike($articleId, $this->user->id); if ($this->isAjax()) { $this->redrawControl('articlesContainer'); - // $this->redrawControl('article-' . $articleId); -- není potřeba + // $this->redrawControl('article-' . $articleId); -- ist nicht notwendig } else { $this->redirect('this'); } } ``` -Ändern Sie die Schwestermethode `handleUnlike()` auf die gleiche Weise, und AJAX ist einsatzbereit! +Analog passen wir auch die Schwestermethode `handleUnlike()` an, und AJAX ist funktionsfähig! -Die Lösung hat jedoch einen Nachteil. Wenn wir uns genauer ansehen, wie die AJAX-Anfrage funktioniert, stellen wir fest, dass die Anwendung zwar effizient aussieht (sie gibt nur ein einziges Snippet für einen bestimmten Artikel zurück), aber tatsächlich alle Snippets auf dem Server wiedergibt. Sie hat das gewünschte Snippet in unserem Payload platziert und die anderen verworfen (und damit unnötigerweise auch aus der Datenbank abgerufen). +Die Lösung hat jedoch einen Nachteil. Wenn wir genauer untersuchen, wie die AJAX-Anfrage abläuft, stellen wir fest, dass, obwohl sich die Anwendung nach außen hin sparsam verhält (sie gibt nur ein einziges Snippet für den gegebenen Artikel zurück), sie tatsächlich auf dem Server alle Snippets gerendert hat. Das gewünschte Snippet wurde uns in den Payload platziert, und die anderen wurden verworfen (sie wurden also auch völlig unnötig aus der Datenbank abgerufen). -Um diesen Prozess zu optimieren, müssen wir die Sammlung `$articles` an die Vorlage übergeben (z. B. in der Methode `renderDefault()` ). Wir werden uns die Tatsache zunutze machen, dass die Signalverarbeitung vor den `render<Something>` Methoden: +Um diesen Prozess zu optimieren, müssen wir dort eingreifen, wo wir die Sammlung `$articles` an das Template übergeben (angenommen in der Methode `renderDefault()`). Wir nutzen die Tatsache, dass die Signalverarbeitung vor den `render<Something>`-Methoden erfolgt: ```php public function handleLike(int $articleId): void @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connection->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,18 +101,18 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connection->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` -Wenn das Signal verarbeitet wird, wird statt einer Sammlung mit allen Artikeln nur ein Array mit einem einzigen Artikel an die Vorlage übergeben - derjenige, den wir rendern und als Nutzlast an den Browser senden wollen. Somit wird `{foreach}` nur einmal ausgeführt und es werden keine zusätzlichen Snippets gerendert. +Nun wird bei der Signalverarbeitung anstelle der Sammlung mit allen Artikeln nur ein Array mit einem einzigen Artikel an das Template übergeben - demjenigen, den wir rendern und im Payload an den Browser senden möchten. `{foreach}` wird also nur einmal durchlaufen und keine zusätzlichen Snippets werden gerendert. -Komponente Weg .[#toc-component-way] -==================================== +Der Weg der Komponenten +======================= -Eine völlig andere Lösung verwendet einen anderen Ansatz, um dynamische Snippets zu vermeiden. Der Trick besteht darin, die gesamte Logik in eine separate Komponente zu verlagern - von nun an haben wir keinen Presenter mehr, der sich um die Eingabe der Bewertung kümmert, sondern eine eigene `LikeControl`. Die Klasse wird wie folgt aussehen (außerdem wird sie auch die Methoden `render`, `handleUnlike`, usw. enthalten): +Eine völlig andere Lösung vermeidet dynamische Snippets. Der Trick besteht darin, die gesamte Logik in eine separate Komponente zu übertragen - um die Eingabe von Bewertungen kümmert sich von nun an nicht mehr der Presenter, sondern eine dedizierte `LikeControl`. Die Klasse wird wie folgt aussehen (außerdem wird sie auch Methoden `render`, `handleUnlike` usw. enthalten): ```php class LikeControl extends Nette\Application\UI\Control @@ -134,31 +134,31 @@ class LikeControl extends Nette\Application\UI\Control } ``` -Vorlage der Komponente: +Template der Komponente: ```latte {snippet} {if !$article->liked} - <a n:href="like!" class=ajax>I like it</a> + <a n:href="like!" class=ajax>Gefällt mir</a> {else} - <a n:href="unlike!" class=ajax>I don't like it anymore</a> + <a n:href="unlike!" class=ajax>Gefällt mir nicht mehr</a> {/if} {/snippet} ``` -Natürlich werden wir die Ansichtsvorlage ändern und dem Präsentator eine Fabrik hinzufügen. Da wir die Komponente so oft erstellen werden, wie wir Artikel aus der Datenbank erhalten, werden wir die Klasse [application:Multiplier] verwenden, um sie zu "vervielfältigen". +Natürlich ändert sich unser View-Template und wir müssen eine Factory-Methode zum Presenter hinzufügen. Da wir die Komponente so oft erstellen, wie wir Artikel aus der Datenbank abrufen, verwenden wir zu ihrer "Vervielfältigung" die Klasse [application:Multiplier]. ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); } ``` -Die Template-Ansicht ist auf das notwendige Minimum reduziert (und völlig frei von Snippets!): +Das View-Template wird auf das notwendige Minimum reduziert (und völlig frei von Snippets!): ```latte <article n:foreach="$articles as $article"> @@ -168,8 +168,6 @@ Die Template-Ansicht ist auf das notwendige Minimum reduziert (und völlig frei </article> ``` -Wir sind fast fertig: die Anwendung wird nun in AJAX funktionieren. Auch hier müssen wir die Anwendung optimieren, denn aufgrund der Verwendung der Nette-Datenbank wird die Signalverarbeitung unnötigerweise alle Artikel aus der Datenbank laden, anstatt nur einen. Der Vorteil ist jedoch, dass es kein Rendering gibt, da nur unsere Komponente tatsächlich gerendert wird. - +Wir sind fast fertig: Die Anwendung wird nun AJAX-fähig funktionieren. Auch hier müssen wir die Anwendung optimieren, da aufgrund der Verwendung von Nette Database bei der Signalverarbeitung unnötigerweise alle Artikel aus der Datenbank anstelle von nur einem geladen werden. Der Vorteil ist jedoch, dass es nicht zu deren Rendern kommt, da tatsächlich nur unsere Komponente gerendert wird. {{priority: -1}} -{{sitename: Bewährte Praktiken}} diff --git a/best-practices/de/editors-and-tools.texy b/best-practices/de/editors-and-tools.texy index 46d986e163..bda0c9c7fb 100644 --- a/best-practices/de/editors-and-tools.texy +++ b/best-practices/de/editors-and-tools.texy @@ -1,40 +1,40 @@ -Redakteure & Tools -****************** +Editoren & Werkzeuge +******************** .[perex] -Sie können ein guter Programmierer sein, aber nur mit guten Werkzeugen werden Sie ein Meister. In diesem Kapitel finden Sie Tipps zu wichtigen Tools, Editoren und Plugins. +Sie können ein geschickter Programmierer sein, aber erst mit guten Werkzeugen werden Sie zum Meister. In diesem Kapitel finden Sie Tipps zu wichtigen Werkzeugen, Editoren und Plugins. -IDE-Editor .[#toc-ide-editor] -============================= +IDE-Editor +========== -Wir empfehlen dringend, für die Entwicklung eine voll funktionsfähige IDE wie PhpStorm, NetBeans oder VS Code zu verwenden, und nicht nur einen Texteditor mit PHP-Unterstützung. Der Unterschied ist wirklich entscheidend. Es gibt keinen Grund, sich mit einem klassischen Editor mit Syntaxhervorhebung zufrieden zu geben, da dieser nicht an die Fähigkeiten einer IDE mit präzisen Codevorschlägen, die Code refaktorisieren kann, und mehr heranreicht. Einige IDEs sind kostenpflichtig, andere sind kostenlos. +Wir empfehlen dringend, für die Entwicklung eine vollwertige IDE wie PhpStorm, NetBeans, VS Code zu verwenden und nicht nur einen Texteditor mit PHP-Unterstützung. Der Unterschied ist wirklich grundlegend. Es gibt keinen Grund, sich mit einem reinen Editor zufrieden zu geben, der zwar Syntax hervorheben kann, aber nicht die Möglichkeiten einer Spitzen-IDE erreicht, die präzise Vorschläge macht, Fehler überwacht, Code refaktorieren kann und vieles mehr. Einige IDEs sind kostenpflichtig, andere sogar kostenlos. -**NetBeans IDE** hat integrierte Unterstützung für Nette, Latte und NEON. +**NetBeans IDE** hat bereits integrierte Unterstützung für Nette, Latte und NEON. -**PhpStorm**: Installieren Sie diese Plugins unter `Settings > Plugins > Marketplace`: -- Nette-Framework-Hilfsmittel +**PhpStorm**: Installieren Sie diese Plugins unter `Settings > Plugins > Marketplace` +- Nette framework helpers - Latte -- NEON-Unterstützung -- Nette-Tester +- NEON support +- Nette Tester -**VS Code**: finden Sie das "Nette Latte + Neon" Plugin auf dem Marktplatz. +**VS Code**: Suchen Sie im Marketplace nach dem Plugin "Nette Latte + Neon". -Verbinden Sie Tracy auch mit dem Editor. Wenn die Fehlerseite angezeigt wird, können Sie auf die Dateinamen klicken und sie werden im Editor mit dem Cursor auf der entsprechenden Zeile geöffnet. Erfahren Sie, [wie Sie das System konfigurieren |tracy:open-files-in-ide] können. +Verbinden Sie auch Tracy mit dem Editor. Bei der Anzeige einer Fehlerseite können Sie dann auf Dateinamen klicken und diese werden im Editor mit dem Cursor an der entsprechenden Zeile geöffnet. Lesen Sie, [wie das System konfiguriert wird|tracy:open-files-in-ide]. -PHPStan .[#toc-phpstan] -======================= +PHPStan +======= -PHPStan ist ein Werkzeug, das logische Fehler in Ihrem Code aufspürt, bevor Sie ihn ausführen. +PHPStan ist ein Werkzeug, das logische Fehler im Code aufdeckt, bevor Sie ihn ausführen. -Installieren Sie es über Composer: +Wir installieren es mit Composer: ```shell composer require --dev phpstan/phpstan-nette ``` -Erstellen Sie eine Konfigurationsdatei `phpstan.neon` im Projekt: +Wir erstellen im Projekt eine Konfigurationsdatei `phpstan.neon`: ```neon includes: @@ -47,40 +47,38 @@ parameters: level: 5 ``` -Und lassen Sie dann die Klassen im Ordner `app/` analysieren: +Und lassen es anschließend die Klassen im Ordner `app/` analysieren: ```shell vendor/bin/phpstan analyse app ``` -Eine ausführliche Dokumentation finden Sie direkt bei [PHPStan |https://phpstan.org]. +Eine ausführliche Dokumentation finden Sie direkt auf den [PHPStan-Seiten |https://phpstan.org]. -Code-Prüfer .[#toc-code-checker] -================================ +Code Checker +============ -[Code Checker |code-checker:] überprüft und repariert möglicherweise einige der formalen Fehler in Ihrem Quellcode. +Der [Code Checker|code-checker:] überprüft und korrigiert gegebenenfalls einige formale Fehler in Ihren Quellcodes: -- entfernt [BOM |nette:glossary#bom] -- prüft die Gültigkeit von [Latte-Vorlagen |latte:] -- prüft die Gültigkeit der Dateien `.neon`, `.php` und `.json` -- prüft auf [Steuerzeichen |nette:glossary#control characters] -- prüft, ob die Datei in UTF-8 kodiert ist -- prüft falsch geschriebene `/* @annotations */` (zweites Sternchen fehlt) -- entfernt PHP-Endungstags `?>` in PHP-Dateien -- entfernt nachstehende Leerzeichen und unnötige Leerzeilen am Ende einer Datei -- normalisiert die Zeilenenden auf die Systemvorgabe (mit dem Parameter `-l` ) +- entfernt [BOM |nette:glossary#BOM] +- überprüft die Gültigkeit von [Latte |latte:]-Templates +- überprüft die Gültigkeit von `.neon`-, `.php`- und `.json`-Dateien +- überprüft das Vorkommen von [Steuerzeichen |nette:glossary#Steuerzeichen] +- überprüft, ob die Datei in UTF-8 kodiert ist +- überprüft falsch geschriebene `/* @annotation */` (Stern fehlt) +- entfernt abschließende `?>` bei PHP-Dateien +- entfernt Leerzeichen am Zeilenende und unnötige Zeilen am Ende der Datei +- normalisiert Zeilentrenner auf Systemstandard (wenn Sie die Option `-l` angeben) -Composer .[#toc-composer] -========================= +Composer +======== -[Composer] ist ein Werkzeug zur Verwaltung von Abhängigkeiten in PHP. Es erlaubt uns, Bibliotheksabhängigkeiten zu deklarieren und es wird sie für uns in unserem Projekt installieren. +[Composer] ist ein Werkzeug zur Verwaltung von Abhängigkeiten in PHP. Es ermöglicht uns, beliebig komplexe Abhängigkeiten einzelner Bibliotheken zu deklarieren und diese dann für uns in unser Projekt zu installieren. -Anforderungs-Checker .[#toc-requirements-checker] -================================================= +Requirements Checker +==================== -Dabei handelt es sich um ein Tool, das die Betriebsumgebung des Servers testet und Auskunft darüber gibt, ob (und in welchem Umfang) das Framework verwendet werden kann. Derzeit kann Nette auf jedem Server verwendet werden, der über die erforderliche Mindestversion von PHP verfügt. - -{{sitename: Bewährte Praktiken}} +Dies war ein Werkzeug, das die Laufzeitumgebung des Servers testete und informierte, ob (und inwieweit) das Framework verwendet werden kann. Derzeit kann Nette auf jedem Server verwendet werden, der die minimal erforderliche PHP-Version hat. diff --git a/best-practices/de/form-reuse.texy b/best-practices/de/form-reuse.texy index 08cc17d64f..0da82ed0e4 100644 --- a/best-practices/de/form-reuse.texy +++ b/best-practices/de/form-reuse.texy @@ -1,16 +1,16 @@ -Formulare an mehreren Stellen wiederverwenden -********************************************* +Wiederverwendung von Formularen an mehreren Stellen +*************************************************** .[perex] -In Nette haben Sie mehrere Möglichkeiten, dasselbe Formular an mehreren Stellen wiederzuverwenden, ohne Code zu duplizieren. In diesem Artikel gehen wir auf die verschiedenen Lösungen ein, einschließlich derer, die Sie vermeiden sollten. +In Nette haben Sie mehrere Möglichkeiten, dasselbe Formular an mehreren Stellen zu verwenden und Code nicht zu duplizieren. In diesem Artikel zeigen wir Ihnen verschiedene Lösungen, einschließlich solcher, die Sie vermeiden sollten. -Formular-Fabrik .[#toc-form-factory] -==================================== +Formular-Factory +================ -Ein grundlegender Ansatz für die Verwendung derselben Komponente an mehreren Stellen besteht darin, eine Methode oder Klasse zu erstellen, die die Komponente erzeugt, und diese Methode dann an verschiedenen Stellen in der Anwendung aufzurufen. Eine solche Methode oder Klasse wird als *Factory* bezeichnet. Bitte nicht mit dem Entwurfsmuster *Fabrikmethode* verwechseln, das eine spezielle Art der Verwendung von Fabriken beschreibt und nicht mit diesem Thema zusammenhängt. +Eine der grundlegenden Herangehensweisen, dieselbe Komponente an mehreren Stellen zu verwenden, ist die Erstellung einer Methode oder Klasse, die diese Komponente generiert, und der anschließende Aufruf dieser Methode an verschiedenen Stellen der Anwendung. Eine solche Methode oder Klasse wird *Factory* genannt. Bitte nicht verwechseln mit dem Entwurfsmuster *Factory Method*, das eine spezifische Verwendung von Factories beschreibt und nichts mit diesem Thema zu tun hat. -Lassen Sie uns als Beispiel eine Fabrik erstellen, die ein Bearbeitungsformular erstellt: +Als Beispiel erstellen wir eine Factory, die ein Bearbeitungsformular erstellt: ```php use Nette\Application\UI\Form; @@ -20,22 +20,22 @@ class FormFactory public function createEditForm(): Form { $form = new Form; - $form->addText('title', 'Title:'); - // zusätzliche Formularfelder werden hier hinzugefügt - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Titel:'); + // hier werden weitere Formularfelder hinzugefügt + $form->addSubmit('send', 'Senden'); return $form; } } ``` -Jetzt können Sie diese Fabrik an verschiedenen Stellen in Ihrer Anwendung verwenden, zum Beispiel in Presentern oder Komponenten. Und wir tun dies, indem wir [sie als Abhängigkeit anfordern |dependency-injection:passing-dependencies]. Zuerst schreiben wir also die Klasse in die Konfigurationsdatei: +Nun können Sie diese Factory an verschiedenen Stellen in Ihrer Anwendung verwenden, beispielsweise in Presentern oder Komponenten. Und zwar, indem Sie sie [als Abhängigkeit anfordern|dependency-injection:passing-dependencies]. Zuerst schreiben wir also die Klasse in die Konfigurationsdatei: ```neon services: - FormFactory ``` -Und dann verwenden wir sie im Präsentator: +Und dann verwenden wir sie im Presenter: ```php @@ -57,7 +57,7 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Sie können die Formularfabrik mit zusätzlichen Methoden erweitern, um andere Arten von Formularen für Ihre Anwendung zu erstellen. Und natürlich können Sie auch eine Methode hinzufügen, die ein Basisformular ohne Elemente erstellt, das die anderen Methoden verwenden werden: +Sie können die Formular-Factory um weitere Methoden zur Erstellung anderer Formulartypen erweitern, je nach Bedarf Ihrer Anwendung. Und natürlich können wir auch eine Methode hinzufügen, die ein Basisformular ohne Elemente erstellt, und diese werden die anderen Methoden nutzen: ```php class FormFactory @@ -71,21 +71,21 @@ class FormFactory public function createEditForm(): Form { $form = $this->createForm(); - $form->addText('title', 'Title:'); - // zusätzliche Formularfelder werden hier hinzugefügt - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Titel:'); + // hier werden weitere Formularfelder hinzugefügt + $form->addSubmit('send', 'Senden'); return $form; } } ``` -Die Methode `createForm()` tut noch nichts Nützliches, aber das wird sich schnell ändern. +Die Methode `createForm()` tut bisher nichts Nützliches, aber das wird sich schnell ändern. -Abhängigkeiten von der Fabrik .[#toc-factory-dependencies] -========================================================== +Abhängigkeiten der Factory +========================== -Mit der Zeit wird sich herausstellen, dass die Formulare mehrsprachig sein müssen. Das bedeutet, dass wir einen [Übersetzer |forms:rendering#Translating] für alle Formulare einrichten müssen. Zu diesem Zweck ändern wir die Klasse `FormFactory` so, dass sie das Objekt `Translator` als Abhängigkeit im Konstruktor akzeptiert und an das Formular übergibt: +Mit der Zeit stellt sich heraus, dass wir Formulare mehrsprachig benötigen. Das bedeutet, dass wir allen Formularen einen sogenannten [Translator |forms:rendering#Übersetzung] zuweisen müssen. Zu diesem Zweck passen wir die Klasse `FormFactory` so an, dass sie das `Translator`-Objekt als Abhängigkeit im Konstruktor akzeptiert und es an das Formular übergibt: ```php use Nette\Localization\Translator; @@ -104,18 +104,17 @@ class FormFactory return $form; } - //... + // ... } ``` -Da die Methode `createForm()` auch von anderen Methoden aufgerufen wird, die bestimmte Formulare erstellen, müssen wir den Übersetzer nur in dieser Methode festlegen. Und schon sind wir fertig. Es ist nicht nötig, den Code des Presenters oder der Komponente zu ändern, was großartig ist. +Da die Methode `createForm()` auch von anderen Methoden aufgerufen wird, die spezifische Formulare erstellen, genügt es, den Translator nur in ihr zu setzen. Und wir sind fertig. Es ist nicht nötig, den Code irgendeines Presenters oder einer Komponente zu ändern, was großartig ist. -Weitere Factory-Klassen .[#toc-more-factory-classes] -==================================================== +Mehrere Factory-Klassen +======================= -Alternativ können Sie für jedes Formular, das Sie in Ihrer Anwendung verwenden möchten, mehrere Klassen erstellen. -Dieser Ansatz kann die Lesbarkeit des Codes erhöhen und die Verwaltung der Formulare erleichtern. Belassen Sie das Original `FormFactory`, um nur ein reines Formular mit Grundkonfiguration zu erstellen (z. B. mit Übersetzungsunterstützung), und erstellen Sie eine neue Fabrik `EditFormFactory` für das Bearbeitungsformular. +Alternativ können Sie mehrere Klassen für jedes Formular erstellen, das Sie in Ihrer Anwendung verwenden möchten. Dieser Ansatz kann die Lesbarkeit des Codes erhöhen und die Verwaltung von Formularen erleichtern. Die ursprüngliche `FormFactory` lassen wir nur ein reines Formular mit Grundkonfiguration erstellen (z.B. mit Übersetzungsunterstützung) und für das Bearbeitungsformular erstellen wir eine neue Factory `EditFormFactory`. ```php class FormFactory @@ -134,7 +133,7 @@ class FormFactory } -// ✅ Verwendung der Zusammensetzung +// ✅ Verwendung von Komposition class EditFormFactory { public function __construct( @@ -145,40 +144,39 @@ class EditFormFactory public function create(): Form { $form = $this->formFactory->create(); - // hier werden zusätzliche Formularfelder hinzugefügt - $form->addSubmit('send', 'Save'); + // hier werden weitere Formularfelder hinzugefügt + $form->addSubmit('send', 'Senden'); return $form; } } ``` -Es ist sehr wichtig, dass die Bindung zwischen den Klassen `FormFactory` und `EditFormFactory` durch Komposition und nicht durch Objektvererbung implementiert wird: +Sehr wichtig ist, dass die Bindung zwischen den Klassen `FormFactory` und `EditFormFactory` durch [Komposition |nette:introduction-to-object-oriented-programming#Komposition] realisiert wird, nicht durch [objektorientierte Vererbung |nette:introduction-to-object-oriented-programming#Vererbung]: ```php -// ⛔ NEIN! VERERBUNG GEHÖRT HIER NICHT HIN +// ⛔ SO NICHT! HIER GEHÖRT KEINE VERERBUNG HIN class EditFormFactory extends FormFactory { public function create(): Form { $form = parent::create(); - $form->addText('title', 'Title:'); - // zusätzliche Formularfelder werden hier hinzugefügt - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Titel:'); + // hier werden weitere Formularfelder hinzugefügt + $form->addSubmit('send', 'Senden'); return $form; } } ``` -Die Verwendung von Vererbung wäre in diesem Fall völlig kontraproduktiv. Sie würden sehr schnell auf Probleme stoßen. Wenn Sie z.B. der Methode `create()` Parameter hinzufügen wollten, würde PHP einen Fehler melden, dass sich die Signatur der Methode von der des Elternteils unterscheidet. -Oder bei der Übergabe einer Abhängigkeit an die Klasse `EditFormFactory` über den Konstruktor. Dies würde zu dem führen, was wir [Konstruktorhölle |dependency-injection:passing-dependencies#Constructor hell] nennen. +Die Verwendung von Vererbung wäre in diesem Fall völlig kontraproduktiv. Sie würden sehr schnell auf Probleme stoßen. Zum Beispiel, wenn Sie der Methode `create()` Parameter hinzufügen möchten; PHP würde einen Fehler melden, dass ihre Signatur von der der Elternklasse abweicht. Oder bei der Übergabe von Abhängigkeiten an die Klasse `EditFormFactory` über den Konstruktor. Es würde eine Situation entstehen, die wir [Constructor Hell |dependency-injection:passing-dependencies#Constructor Hell] nennen. -Im Allgemeinen ist es besser, Komposition der Vererbung vorzuziehen. +Im Allgemeinen ist es besser, [Komposition der Vererbung vorzuziehen |dependency-injection:faq#Warum wird Komposition der Vererbung vorgezogen]. -Handhabung von Formularen .[#toc-form-handling] -=============================================== +Formularverarbeitung +==================== -Der Formular-Handler, der nach einer erfolgreichen Übermittlung aufgerufen wird, kann auch Teil einer Fabrikklasse sein. Er arbeitet, indem er die übermittelten Daten zur Verarbeitung an das Modell weitergibt. Eventuelle Fehler werden [an |forms:validation#Processing Errors] das Formular [zurückgegeben |forms:validation#Processing Errors]. Das Modell im folgenden Beispiel wird durch die Klasse `Facade` repräsentiert: +Die Verarbeitung des Formulars, die nach erfolgreichem Absenden aufgerufen wird, kann auch Teil der Factory-Klasse sein. Sie wird so funktionieren, dass sie die gesendeten Daten zur Verarbeitung an das Modell übergibt. Eventuelle Fehler [gibt sie zurück |forms:validation#Fehler bei der Verarbeitung] an das Formular. Das Modell wird im folgenden Beispiel durch die Klasse `Facade` repräsentiert: ```php class EditFormFactory @@ -192,9 +190,9 @@ class EditFormFactory public function create(): Form { $form = $this->formFactory->create(); - $form->addText('title', 'Title:'); - // zusätzliche Formularfelder werden hier hinzugefügt - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Titel:'); + // hier werden weitere Formularfelder hinzugefügt + $form->addSubmit('send', 'Senden'); $form->onSuccess[] = [$this, 'processForm']; return $form; } @@ -202,7 +200,7 @@ class EditFormFactory public function processForm(Form $form, array $data): void { try { - // Verarbeitung der übermittelten Daten + // Verarbeitung der gesendeten Daten $this->facade->process($data); } catch (AnyModelException $e) { @@ -212,7 +210,7 @@ class EditFormFactory } ``` -Der Präsentator soll die Umleitung selbst durchführen. Er fügt dem Ereignis `onSuccess` einen weiteren Handler hinzu, der die Umleitung durchführt. Auf diese Weise kann das Formular in verschiedenen Präsentatoren verwendet werden, und jeder kann an eine andere Stelle weiterleiten. +Die eigentliche Weiterleitung überlassen wir jedoch dem Presenter. Dieser fügt dem `onSuccess`-Ereignis einen weiteren Handler hinzu, der die Weiterleitung durchführt. Dadurch wird es möglich, das Formular in verschiedenen Presentern zu verwenden und in jedem woandershin weiterzuleiten. ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -226,7 +224,7 @@ class MyPresenter extends Nette\Application\UI\Presenter { $form = $this->formFactory->create(); $form->onSuccess[] = function () { - $this->flashMessage('Záznam byl uložen'); + $this->flashMessage('Der Datensatz wurde gespeichert'); $this->redirect('Homepage:'); }; return $form; @@ -234,39 +232,38 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Diese Lösung macht sich die Eigenschaft von Formularen zunutze, dass beim Aufruf von `addError()` auf einem Formular oder seinem Element der nächste `onSuccess` -Handler nicht aufgerufen wird. +Diese Lösung nutzt die Eigenschaft von Formularen, dass, wenn `addError()` über dem Formular oder einem seiner Elemente aufgerufen wird, der nächste `onSuccess`-Handler nicht mehr aufgerufen wird. -Vererbung von der Formularklasse .[#toc-inheriting-from-the-form-class] -======================================================================= +Vererbung von der Klasse Form +============================= -Ein erstelltes Formular sollte nicht das Kind eines Formulars sein. Mit anderen Worten: Verwenden Sie diese Lösung nicht: +Das erstellte Formular soll kein Nachkomme der Klasse `Form` sein. Mit anderen Worten, verwenden Sie nicht diese Lösung: ```php -// ⛔ NEIN! VERERBUNG GEHÖRT HIER NICHT HIN +// ⛔ SO NICHT! HIER GEHÖRT KEINE VERERBUNG HIN class EditForm extends Form { public function __construct(Translator $translator) { parent::__construct(); - $form->addText('title', 'Title:'); - // zusätzliche Formularfelder werden hier hinzugefügt - $form->addSubmit('send', 'Save'); - $form->setTranslator($translator); + $this->addText('title', 'Titel:'); + // hier werden weitere Formularfelder hinzugefügt + $this->addSubmit('send', 'Senden'); + $this->setTranslator($translator); } } ``` -Anstatt das Formular im Konstruktor zu erstellen, verwenden Sie die Fabrik. +Anstatt das Formular im Konstruktor zu erstellen, verwenden Sie eine Factory. -Es ist wichtig zu erkennen, dass die Klasse `Form` in erster Linie ein Werkzeug zum Zusammenstellen eines Formulars ist, d.h. ein Formularersteller. Und das zusammengesetzte Formular kann als ihr Produkt betrachtet werden. Das Produkt ist jedoch kein Sonderfall des Builders; es gibt keine *ist eine* Beziehung zwischen ihnen, die die Grundlage der Vererbung bildet. +Es ist wichtig zu erkennen, dass die Klasse `Form` in erster Linie ein Werkzeug zur Erstellung eines Formulars ist, also ein *Form Builder*. Und das erstellte Formular kann als ihr Produkt betrachtet werden. Aber ein Produkt ist kein spezifischer Fall eines Builders, es gibt keine *is a*-Beziehung zwischen ihnen, die die Grundlage der Vererbung bildet. -Formular-Komponente .[#toc-form-component] -========================================== +Komponente mit Formular +======================= -Ein völlig anderer Ansatz besteht darin, eine [Komponente |application:components] zu erstellen, die ein Formular enthält. Dadurch ergeben sich neue Möglichkeiten, z. B. um das Formular auf eine bestimmte Art und Weise zu rendern, da die Komponente eine Vorlage enthält. -Oder es können Signale für die AJAX-Kommunikation und das Laden von Informationen in das Formular verwendet werden, z. B. für Hinting usw. +Ein völlig anderer Ansatz ist die Erstellung einer [Komponente|application:components], die ein Formular enthält. Dies eröffnet neue Möglichkeiten, zum Beispiel das Formular auf spezifische Weise zu rendern, da die Komponente auch ein Template enthält. Oder man kann Signale für die AJAX-Kommunikation und das Nachladen von Informationen in das Formular nutzen, zum Beispiel für Vorschläge usw. ```php @@ -284,9 +281,9 @@ class EditControl extends Nette\Application\UI\Control protected function createComponentForm(): Form { $form = new Form; - $form->addText('title', 'Title:'); - // zusätzliche Formularfelder werden hier hinzugefügt - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Titel:'); + // hier werden weitere Formularfelder hinzugefügt + $form->addSubmit('send', 'Senden'); $form->onSuccess[] = [$this, 'processForm']; return $form; @@ -295,7 +292,7 @@ class EditControl extends Nette\Application\UI\Control public function processForm(Form $form, array $data): void { try { - // Verarbeitung der übermittelten Daten + // Verarbeitung der gesendeten Daten $this->facade->process($data); } catch (AnyModelException $e) { @@ -303,13 +300,13 @@ class EditControl extends Nette\Application\UI\Control return; } - // Ereignisaufruf + // Ereignis auslösen $this->onSave($this, $data); } } ``` -Lassen Sie uns eine Fabrik erstellen, die diese Komponente produzieren wird. Es genügt, [ihre Schnittstelle |application:components#Components with Dependencies] zu schreiben: +Wir erstellen noch eine Factory, die diese Komponente herstellt. Es genügt, [ihr Interface zu definieren |application:components#Komponenten mit Abhängigkeiten]: ```php interface EditControlFactory @@ -318,14 +315,14 @@ interface EditControlFactory } ``` -Und fügen Sie sie der Konfigurationsdatei hinzu: +Und in die Konfigurationsdatei hinzufügen: ```neon services: - EditControlFactory ``` -Jetzt können wir die Fabrik anfordern und sie im Präsentator verwenden: +Und nun können wir die Factory anfordern und im Presenter verwenden: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -335,13 +332,13 @@ class MyPresenter extends Nette\Application\UI\Presenter ) { } - protected function createComponentEditForm(): Form + protected function createComponentEditForm(): EditControl { $control = $this->controlFactory->create(); $control->onSave[] = function (EditControl $control, $data) { $this->redirect('this'); - // oder auf das Ergebnis der Bearbeitung umleiten, z. B: + // oder wir leiten zum Ergebnis der Bearbeitung weiter, z.B.: // $this->redirect('detail', ['id' => $data->id]); }; @@ -349,5 +346,3 @@ class MyPresenter extends Nette\Application\UI\Presenter } } ``` - -{{sitename: Bewährte Praktiken}} diff --git a/best-practices/de/inject-method-attribute.texy b/best-practices/de/inject-method-attribute.texy index 546eca4b00..45788eca01 100644 --- a/best-practices/de/inject-method-attribute.texy +++ b/best-practices/de/inject-method-attribute.texy @@ -1,21 +1,18 @@ -Injektionsmethoden und -attribute -********************************* +Methoden und Attribute inject +***************************** .[perex] -In diesem Artikel werden wir uns auf verschiedene Möglichkeiten konzentrieren, Abhängigkeiten an Presenter im Nette-Framework zu übergeben. Wir werden die bevorzugte Methode, nämlich den Konstruktor, mit anderen Optionen wie `inject` Methoden und Attributen vergleichen. +In diesem Artikel konzentrieren wir uns auf verschiedene Methoden zur Übergabe von Abhängigkeiten an Presenter im Nette Framework. Wir vergleichen die bevorzugte Methode, nämlich den Konstruktor, mit anderen Möglichkeiten wie `inject`-Methoden und -Attributen. -Auch für Presenter ist die Übergabe von Abhängigkeiten über den [Konstruktor |dependency-injection:passing-dependencies#Constructor Injection] die bevorzugte Methode. -Wenn Sie jedoch einen gemeinsamen Vorfahren erstellen, von dem andere Präsentatoren erben (z. B. BasePresenter), und dieser Vorfahre ebenfalls Abhängigkeiten hat, tritt ein Problem auf, das wir [Konstruktorhölle |dependency-injection:passing-dependencies#Constructor hell] nennen. -Dieses Problem kann durch alternative Methoden umgangen werden, die Methoden und Attribute (Annotationen) einschließen. +Auch für Presenter gilt, dass die Übergabe von Abhängigkeiten über den [Konstruktor |dependency-injection:passing-dependencies#Übergabe per Konstruktor] der bevorzugte Weg ist. Wenn Sie jedoch einen gemeinsamen Vorfahren erstellen, von dem andere Presenter erben (z. B. `BasePresenter`), und dieser Vorfahre ebenfalls Abhängigkeiten hat, tritt ein Problem auf, das wir [Constructor Hell |dependency-injection:passing-dependencies#Constructor Hell] nennen. Dies kann durch alternative Wege umgangen werden, die `inject`-Methoden und -Attribute (früher Annotationen) darstellen. -`inject*()` Methoden .[#toc-inject-methods] -=========================================== +`inject*()`-Methoden +==================== -Dies ist eine Form der Übergabe von Abhängigkeiten unter Verwendung von [Settern |dependency-injection:passing-dependencies#Setter Injection]. Die Namen dieser Setzer beginnen mit dem Präfix inject. -Nette DI ruft solche benannten Methoden unmittelbar nach dem Erzeugen der Presenter-Instanz automatisch auf und übergibt ihnen alle erforderlichen Abhängigkeiten. Sie müssen daher als public deklariert werden. +Dies ist eine Form der Abhängigkeitsübergabe per [Setter |dependency-injection:passing-dependencies#Übergabe per Setter]. Der Name dieser Setter beginnt mit dem Präfix `inject`. Nette DI ruft solche benannten Methoden automatisch sofort nach der Erstellung der Presenter-Instanz auf und übergibt ihnen alle erforderlichen Abhängigkeiten. Sie müssen daher als `public` deklariert sein. -`inject*()` Methoden können als eine Art Konstruktorerweiterung in mehrere Methoden betrachtet werden. Dadurch kann `BasePresenter` Abhängigkeiten durch eine andere Methode aufnehmen und den Konstruktor für seine Nachkommen frei lassen: +`inject*()`-Methoden können als eine Art Erweiterung des Konstruktors auf mehrere Methoden betrachtet werden. Dadurch kann `BasePresenter` Abhängigkeiten über eine andere Methode übernehmen und den Konstruktor für seine Nachkommen frei lassen: ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -39,18 +36,18 @@ class MyPresenter extends BasePresenter } ``` -Der Präsentator kann eine beliebige Anzahl von `inject*()` Methoden enthalten, und jede kann eine beliebige Anzahl von Parametern haben. Dies eignet sich auch hervorragend für Fälle, in denen der Presenter [aus Traits |presenter-traits] besteht, von denen jeder seine eigene Abhängigkeit erfordert. +Ein Presenter kann beliebig viele `inject*()`-Methoden enthalten, und jede kann beliebig viele Parameter haben. Sie eignen sich auch hervorragend in Fällen, in denen der Presenter [aus Traits zusammengesetzt ist |presenter-traits] und jede von ihnen ihre eigene Abhängigkeit benötigt. -`Inject` Attribute .[#toc-inject-attributes] -============================================ +`Inject`-Attribute +================== -Dies ist eine Form der [Injektion in Eigenschaften |dependency-injection:passing-dependencies#Property Injection]. Es genügt, anzugeben, welche Eigenschaften injiziert werden sollen, und Nette DI übergibt die Abhängigkeiten automatisch sofort nach der Erstellung der Presenter-Instanz. Um sie einzufügen, ist es notwendig, sie als öffentlich zu deklarieren. +Dies ist eine Form der [Injection in eine Eigenschaft |dependency-injection:passing-dependencies#Zuweisung zu einer Variablen]. Es genügt, zu markieren, in welche Eigenschaften injiziert werden soll, und Nette DI übergibt die Abhängigkeiten automatisch sofort nach der Erstellung der Presenter-Instanz. Damit sie eingefügt werden können, müssen sie als `public` deklariert sein. -Eigenschaften werden mit einem Attribut gekennzeichnet: (früher wurde die Annotation `/** @inject */` verwendet) +Eigenschaften markieren wir mit einem Attribut: (früher wurde die Annotation `/** @inject */` verwendet) ```php -use Nette\DI\Attributes\Inject; // diese Zeile ist wichtig +use Nette\DI\Attributes\Inject; // diese Zeile ist wichtig class MyPresenter extends Nette\Application\UI\Presenter { @@ -59,9 +56,6 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Der Vorteil dieser Methode der Übergabe von Abhängigkeiten war die sehr sparsame Notation. Mit der Einführung der [Förderung von Konstruktoreigenschaften |https://blog.nette.org/de/php-8-0-vollstaendiger-ueberblick-ueber-die-neuigkeiten#toc-constructor-property-promotion] scheint die Verwendung des Konstruktors jedoch einfacher zu sein. +Der Vorteil dieser Art der Abhängigkeitsübergabe war die sehr sparsame Schreibweise. Mit der Einführung von [Constructor Property Promotion |https://blog.nette.org/de/php-8-0-kompletter-ueberblick-ueber-neuerungen#toc-constructor-property-promotion] erscheint es jedoch einfacher, den Konstruktor zu verwenden. -Andererseits leidet diese Methode unter denselben Mängeln wie die Übergabe von Abhängigkeiten in Eigenschaften im Allgemeinen: Wir haben keine Kontrolle über Änderungen an der Variablen, und gleichzeitig wird die Variable Teil der öffentlichen Schnittstelle der Klasse, was unerwünscht ist. - - -{{sitename: Bewährte Praktiken}} +Im Gegenteil leidet diese Methode unter denselben Nachteilen wie die Übergabe von Abhängigkeiten an Eigenschaften im Allgemeinen: Wir haben keine Kontrolle über Änderungen in der Variablen, und gleichzeitig wird die Variable Teil der öffentlichen Schnittstelle der Klasse, was unerwünscht ist. diff --git a/best-practices/de/lets-create-contact-form.texy b/best-practices/de/lets-create-contact-form.texy index 67b26710b2..998313e20f 100644 --- a/best-practices/de/lets-create-contact-form.texy +++ b/best-practices/de/lets-create-contact-form.texy @@ -1,12 +1,12 @@ -Erstellen wir ein Kontakt-Formular -********************************** +Wir erstellen ein Kontaktformular +********************************* .[perex] -Schauen wir uns an, wie man in Nette ein Kontaktformular erstellt und es an eine E-Mail sendet. Also, los geht's! +Wir schauen uns an, wie man in Nette ein Kontaktformular erstellt, einschließlich des E-Mail-Versands. Los geht's! -Zuerst müssen wir ein neues Projekt erstellen. Wie auf der Seite " [Erste Schritte" |nette:installation] erklärt wird. Und dann können wir mit der Erstellung des Formulars beginnen. +Zuerst müssen wir ein neues Projekt erstellen. Wie das geht, erklärt die Seite [Erste Schritte |nette:installation]. Und dann können wir mit der Erstellung des Formulars beginnen. -Der einfachste Weg ist, das [Formular direkt in Presenter |forms:in-presenter] zu erstellen. Wir können die vorgefertigten `HomePresenter` verwenden. Wir werden die Komponente `contactForm` hinzufügen, die das Formular darstellt. Dies geschieht, indem wir die `createComponentContactForm()` Factory-Methode in den Code schreiben, der die Komponente erzeugt: +Am einfachsten ist die Erstellung eines [Formulars direkt im Presenter |forms:in-presenter]. Wir können den vorbereiteten `HomePresenter` verwenden. In ihn fügen wir die Komponente `contactForm` hinzu, die das Formular darstellt. Dazu schreiben wir eine Factory-Methode `createComponentContactForm()` in den Code, die die Komponente erstellt: ```php use Nette\Application\UI\Form; @@ -18,36 +18,34 @@ class HomePresenter extends Presenter { $form = new Form; $form->addText('name', 'Name:') - ->setRequired('Enter your name'); - $form->addEmail('email', 'E-mail:') - ->setRequired('Enter your e-mail'); - $form->addTextarea('message', 'Message:') - ->setRequired('Enter message'); - $form->addSubmit('send', 'Send'); + ->setRequired('Geben Sie einen Namen ein'); + $form->addEmail('email', 'E-Mail:') + ->setRequired('Geben Sie eine E-Mail-Adresse ein'); + $form->addTextarea('message', 'Nachricht:') + ->setRequired('Geben Sie eine Nachricht ein'); + $form->addSubmit('send', 'Senden'); $form->onSuccess[] = [$this, 'contactFormSucceeded']; return $form; } public function contactFormSucceeded(Form $form, $data): void { - // sending an email + // E-Mail senden } } ``` -Wie Sie sehen können, haben wir zwei Methoden erstellt. Die erste Methode `createComponentContactForm()` erstellt ein neues Formular. Dieses hat Felder für Name, E-Mail und Nachricht, die wir mit den Methoden `addText()`, `addEmail()` und `addTextArea()` hinzufügen. Wir haben auch eine Schaltfläche zum Absenden des Formulars hinzugefügt. -Was aber, wenn der Benutzer einige Felder nicht ausfüllt? In diesem Fall sollten wir ihn darauf hinweisen, dass es sich um ein Pflichtfeld handelt. Wir haben dies mit der Methode `setRequired()` getan. -Schließlich haben wir auch ein [Ereignis |nette:glossary#events] `onSuccess` hinzugefügt, das ausgelöst wird, wenn das Formular erfolgreich abgeschickt wurde. In unserem Fall ruft es die Methode `contactFormSucceeded` auf, die sich um die Verarbeitung des übermittelten Formulars kümmert. Das fügen wir dem Code gleich hinzu. +Wie Sie sehen, haben wir zwei Methoden erstellt. Die erste Methode `createComponentContactForm()` erstellt ein neues Formular. Dieses hat Felder für Name, E-Mail und Nachricht, die wir mit den Methoden `addText()`, `addEmail()` und `addTextArea()` hinzufügen. Wir haben auch einen Button zum Absenden des Formulars hinzugefügt. Aber was, wenn der Benutzer ein Feld nicht ausfüllt? In diesem Fall sollten wir ihm mitteilen, dass es sich um ein Pflichtfeld handelt. Das haben wir mit der Methode `setRequired()` erreicht. Schließlich haben wir auch das [Ereignis |nette:glossary#Events Ereignisse] `onSuccess` hinzugefügt, das ausgelöst wird, wenn das Formular erfolgreich gesendet wird. In unserem Fall ruft es die Methode `contactFormSucceeded` auf, die sich um die Verarbeitung des gesendeten Formulars kümmert. Das werden wir gleich in den Code einfügen. -Die Komponente `contantForm` soll in der Vorlage `templates/Home/default.latte` gerendert werden: +Die Komponente `contactForm` lassen wir im Template `Home/default.latte` rendern: ```latte {block content} -<h1>Contant Form</h1> +<h1>Kontaktformular</h1> {control contactForm} ``` -Um die E-Mail selbst zu versenden, erstellen wir eine neue Klasse namens `ContactFacade` und platzieren sie in der Datei `app/Model/ContactFacade.php`: +Für den eigentlichen E-Mail-Versand erstellen wir eine neue Klasse, die wir `ContactFacade` nennen und in der Datei `app/Model/ContactFacade.php` ablegen: ```php <?php @@ -68,9 +66,9 @@ class ContactFacade public function sendMessage(string $email, string $name, string $message): void { $mail = new Message; - $mail->addTo('admin@example.com') // your email + $mail->addTo('admin@example.com') // Ihre E-Mail ->setFrom($email, $name) - ->setSubject('Message from the contact form') + ->setSubject('Nachricht vom Kontaktformular') ->setBody($message); $this->mailer->send($mail); @@ -78,9 +76,9 @@ class ContactFacade } ``` -Die Methode `sendMessage()` wird die E-Mail erstellen und versenden. Sie verwendet dazu einen so genannten Mailer, den sie als Abhängigkeit über den Konstruktor übergibt. Lesen Sie mehr über das [Versenden von E-Mails |mail:]. +Die Methode `sendMessage()` erstellt und sendet eine E-Mail. Sie verwendet dazu einen sogenannten Mailer, den sie als Abhängigkeit über den Konstruktor erhält. Lesen Sie mehr über das [Senden von E-Mails |mail:]. -Nun kehren wir zum Presenter zurück und vervollständigen die Methode `contactFormSucceeded()`. Sie ruft die Methode `sendMessage()` der Klasse `ContactFacade` auf und übergibt ihr die Formulardaten. Und wie erhalten wir das `ContactFacade` Objekt? Wir lassen es uns vom Konstruktor übergeben: +Nun kehren wir zum Presenter zurück und vervollständigen die Methode `contactFormSucceeded()`. Diese ruft die Methode `sendMessage()` der Klasse `ContactFacade` auf und übergibt ihr die Daten aus dem Formular. Und wie erhalten wir das Objekt `ContactFacade`? Wir lassen es uns über den Konstruktor übergeben: ```php use App\Model\ContactFacade; @@ -102,36 +100,36 @@ class HomePresenter extends Presenter public function contactFormSucceeded(stdClass $data): void { $this->facade->sendMessage($data->email, $data->name, $data->message); - $this->flashMessage('The message has been sent'); + $this->flashMessage('Die Nachricht wurde gesendet'); $this->redirect('this'); } } ``` -Nachdem die E-Mail versendet wurde, zeigen wir dem Benutzer die so genannte [Flash-Nachricht |application:components#flash-messages] an, die bestätigt, dass die Nachricht versendet wurde, und leiten dann zur nächsten Seite weiter, damit das Formular nicht mit *refresh* im Browser erneut abgeschickt werden kann. +Nachdem die E-Mail gesendet wurde, zeigen wir dem Benutzer noch eine sogenannte [Flash-Nachricht |application:components#Flash-Nachrichten] an, die bestätigt, dass die Nachricht gesendet wurde, und leiten dann auf dieselbe Seite weiter (`this`), damit das Formular nicht durch *Aktualisieren* im Browser erneut gesendet werden kann. -Nun, wenn alles funktioniert, sollten Sie in der Lage sein, eine E-Mail über Ihr Kontaktformular zu versenden. Herzlichen Glückwunsch! +So, und wenn alles funktioniert, sollten Sie in der Lage sein, eine E-Mail von Ihrem Kontaktformular zu senden. Herzlichen Glückwunsch! -HTML-E-Mail-Vorlage .[#toc-html-email-template] ------------------------------------------------ +HTML-Template für E-Mails +------------------------- -Im Moment wird eine einfache Text-E-Mail verschickt, die nur die vom Formular gesendete Nachricht enthält. Aber wir können HTML in der E-Mail verwenden und sie attraktiver gestalten. Wir werden dafür eine Vorlage in Latte erstellen, die wir unter `app/Model/contactEmail.latte` speichern werden: +Bisher wird eine einfache Text-E-Mail gesendet, die nur die über das Formular gesendete Nachricht enthält. In der E-Mail können wir jedoch HTML verwenden und ihr Erscheinungsbild attraktiver gestalten. Wir erstellen dafür ein Template in Latte, das wir in `app/Model/contactEmail.latte` speichern: ```latte <html> - <title>Message from the contact form + Nachricht vom Kontaktformular

    Name: {$name}

    -

    E-mail: {$email}

    -

    Message: {$message}

    +

    E-Mail: {$email}

    +

    Nachricht: {$message}

    ``` -Es bleibt noch `ContactFacade` zu ändern, um diese Vorlage zu verwenden. Im Konstruktor fordern wir die Klasse `LatteFactory` an, die das Objekt `Latte\Engine` erzeugen kann, einen [Latte-Vorlagen-Renderer |latte:develop#how-to-render-a-template]. Wir verwenden die Methode `renderToString()`, um die Vorlage in eine Datei zu rendern; der erste Parameter ist der Pfad zur Vorlage und der zweite die Variablen. +Es bleibt übrig, `ContactFacade` anzupassen, damit dieses Template verwendet wird. Im Konstruktor fordern wir die Klasse `LatteFactory` an, die ein Objekt `Latte\Engine`, also den [Latte-Template-Renderer |latte:develop#Wie rendert man ein Template], erstellen kann. Mit der Methode `renderToString()` rendern wir das Template in einen String, der erste Parameter ist der Pfad zum Template und der zweite sind die Variablen. ```php namespace App\Model; @@ -158,7 +156,7 @@ class ContactFacade ]); $mail = new Message; - $mail->addTo('admin@example.com') // your email + $mail->addTo('admin@example.com') // Ihre E-Mail ->setFrom($email, $name) ->setHtmlBody($body); @@ -167,15 +165,15 @@ class ContactFacade } ``` -Anschließend übergeben wir die generierte HTML-E-Mail an die Methode `setHtmlBody()` anstelle der ursprünglichen `setBody()`. Wir müssen auch den Betreff der E-Mail in `setSubject()` nicht angeben, da die Bibliothek ihn aus dem Element `` in der Vorlage übernimmt. +Die generierte HTML-E-Mail übergeben wir dann der Methode `setHtmlBody()` anstelle der ursprünglichen `setBody()`. Ebenso müssen wir den Betreff der E-Mail nicht in `setSubject()` angeben, da ihn die Bibliothek aus dem `<title>`-Element des Templates übernimmt. -Konfigurieren von .[#toc-configuring] -------------------------------------- +Konfiguration +------------- -Im Code der Klasse `ContactFacade` ist unsere Admin-E-Mail `admin@example.com` noch fest codiert. Es wäre besser, sie in die Konfigurationsdatei zu verschieben. Wie kann man das tun? +Im Code der Klasse `ContactFacade` ist immer noch unsere Administrator-E-Mail `admin@example.com` fest codiert. Es wäre besser, sie in die Konfigurationsdatei zu verschieben. Wie geht das? -Zunächst ändern wir die Klasse `ContactFacade` und ersetzen den E-Mail-String durch eine Variable, die vom Konstruktor übergeben wird: +Zuerst passen wir die Klasse `ContactFacade` an und ersetzen den String mit der E-Mail durch eine Variable, die über den Konstruktor übergeben wird: ```php class ContactFacade @@ -199,28 +197,25 @@ class ContactFacade } ``` -Der zweite Schritt besteht darin, den Wert dieser Variable in die Konfiguration aufzunehmen. In der Datei `app/config/services.neon` fügen wir hinzu: +Und der zweite Schritt ist die Angabe des Wertes dieser Variablen in der Konfiguration. In die Datei `app/config/services.neon` schreiben wir: ```neon services: - App\Model\ContactFacade(adminEmail: admin@example.com) ``` -Und das war's. Wenn es viele Einträge im Abschnitt `services` gibt und Sie das Gefühl haben, dass die E-Mail unter den Einträgen verloren geht, können wir sie zu einer Variablen machen. Wir ändern den Eintrag in: +Und das war's. Wenn es viele Einträge im Abschnitt `services` gäbe und Sie das Gefühl hätten, dass die E-Mail dazwischen untergeht, können wir sie zu einer Variablen machen. Wir ändern die Notation auf: ```neon services: - App\Model\ContactFacade(adminEmail: %adminEmail%) ``` -Und definieren diese Variable in der Datei `app/config/common.neon`: +Und in der Datei `app/config/common.neon` definieren wir diese Variable: ```neon parameters: adminEmail: admin@example.com ``` -Und schon ist es geschafft! - - -{{sitename: Bewährte Praktiken}} +Und fertig! diff --git a/best-practices/de/microsites.texy b/best-practices/de/microsites.texy new file mode 100644 index 0000000000..71eac0ad76 --- /dev/null +++ b/best-practices/de/microsites.texy @@ -0,0 +1,63 @@ +Wie man Mikro-Websites schreibt +******************************* + +Stellen Sie sich vor, Sie müssen schnell eine kleine Website für die bevorstehende Veranstaltung Ihrer Firma erstellen. Sie soll einfach, schnell und ohne unnötige Komplikationen sein. Vielleicht denken Sie, dass Sie für ein so kleines Projekt kein robustes Framework benötigen. Aber was, wenn die Verwendung des Nette Frameworks diesen Prozess grundlegend vereinfachen und beschleunigen kann? + +Denn auch bei der Erstellung einfacher Websites möchten Sie nicht auf Komfort verzichten. Sie möchten nicht das neu erfinden, was bereits einmal gelöst wurde. Seien Sie ruhig faul und lassen Sie sich verwöhnen. Das Nette Framework kann auch hervorragend als Micro Framework genutzt werden. + +Wie kann eine solche Microsite aussehen? Zum Beispiel so, dass der gesamte Code der Website in einer einzigen Datei `index.php` im öffentlichen Ordner platziert wird: + +```php +<?php + +require __DIR__ . '/../vendor/autoload.php'; + +$configurator = new Nette\Bootstrap\Configurator; +$configurator->enableTracy(__DIR__ . '/../log'); +$configurator->setTempDirectory(__DIR__ . '/../temp'); + +// DI-Container basierend auf der Konfiguration in config.neon erstellen +$configurator->addConfig(__DIR__ . '/../app/config.neon'); +$container = $configurator->createContainer(); + +// Routing einstellen +$router = new Nette\Application\Routers\RouteList; +$container->addService('router', $router); + +// Route für URL https://example.com/ +$router->addRoute('', function ($presenter, Nette\Http\Request $httpRequest) { + // Browsersprache erkennen und auf URL /en oder /de usw. umleiten + $supportedLangs = ['en', 'de', 'cs']; + $lang = $httpRequest->detectLanguage($supportedLangs) ?: reset($supportedLangs); + $presenter->redirectUrl("/$lang"); +}); + +// Route für URL https://example.com/cs oder https://example.com/en +$router->addRoute('<lang cs|en>', function ($presenter, string $lang) { + // entsprechende Vorlage anzeigen, z.B. ../templates/en.latte + $template = $presenter->createTemplate() + ->setFile(__DIR__ . '/../templates/' . $lang . '.latte'); + return $template; +}); + +// Anwendung starten! +$container->getByType(Nette\Application\Application::class)->run(); +``` + +Alles andere sind Templates, die im übergeordneten Ordner `/templates` gespeichert sind. + +Der PHP-Code in `index.php` [bereitet zuerst die Umgebung vor |bootstrap:], definiert dann die [Routen |application:routing#Dynamisches Routing mit Callbacks] und startet schließlich die Anwendung. Der Vorteil ist, dass der zweite Parameter der Funktion `addRoute()` ein Callable sein kann, das ausgeführt wird, wenn die entsprechende Seite geöffnet wird. + + +Warum Nette für Microsites verwenden? +------------------------------------- + +- Programmierer, die jemals [Tracy|tracy:] ausprobiert haben, können sich heute nicht mehr vorstellen, ohne sie etwas zu programmieren. +- Vor allem aber werden Sie das Templating-System [Latte|latte:] nutzen, denn schon ab 2 Seiten werden Sie [Layout und Inhalt|latte:template-inheritance] trennen wollen. +- Und Sie möchten sich auf jeden Fall auf das [automatische Escaping |latte:safety-first] verlassen, damit keine XSS-Schwachstelle entsteht. +- Nette stellt auch sicher, dass bei einem Fehler niemals PHP-Fehlermeldungen für Programmierer angezeigt werden, sondern eine für den Benutzer verständliche Seite. +- Wenn Sie Feedback von Benutzern erhalten möchten, zum Beispiel in Form eines Kontaktformulars, fügen Sie noch [Formulare|forms:] und eine [Datenbank|database:] hinzu. +- Ausgefüllte Formulare können Sie sich auch einfach [per E-Mail zusenden lassen|mail:]. +- Manchmal kann Ihnen [Caching|caching:] nützlich sein, zum Beispiel wenn Sie Feeds herunterladen und anzeigen. + +In der heutigen Zeit, in der Geschwindigkeit und Effizienz entscheidend sind, ist es wichtig, Werkzeuge zu haben, die es Ihnen ermöglichen, Ergebnisse ohne unnötige Verzögerung zu erzielen. Das Nette Framework bietet Ihnen genau das - schnelle Entwicklung, Sicherheit und eine breite Palette von Werkzeugen wie Tracy und Latte, die den Prozess vereinfachen. Installieren Sie einfach ein paar Nette-Pakete und der Aufbau einer solchen Microsite ist plötzlich ein Kinderspiel. Und Sie wissen, dass sich nirgendwo eine Sicherheitslücke verbirgt. diff --git a/best-practices/de/pagination.texy b/best-practices/de/pagination.texy index 5775e52df1..17aabcacc3 100644 --- a/best-practices/de/pagination.texy +++ b/best-practices/de/pagination.texy @@ -1,17 +1,16 @@ -Paginieren von Datenbankergebnissen -*********************************** +Paginierung von Datenbankergebnissen +************************************ .[perex] -Bei der Entwicklung von Webanwendungen stößt man häufig auf die Anforderung, eine begrenzte Anzahl von Datensätzen auf einer Seite auszudrucken. +Bei der Erstellung von Webanwendungen stoßen Sie sehr oft auf die Anforderung, die Anzahl der angezeigten Elemente pro Seite zu begrenzen. -Wir kommen aus diesem Zustand heraus, wenn wir alle Daten ohne Paging auflisten. Um Daten aus der Datenbank auszuwählen, haben wir die Klasse ArticleRepository, die den Konstruktor und die Methode `findPublishedArticles` enthält, die alle veröffentlichten Artikel in absteigender Reihenfolge des Veröffentlichungsdatums zurückgibt. +Wir gehen von einem Zustand aus, in dem wir alle Daten ohne Paginierung auflisten. Für die Auswahl der Daten aus der Datenbank haben wir die Klasse `ArticleRepository`, die neben dem Konstruktor die Methode `findPublishedArticles` enthält, die alle veröffentlichten Artikel absteigend nach Veröffentlichungsdatum sortiert zurückgibt. ```php namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -31,10 +30,10 @@ class ArticleRepository } ``` -In den Presenter injizieren wir dann die Modellklasse, und in der Rendering-Methode fragen wir nach den veröffentlichten Artikeln, die wir an die Vorlage übergeben: +Im Presenter injizieren wir dann die Modellklasse und in der Render-Methode fordern wir die veröffentlichten Artikel an, die wir an das Template übergeben: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -53,11 +52,11 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -In der Vorlage kümmern wir uns um das Rendern einer Artikelliste: +In der Vorlage `default.latte` kümmern wir uns dann um die Auflistung der Artikel: ```latte {block content} -<h1>Articles</h1> +<h1>Artikel</h1> <div class="articles"> {foreach $articles as $article} @@ -68,11 +67,11 @@ In der Vorlage kümmern wir uns um das Rendern einer Artikelliste: ``` -Auf diese Weise können wir alle Artikel schreiben, aber das wird zu Problemen führen, wenn die Anzahl der Artikel wächst. Zu diesem Zeitpunkt ist es sinnvoll, einen Paging-Mechanismus zu implementieren. +Auf diese Weise können wir alle Artikel auflisten, was jedoch Probleme verursacht, sobald die Anzahl der Artikel steigt. In diesem Moment ist die Implementierung eines Paginierungsmechanismus sinnvoll. -Dadurch wird sichergestellt, dass alle Artikel auf mehrere Seiten aufgeteilt werden und wir nur die Artikel einer aktuellen Seite anzeigen. Die Gesamtzahl der Seiten und die Aufteilung der Artikel wird von [utils:Paginator] selbst berechnet, je nachdem, wie viele Artikel wir insgesamt haben und wie viele Artikel wir auf der Seite anzeigen wollen. +Dieser sorgt dafür, dass alle Artikel auf mehrere Seiten aufgeteilt werden und wir nur die Artikel der aktuellen Seite anzeigen. Die Gesamtzahl der Seiten und die Aufteilung der Artikel berechnet der [utils:Paginator] selbst, basierend darauf, wie viele Artikel wir insgesamt haben und wie viele Artikel wir pro Seite anzeigen möchten. -In einem ersten Schritt werden wir die Methode zum Abrufen von Artikeln in der Repository-Klasse so ändern, dass nur einseitige Artikel zurückgegeben werden. Außerdem fügen wir eine neue Methode hinzu, um die Gesamtzahl der Artikel in der Datenbank zu ermitteln, die wir benötigen, um einen Paginator zu setzen: +Im ersten Schritt passen wir die Methode zum Abrufen der Artikel in der Repository-Klasse so an, dass sie uns nur Artikel für eine Seite zurückgeben kann. Wir fügen auch eine Methode hinzu, um die Gesamtzahl der Artikel in der Datenbank zu ermitteln, die wir zum Einrichten des Paginators benötigen: ```php namespace App\Model; @@ -100,7 +99,7 @@ class ArticleRepository } /** - * Returns the total number of published articles + * Gibt die Gesamtzahl der veröffentlichten Artikel zurück */ public function getPublishedArticlesCount(): int { @@ -109,12 +108,12 @@ class ArticleRepository } ``` -Der nächste Schritt besteht darin, den Präsentator zu bearbeiten. Wir werden die Nummer der aktuell angezeigten Seite an die Render-Methode weiterleiten. Für den Fall, dass diese Nummer nicht Teil der URL ist, müssen wir den Standardwert auf die erste Seite setzen. +Anschließend widmen wir uns den Anpassungen des Presenters. An die Render-Methode übergeben wir die Nummer der aktuell angezeigten Seite. Für den Fall, dass diese Nummer nicht Teil der URL ist, legen wir den Standardwert auf die erste Seite fest. -Wir erweitern die Render-Methode auch, um die Paginator-Instanz zu erhalten, sie einzurichten und die richtigen Artikel für die Anzeige in der Vorlage auszuwählen. Der HomePresenter wird wie folgt aussehen: +Weiterhin erweitern wir die Render-Methode um das Abrufen der Paginator-Instanz, deren Einrichtung und die Auswahl der richtigen Artikel zur Anzeige im Template. Der `HomePresenter` sieht nach den Anpassungen wie folgt aus: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -131,28 +130,28 @@ class HomePresenter extends Nette\Application\UI\Presenter // Wir ermitteln die Gesamtzahl der veröffentlichten Artikel $articlesCount = $this->articleRepository->getPublishedArticlesCount(); - // Wir erstellen die Paginator-Instanz und richten sie ein + // Wir erstellen eine Instanz des Paginators und richten sie ein $paginator = new Nette\Utils\Paginator; - $paginator->setItemCount($articlesCount); // Gesamtanzahl der Artikel - $paginator->setItemsPerPage(10); // Artikel pro Seite - $paginator->setPage($page); // aktuelle Seitenzahl + $paginator->setItemCount($articlesCount); // Gesamtzahl der Artikel + $paginator->setItemsPerPage(10); // Anzahl der Elemente pro Seite + $paginator->setPage($page); // Nummer der aktuellen Seite - // Basierend auf den Berechnungen von Paginator finden wir eine begrenzte Anzahl von Artikeln aus der Datenbank + // Aus der Datenbank ziehen wir eine begrenzte Menge von Artikeln gemäß der Berechnung des Paginators $articles = $this->articleRepository->findPublishedArticles($paginator->getLength(), $paginator->getOffset()); // die wir an die Vorlage übergeben $this->template->articles = $articles; - // und auch Paginator selbst, um die Optionen für das Paging anzuzeigen + // und auch den Paginator selbst zur Anzeige der Paginierungsoptionen $this->template->paginator = $paginator; } } ``` -Die Vorlage iteriert bereits über Artikel auf einer Seite, fügen Sie einfach Links zum Blättern hinzu: +Das Template iteriert nun bereits nur über die Artikel einer Seite, wir müssen lediglich die Paginierungslinks hinzufügen: ```latte {block content} -<h1>Articles</h1> +<h1>Artikel</h1> <div class="articles"> {foreach $articles as $article} @@ -163,34 +162,33 @@ Die Vorlage iteriert bereits über Artikel auf einer Seite, fügen Sie einfach L <div class="pagination"> {if !$paginator->isFirst()} - <a n:href="default, 1">First</a> + <a n:href="default, 1">Erste</a>  |  - <a n:href="default, $paginator->page-1">Previous</a> + <a n:href="default, $paginator->page-1">Vorherige</a>  |  {/if} - Page {$paginator->getPage()} of {$paginator->getPageCount()} + Seite {$paginator->getPage()} von {$paginator->getPageCount()} {if !$paginator->isLast()}  |  - <a n:href="default, $paginator->getPage() + 1">Next</a> + <a n:href="default, $paginator->getPage() + 1">Nächste</a>  |  - <a n:href="default, $paginator->getPageCount()">Last</a> + <a n:href="default, $paginator->getPageCount()">Letzte</a> {/if} </div> ``` -So haben wir die Paginierung mit Paginator hinzugefügt. Wenn [Nette Database Explorer |database:explorer] anstelle von [Nette Database Core |database:core] als Datenbankschicht verwendet wird, können wir die Paginierung auch ohne Paginator implementieren. Die Klasse `Nette\Database\Table\Selection` enthält die [Page-Methode |api:Nette\Database\Table\Selection::_ page] mit der Paginierungslogik aus dem Paginator. +So haben wir die Seite um die Möglichkeit der Paginierung mit dem Paginator ergänzt. Falls wir anstelle von [Nette Database Core |database:sql-way] als Datenbankschicht [Nette Database Explorer |database:explorer] verwenden, können wir die Paginierung auch ohne Paginator implementieren. Die Klasse `Nette\Database\Table\Selection` enthält nämlich die Methode [page() |api:Nette\Database\Table\Selection::page()] mit der vom Paginator übernommenen Paginierungslogik. -Das Repository sieht dann wie folgt aus: +Das Repository sieht bei dieser Implementierungsmethode wie folgt aus: ```php namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -198,7 +196,6 @@ class ArticleRepository ) { } - public function findPublishedArticles(): Nette\Database\Table\Selection { return $this->database->table('articles') @@ -208,10 +205,10 @@ class ArticleRepository } ``` -Wir müssen keinen Paginator im Presenter erstellen, sondern verwenden die Methode des `Selection` -Objekts, das vom Repository zurückgegeben wird: +Im Presenter müssen wir keinen Paginator erstellen, wir verwenden stattdessen die Methode der `Selection`-Klasse, die uns das Repository zurückgibt: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -225,25 +222,25 @@ class HomePresenter extends Nette\Application\UI\Presenter public function renderDefault(int $page = 1): void { - // Wir finden die veröffentlichten Artikel + // Wir ziehen die veröffentlichten Artikel heraus $articles = $this->articleRepository->findPublishedArticles(); - // und deren Teil, der durch die Berechnung der Seitenmethode begrenzt wird, übergeben wir an die Vorlage + // und senden nur einen Teil davon an die Vorlage, begrenzt durch die Berechnung der page-Methode $lastPage = 0; $this->template->articles = $articles->page($page, 10, $lastPage); - // sowie die erforderlichen Daten für die Anzeige der Seitenoptionen + // und auch die notwendigen Daten zur Anzeige der Paginierungsoptionen $this->template->page = $page; $this->template->lastPage = $lastPage; } } ``` -Da wir keinen Paginator verwenden, müssen wir den Abschnitt bearbeiten, der die Seitenverknüpfungen anzeigt: +Da wir nun keinen Paginator an das Template senden, passen wir den Teil an, der die Paginierungslinks anzeigt: ```latte {block content} -<h1>Articles</h1> +<h1>Artikel</h1> <div class="articles"> {foreach $articles as $article} @@ -254,24 +251,23 @@ Da wir keinen Paginator verwenden, müssen wir den Abschnitt bearbeiten, der die <div class="pagination"> {if $page > 1} - <a n:href="default, 1">First</a> + <a n:href="default, 1">Erste</a>  |  - <a n:href="default, $page - 1">Previous</a> + <a n:href="default, $page - 1">Vorherige</a>  |  {/if} - Page {$page} of {$lastPage} + Seite {$page} von {$lastPage} {if $page < $lastPage}  |  - <a n:href="default, $page + 1">Next</a> + <a n:href="default, $page + 1">Nächste</a>  |  - <a n:href="default, $lastPage">Last</a> + <a n:href="default, $lastPage">Letzte</a> {/if} </div> ``` -Auf diese Weise haben wir einen Paging-Mechanismus implementiert, ohne einen Paginator zu verwenden. +Auf diese Weise haben wir den Paginierungsmechanismus ohne Verwendung des Paginators implementiert. {{priority: -1}} -{{sitename: Bewährte Praktiken}} diff --git a/best-practices/de/passing-settings-to-presenters.texy b/best-practices/de/passing-settings-to-presenters.texy index cfab5e6941..d84c9fb780 100644 --- a/best-practices/de/passing-settings-to-presenters.texy +++ b/best-practices/de/passing-settings-to-presenters.texy @@ -1,10 +1,10 @@ -Übergabe von Einstellungen an Moderatoren -***************************************** +Einstellungen an Presenter übergeben +************************************ .[perex] -Müssen Sie Argumente an Presenter übergeben, bei denen es sich nicht um Objekte handelt (z. B. Informationen darüber, ob der Presenter im Debug-Modus läuft, Verzeichnispfade usw.) und die daher nicht automatisch durch Autowiring übergeben werden können? Die Lösung ist, sie in einem `Settings` Objekt zu kapseln. +Müssen Sie Argumente an Presenter übergeben, die keine Objekte sind (z. B. Information, ob im Debug-Modus ausgeführt wird, Pfade zu Verzeichnissen usw.) und daher nicht automatisch über Autowiring übergeben werden können? Die Lösung besteht darin, sie in ein `Settings`-Objekt zu kapseln. -Der Dienst `Settings` ist eine sehr einfache, aber nützliche Möglichkeit, Präsentatoren Informationen über eine laufende Anwendung zur Verfügung zu stellen. Die konkrete Ausgestaltung hängt ganz von Ihren speziellen Bedürfnissen ab. Beispiel: +Der `Settings`-Dienst stellt eine sehr einfache und dennoch nützliche Methode dar, um Informationen über die laufende Anwendung an Presenter bereitzustellen. Seine konkrete Form hängt ganz von Ihren spezifischen Bedürfnissen ab. Beispiel: ```php namespace App; @@ -12,7 +12,7 @@ namespace App; class Settings { public function __construct( - // seit PHP 8.1 ist es möglich, readonly anzugeben + // ab PHP 8.1 kann readonly angegeben werden public bool $debugMode, public string $appDir, // und so weiter @@ -20,7 +20,7 @@ class Settings } ``` -Beispiel für die Anmeldung zur Konfiguration: +Beispiel für die Registrierung in der Konfiguration: ```neon services: @@ -30,7 +30,7 @@ services: ) ``` -Wenn der Präsentator die von diesem Dienst bereitgestellten Informationen benötigt, fragt er sie einfach im Konstruktor ab: +Wenn der Presenter die von diesem Dienst bereitgestellten Informationen benötigt, fordert er sie einfach im Konstruktor an: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -47,5 +47,3 @@ class MyPresenter extends Nette\Application\UI\Presenter } } ``` - -{{sitename: Bewährte Praktiken}} diff --git a/best-practices/de/post-links.texy b/best-practices/de/post-links.texy new file mode 100644 index 0000000000..f4f90e1f24 --- /dev/null +++ b/best-practices/de/post-links.texy @@ -0,0 +1,56 @@ +Wie man POST-Links richtig verwendet +************************************ + +.[perex] +In Webanwendungen, insbesondere in administrativen Oberflächen, sollte es eine Grundregel sein, dass Aktionen, die den Serverzustand ändern, nicht über die HTTP-Methode GET durchgeführt werden sollten. Wie der Name der Methode andeutet, sollte GET nur zum Abrufen von Daten verwendet werden, nicht zu deren Änderung. Für Aktionen wie das Löschen von Datensätzen ist es besser, die POST-Methode zu verwenden. Obwohl die DELETE-Methode ideal wäre, kann sie ohne JavaScript nicht aufgerufen werden, daher wird historisch POST verwendet. + +Wie geht das in der Praxis? Nutzen Sie diesen einfachen Trick. Am Anfang des Templates erstellen Sie ein Hilfsformular mit dem Bezeichner `postForm`, das Sie anschließend für die Löschbuttons verwenden: + +```latte .{file:@layout.latte} +<form method="post" id="postForm"></form> +``` + +Dank dieses Formulars können Sie anstelle eines klassischen Links `<a>` einen Button `<button>` verwenden, der visuell so angepasst werden kann, dass er wie ein normaler Link aussieht. Beispielsweise bietet das CSS-Framework Bootstrap die Klassen `btn btn-link`, mit denen Sie erreichen, dass der Button visuell nicht von anderen Links zu unterscheiden ist. Mit dem Attribut `form="postForm"` verknüpfen wir ihn mit dem vorbereiteten Formular: + +```latte .{file:admin.latte} +<table> + <tr n:foreach="$posts as $post"> + <td>{$post->title}</td> + <td> + <button class="btn btn-link" form="postForm" formaction="{link delete $post->id}">löschen</button> + <!-- anstelle von <a n:href="delete $post->id">löschen</a> --> + </td> + </tr> +</table> +``` + +Beim Klicken auf den Link wird nun die Aktion `delete` aufgerufen. Um sicherzustellen, dass Anfragen nur über die POST-Methode und von derselben Domain akzeptiert werden (was eine wirksame Verteidigung gegen CSRF-Angriffe ist), verwenden Sie das Attribut `#[Requires]`: + +```php .{file:AdminPresenter.php} +use Nette\Application\Attributes\Requires; + +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST', sameOrigin: true)] + public function actionDelete(int $id): void + { + $this->facade->deletePost($id); // hypothetischer Code zum Löschen des Datensatzes + $this->redirect('default'); + } +} +``` + +Das Attribut existiert seit Nette Application 3.2 und mehr über seine Möglichkeiten erfahren Sie auf der Seite [Wie man das Attribut #Requires verwendet |attribute-requires]. + +Wenn Sie anstelle der Aktion `actionDelete()` das Signal `handleDelete()` verwenden würden, ist es nicht notwendig, `sameOrigin: true` anzugeben, da Signale diesen Schutz implizit eingestellt haben: + +```php .{file:AdminPresenter.php} +#[Requires(methods: 'POST')] +public function handleDelete(int $id): void +{ + $this->facade->deletePost($id); + $this->redirect('this'); +} +``` + +Dieser Ansatz verbessert nicht nur die Sicherheit Ihrer Anwendung, sondern trägt auch zur Einhaltung korrekter Webstandards und Praktiken bei. Durch die Verwendung von POST-Methoden für zustandsändernde Aktionen erreichen Sie eine robustere und sicherere Anwendung. diff --git a/best-practices/de/presenter-traits.texy b/best-practices/de/presenter-traits.texy index 41b4d98037..e6cf4290dc 100644 --- a/best-practices/de/presenter-traits.texy +++ b/best-practices/de/presenter-traits.texy @@ -1,14 +1,14 @@ -Zusammenstellung von Moderatoren aus Merkmalen -********************************************** +Presenter aus Traits zusammensetzen +*********************************** .[perex] -Wenn wir denselben Code in mehreren Presentern implementieren müssen (z. B. die Überprüfung, ob der Benutzer angemeldet ist), ist es verlockend, den Code in einem gemeinsamen Vorgänger zu platzieren. Die zweite Möglichkeit ist die Erstellung von Single-Purpose-Traits. +Wenn wir in mehreren Presentern denselben Code implementieren müssen (z. B. die Überprüfung, ob der Benutzer angemeldet ist), bietet es sich an, den Code in einem gemeinsamen Vorfahren zu platzieren. Die zweite Möglichkeit ist die Erstellung von zweckgebundenen [Traits |nette:introduction-to-object-oriented-programming#Traits]. -Der Vorteil dieser Lösung ist, dass jeder Präsentator nur die Traits verwenden kann, die er tatsächlich benötigt, während Mehrfachvererbung in PHP nicht möglich ist. +Der Vorteil dieser Lösung ist, dass jeder der Presenter genau die Traits verwenden kann, die er tatsächlich benötigt, während Mehrfachvererbung in PHP nicht möglich ist. -Diese Traits können sich die Tatsache zunutze machen, dass alle [Inject-Methoden |inject-method-attribute#inject methods] bei der Erstellung des Presenters nacheinander aufgerufen werden. Sie müssen nur darauf achten, dass der Name jeder Inject-Methode eindeutig ist. +Diese Traits können die Tatsache nutzen, dass bei der Erstellung des Presenters nacheinander alle [inject-Methoden |inject-method-attribute#inject -Methoden] aufgerufen werden. Es muss nur sichergestellt werden, dass der Name jeder inject-Methode eindeutig ist. -Traits können den Initialisierungscode in [onStartup- oder onRender-Ereignisse |application:presenters#Events] einhängen. +Traits können Initialisierungscode an die Ereignisse [onStartup oder onRender |application:presenters#Ereignisse] anhängen. Beispiele: @@ -36,7 +36,7 @@ trait StandardTemplateFilters } ``` -Der Vortragende nutzt dann einfach diese Eigenschaften: +Der Presenter verwendet diese Traits dann einfach: ```php class ArticlePresenter extends Nette\Application\UI\Presenter @@ -45,6 +45,3 @@ class ArticlePresenter extends Nette\Application\UI\Presenter use RequireLoggedUser; } ``` - - -{{sitename: Bewährte Praktiken}} diff --git a/best-practices/de/restore-request.texy b/best-practices/de/restore-request.texy index 16471c65d1..957f7c6605 100644 --- a/best-practices/de/restore-request.texy +++ b/best-practices/de/restore-request.texy @@ -1,17 +1,16 @@ -Wie kann man zu einer früheren Seite zurückkehren? -************************************************** +Wie man zu einer früheren Seite zurückkehrt? +******************************************** .[perex] -Was passiert, wenn ein Benutzer ein Formular ausfüllt und seine Anmeldung abläuft? Um zu vermeiden, dass die Daten verloren gehen, speichern wir die Daten in der Session, bevor wir zur Anmeldeseite weiterleiten. In Nette ist das ein Kinderspiel. +Was, wenn ein Benutzer ein Formular ausfüllt und seine Anmeldung abläuft? Damit er die Daten nicht verliert, speichern wir sie vor der Weiterleitung zur Anmeldeseite in der Session. In Nette ist das ein Kinderspiel. -Die aktuelle Anfrage kann mit der Methode `storeRequest()` in der Session gespeichert werden, die ihre Kennung als kurze Zeichenkette zurückgibt. Die Methode speichert den Namen des aktuellen Präsentators, die Ansicht und ihre Parameter. -Wurde auch ein Formular abgeschickt, werden die Werte der Felder (mit Ausnahme der hochgeladenen Dateien) ebenfalls gespeichert. +Die aktuelle Anfrage kann mit der Methode `storeRequest()` in der Session gespeichert werden, die ihren Bezeichner in Form einer kurzen Zeichenkette zurückgibt. Die Methode speichert den Namen des aktuellen Presenters, die View und ihre Parameter. Falls auch ein Formular gesendet wurde, wird auch der Inhalt der Felder gespeichert (mit Ausnahme von hochgeladenen Dateien). -Die Anfrage wird durch die Methode `restoreRequest($key)` wiederhergestellt, an die wir den abgerufenen Bezeichner übergeben. Diese leitet zum ursprünglichen Präsentator und zur ursprünglichen Ansicht zurück. Enthält die gespeicherte Anfrage jedoch eine Formularübermittlung, wird sie mit der Methode `forward()` an den ursprünglichen Präsentator weitergeleitet, wobei die zuvor ausgefüllten Werte an das Formular übergeben werden und dieses neu gezeichnet werden kann. So kann der Benutzer das Formular erneut abschicken, ohne dass Daten verloren gehen. +Die Wiederherstellung der Anfrage erfolgt durch die Methode `restoreRequest($key)`, der wir den erhaltenen Bezeichner übergeben. Diese leitet zum ursprünglichen Presenter und zur View weiter. Wenn die gespeicherte Anfrage jedoch das Senden eines Formulars enthält, wechselt sie zum ursprünglichen Presenter mit der Methode `forward()`, übergibt dem Formular die zuvor ausgefüllten Werte und lässt es erneut rendern. Der Benutzer hat so die Möglichkeit, das Formular erneut zu senden, und es gehen keine Daten verloren. -Wichtig ist, dass `restoreRequest()` überprüft, ob der neu angemeldete Benutzer derselbe ist, der das Formular ursprünglich ausgefüllt hat. Wenn nicht, wird die Anfrage verworfen und nichts unternommen. +Wichtig ist, dass `restoreRequest()` überprüft, ob der neu angemeldete Benutzer derselbe ist, der das Formular ursprünglich ausgefüllt hat. Wenn nicht, verwirft sie die Anfrage und tut nichts. -Lassen Sie uns das Ganze anhand eines Beispiels demonstrieren. Nehmen wir einen Präsentator `AdminPresenter`, in dem Daten bearbeitet werden und dessen Methode `startup()` prüft, ob der Benutzer angemeldet ist. Ist dies nicht der Fall, leiten wir ihn zu `SignPresenter` um. Gleichzeitig speichern wir die aktuelle Anfrage und senden ihren Schlüssel an `SignPresenter`. +Zeigen wir alles an einem Beispiel. Wir haben einen Presenter `AdminPresenter`, in dem Daten bearbeitet werden und in dessen Methode `startup()` wir überprüfen, ob der Benutzer angemeldet ist. Wenn nicht, leiten wir ihn zum `SignPresenter` weiter. Gleichzeitig speichern wir die aktuelle Anfrage und senden ihren Schlüssel an den `SignPresenter`. ```php class AdminPresenter extends Nette\Application\UI\Presenter @@ -27,7 +26,7 @@ class AdminPresenter extends Nette\Application\UI\Presenter } ``` -Der Präsentator `SignPresenter` enthält einen persistenten Parameter `$backlink`, in den der Schlüssel zusätzlich zum Anmeldeformular geschrieben wird. Da der Parameter persistent ist, wird er auch nach dem Absenden des Anmeldeformulars beibehalten. +Der Presenter `SignPresenter` enthält neben dem Anmeldeformular auch einen persistenten Parameter `$backlink`, in den der Schlüssel geschrieben wird. Da der Parameter persistent ist, wird er auch nach dem Absenden des Anmeldeformulars übertragen. ```php @@ -48,7 +47,7 @@ class SignPresenter extends Nette\Application\UI\Presenter public function signInFormSubmitted($form) { - // ... hier melden wir den Benutzer an ... + // ... hier den Benutzer anmelden ... $this->restoreRequest($this->backlink); $this->redirect('Admin:'); @@ -56,9 +55,8 @@ class SignPresenter extends Nette\Application\UI\Presenter } ``` -Wir übergeben den Schlüssel der gespeicherten Anfrage an die Methode `restoreRequest()` und diese leitet zum ursprünglichen Präsentator um (oder weiter). +Wir übergeben der Methode `restoreRequest()` den Schlüssel der gespeicherten Anfrage, und sie leitet (oder wechselt per `forward()`) zum ursprünglichen Presenter weiter. -Ist der Schlüssel jedoch ungültig (z. B. weil er in der Session nicht mehr existiert), unternimmt die Methode nichts. Der nächste Aufruf ist dann `$this->redirect('Admin:')`, der an `AdminPresenter` weiterleitet. +Wenn der Schlüssel jedoch ungültig ist (z. B. nicht mehr in der Session existiert oder der Benutzer nicht übereinstimmt), tut die Methode nichts. Es folgt also der Aufruf `$this->redirect('Admin:')`, der zum `AdminPresenter` (oder einer anderen Standardseite nach dem Login) weiterleitet. {{priority: -1}} -{{sitename: Bewährte Praktiken}} diff --git a/best-practices/el/@home.texy b/best-practices/el/@home.texy index ba364faa14..0e8088a224 100644 --- a/best-practices/el/@home.texy +++ b/best-practices/el/@home.texy @@ -1,57 +1,61 @@ -Βέλτιστες πρακτικές -******************* +Οδηγοί και διαδικασίες +********************** .[perex] -Σεμινάρια, λύσεις σε συνήθη προβλήματα και βέλτιστες πρακτικές για το Nette. +Οδηγοί, λύσεις για συχνές εργασίες και *βέλτιστες πρακτικές* για το Nette. <div class=documentation> <div> -Εφαρμογή Nette --------------- -- [Ένθεση μεθόδων και χαρακτηριστικών |inject-method-attribute] -- [Σύνθεση παρουσιαστών από γνωρίσματα |presenter-traits] -- [Πέρασμα ρυθμίσεων σε παρουσιαστές |passing-settings-to-presenters] -- [Πώς να επιστρέψετε σε μια προηγούμενη σελίδα|restore-request] -- [Σελιδοποίηση των αποτελεσμάτων της βάσης δεδομένων |Pagination] -- [Δυναμικά αποσπάσματα |dynamic-snippets] +Εφαρμογές Nette +--------------- +- [Μέθοδοι και χαρακτηριστικά inject |inject-method-attribute] +- [Σύνθεση presenters από traits |presenter-traits] +- [Πέρασμα ρυθμίσεων σε presenters |passing-settings-to-presenters] +- [Πώς να επιστρέψετε σε προηγούμενη σελίδα |restore-request] +- [Σελίδωση αποτελεσμάτων βάσης δεδομένων |pagination] +- [Δυναμικά snippets |dynamic-snippets] +- [Πώς να χρησιμοποιήσετε το attribute #Requires |attribute-requires] +- [Πώς να χρησιμοποιήσετε σωστά τους συνδέσμους POST |post-links] </div> <div> -Έντυπα +Φόρμες ------ -- [Επαναχρησιμοποίηση εντύπων |form-reuse] -- [Φόρμα για τη δημιουργία και επεξεργασία εγγραφής |creating-editing-form] -- [Ας δημιουργήσουμε μια φόρμα επικοινωνίας |lets-create-contact-form] -- [Εξαρτώμενα πλαίσια επιλογής |https://blog.nette.org/el/exartomena-selectboxes-kompsa-se-nette-kai-katharo-js] +- [Επαναχρησιμοποίηση φορμών |form-reuse] +- [Φόρμα για δημιουργία και επεξεργασία εγγραφής |creating-editing-form] +- [Δημιουργούμε φόρμα επικοινωνίας |lets-create-contact-form] +- [Εξαρτώμενα selectboxes |https://blog.nette.org/el/dependent-selectboxes-elegantly-in-nette-and-pure-js] </div> <div> -Κοινό ------ -- [Πώς να φορτώσετε το αρχείο ρυθμίσεων |bootstrap:] -- [Γιατί η Nette χρησιμοποιεί τη σημειογραφία σταθερών PascalCase; |https://blog.nette.org/el/gia-ligoteres-krauges-ston-kodika] -- [Γιατί η Nette δεν χρησιμοποιεί την κατάληξη Interface; |https://blog.nette.org/el/ta-prothemata-kai-ta-epithemata-den-anekoun-sta-onomata-diasyndeses] -- [Συμβουλές χρήσης του Composer |composer] -- [Συμβουλές για επεξεργαστές & εργαλεία |editors-and-tools] +Γενικά +------ +- [Πώς να φορτώσετε ένα αρχείο διαμόρφωσης |bootstrap:] +- [Πώς να γράψετε micro-sites |microsites] +- [Γιατί το Nette χρησιμοποιεί τη σημειογραφία PascalCase για σταθερές; |https://blog.nette.org/el/for-less-screaming-in-the-code] +- [Γιατί το Nette δεν χρησιμοποιεί το επίθημα Interface; |https://blog.nette.org/el/prefixes-and-suffixes-do-not-belong-in-interface-names] +- [Composer: συμβουλές χρήσης |composer] +- [Συμβουλές για editors & εργαλεία |editors-and-tools] +- [Εισαγωγή στον αντικειμενοστρεφή προγραμματισμό |nette:introduction-to-object-oriented-programming] </div> <div> -Δείγμα λύσης ------------- +Δείγματα λύσεων +--------------- - [Παραδείγματα Nette |https://github.com/nette-examples] -- [Δόγμα & Nette |https://contributte.org/nettrine/] +- [Doctrine & Nette |https://contributte.org/nettrine/] - [Παραδείγματα Contributte |https://contributte.org/examples.html] -- [Δικτυακός τόπος Doctrine ORM |https://github.com/MinecordNetwork/Website] -- [Γρήγορη εκκίνηση |quickstart:] +- [Doctrine ORM Website |https://github.com/MinecordNetwork/Website] +- [Quick start |quickstart:] </div> <div> @@ -59,10 +63,7 @@ Βίντεο ------ -Μπορείτε να βρείτε εκατοντάδες ηχογραφήσεις από την Posobota και βίντεο σχετικά με τη Nette κάτω από μία στέγη στο "Nette Framework YouTube Channel":https://www.youtube.com/user/NetteFramework. +Εκατοντάδες ηχογραφήσεις από τα Poslední soboty και βίντεο για το Nette μπορείτε να βρείτε κάτω από μία στέγη στο "κανάλι YouTube του Nette Framework":https://www.youtube.com/user/NetteFramework. </div> </div> - -{{sitename: Best Practices}} -{{leftbar: www:@menu-common}} diff --git a/best-practices/el/@meta.texy b/best-practices/el/@meta.texy new file mode 100644 index 0000000000..9ae15ea14a --- /dev/null +++ b/best-practices/el/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Οδηγοί και διαδικασίες}} +{{leftbar: www:@menu-common}} diff --git a/best-practices/el/attribute-requires.texy b/best-practices/el/attribute-requires.texy new file mode 100644 index 0000000000..6a3211185c --- /dev/null +++ b/best-practices/el/attribute-requires.texy @@ -0,0 +1,177 @@ +Πώς να χρησιμοποιήσετε το attribute `#[Requires]` +************************************************* + +.[perex] +Όταν γράφετε μια διαδικτυακή εφαρμογή, συχνά αντιμετωπίζετε την ανάγκη να περιορίσετε την πρόσβαση σε ορισμένα τμήματα της εφαρμογής σας. Ίσως θέλετε ορισμένα αιτήματα να μπορούν να στέλνουν δεδομένα μόνο μέσω φόρμας (δηλαδή με τη μέθοδο POST), ή να είναι προσβάσιμα μόνο για κλήσεις AJAX. Στο Nette Framework 3.2, εμφανίστηκε ένα νέο εργαλείο που σας επιτρέπει να ορίσετε τέτοιους περιορισμούς με πολύ κομψό και σαφή τρόπο: το attribute `#[Requires]`. + +Το attribute είναι μια ειδική ετικέτα στην PHP, την οποία προσθέτετε πριν από τον ορισμό μιας κλάσης ή μεθόδου. Επειδή είναι στην πραγματικότητα μια κλάση, για να λειτουργήσουν τα παρακάτω παραδείγματα, είναι απαραίτητο να συμπεριλάβετε τη δήλωση use: + +```php +use Nette\Application\Attributes\Requires; +``` + +Μπορείτε να χρησιμοποιήσετε το attribute `#[Requires]` στην ίδια την κλάση του presenter και επίσης σε αυτές τις μεθόδους: + +- `action<Action>()` +- `render<View>()` +- `handle<Signal>()` +- `createComponent<Name>()` + +Οι δύο τελευταίες μέθοδοι αφορούν επίσης τα components, οπότε μπορείτε να χρησιμοποιήσετε το attribute και σε αυτά. + +Αν δεν πληρούνται οι προϋποθέσεις που αναφέρει το attribute, προκαλείται σφάλμα HTTP 4xx. + + +Μέθοδοι HTTP +------------ + +Μπορείτε να καθορίσετε ποιες μέθοδοι HTTP (όπως GET, POST κ.λπ.) επιτρέπονται για πρόσβαση. Για παράδειγμα, αν θέλετε να επιτρέψετε την πρόσβαση μόνο με την υποβολή φόρμας, ορίζετε: + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +Γιατί πρέπει να χρησιμοποιείτε POST αντί για GET για ενέργειες που αλλάζουν την κατάσταση και πώς να το κάνετε; [Διαβάστε τον οδηγό |post-links]. + +Μπορείτε να καθορίσετε μια μέθοδο ή έναν πίνακα μεθόδων. Μια ειδική περίπτωση είναι η τιμή `'*'`, η οποία επιτρέπει όλες τις μεθόδους, κάτι που οι presenters κανονικά [δεν επιτρέπουν για λόγους ασφαλείας |application:presenters#Έλεγχος μεθόδου HTTP]. + + +Κλήσεις AJAX +------------ + +Αν θέλετε ο presenter ή η μέθοδος να είναι διαθέσιμη μόνο για αιτήσεις AJAX, χρησιμοποιήστε: + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Ίδια προέλευση +-------------- + +Για να αυξήσετε την ασφάλεια, μπορείτε να απαιτήσετε η αίτηση να γίνεται από τον ίδιο τομέα. Αυτό αποτρέπει την [ευπάθεια CSRF |nette:vulnerability-protection#Cross-Site Request Forgery CSRF]: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Για τις μεθόδους `handle<Signal>()`, η πρόσβαση από τον ίδιο τομέα απαιτείται αυτόματα. Έτσι, αν αντίθετα θέλετε να επιτρέψετε την πρόσβαση από οποιονδήποτε τομέα, καθορίστε: + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Πρόσβαση μέσω forward +--------------------- + +Μερικές φορές είναι χρήσιμο να περιορίσετε την πρόσβαση στον presenter έτσι ώστε να είναι διαθέσιμος μόνο έμμεσα, για παράδειγμα, χρησιμοποιώντας τη μέθοδο `forward()` ή `switch()` από άλλο presenter. Έτσι προστατεύονται, για παράδειγμα, οι error-presenters, ώστε να μην είναι δυνατό να κληθούν από το URL: + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Στην πράξη, συχνά είναι απαραίτητο να επισημάνετε ορισμένα views, στα οποία μπορείτε να φτάσετε μόνο βάσει της λογικής στον presenter. Δηλαδή, ξανά, ώστε να μην είναι δυνατό να ανοίξουν απευθείας: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = $this->facade->getProduct($id); + if (!$product) { + $this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Συγκεκριμένες ενέργειες +----------------------- + +Μπορείτε επίσης να περιορίσετε ότι ένας συγκεκριμένος κώδικας, όπως η δημιουργία ενός component, θα είναι διαθέσιμος μόνο για συγκεκριμένες actions στον presenter: + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +Σε περίπτωση μίας action, δεν χρειάζεται να γράψετε πίνακα: `#[Requires(actions: 'default')]` + + +Προσαρμοσμένα attributes +------------------------ + +Αν θέλετε να χρησιμοποιήσετε το attribute `#[Requires]` επανειλημμένα με τις ίδιες ρυθμίσεις, μπορείτε να δημιουργήσετε το δικό σας attribute που θα κληρονομεί το `#[Requires]` και θα το ρυθμίζει ανάλογα με τις ανάγκες. + +Για παράδειγμα, το `#[SingleAction]` θα επιτρέπει την πρόσβαση μόνο μέσω της action `default`: + +```php +#[\Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Ή το `#[RestMethods]` θα επιτρέπει την πρόσβαση μέσω όλων των μεθόδων HTTP που χρησιμοποιούνται για το REST API: + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Συμπέρασμα +---------- + +Το attribute `#[Requires]` σας δίνει μεγάλη ευελιξία και έλεγχο στο πώς είναι προσβάσιμες οι ιστοσελίδες σας. Χρησιμοποιώντας απλούς αλλά ισχυρούς κανόνες, μπορείτε να αυξήσετε την ασφάλεια και τη σωστή λειτουργία της εφαρμογής σας. Όπως βλέπετε, η χρήση attributes στο Nette μπορεί όχι μόνο να διευκολύνει τη δουλειά σας, αλλά και να την ασφαλίσει. diff --git a/best-practices/el/composer.texy b/best-practices/el/composer.texy index 0971df08e3..9e0a6a7715 100644 --- a/best-practices/el/composer.texy +++ b/best-practices/el/composer.texy @@ -1,44 +1,44 @@ -Συμβουλές χρήσης του Composer +Composer: συμβουλές για χρήση ***************************** <div class=perex> -Το Composer είναι ένα εργαλείο για τη διαχείριση εξαρτήσεων στην PHP. Σας επιτρέπει να δηλώσετε τις βιβλιοθήκες από τις οποίες εξαρτάται το έργο σας και θα τις εγκαταστήσει και θα τις ενημερώσει για εσάς. Θα μάθουμε: +Ο Composer είναι ένα εργαλείο για τη διαχείριση εξαρτήσεων στην PHP. Μας επιτρέπει να απαριθμήσουμε τις βιβλιοθήκες από τις οποίες εξαρτάται το έργο μας και θα τις εγκαθιστά και θα τις ενημερώνει για εμάς. Θα δείξουμε: -- πώς να εγκαταστήσετε το Composer -- να το χρησιμοποιείτε σε νέο ή υπάρχον έργο +- πώς να εγκαταστήσετε τον Composer +- τη χρήση του σε ένα νέο ή υπάρχον έργο </div> -Εγκατάσταση .[#toc-installation] -================================ +Εγκατάσταση +=========== -Το Composer είναι ένα εκτελέσιμο αρχείο `.phar` που μπορείτε να κατεβάσετε και να εγκαταστήσετε ως εξής. +Ο Composer είναι ένα εκτελέσιμο αρχείο `.phar`, το οποίο κατεβάζετε και εγκαθιστάτε ως εξής: -Windows .[#toc-windows] ------------------------ +Windows +------- -Χρησιμοποιήστε το επίσημο πρόγραμμα εγκατάστασης [Composer-Setup.exe |https://getcomposer.org/Composer-Setup.exe]. +Χρησιμοποιήστε τον επίσημο εγκαταστάτη [Composer-Setup.exe |https://getcomposer.org/Composer-Setup.exe]. -Linux, macOS .[#toc-linux-macos] --------------------------------- +Linux, macOS +------------ -Το μόνο που χρειάζεστε είναι 4 εντολές, τις οποίες μπορείτε να αντιγράψετε από [αυτή τη σελίδα |https://getcomposer.org/download/]. +Αρκούν 4 εντολές, τις οποίες αντιγράφετε από [αυτή τη σελίδα |https://getcomposer.org/download/]. -Επιπλέον, αντιγράφοντας σε φάκελο που βρίσκεται στο `PATH` του συστήματος, το Composer γίνεται παγκοσμίως προσβάσιμο: +Στη συνέχεια, τοποθετώντας τον στον φάκελο που βρίσκεται στο σύστημα `PATH`, ο Composer γίνεται προσβάσιμος καθολικά: ```shell $ mv ./composer.phar ~/bin/composer # or /usr/local/bin/composer ``` -Χρήση στο Project .[#toc-use-in-project] -======================================== +Χρήση στο έργο +============== -Για να αρχίσετε να χρησιμοποιείτε το Composer στο έργο σας, το μόνο που χρειάζεστε είναι ένα αρχείο `composer.json`. Αυτό το αρχείο περιγράφει τις εξαρτήσεις του έργου σας και μπορεί να περιέχει και άλλα μεταδεδομένα. Το απλούστερο `composer.json` μπορεί να μοιάζει ως εξής: +Για να αρχίσουμε να χρησιμοποιούμε τον Composer στο έργο μας, χρειαζόμαστε μόνο το αρχείο `composer.json`. Αυτό περιγράφει τις εξαρτήσεις του έργου μας και μπορεί επίσης να περιέχει άλλα μεταδεδομένα. Ένα βασικό `composer.json` μπορεί λοιπόν να μοιάζει ως εξής: ```js { @@ -48,17 +48,17 @@ $ mv ./composer.phar ~/bin/composer # or /usr/local/bin/composer } ``` -Λέμε εδώ, ότι η εφαρμογή μας (ή η βιβλιοθήκη) εξαρτάται από το πακέτο `nette/database` (το όνομα του πακέτου αποτελείται από ένα όνομα προμηθευτή και το όνομα του έργου) και θέλει την έκδοση που ταιριάζει με τον περιορισμό έκδοσης `^3.0`. +Λέμε εδώ ότι η εφαρμογή μας (ή η βιβλιοθήκη) απαιτεί το πακέτο `nette/database` (το όνομα του πακέτου αποτελείται από το όνομα του οργανισμού και το όνομα του έργου) και θέλει μια έκδοση που αντιστοιχεί στη συνθήκη `^3.0` (δηλαδή την τελευταία έκδοση 3). -Έτσι, όταν έχουμε το αρχείο `composer.json` στη ρίζα του έργου και εκτελούμε: +Έχουμε λοιπόν στη ρίζα του έργου το αρχείο `composer.json` και εκκινούμε την εγκατάσταση: ```shell composer update ``` -Το Composer θα κατεβάσει τη βάση δεδομένων Nette στον κατάλογο `vendor`. Δημιουργεί επίσης ένα αρχείο `composer.lock`, το οποίο περιέχει πληροφορίες σχετικά με το ποιες ακριβώς εκδόσεις βιβλιοθηκών εγκατέστησε. +Ο Composer θα κατεβάσει το Nette Database στον φάκελο `vendor/`. Στη συνέχεια, θα δημιουργήσει το αρχείο `composer.lock`, το οποίο περιέχει πληροφορίες για τις ακριβείς εκδόσεις των βιβλιοθηκών που εγκατέστησε. -Το Composer δημιουργεί ένα αρχείο `vendor/autoload.php`. Μπορείτε απλά να συμπεριλάβετε αυτό το αρχείο και να αρχίσετε να χρησιμοποιείτε τις κλάσεις που παρέχουν αυτές οι βιβλιοθήκες χωρίς καμία επιπλέον εργασία: +Ο Composer θα δημιουργήσει το αρχείο `vendor/autoload.php`, το οποίο μπορούμε απλά να συμπεριλάβουμε και να αρχίσουμε να χρησιμοποιούμε τις βιβλιοθήκες χωρίς καμία περαιτέρω εργασία: ```php require __DIR__ . '/vendor/autoload.php'; @@ -67,52 +67,52 @@ $db = new Nette\Database\Connection('sqlite::memory:'); ``` -Ενημέρωση πακέτων στις τελευταίες εκδόσεις .[#toc-update-packages-to-the-latest-versions] -========================================================================================= +Ενημέρωση πακέτων στις τελευταίες εκδόσεις +========================================== -Για να ενημερώσετε όλα τα χρησιμοποιούμενα πακέτα στην τελευταία έκδοση σύμφωνα με τους περιορισμούς έκδοσης που ορίζονται στο `composer.json` χρησιμοποιήστε την εντολή `composer update`. Για παράδειγμα, για την εξάρτηση `"nette/database": "^3.0"` θα εγκαταστήσει την τελευταία έκδοση 3.x.x, αλλά όχι την έκδοση 4. +Η ενημέρωση των χρησιμοποιούμενων βιβλιοθηκών στις τελευταίες εκδόσεις σύμφωνα με τις συνθήκες που ορίζονται στο `composer.json` γίνεται με την εντολή `composer update`. Για παράδειγμα, για την εξάρτηση `"nette/database": "^3.0"`, θα εγκαταστήσει την τελευταία έκδοση 3.x.x, αλλά όχι την έκδοση 4. -Για να ενημερώσετε τους περιορισμούς έκδοσης στο αρχείο `composer.json` σε π.χ. `"nette/database": "^4.1"`, για να μπορέσετε να εγκαταστήσετε την τελευταία έκδοση, χρησιμοποιήστε την εντολή `composer require nette/database`. +Για να ενημερώσετε τις συνθήκες στο αρχείο `composer.json`, για παράδειγμα σε `"nette/database": "^4.1"`, ώστε να είναι δυνατή η εγκατάσταση της τελευταίας έκδοσης, χρησιμοποιήστε την εντολή `composer require nette/database`. -Για να ενημερώσετε όλα τα χρησιμοποιούμενα πακέτα Nette, θα πρέπει να τα απαριθμήσετε όλα στη γραμμή εντολών, π.χ: +Για να ενημερώσετε όλα τα χρησιμοποιούμενα πακέτα Nette, θα ήταν απαραίτητο να τα απαριθμήσετε όλα στη γραμμή εντολών, π.χ.: ```shell composer require nette/application nette/forms latte/latte tracy/tracy ... ``` -Το οποίο είναι πρακτικά ανέφικτο. Επομένως, χρησιμοποιήστε ένα απλό σενάριο "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff που θα το κάνει για εσάς: +Κάτι που είναι μη πρακτικό. Χρησιμοποιήστε επομένως το απλό σενάριο "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff, το οποίο θα το κάνει για εσάς: ```shell php composer-frontline.php ``` -Δημιουργία νέου έργου .[#toc-creating-new-project] -================================================== +Δημιουργία νέου έργου +===================== -Το νέο έργο Nette μπορεί να δημιουργηθεί με την εκτέλεση μιας απλής εντολής: +Δημιουργείτε ένα νέο έργο Nette με μία μόνο εντολή: ```shell -composer create-project nette/web-project name-of-the-project +composer create-project nette/web-project project-name ``` -Αντί για το `name-of-the-project` θα πρέπει να δώσετε το όνομα του καταλόγου για το έργο σας και να εκτελέσετε την εντολή. Το Composer θα φέρει το αποθετήριο `nette/web-project` από το GitHub, το οποίο περιέχει ήδη το αρχείο `composer.json`, και αμέσως μετά θα εγκαταστήσει το ίδιο το Nette Framework. Το μόνο που απομένει είναι να [ελέγξετε τα δικαιώματα εγγραφής |nette:troubleshooting#setting-directory-permissions] στους καταλόγους `temp/` και `log/` και είστε έτοιμοι να ξεκινήσετε. +Ως `project-name` εισάγετε το όνομα του καταλόγου για το έργο σας και επιβεβαιώστε. Ο Composer θα κατεβάσει το αποθετήριο `nette/web-project` από το GitHub, το οποίο περιέχει ήδη το αρχείο `composer.json`, και αμέσως μετά το Nette Framework. Θα πρέπει ήδη να αρκεί μόνο να [ορίσετε τα δικαιώματα |nette:troubleshooting#Ρύθμιση δικαιωμάτων καταλόγου] εγγραφής στους φακέλους `temp/` και `log/` και το έργο θα πρέπει να ζωντανέψει. -Αν γνωρίζετε σε ποια έκδοση PHP θα φιλοξενηθεί το έργο, φροντίστε να [το ρυθμίσετε |#PHP Version]. +Αν γνωρίζετε σε ποια έκδοση PHP θα φιλοξενηθεί το έργο, μην ξεχάσετε να [την ορίσετε |#Έκδοση PHP]. -Έκδοση PHP .[#toc-php-version] -============================== +Έκδοση PHP +========== -Το Composer εγκαθιστά πάντα τις εκδόσεις των πακέτων που είναι συμβατές με την έκδοση της PHP που χρησιμοποιείτε αυτή τη στιγμή (ή μάλλον, την έκδοση της PHP που χρησιμοποιείται στη γραμμή εντολών όταν εκτελείτε το Composer). Η οποία πιθανότατα δεν είναι η ίδια έκδοση που χρησιμοποιεί ο web host σας. Γι' αυτό είναι πολύ σημαντικό να προσθέσετε πληροφορίες σχετικά με την έκδοση της PHP στη φιλοξενία σας στο αρχείο `composer.json`. Μετά από αυτό, θα εγκατασταθούν μόνο οι εκδόσεις των πακέτων που είναι συμβατές με τον host. +Ο Composer εγκαθιστά πάντα τις εκδόσεις των πακέτων που είναι συμβατές με την έκδοση PHP που χρησιμοποιείτε αυτή τη στιγμή (καλύτερα, με την έκδοση PHP που χρησιμοποιείται στη γραμμή εντολών κατά την εκτέλεση του Composer). Αυτό όμως πιθανότατα δεν είναι η ίδια έκδοση που χρησιμοποιεί το hosting σας. Γι' αυτό είναι πολύ σημαντικό να προσθέσετε στο αρχείο `composer.json` την πληροφορία για την έκδοση PHP στο hosting. Στη συνέχεια, θα εγκαθίστανται μόνο οι εκδόσεις των πακέτων που είναι συμβατές με το hosting. -Για παράδειγμα, για να ρυθμίσετε το έργο να τρέχει σε PHP 8.2.3, χρησιμοποιήστε την εντολή: +Το ότι το έργο θα εκτελείται, για παράδειγμα, σε PHP 8.2.3, το ορίζουμε με την εντολή: ```shell composer config platform.php 8.2.3 ``` -Έτσι γράφεται η έκδοση στο αρχείο `composer.json`: +Έτσι, η έκδοση θα καταγραφεί στο αρχείο `composer.json`: ```js { @@ -124,8 +124,7 @@ composer config platform.php 8.2.3 } ``` -Ωστόσο, ο αριθμός έκδοσης της PHP αναφέρεται και σε άλλο σημείο του αρχείου, στην ενότητα `require`. Ενώ ο πρώτος αριθμός καθορίζει την έκδοση για την οποία θα εγκατασταθούν τα πακέτα, ο δεύτερος αριθμός λέει για ποια έκδοση είναι γραμμένη η ίδια η εφαρμογή. -(Φυσικά, δεν έχει νόημα αυτές οι εκδόσεις να είναι διαφορετικές, οπότε η διπλή καταχώρηση είναι πλεονασμός). Ορίζετε αυτή την έκδοση με την εντολή: +Ωστόσο, ο αριθμός έκδοσης της PHP αναφέρεται και σε άλλο σημείο του αρχείου, στην ενότητα `require`. Ενώ ο πρώτος αριθμός καθορίζει για ποια έκδοση θα εγκατασταθούν τα πακέτα, ο δεύτερος αριθμός λέει για ποια έκδοση είναι γραμμένη η ίδια η εφαρμογή. Και σύμφωνα με αυτόν, για παράδειγμα, το PhpStorm ορίζει το *PHP language level*. (Φυσικά, δεν έχει νόημα αυτές οι εκδόσεις να διαφέρουν, οπότε η διπλή καταγραφή είναι αβλεψία.) Αυτή την έκδοση την ορίζετε με την εντολή: ```shell composer require php 8.2.3 --no-update @@ -142,66 +141,72 @@ composer require php 8.2.3 --no-update ``` -Ψευδείς αναφορές .[#toc-false-reports] -====================================== +Αγνόηση έκδοσης PHP +=================== -Όταν αναβαθμίζετε πακέτα ή αλλάζετε αριθμούς εκδόσεων, συμβαίνουν συγκρούσεις. Ένα πακέτο έχει απαιτήσεις που συγκρούονται με ένα άλλο και ούτω καθεξής. Ωστόσο, το Composer εκτυπώνει περιστασιακά ψευδή μηνύματα. Αναφέρει μια σύγκρουση που στην πραγματικότητα δεν υπάρχει. Σε αυτή την περίπτωση, βοηθάει να διαγράψετε το αρχείο `composer.lock` και να δοκιμάσετε ξανά. +Τα πακέτα συνήθως αναφέρουν τόσο την κατώτατη έκδοση PHP με την οποία είναι συμβατά, όσο και την ανώτατη με την οποία έχουν δοκιμαστεί. Αν σκοπεύετε να χρησιμοποιήσετε μια έκδοση PHP ακόμα νεότερη, για παράδειγμα για λόγους δοκιμών, ο Composer θα αρνηθεί να εγκαταστήσει ένα τέτοιο πακέτο. Η λύση είναι η επιλογή `--ignore-platform-req=php+`, η οποία προκαλεί τον Composer να αγνοήσει τα ανώτατα όρια της απαιτούμενης έκδοσης PHP. -Αν το μήνυμα σφάλματος επιμένει, τότε εννοείται σοβαρά και πρέπει να διαβάσετε από αυτό τι πρέπει να τροποποιήσετε και πώς. +Ψευδή μηνύματα +============== -Packagist.org - Παγκόσμιο αποθετήριο .[#toc-packagist-org-global-repository] -============================================================================ +Κατά την αναβάθμιση πακέτων ή την αλλαγή αριθμών έκδοσης, συμβαίνει να προκύψει σύγκρουση. Ένα πακέτο έχει απαιτήσεις που έρχονται σε αντίθεση με ένα άλλο και παρόμοια. Ο Composer όμως μερικές φορές εμφανίζει ψευδή μηνύματα. Αναφέρει σύγκρουση που στην πραγματικότητα δεν υπάρχει. Σε τέτοια περίπτωση, βοηθά η διαγραφή του αρχείου `composer.lock` και η επανάληψη της προσπάθειας. -[Το Packagist |https://packagist.org] είναι το κύριο αποθετήριο πακέτων, στο οποίο το Composer προσπαθεί να αναζητήσει πακέτα, αν δεν του έχει ειπωθεί κάτι διαφορετικό. Μπορείτε επίσης να δημοσιεύσετε τα δικά σας πακέτα εδώ. +Αν το μήνυμα σφάλματος επιμένει, τότε εννοείται σοβαρά και πρέπει να διαβάσετε από αυτό τι και πώς να τροποποιήσετε. -Τι γίνεται αν δεν θέλουμε το κεντρικό αποθετήριο .[#toc-what-if-we-don-t-want-the-central-repository] ------------------------------------------------------------------------------------------------------ +Packagist.org - κεντρικό αποθετήριο +=================================== -Αν έχουμε εσωτερικές εφαρμογές ή βιβλιοθήκες στην εταιρεία μας, οι οποίες δεν μπορούν να φιλοξενηθούν δημόσια στο Packagist, μπορούμε να δημιουργήσουμε τα δικά μας αποθετήρια για αυτά τα έργα. +Το [Packagist |https://packagist.org] είναι το κύριο αποθετήριο στο οποίο ο Composer προσπαθεί να αναζητήσει πακέτα, αν δεν του πούμε διαφορετικά. Μπορούμε επίσης να δημοσιεύσουμε εδώ τα δικά μας πακέτα. -Περισσότερα για τα αποθετήρια στην [επίσημη τεκμηρίωση |https://getcomposer.org/doc/05-repositories.md#repositories]. +Τι γίνεται αν δεν θέλουμε να χρησιμοποιήσουμε το κεντρικό αποθετήριο; +--------------------------------------------------------------------- -Αυτόματη φόρτωση .[#toc-autoloading] -==================================== +Αν έχουμε εσωτερικές εταιρικές εφαρμογές, τις οποίες απλά δεν μπορούμε να φιλοξενήσουμε δημόσια, τότε δημιουργούμε γι' αυτές ένα εταιρικό αποθετήριο. -Ένα βασικό χαρακτηριστικό του Composer είναι ότι παρέχει αυτόματη φόρτωση για όλες τις κλάσεις που εγκαθιστά, την οποία ξεκινάτε συμπεριλαμβάνοντας ένα αρχείο `vendor/autoload.php`. +Περισσότερα για το θέμα των αποθετηρίων [στην επίσημη τεκμηρίωση |https://getcomposer.org/doc/05-repositories.md#repositories]. -Ωστόσο, είναι επίσης δυνατό να χρησιμοποιήσετε το Composer για να φορτώσετε άλλες κλάσεις εκτός του φακέλου `vendor`. Η πρώτη επιλογή είναι να αφήσετε το Composer να σαρώσει τους καθορισμένους φακέλους και υποφακέλους, να βρει όλες τις κλάσεις και να τις συμπεριλάβει στον αυτόματο επαναφορτωτή. Για να το κάνετε αυτό, ορίστε το `autoload > classmap` στο `composer.json`: + +Autoloading +=========== + +Ένα θεμελιώδες χαρακτηριστικό του Composer είναι ότι παρέχει αυτόματη φόρτωση για όλες τις κλάσεις που έχει εγκαταστήσει, την οποία ξεκινάτε συμπεριλαμβάνοντας το αρχείο `vendor/autoload.php`. + +Ωστόσο, είναι δυνατό να χρησιμοποιήσετε τον Composer και για τη φόρτωση άλλων κλάσεων εκτός του φακέλου `vendor`. Η πρώτη επιλογή είναι να αφήσετε τον Composer να σαρώσει καθορισμένους φακέλους και υποφακέλους, να βρει όλες τις κλάσεις και να τις συμπεριλάβει στον autoloader. Αυτό επιτυγχάνεται ορίζοντας το `autoload > classmap` στο `composer.json`: ```js { "autoload": { "classmap": [ - "src/", # includes the src/ folder and its subfolders + "src/", # περιλαμβάνει τον φάκελο src/ και τους υποφακέλους του ] } } ``` -Στη συνέχεια, είναι απαραίτητο να εκτελείτε την εντολή `composer dumpautoload` με κάθε αλλαγή και να αφήνετε τους πίνακες αυτόματης φόρτωσης να αναγεννώνται. Αυτό είναι εξαιρετικά άβολο και είναι πολύ καλύτερο να αναθέσετε αυτή την εργασία στο [RobotLoader |robot-loader:], το οποίο εκτελεί την ίδια δραστηριότητα αυτόματα στο παρασκήνιο και πολύ πιο γρήγορα. +Στη συνέχεια, είναι απαραίτητο σε κάθε αλλαγή να εκτελείτε την εντολή `composer dumpautoload` και να αφήνετε τους πίνακες αυτόματης φόρτωσης να αναδημιουργηθούν. Αυτό είναι εξαιρετικά άβολο και είναι πολύ καλύτερο να αναθέσετε αυτή την εργασία στο [RobotLoader|robot-loader:], το οποίο εκτελεί την ίδια δραστηριότητα αυτόματα στο παρασκήνιο και πολύ πιο γρήγορα. -Η δεύτερη επιλογή είναι να ακολουθήσετε το [PSR-4 |https://www.php-fig.org/psr/psr-4/]. Με απλά λόγια, πρόκειται για ένα σύστημα όπου τα namespaces και τα ονόματα των κλάσεων αντιστοιχούν στη δομή των καταλόγων και των ονομάτων των αρχείων, δηλαδή το `App\Router\RouterFactory` βρίσκεται στο αρχείο `/path/to/App/Router/RouterFactory.php`. Παράδειγμα διαμόρφωσης: +Η δεύτερη επιλογή είναι η τήρηση του [PSR-4|https://www.php-fig.org/psr/psr-4/]. Με απλά λόγια, πρόκειται για ένα σύστημα όπου οι χώροι ονομάτων και τα ονόματα κλάσεων αντιστοιχούν στη δομή καταλόγων και τα ονόματα αρχείων, δηλαδή π.χ. το `App\Core\RouterFactory` θα βρίσκεται στο αρχείο `/path/to/App/Core/RouterFactory.php`. Παράδειγμα διαμόρφωσης: ```js { "autoload": { "psr-4": { - "App\\": "app/" # the App\ namespace is in the app/ directory + "App\\": "app/" # ο χώρος ονομάτων App\ βρίσκεται στον κατάλογο app/ } } } ``` -Ανατρέξτε στην [Τεκμηρίωση του Composer |https://getcomposer.org/doc/04-schema.md#psr-4] για τον ακριβή τρόπο διαμόρφωσης αυτής της συμπεριφοράς. +Πώς ακριβώς να διαμορφώσετε τη συμπεριφορά θα μάθετε στην [τεκμηρίωση του Composer|https://getcomposer.org/doc/04-schema.md#psr-4]. -Δοκιμές νέων εκδόσεων .[#toc-testing-new-versions] -================================================== +Δοκιμή νέων εκδόσεων +==================== -Θέλετε να δοκιμάσετε μια νέα έκδοση ανάπτυξης ενός πακέτου. Πώς να το κάνετε; Αρχικά, προσθέστε αυτό το ζευγάρι επιλογών στο αρχείο `composer.json`, το οποίο θα σας επιτρέψει να εγκαταστήσετε εκδόσεις ανάπτυξης πακέτων, αλλά θα το κάνει μόνο αν δεν υπάρχει συνδυασμός σταθερών εκδόσεων που να ικανοποιεί τις απαιτήσεις: +Θέλετε να δοκιμάσετε μια νέα αναπτυξιακή έκδοση ενός πακέτου. Πώς να το κάνετε; Πρώτα, προσθέστε στο αρχείο `composer.json` αυτό το ζεύγος επιλογών, το οποίο επιτρέπει την εγκατάσταση αναπτυξιακών εκδόσεων πακέτων, αλλά καταφεύγει σε αυτό μόνο στην περίπτωση που δεν υπάρχει κανένας συνδυασμός σταθερών εκδόσεων που να ικανοποιεί τις απαιτήσεις: ```js { @@ -210,9 +215,9 @@ Packagist.org - Παγκόσμιο αποθετήριο .[#toc-packagist-org-glo } ``` -Συνιστούμε επίσης να διαγράψετε το αρχείο `composer.lock`, επειδή μερικές φορές το Composer αρνείται ακατανόητα να εγκαταστήσει και αυτό θα λύσει το πρόβλημα. +Στη συνέχεια, συνιστούμε να διαγράψετε το αρχείο `composer.lock`, μερικές φορές ο Composer ανεξήγητα αρνείται την εγκατάσταση και αυτό λύνει το πρόβλημα. -Ας υποθέσουμε ότι το πακέτο είναι `nette/utils` και η νέα έκδοση είναι 4.0. Το εγκαθιστάτε με την εντολή: +Ας υποθέσουμε ότι πρόκειται για το πακέτο `nette/utils` και η νέα έκδοση έχει τον αριθμό 4.0. Την εγκαθιστάτε με την εντολή: ```shell composer require nette/utils:4.0.x-dev @@ -224,20 +229,19 @@ composer require nette/utils:4.0.x-dev composer require nette/utils:4.0.0-RC2 ``` -Εάν ένα άλλο πακέτο εξαρτάται από τη βιβλιοθήκη και είναι κλειδωμένο σε μια παλαιότερη έκδοση (π.χ. `^3.1`), είναι ιδανικό να ενημερώσετε το πακέτο ώστε να λειτουργεί με τη νέα έκδοση. -Ωστόσο, αν θέλετε απλώς να παρακάμψετε τον περιορισμό και να αναγκάσετε το Composer να εγκαταστήσει την έκδοση ανάπτυξης και να προσποιηθεί ότι είναι μια παλαιότερη έκδοση (π.χ. 3.1.6), μπορείτε να χρησιμοποιήσετε τη λέξη-κλειδί `as`: +Αν όμως από τη βιβλιοθήκη εξαρτάται ένα άλλο πακέτο που είναι κλειδωμένο σε παλαιότερη έκδοση (π.χ. `^3.1`), τότε είναι ιδανικό να ενημερώσετε το πακέτο, ώστε να λειτουργεί με τη νέα έκδοση. Αν όμως θέλετε απλώς να παρακάμψετε τον περιορισμό και να αναγκάσετε τον Composer να εγκαταστήσει την αναπτυξιακή έκδοση και να προσποιηθεί ότι πρόκειται για παλαιότερη έκδοση (π.χ. 3.1.6), μπορείτε να χρησιμοποιήσετε τη λέξη-κλειδί `as`: ```shell composer require nette/utils "4.0.x-dev as 3.1.6" ``` -Κλήση εντολών .[#toc-calling-commands] -====================================== +Κλήση εντολών +============= -Μπορείτε να καλέσετε τις δικές σας προσαρμοσμένες εντολές και σενάρια μέσω του Composer σαν να ήταν εγγενείς εντολές του Composer. Τα σενάρια που βρίσκονται στο φάκελο `vendor/bin` δεν χρειάζεται να προσδιορίσουν αυτόν το φάκελο. +Μέσω του Composer μπορείτε να καλέσετε τις δικές σας προκαθορισμένες εντολές και σενάρια, σαν να ήταν εγγενείς εντολές του Composer. Για σενάρια που βρίσκονται στον φάκελο `vendor/bin`, δεν χρειάζεται να αναφέρετε αυτόν τον φάκελο. -Ως παράδειγμα, ορίζουμε μια δέσμη ενεργειών στο αρχείο `composer.json` που χρησιμοποιεί [το Nette Tester |tester:] για την εκτέλεση δοκιμών: +Ως παράδειγμα, ορίζουμε στο αρχείο `composer.json` ένα σενάριο που χρησιμοποιεί το [Nette Tester|tester:] για την εκτέλεση δοκιμών: ```js { @@ -247,19 +251,19 @@ composer require nette/utils "4.0.x-dev as 3.1.6" } ``` -Στη συνέχεια, εκτελούμε τις δοκιμές με το `composer tester`. Μπορούμε να καλέσουμε την εντολή ακόμη και αν δεν βρισκόμαστε στον ριζικό φάκελο του έργου, αλλά σε έναν υποκατάλογο. +Στη συνέχεια, εκτελούμε τις δοκιμές χρησιμοποιώντας το `composer tester`. Μπορούμε να καλέσουμε την εντολή ακόμα κι αν δεν βρισκόμαστε στον ριζικό φάκελο του έργου, αλλά σε κάποιον υποφάκελο. -Αποστολή ευχαριστιών .[#toc-send-thanks] -======================================== +Στείλτε ευχαριστίες +=================== -Θα σας δείξουμε ένα κόλπο που θα κάνει τους συγγραφείς ανοιχτού κώδικα ευτυχισμένους. Μπορείτε εύκολα να δώσετε ένα αστέρι στο GitHub στις βιβλιοθήκες που χρησιμοποιεί το έργο σας. Απλά εγκαταστήστε τη βιβλιοθήκη `symfony/thanks`: +Θα σας δείξουμε ένα κόλπο με το οποίο θα ευχαριστήσετε τους δημιουργούς open source. Με έναν απλό τρόπο, δίνετε αστέρι στο GitHub στις βιβλιοθήκες που χρησιμοποιεί το έργο σας. Αρκεί να εγκαταστήσετε τη βιβλιοθήκη `symfony/thanks`: ```shell composer global require symfony/thanks ``` -Και στη συνέχεια εκτελέστε: +Και στη συνέχεια να εκτελέσετε: ```shell composer thanks @@ -268,13 +272,11 @@ composer thanks Δοκιμάστε το! -Διαμόρφωση .[#toc-configuration] -================================ +Διαμόρφωση +========== -Το Composer είναι στενά ενσωματωμένο με το εργαλείο ελέγχου εκδόσεων [Git |https://git-scm.com]. Εάν δεν χρησιμοποιείτε το Git, είναι απαραίτητο να το δηλώσετε στο Composer: +Ο Composer είναι στενά συνδεδεμένος με το εργαλείο διαχείρισης εκδόσεων [Git |https://git-scm.com]. Αν δεν το έχετε εγκατεστημένο, πρέπει να πείτε στον Composer να μην το χρησιμοποιεί: ```shell composer -g config preferred-install dist ``` - -{{sitename: Best Practices}} diff --git a/best-practices/el/creating-editing-form.texy b/best-practices/el/creating-editing-form.texy index 8b43ddd2fd..4566a81628 100644 --- a/best-practices/el/creating-editing-form.texy +++ b/best-practices/el/creating-editing-form.texy @@ -1,16 +1,16 @@ -Φόρμα δημιουργίας και επεξεργασίας εγγραφής -******************************************* +Φόρμα για τη δημιουργία και την επεξεργασία εγγραφών +**************************************************** .[perex] -Πώς να υλοποιήσετε σωστά την προσθήκη και την επεξεργασία μιας εγγραφής στη Nette, χρησιμοποιώντας την ίδια φόρμα και για τα δύο; +Πώς να υλοποιήσετε σωστά την προσθήκη και την επεξεργασία εγγραφών στο Nette, χρησιμοποιώντας την ίδια φόρμα και για τις δύο λειτουργίες; -Σε πολλές περιπτώσεις, οι φόρμες για την προσθήκη και την επεξεργασία μιας εγγραφής είναι ίδιες, διαφέροντας μόνο από την ετικέτα του κουμπιού. Θα παρουσιάσουμε παραδείγματα απλών παρουσιαστών όπου χρησιμοποιούμε τη φόρμα πρώτα για την προσθήκη μιας εγγραφής, στη συνέχεια για την επεξεργασία της και τέλος συνδυάζουμε τις δύο λύσεις. +Σε πολλές περιπτώσεις, οι φόρμες για την προσθήκη και την επεξεργασία εγγραφών είναι ίδιες, διαφέροντας ίσως μόνο στην ετικέτα του κουμπιού. Θα δείξουμε παραδείγματα απλών presenters, όπου θα χρησιμοποιήσουμε τη φόρμα πρώτα για την προσθήκη εγγραφής, μετά για την επεξεργασία και τέλος θα συνδυάσουμε τις δύο λύσεις. -Προσθήκη εγγραφής .[#toc-adding-a-record] ------------------------------------------ +Προσθήκη εγγραφής +----------------- -Ένα παράδειγμα παρουσιαστή που χρησιμοποιείται για την προσθήκη μιας εγγραφής. Θα αφήσουμε την πραγματική εργασία της βάσης δεδομένων στην κλάση `Facade`, της οποίας ο κώδικας δεν είναι σχετικός με το παράδειγμα. +Παράδειγμα presenter που χρησιμεύει για την προσθήκη εγγραφής. Την πραγματική εργασία με τη βάση δεδομένων θα την αφήσουμε στην κλάση `Facade`, ο κώδικας της οποίας δεν είναι ουσιαστικός για το παράδειγμα. ```php @@ -27,7 +27,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $form = new Form; - // ... προσθέστε πεδία φόρμας ... + // ... προσθέτουμε πεδία φόρμας ... $form->onSuccess[] = [$this, 'recordFormSucceeded']; return $form; @@ -48,10 +48,10 @@ class RecordPresenter extends Nette\Application\UI\Presenter ``` -Επεξεργασία εγγραφής .[#toc-editing-a-record] ---------------------------------------------- +Επεξεργασία εγγραφής +-------------------- -Τώρα ας δούμε πώς θα έμοιαζε ένας παρουσιαστής που χρησιμοποιείται για την επεξεργασία μιας εγγραφής: +Τώρα θα δείξουμε πώς θα έμοιαζε ο presenter που χρησιμεύει για την επεξεργασία εγγραφής: ```php @@ -70,7 +70,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $record = $this->facade->get($id); if ( - !$record // επαληθεύει την ύπαρξη του αρχείου + !$record // έλεγχος ύπαρξης εγγραφής || !$this->facade->isEditAllowed(/*...*/) // έλεγχος δικαιωμάτων ) { $this->error(); // σφάλμα 404 @@ -81,16 +81,16 @@ class RecordPresenter extends Nette\Application\UI\Presenter protected function createComponentRecordForm(): Form { - // επαληθεύστε ότι η ενέργεια είναι 'edit' + // ελέγχουμε ότι η action είναι 'edit' if ($this->getAction() !== 'edit') { $this->error(); } $form = new Form; - // ... προσθέστε πεδία φόρμας ... + // ... προσθέτουμε πεδία φόρμας ... - $form->setDefaults($this->record); // Ορισμός προεπιλεγμένων τιμών + $form->setDefaults($this->record); // ορισμός προεπιλεγμένων τιμών $form->onSuccess[] = [$this, 'recordFormSucceeded']; return $form; } @@ -104,9 +104,9 @@ class RecordPresenter extends Nette\Application\UI\Presenter } ``` -Στη μέθοδο *action*, η οποία καλείται ακριβώς στην αρχή του [κύκλου ζωής του παρουσιαστή |application:presenters#Life Cycle of Presenter], επαληθεύουμε την ύπαρξη της εγγραφής και την άδεια του χρήστη να την επεξεργαστεί. +Στη μέθοδο *action*, η οποία εκτελείται αμέσως στην αρχή του [κύκλου ζωής του presenter |application:presenters#Κύκλος ζωής του presenter], ελέγχουμε την ύπαρξη της εγγραφής και τα δικαιώματα του χρήστη να την επεξεργαστεί. -Αποθηκεύουμε την εγγραφή στην ιδιότητα `$record`, ώστε να είναι διαθέσιμη στη μέθοδο `createComponentRecordForm()` για τον καθορισμό των προεπιλογών και `recordFormSucceeded()` για το αναγνωριστικό. Μια εναλλακτική λύση θα ήταν να ορίσουμε τις προεπιλεγμένες τιμές απευθείας στη μέθοδο `actionEdit()` και η τιμή του ID, η οποία αποτελεί μέρος της διεύθυνσης URL, ανακτάται με τη χρήση της μεθόδου `getParameter('id')`: +Αποθηκεύουμε την εγγραφή στην ιδιότητα `$record`, ώστε να την έχουμε διαθέσιμη στη μέθοδο `createComponentRecordForm()` για τον ορισμό των προεπιλεγμένων τιμών, και στη `recordFormSucceeded()` για το ID. Μια εναλλακτική λύση θα ήταν να ορίσουμε τις προεπιλεγμένες τιμές απευθείας στην `actionEdit()` και να λάβουμε την τιμή του ID, η οποία είναι μέρος του URL, χρησιμοποιώντας το `getParameter('id')`: ```php @@ -114,12 +114,12 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $record = $this->facade->get($id); if ( - // επαληθεύει την ύπαρξη και ελέγχει τα δικαιώματα + // έλεγχος ύπαρξης και έλεγχος δικαιωμάτων ) { $this->error(); } - // ορισμός προεπιλεγμένων τιμών φόρμας + // ορισμός προεπιλεγμένων τιμών της φόρμας $this->getComponent('recordForm') ->setDefaults($record); } @@ -133,13 +133,13 @@ class RecordPresenter extends Nette\Application\UI\Presenter } ``` -Ωστόσο, και αυτό θα πρέπει να είναι **το πιο σημαντικό συμπέρασμα από όλο τον κώδικα**, πρέπει να βεβαιωθούμε ότι η ενέργεια είναι όντως `edit` όταν δημιουργούμε τη φόρμα. Διότι διαφορετικά η επικύρωση στη μέθοδο `actionEdit()` δεν θα γινόταν καθόλου! +Ωστόσο, και αυτό θα έπρεπε να είναι **το πιο σημαντικό συμπέρασμα όλου του κώδικα**, πρέπει κατά τη δημιουργία της φόρμας να βεβαιωθούμε ότι η action είναι όντως `edit`. Διότι διαφορετικά, ο έλεγχος στη μέθοδο `actionEdit()` δεν θα είχε πραγματοποιηθεί καθόλου! -Ίδια φόρμα για προσθήκη και επεξεργασία .[#toc-same-form-for-adding-and-editing] --------------------------------------------------------------------------------- +Ίδια φόρμα για προσθήκη και επεξεργασία +--------------------------------------- -Και τώρα θα συνδυάσουμε και τους δύο παρουσιαστές σε έναν. Είτε θα μπορούσαμε να διακρίνουμε ποια ενέργεια εμπλέκεται στη μέθοδο `createComponentRecordForm()` και να διαμορφώσουμε τη φόρμα ανάλογα, είτε μπορούμε να το αφήσουμε απευθείας στις μεθόδους ενέργειας και να απαλλαγούμε από τη συνθήκη: +Και τώρα συνδυάζουμε τους δύο presenters σε έναν. Είτε θα μπορούσαμε στη μέθοδο `createComponentRecordForm()` να διακρίνουμε ποια action είναι και ανάλογα να διαμορφώσουμε τη φόρμα, είτε μπορούμε να το αφήσουμε απευθείας στις action-μεθόδους και να απαλλαγούμε από τη συνθήκη: ```php @@ -160,27 +160,27 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $record = $this->facade->get($id); if ( - !$record // επαληθεύει την ύπαρξη του αρχείου + !$record // έλεγχος ύπαρξης εγγραφής || !$this->facade->isEditAllowed(/*...*/) // έλεγχος δικαιωμάτων ) { $this->error(); // σφάλμα 404 } $form = $this->getComponent('recordForm'); - $form->setDefaults($record); // Ορισμός προεπιλογών + $form->setDefaults($record); // ορισμός προεπιλεγμένων τιμών $form->onSuccess[] = [$this, 'editingFormSucceeded']; } protected function createComponentRecordForm(): Form { - // επαληθεύστε ότι η ενέργεια είναι 'add' ή 'edit'. + // ελέγχουμε ότι η action είναι 'add' ή 'edit' if (!in_array($this->getAction(), ['add', 'edit'])) { $this->error(); } $form = new Form; - // ... προσθέστε πεδία φόρμας ... + // ... προσθέτουμε πεδία φόρμας ... return $form; } @@ -203,4 +203,3 @@ class RecordPresenter extends Nette\Application\UI\Presenter ``` {{priority: -1}} -{{sitename: Best Practices}} diff --git a/best-practices/el/dynamic-snippets.texy b/best-practices/el/dynamic-snippets.texy index 59031461f0..134ee63491 100644 --- a/best-practices/el/dynamic-snippets.texy +++ b/best-practices/el/dynamic-snippets.texy @@ -1,7 +1,7 @@ -Δυναμικά αποσπάσματα -******************** +Δυναμικά snippets +***************** -Αρκετά συχνά κατά την ανάπτυξη εφαρμογών υπάρχει η ανάγκη εκτέλεσης λειτουργιών AJAX, για παράδειγμα, σε μεμονωμένες γραμμές ενός πίνακα ή σε στοιχεία λίστας. Ως παράδειγμα, μπορούμε να επιλέξουμε να απαριθμήσουμε άρθρα, επιτρέποντας στον συνδεδεμένο χρήστη να επιλέξει μια βαθμολογία "like/dislike" για καθένα από αυτά. Ο κώδικας του παρουσιαστή και του αντίστοιχου προτύπου χωρίς AJAX θα μοιάζει κάπως έτσι (παραθέτω τα πιο σημαντικά αποσπάσματα, ο κώδικας προϋποθέτει την ύπαρξη μιας υπηρεσίας για τη σήμανση των αξιολογήσεων και τη λήψη μιας συλλογής άρθρων - η συγκεκριμένη υλοποίηση δεν είναι σημαντική για τους σκοπούς αυτού του σεμιναρίου): +Αρκετά συχνά κατά την ανάπτυξη εφαρμογών προκύπτει η ανάγκη εκτέλεσης λειτουργιών AJAX, για παράδειγμα, σε μεμονωμένες γραμμές πίνακα ή στοιχεία λίστας. Ως παράδειγμα, μπορούμε να επιλέξουμε την εμφάνιση άρθρων, όπου για κάθε ένα από αυτά επιτρέπουμε στον συνδεδεμένο χρήστη να επιλέξει βαθμολογία "μου αρέσει/δεν μου αρέσει". Ο κώδικας του presenter και του αντίστοιχου template χωρίς AJAX θα μοιάζει περίπου ως εξής (παραθέτω τα πιο σημαντικά αποσπάσματα, ο κώδικας υπολογίζει την ύπαρξη μιας υπηρεσίας για την επισήμανση της βαθμολογίας και τη λήψη της συλλογής άρθρων - η συγκεκριμένη υλοποίηση δεν είναι σημαντική για τους σκοπούς αυτού του οδηγού): ```php public function handleLike(int $articleId): void @@ -24,26 +24,26 @@ Template: <h2>{$article->title}</h2> <div class="content">{$article->content}</div> {if !$article->liked} - <a n:href="like! $article->id" class=ajax>I like it</a> + <a n:href="like! $article->id" class=ajax>μου αρέσει</a> {else} - <a n:href="unlike! $article->id" class=ajax>I don't like it anymore</a> + <a n:href="unlike! $article->id" class=ajax>δεν μου αρέσει πια</a> {/if} </article> ``` -Ajaxization .[#toc-ajaxization] -=============================== +Ajaxification +============= -Ας φέρουμε τώρα το AJAX σε αυτή την απλή εφαρμογή. Η αλλαγή της βαθμολογίας ενός άρθρου δεν είναι αρκετά σημαντική ώστε να απαιτεί ένα αίτημα HTTP με ανακατεύθυνση, οπότε ιδανικά θα πρέπει να γίνεται με AJAX στο παρασκήνιο. Θα χρησιμοποιήσουμε το [σενάριο χειρισμού από τα πρόσθετα |https://componette.org/vojtech-dobes/nette.ajax.js/] με τη συνήθη σύμβαση ότι οι σύνδεσμοι AJAX έχουν την κλάση CSS `ajax`. +Ας εξοπλίσουμε τώρα αυτήν την απλή εφαρμογή με AJAX. Η αλλαγή της βαθμολογίας ενός άρθρου δεν είναι τόσο σημαντική ώστε να απαιτείται ανακατεύθυνση, και επομένως θα έπρεπε ιδανικά να γίνεται με AJAX στο παρασκήνιο. Θα χρησιμοποιήσουμε το [βοηθητικό script από τα add-ons |application:ajax#Naja] με τη συνήθη σύμβαση ότι οι σύνδεσμοι AJAX έχουν την CSS κλάση `ajax`. -Ωστόσο, πώς να το κάνουμε συγκεκριμένα; Η Nette προσφέρει 2 τρόπους: τον τρόπο με τα δυναμικά αποσπάσματα και τον τρόπο με τα συστατικά. Και οι δύο έχουν τα πλεονεκτήματα και τα μειονεκτήματά τους, γι' αυτό θα τους παρουσιάσουμε έναν προς έναν. +Ωστόσο, πώς να το κάνουμε συγκεκριμένα; Το Nette προσφέρει 2 δρόμους: τον δρόμο των λεγόμενων δυναμικών snippets και τον δρόμο των components. Και οι δύο έχουν τα υπέρ και τα κατά τους, και γι' αυτό θα τους παρουσιάσουμε έναν προς έναν. -Ο τρόπος των δυναμικών αποσπασμάτων .[#toc-the-dynamic-snippets-way] -==================================================================== +Ο δρόμος των δυναμικών snippets +=============================== -Στην ορολογία Latte, ένα δυναμικό απόσπασμα είναι μια ειδική περίπτωση χρήσης της ετικέτας `{snippet}` όπου μια μεταβλητή χρησιμοποιείται στο όνομα του αποσπάσματος. Ένα τέτοιο snippet δεν μπορεί να βρεθεί οπουδήποτε στο πρότυπο - πρέπει να είναι τυλιγμένο από ένα στατικό snippet, δηλαδή ένα κανονικό, ή μέσα σε ένα `{snippetArea}`. Θα μπορούσαμε να τροποποιήσουμε το πρότυπό μας ως εξής. +Ένα δυναμικό snippet σημαίνει στην ορολογία του Latte μια συγκεκριμένη περίπτωση χρήσης του tag `{snippet}`, όπου στο όνομα του snippet χρησιμοποιείται μια μεταβλητή. Ένα τέτοιο snippet δεν μπορεί να βρίσκεται οπουδήποτε στο template - πρέπει να περιβάλλεται από ένα στατικό snippet, δηλαδή ένα συνηθισμένο, ή μέσα σε `{snippetArea}`. Θα μπορούσαμε να τροποποιήσουμε το template μας ως εξής. ```latte @@ -53,18 +53,18 @@ Ajaxization .[#toc-ajaxization] <div class="content">{$article->content}</div> {snippet article-{$article->id}} {if !$article->liked} - <a n:href="like! $article->id" class=ajax>I like it</a> + <a n:href="like! $article->id" class=ajax>μου αρέσει</a> {else} - <a n:href="unlike! $article->id" class=ajax>I don't like it anymore</a> + <a n:href="unlike! $article->id" class=ajax>δεν μου αρέσει πια</a> {/if} {/snippet} </article> {/snippet} ``` -Κάθε άρθρο ορίζει τώρα ένα μόνο snippet, το οποίο έχει ένα αναγνωριστικό άρθρου στον τίτλο. Όλα αυτά τα αποσπάσματα τυλίγονται στη συνέχεια σε ένα ενιαίο απόσπασμα που ονομάζεται `articlesContainer`. Εάν παραλείψουμε αυτό το απόσπασμα περιτύλιξης, το Latte θα μας ειδοποιήσει με μια εξαίρεση. +Κάθε άρθρο ορίζει τώρα ένα snippet, το οποίο έχει στο όνομά του το ID του άρθρου. Όλα αυτά τα snippets είναι στη συνέχεια ομαδοποιημένα μαζί με ένα snippet με το όνομα `articlesContainer`. Αν παραλείπαμε αυτό το περιβάλλον snippet, το Latte θα μας ειδοποιούσε με μια εξαίρεση. -Το μόνο που απομένει να κάνουμε είναι να προσθέσουμε ανασχεδίαση στον παρουσιαστή - απλά ανασχεδιάζουμε το στατικό περιτύλιγμα. +Μας μένει να συμπληρώσουμε την επανασχεδίαση στον presenter - αρκεί να επανασχεδιάσουμε το στατικό περιτύλιγμα. ```php public function handleLike(int $articleId): void @@ -79,11 +79,11 @@ public function handleLike(int $articleId): void } ``` -Τροποποιήστε την αδελφή μέθοδο `handleUnlike()` με τον ίδιο τρόπο, και η AJAX είναι έτοιμη και λειτουργεί! +Ομοίως, τροποποιούμε και την αδελφή μέθοδο `handleUnlike()`, και το AJAX είναι λειτουργικό! -Ωστόσο, η λύση έχει ένα μειονέκτημα. Αν ερευνήσουμε περισσότερο τον τρόπο με τον οποίο λειτουργεί η αίτηση AJAX, θα διαπιστώσουμε ότι παρόλο που η εφαρμογή φαίνεται αποτελεσματική εμφανισιακά (επιστρέφει μόνο ένα απόσπασμα για ένα συγκεκριμένο άρθρο), στην πραγματικότητα απεικονίζει όλα τα αποσπάσματα στον διακομιστή. Έχει τοποθετήσει το επιθυμητό απόσπασμα στο ωφέλιμο φορτίο μας και έχει απορρίψει τα υπόλοιπα (έτσι, εντελώς περιττά, τα έχει ανακτήσει και από τη βάση δεδομένων). +Η λύση έχει όμως ένα σκοτεινό σημείο. Αν εξετάζαμε περισσότερο πώς διεξάγεται το αίτημα AJAX, θα διαπιστώναμε ότι παρόλο που εξωτερικά η εφαρμογή φαίνεται οικονομική (επιστρέφει μόνο ένα μοναδικό snippet για το συγκεκριμένο άρθρο), στην πραγματικότητα στον server σχεδίασε όλα τα snippets. Το επιθυμητό snippet τοποθετήθηκε στο payload, και τα υπόλοιπα απορρίφθηκαν (εντελώς άσκοπα τα απέκτησε επίσης από τη βάση δεδομένων). -Για να βελτιστοποιήσουμε αυτή τη διαδικασία, θα πρέπει να προβούμε σε ενέργειες όπου θα περνάμε τη συλλογή `$articles` στο πρότυπο (π.χ. στη μέθοδο `renderDefault()` ). Θα εκμεταλλευτούμε το γεγονός ότι η επεξεργασία του σήματος πραγματοποιείται πριν από την `render<Something>` μεθόδους: +Για να βελτιστοποιήσουμε αυτή τη διαδικασία, θα πρέπει να παρέμβουμε εκεί όπου περνάμε τη συλλογή `$articles` στο template (ας πούμε στη μέθοδο `renderDefault()`). Θα εκμεταλλευτούμε το γεγονός ότι η επεξεργασία των σημάτων γίνεται πριν από τις μεθόδους `render<Something>`: ```php public function handleLike(int $articleId): void @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connection->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,18 +101,18 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connection->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` -Τώρα, όταν το σήμα επεξεργάζεται, αντί για μια συλλογή με όλα τα άρθρα, μόνο ένας πίνακας με ένα μόνο άρθρο περνάει στο πρότυπο - αυτό που θέλουμε να αποδώσουμε και να στείλουμε ως payload στο πρόγραμμα περιήγησης. Έτσι, το `{foreach}` θα γίνει μόνο μία φορά και δεν θα αποδοθούν επιπλέον αποσπάσματα. +Τώρα, κατά την επεξεργασία του σήματος, στο template περνιέται αντί για τη συλλογή με όλα τα άρθρα, μόνο ένας πίνακας με ένα μοναδικό άρθρο - αυτό που θέλουμε να σχεδιάσουμε και να στείλουμε στο payload στον browser. Το `{foreach}` λοιπόν θα εκτελεστεί μόνο μία φορά και κανένα επιπλέον snippet δεν θα σχεδιαστεί. -Τρόπος συστατικού .[#toc-component-way] -======================================= +Ο δρόμος των components +======================= -Μια εντελώς διαφορετική λύση χρησιμοποιεί μια διαφορετική προσέγγιση για την αποφυγή δυναμικών αποσπασμάτων. Το κόλπο είναι να μεταφέρουμε όλη τη λογική σε ένα ξεχωριστό συστατικό - από εδώ και πέρα, δεν έχουμε έναν παρουσιαστή που φροντίζει για την εισαγωγή της βαθμολογίας, αλλά ένα ειδικό `LikeControl`. Η κλάση θα μοιάζει με την ακόλουθη (επιπλέον, θα περιέχει και τις μεθόδους `render`, `handleUnlike`, κ.λπ:) +Ένας εντελώς διαφορετικός τρόπος λύσης αποφεύγει τα δυναμικά snippets. Το κόλπο έγκειται στη μεταφορά ολόκληρης της λογικής σε ένα ξεχωριστό component - από τώρα και στο εξής, η εισαγωγή βαθμολογίας δεν θα γίνεται από τον presenter, αλλά από ένα εξειδικευμένο `LikeControl`. Η κλάση θα μοιάζει ως εξής (εκτός από αυτό, θα περιέχει επίσης τις μεθόδους `render`, `handleUnlike` κ.λπ.): ```php class LikeControl extends Nette\Application\UI\Control @@ -134,31 +134,31 @@ class LikeControl extends Nette\Application\UI\Control } ``` -Πρότυπο του συστατικού: +Το template του component: ```latte {snippet} {if !$article->liked} - <a n:href="like!" class=ajax>I like it</a> + <a n:href="like!" class=ajax>μου αρέσει</a> {else} - <a n:href="unlike!" class=ajax>I don't like it anymore</a> + <a n:href="unlike!" class=ajax>δεν μου αρέσει πια</a> {/if} {/snippet} ``` -Φυσικά θα αλλάξουμε το πρότυπο προβολής και θα πρέπει να προσθέσουμε ένα εργοστάσιο στον παρουσιαστή. Δεδομένου ότι θα δημιουργήσουμε το συστατικό τόσες φορές όσες φορές λαμβάνουμε άρθρα από τη βάση δεδομένων, θα χρησιμοποιήσουμε την κλάση [application:Multiplier] για να το "πολλαπλασιάσουμε". +Φυσικά, το template της προβολής θα αλλάξει και θα πρέπει να προσθέσουμε ένα factory στον presenter. Επειδή θα δημιουργήσουμε το component τόσες φορές όσα άρθρα λάβουμε από τη βάση δεδομένων, θα χρησιμοποιήσουμε την κλάση [Multiplier |application:Multiplier] για τον "πολλαπλασιασμό" του. ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); } ``` -Το πρότυπο προβολής μειώνεται στο ελάχιστο απαραίτητο (και εντελώς απαλλαγμένο από αποσπάσματα!): +Το template της προβολής θα μειωθεί στο ελάχιστο απαραίτητο (και εντελώς απαλλαγμένο από snippets!): ```latte <article n:foreach="$articles as $article"> @@ -168,7 +168,6 @@ protected function createComponentLikeControl() </article> ``` -Η εφαρμογή θα λειτουργεί τώρα με AJAX. Και εδώ πρέπει να βελτιστοποιήσουμε την εφαρμογή, επειδή λόγω της χρήσης της βάσης δεδομένων Nette, η επεξεργασία σήματος θα φορτώσει άσκοπα όλα τα άρθρα από τη βάση δεδομένων αντί για ένα. Ωστόσο, το πλεονέκτημα είναι ότι δεν θα υπάρξει rendering, επειδή μόνο το δικό μας συστατικό θα αποδοθεί πραγματικά. +Έχουμε σχεδόν τελειώσει: η εφαρμογή τώρα θα λειτουργεί με AJAX. Και εδώ μας περιμένει η βελτιστοποίηση της εφαρμογής, επειδή λόγω της χρήσης του Nette Database, κατά την επεξεργασία του σήματος φορτώνονται άσκοπα όλα τα άρθρα από τη βάση δεδομένων αντί για ένα. Το πλεονέκτημα όμως είναι ότι δεν θα γίνει η σχεδίασή τους, επειδή θα αποδοθεί πραγματικά μόνο το component μας. {{priority: -1}} -{{sitename: Best Practices}} diff --git a/best-practices/el/editors-and-tools.texy b/best-practices/el/editors-and-tools.texy index 3e894b27ea..a5d46f739c 100644 --- a/best-practices/el/editors-and-tools.texy +++ b/best-practices/el/editors-and-tools.texy @@ -1,40 +1,40 @@ -Συντάκτες & Εργαλεία -******************** +Επεξεργαστές & εργαλεία +*********************** .[perex] -Μπορείτε να γίνετε ικανός προγραμματιστής, αλλά μόνο με καλά εργαλεία θα γίνετε μάστορας. Σε αυτό το κεφάλαιο θα βρείτε συμβουλές για σημαντικά εργαλεία, επεξεργαστές και πρόσθετα. +Μπορεί να είστε ένας ικανός προγραμματιστής, αλλά μόνο με καλά εργαλεία γίνεστε μάστορας. Σε αυτό το κεφάλαιο θα βρείτε συμβουλές για σημαντικά εργαλεία, επεξεργαστές και plugins. -Επεξεργαστής IDE .[#toc-ide-editor] -=================================== +IDE editor +========== -Συνιστούμε ανεπιφύλακτα τη χρήση ενός πλήρως εξοπλισμένου IDE για την ανάπτυξη, όπως το PhpStorm, NetBeans, VS Code, και όχι απλώς ενός επεξεργαστή κειμένου με υποστήριξη PHP. Η διαφορά είναι πραγματικά κρίσιμη. Δεν υπάρχει κανένας λόγος να είστε ικανοποιημένοι με έναν κλασικό επεξεργαστή με υπογράμμιση συντακτικού, επειδή δεν φτάνει τις δυνατότητες ενός IDE με ακριβή πρόταση κώδικα, που μπορεί να κάνει refactor κώδικα και πολλά άλλα. Ορισμένα IDE είναι επί πληρωμή, άλλα είναι δωρεάν. +Συνιστούμε ανεπιφύλακτα τη χρήση ενός πλήρους IDE για την ανάπτυξη, όπως το PhpStorm, το NetBeans, το VS Code, και όχι απλώς ενός επεξεργαστή κειμένου με υποστήριξη PHP. Η διαφορά είναι πραγματικά θεμελιώδης. Δεν υπάρχει λόγος να αρκεστείτε σε έναν απλό επεξεργαστή που, αν και μπορεί να χρωματίζει τη σύνταξη, δεν φτάνει τις δυνατότητες ενός κορυφαίου IDE, το οποίο προτείνει με ακρίβεια, ελέγχει για σφάλματα, μπορεί να αναδιαμορφώσει τον κώδικα και πολλά άλλα. Ορισμένα IDE είναι επί πληρωμή, άλλα είναι ακόμη και δωρεάν. -Το **NetBeans IDE** έχει ενσωματωμένη υποστήριξη για τα Nette, Latte και NEON. +Το **NetBeans IDE** έχει ενσωματωμένη υποστήριξη για Nette, Latte και NEON. -**PhpStorm**: εγκαταστήστε αυτά τα plugins στο `Settings > Plugins > Marketplace`: -- Βοηθητικά του πλαισίου Nette +**PhpStorm**: εγκαταστήστε αυτά τα plugins στο `Settings > Plugins > Marketplace` +- Nette framework helpers - Latte -- Υποστήριξη NEON -- Δοκιμαστής Nette +- NEON support +- Nette Tester -**VS Code**: βρείτε το πρόσθετο "Nette Latte + Neon" στην αγορά. +**VS Code**: βρείτε το plugin "Nette Latte + Neon" στο marketplace. -Συνδέστε επίσης την Tracy με τον επεξεργαστή. Όταν εμφανίζεται η σελίδα σφάλματος, μπορείτε να κάνετε κλικ στα ονόματα των αρχείων και θα ανοίξουν στον επεξεργαστή με τον κέρσορα στην αντίστοιχη γραμμή. Μάθετε [πώς να ρυθμίζετε το σύστημα |tracy:open-files-in-ide]. +Συνδέστε επίσης το Tracy με τον επεξεργαστή σας. Όταν εμφανίζεται μια σελίδα σφάλματος, θα μπορείτε να κάνετε κλικ στα ονόματα των αρχείων και αυτά θα ανοίγουν στον επεξεργαστή με τον κέρσορα στην αντίστοιχη γραμμή. Διαβάστε [πώς να διαμορφώσετε το σύστημα |tracy:open-files-in-ide]. -PHPStan .[#toc-phpstan] -======================= +PHPStan +======= -Το PHPStan είναι ένα εργαλείο που ανιχνεύει λογικά σφάλματα στον κώδικά σας πριν τον εκτελέσετε. +Το PHPStan είναι ένα εργαλείο που εντοπίζει λογικά σφάλματα στον κώδικα πριν τον εκτελέσετε. -Εγκαταστήστε το μέσω του Composer: +Το εγκαθιστούμε χρησιμοποιώντας το Composer: ```shell composer require --dev phpstan/phpstan-nette ``` -Δημιουργήστε ένα αρχείο ρυθμίσεων `phpstan.neon` στο έργο: +Δημιουργούμε στο έργο ένα αρχείο διαμόρφωσης `phpstan.neon`: ```neon includes: @@ -47,40 +47,38 @@ parameters: level: 5 ``` -Και στη συνέχεια αφήστε το να αναλύσει τις κλάσεις στο φάκελο `app/`: +Και στη συνέχεια το αφήνουμε να αναλύσει τις κλάσεις στον φάκελο `app/`: ```shell vendor/bin/phpstan analyse app ``` -Μπορείτε να βρείτε αναλυτική τεκμηρίωση απευθείας στο [PHPStan |https://phpstan.org]. +Μπορείτε να βρείτε εξαντλητική τεκμηρίωση απευθείας στην [ιστοσελίδα του PHPStan |https://phpstan.org]. -Έλεγχος κώδικα .[#toc-code-checker] -=================================== +Code Checker +============ -[Ο έλεγχος κώδικα |code-checker:] ελέγχει και ενδεχομένως επιδιορθώνει ορισμένα τυπικά σφάλματα στον πηγαίο σας κώδικα. +Ο [Code Checker|code-checker:] ελέγχει και ενδεχομένως διορθώνει ορισμένα από τα τυπικά σφάλματα στους πηγαίους κώδικές σας: -- αφαιρεί [BOM |nette:glossary#bom] -- ελέγχει την εγκυρότητα των προτύπων [Latte |latte:] +- αφαιρεί το [BOM |nette:glossary#BOM] +- ελέγχει την εγκυρότητα των templates [Latte |latte:] - ελέγχει την εγκυρότητα των αρχείων `.neon`, `.php` και `.json` -- ελέγχει για [χαρακτήρες ελέγχου |nette:glossary#control characters] +- ελέγχει την ύπαρξη [χαρακτήρων ελέγχου |nette:glossary#Control characters] - ελέγχει αν το αρχείο είναι κωδικοποιημένο σε UTF-8 -- ελέγχει το ορθογραφικό λάθος `/* @annotations */` (λείπει ο δεύτερος αστερίσκος) -- αφαιρεί τις ετικέτες κατάληξης PHP `?>` σε αρχεία PHP -- αφαιρεί τα κενά διαστήματα και τις περιττές κενές γραμμές από το τέλος ενός αρχείου -- ομαλοποιεί τις καταλήξεις γραμμών στην προεπιλογή του συστήματος (με την παράμετρο `-l` ) +- ελέγχει λανθασμένα γραμμένα `/* @anotace */` (λείπει ο αστερίσκος) +- αφαιρεί το τελικό `?>` από τα αρχεία PHP +- αφαιρεί τα δεξιά κενά και τις περιττές γραμμές στο τέλος του αρχείου +- κανονικοποιεί τους διαχωριστές γραμμών σε συστήματος (αν δώσετε την επιλογή `-l`) -Composer .[#toc-composer] -========================= +Composer +======== -Το [Composer |Composer] είναι ένα εργαλείο για τη διαχείριση των εξαρτήσεών σας στην PHP. Μας επιτρέπει να δηλώσουμε εξαρτήσεις βιβλιοθηκών και θα τις εγκαταστήσει για εμάς, στο έργο μας. +Ο [Composer |Composer] είναι ένα εργαλείο διαχείρισης εξαρτήσεων στο PHP. Μας επιτρέπει να δηλώνουμε αυθαίρετα πολύπλοκες εξαρτήσεις μεμονωμένων βιβλιοθηκών και στη συνέχεια τις εγκαθιστά για εμάς στο έργο μας. -Έλεγχος απαιτήσεων .[#toc-requirements-checker] -=============================================== +Requirements Checker +==================== -Ήταν ένα εργαλείο που εξέταζε το περιβάλλον λειτουργίας του διακομιστή και ενημέρωνε για το αν (και σε ποιο βαθμό) το πλαίσιο μπορούσε να χρησιμοποιηθεί. Επί του παρόντος, το Nette μπορεί να χρησιμοποιηθεί σε οποιονδήποτε διακομιστή που διαθέτει την ελάχιστη απαιτούμενη έκδοση της PHP. - -{{sitename: Best Practices}} +Ήταν ένα εργαλείο που δοκίμαζε το περιβάλλον εκτέλεσης του server και ενημέρωνε αν (και σε ποιο βαθμό) ήταν δυνατό να χρησιμοποιηθεί το framework. Επί του παρόντος, το Nette μπορεί να χρησιμοποιηθεί σε κάθε server που έχει την ελάχιστη απαιτούμενη έκδοση PHP. diff --git a/best-practices/el/form-reuse.texy b/best-practices/el/form-reuse.texy index ec91ec79e1..28f0bf1b38 100644 --- a/best-practices/el/form-reuse.texy +++ b/best-practices/el/form-reuse.texy @@ -1,16 +1,16 @@ -Επαναχρησιμοποίηση φορμών σε πολλαπλά σημεία -******************************************** +Επαναχρησιμοποίηση φορμών σε πολλαπλά μέρη +****************************************** .[perex] -Στη Nette, έχετε αρκετές επιλογές για να επαναχρησιμοποιήσετε την ίδια φόρμα σε πολλά σημεία χωρίς να αντιγράψετε κώδικα. Σε αυτό το άρθρο, θα εξετάσουμε τις διάφορες λύσεις, συμπεριλαμβανομένων αυτών που πρέπει να αποφύγετε. +Στο Nette έχετε στη διάθεσή σας αρκετές επιλογές για να χρησιμοποιήσετε την ίδια φόρμα σε πολλαπλά μέρη και να μην επαναλαμβάνετε τον κώδικα. Σε αυτό το άρθρο θα δείξουμε διάφορες λύσεις, συμπεριλαμβανομένων εκείνων που θα έπρεπε να αποφύγετε. -Εργοστάσιο φορμών .[#toc-form-factory] -====================================== +Factory φορμών +============== -Μια βασική προσέγγιση για τη χρήση του ίδιου συστατικού σε πολλά σημεία είναι η δημιουργία μιας μεθόδου ή κλάσης που παράγει το συστατικό και στη συνέχεια η κλήση αυτής της μεθόδου σε διαφορετικά σημεία της εφαρμογής. Μια τέτοια μέθοδος ή κλάση ονομάζεται *factory*. Μην συγχέετε με το πρότυπο σχεδίασης *factory method*, το οποίο περιγράφει έναν συγκεκριμένο τρόπο χρήσης των εργοστασίων και δεν σχετίζεται με αυτό το θέμα. +Μία από τις βασικές προσεγγίσεις για τη χρήση του ίδιου component σε πολλαπλά μέρη είναι η δημιουργία μιας μεθόδου ή κλάσης που παράγει αυτό το component, και στη συνέχεια η κλήση αυτής της μεθόδου σε διάφορα μέρη της εφαρμογής. Μια τέτοια μέθοδος ή κλάση ονομάζεται *factory*. Μην τη συγχέετε με το design pattern *factory method*, το οποίο περιγράφει έναν συγκεκριμένο τρόπο χρήσης των factories και δεν σχετίζεται με αυτό το θέμα. -Για παράδειγμα, ας δημιουργήσουμε ένα εργοστάσιο που θα δημιουργήσει μια φόρμα επεξεργασίας: +Ως παράδειγμα, θα δημιουργήσουμε ένα factory που θα κατασκευάζει μια φόρμα επεξεργασίας: ```php use Nette\Application\UI\Form; @@ -20,22 +20,22 @@ class FormFactory public function createEditForm(): Form { $form = new Form; - $form->addText('title', 'Title:'); - // πρόσθετα πεδία φόρμας προστίθενται εδώ - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Τίτλος:'); + // εδώ προστίθενται άλλα πεδία φόρμας + $form->addSubmit('send', 'Αποστολή'); return $form; } } ``` -Τώρα μπορείτε να χρησιμοποιήσετε αυτό το εργοστάσιο σε διάφορα σημεία της εφαρμογής σας, για παράδειγμα σε παρουσιαστές ή στοιχεία. Και το κάνουμε αυτό [ζητώντας το ως εξάρτηση |dependency-injection:passing-dependencies]. Έτσι, πρώτα, θα γράψουμε την κλάση στο αρχείο ρυθμίσεων: +Τώρα μπορείτε να χρησιμοποιήσετε αυτό το factory σε διάφορα μέρη της εφαρμογής σας, για παράδειγμα σε presenters ή components. Και αυτό γίνεται [ζητώντας το ως εξάρτηση |dependency-injection:passing-dependencies]. Πρώτα, λοιπόν, καταχωρούμε την κλάση στο αρχείο διαμόρφωσης: ```neon services: - FormFactory ``` -Και στη συνέχεια τη χρησιμοποιούμε στον παρουσιαστή: +Και στη συνέχεια τη χρησιμοποιούμε στον presenter: ```php @@ -50,14 +50,14 @@ class MyPresenter extends Nette\Application\UI\Presenter { $form = $this->formFactory->createEditForm(); $form->onSuccess[] = function () { - // επεξεργασία των δεδομένων που αποστέλλονται + // επεξεργασία των απεσταλμένων δεδομένων }; return $form; } } ``` -Μπορείτε να επεκτείνετε το εργοστάσιο φορμών με πρόσθετες μεθόδους για να δημιουργήσετε άλλους τύπους φορμών ανάλογα με την εφαρμογή σας. Και, φυσικά, μπορείτε να προσθέσετε μια μέθοδο που δημιουργεί μια βασική φόρμα χωρίς στοιχεία, την οποία θα χρησιμοποιούν οι άλλες μέθοδοι: +Μπορείτε να επεκτείνετε το factory φορμών με επιπλέον μεθόδους για τη δημιουργία άλλων τύπων φορμών ανάλογα με τις ανάγκες της εφαρμογής σας. Και φυσικά, μπορούμε να προσθέσουμε και μια μέθοδο που δημιουργεί μια βασική φόρμα χωρίς στοιχεία, και αυτή θα χρησιμοποιείται από τις άλλες μεθόδους: ```php class FormFactory @@ -71,21 +71,21 @@ class FormFactory public function createEditForm(): Form { $form = $this->createForm(); - $form->addText('title', 'Title:'); - // πρόσθετα πεδία φόρμας προστίθενται εδώ - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Τίτλος:'); + // εδώ προστίθενται άλλα πεδία φόρμας + $form->addSubmit('send', 'Αποστολή'); return $form; } } ``` -Η μέθοδος `createForm()` δεν κάνει τίποτα χρήσιμο ακόμα, αλλά αυτό θα αλλάξει γρήγορα. +Η μέθοδος `createForm()` προς το παρόν δεν κάνει τίποτα χρήσιμο, αλλά αυτό θα αλλάξει γρήγορα. -Εξαρτήσεις εργοστασίων .[#toc-factory-dependencies] -=================================================== +Εξαρτήσεις του factory +====================== -Με τον καιρό, θα γίνει προφανές ότι χρειαζόμαστε τις φόρμες να είναι πολύγλωσσες. Αυτό σημαίνει ότι πρέπει να δημιουργήσουμε έναν [μεταφραστή |forms:rendering#Translating] για όλες τις φόρμες. Για να το κάνουμε αυτό, τροποποιούμε την κλάση `FormFactory` ώστε να δέχεται το αντικείμενο `Translator` ως εξάρτηση στον κατασκευαστή και να το περνάει στη φόρμα: +Με τον καιρό θα φανεί ότι χρειαζόμαστε οι φόρμες να είναι πολυγλωσσικές. Αυτό σημαίνει ότι σε όλες τις φόρμες πρέπει να ορίσουμε τον λεγόμενο [translator |forms:rendering#Μετάφραση]. Για τον σκοπό αυτό, τροποποιούμε την κλάση `FormFactory` ώστε να δέχεται το αντικείμενο `Translator` ως εξάρτηση στον constructor, και το περνάμε στη φόρμα: ```php use Nette\Localization\Translator; @@ -104,18 +104,17 @@ class FormFactory return $form; } - //... + // ... } ``` -Δεδομένου ότι η μέθοδος `createForm()` καλείται και από άλλες μεθόδους που δημιουργούν συγκεκριμένες φόρμες, χρειάζεται να ορίσουμε τον μεταφραστή μόνο σε αυτή τη μέθοδο. Και τελειώσαμε. Δεν χρειάζεται να αλλάξουμε τον κώδικα του παρουσιαστή ή του συστατικού, πράγμα που είναι υπέροχο. +Επειδή η μέθοδος `createForm()` καλείται και από τις άλλες μεθόδους που δημιουργούν συγκεκριμένες φόρμες, αρκεί να ορίσουμε τον translator μόνο σε αυτήν. Και τελειώσαμε. Δεν χρειάζεται να αλλάξουμε τον κώδικα κανενός presenter ή component, πράγμα που είναι εξαιρετικό. -Περισσότερες εργοστασιακές κλάσεις .[#toc-more-factory-classes] -=============================================================== +Περισσότερες κλάσεις factory +============================ -Εναλλακτικά, μπορείτε να δημιουργήσετε πολλαπλές κλάσεις για κάθε φόρμα που θέλετε να χρησιμοποιήσετε στην εφαρμογή σας. -Αυτή η προσέγγιση μπορεί να αυξήσει την αναγνωσιμότητα του κώδικα και να διευκολύνει τη διαχείριση των φορμών. Αφήστε το αρχικό `FormFactory` για να δημιουργήσετε μόνο μια καθαρή φόρμα με βασικές ρυθμίσεις (για παράδειγμα, με υποστήριξη μετάφρασης) και δημιουργήστε ένα νέο εργοστάσιο `EditFormFactory` για τη φόρμα επεξεργασίας. +Εναλλακτικά, μπορείτε να δημιουργήσετε περισσότερες κλάσεις για κάθε φόρμα που θέλετε να χρησιμοποιήσετε στην εφαρμογή σας. Αυτή η προσέγγιση μπορεί να αυξήσει την αναγνωσιμότητα του κώδικα και να διευκολύνει τη διαχείριση των φορμών. Το αρχικό `FormFactory` θα το αφήσουμε να δημιουργεί μόνο μια καθαρή φόρμα με βασική διαμόρφωση (για παράδειγμα με υποστήριξη μεταφράσεων) και για τη φόρμα επεξεργασίας θα δημιουργήσουμε ένα νέο factory `EditFormFactory`. ```php class FormFactory @@ -134,7 +133,7 @@ class FormFactory } -// ✅ χρήση της σύνθεσης +// ✅ χρήση σύνθεσης class EditFormFactory { public function __construct( @@ -145,40 +144,39 @@ class EditFormFactory public function create(): Form { $form = $this->formFactory->create(); - // εδώ προστίθενται πρόσθετα πεδία φόρμας - $form->addSubmit('send', 'Save'); + // εδώ προστίθενται άλλα πεδία φόρμας + $form->addSubmit('send', 'Αποστολή'); return $form; } } ``` -Είναι πολύ σημαντικό ότι η σύνδεση μεταξύ των κλάσεων `FormFactory` και `EditFormFactory` υλοποιείται με σύνθεση και όχι με κληρονομικότητα αντικειμένων: +Είναι πολύ σημαντικό η σχέση μεταξύ των κλάσεων `FormFactory` και `EditFormFactory` να υλοποιείται με [σύνθεση |nette:introduction-to-object-oriented-programming#Σύνθεση], και όχι με [κληρονομικότητα αντικειμένων |nette:introduction-to-object-oriented-programming#Κληρονομικότητα]: ```php -// ⛔ ΟΧΙ! Η ΚΛΗΡΟΝΟΜΙΆ ΔΕΝ ΑΝΉΚΕΙ ΕΔΏ +// ⛔ ΟΧΙ ΕΤΣΙ! Η ΚΛΗΡΟΝΟΜΙΚΟΤΗΤΑ ΔΕΝ ΑΝΗΚΕΙ ΕΔΩ class EditFormFactory extends FormFactory { public function create(): Form { $form = parent::create(); - $form->addText('title', 'Title:'); - // πρόσθετα πεδία φόρμας προστίθενται εδώ - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Τίτλος:'); + // εδώ προστίθενται άλλα πεδία φόρμας + $form->addSubmit('send', 'Αποστολή'); return $form; } } ``` -Η χρήση κληρονομικότητας σε αυτή την περίπτωση θα ήταν εντελώς αντιπαραγωγική. Θα αντιμετωπίζατε προβλήματα πολύ γρήγορα. Για παράδειγμα, αν θέλατε να προσθέσετε παραμέτρους στη μέθοδο `create()`, η PHP θα ανέφερε ένα σφάλμα ότι η υπογραφή της ήταν διαφορετική από την υπογραφή του γονέα. -Ή όταν περνούσατε μια εξάρτηση στην κλάση `EditFormFactory` μέσω του κατασκευαστή. Αυτό θα προκαλούσε αυτό που ονομάζουμε [κόλαση του κατασκευαστή |dependency-injection:passing-dependencies#Constructor hell]. +Η χρήση κληρονομικότητας θα ήταν σε αυτή την περίπτωση εντελώς αντιπαραγωγική. Θα αντιμετωπίζατε προβλήματα πολύ γρήγορα. Για παράδειγμα, τη στιγμή που θα θέλατε να προσθέσετε παραμέτρους στη μέθοδο `create()`; η PHP θα ανέφερε σφάλμα ότι η υπογραφή της διαφέρει από την γονική. Ή κατά το πέρασμα εξαρτήσεων στην κλάση `EditFormFactory` μέσω του constructor. Θα προέκυπτε η κατάσταση που ονομάζουμε [constructor hell |dependency-injection:passing-dependencies#Constructor hell]. -Γενικά, είναι προτιμότερο να προτιμάτε τη σύνθεση από την κληρονομικότητα. +Γενικά, είναι καλύτερο να προτιμάτε τη [σύνθεση έναντι κληρονομικότητας |dependency-injection:faq#Γιατί προτιμάται η σύνθεση composition έναντι της κληρονομικότητας]. -Χειρισμός φόρμας .[#toc-form-handling] -====================================== +Χειρισμός φόρμας +================ -Ο χειριστής φόρμας που καλείται μετά από μια επιτυχή υποβολή μπορεί επίσης να είναι μέρος μιας εργοστασιακής κλάσης. Θα λειτουργεί περνώντας τα υποβληθέντα δεδομένα στο μοντέλο για επεξεργασία. Θα μεταβιβάζει τυχόν σφάλματα [πίσω |forms:validation#Processing Errors] στη φόρμα. Το μοντέλο στο ακόλουθο παράδειγμα αντιπροσωπεύεται από την κλάση `Facade`: +Ο χειρισμός της φόρμας, που καλείται μετά την επιτυχή υποβολή, μπορεί επίσης να είναι μέρος της κλάσης factory. Θα λειτουργεί έτσι ώστε να παραδίδει τα υποβληθέντα δεδομένα στο μοντέλο για επεξεργασία. Τυχόν σφάλματα θα τα [επιστρέψει |forms:validation#Σφάλματα κατά την Επεξεργασία] στη φόρμα. Το μοντέλο στο ακόλουθο παράδειγμα αντιπροσωπεύεται από την κλάση `Facade`: ```php class EditFormFactory @@ -192,9 +190,9 @@ class EditFormFactory public function create(): Form { $form = $this->formFactory->create(); - $form->addText('title', 'Title:'); - // πρόσθετα πεδία φόρμας προστίθενται εδώ - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Τίτλος:'); + // εδώ προστίθενται άλλα πεδία φόρμας + $form->addSubmit('send', 'Αποστολή'); $form->onSuccess[] = [$this, 'processForm']; return $form; } @@ -202,7 +200,7 @@ class EditFormFactory public function processForm(Form $form, array $data): void { try { - // επεξεργασία των υποβληθέντων δεδομένων + // επεξεργασία των απεσταλμένων δεδομένων $this->facade->process($data); } catch (AnyModelException $e) { @@ -212,7 +210,7 @@ class EditFormFactory } ``` -Αφήστε τον παρουσιαστή να χειριστεί ο ίδιος την ανακατεύθυνση. Θα προσθέσει έναν άλλο χειριστή στο συμβάν `onSuccess`, ο οποίος θα εκτελέσει την ανακατεύθυνση. Αυτό θα επιτρέψει τη χρήση της φόρμας σε διαφορετικούς παρουσιαστές και ο καθένας θα μπορεί να κάνει ανακατεύθυνση σε διαφορετική τοποθεσία. +Την ίδια την ανακατεύθυνση όμως θα την αφήσουμε στον presenter. Αυτός θα προσθέσει στο event `onSuccess` έναν επιπλέον handler που θα πραγματοποιήσει την ανακατεύθυνση. Χάρη σε αυτό, θα είναι δυνατό να χρησιμοποιηθεί η φόρμα σε διάφορους presenters και σε καθέναν να γίνει ανακατεύθυνση αλλού. ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -226,7 +224,7 @@ class MyPresenter extends Nette\Application\UI\Presenter { $form = $this->formFactory->create(); $form->onSuccess[] = function () { - $this->flashMessage('Záznam byl uložen'); + $this->flashMessage('Η εγγραφή αποθηκεύτηκε'); $this->redirect('Homepage:'); }; return $form; @@ -234,39 +232,38 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Αυτή η λύση εκμεταλλεύεται την ιδιότητα των φορμών ότι, όταν καλείται το `addError()` σε μια φόρμα ή το στοιχείο της, δεν καλείται ο επόμενος χειριστής `onSuccess`. +Αυτή η λύση εκμεταλλεύεται την ιδιότητα των φορμών ότι όταν καλείται το `addError()` πάνω στη φόρμα ή στα στοιχεία της, ο επόμενος handler `onSuccess` δεν καλείται πλέον. -Κληρονομικότητα από την κλάση Form .[#toc-inheriting-from-the-form-class] -========================================================================= +Κληρονομικότητα από την κλάση Form +================================== -Μια δομημένη φόρμα δεν πρέπει να είναι παιδί μιας φόρμας. Με άλλα λόγια, μην χρησιμοποιείτε αυτή τη λύση: +Η συναρμολογημένη φόρμα δεν πρέπει να είναι απόγονος της φόρμας. Με άλλα λόγια, μην χρησιμοποιείτε αυτή τη λύση: ```php -// ⛔ ΟΧΙ! Η ΚΛΗΡΟΝΟΜΙΆ ΔΕΝ ΑΝΉΚΕΙ ΕΔΏ +// ⛔ ΟΧΙ ΕΤΣΙ! Η ΚΛΗΡΟΝΟΜΙΚΟΤΗΤΑ ΔΕΝ ΑΝΗΚΕΙ ΕΔΩ class EditForm extends Form { public function __construct(Translator $translator) { parent::__construct(); - $form->addText('title', 'Title:'); - // πρόσθετα πεδία φόρμας προστίθενται εδώ - $form->addSubmit('send', 'Save'); - $form->setTranslator($translator); + $this->addText('title', 'Τίτλος:'); + // εδώ προστίθενται άλλα πεδία φόρμας + $this->addSubmit('send', 'Αποστολή'); + $this->setTranslator($translator); } } ``` -Αντί να δημιουργείτε τη φόρμα στον κατασκευαστή, χρησιμοποιήστε το εργοστάσιο. +Αντί να συναρμολογείτε τη φόρμα στον constructor, χρησιμοποιήστε ένα factory. -Είναι σημαντικό να συνειδητοποιήσετε ότι η κλάση `Form` είναι πρωτίστως ένα εργαλείο για τη συναρμολόγηση μιας φόρμας, δηλαδή ένας κατασκευαστής φόρμας. Και η συναρμολογημένη φόρμα μπορεί να θεωρηθεί το προϊόν της. Ωστόσο, το προϊόν δεν είναι μια ειδική περίπτωση του κατασκευαστή- δεν υπάρχει *είναι μια* σχέση μεταξύ τους, η οποία αποτελεί τη βάση της κληρονομικότητας. +Πρέπει να συνειδητοποιήσετε ότι η κλάση `Form` είναι πρωτίστως ένα εργαλείο για τη συναρμολόγηση μιας φόρμας, δηλαδή ένας *form builder*. Και η συναρμολογημένη φόρμα μπορεί να θεωρηθεί ως προϊόν της. Όμως το προϊόν δεν είναι μια ειδική περίπτωση του builder, δεν υπάρχει μεταξύ τους σχέση *is a* που αποτελεί τη βάση της κληρονομικότητας. -Στοιχείο φόρμας .[#toc-form-component] -====================================== +Component με φόρμα +================== -Μια εντελώς διαφορετική προσέγγιση είναι η δημιουργία ενός [συστατικού |application:components] που περιλαμβάνει μια φόρμα. Αυτό δίνει νέες δυνατότητες, για παράδειγμα να αποδώσετε τη φόρμα με συγκεκριμένο τρόπο, αφού το συστατικό περιλαμβάνει ένα πρότυπο. -Ή μπορούν να χρησιμοποιηθούν σήματα για επικοινωνία AJAX και φόρτωση πληροφοριών στη φόρμα, για παράδειγμα για υποδείξεις κ.λπ. +Μια εντελώς διαφορετική προσέγγιση είναι η δημιουργία ενός [component |application:components], μέρος του οποίου είναι μια φόρμα. Αυτό δίνει νέες δυνατότητες, για παράδειγμα την απόδοση της φόρμας με συγκεκριμένο τρόπο, καθώς μέρος του component είναι και ένα template. Ή μπορεί να χρησιμοποιηθούν σήματα για επικοινωνία AJAX και φόρτωση πληροφοριών στη φόρμα, για παράδειγμα για προτάσεις, κ.λπ. ```php @@ -284,9 +281,9 @@ class EditControl extends Nette\Application\UI\Control protected function createComponentForm(): Form { $form = new Form; - $form->addText('title', 'Title:'); - // πρόσθετα πεδία φόρμας προστίθενται εδώ - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Τίτλος:'); + // εδώ προστίθενται άλλα πεδία φόρμας + $form->addSubmit('send', 'Αποστολή'); $form->onSuccess[] = [$this, 'processForm']; return $form; @@ -295,7 +292,7 @@ class EditControl extends Nette\Application\UI\Control public function processForm(Form $form, array $data): void { try { - // επεξεργασία των υποβληθέντων δεδομένων + // επεξεργασία των απεσταλμένων δεδομένων $this->facade->process($data); } catch (AnyModelException $e) { @@ -303,13 +300,13 @@ class EditControl extends Nette\Application\UI\Control return; } - // κλήση συμβάντος + // εκκίνηση του event $this->onSave($this, $data); } } ``` -Ας δημιουργήσουμε ένα εργοστάσιο που θα παράγει αυτό το στοιχείο. Αρκεί να [γράψουμε τη διεπαφή του |application:components#Components with Dependencies]: +Θα δημιουργήσουμε επίσης ένα factory που θα παράγει αυτό το component. Αρκεί να [καταχωρήσετε το interface του |application:components#Components με Εξαρτήσεις]: ```php interface EditControlFactory @@ -318,14 +315,14 @@ interface EditControlFactory } ``` -Και να το προσθέσουμε στο αρχείο ρυθμίσεων: +Και να το προσθέσουμε στο αρχείο διαμόρφωσης: ```neon services: - EditControlFactory ``` -Και τώρα μπορούμε να ζητήσουμε το εργοστάσιο και να το χρησιμοποιήσουμε στον παρουσιαστή: +Και τώρα μπορούμε ήδη να ζητήσουμε το factory και να το χρησιμοποιήσουμε στον presenter: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -335,19 +332,17 @@ class MyPresenter extends Nette\Application\UI\Presenter ) { } - protected function createComponentEditForm(): Form + protected function createComponentEditForm(): EditControl { $control = $this->controlFactory->create(); $control->onSave[] = function (EditControl $control, $data) { $this->redirect('this'); - // ή ανακατεύθυνση στο αποτέλεσμα της επεξεργασίας, π.χ.: - // $this->redirect('detail', ['id' => $data->id]), + // ή ανακατευθύνουμε στο αποτέλεσμα της επεξεργασίας, π.χ.: + // $this->redirect('detail', ['id' => $data->id]); }; return $control; } } ``` - -{{sitename: Best Practices}} diff --git a/best-practices/el/inject-method-attribute.texy b/best-practices/el/inject-method-attribute.texy index df67326bd8..a13bfcff48 100644 --- a/best-practices/el/inject-method-attribute.texy +++ b/best-practices/el/inject-method-attribute.texy @@ -1,21 +1,18 @@ -Μέθοδοι και χαρακτηριστικά Inject -********************************* +Μέθοδοι και attributes inject +***************************** .[perex] -Σε αυτό το άρθρο, θα επικεντρωθούμε σε διάφορους τρόπους μετάδοσης εξαρτήσεων σε παρουσιαστές στο πλαίσιο Nette. Θα συγκρίνουμε την προτιμώμενη μέθοδο, που είναι ο κατασκευαστής, με άλλες επιλογές, όπως οι μέθοδοι `inject` και τα χαρακτηριστικά. +Σε αυτό το άρθρο, θα επικεντρωθούμε στους διάφορους τρόπους περάσματος εξαρτήσεων στους presenters στο Nette framework. Θα συγκρίνουμε τον προτιμώμενο τρόπο, που είναι ο constructor, με άλλες επιλογές, όπως οι μέθοδοι και τα attributes `inject`. -Και για τους παρουσιαστές, το πέρασμα εξαρτήσεων με τη χρήση του [κατασκευαστή |dependency-injection:passing-dependencies#Constructor Injection] είναι ο προτιμώμενος τρόπος. -Ωστόσο, αν δημιουργήσετε έναν κοινό πρόγονο από τον οποίο κληρονομούν άλλοι παρουσιαστές (π.χ. BasePresenter) και αυτός ο πρόγονος έχει επίσης εξαρτήσεις, προκύπτει ένα πρόβλημα, το οποίο ονομάζουμε [κόλαση του κατασκευαστή |dependency-injection:passing-dependencies#Constructor hell]. -Αυτό μπορεί να παρακαμφθεί με τη χρήση εναλλακτικών μεθόδων, οι οποίες περιλαμβάνουν μεθόδους inject και χαρακτηριστικά (annotations). +Και για τους presenters ισχύει ότι το πέρασμα εξαρτήσεων μέσω του [constructor |dependency-injection:passing-dependencies#Παράδοση μέσω κατασκευαστή] είναι ο προτιμώμενος δρόμος. Αν όμως δημιουργείτε έναν κοινό πρόγονο από τον οποίο κληρονομούν άλλοι presenters (π.χ. `BasePresenter`), και αυτός ο πρόγονος έχει επίσης εξαρτήσεις, προκύπτει ένα πρόβλημα που ονομάζουμε [constructor hell |dependency-injection:passing-dependencies#Constructor hell]. Αυτό μπορεί να παρακαμφθεί χρησιμοποιώντας εναλλακτικούς δρόμους, που αντιπροσωπεύουν οι μέθοδοι και τα attributes (annotations) `inject`. -`inject*()` Μέθοδοι .[#toc-inject-methods] -========================================== +Μέθοδοι `inject*()` +=================== -Πρόκειται για μια μορφή μεταβίβασης εξαρτήσεων με χρήση [ρυθμιστών |dependency-injection:passing-dependencies#Setter Injection]. Τα ονόματα αυτών των ρυθμιστών αρχίζουν με το πρόθεμα inject. -Η Nette DI καλεί αυτόματα τέτοιες ονομαστικές μεθόδους αμέσως μετά τη δημιουργία της περίπτωσης παρουσιαστή και τους μεταβιβάζει όλες τις απαιτούμενες εξαρτήσεις. Επομένως, πρέπει να δηλωθούν ως δημόσιες. +Πρόκειται για μια μορφή περάσματος εξάρτησης με [setter |dependency-injection:passing-dependencies#Παράδοση μέσω setter]. Το όνομα αυτών των setters ξεκινά με το πρόθεμα `inject`. Το Nette DI καλεί αυτόματα τις μεθόδους με αυτό το όνομα αμέσως μετά τη δημιουργία της παρουσίας του presenter και τους περνά όλες τις απαιτούμενες εξαρτήσεις. Πρέπει επομένως να δηλώνονται ως public. -`inject*()` μέθοδοι μπορούν να θεωρηθούν ως ένα είδος επέκτασης του κατασκευαστή σε πολλαπλές μεθόδους. Χάρη σε αυτό, το `BasePresenter` μπορεί να λάβει εξαρτήσεις μέσω μιας άλλης μεθόδου και να αφήσει τον κατασκευαστή ελεύθερο για τους απογόνους του: +Οι μέθοδοι `inject*()` μπορούν να θεωρηθούν ως ένα είδος επέκτασης του constructor σε περισσότερες μεθόδους. Χάρη σε αυτό, ο `BasePresenter` μπορεί να λάβει εξαρτήσεις μέσω μιας άλλης μεθόδου και να αφήσει τον constructor ελεύθερο για τους απογόνους του: ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -39,18 +36,18 @@ class MyPresenter extends BasePresenter } ``` -Ο παρουσιαστής μπορεί να περιέχει οποιονδήποτε αριθμό μεθόδων `inject*()` και κάθε μία από αυτές μπορεί να έχει οποιονδήποτε αριθμό παραμέτρων. Αυτό είναι επίσης εξαιρετικό για περιπτώσεις όπου ο παρουσιαστής [αποτελείται από γνωρίσματα |presenter-traits] και κάθε ένα από αυτά απαιτεί τη δική του εξάρτηση. +Ο presenter μπορεί να περιέχει οποιονδήποτε αριθμό μεθόδων `inject*()` και καθεμία μπορεί να έχει οποιονδήποτε αριθμό παραμέτρων. Ταιριάζουν εξαιρετικά επίσης σε περιπτώσεις όπου ο presenter [αποτελείται από traits |presenter-traits] και καθεμία από αυτές απαιτεί τη δική της εξάρτηση. -`Inject` Χαρακτηριστικά .[#toc-inject-attributes] -================================================= +Attributes `Inject` +=================== -Αυτή είναι μια μορφή [έγχυσης σε ιδιότητες |dependency-injection:passing-dependencies#Property Injection]. Αρκεί να υποδείξετε ποιες ιδιότητες πρέπει να εγχυθούν, και το Nette DI περνάει αυτόματα τις εξαρτήσεις αμέσως μετά τη δημιουργία της παρουσιαζόμενης περίπτωσης. Για την εισαγωγή τους, είναι απαραίτητο να δηλωθούν ως δημόσιες. +Πρόκειται για μια μορφή [injection στην ιδιότητα |dependency-injection:passing-dependencies#Ρύθμιση μεταβλητής]. Αρκεί να επισημάνετε σε ποιες μεταβλητές πρέπει να γίνει inject, και το Nette DI περνά αυτόματα τις εξαρτήσεις αμέσως μετά τη δημιουργία της παρουσίας του presenter. Για να μπορέσει να τις εισαγάγει, είναι απαραίτητο να δηλώνονται ως public. -Οι ιδιότητες επισημαίνονται με μια ιδιότητα: (προηγουμένως, χρησιμοποιούνταν ο σχολιασμός `/** @inject */`) +Επισημαίνουμε τις properties με το attribute: (παλαιότερα χρησιμοποιούνταν η annotation `/** @inject */`) ```php -use Nette\DI\Attributes\Inject; // αυτή η γραμμή είναι σημαντική +use Nette\DI\Attributes\Inject; // αυτή η γραμμή είναι σημαντική class MyPresenter extends Nette\Application\UI\Presenter { @@ -59,9 +56,6 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Το πλεονέκτημα αυτής της μεθόδου μετάδοσης εξαρτήσεων ήταν η πολύ οικονομική μορφή συμβολισμού της. Ωστόσο, με την εισαγωγή της [προώθησης των ιδιοτήτων του κατασκευαστή |https://blog.nette.org/el/php-8-0-pleres-episkopese-ton-neon#toc-constructor-property-promotion], η χρήση του κατασκευαστή φαίνεται ευκολότερη. +Το πλεονέκτημα αυτού του τρόπου περάσματος εξαρτήσεων ήταν η πολύ οικονομική μορφή γραφής. Ωστόσο, με την έλευση του [constructor property promotion |https://blog.nette.org/el/php-8-0-complete-overview-of-news#toc-constructor-property-promotion], φαίνεται ευκολότερο να χρησιμοποιηθεί ο constructor. -Από την άλλη πλευρά, η μέθοδος αυτή πάσχει από τις ίδιες αδυναμίες με τη μεταβίβαση εξαρτήσεων σε ιδιότητες γενικά: δεν έχουμε κανέναν έλεγχο των αλλαγών στη μεταβλητή και ταυτόχρονα, η μεταβλητή γίνεται μέρος της δημόσιας διεπαφής της κλάσης, πράγμα ανεπιθύμητο. - - -{{sitename: Best Practices}} +Αντίθετα, αυτός ο τρόπος πάσχει από τις ίδιες αδυναμίες με το πέρασμα εξαρτήσεων σε properties γενικά: δεν έχουμε έλεγχο στις αλλαγές στη μεταβλητή και ταυτόχρονα η μεταβλητή γίνεται μέρος του δημόσιου interface της κλάσης, πράγμα που είναι ανεπιθύμητο. diff --git a/best-practices/el/lets-create-contact-form.texy b/best-practices/el/lets-create-contact-form.texy index aeded4979a..fa4e52cc69 100644 --- a/best-practices/el/lets-create-contact-form.texy +++ b/best-practices/el/lets-create-contact-form.texy @@ -1,12 +1,12 @@ -Ας δημιουργήσουμε μια φόρμα επικοινωνίας -**************************************** +Δημιουργούμε μια φόρμα επικοινωνίας +*********************************** .[perex] -Ας δούμε πώς μπορείτε να δημιουργήσετε μια φόρμα επικοινωνίας στη Nette, συμπεριλαμβανομένης της αποστολής της σε ένα email. Ας το κάνουμε λοιπόν! +Θα δούμε πώς να δημιουργήσουμε μια φόρμα επικοινωνίας στο Nette, συμπεριλαμβανομένης της αποστολής μέσω email. Ας ξεκινήσουμε λοιπόν! -Πρώτα πρέπει να δημιουργήσουμε ένα νέο έργο. Όπως εξηγεί η σελίδα [Getting Started |nette:installation]. Και στη συνέχεια μπορούμε να ξεκινήσουμε τη δημιουργία της φόρμας. +Πρώτα πρέπει να δημιουργήσουμε ένα νέο έργο. Πώς να το κάνετε αυτό εξηγείται στη σελίδα [Ξεκινώντας |nette:installation]. Και μετά μπορούμε ήδη να αρχίσουμε να δημιουργούμε τη φόρμα. -Ο ευκολότερος τρόπος είναι να δημιουργήσουμε τη [φόρμα απευθείας στο Presenter |forms:in-presenter]. Μπορούμε να χρησιμοποιήσουμε την προκατασκευασμένη διεύθυνση `HomePresenter`. Θα προσθέσουμε το στοιχείο `contactForm` που αντιπροσωπεύει τη φόρμα. Αυτό το κάνουμε γράφοντας τη μέθοδο `createComponentContactForm()` factory στον κώδικα που θα παράγει το συστατικό: +Ο ευκολότερος τρόπος είναι να δημιουργήσετε τη [φόρμα απευθείας στον presenter |forms:in-presenter]. Μπορούμε να χρησιμοποιήσουμε τον προετοιμασμένο `HomePresenter`. Σε αυτόν θα προσθέσουμε το component `contactForm` που αντιπροσωπεύει τη φόρμα. Θα το κάνουμε γράφοντας στον κώδικα τη μέθοδο factory `createComponentContactForm()`, η οποία θα κατασκευάσει το component: ```php use Nette\Application\UI\Form; @@ -17,37 +17,35 @@ class HomePresenter extends Presenter protected function createComponentContactForm(): Form { $form = new Form; - $form->addText('name', 'Name:') - ->setRequired('Enter your name'); + $form->addText('name', 'Όνομα:') + ->setRequired('Εισάγετε όνομα'); $form->addEmail('email', 'E-mail:') - ->setRequired('Enter your e-mail'); - $form->addTextarea('message', 'Message:') - ->setRequired('Enter message'); - $form->addSubmit('send', 'Send'); + ->setRequired('Εισάγετε e-mail'); + $form->addTextarea('message', 'Μήνυμα:') + ->setRequired('Εισάγετε μήνυμα'); + $form->addSubmit('send', 'Αποστολή'); $form->onSuccess[] = [$this, 'contactFormSucceeded']; return $form; } public function contactFormSucceeded(Form $form, $data): void { - // sending an email + // αποστολή email } } ``` -Όπως μπορείτε να δείτε, έχουμε δημιουργήσει δύο μεθόδους. Η πρώτη μέθοδος `createComponentContactForm()` δημιουργεί μια νέα φόρμα. Αυτή έχει πεδία για το όνομα, το email και το μήνυμα, τα οποία προσθέτουμε χρησιμοποιώντας τις μεθόδους `addText()`, `addEmail()` και `addTextArea()`. Προσθέσαμε επίσης ένα κουμπί για την υποβολή της φόρμας. -Τι γίνεται όμως αν ο χρήστης δεν συμπληρώσει κάποια πεδία; Σε αυτή την περίπτωση, θα πρέπει να τον ενημερώσουμε ότι πρόκειται για υποχρεωτικό πεδίο. Αυτό το κάναμε με τη μέθοδο `setRequired()`. -Τέλος, προσθέσαμε και ένα [συμβάν |nette:glossary#events] `onSuccess`, το οποίο ενεργοποιείται αν η φόρμα υποβληθεί με επιτυχία. Στην περίπτωσή μας, καλεί τη μέθοδο `contactFormSucceeded`, η οποία αναλαμβάνει την επεξεργασία της υποβληθείσας φόρμας. Θα το προσθέσουμε αυτό στον κώδικα σε λίγο. +Όπως βλέπετε, δημιουργήσαμε δύο μεθόδους. Η πρώτη μέθοδος `createComponentContactForm()` δημιουργεί μια νέα φόρμα. Αυτή έχει πεδία για όνομα, email και μήνυμα, τα οποία προσθέτουμε με τις μεθόδους `addText()`, `addEmail()` και `addTextArea()`. Προσθέσαμε επίσης ένα κουμπί για την αποστολή της φόρμας. Αλλά τι γίνεται αν ο χρήστης δεν συμπληρώσει κάποιο πεδίο; Σε αυτή την περίπτωση, θα πρέπει να τον ενημερώσουμε ότι είναι υποχρεωτικό πεδίο. Αυτό το πετύχαμε με τη μέθοδο `setRequired()`. Τέλος, προσθέσαμε επίσης το [event |nette:glossary#Events] `onSuccess`, το οποίο ενεργοποιείται εάν η φόρμα υποβληθεί επιτυχώς. Στην περίπτωσή μας, καλεί τη μέθοδο `contactFormSucceeded`, η οποία θα αναλάβει την επεξεργασία της υποβληθείσας φόρμας. Αυτό θα το συμπληρώσουμε στον κώδικα σε μια στιγμή. -Αφήστε το στοιχείο `contantForm` να αποδοθεί στο πρότυπο `templates/Home/default.latte`: +Το component `contactForm` θα το αφήσουμε να αποδοθεί στο template `Home/default.latte`: ```latte {block content} -<h1>Contant Form</h1> +<h1>Φόρμα επικοινωνίας</h1> {control contactForm} ``` -Για να στείλουμε το ίδιο το email, δημιουργούμε μια νέα κλάση με όνομα `ContactFacade` και την τοποθετούμε στο αρχείο `app/Model/ContactFacade.php`: +Για την ίδια την αποστολή του email θα δημιουργήσουμε μια νέα κλάση, την οποία θα ονομάσουμε `ContactFacade` και θα την τοποθετήσουμε στο αρχείο `app/Model/ContactFacade.php`: ```php <?php @@ -68,9 +66,9 @@ class ContactFacade public function sendMessage(string $email, string $name, string $message): void { $mail = new Message; - $mail->addTo('admin@example.com') // your email + $mail->addTo('admin@example.com') // το email σας ->setFrom($email, $name) - ->setSubject('Message from the contact form') + ->setSubject('Μήνυμα από τη φόρμα επικοινωνίας') ->setBody($message); $this->mailer->send($mail); @@ -78,9 +76,9 @@ class ContactFacade } ``` -Η μέθοδος `sendMessage()` θα δημιουργήσει και θα στείλει το email. Χρησιμοποιεί έναν λεγόμενο mailer για να το κάνει αυτό, τον οποίο περνάει ως εξάρτηση μέσω του κατασκευαστή. Διαβάστε περισσότερα σχετικά με την [αποστολή μηνυμάτων ηλεκτρονικού ταχυδρομείου |mail:]. +Η μέθοδος `sendMessage()` δημιουργεί και στέλνει το email. Χρησιμοποιεί για αυτό τον λεγόμενο mailer, τον οποίο λαμβάνει ως εξάρτηση μέσω του constructor. Διαβάστε περισσότερα για την [αποστολή emails |mail:]. -Τώρα, θα επιστρέψουμε στον παρουσιαστή και θα ολοκληρώσουμε τη μέθοδο `contactFormSucceeded()`. Καλεί τη μέθοδο `sendMessage()` της κλάσης `ContactFacade` και της περνάει τα δεδομένα της φόρμας. Και πώς παίρνουμε το αντικείμενο `ContactFacade`; Θα μας το περάσει ο κατασκευαστής: +Τώρα θα επιστρέψουμε στον presenter και θα ολοκληρώσουμε τη μέθοδο `contactFormSucceeded()`. Αυτή θα καλέσει τη μέθοδο `sendMessage()` της κλάσης `ContactFacade` και θα της παραδώσει τα δεδομένα από τη φόρμα. Και πώς θα αποκτήσουμε το αντικείμενο `ContactFacade`; Θα το ζητήσουμε να μας παραδοθεί μέσω του constructor: ```php use App\Model\ContactFacade; @@ -102,36 +100,36 @@ class HomePresenter extends Presenter public function contactFormSucceeded(stdClass $data): void { $this->facade->sendMessage($data->email, $data->name, $data->message); - $this->flashMessage('The message has been sent'); + $this->flashMessage('Το μήνυμα στάλθηκε'); $this->redirect('this'); } } ``` -Μετά την αποστολή του ηλεκτρονικού ταχυδρομείου, εμφανίζουμε στο χρήστη το λεγόμενο [flash message |application:components#flash-messages], επιβεβαιώνοντας ότι το μήνυμα έχει σταλεί, και στη συνέχεια ανακατευθύνουμε στην επόμενη σελίδα, έτσι ώστε να μην μπορεί να ξαναστείλει τη φόρμα χρησιμοποιώντας *refresh* στο πρόγραμμα περιήγησης. +Αφού σταλεί το email, θα εμφανίσουμε επίσης στον χρήστη ένα λεγόμενο [flash message |application:components#Flash Μηνύματα], επιβεβαιώνοντας ότι το μήνυμα στάλθηκε, και στη συνέχεια θα ανακατευθύνουμε σε άλλη σελίδα, ώστε να μην είναι δυνατή η επανειλημμένη αποστολή της φόρμας μέσω *refresh* στον browser. -Λοιπόν, αν όλα λειτουργούν, θα πρέπει να είστε σε θέση να στείλετε ένα μήνυμα ηλεκτρονικού ταχυδρομείου από τη φόρμα επικοινωνίας σας. Συγχαρητήρια! +Λοιπόν, και αν όλα λειτουργούν, θα πρέπει να μπορείτε να στείλετε email από τη φόρμα επικοινωνίας σας. Συγχαρητήρια! -Πρότυπο ηλεκτρονικού ταχυδρομείου HTML .[#toc-html-email-template] ------------------------------------------------------------------- +HTML template email +------------------- -Προς το παρόν, αποστέλλεται ένα email απλού κειμένου που περιέχει μόνο το μήνυμα που αποστέλλεται από τη φόρμα. Μπορούμε όμως να χρησιμοποιήσουμε HTML στο email και να το κάνουμε πιο ελκυστικό. Θα δημιουργήσουμε ένα πρότυπο για αυτό στο Latte, το οποίο θα αποθηκεύσουμε στο `app/Model/contactEmail.latte`: +Μέχρι στιγμής, αποστέλλεται ένα απλό email κειμένου που περιέχει μόνο το μήνυμα που στάλθηκε από τη φόρμα. Στο email όμως μπορούμε να χρησιμοποιήσουμε HTML και να κάνουμε την εμφάνισή του πιο ελκυστική. Θα δημιουργήσουμε γι' αυτό ένα template στο Latte, το οποίο θα γράψουμε στο `app/Model/contactEmail.latte`: ```latte <html> - <title>Message from the contact form + Μήνυμα από τη φόρμα επικοινωνίας -

    Name: {$name}

    +

    Όνομα: {$name}

    E-mail: {$email}

    -

    Message: {$message}

    +

    Μήνυμα: {$message}

    ``` -Μένει να τροποποιήσουμε το `ContactFacade` για να χρησιμοποιήσουμε αυτό το πρότυπο. Στον κατασκευαστή, ζητάμε την κλάση `LatteFactory`, η οποία μπορεί να παράγει το αντικείμενο `Latte\Engine`, ένα [πρότυπο απόδοσης Latte |latte:develop#how-to-render-a-template]. Χρησιμοποιούμε τη μέθοδο `renderToString()` για την απόδοση του προτύπου σε ένα αρχείο, η πρώτη παράμετρος είναι η διαδρομή προς το πρότυπο και η δεύτερη οι μεταβλητές. +Μένει να τροποποιήσουμε το `ContactFacade`, ώστε να χρησιμοποιεί αυτό το template. Στον constructor θα ζητήσουμε την κλάση `LatteFactory`, η οποία μπορεί να δημιουργήσει ένα αντικείμενο `Latte\Engine`, δηλαδή τον [Latte template renderer |latte:develop#Πώς να Αποδώσετε ένα Πρότυπο]. Με τη μέθοδο `renderToString()` θα αποδώσουμε το template σε αρχείο, η πρώτη παράμετρος είναι η διαδρομή προς το template και η δεύτερη είναι οι μεταβλητές. ```php namespace App\Model; @@ -158,7 +156,7 @@ class ContactFacade ]); $mail = new Message; - $mail->addTo('admin@example.com') // your email + $mail->addTo('admin@example.com') // το email σας ->setFrom($email, $name) ->setHtmlBody($body); @@ -167,15 +165,15 @@ class ContactFacade } ``` -Στη συνέχεια, περνάμε το παραγόμενο email HTML στη μέθοδο `setHtmlBody()` αντί του αρχικού `setBody()`. Επίσης, δεν χρειάζεται να καθορίσουμε το θέμα του email στο `setSubject()`, επειδή η βιβλιοθήκη το παίρνει από το στοιχείο `` στο πρότυπο. +Το παραγόμενο HTML email θα το παραδώσουμε στη συνέχεια στη μέθοδο `setHtmlBody()` αντί της αρχικής `setBody()`. Επίσης, δεν χρειάζεται να αναφέρουμε το θέμα του email στο `setSubject()`, επειδή η βιβλιοθήκη θα το πάρει από το στοιχείο `<title>` του template. -Διαμόρφωση του .[#toc-configuring] ----------------------------------- +Διαμόρφωση +---------- -Στον κώδικα της κλάσης `ContactFacade`, το email του διαχειριστή μας `admin@example.com` εξακολουθεί να είναι σκληρά κωδικοποιημένο. Θα ήταν καλύτερα να το μεταφέρουμε στο αρχείο ρυθμίσεων. Πώς να το κάνετε; +Στον κώδικα της κλάσης `ContactFacade` είναι ακόμα σκληρά κωδικοποιημένο το διαχειριστικό μας email `admin@example.com`. Θα ήταν καλύτερο να το μεταφέρουμε στο αρχείο διαμόρφωσης. Πώς να το κάνουμε αυτό; -Πρώτα, τροποποιούμε την κλάση `ContactFacade` και αντικαθιστούμε το αλφαριθμητικό email με μια μεταβλητή που περνάει από τον κατασκευαστή: +Πρώτα θα τροποποιήσουμε την κλάση `ContactFacade` και θα αντικαταστήσουμε το string με το email με μια μεταβλητή που παραδίδεται μέσω του constructor: ```php class ContactFacade @@ -199,28 +197,25 @@ class ContactFacade } ``` -Και το δεύτερο βήμα είναι να τοποθετήσουμε την τιμή αυτής της μεταβλητής στη διαμόρφωση. Στο αρχείο `app/config/services.neon` προσθέτουμε: +Και το δεύτερο βήμα είναι η αναφορά της τιμής αυτής της μεταβλητής στη διαμόρφωση. Στο αρχείο `app/config/services.neon` γράφουμε: ```neon services: - App\Model\ContactFacade(adminEmail: admin@example.com) ``` -Και αυτό είναι όλο. Αν υπάρχουν πολλά στοιχεία στην ενότητα `services` και νιώθετε ότι το email χάνεται ανάμεσά τους, μπορούμε να το κάνουμε μεταβλητή. Θα τροποποιήσουμε την καταχώρηση σε: +Και αυτό είναι όλο. Αν τα στοιχεία στην ενότητα `services` ήταν πολλά και είχατε την αίσθηση ότι το email χάνεται ανάμεσά τους, μπορούμε να το κάνουμε μεταβλητή. Τροποποιούμε την καταχώρηση σε: ```neon services: - App\Model\ContactFacade(adminEmail: %adminEmail%) ``` -Και θα ορίσουμε αυτή τη μεταβλητή στο αρχείο `app/config/common.neon`: +Και στο αρχείο `app/config/common.neon` ορίζουμε αυτή τη μεταβλητή: ```neon parameters: adminEmail: admin@example.com ``` -Και τελείωσε! - - -{{sitename: Best Practices}} +Και τελειώσαμε! diff --git a/best-practices/el/microsites.texy b/best-practices/el/microsites.texy new file mode 100644 index 0000000000..2224119362 --- /dev/null +++ b/best-practices/el/microsites.texy @@ -0,0 +1,63 @@ +Πώς να γράφετε μικρο-ιστοσελίδες +******************************** + +Φανταστείτε ότι χρειάζεστε να δημιουργήσετε γρήγορα μια μικρή ιστοσελίδα για την επερχόμενη εκδήλωση της εταιρείας σας. Πρέπει να είναι απλό, γρήγορο και χωρίς περιττές πολυπλοκότητες. Ίσως σκέφτεστε ότι για ένα τόσο μικρό έργο δεν χρειάζεστε ένα στιβαρό framework. Αλλά τι γίνεται αν η χρήση του Nette framework μπορεί να απλοποιήσει και να επιταχύνει θεμελιωδώς αυτή τη διαδικασία; + +Ακόμα και κατά τη δημιουργία απλών ιστοσελίδων, δεν θέλετε να εγκαταλείψετε την άνεση. Δεν θέλετε να εφευρίσκετε αυτό που έχει ήδη λυθεί μία φορά. Μείνετε ήσυχα τεμπέλης και αφήστε τον εαυτό σας να κακομάθει. Το Nette Framework μπορεί να χρησιμοποιηθεί εξαιρετικά και ως micro framework. + +Πώς μπορεί να μοιάζει ένα τέτοιο microsite; Για παράδειγμα, έτσι ώστε ολόκληρος ο κώδικας της ιστοσελίδας να τοποθετηθεί σε ένα μόνο αρχείο `index.php` στον δημόσιο φάκελο: + +```php +<?php + +require __DIR__ . '/../vendor/autoload.php'; + +$configurator = new Nette\Bootstrap\Configurator; +$configurator->enableTracy(__DIR__ . '/../log'); +$configurator->setTempDirectory(__DIR__ . '/../temp'); + +// δημιουργία DI container βάσει της διαμόρφωσης στο config.neon +$configurator->addConfig(__DIR__ . '/../app/config.neon'); +$container = $configurator->createContainer(); + +// ορίζουμε το routing +$router = new Nette\Application\Routers\RouteList; +$container->addService('router', $router); + +// route για το URL https://example.com/ +$router->addRoute('', function ($presenter, Nette\Http\Request $httpRequest) { + // ανιχνεύουμε τη γλώσσα του browser και ανακατευθύνουμε στο URL /en ή /de κ.λπ. + $supportedLangs = ['en', 'de', 'cs']; + $lang = $httpRequest->detectLanguage($supportedLangs) ?: reset($supportedLangs); + $presenter->redirectUrl("/$lang"); +}); + +// route για το URL https://example.com/cs ή https://example.com/en +$router->addRoute('<lang cs|en>', function ($presenter, string $lang) { + // εμφανίζουμε το αντίστοιχο template, για παράδειγμα ../templates/en.latte + $template = $presenter->createTemplate() + ->setFile(__DIR__ . '/../templates/' . $lang . '.latte'); + return $template; +}); + +// εκκίνηση της εφαρμογής! +$container->getByType(Nette\Application\Application::class)->run(); +``` + +Όλα τα υπόλοιπα θα είναι templates αποθηκευμένα στον γονικό φάκελο `/templates`. + +Ο κώδικας PHP στο `index.php` πρώτα [προετοιμάζει το περιβάλλον |bootstrap:], στη συνέχεια ορίζει τις [routes |application:routing#Δυναμική δρομολόγηση με callbacks] και τέλος εκκινεί την εφαρμογή. Το πλεονέκτημα είναι ότι η δεύτερη παράμετρος της συνάρτησης `addRoute()` μπορεί να είναι ένα callable, το οποίο εκτελείται μετά το άνοιγμα της αντίστοιχης σελίδας. + + +Γιατί να χρησιμοποιήσετε το Nette για microsite; +------------------------------------------------ + +- Οι προγραμματιστές που έχουν δοκιμάσει ποτέ το [Tracy |tracy:] δεν μπορούν σήμερα να φανταστούν ότι θα προγραμματίσουν κάτι χωρίς αυτό. +- Πάνω απ' όλα, όμως, θα χρησιμοποιήσετε το σύστημα προτύπων [Latte |latte:], επειδή ήδη από 2 σελίδες θα θέλετε να έχετε ξεχωριστή [διάταξη και περιεχόμενο |latte:template-inheritance]. +- Και σίγουρα θέλετε να βασιστείτε στο [αυτόματο escaping |latte:safety-first], ώστε να μην προκύψει ευπάθεια XSS +- Το Nette επίσης εξασφαλίζει ότι σε περίπτωση σφάλματος δεν θα εμφανιστούν ποτέ τα μηνύματα σφαλμάτων PHP για προγραμματιστές, αλλά μια κατανοητή σελίδα για τον χρήστη. +- Αν θέλετε να λαμβάνετε ανατροφοδότηση από τους χρήστες, για παράδειγμα με τη μορφή μιας φόρμας επικοινωνίας, τότε θα προσθέσετε επίσης [φόρμες |forms:] και [βάση δεδομένων |database:]. +- Μπορείτε επίσης εύκολα να [στείλετε μέσω email |mail:] τις συμπληρωμένες φόρμες. +- Μερικές φορές μπορεί να σας φανεί χρήσιμο το [caching |caching:], για παράδειγμα αν κατεβάζετε και εμφανίζετε feeds. + +Στη σημερινή εποχή, όπου η ταχύτητα και η αποτελεσματικότητα είναι καθοριστικής σημασίας, είναι σημαντικό να έχετε εργαλεία που σας επιτρέπουν να επιτύχετε αποτελέσματα χωρίς περιττές καθυστερήσεις. Το Nette framework σας προσφέρει ακριβώς αυτό - γρήγορη ανάπτυξη, ασφάλεια και ένα ευρύ φάσμα εργαλείων, όπως το Tracy και το Latte, που απλοποιούν τη διαδικασία. Αρκεί να εγκαταστήσετε μερικά πακέτα Nette και η κατασκευή ενός τέτοιου microsite γίνεται ξαφνικά παιχνιδάκι. Και ξέρετε ότι πουθενά δεν κρύβεται καμία τρύπα ασφαλείας. diff --git a/best-practices/el/pagination.texy b/best-practices/el/pagination.texy index 3003b38a84..cdefb763e4 100644 --- a/best-practices/el/pagination.texy +++ b/best-practices/el/pagination.texy @@ -1,17 +1,16 @@ -Σελιδοποίηση αποτελεσμάτων βάσης δεδομένων -****************************************** +Σελίδωση αποτελεσμάτων βάσης δεδομένων +************************************** .[perex] -Κατά την ανάπτυξη εφαρμογών ιστού, συχνά συναντάτε την απαίτηση να εκτυπώσετε έναν περιορισμένο αριθμό εγγραφών σε μια σελίδα. +Κατά τη δημιουργία web εφαρμογών, πολύ συχνά θα συναντήσετε την απαίτηση για περιορισμό του αριθμού των εμφανιζόμενων στοιχείων ανά σελίδα. -Βγαίνουμε από την κατάσταση όταν παραθέτουμε όλα τα δεδομένα χωρίς σελιδοποίηση. Για την επιλογή δεδομένων από τη βάση δεδομένων, έχουμε την κλάση ArticleRepository, η οποία περιέχει τον κατασκευαστή και τη μέθοδο `findPublishedArticles`, η οποία επιστρέφει όλα τα δημοσιευμένα άρθρα ταξινομημένα κατά φθίνουσα σειρά ημερομηνίας δημοσίευσης. +Θα ξεκινήσουμε από την κατάσταση όπου εμφανίζουμε όλα τα δεδομένα χωρίς σελίδωση. Για την επιλογή δεδομένων από τη βάση δεδομένων έχουμε την κλάση ArticleRepository, η οποία εκτός από τον constructor περιέχει τη μέθοδο `findPublishedArticles`, η οποία επιστρέφει όλα τα δημοσιευμένα άρθρα ταξινομημένα φθίνοντα κατά ημερομηνία δημοσίευσης. ```php namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -31,10 +30,10 @@ class ArticleRepository } ``` -Στον Presenter στη συνέχεια εισάγουμε την κλάση model και στη μέθοδο render θα ζητήσουμε τα δημοσιευμένα άρθρα που θα περάσουμε στο template: +Στον presenter, στη συνέχεια, κάνουμε inject την κλάση του μοντέλου και στη μέθοδο render ζητάμε τα δημοσιευμένα άρθρα, τα οποία περνάμε στο template: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -53,11 +52,11 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -Στο πρότυπο, θα φροντίσουμε για την απόδοση μιας λίστας άρθρων: +Στο template `default.latte` φροντίζουμε στη συνέχεια για την εμφάνιση των άρθρων: ```latte {block content} -<h1>Articles</h1> +<h1>Άρθρα</h1> <div class="articles"> {foreach $articles as $article} @@ -68,11 +67,11 @@ class HomePresenter extends Nette\Application\UI\Presenter ``` -Με αυτόν τον τρόπο, μπορούμε να γράψουμε όλα τα άρθρα, αλλά αυτό θα προκαλέσει προβλήματα όταν ο αριθμός των άρθρων αυξηθεί. Σε εκείνο το σημείο, θα είναι χρήσιμο να υλοποιήσουμε τον μηχανισμό σελιδοποίησης. +Με αυτόν τον τρόπο μπορούμε να εμφανίσουμε όλα τα άρθρα, πράγμα που όμως αρχίζει να δημιουργεί προβλήματα τη στιγμή που ο αριθμός των άρθρων αυξάνεται. Σε εκείνη τη στιγμή, έρχεται βολική η υλοποίηση ενός μηχανισμού σελίδωσης. -Αυτό θα διασφαλίσει ότι όλα τα άρθρα θα χωρίζονται σε διάφορες σελίδες και θα εμφανίζουμε μόνο τα άρθρα μιας τρέχουσας σελίδας. Ο συνολικός αριθμός των σελίδων και η κατανομή των άρθρων υπολογίζεται από το ίδιο το [utils:Paginator], ανάλογα με το πόσα άρθρα έχουμε συνολικά και πόσα άρθρα θέλουμε να εμφανίσουμε στη σελίδα. +Αυτός εξασφαλίζει ότι όλα τα άρθρα θα χωριστούν σε αρκετές σελίδες και εμείς θα εμφανίσουμε μόνο τα άρθρα μιας τρέχουσας σελίδας. Τον συνολικό αριθμό σελίδων και τη διαίρεση των άρθρων θα τον υπολογίσει ο [Paginator |utils:Paginator] μόνος του ανάλογα με το πόσα άρθρα έχουμε συνολικά και πόσα άρθρα ανά σελίδα θέλουμε να εμφανίσουμε. -Στο πρώτο βήμα, θα τροποποιήσουμε τη μέθοδο για τη λήψη άρθρων στην κλάση repository ώστε να επιστρέφει μόνο άρθρα μιας σελίδας. Θα προσθέσουμε επίσης μια νέα μέθοδο για να πάρουμε το συνολικό αριθμό των άρθρων στη βάση δεδομένων, τον οποίο θα χρειαστούμε για να ορίσουμε ένα Paginator: +Στο πρώτο βήμα, θα τροποποιήσουμε τη μέθοδο για την απόκτηση άρθρων στην κλάση του repository έτσι ώστε να μπορεί να μας επιστρέφει μόνο άρθρα για μία σελίδα. Θα προσθέσουμε επίσης μια μέθοδο για τη διαπίστωση του συνολικού αριθμού άρθρων στη βάση δεδομένων, την οποία θα χρειαστούμε για τη ρύθμιση του Paginator: ```php namespace App\Model; @@ -100,7 +99,7 @@ class ArticleRepository } /** - * Returns the total number of published articles + * Επιστρέφει τον συνολικό αριθμό δημοσιευμένων άρθρων */ public function getPublishedArticlesCount(): int { @@ -109,12 +108,12 @@ class ArticleRepository } ``` -Το επόμενο βήμα είναι να επεξεργαστούμε τον παρουσιαστή. Θα προωθήσουμε τον αριθμό της τρέχουσας σελίδας που εμφανίζεται στη μέθοδο render. Στην περίπτωση που αυτός ο αριθμός δεν αποτελεί μέρος της διεύθυνσης URL, πρέπει να ορίσουμε την προεπιλεγμένη τιμή στην πρώτη σελίδα. +Στη συνέχεια, θα προχωρήσουμε στις τροποποιήσεις του presenter. Στη μέθοδο render θα περνάμε τον αριθμό της τρέχουσας εμφανιζόμενης σελίδας. Για την περίπτωση που αυτός ο αριθμός δεν θα είναι μέρος του URL, θα ορίσουμε την προεπιλεγμένη τιμή της πρώτης σελίδας. -Επεκτείνουμε επίσης τη μέθοδο render για να λάβουμε την περίπτωση Paginator, να τη ρυθμίσουμε και να επιλέξουμε τα σωστά άρθρα που θα εμφανίζονται στο πρότυπο. Το HomePresenter θα μοιάζει με αυτό: +Επίσης, θα επεκτείνουμε τη μέθοδο render με την απόκτηση της παρουσίας του Paginator, τη ρύθμισή του και την επιλογή των σωστών άρθρων για εμφάνιση στο template. Ο HomePresenter μετά τις τροποποιήσεις θα μοιάζει ως εξής: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -128,31 +127,31 @@ class HomePresenter extends Nette\Application\UI\Presenter public function renderDefault(int $page = 1): void { - // Θα βρούμε το συνολικό αριθμό των δημοσιευμένων άρθρων + // Θα διαπιστώσουμε τον συνολικό αριθμό δημοσιευμένων άρθρων $articlesCount = $this->articleRepository->getPublishedArticlesCount(); - // Θα δημιουργήσουμε την περίπτωση Paginator και θα τη ρυθμίσουμε + // Θα δημιουργήσουμε μια παρουσία του Paginator και θα τον ρυθμίσουμε $paginator = new Nette\Utils\Paginator; $paginator->setItemCount($articlesCount); // συνολικός αριθμός άρθρων - $paginator->setItemsPerPage(10); // στοιχεία ανά σελίδα - $paginator->setPage($page); // πραγματικός αριθμός σελίδων + $paginator->setItemsPerPage(10); // αριθμός στοιχείων ανά σελίδα + $paginator->setPage($page); // αριθμός τρέχουσας σελίδας - // Θα βρούμε ένα περιορισμένο σύνολο άρθρων από τη βάση δεδομένων με βάση τους υπολογισμούς του Paginator + // Από τη βάση δεδομένων θα τραβήξουμε ένα περιορισμένο σύνολο άρθρων σύμφωνα με τον υπολογισμό του Paginator $articles = $this->articleRepository->findPublishedArticles($paginator->getLength(), $paginator->getOffset()); - // τα οποία θα περάσουμε στο πρότυπο + // το οποίο θα περάσουμε στο template $this->template->articles = $articles; - // καθώς και στον ίδιο τον Paginator για να εμφανίσει τις επιλογές σελιδοποίησης + // και επίσης τον ίδιο τον Paginator για την εμφάνιση των επιλογών σελίδωσης $this->template->paginator = $paginator; } } ``` -Το πρότυπο επαναλαμβάνει ήδη τα άρθρα σε μία σελίδα, απλά προσθέστε συνδέσμους σελιδοποίησης: +Το template μας ήδη τώρα επαναλαμβάνεται μόνο πάνω στα άρθρα μιας σελίδας, αρκεί να προσθέσουμε τους συνδέσμους σελίδωσης: ```latte {block content} -<h1>Articles</h1> +<h1>Άρθρα</h1> <div class="articles"> {foreach $articles as $article} @@ -163,34 +162,33 @@ class HomePresenter extends Nette\Application\UI\Presenter <div class="pagination"> {if !$paginator->isFirst()} - <a n:href="default, 1">First</a> + <a n:href="default, 1">Πρώτη</a>  |  - <a n:href="default, $paginator->page-1">Previous</a> + <a n:href="default, $paginator->page-1">Προηγούμενη</a>  |  {/if} - Page {$paginator->getPage()} of {$paginator->getPageCount()} + Σελίδα {$paginator->getPage()} από {$paginator->getPageCount()} {if !$paginator->isLast()}  |  - <a n:href="default, $paginator->getPage() + 1">Next</a> + <a n:href="default, $paginator->getPage() + 1">Επόμενη</a>  |  - <a n:href="default, $paginator->getPageCount()">Last</a> + <a n:href="default, $paginator->getPageCount()">Τελευταία</a> {/if} </div> ``` -Αυτό είναι το πώς έχουμε προσθέσει σελιδοποίηση χρησιμοποιώντας Paginator. Εάν χρησιμοποιείται ο [Nette Database Explorer |database:explorer] αντί του [Nette Database |database:core] Core ως επίπεδο βάσης δεδομένων, είμαστε σε θέση να υλοποιήσουμε τη σελιδοποίηση ακόμη και χωρίς Paginator. Η κλάση `Nette\Database\Table\Selection` περιέχει τη μέθοδο [page |api:Nette\Database\Table\Selection::_ page] με λογική σελιδοποίησης που λαμβάνεται από το Paginator. +Έτσι συμπληρώσαμε τη σελίδα με τη δυνατότητα σελίδωσης χρησιμοποιώντας τον Paginator. Στην περίπτωση που αντί του [Nette Database Core |database:sql-way] ως επίπεδο βάσης δεδομένων χρησιμοποιήσουμε το [Nette Database Explorer |database:explorer], είμαστε σε θέση να υλοποιήσουμε τη σελίδωση και χωρίς τη χρήση του Paginator. Η κλάση `Nette\Database\Table\Selection` περιέχει τη μέθοδο [page |api:Nette\Database\Table\Selection::_page] με τη λογική σελίδωσης που έχει ληφθεί από τον Paginator. -Το αποθετήριο θα έχει την εξής μορφή: +Το repository με αυτόν τον τρόπο υλοποίησης θα μοιάζει ως εξής: ```php namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -198,7 +196,6 @@ class ArticleRepository ) { } - public function findPublishedArticles(): Nette\Database\Table\Selection { return $this->database->table('articles') @@ -208,10 +205,10 @@ class ArticleRepository } ``` -Δεν χρειάζεται να δημιουργήσουμε Paginator στο Presenter, αντίθετα θα χρησιμοποιήσουμε τη μέθοδο του αντικειμένου `Selection` που επιστρέφεται από το αποθετήριο: +Στον presenter δεν χρειάζεται να δημιουργήσουμε Paginator, θα χρησιμοποιήσουμε αντί γι' αυτόν τη μέθοδο της κλάσης `Selection`, την οποία μας επιστρέφει το repository: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -225,25 +222,25 @@ class HomePresenter extends Nette\Application\UI\Presenter public function renderDefault(int $page = 1): void { - // Θα βρούμε δημοσιευμένα άρθρα + // Θα τραβήξουμε τα δημοσιευμένα άρθρα $articles = $this->articleRepository->findPublishedArticles(); - // και το μέρος τους που περιορίζεται από τον υπολογισμό της μεθόδου σελίδας που θα περάσουμε στο πρότυπο + // και στο template θα στείλουμε μόνο το μέρος τους που περιορίζεται σύμφωνα με τον υπολογισμό της μεθόδου page $lastPage = 0; $this->template->articles = $articles->page($page, 10, $lastPage); - // καθώς και τα απαραίτητα δεδομένα για την εμφάνιση των επιλογών σελιδοποίησης + // και επίσης τα απαραίτητα δεδομένα για την εμφάνιση των επιλογών σελίδωσης $this->template->page = $page; $this->template->lastPage = $lastPage; } } ``` -Επειδή δεν χρησιμοποιούμε το Paginator, πρέπει να επεξεργαστούμε το τμήμα που εμφανίζει τους συνδέσμους σελιδοποίησης: +Επειδή στο template τώρα δεν στέλνουμε τον Paginator, θα τροποποιήσουμε το μέρος που εμφανίζει τους συνδέσμους σελίδωσης: ```latte {block content} -<h1>Articles</h1> +<h1>Άρθρα</h1> <div class="articles"> {foreach $articles as $article} @@ -254,24 +251,23 @@ class HomePresenter extends Nette\Application\UI\Presenter <div class="pagination"> {if $page > 1} - <a n:href="default, 1">First</a> + <a n:href="default, 1">Πρώτη</a>  |  - <a n:href="default, $page - 1">Previous</a> + <a n:href="default, $page - 1">Προηγούμενη</a>  |  {/if} - Page {$page} of {$lastPage} + Σελίδα {$page} από {$lastPage} {if $page < $lastPage}  |  - <a n:href="default, $page + 1">Next</a> + <a n:href="default, $page + 1">Επόμενη</a>  |  - <a n:href="default, $lastPage">Last</a> + <a n:href="default, $lastPage">Τελευταία</a> {/if} </div> ``` -Με αυτόν τον τρόπο, υλοποιήσαμε έναν μηχανισμό σελιδοποίησης χωρίς τη χρήση Paginator. +Με αυτόν τον τρόπο υλοποιήσαμε τον μηχανισμό σελίδωσης χωρίς τη χρήση του Paginator. {{priority: -1}} -{{sitename: Best Practices}} diff --git a/best-practices/el/passing-settings-to-presenters.texy b/best-practices/el/passing-settings-to-presenters.texy index 318c8aa47d..f600bbb498 100644 --- a/best-practices/el/passing-settings-to-presenters.texy +++ b/best-practices/el/passing-settings-to-presenters.texy @@ -1,10 +1,10 @@ -Μεταβίβαση ρυθμίσεων σε παρουσιαστές -************************************ +Πέρασμα ρυθμίσεων στους presenters +********************************** .[perex] -Χρειάζεται να περάσετε ορίσματα σε παρουσιαστές που δεν είναι αντικείμενα (π.χ. πληροφορίες σχετικά με το αν εκτελείται σε κατάσταση εντοπισμού σφαλμάτων, διαδρομές καταλόγων κ.λπ.) και συνεπώς δεν μπορούν να περάσουν αυτόματα από την αυτόματη σύνδεση; Η λύση είναι να τα ενθυλακώσετε σε ένα αντικείμενο `Settings`. +Χρειάζεστε να περάσετε ορίσματα στους presenters που δεν είναι αντικείμενα (π.χ. πληροφορία αν τρέχουν σε debug mode, διαδρομές προς καταλόγους κ.λπ.), και επομένως δεν μπορούν να περαστούν αυτόματα μέσω autowiring; Η λύση είναι να τα ενσωματώσετε σε ένα αντικείμενο `Settings`. -Η υπηρεσία `Settings` είναι ένας πολύ εύκολος αλλά και χρήσιμος τρόπος για να παρέχετε πληροφορίες σχετικά με μια εφαρμογή που εκτελείται στους παρουσιαστές. Η συγκεκριμένη μορφή της εξαρτάται εξ ολοκλήρου από τις ιδιαίτερες ανάγκες σας. Παράδειγμα: +Η υπηρεσία `Settings` αποτελεί έναν πολύ εύκολο και ταυτόχρονα χρήσιμο τρόπο παροχής πληροφοριών σχετικά με την τρέχουσα εφαρμογή στους presenters. Η συγκεκριμένη της μορφή εξαρτάται αποκλειστικά από τις δικές σας συγκεκριμένες ανάγκες. Παράδειγμα: ```php namespace App; @@ -12,7 +12,7 @@ namespace App; class Settings { public function __construct( - // από την PHP 8.1 είναι δυνατό να καθορίσετε readonly + // από την PHP 8.1 είναι δυνατό να δηλωθεί readonly public bool $debugMode, public string $appDir, // και ούτω καθεξής @@ -20,7 +20,7 @@ class Settings } ``` -Παράδειγμα εγγραφής στη διαμόρφωση: +Παράδειγμα καταχώρησης στη διαμόρφωση: ```neon services: @@ -30,7 +30,7 @@ services: ) ``` -Όταν ο παρουσιαστής χρειάζεται τις πληροφορίες που παρέχει αυτή η υπηρεσία, απλώς τις ζητάει στον κατασκευαστή: +Όταν ο presenter χρειαστεί τις πληροφορίες που παρέχονται από αυτή την υπηρεσία, απλά θα τη ζητήσει στον constructor: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -47,5 +47,3 @@ class MyPresenter extends Nette\Application\UI\Presenter } } ``` - -{{sitename: Best Practices}} diff --git a/best-practices/el/post-links.texy b/best-practices/el/post-links.texy new file mode 100644 index 0000000000..a6147e263a --- /dev/null +++ b/best-practices/el/post-links.texy @@ -0,0 +1,56 @@ +Πώς να χρησιμοποιείτε σωστά τους συνδέσμους POST +************************************************ + +.[perex] +Σε web εφαρμογές, ειδικά σε διαχειριστικά interfaces, θα έπρεπε να είναι βασικός κανόνας ότι οι ενέργειες που αλλάζουν την κατάσταση του server δεν θα έπρεπε να εκτελούνται μέσω της μεθόδου HTTP GET. Όπως υποδηλώνει το όνομα της μεθόδου, η GET θα έπρεπε να χρησιμεύει μόνο για τη λήψη δεδομένων, όχι για την αλλαγή τους. Για ενέργειες όπως η διαγραφή εγγραφών, είναι προτιμότερη η χρήση της μεθόδου POST. Αν και η ιδανική θα ήταν η μέθοδος DELETE, αλλά αυτή δεν μπορεί να κληθεί χωρίς JavaScript, γι' αυτό ιστορικά χρησιμοποιείται η POST. + +Πώς να το κάνετε στην πράξη; Χρησιμοποιήστε αυτό το απλό κόλπο. Στην αρχή του template, δημιουργήστε μια βοηθητική φόρμα με το αναγνωριστικό `postForm`, την οποία στη συνέχεια θα χρησιμοποιήσετε για τα κουμπιά διαγραφής: + +```latte .{file:@layout.latte} +<form method="post" id="postForm"></form> +``` + +Χάρη σε αυτή τη φόρμα, μπορείτε αντί για τον κλασικό σύνδεσμο `<a>` να χρησιμοποιήσετε ένα κουμπί `<button>`, το οποίο μπορεί να διαμορφωθεί οπτικά ώστε να μοιάζει με συνηθισμένο σύνδεσμο. Για παράδειγμα, το CSS framework Bootstrap προσφέρει τις κλάσεις `btn btn-link` με τις οποίες επιτυγχάνετε το κουμπί να μην διαφέρει οπτικά από τους άλλους συνδέσμους. Με το attribute `form="postForm"` το συνδέουμε με την προετοιμασμένη φόρμα: + +```latte .{file:admin.latte} +<table> + <tr n:foreach="$posts as $post"> + <td>{$post->title}</td> + <td> + <button class="btn btn-link" form="postForm" formaction="{link delete $post->id}">delete</button> + <!-- instead of <a n:href="delete $post->id">delete</a> --> + </td> + </tr> +</table> +``` + +Κατά το κλικ στον σύνδεσμο, καλείται τώρα η ενέργεια `delete`. Για να διασφαλίσετε ότι τα αιτήματα θα γίνονται δεκτά μόνο μέσω της μεθόδου POST και από τον ίδιο τομέα (που είναι μια αποτελεσματική άμυνα κατά των επιθέσεων CSRF), χρησιμοποιήστε το attribute `#[Requires]`: + +```php .{file:AdminPresenter.php} +use Nette\Application\Attributes\Requires; + +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST', sameOrigin: true)] + public function actionDelete(int $id): void + { + $this->facade->deletePost($id); // υποθετικός κώδικας που διαγράφει την εγγραφή + $this->redirect('default'); + } +} +``` + +Το attribute υπάρχει από το Nette Application 3.2 και περισσότερα για τις δυνατότητές του θα μάθετε στη σελίδα [Πώς να χρησιμοποιήσετε το attribute #Requires |attribute-requires]. + +Αν αντί για την ενέργεια `actionDelete()` χρησιμοποιούσατε το σήμα `handleDelete()`, δεν είναι απαραίτητο να αναφέρετε `sameOrigin: true`, επειδή τα σήματα έχουν αυτή την προστασία ρυθμισμένη από προεπιλογή: + +```php .{file:AdminPresenter.php} +#[Requires(methods: 'POST')] +public function handleDelete(int $id): void +{ + $this->facade->deletePost($id); + $this->redirect('this'); +} +``` + +Αυτή η προσέγγιση όχι μόνο βελτιώνει την ασφάλεια της εφαρμογής σας, αλλά συμβάλλει επίσης στην τήρηση των σωστών web προτύπων και πρακτικών. Χρησιμοποιώντας τις μεθόδους POST για ενέργειες που αλλάζουν την κατάσταση, επιτυγχάνετε μια πιο στιβαρή και ασφαλή εφαρμογή. diff --git a/best-practices/el/presenter-traits.texy b/best-practices/el/presenter-traits.texy index dcfda68d36..b5c9e24f28 100644 --- a/best-practices/el/presenter-traits.texy +++ b/best-practices/el/presenter-traits.texy @@ -1,14 +1,14 @@ -Σύνθεση παρουσιαστών από γνωρίσματα -*********************************** +Σύνθεση presenters από traits +***************************** .[perex] -Αν πρέπει να υλοποιήσουμε τον ίδιο κώδικα σε πολλούς παρουσιαστές (π.χ. επαλήθευση ότι ο χρήστης είναι συνδεδεμένος), είναι δελεαστικό να τοποθετήσουμε τον κώδικα σε έναν κοινό πρόγονο. Η δεύτερη επιλογή είναι να δημιουργήσουμε γνωρίσματα ενός σκοπού. +Αν χρειαζόμαστε να υλοποιήσουμε τον ίδιο κώδικα σε περισσότερους presenters (π.χ. έλεγχος ότι ο χρήστης είναι συνδεδεμένος), προσφέρεται η τοποθέτηση του κώδικα σε έναν κοινό πρόγονο. Η δεύτερη δυνατότητα είναι η δημιουργία μονοσκοπικών [traits |nette:introduction-to-object-oriented-programming#Traits]. -Το πλεονέκτημα αυτής της λύσης είναι ότι κάθε παρουσιαστής μπορεί να χρησιμοποιήσει μόνο τα γνωρίσματα που πραγματικά χρειάζεται, ενώ η πολλαπλή κληρονομικότητα δεν είναι δυνατή στην PHP. +Το πλεονέκτημα αυτής της λύσης είναι ότι καθένας από τους presenters μπορεί να χρησιμοποιήσει ακριβώς τα traits που πραγματικά χρειάζεται, ενώ η πολλαπλή κληρονομικότητα δεν είναι δυνατή στην PHP. -Αυτά τα γνωρίσματα μπορούν να επωφεληθούν από το γεγονός ότι όλες οι [μέθοδοι inject |inject-method-attribute#inject methods] καλούνται διαδοχικά όταν δημιουργείται ο παρουσιαστής. Απλά πρέπει να βεβαιωθείτε ότι το όνομα κάθε μεθόδου inject είναι μοναδικό. +Αυτά τα traits μπορούν να εκμεταλλευτούν το γεγονός ότι κατά τη δημιουργία του presenter καλούνται διαδοχικά όλες οι [μέθοδοι inject |inject-method-attribute#Μέθοδοι inject]. Απλά πρέπει να διασφαλιστεί ότι το όνομα κάθε μεθόδου inject είναι μοναδικό. -Τα γνωρίσματα μπορούν να κρεμάσουν τον κώδικα αρχικοποίησης σε συμβάντα [onStartup ή onRender |application:presenters#Events]. +Τα traits μπορούν να επισυνάψουν κώδικα αρχικοποίησης στα events [onStartup ή onRender |application:presenters#Γεγονότα]. Παραδείγματα: @@ -36,7 +36,7 @@ trait StandardTemplateFilters } ``` -Ο παρουσιαστής στη συνέχεια απλά χρησιμοποιεί αυτά τα χαρακτηριστικά: +Ο presenter στη συνέχεια χρησιμοποιεί απλά αυτά τα traits: ```php class ArticlePresenter extends Nette\Application\UI\Presenter @@ -45,6 +45,3 @@ class ArticlePresenter extends Nette\Application\UI\Presenter use RequireLoggedUser; } ``` - - -{{sitename: Best Practices}} diff --git a/best-practices/el/restore-request.texy b/best-practices/el/restore-request.texy index 66883715a1..631a0c432b 100644 --- a/best-practices/el/restore-request.texy +++ b/best-practices/el/restore-request.texy @@ -1,17 +1,16 @@ -Πώς να επιστρέψω σε μια προηγούμενη σελίδα; -******************************************* +Πώς να επιστρέψετε σε προηγούμενη σελίδα; +***************************************** .[perex] -Τι γίνεται αν ένας χρήστης συμπληρώσει μια φόρμα και η σύνδεσή του λήξει; Για να μην χαθούν τα δεδομένα, αποθηκεύουμε τα δεδομένα στη σύνοδο πριν από την ανακατεύθυνση στη σελίδα σύνδεσης. Στη Nette, αυτό είναι πανεύκολο. +Τι γίνεται αν ο χρήστης συμπληρώνει μια φόρμα και η σύνδεσή του λήξει; Για να μην χάσει τα δεδομένα, πριν την ανακατεύθυνση στη σελίδα σύνδεσης, αποθηκεύουμε τα δεδομένα στο session. Στο Nette αυτό είναι παιχνιδάκι. -Η τρέχουσα αίτηση μπορεί να αποθηκευτεί στη σύνοδο χρησιμοποιώντας τη μέθοδο `storeRequest()`, η οποία επιστρέφει το αναγνωριστικό της ως σύντομο αλφαριθμητικό. Η μέθοδος αποθηκεύει το όνομα του τρέχοντος παρουσιαστή, την προβολή και τις παραμέτρους της. -Εάν υποβλήθηκε επίσης μια φόρμα, αποθηκεύονται επίσης οι τιμές των πεδίων (εκτός από τα αρχεία που έχουν μεταφορτωθεί). +Το τρέχον αίτημα μπορεί να αποθηκευτεί στο session χρησιμοποιώντας τη μέθοδο `storeRequest()`, η οποία επιστρέφει το αναγνωριστικό του με τη μορφή ενός σύντομου string. Η μέθοδος αποθηκεύει το όνομα του τρέχοντος presenter, την προβολή και τις παραμέτρους του. Σε περίπτωση που έχει υποβληθεί και φόρμα, αποθηκεύεται επίσης το περιεχόμενο των πεδίων (με εξαίρεση τα ανεβασμένα αρχεία). -Η αίτηση αποκαθίσταται από τη μέθοδο `restoreRequest($key)`, στην οποία περνάμε το ανακτηθέν αναγνωριστικό. Αυτή ανακατευθύνει στον αρχικό παρουσιαστή και στην αρχική προβολή. Ωστόσο, αν το αποθηκευμένο αίτημα περιέχει υποβολή φόρμας, θα προωθηθεί στον αρχικό παρουσιαστή με τη μέθοδο `forward()`, θα περάσει τις προηγουμένως συμπληρωμένες τιμές στη φόρμα και θα αφήσει να επανασχεδιαστεί. Αυτό επιτρέπει στον χρήστη να υποβάλει εκ νέου τη φόρμα και δεν χάνονται δεδομένα. +Η επαναφορά του αιτήματος γίνεται με τη μέθοδο `restoreRequest($key)`, στην οποία περνάμε το ληφθέν αναγνωριστικό. Αυτή ανακατευθύνει στον αρχικό presenter και προβολή. Αν όμως το αποθηκευμένο αίτημα περιέχει υποβολή φόρμας, μεταβαίνει στον αρχικό presenter με τη μέθοδο `forward()`, παραδίδει στη φόρμα τις προηγουμένως συμπληρωμένες τιμές και την αφήνει να αποδοθεί ξανά. Ο χρήστης έτσι έχει τη δυνατότητα να υποβάλει ξανά τη φόρμα και κανένα δεδομένο δεν χάνεται. -Είναι σημαντικό ότι το `restoreRequest()` ελέγχει ότι ο νεοεισερχόμενος χρήστης είναι ο ίδιος που συμπλήρωσε αρχικά τη φόρμα. Εάν όχι, απορρίπτει την αίτηση και δεν κάνει τίποτα. +Σημαντικό είναι ότι το `restoreRequest()` ελέγχει αν ο νέος συνδεδεμένος χρήστης είναι ο ίδιος που συμπλήρωσε αρχικά τη φόρμα. Αν όχι, απορρίπτει το αίτημα και δεν κάνει τίποτα. -Ας δείξουμε τα πάντα με ένα παράδειγμα. Ας έχουμε έναν παρουσιαστή `AdminPresenter` στον οποίο γίνεται επεξεργασία δεδομένων και του οποίου η μέθοδος `startup()` ελέγχει αν ο χρήστης είναι συνδεδεμένος. Αν δεν είναι, τον ανακατευθύνουμε στο `SignPresenter`. Ταυτόχρονα, αποθηκεύουμε την τρέχουσα αίτηση και στέλνουμε το κλειδί της στο `SignPresenter`. +Θα δείξουμε τα πάντα με ένα παράδειγμα. Έστω ότι έχουμε έναν presenter `AdminPresenter`, στον οποίο επεξεργαζόμαστε δεδομένα και στη μέθοδο `startup()` του οποίου ελέγχουμε αν ο χρήστης είναι συνδεδεμένος. Αν δεν είναι, τον ανακατευθύνουμε στον `SignPresenter`. Ταυτόχρονα αποθηκεύουμε το τρέχον αίτημα και στέλνουμε το κλειδί του στον `SignPresenter`. ```php class AdminPresenter extends Nette\Application\UI\Presenter @@ -27,7 +26,7 @@ class AdminPresenter extends Nette\Application\UI\Presenter } ``` -Ο παρουσιαστής `SignPresenter` θα περιέχει μια μόνιμη παράμετρο `$backlink` στην οποία γράφεται το κλειδί, εκτός από τη φόρμα σύνδεσης. Δεδομένου ότι η παράμετρος είναι μόνιμη, θα μεταφέρεται ακόμη και μετά την υποβολή της φόρμας σύνδεσης. +Ο presenter `SignPresenter` θα περιέχει εκτός από τη φόρμα σύνδεσης και μια persistent παράμετρο `$backlink`, στην οποία θα γραφτεί το κλειδί. Επειδή η παράμετρος είναι persistent, θα μεταφέρεται και μετά την υποβολή της φόρμας σύνδεσης. ```php @@ -41,14 +40,14 @@ class SignPresenter extends Nette\Application\UI\Presenter protected function createComponentSignInForm() { $form = new Nette\Application\UI\Form; - // ... προσθέστε πεδία φόρμας ... + // ... προσθέτουμε πεδία φόρμας ... $form->onSuccess[] = [$this, 'signInFormSubmitted']; return $form; } public function signInFormSubmitted($form) { - // ... εδώ υπογράφουμε την είσοδο του χρήστη ... + // ... εδώ συνδέουμε τον χρήστη ... $this->restoreRequest($this->backlink); $this->redirect('Admin:'); @@ -56,9 +55,8 @@ class SignPresenter extends Nette\Application\UI\Presenter } ``` -Περνάμε το κλειδί της αποθηκευμένης αίτησης στη μέθοδο `restoreRequest()` και αυτή ανακατευθύνει (ή προωθεί) στον αρχικό παρουσιαστή. +Στη μέθοδο `restoreRequest()` περνάμε το κλειδί του αποθηκευμένου αιτήματος και αυτή ανακατευθύνει (ή μεταβαίνει) στον αρχικό presenter. -Ωστόσο, αν το κλειδί είναι άκυρο (για παράδειγμα, δεν υπάρχει πλέον στη σύνοδο), η μέθοδος δεν κάνει τίποτα. Έτσι, η επόμενη κλήση είναι η `$this->redirect('Admin:')`, η οποία ανακατευθύνει στο `AdminPresenter`. +Αν όμως το κλειδί είναι άκυρο (για παράδειγμα δεν υπάρχει πλέον στο session), η μέθοδος δεν κάνει τίποτα. Ακολουθεί επομένως η κλήση `$this->redirect('Admin:')`, η οποία ανακατευθύνει στον `AdminPresenter`. {{priority: -1}} -{{sitename: Best Practices}} diff --git a/best-practices/en/@home.texy b/best-practices/en/@home.texy index 43f088951b..04508b55aa 100644 --- a/best-practices/en/@home.texy +++ b/best-practices/en/@home.texy @@ -1,8 +1,8 @@ -Best Practices -************** +Tutorials and Best Practices +**************************** .[perex] -Tutorials, solutions to common problems and best practices for Nette. +Tutorials, solutions to common tasks, and best practices for Nette. <div class=documentation> @@ -11,12 +11,14 @@ Tutorials, solutions to common problems and best practices for Nette. Nette Application ----------------- -- [Inject methods and attributes |inject-method-attribute] -- [Composing presenters from traits |presenter-traits] -- [Passing settings to presenters |passing-settings-to-presenters] -- [How to return to an earlier page |restore-request] -- [Paginating database results |Pagination] -- [Dynamic snippets |dynamic-snippets] +- [Inject Methods and Attributes |inject-method-attribute] +- [Composing Presenters from Traits |presenter-traits] +- [Passing Settings to Presenters |passing-settings-to-presenters] +- [How to Restore a Request |restore-request] +- [Paginating Database Results |pagination] +- [Dynamic Snippets |dynamic-snippets] +- [How to Use the #Requires Attribute |attribute-requires] +- [How to Properly Use POST Links |post-links] </div> <div> @@ -24,29 +26,31 @@ Nette Application Forms ----- -- [Reuse of forms |form-reuse] -- [Form for creating and editing record |creating-editing-form] +- [Reusing Forms |form-reuse] +- [Form for Creating and Editing Records |creating-editing-form] - [Let's Create a Contact Form |lets-create-contact-form] -- [Dependent selectboxes |https://blog.nette.org/en/dependent-selectboxes-elegantly-in-nette-and-pure-js] +- [Dependent Selectboxes |https://blog.nette.org/en/dependent-selectboxes-elegantly-in-nette-and-pure-js] </div> <div> -Common ------- -- [How to load configuration file |bootstrap:] -- [Why Nette uses PascalCase constant notation? |https://blog.nette.org/en/for-less-screaming-in-the-code] -- [Why Nette doesn't use the Interface suffix? |https://blog.nette.org/en/prefixes-and-suffixes-do-not-belong-in-interface-names] -- [Composer usage tips |composer] -- [Tips on editors & tools |editors-and-tools] +General +------- +- [How to Load a Configuration File |bootstrap:] +- [How to Write Microsites |microsites] +- [Why Does Nette Use PascalCase Notation for Constants? |https://blog.nette.org/en/for-less-screaming-in-the-code] +- [Why Doesn't Nette Use the Interface Suffix? |https://blog.nette.org/en/prefixes-and-suffixes-do-not-belong-in-interface-names] +- [Composer: Usage Tips |composer] +- [Tips for Editors & Tools |editors-and-tools] +- [Introduction to Object-Oriented Programming |nette:introduction-to-object-oriented-programming] </div> <div> -Sample Solution ---------------- +Example Solutions +----------------- - [Nette examples |https://github.com/nette-examples] - [Doctrine & Nette |https://contributte.org/nettrine/] - [Contributte examples |https://contributte.org/examples.html] @@ -59,10 +63,7 @@ Sample Solution Videos ------ -You can find hundreds of recordings from Posobota and videos about Nette under one roof on the "Nette Framework YouTube Channel":https://www.youtube.com/user/NetteFramework. +Hundreds of recordings from Last Saturday meetups and videos about Nette can be found all in one place on the "Nette Framework YouTube Channel":https://www.youtube.com/user/NetteFramework. </div> </div> - -{{sitename: Best Practices}} -{{leftbar: www:@menu-common}} diff --git a/best-practices/en/@meta.texy b/best-practices/en/@meta.texy new file mode 100644 index 0000000000..10c126830b --- /dev/null +++ b/best-practices/en/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Tutorials and Best Practices}} +{{leftbar: www:@menu-common}} diff --git a/best-practices/en/attribute-requires.texy b/best-practices/en/attribute-requires.texy new file mode 100644 index 0000000000..54da7db490 --- /dev/null +++ b/best-practices/en/attribute-requires.texy @@ -0,0 +1,177 @@ +How to Use the `#[Requires]` Attribute +************************************** + +.[perex] +When writing a web application, you often encounter the need to restrict access to certain parts of your application. Perhaps you want some requests to only be able to send data via a form (thus using the POST method) or to be accessible only to AJAX calls. In Nette Framework 3.2, a new tool has been introduced that allows you to set such restrictions elegantly and clearly: the `#[Requires]` attribute. + +An attribute is a special marker in PHP that you add before the definition of a class or method. Since it is essentially a class, you need to include the `use` clause to make the following examples work: + +```php +use Nette\Application\Attributes\Requires; +``` + +You can use the `#[Requires]` attribute with the presenter class itself and on these methods: + +- `action<Action>()` +- `render<View>()` +- `handle<Signal>()` +- `createComponent<Name>()` + +The last two methods also apply to components, so you can use the attribute with them as well. + +If the conditions specified by the attribute are not met, an HTTP 4xx error is triggered. + + +HTTP Methods +------------ + +You can specify which HTTP methods (such as GET, POST, etc.) are allowed for access. For example, if you want to allow access only by submitting a form, set: + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +Why should you use POST instead of GET for state-changing actions and how to do it? [Read the guide |post-links]. + +You can specify a method or an array of methods. A special case is the value `'*'`, which allows all methods, something presenters [do not allow by default for security reasons |application:presenters#HTTP Method Check]. + + +AJAX Calls +---------- + +If you want a presenter or method to be accessible only for AJAX requests, use: + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Same Origin +----------- + +To enhance security, you can require that the request be made from the same domain. This prevents the [CSRF vulnerability |nette:vulnerability-protection#Cross-Site Request Forgery CSRF]: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +For `handle<Signal>()` methods, access from the same domain is required automatically. So, if you want to allow access from any domain, specify: + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Access via Forward +------------------ + +Sometimes it is useful to restrict access to a presenter so that it is only available indirectly, for example, using the `forward()` or `switch()` methods from another presenter. This is how error-presenters are protected, for instance, to prevent them from being triggered from a URL: + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +In practice, it is often necessary to mark certain views that can only be accessed based on logic in the presenter. Again, so that they cannot be opened directly: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = $this->facade->getProduct($id); + if (!$product) { + $this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Specific Actions +---------------- + +You can also restrict certain code, like creating a component, to be accessible only for specific actions in the presenter: + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +In the case of a single action, there's no need to write an array: `#[Requires(actions: 'default')]` + + +Custom Attributes +----------------- + +If you want to use the `#[Requires]` attribute repeatedly with the same settings, you can create your own attribute that inherits `#[Requires]` and configures it according to your needs. + +For example, `#[SingleAction]` allows access only through the `default` action: + +```php +#[Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Or `#[RestMethods]` will allow access via all HTTP methods used for the REST API: + +```php +#[Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Conclusion +---------- + +The `#[Requires]` attribute gives you great flexibility and control over how your web pages are accessed. Using simple, yet powerful rules, you can enhance the security and proper functioning of your application. As you can see, using attributes in Nette can not only simplify your work but also secure it. diff --git a/best-practices/en/composer.texy b/best-practices/en/composer.texy index e87a29b0f7..1e08a82668 100644 --- a/best-practices/en/composer.texy +++ b/best-practices/en/composer.texy @@ -6,7 +6,7 @@ Composer Usage Tips Composer is a tool for dependency management in PHP. It allows you to declare the libraries your project depends on and it will install and update them for you. We will learn: - how to install Composer -- use it in new or existing project +- how to use it in a new or existing project </div> @@ -28,7 +28,7 @@ Linux, macOS All you need is 4 commands, which you can copy from [this page |https://getcomposer.org/download/]. -Further more, by copying into folder that is in system's `PATH`, Composer becomes globally accessible: +Furthermore, by copying it into a folder that is in the system's `PATH`, Composer becomes globally accessible: ```shell $ mv ./composer.phar ~/bin/composer # or /usr/local/bin/composer @@ -38,7 +38,7 @@ $ mv ./composer.phar ~/bin/composer # or /usr/local/bin/composer Use in Project ============== -To start using Composer in your project, all you need is a `composer.json` file. This file describes the dependencies of your project and may contain other metadata as well. The simplest `composer.json` can look like this: +To start using Composer in your project, all you need is a `composer.json` file. This file describes the dependencies of your project and may also contain other metadata. The simplest `composer.json` can look like this: ```js { @@ -48,17 +48,17 @@ To start using Composer in your project, all you need is a `composer.json` file. } ``` -We're saying here, that our application (or library) depends on package `nette/database` (the package name consists of a vendor name and the project's name) and it wants the version that matches the `^3.0` version constraint. +We're saying here that our application (or library) requires the package `nette/database` (the package name consists of a vendor name and the project's name) and it wants a version that matches the `^3.0` version constraint (i.e., the latest version 3). -So, when we have the `composer.json` file in the project root and we run: +So, with the `composer.json` file in the project root, run: ```shell composer update ``` -Composer will download the Nette Database into directory `vendor`. It also creates a `composer.lock` file, which contains information about exactly which library versions it installed. +Composer will download Nette Database into the `vendor/` directory. It also creates a `composer.lock` file, which contains information about exactly which library versions it installed. -Composer generates a `vendor/autoload.php` file. You can simply include this file and start using the classes that those libraries provide without any extra work: +Composer generates a `vendor/autoload.php` file. You can simply include this file and start using the libraries' classes without any extra work: ```php require __DIR__ . '/vendor/autoload.php'; @@ -70,17 +70,17 @@ $db = new Nette\Database\Connection('sqlite::memory:'); Update Packages to the Latest Versions ====================================== -To update all used packages to the latest version according to version constraints defined in `composer.json` use command `composer update`. For example for dependency `"nette/database": "^3.0"` it will install the latest version 3.x.x, but not version 4. +To update the used libraries to the latest versions according to the constraints defined in `composer.json`, use the `composer update` command. For example, with the dependency `"nette/database": "^3.0"`, it will install the latest 3.x.x version, but not version 4. -To update the version constrains in the `composer.json` file to e.g. `"nette/database": "^4.1"`, to enable to install the latest version, use the `composer require nette/database` command. +To update the constraints in the `composer.json` file, for example to `"nette/database": "^4.1"`, allowing the installation of the latest version, use the `composer require nette/database` command. -To update all used Nette packages, it would be necessary to list them all on the command line, eg: +To update all used Nette packages, you would need to list them all on the command line, e.g.: ```shell composer require nette/application nette/forms latte/latte tracy/tracy ... ``` -Which is impractical. Therefore, use a simple script "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff that will do it for you: +This is impractical. Therefore, use the simple script "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff that will do it for you: ```shell php composer-frontline.php @@ -90,29 +90,29 @@ php composer-frontline.php Creating New Project ==================== -New Nette project can be created by executing a simple command: +You can create a new Nette project using a single command: ```shell composer create-project nette/web-project name-of-the-project ``` -Instead the `name-of-the-project` you should provide the name of the directory for your project and execute the command. Composer will fetch the `nette/web-project` repository from GitHub, which already contains the `composer.json` file, and right after that install the Nette Framework itself. The only thing which remains is to [check write permissions |nette:troubleshooting#setting-directory-permissions] on directories `temp/` and `log/` and you're ready to go. +Replace `name-of-the-project` with the directory name for your project and execute the command. Composer will download the `nette/web-project` repository from GitHub, which already contains a `composer.json` file, and then install the Nette Framework itself. All that remains is to [set directory permissions |nette:troubleshooting#Setting Directory Permissions] for the `temp/` and `log/` directories, and the project should be live. -If you know what version of PHP the project will be hosted on, be sure to [set it up |#PHP Version]. +If you know which PHP version your project will be hosted on, be sure to [set it |#PHP Version]. PHP Version =========== -Composer always installs the versions of packages that are compatible with the version of PHP you are currently using (or rather, the version of PHP used on the command line when you run Composer). Which is probably not the same version your web host is using. That's why it's very important to add information about the PHP version on your hosting to your `composer.json` file. After that, only versions of packages compatible with the host will be installed. +Composer always installs package versions compatible with the PHP version you are currently using (specifically, the PHP version used on the command line when running Composer). This might not be the same version your web host uses. Therefore, it's crucial to add information about the PHP version on your hosting to the `composer.json` file. Then, only package versions compatible with the host will be installed. -For example, to set the project to run on PHP 8.2.3, use the command: +For example, to specify that the project will run on PHP 8.2.3, use the command: ```shell composer config platform.php 8.2.3 ``` -This is how the version is written to the `composer.json` file: +The version will be written to the `composer.json` file like this: ```js { @@ -124,8 +124,7 @@ This is how the version is written to the `composer.json` file: } ``` -However, the PHP version number is also listed elsewhere in the file, in the `require` section. While the first number specifies the version for which packages will be installed, the second number tells what version the application itself is written for. -(Of course, it doesn't make sense for these versions to be different, so double entry is a redundancy.) You set this version with the command: +However, the PHP version number is also specified elsewhere in the file, in the `require` section. While the first number determines the version for which packages are installed, the second number indicates the version the application itself is written for. For example, PhpStorm uses this to set the *PHP language level*. (Of course, it doesn't make sense for these versions to differ, so the double entry is an oversight.) Set this version using the command: ```shell composer require php 8.2.3 --no-update @@ -142,48 +141,54 @@ Or directly in the `composer.json` file: ``` +Ignoring PHP Version +==================== + +Packages typically specify both the lowest PHP version they are compatible with and the highest version they have been tested against. If you intend to use an even newer PHP version, perhaps for testing, Composer will refuse to install such a package. The solution is the `--ignore-platform-req=php+` option, which makes Composer ignore the upper limits of the required PHP version. + + False Reports ============= -When upgrading packages or changing version numbers, conflicts happen. One package has requirements that conflict with another and so on. However, Composer occasionally prints a false messages. It reports a conflict that doesn't really exist. In this case, it helps to delete the `composer.lock` file and try again. +When upgrading packages or changing version numbers, conflicts sometimes occur. One package has requirements that conflict with another, and so on. However, Composer sometimes outputs false reports. It reports a conflict that doesn't actually exist. In such cases, deleting the `composer.lock` file and trying again can help. -If the error message persists, then it is meant seriously and you need to read from it what to modify and how. +If the error message persists, it is genuine, and you need to read it to understand what to modify and how. Packagist.org - Global Repository ================================= -[Packagist |https://packagist.org] is the main package repository, in which Composer tries to search packages, if not told otherwise. You can also publish your own packages here. +[Packagist |https://packagist.org] is the main repository where Composer searches for packages by default. You can also publish your own packages here. What If We Don’t Want the Central Repository -------------------------------------------- -If we have internal applications or libraries in our company, which cannot be hosted publicly on Packagist, we can create our own repositories for those project. +If we have internal applications or libraries within our company that cannot be hosted publicly, we can create our own repositories for them. -More on repositories in [the official documentation |https://getcomposer.org/doc/05-repositories.md#repositories]. +Read more about repositories in [the official documentation |https://getcomposer.org/doc/05-repositories.md#repositories]. Autoloading =========== -A key feature of Composer is that it provides autoloading for all classes it installs, which you start by including a file `vendor/autoload.php`. +A key feature of Composer is that it provides autoloading for all the classes it installs. You activate this by including the `vendor/autoload.php` file. -However, it is also possible to use Composer to load other classes outside the folder `vendor`. The first option is to let Composer scan the defined folders and subfolders, find all the classes and include them in the autoloader. To do this, set `autoload > classmap` in `composer.json`: +However, you can also use Composer to load other classes from outside the `vendor/` directory. The first option is to let Composer scan defined directories and subdirectories, find all classes, and include them in the autoloader. To achieve this, set `autoload > classmap` in `composer.json`: ```js { "autoload": { "classmap": [ - "src/", # includes the src/ folder and its subfolders + "src/", # includes the src/ directory and its subdirectories ] } } ``` -Subsequently, it is necessary to run the command `composer dumpautoload` with each change and let the autoloading tables regenerate. This is extremely inconvenient, and it is far better to entrust this task to [RobotLoader|robot-loader:], which performs the same activity automatically in the background and much faster. +Subsequently, you need to run the `composer dumpautoload` command after each change to regenerate the autoloading tables. This is extremely inconvenient. It's much better to entrust this task to [RobotLoader|robot-loader:], which performs the same activity automatically in the background and much faster. -The second option is to follow [PSR-4 |https://www.php-fig.org/psr/psr-4/]. Simply saying, it is a system where the namespaces and class names correspond to the directory structure and file names, ie `App\Router\RouterFactory` is located in the file `/path/to/App/Router/RouterFactory.php`. Configuration example: +The second option is to adhere to [PSR-4 |https://www.php-fig.org/psr/psr-4/]. Simply put, it's a system where namespaces and class names correspond to the directory structure and file names, e.g., `App\Core\RouterFactory` will be located in the file `/path/to/App/Core/RouterFactory.php`. Configuration example: ```js { @@ -195,13 +200,13 @@ The second option is to follow [PSR-4 |https://www.php-fig.org/psr/psr-4/]. Simp } ``` -See [Composer Documentation |https://getcomposer.org/doc/04-schema.md#psr-4] for exactly how to configure this behavior. +See the [Composer documentation |https://getcomposer.org/doc/04-schema.md#psr-4] for details on how to configure this behavior. Testing New Versions ==================== -You want to test a new development version of a package. How to do it? First, add this pair of options to the `composer.json` file, which will allow you to install development versions of packages, but will only do so if there is no stable version combination that meets the requirements: +Want to test a new development version of a package? Here's how. First, add this pair of options to your `composer.json` file. This allows installing development versions, but Composer will only resort to them if no stable version combination satisfies the requirements: ```js { @@ -210,22 +215,21 @@ You want to test a new development version of a package. How to do it? First, ad } ``` -We also recommend deleting the `composer.lock` file, because sometimes Composer incomprehensibly refuses to install and this will solve the problem. +We also recommend deleting the `composer.lock` file, as Composer sometimes inexplicably refuses installation, and this can resolve the issue. -Let's say the package is `nette/utils` and the new version is 4.0. You install it with the command: +Let's say the package is `nette/utils` and the new version is 4.0. Install it using the command: ```shell composer require nette/utils:4.0.x-dev ``` -Or you can install a specific version, for example 4.0.0-RC2: +Or you can install a specific version, for example, 4.0.0-RC2: ```shell composer require nette/utils:4.0.0-RC2 ``` -If another package depends on the library and is locked to an older version (e.g. `^3.1`), it is ideal to update the package to work with the new version. -However, if you just want to get around the limitation and force Composer to install the development version and pretend it is an older version (e.g., 3.1.6), you can use the `as` keyword: +However, if another package depends on the library and is locked to an older version (e.g., `^3.1`), the ideal solution is to update that dependent package to work with the new version. But if you just want to bypass the restriction and force Composer to install the development version while pretending it's an older version (e.g., 3.1.6), you can use the `as` keyword: ```shell composer require nette/utils "4.0.x-dev as 3.1.6" @@ -235,9 +239,9 @@ composer require nette/utils "4.0.x-dev as 3.1.6" Calling Commands ================ -You can call your own custom commands and scripts through Composer as if they were native Composer commands. Scripts located in the `vendor/bin` folder do not need to specify this folder. +You can call your own predefined commands and scripts via Composer as if they were native Composer commands. For scripts located in the `vendor/bin` directory, you don't need to specify this path. -As an example, we define a script in the `composer.json` file that uses [Nette Tester |tester:] to run tests: +As an example, let's define a script in `composer.json` that uses [Nette Tester |tester:] to run tests: ```js { @@ -247,13 +251,13 @@ As an example, we define a script in the `composer.json` file that uses [Nette T } ``` -We then run the tests with `composer tester`. We can call the command even if we are not in the root folder of the project, but in a subdirectory. +We then run the tests using `composer tester`. You can call the command even if you are not in the project's root directory, but in one of its subdirectories. Send Thanks =========== -We will show you a trick that will make open source authors happy. You can easily give a star on GitHub to the libraries that your project uses. Just install the `symfony/thanks` library: +We'll show you a trick to please open source authors. You can easily give stars on GitHub to the libraries your project uses. Simply install the `symfony/thanks` library: ```shell composer global require symfony/thanks @@ -271,10 +275,8 @@ Try it! Configuration ============= -Composer is closely integrated with version control tool [Git |https://git-scm.com]. If you do not use Git, it is necessary to tell it to Composer: +Composer is closely integrated with the version control tool [Git |https://git-scm.com]. If you don't have Git installed, you need to tell Composer not to use it: ```shell composer -g config preferred-install dist ``` - -{{sitename: Best Practices}} diff --git a/best-practices/en/creating-editing-form.texy b/best-practices/en/creating-editing-form.texy index 97526562e3..27e0af202b 100644 --- a/best-practices/en/creating-editing-form.texy +++ b/best-practices/en/creating-editing-form.texy @@ -4,13 +4,13 @@ Form for Creating and Editing a Record .[perex] How to properly implement adding and editing a record in Nette, using the same form for both? -In many cases, the forms for adding and editing a record are the same, differing only by the label on the button. We will show examples of simple presenters where we use the form first to add a record, then to edit it, and finally combine the two solutions. +In many cases, the forms for adding and editing records are identical, perhaps differing only in the button label. We will show examples of simple presenters where we use the form first to add a record, then to edit it, and finally combine the two solutions. Adding a Record --------------- -An example of a presenter used to add a record. We will leave the actual database work to the `Facade` class, whose code is not relevant for the example. +Example of a presenter for adding a record. We'll leave the actual database interaction to a `Facade` class, whose code isn't essential for this example. ```php @@ -29,13 +29,13 @@ class RecordPresenter extends Nette\Application\UI\Presenter // ... add form fields ... - $form->onSuccess[] = [$this, 'recordFormSucceeded']; + $form->onSuccess[] = $this->recordFormSucceeded(...); return $form; } - public function recordFormSucceeded(Form $form, array $data): void + private function recordFormSucceeded(Form $form, array $data): void { - $this->facade->add($data); // add record to database + $this->facade->add($data); // add record to the database $this->flashMessage('Successfully added'); $this->redirect('...'); } @@ -51,7 +51,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter Editing a Record ---------------- -Now let's see what a presenter used to edit a records would look like: +Now let's look at what a presenter for editing a record would look like: ```php @@ -91,11 +91,11 @@ class RecordPresenter extends Nette\Application\UI\Presenter // ... add form fields ... $form->setDefaults($this->record); // set default values - $form->onSuccess[] = [$this, 'recordFormSucceeded']; + $form->onSuccess[] = $this->recordFormSucceeded(...); return $form; } - public function recordFormSucceeded(Form $form, array $data): void + private function recordFormSucceeded(Form $form, array $data): void { $this->facade->update($this->record->id, $data); // update record $this->flashMessage('Successfully updated'); @@ -104,9 +104,9 @@ class RecordPresenter extends Nette\Application\UI\Presenter } ``` -In the *action* method, which is invoked right at the beginning of the [presenter lifecycle|application:presenters#Life Cycle of Presenter], we verify the existence of the record and the user's permission to edit it. +In the *action* method, invoked at the beginning of the [presenter lifecycle |application:presenters#Presenter Life Cycle], we verify the record's existence and the user's permission to edit it. -We store the record in the `$record` property so that it is available in the `createComponentRecordForm()` method for setting defaults, and `recordFormSucceeded()` for the ID. An alternative solution would be to set the default values directly in `actionEdit()` and the ID value, which is part of the URL, is retrieved using `getParameter('id')`: +We store the record in the `$record` property, making it available in the `createComponentRecordForm()` method for setting default values and in `recordFormSucceeded()` for accessing the ID. An alternative solution is to set the default values directly in `actionEdit()` and retrieve the ID value (part of the URL) using `getParameter('id')`: ```php @@ -133,13 +133,13 @@ We store the record in the `$record` property so that it is available in the `cr } ``` -However, and this should be **the most important takeaway from all the code**, we need to make sure that the action is indeed `edit` when we create the form. Because otherwise the validation in the `actionEdit()` method wouldn't happen at all! +However, and this should be **the most important takeaway from the entire code**, we must ensure the action is indeed `edit` when creating the form. Otherwise, the verification in the `actionEdit()` method would not occur at all! Same Form for Adding and Editing -------------------------------- -And now we will combine both presenters into one. Either we could distinguish which action is involved in the `createComponentRecordForm()` method and configure the form accordingly, or we can leave it directly to the action-methods and get rid of the condition: +Now, let's combine both presenters into one. We could either differentiate the action within the `createComponentRecordForm()` method and configure the form accordingly, or we can delegate this to the action methods directly and eliminate the conditional check: ```php @@ -153,7 +153,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter public function actionAdd(): void { $form = $this->getComponent('recordForm'); - $form->onSuccess[] = [$this, 'addingFormSucceeded']; + $form->onSuccess[] = $this->addingFormSucceeded(...); } public function actionEdit(int $id): void @@ -167,8 +167,8 @@ class RecordPresenter extends Nette\Application\UI\Presenter } $form = $this->getComponent('recordForm'); - $form->setDefaults($record); // set defaults - $form->onSuccess[] = [$this, 'editingFormSucceeded']; + $form->setDefaults($record); // set default values + $form->onSuccess[] = $this->editingFormSucceeded(...); } protected function createComponentRecordForm(): Form @@ -185,14 +185,14 @@ class RecordPresenter extends Nette\Application\UI\Presenter return $form; } - public function addingFormSucceeded(Form $form, array $data): void + private function addingFormSucceeded(Form $form, array $data): void { - $this->facade->add($data); // add record to database + $this->facade->add($data); // add record to the database $this->flashMessage('Successfully added'); $this->redirect('...'); } - public function editingFormSucceeded(Form $form, array $data): void + private function editingFormSucceeded(Form $form, array $data): void { $id = (int) $this->getParameter('id'); $this->facade->update($id, $data); // update record @@ -203,4 +203,3 @@ class RecordPresenter extends Nette\Application\UI\Presenter ``` {{priority: -1}} -{{sitename: Best Practices}} diff --git a/best-practices/en/dynamic-snippets.texy b/best-practices/en/dynamic-snippets.texy index a0e8e09cae..144d00268a 100644 --- a/best-practices/en/dynamic-snippets.texy +++ b/best-practices/en/dynamic-snippets.texy @@ -1,7 +1,7 @@ Dynamic Snippets **************** -Quite often in application development there is a need to perform AJAX operations, for example, in individual rows of a table or list items. As an example, we can choose to list articles, allowing the logged-in user to select a "like/dislike" rating for each of them. The code of the presenter and the corresponding template without AJAX will look something like this (I list the most important snippets, the code assumes the existence of a service for marking up the ratings and getting a collection of articles - the specific implementation is not important for the purposes of this tutorial): +Quite often during application development, the need arises to perform AJAX operations, for example, on individual rows of a table or list items. As an example, let's consider listing articles where logged-in users can rate each article with 'like' or 'dislike'. The presenter code and corresponding template without AJAX would look something like this (showing the most relevant parts; the code assumes a service exists for handling ratings and retrieving articles - the specific implementation isn't crucial for this guide): ```php public function handleLike(int $articleId): void @@ -35,15 +35,15 @@ Template: Ajaxization =========== -Let's now bring AJAX to this simple application. Changing the rating of an article is not important enough to require a HTTP request with redirect, so ideally it should be done with AJAX in the background. We'll use the [handler script from add-ons |https://componette.org/vojtech-dobes/nette.ajax.js/] with the usual convention that AJAX links have the CSS class `ajax`. +Now, let's add AJAX functionality to this simple application. Changing an article's rating isn't critical enough to warrant a full page redirect, so it should ideally happen via AJAX in the background. We'll use the [handler script from add-ons |application:ajax#Naja] with the common convention that AJAX links have the CSS class `ajax`. -However, how to do it specifically? Nette offers 2 ways: the dynamic snippet way and the component way. Both of them have their pros and cons, so we will show them one by one. +But how exactly do we implement this? Nette offers two approaches: dynamic snippets and components. Both have their pros and cons, so we'll demonstrate each one. The Dynamic Snippets Way ======================== -In Latte terminology, a dynamic snippet is a specific use case of the `{snippet}` tag where a variable is used in the snippet name. Such a snippet cannot be found just anywhere in the template - it must be wrapped by a static snippet, i.e. a regular one, or inside a `{snippetArea}`. We could modify our template as follows. +In Latte terminology, a dynamic snippet refers to a specific use of the `{snippet}` tag where a variable is used in the snippet's name. Such a snippet cannot be placed just anywhere in the template; it must be wrapped by a static (regular) snippet or be inside a `{snippetArea}`. We could modify our template as follows: ```latte @@ -62,9 +62,9 @@ In Latte terminology, a dynamic snippet is a specific use case of the `{snippet} {/snippet} ``` -Each article now defines a single snippet, which has an article ID in the title. All these snippets are then wrapped together in a single snippet called `articlesContainer`. If we omit this wrapping snippet, Latte will alert us with an exception. +Each article now defines a snippet whose name includes the article's ID. All these dynamic snippets are then wrapped together by a static snippet named `articlesContainer`. If we were to omit this outer snippet, Latte would throw an exception. -All that's left to do is to add redrawing to the presenter - just redraw the static wrapper. +All that remains is to add the redrawing logic to the presenter – simply redraw the static wrapper. ```php public function handleLike(int $articleId): void @@ -72,18 +72,18 @@ public function handleLike(int $articleId): void $this->ratingService->saveLike($articleId, $this->user->id); if ($this->isAjax()) { $this->redrawControl('articlesContainer'); - // $this->redrawControl('article-' . $articleId); -- není potřeba + // $this->redrawControl('article-' . $articleId); -- not needed } else { $this->redirect('this'); } } ``` -Modify the sister method `handleUnlike()` in the same way, and AJAX is up and running! +Modify the corresponding `handleUnlike()` method similarly, and AJAX is functional! -The solution has one downside, however. If we dig more into how the AJAX request works, we find that although the application looks efficient in appearance (it only returns a single snippet for a given article), it actually renders all the snippets on the server. It has placed the desired snippet in our payload, and discarded the others (thus, quite unnecessarily, it also retrieved them from the database). +However, this solution has a drawback. If we examine the AJAX request more closely, we'll find that while the application appears efficient externally (returning only a single snippet for the specific article), it actually renders *all* snippets on the server side. It places the required snippet into the payload and discards the others (meaning it also unnecessarily retrieved and rendered them). -To optimize this process, we'll need to take action where we pass the `$articles` collection to the template (say in the `renderDefault()` method). We will take advantage of the fact that signal processing takes place before the `render<Something>` methods: +To optimize this, we need to intervene where the `$articles` collection is passed to the template (let's say in the `renderDefault()` method). We'll leverage the fact that signal handling occurs before the `render<Something>` methods: ```php public function handleLike(int $articleId): void @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connection->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,18 +101,18 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connection->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` -Now, when the signal is processed, instead of a collection with all articles, only an array with a single article is passed to the template - the one we want to render and send in payload to the browser. Thus, `{foreach}` will be done only once and no extra snippets will be rendered. +Now, during signal processing, instead of passing the entire collection of articles, only an array containing the single relevant article is passed to the template – the one we intend to render and send in the payload to the browser. Consequently, the `{foreach}` loop runs only once, and no unnecessary snippets are rendered. Component Way ============= -A completely different solution uses a different approach to avoid dynamic snippets. The trick is to move all the logic into a separate component - from now on, we don't have a presenter to take care of entering the rating, but a dedicated `LikeControl`. The class will look like the following (in addition, it will also contain the `render`, `handleUnlike`, etc. methods): +A completely different approach avoids dynamic snippets altogether. The trick involves encapsulating the entire logic within a separate component. Instead of the presenter handling the rating, a dedicated `LikeControl` will manage it. The class will look like this (it would also contain `render`, `handleUnlike`, etc. methods): ```php class LikeControl extends Nette\Application\UI\Control @@ -146,19 +146,19 @@ Template of component: {/snippet} ``` -Of course we will change the view template and we will have to add a factory to the presenter. Since we will create the component as many times as we receive articles from the database, we will use the [application:Multiplier] class to "multiply" it. +Naturally, the view's template will change, and we'll need to add a factory to the presenter. Since we'll create an instance of this component for each article retrieved from the database, we'll use the [Multiplier |application:Multiplier] class to manage their creation. ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); } ``` -The template view is reduced to the minimum necessary (and completely free of snippets!): +The view's template shrinks to the bare minimum (and is completely free of snippets!): ```latte <article n:foreach="$articles as $article"> @@ -168,7 +168,6 @@ The template view is reduced to the minimum necessary (and completely free of sn </article> ``` -We are almost done: the application will now work in AJAX. Here too we have to optimize the application, because due to the use of Nette Database, the signal processing will unnecessarily load all articles from the database instead of one. However, the advantage is that there will be no rendering, because only our component is actually rendered. +We're almost finished: the application will now function with AJAX. Here too, optimization is needed because, due to the use of Nette Database, signal processing unnecessarily loads all articles from the database instead of just the relevant one. The advantage, however, is that no unnecessary rendering occurs, as only the specific component instance is rendered. {{priority: -1}} -{{sitename: Best Practices}} diff --git a/best-practices/en/editors-and-tools.texy b/best-practices/en/editors-and-tools.texy index b8446fea90..c6109dcbab 100644 --- a/best-practices/en/editors-and-tools.texy +++ b/best-practices/en/editors-and-tools.texy @@ -2,39 +2,39 @@ Editors & Tools *************** .[perex] -You can be a skilled programmer, but only with good tools will you become a master. In this chapter you will find tips on important tools, editors and plugins. +You might be a skilled programmer, but good tools are what make you a master. This chapter provides tips on essential tools, editors, and plugins. IDE Editor ========== -We strongly recommend using a full-featured IDE for development, such as PhpStorm, NetBeans, VS Code, and not just a text editor with PHP support. The difference is really crucial. There is no reason to be satisfied with a classic editor with syntax highlighting, because it doesn't reach the capabilities of a IDE with accurate code suggestion, that can refactor code, and more. Some IDEs are paid, others are free. +We strongly recommend using a full-featured IDE for development, like PhpStorm, NetBeans, or VS Code, rather than just a text editor with PHP support. The difference is truly significant. There's no reason to settle for a basic editor that only offers syntax highlighting when you can have a top-tier IDE providing accurate code suggestions, error checking, refactoring capabilities, and much more. Some IDEs are paid, while others are free. -**NetBeans IDE** has built-in support for Nette, Latte and NEON. +**NetBeans IDE** has built-in support for Nette, Latte, and NEON. -**PhpStorm**: install these plugins in `Settings > Plugins > Marketplace`: +**PhpStorm**: Install these plugins via `Settings > Plugins > Marketplace`: - Nette framework helpers - Latte - NEON support - Nette Tester -**VS Code**: find the "Nette Latte + Neon" plugin in the marketplace. +**VS Code**: Find the "Nette Latte + Neon" plugin in the marketplace. -Also connect Tracy with the editor. When the error page is displayed, you can click on the file names and they will open in the editor with the cursor on the corresponding line. Learn [how to configure the system |tracy:open-files-in-ide]. +Also, integrate Tracy with your editor. When an error page is displayed, clicking on file names will open them directly in your editor at the corresponding line. Learn [how to configure this feature |tracy:open-files-in-ide]. PHPStan ======= -PHPStan is a tool that detects logical errors in your code before you run it. +PHPStan is a static analysis tool that detects logical errors in your code before you even run it. -Install it via Composer: +Install it using Composer: ```shell composer require --dev phpstan/phpstan-nette ``` -Create a configuration file `phpstan.neon` in the project: +Create a configuration file `phpstan.neon` in your project: ```neon includes: @@ -47,40 +47,38 @@ parameters: level: 5 ``` -And then let it analyze the classes in the `app/` folder: +Then, let it analyze the classes within the `app/` directory: ```shell vendor/bin/phpstan analyse app ``` -You can find comprehensive documentation directly at [PHPStan |https://phpstan.org]. +You can find comprehensive documentation directly on the [PHPStan website |https://phpstan.org]. Code Checker ============ -[Code Checker|code-checker:] checks and possibly repairs some of the formal errors in your source code. +[Code Checker|code-checker:] checks and potentially fixes some formal errors in your source code: -- removes [BOM |nette:glossary#bom] -- checks validity of [Latte |latte:] templates -- checks validity of `.neon`, `.php` and `.json` files -- checks for [control characters |nette:glossary#control characters] -- checks whether the file is encoded in UTF-8 -- controls misspelled `/* @annotations */` (second asterisk missing) -- removes PHP ending tags `?>` in PHP files -- removes trailing whitespace and unnecessary blank lines from the end of a file -- normalizes line endings to system-default (with the `-l` parameter) +- removes [BOM |nette:glossary#BOM] +- checks the validity of [Latte |latte:] templates +- checks the validity of `.neon`, `.php`, and `.json` files +- checks for [control characters |nette:glossary#Control Characters] +- checks if the file is encoded in UTF-8 +- checks for incorrectly written `/* @annotations */` (missing second asterisk) +- removes trailing `?>` PHP tags from files containing only PHP code +- removes trailing whitespace and unnecessary blank lines at the end of files +- normalizes line endings to the system default (using the `-l` option) Composer ======== -[Composer] is a tool for managing your dependencies in PHP. It allows us to declare library dependencies and it will install them for us, into our project. +[Composer] is a tool for dependency management in PHP. It allows you to declare the libraries your project depends on and manages their installation and updates. Requirements Checker ==================== -It was a tool that tested the server's running environment and informed whether (and to what extent) the framework could be used. Currently, Nette can be used on any server that has the minimum required version of PHP. - -{{sitename: Best Practices}} +This was a tool that tested the server's runtime environment and indicated whether (and to what extent) the framework could be used. Currently, Nette can be used on any server that meets the minimum required PHP version. diff --git a/best-practices/en/form-reuse.texy b/best-practices/en/form-reuse.texy index 176a6cb783..2d91c006fc 100644 --- a/best-practices/en/form-reuse.texy +++ b/best-practices/en/form-reuse.texy @@ -2,15 +2,15 @@ Reusing Forms in Multiple Places ******************************** .[perex] -In Nette, you have several options to reuse the same form in multiple places without duplicating code. In this article, we'll go over the different solutions, including the ones you should avoid. +Nette offers several ways to reuse the same form in multiple places without duplicating code. This article will cover various solutions, including those you should avoid. Form Factory ============ -One basic approach to using the same component in multiple places is to create a method or class that generates the component, and then call that method in different places in the application. Such a method or class is called a *factory*. Please do not confuse with the *factory method* design pattern, which describes a specific way of using factories and is not related to this topic. +A fundamental approach to reusing a component in multiple locations is to create a method or class that generates this component. This method is then called from various places in the application. Such a method or class is called a *factory*. Please don't confuse this with the *factory method* design pattern, which describes a specific way of using factories and isn't directly related to this topic. -As an example, let's create a factory that will build an edit form: +As an example, let's create a factory that builds an editing form: ```php use Nette\Application\UI\Form; @@ -28,14 +28,14 @@ class FormFactory } ``` -Now you can use this factory in different places in your application, for example in presenters or components. And we do this by [requesting it as a dependency |dependency-injection:passing-dependencies]. So first, we'll write the class to the configuration file: +Now you can use this factory in various parts of your application, such as presenters or components. You do this by [requesting it as a dependency |dependency-injection:passing-dependencies]. First, register the class in the configuration file: ```neon services: - FormFactory ``` -And then we use it in the presenter: +Then, use it in a presenter: ```php @@ -50,14 +50,14 @@ class MyPresenter extends Nette\Application\UI\Presenter { $form = $this->formFactory->createEditForm(); $form->onSuccess[] = function () { - // processing of sent data + // processing of submitted data }; return $form; } } ``` -You can extend the form factory with additional methods to create other types of forms to suit your application. And, of course, you can add a method that creates a basic form without elements, which the other methods will use: +You can extend the form factory with more methods to create other types of forms as needed by your application. And naturally, we can add a method that creates a basic form without elements, which other methods can then utilize: ```php class FormFactory @@ -79,13 +79,13 @@ class FormFactory } ``` -The `createForm()` method doesn't do anything useful yet, but that will change quickly. +The `createForm()` method doesn't do much useful yet, but that will change soon. Factory Dependencies ==================== -In time, it will become apparent that we need forms to be multilingual. This means that we need to set up a [translator |forms:rendering#Translating] for all forms. To do this, we modify the `FormFactory` class to accept the `Translator` object as a dependency in the constructor, and pass it to the form: +Over time, it may become necessary for forms to be multilingual. This means setting a [translator |forms:rendering#Translating] for all forms. To achieve this, modify the `FormFactory` class to accept the `Translator` object as a dependency in its constructor and pass it to the created form: ```php use Nette\Localization\Translator; @@ -108,14 +108,13 @@ class FormFactory } ``` -Since the `createForm()` method is also called by other methods that create specific forms, we only need to set the translator in that method. And we're done. No need to change any presenter or component code, which is great. +Since the `createForm()` method is also called by other methods creating specific forms, setting the translator here is sufficient. And we're done. There's no need to modify any presenter or component code, which is excellent. More Factory Classes ==================== -Alternatively, you can create multiple classes for each form you want to use in your application. -This approach can increase code readability and make forms easier to manage. Leave the original `FormFactory` to create just a pure form with basic configuration (for example, with translation support) and create a new factory `EditFormFactory` for the edit form. +Alternatively, you can create separate factory classes for each form you intend to use in your application. This approach can enhance code readability and simplify form management. Let the original `FormFactory` create only a basic form with fundamental configuration (like translation support), and create a new factory, `EditFormFactory`, specifically for the editing form. ```php class FormFactory @@ -134,7 +133,7 @@ class FormFactory } -// ✅ use of composition +// ✅ using composition class EditFormFactory { public function __construct( @@ -152,7 +151,7 @@ class EditFormFactory } ``` -It is very important that the binding between the `FormFactory` and `EditFormFactory` classes is implemented by composition, not object inheritance: +It's crucial that the relationship between the `FormFactory` and `EditFormFactory` classes is realized through [composition |nette:introduction-to-object-oriented-programming#Composition], not [object inheritance |nette:introduction-to-object-oriented-programming#Inheritance]: ```php // ⛔ NO! INHERITANCE DOESN'T BELONG HERE @@ -169,16 +168,15 @@ class EditFormFactory extends FormFactory } ``` -Using inheritance in this case would be completely counterproductive. You would run into problems very quickly. For example, if you wanted to add parameters to the `create()` method; PHP would report an error that its signature was different from the parent. -Or when passing a dependency to the `EditFormFactory` class via the constructor. This would cause what we call [constructor hell |dependency-injection:passing-dependencies#Constructor hell]. +Using inheritance here would be entirely counterproductive. You'd encounter problems very quickly. For instance, if you wanted to add parameters to the `create()` method, PHP would report an error because its signature would differ from the parent's. Or when passing dependencies to the `EditFormFactory` class via the constructor. This would lead to what's known as [constructor hell |dependency-injection:passing-dependencies#Constructor Hell]. -In general, it is better to prefer composition over inheritance. +Generally, it's better to prefer [composition over inheritance |dependency-injection:faq#Why composition is preferred over inheritance]. Form Handling ============= -The form handler that is called after a successful submission can also be part of a factory class. It will work by passing the submitted data to the model for processing. It will pass any errors [back to |forms:validation#Processing Errors] the form. The model in the following example is represented by the class `Facade`: +The form handler, invoked upon successful submission, can also be part of the factory class. It functions by passing the submitted data to the model layer for processing. Any processing errors are passed [back to |forms:validation#Processing Errors] the form. In the following example, the model is represented by the `Facade` class: ```php class EditFormFactory @@ -195,11 +193,11 @@ class EditFormFactory $form->addText('title', 'Title:'); // additional form fields are added here $form->addSubmit('send', 'Save'); - $form->onSuccess[] = [$this, 'processForm']; + $form->onSuccess[] = $this->processForm(...); return $form; } - public function processForm(Form $form, array $data): void + private function processForm(Form $form, array $data): void { try { // processing of submitted data @@ -212,7 +210,7 @@ class EditFormFactory } ``` -Let the presenter handle the redirection itself. It will add another handler to the `onSuccess` event, which will perform the redirection. This will allow the form to be used in different presenters, and each can redirect to a different location. +However, let the presenter handle the redirection itself. It adds another handler to the `onSuccess` event, which performs the redirection. This allows the form to be used in various presenters, each redirecting to a different location upon success. ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -226,7 +224,7 @@ class MyPresenter extends Nette\Application\UI\Presenter { $form = $this->formFactory->create(); $form->onSuccess[] = function () { - $this->flashMessage('Záznam byl uložen'); + $this->flashMessage('Record was saved'); $this->redirect('Homepage:'); }; return $form; @@ -234,13 +232,13 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -This solution takes advantage of the property of forms that, when `addError()` is called on a form or its element, the next `onSuccess` handler is not invoked. +This solution leverages the characteristic of forms where if `addError()` is called on the form or one of its elements, subsequent `onSuccess` handlers are not invoked. Inheriting from the Form Class ============================== -A built form is not supposed to be a child of a form. In other words, don't use this solution: +An assembled form should not be a descendant of the `Form` class. In other words, avoid this approach: ```php // ⛔ NO! INHERITANCE DOESN'T BELONG HERE @@ -249,24 +247,23 @@ class EditForm extends Form public function __construct(Translator $translator) { parent::__construct(); - $form->addText('title', 'Title:'); + $this->addText('title', 'Title:'); // additional form fields are added here - $form->addSubmit('send', 'Save'); - $form->setTranslator($translator); + $this->addSubmit('send', 'Save'); + $this->setTranslator($translator); } } ``` -Instead of building the form in the constructor, use the factory. +Instead of assembling the form within the constructor, use a factory. -It's important to realize that the `Form` class is primarily a tool for assembling a form, i.e., a form builder. And the assembled form can be considered its product. However, the product is not a specific case of the builder; there is no *is a* relationship between them, which forms the basis of inheritance. +It's important to recognize that the `Form` class is primarily a tool for building forms, i.e., a *form builder*. The assembled form can be considered its product. However, a product is not a specific type of builder; there's no *is a* relationship between them, which is the foundation of inheritance. Form Component ============== -A completely different approach is to create a [component |application:components] that includes a form. This gives new possibilities, for example to render the form in a specific way, since the component includes a template. -Or signals can be used for AJAX communication and loading information into the form, for example for hinting, etc. +A completely different approach involves creating a [component |application:components] that encapsulates the form. This opens up new possibilities, such as rendering the form in a specific way, as the component includes its own template. Alternatively, signals can be used for AJAX communication and dynamically loading information into the form, for example, for suggestions, etc. ```php @@ -287,12 +284,12 @@ class EditControl extends Nette\Application\UI\Control $form->addText('title', 'Title:'); // additional form fields are added here $form->addSubmit('send', 'Save'); - $form->onSuccess[] = [$this, 'processForm']; + $form->onSuccess[] = $this->processForm(...); return $form; } - public function processForm(Form $form, array $data): void + private function processForm(Form $form, array $data): void { try { // processing of submitted data @@ -309,7 +306,7 @@ class EditControl extends Nette\Application\UI\Control } ``` -Let's create a factory that will produce this component. It's enough to [write its interface|application:components#Components with Dependencies]: +Next, let's create a factory that will produce this component. It's sufficient to [define its interface |application:components#Components with Dependencies]: ```php interface EditControlFactory @@ -325,7 +322,7 @@ services: - EditControlFactory ``` -And now we can request the factory and use it in the presenter: +Now, we can request the factory and use it in the presenter: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -335,13 +332,13 @@ class MyPresenter extends Nette\Application\UI\Presenter ) { } - protected function createComponentEditForm(): Form + protected function createComponentEditForm(): EditControl { $control = $this->controlFactory->create(); $control->onSave[] = function (EditControl $control, $data) { $this->redirect('this'); - // or redirect to the result of editing, e.g.: + // or redirect to the edit result, e.g.: // $this->redirect('detail', ['id' => $data->id]); }; @@ -349,5 +346,3 @@ class MyPresenter extends Nette\Application\UI\Presenter } } ``` - -{{sitename: Best Practices}} diff --git a/best-practices/en/inject-method-attribute.texy b/best-practices/en/inject-method-attribute.texy index e619002214..b1c36d8df1 100644 --- a/best-practices/en/inject-method-attribute.texy +++ b/best-practices/en/inject-method-attribute.texy @@ -2,20 +2,17 @@ Inject Methods and Attributes ***************************** .[perex] -In this article, we will focus on various ways of passing dependencies to presenters in the Nette framework. We will compare the preferred method, which is the constructor, with other options such as `inject` methods and attributes. +This article focuses on various ways to pass dependencies into presenters within the Nette framework. We'll compare the preferred method, constructor injection, with alternatives like `inject` methods and attributes. -For presenters as well, passing dependencies using the [constructor |dependency-injection:passing-dependencies#Constructor Injection] is the preferred way. -However, if you create a common ancestor from which other presenters inherit (e.g., BasePresenter), and this ancestor also has dependencies, a problem arises, which we call [constructor hell |dependency-injection:passing-dependencies#Constructor hell]. -This can be bypassed using alternative methods, which include inject methods and attributes (annotations). +For presenters, as with other classes, passing dependencies via the [constructor |dependency-injection:passing-dependencies#Constructor Injection] is the preferred approach. However, if you create a common ancestor from which other presenters inherit (e.g., `BasePresenter`), and this ancestor also requires dependencies, a problem known as [constructor hell |dependency-injection:passing-dependencies#Constructor Hell] can arise. This can be circumvented using alternative methods, namely inject methods and attributes (formerly annotations). `inject*()` Methods =================== -This is a form of dependency passing using [setters |dependency-injection:passing-dependencies#Setter Injection]. The names of these setters begin with the prefix inject. -Nette DI automatically calls such named methods immediately after creating the presenter instance and passes all required dependencies to them. They must therefore be declared as public. +This is a form of dependency passing via [setters |dependency-injection:passing-dependencies#Setter Injection]. The names of these setters must start with the prefix `inject`. Nette DI automatically calls methods named this way immediately after creating the presenter instance, passing all required dependencies. Therefore, they must be declared as public. -`inject*()` methods can be considered as a kind of constructor extension into multiple methods. Thanks to this, the `BasePresenter` can take dependencies through another method and leave the constructor free for its descendants: +`inject*()` methods can be seen as extensions of the constructor, split into multiple methods. This allows `BasePresenter` to receive its dependencies via a separate method, leaving the constructor free for its descendants: ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -39,18 +36,18 @@ class MyPresenter extends BasePresenter } ``` -The presenter can contain any number of `inject*()` methods, and each can have any number of parameters. This is also great for cases where the presenter is [composed of traits |presenter-traits], and each of them requires its own dependency. +A presenter can have any number of `inject*()` methods, and each can accept any number of parameters. This approach is also well-suited for cases where a presenter is [composed of traits |presenter-traits], and each trait requires its own dependencies. `Inject` Attributes =================== -This is a form of [injection into properties |dependency-injection:passing-dependencies#Property Injection]. It is enough to indicate which properties should be injected, and Nette DI automatically passes dependencies immediately after creating the presenter instance. To insert them, it is necessary to declare them as public. +This is a form of [injecting into properties |dependency-injection:passing-dependencies#Property Injection]. Simply mark the properties that should be injected, and Nette DI will automatically pass the dependencies immediately after creating the presenter instance. To allow injection, these properties must be declared as public. -Properties are marked with an attribute: (previously, the annotation `/** @inject */` was used) +Properties are marked with an attribute: (previously, the `/** @inject */` annotation was used) ```php -use Nette\DI\Attributes\Inject; // this line is important +use Nette\DI\Attributes\Inject; // this line is important class MyPresenter extends Nette\Application\UI\Presenter { @@ -59,9 +56,6 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -The advantage of this method of passing dependencies was its very economical form of notation. However, with the introduction of [constructor property promotion |https://blog.nette.org/en/php-8-0-complete-overview-of-news#toc-constructor-property-promotion], using the constructor seems easier. +The advantage of this dependency passing method was its very concise syntax. However, with the introduction of [constructor property promotion |https://blog.nette.org/en/php-8-0-complete-overview-of-news#toc-constructor-property-promotion], using the constructor often appears simpler. -On the other hand, this method suffers from the same shortcomings as passing dependencies into properties in general: we have no control over changes in the variable, and at the same time, the variable becomes part of the public interface of the class, which is undesirable. - - -{{sitename: Best Practices}} +Conversely, this method suffers from the same drawbacks as property injection in general: we lack control over changes to the variable, and the variable becomes part of the class's public interface, which is generally undesirable. diff --git a/best-practices/en/lets-create-contact-form.texy b/best-practices/en/lets-create-contact-form.texy index ad2472ed15..695985f569 100644 --- a/best-practices/en/lets-create-contact-form.texy +++ b/best-practices/en/lets-create-contact-form.texy @@ -2,11 +2,11 @@ Let's Create a Contact Form *************************** .[perex] -Let's take a look at how to create a contact form in Nette, including sending it to an email. So let's do it! +Let's explore how to create a contact form in Nette, including sending the submitted data via email. Let's get started! -First we have to create a new project. As the [Getting Started |nette:installation] page explains. And then we can start creating the form. +First, we need to create a new project. The [Getting Started |nette:installation] page explains how. Then, we can begin creating the form. -The easiest way is to create the [form directly in Presenter |forms:in-presenter]. We can use the pre-made `HomePresenter`. We will add the `contactForm` component representing the form. We do this by writing the `createComponentContactForm()` factory method into the code that will produce the component: +The simplest approach is to create the [form directly within the Presenter |forms:in-presenter]. We can utilize the pre-existing `HomePresenter`. We'll add a component named `contactForm` to represent our form. We achieve this by adding a factory method `createComponentContactForm()` to the presenter's code, which will create the component: ```php use Nette\Application\UI\Form; @@ -18,41 +18,36 @@ class HomePresenter extends Presenter { $form = new Form; $form->addText('name', 'Name:') - ->setRequired('Enter your name'); + ->setRequired('Please enter your name'); $form->addEmail('email', 'E-mail:') - ->setRequired('Enter your e-mail'); + ->setRequired('Please enter your email'); $form->addTextarea('message', 'Message:') - ->setRequired('Enter message'); + ->setRequired('Please enter a message'); $form->addSubmit('send', 'Send'); - $form->onSuccess[] = [$this, 'contactFormSucceeded']; + $form->onSuccess[] = $this->contactFormSucceeded(...); return $form; } - public function contactFormSucceeded(Form $form, $data): void + private function contactFormSucceeded(Form $form, $data): void { // sending an email } } ``` -As you can see, we have created two methods. The first method `createComponentContactForm()` creates a new form. This has fields for name, email and message, which we add using the `addText()`, `addEmail()` and `addTextArea()` methods. We also added a button to submit the form. -But what if the user doesn't fill in some fields? In that case, we should let him know that it is a required field. We did this with the `setRequired()` method. -Finally, we also added an [event |nette:glossary#events] `onSuccess`, which is triggered if the form is submitted successfully. In our case, it calls the `contactFormSucceeded` method , which takes care of processing the submitted form. We'll add that to the code in a moment. +As you can see, we've created two methods. The first method, `createComponentContactForm()`, creates a new form instance. It includes fields for name, email, and message, added using the `addText()`, `addEmail()`, and `addTextArea()` methods respectively. We've also added a submit button. But what if the user leaves a field empty? In that case, we should inform them that the field is required. We achieved this using the `setRequired()` method. Finally, we attached an [event |nette:glossary#Events] handler to `onSuccess`, which is triggered upon successful form submission. In our case, it calls the `contactFormSucceeded` method, which will handle the processing of the submitted data. We'll implement this method shortly. -Let the `contantForm` component be rendered in the `templates/Home/default.latte` template: +Let's render the `contactForm` component in the `Home/default.latte` template: ```latte {block content} -<h1>Contant Form</h1> +<h1>Contact Form</h1> {control contactForm} ``` -To send the email itself, we create a new class called `ContactFacade` and place it in the `app/Model/ContactFacade.php` file: +For sending the email itself, we'll create a new class named `ContactFacade` and place it in the file `app/Model/ContactFacade.php`: ```php -<?php -declare(strict_types=1); - namespace App\Model; use Nette\Mail\Mailer; @@ -78,9 +73,9 @@ class ContactFacade } ``` -The `sendMessage()` method will create and send the email. It uses a so-called mailer to do this, which it passes as a dependency via the constructor. Read more about [sending emails |mail:]. +The `sendMessage()` method creates and sends the email. It utilizes a mailer service for this, which it receives as a dependency via the constructor. Read more about [sending emails |mail:]. -Now, we'll go back to the presenter and complete the `contactFormSucceeded()` method. It calls the `sendMessage()` method of the `ContactFacade` class and passes it the form data. And how do we get the `ContactFacade` object ? We'll have it passed to us by the constructor: +Now, let's return to the presenter and complete the `contactFormSucceeded()` method. It will call the `sendMessage()` method of the `ContactFacade` class, passing the data submitted through the form. And how do we obtain the `ContactFacade` object? We'll request it via the constructor using dependency injection: ```php use App\Model\ContactFacade; @@ -102,22 +97,22 @@ class HomePresenter extends Presenter public function contactFormSucceeded(stdClass $data): void { $this->facade->sendMessage($data->email, $data->name, $data->message); - $this->flashMessage('The message has been sent'); + $this->flashMessage('Message has been sent'); $this->redirect('this'); } } ``` -After the email is sent, we show the user the so-called [flash message |application:components#flash-messages], confirming that the message has been sent, and then redirect to the next page so that the form cannot be resubmitted using *refresh* in the browser. +After the email is sent, we display a [flash message |application:components#Flash Messages] to the user confirming the submission. Then, we redirect to prevent the form from being resubmitted via browser refresh. -Well, if everything works, you should be able to send an email from your contact form. Congratulations! +So, if everything is set up correctly, you should now be able to send an email from your contact form. Congratulations! HTML Email Template ------------------- -For now, a plain text email containing only the message sent by the form is sent. But we can use HTML in the email and make it more attractive. We will create a template for it in Latte, which we will save into `app/Model/contactEmail.latte`: +Currently, a plain text email containing only the message submitted via the form is sent. However, we can use HTML in the email to make its appearance more appealing. We'll create a template for it in Latte and save it as `app/Model/contactEmail.latte`: ```latte <html> @@ -131,7 +126,7 @@ For now, a plain text email containing only the message sent by the form is sent </html> ``` -It remains to modify `ContactFacade` to use this template. In the constructor, we request the `LatteFactory` class, which can produce the `Latte\Engine` object, a [Latte template renderer |latte:develop#how-to-render-a-template]. We use the `renderToString()` method to render the template to a file, the first parameter is the path to the template and the second is the variables. +It remains to modify `ContactFacade` to use this template. In the constructor, we'll request the `LatteFactory` class, which can create a `Latte\Engine` object, the [Latte template renderer |latte:develop#How to Render a Template]. Using the `renderToString()` method, we render the template into a string. The first parameter is the path to the template file, and the second is an array of variables to pass to it. ```php namespace App\Model; @@ -167,15 +162,15 @@ class ContactFacade } ``` -We then pass the generated HTML email to the `setHtmlBody()` method instead of the original `setBody()`. We also don't need to specify the subject of the email in `setSubject()`, because the library takes it from the element `<title>` in template. +We then pass the generated HTML email content to the `setHtmlBody()` method instead of the original `setBody()`. We also don't need to specify the email subject using `setSubject()`, as the library automatically extracts it from the `<title>` element within the template. Configuring ----------- -In the `ContactFacade` class code, our admin email `admin@example.com` is still hardcoded. It would be better to move it to the configuration file. How to do it? +In the `ContactFacade` class code, our administrator email `admin@example.com` is still hardcoded. It would be better to move this into the configuration file. How can we do that? -First, we modify the `ContactFacade` class and replace the email string with a variable passed by the constructor: +First, modify the `ContactFacade` class, replacing the hardcoded email string with a variable passed through the constructor: ```php class ContactFacade @@ -199,21 +194,21 @@ class ContactFacade } ``` -And the second step is to put the value of this variable in the configuration. In the `app/config/services.neon` file we add: +The second step is to provide the value for this variable in the configuration. In the `app/config/services.neon` file, add: ```neon services: - App\Model\ContactFacade(adminEmail: admin@example.com) ``` -And that's it. If there are a lot of items in the `services` section and you feel like the email is getting lost among them, we can make it a variable. We'll modify the entry to: +And that's it. If the `services` section contains many items and you feel the email address gets lost among them, we can turn it into a parameter. Modify the entry like this: ```neon services: - App\Model\ContactFacade(adminEmail: %adminEmail%) ``` -And define this variable in the `app/config/common.neon` file: +And define this parameter in the `app/config/common.neon` file: ```neon parameters: @@ -221,6 +216,3 @@ parameters: ``` And it's done! - - -{{sitename: Best Practices}} diff --git a/best-practices/en/microsites.texy b/best-practices/en/microsites.texy new file mode 100644 index 0000000000..cc4e038ff6 --- /dev/null +++ b/best-practices/en/microsites.texy @@ -0,0 +1,63 @@ +How to Write Microsites +*********************** + +Imagine needing to quickly create a small website for your company's upcoming event. It needs to be simple, fast, and without unnecessary complications. You might think a robust framework isn't necessary for such a small project. But what if using the Nette Framework could actually simplify and accelerate this process? + +Even when creating simple websites, you don't want to sacrifice convenience. You don't want to reinvent what has already been solved. Feel free to be lazy and let yourself be pampered. The Nette Framework is also excellent for use as a micro-framework. + +What might such a microsite look like? For instance, the entire website code could reside in a single `index.php` file within the public directory: + +```php +<?php + +require __DIR__ . '/../vendor/autoload.php'; + +$configurator = new Nette\Bootstrap\Configurator; +$configurator->enableTracy(__DIR__ . '/../log'); +$configurator->setTempDirectory(__DIR__ . '/../temp'); + +// create DI container based on configuration in config.neon +$configurator->addConfig(__DIR__ . '/../app/config.neon'); +$container = $configurator->createContainer(); + +// set up routing +$router = new Nette\Application\Routers\RouteList; +$container->addService('router', $router); + +// route for URL https://example.com/ +$router->addRoute('', function ($presenter, Nette\Http\Request $httpRequest) { + // detect browser language and redirect to URL /en or /de etc. + $supportedLangs = ['en', 'de', 'cs']; + $lang = $httpRequest->detectLanguage($supportedLangs) ?: reset($supportedLangs); + $presenter->redirectUrl("/$lang"); +}); + +// route for URL https://example.com/cs or https://example.com/en +$router->addRoute('<lang cs|en>', function ($presenter, string $lang) { + // display the appropriate template, for example ../templates/en.latte + $template = $presenter->createTemplate() + ->setFile(__DIR__ . '/../templates/' . $lang . '.latte'); + return $template; +}); + +// run the application! +$container->getByType(Nette\Application\Application::class)->run(); +``` + +Everything else will be templates stored in the parent `/templates` directory. + +The PHP code in `index.php` first [sets up the environment |bootstrap:], then defines [routes |application:routing#Dynamic Routing with Callbacks], and finally runs the application. The advantage is that the second parameter of the `addRoute()` function can be a callable, which gets executed when the corresponding page is accessed. + + +Why use Nette for Microsites? +----------------------------- + +- Programmers who have tried [Tracy|tracy:] can hardly imagine coding without it today. +- Above all, you'll benefit from the [Latte|latte:] templating system, because even with just two pages, you'll want to separate the [layout and content|latte:template-inheritance]. +- And you definitely want to rely on [automatic escaping |latte:safety-first] to prevent XSS vulnerabilities. +- Nette also ensures that in case of an error, raw PHP error messages are never displayed; instead, a user-friendly page is shown. +- If you want to gather user feedback, perhaps through a contact form, you can easily add [forms|forms:] and [database|database:] support. +- You can also easily have completed forms [sent via email|mail:]. +- Sometimes, [caching|caching:] might be useful, for example, when downloading and displaying feeds. + +In today's fast-paced world, where speed and efficiency are crucial, having tools that enable you to achieve results without unnecessary delays is vital. The Nette Framework offers precisely that – rapid development, security, and a wide array of tools like Tracy and Latte that streamline the process. Just install a few Nette packages, and building such a microsite becomes incredibly easy. And you can be confident there are no hidden security vulnerabilities. diff --git a/best-practices/en/pagination.texy b/best-practices/en/pagination.texy index c0c0007e9b..7f5e0db062 100644 --- a/best-practices/en/pagination.texy +++ b/best-practices/en/pagination.texy @@ -2,16 +2,15 @@ Paginating Database Results *************************** .[perex] -When developing web applications, you often meet with the requirement to print out a restricted number of records on a page. +When developing web applications, you often encounter the requirement to limit the number of listed items per page, a technique known as pagination. -We come out of the state when we list all the data without paging. To select data from the database, we have the ArticleRepository class, which contains the constructor and the `findPublishedArticles` method, which returns all published articles sorted in descending order of publication date. +Let's start from a state where we list all data without pagination. For selecting data from the database, we have an `ArticleRepository` class. Besides the constructor, it contains a `findPublishedArticles` method that returns all published articles sorted descending by publication date. ```php namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -31,10 +30,10 @@ class ArticleRepository } ``` -In the Presenter we then inject the model class and in the render method we will ask for the published articles that we pass to the template: +In the presenter, we then inject this model class. In the render method, we retrieve the published articles and pass them to the template: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -53,7 +52,7 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -In the template, we will take care of rendering an articles list: +The `default.latte` template will then take care of listing the articles: ```latte {block content} @@ -68,11 +67,11 @@ In the template, we will take care of rendering an articles list: ``` -In this way, we can write all articles, but this will cause problems when the number of articles grows. At that point, it will be useful to implement the paging mechanism. +This way, we can list all articles, but this becomes problematic as the number of articles increases. At that point, implementing a pagination mechanism becomes useful. -This will ensure that all articles are split into several pages and we will only show the articles of one current page. The total number of pages and the distribution of the articles is calculated by [utils:Paginator] itself, depending on how many articles we have in total and how many articles we want to display on the page. +This mechanism divides all articles into several pages, and we only display the articles belonging to the currently selected page. The total number of pages and the division of articles are calculated by the [Paginator |utils:Paginator] utility based on the total number of articles and the desired number of articles per page. -In the first step, we will modify the method for getting articles in the repository class to return only single-page articles. We will also add a new method to get the total number of articles in the database, which we will need to set a Paginator: +In the first step, we'll modify the article retrieval method in the repository class so it can return articles for just one page. We'll also add a method to get the total count of articles in the database, which is needed to configure the Paginator: ```php namespace App\Model; @@ -109,12 +108,12 @@ class ArticleRepository } ``` -The next step is to edit the presenter. We will forward the number of the currently displayed page to the render method. In the case that this number is not part of the URL, we need to set the default value to the first page. +Next, let's modify the presenter. We'll pass the current page number to the `renderDefault` method. If this number isn't part of the URL, we'll set a default value of 1 (the first page). -We also expand the render method to get the Paginator instance, setting it up, and selecting the correct articles to display in the template. HomePresenter will look like this: +We'll also extend the render method to create and configure a Paginator instance and select the appropriate articles for display in the template. The modified `HomePresenter` will look like this: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -128,27 +127,27 @@ class HomePresenter extends Nette\Application\UI\Presenter public function renderDefault(int $page = 1): void { - // We'll find the total number of published articles + // Get the total number of published articles $articlesCount = $this->articleRepository->getPublishedArticlesCount(); - // We'll make the Paginator instance and set it up + // Create and configure the Paginator instance $paginator = new Nette\Utils\Paginator; - $paginator->setItemCount($articlesCount); // total articles count + $paginator->setItemCount($articlesCount); // total item count $paginator->setItemsPerPage(10); // items per page - $paginator->setPage($page); // actual page number + $paginator->setPage($page); // current page number - // We'll find a limited set of articles from the database based on Paginator's calculations + // Fetch a limited set of articles from the database based on Paginator's calculation $articles = $this->articleRepository->findPublishedArticles($paginator->getLength(), $paginator->getOffset()); - // which we pass to the template + // pass them to the template $this->template->articles = $articles; - // and also Paginator itself to display paging options + // and also the Paginator itself for displaying pagination controls $this->template->paginator = $paginator; } } ``` -The template already iterates over articles in one page, just add paging links: +The template now iterates only over the articles for the current page. We just need to add the pagination links: ```latte {block content} @@ -181,16 +180,15 @@ The template already iterates over articles in one page, just add paging links: ``` -This is how we've added pagination using Paginator. If [Nette Database Explorer |database:explorer] is used instead of [Nette Database Core |database:core] as a database layer, we are able to implement paging even without Paginator. The `Nette\Database\Table\Selection` class contains the [page |api:Nette\Database\Table\Selection::_ page] method with pagination logic taken from the Paginator. +This completes the pagination implementation using the Paginator. If you use [Nette Database Explorer |database:explorer] instead of [Nette Database Core |database:sql-way] as your database layer, you can implement pagination even without using the Paginator utility directly. The `Nette\Database\Table\Selection` class includes a [page() |api:Nette\Database\Table\Selection::page] method that incorporates the pagination logic. -The repository will look like this: +With this approach, the repository will look like this: ```php namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -198,7 +196,6 @@ class ArticleRepository ) { } - public function findPublishedArticles(): Nette\Database\Table\Selection { return $this->database->table('articles') @@ -208,10 +205,10 @@ class ArticleRepository } ``` -We do not have to create Paginator in the Presenter, instead we will use the method of `Selection` object returned by the repository: +In the presenter, we don't need to create a Paginator instance. Instead, we'll use the `page()` method provided by the `Selection` object returned from the repository: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -225,21 +222,21 @@ class HomePresenter extends Nette\Application\UI\Presenter public function renderDefault(int $page = 1): void { - // We'll find published articles + // Fetch the published articles $articles = $this->articleRepository->findPublishedArticles(); - // and their part limited by page method calculation we'll pass to the template + // and pass only their portion limited by the page method calculation to the template $lastPage = 0; $this->template->articles = $articles->page($page, 10, $lastPage); - // and the necessary data to display paging options as well + // and also the necessary data for displaying pagination options $this->template->page = $page; $this->template->lastPage = $lastPage; } } ``` -Because we do not use Paginator, we need to edit the section showing the paging links: +Since we are no longer passing the Paginator object to the template, we need to adjust the part that displays the pagination links: ```latte {block content} @@ -271,7 +268,6 @@ Because we do not use Paginator, we need to edit the section showing the paging </div> ``` -In this way, we implemented a paging mechanism without using a Paginator. +This way, we've implemented the pagination mechanism without explicitly using the Paginator utility. {{priority: -1}} -{{sitename: Best Practices}} diff --git a/best-practices/en/passing-settings-to-presenters.texy b/best-practices/en/passing-settings-to-presenters.texy index 08a6b619b8..4a4bc51594 100644 --- a/best-practices/en/passing-settings-to-presenters.texy +++ b/best-practices/en/passing-settings-to-presenters.texy @@ -2,9 +2,9 @@ Passing Settings to Presenters ****************************** .[perex] -Do you need to pass arguments to presenters that are not objects (e.g. information about whether it is running in debug mode, directory paths, etc.) and thus cannot be passed automatically by autowiring? The solution is to encapsulate them in a `Settings` object. +Do you need to pass non-object arguments to presenters (like a flag indicating debug mode, directory paths, etc.) which cannot be automatically passed via autowiring? The solution is to encapsulate them within a dedicated `Settings` object. -The `Settings` service is a very easy yet useful way to provide information about a running application to presenters. Its specific form depends entirely on your particular needs. Example: +The `Settings` service provides a very simple yet effective way to supply information about the running application to presenters. Its specific structure depends entirely on your particular needs. Example: ```php namespace App; @@ -12,7 +12,7 @@ namespace App; class Settings { public function __construct( - // since PHP 8.1 it is possible to specify readonly + // since PHP 8.1, readonly can be used public bool $debugMode, public string $appDir, // and so on @@ -20,7 +20,7 @@ class Settings } ``` -Example of registration to the configuration: +Example of registering it in the configuration: ```neon services: @@ -30,7 +30,7 @@ services: ) ``` -When the presenter needs the information provided by this service, he simply asks for it in the constructor: +When a presenter requires the information provided by this service, it simply requests it in its constructor: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -47,5 +47,3 @@ class MyPresenter extends Nette\Application\UI\Presenter } } ``` - -{{sitename: Best Practices}} diff --git a/best-practices/en/post-links.texy b/best-practices/en/post-links.texy new file mode 100644 index 0000000000..3bcb6628a7 --- /dev/null +++ b/best-practices/en/post-links.texy @@ -0,0 +1,56 @@ +How to Properly Use POST Links +****************************** + +.[perex] +In web applications, particularly in administrative interfaces, a fundamental rule should be that actions modifying server state are not performed using the HTTP GET method. As the name implies, GET should only be used for retrieving data, not altering it. For actions like deleting records, using the POST method is more appropriate. While the DELETE method would be ideal, it cannot be invoked without JavaScript, which is why POST has historically been used for such actions. + +How to implement this in practice? Use this simple trick. At the beginning of your layout template, create a helper form with the ID `postForm`. You will then use this form for actions like delete buttons: + +```latte .{file:@layout.latte} +<form method="post" id="postForm"></form> +``` + +Thanks to this form, instead of a standard `<a>` link, you can use a `<button>`. This button can be styled to look like a regular link. For example, the Bootstrap CSS framework provides the `btn btn-link` classes, making the button visually indistinguishable from other links. Using the `form="postForm"` attribute, link the button to the prepared helper form: + +```latte .{file:admin.latte} +<table> + <tr n:foreach="$posts as $post"> + <td>{$post->title}</td> + <td> + <button class="btn btn-link" form="postForm" formaction="{link delete $post->id}">delete</button> + <!-- instead of <a n:href="delete $post->id">delete</a> --> + </td> + </tr> +</table> +``` + +Clicking this button now invokes the `delete` action. To ensure requests are accepted only via the POST method and originate from the same domain (an effective defense against CSRF attacks), use the `#[Requires]` attribute: + +```php .{file:AdminPresenter.php} +use Nette\Application\Attributes\Requires; + +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST', sameOrigin: true)] + public function actionDelete(int $id): void + { + $this->facade->deletePost($id); // hypothetical code for deleting a record + $this->redirect('default'); + } +} +``` + +This attribute is available since Nette Application 3.2. You can learn more about its capabilities on the [How to Use the #Requires Attribute |attribute-requires] page. + +If you were using the `handleDelete()` signal instead of the `actionDelete()` action, specifying `sameOrigin: true` is unnecessary, as signals have this protection enabled by default: + +```php .{file:AdminPresenter.php} +#[Requires(methods: 'POST')] +public function handleDelete(int $id): void +{ + $this->facade->deletePost($id); + $this->redirect('this'); +} +``` + +This approach not only enhances your application's security but also promotes adherence to proper web standards and practices. Using POST methods for state-changing actions results in a more robust and secure application. diff --git a/best-practices/en/presenter-traits.texy b/best-practices/en/presenter-traits.texy index 15edbdb7bd..2db62c1dce 100644 --- a/best-practices/en/presenter-traits.texy +++ b/best-practices/en/presenter-traits.texy @@ -2,13 +2,13 @@ Composing Presenters from Traits ******************************** .[perex] -If we need to implement the same code in multiple presenters (e.g. verification that the user is logged in), it is tempting to place the code in a common ancestor. The second option is to create single-purpose traits. +If you need to implement the same functionality in multiple presenters (e.g., verifying user login), placing the code in a common ancestor is a common approach. Another option is to create single-purpose [traits |nette:introduction-to-object-oriented-programming#Traits]. -The advantage of this solution is that each presenter can use just the traits it actually needs, while multiple inheritance is not possible in PHP. +The advantage of using traits is that each presenter can incorporate only the traits it actually needs, especially since multiple inheritance is not supported in PHP. -These traits can take advantage of the fact that all [inject methods|inject-method-attribute#inject methods] are called sequentially when the presenter is created. You just need to make sure that the name of each inject method is unique. +These traits can leverage the fact that all [inject methods |inject-method-attribute#inject Methods] are called sequentially when the presenter instance is created. You just need to ensure that the name of each inject method is unique across all used traits and the presenter itself. -Traits can hang the initialization code into [onStartup or onRender |application:presenters#Events] events. +Traits can attach initialization code to the [onStartup or onRender |application:presenters#Events] events. Examples: @@ -45,6 +45,3 @@ class ArticlePresenter extends Nette\Application\UI\Presenter use RequireLoggedUser; } ``` - - -{{sitename: Best Practices}} diff --git a/best-practices/en/restore-request.texy b/best-practices/en/restore-request.texy index 04f407afc6..cea14d7b66 100644 --- a/best-practices/en/restore-request.texy +++ b/best-practices/en/restore-request.texy @@ -2,16 +2,15 @@ How to Return to an Earlier Page? ********************************* .[perex] -What if a user fills out a form and his login expires? To avoid losing the data, we save the data in the session before redirecting to the login page. In Nette, this is a piece of cake. +What happens if a user is filling out a form and their login session expires? To prevent data loss, we can save the current request (including form data) to the session before redirecting to the login page. In Nette, this is surprisingly easy. -The current request can be stored in the session using the `storeRequest()` method, which returns its identifier as a short string. The method stores the name of the current presenter, the view and its parameters. -If a form was also submitted, the values of the fields (except for the uploaded files) are also saved. +The current request can be stored in the session using the `storeRequest()` method. This method returns a unique identifier (a short string) for the stored request. The method saves the name of the current presenter, its view, and its parameters. If a form was submitted as part of the request, the values entered into the fields (excluding uploaded files) are also saved. -The request is restored by the `restoreRequest($key)` method, to which we pass the retrieved identifier. This redirects to the original presenter and view. However, if the saved request contains a form submission, it will forward to the original presenter using method `forward()`, pass the previously filled values to the form and let it be redrawn. This allows the user to resubmit the form and no data is lost. +The request is restored using the `restoreRequest($key)` method, to which you pass the previously obtained identifier. This method redirects the user back to the original presenter and view. However, if the stored request included a form submission, `restoreRequest()` uses the `forward()` method instead of redirecting. It passes the previously filled values back to the form and allows it to be rendered again. This allows the user to resubmit the form without losing any entered data. -Importantly, `restoreRequest()` checks that the newly logged in user is the same one who originally filled out the form. If not, it discards the request and does nothing. +Crucially, `restoreRequest()` verifies that the newly logged-in user is the same user who originally submitted the form. If the user is different, the stored request is discarded, and the method does nothing, enhancing security. -Let's demonstrate everything with an example. Let's have a presenter `AdminPresenter` in which data is being edited and whose method `startup()` checks if the user is logged in. If he is not, we redirect him to `SignPresenter`. At the same time, we save the current request and send its key to `SignPresenter`. +Let's illustrate this with an example. Consider an `AdminPresenter` where data is edited. Its `startup()` method verifies if the user is logged in. If not, the user is redirected to `SignPresenter`. Simultaneously, we store the current request using `storeRequest()` and pass its key (the `$backlink`) to `SignPresenter`. ```php class AdminPresenter extends Nette\Application\UI\Presenter @@ -27,7 +26,7 @@ class AdminPresenter extends Nette\Application\UI\Presenter } ``` -The `SignPresenter` presenter will contain a persistent `$backlink` parameter to which the key is written, in addition to the log-in form. Since the parameter is persistent, it will be carried over even after the login form is submitted. +The `SignPresenter` will contain, in addition to the login form, a persistent parameter `$backlink` where the key is stored. Because the parameter is persistent, its value is retained even after the login form is submitted. ```php @@ -42,13 +41,13 @@ class SignPresenter extends Nette\Application\UI\Presenter { $form = new Nette\Application\UI\Form; // ... add form fields ... - $form->onSuccess[] = [$this, 'signInFormSubmitted']; + $form->onSuccess[] = $this->signInFormSubmitted(...); return $form; } - public function signInFormSubmitted($form) + private function signInFormSubmitted($form) { - // ... here we sign the user in ... + // ... log the user in here ... $this->restoreRequest($this->backlink); $this->redirect('Admin:'); @@ -56,9 +55,8 @@ class SignPresenter extends Nette\Application\UI\Presenter } ``` -We pass the key of the saved request to the `restoreRequest()` method and it redirects (or forwards) to the original presenter. +We pass the key (`$this->backlink`) of the stored request to the `restoreRequest()` method. It then redirects (or forwards) the user back to the original presenter and view. -However, if the key is invalid (for example, no longer exists in the session), the method does nothing. So the next call is `$this->redirect('Admin:')`, which redirects to `AdminPresenter`. +However, if the key is invalid (e.g., it has expired from the session), the method does nothing. Therefore, the subsequent call `$this->redirect('Admin:')` acts as a fallback, redirecting to a default page like `AdminPresenter`. {{priority: -1}} -{{sitename: Best Practices}} diff --git a/best-practices/es/@home.texy b/best-practices/es/@home.texy index 2bbf67aa4b..6a6ee9df47 100644 --- a/best-practices/es/@home.texy +++ b/best-practices/es/@home.texy @@ -1,8 +1,8 @@ -Buenas prácticas -**************** +Tutoriales y procedimientos +*************************** .[perex] -Tutoriales, soluciones a problemas comunes y mejores prácticas para Nette. +Tutoriales, soluciones a tareas comunes y *best practices* para Nette. <div class=documentation> @@ -11,12 +11,14 @@ Tutoriales, soluciones a problemas comunes y mejores prácticas para Nette. Aplicación Nette ---------------- -- [Inyectar métodos y atributos |inject-method-attribute] -- [Componer presentadores a partir de rasgos |presenter-traits] -- [Pasar configuraciones a los presentadores|passing-settings-to-presenters] +- [Métodos y atributos inject |inject-method-attribute] +- [Composición de presenters a partir de traits |presenter-traits] +- [Pasar configuraciones a los presenters |passing-settings-to-presenters] - [Cómo volver a una página anterior |restore-request] -- [Paginación de los resultados de la base de datos |Pagination] -- [Fragmentos dinámicos |dynamic-snippets] +- [Paginación de resultados de base de datos |pagination] +- [Snippets dinámicos |dynamic-snippets] +- [Cómo usar el atributo #Requires |attribute-requires] +- [Cómo usar correctamente los enlaces POST |post-links] </div> <div> @@ -25,32 +27,34 @@ Aplicación Nette Formularios ----------- - [Reutilización de formularios |form-reuse] -- [Formulario de creación y edición de registros |creating-editing-form] -- [Creemos un formulario de contacto |lets-create-contact-form] -- [Casillas de selección dependientes |https://blog.nette.org/es/selectboxes-dependientes-elegantemente-en-nette-y-js-puro] +- [Formulario para crear y editar registros |creating-editing-form] +- [Creando un formulario de contacto |lets-create-contact-form] +- [Selectboxes dependientes |https://blog.nette.org/es/dependent-selectboxes-elegantly-in-nette-and-pure-js] </div> <div> -Común ------ -- [Cómo cargar el fichero de configuración |bootstrap:] -- [¿Por qué Nette utiliza la notación constante PascalCase? |https://blog.nette.org/es/para-menos-gritos-en-el-codigo] -- [¿Por qué Nette no utiliza el sufijo Interface? |https://blog.nette.org/es/los-prefijos-y-sufijos-no-pertenecen-a-los-nombres-de-interfaz] -- [Consejos de uso de Composer |composer] +General +------- +- [Cómo cargar un archivo de configuración |bootstrap:] +- [Cómo escribir micro-sitios web |microsites] +- [¿Por qué Nette usa la notación PascalCase para las constantes? |https://blog.nette.org/es/for-less-screaming-in-the-code] +- [¿Por qué Nette no usa el sufijo Interface? |https://blog.nette.org/es/prefixes-and-suffixes-do-not-belong-in-interface-names] +- [Composer: consejos para su uso |composer] - [Consejos sobre editores y herramientas |editors-and-tools] +- [Introducción a la programación orientada a objetos |nette:introduction-to-object-oriented-programming] </div> <div> -Solución de muestra +Solución de ejemplo ------------------- -- [Ejemplos de Nette|https://github.com/nette-examples] -- [Doctrina y Nette|https://contributte.org/nettrine/] -- [Ejemplos de Contributte |https://contributte.org/examples.html] -- [Sitio web de Doctrine ORM |https://github.com/MinecordNetwork/Website] +- [Nette examples |https://github.com/nette-examples] +- [Doctrine & Nette |https://contributte.org/nettrine/] +- [Contributte examples |https://contributte.org/examples.html] +- [Doctrine ORM Website |https://github.com/MinecordNetwork/Website] - [Inicio rápido |quickstart:] </div> @@ -59,10 +63,7 @@ Solución de muestra Vídeos ------ -Puedes encontrar cientos de grabaciones de Posobota y vídeos sobre Nette bajo un mismo techo en el "Nette Framework YouTube Channel":https://www.youtube.com/user/NetteFramework. +Cientos de grabaciones de los Últimos Sábados y vídeos sobre Nette se pueden encontrar bajo un mismo techo en el "Canal de Youtube de Nette Framework":https://www.youtube.com/user/NetteFramework. </div> </div> - -{{sitename: Buenas prácticas}} -{{leftbar: www:@menu-common}} diff --git a/best-practices/es/@meta.texy b/best-practices/es/@meta.texy new file mode 100644 index 0000000000..524cb19ad0 --- /dev/null +++ b/best-practices/es/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Tutoriales y procedimientos}} +{{leftbar: www:@menu-common}} diff --git a/best-practices/es/attribute-requires.texy b/best-practices/es/attribute-requires.texy new file mode 100644 index 0000000000..0eebe50721 --- /dev/null +++ b/best-practices/es/attribute-requires.texy @@ -0,0 +1,177 @@ +Cómo usar el atributo `#[Requires]` +*********************************** + +.[perex] +Cuando escribe una aplicación web, a menudo se encuentra con la necesidad de restringir el acceso a ciertas partes de su aplicación. Quizás quiera que algunas peticiones solo puedan enviar datos mediante un formulario (es decir, con el método POST), o que sean accesibles solo para llamadas AJAX. En Nette Framework 3.2 apareció una nueva herramienta que le permitirá establecer tales restricciones de manera muy elegante y clara: el atributo `#[Requires]`. + +Un atributo es una marca especial en PHP que agrega antes de la definición de una clase o método. Como en realidad es una clase, para que los siguientes ejemplos funcionen, es necesario indicar la cláusula use: + +```php +use Nette\Application\Attributes\Requires; +``` + +Puede usar el atributo `#[Requires]` en la propia clase del presenter y también en estos métodos: + +- `action<Action>()` +- `render<View>()` +- `handle<Signal>()` +- `createComponent<Name>()` + +Los dos últimos métodos también se aplican a los componentes, por lo que también puede usar el atributo en ellos. + +Si no se cumplen las condiciones que indica el atributo, se producirá un error HTTP 4xx. + + +Métodos HTTP +------------ + +Puede especificar qué métodos HTTP (como GET, POST, etc.) están permitidos para el acceso. Por ejemplo, si desea permitir el acceso solo enviando un formulario, establezca: + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +¿Por qué debería usar POST en lugar de GET para acciones que cambian el estado y cómo hacerlo? [Lea el tutorial |post-links]. + +Puede indicar un método o un array de métodos. Un caso especial es el valor `'*'`, que permite todos los métodos, lo cual los presenters estándarmente [no permiten por razones de seguridad |application:presenters#Verificación del método HTTP]. + + +Llamada AJAX +------------ + +Si desea que el presenter o el método esté disponible solo para peticiones AJAX, use: + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Mismo origen +------------ + +Para aumentar la seguridad, puede requerir que la petición se realice desde el mismo dominio. Con esto evitará la [vulnerabilidad CSRF |nette:vulnerability-protection#Cross-Site Request Forgery CSRF]: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Para los métodos `handle<Signal>()`, el acceso desde el mismo dominio se requiere automáticamente. Así que si, por el contrario, desea permitir el acceso desde cualquier dominio, indique: + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Acceso a través de forward +-------------------------- + +A veces es útil restringir el acceso a un presenter para que esté disponible solo indirectamente, por ejemplo, usando el método `forward()` o `switch()` desde otro presenter. Así se protegen, por ejemplo, los error-presenters, para que no sea posible invocarlos desde la URL: + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +En la práctica, a menudo es necesario marcar ciertas vistas a las que solo se puede acceder en función de la lógica en el presenter. Es decir, nuevamente, para que no sea posible abrirlas directamente: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = $this->facade->getProduct($id); + if (!$product) { + $this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Acciones específicas +-------------------- + +También puede restringir que cierto código, como la creación de un componente, esté disponible solo para acciones específicas en el presenter: + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +En caso de una sola acción, no es necesario escribir un array: `#[Requires(actions: 'default')]` + + +Atributos personalizados +------------------------ + +Si desea usar el atributo `#[Requires]` repetidamente con la misma configuración, puede crear su propio atributo que herede `#[Requires]` y lo configure según sus necesidades. + +Por ejemplo, `#[SingleAction]` permitirá el acceso solo a través de la acción `default`: + +```php +#[\Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +O `#[RestMethods]` permitirá el acceso a través de todos los métodos HTTP utilizados para la API REST: + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Conclusión +---------- + +El atributo `#[Requires]` le da una gran flexibilidad y control sobre cómo son accesibles sus páginas web. Usando reglas simples pero potentes, puede aumentar la seguridad y el correcto funcionamiento de su aplicación. Como puede ver, el uso de atributos en Nette no solo puede facilitar su trabajo, sino también asegurarlo. diff --git a/best-practices/es/composer.texy b/best-practices/es/composer.texy index c550b6a92f..20959bbc87 100644 --- a/best-practices/es/composer.texy +++ b/best-practices/es/composer.texy @@ -1,44 +1,44 @@ -Consejos de uso de Composer -*************************** +Composer: consejos para su uso +****************************** <div class=perex> -Composer es una herramienta para la gestión de dependencias en PHP. Te permite declarar las librerías de las que depende tu proyecto y las instalará y actualizará por ti. Aprenderemos: +Composer es una herramienta para gestionar dependencias en PHP. Nos permite enumerar las librerías de las que depende nuestro proyecto, y las instalará y actualizará por nosotros. Mostraremos: - cómo instalar Composer -- usarlo en un proyecto nuevo o existente +- su uso en un proyecto nuevo o existente </div> -Instalación .[#toc-installation] -================================ +Instalación +=========== -Composer es un archivo ejecutable `.phar` que se descarga e instala de la siguiente manera. +Composer es un archivo `.phar` ejecutable, que descarga e instala de la siguiente manera: -Windows .[#toc-windows] ------------------------ +Windows +------- -Utilice el instalador oficial [Composer-Setup.exe |https://getcomposer.org/Composer-Setup.exe]. +Use el instalador oficial [Composer-Setup.exe |https://getcomposer.org/Composer-Setup.exe]. -Linux, macOS .[#toc-linux-macos] --------------------------------- +Linux, macOS +------------ -Todo lo que necesitas son 4 comandos, que puedes copiar de [esta página |https://getcomposer.org/download/]. +Bastarán 4 comandos, que puede copiar de [esta página |https://getcomposer.org/download/]. -Además, al copiar en la carpeta que se encuentra en el sistema `PATH`, Composer se convierte en accesible a nivel mundial: +Además, insertándolo en una carpeta que esté en el `PATH` del sistema, Composer se volverá accesible globalmente: ```shell -$ mv ./composer.phar ~/bin/composer # or /usr/local/bin/composer +$ mv ./composer.phar ~/bin/composer # o /usr/local/bin/composer ``` -Uso en el proyecto .[#toc-use-in-project] -========================================= +Uso en el proyecto +================== -Para empezar a utilizar Composer en su proyecto, todo lo que necesita es un archivo `composer.json`. Este archivo describe las dependencias de tu proyecto y puede contener también otros metadatos. El `composer.json` más simple puede tener este aspecto: +Para poder empezar a usar Composer en su proyecto, necesita solo el archivo `composer.json`. Este describe las dependencias de nuestro proyecto y también puede contener otros metadatos. Un `composer.json` básico, por lo tanto, puede verse así: ```js { @@ -48,17 +48,17 @@ Para empezar a utilizar Composer en su proyecto, todo lo que necesita es un arch } ``` -Estamos diciendo aquí, que nuestra aplicación (o biblioteca) depende del paquete `nette/database` (el nombre del paquete consiste en un nombre de proveedor y el nombre del proyecto) y quiere la versión que coincida con la restricción de versión `^3.0`. +Aquí decimos que nuestra aplicación (o librería) requiere el paquete `nette/database` (el nombre del paquete se compone del nombre de la organización y el nombre del proyecto) y quiere una versión que cumpla la condición `^3.0` (es decir, la última versión 3). -Entonces, cuando tenemos el archivo `composer.json` en la raíz del proyecto y ejecutamos: +Tenemos, por lo tanto, en la raíz del proyecto el archivo `composer.json` y ejecutamos la instalación: ```shell composer update ``` -Composer descargará la base de datos Nette en el directorio `vendor`. También creará un archivo `composer.lock`, que contiene información sobre las versiones exactas de las librerías que ha instalado. +Composer descargará Nette Database en la carpeta `vendor/`. Además, creará el archivo `composer.lock`, que contiene información sobre qué versiones exactas de las librerías instaló. -Composer genera un archivo `vendor/autoload.php`. Puedes simplemente incluir este archivo y empezar a usar las clases que esas librerías proporcionan sin ningún trabajo extra: +Composer generará el archivo `vendor/autoload.php`, que podemos simplemente incluir y empezar a usar las librerías sin ningún trabajo adicional: ```php require __DIR__ . '/vendor/autoload.php'; @@ -67,46 +67,46 @@ $db = new Nette\Database\Connection('sqlite::memory:'); ``` -Actualizar paquetes a las últimas versiones .[#toc-update-packages-to-the-latest-versions] -========================================================================================== +Actualización de paquetes a las últimas versiones +================================================= -Para actualizar todos los paquetes utilizados a la última versión según las restricciones de versión definidas en `composer.json` utilice el comando `composer update`. Por ejemplo, para la dependencia `"nette/database": "^3.0"` instalará la última versión 3.x.x, pero no la versión 4. +La actualización de las librerías usadas a las últimas versiones según las condiciones definidas en `composer.json` está a cargo del comando `composer update`. Por ejemplo, para la dependencia `"nette/database": "^3.0"` instalará la última versión 3.x.x, pero ya no la versión 4. -Para actualizar las restricciones de versión en el archivo `composer.json` a, por ejemplo, `"nette/database": "^4.1"`, para permitir la instalación de la última versión, utilice el comando `composer require nette/database`. +Para actualizar las condiciones en el archivo `composer.json`, por ejemplo a `"nette/database": "^4.1"`, para poder instalar la última versión, use el comando `composer require nette/database`. -Para actualizar todos los paquetes Nette utilizados, sería necesario listarlos todos en la línea de comandos, p. ej: +Para actualizar todos los paquetes Nette usados sería necesario enumerarlos todos en la línea de comandos, p. ej.: ```shell composer require nette/application nette/forms latte/latte tracy/tracy ... ``` -Lo cual es poco práctico. Por lo tanto, utilice un simple script "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff que lo hará por usted: +Lo cual es poco práctico. Use por lo tanto el script simple "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff, que lo hará por usted: ```shell php composer-frontline.php ``` -Crear nuevo proyecto .[#toc-creating-new-project] -================================================= +Creación de un nuevo proyecto +============================= -Se puede crear un nuevo proyecto Nette ejecutando un simple comando: +Creará un nuevo proyecto en Nette con un solo comando: ```shell -composer create-project nette/web-project name-of-the-project +composer create-project nette/web-project nombre-proyecto ``` -En lugar de `name-of-the-project` debe proporcionar el nombre del directorio para su proyecto y ejecutar el comando. Composer obtendrá el repositorio `nette/web-project` de GitHub, que ya contiene el archivo `composer.json`, y justo después instalará el propio Nette Framework. La única cosa que queda es [comprobar los permisos de escritura |nette:troubleshooting#setting-directory-permissions] en los directorios `temp/` y `log/` y ya está listo para ir. +Como `nombre-proyecto` inserte el nombre del directorio para su proyecto y confirme. Composer descargará el repositorio `nette/web-project` de GitHub, que ya contiene el archivo `composer.json`, e inmediatamente después Nette Framework. Ya debería bastar con [establecer los permisos |nette:troubleshooting#Configuración de permisos de directorio] de escritura en las carpetas `temp/` y `log/` y el proyecto debería cobrar vida. -Si sabes en qué versión de PHP se alojará el proyecto, asegúrate de [configurarlo |#PHP Version]. +Si sabe en qué versión de PHP se alojará el proyecto, no olvide [configurarla |#Versión de PHP]. -Versión PHP .[#toc-php-version] -=============================== +Versión de PHP +============== -Composer siempre instala las versiones de los paquetes que son compatibles con la versión de PHP que está utilizando actualmente (o más bien, la versión de PHP utilizada en la línea de comandos cuando se ejecuta Composer). Que probablemente no es la misma versión que utiliza su proveedor de alojamiento web. Por eso es muy importante añadir información sobre la versión de PHP de tu alojamiento a tu archivo `composer.json`. Después de eso, sólo se instalarán las versiones de paquetes compatibles con el host. +Composer siempre instala aquellas versiones de paquetes que son compatibles con la versión de PHP que está usando actualmente (mejor dicho, con la versión de PHP usada en la línea de comandos al ejecutar Composer). Lo cual, sin embargo, probablemente no sea la misma versión que usa su hosting. Por lo tanto, es muy importante agregar al archivo `composer.json` información sobre la versión de PHP en el hosting. Después, solo se instalarán versiones de paquetes compatibles con el hosting. -Por ejemplo, para configurar el proyecto para que se ejecute en PHP 8.2.3, utilice el comando: +Que el proyecto se ejecutará, por ejemplo, en PHP 8.2.3, lo configuramos con el comando: ```shell composer config platform.php 8.2.3 @@ -124,8 +124,7 @@ Así se escribe la versión en el archivo `composer.json`: } ``` -Sin embargo, el número de versión de PHP también aparece en otra parte del archivo, en la sección `require`. Mientras que el primer número especifica la versión para la que se instalarán los paquetes, el segundo indica para qué versión está escrita la aplicación. -(Por supuesto, no tiene sentido que estas versiones sean diferentes, así que la doble entrada es una redundancia). Esta versión se establece con el comando +Sin embargo, el número de versión de PHP se indica también en otro lugar del archivo, en la sección `require`. Mientras que el primer número determina para qué versión se instalarán los paquetes, el segundo número dice para qué versión está escrita la propia aplicación. Y según él, por ejemplo, PhpStorm establece el *PHP language level*. (Por supuesto, no tiene sentido que estas versiones difieran, por lo que la doble escritura es una falta de previsión.) Esta versión la establece con el comando: ```shell composer require php 8.2.3 --no-update @@ -142,66 +141,72 @@ O directamente en el archivo `composer.json`: ``` -Informes falsos .[#toc-false-reports] -===================================== +Ignorar la versión de PHP +========================= + +Los paquetes generalmente suelen tener indicada tanto la versión más baja de PHP con la que son compatibles, como la más alta con la que están probados. Si se dispone a usar una versión de PHP aún más nueva, por ejemplo, con fines de prueba, Composer se negará a instalar tal paquete. La solución es la opción `--ignore-platform-req=php+`, que hace que Composer ignore los límites superiores de la versión de PHP requerida. + -Cuando se actualizan paquetes o se cambian los números de versión, surgen conflictos. Un paquete tiene requisitos que entran en conflicto con otro y así sucesivamente. Sin embargo, Composer ocasionalmente imprime un mensaje falso. Informa de un conflicto que realmente no existe. En este caso, ayuda borrar el archivo `composer.lock` e intentarlo de nuevo. +Informes falsos +=============== -Si el mensaje de error persiste, entonces va en serio y hay que leer en él qué modificar y cómo. +Al actualizar paquetes o cambiar números de versión, sucede que se produce un conflicto. Un paquete tiene requisitos que están en conflicto con otro y similares. Composer, sin embargo, a veces emite informes falsos. Informa de un conflicto que realmente no existe. En tal caso, ayuda eliminar el archivo `composer.lock` e intentarlo de nuevo. +Si el mensaje de error persiste, entonces se toma en serio y es necesario leer de él qué y cómo modificar. -Packagist.org - Repositorio global .[#toc-packagist-org-global-repository] -========================================================================== -[Packagist |https://packagist.org] es el repositorio principal de paquetes, en el que Composer intenta buscar paquetes, si no se le indica lo contrario. También puede publicar sus propios paquetes aquí. +Packagist.org - repositorio central +=================================== + +[Packagist |https://packagist.org] es el repositorio principal en el que Composer intenta buscar paquetes, si no le decimos lo contrario. Aquí también podemos publicar nuestros propios paquetes. -¿Y si no queremos el repositorio central? .[#toc-what-if-we-don-t-want-the-central-repository] ----------------------------------------------------------------------------------------------- +¿Y si no queremos usar el repositorio central? +---------------------------------------------- -Si tenemos aplicaciones o librerías internas en nuestra empresa, que no pueden ser alojadas públicamente en Packagist, podemos crear nuestros propios repositorios para esos proyectos. +Si tenemos aplicaciones internas de la empresa, que simplemente no podemos alojar públicamente, entonces crearemos un repositorio de empresa para ellas. -Más sobre repositorios en [la documentación |https://getcomposer.org/doc/05-repositories.md#repositories] oficial. +Más sobre el tema de repositorios [en la documentación oficial |https://getcomposer.org/doc/05-repositories.md#repositories]. -Carga automática .[#toc-autoloading] -==================================== +Autoloading +=========== -Una característica clave de Composer es que proporciona autoloading para todas las clases que instala, que se inicia mediante la inclusión de un archivo `vendor/autoload.php`. +Una característica fundamental de Composer es que proporciona autoloading para todas las clases instaladas por él, que inicia incluyendo el archivo `vendor/autoload.php`. -Sin embargo, también es posible utilizar Composer para cargar otras clases fuera de la carpeta `vendor`. La primera opción es dejar que Composer explore las carpetas y subcarpetas definidas, encuentre todas las clases y las incluya en el autoloader. Para ello, configure `autoload > classmap` en `composer.json`: +Sin embargo, es posible usar Composer también para cargar otras clases incluso fuera de la carpeta `vendor`. La primera opción es dejar que Composer explore las carpetas y subcarpetas definidas, encuentre todas las clases y las incluya en el autoloader. Esto se logra configurando `autoload > classmap` en `composer.json`: ```js { "autoload": { "classmap": [ - "src/", # includes the src/ folder and its subfolders + "src/", # incluye la carpeta src/ y sus subcarpetas ] } } ``` -Posteriormente, es necesario ejecutar el comando `composer dumpautoload` con cada cambio y dejar que se regeneren las tablas de autocarga. Esto es extremadamente incómodo, y es mucho mejor confiar esta tarea a [RobotLoader |robot-loader:], que realiza la misma actividad automáticamente en segundo plano y mucho más rápido. +Posteriormente, es necesario ejecutar el comando `composer dumpautoload` cada vez que se realice un cambio y dejar que las tablas de autoloading se regeneren. Esto es extremadamente incómodo y es mucho mejor confiar esta tarea a [RobotLoader|robot-loader:], que realiza la misma actividad automáticamente en segundo plano y mucho más rápido. -La segunda opción es seguir [PSR-4 |https://www.php-fig.org/psr/psr-4/]. En pocas palabras, se trata de un sistema en el que los espacios de nombres y los nombres de las clases se corresponden con la estructura de directorios y los nombres de los archivos, es decir, `App\Router\RouterFactory` se encuentra en el archivo `/path/to/App/Router/RouterFactory.php`. Ejemplo de configuración: +La segunda opción es cumplir con [PSR-4|https://www.php-fig.org/psr/psr-4/]. Simplificando, se trata de un sistema donde los espacios de nombres y los nombres de las clases corresponden a la estructura de directorios y los nombres de los archivos, es decir, p. ej., `App\Core\RouterFactory` estará en el archivo `/path/to/App/Core/RouterFactory.php`. Ejemplo de configuración: ```js { "autoload": { "psr-4": { - "App\\": "app/" # the App\ namespace is in the app/ directory + "App\\": "app/" # el espacio de nombres App\ está en el directorio app/ } } } ``` -Ver [Documentación de Composer |https://getcomposer.org/doc/04-schema.md#psr-4] para saber exactamente cómo configurar este comportamiento. +Cómo configurar exactamente el comportamiento se aprende en la [documentación de Composer|https://getcomposer.org/doc/04-schema.md#psr-4]. -Probar nuevas versiones .[#toc-testing-new-versions] -==================================================== +Prueba de nuevas versiones +========================== -Desea probar una nueva versión de desarrollo de un paquete. ¿Cómo hacerlo? En primer lugar, añada este par de opciones al archivo `composer.json`, que le permitirán instalar versiones de desarrollo de paquetes, pero sólo lo harán si no existe una combinación de versiones estables que cumpla los requisitos: +Quiere probar una nueva versión de desarrollo de un paquete. ¿Cómo hacerlo? Primero, agregue al archivo `composer.json` este par de opciones, que permiten instalar versiones de desarrollo de paquetes, pero recurrirá a ello solo si no existe ninguna combinación de versiones estables que cumpla los requisitos: ```js { @@ -210,34 +215,33 @@ Desea probar una nueva versión de desarrollo de un paquete. ¿Cómo hacerlo? En } ``` -También recomendamos borrar el archivo `composer.lock`, porque a veces Composer rechaza incomprensiblemente la instalación y esto solucionará el problema. +Además, recomendamos eliminar el archivo `composer.lock`, a veces Composer inexplicablemente se niega a la instalación y esto resuelve el problema. -Digamos que el paquete es `nette/utils` y la nueva versión es 4.0. Lo instalas con el comando +Supongamos que se trata del paquete `nette/utils` y la nueva versión tiene el número 4.0. La instala con el comando: ```shell composer require nette/utils:4.0.x-dev ``` -O puedes instalar una versión específica, por ejemplo 4.0.0-RC2: +O puede instalar una versión específica, por ejemplo 4.0.0-RC2: ```shell composer require nette/utils:4.0.0-RC2 ``` -Si otro paquete depende de la biblioteca y está bloqueado a una versión anterior (por ejemplo `^3.1`), lo ideal es actualizar el paquete para que funcione con la nueva versión. -Sin embargo, si sólo quieres evitar la limitación y forzar a Composer a instalar la versión de desarrollo y fingir que es una versión anterior (por ejemplo, 3.1.6), puedes utilizar la palabra clave `as`: +Pero si otro paquete depende de la librería, que está bloqueado en una versión anterior (p. ej., `^3.1`), entonces lo ideal es actualizar el paquete para que funcione con la nueva versión. Sin embargo, si solo quiere eludir la restricción y forzar a Composer a instalar la versión de desarrollo y fingir que es una versión anterior (p. ej., 3.1.6), puede usar la palabra clave `as`: ```shell composer require nette/utils "4.0.x-dev as 3.1.6" ``` -Llamada a comandos .[#toc-calling-commands] -=========================================== +Llamada de comandos +=================== -Puede llamar a sus propios comandos y scripts personalizados a través de Composer como si fueran comandos nativos de Composer. Los scripts ubicados en la carpeta `vendor/bin` no necesitan especificar esta carpeta. +A través de Composer se pueden llamar comandos y scripts propios pre-preparados, como si fueran comandos nativos de Composer. Para los scripts que se encuentran en la carpeta `vendor/bin`, no es necesario indicar esta carpeta. -Como ejemplo, definimos un script en el archivo `composer.json` que utiliza [Nette Tester |tester:] para ejecutar pruebas: +Como ejemplo, definimos en el archivo `composer.json` un script que usando [Nette Tester|tester:] ejecuta las pruebas: ```js { @@ -247,34 +251,32 @@ Como ejemplo, definimos un script en el archivo `composer.json` que utiliza [Net } ``` -A continuación, ejecutamos las pruebas con `composer tester`. Podemos invocar el comando aunque no nos encontremos en la carpeta raíz del proyecto, sino en un subdirectorio. +Las pruebas luego las ejecutamos con `composer tester`. El comando podemos llamarlo incluso si no estamos en la carpeta raíz del proyecto, sino en algún subdirectorio. -Enviar Gracias .[#toc-send-thanks] -================================== +Envíe un agradecimiento +======================= -Te vamos a enseñar un truco que hará felices a los autores de código abierto. Puedes dar fácilmente una estrella en GitHub a las bibliotecas que utiliza tu proyecto. Sólo tienes que instalar la biblioteca `symfony/thanks`: +Le mostraremos un truco con el que complacerá a los autores de código abierto. De manera simple, dará una estrella en GitHub a las librerías que usa su proyecto. Basta con instalar la librería `symfony/thanks`: ```shell composer global require symfony/thanks ``` -Y luego ejecuta +Y luego ejecutar: ```shell composer thanks ``` -¡Inténtalo! +¡Pruébelo! -Configuración .[#toc-configuration] -=================================== +Configuración +============= -Composer está estrechamente integrado con la herramienta de control de versiones [Git |https://git-scm.com]. Si no utiliza Git, es necesario indicárselo a Composer: +Composer está estrechamente vinculado con la herramienta de versionado [Git |https://git-scm.com]. Si no la tiene instalada, es necesario decirle a Composer que no la use: ```shell composer -g config preferred-install dist ``` - -{{sitename: Buenas prácticas}} diff --git a/best-practices/es/creating-editing-form.texy b/best-practices/es/creating-editing-form.texy index 9b7b42985b..9d44a9d52f 100644 --- a/best-practices/es/creating-editing-form.texy +++ b/best-practices/es/creating-editing-form.texy @@ -2,15 +2,15 @@ Formulario para crear y editar un registro ****************************************** .[perex] -¿Cómo implementar correctamente la creación y edición de un registro en Nette, utilizando el mismo formulario para ambas cosas? +¿Cómo implementar correctamente la adición y edición de un registro en Nette, utilizando el mismo formulario para ambos? -En muchos casos, los formularios para añadir y editar un registro son iguales, diferenciándose únicamente por la etiqueta del botón. Mostraremos ejemplos de presentadores sencillos en los que utilizamos el formulario primero para añadir un registro, luego para editarlo y finalmente combinamos las dos soluciones. +En muchos casos, los formularios para añadir y editar un registro son los mismos, diferenciándose quizás sólo en la etiqueta del botón. Mostraremos ejemplos de Presenters simples donde usaremos el formulario primero para añadir un registro, luego para editarlo, y finalmente combinaremos ambas soluciones. -Añadir un registro .[#toc-adding-a-record] ------------------------------------------- +Añadir un registro +------------------ -Ejemplo de presentador utilizado para añadir un registro. Dejaremos el trabajo real de la base de datos a la clase `Facade`, cuyo código no es relevante para el ejemplo. +Ejemplo de un Presenter que sirve para añadir un registro. Dejaremos el trabajo real con la base de datos a la clase `Facade`, cuyo código no es esencial para la demostración. ```php @@ -27,7 +27,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $form = new Form; - // ... añadir campos de formulario ... + // ... añadimos los campos del formulario ... $form->onSuccess[] = [$this, 'recordFormSucceeded']; return $form; @@ -36,7 +36,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter public function recordFormSucceeded(Form $form, array $data): void { $this->facade->add($data); // añadir registro a la base de datos - $this->flashMessage('Successfully added'); + $this->flashMessage('Añadido correctamente'); $this->redirect('...'); } @@ -48,10 +48,10 @@ class RecordPresenter extends Nette\Application\UI\Presenter ``` -Editar un registro .[#toc-editing-a-record] -------------------------------------------- +Editar un registro +------------------ -Veamos ahora cómo sería un presentador utilizado para editar un registro: +Ahora mostraremos cómo sería un Presenter para editar un registro: ```php @@ -81,14 +81,14 @@ class RecordPresenter extends Nette\Application\UI\Presenter protected function createComponentRecordForm(): Form { - // verificar que la acción es 'editar + // verificar que la acción es 'edit' if ($this->getAction() !== 'edit') { $this->error(); } $form = new Form; - // ... añadir campos de formulario ... + // ... añadimos los campos del formulario ... $form->setDefaults($this->record); // establecer valores por defecto $form->onSuccess[] = [$this, 'recordFormSucceeded']; @@ -97,16 +97,16 @@ class RecordPresenter extends Nette\Application\UI\Presenter public function recordFormSucceeded(Form $form, array $data): void { - $this->facade->update($this->record->id, $data); // actualizar registro - $this->flashMessage('Successfully updated'); + $this->facade->update($this->record->id, $data); // actualizar el registro + $this->flashMessage('Actualizado correctamente'); $this->redirect('...'); } } ``` -En el método *action*, que se invoca justo al principio del [ciclo de vida |application:presenters#Life Cycle of Presenter] del presentador, verificamos la existencia del registro y el permiso del usuario para editarlo. +En el método *action*, que se ejecuta al principio del [ciclo de vida del Presenter |application:presenters#Ciclo de vida del presenter], verificamos la existencia del registro y los permisos del usuario para editarlo. -Almacenamos el registro en la propiedad `$record` para que esté disponible en el método `createComponentRecordForm()` para establecer los valores por defecto, y `recordFormSucceeded()` para el ID. Una solución alternativa sería establecer los valores por defecto directamente en `actionEdit()` y el valor del ID, que forma parte de la URL, se recupera utilizando `getParameter('id')`: +Guardamos el registro en la propiedad `$record`, para tenerlo disponible en el método `createComponentRecordForm()` para establecer los valores por defecto, y en `recordFormSucceeded()` para el ID. Una solución alternativa sería establecer los valores por defecto directamente en `actionEdit()` y obtener el valor del ID, que forma parte de la URL, usando `getParameter('id')`: ```php @@ -114,12 +114,12 @@ Almacenamos el registro en la propiedad `$record` para que esté disponible en e { $record = $this->facade->get($id); if ( - // verificar la existencia y comprobar los permisos + // verificar existencia y comprobar permisos ) { $this->error(); } - // establecer valores de formulario por defecto + // establecer valores por defecto del formulario $this->getComponent('recordForm') ->setDefaults($record); } @@ -133,13 +133,13 @@ Almacenamos el registro en la propiedad `$record` para que esté disponible en e } ``` -Sin embargo, y esto debería ser **lo más importante de todo el código**, necesitamos asegurarnos de que la acción es efectivamente `edit` cuando creamos el formulario. De lo contrario, la validación en el método `actionEdit()` no se realizaría. +Sin embargo, y esto debería ser **el punto más importante de todo el código**, debemos asegurarnos al crear el formulario de que la acción es realmente `edit`. ¡Porque de lo contrario, la verificación en el método `actionEdit()` no se realizaría en absoluto! -El mismo formulario para añadir y editar .[#toc-same-form-for-adding-and-editing] ---------------------------------------------------------------------------------- +El mismo formulario para añadir y editar +---------------------------------------- -Y ahora combinaremos ambos presentadores en uno solo. Podríamos distinguir qué acción está implicada en el método `createComponentRecordForm()` y configurar el formulario en consecuencia, o podemos dejarlo directamente en manos de los métodos de acción y deshacernos de la condición: +Y ahora combinaremos ambos Presenters en uno. Podríamos distinguir en el método `createComponentRecordForm()` de qué acción se trata y configurar el formulario en consecuencia, o podemos dejarlo directamente en los métodos action y deshacernos de la condición: ```php @@ -173,14 +173,14 @@ class RecordPresenter extends Nette\Application\UI\Presenter protected function createComponentRecordForm(): Form { - // verificar que la acción es 'añadir' o 'editar + // verificamos que la acción es 'add' o 'edit' if (!in_array($this->getAction(), ['add', 'edit'])) { $this->error(); } $form = new Form; - // ... añadir campos de formulario ... + // ... añadimos los campos del formulario ... return $form; } @@ -188,19 +188,18 @@ class RecordPresenter extends Nette\Application\UI\Presenter public function addingFormSucceeded(Form $form, array $data): void { $this->facade->add($data); // añadir registro a la base de datos - $this->flashMessage('Añadido con éxito'); + $this->flashMessage('Añadido correctamente'); $this->redirect('...'); } public function editingFormSucceeded(Form $form, array $data): void { $id = (int) $this->getParameter('id'); - $this->facade->update($id, $data); // actualizar registro - $this->flashMessage('Successfully updated'); + $this->facade->update($id, $data); // actualizar el registro + $this->flashMessage('Actualizado correctamente'); $this->redirect('...'); } } ``` {{priority: -1}} -{{sitename: Buenas prácticas}} diff --git a/best-practices/es/dynamic-snippets.texy b/best-practices/es/dynamic-snippets.texy index 2133b4a573..37ca43eec5 100644 --- a/best-practices/es/dynamic-snippets.texy +++ b/best-practices/es/dynamic-snippets.texy @@ -1,7 +1,7 @@ -Fragmentos dinámicos -******************** +Snippets dinámicos +****************** -Muy a menudo en el desarrollo de aplicaciones existe la necesidad de realizar operaciones AJAX, por ejemplo, en filas individuales de una tabla o en elementos de una lista. Como ejemplo, podemos elegir listar artículos, permitiendo al usuario logueado seleccionar una valoración "me gusta/no me gusta" para cada uno de ellos. El código del presentador y la plantilla correspondiente sin AJAX tendrán un aspecto similar al siguiente (enumero los fragmentos más importantes, el código asume la existencia de un servicio para marcar las valoraciones y obtener una colección de artículos - la implementación específica no es importante a efectos de este tutorial): +Con bastante frecuencia, durante el desarrollo de aplicaciones, surge la necesidad de realizar operaciones AJAX, por ejemplo, sobre filas individuales de una tabla o elementos de una lista. Como ejemplo, podemos elegir mostrar artículos, permitiendo a cada usuario conectado elegir una calificación de "me gusta/no me gusta". El código del Presenter y la plantilla correspondiente sin AJAX se verían aproximadamente así (presento los fragmentos más importantes, el código asume la existencia de un servicio para marcar calificaciones y obtener una colección de artículos; la implementación específica no es importante para los fines de este tutorial): ```php public function handleLike(int $articleId): void @@ -24,26 +24,26 @@ Plantilla: <h2>{$article->title}</h2> <div class="content">{$article->content}</div> {if !$article->liked} - <a n:href="like! $article->id" class=ajax>I like it</a> + <a n:href="like! $article->id" class=ajax>me gusta</a> {else} - <a n:href="unlike! $article->id" class=ajax>I don't like it anymore</a> + <a n:href="unlike! $article->id" class=ajax>ya no me gusta</a> {/if} </article> ``` -Ajaxización .[#toc-ajaxization] -=============================== +Ajaxificación +============= -Llevemos ahora AJAX a esta sencilla aplicación. Cambiar la calificación de un artículo no es lo suficientemente importante como para requerir una petición HTTP con redirección, así que lo ideal sería hacerlo con AJAX en segundo plano. Usaremos el [script handler de add-ons |https://componette.org/vojtech-dobes/nette.ajax.js/] con la convención habitual de que los enlaces AJAX tienen la clase CSS `ajax`. +Ahora equipemos esta sencilla aplicación con AJAX. El cambio de calificación de un artículo no es tan importante como para requerir una redirección, por lo que idealmente debería ocurrir mediante AJAX en segundo plano. Utilizaremos [el script de manejo de complementos |application:ajax#Naja] con la convención habitual de que los enlaces AJAX tienen la clase CSS `ajax`. -Sin embargo, ¿cómo hacerlo específicamente? Nette ofrece 2 maneras: la del fragmento dinámico y la del componente. Ambas tienen sus pros y sus contras, así que las mostraremos una a una. +Pero, ¿cómo hacerlo específicamente? Nette ofrece 2 enfoques: el enfoque de los llamados snippets dinámicos y el enfoque de los componentes. Ambos tienen sus pros y sus contras, por lo que los mostraremos uno por uno. -La manera de los fragmentos dinámicos .[#toc-the-dynamic-snippets-way] -====================================================================== +El enfoque de los snippets dinámicos +==================================== -En la terminología de Latte, un fragmento dinámico es un caso de uso específico de la etiqueta `{snippet}` en el que se utiliza una variable en el nombre del fragmento. Un fragmento de este tipo no puede encontrarse en cualquier lugar de la plantilla: debe estar envuelto por un fragmento estático, es decir, uno normal, o dentro de un `{snippetArea}`. Podríamos modificar nuestra plantilla de la siguiente manera. +Un snippet dinámico, en la terminología de Latte, significa un caso específico de uso de la etiqueta `{snippet}`, donde se utiliza una variable en el nombre del snippet. Dicho snippet no puede encontrarse en cualquier lugar de la plantilla; debe estar envuelto por un snippet estático, es decir, uno normal, o dentro de `{snippetArea}`. Podríamos modificar nuestra plantilla de la siguiente manera. ```latte @@ -53,18 +53,18 @@ En la terminología de Latte, un fragmento dinámico es un caso de uso específi <div class="content">{$article->content}</div> {snippet article-{$article->id}} {if !$article->liked} - <a n:href="like! $article->id" class=ajax>I like it</a> + <a n:href="like! $article->id" class=ajax>me gusta</a> {else} - <a n:href="unlike! $article->id" class=ajax>I don't like it anymore</a> + <a n:href="unlike! $article->id" class=ajax>ya no me gusta</a> {/if} {/snippet} </article> {/snippet} ``` -Cada artículo define ahora un único fragmento, que tiene un ID de artículo en el título. Todos estos fragmentos se agrupan en un único fragmento llamado `articlesContainer`. Si omitimos este fragmento, Latte nos avisará con una excepción. +Cada artículo ahora define un snippet que tiene el ID del artículo en su nombre. Todos estos snippets se envuelven juntos en un snippet llamado `articlesContainer`. Si omitiéramos este snippet envolvente, Latte nos advertiría con una excepción. -Todo lo que queda por hacer es añadir el redibujado al presentador: basta con redibujar la envoltura estática. +Nos queda añadir el redibujado al Presenter; basta con redibujar el envoltorio estático. ```php public function handleLike(int $articleId): void @@ -72,18 +72,18 @@ public function handleLike(int $articleId): void $this->ratingService->saveLike($articleId, $this->user->id); if ($this->isAjax()) { $this->redrawControl('articlesContainer'); - // $this->redrawControl('article-' . $articleId); -- není potřeba + // $this->redrawControl('article-' . $articleId); -- no es necesario } else { $this->redirect('this'); } } ``` -Modifica el método hermano `handleUnlike()` de la misma manera, ¡y AJAX está listo y funcionando! +Modificamos de manera similar el método hermano `handleUnlike()`, ¡y AJAX funciona! -Sin embargo, la solución tiene un inconveniente. Si indagamos más en el funcionamiento de la petición AJAX, descubriremos que, aunque la aplicación parece eficiente en apariencia (sólo devuelve un único fragmento para un artículo determinado), en realidad renderiza todos los fragmentos en el servidor. Ha colocado el fragmento deseado en nuestra carga útil y ha descartado los demás (por lo que, de forma bastante innecesaria, también los ha recuperado de la base de datos). +Sin embargo, la solución tiene un inconveniente. Si investigáramos más a fondo cómo se procesa la solicitud AJAX, descubriríamos que aunque la aplicación parece eficiente externamente (devuelve solo un snippet para el artículo dado), en realidad renderizó todos los snippets en el servidor. Colocó el snippet deseado en el payload y descartó los demás (obteniéndolos también innecesariamente de la base de datos). -Para optimizar este proceso, necesitaremos realizar una acción en la que pasemos la colección `$articles` a la plantilla (digamos en el método `renderDefault()` ). Aprovecharemos el hecho de que el procesamiento de la señal tiene lugar antes de que los `render<Something>` métodos: +Para optimizar este proceso, tendremos que intervenir donde pasamos la colección `$articles` a la plantilla (digamos, en el método `renderDefault()`). Aprovecharemos el hecho de que el procesamiento de señales ocurre antes de los métodos `render<Something>`: ```php public function handleLike(int $articleId): void @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connection->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,18 +101,18 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connection->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` -Ahora, cuando la señal es procesada, en lugar de una colección con todos los artículos, sólo un array con un único artículo es pasado a la plantilla - el que queremos renderizar y enviar en payload al navegador. Así, `{foreach}` se hará sólo una vez y no se renderizarán fragmentos extra. +Ahora, al procesar la señal, en lugar de la colección con todos los artículos, se pasa a la plantilla solo un array con un único artículo: el que queremos renderizar y enviar en el payload al navegador. Por lo tanto, `{foreach}` se ejecutará solo una vez y no se renderizarán snippets adicionales. -Componente .[#toc-component-way] -================================ +El enfoque de los componentes +============================= -Una solución completamente diferente utiliza un enfoque distinto para evitar los fragmentos dinámicos. El truco consiste en trasladar toda la lógica a un componente independiente: a partir de ahora, no tendremos un presentador que se encargue de introducir la calificación, sino una clase dedicada `LikeControl`. La clase tendrá el siguiente aspecto (además, también contendrá los métodos `render`, `handleUnlike`, etc.): +Una forma completamente diferente de resolverlo evita los snippets dinámicos. El truco consiste en trasladar toda la lógica a un componente separado: a partir de ahora, no será el Presenter el que se encargue de introducir las calificaciones, sino un `LikeControl` dedicado. La clase se verá así (además, también contendrá los métodos `render`, `handleUnlike`, etc.): ```php class LikeControl extends Nette\Application\UI\Control @@ -139,26 +139,26 @@ Plantilla del componente: ```latte {snippet} {if !$article->liked} - <a n:href="like!" class=ajax>I like it</a> + <a n:href="like!" class=ajax>me gusta</a> {else} - <a n:href="unlike!" class=ajax>I don't like it anymore</a> + <a n:href="unlike!" class=ajax>ya no me gusta</a> {/if} {/snippet} ``` -Por supuesto cambiaremos la plantilla de la vista y tendremos que añadir una fábrica al presentador. Como crearemos el componente tantas veces como artículos recibamos de la base de datos, utilizaremos la clase [application:Multiplier] para "multiplicarlo". +Por supuesto, la plantilla de la vista cambiará y tendremos que añadir una fábrica al Presenter. Dado que crearemos el componente tantas veces como artículos obtengamos de la base de datos, utilizaremos la clase [application:Multiplier] para su "multiplicación". ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); } ``` -La vista de plantilla queda reducida al mínimo necesario (¡y completamente libre de fragmentos!): +La plantilla de la vista se reduce al mínimo indispensable (¡y completamente libre de snippets!): ```latte <article n:foreach="$articles as $article"> @@ -168,8 +168,6 @@ La vista de plantilla queda reducida al mínimo necesario (¡y completamente lib </article> ``` -Casi hemos terminado: la aplicación funcionará ahora en AJAX. Aquí también tenemos que optimizar la aplicación, porque debido al uso de Nette Database, el procesamiento de la señal cargará innecesariamente todos los artículos de la base de datos en lugar de uno. Sin embargo, la ventaja es que no habrá renderizado, porque sólo nuestro componente es realmente renderizado. - +Casi hemos terminado: la aplicación ahora funcionará con AJAX. Aquí también tendremos que optimizar la aplicación, porque debido al uso de Nette Database, al procesar la señal se cargan innecesariamente todos los artículos de la base de datos en lugar de uno solo. Sin embargo, la ventaja es que no se renderizarán, ya que realmente solo se renderizará nuestro componente. {{priority: -1}} -{{sitename: Buenas prácticas}} diff --git a/best-practices/es/editors-and-tools.texy b/best-practices/es/editors-and-tools.texy index cb037a479b..36d9c62866 100644 --- a/best-practices/es/editors-and-tools.texy +++ b/best-practices/es/editors-and-tools.texy @@ -2,39 +2,39 @@ Editores y herramientas *********************** .[perex] -Puedes ser un programador experto, pero sólo con buenas herramientas llegarás a ser un maestro. En este capítulo encontrarás consejos sobre herramientas, editores y plugins importantes. +Puedes ser un programador competente, pero solo con buenas herramientas te convertirás en un maestro. En este capítulo encontrarás consejos sobre herramientas, editores y plugins importantes. -Editor IDE .[#toc-ide-editor] -============================= +Editor IDE +========== -Recomendamos encarecidamente el uso de un IDE con todas las funciones para el desarrollo, como PhpStorm, NetBeans, VS Code, y no sólo un editor de texto con soporte PHP. La diferencia es realmente crucial. No hay razón para conformarse con un editor clásico con resaltado de sintaxis, porque no alcanza las capacidades de un IDE con sugerencia de código precisa, que puede refactorizar código, y más. Algunos IDE son de pago, otros son gratuitos. +Recomendamos encarecidamente utilizar un IDE completo para el desarrollo, como PhpStorm, NetBeans, VS Code, y no solo un editor de texto con soporte para PHP. La diferencia es realmente fundamental. No hay razón para conformarse con un simple editor que colorea la sintaxis pero no alcanza las capacidades de un IDE de primer nivel, que sugiere con precisión, detecta errores, puede refactorizar código y mucho más. Algunos IDE son de pago, otros incluso gratuitos. -**NetBeans IDE** tiene soporte incorporado para Nette, Latte y NEON. +**NetBeans IDE** ya tiene soporte integrado para Nette, Latte y NEON. -**PhpStorm**: instala estos plugins en `Settings > Plugins > Marketplace`: +**PhpStorm**: instala estos plugins en `Settings > Plugins > Marketplace` - Nette framework helpers - Latte -- Soporte NEON -- Comprobador Nette +- NEON support +- Nette Tester -**Código VS**: encuentra el plugin "Nette Latte + Neon" en el mercado. +**VS Code**: busca el plugin "Nette Latte + Neon" en el marketplace. -Conecta también Tracy con el editor. Cuando aparezca la página de error, puede hacer clic en los nombres de los archivos y se abrirán en el editor con el cursor en la línea correspondiente. Aprende [a configurar el sistema |tracy:open-files-in-ide]. +También vincula Tracy con tu editor. Cuando se muestre una página de error, podrás hacer clic en los nombres de los archivos y se abrirán en el editor con el cursor en la línea correspondiente. Lee [cómo configurar el sistema|tracy:open-files-in-ide]. -PHPStan .[#toc-phpstan] -======================= +PHPStan +======= -PHPStan es una herramienta que detecta errores lógicos en su código antes de ejecutarlo. +PHPStan es una herramienta que detecta errores lógicos en el código antes de ejecutarlo. -Instálelo a través de Composer: +Lo instalamos usando Composer: ```shell composer require --dev phpstan/phpstan-nette ``` -Cree un archivo de configuración `phpstan.neon` en el proyecto: +Creamos un archivo de configuración `phpstan.neon` en el proyecto: ```neon includes: @@ -47,40 +47,38 @@ parameters: level: 5 ``` -Y luego deja que analice las clases en la carpeta `app/`: +Y luego le pedimos que analice las clases en la carpeta `app/`: ```shell vendor/bin/phpstan analyse app ``` -Puede encontrar documentación completa directamente en [PHPStan |https://phpstan.org]. +Encontrarás documentación exhaustiva directamente en el [sitio web de PHPStan |https://phpstan.org]. -Comprobador de código .[#toc-code-checker] -========================================== +Code Checker +============ -[Code Checker |code-checker:] comprueba y posiblemente repara algunos de los errores formales de su código fuente. +[Code Checker|code-checker:] comprueba y, opcionalmente, corrige algunos de los errores formales en tus códigos fuente: -- elimina [la lista de materiales |nette:glossary#bom] +- elimina [BOM |nette:glossary#BOM] - comprueba la validez de las plantillas [Latte |latte:] - comprueba la validez de los archivos `.neon`, `.php` y `.json` -- comprueba los [caracteres de control |nette:glossary#control characters] -- comprueba si el fichero está codificado en UTF-8 -- controla los errores ortográficos `/* @annotations */` (falta el segundo asterisco) -- elimina las etiquetas finales de PHP `?>` en los archivos PHP -- elimina los espacios en blanco finales y las líneas en blanco innecesarias al final de un archivo -- normaliza los finales de línea a los predeterminados por el sistema (con el parámetro `-l` ) +- comprueba la presencia de [caracteres de control |nette:glossary#Caracteres de control] +- comprueba si el archivo está codificado en UTF-8 +- comprueba `/* @anotaciones */` mal escritas (falta el asterisco) +- elimina el `?>` final de los archivos PHP +- elimina los espacios finales y las líneas innecesarias al final del archivo +- normaliza los separadores de línea a los del sistema (si se especifica la opción `-l`) -Compositor .[#toc-composer] -=========================== +Composer +======== -[Composer] es una herramienta para gestionar tus dependencias en PHP. Nos permite declarar dependencias de librerías y las instalará por nosotros, en nuestro proyecto. +[Composer |Composer] es una herramienta para gestionar dependencias en PHP. Nos permite declarar dependencias arbitrariamente complejas de bibliotecas individuales y luego las instala por nosotros en nuestro proyecto. -Verificador de Requisitos .[#toc-requirements-checker] -====================================================== +Requirements Checker +==================== -Se trata de una herramienta que comprueba el entorno de ejecución del servidor e informa de si se puede utilizar el framework (y en qué medida). Actualmente, Nette puede utilizarse en cualquier servidor que tenga la versión mínima requerida de PHP. - -{{sitename: Buenas prácticas}} +Era una herramienta que probaba el entorno de ejecución del servidor e informaba si (y en qué medida) se podía utilizar el framework. Actualmente, Nette se puede utilizar en cualquier servidor que tenga la versión mínima requerida de PHP. diff --git a/best-practices/es/form-reuse.texy b/best-practices/es/form-reuse.texy index 12e62fa802..3998002707 100644 --- a/best-practices/es/form-reuse.texy +++ b/best-practices/es/form-reuse.texy @@ -1,16 +1,16 @@ -Reutilización de formularios en varios sitios -********************************************* +Reutilización de formularios en múltiples lugares +************************************************* .[perex] -En Nette, tienes varias opciones para reutilizar el mismo formulario en múltiples lugares sin duplicar código. En este artículo, repasaremos las diferentes soluciones, incluyendo las que deberías evitar. +En Nette, tienes varias opciones para usar el mismo formulario en múltiples lugares sin duplicar código. En este artículo, mostraremos diferentes soluciones, incluidas aquellas que deberías evitar. -Fábrica de formularios .[#toc-form-factory] -=========================================== +Fábrica de formularios +====================== -Un enfoque básico para utilizar el mismo componente en múltiples lugares es crear un método o clase que genere el componente, y luego llamar a ese método en diferentes lugares de la aplicación. Este método o clase se llama *factory*. Por favor, no confundir con el patrón de diseño *método de fábrica*, que describe una forma específica de utilizar fábricas y no está relacionado con este tema. +Uno de los enfoques básicos para usar el mismo componente en múltiples lugares es crear un método o clase que genere este componente y luego llamar a este método en diferentes lugares de la aplicación. Tal método o clase se llama *fábrica*. Por favor, no lo confundas con el patrón de diseño *factory method*, que describe una forma específica de usar fábricas y no está relacionado con este tema. -Como ejemplo, vamos a crear una fábrica que construirá un formulario de edición: +Como ejemplo, crearemos una fábrica que construirá un formulario de edición: ```php use Nette\Application\UI\Form; @@ -20,22 +20,22 @@ class FormFactory public function createEditForm(): Form { $form = new Form; - $form->addText('title', 'Title:'); - // aquí se añaden campos de formulario adicionales - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Título:'); + // aquí se añaden otros campos del formulario + $form->addSubmit('send', 'Enviar'); return $form; } } ``` -Ahora puedes usar esta fábrica en diferentes lugares de tu aplicación, por ejemplo en presentadores o componentes. Y hacemos esto [solicitándola como una dependencia |dependency-injection:passing-dependencies]. Así que primero, escribiremos la clase en el archivo de configuración: +Ahora puedes usar esta fábrica en diferentes lugares de tu aplicación, por ejemplo, en Presenters o componentes. Y lo haces [solicitándola como dependencia|dependency-injection:passing-dependencies]. Primero, registramos la clase en el archivo de configuración: ```neon services: - FormFactory ``` -Y luego la usamos en el presentador: +Y luego la usamos en un Presenter: ```php @@ -50,14 +50,14 @@ class MyPresenter extends Nette\Application\UI\Presenter { $form = $this->formFactory->createEditForm(); $form->onSuccess[] = function () { - // tratamiento de los datos enviados + // procesamiento de los datos enviados }; return $form; } } ``` -Puedes extender la fábrica de formularios con métodos adicionales para crear otros tipos de formularios que se adapten a tu aplicación. Y, por supuesto, puedes añadir un método que cree un formulario básico sin elementos, que utilizarán los demás métodos: +Puedes extender la fábrica de formularios con métodos adicionales para crear otros tipos de formularios según las necesidades de tu aplicación. Y, por supuesto, también podemos añadir un método que cree un formulario base sin elementos, que los otros métodos utilizarán: ```php class FormFactory @@ -71,21 +71,21 @@ class FormFactory public function createEditForm(): Form { $form = $this->createForm(); - $form->addText('title', 'Title:'); - // aquí se añaden campos de formulario adicionales - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Título:'); + // aquí se añaden otros campos del formulario + $form->addSubmit('send', 'Enviar'); return $form; } } ``` -El método `createForm()` no hace nada útil todavía, pero eso cambiará rápidamente. +El método `createForm()` aún no hace nada útil, pero eso cambiará rápidamente. -Dependencias de fábrica .[#toc-factory-dependencies] -==================================================== +Dependencias de la fábrica +========================== -Con el tiempo, se hará evidente que necesitamos que los formularios sean multilingües. Esto significa que necesitamos configurar un [traductor |forms:rendering#Translating] para todos los formularios. Para ello, modificamos la clase `FormFactory` para que acepte el objeto `Translator` como dependencia en el constructor, y lo pase al formulario: +Con el tiempo, resultará que necesitamos que los formularios sean multilingües. Esto significa que debemos establecer el llamado [traductor |forms:rendering#Traducción] para todos los formularios. Para ello, modificaremos la clase `FormFactory` para que acepte un objeto `Translator` como dependencia en el constructor y lo pasaremos al formulario: ```php use Nette\Localization\Translator; @@ -104,18 +104,17 @@ class FormFactory return $form; } - //... + // ... } ``` -Como el método `createForm()` también es llamado por otros métodos que crean formularios específicos, sólo necesitamos establecer el traductor en ese método. Y ya está. No hay necesidad de cambiar ningún código de presentador o componente, lo cual es genial. +Dado que el método `createForm()` también es llamado por otros métodos que crean formularios específicos, basta con establecer el traductor solo en él. Y hemos terminado. No es necesario cambiar el código de ningún Presenter o componente, lo cual es genial. -Más clases de fábrica .[#toc-more-factory-classes] -================================================== +Múltiples clases de fábrica +=========================== -Alternativamente, puede crear múltiples clases para cada formulario que desee utilizar en su aplicación. -Este enfoque puede aumentar la legibilidad del código y hacer que los formularios sean más fáciles de gestionar. Deje el original `FormFactory` para crear sólo un formulario puro con configuración básica (por ejemplo, con soporte de traducción) y cree una nueva fábrica `EditFormFactory` para el formulario de edición. +Alternativamente, puedes crear múltiples clases para cada formulario que quieras usar en tu aplicación. Este enfoque puede aumentar la legibilidad del código y facilitar la gestión de los formularios. Dejaremos que la `FormFactory` original cree solo un formulario limpio con la configuración básica (por ejemplo, con soporte para traducciones) y crearemos una nueva fábrica `EditFormFactory` para el formulario de edición. ```php class FormFactory @@ -134,7 +133,7 @@ class FormFactory } -// ✅ uso de la composición +// ✅ uso de composición class EditFormFactory { public function __construct( @@ -145,40 +144,39 @@ class EditFormFactory public function create(): Form { $form = $this->formFactory->create(); - // aquí se añaden campos de formulario adicionales - $form->addSubmit('send', 'Save'); + // aquí se añaden otros campos del formulario + $form->addSubmit('send', 'Enviar'); return $form; } } ``` -Es muy importante que la unión entre las clases `FormFactory` y `EditFormFactory` se implemente por composición, no por herencia de objetos: +Es muy importante que la relación entre las clases `FormFactory` y `EditFormFactory` se realice mediante [composición |nette:introduction-to-object-oriented-programming#Composición], y no mediante [herencia de objetos |nette:introduction-to-object-oriented-programming#Herencia]: ```php -// ⛔ ¡NO! LA HERENCIA NO PERTENECE AQUÍ +// ⛔ ¡ASÍ NO! LA HERENCIA NO PERTENECE AQUÍ class EditFormFactory extends FormFactory { public function create(): Form { $form = parent::create(); - $form->addText('title', 'Title:'); - // los campos de formulario adicionales se añaden aquí - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Título:'); + // aquí se añaden otros campos del formulario + $form->addSubmit('send', 'Enviar'); return $form; } } ``` -Utilizar la herencia en este caso sería totalmente contraproducente. Se encontraría con problemas muy rápidamente. Por ejemplo, si quisiera agregar parámetros al método `create()`; PHP reportaría un error de que su firma es diferente a la del padre. -O al pasar una dependencia a la clase `EditFormFactory` a través del constructor. Esto causaría lo que llamamos el infierno del [constructor |dependency-injection:passing-dependencies#Constructor hell]. +Usar la herencia en este caso sería completamente contraproducente. Te encontrarías con problemas muy rápidamente. Por ejemplo, en el momento en que quisieras añadir parámetros al método `create()`; PHP informaría de un error indicando que su firma difiere de la del padre. O al pasar dependencias a la clase `EditFormFactory` a través del constructor. Se produciría una situación que llamamos [constructor hell |dependency-injection:passing-dependencies#Constructor hell]. -En general, es mejor preferir la composición a la herencia. +En general, es mejor preferir la [composición sobre la herencia |dependency-injection:faq#Por qué se prefiere la composición sobre la herencia]. -Manejo de Formularios .[#toc-form-handling] -=========================================== +Manejo del formulario +===================== -El manejador de formularios que es llamado después de un envío exitoso también puede ser parte de una clase fábrica. Funcionará pasando los datos enviados al modelo para su procesamiento. [Devolverá |forms:validation#Processing Errors] cualquier error al formulario. El modelo en el siguiente ejemplo está representado por la clase `Facade`: +El manejador del formulario, que se llama después de un envío exitoso, también puede ser parte de la clase de fábrica. Funcionará pasando los datos enviados al modelo para su procesamiento. Los posibles errores se [pasarán de vuelta |forms:validation#Errores durante el procesamiento] al formulario. El modelo en el siguiente ejemplo está representado por la clase `Facade`: ```php class EditFormFactory @@ -192,9 +190,9 @@ class EditFormFactory public function create(): Form { $form = $this->formFactory->create(); - $form->addText('title', 'Title:'); - // aquí se añaden campos de formulario adicionales - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Título:'); + // aquí se añaden otros campos del formulario + $form->addSubmit('send', 'Enviar'); $form->onSuccess[] = [$this, 'processForm']; return $form; } @@ -202,7 +200,7 @@ class EditFormFactory public function processForm(Form $form, array $data): void { try { - // tratamiento de los datos enviados + // procesamiento de los datos enviados $this->facade->process($data); } catch (AnyModelException $e) { @@ -212,7 +210,7 @@ class EditFormFactory } ``` -Deje que el presentador se encargue de la redirección. Añadirá otro manejador al evento `onSuccess`, que realizará la redirección. Esto permitirá utilizar el formulario en diferentes presentadores, y cada uno puede redirigir a una ubicación diferente. +Sin embargo, dejaremos la redirección real al Presenter. Este añadirá otro manejador al evento `onSuccess` que realizará la redirección. Gracias a esto, será posible usar el formulario en diferentes Presenters y redirigir a un lugar diferente en cada uno. ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -226,7 +224,7 @@ class MyPresenter extends Nette\Application\UI\Presenter { $form = $this->formFactory->create(); $form->onSuccess[] = function () { - $this->flashMessage('Záznam byl uložen'); + $this->flashMessage('El registro ha sido guardado'); $this->redirect('Homepage:'); }; return $form; @@ -234,39 +232,38 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Esta solución aprovecha la propiedad de los formularios de que, cuando se llama a `addError()` sobre un formulario o su elemento, no se invoca al siguiente manejador `onSuccess`. +Esta solución aprovecha la propiedad de los formularios de que cuando se llama a `addError()` en el formulario o en uno de sus elementos, el siguiente manejador `onSuccess` ya no se llama. -Heredando de la clase Form .[#toc-inheriting-from-the-form-class] -================================================================= +Herencia de la clase Form +========================= -Un formulario creado no debe ser hijo de un formulario. En otras palabras, no utilice esta solución: +Un formulario construido no debe ser un descendiente del formulario. En otras palabras, no uses esta solución: ```php -// ⛔ ¡NO! LA HERENCIA NO PERTENECE AQUÍ +// ⛔ ¡ASÍ NO! LA HERENCIA NO PERTENECE AQUÍ class EditForm extends Form { public function __construct(Translator $translator) { parent::__construct(); - $form->addText('title', 'Title:'); - // los campos de formulario adicionales se añaden aquí - $form->addSubmit('send', 'Save'); - $form->setTranslator($translator); + $this->addText('title', 'Título:'); + // aquí se añaden otros campos del formulario + $this->addSubmit('send', 'Enviar'); + $this->setTranslator($translator); } } ``` -En lugar de construir el formulario en el constructor, utilice la fábrica. +En lugar de construir el formulario en el constructor, usa una fábrica. -Es importante darse cuenta de que la clase `Form` es principalmente una herramienta para ensamblar un formulario, es decir, un constructor de formularios. Y el formulario ensamblado puede considerarse su producto. Sin embargo, el producto no es un caso específico del constructor; no existe una relación *es a* entre ellos, que constituye la base de la herencia. +Es necesario darse cuenta de que la clase `Form` es, ante todo, una herramienta para construir un formulario, es decir, un *form builder*. Y el formulario construido puede entenderse como su producto. Pero el producto no es un caso específico del constructor, no hay una relación *es un* entre ellos que forme la base de la herencia. -Componente Form .[#toc-form-component] -====================================== +Componente con formulario +========================= -Un enfoque completamente diferente es crear un [componente |application:components] que incluya un formulario. Esto da nuevas posibilidades, por ejemplo para renderizar el formulario de una manera específica, ya que el componente incluye una plantilla. -O se pueden utilizar señales para la comunicación AJAX y cargar información en el formulario, por ejemplo para sugerencias, etc. +Un enfoque completamente diferente es la creación de un [componente|application:components] que incluya un formulario. Esto ofrece nuevas posibilidades, por ejemplo, renderizar el formulario de una manera específica, ya que el componente también incluye una plantilla. O se pueden usar señales para la comunicación AJAX y la carga de información en el formulario, por ejemplo, para sugerencias, etc. ```php @@ -284,9 +281,9 @@ class EditControl extends Nette\Application\UI\Control protected function createComponentForm(): Form { $form = new Form; - $form->addText('title', 'Title:'); - // aquí se añaden campos de formulario adicionales - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Título:'); + // aquí se añaden otros campos del formulario + $form->addSubmit('send', 'Enviar'); $form->onSuccess[] = [$this, 'processForm']; return $form; @@ -303,13 +300,13 @@ class EditControl extends Nette\Application\UI\Control return; } - // invocación de eventos + // disparar el evento $this->onSave($this, $data); } } ``` -Vamos a crear una fábrica que producirá este componente. Basta con [escribir su interfaz |application:components#Components with Dependencies]: +También crearemos una fábrica que producirá este componente. Basta con [escribir su interfaz |application:components#Componentes con dependencias]: ```php interface EditControlFactory @@ -318,14 +315,14 @@ interface EditControlFactory } ``` -Y añadirla al fichero de configuración: +Y añadirla al archivo de configuración: ```neon services: - EditControlFactory ``` -Y ahora podemos solicitar la fábrica y utilizarla en el presentador: +Y ahora podemos solicitar la fábrica y usarla en el Presenter: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -335,13 +332,13 @@ class MyPresenter extends Nette\Application\UI\Presenter ) { } - protected function createComponentEditForm(): Form + protected function createComponentEditForm(): EditControl { $control = $this->controlFactory->create(); $control->onSave[] = function (EditControl $control, $data) { $this->redirect('this'); - // o redirigir al resultado de la edición, por ejemplo + // o redirigimos al resultado de la edición, p.ej.: // $this->redirect('detail', ['id' => $data->id]); }; @@ -349,5 +346,3 @@ class MyPresenter extends Nette\Application\UI\Presenter } } ``` - -{{sitename: Buenas prácticas}} diff --git a/best-practices/es/inject-method-attribute.texy b/best-practices/es/inject-method-attribute.texy index df0ea93d35..c24c918236 100644 --- a/best-practices/es/inject-method-attribute.texy +++ b/best-practices/es/inject-method-attribute.texy @@ -1,21 +1,18 @@ -Métodos y atributos de inyección -******************************** +Métodos y atributos inject +************************** .[perex] -En este artículo, nos centraremos en varias formas de pasar dependencias a los presentadores en el framework Nette. Compararemos el método preferido, que es el constructor, con otras opciones como los métodos y atributos de `inject`. +En este artículo, nos centraremos en las diferentes formas de pasar dependencias a los Presenters en el framework Nette. Compararemos la forma preferida, que es el constructor, con otras opciones como los métodos y atributos `inject`. -En el caso de los presentadores, el método preferido es pasar las dependencias mediante el [constructor |dependency-injection:passing-dependencies#Constructor Injection]. -Sin embargo, si creas un ancestro común del que heredan otros presentadores (por ejemplo, BasePresenter), y este ancestro también tiene dependencias, surge un problema, que llamamos [constructor hell |dependency-injection:passing-dependencies#Constructor hell]. -Esto puede evitarse utilizando métodos alternativos, que incluyen inyectar métodos y atributos (anotaciones). +También para los Presenters, pasar dependencias mediante el [constructor |dependency-injection:passing-dependencies#Paso por constructor] es la ruta preferida. Sin embargo, si creas un ancestro común del que heredan otros Presenters (p. ej., `BasePresenter`), y este ancestro también tiene dependencias, surge un problema que llamamos [constructor hell |dependency-injection:passing-dependencies#Constructor hell]. Esto se puede evitar utilizando rutas alternativas, que son los métodos y atributos (anotaciones) `inject`. -`inject*()` Métodos .[#toc-inject-methods] -========================================== +Métodos `inject*()` +=================== -Se trata de una forma de pasar dependencias mediante [setters |dependency-injection:passing-dependencies#Setter Injection]. Los nombres de estos setters comienzan con el prefijo inject. -Nette DI llama automáticamente a estos métodos con nombre inmediatamente después de crear la instancia del presentador y les pasa todas las dependencias necesarias. Por lo tanto, deben declararse como públicos. +Es una forma de pasar dependencias mediante [setter |dependency-injection:passing-dependencies#Paso por setter]. El nombre de estos setters comienza con el prefijo `inject`. Nette DI llama automáticamente a los métodos con este nombre justo después de crear la instancia del Presenter y les pasa todas las dependencias requeridas. Por lo tanto, deben declararse como public. -`inject*()` pueden considerarse como una especie de extensión del constructor en varios métodos. Gracias a esto, el `BasePresenter` puede tomar dependencias a través de otro método y dejar el constructor libre para sus descendientes: +Los métodos `inject*()` pueden considerarse como una especie de extensión del constructor en múltiples métodos. Gracias a esto, `BasePresenter` puede recibir dependencias a través de otro método y dejar el constructor libre para sus descendientes: ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -39,18 +36,18 @@ class MyPresenter extends BasePresenter } ``` -El presentador puede contener cualquier número de métodos `inject*()`, y cada uno puede tener cualquier número de parámetros. Esto también es genial para los casos en que el presentador [se compone de rasgos |presenter-traits], y cada uno de ellos requiere su propia dependencia. +Un Presenter puede contener cualquier número de métodos `inject*()` y cada uno puede tener cualquier número de parámetros. También son excelentes en casos donde el Presenter está [compuesto de traits |presenter-traits] y cada uno requiere su propia dependencia. -`Inject` Atributos .[#toc-inject-attributes] -============================================ +Atributos `Inject` +================== -Esta es una forma de inyección [en propiedades |dependency-injection:passing-dependencies#Property Injection]. Basta con indicar qué propiedades deben inyectarse, y Nette DI pasa automáticamente las dependencias inmediatamente después de crear la instancia del presentador. Para insertarlas, es necesario declararlas como públicas. +Es una forma de [inyección en la propiedad |dependency-injection:passing-dependencies#Asignación a variable]. Simplemente marca en qué variables se debe inyectar, y Nette DI pasa automáticamente las dependencias justo después de crear la instancia del Presenter. Para poder insertarlas, es necesario declararlas como public. -Las propiedades se marcan con un atributo: (antes se utilizaba la anotación `/** @inject */`) +Marcamos las propiedades con un atributo: (anteriormente se usaba la anotación `/** @inject */`) ```php -use Nette\DI\Attributes\Inject; // esta línea es importante +use Nette\DI\Attributes\Inject; // esta línea es importante class MyPresenter extends Nette\Application\UI\Presenter { @@ -59,9 +56,6 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -La ventaja de este método de pasar dependencias era su forma de notación muy económica. Sin embargo, con la introducción de la [promoción de propiedades |https://blog.nette.org/es/php-8-0-vision-completa-de-las-novedades#toc-constructor-property-promotion] del constructor, el uso del constructor parece más sencillo. +La ventaja de esta forma de pasar dependencias era una sintaxis muy concisa. Sin embargo, con la llegada de [constructor property promotion |https://blog.nette.org/es/php-8-0-complete-overview-of-news#toc-constructor-property-promotion], parece más fácil usar el constructor. -Por otro lado, este método adolece de los mismos defectos que pasar dependencias a propiedades en general: no tenemos control sobre los cambios en la variable y, al mismo tiempo, la variable pasa a formar parte de la interfaz pública de la clase, lo cual no es deseable. - - -{{sitename: Buenas prácticas}} +Por el contrario, esta forma sufre las mismas deficiencias que pasar dependencias a propiedades en general: no tenemos control sobre los cambios en la variable y, al mismo tiempo, la variable se convierte en parte de la interfaz pública de la clase, lo cual no es deseable. diff --git a/best-practices/es/lets-create-contact-form.texy b/best-practices/es/lets-create-contact-form.texy index 2c7e155167..208b6174f5 100644 --- a/best-practices/es/lets-create-contact-form.texy +++ b/best-practices/es/lets-create-contact-form.texy @@ -1,12 +1,12 @@ -Creemos un formulario de contacto +Creando un formulario de contacto ********************************* .[perex] -Veamos cómo crear un formulario de contacto en Nette, incluido el envío a un correo electrónico. ¡Hagámoslo! +Veremos cómo crear un formulario de contacto en Nette, incluyendo el envío por correo electrónico. ¡Así que manos a la obra! -Primero tenemos que crear un nuevo proyecto. Como explica la página de [introducción |nette:installation]. Y luego podemos empezar a crear el formulario. +Primero, debemos crear un nuevo proyecto. Cómo hacerlo se explica en la página [Empezando |nette:installation]. Y luego podemos empezar a crear el formulario. -La forma más sencilla es crear el formulario [directamente en Presenter |forms:in-presenter]. Podemos utilizar el pre-hecho `HomePresenter`. Añadiremos el componente `contactForm` que representa el formulario. Hacemos esto escribiendo el método de fábrica `createComponentContactForm()` en el código que producirá el componente: +La forma más sencilla es crear el [formulario directamente en el Presenter |forms:in-presenter]. Podemos usar el `HomePresenter` predefinido. Añadiremos el componente `contactForm` que representa el formulario. Haremos esto escribiendo el método de fábrica `createComponentContactForm()` en el código, que producirá el componente: ```php use Nette\Application\UI\Form; @@ -17,37 +17,35 @@ class HomePresenter extends Presenter protected function createComponentContactForm(): Form { $form = new Form; - $form->addText('name', 'Name:') - ->setRequired('Enter your name'); + $form->addText('name', 'Nombre:') + ->setRequired('Introduce tu nombre'); $form->addEmail('email', 'E-mail:') - ->setRequired('Enter your e-mail'); - $form->addTextarea('message', 'Message:') - ->setRequired('Enter message'); - $form->addSubmit('send', 'Send'); + ->setRequired('Introduce tu e-mail'); + $form->addTextarea('message', 'Mensaje:') + ->setRequired('Introduce tu mensaje'); + $form->addSubmit('send', 'Enviar'); $form->onSuccess[] = [$this, 'contactFormSucceeded']; return $form; } public function contactFormSucceeded(Form $form, $data): void { - // sending an email + // envío de correo electrónico } } ``` -Como puedes ver, hemos creado dos métodos. El primer método `createComponentContactForm()` crea un nuevo formulario. Este tiene campos para nombre, email y mensaje, que añadimos usando los métodos `addText()`, `addEmail()` y `addTextArea()`. También añadimos un botón para enviar el formulario. -Pero, ¿qué pasa si el usuario no rellena algunos campos? En ese caso, debemos hacerle saber que se trata de un campo obligatorio. Hicimos esto con el método `setRequired()`. -Por último, también añadimos un [evento |nette:glossary#events] `onSuccess`, que se activa si el formulario se envía correctamente. En nuestro caso, llama al método `contactFormSucceeded`, que se encarga de procesar el formulario enviado. Lo añadiremos al código en un momento. +Como puedes ver, hemos creado dos métodos. El primer método `createComponentContactForm()` crea un nuevo formulario. Tiene campos para el nombre, correo electrónico y mensaje, que añadimos con los métodos `addText()`, `addEmail()` y `addTextArea()`. También hemos añadido un botón para enviar el formulario. Pero, ¿y si el usuario no rellena algún campo? En ese caso, deberíamos informarle de que es un campo obligatorio. Logramos esto con el método `setRequired()`. Finalmente, también añadimos el [evento |nette:glossary#Eventos] `onSuccess`, que se dispara si el formulario se envía con éxito. En nuestro caso, llama al método `contactFormSucceeded`, que se encargará de procesar el formulario enviado. Añadiremos esto al código en un momento. -Dejemos que el componente `contantForm` sea renderizado en la plantilla `templates/Home/default.latte`: +Haremos que el componente `contactForm` se renderice en la plantilla `Home/default.latte`: ```latte {block content} -<h1>Contant Form</h1> +<h1>Formulario de contacto</h1> {control contactForm} ``` -Para enviar el correo electrónico propiamente dicho, creamos una nueva clase llamada `ContactFacade` y la colocamos en el archivo `app/Model/ContactFacade.php`: +Para el envío real del correo electrónico, crearemos una nueva clase, que llamaremos `ContactFacade` y la ubicaremos en el archivo `app/Model/ContactFacade.php`: ```php <?php @@ -68,9 +66,9 @@ class ContactFacade public function sendMessage(string $email, string $name, string $message): void { $mail = new Message; - $mail->addTo('admin@example.com') // your email + $mail->addTo('admin@example.com') // tu correo electrónico ->setFrom($email, $name) - ->setSubject('Message from the contact form') + ->setSubject('Mensaje del formulario de contacto') ->setBody($message); $this->mailer->send($mail); @@ -78,9 +76,9 @@ class ContactFacade } ``` -El método `sendMessage()` creará y enviará el correo electrónico. Para ello utiliza el llamado mailer, que pasa como dependencia a través del constructor. Más información sobre el envío de [correos electrónicos |mail:]. +El método `sendMessage()` crea y envía el correo electrónico. Utiliza para ello el llamado mailer, que recibe como dependencia a través del constructor. Lee más sobre [envío de correos electrónicos |mail:]. -Ahora, volveremos al presentador y completaremos el método `contactFormSucceeded()`. Llama al método `sendMessage()` de la clase `ContactFacade` y le pasa los datos del formulario. ¿Y cómo obtenemos el objeto `ContactFacade`? Nos lo pasará el constructor: +Ahora volveremos al Presenter y completaremos el método `contactFormSucceeded()`. Este llamará al método `sendMessage()` de la clase `ContactFacade` y le pasará los datos del formulario. ¿Y cómo obtenemos el objeto `ContactFacade`? Lo recibiremos a través del constructor: ```php use App\Model\ContactFacade; @@ -102,36 +100,36 @@ class HomePresenter extends Presenter public function contactFormSucceeded(stdClass $data): void { $this->facade->sendMessage($data->email, $data->name, $data->message); - $this->flashMessage('The message has been sent'); + $this->flashMessage('El mensaje ha sido enviado'); $this->redirect('this'); } } ``` -Una vez enviado el email, mostramos al usuario el llamado [mensaje flash |application:components#flash-messages], confirmando que el mensaje ha sido enviado, y luego redirigimos a la siguiente página para que el formulario no pueda ser reenviado usando *refresh* en el navegador. +Después de enviar el correo electrónico, mostraremos al usuario un llamado [flash message |application:components#Mensajes flash], confirmando que el mensaje se ha enviado, y luego redirigiremos a la siguiente página para que no sea posible reenviar el formulario usando *refresh* en el navegador. -Bien, si todo funciona, deberías poder enviar un correo electrónico desde tu formulario de contacto. ¡Enhorabuena! +Bien, y si todo funciona, deberías poder enviar un correo electrónico desde tu formulario de contacto. ¡Felicidades! -Plantilla HTML de correo electrónico .[#toc-html-email-template] ----------------------------------------------------------------- +Plantilla HTML del correo electrónico +------------------------------------- -Por ahora, se envía un email de texto plano que contiene sólo el mensaje enviado por el formulario. Pero podemos utilizar HTML en el email y hacerlo más atractivo. Crearemos una plantilla para ello en Latte, que guardaremos en `app/Model/contactEmail.latte`: +Hasta ahora, se envía un correo electrónico de texto sin formato que contiene solo el mensaje enviado por el formulario. Pero podemos usar HTML en el correo electrónico y hacer que su apariencia sea más atractiva. Crearemos una plantilla para ello en Latte, que escribiremos en `app/Model/contactEmail.latte`: ```latte <html> - <title>Message from the contact form + Mensaje del formulario de contacto -

    Name: {$name}

    +

    Nombre: {$name}

    E-mail: {$email}

    -

    Message: {$message}

    +

    Mensaje: {$message}

    ``` -Queda modificar `ContactFacade` para utilizar esta plantilla. En el constructor, solicitamos la clase `LatteFactory`, que puede producir el objeto `Latte\Engine`, un [renderizador de |latte:develop#how-to-render-a-template] plantillas Latte. Usamos el método `renderToString()` para renderizar la plantilla a un fichero, el primer parámetro es la ruta a la plantilla y el segundo las variables. +Queda por modificar `ContactFacade` para que use esta plantilla. En el constructor, solicitaremos la clase `LatteFactory`, que puede crear un objeto `Latte\Engine`, es decir, el [renderizador de plantillas Latte |latte:develop#Cómo renderizar una plantilla]. Usando el método `renderToString()`, renderizaremos la plantilla en un archivo, el primer parámetro es la ruta a la plantilla y el segundo son las variables. ```php namespace App\Model; @@ -158,7 +156,7 @@ class ContactFacade ]); $mail = new Message; - $mail->addTo('admin@example.com') // your email + $mail->addTo('admin@example.com') // tu correo electrónico ->setFrom($email, $name) ->setHtmlBody($body); @@ -167,15 +165,15 @@ class ContactFacade } ``` -Luego pasamos el correo HTML generado al método `setHtmlBody()` en lugar del original `setBody()`. Tampoco necesitamos especificar el asunto del correo electrónico en `setSubject()`, porque la biblioteca lo toma del elemento `` de la plantilla. +Luego pasaremos el correo electrónico HTML generado al método `setHtmlBody()` en lugar del `setBody()` original. Tampoco necesitamos especificar el asunto del correo electrónico en `setSubject()`, ya que la biblioteca lo tomará del elemento `<title>` de la plantilla. -Configuración de .[#toc-configuring] ------------------------------------- +Configuración +------------- -En el código de la clase `ContactFacade`, nuestro email de administrador `admin@example.com` está todavía codificado. Sería mejor moverlo al archivo de configuración. ¿Cómo hacerlo? +En el código de la clase `ContactFacade`, nuestro correo electrónico de administrador `admin@example.com` todavía está codificado. Sería mejor moverlo al archivo de configuración. ¿Cómo hacerlo? -Primero, modificamos la clase `ContactFacade` y reemplazamos la cadena de email con una variable pasada por el constructor: +Primero, modificaremos la clase `ContactFacade` y reemplazaremos la cadena con el correo electrónico por una variable pasada a través del constructor: ```php class ContactFacade @@ -199,28 +197,25 @@ class ContactFacade } ``` -Y el segundo paso es poner el valor de esta variable en la configuración. En el fichero `app/config/services.neon` añadimos: +Y el segundo paso es especificar el valor de esta variable en la configuración. En el archivo `app/config/services.neon`, escribimos: ```neon services: - App\Model\ContactFacade(adminEmail: admin@example.com) ``` -Y ya está. Si hay muchos elementos en la sección `services` y te parece que el correo electrónico se pierde entre ellos, podemos convertirlo en una variable. Modificaremos la entrada a: +Y eso es todo. Si hubiera muchos elementos en la sección `services` y sintieras que el correo electrónico se pierde entre ellos, podemos convertirlo en una variable. Modificamos la entrada a: ```neon services: - App\Model\ContactFacade(adminEmail: %adminEmail%) ``` -Y definiremos esta variable en el fichero `app/config/common.neon`: +Y en el archivo `app/config/common.neon`, definimos esta variable: ```neon parameters: adminEmail: admin@example.com ``` -¡Y ya está! - - -{{sitename: Buenas prácticas}} +¡Y listo! diff --git a/best-practices/es/microsites.texy b/best-practices/es/microsites.texy new file mode 100644 index 0000000000..392f299d5f --- /dev/null +++ b/best-practices/es/microsites.texy @@ -0,0 +1,63 @@ +Cómo escribir micro-sitios web +****************************** + +Imagina que necesitas crear rápidamente un pequeño sitio web para el próximo evento de tu empresa. Tiene que ser simple, rápido y sin complicaciones innecesarias. Quizás pienses que para un proyecto tan pequeño no necesitas un framework robusto. Pero, ¿y si usar el framework Nette puede simplificar y acelerar fundamentalmente este proceso? + +Incluso al crear sitios web simples, no quieres renunciar a la comodidad. No quieres inventar lo que ya ha sido resuelto una vez. Siéntete libre de ser perezoso y déjate mimar. Nette Framework también se puede utilizar perfectamente como un micro framework. + +¿Cómo puede verse un micrositio así? Por ejemplo, colocando todo el código del sitio web en un único archivo `index.php` en la carpeta pública: + +```php +<?php + +require __DIR__ . '/../vendor/autoload.php'; + +$configurator = new Nette\Bootstrap\Configurator; +$configurator->enableTracy(__DIR__ . '/../log'); +$configurator->setTempDirectory(__DIR__ . '/../temp'); + +// crear contenedor DI basado en la configuración en config.neon +$configurator->addConfig(__DIR__ . '/../app/config.neon'); +$container = $configurator->createContainer(); + +// configurar el enrutamiento +$router = new Nette\Application\Routers\RouteList; +$container->addService('router', $router); + +// ruta para la URL https://example.com/ +$router->addRoute('', function ($presenter, Nette\Http\Request $httpRequest) { + // detectar el idioma del navegador y redirigir a la URL /en o /de, etc. + $supportedLangs = ['en', 'de', 'cs']; + $lang = $httpRequest->detectLanguage($supportedLangs) ?: reset($supportedLangs); + $presenter->redirectUrl("/$lang"); +}); + +// ruta para la URL https://example.com/cs o https://example.com/en +$router->addRoute('<lang cs|en>', function ($presenter, string $lang) { + // mostrar la plantilla correspondiente, por ejemplo ../templates/en.latte + $template = $presenter->createTemplate() + ->setFile(__DIR__ . '/../templates/' . $lang . '.latte'); + return $template; +}); + +// ¡ejecutar la aplicación! +$container->getByType(Nette\Application\Application::class)->run(); +``` + +Todo lo demás serán plantillas almacenadas en la carpeta padre `/templates`. + +El código PHP en `index.php` primero [prepara el entorno |bootstrap:], luego define las [rutas |application:routing#Enrutamiento dinámico con callbacks] y finalmente ejecuta la aplicación. La ventaja es que el segundo parámetro de la función `addRoute()` puede ser un callable que se ejecuta después de abrir la página correspondiente. + + +¿Por qué usar Nette para un micrositio? +--------------------------------------- + +- Los programadores que alguna vez han probado [Tracy|tracy:] hoy no pueden imaginar programar nada sin ella. +- Pero sobre todo, utilizarás el sistema de plantillas [Latte|latte:], porque a partir de 2 páginas querrás tener separados el [layout y el contenido|latte:template-inheritance]. +- Y definitivamente quieres confiar en el [escape automático |latte:safety-first] para evitar la vulnerabilidad XSS. +- Nette también asegura que en caso de error, nunca se muestren mensajes de error de PHP para programadores, sino una página comprensible para el usuario. +- Si quieres obtener retroalimentación de los usuarios, por ejemplo, en forma de formulario de contacto, entonces también añadirás [formularios|forms:] y [base de datos|database:]. +- También puedes hacer que los formularios completados se [envíen fácilmente por correo electrónico|mail:]. +- A veces te puede resultar útil el [caching|caching:], por ejemplo, si descargas y muestras feeds. + +En la actualidad, donde la velocidad y la eficiencia son clave, es importante tener herramientas que te permitan lograr resultados sin demoras innecesarias. Nette framework te ofrece precisamente eso: desarrollo rápido, seguridad y una amplia gama de herramientas, como Tracy y Latte, que simplifican el proceso. Simplemente instala algunos paquetes de Nette y construir un micrositio así se convierte de repente en un juego de niños. Y sabes que no hay ninguna brecha de seguridad oculta en ninguna parte. diff --git a/best-practices/es/pagination.texy b/best-practices/es/pagination.texy index 4638f53da7..13d443cbaf 100644 --- a/best-practices/es/pagination.texy +++ b/best-practices/es/pagination.texy @@ -1,17 +1,16 @@ -Paginación de resultados de bases de datos -****************************************** +Paginación de resultados de la base de datos +******************************************** .[perex] -Al desarrollar aplicaciones web, a menudo se encuentra con la necesidad de imprimir un número restringido de registros en una página. +Al crear aplicaciones web, muy a menudo te encontrarás con el requisito de limitar el número de elementos mostrados por página. -Salimos de este estado cuando listamos todos los datos sin paginar. Para seleccionar los datos de la base de datos, tenemos la clase ArticleRepository, que contiene el constructor y el método `findPublishedArticles`, que devuelve todos los artículos publicados ordenados en orden descendente de fecha de publicación. +Partiremos del estado en el que mostramos todos los datos sin paginación. Para seleccionar datos de la base de datos, tenemos la clase ArticleRepository, que además del constructor contiene el método `findPublishedArticles`, que devuelve todos los artículos publicados ordenados descendentemente por fecha de publicación. ```php namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -31,10 +30,10 @@ class ArticleRepository } ``` -En el Presentador inyectamos entonces la clase modelo y en el método render pediremos los artículos publicados que pasamos al modelo: +En el Presenter, inyectamos la clase del modelo y en el método render solicitamos los artículos publicados, que pasamos a la plantilla: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -53,11 +52,11 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -En la plantilla nos encargaremos de renderizar una lista de artículos: +En la plantilla `default.latte` nos encargamos de mostrar los artículos: ```latte {block content} -<h1>Articles</h1> +<h1>Artículos</h1> <div class="articles"> {foreach $articles as $article} @@ -68,11 +67,11 @@ En la plantilla nos encargaremos de renderizar una lista de artículos: ``` -De esta manera, podemos escribir todos los artículos, pero esto causará problemas cuando el número de artículos crezca. En ese momento, será útil implementar el mecanismo de paginación. +De esta manera, podemos mostrar todos los artículos, lo que, sin embargo, comenzará a causar problemas cuando el número de artículos aumente. En ese momento, será útil implementar un mecanismo de paginación. -Esto asegurará que todos los artículos se dividan en varias páginas y sólo mostraremos los artículos de una página actual. El número total de páginas y la distribución de los artículos lo calcula el propio [utils:Paginator], dependiendo de cuántos artículos tengamos en total y cuántos artículos queramos mostrar en la página. +Este asegurará que todos los artículos se dividan en varias páginas y solo mostraremos los artículos de la página actual. El número total de páginas y la división de los artículos los calculará [utils:Paginator] por sí mismo según cuántos artículos tengamos en total y cuántos artículos por página queramos mostrar. -En el primer paso, modificaremos el método para obtener artículos de la clase repositorio para que sólo devuelva artículos de una sola página. También añadiremos un nuevo método para obtener el número total de artículos en la base de datos, que necesitaremos para establecer un Paginator: +En el primer paso, modificaremos el método para obtener artículos en la clase del repositorio para que pueda devolver solo los artículos de una página. También añadiremos un método para averiguar el número total de artículos en la base de datos, que necesitaremos para configurar el Paginator: ```php namespace App\Model; @@ -100,7 +99,7 @@ class ArticleRepository } /** - * Returns the total number of published articles + * Devuelve el número total de artículos publicados */ public function getPublishedArticlesCount(): int { @@ -109,12 +108,12 @@ class ArticleRepository } ``` -El siguiente paso es editar el presentador. Enviaremos el número de la página actualmente mostrada al método render. En el caso de que este número no forme parte de la URL, debemos establecer el valor por defecto en la primera página. +A continuación, procederemos a modificar el Presenter. Pasaremos el número de la página actualmente mostrada al método render. En caso de que este número no forme parte de la URL, estableceremos el valor predeterminado de la primera página. -También expandimos el método render para obtener la instancia Paginator, configurándola, y seleccionando los artículos correctos para mostrar en la plantilla. HomePresenter tendrá este aspecto: +También ampliaremos el método render para obtener una instancia de Paginator, configurarlo y seleccionar los artículos correctos para mostrar en la plantilla. HomePresenter se verá así después de las modificaciones: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -128,31 +127,31 @@ class HomePresenter extends Nette\Application\UI\Presenter public function renderDefault(int $page = 1): void { - // Encontraremos el número total de artículos publicados + // Averiguamos el número total de artículos publicados $articlesCount = $this->articleRepository->getPublishedArticlesCount(); - // Crearemos la instancia Paginator y la configuraremos - $paginator = new Nette\Utils\Paginator; - $paginator->setItemCount($articlesCount); // recuento total de artículos - $paginator->setItemsPerPage(10); // artículos por página - $paginator->setPage($page); // número de página actual + // Creamos una instancia de Paginator y la configuramos + $paginator = new Paginator; + $paginator->setItemCount($articlesCount); // número total de artículos + $paginator->setItemsPerPage(10); // número de elementos por página + $paginator->setPage($page); // número de la página actual - // Buscaremos un conjunto limitado de artículos de la base de datos basándonos en los cálculos de Paginator + // Extraemos de la base de datos un conjunto limitado de artículos según el cálculo de Paginator $articles = $this->articleRepository->findPublishedArticles($paginator->getLength(), $paginator->getOffset()); // que pasamos a la plantilla $this->template->articles = $articles; - // y también al propio Paginator para mostrar las opciones de paginación + // y también el propio Paginator para mostrar las opciones de paginación $this->template->paginator = $paginator; } } ``` -La plantilla ya itera sobre los artículos en una página, sólo hay que añadir enlaces de paginación: +La plantilla ahora solo itera sobre los artículos de una página, solo necesitamos añadir los enlaces de paginación: ```latte {block content} -<h1>Articles</h1> +<h1>Artículos</h1> <div class="articles"> {foreach $articles as $article} @@ -163,34 +162,33 @@ La plantilla ya itera sobre los artículos en una página, sólo hay que añadir <div class="pagination"> {if !$paginator->isFirst()} - <a n:href="default, 1">First</a> + <a n:href="default, 1">Primera</a>  |  - <a n:href="default, $paginator->page-1">Previous</a> + <a n:href="default, $paginator->page-1">Anterior</a>  |  {/if} - Page {$paginator->getPage()} of {$paginator->getPageCount()} + Página {$paginator->getPage()} de {$paginator->getPageCount()} {if !$paginator->isLast()}  |  - <a n:href="default, $paginator->getPage() + 1">Next</a> + <a n:href="default, $paginator->getPage() + 1">Siguiente</a>  |  - <a n:href="default, $paginator->getPageCount()">Last</a> + <a n:href="default, $paginator->getPageCount()">Última</a> {/if} </div> ``` -Así es como hemos añadido la paginación usando Paginator. Si se utiliza [Nette Database Explorer |database:explorer] en lugar de [Nette Database Core |database:core] como capa de base de datos, podemos implementar la paginación incluso sin Paginator. La clase `Nette\Database\Table\Selection` contiene el método [page |api:Nette\Database\Table\Selection::_ page] con la lógica de paginación tomada del Paginator. +Así hemos añadido la opción de paginación a la página usando Paginator. En caso de que en lugar de [Nette Database Core |database:sql-way] usemos [Nette Database Explorer |database:explorer] como capa de base de datos, podemos implementar la paginación incluso sin usar Paginator. La clase `Nette\Database\Table\Selection` contiene el método [page |api:Nette\Database\Table\Selection::_page] con la lógica de paginación tomada de Paginator. -El repositorio tendrá este aspecto: +El repositorio se verá así con esta forma de implementación: ```php namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -198,7 +196,6 @@ class ArticleRepository ) { } - public function findPublishedArticles(): Nette\Database\Table\Selection { return $this->database->table('articles') @@ -208,10 +205,10 @@ class ArticleRepository } ``` -No tenemos que crear Paginator en el Presentador, en su lugar utilizaremos el método del objeto `Selection` devuelto por el repositorio: +En el Presenter no necesitamos crear un Paginator, usaremos en su lugar el método de la clase `Selection`, que nos devuelve el repositorio: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -225,11 +222,11 @@ class HomePresenter extends Nette\Application\UI\Presenter public function renderDefault(int $page = 1): void { - // Encontraremos artículos publicados + // Extraemos los artículos publicados $articles = $this->articleRepository->findPublishedArticles(); - // y su parte limitada por el método de cálculo de páginas que pasaremos a la plantilla - $lastPage = 0 + // y a la plantilla enviamos solo una parte de ellos limitada según el cálculo del método page + $lastPage = 0; $this->template->articles = $articles->page($page, 10, $lastPage); // y también los datos necesarios para mostrar las opciones de paginación @@ -239,11 +236,11 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -Como no usamos Paginator, necesitamos editar la sección que muestra los enlaces de paginación: +Dado que ahora no enviamos Paginator a la plantilla, modificaremos la parte que muestra los enlaces de paginación: ```latte {block content} -<h1>Articles</h1> +<h1>Artículos</h1> <div class="articles"> {foreach $articles as $article} @@ -254,24 +251,23 @@ Como no usamos Paginator, necesitamos editar la sección que muestra los enlaces <div class="pagination"> {if $page > 1} - <a n:href="default, 1">First</a> + <a n:href="default, 1">Primera</a>  |  - <a n:href="default, $page - 1">Previous</a> + <a n:href="default, $page - 1">Anterior</a>  |  {/if} - Page {$page} of {$lastPage} + Página {$page} de {$lastPage} {if $page < $lastPage}  |  - <a n:href="default, $page + 1">Next</a> + <a n:href="default, $page + 1">Siguiente</a>  |  - <a n:href="default, $lastPage">Last</a> + <a n:href="default, $lastPage">Última</a> {/if} </div> ``` -De esta manera, implementamos un mecanismo de paginación sin usar un Paginador. +De esta manera, hemos implementado el mecanismo de paginación sin usar Paginator. {{priority: -1}} -{{sitename: Buenas prácticas}} diff --git a/best-practices/es/passing-settings-to-presenters.texy b/best-practices/es/passing-settings-to-presenters.texy index cd7f4b0303..a54e1ab5da 100644 --- a/best-practices/es/passing-settings-to-presenters.texy +++ b/best-practices/es/passing-settings-to-presenters.texy @@ -1,10 +1,10 @@ -Transmisión de ajustes a los presentadores -****************************************** +Pasar la configuración a los Presenters +*************************************** .[perex] -¿Necesita pasar argumentos a los presentadores que no son objetos (por ejemplo, información sobre si se está ejecutando en modo de depuración, rutas de directorios, etc.) y que, por tanto, no se pueden pasar automáticamente mediante autocableado? La solución es encapsularlos en un objeto `Settings`. +¿Necesitas pasar argumentos a los Presenters que no son objetos (p. ej., información sobre si se ejecuta en modo de depuración, rutas a directorios, etc.) y, por lo tanto, no se pueden pasar automáticamente mediante autowiring? La solución es encapsularlos en un objeto `Settings`. -El servicio `Settings` es una forma muy sencilla y útil de proporcionar información sobre una aplicación en ejecución a los presentadores. Su forma concreta depende enteramente de sus necesidades particulares. Ejemplo: +El servicio `Settings` representa una forma muy fácil y útil de proporcionar información sobre la aplicación en ejecución a los Presenters. Su forma específica depende puramente de tus necesidades concretas. Ejemplo: ```php namespace App; @@ -12,15 +12,15 @@ namespace App; class Settings { public function __construct( - // since PHP 8.1 it is possible to specify readonly - public bool $debugMode, - public string $appDir, - // and so on + // desde PHP 8.1 es posible indicar readonly + public readonly bool $debugMode, + public readonly string $appDir, + // y así sucesivamente ) {} } ``` -Ejemplo de inscripción a la configuración: +Ejemplo de registro en la configuración: ```neon services: @@ -30,7 +30,7 @@ services: ) ``` -Cuando el presentador necesita la información proporcionada por este servicio, simplemente la solicita en el constructor: +Cuando un Presenter necesite la información proporcionada por este servicio, simplemente la solicitará en el constructor: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -47,5 +47,3 @@ class MyPresenter extends Nette\Application\UI\Presenter } } ``` - -{{sitename: Buenas prácticas}} diff --git a/best-practices/es/post-links.texy b/best-practices/es/post-links.texy new file mode 100644 index 0000000000..8b0d2e0bab --- /dev/null +++ b/best-practices/es/post-links.texy @@ -0,0 +1,56 @@ +Cómo usar correctamente los enlaces POST +**************************************** + +.[perex] +En las aplicaciones web, especialmente en las interfaces administrativas, debería ser una regla básica que las acciones que cambian el estado del servidor no se realicen mediante el método HTTP GET. Como sugiere el nombre del método, GET solo debe usarse para obtener datos, no para modificarlos. Para acciones como eliminar registros, es preferible usar el método POST. Aunque lo ideal sería el método DELETE, pero no se puede invocar sin JavaScript, por lo que históricamente se usa POST. + +¿Cómo hacerlo en la práctica? Utiliza este simple truco. Al principio de la plantilla, crea un formulario auxiliar con el identificador `postForm`, que luego usarás para los botones de eliminación: + +```latte .{file:@layout.latte} +<form method="post" id="postForm"></form> +``` + +Gracias a este formulario, en lugar del enlace clásico `<a>`, puedes usar un botón `<button>`, que se puede modificar visualmente para que parezca un enlace normal. Por ejemplo, el framework CSS Bootstrap ofrece las clases `btn btn-link` con las que conseguirás que el botón no sea visualmente diferente de otros enlaces. Mediante el atributo `form="postForm"`, lo vinculamos con el formulario predefinido: + +```latte .{file:admin.latte} +<table> + <tr n:foreach="$posts as $post"> + <td>{$post->title}</td> + <td> + <button class="btn btn-link" form="postForm" formaction="{link delete $post->id}">eliminar</button> + <!-- en lugar de <a n:href="delete $post->id">eliminar</a> --> + </td> + </tr> +</table> +``` + +Al hacer clic en el enlace, ahora se invoca la acción `delete`. Para asegurar que las solicitudes se acepten solo mediante el método POST y desde el mismo dominio (lo cual es una defensa eficaz contra los ataques CSRF), utiliza el atributo `#[Requires]`: + +```php .{file:AdminPresenter.php} +use Nette\Application\Attributes\Requires; + +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST', sameOrigin: true)] + public function actionDelete(int $id): void + { + $this->facade->deletePost($id); // código hipotético que elimina el registro + $this->redirect('default'); + } +} +``` + +El atributo existe desde Nette Application 3.2 y puedes aprender más sobre sus posibilidades en la página [Cómo usar el atributo #Requires |attribute-requires]. + +Si en lugar de la acción `actionDelete()` usaras la señal `handleDelete()`, no es necesario especificar `sameOrigin: true`, porque las señales tienen esta protección configurada implícitamente: + +```php .{file:AdminPresenter.php} +#[Requires(methods: 'POST')] +public function handleDelete(int $id): void +{ + $this->facade->deletePost($id); + $this->redirect('this'); +} +``` + +Este enfoque no solo mejora la seguridad de tu aplicación, sino que también contribuye al cumplimiento de los estándares y prácticas web correctos. Al utilizar métodos POST para acciones que cambian el estado, lograrás una aplicación más robusta y segura. diff --git a/best-practices/es/presenter-traits.texy b/best-practices/es/presenter-traits.texy index a50e8ec4e1..b1dc7af72f 100644 --- a/best-practices/es/presenter-traits.texy +++ b/best-practices/es/presenter-traits.texy @@ -1,14 +1,14 @@ -Composición de presentadores a partir de rasgos -*********************************************** +Composición de Presenters a partir de traits +******************************************** .[perex] -Si necesitamos implementar el mismo código en varios presentadores (por ejemplo, verificar que el usuario ha iniciado sesión), resulta tentador colocar el código en un ancestro común. La segunda opción es crear rasgos de propósito único. +Si necesitamos implementar el mismo código en varios Presenters (p. ej., verificar que el usuario haya iniciado sesión), una opción es colocar el código en un ancestro común. La segunda opción es crear [traits |nette:introduction-to-object-oriented-programming#Traits] de propósito único. -La ventaja de esta solución es que cada presentador puede utilizar sólo los rasgos que realmente necesita, mientras que la herencia múltiple no es posible en PHP. +La ventaja de esta solución es que cada Presenter puede usar exactamente los traits que realmente necesita, mientras que la herencia múltiple no es posible en PHP. -Estos traits pueden aprovechar el hecho de que todos los [métodos de inyección |inject-method-attribute#inject methods] son llamados secuencialmente cuando se crea el presentador. Sólo tiene que asegurarse de que el nombre de cada método de inyección es único. +Estos traits pueden aprovechar el hecho de que al crear un Presenter, todos los [métodos inject |inject-method-attribute#Métodos inject] se llaman secuencialmente. Solo es necesario asegurarse de que el nombre de cada método inject sea único. -Los traits pueden colgar el código de inicialización en los eventos [onStartup o onRender |application:presenters#Events]. +Los traits pueden adjuntar código de inicialización a los eventos [onStartup u onRender |application:presenters#Eventos]. Ejemplos: @@ -36,7 +36,7 @@ trait StandardTemplateFilters } ``` -El presentador se limita a utilizar estos rasgos: +El Presenter luego simplemente usa estos traits: ```php class ArticlePresenter extends Nette\Application\UI\Presenter @@ -45,6 +45,3 @@ class ArticlePresenter extends Nette\Application\UI\Presenter use RequireLoggedUser; } ``` - - -{{sitename: Buenas prácticas}} diff --git a/best-practices/es/restore-request.texy b/best-practices/es/restore-request.texy index b4f08ec217..23de10d19f 100644 --- a/best-practices/es/restore-request.texy +++ b/best-practices/es/restore-request.texy @@ -1,17 +1,16 @@ -¿Cómo volver a una página anterior? -*********************************** +¿Cómo volver a la página anterior? +********************************** .[perex] -¿Qué pasa si un usuario rellena un formulario y su login caduca? Para no perder los datos, guardamos los datos en la sesión antes de redirigir a la página de inicio de sesión. En Nette, esto es pan comido. +¿Qué pasa si un usuario está llenando un formulario y su sesión expira? Para que no pierda los datos, antes de redirigir a la página de inicio de sesión, guardamos los datos en la sesión. En Nette, esto es pan comido. -La solicitud actual puede almacenarse en la sesión utilizando el método `storeRequest()`, que devuelve su identificador como una cadena corta. El método almacena el nombre del presentador actual, la vista y sus parámetros. -Si también se presentó un formulario, los valores de los campos (excepto los archivos cargados) también se guardan. +La solicitud actual se puede guardar en la sesión usando el método `storeRequest()`, que devuelve su identificador en forma de una cadena corta. El método guarda el nombre del Presenter actual, la vista y sus parámetros. En caso de que también se haya enviado un formulario, también se guarda el contenido de los campos (con la excepción de los archivos subidos). -La solicitud es restaurada por el método `restoreRequest($key)`, al que pasamos el identificador recuperado. Esto redirige al presentador y a la vista originales. Sin embargo, si la petición guardada contiene el envío de un formulario, se reenviará al presentador original mediante el método `forward()`, se pasarán los valores rellenados previamente al formulario y se dejará que se redibuje. Esto permite al usuario reenviar el formulario y no se pierde ningún dato. +La restauración de la solicitud la realiza el método `restoreRequest($key)`, al que le pasamos el identificador obtenido. Este redirige al Presenter y vista originales. Sin embargo, si la solicitud guardada contiene el envío de un formulario, pasa al Presenter original mediante el método `forward()`, le pasa los valores previamente completados al formulario y lo vuelve a renderizar. De esta manera, el usuario tiene la posibilidad de reenviar el formulario y no se pierde ningún dato. -Es importante destacar que `restoreRequest()` comprueba que el nuevo usuario es el mismo que rellenó el formulario originalmente. Si no es así, descarta la solicitud y no hace nada. +Es importante que `restoreRequest()` compruebe si el usuario recién conectado es el mismo que completó originalmente el formulario. Si no es así, descarta la solicitud y no hace nada. -Vamos a demostrarlo todo con un ejemplo. Tengamos un presentador `AdminPresenter` en el que se están editando datos y cuyo método `startup()` comprueba si el usuario ha iniciado sesión. Si no lo está, le redirigimos a `SignPresenter`. Al mismo tiempo, guardamos la petición actual y enviamos su clave a `SignPresenter`. +Mostraremos todo con un ejemplo. Tenemos un Presenter `AdminPresenter`, en el que se editan datos y en cuyo método `startup()` verificamos si el usuario ha iniciado sesión. Si no es así, lo redirigimos a `SignPresenter`. Al mismo tiempo, guardamos la solicitud actual y enviamos su clave a `SignPresenter`. ```php class AdminPresenter extends Nette\Application\UI\Presenter @@ -27,7 +26,7 @@ class AdminPresenter extends Nette\Application\UI\Presenter } ``` -El presentador `SignPresenter` contendrá un parámetro persistente `$backlink` en el que se escribirá la clave, además del formulario de inicio de sesión. Como el parámetro es persistente, se mantendrá incluso después de que se envíe el formulario de inicio de sesión. +El Presenter `SignPresenter`, además del formulario de inicio de sesión, contendrá también un parámetro persistente `$backlink`, en el que se escribirá la clave. Dado que el parámetro es persistente, se transferirá también después de enviar el formulario de inicio de sesión. ```php @@ -38,17 +37,17 @@ class SignPresenter extends Nette\Application\UI\Presenter #[Persistent] public string $backlink = ''; - protected function createComponentSignInForm() + protected function createComponentSignInForm(): Form { - $form = new Nette\Application\UI\Form; - // ... añadir campos de formulario ... + $form = new Form; + // ... añadimos los campos del formulario ... $form->onSuccess[] = [$this, 'signInFormSubmitted']; return $form; } public function signInFormSubmitted($form) { - // ... aquí registramos al usuario ... + // ... aquí iniciamos sesión del usuario ... $this->restoreRequest($this->backlink); $this->redirect('Admin:'); @@ -56,9 +55,8 @@ class SignPresenter extends Nette\Application\UI\Presenter } ``` -Pasamos la clave de la solicitud guardada al método `restoreRequest()` y éste redirige (o reenvía) al presentador original. +Pasamos la clave de la solicitud guardada al método `restoreRequest()` y este redirige (o avanza) al Presenter original. -Sin embargo, si la clave no es válida (por ejemplo, ya no existe en la sesión), el método no hace nada. Así que la siguiente llamada es `$this->redirect('Admin:')`, que redirige a `AdminPresenter`. +Sin embargo, si la clave no es válida (por ejemplo, ya no existe en la sesión), el método no hace nada. Por lo tanto, sigue la llamada `$this->redirect('Admin:')`, que redirige a `AdminPresenter`. {{priority: -1}} -{{sitename: Buenas prácticas}} diff --git a/best-practices/fr/@home.texy b/best-practices/fr/@home.texy index 0b3d8c26f6..829741034c 100644 --- a/best-practices/fr/@home.texy +++ b/best-practices/fr/@home.texy @@ -1,8 +1,8 @@ -Meilleures pratiques -******************** +Tutoriels et bonnes pratiques +***************************** .[perex] -Tutoriels, solutions aux problèmes courants et meilleures pratiques pour Nette. +Tutoriels, solutions pour les tâches courantes et *bonnes pratiques* pour Nette. <div class=documentation> @@ -11,12 +11,14 @@ Tutoriels, solutions aux problèmes courants et meilleures pratiques pour Nette. Application Nette ----------------- -- [Injecter des méthodes et des attributs |inject-method-attribute] -- [Composer des présentateurs à partir de traits |presenter-traits] -- [Passer des paramètres aux présentateurs |passing-settings-to-presenters] +- [Méthodes et attributs inject |inject-method-attribute] +- [Composition des presenters à partir de traits |presenter-traits] +- [Passage des paramètres aux presenters |passing-settings-to-presenters] - [Comment revenir à une page précédente |restore-request] -- [Pagination des résultats de la base de données |Pagination] -- [Extraits dynamiques |dynamic-snippets] +- [Pagination des résultats de la base de données |pagination] +- [Snippets dynamiques |dynamic-snippets] +- [Comment utiliser l'attribut #Requires |attribute-requires] +- [Comment utiliser correctement les liens POST |post-links] </div> <div> @@ -25,32 +27,34 @@ Application Nette Formulaires ----------- - [Réutilisation des formulaires |form-reuse] -- [Formulaire de création et d'édition d'une fiche |creating-editing-form] +- [Formulaire pour la création et l'édition d'enregistrements |creating-editing-form] - [Créons un formulaire de contact |lets-create-contact-form] -- [Boîtes de sélection dépendantes |https://blog.nette.org/fr/des-boites-de-selection-dependantes-de-facon-elegante-en-nette-et-en-js-pur] +- [Selectbox dépendants |https://blog.nette.org/fr/dependent-selectboxes-elegantly-in-nette-and-pure-js] </div> <div> -Commun ------- -- [Comment charger le fichier de configuration |bootstrap:] -- [Pourquoi Nette utilise la notation constante PascalCase ? |https://blog.nette.org/fr/pour-moins-crier-dans-le-code] -- [Pourquoi Nette n'utilise pas le suffixe Interface ? |https://blog.nette.org/fr/les-prefixes-et-les-suffixes-n-ont-pas-leur-place-dans-les-noms-d-interface] -- [Conseils d'utilisation de Composer |composer] -- [Conseils sur les éditeurs et les outils |editors-and-tools] +Général +------- +- [Comment charger un fichier de configuration |bootstrap:] +- [Comment écrire des micro-sites |microsites] +- [Pourquoi Nette utilise la notation PascalCase pour les constantes ? |https://blog.nette.org/fr/for-less-screaming-in-the-code] +- [Pourquoi Nette n'utilise pas le suffixe Interface ? |https://blog.nette.org/fr/prefixes-and-suffixes-do-not-belong-in-interface-names] +- [Composer : conseils d'utilisation |composer] +- [Conseils sur les éditeurs & outils |editors-and-tools] +- [Introduction à la programmation orientée objet |nette:introduction-to-object-oriented-programming] </div> <div> -Exemple de solution -------------------- -- [Exemples de Nette |https://github.com/nette-examples] +Exemples de solutions +--------------------- +- [Exemples Nette |https://github.com/nette-examples] - [Doctrine & Nette |https://contributte.org/nettrine/] -- [Exemples de contributions |https://contributte.org/examples.html] -- [Site web de Doctrine ORM |https://github.com/MinecordNetwork/Website] +- [Exemples Contributte |https://contributte.org/examples.html] +- [Site Web Doctrine ORM |https://github.com/MinecordNetwork/Website] - [Démarrage rapide |quickstart:] </div> @@ -59,10 +63,7 @@ Exemple de solution Vidéos ------ -Vous pouvez trouver des centaines d'enregistrements de Posobota et de vidéos sur Nette sous un même toit sur la "Nette Framework YouTube Channel":https://www.youtube.com/user/NetteFramework. +Des centaines d'enregistrements des Derniers Samedis et de vidéos sur Nette peuvent être trouvés sous un même toit sur [la chaîne Youtube de Nette Framework |https://www.youtube.com/user/NetteFramework]. </div> </div> - -{{sitename: Meilleures pratiques}} -{{leftbar: www:@menu-common}} diff --git a/best-practices/fr/@meta.texy b/best-practices/fr/@meta.texy new file mode 100644 index 0000000000..ae1deef2d5 --- /dev/null +++ b/best-practices/fr/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Tutoriels et bonnes pratiques}} +{{leftbar: www:@menu-common}} diff --git a/best-practices/fr/attribute-requires.texy b/best-practices/fr/attribute-requires.texy new file mode 100644 index 0000000000..afeb4699c6 --- /dev/null +++ b/best-practices/fr/attribute-requires.texy @@ -0,0 +1,177 @@ +Comment utiliser l'attribut `#[Requires]` +***************************************** + +.[perex] +Lorsque vous écrivez une application web, vous rencontrez souvent le besoin de restreindre l'accès à certaines parties de votre application. Peut-être souhaitez-vous que certaines requêtes ne puissent envoyer des données qu'à l'aide d'un formulaire (c'est-à-dire avec la méthode POST), ou qu'elles ne soient accessibles que pour les appels AJAX. Dans Nette Framework 3.2, un nouvel outil est apparu qui vous permet de définir de telles restrictions de manière très élégante et claire : l'attribut `#[Requires]`. + +Un attribut est une marque spéciale en PHP que vous ajoutez avant la définition d'une classe ou d'une méthode. Comme il s'agit en fait d'une classe, pour que les exemples suivants fonctionnent, il est nécessaire d'inclure la clause use : + +```php +use Nette\Application\Attributes\Requires; +``` + +Vous pouvez utiliser l'attribut `#[Requires]` sur la classe du presenter elle-même et également sur ces méthodes : + +- `action<Action>()` +- `render<View>()` +- `handle<Signal>()` +- `createComponent<Name>()` + +Les deux dernières méthodes concernent également les composants, vous pouvez donc également utiliser l'attribut avec eux. + +Si les conditions spécifiées par l'attribut ne sont pas remplies, une erreur HTTP 4xx est levée. + + +Méthodes HTTP +------------- + +Vous pouvez spécifier quelles méthodes HTTP (comme GET, POST, etc.) sont autorisées pour l'accès. Par exemple, si vous souhaitez autoriser l'accès uniquement en soumettant un formulaire, définissez : + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +Pourquoi devriez-vous utiliser POST au lieu de GET pour les actions modifiant l'état et comment faire ? [Lisez le guide |post-links]. + +Vous pouvez spécifier une méthode ou un tableau de méthodes. Un cas spécial est la valeur `'*'`, qui autorise toutes les méthodes, ce que les presenters par défaut [n'autorisent pas pour des raisons de sécurité |application:presenters#Vérification de la méthode HTTP]. + + +Appel AJAX +---------- + +Si vous souhaitez que le presenter ou la méthode ne soit disponible que pour les requêtes AJAX, utilisez : + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Même origine +------------ + +Pour augmenter la sécurité, vous pouvez exiger que la requête soit effectuée depuis le même domaine. Cela empêche la [vulnérabilité CSRF |nette:vulnerability-protection#Cross-Site Request Forgery CSRF] : + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Pour les méthodes `handle<Signal>()`, l'accès depuis le même domaine est requis automatiquement. Donc, si au contraire vous souhaitez autoriser l'accès depuis n'importe quel domaine, spécifiez : + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Accès via forward +----------------- + +Parfois, il est utile de restreindre l'accès à un presenter de manière à ce qu'il ne soit disponible qu'indirectement, par exemple en utilisant la méthode `forward()` ou `switch()` depuis un autre presenter. C'est ainsi que l'on protège par exemple les error-presenters, afin qu'ils ne puissent pas être appelés depuis une URL : + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +En pratique, il est souvent nécessaire de marquer certaines vues auxquelles on ne peut accéder qu'en fonction de la logique du presenter. Donc encore une fois, pour qu'elles ne puissent pas être ouvertes directement : + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = $this->facade->getProduct($id); + if (!$product) { + $this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Actions spécifiques +------------------- + +Vous pouvez également restreindre l'accès à un certain code, comme la création d'un composant, pour qu'il ne soit disponible que pour des actions spécifiques dans le presenter : + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +Dans le cas d'une seule action, il n'est pas nécessaire d'écrire un tableau : `#[Requires(actions: 'default')]` + + +Attributs personnalisés +----------------------- + +Si vous souhaitez utiliser l'attribut `#[Requires]` de manière répétée avec les mêmes paramètres, vous pouvez créer votre propre attribut qui héritera de `#[Requires]` et le configurera selon vos besoins. + +Par exemple, `#[SingleAction]` permettra l'accès uniquement via l'action `default` : + +```php +#[\Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Ou `#[RestMethods]` permettra l'accès via toutes les méthodes HTTP utilisées pour les API REST : + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Conclusion +---------- + +L'attribut `#[Requires]` vous offre une grande flexibilité et un contrôle sur la manière dont vos pages web sont accessibles. À l'aide de règles simples mais puissantes, vous pouvez augmenter la sécurité et le bon fonctionnement de votre application. Comme vous pouvez le voir, l'utilisation des attributs dans Nette peut non seulement faciliter votre travail, mais aussi le sécuriser. diff --git a/best-practices/fr/composer.texy b/best-practices/fr/composer.texy index 485cd78e67..b8f9a3e43b 100644 --- a/best-practices/fr/composer.texy +++ b/best-practices/fr/composer.texy @@ -1,44 +1,44 @@ -Conseils d'utilisation du compositeur -************************************* +Composer : Conseils d'utilisation +********************************* <div class=perex> -Composer est un outil de gestion des dépendances en PHP. Il vous permet de déclarer les bibliothèques dont dépend votre projet et il les installera et les mettra à jour pour vous. Nous allons apprendre : +Composer est un outil de gestion des dépendances en PHP. Il nous permet de lister les bibliothèques dont notre projet dépend, et il les installera et les mettra à jour pour nous. Nous allons montrer : - comment installer Composer -- l'utiliser dans un projet nouveau ou existant +- son utilisation dans un projet nouveau ou existant </div> -Installation .[#toc-installation] -================================= +Installation +============ -Composer est un fichier exécutable `.phar` que vous téléchargez et installez comme suit. +Composer est un fichier `.phar` exécutable que vous téléchargez et installez de la manière suivante : -Windows .[#toc-windows] ------------------------ +Windows +------- -Utilisez le programme d'installation officiel [Composer-Setup.exe |https://getcomposer.org/Composer-Setup.exe]. +Utilisez l'installeur officiel [Composer-Setup.exe |https://getcomposer.org/Composer-Setup.exe]. -Linux, macOS .[#toc-linux-macos] --------------------------------- +Linux, macOS +------------ -Tout ce dont vous avez besoin, c'est de 4 commandes, que vous pouvez copier à partir de [cette page |https://getcomposer.org/download/]. +Il suffit de 4 commandes que vous copiez depuis [cette page |https://getcomposer.org/download/]. -De plus, en copiant dans le dossier qui se trouve dans le système `PATH`, Composer devient globalement accessible : +Ensuite, en le plaçant dans un dossier qui se trouve dans le `PATH` système, Composer devient accessible globalement : ```shell -$ mv ./composer.phar ~/bin/composer # or /usr/local/bin/composer +$ mv ./composer.phar ~/bin/composer # ou /usr/local/bin/composer ``` -Utilisation dans le projet .[#toc-use-in-project] -================================================= +Utilisation dans un projet +========================== -Pour commencer à utiliser Composer dans votre projet, tout ce dont vous avez besoin est un fichier `composer.json`. Ce fichier décrit les dépendances de votre projet et peut également contenir d'autres métadonnées. Le fichier `composer.json` le plus simple peut ressembler à ceci : +Pour pouvoir commencer à utiliser Composer dans votre projet, vous n'avez besoin que du fichier `composer.json`. Celui-ci décrit les dépendances de notre projet et peut également contenir d'autres métadonnées. Un `composer.json` de base peut donc ressembler à ceci : ```js { @@ -48,17 +48,17 @@ Pour commencer à utiliser Composer dans votre projet, tout ce dont vous avez be } ``` -Nous disons ici que notre application (ou bibliothèque) dépend du paquet `nette/database` (le nom du paquet est composé d'un nom de fournisseur et du nom du projet) et qu'elle veut la version qui correspond à la contrainte de version `^3.0`. +Nous indiquons ici que notre application (ou bibliothèque) nécessite le paquet `nette/database` (le nom du paquet se compose du nom de l'organisation et du nom du projet) et veut une version qui correspond à la contrainte `^3.0` (c'est-à-dire la dernière version 3). -Ainsi, lorsque nous avons le fichier `composer.json` à la racine du projet et que nous exécutons : +Nous avons donc à la racine du projet le fichier `composer.json` et nous lançons l'installation : ```shell composer update ``` -Composer téléchargera la base de données Nette dans le répertoire `vendor`. Il crée également un fichier `composer.lock`, qui contient des informations sur les versions exactes des bibliothèques qu'il a installées. +Composer téléchargera Nette Database dans le dossier `vendor/`. Il créera également le fichier `composer.lock`, qui contient des informations sur les versions exactes des bibliothèques qu'il a installées. -Composer génère un fichier `vendor/autoload.php`. Vous pouvez simplement inclure ce fichier et commencer à utiliser les classes que ces bibliothèques fournissent sans aucun travail supplémentaire : +Composer génère le fichier `vendor/autoload.php`, que nous pouvons simplement inclure et commencer à utiliser les bibliothèques sans aucun travail supplémentaire : ```php require __DIR__ . '/vendor/autoload.php'; @@ -67,52 +67,52 @@ $db = new Nette\Database\Connection('sqlite::memory:'); ``` -Mise à jour des paquets vers les dernières versions .[#toc-update-packages-to-the-latest-versions] -================================================================================================== +Mise à jour des paquets vers les dernières versions +=================================================== -Pour mettre à jour tous les paquets utilisés à la dernière version selon les contraintes de version définies dans `composer.json`, utilisez la commande `composer update`. Par exemple, pour la dépendance `"nette/database": "^3.0"`, elle installera la dernière version 3.x.x, mais pas la version 4. +La mise à jour des bibliothèques utilisées vers les dernières versions selon les contraintes définies dans `composer.json` est gérée par la commande `composer update`. Par exemple, pour la dépendance `"nette/database": "^3.0"`, il installera la dernière version 3.x.x, mais pas la version 4. -Pour mettre à jour les contraintes de version dans le fichier `composer.json`, par exemple `"nette/database": "^4.1"`, afin de permettre l'installation de la dernière version, utilisez la commande `composer require nette/database`. +Pour mettre à jour les contraintes dans le fichier `composer.json`, par exemple vers `"nette/database": "^4.1"`, afin de pouvoir installer la dernière version, utilisez la commande `composer require nette/database`. -Pour mettre à jour tous les paquets Nette utilisés, il serait nécessaire de les lister tous sur la ligne de commande, par ex : +Pour mettre à jour tous les paquets Nette utilisés, il faudrait tous les lister dans la ligne de commande, par ex. : ```shell composer require nette/application nette/forms latte/latte tracy/tracy ... ``` -Ce qui n'est pas pratique. Par conséquent, utilisez un simple script "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff qui le fera pour vous : +Ce qui n'est pas pratique. Utilisez donc le script simple "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff, qui le fera pour vous : ```shell php composer-frontline.php ``` -Création d'un nouveau projet .[#toc-creating-new-project] -========================================================= +Création d'un nouveau projet +============================ -Un nouveau projet Nette peut être créé en exécutant une simple commande : +Vous créez un nouveau projet Nette à l'aide d'une seule commande : ```shell -composer create-project nette/web-project name-of-the-project +composer create-project nette/web-project nom-du-projet ``` -A la place de `name-of-the-project`, vous devez fournir le nom du répertoire de votre projet et exécuter la commande. Composer ira chercher le dépôt `nette/web-project` de GitHub, qui contient déjà le fichier `composer.json`, et juste après, il installera le Framework Nette lui-même. La seule chose qui reste à faire est de [vérifier les droits d'écriture |nette:troubleshooting#setting-directory-permissions] sur les répertoires `temp/` et `log/` et vous êtes prêt à partir. +Comme `nom-du-projet`, insérez le nom du répertoire pour votre projet et confirmez. Composer téléchargera le dépôt `nette/web-project` depuis GitHub, qui contient déjà le fichier `composer.json`, puis Nette Framework. Il devrait suffire de [définir les permissions |nette:troubleshooting#Configuration des permissions de répertoire] d'écriture sur les dossiers `temp/` et `log/` et le projet devrait prendre vie. -Si vous connaissez la version de PHP sur laquelle le projet sera hébergé, assurez-vous de la [configurer |#PHP Version]. +Si vous savez sur quelle version de PHP le projet sera hébergé, n'oubliez pas de [la définir |#Version de PHP]. -Version PHP .[#toc-php-version] -=============================== +Version de PHP +============== -Composer installe toujours les versions des paquets compatibles avec la version de PHP que vous utilisez actuellement (ou plutôt, la version de PHP utilisée en ligne de commande lorsque vous lancez Composer). Ce qui n'est probablement pas la même version que celle utilisée par votre hébergeur. C'est pourquoi il est très important d'ajouter l'information sur la version de PHP de votre hébergement dans votre fichier `composer.json`. Ensuite, seules les versions des paquets compatibles avec l'hébergeur seront installées. +Composer installe toujours les versions de paquets compatibles avec la version de PHP que vous utilisez actuellement (plus précisément, avec la version de PHP utilisée dans la ligne de commande lors de l'exécution de Composer). Ce qui n'est probablement pas la même version que celle utilisée par votre hébergement. C'est pourquoi il est très important d'ajouter au fichier `composer.json` l'information sur la version de PHP sur l'hébergement. Ensuite, seules les versions de paquets compatibles avec l'hébergement seront installées. -Par exemple, pour configurer le projet pour qu'il fonctionne avec PHP 8.2.3, utilisez la commande : +Le fait que le projet fonctionnera par exemple sur PHP 8.2.3 est défini par la commande : ```shell composer config platform.php 8.2.3 ``` -C'est ainsi que la version est écrite dans le fichier `composer.json`: +La version est ainsi écrite dans le fichier `composer.json` : ```js { @@ -124,8 +124,7 @@ C'est ainsi que la version est écrite dans le fichier `composer.json`: } ``` -Cependant, le numéro de version de PHP est également indiqué ailleurs dans le fichier, dans la section `require`. Alors que le premier numéro spécifie la version pour laquelle les paquets seront installés, le second numéro indique la version pour laquelle l'application elle-même est écrite. -(Bien sûr, il n'y a pas de sens à ce que ces versions soient différentes, donc la double entrée est une redondance). Vous définissez cette version à l'aide de la commande : +Cependant, le numéro de version de PHP est indiqué à un autre endroit du fichier, dans la section `require`. Alors que le premier numéro détermine pour quelle version les paquets seront installés, le second numéro indique pour quelle version l'application elle-même est écrite. Et selon lui, par exemple, PhpStorm définit le *niveau de langage PHP*. (Bien sûr, il n'est pas logique que ces versions diffèrent, donc la double écriture est une imperfection.) Vous définissez cette version avec la commande : ```shell composer require php 8.2.3 --no-update @@ -135,73 +134,79 @@ Ou directement dans le fichier `composer.json` : ```js { - "require" : { - "php" : "8.2.3" + "require": { + "php": "8.2.3" } } ``` -Faux rapports .[#toc-false-reports] -=================================== +Ignorer la version de PHP +========================= + +Les paquets indiquent généralement à la fois la version minimale de PHP avec laquelle ils sont compatibles et la version maximale avec laquelle ils sont testés. Si vous prévoyez d'utiliser une version de PHP encore plus récente, par exemple à des fins de test, Composer refusera d'installer un tel paquet. La solution est l'option `--ignore-platform-req=php+`, qui fait que Composer ignorera les limites supérieures de la version de PHP requise. + -Lors de la mise à niveau de paquets ou du changement de numéro de version, des conflits se produisent. Un paquet a des exigences qui entrent en conflit avec un autre, et ainsi de suite. Cependant, Composer imprime parfois un faux message. Il signale un conflit qui n'existe pas réellement. Dans ce cas, il est utile de supprimer le fichier `composer.lock` et de réessayer. +Faux rapports +============= -Si le message d'erreur persiste, c'est qu'il est sérieux et que vous devez y lire ce qu'il faut modifier et comment. +Lors de la mise à niveau des paquets ou des changements de numéros de version, il arrive qu'un conflit se produise. Un paquet a des exigences qui sont en conflit avec un autre, etc. Mais Composer affiche parfois un faux rapport. Il signale un conflit qui n'existe pas réellement. Dans ce cas, il est utile de supprimer le fichier `composer.lock` et de réessayer. +Si le message d'erreur persiste, alors il est sérieux et il faut en déduire quoi et comment modifier. -Packagist.org - Dépôt global .[#toc-packagist-org-global-repository] -==================================================================== -[Packagist |https://packagist.org] est le dépôt de paquets principal, dans lequel Composer tente de rechercher des paquets, sauf indication contraire. Vous pouvez également y publier vos propres paquets. +Packagist.org - dépôt central +============================= +[Packagist |https://packagist.org] est le dépôt principal dans lequel Composer essaie de rechercher des paquets, sauf indication contraire. Nous pouvons y publier nos propres paquets. -Et si nous ne voulons pas du dépôt central ? .[#toc-what-if-we-don-t-want-the-central-repository] -------------------------------------------------------------------------------------------------- -Si nous avons des applications ou des bibliothèques internes à notre entreprise, qui ne peuvent pas être hébergées publiquement sur Packagist, nous pouvons créer nos propres dépôts pour ces projets. +Et si nous ne voulons pas utiliser le dépôt central ? +----------------------------------------------------- -Pour en savoir plus sur les dépôts, consultez la [documentation officielle |https://getcomposer.org/doc/05-repositories.md#repositories]. +Si nous avons des applications internes à l'entreprise que nous ne pouvons tout simplement pas héberger publiquement, nous créons pour elles un dépôt d'entreprise. +Plus d'informations sur le sujet des dépôts [dans la documentation officielle |https://getcomposer.org/doc/05-repositories.md#repositories]. -Autoloading .[#toc-autoloading] -=============================== -Une caractéristique clé de Composer est qu'il fournit un chargement automatique pour toutes les classes qu'il installe, ce que vous commencez par inclure un fichier `vendor/autoload.php`. +Autoloading +=========== -Toutefois, il est également possible d'utiliser Composer pour charger d'autres classes en dehors du dossier `vendor`. La première option consiste à laisser Composer analyser les dossiers et sous-dossiers définis, trouver toutes les classes et les inclure dans l'autochargeur. Pour ce faire, définissez `autoload > classmap` dans `composer.json`: +Une caractéristique essentielle de Composer est qu'il fournit l'autoloading pour toutes les classes qu'il a installées, que vous démarrez en incluant le fichier `vendor/autoload.php`. + +Cependant, il est possible d'utiliser Composer également pour charger d'autres classes en dehors du dossier `vendor`. La première option est de laisser Composer parcourir les dossiers et sous-dossiers définis, trouver toutes les classes et les inclure dans l'autoloader. Vous obtenez cela en définissant `autoload > classmap` dans `composer.json` : ```js { "autoload": { "classmap": [ - "src/", # includes the src/ folder and its subfolders + "src/", # inclut le dossier src/ et ses sous-dossiers ] } } ``` -Par la suite, il est nécessaire d'exécuter la commande `composer dumpautoload` à chaque modification et de laisser les tables d'autoloadage se régénérer. Ceci est extrêmement gênant, et il est de loin préférable de confier cette tâche à [RobotLoader |robot-loader:], qui effectue la même activité automatiquement en arrière-plan et beaucoup plus rapidement. +Ensuite, il est nécessaire, à chaque modification, d'exécuter la commande `composer dumpautoload` et de laisser les tables d'autoloading se régénérer. C'est extrêmement inconfortable et il est bien préférable de confier cette tâche à [RobotLoader|robot-loader:], qui effectue la même activité automatiquement en arrière-plan et beaucoup plus rapidement. -La deuxième option consiste à suivre le [système PSR-4 |https://www.php-fig.org/psr/psr-4/]. Pour faire simple, il s'agit d'un système où les espaces de noms et les noms de classes correspondent à la structure des répertoires et aux noms de fichiers, c'est-à-dire que `App\Router\RouterFactory` est situé dans le fichier `/path/to/App/Router/RouterFactory.php`. Exemple de configuration : +La deuxième option est de respecter [PSR-4|https://www.php-fig.org/psr/psr-4/]. En termes simples, c'est un système où les espaces de noms et les noms de classes correspondent à la structure des répertoires et aux noms de fichiers, donc par ex. `App\Core\RouterFactory` sera dans le fichier `/chemin/vers/App/Core/RouterFactory.php`. Exemple de configuration : ```js { "autoload": { "psr-4": { - "App\\": "app/" # the App\ namespace is in the app/ directory + "App\\": "app/" # l'espace de noms App\ est dans le répertoire app/ } } } ``` -Voir la [documentation de Composer |https://getcomposer.org/doc/04-schema.md#psr-4] pour savoir exactement comment configurer ce comportement. +Comment configurer précisément le comportement est expliqué dans la [documentation de Composer|https://getcomposer.org/doc/04-schema.md#psr-4]. -Test des nouvelles versions .[#toc-testing-new-versions] -======================================================== +Test de nouvelles versions +========================== -Vous voulez tester une nouvelle version de développement d'un paquet. Comment faire ? Tout d'abord, ajoutez cette paire d'options au fichier `composer.json`, qui vous permettra d'installer des versions de développement de paquets, mais ne le fera que s'il n'existe aucune combinaison de versions stables répondant aux exigences : +Vous voulez tester une nouvelle version de développement d'un paquet. Comment faire ? Tout d'abord, ajoutez cette paire d'options au fichier `composer.json`, qui permettra d'installer les versions de développement des paquets, mais n'y recourra que s'il n'existe aucune combinaison de versions stables qui satisferait aux exigences : ```js { @@ -210,9 +215,9 @@ Vous voulez tester une nouvelle version de développement d'un paquet. Comment f } ``` -Nous vous recommandons également de supprimer le fichier `composer.lock`, car il arrive que Composer refuse de manière incompréhensible de procéder à l'installation et cela résoudra le problème. +Ensuite, nous recommandons de supprimer le fichier `composer.lock`, parfois Composer refuse inexplicablement l'installation et cela résout le problème. -Disons que le paquet est `nette/utils` et que la nouvelle version est 4.0. Vous l'installez avec la commande : +Supposons qu'il s'agisse du paquet `nette/utils` et que la nouvelle version porte le numéro 4.0. Vous l'installez avec la commande : ```shell composer require nette/utils:4.0.x-dev @@ -224,20 +229,19 @@ Ou vous pouvez installer une version spécifique, par exemple 4.0.0-RC2 : composer require nette/utils:4.0.0-RC2 ``` -Si un autre paquet dépend de la bibliothèque et est verrouillé sur une version plus ancienne (par exemple `^3.1`), il est idéal de mettre à jour le paquet pour qu'il fonctionne avec la nouvelle version. -Toutefois, si vous souhaitez simplement contourner la limitation et forcer Composer à installer la version de développement en prétendant qu'il s'agit d'une version plus ancienne (par exemple, 3.1.6), vous pouvez utiliser le mot-clé `as`: +Mais si un autre paquet dépend de la bibliothèque et est verrouillé sur une version plus ancienne (par ex. `^3.1`), alors il est idéal de mettre à jour le paquet pour qu'il fonctionne avec la nouvelle version. Cependant, si vous voulez simplement contourner la restriction et forcer Composer à installer la version de développement et prétendre qu'il s'agit d'une version plus ancienne (par ex. 3.1.6), vous pouvez utiliser le mot-clé `as` : ```shell composer require nette/utils "4.0.x-dev as 3.1.6" ``` -Appeler des commandes .[#toc-calling-commands] -============================================== +Appel de commandes +================== -Vous pouvez appeler vos propres commandes et scripts personnalisés via Composer comme s'il s'agissait de commandes Composer natives. Les scripts situés dans le dossier `vendor/bin` n'ont pas besoin de spécifier ce dossier. +Via Composer, il est possible d'appeler des commandes et des scripts personnalisés prédéfinis, comme s'il s'agissait de commandes natives de Composer. Pour les scripts qui se trouvent dans le dossier `vendor/bin`, il n'est pas nécessaire de spécifier ce dossier. -À titre d'exemple, nous définissons un script dans le dossier `composer.json` qui utilise [Nette Tester |tester:] pour exécuter des tests : +Comme exemple, définissons dans le fichier `composer.json` un script qui utilise [Nette Tester|tester:] pour lancer les tests : ```js { @@ -247,19 +251,19 @@ Vous pouvez appeler vos propres commandes et scripts personnalisés via Composer } ``` -Nous exécutons ensuite les tests avec `composer tester`. Nous pouvons appeler la commande même si nous ne sommes pas dans le dossier racine du projet, mais dans un sous-répertoire. +Nous lançons ensuite les tests à l'aide de `composer tester`. Nous pouvons appeler la commande même si nous ne sommes pas dans le dossier racine du projet, mais dans l'un des sous-répertoires. -Envoyer Merci .[#toc-send-thanks] -================================= +Envoyez un merci +================ -Nous allons vous montrer une astuce qui va rendre les auteurs de logiciels libres heureux. Vous pouvez facilement donner une étoile sur GitHub aux bibliothèques que votre projet utilise. Il suffit d'installer la bibliothèque `symfony/thanks`: +Nous allons vous montrer une astuce qui fera plaisir aux auteurs open source. Vous pouvez facilement donner une étoile sur GitHub aux bibliothèques que votre projet utilise. Il suffit d'installer la bibliothèque `symfony/thanks` : ```shell composer global require symfony/thanks ``` -Et puis exécutez : +Et ensuite exécuter : ```shell composer thanks @@ -268,13 +272,11 @@ composer thanks Essayez ! -Configuration .[#toc-configuration] -=================================== +Configuration +============= -Composer est étroitement intégré à l'outil de contrôle de version [Git |https://git-scm.com]. Si vous n'utilisez pas Git, il est nécessaire de l'indiquer à Composer : +Composer est étroitement lié à l'outil de versionnement [Git |https://git-scm.com]. Si vous ne l'avez pas installé, il faut dire à Composer de ne pas l'utiliser : ```shell composer -g config preferred-install dist ``` - -{{sitename: Meilleures pratiques}} diff --git a/best-practices/fr/creating-editing-form.texy b/best-practices/fr/creating-editing-form.texy index b381781e4b..b05616ac5e 100644 --- a/best-practices/fr/creating-editing-form.texy +++ b/best-practices/fr/creating-editing-form.texy @@ -1,16 +1,16 @@ -Formulaire de création et de modification d'un enregistrement -************************************************************* +Formulaire pour la création et l'édition d'enregistrements +********************************************************** .[perex] -Comment mettre en œuvre correctement l'ajout et la modification d'un enregistrement dans Nette, en utilisant le même formulaire pour les deux ? +Comment implémenter correctement l'ajout et l'édition d'enregistrements dans Nette, en utilisant le même formulaire pour les deux ? -Dans de nombreux cas, les formulaires d'ajout et de modification d'un enregistrement sont identiques et ne diffèrent que par le libellé du bouton. Nous allons montrer des exemples de présentateurs simples où nous utilisons le formulaire d'abord pour ajouter un enregistrement, puis pour le modifier, et enfin combiner les deux solutions. +Dans de nombreux cas, les formulaires pour ajouter et éditer des enregistrements sont identiques, ne différant peut-être que par l'étiquette du bouton. Nous montrerons des exemples de presenters simples où nous utiliserons d'abord le formulaire pour ajouter un enregistrement, puis pour l'éditer, et enfin combinerons les deux solutions. -Ajout d'un enregistrement .[#toc-adding-a-record] -------------------------------------------------- +Ajout d'un enregistrement +------------------------- -Un exemple de présentateur utilisé pour ajouter un enregistrement. Nous laisserons le travail réel sur la base de données à la classe `Facade`, dont le code n'est pas pertinent pour cet exemple. +Exemple de presenter servant à ajouter un enregistrement. Nous laisserons le travail réel avec la base de données à la classe `Facade`, dont le code n'est pas essentiel pour la démonstration. ```php @@ -27,7 +27,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $form = new Form; - // ... ajouter des champs de formulaire ... + // ... ajouter les champs du formulaire ... $form->onSuccess[] = [$this, 'recordFormSucceeded']; return $form; @@ -35,8 +35,8 @@ class RecordPresenter extends Nette\Application\UI\Presenter public function recordFormSucceeded(Form $form, array $data): void { - $this->facade->add($data); // ajouter un enregistrement à la base de données - $this->flashMessage('Successfully added'); + $this->facade->add($data); // ajout de l'enregistrement à la base de données + $this->flashMessage('Ajouté avec succès'); $this->redirect('...'); } @@ -48,10 +48,10 @@ class RecordPresenter extends Nette\Application\UI\Presenter ``` -Modification d'un enregistrement .[#toc-editing-a-record] ---------------------------------------------------------- +Édition d'un enregistrement +--------------------------- -Voyons maintenant à quoi ressemble un présentateur utilisé pour modifier un enregistrement : +Maintenant, montrons à quoi ressemblerait un presenter servant à éditer un enregistrement : ```php @@ -70,8 +70,8 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $record = $this->facade->get($id); if ( - !$record // vérifier l'existence de l'enregistrement - || !$this->facade->isEditAllowed(/*...*/) // vérifier les permissions + !$record // vérification de l'existence de l'enregistrement + || !$this->facade->isEditAllowed(/*...*/) // contrôle des permissions ) { $this->error(); // erreur 404 } @@ -81,32 +81,32 @@ class RecordPresenter extends Nette\Application\UI\Presenter protected function createComponentRecordForm(): Form { - // vérifiez que l'action est "modifier". + // vérifions que l'action est 'edit' if ($this->getAction() !== 'edit') { $this->error(); } $form = new Form; - // ... ajouter des champs de formulaire ... + // ... ajouter les champs du formulaire ... - $form->setDefaults($this->record); // définir les valeurs par défaut + $form->setDefaults($this->record); // définition des valeurs par défaut $form->onSuccess[] = [$this, 'recordFormSucceeded']; return $form; } public function recordFormSucceeded(Form $form, array $data): void { - $this->facade->update($this->record->id, $data); // mettre à jour l'enregistrement - $this->flashMessage('Successfully updated'); + $this->facade->update($this->record->id, $data); // mise à jour de l'enregistrement + $this->flashMessage('Mis à jour avec succès'); $this->redirect('...'); } } ``` -Dans la méthode *action*, qui est invoquée dès le début du [cycle de vie du présentateur |application:presenters#Life Cycle of Presenter], nous vérifions l'existence de l'enregistrement et l'autorisation de l'utilisateur à le modifier. +Dans la méthode *action*, qui s'exécute au tout début du [cycle de vie du presenter |application:presenters#Cycle de vie du presenter], nous vérifions l'existence de l'enregistrement et les permissions de l'utilisateur pour l'éditer. -Nous stockons l'enregistrement dans la propriété `$record` afin qu'il soit disponible dans la méthode `createComponentRecordForm()` pour définir les valeurs par défaut, et `recordFormSucceeded()` pour l'ID. Une autre solution consisterait à définir les valeurs par défaut directement dans `actionEdit()` et la valeur de l'ID, qui fait partie de l'URL, est récupérée à l'aide de `getParameter('id')`: +Nous sauvegardons l'enregistrement dans la propriété `$record` pour l'avoir disponible dans la méthode `createComponentRecordForm()` pour définir les valeurs par défaut, et dans `recordFormSucceeded()` pour l'ID. Une solution alternative serait de définir les valeurs par défaut directement dans `actionEdit()` et d'obtenir la valeur de l'ID, qui fait partie de l'URL, en utilisant `getParameter('id')` : ```php @@ -114,12 +114,12 @@ Nous stockons l'enregistrement dans la propriété `$record` afin qu'il soit dis { $record = $this->facade->get($id); if ( - // vérifier l'existence et contrôler les permissions + // vérification de l'existence et contrôle des permissions ) { $this->error(); } - // définir les valeurs par défaut des formulaires + // définition des valeurs par défaut du formulaire $this->getComponent('recordForm') ->setDefaults($record); } @@ -133,13 +133,13 @@ Nous stockons l'enregistrement dans la propriété `$record` afin qu'il soit dis } ``` -Cependant, et cela devrait être **le plus important à retenir de tout ce code**, nous devons nous assurer que l'action est bien `edit` lorsque nous créons le formulaire. Car sinon, la validation dans la méthode `actionEdit()` n'aurait pas lieu du tout ! +Cependant, et cela devrait être **la leçon la plus importante de tout le code**, nous devons nous assurer lors de la création du formulaire que l'action est bien `edit`. Sinon, la vérification dans la méthode `actionEdit()` n'aurait pas lieu du tout ! -Le même formulaire pour l'ajout et la modification .[#toc-same-form-for-adding-and-editing] -------------------------------------------------------------------------------------------- +Même formulaire pour l'ajout et l'édition +----------------------------------------- -Et maintenant, nous allons combiner les deux présentateurs en un seul. Nous pouvons soit distinguer quelle action est impliquée dans la méthode `createComponentRecordForm()` et configurer le formulaire en conséquence, soit nous en remettre directement aux méthodes d'action et nous débarrasser de la condition : +Et maintenant, combinons les deux presenters en un seul. Soit nous pourrions distinguer dans la méthode `createComponentRecordForm()` de quelle action il s'agit et configurer le formulaire en conséquence, soit nous pouvons laisser cela directement aux méthodes d'action et nous débarrasser de la condition : ```php @@ -160,47 +160,46 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $record = $this->facade->get($id); if ( - !$record // vérifier l'existence de l'enregistrement - || !$this->facade->isEditAllowed(/*...*/) // vérifier les permissions + !$record // vérification de l'existence de l'enregistrement + || !$this->facade->isEditAllowed(/*...*/) // contrôle des permissions ) { $this->error(); // erreur 404 } $form = $this->getComponent('recordForm'); - $form->setDefaults($record); // définir les valeurs par défaut + $form->setDefaults($record); // définition des valeurs par défaut $form->onSuccess[] = [$this, 'editingFormSucceeded']; } protected function createComponentRecordForm(): Form { - // vérifiez que l'action est "ajouter" ou "modifier". + // vérifions que l'action est 'add' ou 'edit' if (!in_array($this->getAction(), ['add', 'edit'])) { $this->error(); } $form = new Form; - // ... ajouter des champs de formulaire ... + // ... ajouter les champs du formulaire ... return $form; } public function addingFormSucceeded(Form $form, array $data): void { - $this->facade->add($data); // ajouter un enregistrement à la base de données - $this->flashMessage('Successfully added'); + $this->facade->add($data); // ajout de l'enregistrement à la base de données + $this->flashMessage('Ajouté avec succès'); $this->redirect('...'); } public function editingFormSucceeded(Form $form, array $data): void { $id = (int) $this->getParameter('id'); - $this->facade->update($id, $data); // mettre à jour un enregistrement - $this->flashMessage('Successfully updated'); + $this->facade->update($id, $data); // mise à jour de l'enregistrement + $this->flashMessage('Mis à jour avec succès'); $this->redirect('...'); } } ``` {{priority: -1}} -{{sitename: Meilleures pratiques}} diff --git a/best-practices/fr/dynamic-snippets.texy b/best-practices/fr/dynamic-snippets.texy index 5bc4743f67..d285c2c138 100644 --- a/best-practices/fr/dynamic-snippets.texy +++ b/best-practices/fr/dynamic-snippets.texy @@ -1,7 +1,7 @@ -Snippets dynamiques +Snippets Dynamiques ******************* -Très souvent, lors du développement d'une application, il est nécessaire d'effectuer des opérations AJAX, par exemple dans les lignes individuelles d'un tableau ou dans les éléments d'une liste. À titre d'exemple, nous pouvons choisir de dresser une liste d'articles, permettant à l'utilisateur connecté de sélectionner une note "j'aime/je n'aime pas" pour chacun d'entre eux. Le code du présentateur et du modèle correspondant sans AJAX ressemblera à quelque chose comme ceci (je liste les extraits les plus importants, le code suppose l'existence d'un service pour marquer les évaluations et obtenir une collection d'articles - l'implémentation spécifique n'est pas importante pour les besoins de ce tutoriel) : +Assez souvent, lors du développement d'applications, le besoin se fait sentir d'effectuer des opérations AJAX, par exemple sur des lignes individuelles d'une table ou des éléments d'une liste. À titre d'exemple, nous pouvons choisir l'affichage d'articles, où pour chacun d'eux, nous permettons à l'utilisateur connecté de choisir une évaluation "j'aime/je n'aime pas". Le code du presenter et du template correspondant sans AJAX ressemblera approximativement à ceci (je présente les extraits les plus importants, le code suppose l'existence d'un service pour marquer les évaluations et obtenir la collection d'articles - l'implémentation spécifique n'est pas importante aux fins de ce tutoriel) : ```php public function handleLike(int $articleId): void @@ -24,26 +24,26 @@ Template : <h2>{$article->title}</h2> <div class="content">{$article->content}</div> {if !$article->liked} - <a n:href="like! $article->id" class=ajax>I like it</a> + <a n:href="like! $article->id" class=ajax>J'aime</a> {else} - <a n:href="unlike! $article->id" class=ajax>I don't like it anymore</a> + <a n:href="unlike! $article->id" class=ajax>Je n'aime plus</a> {/if} </article> ``` -Ajaxisation .[#toc-ajaxization] -=============================== +Ajaxification +============= -Introduisons maintenant AJAX dans cette application simple. La modification de l'évaluation d'un article n'est pas assez importante pour nécessiter une requête HTTP avec redirection, donc l'idéal serait de le faire avec AJAX en arrière-plan. Nous utiliserons le [script de gestion des modules complémentaires |https://componette.org/vojtech-dobes/nette.ajax.js/] avec la convention habituelle selon laquelle les liens AJAX ont la classe CSS `ajax`. +Ajoutons maintenant AJAX à cette application simple. Le changement d'évaluation d'un article n'est pas assez important pour nécessiter une redirection, il devrait donc idéalement se faire en arrière-plan via AJAX. Nous utiliserons le [script de gestion des extensions |application:ajax#Naja] avec la convention habituelle selon laquelle les liens AJAX ont la classe CSS `ajax`. -Cependant, comment le faire spécifiquement ? Nette propose deux méthodes : le snippet dynamique et le composant. Ces deux méthodes ont leurs avantages et leurs inconvénients, nous allons donc les présenter une par une. +Mais comment faire concrètement ? Nette propose 2 voies : la voie des snippets dynamiques et la voie des composants. Les deux ont leurs avantages et leurs inconvénients, nous allons donc les présenter l'une après l'autre. -La méthode des snippets dynamiques .[#toc-the-dynamic-snippets-way] -=================================================================== +La voie des snippets dynamiques +=============================== -Dans la terminologie Latte, un extrait dynamique est un cas d'utilisation spécifique de la balise `{snippet}` où une variable est utilisée dans le nom de l'extrait. Un tel extrait ne peut pas se trouver n'importe où dans le modèle - il doit être entouré d'un extrait statique, c'est-à-dire un extrait normal, ou à l'intérieur d'un `{snippetArea}`. Nous pourrions modifier notre modèle comme suit. +Un snippet dynamique, dans la terminologie Latte, signifie un cas d'utilisation spécifique de la balise `{snippet}`, où une variable est utilisée dans le nom du snippet. Un tel snippet ne peut pas se trouver n'importe où dans le template - il doit être enveloppé par un snippet statique, c'est-à-dire ordinaire, ou à l'intérieur de `{snippetArea}`. Nous pourrions modifier notre template comme suit. ```latte @@ -53,18 +53,18 @@ Dans la terminologie Latte, un extrait dynamique est un cas d'utilisation spéci <div class="content">{$article->content}</div> {snippet article-{$article->id}} {if !$article->liked} - <a n:href="like! $article->id" class=ajax>I like it</a> + <a n:href="like! $article->id" class=ajax>J'aime</a> {else} - <a n:href="unlike! $article->id" class=ajax>I don't like it anymore</a> + <a n:href="unlike! $article->id" class=ajax>Je n'aime plus</a> {/if} {/snippet} </article> {/snippet} ``` -Chaque article définit désormais un seul extrait, dont le titre contient l'ID de l'article. Tous ces extraits sont ensuite regroupés dans un seul extrait appelé `articlesContainer`. Si nous omettons cet extrait, Latte nous avertit par une exception. +Chaque article définit maintenant un snippet qui a l'ID de l'article dans son nom. Tous ces snippets sont ensuite regroupés dans un seul snippet nommé `articlesContainer`. Si nous omettions ce snippet enveloppant, Latte nous le signalerait par une exception. -Il ne reste plus qu'à ajouter le redécoupage du présentateur - il suffit de redécouper le wrapper statique. +Il nous reste à ajouter le redessin dans le presenter - il suffit de redessiner l'enveloppe statique. ```php public function handleLike(int $articleId): void @@ -72,18 +72,18 @@ public function handleLike(int $articleId): void $this->ratingService->saveLike($articleId, $this->user->id); if ($this->isAjax()) { $this->redrawControl('articlesContainer'); - // $this->redrawControl('article-' . $articleId); -- není potřeba + // $this->redrawControl('article-' . $articleId); -- pas nécessaire } else { $this->redirect('this'); } } ``` -Modifiez la méthode sœur `handleUnlike()` de la même manière, et AJAX est opérationnel ! +Nous modifions de la même manière la méthode sœur `handleUnlike()`, et AJAX est fonctionnel ! -Cette solution présente toutefois un inconvénient. Si nous examinons de plus près le fonctionnement de la requête AJAX, nous constatons que même si l'application semble efficace en apparence (elle ne renvoie qu'un seul extrait pour un article donné), elle rend en fait tous les extraits sur le serveur. Elle a placé l'extrait désiré dans notre charge utile et a écarté les autres (ainsi, tout à fait inutilement, elle les a également récupérés dans la base de données). +Cependant, la solution a un inconvénient. Si nous examinions plus en détail le déroulement de la requête AJAX, nous constaterions que bien que l'application semble économe en apparence (elle ne renvoie qu'un seul snippet pour l'article donné), elle a en fait rendu tous les snippets sur le serveur. Elle a placé le snippet souhaité dans le payload et a jeté les autres (les récupérant donc également inutilement de la base de données). -Pour optimiser ce processus, nous devrons prendre une mesure où nous passons la collection `$articles` au modèle (disons dans la méthode `renderDefault()` ). Nous tirerons parti du fait que le traitement du signal a lieu avant la méthode `render<Something>` méthodes : +Pour optimiser ce processus, nous devrons intervenir là où nous passons la collection `$articles` au template (disons dans la méthode `renderDefault()`). Nous utiliserons le fait que le traitement des signaux a lieu avant les méthodes `render<Something>` : ```php public function handleLike(int $articleId): void @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connexion->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,18 +101,18 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connexion->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` -Désormais, lorsque le signal est traité, au lieu d'une collection contenant tous les articles, seul un tableau contenant un seul article est transmis au modèle - celui que nous voulons rendre et envoyer dans la charge utile au navigateur. Ainsi, `{foreach}` ne sera fait qu'une seule fois et aucun extrait supplémentaire ne sera rendu. +Maintenant, lors du traitement du signal, au lieu de la collection avec tous les articles, seul un tableau avec un seul article est passé au template - celui que nous voulons rendre et envoyer dans le payload au navigateur. `{foreach}` ne s'exécutera donc qu'une seule fois et aucun snippet supplémentaire ne sera rendu. -Méthode des composants .[#toc-component-way] -============================================ +La voie des composants +====================== -Une solution complètement différente utilise une approche différente pour éviter les snippets dynamiques. L'astuce consiste à déplacer toute la logique dans un composant séparé - à partir de maintenant, nous n'avons pas de présentateur pour s'occuper de la saisie de la note, mais un `LikeControl` dédié. La classe ressemblera à ce qui suit (elle contiendra également les méthodes `render`, `handleUnlike`, etc.) : +Une approche complètement différente évite les snippets dynamiques. L'astuce consiste à transférer toute la logique dans un composant séparé - la saisie des évaluations ne sera plus gérée par le presenter, mais par un `LikeControl` dédié. La classe ressemblera à ceci (en plus, elle contiendra également les méthodes `render`, `handleUnlike`, etc.) : ```php class LikeControl extends Nette\Application\UI\Control @@ -134,31 +134,31 @@ class LikeControl extends Nette\Application\UI\Control } ``` -Modèle de composant : +Template du composant : ```latte {snippet} {if !$article->liked} - <a n:href="like!" class=ajax>I like it</a> + <a n:href="like!" class=ajax>J'aime</a> {else} - <a n:href="unlike!" class=ajax>I don't like it anymore</a> + <a n:href="unlike!" class=ajax>Je n'aime plus</a> {/if} {/snippet} ``` -Bien sûr, nous allons modifier le modèle de vue et nous devrons ajouter une fabrique au présentateur. Comme nous allons créer le composant autant de fois que nous recevons d'articles de la base de données, nous allons utiliser la classe [application:Multiplier] pour le "multiplier". +Bien sûr, le template de la vue changera et nous devrons ajouter une factory au presenter. Comme nous créerons le composant autant de fois que nous obtiendrons d'articles de la base de données, nous utiliserons la classe [Multiplier |application:Multiplier] pour sa "multiplication". ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); } ``` -La vue du modèle est réduite au minimum nécessaire (et totalement dépourvue de snippets !) : +Le template de la vue est réduit au minimum nécessaire (et totalement dépourvu de snippets !) : ```latte <article n:foreach="$articles as $article"> @@ -168,8 +168,6 @@ La vue du modèle est réduite au minimum nécessaire (et totalement dépourvue </article> ``` -Nous avons presque terminé : l'application va maintenant fonctionner en AJAX. Ici aussi, nous devons optimiser l'application, car en raison de l'utilisation de la base de données Nette, le traitement du signal chargera inutilement tous les articles de la base de données au lieu d'un seul. Cependant, l'avantage est qu'il n'y aura pas de rendu, car seul notre composant est réellement rendu. - +Nous avons presque terminé : l'application fonctionnera désormais en AJAX. Ici aussi, nous devons optimiser l'application, car en raison de l'utilisation de Nette Database, lors du traitement du signal, tous les articles sont inutilement chargés depuis la base de données au lieu d'un seul. L'avantage, cependant, est qu'ils ne seront pas rendus, car seul notre composant sera réellement rendu. {{priority: -1}} -{{sitename: Meilleures pratiques}} diff --git a/best-practices/fr/editors-and-tools.texy b/best-practices/fr/editors-and-tools.texy index 61513bfaf6..f80342f3fe 100644 --- a/best-practices/fr/editors-and-tools.texy +++ b/best-practices/fr/editors-and-tools.texy @@ -1,40 +1,40 @@ -Éditeurs et outils -****************** +Éditeurs & Outils +***************** .[perex] Vous pouvez être un programmeur compétent, mais ce n'est qu'avec de bons outils que vous deviendrez un maître. Dans ce chapitre, vous trouverez des conseils sur les outils, éditeurs et plugins importants. -Éditeur IDE .[#toc-ide-editor] -============================== +Éditeur IDE +=========== -Nous recommandons fortement d'utiliser un IDE complet pour le développement, tel que PhpStorm, NetBeans, VS Code, et pas seulement un éditeur de texte avec support PHP. La différence est vraiment cruciale. Il n'y a aucune raison de se contenter d'un éditeur classique avec coloration syntaxique, car il n'atteint pas les capacités d'un IDE avec suggestion de code précise, qui peut refactoriser le code, et plus encore. Certains IDE sont payants, d'autres sont gratuits. +Nous recommandons vivement d'utiliser un IDE complet pour le développement, tel que PhpStorm, NetBeans, VS Code, et pas seulement un éditeur de texte avec prise en charge PHP. La différence est vraiment fondamentale. Il n'y a aucune raison de se contenter d'un simple éditeur qui colore la syntaxe mais n'atteint pas les capacités d'un IDE de pointe, qui suggère précisément, surveille les erreurs, peut refactoriser le code et bien plus encore. Certains IDE sont payants, d'autres sont même gratuits. -**NetBeans IDE** a un support intégré pour Nette, Latte et NEON. +**NetBeans IDE** intègre déjà la prise en charge de Nette, Latte et NEON. -**PhpStorm** : installez ces plugins dans `Settings > Plugins > Marketplace`: -- Aides pour le framework Nette +**PhpStorm** : installez ces plugins dans `Settings > Plugins > Marketplace` +- Nette framework helpers - Latte -- Support NEON -- Testeur Nette +- NEON support +- Nette Tester -**VS Code** : trouvez le plugin "Nette Latte + Neon" sur la place de marché. +**VS Code** : trouvez le plugin "Nette Latte + Neon" dans le marketplace. -Connectez également Tracy avec l'éditeur. Lorsque la page d'erreur est affichée, vous pouvez cliquer sur les noms de fichiers et ils s'ouvriront dans l'éditeur avec le curseur sur la ligne correspondante. Apprenez [à configurer le système |tracy:open-files-in-ide]. +Connectez également Tracy à votre éditeur. Lorsque la page d'erreur s'affiche, vous pourrez cliquer sur les noms de fichiers et ils s'ouvriront dans l'éditeur avec le curseur sur la ligne correspondante. Lisez [comment configurer le système|tracy:open-files-in-ide]. -PHPStan .[#toc-phpstan] -======================= +PHPStan +======= -PHPStan est un outil qui détecte les erreurs logiques dans votre code avant que vous ne l'exécutiez. +PHPStan est un outil qui détecte les erreurs logiques dans le code avant même de l'exécuter. -Installez-le via Composer : +Nous l'installons à l'aide de Composer : ```shell composer require --dev phpstan/phpstan-nette ``` -Créez un fichier de configuration `phpstan.neon` dans le projet : +Nous créons un fichier de configuration `phpstan.neon` dans le projet : ```neon includes: @@ -47,40 +47,38 @@ parameters: level: 5 ``` -Et ensuite laissez-le analyser les classes dans le dossier `app/`: +Et ensuite, nous le laissons analyser les classes dans le dossier `app/` : ```shell vendor/bin/phpstan analyse app ``` -Vous pouvez trouver une documentation complète directement sur [PHPStan |https://phpstan.org]. +Vous trouverez une documentation exhaustive directement sur le [site web de PHPStan |https://phpstan.org]. -Vérificateur de code .[#toc-code-checker] -========================================= +Code Checker +============ -[Code Checker |code-checker:] vérifie et répare éventuellement certaines des erreurs formelles de votre code source. +[Code Checker|code-checker:] vérifie et corrige éventuellement certaines erreurs formelles dans vos codes sources : -- supprime la [nomenclature |nette:glossary#bom] -- vérifie la validité des modèles [Latte |latte:] +- supprime le [BOM |nette:glossary#BOM] +- vérifie la validité des templates [Latte |latte:] - vérifie la validité des fichiers `.neon`, `.php` et `.json` -- vérifie les [caractères de contrôle |nette:glossary#control characters] -- vérifie si le fichier est codé en UTF-8 -- contrôle les fautes d'orthographe de `/* @annotations */` (deuxième astérisque manquant) -- supprime les balises de fin PHP `?>` dans les fichiers PHP -- supprime les espaces et les lignes vides inutiles à la fin d'un fichier -- normalise les fins de ligne par rapport au système par défaut (avec le paramètre `-l` ) +- vérifie la présence de [caractères de contrôle |nette:glossary#Caractères de contrôle] +- vérifie si le fichier est encodé en UTF-8 +- vérifie les `/* @anotace */` mal écrites (manque une étoile) +- supprime les `?>` de fin dans les fichiers PHP +- supprime les espaces de fin de ligne et les lignes vides inutiles à la fin du fichier +- normalise les séparateurs de ligne en séparateurs système (si vous spécifiez l'option `-l`) -Composer .[#toc-composer] -========================= +Composer +======== -[Composer] est un outil pour gérer vos dépendances en PHP. Il nous permet de déclarer les dépendances des bibliothèques et il les installera pour nous, dans notre projet. +[Composer |best-practices:composer] est un outil de gestion des dépendances en PHP. Il nous permet de déclarer des dépendances arbitrairement complexes de différentes bibliothèques et les installe ensuite pour nous dans notre projet. -Vérificateur d'exigences .[#toc-requirements-checker] -===================================================== +Requirements Checker +==================== -Il s'agissait d'un outil qui testait l'environnement de fonctionnement du serveur et indiquait si (et dans quelle mesure) le framework pouvait être utilisé. Actuellement, Nette peut être utilisé sur tout serveur disposant de la version minimale requise de PHP. - -{{sitename: Meilleures pratiques}} +C'était un outil qui testait l'environnement d'exécution du serveur et informait si (et dans quelle mesure) le framework pouvait être utilisé. Actuellement, Nette peut être utilisé sur n'importe quel serveur disposant de la version minimale requise de PHP. diff --git a/best-practices/fr/form-reuse.texy b/best-practices/fr/form-reuse.texy index a5a2829f81..234ff21ee0 100644 --- a/best-practices/fr/form-reuse.texy +++ b/best-practices/fr/form-reuse.texy @@ -1,16 +1,16 @@ -Réutilisation de formulaires à plusieurs endroits -************************************************* +Réutilisation des formulaires à plusieurs endroits +************************************************** .[perex] -Dans Nette, vous avez plusieurs options pour réutiliser le même formulaire à plusieurs endroits sans dupliquer le code. Dans cet article, nous allons passer en revue les différentes solutions, y compris celles que vous devriez éviter. +Dans Nette, vous disposez de plusieurs options pour utiliser le même formulaire à plusieurs endroits sans dupliquer de code. Dans cet article, nous allons examiner différentes solutions, y compris celles que vous devriez éviter. -Form Factory .[#toc-form-factory] -================================= +Factory de formulaires +====================== -Une approche de base pour utiliser le même composant à plusieurs endroits consiste à créer une méthode ou une classe qui génère le composant, puis à appeler cette méthode à différents endroits de l'application. Une telle méthode ou classe est appelée *factory*. Ne pas confondre avec le modèle de conception *méthode usine*, qui décrit une manière spécifique d'utiliser les usines et n'est pas lié à ce sujet. +L'une des approches fondamentales pour utiliser le même composant à plusieurs endroits est de créer une méthode ou une classe qui génère ce composant, puis d'appeler cette méthode à différents endroits de l'application. Une telle méthode ou classe est appelée une *factory*. Ne confondez pas s'il vous plaît avec le patron de conception *factory method*, qui décrit une manière spécifique d'utiliser les factories et n'est pas lié à ce sujet. -A titre d'exemple, créons une fabrique qui construira un formulaire d'édition : +À titre d'exemple, créons une factory qui assemblera un formulaire d'édition : ```php use Nette\Application\UI\Form; @@ -20,22 +20,22 @@ class FormFactory public function createEditForm(): Form { $form = new Form; - $form->addText('title', 'Title:'); - // les champs supplémentaires du formulaire sont ajoutés ici - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Titre :'); + // ici, on ajoute d'autres champs de formulaire + $form->addSubmit('send', 'Envoyer'); return $form; } } ``` -Vous pouvez maintenant utiliser cette fabrique à différents endroits de votre application, par exemple dans des présentateurs ou des composants. Pour ce faire, nous la [demandons en tant que dépendance |dependency-injection:passing-dependencies]. Tout d'abord, nous allons écrire la classe dans le fichier de configuration : +Vous pouvez maintenant utiliser cette factory à différents endroits de votre application, par exemple dans les presenters ou les composants. Et ce, en la [demandant comme dépendance|dependency-injection:passing-dependencies]. Tout d'abord, inscrivons la classe dans le fichier de configuration : ```neon services: - FormFactory ``` -Puis nous l'utilisons dans le présentateur : +Et ensuite, utilisons-la dans un presenter : ```php @@ -50,14 +50,14 @@ class MyPresenter extends Nette\Application\UI\Presenter { $form = $this->formFactory->createEditForm(); $form->onSuccess[] = function () { - // traitement des données envoyées + // traitement des données soumises }; return $form; } } ``` -Vous pouvez étendre la fabrique de formulaires avec des méthodes supplémentaires pour créer d'autres types de formulaires adaptés à votre application. Et, bien sûr, vous pouvez ajouter une méthode qui crée un formulaire de base sans éléments, que les autres méthodes utiliseront : +Vous pouvez étendre la factory de formulaires avec d'autres méthodes pour créer d'autres types de formulaires selon les besoins de votre application. Et bien sûr, nous pouvons également ajouter une méthode qui créera un formulaire de base sans éléments, et que les autres méthodes utiliseront : ```php class FormFactory @@ -71,21 +71,21 @@ class FormFactory public function createEditForm(): Form { $form = $this->createForm(); - $form->addText('title', 'Title:'); - // les champs supplémentaires du formulaire sont ajoutés ici - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Titre :'); + // ici, on ajoute d'autres champs de formulaire + $form->addSubmit('send', 'Envoyer'); return $form; } } ``` -La méthode `createForm()` ne fait rien d'utile pour l'instant, mais cela changera rapidement. +La méthode `createForm()` ne fait rien d'utile pour le moment, mais cela changera rapidement. -Dépendances de l'usine .[#toc-factory-dependencies] -=================================================== +Dépendances de la factory +========================= -Avec le temps, il deviendra évident que nous avons besoin de formulaires multilingues. Cela signifie que nous devons mettre en place un [traducteur |forms:rendering#Translating] pour tous les formulaires. Pour ce faire, nous modifions la classe `FormFactory` afin qu'elle accepte l'objet `Translator` en tant que dépendance dans le constructeur et qu'elle le transmette au formulaire : +Avec le temps, il s'avérera que nous avons besoin que les formulaires soient multilingues. Cela signifie que nous devons définir un [traducteur |forms:rendering#Traduction] pour tous les formulaires. À cette fin, nous modifierons la classe `FormFactory` pour qu'elle accepte un objet `Translator` comme dépendance dans le constructeur, et nous le transmettrons au formulaire : ```php use Nette\Localization\Translator; @@ -104,18 +104,17 @@ class FormFactory return $form; } - //... + // ... } ``` -Comme la méthode `createForm()` est également appelée par d'autres méthodes qui créent des formulaires spécifiques, nous n'avons besoin de définir le traducteur que dans cette méthode. Et le tour est joué. Il n'est pas nécessaire de modifier le code du présentateur ou du composant, ce qui est très bien. +Comme la méthode `createForm()` est également appelée par les autres méthodes créant des formulaires spécifiques, il suffit de définir le traducteur uniquement dans celle-ci. Et c'est fait. Il n'est pas nécessaire de modifier le code d'aucun presenter ou composant, ce qui est génial. -Autres classes d'usine .[#toc-more-factory-classes] -=================================================== +Plusieurs classes de factory +============================ -Vous pouvez également créer plusieurs classes pour chaque formulaire que vous souhaitez utiliser dans votre application. -Cette approche peut améliorer la lisibilité du code et faciliter la gestion des formulaires. Laissez la classe originale `FormFactory` pour créer un formulaire pur avec une configuration de base (par exemple, avec un support de traduction) et créez une nouvelle classe d'usine `EditFormFactory` pour le formulaire d'édition. +Alternativement, vous pouvez créer plusieurs classes pour chaque formulaire que vous souhaitez utiliser dans votre application. Cette approche peut améliorer la lisibilité du code et faciliter la gestion des formulaires. Nous laisserons la `FormFactory` originale créer uniquement un formulaire propre avec une configuration de base (par exemple, avec prise en charge des traductions) et pour le formulaire d'édition, nous créerons une nouvelle factory `EditFormFactory`. ```php class FormFactory @@ -145,40 +144,39 @@ class EditFormFactory public function create(): Form { $form = $this->formFactory->create(); - // des champs de formulaire supplémentaires sont ajoutés ici - $form->addSubmit('send', 'Save'); + // ici, on ajoute d'autres champs de formulaire + $form->addSubmit('send', 'Envoyer'); return $form; } } ``` -Il est très important que la liaison entre les classes `FormFactory` et `EditFormFactory` soit mise en œuvre par composition et non par héritage d'objets : +Il est très important que la liaison entre les classes `FormFactory` et `EditFormFactory` soit réalisée par [composition |nette:introduction-to-object-oriented-programming#Composition], et non par [héritage objet |nette:introduction-to-object-oriented-programming#Héritage] : ```php -// ⛔ NO ! L'HÉRITAGE N'A PAS SA PLACE ICI +// ⛔ PAS COMME ÇA ! L'HÉRITAGE N'A PAS SA PLACE ICI class EditFormFactory extends FormFactory { public function create(): Form { $form = parent::create(); - $form->addText('title', 'Title:'); - // des champs de formulaire supplémentaires sont ajoutés ici - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Titre :'); + // ici, on ajoute d'autres champs de formulaire + $form->addSubmit('send', 'Envoyer'); return $form; } } ``` -L'utilisation de l'héritage dans ce cas serait totalement contre-productive. Vous rencontreriez des problèmes très rapidement. Par exemple, si vous vouliez ajouter des paramètres à la méthode `create()`, PHP signalerait une erreur parce que sa signature est différente de celle du parent. -Ou lorsque vous passez une dépendance à la classe `EditFormFactory` via le constructeur. Cela provoquerait ce que nous appelons l'[enfer du constructeur |dependency-injection:passing-dependencies#Constructor hell]. +L'utilisation de l'héritage serait dans ce cas totalement contre-productive. Vous rencontreriez des problèmes très rapidement. Par exemple, au moment où vous voudriez ajouter des paramètres à la méthode `create()` ; PHP signalerait une erreur indiquant que sa signature diffère de celle du parent. Ou lors de la transmission de dépendances à la classe `EditFormFactory` via le constructeur. Une situation appelée [enfer du constructeur |dependency-injection:passing-dependencies#Constructor hell] se produirait. -En général, il est préférable de préférer la composition à l'héritage. +En général, il est préférable de privilégier la [composition plutôt que l'héritage |dependency-injection:faq#Pourquoi la composition est-elle préférée à l héritage]. -Traitement des formulaires .[#toc-form-handling] -================================================ +Gestion du formulaire +===================== -Le gestionnaire de formulaire qui est appelé après une soumission réussie peut également faire partie d'une classe d'usine. Il transmet les données soumises au modèle pour traitement. Il renvoie les erreurs éventuelles [au |forms:validation#Processing Errors] formulaire. Dans l'exemple suivant, le modèle est représenté par la classe `Facade`: +La gestion du formulaire, qui est appelée après une soumission réussie, peut également faire partie de la classe factory. Elle fonctionnera en transmettant les données soumises au modèle pour traitement. Les erreurs éventuelles seront [retournées |forms:validation#Erreurs lors du traitement] au formulaire. Le modèle dans l'exemple suivant est représenté par la classe `Facade` : ```php class EditFormFactory @@ -192,9 +190,9 @@ class EditFormFactory public function create(): Form { $form = $this->formFactory->create(); - $form->addText('title', 'Title:'); - // les champs supplémentaires du formulaire sont ajoutés ici - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Titre :'); + // ici, on ajoute d'autres champs de formulaire + $form->addSubmit('send', 'Envoyer'); $form->onSuccess[] = [$this, 'processForm']; return $form; } @@ -212,7 +210,7 @@ class EditFormFactory } ``` -Laissez le présentateur gérer lui-même la redirection. Il ajoutera un autre gestionnaire à l'événement `onSuccess`, qui effectuera la redirection. Le formulaire pourra ainsi être utilisé par différents présentateurs et chacun d'entre eux pourra rediriger vers un emplacement différent. +Cependant, nous laisserons la redirection elle-même au presenter. Celui-ci ajoutera un autre gestionnaire à l'événement `onSuccess`, qui effectuera la redirection. Grâce à cela, il sera possible d'utiliser le formulaire dans différents presenters et de rediriger différemment dans chacun d'eux. ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -226,7 +224,7 @@ class MyPresenter extends Nette\Application\UI\Presenter { $form = $this->formFactory->create(); $form->onSuccess[] = function () { - $this->flashMessage('Záznam byl uložen'); + $this->flashMessage('L\'enregistrement a été sauvegardé'); $this->redirect('Homepage:'); }; return $form; @@ -234,39 +232,38 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Cette solution tire parti de la propriété des formulaires selon laquelle, lorsque `addError()` est appelé sur un formulaire ou son élément, le gestionnaire `onSuccess` suivant n'est pas invoqué. +Cette solution utilise la propriété des formulaires selon laquelle si `addError()` est appelé sur le formulaire ou l'un de ses éléments, le gestionnaire `onSuccess` suivant n'est plus appelé. -Héritage de la classe de formulaire .[#toc-inheriting-from-the-form-class] -========================================================================== +Héritage de la classe Form +========================== -Un formulaire construit n'est pas censé être un enfant d'un formulaire. En d'autres termes, n'utilisez pas cette solution : +Un formulaire assemblé ne doit pas être un descendant du formulaire. En d'autres termes, n'utilisez pas cette solution : ```php -// ⛔ NO ! L'HÉRITAGE N'A PAS SA PLACE ICI +// ⛔ PAS COMME ÇA ! L'HÉRITAGE N'A PAS SA PLACE ICI class EditForm extends Form { public function __construct(Translator $translator) { parent::__construct(); - $form->addText('title', 'Title:'); - // des champs de formulaire supplémentaires sont ajoutés ici - $form->addSubmit('send', 'Save'); - $form->setTranslator($translator); + $this->addText('title', 'Titre :'); + // ici, on ajoute d'autres champs de formulaire + $this->addSubmit('send', 'Envoyer'); + $this->setTranslator($translator); } } ``` -Au lieu de construire le formulaire dans le constructeur, utilisez la fabrique. +Au lieu d'assembler le formulaire dans le constructeur, utilisez une factory. -Il est important de comprendre que la classe `Form` est avant tout un outil permettant d'assembler un formulaire, c'est-à-dire un constructeur de formulaire. Le formulaire assemblé peut être considéré comme son produit. Cependant, le produit n'est pas un cas spécifique du constructeur ; il n'y a pas de relation *is a* entre eux, ce qui constitue la base de l'héritage. +Il faut comprendre que la classe `Form` est avant tout un outil pour assembler un formulaire, c'est-à-dire un *form builder*. Et le formulaire assemblé peut être considéré comme son produit. Or, le produit n'est pas un cas spécifique du builder, il n'y a pas entre eux de relation *is a* qui constitue la base de l'héritage. -Composant de formulaire .[#toc-form-component] -============================================== +Composant avec formulaire +========================= -Une approche complètement différente consiste à créer un [composant |application:components] qui inclut un formulaire. Cela offre de nouvelles possibilités, par exemple pour rendre le formulaire d'une manière spécifique, puisque le composant inclut un modèle. -Des signaux peuvent également être utilisés pour la communication AJAX et le chargement d'informations dans le formulaire, par exemple pour des indications, etc. +Une approche totalement différente consiste à créer un [composant|application:components] qui inclut un formulaire. Cela offre de nouvelles possibilités, par exemple rendre le formulaire d'une manière spécifique, car le composant inclut également un template. Ou bien, on peut utiliser des signaux pour la communication AJAX et le chargement différé d'informations dans le formulaire, par exemple pour l'autocomplétion, etc. ```php @@ -284,9 +281,9 @@ class EditControl extends Nette\Application\UI\Control protected function createComponentForm(): Form { $form = new Form; - $form->addText('title', 'Title:'); - // les champs supplémentaires du formulaire sont ajoutés ici - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Titre :'); + // ici, on ajoute d'autres champs de formulaire + $form->addSubmit('send', 'Envoyer'); $form->onSuccess[] = [$this, 'processForm']; return $form; @@ -303,13 +300,13 @@ class EditControl extends Nette\Application\UI\Control return; } - // invocation d'événements + // déclenchement de l'événement $this->onSave($this, $data); } } ``` -Créons une fabrique qui produira ce composant. Il suffit d'[écrire son interface |application:components#Components with Dependencies]: +Nous allons également créer une factory qui produira ce composant. Il suffit d'[écrire son interface |application:components#Composants avec dépendances] : ```php interface EditControlFactory @@ -318,14 +315,14 @@ interface EditControlFactory } ``` -et de l'ajouter au fichier de configuration : +Et l'ajouter au fichier de configuration : ```neon services: - EditControlFactory ``` -Nous pouvons maintenant demander la fabrique et l'utiliser dans le présentateur : +Et maintenant, nous pouvons demander la factory et l'utiliser dans le presenter : ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -335,13 +332,13 @@ class MyPresenter extends Nette\Application\UI\Presenter ) { } - protected function createComponentEditForm(): Form + protected function createComponentEditForm(): EditControl { $control = $this->controlFactory->create(); $control->onSave[] = function (EditControl $control, $data) { $this->redirect('this'); - // ou rediriger vers le résultat de l'édition, par exemple: + // ou nous redirigeons vers le résultat de l'édition, par ex. : // $this->redirect('detail', ['id' => $data->id]); }; @@ -349,5 +346,3 @@ class MyPresenter extends Nette\Application\UI\Presenter } } ``` - -{{sitename: Meilleures pratiques}} diff --git a/best-practices/fr/inject-method-attribute.texy b/best-practices/fr/inject-method-attribute.texy index 0cd31cc567..a9896afe31 100644 --- a/best-practices/fr/inject-method-attribute.texy +++ b/best-practices/fr/inject-method-attribute.texy @@ -1,21 +1,18 @@ -Méthodes et attributs d'injection -********************************* +Méthodes et attributs inject +**************************** .[perex] -Dans cet article, nous allons nous concentrer sur les différentes façons de transmettre des dépendances aux présentateurs dans le cadre de Nette. Nous comparerons la méthode préférée, qui est le constructeur, avec d'autres options telles que les méthodes et les attributs `inject`. +Dans cet article, nous nous concentrerons sur les différentes manières de transmettre des dépendances aux presenters dans le framework Nette. Nous comparerons la méthode préférée, qui est le constructeur, avec d'autres options telles que les méthodes et les attributs `inject`. -Pour les présentateurs également, la transmission des dépendances à l'aide du [constructeur |dependency-injection:passing-dependencies#Constructor Injection] est la méthode préférée. -Cependant, si vous créez un ancêtre commun dont les autres présentateurs héritent (par exemple, BasePresenter) et que cet ancêtre possède également des dépendances, un problème se pose, que nous appelons l'[enfer du constructeur |dependency-injection:passing-dependencies#Constructor hell]. -Ce problème peut être contourné en utilisant des méthodes alternatives, qui incluent l'injection de méthodes et d'attributs (annotations). +Pour les presenters également, la transmission de dépendances via le [constructeur |dependency-injection:passing-dependencies#Passage par constructeur] est la voie préférée. Cependant, si vous créez un ancêtre commun dont héritent d'autres presenters (par exemple `BasePresenter`), et que cet ancêtre a également des dépendances, un problème survient que nous appelons [l'enfer du constructeur |dependency-injection:passing-dependencies#Constructor hell]. Celui-ci peut être contourné en utilisant des voies alternatives, qui sont les méthodes et les attributs (annotations) `inject`. -`inject*()` Méthodes .[#toc-inject-methods] -=========================================== +Méthodes `inject*()` +==================== -Il s'agit d'une forme de transfert de dépendance à l'aide de [fixateurs |dependency-injection:passing-dependencies#Setter Injection]. Les noms de ces setters commencent par le préfixe inject. -Nette DI appelle automatiquement ces méthodes nommées immédiatement après la création de l'instance du présentateur et leur transmet toutes les dépendances nécessaires. Elles doivent donc être déclarées comme publiques. +Il s'agit d'une forme de transmission de dépendances par [setter |dependency-injection:passing-dependencies#Passage par setter]. Le nom de ces setters commence par le préfixe `inject`. Nette DI appelle automatiquement les méthodes ainsi nommées juste après la création de l'instance du presenter et leur transmet toutes les dépendances requises. Elles doivent donc être déclarées comme public. -`inject*()` Les méthodes publiques peuvent être considérées comme une sorte d'extension de constructeur en plusieurs méthodes. Grâce à cela, `BasePresenter` peut prendre des dépendances par le biais d'une autre méthode et laisser le constructeur libre pour ses descendants : +Les méthodes `inject*()` peuvent être considérées comme une sorte d'extension du constructeur en plusieurs méthodes. Grâce à cela, `BasePresenter` peut recevoir des dépendances via une autre méthode et laisser le constructeur libre pour ses descendants : ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -39,18 +36,18 @@ class MyPresenter extends BasePresenter } ``` -Le présentateur peut contenir n'importe quel nombre de méthodes `inject*()`, et chacune peut avoir n'importe quel nombre de paramètres. C'est également très utile lorsque le présentateur est [composé de traits |presenter-traits] et que chacun d'entre eux nécessite sa propre dépendance. +Un presenter peut contenir un nombre quelconque de méthodes `inject*()` et chacune peut avoir un nombre quelconque de paramètres. Elles sont également très utiles dans les cas où le presenter est [composé de traits |presenter-traits] et que chacun d'eux nécessite sa propre dépendance. -`Inject` Attributs .[#toc-inject-attributes] -============================================ +Attributs `Inject` +================== -Il s'agit d'une forme d'[injection dans les propriétés |dependency-injection:passing-dependencies#Property Injection]. Il suffit d'indiquer quelles propriétés doivent être injectées, et Nette DI passe automatiquement les dépendances immédiatement après la création de l'instance du présentateur. Pour les insérer, il est nécessaire de les déclarer comme publiques. +Il s'agit d'une forme d'[injection dans la propriété |dependency-injection:passing-dependencies#Assignation à une variable]. Il suffit de marquer les propriétés dans lesquelles injecter, et Nette DI transmet automatiquement les dépendances juste après la création de l'instance du presenter. Pour pouvoir les insérer, il est nécessaire de les déclarer comme public. -Les propriétés sont marquées par un attribut : (auparavant, l'annotation `/** @inject */` était utilisée) +Nous marquons les propriétés avec un attribut : (auparavant, l'annotation `/** @inject */` était utilisée) ```php -use Nette\DI\Attributes\Inject ; // cette ligne est importante +use Nette\DI\Attributes\Inject; // cette ligne est importante class MyPresenter extends Nette\Application\UI\Presenter { @@ -59,9 +56,6 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -L'avantage de cette méthode de transmission des dépendances était sa forme de notation très économique. Cependant, avec l'introduction de la [promotion des propriétés du constructeur |https://blog.nette.org/fr/php-8-0-apercu-complet-des-nouveautes#toc-constructor-property-promotion], l'utilisation du constructeur semble plus facile. +L'avantage de cette méthode de transmission des dépendances était sa forme d'écriture très concise. Cependant, avec l'arrivée de la [promotion des propriétés du constructeur |https://blog.nette.org/fr/php-8-0-complete-overview-of-news#toc-constructor-property-promotion], il semble plus facile d'utiliser le constructeur. -D'autre part, cette méthode souffre des mêmes défauts que le passage des dépendances dans les propriétés en général : nous n'avons aucun contrôle sur les changements de la variable, et en même temps, la variable devient une partie de l'interface publique de la classe, ce qui n'est pas souhaitable. - - -{{sitename: Meilleures pratiques}} +Inversement, cette méthode souffre des mêmes défauts que la transmission de dépendances aux propriétés en général : nous n'avons aucun contrôle sur les changements dans la variable, et en même temps, la variable devient partie intégrante de l'interface publique de la classe, ce qui n'est pas souhaitable. diff --git a/best-practices/fr/lets-create-contact-form.texy b/best-practices/fr/lets-create-contact-form.texy index 50afc50327..081268c0b0 100644 --- a/best-practices/fr/lets-create-contact-form.texy +++ b/best-practices/fr/lets-create-contact-form.texy @@ -1,12 +1,12 @@ -Créons un formulaire de contact -******************************* +Création d'un formulaire de contact +*********************************** .[perex] -Voyons comment créer un formulaire de contact dans Nette, y compris l'envoi vers un e-mail. C'est parti ! +Nous allons voir comment créer un formulaire de contact dans Nette, y compris l'envoi par e-mail. Alors, allons-y ! -Tout d'abord, nous devons créer un nouveau projet. Comme l'explique la page " [Getting Started" |nette:installation]. Ensuite, nous pouvons commencer à créer le formulaire. +Tout d'abord, nous devons créer un nouveau projet. La page [Démarrage |nette:installation] explique comment faire. Ensuite, nous pouvons commencer à créer le formulaire. -La manière la plus simple est de créer le [formulaire directement dans Presenter |forms:in-presenter]. Nous pouvons utiliser le formulaire préétabli `HomePresenter`. Nous ajouterons le composant `contactForm` représentant le formulaire. Pour ce faire, nous écrivons la méthode d'usine `createComponentContactForm()` dans le code qui produira le composant : +Le plus simple est de créer le [formulaire directement dans le presenter |forms:in-presenter]. Nous pouvons utiliser le `HomePresenter` pré-préparé. Nous y ajouterons le composant `contactForm` représentant le formulaire. Pour ce faire, nous écrirons dans le code une méthode factory `createComponentContactForm()` qui fabriquera le composant : ```php use Nette\Application\UI\Form; @@ -17,37 +17,35 @@ class HomePresenter extends Presenter protected function createComponentContactForm(): Form { $form = new Form; - $form->addText('name', 'Name:') - ->setRequired('Enter your name'); - $form->addEmail('email', 'E-mail:') - ->setRequired('Enter your e-mail'); - $form->addTextarea('message', 'Message:') - ->setRequired('Enter message'); - $form->addSubmit('send', 'Send'); + $form->addText('name', 'Nom :') + ->setRequired('Veuillez entrer votre nom'); + $form->addEmail('email', 'E-mail :') + ->setRequired('Veuillez entrer votre e-mail'); + $form->addTextarea('message', 'Message :') + ->setRequired('Veuillez entrer votre message'); + $form->addSubmit('send', 'Envoyer'); $form->onSuccess[] = [$this, 'contactFormSucceeded']; return $form; } public function contactFormSucceeded(Form $form, $data): void { - // sending an email + // envoi de l'e-mail } } ``` -Comme vous pouvez le voir, nous avons créé deux méthodes. La première méthode `createComponentContactForm()` crée un nouveau formulaire. Celui-ci comporte des champs pour le nom, l'adresse électronique et le message, que nous ajoutons à l'aide des méthodes `addText()`, `addEmail()` et `addTextArea()`. Nous avons également ajouté un bouton pour soumettre le formulaire. -Mais que se passe-t-il si l'utilisateur ne remplit pas certains champs ? Dans ce cas, nous devons l'informer qu'il s'agit d'un champ obligatoire. C'est ce que nous avons fait avec la méthode `setRequired()`. -Enfin, nous avons également ajouté un [événement |nette:glossary#events] `onSuccess`, qui est déclenché si le formulaire est soumis avec succès. Dans notre cas, il appelle la méthode `contactFormSucceeded`, qui se charge de traiter le formulaire soumis. Nous l'ajouterons au code dans un instant. +Comme vous pouvez le voir, nous avons créé deux méthodes. La première méthode `createComponentContactForm()` crée un nouveau formulaire. Il comporte des champs pour le nom, l'e-mail et le message, que nous ajoutons avec les méthodes `addText()`, `addEmail()` et `addTextArea()`. Nous avons également ajouté un bouton pour soumettre le formulaire. Mais que se passe-t-il si l'utilisateur ne remplit pas un champ ? Dans ce cas, nous devrions lui faire savoir que c'est un champ obligatoire. Nous y sommes parvenus avec la méthode `setRequired()`. Enfin, nous avons également ajouté un [événement |nette:glossary#Événements events] `onSuccess`, qui se déclenche si le formulaire est soumis avec succès. Dans notre cas, il appelle la méthode `contactFormSucceeded`, qui se chargera du traitement du formulaire soumis. Nous ajouterons cela au code dans un instant. -Laissez le composant `contantForm` être rendu dans le modèle `templates/Home/default.latte`: +Nous laisserons le composant `contactForm` se rendre dans le template `Home/default.latte` : ```latte {block content} -<h1>Contant Form</h1> +<h1>Formulaire de contact</h1> {control contactForm} ``` -Pour envoyer le courrier électronique lui-même, nous créons une nouvelle classe appelée `ContactFacade` et la plaçons dans le fichier `app/Model/ContactFacade.php`: +Pour l'envoi de l'e-mail lui-même, nous créerons une nouvelle classe que nous appellerons `ContactFacade` et la placerons dans le fichier `app/Model/ContactFacade.php` : ```php <?php @@ -68,9 +66,9 @@ class ContactFacade public function sendMessage(string $email, string $name, string $message): void { $mail = new Message; - $mail->addTo('admin@example.com') // your email + $mail->addTo('admin@example.com') // votre e-mail ->setFrom($email, $name) - ->setSubject('Message from the contact form') + ->setSubject('Message du formulaire de contact') ->setBody($message); $this->mailer->send($mail); @@ -78,9 +76,9 @@ class ContactFacade } ``` -La méthode `sendMessage()` créera et enverra le courrier électronique. Pour ce faire, elle utilise ce que l'on appelle un "mailer", qu'elle transmet en tant que dépendance via le constructeur. En savoir plus sur l'[envoi de courriels |mail:]. +La méthode `sendMessage()` crée et envoie l'e-mail. Pour ce faire, elle utilise ce qu'on appelle un mailer, qu'elle reçoit comme dépendance via le constructeur. Apprenez-en davantage sur l'[envoi d'e-mails |mail:]. -Nous allons maintenant retourner au présentateur et compléter la méthode `contactFormSucceeded()`. Elle appelle la méthode `sendMessage()` de la classe `ContactFacade` et lui transmet les données du formulaire. Et comment obtenir l'objet `ContactFacade`? Il nous sera transmis par le constructeur : +Maintenant, revenons au presenter et complétons la méthode `contactFormSucceeded()`. Elle appellera la méthode `sendMessage()` de la classe `ContactFacade` et lui transmettra les données du formulaire. Et comment obtenir l'objet `ContactFacade` ? Nous le laisserons nous être transmis par le constructeur : ```php use App\Model\ContactFacade; @@ -102,36 +100,36 @@ class HomePresenter extends Presenter public function contactFormSucceeded(stdClass $data): void { $this->facade->sendMessage($data->email, $data->name, $data->message); - $this->flashMessage('The message has been sent'); + $this->flashMessage('Le message a été envoyé'); $this->redirect('this'); } } ``` -Après l'envoi du courrier électronique, nous montrons à l'utilisateur le " [message flash |application:components#flash-messages]", qui confirme que le message a été envoyé, puis nous le redirigeons vers la page suivante, de sorte que le formulaire ne puisse pas être soumis à nouveau en utilisant la fonction *refresh* du navigateur. +Après l'envoi de l'e-mail, nous afficherons également à l'utilisateur un [message flash |application:components#Messages Flash] confirmant que le message a été envoyé, puis nous redirigerons vers la page actuelle afin qu'il ne soit pas possible de soumettre à nouveau le formulaire en utilisant *refresh* dans le navigateur. -Si tout fonctionne, vous devriez être en mesure d'envoyer un courriel à partir de votre formulaire de contact. Nous vous félicitons ! +Voilà, et si tout fonctionne, vous devriez pouvoir envoyer un e-mail depuis votre formulaire de contact. Félicitations ! -Modèle d'e-mail HTML .[#toc-html-email-template] ------------------------------------------------- +Template HTML de l'e-mail +------------------------- -Pour l'instant, un courriel en texte brut contenant uniquement le message envoyé par le formulaire est envoyé. Mais nous pouvons utiliser le HTML dans l'e-mail et le rendre plus attrayant. Nous allons créer un modèle dans Latte, que nous enregistrerons dans `app/Model/contactEmail.latte`: +Pour l'instant, un e-mail en texte brut est envoyé, contenant uniquement le message soumis par le formulaire. Mais nous pouvons utiliser le HTML dans l'e-mail et rendre son apparence plus attrayante. Nous allons créer un template pour cela en Latte, que nous écrirons dans `app/Model/contactEmail.latte` : ```latte <html> - <title>Message from the contact form + Message du formulaire de contact -

    Name: {$name}

    -

    E-mail: {$email}

    -

    Message: {$message}

    +

    Nom : {$name}

    +

    E-mail : {$email}

    +

    Message : {$message}

    ``` -Il reste à modifier `ContactFacade` pour utiliser ce modèle. Dans le constructeur, nous demandons la classe `LatteFactory`, qui peut produire l'objet `Latte\Engine`, un [moteur de rendu de modèle Latte |latte:develop#how-to-render-a-template]. Nous utilisons la méthode `renderToString()` pour rendre le modèle dans un fichier, le premier paramètre étant le chemin vers le modèle et le second les variables. +Il reste à modifier `ContactFacade`, pour qu'il utilise ce template. Dans le constructeur, nous demanderons la classe `LatteFactory`, qui sait fabriquer un objet `Latte\Engine`, c'est-à-dire le [moteur de rendu de templates Latte |latte:develop#Comment rendre un template]. Avec la méthode `renderToString()`, nous rendrons le template dans un fichier, le premier paramètre est le chemin vers le template et le second sont les variables. ```php namespace App\Model; @@ -158,7 +156,7 @@ class ContactFacade ]); $mail = new Message; - $mail->addTo('admin@example.com') // your email + $mail->addTo('admin@example.com') // votre e-mail ->setFrom($email, $name) ->setHtmlBody($body); @@ -167,15 +165,15 @@ class ContactFacade } ``` -Nous passons ensuite l'e-mail HTML généré à la méthode `setHtmlBody()` au lieu de l'original `setBody()`. Nous n'avons pas non plus besoin de spécifier le sujet de l'e-mail dans `setSubject()`, car la bibliothèque l'extrait de l'élément `` dans le modèle. +Nous transmettrons ensuite l'e-mail HTML généré à la méthode `setHtmlBody()` au lieu de l'original `setBody()`. De même, nous n'avons pas besoin de spécifier l'objet de l'e-mail dans `setSubject()`, car la bibliothèque le prendra à partir de l'élément `<title>` du template. -Configuration de .[#toc-configuring] ------------------------------------- +Configuration +------------- -Dans le code de la classe `ContactFacade`, notre email d'administration `admin@example.com` est encore codé en dur. Il serait préférable de la déplacer dans le fichier de configuration. Comment faire ? +Dans le code de la classe `ContactFacade`, notre e-mail administrateur `admin@example.com` est toujours codé en dur. Il serait préférable de le déplacer dans le fichier de configuration. Comment faire ? -Tout d'abord, nous modifions la classe `ContactFacade` et remplaçons la chaîne de l'email par une variable passée par le constructeur : +Tout d'abord, modifions la classe `ContactFacade` et remplaçons la chaîne avec l'e-mail par une variable transmise par le constructeur : ```php class ContactFacade @@ -199,28 +197,25 @@ class ContactFacade } ``` -Et la deuxième étape consiste à mettre la valeur de cette variable dans la configuration. Dans le fichier `app/config/services.neon` nous ajoutons : +Et la deuxième étape consiste à indiquer la valeur de cette variable dans la configuration. Dans le fichier `config/services.neon` (ou `app/config/services.neon` dans les versions plus anciennes), nous écrirons : ```neon services: - App\Model\ContactFacade(adminEmail: admin@example.com) ``` -Et c'est tout. S'il y a beaucoup d'éléments dans la section `services` et que vous avez l'impression que le courriel se perd parmi eux, nous pouvons en faire une variable. Nous modifierons l'entrée en : +Et c'est tout. S'il y a beaucoup d'éléments dans la section `services` et que vous avez l'impression que l'e-mail se perd parmi eux, nous pouvons en faire une variable. Modifions l'écriture en : ```neon services: - App\Model\ContactFacade(adminEmail: %adminEmail%) ``` -et définir cette variable dans le fichier `app/config/common.neon`: +Et dans le fichier `app/config/common.neon`, nous définirons cette variable : ```neon parameters: adminEmail: admin@example.com ``` -Et c'est fait ! - - -{{sitename: Meilleures pratiques}} +Et c'est terminé ! diff --git a/best-practices/fr/microsites.texy b/best-practices/fr/microsites.texy new file mode 100644 index 0000000000..cf0410f4ed --- /dev/null +++ b/best-practices/fr/microsites.texy @@ -0,0 +1,63 @@ +Comment écrire des micro-sites +****************************** + +Imaginez que vous ayez besoin de créer rapidement un petit site web pour un événement à venir de votre entreprise. Il doit être simple, rapide et sans complications inutiles. Vous pourriez penser que pour un si petit projet, vous n'avez pas besoin d'un framework robuste. Mais que se passerait-il si l'utilisation du framework Nette pouvait simplifier et accélérer considérablement ce processus ? + +Même lors de la création de sites web simples, vous ne voulez pas renoncer au confort. Vous ne voulez pas réinventer ce qui a déjà été résolu une fois. Soyez paresseux et laissez-vous choyer. Nette Framework peut également être parfaitement utilisé comme micro framework. + +À quoi peut ressembler un tel microsite ? Par exemple, en plaçant tout le code du site web dans un seul fichier `index.php` dans le dossier public : + +```php +<?php + +require __DIR__ . '/../vendor/autoload.php'; + +$configurator = new Nette\Bootstrap\Configurator; +$configurator->enableTracy(__DIR__ . '/../log'); +$configurator->setTempDirectory(__DIR__ . '/../temp'); + +// créer un conteneur DI basé sur la configuration dans config.neon +$configurator->addConfig(__DIR__ . '/../app/config.neon'); +$container = $configurator->createContainer(); + +// configurer le routage +$router = new Nette\Application\Routers\RouteList; +$container->addService('router', $router); + +// route pour l'URL https://example.com/ +$router->addRoute('', function ($presenter, Nette\Http\Request $httpRequest) { + // détecter la langue du navigateur et rediriger vers l'URL /en ou /de etc. + $supportedLangs = ['en', 'de', 'fr']; + $lang = $httpRequest->detectLanguage($supportedLangs) ?: reset($supportedLangs); + $presenter->redirectUrl("/$lang"); +}); + +// route pour l'URL https://example.com/fr ou https://example.com/en +$router->addRoute('<lang fr|en>', function ($presenter, string $lang) { + // afficher le template correspondant, par exemple ../templates/fr.latte + $template = $presenter->createTemplate() + ->setFile(__DIR__ . '/../templates/' . $lang . '.latte'); + return $template; +}); + +// lancer l'application ! +$container->getByType(Nette\Application\Application::class)->run(); +``` + +Tout le reste sera constitué de templates stockés dans le dossier parent `/templates`. + +Le code PHP dans `index.php` [prépare d'abord l'environnement |bootstrap:], puis définit les [routes |application:routing#Routage dynamique avec callbacks] et enfin lance l'application. L'avantage est que le deuxième paramètre de la fonction `addRoute()` peut être un callable, qui sera exécuté après l'ouverture de la page correspondante. + + +Pourquoi utiliser Nette pour un microsite ? +------------------------------------------- + +- Les programmeurs qui ont déjà essayé [Tracy|tracy:] ne peuvent plus imaginer programmer quoi que ce soit sans elle aujourd'hui. +- Mais surtout, vous utiliserez le système de templates [Latte|latte:], car dès 2 pages, vous voudrez avoir une [mise en page et un contenu|latte:template-inheritance] séparés. +- Et vous voulez absolument compter sur l'[échappement automatique |latte:safety-first] pour éviter la vulnérabilité XSS. +- Nette garantit également qu'en cas d'erreur, les messages d'erreur PHP destinés aux programmeurs ne s'afficheront jamais, mais une page compréhensible par l'utilisateur. +- Si vous souhaitez obtenir des retours d'utilisateurs, par exemple sous la forme d'un formulaire de contact, vous ajouterez également des [formulaires|forms:] et une [base de données|database:]. +- Vous pouvez également faire [envoyer par e-mail|mail:] facilement les formulaires remplis. +- Parfois, la [mise en cache|caching:] peut vous être utile, par exemple si vous téléchargez et affichez des flux. + +À notre époque, où la vitesse et l'efficacité sont essentielles, il est important de disposer d'outils qui vous permettent d'obtenir des résultats sans délai inutile. Le framework Nette vous offre exactement cela - un développement rapide, la sécurité et une large gamme d'outils, tels que Tracy et Latte, qui simplifient le processus. Il suffit d'installer quelques paquets Nette et construire un tel microsite devient soudain un jeu d'enfant. Et vous savez qu'aucune faille de sécurité ne se cache nulle part. diff --git a/best-practices/fr/pagination.texy b/best-practices/fr/pagination.texy index 9cb7e34af0..4c008577ae 100644 --- a/best-practices/fr/pagination.texy +++ b/best-practices/fr/pagination.texy @@ -1,17 +1,16 @@ -Pagination des résultats des bases de données -********************************************* +Pagination des résultats de la base de données +********************************************** .[perex] -Lorsque vous développez des applications Web, vous êtes souvent confronté à la nécessité d'imprimer un nombre restreint d'enregistrements sur une page. +Lors de la création d'applications web, vous rencontrerez très souvent la nécessité de limiter le nombre d'éléments affichés par page. -Nous sortons de cet état lorsque nous listons toutes les données sans pagination. Pour sélectionner les données dans la base de données, nous disposons de la classe ArticleRepository, qui contient le constructeur et la méthode `findPublishedArticles`, qui renvoie tous les articles publiés triés par ordre décroissant de date de publication. +Nous partons d'un état où nous affichons toutes les données sans pagination. Pour sélectionner les données de la base de données, nous avons une classe `ArticleRepository` qui, en plus du constructeur, contient une méthode `findPublishedArticles` qui renvoie tous les articles publiés triés par ordre décroissant de date de publication. ```php namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -31,10 +30,10 @@ class ArticleRepository } ``` -Dans le Presenter nous injectons ensuite la classe model et dans la méthode render nous allons demander les articles publiés que nous passons au template : +Dans le presenter, nous injectons ensuite la classe de modèle et dans la méthode render, nous demandons les articles publiés, que nous transmettons au template : ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -53,7 +52,7 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -Dans le modèle, nous allons nous occuper de rendre une liste d'articles : +Dans le template `default.latte`, nous nous occupons ensuite de l'affichage des articles : ```latte {block content} @@ -68,11 +67,11 @@ Dans le modèle, nous allons nous occuper de rendre une liste d'articles : ``` -De cette manière, nous pouvons écrire tous les articles, mais cela posera des problèmes lorsque le nombre d'articles augmentera. À ce moment-là, il sera utile d'implémenter le mécanisme de pagination. +De cette manière, nous savons afficher tous les articles, ce qui commencera cependant à poser problème lorsque le nombre d'articles augmentera. À ce moment-là, l'implémentation d'un mécanisme de pagination s'avérera utile. -Ainsi, tous les articles seront répartis sur plusieurs pages et nous n'afficherons que les articles d'une page courante. Le nombre total de pages et la répartition des articles sont calculés par [utils:Paginator] lui-même, en fonction du nombre d'articles que nous avons au total et du nombre d'articles que nous voulons afficher sur la page. +Celui-ci garantira que tous les articles sont répartis sur plusieurs pages et que nous n'affichons que les articles d'une page actuelle. Le nombre total de pages et la répartition des articles seront calculés par [Paginator |utils:paginator] lui-même en fonction du nombre total d'articles que nous avons et du nombre d'articles que nous voulons afficher par page. -Dans un premier temps, nous allons modifier la méthode d'obtention des articles dans la classe du référentiel pour ne retourner que les articles d'une seule page. Nous allons également ajouter une nouvelle méthode pour obtenir le nombre total d'articles dans la base de données, dont nous aurons besoin pour définir un Paginator : +Dans un premier temps, nous modifions la méthode d'obtention des articles dans la classe du repository afin qu'elle puisse nous renvoyer uniquement les articles d'une page. Nous ajoutons également une méthode pour connaître le nombre total d'articles dans la base de données, dont nous aurons besoin pour configurer le Paginator : ```php namespace App\Model; @@ -100,7 +99,7 @@ class ArticleRepository } /** - * Returns the total number of published articles + * Renvoie le nombre total d'articles publiés */ public function getPublishedArticlesCount(): int { @@ -109,12 +108,12 @@ class ArticleRepository } ``` -L'étape suivante consiste à modifier le présentateur. Nous allons transmettre le numéro de la page actuellement affichée à la méthode de rendu. Dans le cas où ce numéro ne fait pas partie de l'URL, nous devons définir la valeur par défaut sur la première page. +Ensuite, nous nous attaquons aux modifications du presenter. Dans la méthode render, nous transmettrons le numéro de la page actuellement affichée. Au cas où ce numéro ne ferait pas partie de l'URL, nous définirons la valeur par défaut de la première page. -Nous étendons également la méthode de rendu pour obtenir l'instance de Paginator, la configurer et sélectionner les bons articles à afficher dans le modèle. Le HomePresenter ressemblera à ceci : +Nous étendrons également la méthode render pour obtenir l'instance du Paginator, la configurer et sélectionner les bons articles à afficher dans le template. Le `HomePresenter` ressemblera à ceci après les modifications : ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -128,27 +127,27 @@ class HomePresenter extends Nette\Application\UI\Presenter public function renderDefault(int $page = 1): void { - // Nous allons trouver le nombre total d'articles publiés + // Nous obtenons le nombre total d'articles publiés $articlesCount = $this->articleRepository->getPublishedArticlesCount(); - // Nous allons créer l'instance de Paginator et la configurer. + // Nous fabriquons une instance de Paginator et la configurons $paginator = new Nette\Utils\Paginator; - $paginator->setItemCount($articlesCount); // compte total des articles - $paginator->setItemsPerPage(10); // articles par page - $paginator->setPage($page); // numéro de page actuel + $paginator->setItemCount($articlesCount); // nombre total d'articles + $paginator->setItemsPerPage(10); // nombre d'éléments par page + $paginator->setPage($page); // numéro de la page actuelle - // Nous allons trouver un ensemble limité d'articles dans la base de données en fonction des calculs de Paginator. + // Nous extrayons de la base de données un ensemble limité d'articles selon le calcul du Paginator $articles = $this->articleRepository->findPublishedArticles($paginator->getLength(), $paginator->getOffset()); - // que nous passons au modèle + // que nous transmettons au template $this->template->articles = $articles; - // et aussi à Paginator lui-même pour afficher les options de pagination + // et aussi le Paginator lui-même pour afficher les options de pagination $this->template->paginator = $paginator; } } ``` -Le modèle fait déjà défiler les articles sur une page, il suffit d'ajouter des liens de pagination : +Le template itère désormais uniquement sur les articles d'une seule page, il nous suffit d'ajouter les liens de pagination : ```latte {block content} @@ -163,34 +162,33 @@ Le modèle fait déjà défiler les articles sur une page, il suffit d'ajouter d <div class="pagination"> {if !$paginator->isFirst()} - <a n:href="default, 1">First</a> + <a n:href="default, 1">Première</a>  |  - <a n:href="default, $paginator->page-1">Previous</a> + <a n:href="default, $paginator->page - 1">Précédente</a>  |  {/if} - Page {$paginator->getPage()} of {$paginator->getPageCount()} + Page {$paginator->getPage()} sur {$paginator->getPageCount()} {if !$paginator->isLast()}  |  - <a n:href="default, $paginator->getPage() + 1">Next</a> + <a n:href="default, $paginator->getPage() + 1">Suivante</a>  |  - <a n:href="default, $paginator->getPageCount()">Last</a> + <a n:href="default, $paginator->getPageCount()">Dernière</a> {/if} </div> ``` -C'est ainsi que nous avons ajouté la pagination en utilisant Paginator. Si [Nette Database Explorer |database:explorer] est utilisé à la place de [Nette Database Core |database:core] comme couche de base de données, nous sommes capables d'implémenter la pagination même sans Paginator. La classe `Nette\Database\Table\Selection` contient la méthode [page |api:Nette\Database\Table\Selection::_ page] avec la logique de pagination prise dans le Paginator. +Nous avons ainsi complété la page avec la possibilité de pagination à l'aide du Paginator. Dans le cas où, au lieu de [Nette Database Core |database:sql-way] comme couche de base de données, nous utilisons [Nette Database Explorer |database:explorer], nous sommes capables d'implémenter la pagination i sans utiliser le Paginator. La classe `Nette\Database\Table\Selection` contient en effet une méthode [page() |api:Nette\Database\Table\Selection::page] avec la logique de pagination intégrée. -Le référentiel ressemblera à ceci : +Le repository ressemblera à ceci avec cette méthode d'implémentation : ```php namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -198,7 +196,6 @@ class ArticleRepository ) { } - public function findPublishedArticles(): Nette\Database\Table\Selection { return $this->database->table('articles') @@ -208,10 +205,10 @@ class ArticleRepository } ``` -Nous n'avons pas besoin de créer le Paginator dans le Presenter, à la place nous utiliserons la méthode de l'objet `Selection` retourné par le référentiel : +Dans le presenter, nous n'avons pas besoin de créer de Paginator, nous utilisons directement la méthode `page()` de la classe `Selection` que nous renvoie le repository : ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -225,21 +222,21 @@ class HomePresenter extends Nette\Application\UI\Presenter public function renderDefault(int $page = 1): void { - // Nous allons trouver les articles publiés + // Nous extrayons les articles publiés $articles = $this->articleRepository->findPublishedArticles(); - // et leur partie limitée par le calcul de la méthode de page que nous passerons au modèle + // et nous envoyons au template seulement une partie d'entre eux limitée selon le calcul de la méthode page $lastPage = 0; $this->template->articles = $articles->page($page, 10, $lastPage); - // et les données nécessaires pour afficher les options de pagination également + // et aussi les données nécessaires pour afficher les options de pagination $this->template->page = $page; $this->template->lastPage = $lastPage; } } ``` -Comme nous n'utilisons pas de Paginator, nous devons modifier la section montrant les liens de pagination : +Comme nous n'envoyons plus de Paginator au template, nous modifions la partie affichant les liens de pagination : ```latte {block content} @@ -254,24 +251,23 @@ Comme nous n'utilisons pas de Paginator, nous devons modifier la section montran <div class="pagination"> {if $page > 1} - <a n:href="default, 1">First</a> + <a n:href="default, 1">Première</a>  |  - <a n:href="default, $page - 1">Previous</a> + <a n:href="default, $page - 1">Précédente</a>  |  {/if} - Page {$page} of {$lastPage} + Page {$page} sur {$lastPage} {if $page < $lastPage}  |  - <a n:href="default, $page + 1">Next</a> + <a n:href="default, $page + 1">Suivante</a>  |  - <a n:href="default, $lastPage">Last</a> + <a n:href="default, $lastPage">Dernière</a> {/if} </div> ``` -De cette façon, nous avons mis en place un mécanisme de pagination sans utiliser de Paginator. +De cette manière, nous avons implémenté le mécanisme de pagination en utilisant la méthode `page()` de Nette Database Explorer. {{priority: -1}} -{{sitename: Meilleures pratiques}} diff --git a/best-practices/fr/passing-settings-to-presenters.texy b/best-practices/fr/passing-settings-to-presenters.texy index d37725ed9b..3aeba172c5 100644 --- a/best-practices/fr/passing-settings-to-presenters.texy +++ b/best-practices/fr/passing-settings-to-presenters.texy @@ -1,10 +1,10 @@ -Transmettre les paramètres aux présentateurs -******************************************** +Transmission des paramètres aux presenters +****************************************** .[perex] -Avez-vous besoin de transmettre aux présentateurs des arguments qui ne sont pas des objets (par exemple, des informations indiquant si le présentateur fonctionne en mode de débogage, des chemins de répertoire, etc.) et qui ne peuvent donc pas être transmis automatiquement par le câblage automatique ? La solution consiste à les encapsuler dans un objet `Settings`. +Avez-vous besoin de transmettre aux presenters des arguments qui ne sont pas des objets (par exemple, l'information s'ils s'exécutent en mode débogage, les chemins vers les répertoires, etc.) et qui ne peuvent donc pas être transmis automatiquement via l'autowiring ? La solution est de les encapsuler dans un objet `Settings`. -Le service `Settings` est un moyen très simple et utile de fournir aux présentateurs des informations sur une application en cours d'exécution. Sa forme spécifique dépend entièrement de vos besoins particuliers. Exemple : +Le service `Settings` représente une manière très simple et pourtant utile de fournir des informations sur l'application en cours d'exécution aux presenters. Sa forme concrète dépend entièrement de vos besoins spécifiques. Exemple : ```php namespace App; @@ -12,7 +12,7 @@ namespace App; class Settings { public function __construct( - // depuis PHP 8.1 il est possible de spécifier readonly + // à partir de PHP 8.1, il est possible d'indiquer readonly public bool $debugMode, public string $appDir, // et ainsi de suite @@ -20,7 +20,7 @@ class Settings } ``` -Exemple d'inscription à la configuration : +Exemple d'enregistrement dans la configuration : ```neon services: @@ -30,7 +30,7 @@ services: ) ``` -Lorsque le diffuseur a besoin des informations fournies par ce service, il lui suffit de les demander dans le constructeur : +Lorsque le presenter aura besoin des informations fournies par ce service, il les demandera simplement dans le constructeur : ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -47,5 +47,3 @@ class MyPresenter extends Nette\Application\UI\Presenter } } ``` - -{{sitename: Meilleures pratiques}} diff --git a/best-practices/fr/post-links.texy b/best-practices/fr/post-links.texy new file mode 100644 index 0000000000..d4c098524f --- /dev/null +++ b/best-practices/fr/post-links.texy @@ -0,0 +1,56 @@ +Comment utiliser correctement les liens POST +******************************************** + +.[perex] +Dans les applications web, en particulier dans les interfaces d'administration, une règle de base devrait être que les actions modifiant l'état du serveur ne devraient pas être effectuées via la méthode HTTP GET. Comme le nom de la méthode l'indique, GET devrait servir uniquement à obtenir des données, non à les modifier. Pour des actions telles que la suppression d'enregistrements, il est préférable d'utiliser la méthode POST. Bien que l'idéal serait la méthode DELETE, mais elle ne peut pas être invoquée sans JavaScript, c'est pourquoi POST est historiquement utilisé. + +Comment faire en pratique ? Utilisez cette astuce simple. Au début du template de votre layout, créez un formulaire auxiliaire avec l'identifiant `postForm`, que vous utiliserez ensuite pour les boutons de suppression : + +```latte .{file:@layout.latte} +<form method="post" id="postForm"></form> +``` + +Grâce à ce formulaire, vous pouvez utiliser un bouton `<button>` au lieu d'un lien classique `<a>`, qui peut être visuellement stylisé pour ressembler à un lien normal. Par exemple, le framework CSS Bootstrap propose les classes `btn btn-link` avec lesquelles vous obtiendrez que le bouton ne soit pas visuellement différent des autres liens. À l'aide de l'attribut `form="postForm"`, nous le lions au formulaire pré-préparé : + +```latte .{file:admin.latte} +<table> + <tr n:foreach="$posts as $post"> + <td>{$post->title}</td> + <td> + <button class="btn btn-link" form="postForm" formaction="{link delete $post->id}">supprimer</button> + <!-- au lieu de <a n:href="delete $post->id">supprimer</a> --> + </td> + </tr> +</table> +``` + +En cliquant sur le bouton, l'action `delete` est maintenant invoquée. Pour garantir que les requêtes ne soient acceptées que via la méthode POST et depuis le même domaine (ce qui est une défense efficace contre les attaques CSRF), utilisez l'attribut `#[Requires]` : + +```php .{file:AdminPresenter.php} +use Nette\Application\Attributes\Requires; + +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST', sameOrigin: true)] + public function actionDelete(int $id): void + { + $this->facade->deletePost($id); // code hypothétique supprimant l'enregistrement + $this->redirect('default'); + } +} +``` + +L'attribut existe depuis Nette Application 3.2 et vous en apprendrez plus sur ses possibilités sur la page [Comment utiliser l'attribut #Requires |attribute-requires]. + +Si vous utilisiez le signal `handleDelete()` au lieu de l'action `actionDelete()`, il n'est pas nécessaire d'indiquer `sameOrigin: true`, car les signaux ont cette protection définie implicitement : + +```php .{file:AdminPresenter.php} +#[Requires(methods: 'POST')] +public function handleDelete(int $id): void +{ + $this->facade->deletePost($id); + $this->redirect('this'); +} +``` + +Cette approche améliore non seulement la sécurité de votre application, mais contribue également au respect des normes et pratiques web correctes. En utilisant les méthodes POST pour les actions modifiant l'état, vous obtiendrez une application plus robuste et plus sûre. diff --git a/best-practices/fr/presenter-traits.texy b/best-practices/fr/presenter-traits.texy index ad30804c3c..551c1d5368 100644 --- a/best-practices/fr/presenter-traits.texy +++ b/best-practices/fr/presenter-traits.texy @@ -1,14 +1,14 @@ -Composer des présentateurs à partir de traits +Composition des presenters à partir de traits ********************************************* .[perex] -Si nous devons implémenter le même code dans plusieurs présentateurs (par exemple, vérifier que l'utilisateur est connecté), il est tentant de placer le code dans un ancêtre commun. La deuxième option consiste à créer des traits à usage unique. +Si nous avons besoin d'implémenter le même code dans plusieurs presenters (par exemple, vérifier si l'utilisateur est connecté), il est possible de placer le code dans un ancêtre commun. La deuxième option est de créer des [traits |nette:introduction-to-object-oriented-programming#Traits] à usage unique. -L'avantage de cette solution est que chaque présentateur peut utiliser uniquement les traits dont il a réellement besoin, alors que l'héritage multiple n'est pas possible en PHP. +L'avantage de cette solution est que chaque presenter peut utiliser exactement les traits dont il a réellement besoin, tandis que l'héritage multiple n'est pas possible en PHP. -Ces traits peuvent tirer parti du fait que toutes les [méthodes d'injection |inject-method-attribute#inject methods] sont appelées séquentiellement lors de la création du présentateur. Il suffit de s'assurer que le nom de chaque méthode d'injection est unique. +Ces traits peuvent tirer parti du fait que lors de la création du presenter, toutes les [méthodes inject |inject-method-attribute#Méthodes inject] sont appelées successivement. Il faut juste s'assurer que le nom de chaque méthode inject est unique. -Les traits peuvent accrocher le code d'initialisation dans les événements [onStartup ou onRender |application:presenters#Events]. +Les traits peuvent accrocher du code d'initialisation aux événements [onStartup ou onRender |application:presenters#Événements]. Exemples : @@ -36,7 +36,7 @@ trait StandardTemplateFilters } ``` -Le présentateur utilise alors simplement ces traits : +Le presenter utilise ensuite simplement ces traits : ```php class ArticlePresenter extends Nette\Application\UI\Presenter @@ -45,6 +45,3 @@ class ArticlePresenter extends Nette\Application\UI\Presenter use RequireLoggedUser; } ``` - - -{{sitename: Meilleures pratiques}} diff --git a/best-practices/fr/restore-request.texy b/best-practices/fr/restore-request.texy index a97f5eaab3..277e87d9ff 100644 --- a/best-practices/fr/restore-request.texy +++ b/best-practices/fr/restore-request.texy @@ -1,17 +1,16 @@ -Comment revenir à une page antérieure ? +Comment revenir à une page précédente ? *************************************** .[perex] -Que faire si un utilisateur remplit un formulaire et que son identifiant expire ? Pour éviter de perdre les données, nous sauvegardons les données dans la session avant de rediriger vers la page de connexion. Avec Nette, c'est un jeu d'enfant. +Que se passe-t-il si un utilisateur remplit un formulaire et que sa session expire ? Pour qu'il ne perde pas ses données, nous sauvegardons les données dans la session avant de le rediriger vers la page de connexion. Dans Nette, c'est un jeu d'enfant. -La requête en cours peut être stockée dans la session à l'aide de la méthode `storeRequest()`, qui renvoie son identifiant sous la forme d'une courte chaîne. La méthode stocke le nom du présentateur actuel, la vue et ses paramètres. -Si un formulaire a également été soumis, les valeurs des champs (sauf pour les fichiers téléchargés) sont également enregistrées. +La requête actuelle peut être sauvegardée dans la session à l'aide de la méthode `storeRequest()`, qui renvoie son identifiant sous forme de chaîne courte. La méthode sauvegarde le nom du presenter actuel, la vue et ses paramètres. Si un formulaire a également été soumis, le contenu des champs (à l'exception des fichiers téléchargés) est également sauvegardé. -La requête est restaurée par la méthode `restoreRequest($key)`, à laquelle nous passons l'identifiant récupéré. Cela redirige vers le présentateur et la vue d'origine. Toutefois, si la demande sauvegardée contient une soumission de formulaire, elle est redirigée vers le présentateur d'origine à l'aide de la méthode `forward()`, transmet les valeurs précédemment remplies au formulaire et le laisse se redessiner. Cela permet à l'utilisateur de soumettre à nouveau le formulaire et aucune donnée n'est perdue. +La restauration de la requête est effectuée par la méthode `restoreRequest($key)`, à laquelle nous passons l'identifiant obtenu. Elle redirige vers le presenter et la vue d'origine. Cependant, si la requête sauvegardée contient une soumission de formulaire, elle passe au presenter d'origine via la méthode `forward()`, transmet les valeurs précédemment remplies au formulaire et le laisse se rendre à nouveau. L'utilisateur a ainsi la possibilité de soumettre à nouveau le formulaire et aucune donnée n'est perdue. -Il est important de noter que `restoreRequest()` vérifie que l'utilisateur nouvellement connecté est le même que celui qui a rempli le formulaire à l'origine. Si ce n'est pas le cas, il rejette la demande et ne fait rien. +Il est important de noter que `restoreRequest()` vérifie si l'utilisateur nouvellement connecté est le même que celui qui a initialement rempli le formulaire. Si ce n'est pas le cas, elle rejette la requête et ne fait rien. -Démontrons tout cela à l'aide d'un exemple. Ayons un présentateur `AdminPresenter` dans lequel des données sont modifiées et dont la méthode `startup()` vérifie si l'utilisateur est connecté. S'il ne l'est pas, nous le redirigeons vers `SignPresenter`. En même temps, nous sauvegardons la requête en cours et envoyons sa clé à `SignPresenter`. +Illustrons tout cela par un exemple. Supposons que nous ayons un presenter `AdminPresenter`, dans lequel des données sont éditées et dans la méthode `startup()` duquel nous vérifions si l'utilisateur est connecté. S'il ne l'est pas, nous le redirigeons vers `SignPresenter`. En même temps, nous sauvegardons la requête actuelle et envoyons sa clé à `SignPresenter`. ```php class AdminPresenter extends Nette\Application\UI\Presenter @@ -27,7 +26,7 @@ class AdminPresenter extends Nette\Application\UI\Presenter } ``` -Le présentateur `SignPresenter` contiendra un paramètre persistant `$backlink` dans lequel la clé est écrite, en plus du formulaire de connexion. Comme le paramètre est persistant, il sera conservé même après l'envoi du formulaire de connexion. +Le presenter `SignPresenter` contiendra, en plus du formulaire de connexion, un paramètre persistant `$backlink`, dans lequel la clé sera écrite. Comme le paramètre est persistant, il sera également transmis après la soumission du formulaire de connexion. ```php @@ -48,7 +47,7 @@ class SignPresenter extends Nette\Application\UI\Presenter public function signInFormSubmitted($form) { - // ... ici nous signons l'utilisateur ... + // ... ici, nous connectons l'utilisateur ... $this->restoreRequest($this->backlink); $this->redirect('Admin:'); @@ -56,9 +55,8 @@ class SignPresenter extends Nette\Application\UI\Presenter } ``` -Nous passons la clé de la requête sauvegardée à la méthode `restoreRequest()` et celle-ci redirige (ou transmet) vers le présentateur d'origine. +Nous passons la clé de la requête sauvegardée à la méthode `restoreRequest()` et elle redirige (ou avance) vers le presenter d'origine. -Toutefois, si la clé n'est pas valide (par exemple, si elle n'existe plus dans la session), la méthode ne fait rien. L'appel suivant est donc `$this->redirect('Admin:')`, qui redirige vers `AdminPresenter`. +Cependant, si la clé n'est pas valide (par exemple, elle n'existe plus dans la session), la méthode ne fait rien. L'appel `$this->redirect('Admin:')` suit donc, qui redirige vers `AdminPresenter`. {{priority: -1}} -{{sitename: Meilleures pratiques}} diff --git a/best-practices/hu/@home.texy b/best-practices/hu/@home.texy index 5d9c323f64..e1ab1cabd8 100644 --- a/best-practices/hu/@home.texy +++ b/best-practices/hu/@home.texy @@ -1,57 +1,61 @@ -Legjobb gyakorlatok -******************* +Útmutatók és eljárások +********************** .[perex] -Oktatóanyagok, megoldások gyakori problémákra és legjobb gyakorlatok a Nette számára. +Útmutatók, gyakori feladatok megoldásai és *best practices* a Nette-hez. <div class=documentation> <div> -Nette alkalmazás ----------------- -- [Injektáló módszerek és attribútumok |inject-method-attribute] -- [Bemutatók összeállítása tulajdonságokból |presenter-traits] -- [Beállítások átadása prezentereknek |passing-settings-to-presenters] +Nette Alkalmazások +------------------ +- [Inject metódusok és attribútumok |inject-method-attribute] +- [Presenterek összeállítása trait-ekből |presenter-traits] +- [Beállítások átadása presentereknek |passing-settings-to-presenters] - [Hogyan térjünk vissza egy korábbi oldalra |restore-request] -- [Adatbázisok eredményeinek lapozgatása |Pagination] -- [Dinamikus snippetek |dynamic-snippets] +- [Adatbázis eredmények lapozása |pagination] +- [Dinamikus snippettek |dynamic-snippets] +- [Hogyan használjuk a #Requires attribútumot |attribute-requires] +- [Hogyan használjuk helyesen a POST linkeket |post-links] </div> <div> -Nyomtatványok -------------- -- [Nyomtatványok újrafelhasználása |form-reuse] -- [Nyomtatvány rekord létrehozásához és szerkesztéséhez |creating-editing-form] -- [Hozzunk létre egy kapcsolatfelvételi űrlapot |lets-create-contact-form] -- [Függő kiválasztó mezők |https://blog.nette.org/hu/fueggo-selectboxok-elegansan-nette-es-tiszta-js-ben] +Űrlapok +------- +- [Űrlapok újrafelhasználása |form-reuse] +- [Űrlap rekord létrehozásához és szerkesztéséhez |creating-editing-form] +- [Készítsünk kapcsolatfelvételi űrlapot |lets-create-contact-form] +- [Függő selectboxok |https://blog.nette.org/hu/dependent-selectboxes-elegantly-in-nette-and-pure-js] </div> <div> -Közös ------ -- [Konfigurációs fájl betöltése |bootstrap:] -- [Miért használ a Nette PascalCase konstans jelölést? |https://blog.nette.org/hu/kevesebb-sikolyert-a-kodban] -- [Miért nem használja a Nette az Interface utótagot? |https://blog.nette.org/hu/az-elotagok-es-utotagok-nem-tartoznak-az-interfesznevekbe] -- [Composer használati tippek |composer] -- [Tippek a szerkesztőkhöz és eszközökhöz |editors-and-tools] +Általános +--------- +- [Hogyan töltsünk be egy konfigurációs fájlt |bootstrap:] +- [Hogyan írjunk mikro-weboldalakat |microsites] +- [Miért használja a Nette a PascalCase konstans jelölést? |https://blog.nette.org/hu/for-less-screaming-in-the-code] +- [Miért nem használja a Nette az Interface utótagot? |https://blog.nette.org/hu/prefixes-and-suffixes-do-not-belong-in-interface-names] +- [Composer: használati tippek |composer] +- [Tippek szerkesztőkhöz & eszközökhöz |editors-and-tools] +- [Bevezetés az objektumorientált programozásba |nette:introduction-to-object-oriented-programming] </div> <div> -Minta megoldás --------------- -- [Nette példák |https://github.com/nette-examples] -- [Doktrína és Nette |https://contributte.org/nettrine/] -- [Contributte példák |https://contributte.org/examples.html] -- [Doctrine ORM weboldal |https://github.com/MinecordNetwork/Website] -- [Gyors kezdés |quickstart:] +Példa megoldások +---------------- +- [Nette examples |https://github.com/nette-examples] +- [Doctrine & Nette |https://contributte.org/nettrine/] +- [Contributte examples |https://contributte.org/examples.html] +- [Doctrine ORM Website |https://github.com/MinecordNetwork/Website] +- [Quick start |quickstart:] </div> <div> @@ -59,10 +63,7 @@ Minta megoldás Videók ------ -A "Nette Framework YouTube-csatornáján":https://www.youtube.com/user/NetteFramework több száz pozobotai felvételt és Nette-ről szóló videót találhat egy fedél alatt. +Több száz felvétel a Poslední sobota eseményekről és Nette videók egy helyen a "Nette Framework Youtube csatornáján":https://www.youtube.com/user/NetteFramework. </div> </div> - -{{sitename: Legjobb gyakorlatok}} -{{leftbar: www:@menu-common}} diff --git a/best-practices/hu/@meta.texy b/best-practices/hu/@meta.texy new file mode 100644 index 0000000000..9a70856e97 --- /dev/null +++ b/best-practices/hu/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Útmutatók és eljárások}} +{{leftbar: www:@menu-common}} diff --git a/best-practices/hu/attribute-requires.texy b/best-practices/hu/attribute-requires.texy new file mode 100644 index 0000000000..819f823943 --- /dev/null +++ b/best-practices/hu/attribute-requires.texy @@ -0,0 +1,177 @@ +Hogyan használjuk a `#[Requires]` attribútumot +********************************************** + +.[perex] +Amikor webalkalmazást ír, gyakran találkozik azzal az igénnyel, hogy korlátozza a hozzáférést az alkalmazás bizonyos részeihez. Talán azt szeretné, hogy bizonyos kérések csak űrlapon keresztül küldhessenek adatokat (azaz POST metódussal), vagy hogy csak AJAX hívások számára legyenek elérhetők. A Nette Framework 3.2-ben megjelent egy új eszköz, amely lehetővé teszi az ilyen korlátozások nagyon elegáns és áttekinthető beállítását: a `#[Requires]` attribútum. + +Az attribútum egy speciális jelölés a PHP-ban, amelyet az osztály vagy metódus definíciója elé adunk hozzá. Mivel valójában egy osztályról van szó, ahhoz, hogy a következő példák működjenek, meg kell adni a use klauzult: + +```php +use Nette\Application\Attributes\Requires; +``` + +A `#[Requires]` attribútumot használhatja magánál a presenter osztálynál és ezeknél a metódusoknál is: + +- `action<Action>()` +- `render<View>()` +- `handle<Signal>()` +- `createComponent<Name>()` + +Az utolsó két metódus a komponensekre is vonatkozik, tehát az attribútumot náluk is használhatja. + +Ha az attribútum által megadott feltételek nem teljesülnek, HTTP 4xx hiba váltódik ki. + + +HTTP metódusok +-------------- + +Megadhatja, hogy mely HTTP metódusok (mint GET, POST stb.) engedélyezettek a hozzáféréshez. Például, ha csak űrlapküldéssel szeretné engedélyezni a hozzáférést, állítsa be: + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +Miért kellene POST-ot használnia GET helyett az állapotot megváltoztató akciókhoz, és hogyan tegye ezt? [Olvassa el az útmutatót |post-links]. + +Megadhat egy metódust vagy metódusok tömbjét. Speciális eset a `'*'` érték, amely minden metódust engedélyez, amit a presenterek [biztonsági okokból |application:presenters#HTTP metódus ellenőrzése] alapértelmezés szerint nem engednek meg. + + +AJAX hívás +---------- + +Ha azt szeretné, hogy a presenter vagy metódus csak AJAX kérések számára legyen elérhető, használja: + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Azonos eredet +------------- + +A biztonság növelése érdekében megkövetelheti, hogy a kérés ugyanarról a domainről érkezzen. Ezzel megakadályozhatja a [CSRF sebezhetőséget |nette:vulnerability-protection#Cross-Site Request Forgery CSRF]: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +A `handle<Signal>()` metódusoknál az azonos domainről való hozzáférés automatikusan megkövetelt. Tehát ha fordítva, bármely domainről szeretné engedélyezni a hozzáférést, adja meg: + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Hozzáférés forwardon keresztül +------------------------------ + +Néha hasznos korlátozni a presenterhez való hozzáférést úgy, hogy csak közvetve legyen elérhető, például a `forward()` vagy `switch()` metódus használatával egy másik presenterből. Így védik például az error-presentereket, hogy ne lehessen őket URL-ből meghívni: + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +A gyakorlatban gyakran szükség van bizonyos view-k megjelölésére, amelyekhez csak a presenter logikája alapján lehet eljutni. Tehát ismét, hogy ne lehessen őket közvetlenül megnyitni: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = $this->facade->getProduct($id); + if (!$product) { + $this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Konkrét akciók +-------------- + +Korlátozhatja azt is, hogy egy bizonyos kód, például egy komponens létrehozása, csak specifikus akciókhoz legyen elérhető a presenterben: + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +Egyetlen akció esetén nem szükséges tömböt írni: `#[Requires(actions: 'default')]` + + +Saját attribútumok +------------------ + +Ha a `#[Requires]` attribútumot ismételten ugyanazzal a beállítással szeretné használni, létrehozhat saját attribútumot, amely örökli a `#[Requires]`-t, és az igényeknek megfelelően állítja be. + +Például a `#[SingleAction]` csak a `default` akción keresztül engedélyezi a hozzáférést: + +```php +#[\Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Vagy a `#[RestMethods]` engedélyezi a hozzáférést az összes REST API-hoz használt HTTP metóduson keresztül: + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Következtetés +------------- + +A `#[Requires]` attribútum nagy rugalmasságot és kontrollt ad Önnek afölött, hogyan érhetők el a weboldalai. Egyszerű, de erőteljes szabályok segítségével növelheti alkalmazása biztonságát és helyes működését. Mint láthatja, az attribútumok használata a Nette-ben nemcsak megkönnyítheti a munkáját, hanem biztonságosabbá is teheti. diff --git a/best-practices/hu/composer.texy b/best-practices/hu/composer.texy index a4159ed069..97d7faa75c 100644 --- a/best-practices/hu/composer.texy +++ b/best-practices/hu/composer.texy @@ -1,44 +1,44 @@ -Composer használati tippek -************************** +Composer: tippek a használathoz +******************************* <div class=perex> -A Composer egy függőségkezelő eszköz PHP-ban. Lehetővé teszi, hogy bejelentse, hogy a projektje mely könyvtáraktól függ, és a program telepíti és frissíti azokat Ön helyett. Megtanuljuk: +A Composer egy eszköz a PHP függőségek kezelésére. Lehetővé teszi számunkra, hogy felsoroljuk azokat a könyvtárakat, amelyektől a projektünk függ, és telepíti és frissíti őket helyettünk. Megmutatjuk: -- hogyan kell telepíteni a Composert -- hogyan használjuk új vagy meglévő projektben +- hogyan telepítsük a Composert +- használatát új vagy meglévő projektben </div> -Telepítés .[#toc-installation] -============================== +Telepítés +========= -A Composer egy futtatható `.phar` fájl, amelyet az alábbiak szerint tölthet le és telepíthet. +A Composer egy futtatható `.phar` fájl, amelyet a következő módon tölthet le és telepíthet: -Windows .[#toc-windows] ------------------------ +Windows +------- -Használja a [Composer-Setup.exe |https://getcomposer.org/Composer-Setup.exe] hivatalos telepítő programot. +Használja a hivatalos telepítőt [Composer-Setup.exe |https://getcomposer.org/Composer-Setup.exe]. -Linux, macOS .[#toc-linux-macos] --------------------------------- +Linux, macOS +------------ -Mindössze 4 parancsra van szükséged, amelyeket [erről az oldalról |https://getcomposer.org/download/] másolhatsz le. +Csak 4 parancsra van szükség, amelyeket másoljon le [erről az oldalról |https://getcomposer.org/download/]. -Továbbá, a rendszerben található `PATH` mappába másolva a Composer globálisan elérhetővé válik: +Továbbá, ha egy olyan mappába helyezi, amely a rendszer `PATH`-jában van, a Composer globálisan elérhetővé válik: ```shell -$ mv ./composer.phar ~/bin/composer # or /usr/local/bin/composer +$ mv ./composer.phar ~/bin/composer # vagy /usr/local/bin/composer ``` -Használat a projektben .[#toc-use-in-project] -============================================= +Használat a projektben +====================== -A Composer projektben való használatának megkezdéséhez mindössze egy `composer.json` fájlra van szüksége. Ez a fájl leírja a projekt függőségeit, és egyéb metaadatokat is tartalmazhat. A legegyszerűbb `composer.json` így nézhet ki: +Ahhoz, hogy a projektünkben elkezdhessük használni a Composert, csak egy `composer.json` fájlra van szükségünk. Ez leírja a projektünk függőségeit, és tartalmazhat további metaadatokat is. Egy alap `composer.json` tehát így nézhet ki: ```js { @@ -48,17 +48,17 @@ A Composer projektben való használatának megkezdéséhez mindössze egy `comp } ``` -Itt azt mondjuk, hogy az alkalmazásunk (vagy könyvtárunk) függ a `nette/database` csomagtól (a csomag neve egy gyártó nevéből és a projekt nevéből áll), és azt a verziót szeretné, amelyik megfelel a `^3.0` verziókövetelménynek. +Itt azt mondjuk, hogy az alkalmazásunk (vagy könyvtárunk) megköveteli a `nette/database` csomagot (a csomag neve a szervezet nevéből és a projekt nevéből áll), és olyan verziót szeretne, amely megfelel a `^3.0` feltételnek (azaz a legújabb 3-as verziót). -Tehát, amikor a `composer.json` fájl a projekt gyökerében van, és futtatjuk a: +Tehát a projekt gyökerében van egy `composer.json` fájlunk, és elindítjuk a telepítést: ```shell composer update ``` -Composer letölti a Nette adatbázist a `vendor` könyvtárba. Létrehoz egy `composer.lock` fájlt is, amely információt tartalmaz arról, hogy pontosan milyen könyvtárverziókat telepített. +A Composer letölti a Nette Database-t a `vendor/` mappába. Továbbá létrehoz egy `composer.lock` fájlt, amely információkat tartalmaz arról, hogy pontosan melyik verziójú könyvtárakat telepítette. -A Composer létrehoz egy `vendor/autoload.php` fájlt. Ezt a fájlt egyszerűen beillesztheti, és minden további munka nélkül elkezdheti használni azokat az osztályokat, amelyeket ezek a könyvtárak biztosítanak: +A Composer generál egy `vendor/autoload.php` fájlt, amelyet egyszerűen includálhatunk, és elkezdhetjük használni a könyvtárakat bármilyen további munka nélkül: ```php require __DIR__ . '/vendor/autoload.php'; @@ -67,52 +67,52 @@ $db = new Nette\Database\Connection('sqlite::memory:'); ``` -Csomagok frissítése a legújabb verziókra .[#toc-update-packages-to-the-latest-versions] -======================================================================================= +Csomagok frissítése a legújabb verziókra +======================================== -Az összes használt csomag frissítéséhez a `composer.json` pontban meghatározott verziókövetelményeknek megfelelően a `composer update` paranccsal frissítheti az összes használt csomagot a legújabb verzióra. Például a `"nette/database": "^3.0"` függőség esetében a parancs a legújabb, 3.x.x.x verziót fogja telepíteni, de a 4-es verziót nem. +A használt könyvtárak frissítését a `composer.json`-ban definiált feltételek szerinti legújabb verziókra a `composer update` parancs végzi. Pl. a `"nette/database": "^3.0"` függőségnél a legújabb 3.x.x verziót telepíti, de a 4-es verziót már nem. -A `composer.json` fájlban lévő verziókövetelmények frissítéséhez pl. `"nette/database": "^4.1"`, a legújabb verzió telepítésének lehetővé tételéhez használja a `composer require nette/database` parancsot. +A `composer.json` fájlban lévő feltételek frissítéséhez, például `"nette/database": "^4.1"`-re, hogy telepíthető legyen a legújabb verzió, használja a `composer require nette/database` parancsot. -Az összes használt Nette csomag frissítéséhez az összeset fel kell sorolni a parancssorban, pl: +Az összes használt Nette csomag frissítéséhez mindet fel kellene sorolni a parancssorban, pl.: ```shell composer require nette/application nette/forms latte/latte tracy/tracy ... ``` -Ami nem praktikus. Ezért használjon egy egyszerű "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff szkriptet, amely megteszi ezt Ön helyett: +Ami nem praktikus. Használja ezért az egyszerű "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff szkriptet, amely ezt megteszi Ön helyett: ```shell php composer-frontline.php ``` -Új projekt létrehozása .[#toc-creating-new-project] -=================================================== +Új projekt létrehozása +====================== -Új Nette projekt egy egyszerű parancs végrehajtásával hozható létre: +Új Nette projektet egyetlen paranccsal hozhat létre: ```shell -composer create-project nette/web-project name-of-the-project +composer create-project nette/web-project projekt-neve ``` -A `name-of-the-project` helyett meg kell adnia a projekt könyvtárának nevét, és végre kell hajtania a parancsot. A Composer le fogja hívni a `nette/web-project` tárolót a GitHubról, amely már tartalmazza a `composer.json` fájlt, és rögtön ezután telepíti magát a Nette keretrendszert. Már csak a `temp/` és a `log/` könyvtárak [írási jogosultságainak ellenőrzése |nette:troubleshooting#setting-directory-permissions] van hátra, és már mehet is. +A `projekt-neve` helyére illessze be a projekt könyvtárának nevét, és erősítse meg. A Composer letölti a `nette/web-project` repository-t a GitHubról, amely már tartalmazza a `composer.json` fájlt, és rögtön utána a Nette Frameworköt. Már csak a [jogosultságokat kell beállítani |nette:troubleshooting#Könyvtárjogosultságok beállítása] a `temp/` és `log/` mappákra való íráshoz, és a projektnek életre kell kelnie. -Ha tudod, hogy a PHP melyik verzióján lesz a projekt, mindenképpen [állítsd be |#PHP Version]. +Ha tudja, milyen PHP verzióval fog futni a projekt a hostingen, ne felejtse el [beállítani |#PHP verzió]. -PHP verzió .[#toc-php-version] -============================== +PHP verzió +========== -A Composer mindig a csomagok azon verzióit telepíti, amelyek kompatibilisek a PHP aktuálisan használt verziójával (vagy inkább a PHP-nak a parancssorban a Composer futtatásakor használt verziójával). Ami valószínűleg nem ugyanaz a verzió, mint amit a webtárhelye használ. Ezért nagyon fontos, hogy a `composer.json` fájlodhoz hozzáadj információt a tárhelyeden használt PHP verziójáról. Ezután csak a tárhelyével kompatibilis csomagok verziói fognak települni. +A Composer mindig azokat a csomagverziókat telepíti, amelyek kompatibilisek az Ön által éppen használt PHP verzióval (pontosabban a parancssorban a Composer futtatásakor használt PHP verzióval). Ami azonban valószínűleg nem ugyanaz a verzió, mint amit a hostingja használ. Ezért nagyon fontos, hogy a `composer.json` fájlba hozzáadja az információt a hostingen lévő PHP verzióról. Ezután csak a hostinggal kompatibilis csomagverziók kerülnek telepítésre. -Például, ha a projektet a PHP 8.2.3-as verzióján szeretné futtatni, használja a következő parancsot: +Azt, hogy a projekt például PHP 8.2.3-on fog futni, a következő paranccsal állítjuk be: ```shell composer config platform.php 8.2.3 ``` -Így íródik a verzió a `composer.json` fájlba: +Így a verzió beíródik a `composer.json` fájlba: ```js { @@ -124,8 +124,7 @@ composer config platform.php 8.2.3 } ``` -A PHP verziószáma azonban a fájlban máshol is szerepel, a `require` szakaszban. Míg az első szám azt a verziót adja meg, amelyhez a csomagokat telepíteni kell, addig a második szám azt mondja meg, hogy maga az alkalmazás milyen verzióra íródott. -(Természetesen nincs értelme, hogy ezek a verziók különbözőek legyenek, így a dupla bejegyzés csak redundancia.) Ezt a verziót a paranccsal állítjuk be: +Azonban a PHP verziószám a fájl egy másik helyén is szerepel, mégpedig a `require` szekcióban. Míg az első szám azt határozza meg, hogy melyik verzióhoz települjenek a csomagok, a második szám azt mondja meg, hogy melyik verzióhoz íródott maga az alkalmazás. És például a PhpStorm ez alapján állítja be a *PHP language level*-t. (Természetesen nincs értelme, hogy ezek a verziók eltérjenek, tehát a kettős beírás egy átgondolatlanság.) Ezt a verziót a következő paranccsal állíthatja be: ```shell composer require php 8.2.3 --no-update @@ -142,66 +141,72 @@ Vagy közvetlenül a `composer.json` fájlban: ``` -Hamis jelentések .[#toc-false-reports] -====================================== +PHP verzió figyelmen kívül hagyása +================================== + +A csomagok általában megadják mind a legalacsonyabb PHP verziót, amellyel kompatibilisek, mind a legmagasabbat, amellyel tesztelve vannak. Ha még újabb PHP verziót tervez használni, például tesztelés céljából, a Composer megtagadja az ilyen csomag telepítését. A megoldás az `--ignore-platform-req=php+` opció, amely miatt a Composer figyelmen kívül hagyja a megkövetelt PHP verzió felső határait. -A csomagok frissítésekor vagy a verziószámok megváltoztatásakor konfliktusok fordulnak elő. Az egyik csomagnak vannak olyan követelményei, amelyek ütköznek egy másikkal, és így tovább. A Composer azonban időnként hamis üzeneteket ír ki. Olyan konfliktust jelent, amely valójában nem létezik. Ebben az esetben segít, ha törli a `composer.lock` fájlt, és újra megpróbálja. -Ha a hibaüzenet továbbra is fennáll, akkor komolyan gondolja, és ki kell olvasni belőle, hogy mit és hogyan kell módosítani. +Hamis jelentések +================ +Csomagok frissítésekor vagy verziószámok változásakor előfordul, hogy konfliktus lép fel. Egy csomag olyan követelményekkel rendelkezik, amelyek ellentmondanak egy másiknak, és így tovább. A Composer azonban néha hamis jelentést ad. Olyan konfliktust jelez, amely valójában nem létezik. Ilyen esetben segít a `composer.lock` fájl törlése és az újrapróbálkozás. -Packagist.org - Globális tárolóhely .[#toc-packagist-org-global-repository] -=========================================================================== +Ha a hibaüzenet továbbra is fennáll, akkor komolyan kell venni, és ki kell olvasni belőle, mit és hogyan kell módosítani. -[Packagist |https://packagist.org] a fő csomagtároló, amelyben a Composer megpróbál csomagokat keresni, ha másképp nem szólnak neki. Saját csomagjaidat is közzéteheted itt. +Packagist.org - központi repository +=================================== -Mi van akkor, ha nem akarjuk a központi tárolót .[#toc-what-if-we-don-t-want-the-central-repository] ----------------------------------------------------------------------------------------------------- +A [Packagist |https://packagist.org] a fő repository, amelyben a Composer megpróbálja megkeresni a csomagokat, hacsak nem mondjuk neki másképp. Itt publikálhatunk saját csomagokat is. -Ha vannak olyan belső alkalmazásaink vagy könyvtáraink a vállalatunkban, amelyeket nem lehet nyilvánosan a Packagist-en hosztolni, akkor létrehozhatunk saját tárolókat az adott projekthez. -A tárolókról bővebben [a hivatalos dokumentációban |https://getcomposer.org/doc/05-repositories.md#repositories]. +Mi van, ha nem akarjuk használni a központi repository-t? +--------------------------------------------------------- +Ha belső vállalati alkalmazásaink vannak, amelyeket egyszerűen nem hostolhatunk nyilvánosan, akkor létrehozunk hozzájuk egy vállalati repository-t. -Automatikus betöltés .[#toc-autoloading] -======================================== +Több információ a repository-król [a hivatalos dokumentációban |https://getcomposer.org/doc/05-repositories.md#repositories]. -A Composer egyik legfontosabb jellemzője, hogy automatikus betöltést biztosít minden általa telepített osztály számára, amit a `vendor/autoload.php` fájl beillesztésével indíthat el. -Lehetőség van azonban arra is, hogy a Composer segítségével a `vendor` mappán kívül más osztályokat is betöltsön. Az első lehetőség, hogy a Composer átvizsgálja a meghatározott mappákat és almappákat, megkeresi az összes osztályt, és felveszi őket az automatikus betöltőbe. Ehhez állítsa be a `autoload > classmap` címet a `composer.json`: +Autoloading +=========== + +A Composer alapvető tulajdonsága, hogy autoloadingot biztosít az összes általa telepített osztályhoz, amelyet a `vendor/autoload.php` fájl includálásával indíthat el. + +Azonban a Composert lehet használni további osztályok betöltésére is a `vendor` mappán kívül. Az első lehetőség az, hogy hagyjuk a Composert átkutatni a definiált mappákat és almappákat, megtalálni az összes osztályt, és bevenni őket az autoloaderbe. Ezt a `composer.json` `autoload > classmap` beállításával érhetjük el: ```js { "autoload": { "classmap": [ - "src/", # includes the src/ folder and its subfolders + "src/", # beleveszi a src/ mappát és annak almappáit ] } } ``` -Ezt követően minden egyes változtatásnál el kell indítani a `composer dumpautoload` parancsot, és hagyni kell, hogy az autoloading táblák újratermelődjenek. Ez rendkívül kényelmetlen, és sokkal jobb, ha ezt a feladatot a [RobotLoaderre |robot-loader:] bízzuk, amely a háttérben automatikusan és sokkal gyorsabban végzi el ugyanezt a tevékenységet. +Ezután minden változáskor futtatni kell a `composer dumpautoload` parancsot, és hagyni kell az autoloading táblák újragenerálását. Ez rendkívül kényelmetlen, és sokkal jobb ezt a feladatot a [RobotLoaderra|robot-loader:] bízni, amely ugyanazt a tevékenységet automatikusan a háttérben és sokkal gyorsabban végzi. -A második lehetőség a [PSR-4 |https://www.php-fig.org/psr/psr-4/] követése. Egyszerűen fogalmazva, ez egy olyan rendszer, ahol a névterek és az osztálynevek megfelelnek a könyvtárszerkezetnek és a fájlneveknek, azaz a `App\Router\RouterFactory` a `/path/to/App/Router/RouterFactory.php` fájlban található. Konfigurációs példa: +A második lehetőség a [PSR-4|https://www.php-fig.org/psr/psr-4/] betartása. Egyszerűsítve ez egy olyan rendszer, ahol a névterek és osztálynevek megfelelnek a könyvtárstruktúrának és a fájlneveknek, tehát pl. az `App\Core\RouterFactory` az `/path/to/App/Core/RouterFactory.php` fájlban lesz. Példa konfiguráció: ```js { "autoload": { "psr-4": { - "App\\": "app/" # the App\ namespace is in the app/ directory + "App\\": "app/" # az App\ névtér az app/ könyvtárban van } } } ``` -Lásd a [Composer dokumentációban |https://getcomposer.org/doc/04-schema.md#psr-4], hogy pontosan hogyan kell konfigurálni ezt a viselkedést. +Hogyan konfigurálja pontosan a viselkedést, megtudhatja a [Composer dokumentációjában|https://getcomposer.org/doc/04-schema.md#psr-4]. -Új verziók tesztelése .[#toc-testing-new-versions] -================================================== +Új verziók tesztelése +===================== -Egy csomag új fejlesztői verzióját szeretné tesztelni. Hogyan kell ezt megtenni? Először is, add hozzá ezt az opciós párost a `composer.json` fájlhoz, amely lehetővé teszi a csomagok fejlesztői verzióinak telepítését, de csak akkor, ha nincs olyan stabil verzió-kombináció, amely megfelel a követelményeknek: +Szeretné tesztelni egy csomag új fejlesztői verzióját. Hogyan tegye? Először adja hozzá ezt a két opciót a `composer.json` fájlhoz, amely lehetővé teszi a fejlesztői verziójú csomagok telepítését, de csak akkor folyamodik ehhez, ha nincs olyan stabil verziókombináció, amely megfelelne a követelményeknek: ```js { @@ -210,34 +215,33 @@ Egy csomag új fejlesztői verzióját szeretné tesztelni. Hogyan kell ezt megt } ``` -Javasoljuk a `composer.lock` fájl törlését is, mert néha a Composer érthetetlen módon megtagadja a telepítést, és ez megoldja a problémát. +Továbbá javasoljuk a `composer.lock` fájl törlését, néha ugyanis a Composer érthetetlen módon megtagadja a telepítést, és ez megoldja a problémát. -Tegyük fel, hogy a csomag a `nette/utils` és az új verzió a 4.0. Telepítjük a következő paranccsal: +Tegyük fel, hogy a `nette/utils` csomagról van szó, és az új verzió száma 4.0. Telepítse a következő paranccsal: ```shell composer require nette/utils:4.0.x-dev ``` -Vagy telepíthetsz egy adott verziót, például a 4.0.0-RC2-t: +Vagy telepíthet konkrét verziót is, például 4.0.0-RC2: ```shell composer require nette/utils:4.0.0-RC2 ``` -Ha egy másik csomag függ a könyvtártól, és egy régebbi verzióhoz van kötve (pl. `^3.1`), akkor ideális frissíteni a csomagot, hogy az új verzióval működjön. -Ha azonban csak meg akarja kerülni a korlátozást, és arra akarja kényszeríteni a Composert, hogy telepítse a fejlesztői verziót, és úgy tegyen, mintha az egy régebbi verzió lenne (pl. 3.1.6), akkor használhatja a `as` kulcsszót: +Ha azonban a könyvtártól egy másik csomag függ, amely egy régebbi verzióra van zárolva (pl. `^3.1`), akkor ideális a csomagot frissíteni, hogy az új verzióval működjön. Ha azonban csak meg akarja kerülni a korlátozást, és rávenni a Composert, hogy telepítse a fejlesztői verziót, és úgy tegyen, mintha egy régebbi verzió lenne (pl. 3.1.6), használhatja az `as` kulcsszót: ```shell composer require nette/utils "4.0.x-dev as 3.1.6" ``` -Parancsok hívása .[#toc-calling-commands] -========================================= +Parancsok hívása +================ -Saját egyéni parancsait és szkriptjeit a Composeren keresztül úgy hívhatja meg, mintha azok natív Composer-parancsok lennének. A `vendor/bin` mappában található szkripteknek nem kell megadniuk ezt a mappát. +A Composer segítségével saját előre elkészített parancsokat és szkripteket hívhat meg, mintha natív Composer parancsok lennének. A `vendor/bin` mappában található szkriptek esetében nem kell ezt a mappát megadni. -Példaként egy olyan szkriptet definiálunk a `composer.json` fájlban, amely a [Nette Tester-t |tester:] használja a tesztek futtatására: +Példaként definiálunk a `composer.json` fájlban egy szkriptet, amely a [Nette Testerrel|tester:] futtatja a teszteket: ```js { @@ -247,34 +251,32 @@ Példaként egy olyan szkriptet definiálunk a `composer.json` fájlban, amely a } ``` -Ezután a teszteket a `composer tester` segítségével futtatjuk. A parancsot akkor is meg tudjuk hívni, ha nem a projekt gyökérmappájában vagyunk, hanem egy alkönyvtárban. +A teszteket ezután a `composer tester` segítségével futtatjuk. A parancsot akkor is meghívhatjuk, ha nem a projekt gyökérkönyvtárában vagyunk, hanem valamelyik alkönyvtárban. -Köszönet küldése .[#toc-send-thanks] -==================================== +Küldjön köszönetet +================== -Mutatunk egy trükköt, aminek a nyílt forráskódú szerzők örülni fognak. A GitHubon könnyen adhatsz csillagot azoknak a könyvtáraknak, amelyeket a projekted használ. Csak telepítsd a `symfony/thanks` könyvtárat: +Mutatunk egy trükköt, amellyel örömet szerezhet az open source szerzőknek. Egyszerű módon adhat csillagot a GitHubon azoknak a könyvtáraknak, amelyeket a projektje használ. Csak telepíteni kell a `symfony/thanks` könyvtárat: ```shell composer global require symfony/thanks ``` -Majd futtasd le: +Majd futtatni: ```shell composer thanks ``` -Próbáld ki! +Próbálja ki! -Konfiguráció .[#toc-configuration] -================================== +Konfiguráció +============ -A Composer szorosan integrálódik a [Git |https://git-scm.com] verziókezelő eszközzel. Ha nem használja a Git-et, akkor ezt meg kell mondani a Composernek: +A Composer szorosan kapcsolódik a [Git |https://git-scm.com] verziókezelő eszközhöz. Ha nincs telepítve, szólni kell a Composernek, hogy ne használja: ```shell composer -g config preferred-install dist ``` - -{{sitename: Legjobb gyakorlatok}} diff --git a/best-practices/hu/creating-editing-form.texy b/best-practices/hu/creating-editing-form.texy index 540be0ba48..959ed13a7b 100644 --- a/best-practices/hu/creating-editing-form.texy +++ b/best-practices/hu/creating-editing-form.texy @@ -1,16 +1,16 @@ -Nyomtatvány rekord létrehozásához és szerkesztéséhez -**************************************************** +Űrlap rekord létrehozásához és szerkesztéséhez +********************************************** .[perex] -Hogyan lehet megfelelően megvalósítani egy rekord hozzáadását és szerkesztését a Nette-ben, ugyanazt az űrlapot használva mindkettőhöz? +Hogyan implementáljuk helyesen a Nette-ben egy rekord hozzáadását és szerkesztését úgy, hogy mindkettőhöz ugyanazt az űrlapot használjuk? -Sok esetben a rekord hozzáadására és szerkesztésére szolgáló űrlapok azonosak, csak a gomb címkéje különbözik. Példákat mutatunk egyszerű bemutatókra, ahol az űrlapot először egy rekord felvételére, majd szerkesztésére használjuk, végül pedig a két megoldást kombináljuk. +Sok esetben a rekord hozzáadására és szerkesztésére szolgáló űrlapok ugyanazok, legfeljebb a gomb felirata különbözik. Egyszerű presenterek példáit mutatjuk be, ahol az űrlapot először rekord hozzáadására, majd szerkesztésére használjuk, végül pedig egyesítjük a két megoldást. -Rekord hozzáadása .[#toc-adding-a-record] ------------------------------------------ +Rekord hozzáadása +----------------- -Egy példa egy rekord hozzáadására használt prezenterre. A tényleges adatbázis-munkát a `Facade` osztályra hagyjuk, amelynek kódja a példa szempontjából nem releváns. +Példa egy presenter-re, amely rekord hozzáadására szolgál. Magát az adatbázis-kezelést a `Facade` osztályra bízzuk, amelynek kódja a példa szempontjából nem lényeges. ```php @@ -27,7 +27,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $form = new Form; - // ... űrlapmezők hozzáadása ... + // ... hozzáadjuk az űrlap mezőit ... $form->onSuccess[] = [$this, 'recordFormSucceeded']; return $form; @@ -35,7 +35,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter public function recordFormSucceeded(Form $form, array $data): void { - $this->facade->add($data); // rekord hozzáadása az adatbázishoz. + $this->facade->add($data); // rekord hozzáadása az adatbázishoz $this->flashMessage('Sikeresen hozzáadva'); $this->redirect('...'); } @@ -48,10 +48,10 @@ class RecordPresenter extends Nette\Application\UI\Presenter ``` -Felvétel szerkesztése .[#toc-editing-a-record] ----------------------------------------------- +Rekord szerkesztése +------------------- -Most nézzük meg, hogyan nézne ki egy rekord szerkesztésére használt prezenter: +Most megmutatjuk, hogyan nézne ki egy presenter, amely rekord szerkesztésére szolgál: ```php @@ -70,8 +70,8 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $record = $this->facade->get($id); if ( - !$record // ellenőrizze a nyilvántartás meglétét - || !$this->facade->isEditAllowed(/*...*/) // engedélyek ellenőrzése + !$record // rekord létezésének ellenőrzése + || !$this->facade->isEditAllowed(/*...*/) // jogosultság ellenőrzése ) { $this->error(); // 404 hiba } @@ -81,14 +81,14 @@ class RecordPresenter extends Nette\Application\UI\Presenter protected function createComponentRecordForm(): Form { - // ellenőrizze, hogy a művelet 'szerkesztés' + // ellenőrizzük, hogy az akció 'edit' if ($this->getAction() !== 'edit') { $this->error(); } $form = new Form; - // ... űrlapmezők hozzáadása ... + // ... hozzáadjuk az űrlap mezőit ... $form->setDefaults($this->record); // alapértelmezett értékek beállítása $form->onSuccess[] = [$this, 'recordFormSucceeded']; @@ -104,9 +104,9 @@ class RecordPresenter extends Nette\Application\UI\Presenter } ``` -Az *action* metódusban, amelyet a [prezenter életciklusának |application:presenters#Life Cycle of Presenter] kezdetén hívunk meg, ellenőrizzük a rekord létezését és a felhasználó engedélyét a szerkesztésre. +Az *action* metódusban, amely rögtön a [presenter életciklusának |application:presenters#Presenter életciklusa] elején fut le, ellenőrizzük a rekord létezését és a felhasználó jogosultságát annak szerkesztésére. -A rekordot a `$record` tulajdonságban tároljuk, hogy a `createComponentRecordForm()` metódusban az alapértelmezések beállításához, valamint a `recordFormSucceeded()` azonosítóhoz rendelkezésre álljon. Egy alternatív megoldás az lenne, ha az alapértelmezett értékeket közvetlenül a `actionEdit()` -ban állítanánk be, és az ID értékét, amely az URL része, a `getParameter('id')` segítségével kérnénk le: +A rekordot a `$record` property-be mentjük, hogy elérhető legyen a `createComponentRecordForm()` metódusban az alapértelmezett értékek beállításához, és a `recordFormSucceeded()` metódusban az ID miatt. Alternatív megoldásként beállíthatnánk az alapértelmezett értékeket közvetlenül az `actionEdit()` metódusban, és az URL részét képező ID értékét a `getParameter('id')` segítségével szerezhetnénk meg: ```php @@ -114,12 +114,12 @@ A rekordot a `$record` tulajdonságban tároljuk, hogy a `createComponentRecordF { $record = $this->facade->get($id); if ( - // ellenőrzi a létezést és a jogosultságokat + // létezés ellenőrzése és jogosultság ellenőrzése ) { $this->error(); } - // alapértelmezett űrlapértékek beállítása + // űrlap alapértelmezett értékeinek beállítása $this->getComponent('recordForm') ->setDefaults($record); } @@ -133,13 +133,13 @@ A rekordot a `$record` tulajdonságban tároljuk, hogy a `createComponentRecordF } ``` -Azonban, és ez lesz **a legfontosabb tanulság az egész kódból**, meg kell győződnünk arról, hogy a művelet valóban `edit`, amikor létrehozzuk az űrlapot. Mert különben a `actionEdit()` metódusban az érvényesítés egyáltalán nem történne meg! +Azonban, és ez kellene, hogy **az egész kód legfontosabb tanulsága** legyen, az űrlap létrehozásakor meg kell győződnünk arról, hogy az akció valóban `edit`. Mert különben az `actionEdit()` metódusban lévő ellenőrzés egyáltalán nem futna le! -Ugyanaz az űrlap a hozzáadáshoz és a szerkesztéshez .[#toc-same-form-for-adding-and-editing] --------------------------------------------------------------------------------------------- +Ugyanaz az űrlap hozzáadáshoz és szerkesztéshez +----------------------------------------------- -És most egyesítjük a két előadót egybe. Vagy megkülönböztetjük, hogy melyik műveletről van szó a `createComponentRecordForm()` módszerben, és ennek megfelelően konfiguráljuk az űrlapot, vagy közvetlenül az action-módszerekre bízzuk, és megszabadulunk a feltételtől: +És most egyesítjük a két presentert egybe. Vagy megkülönböztethetnénk a `createComponentRecordForm()` metódusban, hogy melyik akcióról van szó, és ennek megfelelően konfigurálhatnánk az űrlapot, vagy ezt közvetlenül az action-metódusokra bízhatnánk, és megszabadulhatnánk a feltételtől: ```php @@ -160,8 +160,8 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $record = $this->facade->get($id); if ( - !$record // ellenőrizze a nyilvántartás meglétét - || !$this->facade->isEditAllowed(/*...*/) // engedélyek ellenőrzése + !$record // rekord létezésének ellenőrzése + || !$this->facade->isEditAllowed(/*...*/) // jogosultság ellenőrzése ) { $this->error(); // 404 hiba } @@ -173,21 +173,21 @@ class RecordPresenter extends Nette\Application\UI\Presenter protected function createComponentRecordForm(): Form { - // ellenőrizze, hogy a művelet "hozzáadás" vagy "szerkesztés". + // ellenőrizzük, hogy az akció 'add' vagy 'edit' if (!in_array($this->getAction(), ['add', 'edit'])) { $this->error(); } $form = new Form; - // ... űrlapmezők hozzáadása ... + // ... hozzáadjuk az űrlap mezőit ... return $form; } public function addingFormSucceeded(Form $form, array $data): void { - $this->facade->add($data); // rekord hozzáadása az adatbázishoz. + $this->facade->add($data); // rekord hozzáadása az adatbázishoz $this->flashMessage('Sikeresen hozzáadva'); $this->redirect('...'); } @@ -203,4 +203,3 @@ class RecordPresenter extends Nette\Application\UI\Presenter ``` {{priority: -1}} -{{sitename: Legjobb gyakorlatok}} diff --git a/best-practices/hu/dynamic-snippets.texy b/best-practices/hu/dynamic-snippets.texy index 508065649e..b6df024d32 100644 --- a/best-practices/hu/dynamic-snippets.texy +++ b/best-practices/hu/dynamic-snippets.texy @@ -1,7 +1,7 @@ -Dinamikus snippetek +Dinamikus Snippetek ******************* -Az alkalmazásfejlesztés során gyakran van szükség AJAX műveletek elvégzésére, például egy táblázat vagy lista egyes soraiban. Példaként választhatjuk, hogy cikkeket listázunk, lehetővé téve a bejelentkezett felhasználó számára, hogy mindegyikhez "tetszik/nem tetszik" minősítést válasszon. A bemutató és a megfelelő sablon kódja AJAX nélkül valahogy így fog kinézni (a legfontosabb részleteket sorolom fel, a kód feltételezi egy szolgáltatás meglétét az értékelések jelölésére és a cikkek gyűjteményének kinyerésére - a konkrét megvalósítás nem fontos a bemutató szempontjából): +Az alkalmazásfejlesztés során meglehetősen gyakran felmerül az igény AJAX műveletek végrehajtására, például táblázatok egyes sorain vagy listaelemeken. Példaként választhatjuk a cikkek listázását, ahol minden cikknél lehetővé tesszük a bejelentkezett felhasználó számára, hogy "tetszik/nem tetszik" értékelést adjon. A presenter és a hozzá tartozó sablon kódja AJAX nélkül körülbelül így fog kinézni (a legfontosabb részeket mutatom be, a kód számol az értékelések jelölésére szolgáló szolgáltatás létezésével és a cikkek gyűjteményének megszerzésével - a konkrét implementáció nem fontos ennek az útmutatónak a céljaihoz): ```php public function handleLike(int $articleId): void @@ -17,33 +17,33 @@ public function handleUnlike(int $articleId): void } ``` -Template: +Sablon: ```latte <article n:foreach="$articles as $article"> <h2>{$article->title}</h2> <div class="content">{$article->content}</div> {if !$article->liked} - <a n:href="like! $article->id" class=ajax>I like it</a> + <a n:href="like! $article->id" class=ajax>tetszik</a> {else} - <a n:href="unlike! $article->id" class=ajax>I don't like it anymore</a> + <a n:href="unlike! $article->id" class=ajax>már nem tetszik</a> {/if} </article> ``` -Ajaxization .[#toc-ajaxization] -=============================== +Ajaxizálás +========== -Vigyük most az AJAX-ot ebbe az egyszerű alkalmazásba. Egy cikk értékelésének megváltoztatása nem elég fontos ahhoz, hogy HTTP-kérést igényeljen átirányítással, így ideális esetben ezt AJAX-szel kell elvégezni a háttérben. Használni fogjuk a [kezelőszkriptet a kiegészítésekből |https://componette.org/vojtech-dobes/nette.ajax.js/] a szokásos konvencióval, hogy az AJAX linkek CSS osztálya a `ajax`. +Most lássuk el ezt az egyszerű alkalmazást AJAX-szal. A cikk értékelésének megváltoztatása nem annyira fontos, hogy átirányításra legyen szükség, ezért ideális esetben AJAX-szal kellene történnie a háttérben. Használjuk [a kiegészítők kiszolgáló szkriptjét |application:ajax#Naja] a szokásos konvencióval, miszerint az AJAX linkeknek `ajax` CSS osztályuk van. -Azonban hogyan kell ezt konkrétan megtenni? A Nette 2 módszert kínál: a dinamikus snippet módot és a komponens módot. Mindkettőnek megvannak az előnyei és hátrányai, ezért egyenként mutatjuk be őket. +De hogyan is csináljuk ezt konkrétan? A Nette 2 utat kínál: az ún. dinamikus snippetek útját és a komponensek útját. Mindkettőnek megvannak az előnyei és hátrányai, ezért egyenként bemutatjuk őket. -A dinamikus snippetek módja .[#toc-the-dynamic-snippets-way] -============================================================ +A dinamikus snippetek útja +========================== -A Latte terminológiában a dinamikus snippet a `{snippet}` címke egy speciális felhasználási esete, ahol egy változót használunk a snippet nevében. Egy ilyen snippet nem található meg akárhol a sablonban - egy statikus snippetbe, azaz egy szabályos snippetbe, vagy egy `{snippetArea}` belsejébe kell csomagolni. A sablonunkat a következőképpen módosíthatjuk. +A dinamikus snippet a Latte terminológiájában a `{snippet}` tag egy speciális használati esetét jelenti, amikor a snippet nevében egy változó szerepel. Egy ilyen snippet nem lehet bárhol a sablonban - egy statikus snippetbe, azaz egy közönséges snippetbe vagy egy `{snippetArea}`-ba kell csomagolni. A sablonunkat a következőképpen módosíthatnánk. ```latte @@ -53,18 +53,18 @@ A Latte terminológiában a dinamikus snippet a `{snippet}` címke egy speciáli <div class="content">{$article->content}</div> {snippet article-{$article->id}} {if !$article->liked} - <a n:href="like! $article->id" class=ajax>I like it</a> + <a n:href="like! $article->id" class=ajax>tetszik</a> {else} - <a n:href="unlike! $article->id" class=ajax>I don't like it anymore</a> + <a n:href="unlike! $article->id" class=ajax>már nem tetszik</a> {/if} {/snippet} </article> {/snippet} ``` -Minden cikk mostantól egyetlen snippetet definiál, amelynek a címében szerepel a cikk azonosítója. Ezek a snippetek ezután egyetlen snippetbe vannak csomagolva, melynek neve `articlesContainer`. Ha kihagyjuk ezt a csomagoló snippetet, a Latte egy kivétellel figyelmeztet minket. +Most minden cikk definiál egy snippetet, amelynek nevében a cikk ID-ja szerepel. Mindezeket a snippeket aztán egyetlen, `articlesContainer` nevű snippetbe csomagoljuk. Ha ezt a csomagoló snippetet kihagynánk, a Latte kivétellel figyelmeztetne minket. -Már csak az újrarajzolás hozzáadása van hátra a prezentálóhoz - csak a statikus wrappert kell újrarajzolni. +Már csak a presenterben kell kiegészítenünk az újrarajzolást - elég a statikus burkolót újrarajzolni. ```php public function handleLike(int $articleId): void @@ -72,18 +72,18 @@ public function handleLike(int $articleId): void $this->ratingService->saveLike($articleId, $this->user->id); if ($this->isAjax()) { $this->redrawControl('articlesContainer'); - // $this->redrawControl('article-' . $articleId); -- není potřeba + // $this->redrawControl('article-' . $articleId); -- nem szükséges } else { $this->redirect('this'); } } ``` -Módosítsuk ugyanígy a `handleUnlike()` testvérmetódust, és máris kész az AJAX! +Hasonlóképpen módosítjuk a testvér `handleUnlike()` metódust is, és az AJAX működik! -A megoldásnak azonban van egy hátránya. Ha jobban beleássuk magunkat az AJAX-kérés működésébe, kiderül, hogy bár az alkalmazás látszólag hatékony (egy adott cikkhez csak egyetlen részletet ad vissza), valójában az összes részletet megjeleníti a szerveren. A kívánt snippetet elhelyezte a payloadunkban, a többit pedig elvetette (tehát teljesen feleslegesen azokat is lekérte az adatbázisból). +A megoldásnak azonban van egy árnyoldala. Ha jobban megvizsgálnánk, hogyan zajlik az AJAX kérés, rájönnénk, hogy bár kifelé az alkalmazás takarékosnak tűnik (csak egyetlen snippetet ad vissza az adott cikkhez), valójában a szerveren az összes snippetet kirajzolta. A kívánt snippetet a payloadba helyezte, a többit pedig eldobta (tehát teljesen feleslegesen szerezte be őket az adatbázisból is). -Ahhoz, hogy ezt a folyamatot optimalizáljuk, olyan műveletet kell végrehajtanunk, ahol a `$articles` gyűjteményt átadjuk a sablonhoz (mondjuk a `renderDefault()` metódusban). Ki fogjuk használni azt a tényt, hogy a jelfeldolgozás a jelfeldolgozás előtt történik. `render<Something>` módszerek előtt: +Ahhoz, hogy ezt a folyamatot optimalizáljuk, ott kell beavatkoznunk, ahol a `$articles` gyűjteményt átadjuk a sablonnak (mondjuk a `renderDefault()` metódusban). Kihasználjuk azt a tényt, hogy a signálok feldolgozása a `render<Something>` metódusok előtt történik: ```php public function handleLike(int $articleId): void @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connection->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,18 +101,18 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connection->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` -Most, amikor a jel feldolgozása során az összes cikket tartalmazó gyűjtemény helyett csak egy tömböt adunk át a sablonhoz, amely egyetlen cikket tartalmaz - azt, amelyet renderelni és payloadként elküldeni szeretnénk a böngészőnek. Így a `{foreach}` csak egyszer fog megtörténni, és nem lesz renderelve semmilyen extra snippet. +Most a signál feldolgozásakor a sablonba az összes cikket tartalmazó gyűjtemény helyett csak egy tömb kerül átadásra egyetlen cikkel - azzal, amelyet ki akarunk rajzolni és a payloadban elküldeni a böngészőnek. A `{foreach}` tehát csak egyszer fut le, és nem rajzolódnak ki felesleges snippettek. -Komponens módja .[#toc-component-way] -===================================== +A komponensek útja +================== -Egy teljesen más megoldás más megközelítést használ a dinamikus snippetek elkerülésére. A trükk az, hogy az összes logikát egy különálló komponensbe költöztetjük - innentől kezdve nem egy prezenter gondoskodik az értékelés beviteléről, hanem egy dedikált `LikeControl`. Az osztály a következőképpen fog kinézni (emellett tartalmazza majd a `render`, `handleUnlike`, stb. metódusokat is): +Egy teljesen más megoldási mód elkerüli a dinamikus snippetteket. A trükk abban rejlik, hogy az egész logikát egy külön komponensbe helyezzük át - az értékelések megadásától kezdve nem a presenter fog gondoskodni, hanem egy dedikált `LikeControl`. Az osztály a következőképpen fog kinézni (ezen kívül tartalmazni fogja a `render`, `handleUnlike` stb. metódusokat is): ```php class LikeControl extends Nette\Application\UI\Control @@ -139,26 +139,26 @@ A komponens sablonja: ```latte {snippet} {if !$article->liked} - <a n:href="like!" class=ajax>I like it</a> + <a n:href="like!" class=ajax>tetszik</a> {else} - <a n:href="unlike!" class=ajax>I don't like it anymore</a> + <a n:href="unlike!" class=ajax>már nem tetszik</a> {/if} {/snippet} ``` -Természetesen meg fogjuk változtatni a nézetsablont, és hozzá kell adnunk egy gyárat a prezentálóhoz. Mivel a komponenst annyiszor fogjuk létrehozni, ahány cikket kapunk az adatbázisból, ezért az [Multiplier |application:Multiplier] osztályt fogjuk használni a "sokszorosításhoz". +Természetesen megváltozik a view sablonja, és a presenterbe be kell illesztenünk egy factory-t. Mivel a komponenst annyiszor hozzuk létre, ahány cikket lekérünk az adatbázisból, a "sokszorosításához" a [Multiplier |application:Multiplier] osztályt használjuk. ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); } ``` -A sablon nézetet a szükséges minimumra csökkentjük (és teljesen mentesítjük a snippetektől!): +A view sablonja a szükséges minimumra csökken (és teljesen mentes a snippettektől!): ```latte <article n:foreach="$articles as $article"> @@ -168,7 +168,6 @@ A sablon nézetet a szükséges minimumra csökkentjük (és teljesen mentesítj </article> ``` -Majdnem készen vagyunk: az alkalmazás mostantól AJAX-ben fog működni. Itt is optimalizálnunk kell az alkalmazást, mert a Nette adatbázis használata miatt a jelfeldolgozás feleslegesen fogja betölteni az összes cikket az adatbázisból egy helyett. Előnye viszont, hogy nem lesz renderelés, mert valójában csak a mi komponensünk kerül renderelésre. +Majdnem készen vagyunk: az alkalmazás mostantól AJAX-osan fog működni. Itt is optimalizálnunk kell az alkalmazást, mert a Nette Database használata miatt a signál feldolgozásakor feleslegesen betöltődik az összes cikk az adatbázisból egy helyett. Előnye azonban, hogy nem kerülnek kirajzolásra, mert valóban csak a mi komponensünk renderelődik. {{priority: -1}} -{{sitename: Legjobb gyakorlatok}} diff --git a/best-practices/hu/editors-and-tools.texy b/best-practices/hu/editors-and-tools.texy index 9d0a0f1cc6..7104666da7 100644 --- a/best-practices/hu/editors-and-tools.texy +++ b/best-practices/hu/editors-and-tools.texy @@ -2,39 +2,39 @@ Szerkesztők és eszközök *********************** .[perex] -Lehetsz ügyes programozó, de csak jó eszközökkel válhatsz mesterré. Ebben a fejezetben tippeket találsz a fontos eszközökről, szerkesztőkről és bővítményekről. +Lehetsz ügyes programozó, de csak jó eszközökkel válsz mesterré. Ebben a fejezetben tippeket találsz fontos eszközökhöz, szerkesztőkhöz és bővítményekhez. -IDE-szerkesztő .[#toc-ide-editor] -================================= +IDE szerkesztő +============== -Erősen javasoljuk, hogy a fejlesztéshez teljes funkcionalitású IDE-t használjon, például PhpStorm, NetBeans, VS Code, és ne csak egy PHP-támogatással rendelkező szövegszerkesztőt. A különbség valóban döntő jelentőségű. Nincs okunk megelégedni egy klasszikus, szintaxiskiemeléssel ellátott szerkesztővel, mert az nem éri el egy olyan IDE képességeit, amely pontos kódjavaslattal rendelkezik, amely képes a kód refaktorálására, és még sok minden másra is. Egyes IDE-k fizetősek, mások ingyenesek. +Határozottan javasoljuk, hogy a fejlesztéshez teljes értékű IDE-t használj, mint például a PhpStorm, NetBeans, VS Code, és ne csak egy PHP támogatással rendelkező szövegszerkesztőt. A különbség valóban alapvető. Nincs ok megelégedni egy egyszerű szerkesztővel, amely ugyan tudja színezni a szintaxist, de nem éri el egy csúcskategóriás IDE képességeit, amely pontosan súg, figyeli a hibákat, képes refaktorálni a kódot és sok minden mást. Néhány IDE fizetős, mások pedig ingyenesek. -A **NetBeans IDE** beépített támogatással rendelkezik a Nette, a Latte és a NEON számára. +A **NetBeans IDE** beépített támogatással rendelkezik a Nette, Latte és NEON számára. -**PhpStorm**: telepítse ezeket a bővítményeket a `Settings > Plugins > Marketplace`: -- Nette keretrendszer segédprogramok +**PhpStorm**: telepítsd ezeket a bővítményeket a `Settings > Plugins > Marketplace` menüpontban: +- Nette framework helpers - Latte -- NEON támogatás +- NEON support - Nette Tester -**VS kód**: a "Nette Latte + Neon" bővítményt a piactéren találod. +**VS Code**: keresd meg a marketplace-en a "Nette Latte + Neon" bővítményt. -Csatlakoztassa Tracy-t is a szerkesztőhöz. Amikor a hibaoldal megjelenik, kattints a fájlnevekre, és azok megnyílnak a szerkesztőben, a kurzor a megfelelő soron áll. Ismerje meg a [rendszer konfigurálásának módját |tracy:open-files-in-ide]. +Kapcsold össze a Tracy-t is a szerkesztővel. Amikor egy hibaoldal jelenik meg, rákattinthatsz a fájlnevekre, és azok megnyílnak a szerkesztőben a megfelelő sorra állított kurzorral. Olvasd el, [hogyan konfiguráld a rendszert |tracy:open-files-in-ide]. -PHPStan .[#toc-phpstan] -======================= +PHPStan +======= -A PHPStan egy olyan eszköz, amely még a futtatás előtt felismeri a logikai hibákat a kódban. +A PHPStan egy eszköz, amely logikai hibákat tár fel a kódban, mielőtt futtatnád azt. -Telepítse a Composer segítségével: +Telepítsük a Composer segítségével: ```shell composer require --dev phpstan/phpstan-nette ``` -Hozzon létre egy konfigurációs fájlt `phpstan.neon` a projektben: +Hozzunk létre egy konfigurációs fájlt a projektben `phpstan.neon` néven: ```neon includes: @@ -47,40 +47,38 @@ parameters: level: 5 ``` -Majd hagyja, hogy elemezze a `app/` mappában lévő osztályokat: +Majd futtassuk az elemzést az `app/` mappában lévő osztályokon: ```shell vendor/bin/phpstan analyse app ``` -Átfogó dokumentációt közvetlenül a [PHPStan |https://phpstan.org] oldalon talál. +Kimerítő dokumentációt találsz közvetlenül a [PHPStan oldalán |https://phpstan.org]. -Kódellenőrző .[#toc-code-checker] -================================= +Code Checker +============ -[Code Checker |code-checker:] ellenőrzi és esetleg kijavítja a forráskódodban található formális hibákat. +A [Code Checker|code-checker:] ellenőrzi és szükség esetén kijavítja a forráskódok néhány formai hibáját: -- eltávolítja a [BOM-ot |nette:glossary#bom] +- eltávolítja a [BOM |nette:glossary#BOM]-ot - ellenőrzi a [Latte |latte:] sablonok érvényességét -- ellenőrzi a `.neon`, `.php` és `.json` fájlok érvényességét. -- ellenőrzi a [vezérlő karaktereket |nette:glossary#control characters] +- ellenőrzi a `.neon`, `.php` és `.json` fájlok érvényességét +- ellenőrzi a [vezérlőkarakterek |nette:glossary#Vezérlő karakterek] előfordulását - ellenőrzi, hogy a fájl UTF-8 kódolású-e -- ellenőrzi a hibásan írt `/* @annotations */` címet (hiányzik a második csillag). -- eltávolítja a `?>` PHP végződésű címkéket a PHP-fájlokban. -- eltávolítja a fájl végéről a hátul lévő szóközöket és a felesleges üres sorokat -- normalizálja a sorvégeket a rendszer alapértelmezéséhez (a `-l` paraméterrel) +- ellenőrzi a hibásan írt `/* @anotace */` (hiányzik a csillag) +- eltávolítja a záró `?>` PHP fájlokból +- eltávolítja a jobb oldali szóközöket és a felesleges sorokat a fájl végéről +- normalizálja a sorelválasztókat a rendszer alapértelmezettjére (ha megadja a `-l` opciót) -Composer .[#toc-composer] -========================= +Composer +======== -[Composer |Composer] egy eszköz a függőségek kezelésére PHP-ban. Lehetővé teszi számunkra, hogy könyvtárfüggőségeket deklaráljunk, és telepíti őket helyettünk, a projektünkbe. +A [Composer |best-practices:composer] egy függőségkezelő eszköz PHP-hez. Lehetővé teszi számunkra, hogy tetszőlegesen összetett függőségeket deklaráljunk az egyes könyvtárakhoz, majd telepíti őket a projektünkbe. -Követelményellenőrző .[#toc-requirements-checker] -================================================= +Requirements Checker +==================== -Ez egy olyan eszköz volt, amely tesztelte a szerver futtatási környezetét, és tájékoztatott arról, hogy a keretrendszer használható-e (és milyen mértékben). Jelenleg a Nette bármilyen szerveren használható, amely rendelkezik a PHP minimálisan szükséges verziójával. - -{{sitename: Legjobb gyakorlatok}} +Ez egy eszköz volt, amely tesztelte a szerver futási környezetét, és tájékoztatott arról, hogy (és milyen mértékben) lehet használni a keretrendszert. Jelenleg a Nette minden olyan szerveren használható, amely rendelkezik a minimálisan szükséges PHP verzióval. diff --git a/best-practices/hu/form-reuse.texy b/best-practices/hu/form-reuse.texy index 9245624d97..cbda52b931 100644 --- a/best-practices/hu/form-reuse.texy +++ b/best-practices/hu/form-reuse.texy @@ -1,16 +1,16 @@ -Formanyomtatványok újrafelhasználása több helyen -************************************************ +Űrlapok újrafelhasználása több helyen +************************************* .[perex] -A Nette-ben több lehetőséged is van arra, hogy ugyanazt az űrlapot több helyen újra felhasználd anélkül, hogy a kódot duplikálnád. Ebben a cikkben áttekintjük a különböző megoldásokat, beleértve azokat is, amelyeket érdemes elkerülni. +A Nette-ben több lehetőség is rendelkezésre áll ugyanazon űrlap több helyen történő használatára a kód duplikálása nélkül. Ebben a cikkben különböző megoldásokat mutatunk be, beleértve azokat is, amelyeket érdemes elkerülni. -Form Factory .[#toc-form-factory] -================================= +Űrlap Factory +============= -Egy komponens több helyen történő használatának egyik alapvető megközelítése, hogy létrehozunk egy metódust vagy osztályt, amely létrehozza a komponenst, majd ezt a metódust az alkalmazás különböző helyein hívjuk meg. Az ilyen metódust vagy osztályt *gyárnak* nevezzük. Kérjük, ne keverjük össze a *factory method* tervezési mintával, amely a gyárak használatának egy speciális módját írja le, és nem kapcsolódik ehhez a témához. +Az egyik alapvető megközelítés ugyanazon komponens több helyen történő használatára egy olyan metódus vagy osztály létrehozása, amely ezt a komponenst generálja, majd ennek a metódusnak a meghívása az alkalmazás különböző pontjain. Egy ilyen metódust vagy osztályt *factory*-nak nevezünk. Kérjük, ne keverje össze a *factory method* tervezési mintával, amely a factory-k specifikus felhasználási módját írja le, és nem kapcsolódik ehhez a témához. -Példaként hozzunk létre egy gyárat, amely egy szerkesztési űrlapot készít: +Példaként létrehozunk egy factory-t, amely egy szerkesztő űrlapot fog összeállítani: ```php use Nette\Application\UI\Form; @@ -20,22 +20,22 @@ class FormFactory public function createEditForm(): Form { $form = new Form; - $form->addText('title', 'Title:'); - // itt további űrlapmezők kerülnek hozzáadásra - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Cím:'); + // itt adjuk hozzá a további űrlapmezőket + $form->addSubmit('send', 'Küldés'); return $form; } } ``` -Ezt a gyárat az alkalmazás különböző helyein használhatja, például prezenterekben vagy komponensekben. Ezt pedig úgy tesszük, hogy [függőségként kérjük be |dependency-injection:passing-dependencies]. Tehát először írjuk be az osztályt a konfigurációs fájlba: +Most már használhatja ezt a factory-t az alkalmazás különböző pontjain, például presenterekben vagy komponensekben. Ezt úgy teheti meg, hogy [függőségként kérjük |dependency-injection:passing-dependencies]. Először tehát regisztráljuk az osztályt a konfigurációs fájlban: ```neon services: - FormFactory ``` -És aztán használjuk a prezenterben: +Majd használjuk a presenterben: ```php @@ -50,14 +50,14 @@ class MyPresenter extends Nette\Application\UI\Presenter { $form = $this->formFactory->createEditForm(); $form->onSuccess[] = function () { - // a küldött adatok feldolgozása + // beküldött adatok feldolgozása }; return $form; } } ``` -A form factory-t további metódusokkal bővíthetjük, hogy más típusú űrlapokat hozzunk létre az alkalmazásunknak megfelelően. És természetesen hozzáadhat egy olyan metódust is, amely egy elemek nélküli alap űrlapot hoz létre, amelyet a többi metódus használni fog: +Az űrlap factory-t kibővítheti további metódusokkal más típusú űrlapok létrehozásához az alkalmazás igényei szerint. És természetesen hozzáadhatunk egy metódust is, amely létrehoz egy alap űrlapot elemek nélkül, és ezt a többi metódus fogja használni: ```php class FormFactory @@ -71,21 +71,21 @@ class FormFactory public function createEditForm(): Form { $form = $this->createForm(); - $form->addText('title', 'Title:'); - // itt további űrlapmezők kerülnek hozzáadásra - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Cím:'); + // itt adjuk hozzá a további űrlapmezőket + $form->addSubmit('send', 'Küldés'); return $form; } } ``` -A `createForm()` metódus egyelőre semmi hasznosat nem csinál, de ez hamarosan megváltozik. +A `createForm()` metódus egyelőre nem csinál semmi hasznosat, de ez hamarosan megváltozik. -Gyári függőségek .[#toc-factory-dependencies] -============================================= +A Factory függőségei +==================== -Idővel nyilvánvalóvá válik, hogy az űrlapoknak többnyelvűnek kell lenniük. Ez azt jelenti, hogy minden űrlaphoz be kell állítanunk egy [fordítót |forms:rendering#Translating]. Ehhez módosítjuk a `FormFactory` osztályt, hogy a konstruktorban függőségként elfogadja a `Translator` objektumot, és átadja azt az űrlapnak: +Idővel kiderül, hogy szükségünk van arra, hogy az űrlapok többnyelvűek legyenek. Ez azt jelenti, hogy minden űrlaphoz be kell állítanunk az úgynevezett [translator |forms:rendering#Fordítás]-t. Ebből a célból módosítjuk a `FormFactory` osztályt úgy, hogy a konstruktorban függőségként fogadja el a `Translator` objektumot, és átadjuk azt az űrlapnak: ```php use Nette\Localization\Translator; @@ -104,18 +104,17 @@ class FormFactory return $form; } - //... + // ... } ``` -Mivel a `createForm()` metódust más, konkrét űrlapokat létrehozó metódusok is meghívják, a fordítót csak ebben a metódusban kell beállítanunk. És kész is vagyunk. Nem kell semmilyen prezenter vagy komponens kódot módosítani, ami nagyszerű. +Mivel a `createForm()` metódust a többi, specifikus űrlapokat létrehozó metódus is meghívja, elegendő a translatort csak ebben beállítani. És készen is vagyunk. Nincs szükség egyetlen presenter vagy komponens kódjának módosítására sem, ami nagyszerű. -További gyári osztályok .[#toc-more-factory-classes] -==================================================== +Több Factory osztály +==================== -Alternatívaként több osztályt is létrehozhat minden egyes űrlaphoz, amelyet az alkalmazásban használni szeretne. -Ez a megközelítés növelheti a kód olvashatóságát és megkönnyítheti az űrlapok kezelését. Hagyja meg az eredeti `FormFactory` címet, hogy csak egy tiszta űrlapot hozzon létre alapvető konfigurációval (például fordítástámogatással), és hozzon létre egy új gyári `EditFormFactory` címet a szerkesztési űrlaphoz. +Alternatív megoldásként létrehozhat több osztályt minden egyes űrlaphoz, amelyet használni szeretne az alkalmazásában. Ez a megközelítés növelheti a kód olvashatóságát és megkönnyítheti az űrlapok kezelését. Az eredeti `FormFactory`-t csak egy tiszta űrlap létrehozására hagyjuk meg alapkonfigurációval (például fordítási támogatással), és a szerkesztő űrlaphoz létrehozunk egy új `EditFormFactory` factory-t. ```php class FormFactory @@ -134,7 +133,7 @@ class FormFactory } -// ✅ a kompozíció használata +// ✅ kompozíció használata class EditFormFactory { public function __construct( @@ -145,40 +144,39 @@ class EditFormFactory public function create(): Form { $form = $this->formFactory->create(); - // itt további űrlapmezők kerülnek hozzáadásra - $form->addSubmit('send', 'Save'); + // itt adjuk hozzá a további űrlapmezőket + $form->addSubmit('send', 'Küldés'); return $form; } } ``` -Nagyon fontos, hogy a `FormFactory` és a `EditFormFactory` osztályok közötti kötés kompozícióval, nem pedig objektumörökléssel valósuljon meg: +Nagyon fontos, hogy a `FormFactory` és az `EditFormFactory` osztályok közötti kapcsolat [kompozícióval |nette:introduction-to-object-oriented-programming#Kompozíció] valósuljon meg, nem pedig [objektum öröklődéssel |nette:introduction-to-object-oriented-programming#Öröklődés]: ```php -// ⛔ NO! AZ ÖRÖKSÉG NEM TARTOZIK IDE +// ⛔ ÍGY NE! IDE NEM VALÓ AZ ÖRÖKLŐDÉS class EditFormFactory extends FormFactory { public function create(): Form { $form = parent::create(); - $form->addText('title', 'Title:'); - // további űrlapmezők kerülnek ide - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Cím:'); + // itt adjuk hozzá a további űrlapmezőket + $form->addSubmit('send', 'Küldés'); return $form; } } ``` -Az öröklés használata ebben az esetben teljesen kontraproduktív lenne. Nagyon gyorsan problémákba ütközne. Például, ha paramétereket akarnánk hozzáadni a `create()` metódushoz; a PHP hibát jelezne, hogy a metódus aláírása eltér a szülőétől. -Vagy ha a konstruktoron keresztül átadnánk egy függőséget a `EditFormFactory` osztálynak. Ez azt okozná, amit mi [konstruktor pokolnak |dependency-injection:passing-dependencies#Constructor hell] hívunk. +Az öröklődés használata ebben az esetben teljesen kontraproduktív lenne. Nagyon gyorsan problémákba ütköznél. Például abban a pillanatban, amikor paramétereket szeretnél hozzáadni a `create()` metódushoz; a PHP hibát jelezne, hogy a szignatúrája eltér a szülőétől. Vagy amikor függőséget adnál át az `EditFormFactory` osztálynak a konstruktoron keresztül. Olyan helyzet állna elő, amelyet [constructor hell |dependency-injection:passing-dependencies#Constructor hell]-nek nevezünk. -Általánosságban elmondható, hogy jobb a kompozíciót előnyben részesíteni az örökléssel szemben. +Általában jobb előnyben részesíteni a [kompozíciót az öröklődéssel szemben |dependency-injection:faq#Miért részesítjük előnyben a kompozíciót az öröklődéssel szemben]. -Form kezelés .[#toc-form-handling] -================================== +Űrlapkezelés +============ -A sikeres elküldés után meghívott űrlapkezelő lehet egy gyári osztály része is. Ez úgy fog működni, hogy a beküldött adatokat átadja a modellnek feldolgozásra. Az esetleges hibákat [visszaadja |forms:validation#Processing Errors] az űrlapnak. A következő példában a modellt a `Facade` osztály képviseli: +Az űrlapkezelő, amely a sikeres beküldés után hívódik meg, szintén lehet a factory osztály része. Úgy fog működni, hogy a beküldött adatokat átadja a modellnek feldolgozásra. Az esetleges hibákat [visszaadja |forms:validation#Hibák a feldolgozás során] az űrlapnak. A modellt a következő példában a `Facade` osztály képviseli: ```php class EditFormFactory @@ -192,9 +190,9 @@ class EditFormFactory public function create(): Form { $form = $this->formFactory->create(); - $form->addText('title', 'Title:'); - // itt további űrlapmezők kerülnek hozzáadásra - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Cím:'); + // itt adjuk hozzá a további űrlapmezőket + $form->addSubmit('send', 'Küldés'); $form->onSuccess[] = [$this, 'processForm']; return $form; } @@ -202,7 +200,7 @@ class EditFormFactory public function processForm(Form $form, array $data): void { try { - // a beküldött adatok feldolgozása + // beküldött adatok feldolgozása $this->facade->process($data); } catch (AnyModelException $e) { @@ -212,7 +210,7 @@ class EditFormFactory } ``` -Hagyjuk, hogy a prezenter maga kezelje az átirányítást. A `onSuccess` eseményhez hozzáad egy másik kezelőt, amely elvégzi az átirányítást. Ez lehetővé teszi, hogy az űrlapot különböző prezenterekben lehessen használni, és mindegyik más helyre irányíthasson át. +Magát az átirányítást azonban a presenterre bízzuk. Az `onSuccess` eseményhez hozzáad egy további handlert, amely végrehajtja az átirányítást. Ennek köszönhetően az űrlapot különböző presenterekben lehet majd használni, és mindegyikben máshová lehet átirányítani. ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -226,7 +224,7 @@ class MyPresenter extends Nette\Application\UI\Presenter { $form = $this->formFactory->create(); $form->onSuccess[] = function () { - $this->flashMessage('Záznam byl uložen'); + $this->flashMessage('A rekord mentésre került'); $this->redirect('Homepage:'); }; return $form; @@ -234,39 +232,38 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Ez a megoldás kihasználja az űrlapok azon tulajdonságát, hogy amikor a `addError()` meghívásra kerül egy űrlapon vagy annak elemén, a következő `onSuccess` kezelő nem hívódik meg. +Ez a megoldás kihasználja az űrlapok azon tulajdonságát, hogy ha az űrlapon vagy annak egy elemén meghívják az `addError()` metódust, akkor a további `onSuccess` handler már nem hívódik meg. -Öröklés a Form osztályból .[#toc-inheriting-from-the-form-class] -================================================================ +Öröklődés a Form osztályból +=========================== -Egy épített űrlap nem lehet egy űrlap gyermeke. Más szóval, ne használja ezt a megoldást: +Az összeállított űrlapnak nem szabad az űrlap leszármazottjának lennie. Más szavakkal, ne használja ezt a megoldást: ```php -// ⛔ NO! AZ ÖRÖKSÉG NEM TARTOZIK IDE +// ⛔ ÍGY NE! IDE NEM VALÓ AZ ÖRÖKLŐDÉS class EditForm extends Form { public function __construct(Translator $translator) { parent::__construct(); - $form->addText('title', 'Title:'); - // további űrlapmezők kerülnek ide - $form->addSubmit('send', 'Save'); - $form->setTranslator($translator); + $this->addText('title', 'Cím:'); + // itt adjuk hozzá a további űrlapmezőket + $this->addSubmit('send', 'Küldés'); + $this->setTranslator($translator); } } ``` -Ahelyett, hogy az űrlapot a konstruktorban építenéd fel, használd a gyárat. +Az űrlap konstruktorban történő összeállítása helyett használjon factory-t. -Fontos tisztában lenni azzal, hogy a `Form` osztály elsősorban egy űrlap összeállításának eszköze, azaz egy űrlapépítő. Az összerakott űrlap pedig a termékének tekinthető. A termék azonban nem az építő speciális esete; nincs köztük *is a* kapcsolat, ami az öröklés alapját képezi. +Fontos megérteni, hogy a `Form` osztály elsősorban egy eszköz az űrlap összeállítására, tehát egy *form builder*. Az összeállított űrlap pedig tekinthető annak termékének. Azonban a termék nem a builder specifikus esete, nincs közöttük *is a* kapcsolat, amely az öröklődés alapját képezi. -Form komponens .[#toc-form-component] -===================================== +Komponens űrlappal +================== -Egy teljesen más megközelítés egy olyan [komponens |application:components] létrehozása, amely egy űrlapot tartalmaz. Ez új lehetőségeket ad, például az űrlap meghatározott módon történő megjelenítésére, mivel a komponens tartalmaz egy sablont. -Vagy jeleket lehet használni az AJAX-kommunikációhoz és az információk betöltéséhez az űrlapba, például a hintinghez stb. +Egy teljesen más megközelítés egy olyan [komponens |application:components] létrehozását jelenti, amelynek része egy űrlap. Ez új lehetőségeket kínál, például az űrlap specifikus módon történő renderelését, mivel a komponensnek része egy sablon is. Vagy használhatunk signálokat AJAX kommunikációhoz és információk betöltéséhez az űrlapba, például súgáshoz stb. ```php @@ -284,9 +281,9 @@ class EditControl extends Nette\Application\UI\Control protected function createComponentForm(): Form { $form = new Form; - $form->addText('title', 'Title:'); - // itt további űrlapmezők kerülnek hozzáadásra - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Cím:'); + // itt adjuk hozzá a további űrlapmezőket + $form->addSubmit('send', 'Küldés'); $form->onSuccess[] = [$this, 'processForm']; return $form; @@ -295,7 +292,7 @@ class EditControl extends Nette\Application\UI\Control public function processForm(Form $form, array $data): void { try { - // a beküldött adatok feldolgozása + // beküldött adatok feldolgozása $this->facade->process($data); } catch (AnyModelException $e) { @@ -303,13 +300,13 @@ class EditControl extends Nette\Application\UI\Control return; } - // eseményhívás + // esemény kiváltása $this->onSave($this, $data); } } ``` -Hozzunk létre egy gyárat, amely ezt a komponenst fogja előállítani. Elég, ha [megírjuk az interfészét |application:components#Components with Dependencies]: +Még létrehozunk egy factory-t, amely ezt a komponenst fogja gyártani. Elég [felírni az interfészét |application:components#Komponensek függőségekkel]: ```php interface EditControlFactory @@ -318,14 +315,14 @@ interface EditControlFactory } ``` -És adjuk hozzá a konfigurációs fájlhoz: +És hozzáadjuk a konfigurációs fájlhoz: ```neon services: - EditControlFactory ``` -És most már kérhetjük a gyárat, és használhatjuk a prezenterben: +És most már kérhetjük a factory-t és használhatjuk a presenterben: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -335,19 +332,17 @@ class MyPresenter extends Nette\Application\UI\Presenter ) { } - protected function createComponentEditForm(): Form + protected function createComponentEditForm(): EditControl { $control = $this->controlFactory->create(); $control->onSave[] = function (EditControl $control, $data) { $this->redirect('this'); - // vagy átirányítás a szerkesztés eredményére, pl.: - // $this->redirect('részlet', ['id' => $data->id]); + // vagy átirányítunk a szerkesztés eredményére, pl.: + // $this->redirect('detail', ['id' => $data->id]); }; return $control; } } ``` - -{{sitename: Legjobb gyakorlatok}} diff --git a/best-practices/hu/inject-method-attribute.texy b/best-practices/hu/inject-method-attribute.texy index 212606a7d2..f36ead2d52 100644 --- a/best-practices/hu/inject-method-attribute.texy +++ b/best-practices/hu/inject-method-attribute.texy @@ -1,21 +1,18 @@ -Injektálási módszerek és attribútumok -************************************* +Inject metódusok és attribútumok +******************************** .[perex] -Ebben a cikkben a Nette keretrendszerben a függőségek bemutatóknak való átadásának különböző módjaira fogunk összpontosítani. Összehasonlítjuk az előnyben részesített módszert, azaz a konstruktort, más lehetőségekkel, például a `inject` módszerekkel és attribútumokkal. +Ebben a cikkben a függőségek Nette keretrendszerbeli presenterekbe történő átadásának különböző módjaira összpontosítunk. Összehasonlítjuk az előnyben részesített módszert, amely a konstruktor, más lehetőségekkel, mint például az `inject` metódusok és attribútumok. -A prezenterek esetében is a függőségek átadása a [konstruktor |dependency-injection:passing-dependencies#Constructor Injection] segítségével az előnyben részesített mód. -Ha azonban létrehozunk egy közös őst, amelytől más prezenterek is örökölnek (pl. BasePresenter), és ez az ős is rendelkezik függőségekkel, akkor felmerül egy probléma, amelyet [konstruktorpokolnak |dependency-injection:passing-dependencies#Constructor hell] nevezünk. -Ez megkerülhető alternatív módszerekkel, amelyek közé tartoznak az injektáló metódusok és attribútumok (annotációk). +A presenterekre is igaz, hogy a függőségek [konstruktoron |dependency-injection:passing-dependencies#Konstruktoron keresztüli átadás] keresztüli átadása az előnyben részesített út. Ha azonban létrehozol egy közös őst, amelyből a többi presenter öröklődik (pl. `BasePresenter`), és ennek az ősnek is vannak függőségei, akkor egy problémába ütközünk, amelyet [constructor hell |dependency-injection:passing-dependencies#Constructor hell]-nek nevezünk. Ezt meg lehet kerülni alternatív utakkal, amelyeket az `inject` metódusok és attribútumok (korábban annotációk) jelentenek. -`inject*()` Módszerek .[#toc-inject-methods] -============================================ +`inject*()` metódusok +===================== -Ez a függőségi átadás egy formája [a setterek |dependency-injection:passing-dependencies#Setter Injection] használatával. Ezeknek a beállítóknak a neve az inject előtaggal kezdődik. -A Nette DI automatikusan meghívja az ilyen nevű metódusokat közvetlenül a prezentáló példány létrehozása után, és átadja az összes szükséges függőséget. Ezért ezeket a metódusokat nyilvánosnak kell deklarálni. +Ez a függőségátadás [setterrel |dependency-injection:passing-dependencies#Setteren keresztüli átadás] történő formája. Ezeknek a settereknek a neve `inject` előtaggal kezdődik. A Nette DI az így elnevezett metódusokat automatikusan meghívja rögtön a presenter példányának létrehozása után, és átadja nekik az összes szükséges függőséget. Ezért public-ként kell deklarálni őket. -`inject*()` A metódusok egyfajta konstruktor-bővítésnek tekinthetők több metódusra. Ennek köszönhetően a `BasePresenter` átveheti a függőségeket egy másik metóduson keresztül, és a konstruktort szabadon hagyhatja a leszármazottak számára: +Az `inject*()` metódusok tekinthetők a konstruktor egyfajta kiterjesztésének több metódusba. Ennek köszönhetően a `BasePresenter` más metóduson keresztül veheti át a függőségeket, és a konstruktort szabadon hagyhatja a leszármazottai számára: ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -39,18 +36,18 @@ class MyPresenter extends BasePresenter } ``` -A prezenter tetszőleges számú `inject*()` metódust tartalmazhat, és mindegyiknek tetszőleges számú paramétere lehet. Ez olyan esetekben is nagyszerű, amikor a prezenter [vonásokból áll |presenter-traits], és mindegyiknek saját függőségre van szüksége. +A presenter tetszőleges számú `inject*()` metódust tartalmazhat, és mindegyiknek tetszőleges számú paramétere lehet. Kiválóan alkalmasak olyan esetekben is, amikor a presenter [traitekből |presenter-traits] áll össze, és mindegyik saját függőséget igényel. -`Inject` Attribútumok .[#toc-inject-attributes] -=============================================== +`Inject` attribútumok +===================== -Ez a [tulajdonságokba való befecskendezés |dependency-injection:passing-dependencies#Property Injection] egy formája. Elegendő megadni, hogy mely tulajdonságokat kell befecskendezni, és a Nette DI automatikusan átadja a függőségeket közvetlenül a prezentáló példány létrehozása után. A beillesztéshez szükséges, hogy publicként deklaráljuk őket. +Ez a [property-be történő injektálás |dependency-injection:passing-dependencies#Property beállításával] formája. Elég megjelölni, hogy mely változókba kell injektálni, és a Nette DI automatikusan átadja a függőségeket rögtön a presenter példányának létrehozása után. Ahhoz, hogy be tudja illeszteni őket, public-ként kell deklarálni őket. -A tulajdonságokat egy attribútummal jelöljük: (korábban a `/** @inject */` megjegyzést használtuk) +A property-ket attribútummal jelöljük meg: (korábban a `/** @inject */` annotációt használták) ```php -use Nette\DI\Attributes\Inject; // ez a sor fontos +use Nette\DI\Attributes\Inject; // ez a sor fontos class MyPresenter extends Nette\Application\UI\Presenter { @@ -59,9 +56,6 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -A függőségek átadásának ezen módszerének előnye a nagyon gazdaságos jelölési forma volt. A [konstruktori tulajdonságok promóciójának |https://blog.nette.org/hu/php-8-0-teljes-attekintes-az-ujdonsagokrol#toc-constructor-property-promotion] bevezetésével azonban a konstruktor használata egyszerűbbnek tűnik. +Ennek a függőségátadási módnak az előnye a nagyon tömör írásmód volt. Azonban a [constructor property promotion |https://blog.nette.org/hu/php-8-0-complete-overview-of-news#toc-constructor-property-promotion] megjelenésével egyszerűbbnek tűnik a konstruktor használata. -Másrészt ez a módszer ugyanazokkal a hiányosságokkal küzd, mint a függőségek tulajdonságokba történő átadása általában: nincs kontrollunk a változó változásai felett, ugyanakkor a változó az osztály nyilvános interfészének részévé válik, ami nem kívánatos. - - -{{sitename: Legjobb gyakorlatok}} +Másrészt ez a módszer ugyanazoktól a hiányosságoktól szenved, mint a függőségek általános property-kbe történő átadása: nincs ellenőrzésünk a változóban bekövetkező változások felett, és ugyanakkor a változó az osztály nyilvános interfészének részévé válik, ami nem kívánatos. diff --git a/best-practices/hu/lets-create-contact-form.texy b/best-practices/hu/lets-create-contact-form.texy index 0f6e9b14d8..3ceafb1f03 100644 --- a/best-practices/hu/lets-create-contact-form.texy +++ b/best-practices/hu/lets-create-contact-form.texy @@ -1,12 +1,12 @@ -Hozzunk létre egy kapcsolatfelvételi űrlapot -******************************************** +Kapcsolatfelvételi űrlap létrehozása +************************************ .[perex] -Nézzük meg, hogyan hozhatunk létre egy kapcsolatfelvételi űrlapot a Nette-ben, beleértve annak e-mailben történő elküldését is. Akkor csináljuk! +Megnézzük, hogyan hozzunk létre egy kapcsolatfelvételi űrlapot a Nette-ben, beleértve az e-mail küldést is. Vágjunk bele! -Először is létre kell hoznunk egy új projektet. Ahogy a [Kezdő lépések |nette:installation] oldal elmagyarázza. Ezután pedig elkezdhetjük létrehozni az űrlapot. +Először létre kell hoznunk egy új projektet. Hogy hogyan, azt az [Első lépések |nette:installation] oldal magyarázza el. Ezután elkezdhetjük az űrlap létrehozását. -A legegyszerűbb, ha [közvetlenül a Presenterben |forms:in-presenter] hozzuk létre az [űrlapot |forms:in-presenter]. Használhatjuk az előre elkészített `HomePresenter`. Hozzáadjuk az űrlapot reprezentáló `contactForm` komponenst. Ezt úgy tesszük, hogy a `createComponentContactForm()` gyári metódust írjuk be a komponens előállítását végző kódba: +A legegyszerűbb módja az [űrlap létrehozása közvetlenül a presenterben |forms:in-presenter]. Használhatjuk az előkészített `HomePresenter`-t. Hozzáadjuk a `contactForm` komponenst, amely az űrlapot képviseli. Ezt úgy tesszük, hogy a kódba beírjuk a `createComponentContactForm()` factory metódust, amely létrehozza a komponenst: ```php use Nette\Application\UI\Form; @@ -17,37 +17,35 @@ class HomePresenter extends Presenter protected function createComponentContactForm(): Form { $form = new Form; - $form->addText('name', 'Name:') - ->setRequired('Enter your name'); + $form->addText('name', 'Név:') + ->setRequired('Adja meg a nevét'); $form->addEmail('email', 'E-mail:') - ->setRequired('Enter your e-mail'); - $form->addTextarea('message', 'Message:') - ->setRequired('Enter message'); - $form->addSubmit('send', 'Send'); + ->setRequired('Adja meg az e-mail címét'); + $form->addTextarea('message', 'Üzenet:') + ->setRequired('Adja meg az üzenetet'); + $form->addSubmit('send', 'Küldés'); $form->onSuccess[] = [$this, 'contactFormSucceeded']; return $form; } public function contactFormSucceeded(Form $form, $data): void { - // sending an email + // e-mail küldése } } ``` -Mint látható, két metódust hoztunk létre. Az első metódus `createComponentContactForm()` létrehoz egy új űrlapot. Ez rendelkezik a név, az e-mail és az üzenet mezőivel, amelyeket a `addText()`, `addEmail()` és `addTextArea()` metódusokkal adunk hozzá. Hozzáadtunk egy gombot is az űrlap elküldéséhez. -De mi van akkor, ha a felhasználó nem tölt ki néhány mezőt? Ebben az esetben tudatnunk kell vele, hogy az adott mező kötelezően kitöltendő. Ezt a `setRequired()` metódussal tettük meg. -Végül hozzáadtunk egy `onSuccess`[eseményt |nette:glossary#events] is, amely akkor lép működésbe, ha az űrlapot sikeresen elküldtük. A mi esetünkben meghívja a `contactFormSucceeded` metódust , amely a beküldött űrlap feldolgozásáról gondoskodik. Ezt is hozzáadjuk a kódhoz egy pillanat múlva. +Amint látja, két metódust hoztunk létre. Az első, `createComponentContactForm()` metódus létrehoz egy új űrlapot. Ennek vannak mezői a név, e-mail és üzenet számára, amelyeket az `addText()`, `addEmail()` és `addTextArea()` metódusokkal adunk hozzá. Hozzáadtunk egy gombot is az űrlap elküldéséhez. De mi van, ha a felhasználó nem tölt ki valamelyik mezőt? Ebben az esetben tudatnunk kell vele, hogy ez egy kötelező mező. Ezt a `setRequired()` metódussal értük el. Végül hozzáadtuk az [onSuccess |nette:glossary#Eventek események] eseményt is, amely akkor fut le, ha az űrlapot sikeresen elküldték. Esetünkben a `contactFormSucceeded` metódust hívja meg, amely gondoskodik az elküldött űrlap feldolgozásáról. Ezt hamarosan kiegészítjük a kódban. -Legyen a `contantForm` komponens megjelenítve a `templates/Home/default.latte` sablonban: +A `contactForm` komponenst a `Home/default.latte` sablonban rajzoltatjuk ki: ```latte {block content} -<h1>Contant Form</h1> +<h1>Kapcsolatfelvételi űrlap</h1> {control contactForm} ``` -Magához az e-mail elküldéséhez hozzunk létre egy új osztályt `ContactFacade` néven, és helyezzük el a `app/Model/ContactFacade.php` fájlban: +Magához az e-mail küldéshez létrehozunk egy új osztályt, amelyet `ContactFacade`-nek nevezünk el, és az `app/Model/ContactFacade.php` fájlba helyezzük: ```php <?php @@ -68,9 +66,9 @@ class ContactFacade public function sendMessage(string $email, string $name, string $message): void { $mail = new Message; - $mail->addTo('admin@example.com') // your email + $mail->addTo('admin@example.com') // az Ön e-mail címe ->setFrom($email, $name) - ->setSubject('Message from the contact form') + ->setSubject('Üzenet a kapcsolatfelvételi űrlapról') ->setBody($message); $this->mailer->send($mail); @@ -78,9 +76,9 @@ class ContactFacade } ``` -A `sendMessage()` metódus fogja létrehozni és elküldeni az e-mailt. Ehhez egy úgynevezett mailert használ, amelyet függőségként ad át a konstruktoron keresztül. Olvasson többet az [e-mailek küldéséről |mail:]. +A `sendMessage()` metódus létrehozza és elküldi az e-mailt. Ehhez az úgynevezett mailert használja, amelyet függőségként kap meg a konstruktoron keresztül. Olvasson többet az [e-mailek küldéséről |mail:]. -Most visszamegyünk a prezenterhez, és befejezzük a `contactFormSucceeded()` metódust. Meghívja a `ContactFacade` osztály `sendMessage()` metódusát, és átadja neki az űrlap adatait. És hogyan kapjuk meg a `ContactFacade` objektumot ? A konstruktor fogja átadni nekünk: +Most visszatérünk a presenterhez, és befejezzük a `contactFormSucceeded()` metódust. Ez meghívja a `ContactFacade` osztály `sendMessage()` metódusát, és átadja neki az űrlap adatait. És hogyan szerezzük meg a `ContactFacade` objektumot? Megkapjuk a konstruktoron keresztül: ```php use App\Model\ContactFacade; @@ -102,36 +100,36 @@ class HomePresenter extends Presenter public function contactFormSucceeded(stdClass $data): void { $this->facade->sendMessage($data->email, $data->name, $data->message); - $this->flashMessage('The message has been sent'); + $this->flashMessage('Az üzenet elküldve'); $this->redirect('this'); } } ``` -Az e-mail elküldése után megjelenítjük a felhasználónak az úgynevezett [flash üzenetet |application:components#flash-messages], amely megerősíti, hogy az üzenet elküldésre került, majd átirányítjuk a következő oldalra, hogy az űrlapot ne lehessen újra elküldeni a böngésző *frissítésével*. +Miután az e-mail elküldésre került, még megjelenítünk a felhasználónak egy úgynevezett [flash üzenetet |application:components#Flash üzenetek], amely megerősíti, hogy az üzenet elküldésre került, majd átirányítjuk egy másik oldalra, hogy ne lehessen az űrlapot ismételten elküldeni a böngésző *frissítésével*. -Nos, ha minden működik, akkor a kapcsolatfelvételi űrlapról már tudsz e-mailt küldeni. Gratulálunk! +Nos, ha minden működik, képesnek kell lennie e-mailt küldeni a kapcsolatfelvételi űrlapjáról. Gratulálok! -HTML e-mail sablon .[#toc-html-email-template] ----------------------------------------------- +HTML e-mail sablon +------------------ -Egyelőre egy egyszerű szöveges e-mailt küldünk, amely csak az űrlap által küldött üzenetet tartalmazza. De használhatunk HTML-t az e-mailben, és vonzóbbá tehetjük azt. Létrehozunk hozzá egy sablont a Latte-ban, amelyet a `app/Model/contactEmail.latte` címre mentünk el: +Eddig egy egyszerű szöveges e-mail került elküldésre, amely csak az űrlapon elküldött üzenetet tartalmazta. Az e-mailben azonban használhatunk HTML-t, és vonzóbbá tehetjük a megjelenését. Létrehozunk hozzá egy Latte sablont, amelyet az `app/Model/contactEmail.latte` fájlba írunk: ```latte <html> - <title>Message from the contact form + Üzenet a kapcsolatfelvételi űrlapról -

    Name: {$name}

    +

    Név: {$name}

    E-mail: {$email}

    -

    Message: {$message}

    +

    Üzenet: {$message}

    ``` -Már csak a `ContactFacade` -t kell módosítani, hogy ezt a sablont használhassuk. A konstruktorban kérjük a `LatteFactory` osztályt, amely képes előállítani a `Latte\Engine` objektumot, egy [Latte sablon renderelőt |latte:develop#how-to-render-a-template]. A `renderToString()` metódust használjuk a sablon renderelésére egy fájlba, az első paraméter a sablon elérési útvonala, a második pedig a változók. +Már csak a `ContactFacade`-et kell módosítani, hogy ezt a sablont használja. A konstruktorban kérjük a `LatteFactory` osztályt, amely képes létrehozni egy `Latte\Engine` objektumot, azaz egy [Latte sablon renderelőt |latte:develop#Hogyan rendereljünk sablont]. A `renderToString()` metódussal rendereljük a sablont egy fájlba, az első paraméter a sablon elérési útja, a második pedig a változók. ```php namespace App\Model; @@ -158,7 +156,7 @@ class ContactFacade ]); $mail = new Message; - $mail->addTo('admin@example.com') // your email + $mail->addTo('admin@example.com') // az Ön e-mail címe ->setFrom($email, $name) ->setHtmlBody($body); @@ -167,15 +165,15 @@ class ContactFacade } ``` -Ezután a generált HTML e-mailt átadjuk a `setHtmlBody()` metódusnak az eredeti `setBody()` helyett. Az e-mail tárgyát sem kell megadnunk a `setSubject()` metódusban, mert a könyvtár azt az elemből veszi át. `` sablonból veszi át. +A generált HTML e-mailt ezután a `setHtmlBody()` metódusnak adjuk át az eredeti `setBody()` helyett. Szintén nem kell megadnunk az e-mail tárgyát a `setSubject()`-ben, mert a könyvtár azt a sablon `<title>` eleméből veszi át. -A konfigurálása .[#toc-configuring] ------------------------------------- +Konfiguráció +------------ -A `ContactFacade` osztály kódjában az admin e-mail címünk, a `admin@example.com` még mindig keményen kódolva van. Jobb lenne, ha áthelyeznénk a konfigurációs fájlba. Hogyan kell ezt megtenni? +A `ContactFacade` osztály kódjában még mindig fixen be van írva az adminisztrátori e-mail címünk, az `admin@example.com`. Jobb lenne ezt a konfigurációs fájlba helyezni. Hogyan tegyük ezt? -Először is módosítjuk a `ContactFacade` osztályt, és az email karakterláncot a konstruktor által átadott változóval helyettesítjük: +Először módosítjuk a `ContactFacade` osztályt, és az e-mail címet tartalmazó stringet egy konstruktoron keresztül átadott változóval helyettesítjük: ```php class ContactFacade @@ -199,28 +197,25 @@ class ContactFacade } ``` -A második lépés pedig az, hogy ennek a változónak az értékét a konfigurációba helyezzük. A `app/config/services.neon` fájlban hozzáadjuk: +A második lépés ennek a változónak az értékének megadása a konfigurációban. Az `app/config/services.neon` fájlba írjuk: ```neon services: - App\Model\ContactFacade(adminEmail: admin@example.com) ``` -És ennyi. Ha sok elem van a `services` részben, és úgy érezzük, hogy az e-mail elveszik közöttük, akkor változtathatóvá tehetjük. Módosítjuk a bejegyzést: +És kész is. Ha a `services` szekcióban sok elem lenne, és úgy éreznénk, hogy az e-mail elveszik közöttük, akkor változóvá tehetjük. Módosítjuk a bejegyzést erre: ```neon services: - App\Model\ContactFacade(adminEmail: %adminEmail%) ``` -És definiáljuk ezt a változót a `app/config/common.neon` fájlban: +És az `app/config/common.neon` fájlban definiáljuk ezt a változót: ```neon parameters: adminEmail: admin@example.com ``` -És kész! - - -{{sitename: Legjobb gyakorlatok}} +És kész is vagyunk! diff --git a/best-practices/hu/microsites.texy b/best-practices/hu/microsites.texy new file mode 100644 index 0000000000..feb5f639bb --- /dev/null +++ b/best-practices/hu/microsites.texy @@ -0,0 +1,63 @@ +Hogyan írjunk mikro-weboldalakat +******************************** + +Képzelje el, hogy gyorsan létre kell hoznia egy kis weboldalt a cége közelgő eseményére. Egyszerűnek, gyorsnak és felesleges bonyodalmaktól mentesnek kell lennie. Talán úgy gondolja, hogy egy ilyen kis projekthez nincs szüksége egy robusztus keretrendszerre. De mi van, ha a Nette keretrendszer használata alapvetően leegyszerűsítheti és felgyorsíthatja ezt a folyamatot? + +Hiszen még egyszerű weboldalak készítésekor sem akar lemondani a kényelemről. Nem akarja újra feltalálni azt, amit már egyszer megoldottak. Legyen nyugodtan lusta, és hagyja magát kényeztetni. A Nette Framework kiválóan használható mikro keretrendszerként is. + +Hogyan nézhet ki egy ilyen microsite? Például úgy, hogy a weboldal teljes kódját egyetlen `index.php` fájlba helyezzük a nyilvános mappában: + +```php +<?php + +require __DIR__ . '/../vendor/autoload.php'; + +$configurator = new Nette\Bootstrap\Configurator; +$configurator->enableTracy(__DIR__ . '/../log'); +$configurator->setTempDirectory(__DIR__ . '/../temp'); + +// hozzon létre DI konténert a config.neon konfiguráció alapján +$configurator->addConfig(__DIR__ . '/../app/config.neon'); +$container = $configurator->createContainer(); + +// beállítjuk a routingot +$router = new Nette\Application\Routers\RouteList; +$container->addService('router', $router); + +// route a https://example.com/ URL-hez +$router->addRoute('', function ($presenter, Nette\Http\Request $httpRequest) { + // érzékeljük a böngésző nyelvét és átirányítunk az /en vagy /de stb. URL-re + $supportedLangs = ['en', 'de', 'cs']; + $lang = $httpRequest->detectLanguage($supportedLangs) ?: reset($supportedLangs); + $presenter->redirectUrl("/$lang"); +}); + +// route a https://example.com/cs vagy https://example.com/en URL-hez +$router->addRoute('<lang cs|en>', function ($presenter, string $lang) { + // megjelenítjük a megfelelő sablont, például ../templates/en.latte + $template = $presenter->createTemplate() + ->setFile(__DIR__ . '/../templates/' . $lang . '.latte'); + return $template; +}); + +// indítsa el az alkalmazást! +$container->getByType(Nette\Application\Application::class)->run(); +``` + +Minden más sablon lesz, amelyek a szülő `/templates` mappában vannak tárolva. + +Az `index.php` PHP kódja először [előkészíti a környezetet |bootstrap:], majd definiálja a [route-okat |application:routing#Dinamikus routing callbackekkel], és végül elindítja az alkalmazást. Az előnye, hogy a `addRoute()` függvény második paramétere lehet egy callable, amely a megfelelő oldal megnyitása után végrehajtódik. + + +Miért használjunk Nette-t microsite-hoz? +---------------------------------------- + +- Azok a programozók, akik valaha kipróbálták a [Tracy |tracy:]-t, ma már el sem tudják képzelni, hogy nélküle programozzanak valamit. +- Mindenekelőtt azonban a [Latte |latte:] sablonrendszert fogja használni, mert már 2 oldaltól kezdve külön szeretné választani az [elrendezést és a tartalmat |latte:template-inheritance]. +- És határozottan szeretne támaszkodni az [automatikus escapelésre |latte:safety-first], hogy ne keletkezzen XSS sebezhetőség. +- A Nette azt is biztosítja, hogy hiba esetén soha ne jelenjenek meg a programozói PHP hibaüzenetek, hanem egy felhasználóbarát oldal. +- Ha visszajelzést szeretne kapni a felhasználóktól, például egy kapcsolatfelvételi űrlap formájában, akkor még hozzáadja az [űrlapokat |forms:] és az [adatbázist |database:]. +- A kitöltött űrlapokat szintén könnyedén [elküldheti e-mailben |mail:]. +- Néha hasznos lehet a [gyorsítótárazás |caching:], például ha feedeket tölt le és jelenít meg. + +Napjainkban, amikor a sebesség és a hatékonyság kulcsfontosságú, fontos, hogy olyan eszközök álljanak rendelkezésre, amelyek lehetővé teszik az eredmények elérését felesleges késedelem nélkül. A Nette keretrendszer pontosan ezt kínálja - gyors fejlesztést, biztonságot és széles körű eszközöket, mint például a Tracy és a Latte, amelyek egyszerűsítik a folyamatot. Elég telepíteni néhány Nette csomagot, és egy ilyen microsite létrehozása hirtelen gyerekjáték. És tudja, hogy sehol sem rejtőzik biztonsági rés. diff --git a/best-practices/hu/pagination.texy b/best-practices/hu/pagination.texy index eee3c4024b..286a369a9b 100644 --- a/best-practices/hu/pagination.texy +++ b/best-practices/hu/pagination.texy @@ -1,17 +1,16 @@ -Az adatbázis-eredmények lapozása -******************************** +Adatbázis eredmények lapozása +***************************** .[perex] -Webalkalmazások fejlesztése során gyakran találkozunk azzal a követelménnyel, hogy egy oldalon korlátozott számú rekordot kell kinyomtatni. +Webalkalmazások fejlesztése során nagyon gyakran találkozhat azzal a követelménnyel, hogy korlátozni kell az oldalon megjelenített elemek számát. -Ebből az állapotból akkor jövünk ki, ha az összes adatot lapozás nélkül listázzuk ki. Az adatbázisból való adatkiválasztáshoz rendelkezésünkre áll az ArticleRepository osztály, amely tartalmazza a konstruktort és a `findPublishedArticles` metódust, amely az összes megjelent cikket a megjelenés dátuma szerinti csökkenő sorrendben rendezve adja vissza. +Kezdjük azzal az állapottal, amikor minden adatot lapozás nélkül listázunk ki. Az adatok adatbázisból történő kiválasztásához van egy ArticleRepository osztályunk, amely a konstruktoron kívül tartalmaz egy `findPublishedArticles` metódust, amely visszaadja az összes publikált cikket a publikálás dátuma szerint csökkenő sorrendben. ```php namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -31,10 +30,10 @@ class ArticleRepository } ``` -A Presenterben ezután befecskendezzük a modell osztályt, és a render metódusban lekérdezzük a publikált cikkeket, amelyeket átadunk a sablonhoz: +A presenterben ezután injectáljuk a modell osztályt, és a render metódusban lekérjük a publikált cikkeket, amelyeket átadunk a sablonnak: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -53,11 +52,11 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -A sablonban gondoskodunk egy cikklista rendereléséről: +A `default.latte` sablonban pedig gondoskodunk a cikkek kiírásáról: ```latte {block content} -<h1>Articles</h1> +<h1>Cikkek</h1> <div class="articles"> {foreach $articles as $article} @@ -68,11 +67,11 @@ A sablonban gondoskodunk egy cikklista rendereléséről: ``` -Ez azonban problémákat okoz, ha a cikkek száma nő. Ekkor hasznos lesz a lapozási mechanizmus megvalósítása. +Ezzel a módszerrel ki tudjuk listázni az összes cikket, ami azonban problémákat kezd okozni, amint a cikkek száma megnő. Ebben a pillanatban válik hasznossá egy lapozó mechanizmus implementálása. -Ez biztosítja, hogy az összes cikket több oldalra osztjuk, és csak az egyik aktuális oldal cikkeit fogjuk megjeleníteni. Az oldalak teljes számát és a cikkek elosztását maga az [Paginator |utils:Paginator] számítja ki, attól függően, hogy összesen hány cikkünk van és hány cikket szeretnénk megjeleníteni az oldalon. +Ez biztosítja, hogy az összes cikk több oldalra legyen osztva, és mi csak az aktuális oldal cikkeit jelenítjük meg. Az oldalak teljes számát és a cikkek elosztását a [Paginator |utils:Paginator] maga számítja ki attól függően, hogy összesen hány cikkünk van, és hány cikket szeretnénk megjeleníteni egy oldalon. -Első lépésben módosítjuk a cikkek kinyerésére szolgáló metódust a tároló osztályban, hogy csak egyoldalas cikkeket adjon vissza. Emellett hozzáadunk egy új metódust az adatbázisban lévő cikkek teljes számának lekérdezéséhez, amire szükségünk lesz a Paginator beállításához: +Az első lépésben módosítjuk a cikkek lekérésére szolgáló metódust a repository osztályban úgy, hogy csak egy oldal cikkeit tudja visszaadni. Hozzáadunk egy metódust is az adatbázisban lévő cikkek teljes számának lekérdezésére, amelyre szükségünk lesz a Paginator beállításához: ```php namespace App\Model; @@ -100,7 +99,7 @@ class ArticleRepository } /** - * Returns the total number of published articles + * Visszaadja a publikált cikkek teljes számát */ public function getPublishedArticlesCount(): int { @@ -109,12 +108,12 @@ class ArticleRepository } ``` -A következő lépés a bemutató szerkesztése. Az aktuálisan megjelenített oldal számát továbbítjuk a render metódusnak. Abban az esetben, ha ez a szám nem része az URL-nek, akkor az alapértelmezett értéket az első oldalra kell beállítanunk. +Ezután nekilátunk a presenter módosításának. A render metódusba átadjuk az aktuálisan megjelenített oldal számát. Arra az esetre, ha ez a szám nem lenne része az URL-nek, beállítjuk az első oldal alapértelmezett értékét. -A render metódust kibővítjük a Paginator példány megszerzésével, beállításával és a sablonban megjelenítendő megfelelő cikkek kiválasztásával is. A HomePresenter így fog kinézni: +Továbbá kibővítjük a render metódust a Paginator példányának megszerzésével, beállításával és a sablonban megjelenítendő megfelelő cikkek kiválasztásával. A HomePresenter a módosítások után így fog kinézni: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -128,31 +127,31 @@ class HomePresenter extends Nette\Application\UI\Presenter public function renderDefault(int $page = 1): void { - // Megkeressük a közzétett cikkek teljes számát. + // Lekérdezzük a publikált cikkek teljes számát $articlesCount = $this->articleRepository->getPublishedArticlesCount(); - // Elkészítjük a Paginator példányt és beállítjuk. + // Létrehozunk egy Paginator példányt és beállítjuk $paginator = new Nette\Utils\Paginator; - $paginator->setItemCount($articlesCount); // összes cikk száma - $paginator->setItemsPerPage(10); // cikkek oldalanként - $paginator->setPage($page); // tényleges oldalszám + $paginator->setItemCount($articlesCount); // cikkek teljes száma + $paginator->setItemsPerPage(10); // elemek száma oldalanként + $paginator->setPage($page); // aktuális oldal száma - // A Paginator számításai alapján megkeressük a cikkek egy korlátozott halmazát az adatbázisból. + // Az adatbázisból lekérünk egy korlátozott cikkhalmazt a Paginator számítása szerint $articles = $this->articleRepository->findPublishedArticles($paginator->getLength(), $paginator->getOffset()); - // amit átadunk a sablonnak + // amelyet átadunk a sablonnak $this->template->articles = $articles; - // és magának a Paginatornak is, hogy megjelenítse a lapozási opciókat. + // és magát a Paginatort is a lapozási lehetőségek megjelenítéséhez $this->template->paginator = $paginator; } } ``` -A sablon már egy oldalon belül iterálja a cikkeket, csak a lapozási linkeket kell hozzáadni: +A sablonunk most már csak egy oldal cikkein iterál, elég hozzáadnunk a lapozó linkeket: ```latte {block content} -<h1>Articles</h1> +<h1>Cikkek</h1> <div class="articles"> {foreach $articles as $article} @@ -163,34 +162,33 @@ A sablon már egy oldalon belül iterálja a cikkeket, csak a lapozási linkeket <div class="pagination"> {if !$paginator->isFirst()} - <a n:href="default, 1">First</a> + <a n:href="default, 1">Első</a>  |  - <a n:href="default, $paginator->page-1">Previous</a> + <a n:href="default, $paginator->page-1">Előző</a>  |  {/if} - Page {$paginator->getPage()} of {$paginator->getPageCount()} + Oldal {$paginator->getPage()} / {$paginator->getPageCount()} {if !$paginator->isLast()}  |  - <a n:href="default, $paginator->getPage() + 1">Next</a> + <a n:href="default, $paginator->getPage() + 1">Következő</a>  |  - <a n:href="default, $paginator->getPageCount()">Last</a> + <a n:href="default, $paginator->getPageCount()">Utolsó</a> {/if} </div> ``` -Így adtunk hozzá oldalszámozást a Paginator segítségével. Ha a [Nette Database |database:explorer] [Core |database:core] [helyett a Nette Database Explorer-t |database:explorer] használjuk adatbázis-rétegként, akkor Paginator nélkül is képesek vagyunk a lapozás megvalósítására. A `Nette\Database\Table\Selection` osztály tartalmazza a [Paginatorból |api:Nette\Database\Table\Selection::_ page] átvett paginálási logikával rendelkező [page |api:Nette\Database\Table\Selection::_ page] metódust. +Így egészítettük ki az oldalt a Paginator segítségével történő lapozás lehetőségével. Abban az esetben, ha a [Nette Database Core |database:sql-way] helyett adatbázisrétegként a [Nette Database Explorer |database:explorer]-t használjuk, képesek vagyunk implementálni a lapozást Paginator használata nélkül is. A `Nette\Database\Table\Selection` osztály ugyanis tartalmaz egy [page |api:Nette\Database\Table\Selection::_page] metódust a Paginatorból átvett lapozási logikával. -A tároló így fog kinézni: +A repository ebben az implementációs módban így fog kinézni: ```php namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -198,7 +196,6 @@ class ArticleRepository ) { } - public function findPublishedArticles(): Nette\Database\Table\Selection { return $this->database->table('articles') @@ -208,10 +205,10 @@ class ArticleRepository } ``` -Nem kell Paginátort létrehoznunk a Presenterben, helyette az adattár által visszaadott `Selection` objektum metódusát fogjuk használni: +A presenterben nem kell Paginatort létrehoznunk, helyette a `Selection` osztály metódusát használjuk, amelyet a repository ad vissza: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -225,25 +222,25 @@ class HomePresenter extends Nette\Application\UI\Presenter public function renderDefault(int $page = 1): void { - // Megkeressük a közzétett cikkeket + // Lekérjük a publikált cikkeket $articles = $this->articleRepository->findPublishedArticles(); - // és az oldal által korlátozott részüket az oldal módszer számításával adjuk át a sablonnak. + // és a sablonba csak azok egy részét küldjük el, amelyet a page metódus számítása korlátoz $lastPage = 0; $this->template->articles = $articles->page($page, 10, $lastPage); - // és a lapozási lehetőségek megjelenítéséhez szükséges adatokat is. + // és a szükséges adatokat is a lapozási lehetőségek megjelenítéséhez $this->template->page = $page; $this->template->lastPage = $lastPage; } } ``` -Mivel nem használunk Paginator-t, meg kell szerkesztenünk a lapozási linkeket megjelenítő részt: +Mivel most nem küldünk Paginatort a sablonba, módosítjuk a lapozó linkeket megjelenítő részt: ```latte {block content} -<h1>Articles</h1> +<h1>Cikkek</h1> <div class="articles"> {foreach $articles as $article} @@ -254,24 +251,23 @@ Mivel nem használunk Paginator-t, meg kell szerkesztenünk a lapozási linkeket <div class="pagination"> {if $page > 1} - <a n:href="default, 1">First</a> + <a n:href="default, 1">Első</a>  |  - <a n:href="default, $page - 1">Previous</a> + <a n:href="default, $page - 1">Előző</a>  |  {/if} - Page {$page} of {$lastPage} + Oldal {$page} / {$lastPage} {if $page < $lastPage}  |  - <a n:href="default, $page + 1">Next</a> + <a n:href="default, $page + 1">Következő</a>  |  - <a n:href="default, $lastPage">Last</a> + <a n:href="default, $lastPage">Utolsó</a> {/if} </div> ``` -Így Paginator használata nélkül valósítottunk meg egy lapozási mechanizmust. +Ezzel a módszerrel implementáltuk a lapozó mechanizmust Paginator használata nélkül. {{priority: -1}} -{{sitename: Legjobb gyakorlatok}} diff --git a/best-practices/hu/passing-settings-to-presenters.texy b/best-practices/hu/passing-settings-to-presenters.texy index 773e1beeb6..077d860afd 100644 --- a/best-practices/hu/passing-settings-to-presenters.texy +++ b/best-practices/hu/passing-settings-to-presenters.texy @@ -1,10 +1,10 @@ -Beállítások átadása az előadóknak -********************************* +Beállítások átadása presentereknek +********************************** .[perex] -Szüksége van arra, hogy olyan argumentumokat adjon át a prezentereknek, amelyek nem objektumok (pl. információ arról, hogy debug üzemmódban fut-e, könyvtárak elérési útvonalai stb.), és így nem adhatók át automatikusan az autowiring által? A megoldás az, hogy ezeket egy `Settings` objektumba kapszulázza. +Szüksége van arra, hogy olyan argumentumokat adjon át a presentereknek, amelyek nem objektumok (pl. információ arról, hogy debug módban fut-e, könyvtárak elérési útjai stb.), és ezért nem adhatók át automatikusan autowiring segítségével? A megoldás az, hogy becsomagolja őket egy `Settings` objektumba. -A `Settings` szolgáltatás egy nagyon egyszerű, mégis hasznos módja annak, hogy egy futó alkalmazásról információt adjunk a bemutatóknak. Konkrét formája teljes mértékben az Ön konkrét igényeitől függ. Példa: +A `Settings` szolgáltatás egy nagyon egyszerű, mégis hasznos módja annak, hogy információkat szolgáltassunk a futó alkalmazásról a presentereknek. Konkrét formája kizárólag az Ön igényeitől függ. Példa: ```php namespace App; @@ -12,7 +12,7 @@ namespace App; class Settings { public function __construct( - // a PHP 8.1 óta lehetőség van readonly megadására. + // PHP 8.1-től kezdve megadható a readonly public bool $debugMode, public string $appDir, // és így tovább @@ -20,7 +20,7 @@ class Settings } ``` -Példa a konfigurációhoz történő regisztrációra: +Példa a konfigurációba történő regisztrálásra: ```neon services: @@ -30,7 +30,7 @@ services: ) ``` -Ha az előadónak szüksége van az e szolgáltatás által biztosított információkra, egyszerűen elkéri azokat a konstruktorban: +Amikor egy presenternek szüksége van az e szolgáltatás által nyújtott információkra, egyszerűen elkéri a konstruktorban: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -47,5 +47,3 @@ class MyPresenter extends Nette\Application\UI\Presenter } } ``` - -{{sitename: Legjobb gyakorlatok}} diff --git a/best-practices/hu/post-links.texy b/best-practices/hu/post-links.texy new file mode 100644 index 0000000000..8faa625780 --- /dev/null +++ b/best-practices/hu/post-links.texy @@ -0,0 +1,56 @@ +Hogyan használjuk helyesen a POST linkeket +****************************************** + +.[perex] +Webalkalmazásokban, különösen adminisztrációs felületeken, alapvető szabálynak kellene lennie, hogy a szerver állapotát megváltoztató műveleteket ne a GET HTTP metódussal végezzük. Ahogy a metódus neve is sugallja, a GET csak adatok lekérésére szolgál, nem pedig azok megváltoztatására. Olyan műveletekhez, mint például a rekordok törlése, célszerűbb a POST metódust használni. Bár ideális a DELETE metódus lenne, de azt JavaScript nélkül nem lehet meghívni, ezért történelmileg a POST-ot használják. + +Hogyan tegyük ezt a gyakorlatban? Használja ezt az egyszerű trükköt. A sablon elején hozzon létre egy segédűrlapot `postForm` azonosítóval, amelyet aztán a törlő gombokhoz használ: + +```latte .{file:@layout.latte} +<form method="post" id="postForm"></form> +``` + +Ennek az űrlapnak köszönhetően a klasszikus `<a>` link helyett használhat egy `<button>` gombot, amelyet vizuálisan úgy lehet módosítani, hogy úgy nézzen ki, mint egy normál link. Például a Bootstrap CSS keretrendszer `btn btn-link` osztályokat kínál, amelyekkel elérheti, hogy a gomb vizuálisan ne különbözzön a többi linktől. A `form="postForm"` attribútummal összekapcsoljuk az előkészített űrlappal: + +```latte .{file:admin.latte} +<table> + <tr n:foreach="$posts as $post"> + <td>{$post->title}</td> + <td> + <button class="btn btn-link" form="postForm" formaction="{link delete $post->id}">törlés</button> + <!-- <a n:href="delete $post->id">törlés</a> helyett --> + </td> + </tr> +</table> +``` + +A linkre kattintva most a `delete` akció hívódik meg. Annak biztosítására, hogy a kérések csak a POST metóduson keresztül és ugyanarról a domainről érkezzenek (ami hatékony védelem a CSRF támadások ellen), használja a `#[Requires]` attribútumot: + +```php .{file:AdminPresenter.php} +use Nette\Application\Attributes\Requires; + +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST', sameOrigin: true)] + public function actionDelete(int $id): void + { + $this->facade->deletePost($id); // hipotetikus kód a rekord törlésére + $this->redirect('default'); + } +} +``` + +Az attribútum a Nette Application 3.2 óta létezik, és további lehetőségeiről a [Hogyan használjuk a #Requires attribútumot |attribute-requires] oldalon olvashat többet. + +Ha az `actionDelete()` akció helyett a `handleDelete()` signált használná, nem szükséges megadni a `sameOrigin: true`-t, mert a signáloknak ez a védelme alapértelmezetten be van állítva: + +```php .{file:AdminPresenter.php} +#[Requires(methods: 'POST')] +public function handleDelete(int $id): void +{ + $this->facade->deletePost($id); + $this->redirect('this'); +} +``` + +Ez a megközelítés nemcsak javítja az alkalmazás biztonságát, hanem hozzájárul a helyes webes szabványok és gyakorlatok betartásához is. A POST metódusok használatával az állapotot megváltoztató műveletekhez robusztusabb és biztonságosabb alkalmazást érhet el. diff --git a/best-practices/hu/presenter-traits.texy b/best-practices/hu/presenter-traits.texy index 15a65bf31f..a8ec3c6482 100644 --- a/best-practices/hu/presenter-traits.texy +++ b/best-practices/hu/presenter-traits.texy @@ -1,14 +1,14 @@ -Előadók összeállítása tulajdonságokból -************************************** +Presenterek összeállítása traitekkel +************************************ .[perex] -Ha ugyanazt a kódot több prezenterben kell implementálnunk (pl. annak ellenőrzése, hogy a felhasználó be van-e jelentkezve), akkor csábító, hogy a kódot egy közös ősben helyezzük el. A második lehetőség az egycélú tulajdonságok létrehozása. +Ha több presenterben ugyanazt a kódot kell implementálnunk (pl. annak ellenőrzése, hogy a felhasználó be van-e jelentkezve), kézenfekvő a kódot egy közös ősbe helyezni. A második lehetőség egycélú [traitek |nette:introduction-to-object-oriented-programming#Traitek] létrehozása. -Ennek a megoldásnak az az előnye, hogy minden egyes prezenter csak azokat a tulajdonságokat használhatja, amelyekre ténylegesen szüksége van, míg a többszörös öröklés nem lehetséges a PHP-ben. +Ennek a megoldásnak az az előnye, hogy minden presenter pontosan azokat a traiteket használhatja, amelyekre valóban szüksége van, míg a többszörös öröklődés PHP-ban nem lehetséges. -Ezek a vonások kihasználhatják azt a tényt, hogy a prezenter létrehozásakor az összes [injektált metódus |inject-method-attribute#inject methods] egymás után hívódik meg. Csak arra kell ügyelni, hogy minden egyes inject metódus neve egyedi legyen. +Ezek a traitek kihasználhatják azt a tényt, hogy a presenter létrehozásakor sorban meghívódnak az összes [inject metódus |inject-method-attribute#inject metódusok]. Csak arra kell ügyelni, hogy minden inject metódus neve egyedi legyen. -A vonások az inicializálási kódot [az onStartup vagy onRender |application:presenters#Events] eseményekbe akaszthatják. +A traitek inicializáló kódot csatolhatnak az [onStartup vagy onRender |application:presenters#Események] eseményekhez. Példák: @@ -36,7 +36,7 @@ trait StandardTemplateFilters } ``` -Az előadó egyszerűen felhasználja ezeket a tulajdonságokat: +A presenter ezután egyszerűen használja ezeket a traiteket: ```php class ArticlePresenter extends Nette\Application\UI\Presenter @@ -45,6 +45,3 @@ class ArticlePresenter extends Nette\Application\UI\Presenter use RequireLoggedUser; } ``` - - -{{sitename: Legjobb gyakorlatok}} diff --git a/best-practices/hu/restore-request.texy b/best-practices/hu/restore-request.texy index c1f770beba..9de22622f4 100644 --- a/best-practices/hu/restore-request.texy +++ b/best-practices/hu/restore-request.texy @@ -1,17 +1,16 @@ -Hogyan térhetek vissza egy korábbi oldalra? -******************************************* +Hogyan térjünk vissza egy korábbi oldalra? +****************************************** .[perex] -Mi történik, ha egy felhasználó kitölt egy űrlapot, és a bejelentkezése lejár? Az adatok elvesztésének elkerülése érdekében a bejelentkezési oldalra való átirányítás előtt elmentjük az adatokat a munkamenetbe. A Nette-ben ez gyerekjáték. +Mi van, ha a felhasználó egy űrlapot tölt ki, és lejár a bejelentkezése? Hogy ne vesszenek el az adatok, a bejelentkezési oldalra történő átirányítás előtt az adatokat a sessionbe mentjük. A Nette-ben ez gyerekjáték. -Az aktuális kérést a munkamenetben tárolhatjuk a `storeRequest()` metódus segítségével, amely rövid sztringként adja vissza az azonosítóját. A metódus tárolja az aktuális bemutató nevét, a nézetet és annak paramétereit. -Ha egy űrlapot is elküldtünk, akkor a mezők értékei (a feltöltött fájlok kivételével) szintén elmentésre kerülnek. +Az aktuális kérést a `storeRequest()` metódussal lehet a sessionbe menteni, amely visszaadja annak azonosítóját egy rövid string formájában. A metódus elmenti az aktuális presenter nevét, a view-t és annak paramétereit. Abban az esetben, ha egy űrlap is elküldésre került, a mezők tartalma is elmentésre kerül (a feltöltött fájlok kivételével). -A kérést a `restoreRequest($key)` metódus állítja vissza, amelynek átadjuk a visszakapott azonosítót. Ez átirányít az eredeti prezenterre és nézetre. Ha azonban a mentett kérés űrlapbeadást tartalmaz, akkor a `forward()` metódussal továbbítjuk az eredeti prezenterhez, átadjuk az űrlapnak a korábban kitöltött értékeket, és hagyjuk, hogy újra kirajzolódjon. Ez lehetővé teszi, hogy a felhasználó újra elküldje az űrlapot, és nem vesznek el adatok. +A kérés visszaállítását a `restoreRequest($key)` metódus végzi, amelynek átadjuk a kapott azonosítót. Ez átirányít az eredeti presenterhez és view-hoz. Ha azonban a mentett kérés egy űrlap elküldését tartalmazza, akkor az eredeti presenterhez a `forward()` metódussal lép át, átadja az űrlapnak a korábban kitöltött értékeket, és újra kirajzoltatja azt. Így a felhasználónak lehetősége van újra elküldeni az űrlapot, és nem vesznek el adatok. -Fontos, hogy a `restoreRequest()` ellenőrzi, hogy az újonnan bejelentkezett felhasználó ugyanaz-e, aki eredetileg kitöltötte az űrlapot. Ha nem, akkor elveti a kérést, és nem tesz semmit. +Fontos, hogy a `restoreRequest()` ellenőrzi, hogy az újonnan bejelentkezett felhasználó ugyanaz-e, aki eredetileg kitöltötte az űrlapot. Ha nem, eldobja a kérést, és nem tesz semmit. -Mutassuk be mindezt egy példával. Legyen egy prezenterünk `AdminPresenter`, amelyben adatokat szerkesztünk, és amelynek `startup()` metódusa ellenőrzi, hogy a felhasználó be van-e jelentkezve. Ha nem, akkor átirányítjuk a `SignPresenter` címre. Ezzel egyidejűleg elmentjük az aktuális kérést, és elküldjük a kulcsát a `SignPresenter` címre. +Mutassuk be mindezt egy példán. Legyen egy `AdminPresenter` presenterünk, amelyben adatokat szerkesztünk, és amelynek `startup()` metódusában ellenőrizzük, hogy a felhasználó be van-e jelentkezve. Ha nincs, átirányítjuk a `SignPresenter`-re. Ezzel egyidejűleg elmentjük az aktuális kérést, és annak kulcsát elküldjük a `SignPresenter`-nek. ```php class AdminPresenter extends Nette\Application\UI\Presenter @@ -27,7 +26,7 @@ class AdminPresenter extends Nette\Application\UI\Presenter } ``` -A `SignPresenter` prezenter a bejelentkezési űrlapon kívül tartalmazni fog egy állandó `$backlink` paramétert, amelyre a kulcsot írjuk. Mivel a paraméter állandó, a bejelentkezési űrlap elküldése után is átkerül. +A `SignPresenter` a bejelentkezési űrlapon kívül tartalmazni fog egy `$backlink` perzisztens paramétert is, amelybe a kulcs beíródik. Mivel a paraméter perzisztens, a bejelentkezési űrlap elküldése után is átadásra kerül. ```php @@ -41,14 +40,14 @@ class SignPresenter extends Nette\Application\UI\Presenter protected function createComponentSignInForm() { $form = new Nette\Application\UI\Form; - // ... űrlapmezők hozzáadása ... + // ... hozzáadjuk az űrlap mezőit ... $form->onSuccess[] = [$this, 'signInFormSubmitted']; return $form; } public function signInFormSubmitted($form) { - // ... itt bejelentkezik a felhasználó ... + // ... itt bejelentkeztetjük a felhasználót ... $this->restoreRequest($this->backlink); $this->redirect('Admin:'); @@ -56,9 +55,8 @@ class SignPresenter extends Nette\Application\UI\Presenter } ``` -A mentett kérés kulcsát átadjuk a `restoreRequest()` metódusnak, és az átirányít (vagy továbbít) az eredeti bemutatóhoz. +A `restoreRequest()` metódusnak átadjuk a mentett kérés kulcsát, és az átirányít (vagy átlép) az eredeti presenterhez. -Ha azonban a kulcs érvénytelen (például már nem létezik a munkamenetben), a módszer nem tesz semmit. Így a következő hívás a `$this->redirect('Admin:')`, amely átirányít a `AdminPresenter` címre. +Ha azonban a kulcs érvénytelen (például már nem létezik a sessionben), a metódus nem tesz semmit. Ezt követi a `$this->redirect('Admin:')` hívása, amely átirányít az `AdminPresenter`-re. {{priority: -1}} -{{sitename: Legjobb gyakorlatok}} diff --git a/best-practices/it/@home.texy b/best-practices/it/@home.texy index 10f16cec1d..7a11feda65 100644 --- a/best-practices/it/@home.texy +++ b/best-practices/it/@home.texy @@ -1,8 +1,8 @@ -Migliori pratiche +Guide e procedure ***************** .[perex] -Tutorial, soluzioni a problemi comuni e best practice per Nette. +Guide, soluzioni per compiti comuni e *best practices* per Nette. <div class=documentation> @@ -11,47 +11,51 @@ Tutorial, soluzioni a problemi comuni e best practice per Nette. Applicazione Nette ------------------ -- [Iniettare metodi e attributi |inject-method-attribute] -- [Comporre presentatori da tratti |presenter-traits] -- [Passare le impostazioni ai presentatori |passing-settings-to-presenters] +- [Metodi e attributi inject |inject-method-attribute] +- [Composizione dei presenter da trait |presenter-traits] +- [Passare le impostazioni ai presenter |passing-settings-to-presenters] - [Come tornare a una pagina precedente |restore-request] -- [Impaginazione dei risultati del database |Pagination] +- [Paginazione dei risultati del database |pagination] - [Snippet dinamici |dynamic-snippets] +- [Come usare l'attributo #Requires |attribute-requires] +- [Come usare correttamente i link POST |post-links] </div> <div> -Moduli ------- -- [Riutilizzo dei moduli |form-reuse] -- [Modulo per la creazione e la modifica di un record |creating-editing-form] -- [Creiamo un modulo di contatto |lets-create-contact-form] -- [Caselle di selezione dipendenti |https://blog.nette.org/it/caselle-di-selezione-dipendenti-in-modo-elegante-in-nette-e-puro-js] +Form +---- +- [Riutilizzo dei form |form-reuse] +- [Form per creare e modificare un record |creating-editing-form] +- [Creiamo un form di contatto |lets-create-contact-form] +- [Selectbox dipendenti |https://blog.nette.org/it/dependent-selectboxes-elegantly-in-nette-and-pure-js] </div> <div> -Comune ------- -- [Come caricare il file di configurazione |bootstrap:] -- [Perché Nette usa la notazione costante PascalCase? |https://blog.nette.org/it/per-non-urlare-nel-codice] -- [Perché Nette non usa il suffisso Interface? |https://blog.nette.org/it/i-prefissi-e-i-suffissi-non-appartengono-ai-nomi-delle-interfacce] -- [Suggerimenti per l'uso di Composer |composer] -- [Suggerimenti su editor e strumenti |editors-and-tools] +Generale +-------- +- [Come caricare un file di configurazione |bootstrap:] +- [Come scrivere micro-siti |microsites] +- [Perché Nette usa la notazione PascalCase per le costanti? |https://blog.nette.org/it/for-less-screaming-in-the-code] +- [Perché Nette non usa il suffisso Interface? |https://blog.nette.org/it/prefixes-and-suffixes-do-not-belong-in-interface-names] +- [Composer: suggerimenti per l'uso |composer] +- [Suggerimenti per editor e strumenti |editors-and-tools] +- [Introduzione alla programmazione orientata agli oggetti |nette:introduction-to-object-oriented-programming] </div> <div> -Soluzione campione ------------------- -- [Esempi di Nette |https://github.com/nette-examples] -- [Dottrina & Nette |https://contributte.org/nettrine/] -- [Esempi di contributte |https://contributte.org/examples.html] -- [Sito web di Doctrine ORM |https://github.com/MinecordNetwork/Website] -- [Inizio rapido |quickstart:] +Soluzioni di esempio +-------------------- +- [Nette examples |https://github.com/nette-examples] +- [Doctrine & Nette |https://contributte.org/nettrine/] +- [Contributte examples |https://contributte.org/examples.html] +- [Doctrine ORM Website |https://github.com/MinecordNetwork/Website] +- [Quick start |quickstart:] </div> <div> @@ -59,10 +63,7 @@ Soluzione campione Video ----- -Nel "Nette Framework YouTube Channel":https://www.youtube.com/user/NetteFramework si trovano centinaia di registrazioni di Posobota e video su Nette. +Centinaia di registrazioni dagli Ultimi Sabati e video su Nette si trovano sotto un unico tetto sul "canale Youtube di Nette Framework":https://www.youtube.com/user/NetteFramework. </div> </div> - -{{sitename: Migliori pratiche}} -{{leftbar: www:@menu-common}} diff --git a/best-practices/it/@meta.texy b/best-practices/it/@meta.texy new file mode 100644 index 0000000000..6cd5c59394 --- /dev/null +++ b/best-practices/it/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Guide e procedure}} +{{leftbar: www:@menu-common}} diff --git a/best-practices/it/attribute-requires.texy b/best-practices/it/attribute-requires.texy new file mode 100644 index 0000000000..a2d48132d6 --- /dev/null +++ b/best-practices/it/attribute-requires.texy @@ -0,0 +1,177 @@ +Come usare l'attributo `#[Requires]` +************************************ + +.[perex] +Quando scrivi un'applicazione web, ti imbatterai spesso nella necessità di limitare l'accesso a determinate parti della tua applicazione. Forse vuoi che alcune richieste possano inviare dati solo tramite un form (cioè con il metodo POST), o che siano accessibili solo per chiamate AJAX. In Nette Framework 3.2 è apparso un nuovo strumento che ti permette di impostare tali limitazioni in modo molto elegante e chiaro: l'attributo `#[Requires]`. + +L'attributo è un marcatore speciale in PHP che aggiungi prima della definizione di una classe o di un metodo. Poiché si tratta effettivamente di una classe, affinché gli esempi seguenti funzionino, è necessario specificare la clausola use: + +```php +use Nette\Application\Attributes\Requires; +``` + +L'attributo `#[Requires]` può essere utilizzato sulla classe stessa del presenter e anche su questi metodi: + +- `action<Action>()` +- `render<View>()` +- `handle<Signal>()` +- `createComponent<Name>()` + +Gli ultimi due metodi riguardano anche i componenti, quindi puoi usare l'attributo anche su di essi. + +Se le condizioni specificate dall'attributo non sono soddisfatte, verrà generato un errore HTTP 4xx. + + +Metodi HTTP +----------- + +Puoi specificare quali metodi HTTP (come GET, POST, ecc.) sono consentiti per l'accesso. Ad esempio, se vuoi consentire l'accesso solo inviando un form, imposta: + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +Perché dovresti usare POST invece di GET per azioni che modificano lo stato e come farlo? [Leggi la guida |post-links]. + +Puoi specificare un metodo o un array di metodi. Un caso speciale è il valore `'*'`, che consente tutti i metodi, cosa che i presenter standard [non permettono per motivi di sicurezza |application:presenters#Controllo del metodo HTTP]. + + +Chiamata AJAX +------------- + +Se vuoi che il presenter o il metodo sia disponibile solo per le richieste AJAX, usa: + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Stessa origine +-------------- + +Per aumentare la sicurezza, puoi richiedere che la richiesta sia effettuata dallo stesso dominio. Ciò previene la [vulnerabilità CSRF |nette:vulnerability-protection#Cross-Site Request Forgery CSRF]: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Per i metodi `handle<Signal>()`, l'accesso dallo stesso dominio è richiesto automaticamente. Quindi, se al contrario vuoi consentire l'accesso da qualsiasi dominio, specifica: + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Accesso tramite forward +----------------------- + +A volte è utile limitare l'accesso a un presenter in modo che sia disponibile solo indirettamente, ad esempio utilizzando il metodo `forward()` o `switch()` da un altro presenter. In questo modo si proteggono, ad esempio, gli error-presenter, affinché non possano essere invocati dall'URL: + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +In pratica, è spesso necessario contrassegnare determinate view, alle quali si può accedere solo in base alla logica nel presenter. Cioè, di nuovo, affinché non possano essere aperte direttamente: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = $this->facade->getProduct($id); + if (!$product) { + $this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Azioni specifiche +----------------- + +Puoi anche limitare che un certo codice, ad esempio la creazione di un componente, sia disponibile solo per azioni specifiche nel presenter: + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +Nel caso di una singola azione, non è necessario scrivere un array: `#[Requires(actions: 'default')]` + + +Attributi personalizzati +------------------------ + +Se vuoi usare l'attributo `#[Requires]` ripetutamente con le stesse impostazioni, puoi creare un tuo attributo personalizzato che erediterà `#[Requires]` e lo imposterà secondo le tue esigenze. + +Ad esempio, `#[SingleAction]` consentirà l'accesso solo tramite l'azione `default`: + +```php +#[\Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Oppure `#[RestMethods]` consentirà l'accesso tramite tutti i metodi HTTP utilizzati per le API REST: + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Conclusione +----------- + +L'attributo `#[Requires]` ti offre grande flessibilità e controllo su come sono accessibili le tue pagine web. Utilizzando regole semplici ma potenti, puoi aumentare la sicurezza e il corretto funzionamento della tua applicazione. Come vedi, l'uso degli attributi in Nette può non solo facilitare il tuo lavoro, ma anche renderlo più sicuro. diff --git a/best-practices/it/composer.texy b/best-practices/it/composer.texy index c30fc6d908..8eb011319e 100644 --- a/best-practices/it/composer.texy +++ b/best-practices/it/composer.texy @@ -1,44 +1,44 @@ -Suggerimenti per l'uso di Composer -********************************** +Composer: suggerimenti per l'uso +******************************** <div class=perex> -Composer è uno strumento per la gestione delle dipendenze in PHP. Permette di dichiarare le librerie da cui dipende il progetto e le installerà e aggiornerà per voi. Impareremo: +Composer è uno strumento per la gestione delle dipendenze in PHP. Ci permette di elencare le librerie da cui dipende il nostro progetto e si occuperà di installarle e aggiornarle per noi. Vedremo: - come installare Composer -- usarlo in un progetto nuovo o esistente +- il suo utilizzo in un progetto nuovo o esistente </div> -Installazione .[#toc-installation] -================================== +Installazione +============= -Composer è un file eseguibile `.phar` che si scarica e si installa come segue. +Composer è un file `.phar` eseguibile, che si scarica e si installa nel seguente modo: -Windows .[#toc-windows] ------------------------ +Windows +------- -Utilizzare il programma di installazione ufficiale [Composer-Setup.exe |https://getcomposer.org/Composer-Setup.exe]. +Utilizzare l'installer ufficiale [Composer-Setup.exe |https://getcomposer.org/Composer-Setup.exe]. -Linux, macOS .[#toc-linux-macos] --------------------------------- +Linux, macOS +------------ -Tutto ciò di cui avete bisogno sono 4 comandi, che potete copiare da [questa pagina |https://getcomposer.org/download/]. +Bastano 4 comandi, che possono essere copiati da [questa pagina |https://getcomposer.org/download/]. -Inoltre, copiando nella cartella `PATH` del sistema, Composer diventa accessibile a livello globale: +Inoltre, inserendolo in una cartella che si trova nel `PATH` di sistema, Composer diventa accessibile globalmente: ```shell -$ mv ./composer.phar ~/bin/composer # or /usr/local/bin/composer +$ mv ./composer.phar ~/bin/composer # o /usr/local/bin/composer ``` -Utilizzo nel progetto .[#toc-use-in-project] -============================================ +Utilizzo nel progetto +===================== -Per iniziare a usare Composer nel vostro progetto, tutto ciò che vi serve è un file `composer.json`. Questo file descrive le dipendenze del progetto e può contenere anche altri metadati. Il più semplice `composer.json` può essere simile a questo: +Per poter iniziare a usare Composer nel nostro progetto, è necessario solo il file `composer.json`. Questo descrive le dipendenze del nostro progetto e può anche contenere altri metadati. Un `composer.json` di base può quindi apparire così: ```js { @@ -48,17 +48,17 @@ Per iniziare a usare Composer nel vostro progetto, tutto ciò che vi serve è un } ``` -Stiamo dicendo che la nostra applicazione (o libreria) dipende dal pacchetto `nette/database` (il nome del pacchetto è composto dal nome del fornitore e dal nome del progetto) e vuole la versione che corrisponde al vincolo di versione `^3.0`. +Qui specifichiamo che la nostra applicazione (o libreria) richiede il pacchetto `nette/database` (il nome del pacchetto è composto dal nome dell'organizzazione e dal nome del progetto) e richiede una versione che corrisponda alla condizione `^3.0` (cioè la versione più recente 3.x.x). -Quindi, quando abbiamo il file `composer.json` nella radice del progetto ed eseguiamo: +Quindi, con il file `composer.json` nella root del progetto, si esegue l'installazione: ```shell composer update ``` -Composer scaricherà il database Nette nella directory `vendor`. Crea anche un file `composer.lock`, che contiene informazioni sulle versioni delle librerie installate. +Composer scaricherà Nette Database nella cartella `vendor/`. Inoltre, creerà il file `composer.lock`, che contiene informazioni su quali versioni esatte delle librerie ha installato. -Composer genera un file `vendor/autoload.php`. Si può semplicemente includere questo file e iniziare a usare le classi fornite da queste librerie senza alcun lavoro aggiuntivo: +Composer genererà il file `vendor/autoload.php`, che possiamo semplicemente includere e iniziare a usare le librerie senza alcun lavoro aggiuntivo: ```php require __DIR__ . '/vendor/autoload.php'; @@ -67,46 +67,46 @@ $db = new Nette\Database\Connection('sqlite::memory:'); ``` -Aggiornare i pacchetti alle ultime versioni .[#toc-update-packages-to-the-latest-versions] -========================================================================================== +Aggiornamento dei pacchetti alle versioni più recenti +===================================================== -Per aggiornare tutti i pacchetti utilizzati all'ultima versione in base ai vincoli di versione definiti in `composer.json`, usare il comando `composer update`. Ad esempio, per la dipendenza `"nette/database": "^3.0"` verrà installata l'ultima versione 3.x.x, ma non la versione 4. +L'aggiornamento delle librerie utilizzate alle versioni più recenti secondo le condizioni definite in `composer.json` è gestito dal comando `composer update`. Ad esempio, per la dipendenza `"nette/database": "^3.0"` installerà la versione più recente 3.x.x, ma non la versione 4. -Per aggiornare i vincoli di versione nel file `composer.json` a `"nette/database": "^4.1"`, per esempio, e consentire l'installazione dell'ultima versione, usare il comando `composer require nette/database`. +Per aggiornare le condizioni nel file `composer.json`, ad esempio a `"nette/database": "^4.1"`, in modo da poter installare la versione più recente, utilizzare il comando `composer require nette/database`. -Per aggiornare tutti i pacchetti Nette utilizzati, è necessario elencarli tutti sulla riga di comando, ad es: +Per aggiornare tutti i pacchetti Nette utilizzati, sarebbe necessario elencarli tutti nella riga di comando, ad esempio: ```shell composer require nette/application nette/forms latte/latte tracy/tracy ... ``` -Il che è poco pratico. Pertanto, si può utilizzare un semplice script "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff che lo farà per voi: +Il che è poco pratico. Utilizzare quindi il semplice script "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff, che lo farà al posto vostro: ```shell php composer-frontline.php ``` -Creazione di un nuovo progetto .[#toc-creating-new-project] -=========================================================== +Creazione di un nuovo progetto +============================== -È possibile creare un nuovo progetto Nette eseguendo un semplice comando: +È possibile creare un nuovo progetto Nette con un solo comando: ```shell -composer create-project nette/web-project name-of-the-project +composer create-project nette/web-project nome-progetto ``` -Al posto di `name-of-the-project` si deve fornire il nome della directory del progetto ed eseguire il comando. Composer recupererà il repository `nette/web-project` da GitHub, che contiene già il file `composer.json`, e subito dopo installerà il framework Nette. L'unica cosa che resta da fare è [controllare i permessi di scrittura |nette:troubleshooting#setting-directory-permissions] sulle directory `temp/` e `log/` e il gioco è fatto. +Come `nome-progetto` inserire il nome della directory per il proprio progetto e confermare. Composer scaricherà il repository `nette/web-project` da GitHub, che contiene già il file `composer.json`, e subito dopo Nette Framework. A questo punto, dovrebbe essere sufficiente solo [impostare i permessi delle directory |nette:troubleshooting#Impostazione dei permessi delle directory] di scrittura sulle cartelle `temp/` e `log/` e il progetto dovrebbe prendere vita. -Se si conosce la versione di PHP su cui verrà ospitato il progetto, assicurarsi di [impostarla |#PHP Version]. +Se si sa su quale versione di PHP verrà ospitato il progetto, non dimenticate di [impostarla |#Versione PHP]. -Versione PHP .[#toc-php-version] -================================ +Versione PHP +============ -Composer installa sempre le versioni dei pacchetti compatibili con la versione di PHP attualmente in uso (o meglio, la versione di PHP utilizzata dalla riga di comando quando si esegue Composer). Che probabilmente non è la stessa versione utilizzata dal vostro host web. Per questo motivo è molto importante aggiungere al file `composer.json` le informazioni sulla versione di PHP presente sul vostro hosting. In questo modo, verranno installate solo le versioni dei pacchetti compatibili con l'host. +Composer installa sempre le versioni dei pacchetti compatibili con la versione di PHP attualmente in uso (meglio dire con la versione di PHP utilizzata nella riga di comando durante l'esecuzione di Composer). Che però probabilmente non è la stessa versione utilizzata dall'ambiente di hosting. Pertanto, è molto importante aggiungere al file `composer.json` l'informazione sulla versione di PHP nell'ambiente di hosting. Successivamente verranno installate solo le versioni dei pacchetti compatibili con l'ambiente di hosting. -Per esempio, per impostare il progetto in modo che venga eseguito su PHP 8.2.3, usare il comando: +Per specificare che il progetto verrà eseguito, ad esempio, su PHP 8.2.3, utilizzare il comando: ```shell composer config platform.php 8.2.3 @@ -124,14 +124,13 @@ In questo modo la versione viene scritta nel file `composer.json`: } ``` -Tuttavia, il numero di versione di PHP è elencato anche altrove nel file, nella sezione `require`. Mentre il primo numero specifica la versione per la quale verranno installati i pacchetti, il secondo numero dice per quale versione è stata scritta l'applicazione stessa. -(Naturalmente, non ha senso che queste versioni siano diverse, quindi la doppia indicazione è una ridondanza). La versione viene impostata con il comando: +Tuttavia, il numero di versione di PHP viene specificato anche in un altro punto del file, nella sezione `require`. Mentre il primo numero determina per quale versione verranno installati i pacchetti, il secondo numero indica per quale versione è scritta l'applicazione stessa. E in base ad esso, ad esempio, PhpStorm imposta il *PHP language level*. (Ovviamente non ha senso che queste versioni differiscano, quindi la doppia scrittura è un'imprecisione.) Questa versione si imposta con il comando: ```shell composer require php 8.2.3 --no-update ``` -Oppure direttamente nel file `composer.json`: +O direttamente nel file `composer.json`: ```js { @@ -142,66 +141,72 @@ Oppure direttamente nel file `composer.json`: ``` -Rapporti falsi .[#toc-false-reports] -==================================== +Ignorare la versione di PHP +=========================== -Quando si aggiornano i pacchetti o si cambiano i numeri di versione, si verificano dei conflitti. Un pacchetto ha requisiti in conflitto con un altro e così via. Tuttavia, Composer a volte stampa dei falsi messaggi. Segnala un conflitto che in realtà non esiste. In questo caso, è utile cancellare il file `composer.lock` e riprovare. +I pacchetti di solito specificano sia la versione minima di PHP con cui sono compatibili, sia la più alta con cui sono testati. Se si intende utilizzare una versione di PHP ancora più recente, ad esempio per motivi di test, Composer rifiuterà di installare tale pacchetto. La soluzione è l'opzione `--ignore-platform-req=php+`, che fa sì che Composer ignori i limiti superiori della versione PHP richiesta. -Se il messaggio di errore persiste, allora è da intendersi seriamente e bisogna leggere da esso cosa modificare e come. +Messaggi falsi +============== -Packagist.org - Repository globale .[#toc-packagist-org-global-repository] -========================================================================== +Durante l'aggiornamento dei pacchetti o la modifica dei numeri di versione, capita che si verifichi un conflitto. Un pacchetto ha requisiti che sono in conflitto con un altro e simili. Composer però a volte stampa falsi messaggi. Segnala un conflitto che in realtà non esiste. In tal caso, può essere utile eliminare il file `composer.lock` e riprovare. -[Packagist |https://packagist.org] è il principale repository di pacchetti, nel quale Composer cerca di cercare i pacchetti, se non gli viene detto altrimenti. È anche possibile pubblicare qui i propri pacchetti. +Se il messaggio di errore persiste, allora è reale ed è necessario interpretarlo per capire cosa e come modificare. -Cosa succede se non si vuole il repository centrale? .[#toc-what-if-we-don-t-want-the-central-repository] ---------------------------------------------------------------------------------------------------------- +Packagist.org - repository centrale +=================================== -Se nella nostra azienda abbiamo applicazioni o librerie interne che non possono essere ospitate pubblicamente su Packagist, possiamo creare i nostri repository per questi progetti. +[Packagist |https://packagist.org] è il repository principale in cui Composer cerca di trovare i pacchetti, a meno che non gli diciamo diversamente. Possiamo pubblicare qui anche i nostri pacchetti. -Maggiori informazioni sui repository nella [documentazione ufficiale |https://getcomposer.org/doc/05-repositories.md#repositories]. +Cosa succede se non vogliamo usare il repository centrale? +---------------------------------------------------------- -Caricamento automatico .[#toc-autoloading] -========================================== +Se abbiamo applicazioni interne all'azienda, che semplicemente non possiamo ospitare pubblicamente, allora creiamo per esse un repository aziendale. -Una caratteristica fondamentale di Composer è il caricamento automatico di tutte le classi installate, che si avvia includendo il file `vendor/autoload.php`. +Maggiori informazioni sul tema dei repository [nella documentazione ufficiale |https://getcomposer.org/doc/05-repositories.md#repositories]. -Tuttavia, è anche possibile usare Composer per caricare altre classi al di fuori della cartella `vendor`. La prima opzione consiste nel lasciare che Composer analizzi le cartelle e le sottocartelle definite, trovi tutte le classi e le includa nel caricatore automatico. Per fare ciò, impostare `autoload > classmap` in `composer.json`: + +Autoloading +=========== + +Una caratteristica fondamentale di Composer è che fornisce l'autoloading per tutte le classi da esso installate, che si avvia includendo il file `vendor/autoload.php`. + +Tuttavia, è possibile utilizzare Composer anche per caricare altre classi al di fuori della cartella `vendor`. La prima opzione è far sì che Composer esamini le cartelle e le sottocartelle definite, trovi tutte le classi e le includa nell'autoloader. Ciò si ottiene impostando `autoload > classmap` in `composer.json`: ```js { "autoload": { "classmap": [ - "src/", # includes the src/ folder and its subfolders + "src/", # include la cartella src/ e le sue sottocartelle ] } } ``` -Successivamente, è necessario eseguire il comando `composer dumpautoload` a ogni modifica e lasciare che le tabelle di autocaricamento si rigenerino. Questo è estremamente scomodo ed è molto meglio affidare questo compito a [RobotLoader |robot-loader:], che svolge la stessa attività automaticamente in background e molto più velocemente. +Successivamente, è necessario eseguire il comando `composer dumpautoload` ad ogni modifica e far rigenerare le tabelle di autoloading. Questo è estremamente scomodo ed è molto meglio affidare questo compito a [RobotLoader|robot-loader:], che esegue la stessa attività automaticamente in background e molto più velocemente. -La seconda opzione consiste nel seguire [PSR-4 |https://www.php-fig.org/psr/psr-4/]. In parole povere, si tratta di un sistema in cui gli spazi dei nomi e i nomi delle classi corrispondono alla struttura delle directory e ai nomi dei file, cioè `App\Router\RouterFactory` si trova nel file `/path/to/App/Router/RouterFactory.php`. Esempio di configurazione: +La seconda opzione è rispettare [PSR-4|https://www.php-fig.org/psr/psr-4/]. In parole povere, si tratta di un sistema in cui i namespace e i nomi delle classi corrispondono alla struttura delle directory e ai nomi dei file, quindi ad esempio `App\Core\RouterFactory` sarà nel file `/path/to/App/Core/RouterFactory.php`. Esempio di configurazione: ```js { "autoload": { "psr-4": { - "App\\": "app/" # the App\ namespace is in the app/ directory + "App\\": "app/" # il namespace App\ è nella directory app/ } } } ``` -Vedere la [documentazione di Composer |https://getcomposer.org/doc/04-schema.md#psr-4] per sapere esattamente come configurare questo comportamento. +Come configurare esattamente il comportamento è descritto nella [documentazione di Composer|https://getcomposer.org/doc/04-schema.md#psr-4]. -Testare le nuove versioni .[#toc-testing-new-versions] -====================================================== +Testare nuove versioni +====================== -Si vuole testare una nuova versione di sviluppo di un pacchetto. Come fare? Per prima cosa, aggiungete questa coppia di opzioni al file `composer.json`, che vi permetterà di installare le versioni di sviluppo dei pacchetti, ma lo farà solo se non esiste una combinazione di versioni stabili che soddisfi i requisiti: +Si desidera testare una nuova versione di sviluppo di un pacchetto. Come fare? Innanzitutto, aggiungere al file `composer.json` questa coppia di opzioni, che permetterà di installare versioni di sviluppo dei pacchetti, ma ricorrerà ad essa solo nel caso in cui non esista alcuna combinazione di versioni stabili che soddisfi i requisiti: ```js { @@ -210,34 +215,33 @@ Si vuole testare una nuova versione di sviluppo di un pacchetto. Come fare? Per } ``` -Si consiglia anche di eliminare il file `composer.lock`, perché a volte Composer si rifiuta incomprensibilmente di installare e questo risolverà il problema. +Inoltre, consigliamo di eliminare il file `composer.lock`, a volte infatti Composer rifiuta inspiegabilmente l'installazione e questo risolve il problema. -Supponiamo che il pacchetto sia `nette/utils` e che la nuova versione sia la 4.0. Si installa con il comando: +Supponiamo che si tratti del pacchetto `nette/utils` e che la nuova versione abbia il numero 4.0. Si installa con il comando: ```shell composer require nette/utils:4.0.x-dev ``` -Oppure si può installare una versione specifica, per esempio 4.0.0-RC2: +Oppure è possibile installare una versione specifica, ad esempio 4.0.0-RC2: ```shell composer require nette/utils:4.0.0-RC2 ``` -Se un altro pacchetto dipende dalla libreria ed è bloccato a una versione precedente (ad esempio `^3.1`), è ideale aggiornare il pacchetto per farlo funzionare con la nuova versione. -Tuttavia, se si vuole semplicemente aggirare la limitazione e forzare Composer a installare la versione di sviluppo e fingere che sia una versione precedente (ad esempio, 3.1.6), si può usare la parola chiave `as`: +Se però un altro pacchetto dipende dalla libreria ed è bloccato a una versione precedente (es. `^3.1`), allora è ideale aggiornare il pacchetto affinché funzioni con la nuova versione. Se però si vuole solo aggirare la limitazione e costringere Composer a installare la versione di sviluppo e fingere che sia una versione precedente (es. 3.1.6), si può usare la parola chiave `as`: ```shell composer require nette/utils "4.0.x-dev as 3.1.6" ``` -Comandi di chiamata .[#toc-calling-commands] -============================================ +Chiamata di comandi +=================== -È possibile richiamare i propri comandi e script personalizzati attraverso Composer come se fossero comandi nativi di Composer. Per gli script che si trovano nella cartella `vendor/bin` non è necessario specificare questa cartella. +Tramite Composer è possibile chiamare comandi e script personalizzati pre-preparati, come se fossero comandi nativi di Composer. Per gli script che si trovano nella cartella `vendor/bin`, non è necessario specificare questa cartella. -A titolo di esempio, definiamo uno script nel file `composer.json` che utilizza [Nette Tester |tester:] per eseguire i test: +Come esempio, definiamo nel file `composer.json` uno script che, utilizzando [Nette Tester|tester:], esegue i test: ```js { @@ -247,13 +251,13 @@ A titolo di esempio, definiamo uno script nel file `composer.json` che utilizza } ``` -Eseguiamo quindi i test con `composer tester`. Possiamo richiamare il comando anche se non ci troviamo nella cartella principale del progetto, ma in una sottocartella. +Eseguiamo quindi i test con `composer tester`. Possiamo chiamare il comando anche se non siamo nella cartella principale del progetto, ma in una sottodirectory. -Inviare Grazie .[#toc-send-thanks] -================================== +Invia un ringraziamento +======================= -Vi mostriamo un trucco che renderà felici gli autori open source. Potete facilmente assegnare una stella su GitHub alle librerie utilizzate dal vostro progetto. Basta installare la libreria `symfony/thanks`: +Vi mostreremo un trucco che farà piacere agli autori open source. In modo semplice, si darà una stella su GitHub alle librerie che il vostro progetto utilizza. Basta installare la libreria `symfony/thanks`: ```shell composer global require symfony/thanks @@ -268,13 +272,11 @@ composer thanks Provate! -Configurazione .[#toc-configuration] -==================================== +Configurazione +============== -Composer è strettamente integrato con lo strumento di controllo di versione [Git |https://git-scm.com]. Se non si usa Git, è necessario comunicarlo a Composer: +Composer è strettamente legato allo strumento di versioning [Git |https://git-scm.com]. Se non è installato, è necessario indicare a Composer di non usarlo: ```shell composer -g config preferred-install dist ``` - -{{sitename: Migliori pratiche}} diff --git a/best-practices/it/creating-editing-form.texy b/best-practices/it/creating-editing-form.texy index 8b8c20f7ee..7a5868873c 100644 --- a/best-practices/it/creating-editing-form.texy +++ b/best-practices/it/creating-editing-form.texy @@ -2,15 +2,15 @@ Modulo per la creazione e la modifica di un record ************************************************** .[perex] -Come implementare correttamente l'aggiunta e la modifica di un record in Nette, utilizzando lo stesso modulo per entrambi? +Come implementare correttamente l'aggiunta e la modifica di un record in Nette, utilizzando lo stesso modulo per entrambe le operazioni? -In molti casi, i moduli per l'aggiunta e la modifica di un record sono identici e si differenziano solo per l'etichetta del pulsante. Mostreremo esempi di semplici presentazioni in cui utilizziamo il modulo prima per aggiungere un record, poi per modificarlo e infine combiniamo le due soluzioni. +In molti casi i moduli per l'aggiunta e la modifica di un record sono gli stessi, differendo magari solo per l'etichetta sul pulsante. Mostreremo esempi di semplici presenter in cui utilizzeremo il modulo prima per aggiungere un record, poi per modificarlo e infine combineremo entrambe le soluzioni. -Aggiunta di un record .[#toc-adding-a-record] ---------------------------------------------- +Aggiunta di un record +--------------------- -Un esempio di presentatore usato per aggiungere un record. Lasceremo il lavoro effettivo sul database alla classe `Facade`, il cui codice non è rilevante per l'esempio. +Esempio di un presenter utilizzato per aggiungere un record. Lasceremo il lavoro effettivo con il database alla classe `Facade`, il cui codice non è essenziale per l'esempio. ```php @@ -27,7 +27,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $form = new Form; - // ... aggiungere campi al modulo ... + // ... aggiungiamo i campi del modulo ... $form->onSuccess[] = [$this, 'recordFormSucceeded']; return $form; @@ -35,7 +35,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter public function recordFormSucceeded(Form $form, array $data): void { - $this->facade->add($data); // aggiunge il record al database + $this->facade->add($data); // aggiunta del record al database $this->flashMessage('Aggiunto con successo'); $this->redirect('...'); } @@ -48,10 +48,10 @@ class RecordPresenter extends Nette\Application\UI\Presenter ``` -Modifica di un record .[#toc-editing-a-record] ----------------------------------------------- +Modifica di un record +--------------------- -Vediamo ora come appare un presentatore utilizzato per modificare un record: +Ora mostreremo come apparirebbe un presenter utilizzato per modificare un record: ```php @@ -70,8 +70,8 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $record = $this->facade->get($id); if ( - !$record // verificare l'esistenza del record - || !$this->facade->isEditAllowed(/*...*/) // verificare le autorizzazioni + !$record // verifica dell'esistenza del record + || !$this->facade->isEditAllowed(/*...*/) // controllo dei permessi ) { $this->error(); // errore 404 } @@ -81,32 +81,32 @@ class RecordPresenter extends Nette\Application\UI\Presenter protected function createComponentRecordForm(): Form { - // verificare che l'azione sia 'modifica' + // verifichiamo che l'azione sia 'edit' if ($this->getAction() !== 'edit') { $this->error(); } $form = new Form; - // ... aggiungere campi del modulo ... + // ... aggiungiamo i campi del modulo ... - $form->setDefaults($this->record); // impostare i valori predefiniti + $form->setDefaults($this->record); // impostazione dei valori predefiniti $form->onSuccess[] = [$this, 'recordFormSucceeded']; return $form; } public function recordFormSucceeded(Form $form, array $data): void { - $this->facade->update($this->record->id, $data); // aggiorna il record - $this->flashMessage('Aggiornamento riuscito'); + $this->facade->update($this->record->id, $data); // aggiornamento del record + $this->flashMessage('Aggiornato con successo'); $this->redirect('...'); } } ``` -Nel metodo *action*, che viene invocato proprio all'inizio del [ciclo di vita |application:presenters#Life Cycle of Presenter] del [presentatore |application:presenters#Life Cycle of Presenter], si verifica l'esistenza del record e l'autorizzazione dell'utente a modificarlo. +Nel metodo *action*, che viene eseguito all'inizio del [ciclo di vita del presenter |application:presenters#Ciclo di vita del presenter], verifichiamo l'esistenza del record e i permessi dell'utente per modificarlo. -Memorizziamo il record nella proprietà `$record`, in modo che sia disponibile nel metodo `createComponentRecordForm()` per l'impostazione dei valori predefiniti e `recordFormSucceeded()` per l'ID. Una soluzione alternativa sarebbe quella di impostare i valori predefiniti direttamente in `actionEdit()` e il valore dell'ID, che fa parte dell'URL, viene recuperato con `getParameter('id')`: +Salviamo il record nella proprietà `$record` in modo da averlo disponibile nel metodo `createComponentRecordForm()` per impostare i valori predefiniti e in `recordFormSucceeded()` per l'ID. Una soluzione alternativa sarebbe impostare i valori predefiniti direttamente in `actionEdit()` e ottenere il valore dell'ID, che fa parte dell'URL, utilizzando `getParameter('id')`: ```php @@ -114,12 +114,12 @@ Memorizziamo il record nella proprietà `$record`, in modo che sia disponibile n { $record = $this->facade->get($id); if ( - // verificare l'esistenza e controllare i permessi + // verifica dell'esistenza e controllo dei permessi ) { $this->error(); } - // impostare i valori predefiniti dei moduli + // impostazione dei valori predefiniti del modulo $this->getComponent('recordForm') ->setDefaults($record); } @@ -133,13 +133,13 @@ Memorizziamo il record nella proprietà `$record`, in modo che sia disponibile n } ``` -Tuttavia, e questo dovrebbe essere **il risultato più importante di tutto il codice**, dobbiamo assicurarci che l'azione sia effettivamente `edit` quando creiamo il form. Perché altrimenti la validazione nel metodo `actionEdit()` non avverrebbe affatto! +Tuttavia, e questo dovrebbe essere **il punto chiave più importante dell'intero codice**, dobbiamo assicurarci durante la creazione del modulo che l'azione sia effettivamente `edit`. Altrimenti, la verifica nel metodo `actionEdit()` non verrebbe eseguita affatto! -Stesso modulo per aggiungere e modificare .[#toc-same-form-for-adding-and-editing] ----------------------------------------------------------------------------------- +Stesso modulo per l'aggiunta e la modifica +------------------------------------------ -Ora combineremo entrambi i presentatori in uno solo. Si può distinguere quale azione è coinvolta nel metodo `createComponentRecordForm()` e configurare il modulo di conseguenza, oppure si può lasciare la scelta direttamente ai metodi di azione e sbarazzarsi della condizione: +E ora uniamo entrambi i presenter in uno solo. Potremmo distinguere quale azione è in corso nel metodo `createComponentRecordForm()` e configurare il modulo di conseguenza, oppure possiamo lasciarlo direttamente ai metodi action e liberarci della condizione: ```php @@ -160,34 +160,34 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $record = $this->facade->get($id); if ( - !$record // verificare l'esistenza del record - || !$this->facade->isEditAllowed(/*...*/) // verificare le autorizzazioni + !$record // verifica dell'esistenza del record + || !$this->facade->isEditAllowed(/*...*/) // controllo dei permessi ) { $this->error(); // errore 404 } $form = $this->getComponent('recordForm'); - $form->setDefaults($record); // impostare le impostazioni predefinite + $form->setDefaults($record); // impostazione dei valori predefiniti $form->onSuccess[] = [$this, 'editingFormSucceeded']; } protected function createComponentRecordForm(): Form { - // verificare che l'azione sia "aggiungere" o "modificare". + // verifichiamo che l'azione sia 'add' o 'edit' if (!in_array($this->getAction(), ['add', 'edit'])) { $this->error(); } $form = new Form; - // ... aggiungere i campi del modulo ... + // ... aggiungiamo i campi del modulo ... return $form; } public function addingFormSucceeded(Form $form, array $data): void { - $this->facade->add($data); // aggiunge un record al database + $this->facade->add($data); // aggiunta del record al database $this->flashMessage('Aggiunto con successo'); $this->redirect('...'); } @@ -196,11 +196,10 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $id = (int) $this->getParameter('id'); $this->facade->update($id, $data); // aggiornamento del record - $this->flashMessage('Aggiornamento riuscito'); + $this->flashMessage('Aggiornato con successo'); $this->redirect('...'); } } ``` {{priority: -1}} -{{sitename: Migliori pratiche}} diff --git a/best-practices/it/dynamic-snippets.texy b/best-practices/it/dynamic-snippets.texy index 870ac5ebdc..e2c34a8080 100644 --- a/best-practices/it/dynamic-snippets.texy +++ b/best-practices/it/dynamic-snippets.texy @@ -1,7 +1,7 @@ Snippet dinamici **************** -Molto spesso nello sviluppo di un'applicazione è necessario eseguire operazioni AJAX, ad esempio nelle singole righe di una tabella o negli elementi di un elenco. Ad esempio, possiamo scegliere di elencare degli articoli, consentendo all'utente loggato di selezionare una valutazione "mi piace/dispiace" per ciascuno di essi. Il codice del presentatore e del template corrispondente senza AJAX sarà simile a questo (elenco gli snippet più importanti, il codice presuppone l'esistenza di un servizio per la marcatura delle valutazioni e l'ottenimento di una raccolta di articoli - l'implementazione specifica non è importante ai fini di questo tutorial): +Abbastanza spesso, durante lo sviluppo di applicazioni, sorge la necessità di eseguire operazioni AJAX, ad esempio, su singole righe di una tabella o elementi di un elenco. Come esempio, possiamo scegliere la visualizzazione di articoli, consentendo a ciascun utente loggato di scegliere una valutazione "mi piace/non mi piace". Il codice del presenter e del template corrispondente senza AJAX apparirà approssimativamente come segue (riporto le parti più importanti, il codice presume l'esistenza di un servizio per contrassegnare le valutazioni e ottenere la collezione di articoli - l'implementazione specifica non è importante ai fini di questo tutorial): ```php public function handleLike(int $articleId): void @@ -24,26 +24,26 @@ Template: <h2>{$article->title}</h2> <div class="content">{$article->content}</div> {if !$article->liked} - <a n:href="like! $article->id" class=ajax>I like it</a> + <a n:href="like! $article->id" class=ajax>mi piace</a> {else} - <a n:href="unlike! $article->id" class=ajax>I don't like it anymore</a> + <a n:href="unlike! $article->id" class=ajax>non mi piace più</a> {/if} </article> ``` -Ajaxization .[#toc-ajaxization] -=============================== +Ajaxificazione +============== -Ora aggiungiamo AJAX a questa semplice applicazione. La modifica della valutazione di un articolo non è abbastanza importante da richiedere una richiesta HTTP con redirect, quindi idealmente dovrebbe essere fatta con AJAX in background. Utilizzeremo lo [script handler di add-on |https://componette.org/vojtech-dobes/nette.ajax.js/] con la solita convenzione che i link AJAX abbiano la classe CSS `ajax`. +Ora dotiamo questa semplice applicazione di AJAX. La modifica della valutazione di un articolo non è così importante da richiedere un reindirizzamento, quindi idealmente dovrebbe avvenire tramite AJAX in background. Utilizzeremo lo [script di gestione degli addon |application:ajax#Naja] con la convenzione usuale che i link AJAX abbiano la classe CSS `ajax`. -Tuttavia, come farlo in modo specifico? Nette offre due modi: quello degli snippet dinamici e quello dei componenti. Entrambi hanno pro e contro, quindi li mostreremo uno per uno. +Tuttavia, come farlo concretamente? Nette offre 2 percorsi: il percorso dei cosiddetti snippet dinamici e il percorso dei componenti. Entrambi hanno i loro pro e contro, quindi li mostreremo uno per uno. -Il metodo degli snippet dinamici .[#toc-the-dynamic-snippets-way] -================================================================= +Il percorso degli snippet dinamici +================================== -Nella terminologia di Latte, uno snippet dinamico è un caso d'uso specifico del tag `{snippet}` in cui una variabile è usata nel nome dello snippet. Uno snippet di questo tipo non può trovarsi in qualsiasi punto del template: deve essere avvolto da uno snippet statico, cioè regolare, o all'interno di un `{snippetArea}`. Possiamo modificare il nostro template come segue. +Uno snippet dinamico, nella terminologia Latte, significa un caso specifico di utilizzo del tag `{snippet}`, in cui viene utilizzata una variabile nel nome dello snippet. Tale snippet non può trovarsi ovunque nel template - deve essere racchiuso da uno snippet statico, cioè uno normale, o all'interno di `{snippetArea}`. Potremmo modificare il nostro template come segue. ```latte @@ -53,18 +53,18 @@ Nella terminologia di Latte, uno snippet dinamico è un caso d'uso specifico del <div class="content">{$article->content}</div> {snippet article-{$article->id}} {if !$article->liked} - <a n:href="like! $article->id" class=ajax>I like it</a> + <a n:href="like! $article->id" class=ajax>mi piace</a> {else} - <a n:href="unlike! $article->id" class=ajax>I don't like it anymore</a> + <a n:href="unlike! $article->id" class=ajax>non mi piace più</a> {/if} {/snippet} </article> {/snippet} ``` -Ogni articolo definisce ora un singolo snippet, che ha un ID articolo nel titolo. Tutti questi snippet vengono poi avvolti insieme in un unico snippet chiamato `articlesContainer`. Se omettiamo questo snippet, Latte ci avvisa con un'eccezione. +Ogni articolo ora definisce uno snippet che ha l'ID dell'articolo nel nome. Tutti questi snippet sono poi racchiusi insieme da uno snippet chiamato `articlesContainer`. Se omettessimo questo snippet contenitore, Latte ci avviserebbe con un'eccezione. -Tutto ciò che resta da fare è aggiungere il ridisegno al presentatore: basta ridisegnare il wrapper statico. +Ci resta da aggiungere il ridisegno nel presenter - basta ridisegnare il contenitore statico. ```php public function handleLike(int $articleId): void @@ -72,18 +72,18 @@ public function handleLike(int $articleId): void $this->ratingService->saveLike($articleId, $this->user->id); if ($this->isAjax()) { $this->redrawControl('articlesContainer'); - // $this->redrawControl('article-' . $articleId); -- není potřeba + // $this->redrawControl('article-' . $articleId); -- non è necessario } else { $this->redirect('this'); } } ``` -Modificate allo stesso modo il metodo gemello `handleUnlike()` e AJAX è pronto e funzionante! +Modifichiamo in modo simile anche il metodo gemello `handleUnlike()`, e AJAX è funzionante! -La soluzione ha però un lato negativo. Se approfondiamo il funzionamento della richiesta AJAX, scopriamo che, sebbene l'applicazione sembri efficiente in apparenza (restituisce solo un singolo frammento per un determinato articolo), in realtà rende tutti i frammenti sul server. Ha inserito lo snippet desiderato nel nostro payload e ha scartato gli altri (quindi, inutilmente, li ha recuperati anche dal database). +La soluzione ha però un lato oscuro. Se esaminassimo più da vicino come avviene la richiesta AJAX, scopriremmo che, sebbene esternamente l'applicazione sembri efficiente (restituisce solo un singolo snippet per l'articolo dato), in realtà sul server ha renderizzato tutti gli snippet. Ha inserito lo snippet desiderato nel payload e ha scartato gli altri (ottenendoli quindi inutilmente anche dal database). -Per ottimizzare questo processo, dovremo agire passando la collezione `$articles` al template (ad esempio nel metodo `renderDefault()` ). Sfrutteremo il fatto che l'elaborazione del segnale avviene prima del metodo `render<Something>` metodi: +Per ottimizzare questo processo, dovremo intervenire dove passiamo la collezione `$articles` al template (diciamo nel metodo `renderDefault()`). Sfrutteremo il fatto che l'elaborazione dei segnali avviene prima dei metodi `render<Something>`: ```php public function handleLike(int $articleId): void @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connection->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,18 +101,18 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connection->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` -Ora, quando il segnale viene elaborato, invece di un insieme con tutti gli articoli, viene passato al template solo un array con un singolo articolo, quello che vogliamo rendere e inviare nel payload al browser. In questo modo, `{foreach}` sarà fatto solo una volta e non saranno resi frammenti extra. +Ora, durante l'elaborazione del segnale, al template viene passato un array con un solo articolo - quello che vogliamo renderizzare e inviare nel payload al browser - invece della collezione con tutti gli articoli. `{foreach}` quindi verrà eseguito solo una volta e non verranno renderizzati snippet aggiuntivi. -Il modo in cui il componente .[#toc-component-way] -================================================== +Il percorso dei componenti +========================== -Una soluzione completamente diversa utilizza un approccio diverso per evitare gli snippet dinamici. Il trucco consiste nello spostare tutta la logica in un componente separato: d'ora in poi, non avremo più un presentatore che si occupa di inserire la valutazione, ma un componente dedicato `LikeControl`. La classe avrà l'aspetto seguente (inoltre, conterrà anche i metodi `render`, `handleUnlike`, ecc:) +Un modo completamente diverso di risolvere il problema evita gli snippet dinamici. Il trucco sta nel trasferire l'intera logica in un componente separato - d'ora in poi non sarà il presenter a occuparsi dell'inserimento delle valutazioni, ma un `LikeControl` dedicato. La classe apparirà come segue (oltre a ciò, conterrà anche i metodi `render`, `handleUnlike` ecc.): ```php class LikeControl extends Nette\Application\UI\Control @@ -139,26 +139,26 @@ Template del componente: ```latte {snippet} {if !$article->liked} - <a n:href="like!" class=ajax>I like it</a> + <a n:href="like!" class=ajax>mi piace</a> {else} - <a n:href="unlike!" class=ajax>I don't like it anymore</a> + <a n:href="unlike!" class=ajax>non mi piace più</a> {/if} {/snippet} ``` -Naturalmente cambieremo il modello della vista e dovremo aggiungere un factory al presentatore. Poiché creeremo il componente tante volte quanti sono gli articoli ricevuti dal database, useremo la classe [Multiplier |application:Multiplier] per "moltiplicarlo". +Naturalmente, il template della vista cambierà e dovremo aggiungere una factory al presenter. Poiché creeremo il componente tante volte quanti sono gli articoli ottenuti dal database, utilizzeremo la classe [Multiplier|application:Multiplier] per la sua "moltiplicazione". ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); } ``` -Il modello di vista è ridotto al minimo indispensabile (e completamente privo di snippet!): +Il template della vista si riduce al minimo indispensabile (e completamente privo di snippet!): ```latte <article n:foreach="$articles as $article"> @@ -168,7 +168,6 @@ Il modello di vista è ridotto al minimo indispensabile (e completamente privo d </article> ``` -Abbiamo quasi finito: l'applicazione funzionerà ora in AJAX. Anche in questo caso dobbiamo ottimizzare l'applicazione, perché a causa dell'uso del database Nette, l'elaborazione del segnale caricherà inutilmente tutti gli articoli dal database invece di uno. Tuttavia, il vantaggio è che non ci sarà alcun rendering, perché solo il nostro componente verrà effettivamente renderizzato. +Abbiamo quasi finito: l'applicazione ora funzionerà in modo AJAX. Anche qui dovremo ottimizzare l'applicazione, perché a causa dell'uso di Nette Database, durante l'elaborazione del segnale vengono caricati inutilmente tutti gli articoli dal database invece di uno solo. Il vantaggio, tuttavia, è che non verranno renderizzati, perché verrà renderizzato effettivamente solo il nostro componente. {{priority: -1}} -{{sitename: Migliori pratiche}} diff --git a/best-practices/it/editors-and-tools.texy b/best-practices/it/editors-and-tools.texy index bdafe93ca9..9386b93f8e 100644 --- a/best-practices/it/editors-and-tools.texy +++ b/best-practices/it/editors-and-tools.texy @@ -1,40 +1,40 @@ -Editori e strumenti -******************* +Editor e strumenti +****************** .[perex] -Si può essere programmatori esperti, ma solo con buoni strumenti si diventa maestri. In questo capitolo troverete suggerimenti su importanti strumenti, editor e plugin. +Potreste essere un programmatore esperto, ma solo con i buoni strumenti diventerete dei maestri. In questo capitolo troverete suggerimenti su strumenti, editor e plugin importanti. -Editor IDE .[#toc-ide-editor] -============================= +Editor IDE +========== -Si consiglia vivamente di utilizzare un IDE completo per lo sviluppo, come PhpStorm, NetBeans, VS Code, e non solo un editor di testo con supporto PHP. La differenza è davvero fondamentale. Non c'è motivo di accontentarsi di un editor classico con l'evidenziazione della sintassi, perché non raggiunge le capacità di un IDE con suggerimenti accurati del codice, in grado di rifattorizzare il codice e altro ancora. Alcuni IDE sono a pagamento, altri sono gratuiti. +Consigliamo vivamente di utilizzare un IDE completo per lo sviluppo, come PhpStorm, NetBeans, VS Code, e non solo un editor di testo con supporto PHP. La differenza è davvero fondamentale. Non c'è motivo di accontentarsi di un semplice editor che colora la sintassi ma non raggiunge le capacità di un IDE di alto livello, che suggerisce con precisione, controlla gli errori, sa refattorizzare il codice e molto altro. Alcuni IDE sono a pagamento, altri addirittura gratuiti. -**NetBeans IDE** ha il supporto integrato per Nette, Latte e NEON. +**NetBeans IDE** ha il supporto per Nette, Latte e NEON già integrato. -**PhpStorm**: installare questi plugin in `Settings > Plugins > Marketplace`: -- Aiutanti del framework Nette +**PhpStorm**: installate questi plugin in `Settings > Plugins > Marketplace` +- Nette framework helpers - Latte -- Supporto NEON -- Tester Nette +- NEON support +- Nette Tester -**Codice VS**: trovare il plugin "Nette Latte + Neon" nel marketplace. +**VS Code**: cercate nel marketplace il plugin "Nette Latte + Neon". -Collegare anche Tracy con l'editor. Quando viene visualizzata la pagina di errore, è possibile fare clic sui nomi dei file e questi si apriranno nell'editor con il cursore sulla riga corrispondente. Imparare a [configurare il sistema |tracy:open-files-in-ide]. +Collegate anche Tracy all'editor. Quando viene visualizzata una pagina di errore, potrete cliccare sui nomi dei file e questi si apriranno nell'editor con il cursore sulla riga corrispondente. Leggete [come configurare il sistema|tracy:open-files-in-ide]. -PHPStan .[#toc-phpstan] -======================= +PHPStan +======= -PHPStan è uno strumento che rileva gli errori logici nel codice prima di eseguirlo. +PHPStan è uno strumento che rileva gli errori logici nel codice prima ancora di eseguirlo. -Si installa tramite Composer: +Lo installiamo tramite Composer: ```shell composer require --dev phpstan/phpstan-nette ``` -Creare un file di configurazione `phpstan.neon` nel progetto: +Creiamo nel progetto il file di configurazione `phpstan.neon`: ```neon includes: @@ -47,40 +47,38 @@ parameters: level: 5 ``` -E poi lasciare che analizzi le classi nella cartella `app/`: +E successivamente lo facciamo analizzare le classi nella cartella `app/`: ```shell vendor/bin/phpstan analyse app ``` -La documentazione completa è disponibile direttamente su [PHPStan |https://phpstan.org]. +Troverete una documentazione esaustiva direttamente sul [sito web di PHPStan |https://phpstan.org]. -Controllore di codice .[#toc-code-checker] -========================================== +Code Checker +============ -[Code Checker |code-checker:] controlla ed eventualmente ripara alcuni errori formali del codice sorgente. +[Code Checker|code-checker:] controlla ed eventualmente corregge alcuni errori formali nei vostri codici sorgente: -- rimuove la [distinta base |nette:glossary#bom] -- controlla la validità dei modelli [Latte |latte:] +- rimuove il [BOM |nette:glossary#BOM] +- controlla la validità dei template [Latte |latte:] - controlla la validità dei file `.neon`, `.php` e `.json` -- verifica la presenza di [caratteri di controllo |nette:glossary#control characters] +- controlla la presenza di [caratteri di controllo |nette:glossary#Caratteri di controllo] - controlla se il file è codificato in UTF-8 -- controlla gli errori di scrittura di `/* @annotations */` (manca il secondo asterisco) -- rimuove i tag finali PHP `?>` nei file PHP -- rimuove gli spazi bianchi di coda e le righe vuote non necessarie dalla fine di un file -- normalizza le terminazioni di riga ai valori predefiniti dal sistema (con il parametro `-l` ). +- controlla `/* @anotace */` scritti erroneamente (manca l'asterisco) +- rimuove il tag di chiusura `?>` dai file PHP +- rimuove gli spazi finali e le righe vuote alla fine del file +- normalizza i separatori di riga a quelli di sistema (se si specifica l'opzione `-l`) -Compositore .[#toc-composer] -============================ +Composer +======== -[Composer |Composer] è uno strumento per gestire le dipendenze in PHP. Permette di dichiarare le dipendenze delle librerie e le installerà per noi, nel nostro progetto. +[Composer |Composer] è uno strumento per la gestione delle dipendenze in PHP. Ci permette di dichiarare dipendenze arbitrariamente complesse tra le singole librerie e poi le installa per noi nel nostro progetto. -Controllo dei requisiti .[#toc-requirements-checker] -==================================================== +Requirements Checker +==================== -Si trattava di uno strumento che testava l'ambiente di esecuzione del server e informava se (e fino a che punto) il framework poteva essere utilizzato. Attualmente, Nette può essere utilizzato su qualsiasi server che abbia la versione minima richiesta di PHP. - -{{sitename: Migliori pratiche}} +Era uno strumento che testava l'ambiente di runtime del server e informava se (e in che misura) fosse possibile utilizzare il framework. Attualmente, Nette può essere utilizzato su qualsiasi server che abbia la versione minima richiesta di PHP. diff --git a/best-practices/it/form-reuse.texy b/best-practices/it/form-reuse.texy index 26e0e30dbb..ccd4423502 100644 --- a/best-practices/it/form-reuse.texy +++ b/best-practices/it/form-reuse.texy @@ -1,16 +1,16 @@ -Riutilizzare i moduli in più luoghi -*********************************** +Riutilizzo dei moduli in più punti +********************************** .[perex] -In Nette, esistono diverse opzioni per riutilizzare lo stesso modulo in più punti senza duplicare il codice. In questo articolo esamineremo le diverse soluzioni, comprese quelle da evitare. +In Nette avete diverse opzioni per utilizzare lo stesso modulo in più punti senza duplicare il codice. In questo articolo mostreremo diverse soluzioni, comprese quelle che dovreste evitare. -Fabbrica di moduli .[#toc-form-factory] -======================================= +Factory per moduli +================== -Un approccio di base per utilizzare lo stesso componente in più punti è quello di creare un metodo o una classe che generi il componente e poi richiamare tale metodo in diversi punti dell'applicazione. Un metodo o una classe di questo tipo si chiama *factory*. Non bisogna confondersi con il modello di progettazione *factory method*, che descrive un modo specifico di usare le fabbriche e non è correlato a questo argomento. +Uno degli approcci fondamentali per utilizzare lo stesso componente in più punti è creare un metodo o una classe che genera questo componente e successivamente chiamare questo metodo in diversi punti dell'applicazione. Tale metodo o classe viene chiamato *factory*. Si prega di non confondere con il pattern di progettazione *factory method*, che descrive un modo specifico di utilizzare le factory e non è correlato a questo argomento. -Come esempio, creiamo un factory che costruisca un form di modifica: +Come esempio, creiamo una factory che costruirà un modulo di modifica: ```php use Nette\Application\UI\Form; @@ -20,22 +20,22 @@ class FormFactory public function createEditForm(): Form { $form = new Form; - $form->addText('title', 'Title:'); - // I campi aggiuntivi del modulo sono aggiunti qui - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Titolo:'); + // qui vengono aggiunti altri campi del modulo + $form->addSubmit('send', 'Invia'); return $form; } } ``` -Ora è possibile utilizzare questo factory in diversi punti dell'applicazione, ad esempio nei presenter o nei componenti. Per farlo, lo [richiediamo come dipendenza |dependency-injection:passing-dependencies]. Quindi, per prima cosa, scriveremo la classe nel file di configurazione: +Ora potete utilizzare questa factory in diversi punti della vostra applicazione, ad esempio nei presenter o nei componenti. E questo richiedendola come [dipendenza|dependency-injection:passing-dependencies]. Prima di tutto, quindi, registriamo la classe nel file di configurazione: ```neon services: - FormFactory ``` -e poi la usiamo nel presentatore: +E poi la utilizziamo nel presenter: ```php @@ -57,7 +57,7 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -È possibile estendere il factory di form con metodi aggiuntivi per creare altri tipi di form, in base alle proprie applicazioni. E, naturalmente, si può aggiungere un metodo che crea un modulo di base senza elementi, che verrà utilizzato dagli altri metodi: +Potete estendere la factory dei moduli con altri metodi per creare altri tipi di moduli secondo le esigenze della vostra applicazione. E naturalmente possiamo aggiungere anche un metodo che crei un modulo base senza elementi, e che gli altri metodi utilizzeranno: ```php class FormFactory @@ -71,21 +71,21 @@ class FormFactory public function createEditForm(): Form { $form = $this->createForm(); - $form->addText('title', 'Title:'); - // I campi aggiuntivi del modulo sono aggiunti qui - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Titolo:'); + // qui vengono aggiunti altri campi del modulo + $form->addSubmit('send', 'Invia'); return $form; } } ``` -Il metodo `createForm()` non fa ancora nulla di utile, ma questo cambierà rapidamente. +Il metodo `createForm()` per ora non fa nulla di utile, ma questo cambierà rapidamente. -Dipendenze della fabbrica .[#toc-factory-dependencies] -====================================================== +Dipendenze della factory +======================== -Col tempo, ci si renderà conto che i moduli devono essere multilingue. Ciò significa che dobbiamo impostare un [traduttore |forms:rendering#Translating] per tutti i moduli. Per farlo, modifichiamo la classe `FormFactory` in modo che accetti l'oggetto `Translator` come dipendenza nel costruttore e lo passi al form: +Col tempo si scoprirà che abbiamo bisogno che i moduli siano multilingue. Ciò significa che a tutti i moduli dobbiamo impostare il cosiddetto [translator |forms:rendering#Traduzione]. A tal fine, modifichiamo la classe `FormFactory` in modo che accetti l'oggetto `Translator` come dipendenza nel costruttore e lo passiamo al modulo: ```php use Nette\Localization\Translator; @@ -104,18 +104,17 @@ class FormFactory return $form; } - //... + // ... } ``` -Poiché il metodo `createForm()` viene richiamato anche da altri metodi che creano moduli specifici, dobbiamo impostare il traduttore solo in quel metodo. E il gioco è fatto. Non è necessario modificare il codice del presentatore o del componente, il che è fantastico. +Poiché il metodo `createForm()` viene chiamato anche dagli altri metodi che creano moduli specifici, è sufficiente impostare il translator solo lì. E abbiamo finito. Non è necessario modificare il codice di nessun presenter o componente, il che è fantastico. -Altre classi di fabbrica .[#toc-more-factory-classes] -===================================================== +Più classi factory +================== -In alternativa, è possibile creare più classi per ogni modulo che si desidera utilizzare nell'applicazione. -Questo approccio può aumentare la leggibilità del codice e rendere i moduli più facili da gestire. Lasciate l'originale `FormFactory` per creare solo un form puro con una configurazione di base (ad esempio, con il supporto per la traduzione) e create un nuovo factory `EditFormFactory` per il form di modifica. +In alternativa, potete creare più classi per ogni modulo che volete utilizzare nella vostra applicazione. Questo approccio può aumentare la leggibilità del codice e facilitare la gestione dei moduli. Lasciamo che la `FormFactory` originale crei solo un modulo pulito con la configurazione di base (ad esempio con il supporto alle traduzioni) e per il modulo di modifica creiamo una nuova factory `EditFormFactory`. ```php class FormFactory @@ -145,40 +144,39 @@ class EditFormFactory public function create(): Form { $form = $this->formFactory->create(); - // i campi aggiuntivi del modulo sono aggiunti qui - $form->addSubmit('send', 'Save'); + // qui vengono aggiunti altri campi del modulo + $form->addSubmit('send', 'Invia'); return $form; } } ``` -È molto importante che il legame tra le classi `FormFactory` e `EditFormFactory` sia implementato tramite composizione, non tramite ereditarietà degli oggetti: +È molto importante che la relazione tra le classi `FormFactory` e `EditFormFactory` sia realizzata tramite [composizione |nette:introduction-to-object-oriented-programming#Composizione], e non tramite [ereditarietà degli oggetti |nette:introduction-to-object-oriented-programming#Ereditarietà]: ```php -// NO! L'EREDITÀ NON È QUI +// ⛔ NON COSÌ! L'EREDITARIETÀ NON APPARTIENE QUI class EditFormFactory extends FormFactory { public function create(): Form { $form = parent::create(); - $form->addText('title', 'Title:'); - // i campi aggiuntivi del modulo sono aggiunti qui - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Titolo:'); + // qui vengono aggiunti altri campi del modulo + $form->addSubmit('send', 'Invia'); return $form; } } ``` -L'uso dell'ereditarietà in questo caso sarebbe completamente controproducente. Si incorrerebbe in problemi molto rapidamente. Per esempio, se si volessero aggiungere parametri al metodo `create()`, PHP segnalerebbe un errore perché la sua firma è diversa da quella del genitore. -Oppure quando si passa una dipendenza alla classe `EditFormFactory` tramite il costruttore. Questo causerebbe quello che chiamiamo l'[inferno dei costruttori |dependency-injection:passing-dependencies#Constructor hell]. +L'uso dell'ereditarietà sarebbe in questo caso del tutto controproducente. Incontrereste problemi molto rapidamente. Ad esempio, nel momento in cui voleste aggiungere parametri al metodo `create()`; PHP segnalerebbe un errore indicando che la sua firma differisce da quella del genitore. Oppure passando una dipendenza alla classe `EditFormFactory` tramite il costruttore. Si verificherebbe una situazione che chiamiamo [constructor hell |dependency-injection:passing-dependencies#Constructor hell]. -In generale, è meglio preferire la composizione all'ereditarietà. +In generale, è meglio preferire la [composizione all'ereditarietà |dependency-injection:faq#Perché si preferisce la composizione all ereditarietà]. -Gestione dei moduli .[#toc-form-handling] -========================================= +Gestione del modulo +=================== -Il gestore del form che viene chiamato dopo un invio riuscito può anche far parte di una classe factory. Funzionerà passando i dati inviati al modello per l'elaborazione. Passerà gli eventuali errori [al |forms:validation#Processing Errors] modulo. Il modello dell'esempio seguente è rappresentato dalla classe `Facade`: +La gestione del modulo, che viene chiamata dopo l'invio riuscito, può anche far parte della classe factory. Funzionerà passando i dati inviati al modello per l'elaborazione. Eventuali errori verranno [restituiti |forms:validation#Errori durante l Elaborazione] al modulo. Il modello nell'esempio seguente è rappresentato dalla classe `Facade`: ```php class EditFormFactory @@ -192,9 +190,9 @@ class EditFormFactory public function create(): Form { $form = $this->formFactory->create(); - $form->addText('title', 'Title:'); - // i campi aggiuntivi del modulo vengono aggiunti qui - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Titolo:'); + // qui vengono aggiunti altri campi del modulo + $form->addSubmit('send', 'Invia'); $form->onSuccess[] = [$this, 'processForm']; return $form; } @@ -212,7 +210,7 @@ class EditFormFactory } ``` -Lasciamo che sia il presentatore stesso a gestire il reindirizzamento. Aggiungerà un altro gestore all'evento `onSuccess`, che eseguirà il reindirizzamento. Ciò consentirà di utilizzare il modulo in diversi presentatori, ognuno dei quali potrà reindirizzare a una posizione diversa. +Tuttavia, lasceremo il reindirizzamento effettivo al presenter. Aggiungerà un altro gestore all'evento `onSuccess`, che eseguirà il reindirizzamento. Grazie a ciò, sarà possibile utilizzare il modulo in diversi presenter e reindirizzare a un luogo diverso in ciascuno di essi. ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -226,7 +224,7 @@ class MyPresenter extends Nette\Application\UI\Presenter { $form = $this->formFactory->create(); $form->onSuccess[] = function () { - $this->flashMessage('Záznam byl uložen'); + $this->flashMessage('Il record è stato salvato'); $this->redirect('Homepage:'); }; return $form; @@ -234,39 +232,38 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Questa soluzione sfrutta la proprietà dei moduli per cui, quando `addError()` viene chiamato su un modulo o su un suo elemento, il gestore successivo `onSuccess` non viene invocato. +Questa soluzione sfrutta la proprietà dei moduli secondo cui, quando viene chiamato `addError()` sul modulo o su un suo elemento, il successivo gestore `onSuccess` non viene più chiamato. -Ereditare dalla classe Form .[#toc-inheriting-from-the-form-class] -================================================================== +Ereditarietà dalla classe Form +============================== -Un modulo costruito non dovrebbe essere figlio di un modulo. In altre parole, non utilizzate questa soluzione: +Il modulo costruito non deve essere un discendente del modulo. In altre parole, non utilizzate questa soluzione: ```php -// NO! L'EREDITÀ NON È QUI +// ⛔ NON COSÌ! L'EREDITARIETÀ NON APPARTIENE QUI class EditForm extends Form { public function __construct(Translator $translator) { parent::__construct(); - $form->addText('title', 'Title:'); - // i campi aggiuntivi del modulo sono aggiunti qui - $form->addSubmit('send', 'Save'); - $form->setTranslator($translator); + $this->addText('title', 'Titolo:'); + // qui vengono aggiunti altri campi del modulo + $this->addSubmit('send', 'Invia'); + $this->setTranslator($translator); } } ``` -Invece di costruire il modulo nel costruttore, utilizzare il factory. +Invece di costruire il modulo nel costruttore, utilizzate una factory. -È importante capire che la classe `Form` è principalmente uno strumento per assemblare un modulo, cioè un costruttore di moduli. Il modulo assemblato può essere considerato il suo prodotto. Tuttavia, il prodotto non è un caso specifico del costruttore; non c'è una relazione *è a* tra loro, che è alla base dell'ereditarietà. +È necessario rendersi conto che la classe `Form` è principalmente uno strumento per costruire un modulo, ovvero un *form builder*. E il modulo costruito può essere considerato come il suo prodotto. Ma il prodotto non è un caso specifico del builder, non c'è tra loro una relazione *is a* che costituisce la base dell'ereditarietà. -Componente Form .[#toc-form-component] -====================================== +Componente con modulo +===================== -Un approccio completamente diverso consiste nel creare un [componente |application:components] che includa un modulo. Questo offre nuove possibilità, ad esempio per rendere il modulo in un modo specifico, dato che il componente include un modello. -Oppure si possono usare segnali per la comunicazione AJAX e il caricamento di informazioni nel modulo, ad esempio per i suggerimenti, ecc. +Un approccio completamente diverso è la creazione di un [componente|application:components], che include un modulo. Questo offre nuove possibilità, ad esempio renderizzare il modulo in modo specifico, poiché il componente include anche un template. Oppure è possibile utilizzare i segnali per la comunicazione AJAX e il caricamento dinamico di informazioni nel modulo, ad esempio per i suggerimenti, ecc. ```php @@ -284,9 +281,9 @@ class EditControl extends Nette\Application\UI\Control protected function createComponentForm(): Form { $form = new Form; - $form->addText('title', 'Title:'); - // i campi aggiuntivi del modulo vengono aggiunti qui - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Titolo:'); + // qui vengono aggiunti altri campi del modulo + $form->addSubmit('send', 'Invia'); $form->onSuccess[] = [$this, 'processForm']; return $form; @@ -303,13 +300,13 @@ class EditControl extends Nette\Application\UI\Control return; } - // invocazione di eventi + // attivazione dell'evento $this->onSave($this, $data); } } ``` -Creiamo un factory che produca questo componente. È sufficiente [scrivere la sua interfaccia |application:components#Components with Dependencies]: +Creeremo anche una factory che produrrà questo componente. È sufficiente [scrivere la sua interfaccia |application:components#Componenti con dipendenze]: ```php interface EditControlFactory @@ -318,14 +315,14 @@ interface EditControlFactory } ``` -e aggiungerla al file di configurazione: +E aggiungerla al file di configurazione: ```neon services: - EditControlFactory ``` -Ora possiamo richiedere il factory e utilizzarlo nel presenter: +E ora possiamo richiedere la factory e utilizzarla nel presenter: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -335,13 +332,13 @@ class MyPresenter extends Nette\Application\UI\Presenter ) { } - protected function createComponentEditForm(): Form + protected function createComponentEditForm(): EditControl { $control = $this->controlFactory->create(); $control->onSave[] = function (EditControl $control, $data) { $this->redirect('this'); - // o reindirizzare al risultato della modifica, ad es: + // o reindirizziamo al risultato della modifica, ad esempio: // $this->redirect('detail', ['id' => $data->id]); }; @@ -349,5 +346,3 @@ class MyPresenter extends Nette\Application\UI\Presenter } } ``` - -{{sitename: Migliori pratiche}} diff --git a/best-practices/it/inject-method-attribute.texy b/best-practices/it/inject-method-attribute.texy index 2c060f8ed4..d031886e4c 100644 --- a/best-practices/it/inject-method-attribute.texy +++ b/best-practices/it/inject-method-attribute.texy @@ -1,21 +1,18 @@ -Metodi e attributi di iniezione -******************************* +Metodi e attributi inject +************************* .[perex] -In questo articolo, ci concentreremo sui vari modi di passare le dipendenze ai presentatori nel framework Nette. Confronteremo il metodo preferito, ovvero il costruttore, con altre opzioni, quali i metodi e gli attributi di `inject`. +In questo articolo ci concentreremo sui diversi modi di passare le dipendenze ai presenter nel framework Nette. Confronteremo il metodo preferito, che è il costruttore, con altre opzioni come i metodi e gli attributi `inject`. -Anche per i presenter, il passaggio delle dipendenze tramite il [costruttore |dependency-injection:passing-dependencies#Constructor Injection] è il metodo preferito. -Tuttavia, se si crea un antenato comune da cui ereditano altri presentatori (ad esempio, BasePresenter) e questo antenato ha anch'esso delle dipendenze, si verifica un problema, chiamato [constructor hell |dependency-injection:passing-dependencies#Constructor hell]. -Questo problema può essere aggirato utilizzando metodi alternativi, che includono l'iniezione di metodi e attributi (annotazioni). +Anche per i presenter vale che il passaggio delle dipendenze tramite il [costruttore |dependency-injection:passing-dependencies#Passaggio tramite costruttore] è il percorso preferito. Tuttavia, se si crea un antenato comune da cui ereditano altri presenter (ad es. `BasePresenter`), e questo antenato ha anch'esso delle dipendenze, si verifica un problema che chiamiamo [constructor hell |dependency-injection:passing-dependencies#Constructor hell]. Questo può essere aggirato utilizzando percorsi alternativi, che sono rappresentati dai metodi e dagli attributi (annotazioni) `inject`. -`inject*()` Metodi .[#toc-inject-methods] -========================================= +Metodi `inject*()` +================== -Si tratta di una forma di passaggio di dipendenze che utilizza i [setter |dependency-injection:passing-dependencies#Setter Injection]. I nomi di questi setter iniziano con il prefisso inject. -Nette DI chiama automaticamente questi metodi denominati subito dopo la creazione dell'istanza del presentatore e passa loro tutte le dipendenze richieste. Pertanto, devono essere dichiarati come pubblici. +È una forma di passaggio della dipendenza tramite [setter |dependency-injection:passing-dependencies#Passaggio tramite setter]. Il nome di questi setter inizia con il prefisso `inject`. Nette DI chiama automaticamente i metodi così denominati subito dopo la creazione dell'istanza del presenter e passa loro tutte le dipendenze richieste. Devono quindi essere dichiarati come public. -`inject*()` I metodi possono essere considerati come una sorta di estensione del costruttore in più metodi. Grazie a ciò, `BasePresenter` può prendere le dipendenze attraverso un altro metodo e lasciare il costruttore libero per i suoi discendenti: +I metodi `inject*()` possono essere considerati come una sorta di estensione del costruttore in più metodi. Grazie a ciò, `BasePresenter` può ricevere le dipendenze tramite un altro metodo e lasciare il costruttore libero per i suoi discendenti: ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -39,18 +36,18 @@ class MyPresenter extends BasePresenter } ``` -Il presentatore può contenere un numero qualsiasi di metodi `inject*()` e ognuno può avere un numero qualsiasi di parametri. Questo è ottimo anche per i casi in cui il presentatore è [composto da tratti |presenter-traits] e ognuno di essi richiede la propria dipendenza. +Un presenter può contenere un numero qualsiasi di metodi `inject*()` e ognuno può avere un numero qualsiasi di parametri. Sono ottimi anche nei casi in cui il presenter è [composto da trait |presenter-traits] e ognuno di essi richiede la propria dipendenza. -`Inject` Attributi .[#toc-inject-attributes] -============================================ +Attributi `Inject` +================== -Si tratta di una forma di [iniezione nelle proprietà |dependency-injection:passing-dependencies#Property Injection]. È sufficiente indicare quali proprietà devono essere iniettate e Nette DI passa automaticamente le dipendenze subito dopo aver creato l'istanza del presentatore. Per inserirle, è necessario dichiararle come pubbliche. +È una forma di [iniezione nella proprietà |dependency-injection:passing-dependencies#Impostazione di una variabile]. È sufficiente contrassegnare in quali variabili iniettare e Nette DI passerà automaticamente le dipendenze subito dopo la creazione dell'istanza del presenter. Per poterle inserire, è necessario dichiararle come public. -Le proprietà sono contrassegnate da un attributo: (in precedenza, si utilizzava l'annotazione `/** @inject */`) +Contrassegniamo le proprietà con un attributo: (in precedenza si usava l'annotazione `/** @inject */`) ```php -use Nette\DI\Attributes\Inject; // questa riga è importante +use Nette\DI\Attributes\Inject; // questa riga è importante class MyPresenter extends Nette\Application\UI\Presenter { @@ -59,9 +56,6 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Il vantaggio di questo metodo di passaggio delle dipendenze era la sua forma di notazione molto economica. Tuttavia, con l'introduzione della [promozione delle proprietà del costruttore |https://blog.nette.org/it/php-8-0-panoramica-completa-delle-novita#toc-constructor-property-promotion], l'uso del costruttore sembra più semplice. +Il vantaggio di questo modo di passare le dipendenze era la forma di scrittura molto concisa. Tuttavia, con l'avvento della [constructor property promotion |https://blog.nette.org/it/php-8-0-complete-overview-of-news#toc-constructor-property-promotion], sembra più facile utilizzare il costruttore. -D'altra parte, questo metodo soffre degli stessi difetti del passaggio delle dipendenze nelle proprietà in generale: non abbiamo alcun controllo sulle modifiche della variabile e, allo stesso tempo, la variabile diventa parte dell'interfaccia pubblica della classe, il che è indesiderabile. - - -{{sitename: Migliori pratiche}} +Al contrario, questo metodo soffre degli stessi svantaggi del passaggio delle dipendenze alle proprietà in generale: non abbiamo controllo sulle modifiche nella variabile e allo stesso tempo la variabile diventa parte dell'interfaccia pubblica della classe, il che è indesiderabile. diff --git a/best-practices/it/lets-create-contact-form.texy b/best-practices/it/lets-create-contact-form.texy index 463ed21c39..5b7992de4b 100644 --- a/best-practices/it/lets-create-contact-form.texy +++ b/best-practices/it/lets-create-contact-form.texy @@ -1,12 +1,12 @@ -Creiamo un modulo di contatto -***************************** +Creazione di un modulo di contatto +********************************** .[perex] -Vediamo come creare un modulo di contatto in Nette, compreso l'invio a un'e-mail. Allora, facciamolo! +Vediamo come creare un modulo di contatto in Nette, compreso l'invio di email. Allora, iniziamo! -Per prima cosa dobbiamo creare un nuovo progetto. Come spiega la pagina [introduttiva |nette:installation]. Poi possiamo iniziare a creare il modulo. +Prima di tutto, dobbiamo creare un nuovo progetto. Come farlo è spiegato nella pagina [Iniziare |nette:installation]. E poi possiamo iniziare a creare il modulo. -Il modo più semplice è creare il [modulo direttamente in Presenter |forms:in-presenter]. Possiamo utilizzare il componente preconfezionato `HomePresenter`. Aggiungeremo il componente `contactForm` che rappresenta il modulo. Per farlo, scriviamo il metodo `createComponentContactForm()` factory nel codice che produrrà il componente: +Il modo più semplice è creare il [modulo direttamente nel presenter |forms:in-presenter]. Possiamo utilizzare il `HomePresenter` pre-preparato. Aggiungeremo ad esso il componente `contactForm` che rappresenta il modulo. Lo faremo scrivendo nel codice il metodo factory `createComponentContactForm()`, che produrrà il componente: ```php use Nette\Application\UI\Form; @@ -17,37 +17,35 @@ class HomePresenter extends Presenter protected function createComponentContactForm(): Form { $form = new Form; - $form->addText('name', 'Name:') - ->setRequired('Enter your name'); + $form->addText('name', 'Nome:') + ->setRequired('Inserisci il nome'); $form->addEmail('email', 'E-mail:') - ->setRequired('Enter your e-mail'); - $form->addTextarea('message', 'Message:') - ->setRequired('Enter message'); - $form->addSubmit('send', 'Send'); + ->setRequired('Inserisci l\'e-mail'); + $form->addTextarea('message', 'Messaggio:') + ->setRequired('Inserisci il messaggio'); + $form->addSubmit('send', 'Invia'); $form->onSuccess[] = [$this, 'contactFormSucceeded']; return $form; } public function contactFormSucceeded(Form $form, $data): void { - // sending an email + // invio dell'email } } ``` -Come si può vedere, abbiamo creato due metodi. Il primo metodo `createComponentContactForm()` crea un nuovo modulo. Questo ha campi per il nome, l'email e il messaggio, che vengono aggiunti con i metodi `addText()`, `addEmail()` e `addTextArea()`. Abbiamo anche aggiunto un pulsante per inviare il modulo. -Ma cosa succede se l'utente non compila alcuni campi? In questo caso, dovremmo fargli sapere che si tratta di un campo obbligatorio. Lo abbiamo fatto con il metodo `setRequired()`. -Infine, abbiamo aggiunto anche un [evento |nette:glossary#events] `onSuccess`, che si attiva se il form viene inviato con successo. Nel nostro caso, richiama il metodo `contactFormSucceeded`, che si occupa di elaborare il modulo inviato. Lo aggiungeremo al codice tra poco. +Come potete vedere, abbiamo creato due metodi. Il primo metodo `createComponentContactForm()` crea un nuovo modulo. Questo ha campi per nome, email e messaggio, che aggiungiamo con i metodi `addText()`, `addEmail()` e `addTextArea()`. Abbiamo anche aggiunto un pulsante per inviare il modulo. Ma cosa succede se l'utente non compila qualche campo? In tal caso, dovremmo fargli sapere che è un campo obbligatorio. Abbiamo ottenuto questo risultato con il metodo `setRequired()`. Infine, abbiamo aggiunto anche l'[evento |nette:glossary#Eventi] `onSuccess`, che si attiva se il modulo viene inviato con successo. Nel nostro caso, chiama il metodo `contactFormSucceeded`, che si occuperà dell'elaborazione del modulo inviato. Lo aggiungeremo al codice tra un momento. -Il componente `contantForm` deve essere reso nel template `templates/Home/default.latte`: +Faremo renderizzare il componente `contactForm` nel template `Home/default.latte`: ```latte {block content} -<h1>Contant Form</h1> +<h1>Modulo di contatto</h1> {control contactForm} ``` -Per inviare l'e-mail stessa, creiamo una nuova classe chiamata `ContactFacade` e la inseriamo nel file `app/Model/ContactFacade.php`: +Per l'invio effettivo dell'email, creeremo una nuova classe che chiameremo `ContactFacade` e la posizioneremo nel file `app/Model/ContactFacade.php`: ```php <?php @@ -68,9 +66,9 @@ class ContactFacade public function sendMessage(string $email, string $name, string $message): void { $mail = new Message; - $mail->addTo('admin@example.com') // your email + $mail->addTo('admin@example.com') // la tua email ->setFrom($email, $name) - ->setSubject('Message from the contact form') + ->setSubject('Messaggio dal modulo di contatto') ->setBody($message); $this->mailer->send($mail); @@ -78,9 +76,9 @@ class ContactFacade } ``` -Il metodo `sendMessage()` creerà e invierà l'email. Per farlo, utilizza un cosiddetto mailer, che passa come dipendenza attraverso il costruttore. Per saperne di più sull'[invio di e-mail |mail:]. +Il metodo `sendMessage()` crea e invia l'email. Utilizza a tal fine il cosiddetto mailer, che si fa passare come dipendenza tramite il costruttore. Leggete di più sull'[invio di email |mail:]. -Ora, torniamo al presentatore e completiamo il metodo `contactFormSucceeded()`. Richiama il metodo `sendMessage()` della classe `ContactFacade` e gli passa i dati del modulo. Come si ottiene l'oggetto `ContactFacade`? Ce lo passerà il costruttore: +Ora torniamo al presenter e completiamo il metodo `contactFormSucceeded()`. Questo chiamerà il metodo `sendMessage()` della classe `ContactFacade` e gli passerà i dati del modulo. E come otteniamo l'oggetto `ContactFacade`? Ce lo facciamo passare tramite il costruttore: ```php use App\Model\ContactFacade; @@ -102,36 +100,36 @@ class HomePresenter extends Presenter public function contactFormSucceeded(stdClass $data): void { $this->facade->sendMessage($data->email, $data->name, $data->message); - $this->flashMessage('The message has been sent'); + $this->flashMessage('Il messaggio è stato inviato'); $this->redirect('this'); } } ``` -Dopo l'invio dell'e-mail, mostriamo all'utente il cosiddetto [messaggio flash |application:components#flash-messages], che conferma l'invio del messaggio, e poi reindirizziamo alla pagina successiva, in modo che il modulo non possa essere ripresentato usando *refresh* nel browser. +Dopo l'invio dell'email, mostreremo ancora all'utente il cosiddetto [flash message |application:components#Messaggi flash], confermando che il messaggio è stato inviato, e poi reindirizzeremo a un'altra pagina, in modo che non sia possibile inviare nuovamente il modulo tramite *refresh* nel browser. -Se tutto funziona, dovreste essere in grado di inviare un'e-mail dal vostro modulo di contatto. Congratulazioni! +Bene, e se tutto funziona, dovreste essere in grado di inviare un'email dal vostro modulo di contatto. Congratulazioni! -Modello di e-mail HTML .[#toc-html-email-template] --------------------------------------------------- +Template HTML dell'email +------------------------ -Per ora, viene inviata un'e-mail di testo semplice contenente solo il messaggio inviato dal modulo. Ma possiamo usare l'HTML nell'e-mail e renderla più attraente. Creeremo un modello in Latte, che salveremo in `app/Model/contactEmail.latte`: +Per ora viene inviata un'email di testo semplice contenente solo il messaggio inviato dal modulo. Ma nell'email possiamo utilizzare HTML e renderne l'aspetto più attraente. Creeremo per essa un template in Latte, che scriveremo in `app/Model/contactEmail.latte`: ```latte <html> - <title>Message from the contact form + Messaggio dal modulo di contatto -

    Name: {$name}

    +

    Nome: {$name}

    E-mail: {$email}

    -

    Message: {$message}

    +

    Messaggio: {$message}

    ``` -Resta da modificare `ContactFacade` per utilizzare questo modello. Nel costruttore, richiediamo la classe `LatteFactory`, che può produrre l'oggetto `Latte\Engine`, un [renderizzatore di template di Latte |latte:develop#how-to-render-a-template]. Utilizziamo il metodo `renderToString()` per rendere il template in un file; il primo parametro è il percorso del template e il secondo sono le variabili. +Resta da modificare `ContactFacade` affinché utilizzi questo template. Nel costruttore richiederemo la classe `LatteFactory`, che sa produrre l'oggetto `Latte\Engine`, ovvero il [renderizzatore di template Latte |latte:develop#Come Renderizzare un Template]. Tramite il metodo `renderToString()` renderizzeremo il template in un file, il primo parametro è il percorso del template e il secondo sono le variabili. ```php namespace App\Model; @@ -158,7 +156,7 @@ class ContactFacade ]); $mail = new Message; - $mail->addTo('admin@example.com') // your email + $mail->addTo('admin@example.com') // la tua email ->setFrom($email, $name) ->setHtmlBody($body); @@ -167,15 +165,15 @@ class ContactFacade } ``` -Passiamo poi l'email HTML generata al metodo `setHtmlBody()`, invece del metodo originale `setBody()`. Non è necessario specificare l'oggetto dell'email in `setSubject()`, perché la libreria lo prende dall'elemento `` nel template. +L'email HTML generata la passeremo quindi al metodo `setHtmlBody()` invece dell'originale `setBody()`. Inoltre, non dobbiamo specificare l'oggetto dell'email in `setSubject()`, perché la libreria lo prenderà dall'elemento `<title>` del template. -Configurazione di .[#toc-configuring] -------------------------------------- +Configurazione +-------------- -Nel codice della classe `ContactFacade`, l'email di amministrazione `admin@example.com` è ancora codificata in modo rigido. Sarebbe meglio spostarla nel file di configurazione. Come fare? +Nel codice della classe `ContactFacade` è ancora hardcoded la nostra email di amministratore `admin@example.com`. Sarebbe meglio spostarla nel file di configurazione. Come fare? -Per prima cosa, modifichiamo la classe `ContactFacade` e sostituiamo la stringa dell'email con una variabile passata dal costruttore: +Prima modifichiamo la classe `ContactFacade` e sostituiamo la stringa con l'email con una variabile passata tramite il costruttore: ```php class ContactFacade @@ -199,28 +197,25 @@ class ContactFacade } ``` -Il secondo passo consiste nell'inserire il valore di questa variabile nella configurazione. Nel file `app/config/services.neon` aggiungiamo: +E il secondo passo è specificare il valore di questa variabile nella configurazione. Nel file `app/config/services.neon` scriviamo: ```neon services: - App\Model\ContactFacade(adminEmail: admin@example.com) ``` -E questo è tutto. Se ci sono molte voci nella sezione `services` e si ha la sensazione che l'e-mail si perda tra di esse, possiamo renderla una variabile. Modificheremo la voce in: +Ed è fatto. Se ci fossero molte voci nella sezione `services` e aveste la sensazione che l'email si perda tra di esse, possiamo trasformarla in una variabile. Modifichiamo la scrittura in: ```neon services: - App\Model\ContactFacade(adminEmail: %adminEmail%) ``` -E definiamo questa variabile nel file `app/config/common.neon`: +E nel file `app/config/common.neon` definiamo questa variabile: ```neon parameters: adminEmail: admin@example.com ``` -Ed è fatta! - - -{{sitename: Migliori pratiche}} +Ed è fatto! diff --git a/best-practices/it/microsites.texy b/best-practices/it/microsites.texy new file mode 100644 index 0000000000..1efd628d35 --- /dev/null +++ b/best-practices/it/microsites.texy @@ -0,0 +1,63 @@ +Come scrivere micrositi +*********************** + +Immaginate di dover creare rapidamente un piccolo sito web per il prossimo evento della vostra azienda. Deve essere semplice, veloce e senza complicazioni inutili. Potreste pensare che per un progetto così piccolo non abbiate bisogno di un framework robusto. Ma cosa succederebbe se l'uso del framework Nette potesse semplificare e accelerare radicalmente questo processo? + +Dopotutto, anche nella creazione di siti web semplici, non volete rinunciare alla comodità. Non volete reinventare ciò che è già stato risolto una volta. Siate pure pigri e lasciatevi coccolare. Nette Framework può essere utilizzato egregiamente anche come micro framework. + +Come può apparire un tale microsito? Ad esempio, in modo che l'intero codice del sito web sia collocato in un unico file `index.php` nella cartella pubblica: + +```php +<?php + +require __DIR__ . '/../vendor/autoload.php'; + +$configurator = new Nette\Bootstrap\Configurator; +$configurator->enableTracy(__DIR__ . '/../log'); +$configurator->setTempDirectory(__DIR__ . '/../temp'); + +// crea il container DI basato sulla configurazione in config.neon +$configurator->addConfig(__DIR__ . '/../app/config.neon'); +$container = $configurator->createContainer(); + +// impostiamo il routing +$router = new Nette\Application\Routers\RouteList; +$container->addService('router', $router); + +// route per l'URL https://example.com/ +$router->addRoute('', function ($presenter, Nette\Http\Request $httpRequest) { + // rileviamo la lingua del browser e reindirizziamo all'URL /en o /de ecc. + $supportedLangs = ['en', 'de', 'cs']; + $lang = $httpRequest->detectLanguage($supportedLangs) ?: reset($supportedLangs); + $presenter->redirectUrl("/$lang"); +}); + +// route per l'URL https://example.com/cs o https://example.com/en +$router->addRoute('<lang cs|en>', function ($presenter, string $lang) { + // visualizziamo il template corrispondente, ad esempio ../templates/en.latte + $template = $presenter->createTemplate() + ->setFile(__DIR__ . '/../templates/' . $lang . '.latte'); + return $template; +}); + +// avvia l'applicazione! +$container->getByType(Nette\Application\Application::class)->run(); +``` + +Tutto il resto saranno template salvati nella cartella padre `/templates`. + +Il codice PHP in `index.php` prima [prepara l'ambiente |bootstrap:], poi definisce le [route |application:routing#Routing dinamico con callback] e infine avvia l'applicazione. Il vantaggio è che il secondo parametro della funzione `addRoute()` può essere un callable, che viene eseguito dopo l'apertura della pagina corrispondente. + + +Perché usare Nette per un microsito? +------------------------------------ + +- I programmatori che hanno provato [Tracy|tracy:] una volta, oggi non riescono a immaginare di programmare qualcosa senza di essa. +- Ma soprattutto utilizzerete il sistema di template [Latte|latte:], perché già da 2 pagine vorrete avere separati il [layout e il contenuto|latte:template-inheritance]. +- E sicuramente volete fare affidamento sull'[escaping automatico |latte:safety-first], affinché non si crei una vulnerabilità XSS +- Nette garantisce anche che, in caso di errore, non vengano mai visualizzati messaggi di errore PHP per programmatori, ma una pagina comprensibile per l'utente. +- Se volete ottenere feedback dagli utenti, ad esempio sotto forma di modulo di contatto, aggiungerete anche i [moduli|forms:] e il [database|database:]. +- Potete anche farvi [inviare facilmente via email|mail:] i moduli compilati. +- A volte potrebbe esservi utile il [caching|caching:], ad esempio se scaricate e visualizzate feed. + +Al giorno d'oggi, quando la velocità e l'efficienza sono fondamentali, è importante avere strumenti che vi permettano di ottenere risultati senza inutili ritardi. Nette framework vi offre proprio questo: sviluppo rapido, sicurezza e un'ampia gamma di strumenti, come Tracy e Latte, che semplificano il processo. Basta installare un paio di pacchetti Nette e costruire un tale microsito diventa improvvisamente un gioco da ragazzi. E sapete che non si nasconde nessuna falla di sicurezza da nessuna parte. diff --git a/best-practices/it/pagination.texy b/best-practices/it/pagination.texy index f88acbacb5..1f61f6b633 100644 --- a/best-practices/it/pagination.texy +++ b/best-practices/it/pagination.texy @@ -2,16 +2,15 @@ Paginazione dei risultati del database ************************************** .[perex] -Quando si sviluppano applicazioni Web, capita spesso di dover stampare un numero limitato di record in una pagina. +Durante la creazione di applicazioni web, incontrerete molto spesso la necessità di limitare il numero di elementi visualizzati per pagina. -Si esce da questo stato quando si elencano tutti i dati senza paginazione. Per selezionare i dati dal database, abbiamo la classe ArticleRepository, che contiene il costruttore e il metodo `findPublishedArticles`, che restituisce tutti gli articoli pubblicati ordinati in ordine decrescente di data di pubblicazione. +Partiamo dallo stato in cui visualizziamo tutti i dati senza paginazione. Per selezionare i dati dal database abbiamo la classe ArticleRepository, che oltre al costruttore contiene il metodo `findPublishedArticles`, che restituisce tutti gli articoli pubblicati ordinati in modo decrescente per data di pubblicazione. ```php namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -31,10 +30,10 @@ class ArticleRepository } ``` -Nel Presenter iniettiamo poi la classe Model e nel metodo render chiediamo gli articoli pubblicati che passiamo al template: +Nel presenter, quindi, iniettiamo la classe del modello e nel metodo render richiediamo gli articoli pubblicati, che passiamo al template: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -53,11 +52,11 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -Nel modello, ci occuperemo di rendere un elenco di articoli: +Nel template `default.latte` ci occupiamo quindi della visualizzazione degli articoli: ```latte {block content} -<h1>Articles</h1> +<h1>Articoli</h1> <div class="articles"> {foreach $articles as $article} @@ -68,11 +67,11 @@ Nel modello, ci occuperemo di rendere un elenco di articoli: ``` -In questo modo, possiamo scrivere tutti gli articoli, ma questo causerà problemi quando il numero di articoli crescerà. A quel punto, sarà utile implementare il meccanismo di paginazione. +In questo modo sappiamo visualizzare tutti gli articoli, il che però inizia a creare problemi nel momento in cui il numero di articoli aumenta. In quel momento diventa utile implementare un meccanismo di paginazione. -In questo modo, tutti gli articoli saranno suddivisi in più pagine e verranno mostrati solo gli articoli di una pagina corrente. Il numero totale di pagine e la distribuzione degli articoli sono calcolati da [Paginator |utils:Paginator] stesso, a seconda di quanti articoli abbiamo in totale e di quanti articoli vogliamo visualizzare nella pagina. +Questo garantirà che tutti gli articoli vengano divisi in più pagine e noi visualizzeremo solo gli articoli di una pagina corrente. Il numero totale di pagine e la divisione degli articoli verranno calcolati da [Paginator|utils:Paginator] stesso in base a quanti articoli abbiamo in totale e quanti articoli per pagina vogliamo visualizzare. -Nel primo passo, modificheremo il metodo per ottenere gli articoli nella classe repository, in modo da restituire solo articoli a pagina singola. Aggiungeremo anche un nuovo metodo per ottenere il numero totale di articoli nel database, che ci servirà per impostare un Paginator: +Nel primo passo, modifichiamo il metodo per ottenere gli articoli nella classe del repository in modo che possa restituirci solo gli articoli per una pagina. Aggiungiamo anche un metodo per determinare il numero totale di articoli nel database, che ci servirà per impostare il Paginator: ```php namespace App\Model; @@ -100,7 +99,7 @@ class ArticleRepository } /** - * Returns the total number of published articles + * Restituisce il numero totale di articoli pubblicati */ public function getPublishedArticlesCount(): int { @@ -109,12 +108,12 @@ class ArticleRepository } ``` -Il passo successivo è modificare il presentatore. Inoltreremo il numero della pagina attualmente visualizzata al metodo render. Nel caso in cui questo numero non faccia parte dell'URL, occorre impostare il valore predefinito alla prima pagina. +Successivamente, ci dedichiamo alle modifiche del presenter. Al metodo render passeremo il numero della pagina attualmente visualizzata. Nel caso in cui questo numero non sia parte dell'URL, imposteremo il valore predefinito della prima pagina. -Espandiamo inoltre il metodo render per ottenere l'istanza di Paginator, impostandola e selezionando gli articoli corretti da visualizzare nel template. HomePresenter avrà questo aspetto: +Inoltre, estenderemo il metodo render con l'ottenimento dell'istanza di Paginator, la sua impostazione e la selezione degli articoli corretti per la visualizzazione nel template. HomePresenter dopo le modifiche apparirà così: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -128,31 +127,31 @@ class HomePresenter extends Nette\Application\UI\Presenter public function renderDefault(int $page = 1): void { - // Troveremo il numero totale di articoli pubblicati + // Otteniamo il numero totale di articoli pubblicati $articlesCount = $this->articleRepository->getPublishedArticlesCount(); - // Creeremo l'istanza del Paginator e la configureremo + // Creiamo un'istanza di Paginator e la impostiamo $paginator = new Nette\Utils\Paginator; - $paginator->setItemCount($articlesCount); // conteggio totale degli articoli - $paginator->setItemsPerPage(10); // articoli per pagina - $paginator->setPage($page); // numero di pagina effettivo + $paginator->setItemCount($articlesCount); // numero totale di articoli + $paginator->setItemsPerPage(10); // numero di elementi per pagina + $paginator->setPage($page); // numero della pagina corrente - // Troveremo un insieme limitato di articoli dal database in base ai calcoli di Paginator + // Estraiamo dal database un sottoinsieme limitato di articoli secondo il calcolo del Paginator $articles = $this->articleRepository->findPublishedArticles($paginator->getLength(), $paginator->getOffset()); // che passiamo al template $this->template->articles = $articles; - // e anche a Paginator stesso per visualizzare le opzioni di paginazione + // e anche il Paginator stesso per visualizzare le opzioni di paginazione $this->template->paginator = $paginator; } } ``` -Il template itera già gli articoli in una pagina, basta aggiungere i link di paginazione: +Il template ora itera solo sugli articoli di una pagina, ci basta aggiungere i link di paginazione: ```latte {block content} -<h1>Articles</h1> +<h1>Articoli</h1> <div class="articles"> {foreach $articles as $article} @@ -163,34 +162,33 @@ Il template itera già gli articoli in una pagina, basta aggiungere i link di pa <div class="pagination"> {if !$paginator->isFirst()} - <a n:href="default, 1">First</a> + <a n:href="default, 1">Primo</a>  |  - <a n:href="default, $paginator->page-1">Previous</a> + <a n:href="default, $paginator->page-1">Precedente</a>  |  {/if} - Page {$paginator->getPage()} of {$paginator->getPageCount()} + Pagina {$paginator->getPage()} di {$paginator->getPageCount()} {if !$paginator->isLast()}  |  - <a n:href="default, $paginator->getPage() + 1">Next</a> + <a n:href="default, $paginator->getPage() + 1">Successivo</a>  |  - <a n:href="default, $paginator->getPageCount()">Last</a> + <a n:href="default, $paginator->getPageCount()">Ultimo</a> {/if} </div> ``` -Ecco come abbiamo aggiunto la paginazione utilizzando Paginator. Se si utilizza [Nette Database Explorer |database:explorer] al posto di [Nette Database Core |database:core] come livello di database, siamo in grado di implementare la paginazione anche senza Paginator. La classe `Nette\Database\Table\Selection` contiene il metodo [page |api:Nette\Database\Table\Selection::_ page] con la logica di paginazione presa da Paginator. +In questo modo abbiamo aggiunto alla pagina la possibilità di paginazione tramite Paginator. Nel caso in cui, invece di [Nette Database Core |database:sql-way] come livello di database utilizziamo [Nette Database Explorer |database:explorer], siamo in grado di implementare la paginazione anche senza l'uso di Paginator. La classe `Nette\Database\Table\Selection` infatti contiene il metodo [page |api:Nette\Database\Table\Selection::_page] con la logica di paginazione ereditata da Paginator. -Il repository avrà questo aspetto: +Il repository con questo metodo di implementazione apparirà così: ```php namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -198,7 +196,6 @@ class ArticleRepository ) { } - public function findPublishedArticles(): Nette\Database\Table\Selection { return $this->database->table('articles') @@ -208,10 +205,10 @@ class ArticleRepository } ``` -Non è necessario creare il Paginator nel Presenter, ma si utilizzerà il metodo dell'oggetto `Selection` restituito dal repository: +Nel presenter non dobbiamo creare Paginator, useremo al suo posto il metodo della classe `Selection`, che ci restituisce il repository: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -225,25 +222,25 @@ class HomePresenter extends Nette\Application\UI\Presenter public function renderDefault(int $page = 1): void { - // Troveremo gli articoli pubblicati + // Estraiamo gli articoli pubblicati $articles = $this->articleRepository->findPublishedArticles(); - // e la loro parte limitata dal calcolo del metodo della pagina che passeremo al template + // e inviamo al template solo una loro parte limitata secondo il calcolo del metodo page $lastPage = 0; $this->template->articles = $articles->page($page, 10, $lastPage); - // e i dati necessari per visualizzare anche le opzioni di paginazione + // e anche i dati necessari per visualizzare le opzioni di paginazione $this->template->page = $page; $this->template->lastPage = $lastPage; } } ``` -Poiché non usiamo il Paginator, dobbiamo modificare la sezione che mostra i link di paginazione: +Poiché ora non inviamo Paginator al template, modifichiamo la parte che visualizza i link di paginazione: ```latte {block content} -<h1>Articles</h1> +<h1>Articoli</h1> <div class="articles"> {foreach $articles as $article} @@ -254,24 +251,23 @@ Poiché non usiamo il Paginator, dobbiamo modificare la sezione che mostra i lin <div class="pagination"> {if $page > 1} - <a n:href="default, 1">First</a> + <a n:href="default, 1">Primo</a>  |  - <a n:href="default, $page - 1">Previous</a> + <a n:href="default, $page - 1">Precedente</a>  |  {/if} - Page {$page} of {$lastPage} + Pagina {$page} di {$lastPage} {if $page < $lastPage}  |  - <a n:href="default, $page + 1">Next</a> + <a n:href="default, $page + 1">Successivo</a>  |  - <a n:href="default, $lastPage">Last</a> + <a n:href="default, $lastPage">Ultimo</a> {/if} </div> ``` -In questo modo, abbiamo implementato un meccanismo di paginazione senza usare il Paginator. +In questo modo abbiamo implementato il meccanismo di paginazione senza l'uso di Paginator. {{priority: -1}} -{{sitename: Migliori pratiche}} diff --git a/best-practices/it/passing-settings-to-presenters.texy b/best-practices/it/passing-settings-to-presenters.texy index b218047a90..61357ccd6b 100644 --- a/best-practices/it/passing-settings-to-presenters.texy +++ b/best-practices/it/passing-settings-to-presenters.texy @@ -1,10 +1,10 @@ -Passaggio delle impostazioni ai presentatori -******************************************** +Passaggio delle impostazioni ai presenter +***************************************** .[perex] -È necessario passare ai presentatori argomenti che non sono oggetti (ad esempio, informazioni sull'esecuzione in modalità di debug, percorsi di directory, ecc. La soluzione è incapsularli in un oggetto `Settings`. +Avete bisogno di passare ai presenter argomenti che non sono oggetti (ad esempio, informazioni se è in esecuzione in modalità debug, percorsi di directory, ecc.) e che quindi non possono essere passati automaticamente tramite autowiring? La soluzione è incapsularli in un oggetto `Settings`. -Il servizio `Settings` è un modo molto semplice e utile per fornire informazioni su un'applicazione in esecuzione ai presentatori. La sua forma specifica dipende interamente dalle vostre esigenze particolari. Esempio: +Il servizio `Settings` rappresenta un modo molto semplice e allo stesso tempo utile per fornire informazioni sull'applicazione in esecuzione ai presenter. La sua forma specifica dipende esclusivamente dalle vostre esigenze particolari. Esempio: ```php namespace App; @@ -12,7 +12,7 @@ namespace App; class Settings { public function __construct( - // a partire da PHP 8.1 è possibile specificare readonly + // da PHP 8.1 è possibile specificare readonly public bool $debugMode, public string $appDir, // e così via @@ -20,7 +20,7 @@ class Settings } ``` -Esempio di registrazione alla configurazione: +Esempio di registrazione nella configurazione: ```neon services: @@ -30,7 +30,7 @@ services: ) ``` -Quando il presentatore ha bisogno delle informazioni fornite da questo servizio, le richiede semplicemente nel costruttore: +Quando un presenter avrà bisogno delle informazioni fornite da questo servizio, semplicemente le richiederà nel costruttore: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -47,5 +47,3 @@ class MyPresenter extends Nette\Application\UI\Presenter } } ``` - -{{sitename: Migliori pratiche}} diff --git a/best-practices/it/post-links.texy b/best-practices/it/post-links.texy new file mode 100644 index 0000000000..7eccd96552 --- /dev/null +++ b/best-practices/it/post-links.texy @@ -0,0 +1,56 @@ +Come utilizzare correttamente i link POST +***************************************** + +.[perex] +Nelle applicazioni web, specialmente nelle interfacce amministrative, dovrebbe essere una regola fondamentale che le azioni che modificano lo stato del server non vengano eseguite tramite il metodo HTTP GET. Come suggerisce il nome del metodo, GET dovrebbe servire solo per ottenere dati, non per modificarli. Per azioni come l'eliminazione di record, è preferibile utilizzare il metodo POST. Anche se l'ideale sarebbe il metodo DELETE, ma non può essere invocato senza JavaScript, quindi storicamente si usa POST. + +Come farlo in pratica? Utilizzate questo semplice trucco. All'inizio del template, create un modulo ausiliario con l'identificatore `postForm`, che utilizzerete successivamente per i pulsanti di eliminazione: + +```latte .{file:@layout.latte} +<form method="post" id="postForm"></form> +``` + +Grazie a questo modulo, potete utilizzare un pulsante `<button>` invece di un classico link `<a>`, che può essere stilizzato visivamente per assomigliare a un normale link. Ad esempio, il framework CSS Bootstrap offre le classi `btn btn-link` con cui potete ottenere che il pulsante non sia visivamente diverso dagli altri link. Tramite l'attributo `form="postForm"` lo colleghiamo al modulo pre-preparato: + +```latte .{file:admin.latte} +<table> + <tr n:foreach="$posts as $post"> + <td>{$post->title}</td> + <td> + <button class="btn btn-link" form="postForm" formaction="{link delete $post->id}">delete</button> + <!-- instead of <a n:href="delete $post->id">delete</a> --> + </td> + </tr> +</table> +``` + +Cliccando sul link, ora verrà invocata l'azione `delete`. Per garantire che le richieste vengano accettate solo tramite il metodo POST e dallo stesso dominio (che è una difesa efficace contro gli attacchi CSRF), utilizzate l'attributo `#[Requires]`: + +```php .{file:AdminPresenter.php} +use Nette\Application\Attributes\Requires; + +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST', sameOrigin: true)] + public function actionDelete(int $id): void + { + $this->facade->deletePost($id); // codice ipotetico che elimina il record + $this->redirect('default'); + } +} +``` + +L'attributo esiste da Nette Application 3.2 e potete saperne di più sulle sue possibilità nella pagina [Come utilizzare l'attributo #Requires |attribute-requires]. + +Se invece dell'azione `actionDelete()` utilizzaste il segnale `handleDelete()`, non è necessario specificare `sameOrigin: true`, perché i segnali hanno questa protezione impostata implicitamente: + +```php .{file:AdminPresenter.php} +#[Requires(methods: 'POST')] +public function handleDelete(int $id): void +{ + $this->facade->deletePost($id); + $this->redirect('this'); +} +``` + +Questo approccio non solo migliora la sicurezza della vostra applicazione, ma contribuisce anche al rispetto degli standard e delle pratiche web corrette. Utilizzando i metodi POST per le azioni che modificano lo stato, otterrete un'applicazione più robusta e sicura. diff --git a/best-practices/it/presenter-traits.texy b/best-practices/it/presenter-traits.texy index 4837d5769c..9fa07a6483 100644 --- a/best-practices/it/presenter-traits.texy +++ b/best-practices/it/presenter-traits.texy @@ -1,14 +1,14 @@ -Comporre i presentatori dai tratti -********************************** +Composizione dei presenter da trait +*********************************** .[perex] -Se occorre implementare lo stesso codice in più presentatori (ad esempio, la verifica che l'utente sia loggato), si è tentati di collocare il codice in un antenato comune. La seconda opzione è quella di creare tratti a scopo singolo. +Se abbiamo bisogno di implementare lo stesso codice in più presenter (ad esempio, verificare che l'utente sia loggato), possiamo inserire il codice in un antenato comune. La seconda opzione è creare [trait |nette:introduction-to-object-oriented-programming#Trait] specifici per uno scopo. -Il vantaggio di questa soluzione è che ogni presentatore può utilizzare solo i tratti di cui ha effettivamente bisogno, mentre l'ereditarietà multipla non è possibile in PHP. +Il vantaggio di questa soluzione è che ciascuno dei presenter può utilizzare esattamente i trait di cui ha effettivamente bisogno, mentre l'ereditarietà multipla non è possibile in PHP. -Questi tratti possono sfruttare il fatto che tutti i [metodi di iniezione |inject-method-attribute#inject methods] sono chiamati in sequenza quando il presentatore viene creato. È sufficiente assicurarsi che il nome di ogni metodo di iniezione sia unico. +Questi trait possono sfruttare il fatto che, alla creazione del presenter, vengono chiamati progressivamente tutti i [metodi inject |inject-method-attribute#Metodi inject]. È solo necessario assicurarsi che il nome di ogni metodo inject sia unico. -I tratti possono appendere il codice di inizializzazione negli eventi [onStartup o onRender |application:presenters#Events]. +I trait possono agganciare il codice di inizializzazione agli eventi [onStartup o onRender |application:presenters#Eventi]. Esempi: @@ -36,7 +36,7 @@ trait StandardTemplateFilters } ``` -Il presentatore utilizza semplicemente questi tratti: +Il presenter quindi utilizza semplicemente questi trait: ```php class ArticlePresenter extends Nette\Application\UI\Presenter @@ -45,6 +45,3 @@ class ArticlePresenter extends Nette\Application\UI\Presenter use RequireLoggedUser; } ``` - - -{{sitename: Migliori pratiche}} diff --git a/best-practices/it/restore-request.texy b/best-practices/it/restore-request.texy index bb870fcc84..abb5ee93e8 100644 --- a/best-practices/it/restore-request.texy +++ b/best-practices/it/restore-request.texy @@ -1,17 +1,16 @@ -Come tornare a una pagina precedente? -************************************* +Come tornare alla pagina precedente? +************************************ .[perex] -Cosa succede se un utente compila un modulo e il suo login scade? Per evitare di perdere i dati, li salviamo nella sessione prima di reindirizzare alla pagina di login. In Nette, questo è un gioco da ragazzi. +Cosa succede se un utente sta compilando un modulo e la sua sessione scade? Per evitare la perdita di dati, salviamo i dati nella sessione prima di reindirizzare alla pagina di login. In Nette, questo è un gioco da ragazzi. -La richiesta corrente può essere memorizzata nella sessione utilizzando il metodo `storeRequest()`, che restituisce il suo identificatore come una breve stringa. Il metodo memorizza il nome del presentatore corrente, la vista e i suoi parametri. -Se è stato inviato anche un modulo, vengono salvati anche i valori dei campi (tranne quelli dei file caricati). +La richiesta corrente può essere salvata nella sessione tramite il metodo `storeRequest()`, che restituisce il suo identificatore sotto forma di una breve stringa. Il metodo salva il nome del presenter corrente, la vista e i suoi parametri. Nel caso in cui sia stato inviato anche un modulo, verranno salvati anche i contenuti dei campi (ad eccezione dei file caricati). -La richiesta viene ripristinata dal metodo `restoreRequest($key)`, al quale viene passato l'identificatore recuperato. Questo reindirizza al presentatore e alla vista originali. Tuttavia, se la richiesta salvata contiene l'invio di un modulo, viene inoltrata al presentatore originale con il metodo `forward()`, passando i valori precedentemente compilati al modulo e lasciando che venga ridisegnato. In questo modo, l'utente può inviare nuovamente il modulo senza perdere alcun dato. +Il ripristino della richiesta viene eseguito dal metodo `restoreRequest($key)`, al quale passiamo l'identificatore ottenuto. Questo reindirizza al presenter e alla vista originali. Tuttavia, se la richiesta salvata contiene l'invio di un modulo, passerà al presenter originale tramite il metodo `forward()`, passerà al modulo i valori precedentemente compilati e lo farà renderizzare nuovamente. L'utente ha così la possibilità di inviare nuovamente il modulo e nessun dato andrà perso. -È importante che `restoreRequest()` verifichi che l'utente appena entrato sia lo stesso che ha compilato il modulo in origine. In caso contrario, scarta la richiesta e non fa nulla. +È importante notare che `restoreRequest()` controlla se l'utente appena loggato è lo stesso che ha compilato originariamente il modulo. In caso contrario, scarta la richiesta e non fa nulla. -Dimostriamo tutto con un esempio. Abbiamo un presenter `AdminPresenter` in cui si stanno modificando dei dati e il cui metodo `startup()` controlla se l'utente è connesso. Se non lo è, lo reindirizziamo a `SignPresenter`. Allo stesso tempo, salviamo la richiesta corrente e inviamo la sua chiave a `SignPresenter`. +Mostriamo tutto con un esempio. Abbiamo un presenter `AdminPresenter`, in cui si modificano i dati e nel cui metodo `startup()` verifichiamo se l'utente è loggato. In caso contrario, lo reindirizziamo a `SignPresenter`. Allo stesso tempo, salviamo la richiesta corrente e inviamo la sua chiave a `SignPresenter`. ```php class AdminPresenter extends Nette\Application\UI\Presenter @@ -27,7 +26,7 @@ class AdminPresenter extends Nette\Application\UI\Presenter } ``` -Il presentatore `SignPresenter` conterrà un parametro persistente `$backlink` in cui viene scritta la chiave, oltre al modulo di login. Poiché il parametro è persistente, verrà mantenuto anche dopo l'invio del modulo di login. +Il presenter `SignPresenter` conterrà, oltre al modulo di login, anche un parametro persistente `$backlink`, in cui verrà scritta la chiave. Poiché il parametro è persistente, verrà trasmesso anche dopo l'invio del modulo di login. ```php @@ -41,14 +40,14 @@ class SignPresenter extends Nette\Application\UI\Presenter protected function createComponentSignInForm() { $form = new Nette\Application\UI\Form; - // ... aggiungere i campi del modulo ... + // ... aggiungiamo i campi del modulo ... $form->onSuccess[] = [$this, 'signInFormSubmitted']; return $form; } public function signInFormSubmitted($form) { - // ... qui registriamo l'utente... + // ... qui effettuiamo il login dell'utente ... $this->restoreRequest($this->backlink); $this->redirect('Admin:'); @@ -56,9 +55,8 @@ class SignPresenter extends Nette\Application\UI\Presenter } ``` -Si passa la chiave della richiesta salvata al metodo `restoreRequest()` e questo rinvia (o inoltra) al presentatore originale. +Al metodo `restoreRequest()` passiamo la chiave della richiesta salvata e questo reindirizza (o passa) al presenter originale. -Tuttavia, se la chiave non è valida (per esempio, non esiste più nella sessione), il metodo non fa nulla. Quindi la chiamata successiva è `$this->redirect('Admin:')`, che reindirizza a `AdminPresenter`. +Tuttavia, se la chiave non è valida (ad esempio, non esiste più nella sessione), il metodo non fa nulla. Segue quindi la chiamata `$this->redirect('Admin:')`, che reindirizza a `AdminPresenter`. {{priority: -1}} -{{sitename: Migliori pratiche}} diff --git a/best-practices/ja/@home.texy b/best-practices/ja/@home.texy new file mode 100644 index 0000000000..eab0e604fe --- /dev/null +++ b/best-practices/ja/@home.texy @@ -0,0 +1,69 @@ +ガイドとベストプラクティス +************* + +.[perex] +Netteのガイド、一般的なタスクの解決策、および*ベストプラクティス*。 + + +<div class=documentation> +<div> + + +Netteアプリケーション +------------- +- [inject メソッドと属性 |inject-method-attribute] +- [トレイトからの Presenter の構成 |presenter-traits] +- [Presenter への設定の受け渡し |passing-settings-to-presenters] +- [前のページに戻る方法 |restore-request] +- [データベース結果のページネーション |pagination] +- [動的スニペット |dynamic-snippets] +- [#Requires 属性の使用方法 |attribute-requires] +- [POST リンクの正しい使用方法 |post-links] + +</div> +<div> + + +フォーム +---- +- [フォームの再利用 |form-reuse] +- [レコード作成および編集用フォーム |creating-editing-form] +- [お問い合わせフォームの作成 |lets-create-contact-form] +- [依存セレクトボックス |https://blog.nette.org/en/dependent-selectboxes-elegantly-in-nette-and-pure-js] + +</div> +<div> + + +一般 +------ +- [設定ファイルの読み込み方法 |bootstrap:] +- [マイクロサイトの作成方法 |microsites] +- [なぜ Nette は定数に PascalCase 記法を使用するのですか? |https://blog.nette.org/en/for-less-screaming-in-the-code] +- [なぜ Nette は Interface 接尾辞を使用しないのですか? |https://blog.nette.org/en/prefixes-and-suffixes-do-not-belong-in-interface-names] +- [Composer: 使用のヒント |composer] +- [エディタとツールのヒント |editors-and-tools] +- [オブジェクト指向プログラミング入門 |nette:introduction-to-object-oriented-programming] + +</div> +<div> + + +ソリューション例 +-------- +- [Nette examples |https://github.com/nette-examples] +- [Doctrine & Nette |https://contributte.org/nettrine/] +- [Contributte examples |https://contributte.org/examples.html] +- [Doctrine ORM Website |https://github.com/MinecordNetwork/Website] +- [クイックスタート |quickstart:] + +</div> +<div> + + +ビデオ +--- +Poslední soboty の何百もの録画と Nette に関するビデオは、「Nette Framework Youtube チャンネル」:https://www.youtube.com/user/NetteFramework でまとめて見つけることができます。 + +</div> +</div> diff --git a/best-practices/ja/@meta.texy b/best-practices/ja/@meta.texy new file mode 100644 index 0000000000..faa40c5409 --- /dev/null +++ b/best-practices/ja/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: ガイドとベストプラクティス}} +{{leftbar: www:@menu-common}} diff --git a/best-practices/ja/attribute-requires.texy b/best-practices/ja/attribute-requires.texy new file mode 100644 index 0000000000..5e374df7a0 --- /dev/null +++ b/best-practices/ja/attribute-requires.texy @@ -0,0 +1,177 @@ +`#[Requires]` 属性の使用方法 +********************* + +.[perex] +Web アプリケーションを作成していると、アプリケーションの特定の部分へのアクセスを制限する必要性にしばしば直面します。一部のリクエストはフォーム(つまり POST メソッド)を使用してのみデータを送信できるようにしたり、AJAX コールのみにアクセスできるようにしたりしたい場合があります。Nette Framework 3.2 では、このような制限を非常にエレガントかつ明確に設定できる新しいツールが登場しました。それが `#[Requires]` 属性です。 + +属性は、クラスまたはメソッドの定義の前に追加する PHP の特別なマークです。これは実際にはクラスであるため、以下の例が機能するためには、use 句を含める必要があります。 + +```php +use Nette\Application\Attributes\Requires; +``` + +`#[Requires]` 属性は、Presenter クラス自体、および以下のメソッドで使用できます。 + +- `action<Action>()` +- `render<View>()` +- `handle<Signal>()` +- `createComponent<Name>()` + +最後の 2 つのメソッドはコンポーネントにも関連するため、属性はコンポーネントでも使用できます。 + +属性が指定する条件が満たされない場合、HTTP エラー 4xx がスローされます。 + + +HTTP メソッド +--------- + +アクセスが許可される HTTP メソッド(GET、POST など)を指定できます。たとえば、フォームの送信によるアクセスのみを許可したい場合は、次のように設定します。 + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +状態を変更するアクションになぜ GET ではなく POST を使用すべきか、そしてその方法については? [ガイドを読む |post-links]。 + +メソッドまたはメソッドの配列を指定できます。特別なケースは値 `'*'` で、これはすべてのメソッドを許可します。これは、Presenter が通常 [セキュリティ上の理由で許可されていません |application:presenters#HTTPメソッドのチェック]。 + + +AJAX コール +-------- + +Presenter またはメソッドを AJAX リクエストに対してのみ利用可能にしたい場合は、次を使用します。 + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +同一オリジン +------ + +セキュリティを強化するために、リクエストが同一ドメインから行われることを要求できます。これにより、[CSRF 脆弱性 |nette:vulnerability-protection#Cross-Site Request Forgery CSRF] を防ぐことができます。 + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +`handle<Signal>()` メソッドでは、同一ドメインからのアクセスが自動的に要求されます。したがって、逆に任意のドメインからのアクセスを許可したい場合は、次のように指定します。 + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +フォワード経由のアクセス +------------ + +Presenter へのアクセスを間接的にのみ、たとえば他の Presenter から `forward()` または `switch()` メソッドを使用してのみ利用可能にするように制限すると便利な場合があります。このようにして、たとえばエラー Presenter が URL から呼び出されるのを防ぎます。 + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +実際には、Presenter 内のロジックに基づいて初めてアクセスできる特定のビューを指定する必要があることがよくあります。つまり、再び、直接開くことができないようにするためです。 + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = $this->facade->getProduct($id); + if (!$product) { + $this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +特定のアクション +-------- + +特定のコード、たとえばコンポーネントの作成などを、Presenter 内の特定のアクションに対してのみ利用可能にするように制限することもできます。 + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +単一のアクションの場合、配列を記述する必要はありません:`#[Requires(actions: 'default')]` + + +カスタム属性 +------ + +`#[Requires]` 属性を同じ設定で繰り返し使用したい場合は、`#[Requires]` を継承し、必要に応じて設定する独自の属性を作成できます。 + +たとえば、`#[SingleAction]` は `default` アクション経由でのみアクセスを許可します。 + +```php +#[\Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +または、`#[RestMethods]` は REST API に使用されるすべての HTTP メソッド経由でのアクセスを許可します。 + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +まとめ +--- + +`#[Requires]` 属性は、Web サイトへのアクセス方法について大きな柔軟性とコントロールを提供します。シンプルでありながら強力なルールを使用して、アプリケーションのセキュリティと適切な動作を向上させることができます。ご覧のとおり、Nette で属性を使用すると、作業が容易になるだけでなく、安全にもなります。 diff --git a/best-practices/ja/composer.texy b/best-practices/ja/composer.texy new file mode 100644 index 0000000000..afd0df7f16 --- /dev/null +++ b/best-practices/ja/composer.texy @@ -0,0 +1,282 @@ +Composer: 使用のヒント +**************** + +<div class=perex> + +ComposerはPHPの依存関係管理ツールです。プロジェクトが依存するライブラリをリストアップし、それらをインストールおよび更新してくれます。以下を示します: + +- Composerのインストール方法 +- 新規または既存のプロジェクトでの使用方法 + +</div> + + +インストール +====== + +Composerは実行可能な `.phar` ファイルで、次の方法でダウンロードしてインストールします: + + +Windows +------- + +公式インストーラ [Composer-Setup.exe |https://getcomposer.org/Composer-Setup.exe] を使用してください。 + + +Linux, macOS +------------ + +[このページ |https://getcomposer.org/download/] からコピーできる4つのコマンドを実行するだけです。 + +さらに、システムの `PATH` にあるフォルダに配置することで、Composerはグローバルにアクセス可能になります: + +```shell +$ mv ./composer.phar ~/bin/composer # または /usr/local/bin/composer +``` + + +プロジェクトでの使用 +========== + +プロジェクトでComposerの使用を開始するには、`composer.json` ファイルのみが必要です。これはプロジェクトの依存関係を記述し、他のメタデータも含むことができます。基本的な `composer.json` は次のようになります: + +```js +{ + "require": { + "nette/database": "^3.0" + } +} +``` + +ここでは、アプリケーション(またはライブラリ)が `nette/database` パッケージ(パッケージ名は組織名とプロジェクト名で構成されます)を必要とし、条件 `^3.0` に一致するバージョン(つまり、最新のバージョン3)を要求していることを示しています。 + +プロジェクトのルートに `composer.json` ファイルがあるので、インストールを実行します: + +```shell +composer update +``` + +ComposerはNette Databaseを `vendor/` フォルダにダウンロードします。さらに、どのバージョンのライブラリを正確にインストールしたかに関する情報を含む `composer.lock` ファイルを作成します。 + +Composerは `vendor/autoload.php` ファイルを生成します。これを単純にインクルードするだけで、追加の作業なしにライブラリの使用を開始できます: + +```php +require __DIR__ . '/vendor/autoload.php'; + +$db = new Nette\Database\Connection('sqlite::memory:'); +``` + + +パッケージを最新バージョンに更新する +================== + +`composer.json` で定義された条件に従って使用されているライブラリを最新バージョンに更新するには、`composer update` コマンドを使用します。たとえば、依存関係 `"nette/database": "^3.0"` の場合、最新のバージョン3.x.xをインストールしますが、バージョン4はインストールしません。 + +`composer.json` ファイル内の条件を、たとえば `"nette/database": "^4.1"` に更新して最新バージョンをインストールできるようにするには、`composer require nette/database` コマンドを使用します。 + +使用されているすべてのNetteパッケージを更新するには、コマンドラインですべてをリストする必要があります。例: + +```shell +composer require nette/application nette/forms latte/latte tracy/tracy ... +``` + +これは非実用的です。代わりに、簡単なスクリプト "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff を使用してください。これはあなたのためにそれを行います: + +```shell +php composer-frontline.php +``` + + +新しいプロジェクトの作成 +============ + +Netteで新しいプロジェクトを作成するには、単一のコマンドを使用します: + +```shell +composer create-project nette/web-project project-name +``` + +`project-name` として、プロジェクトのディレクトリ名を入力して確認します。ComposerはGitHubから `nette/web-project` リポジトリをダウンロードします。これにはすでに `composer.json` ファイルが含まれており、その後すぐにNette Frameworkをダウンロードします。あとは `temp/` および `log/` フォルダへの書き込み[権限を設定する |nette:troubleshooting#ディレクトリ権限の設定] だけで、プロジェクトは稼働するはずです。 + +プロジェクトがホストされるPHPのバージョンがわかっている場合は、[それを設定する |#PHPバージョン] ことを忘れないでください。 + + +PHPバージョン +======== + +Composerは常に、現在使用しているPHPのバージョン(より正確には、Composerを実行するときにコマンドラインで使用されるPHPのバージョン)と互換性のあるパッケージのバージョンをインストールします。しかし、これはおそらくホスティングで使用しているバージョンと同じではありません。したがって、ホスティング上のPHPのバージョンに関する情報を `composer.json` ファイルに追加することが非常に重要です。その後、ホスティングと互換性のあるパッケージのバージョンのみがインストールされます。 + +プロジェクトがたとえばPHP 8.2.3で実行されることを設定するには、次のコマンドを使用します: + +```shell +composer config platform.php 8.2.3 +``` + +バージョンは `composer.json` ファイルに次のように書き込まれます: + +```js +{ + "config": { + "platform": { + "php": "8.2.3" + } + } +} +``` + +ただし、PHPのバージョン番号はファイルの別の場所、つまり `require` セクションにも記載されています。最初の番号はどのバージョン用にパッケージがインストールされるかを決定し、2番目の番号はアプリケーション自体がどのバージョン用に書かれているかを示します。そして、たとえばPhpStormはそれに基づいて *PHP language level* を設定します。(もちろん、これらのバージョンが異なることは意味がないため、二重の記述は不注意です。)このバージョンは次のコマンドで設定します: + +```shell +composer require php 8.2.3 --no-update +``` + +または `composer.json` ファイルで直接: + +```js +{ + "require": { + "php": "8.2.3" + } +} +``` + + +PHPバージョンの無視 +=========== + +パッケージは通常、互換性のある最低PHPバージョンと、テストされた最高バージョンの両方を指定しています。さらに新しいPHPバージョンを使用する予定がある場合、たとえばテスト目的で、Composerはそのようなパッケージのインストールを拒否します。解決策は `--ignore-platform-req=php+` オプションです。これにより、Composerは要求されたPHPバージョンの上限を無視します。 + + +誤った報告 +===== + +パッケージのアップグレードやバージョン番号の変更時に、競合が発生することがあります。あるパッケージには、別のパッケージと矛盾する要件があるなどです。しかし、Composerは時々誤った報告を表示することがあります。実際には存在しない競合を報告します。そのような場合は、`composer.lock` ファイルを削除して再試行すると役立ちます。 + +エラーメッセージが続く場合は、真剣に受け止められ、何とどのように修正する必要があるかを読み取る必要があります。 + + +Packagist.org - 中央リポジトリ +======================= + +[Packagist |https://packagist.org] は、Composerが他に指示されない限りパッケージを検索しようとするメインリポジトリです。独自のパッケージをここで公開することもできます。 + + +中央リポジトリを使用したくない場合は? +------------------- + +社内アプリケーションがあり、単に公開でホストできない場合は、それらのために企業リポジトリを作成します。 + +リポジトリに関する詳細は、[公式ドキュメント |https://getcomposer.org/doc/05-repositories.md#repositories] で確認できます。 + + +オートローディング +========= + +Composerの重要な機能は、インストールされたすべてのクラスに対してオートローディングを提供することです。これは `vendor/autoload.php` ファイルをインクルードすることで開始します。 + +ただし、`vendor` フォルダ外の他のクラスをロードするためにComposerを使用することも可能です。最初のオプションは、Composerに定義されたフォルダとサブフォルダを検索させ、すべてのクラスを見つけてオートローダーに含めることです。これは、`composer.json` で `autoload > classmap` を設定することで実現できます: + +```js +{ + "autoload": { + "classmap": [ + "src/", # src/ フォルダとそのサブフォルダを含める + ] + } +} +``` + +その後、変更があるたびに `composer dumpautoload` コマンドを実行し、オートロードテーブルを再生成する必要があります。これは非常に不便であり、このタスクを[RobotLoader|robot-loader:]に委ねる方がはるかに優れています。RobotLoaderは同じ操作をバックグラウンドで自動的に、はるかに高速に実行します。 + +2番目のオプションは、[PSR-4|https://www.php-fig.org/psr/psr-4/] に従うことです。簡単に言えば、これは名前空間とクラス名がディレクトリ構造とファイル名に対応するシステムです。たとえば、`App\Core\RouterFactory` は `/path/to/App/Core/RouterFactory.php` ファイルにあります。設定例: + +```js +{ + "autoload": { + "psr-4": { + "App\\": "app/" # App\ 名前空間は app/ ディレクトリにあります + } + } +} +``` + +動作を正確に設定する方法については、[Composerドキュメント|https://getcomposer.org/doc/04-schema.md#psr-4] を参照してください。 + + +新しいバージョンのテスト +============ + +パッケージの新しい開発バージョンをテストしたいですか?どうすればよいでしょうか?まず、`composer.json` ファイルに次の2つのオプションを追加します。これにより、開発バージョンのパッケージをインストールできますが、要件を満たす安定バージョンの組み合わせが存在しない場合にのみ使用されます: + +```js +{ + "minimum-stability": "dev", + "prefer-stable": true, +} +``` + +さらに、`composer.lock` ファイルを削除することをお勧めします。Composerが理解できない理由でインストールを拒否することがあり、これが問題を解決します。 + +パッケージが `nette/utils` で、新しいバージョンが4.0だとしましょう。次のコマンドでインストールします: + +```shell +composer require nette/utils:4.0.x-dev +``` + +または、特定のバージョン、たとえば4.0.0-RC2をインストールできます: + +```shell +composer require nette/utils:4.0.0-RC2 +``` + +しかし、ライブラリが古いバージョン(例:`^3.1`)にロックされている別のパッケージに依存している場合、理想的にはパッケージを更新して新しいバージョンで動作するようにすることです。ただし、制限を回避してComposerに開発バージョンをインストールさせ、それが古いバージョン(例:3.1.6)であるかのように見せかけたい場合は、キーワード `as` を使用できます: + +```shell +composer require nette/utils "4.0.x-dev as 3.1.6" +``` + + +コマンドの呼び出し +========= + +Composerを介して、Composerのネイティブコマンドであるかのように、独自の事前に準備されたコマンドやスクリプトを呼び出すことができます。`vendor/bin` フォルダにあるスクリプトの場合、このフォルダを指定する必要はありません。 + +例として、`composer.json` ファイルに、[Nette Tester|tester:] を使用してテストを実行するスクリプトを定義します: + +```js +{ + "scripts": { + "tester": "tester tests -s" + } +} +``` + +次に、`composer tester` を使用してテストを実行します。プロジェクトのルートフォルダにいなくても、サブディレクトリのいずれかにいる場合でもコマンドを呼び出すことができます。 + + +感謝を送る +===== + +オープンソースの作者を喜ばせるトリックをお見せします。簡単な方法で、プロジェクトが使用しているライブラリにGitHubでスターを付けることができます。`symfony/thanks` ライブラリをインストールするだけです: + +```shell +composer global require symfony/thanks +``` + +そして実行します: + +```shell +composer thanks +``` + +試してみてください! + + +設定 +===== + +Composerはバージョン管理ツール [Git |https://git-scm.com] と密接に連携しています。インストールされていない場合は、Composerに使用しないように指示する必要があります: + +```shell +composer -g config preferred-install dist +``` diff --git a/best-practices/ja/creating-editing-form.texy b/best-practices/ja/creating-editing-form.texy new file mode 100644 index 0000000000..806cea4a84 --- /dev/null +++ b/best-practices/ja/creating-editing-form.texy @@ -0,0 +1,205 @@ +レコードの作成と編集のためのフォーム +****************** + +.[perex] +Netteでレコードの追加と編集を正しく実装する方法は?両方に同じフォームを使用します。 + +多くの場合、レコードの追加と編集のためのフォームは同じであり、ボタンのラベルなどが異なるだけです。まずレコードを追加するためにフォームを使用し、次に編集のために使用し、最後に両方の解決策を組み合わせる簡単なPresenterの例を示します。 + + +レコードの追加 +------- + +レコードを追加するためのPresenterの例です。データベース自体の操作は`Facade`クラスに任せます。そのコードはこの例では重要ではありません。 + + +```php +use Nette\Application\UI\Form; + +class RecordPresenter extends Nette\Application\UI\Presenter +{ + public function __construct( + private Facade $facade, + ) { + } + + protected function createComponentRecordForm(): Form + { + $form = new Form; + + // ... フォームコントロールを追加 ... + + $form->onSuccess[] = [$this, 'recordFormSucceeded']; + return $form; + } + + public function recordFormSucceeded(Form $form, array $data): void + { + $this->facade->add($data); // データベースへのレコード追加 + $this->flashMessage('正常に追加されました'); + $this->redirect('...'); + } + + public function renderAdd(): void + { + // ... + } +} +``` + + +レコードの編集 +------- + +次に、レコードを編集するためのPresenterがどのようになるかを示します。 + + +```php +use Nette\Application\UI\Form; + +class RecordPresenter extends Nette\Application\UI\Presenter +{ + private $record; + + public function __construct( + private Facade $facade, + ) { + } + + public function actionEdit(int $id): void + { + $record = $this->facade->get($id); + if ( + !$record // レコードの存在確認 + || !$this->facade->isEditAllowed(/*...*/) // 権限チェック + ) { + $this->error(); // エラー 404 + } + + $this->record = $record; + } + + protected function createComponentRecordForm(): Form + { + // アクションが'edit'であることを確認 + if ($this->getAction() !== 'edit') { + $this->error(); + } + + $form = new Form; + + // ... フォームコントロールを追加 ... + + $form->setDefaults($this->record); // デフォルト値の設定 + $form->onSuccess[] = [$this, 'recordFormSucceeded']; + return $form; + } + + public function recordFormSucceeded(Form $form, array $data): void + { + $this->facade->update($this->record->id, $data); // レコードの更新 + $this->flashMessage('正常に更新されました'); + $this->redirect('...'); + } +} +``` + +[presenterのライフサイクル |application:presenters#Presenterのライフサイクル] の最初に実行される *action* メソッドで、レコードの存在とユーザーがそれを編集する権限を確認します。 + +レコードをプロパティ `$record` に保存して、デフォルト値を設定するために `createComponentRecordForm()` メソッドで、そしてIDのために `recordFormSucceeded()` で利用できるようにします。代替の解決策は、デフォルト値を直接 `actionEdit()` で設定し、URLの一部であるIDの値を `getParameter('id')` を使用して取得することです。 + + +```php + public function actionEdit(int $id): void + { + $record = $this->facade->get($id); + if ( + // 存在確認と権限チェック + ) { + $this->error(); + } + + // フォームのデフォルト値設定 + $this->getComponent('recordForm') + ->setDefaults($record); + } + + public function recordFormSucceeded(Form $form, array $data): void + { + $id = (int) $this->getParameter('id'); + $this->facade->update($id, $data); + // ... + } +} +``` + +しかし、そしてこれが **コード全体の最も重要なポイント** であるべきですが、フォームを作成する際には、アクションが実際に `edit` であることを確認する必要があります。そうでなければ、`actionEdit()` メソッドでの検証はまったく行われません! + + +追加と編集のための同じフォーム +--------------- + +そして今、両方のPresenterを1つに結合します。`createComponentRecordForm()` メソッドでどのアクションかを区別し、それに応じてフォームを設定することもできますし、それを直接actionメソッドに任せて条件をなくすこともできます。 + + +```php +class RecordPresenter extends Nette\Application\UI\Presenter +{ + public function __construct( + private Facade $facade, + ) { + } + + public function actionAdd(): void + { + $form = $this->getComponent('recordForm'); + $form->onSuccess[] = [$this, 'addingFormSucceeded']; + } + + public function actionEdit(int $id): void + { + $record = $this->facade->get($id); + if ( + !$record // レコードの存在確認 + || !$this->facade->isEditAllowed(/*...*/) // 権限チェック + ) { + $this->error(); // エラー 404 + } + + $form = $this->getComponent('recordForm'); + $form->setDefaults($record); // デフォルト値の設定 + $form->onSuccess[] = [$this, 'editingFormSucceeded']; + } + + protected function createComponentRecordForm(): Form + { + // アクションが'add'または'edit'であることを確認 + if (!in_array($this->getAction(), ['add', 'edit'])) { + $this->error(); + } + + $form = new Form; + + // ... フォームコントロールを追加 ... + + return $form; + } + + public function addingFormSucceeded(Form $form, array $data): void + { + $this->facade->add($data); // データベースへのレコード追加 + $this->flashMessage('正常に追加されました'); + $this->redirect('...'); + } + + public function editingFormSucceeded(Form $form, array $data): void + { + $id = (int) $this->getParameter('id'); + $this->facade->update($id, $data); // レコードの更新 + $this->flashMessage('正常に更新されました'); + $this->redirect('...'); + } +} +``` + +{{priority: -1}} diff --git a/best-practices/ja/dynamic-snippets.texy b/best-practices/ja/dynamic-snippets.texy new file mode 100644 index 0000000000..eae6f47e34 --- /dev/null +++ b/best-practices/ja/dynamic-snippets.texy @@ -0,0 +1,173 @@ +動的スニペット +******* + +アプリケーション開発において、テーブルの個々の行やリストの項目などに対してAJAX操作を実行する必要性がしばしば生じます。例として、記事のリストを表示し、ログインしたユーザーが各記事に対して「いいね/いいねしない」の評価を選択できるようにします。AJAXなしのPresenterと対応するテンプレートのコードは、おおよそ次のようになります(最も重要な部分を示します。コードは評価を記録し、記事のコレクションを取得するためのサービスの存在を前提としています - 具体的な実装はこのチュートリアルの目的には重要ではありません): + +```php +public function handleLike(int $articleId): void +{ + $this->ratingService->saveLike($articleId, $this->user->id); + $this->redirect('this'); +} + +public function handleUnlike(int $articleId): void +{ + $this->ratingService->removeLike($articleId, $this->user->id); + $this->redirect('this'); +} +``` + +テンプレート: + +```latte +<article n:foreach="$articles as $article"> + <h2>{$article->title}</h2> + <div class="content">{$article->content}</div> + {if !$article->liked} + <a n:href="like! $article->id" class=ajax>いいね</a> + {else} + <a n:href="unlike! $article->id" class=ajax>いいねを取り消す</a> + {/if} +</article> +``` + + +Ajax化 +===== + +では、この簡単なアプリケーションにAJAXを追加しましょう。記事の評価の変更はリダイレクトが必要なほど重要ではないため、理想的にはバックグラウンドでAJAXで行われるべきです。[アドオンのハンドリングスクリプト |application:ajax#Naja] を使用し、AJAXリンクにはCSSクラス `ajax` を付けるという通常の慣習に従います。 + +しかし、具体的にはどのようにすればよいでしょうか?Netteは2つの方法を提供します:いわゆる動的スニペットの方法とコンポーネントの方法です。どちらにも長所と短所があるため、それぞれを順番に見ていきます。 + + +動的スニペットの方法 +========== + +動的スニペットとは、Latteの用語では、スニペット名に変数を使用する `{snippet}` タグの特定のユースケースを意味します。このようなスニペットはテンプレートのどこにでも配置できるわけではありません - 静的スニペット、つまり通常の、または `{snippetArea}` 内で囲まれている必要があります。私たちのテンプレートを次のように変更できます。 + + +```latte +{snippet articlesContainer} + <article n:foreach="$articles as $article"> + <h2>{$article->title}</h2> + <div class="content">{$article->content}</div> + {snippet article-{$article->id}} + {if !$article->liked} + <a n:href="like! $article->id" class=ajax>いいね</a> + {else} + <a n:href="unlike! $article->id" class=ajax>いいねを取り消す</a> + {/if} + {/snippet} + </article> +{/snippet} +``` + +各記事は、記事IDを名前に含むスニペットを定義します。これらすべてのスニペットは、`articlesContainer` という名前の1つのスニペットでまとめてラップされます。このラッピングスニペットを省略すると、Latteは例外で警告します。 + +残っているのは、Presenterに再描画を追加することです - 静的なラッパーを再描画するだけで十分です。 + +```php +public function handleLike(int $articleId): void +{ + $this->ratingService->saveLike($articleId, $this->user->id); + if ($this->isAjax()) { + $this->redrawControl('articlesContainer'); + // $this->redrawControl('article-' . $articleId); -- 不要 + } else { + $this->redirect('this'); + } +} +``` + +同様に、姉妹メソッド `handleUnlike()` も変更すれば、AJAXは機能します! + +しかし、この解決策には1つの欠点があります。AJAXリクエストがどのように進行するかをさらに調査すると、アプリケーションは表面的には効率的に見える(特定の記事に対して1つのスニペットのみを返す)ものの、実際にはサーバー上で全てのスニペットを描画していることがわかります。目的のスニペットをペイロードに配置し、他のスニペットは破棄しました(したがって、それらもデータベースから不必要に取得しました)。 + +このプロセスを最適化するには、テンプレートに `$articles` コレクションを渡す場所(例えば `renderDefault()` メソッド内)に介入する必要があります。シグナルの処理が `render<Something>` メソッドの前に行われるという事実を利用します。 + +```php +public function handleLike(int $articleId): void +{ + // ... + if ($this->isAjax()) { + // ... + $this->template->articles = [ + $this->db->table('articles')->get($articleId), + ]; + } else { + // ... +} + +public function renderDefault(): void +{ + if (!isset($this->template->articles)) { + $this->template->articles = $this->db->table('articles'); + } +} +``` + +これで、シグナルの処理中に、すべての記事を含むコレクションの代わりに、描画してペイロードでブラウザに送信したい1つの記事のみを含む配列がテンプレートに渡されます。したがって、`{foreach}` は一度だけ実行され、余分なスニペットは描画されません。 + + +コンポーネントの方法 +========== + +全く異なる解決方法は、動的スニペットを回避します。トリックは、ロジック全体を特別なコンポーネントに移すことです - これからは、評価の入力はPresenterではなく、専用の `LikeControl` が担当します。クラスは次のようになります(それに加えて、`render`、`handleUnlike` などのメソッドも含まれます): + +```php +class LikeControl extends Nette\Application\UI\Control +{ + public function __construct( + private Article $article, + ) { + } + + public function handleLike(): void + { + $this->ratingService->saveLike($this->article->id, $this->presenter->user->id); + if ($this->presenter->isAjax()) { + $this->redrawControl(); + } else { + $this->presenter->redirect('this'); + } + } +} +``` + +コンポーネントのテンプレート: + +```latte +{snippet} + {if !$article->liked} + <a n:href="like!" class=ajax>いいね</a> + {else} + <a n:href="unlike!" class=ajax>いいねを取り消す</a> + {/if} +{/snippet} +``` + +もちろん、ビューテンプレートが変更され、Presenterにファクトリを追加する必要があります。データベースから取得する記事の数だけコンポーネントを作成するため、その「増殖」には [Multiplier |application:Multiplier] クラスを使用します。 + +```php +protected function createComponentLikeControl() +{ + $articles = $this->db->table('articles'); + return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { + return new LikeControl($articles[$articleId]); + }); +} +``` + +ビューテンプレートは必要最小限に縮小されます(そして完全にスニペットなし!): + +```latte +<article n:foreach="$articles as $article"> + <h2>{$article->title}</h2> + <div class="content">{$article->content}</div> + {control "likeControl-$article->id"} +</article> +``` + +ほぼ完了です:アプリケーションはこれでAJAXで動作します。ここでもアプリケーションを最適化する必要があります。なぜなら、Nette Databaseを使用しているため、シグナルの処理中にデータベースから1つではなく、すべての記事が不必要にロードされるからです。しかし、利点は、実際に私たちのコンポーネントだけがレンダリングされるため、それらの描画が行われないことです。 + +{{priority: -1}} diff --git a/best-practices/ja/editors-and-tools.texy b/best-practices/ja/editors-and-tools.texy new file mode 100644 index 0000000000..36e72f6ea2 --- /dev/null +++ b/best-practices/ja/editors-and-tools.texy @@ -0,0 +1,84 @@ +エディタとツール +******** + +.[perex] +あなたは熟練したプログラマかもしれませんが、優れたツールがあってこそマスターになれます。この章では、重要なツール、エディタ、プラグインのヒントを紹介します。 + + +IDEエディタ +======= + +開発には、PhpStorm、NetBeans、VS Codeなどの本格的なIDEを使用することを強くお勧めします。単なるPHPサポート付きのテキストエディタだけではありません。違いは本当に決定的です。構文を色付けできるだけの単なるエディタで満足する理由はありません。それは、正確なヒントを提供し、エラーを監視し、コードをリファクタリングし、その他多くのことができるトップクラスのIDEの能力には及びません。一部のIDEは有料ですが、無料のものもあります。 + +**NetBeans IDE** は、Nette、Latte、NEONのサポートを組み込みで持っています。 + +**PhpStorm**: `Settings > Plugins > Marketplace` でこれらのプラグインをインストールしてください +- Nette framework helpers +- Latte +- NEON support +- Nette Tester + +**VS Code**: マーケットプレイスで "Nette Latte + Neon" プラグインを見つけてください。 + +また、Tracyをエディタと連携させてください。エラーページが表示されたときに、ファイル名をクリックすると、エディタで該当する行にカーソルがある状態で開くことができます。[システムの設定方法|tracy:open-files-in-ide] を読んでください。 + + +PHPStan +======= + +PHPStanは、コードを実行する前に論理エラーを検出するツールです。 + +Composerを使用してインストールします: + +```shell +composer require --dev phpstan/phpstan-nette +``` + +プロジェクトに設定ファイル `phpstan.neon` を作成します: + +```neon +includes: + - vendor/phpstan/phpstan-nette/extension.neon + +parameters: + scanDirectories: + - app + + level: 5 +``` + +そして、`app/` フォルダ内のクラスを分析させます: + +```shell +vendor/bin/phpstan analyse app +``` + +包括的なドキュメントは、[PHPStanのサイト |https://phpstan.org] で直接見つけることができます。 + + +Code Checker +============ + +[Code Checker|code-checker:] は、ソースコード内の一部の形式的なエラーをチェックし、場合によっては修正します: + +- [BOM |nette:glossary#BOM] を削除します +- [Latte |latte:] テンプレートの有効性をチェックします +- `.neon`、`.php`、`.json` ファイルの有効性をチェックします +- [制御文字 |nette:glossary#制御文字] の出現をチェックします +- ファイルがUTF-8でエンコードされているかチェックします +- 誤って書かれた `/* @anotace */` (アスタリスクが欠けている)をチェックします +- PHPファイルの終了タグ `?>` を削除します +- ファイルの末尾にある右側のスペースや不要な行を削除します +- `-l` オプションを指定した場合、行区切り文字をシステムのものに正規化します + + +Composer +======== + +[Composer |best-practices:composer] はPHPの依存関係管理ツールです。これにより、個々のライブラリの任意の複雑な依存関係を宣言し、それらをプロジェクトにインストールすることができます。 + + +Requirements Checker +==================== + +これは、サーバーの実行環境をテストし、フレームワークを使用できるかどうか(およびどの程度まで)を通知するツールでした。現在、Netteは最小限必要なPHPバージョンを持つすべてのサーバーで使用できます。 diff --git a/best-practices/ja/form-reuse.texy b/best-practices/ja/form-reuse.texy new file mode 100644 index 0000000000..ed1fa5507b --- /dev/null +++ b/best-practices/ja/form-reuse.texy @@ -0,0 +1,348 @@ +複数の場所でのフォームの再利用 +*************** + +.[perex] +Netteでは、コードを複製することなく、同じフォームを複数の場所で使用するためのいくつかのオプションがあります。この記事では、避けるべきものも含め、さまざまな解決策を紹介します。 + + +フォームファクトリ +========= + +同じコンポーネントを複数の場所で使用するための基本的なアプローチの1つは、このコンポーネントを生成するメソッドまたはクラスを作成し、その後、アプリケーションのさまざまな場所でこのメソッドを呼び出すことです。このようなメソッドまたはクラスは *ファクトリ* と呼ばれます。ファクトリの特定の利用方法を説明するデザインパターン *factory method* と混同しないでください。これはこのトピックとは関係ありません。 + +例として、編集フォームを組み立てるファクトリを作成します。 + +```php +use Nette\Application\UI\Form; + +class FormFactory +{ + public function createEditForm(): Form + { + $form = new Form; + $form->addText('title', 'タイトル:'); + // ここに他のフォームフィールドを追加します + $form->addSubmit('send', '送信'); + return $form; + } +} +``` + +これで、アプリケーションのさまざまな場所、たとえばPresenterやコンポーネントで、このファクトリを使用できます。それは、[依存関係として要求する|dependency-injection:passing-dependencies] ことによって行います。まず、クラスを設定ファイルに記述します。 + +```neon +services: + - FormFactory +``` + +そして、Presenterで使用します。 + + +```php +class MyPresenter extends Nette\Application\UI\Presenter +{ + public function __construct( + private FormFactory $formFactory, + ) { + } + + protected function createComponentEditForm(): Form + { + $form = $this->formFactory->createEditForm(); + $form->onSuccess[] = function () { + // 送信されたデータの処理 + }; + return $form; + } +} +``` + +フォームファクトリを、アプリケーションのニーズに応じて他の種類のフォームを作成するための追加メソッドで拡張できます。そしてもちろん、要素のない基本フォームを作成するメソッドを追加し、他のメソッドがそれを利用することもできます。 + +```php +class FormFactory +{ + public function createForm(): Form + { + $form = new Form; + return $form; + } + + public function createEditForm(): Form + { + $form = $this->createForm(); + $form->addText('title', 'タイトル:'); + // ここに他のフォームフィールドを追加します + $form->addSubmit('send', '送信'); + return $form; + } +} +``` + +`createForm()` メソッドはまだ何も有用なことをしていませんが、それはすぐに変わります。 + + +ファクトリの依存関係 +========== + +やがて、フォームが多言語対応である必要があることがわかります。これは、すべてのフォームにいわゆる [translator |forms:rendering#翻訳] を設定する必要があることを意味します。この目的のために、`FormFactory` クラスを変更して、コンストラクタで `Translator` オブジェクトを依存関係として受け入れ、それをフォームに渡します。 + +```php +use Nette\Localization\Translator; + +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function createForm(): Form + { + $form = new Form; + $form->setTranslator($this->translator); + return $form; + } + + // ... +} +``` + +`createForm()` メソッドは特定のフォームを作成する他のメソッドからも呼び出されるため、translatorを設定するのはそのメソッドだけで十分です。そして完了です。Presenterやコンポーネントのコードを変更する必要はありません。これは素晴らしいことです。 + + +複数のファクトリクラス +=========== + +あるいは、アプリケーションで使用したい各フォームに対して複数のクラスを作成することもできます。 このアプローチは、コードの可読性を向上させ、フォームの管理を容易にすることができます。元の `FormFactory` は、基本的な設定(たとえば翻訳サポート付き)を持つクリーンなフォームのみを作成するようにし、編集フォーム用に新しいファクトリ `EditFormFactory` を作成します。 + +```php +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function create(): Form + { + $form = new Form; + $form->setTranslator($this->translator); + return $form; + } +} + + +// ✅ コンポジションの使用 +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + // ここに他のフォームフィールドを追加します + $form->addSubmit('send', '送信'); + return $form; + } +} +``` + +`FormFactory` と `EditFormFactory` クラス間の関連付けが、[オブジェクト継承 |nette:introduction-to-object-oriented-programming#コンポジション] ではなく [コンポジション |nette:introduction-to-object-oriented-programming#継承] によって実現されることが非常に重要です。 + +```php +// ⛔ これはダメ!継承はここには属しません +class EditFormFactory extends FormFactory +{ + public function create(): Form + { + $form = parent::create(); + $form->addText('title', 'タイトル:'); + // ここに他のフォームフィールドを追加します + $form->addSubmit('send', '送信'); + return $form; + } +} +``` + +この場合に継承を使用することは、完全に逆効果になります。問題は非常に早く発生します。たとえば、`create()` メソッドにパラメータを追加したいと思ったとき、PHPはそのシグネチャが親のものと異なるとエラーを報告します。 または、コンストラクタを介して `EditFormFactory` クラスに依存関係を渡す場合。 [コンストラクタ地獄 |dependency-injection:passing-dependencies#コンストラクタ地獄] と呼ばれる状況が発生します。 + +一般的に、[継承よりもコンポジションを |dependency-injection:faq#なぜ継承よりもコンポジションが優先されるのですか] 優先する方が良いです。 + + +フォームハンドラ +======== + +正常に送信された後に呼び出されるフォームハンドラも、ファクトリクラスの一部にすることができます。送信されたデータを処理のためにモデルに渡すように機能します。潜在的なエラーは、フォームに [返します |forms:validation#処理中のエラー] 。次の例のモデルは、`Facade` クラスによって表されます。 + +```php +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + private Facade $facade, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + $form->addText('title', 'タイトル:'); + // ここに他のフォームフィールドを追加します + $form->addSubmit('send', '送信'); + $form->onSuccess[] = [$this, 'processForm']; + return $form; + } + + public function processForm(Form $form, array $data): void + { + try { + // 送信されたデータの処理 + $this->facade->process($data); + + } catch (AnyModelException $e) { + $form->addError('...'); + } + } +} +``` + +ただし、リダイレクト自体はPresenterに任せます。Presenterは `onSuccess` イベントにリダイレクトを実行する別のハンドラを追加します。これにより、フォームを異なるPresenterで使用し、それぞれで異なる場所にリダイレクトすることが可能になります。 + +```php +class MyPresenter extends Nette\Application\UI\Presenter +{ + public function __construct( + private EditFormFactory $formFactory, + ) { + } + + protected function createComponentEditForm(): Form + { + $form = $this->formFactory->create(); + $form->onSuccess[] = function () { + $this->flashMessage('レコードが保存されました'); + $this->redirect('Homepage:'); + }; + return $form; + } +} +``` + +この解決策は、フォームまたはその要素に対して `addError()` が呼び出されると、次の `onSuccess` ハンドラが呼び出されないというフォームのプロパティを利用します。 + + +Formクラスからの継承 +============ + +組み立てられたフォームは、フォームの子孫であってはなりません。言い換えれば、この解決策を使用しないでください。 + +```php +// ⛔ これはダメ!継承はここには属しません +class EditForm extends Form +{ + public function __construct(Translator $translator) + { + parent::__construct(); + $this->addText('title', 'タイトル:'); // $form-> を $this-> に変更 + // ここに他のフォームフィールドを追加します + $this->addSubmit('send', '送信'); // $form-> を $this-> に変更 + $this->setTranslator($translator); // $form-> を $this-> に変更 + } +} +``` + +コンストラクタでフォームを組み立てる代わりに、ファクトリを使用してください。 + +`Form` クラスは、主にフォームを組み立てるためのツール、つまり *フォームビルダー* であることを理解する必要があります。そして、組み立てられたフォームはその製品と見なすことができます。しかし、製品はビルダーの特定のケースではなく、それらの間には継承の基礎を形成する *is a* 関係はありません。 + + +フォームを持つコンポーネント +============== + +まったく異なるアプローチは、フォームを含む [コンポーネント|application:components] の作成を表します。これにより、たとえば、コンポーネントにテンプレートも含まれているため、フォームを特定の方法でレンダリングするなど、新しい可能性が生まれます。 または、AJAX通信や、たとえばオートコンプリートなどのフォームへの情報の遅延読み込みにシグナルを利用できます。 + + +```php +use Nette\Application\UI\Form; + +class EditControl extends Nette\Application\UI\Control +{ + public array $onSave = []; + + public function __construct( + private Facade $facade, + ) { + } + + protected function createComponentForm(): Form + { + $form = new Form; + $form->addText('title', 'タイトル:'); + // ここに他のフォームフィールドを追加します + $form->addSubmit('send', '送信'); + $form->onSuccess[] = [$this, 'processForm']; + + return $form; + } + + public function processForm(Form $form, array $data): void + { + try { + // 送信されたデータの処理 + $this->facade->process($data); + + } catch (AnyModelException $e) { + $form->addError('...'); + return; + } + + // イベントの発火 + $this->onSave($this, $data); + } +} +``` + +このコンポーネントを生成するファクトリも作成します。[そのインターフェースを記述する |application:components#依存関係を持つコンポーネント] だけで十分です。 + +```php +interface EditControlFactory +{ + function create(): EditControl; +} +``` + +そして、設定ファイルに追加します。 + +```neon +services: + - EditControlFactory +``` + +そして今、ファクトリを要求してPresenterで使用できます。 + +```php +class MyPresenter extends Nette\Application\UI\Presenter +{ + public function __construct( + private EditControlFactory $controlFactory, + ) { + } + + protected function createComponentEditForm(): EditControl + { + $control = $this->controlFactory->create(); + + $control->onSave[] = function (EditControl $control, $data) { + $this->redirect('this'); + // または編集結果にリダイレクトします、例: + // $this->redirect('detail', ['id' => $data->id]); + }; + + return $control; + } +} +``` diff --git a/best-practices/ja/inject-method-attribute.texy b/best-practices/ja/inject-method-attribute.texy new file mode 100644 index 0000000000..7e85e1e486 --- /dev/null +++ b/best-practices/ja/inject-method-attribute.texy @@ -0,0 +1,61 @@ +injectメソッドと属性 +************* + +.[perex] +この記事では、NetteフレームワークでPresenterに依存関係を渡すさまざまな方法に焦点を当てます。推奨される方法であるコンストラクタを、`inject`メソッドや属性などの他のオプションと比較します。 + +Presenterについても、[コンストラクタ |dependency-injection:passing-dependencies#コンストラクタによる受け渡し] による依存関係の受け渡しが推奨される方法です。 しかし、他のPresenterが継承する共通の祖先(例:`BasePresenter`)を作成し、この祖先も依存関係を持っている場合、[コンストラクタ地獄 |dependency-injection:passing-dependencies#コンストラクタ地獄] と呼ばれる問題が発生します。 これは、injectメソッドと属性(アノテーション)という代替手段を使用して回避できます。 + + +`inject*()` メソッド +================ + +これは、[セッター |dependency-injection:passing-dependencies#セッターによる受け渡し] による依存関係の受け渡しの一形態です。これらのセッターの名前は、接頭辞 `inject` で始まります。 Nette DIは、このように名付けられたメソッドをPresenterインスタンスの作成直後に自動的に呼び出し、必要なすべての依存関係を渡します。したがって、publicとして宣言する必要があります。 + +`inject*()` メソッドは、コンストラクタを複数のメソッドに拡張したものと考えることができます。これにより、`BasePresenter` は別のメソッドを介して依存関係を受け取り、コンストラクタをその子孫のために空けておくことができます。 + +```php +abstract class BasePresenter extends Nette\Application\UI\Presenter +{ + private Foo $foo; + + public function injectBase(Foo $foo): void + { + $this->foo = $foo; + } +} + +class MyPresenter extends BasePresenter +{ + private Bar $bar; + + public function __construct(Bar $bar) + { + $this->bar = $bar; + } +} +``` + +Presenterは任意の数の `inject*()` メソッドを持つことができ、各メソッドは任意の数のパラメータを持つことができます。これは、Presenterが [トレイトで構成されている |presenter-traits] 場合や、各トレイトが独自の依存関係を必要とする場合に非常に便利です。 + + +`Inject` 属性 +=========== + +これは、[プロパティへのインジェクション |dependency-injection:passing-dependencies#変数の設定による受け渡し] の一形態です。どの変数にインジェクトするかを指定するだけで、Nette DIはPresenterインスタンスの作成直後に依存関係を自動的に渡します。それらを挿入できるようにするには、publicとして宣言する必要があります。 + +プロパティを属性でマークします:(以前はアノテーション `/** @inject */` が使用されていました) + +```php +use Nette\DI\Attributes\Inject; // この行は重要です + +class MyPresenter extends Nette\Application\UI\Presenter +{ + #[Inject] + public Cache $cache; +} +``` + +この依存関係の受け渡し方法の利点は、非常に簡潔な記述形式でした。しかし、[コンストラクタプロパティプロモーション |https://blog.nette.org/ja/php-8-0-new-features-overview#toc-constructor-property-promotion] の登場により、コンストラクタを使用する方が簡単に見えます。 + +逆に、この方法は、一般的にプロパティへの依存関係の受け渡しと同じ欠点があります:変数内の変更を制御できず、同時に変数がクラスのパブリックインターフェースの一部となり、これは望ましくありません。 diff --git a/best-practices/ja/lets-create-contact-form.texy b/best-practices/ja/lets-create-contact-form.texy new file mode 100644 index 0000000000..e810ca3a2b --- /dev/null +++ b/best-practices/ja/lets-create-contact-form.texy @@ -0,0 +1,221 @@ +お問い合わせフォームの作成 +************* + +.[perex] +Netteでお問い合わせフォームを作成し、メールで送信する方法を見ていきましょう。さあ、始めましょう! + +まず、新しいプロジェクトを作成する必要があります。その方法は [はじめに |nette:installation] ページで説明されています。その後、フォームの作成を開始できます。 + +最も簡単な方法は、[Presenter内で直接フォームを作成する |forms:in-presenter] ことです。事前に準備された `HomePresenter` を使用できます。そこにフォームを表す `contactForm` コンポーネントを追加します。これを行うには、コードにコンポーネントを作成するファクトリメソッド `createComponentContactForm()` を記述します。 + +```php +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + protected function createComponentContactForm(): Form + { + $form = new Form; + $form->addText('name', 'お名前:') + ->setRequired('名前を入力してください'); + $form->addEmail('email', 'メールアドレス:') + ->setRequired('メールアドレスを入力してください'); + $form->addTextarea('message', 'メッセージ:') + ->setRequired('メッセージを入力してください'); + $form->addSubmit('send', '送信'); + $form->onSuccess[] = [$this, 'contactFormSucceeded']; + return $form; + } + + public function contactFormSucceeded(Form $form, $data): void + { + // メールの送信 + } +} +``` + +ご覧のとおり、2つのメソッドを作成しました。最初のメソッド `createComponentContactForm()` は新しいフォームを作成します。このフォームには、名前、メール、メッセージのフィールドがあり、これらは `addText()`、`addEmail()`、`addTextArea()` メソッドで追加します。フォームを送信するためのボタンも追加しました。 しかし、ユーザーがフィールドを空欄にした場合はどうでしょうか?その場合、それが必須フィールドであることを知らせるべきです。これは `setRequired()` メソッドで実現しました。 最後に、フォームが正常に送信された場合にトリガーされる [イベント |nette:glossary#イベント] `onSuccess` も追加しました。私たちの場合、送信されたフォームの処理を担当する `contactFormSucceeded` メソッドを呼び出します。これはすぐにコードに追加します。 + +`contactForm` コンポーネントを `Home/default.latte` テンプレートでレンダリングさせます。 + +```latte +{block content} +<h1>お問い合わせフォーム</h1> +{control contactForm} +``` + +メールの送信自体については、`ContactFacade` という名前の新しいクラスを作成し、それを `app/Model/ContactFacade.php` ファイルに配置します。 + +```php +<?php +declare(strict_types=1); + +namespace App\Model; + +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $mail = new Message; + $mail->addTo('admin@example.com') // あなたのメールアドレス + ->setFrom($email, $name) + ->setSubject('お問い合わせフォームからのメッセージ') + ->setBody($message); + + $this->mailer->send($mail); + } +} +``` + +`sendMessage()` メソッドはメールを作成して送信します。これには、コンストラクタを介して依存関係として渡される、いわゆるメーラーを使用します。[メールの送信 |mail:] について詳しく読んでください。 + +次に、Presenterに戻り、`contactFormSucceeded()` メソッドを完成させます。これは `ContactFacade` クラスの `sendMessage()` メソッドを呼び出し、フォームからのデータを渡します。そして、`ContactFacade` オブジェクトをどのように取得しますか?コンストラクタを介して渡してもらいます。 + +```php +use App\Model\ContactFacade; +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + public function __construct( + private ContactFacade $facade, + ) { + } + + protected function createComponentContactForm(): Form + { + // ... + } + + public function contactFormSucceeded(stdClass $data): void + { + $this->facade->sendMessage($data->email, $data->name, $data->message); + $this->flashMessage('メッセージは送信されました'); + $this->redirect('this'); + } +} +``` + +メールが送信された後、ユーザーにメッセージが送信されたことを確認する、いわゆる [フラッシュメッセージ |application:components#フラッシュメッセージ] を表示し、その後、ブラウザの *リフレッシュ* でフォームが繰り返し送信されるのを防ぐために現在のページにリダイレクトします。 + + +さて、すべてが機能していれば、お問い合わせフォームからメールを送信できるはずです。おめでとうございます! + + +HTMLメールテンプレート +------------- + +今のところ、フォームから送信されたメッセージのみを含むプレーンテキストのメールが送信されます。しかし、メールでHTMLを使用して、その外観をより魅力的にすることができます。そのためにLatteでテンプレートを作成し、それを `app/Model/contactEmail.latte` に記述します。 + +```latte +<html> + <title>お問い合わせフォームからのメッセージ + + +

    お名前: {$name}

    +

    メールアドレス: {$email}

    +

    メッセージ: {$message}

    + + +``` + +残りは、このテンプレートを使用するように `ContactFacade` を変更することです。コンストラクタで、`Latte\Engine` オブジェクト、つまり [Latteテンプレートレンダラー |latte:develop#テンプレートをレンダリングする方法] を作成できる `LatteFactory` クラスを要求します。`renderToString()` メソッドを使用して、テンプレートを文字列にレンダリングします。最初のパラメータはテンプレートへのパス、2番目は変数です。 + +```php +namespace App\Model; + +use Nette\Bridges\ApplicationLatte\LatteFactory; +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $latte = $this->latteFactory->create(); + $body = $latte->renderToString(__DIR__ . '/contactEmail.latte', [ + 'email' => $email, + 'name' => $name, + 'message' => $message, + ]); + + $mail = new Message; + $mail->addTo('admin@example.com') // あなたのメールアドレス + ->setFrom($email, $name) + ->setHtmlBody($body); + + $this->mailer->send($mail); + } +} +``` + +生成されたHTMLメールを、元の `setBody()` の代わりに `setHtmlBody()` メソッドに渡します。また、ライブラリがテンプレートの `` 要素から件名を取得するため、`setSubject()` でメールの件名を指定する必要もありません。 + + +設定 +----------- + +`ContactFacade` クラスのコードには、まだ管理者メール `admin@example.com` がハードコーディングされています。これを設定ファイルに移動する方が良いでしょう。どうすればよいでしょうか? + +まず、`ContactFacade` クラスを変更し、メールを含む文字列をコンストラクタで渡される変数に置き換えます。 + +```php +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + private string $adminEmail, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + // ... + $mail = new Message; + $mail->addTo($this->adminEmail) + ->setFrom($email, $name) + ->setHtmlBody($body); + // ... + } +} +``` + +そして2番目のステップは、設定でこの変数の値を指定することです。`config/services.neon` ファイル(または `app/config/services.neon`)に次のように記述します。 + +```neon +services: + - App\Model\ContactFacade(adminEmail: admin@example.com) +``` + +これで完了です。`services` セクションの項目が多く、メールがその中で見失われていると感じる場合は、それを変数にすることができます。記述を次のように変更します。 + +```neon +services: + - App\Model\ContactFacade(adminEmail: %adminEmail%) +``` + +そして、`config/common.neon` ファイル(または `app/config/common.neon`)でこのパラメータを定義します。 + +```neon +parameters: + adminEmail: admin@example.com +``` + +これで完了です! diff --git a/best-practices/ja/microsites.texy b/best-practices/ja/microsites.texy new file mode 100644 index 0000000000..f534524da8 --- /dev/null +++ b/best-practices/ja/microsites.texy @@ -0,0 +1,63 @@ +マイクロサイトの書き方 +*********** + +あなたの会社の次のイベントのために、すぐに小さなウェブサイトを作成する必要があると想像してみてください。それはシンプルで、速く、不必要な複雑さがないものでなければなりません。このような小さなプロジェクトには、堅牢なフレームワークは必要ないと思うかもしれません。しかし、Netteフレームワークを使用することで、このプロセスが大幅に簡素化され、高速化されるとしたらどうでしょうか? + +結局のところ、単純なウェブサイトを作成する場合でも、快適さを諦めたくはありません。すでに解決されたことを再発明したくはありません。怠惰になって、甘やかされてください。Nette Frameworkは、マイクロフレームワークとしても最適に使用できます。 + +そのようなマイクロサイトはどのように見えるでしょうか?たとえば、ウェブサイトのコード全体をパブリックフォルダ内の単一のファイル `index.php` に配置します。 + +```php +<?php + +require __DIR__ . '/../vendor/autoload.php'; + +$configurator = new Nette\Bootstrap\Configurator; +$configurator->enableTracy(__DIR__ . '/../log'); +$configurator->setTempDirectory(__DIR__ . '/../temp'); + +// config.neonの設定に基づいてDIコンテナを作成 +$configurator->addConfig(__DIR__ . '/../app/config.neon'); +$container = $configurator->createContainer(); + +// ルーティングを設定 +$router = new Nette\Application\Routers\RouteList; +$container->addService('router', $router); + +// URL https://example.com/ のルート +$router->addRoute('', function ($presenter, Nette\Http\Request $httpRequest) { + // ブラウザの言語を検出し、URL /en や /de などにリダイレクト + $supportedLangs = ['en', 'de', 'cs']; + $lang = $httpRequest->detectLanguage($supportedLangs) ?: reset($supportedLangs); + $presenter->redirectUrl("/$lang"); +}); + +// URL https://example.com/cs または https://example.com/en のルート +$router->addRoute('<lang cs|en>', function ($presenter, string $lang) { + // 対応するテンプレートを表示します、例:../templates/en.latte + $template = $presenter->createTemplate() + ->setFile(__DIR__ . '/../templates/' . $lang . '.latte'); + return $template; +}); + +// アプリケーションを実行! +$container->getByType(Nette\Application\Application::class)->run(); +``` + +その他すべては、親フォルダ `/templates` に保存されたテンプレートになります。 + +`index.php` のPHPコードは、まず [環境を準備し |bootstrap:]、次に [ルート |application:routing#コールバックによる動的ルーティング] を定義し、最後にアプリケーションを実行します。利点は、`addRoute()` 関数の2番目のパラメータが呼び出し可能であり、対応するページが開かれた後に実行されることです。 + + +マイクロサイトにNetteを使用する理由 +-------------------- + +- [Tracy|tracy:] を試したことのあるプログラマは、今日、それなしで何かをプログラミングすることを想像できません。 +- しかし、何よりも、[Latte|latte:] テンプレートシステムを利用するでしょう。なぜなら、2ページ目から[レイアウトとコンテンツ|latte:template-inheritance] を分離したいからです。 +- そして、XSS脆弱性が発生しないように、[自動エスケープ |latte:safety-first] に頼りたいはずです。 +- Netteはまた、エラーが発生した場合にプログラマ向けのエラーメッセージPHPが表示されず、ユーザーフレンドリーなページが表示されることを保証します。 +- たとえばお問い合わせフォームの形でユーザーからのフィードバックを得たい場合は、[フォーム|forms:] と [データベース|database:] を追加するだけです。 +- 記入されたフォームを簡単に [メールで送信する|mail:] こともできます。 +- たとえばフィードを取得して表示する場合など、[キャッシュ|caching:] が役立つことがあります。 + +速度と効率が鍵となる今日の世界では、不必要な遅延なしに結果を達成できるツールを持つことが重要です。Nette frameworkはまさにそれを提供します - 迅速な開発、セキュリティ、そしてプロセスを簡素化するTracyやLatteなどの幅広いツール。いくつかのNetteパッケージをインストールするだけで、このようなマイクロサイトを構築するのは突然非常に簡単になります。そして、どこにもセキュリティホールが隠れていないことを知っています。 diff --git a/best-practices/ja/pagination.texy b/best-practices/ja/pagination.texy new file mode 100644 index 0000000000..c2524004bd --- /dev/null +++ b/best-practices/ja/pagination.texy @@ -0,0 +1,273 @@ +データベース結果のページネーション +***************** + +.[perex] +Webアプリケーションを作成する際、ページに表示される項目数を制限するという要件に非常に頻繁に遭遇します。 + +ページネーションなしですべてのデータを表示する状態から始めます。データベースからデータを選択するために、コンストラクタに加えて、公開されたすべての記事を公開日の降順で返す `findPublishedArticles` メソッドを含む `ArticleRepository` クラスがあります。 + +```php +namespace App\Model; + +use Nette; + +class ArticleRepository +{ + public function __construct( + private Nette\Database\Connection $database, + ) { + } + + public function findPublishedArticles(): Nette\Database\ResultSet + { + return $this->database->query(' + SELECT * FROM articles + WHERE created_at < ? + ORDER BY created_at DESC', + new \DateTime, + ); + } +} +``` + +Presenterでは、モデルクラスをインジェクトし、renderメソッドで公開された記事を要求し、それをテンプレートに渡します。 + +```php +namespace App\Presentation\Home; + +use Nette; +use App\Model\ArticleRepository; + +class HomePresenter extends Nette\Application\UI\Presenter +{ + public function __construct( + private ArticleRepository $articleRepository, + ) { + } + + public function renderDefault(): void + { + $this->template->articles = $this->articleRepository->findPublishedArticles(); + } +} +``` + +`default.latte` テンプレートでは、記事の表示を担当します。 + +```latte +{block content} +<h1>記事</h1> + +<div class="articles"> + {foreach $articles as $article} + <h2>{$article->title}</h2> + <p>{$article->content}</p> + {/foreach} +</div> +``` + + +この方法で、すべての記事を表示できますが、記事の数が増えると問題が発生し始めます。その時点で、ページネーションメカニズムの実装が役立ちます。 + +これにより、すべての記事がいくつかのページに分割され、現在の1ページの記​​事のみが表示されます。合計ページ数と記事の分割は、[Paginator |utils:Paginator] が、合計でいくつの記事があり、ページごとに表示したい記事の数に基づいて自動的に計算します。 + +最初のステップでは、リポジトリクラスの記事取得メソッドを変更して、1ページの記事のみを返すようにします。また、Paginatorを設定するために必要なデータベース内の記事の総数を取得するメソッドを追加します。 + +```php +namespace App\Model; + +use Nette; + + +class ArticleRepository +{ + public function __construct( + private Nette\Database\Connection $database, + ) { + } + + public function findPublishedArticles(int $limit, int $offset): Nette\Database\ResultSet + { + return $this->database->query(' + SELECT * FROM articles + WHERE created_at < ? + ORDER BY created_at DESC + LIMIT ? + OFFSET ?', + new \DateTime, $limit, $offset, + ); + } + + /** + * 公開された記事の総数を返します + */ + public function getPublishedArticlesCount(): int + { + return $this->database->fetchField('SELECT COUNT(*) FROM articles WHERE created_at < ?', new \DateTime); + } +} +``` + +次に、Presenterの変更に取り掛かります。renderメソッドに現在表示されているページの番号を渡します。この番号がURLの一部でない場合、最初のページのデフォルト値を設定します。 + +また、renderメソッドを拡張して、Paginatorインスタンスの取得、その設定、およびテンプレートで表示するための正しい記事の選択を行います。変更後のHomePresenterは次のようになります(Paginatorを使用する場合): + +```php +namespace App\Presentation\Home; + +use Nette; +use App\Model\ArticleRepository; + +class HomePresenter extends Nette\Application\UI\Presenter +{ + public function __construct( + private ArticleRepository $articleRepository, + ) { + } + + public function renderDefault(int $page = 1): void + { + // 公開された記事の総数を取得します + $articlesCount = $this->articleRepository->getPublishedArticlesCount(); + + // Paginatorのインスタンスを作成し、設定します + $paginator = new Nette\Utils\Paginator; + $paginator->setItemCount($articlesCount); // 記事の総数 + $paginator->setItemsPerPage(10); // ページあたりの項目数 + $paginator->setPage($page); // 現在のページ番号 + + // Paginatorの計算に基づいてデータベースから記事の限定されたセットを取得します + $articles = $this->articleRepository->findPublishedArticles($paginator->getLength(), $paginator->getOffset()); + + // それをテンプレートに渡します + $this->template->articles = $articles; + // そして、ページネーションオプションを表示するためのPaginator自体も + $this->template->paginator = $paginator; + } +} +``` + +テンプレートはすでに1ページの記​​事のみを反復処理しているため、ページネーションリンクを追加するだけで済みます。 + +```latte +{block content} +<h1>記事</h1> + +<div class="articles"> + {foreach $articles as $article} + <h2>{$article->title}</h2> + <p>{$article->content}</p> + {/foreach} +</div> + +<div class="pagination"> + {if !$paginator->isFirst()} + <a n:href="default, 1">最初</a> +  |  + <a n:href="default, $paginator->page-1">前へ</a> +  |  + {/if} + + ページ {$paginator->getPage()} / {$paginator->getPageCount()} + + {if !$paginator->isLast()} +  |  + <a n:href="default, $paginator->getPage() + 1">次へ</a> +  |  + <a n:href="default, $paginator->getPageCount()">最後</a> + {/if} +</div> +``` + + +このようにして、Paginatorを使用してページネーションオプションをページに追加しました。データベース層として [Nette Database Core |database:sql-way] の代わりに [Nette Database Explorer |database:explorer] を使用する場合、Paginatorを使用せずにページネーションを実装することもできます。`Nette\Database\Table\Selection` クラスには、Paginatorから継承されたページネーションロジックを持つ [page() |api:Nette\Database\Table\Selection::page()] メソッドが含まれています。 + +この実装方法では、リポジトリは次のようになります。 + +```php +namespace App\Model; + +use Nette; + +class ArticleRepository +{ + public function __construct( + private Nette\Database\Explorer $database, + ) { + } + + public function findPublishedArticles(): Nette\Database\Table\Selection + { + return $this->database->table('articles') + ->where('created_at < ', new \DateTime) + ->order('created_at DESC'); + } +} +``` + +Presenterでは、Paginatorを作成する必要はありません。代わりに、リポジトリが返す `Selection` クラスのメソッドを使用します。 + +```php +namespace App\Presentation\Home; + +use Nette; +use App\Model\ArticleRepository; + +class HomePresenter extends Nette\Application\UI\Presenter +{ + public function __construct( + private ArticleRepository $articleRepository, + ) { + } + + public function renderDefault(int $page = 1): void + { + // 公開された記事を取得します + $articles = $this->articleRepository->findPublishedArticles(); + + // そして、pageメソッドの計算に基づいて制限された部分のみをテンプレートに送信します + $lastPage = 0; + $this->template->articles = $articles->page($page, 10, $lastPage); + + // そして、ページネーションオプションを表示するために必要なデータも + $this->template->page = $page; + $this->template->lastPage = $lastPage; + } +} +``` + +テンプレートにPaginatorを送信しなくなったため、ページネーションリンクを表示する部分を変更します。 + +```latte +{block content} +<h1>記事</h1> + +<div class="articles"> + {foreach $articles as $article} + <h2>{$article->title}</h2> + <p>{$article->content}</p> + {/foreach} +</div> + +<div class="pagination"> + {if $page > 1} + <a n:href="default, 1">最初</a> +  |  + <a n:href="default, $page - 1">前へ</a> +  |  + {/if} + + ページ {$page} / {$lastPage} + + {if $page < $lastPage} +  |  + <a n:href="default, $page + 1">次へ</a> +  |  + <a n:href="default, $lastPage">最後</a> + {/if} +</div> +``` + +この方法で、Paginatorを使用せずにページネーションメカニズムを実装しました。 + +{{priority: -1}} diff --git a/best-practices/ja/passing-settings-to-presenters.texy b/best-practices/ja/passing-settings-to-presenters.texy new file mode 100644 index 0000000000..78fcc70158 --- /dev/null +++ b/best-practices/ja/passing-settings-to-presenters.texy @@ -0,0 +1,49 @@ +Presenterへの設定の受け渡し +****************** + +.[perex] +Presenterにオブジェクトではない引数(デバッグモードで実行されているかどうかの情報、ディレクトリへのパスなど)を渡す必要があり、したがってautowiringを使用して自動的に渡すことができない場合はどうすればよいですか?解決策は、それらを`Settings`オブジェクトにカプセル化することです。 + +`Settings` サービスは、実行中のアプリケーションに関する情報をPresenterに提供するための非常に簡単で便利な方法を提供します。その具体的な形式は、完全にあなたの特定のニーズに依存します。例: + +```php +namespace App; + +class Settings +{ + public function __construct( + // PHP 8.1以降、readonlyを指定可能 + public bool $debugMode, + public string $appDir, + // など + ) {} +} +``` + +設定への登録例: + +```neon +services: + - App\Settings( + %debugMode%, + %appDir%, + ) +``` + +Presenterがこのサービスによって提供される情報を必要とする場合、単にコンストラクタでそれを要求します: + +```php +class MyPresenter extends Nette\Application\UI\Presenter +{ + public function __construct( + private App\Settings $settings, + ) {} + + public function renderDefault() + { + if ($this->settings->debugMode) { + // ... + } + } +} +``` diff --git a/best-practices/ja/post-links.texy b/best-practices/ja/post-links.texy new file mode 100644 index 0000000000..2d4877f097 --- /dev/null +++ b/best-practices/ja/post-links.texy @@ -0,0 +1,56 @@ +POSTリンクの正しい使い方 +************** + +.[perex] +Webアプリケーション、特に管理インターフェースでは、サーバーの状態を変更するアクションはHTTPメソッドGETを介して実行されるべきではないという基本的なルールがあるべきです。メソッド名が示すように、GETはデータの取得にのみ使用されるべきであり、変更には使用されるべきではありません。 レコードの削除などのアクションには、POSTメソッドを使用する方が適しています。理想的にはDELETEメソッドですが、JavaScriptなしでは呼び出せないため、歴史的にPOSTが使用されています。 + +実践的にはどうすればよいでしょうか?この簡単なトリックを利用してください。テンプレートの最初に、`postForm` という識別子を持つ補助フォームを作成し、それを削除ボタンに使用します。 + +```latte .{file:@layout.latte} +<form method="post" id="postForm"></form> +``` + +このフォームのおかげで、古典的なリンク `<a>` の代わりに、通常のリンクのように見えるように視覚的に調整できるボタン `<button>` を使用できます。たとえば、CSSフレームワークBootstrapは、ボタンが他のリンクと視覚的に区別されないようにするクラス `btn btn-link text-danger` を提供します(削除なので赤文字にする例)。属性 `form="postForm"` を使用して、事前に準備されたフォームにリンクします。`formaction` 属性で送信先URLを指定します。 + +```latte .{file:admin.latte} +<table> + <tr n:foreach="$posts as $post"> + <td>{$post->title}</td> + <td> + <button class="btn btn-link" form="postForm" formaction="{link delete $post->id}">削除</button> + <!-- <a n:href="delete $post->id">delete</a> の代わりに --> + </td> + </tr> +</table> +``` + +リンク(ボタン)をクリックすると、`delete` アクションが呼び出されます。リクエストがPOSTメソッドと同一ドメインからのみ受け入れられるようにするため(これはCSRF攻撃に対する効果的な防御策です)、`#[Requires]` 属性を使用します。 + +```php .{file:AdminPresenter.php} +use Nette\Application\Attributes\Requires; + +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST', sameOrigin: true)] + public function actionDelete(int $id): void + { + $this->facade->deletePost($id); // レコードを削除する仮のコード + $this->redirect('default'); + } +} +``` + +この属性はNette Application 3.2以降存在し、その可能性については [#Requires属性の使い方 |attribute-requires] ページで詳しく知ることができます。 + +`actionDelete()` アクションの代わりに `handleDelete()` シグナルを使用する場合、シグナルにはこの保護が暗黙的に設定されているため、`sameOrigin: true` を指定する必要はありません。 + +```php .{file:AdminPresenter.php} +#[Requires(methods: 'POST')] +public function handleDelete(int $id): void +{ + $this->facade->deletePost($id); + $this->redirect('this'); +} +``` + +このアプローチは、アプリケーションのセキュリティを向上させるだけでなく、正しいWeb標準と実践の遵守にも貢献します。状態を変更するアクションにPOSTメソッドを利用することで、より堅牢で安全なアプリケーションを実現できます。 diff --git a/best-practices/ja/presenter-traits.texy b/best-practices/ja/presenter-traits.texy new file mode 100644 index 0000000000..3410b8424f --- /dev/null +++ b/best-practices/ja/presenter-traits.texy @@ -0,0 +1,47 @@ +トレイトからのPresenterの構成 +******************* + +.[perex] +複数のPresenterで同じコードを実装する必要がある場合(例:ユーザーがログインしているかの検証)、コードを共通の祖先に配置することが考えられます。もう一つの選択肢は、単一目的の[トレイト |nette:introduction-to-object-oriented-programming#トレイト]を作成することです。 + +この解決策の利点は、各Presenterが必要とするトレイトだけを使用できることです。一方、PHPでは多重継承は不可能です。 + +これらのトレイトは、Presenterが作成されるときに、すべての [injectメソッド |inject-method-attribute#inject メソッド] が順次呼び出されるという事実を利用できます。各injectメソッドの名前が一意であることを確認するだけで済みます。 + +トレイトは、[onStartup または onRender |application:presenters#イベント] イベントに初期化コードをフックすることができます。 + +例: + +```php +trait RequireLoggedUser +{ + public function injectRequireLoggedUser(): void + { + $this->onStartup[] = function () { + if (!$this->getUser()->isLoggedIn()) { + $this->redirect('Sign:in', $this->storeRequest()); + } + }; + } +} + +trait StandardTemplateFilters +{ + public function injectStandardTemplateFilters(TemplateBuilder $builder): void + { + $this->onRender[] = function () use ($builder) { + $builder->setupTemplate($this->template); + }; + } +} +``` + +Presenterはこれらのトレイトを簡単に使用します: + +```php +class ArticlePresenter extends Nette\Application\UI\Presenter +{ + use StandardTemplateFilters; + use RequireLoggedUser; +} +``` diff --git a/best-practices/ja/restore-request.texy b/best-practices/ja/restore-request.texy new file mode 100644 index 0000000000..6a17b77503 --- /dev/null +++ b/best-practices/ja/restore-request.texy @@ -0,0 +1,62 @@ +前のページに戻る方法は? +************ + +.[perex] +ユーザーがフォームに入力中にログインセッションが切れたらどうしますか?データを失わないように、ログインページにリダイレクトする前にデータをセッションに保存します。Netteではこれは非常に簡単です。 + +現在のリクエストは `storeRequest()` メソッドを使用してセッションに保存でき、その識別子を短い文字列として返します。このメソッドは、現在のPresenterの名前、ビュー、およびそのパラメータを保存します。 フォームも送信された場合、フィールドの内容も保存されます(アップロードされたファイルを除く)。 + +リクエストの復元は `restoreRequest($key)` メソッドによって行われ、取得した識別子を渡します。これは元のPresenterとビューにリダイレクトします。ただし、保存されたリクエストにフォーム送信が含まれている場合、元のPresenterには `forward()` メソッドで移動し、以前に入力された値をフォームに渡し、再度レンダリングさせます。これにより、ユーザーはフォームを再度送信する機会があり、データは失われません。 + +重要なのは、`restoreRequest()` が新しくログインしたユーザーが最初にフォームに入力したユーザーと同じであるかどうかを確認することです。そうでない場合、リクエストは破棄され、何も行われません。 + +例で説明しましょう。データを編集する `AdminPresenter` があり、その `startup()` メソッドでユーザーがログインしているかどうかを確認します。ログインしていない場合は、`SignPresenter` にリダイレクトします。同時に、現在のリクエストを保存し、そのキーを `SignPresenter` に送信します。 + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + protected function startup() + { + parent::startup(); + + if (!$this->user->isLoggedIn()) { + $this->redirect('Sign:in', ['backlink' => $this->storeRequest()]); + } + } +} +``` + +`SignPresenter` は、ログインフォームに加えて、キーが書き込まれる永続パラメータ `$backlink` も含みます。パラメータは永続的であるため、ログインフォームの送信後も転送されます。 + + +```php +use Nette\Application\Attributes\Persistent; + +class SignPresenter extends Nette\Application\UI\Presenter +{ + #[Persistent] + public string $backlink = ''; + + protected function createComponentSignInForm() + { + $form = new Nette\Application\UI\Form; + // ... フォームコントロールを追加 ... + $form->onSuccess[] = [$this, 'signInFormSubmitted']; + return $form; + } + + public function signInFormSubmitted($form) + { + // ... ここでユーザーをログインさせます ... + + $this->restoreRequest($this->backlink); + $this->redirect('Admin:'); + } +} +``` + +保存されたリクエストのキーを `restoreRequest()` メソッドに渡し、元のPresenterにリダイレクト(または移動)します。 + +ただし、キーが無効な場合(たとえば、セッションに存在しなくなった場合)、メソッドは何も行いません。したがって、`AdminPresenter` にリダイレクトする `$this->redirect('Admin:')` の呼び出しが続きます。 + +{{priority: -1}} diff --git a/best-practices/pl/@home.texy b/best-practices/pl/@home.texy index 32416cbcfb..64470576e1 100644 --- a/best-practices/pl/@home.texy +++ b/best-practices/pl/@home.texy @@ -1,8 +1,8 @@ -Instrukcje i procedury -********************** +Przewodniki i dobre praktyki +**************************** .[perex] -Tutoriale, rozwiązania typowych problemów i *najlepsze praktyki* dla Nette. +Poradniki, rozwiązania częstych zadań i *dobre praktyki* dla Nette. <div class=documentation> @@ -11,12 +11,14 @@ Tutoriale, rozwiązania typowych problemów i *najlepsze praktyki* dla Nette. Aplikacje Nette --------------- -- [Metody i atrybuty wst rzykiwania|inject-method-attribute] -- [Komponowanie prezenterów z cech |presenter-traits] -- [Przekazywanie ustawień do prezenterów |passing-settings-to-presenters] -- [Jak wrócić do wcześniejszej strony |restore-request] -- [Paginowanie wyników bazy danych |pagination] -- [Dynamiczne fragmenty |dynamic-snippets] +- [Metody i atrybuty inject |inject-method-attribute] +- [Składanie presenterów z traitów |presenter-traits] +- [Przekazywanie ustawień do presenterów |passing-settings-to-presenters] +- [Jak wrócić do poprzedniej strony |restore-request] +- [Stronicowanie wyników bazy danych |pagination] +- [Dynamiczne snippety |dynamic-snippets] +- [Jak używać atrybutu #Requires |attribute-requires] +- [Jak poprawnie używać linków POST |post-links] </div> <div> @@ -24,32 +26,34 @@ Aplikacje Nette Formularze ---------- -- [Ponowne wykorzystanie formularzy |form-reuse] +- [Reużycie formularzy |form-reuse] - [Formularz do tworzenia i edycji rekordu |creating-editing-form] -- [Stwórzmy formularz kontaktowy |lets-create-contact-form] -- [Zależne pola wyboru |https://blog.nette.org/pl/zalezne-selectboxy-elegancko-w-nette-i-czysty-js] +- [Tworzymy formularz kontaktowy |lets-create-contact-form] +- [Zależne selectboxy |https://blog.nette.org/pl/dependent-selectboxes-elegantly-in-nette-and-pure-js] </div> <div> -Wspólne -------- -- [Jak załadować plik konfiguracyjny |bootstrap:] -- [Dlaczego Nette używa notacji stałej PascalCase? |https://blog.nette.org/pl/aby-mniej-krzyczec-w-kodzie] -- [Dlaczego Nette nie używa przyrostka Interface? |https://blog.nette.org/pl/przedrostki-i-przyrostki-nie-sa-czescia-nazw-interfejsow] -- [Kompozytor: wskazówki dotyczące użytkowania |composer] -- [Porady dotyczące edytorów i narzędzi |editors-and-tools] +Ogólne +------ +- [Jak wczytać plik konfiguracyjny |bootstrap:] +- [Jak pisać mikro-strony |microsites] +- [Dlaczego Nette używa notacji PascalCase dla stałych? |https://blog.nette.org/pl/for-less-screaming-in-the-code] +- [Dlaczego Nette nie używa przyrostka Interface? |https://blog.nette.org/pl/prefixes-and-suffixes-do-not-belong-in-interface-names] +- [Composer: wskazówki dotyczące użycia |composer] +- [Wskazówki dotyczące edytorów i narzędzi |editors-and-tools] +- [Wprowadzenie do programowania obiektowego |nette:introduction-to-object-oriented-programming] </div> <div> -Roztwór próbki --------------- +Przykładowe rozwiązania +----------------------- - [Przykłady Nette |https://github.com/nette-examples] -- [Doktryna i Nette |https://contributte.org/nettrine/] -- [Przykłady kont rybucji|https://contributte.org/examples.html] +- [Doctrine & Nette |https://contributte.org/nettrine/] +- [Przykłady Contributte |https://contributte.org/examples.html] - [Strona internetowa Doctrine ORM |https://github.com/MinecordNetwork/Website] - [Szybki start |quickstart:] @@ -59,10 +63,7 @@ Roztwór próbki Wideo ----- -Setki filmów z Ostatnich Sobót i Nette można znaleźć pod jednym na kanale "Nette Framework YouTube Channel":https://www.youtube.com/user/NetteFramework. +Setki nagrań z Posledních sobot i filmów o Nette znajdziesz pod jednym dachem na "kanale Youtube Nette Frameworku":https://www.youtube.com/user/NetteFramework. </div> </div> - -{{sitename: Najlepsze praktyki}} -{{leftbar: www:@menu-common}} diff --git a/best-practices/pl/@meta.texy b/best-practices/pl/@meta.texy new file mode 100644 index 0000000000..28fbec9e34 --- /dev/null +++ b/best-practices/pl/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Przewodniki i dobre praktyki}} +{{leftbar: www:@menu-common}} diff --git a/best-practices/pl/attribute-requires.texy b/best-practices/pl/attribute-requires.texy new file mode 100644 index 0000000000..750a41a034 --- /dev/null +++ b/best-practices/pl/attribute-requires.texy @@ -0,0 +1,177 @@ +Jak używać atrybutu `#[Requires]` +********************************* + +.[perex] +Kiedy piszesz aplikację internetową, często spotykasz się z potrzebą ograniczenia dostępu do określonych części Twojej aplikacji. Być może chcesz, aby niektóre żądania mogły wysyłać dane tylko za pomocą formularza (czyli metodą POST), lub aby były dostępne tylko dla wywołań AJAX. W Nette Framework 3.2 pojawiło się nowe narzędzie, które pozwoli Ci ustawić takie ograniczenia bardzo elegancko i przejrzyście: atrybut `#[Requires]`. + +Atrybut to specjalny znacznik w PHP, który dodajesz przed definicją klasy lub metody. Ponieważ jest to właściwie klasa, aby poniższe przykłady działały, konieczne jest podanie klauzuli use: + +```php +use Nette\Application\Attributes\Requires; +``` + +Atrybut `#[Requires]` możesz użyć przy samej klasie presentera, a także przy tych metodach: + +- `action<Action>()` +- `render<View>()` +- `handle<Signal>()` +- `createComponent<Name>()` + +Ostatnie dwie metody dotyczą również komponentów, więc atrybut możesz używać również przy nich. + +Jeśli nie są spełnione warunki, które atrybut podaje, dojdzie do wywołania błędu HTTP 4xx. + + +Metody HTTP +----------- + +Możesz określić, które metody HTTP (jak GET, POST itp.) są dozwolone dla dostępu. Na przykład, jeśli chcesz zezwolić na dostęp tylko przez wysłanie formularza, ustawisz: + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +Dlaczego powinieneś używać POST zamiast GET dla akcji zmieniających stan i jak to zrobić? [Przeczytaj poradnik |post-links]. + +Możesz podać metodę lub tablicę metod. Specjalnym przypadkiem jest wartość `'*'`, która zezwoli na wszystkie metody, czego standardowo presentery z [powodów bezpieczeństwa nie pozwalają |application:presenters#Kontrola metody HTTP]. + + +Wywołanie AJAX +-------------- + +Jeśli chcesz, aby presenter lub metoda była dostępna tylko dla żądań AJAX, użyj: + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +To samo pochodzenie +------------------- + +Dla zwiększenia bezpieczeństwa możesz wymagać, aby żądanie zostało wykonane z tej samej domeny. Tym samym zapobiegniesz [podatności CSRF |nette:vulnerability-protection#Cross-Site Request Forgery CSRF]: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +W przypadku metod `handle<Signal>()` dostęp z tej samej domeny jest wymagany automatycznie. Więc jeśli odwrotnie chcesz zezwolić na dostęp z dowolnej domeny, podaj: + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Dostęp przez forward +-------------------- + +Czasami przydatne jest ograniczenie dostępu do presentera tak, aby był dostępny tylko pośrednio, na przykład używając metody `forward()` lub `switch()` z innego presentera. W ten sposób na przykład chroni się error-presentery, aby nie można było ich wywołać z URL: + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +W praktyce często bywa potrzeba oznaczenia określonych widoków, do których można dostać się dopiero na podstawie logiki w presenterze. Czyli ponownie, aby nie można było ich otworzyć bezpośrednio: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = $this->facade->getProduct($id); + if (!$product) { + $this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Konkretne akcje +--------------- + +Możesz również ograniczyć, że określony kod, na przykład utworzenie komponentu, będzie dostępny tylko dla specyficznych akcji w presenterze: + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +W przypadku jednej akcji nie trzeba zapisywać tablicy: `#[Requires(actions: 'default')]` + + +Własne atrybuty +--------------- + +Jeśli chcesz użyć atrybutu `#[Requires]` wielokrotnie z tym samym ustawieniem, możesz stworzyć własny atrybut, który będzie dziedziczył `#[Requires]` i ustawi go według potrzeb. + +Na przykład `#[SingleAction]` umożliwi dostęp tylko przez akcję `default`: + +```php +#[\Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Lub `#[RestMethods]` umożliwi dostęp przez wszystkie metody HTTP używane dla REST API: + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Zakończenie +----------- + +Atrybut `#[Requires]` daje Ci dużą elastyczność i kontrolę nad tym, jak dostępne są Twoje strony internetowe. Za pomocą prostych, ale potężnych reguł możesz zwiększyć bezpieczeństwo i prawidłowe funkcjonowanie Twojej aplikacji. Jak widzisz, użycie atrybutów w Nette może Twoją pracę nie tylko ułatwić, ale i zabezpieczyć. diff --git a/best-practices/pl/composer.texy b/best-practices/pl/composer.texy index f208932801..f8ccf099f8 100644 --- a/best-practices/pl/composer.texy +++ b/best-practices/pl/composer.texy @@ -1,44 +1,44 @@ -Kompozytor: wskazówki dotyczące użytkowania -******************************************* +Composer: wskazówki dotyczące użytkowania +***************************************** <div class=perex> -Composer to narzędzie do zarządzania zależnościami w PHP. Pozwoli nam ona na wypisanie bibliotek, od których zależy nasz projekt oraz zainstaluje i zaktualizuje je za nas. Zobaczymy: +Composer to narzędzie do zarządzania zależnościami w PHP. Umożliwia nam zdefiniowanie bibliotek, od których zależy nasz projekt, i będzie je za nas instalować oraz aktualizować. Pokażemy: -- jak zainstalować Composera -- jak używać go w nowym lub istniejącym projekcie +- jak zainstalować Composer +- jego użycie w nowym lub istniejącym projekcie </div> -Instalacja .[#toc-installation] -=============================== +Instalacja +========== -Composer to plik wykonywalny `.phar`, który pobierasz i instalujesz w następujący sposób: +Composer to plik wykonywalny `.phar`, który pobierzesz i zainstalujesz w następujący sposób: -Windows .[#toc-windows] ------------------------ +Windows +------- Użyj oficjalnego instalatora [Composer-Setup.exe |https://getcomposer.org/Composer-Setup.exe]. -Linux, macOS .[#toc-linux-macos] --------------------------------- +Linux, macOS +------------ -Wystarczy 4 polecenia, które kopiujesz z [tej strony |https://getcomposer.org/download/]. +Wystarczą 4 polecenia, które skopiujesz z [tej strony |https://getcomposer.org/download/]. -Następnie wklejenie go do folderu, który znajduje się w systemie `PATH`, sprawi, że Composer będzie dostępny globalnie: +Następnie, umieszczając go w folderze, który znajduje się w systemowym `PATH`, Composer stanie się dostępny globalnie: ```shell -$ mv ./composer.phar ~/bin/composer # nebo /usr/local/bin/composer +$ mv ./composer.phar ~/bin/composer # lub /usr/local/bin/composer ``` -Zastosowanie w projekcie .[#toc-use-in-project] -=============================================== +Użycie w projekcie +================== -Aby zacząć używać Composera w swoim projekcie, wszystko czego potrzebujesz to plik `composer.json`. Opisuje on zależności twojego projektu i może zawierać dodatkowe metadane. Zatem podstawowy `composer.json` może wyglądać tak: +Aby móc w swoim projekcie zacząć używać Composera, potrzebujesz tylko pliku `composer.json`. Opisuje on zależności naszego projektu i może również zawierać inne metadane. Podstawowy `composer.json` może więc wyglądać tak: ```js { @@ -50,15 +50,15 @@ Aby zacząć używać Composera w swoim projekcie, wszystko czego potrzebujesz t Mówimy tutaj, że nasza aplikacja (lub biblioteka) wymaga pakietu `nette/database` (nazwa pakietu składa się z nazwy organizacji i nazwy projektu) i chce wersji, która odpowiada warunkowi `^3.0` (tj. najnowszej wersji 3). -Mamy więc `composer.json` w korzeniu projektu i rozpoczynamy instalację: +Mamy więc w katalogu głównym projektu plik `composer.json` i uruchamiamy instalację: ```shell composer update ``` -Composer pobiera bazę danych Nette Database do folderu `vendor/`. Następnie tworzy plik `composer.lock`, który zawiera informacje o tym, jakie dokładnie wersje bibliotek zainstalował. +Composer pobierze Nette Database do folderu `vendor/`. Następnie utworzy plik `composer.lock`, który zawiera informacje o tym, które wersje bibliotek dokładnie zainstalował. -Composer wygeneruje plik `vendor/autoload.php`, który możemy po prostu zintegrować i zacząć korzystać z bibliotek bez dalszej pracy: +Composer wygeneruje plik `vendor/autoload.php`, który możemy po prostu dołączyć i zacząć używać bibliotek bez żadnej dodatkowej pracy: ```php require __DIR__ . '/vendor/autoload.php'; @@ -67,52 +67,52 @@ $db = new Nette\Database\Connection('sqlite::memory:'); ``` -Aktualizacja pakietów do najnowszych wersji .[#toc-update-packages-to-the-latest-versions] -========================================================================================== +Aktualizacja pakietów do najnowszych wersji +=========================================== -Polecenie `composer update` jest odpowiedzialne za aktualizację używanych bibliotek do najnowszych wersji zgodnie z warunkami określonymi w `composer.json`. Na przykład dla zależności `"nette/database": "^3.0"` zainstaluje najnowszą wersję 3.x.x, ale już nie wersję 4. +Za aktualizację używanych bibliotek do najnowszych wersji zgodnie z warunkami zdefiniowanymi w `composer.json` odpowiada polecenie `composer update`. Np. przy zależności `"nette/database": "^3.0"` zainstaluje najnowszą wersję 3.x.x, ale już nie wersję 4. -Aby zaktualizować warunki w pliku `composer.json` do `"nette/database": "^4.1"`, na przykład w celu zainstalowania najnowszej wersji, należy użyć polecenia `composer require nette/database`. +Aby zaktualizować warunki w pliku `composer.json`, na przykład na `"nette/database": "^4.1"`, aby można było zainstalować najnowszą wersję, użyj polecenia `composer require nette/database`. -Aby zaktualizować wszystkie używane pakiety Nette, należałoby wymienić je wszystkie w linii poleceń, np: +Aby zaktualizować wszystkie używane pakiety Nette, trzeba by je wszystkie wymienić w wierszu poleceń, np.: ```shell composer require nette/application nette/forms latte/latte tracy/tracy ... ``` -Co jest niepraktyczne. Użyj więc prostego skryptu "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff, aby zrobić to za Ciebie: +Co jest niepraktyczne. Użyj dlatego prostego skryptu "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff, który to zrobi za Ciebie: ```shell php composer-frontline.php ``` -Utwórz nowy projekt .[#toc-creating-new-project] -================================================ +Tworzenie nowego projektu +========================= -Nowy projekt w Nette można utworzyć za pomocą jednego polecenia: +Nowy projekt Nette utworzysz za pomocą jednego polecenia: ```shell -composer create-project nette/web-project nazev-projektu +composer create-project nette/web-project nazwa-projektu ``` -Wpisz nazwę katalogu dla swojego projektu jako `nazev-projektu` i zatwierdź. Composer pobiera z GitHuba repozytorium `nette/web-project`, które zawiera już `composer.json`, a następnie Nette Framework. Powinieneś tylko [ustawić uprawnienia |nette:troubleshooting#Setting-Directory-Permissions] do [zapisu |nette:troubleshooting#Setting-Directory-Permissions] na `temp/` i `log/` i twój projekt powinien ożyć. +Jako `nazwa-projektu` wstaw nazwę katalogu dla swojego projektu i potwierdź. Composer pobierze repozytorium `nette/web-project` z GitHubu, które już zawiera plik `composer.json`, a zaraz potem Nette Framework. Powinno już wystarczyć tylko [ustawić uprawnienia |nette:troubleshooting#Ustawianie uprawnień do katalogów] do zapisu w folderach `temp/` i `log/`, a projekt powinien ożyć. -Jeśli wiesz, na jakiej wersji PHP będzie hostowany projekt, koniecznie [go |#PHP Version] skonfiguruj. +Jeśli wiesz, na jakiej wersji PHP projekt będzie hostowany, nie zapomnij [jej ustawić |#Wersja PHP]. -Wersja PHP .[#toc-php-version] -============================== +Wersja PHP +========== -Composer zawsze instaluje wersje pakietów, które są kompatybilne z wersją PHP, której aktualnie używasz (lub raczej z wersją PHP używaną w linii poleceń, gdy uruchamiasz Composera). Co prawdopodobnie nie jest tą samą wersją, której używa Twój hosting. Dlatego bardzo ważne jest dodanie informacji o wersji PHP na Twoim hostingu do pliku `composer.json`. Następnie zostaną zainstalowane tylko wersje pakietów zgodne z hostem. +Composer zawsze instaluje te wersje pakietów, które są kompatybilne z wersją PHP, której właśnie używasz (a raczej z wersją PHP używaną w wierszu poleceń podczas uruchamiania Composera). Co jednak najprawdopodobniej nie jest tą samą wersją, której używa Twój hosting. Dlatego bardzo ważne jest dodanie do pliku `composer.json` informacji o wersji PHP na hostingu. Wtedy będą instalowane tylko wersje pakietów kompatybilne z hostingiem. -Na przykład, aby ustawić projekt tak, aby działał na PHP 8.2.3, użyj polecenia: +To, że projekt będzie działał na przykład na PHP 8.2.3, ustawimy poleceniem: ```shell composer config platform.php 8.2.3 ``` -W ten sposób wersja jest zapisywana do pliku `composer.json`: +W ten sposób wersja zostanie zapisana do pliku `composer.json`: ```js { @@ -124,8 +124,7 @@ W ten sposób wersja jest zapisywana do pliku `composer.json`: } ``` -Jednakże, numer wersji PHP jest również podany w innym miejscu pliku, w sekcji `require`. Podczas gdy pierwszy numer określa wersję, dla której zostaną zainstalowane pakiety, drugi mówi, dla jakiej wersji została napisana sama aplikacja. -(Oczywiście nie ma sensu, aby te wersje były różne, więc podwójny wpis jest redundancją). Wersję tę ustawiasz poleceniem: +Jednak numer wersji PHP podaje się jeszcze w innym miejscu pliku, a mianowicie w sekcji `require`. Podczas gdy pierwszy numer określa, dla jakiej wersji będą instalowane pakiety, drugi numer mówi, dla jakiej wersji jest napisana sama aplikacja. A według niego na przykład PhpStorm ustawia *PHP language level*. (Oczywiście nie ma sensu, aby te wersje się różniły, więc podwójny zapis jest niedopatrzeniem.) Tę wersję ustawisz poleceniem: ```shell composer require php 8.2.3 --no-update @@ -142,66 +141,72 @@ Lub bezpośrednio w pliku `composer.json`: ``` -Fałszywe raporty .[#toc-false-reports] -====================================== +Ignorowanie wersji PHP +====================== + +Pakiety zazwyczaj mają podaną zarówno najniższą wersję PHP, z którą są kompatybilne, jak i najwyższą, z którą są testowane. Jeśli zamierzasz używać wersji PHP jeszcze nowszej, na przykład w celu testowania, Composer odmówi zainstalowania takiego pakietu. Rozwiązaniem jest opcja `--ignore-platform-req=php+`, która spowoduje, że Composer będzie ignorować górne limity wymaganej wersji PHP. + -Podczas aktualizacji pakietów lub zmiany numerów wersji zdarzają się konflikty. Jeden pakiet ma wymagania, które są sprzeczne z innym i tak dalej. Jednakże, Composer czasami drukuje fałszywe wiadomości. Zgłasza konflikt, który tak naprawdę nie istnieje. W takim przypadku pomaga usunięcie pliku `composer.lock` i ponowna próba. +Fałszywe komunikaty +=================== -Jeśli komunikat o błędzie utrzymuje się, to znaczy, że jest on poważny i trzeba z niego wyczytać, co i jak należy zmodyfikować. +Podczas aktualizacji pakietów lub zmian numerów wersji zdarza się, że dochodzi do konfliktu. Jeden pakiet ma wymagania, które są sprzeczne z innym i podobnie. Composer jednak czasami wypisuje fałszywe komunikaty. Zgłasza konflikt, który realnie nie istnieje. W takim przypadku pomaga usunięcie pliku `composer.lock` i spróbowanie ponownie. +Jeśli komunikat błędu nadal się pojawia, to jest on myśleny poważnie i trzeba z niego wyczytać, co i jak zmodyfikować. -Packagist.org - centralne repozytorium .[#toc-packagist-org-global-repository] -============================================================================== -[Packagist |https://packagist.org] jest centralnym repozytorium, w którym Composer próbuje znaleźć pakiety, chyba że powiemy mu inaczej. Możemy tu również opublikować własne pakiety. +Packagist.org - centralne repozytorium +====================================== + +[Packagist |https://packagist.org] to główne repozytorium, w którym Composer stara się wyszukiwać pakiety, jeśli mu nie powiemy inaczej. Możemy tutaj publikować również własne pakiety. -Co jeśli nie chcemy korzystać z centralnego repozytorium? .[#toc-what-if-we-don-t-want-the-central-repository] --------------------------------------------------------------------------------------------------------------- +Co jeśli nie chcemy używać centralnego repozytorium? +---------------------------------------------------- -Jeśli mamy wewnętrzne aplikacje, których po prostu nie możemy hostować publicznie, tworzymy dla nich firmowe repozytorium. +Jeśli mamy wewnętrzne aplikacje firmowe, których po prostu nie możemy hostować publicznie, to stworzymy dla nich firmowe repozytorium. -Zobacz [oficjalną dokumentację |https://getcomposer.org/doc/05-repositories.md#repositories], aby dowiedzieć się więcej o repozytoriach. +Więcej na temat repozytoriów [w oficjalnej dokumentacji |https://getcomposer.org/doc/05-repositories.md#repositories]. -Autoloading .[#toc-autoloading] -=============================== +Autoloading +=========== -Kluczową cechą Composera jest to, że zapewnia on autoloading dla wszystkich swoich zainstalowanych klas, który rozpoczynasz poprzez włączenie pliku `vendor/autoload.php`. +Zasadniczą cechą Composera jest to, że zapewnia autoloading dla wszystkich przez niego zainstalowanych klas, który uruchamiasz przez dołączenie pliku `vendor/autoload.php`. -Jednakże możliwe jest również użycie Composera do załadowania innych klas spoza folderu `vendor`. Pierwszą opcją jest zlecenie Composerowi przeszukania zdefiniowanych folderów i podfolderów, znalezienie wszystkich klas i włączenie ich do autoloadera. Odbywa się to poprzez ustawienie `autoload > classmap` w `composer.json`: +Jednak możliwe jest używanie Composera również do ładowania innych klas spoza folderu `vendor`. Pierwszą możliwością jest pozwolenie Composerowi przeszukać zdefiniowane foldery i podfoldery, znaleźć wszystkie klasy i dołączyć je do autoloadera. Osiągniesz to ustawiając `autoload > classmap` w `composer.json`: ```js { "autoload": { "classmap": [ - "src/", # zahrne složku src/ a její podsložky + "src/", # dołączy folder src/ i jego podfoldery ] } } ``` -Następnie musisz uruchomić polecenie `composer dumpautoload` za każdym razem, gdy dokonujesz zmiany i masz ponownie wygenerowane tabele autoloader. Jest to niezwykle uciążliwe i zdecydowanie lepiej powierzyć to zadanie [RobotLoaderowi |robot-loader:], który wykonuje tę samą pracę automatycznie w tle i znacznie szybciej. +Następnie przy każdej zmianie trzeba uruchomić polecenie `composer dumpautoload` i pozwolić na przegenerowanie tabel autoloadingu. To jest niezwykle niewygodne i znacznie lepiej jest powierzyć to zadanie [RobotLoaderowi|robot-loader:], który tę samą czynność wykonuje automatycznie w tle i znacznie szybciej. -Inną możliwością jest zastosowanie się do [PSR-4 |https://www.php-fig.org/psr/psr-4/]. Mówiąc najprościej, jest to system, w którym przestrzenie nazw i nazwy klas odpowiadają strukturom katalogów i nazwom plików, więc na przykład `App\Router\RouterFactory` będzie w pliku `/path/to/App/Router/RouterFactory.php`. Przykładowa konfiguracja: +Drugą możliwością jest przestrzeganie [PSR-4|https://www.php-fig.org/psr/psr-4/]. Uproszczając, chodzi o system, w którym przestrzenie nazw i nazwy klas odpowiadają strukturze katalogów i nazwom plików, czyli np. `App\Core\RouterFactory` będzie w pliku `/path/to/App/Core/RouterFactory.php`. Przykład konfiguracji: ```js { "autoload": { "psr-4": { - "App\\": "app/" # jmenný prostor App\ je v adresáři app/ + "App\\": "app/" # przestrzeń nazw App\ jest w katalogu app/ } } } ``` -Zobacz [dokumentację Composera, aby |https://getcomposer.org/doc/04-schema.md#PSR-4] dowiedzieć się dokładnie, jak skonfigurować zachowanie. +Jak dokładnie skonfigurować zachowanie dowiesz się w [dokumentacji Composera|https://getcomposer.org/doc/04-schema.md#psr-4]. -Testowanie nowych wersji .[#toc-testing-new-versions] -===================================================== +Testowanie nowych wersji +======================== -Chcesz przetestować nową wersję rozwojową pakietu. Jak to zrobić? Po pierwsze, dodaj tę parę opcji do pliku `composer.json`, która pozwoli ci zainstalować wersje rozwojowe pakietów, ale będzie uciekać się do tego tylko wtedy, gdy nie ma kombinacji stabilnych wersji, które spełniają wymagania: +Chcesz przetestować nową wersję rozwojową pakietu. Jak to zrobić? Najpierw do pliku `composer.json` dodaj tę parę opcji, która pozwoli instalować wersje rozwojowe pakietów, jednak ucieknie się do tego tylko w przypadku, gdy nie istnieje żadna kombinacja stabilnych wersji, która spełniałaby wymagania: ```js { @@ -210,34 +215,33 @@ Chcesz przetestować nową wersję rozwojową pakietu. Jak to zrobić? Po pierws } ``` -Zaleca się również usunięcie pliku `composer.lock`, ponieważ czasami Composer niezrozumiale odmawia instalacji i to rozwiąże problem. +Następnie zalecamy usunięcie pliku `composer.lock`, czasami bowiem Composer niezrozumiale odmawia instalacji i to rozwiązuje problem. -Załóżmy, że jest to pakiet `nette/utils`, a nowa wersja ma numer 4.0. Instalujesz go za pomocą polecenia: +Powiedzmy, że chodzi o pakiet `nette/utils` i nowa wersja ma numer 4.0. Zainstalujesz ją poleceniem: ```shell composer require nette/utils:4.0.x-dev ``` -Możesz też zainstalować konkretną wersję, na przykład 4.0.0-RC2: +Lub możesz zainstalować konkretną wersję, na przykład 4.0.0-RC2: ```shell composer require nette/utils:4.0.0-RC2 ``` -Ale jeśli inny pakiet zależy od biblioteki i jest zablokowany do starszej wersji (np. `^3.1`), idealnie jest zaktualizować pakiet, aby działał z nową wersją. -Jeśli jednak chcesz po prostu obejść ograniczenie i zmusić Composera do zainstalowania wersji rozwojowej i udawania, że jest to starsza wersja (np. 3.1.6), możesz użyć słowa kluczowego `as`: +Gdy jednak od biblioteki zależy inny pakiet, który jest zablokowany na starszej wersji (np. `^3.1`), to idealnie jest zaktualizować pakiet, aby działał z nową wersją. Jeśli jednak chcesz tylko obejść ograniczenie i zmusić Composera do zainstalowania wersji rozwojowej i udawania, że jest to wersja starsza (np. 3.1.6), możesz użyć słowa kluczowego `as`: ```shell composer require nette/utils "4.0.x-dev as 3.1.6" ``` -Wywoływanie poleceń .[#toc-calling-commands] -============================================ +Wywoływanie poleceń +=================== -Możesz wywoływać własne, gotowe polecenia i skrypty poprzez Composera, tak jakby były one natywnymi poleceniami Composera. W przypadku skryptów, które znajdują się w folderze `vendor/bin`, nie ma potrzeby określania tego folderu. +Przez Composer można wywoływać własne przygotowane polecenia i skrypty, jakby były to natywne polecenia Composera. W przypadku skryptów, które znajdują się w folderze `vendor/bin`, nie trzeba podawać tego folderu. -Jako przykład zdefiniujmy w pliku `composer.json` skrypt, który uruchamia testy przy użyciu [Nette Tester |tester:]: +Jako przykład zdefiniujemy w pliku `composer.json` skrypt, który za pomocą [Nette Testera|tester:] uruchomi testy: ```js { @@ -247,19 +251,19 @@ Jako przykład zdefiniujmy w pliku `composer.json` skrypt, który uruchamia test } ``` -Następnie możemy uruchomić testy za pomocą `composer tester`. Polecenie możemy wywołać nawet jeśli nie znajdujemy się w korzeniu projektu, ale w podkatalogu. +Testy następnie uruchomimy za pomocą `composer tester`. Polecenie możemy wywołać również w przypadku, gdy nie jesteśmy w folderze głównym projektu, ale w którymś podkatalogu. -Wyślij podziękowania .[#toc-send-thanks] -======================================== +Wyślij podziękowania +==================== -Pokażemy Ci sztuczkę, która pozwoli Ci zadowolić autorów open source. Prosty sposób na rozgwiazdkowanie bibliotek, z których korzysta twój projekt na GitHubie. Wystarczy zainstalować bibliotekę `symfony/thanks`: +Pokażemy Ci sztuczkę, którą ucieszysz autorów open source. W prosty sposób dasz na GitHubie gwiazdkę bibliotekom, których używa Twój projekt. Wystarczy zainstalować bibliotekę `symfony/thanks`: ```shell composer global require symfony/thanks ``` -A potem go uruchomić: +A następnie uruchomić: ```shell composer thanks @@ -268,13 +272,11 @@ composer thanks Spróbuj! -Konfiguracja .[#toc-configuration] -================================== +Konfiguracja +============ -Composer jest ściśle zintegrowany z narzędziem do wersjonowania [Git |https://git-scm.com]. Jeśli nie masz go zainstalowanego, musisz powiedzieć Composerowi, aby go nie używał: +Composer jest ściśle powiązany z narzędziem do wersjonowania [Git |https://git-scm.com]. Jeśli go nie masz zainstalowanego, trzeba powiedzieć Composerowi, aby go nie używał: ```shell composer -g config preferred-install dist ``` - -{{sitename: Najlepsze praktyki}} diff --git a/best-practices/pl/creating-editing-form.texy b/best-practices/pl/creating-editing-form.texy index ac0921f88b..d5aeaa3c1c 100644 --- a/best-practices/pl/creating-editing-form.texy +++ b/best-practices/pl/creating-editing-form.texy @@ -1,16 +1,16 @@ -Formularz tworzenia i edycji rekordu -************************************ +Formularz do tworzenia i edycji rekordu +*************************************** .[perex] -Jak poprawnie zaimplementować dodawanie i edycję rekordu w Nette, używając tego samego formularza dla obu? +Jak poprawnie zaimplementować w Nette dodawanie i edycję rekordu, wykorzystując ten sam formularz do obu operacji? -W wielu przypadkach formularze dodawania i edycji rekordu są takie same, różniąc się jedynie etykietą na przycisku. Pokażemy przykłady prostych prezenterów, w których wykorzystujemy formularz najpierw do dodania rekordu, potem do jego edycji, a na koniec łączymy te dwa rozwiązania. +W wielu przypadkach formularze do dodawania i edycji rekordu są takie same, różnią się np. tylko etykietą na przycisku. Pokażemy przykłady prostych presenterów, gdzie formularz użyjemy najpierw do dodawania rekordu, potem do edycji, a na końcu połączymy oba rozwiązania. -Dodanie rekordu .[#toc-adding-a-record] ---------------------------------------- +Dodawanie rekordu +----------------- -Przykład prezentera używanego do dodania rekordu. Faktyczną pracę z bazą danych pozostawimy klasie `Facade`, której kod nie jest istotny dla tego przykładu. +Przykład presentera służącego do dodawania rekordu. Samą pracę z bazą danych pozostawimy klasie `Facade`, której kod nie jest istotny dla przykładu. ```php @@ -27,7 +27,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $form = new Form; - // ... dodać pola formularza ... + // ... dodajemy pola formularza ... $form->onSuccess[] = [$this, 'recordFormSucceeded']; return $form; @@ -35,8 +35,8 @@ class RecordPresenter extends Nette\Application\UI\Presenter public function recordFormSucceeded(Form $form, array $data): void { - $this->facade->add($data); // dodaj rekord do bazy danych - $this->flashMessage('Successfully added'); + $this->facade->add($data); // dodanie rekordu do bazy danych + $this->flashMessage('Pomyślnie dodano'); $this->redirect('...'); } @@ -48,10 +48,10 @@ class RecordPresenter extends Nette\Application\UI\Presenter ``` -Edycja zapisu .[#toc-editing-a-record] --------------------------------------- +Edycja rekordu +-------------- -Teraz zobaczmy jak wyglądałby prezenter używany do edycji nagrania: +Teraz pokażemy, jak wyglądałby presenter służący do edycji rekordu: ```php @@ -70,8 +70,8 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $record = $this->facade->get($id); if ( - !$record // sprawdzić istnienie rekordu - || !$this->facade->isEditAllowed(/*...*/) // sprawdzić uprawnienia + !$record // weryfikacja istnienia rekordu + || !$this->facade->isEditAllowed(/*...*/) // kontrola uprawnień ) { $this->error(); // błąd 404 } @@ -81,32 +81,32 @@ class RecordPresenter extends Nette\Application\UI\Presenter protected function createComponentRecordForm(): Form { - // sprawdź, czy akcja to 'edit' + // sprawdzamy, czy akcja to 'edit' if ($this->getAction() !== 'edit') { $this->error(); } $form = new Form; - // dodaj pola formularza ... + // ... dodajemy pola formularza ... - $form->setDefaults($this->record); // ustaw wartości domyślne + $form->setDefaults($this->record); // ustawienie wartości domyślnych $form->onSuccess[] = [$this, 'recordFormSucceeded']; return $form; } public function recordFormSucceeded(Form $form, array $data): void { - $this->facade->update($this->record->id, $data); // aktualizować rekord + $this->facade->update($this->record->id, $data); // aktualizacja rekordu $this->flashMessage('Pomyślnie zaktualizowano'); $this->redirect('...'); } } ``` -W metodzie *action*, która jest wywoływana zaraz na początku [cyklu życia prezentera |application:presenters#Life Cycle of Presenter], sprawdzamy istnienie rekordu oraz uprawnienia użytkownika do jego edycji. +W metodzie `actionEdit()`, która uruchamia się na samym początku [cyklu życia presentera |application:presenters#Cykl życia presentera], weryfikujemy istnienie rekordu i uprawnienia użytkownika do jego edycji. -Rekord przechowujemy we właściwości `$record`, tak aby był dostępny w metodzie `createComponentRecordForm()` do ustawiania wartości domyślnych, oraz `recordFormSucceeded()` do identyfikatora. Alternatywnym rozwiązaniem byłoby ustawienie wartości domyślnych bezpośrednio w `actionEdit()`, a wartość ID, która jest częścią adresu URL, jest pobierana za pomocą `getParameter('id')`: +Rekord zapisujemy do właściwości `$record`, aby mieć go dostępnego w metodzie `createComponentRecordForm()` w celu ustawienia wartości domyślnych, oraz w `recordFormSucceeded()` ze względu na ID. Alternatywnym rozwiązaniem byłoby ustawienie wartości domyślnych bezpośrednio w `actionEdit()` i pobranie wartości ID, która jest częścią URL, za pomocą `getParameter('id')`: ```php @@ -114,12 +114,12 @@ Rekord przechowujemy we właściwości `$record`, tak aby był dostępny w metod { $record = $this->facade->get($id); if ( - // zweryfikować istnienie i sprawdzić uprawnienia + // weryfikacja istnienia i kontrola uprawnień ) { $this->error(); } - // ustawić domyślne wartości formularza + // ustawienie wartości domyślnych formularza $this->getComponent('recordForm') ->setDefaults($record); } @@ -133,13 +133,13 @@ Rekord przechowujemy we właściwości `$record`, tak aby był dostępny w metod } ``` -Jednakże, i to powinno być **najważniejsze spostrzeżenie z całego kodu**, musimy upewnić się, kiedy tworzymy formularz, że akcja jest faktycznie `edit`. Ponieważ w przeciwnym razie walidacja w metodzie `actionEdit()` nie miałaby w ogóle miejsca! +Jednakże, i to powinno być **najważniejszą lekcją całego kodu**, musimy podczas tworzenia formularza upewnić się, że akcja to rzeczywiście `edit`. W przeciwnym razie weryfikacja w metodzie `actionEdit()` w ogóle by nie została przeprowadzona! -Ten sam formularz do dodawania i edycji .[#toc-same-form-for-adding-and-editing] --------------------------------------------------------------------------------- +Ten sam formularz do dodawania i edycji +--------------------------------------- -A teraz połączymy obu prezenterów w jednego. Moglibyśmy albo rozróżnić, która akcja jest tą w metodzie `createComponentRecordForm()` i odpowiednio skonfigurować formularz, albo zostawić to bezpośrednio w metodach akcji i pozbyć się warunku: +A teraz połączymy oba presentery w jeden. Albo moglibyśmy w metodzie `createComponentRecordForm()` rozróżnić, o którą akcję chodzi i na tej podstawie skonfigurować formularz, albo możemy to zostawić bezpośrednio metodom akcji i pozbyć się warunku: ```php @@ -160,47 +160,46 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $record = $this->facade->get($id); if ( - !$record // sprawdzić istnienie rekordu - || !$this->facade->isEditAllowed(/*...*/) // sprawdzić uprawnienia + !$record // weryfikacja istnienia rekordu + || !$this->facade->isEditAllowed(/*...*/) // kontrola uprawnień ) { $this->error(); // błąd 404 } $form = $this->getComponent('recordForm'); - $form->setDefaults($record); // ustawić wartości domyślne + $form->setDefaults($record); // ustawienie wartości domyślnych $form->onSuccess[] = [$this, 'editingFormSucceeded']; } protected function createComponentRecordForm(): Form { - // sprawdź, czy akcja to "dodaj" lub "edytuj + // sprawdzamy, czy akcja to 'add' lub 'edit' if (!in_array($this->getAction(), ['add', 'edit'])) { $this->error(); } $form = new Form; - // dodaj pola formularza ... + // ... dodajemy pola formularza ... return $form; } public function addingFormSucceeded(Form $form, array $data): void { - $this->facade->add($data); // dodać rekord do bazy danych - $this->flashMessage('Successfully added'); + $this->facade->add($data); // dodanie rekordu do bazy danych + $this->flashMessage('Pomyślnie dodano'); $this->redirect('...'); } public function editingFormSucceeded(Form $form, array $data): void { $id = (int) $this->getParameter('id'); - $this->facade->update($id, $data); // aktualizować rekord - $this->flashMessage('Successfully updated'); + $this->facade->update($id, $data); // aktualizacja rekordu + $this->flashMessage('Pomyślnie zaktualizowano'); $this->redirect('...'); } } ``` {{priority: -1}} -{{sitename: Najlepsze praktyki}} diff --git a/best-practices/pl/dynamic-snippets.texy b/best-practices/pl/dynamic-snippets.texy index c1ad93724d..67ac6ba7c0 100644 --- a/best-practices/pl/dynamic-snippets.texy +++ b/best-practices/pl/dynamic-snippets.texy @@ -1,7 +1,7 @@ -Dynamiczne fragmenty -******************** +Dynamiczne snippety +******************* -Dość często podczas tworzenia aplikacji zachodzi potrzeba wykonania operacji AJAX, np. nad poszczególnymi wierszami tabeli lub elementami listy. Jako przykład możemy wybrać listę artykułów, pozwalając zalogowanemu użytkownikowi wybrać ocenę "lubię/nie lubię" dla każdego z nich. Kod prezentera i odpowiadającego mu szablonu bez AJAX-a będzie wyglądał coś takiego (wymieniam najważniejsze snippety, kod zakłada istnienie serwisu do oznaczania ocen i otrzymywania kolekcji artykułów - konkretna implementacja nie jest istotna na potrzeby tego tutoriala): +Dość często podczas tworzenia aplikacji pojawia się potrzeba wykonywania operacji AJAX, na przykład na poszczególnych wierszach tabeli lub elementach listy. Jako przykład możemy wybrać listę artykułów, przy czym dla każdego z nich umożliwimy zalogowanemu użytkownikowi wybranie oceny "lubię/nie lubię". Kod presentera i odpowiadającego mu szablonu bez AJAX będzie wyglądał mniej więcej tak (podaję najważniejsze fragmenty, kod zakłada istnienie usługi do oznaczania ocen i pobierania kolekcji artykułów - konkretna implementacja nie jest ważna dla celów tego poradnika): ```php public function handleLike(int $articleId): void @@ -24,26 +24,26 @@ Szablon: <h2>{$article->title}</h2> <div class="content">{$article->content}</div> {if !$article->liked} - <a n:href="like! $article->id" class=ajax>to se mi líbí</a> + <a n:href="like! $article->id" class=ajax>lubię to</a> {else} - <a n:href="unlike! $article->id" class=ajax>už se mi to nelíbí</a> + <a n:href="unlike! $article->id" class=ajax>już mi się to nie podoba</a> {/if} </article> ``` -Ajaxizacja .[#toc-ajaxization] -============================== +Ajaxizacja +========== -Wyposażmy teraz tę prostą aplikację w AJAX. Zmiana oceny artykułu nie jest na tyle ważna, aby wymagała przekierowania, więc idealnie powinna być wykonana z AJAX w tle. Wykorzystamy [skrypt handler z dodatków ze |https://componette.org/vojtech-dobes/nette.ajax.js/] zwykłą konwencją, że linki AJAX mają klasę CSS `ajax`. +Teraz wyposażmy tę prostą aplikację w AJAX. Zmiana oceny artykułu nie jest na tyle ważna, aby musiało dojść do przekierowania, dlatego idealnie powinna odbywać się za pomocą AJAX w tle. Wykorzystamy [skrypt obsługi z dodatków |application:ajax#Naja] ze zwyczajową konwencją, że linki AJAX mają klasę CSS `ajax`. -Jednak jak to konkretnie zrobić? Nette oferuje 2 ścieżki: tzw. ścieżkę dynamicznych snippetów oraz ścieżkę komponentów. Oba mają swoje plusy i minusy, więc pokażemy je po kolei. +Jednak jak to zrobić konkretnie? Nette oferuje 2 ścieżki: ścieżkę tzw. dynamicznych snippetów i ścieżkę komponentów. Obie mają swoje zalety i wady, dlatego pokażemy je po kolei. -Ścieżka dynamicznych wycinków .[#toc-the-dynamic-snippets-way] -============================================================== +Ścieżka dynamicznych snippetów +============================== -W terminologii Latte, dynamiczny snippet jest szczególnym przypadkiem użycia makra `{snippet}`, w którym w nazwie snippetu użyta jest zmienna. Taki snippet nie może znajdować się byle gdzie w szablonie - musi być zawinięty przez snippet statyczny, czyli zwykły snippet, lub wewnątrz `{snippetArea}`. Moglibyśmy zmodyfikować nasz szablon w następujący sposób. +Dynamiczny snippet w terminologii Latte oznacza specyficzny przypadek użycia znacznika `{snippet}`, gdzie w nazwie snippetu używana jest zmienna. Taki snippet nie może znajdować się w szablonie byle gdzie - musi być opakowany statycznym snippetem, tj. zwykłym, lub wewnątrz `{snippetArea}`. Nasz szablon moglibyśmy zmodyfikować w następujący sposób. ```latte @@ -53,18 +53,18 @@ W terminologii Latte, dynamiczny snippet jest szczególnym przypadkiem użycia m <div class="content">{$article->content}</div> {snippet article-{$article->id}} {if !$article->liked} - <a n:href="like! $article->id" class=ajax>to se mi líbí</a> + <a n:href="like! $article->id" class=ajax>lubię to</a> {else} - <a n:href="unlike! $article->id" class=ajax>už se mi to nelíbí</a> + <a n:href="unlike! $article->id" class=ajax>już mi się to nie podoba</a> {/if} {/snippet} </article> {/snippet} ``` -Każdy artykuł definiuje teraz jeden snippet, który w tytule ma ID artykułu. Wszystkie te snippety są następnie zawijane razem przez pojedynczy snippet o nazwie `articlesContainer`. Jeśli pominiemy ten snippet zawijający, Latte zaalarmuje nas wyjątkiem. +Każdy artykuł definiuje teraz jeden snippet, który ma w nazwie ID artykułu. Wszystkie te snippety są następnie razem opakowane jednym snippetem o nazwie `articlesContainer`. Gdybyśmy pominęli ten opakowujący snippet, Latte poinformowałoby nas o tym wyjątkiem. -Pozostaje tylko dodać redraw do prezentera - wystarczy przerysować statyczny wrapper. +Pozostaje nam uzupełnić w prezenterze przerysowanie - wystarczy przerysować statyczną otoczkę. ```php public function handleLike(int $articleId): void @@ -72,18 +72,18 @@ public function handleLike(int $articleId): void $this->ratingService->saveLike($articleId, $this->user->id); if ($this->isAjax()) { $this->redrawControl('articlesContainer'); - // $this->redrawControl('article-' . $articleId); -- není potřeba + // $this->redrawControl('article-' . $articleId); -- nie jest potrzebne } else { $this->redirect('this'); } } ``` -Modyfikujemy również siostrzaną metodę `handleUnlike()`, a AJAX działa! +Podobnie zmodyfikujemy również siostrzaną metodę `handleUnlike()`, i AJAX działa! -Jest jednak jeden minus tego rozwiązania. Jeśli zbadamy bardziej, jak działa żądanie AJAX, okaże się, że chociaż aplikacja wygląda oszczędnie na zewnątrz (zwraca tylko pojedynczy snippet dla danego artykułu), w rzeczywistości renderuje wszystkie snippety na serwerze. Umieścił on w naszym payloadzie pożądany snippet, a pozostałe wyrzucił (a więc, całkiem niepotrzebnie, pobrał je również z bazy danych). +Rozwiązanie ma jednak jedną wadę. Gdybyśmy bardziej zbadali, jak przebiega żądanie AJAX, odkrylibyśmy, że chociaż na zewnątrz aplikacja wydaje się oszczędna (zwraca tylko jeden snippet dla danego artykułu), w rzeczywistości na serwerze wyrenderowała wszystkie snippety. Pożądany snippet umieściła w payloadzie, a pozostałe odrzuciła (całkowicie niepotrzebnie je również pobrała z bazy danych). -Aby zoptymalizować ten proces, będziemy musieli zainterweniować w miejscu, w którym przekazujemy kolekcję `$articles` do szablonu (powiedzmy w metodzie `renderDefault()`). Wykorzystamy fakt, że przetwarzanie sygnału odbywa się przed metodami `render<Something>`: +Aby zoptymalizować ten proces, będziemy musieli interweniować tam, gdzie przekazujemy do szablonu kolekcję `$articles` (powiedzmy w metodzie `renderDefault()`). Wykorzystamy fakt, że przetwarzanie sygnałów odbywa się przed metodami `render<Something>`: ```php public function handleLike(int $articleId): void @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connection->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,18 +101,18 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connection->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` -Teraz, gdy sygnał jest przetwarzany, zamiast przekazywać do szablonu kolekcję ze wszystkimi artykułami, przekaże tylko tablicę z pojedynczym artykułem - tym, który chcemy wyrenderować i wysłać w payload do przeglądarki. Tak więc `{foreach}` zostanie przekazany tylko raz i żadne dodatkowe snippety nie będą renderowane. +Teraz podczas przetwarzania sygnału do szablonu przekazywana jest zamiast kolekcji ze wszystkimi artykułami tylko tablica z jednym artykułem - tym, który chcemy wyrenderować i wysłać w payloadzie do przeglądarki. `{foreach}` przebiegnie więc tylko raz i żadne dodatkowe snippety się nie wyrenderują. -Ścieżka komponentu .[#toc-component-way] -======================================== +Ścieżka komponentów +=================== -Zupełnie inne rozwiązanie pozwala uniknąć dynamicznych snippetów. Sztuką jest przeniesienie całej logiki do osobnego komponentu - od teraz nie będziemy mieli prezentera, który zajmie się wprowadzaniem oceny, ale dedykowaną `LikeControl`. Klasa będzie wyglądała tak (dodatkowo będzie zawierała metody `render`, `handleUnlike` itd.): +Zupełnie inny sposób rozwiązania unika dynamicznych snippetów. Sztuczka polega na przeniesieniu całej logiki do osobnego komponentu - od teraz o wprowadzanie ocen nie będzie dbał presenter, ale dedykowany `LikeControl`. Klasa będzie wyglądać następująco (oprócz tego będzie zawierać również metody `render`, `handleUnlike` itd.): ```php class LikeControl extends Nette\Application\UI\Control @@ -139,26 +139,26 @@ Szablon komponentu: ```latte {snippet} {if !$article->liked} - <a n:href="like!" class=ajax>to se mi líbí</a> + <a n:href="like!" class=ajax>lubię to</a> {else} - <a n:href="unlike!" class=ajax>už se mi to nelíbí</a> + <a n:href="unlike!" class=ajax>już mi się to nie podoba</a> {/if} {/snippet} ``` -Oczywiście zmienimy szablon widoku i będziemy musieli dodać fabrykę do prezentera. Ponieważ komponent będziemy tworzyć tyle razy, ile artykułów pobierzemy z bazy danych, do jego "pomnożenia" użyjemy klasy [Multiplier |application:Multiplier]. +Oczywiście zmieni nam się szablon widoku i do presentera będziemy musieli dodać fabrykę. Ponieważ komponent utworzymy tyle razy, ile artykułów pobierzemy z bazy danych, wykorzystamy do jego "rozmnożenia" klasę [Multiplier |application:Multiplier]. ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); } ``` -Widok szablonu zostanie zredukowany do niezbędnego minimum (i całkowicie pozbawiony snippetów!): +Szablon widoku zmniejszy się do niezbędnego minimum (i całkowicie pozbawiony snippetów!): ```latte <article n:foreach="$articles as $article"> @@ -168,7 +168,6 @@ Widok szablonu zostanie zredukowany do niezbędnego minimum (i całkowicie pozba </article> ``` -Jesteśmy prawie gotowi: aplikacja będzie teraz działać w sposób zbliżony do AJAX-a. Tutaj również musimy zoptymalizować aplikację, ponieważ ze względu na wykorzystanie Nette Database, przetwarzanie sygnału będzie niepotrzebnie ładować wszystkie artykuły z bazy zamiast jednego. Zaletą jest jednak to, że nie będzie renderowania, ponieważ tylko nasz komponent jest faktycznie renderowany. +Mamy prawie gotowe: aplikacja teraz będzie działać AJAXowo. Również tutaj czeka nas optymalizacja aplikacji, ponieważ ze względu na użycie Nette Database podczas przetwarzania sygnału niepotrzebnie ładowane są wszystkie artykuły z bazy danych zamiast jednego. Zaletą jest jednak to, że nie dojdzie do ich renderowania, ponieważ wyrenderuje się rzeczywiście tylko nasz komponent. {{priority: -1}} -{{sitename: Najlepsze praktyki}} diff --git a/best-practices/pl/editors-and-tools.texy b/best-practices/pl/editors-and-tools.texy index fee21ea5bc..7488231779 100644 --- a/best-practices/pl/editors-and-tools.texy +++ b/best-practices/pl/editors-and-tools.texy @@ -2,39 +2,39 @@ Edytory i narzędzia ******************* .[perex] -Możesz być zdolnym programistą, ale tylko dzięki dobrym narzędziom staniesz się mistrzem. W tym rozdziale znajdziesz wskazówki dotyczące ważnych narzędzi, edytorów i wtyczek. +Możesz być biegłym programistą, ale dopiero z dobrymi narzędziami staniesz się mistrzem. W tym rozdziale znajdziesz wskazówki dotyczące ważnych narzędzi, edytorów i wtyczek. -Edytor IDE .[#toc-ide-editor] -============================= +Edytor IDE +========== -Zdecydowanie zalecamy używanie w pełni funkcjonalnego IDE do programowania, takiego jak PhpStorm, NetBeans, VS Code, a nie tylko edytora tekstu z obsługą PHP. Różnica jest naprawdę kluczowa. Nie ma powodu, aby zadowalać się klasycznym edytorem z kolorowaniem składni, ponieważ nie osiąga on możliwości IDE z dokładną sugestią kodu, który może refaktoryzować kod i więcej. Niektóre IDE są płatne, inne darmowe. +Zdecydowanie zalecamy używanie do programowania pełnoprawnego IDE, takiego jak PhpStorm, NetBeans, VS Code, a nie tylko edytora tekstu z obsługą PHP. Różnica jest naprawdę zasadnicza. Nie ma powodu zadowalać się zwykłym edytorem, który co prawda potrafi kolorować składnię, ale nie dorównuje możliwościom zaawansowanego IDE, które precyzyjnie podpowiada, pilnuje błędów, potrafi refaktoryzować kod i wiele więcej. Niektóre IDE są płatne, inne nawet darmowe. **NetBeans IDE** ma wbudowane wsparcie dla Nette, Latte i NEON. -**PhpStorm**: zainstaluj te wtyczki w `Settings > Plugins > Marketplace`: -- Pomocnicy frameworka Nette +**PhpStorm**: zainstaluj te wtyczki w `Settings > Plugins > Marketplace` +- Nette framework helpers - Latte -- Wsparcie dla NEON -- Tester Nette +- NEON support +- Nette Tester -**Kod VS**: znajdź wtyczkę "Nette Latte + Neon" na rynku. +**VS Code**: znajdź w marketplace wtyczkę "Nette Latte + Neon". -Zamieść również link Tracy to the editor. Gdy pojawi się strona z błędami, można wtedy kliknąć na nazwy plików i otworzą się one w edytorze z kursorem w odpowiedniej linii. Dowiedz się, [jak skonfigurować system |tracy:open-files-in-ide]. +Połącz również Tracy z edytorem. Podczas wyświetlania strony błędu będzie można kliknąć na nazwy plików, a te otworzą się w edytorze z kursorem na odpowiedniej linii. Przeczytaj, [jak skonfigurować system|tracy:open-files-in-ide]. -PHPStan .[#toc-phpstan] -======================= +PHPStan +======= -PHPStan to narzędzie, które wykrywa błędy logiczne w Twoim kodzie, zanim go uruchomisz. +PHPStan to narzędzie, które wykrywa błędy logiczne w kodzie, zanim go uruchomisz. -Instalujemy go za pomocą programu Composer: +Zainstalujemy go za pomocą Composera: ```shell composer require --dev phpstan/phpstan-nette ``` -Utwórz plik konfiguracyjny w projekcie `phpstan.neon`: +Utworzymy w projekcie plik konfiguracyjny `phpstan.neon`: ```neon includes: @@ -47,40 +47,38 @@ parameters: level: 5 ``` -A potem niech analizuje klasy w folderze `app/`: +A następnie zlecimy mu analizę klas w folderze `app/`: ```shell vendor/bin/phpstan analyse app ``` -Obszerną dokumentację można znaleźć bezpośrednio na [stronie PHPStan |https://phpstan.org]. +Wyczerpującą dokumentację znajdziesz bezpośrednio na [stronie PHPStan |https://phpstan.org]. -Code Checker .[#toc-code-checker] -================================= +Code Checker +============ -[Code Checker |code-checker:] sprawdzi i ewentualnie naprawi niektóre błędy formalne w Twoim kodzie źródłowym: +[Code Checker|code-checker:] sprawdza i ewentualnie poprawia niektóre błędy formalne w twoich kodach źródłowych: -- usuwa [BOM |nette:glossary#bom] -- sprawdza ważność szablonów [Latte |latte:] -- sprawdza ważność stron `.neon`, `.php` oraz `.json` -- kontrole dla [znaków kontrolnych |nette:glossary#Control-Characters] -- sprawdza czy plik jest zakodowany w UTF-8 -- sprawdza, czy nie ma błędnie napisanej strony `/* @anotace */` (brak gwiazdki) -- usuwa terminator `?>` z plików PHP -- usuwa prawe spacje i niepotrzebne linie na końcu pliku -- normalizuje separatory linii do systemowych separatorów linii (jeśli podano opcję `-l`) +- usuwa [BOM |nette:glossary#BOM] +- sprawdza poprawność szablonów [Latte |latte:] +- sprawdza poprawność plików `.neon`, `.php` i `.json` +- sprawdza występowanie [znaków kontrolnych |nette:glossary#Znaki kontrolne] +- sprawdza, czy plik jest kodowany w UTF-8 +- sprawdza błędnie zapisane `/* @anotace */` (brakuje gwiazdki) +- usuwa kończące `?>` w plikach PHP +- usuwa spacje na końcu linii i zbędne linie na końcu pliku +- normalizuje separatory linii do systemowych (jeśli podasz opcję `-l`) -Kompozytor .[#toc-composer] -=========================== +Composer +======== -[Composer |Composer] to narzędzie do zarządzania zależnościami w PHP. Pozwala nam zadeklarować dowolnie złożone zależności biblioteczne, a następnie instaluje je w naszym projekcie za nas. +[Composer|best-practices:composer] to narzędzie do zarządzania zależnościami w PHP. Pozwala nam deklarować dowolnie złożone zależności poszczególnych bibliotek, a następnie instaluje je za nas w naszym projekcie. -Sprawdzanie wymagań .[#toc-requirements-checker] -================================================ +Requirements Checker +==================== -Było to narzędzie, które testowało środowisko uruchomieniowe serwera i informowało, czy (i w jakim zakresie) można wykorzystać framework. Obecnie Nette można używać na każdym serwerze, który posiada minimalną wymaganą wersję PHP. - -{{sitename: Najlepsze praktyki}} +Było to narzędzie, które testowało środowisko uruchomieniowe serwera i informowało, czy (i w jakim stopniu) można używać frameworka. Obecnie Nette można używać na każdym serwerze, który ma minimalną wymaganą wersję PHP. diff --git a/best-practices/pl/form-reuse.texy b/best-practices/pl/form-reuse.texy index 46ee665dec..c4b5179419 100644 --- a/best-practices/pl/form-reuse.texy +++ b/best-practices/pl/form-reuse.texy @@ -1,16 +1,16 @@ -Ponowne użycie formularzy w wielu miejscach -******************************************* +Wielokrotne użycie formularzy w wielu miejscach +*********************************************** .[perex] -W Nette masz kilka opcji, aby ponownie wykorzystać ten sam formularz w wielu miejscach bez duplikowania kodu. W tym artykule omówimy różne rozwiązania, w tym te, których powinieneś unikać. +W Nette masz do dyspozycji kilka opcji, jak użyć tego samego formularza w wielu miejscach i nie duplikować kodu. W tym artykule pokażemy różne rozwiązania, w tym te, których powinieneś unikać. -Fabryka formularzy .[#toc-form-factory] -======================================= +Fabryka formularzy +================== -Jednym z podstawowych podejść do używania tego samego komponentu w wielu miejscach jest stworzenie metody lub klasy, która generuje komponent, a następnie wywołanie tej metody w różnych miejscach w aplikacji. Taka metoda lub klasa nazywana jest *factory*. Proszę nie mylić z wzorcem projektowym *factory method*, który opisuje specyficzny sposób korzystania z fabryk i nie jest związany z tym tematem. +Jednym z podstawowych podejść do użycia tego samego komponentu w wielu miejscach jest utworzenie metody lub klasy, która generuje ten komponent, a następnie wywoływanie tej metody w różnych miejscach aplikacji. Taka metoda lub klasa nazywana jest *fabryką*. Proszę nie mylić z wzorcem projektowym *factory method*, który opisuje specyficzny sposób wykorzystania fabryk i nie jest związany z tym tematem. -Jako przykład, stwórzmy fabrykę, która zbuduje formularz edycji: +Jako przykład stworzymy fabrykę, która będzie budować formularz edycyjny: ```php use Nette\Application\UI\Form; @@ -20,22 +20,22 @@ class FormFactory public function createEditForm(): Form { $form = new Form; - $form->addText('title', 'Title:'); - // dodatkowe pola formularza są dodane tutaj - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Tytuł:'); + // tutaj dodawane są kolejne pola formularza + $form->addSubmit('send', 'Wyślij'); return $form; } } ``` -Teraz można użyć tej fabryki w różnych miejscach w aplikacji, na przykład w prezenterach lub komponentach. A zrobimy to poprzez [zażądanie jej jako zależności |dependency-injection:passing-dependencies]. Więc najpierw zapiszemy klasę do pliku konfiguracyjnego: +Teraz możesz użyć tej fabryki w różnych miejscach w swojej aplikacji, na przykład w presenterach lub komponentach. A to tak, że [zażądamy jej jako zależności|dependency-injection:passing-dependencies]. Najpierw więc zapiszemy klasę do pliku konfiguracyjnego: ```neon services: - FormFactory ``` -A potem używamy jej w prezenterze: +A potem użyjemy jej w prezenterze: ```php @@ -50,14 +50,14 @@ class MyPresenter extends Nette\Application\UI\Presenter { $form = $this->formFactory->createEditForm(); $form->onSuccess[] = function () { - // przetwarzanie przesłanych danych + // przetwarzanie wysłanych danych }; return $form; } } ``` -Możesz rozszerzyć fabrykę formularzy o dodatkowe metody, aby stworzyć inne typy formularzy, aby dopasować je do swojej aplikacji. I, oczywiście, możesz dodać metodę, która tworzy podstawowy formularz bez elementów, z którego będą korzystać inne metody: +Fabrykę formularzy możesz rozszerzyć o kolejne metody do tworzenia innych rodzajów formularzy zgodnie z potrzebami Twojej aplikacji. I oczywiście możemy dodać również metodę, która stworzy podstawowy formularz bez elementów, a tę będą wykorzystywać inne metody: ```php class FormFactory @@ -71,21 +71,21 @@ class FormFactory public function createEditForm(): Form { $form = $this->createForm(); - $form->addText('title', 'Title:'); - // dodatkowe pola formularza są dodane tutaj - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Tytuł:'); + // tutaj dodawane są kolejne pola formularza + $form->addSubmit('send', 'Wyślij'); return $form; } } ``` -Metoda `createForm()` nie robi jeszcze nic użytecznego, ale to się szybko zmieni. +Metoda `createForm()` na razie nie robi nic użytecznego, ale to się szybko zmieni. -Zależności fabryczne .[#toc-factory-dependencies] -================================================= +Zależności fabryki +================== -Z czasem okaże się, że potrzebujemy, aby formularze były wielojęzyczne. Oznacza to, że musimy skonfigurować [translator |forms:rendering#Translating] dla wszystkich formularzy. Aby to zrobić, modyfikujemy klasę `FormFactory`, aby zaakceptowała obiekt `Translator` jako zależność w konstruktorze i przekazała go do formularza: +Z czasem okaże się, że potrzebujemy, aby formularze były wielojęzyczne. Oznacza to, że wszystkim formularzom musimy ustawić tzw. [translator |forms:rendering#Tłumaczenie]. W tym celu zmodyfikujemy klasę `FormFactory` tak, aby przyjmowała obiekt `Translator` jako zależność w konstruktorze, i przekażemy go formularzowi: ```php use Nette\Localization\Translator; @@ -104,18 +104,17 @@ class FormFactory return $form; } - //... + // ... } ``` -Ponieważ metoda `createForm()` jest wywoływana również przez inne metody tworzące konkretne formularze, musimy ustawić translator tylko w tej metodzie. I gotowe. Nie trzeba zmieniać żadnego kodu prezentera lub komponentu, co jest świetne. +Ponieważ metodę `createForm()` wywołują również inne metody tworzące specyficzne formularze, wystarczy translator ustawić tylko w niej. I gotowe. Nie ma potrzeby zmieniać kodu żadnego presentera ani komponentu, co jest świetne. -Więcej klas fabrycznych .[#toc-more-factory-classes] -==================================================== +Wiele klas fabryk +================= -Alternatywnie, możesz stworzyć wiele klas dla każdego formularza, który chcesz użyć w swojej aplikacji. -Takie podejście może zwiększyć czytelność kodu i ułatwić zarządzanie formularzami. Pozostaw oryginalną `FormFactory`, aby stworzyć tylko czysty formularz z podstawową konfiguracją (na przykład z obsługą tłumaczeń) i utwórz nową fabrykę `EditFormFactory` dla formularza edycji. +Alternatywnie możesz utworzyć wiele klas dla każdego formularza, który chcesz użyć w swojej aplikacji. Takie podejście może zwiększyć czytelność kodu i ułatwić zarządzanie formularzami. Pierwotną `FormFactory` pozostawimy do tworzenia tylko czystego formularza z podstawową konfiguracją (na przykład ze wsparciem tłumaczeń), a dla formularza edycyjnego stworzymy nową fabrykę `EditFormFactory`. ```php class FormFactory @@ -145,40 +144,39 @@ class EditFormFactory public function create(): Form { $form = $this->formFactory->create(); - // dodatkowe pola formularza są dodawane tutaj - $form->addSubmit('send', 'Save'); + // tutaj dodawane są kolejne pola formularza + $form->addSubmit('send', 'Wyślij'); return $form; } } ``` -Bardzo ważne jest, że wiązanie między klasami `FormFactory` i `EditFormFactory` jest realizowane przez kompozycję, a nie dziedziczenie obiektów: +Bardzo ważne jest, aby powiązanie między klasami `FormFactory` i `EditFormFactory` było realizowane przez [kompozycję |nette:introduction-to-object-oriented-programming#Kompozycja], a nie przez [dziedziczenie obiektowe |nette:introduction-to-object-oriented-programming#Dziedziczenie]: ```php -// ⛔ NIE! DZIEDZICZENIE NIE NALEŻY DO TEGO MIEJSCA +// ⛔ TAK NIE! DZIEDZICZENIE TU NIE PASUJE class EditFormFactory extends FormFactory { public function create(): Form { $form = parent::create(); - $form->addText('title', 'Title:'); - // tutaj dodaje się dodatkowe pola formularza - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Tytuł:'); + // tutaj dodawane są kolejne pola formularza + $form->addSubmit('send', 'Wyślij'); return $form; } } ``` -Używanie dziedziczenia w tym przypadku byłoby całkowicie przeciwne do zamierzonego. Bardzo szybko napotkałbyś problemy. Na przykład, gdybyś chciał dodać parametry do metody `create()`; PHP zgłosiłoby błąd, że jej podpis jest inny niż rodzica. -Albo podczas przekazywania zależności do klasy `EditFormFactory` poprzez konstruktor. To spowodowałoby coś, co nazywamy [piekłem konstru |dependency-injection:passing-dependencies#Constructor hell]ktora. +Użycie dziedziczenia byłoby w tym przypadku całkowicie kontrproduktywne. Na problemy napotkałbyś bardzo szybko. Na przykład w chwili, gdy chciałbyś dodać parametry do metody `create()`; PHP zgłosiłoby błąd, że jej sygnatura różni się od rodzicielskiej. Lub przy przekazywaniu zależności do klasy `EditFormFactory` przez konstruktor. Powstałaby sytuacja, którą nazywamy [constructor hell |dependency-injection:passing-dependencies#Constructor hell]. -Ogólnie rzecz biorąc, lepiej jest preferować kompozycję niż dziedziczenie. +Ogólnie lepiej jest preferować [kompozycję nad dziedziczeniem |dependency-injection:faq#Dlaczego preferuje się kompozycję nad dziedziczeniem]. -Obsługa formularzy .[#toc-form-handling] -======================================== +Obsługa formularza +================== -Obsługa formularza, która jest wywoływana po pomyślnym przesłaniu danych, może być również częścią klasy fabrycznej. Jego działanie będzie polegało na przekazaniu przesłanych danych do modelu w celu ich przetworzenia. Wszelkie błędy zostaną przekazane z [powrotem do |forms:validation#Processing Errors] formularza. Model w poniższym przykładzie jest reprezentowany przez klasę `Facade`: +Obsługa formularza, która jest wywoływana po pomyślnym wysłaniu, może być również częścią klasy fabryki. Będzie działać tak, że przekaże wysłane dane do modelu w celu przetworzenia. Ewentualne błędy [przekazuje z powrotem |forms:validation#Błędy podczas przetwarzania] do formularza. Model w poniższym przykładzie reprezentuje klasa `Facade`: ```php class EditFormFactory @@ -192,9 +190,9 @@ class EditFormFactory public function create(): Form { $form = $this->formFactory->create(); - $form->addText('title', 'Title:'); - // tutaj dodaje się dodatkowe pola formularza - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Tytuł:'); + // tutaj dodawane są kolejne pola formularza + $form->addSubmit('send', 'Wyślij'); $form->onSuccess[] = [$this, 'processForm']; return $form; } @@ -202,7 +200,7 @@ class EditFormFactory public function processForm(Form $form, array $data): void { try { - // przetwarzanie przesłanych danych + // przetwarzanie wysłanych danych $this->facade->process($data); } catch (AnyModelException $e) { @@ -212,7 +210,7 @@ class EditFormFactory } ``` -Niech prezenter sam zajmie się przekierowaniem. Doda on kolejny handler do zdarzenia `onSuccess`, który wykona przekierowanie. Dzięki temu formularz będzie mógł być używany w różnych prezenterach, a każdy z nich może przekierować do innej lokalizacji. +Samo przekierowanie pozostawimy jednak prezenterowi. Ten doda do zdarzenia `onSuccess` kolejny handler, który wykona przekierowanie. Dzięki temu będzie można użyć formularza w różnych prezenterach i w każdym przekierować gdzie indziej. ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -226,7 +224,7 @@ class MyPresenter extends Nette\Application\UI\Presenter { $form = $this->formFactory->create(); $form->onSuccess[] = function () { - $this->flashMessage('Záznam byl uložen'); + $this->flashMessage('Rekord został zapisany'); $this->redirect('Homepage:'); }; return $form; @@ -234,39 +232,38 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Rozwiązanie to wykorzystuje właściwość formularzy polegającą na tym, że po wywołaniu `addError()` na formularzu lub jego elemencie nie jest wywoływany następny handler `onSuccess`. +To rozwiązanie wykorzystuje właściwość formularzy, że gdy na formularzu lub jego elemencie zostanie wywołane `addError()`, kolejne handlery `onSuccess` nie są już wywoływane. -Dziedziczenie po klasie Form .[#toc-inheriting-from-the-form-class] -=================================================================== +Dziedziczenie od klasy Form +=========================== -Zbudowany formularz nie powinien być dzieckiem formularza. Innymi słowy, nie używaj tego rozwiązania: +Zbudowany formularz nie powinien być potomkiem formularza. Innymi słowy, nie używaj tego rozwiązania: ```php -// ⛔ NIE! DZIEDZICZENIE NIE NALEŻY DO TEGO MIEJSCA +// ⛔ TAK NIE! DZIEDZICZENIE TU NIE PASUJE class EditForm extends Form { public function __construct(Translator $translator) { parent::__construct(); - $form->addText('title', 'Title:'); - // tutaj dodaje się dodatkowe pola formularza - $form->addSubmit('send', 'Save'); - $form->setTranslator($translator); + $this->addText('title', 'Tytuł:'); + // tutaj dodawane są kolejne pola formularza + $this->addSubmit('send', 'Wyślij'); + $this->setTranslator($translator); } } ``` Zamiast budować formularz w konstruktorze, użyj fabryki. -Ważne jest, aby zdać sobie sprawę, że klasa `Form` jest przede wszystkim narzędziem do składania formularza, czyli konstruktorem formularzy. A złożony formularz można uznać za jej produkt. Produkt nie jest jednak szczególnym przypadkiem konstruktora; nie ma między nimi relacji *is a*, która stanowi podstawę dziedziczenia. +Należy zdać sobie sprawę, że klasa `Form` jest przede wszystkim narzędziem do budowania formularza, czyli *form builder*. A zbudowany formularz można rozumieć jako jej produkt. Jednak produkt nie jest specyficznym przypadkiem buildera, nie ma między nimi relacji *is a* stanowiącej podstawę dziedziczenia. -Komponent formularza .[#toc-form-component] -=========================================== +Komponent z formularzem +======================= -Zupełnie innym podejściem jest stworzenie [komponentu |application:components], który zawiera formularz. Daje to nowe możliwości, na przykład renderowanie formularza w określony sposób, ponieważ komponent zawiera szablon. -Albo sygnały mogą być użyte do komunikacji AJAX i ładowania informacji do formularza, na przykład do podpowiedzi itp. +Całkowicie inne podejście stanowi tworzenie [komponentu|application:components], którego częścią jest formularz. Daje to nowe możliwości, na przykład renderowanie formularza w specyficzny sposób, ponieważ częścią komponentu jest również szablon. Lub można wykorzystać sygnały do komunikacji AJAX i doładowywania informacji do formularza, na przykład do podpowiadania, itd. ```php @@ -284,9 +281,9 @@ class EditControl extends Nette\Application\UI\Control protected function createComponentForm(): Form { $form = new Form; - $form->addText('title', 'Title:'); - // tutaj dodaje się dodatkowe pola formularza - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Tytuł:'); + // tutaj dodawane są kolejne pola formularza + $form->addSubmit('send', 'Wyślij'); $form->onSuccess[] = [$this, 'processForm']; return $form; @@ -295,7 +292,7 @@ class EditControl extends Nette\Application\UI\Control public function processForm(Form $form, array $data): void { try { - // przetwarzanie przesłanych danych + // przetwarzanie wysłanych danych $this->facade->process($data); } catch (AnyModelException $e) { @@ -303,13 +300,13 @@ class EditControl extends Nette\Application\UI\Control return; } - // wywoływanie zdarzeń + // wywołanie zdarzenia $this->onSave($this, $data); } } ``` -Stwórzmy fabrykę, która będzie produkowała ten komponent. Wystarczy, że [napiszemy jej interfejs |application:components#Components with Dependencies]: +Stworzymy jeszcze fabrykę, która będzie produkować ten komponent. Wystarczy [zapisać jej interfejs |application:components#Komponenty z zależnościami]: ```php interface EditControlFactory @@ -318,14 +315,14 @@ interface EditControlFactory } ``` -I dodać go do pliku konfiguracyjnego: +I dodać do pliku konfiguracyjnego: ```neon services: - EditControlFactory ``` -A teraz możemy zażądać fabryki i użyć jej w prezenterze: +A teraz już możemy zażądać fabryki i użyć jej w prezenterze: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -335,13 +332,13 @@ class MyPresenter extends Nette\Application\UI\Presenter ) { } - protected function createComponentEditForm(): Form + protected function createComponentEditForm(): EditControl { $control = $this->controlFactory->create(); $control->onSave[] = function (EditControl $control, $data) { $this->redirect('this'); - // lub przekierować na wynik edycji, np: + // lub przekierowujemy na wynik edycji, np.: // $this->redirect('detail', ['id' => $data->id]); }; @@ -349,5 +346,3 @@ class MyPresenter extends Nette\Application\UI\Presenter } } ``` - -{{sitename: Najlepsze praktyki}} diff --git a/best-practices/pl/inject-method-attribute.texy b/best-practices/pl/inject-method-attribute.texy index 8c36b2ffbe..a5889d2155 100644 --- a/best-practices/pl/inject-method-attribute.texy +++ b/best-practices/pl/inject-method-attribute.texy @@ -1,21 +1,18 @@ -Metody i atrybuty wstrzykiwania -******************************* +Metody i atrybuty inject +************************ .[perex] -W tym artykule skupimy się na różnych sposobach przekazywania zależności do prezenterów w frameworku Nette. Porównamy preferowaną metodę, jaką jest konstruktor, z innymi opcjami, takimi jak `inject` metody i atrybuty. +W tym artykule skupimy się na różnych sposobach przekazywania zależności do presenterów w frameworku Nette. Porównamy preferowany sposób, którym jest konstruktor, z innymi możliwościami, takimi jak metody i atrybuty `inject`. -Również dla prezenterów przekazywanie zależności za pomocą [konstruktora |dependency-injection:passing-dependencies#Constructor Injection] jest preferowanym sposobem. -Jednakże, jeśli stworzysz wspólnego przodka, z którego dziedziczą inne prezentery (np. BasePresenter), a ten przodek również posiada zależności, pojawia się problem, który nazywamy [piekłem |dependency-injection:passing-dependencies#Constructor hell] konstruktora. -Można to obejść, stosując alternatywne metody, które obejmują metody wstrzykiwania i atrybuty (adnotacje). +Również dla presenterów obowiązuje zasada, że przekazywanie zależności za pomocą [konstruktora |dependency-injection:passing-dependencies#Przekazywanie przez konstruktor] jest preferowaną ścieżką. Jeśli jednak tworzysz wspólnego przodka, z którego dziedziczą inne presentery (np. `BasePresenter`), i ten przodek również ma zależności, pojawia się problem, który nazywamy [constructor hell |dependency-injection:passing-dependencies#Constructor hell]. Można go obejść za pomocą alternatywnych ścieżek, które stanowią metody i atrybuty (dawniej adnotacje) `inject`. -Metody `inject*()` .[#toc-inject-methods] -========================================= +Metody `inject*()` +================== -Jest to forma przekazywania zależności za pomocą [seterów |dependency-injection:passing-dependencies#Setter Injection]. Nazwy tych seterów zaczynają się od przedrostka inject. -Nette DI automatycznie wywołuje tak nazwane metody zaraz po utworzeniu instancji prezentera i przekazuje do nich wszystkie wymagane zależności. Dlatego muszą być one zadeklarowane jako publiczne. +Jest to forma przekazywania zależności przez [setter |dependency-injection:passing-dependencies#Przekazywanie przez setter]. Nazwa tych setterów zaczyna się prefiksem `inject`. Nette DI automatycznie wywołuje tak nazwane metody zaraz po utworzeniu instancji presentera i przekazuje im wszystkie wymagane zależności. Muszą być zatem zadeklarowane jako public. -`inject*()` metody można uznać za rodzaj rozszerzenia konstruktora na wiele metod. Dzięki temu `BasePresenter` może przyjmować zależności poprzez inną metodę i pozostawić konstruktor wolny dla swoich potomków: +Metody `inject*()` można uznać za pewnego rodzaju rozszerzenie konstruktora na wiele metod. Dzięki temu `BasePresenter` może przyjąć zależności przez inną metodę i pozostawić konstruktor wolny dla swoich potomków: ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -39,29 +36,26 @@ class MyPresenter extends BasePresenter } ``` -Prezenter może zawierać dowolną liczbę metod `inject*()`, a każda z nich może mieć dowolną liczbę parametrów. Jest to również świetne rozwiązanie dla przypadków, w których prezenter [składa się z cech |presenter-traits], a każda z nich wymaga własnej zależności. +Metod `inject*()` presenter może zawierać dowolną liczbę, a każda może mieć dowolną liczbę parametrów. Świetnie sprawdzają się również w przypadkach, gdy presenter jest [złożony z traitów |presenter-traits], a każdy z nich wymaga własnej zależności. -`Inject` Atrybuty .[#toc-inject-attributes] -=========================================== +Atrybuty `Inject` +================= -Jest to forma wstrzyknięcia [do właściwości |dependency-injection:passing-dependencies#Property Injection]. Wystarczy wskazać, które właściwości powinny zostać wstrzyknięte, a Nette DI automatycznie przekazuje zależności zaraz po utworzeniu instancji prezentera. Aby je wstawić, konieczne jest zadeklarowanie ich jako publicznych. +Jest to forma [wstrzykiwania do właściwości |dependency-injection:passing-dependencies#Ustawienie właściwości]. Wystarczy oznaczyć, do których zmiennych ma nastąpić wstrzyknięcie, a Nette DI automatycznie przekaże zależności zaraz po utworzeniu instancji presentera. Aby mógł je wstawić, konieczne jest zadeklarowanie ich jako public. -Właściwości oznaczane są atrybutem: (wcześniej używano adnotacji `/** @inject */`) +Właściwości oznaczamy atrybutem: (wcześniej używano adnotacji `/** @inject */`) ```php -use Nette\DI\Attributes\Inject; // ta linia jest ważna. +use Nette\DI\Attributes\Inject; // ta linia jest ważna class MyPresenter extends Nette\Application\UI\Presenter { #[Inject] - public Cache $cache; + public Nette\Caching\Cache $cache; // Zmiana typu na Nette\Caching\Cache dla spójności z Cache } ``` -Zaletą tej metody przekazywania zależności była bardzo oszczędna forma notacji. Jednak po wprowadzeniu [promocji właściwości konstru |https://blog.nette.org/pl/php-8-0-kompletny-przeglad-nowosci#toc-constructor-property-promotion]ktora, korzystanie z konstruktora wydaje się łatwiejsze. +Zaletą tego sposobu przekazywania zależności była bardzo oszczędna forma zapisu. Jednak wraz z pojawieniem się [constructor property promotion |https://blog.nette.org/pl/php-8-0-complete-overview-of-news#toc-constructor-property-promotion] wydaje się łatwiejsze użycie konstruktora. -Z drugiej strony, metoda ta cierpi na te same wady, co przekazywanie zależności do właściwości w ogóle: nie mamy kontroli nad zmianami w zmiennej, a jednocześnie zmienna staje się częścią publicznego interfejsu klasy, co jest niepożądane. - - -{{sitename: Najlepsze praktyki}} +Z drugiej strony, ten sposób cierpi na te same wady, co przekazywanie zależności do właściwości ogólnie: nie mamy kontroli nad zmianami w zmiennej, a jednocześnie zmienna staje się częścią publicznego interfejsu klasy, co jest niepożądane. diff --git a/best-practices/pl/lets-create-contact-form.texy b/best-practices/pl/lets-create-contact-form.texy index 25c109b3c9..4381796a10 100644 --- a/best-practices/pl/lets-create-contact-form.texy +++ b/best-practices/pl/lets-create-contact-form.texy @@ -1,12 +1,12 @@ -Stwórzmy formularz kontaktowy +Tworzymy formularz kontaktowy ***************************** .[perex] -Przyjrzyjmy się jak stworzyć formularz kontaktowy w Nette, łącznie z wysłaniem go na maila. A więc do dzieła! +Zobaczymy, jak w Nette stworzyć formularz kontaktowy, w tym wysyłanie na e-mail. Zatem do dzieła! -Najpierw musimy stworzyć nowy projekt. Jak wyjaśnia strona [Getting Started |nette:installation]. A następnie możemy przystąpić do tworzenia formularza. +Najpierw musimy stworzyć nowy projekt. Jak to zrobić, wyjaśnia strona [Pierwsze kroki |nette:installation]. A potem już możemy zacząć tworzyć formularz. -Najprostszym sposobem jest stworzenie [formularza bezpośrednio w Presenterze |forms:in-presenter]. Możemy skorzystać z gotowego `HomePresenter`. Dodamy komponent `contactForm` reprezentujący formularz. Zrobimy to wpisując metodę `createComponentContactForm()` factory do kodu, który będzie wytwarzał komponent: +Najprościej jest stworzyć [formularz bezpośrednio w prezenterze |forms:in-presenter]. Możemy wykorzystać przygotowany `HomePresenter`. Dodamy do niego komponent `contactForm` reprezentujący formularz. Zrobimy to tak, że do kodu wpiszemy metodę fabryczną `createComponentContactForm()`, która wyprodukuje komponent: ```php use Nette\Application\UI\Form; @@ -17,37 +17,35 @@ class HomePresenter extends Presenter protected function createComponentContactForm(): Form { $form = new Form; - $form->addText('name', 'Name:') - ->setRequired('Enter your name'); + $form->addText('name', 'Imię:') + ->setRequired('Proszę podać imię'); $form->addEmail('email', 'E-mail:') - ->setRequired('Enter your e-mail'); - $form->addTextarea('message', 'Message:') - ->setRequired('Enter message'); - $form->addSubmit('send', 'Send'); + ->setRequired('Proszę podać e-mail'); + $form->addTextarea('message', 'Wiadomość:') + ->setRequired('Proszę wpisać wiadomość'); + $form->addSubmit('send', 'Wyślij'); $form->onSuccess[] = [$this, 'contactFormSucceeded']; return $form; } public function contactFormSucceeded(Form $form, $data): void { - // sending an email + // wysłanie e-maila } } ``` -Jak widać, stworzyliśmy dwie metody. Pierwsza metoda `createComponentContactForm()` tworzy nowy formularz. Ma on pola na imię, e-mail i wiadomość, które dodajemy za pomocą metod `addText()`, `addEmail()` i `addTextArea()`. Dodaliśmy również przycisk do wysłania formularza. -Ale co jeśli użytkownik nie wypełni niektórych pól? W takim przypadku powinniśmy dać mu znać, że jest to pole wymagane. Zrobiliśmy to za pomocą metody `setRequired()`. -Na koniec dodaliśmy również [zdarzenie |nette:glossary#events] `onSuccess`, które jest wywoływane, jeśli formularz zostanie przesłany pomyślnie. W naszym przypadku wywołuje ono metodę `contactFormSucceeded`, która zajmuje się przetwarzaniem przesłanego formularza. Za chwilę dodamy to do kodu. +Jak widzisz, stworzyliśmy dwie metody. Pierwsza metoda `createComponentContactForm()` tworzy nowy formularz. Ma on pola na imię, e-mail i wiadomość, które dodajemy metodami `addText()`, `addEmail()` i `addTextArea()`. Dodaliśmy również przycisk do wysłania formularza. Ale co jeśli użytkownik nie wypełni jakiegoś pola? W takim przypadku powinniśmy go poinformować, że jest to pole obowiązkowe. Osiągnęliśmy to metodą `setRequired()`. Na koniec dodaliśmy również [zdarzenie |nette:glossary#Eventy zdarzenia] `onSuccess`, które uruchamia się, jeśli formularz zostanie pomyślnie wysłany i jest poprawny. W naszym przypadku wywołuje metodę `contactFormSucceeded`, która zajmie się przetwarzaniem wysłanego formularza. Uzupełnimy to w kodzie za chwilę. -Niech komponent `contantForm` będzie renderowany w szablonie `templates/Home/default.latte`: +Komponent `contactForm` wyrenderujemy w szablonie `Home/default.latte`: ```latte {block content} -<h1>Contant Form</h1> +<h1>Formularz kontaktowy</h1> {control contactForm} ``` -Aby wysłać sam e-mail, tworzymy nową klasę o nazwie `ContactFacade` i umieszczamy ją w pliku `app/Model/ContactFacade.php`: +Do samego wysłania e-maila stworzymy nową klasę, którą nazwiemy `ContactFacade` i umieścimy ją w pliku `app/Model/ContactFacade.php`: ```php <?php @@ -68,9 +66,9 @@ class ContactFacade public function sendMessage(string $email, string $name, string $message): void { $mail = new Message; - $mail->addTo('admin@example.com') // your email + $mail->addTo('admin@example.com') // twój e-mail ->setFrom($email, $name) - ->setSubject('Message from the contact form') + ->setSubject('Wiadomość z formularza kontaktowego') ->setBody($message); $this->mailer->send($mail); @@ -78,9 +76,9 @@ class ContactFacade } ``` -Metoda `sendMessage()` będzie tworzyć i wysyłać e-mail. Używa do tego tzw. mailera, którego przekazuje jako zależność poprzez konstruktor. Przeczytaj więcej o [wysyłaniu e-maili |mail:]. +Metoda `sendMessage()` tworzy i wysyła e-mail. Wykorzystuje do tego tzw. mailer, który przyjmuje jako zależność przez konstruktor. Przeczytaj więcej o [wysyłaniu e-maili |mail:]. -Teraz wrócimy do prezentera i zakończymy pracę nad metodą `contactFormSucceeded()`. Wywołuje ona metodę `sendMessage()` klasy `ContactFacade` i przekazuje jej dane formularza. A jak otrzymamy obiekt `ContactFacade`? Będziemy mieli go przekazanego nam przez konstruktor: +Teraz wrócimy do presentera i dokończymy metodę `contactFormSucceeded()`. Wywoła ona metodę `sendMessage()` klasy `ContactFacade` i przekaże jej dane z formularza. A jak uzyskać obiekt `ContactFacade`? Przyjmiemy go przez konstruktor: ```php use App\Model\ContactFacade; @@ -102,36 +100,36 @@ class HomePresenter extends Presenter public function contactFormSucceeded(stdClass $data): void { $this->facade->sendMessage($data->email, $data->name, $data->message); - $this->flashMessage('The message has been sent'); + $this->flashMessage('Wiadomość została wysłana'); $this->redirect('this'); } } ``` -Po wysłaniu maila pokazujemy użytkownikowi tzw. [flash |application:components#flash-messages] message, potwierdzający wysłanie wiadomości, a następnie przekierowujemy na następną stronę, aby nie można było ponownie wysłać formularza za pomocą *refresh* w przeglądarce. +Po wysłaniu e-maila jeszcze wyświetlimy użytkownikowi tzw. [flash message |application:components#Wiadomości flash], potwierdzającą, że wiadomość została wysłana, a następnie przekierujemy z powrotem na tę samą stronę, aby nie było możliwe ponowne wysłanie formularza za pomocą *refresh* w przeglądarce. -No cóż, jeśli wszystko działa, powinieneś móc wysłać maila ze swojego formularza kontaktowego. Gratulacje!!! +Tak, i jeśli wszystko działa, powinieneś być w stanie wysłać e-mail z twojego formularza kontaktowego. Gratulacje! -Szablon HTML Email .[#toc-html-email-template] ----------------------------------------------- +Szablon HTML e-maila +-------------------- -Na razie wysyłany jest zwykły email tekstowy zawierający tylko wiadomość wysłaną przez formularz. Możemy jednak wykorzystać w mailu HTML i uatrakcyjnić go. Stworzymy do tego szablon w Latte, który zapiszemy w `app/Model/contactEmail.latte`: +Na razie wysyłany jest prosty tekstowy e-mail zawierający tylko wiadomość wysłaną formularzem. W e-mailu możemy jednak wykorzystać HTML i uczynić jego wygląd bardziej atrakcyjnym. Stworzymy dla niego szablon w Latte, który zapiszemy w `app/Model/contactEmail.latte`: ```latte <html> - <title>Message from the contact form + Wiadomość z formularza kontaktowego -

    Name: {$name}

    +

    Imię: {$name}

    E-mail: {$email}

    -

    Message: {$message}

    +

    Wiadomość: {$message}

    ``` -Pozostaje jeszcze zmodyfikować `ContactFacade`, aby wykorzystać ten szablon. W konstruktorze żądamy klasy `LatteFactory`, która może wyprodukować obiekt `Latte\Engine`, czyli [renderer szablonu Latte |latte:develop#how-to-render-a-template]. Używamy metody `renderToString()` do renderowania szablonu do pliku, pierwszy parametr to ścieżka do szablonu, a drugi to zmienne. +Pozostaje zmodyfikować `ContactFacade`, aby używał tego szablonu. W konstruktorze zażądamy klasy `LatteFactory`, która potrafi stworzyć obiekt `Latte\Engine`, czyli [renderera szablonów Latte |latte:develop#Jak renderować szablon]. Za pomocą metody `renderToString()` wyrenderujemy szablon do stringa, pierwszym parametrem jest ścieżka do szablonu, a drugim są parametry. ```php namespace App\Model; @@ -158,7 +156,7 @@ class ContactFacade ]); $mail = new Message; - $mail->addTo('admin@example.com') // your email + $mail->addTo('admin@example.com') // twój e-mail ->setFrom($email, $name) ->setHtmlBody($body); @@ -167,15 +165,15 @@ class ContactFacade } ``` -Następnie przekazujemy wygenerowany e-mail HTML do metody `setHtmlBody()` zamiast oryginalnego `setBody()`. Nie musimy również określać tematu wiadomości w `setSubject()`, ponieważ biblioteka pobiera go z elementu `` w szablonie. +Wygenerowany e-mail HTML przekażemy następnie metodzie `setHtmlBody()` zamiast pierwotnej `setBody()`. Również nie musimy podawać tematu e-maila w `setSubject()`, ponieważ biblioteka pobierze go z elementu `<title>` szablonu. -Konfiguracja .[#toc-configuring] --------------------------------- +Konfiguracja +------------ -W kodzie klasy `ContactFacade` nasz adminowy email `admin@example.com` jest wciąż hardcoded. Lepiej byłoby przenieść go do pliku konfiguracyjnego. Jak to zrobić? +W kodzie klasy `ContactFacade` nadal jest na sztywno zapisany nasz e-mail administratora `admin@example.com`. Lepiej byłoby przenieść go do pliku konfiguracyjnego. Jak to zrobić? -Najpierw modyfikujemy klasę `ContactFacade` i zamieniamy ciąg email na zmienną przekazywaną przez konstruktor: +Najpierw zmodyfikujemy klasę `ContactFacade` i ciąg znaków z e-mailem zastąpimy zmienną przekazaną przez konstruktor: ```php class ContactFacade @@ -199,21 +197,21 @@ class ContactFacade } ``` -A drugi krok to umieszczenie wartości tej zmiennej w konfiguracji. W pliku `app/config/services.neon` dodajemy: +A drugim krokiem jest podanie wartości tej zmiennej w konfiguracji. Do pliku `app/config/services.neon` zapiszemy: ```neon services: - App\Model\ContactFacade(adminEmail: admin@example.com) ``` -I to wszystko. Jeśli w sekcji `services` jest dużo pozycji i mamy wrażenie, że mail się wśród nich gubi, możemy uczynić go zmienną. Zmodyfikujemy wpis na: +I to wszystko. Jeśli pozycji w sekcji `services` byłoby dużo i mielibyście wrażenie, że e-mail ginie wśród nich, możemy uczynić go parametrem. Zmodyfikujemy zapis na: ```neon services: - App\Model\ContactFacade(adminEmail: %adminEmail%) ``` -I zdefiniujemy tę zmienną w pliku `app/config/common.neon`: +A w pliku `app/config/common.neon` zdefiniujemy tę zmienną: ```neon parameters: @@ -221,6 +219,3 @@ parameters: ``` I gotowe! - - -{{sitename: Najlepsze praktyki}} diff --git a/best-practices/pl/microsites.texy b/best-practices/pl/microsites.texy new file mode 100644 index 0000000000..72b1666dae --- /dev/null +++ b/best-practices/pl/microsites.texy @@ -0,0 +1,63 @@ +Jak tworzyć mikro-strony +************************ + +Wyobraź sobie, że potrzebujesz szybko stworzyć małą stronę internetową na nadchodzące wydarzenie Twojej firmy. Ma być prosta, szybka i bez zbędnych komplikacji. Możesz pomyśleć, że do tak małego projektu nie potrzebujesz solidnego frameworka. Ale co jeśli użycie frameworka Nette może ten proces zasadniczo uprościć i przyspieszyć? + +Przecież nawet przy tworzeniu prostych stron internetowych nie chcesz rezygnować z wygody. Nie chcesz wymyślać tego, co już zostało raz rozwiązane. Bądź spokojnie leniwy i pozwól się rozpieszczać. Nette Framework można świetnie wykorzystać również jako micro framework. + +Jak taka mikrostroń może wyglądać? Na przykład tak, że cały kod strony umieścimy w jednym pliku `index.php` w folderze publicznym (`www`): + +```php +<?php + +require __DIR__ . '/../vendor/autoload.php'; + +$configurator = new Nette\Bootstrap\Configurator; +$configurator->enableTracy(__DIR__ . '/../log'); +$configurator->setTempDirectory(__DIR__ . '/../temp'); + +// utwórz kontener DI na podstawie konfiguracji w config.neon +$configurator->addConfig(__DIR__ . '/../app/config.neon'); +$container = $configurator->createContainer(); + +// ustawiamy routing +$router = new Nette\Application\Routers\RouteList; +$container->addService('router', $router); + +// trasa dla URL https://example.com/ +$router->addRoute('', function ($presenter, Nette\Http\Request $httpRequest) { + // wykrywamy język przeglądarki i przekierowujemy na URL /en lub /de itd. + $supportedLangs = ['en', 'de', 'cs']; + $lang = $httpRequest->detectLanguage($supportedLangs) ?: reset($supportedLangs); + $presenter->redirectUrl("/$lang"); +}); + +// trasa dla URL https://example.com/cs lub https://example.com/en +$router->addRoute('<lang cs|en>', function ($presenter, string $lang) { + // wyświetlamy odpowiedni szablon, na przykład ../templates/en.latte + $template = $presenter->createTemplate() + ->setFile(__DIR__ . '/../templates/' . $lang . '.latte'); + return $template; +}); + +// uruchom aplikację! +$container->getByType(Nette\Application\Application::class)->run(); +``` + +Wszystko pozostałe to szablony zapisane w nadrzędnym folderze `/templates`. + +Kod PHP w `index.php` najpierw [przygotowuje środowisko |bootstrap:], następnie definiuje [trasy |application:routing#Dynamiczne routowanie z callbackami] i na końcu uruchamia aplikację. Zaletą jest to, że drugi parametr funkcji `addRoute()` może być callable, który zostanie wykonany po otwarciu odpowiedniej strony. + + +Dlaczego używać Nette do mikrostroń? +------------------------------------ + +- Programiści, którzy kiedykolwiek wypróbowali [Tracy|tracy:], dziś nie wyobrażają sobie, że mogliby coś programować bez niej. +- Przede wszystkim jednak wykorzystasz system szablonów [Latte|latte:], ponieważ już od 2 stron będziesz chciał mieć oddzielony [layout i treść|latte:template-inheritance]. +- I zdecydowanie chcesz polegać na [automatycznym escapowaniu |latte:safety-first], aby nie powstała podatność XSS. +- Nette również zapewni, że w przypadku błędu nigdy nie pojawią się programistyczne komunikaty błędów PHP, ale zrozumiała dla użytkownika strona. +- Jeśli chcesz zbierać informacje zwrotne od użytkowników, na przykład w postaci formularza kontaktowego, to jeszcze dodasz [formularze|forms:] i [bazę danych|database:]. +- Wypełnione formularze możesz również łatwo [wysyłać e-mailem|mail:]. +- Czasami może przydać się [cache|caching:], na przykład jeśli pobierasz i wyświetlasz feedy. + +W dzisiejszych czasach, gdy szybkość i efektywność są kluczowe, ważne jest posiadanie narzędzi, które pozwolą Ci osiągnąć wyniki bez zbędnego opóźnienia. Nette framework oferuje właśnie to - szybki rozwój, bezpieczeństwo i szeroką gamę narzędzi, takich jak Tracy i Latte, które upraszczają proces. Wystarczy zainstalować kilka pakietów Nette, a zbudowanie takiej mikrostroń staje się nagle dziecinnie proste. I wiesz, że nigdzie nie kryje się żadna dziura bezpieczeństwa. diff --git a/best-practices/pl/pagination.texy b/best-practices/pl/pagination.texy index 83ff2c4cd9..b79dc18dce 100644 --- a/best-practices/pl/pagination.texy +++ b/best-practices/pl/pagination.texy @@ -2,16 +2,15 @@ Paginacja wyników bazy danych ***************************** .[perex] -Podczas tworzenia aplikacji internetowych często spotkasz się z wymogiem ograniczenia liczby elementów wyszczególnionych na stronie. +Podczas tworzenia aplikacji internetowych bardzo często spotkasz się z wymogiem ograniczenia liczby wyświetlanych elementów na stronie. -Zaczynamy od stanu, w którym wypisujemy wszystkie dane bez paginacji. Do wybierania danych z bazy mamy klasę ArticleRepository, która oprócz konstruktora zawiera metodę `findPublishedArticles`, która zwraca wszystkie opublikowane artykuły posortowane w porządku malejącym według daty publikacji. +Wyjdziemy ze stanu, w którym wyświetlamy wszystkie dane bez paginacji. Do wyboru danych z bazy danych mamy klasę `ArticleRepository`, która oprócz konstruktora zawiera metodę `findPublishedArticles`, zwracającą wszystkie opublikowane artykuły posortowane malejąco według daty publikacji. ```php namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -31,10 +30,10 @@ class ArticleRepository } ``` -Następnie wstrzykujemy klasę modelu w prezenterze i w metodzie render żądamy opublikowanych artykułów do przekazania do szablonu: +W prezenterze następnie wstrzykujemy klasę modelu, a w metodzie render pobieramy opublikowane artykuły, które przekazujemy do szablonu: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -53,11 +52,11 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -W szablonie zajmujemy się następnie wyszczególnieniem artykułów: +W szablonie `default.latte` zajmujemy się następnie wyświetlaniem artykułów: ```latte {block content} -<h1>Články</h1> +<h1>Artykuły</h1> <div class="articles"> {foreach $articles as $article} @@ -68,11 +67,11 @@ W szablonie zajmujemy się następnie wyszczególnieniem artykułów: ``` -W ten sposób możemy wymienić wszystkie artykuły, ale spowoduje to problemy, gdy liczba artykułów wzrośnie. Wtedy właśnie przydaje się implementacja mechanizmu paginacji. +W ten sposób potrafimy wyświetlić wszystkie artykuły, co jednak zacznie sprawiać problemy w momencie, gdy liczba artykułów wzrośnie. W tym momencie przyda się implementacja mechanizmu paginacji. -Dzięki temu wszystkie artykuły zostaną podzielone na wiele stron, a my wyświetlimy tylko artykuły z jednej bieżącej strony. [Paginator |utils:Paginator] sam obliczy całkowitą liczbę stron i podział artykułów, w zależności od tego ile mamy artykułów w sumie i ile artykułów na stronę chcemy wyświetlić. +Zapewni on, że wszystkie artykuły zostaną podzielone na kilka stron, a my wyświetlimy tylko artykuły z jednej bieżącej strony. Całkowitą liczbę stron i podział artykułów obliczy [Paginator |utils:Paginator] sam na podstawie tego, ile artykułów mamy łącznie i ile artykułów na stronę chcemy wyświetlić. -W pierwszym kroku modyfikujemy metodę pobierania artykułów w klasie repozytorium tak, aby zwracała ona artykuły tylko dla jednej strony. Dodamy też metodę, która pozwoli nam uzyskać całkowitą liczbę artykułów w bazie, co będzie nam potrzebne do skonfigurowania Paginatora: +W pierwszym kroku zmodyfikujemy metodę do pobierania artykułów w klasie repozytorium tak, aby potrafiła zwracać tylko artykuły dla jednej strony. Dodamy również metodę do sprawdzania całkowitej liczby artykułów w bazie danych, której będziemy potrzebować do ustawienia Paginatora: ```php namespace App\Model; @@ -100,7 +99,7 @@ class ArticleRepository } /** - * Vrací celkový počet publikovaných článků + * Zwraca całkowitą liczbę opublikowanych artykułów */ public function getPublishedArticlesCount(): int { @@ -109,12 +108,12 @@ class ArticleRepository } ``` -Następnie zabierzemy się do pracy nad modyfikacją prezentera. Do metody render przekażemy numer aktualnie wyświetlanej strony. W przypadku, gdy ten numer nie jest częścią adresu URL, ustawimy domyślną wartość pierwszej strony. +Następnie przystąpimy do modyfikacji presentera. Do metody render będziemy przekazywać numer aktualnie wyświetlanej strony jako parametr. W przypadku, gdy ten numer nie będzie częścią URL, ustawimy domyślną wartość pierwszej strony. -Następnie rozszerzymy również metodę render, aby uzyskać instancję Paginatora, skonfigurować ją i wybrać odpowiednie artykuły do wyświetlenia w szablonie. HomePresenter po modyfikacjach będzie wyglądał tak: +Dalej rozszerzymy również metodę render o uzyskanie instancji Paginatora, jego ustawienie i wybór odpowiednich artykułów do wyświetlenia w szablonie. `HomePresenter` po modyfikacjach będzie wyglądał tak: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -128,31 +127,31 @@ class HomePresenter extends Nette\Application\UI\Presenter public function renderDefault(int $page = 1): void { - // Uzyskaj całkowitą liczbę opublikowanych artykułów + // Sprawdzamy całkowitą liczbę opublikowanych artykułów $articlesCount = $this->articleRepository->getPublishedArticlesCount(); - // Utwórz instancję Paginatora i skonfiguruj ją + // Tworzymy instancję Paginatora i ustawiamy ją $paginator = new Nette\Utils\Paginator; $paginator->setItemCount($articlesCount); // całkowita liczba artykułów - $paginator->setItemsPerPage(10); // ilość elementów na stronie - $paginator->setPage($page); // aktualny numer strony + $paginator->setItemsPerPage(10); // liczba elementów na stronie + $paginator->setPage($page); // numer bieżącej strony - // Wyciągamy z bazy ograniczony zestaw artykułów według obliczeń Paginatora + // Pobieramy z bazy danych ograniczony zestaw artykułów zgodnie z obliczeniami Paginatora $articles = $this->articleRepository->findPublishedArticles($paginator->getLength(), $paginator->getOffset()); - // które przekażemy do szablonu + // który przekazujemy do szablonu $this->template->articles = $articles; - // a także sam Paginator, aby wyświetlić opcje paginacji + // a także sam Paginator do wyświetlania opcji paginacji $this->template->paginator = $paginator; } } ``` -Szablon już iteruje po tylko artykułach jednej strony, musimy tylko dodać linki paginacji: +Szablon już teraz iteruje tylko po artykułach jednej strony, wystarczy nam dodać linki paginacji: ```latte {block content} -<h1>Články</h1> +<h1>Artykuły</h1> <div class="articles"> {foreach $articles as $article} @@ -163,34 +162,33 @@ Szablon już iteruje po tylko artykułach jednej strony, musimy tylko dodać lin <div class="pagination"> {if !$paginator->isFirst()} - <a n:href="default, 1">První</a> + <a n:href="default, 1">Pierwsza</a>  |  - <a n:href="default, $paginator->page-1">Předchozí</a> + <a n:href="default, $paginator->page-1">Poprzednia</a>  |  {/if} - Stránka {$paginator->getPage()} z {$paginator->getPageCount()} + Strona {$paginator->getPage()} z {$paginator->getPageCount()} {if !$paginator->isLast()}  |  - <a n:href="default, $paginator->getPage() + 1">Další</a> + <a n:href="default, $paginator->getPage() + 1">Następna</a>  |  - <a n:href="default, $paginator->getPageCount()">Poslední</a> + <a n:href="default, $paginator->getPageCount()">Ostatnia</a> {/if} </div> ``` -W ten sposób dodaliśmy do strony paginację Paginator. W przypadku, gdy jako warstwy bazy danych użyjemy [Nette |database:core] Database [Explorer |database:explorer] zamiast [Nette Database Core |database:core], jesteśmy w stanie zaimplementować paginację bez użycia Paginatora. Klasa `Nette\Database\Table\Selection` zawiera metodę [page |api:Nette\Database\Table\Selection::_page] z logiką paginacji zaczerpniętą z Paginatora. +W ten sposób uzupełniliśmy stronę o możliwość paginacji za pomocą Paginatora. W przypadku, gdy zamiast [Nette Database Core |database:sql-way] jako warstwę bazodanową użyjemy [Nette Database Explorer |database:explorer], jesteśmy w stanie zaimplementować paginację również bez użycia Paginatora. Klasa `Nette\Database\Table\Selection` bowiem zawiera metodę [page() |api:Nette\Database\Table\Selection::page()], która implementuje logikę paginacji. -Repozytorium będzie wyglądać tak z tą implementacją: +Repozytorium przy tym sposobie implementacji będzie wyglądać tak: ```php namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -198,7 +196,6 @@ class ArticleRepository ) { } - public function findPublishedArticles(): Nette\Database\Table\Selection { return $this->database->table('articles') @@ -208,10 +205,10 @@ class ArticleRepository } ``` -Nie musimy tworzyć Paginatora w prezenterze, zamiast tego używamy metody klasy `Selection` zwracanej przez repozytorium: +W prezenterze nie musimy tworzyć Paginatora, użyjemy zamiast niego metody `page()` klasy `Selection`, którą zwraca repozytorium: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -225,25 +222,25 @@ class HomePresenter extends Nette\Application\UI\Presenter public function renderDefault(int $page = 1): void { - // Vytáhneme si publikované články + // Pobieramy opublikowane artykuły $articles = $this->articleRepository->findPublishedArticles(); - // i przesłać do szablonu tylko tę ich część, która jest ograniczona przez obliczenie metody strony + // i do szablonu wysyłamy tylko ich część ograniczoną zgodnie z obliczeniami metody page $lastPage = 0; $this->template->articles = $articles->page($page, 10, $lastPage); - // a także dane niezbędne do wyświetlenia opcji paginacji + // a także potrzebne dane do wyświetlania opcji paginacji $this->template->page = $page; $this->template->lastPage = $lastPage; } } ``` -Ponieważ nie wysyłamy teraz Paginatora do szablonu, wyedytujemy część pokazującą linki paginacji: +Ponieważ do szablonu teraz nie wysyłamy obiektu Paginator, zmodyfikujemy część wyświetlającą linki paginacji: ```latte {block content} -<h1>Články</h1> +<h1>Artykuły</h1> <div class="articles"> {foreach $articles as $article} @@ -254,19 +251,19 @@ Ponieważ nie wysyłamy teraz Paginatora do szablonu, wyedytujemy część pokaz <div class="pagination"> {if $page > 1} - <a n:href="default, 1">První</a> + <a n:href="default, 1">Pierwsza</a>  |  - <a n:href="default, $page - 1">Předchozí</a> + <a n:href="default, $page - 1">Poprzednia</a>  |  {/if} - Stránka {$page} z {$lastPage} + Strona {$page} z {$lastPage} {if $page < $lastPage}  |  - <a n:href="default, $page + 1">Další</a> + <a n:href="default, $page + 1">Następna</a>  |  - <a n:href="default, $lastPage">Poslední</a> + <a n:href="default, $lastPage">Ostatnia</a> {/if} </div> ``` @@ -274,4 +271,3 @@ Ponieważ nie wysyłamy teraz Paginatora do szablonu, wyedytujemy część pokaz W ten sposób zaimplementowaliśmy mechanizm paginacji bez użycia Paginatora. {{priority: -1}} -{{sitename: Najlepsze praktyki}} diff --git a/best-practices/pl/passing-settings-to-presenters.texy b/best-practices/pl/passing-settings-to-presenters.texy index 1c49c964e4..2c8ef4b1f7 100644 --- a/best-practices/pl/passing-settings-to-presenters.texy +++ b/best-practices/pl/passing-settings-to-presenters.texy @@ -1,10 +1,10 @@ -Przekazywanie ustawień do prezenterów +Przekazywanie ustawień do presenterów ************************************* .[perex] -Czy potrzebujesz przekazać argumenty do prezenterów, które nie są obiektami (np. Informacje o tym, czy działają w trybie debugowania, ścieżki do katalogów itp.) I dlatego nie mogą być przekazywane automatycznie przez autowiring? Rozwiązaniem jest enkapsulacja ich w obiekcie `Settings`. +Potrzebujesz przekazywać do presenterów argumenty, które nie są obiektami (np. informację, czy działa w trybie debugowania, ścieżki do katalogów itp.), a więc nie mogą być przekazane automatycznie za pomocą autowiringu? Rozwiązaniem jest zamknięcie ich w obiekcie `Settings`. -Serwis `Settings` to bardzo prosty, a zarazem przydatny sposób na przekazanie informacji o działającej aplikacji prezenterom. Jego konkretna forma zależy wyłącznie od Twoich konkretnych potrzeb. Przykład: +Usługa `Settings` stanowi bardzo łatwy, a zarazem użyteczny sposób dostarczania informacji o działającej aplikacji presenterom. Jej konkretna postać zależy wyłącznie od Twoich konkretnych potrzeb. Przykład: ```php namespace App; @@ -12,15 +12,15 @@ namespace App; class Settings { public function __construct( - // od PHP 8.1 je možné uvést readonly + // od PHP 8.1 można użyć readonly public bool $debugMode, public string $appDir, - // a tak dále + // i tak dalej ) {} } ``` -Przykładowa rejestracja konfiguracji: +Przykład rejestracji w konfiguracji: ```neon services: @@ -30,7 +30,7 @@ services: ) ``` -Kiedy prezenter potrzebuje informacji dostarczanych przez tę usługę, po prostu prosi o nie w konstruktorze: +Gdy presenter będzie potrzebował informacji dostarczanych przez tę usługę, po prostu poprosi o nią w konstruktorze: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -47,5 +47,3 @@ class MyPresenter extends Nette\Application\UI\Presenter } } ``` - -{{sitename: Najlepsze praktyki}} diff --git a/best-practices/pl/post-links.texy b/best-practices/pl/post-links.texy new file mode 100644 index 0000000000..257edf12d6 --- /dev/null +++ b/best-practices/pl/post-links.texy @@ -0,0 +1,56 @@ +Jak poprawnie używać linków POST +******************************** + +.[perex] +W aplikacjach internetowych, zwłaszcza w interfejsach administracyjnych, podstawową zasadą powinno być, że akcje zmieniające stan serwera nie powinny być wykonywane za pomocą metody HTTP GET. Jak sama nazwa metody wskazuje, GET powinien służyć wyłącznie do pobierania danych, a nie do ich zmiany. Dla akcji takich jak na przykład usuwanie rekordów bardziej odpowiednie jest użycie metody POST. Chociaż idealna byłaby metoda DELETE, ale tej nie można wywołać bez JavaScriptu, dlatego historycznie używa się POST. + +Jak to zrobić w praktyce? Wykorzystaj ten prosty trik. Na początku szablonu stworzysz pomocniczy formularz z identyfikatorem `postForm`, który następnie użyjesz do przycisków usuwania: + +```latte .{file:@layout.latte} +<form method="post" id="postForm"></form> +``` + +Dzięki temu formularzowi możesz zamiast klasycznego linku `<a>` użyć przycisku `<button>`, który można wizualnie dostosować tak, aby wyglądał jak zwykły link. Na przykład framework CSS Bootstrap oferuje klasy `btn btn-link`, dzięki którym osiągniesz to, że przycisk nie będzie wizualnie różnił się od innych linków. Za pomocą atrybutu `form="postForm"` powiążemy go z przygotowanym formularzem: + +```latte .{file:admin.latte} +<table> + <tr n:foreach="$posts as $post"> + <td>{$post->title}</td> + <td> + <button class="btn btn-link" form="postForm" formaction="{link delete $post->id}">usuń</button> + <!-- zamiast <a n:href="delete $post->id">usuń</a> --> + </td> + </tr> +</table> +``` + +Po kliknięciu na link zostanie teraz wywołana akcja `delete`. Aby zapewnić, że żądania będą przyjmowane wyłącznie za pomocą metody POST i z tej samej domeny (co jest skuteczną obroną przed atakami CSRF), użyj atrybutu `#[Requires]`: + +```php .{file:AdminPresenter.php} +use Nette\Application\Attributes\Requires; + +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST', sameOrigin: true)] + public function actionDelete(int $id): void + { + $this->facade->deletePost($id); // hipotetyczny kod usuwający rekord + $this->redirect('default'); + } +} +``` + +Atrybut istnieje od Nette Application 3.2, a więcej o jego możliwościach dowiesz się na stronie [Jak używać atrybutu #Requires |attribute-requires]. + +Gdybyś zamiast akcji `actionDelete()` używał sygnału `handleDelete()`, nie jest konieczne podawanie `sameOrigin: true`, ponieważ sygnały mają tę ochronę ustawioną domyślnie: + +```php .{file:AdminPresenter.php} +#[Requires(methods: 'POST')] +public function handleDelete(int $id): void +{ + $this->facade->deletePost($id); + $this->redirect('this'); +} +``` + +Takie podejście nie tylko poprawia bezpieczeństwo Twojej aplikacji, ale także przyczynia się do przestrzegania prawidłowych standardów i praktyk internetowych. Wykorzystując metody POST do akcji zmieniających stan, osiągniesz bardziej solidną i bezpieczniejszą aplikację. diff --git a/best-practices/pl/presenter-traits.texy b/best-practices/pl/presenter-traits.texy index 577763646c..d819871207 100644 --- a/best-practices/pl/presenter-traits.texy +++ b/best-practices/pl/presenter-traits.texy @@ -1,14 +1,14 @@ -Komponowanie prezenterów z cech +Składanie presenterów z traitów ******************************* .[perex] -Jeśli potrzebujemy zaimplementować ten sam kod w wielu prezenterach (np. w celu sprawdzenia, czy użytkownik jest zalogowany), sugeruje się umieszczenie kodu we wspólnym przodku. Inną opcją jest tworzenie cech jednozadaniowych. +Jeśli potrzebujemy w wielu presenterach zaimplementować ten sam kod (np. weryfikację, czy użytkownik jest zalogowany), można umieścić kod we wspólnym przodku. Drugą możliwością jest stworzenie jednofunkcyjnych [traitów |nette:introduction-to-object-oriented-programming#Traity]. -Zaletą tego rozwiązania jest to, że każdy prezenter może używać tylko cech, których faktycznie potrzebuje, podczas gdy wielokrotne dziedziczenie nie jest możliwe w PHP. +Zaletą tego rozwiązania jest to, że każdy z presenterów może użyć dokładnie tych traitów, których rzeczywiście potrzebuje, podczas gdy wielokrotne dziedziczenie nie jest możliwe w PHP. -Te cechy mogą wykorzystać fakt, że wszystkie [metody inject |inject-method-attribute#inject-Methods] są wywoływane sekwencyjnie, gdy prezenter jest tworzony. Musisz tylko upewnić się, że nazwa każdej metody wtrysku jest unikalna. +Te traity mogą wykorzystywać fakt, że przy tworzeniu presentera kolejno wywoływane są wszystkie [metody inject |inject-method-attribute#Metody inject]. Trzeba tylko dopilnować, aby nazwa każdej metody inject była unikalna. -Traits mogą powiązać kod inicjalizacyjny ze zdarzeniami [onStartup lub onRender |application:presenters#Events]. +Traity mogą dołączyć kod inicjalizacyjny do zdarzeń [onStartup lub onRender |application:presenters#Zdarzenia]. Przykłady: @@ -36,7 +36,7 @@ trait StandardTemplateFilters } ``` -Prezenter wtedy po prostu wykorzystuje te cechy: +Presenter następnie po prostu używa tych traitów: ```php class ArticlePresenter extends Nette\Application\UI\Presenter @@ -45,6 +45,3 @@ class ArticlePresenter extends Nette\Application\UI\Presenter use RequireLoggedUser; } ``` - - -{{sitename: Najlepsze praktyki}} diff --git a/best-practices/pl/restore-request.texy b/best-practices/pl/restore-request.texy index 018770eab2..4d279ee9bb 100644 --- a/best-practices/pl/restore-request.texy +++ b/best-practices/pl/restore-request.texy @@ -2,16 +2,15 @@ Jak wrócić do poprzedniej strony? ********************************* .[perex] -Co jeśli użytkownik wypełni formularz, a jego login wygaśnie? Aby uniknąć utraty danych, zapisujemy je w sesji przed przekierowaniem na stronę logowania. W Nette jest to bułka z masłem. +Co jeśli użytkownik wypełnia formularz i jego sesja wygaśnie? Aby nie stracił danych, przed przekierowaniem na stronę logowania zapiszemy żądanie w sesji. W Nette to bułka z masłem. -Bieżące żądanie można zapisać do sesji za pomocą metody `storeRequest()`, która zwraca jego identyfikator jako krótki łańcuch. Metoda przechowuje nazwę bieżącego prezentera, widok i jego parametry. -Jeśli formularz został również przesłany, zawartość pól (z wyjątkiem przesłanych plików) również zostanie zapisana. +Aktualne żądanie można zapisać w sesji za pomocą metody `storeRequest()`, która zwraca jego identyfikator w postaci krótkiego ciągu znaków. Metoda zapisuje nazwę aktualnego presentera, widoku i jego parametrów. W przypadku, gdy został również wysłany formularz, zapisywana jest także zawartość pól (z wyjątkiem przesłanych plików). -Żądanie jest przywracane przez metodę `restoreRequest($key)`, do której przekazujemy odzyskany identyfikator. Powoduje to przekierowanie do oryginalnego prezentera i widoku. Jeśli jednak zapisane żądanie zawiera przesłanie formularza, przekieruje do oryginalnego prezentera za pomocą metody `forward()`, przekaże poprzednio wypełnione wartości do formularza i pozwoli na jego ponowne narysowanie. Dzięki temu użytkownik może ponownie przesłać formularz i żadne dane nie zostaną utracone. +Przywrócenie żądania wykonuje metoda `restoreRequest($key)`, której przekazujemy uzyskany identyfikator. Przekierowuje ona na pierwotny presenter i akcję. Jeśli jednak zapisane żądanie zawiera wysłanie formularza, przechodzi na pierwotny presenter metodą `forward()`, przekazuje formularzowi wcześniej wypełnione wartości i pozwala go ponownie wyrenderować. Użytkownik ma w ten sposób możliwość ponownego wysłania formularza i żadne dane się nie tracą. -Co ważne, `restoreRequest()` sprawdza, czy nowo zalogowany użytkownik jest tym samym, który pierwotnie wypełnił formularz. Jeśli nie, odrzuca żądanie i nie robi nic. +Ważne jest, że `restoreRequest()` sprawdza, czy nowo zalogowany użytkownik jest tym samym, który pierwotnie wypełniał formularz. Jeśli nie, żądanie odrzuca i nic nie robi. -Zobrazujmy wszystko na przykładzie. Miejmy prezenter `AdminPresenter`, w którym edytowane są dane i w którego metodzie `startup()` sprawdzamy czy użytkownik jest zalogowany. Jeśli nie jest, przekierowujemy go na stronę `SignPresenter`. Jednocześnie zapisujemy bieżące żądanie i wysyłamy jego klucz na stronę `SignPresenter`. +Pokażemy wszystko na przykładzie. Mamy presenter `AdminPresenter`, w którym edytuje się dane i w którego metodzie `startup()` weryfikujemy, czy użytkownik jest zalogowany. Jeśli nie, przekierowujemy go na `SignPresenter`. Jednocześnie zapisujemy aktualne żądanie i jego klucz wysyłamy do `SignPresenter`. ```php class AdminPresenter extends Nette\Application\UI\Presenter @@ -27,7 +26,7 @@ class AdminPresenter extends Nette\Application\UI\Presenter } ``` -Prezenter `SignPresenter` będzie zawierał, oprócz formularza logowania, trwały parametr `$backlink`, w który wpisywany jest klucz. Ponieważ parametr jest trwały, będzie przekazywany nawet po przesłaniu formularza logowania. +Presenter `SignPresenter` będzie oprócz formularza logowania zawierał również parametr persistentny `$backlink`, do którego zapisze się klucz. Ponieważ parametr jest persistentny, będzie przenoszony również po odesłaniu formularza logowania. ```php @@ -48,7 +47,7 @@ class SignPresenter extends Nette\Application\UI\Presenter public function signInFormSubmitted($form) { - // ... tutaj podpisujemy użytkownika ... + // ... tutaj logujemy użytkownika ... $this->restoreRequest($this->backlink); $this->redirect('Admin:'); @@ -56,9 +55,8 @@ class SignPresenter extends Nette\Application\UI\Presenter } ``` -Przekazujemy klucz zapisanego żądania do metody `restoreRequest()`, a ona przekierowuje (lub przekazuje) do oryginalnego prezentera. +Metodzie `restoreRequest()` przekazujemy klucz zapisanego żądania, a ona przekierowuje (lub przechodzi) na pierwotny presenter. -Jeśli jednak klucz jest nieważny (na przykład nie istnieje już w sesji), metoda nie robi nic. Tak więc następnym wywołaniem jest `$this->redirect('Admin:')`, które przekierowuje do `AdminPresenter`. +Jeśli jednak klucz jest nieprawidłowy (na przykład już nie istnieje w sesji), metoda nic nie robi. Następuje więc wywołanie `$this->redirect('Admin:')`, które przekierowuje na `AdminPresenter`. {{priority: -1}} -{{sitename: Najlepsze praktyki}} diff --git a/best-practices/pt/@home.texy b/best-practices/pt/@home.texy index 5435e5eceb..ff77ce2b7f 100644 --- a/best-practices/pt/@home.texy +++ b/best-practices/pt/@home.texy @@ -1,8 +1,8 @@ -Melhores Práticas -***************** +Guias e melhores práticas +************************* .[perex] -Tutoriais, soluções para problemas comuns e melhores práticas para a Nette. +Guias, soluções para tarefas comuns e *melhores práticas* para Nette. <div class=documentation> @@ -11,12 +11,14 @@ Tutoriais, soluções para problemas comuns e melhores práticas para a Nette. Aplicação Nette --------------- -- [Métodos de injeção e atributos |inject-method-attribute] -- [Composição dos apresentadores a partir de traços |presenter-traits] -- [Passagem de configurações para os apresentadores |passing-settings-to-presenters] -- [Como voltar a uma página anterior |restore-request] -- [Paginação dos resultados do banco de dados |Pagination] -- [Trechos dinâmicos |dynamic-snippets] +- [Métodos e atributos inject |inject-method-attribute] +- [Composição de presenters a partir de traits |presenter-traits] +- [Passando configurações para presenters |passing-settings-to-presenters] +- [Como retornar a uma página anterior |restore-request] +- [Paginação de resultados do banco de dados |pagination] +- [Snippets dinâmicos |dynamic-snippets] +- [Como usar o atributo #Requires |attribute-requires] +- [Como usar corretamente links POST |post-links] </div> <div> @@ -25,32 +27,34 @@ Aplicação Nette Formulários ----------- - [Reutilização de formulários |form-reuse] -- [Formulário para criação e edição de registro |creating-editing-form] -- [Vamos criar um formulário de contato |lets-create-contact-form] -- [Caixas de seleção dependentes |https://blog.nette.org/pt/caixas-de-selecao-dependentes-elegantemente-em-nette-e-js-puro] +- [Formulário para criar e editar registros |creating-editing-form] +- [Criando um formulário de contato |lets-create-contact-form] +- [Selectboxes dependentes |https://blog.nette.org/pt/dependent-selectboxes-elegantly-in-nette-and-pure-js] </div> <div> -Comum +Geral ----- -- [Como carregar o arquivo de configuração |bootstrap:] -- [Por que a Nette usa a notação constante PascalCase? |https://blog.nette.org/pt/por-menos-gritos-no-codigo] -- [Por que Nette não usa o sufixo Interface? |https://blog.nette.org/pt/prefixos-e-sufixos-nao-pertencem-a-nomes-de-interface] -- [Dicas de uso do compositor |composer] -- [Dicas sobre editores e ferramentas |editors-and-tools] +- [Como carregar um arquivo de configuração |bootstrap:] +- [Como escrever microsites |microsites] +- [Por que o Nette usa a notação PascalCase para constantes? |https://blog.nette.org/pt/for-less-screaming-in-the-code] +- [Por que o Nette não usa o sufixo Interface? |https://blog.nette.org/pt/prefixes-and-suffixes-do-not-belong-in-interface-names] +- [Composer: dicas de uso |composer] +- [Dicas sobre editores & ferramentas |editors-and-tools] +- [Introdução à programação orientada a objetos |nette:introduction-to-object-oriented-programming] </div> <div> -Exemplo de solução +Solução de exemplo ------------------ - [Exemplos Nette |https://github.com/nette-examples] -- [Doutrina & Nette |https://contributte.org/nettrine/] -- [Exemplos de contribuição |https://contributte.org/examples.html] -- [Site da Doutrina ORM |https://github.com/MinecordNetwork/Website] +- [Doctrine & Nette |https://contributte.org/nettrine/] +- [Exemplos Contributte |https://contributte.org/examples.html] +- [Site Doctrine ORM |https://github.com/MinecordNetwork/Website] - [Início rápido |quickstart:] </div> @@ -59,10 +63,7 @@ Exemplo de solução Vídeos ------ -Você pode encontrar centenas de gravações da Posobota e vídeos sobre Nette sob o mesmo teto no "Nette Framework YouTube Channel":https://www.youtube.com/user/NetteFramework. +Centenas de gravações dos Últimos Sábados e vídeos sobre Nette podem ser encontrados sob um mesmo teto no "Canal do Youtube Nette Framework":https://www.youtube.com/user/NetteFramework. </div> </div> - -{{sitename: Melhores Práticas}} -{{leftbar: www:@menu-common}} diff --git a/best-practices/pt/@meta.texy b/best-practices/pt/@meta.texy new file mode 100644 index 0000000000..1bf3200c6f --- /dev/null +++ b/best-practices/pt/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Guias e melhores práticas}} +{{leftbar: www:@menu-common}} diff --git a/best-practices/pt/attribute-requires.texy b/best-practices/pt/attribute-requires.texy new file mode 100644 index 0000000000..e128fa61b9 --- /dev/null +++ b/best-practices/pt/attribute-requires.texy @@ -0,0 +1,177 @@ +Como usar o atributo `#[Requires]` +********************************** + +.[perex] +Ao escrever uma aplicação web, você frequentemente encontrará a necessidade de restringir o acesso a certas partes da sua aplicação. Talvez você queira que algumas requisições possam enviar dados apenas através de um formulário (ou seja, pelo método POST), ou que sejam acessíveis apenas para chamadas AJAX. No Nette Framework 3.2, surgiu uma nova ferramenta que permite definir tais restrições de forma muito elegante e clara: o atributo `#[Requires]`. + +Um atributo é uma marca especial em PHP que você adiciona antes da definição de uma classe ou método. Como na verdade é uma classe, para que os exemplos a seguir funcionem, é necessário incluir a cláusula use: + +```php +use Nette\Application\Attributes\Requires; +``` + +Você pode usar o atributo `#[Requires]` na própria classe do presenter e também nestes métodos: + +- `action<Action>()` +- `render<View>()` +- `handle<Signal>()` +- `createComponent<Name>()` + +Os dois últimos métodos também se aplicam a componentes, ou seja, você também pode usar o atributo neles. + +Se as condições especificadas pelo atributo não forem atendidas, um erro HTTP 4xx será lançado. + + +Métodos HTTP +------------ + +Você pode especificar quais métodos HTTP (como GET, POST, etc.) são permitidos para acesso. Por exemplo, se você quiser permitir o acesso apenas enviando um formulário, defina: + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +Por que você deve usar POST em vez de GET para ações que alteram o estado e como fazer isso? [Leia o tutorial |post-links]. + +Você pode especificar um método ou um array de métodos. Um caso especial é o valor `'*'`, que permite todos os métodos, o que os presenters normalmente não permitem por [razões de segurança |application:presenters#Verificação do método HTTP]. + + +Chamada AJAX +------------ + +Se você quiser que o presenter ou método esteja disponível apenas para requisições AJAX, use: + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Mesma origem +------------ + +Para aumentar a segurança, você pode exigir que a requisição seja feita do mesmo domínio. Isso evita a [vulnerabilidade CSRF |nette:vulnerability-protection#Cross-Site Request Forgery CSRF]: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Para os métodos `handle<Signal>()`, o acesso do mesmo domínio é exigido automaticamente. Portanto, se, pelo contrário, você quiser permitir o acesso de qualquer domínio, especifique: + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Acesso via forward +------------------ + +Às vezes, é útil restringir o acesso a um presenter para que ele esteja disponível apenas indiretamente, por exemplo, usando o método `forward()` ou `switch()` de outro presenter. Assim, por exemplo, protegem-se os error-presenters para que não possam ser chamados a partir da URL: + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Na prática, muitas vezes é necessário marcar certas views às quais só se pode chegar com base na lógica do presenter. Ou seja, novamente, para que não possam ser abertas diretamente: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = $this->facade->getProduct($id); + if (!$product) { + $this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Ações específicas +----------------- + +Você também pode restringir que um determinado código, como a criação de um componente, esteja disponível apenas para ações específicas no presenter: + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +No caso de uma única ação, não é necessário escrever um array: `#[Requires(actions: 'default')]` + + +Atributos personalizados +------------------------ + +Se você quiser usar o atributo `#[Requires]` repetidamente com as mesmas configurações, pode criar seu próprio atributo que herdará `#[Requires]` e o configurará de acordo com as necessidades. + +Por exemplo, `#[SingleAction]` permitirá o acesso apenas através da ação `default`: + +```php +#[\Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Ou `#[RestMethods]` permitirá o acesso através de todos os métodos HTTP usados para APIs REST: + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Conclusão +--------- + +O atributo `#[Requires]` oferece grande flexibilidade e controle sobre como suas páginas web são acessíveis. Usando regras simples, mas poderosas, você pode aumentar a segurança e o funcionamento correto da sua aplicação. Como você pode ver, o uso de atributos no Nette pode não apenas facilitar seu trabalho, mas também torná-lo mais seguro. diff --git a/best-practices/pt/composer.texy b/best-practices/pt/composer.texy index d6f7112628..0bbb245278 100644 --- a/best-practices/pt/composer.texy +++ b/best-practices/pt/composer.texy @@ -1,44 +1,44 @@ -Dicas de uso do compositor -************************** +Composer: dicas de uso +********************** <div class=perex> -Composer é uma ferramenta para o gerenciamento de dependência em PHP. Ele permite que você declare as bibliotecas das quais seu projeto depende e as instale e atualize para você. Nós aprenderemos: +O Composer é uma ferramenta para gerenciamento de dependências em PHP. Ele nos permite listar as bibliotecas das quais nosso projeto depende e as instalará e atualizará para nós. Vamos mostrar: - como instalar o Composer -- utilizá-lo em projetos novos ou existentes +- seu uso em um projeto novo ou existente </div> -Instalação .[#toc-installation] -=============================== +Instalação +========== -O Composer é um arquivo executável `.phar` que você pode baixar e instalar da seguinte forma. +O Composer é um arquivo `.phar` executável que você baixa e instala da seguinte maneira: -Windows .[#toc-windows] ------------------------ +Windows +------- -Use o instalador oficial [Composer-Setup.exe. |https://getcomposer.org/Composer-Setup.exe] +Use o instalador oficial [Composer-Setup.exe |https://getcomposer.org/Composer-Setup.exe]. -Linux, macOS .[#toc-linux-macos] --------------------------------- +Linux, macOS +------------ -Tudo que você precisa são 4 comandos, que você pode copiar a partir [desta página |https://getcomposer.org/download/]. +Basta seguir 4 comandos que você pode copiar [desta página |https://getcomposer.org/download/]. -Além disso, ao copiar para a pasta que está no sistema `PATH`, o Composer torna-se acessível globalmente: +Além disso, colocando-o em uma pasta que esteja no `PATH` do sistema, o Composer se torna acessível globalmente: ```shell -$ mv ./composer.phar ~/bin/composer # or /usr/local/bin/composer +$ mv ./composer.phar ~/bin/composer # ou /usr/local/bin/composer ``` -Uso em Projeto .[#toc-use-in-project] -===================================== +Uso no projeto +============== -Para começar a usar o Composer em seu projeto, tudo o que você precisa é um arquivo `composer.json`. Este arquivo descreve as dependências de seu projeto e pode conter também outros metadados. O mais simples `composer.json` pode se parecer com este: +Para começar a usar o Composer em seu projeto, você só precisa do arquivo `composer.json`. Ele descreve as dependências do nosso projeto e também pode conter outros metadados. Um `composer.json` básico pode, portanto, parecer assim: ```js { @@ -48,17 +48,17 @@ Para começar a usar o Composer em seu projeto, tudo o que você precisa é um a } ``` -Estamos dizendo aqui que nossa aplicação (ou biblioteca) depende do pacote `nette/database` (o nome do pacote consiste de um nome de fornecedor e o nome do projeto) e quer a versão que corresponda à restrição da versão `^3.0`. +Aqui dizemos que nossa aplicação (ou biblioteca) requer o pacote `nette/database` (o nome do pacote consiste no nome da organização e no nome do projeto) e quer a versão que corresponde à condição `^3.0` (ou seja, a versão 3 mais recente). -Portanto, quando temos o arquivo `composer.json` na raiz do projeto e executamos: +Temos, portanto, o arquivo `composer.json` na raiz do projeto e executamos a instalação: ```shell composer update ``` -O Composer irá baixar o banco de dados Nette no diretório `vendor`. Ele também cria um arquivo `composer.lock`, que contém informações sobre exatamente quais versões de biblioteca ele instalou. +O Composer baixará o Nette Database para a pasta `vendor/`. Além disso, criará o arquivo `composer.lock`, que contém informações sobre quais versões exatas das bibliotecas ele instalou. -O compositor gera um arquivo `vendor/autoload.php`. Você pode simplesmente incluir este arquivo e começar a usar as classes que estas bibliotecas fornecem sem nenhum trabalho extra: +O Composer gera o arquivo `vendor/autoload.php`, que podemos simplesmente incluir e começar a usar as bibliotecas sem qualquer trabalho adicional: ```php require __DIR__ . '/vendor/autoload.php'; @@ -67,52 +67,52 @@ $db = new Nette\Database\Connection('sqlite::memory:'); ``` -Pacotes de atualização para as versões mais recentes .[#toc-update-packages-to-the-latest-versions] -=================================================================================================== +Atualização de pacotes para as versões mais recentes +==================================================== -Para atualizar todos os pacotes usados para a versão mais recente de acordo com as restrições de versão definidas em `composer.json` use o comando `composer update`. Por exemplo, para a dependência `"nette/database": "^3.0"` será instalada a versão mais recente 3.x.x, mas não a versão 4. +A atualização das bibliotecas usadas para as versões mais recentes, de acordo com as condições definidas em `composer.json`, é responsabilidade do comando `composer update`. Por exemplo, para a dependência `"nette/database": "^3.0"`, ele instalará a versão 3.x.x mais recente, mas não a versão 4. -Para atualizar as restrições da versão no arquivo `composer.json` para, por exemplo, `"nette/database": "^4.1"`, para permitir a instalação da versão mais recente, use o comando `composer require nette/database`. +Para atualizar as condições no arquivo `composer.json`, por exemplo, para `"nette/database": "^4.1"`, para que seja possível instalar a versão mais recente, use o comando `composer require nette/database`. -Para atualizar todos os pacotes Nette usados, seria necessário listá-los todos na linha de comando, por exemplo +Para atualizar todos os pacotes Nette usados, seria necessário listá-los todos na linha de comando, por exemplo: ```shell composer require nette/application nette/forms latte/latte tracy/tracy ... ``` -O que é impraticável. Portanto, use um simples roteiro "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff que o fará por você: +O que é impraticável. Use, portanto, o script simples "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff, que fará isso por você: ```shell php composer-frontline.php ``` -Criando um novo projeto .[#toc-creating-new-project] -==================================================== +Criação de um novo projeto +========================== -O novo projeto Nette pode ser criado através da execução de um simples comando: +Você pode criar um novo projeto Nette com um único comando: ```shell -composer create-project nette/web-project name-of-the-project +composer create-project nette/web-project nome-do-projeto ``` -Em vez disso, o `name-of-the-project` você deve fornecer o nome do diretório para seu projeto e executar o comando. O compositor buscará o repositório `nette/web-project` no GitHub, que já contém o arquivo `composer.json`, e logo em seguida instalará o próprio Nette Framework. A única coisa que resta é [verificar as permissões de escrita |nette:troubleshooting#setting-directory-permissions] nos diretórios `temp/` e `log/` e você está pronto para ir. +Como `nome-do-projeto`, insira o nome do diretório para o seu projeto e confirme. O Composer baixará o repositório `nette/web-project` do GitHub, que já contém o arquivo `composer.json`, e logo depois o Nette Framework. Deve bastar apenas [definir as permissões |nette:troubleshooting#Configurando Permissões de Diretório] de escrita nas pastas `temp/` e `log/` e o projeto deve ganhar vida. -Se você sabe em que versão do PHP o projeto será hospedado, não deixe de [configurá-lo |#PHP Version]. +Se você sabe em qual versão do PHP o projeto será hospedado, não se esqueça de [configurá-la |#Versão do PHP]. -Versão PHP .[#toc-php-version] -============================== +Versão do PHP +============= -O Composer sempre instala as versões dos pacotes que são compatíveis com a versão do PHP que você está usando atualmente (ou melhor, a versão do PHP usada na linha de comando quando você executa o Composer). Que provavelmente não é a mesma versão que seu web host está usando. É por isso que é muito importante adicionar informações sobre a versão do PHP em sua hospedagem ao seu arquivo `composer.json`. Depois disso, somente versões de pacotes compatíveis com o host serão instaladas. +O Composer sempre instala as versões dos pacotes que são compatíveis com a versão do PHP que você está usando atualmente (mais precisamente, com a versão do PHP usada na linha de comando ao executar o Composer). O que, no entanto, provavelmente não é a mesma versão que sua hospedagem usa. Por isso, é muito importante adicionar ao arquivo `composer.json` a informação sobre a versão do PHP na hospedagem. Depois disso, apenas as versões dos pacotes compatíveis com a hospedagem serão instaladas. -Por exemplo, para configurar o projeto para rodar no PHP 8.2.3, use o comando: +Para definir que o projeto será executado, por exemplo, no PHP 8.2.3, usamos o comando: ```shell composer config platform.php 8.2.3 ``` -Esta é a forma como a versão é escrita no arquivo `composer.json`: +Assim, a versão será escrita no arquivo `composer.json`: ```js { @@ -124,14 +124,13 @@ Esta é a forma como a versão é escrita no arquivo `composer.json`: } ``` -No entanto, o número da versão PHP também está listado em outra parte do arquivo, na seção `require`. Enquanto o primeiro número especifica a versão para a qual os pacotes serão instalados, o segundo número diz para qual versão o aplicativo em si é escrito. -(É claro, não faz sentido que estas versões sejam diferentes, portanto, a entrada dupla é uma redundância). Você define esta versão com o comando: +No entanto, o número da versão do PHP é especificado em outro local do arquivo, na seção `require`. Enquanto o primeiro número determina para qual versão os pacotes serão instalados, o segundo número diz para qual versão a própria aplicação foi escrita. E de acordo com ele, por exemplo, o PhpStorm define o *PHP language level*. (Claro, não faz sentido que essas versões sejam diferentes, então a dupla escrita é uma falha de design.) Você define esta versão com o comando: ```shell composer require php 8.2.3 --no-update ``` -Ou diretamente no arquivo `composer.json': +Ou diretamente no arquivo `composer.json`: ```js { @@ -142,77 +141,83 @@ Ou diretamente no arquivo `composer.json': ``` -Falsos relatórios .[#toc-false-reports] -======================================= +Ignorar versão do PHP +===================== + +Os pacotes geralmente especificam tanto a versão mais baixa do PHP com a qual são compatíveis quanto a mais alta com a qual foram testados. Se você planeja usar uma versão do PHP ainda mais recente, talvez para fins de teste, o Composer se recusará a instalar tal pacote. A solução é a opção `--ignore-platform-req=php+`, que faz com que o Composer ignore os limites superiores da versão do PHP exigida. -Ao atualizar pacotes ou mudar números de versão, conflitos acontecem. Um pacote tem requisitos que entram em conflito com outro e assim por diante. No entanto, o Composer ocasionalmente imprime uma mensagem falsa. Ele relata um conflito que realmente não existe. Neste caso, ele ajuda a apagar o arquivo `composer.lock` e tenta novamente. -Se a mensagem de erro persistir, então o objetivo é sério e você precisa ler a partir dela o que modificar e como. +Mensagens falsas +================ +Ao atualizar pacotes ou alterar números de versão, pode ocorrer um conflito. Um pacote tem requisitos que estão em conflito com outro e assim por diante. Mas o Composer às vezes exibe uma mensagem falsa. Ele relata um conflito que realmente não existe. Nesse caso, ajuda excluir o arquivo `composer.lock` e tentar novamente. -Packagist.org - Repositório Global .[#toc-packagist-org-global-repository] -========================================================================== +Se a mensagem de erro persistir, então ela é séria e é necessário ler nela o que e como ajustar. -[Packagist |https://packagist.org] é o principal repositório de pacotes, no qual o Composer tenta pesquisar pacotes, se não for dito o contrário. Você também pode publicar seus próprios pacotes aqui. +Packagist.org - repositório central +=================================== -E se não quisermos o Repositório Central .[#toc-what-if-we-don-t-want-the-central-repository] ---------------------------------------------------------------------------------------------- +[Packagist |https://packagist.org] é o repositório principal no qual o Composer tenta procurar pacotes, a menos que lhe digamos o contrário. Também podemos publicar nossos próprios pacotes aqui. -Se temos aplicações internas ou bibliotecas em nossa empresa, que não podem ser hospedadas publicamente na Packagist, podemos criar nossos próprios repositórios para esse projeto. -Mais sobre os repositórios na [documentação oficial |https://getcomposer.org/doc/05-repositories.md#repositories]. +E se não quisermos usar o repositório central? +---------------------------------------------- +Se tivermos aplicações internas da empresa que simplesmente não podemos hospedar publicamente, criaremos um repositório corporativo para elas. -Carregamento automático .[#toc-autoloading] -=========================================== +Mais sobre o tema de repositórios [na documentação oficial |https://getcomposer.org/doc/05-repositories.md#repositories]. -Uma característica chave do Composer é que ele fornece auto-carga para todas as classes que ele instala, que você começa por incluir um arquivo `vendor/autoload.php`. -Entretanto, também é possível utilizar o Composer para carregar outras classes fora da pasta `vendor`. A primeira opção é deixar o Composer escanear as pastas e subpastas definidas, encontrar todas as classes e incluí-las no autoloader. Para fazer isso, configure `autoload > classmap` em `composer.json`: +Autoloading +=========== + +Uma característica fundamental do Composer é que ele fornece autoloading para todas as classes instaladas por ele, que você inicia incluindo o arquivo `vendor/autoload.php`. + +No entanto, é possível usar o Composer também para carregar outras classes fora da pasta `vendor`. A primeira opção é deixar o Composer pesquisar pastas e subpastas definidas, encontrar todas as classes e incluí-las no autoloader. Isso é alcançado definindo `autoload > classmap` em `composer.json`: ```js { "autoload": { "classmap": [ - "src/", # includes the src/ folder and its subfolders + "src/" # inclui a pasta src/ e suas subpastas ] } } ``` -Em seguida, é necessário executar o comando `composer dumpautoload` a cada mudança e deixar as mesas de auto-carga se regenerar. Isto é extremamente inconveniente, e é muito melhor confiar esta tarefa ao [RobotLoader |robot-loader:], que executa a mesma atividade automaticamente em segundo plano e muito mais rápido. +Posteriormente, é necessário executar o comando `composer dumpautoload` a cada alteração e deixar as tabelas de autoloading serem regeneradas. Isso é extremamente inconveniente e é muito melhor confiar esta tarefa ao [RobotLoader|robot-loader:], que realiza a mesma atividade automaticamente em segundo plano e muito mais rapidamente. -A segunda opção é seguir o [PSR-4 |https://www.php-fig.org/psr/psr-4/]. Simplesmente dizendo, é um sistema onde os namespaces e nomes de classes correspondem à estrutura do diretório e nomes de arquivos, ou seja, `App\Router\RouterFactory` está localizado no arquivo `/path/to/App/Router/RouterFactory.php`. Exemplo de configuração: +A segunda opção é seguir o [PSR-4|https://www.php-fig.org/psr/psr-4/]. Simplificadamente, é um sistema onde namespaces e nomes de classes correspondem à estrutura de diretórios e nomes de arquivos, ou seja, por exemplo, `App\Core\RouterFactory` estará no arquivo `/path/to/App/Core/RouterFactory.php`. Exemplo de configuração: ```js { "autoload": { "psr-4": { - "App\\": "app/" # the App\ namespace is in the app/ directory + "App\\": "app/" # o namespace App\ está no diretório app/ } } } ``` -Consulte a [documentação do Composer |https://getcomposer.org/doc/04-schema.md#psr-4] para saber exatamente como configurar este comportamento. +Como configurar exatamente o comportamento pode ser encontrado na [documentação do Composer|https://getcomposer.org/doc/04-schema.md#psr-4]. -Teste de Novas Versões .[#toc-testing-new-versions] -=================================================== +Testando novas versões +====================== -Você quer testar uma nova versão de desenvolvimento de um pacote. Como fazer isso? Primeiro, adicione este par de opções ao arquivo `composer.json`, o que lhe permitirá instalar versões de desenvolvimento de pacotes, mas só o fará se não houver uma combinação estável de versões que atenda aos requisitos: +Você quer testar uma nova versão de desenvolvimento de um pacote. Como fazer isso? Primeiro, adicione este par de opções ao arquivo `composer.json`, que permite instalar versões de desenvolvimento de pacotes, mas recorrerá a isso apenas se não houver nenhuma combinação de versões estáveis que atenda aos requisitos: ```js { "minimum-stability": "dev", - "prefer-stable": true, + "prefer-stable": true } ``` -Também recomendamos apagar o arquivo `composer.lock`, porque às vezes o Composer incompreensivelmente se recusa a instalar e isso resolverá o problema. +Além disso, recomendamos excluir o arquivo `composer.lock`, às vezes o Composer inexplicavelmente se recusa a instalar e isso resolve o problema. -Digamos que o pacote é `nette/utils` e a nova versão é a 4.0. Você o instala com o comando: +Digamos que seja o pacote `nette/utils` e a nova versão tenha o número 4.0. Você a instala com o comando: ```shell composer require nette/utils:4.0.x-dev @@ -224,20 +229,19 @@ Ou você pode instalar uma versão específica, por exemplo, 4.0.0-RC2: composer require nette/utils:4.0.0-RC2 ``` -Se outro pacote depender da biblioteca e estiver bloqueado para uma versão mais antiga (por exemplo `^3.1`), é ideal atualizar o pacote para trabalhar com a nova versão. -Entretanto, se você quiser apenas contornar a limitação e forçar o Composer a instalar a versão em desenvolvimento e fingir que é uma versão mais antiga (por exemplo, 3.1.6), você pode usar a palavra-chave `as`: +Mas se outro pacote depender da biblioteca, que está bloqueada em uma versão mais antiga (por exemplo, `^3.1`), então o ideal é atualizar o pacote para que funcione com a nova versão. No entanto, se você quiser apenas contornar a restrição e forçar o Composer a instalar a versão de desenvolvimento e fingir que é uma versão mais antiga (por exemplo, 3.1.6), pode usar a palavra-chave `as`: ```shell composer require nette/utils "4.0.x-dev as 3.1.6" ``` -Comandos de Chamada .[#toc-calling-commands] -============================================ +Chamada de comandos +=================== -Você pode chamar seus próprios comandos e scripts personalizados através do Composer, como se fossem comandos nativos do Composer. Os scripts localizados na pasta `vendor/bin` não precisam especificar esta pasta. +Através do Composer, é possível chamar comandos e scripts próprios pré-preparados, como se fossem comandos nativos do Composer. Para scripts localizados na pasta `vendor/bin`, não é necessário especificar esta pasta. -Como exemplo, definimos um roteiro no arquivo `composer.json` que utiliza o [Nette Tester |tester:] para realizar testes: +Como exemplo, definimos no arquivo `composer.json` um script que, usando o [Nette Tester|tester:], executa os testes: ```js { @@ -247,19 +251,19 @@ Como exemplo, definimos um roteiro no arquivo `composer.json` que utiliza o [Net } ``` -Em seguida, realizamos os testes com `composer tester`. Podemos chamar o comando mesmo que não estejamos na pasta raiz do projeto, mas em um subdiretório. +Os testes são então executados usando `composer tester`. O comando pode ser chamado mesmo que não estejamos na pasta raiz do projeto, mas em algum subdiretório. -Enviar agradecimentos .[#toc-send-thanks] -========================================= +Envie agradecimentos +==================== -Mostraremos a vocês um truque que fará felizes os autores de código aberto. Você pode facilmente dar uma estrela no GitHub para as bibliotecas que seu projeto utiliza. Basta instalar a biblioteca `symfony/thanks`: +Mostraremos um truque que agradará os autores de open source. De maneira simples, você pode dar uma estrela no GitHub às bibliotecas que seu projeto usa. Basta instalar a biblioteca `symfony/thanks`: ```shell composer global require symfony/thanks ``` -E depois correr: +E depois executar: ```shell composer thanks @@ -268,13 +272,11 @@ composer thanks Experimente! -Configuração .[#toc-configuration] -================================== +Configuração +============ -O Composer está estreitamente integrado com a ferramenta de controle de versão [Git |https://git-scm.com]. Se você não usar Git, é necessário dizer isso ao Composer: +O Composer está intimamente ligado à ferramenta de versionamento [Git |https://git-scm.com]. Se você não o tiver instalado, é necessário dizer ao Composer para não usá-lo: ```shell composer -g config preferred-install dist ``` - -{{sitename: Melhores Práticas}} diff --git a/best-practices/pt/creating-editing-form.texy b/best-practices/pt/creating-editing-form.texy index d3523dee63..c08c92a039 100644 --- a/best-practices/pt/creating-editing-form.texy +++ b/best-practices/pt/creating-editing-form.texy @@ -1,16 +1,16 @@ -Formulário para a criação e edição de um registro -************************************************* +Formulário para criar e editar um registro +****************************************** .[perex] -Como implementar corretamente a adição e edição de um registro em Nette, usando o mesmo formulário para ambos? +Como implementar corretamente a adição e edição de um registro no Nette, usando o mesmo formulário para ambos? -Em muitos casos, os formulários para adicionar e editar um registro são os mesmos, diferindo apenas pela etiqueta no botão. Vamos mostrar exemplos de apresentadores simples onde usamos o formulário primeiro para adicionar um registro, depois para editá-lo e, finalmente, combinar as duas soluções. +Em muitos casos, os formulários para adicionar e editar um registro são os mesmos, diferindo talvez apenas no rótulo do botão. Mostraremos exemplos de presenters simples onde usaremos o formulário primeiro para adicionar um registro, depois para editar e, finalmente, combinaremos ambas as soluções. -Adicionando um registro .[#toc-adding-a-record] ------------------------------------------------ +Adicionar um registro +--------------------- -Um exemplo de um apresentador usado para adicionar um registro. Deixaremos o trabalho real do banco de dados para a classe `Facade`, cujo código não é relevante para o exemplo. +Exemplo de um presenter usado para adicionar um registro. Deixaremos o trabalho real com o banco de dados para a classe `Facade`, cujo código não é essencial para a demonstração. ```php @@ -27,7 +27,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $form = new Form; - // ... acrescentar campos de formulário ... + // ... adicionamos os campos do formulário ... $form->onSuccess[] = [$this, 'recordFormSucceeded']; return $form; @@ -35,8 +35,8 @@ class RecordPresenter extends Nette\Application\UI\Presenter public function recordFormSucceeded(Form $form, array $data): void { - $this->facade->add($data); // adicionar registro ao banco de dados - $this->flashMessage("Adicionado com sucesso"); + $this->facade->add($data); // adiciona o registro ao banco de dados + $this->flashMessage('Adicionado com sucesso'); $this->redirect('...'); } @@ -48,10 +48,10 @@ class RecordPresenter extends Nette\Application\UI\Presenter ``` -Edição de um registro .[#toc-editing-a-record] ----------------------------------------------- +Editar um registro +------------------ -Agora vamos ver como seria um apresentador usado para editar um registro: +Agora mostraremos como seria um presenter usado para editar um registro: ```php @@ -70,10 +70,10 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $record = $this->facade->get($id); if ( - !$record // verificar a existência do registro - || !$this->facade->isEditAllowed(/*...*/) // verificar permissões + !$record // verifica a existência do registro + || !$this->facade->isEditAllowed(/*...*/) // verifica a permissão ) { - $this->error(); // 404 erro + $this->error(); // erro 404 } $this->record = $record; @@ -81,32 +81,32 @@ class RecordPresenter extends Nette\Application\UI\Presenter protected function createComponentRecordForm(): Form { - // verificar se a ação é 'editar' + // verificamos se a ação é 'edit' if ($this->getAction() !== 'edit') { $this->error(); } $form = new Form; - // ... acrescentar campos de formulário ... + // ... adicionamos os campos do formulário ... - $form->setDefaults($this->record); // definir valores padrão + $form->setDefaults($this->record); // define os valores padrão $form->onSuccess[] = [$this, 'recordFormSucceeded']; return $form; } public function recordFormSucceeded(Form $form, array $data): void { - $this->facade->update($this->record->id, $data); // registro de atualização + $this->facade->update($this->record->id, $data); // atualiza o registro $this->flashMessage('Atualizado com sucesso'); $this->redirect('...'); } } ``` -No método *ação*, que é invocado logo no início do [ciclo de vida do apresentador |application:presenters#Life Cycle of Presenter], verificamos a existência do registro e a permissão do usuário para editá-lo. +No método `actionEdit`, que é executado logo no início do [ciclo de vida do presenter |application:presenters#Ciclo de vida do presenter], verificamos a existência do registro e a permissão do usuário para editá-lo. -Armazenamos o registro na propriedade `$record` para que esteja disponível no método `createComponentRecordForm()` para definir os padrões, e `recordFormSucceeded()` para a identificação. Uma solução alternativa seria definir os valores padrão diretamente em `actionEdit()` e o valor do ID, que faz parte da URL, é recuperado usando `getParameter('id')`: +Armazenamos o registro na propriedade `$record` para tê-lo disponível no método `createComponentRecordForm()` para definir os valores padrão e em `recordFormSucceeded()` para o ID. Uma solução alternativa seria definir os valores padrão diretamente em `actionEdit()` e obter o valor do ID, que faz parte da URL, usando `getParameter('id')`: ```php @@ -114,12 +114,12 @@ Armazenamos o registro na propriedade `$record` para que esteja disponível no m { $record = $this->facade->get($id); if ( - // verificar a existência e verificar as permissões + // verifica a existência e a permissão ) { $this->error(); } - // definir valores de forma padrão + // define os valores padrão do formulário $this->getComponent('recordForm') ->setDefaults($record); } @@ -133,13 +133,13 @@ Armazenamos o registro na propriedade `$record` para que esteja disponível no m } ``` -Entretanto, e esta deve ser ** a retirada mais importante de todo o código***, precisamos ter certeza de que a ação é de fato `edit` quando criamos o formulário. Porque senão a validação no método `actionEdit()` não aconteceria de forma alguma! +No entanto, e isso deve ser **o ponto mais importante de todo o código**, devemos garantir ao criar o formulário que a ação seja realmente `edit`. Caso contrário, a verificação no método `actionEdit()` não ocorreria de forma alguma! -O mesmo formulário para adicionar e editar .[#toc-same-form-for-adding-and-editing] ------------------------------------------------------------------------------------ +O mesmo formulário para adicionar e editar +------------------------------------------ -E agora vamos combinar os dois apresentadores em um só. Ou podemos distinguir qual ação está envolvida no método `createComponentRecordForm()` e configurar a forma de acordo, ou podemos deixá-la diretamente com os métodos de ação e nos livrarmos da condição: +E agora combinamos ambos os presenters em um só. Poderíamos distinguir qual ação está sendo realizada no método `createComponentRecordForm()` e configurar o formulário de acordo, ou podemos deixar isso diretamente para os métodos de ação e nos livrar da condição: ```php @@ -160,42 +160,42 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $record = $this->facade->get($id); if ( - !$record // verificar a existência do registro - || !$this->facade->isEditAllowed(/*...*/) // verificar permissões + !$record // verifica a existência do registro + || !$this->facade->isEditAllowed(/*...*/) // verifica a permissão ) { - $this->error(); // 404 error + $this->error(); // erro 404 } $form = $this->getComponent('recordForm'); - $form->setDefaults($record); // definir padrões + $form->setDefaults($record); // define os valores padrão $form->onSuccess[] = [$this, 'editingFormSucceeded']; } protected function createComponentRecordForm(): Form { - // verificar se a ação é 'adicionar' ou 'editar'. + // verificamos se a ação é 'add' ou 'edit' if (!in_array($this->getAction(), ['add', 'edit'])) { $this->error(); } $form = new Form; - // ... acrescentar campos de formulário ... + // ... adicionamos os campos do formulário ... return $form; } public function addingFormSucceeded(Form $form, array $data): void { - $this->facade->add($data); // adicionar registro ao banco de dados - $this->flashMessage("Adicionado com sucesso"); + $this->facade->add($data); // adiciona o registro ao banco de dados + $this->flashMessage('Adicionado com sucesso'); $this->redirect('...'); } public function editingFormSucceeded(Form $form, array $data): void { $id = (int) $this->getParameter('id'); - $this->facade->update($id, $data); // registro de atualização + $this->facade->update($id, $data); // atualiza o registro $this->flashMessage('Atualizado com sucesso'); $this->redirect('...'); } @@ -203,4 +203,3 @@ class RecordPresenter extends Nette\Application\UI\Presenter ``` {{priority: -1}} -{{sitename: Melhores Práticas}} diff --git a/best-practices/pt/dynamic-snippets.texy b/best-practices/pt/dynamic-snippets.texy index 660329a182..a77d207b02 100644 --- a/best-practices/pt/dynamic-snippets.texy +++ b/best-practices/pt/dynamic-snippets.texy @@ -1,7 +1,7 @@ Snippets dinâmicos ****************** -Muitas vezes, no desenvolvimento de aplicações é necessário realizar operações AJAX, por exemplo, em filas individuais de uma tabela ou itens de lista. Como exemplo, podemos optar por listar artigos, permitindo ao usuário logado selecionar uma classificação "like/dislike" para cada um deles. O código do apresentador e o modelo correspondente sem AJAX será algo parecido com isto (eu listo os trechos mais importantes, o código assume a existência de um serviço para marcar as classificações e obter uma coleção de artigos - a implementação específica não é importante para os propósitos deste tutorial): +Com bastante frequência, durante o desenvolvimento de aplicações, surge a necessidade de realizar operações AJAX, por exemplo, em linhas individuais de uma tabela ou itens de uma lista. Como exemplo, podemos escolher a exibição de artigos, onde permitimos que um usuário logado escolha a avaliação "gosto/não gosto" para cada um deles. O código do presenter e do template correspondente sem AJAX será aproximadamente o seguinte (apresento os trechos mais importantes, o código assume a existência de um serviço para marcar a avaliação e obter a coleção de artigos - a implementação específica não é importante para os fins deste tutorial): ```php public function handleLike(int $articleId): void @@ -17,33 +17,33 @@ public function handleUnlike(int $articleId): void } ``` -Modelo: +Template: ```latte <article n:foreach="$articles as $article"> <h2>{$article->title}</h2> <div class="content">{$article->content}</div> {if !$article->liked} - <a n:href="like! $article->id" class=ajax>I like it</a> + <a n:href="like! $article->id" class=ajax>Gosto disto</a> {else} - <a n:href="unlike! $article->id" class=ajax>I don't like it anymore</a> + <a n:href="unlike! $article->id" class=ajax>Já não gosto disto</a> {/if} </article> ``` -Ajaxização .[#toc-ajaxization] -============================== +Ajaxificação +============ -Vamos agora trazer o AJAX para esta simples aplicação. Mudar a classificação de um artigo não é suficientemente importante para exigir um pedido HTTP com redirecionamento, então o ideal é que isso seja feito com AJAX em segundo plano. Usaremos o [script do handler dos add-ons |https://componette.org/vojtech-dobes/nette.ajax.js/] com a convenção usual de que os links AJAX têm a classe CSS `ajax`. +Vamos agora equipar esta aplicação simples com AJAX. A alteração da avaliação de um artigo não é tão importante a ponto de exigir um redirecionamento, e, portanto, idealmente, deveria ocorrer via AJAX em segundo plano. Usaremos o [script auxiliar dos add-ons |application:ajax#Naja] com a convenção usual de que os links AJAX têm a classe CSS `ajax`. -No entanto, como fazer isso especificamente? A Nette oferece 2 maneiras: a maneira dinâmica do snippet e a maneira dos componentes. Ambas têm seus prós e contras, por isso vamos mostrar-lhes uma a uma. +Mas como fazer isso especificamente? O Nette oferece 2 caminhos: o caminho dos chamados snippets dinâmicos e o caminho dos componentes. Ambos têm seus prós e contras, e por isso vamos mostrá-los um por um. -A maneira Dynamic Snippets .[#toc-the-dynamic-snippets-way] -=========================================================== +Caminho dos snippets dinâmicos +============================== -Na terminologia latte, um snippet dinâmico é um caso de uso específico da tag `{snippet}` onde uma variável é usada no nome do snippet. Tal trecho não pode ser encontrado apenas em qualquer parte do modelo - ele deve ser envolvido por um trecho estático, ou seja, um trecho normal, ou dentro de um `{snippetArea}`. Poderíamos modificar nosso modelo da seguinte forma. +Um snippet dinâmico, na terminologia Latte, significa um caso específico de uso da tag `{snippet}`, onde uma variável é usada no nome do snippet. Tal snippet não pode estar em qualquer lugar no template - deve ser envolvido por um snippet estático, ou seja, um comum, ou dentro de `{snippetArea}`. Poderíamos modificar nosso template da seguinte forma. ```latte @@ -53,18 +53,18 @@ Na terminologia latte, um snippet dinâmico é um caso de uso específico da tag <div class="content">{$article->content}</div> {snippet article-{$article->id}} {if !$article->liked} - <a n:href="like! $article->id" class=ajax>I like it</a> + <a n:href="like! $article->id" class=ajax>Gosto disto</a> {else} - <a n:href="unlike! $article->id" class=ajax>I don't like it anymore</a> + <a n:href="unlike! $article->id" class=ajax>Já não gosto disto</a> {/if} {/snippet} </article> {/snippet} ``` -Cada artigo agora define um único trecho, que tem uma identificação do artigo no título. Todos estes trechos são então embrulhados em um único trecho chamado `articlesContainer`. Se omitirmos este trecho de embrulho, Latte nos alertará com uma exceção. +Cada artigo agora define um snippet que tem o ID do artigo em seu nome. Todos esses snippets são então agrupados em um único snippet chamado `articlesContainer`. Se omitíssemos este snippet envolvente, o Latte nos alertaria com uma exceção. -Tudo o que resta a fazer é acrescentar um novo desenho ao apresentador - apenas redesenhar o invólucro estático. +Resta-nos adicionar o redesenho ao presenter - basta redesenhar o invólucro estático. ```php public function handleLike(int $articleId): void @@ -72,18 +72,18 @@ public function handleLike(int $articleId): void $this->ratingService->saveLike($articleId, $this->user->id); if ($this->isAjax()) { $this->redrawControl('articlesContainer'); - // $this->redrawControl('article-' . $articleId); -- není potřeba + // $this->redrawControl('article-' . $articleId); -- não é necessário } else { $this->redirect('this'); } } ``` -Modifique o método irmão `handleUnlike()` da mesma forma, e o AJAX está em funcionamento! +Modificamos de forma semelhante o método irmão `handleUnlike()`, e o AJAX está funcional! -A solução tem, no entanto, um lado negativo. Se nos aprofundarmos mais na forma como o pedido AJAX funciona, descobrimos que, embora a aplicação pareça eficiente na aparência (só devolve um único trecho para um determinado artigo), ela na verdade renderiza todos os trechos no servidor. Ele colocou o trecho desejado em nossa carga útil, e descartou os outros (assim, desnecessariamente, ele também os recuperou do banco de dados). +A solução, no entanto, tem um lado sombrio. Se investigássemos mais a fundo como a requisição AJAX ocorre, descobriríamos que, embora externamente a aplicação pareça econômica (retorna apenas um único snippet para o artigo em questão), na realidade, no servidor, ela renderizou todos os snippets. Ela colocou o snippet desejado no payload e descartou os outros (obtendo-os desnecessariamente do banco de dados também). -Para otimizar este processo, será necessário tomar medidas onde passamos a coleção `$articles` para o modelo (digamos no método `renderDefault()` ). Aproveitaremos o fato de que o processamento do sinal ocorre antes da `render<Something>` métodos: +Para otimizar este processo, teremos que intervir onde passamos a coleção `$articles` para o template (digamos, no método `renderDefault()`). Aproveitaremos o fato de que o processamento de sinais ocorre antes dos métodos `render<Something>`: ```php public function handleLike(int $articleId): void @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connection->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,18 +101,18 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connection->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` -Agora, quando o sinal é processado, em vez de uma coleção com todos os artigos, apenas um array com um único artigo é passado para o modelo - aquele que queremos renderizar e enviar a carga útil para o navegador. Assim, `{foreach}` será feito apenas uma vez e nenhum trecho extra será entregue. +Agora, ao processar o sinal, em vez de uma coleção com todos os artigos, apenas um array com um único artigo é passado para o template - aquele que queremos renderizar e enviar no payload para o navegador. O `{foreach}` então ocorrerá apenas uma vez e nenhum snippet extra será renderizado. -Via Componente .[#toc-component-way] -==================================== +Caminho dos componentes +======================= -Uma solução completamente diferente utiliza uma abordagem diferente para evitar trechos dinâmicos. O truque é mover toda a lógica para um componente separado - de agora em diante, não temos um apresentador para cuidar de entrar na classificação, mas um dedicado `LikeControl`. A classe será parecida com a seguinte (além disso, conterá também os métodos `render`, `handleUnlike`, etc.): +Uma forma completamente diferente de solução evita os snippets dinâmicos. O truque consiste em transferir toda a lógica para um componente separado - a partir de agora, o presenter não será responsável pela inserção da avaliação, mas sim um `LikeControl` dedicado. A classe ficará assim (além disso, conterá também os métodos `render`, `handleUnlike`, etc.): ```php class LikeControl extends Nette\Application\UI\Control @@ -134,31 +134,31 @@ class LikeControl extends Nette\Application\UI\Control } ``` -Modelo de componente: +Template do componente: ```latte {snippet} {if !$article->liked} - <a n:href="like!" class=ajax>I like it</a> + <a n:href="like!" class=ajax>Gosto disto</a> {else} - <a n:href="unlike!" class=ajax>I don't like it anymore</a> + <a n:href="unlike!" class=ajax>Já não gosto disto</a> {/if} {/snippet} ``` -É claro que mudaremos o modelo de visualização e teremos que adicionar uma fábrica ao apresentador. Como criaremos o componente tantas vezes quanto recebermos artigos do banco de dados, usaremos a [aplicação: |application:Multiplier] classe [multiplicadora |application:Multiplier] para "multiplicá-lo". +Claro, o template da view mudará e teremos que adicionar uma fábrica ao presenter. Como criaremos o componente tantas vezes quantos artigos obtivermos do banco de dados, usaremos a classe [application:Multiplier] para sua "multiplicação". ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); } ``` -A visualização do modelo é reduzida ao mínimo necessário (e completamente livre de trechos!): +O template da view será reduzido ao mínimo necessário (e completamente livre de snippets!): ```latte <article n:foreach="$articles as $article"> @@ -168,7 +168,6 @@ A visualização do modelo é reduzida ao mínimo necessário (e completamente l </article> ``` -Estamos quase terminando: a aplicação agora vai funcionar em AJAX. Aqui também temos que otimizar a aplicação, pois devido ao uso do Nette Database, o processamento do sinal carregará desnecessariamente todos os artigos do banco de dados em vez de um. Entretanto, a vantagem é que não haverá renderização, porque apenas nosso componente é realmente renderizado. +Estamos quase lá: a aplicação agora funcionará com AJAX. Aqui também teremos que otimizar a aplicação, porque devido ao uso do Nette Database, ao processar o sinal, todos os artigos são carregados desnecessariamente do banco de dados em vez de apenas um. A vantagem, no entanto, é que eles não serão renderizados, pois apenas nosso componente será renderizado. {{priority: -1}} -{{sitename: Melhores Práticas}} diff --git a/best-practices/pt/editors-and-tools.texy b/best-practices/pt/editors-and-tools.texy index 8eb691a59e..6d841136f7 100644 --- a/best-practices/pt/editors-and-tools.texy +++ b/best-practices/pt/editors-and-tools.texy @@ -1,40 +1,40 @@ -Editores e ferramentas +Editores & Ferramentas ********************** .[perex] -Você pode ser um programador hábil, mas somente com boas ferramentas você se tornará um mestre. Neste capítulo, você encontrará dicas sobre ferramentas importantes, editores e plugins. +Você pode ser um programador habilidoso, mas é com boas ferramentas que você se torna um mestre. Neste capítulo, você encontrará dicas sobre ferramentas importantes, editores e plugins. -Editor IDE .[#toc-ide-editor] -============================= +Editor IDE +========== -Recomendamos fortemente o uso de uma IDE completa para desenvolvimento, como PhpStorm, NetBeans, VS Code, e não apenas um editor de texto com suporte a PHP. A diferença é realmente crucial. Não há razão para estar satisfeito com um editor clássico com destaque de sintaxe, pois ele não atinge as capacidades de uma IDE com sugestão de código preciso, que pode refatorar o código, e muito mais. Algumas IDEs são pagas, outras são gratuitas. +Recomendamos fortemente o uso de um IDE completo para desenvolvimento, como PhpStorm, NetBeans, VS Code, e não apenas um editor de texto com suporte a PHP. A diferença é realmente fundamental. Não há razão para se contentar com um simples editor que, embora possa colorir a sintaxe, não atinge as capacidades de um IDE de ponta, que sugere com precisão, monitora erros, pode refatorar código e muito mais. Alguns IDEs são pagos, outros são até gratuitos. -**NetBeans IDE*** tem suporte integrado para Nette, Latte e NEON. +**NetBeans IDE** já vem com suporte integrado para Nette, Latte e NEON. -**PhpStorm**: instalar estes plugins em `Settings > Plugins > Marketplace`: -- Auxiliares da estrutura Nette +**PhpStorm**: instale estes plugins em `Settings > Plugins > Marketplace` +- Nette framework helpers - Latte -- Apoio NEON -- Testador Nette +- NEON support +- Nette Tester -**Código VS**: encontrar o plugin "Nette Latte + Neon" no mercado. +**VS Code**: encontre o plugin "Nette Latte + Neon" no marketplace. -Também conecte Tracy com o editor. Quando a página de erro for exibida, você pode clicar nos nomes dos arquivos e eles serão abertos no editor com o cursor sobre a linha correspondente. Aprenda [como configurar o sistema |tracy:open-files-in-ide]. +Conecte também o Tracy ao seu editor. Ao exibir uma página de erro, você poderá clicar nos nomes dos arquivos e eles serão abertos no editor com o cursor na linha correspondente. Leia [como configurar o sistema|tracy:open-files-in-ide]. -PHPStan .[#toc-phpstan] -======================= +PHPStan +======= -PHPStan é uma ferramenta que detecta erros lógicos em seu código antes de executá-lo. +PHPStan é uma ferramenta que detecta erros lógicos no código antes mesmo de você executá-lo. -Instale-o através do Composer: +Instalamos usando o Composer: ```shell composer require --dev phpstan/phpstan-nette ``` -Criar um arquivo de configuração `phpstan.neon` no projeto: +Criamos um arquivo de configuração `phpstan.neon` no projeto: ```neon includes: @@ -47,40 +47,38 @@ parameters: level: 5 ``` -E depois deixe que ele analise as classes na pasta `app/`: +E, em seguida, deixamos que ele analise as classes na pasta `app/`: ```shell vendor/bin/phpstan analyse app ``` -Você pode encontrar uma documentação abrangente diretamente na [PHPStan |https://phpstan.org]. +Você encontrará documentação completa diretamente no [site do PHPStan |https://phpstan.org]. -Verificador de código .[#toc-code-checker] -========================================== +Code Checker +============ -[O Code Checker |code-checker:] verifica e possivelmente repara alguns dos erros formais em seu código fonte. +O [Code Checker|code-checker:] verifica e, opcionalmente, corrige alguns erros formais em seus códigos-fonte: -- remove a [lista técnica |nette:glossary#bom] -- verifica a validade dos modelos [Latte |latte:] +- remove [BOM |nette:glossary#BOM] +- verifica a validade dos templates [Latte |latte:] - verifica a validade dos arquivos `.neon`, `.php` e `.json` -- verifica os [caracteres de controle |nette:glossary#control characters] +- verifica a ocorrência de [caracteres de controle |nette:glossary#Caracteres de controle] - verifica se o arquivo está codificado em UTF-8 -- controles mal soletrados `/* @annotations */` (falta o segundo asterisco) -- remove as tags finais do PHP `?>` em arquivos PHP -- remove o espaço em branco e as linhas em branco desnecessárias do final de um arquivo -- normaliza as terminações da linha para o sistema por defeito (com o parâmetro `-l` ) +- verifica `/* @anotações */` escritas incorretamente (falta um asterisco) +- remove `?>` de fechamento em arquivos PHP +- remove espaços em branco à direita e linhas desnecessárias no final do arquivo +- normaliza os separadores de linha para os do sistema (se você usar a opção `-l`) -Compositor .[#toc-composer] -=========================== +Composer +======== -[O Composer |Composer] é uma ferramenta para gerenciar suas dependências em PHP. Ele nos permite declarar as dependências das bibliotecas e as instalará para nós, em nosso projeto. +[Composer|best-practices:composer] é uma ferramenta para gerenciamento de dependências em PHP. Permite declarar dependências arbitrariamente complexas de bibliotecas individuais e, em seguida, as instala para nós em nosso projeto. -Verificador de requisitos .[#toc-requirements-checker] -====================================================== +Requirements Checker +==================== -Foi uma ferramenta que testou o ambiente de funcionamento do servidor e informou se (e até que ponto) a estrutura poderia ser usada. Atualmente, a Nette pode ser usada em qualquer servidor que tenha a versão mínima exigida do PHP. - -{{sitename: Melhores Práticas}} +Era uma ferramenta que testava o ambiente de execução do servidor e informava se (e em que medida) o framework poderia ser usado. Atualmente, o Nette pode ser usado em qualquer servidor que tenha a versão mínima exigida do PHP. diff --git a/best-practices/pt/form-reuse.texy b/best-practices/pt/form-reuse.texy index 90da87b3c5..a1c88ae4c5 100644 --- a/best-practices/pt/form-reuse.texy +++ b/best-practices/pt/form-reuse.texy @@ -2,15 +2,15 @@ Reutilização de formulários em vários lugares ********************************************* .[perex] -Em Nette, você tem várias opções para reutilizar a mesma forma em vários lugares sem duplicar o código. Neste artigo, analisaremos as diferentes soluções, inclusive as que você deve evitar. +No Nette, você tem várias opções para usar o mesmo formulário em vários lugares sem duplicar o código. Neste artigo, mostraremos diferentes soluções, incluindo aquelas que você deve evitar. -Fábrica de formulários .[#toc-form-factory] -=========================================== +Fábrica de formulários +====================== -Uma abordagem básica para usar o mesmo componente em vários lugares é criar um método ou classe que gere o componente, e depois chamar esse método em lugares diferentes na aplicação. Tal método ou classe é chamado de *fábrica*. Por favor, não confunda com o padrão de projeto *método de fábrica*, que descreve uma forma específica de usar fábricas e não está relacionado a este tópico. +Uma das abordagens básicas para usar o mesmo componente em vários lugares é criar um método ou classe que gera esse componente e, em seguida, chamar esse método em diferentes lugares da aplicação. Tal método ou classe é chamado de *fábrica*. Por favor, não confunda com o padrão de projeto *factory method*, que descreve uma forma específica de usar fábricas e não está relacionado a este tópico. -Como exemplo, vamos criar uma fábrica que irá construir um formulário de edição: +Como exemplo, criaremos uma fábrica que construirá um formulário de edição: ```php use Nette\Application\UI\Form; @@ -20,22 +20,22 @@ class FormFactory public function createEditForm(): Form { $form = new Form; - $form->addText('title', 'Title:'); - // campos adicionais do formulário são adicionados aqui - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Título:'); + // aqui são adicionados outros campos do formulário + $form->addSubmit('send', 'Enviar'); return $form; } } ``` -Agora você pode usar esta fábrica em diferentes lugares em sua aplicação, por exemplo, em apresentadores ou componentes. E o fazemos [solicitando-o como uma dependência |dependency-injection:passing-dependencies]. Portanto, primeiro, escreveremos a classe no arquivo de configuração: +Agora você pode usar esta fábrica em diferentes lugares da sua aplicação, por exemplo, em presenters ou componentes. E isso é feito [solicitando-a como dependência|dependency-injection:passing-dependencies]. Primeiro, registramos a classe no arquivo de configuração: ```neon services: - FormFactory ``` -E depois a usamos no apresentador: +E depois a usamos no presenter: ```php @@ -57,7 +57,7 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Você pode ampliar a fábrica de formulários com métodos adicionais para criar outros tipos de formulários que se adaptem à sua aplicação. E, é claro, você pode adicionar um método que cria um formulário básico sem elementos, que os outros métodos utilizarão: +Você pode estender a fábrica de formulários com outros métodos para criar outros tipos de formulários de acordo com as necessidades da sua aplicação. E, claro, também podemos adicionar um método que cria um formulário básico sem elementos, e os outros métodos o utilizarão: ```php class FormFactory @@ -71,21 +71,21 @@ class FormFactory public function createEditForm(): Form { $form = $this->createForm(); - $form->addText('title', 'Title:'); - // campos adicionais do formulário são adicionados aqui - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Título:'); + // aqui são adicionados outros campos do formulário + $form->addSubmit('send', 'Enviar'); return $form; } } ``` -O método `createForm()` ainda não faz nada de útil, mas isso vai mudar rapidamente. +O método `createForm()` ainda não faz nada útil, mas isso mudará rapidamente. -Dependências de Fábrica .[#toc-factory-dependencies] -==================================================== +Dependências da fábrica +======================= -Com o tempo, tornar-se-á evidente que precisamos de formulários para sermos multilíngues. Isto significa que precisamos criar um [tradutor |forms:rendering#Translating] para todos os formulários. Para fazer isso, modificamos a classe `FormFactory` para aceitar o objeto `Translator` como uma dependência no construtor, e o passamos para o formulário: +Com o tempo, percebe-se que precisamos que os formulários sejam multilíngues. Isso significa que precisamos definir o chamado [tradutor |forms:rendering#Tradução] para todos os formulários. Para isso, modificaremos a classe `FormFactory` para que ela aceite o objeto `Translator` como dependência no construtor e o passe para o formulário: ```php use Nette\Localization\Translator; @@ -104,18 +104,17 @@ class FormFactory return $form; } - //... + // ... } ``` -Como o método `createForm()` também é chamado por outros métodos que criam formas específicas, só precisamos colocar o tradutor nesse método. E estamos terminados. Não há necessidade de alterar nenhum apresentador ou código de componente, o que é ótimo. +Como o método `createForm()` também é chamado por outros métodos que criam formulários específicos, basta definir o tradutor apenas nele. E está feito. Não é necessário alterar o código de nenhum presenter ou componente, o que é ótimo. -Mais Classes de Fábrica .[#toc-more-factory-classes] -==================================================== +Múltiplas classes de fábrica +============================ -Alternativamente, você pode criar múltiplas classes para cada formulário que deseja utilizar em sua aplicação. -Esta abordagem pode aumentar a legibilidade do código e tornar os formulários mais fáceis de gerenciar. Deixe o original `FormFactory` para criar apenas um formulário puro com configuração básica (por exemplo, com suporte a tradução) e crie uma nova fábrica `EditFormFactory` para o formulário de edição. +Alternativamente, você pode criar várias classes para cada formulário que deseja usar em sua aplicação. Essa abordagem pode aumentar a legibilidade do código e facilitar o gerenciamento dos formulários. Deixaremos a `FormFactory` original criar apenas um formulário limpo com configuração básica (por exemplo, com suporte a traduções) e criaremos uma nova fábrica `EditFormFactory` para o formulário de edição. ```php class FormFactory @@ -134,7 +133,7 @@ class FormFactory } -// ✅ uso da composição +// ✅ uso de composição class EditFormFactory { public function __construct( @@ -145,40 +144,39 @@ class EditFormFactory public function create(): Form { $form = $this->formFactory->create(); - // campos adicionais do formulário são adicionados aqui - $form->addSubmit('send', 'Save'); + // aqui são adicionados outros campos do formulário + $form->addSubmit('send', 'Enviar'); return $form; } } ``` -É muito importante que a ligação entre as classes `FormFactory` e `EditFormFactory` seja implementada por composição e não por herança de objetos: +É muito importante que a relação entre as classes `FormFactory` e `EditFormFactory` seja realizada por [composição |nette:introduction-to-object-oriented-programming#Composição], e não por [herança de objetos |nette:introduction-to-object-oriented-programming#Herança]: ```php -// ⛔ NÃO! A HERANÇA NÃO PERTENCE AQUI +// ⛔ ASSIM NÃO! A HERANÇA NÃO PERTENCE AQUI class EditFormFactory extends FormFactory { public function create(): Form { $form = parent::create(); - $form->addText('title', 'Title:'); - // campos adicionais do formulário são adicionados aqui - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Título:'); + // aqui são adicionados outros campos do formulário + $form->addSubmit('send', 'Enviar'); return $form; } } ``` -Usar a herança neste caso seria completamente contraproducente. Você se depararia com problemas muito rapidamente. Por exemplo, se você quisesse adicionar parâmetros ao método `create()`; o PHP relataria um erro de que sua assinatura era diferente da dos pais. -Ou ao passar uma dependência para a classe `EditFormFactory` através do construtor. Isto causaria o que chamamos de [inferno do construtor |dependency-injection:passing-dependencies#Constructor hell]. +O uso de herança seria completamente contraproducente neste caso. Você encontraria problemas muito rapidamente. Por exemplo, no momento em que quisesse adicionar parâmetros ao método `create()`; o PHP relataria um erro de que sua assinatura difere da do pai. Ou ao passar dependências para a classe `EditFormFactory` através do construtor. Ocorreria uma situação que chamamos de [constructor hell |dependency-injection:passing-dependencies#Constructor hell]. -Em geral, é melhor preferir a composição do que a herança. +Em geral, é melhor [preferir composição em vez de herança |dependency-injection:faq#Por que a composição é preferida em relação à herança]. -Manuseio de formulários .[#toc-form-handling] -============================================= +Manipulação do formulário +========================= -O manipulador de formulários que é chamado após uma apresentação bem-sucedida também pode fazer parte de uma classe de fábrica. Ele funcionará passando os dados enviados para o modelo para processamento. Ele passará quaisquer erros de [volta para o |forms:validation#Processing Errors] formulário. O modelo no exemplo a seguir é representado pela classe `Facade`: +O manipulador do formulário, que é chamado após o envio bem-sucedido, também pode fazer parte da classe de fábrica. Ele funcionará passando os dados enviados para o modelo para processamento. Eventuais erros são [passados de volta |forms:validation#Erros durante o processamento] para o formulário. O modelo no exemplo a seguir é representado pela classe `Facade`: ```php class EditFormFactory @@ -192,9 +190,9 @@ class EditFormFactory public function create(): Form { $form = $this->formFactory->create(); - $form->addText('title', 'Title:'); - // campos adicionais do formulário são adicionados aqui - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Título:'); + // aqui são adicionados outros campos do formulário + $form->addSubmit('send', 'Enviar'); $form->onSuccess[] = [$this, 'processForm']; return $form; } @@ -202,7 +200,7 @@ class EditFormFactory public function processForm(Form $form, array $data): void { try { - // processamento dos dados apresentados + // processamento dos dados enviados $this->facade->process($data); } catch (AnyModelException $e) { @@ -212,7 +210,7 @@ class EditFormFactory } ``` -Deixe o apresentador cuidar do redirecionamento em si. Ele adicionará outro manipulador ao evento `onSuccess`, que realizará o redirecionamento. Isto permitirá que o formulário seja utilizado em diferentes apresentadores, e cada um poderá ser redirecionado para um local diferente. +No entanto, deixaremos o redirecionamento em si para o presenter. Ele adicionará outro manipulador ao evento `onSuccess`, que realizará o redirecionamento. Graças a isso, será possível usar o formulário em diferentes presenters e redirecionar para um local diferente em cada um deles. ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -226,7 +224,7 @@ class MyPresenter extends Nette\Application\UI\Presenter { $form = $this->formFactory->create(); $form->onSuccess[] = function () { - $this->flashMessage('Záznam byl uložen'); + $this->flashMessage('O registro foi salvo'); $this->redirect('Homepage:'); }; return $form; @@ -234,39 +232,38 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Esta solução aproveita a propriedade dos formulários que, quando `addError()` é chamado em um formulário ou em seu elemento, o próximo manipulador `onSuccess` não é invocado. +Esta solução utiliza a propriedade dos formulários de que, quando `addError()` é chamado no formulário ou em seu elemento, o próximo manipulador `onSuccess` não é chamado. -Herdando da classe do formulário .[#toc-inheriting-from-the-form-class] -======================================================================= +Herança da classe Form +====================== -Uma forma construída não deve ser uma criança de uma forma. Em outras palavras, não use esta solução: +Um formulário construído não deve ser um descendente da classe `Form`. Em outras palavras, não use esta solução: ```php -// ⛔ NÃO! A HERANÇA NÃO PERTENCE AQUI +// ⛔ ASSIM NÃO! A HERANÇA NÃO PERTENCE AQUI class EditForm extends Form { public function __construct(Translator $translator) { parent::__construct(); - $form->addText('title', 'Title:'); - // campos adicionais do formulário são adicionados aqui - $form->addSubmit('send', 'Save'); - $form->setTranslator($translator); + $this->addText('title', 'Título:'); + // aqui são adicionados outros campos do formulário + $this->addSubmit('send', 'Enviar'); + $this->setTranslator($translator); } } ``` -Em vez de construir a forma na construtora, use a fábrica. +Em vez de construir o formulário no construtor, use uma fábrica. -É importante perceber que a classe `Form` é principalmente uma ferramenta para montagem de um formulário, ou seja, um construtor de formulários. E a forma montada pode ser considerada seu produto. Entretanto, o produto não é um caso específico do construtor; não há *é uma* relação entre eles, o que forma a base da herança. +É preciso perceber que a classe `Form` é, antes de tudo, uma ferramenta para construir um formulário, ou seja, um *form builder*. E o formulário construído pode ser entendido como seu produto. Mas o produto não é um caso específico do builder, não há entre eles uma relação *is a* que forma a base da herança. -Componente do formulário .[#toc-form-component] -=============================================== +Componente com formulário +========================= -Uma abordagem completamente diferente é criar um [componente |application:components] que inclua uma forma. Isto dá novas possibilidades, por exemplo, de tornar o formulário de uma forma específica, uma vez que o componente inclui um modelo. -Ou sinais podem ser usados para comunicação AJAX e carregamento de informações no formulário, por exemplo, para insinuação, etc. +Uma abordagem completamente diferente é a criação de [componentes|application:components] que incluem um formulário. Isso oferece novas possibilidades, como renderizar o formulário de uma maneira específica, já que o componente também inclui um template. Ou é possível usar sinais para comunicação AJAX e carregamento de informações no formulário, por exemplo, para sugestões, etc. ```php @@ -284,9 +281,9 @@ class EditControl extends Nette\Application\UI\Control protected function createComponentForm(): Form { $form = new Form; - $form->addText('title', 'Title:'); - // campos adicionais do formulário são adicionados aqui - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Título:'); + // aqui são adicionados outros campos do formulário + $form->addSubmit('send', 'Enviar'); $form->onSuccess[] = [$this, 'processForm']; return $form; @@ -295,7 +292,7 @@ class EditControl extends Nette\Application\UI\Control public function processForm(Form $form, array $data): void { try { - // processamento dos dados apresentados + // processamento dos dados enviados $this->facade->process($data); } catch (AnyModelException $e) { @@ -303,13 +300,13 @@ class EditControl extends Nette\Application\UI\Control return; } - // invocação de eventos + // dispara o evento $this->onSave($this, $data); } } ``` -Vamos criar uma fábrica que produzirá este componente. É o suficiente para [escrever sua interface |application:components#Components with Dependencies]: +Criaremos também uma fábrica que produzirá este componente. Basta [registrar sua interface |application:components#Componentes com dependências]: ```php interface EditControlFactory @@ -318,14 +315,14 @@ interface EditControlFactory } ``` -E adicione-a ao arquivo de configuração: +E adicionar ao arquivo de configuração: ```neon services: - EditControlFactory ``` -E agora podemos solicitar a fábrica e utilizá-la no apresentador: +E agora podemos solicitar a fábrica e usá-la no presenter: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -335,13 +332,13 @@ class MyPresenter extends Nette\Application\UI\Presenter ) { } - protected function createComponentEditForm(): Form + protected function createComponentEditForm(): EditControl { $control = $this->controlFactory->create(); $control->onSave[] = function (EditControl $control, $data) { $this->redirect('this'); - // ou redirecionar para o resultado da edição, por exemplo + // ou redirecionamos para o resultado da edição, por exemplo: // $this->redirect('detail', ['id' => $data->id]); }; @@ -349,5 +346,3 @@ class MyPresenter extends Nette\Application\UI\Presenter } } ``` - -{{sitename: Melhores Práticas}} diff --git a/best-practices/pt/inject-method-attribute.texy b/best-practices/pt/inject-method-attribute.texy index 594be4c602..c5b05410c2 100644 --- a/best-practices/pt/inject-method-attribute.texy +++ b/best-practices/pt/inject-method-attribute.texy @@ -1,21 +1,18 @@ -Métodos de Injeção e Atributos -****************************** +Métodos e atributos inject +************************** .[perex] -Neste artigo, vamos nos concentrar em várias formas de passar dependências aos apresentadores na estrutura da Nette. Vamos comparar o método preferido, que é o construtor, com outras opções como `inject` métodos e atributos. +Neste artigo, focaremos nas diferentes maneiras de passar dependências para presenters no framework Nette. Compararemos a forma preferida, que é o construtor, com outras opções, como métodos e atributos `inject`. -Também para os apresentadores, a passagem de dependências utilizando o [construtor |dependency-injection:passing-dependencies#Constructor Injection] é a forma preferida. -Entretanto, se você criar um ancestral comum do qual outros apresentadores herdam (por exemplo, BasePresenter), e este ancestral também tiver dependências, surge um problema, ao qual chamamos de [construtor inferno |dependency-injection:passing-dependencies#Constructor hell]. -Isto pode ser contornado usando métodos alternativos, que incluem métodos de injeção e atributos (anotações). +Também para presenters, passar dependências usando o [construtor |dependency-injection:passing-dependencies#Passagem pelo construtor] é o caminho preferido. No entanto, se você criar um ancestral comum do qual outros presenters herdam (por exemplo, `BasePresenter`), e este ancestral também tiver dependências, ocorrerá um problema que chamamos de [constructor hell |dependency-injection:passing-dependencies#Constructor hell]. Isso pode ser contornado usando caminhos alternativos, que são os métodos e atributos (anotações) `inject`. -`inject*()` Métodos .[#toc-inject-methods] -========================================== +Métodos `inject*()` +=================== -Esta é uma forma de passagem de dependência utilizando [setters |dependency-injection:passing-dependencies#Setter Injection]. Os nomes desses setters começam com o prefixo injetar. -Nette DI chama automaticamente tais métodos nomeados imediatamente após criar a instância apresentadora e passa todas as dependências necessárias a eles. Portanto, eles devem ser declarados como públicos. +É uma forma de passar dependências por [setter |dependency-injection:passing-dependencies#Passagem por setter]. O nome desses setters começa com o prefixo `inject`. O Nette DI chama automaticamente métodos com esse nome logo após a criação da instância do presenter e passa a eles todas as dependências necessárias. Portanto, eles devem ser declarados como `public`. -`inject*()` métodos podem ser considerados como uma espécie de extensão do construtor em múltiplos métodos. Graças a isso, o `BasePresenter` pode levar dependências através de outro método e deixar o construtor livre para seus descendentes: +Os métodos `inject*()` podem ser considerados como uma extensão do construtor em vários métodos. Graças a isso, o `BasePresenter` pode receber dependências através de outro método e deixar o construtor livre para seus descendentes: ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -39,18 +36,18 @@ class MyPresenter extends BasePresenter } ``` -O apresentador pode conter qualquer número de métodos `inject*()`, e cada um pode ter qualquer número de parâmetros. Isto também é ótimo para casos onde o apresentador é [composto de traços |presenter-traits], e cada um deles requer sua própria dependência. +Um presenter pode conter qualquer número de métodos `inject*()` e cada um pode ter qualquer número de parâmetros. Eles também são ótimos em casos onde o presenter é [composto por traits |presenter-traits] e cada um deles requer sua própria dependência. -`Inject` Atributos .[#toc-inject-attributes] -============================================ +Atributos `Inject` +================== -Esta é uma forma de [injeção em propriedades |dependency-injection:passing-dependencies#Property Injection]. Basta indicar quais propriedades devem ser injetadas, e a Nette DI passa automaticamente as dependências imediatamente após a criação da instância apresentadora. Para inseri-las, é necessário declará-las como públicas. +É uma forma de [injeção na propriedade |dependency-injection:passing-dependencies#Configuração de propriedade]. Basta marcar em quais propriedades injetar, e o Nette DI passa automaticamente as dependências logo após a criação da instância do presenter. Para poder inseri-las, é necessário declará-las como `public`. -As propriedades são marcadas com um atributo: (anteriormente, foi utilizada a anotação `/** @inject */`) +Marcamos as propriedades com um atributo: (anteriormente, usava-se a anotação `/** @inject */`) ```php -use Nette\DI\Attributes\Inject; // esta linha é importante +use Nette\DI\Attributes\Inject; // esta linha é importante class MyPresenter extends Nette\Application\UI\Presenter { @@ -59,9 +56,6 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -A vantagem deste método de passar dependências foi sua forma muito econômica de notação. Entretanto, com a introdução da [promoção da propriedade do construtor |https://blog.nette.org/pt/php-8-0-visao-geral-completa-das-noticias#toc-constructor-property-promotion], o uso do construtor parece mais fácil. +A vantagem dessa forma de passar dependências era a forma de escrita muito concisa. No entanto, com a chegada da [promoção de propriedades do construtor |https://blog.nette.org/pt/php-8-0-complete-overview-of-news#toc-constructor-property-promotion], parece mais fácil usar o construtor. -Por outro lado, este método sofre das mesmas deficiências que a passagem de dependências para propriedades em geral: não temos controle sobre as mudanças na variável e, ao mesmo tempo, a variável se torna parte da interface pública da classe, o que é indesejável. - - -{{sitename: Melhores Práticas}} +Por outro lado, essa forma sofre das mesmas desvantagens que a passagem de dependências para propriedades em geral: não temos controle sobre as alterações na variável e, ao mesmo tempo, a variável se torna parte da interface pública da classe, o que é indesejável. diff --git a/best-practices/pt/lets-create-contact-form.texy b/best-practices/pt/lets-create-contact-form.texy index 73db878db9..f75fa749a8 100644 --- a/best-practices/pt/lets-create-contact-form.texy +++ b/best-practices/pt/lets-create-contact-form.texy @@ -1,12 +1,12 @@ -Vamos criar um formulário de contato -************************************ +Criando um formulário de contato +******************************** .[perex] -Vamos ver como criar um formulário de contato em Nette, inclusive enviando-o para um e-mail. Então, vamos fazer isso! +Vamos ver como criar um formulário de contato no Nette, incluindo o envio para e-mail. Então, vamos lá! -Primeiro temos que criar um novo projeto. Como explica a página [Primeiros Pass |nette:installation] os. E depois podemos começar a criar o formulário. +Primeiro, precisamos criar um novo projeto. Como fazer isso é explicado na página [Começando |nette:installation]. E então podemos começar a criar o formulário. -A maneira mais fácil é criar o [formulário diretamente no Apresentador |forms:in-presenter]. Podemos usar o pré-fabricado `HomePresenter`. Acrescentaremos o componente `contactForm` que representa o formulário. Fazemos isso escrevendo o método de fábrica `createComponentContactForm()` no código que irá produzir o componente: +A maneira mais simples é criar o [formulário diretamente no presenter |forms:in-presenter]. Podemos usar o `HomePresenter` pré-preparado. Nele, adicionaremos o componente `contactForm` que representa o formulário. Faremos isso escrevendo o método de fábrica `createComponentContactForm()` no código, que produzirá o componente: ```php use Nette\Application\UI\Form; @@ -17,37 +17,35 @@ class HomePresenter extends Presenter protected function createComponentContactForm(): Form { $form = new Form; - $form->addText('name', 'Name:') - ->setRequired('Enter your name'); + $form->addText('name', 'Nome:') + ->setRequired('Por favor, digite seu nome.'); $form->addEmail('email', 'E-mail:') - ->setRequired('Enter your e-mail'); - $form->addTextarea('message', 'Message:') - ->setRequired('Enter message'); - $form->addSubmit('send', 'Send'); + ->setRequired('Por favor, digite seu e-mail.'); + $form->addTextarea('message', 'Mensagem:') + ->setRequired('Por favor, digite sua mensagem.'); + $form->addSubmit('send', 'Enviar'); $form->onSuccess[] = [$this, 'contactFormSucceeded']; return $form; } public function contactFormSucceeded(Form $form, $data): void { - // sending an email + // envio de e-mail } } ``` -Como você pode ver, nós criamos dois métodos. O primeiro método `createComponentContactForm()` cria uma nova forma. Este tem campos para nome, e-mail e mensagem, que adicionamos usando os métodos `addText()`, `addEmail()` e `addTextArea()`. Também adicionamos um botão para enviar o formulário. -Mas e se o usuário não preencher alguns campos? Nesse caso, devemos avisá-lo que se trata de um campo obrigatório. Fizemos isso com o método `setRequired()`. -Finalmente, adicionamos também um [evento |nette:glossary#events] `onSuccess`, que é acionado se o formulário for submetido com sucesso. Em nosso caso, ele chama o método `contactFormSucceeded`, que se encarrega de processar o formulário submetido. Acrescentaremos isso ao código em um momento. +Como você pode ver, criamos dois métodos. O primeiro método `createComponentContactForm()` cria um novo formulário. Ele tem campos para nome, e-mail e mensagem, que adicionamos com os métodos `addText()`, `addEmail()` e `addTextArea()`. Também adicionamos um botão para enviar o formulário. Mas e se o usuário não preencher algum campo? Nesse caso, devemos informá-lo de que é um campo obrigatório. Conseguimos isso com o método `setRequired()`. Finalmente, adicionamos também o [evento |nette:glossary#Eventos] `onSuccess`, que é acionado se o formulário for enviado com sucesso. No nosso caso, ele chama o método `contactFormSucceeded`, que cuidará do processamento do formulário enviado. Adicionaremos isso ao código em um momento. -Deixe o componente `contantForm` ser apresentado no modelo `templates/Home/default.latte`: +Deixaremos o componente `contactForm` ser renderizado no template `Home/default.latte`: ```latte {block content} -<h1>Contant Form</h1> +<h1>Formulário de Contato</h1> {control contactForm} ``` -Para enviar o e-mail em si, criamos uma nova classe chamada `ContactFacade` e a colocamos no arquivo `app/Model/ContactFacade.php`: +Para o envio do e-mail em si, criaremos uma nova classe, que chamaremos de `ContactFacade` e a colocaremos no arquivo `app/Model/ContactFacade.php`: ```php <?php @@ -68,9 +66,9 @@ class ContactFacade public function sendMessage(string $email, string $name, string $message): void { $mail = new Message; - $mail->addTo('admin@example.com') // your email + $mail->addTo('admin@example.com') // seu e-mail ->setFrom($email, $name) - ->setSubject('Message from the contact form') + ->setSubject('Mensagem do formulário de contato') ->setBody($message); $this->mailer->send($mail); @@ -78,9 +76,9 @@ class ContactFacade } ``` -O método `sendMessage()` irá criar e enviar o e-mail. Para isso, ele usa o chamado mailer, que passa como uma dependência através do construtor. Leia mais sobre o [envio de e-mails |mail:]. +O método `sendMessage()` cria e envia o e-mail. Ele usa o chamado mailer para isso, que ele recebe como dependência através do construtor. Leia mais sobre [envio de e-mails |mail:]. -Agora, vamos voltar ao apresentador e completar o método `contactFormSucceeded()`. Ele chama o método `sendMessage()` da classe `ContactFacade` e lhe passa os dados do formulário. E como obtemos o objeto `ContactFacade`? O construtor nos passará os dados: +Agora voltaremos ao presenter e finalizaremos o método `contactFormSucceeded()`. Ele chamará o método `sendMessage()` da classe `ContactFacade` e passará os dados do formulário para ele. E como obtemos o objeto `ContactFacade`? Vamos recebê-lo através do construtor: ```php use App\Model\ContactFacade; @@ -102,36 +100,36 @@ class HomePresenter extends Presenter public function contactFormSucceeded(stdClass $data): void { $this->facade->sendMessage($data->email, $data->name, $data->message); - $this->flashMessage('The message has been sent'); + $this->flashMessage('A mensagem foi enviada'); $this->redirect('this'); } } ``` -Após o envio do e-mail, mostramos ao usuário a chamada [mensagem flash |application:components#flash-messages], confirmando que a mensagem foi enviada, e então redirecionamos para a página seguinte para que o formulário não possa ser reapresentado usando *refresh* no navegador. +Depois que o e-mail for enviado, ainda exibiremos ao usuário a chamada [flash message |application:components#Mensagens Flash], confirmando que a mensagem foi enviada, e depois redirecionaremos para a mesma página (usando `this`), para que não seja possível reenviar o formulário usando *refresh* no navegador. -Bem, se tudo funcionar, você deve poder enviar um e-mail a partir de seu formulário de contato. Parabéns! +Então, se tudo funcionar, você deve ser capaz de enviar um e-mail do seu formulário de contato. Parabéns! -Modelo de e-mail HTML .[#toc-html-email-template] -------------------------------------------------- +Template HTML do e-mail +----------------------- -Por enquanto, é enviado um e-mail com texto simples contendo apenas a mensagem enviada pelo formulário. Mas podemos usar HTML no e-mail e torná-lo mais atraente. Criaremos um modelo para ele em Latte, que salvaremos em `app/Model/contactEmail.latte`: +Até agora, um e-mail de texto simples está sendo enviado, contendo apenas a mensagem enviada pelo formulário. Mas no e-mail, podemos usar HTML e tornar sua aparência mais atraente. Criaremos um template em Latte para ele, que escreveremos em `app/Model/contactEmail.latte`: ```latte <html> - <title>Message from the contact form + Mensagem do formulário de contato -

    Name: {$name}

    +

    Nome: {$name}

    E-mail: {$email}

    -

    Message: {$message}

    +

    Mensagem: {$message}

    ``` -Resta modificar `ContactFacade` para usar este modelo. No construtor, solicitamos a classe `LatteFactory`, que pode produzir o objeto `Latte\Engine`, um [renderizador de modelos Latte |latte:develop#how-to-render-a-template]. Usamos o método `renderToString()` para renderizar o modelo em um arquivo, o primeiro parâmetro é o caminho para o modelo e o segundo são as variáveis. +Resta modificar o `ContactFacade`, para usar este template. No construtor, solicitaremos a classe `LatteFactory`, que pode criar um objeto `Latte\Engine`, ou seja, o [renderizador de templates Latte |latte:develop#Como renderizar um template]. Usando o método `renderToString()`, renderizamos o template para uma string, o primeiro parâmetro é o caminho para o template e o segundo são as variáveis. ```php namespace App\Model; @@ -158,7 +156,7 @@ class ContactFacade ]); $mail = new Message; - $mail->addTo('admin@example.com') // your email + $mail->addTo('admin@example.com') // seu e-mail ->setFrom($email, $name) ->setHtmlBody($body); @@ -167,15 +165,15 @@ class ContactFacade } ``` -Passamos então o e-mail HTML gerado para o método `setHtmlBody()` em vez do original `setBody()`. Também não precisamos especificar o assunto do e-mail em `setSubject()`, pois a biblioteca o retira do elemento `` em modelo. +O e-mail HTML gerado é então passado para o método `setHtmlBody()` em vez do original `setBody()`. Da mesma forma, não precisamos especificar o assunto do e-mail em `setSubject()`, pois a biblioteca o pegará do elemento `<title>` do template. -Configurando .[#toc-configuring] --------------------------------- +Configuração +------------ -No código de classe `ContactFacade`, nosso e-mail administrativo `admin@example.com` ainda está codificado. Seria melhor movê-lo para o arquivo de configuração. Como fazer isso? +No código da classe `ContactFacade`, nosso e-mail de administrador `admin@example.com` ainda está codificado. Seria melhor movê-lo para o arquivo de configuração. Como fazer isso? -Primeiro, modificamos a classe `ContactFacade` e substituímos a string de e-mail por uma variável passada pelo construtor: +Primeiro, modificamos a classe `ContactFacade` e substituímos a string com o e-mail por uma variável passada pelo construtor: ```php class ContactFacade @@ -199,28 +197,25 @@ class ContactFacade } ``` -E o segundo passo é colocar o valor desta variável na configuração. No arquivo `app/config/services.neon`, adicionamos: +E o segundo passo é especificar o valor desta variável na configuração. No arquivo `app/config/services.neon`, escrevemos: ```neon services: - App\Model\ContactFacade(adminEmail: admin@example.com) ``` -E é isso aí. Se houver muitos itens na seção `services` e você sentir que o e-mail está se perdendo entre eles, podemos torná-lo uma variável. Modificaremos a entrada para: +E está feito. Se houvesse muitos itens na seção `services` e você sentisse que o e-mail se perde entre eles, podemos transformá-lo em um parâmetro. Modificamos a entrada para: ```neon services: - App\Model\ContactFacade(adminEmail: %adminEmail%) ``` -E defina esta variável no arquivo `app/config/common.neon`: +E no arquivo `app/config/common.neon`, definimos esta variável: ```neon parameters: adminEmail: admin@example.com ``` -E está feito! - - -{{sitename: Melhores Práticas}} +E está pronto! diff --git a/best-practices/pt/microsites.texy b/best-practices/pt/microsites.texy new file mode 100644 index 0000000000..92abd0db15 --- /dev/null +++ b/best-practices/pt/microsites.texy @@ -0,0 +1,63 @@ +Como criar micro-sites +********************** + +Imagine que você precisa criar rapidamente um pequeno site para o próximo evento da sua empresa. Deve ser simples, rápido e sem complicações desnecessárias. Você pode pensar que para um projeto tão pequeno não precisa de um framework robusto. Mas e se o uso do framework Nette puder simplificar e acelerar fundamentalmente esse processo? + +Afinal, mesmo ao criar sites simples, você não quer abrir mão do conforto. Você não quer reinventar o que já foi resolvido uma vez. Sinta-se à vontade para ser preguiçoso e deixe-se mimar. O Nette Framework pode ser perfeitamente usado também como um micro framework. + +Como pode ser um microsite assim? Por exemplo, colocando todo o código do site em um único arquivo `index.php` na pasta pública: + +```php +<?php + +require __DIR__ . '/../vendor/autoload.php'; + +$configurator = new Nette\Bootstrap\Configurator; +$configurator->enableTracy(__DIR__ . '/../log'); +$configurator->setTempDirectory(__DIR__ . '/../temp'); + +// cria o contêiner de DI com base na configuração em config.neon +$configurator->addConfig(__DIR__ . '/../app/config.neon'); +$container = $configurator->createContainer(); + +// definimos o roteamento +$router = new Nette\Application\Routers\RouteList; +$container->addService('router', $router); + +// rota para a URL https://example.com/ +$router->addRoute('', function ($presenter, Nette\Http\Request $httpRequest) { + // detectamos o idioma do navegador e redirecionamos para a URL /en ou /de etc. + $supportedLangs = ['en', 'de', 'cs']; + $lang = $httpRequest->detectLanguage($supportedLangs) ?: reset($supportedLangs); + $presenter->redirectUrl("/$lang"); +}); + +// rota para a URL https://example.com/cs ou https://example.com/en +$router->addRoute('<lang cs|en>', function ($presenter, string $lang) { + // exibimos o template correspondente, por exemplo ../templates/en.latte + $template = $presenter->createTemplate() + ->setFile(__DIR__ . '/../templates/' . $lang . '.latte'); + return $template; +}); + +// execute a aplicação! +$container->getByType(Nette\Application\Application::class)->run(); +``` + +Todo o resto serão templates armazenados na pasta pai `/templates`. + +O código PHP em `index.php` primeiro [prepara o ambiente |bootstrap:], depois define as [rotas |application:routing#Roteamento dinâmico com callbacks] e finalmente executa a aplicação. A vantagem é que o segundo parâmetro da função `addRoute()` pode ser um callable, que será executado após a abertura da página correspondente. + + +Por que usar Nette para microsites? +----------------------------------- + +- Programadores que já experimentaram o [Tracy|tracy:] hoje não conseguem imaginar programar algo sem ele. +- Acima de tudo, você usará o sistema de templates [Latte|latte:], porque a partir de 2 páginas você vai querer ter o [layout e conteúdo|latte:template-inheritance] separados. +- E você definitivamente quer confiar no [escaping automático |latte:safety-first] para evitar a vulnerabilidade XSS. +- O Nette também garante que, em caso de erro, nunca sejam exibidas mensagens de erro de programação PHP, mas sim uma página compreensível para o usuário. +- Se você quiser obter feedback dos usuários, por exemplo, na forma de um formulário de contato, você ainda adicionará [formulários|forms:] e [banco de dados|database:]. +- Você também pode facilmente [enviar por e-mail|mail:] os formulários preenchidos. +- Às vezes, pode ser útil usar [cache|caching:], por exemplo, se você baixa e exibe feeds. + +Nos dias de hoje, onde a velocidade e a eficiência são cruciais, é importante ter ferramentas que permitam alcançar resultados sem atrasos desnecessários. O framework Nette oferece exatamente isso - desenvolvimento rápido, segurança e uma ampla gama de ferramentas, como Tracy e Latte, que simplificam o processo. Basta instalar alguns pacotes Nette e construir tal microsite torna-se de repente uma brincadeira de criança. E você sabe que não há nenhuma falha de segurança escondida em lugar nenhum. diff --git a/best-practices/pt/pagination.texy b/best-practices/pt/pagination.texy index dfce31dfc6..a1ec4b1351 100644 --- a/best-practices/pt/pagination.texy +++ b/best-practices/pt/pagination.texy @@ -1,17 +1,16 @@ -Paginação dos resultados do banco de dados -****************************************** +Paginação de resultados do banco de dados +***************************************** .[perex] -Ao desenvolver aplicações web, você frequentemente atende à exigência de imprimir um número restrito de registros em uma página. +Ao criar aplicações web, você frequentemente encontrará a exigência de limitar o número de itens exibidos por página, ou seja, implementar a paginação. -Saímos do estado quando listamos todos os dados sem paginação. Para selecionar os dados do banco de dados, temos a classe ArticleRepository, que contém o construtor e o método `findPublishedArticles`, que retorna todos os artigos publicados ordenados em ordem decrescente de data de publicação. +Partiremos do estado em que exibimos todos os dados sem paginação. Para selecionar dados do banco de dados, temos a classe `ArticleRepository`, que, além do construtor, contém o método `findPublishedArticles`, que retorna todos os artigos publicados ordenados decrescentemente pela data de publicação. ```php namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -31,10 +30,10 @@ class ArticleRepository } ``` -No Apresentador injetamos então a classe do modelo e no método de renderização pediremos os artigos publicados que passamos para o modelo: +No presenter, injetamos a classe do modelo e no método render solicitamos os artigos publicados, que passamos para o template: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -53,11 +52,11 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -No modelo, nós nos encarregaremos de elaborar uma lista de artigos: +No template `default.latte`, cuidamos da exibição dos artigos: ```latte {block content} -<h1>Articles</h1> +<h1>Artigos</h1> <div class="articles"> {foreach $articles as $article} @@ -68,11 +67,11 @@ No modelo, nós nos encarregaremos de elaborar uma lista de artigos: ``` -Desta forma, podemos escrever todos os artigos, mas isto causará problemas quando o número de artigos crescer. Nesse momento, será útil implementar o mecanismo de paginação. +Desta forma, podemos exibir todos os artigos, o que, no entanto, começará a causar problemas quando o número de artigos aumentar. Nesse momento, a implementação de um mecanismo de paginação se torna útil. -Isto garantirá que todos os artigos sejam divididos em várias páginas e mostraremos apenas os artigos de uma página atual. O número total de páginas e a distribuição dos artigos é calculada pelo próprio [paginador |utils:Paginator], dependendo de quantos artigos temos no total e quantos artigos queremos exibir na página. +Ele garantirá que todos os artigos sejam divididos em várias páginas e exibiremos apenas os artigos da página atual. O número total de páginas e a divisão dos artigos serão calculados pelo [Paginator|utils:paginator] com base em quantos artigos temos no total e quantos artigos por página queremos exibir. -No primeiro passo, modificaremos o método para obter artigos na classe de repositório para devolver apenas artigos de uma página. Também acrescentaremos um novo método para obter o número total de artigos no banco de dados, que precisaremos definir um Paginador: +No primeiro passo, modificamos o método para obter artigos na classe do repositório para que ele possa retornar apenas artigos para uma página. Também adicionamos um método para descobrir o número total de artigos no banco de dados, que precisaremos para configurar o Paginator: ```php namespace App\Model; @@ -100,7 +99,7 @@ class ArticleRepository } /** - * Returns the total number of published articles + * Retorna o número total de artigos publicados */ public function getPublishedArticlesCount(): int { @@ -109,12 +108,12 @@ class ArticleRepository } ``` -O próximo passo é editar o apresentador. Nós encaminharemos o número da página atualmente exibida para o método de renderização. Caso este número não seja parte da URL, precisamos definir o valor padrão para a primeira página. +Em seguida, começamos a modificar o presenter. Passaremos o número da página atualmente exibida para o método render. Caso este número não faça parte da URL, definiremos o valor padrão da primeira página (`1`). -Também expandimos o método de renderização para obter a instância Paginator, configurando-a e selecionando os artigos corretos a serem exibidos no modelo. Home PagePresenter terá este aspecto: +Também estenderemos o método render para obter a instância do Paginator, configurá-lo e selecionar os artigos corretos para exibição no template. O `HomePresenter` ficará assim após as modificações: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -128,31 +127,31 @@ class HomePresenter extends Nette\Application\UI\Presenter public function renderDefault(int $page = 1): void { - // Vamos encontrar o número total de artigos publicados + // Descobrimos o número total de artigos publicados $articlesCount = $this->articleRepository->getPublishedArticlesCount(); - // Nós faremos a instância do Paginador e a criaremos + // Criamos uma instância do Paginator e a configuramos $paginator = new Nette\Utils\Paginator; - $paginator->setItemCount($articlesCount); // contagem total de artigos - $paginator->setItemsPerPage(10); // itens por página - $paginator->setPage($page); // número de página real + $paginator->setItemCount($articlesCount); // número total de artigos + $paginator->setItemsPerPage(10); // número de itens por página + $paginator->setPage($page); // número da página atual - // Encontraremos um conjunto limitado de artigos do banco de dados com base nos cálculos do Paginator + // Do banco de dados, extraímos um conjunto limitado de artigos de acordo com o cálculo do Paginator $articles = $this->articleRepository->findPublishedArticles($paginator->getLength(), $paginator->getOffset()); - // que passamos para o modelo + // que passamos para o template $this->template->articles = $articles; - // e também o próprio Paginador para exibir as opções de paginação + // e também o próprio Paginator para exibir as opções de paginação $this->template->paginator = $paginator; } } ``` -O modelo já itera sobre artigos em uma página, basta adicionar links de paginação: +O template agora itera apenas sobre os artigos de uma página, basta adicionar os links de paginação: ```latte {block content} -<h1>Articles</h1> +<h1>Artigos</h1> <div class="articles"> {foreach $articles as $article} @@ -163,34 +162,33 @@ O modelo já itera sobre artigos em uma página, basta adicionar links de pagina <div class="pagination"> {if !$paginator->isFirst()} - <a n:href="default, 1">First</a> + <a n:href="default, 1">Primeira</a>  |  - <a n:href="default, $paginator->page-1">Previous</a> + <a n:href="default, $paginator->page-1">Anterior</a>  |  {/if} - Page {$paginator->getPage()} of {$paginator->getPageCount()} + Página {$paginator->getPage()} de {$paginator->getPageCount()} {if !$paginator->isLast()}  |  - <a n:href="default, $paginator->getPage() + 1">Next</a> + <a n:href="default, $paginator->getPage() + 1">Próxima</a>  |  - <a n:href="default, $paginator->getPageCount()">Last</a> + <a n:href="default, $paginator->getPageCount()">Última</a> {/if} </div> ``` -Foi assim que adicionamos a paginação usando o Paginador. Se for usado o [Nette Database Explorer |database:explorer] em vez do [Nette Database Core |database:core] como camada de banco de dados, podemos implementar paginação mesmo sem o Paginator. A classe `Nette\Database\Table\Selection` contém o método de [paginação |api:Nette\Database\Table\Selection::_ page] com lógica de paginação extraída do Paginator. +Assim, adicionamos a opção de paginação à página usando o Paginator. Caso, em vez do [Nette Database Core |database:sql-way], usemos o [Nette Database Explorer |database:explorer], somos capazes de implementar a paginação de forma ainda mais simples. A classe `Nette\Database\Table\Selection` contém o método [page() |api:Nette\Database\Table\Selection::page()] que encapsula a lógica de paginação. -O repositório terá este aspecto: +O repositório ficará assim com este método de implementação: ```php namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -198,7 +196,6 @@ class ArticleRepository ) { } - public function findPublishedArticles(): Nette\Database\Table\Selection { return $this->database->table('articles') @@ -208,10 +205,10 @@ class ArticleRepository } ``` -Não temos que criar o Paginador no Apresentador, em vez disso usaremos o método do objeto `Selection` devolvido pelo repositório: +No presenter, não precisamos criar o Paginator, usamos diretamente o método `page()` da `Selection` retornada pelo repositório: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -225,25 +222,25 @@ class HomePresenter extends Nette\Application\UI\Presenter public function renderDefault(int $page = 1): void { - // Vamos encontrar artigos publicados + // Extraímos os artigos publicados $articles = $this->articleRepository->findPublishedArticles(); - // e sua parte limitada pelo método de cálculo por página, passaremos ao modelo + // e para o template enviamos apenas sua parte limitada de acordo com o cálculo do método page $lastPage = 0; $this->template->articles = $articles->page($page, 10, $lastPage); - // e os dados necessários para exibir as opções de paginação também + // e também os dados necessários para exibir as opções de paginação $this->template->page = $page; $this->template->lastPage = $lastPage; } } ``` -Como não usamos o Paginador, precisamos editar a seção que mostra os links de paginação: +Como agora não enviamos o Paginator para o template, modificamos a parte que exibe os links de paginação: ```latte {block content} -<h1>Articles</h1> +<h1>Artigos</h1> <div class="articles"> {foreach $articles as $article} @@ -254,24 +251,23 @@ Como não usamos o Paginador, precisamos editar a seção que mostra os links de <div class="pagination"> {if $page > 1} - <a n:href="default, 1">First</a> + <a n:href="default, 1">Primeira</a>  |  - <a n:href="default, $page - 1">Previous</a> + <a n:href="default, $page - 1">Anterior</a>  |  {/if} - Page {$page} of {$lastPage} + Página {$page} de {$lastPage} {if $page < $lastPage}  |  - <a n:href="default, $page + 1">Next</a> + <a n:href="default, $page + 1">Próxima</a>  |  - <a n:href="default, $lastPage">Last</a> + <a n:href="default, $lastPage">Última</a> {/if} </div> ``` -Desta forma, implementamos um mecanismo de paginação sem utilizar um Paginador. +Desta forma, implementamos o mecanismo de paginação usando o Nette Database Explorer sem a necessidade explícita do Paginator. {{priority: -1}} -{{sitename: Melhores Práticas}} diff --git a/best-practices/pt/passing-settings-to-presenters.texy b/best-practices/pt/passing-settings-to-presenters.texy index f6f86b995f..a9f8a66798 100644 --- a/best-practices/pt/passing-settings-to-presenters.texy +++ b/best-practices/pt/passing-settings-to-presenters.texy @@ -1,10 +1,10 @@ -Passagem de configurações para os apresentadores -************************************************ +Passando configurações para presenters +************************************** .[perex] -Você precisa passar argumentos aos apresentadores que não são objetos (por exemplo, informações sobre se está funcionando em modo de depuração, caminhos de diretório, etc.) e, portanto, não pode ser passado automaticamente por cabeamento automático? A solução é encapsulá-los em um objeto `Settings`. +Você precisa passar argumentos para presenters que não são objetos (por exemplo, informação se está rodando em modo debug, caminhos para diretórios, etc.), e portanto não podem ser passados automaticamente via autowiring? A solução é encapsulá-los em um objeto `Settings`. -O serviço `Settings` é uma maneira muito fácil, porém útil de fornecer informações sobre uma aplicação em execução para os apresentadores. Sua forma específica depende inteiramente de suas necessidades particulares. Exemplo: +O serviço `Settings` representa uma maneira muito fácil e útil de fornecer informações sobre a aplicação em execução aos presenters. Sua forma específica depende puramente de suas necessidades particulares. Exemplo: ```php namespace App; @@ -12,15 +12,15 @@ namespace App; class Settings { public function __construct( - // desde PHP 8.1 é possível especificar somente leitura + // a partir do PHP 8.1 é possível usar readonly public bool $debugMode, public string $appDir, - // and so on + // e assim por diante ) {} } ``` -Exemplo de registro para a configuração: +Exemplo de registro na configuração: ```neon services: @@ -30,7 +30,7 @@ services: ) ``` -Quando o apresentador precisa das informações fornecidas por este serviço, ele simplesmente as pede ao construtor: +Quando um presenter precisar das informações fornecidas por este serviço, ele simplesmente as solicitará no construtor: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -47,5 +47,3 @@ class MyPresenter extends Nette\Application\UI\Presenter } } ``` - -{{sitename: Melhores Práticas}} diff --git a/best-practices/pt/post-links.texy b/best-practices/pt/post-links.texy new file mode 100644 index 0000000000..b1ddb720ee --- /dev/null +++ b/best-practices/pt/post-links.texy @@ -0,0 +1,56 @@ +Como usar corretamente links POST +********************************* + +.[perex] +Em aplicações web, especialmente em interfaces administrativas, deve ser uma regra básica que ações que alteram o estado do servidor não devem ser realizadas através do método HTTP GET. Como o nome do método sugere, GET deve ser usado apenas para obter dados, não para alterá-los. Para ações como excluir registros, é mais apropriado usar o método POST. Embora o ideal fosse o método DELETE, ele não pode ser invocado sem JavaScript, por isso historicamente se usa POST. + +Como fazer isso na prática? Use este truque simples. No início do template, crie um formulário auxiliar com o identificador `postForm`, que você usará posteriormente para os botões de exclusão: + +```latte .{file:@layout.latte} +<form method="post" id="postForm"></form> +``` + +Graças a este formulário, você pode usar um botão `<button>` em vez de um link `<a>` clássico, que pode ser estilizado visualmente para parecer um link comum. Por exemplo, o framework CSS Bootstrap oferece as classes `btn btn-link` com as quais você pode garantir que o botão não seja visualmente diferente de outros links. Usando o atributo `form="postForm"`, nós o vinculamos ao formulário pré-preparado: + +```latte .{file:admin.latte} +<table> + <tr n:foreach="$posts as $post"> + <td>{$post->title}</td> + <td> + <button class="btn btn-link" form="postForm" formaction="{link delete $post->id}">delete</button> + <!-- em vez de <a n:href="delete $post->id">delete</a> --> + </td> + </tr> +</table> +``` + +Ao clicar no link, a ação `delete` agora é invocada. Para garantir que as requisições sejam aceitas apenas através do método POST e do mesmo domínio (o que é uma defesa eficaz contra ataques CSRF), use o atributo `#[Requires]`: + +```php .{file:AdminPresenter.php} +use Nette\Application\Attributes\Requires; + +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST', sameOrigin: true)] + public function actionDelete(int $id): void + { + $this->facade->deletePost($id); // código hipotético que exclui o registro + $this->redirect('default'); + } +} +``` + +O atributo existe desde o Nette Application 3.2 e você pode aprender mais sobre suas possibilidades na página [Como usar o atributo #Requires |attribute-requires]. + +Se você estivesse usando o sinal `handleDelete()` em vez da ação `actionDelete()`, não seria necessário especificar `sameOrigin: true`, pois os sinais têm essa proteção definida implicitamente: + +```php .{file:AdminPresenter.php} +#[Requires(methods: 'POST')] +public function handleDelete(int $id): void +{ + $this->facade->deletePost($id); + $this->redirect('this'); +} +``` + +Esta abordagem não só melhora a segurança da sua aplicação, mas também contribui para a adesão aos padrões e práticas corretas da web. Ao utilizar métodos POST para ações que alteram o estado, você alcançará uma aplicação mais robusta e segura. diff --git a/best-practices/pt/presenter-traits.texy b/best-practices/pt/presenter-traits.texy index 65834541f8..0a6ad31278 100644 --- a/best-practices/pt/presenter-traits.texy +++ b/best-practices/pt/presenter-traits.texy @@ -1,14 +1,14 @@ -Composição dos apresentadores a partir de traços -************************************************ +Compondo presenters a partir de traits +************************************** .[perex] -Se precisarmos implementar o mesmo código em vários apresentadores (por exemplo, verificação de que o usuário está logado), é tentador colocar o código em um ancestral comum. A segunda opção é criar características de uso único. +Se precisarmos implementar o mesmo código em vários presenters (por exemplo, verificar se o usuário está logado), uma opção é colocar o código em um ancestral comum. A segunda opção é criar [traits |nette:introduction-to-object-oriented-programming#Traits] de propósito único. -A vantagem desta solução é que cada apresentador pode usar apenas os traços de que realmente precisa, enquanto a herança múltipla não é possível em PHP. +A vantagem desta solução é que cada presenter pode usar exatamente as traits que realmente precisa, enquanto a herança múltipla não é possível em PHP. -Estas características podem aproveitar o fato de que todos os [métodos de injeção |inject-method-attribute#inject methods] são chamados sequencialmente quando o apresentador é criado. Basta ter certeza de que o nome de cada método de injeção é único. +Essas traits podem aproveitar o fato de que, ao criar um presenter, todos os [métodos inject |inject-method-attribute#Métodos inject] são chamados sequencialmente. É apenas necessário garantir que o nome de cada método inject seja único. -As características podem pendurar o código de inicialização em eventos [onStartup ou onRender |application:presenters#Events]. +As traits podem anexar código de inicialização aos eventos [onStartup ou onRender |application:presenters#Eventos]. Exemplos: @@ -36,7 +36,7 @@ trait StandardTemplateFilters } ``` -O apresentador então simplesmente usa estes traços: +O presenter então simplesmente usa essas traits: ```php class ArticlePresenter extends Nette\Application\UI\Presenter @@ -45,6 +45,3 @@ class ArticlePresenter extends Nette\Application\UI\Presenter use RequireLoggedUser; } ``` - - -{{sitename: Melhores Práticas}} diff --git a/best-practices/pt/restore-request.texy b/best-practices/pt/restore-request.texy index ddb376221d..c46f2ecb8d 100644 --- a/best-practices/pt/restore-request.texy +++ b/best-practices/pt/restore-request.texy @@ -1,17 +1,16 @@ -Como voltar a uma página anterior? -********************************** +Como retornar a uma página anterior? +************************************ .[perex] -E se um usuário preenche um formulário e seu login expira? Para evitar perder os dados, salvamos os dados na sessão antes de redirecionar para a página de login. Em Nette, isto é canja. +E se o usuário estiver preenchendo um formulário e sua sessão expirar? Para que ele não perca os dados, antes de redirecionar para a página de login, salvamos a requisição atual na sessão. No Nette, isso é muito fácil. -A solicitação atual pode ser armazenada na sessão usando o método `storeRequest()`, que retorna seu identificador como uma cadeia curta. O método armazena o nome do apresentador atual, a vista e seus parâmetros. -Se um formulário também foi submetido, os valores dos campos (exceto os arquivos carregados) também são salvos. +A requisição atual pode ser salva na sessão usando o método `storeRequest()`, que retorna seu identificador na forma de uma string curta. O método salva o nome do presenter atual, a view e seus parâmetros. Caso um formulário também tenha sido enviado, o conteúdo dos campos também é salvo (com exceção dos arquivos enviados por upload). -O pedido é restaurado pelo método `restoreRequest($key)`, ao qual passamos o identificador recuperado. Isto é redirecionado para o apresentador e visualização originais. Entretanto, se a solicitação salva contiver uma apresentação do formulário, ela será encaminhada ao apresentador original pelo método `forward()`, passando os valores previamente preenchidos para o formulário e deixando-o ser redesenhado. Isto permite que o usuário submeta novamente o formulário e nenhum dado seja perdido. +A restauração da requisição é feita pelo método `restoreRequest($key)`, ao qual passamos o identificador obtido. Ele redireciona para o presenter e view originais. No entanto, se a requisição salva contiver o envio de um formulário, ele vai para o presenter original usando o método `forward()`, passa os valores preenchidos anteriormente para o formulário e o renderiza novamente. O usuário tem assim a possibilidade de reenviar o formulário e nenhum dado é perdido. -É importante destacar que `restoreRequest()` verifica que o usuário recém-identificado é o mesmo que preencheu originalmente o formulário. Caso contrário, ele descarta o pedido e não faz nada. +Importante: `restoreRequest()` verifica se o usuário recém-logado é o mesmo que preencheu o formulário originalmente. Se não for, ele descarta a requisição e não faz nada para evitar vazamento de dados. -Vamos demonstrar tudo com um exemplo. Vamos ter um apresentador `AdminPresenter` no qual os dados estão sendo editados e cujo método `startup()` verifica se o usuário está logado. Se ele não estiver, nós o redirecionamos para `SignPresenter`. Ao mesmo tempo, salvamos o pedido atual e enviamos sua chave para `SignPresenter`. +Vamos mostrar tudo com um exemplo. Temos um presenter `AdminPresenter`, no qual os dados são editados e em cujo método `startup()` verificamos se o usuário está logado. Se não estiver, o redirecionamos para `SignPresenter`. Ao mesmo tempo, salvamos a requisição atual e enviamos sua chave para `SignPresenter`. ```php class AdminPresenter extends Nette\Application\UI\Presenter @@ -27,7 +26,7 @@ class AdminPresenter extends Nette\Application\UI\Presenter } ``` -O apresentador `SignPresenter` conterá um parâmetro persistente `$backlink` no qual a chave está escrita, além do formulário de log-in. Como o parâmetro é persistente, ele será transportado mesmo depois que o formulário de login for enviado. +O presenter `SignPresenter` conterá, além do formulário de login, também um parâmetro persistente `$backlink`, no qual a chave será escrita. Como o parâmetro é persistente, ele será transmitido mesmo após o envio do formulário de login. ```php @@ -41,14 +40,14 @@ class SignPresenter extends Nette\Application\UI\Presenter protected function createComponentSignInForm() { $form = new Nette\Application\UI\Form; - // ... adicionar campos do formulário ... + // ... adicionamos os campos do formulário ... $form->onSuccess[] = [$this, 'signInFormSubmitted']; return $form; } public function signInFormSubmitted($form) { - // ... aqui nós assinamos o usuário em ... + // ... aqui fazemos o login do usuário ... $this->restoreRequest($this->backlink); $this->redirect('Admin:'); @@ -56,9 +55,8 @@ class SignPresenter extends Nette\Application\UI\Presenter } ``` -Passamos a chave do pedido salvo para o método `restoreRequest()` e ele redireciona (ou encaminha) para o apresentador original. +Passamos a chave da requisição salva para o método `restoreRequest()` e ele redireciona (ou avança) para o presenter original. -Entretanto, se a chave é inválida (por exemplo, não existe mais na sessão), o método não faz nada. Portanto, a próxima chamada é `$this->redirect('Admin:')`, que redireciona para `AdminPresenter`. +No entanto, se a chave for inválida (por exemplo, não existir mais na sessão), o método não faz nada. Segue-se então a chamada `$this->redirect('Admin:')`, que redireciona para `AdminPresenter`. {{priority: -1}} -{{sitename: Melhores Práticas}} diff --git a/best-practices/ro/@home.texy b/best-practices/ro/@home.texy index 823591e492..a742e5d015 100644 --- a/best-practices/ro/@home.texy +++ b/best-practices/ro/@home.texy @@ -1,22 +1,24 @@ -Cele mai bune practici +Tutoriale și proceduri ********************** .[perex] -Tutoriale, soluții la probleme comune și cele mai bune practici pentru Nette. +Tutoriale, soluții pentru sarcini frecvente și *best practices* pentru Nette. <div class=documentation> <div> -Aplicație Nette +Aplicații Nette --------------- -- [Injectarea metodelor și atributelor |inject-method-attribute] -- [Compunerea prezentatorilor din trăsături |presenter-traits] -- [Transmiterea de setări către prezentatori |passing-settings-to-presenters] -- [Cum să vă întoarceți la o pagină anterioară |restore-request] -- [Paginarea rezultatelor bazei de date |Pagination] -- [Fragmente dinamice |dynamic-snippets] +- [Metode și atribute inject |inject-method-attribute] +- [Compunerea presenterilor din trait-uri |presenter-traits] +- [Transmiterea setărilor către presenteri |passing-settings-to-presenters] +- [Cum să reveniți la pagina anterioară |restore-request] +- [Paginarea rezultatelor bazei de date |pagination] +- [Snippete dinamice |dynamic-snippets] +- [Cum să utilizați atributul #Requires |attribute-requires] +- [Cum să utilizați corect linkurile POST |post-links] </div> <div> @@ -25,33 +27,35 @@ Aplicație Nette Formulare --------- - [Reutilizarea formularelor |form-reuse] -- [Formular pentru crearea și editarea înregistrării |creating-editing-form] -- [Să creăm un formular de contact |lets-create-contact-form] -- [Căsuțe de selectare dependente |https://blog.nette.org/ro/casete-de-selectare-dependente-in-mod-elegant-in-nette-si-js-pur] +- [Formular pentru crearea și editarea înregistrărilor |creating-editing-form] +- [Creăm un formular de contact |lets-create-contact-form] +- [Selectbox-uri dependente |https://blog.nette.org/ro/dependent-selectboxes-elegantly-in-nette-and-pure-js] </div> <div> -Comună ------- -- [Cum se încarcă fișierul de configurare |bootstrap:] -- [De ce Nette folosește notația constantă PascalCase? |https://blog.nette.org/ro/pentru-mai-putine-tipete-in-cod] -- [De ce Nette nu folosește sufixul Interface? |https://blog.nette.org/ro/prefixele-si-sufixele-nu-se-regasesc-in-numele-interfetelor] -- [Sfaturi pentru utilizarea Composer |composer] -- [Sfaturi privind editorii și instrumentele |editors-and-tools] +Generale +-------- +- [Cum să încărcați un fișier de configurare |bootstrap:] +- [Cum să scrieți micro-site-uri |microsites] +- [De ce Nette utilizează notația PascalCase pentru constante? |https://blog.nette.org/ro/for-less-screaming-in-the-code] +- [De ce Nette nu utilizează sufixul Interface? |https://blog.nette.org/ro/prefixes-and-suffixes-do-not-belong-in-interface-names] +- [Composer: sfaturi de utilizare |composer] +- [Sfaturi pentru editori & instrumente |editors-and-tools] +- [Introducere în programarea orientată pe obiecte |nette:introduction-to-object-oriented-programming] </div> <div> -Soluție de probă ----------------- -- [Exemple Nette |https://github.com/nette-examples] -- [Doctrină și Nette |https://contributte.org/nettrine/] -- [Exemple Contributte |https://contributte.org/examples.html] -- [Site web Doctrine ORM |https://github.com/MinecordNetwork/Website] -- [Început rapid |quickstart:] +Soluții exemplu +--------------- +- [Nette examples |https://github.com/nette-examples] +- [Doctrine & Nette |https://contributte.org/nettrine/] +- [Contributte examples |https://contributte.org/examples.html] +- [Doctrine ORM Website |https://github.com/MinecordNetwork/Website] +- [Quick start |quickstart:] </div> <div> @@ -59,10 +63,7 @@ Soluție de probă Videoclipuri ------------ -Puteți găsi sute de înregistrări de la Posobota și videoclipuri despre Nette sub un singur acoperiș pe "Nette Framework YouTube Channel":https://www.youtube.com/user/NetteFramework. +Sute de înregistrări de la Ultimele Sâmbete și videoclipuri despre Nette pot fi găsite sub un singur acoperiș pe "Canalul Youtube Nette Framework":https://www.youtube.com/user/NetteFramework. </div> </div> - -{{sitename: Best Practices}} -{{leftbar: www:@menu-common}} diff --git a/best-practices/ro/@meta.texy b/best-practices/ro/@meta.texy new file mode 100644 index 0000000000..738844dc28 --- /dev/null +++ b/best-practices/ro/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Tutoriale și proceduri}} +{{leftbar: www:@menu-common}} diff --git a/best-practices/ro/attribute-requires.texy b/best-practices/ro/attribute-requires.texy new file mode 100644 index 0000000000..26bc6f5e2b --- /dev/null +++ b/best-practices/ro/attribute-requires.texy @@ -0,0 +1,177 @@ +Cum se utilizează atributul `#[Requires]` +***************************************** + +.[perex] +Când scrieți o aplicație web, adesea vă confruntați cu nevoia de a restricționa accesul la anumite părți ale aplicației dvs. Poate doriți ca unele cereri să poată trimite date doar folosind un formular (adică prin metoda POST), sau să fie accesibile doar pentru apeluri AJAX. În Nette Framework 3.2 a apărut un nou instrument care vă permite să setați astfel de restricții foarte elegant și clar: atributul `#[Requires]`. + +Atributul este o marcă specială în PHP, pe care o adăugați înaintea definiției unei clase sau metode. Deoarece este de fapt o clasă, pentru ca următoarele exemple să funcționeze, este necesar să specificați clauza use: + +```php +use Nette\Application\Attributes\Requires; +``` + +Atributul `#[Requires]` îl puteți utiliza la clasa presenterului însuși și, de asemenea, la aceste metode: + +- `action<Action>()` +- `render<View>()` +- `handle<Signal>()` +- `createComponent<Name>()` + +Ultimele două metode se referă și la componente, deci atributul îl puteți utiliza și la ele. + +Dacă nu sunt îndeplinite condițiile specificate de atribut, se va declanșa o eroare HTTP 4xx. + + +Metode HTTP +----------- + +Puteți specifica ce metode HTTP (cum ar fi GET, POST etc.) sunt permise pentru acces. De exemplu, dacă doriți să permiteți accesul doar prin trimiterea unui formular, setați: + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +De ce ar trebui să utilizați POST în loc de GET pentru acțiunile care modifică starea și cum să faceți asta? [Citiți ghidul |post-links]. + +Puteți specifica o metodă sau un array de metode. Un caz special este valoarea `'*'`, care permite toate metodele, ceea ce presenterele standard nu permit din [motive de securitate |application:presenters#Verificarea metodei HTTP]. + + +Apel AJAX +--------- + +Dacă doriți ca presenterul sau metoda să fie disponibilă doar pentru cereri AJAX, utilizați: + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Aceeași origine +--------------- + +Pentru a crește securitatea, puteți solicita ca cererea să fie făcută din același domeniu. Astfel preveniți [vulnerabilitatea CSRF |nette:vulnerability-protection#Cross-Site Request Forgery CSRF]: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Pentru metodele `handle<Signal>()`, accesul din același domeniu este solicitat automat. Deci, dacă, dimpotrivă, doriți să permiteți accesul din orice domeniu, specificați: + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Acces prin forward +------------------ + +Uneori este util să restricționați accesul la presenter astfel încât să fie disponibil doar indirect, de exemplu folosind metoda `forward()` sau `switch()` dintr-un alt presenter. Astfel se protejează, de exemplu, error-presenterele, pentru a nu putea fi apelate din URL: + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +În practică, este adesea necesar să se marcheze anumite view-uri, la care se poate ajunge doar pe baza logicii din presenter. Adică, din nou, pentru a nu putea fi deschise direct: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = $this->facade->getProduct($id); + if (!$product) { + $this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Acțiuni specifice +----------------- + +Puteți, de asemenea, să restricționați ca un anumit cod, de exemplu crearea unei componente, să fie disponibil doar pentru acțiuni specifice în presenter: + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +În cazul unei singure acțiuni, nu este necesar să scrieți un array: `#[Requires(actions: 'default')]` + + +Atribute personalizate +---------------------- + +Dacă doriți să utilizați atributul `#[Requires]` în mod repetat cu aceleași setări, puteți crea propriul atribut, care va moșteni `#[Requires]` și îl va seta conform nevoilor. + +De exemplu, `#[SingleAction]` va permite accesul doar prin acțiunea `default`: + +```php +#[\Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Sau `#[RestMethods]` va permite accesul prin toate metodele HTTP utilizate pentru API-uri REST: + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Concluzie +--------- + +Atributul `#[Requires]` vă oferă o mare flexibilitate și control asupra modului în care paginile dvs. web sunt accesibile. Folosind reguli simple, dar puternice, puteți crește securitatea și funcționarea corectă a aplicației dvs. După cum vedeți, utilizarea atributelor în Nette vă poate nu numai ușura munca, ci și securiza. diff --git a/best-practices/ro/composer.texy b/best-practices/ro/composer.texy index ef98ebdb69..2e63994768 100644 --- a/best-practices/ro/composer.texy +++ b/best-practices/ro/composer.texy @@ -1,44 +1,44 @@ -Sfaturi pentru utilizarea Composer -********************************** +Composer: sfaturi de utilizare +****************************** <div class=perex> -Composer este un instrument pentru gestionarea dependențelor în PHP. Vă permite să declarați bibliotecile de care depinde proiectul dumneavoastră, iar acesta le va instala și actualiza pentru dumneavoastră. Vom învăța: +Composer este un instrument pentru gestionarea dependențelor în PHP. Ne permite să enumerăm bibliotecile de care depinde proiectul nostru și le va instala și actualiza pentru noi. Vom arăta: -- cum să instalăm Composer -- cum să-l folosim într-un proiect nou sau existent +- cum se instalează Composer +- utilizarea sa într-un proiect nou sau existent </div> -Instalare .[#toc-installation] -============================== +Instalare +========= -Composer este un fișier executabil `.phar` pe care îl descărcați și îl instalați după cum urmează. +Composer este un fișier executabil `.phar`, pe care îl descărcați și instalați în felul următor: -Windows .[#toc-windows] ------------------------ +Windows +------- -Utilizați programul de instalare oficial [Composer-Setup.exe |https://getcomposer.org/Composer-Setup.exe]. +Utilizați instalatorul oficial [Composer-Setup.exe |https://getcomposer.org/Composer-Setup.exe]. -Linux, macOS .[#toc-linux-macos] --------------------------------- +Linux, macOS +------------ -Aveți nevoie doar de 4 comenzi, pe care le puteți copia de pe [această pagină |https://getcomposer.org/download/]. +Sunt suficiente 4 comenzi, pe care le copiați de pe [această pagină |https://getcomposer.org/download/]. -Mai mult, prin copierea în folderul care se află în sistemul `PATH`, Composer devine accesibil la nivel global: +Apoi, prin plasarea în directorul care se află în `PATH`-ul sistemului, Composer devine accesibil global: ```shell -$ mv ./composer.phar ~/bin/composer # or /usr/local/bin/composer +$ mv ./composer.phar ~/bin/composer # sau /usr/local/bin/composer ``` -Utilizare în proiect .[#toc-use-in-project] -=========================================== +Utilizare în proiect +==================== -Pentru a începe să utilizați Composer în proiectul dumneavoastră, tot ce aveți nevoie este un fișier `composer.json`. Acest fișier descrie dependențele proiectului dumneavoastră și poate conține și alte metadate. Cel mai simplu `composer.json` poate arăta astfel: +Pentru a putea începe să utilizați Composer în proiectul dvs., aveți nevoie doar de fișierul `composer.json`. Acesta descrie dependențele proiectului nostru și poate conține și alte metadate. Un `composer.json` de bază poate arăta deci astfel: ```js { @@ -48,17 +48,17 @@ Pentru a începe să utilizați Composer în proiectul dumneavoastră, tot ce av } ``` -Spunem aici că aplicația noastră (sau biblioteca) depinde de pachetul `nette/database` (numele pachetului este format din numele furnizorului și numele proiectului) și că dorește versiunea care corespunde constrângerii de versiune `^3.0`. +Spunem aici că aplicația noastră (sau biblioteca) necesită pachetul `nette/database` (numele pachetului este format din numele organizației și numele proiectului) și dorește o versiune care corespunde condiției `^3.0` (adică cea mai recentă versiune 3). -Astfel, atunci când avem fișierul `composer.json` în rădăcina proiectului și executăm: +Avem deci în rădăcina proiectului fișierul `composer.json` și rulăm instalarea: ```shell composer update ``` -Composer va descărca baza de date Nette în directorul `vendor`. De asemenea, creează un fișier `composer.lock`, care conține informații despre versiunile exacte ale bibliotecilor pe care le-a instalat. +Composer va descărca Nette Database în directorul `vendor/`. Apoi va crea fișierul `composer.lock`, care conține informații despre ce versiuni exacte ale bibliotecilor a instalat. -Composer generează un fișier `vendor/autoload.php`. Puteți include pur și simplu acest fișier și puteți începe să utilizați clasele pe care aceste biblioteci le furnizează fără nicio muncă suplimentară: +Composer generează fișierul `vendor/autoload.php`, pe care îl putem include simplu și începe să folosim bibliotecile fără nicio altă muncă: ```php require __DIR__ . '/vendor/autoload.php'; @@ -67,52 +67,52 @@ $db = new Nette\Database\Connection('sqlite::memory:'); ``` -Actualizarea pachetelor la cele mai recente versiuni .[#toc-update-packages-to-the-latest-versions] -=================================================================================================== +Actualizarea pachetelor la cele mai recente versiuni +==================================================== -Pentru a actualiza toate pachetele utilizate la cea mai recentă versiune în conformitate cu constrângerile de versiune definite în `composer.json`, utilizați comanda `composer update`. De exemplu, pentru dependența `"nette/database": "^3.0"`, se va instala cea mai recentă versiune 3.x.x, dar nu și versiunea 4. +Actualizarea bibliotecilor utilizate la cele mai recente versiuni conform condițiilor definite în `composer.json` este responsabilitatea comenzii `composer update`. De ex., pentru dependența `"nette/database": "^3.0"`, va instala cea mai recentă versiune 3.x.x, dar nu și versiunea 4. -Pentru a actualiza constrângerile de versiune din fișierul `composer.json` la, de exemplu, `"nette/database": "^4.1"`, pentru a permite instalarea celei mai recente versiuni, utilizați comanda `composer require nette/database`. +Pentru a actualiza condițiile din fișierul `composer.json`, de exemplu la `"nette/database": "^4.1"`, pentru a putea instala cea mai recentă versiune, utilizați comanda `composer require nette/database`. -Pentru a actualiza toate pachetele Nette utilizate, ar fi necesar să le enumerați pe toate în linia de comandă, de ex: +Pentru a actualiza toate pachetele Nette utilizate, ar fi necesar să le enumerați pe toate în linia de comandă, de ex.: ```shell composer require nette/application nette/forms latte/latte tracy/tracy ... ``` -Ceea ce nu este practic. Prin urmare, utilizați un script simplu "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff care va face acest lucru pentru dumneavoastră: +Ceea ce este nepractic. Utilizați, prin urmare, scriptul simplu "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff, care va face asta pentru dvs.: ```shell php composer-frontline.php ``` -Crearea unui nou proiect .[#toc-creating-new-project] -===================================================== +Crearea unui proiect nou +======================== -Un nou proiect Nette poate fi creat prin executarea unei comenzi simple: +Un proiect nou pe Nette îl creați folosind o singură comandă: ```shell -composer create-project nette/web-project name-of-the-project +composer create-project nette/web-project nume-proiect ``` -În loc de `name-of-the-project` trebuie să furnizați numele directorului pentru proiectul dumneavoastră și să executați comanda. Composer va prelua depozitul `nette/web-project` de pe GitHub, care conține deja fișierul `composer.json`, și imediat după aceea va instala chiar Nette Framework. Singurul lucru care rămâne este să [verificați permisiunile de scriere |nette:troubleshooting#setting-directory-permissions] pe directoarele `temp/` și `log/` și sunteți gata de plecare. +Ca `nume-proiect` introduceți numele directorului pentru proiectul dvs. și confirmați. Composer va descărca repository-ul `nette/web-project` de pe GitHub, care conține deja fișierul `composer.json`, și imediat după aceea Nette Framework. Ar trebui să fie suficient doar să [setați permisiunile |nette:troubleshooting#Setarea permisiunilor pentru directoare] de scriere pentru directoarele `temp/` și `log/` și proiectul ar trebui să prindă viață. -Dacă știți pe ce versiune de PHP va fi găzduit proiectul, nu uitați să [o configura |#PHP Version]ți. +Dacă știți pe ce versiune de PHP va fi găzduit proiectul, nu uitați [să o setați |#Versiunea PHP]. -Versiunea PHP .[#toc-php-version] -================================= +Versiunea PHP +============= -Composer instalează întotdeauna versiunile de pachete care sunt compatibile cu versiunea de PHP pe care o folosiți în prezent (sau, mai degrabă, versiunea de PHP utilizată în linia de comandă atunci când executați Composer). Care, probabil, nu este aceeași versiune pe care o folosește gazda dvs. web. De aceea, este foarte important să adăugați informații despre versiunea PHP de pe gazda dumneavoastră în fișierul `composer.json`. După aceea, vor fi instalate numai versiunile de pachete compatibile cu gazda. +Composer instalează întotdeauna acele versiuni de pachete care sunt compatibile cu versiunea de PHP pe care o utilizați în prezent (mai precis, cu versiunea de PHP utilizată în linia de comandă la rularea Composerului). Ceea ce, însă, probabil nu este aceeași versiune pe care o utilizează găzduirea dvs. De aceea, este foarte important să adăugați în fișierul `composer.json` informații despre versiunea PHP de pe găzduire. Apoi se vor instala doar versiuni de pachete compatibile cu găzduirea. -De exemplu, pentru a seta proiectul să ruleze pe PHP 8.2.3, utilizați comanda: +Faptul că proiectul va rula, de exemplu, pe PHP 8.2.3, îl setăm cu comanda: ```shell composer config platform.php 8.2.3 ``` -În acest fel, versiunea este scrisă în fișierul `composer.json`: +Astfel se va scrie versiunea în fișierul `composer.json`: ```js { @@ -124,8 +124,7 @@ composer config platform.php 8.2.3 } ``` -Cu toate acestea, numărul versiunii PHP este, de asemenea, listat în altă parte în fișier, în secțiunea `require`. În timp ce primul număr specifică versiunea pentru care vor fi instalate pachetele, al doilea număr indică versiunea pentru care este scrisă aplicația în sine. -(Desigur, nu are sens ca aceste versiuni să fie diferite, așa că dubla înscriere este o redundanță). Setați această versiune cu ajutorul comenzii: +Cu toate acestea, numărul versiunii PHP se specifică și în alt loc al fișierului, și anume în secțiunea `require`. În timp ce primul număr specifică pentru ce versiune se vor instala pachetele, al doilea număr spune pentru ce versiune este scrisă aplicația însăși. Și conform acestuia, de exemplu, PhpStorm setează *PHP language level*. (Desigur, nu are sens ca aceste versiuni să difere, deci dubla scriere este o neglijență.) Această versiune o setați cu comanda: ```shell composer require php 8.2.3 --no-update @@ -142,66 +141,72 @@ Sau direct în fișierul `composer.json`: ``` -Rapoarte false .[#toc-false-reports] -==================================== +Ignorarea versiunii PHP +======================= -La actualizarea pachetelor sau la schimbarea numerelor de versiune, apar conflicte. Un pachet are cerințe care intră în conflict cu un alt pachet și așa mai departe. Cu toate acestea, Composer tipărește ocazional un mesaj fals. Acesta raportează un conflict care nu există cu adevărat. În acest caz, este util să ștergeți fișierul `composer.lock` și să încercați din nou. +Pachetele au de obicei specificată atât cea mai mică versiune de PHP cu care sunt compatibile, cât și cea mai mare cu care sunt testate. Dacă intenționați să utilizați o versiune de PHP și mai nouă, de exemplu din motive de testare, Composer va refuza să instaleze un astfel de pachet. Soluția este opțiunea `--ignore-platform-req=php+`, care face ca Composer să ignore limitele superioare ale versiunii PHP solicitate. -Dacă mesajul de eroare persistă, atunci acesta are o intenție serioasă și trebuie să citiți din el ce trebuie să modificați și cum. +Mesaje false +============ -Packagist.org - Depozitul global .[#toc-packagist-org-global-repository] -======================================================================== +La actualizarea pachetelor sau modificarea numerelor de versiuni, se întâmplă să apară conflicte. Un pachet are cerințe care sunt în contradicție cu altul și altele asemenea. Composer, însă, uneori afișează mesaje false. Raportează un conflict care în realitate nu există. În acest caz, ajută ștergerea fișierului `composer.lock` și încercarea din nou. -[Packagist |https://packagist.org] este principalul depozit de pachete, în care Composer încearcă să caute pachete, dacă nu i se spune altfel. De asemenea, puteți publica propriile pachete aici. +Dacă mesajul de eroare persistă, atunci este serios și trebuie să citiți din el ce și cum să modificați. -Ce se întâmplă dacă nu dorim depozitul central .[#toc-what-if-we-don-t-want-the-central-repository] ---------------------------------------------------------------------------------------------------- +Packagist.org - repository central +================================== + +[Packagist |https://packagist.org] este repository-ul principal în care Composer încearcă să caute pachete, dacă nu îi spunem altfel. Putem publica aici și propriile pachete. + -Dacă avem aplicații sau biblioteci interne în cadrul companiei noastre, care nu pot fi găzduite public pe Packagist, putem crea propriile noastre depozite pentru aceste proiecte. +Ce facem dacă nu vrem să folosim repository-ul central? +------------------------------------------------------- -Mai multe despre depozite în [documentația oficială |https://getcomposer.org/doc/05-repositories.md#repositories]. +Dacă avem aplicații interne ale companiei, pe care pur și simplu nu le putem găzdui public, atunci ne creăm pentru ele un repository al companiei. +Mai multe despre subiectul repository-urilor [în documentația oficială |https://getcomposer.org/doc/05-repositories.md#repositories]. -Încărcare automată .[#toc-autoloading] -====================================== -O caracteristică cheie a Composer este că oferă încărcare automată pentru toate clasele pe care le instalează, pe care o începeți prin includerea unui fișier `vendor/autoload.php`. +Autoloading +=========== -Cu toate acestea, este de asemenea posibil să utilizați Composer pentru a încărca alte clase în afara dosarului `vendor`. Prima opțiune este de a lăsa Composer să scaneze dosarele și subdosarele definite, să găsească toate clasele și să le includă în autoloader. Pentru a face acest lucru, setați `autoload > classmap` în `composer.json`: +O caracteristică esențială a Composerului este că oferă autoloading pentru toate clasele instalate de el, pe care îl porniți prin includerea fișierului `vendor/autoload.php`. + +Cu toate acestea, este posibil să utilizați Composer și pentru încărcarea altor clase și în afara directorului `vendor`. Prima opțiune este să lăsați Composer să caute în directoarele și subdirectoarele definite, să găsească toate clasele și să le includă în autoloader. Acest lucru se realizează prin setarea `autoload > classmap` în `composer.json`: ```js { "autoload": { "classmap": [ - "src/", # includes the src/ folder and its subfolders + "src/", # include directorul src/ și subdirectoarele sale ] } } ``` -Ulterior, este necesar să executați comanda `composer dumpautoload` cu fiecare modificare și să lăsați tabelele de autoloading să se regenereze. Acest lucru este extrem de incomod și este mult mai bine să încredințați această sarcină lui [RobotLoader |robot-loader:], care efectuează aceeași activitate în mod automat în fundal și mult mai rapid. +Ulterior, este necesar la fiecare modificare să rulați comanda `composer dumpautoload` și să lăsați tabelele de autoloading să se regenereze. Acest lucru este extrem de incomod și mult mai bine este să încredințați această sarcină [RobotLoaderului |robot-loader:], care efectuează aceeași activitate automat în fundal și mult mai rapid. -A doua opțiune este să urmați [PSR-4 |https://www.php-fig.org/psr/psr-4/]. Spunând simplu, este un sistem în care spațiile de nume și numele claselor corespund structurii directoarelor și numelor de fișiere, adică `App\Router\RouterFactory` se află în fișierul `/path/to/App/Router/RouterFactory.php`. Exemplu de configurare: +A doua opțiune este să respectați [PSR-4 |https://www.php-fig.org/psr/psr-4/]. Simplificat spus, este vorba despre un sistem în care spațiile de nume și numele claselor corespund structurii directoarelor și numelor fișierelor, adică, de ex., `App\Core\RouterFactory` va fi în fișierul `/path/to/App/Core/RouterFactory.php`. Exemplu de configurare: ```js { "autoload": { "psr-4": { - "App\\": "app/" # the App\ namespace is in the app/ directory + "App\\": "app/" # spațiul de nume App\ este în directorul app/ } } } ``` -Consultați [Documentația Composer |https://getcomposer.org/doc/04-schema.md#psr-4] pentru a afla exact cum se poate configura acest comportament. +Cum să configurați exact comportamentul veți afla în [documentația Composerului |https://getcomposer.org/doc/04-schema.md#psr-4]. -Testarea noilor versiuni .[#toc-testing-new-versions] -===================================================== +Testarea versiunilor noi +======================== -Doriți să testați o nouă versiune de dezvoltare a unui pachet. Cum se face acest lucru? În primul rând, adăugați această pereche de opțiuni la fișierul `composer.json`, care vă va permite să instalați versiuni de dezvoltare ale pachetelor, dar va face acest lucru numai dacă nu există o combinație de versiuni stabile care să îndeplinească cerințele: +Doriți să testați o nouă versiune de dezvoltare a unui pachet. Cum să faceți asta? Mai întâi, adăugați în fișierul `composer.json` această pereche de opțiuni, care permite instalarea versiunilor de dezvoltare ale pachetelor, însă recurge la aceasta doar în cazul în care nu există nicio combinație de versiuni stabile care să satisfacă cerințele: ```js { @@ -210,9 +215,9 @@ Doriți să testați o nouă versiune de dezvoltare a unui pachet. Cum se face a } ``` -De asemenea, vă recomandăm să ștergeți fișierul `composer.lock`, deoarece uneori Composer refuză în mod incomprehensibil să instaleze, iar acest lucru va rezolva problema. +Apoi, recomandăm ștergerea fișierului `composer.lock`, uneori Composer refuză inexplicabil instalarea și acest lucru rezolvă problema. -Să spunem că pachetul este `nette/utils` și că noua versiune este 4.0. Îl instalați cu ajutorul comenzii: +Să presupunem că este vorba despre pachetul `nette/utils` și noua versiune are numărul 4.0. O instalați cu comanda: ```shell composer require nette/utils:4.0.x-dev @@ -224,20 +229,19 @@ Sau puteți instala o versiune specifică, de exemplu 4.0.0-RC2: composer require nette/utils:4.0.0-RC2 ``` -Dacă un alt pachet depinde de bibliotecă și este blocat la o versiune mai veche (de exemplu, `^3.1`), este ideal să actualizați pachetul pentru a funcționa cu noua versiune. -Cu toate acestea, dacă doriți doar să ocoliți limitarea și să forțați Composer să instaleze versiunea de dezvoltare și să pretindeți că este o versiune mai veche (de exemplu, 3.1.6), puteți utiliza cuvântul cheie `as`: +Dar dacă de bibliotecă depinde un alt pachet, care este blocat la o versiune mai veche (de ex. `^3.1`), atunci este ideal să actualizați pachetul, pentru a funcționa cu noua versiune. Dacă însă doriți doar să ocoliți restricția și să forțați Composer să instaleze versiunea de dezvoltare și să pretindă că este o versiune mai veche (de ex. 3.1.6), puteți utiliza cuvântul cheie `as`: ```shell composer require nette/utils "4.0.x-dev as 3.1.6" ``` -Comenzi de apelare .[#toc-calling-commands] -=========================================== +Apelarea comenzilor +=================== -Puteți apela propriile comenzi și scripturi personalizate prin Composer ca și cum ar fi comenzi Composer native. Scripturile localizate în folderul `vendor/bin` nu trebuie să specificați acest folder. +Prin Composer se pot apela comenzi și scripturi proprii pre-pregătite, ca și cum ar fi comenzi native ale Composerului. Pentru scripturile care se află în directorul `vendor/bin`, nu este necesar să specificați acest director. -Ca exemplu, definim un script în fișierul `composer.json` care utilizează [Nette Tester |tester:] pentru a rula teste: +Ca exemplu, definim în fișierul `composer.json` un script care, folosind [Nette Tester |tester:], rulează testele: ```js { @@ -247,34 +251,32 @@ Ca exemplu, definim un script în fișierul `composer.json` care utilizează [Ne } ``` -Apoi executăm testele cu `composer tester`. Putem apela comanda chiar dacă nu ne aflăm în folderul rădăcină al proiectului, ci într-un subdirectoriu. +Testele le rulăm apoi folosind `composer tester`. Comanda o putem apela și în cazul în care nu ne aflăm în directorul rădăcină al proiectului, ci într-un subdirector. -Trimiteți mulțumiri .[#toc-send-thanks] -======================================= +Trimiteți mulțumiri +=================== -Vă vom arăta un truc care îi va face fericiți pe autorii open source. Puteți să acordați cu ușurință o stea pe GitHub bibliotecilor pe care le folosește proiectul dumneavoastră. Trebuie doar să instalați biblioteca `symfony/thanks`: +Vă vom arăta un truc prin care veți bucura autorii de open source. Într-un mod simplu, dați o stea pe GitHub bibliotecilor pe care proiectul dvs. le utilizează. Este suficient să instalați biblioteca `symfony/thanks`: ```shell composer global require symfony/thanks ``` -Și apoi rulați: +Și apoi să rulați: ```shell composer thanks ``` -Încearcă! +Încercați! -Configurație .[#toc-configuration] -================================== +Configurare +=========== -Composer este strâns integrat cu instrumentul de control al versiunilor [Git |https://git-scm.com]. Dacă nu folosiți Git, este necesar să o comunicați lui Composer: +Composer este strâns legat de instrumentul de versionare [Git |https://git-scm.com]. Dacă nu îl aveți instalat, trebuie să îi spuneți Composerului să nu îl utilizeze: ```shell composer -g config preferred-install dist ``` - -{{sitename: Best Practices}} diff --git a/best-practices/ro/creating-editing-form.texy b/best-practices/ro/creating-editing-form.texy index a8ed0bcb95..8c58fc221a 100644 --- a/best-practices/ro/creating-editing-form.texy +++ b/best-practices/ro/creating-editing-form.texy @@ -1,16 +1,16 @@ -Formular pentru crearea și modificarea unei înregistrări -******************************************************** +Formular pentru crearea și editarea înregistrărilor +*************************************************** .[perex] -Cum se implementează în mod corespunzător adăugarea și editarea unei înregistrări în Nette, utilizând același formular pentru ambele? +Cum să implementăm corect adăugarea și editarea unei înregistrări în Nette, folosind același formular pentru ambele operațiuni? -În multe cazuri, formularele de adăugare și de editare a unei înregistrări sunt identice, diferind doar prin eticheta de pe buton. Vom prezenta exemple de prezentări simple în care folosim formularul mai întâi pentru a adăuga o înregistrare, apoi pentru a o edita și, în final, vom combina cele două soluții. +În multe cazuri, formularele pentru adăugarea și editarea înregistrărilor sunt identice, diferind poate doar prin eticheta butonului. Vom prezenta exemple de presenteri simpli, unde vom folosi formularul mai întâi pentru adăugarea unei înregistrări, apoi pentru editare și, în final, vom combina ambele soluții. -Adăugarea unei înregistrări .[#toc-adding-a-record] ---------------------------------------------------- +Adăugarea unei înregistrări +--------------------------- -Un exemplu de prezentator utilizat pentru a adăuga o înregistrare. Vom lăsa activitatea efectivă a bazei de date pe seama clasei `Facade`, al cărei cod nu este relevant pentru acest exemplu. +Exemplu de presenter utilizat pentru adăugarea unei înregistrări. Vom lăsa lucrul efectiv cu baza de date în seama clasei `Facade`, al cărei cod nu este esențial pentru exemplu. ```php @@ -27,7 +27,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $form = new Form; - // ... adăugați câmpuri de formular ... + // ... adăugăm câmpurile formularului ... $form->onSuccess[] = [$this, 'recordFormSucceeded']; return $form; @@ -35,8 +35,8 @@ class RecordPresenter extends Nette\Application\UI\Presenter public function recordFormSucceeded(Form $form, array $data): void { - $this->facade->add($data); // adăugarea unei înregistrări în baza de date - $this->flashMessage('Successfully added'); + $this->facade->add($data); // adăugarea înregistrării în baza de date + $this->flashMessage('Adăugat cu succes'); $this->redirect('...'); } @@ -48,10 +48,10 @@ class RecordPresenter extends Nette\Application\UI\Presenter ``` -Editarea unei înregistrări .[#toc-editing-a-record] ---------------------------------------------------- +Editarea unei înregistrări +-------------------------- -Acum să vedem cum ar arăta un prezentator utilizat pentru a edita o înregistrare: +Acum vom arăta cum ar arăta un presenter utilizat pentru editarea unei înregistrări: ```php @@ -70,10 +70,10 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $record = $this->facade->get($id); if ( - !$record // să verifice existența înregistrării + !$record // verificarea existenței înregistrării || !$this->facade->isEditAllowed(/*...*/) // verificarea permisiunilor ) { - $this->error(); // Eroare 404 + $this->error(); // eroare 404 } $this->record = $record; @@ -81,32 +81,32 @@ class RecordPresenter extends Nette\Application\UI\Presenter protected function createComponentRecordForm(): Form { - // verificați dacă acțiunea este "edit" (editare) + // verificăm dacă acțiunea este 'edit' if ($this->getAction() !== 'edit') { $this->error(); } $form = new Form; - // ... adăugați câmpuri de formular ... + // ... adăugăm câmpurile formularului ... - $form->setDefaults($this->record); // setați valorile implicite + $form->setDefaults($this->record); // setarea valorilor implicite $form->onSuccess[] = [$this, 'recordFormSucceeded']; return $form; } public function recordFormSucceeded(Form $form, array $data): void { - $this->facade->update($this->record->id, $data); // actualizează înregistrarea - $this->flashMessage('Successfully updated'); + $this->facade->update($this->record->id, $data); // actualizarea înregistrării + $this->flashMessage('Actualizat cu succes'); $this->redirect('...'); } } ``` -În metoda *action*, care este invocată chiar la începutul [ciclului de viață al prezentatorului |application:presenters#Life Cycle of Presenter], se verifică existența înregistrării și permisiunea utilizatorului de a o edita. +În metoda *action*, care se execută chiar la începutul [ciclului de viață al presenterului |application:presenters#Ciclul de viață al presenterului], verificăm existența înregistrării și permisiunea utilizatorului de a o edita. -Stocăm înregistrarea în proprietatea `$record`, astfel încât să fie disponibilă în metoda `createComponentRecordForm()` pentru setarea valorilor implicite și `recordFormSucceeded()` pentru ID. O soluție alternativă ar fi să setați valorile implicite direct în `actionEdit()`, iar valoarea ID, care face parte din URL, este recuperată cu ajutorul `getParameter('id')`: +Salvăm înregistrarea în proprietatea `$record`, pentru a o avea disponibilă în metoda `createComponentRecordForm()` pentru setarea valorilor implicite și în `recordFormSucceeded()` pentru ID. O soluție alternativă ar fi setarea valorilor implicite direct în `actionEdit()` și obținerea valorii ID-ului, care face parte din URL, folosind `getParameter('id')`: ```php @@ -114,12 +114,12 @@ Stocăm înregistrarea în proprietatea `$record`, astfel încât să fie dispon { $record = $this->facade->get($id); if ( - // verificarea existenței și verificarea permisiunilor + // verificarea existenței și controlul permisiunilor ) { $this->error(); } - // stabilirea valorilor implicite ale formularelor + // setarea valorilor implicite ale formularului $this->getComponent('recordForm') ->setDefaults($record); } @@ -133,13 +133,13 @@ Stocăm înregistrarea în proprietatea `$record`, astfel încât să fie dispon } ``` -Cu toate acestea, și aceasta ar trebui să fie **cea mai importantă concluzie din tot acest cod**, trebuie să ne asigurăm că acțiunea este într-adevăr `edit` atunci când creăm formularul. Pentru că, altfel, validarea din metoda `actionEdit()` nu ar avea loc deloc! +Cu toate acestea, și aceasta ar trebui să fie **cea mai importantă concluzie a întregului cod**, trebuie să ne asigurăm la crearea formularului că acțiunea este într-adevăr `edit`. Altfel, verificarea din metoda `actionEdit()` nu ar avea loc deloc! -Același formular pentru adăugare și editare .[#toc-same-form-for-adding-and-editing] ------------------------------------------------------------------------------------- +Același formular pentru adăugare și editare +------------------------------------------- -Și acum vom combina ambii prezentatori într-unul singur. Fie putem distinge ce acțiune este implicată în metoda `createComponentRecordForm()` și configura formularul în consecință, fie putem lăsa direct pe seama metodelor de acțiune și să scăpăm de condiție: +Și acum vom combina ambii presenteri într-unul singur. Fie am putea distinge în metoda `createComponentRecordForm()` despre ce acțiune este vorba și să configurăm formularul în consecință, fie putem lăsa acest lucru direct pe seama metodelor action și să scăpăm de condiție: ```php @@ -160,47 +160,46 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $record = $this->facade->get($id); if ( - !$record // să verifice existența înregistrării + !$record // verificarea existenței înregistrării || !$this->facade->isEditAllowed(/*...*/) // verificarea permisiunilor ) { - $this->error(); // Eroare 404 + $this->error(); // eroare 404 } $form = $this->getComponent('recordForm'); - $form->setDefaults($record); // setați valorile implicite + $form->setDefaults($record); // setarea valorilor implicite $form->onSuccess[] = [$this, 'editingFormSucceeded']; } protected function createComponentRecordForm(): Form { - // verifică dacă acțiunea este "add" sau "edit". + // verificăm dacă acțiunea este 'add' sau 'edit' if (!in_array($this->getAction(), ['add', 'edit'])) { $this->error(); } $form = new Form; - // ... adaugă câmpuri de formular ... + // ... adăugăm câmpurile formularului ... return $form; } public function addingFormSucceeded(Form $form, array $data): void { - $this->facade->add($data); // adăugarea unei înregistrări în baza de date - $this->flashMessage('Successfully added'); + $this->facade->add($data); // adăugarea înregistrării în baza de date + $this->flashMessage('Adăugat cu succes'); $this->redirect('...'); } public function editingFormSucceeded(Form $form, array $data): void { $id = (int) $this->getParameter('id'); - $this->facade->update($id, $data); // actualizează înregistrarea - $this->flashMessage('Successfully updated'); + $this->facade->update($id, $data); // actualizarea înregistrării + $this->flashMessage('Actualizat cu succes'); $this->redirect('...'); } } ``` {{priority: -1}} -{{sitename: Best Practices}} diff --git a/best-practices/ro/dynamic-snippets.texy b/best-practices/ro/dynamic-snippets.texy index c5201295d5..f6879f7488 100644 --- a/best-practices/ro/dynamic-snippets.texy +++ b/best-practices/ro/dynamic-snippets.texy @@ -1,7 +1,7 @@ -Fragmente dinamice -****************** +Snippets dinamice +***************** -Destul de des în dezvoltarea aplicațiilor există necesitatea de a efectua operații AJAX, de exemplu, în rândurile individuale ale unui tabel sau în elementele unei liste. Ca exemplu, putem alege să listăm articole, permițând utilizatorului logat să selecteze un rating "îmi place/nu-mi place" pentru fiecare dintre ele. Codul prezentatorului și șablonul corespunzător fără AJAX vor arăta cam așa (enumăr cele mai importante fragmente, codul presupune existența unui serviciu pentru marcarea ratingurilor și obținerea unei colecții de articole - implementarea specifică nu este importantă în scopul acestui tutorial): +Destul de des, în timpul dezvoltării aplicațiilor, apare nevoia de a efectua operațiuni AJAX, de exemplu, pe rândurile individuale ale unui tabel sau pe elementele unei liste. Ca exemplu, putem alege afișarea articolelor, permițând fiecărui utilizator autentificat să aleagă evaluarea "îmi place/nu-mi place". Codul presenterului și șablonul corespunzător fără AJAX vor arăta aproximativ astfel (prezint cele mai importante fragmente, codul presupune existența unui serviciu pentru marcarea evaluărilor și obținerea colecției de articole - implementarea specifică nu este importantă pentru scopul acestui ghid): ```php public function handleLike(int $articleId): void @@ -24,26 +24,26 @@ public function handleUnlike(int $articleId): void <h2>{$article->title}</h2> <div class="content">{$article->content}</div> {if !$article->liked} - <a n:href="like! $article->id" class=ajax>I like it</a> + <a n:href="like! $article->id" class=ajax>îmi place</a> {else} - <a n:href="unlike! $article->id" class=ajax>I don't like it anymore</a> + <a n:href="unlike! $article->id" class=ajax>nu-mi mai place</a> {/if} </article> ``` -Ajaxizare .[#toc-ajaxization] -============================= +Ajaxizare +========= -Să aducem acum AJAX în această aplicație simplă. Modificarea ratingului unui articol nu este suficient de importantă pentru a necesita o cerere HTTP cu redirecționare, așa că, în mod ideal, ar trebui să se facă cu AJAX în fundal. Vom folosi [scriptul handler din add-ons |https://componette.org/vojtech-dobes/nette.ajax.js/] cu convenția obișnuită ca legăturile AJAX să aibă clasa CSS `ajax`. +Să echipăm acum această aplicație simplă cu AJAX. Schimbarea evaluării unui articol nu este atât de importantă încât să necesite o redirecționare, așa că ideal ar fi să se desfășoare prin AJAX în fundal. Vom folosi [scriptul de ajutor din add-on-uri |application:ajax#Naja] cu convenția obișnuită că linkurile AJAX au clasa CSS `ajax`. -Totuși, cum să o facem în mod specific? Nette oferă 2 modalități: modalitatea cu fragmente dinamice și modalitatea cu componente. Ambele au avantajele și dezavantajele lor, așa că le vom prezenta pe rând. +Dar cum facem asta concret? Nette oferă 2 căi: calea așa-numitelor snippets dinamice și calea componentelor. Ambele au avantaje și dezavantaje, așa că le vom prezenta pe rând. -Metoda snippeturilor dinamice .[#toc-the-dynamic-snippets-way] -============================================================== +Calea snippetelor dinamice +========================== -În terminologia Latte, un fragment dinamic este un caz specific de utilizare a etichetei `{snippet}` în care se utilizează o variabilă în numele fragmentului. Un astfel de snippet nu poate fi găsit oriunde în șablon - trebuie să fie inclus într-un snippet static, adică unul obișnuit, sau în interiorul unui `{snippetArea}`. Am putea modifica șablonul nostru după cum urmează. +Un snippet dinamic înseamnă, în terminologia Latte, un caz specific de utilizare a tag-ului `{snippet}`, unde în numele snippetului este folosită o variabilă. Un astfel de snippet nu poate fi găsit oriunde în șablon - trebuie să fie încapsulat într-un snippet static, adică unul obișnuit, sau în interiorul `{snippetArea}`. Am putea modifica șablonul nostru astfel: ```latte @@ -53,18 +53,18 @@ Metoda snippeturilor dinamice .[#toc-the-dynamic-snippets-way] <div class="content">{$article->content}</div> {snippet article-{$article->id}} {if !$article->liked} - <a n:href="like! $article->id" class=ajax>I like it</a> + <a n:href="like! $article->id" class=ajax>îmi place</a> {else} - <a n:href="unlike! $article->id" class=ajax>I don't like it anymore</a> + <a n:href="unlike! $article->id" class=ajax>nu-mi mai place</a> {/if} {/snippet} </article> {/snippet} ``` -Fiecare articol definește acum un singur snippet, care are un ID de articol în titlu. Toate aceste fragmente sunt apoi grupate într-un singur fragment numit `articlesContainer`. Dacă omitem acest snippet de împachetare, Latte ne va alerta cu o excepție. +Fiecare articol definește acum un snippet care are ID-ul articolului în nume. Toate aceste snippets sunt apoi împachetate împreună într-un singur snippet cu numele `articlesContainer`. Dacă am omite acest snippet încapsulator, Latte ne-ar avertiza cu o excepție. -Tot ce mai rămâne de făcut este să adăugăm redesenarea în prezentator - doar redesenăm învelișul static. +Ne rămâne să adăugăm redesenarea în presenter - este suficient să redesenăm învelișul static. ```php public function handleLike(int $articleId): void @@ -79,11 +79,11 @@ public function handleLike(int $articleId): void } ``` -Modificați metoda soră `handleUnlike()` în același mod, iar AJAX este gata de funcționare! +Modificăm în mod similar și metoda soră `handleUnlike()`, iar AJAX-ul este funcțional! -Soluția are totuși un dezavantaj. Dacă cercetăm mai bine modul în care funcționează cererea AJAX, vom descoperi că, deși aplicația pare eficientă în aparență (returnează un singur fragment pentru un anumit articol), de fapt, redă toate fragmentele pe server. Acesta a plasat fragmentul dorit în sarcina noastră utilă și le-a eliminat pe celelalte (astfel, destul de inutil, le-a recuperat și din baza de date). +Soluția are însă un dezavantaj. Dacă am examina mai atent cum decurge cererea AJAX, am descoperi că, deși aplicația pare economică la exterior (returnează doar un singur snippet pentru articolul respectiv), în realitate, pe server, a redat toate snippet-urile. Snippet-ul dorit a fost plasat în payload, iar celelalte au fost aruncate (deci au fost obținute inutil din baza de date). -Pentru a optimiza acest proces, va trebui să luăm măsuri prin care să transmitem colecția `$articles` șablonului (de exemplu, în metoda `renderDefault()` ). Vom profita de faptul că procesarea semnalului are loc înainte de `render<Something>` metodelor: +Pentru a optimiza acest proces, va trebui să intervenim acolo unde transmitem colecția `$articles` către șablon (să zicem în metoda `renderDefault()`). Vom profita de faptul că procesarea semnalelor are loc înainte de metodele `render<Something>`: ```php public function handleLike(int $articleId): void @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connection->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,18 +101,18 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connection->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` -Acum, atunci când semnalul este procesat, în loc de o colecție cu toate articolele, doar o matrice cu un singur articol este transmisă șablonului - cel pe care dorim să îl redăm și să îl trimitem în sarcină utilă către browser. Astfel, `{foreach}` se va face o singură dată și nu vor fi redate fragmente suplimentare. +Acum, la procesarea semnalului, în loc de colecția cu toate articolele, se va transmite către șablon doar un array cu un singur articol - cel pe care dorim să-l redăm și să-l trimitem în payload către browser. `{foreach}` va rula deci o singură dată și nu se vor mai reda snippet-uri suplimentare. -Calea componentei .[#toc-component-way] -======================================= +Calea componentelor +=================== -O soluție complet diferită utilizează o abordare diferită pentru a evita fragmentele dinamice. Trucul constă în mutarea întregii logici într-o componentă separată - de acum încolo, nu mai avem un prezentator care să se ocupe de introducerea ratingului, ci o componentă dedicată `LikeControl`. Clasa va arăta ca mai jos (în plus, va conține și metodele `render`, `handleUnlike`, etc.): +O modalitate complet diferită de rezolvare evită snippet-urile dinamice. Trucul constă în transferarea întregii logici într-o componentă separată - de acum înainte, introducerea evaluărilor nu va mai fi gestionată de presenter, ci de o `LikeControl` dedicată. Clasa va arăta astfel (în plus, va conține și metodele `render`, `handleUnlike` etc.): ```php class LikeControl extends Nette\Application\UI\Control @@ -134,31 +134,31 @@ class LikeControl extends Nette\Application\UI\Control } ``` -Șablon de componentă: +Șablonul componentei: ```latte {snippet} {if !$article->liked} - <a n:href="like!" class=ajax>I like it</a> + <a n:href="like!" class=ajax>îmi place</a> {else} - <a n:href="unlike!" class=ajax>I don't like it anymore</a> + <a n:href="unlike!" class=ajax>nu-mi mai place</a> {/if} {/snippet} ``` -Bineînțeles că vom schimba șablonul de vizualizare și va trebui să adăugăm o fabrică la prezentator. Deoarece vom crea componenta de câte ori vom primi articole din baza de date, vom folosi clasa [application:Multiplier] pentru a o "multiplica". +Desigur, șablonul view-ului se va schimba și va trebui să adăugăm o fabrică în presenter. Deoarece vom crea componenta de atâtea ori câte articole obținem din baza de date, vom folosi clasa [Multiplier |application:Multiplier] pentru a o "multiplica". ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); } ``` -Șablonul de vizualizare este redus la minimul necesar (și complet lipsit de fragmente!): +Șablonul view-ului se reduce la minimul necesar (și complet lipsit de snippet-uri!): ```latte <article n:foreach="$articles as $article"> @@ -168,7 +168,6 @@ protected function createComponentLikeControl() </article> ``` -Aproape am terminat: aplicația va funcționa acum în AJAX. Și aici trebuie să optimizăm aplicația, deoarece, datorită utilizării bazei de date Nette, procesarea semnalului va încărca inutil toate articolele din baza de date în loc de unul singur. Totuși, avantajul este că nu va exista nicio redare, deoarece doar componenta noastră este efectiv redată. +Aproape am terminat: aplicația va funcționa acum cu AJAX. Și aici va trebui să optimizăm aplicația, deoarece, datorită utilizării Nette Database, la procesarea semnalului se încarcă inutil toate articolele din baza de date în loc de unul singur. Avantajul este însă că acestea nu vor fi redate, deoarece se va reda efectiv doar componenta noastră. {{priority: -1}} -{{sitename: Best Practices}} diff --git a/best-practices/ro/editors-and-tools.texy b/best-practices/ro/editors-and-tools.texy index c16ba93df7..7c44d258a6 100644 --- a/best-practices/ro/editors-and-tools.texy +++ b/best-practices/ro/editors-and-tools.texy @@ -1,40 +1,40 @@ -Editori și instrumente +Editoare & instrumente ********************** .[perex] -Puteți fi un programator priceput, dar numai cu instrumente bune veți deveni un maestru. În acest capitol veți găsi sfaturi despre instrumente, editori și plugin-uri importante. +Poți fi un programator priceput, dar numai cu instrumentele potrivite devii un maestru. În acest capitol vei găsi sfaturi despre instrumente, editoare și plugin-uri importante. -Editorul IDE .[#toc-ide-editor] -=============================== +Editor IDE +========== -Vă recomandăm cu tărie să utilizați un IDE complet pentru dezvoltare, cum ar fi PhpStorm, NetBeans, VS Code, și nu doar un editor de text cu suport PHP. Diferența este cu adevărat crucială. Nu există niciun motiv să vă mulțumiți cu un editor clasic cu evidențiere a sintaxei, pentru că nu atinge capacitățile unui IDE cu sugestii precise de cod, care poate refactoriza codul și multe altele. Unele IDE-uri sunt plătite, altele sunt gratuite. +Recomandăm cu tărie utilizarea unui IDE complet pentru dezvoltare, cum ar fi PhpStorm, NetBeans, VS Code, și nu doar un editor de text cu suport pentru PHP. Diferența este cu adevărat fundamentală. Nu există niciun motiv să te mulțumești cu un simplu editor care colorează sintaxa, dar nu atinge capacitățile unui IDE de top, care oferă sugestii precise, verifică erorile, poate refactoriza codul și multe altele. Unele IDE-uri sunt plătite, altele sunt chiar gratuite. -**NetBeans IDE** are suport integrat pentru Nette, Latte și NEON. +**NetBeans IDE** are suport încorporat pentru Nette, Latte și NEON. -**PhpStorm**: instalați aceste plugin-uri în `Settings > Plugins > Marketplace`: +**PhpStorm**: instalează aceste plugin-uri în `Settings > Plugins > Marketplace` - Nette framework helpers - Latte -- Suport NEON +- NEON support - Nette Tester -**VS Code**: găsiți plugin-ul "Nette Latte + Neon" în piață. +**VS Code**: găsește pluginul "Nette Latte + Neon" în marketplace. -De asemenea, conectați Tracy cu editorul. Când este afișată pagina de erori, puteți face clic pe numele fișierelor și acestea se vor deschide în editor cu cursorul pe linia corespunzătoare. Aflați [cum să configurați sistemul |tracy:open-files-in-ide]. +Conectează, de asemenea, Tracy la editor. Când se afișează pagina de eroare, vei putea da clic pe numele fișierelor și acestea se vor deschide în editor cu cursorul pe linia corespunzătoare. Citește [cum să configurezi sistemul |tracy:open-files-in-ide]. -PHPStan .[#toc-phpstan] -======================= +PHPStan +======= -PHPStan este un instrument care detectează erorile logice din codul dvs. înainte de a-l rula. +PHPStan este un instrument care detectează erorile logice din cod înainte de a-l rula. -Instalați-l prin Composer: +Îl instalăm folosind Composer: ```shell composer require --dev phpstan/phpstan-nette ``` -Creați un fișier de configurare `phpstan.neon` în proiect: +Creăm în proiect fișierul de configurare `phpstan.neon`: ```neon includes: @@ -47,40 +47,38 @@ parameters: level: 5 ``` -Și apoi lăsați-l să analizeze clasele din dosarul `app/`: +Și apoi îl lăsăm să analizeze clasele din directorul `app/`: ```shell vendor/bin/phpstan analyse app ``` -Puteți găsi documentația completă direct la [PHPStan |https://phpstan.org]. +Documentația exhaustivă o găsiți direct pe [site-ul PHPStan |https://phpstan.org]. -Verificator de coduri .[#toc-code-checker] -========================================== +Code Checker +============ -[Code Checker |code-checker:] verifică și, eventual, repară unele dintre erorile formale din codul dvs. sursă. +[Code Checker |code-checker:] verifică și, eventual, corectează unele dintre erorile formale din codurile sursă: -- elimină [BOM |nette:glossary#bom] +- elimină [BOM |nette:glossary#BOM] - verifică validitatea șabloanelor [Latte |latte:] - verifică validitatea fișierelor `.neon`, `.php` și `.json` -- verifică dacă există [caractere de control |nette:glossary#control characters] +- verifică prezența [caracterelor de control |nette:glossary#Caractere de control] - verifică dacă fișierul este codificat în UTF-8 -- controlează dacă `/* @annotations */` este scris greșit (lipsește al doilea asterisc) -- elimină etichetele finale PHP `?>` din fișierele PHP -- elimină spațiile albe și liniile goale inutile de la sfârșitul unui fișier -- normalizează terminațiile de linie la valoarea implicită a sistemului (cu parametrul `-l` ) +- verifică `/* @anotace */` scrise incorect (lipsește asteriscul) +- elimină `?>` de închidere din fișierele PHP +- elimină spațiile de la sfârșitul rândului și rândurile goale inutile de la sfârșitul fișierului +- normalizează delimitatorii de rând la cei de sistem (dacă specificați opțiunea `-l`) -Composer .[#toc-composer] -========================= +Composer +======== -[Composer |Composer] este un instrument pentru gestionarea dependențelor în PHP. Acesta ne permite să declarăm dependențele de bibliotecă și le va instala pentru noi, în proiectul nostru. +[Composer |best-practices:composer] este un instrument pentru gestionarea dependențelor în PHP. Ne permite să declarăm dependențe oricât de complexe ale diferitelor biblioteci și apoi le instalează pentru noi în proiectul nostru. -Verificator de cerințe .[#toc-requirements-checker] -=================================================== +Requirements Checker +==================== -Acesta era un instrument care testa mediul de funcționare a serverului și informa dacă (și în ce măsură) cadrul poate fi utilizat. În prezent, Nette poate fi utilizat pe orice server care dispune de versiunea minimă necesară de PHP. - -{{sitename: Best Practices}} +Acesta a fost un instrument care testa mediul de rulare al serverului și informa dacă (și în ce măsură) framework-ul poate fi utilizat. În prezent, Nette poate fi utilizat pe orice server care are versiunea minimă necesară de PHP. diff --git a/best-practices/ro/form-reuse.texy b/best-practices/ro/form-reuse.texy index 0f1e60600b..20b7c45804 100644 --- a/best-practices/ro/form-reuse.texy +++ b/best-practices/ro/form-reuse.texy @@ -2,15 +2,15 @@ Reutilizarea formularelor în mai multe locuri ********************************************* .[perex] -În Nette, aveți mai multe opțiuni pentru a reutiliza același formular în mai multe locuri fără a duplica codul. În acest articol, vom trece în revistă diferitele soluții, inclusiv pe cele pe care ar trebui să le evitați. +În Nette aveți la dispoziție mai multe opțiuni pentru a utiliza același formular în mai multe locuri și a nu duplica codul. În acest articol vom prezenta diverse soluții, inclusiv cele pe care ar trebui să le evitați. -Fabrica de formulare .[#toc-form-factory] -========================================= +Fabrica de formulare +==================== -O abordare de bază pentru utilizarea aceleiași componente în mai multe locuri este de a crea o metodă sau o clasă care generează componenta și apoi de a apela acea metodă în diferite locuri din aplicație. O astfel de metodă sau clasă se numește *factory*. Vă rugăm să nu faceți confuzie cu modelul de proiectare *factory method*, care descrie un mod specific de utilizare a fabricilor și nu are legătură cu acest subiect. +Una dintre abordările de bază pentru utilizarea aceleiași componente în mai multe locuri este crearea unei metode sau clase care generează această componentă și apoi apelarea acestei metode în diferite locuri ale aplicației. O astfel de metodă sau clasă se numește *fabrică*. Vă rugăm să nu confundați cu modelul de proiectare *factory method*, care descrie un mod specific de utilizare a fabricilor și nu are legătură cu acest subiect. -Ca exemplu, să creăm o fabrică care va construi un formular de editare: +Ca exemplu, vom crea o fabrică care va construi un formular de editare: ```php use Nette\Application\UI\Form; @@ -20,22 +20,22 @@ class FormFactory public function createEditForm(): Form { $form = new Form; - $form->addText('title', 'Title:'); - // câmpurile suplimentare ale formularului sunt adăugate aici - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Titlu:'); + // aici se adaugă alte câmpuri de formular + $form->addSubmit('send', 'Trimite'); return $form; } } ``` -Acum puteți utiliza această fabrică în diferite locuri din aplicația dumneavoastră, de exemplu în prezentări sau componente. Și facem acest lucru [solicitând-o ca dependență |dependency-injection:passing-dependencies]. Deci, mai întâi, vom scrie clasa în fișierul de configurare: +Acum puteți utiliza această fabrică în diferite locuri din aplicația dvs., de exemplu în presenteri sau componente. Și asta prin [solicitarea ei ca dependență |dependency-injection:passing-dependencies]. Mai întâi, vom înregistra clasa în fișierul de configurare: ```neon services: - FormFactory ``` -Și apoi o vom folosi în prezentator: +Și apoi o vom folosi într-un presenter: ```php @@ -50,14 +50,14 @@ class MyPresenter extends Nette\Application\UI\Presenter { $form = $this->formFactory->createEditForm(); $form->onSuccess[] = function () { - // prelucrarea datelor trimise + // procesarea datelor trimise }; return $form; } } ``` -Puteți extinde fabrica de formulare cu metode suplimentare pentru a crea alte tipuri de formulare care să se potrivească aplicației dumneavoastră. Și, bineînțeles, puteți adăuga o metodă care să creeze un formular de bază fără elemente, pe care celelalte metode îl vor utiliza: +Puteți extinde fabrica de formulare cu alte metode pentru crearea altor tipuri de formulare, în funcție de nevoile aplicației dvs. Și, desigur, putem adăuga și o metodă care creează un formular de bază fără elemente, pe care celelalte metode o vor utiliza: ```php class FormFactory @@ -71,9 +71,9 @@ class FormFactory public function createEditForm(): Form { $form = $this->createForm(); - $form->addText('title', 'Title:'); - // câmpurile suplimentare ale formularului sunt adăugate aici - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Titlu:'); + // aici se adaugă alte câmpuri de formular + $form->addSubmit('send', 'Trimite'); return $form; } } @@ -82,10 +82,10 @@ class FormFactory Metoda `createForm()` nu face încă nimic util, dar acest lucru se va schimba rapid. -Dependențe de fabrică .[#toc-factory-dependencies] -================================================== +Dependențele fabricii +===================== -În timp, va deveni evident că avem nevoie ca formularele să fie multilingve. Acest lucru înseamnă că trebuie să configurăm un [traducător |forms:rendering#Translating] pentru toate formularele. Pentru a face acest lucru, modificăm clasa `FormFactory` pentru a accepta obiectul `Translator` ca dependență în constructor și îl transmitem formularului: +Cu timpul, se va dovedi că avem nevoie ca formularele să fie multilingve. Acest lucru înseamnă că trebuie să setăm un așa-numit [translator |forms:rendering#Traducere] pentru toate formularele. În acest scop, vom modifica clasa `FormFactory` astfel încât să accepte obiectul `Translator` ca dependență în constructor și să-l transmitem formularului: ```php use Nette\Localization\Translator; @@ -104,18 +104,17 @@ class FormFactory return $form; } - //... + // ... } ``` -Deoarece metoda `createForm()` este apelată și de alte metode care creează formulare specifice, trebuie să setăm translatorul doar în acea metodă. Și am terminat. Nu este nevoie să modificăm niciun cod de prezentator sau de componentă, ceea ce este minunat. +Deoarece metoda `createForm()` este apelată și de celelalte metode care creează formulare specifice, este suficient să setăm translatorul doar în ea. Și am terminat. Nu este nevoie să modificăm codul niciunui presenter sau componente, ceea ce este grozav. -Mai multe clase fabrică .[#toc-more-factory-classes] -==================================================== +Mai multe clase de fabrici +========================== -Alternativ, puteți crea mai multe clase pentru fiecare formular pe care doriți să îl utilizați în aplicația dumneavoastră. -Această abordare poate crește lizibilitatea codului și face ca formularele să fie mai ușor de gestionat. Lăsați originalul `FormFactory` pentru a crea doar un formular pur cu o configurație de bază (de exemplu, cu suport pentru traducere) și creați o nouă fabrică `EditFormFactory` pentru formularul de editare. +Alternativ, puteți crea mai multe clase pentru fiecare formular pe care doriți să-l utilizați în aplicația dvs. Această abordare poate crește lizibilitatea codului și facilita gestionarea formularelor. Vom lăsa `FormFactory` originală să creeze doar un formular curat cu configurația de bază (de exemplu, cu suport pentru traduceri) și vom crea o nouă fabrică `EditFormFactory` pentru formularul de editare. ```php class FormFactory @@ -145,40 +144,39 @@ class EditFormFactory public function create(): Form { $form = $this->formFactory->create(); - // aici se adaugă câmpuri suplimentare de formular - $form->addSubmit('send', 'Save'); + // aici se adaugă alte câmpuri de formular + $form->addSubmit('send', 'Trimite'); return $form; } } ``` -Este foarte important ca legătura dintre clasele `FormFactory` și `EditFormFactory` să fie implementată prin compoziție, nu prin moștenirea obiectelor: +Este foarte important ca legătura dintre clasele `FormFactory` și `EditFormFactory` să fie realizată prin [compoziție |nette:introduction-to-object-oriented-programming#Compoziție], nu prin [moștenire de obiecte |nette:introduction-to-object-oriented-programming#Moștenire]: ```php -// ⛔ NU! MOȘTENIREA NU ARE CE CĂUTA AICI +// ⛔ NU AȘA! MOȘTENIREA NU APARȚINE AICI class EditFormFactory extends FormFactory { public function create(): Form { $form = parent::create(); - $form->addText('title', 'Title:'); - // câmpurile suplimentare ale formularului se adaugă aici - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Titlu:'); + // aici se adaugă alte câmpuri de formular + $form->addSubmit('send', 'Trimite'); return $form; } } ``` -Utilizarea moștenirii în acest caz ar fi complet contraproductivă. Ați întâmpina foarte repede probleme. De exemplu, dacă ați dori să adăugați parametri la metoda `create()`, PHP ar raporta o eroare deoarece semnătura acesteia este diferită de cea a metodei părinte. -Sau atunci când treceți o dependență clasei `EditFormFactory` prin intermediul constructorului. Acest lucru ar cauza ceea ce numim " [iadul constructorilor |dependency-injection:passing-dependencies#Constructor hell]". +Utilizarea moștenirii ar fi complet contraproductivă în acest caz. Ați întâmpina probleme foarte rapid. De exemplu, în momentul în care ați dori să adăugați parametri metodei `create()`; PHP ar raporta o eroare că semnătura sa diferă de cea a părintelui. Sau la transmiterea dependențelor către clasa `EditFormFactory` prin constructor. Ar apărea o situație pe care o numim [constructor hell |dependency-injection:passing-dependencies#Constructor hell]. -În general, este mai bine să preferați compoziția decât moștenirea. +În general, este mai bine să preferăm [compoziția în detrimentul moștenirii |dependency-injection:faq#De ce se preferă compoziția în locul moștenirii]. -Gestionarea formularelor .[#toc-form-handling] -============================================== +Gestionarea formularului +======================== -Gestionatorul de formulare care este apelat după o trimitere reușită poate fi, de asemenea, parte a unei clase fabrică. Acesta va funcționa prin transmiterea datelor trimise către model pentru procesare. El va transmite orice eroare [înapoi la |forms:validation#Processing Errors] formular. Modelul din exemplul următor este reprezentat de clasa `Facade`: +Gestionarea formularului, care este apelată după trimiterea cu succes, poate fi, de asemenea, parte a clasei fabricii. Va funcționa prin transmiterea datelor trimise către model pentru procesare. Eventualele erori le va [transmite înapoi |forms:validation#Erori în timpul procesării] formularului. Modelul din exemplul următor este reprezentat de clasa `Facade`: ```php class EditFormFactory @@ -192,9 +190,9 @@ class EditFormFactory public function create(): Form { $form = $this->formFactory->create(); - $form->addText('title', 'Title:'); - // câmpurile suplimentare ale formularului sunt adăugate aici - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Titlu:'); + // aici se adaugă alte câmpuri de formular + $form->addSubmit('send', 'Trimite'); $form->onSuccess[] = [$this, 'processForm']; return $form; } @@ -212,7 +210,7 @@ class EditFormFactory } ``` -Lăsați prezentatorul să se ocupe singur de redirecționare. Acesta va adăuga un alt gestionar la evenimentul `onSuccess`, care va efectua redirecționarea. Acest lucru va permite ca formularul să fie utilizat în prezentatori diferiți, iar fiecare poate redirecționa către o locație diferită. +Redirecționarea în sine o vom lăsa însă pe seama presenterului. Acesta va adăuga evenimentului `onSuccess` un alt handler care va efectua redirecționarea. Datorită acestui fapt, va fi posibilă utilizarea formularului în diferiți presenteri și redirecționarea către locuri diferite în fiecare. ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -226,7 +224,7 @@ class MyPresenter extends Nette\Application\UI\Presenter { $form = $this->formFactory->create(); $form->onSuccess[] = function () { - $this->flashMessage('Záznam byl uložen'); + $this->flashMessage('Înregistrarea a fost salvată'); $this->redirect('Homepage:'); }; return $form; @@ -234,39 +232,38 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Această soluție profită de proprietatea formularelor conform căreia, atunci când `addError()` este apelat pe un formular sau pe un element al acestuia, nu este invocat următorul procesator `onSuccess`. +Această soluție utilizează proprietatea formularelor că, atunci când se apelează `addError()` pe formular sau pe elementele sale, următorul handler `onSuccess` nu mai este apelat. -Moștenirea din clasa Form .[#toc-inheriting-from-the-form-class] -================================================================ +Moștenirea de la clasa Form +=========================== -Un formular construit nu ar trebui să fie un copil al unui formular. Cu alte cuvinte, nu utilizați această soluție: +Formularul construit nu trebuie să fie un descendent al formularului. Cu alte cuvinte, nu utilizați această soluție: ```php -// ⛔ NU! MOȘTENIREA NU ARE CE CĂUTA AICI +// ⛔ NU AȘA! MOȘTENIREA NU APARȚINE AICI class EditForm extends Form { public function __construct(Translator $translator) { parent::__construct(); - $form->addText('title', 'Title:'); - // câmpurile suplimentare ale formularului se adaugă aici - $form->addSubmit('send', 'Save'); - $form->setTranslator($translator); + $this->addText('title', 'Titlu:'); + // aici se adaugă alte câmpuri de formular + $this->addSubmit('send', 'Trimite'); + $this->setTranslator($translator); } } ``` -În loc să construiți formularul în constructor, utilizați fabrica. +În loc să construiți formularul în constructor, utilizați o fabrică. -Este important să realizăm că clasa `Form` este în primul rând un instrument de asamblare a unui formular, adică un constructor de formulare. Iar formularul asamblat poate fi considerat produsul său. Cu toate acestea, produsul nu este un caz specific al constructorului; nu există o relație *este a* între ele, care stă la baza moștenirii. +Este necesar să realizăm că clasa `Form` este în primul rând un instrument pentru construirea unui formular, adică un *form builder*. Iar formularul construit poate fi considerat produsul său. Însă produsul nu este un caz specific al builder-ului, nu există între ele o legătură *is a* care stă la baza moștenirii. -Componenta Form .[#toc-form-component] -====================================== +Componenta cu formular +====================== -O abordare complet diferită constă în crearea unei [componente |application:components] care include un formular. Acest lucru oferă noi posibilități, de exemplu, pentru a reda formularul într-un mod specific, deoarece componenta include un șablon. -Sau pot fi utilizate semnale pentru comunicarea AJAX și încărcarea de informații în formular, de exemplu pentru indicii etc. +O abordare complet diferită este crearea unei [componente |application:components], care include un formular. Acest lucru oferă noi posibilități, de exemplu, redarea formularului într-un mod specific, deoarece componenta include și un șablon. Sau se pot utiliza semnale pentru comunicarea AJAX și încărcarea suplimentară a informațiilor în formular, de exemplu pentru sugestii, etc. ```php @@ -284,9 +281,9 @@ class EditControl extends Nette\Application\UI\Control protected function createComponentForm(): Form { $form = new Form; - $form->addText('title', 'Title:'); - // câmpurile suplimentare ale formularului sunt adăugate aici - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Titlu:'); + // aici se adaugă alte câmpuri de formular + $form->addSubmit('send', 'Trimite'); $form->onSuccess[] = [$this, 'processForm']; return $form; @@ -303,13 +300,13 @@ class EditControl extends Nette\Application\UI\Control return; } - // invocarea unui eveniment + // declanșarea evenimentului $this->onSave($this, $data); } } ``` -Să creăm o fabrică care va produce această componentă. Este suficient să [îi scriem interfața |application:components#Components with Dependencies]: +Vom crea și o fabrică care va produce această componentă. Este suficient să [înregistrăm interfața sa |application:components#Componente cu dependențe]: ```php interface EditControlFactory @@ -318,14 +315,14 @@ interface EditControlFactory } ``` -și să o adăugăm la fișierul de configurare: +Și să o adăugăm în fișierul de configurare: ```neon services: - EditControlFactory ``` -Și acum putem solicita fabrica și o putem folosi în prezentator: +Și acum putem solicita fabrica și o putem utiliza în presenter: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -335,13 +332,13 @@ class MyPresenter extends Nette\Application\UI\Presenter ) { } - protected function createComponentEditForm(): Form + protected function createComponentEditForm(): EditControl { $control = $this->controlFactory->create(); $control->onSave[] = function (EditControl $control, $data) { $this->redirect('this'); - // sau redirecționarea către rezultatul editării, de exemplu: + // sau redirecționăm către rezultatul editării, de ex.: // $this->redirect('detail', ['id' => $data->id]); }; @@ -349,5 +346,3 @@ class MyPresenter extends Nette\Application\UI\Presenter } } ``` - -{{sitename: Best Practices}} diff --git a/best-practices/ro/inject-method-attribute.texy b/best-practices/ro/inject-method-attribute.texy index f72ed67e2b..0479b38c22 100644 --- a/best-practices/ro/inject-method-attribute.texy +++ b/best-practices/ro/inject-method-attribute.texy @@ -1,21 +1,18 @@ -Metode și atribute de injectare -******************************* +Metode și atribute inject +************************* .[perex] -În acest articol, ne vom concentra asupra diferitelor modalități de transmitere a dependențelor către prezentatori în cadrul Nette. Vom compara metoda preferată, care este constructorul, cu alte opțiuni, cum ar fi metodele și atributele `inject`. +În acest articol ne vom concentra pe diferite modalități de a transmite dependențe către presenteri în framework-ul Nette. Vom compara metoda preferată, care este constructorul, cu alte opțiuni, cum ar fi metodele și atributele `inject`. -Și pentru prezentatori, transmiterea dependențelor folosind [constructorul |dependency-injection:passing-dependencies#Constructor Injection] este metoda preferată. -Cu toate acestea, dacă creați un strămoș comun din care moștenesc alți prezentatori (de exemplu, BasePresenter), iar acest strămoș are, de asemenea, dependențe, apare o problemă, pe care o numim [constructor hell |dependency-injection:passing-dependencies#Constructor hell]. -Aceasta poate fi ocolită folosind metode alternative, care includ metode și atribute de injectare (adnotări). +Și pentru presenteri este valabil că transmiterea dependențelor prin [constructor |dependency-injection:passing-dependencies#Transmitere prin constructor] este calea preferată. Dacă însă creați un strămoș comun din care moștenesc alți presenteri (de ex. `BasePresenter`), și acest strămoș are de asemenea dependențe, apare o problemă pe care o numim [constructor hell |dependency-injection:passing-dependencies#Constructor hell]. Aceasta poate fi ocolită folosind căi alternative, reprezentate de metode și atribute (anterior adnotări) `inject`. -`inject*()` Metode .[#toc-inject-methods] -========================================= +Metode `inject*()` +================== -Aceasta este o formă de transmitere a dependențelor prin intermediul [setorilor |dependency-injection:passing-dependencies#Setter Injection]. Numele acestor setteri încep cu prefixul inject. -Nette DI apelează automat astfel de metode numite imediat după crearea instanței de prezentator și le transmite toate dependențele necesare. Prin urmare, acestea trebuie să fie declarate ca fiind publice. +Este o formă de transmitere a dependenței prin [setter |dependency-injection:passing-dependencies#Transmitere prin setter]. Numele acestor setteri începe cu prefixul `inject`. Nette DI apelează automat metodele numite astfel imediat după crearea instanței presenterului și le transmite toate dependențele necesare. Prin urmare, trebuie declarate ca public. -`inject*()` metodele pot fi considerate ca un fel de extindere a constructorului în mai multe metode. Datorită acestui fapt, `BasePresenter` poate prelua dependențele printr-o altă metodă și poate lăsa constructorul liber pentru descendenții săi: +Metodele `inject*()` pot fi considerate un fel de extensie a constructorului în mai multe metode. Datorită acestui fapt, `BasePresenter` poate prelua dependențe printr-o altă metodă și lăsa constructorul liber pentru descendenții săi: ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -39,18 +36,18 @@ class MyPresenter extends BasePresenter } ``` -Prezentatorul poate conține un număr nelimitat de metode `inject*()`, iar fiecare poate avea un număr nelimitat de parametri. Acest lucru este, de asemenea, excelent pentru cazurile în care prezentatorul este [compus din trăsături |presenter-traits], iar fiecare dintre acestea necesită propria dependență. +Un presenter poate conține un număr arbitrar de metode `inject*()` și fiecare poate avea un număr arbitrar de parametri. Se potrivesc excelent și în cazurile în care presenterul este [compus din trait-uri |presenter-traits] și fiecare dintre ele necesită propria dependență. -`Inject` Atribute .[#toc-inject-attributes] -=========================================== +Atribute `Inject` +================= -Aceasta este o formă de [injecție în proprietăți |dependency-injection:passing-dependencies#Property Injection]. Este suficient să indicați ce proprietăți trebuie injectate, iar Nette DI trece automat dependențele imediat după crearea instanței de prezentator. Pentru a le insera, este necesar să le declarați ca fiind publice. +Este o formă de [injectare în proprietate |dependency-injection:passing-dependencies#Setarea proprietății]. Este suficient să marcați în ce variabile trebuie injectat, iar Nette DI transmite automat dependențele imediat după crearea instanței presenterului. Pentru a le putea insera, este necesar să le declarați ca public. -Proprietățile sunt marcate cu un atribut: (anterior, se folosea adnotarea `/** @inject */`) +Marcăm proprietățile cu atributul: (anterior se folosea adnotarea `/** @inject */`) ```php -use Nette\DI\Attributes\Inject; // această linie este importantă +use Nette\DI\Attributes\Inject; // această linie este importantă class MyPresenter extends Nette\Application\UI\Presenter { @@ -59,9 +56,6 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Avantajul acestei metode de transmitere a dependențelor a fost forma foarte economică de notare. Cu toate acestea, odată cu introducerea [promovării proprietăților constructorului |https://blog.nette.org/ro/php-8-0-prezentare-completa-a-noutatilor#toc-constructor-property-promotion], utilizarea constructorului pare mai ușoară. +Avantajul acestei metode de transmitere a dependențelor a fost forma foarte concisă a scrierii. Cu toate acestea, odată cu apariția [constructor property promotion |https://blog.nette.org/ro/php-8-0-complete-overview-of-news#toc-constructor-property-promotion], pare mai ușor să folosești constructorul. -Pe de altă parte, această metodă suferă de aceleași neajunsuri ca și trecerea dependențelor în proprietăți în general: nu avem niciun control asupra modificărilor variabilei și, în același timp, variabila devine parte a interfeței publice a clasei, ceea ce nu este de dorit. - - -{{sitename: Best Practices}} +Pe de altă parte, această metodă suferă de aceleași neajunsuri ca și transmiterea dependențelor către proprietăți în general: nu avem control asupra modificărilor din variabilă și, în același timp, variabila devine parte a interfeței publice a clasei, ceea ce este nedorit. diff --git a/best-practices/ro/lets-create-contact-form.texy b/best-practices/ro/lets-create-contact-form.texy index eecffd9791..9ae4c05523 100644 --- a/best-practices/ro/lets-create-contact-form.texy +++ b/best-practices/ro/lets-create-contact-form.texy @@ -1,12 +1,12 @@ -Să creăm un formular de contact -******************************* +Creăm un formular de contact +**************************** .[perex] -Haideți să vedem cum se creează un formular de contact în Nette, inclusiv trimiterea acestuia la un e-mail. Deci, să o facem! +Vom analiza cum să creăm un formular de contact în Nette, inclusiv trimiterea pe email. Să începem! -Mai întâi trebuie să creăm un nou proiect. După cum explică pagina de [început |nette:installation]. Și apoi putem începe să creăm formularul. +Mai întâi trebuie să creăm un proiect nou. Cum se face acest lucru este explicat pe pagina [Începeți |nette:installation]. Apoi putem începe crearea formularului. -Cel mai simplu mod este să creăm [formularul direct în Presenter |forms:in-presenter]. Putem folosi formularul pre-fabricat `HomePresenter`. Vom adăuga componenta `contactForm` care reprezintă formularul. Vom face acest lucru scriind metoda factory `createComponentContactForm()` în codul care va produce componenta: +Cel mai simplu este să creăm [formularul direct în presenter |forms:in-presenter]. Putem folosi `HomePresenter` pre-pregătit. În el vom adăuga componenta `contactForm` care reprezintă formularul. Vom face acest lucru scriind în cod metoda fabrică `createComponentContactForm()`, care va produce componenta: ```php use Nette\Application\UI\Form; @@ -17,37 +17,35 @@ class HomePresenter extends Presenter protected function createComponentContactForm(): Form { $form = new Form; - $form->addText('name', 'Name:') - ->setRequired('Enter your name'); + $form->addText('name', 'Nume:') + ->setRequired('Introduceți numele'); $form->addEmail('email', 'E-mail:') - ->setRequired('Enter your e-mail'); - $form->addTextarea('message', 'Message:') - ->setRequired('Enter message'); - $form->addSubmit('send', 'Send'); + ->setRequired('Introduceți e-mailul'); + $form->addTextarea('message', 'Mesaj:') + ->setRequired('Introduceți mesajul'); + $form->addSubmit('send', 'Trimite'); $form->onSuccess[] = [$this, 'contactFormSucceeded']; return $form; } public function contactFormSucceeded(Form $form, $data): void { - // sending an email + // trimiterea emailului } } ``` -După cum puteți vedea, am creat două metode. Prima metodă `createComponentContactForm()` creează un nou formular. Acesta are câmpuri pentru nume, e-mail și mesaj, pe care le adăugăm cu ajutorul metodelor `addText()`, `addEmail()` și `addTextArea()`. De asemenea, am adăugat un buton pentru a trimite formularul. -Dar ce se întâmplă dacă utilizatorul nu completează unele câmpuri? În acest caz, ar trebui să-l anunțăm că este un câmp obligatoriu. Am făcut acest lucru cu metoda `setRequired()`. -În cele din urmă, am adăugat și un [eveniment |nette:glossary#events] `onSuccess`, care este declanșat dacă formularul este trimis cu succes. În cazul nostru, acesta apelează metoda `contactFormSucceeded`, care se ocupă de procesarea formularului trimis. Vom adăuga acest lucru în cod imediat. +După cum vedeți, am creat două metode. Prima metodă `createComponentContactForm()` creează un nou formular. Acesta are câmpuri pentru nume, email și mesaj, pe care le adăugăm cu metodele `addText()`, `addEmail()` și `addTextArea()`. Am adăugat și un buton pentru trimiterea formularului. Dar ce se întâmplă dacă utilizatorul nu completează un câmp? În acest caz, ar trebui să-l informăm că este un câmp obligatoriu. Am realizat acest lucru cu metoda `setRequired()`. În final, am adăugat și [evenimentul |nette:glossary#Evenimente] `onSuccess`, care se declanșează dacă formularul este trimis cu succes. În cazul nostru, apelează metoda `contactFormSucceeded`, care se ocupă de procesarea formularului trimis. Vom completa codul pentru aceasta imediat. -Lăsați componenta `contantForm` să fie redată în șablonul `templates/Home/default.latte`: +Vom lăsa componenta `contactForm` să fie redată în șablonul `Home/default.latte`: ```latte {block content} -<h1>Contant Form</h1> +<h1>Formular de contact</h1> {control contactForm} ``` -Pentru a trimite e-mailul propriu-zis, creăm o nouă clasă numită `ContactFacade` și o plasăm în fișierul `app/Model/ContactFacade.php`: +Pentru trimiterea efectivă a emailului, vom crea o nouă clasă, pe care o vom numi `ContactFacade` și o vom plasa în fișierul `app/Model/ContactFacade.php`: ```php <?php @@ -68,9 +66,9 @@ class ContactFacade public function sendMessage(string $email, string $name, string $message): void { $mail = new Message; - $mail->addTo('admin@example.com') // your email + $mail->addTo('admin@example.com') // emailul dvs. ->setFrom($email, $name) - ->setSubject('Message from the contact form') + ->setSubject('Mesaj din formularul de contact') ->setBody($message); $this->mailer->send($mail); @@ -78,9 +76,9 @@ class ContactFacade } ``` -Metoda `sendMessage()` va crea și va trimite e-mailul. Pentru a face acest lucru, folosește un așa-numit mailer, pe care îl transmite ca dependență prin intermediul constructorului. Citiți mai multe despre [trimiterea de e-mailuri |mail:]. +Metoda `sendMessage()` creează și trimite emailul. Utilizează pentru aceasta așa-numitul mailer, pe care îl primește ca dependență prin constructor. Citiți mai multe despre [trimiterea emailurilor |mail:]. -Acum, ne vom întoarce la prezentator și vom finaliza metoda `contactFormSucceeded()`. Acesta apelează metoda `sendMessage()` a clasei `ContactFacade` și îi transmite datele formularului. Și cum obținem obiectul `ContactFacade`? Ne va fi transmis de către constructor: +Acum ne vom întoarce la presenter și vom finaliza metoda `contactFormSucceeded()`. Aceasta va apela metoda `sendMessage()` a clasei `ContactFacade` și îi va transmite datele din formular. Și cum obținem obiectul `ContactFacade`? Îl vom primi prin constructor: ```php use App\Model\ContactFacade; @@ -102,36 +100,36 @@ class HomePresenter extends Presenter public function contactFormSucceeded(stdClass $data): void { $this->facade->sendMessage($data->email, $data->name, $data->message); - $this->flashMessage('The message has been sent'); + $this->flashMessage('Mesajul a fost trimis'); $this->redirect('this'); } } ``` -După ce e-mailul este trimis, îi arătăm utilizatorului așa-numitul [mesaj flash |application:components#flash-messages], confirmând că mesajul a fost trimis, și apoi îl redirecționăm către pagina următoare, astfel încât formularul să nu poată fi trimis din nou folosind *refresh* în browser. +După ce emailul este trimis, vom afișa utilizatorului un așa-numit [flash message |application:components#Mesaje flash], confirmând că mesajul a fost trimis, și apoi vom redirecționa către aceeași pagină (pentru a curăța formularul), astfel încât să nu fie posibilă retrimiterea formularului prin *refresh* în browser. -Ei bine, dacă totul funcționează, ar trebui să puteți trimite un e-mail din formularul de contact. Felicitări! +Deci, dacă totul funcționează, ar trebui să puteți trimite un email din formularul dvs. de contact. Felicitări! -Șablon de e-mail HTML .[#toc-html-email-template] -------------------------------------------------- +Șablon HTML pentru email +------------------------ -Deocamdată, se trimite un e-mail text simplu care conține doar mesajul trimis prin formular. Dar putem folosi HTML în e-mail și să-l facem mai atractiv. Vom crea un șablon pentru aceasta în Latte, pe care îl vom salva în `app/Model/contactEmail.latte`: +Deocamdată se trimite un email text simplu care conține doar mesajul trimis prin formular. Dar în email putem folosi HTML și să-i facem aspectul mai atractiv. Vom crea un șablon pentru el în Latte, pe care îl vom scrie în `app/Model/contactEmail.latte`: ```latte <html> - <title>Message from the contact form + Mesaj din formularul de contact -

    Name: {$name}

    +

    Nume: {$name}

    E-mail: {$email}

    -

    Message: {$message}

    +

    Mesaj: {$message}

    ``` -Rămâne să modificăm `ContactFacade` pentru a utiliza acest șablon. În constructor, solicităm clasa `LatteFactory`, care poate produce obiectul `Latte\Engine`, un [renderizator de șabloane Latte |latte:develop#how-to-render-a-template]. Utilizăm metoda `renderToString()` pentru a reda șablonul într-un fișier, primul parametru este calea către șablon, iar al doilea sunt variabilele. +Rămâne să modificăm `ContactFacade` pentru a utiliza acest șablon. În constructor vom solicita clasa `LatteFactory`, care poate produce obiectul `Latte\Engine`, adică [motorul de redare a șabloanelor Latte |latte:develop#Cum se randează un șablon]. Folosind metoda `renderToString()`, vom reda șablonul într-un șir, primul parametru este calea către șablon și al doilea sunt variabilele. ```php namespace App\Model; @@ -158,7 +156,7 @@ class ContactFacade ]); $mail = new Message; - $mail->addTo('admin@example.com') // your email + $mail->addTo('admin@example.com') // emailul dvs. ->setFrom($email, $name) ->setHtmlBody($body); @@ -167,15 +165,15 @@ class ContactFacade } ``` -Apoi, trecem e-mailul HTML generat la metoda `setHtmlBody()` în loc de originalul `setBody()`. De asemenea, nu trebuie să specificăm subiectul e-mailului în `setSubject()`, deoarece biblioteca îl preia din elementul `` din șablon. +Emailul HTML generat îl vom transmite apoi metodei `setHtmlBody()` în locul celei originale `setBody()`. De asemenea, nu trebuie să specificăm subiectul emailului în `setSubject()`, deoarece biblioteca îl va prelua din elementul `<title>` al șablonului. -Configurarea .[#toc-configuring] --------------------------------- +Configurare +----------- -În codul clasei `ContactFacade`, e-mailul nostru de administrare `admin@example.com` este încă codificat în mod greșit. Ar fi mai bine să îl mutați în fișierul de configurare. Cum se face acest lucru? +În codul clasei `ContactFacade` este încă hardcodat emailul nostru de administrator `admin@example.com`. Ar fi mai bine să-l mutăm în fișierul de configurare. Cum facem asta? -Mai întâi, modificăm clasa `ContactFacade` și înlocuim șirul de e-mail cu o variabilă transmisă de constructor: +Mai întâi modificăm clasa `ContactFacade` și înlocuim șirul cu emailul cu o variabilă transmisă prin constructor: ```php class ContactFacade @@ -199,28 +197,25 @@ class ContactFacade } ``` -Iar al doilea pas este să introducem valoarea acestei variabile în configurație. În fișierul `app/config/services.neon` adăugăm: +Și al doilea pas este specificarea valorii acestei variabile în configurație. În fișierul `app/config/services.neon` scriem: ```neon services: - App\Model\ContactFacade(adminEmail: admin@example.com) ``` -Și asta e tot. Dacă există multe elemente în secțiunea `services` și aveți impresia că e-mailul se pierde printre ele, îl putem transforma într-o variabilă. Vom modifica intrarea în: +Și gata. Dacă ar fi multe elemente în secțiunea `services` și ați avea senzația că emailul se pierde printre ele, îl putem transforma într-o variabilă. Modificăm înregistrarea la: ```neon services: - App\Model\ContactFacade(adminEmail: %adminEmail%) ``` -Și vom defini această variabilă în fișierul `app/config/common.neon`: +Și în fișierul `app/config/common.neon` definim această variabilă: ```neon parameters: adminEmail: admin@example.com ``` -Și gata! - - -{{sitename: Best Practices}} +Și am terminat! diff --git a/best-practices/ro/microsites.texy b/best-practices/ro/microsites.texy new file mode 100644 index 0000000000..6c4441df8b --- /dev/null +++ b/best-practices/ro/microsites.texy @@ -0,0 +1,63 @@ +Cum să scrii micro-site-uri +*************************** + +Imaginați-vă că trebuie să creați rapid un mic site web pentru un eveniment viitor al companiei dvs. Trebuie să fie simplu, rapid și fără complicații inutile. Poate credeți că pentru un proiect atât de mic nu aveți nevoie de un framework robust. Dar ce se întâmplă dacă utilizarea framework-ului Nette poate simplifica și accelera fundamental acest proces? + +Chiar și la crearea site-urilor web simple, nu doriți să renunțați la confort. Nu doriți să reinventați ceea ce a fost deja rezolvat. Fiți liniștit leneș și lăsați-vă răsfățat. Nette Framework poate fi utilizat excelent și ca micro framework. + +Cum poate arăta un astfel de microsite? De exemplu, astfel încât întregul cod al site-ului să fie plasat într-un singur fișier `index.php` în directorul public: + +```php +<?php + +require __DIR__ . '/../vendor/autoload.php'; + +$configurator = new Nette\Bootstrap\Configurator; +$configurator->enableTracy(__DIR__ . '/../log'); +$configurator->setTempDirectory(__DIR__ . '/../temp'); + +// creează containerul DI pe baza configurației din config.neon +$configurator->addConfig(__DIR__ . '/../app/config.neon'); +$container = $configurator->createContainer(); + +// setăm rutarea +$router = new Nette\Application\Routers\RouteList; +$container->addService('router', $router); + +// rută pentru URL https://example.com/ +$router->addRoute('', function ($presenter, Nette\Http\Request $httpRequest) { + // detectăm limba browserului și redirecționăm către URL /en sau /de etc. + $supportedLangs = ['en', 'de', 'cs']; + $lang = $httpRequest->detectLanguage($supportedLangs) ?: reset($supportedLangs); + $presenter->redirectUrl("/$lang"); +}); + +// rută pentru URL https://example.com/cs sau https://example.com/en +$router->addRoute('<lang cs|en>', function ($presenter, string $lang) { + // afișăm șablonul corespunzător, de exemplu ../templates/en.latte + $template = $presenter->createTemplate() + ->setFile(__DIR__ . '/../templates/' . $lang . '.latte'); + return $template; +}); + +// pornește aplicația! +$container->getByType(Nette\Application\Application::class)->run(); +``` + +Restul vor fi șabloane stocate în directorul părinte `/templates`. + +Codul PHP din `index.php` mai întâi [pregătește mediul |bootstrap:], apoi definește [rutele |application:routing#Rutare dinamică cu callback-uri] și în final pornește aplicația. Avantajul este că al doilea parametru al funcției `addRoute()` poate fi un callable, care se execută după deschiderea paginii corespunzătoare. + + +De ce să folosiți Nette pentru microsite-uri? +--------------------------------------------- + +- Programatorii care au încercat vreodată [Tracy |tracy:] nu își pot imagina astăzi că ar programa ceva fără ea. +- În primul rând, veți utiliza sistemul de șabloane [Latte |latte:], deoarece de la 2 pagini veți dori să aveți [layout-ul și conținutul separate |latte:template-inheritance]. +- Și cu siguranță doriți să vă bazați pe [escaparea automată |latte:safety-first], pentru a nu crea o vulnerabilitate XSS. +- Nette asigură, de asemenea, că în caz de eroare nu se vor afișa niciodată mesaje de eroare PHP pentru programatori, ci o pagină inteligibilă pentru utilizator. +- Dacă doriți să obțineți feedback de la utilizatori, de exemplu sub forma unui formular de contact, atunci veți adăuga și [formulare |forms:] și [bază de date |database:]. +- Formularele completate le puteți, de asemenea, [trimite ușor prin email |mail:]. +- Uneori vă poate fi utilă [cache-uirea |caching:], de exemplu dacă descărcați și afișați feed-uri. + +În zilele noastre, când viteza și eficiența sunt esențiale, este important să aveți instrumente care vă permit să obțineți rezultate fără întârzieri inutile. Nette framework vă oferă exact asta - dezvoltare rapidă, securitate și o gamă largă de instrumente, cum ar fi Tracy și Latte, care simplifică procesul. Este suficient să instalați câteva pachete Nette și construirea unui astfel de microsite devine brusc o joacă de copii. Și știți că nu se ascunde nicio gaură de securitate nicăieri. diff --git a/best-practices/ro/pagination.texy b/best-practices/ro/pagination.texy index 4175e89bd9..fe8b8e2114 100644 --- a/best-practices/ro/pagination.texy +++ b/best-practices/ro/pagination.texy @@ -2,16 +2,15 @@ Paginarea rezultatelor bazei de date ************************************ .[perex] -Atunci când dezvoltați aplicații web, vă confruntați adesea cu cerința de a imprima un număr restrâns de înregistrări pe o pagină. +La crearea aplicațiilor web, vă veți întâlni foarte des cu cerința de a limita numărul de elemente afișate pe pagină. -Ieșim din starea în care ne aflăm atunci când enumerăm toate datele fără paginare. Pentru a selecta datele din baza de date, avem clasa ArticleRepository, care conține constructorul și metoda `findPublishedArticles`, care returnează toate articolele publicate, sortate în ordinea descrescătoare a datei de publicare. +Pornim de la starea în care afișăm toate datele fără paginare. Pentru selectarea datelor din baza de date avem clasa `ArticleRepository`, care, pe lângă constructor, conține metoda `findPublishedArticles`, ce returnează toate articolele publicate sortate descrescător după data publicării. ```php namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -31,10 +30,10 @@ class ArticleRepository } ``` -În Presenter vom injecta apoi clasa model, iar în metoda render vom cere articolele publicate pe care le vom trece în șablon: +În presenter injectăm apoi clasa model și în metoda render solicităm articolele publicate, pe care le transmitem șablonului: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -53,11 +52,11 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -În șablon, ne vom ocupa de redarea unei liste de articole: +În șablonul `default.latte` ne ocupăm apoi de afișarea articolelor: ```latte {block content} -<h1>Articles</h1> +<h1>Articole</h1> <div class="articles"> {foreach $articles as $article} @@ -68,11 +67,11 @@ class HomePresenter extends Nette\Application\UI\Presenter ``` -În acest fel, putem scrie toate articolele, dar acest lucru va cauza probleme atunci când numărul de articole crește. În acel moment, va fi util să implementăm mecanismul de paginare. +În acest mod putem afișa toate articolele, ceea ce însă începe să cauzeze probleme în momentul în care numărul articolelor crește. În acel moment devine utilă implementarea unui mecanism de paginare. -Acest lucru va asigura că toate articolele sunt împărțite în mai multe pagini și vom afișa doar articolele de pe o singură pagină curentă. Numărul total de pagini și distribuția articolelor este calculat chiar de [utils:Paginator], în funcție de câte articole avem în total și câte articole dorim să afișăm pe pagină. +Acesta asigură că toate articolele sunt împărțite în mai multe pagini și noi afișăm doar articolele unei pagini curente. Numărul total de pagini și împărțirea articolelor sunt calculate de [Paginator |utils:Paginator] singur, în funcție de câte articole avem în total și câte articole dorim să afișăm pe pagină. -În primul pas, vom modifica metoda de obținere a articolelor din clasa repository pentru a returna numai articole de o singură pagină. De asemenea, vom adăuga o nouă metodă pentru a obține numărul total de articole din baza de date, de care vom avea nevoie pentru a seta un Paginator: +În primul pas, vom folosi obiectul `Paginator` în presenter pentru a calcula limita și offset-ul necesare pentru interogarea bazei de date. Clasa `ArticleRepository` nu necesită modificări dacă folosim `Nette\Database\Explorer`, deoarece putem aplica paginarea direct pe obiectul `Selection`. ```php namespace App\Model; @@ -100,7 +99,7 @@ class ArticleRepository } /** - * Returns the total number of published articles + * Returnează numărul total de articole publicate */ public function getPublishedArticlesCount(): int { @@ -109,12 +108,12 @@ class ArticleRepository } ``` -Următorul pas este să modificăm prezentatorul. Vom transmite numărul paginii afișate în prezent către metoda de randare. În cazul în care acest număr nu face parte din URL, trebuie să stabilim valoarea implicită la prima pagină. +Ulterior, ne apucăm de modificările presenterului. În metoda render vom transmite numărul paginii afișate curent. Pentru cazul în care acest număr nu va face parte din URL, setăm valoarea implicită a primei pagini. -De asemenea, extindem metoda de randare pentru a obține instanța Paginator, configurând-o și selectând articolele corecte pentru a fi afișate în șablon. HomePresenter va arăta astfel: +Extindem, de asemenea, metoda render cu obținerea instanței Paginatorului, setarea sa și selectarea articolelor corecte pentru afișare în șablon. HomePresenter va arăta astfel după modificări: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -128,31 +127,31 @@ class HomePresenter extends Nette\Application\UI\Presenter public function renderDefault(int $page = 1): void { - // Vom afla numărul total de articole publicate + // Aflăm numărul total de articole publicate $articlesCount = $this->articleRepository->getPublishedArticlesCount(); - // Vom crea instanța Paginator și o vom configura + // Creăm o instanță a Paginatorului și o setăm $paginator = new Nette\Utils\Paginator; $paginator->setItemCount($articlesCount); // numărul total de articole - $paginator->setItemsPerPage(10); // articole pe pagină - $paginator->setPage($page); // numărul actual de pagini + $paginator->setItemsPerPage(10); // numărul de elemente pe pagină + $paginator->setPage($page); // numărul paginii curente - // Vom găsi un set limitat de articole din baza de date pe baza calculelor efectuate de Paginator + // Extragem din baza de date un set limitat de articole conform calculului Paginatorului $articles = $this->articleRepository->findPublishedArticles($paginator->getLength(), $paginator->getOffset()); - // pe care le vom transmite șablonului + // pe care îl transmitem șablonului $this->template->articles = $articles; - // și, de asemenea, Paginator însuși pentru a afișa opțiunile de paginare + // și, de asemenea, Paginatorul însuși pentru afișarea opțiunilor de paginare $this->template->paginator = $paginator; } } ``` -Șablonul itera deja peste articole într-o singură pagină, trebuie doar să adăugăm linkuri de paginare: +Șablonul nostru iterează acum doar peste articolele unei singure pagini, este suficient să adăugăm linkurile de paginare: ```latte {block content} -<h1>Articles</h1> +<h1>Articole</h1> <div class="articles"> {foreach $articles as $article} @@ -163,34 +162,33 @@ class HomePresenter extends Nette\Application\UI\Presenter <div class="pagination"> {if !$paginator->isFirst()} - <a n:href="default, 1">First</a> + <a n:href="default, 1">Prima</a>  |  - <a n:href="default, $paginator->page-1">Previous</a> + <a n:href="default, $paginator->page-1">Anterioara</a>  |  {/if} - Page {$paginator->getPage()} of {$paginator->getPageCount()} + Pagina {$paginator->getPage()} din {$paginator->getPageCount()} {if !$paginator->isLast()}  |  - <a n:href="default, $paginator->getPage() + 1">Next</a> + <a n:href="default, $paginator->getPage() + 1">Următoarea</a>  |  - <a n:href="default, $paginator->getPageCount()">Last</a> + <a n:href="default, $paginator->getPageCount()">Ultima</a> {/if} </div> ``` -Acesta este modul în care am adăugat paginarea folosind Paginator. Dacă [Nette |database:core] [Database Explorer |database:explorer] este utilizat în locul [Nette Database Core |database:core] ca strat de bază de date, putem implementa paginarea chiar și fără Paginator. Clasa `Nette\Database\Table\Selection` conține metoda [page |api:Nette\Database\Table\Selection::_ page] cu logica de paginare preluată din Paginator. +Astfel am completat pagina cu posibilitatea de paginare folosind `Paginator`. În cazul în care folosim [Nette Database Explorer |database:explorer], suntem capabili să implementăm paginarea și **fără a utiliza explicit** obiectul `Paginator` în presenter, deoarece clasa `Nette\Database\Table\Selection` conține metoda `page()` care încapsulează logica paginatorului. -Depozitul va arăta astfel: +Repository-ul rămâne același ca în exemplul cu Explorer: ```php namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -198,7 +196,6 @@ class ArticleRepository ) { } - public function findPublishedArticles(): Nette\Database\Table\Selection { return $this->database->table('articles') @@ -208,10 +205,10 @@ class ArticleRepository } ``` -Nu trebuie să creăm Paginator în Presenter, în schimb vom folosi metoda obiectului `Selection` returnat de depozit: +În presenter nu trebuie să creăm Paginator, folosim în locul său metoda clasei `Selection`, pe care ne-o returnează repository-ul: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -225,25 +222,25 @@ class HomePresenter extends Nette\Application\UI\Presenter public function renderDefault(int $page = 1): void { - // Vom găsi articole publicate + // Extragem articolele publicate $articles = $this->articleRepository->findPublishedArticles(); - // și partea lor limitată de metoda de calcul a paginii pe care o vom trece la șablonul + // și trimitem către șablon doar o parte din ele, limitată conform calculului metodei page $lastPage = 0; $this->template->articles = $articles->page($page, 10, $lastPage); - // și datele necesare pentru a afișa și opțiunile de paginare + // și, de asemenea, datele necesare pentru afișarea opțiunilor de paginare $this->template->page = $page; $this->template->lastPage = $lastPage; } } ``` -Deoarece nu folosim Paginator, trebuie să modificăm secțiunea care arată legăturile de paginare: +Deoarece acum nu trimitem `Paginator` către șablon, modificăm partea care afișează linkurile de paginare pentru a folosi variabilele `$page` și `$lastPage`: ```latte {block content} -<h1>Articles</h1> +<h1>Articole</h1> <div class="articles"> {foreach $articles as $article} @@ -254,24 +251,23 @@ Deoarece nu folosim Paginator, trebuie să modificăm secțiunea care arată leg <div class="pagination"> {if $page > 1} - <a n:href="default, 1">First</a> + <a n:href="default, 1">Prima</a>  |  - <a n:href="default, $page - 1">Previous</a> + <a n:href="default, $page - 1">Anterioara</a>  |  {/if} - Page {$page} of {$lastPage} + Pagina {$page} din {$lastPage} {if $page < $lastPage}  |  - <a n:href="default, $page + 1">Next</a> + <a n:href="default, $page + 1">Următoarea</a>  |  - <a n:href="default, $lastPage">Last</a> + <a n:href="default, $lastPage">Ultima</a> {/if} </div> ``` -În acest fel, am implementat un mecanism de paginare fără a utiliza un Paginator. +În acest mod am implementat mecanismul de paginare fără utilizarea Paginatorului. {{priority: -1}} -{{sitename: Best Practices}} diff --git a/best-practices/ro/passing-settings-to-presenters.texy b/best-practices/ro/passing-settings-to-presenters.texy index 4250d80c53..0b0e2883ee 100644 --- a/best-practices/ro/passing-settings-to-presenters.texy +++ b/best-practices/ro/passing-settings-to-presenters.texy @@ -1,10 +1,10 @@ -Transmiterea setărilor către prezentatori -***************************************** +Transmiterea setărilor către presenteri +*************************************** .[perex] -Aveți nevoie să transmiteți prezentatorilor argumente care nu sunt obiecte (de exemplu, informații despre faptul că rulează în modul de depanare, căi de acces la directoare etc.) și care, prin urmare, nu pot fi transmise automat prin autowiring? Soluția este să le încapsulați într-un obiect `Settings`. +Aveți nevoie să transmiteți argumente către presenteri care nu sunt obiecte (de ex. informația dacă rulează în modul debug, căi către directoare etc.) și, prin urmare, nu pot fi transmise automat prin autowiring? Soluția este să le încapsulați într-un obiect `Settings`. -Serviciul `Settings` este o modalitate foarte simplă, dar utilă, de a furniza prezentatorilor informații despre o aplicație în curs de execuție. Forma sa specifică depinde în întregime de nevoile dumneavoastră particulare. Exemplu: +Serviciul `Settings` reprezintă o modalitate foarte ușoară și totuși utilă de a furniza informații despre aplicația care rulează către presenteri. Forma sa specifică depinde exclusiv de nevoile dvs. concrete. Exemplu: ```php namespace App; @@ -12,7 +12,7 @@ namespace App; class Settings { public function __construct( - // de la PHP 8.1 este posibil să se specifice readonly + // de la PHP 8.1 este posibil să specificați readonly public bool $debugMode, public string $appDir, // și așa mai departe @@ -30,7 +30,7 @@ services: ) ``` -Atunci când prezentatorul are nevoie de informațiile furnizate de acest serviciu, el le solicită pur și simplu în constructor: +Când presenterul va avea nevoie de informațiile furnizate de acest serviciu, pur și simplu îl va solicita în constructor: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -47,5 +47,3 @@ class MyPresenter extends Nette\Application\UI\Presenter } } ``` - -{{sitename: Best Practices}} diff --git a/best-practices/ro/post-links.texy b/best-practices/ro/post-links.texy new file mode 100644 index 0000000000..449a9de1c5 --- /dev/null +++ b/best-practices/ro/post-links.texy @@ -0,0 +1,56 @@ +Cum să utilizați corect linkurile POST +************************************** + +.[perex] +În aplicațiile web, în special în interfețele administrative, ar trebui să fie o regulă de bază ca acțiunile care modifică starea serverului să nu fie efectuate prin metoda HTTP GET. După cum sugerează și numele metodei, GET ar trebui utilizat doar pentru obținerea datelor, nu pentru modificarea lor. Pentru acțiuni precum ștergerea înregistrărilor, este mai potrivită utilizarea metodei POST. Deși ideală ar fi metoda DELETE, aceasta nu poate fi invocată fără JavaScript, de aceea se folosește istoric POST. + +Cum se face acest lucru în practică? Utilizați acest truc simplu. La începutul șablonului, creați un formular auxiliar cu identificatorul `postForm`, pe care îl veți utiliza ulterior pentru butoanele de ștergere: + +```latte .{file:@layout.latte} +<form method="post" id="postForm"></form> +``` + +Datorită acestui formular, puteți utiliza un buton `<button>` în loc de linkul clasic `<a>`, care poate fi stilizat vizual pentru a arăta ca un link obișnuit. De exemplu, framework-ul CSS Bootstrap oferă clasele `btn btn-link` cu care puteți obține ca butonul să nu fie vizual diferit de alte linkuri. Folosind atributul `form="postForm"`, îl legați de formularul pre-pregătit: + +```latte .{file:admin.latte} +<table> + <tr n:foreach="$posts as $post"> + <td>{$post->title}</td> + <td> + <button class="btn btn-link" form="postForm" formaction="{link delete $post->id}">delete</button> + <!-- instead of <a n:href="delete $post->id">delete</a> --> + </td> + </tr> +</table> +``` + +La click pe buton, se va invoca acum acțiunea `delete` prin metoda POST. Pentru a asigura că cererile sunt acceptate doar prin metoda POST și de pe același domeniu (ceea ce este o apărare eficientă împotriva atacurilor CSRF), utilizați atributul `#[Requires]`: + +```php .{file:AdminPresenter.php} +use Nette\Application\Attributes\Requires; + +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST', sameOrigin: true)] + public function actionDelete(int $id): void + { + $this->facade->deletePost($id); // cod ipotetic care șterge înregistrarea + $this->redirect('default'); + } +} +``` + +Atributul există de la Nette Application 3.2 și mai multe despre posibilitățile sale puteți afla pe pagina [Cum să utilizați atributul #Requires |attribute-requires]. + +Dacă ați utiliza semnalul `handleDelete()` în loc de acțiunea `actionDelete()`, nu este necesar să specificați `sameOrigin: true`, deoarece semnalele au această protecție setată implicit: + +```php .{file:AdminPresenter.php} +#[Requires(methods: 'POST')] +public function handleDelete(int $id): void +{ + $this->facade->deletePost($id); + $this->redirect('this'); +} +``` + +Această abordare nu numai că îmbunătățește securitatea aplicației dvs., dar contribuie și la respectarea standardelor și practicilor web corecte. Prin utilizarea metodelor POST pentru acțiunile care modifică starea, veți obține o aplicație mai robustă și mai sigură. diff --git a/best-practices/ro/presenter-traits.texy b/best-practices/ro/presenter-traits.texy index 496296a400..a074c21292 100644 --- a/best-practices/ro/presenter-traits.texy +++ b/best-practices/ro/presenter-traits.texy @@ -1,14 +1,14 @@ -Compunerea prezentatorilor din trăsături -**************************************** +Compunerea presenterilor din trait-uri +************************************** .[perex] -Dacă trebuie să implementăm același cod în mai multe prezentări (de exemplu, verificarea faptului că utilizatorul este conectat), este tentant să plasăm codul într-un strămoș comun. A doua opțiune este crearea unor trăsături cu scop unic. +Dacă avem nevoie să implementăm același cod în mai mulți presenteri (de ex. verificarea că utilizatorul este autentificat), o opțiune este plasarea codului într-un strămoș comun. A doua opțiune este crearea de [trait-uri |nette:introduction-to-object-oriented-programming#Trait-uri] cu un singur scop. -Avantajul acestei soluții este că fiecare prezentator poate utiliza doar trăsăturile de care are nevoie efectiv, în timp ce moștenirea multiplă nu este posibilă în PHP. +Avantajul acestei soluții este că fiecare dintre presenteri poate folosi exact acele trait-uri de care are nevoie cu adevărat, în timp ce moștenirea multiplă nu este posibilă în PHP. -Aceste trăsături pot profita de faptul că toate [metodele de injectare |inject-method-attribute#inject methods] sunt apelate secvențial atunci când este creat prezentatorul. Trebuie doar să vă asigurați că numele fiecărei metode inject este unic. +Aceste trait-uri pot profita de faptul că la crearea presenterului se apelează succesiv toate [metodele inject |inject-method-attribute#Metode inject]. Este necesar doar să se asigure că numele fiecărei metode inject este unic pentru a evita conflictele. -Traits pot agăța codul de inițializare în evenimentele [onStartup sau onRender |application:presenters#Events]. +Trait-urile pot atașa cod de inițializare la evenimentele [onStartup sau onRender |application:presenters#Evenimente]. Exemple: @@ -36,7 +36,7 @@ trait StandardTemplateFilters } ``` -Prezentatorul folosește apoi pur și simplu aceste trăsături: +Presenterul apoi utilizează simplu aceste trait-uri: ```php class ArticlePresenter extends Nette\Application\UI\Presenter @@ -45,6 +45,3 @@ class ArticlePresenter extends Nette\Application\UI\Presenter use RequireLoggedUser; } ``` - - -{{sitename: Best Practices}} diff --git a/best-practices/ro/restore-request.texy b/best-practices/ro/restore-request.texy index 65ae422103..a8cee5857c 100644 --- a/best-practices/ro/restore-request.texy +++ b/best-practices/ro/restore-request.texy @@ -1,17 +1,16 @@ -Cum să vă întoarceți la o pagină anterioară? -******************************************** +Cum să reveniți la pagina anterioară? +************************************* .[perex] -Ce se întâmplă dacă un utilizator completează un formular și îi expiră autentificarea? Pentru a evita pierderea datelor, salvăm datele în sesiune înainte de a le redirecționa către pagina de autentificare. În Nette, acest lucru este floare la ureche. +Ce se întâmplă dacă un utilizator completează un formular și sesiunea sa expiră? Pentru a nu pierde datele, înainte de a redirecționa către pagina de autentificare, salvăm cererea curentă în sesiune. În Nette, acest lucru este extrem de simplu. -Cererea curentă poate fi stocată în sesiune folosind metoda `storeRequest()`, care returnează identificatorul acesteia sub forma unui șir scurt. Metoda stochează numele prezentatorului curent, vizualizarea și parametrii acesteia. -Dacă a fost trimis și un formular, sunt salvate și valorile câmpurilor (cu excepția fișierelor încărcate). +Cererea curentă poate fi salvată în sesiune folosind metoda `storeRequest()`, care returnează identificatorul său sub forma unui șir scurt. Metoda salvează numele presenterului curent, view-ul și parametrii săi. În cazul în care a fost trimis și un formular, se salvează și conținutul câmpurilor (cu excepția fișierelor încărcate). -Cererea este restaurată de metoda `restoreRequest($key)`, căreia îi transmitem identificatorul recuperat. Aceasta redirecționează către prezentatorul și vizualizarea originale. Cu toate acestea, în cazul în care cererea salvată conține o trimitere de formular, se redirecționează către prezentatorul original prin metoda `forward()`, se trec valorile completate anterior în formular și se lasă să fie redesenat. Acest lucru permite utilizatorului să retrimită formularul și nu se pierd date. +Restaurarea cererii se face prin metoda `restoreRequest($key)`, căreia îi transmitem identificatorul obținut. Aceasta redirecționează către presenterul și view-ul original. Dacă însă cererea salvată conține trimiterea unui formular, trece la presenterul original prin metoda `forward()`, transmite formularului valorile completate anterior și îl lasă să se redeseneze din nou. Astfel, utilizatorul are posibilitatea de a retrimite formularul și nu se pierd date. -Este important faptul că `restoreRequest()` verifică dacă utilizatorul nou conectat este același care a completat inițial formularul. În caz contrar, acesta respinge cererea și nu face nimic. +Important este că `restoreRequest()` verifică dacă utilizatorul nou autentificat este același cu cel care a completat inițial formularul. Dacă nu, cererea este abandonată și nu se face nimic. -Să demonstrăm totul cu un exemplu. Să avem un prezentator `AdminPresenter` în care se editează date și a cărui metodă `startup()` verifică dacă utilizatorul este logat. Dacă nu este, îl redirecționăm către `SignPresenter`. În același timp, salvăm cererea curentă și trimitem cheia acesteia către `SignPresenter`. +Vom ilustra totul cu un exemplu. Avem un presenter `AdminPresenter`, în care se editează date și în a cărui metodă `startup()` verificăm dacă utilizatorul este autentificat. Dacă nu este, îl redirecționăm către `SignPresenter`. În același timp, salvăm cererea curentă și trimitem cheia sa (`backlink`) către `SignPresenter`. ```php class AdminPresenter extends Nette\Application\UI\Presenter @@ -27,7 +26,7 @@ class AdminPresenter extends Nette\Application\UI\Presenter } ``` -Prezentatorul `SignPresenter` va conține un parametru persistent `$backlink` în care este scrisă cheia, în plus față de formularul de logare. Deoarece parametrul este persistent, acesta va fi păstrat chiar și după trimiterea formularului de conectare. +Presenterul `SignPresenter` va conține, pe lângă formularul de autentificare, și un parametru persistent `$backlink`, în care se va scrie cheia. Deoarece parametrul este persistent, acesta se va transmite și după trimiterea formularului de autentificare. ```php @@ -41,14 +40,14 @@ class SignPresenter extends Nette\Application\UI\Presenter protected function createComponentSignInForm() { $form = new Nette\Application\UI\Form; - // ... adăugați câmpuri de formular ... + // ... adăugăm câmpurile formularului ... $form->onSuccess[] = [$this, 'signInFormSubmitted']; return $form; } public function signInFormSubmitted($form) { - // ... aici semnăm intrarea utilizatorului ... + // ... aici autentificăm utilizatorul ... $this->restoreRequest($this->backlink); $this->redirect('Admin:'); @@ -56,9 +55,8 @@ class SignPresenter extends Nette\Application\UI\Presenter } ``` -Trecem cheia cererii salvate la metoda `restoreRequest()`, iar aceasta redirecționează (sau transmite) către prezentatorul original. +Metodei `restoreRequest()` îi transmitem cheia cererii salvate și aceasta redirecționează (sau trece) la presenterul original. -Cu toate acestea, dacă cheia nu este validă (de exemplu, nu mai există în sesiune), metoda nu face nimic. Așadar, următorul apel este `$this->redirect('Admin:')`, care redirecționează către `AdminPresenter`. +Dacă însă cheia este invalidă (de exemplu, nu mai există în sesiune), metoda nu face nimic. Urmează deci apelul `$this->redirect('Admin:')`, care redirecționează către `AdminPresenter`. {{priority: -1}} -{{sitename: Best Practices}} diff --git a/best-practices/ru/@home.texy b/best-practices/ru/@home.texy index 05f2905032..1ea3ce7e1e 100644 --- a/best-practices/ru/@home.texy +++ b/best-practices/ru/@home.texy @@ -1,22 +1,24 @@ -Лучшие практики -*************** +Руководства и лучшие практики +***************************** .[perex] -Учебные пособия, решения распространенных проблем и лучшие практики для Nette. +Руководства, решения частых задач и *лучшие практики* для Nette. <div class=documentation> <div> -Приложение Nette +Приложения Nette ---------------- -- [Инжектирование методов и атрибутов |inject-method-attribute] -- [Составление презентеров из признаков |presenter-traits] -- [Передача параметров в презентеры |passing-settings-to-presenters] +- [Методы и атрибуты inject |inject-method-attribute] +- [Компоновка презентеров из трейтов |presenter-traits] +- [Передача настроек в презентеры |passing-settings-to-presenters] - [Как вернуться на предыдущую страницу |restore-request] -- [Пагинация результатов базы данных |Pagination] -- [Динамические фрагменты |dynamic-snippets] +- [Пагинация результатов базы данных |pagination] +- [Динамические сниппеты |dynamic-snippets] +- [Как использовать атрибут #Requires |attribute-requires] +- [Как правильно использовать POST-ссылки |post-links] </div> <div> @@ -25,9 +27,9 @@ Формы ----- - [Повторное использование форм |form-reuse] -- [Форма для создания и редактирования записи |creating-editing-form] -- [Давайте создадим контактную форму |lets-create-contact-form] -- [Зависимые поля выбора |https://blog.nette.org/ru/zavisimye-selekboksy-elegantno-v-nette-i-cistom-js] +- [Форма для создания и редактирования записей |creating-editing-form] +- [Создаем контактную форму |lets-create-contact-form] +- [Зависимые селектбоксы |https://blog.nette.org/ru/dependent-selectboxes-elegantly-in-nette-and-pure-js] </div> <div> @@ -35,20 +37,22 @@ Общие ----- -- [Как загрузить файл конфигурации |bootstrap:] -- [Почему Nette использует константную нотацию PascalCase? |https://blog.nette.org/ru/ctoby-men-se-kricat-v-kode] -- [Почему Nette не использует суффикс Interface? |https://blog.nette.org/ru/prefiksy-i-suffiksy-ne-dolzny-prisutstvovat-v-imenah-interfejsov] -- [Советы по использованию Composer |composer] +- [Как загрузить конфигурационный файл |bootstrap:] +- [Как писать микросайты |microsites] +- [Почему Nette использует PascalCase нотацию для констант? |https://blog.nette.org/ru/for-less-screaming-in-the-code] +- [Почему Nette не использует суффикс Interface? |https://blog.nette.org/ru/prefixes-and-suffixes-do-not-belong-in-interface-names] +- [Composer: советы по использованию |composer] - [Советы по редакторам и инструментам |editors-and-tools] +- [Введение в объектно-ориентированное программирование |nette:introduction-to-object-oriented-programming] </div> <div> -Образец решения +Примеры решений --------------- - [Примеры Nette |https://github.com/nette-examples] -- [Доктрина и Nette |https://contributte.org/nettrine/] +- [Doctrine и Nette |https://contributte.org/nettrine/] - [Примеры Contributte |https://contributte.org/examples.html] - [Сайт Doctrine ORM |https://github.com/MinecordNetwork/Website] - [Быстрый старт |quickstart:] @@ -59,10 +63,7 @@ Видео ----- -На "YouTube-канале Nette Framework":https://www.youtube.com/user/NetteFramework вы можете найти сотни записей из Posobota и видео о Nette под одной крышей. +Сотни записей с Posledních sobot и видео о Nette вы найдете под одной крышей на "Youtube-канале Nette Framework":https://www.youtube.com/user/NetteFramework. </div> </div> - -{{sitename: Лучшие практики}} -{{leftbar: www:@menu-common}} diff --git a/best-practices/ru/@meta.texy b/best-practices/ru/@meta.texy new file mode 100644 index 0000000000..6463960eed --- /dev/null +++ b/best-practices/ru/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Руководства и лучшие практики}} +{{leftbar: www:@menu-common}} diff --git a/best-practices/ru/attribute-requires.texy b/best-practices/ru/attribute-requires.texy new file mode 100644 index 0000000000..8fafce3e80 --- /dev/null +++ b/best-practices/ru/attribute-requires.texy @@ -0,0 +1,177 @@ +Как использовать атрибут `#[Requires]` +************************************** + +.[perex] +При написании веб-приложения вы часто сталкиваетесь с необходимостью ограничить доступ к определенным частям вашего приложения. Возможно, вы хотите, чтобы некоторые запросы могли отправлять данные только с помощью формы (то есть методом POST), или чтобы они были доступны только для AJAX-вызовов. В Nette Framework 3.2 появился новый инструмент, который позволяет вам устанавливать такие ограничения очень элегантно и наглядно: атрибут `#[Requires]`. + +Атрибут — это специальная метка в PHP, которую вы добавляете перед определением класса или метода. Поскольку это фактически класс, чтобы следующие примеры работали, необходимо указать клаузу use: + +```php +use Nette\Application\Attributes\Requires; +``` + +Атрибут `#[Requires]` можно использовать у самого класса презентера, а также у следующих методов: + +- `action<Action>()` +- `render<View>()` +- `handle<Signal>()` +- `createComponent<Name>()` + +Последние два метода относятся и к компонентам, то есть атрибут можно использовать и у них. + +Если условия, указанные атрибутом, не выполнены, вызывается HTTP-ошибка 4xx. + + +Методы HTTP +----------- + +Вы можете указать, какие HTTP-методы (например, GET, POST и т. д.) разрешены для доступа. Например, если вы хотите разрешить доступ только путем отправки формы, установите: + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +Почему следует использовать POST вместо GET для действий, изменяющих состояние, и как это сделать? [Прочитайте руководство |post-links]. + +Вы можете указать метод или массив методов. Особым случаем является значение `'*'`, которое разрешает все методы, что стандартно презентеры по [соображениям безопасности не позволяют |application:presenters#Проверка HTTP-метода]. + + +AJAX-вызов +---------- + +Если вы хотите, чтобы презентер или метод был доступен только для AJAX-запросов, используйте: + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Тот же источник +--------------- + +Для повышения безопасности вы можете требовать, чтобы запрос был сделан с того же домена. Это предотвратит [уязвимость CSRF |nette:vulnerability-protection#Межсайтовая подделка запроса CSRF]: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +У методов `handle<Signal>()` доступ с того же домена требуется автоматически. Так что если, наоборот, вы хотите разрешить доступ с любого домена, укажите: + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Доступ через forward +-------------------- + +Иногда полезно ограничить доступ к презентеру так, чтобы он был доступен только косвенно, например, с использованием метода `forward()` или `switch()` из другого презентера. Так, например, защищаются error-презентеры, чтобы их нельзя было вызвать из URL: + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +На практике часто бывает необходимо пометить определенные представления, к которым можно получить доступ только на основе логики в презентере. То есть опять же, чтобы их нельзя было открыть напрямую: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = $this->facade->getProduct($id); + if (!$product) { + $this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Конкретные действия +------------------- + +Вы также можете ограничить, чтобы определенный код, например, создание компонента, был доступен только для специфических действий в презентере: + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +В случае одного действия нет необходимости записывать массив: `#[Requires(actions: 'default')]` + + +Собственные атрибуты +-------------------- + +Если вы хотите использовать атрибут `#[Requires]` повторно с теми же настройками, вы можете создать собственный атрибут, который будет наследовать `#[Requires]` и настроит его в соответствии с потребностями. + +Например, `#[SingleAction]` разрешит доступ только через действие `default`: + +```php +#[\Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Или `#[RestMethods]` разрешит доступ через все HTTP-методы, используемые для REST API: + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Заключение +---------- + +Атрибут `#[Requires]` дает вам большую гибкость и контроль над тем, как доступны ваши веб-страницы. С помощью простых, но мощных правил вы можете повысить безопасность и правильное функционирование вашего приложения. Как видите, использование атрибутов в Nette может не только упростить вашу работу, но и обезопасить ее. diff --git a/best-practices/ru/composer.texy b/best-practices/ru/composer.texy index 8b6c3fca4a..b8f63ccad6 100644 --- a/best-practices/ru/composer.texy +++ b/best-practices/ru/composer.texy @@ -1,44 +1,44 @@ -Советы по использованию Composer -******************************** +Composer: советы по использованию +********************************* <div class=perex> -Composer — это инструмент для управления зависимостями в PHP. Он позволяет вам объявить библиотеки, от которых зависит ваш проект, и он будет устанавливать и обновлять их за вас. Мы узнаем: +Composer — это инструмент для управления зависимостями в PHP. Он позволяет нам перечислить библиотеки, от которых зависит наш проект, и будет устанавливать и обновлять их за нас. Мы покажем: - как установить Composer -- использовать его в новом или существующем проекте +- его использование в новом или существующем проекте </div> -Установка .[#toc-installation] -============================== +Установка +========= -Composer — это исполняемый файл `.phar`, который вы загружаете и устанавливаете следующим образом. +Composer — это исполняемый файл `.phar`, который вы скачиваете и устанавливаете следующим образом: Windows ------- -Используйте официальную программу установки [Composer-Setup.exe|https://getcomposer.org/Composer-Setup.exe]. +Используйте официальный установщик [Composer-Setup.exe |https://getcomposer.org/Composer-Setup.exe]. Linux, macOS ------------ -Всё, что вам нужно, это 4 команды, которые вы можете скопировать с [этой страницы |https://getcomposer.org/download/]. +Достаточно 4 команд, которые скопируйте с [этой страницы |https://getcomposer.org/download/]. -Более того, при копировании в папку, находящуюся в системном `PATH`, Composer становится глобально доступным: +Далее, поместив в папку, которая находится в системном `PATH`, Composer станет доступен глобально: ```shell $ mv ./composer.phar ~/bin/composer # или /usr/local/bin/composer ``` -Использование в существующем проекте .[#toc-use-in-project] -=========================================================== +Использование в проекте +======================= -Чтобы начать использовать Composer в своем проекте, всё, что вам нужно, это файл `composer.json`. Этот файл описывает зависимости вашего проекта и может содержать другие метаданные. Самый простой `composer.json` может выглядеть следующим образом: +Чтобы начать использовать Composer в своем проекте, вам нужен только файл `composer.json`. Он описывает зависимости нашего проекта и может также содержать другие метаданные. Базовый `composer.json` может выглядеть так: ```js { @@ -48,17 +48,17 @@ $ mv ./composer.phar ~/bin/composer # или /usr/local/bin/composer } ``` -Здесь мы говорим, что наше приложение (или библиотека) зависит от пакета `nette/database` (имя пакета состоит из имени поставщика и имени проекта) и ему нужна версия, соответствующая ограничению `^3.0`. +Здесь мы говорим, что наше приложение (или библиотека) требует пакет `nette/database` (название пакета состоит из названия организации и названия проекта) и хочет версию, которая соответствует условию `^3.0` (т. е. последнюю версию 3). -Итак, когда у нас есть файл `composer.json` в корне проекта и мы запускаем: +Итак, у нас есть в корне проекта файл `composer.json`, и мы запускаем установку: ```shell composer update ``` -Composer загрузит исходные файлы Nette в каталог `vendor`. Он также создает файл `composer.lock`, который содержит информацию о том, какие именно версии библиотек установлены. +Composer скачает Nette Database в папку `vendor/`. Далее он создаст файл `composer.lock`, который содержит информацию о том, какие именно версии библиотек он установил. -Composer генерирует файл `vendor/autoload.php`. Вы можете просто включить этот файл и начать использовать классы, которые предоставляют эти библиотеки, без лишней работы: +Composer сгенерирует файл `vendor/autoload.php`, который мы можем просто включить и начать использовать библиотеки без какой-либо дополнительной работы: ```php require __DIR__ . '/vendor/autoload.php'; @@ -67,52 +67,52 @@ $db = new Nette\Database\Connection('sqlite::memory:'); ``` -Обновление пакетов до последних версий .[#toc-update-packages-to-the-latest-versions] -===================================================================================== +Обновление пакетов до последних версий +====================================== -Для обновления всех используемых пакетов до последней версии в соответствии с ограничениями версий, определенными в файле `composer.json`, используйте команду `composer update`. Например, для зависимости `"nette/database": "^3.0"` будет установлена последняя версия 3.x.x, но не версия 4. +За обновление используемых библиотек до последних версий в соответствии с условиями, определенными в `composer.json`, отвечает команда `composer update`. Например, для зависимости `"nette/database": "^3.0"` установит последнюю версию 3.x.x, но не версию 4. -Чтобы обновить ограничения версии в файле `composer.json` на, например, "nette/database": "^4.1"`, для установки последней версии, используйте команду `composer require nette/database`. +Для обновления условий в файле `composer.json`, например, до `"nette/database": "^4.1"`, чтобы можно было установить последнюю версию, используйте команду `composer require nette/database`. -Чтобы обновить все используемые пакеты Nette, необходимо перечислить их все в командной строке, например: +Для обновления всех используемых пакетов Nette потребовалось бы перечислить их все в командной строке, например: ```shell composer require nette/application nette/forms latte/latte tracy/tracy ... ``` -Что непрактично. Поэтому используйте простой сценарий "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff, который сделает это за вас: +Что непрактично. Используйте поэтому простой скрипт "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff, который сделает это за вас: ```shell php composer-frontline.php ``` -Создание нового проекта .[#toc-creating-new-project] -==================================================== +Создание нового проекта +======================= -Новый проект Nette можно создать, выполнив простую команду: +Новый проект на Nette создается с помощью одной команды: ```shell -composer create-project nette/web-project name-of-the-project +composer create-project nette/web-project nazev-projekta ``` -Вместо `name-of-the-project` укажите имя каталога для вашего проекта и выполните команду. Composer получит репозиторий `nette/web-project` с GitHub, который уже содержит файл `composer.json`, и сразу после этого установит сам фреймворк Nette. Осталось только [проверить права на запись |nette:troubleshooting#Setting-Directory-Permissions] для директорий `temp/` и `log/`, и вы готовы к работе. +В качестве `nazev-projekta` вставьте название каталога для своего проекта и подтвердите. Composer скачает репозиторий `nette/web-project` с GitHub, который уже содержит файл `composer.json`, и сразу после этого Nette Framework. Должно уже хватить только [установить права |nette:troubleshooting#Настройка прав доступа к каталогам] на запись в папки `temp/` и `log/`, и проект должен ожить. -Если вы знаете, на какой версии PHP будет размещен проект, обязательно установите [ее |#PHP Version]. +Если вы знаете, на какой версии PHP будет хоститься проект, не забудьте [ее установить |#Версия PHP]. -Версия PHP .[#toc-php-version] -============================== +Версия PHP +========== -Composer всегда устанавливает версии пакетов, совместимые с версией PHP, которую вы используете в данный момент (точнее, версию PHP, используемую в командной строке при запуске Composer). А это, скорее всего, не та версия, которую использует ваш веб-хост. Поэтому очень важно добавить информацию о версии PHP на вашем хостинге в файл `composer.json`. После этого будут установлены только версии пакетов, совместимые с хостом. +Composer всегда устанавливает те версии пакетов, которые совместимы с версией PHP, которую вы сейчас используете (точнее, с версией PHP, используемой в командной строке при запуске Composer). Что, однако, скорее всего, не та же версия, которую использует ваш хостинг. Поэтому очень важно добавить в файл `composer.json` информацию о версии PHP на хостинге. После этого будут устанавливаться только версии пакетов, совместимые с хостингом. -Например, чтобы настроить проект для работы на PHP 8.2.3, используйте команду: +То, что проект будет работать, например, на PHP 8.2.3, мы установим командой: ```shell composer config platform.php 8.2.3 ``` -Таким образом версия записывается в файл `composer.json`: +Так версия запишется в файл `composer.json`: ```js { @@ -124,14 +124,13 @@ composer config platform.php 8.2.3 } ``` -Однако номер версии PHP также указывается в другом месте файла, в секции `require`. В то время как первое число указывает версию, для которой будут установлены пакеты, второе число говорит о том, для какой версии написано само приложение. -(Конечно, нет смысла в том, чтобы эти версии были разными, поэтому двойная запись является излишеством). Вы устанавливаете эту версию с помощью команды: +Однако номер версии PHP указывается еще в другом месте файла, а именно в секции `require`. В то время как первое число определяет, для какой версии будут устанавливаться пакеты, второе число говорит, для какой версии написано само приложение. И по нему, например, PhpStorm устанавливает `PHP language level`. (Конечно, нет смысла, чтобы эти версии различались, так что двойная запись — это недоработка.) Эту версию вы установите командой: ```shell composer require php 8.2.3 --no-update ``` -Или непосредственно в файле `composer.json`: +Или прямо в файле `composer.json`: ```js { @@ -142,66 +141,72 @@ composer require php 8.2.3 --no-update ``` -Ложные отчеты .[#toc-false-reports] -=================================== +Игнорирование версии PHP +======================== -При обновлении пакетов или изменении номеров версий возникают конфликты. Один пакет имеет требования, которые конфликтуют с другим и так далее. Однако иногда Composer выдает ложные сообщения. Он сообщает о конфликте, которого на самом деле не существует. В этом случае следует удалить файл `composer.lock` и повторить попытку. +Пакеты обычно указывают как самую низкую версию PHP, с которой они совместимы, так и самую высокую, с которой они протестированы. Если вы собираетесь использовать версию PHP еще новее, например, для тестирования, Composer откажется устанавливать такой пакет. Решением является опция `--ignore-platform-req=php+`, которая заставит Composer игнорировать верхние пределы требуемой версии PHP. -Если сообщение об ошибке не исчезает, значит, оно имеет серьезное значение, и вам нужно прочитать из него, что и как нужно изменить. +Ложные сообщения +================ -Packagist.org — глобальный репозиторий .[#toc-packagist-org-global-repository] -============================================================================== +При обновлении пакетов или изменении номеров версий случается, что возникает конфликт. Один пакет имеет требования, которые противоречат другому, и т. п. Composer, однако, иногда выводит ложные сообщения. Сообщает о конфликте, которого на самом деле нет. В таком случае поможет удалить файл `composer.lock` и попробовать снова. -[Packagist |https://packagist.org] — это основное хранилище пакетов, в котором Composer пытается искать пакеты, если не сказано иначе. Вы также можете опубликовать здесь свои собственные пакеты. +Если сообщение об ошибке сохраняется, то оно серьезное, и нужно из него понять, что и как исправить. -Что если нам не нужен центральный репозиторий .[#toc-what-if-we-don-t-want-the-central-repository] --------------------------------------------------------------------------------------------------- +Packagist.org - центральный репозиторий +======================================= + +[Packagist |https://packagist.org] — это главный репозиторий, в котором Composer пытается искать пакеты, если ему не скажут иначе. Здесь мы можем публиковать и собственные пакеты. + -Если в нашей компании есть внутренние приложения или библиотеки, которые не могут быть размещены публично на Packagist, мы можем создать собственные репозитории для этих проектов. +Что делать, если мы не хотим использовать центральный репозиторий? +------------------------------------------------------------------ -Подробнее о репозиториях в [официальной документации |https://getcomposer.org/doc/05-repositories.md#Репозитории]. +Если у нас есть внутрифирменные приложения, которые мы просто не можем хостить публично, то мы создадим для них фирменный репозиторий. +Больше на тему репозиториев [в официальной документации |https://getcomposer.org/doc/05-repositories.md#repositories]. -Автозагрузка .[#toc-autoloading] -================================ -Ключевой особенностью Composer является то, что он обеспечивает автозагрузку для всех устанавливаемых классов, которую вы запускаете путем включения файла `vendor/autoload.php`. +Автозагрузка +============ -Однако можно также использовать Composer для загрузки других классов вне папки `vendor`. Первый вариант — позволить Composer просканировать определенные папки и подпапки, найти все классы и включить их в автозагрузку. Для этого установите `autoload > classmap` в файле `composer.json`: +Ключевой особенностью Composer является то, что он предоставляет автозагрузку для всех установленных им классов, которую вы запускаете, включив файл `vendor/autoload.php`. + +Однако можно использовать Composer и для загрузки других классов и вне папки `vendor`. Первой возможностью является позволить Composer просканировать определенные папки и подпапки, найти все классы и включить их в автозагрузчик. Этого можно достичь, установив `autoload > classmap` в `composer.json`: ```js { "autoload": { "classmap": [ - "src/", # включает папку src/ со всеми вложенными директориями + "src/", # включит папку src/ и ее подпапки ] } } ``` -Впоследствии необходимо выполнять команду `composer dumpautoload` при каждом изменении и позволять таблицам автозагрузки регенерироваться. Это крайне неудобно, и гораздо лучше доверить эту задачу [RobotLoader|robot-loader:], который выполняет ту же самую работу автоматически в фоновом режиме и гораздо быстрее. +Затем необходимо при каждом изменении запускать команду `composer dumpautoload` и позволить перегенерировать таблицы автозагрузки. Это крайне неудобно, и гораздо лучше доверить эту задачу [RobotLoader |robot-loader:], который ту же самую деятельность выполняет автоматически в фоновом режиме и гораздо быстрее. -Второй вариант — следовать [PSR-4 |https://www.php-fig.org/psr/psr-4/]. Проще говоря, это система, в которой пространства имен и имена классов соответствуют структуре каталогов и именам файлов, т. е. `App\Router\RouterFactory` находится в файле `/path/to/App/Router/RouterFactory.php`. Пример конфигурации: +Второй возможностью является соблюдение [PSR-4 |https://www.php-fig.org/psr/psr-4/]. Упрощенно говоря, это система, когда пространства имен и названия классов соответствуют структуре каталогов и названиям файлов, то есть, например, `App\Core\RouterFactory` будет в файле `/path/to/App/Core/RouterFactory.php`. Пример конфигурации: ```js { "autoload": { "psr-4": { - "App\\": "app/" # пространство имён App\ указывает на директорию app/ + "App\\": "app/" # пространство имен App\ находится в каталоге app/ } } } ``` -Как именно настроить это поведение, смотрите в [документации Composer |https://getcomposer.org/doc/04-schema.md#PSR-4]. +Как точно настроить поведение, вы узнаете в [документации Composer |https://getcomposer.org/doc/04-schema.md#psr-4]. -Тестирование новых версий .[#toc-testing-new-versions] -====================================================== +Тестирование новых версий +========================= -Вы хотите протестировать новую версию пакета. Как это сделать? Во-первых, добавьте эту пару опций в файл `composer.json`, которая позволит вам устанавливать версии пакетов для разработки, но сделает это только в том случае, если нет стабильной версии, отвечающей требованиям: +Хотите протестировать новую разработочную версию пакета? Как это сделать? Сначала в файл `composer.json` добавьте эту пару опций, которая позволит устанавливать разработочные версии пакетов, однако прибегнет к этому только в случае, если не существует никакой комбинации стабильных версий, которая бы удовлетворяла требованиям: ```js { @@ -210,9 +215,9 @@ Packagist.org — глобальный репозиторий .[#toc-packagist-o } ``` -Мы также рекомендуем удалить файл `composer.lock`, потому что иногда Composer непонятным образом отказывается устанавливаться, и это решит проблему. +Далее рекомендуем удалить файл `composer.lock`, иногда Composer необъяснимо отказывается от установки, и это решает проблему. -Допустим, пакет - `nette/utils` и новая версия - 4.0. Вы устанавливаете его с помощью команды: +Допустим, это пакет `nette/utils`, и новая версия имеет номер 4.0. Установите ее командой: ```shell composer require nette/utils:4.0.x-dev @@ -224,20 +229,19 @@ composer require nette/utils:4.0.x-dev composer require nette/utils:4.0.0-RC2 ``` -Если другой пакет зависит от библиотеки и заблокирован на более старую версию (например, `^3.1`), идеально будет обновить пакет для работы с новой версией. -Однако если вы просто хотите обойти ограничение и заставить Composer установить версию разработки и притвориться, что это старая версия (например, 3.1.6), вы можете использовать ключевое слово `as`: +Но если от библиотеки зависит другой пакет, который заблокирован на старой версии (например, `^3.1`), то идеально обновить пакет, чтобы он работал с новой версией. Однако, если вы хотите просто обойти ограничение и заставить Composer установить разработочную версию и притвориться, что это старая версия (например, 3.1.6), вы можете использовать ключевое слово `as`: ```shell composer require nette/utils "4.0.x-dev as 3.1.6" ``` -Команды вызова .[#toc-calling-commands] -======================================= +Вызов команд +============ -Вы можете вызывать свои собственные пользовательские команды и сценарии через Composer, как если бы это были родные команды Composer. Скриптам, расположенным в папке `vendor/bin`, не нужно указывать эту папку. +Через Composer можно вызывать собственные предопределенные команды и скрипты, как если бы это были нативные команды Composer. Для скриптов, которые находятся в папке `vendor/bin`, не нужно указывать эту папку. -В качестве примера мы определяем сценарий в файле `composer.json`, который использует [Nette Tester |tester:] для запуска тестов: +В качестве примера определим в файле `composer.json` скрипт, который с помощью [Nette Tester |tester:] запустит тесты: ```js { @@ -247,19 +251,19 @@ composer require nette/utils "4.0.x-dev as 3.1.6" } ``` -Затем мы запускаем тесты с помощью `composer tester`. Мы можем вызвать команду, даже если находимся не в корневой папке проекта, а в подкаталоге. +Тесты затем запустим с помощью `composer tester`. Команду можно вызвать и в случае, если мы не находимся в корневой папке проекта, а в каком-либо подкаталоге. -Отправьте благодарность .[#toc-send-thanks] -=========================================== +Отправьте благодарность +======================= -Мы покажем вам трюк, который порадует авторов открытых исходников. Вы можете легко присвоить звезду на GitHub библиотекам, которые использует ваш проект. Просто установите библиотеку `symfony/thanks`: +Покажем вам трюк, которым вы порадуете авторов open source. Простым способом поставите на GitHub звездочку библиотекам, которые использует ваш проект. Достаточно установить библиотеку `symfony/thanks`: ```shell composer global require symfony/thanks ``` -А потом наберите: +А затем запустить: ```shell composer thanks @@ -268,13 +272,11 @@ composer thanks Попробуйте! -Конфигурация .[#toc-configuration] -================================== +Конфигурация +============ -Composer тесно интегрирован с инструментом контроля версий [Git |https://git-scm.com]. Если вы не используете Git, необходимо сообщить об этом Composer: +Composer тесно связан с инструментом версионирования [Git |https://git-scm.com]. Если он у вас не установлен, нужно сказать Composer, чтобы он его не использовал: ```shell composer -g config preferred-install dist ``` - -{{sitename: Лучшие практики}} diff --git a/best-practices/ru/creating-editing-form.texy b/best-practices/ru/creating-editing-form.texy index 3acfca7d11..bc6b5b84e1 100644 --- a/best-practices/ru/creating-editing-form.texy +++ b/best-practices/ru/creating-editing-form.texy @@ -2,15 +2,15 @@ ****************************************** .[perex] -Как правильно реализовать добавление и редактирование записи в Nette, используя одну и ту же форму для обеих? +Как правильно реализовать в Nette добавление и редактирование записи, используя одну и ту же форму для обеих операций? -Во многих случаях формы для добавления и редактирования записи одинаковы, различаясь только меткой на кнопке. Мы покажем примеры простых презентеров, где мы используем форму сначала для добавления записи, затем для её`редактирования, и, наконец, объединяем эти два решения. +Во многих случаях формы для добавления и редактирования записей идентичны, отличаясь, возможно, только надписью на кнопке. Мы покажем примеры простых презентеров, где форма используется сначала для добавления записи, затем для редактирования, и, наконец, объединим оба решения. -Добавление записи .[#toc-adding-a-record] ------------------------------------------ +Добавление записи +----------------- -Пример презентера, используемого для добавления записи. Мы оставим работу с базой данных классу `Facade`, код которого не имеет отношения к данному примеру. +Пример презентера, служащего для добавления записи. Саму работу с базой данных оставим классу `Facade`, код которого для примера не важен. ```php @@ -35,7 +35,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter public function recordFormSucceeded(Form $form, array $data): void { - $this->facade->add($data); // добавляем запись в базу данных + $this->facade->add($data); // добавление записи в базу данных $this->flashMessage('Успешно добавлено'); $this->redirect('...'); } @@ -48,10 +48,10 @@ class RecordPresenter extends Nette\Application\UI\Presenter ``` -Редактирование записи .[#toc-editing-a-record] ----------------------------------------------- +Редактирование записи +--------------------- -Теперь давайте посмотрим, как будет выглядеть презентер, используемый для редактирования записей: +Теперь покажем, как выглядел бы презентер, служащий для редактирования записи: ```php @@ -70,8 +70,8 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $record = $this->facade->get($id); if ( - !$record // проверяем существование записи - || !$this->facade->isEditAllowed(/*...*/) // проверяем права доступа + !$record // проверка существования записи + || !$this->facade->isEditAllowed(/*...*/) // проверка прав доступа ) { $this->error(); // ошибка 404 } @@ -81,7 +81,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter protected function createComponentRecordForm(): Form { - // проверяем, что выбрано действие 'edit' + // проверяем, что действие - 'edit' if ($this->getAction() !== 'edit') { $this->error(); } @@ -90,23 +90,23 @@ class RecordPresenter extends Nette\Application\UI\Presenter // ... добавляем поля формы ... - $form->setDefaults($this->record); // устанавливаем значения по умолчанию + $form->setDefaults($this->record); // установка значений по умолчанию $form->onSuccess[] = [$this, 'recordFormSucceeded']; return $form; } public function recordFormSucceeded(Form $form, array $data): void { - $this->facade->update($this->record->id, $data); // обновляем запись + $this->facade->update($this->record->id, $data); // обновление записи $this->flashMessage('Успешно обновлено'); $this->redirect('...'); } } ``` -В методе *action*, который вызывается в самом начале жизненного цикла [презентера|application:presenters#Life-Cycle-of-Presenter], мы проверяем существование записи и разрешение пользователя на её редактирование. +В методе *action*, который запускается сразу в начале [жизненного цикла презентера |application:presenters#Жизненный цикл презентера], мы проверяем существование записи и права пользователя на ее редактирование. -Мы храним запись в свойстве `$record`, чтобы она была доступна в методе `createComponentRecordForm()` для установки значений по умолчанию и `recordFormSucceeded()` для идентификатора. Альтернативным решением может быть установка значений по умолчанию непосредственно в `actionEdit()` и значения ID, который является частью URL и извлекается с помощью `getParameter('id')`: +Запись сохраняем в свойстве `$record`, чтобы она была доступна в методе `createComponentRecordForm()` для установки значений по умолчанию и в `recordFormSucceeded()` для получения ID. Альтернативным решением было бы установить значения по умолчанию прямо в `actionEdit()` и получить значение ID, которое является частью URL, с помощью `getParameter('id')`: ```php @@ -114,12 +114,12 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $record = $this->facade->get($id); if ( - // проверяем существование и права доступа + // проверка существования и прав доступа ) { $this->error(); } - // устанавливаем значения по умолчанию для полей формы + // установка значений по умолчанию для формы $this->getComponent('recordForm') ->setDefaults($record); } @@ -133,13 +133,13 @@ class RecordPresenter extends Nette\Application\UI\Presenter } ``` -Однако, и это должно быть **самым важным выводом из всего кода**, нам нужно убедиться, что действие действительно `edit`, когда мы создаем форму. Потому что иначе валидация в методе `actionEdit()` вообще не произойдет! +Однако, и это должно быть **самым важным выводом из всего кода**, при создании формы мы должны убедиться, что действие действительно `edit`. Иначе проверка в методе `actionEdit()` вообще не выполнится! -Одна и та же форма для добавления и редактирования .[#toc-same-form-for-adding-and-editing] -------------------------------------------------------------------------------------------- +Одна форма для добавления и редактирования +------------------------------------------ -А сейчас мы объединим оба презентера в один. Либо мы можем отличить, какое действие задействовано в методе `createComponentRecordForm()` и настроить форму соответствующим образом, либо мы можем оставить это непосредственно action-методам и избавиться от условия: +А теперь объединим оба презентера в один. Мы могли бы в методе `createComponentRecordForm()` различать, какое действие выполняется, и в соответствии с этим конфигурировать форму, или же мы можем оставить это непосредственно action-методам и избавиться от условия: ```php @@ -160,20 +160,20 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $record = $this->facade->get($id); if ( - !$record // проверяем существование записи - || !$this->facade->isEditAllowed(/*...*/) // проверяем права доступа + !$record // проверка существования записи + || !$this->facade->isEditAllowed(/*...*/) // проверка прав доступа ) { $this->error(); // ошибка 404 } $form = $this->getComponent('recordForm'); - $form->setDefaults($record); // устанавливаем значения по умолчанию + $form->setDefaults($record); // установка значений по умолчанию $form->onSuccess[] = [$this, 'editingFormSucceeded']; } protected function createComponentRecordForm(): Form { - // проверяем, что текущее действие — 'add' или 'edit' + // проверяем, что действие - 'add' или 'edit' if (!in_array($this->getAction(), ['add', 'edit'])) { $this->error(); } @@ -187,7 +187,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter public function addingFormSucceeded(Form $form, array $data): void { - $this->facade->add($data); // добавляем запись в базу данных + $this->facade->add($data); // добавление записи в базу данных $this->flashMessage('Успешно добавлено'); $this->redirect('...'); } @@ -195,7 +195,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter public function editingFormSucceeded(Form $form, array $data): void { $id = (int) $this->getParameter('id'); - $this->facade->update($id, $data); // обновляем запись + $this->facade->update($id, $data); // обновление записи $this->flashMessage('Успешно обновлено'); $this->redirect('...'); } @@ -203,4 +203,3 @@ class RecordPresenter extends Nette\Application\UI\Presenter ``` {{priority: -1}} -{{sitename: Лучшие практики}} diff --git a/best-practices/ru/dynamic-snippets.texy b/best-practices/ru/dynamic-snippets.texy index 744b905458..30a58602e3 100644 --- a/best-practices/ru/dynamic-snippets.texy +++ b/best-practices/ru/dynamic-snippets.texy @@ -1,7 +1,7 @@ Динамические сниппеты ********************* -Довольно часто при разработке приложений возникает необходимость выполнения операций AJAX, например, в отдельных строках таблицы или элементах списка. В качестве примера, мы можем выбрать список статей, позволяя вошедшему в систему пользователю выбрать «нравится/не нравится» для каждой из них. Код презентера и соответствующего шаблона без AJAX будет выглядеть примерно так (я перечисляю наиболее важные фрагменты, код предполагает наличие сервиса для разметки рейтингов и получения коллекции статей — конкретная реализация не важна для целей данного руководства): +Довольно часто при разработке приложений возникает необходимость выполнять AJAX-операции, например, над отдельными строками таблицы или элементами списка. В качестве примера можно выбрать вывод статей, при этом для каждой из них мы позволим авторизованному пользователю выбрать оценку "нравится/не нравится". Код презентера и соответствующего шаблона без AJAX будет выглядеть примерно так (привожу наиболее важные фрагменты, код предполагает существование сервиса для отметки оценок и получения коллекции статей - конкретная реализация для целей этого руководства не важна): ```php public function handleLike(int $articleId): void @@ -17,7 +17,7 @@ public function handleUnlike(int $articleId): void } ``` -Template: +Шаблон: ```latte <article n:foreach="$articles as $article"> @@ -26,24 +26,24 @@ Template: {if !$article->liked} <a n:href="like! $article->id" class=ajax>Мне нравится</a> {else} - <a n:href="unlike! $article->id" class=ajax>Мне не нравится</a> + <a n:href="unlike! $article->id" class=ajax>Мне больше не нравится</a> {/if} </article> ``` -Аяксизация .[#toc-ajaxization] -============================== +Аяксизация +========== -Теперь давайте привнесем AJAX в это простое приложение. Изменение рейтинга статьи не настолько важно, чтобы требовать HTTP-запрос с перенаправлением, поэтому в идеале это должно быть сделано с помощью AJAX в фоновом режиме. Мы будем использовать [скрипт обработчика из дополнений |https://componette.org/vojtech-dobes/nette.ajax.js/] с обычным соглашением, что AJAX ссылки имеют CSS класс `ajax`. +Теперь давайте оснастим это простое приложение AJAX. Изменение оценки статьи не настолько важно, чтобы требовалось перенаправление, поэтому в идеале оно должно происходить с помощью AJAX в фоновом режиме. Мы будем использовать [обслуживающий скрипт из дополнений |application:ajax#Naja] с обычной конвенцией, что AJAX-ссылки имеют CSS-класс `ajax`. -Однако как это сделать конкретно? Nette предлагает 2 способа: способ динамических фрагментов и способ компонентов. У обоих есть свои плюсы и минусы, поэтому мы покажем их по очереди. +Но как это сделать конкретно? Nette предлагает 2 пути: путь так называемых динамических сниппетов и путь компонентов. Оба имеют свои плюсы и минусы, поэтому мы рассмотрим их по очереди. -Путь динамических сниппетов .[#toc-the-dynamic-snippets-way] -============================================================ +Путь динамических сниппетов +=========================== -В терминологии Latte динамический сниппет — это особый случай использования тега `{snippet}`, когда в имени сниппета используется переменная. Такой сниппет не может быть найден просто в любом месте шаблона — он должен быть обернут статическим сниппетом, т. е. обычный, или внутри `{snippetArea}`. Мы можем изменить наш шаблон следующим образом. +Динамический сниппет в терминологии Latte означает специфический случай использования тега `{snippet}`, когда в названии сниппета используется переменная. Такой сниппет не может находиться в шаблоне где угодно - он должен быть обернут статическим сниппетом, то есть обычным, или находиться внутри `{snippetArea}`. Наш шаблон можно было бы изменить следующим образом. ```latte @@ -55,16 +55,16 @@ Template: {if !$article->liked} <a n:href="like! $article->id" class=ajax>Мне нравится</a> {else} - <a n:href="unlike! $article->id" class=ajax>Мне не нравится</a> + <a n:href="unlike! $article->id" class=ajax>Мне больше не нравится</a> {/if} {/snippet} </article> {/snippet} ``` -Каждая статья теперь определяет один сниппет, который имеет ID статьи в заголовке. Все эти фрагменты затем объединяются в один фрагмент под названием `articlesContainer`. Если мы опустим этот фрагмент обертки, Latte предупредит нас об исключении. +Каждая статья теперь определяет один сниппет, который содержит ID статьи в своем названии. Все эти сниппеты затем обернуты одним сниппетом с названием `articlesContainer`. Если бы мы пропустили этот обертывающий сниппет, Latte предупредил бы нас исключением. -Всё, что осталось сделать, это добавить перерисовку в презентер — просто перерисовать статическую обертку. +Остается добавить в презентер перерисовку - достаточно перерисовать статическую обертку. ```php public function handleLike(int $articleId): void @@ -72,18 +72,18 @@ public function handleLike(int $articleId): void $this->ratingService->saveLike($articleId, $this->user->id); if ($this->isAjax()) { $this->redrawControl('articlesContainer'); - // $this->redrawControl('article-' . $articleId); -- нет необходимости + // $this->redrawControl('article-' . $articleId); -- не требуется } else { $this->redirect('this'); } } ``` -Измените родственный метод `handleUnlike()` таким же образом, и AJAX будет работать! +Аналогично изменим и сестринский метод `handleUnlike()`, и AJAX заработает! -Однако у этого решения есть и обратная сторона. Если мы подробнее рассмотрим, как работает AJAX-запрос, то обнаружим, что хотя приложение выглядит эффективным внешне (оно возвращает только один сниппет для данной статьи), на самом деле оно отображает все сниппеты на сервере. Он поместил нужный фрагмент в нашу полезную нагрузку, а остальные отбросил (таким образом, совершенно без необходимости, он также извлек их из базы данных). +Однако у этого решения есть один недостаток. Если бы мы подробнее изучили, как происходит AJAX-запрос, мы бы обнаружили, что хотя внешне приложение выглядит экономно (возвращает только один сниппет для данной статьи), на самом деле на сервере оно отрисовало все сниппеты. Нужный сниппет оно поместило в payload, а остальные отбросило (совершенно зря, таким образом, также получив их из базы данных). -Чтобы оптимизировать этот процесс, нам понадобится действие, при котором мы передаем коллекцию `$articles` шаблону (скажем, в методе `renderDefault()`). Мы воспользуемся тем, что обработка сигнала происходит до методов `render<Something>`: +Чтобы оптимизировать этот процесс, нам придется вмешаться там, где мы передаем коллекцию `$articles` в шаблон (скажем, в методе `renderDefault()`). Мы воспользуемся тем фактом, что обработка сигналов происходит перед методами `render<Something>`: ```php public function handleLike(int $articleId): void @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connection->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,18 +101,18 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connection->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` -Теперь, когда сигнал обрабатывается, вместо коллекции со всеми статьями в шаблон передается только массив с одной статьей — той, которую мы хотим отобразить и отправить в полезной нагрузке браузеру. Таким образом, `{foreach}` будет выполнен только один раз, и никаких дополнительных сниппетов не будет выведено. +Теперь при обработке сигнала в шаблон вместо коллекции со всеми статьями передается массив с одной единственной статьей - той, которую мы хотим отрисовать и отправить в payload в браузер. `{foreach}` таким образом выполнится только один раз, и никакие лишние сниппеты не будут отрисованы. -Компонентный способ .[#toc-component-way] -========================================= +Путь компонентов +================ -Совершенно другое решение использует другой подход, чтобы избежать динамических сниппетов. Хитрость заключается в том, чтобы перенести всю логику в отдельный компонент — отныне у нас не презентер, который будет заботиться о вводе рейтинга, а специальный `LikeControl`. Класс будет выглядеть следующим образом (кроме того, он также будет содержать `render`, `handleUnlike` и т. д. методы): +Совершенно другой способ решения избегает динамических сниппетов. Трюк заключается в переносе всей логики в отдельный компонент - теперь за ввод оценок будет отвечать не презентер, а выделенный `LikeControl`. Класс будет выглядеть следующим образом (кроме того, он будет содержать методы `render`, `handleUnlike` и т.д.): ```php class LikeControl extends Nette\Application\UI\Control @@ -141,24 +141,24 @@ class LikeControl extends Nette\Application\UI\Control {if !$article->liked} <a n:href="like!" class=ajax>Мне нравится</a> {else} - <a n:href="unlike!" class=ajax>Мне не нравится</a> + <a n:href="unlike!" class=ajax>Мне больше не нравится</a> {/if} {/snippet} ``` -Конечно, мы изменим шаблон представления, и нам придется добавить фабрику к презентеру. Поскольку мы будем создавать компонент столько раз, сколько статей мы получим из базы данных, мы будем использовать класс [application:Multiplier] для этого: +Конечно, нам придется изменить шаблон представления и добавить в презентер фабрику. Поскольку мы создадим компонент столько раз, сколько статей получим из базы данных, мы используем для его "размножения" класс [Multiplier |application:Multiplier]. ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); } ``` -Вид шаблона сокращен до необходимого минимума (и полностью свободен от сниппетов!): +Шаблон представления сократится до необходимого минимума (и будет полностью лишен сниппетов!): ```latte <article n:foreach="$articles as $article"> @@ -168,7 +168,6 @@ protected function createComponentLikeControl() </article> ``` -Мы почти закончили: приложение теперь будет работать в AJAX. Здесь также необходимо оптимизировать приложение, так как из-за использования базы данных Nette, обработка сигнала будет излишне загружать все статьи из базы данных вместо одной. Однако преимущество в том, что рендеринга не будет, потому что на самом деле рендерится только наш компонент. +Почти готово: приложение теперь будет работать с AJAX. Здесь нас также ждет оптимизация приложения, потому что из-за использования Nette Database при обработке сигнала из базы данных излишне загружаются все статьи вместо одной. Преимуществом, однако, является то, что их отрисовка не происходит, потому что рендерится действительно только наш компонент. {{priority: -1}} -{{sitename: Лучшие практики}} diff --git a/best-practices/ru/editors-and-tools.texy b/best-practices/ru/editors-and-tools.texy index fcb05471e7..7508f40017 100644 --- a/best-practices/ru/editors-and-tools.texy +++ b/best-practices/ru/editors-and-tools.texy @@ -2,39 +2,39 @@ *********************** .[perex] -Вы можете быть искусным программистом, но только с хорошими инструментами вы станете мастером. В этой главе вы найдете советы о важных инструментах, редакторах и плагинах. +Вы можете быть опытным программистом, но только с хорошими инструментами вы станете мастером. В этой главе вы найдете советы по важным инструментам, редакторам и плагинам. -Редактор IDE .[#toc-ide-editor] -=============================== +IDE редактор +============ -Мы настоятельно рекомендуем использовать для разработки полнофункциональную IDE, такую как PhpStorm, NetBeans, VS Code, а не просто текстовый редактор с поддержкой PHP. Разница действительно принципиальна. Нет причин довольствоваться классическим редактором с подсветкой синтаксиса, потому что он не дотягивает до возможностей IDE с точным предложением кода, возможностью рефакторинга кода и т. д. Некоторые IDE являются платными, другие — бесплатными. +Мы настоятельно рекомендуем использовать для разработки полноценную IDE, такую как PhpStorm, NetBeans, VS Code, а не просто текстовый редактор с поддержкой PHP. Разница действительно существенная. Нет причин довольствоваться простым редактором, который хоть и умеет подсвечивать синтаксис, но не достигает возможностей передовой IDE, которая точно подсказывает, отслеживает ошибки, умеет рефакторить код и многое другое. Некоторые IDE платные, другие даже бесплатные. **NetBeans IDE** имеет встроенную поддержку Nette, Latte и NEON. -**PhpStorm**: установите эти плагины в `Settings > Plugins > Marketplace`: +**PhpStorm**: установите эти плагины в `Settings > Plugins > Marketplace` - Nette framework helpers - Latte - NEON support - Nette Tester -**VS Code**: найдите плагин "Nette Latte + Neon" в маркетплейсе. +**VS Code**: найдите в marketplace плагин "Nette Latte + Neon". -Также соедините Tracy с редактором. Когда отображается страница ошибки, вы можете кликнуть по именам файлов, и они откроются в редакторе с курсором в соответствующей строке. Узнайте [как настроить систему |tracy:open-files-in-ide]. +Также свяжите Tracy с редактором. При отображении страницы ошибки можно будет кликнуть на имена файлов, и они откроются в редакторе с курсором на соответствующей строке. Прочтите, [как настроить систему|tracy:open-files-in-ide]. PHPStan ======= -PHPStan — это инструмент, который обнаруживает логические ошибки в вашем коде до того, как вы его запустите. +PHPStan — это инструмент, который обнаруживает логические ошибки в коде до его запуска. -Установите его через Composer: +Установим его с помощью Composer: ```shell composer require --dev phpstan/phpstan-nette ``` -Создайте в проекте конфигурационный файл `phpstan.neon`: +Создадим в проекте конфигурационный файл `phpstan.neon`: ```neon includes: @@ -47,40 +47,38 @@ parameters: level: 5 ``` -А затем позвольте ему проанализировать классы в папке `app/`: +А затем позволим ему проанализировать классы в папке `app/`: ```shell vendor/bin/phpstan analyse app ``` -Вы можете найти полную документацию непосредственно на сайте [PHPStan |https://phpstan.org]. +Исчерпывающую документацию вы найдете прямо на [сайте PHPStan |https://phpstan.org]. Code Checker ============ -[Code Checker|code-checker:] проверяет и по возможности исправляет некоторые формальные ошибки в вашем исходном коде. +[Code Checker|code-checker:] проверяет и, при необходимости, исправляет некоторые формальные ошибки в ваших исходных кодах: -- удаляет [BOM |nette:glossary#bom]. -- проверяет валидность шаблонов [Latte |latte:]. -- проверяет валидность файлов `.neon`, `.php` и `.json`. -- проверяет наличие [управляющих символов |nette:glossary#Control-Characters]. +- удаляет [BOM |nette:glossary#BOM] +- проверяет валидность шаблонов [Latte |latte:] +- проверяет валидность файлов `.neon`, `.php` и `.json` +- проверяет наличие [контрольных символов |nette:glossary#Управляющие символы] - проверяет, закодирован ли файл в UTF-8 -- контролирует правильность написания `/* @annotations */` (пропущена вторая звездочка) -- удаляет завершающие теги PHP `?>` в файлах PHP -- удаляет из конца файла пробельные символы и ненужные пустые строки -- нормализует окончания строк к системному значению по умолчанию (с параметром `-l`) +- проверяет неправильно записанные `/* @anotace */` (отсутствует звездочка) +- удаляет завершающие `?>` в PHP файлах +- удаляет пробелы в конце строк и лишние строки в конце файла +- нормализует разделители строк к системным (если указана опция `-l`) Composer ======== -[Composer] — это инструмент для управления зависимостями в PHP. Он позволяет нам объявить зависимости библиотек, и он установит их за нас в наш проект. - +[Composer] — это инструмент для управления зависимостями в PHP. Он позволяет нам объявлять произвольно сложные зависимости отдельных библиотек и затем устанавливает их для нас в наш проект. -Скрипт проверки требований .[#toc-requirements-checker] -======================================================= -Это был инструмент, который тестировал рабочую среду сервера и сообщал, можно ли (и в какой степени) использовать фреймворк. В настоящее время Nette можно использовать на любом сервере, на котором установлена минимально необходимая версия PHP. +Requirements Checker +==================== -{{sitename: Лучшие практики}} +Это был инструмент, который тестировал среду выполнения сервера и сообщал, можно ли (и в какой степени) использовать фреймворк. В настоящее время Nette можно использовать на любом сервере, имеющем минимально требуемую версию PHP. diff --git a/best-practices/ru/form-reuse.texy b/best-practices/ru/form-reuse.texy index 534ce944d8..2c8dd3114c 100644 --- a/best-practices/ru/form-reuse.texy +++ b/best-practices/ru/form-reuse.texy @@ -2,15 +2,15 @@ ************************************************ .[perex] -В Nette у вас есть несколько вариантов повторного использования одной и той же формы в нескольких местах без дублирования кода. В этой статье мы рассмотрим различные решения, включая те, которых следует избегать. +В Nette у вас есть несколько вариантов использования одной и той же формы в нескольких местах без дублирования кода. В этой статье мы рассмотрим различные решения, включая те, которых следует избегать. -Фабрика форм .[#toc-form-factory] -================================= +Фабрика форм +============ -Один из основных подходов к использованию одного и того же компонента в нескольких местах заключается в создании метода или класса, который генерирует компонент, а затем вызывает этот метод в разных местах приложения. Такой метод или класс называется *фабрикой*. Пожалуйста, не путайте с шаблоном проектирования *фабричный метод*, который описывает особый способ использования фабрик и не относится к данной теме. +Одним из основных подходов к использованию одного и того же компонента в нескольких местах является создание метода или класса, который генерирует этот компонент, и последующий вызов этого метода в разных частях приложения. Такой метод или класс называется *фабрикой*. Пожалуйста, не путайте с паттерном проектирования *factory method*, который описывает специфический способ использования фабрик и не связан с этой темой. -В качестве примера давайте создадим фабрику, которая будет создавать форму редактирования: +В качестве примера создадим фабрику, которая будет собирать форму редактирования: ```php use Nette\Application\UI\Form; @@ -20,22 +20,22 @@ class FormFactory public function createEditForm(): Form { $form = new Form; - $form->addText('title', 'Title:'); - // здесь добавляются дополнительные поля формы - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Заголовок:'); + // здесь добавляются другие поля формы + $form->addSubmit('send', 'Отправить'); return $form; } } ``` -Теперь вы можете использовать эту фабрику в различных местах вашего приложения, например, в презентаторах или компонентах. И мы сделаем это, [запросив ее как зависимость |dependency-injection:passing-dependencies]. Итак, сначала мы запишем класс в конфигурационный файл: +Теперь вы можете использовать эту фабрику в разных местах вашего приложения, например, в презентерах или компонентах. Для этого [запросим ее как зависимость|dependency-injection:passing-dependencies]. Сначала запишем класс в конфигурационный файл: ```neon services: - FormFactory ``` -А затем используем его в презентаторе: +А затем используем ее в презентере: ```php @@ -57,7 +57,7 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Вы можете расширить фабрику форм дополнительными методами для создания других типов форм в соответствии с вашим приложением. И, конечно, вы можете добавить метод, создающий базовую форму без элементов, которую будут использовать другие методы: +Фабрику форм можно расширить дополнительными методами для создания других видов форм в соответствии с потребностями вашего приложения. И, конечно, мы можем добавить метод, который создаст базовую форму без элементов, и этот метод будут использовать другие методы: ```php class FormFactory @@ -71,9 +71,9 @@ class FormFactory public function createEditForm(): Form { $form = $this->createForm(); - $form->addText('title', 'Title:'); - // здесь добавляются дополнительные поля формы - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Заголовок:'); + // здесь добавляются другие поля формы + $form->addSubmit('send', 'Отправить'); return $form; } } @@ -82,10 +82,10 @@ class FormFactory Метод `createForm()` пока не делает ничего полезного, но это быстро изменится. -Зависимости фабрики .[#toc-factory-dependencies] -================================================ +Зависимости фабрики +=================== -Со временем станет очевидно, что формы должны быть многоязычными. Это означает, что нам необходимо установить [переводчик |forms:rendering#Translating] для всех форм. Для этого мы модифицируем класс `FormFactory`, чтобы он принимал объект `Translator` в качестве зависимости в конструкторе и передавал его форме: +Со временем выяснится, что нам нужно, чтобы формы были многоязычными. Это означает, что всем формам нужно установить так называемый [translator |forms:rendering#Перевод]. Для этого изменим класс `FormFactory` так, чтобы он принимал объект `Translator` как зависимость в конструкторе, и передадим его форме: ```php use Nette\Localization\Translator; @@ -104,18 +104,17 @@ class FormFactory return $form; } - //... + // ... } ``` -Поскольку метод `createForm()` вызывается и другими методами, создающими конкретные формы, нам нужно установить транслятор только в этом методе. И все готово. Нет необходимости изменять код презентатора или компонента, что очень хорошо. +Поскольку метод `createForm()` вызывают и другие методы, создающие специфические формы, достаточно установить translator только в нем. И готово. Нет необходимости менять код какого-либо презентера или компонента, что замечательно. -Другие фабричные классы .[#toc-more-factory-classes] -==================================================== +Несколько фабричных классов +=========================== -В качестве альтернативы вы можете создать несколько классов для каждой формы, которую вы хотите использовать в своем приложении. -Такой подход может повысить читаемость кода и облегчить управление формами. Оставьте исходный `FormFactory` для создания только чистой формы с базовой конфигурацией (например, с поддержкой перевода) и создайте новую фабрику `EditFormFactory` для формы редактирования. +Альтернативно, вы можете создать несколько классов для каждой формы, которую хотите использовать в вашем приложении. Этот подход может повысить читаемость кода и упростить управление формами. Исходную `FormFactory` оставим создавать только чистую форму с базовой конфигурацией (например, с поддержкой переводов), а для формы редактирования создадим новую фабрику `EditFormFactory`. ```php class FormFactory @@ -145,40 +144,39 @@ class EditFormFactory public function create(): Form { $form = $this->formFactory->create(); - // здесь добавляются дополнительные поля формы - $form->addSubmit('send', 'Save'); + // здесь добавляются другие поля формы + $form->addSubmit('send', 'Отправить'); return $form; } } ``` -Очень важно, чтобы связь между классами `FormFactory` и `EditFormFactory` была реализована композицией, а не наследованием объектов: +Очень важно, чтобы связь между классами `FormFactory` и `EditFormFactory` была реализована [композицией |nette:introduction-to-object-oriented-programming#Композиция], а не [объектным наследованием |nette:introduction-to-object-oriented-programming#Наследование]: ```php -// ⛔ НЕТ! НАСЛЕДСТВУ ЗДЕСЬ НЕ МЕСТО +// ⛔ ТАК НЕ НАДО! НАСЛЕДОВАНИЕ ЗДЕСЬ НЕУМЕСТНО class EditFormFactory extends FormFactory { public function create(): Form { $form = parent::create(); - $form->addText('title', 'Title:'); - // дополнительные поля формы добавляются здесь - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Заголовок:'); + // здесь добавляются другие поля формы + $form->addSubmit('send', 'Отправить'); return $form; } } ``` -Использование наследования в этом случае было бы совершенно непродуктивным. Вы бы очень быстро столкнулись с проблемами. Например, если бы вы захотели добавить параметры в метод `create()`, PHP выдал бы ошибку, что его сигнатура отличается от родительской. -Или при передаче зависимости классу `EditFormFactory` через конструктор. Это привело бы к тому, что мы называем " [ад конструктора |dependency-injection:passing-dependencies#Constructor hell]". +Использование наследования в этом случае было бы совершенно контрпродуктивным. Вы очень быстро столкнулись бы с проблемами. Например, в тот момент, когда вы захотели бы добавить параметры к методу `create()`; PHP выдал бы ошибку, что его сигнатура отличается от родительской. Или при передаче зависимости в класс `EditFormFactory` через конструктор. Возникла бы ситуация, которую мы называем [constructor hell |dependency-injection:passing-dependencies#Ад конструкторов]. -В целом, лучше предпочесть композицию наследованию. +В целом, лучше отдавать предпочтение [композиции перед наследованием |dependency-injection:faq#Почему композиция предпочтительнее наследования]. -Работа с формами .[#toc-form-handling] -====================================== +Обработка формы +=============== -Обработчик формы, вызываемый после успешной отправки, также может быть частью класса-фабрики. Он будет работать, передавая отправленные данные в модель для обработки. Любые ошибки он будет передавать [обратно |forms:validation#Processing Errors] в форму. Модель в следующем примере представлена классом `Facade`: +Обработка формы, которая вызывается после успешной отправки, также может быть частью фабричного класса. Она будет работать так, что передаст отправленные данные модели для обработки. Возможные ошибки [передаст обратно |forms:validation#Ошибки при обработке] в форму. Модель в следующем примере представляет класс `Facade`: ```php class EditFormFactory @@ -192,9 +190,9 @@ class EditFormFactory public function create(): Form { $form = $this->formFactory->create(); - $form->addText('title', 'Title:'); - // здесь добавляются дополнительные поля формы - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Заголовок:'); + // здесь добавляются другие поля формы + $form->addSubmit('send', 'Отправить'); $form->onSuccess[] = [$this, 'processForm']; return $form; } @@ -202,7 +200,7 @@ class EditFormFactory public function processForm(Form $form, array $data): void { try { - // обработка предоставленных данных + // обработка отправленных данных $this->facade->process($data); } catch (AnyModelException $e) { @@ -212,7 +210,7 @@ class EditFormFactory } ``` -Пусть ведущий сам выполняет перенаправление. Он добавит еще один обработчик к событию `onSuccess`, который будет выполнять перенаправление. Это позволит использовать форму в разных презентаторах, и каждый из них может перенаправлять на разные места. +Само перенаправление оставим на презентере. Он добавит к событию `onSuccess` еще один обработчик, который выполнит перенаправление. Благодаря этому форму можно будет использовать в разных презентерах и в каждом перенаправлять в другое место. ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -226,7 +224,7 @@ class MyPresenter extends Nette\Application\UI\Presenter { $form = $this->formFactory->create(); $form->onSuccess[] = function () { - $this->flashMessage('Záznam byl uložen'); + $this->flashMessage('Запись была сохранена'); $this->redirect('Homepage:'); }; return $form; @@ -234,39 +232,38 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Это решение использует свойство форм: когда `addError()` вызывается на форме или ее элементе, следующий обработчик `onSuccess` не вызывается. +Это решение использует свойство форм, что если над формой или ее элементом вызывается `addError()`, то следующий обработчик `onSuccess` уже не вызывается. -Наследование от класса формы .[#toc-inheriting-from-the-form-class] -=================================================================== +Наследование от класса Form +=========================== -Построенная форма не должна быть дочерней по отношению к форме. Другими словами, не используйте это решение: +Собранная форма не должна быть потомком формы. Другими словами, не используйте это решение: ```php -// ⛔ НЕТ! НАСЛЕДСТВУ ЗДЕСЬ НЕ МЕСТО +// ⛔ ТАК НЕ НАДО! НАСЛЕДОВАНИЕ ЗДЕСЬ НЕУМЕСТНО class EditForm extends Form { public function __construct(Translator $translator) { parent::__construct(); - $form->addText('title', 'Title:'); - // дополнительные поля формы добавляются здесь - $form->addSubmit('send', 'Save'); - $form->setTranslator($translator); + $this->addText('title', 'Заголовок:'); + // здесь добавляются другие поля формы + $this->addSubmit('send', 'Отправить'); + $this->setTranslator($translator); } } ``` -Вместо того чтобы создавать форму в конструкторе, используйте фабрику. +Вместо сборки формы в конструкторе используйте фабрику. -Важно понимать, что класс `Form` - это прежде всего инструмент для сборки формы, то есть конструктор форм. А собранную форму можно считать его продуктом. Однако продукт не является частным случаем конструктора; между ними нет отношения *is a*, которое лежит в основе наследования. +Нужно понимать, что класс `Form` — это в первую очередь инструмент для сборки формы, то есть *form builder*. А собранную форму можно рассматривать как ее продукт. Но продукт не является специфическим случаем билдера, между ними нет связи *is a*, составляющей основу наследования. -Компонент формы .[#toc-form-component] -====================================== +Компонент с формой +================== -Совершенно другой подход - создать [компонент |application:components], включающий форму. Это дает новые возможности, например, отображение формы определенным образом, поскольку компонент включает в себя шаблон. -Или сигналы могут быть использованы для AJAX-коммуникации и загрузки информации в форму, например, для подсказок и т.д. +Совершенно другой подход представляет собой создание [компонента|application:components], частью которого является форма. Это дает новые возможности, например, рендерить форму специфическим образом, поскольку частью компонента является и шаблон. Или можно использовать сигналы для AJAX-коммуникации и дозагрузки информации в форму, например, для подсказок и т.д. ```php @@ -284,9 +281,9 @@ class EditControl extends Nette\Application\UI\Control protected function createComponentForm(): Form { $form = new Form; - $form->addText('title', 'Title:'); - // здесь добавляются дополнительные поля формы - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Заголовок:'); + // здесь добавляются другие поля формы + $form->addSubmit('send', 'Отправить'); $form->onSuccess[] = [$this, 'processForm']; return $form; @@ -309,7 +306,7 @@ class EditControl extends Nette\Application\UI\Control } ``` -Давайте создадим фабрику, которая будет производить этот компонент. Достаточно [написать ее интерфейс |application:components#Components with Dependencies]: +Еще создадим фабрику, которая будет производить этот компонент. Достаточно [записать ее интерфейс |application:components#Компоненты с зависимостями]: ```php interface EditControlFactory @@ -318,14 +315,14 @@ interface EditControlFactory } ``` -И добавить его в конфигурационный файл: +И добавить в конфигурационный файл: ```neon services: - EditControlFactory ``` -И теперь мы можем запросить фабрику и использовать ее в презентере: +А теперь уже можем запросить фабрику и использовать ее в презентере: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -335,13 +332,13 @@ class MyPresenter extends Nette\Application\UI\Presenter ) { } - protected function createComponentEditForm(): Form + protected function createComponentEditForm(): EditControl { $control = $this->controlFactory->create(); $control->onSave[] = function (EditControl $control, $data) { $this->redirect('this'); - // или перенаправить на результат редактирования, например: + // или перенаправим на результат редактирования, напр.: // $this->redirect('detail', ['id' => $data->id]); }; @@ -349,5 +346,3 @@ class MyPresenter extends Nette\Application\UI\Presenter } } ``` - -{{sitename: Лучшие практики}} diff --git a/best-practices/ru/inject-method-attribute.texy b/best-practices/ru/inject-method-attribute.texy index cc51a356bb..689ddbfa3c 100644 --- a/best-practices/ru/inject-method-attribute.texy +++ b/best-practices/ru/inject-method-attribute.texy @@ -1,21 +1,18 @@ -Методы и атрибуты инъекции -************************** +Методы и атрибуты inject +************************ .[perex] -В этой статье мы рассмотрим различные способы передачи зависимостей презентаторам во фреймворке Nette. Мы сравним предпочтительный метод, которым является конструктор, с другими вариантами, такими как методы `inject` и атрибуты. +В этой статье мы рассмотрим различные способы передачи зависимостей в презентеры фреймворка Nette. Сравним предпочтительный способ, которым является конструктор, с другими возможностями, такими как методы и атрибуты `inject`. -Для ведущих также передача зависимостей с помощью [конструктора |dependency-injection:passing-dependencies#Constructor Injection] является предпочтительным способом. -Однако если вы создаете общего предка, от которого наследуют другие презентаторы (например, BasePresenter), и этот предок также имеет зависимости, возникает проблема, которую мы называем [адом конструктора |dependency-injection:passing-dependencies#Constructor hell]. -Ее можно обойти с помощью альтернативных методов, которые включают в себя методы инъекции и атрибуты (аннотации). +И для презентеров действует правило, что передача зависимостей с помощью [конструктора |dependency-injection:passing-dependencies#Передача через конструктор] является предпочтительным путем. Однако, если вы создаете общего предка, от которого наследуются другие презентеры (например, `BasePresenter`), и этот предок также имеет зависимости, возникает проблема, которую мы называем [constructor hell |dependency-injection:passing-dependencies#Ад конструкторов]. Ее можно обойти с помощью альтернативных путей, которые представляют собой методы и атрибуты (ранее аннотации) `inject`. -`inject*()` Методы .[#toc-inject-methods] -========================================= +Методы `inject*()` +================== -Это форма передачи зависимостей с помощью [сеттеров |dependency-injection:passing-dependencies#Setter Injection]. Имена этих сеттеров начинаются с префикса inject. -Nette DI автоматически вызывает такие именованные методы сразу после создания экземпляра ведущего и передает им все необходимые зависимости. Поэтому они должны быть объявлены как public. +Это форма передачи зависимости [сеттером |dependency-injection:passing-dependencies#Передача сеттером]. Название этих сеттеров начинается с префикса `inject`. Nette DI автоматически вызывает методы с таким названием сразу после создания экземпляра презентера и передает им все необходимые зависимости. Поэтому они должны быть объявлены как public. -`inject*()` Методы можно рассматривать как своего рода расширение конструктора на несколько методов. Благодаря этому `BasePresenter` может принимать зависимости через другой метод и оставлять конструктор свободным для его потомков: +Методы `inject*()` можно рассматривать как своего рода расширение конструктора на несколько методов. Благодаря этому `BasePresenter` может принимать зависимости через другой метод и оставлять конструктор свободным для своих потомков: ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -39,18 +36,18 @@ class MyPresenter extends BasePresenter } ``` -Ведущий может содержать любое количество методов `inject*()`, и каждый из них может иметь любое количество параметров. Это также отлично подходит для случаев, когда ведущий [состоит из признаков |presenter-traits], и каждый из них требует своей собственной зависимости. +Презентер может содержать любое количество методов `inject*()`, и каждый может иметь любое количество параметров. Они отлично подходят также в случаях, когда презентер [составлен из трейтов |presenter-traits], и каждый из них требует свою собственную зависимость. -`Inject` Атрибуты .[#toc-inject-attributes] -=========================================== +Атрибуты `Inject` +================= -Это форма [инъекции в свойства |dependency-injection:passing-dependencies#Property Injection]. Достаточно указать, какие свойства должны быть инжектированы, и Nette DI автоматически передает зависимости сразу после создания экземпляра ведущего. Для инъекции необходимо объявить их как public. +Это форма [инъекции в свойство |dependency-injection:passing-dependencies#Установка переменной]. Достаточно отметить, в какие свойства нужно инжектировать, и Nette DI автоматически передаст зависимости сразу после создания экземпляра презентера. Чтобы он мог их вставить, необходимо объявить их как public. -Свойства помечаются атрибутом: (ранее использовалась аннотация `/** @inject */`) +Свойства помечаем атрибутом: (раньше использовалась аннотация `/** @inject */`) ```php -use Nette\DI\Attributes\Inject; // эта строка важна +use Nette\DI\Attributes\Inject; // эта строка важна class MyPresenter extends Nette\Application\UI\Presenter { @@ -59,9 +56,6 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Преимуществом этого метода передачи зависимостей была очень экономичная форма нотации. Однако, с введением [продвижения свойств конструктора |https://blog.nette.org/ru/php-8-0-polnyj-obzor-novostej#toc-constructor-property-promotion], использование конструктора кажется более простым. +Преимуществом этого способа передачи зависимостей была очень лаконичная форма записи. Однако с появлением [constructor property promotion |https://blog.nette.org/ru/php-8-0-complete-overview-of-news#toc-constructor-property-promotion] кажется проще использовать конструктор. -С другой стороны, этот метод страдает теми же недостатками, что и передача зависимостей в свойства в целом: у нас нет контроля над изменениями переменной, и в то же время переменная становится частью публичного интерфейса класса, что нежелательно. - - -{{sitename: Лучшие практики}} +Напротив, этот способ страдает теми же недостатками, что и передача зависимости в свойства в целом: у нас нет контроля над изменениями в переменной, и в то же время переменная становится частью публичного интерфейса класса, что нежелательно. diff --git a/best-practices/ru/lets-create-contact-form.texy b/best-practices/ru/lets-create-contact-form.texy index f81335f4ef..981a38df9b 100644 --- a/best-practices/ru/lets-create-contact-form.texy +++ b/best-practices/ru/lets-create-contact-form.texy @@ -1,12 +1,12 @@ -Давайте создадим контактную форму -********************************* +Создаем контактную форму +************************ .[perex] -Давайте рассмотрим, как создать контактную форму в Nette, включая отправку ее на электронную почту. Итак, давайте сделаем это! +Посмотрим, как в Nette создать контактную форму, включая отправку на email. Итак, приступим! -Сначала мы должны создать новый проект. Как объясняется на странице " [Начало работы" |nette:installation]. А затем мы можем приступить к созданию формы. +Сначала нам нужно создать новый проект. Как это сделать, объясняется на странице [Начало работы |nette:installation]. А затем уже можно приступать к созданию формы. -Самый простой способ - создать [форму непосредственно в Presenter |forms:in-presenter]. Мы можем использовать готовый `HomePresenter`. Мы добавим компонент `contactForm`, представляющий форму. Для этого мы напишем фабричный метод `createComponentContactForm()` в коде, который будет создавать компонент: +Проще всего создать [форму прямо в презентере |forms:in-presenter]. Мы можем использовать готовый `HomePresenter`. В него добавим компонент `contactForm`, представляющий форму. Сделаем это так: запишем в код фабричный метод `createComponentContactForm()`, который создаст компонент: ```php use Nette\Application\UI\Form; @@ -17,37 +17,35 @@ class HomePresenter extends Presenter protected function createComponentContactForm(): Form { $form = new Form; - $form->addText('name', 'Name:') - ->setRequired('Enter your name'); + $form->addText('name', 'Имя:') + ->setRequired('Введите имя'); $form->addEmail('email', 'E-mail:') - ->setRequired('Enter your e-mail'); - $form->addTextarea('message', 'Message:') - ->setRequired('Enter message'); - $form->addSubmit('send', 'Send'); + ->setRequired('Введите e-mail'); + $form->addTextarea('message', 'Сообщение:') + ->setRequired('Введите сообщение'); + $form->addSubmit('send', 'Отправить'); $form->onSuccess[] = [$this, 'contactFormSucceeded']; return $form; } public function contactFormSucceeded(Form $form, $data): void { - // sending an email + // отправка email } } ``` -Как вы можете видеть, мы создали два метода. Первый метод `createComponentContactForm()` создает новую форму. В ней есть поля для имени, электронной почты и сообщения, которые мы добавляем с помощью методов `addText()`, `addEmail()` и `addTextArea()`. Мы также добавили кнопку для отправки формы. -Но что, если пользователь не заполнит некоторые поля? В этом случае мы должны сообщить ему, что это обязательное поле. Мы сделали это с помощью метода `setRequired()`. -Наконец, мы также добавили [событие |nette:glossary#events] `onSuccess`, которое срабатывает в случае успешной отправки формы. В нашем случае оно вызывает метод `contactFormSucceeded`, который обрабатывает отправленную форму. Мы добавим это в код через некоторое время. +Как видите, мы создали два метода. Первый метод `createComponentContactForm()` создает новую форму. У нее есть поля для имени, email и сообщения, которые мы добавляем методами `addText()`, `addEmail()` и `addTextArea()`. Также мы добавили кнопку для отправки формы. Но что, если пользователь не заполнит какое-то поле? В таком случае мы должны сообщить ему, что это обязательное поле. Этого мы добились с помощью метода `setRequired()`. Наконец, мы добавили также [событие |nette:glossary#События Events] `onSuccess`, которое сработает, если форма успешно отправлена. В нашем случае оно вызовет метод `contactFormSucceeded`, который позаботится об обработке отправленной формы. Это мы добавим в код через мгновение. -Пусть компонент `contantForm` будет отображен в шаблоне `templates/Home/default.latte`: +Компонент `contactForm` выведем в шаблоне `Home/default.latte`: ```latte {block content} -<h1>Contant Form</h1> +<h1>Контактная форма</h1> {control contactForm} ``` -Для отправки самого письма мы создадим новый класс `ContactFacade` и поместим его в файл `app/Model/ContactFacade.php`: +Для самой отправки email создадим новый класс, который назовем `ContactFacade` и разместим его в файле `app/Model/ContactFacade.php`: ```php <?php @@ -68,9 +66,9 @@ class ContactFacade public function sendMessage(string $email, string $name, string $message): void { $mail = new Message; - $mail->addTo('admin@example.com') // your email + $mail->addTo('admin@example.com') // ваш email ->setFrom($email, $name) - ->setSubject('Message from the contact form') + ->setSubject('Сообщение из контактной формы') ->setBody($message); $this->mailer->send($mail); @@ -78,9 +76,9 @@ class ContactFacade } ``` -Метод `sendMessage()` будет создавать и отправлять электронное письмо. Для этого он использует так называемый mailer, который он передает как зависимость через конструктор. Подробнее об отправке [электронных писем |mail:]. +Метод `sendMessage()` создает и отправляет email. Для этого он использует так называемый mailer, который получает как зависимость через конструктор. Узнайте больше об [отправке email |mail:]. -Теперь вернемся к ведущему и завершим метод `contactFormSucceeded()`. Он вызывает метод `sendMessage()` класса `ContactFacade` и передает ему данные формы. А как мы получим объект `ContactFacade`? Он будет передан нам конструктором: +Теперь вернемся к презентеру и завершим метод `contactFormSucceeded()`. Он вызовет метод `sendMessage()` класса `ContactFacade` и передаст ему данные из формы. А как получить объект `ContactFacade`? Попросим передать его через конструктор: ```php use App\Model\ContactFacade; @@ -102,36 +100,36 @@ class HomePresenter extends Presenter public function contactFormSucceeded(stdClass $data): void { $this->facade->sendMessage($data->email, $data->name, $data->message); - $this->flashMessage('The message has been sent'); + $this->flashMessage('Сообщение было отправлено'); $this->redirect('this'); } } ``` -После отправки письма мы показываем пользователю так называемое [flash-сообщение |application:components#flash-messages], подтверждающее, что письмо отправлено, а затем перенаправляем на следующую страницу, чтобы форма не могла быть повторно отправлена с помощью *refresh* в браузере. +После того как email будет отправлен, мы еще покажем пользователю так называемое [flash-сообщение |application:components#Flash-сообщения], подтверждающее, что сообщение отправлено, а затем перенаправим на другую страницу, чтобы нельзя было повторно отправить форму с помощью *refresh* в браузере. -Ну, если все работает, вы должны быть в состоянии отправить электронное письмо из вашей контактной формы. Поздравляем! +Итак, если все работает, вы должны быть в состоянии отправить email из вашей контактной формы. Поздравляю! -Шаблон электронной почты HTML .[#toc-html-email-template] ---------------------------------------------------------- +HTML шаблон email +----------------- -Пока что отправляется обычное текстовое письмо, содержащее только сообщение, отправленное формой. Но мы можем использовать HTML в письме и сделать его более привлекательным. Для этого мы создадим шаблон в Latte, который сохраним в `app/Model/contactEmail.latte`: +Пока отправляется простой текстовый email, содержащий только сообщение, отправленное формой. Но в email мы можем использовать HTML и сделать его вид более привлекательным. Создадим для него шаблон в Latte, который запишем в `app/Model/contactEmail.latte`: ```latte <html> - <title>Message from the contact form + Сообщение из контактной формы -

    Name: {$name}

    +

    Имя: {$name}

    E-mail: {$email}

    -

    Message: {$message}

    +

    Сообщение: {$message}

    ``` -Осталось модифицировать `ContactFacade`, чтобы использовать этот шаблон. В конструкторе мы запрашиваем класс `LatteFactory`, который может произвести объект `Latte\Engine`, [рендерер шаблона Latte |latte:develop#how-to-render-a-template]. Мы используем метод `renderToString()` для рендеринга шаблона в файл, первый параметр - путь к шаблону, второй - переменные. +Остается изменить `ContactFacade`, чтобы он использовал этот шаблон. В конструкторе запросим класс `LatteFactory`, который умеет создавать объект `Latte\Engine`, то есть [рендерер Latte шаблонов |latte:develop#Как отобразить шаблон]. С помощью метода `renderToString()` отрендерим шаблон в строку, первым параметром является путь к шаблону, а вторым — переменные. ```php namespace App\Model; @@ -158,7 +156,7 @@ class ContactFacade ]); $mail = new Message; - $mail->addTo('admin@example.com') // your email + $mail->addTo('admin@example.com') // ваш email ->setFrom($email, $name) ->setHtmlBody($body); @@ -167,15 +165,15 @@ class ContactFacade } ``` -Затем мы передаем сгенерированное HTML-письмо в метод `setHtmlBody()` вместо исходного `setBody()`. Нам также не нужно указывать тему письма в `setSubject()`, потому что библиотека берет ее из элемента `` в шаблоне. +Сгенерированный HTML email затем передадим методу `setHtmlBody()` вместо исходного `setBody()`. Также нам не нужно указывать тему email в `setSubject()`, потому что библиотека возьмет ее из элемента `<title>` шаблона. -Настройка .[#toc-configuring] ------------------------------ +Конфигурация +------------ -В коде класса `ContactFacade` наш администраторский email `admin@example.com` все еще жестко закодирован. Было бы лучше перенести его в конфигурационный файл. Как это сделать? +В коде класса `ContactFacade` все еще жестко прописан наш администраторский email `admin@example.com`. Было бы лучше перенести его в конфигурационный файл. Как это сделать? -Во-первых, мы изменим класс `ContactFacade` и заменим строку email на переменную, передаваемую конструктором: +Сначала изменим класс `ContactFacade` и заменим строку с email переменной, переданной через конструктор: ```php class ContactFacade @@ -199,21 +197,21 @@ class ContactFacade } ``` -И второй шаг - поместить значение этой переменной в конфигурацию. В файле `app/config/services.neon` мы добавляем: +А вторым шагом является указание значения этой переменной в конфигурации. В файл `app/config/services.neon` запишем: ```neon services: - App\Model\ContactFacade(adminEmail: admin@example.com) ``` -И все. Если в разделе `services` много элементов и вам кажется, что письмо теряется среди них, мы можем сделать его переменной. Мы изменим запись на: +И это все. Если бы записей в секции `services` было много и у вас было бы ощущение, что email среди них теряется, мы можем сделать из него переменную. Изменим запись на: ```neon services: - App\Model\ContactFacade(adminEmail: %adminEmail%) ``` -И определим эту переменную в файле `app/config/common.neon`: +А в файле `app/config/common.neon` определим эту переменную: ```neon parameters: @@ -221,6 +219,3 @@ parameters: ``` И готово! - - -{{sitename: Лучшие практики}} diff --git a/best-practices/ru/microsites.texy b/best-practices/ru/microsites.texy new file mode 100644 index 0000000000..c82e82b71a --- /dev/null +++ b/best-practices/ru/microsites.texy @@ -0,0 +1,63 @@ +Как писать микро-сайты +********************** + +Представьте, что вам нужно быстро создать небольшой сайт для предстоящего мероприятия вашей компании. Он должен быть простым, быстрым и без лишних сложностей. Возможно, вы думаете, что для такого маленького проекта вам не нужен надежный фреймворк. Но что, если использование фреймворка Nette может существенно упростить и ускорить этот процесс? + +Ведь даже при создании простых сайтов вы не хотите отказываться от удобства. Вы не хотите изобретать то, что уже было решено. Будьте спокойно ленивы и позвольте себя побаловать. Nette Framework можно отлично использовать и как микро-фреймворк. + +Как может выглядеть такой микросайт? Например, так, что весь код сайта мы разместим в одном файле `index.php` в публичной папке: + +```php +<?php + +require __DIR__ . '/../vendor/autoload.php'; + +$configurator = new Nette\Bootstrap\Configurator; +$configurator->enableTracy(__DIR__ . '/../log'); +$configurator->setTempDirectory(__DIR__ . '/../temp'); + +// создаем DI-контейнер на основе конфигурации в config.neon +$configurator->addConfig(__DIR__ . '/../app/config.neon'); +$container = $configurator->createContainer(); + +// настраиваем маршрутизацию +$router = new Nette\Application\Routers\RouteList; +$container->addService('router', $router); + +// маршрут для URL https://example.com/ +$router->addRoute('', function ($presenter, Nette\Http\Request $httpRequest) { + // определяем язык браузера и перенаправляем на URL /en или /de и т.д. + $supportedLangs = ['en', 'de', 'cs']; + $lang = $httpRequest->detectLanguage($supportedLangs) ?: reset($supportedLangs); + $presenter->redirectUrl("/$lang"); +}); + +// маршрут для URL https://example.com/cs или https://example.com/en +$router->addRoute('<lang cs|en>', function ($presenter, string $lang) { + // отображаем соответствующий шаблон, например ../templates/en.latte + $template = $presenter->createTemplate() + ->setFile(__DIR__ . '/../templates/' . $lang . '.latte'); + return $template; +}); + +// запускаем приложение! +$container->getByType(Nette\Application\Application::class)->run(); +``` + +Все остальное будут шаблоны, сохраненные в родительской папке `/templates`. + +PHP-код в `index.php` сначала [подготавливает среду |bootstrap:], затем определяет [маршруты |application:routing#Динамическая маршрутизация с callback-функциями] и, наконец, запускает приложение. Преимущество в том, что второй параметр функции `addRoute()` может быть callable, который выполнится после открытия соответствующей страницы. + + +Зачем использовать Nette для микросайта? +---------------------------------------- + +- Программисты, которые когда-либо пробовали [Tracy|tracy:], сегодня не могут представить себе программирование без нее. +- Прежде всего, вы воспользуетесь системой шаблонов [Latte|latte:], потому что уже со 2 страниц вы захотите иметь разделенный [макет и контент|latte:template-inheritance]. +- И вы определенно хотите положиться на [автоматическое экранирование |latte:safety-first], чтобы не возникла уязвимость XSS +- Nette также гарантирует, что при ошибке никогда не отобразятся программистские сообщения об ошибках PHP, а пользователю понятная страница. +- Если вы хотите получать обратную связь от пользователей, например, в виде контактной формы, то вы еще добавите [формы|forms:] и [базу данных|database:]. +- Заполненные формы вы также можете легко [отправлять по email|mail:]. +- Иногда вам может пригодиться [кеширование|caching:], например, если вы скачиваете и отображаете фиды. + +В наше время, когда скорость и эффективность являются ключевыми, важно иметь инструменты, которые позволят вам достигать результатов без лишних задержек. Фреймворк Nette предлагает именно это - быструю разработку, безопасность и широкий спектр инструментов, таких как Tracy и Latte, которые упрощают процесс. Достаточно установить несколько пакетов Nette, и создание такого микросайта становится совершенно простым делом. И вы знаете, что нигде не скрывается никакой дыры в безопасности. diff --git a/best-practices/ru/pagination.texy b/best-practices/ru/pagination.texy index 11f52f40f2..15756e6d22 100644 --- a/best-practices/ru/pagination.texy +++ b/best-practices/ru/pagination.texy @@ -1,17 +1,16 @@ -Пагинация результатов запроса к базе данных -******************************************* +Пагинация результатов базы данных +********************************* .[perex] -При разработке веб-приложений вы часто сталкиваетесь с требованием выводить на странице ограниченное количество записей. +При создании веб-приложений очень часто возникает требование ограничить количество выводимых элементов на странице. -Мы выходим из состояния, когда перечисляем все данные без пагинации. Для выбора данных из базы данных у нас есть класс ArticleRepository, который содержит конструктор и метод `findPublishedArticles`, возвращающий все опубликованные статьи, отсортированные в порядке убывания даты публикации. +Начнем с состояния, когда мы выводим все данные без пагинации. Для выбора данных из базы данных у нас есть класс `ArticleRepository`, который, помимо конструктора, содержит метод `findPublishedArticles`, возвращающий все опубликованные статьи, отсортированные по убыванию даты публикации. ```php namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -31,10 +30,10 @@ class ArticleRepository } ``` -Затем в презентере мы вводим класс модели и в методе `render` запрашиваем опубликованные статьи, которые передаем в шаблон: +В презентере мы затем инжектируем класс модели и в методе рендеринга запрашиваем опубликованные статьи, которые передаем в шаблон: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -53,7 +52,7 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -В шаблоне мы позаботимся о выводе списка статей: +В шаблоне `default.latte` затем позаботимся о выводе статей: ```latte {block content} @@ -68,11 +67,11 @@ class HomePresenter extends Nette\Application\UI\Presenter ``` -Таким образом, мы можем написать все статьи, но это вызовет проблемы, когда количество статей вырастет. В этот момент будет полезно реализовать механизм пагинации. +Таким образом, мы умеем выводить все статьи, что, однако, начнет вызывать проблемы, когда количество статей возрастет. В этот момент пригодится реализация механизма пагинации. -Это обеспечит разбиение всех статей на несколько страниц, и мы будем показывать только статьи одной текущей страницы. Общее количество страниц и распределение статей рассчитывается самим [utils:Paginator], в зависимости от того, сколько статей у нас всего и сколько статей мы хотим отобразить на странице. +Он обеспечит разделение всех статей на несколько страниц, и мы будем отображать только статьи текущей страницы. Общее количество страниц и распределение статей вычислит [Paginator |utils:Paginator] сам, исходя из того, сколько всего у нас статей и сколько статей мы хотим отображать на странице. -На первом этапе мы изменим метод получения статей в классе репозитория, чтобы он возвращал только одностраничные статьи. Мы также добавим новый метод для получения общего количества статей в базе данных, который нам понадобится для установки Paginator: +На первом шаге мы изменим метод получения статей в классе репозитория так, чтобы он мог возвращать только статьи для одной страницы. Также добавим метод для определения общего количества статей в базе данных, который нам понадобится для настройки Paginator: ```php namespace App\Model; @@ -100,7 +99,7 @@ class ArticleRepository } /** - * Returns the total number of published articles + * Возвращает общее количество опубликованных статей */ public function getPublishedArticlesCount(): int { @@ -109,12 +108,12 @@ class ArticleRepository } ``` -Следующим шагом будет редактирование презентера. Мы передадим номер текущей отображаемой страницы в метод `render`. В случае, если этот номер не является частью URL, нам нужно установить значение по умолчанию для первой страницы. +Затем приступим к изменениям в презентере. В метод рендеринга будем передавать номер текущей отображаемой страницы. На случай, если этот номер не будет частью URL, установим значение по умолчанию — первая страница. -Мы также расширяем метод `render` для получения экземпляра Paginator, его настройки и выбора нужных статей для отображения в шаблоне. HomePresenter будет выглядеть следующим образом: +Далее также расширим метод рендеринга получением экземпляра Paginator, его настройкой и выбором правильных статей для отображения в шаблоне. `HomePresenter` после изменений будет выглядеть так: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -128,19 +127,19 @@ class HomePresenter extends Nette\Application\UI\Presenter public function renderDefault(int $page = 1): void { - // Найдем общее количество опубликованных статей + // Узнаем общее количество опубликованных статей $articlesCount = $this->articleRepository->getPublishedArticlesCount(); - // Мы создадим экземпляр Paginator и настроим его + // Создадим экземпляр Paginator и настроим его $paginator = new Nette\Utils\Paginator; - $paginator->setItemCount($articlesCount); // total articles count - $paginator->setItemsPerPage(10); // items per page - $paginator->setPage($page); // actual page number + $paginator->setItemCount($articlesCount); // общее количество статей + $paginator->setItemsPerPage(10); // количество элементов на странице + $paginator->setPage($page); // номер текущей страницы - // Мы найдем ограниченный набор статей из базы данных на основе расчетов Paginator + // Из базы данных извлечем ограниченное количество статей согласно расчету Paginator $articles = $this->articleRepository->findPublishedArticles($paginator->getLength(), $paginator->getOffset()); - // который мы передаем в шаблон + // которую передадим в шаблон $this->template->articles = $articles; // а также сам Paginator для отображения опций пагинации $this->template->paginator = $paginator; @@ -148,11 +147,11 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -Шаблон уже итерирует статьи на одной странице, просто добавьте ссылки пагинации: +Шаблон теперь уже итерирует только по статьям одной страницы, нам остается добавить ссылки пагинации: ```latte {block content} -<h1>Articles</h1> +<h1>Статьи</h1> <div class="articles"> {foreach $articles as $article} @@ -181,16 +180,15 @@ class HomePresenter extends Nette\Application\UI\Presenter ``` -Вот как мы добавили пагинацию с помощью Paginator. Если вместо [Nette Database Explorer |database:explorer] в качестве слоя базы данных используется [Nette Database Core |database:core], мы можем реализовать подкачку даже без Paginator. Класс `Nette\Database\Table\Selection` содержит метод [page |api:Nette\Database\Table\Selection::_ page] с логикой пагинации, взятой из Paginator. +Таким образом, мы дополнили страницу возможностью пагинации с помощью Paginator. В случае, когда вместо [Nette Database Core |database:sql-way] в качестве слоя базы данных мы используем [Nette Database Explorer |database:explorer], мы можем реализовать пагинацию и без использования Paginator. Класс `Nette\Database\Table\Selection` содержит метод [page |api:Nette\Database\Table\Selection::_page] с логикой пагинации, взятой из Paginator. -Репозиторий будет выглядеть следующим образом: +Репозиторий при таком способе реализации будет выглядеть так: ```php namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -198,7 +196,6 @@ class ArticleRepository ) { } - public function findPublishedArticles(): Nette\Database\Table\Selection { return $this->database->table('articles') @@ -208,10 +205,10 @@ class ArticleRepository } ``` -Нам не нужно создавать Paginator в презентере, вместо этого мы будем использовать метод объекта `Selection`, возвращаемый репозиторием: +В презентере нам не нужно создавать Paginator, вместо него мы используем метод класса `Selection`, который возвращает репозиторий: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -225,10 +222,10 @@ class HomePresenter extends Nette\Application\UI\Presenter public function renderDefault(int $page = 1): void { - // Найдем опубликованные статьи + // Извлечем опубликованные статьи $articles = $this->articleRepository->findPublishedArticles(); - // и их часть, ограниченную вычислением метода page, которую мы передадим в шаблон + // и в шаблон отправим только их часть, ограниченную согласно расчету метода page $lastPage = 0; $this->template->articles = $articles->page($page, 10, $lastPage); @@ -239,7 +236,7 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -Так как мы не используем Paginator, нам нужно отредактировать раздел, показывающий ссылки пагинации: +Поскольку в шаблон мы теперь не передаем Paginator, изменим часть, отображающую ссылки пагинации: ```latte {block content} @@ -271,7 +268,6 @@ class HomePresenter extends Nette\Application\UI\Presenter </div> ``` -Таким образом, мы реализовали механизм пагинации без использования пагинатора. +Таким образом, мы реализовали механизм пагинации без использования Paginator. {{priority: -1}} -{{sitename: Лучшие практики}} diff --git a/best-practices/ru/passing-settings-to-presenters.texy b/best-practices/ru/passing-settings-to-presenters.texy index 6c310f2c2d..b65b1186c6 100644 --- a/best-practices/ru/passing-settings-to-presenters.texy +++ b/best-practices/ru/passing-settings-to-presenters.texy @@ -1,10 +1,10 @@ -Передача параметров презентаторам -********************************* +Передача настроек в презентеры +****************************** .[perex] -Вам нужно передавать презентерам аргументы, которые не являются объектами (например, информацию о том, работает ли он в режиме отладки, пути к каталогам и т.д.) и поэтому не могут быть переданы автоматически с помощью автоподключения? Решение - инкапсулировать их в объект `Settings`. +Вам нужно передавать в презентеры аргументы, которые не являются объектами (например, информацию о том, работает ли приложение в режиме отладки, пути к каталогам и т.д.), и поэтому не могут быть переданы автоматически с помощью autowiring? Решением является инкапсуляция их в объект `Settings`. -Служба `Settings` - это очень простой и полезный способ предоставления информации о запущенном приложении докладчикам. Его конкретная форма полностью зависит от ваших конкретных потребностей. Пример: +Сервис `Settings` представляет собой очень простой и в то же время полезный способ предоставления информации о работающем приложении презентерам. Его конкретный вид зависит исключительно от ваших конкретных потребностей. Пример: ```php namespace App; @@ -12,10 +12,10 @@ namespace App; class Settings { public function __construct( - // since PHP 8.1 it is possible to specify readonly + // с PHP 8.1 можно указать readonly public bool $debugMode, public string $appDir, - // and so on + // и так далее ) {} } ``` @@ -30,7 +30,7 @@ services: ) ``` -Когда ведущему нужна информация, предоставляемая этой службой, он просто запрашивает ее в конструкторе: +Когда презентеру понадобится информация, предоставляемая этим сервисом, он просто запросит ее в конструкторе: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -47,5 +47,3 @@ class MyPresenter extends Nette\Application\UI\Presenter } } ``` - -{{sitename: Лучшие практики}} diff --git a/best-practices/ru/post-links.texy b/best-practices/ru/post-links.texy new file mode 100644 index 0000000000..59daf4a9e5 --- /dev/null +++ b/best-practices/ru/post-links.texy @@ -0,0 +1,56 @@ +Как правильно использовать POST ссылки +************************************** + +.[perex] +В веб-приложениях, особенно в административных интерфейсах, основным правилом должно быть то, что действия, изменяющие состояние сервера, не должны выполняться посредством HTTP-метода GET. Как следует из названия метода, GET должен служить только для получения данных, а не для их изменения. Для действий, таких как удаление записей, предпочтительнее использовать метод POST. Хотя идеальным был бы метод DELETE, но его нельзя вызвать без JavaScript, поэтому исторически используется POST. + +Как это сделать на практике? Используйте этот простой трюк. В начале шаблона создайте вспомогательную форму с идентификатором `postForm`, которую затем используете для кнопок удаления: + +```latte .{file:@layout.latte} +<form method="post" id="postForm"></form> +``` + +Благодаря этой форме вы можете вместо классической ссылки `<a>` использовать кнопку `<button>`, которую можно визуально стилизовать так, чтобы она выглядела как обычная ссылка. Например, CSS-фреймворк Bootstrap предлагает классы `btn btn-link`, с помощью которых вы добьетесь того, что кнопка не будет визуально отличаться от других ссылок. С помощью атрибута `form="postForm"` мы свяжем ее с подготовленной формой: + +```latte .{file:admin.latte} +<table> + <tr n:foreach="$posts as $post"> + <td>{$post->title}</td> + <td> + <button class="btn btn-link" form="postForm" formaction="{link delete $post->id}">удалить</button> + <!-- вместо <a n:href="delete $post->id">удалить</a> --> + </td> + </tr> +</table> +``` + +При нажатии на ссылку теперь вызывается действие `delete`. Для обеспечения того, чтобы запросы принимались только через метод POST и с того же домена (что является эффективной защитой от CSRF-атак), используйте атрибут `#[Requires]`: + +```php .{file:AdminPresenter.php} +use Nette\Application\Attributes\Requires; + +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST', sameOrigin: true)] + public function actionDelete(int $id): void + { + $this->facade->deletePost($id); // гипотетический код, удаляющий запись + $this->redirect('default'); + } +} +``` + +Атрибут существует с Nette Application 3.2, и больше о его возможностях вы узнаете на странице [Как использовать атрибут #Requires |attribute-requires]. + +Если бы вы вместо действия `actionDelete()` использовали сигнал `handleDelete()`, не нужно указывать `sameOrigin: true`, потому что сигналы имеют эту защиту, установленную по умолчанию: + +```php .{file:AdminPresenter.php} +#[Requires(methods: 'POST')] +public function handleDelete(int $id): void +{ + $this->facade->deletePost($id); + $this->redirect('this'); +} +``` + +Этот подход не только улучшает безопасность вашего приложения, но и способствует соблюдению правильных веб-стандартов и практик. Используя методы POST для действий, изменяющих состояние, вы достигнете более надежного и безопасного приложения. diff --git a/best-practices/ru/presenter-traits.texy b/best-practices/ru/presenter-traits.texy index 91ec5df365..96d90aef15 100644 --- a/best-practices/ru/presenter-traits.texy +++ b/best-practices/ru/presenter-traits.texy @@ -1,14 +1,14 @@ -Составление презентеров из признаков -************************************ +Компоновка презентеров из трейтов +********************************* .[perex] -Если нам нужно реализовать один и тот же код в нескольких презентерах (например, проверка того, что пользователь вошел в систему), заманчиво поместить этот код в общего предка. Второй вариант - создать одноцелевые трейты. +Если нам нужно реализовать один и тот же код в нескольких презентерах (например, проверку, что пользователь авторизован), предлагается разместить код в общем предке. Вторая возможность — создание одноцелевых [трейтов |nette:introduction-to-object-oriented-programming#Трейты]. -Преимущество этого решения заключается в том, что каждый ведущий может использовать только те признаки, которые ему действительно нужны, в то время как множественное наследование в PHP невозможно. +Преимущество этого решения в том, что каждый из презентеров может использовать именно те трейты, которые ему действительно нужны, в то время как множественное наследование в PHP невозможно. -Эти черты могут использовать тот факт, что все [методы inject |inject-method-attribute#inject-Methods] вызываются последовательно при создании ведущего. Вам просто нужно убедиться, что имя каждого метода inject уникально. +Эти трейты могут использовать тот факт, что при создании презентера последовательно вызываются все [inject методы |inject-method-attribute#Методы inject]. Необходимо только следить за тем, чтобы имя каждого inject метода было уникальным. -Трейты могут повесить код инициализации в события [onStartup или onRender |application:presenters#Events]. +Трейты могут навешивать инициализационный код на события [onStartup или onRender |application:presenters#События]. Примеры: @@ -36,7 +36,7 @@ trait StandardTemplateFilters } ``` -Затем ведущий просто использует эти черты: +Презентер затем просто использует эти трейты: ```php class ArticlePresenter extends Nette\Application\UI\Presenter @@ -45,6 +45,3 @@ class ArticlePresenter extends Nette\Application\UI\Presenter use RequireLoggedUser; } ``` - - -{{sitename: Лучшие практики}} diff --git a/best-practices/ru/restore-request.texy b/best-practices/ru/restore-request.texy index f93b41ae3c..19f5c12df1 100644 --- a/best-practices/ru/restore-request.texy +++ b/best-practices/ru/restore-request.texy @@ -1,17 +1,16 @@ -Как вернуться на предыдущую страницу? -************************************* +Как вернуться к предыдущей странице? +************************************ .[perex] -Что делать, если пользователь заполнил форму, а срок действия его логина истек? Чтобы избежать потери данных, мы сохраняем их в сессии перед перенаправлением на страницу входа в систему. В Nette это проще простого. +Что, если пользователь заполняет форму, а его сессия истекает? Чтобы он не потерял данные, перед перенаправлением на страницу входа мы сохраним данные в сессию. В Nette это совершенно просто. -Текущий запрос может быть сохранен в сессии с помощью метода `storeRequest()`, который возвращает его идентификатор в виде короткой строки. Метод сохраняет имя текущего презентера, представление и его параметры. -Если форма также была отправлена, значения полей (за исключением загруженных файлов) также сохраняются. +Текущий запрос можно сохранить в сессию с помощью метода `storeRequest()`, который возвращает его идентификатор в виде короткой строки. Метод сохраняет имя текущего презентера, представление и его параметры. В случае, если была отправлена и форма, сохраняется также содержимое полей (за исключением загруженных файлов). -Запрос восстанавливается методом `restoreRequest($key)`, которому мы передаем извлеченный идентификатор. Это перенаправляет к исходному презентеру и представлению. Однако, если сохраненный запрос содержит форму отправки, он будет перенаправлен первоначальному презентеру, используя метод `forward()`, передайте ранее заполненные значения в форму и позвольте ей перерисоваться. Это позволяет пользователю повторно отправить форму, и данные не будут потеряны. +Восстановление запроса выполняет метод `restoreRequest($key)`, которому мы передаем полученный идентификатор. Он перенаправляет на исходный презентер и представление. Однако, если сохраненный запрос содержит отправку формы, он перейдет на исходный презентер методом `forward()`, передаст форме ранее заполненные значения и позволит ее снова отрисовать. Таким образом, пользователь имеет возможность повторно отправить форму, и никакие данные не теряются. -Важно отметить, что `restoreRequest()` проверяет, что вновь вошедший пользователь является тем же самым, который первоначально заполнил форму. Если нет, он отбрасывает запрос и ничего не делает. +Важно, что `restoreRequest()` проверяет, является ли вновь вошедший пользователь тем же, кто изначально заполнял форму. Если нет, запрос отбрасывается, и ничего не происходит. -Давайте продемонстрируем всё на примере. Пусть у нас есть презентер `AdminPresenter`, в котором редактируются данные и метод которого `startup()` проверяет, вошел ли пользователь в систему. Если это не так, мы перенаправляем его на `SignPresenter`. В то же время, мы сохраняем текущий запрос и отправляем его ключ в `SignPresenter`. +Покажем все на примере. Пусть у нас есть презентер `AdminPresenter`, в котором редактируются данные и в методе `startup()` которого проверяется, авторизован ли пользователь. Если нет, перенаправляем его на `SignPresenter`. Одновременно сохраняем текущий запрос и его ключ отправляем в `SignPresenter`. ```php class AdminPresenter extends Nette\Application\UI\Presenter @@ -27,7 +26,7 @@ class AdminPresenter extends Nette\Application\UI\Presenter } ``` -Презентер `SignPresenter` будет содержать постоянный параметр `$backlink`, в который записывается ключ, в дополнение к форме входа в систему. Поскольку параметр является постоянным, он будет перенесен даже после отправки формы входа. +Презентер `SignPresenter` будет содержать, помимо формы для входа, также персистентный параметр `$backlink`, в который запишется ключ. Поскольку параметр персистентный, он будет передаваться и после отправки формы входа. ```php @@ -48,7 +47,7 @@ class SignPresenter extends Nette\Application\UI\Presenter public function signInFormSubmitted($form) { - // ... здесь мы регистрируем пользователя ... + // ... здесь авторизуем пользователя ... $this->restoreRequest($this->backlink); $this->redirect('Admin:'); @@ -56,9 +55,8 @@ class SignPresenter extends Nette\Application\UI\Presenter } ``` -Мы передаем ключ сохраненного запроса методу `restoreRequest()`, и он перенаправляет (или переадресует) к исходному презентеру. +Методу `restoreRequest()` передаем ключ сохраненного запроса, и он перенаправляет (или переходит) на исходный презентер. -Однако, если ключ недействителен (например, больше не существует в сессии), метод ничего не делает. Поэтому следующим вызовом будет `$this->redirect('Admin:')`, который перенаправляет на `AdminPresenter`. +Однако, если ключ недействителен (например, его уже нет в сессии), метод ничего не делает. Затем следует вызов `$this->redirect('Admin:')`, который перенаправляет на `AdminPresenter`. {{priority: -1}} -{{sitename: Лучшие практики}} diff --git a/best-practices/sl/@home.texy b/best-practices/sl/@home.texy index ea4899f2ec..a4cf0efbec 100644 --- a/best-practices/sl/@home.texy +++ b/best-practices/sl/@home.texy @@ -1,22 +1,24 @@ -Najboljše prakse -**************** +Navodila in postopki +******************** .[perex] -Navodila, rešitve pogostih težav in najboljše prakse za Nette. +Navodila, rešitve pogostih nalog in *najboljše prakse* za Nette. <div class=documentation> <div> -Nette aplikacija +Nette Aplikacije ---------------- -- [Vbrizgavanje metod in atributov |inject-method-attribute] -- [Sestavljanje predstavnikov iz lastnosti |presenter-traits] -- [Posredovanje nastavitev predstavnikom |passing-settings-to-presenters] +- [Metode in atributi inject |inject-method-attribute] +- [Sestavljanje presenterjev iz traitov |presenter-traits] +- [Posredovanje nastavitev v presenterje |passing-settings-to-presenters] - [Kako se vrniti na prejšnjo stran |restore-request] -- [Postransko prikazovanje rezultatov podatkovne zbirke |Pagination] -- [Dinamični utrinki |dynamic-snippets] +- [Strankanje rezultatov podatkovne baze |pagination] +- [Dinamični odrezki |dynamic-snippets] +- [Kako uporabljati atribut #Requires |attribute-requires] +- [Kako pravilno uporabljati POST povezave |post-links] </div> <div> @@ -26,43 +28,42 @@ Obrazci ------- - [Ponovna uporaba obrazcev |form-reuse] - [Obrazec za ustvarjanje in urejanje zapisa |creating-editing-form] -- [Ustvarimo obrazec za stike |lets-create-contact-form] -- [Odvisna izbirna polja |https://blog.nette.org/sl/odvisna-izbirna-polja-elegantno-v-nette-in-cistem-js] +- [Ustvarjamo kontaktni obrazec |lets-create-contact-form] +- [Odvisni selectboxi |https://blog.nette.org/sl/dependent-selectboxes-elegantly-in-nette-and-pure-js] </div> <div> -Skupna spletna stran --------------------- +Splošno +------- - [Kako naložiti konfiguracijsko datoteko |bootstrap:] -- [Zakaj Nette uporablja konstantni zapis PascalCase |https://blog.nette.org/sl/za-manj-kricanja-v-kodi]? -- [Zakaj Nette ne uporablja končnice Interface |https://blog.nette.org/sl/predpone-in-pripone-ne-sodijo-v-imena-vmesnikov]? -- [Nasveti za uporabo programa Composer |composer] -- [Nasveti o urejevalnikih in orodjih |editors-and-tools] +- [Kako pisati mikro-spletne strani |microsites] +- [Zakaj Nette uporablja PascalCase notacijo konstant? |https://blog.nette.org/sl/for-less-screaming-in-the-code] +- [Zakaj Nette ne uporablja pripone Interface? |https://blog.nette.org/sl/prefixes-and-suffixes-do-not-belong-in-interface-names] +- [Composer: nasveti za uporabo |composer] +- [Nasveti za urejevalnike & orodja |editors-and-tools] +- [Uvod v objektno orientirano programiranje |nette:introduction-to-object-oriented-programming] </div> <div> -Rešitev vzorca --------------- -- [Primeri Nette |https://github.com/nette-examples] -- [Doktrina & Nette |https://contributte.org/nettrine/] -- [Primeri Contributte |https://contributte.org/examples.html] -- [Spletna stran Doctrine ORM |https://github.com/MinecordNetwork/Website] -- [Hitri začetek |quickstart:] +Primeri rešitev +--------------- +- [Nette examples |https://github.com/nette-examples] +- [Doctrine & Nette |https://contributte.org/nettrine/] +- [Contributte examples |https://contributte.org/examples.html] +- [Doctrine ORM Website |https://github.com/MinecordNetwork/Website] +- [Quick start |quickstart:] </div> <div> -Videoposnetki -------------- -Na stotine posnetkov iz Posobote in videoposnetkov o Nette najdete pod eno streho na "Nette Framework YouTube Channel":https://www.youtube.com/user/NetteFramework. +Videi +----- +Stotine posnetkov iz Poslednjih sobot in videov o Nette najdete pod eno streho na "Youtube kanalu Nette Frameworka":https://www.youtube.com/user/NetteFramework. </div> </div> - -{{sitename: Best Practices}} -{{leftbar: www:@menu-common}} diff --git a/best-practices/sl/@meta.texy b/best-practices/sl/@meta.texy new file mode 100644 index 0000000000..f58ad17850 --- /dev/null +++ b/best-practices/sl/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Navodila in postopki}} +{{leftbar: www:@menu-common}} diff --git a/best-practices/sl/attribute-requires.texy b/best-practices/sl/attribute-requires.texy new file mode 100644 index 0000000000..8fb0588c40 --- /dev/null +++ b/best-practices/sl/attribute-requires.texy @@ -0,0 +1,177 @@ +Kako uporabljati atribut `#[Requires]` +************************************** + +.[perex] +Ko pišete spletno aplikacijo, se pogosto srečate s potrebo po omejitvi dostopa do določenih delov vaše aplikacije. Morda želite, da lahko nekateri zahtevki pošiljajo podatke samo s pomočjo obrazca (torej z metodo POST), ali da so dostopni samo za AJAX klice. V Nette Frameworku 3.2 se je pojavilo novo orodje, ki vam omogoča takšne omejitve nastaviti zelo elegantno in pregledno: atribut `#[Requires]`. + +Atribut je posebna oznaka v PHP, ki jo dodate pred definicijo razreda ali metode. Ker gre pravzaprav za razred, da bi vam naslednji primeri delovali, je treba navesti klavzulo use: + +```php +use Nette\Application\Attributes\Requires; +``` + +Atribut `#[Requires]` lahko uporabite pri samem razredu presenterja in tudi na teh metodah: + +- `action<Action>()` +- `render<View>()` +- `handle<Signal>()` +- `createComponent<Name>()` + +Zadnji dve metodi se nanašata tudi na komponente, torej atribut lahko uporabljate tudi pri njih. + +Če pogoji, ki jih atribut navaja, niso izpolnjeni, pride do sprožitve HTTP napake 4xx. + + +Metode HTTP +----------- + +Lahko specificirate, katere HTTP metode (kot GET, POST itd.) so za dostop dovoljene. Na primer, če želite dovoliti dostop samo s pošiljanjem obrazca, nastavite: + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +Zakaj bi morali uporabljati POST namesto GET za akcije, ki spreminjajo stanje, in kako to storiti? [Preberite navodilo |post-links]. + +Lahko navedete metodo ali polje metod. Poseben primer je vrednost `'*'`, ki dovoli vse metode, kar standardno presenterji iz [varnostnih razlogov ne dovoljujejo |application:presenters#Preverjanje HTTP metode]. + + +AJAX klici +---------- + +Če želite, da je presenter ali metoda dostopna samo za AJAX zahtevke, uporabite: + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Isti izvor +---------- + +Za povečanje varnosti lahko zahtevate, da je zahtevek narejen iz iste domene. S tem preprečite [ranljivost CSRF |nette:vulnerability-protection#Cross-Site Request Forgery CSRF]: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Pri metodah `handle<Signal>()` je dostop iz iste domene zahtevan samodejno. Torej, če nasprotno želite dovoliti dostop iz katerekoli domene, navedite: + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Dostop prek posredovanja +------------------------ + +Včasih je koristno omejiti dostop do presenterja tako, da je dostopen samo posredno, na primer z uporabo metode `forward()` ali `switch()` iz drugega presenterja. Tako se na primer ščitijo error-presenterji, da jih ni mogoče poklicati iz URL-ja: + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +V praksi je pogosto treba označiti določene poglede (views), do katerih je mogoče priti šele na podlagi logike v presenterju. Torej spet, da jih ni mogoče odpreti neposredno: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = $this->facade->getProduct($id); + if (!$product) { + $this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Konkretne akcije +---------------- + +Lahko tudi omejite, da bo določena koda, na primer ustvarjanje komponente, dostopna samo za specifične akcije v presenterju: + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +V primeru ene akcije ni treba zapisovati polja: `#[Requires(actions: 'default')]` + + +Lastni atributi +--------------- + +Če želite atribut `#[Requires]` uporabiti večkrat z isto nastavitvijo, si lahko ustvarite lasten atribut, ki bo dedoval `#[Requires]` in ga nastavil po potrebi. + +Na primer `#[SingleAction]` bo omogočil dostop samo prek akcije `default`: + +```php +#[\Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Ali `#[RestMethods]` bo omogočil dostop prek vseh HTTP metod, uporabljenih za REST API: + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Zaključek +--------- + +Atribut `#[Requires]` vam daje veliko fleksibilnosti in nadzora nad tem, kako so vaše spletne strani dostopne. S pomočjo preprostih, a močnih pravil lahko povečate varnost in pravilno delovanje vaše aplikacije. Kot vidite, lahko uporaba atributov v Nette vaše delo ne samo olajša, ampak tudi zavaruje. diff --git a/best-practices/sl/composer.texy b/best-practices/sl/composer.texy index 52eb9cc2da..bcfe1802c9 100644 --- a/best-practices/sl/composer.texy +++ b/best-practices/sl/composer.texy @@ -1,44 +1,44 @@ -Nasveti za uporabo programa Composer -************************************ +Composer: nasveti za uporabo +**************************** <div class=perex> -Composer je orodje za upravljanje odvisnosti v PHP. Z njim lahko prijavite knjižnice, od katerih je odvisen vaš projekt, in orodje jih bo namestilo in posodobilo namesto vas. Naučili se bomo: +Composer je orodje za upravljanje odvisnosti v PHP. Omogoča nam, da naštejemo knjižnice, od katerih je naš projekt odvisen, in jih bo za nas nameščal in posodabljal. Pokazali bomo: - kako namestiti Composer -- kako ga uporabiti v novem ali obstoječem projektu +- njegovo uporabo v novem ali obstoječem projektu </div> -Namestitev .[#toc-installation] -=============================== +Namestitev +========== -Composer je izvršljiva datoteka `.phar`, ki jo prenesete in namestite na naslednji način. +Composer je izvedljiva datoteka `.phar`, ki jo prenesete in namestite na naslednji način: -Windows .[#toc-windows] ------------------------ +Windows +------- Uporabite uradni namestitveni program [Composer-Setup.exe |https://getcomposer.org/Composer-Setup.exe]. -Linux, macOS .[#toc-linux-macos] --------------------------------- +Linux, macOS +------------ -Vse, kar potrebujete, so 4 ukazi, ki jih lahko kopirate s [te strani |https://getcomposer.org/download/]. +Dovolj so 4 ukazi, ki jih kopirate s [te strani |https://getcomposer.org/download/]. -Poleg tega s kopiranjem v mapo, ki je v sistemski mapi `PATH`, postane Composer globalno dostopen: +Nato z vstavitvijo v mapo, ki je v sistemskem `PATH`, postane Composer dostopen globalno: ```shell -$ mv ./composer.phar ~/bin/composer # or /usr/local/bin/composer +$ mv ./composer.phar ~/bin/composer # ali /usr/local/bin/composer ``` -Uporaba v projektu .[#toc-use-in-project] -========================================= +Uporaba v projektu +================== -Za začetek uporabe programa Composer v projektu potrebujete samo datoteko `composer.json`. Ta datoteka opisuje odvisnosti vašega projekta in lahko vsebuje tudi druge metapodatke. Najpreprostejša datoteka `composer.json` je lahko videti takole: +Da bi lahko v svojem projektu začeli uporabljati Composer, potrebujete samo datoteko `composer.json`. Ta opisuje odvisnosti našega projekta in lahko vsebuje tudi druge metapodatke. Osnovni `composer.json` torej lahko izgleda takole: ```js { @@ -48,17 +48,17 @@ Za začetek uporabe programa Composer v projektu potrebujete samo datoteko `comp } ``` -Tu pravimo, da je naša aplikacija (ali knjižnica) odvisna od paketa `nette/database` (ime paketa je sestavljeno iz imena prodajalca in imena projekta) in želi različico, ki ustreza omejitvi različice `^3.0`. +Tukaj pravimo, da naša aplikacija (ali knjižnica) zahteva paket `nette/database` (ime paketa sestoji iz imena organizacije in imena projekta) in želi različico, ki ustreza pogoju `^3.0` (tj. najnovejšo različico 3). -Torej, ko imamo datoteko `composer.json` v korenu projekta in zaženemo: +Imamo torej v korenu projekta datoteko `composer.json` in zaženemo namestitev: ```shell composer update ``` -Composer bo prenesel podatkovno zbirko Nette v imenik `vendor`. Ustvari tudi datoteko `composer.lock`, ki vsebuje informacije o tem, katere različice knjižnic je natančno namestil. +Composer bo prenesel Nette Database v mapo `vendor/`. Nato bo ustvaril datoteko `composer.lock`, ki vsebuje informacije o tem, katere različice knjižnic je točno namestil. -Composer ustvari datoteko `vendor/autoload.php`. To datoteko lahko preprosto vključite in brez dodatnega dela začnete uporabljati razrede, ki jih zagotavljajo te knjižnice: +Composer bo generiral datoteko `vendor/autoload.php`, ki jo lahko preprosto vključimo in začnemo uporabljati knjižnice brez kakršnegakoli dodatnega dela: ```php require __DIR__ . '/vendor/autoload.php'; @@ -67,46 +67,46 @@ $db = new Nette\Database\Connection('sqlite::memory:'); ``` -Posodobitev paketov na najnovejše različice .[#toc-update-packages-to-the-latest-versions] -========================================================================================== +Posodabljanje paketov na najnovejše različice +============================================= -Za posodobitev vseh uporabljenih paketov na najnovejšo različico v skladu z omejitvami različic, opredeljenimi v `composer.json`, uporabite ukaz `composer update`. Na primer za odvisnost `"nette/database": "^3.0"` bo namestil najnovejšo različico 3.x.x, ne pa tudi različice 4. +Za posodabljanje uporabljenih knjižnic na najnovejše različice glede na pogoje, definirane v `composer.json`, skrbi ukaz `composer update`. Npr. pri odvisnosti `"nette/database": "^3.0"` bo namestil najnovejšo različico 3.x.x, vendar ne več različice 4. -Za posodobitev omejitev različice v datoteki `composer.json` na primer na `"nette/database": "^4.1"`, da omogočite namestitev najnovejše različice, uporabite ukaz `composer require nette/database`. +Za posodobitev pogojev v datoteki `composer.json`, na primer na `"nette/database": "^4.1"`, da bi bilo mogoče namestiti najnovejšo različico, uporabite ukaz `composer require nette/database`. -Če želite posodobiti vse uporabljene pakete Nette, bi jih bilo treba vse našteti v ukazni vrstici, npr: +Za posodobitev vseh uporabljenih paketov Nette bi bilo treba vse v ukazni vrstici našteti, npr.: ```shell composer require nette/application nette/forms latte/latte tracy/tracy ... ``` -To je nepraktično. Zato uporabite preprosto skripto "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff, ki bo to storila namesto vas: +Kar je nepraktično. Uporabite zato preprost skript "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff, ki to stori za vas: ```shell php composer-frontline.php ``` -Ustvarjanje novega projekta .[#toc-creating-new-project] -======================================================== +Ustvarjanje novega projekta +=========================== -Nov projekt Nette lahko ustvarite s preprostim ukazom: +Nov projekt na Nette ustvarite s pomočjo enega samega ukaza: ```shell -composer create-project nette/web-project name-of-the-project +composer create-project nette/web-project ime-projekta ``` -Namesto `name-of-the-project` morate navesti ime imenika za vaš projekt in izvesti ukaz. Composer bo iz GitHuba pobral skladišče `nette/web-project`, ki že vsebuje datoteko `composer.json`, in takoj zatem namestil samo ogrodje Nette. Edina stvar, ki vam preostane, je, da [preverite dovoljenja za pisanje |nette:troubleshooting#setting-directory-permissions] v imenikih `temp/` in `log/`, in že ste pripravljeni za delo. +Kot `ime-projekta` vstavite ime mape za svoj projekt in potrdite. Composer bo prenesel repozitorij `nette/web-project` z GitHuba, ki že vsebuje datoteko `composer.json`, in takoj zatem Nette Framework. Moralo bi že zadostovati samo [nastaviti dovoljenja |nette:troubleshooting#Nastavitev pravic map] za pisanje v mape `temp/` in `log/` in projekt bi moral oživeti. -Če veste, na kateri različici PHP bo projekt gostoval, [jo |#PHP Version] obvezno [nastavite |#PHP Version]. +Če veste, na kateri različici PHP bo projekt gostoval, ne pozabite [jo nastaviti |#Različica PHP]. -Različica PHP .[#toc-php-version] -================================= +Različica PHP +============= -Composer vedno namesti različice paketov, ki so združljive z različico PHP, ki jo trenutno uporabljate (oziroma z različico PHP, ki je uporabljena v ukazni vrstici, ko zaženete Composer). Ta različica verjetno ni enaka različici, ki jo uporablja vaš spletni gostitelj. Zato je zelo pomembno, da v datoteko `composer.json` dodate informacije o različici PHP na vašem gostovanju. Nato bodo nameščene samo različice paketov, ki so združljive z gostiteljem. +Composer vedno namešča tiste različice paketov, ki so združljive z različico PHP, ki jo pravkar uporabljate (bolje rečeno z različico PHP, uporabljeno v ukazni vrstici pri zagonu Composerja). Kar pa najverjetneje ni ista različica, kot jo uporablja vaše gostovanje. Zato je zelo pomembno, da si v datoteko `composer.json` dodate informacijo o različici PHP na gostovanju. Nato se bodo nameščale samo različice paketov, združljive z gostovanjem. -Na primer, če želite projekt nastaviti tako, da bo deloval na PHP 8.2.3, uporabite ukaz: +To, da bo projekt tekel na primer na PHP 8.2.3, nastavimo z ukazom: ```shell composer config platform.php 8.2.3 @@ -124,8 +124,7 @@ Tako se različica zapiše v datoteko `composer.json`: } ``` -Vendar je številka različice PHP navedena tudi na drugem mestu v datoteki, v razdelku `require`. Medtem ko prva številka določa različico, za katero bodo nameščeni paketi, druga številka pove, za katero različico je napisana sama aplikacija. -(Seveda ni smiselno, da bi se različici razlikovali, zato je dvojni vpis odveč.) To različico nastavite z ukazom: +Vendar se številka različice PHP navaja še na drugem mestu datoteke, in sicer v sekciji `require`. Medtem ko prva številka določa, za katero različico se bodo nameščali paketi, druga številka pravi, za katero različico je napisana sama aplikacija. In po njej na primer PhpStorm nastavlja *PHP language level*. (Seveda nima smisla, da bi se te različice razlikovale, zato je dvojni zapis nedomišljenost.) To različico nastavite z ukazom: ```shell composer require php 8.2.3 --no-update @@ -142,66 +141,72 @@ Ali neposredno v datoteki `composer.json`: ``` -Napačna poročila .[#toc-false-reports] -====================================== +Ignoriranje različice PHP +========================= -Pri nadgradnji paketov ali spreminjanju številk različic prihaja do konfliktov. En paket ima zahteve, ki so v nasprotju z drugim, in tako naprej. Vendar program Composer občasno izpiše lažna sporočila. Poroča o konfliktu, ki v resnici ne obstaja. V tem primeru pomaga, če izbrišete datoteko `composer.lock` in poskusite znova. +Paketi praviloma imajo navedeno tako najnižjo različico PHP, s katero so združljivi, kot tudi najvišjo, s katero so testirani. Če nameravate uporabljati še novejšo različico PHP, na primer zaradi testiranja, bo Composer zavrnil namestitev takšnega paketa. Rešitev je možnost `--ignore-platform-req=php+`, ki povzroči, da bo Composer ignoriral zgornje meje zahtevane različice PHP. -Če sporočilo o napaki vztraja, je mišljeno resno in iz njega morate razbrati, kaj in kako morate spremeniti. +Lažna sporočila +=============== -Packagist.org - Globalni repozitorij .[#toc-packagist-org-global-repository] -============================================================================ +Pri nadgradnji paketov ali spremembah številk različic se zgodi, da pride do konflikta. En paket ima zahteve, ki so v nasprotju z drugim in podobno. Composer pa včasih izpisuje lažna sporočila. Poroča o konfliktu, ki realno ne obstaja. V takem primeru pomaga izbrisati datoteko `composer.lock` in poskusiti znova. -[Packagist |https://packagist.org] je glavno skladišče paketov, v katerem Composer poskuša iskati pakete, če mu ni naročeno drugače. Tu lahko objavite tudi svoje pakete. +Če sporočilo o napaki vztraja, potem je mišljeno resno in je treba iz njega razbrati, kaj in kako urediti. -Kaj pa, če ne želimo osrednjega repozitorija .[#toc-what-if-we-don-t-want-the-central-repository] -------------------------------------------------------------------------------------------------- +Packagist.org - centralni repozitorij +===================================== -Če imamo v podjetju notranje aplikacije ali knjižnice, ki jih ne moremo javno gostiti na Packagistu, lahko za te projekte ustvarimo lastne repozitorije. +[Packagist |https://packagist.org] je glavni repozitorij, v katerem Composer poskuša iskati pakete, če mu ne povemo drugače. Tukaj lahko objavimo tudi lastne pakete. -Več o repozitorijih najdete v [uradni dokumentaciji |https://getcomposer.org/doc/05-repositories.md#repositories]. +Kaj če ne želimo uporabljati centralnega repozitorija? +------------------------------------------------------ -Samodejno nalaganje .[#toc-autoloading] -======================================= +Če imamo znotrajpodjetniške aplikacije, ki jih preprosto ne moremo gostovati javno, si zanje ustvarimo podjetniški repozitorij. -Ključna lastnost programa Composer je, da zagotavlja samodejno nalaganje za vse razrede, ki jih namesti, kar začnete z vključitvijo datoteke `vendor/autoload.php`. +Več na temo repozitorijev [v uradni dokumentaciji |https://getcomposer.org/doc/05-repositories.md#repositories]. -Vendar je mogoče Composer uporabiti tudi za nalaganje drugih razredov zunaj mape `vendor`. Prva možnost je, da Composer pregleda opredeljene mape in podmape, poišče vse razrede in jih vključi v samodejno nalaganje. To storite tako, da nastavite `autoload > classmap` v `composer.json`: + +Samodejno nalaganje +=================== + +Ključna lastnost Composerja je, da zagotavlja samodejno nalaganje za vse z njim nameščene razrede, ki ga zaženete z vključitvijo datoteke `vendor/autoload.php`. + +Vendar je mogoče uporabljati Composer tudi za nalaganje drugih razredov izven mape `vendor`. Prva možnost je, da pustite Composerju preiskati definirane mape in podmape, najti vse razrede in jih vključiti v samodejni nalagalnik. To dosežete z nastavitvijo `autoload > classmap` v `composer.json`: ```js { "autoload": { "classmap": [ - "src/", # includes the src/ folder and its subfolders + "src/", # vključi mapo src/ in njene podmape ] } } ``` -Nato je treba ob vsaki spremembi zagnati ukaz `composer dumpautoload` in pustiti, da se tabele za samodejno nalaganje regenerirajo. To je izredno neprijetno in veliko bolje je to nalogo zaupati programu [RobotLoader |robot-loader:], ki isto dejavnost opravi samodejno v ozadju in veliko hitreje. +Nato je treba ob vsaki spremembi zagnati ukaz `composer dumpautoload` in pustiti, da se tabele samodejnega nalaganja ponovno generirajo. To je izjemno neprijetno in veliko bolje je to nalogo zaupati [RobotLoaderju|robot-loader:], ki isto dejavnost izvaja samodejno v ozadju in veliko hitreje. -Druga možnost je, da sledite [priporočilu PSR-4 |https://www.php-fig.org/psr/psr-4/]. Preprosto povedano, gre za sistem, v katerem imenska območja in imena razredov ustrezajo imeniški strukturi in imenom datotek, tj. `App\Router\RouterFactory` se nahaja v datoteki `/path/to/App/Router/RouterFactory.php`. Primer konfiguracije: +Druga možnost je upoštevati [PSR-4|https://www.php-fig.org/psr/psr-4/]. Poenostavljeno rečeno gre za sistem, kjer imenski prostori in imena razredov ustrezajo strukturi map in imenom datotek, torej npr. `App\Core\RouterFactory` bo v datoteki `/path/to/App/Core/RouterFactory.php`. Primer konfiguracije: ```js { "autoload": { "psr-4": { - "App\\": "app/" # the App\ namespace is in the app/ directory + "App\\": "app/" # imenski prostor App\ je v mapi app/ } } } ``` -Kako natančno konfigurirati to vedenje, si oglejte v [dokumentaciji Composerja |https://getcomposer.org/doc/04-schema.md#psr-4]. +Kako natančno konfigurirati obnašanje, boste izvedeli v [dokumentaciji Composerja|https://getcomposer.org/doc/04-schema.md#psr-4]. -Testiranje novih različic .[#toc-testing-new-versions] -====================================================== +Testiranje novih različic +========================= -Želite preizkusiti novo razvojno različico paketa. Kako to storiti? Najprej v datoteko `composer.json` dodajte ta par možnosti, ki vam bo omogočil namestitev razvojnih različic paketov, vendar bo to storil le, če ni na voljo kombinacije stabilnih različic, ki izpolnjujejo zahteve: +Želite preizkusiti novo razvojno različico paketa. Kako to storiti? Najprej v datoteko `composer.json` dodajte ta par možnosti, ki dovoli nameščanje razvojnih različic paketov, vendar se k temu zateče samo v primeru, da ne obstaja nobena kombinacija stabilnih različic, ki bi ustrezala zahtevam: ```js { @@ -210,34 +215,33 @@ Testiranje novih različic .[#toc-testing-new-versions] } ``` -Priporočamo tudi, da izbrišete datoteko `composer.lock`, saj Composer včasih nerazumljivo zavrne namestitev, in to bo rešilo težavo. +Nato priporočamo izbris datoteke `composer.lock`, včasih namreč Composer nerazumljivo zavrne namestitev in to težavo reši. -Recimo, da je paket `nette/utils` in da je nova različica 4.0. Namestite ga z ukazom: +Recimo, da gre za paket `nette/utils` in nova različica ima številko 4.0. Namestite jo z ukazom: ```shell composer require nette/utils:4.0.x-dev ``` -Lahko pa namestite določeno različico, na primer 4.0.0-RC2: +Ali pa lahko namestite konkretno različico, na primer 4.0.0-RC2: ```shell composer require nette/utils:4.0.0-RC2 ``` -Če je drug paket odvisen od knjižnice in je zaklenjen na starejšo različico (npr. `^3.1`), je idealno posodobiti paket, da deluje z novo različico. -Če pa želite le zaobiti omejitev in prisiliti program Composer, da namesti razvojno različico in se pretvarja, da gre za starejšo različico (npr. 3.1.6), lahko uporabite ključno besedo `as`: +Ko pa je od knjižnice odvisen drug paket, ki je zaklenjen na starejšo različico (npr. `^3.1`), je idealno paket posodobiti, da bo deloval z novo različico. Če pa želite omejitev samo zaobiti in prisiliti Composer, da namesti razvojno različico in se pretvarja, da gre za starejšo različico (npr. 3.1.6), lahko uporabite ključno besedo `as`: ```shell composer require nette/utils "4.0.x-dev as 3.1.6" ``` -Klicanje ukazov .[#toc-calling-commands] -======================================== +Klicanje ukazov +=============== -Svoje ukaze in skripte po meri lahko kličete prek programa Composer, kot da bi bili izvirni ukazi programa Composer. Skriptam, ki se nahajajo v mapi `vendor/bin`, te mape ni treba navesti. +Prek Composerja lahko kličete lastne vnaprej pripravljene ukaze in skripte, kot da bi šlo za izvorne ukaze Composerja. Pri skriptih, ki se nahajajo v mapi `vendor/bin`, ni treba te mape navajati. -Kot primer definiramo skripto v datoteki `composer.json`, ki za izvajanje testov uporablja program [Nette Tester |tester:]: +Kot primer si definiramo v datoteki `composer.json` skript, ki s pomočjo [Nette Testerja|tester:] zažene teste: ```js { @@ -247,19 +251,19 @@ Kot primer definiramo skripto v datoteki `composer.json`, ki za izvajanje testov } ``` -Teste nato zaženemo s spletno stranjo `composer tester`. Ukaz lahko prikličemo, tudi če nismo v korenskem imeniku projekta, temveč v podimeniku. +Teste nato zaženemo s pomočjo `composer tester`. Ukaz lahko pokličemo tudi v primeru, da nismo v korenski mapi projekta, ampak v katerem od poddirektorijev. -Pošljite Zahvala .[#toc-send-thanks] -==================================== +Pošljite zahvalo +================ -Pokazali vam bomo trik, ki bo razveselil avtorje odprte kode. Knjižnicam, ki jih uporablja vaš projekt, lahko na GitHubu preprosto dodate zvezdico. Samo namestite knjižnico `symfony/thanks`: +Pokazali vam bomo trik, s katerim boste razveselili avtorje odprte kode. Na preprost način boste na GitHubu dali zvezdico knjižnicam, ki jih vaš projekt uporablja. Dovolj je namestiti knjižnico `symfony/thanks`: ```shell composer global require symfony/thanks ``` -in nato zaženite: +In nato zagnati: ```shell composer thanks @@ -268,13 +272,11 @@ composer thanks Poskusite! -Konfiguracija .[#toc-configuration] -=================================== +Konfiguracija +============= -Composer je tesno povezan z orodjem za nadzor različic [Git |https://git-scm.com]. Če ne uporabljate orodja Git, je treba to sporočiti programu Composer: +Composer je tesno povezan z orodjem za verzioniranje [Git |https://git-scm.com]. Če ga nimate nameščenega, je treba Composerju povedati, naj ga ne uporablja: ```shell composer -g config preferred-install dist ``` - -{{sitename: Best Practices}} diff --git a/best-practices/sl/creating-editing-form.texy b/best-practices/sl/creating-editing-form.texy index 87b037f312..650812cb3a 100644 --- a/best-practices/sl/creating-editing-form.texy +++ b/best-practices/sl/creating-editing-form.texy @@ -2,15 +2,15 @@ Obrazec za ustvarjanje in urejanje zapisa ***************************************** .[perex] -Kako pravilno izvajati dodajanje in urejanje zapisa v Nette, pri čemer se za oboje uporablja isti obrazec? +Kako v Nette pravilno implementirati dodajanje in urejanje zapisa, pri čemer za oboje uporabimo isti obrazec? -V številnih primerih sta obrazca za dodajanje in urejanje zapisa enaka, razlikujeta se le po oznaki na gumbu. Prikazali bomo primere preprostih predstavnikov, kjer najprej uporabimo obrazec za dodajanje zapisa, nato za njegovo urejanje in na koncu obe rešitvi združimo. +V mnogih primerih so obrazci za dodajanje in urejanje zapisa enaki, razlikujejo se morda le po napisu na gumbu. Prikazali bomo primere preprostih presenterjev, kjer bomo obrazec najprej uporabili za dodajanje zapisa, nato za urejanje in na koncu obe rešitvi združili. -Dodajanje zapisa .[#toc-adding-a-record] ----------------------------------------- +Dodajanje zapisa +---------------- -Primer predstavnika, ki se uporablja za dodajanje zapisa. Dejansko delo s podatkovno bazo bomo prepustili razredu `Facade`, katerega koda za primer ni pomembna. +Primer presenterja, ki služi za dodajanje zapisa. Samo delo s podatkovno bazo bomo prepustili razredu `Facade`, katerega koda za prikaz ni bistvena. ```php @@ -27,7 +27,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $form = new Form; - // ... dodajanje polj obrazca ... + // ... dodamo polja obrazca ... $form->onSuccess[] = [$this, 'recordFormSucceeded']; return $form; @@ -35,8 +35,8 @@ class RecordPresenter extends Nette\Application\UI\Presenter public function recordFormSucceeded(Form $form, array $data): void { - $this->facade->add($data); // dodajanje zapisa v zbirko podatkov - $this->flashMessage('Successfully added'); + $this->facade->add($data); // dodajanje zapisa v podatkovno bazo + $this->flashMessage('Uspešno dodano'); $this->redirect('...'); } @@ -48,10 +48,10 @@ class RecordPresenter extends Nette\Application\UI\Presenter ``` -Urejanje zapisa .[#toc-editing-a-record] ----------------------------------------- +Urejanje zapisa +--------------- -Zdaj si oglejmo, kako je videti predstavitev, ki se uporablja za urejanje zapisov: +Zdaj si poglejmo, kako bi izgledal presenter, ki služi za urejanje zapisa: ```php @@ -70,10 +70,10 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $record = $this->facade->get($id); if ( - !$record // preverjanje obstoja zapisa. - || !$this->facade->isEditAllowed(/*...*/) // preverjanje dovoljenj. + !$record // preverjanje obstoja zapisa + || !$this->facade->isEditAllowed(/*...*/) // preverjanje dovoljenj ) { - $this->error(); // Napaka 404 + $this->error(); // napaka 404 } $this->record = $record; @@ -81,32 +81,32 @@ class RecordPresenter extends Nette\Application\UI\Presenter protected function createComponentRecordForm(): Form { - // preverite, ali je dejanje "uredi". + // preverimo, da je akcija 'edit' if ($this->getAction() !== 'edit') { $this->error(); } $form = new Form; - // ... dodajte polja obrazca ... + // ... dodamo polja obrazca ... - $form->setDefaults($this->record); // nastavite privzete vrednosti + $form->setDefaults($this->record); // nastavitev privzetih vrednosti $form->onSuccess[] = [$this, 'recordFormSucceeded']; return $form; } public function recordFormSucceeded(Form $form, array $data): void { - $this->facade->update($this->record->id, $data); // posodobi zapis - $this->flashMessage('Successfully updated'); + $this->facade->update($this->record->id, $data); // posodobitev zapisa + $this->flashMessage('Uspešno posodobljeno'); $this->redirect('...'); } } ``` -V metodi *action*, ki se sproži na začetku [življenjskega cikla predstavnika |application:presenters#Life Cycle of Presenter], preverimo obstoj zapisa in uporabnikovo dovoljenje za njegovo urejanje. +V metodi *action*, ki se zažene takoj na začetku [življenjskega cikla presenterja |application:presenters#Življenjski cikel presenterja], preverimo obstoj zapisa in dovoljenje uporabnika za urejanje. -Zapis shranimo v lastnost `$record`, tako da je na voljo v metodi `createComponentRecordForm()` za nastavitev privzetih vrednosti in `recordFormSucceeded()` za ID. Alternativna rešitev bi bila, da bi privzete vrednosti nastavili neposredno v metodi `actionEdit()`, vrednost ID, ki je del naslova URL, pa bi pridobili z uporabo metode `getParameter('id')`: +Zapis shranimo v lastnost `$record`, da ga imamo na voljo v metodi `createComponentRecordForm()` za nastavitev privzetih vrednosti in v `recordFormSucceeded()` zaradi ID-ja. Alternativna rešitev bi bila nastavitev privzetih vrednosti neposredno v `actionEdit()` in pridobitev vrednosti ID, ki je del URL-ja, s pomočjo `getParameter('id')`: ```php @@ -119,7 +119,7 @@ Zapis shranimo v lastnost `$record`, tako da je na voljo v metodi `createCompone $this->error(); } - // nastaviti privzete vrednosti obrazca. + // nastavitev privzetih vrednosti obrazca $this->getComponent('recordForm') ->setDefaults($record); } @@ -133,13 +133,13 @@ Zapis shranimo v lastnost `$record`, tako da je na voljo v metodi `createCompone } ``` -Vendar, in to bi morala biti **najpomembnejša ugotovitev iz celotne kode**, moramo pri ustvarjanju obrazca zagotoviti, da je dejanje res `edit`. Kajti v nasprotnem primeru se preverjanje v metodi `actionEdit()` sploh ne bi zgodilo! +Vendar pa, in to bi moralo biti **najpomembnejše spoznanje celotne kode**, se moramo pri ustvarjanju obrazca prepričati, da je akcija resnično `edit`. Ker sicer preverjanje v metodi `actionEdit()` sploh ne bi potekalo! -Isti obrazec za dodajanje in urejanje .[#toc-same-form-for-adding-and-editing] ------------------------------------------------------------------------------- +Isti obrazec za dodajanje in urejanje +------------------------------------- -Zdaj bomo oba predstavnika združili v enega. Lahko razlikujemo, katero dejanje je vključeno v metodo `createComponentRecordForm()`, in ustrezno konfiguriramo obrazec ali pa to prepustimo neposredno metodam dejanj in se znebimo pogoja: +In zdaj oba presenterja združimo v enega. Ali bi lahko v metodi `createComponentRecordForm()` razlikovali, za katero akcijo gre, in glede na to konfigurirali obrazec, ali pa to prepustimo neposredno action-metodam in se znebimo pogoja: ```php @@ -160,35 +160,35 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $record = $this->facade->get($id); if ( - !$record // preverjanje obstoja zapisa. - || !$this->facade->isEditAllowed(/*...*/) // preverjanje dovoljenj. + !$record // preverjanje obstoja zapisa + || !$this->facade->isEditAllowed(/*...*/) // preverjanje dovoljenj ) { - $this->error(); // Napaka 404 + $this->error(); // napaka 404 } $form = $this->getComponent('recordForm'); - $form->setDefaults($record); // nastavite privzete nastavitve + $form->setDefaults($record); // nastavitev privzetih vrednosti $form->onSuccess[] = [$this, 'editingFormSucceeded']; } protected function createComponentRecordForm(): Form { - // preveri, ali je dejanje "dodaj" ali "uredi". + // preverimo, da je akcija 'add' ali 'edit' if (!in_array($this->getAction(), ['add', 'edit'])) { $this->error(); } $form = new Form; - // ... dodajte polja obrazca ... + // ... dodamo polja obrazca ... return $form; } public function addingFormSucceeded(Form $form, array $data): void { - $this->facade->add($data); // dodajanje zapisa v zbirko podatkov - $this->flashMessage('Successfully added'); + $this->facade->add($data); // dodajanje zapisa v podatkovno bazo + $this->flashMessage('Uspešno dodano'); $this->redirect('...'); } @@ -196,11 +196,10 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $id = (int) $this->getParameter('id'); $this->facade->update($id, $data); // posodobitev zapisa - $this->flashMessage('Successfully updated'); + $this->flashMessage('Uspešno posodobljeno'); $this->redirect('...'); } } ``` {{priority: -1}} -{{sitename: Best Practices}} diff --git a/best-practices/sl/dynamic-snippets.texy b/best-practices/sl/dynamic-snippets.texy index dcd25eb0c7..1f5704cd9c 100644 --- a/best-practices/sl/dynamic-snippets.texy +++ b/best-practices/sl/dynamic-snippets.texy @@ -1,7 +1,7 @@ -Dinamični utrinki -***************** +Dinamični snippeti +****************** -Pri razvoju aplikacij se pogosto pojavi potreba po izvajanju operacij AJAX, na primer v posameznih vrsticah tabele ali elementih seznama. Kot primer lahko izberemo seznam člankov, ki prijavljenemu uporabniku omogoča, da za vsakega od njih izbere oceno "všeč/neprijetno". Koda predstavitvenega programa in ustrezne predloge brez AJAX bo videti nekako takole (navajam najpomembnejše utrinke, koda predpostavlja obstoj storitve za označevanje ocen in pridobivanje zbirke člankov - konkretna izvedba za namene tega vodnika ni pomembna): +Precej pogosto se pri razvoju aplikacij pojavi potreba po izvajanju AJAX operacij, na primer nad posameznimi vrsticami tabele ali elementi seznama. Za primer lahko izberemo izpis člankov, pri čemer pri vsakem od njih prijavljenemu uporabniku omogočimo izbiro ocene "všeč mi je/ni mi všeč". Koda presenterja in ustrezne predloge brez AJAX-a bo izgledala približno takole (navajam najpomembnejše odseke, koda predvideva obstoj storitve za označevanje ocen in pridobivanje zbirke člankov - konkretna implementacija za namene tega navodila ni pomembna): ```php public function handleLike(int $articleId): void @@ -24,26 +24,26 @@ Predloga: <h2>{$article->title}</h2> <div class="content">{$article->content}</div> {if !$article->liked} - <a n:href="like! $article->id" class=ajax>I like it</a> + <a n:href="like! $article->id" class=ajax>všeč mi je</a> {else} - <a n:href="unlike! $article->id" class=ajax>I don't like it anymore</a> + <a n:href="unlike! $article->id" class=ajax>ni mi več všeč</a> {/if} </article> ``` -Ajaksizacija .[#toc-ajaxization] -================================ +Ajaxizacija +=========== -V to preprosto aplikacijo zdaj vnesimo AJAX. Spreminjanje ocene članka ni dovolj pomembno, da bi zahtevalo zahtevo HTTP s preusmeritvijo, zato bi bilo idealno, če bi to opravili z AJAXom v ozadju. Uporabili bomo [skript za obdelavo iz dodatkov |https://componette.org/vojtech-dobes/nette.ajax.js/] z običajno konvencijo, da imajo povezave AJAX razred CSS `ajax`. +Zdaj pa opremimo to preprosto aplikacijo z AJAX-om. Sprememba ocene članka ni tako pomembna, da bi moralo priti do preusmeritve, zato bi idealno morala potekati z AJAX-om v ozadju. Uporabili bomo [pomožni skript iz dodatkov |application:ajax#Naja] z običajno konvencijo, da imajo AJAX povezave CSS razred `ajax`. -Vendar pa, kako to storiti konkretno? Nette ponuja dva načina: način z dinamičnimi utrinki in način s komponentami. Oba načina imata svoje prednosti in slabosti, zato ju bomo prikazali enega za drugim. +Vendar kako to storiti konkretno? Nette ponuja 2 poti: pot t.i. dinamičnih snippetov in pot komponent. Obe imata svoje prednosti in slabosti, zato si ju bomo ogledali eno za drugo. -Način dinamičnih utrinkov .[#toc-the-dynamic-snippets-way] -========================================================== +Pot dinamičnih snippetov +======================== -V terminologiji Latte je dinamični izsek poseben primer uporabe oznake `{snippet}`, pri katerem se v imenu izseka uporabi spremenljivka. Takšnega odlomka ne moremo najti kar kjerkoli v predlogi - oviti ga mora statični odlomek, tj. običajni odlomek, ali pa mora biti znotraj `{snippetArea}`. Našo predlogo lahko spremenimo na naslednji način. +Dinamični snippet v terminologiji Latte pomeni specifičen primer uporabe značke `{snippet}`, kjer je v imenu snippeta uporabljena spremenljivka. Takšen snippet se v predlogi ne more nahajati kjerkoli - mora biti ovit s statičnim snippetom, tj. običajnim, ali znotraj `{snippetArea}`. Našo predlogo bi lahko prilagodili na naslednji način. ```latte @@ -53,18 +53,18 @@ V terminologiji Latte je dinamični izsek poseben primer uporabe oznake `{snippe <div class="content">{$article->content}</div> {snippet article-{$article->id}} {if !$article->liked} - <a n:href="like! $article->id" class=ajax>I like it</a> + <a n:href="like! $article->id" class=ajax>všeč mi je</a> {else} - <a n:href="unlike! $article->id" class=ajax>I don't like it anymore</a> + <a n:href="unlike! $article->id" class=ajax>ni mi več všeč</a> {/if} {/snippet} </article> {/snippet} ``` -Vsak članek zdaj opredeljuje en sam izsek, ki ima v naslovu ID članka. Vsi ti odlomki so nato združeni v en sam odlomek, ki se imenuje `articlesContainer`. Če ta ovitek izpustimo, nas bo Latte opozoril z izjemo. +Vsak članek zdaj definira en snippet, ki ima v imenu ID članka. Vsi ti snippeti so nato skupaj zaviti v en snippet z imenom `articlesContainer`. Če bi ta ovojni snippet izpustili, bi nas Latte na to opozoril z izjemo. -Vse, kar nam preostane, je, da predstavniku dodamo ponovno risanje - samo ponovno narišemo statični ovitek. +Ostane nam še, da v presenter dodamo ponovno izrisovanje - dovolj je ponovno izrisati statični ovoj. ```php public function handleLike(int $articleId): void @@ -79,11 +79,11 @@ public function handleLike(int $articleId): void } ``` -Na enak način spremenite sestrsko metodo `handleUnlike()` in AJAX je pripravljen in deluje! +Podobno prilagodimo tudi sestrsko metodo `handleUnlike()`, in AJAX deluje! -Vendar ima rešitev eno slabost. Če se podrobneje poglobimo v delovanje zahteve AJAX, ugotovimo, da čeprav je aplikacija na videz videti učinkovita (vrne le en odlomek za določen članek), dejansko vse odlomke prikaže v strežniku. Želene utrinke je umestila v naš koristni tovor, druge pa zavrgla (tako jih je povsem po nepotrebnem pridobila tudi iz podatkovne zbirke). +Rešitev pa ima eno senčno stran. Če bi podrobneje preučili, kako poteka AJAX zahteva, bi ugotovili, da čeprav se aplikacija navzven zdi varčna (vrne samo en sam snippet za določen članek), je v resnici na strežniku izrisala vse snippete. Želeni snippet nam je postavila v payload, ostale pa zavrgla (popolnoma nepotrebno jih je torej tudi pridobila iz podatkovne baze). -Da bi optimizirali ta postopek, bomo morali izvesti dejanje, pri katerem zbirko `$articles` posredujemo predlogi (recimo v metodi `renderDefault()` ). Pri tem bomo izkoristili dejstvo, da obdelava signalov poteka pred `render<Something>` metodami: +Da bi ta proces optimizirali, bomo morali poseči tja, kjer v predlogo posredujemo zbirko `$articles` (recimo v metodi `renderDefault()`). Izkoristili bomo dejstvo, da obdelava signalov poteka pred metodami `render<Something>`: ```php public function handleLike(int $articleId): void @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connection->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,18 +101,18 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connection->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` -Zdaj, ko se signal obdela, se namesto zbirke z vsemi članki predlogi posreduje le polje z enim samim členom - tistim, ki ga želimo prikazati in poslati v breme brskalniku. Tako bo `{foreach}` izveden samo enkrat in ne bodo prikazani nobeni dodatni odlomki. +Zdaj se pri obdelavi signala v predlogo namesto zbirke z vsemi članki posreduje le polje z enim samim člankom - tistim, ki ga želimo izrisati in poslati v payloadu v brskalnik. `{foreach}` se torej izvede samo enkrat in nobeni dodatni snippeti se ne izrišejo. -Način komponente .[#toc-component-way] -====================================== +Pot komponent +============= -Popolnoma drugačna rešitev uporablja drugačen pristop za preprečevanje dinamičnih utrinkov. Trik je v tem, da vso logiko prenesemo v ločeno komponento - odslej nimamo predstavnika, ki bi skrbel za vnos ocene, temveč namensko komponento `LikeControl`. Razred bo videti takole (poleg tega bo vseboval tudi metode `render`, `handleUnlike` itd.): +Popolnoma drugačen način reševanja se izogne dinamičnim snippetom. Trik je v prenosu celotne logike v posebno komponento - za vnos ocen ne bo več skrbel presenter, temveč namenska `LikeControl`. Razred bo izgledal takole (poleg tega bo vseboval tudi metode `render`, `handleUnlike` itd.): ```php class LikeControl extends Nette\Application\UI\Control @@ -139,26 +139,26 @@ Predloga komponente: ```latte {snippet} {if !$article->liked} - <a n:href="like!" class=ajax>I like it</a> + <a n:href="like!" class=ajax>všeč mi je</a> {else} - <a n:href="unlike!" class=ajax>I don't like it anymore</a> + <a n:href="unlike!" class=ajax>ni mi več všeč</a> {/if} {/snippet} ``` -Seveda bomo spremenili predlogo pogleda in morali bomo dodati tovarno za predstavljanje. Ker bomo komponento ustvarili tolikokrat, kolikorkrat bomo prejeli člankov iz podatkovne zbirke, bomo za njeno "pomnoževanje" uporabili razred [application:Multiplier]. +Seveda se nam bo spremenila predloga pogleda (view) in v presenter bomo morali dodati tovarno. Ker bomo komponento ustvarili tolikokrat, kolikor člankov pridobimo iz podatkovne baze, bomo za njeno "razmnoževanje" uporabili razred [Multiplier |application:multiplier]. ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); } ``` -Predloga pogleda je tako zmanjšana na najmanjšo potrebno mero (in popolnoma brez sličic!): +Predloga pogleda (view) se zmanjša na nujni minimum (in je popolnoma brez snippetov!): ```latte <article n:foreach="$articles as $article"> @@ -168,7 +168,6 @@ Predloga pogleda je tako zmanjšana na najmanjšo potrebno mero (in popolnoma br </article> ``` -Skoraj smo končali: aplikacija bo zdaj delovala v načinu AJAX. Tudi tu moramo aplikacijo optimizirati, saj bo zaradi uporabe podatkovne zbirke Nette obdelava signalov po nepotrebnem naložila vse članke iz zbirke namesto enega. Prednost pa je v tem, da ne bo prišlo do izrisovanja, saj se dejansko izriše le naša komponenta. +Skoraj smo končali: aplikacija bo zdaj delovala AJAX-ovsko. Tudi tukaj nas čaka optimizacija aplikacije, saj se zaradi uporabe Nette Database pri obdelavi signala nepotrebno naložijo vsi članki iz podatkovne baze namesto enega. Prednost pa je, da ne pride do njihovega izrisovanja, ker se dejansko izriše samo naša komponenta. {{priority: -1}} -{{sitename: Best Practices}} diff --git a/best-practices/sl/editors-and-tools.texy b/best-practices/sl/editors-and-tools.texy index b0cb974942..9ac5d2ff3c 100644 --- a/best-practices/sl/editors-and-tools.texy +++ b/best-practices/sl/editors-and-tools.texy @@ -1,40 +1,40 @@ -Uredniki in orodja -****************** +Urejevalniki & orodja +********************* .[perex] -Lahko ste spreten programer, a le z dobrimi orodji boste postali mojster. V tem poglavju boste našli nasvete o pomembnih orodjih, urejevalnikih in vtičnikih. +Lahko ste spreten programer, vendar šele z dobrimi orodji postanete mojster. V tem poglavju boste našli nasvete za pomembna orodja, urejevalnike in vtičnike. -Urejevalnik IDE .[#toc-ide-editor] -================================== +IDE urejevalnik +=============== -Priporočamo, da za razvoj uporabljate celovit IDE, kot so PhpStorm, NetBeans, VS Code, in ne le urejevalnik besedila s podporo za PHP. Razlika je res bistvena. Ni razloga, da bi se zadovoljili s klasičnim urejevalnikom z označevanjem sintakse, saj ne dosega zmogljivosti IDE z natančnim predlaganjem kode, ki lahko kodo refaktorizira in še več. Nekateri IDE so plačljivi, drugi so brezplačni. +Vsekakor priporočamo, da za razvoj uporabljate polnopravno IDE, kot so na primer PhpStorm, NetBeans, VS Code, in ne le urejevalnika besedil s podporo za PHP. Razlika je resnično bistvena. Ni razloga, da bi se zadovoljili zgolj z urejevalnikom, ki sicer zna obarvati sintakso, vendar ne dosega zmožnosti vrhunskega IDE-ja, ki natančno predlaga, preverja napake, zna refaktorirati kodo in še veliko več. Nekateri IDE-ji so plačljivi, drugi celo brezplačni. -**NetBeans IDE** ima vgrajeno podporo za Nette, Latte in NEON. +**NetBeans IDE** ima podporo za Nette, Latte in NEON že vgrajeno. -**PhpStorm**: namestite te vtičnike v `Settings > Plugins > Marketplace`: -- Pomočniki ogrodja Nette +**PhpStorm**: namestite te vtičnike v `Settings > Plugins > Marketplace` +- Nette framework helpers - Latte -- Podpora za NEON +- NEON support - Nette Tester -**VS koda**: poiščite vtičnik "Nette Latte + Neon" na tržnici. +**VS Code**: v tržnici (marketplace) poiščite vtičnik "Nette Latte + Neon". -Prav tako povežite Tracy z urejevalnikom. Ko se prikaže stran z napako, lahko kliknete na imena datotek in te se bodo odprle v urejevalniku s kazalcem na ustrezni vrstici. Naučite se, [kako konfigurirati sistem |tracy:open-files-in-ide]. +Povežite tudi Tracy z urejevalnikom. Pri prikazu strani z napako bo potem mogoče klikniti na imena datotek, ki se bodo odprla v urejevalniku s kazalcem na ustrezni vrstici. Preberite, [kako konfigurirati sistem|tracy:open-files-in-ide]. -PHPStan .[#toc-phpstan] -======================= +PHPStan +======= -PHPStan je orodje, ki odkriva logične napake v vaši kodi, preden jo zaženete. +PHPStan je orodje, ki odkrije logične napake v kodi, preden jo zaženete. -Namestite ga prek programa Composer: +Namestimo ga s pomočjo Composerja: ```shell composer require --dev phpstan/phpstan-nette ``` -Ustvarite konfiguracijsko datoteko `phpstan.neon` v projektu: +V projektu ustvarimo konfiguracijsko datoteko `phpstan.neon`: ```neon includes: @@ -47,40 +47,38 @@ parameters: level: 5 ``` -Nato naj analizira razrede v mapi `app/`: +Nato pustimo, da analizira razrede v mapi `app/`: ```shell vendor/bin/phpstan analyse app ``` -Izčrpno dokumentacijo lahko najdete neposredno na spletnem mestu [PHPStan |https://phpstan.org]. +Izčrpno dokumentacijo najdete neposredno na [straneh PHPStan |https://phpstan.org]. -Preverjanje kode .[#toc-code-checker] -===================================== +Code Checker +============ -[Code Checker |code-checker:] preveri in po možnosti popravi nekatere formalne napake v vaši izvorni kodi. +[Code Checker|code-checker:] preveri in po potrebi popravi nekatere formalne napake v vaši izvorni kodi: -- odstrani [BOM |nette:glossary#bom] -- preveri veljavnost predlog [Latte |latte:] -- preveri veljavnost datotek `.neon`, `.php` in `.json` -- preveri [kontrolne znake |nette:glossary#control characters] -- preveri, ali je datoteka kodirana v UTF-8 -- preveri napačno zapisano `/* @annotations */` (manjka druga zvezdica) -- odstrani končne oznake PHP `?>` v datotekah PHP -- na koncu datoteke odstrani zaključni beli prostor in nepotrebne prazne vrstice -- normalizira konce vrstic na privzete sistemske (s parametrom `-l` ) +- odstranjuje [BOM |nette:glossary#BOM] +- preverja veljavnost predlog [Latte |latte:] +- preverja veljavnost datotek `.neon`, `.php` in `.json` +- preverja pojav [kontrolnih znakov |nette:glossary#Kontrolni znaki] +- preverja, ali je datoteka kodirana v UTF-8 +- preverja napačno zapisane `/* @anotacije */` (manjka zvezdica) +- odstranjuje zaključno oznako `?>` pri PHP datotekah +- odstranjuje presledke na desni strani in nepotrebne vrstice na koncu datoteke +- normalizira ločila vrstic na sistemska (če navedete možnost `-l`) -Composer .[#toc-composer] -========================= +Composer +======== -[Composer |Composer] je orodje za upravljanje odvisnosti v PHP. Omogoča nam, da prijavimo odvisnosti knjižnic, in jih namesto nas namesti v naš projekt. +[Composer |best-practices:composer] je orodje za upravljanje odvisnosti v PHP. Omogoča nam deklariranje poljubno zapletenih odvisnosti posameznih knjižnic in jih nato za nas namesti v naš projekt. -Preverjanje zahtev .[#toc-requirements-checker] -=============================================== +Requirements Checker +==================== -To je bilo orodje, ki je preverjalo okolje, v katerem deluje strežnik, in obveščalo o tem, ali (in v kolikšni meri) je mogoče uporabiti ogrodje. Trenutno se lahko Nette uporablja v vsakem strežniku, ki ima najmanjšo zahtevano različico PHP. - -{{sitename: Best Practices}} +To je bilo orodje, ki je testiralo izvajalno okolje strežnika in obveščalo, ali (in v kolikšni meri) je mogoče ogrodje uporabljati. Trenutno je Nette mogoče uporabljati na vsakem strežniku, ki ima minimalno zahtevano različico PHP. diff --git a/best-practices/sl/form-reuse.texy b/best-practices/sl/form-reuse.texy index 398e4490b0..4a388acd07 100644 --- a/best-practices/sl/form-reuse.texy +++ b/best-practices/sl/form-reuse.texy @@ -2,15 +2,15 @@ Ponovna uporaba obrazcev na več mestih ************************************** .[perex] -V Nette imate več možnosti za ponovno uporabo istega obrazca na več mestih brez podvajanja kode. V tem članku bomo pregledali različne rešitve, vključno s tistimi, ki se jim morate izogniti. +V Nette imate na voljo več možnosti, kako uporabiti isti obrazec na več mestih in ne podvajati kode. V tem članku si bomo ogledali različne rešitve, vključno s tistimi, ki se jim morate izogibati. -Tovarna obrazcev .[#toc-form-factory] -===================================== +Tovarna obrazcev +================ -Osnovni pristop k uporabi iste komponente na več mestih je ustvarjanje metode ali razreda, ki generira komponento, in nato klicanje te metode na različnih mestih v aplikaciji. Takšna metoda ali razred se imenuje *factory*. Ne zamenjujte z načrtovalskim vzorcem *tovarniška metoda*, ki opisuje poseben način uporabe tovarn in ni povezan s to temo. +Eden od osnovnih pristopov k uporabi iste komponente na več mestih je ustvarjanje metode ali razreda, ki to komponento generira, in nato klicanje te metode na različnih mestih aplikacije. Takšni metodi ali razredu pravimo *tovarna*. Prosimo, ne zamenjujte z oblikovalskim vzorcem *factory method*, ki opisuje specifičen način uporabe tovarn in ni povezan s to temo. -Kot primer ustvarimo tovarno, ki bo ustvarila obrazec za urejanje: +Kot primer bomo ustvarili tovarno, ki bo sestavljala urejevalni obrazec: ```php use Nette\Application\UI\Form; @@ -20,22 +20,22 @@ class FormFactory public function createEditForm(): Form { $form = new Form; - $form->addText('title', 'Title:'); - // tukaj so dodana dodatna polja obrazca - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Naslov:'); + // tukaj se dodajajo dodatna polja obrazca + $form->addSubmit('send', 'Pošlji'); return $form; } } ``` -Zdaj lahko to tovarno uporabite na različnih mestih v aplikaciji, na primer v predstavitvah ali komponentah. To storimo tako, da [jo zahtevamo kot odvisnost |dependency-injection:passing-dependencies]. Najprej bomo razred zapisali v konfiguracijsko datoteko: +Zdaj lahko to tovarno uporabite na različnih mestih v vaši aplikaciji, na primer v presenterjih ali komponentah. In sicer tako, da jo [zahtevamo kot odvisnost|dependency-injection:passing-dependencies]. Najprej torej razred zapišemo v konfiguracijsko datoteko: ```neon services: - FormFactory ``` -Nato ga bomo uporabili v predstavitvi: +Nato jo uporabimo v presenterju: ```php @@ -57,7 +57,7 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Tovarno obrazcev lahko razširite z dodatnimi metodami in tako ustvarite druge vrste obrazcev, ki ustrezajo vaši aplikaciji. Seveda lahko dodate tudi metodo, ki ustvari osnovni obrazec brez elementov, ki ga bodo uporabljale druge metode: +Tovarno obrazcev lahko razširite z dodatnimi metodami za ustvarjanje drugih vrst obrazcev glede na potrebe vaše aplikacije. In seveda lahko dodamo tudi metodo, ki ustvari osnovni obrazec brez elementov, in to bodo uporabljale druge metode: ```php class FormFactory @@ -71,21 +71,21 @@ class FormFactory public function createEditForm(): Form { $form = $this->createForm(); - $form->addText('title', 'Title:'); - // tukaj so dodana dodatna polja obrazca - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Naslov:'); + // tukaj se dodajajo dodatna polja obrazca + $form->addSubmit('send', 'Pošlji'); return $form; } } ``` -Metoda `createForm()` še ne počne ničesar uporabnega, vendar se bo to hitro spremenilo. +Metoda `createForm()` zaenkrat ne počne ničesar uporabnega, vendar se bo to hitro spremenilo. -Tovarniške odvisnosti .[#toc-factory-dependencies] -================================================== +Odvisnosti tovarne +================== -Sčasoma se bo izkazalo, da potrebujemo večjezične obrazce. To pomeni, da moramo za vse obrazce vzpostaviti [prevajalnik |forms:rendering#Translating]. V ta namen spremenimo razred `FormFactory` tako, da v konstruktorju sprejme objekt `Translator` kot odvisnost in ga posreduje obrazcu: +Sčasoma se bo izkazalo, da potrebujemo, da so obrazci večjezični. To pomeni, da moramo vsem obrazcem nastaviti t.i. [prevajalnik |forms:rendering#Prevajanje]. V ta namen bomo prilagodili razred `FormFactory`, da bo sprejemal objekt `Translator` kot odvisnost v konstruktorju, in ga posredovali obrazcu: ```php use Nette\Localization\Translator; @@ -104,18 +104,17 @@ class FormFactory return $form; } - //... + // ... } ``` -Ker metodo `createForm()` kličejo tudi druge metode, ki ustvarjajo določene obrazce, moramo prevajalnik nastaviti le v tej metodi. In končali smo. Ni treba spreminjati kode predstavnika ali komponente, kar je odlično. +Ker metodo `createForm()` kličejo tudi druge metode, ki ustvarjajo specifične obrazce, je dovolj, da prevajalnik nastavimo samo v njej. In končali smo. Ni treba spreminjati kode nobenega presenterja ali komponente, kar je odlično. -Več tovarniških razredov .[#toc-more-factory-classes] -===================================================== +Več tovarniških razredov +======================== -Ustvarite lahko tudi več razredov za vsak obrazec, ki ga želite uporabiti v svoji aplikaciji. -Ta pristop lahko poveča berljivost kode in olajša upravljanje obrazcev. Pustite prvotni `FormFactory` za ustvarjanje samo čistega obrazca z osnovno konfiguracijo (na primer s podporo za prevajanje) in ustvarite novo tovarno `EditFormFactory` za obrazec za urejanje. +Alternativno lahko ustvarite več razredov za vsak obrazec, ki ga želite uporabiti v svoji aplikaciji. Ta pristop lahko poveča berljivost kode in olajša upravljanje obrazcev. Prvotno `FormFactory` bomo pustili, da ustvarja samo čist obrazec z osnovno konfiguracijo (na primer s podporo za prevode), za urejevalni obrazec pa bomo ustvarili novo tovarno `EditFormFactory`. ```php class FormFactory @@ -134,7 +133,7 @@ class FormFactory } -// ✅ uporaba sestave +// ✅ uporaba kompozicije class EditFormFactory { public function __construct( @@ -145,40 +144,39 @@ class EditFormFactory public function create(): Form { $form = $this->formFactory->create(); - // tu so dodana dodatna polja obrazca - $form->addSubmit('send', 'Save'); + // tukaj se dodajajo dodatna polja obrazca + $form->addSubmit('send', 'Pošlji'); return $form; } } ``` -Zelo pomembno je, da se vezava med razredoma `FormFactory` in `EditFormFactory` izvaja s kompozicijo in ne z dedovanjem objektov: +Zelo pomembno je, da je povezava med razredoma `FormFactory` in `EditFormFactory` realizirana s [kompozicijo |nette:introduction-to-object-oriented-programming#Kompozicija], ne pa z [objektnim dedovanjem |nette:introduction-to-object-oriented-programming#Dedovanje]: ```php -// ⛔ NE! DEDIŠČINA NE SPADA SEM +// ⛔ TAKOLE NE! SEM DEDOVANJE NE SPADA class EditFormFactory extends FormFactory { public function create(): Form { $form = parent::create(); - $form->addText('title', 'Title:'); - // tu so dodana dodatna polja obrazca - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Naslov:'); + // tukaj se dodajajo dodatna polja obrazca + $form->addSubmit('send', 'Pošlji'); return $form; } } ``` -Uporaba dedovanja bi bila v tem primeru popolnoma neproduktivna. Zelo hitro bi naleteli na težave. Na primer, če bi želeli metodi `create()` dodati parametre; PHP bi sporočil napako, ker se njen podpis razlikuje od podpisa nadrejene metode. -Ali pa pri posredovanju odvisnosti razredu `EditFormFactory` prek konstruktorja. To bi povzročilo tako imenovani [konstruktorski pekel |dependency-injection:passing-dependencies#Constructor hell]. +Uporaba dedovanja bi bila v tem primeru popolnoma kontraproduktivna. Na težave bi naleteli zelo hitro. Na primer v trenutku, ko bi želeli metodi `create()` dodati parametre; PHP bi javil napako, da se njena signatura razlikuje od starševske. Ali pri posredovanju odvisnosti v razred `EditFormFactory` prek konstruktorja. Nastala bi situacija, ki ji pravimo [constructor hell |dependency-injection:passing-dependencies#Constructor hell]. -Na splošno je bolje dati prednost kompoziciji pred dedovanjem. +Na splošno je bolje dati prednost [kompoziciji pred dedovanjem |dependency-injection:faq#Zakaj se daje prednost kompoziciji pred dedovanjem]. -Ravnanje z obrazci .[#toc-form-handling] -======================================== +Obdelava obrazca +================ -Obvladovalnik obrazca, ki se pokliče po uspešni oddaji, je lahko tudi del tovarniškega razreda. Deloval bo tako, da bo predložene podatke posredoval modelu v obdelavo. Morebitne napake bo posredoval [nazaj |forms:validation#Processing Errors] v obrazec. Model v naslednjem primeru predstavlja razred `Facade`: +Obdelava obrazca, ki se pokliče po uspešnem pošiljanju, je lahko tudi del tovarniškega razreda. Delovala bo tako, da bo poslana podatke posredovala modelu v obdelavo. Morebitne napake [posreduje nazaj |forms:validation#Napake pri obdelavi] v obrazec. Model v naslednjem primeru predstavlja razred `Facade`: ```php class EditFormFactory @@ -192,9 +190,9 @@ class EditFormFactory public function create(): Form { $form = $this->formFactory->create(); - $form->addText('title', 'Title:'); - // tukaj so dodana dodatna polja obrazca - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Naslov:'); + // tukaj se dodajajo dodatna polja obrazca + $form->addSubmit('send', 'Pošlji'); $form->onSuccess[] = [$this, 'processForm']; return $form; } @@ -202,7 +200,7 @@ class EditFormFactory public function processForm(Form $form, array $data): void { try { - // obdelava posredovanih podatkov + // obdelava poslanih podatkov $this->facade->process($data); } catch (AnyModelException $e) { @@ -212,7 +210,7 @@ class EditFormFactory } ``` -Naj predstavnik sam poskrbi za preusmeritev. Dogodku `onSuccess` bo dodal še eno izvajalko, ki bo izvedla preusmeritev. To bo omogočilo uporabo obrazca v različnih predstavitvenih programih, pri čemer lahko vsak od njih preusmeri na drugo mesto. +Samo preusmeritev pa bomo prepustili presenterju. Ta bo dogodku `onSuccess` dodal še en handler, ki bo izvedel preusmeritev. Zaradi tega bo mogoče obrazec uporabiti v različnih presenterjih in v vsakem preusmeriti drugam. ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -226,7 +224,7 @@ class MyPresenter extends Nette\Application\UI\Presenter { $form = $this->formFactory->create(); $form->onSuccess[] = function () { - $this->flashMessage('Záznam byl uložen'); + $this->flashMessage('Zapis je bil shranjen'); $this->redirect('Homepage:'); }; return $form; @@ -234,39 +232,38 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Ta rešitev izkorišča lastnost obrazcev, da se ob klicu `addError()` na obrazcu ali njegovem elementu ne prikliče naslednji izvajalec `onSuccess`. +Ta rešitev izkorišča lastnost obrazcev, da ko se nad obrazcem ali njegovim elementom pokliče `addError()`, se naslednji handler `onSuccess` ne pokliče več. -Dedovanje iz razreda Form .[#toc-inheriting-from-the-form-class] -================================================================ +Dedovanje od razreda Form +========================= -Vgrajeni obrazec naj ne bi bil otrok obrazca. Z drugimi besedami, ne uporabljajte te rešitve: +Sestavljen obrazec ne sme biti potomec obrazca. Z drugimi besedami, ne uporabljajte te rešitve: ```php -// ⛔ NE! DEDIŠČINA NE SPADA SEM +// ⛔ TAKOLE NE! SEM DEDOVANJE NE SPADA class EditForm extends Form { public function __construct(Translator $translator) { parent::__construct(); - $form->addText('title', 'Title:'); - // tu so dodana dodatna polja obrazca - $form->addSubmit('send', 'Save'); - $form->setTranslator($translator); + $this->addText('title', 'Naslov:'); + // tukaj se dodajajo dodatna polja obrazca + $this->addSubmit('send', 'Pošlji'); + $this->setTranslator($translator); } } ``` -Namesto da bi obrazec zgradili v konstruktorju, uporabite tovarno. +Namesto sestavljanja obrazca v konstruktorju uporabite tovarno. -Pomembno se je zavedati, da je razred `Form` predvsem orodje za sestavljanje obrazca, tj. gradnik obrazca. Sestavljeni obrazec pa lahko štejemo za njegov izdelek. Vendar izdelek ni poseben primer gradnika; med njima ni razmerja *is a*, ki je osnova dedovanja. +Treba se je zavedati, da je razred `Form` v prvi vrsti orodje za sestavljanje obrazca, torej *form builder*. In sestavljen obrazec lahko razumemo kot njen produkt. Vendar produkt ni specifičen primer graditelja (builder), med njimi ni povezave *is a*, ki tvori osnovo dedovanja. -Komponenta obrazca .[#toc-form-component] -========================================= +Komponenta z obrazcem +===================== -Povsem drugačen pristop je ustvarjanje [komponente |application:components], ki vključuje obrazec. To daje nove možnosti, na primer prikazovanje obrazca na poseben način, saj komponenta vključuje predlogo. -Ali pa se lahko signali uporabijo za komunikacijo AJAX in nalaganje informacij v obrazec, na primer za namige itd. +Popolnoma drugačen pristop predstavlja ustvarjanje [komponente|application:components], katere del je obrazec. To daje nove možnosti, na primer izrisovanje obrazca na specifičen način, saj je del komponente tudi predloga. Ali pa je mogoče uporabiti signale za AJAX komunikacijo in nalaganje informacij v obrazec, na primer za predlaganje itd. ```php @@ -284,9 +281,9 @@ class EditControl extends Nette\Application\UI\Control protected function createComponentForm(): Form { $form = new Form; - $form->addText('title', 'Title:'); - // tukaj so dodana dodatna polja obrazca - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Naslov:'); + // tukaj se dodajajo dodatna polja obrazca + $form->addSubmit('send', 'Pošlji'); $form->onSuccess[] = [$this, 'processForm']; return $form; @@ -295,7 +292,7 @@ class EditControl extends Nette\Application\UI\Control public function processForm(Form $form, array $data): void { try { - // obdelava posredovanih podatkov + // obdelava poslanih podatkov $this->facade->process($data); } catch (AnyModelException $e) { @@ -303,13 +300,13 @@ class EditControl extends Nette\Application\UI\Control return; } - // priklic dogodka + // sprožitev dogodka $this->onSave($this, $data); } } ``` -Ustvarimo tovarno, ki bo izdelala to komponento. Dovolj je, če [napišemo njen vmesnik |application:components#Components with Dependencies]: +Ustvarili bomo še tovarno, ki bo izdelovala to komponento. Dovolj je [zapisati njen vmesnik |application:components#Komponente z odvisnostmi]: ```php interface EditControlFactory @@ -318,14 +315,14 @@ interface EditControlFactory } ``` -in ga dodamo v konfiguracijsko datoteko: +In dodati v konfiguracijsko datoteko: ```neon services: - EditControlFactory ``` -Zdaj lahko zahtevamo tovarno in jo uporabimo v predstavitvenem programu: +In zdaj lahko že zahtevamo tovarno in jo uporabimo v presenterju: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -335,19 +332,17 @@ class MyPresenter extends Nette\Application\UI\Presenter ) { } - protected function createComponentEditForm(): Form + protected function createComponentEditForm(): EditControl { $control = $this->controlFactory->create(); $control->onSave[] = function (EditControl $control, $data) { $this->redirect('this'); - // ali preusmerite na rezultat urejanja, npr.: - // $this->reirect('detail', ['id' => $data->id]); + // ali preusmerimo na rezultat urejanja, npr.: + // $this->redirect('detail', ['id' => $data->id]); }; return $control; } } ``` - -{{sitename: Best Practices}} diff --git a/best-practices/sl/inject-method-attribute.texy b/best-practices/sl/inject-method-attribute.texy index 9512ab3e0d..e09c5e2471 100644 --- a/best-practices/sl/inject-method-attribute.texy +++ b/best-practices/sl/inject-method-attribute.texy @@ -1,21 +1,18 @@ -Metode in atributi injiciranja -****************************** +Metode in atributi inject +************************* .[perex] -V tem članku se bomo osredotočili na različne načine posredovanja odvisnosti predstavnikom v ogrodju Nette. Primerjali bomo prednostni način, to je konstruktor, z drugimi možnostmi, kot so metode in atributi `inject`. +V tem članku se bomo osredotočili na različne načine posredovanja odvisnosti v presenterje v ogrodju Nette. Primerjali bomo prednostni način, ki je konstruktor, z drugimi možnostmi, kot so metode in atributi `inject`. -Tudi za predstavnike je prednostni način posredovanja odvisnosti z uporabo [konstruktorja |dependency-injection:passing-dependencies#Constructor Injection]. -Če pa ustvarite skupnega prednika, od katerega dedujejo drugi predstavniki (npr. BasePresenter), in ima ta prednik tudi odvisnosti, nastane težava, ki jo imenujemo [konstruktorski pekel |dependency-injection:passing-dependencies#Constructor hell]. -To lahko obidemo z uporabo alternativnih metod, ki vključujejo metode inject in atribute (anotacije). +Tudi za presenterje velja, da je posredovanje odvisnosti s pomočjo [konstruktorja |dependency-injection:passing-dependencies#Predajanje s konstruktorjem] prednostna pot. Če pa ustvarjate skupnega prednika, od katerega dedujejo drugi presenterji (npr. `BasePresenter`), in ta prednik ima tudi odvisnosti, nastane problem, ki mu pravimo [constructor hell |dependency-injection:passing-dependencies#Constructor hell]. Temu se lahko izognemo z alternativnimi potmi, ki jih predstavljajo metode in atributi (anotacije) `inject`. -`inject*()` Metode .[#toc-inject-methods] -========================================= +Metode `inject*()` +================== -To je oblika posredovanja odvisnosti z uporabo [nastavljalcev |dependency-injection:passing-dependencies#Setter Injection]. Imena teh nastavljalcev se začnejo s predpono inject. -Nette DI samodejno pokliče tako poimenovane metode takoj po ustvarjanju primerka predstavnika in jim posreduje vse zahtevane odvisnosti. Zato morajo biti deklarirane kot javne. +Gre za obliko posredovanja odvisnosti s [setterjem |dependency-injection:passing-dependencies#Predajanje s setterjem]. Ime teh setterjev se začne s predpono `inject`. Nette DI tako poimenovane metode samodejno pokliče takoj po ustvarjanju instance presenterja in jim posreduje vse zahtevane odvisnosti. Zato morajo biti deklarirane kot public. -`inject*()` metode lahko obravnavamo kot nekakšno razširitev konstruktorja na več metod. Zahvaljujoč temu lahko `BasePresenter` prevzame odvisnosti prek druge metode, konstruktor pa ostane prost za svoje potomce: +Metode `inject*()` lahko štejemo za nekakšno razširitev konstruktorja v več metod. Zahvaljujoč temu lahko `BasePresenter` prevzame odvisnosti prek druge metode in pusti konstruktor prost za svoje potomce: ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -39,18 +36,18 @@ class MyPresenter extends BasePresenter } ``` -Predstavitelj lahko vsebuje poljubno število metod `inject*()`, vsaka pa ima lahko poljubno število parametrov. To je odlično tudi v primerih, ko je predstavnik [sestavljen iz lastnosti |presenter-traits] in vsaka od njih zahteva svojo odvisnost. +Presenter lahko vsebuje poljubno število metod `inject*()` in vsaka lahko ima poljubno število parametrov. Odlično se obnesejo tudi v primerih, ko je presenter [sestavljen iz lastnosti (trait) |presenter-traits] in vsaka od njih zahteva svojo odvisnost. -`Inject` Atributi .[#toc-inject-attributes] -=========================================== +Atributi `Inject` +================= -To je oblika [vbrizgavanja v lastnosti |dependency-injection:passing-dependencies#Property Injection]. Dovolj je, da navedete, katere lastnosti naj se injicirajo, in Nette DI samodejno prenese odvisnosti takoj po ustvarjanju primerka predstavnika. Če jih želite vstaviti, jih je treba deklarirati kot javne. +Gre za obliko [injiciranja v lastnost |dependency-injection:passing-dependencies#Nastavitev spremenljivke]. Dovolj je označiti, v katere spremenljivke naj se injicira, in Nette DI samodejno posreduje odvisnosti takoj po ustvarjanju instance presenterja. Da jih lahko vstavi, jih je treba deklarirati kot public. -Lastnosti so označene z atributom: (prej se je uporabljala anotacija `/** @inject */`) +Lastnosti označimo z atributom: (prej se je uporabljala anotacija `/** @inject */`) ```php -use Nette\DI\Attributes\Inject; // ta vrstica je pomembna +use Nette\DI\Attributes\Inject; // ta vrstica je pomembna class MyPresenter extends Nette\Application\UI\Presenter { @@ -59,9 +56,6 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Prednost tega načina posredovanja odvisnosti je bila zelo ekonomična oblika zapisa. Z uvedbo [napredovanja lastnosti konstruktorja |https://blog.nette.org/sl/php-8-0-popoln-pregled-novic#toc-constructor-property-promotion] pa se zdi uporaba konstruktorja lažja. +Prednost tega načina posredovanja odvisnosti je bila zelo varčna oblika zapisa. Vendar pa se z uvedbo [constructor property promotion |https://blog.nette.org/sl/php-8-0-complete-overview-of-news#toc-constructor-property-promotion] zdi lažje uporabiti konstruktor. -Po drugi strani pa ima ta metoda enake pomanjkljivosti kot posredovanje odvisnosti v lastnosti na splošno: nimamo nadzora nad spremembami spremenljivke, hkrati pa spremenljivka postane del javnega vmesnika razreda, kar ni zaželeno. - - -{{sitename: Best Practices}} +Nasprotno pa ta način trpi za enakimi pomanjkljivostmi kot posredovanje odvisnosti v lastnosti (properties) na splošno: nimamo nadzora nad spremembami v spremenljivki in hkrati spremenljivka postane del javnega vmesnika razreda, kar je nezaželeno. diff --git a/best-practices/sl/lets-create-contact-form.texy b/best-practices/sl/lets-create-contact-form.texy index feeae86ee8..ed3d4baf0b 100644 --- a/best-practices/sl/lets-create-contact-form.texy +++ b/best-practices/sl/lets-create-contact-form.texy @@ -1,12 +1,12 @@ -Ustvarimo kontaktni obrazec -*************************** +Ustvarjamo kontaktni obrazec +**************************** .[perex] -Oglejmo si, kako ustvariti obrazec za stik v Nette, vključno s pošiljanjem v e-pošto. Naredimo to! +Pogledali si bomo, kako v Nette ustvariti kontaktni obrazec, vključno s pošiljanjem na e-pošto. Pa začnimo! -Najprej moramo ustvariti nov projekt. Kot je razloženo na strani [Začetek |nette:installation]. Nato pa lahko začnemo ustvarjati obrazec. +Najprej moramo ustvariti nov projekt. Kako to storiti, pojasnjuje stran [Začenjamo |nette:installation]. Nato pa lahko že začnemo z ustvarjanjem obrazca. -Najlažje je [obrazec |forms:in-presenter] ustvariti [neposredno v programu Presenter |forms:in-presenter]. Uporabimo lahko vnaprej pripravljene spletne strani `HomePresenter`. Dodali bomo komponento `contactForm`, ki predstavlja obrazec. To storimo tako, da v kodo, ki bo izdelala komponento, zapišemo tovarniško metodo `createComponentContactForm()`: +Najenostavneje je ustvariti [obrazec neposredno v presenterju |forms:in-presenter]. Lahko uporabimo vnaprej pripravljen `HomePresenter`. Vanjo dodamo komponento `contactForm`, ki predstavlja obrazec. To storimo tako, da v kodo zapišemo tovarniško metodo `createComponentContactForm()`, ki bo komponento izdelala: ```php use Nette\Application\UI\Form; @@ -17,37 +17,35 @@ class HomePresenter extends Presenter protected function createComponentContactForm(): Form { $form = new Form; - $form->addText('name', 'Name:') - ->setRequired('Enter your name'); - $form->addEmail('email', 'E-mail:') - ->setRequired('Enter your e-mail'); - $form->addTextarea('message', 'Message:') - ->setRequired('Enter message'); - $form->addSubmit('send', 'Send'); + $form->addText('name', 'Ime:') + ->setRequired('Vnesite ime'); + $form->addEmail('email', 'E-pošta:') + ->setRequired('Vnesite e-pošto'); + $form->addTextarea('message', 'Sporočilo:') + ->setRequired('Vnesite sporočilo'); + $form->addSubmit('send', 'Pošlji'); $form->onSuccess[] = [$this, 'contactFormSucceeded']; return $form; } - public function contactFormSucceeded(Form $form, $data): void + public function contactFormSucceeded(Form $form, stdClass $data): void { - // sending an email + // pošiljanje e-pošte } } ``` -Kot lahko vidite, smo ustvarili dve metodi. Prva metoda `createComponentContactForm()` ustvari nov obrazec. Ta ima polja za ime, e-pošto in sporočilo, ki jih dodamo z metodami `addText()`, `addEmail()` in `addTextArea()`. Dodali smo tudi gumb za pošiljanje obrazca. -Kaj pa, če uporabnik ne izpolni nekaterih polj? V tem primeru mu moramo sporočiti, da gre za zahtevano polje. To smo storili z metodo `setRequired()`. -Na koncu smo dodali še [dogodek |nette:glossary#events] `onSuccess`, ki se sproži, če je obrazec uspešno oddan. V našem primeru pokliče metodo `contactFormSucceeded`, ki poskrbi za obdelavo oddanega obrazca. To bomo v kodo dodali v naslednjem trenutku. +Kot vidite, smo ustvarili dve metodi. Prva metoda `createComponentContactForm()` ustvari nov obrazec. Ta ima polja za ime, e-pošto in sporočilo, ki jih dodajamo z metodami `addText()`, `addEmail()` in `addTextArea()`. Dodali smo tudi gumb za pošiljanje obrazca. Kaj pa, če uporabnik ne izpolni katerega od polj? V takem primeru bi mu morali sporočiti, da je to obvezno polje. To smo dosegli z metodo `setRequired()`. Na koncu smo dodali tudi [dogodek |nette:glossary#Dogodki eventi] `onSuccess`, ki se sproži, če je obrazec uspešno poslan. V našem primeru pokliče metodo `contactFormSucceeded`, ki poskrbi za obdelavo poslanega obrazca. To bomo v kodo dodali čez trenutek. -Naj bo komponenta `contantForm` prikazana v predlogi `templates/Home/default.latte`: +Komponento `contactForm` bomo pustili izrisati v predlogi `Home/default.latte`: ```latte {block content} -<h1>Contant Form</h1> +<h1>Kontaktni obrazec</h1> {control contactForm} ``` -Za pošiljanje samega elektronskega sporočila ustvarimo nov razred z imenom `ContactFacade` in ga postavimo v datoteko `app/Model/ContactFacade.php`: +Za samo pošiljanje e-pošte bomo ustvarili nov razred, ki ga bomo poimenovali `ContactFacade` in ga postavili v datoteko `app/Model/ContactFacade.php`: ```php <?php @@ -68,9 +66,9 @@ class ContactFacade public function sendMessage(string $email, string $name, string $message): void { $mail = new Message; - $mail->addTo('admin@example.com') // your email + $mail->addTo('admin@example.com') // vaša e-pošta ->setFrom($email, $name) - ->setSubject('Message from the contact form') + ->setSubject('Sporočilo iz kontaktnega obrazca') ->setBody($message); $this->mailer->send($mail); @@ -78,9 +76,9 @@ class ContactFacade } ``` -Metoda `sendMessage()` bo ustvarila in poslala elektronsko sporočilo. Za to uporablja tako imenovani mailer, ki ga prek konstruktorja posreduje kot odvisnost. Preberite več o [pošiljanju e-pošte |mail:]. +Metoda `sendMessage()` ustvari in pošlje e-pošto. Za to uporablja t.i. mailer, ki si ga pusti posredovati kot odvisnost prek konstruktorja. Preberite več o [pošiljanju e-pošte |mail:]. -Zdaj se bomo vrnili k predstavniku in dokončali metodo `contactFormSucceeded()`. Pokliče metodo `sendMessage()` razreda `ContactFacade` in mu posreduje podatke iz obrazca. In kako dobimo objekt `ContactFacade`? Predal nam ga bo konstruktor: +Zdaj se vrnemo nazaj k presenterju in dokončamo metodo `contactFormSucceeded()`. Ta pokliče metodo `sendMessage()` razreda `ContactFacade` in ji posreduje podatke iz obrazca. In kako pridobimo objekt `ContactFacade`? Pustimo si ga posredovati s konstruktorjem: ```php use App\Model\ContactFacade; @@ -102,36 +100,36 @@ class HomePresenter extends Presenter public function contactFormSucceeded(stdClass $data): void { $this->facade->sendMessage($data->email, $data->name, $data->message); - $this->flashMessage('The message has been sent'); + $this->flashMessage('Sporočilo je bilo poslano'); $this->redirect('this'); } } ``` -Ko je elektronsko sporočilo poslano, uporabniku prikažemo tako imenovano [sporočilo flash |application:components#flash-messages], ki potrjuje, da je bilo sporočilo poslano, nato pa ga preusmerimo na naslednjo stran, tako da obrazca ni mogoče ponovno poslati z uporabo *refresh* v brskalniku. +Ko je e-pošta poslana, uporabniku prikažemo še t.i. [flash sporočilo |application:components#Flash sporočila], ki potrjuje, da je bilo sporočilo poslano, nato pa preusmerimo na naslednjo stran, da obrazca ni mogoče ponovno poslati s pomočjo *refresh* v brskalniku. -Če vse deluje, bi morali biti sposobni poslati elektronsko sporočilo iz kontaktnega obrazca. Čestitamo! +Tako, in če vse deluje, bi morali biti sposobni poslati e-pošto iz vašega kontaktnega obrazca. Čestitam! -Predloga e-pošte HTML .[#toc-html-email-template] -------------------------------------------------- +HTML predloga e-pošte +--------------------- -Za zdaj je poslano navadno besedilno e-poštno sporočilo, ki vsebuje samo sporočilo, poslano z obrazcem. Vendar lahko v e-poštnem sporočilu uporabimo HTML in ga naredimo privlačnejšega. Za to bomo v Latte ustvarili predlogo, ki jo bomo shranili v `app/Model/contactEmail.latte`: +Zaenkrat se pošilja navadno besedilno e-sporočilo, ki vsebuje samo sporočilo, poslano z obrazcem. V e-pošti pa lahko uporabimo HTML in naredimo njen videz privlačnejši. Zanjo bomo ustvarili predlogo v Latte, ki jo bomo zapisali v `app/Model/contactEmail.latte`: ```latte <html> - <title>Message from the contact form + Sporočilo iz kontaktnega obrazca -

    Name: {$name}

    -

    E-mail: {$email}

    -

    Message: {$message}

    +

    Ime: {$name}

    +

    E-pošta: {$email}

    +

    Sporočilo: {$message}

    ``` -Za uporabo te predloge je treba spremeniti spletno mesto `ContactFacade`. V konstruktorju zahtevamo razred `LatteFactory`, ki lahko ustvari objekt `Latte\Engine`, [upodobljevalnik predloge Latte |latte:develop#how-to-render-a-template]. Uporabimo metodo `renderToString()` za upodabljanje predloge v datoteko, pri čemer je prvi parameter pot do predloge, drugi pa spremenljivke. +Ostane še prilagoditi `ContactFacade`, da bo uporabljal to predlogo. V konstruktorju bomo zahtevali razred `LatteFactory`, ki zna izdelati objekt `Latte\Engine`, torej [izrisovalnik Latte predlog |latte:develop#Kako izrisati predlogo]. S pomočjo metode `renderToString()` bomo predlogo izrisali v datoteko, prvi parameter je pot do predloge, drugi pa so spremenljivke. ```php namespace App\Model; @@ -158,7 +156,7 @@ class ContactFacade ]); $mail = new Message; - $mail->addTo('admin@example.com') // your email + $mail->addTo('admin@example.com') // vaša e-pošta ->setFrom($email, $name) ->setHtmlBody($body); @@ -167,15 +165,15 @@ class ContactFacade } ``` -Ustvarjeno elektronsko sporočilo HTML nato posredujemo metodi `setHtmlBody()` namesto prvotnega `setBody()`. Prav tako nam ni treba določiti predmeta e-pošte v `setSubject()`, saj ga knjižnica prevzame iz elementa `` v predlogi. +Generirano HTML e-pošto nato posredujemo metodi `setHtmlBody()` namesto prvotni `setBody()`. Prav tako nam ni treba navajati zadeve e-pošte v `setSubject()`, ker si jo bo knjižnica vzela iz elementa `<title>` predloge. -Konfiguracija .[#toc-configuring] ---------------------------------- +Konfiguracija +------------- -V kodi razreda `ContactFacade` je še vedno trdno zakodirano naše upraviteljevo e-poštno sporočilo `admin@example.com`. Bolje bi bilo, če bi ga prenesli v konfiguracijsko datoteko. Kako to storiti? +V kodi razreda `ContactFacade` je še vedno trdo kodiran naš administratorski e-naslov `admin@example.com`. Bolje bi bilo, da ga premaknemo v konfiguracijsko datoteko. Kako to storiti? -Najprej spremenimo razred `ContactFacade` in niz elektronske pošte nadomestimo s spremenljivko, ki jo posreduje konstruktor: +Najprej prilagodimo razred `ContactFacade` in niz z e-pošto nadomestimo s spremenljivko, posredovano s konstruktorjem: ```php class ContactFacade @@ -199,28 +197,25 @@ class ContactFacade } ``` -Drugi korak je, da vrednost te spremenljivke vnesemo v konfiguracijo. V datoteko `app/config/services.neon` dodamo: +Drugi korak pa je navedba vrednosti te spremenljivke v konfiguraciji. V datoteko `app/config/services.neon` zapišemo: ```neon services: - App\Model\ContactFacade(adminEmail: admin@example.com) ``` -In to je to. Če je v razdelku `services` veliko elementov in se vam zdi, da se elektronsko sporočilo izgublja med njimi, ga lahko naredimo za spremenljivko. Vnos bomo spremenili v: +In to je to. Če bi bilo elementov v odseku `services` veliko in bi imeli občutek, da se e-pošta med njimi izgublja, jo lahko naredimo za spremenljivko. Prilagodimo zapis na: ```neon services: - App\Model\ContactFacade(adminEmail: %adminEmail%) ``` -in to spremenljivko opredelimo v datoteki `app/config/common.neon`: +In v datoteki `app/config/common.neon` definiramo to spremenljivko: ```neon parameters: adminEmail: admin@example.com ``` -In končano je! - - -{{sitename: Best Practices}} +In končano! diff --git a/best-practices/sl/microsites.texy b/best-practices/sl/microsites.texy new file mode 100644 index 0000000000..fd4a5be3f2 --- /dev/null +++ b/best-practices/sl/microsites.texy @@ -0,0 +1,63 @@ +Kako pisati mikro-spletna mesta +******************************* + +Predstavljajte si, da morate hitro ustvariti majhno spletno mesto za prihajajoči dogodek vašega podjetja. Mora biti preprosto, hitro in brez nepotrebnih zapletov. Morda mislite, da za tako majhen projekt ne potrebujete robustnega ogrodja. Kaj pa, če lahko uporaba ogrodja Nette ta proces bistveno poenostavi in pospeši? + +Saj se tudi pri ustvarjanju preprostih spletnih mest nočete odreči udobju. Nočete izumljati tistega, kar je bilo že enkrat rešeno. Bodite mirno leni in se pustite razvajati. Nette Framework lahko odlično uporabite tudi kot mikro ogrodje. + +Kako lahko izgleda takšno mikro-spletno mesto? Na primer tako, da celotno kodo spletnega mesta postavimo v eno samo datoteko `index.php` v javni mapi: + +```php +<?php + +require __DIR__ . '/../vendor/autoload.php'; + +$configurator = new Nette\Bootstrap\Configurator; +$configurator->enableTracy(__DIR__ . '/../log'); +$configurator->setTempDirectory(__DIR__ . '/../temp'); + +// ustvari DI vsebnik na podlagi konfiguracije v config.neon +$configurator->addConfig(__DIR__ . '/../app/config.neon'); +$container = $configurator->createContainer(); + +// nastavimo usmerjanje (routing) +$router = new Nette\Application\Routers\RouteList; +$container->addService('router', $router); + +// pot za URL https://example.com/ +$router->addRoute('', function ($presenter, Nette\Http\Request $httpRequest) { + // zaznamo jezik brskalnika in preusmerimo na URL /en ali /de itd. + $supportedLangs = ['en', 'de', 'cs']; + $lang = $httpRequest->detectLanguage($supportedLangs) ?: reset($supportedLangs); + $presenter->redirectUrl("/$lang"); +}); + +// pot za URL https://example.com/cs ali https://example.com/en +$router->addRoute('<lang cs|en>', function ($presenter, string $lang) { + // prikažemo ustrezno predlogo, na primer ../templates/en.latte + $template = $presenter->createTemplate() + ->setFile(__DIR__ . '/../templates/' . $lang . '.latte'); + return $template; +}); + +// zaženi aplikacijo! +$container->getByType(Nette\Application\Application::class)->run(); +``` + +Vse ostalo bodo predloge, shranjene v nadrejeni mapi `/templates`. + +PHP koda v `index.php` najprej [pripravi okolje |bootstrap:], nato definira [poti (route) |application:routing#Dinamično usmerjanje s povratnimi klici] in na koncu zažene aplikacijo. Prednost je, da je lahko drugi parameter funkcije `addRoute()` callable, ki se izvede po odprtju ustrezne strani. + + +Zakaj uporabljati Nette za mikro-spletna mesta? +----------------------------------------------- + +- Programerji, ki so kdaj preizkusili [Tracy|tracy:], si danes ne predstavljajo, da bi kaj programirali brez nje. +- Predvsem pa boste izkoristili sistem predlog [Latte|latte:], saj boste že od 2 strani želeli imeti ločeno [postavitev in vsebino|latte:template-inheritance]. +- In zagotovo se želite zanesti na [samodejno ubežanje znakov |latte:safety-first], da ne nastane ranljivost XSS +- Nette bo tudi zagotovil, da se ob napaki nikoli ne prikažejo programerska sporočila o napakah PHP, temveč uporabniku razumljiva stran. +- Če želite pridobivati povratne informacije od uporabnikov, na primer v obliki kontaktnega obrazca, boste dodali še [obrazce|forms:] in [podatkovno bazo|database:]. +- Izpolnjene obrazce si lahko prav tako enostavno [pošiljate po e-pošti|mail:]. +- Včasih vam lahko koristi [predpomnjenje|caching:], na primer če prenašate in prikazujete vire (feeds). + +V današnjem času, ko sta hitrost in učinkovitost ključnega pomena, je pomembno imeti orodja, ki vam omogočajo doseganje rezultatov brez nepotrebnega odlašanja. Ogrodje Nette vam ponuja prav to - hiter razvoj, varnost in široko paleto orodij, kot sta Tracy in Latte, ki poenostavljajo proces. Dovolj je namestiti nekaj Nette paketov in zgraditi takšno mikro-spletno mesto je naenkrat povsem enostavno. In veste, da se nikjer ne skriva nobena varnostna luknja. diff --git a/best-practices/sl/pagination.texy b/best-practices/sl/pagination.texy index b5322e1cd1..f543008252 100644 --- a/best-practices/sl/pagination.texy +++ b/best-practices/sl/pagination.texy @@ -1,17 +1,16 @@ -Strani rezultatov podatkovne zbirke -*********************************** +Stranskanje rezultatov podatkovne baze +************************************** .[perex] -Pri razvoju spletnih aplikacij se pogosto srečate z zahtevo po izpisu omejenega števila zapisov na strani. +Pri ustvarjanju spletnih aplikacij se zelo pogosto srečate z zahtevo po omejitvi števila izpisanih postavk na strani. -Iz tega stanja izstopimo, ko izpišemo vse podatke brez listanja. Za izbiro podatkov iz zbirke podatkov imamo razred ArticleRepository, ki vsebuje konstruktor in metodo `findPublishedArticles`, ki vrne vse objavljene članke, razvrščene v padajočem vrstnem redu glede na datum objave. +Izhajali bomo iz stanja, ko izpisujemo vse podatke brez stranskanja. Za izbiro podatkov iz podatkovne baze imamo razred ArticleRepository, ki poleg konstruktorja vsebuje metodo `findPublishedArticles`, ki vrača vse objavljene članke, razvrščene padajoče po datumu objave. ```php namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -31,10 +30,10 @@ class ArticleRepository } ``` -V Presenter nato injiciramo razred model in v metodi render bomo zahtevali objavljene članke, ki jih posredujemo predlogi: +V presenterju si nato injiciramo modelni razred in v render metodi zahtevamo objavljene članke, ki jih posredujemo v predlogo: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -53,11 +52,11 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -V predlogi bomo poskrbeli za upodobitev seznama člankov: +V predlogi `default.latte` se nato poskrbimo za izpis člankov: ```latte {block content} -<h1>Articles</h1> +<h1>Članki</h1> <div class="articles"> {foreach $articles as $article} @@ -68,11 +67,11 @@ V predlogi bomo poskrbeli za upodobitev seznama člankov: ``` -Na ta način lahko izpišemo vse članke, vendar bo to povzročilo težave, ko bo število člankov naraslo. Takrat bo koristno uvesti mehanizem paginga. +Na ta način znamo izpisati vse članke, kar pa začne povzročati težave v trenutku, ko število člankov naraste. V tem trenutku pride prav implementacija mehanizma za stranskanje. -Ta bo zagotovil, da bodo vsi članki razdeljeni na več strani, mi pa bomo prikazali le članke ene trenutne strani. Skupno število strani in razdelitev člankov izračuna [utils:Paginator] sam, odvisno od tega, koliko člankov imamo skupaj in koliko člankov želimo prikazati na strani. +Ta zagotovi, da se vsi članki razdelijo na več strani in mi prikažemo samo članke ene trenutne strani. Skupno število strani in razdelitev člankov si izračuna [utils:Paginator] sam glede na to, koliko člankov skupaj imamo in koliko člankov na stran želimo prikazati. -V prvem koraku bomo spremenili metodo za pridobivanje člankov v razredu repozitorija, da bo vrnila samo članke z ene strani. Dodali bomo tudi novo metodo za pridobitev skupnega števila člankov v zbirki podatkov, ki jo bomo potrebovali za nastavitev Paginatorja: +V prvem koraku si prilagodimo metodo za pridobivanje člankov v razredu repozitorija tako, da nam zna vračati samo članke za eno stran. Dodamo tudi metodo za ugotavljanje skupnega števila člankov v podatkovni bazi, ki jo bomo potrebovali za nastavitev Paginatorja: ```php namespace App\Model; @@ -100,7 +99,7 @@ class ArticleRepository } /** - * Returns the total number of published articles + * Vrača skupno število objavljenih člankov */ public function getPublishedArticlesCount(): int { @@ -109,12 +108,12 @@ class ArticleRepository } ``` -Naslednji korak je urejanje predstavnika. Številko trenutno prikazane strani bomo posredovali metodi render. V primeru, da ta številka ni del naslova URL, moramo privzeto vrednost nastaviti na prvo stran. +Nato se lotimo prilagoditev presenterja. V render metodo bomo posredovali številko trenutno prikazane strani. Za primer, ko ta številka ne bo del URL-ja, nastavimo privzeto vrednost prve strani. -Metodo upodabljanja razširimo tudi na pridobitev primerka Paginatorja, njegovo nastavitev in izbiro pravilnih člankov za prikaz v predlogi. HomePresenter bo videti takole: +Nadalje render metodo razširimo še s pridobivanjem instance Paginatorja, njegovo nastavitvijo in izbiro pravilnih člankov za prikaz v predlogi. HomePresenter bo po prilagoditvah izgledal takole: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -128,31 +127,31 @@ class HomePresenter extends Nette\Application\UI\Presenter public function renderDefault(int $page = 1): void { - // Poiskali bomo skupno število objavljenih člankov + // Ugotovimo skupno število objavljenih člankov $articlesCount = $this->articleRepository->getPublishedArticlesCount(); - // Izdelali bomo primerek Paginatorja in ga nastavili + // Izdelamo instanco Paginatorja in jo nastavimo $paginator = new Nette\Utils\Paginator; $paginator->setItemCount($articlesCount); // skupno število člankov - $paginator->setItemsPerPage(10); // člankov na stran - $paginator->setPage($page); // dejansko število strani + $paginator->setItemsPerPage(10); // število postavk na stran + $paginator->setPage($page); // številka trenutne strani - // Na podlagi Paginatorjevih izračunov bomo v zbirki podatkov poiskali omejen nabor člankov + // Iz podatkovne baze izvlečemo omejeno množico člankov glede na izračun Paginatorja $articles = $this->articleRepository->findPublishedArticles($paginator->getLength(), $paginator->getOffset()); - // ki ga bomo posredovali predlogi + // ki jo posredujemo v predlogo $this->template->articles = $articles; - // in tudi sam Paginator, da prikaže možnosti za prikazovanje strani + // in tudi sam Paginator za prikaz možnosti stranskanja $this->template->paginator = $paginator; } } ``` -Predloga že iterira po člankih na eni strani, dodajte le povezave za paging: +Predloga nam že zdaj iterira samo nad članki ene strani, dodati moramo le še povezave za stranskanje: ```latte {block content} -<h1>Articles</h1> +<h1>Članki</h1> <div class="articles"> {foreach $articles as $article} @@ -163,34 +162,33 @@ Predloga že iterira po člankih na eni strani, dodajte le povezave za paging: <div class="pagination"> {if !$paginator->isFirst()} - <a n:href="default, 1">First</a> + <a n:href="default, 1">Prva</a>  |  - <a n:href="default, $paginator->page-1">Previous</a> + <a n:href="default, $paginator->page-1">Prejšnja</a>  |  {/if} - Page {$paginator->getPage()} of {$paginator->getPageCount()} + Stran {$paginator->getPage()} od {$paginator->getPageCount()} {if !$paginator->isLast()}  |  - <a n:href="default, $paginator->getPage() + 1">Next</a> + <a n:href="default, $paginator->getPage() + 1">Naslednja</a>  |  - <a n:href="default, $paginator->getPageCount()">Last</a> + <a n:href="default, $paginator->getPageCount()">Zadnja</a> {/if} </div> ``` -To je način, kako smo s Paginatorjem dodali paginacijo. Če namesto [Nette |database:explorer] [Database Core |database:core] kot plast podatkovne baze uporabimo [Nette Database Explorer |database:explorer], lahko izvedemo paging tudi brez Paginatorja. Razred `Nette\Database\Table\Selection` vsebuje metodo [page |api:Nette\Database\Table\Selection::_ page] z logiko paginacije, ki je prevzeta iz Paginatorja. +Tako smo stran dopolnili z možnostjo stranskanja s pomočjo Paginatorja. V primeru, ko namesto [Nette Database Core |database:sql-way] kot podatkovno plast uporabimo [Nette Database Explorer |database:explorer], smo sposobni implementirati stranskanje tudi brez uporabe Paginatorja. Razred `Nette\Database\Table\Selection` namreč vsebuje metodo [page |api:Nette\Database\Table\Selection::page] z logiko stranskanja, prevzeto iz Paginatorja. -Skladišče bo videti takole: +Repozitorij bo pri tem načinu implementacije izgledal takole: ```php namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -198,7 +196,6 @@ class ArticleRepository ) { } - public function findPublishedArticles(): Nette\Database\Table\Selection { return $this->database->table('articles') @@ -208,10 +205,10 @@ class ArticleRepository } ``` -Namesto tega bomo uporabili metodo predmeta `Selection`, ki ga je vrnil repozitorij: +V presenterju nam ni treba ustvarjati Paginatorja, namesto njega uporabimo metodo razreda `Selection`, ki nam jo vrača repozitorij: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -225,25 +222,25 @@ class HomePresenter extends Nette\Application\UI\Presenter public function renderDefault(int $page = 1): void { - // Poiskali bomo objavljene članke + // Izvlečemo objavljene članke $articles = $this->articleRepository->findPublishedArticles(); - // in njihov del, omejen z izračunom metode strani, ki ga bomo posredovali predlogi + // in v predlogo pošljemo samo njihov del, omejen glede na izračun metode page $lastPage = 0; $this->template->articles = $articles->page($page, 10, $lastPage); - // in potrebne podatke za prikaz možnosti za stranski prikaz. + // in tudi potrebne podatke za prikaz možnosti stranskanja $this->template->page = $page; $this->template->lastPage = $lastPage; } } ``` -Ker ne uporabljamo Paginatorja, moramo urediti razdelek, ki prikazuje povezave za pomikanje: +Ker v predlogo zdaj ne pošiljamo Paginatorja, prilagodimo del, ki prikazuje povezave za stranskanje: ```latte {block content} -<h1>Articles</h1> +<h1>Članki</h1> <div class="articles"> {foreach $articles as $article} @@ -254,24 +251,23 @@ Ker ne uporabljamo Paginatorja, moramo urediti razdelek, ki prikazuje povezave z <div class="pagination"> {if $page > 1} - <a n:href="default, 1">First</a> + <a n:href="default, 1">Prva</a>  |  - <a n:href="default, $page - 1">Previous</a> + <a n:href="default, $page - 1">Prejšnja</a>  |  {/if} - Page {$page} of {$lastPage} + Stran {$page} od {$lastPage} {if $page < $lastPage}  |  - <a n:href="default, $page + 1">Next</a> + <a n:href="default, $page + 1">Naslednja</a>  |  - <a n:href="default, $lastPage">Last</a> + <a n:href="default, $lastPage">Zadnja</a> {/if} </div> ``` -Na ta način smo izvedli mehanizem za paging brez uporabe Paginatorja. +Na ta način smo implementirali mehanizem za stranskanje brez uporabe Paginatorja. {{priority: -1}} -{{sitename: Best Practices}} diff --git a/best-practices/sl/passing-settings-to-presenters.texy b/best-practices/sl/passing-settings-to-presenters.texy index 86de611511..8ced4b88f2 100644 --- a/best-practices/sl/passing-settings-to-presenters.texy +++ b/best-practices/sl/passing-settings-to-presenters.texy @@ -1,10 +1,10 @@ -Posredovanje nastavitev predstavnikom +Posredovanje nastavitev v presenterje ************************************* .[perex] -Ali morate predstavnikom posredovati argumente, ki niso predmeti (npr. informacije o tem, ali deluje v načinu za odpravljanje napak, poti do imenikov itd.) in jih zato ni mogoče samodejno posredovati s samodejnim povezovanjem? Rešitev je, da jih zapakirate v objekt `Settings`. +Ali morate v presenterje posredovati argumente, ki niso objekti (npr. informacijo, ali teče v načinu za odpravljanje napak, poti do map itd.), in jih torej ni mogoče samodejno posredovati s pomočjo autowiringa? Rešitev je, da jih zapakirate v objekt `Settings`. -Storitev `Settings` je zelo enostaven in hkrati uporaben način za zagotavljanje informacij o delujoči aplikaciji predavateljem. Njena posebna oblika je v celoti odvisna od vaših posebnih potreb. Primer: +Storitev `Settings` predstavlja zelo enostaven in hkrati uporaben način za zagotavljanje informacij o tekoči aplikaciji presenterjem. Njena konkretna oblika je odvisna izključno od vaših specifičnih potreb. Primer: ```php namespace App; @@ -12,7 +12,7 @@ namespace App; class Settings { public function __construct( - // od PHP 8.1 je mogoče določiti samo za branje + // od PHP 8.1 je mogoče navesti readonly public bool $debugMode, public string $appDir, // in tako naprej @@ -20,7 +20,7 @@ class Settings } ``` -Primer registracije v konfiguracijo: +Primer registracije v konfiguraciji: ```neon services: @@ -30,7 +30,7 @@ services: ) ``` -Ko voditelj potrebuje informacije, ki jih zagotavlja ta storitev, jih preprosto zahteva v konstruktorju: +Ko bo presenter potreboval informacije, ki jih zagotavlja ta storitev, jo bo preprosto zahteval v konstruktorju: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -47,5 +47,3 @@ class MyPresenter extends Nette\Application\UI\Presenter } } ``` - -{{sitename: Best Practices}} diff --git a/best-practices/sl/post-links.texy b/best-practices/sl/post-links.texy new file mode 100644 index 0000000000..0b99158d78 --- /dev/null +++ b/best-practices/sl/post-links.texy @@ -0,0 +1,56 @@ +Kako pravilno uporabljati POST povezave +*************************************** + +.[perex] +V spletnih aplikacijah, zlasti v administrativnih vmesnikih, bi moralo biti osnovno pravilo, da se akcije, ki spreminjajo stanje strežnika, ne izvajajo prek metode HTTP GET. Kot pove že ime metode, bi moral GET služiti samo za pridobivanje podatkov, ne pa za njihovo spreminjanje. Za akcije, kot je na primer brisanje zapisov, je primernejša uporaba metode POST. Čeprav bi bila idealna metoda DELETE, je te brez JavaScripta ni mogoče izvesti, zato se zgodovinsko uporablja POST. + +Kako to storiti v praksi? Uporabite ta preprost trik. Na začetku predloge si ustvarite pomožni obrazec z identifikatorjem `postForm`, ki ga nato uporabite za gumbe za brisanje: + +```latte .{file:@layout.latte} +<form method="post" id="postForm"></form> +``` + +Zahvaljujoč temu obrazcu lahko namesto klasične povezave `<a>` uporabite gumb `<button>`, ki ga lahko vizualno prilagodite tako, da izgleda kot običajna povezava. Na primer, CSS ogrodje Bootstrap ponuja razreda `btn btn-link`, s katerima dosežete, da gumb ne bo vizualno drugačen od ostalih povezav. Z atributom `form="postForm"` ga povežemo z vnaprej pripravljenim obrazcem: + +```latte .{file:admin.latte} +<table> + <tr n:foreach="$posts as $post"> + <td>{$post->title}</td> + <td> + <button class="btn btn-link" form="postForm" formaction="{link delete $post->id}">izbriši</button> + <!-- namesto <a n:href="delete $post->id">izbriši</a> --> + </td> + </tr> +</table> +``` + +Ob kliku na povezavo se zdaj izvede akcija `delete`. Za zagotovitev, da bodo zahteve sprejete samo prek metode POST in iz iste domene (kar je učinkovita obramba pred napadi CSRF), uporabite atribut `#[Requires]`: + +```php .{file:AdminPresenter.php} +use Nette\Application\Attributes\Requires; + +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST', sameOrigin: true)] + public function actionDelete(int $id): void + { + $this->facade->deletePost($id); // hipotetična koda, ki briše zapis + $this->redirect('default'); + } +} +``` + +Atribut obstaja od Nette Application 3.2 in več o njegovih možnostih boste izvedeli na strani [Kako uporabljati atribut #Requires |attribute-requires]. + +Če bi namesto akcije `actionDelete()` uporabljali signal `handleDelete()`, ni treba navajati `sameOrigin: true`, ker imajo signali to zaščito nastavljeno implicitno: + +```php .{file:AdminPresenter.php} +#[Requires(methods: 'POST')] +public function handleDelete(int $id): void +{ + $this->facade->deletePost($id); + $this->redirect('this'); +} +``` + +Ta pristop ne samo izboljšuje varnost vaše aplikacije, ampak tudi prispeva k spoštovanju pravilnih spletnih standardov in praks. Z uporabo metod POST za akcije, ki spreminjajo stanje, dosežete bolj robustno in varnejšo aplikacijo. diff --git a/best-practices/sl/presenter-traits.texy b/best-practices/sl/presenter-traits.texy index e20064a8e4..894c0add0e 100644 --- a/best-practices/sl/presenter-traits.texy +++ b/best-practices/sl/presenter-traits.texy @@ -1,14 +1,14 @@ -Sestavljanje predavateljev iz lastnosti -*************************************** +Sestavljanje presenterjev iz lastnosti (trait) +********************************************** .[perex] -Če moramo isto kodo implementirati v več predstavitvah (npr. preverjanje, ali je uporabnik prijavljen), je skušnjava, da kodo postavimo v skupnega prednika. Druga možnost je, da ustvarimo enonamenske lastnosti. +Če moramo v več presenterjih implementirati isto kodo (npr. preverjanje, ali je uporabnik prijavljen), se ponuja možnost, da kodo postavimo v skupnega prednika. Druga možnost je ustvarjanje namensko usmerjenih [lastnosti (trait) |nette:introduction-to-object-oriented-programming#Lastnosti Traits]. -Prednost te rešitve je, da lahko vsak predstavnik uporablja samo lastnosti, ki jih dejansko potrebuje, medtem ko večkratno dedovanje v PHP ni mogoče. +Prednost te rešitve je, da lahko vsak od presenterjev uporabi točno tiste lastnosti (traits), ki jih dejansko potrebuje, medtem ko večkratno dedovanje v PHP ni mogoče. -Te lastnosti lahko izkoristijo dejstvo, da se ob ustvarjanju predstavnika zaporedno kličejo vse [metode inject |inject-method-attribute#inject methods]. Poskrbeti morate le, da je ime vsake metode inject edinstveno. +Te lastnosti (traits) lahko izkoristijo dejstvo, da se ob ustvarjanju presenterja postopoma pokličejo vse [inject metode |inject-method-attribute#Metode inject]. Paziti je treba le, da je ime vsake inject metode edinstveno. -Značilnosti lahko inicializacijsko kodo obesijo v dogodka [onStartup ali onRender |application:presenters#Events]. +Lastnosti (traits) lahko pripnejo inicializacijsko kodo na dogodke [onStartup ali onRender |application:presenters#Dogodki]. Primeri: @@ -36,7 +36,7 @@ trait StandardTemplateFilters } ``` -Voditelj nato preprosto uporabi te lastnosti: +Presenter nato te lastnosti (traits) preprosto uporabi: ```php class ArticlePresenter extends Nette\Application\UI\Presenter @@ -45,6 +45,3 @@ class ArticlePresenter extends Nette\Application\UI\Presenter use RequireLoggedUser; } ``` - - -{{sitename: Best Practices}} diff --git a/best-practices/sl/restore-request.texy b/best-practices/sl/restore-request.texy index 4790fc3457..6f81a2d371 100644 --- a/best-practices/sl/restore-request.texy +++ b/best-practices/sl/restore-request.texy @@ -2,16 +2,15 @@ Kako se vrniti na prejšnjo stran? ********************************* .[perex] -Kaj če uporabnik izpolni obrazec in mu poteče prijava? Da ne bi izgubili podatkov, jih pred preusmeritvijo na prijavno stran shranimo v sejo. V sistemu Nette je to zelo enostavno. +Kaj če uporabnik izpolnjuje obrazec in mu poteče prijava? Da ne bi izgubil podatkov, pred preusmeritvijo na prijavno stran podatke shranimo v sejo (session). V Nette je to povsem enostavno. -Trenutni zahtevek lahko shranimo v sejo z metodo `storeRequest()`, ki vrne njegov identifikator kot kratek niz. Metoda shrani ime trenutnega predstavnika, pogled in njegove parametre. -Če je bil poslan tudi obrazec, se shranijo tudi vrednosti polj (razen naloženih datotek). +Trenutno zahtevo lahko shranite v sejo s pomočjo metode `storeRequest()`, ki vrne njen identifikator v obliki kratkega niza. Metoda shrani ime trenutnega presenterja, pogled (view) in njegove parametre. V primeru, da je bil poslan tudi obrazec, se shrani tudi vsebina polj (z izjemo naloženih datotek). -Zahtevo obnovi metoda `restoreRequest($key)`, ki ji posredujemo pridobljeni identifikator. Ta preusmeri na prvotnega predstavnika in pogled. Če pa shranjena zahteva vsebuje oddajo obrazca, se z metodo `forward()` posreduje prvotnemu predstavniku , obrazcu se posredujejo predhodno izpolnjene vrednosti in omogoči ponovni izris. To uporabniku omogoča, da ponovno odda obrazec, pri čemer se podatki ne izgubijo. +Obnovitev zahteve izvede metoda `restoreRequest($key)`, ki ji posredujemo pridobljeni identifikator. Ta preusmeri na prvotni presenter in pogled. Če pa shranjena zahteva vsebuje pošiljanje obrazca, na prvotni presenter preide z metodo `forward()`, obrazcu posreduje prej izpolnjene vrednosti in ga pusti ponovno izrisati. Uporabnik ima tako možnost obrazec ponovno poslati in nobeni podatki se ne izgubijo. -Pomembno je, da `restoreRequest()` preveri, ali je novo prijavljeni uporabnik isti, ki je prvotno izpolnil obrazec. Če ni tako, zavrže zahtevo in ne stori ničesar. +Pomembno je, da `restoreRequest()` preveri, ali je novo prijavljeni uporabnik isti, kot tisti, ki je obrazec prvotno izpolnjeval. Če ne, zahtevo zavrže in ne naredi ničesar. -Vse skupaj prikažimo s primerom. Imejmo predstavnik `AdminPresenter`, v katerem se urejajo podatki in katerega metoda `startup()` preverja, ali je uporabnik prijavljen. Če ni, ga preusmerimo na `SignPresenter`. Hkrati shranimo trenutno zahtevo in njen ključ pošljemo na `SignPresenter`. +Poglejmo si vse na primeru. Imejmo presenter `AdminPresenter`, v katerem se urejajo podatki in v njegovi metodi `startup()` preverjamo, ali je uporabnik prijavljen. Če ni, ga preusmerimo na `SignPresenter`. Hkrati shranimo trenutno zahtevo in njen ključ pošljemo v `SignPresenter`. ```php class AdminPresenter extends Nette\Application\UI\Presenter @@ -27,7 +26,7 @@ class AdminPresenter extends Nette\Application\UI\Presenter } ``` -Predstavnik `SignPresenter` bo poleg obrazca za prijavo vseboval tudi trajni parameter `$backlink`, v katerega se zapiše ključ. Ker je parameter obstojen, se bo prenesel tudi po oddaji obrazca za prijavo. +Presenter `SignPresenter` bo poleg obrazca za prijavo vseboval tudi persistentni parameter `$backlink`, v katerega se zapiše ključ. Ker je parameter persistenten, se bo prenašal tudi po pošiljanju prijavnega obrazca. ```php @@ -41,14 +40,14 @@ class SignPresenter extends Nette\Application\UI\Presenter protected function createComponentSignInForm() { $form = new Nette\Application\UI\Form; - // ... dodajanje polj obrazca ... + // ... dodamo polja obrazca ... $form->onSuccess[] = [$this, 'signInFormSubmitted']; return $form; } public function signInFormSubmitted($form) { - // ... tukaj se uporabnik vpiše ... + // ... tukaj uporabnika prijavimo ... $this->restoreRequest($this->backlink); $this->redirect('Admin:'); @@ -56,9 +55,8 @@ class SignPresenter extends Nette\Application\UI\Presenter } ``` -Ključ shranjene zahteve posredujemo metodi `restoreRequest()` in ta nas preusmeri (ali posreduje) na prvotni predstavnik. +Metodi `restoreRequest()` posredujemo ključ shranjene zahteve in ta preusmeri (ali preide) na prvotni presenter. -Če pa je ključ neveljaven (na primer ne obstaja več v seji), metoda ne stori ničesar. Zato je naslednji klic `$this->redirect('Admin:')`, ki preusmeri na `AdminPresenter`. +Če pa je ključ neveljaven (na primer že ne obstaja v seji), metoda ne naredi ničesar. Sledi torej klic `$this->redirect('Admin:')`, ki preusmeri na `AdminPresenter`. {{priority: -1}} -{{sitename: Best Practices}} diff --git a/best-practices/tr/@home.texy b/best-practices/tr/@home.texy index 1cc97db6f5..c8b90b9e40 100644 --- a/best-practices/tr/@home.texy +++ b/best-practices/tr/@home.texy @@ -1,22 +1,24 @@ -En İyi Uygulamalar -****************** +Kılavuzlar ve yöntemler +*********************** .[perex] -Öğreticiler, yaygın sorunlara çözümler ve Nette için en iyi uygulamalar. +Nette için kılavuzlar, sık karşılaşılan görevlerin çözümleri ve *en iyi uygulamalar*. <div class=documentation> <div> -Nette Uygulama --------------- -- [Yöntemleri ve öznitelikleri enjekte etme|inject-method-attribute] -- [Özelliklerden sunum yapanların oluşturulması |presenter-traits] -- [Ayarların sunum yapanlara aktarılması |passing-settings-to-presenters] -- [Önceki bir sayfaya nasıl dönülür |restore-request]? -- [Veritabanı sonuçlarını sayfalandırma |Pagination] +Nette Uygulaması +---------------- +- [Inject metotları ve nitelikleri |inject-method-attribute] +- [Trait'lerden presenter oluşturma |presenter-traits] +- [Ayarları presenter'lara geçirme |passing-settings-to-presenters] +- [Önceki sayfaya nasıl dönülür |restore-request] +- [Veritabanı sonuçlarını sayfalama |pagination] - [Dinamik snippet'ler |dynamic-snippets] +- [#Requires niteliği nasıl kullanılır |attribute-requires] +- [POST bağlantıları nasıl doğru kullanılır |post-links] </div> <div> @@ -26,30 +28,32 @@ Formlar ------- - [Formların yeniden kullanımı |form-reuse] - [Kayıt oluşturma ve düzenleme formu |creating-editing-form] -- [Bir İletişim Formu Oluşturalım |lets-create-contact-form] -- [Bağımlı seçim kutuları |https://blog.nette.org/tr/bagimli-secim-kutulari-nette-ve-saf-js-de-zarif-bir-sekilde] +- [İletişim formu oluşturuyoruz |lets-create-contact-form] +- [Bağımlı seçme kutuları |https://blog.nette.org/tr/dependent-selectboxes-elegantly-in-nette-and-pure-js] </div> <div> -Ortak +Genel ----- - [Yapılandırma dosyası nasıl yüklenir |bootstrap:] -- [Nette neden PascalCase sabit gösterimini kullanıyor? |https://blog.nette.org/tr/kodda-daha-az-ciglik-atmak-icin] -- [Nette neden Interface son ekini kullanmıyor? |https://blog.nette.org/tr/oenek-ve-sonekler-arayuez-adlarina-ait-degildir] -- [Composer kullanım ipuçları |composer] -- [Editörler ve araçlar hakkında ipuçları |editors-and-tools] +- [Mikro web siteleri nasıl yazılır |microsites] +- [Nette neden PascalCase sabit gösterimini kullanıyor? |https://blog.nette.org/tr/for-less-screaming-in-the-code] +- [Nette neden Interface son ekini kullanmıyor? |https://blog.nette.org/tr/prefixes-and-suffixes-do-not-belong-in-interface-names] +- [Composer: kullanım ipuçları |composer] +- [Düzenleyiciler ve araçlar için ipuçları |editors-and-tools] +- [Nesne yönelimli programlamaya giriş |nette:introduction-to-object-oriented-programming] </div> <div> -Örnek Çözüm ------------ +Örnek Çözümler +-------------- - [Nette örnekleri |https://github.com/nette-examples] -- [Doktrin & Nette |https://contributte.org/nettrine/] -- [Katkı örnekleri |https://contributte.org/examples.html] +- [Doctrine & Nette |https://contributte.org/nettrine/] +- [Contributte örnekleri |https://contributte.org/examples.html] - [Doctrine ORM Web Sitesi |https://github.com/MinecordNetwork/Website] - [Hızlı başlangıç |quickstart:] @@ -59,10 +63,7 @@ Ortak Videolar -------- -Posobota'dan yüzlerce kaydı ve Nette ile ilgili videoları tek bir çatı altında "Nette Framework YouTube Kanalı":https://www.youtube.com/user/NetteFrameworknda bulabilirsiniz. +Poslední soboty'den yüzlerce kayıt ve Nette hakkındaki videoları tek bir çatı altında "Nette Framework Youtube kanalında":https://www.youtube.com/user/NetteFramework bulabilirsiniz. </div> </div> - -{{sitename: En İyi Uygulamalar}} -{{leftbar: www:@menu-common}} diff --git a/best-practices/tr/@meta.texy b/best-practices/tr/@meta.texy new file mode 100644 index 0000000000..7f915d9a01 --- /dev/null +++ b/best-practices/tr/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Kılavuzlar ve yöntemler}} +{{leftbar: www:@menu-common}} diff --git a/best-practices/tr/attribute-requires.texy b/best-practices/tr/attribute-requires.texy new file mode 100644 index 0000000000..0fe1f082f5 --- /dev/null +++ b/best-practices/tr/attribute-requires.texy @@ -0,0 +1,177 @@ +`#[Requires]` Niteliği Nasıl Kullanılır +*************************************** + +.[perex] +Bir web uygulaması yazarken, uygulamanızın belirli bölümlerine erişimi kısıtlama ihtiyacıyla sık sık karşılaşırsınız. Belki bazı isteklerin yalnızca bir form kullanarak (yani POST metoduyla) veri gönderebilmesini veya yalnızca AJAX çağrıları için erişilebilir olmasını istersiniz. Nette Framework 3.2'de, bu tür kısıtlamaları çok zarif ve anlaşılır bir şekilde ayarlamanıza olanak tanıyan yeni bir araç ortaya çıktı: `#[Requires]` niteliği. + +Nitelik, bir sınıf veya metot tanımının önüne eklediğiniz PHP'deki özel bir işarettir. Aslında bir sınıf olduğu için, aşağıdaki örneklerin çalışması için use ifadesini belirtmek gerekir: + +```php +use Nette\Application\Attributes\Requires; +``` + +`#[Requires]` niteliğini presenter sınıfının kendisinde ve ayrıca şu metotlarda kullanabilirsiniz: + +- `action<Action>()` +- `render<View>()` +- `handle<Signal>()` +- `createComponent<Name>()` + +Son iki metot bileşenlerle de ilgilidir, yani niteliği onlarda da kullanabilirsiniz. + +Niteliğin belirttiği koşullar karşılanmazsa, bir HTTP 4xx hatası tetiklenir. + + +HTTP Metotları +-------------- + +Erişim için hangi HTTP metotlarının (GET, POST vb. gibi) izinli olduğunu belirleyebilirsiniz. Örneğin, yalnızca bir form göndererek erişime izin vermek istiyorsanız, şunu ayarlarsınız: + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +Durumu değiştiren eylemler için neden GET yerine POST kullanmalısınız ve bunu nasıl yapmalısınız? [Kılavuzu okuyun |post-links]. + +Bir metot veya metot dizisi belirtebilirsiniz. Özel bir durum, tüm metotlara izin veren `'*'` değeridir, ki bu presenter'ların standart olarak [güvenlik nedenleriyle izin vermediği |application:presenters#HTTP Metodu Kontrolü] bir durumdur. + + +AJAX Çağrıları +-------------- + +Bir presenter veya metodun yalnızca AJAX istekleri için kullanılabilir olmasını istiyorsanız, şunu kullanın: + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Aynı Kaynak +----------- + +Güvenliği artırmak için, isteğin aynı alan adından yapılmasını zorunlu kılabilirsiniz. Bu, [CSRF güvenlik açığını |nette:vulnerability-protection#Cross-Site Request Forgery CSRF] önler: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +`handle<Signal>()` metotlarında, aynı alan adından erişim otomatik olarak zorunlu kılınır. Dolayısıyla tam tersine, herhangi bir alan adından erişime izin vermek istiyorsanız, şunu belirtin: + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Forward ile Erişim +------------------ + +Bazen bir presenter'a erişimi yalnızca dolaylı olarak, örneğin başka bir presenter'dan `forward()` veya `switch()` metodunu kullanarak kullanılabilir olacak şekilde kısıtlamak yararlıdır. Örneğin error-presenter'lar bu şekilde korunur, böylece URL'den çağrılamazlar: + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Pratikte, presenter'daki mantığa dayalı olarak erişilebilen belirli view'leri işaretlemek genellikle gereklidir. Yani yine, doğrudan açılamamaları için: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = $this->facade->getProduct($id); + if (!$product) { + $this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Belirli Eylemler +---------------- + +Ayrıca, belirli bir kodun, örneğin bir bileşenin oluşturulmasının, yalnızca presenter'daki belirli eylemler için kullanılabilir olmasını da kısıtlayabilirsiniz: + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +Tek bir eylem durumunda, bir dizi yazmaya gerek yoktur: `#[Requires(actions: 'default')]` + + +Özel Nitelikler +--------------- + +`#[Requires]` niteliğini aynı ayarlarla tekrar tekrar kullanmak istiyorsanız, `#[Requires]`'ı miras alan ve onu ihtiyaçlara göre ayarlayan kendi niteliğinizi oluşturabilirsiniz. + +Örneğin, `#[SingleAction]` yalnızca `default` eylemi aracılığıyla erişime izin verir: + +```php +#[\Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Veya `#[RestMethods]`, REST API için kullanılan tüm HTTP metotları aracılığıyla erişime izin verir: + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Sonuç +----- + +`#[Requires]` niteliği, web sayfalarınızın nasıl erişilebilir olduğu konusunda size büyük esneklik ve kontrol sağlar. Basit ama güçlü kurallar kullanarak uygulamanızın güvenliğini ve doğru çalışmasını artırabilirsiniz. Gördüğünüz gibi, Nette'de nitelikleri kullanmak işinizi yalnızca kolaylaştırmakla kalmaz, aynı zamanda güvence altına da alabilir. diff --git a/best-practices/tr/composer.texy b/best-practices/tr/composer.texy index 74d2c032f1..c0eb5fb788 100644 --- a/best-practices/tr/composer.texy +++ b/best-practices/tr/composer.texy @@ -1,44 +1,44 @@ -Composer Kullanım İpuçları -************************** +Composer: Kullanım İpuçları +*************************** <div class=perex> -Composer PHP'de bağımlılık yönetimi için bir araçtır. Projenizin bağlı olduğu kütüphaneleri bildirmenize olanak tanır ve bunları sizin için yükler ve günceller. Öğreneceğiz: +Composer, PHP'de bağımlılıkları yönetmek için bir araçtır. Projemizin bağlı olduğu kütüphaneleri listelememize olanak tanır ve bunları bizim için kurar ve günceller. Şunları göstereceğiz: - Composer nasıl kurulur -- yeni veya mevcut projede kullanın +- yeni veya mevcut bir projede kullanımı </div> -Kurulum .[#toc-installation] -============================ +Kurulum +======= -Composer, aşağıdaki şekilde indirip kurduğunuz çalıştırılabilir bir `.phar` dosyasıdır. +Composer, aşağıdaki şekilde indirip kuracağınız çalıştırılabilir bir `.phar` dosyasıdır: -Pencereler .[#toc-windows] --------------------------- +Windows +------- -[Composer-Setup.exe |https://getcomposer.org/Composer-Setup.exe] resmi yükleyicisini kullanın. +Resmi [Composer-Setup.exe |https://getcomposer.org/Composer-Setup.exe] yükleyicisini kullanın. -Linux, macOS .[#toc-linux-macos] --------------------------------- +Linux, macOS +------------ -İhtiyacınız olan tek şey [bu sayfadan |https://getcomposer.org/download/] kopyalayabileceğiniz 4 komuttur. +[Bu sayfadan |https://getcomposer.org/download/] kopyalayacağınız sadece 4 komut yeterlidir. -Dahası, sistemin `PATH` adresindeki klasöre kopyalandığında Composer global olarak erişilebilir hale gelir: +Ayrıca, sistem `PATH`'inde bulunan bir klasöre yerleştirerek, Composer genel olarak erişilebilir hale gelir: ```shell -$ mv ./composer.phar ~/bin/composer # or /usr/local/bin/composer +$ mv ./composer.phar ~/bin/composer # veya /usr/local/bin/composer ``` -Projede Kullanım .[#toc-use-in-project] -======================================= +Projede Kullanım +================ -Projenizde Composer'ı kullanmaya başlamak için tek ihtiyacınız olan bir `composer.json` dosyasıdır. Bu dosya projenizin bağımlılıklarını tanımlar ve başka meta veriler de içerebilir. En basit `composer.json` şu şekilde görünebilir: +Projemizde Composer kullanmaya başlamak için yalnızca `composer.json` dosyasına ihtiyacımız var. Bu dosya projemizin bağımlılıklarını tanımlar ve ayrıca ek meta veriler içerebilir. Temel bir `composer.json` dosyası şöyle görünebilir: ```js { @@ -48,17 +48,17 @@ Projenizde Composer'ı kullanmaya başlamak için tek ihtiyacınız olan bir `co } ``` -Burada, uygulamamızın (veya kütüphanemizin) `nette/database` paketine bağlı olduğunu (paket adı bir satıcı adı ve proje adından oluşur) ve `^3.0` sürüm kısıtlamasına uyan sürümü istediğini söylüyoruz. +Burada uygulamamızın (veya kütüphanemizin) `nette/database` paketini (paket adı kuruluş adı ve proje adından oluşur) gerektirdiğini ve `^3.0` koşuluna uyan sürümü (yani en son 3 sürümünü) istediğini söylüyoruz. -Yani, proje kökünde `composer.json` dosyası olduğunda ve çalıştırdığımızda: +Yani projenin kökünde `composer.json` dosyamız var ve kurulumu başlatıyoruz: ```shell composer update ``` -Composer Nette Veritabanını `vendor` dizinine indirecektir. Ayrıca, tam olarak hangi kütüphane sürümlerini yüklediği hakkında bilgi içeren bir `composer.lock` dosyası oluşturur. +Composer, Nette Database'i `vendor/` klasörüne indirecektir. Ayrıca, tam olarak hangi kütüphane sürümlerini kurduğu hakkında bilgi içeren `composer.lock` dosyasını oluşturacaktır. -Composer bir `vendor/autoload.php` dosyası oluşturur. Bu dosyayı kolayca dahil edebilir ve bu kütüphanelerin sağladığı sınıfları herhangi bir ekstra çalışma yapmadan kullanmaya başlayabilirsiniz: +Composer, `vendor/autoload.php` dosyasını oluşturur, bunu basitçe dahil edebilir ve başka herhangi bir iş yapmadan kütüphaneleri kullanmaya başlayabiliriz: ```php require __DIR__ . '/vendor/autoload.php'; @@ -67,52 +67,52 @@ $db = new Nette\Database\Connection('sqlite::memory:'); ``` -Paketleri En Son Sürümlere Güncelleyin .[#toc-update-packages-to-the-latest-versions] -===================================================================================== +Paketleri En Son Sürümlere Güncelleme +===================================== -Kullanılan tüm paketleri `composer.json` adresinde tanımlanan sürüm kısıtlamalarına göre en son sürüme güncellemek için `composer update` komutunu kullanın. Örneğin `"nette/database": "^3.0"` bağımlılığı için en son 3.x.x sürümünü yükleyecek, ancak sürüm 4'ü yüklemeyecektir. +Kullanılan kütüphaneleri `composer.json`'da tanımlanan koşullara göre en son sürümlere güncellemek `composer update` komutunun sorumluluğundadır. Örneğin, `"nette/database": "^3.0"` bağımlılığı için en son 3.x.x sürümünü kurar, ancak 4 sürümünü kurmaz. -`composer.json` dosyasındaki sürüm kısıtlamalarını örneğin `"nette/database": "^4.1"` olarak güncellemek ve en son sürümü yüklemeyi etkinleştirmek için `composer require nette/database` komutunu kullanın. +En son sürümü kurabilmek için `composer.json` dosyasındaki koşulları örneğin `"nette/database": "^4.1"` olarak güncellemek için `composer require nette/database` komutunu kullanın. -Kullanılan tüm Nette paketlerini güncellemek için, hepsini komut satırında listelemek gerekir, örn: +Kullanılan tüm Nette paketlerini güncellemek için hepsini komut satırında listelemek gerekirdi, örn.: ```shell composer require nette/application nette/forms latte/latte tracy/tracy ... ``` -Bu da pratik değildir. Bu nedenle, bunu sizin için yapacak basit bir komut dosyası "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff kullanın: +Bu pratik değildir. Bu yüzden bunu sizin için yapacak basit "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff betiğini kullanın: ```shell php composer-frontline.php ``` -Yeni Proje Oluşturma .[#toc-creating-new-project] -================================================= +Yeni Proje Oluşturma +==================== -Yeni Nette projesi basit bir komut çalıştırılarak oluşturulabilir: +Nette üzerinde yeni bir proje tek bir komutla oluşturulur: ```shell -composer create-project nette/web-project name-of-the-project +composer create-project nette/web-project proje-adi ``` -Bunun yerine `name-of-the-project` adresine projenizin dizininin adını girmeli ve komutu çalıştırmalısınız. Composer, `composer.json` dosyasını zaten içeren GitHub'dan `nette/web-project` deposunu getirecek ve hemen ardından Nette Framework'ün kendisini yükleyecektir. Geriye kalan tek şey `temp/` ve `log/` dizinleri üzerindeki [yazma izinlerini kontrol |nette:troubleshooting#setting-directory-permissions] etmektir ve artık hazırsınız. +`proje-adi` olarak projeniz için dizin adını girin ve onaylayın. Composer, zaten `composer.json` dosyasını içeren `nette/web-project` deposunu GitHub'dan indirecek ve hemen ardından Nette Framework'ü indirecektir. Artık yalnızca `temp/` ve `log/` klasörlerine yazma [izinlerini ayarlamak |nette:troubleshooting#Dizin İzinlerini Ayarlama] yeterli olmalı ve proje canlanmalıdır. -Projenin hangi PHP sürümünde barındırılacağını biliyorsanız, [bunu ayarladığınızdan |#PHP Version] emin olun. +Projenin hangi PHP sürümünde barındırılacağını biliyorsanız, [onu ayarlamayı |#PHP Sürümü] unutmayın. -PHP Sürümü .[#toc-php-version] -============================== +PHP Sürümü +========== -Composer her zaman kullanmakta olduğunuz PHP sürümüyle (ya da Composer'ı çalıştırdığınızda komut satırında kullanılan PHP sürümüyle) uyumlu olan paket sürümlerini yükler. Bu sürüm muhtemelen web barındırıcınızın kullandığı sürümle aynı değildir. Bu nedenle `composer.json` dosyanıza barındırıcınızdaki PHP sürümü hakkında bilgi eklemeniz çok önemlidir. Bundan sonra, yalnızca ana bilgisayarla uyumlu paket sürümleri yüklenecektir. +Composer her zaman kullandığınız PHP sürümüyle uyumlu paket sürümlerini kurar (daha doğrusu Composer'ı çalıştırırken komut satırında kullanılan PHP sürümüyle). Ancak bu muhtemelen barındırma hizmetinizin kullandığı sürümle aynı değildir. Bu nedenle, barındırmadaki PHP sürümü hakkındaki bilgiyi `composer.json` dosyasına eklemek çok önemlidir. Ardından yalnızca barındırma ile uyumlu paket sürümleri kurulacaktır. -Örneğin, projeyi PHP 8.2.3 üzerinde çalışacak şekilde ayarlamak için şu komutu kullanın: +Projenin örneğin PHP 8.2.3 üzerinde çalışacağını şu komutla ayarlarız: ```shell composer config platform.php 8.2.3 ``` -Sürüm `composer.json` dosyasına bu şekilde yazılır: +Sürüm `composer.json` dosyasına şu şekilde yazılır: ```js { @@ -124,8 +124,7 @@ Sürüm `composer.json` dosyasına bu şekilde yazılır: } ``` -Bununla birlikte, PHP sürüm numarası dosyanın başka bir yerinde, `require` bölümünde de listelenir. İlk numara paketlerin hangi sürüm için yükleneceğini belirtirken, ikinci numara uygulamanın kendisinin hangi sürüm için yazıldığını söyler. -(Tabii ki, bu sürümlerin farklı olması mantıklı değildir, bu nedenle çift giriş bir fazlalıktır). Bu sürümü şu komutla ayarlarsınız: +Ancak, PHP sürüm numarası dosyanın başka bir yerinde, `require` bölümünde de belirtilir. İlk sayı, paketlerin hangi sürüm için kurulacağını belirlerken, ikinci sayı uygulamanın kendisinin hangi sürüm için yazıldığını söyler. Ve örneğin PhpStorm, *PHP dil seviyesini* buna göre ayarlar. (Elbette bu sürümlerin farklı olmasının bir anlamı yoktur, bu yüzden çift kayıt düşüncesizliktir.) Bu sürümü şu komutla ayarlarsınız: ```shell composer require php 8.2.3 --no-update @@ -142,66 +141,72 @@ Veya doğrudan `composer.json` dosyasında: ``` -Yanlış Raporlar .[#toc-false-reports] -===================================== +PHP Sürümünü Yoksayma +===================== + +Paketler genellikle hem uyumlu oldukları en düşük PHP sürümünü hem de test edildikleri en yüksek sürümü belirtirler. Henüz daha yeni bir PHP sürümü kullanmayı planlıyorsanız, örneğin test amacıyla, Composer böyle bir paketi kurmayı reddedecektir. Çözüm, Composer'ın gerekli PHP sürümünün üst sınırlarını yoksaymasına neden olan `--ignore-platform-req=php+` seçeneğidir. -Paketleri yükseltirken veya sürüm numaralarını değiştirirken çakışmalar meydana gelir. Bir paketin gereksinimleri diğeriyle çakışır ve bu böyle devam eder. Ancak, Composer bazen yanlış bir mesaj yazdırır. Gerçekte var olmayan bir çakışma bildirir. Bu durumda, `composer.lock` dosyasını silmek ve tekrar denemek yardımcı olur. -Hata mesajı devam ederse, ciddi bir mesajdır ve neyi nasıl değiştireceğinizi okumanız gerekir. +Yanıltıcı Bildirimler +===================== +Paketleri yükseltirken veya sürüm numaralarını değiştirirken çakışmalar meydana gelebilir. Bir paket, başka bir paketle çelişen gereksinimlere sahip olabilir vb. Ancak Composer bazen yanıltıcı bildirimler yazdırır. Gerçekte var olmayan bir çakışma bildirir. Bu durumda, `composer.lock` dosyasını silmek ve tekrar denemek yardımcı olur. -Packagist.org - Küresel Depo .[#toc-packagist-org-global-repository] -==================================================================== +Hata mesajı devam ederse, ciddiye alınmalı ve neyin nasıl ayarlanacağını anlamak için okunmalıdır. -[Packagist |https://packagist.org], aksi söylenmediği takdirde Composer'ın paketleri aramaya çalıştığı ana paket deposudur. Kendi paketlerinizi de burada yayınlayabilirsiniz. +Packagist.org - Merkezi Depo +============================ + +[Packagist |https://packagist.org], Composer'ın aksi belirtilmedikçe paketleri aramaya çalıştığı ana depodur. Burada kendi paketlerimizi de yayınlayabiliriz. -Ya Merkezi Depoyu İstemiyorsak .[#toc-what-if-we-don-t-want-the-central-repository] ------------------------------------------------------------------------------------ -Şirketimizde Packagist'te herkese açık olarak barındırılamayan dahili uygulamalarımız veya kütüphanelerimiz varsa, bu projeler için kendi depolarımızı oluşturabiliriz. +Merkezi Depoyu Kullanmak İstemezsek Ne Olur? +-------------------------------------------- -[Resmi belgelerde |https://getcomposer.org/doc/05-repositories.md#repositories] depolar hakkında daha fazla bilgi bulabilirsiniz. +Şirket içi uygulamalarımız varsa ve bunları kamuya açık olarak barındıramıyorsak, onlar için bir şirket deposu oluştururuz. +Depolar hakkında daha fazla bilgi [resmi belgelerde |https://getcomposer.org/doc/05-repositories.md#repositories]. -Otomatik Yükleme .[#toc-autoloading] -==================================== -Composer'ın önemli bir özelliği, yüklediği tüm sınıflar için otomatik yükleme sağlamasıdır; bunu `vendor/autoload.php` dosyasını ekleyerek başlatırsınız. +Otomatik Yükleme (Autoloading) +============================== -Ancak, Composer'ı `vendor` klasörü dışındaki diğer sınıfları yüklemek için kullanmak da mümkündür. İlk seçenek, Composer'ın tanımlı klasörleri ve alt klasörleri taramasına, tüm sınıfları bulmasına ve bunları otomatik yükleyiciye dahil etmesine izin vermektir. Bunu yapmak için `autoload > classmap` adresini `composer.json` olarak ayarlayın: +Composer'ın temel bir özelliği, kurduğu tüm sınıflar için otomatik yükleme sağlamasıdır; bunu `vendor/autoload.php` dosyasını dahil ederek başlatırsınız. + +Ancak, Composer'ı `vendor` klasörü dışındaki diğer sınıfları yüklemek için de kullanmak mümkündür. İlk seçenek, Composer'ın tanımlanmış klasörleri ve alt klasörleri taramasını, tüm sınıfları bulmasını ve bunları otomatik yükleyiciye dahil etmesini sağlamaktır. Bunu `composer.json`'da `autoload > classmap` ayarlayarak başarırsınız: ```js { "autoload": { "classmap": [ - "src/", # includes the src/ folder and its subfolders + "src/", # src/ klasörünü ve alt klasörlerini dahil eder ] } } ``` -Daha sonra, her değişiklikte `composer dumpautoload` komutunu çalıştırmak ve otomatik yükleme tablolarının yeniden oluşturulmasına izin vermek gerekir. Bu son derece zahmetlidir ve bu görevi, aynı etkinliği arka planda otomatik olarak ve çok daha hızlı gerçekleştiren [RobotLoader'a |robot-loader:] emanet etmek çok daha iyidir. +Ardından, her değişiklikte `composer dumpautoload` komutunu çalıştırmak ve otomatik yükleme tablolarını yeniden oluşturmak gerekir. Bu son derece zahmetlidir ve bu görevi aynı işlemi arka planda otomatik olarak ve çok daha hızlı gerçekleştiren [RobotLoader|robot-loader:]'a devretmek çok daha iyidir. -İkinci seçenek [PSR-4'ü |https://www.php-fig.org/psr/psr-4/] takip etmektir. Basitçe söylemek gerekirse, ad alanlarının ve sınıf adlarının dizin yapısına ve dosya adlarına karşılık geldiği bir sistemdir, yani `App\Router\RouterFactory`, `/path/to/App/Router/RouterFactory.php` dosyasında bulunur. Yapılandırma örneği: +İkinci seçenek [PSR-4|https://www.php-fig.org/psr/psr-4/]'e uymaktır. Basitçe ifade etmek gerekirse, bu, isim alanlarının ve sınıf adlarının dizin yapısına ve dosya adlarına karşılık geldiği bir sistemdir, yani örn. `App\Core\RouterFactory`, `/path/to/App/Core/RouterFactory.php` dosyasında olacaktır. Yapılandırma örneği: ```js { "autoload": { "psr-4": { - "App\\": "app/" # the App\ namespace is in the app/ directory + "App\\": "app/" # App\ isim alanı app/ dizinindedir } } } ``` -Bu davranışın tam olarak nasıl yapılandırılacağını öğrenmek için [Composer Belgelerine |https://getcomposer.org/doc/04-schema.md#psr-4] bakın. +Davranışın tam olarak nasıl yapılandırılacağını [Composer belgelerinde|https://getcomposer.org/doc/04-schema.md#psr-4] öğrenebilirsiniz. -Yeni Sürümlerin Test Edilmesi .[#toc-testing-new-versions] -========================================================== +Yeni Sürümleri Test Etme +======================== -Bir paketin yeni bir geliştirme sürümünü test etmek istiyorsunuz. Bunu nasıl yapabilirsiniz? Öncelikle, `composer.json` dosyasına paketlerin geliştirme sürümlerini yüklemenize izin verecek, ancak bunu yalnızca gereksinimleri karşılayan kararlı sürüm kombinasyonu yoksa yapacak olan bu seçenek çiftini ekleyin: +Bir paketin yeni bir geliştirme sürümünü test etmek istiyorsunuz. Nasıl yapılır? Öncelikle `composer.json` dosyasına, paketlerin geliştirme sürümlerinin kurulmasına izin veren, ancak yalnızca gereksinimleri karşılayan kararlı sürüm kombinasyonu yoksa buna başvuran şu çift seçeneği ekleyin: ```js { @@ -210,34 +215,33 @@ Bir paketin yeni bir geliştirme sürümünü test etmek istiyorsunuz. Bunu nas } ``` -Ayrıca `composer.lock` dosyasını silmenizi öneririz, çünkü bazen Composer anlaşılmaz bir şekilde yüklemeyi reddeder ve bu sorunu çözecektir. +Ayrıca `composer.lock` dosyasını silmenizi öneririz, bazen Composer anlaşılmaz bir şekilde kurulumu reddeder ve bu sorunu çözer. -Paketin `nette/utils` olduğunu ve yeni sürümün 4.0 olduğunu varsayalım. Şu komut ile yüklüyorsunuz: +Diyelim ki paket `nette/utils` ve yeni sürümün numarası 4.0. Şu komutla kurarsınız: ```shell composer require nette/utils:4.0.x-dev ``` -Ya da 4.0.0-RC2 gibi belirli bir sürümü yükleyebilirsiniz: +Veya belirli bir sürümü kurabilirsiniz, örneğin 4.0.0-RC2: ```shell composer require nette/utils:4.0.0-RC2 ``` -Başka bir paket kütüphaneye bağlıysa ve eski bir sürüme kilitliyse (örn. `^3.1`), paketi yeni sürümle çalışacak şekilde güncellemek idealdir. -Ancak, sınırlamayı aşmak ve Composer'ı geliştirme sürümünü yüklemeye ve eski bir sürümmüş gibi davranmaya zorlamak istiyorsanız (örneğin, 3.1.6), `as` anahtar sözcüğünü kullanabilirsiniz: +Ancak kütüphaneye daha eski bir sürüme kilitlenmiş başka bir paket bağlıysa (örn. `^3.1`), o zaman paketi yeni sürümle çalışacak şekilde güncellemek idealdir. Ancak yalnızca kısıtlamayı aşmak ve Composer'ı geliştirme sürümünü kurmaya ve daha eski bir sürüm (örn. 3.1.6) gibi davranmaya zorlamak istiyorsanız, `as` anahtar kelimesini kullanabilirsiniz: ```shell composer require nette/utils "4.0.x-dev as 3.1.6" ``` -Çağrı Komutları .[#toc-calling-commands] -======================================== +Komutları Çağırma +================= -Kendi özel komutlarınızı ve komut dosyalarınızı Composer aracılığıyla yerel Composer komutlarıymış gibi çağırabilirsiniz. `vendor/bin` klasöründe bulunan komut dosyalarının bu klasörü belirtmesine gerek yoktur. +Composer aracılığıyla, sanki yerel Composer komutlarıymış gibi kendi önceden hazırlanmış komutlarınızı ve betiklerinizi çağırabilirsiniz. `vendor/bin` klasöründe bulunan betikler için bu klasörü belirtmeye gerek yoktur. -Örnek olarak, `composer.json` dosyasında testleri çalıştırmak için [Nette Tester |tester:] 'ı kullanan bir komut dosyası tanımlıyoruz: +Örnek olarak, `composer.json` dosyasında [Nette Tester|tester:] kullanarak testleri çalıştıran bir betik tanımlayalım: ```js { @@ -247,34 +251,32 @@ Kendi özel komutlarınızı ve komut dosyalarınızı Composer aracılığıyla } ``` -Daha sonra testleri `composer tester` ile çalıştırıyoruz. Projenin kök klasöründe değil, bir alt dizinde olsak bile komutu çağırabiliriz. +Testleri daha sonra `composer tester` kullanarak çalıştırırız. Komutu, projenin kök klasöründe olmasak bile, bazı alt dizinlerde olsak bile çağırabiliriz. -Teşekkür Gönder .[#toc-send-thanks] -=================================== +Teşekkür Gönderin +================= -Size açık kaynak yazarlarını mutlu edecek bir numara göstereceğiz. Projenizin kullandığı kütüphanelere GitHub'da kolayca bir yıldız verebilirsiniz. Sadece `symfony/thanks` kütüphanesini yükleyin: +Size açık kaynak yazarlarını memnun edecek bir numara göstereceğiz. Projenizin kullandığı kütüphanelere GitHub'da basit bir şekilde yıldız verebilirsiniz. Sadece `symfony/thanks` kütüphanesini kurmanız yeterlidir: ```shell composer global require symfony/thanks ``` -Ve sonra koş: +Ve sonra çalıştırın: ```shell composer thanks ``` -Dene bakalım! +Deneyin! -Konfigürasyon .[#toc-configuration] -=================================== +Yapılandırma +============ -Composer, sürüm kontrol aracı [Git |https://git-scm.com] ile yakından entegre edilmiştir. Eğer Git kullanmıyorsanız bunu Composer'a söylemeniz gerekmektedir: +Composer, [Git |https://git-scm.com] sürüm kontrol aracıyla yakından bağlantılıdır. Eğer kurulu değilse, Composer'a onu kullanmamasını söylemeniz gerekir: ```shell composer -g config preferred-install dist ``` - -{{sitename: En İyi Uygulamalar}} diff --git a/best-practices/tr/creating-editing-form.texy b/best-practices/tr/creating-editing-form.texy index b99b748101..0274a0e122 100644 --- a/best-practices/tr/creating-editing-form.texy +++ b/best-practices/tr/creating-editing-form.texy @@ -2,15 +2,15 @@ Kayıt Oluşturma ve Düzenleme Formu ********************************** .[perex] -Her ikisi için de aynı formu kullanarak Nette'de kayıt ekleme ve düzenleme nasıl düzgün bir şekilde uygulanır? +Nette'de, her ikisi için de aynı formu kullanarak bir kaydın eklenmesini ve düzenlenmesini nasıl doğru bir şekilde uygulayabiliriz? -Birçok durumda, kayıt ekleme ve düzenleme formları aynıdır, sadece düğme üzerindeki etiket farklıdır. Formu önce bir kayıt eklemek, sonra düzenlemek ve son olarak iki çözümü birleştirmek için kullandığımız basit sunum örneklerini göstereceğiz. +Birçok durumda, kayıt ekleme ve düzenleme formları aynıdır, belki sadece düğme üzerindeki etiket farklıdır. Formu önce kayıt eklemek, sonra düzenlemek için kullanacağımız ve son olarak her iki çözümü birleştireceğimiz basit presenter örneklerini göstereceğiz. -Kayıt Ekleme .[#toc-adding-a-record] ------------------------------------- +Kayıt Ekleme +------------ -Kayıt eklemek için kullanılan bir sunum örneği. Gerçek veritabanı işini, kodu örnekle ilgili olmayan `Facade` sınıfına bırakacağız. +Kayıt eklemeye hizmet eden bir presenter örneği. Veritabanıyla olan asıl işi, kodu gösterim için önemli olmayan `Facade` sınıfına bırakacağız. ```php @@ -27,7 +27,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $form = new Form; - // ... form alanları ekle ... + // ... form alanlarını ekleyin ... $form->onSuccess[] = [$this, 'recordFormSucceeded']; return $form; @@ -35,7 +35,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter public function recordFormSucceeded(Form $form, array $data): void { - $this->facade->add($data); // veritabanına kayıt ekle + $this->facade->add($data); // veritabanına kayıt ekleme $this->flashMessage('Başarıyla eklendi'); $this->redirect('...'); } @@ -48,10 +48,10 @@ class RecordPresenter extends Nette\Application\UI\Presenter ``` -Bir Kaydı Düzenleme .[#toc-editing-a-record] --------------------------------------------- +Kayıt Düzenleme +--------------- -Şimdi bir kaydı düzenlemek için kullanılan bir sunucunun nasıl görüneceğini görelim: +Şimdi bir kaydı düzenlemeye hizmet eden presenter'ın nasıl görüneceğini göstereceğiz: ```php @@ -70,10 +70,10 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $record = $this->facade->get($id); if ( - !$record // kaydın varlığını doğrulayın - || !$this->facade->isEditAllowed(/*...*/) // izinleri kontrol et + !$record // kaydın varlığının doğrulanması + || !$this->facade->isEditAllowed(/*...*/) // yetki kontrolü ) { - $this->error(); // 404 hatası + $this->error(); // hata 404 } $this->record = $record; @@ -81,32 +81,32 @@ class RecordPresenter extends Nette\Application\UI\Presenter protected function createComponentRecordForm(): Form { - // eylemin 'düzenle' olduğunu doğrulayın + // eylemin 'edit' olduğunu doğrulayın if ($this->getAction() !== 'edit') { $this->error(); } $form = new Form; - // ... form alanları ekle ... + // ... form alanlarını ekleyin ... - $form->setDefaults($this->record); // varsayılan değerleri ayarla + $form->setDefaults($this->record); // varsayılan değerlerin ayarlanması $form->onSuccess[] = [$this, 'recordFormSucceeded']; return $form; } public function recordFormSucceeded(Form $form, array $data): void { - $this->facade->update($this->record->id, $data); // kaydı güncelle + $this->facade->update($this->record->id, $data); // kaydın güncellenmesi $this->flashMessage('Başarıyla güncellendi'); $this->redirect('...'); } } ``` - [Sunucu yaşam döngüsünün |application:presenters#Life Cycle of Presenter] hemen başında çağrılan *action* yönteminde, kaydın varlığını ve kullanıcının düzenleme iznini doğrularız. +[Presenter yaşam döngüsünün |application:presenters#Presenter Yaşam Döngüsü] hemen başında çalışan *action* metodunda, kaydın varlığını ve kullanıcının onu düzenleme iznini doğrularız. -Kaydı `$record` özelliğinde saklarız, böylece varsayılan değerleri ayarlamak için `createComponentRecordForm()` yönteminde ve ID için `recordFormSucceeded()` yönteminde kullanılabilir. Alternatif bir çözüm, varsayılan değerleri doğrudan `actionEdit()` adresinde ayarlamak ve URL'nin bir parçası olan kimlik değerini `getParameter('id')` adresini kullanarak almak olabilir: +Kaydı `$record` özelliğinde saklarız, böylece varsayılan değerleri ayarlamak için `createComponentRecordForm()` metodunda ve ID için `recordFormSucceeded()` metodunda kullanılabilir olur. Alternatif bir çözüm, varsayılan değerleri doğrudan `actionEdit()` içinde ayarlamak ve URL'nin bir parçası olan ID değerini `getParameter('id')` kullanarak almaktır: ```php @@ -114,12 +114,12 @@ Kaydı `$record` özelliğinde saklarız, böylece varsayılan değerleri ayarla { $record = $this->facade->get($id); if ( - // varlığı doğrulayın ve izinleri kontrol edin + // varlığın doğrulanması ve yetki kontrolü ) { $this->error(); } - // varsayılan form değerlerini ayarlama + // formun varsayılan değerlerini ayarlama $this->getComponent('recordForm') ->setDefaults($record); } @@ -133,13 +133,13 @@ Kaydı `$record` özelliğinde saklarız, böylece varsayılan değerleri ayarla } ``` -Ancak, **tüm kodlardan çıkarılacak en önemli sonuç** bu olmalıdır, formu oluştururken eylemin gerçekten `edit` olduğundan emin olmamız gerekir. Çünkü aksi takdirde `actionEdit()` yöntemindeki doğrulama hiç gerçekleşmez! +Ancak, ve bu **tüm kodun en önemli çıkarımı olmalı**, formu oluştururken eylemin gerçekten `edit` olduğundan emin olmalıyız. Aksi takdirde, `actionEdit()` metodundaki doğrulama hiç gerçekleşmezdi! -Ekleme ve Düzenleme için Aynı Form .[#toc-same-form-for-adding-and-editing] ---------------------------------------------------------------------------- +Ekleme ve Düzenleme için Aynı Form +---------------------------------- -Ve şimdi her iki sunucuyu bir araya getireceğiz. Ya `createComponentRecordForm()` yönteminde hangi eylemin yer aldığını ayırt edip formu buna göre yapılandırabiliriz ya da doğrudan eylem yöntemlerine bırakıp koşuldan kurtulabiliriz: +Ve şimdi her iki presenter'ı tek bir tanede birleştireceğiz. Ya `createComponentRecordForm()` metodunda hangi eylemin söz konusu olduğunu ayırt edebilir ve formu buna göre yapılandırabiliriz ya da bunu doğrudan action metotlarına bırakıp koşuldan kurtulabiliriz: ```php @@ -160,34 +160,34 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $record = $this->facade->get($id); if ( - !$record // kaydın varlığını doğrulamak - || !$this->facade->isEditAllowed(/*...*/) // izinleri kontrol edin + !$record // kaydın varlığının doğrulanması + || !$this->facade->isEditAllowed(/*...*/) // yetki kontrolü ) { - $this->error(); // 404 hatası + $this->error(); // hata 404 } $form = $this->getComponent('recordForm'); - $form->setDefaults($record); // varsayılanları ayarla + $form->setDefaults($record); // varsayılan değerlerin ayarlanması $form->onSuccess[] = [$this, 'editingFormSucceeded']; } protected function createComponentRecordForm(): Form { - // eylemin 'ekle' veya 'düzenle' olduğunu doğrulayın + // eylemin 'add' veya 'edit' olduğunu doğrulayın if (!in_array($this->getAction(), ['add', 'edit'])) { $this->error(); } $form = new Form; - // ... form alanları ekle ... + // ... form alanlarını ekleyin ... return $form; } public function addingFormSucceeded(Form $form, array $data): void { - $this->facade->add($data); // veritabanına kayıt ekle + $this->facade->add($data); // veritabanına kayıt ekleme $this->flashMessage('Başarıyla eklendi'); $this->redirect('...'); } @@ -195,7 +195,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter public function editingFormSucceeded(Form $form, array $data): void { $id = (int) $this->getParameter('id'); - $this->facade->update($id, $data); // kaydı güncelle + $this->facade->update($id, $data); // kaydın güncellenmesi $this->flashMessage('Başarıyla güncellendi'); $this->redirect('...'); } @@ -203,4 +203,3 @@ class RecordPresenter extends Nette\Application\UI\Presenter ``` {{priority: -1}} -{{sitename: En İyi Uygulamalar}} diff --git a/best-practices/tr/dynamic-snippets.texy b/best-practices/tr/dynamic-snippets.texy index bae43f06d3..c805aeedb5 100644 --- a/best-practices/tr/dynamic-snippets.texy +++ b/best-practices/tr/dynamic-snippets.texy @@ -1,7 +1,7 @@ -Dinamik Parçacıklar +Dinamik Snippet'ler ******************* -Uygulama geliştirmede sıklıkla, örneğin bir tablonun tek tek satırlarında veya liste öğelerinde AJAX işlemleri gerçekleştirme ihtiyacı vardır. Örnek olarak, giriş yapan kullanıcının her biri için bir "beğenme/beğenmeme" derecesi seçmesine olanak tanıyan makaleleri listelemeyi seçebiliriz. Sunucunun kodu ve AJAX olmadan ilgili şablon aşağıdaki gibi görünecektir (en önemli parçacıkları listeliyorum, kod, derecelendirmeleri işaretlemek ve bir makale koleksiyonu almak için bir hizmetin varlığını varsayar - özel uygulama bu eğitimin amaçları için önemli değildir): +Uygulama geliştirirken, örneğin bir tablonun tek tek satırları veya bir listenin öğeleri üzerinde AJAX işlemleri yapma ihtiyacı oldukça sık ortaya çıkar. Örnek olarak, makalelerin bir listesini seçebiliriz, burada her makale için giriş yapmış kullanıcının "beğen/beğenme" derecelendirmesini seçmesine izin veririz. AJAX olmadan presenter ve ilgili şablonun kodu yaklaşık olarak aşağıdaki gibi görünecektir (en önemli bölümleri listeliyorum, kod derecelendirmeleri işaretlemek ve makale koleksiyonunu almak için bir servisin varlığını varsayar - belirli uygulama bu kılavuzun amaçları için önemli değildir): ```php public function handleLike(int $articleId): void @@ -24,26 +24,26 @@ public function handleUnlike(int $articleId): void <h2>{$article->title}</h2> <div class="content">{$article->content}</div> {if !$article->liked} - <a n:href="like! $article->id" class=ajax>I like it</a> + <a n:href="like! $article->id" class=ajax>Beğen</a> {else} - <a n:href="unlike! $article->id" class=ajax>I don't like it anymore</a> + <a n:href="unlike! $article->id" class=ajax>Beğenmekten Vazgeç</a> {/if} </article> ``` -Ajaxlaştırma .[#toc-ajaxization] -================================ +AJAXlaştırma +============ -Şimdi bu basit uygulamaya AJAX'ı getirelim. Bir makalenin derecelendirmesini değiştirmek, yönlendirmeli bir HTTP isteği gerektirecek kadar önemli değildir, bu nedenle ideal olarak arka planda AJAX ile yapılmalıdır. AJAX bağlantılarının `ajax` CSS sınıfına sahip olduğu olağan kuralıyla [eklentilerdeki işleyici |https://componette.org/vojtech-dobes/nette.ajax.js/] komut dosyasını kullanacağız. +Şimdi bu basit uygulamayı AJAX ile donatalım. Bir makalenin derecelendirmesini değiştirmek, bir yönlendirme gerektirecek kadar önemli değildir ve bu nedenle ideal olarak arka planda AJAX ile gerçekleşmelidir. [Eklentilerden yardımcı betiği |application:ajax#Naja] AJAX bağlantılarının `ajax` CSS sınıfına sahip olduğu olağan kuralıyla kullanacağız. -Ancak, bunu özellikle nasıl yapmalı? Nette 2 yol sunuyor: dinamik snippet yolu ve bileşen yolu. Her ikisinin de artıları ve eksileri var, bu yüzden bunları tek tek göstereceğiz. +Ancak, bunu tam olarak nasıl yapacağız? Nette 2 yol sunar: dinamik snippet'ler yolu ve bileşenler yolu. Her ikisinin de artıları ve eksileri vardır, bu yüzden onları birer birer göstereceğiz. -Dinamik Parçacıklar Yolu .[#toc-the-dynamic-snippets-way] -========================================================= +Dinamik Snippet Yolu +==================== -Latte terminolojisinde, dinamik bir snippet, snippet adında bir değişkenin kullanıldığı `{snippet}` etiketinin özel bir kullanım durumudur. Böyle bir snippet şablonun herhangi bir yerinde bulunamaz - statik bir snippet, yani normal bir snippet tarafından veya bir `{snippetArea}` içinde sarılmalıdır. Şablonumuzu aşağıdaki gibi değiştirebiliriz. +Latte terminolojisinde dinamik bir snippet, snippet adında bir değişkenin kullanıldığı `{snippet}` etiketinin özel bir kullanım durumunu ifade eder. Böyle bir snippet şablonda herhangi bir yerde bulunamaz - statik bir snippet, yani sıradan bir snippet veya `{snippetArea}` içinde sarmalanmalıdır. Şablonumuzu aşağıdaki gibi değiştirebiliriz. ```latte @@ -53,18 +53,18 @@ Latte terminolojisinde, dinamik bir snippet, snippet adında bir değişkenin ku <div class="content">{$article->content}</div> {snippet article-{$article->id}} {if !$article->liked} - <a n:href="like! $article->id" class=ajax>I like it</a> + <a n:href="like! $article->id" class=ajax>Beğen</a> {else} - <a n:href="unlike! $article->id" class=ajax>I don't like it anymore</a> + <a n:href="unlike! $article->id" class=ajax>Beğenmekten Vazgeç</a> {/if} {/snippet} </article> {/snippet} ``` -Artık her makale, başlığında bir makale kimliği bulunan tek bir snippet tanımlamaktadır. Tüm bu snippet'ler daha sonra `articlesContainer` adlı tek bir snippet'te bir araya getirilir. Bu sarmalama parçacığını atlarsak, Latte bizi bir istisna ile uyaracaktır. +Her makale şimdi adında makale ID'si bulunan bir snippet tanımlar. Tüm bu snippet'ler daha sonra `articlesContainer` adlı tek bir snippet ile birlikte sarmalanır. Bu sarmalayıcı snippet'i atlarsak, Latte bizi bir istisna ile uyaracaktır. -Geriye kalan tek şey sunucuya yeniden çizim eklemektir - sadece statik sarmalayıcıyı yeniden çizin. +Geriye presenter'a yeniden çizimi eklemek kalıyor - sadece statik sarmalayıcıyı yeniden çizmek yeterlidir. ```php public function handleLike(int $articleId): void @@ -72,18 +72,18 @@ public function handleLike(int $articleId): void $this->ratingService->saveLike($articleId, $this->user->id); if ($this->isAjax()) { $this->redrawControl('articlesContainer'); - // $this->redrawControl('article-' . $articleId); -- není potřeba + // $this->redrawControl('article-' . $articleId); -- gerekli değil } else { $this->redirect('this'); } } ``` -`handleUnlike()` kardeş yöntemini de aynı şekilde değiştirin ve AJAX çalışmaya başlasın! +Benzer şekilde, kardeş metot `handleUnlike()`'ı da değiştiririz ve AJAX işlevseldir! -Ancak bu çözümün bir dezavantajı var. AJAX isteğinin nasıl çalıştığını daha fazla araştırırsak, uygulamanın görünüşte verimli görünmesine rağmen (belirli bir makale için yalnızca tek bir snippet döndürür), aslında sunucudaki tüm snippet'leri işlediğini görürüz. İstenen snippet'i payload'umuza yerleştirmiş ve diğerlerini atmıştır (böylece, oldukça gereksiz bir şekilde, bunları veritabanından da almıştır). +Ancak çözümün bir dezavantajı var. AJAX isteğinin nasıl ilerlediğini daha fazla incelersek, uygulamanın dışarıdan verimli görünmesine rağmen (belirli makale için yalnızca tek bir snippet döndürür), aslında sunucuda tüm snippet'leri oluşturduğunu fark ederiz. İstenen snippet'i payload'a yerleştirdi ve diğerlerini attı (bu nedenle onları veritabanından tamamen gereksiz yere aldı). -Bu süreci optimize etmek için, `$articles` koleksiyonunu şablona aktardığımız yerde (örneğin `renderDefault()` yönteminde) işlem yapmamız gerekecek. Sinyal işlemenin şablondan önce gerçekleştiği gerçeğinden yararlanacağız. `render<Something>` yöntemler: +Bu süreci optimize etmek için, `$articles` koleksiyonunu şablona ilettiğimiz yere müdahale etmemiz gerekecek (diyelim ki `renderDefault()` metodunda). Sinyal işlemenin `render<Something>` metotlarından önce gerçekleştiği gerçeğinden yararlanacağız: ```php public function handleLike(int $articleId): void @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connection->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,18 +101,18 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connection->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` -Şimdi, sinyal işlendiğinde, tüm makaleleri içeren bir koleksiyon yerine, yalnızca tek bir makaleyi içeren bir dizi şablona aktarılır - tarayıcıya payload olarak göndermek ve render etmek istediğimiz. Böylece, `{foreach}` yalnızca bir kez yapılacak ve fazladan snippet oluşturulmayacaktır. +Şimdi, sinyal işlenirken, tüm makaleleri içeren koleksiyon yerine şablona yalnızca tek bir makale içeren bir dizi iletilir - yani, tarayıcıya payload'da oluşturmak ve göndermek istediğimiz makale. `{foreach}` bu nedenle yalnızca bir kez çalışır ve fazladan snippet oluşturulmaz. -Bileşen Yolu .[#toc-component-way] -================================== +Bileşen Yolu +============ -Tamamen farklı bir çözüm, dinamik snippet'lerden kaçınmak için farklı bir yaklaşım kullanır. İşin püf noktası, tüm mantığı ayrı bir bileşene taşımaktır - şu andan itibaren, derecelendirmeyi girmekle ilgilenecek bir sunumcumuz yok, ancak özel bir `LikeControl`. Sınıf aşağıdaki gibi görünecektir (ek olarak, `render`, `handleUnlike`, vb. yöntemleri de içerecektir): +Tamamen farklı bir çözüm yaklaşımı dinamik snippet'lerden kaçınır. Hile, tüm mantığı özel bir bileşene aktarmaktır - bundan sonra derecelendirme girişi presenter tarafından değil, özel bir `LikeControl` tarafından yönetilecektir. Sınıf aşağıdaki gibi görünecektir (ayrıca `render`, `handleUnlike` vb. metotları da içerecektir): ```php class LikeControl extends Nette\Application\UI\Control @@ -139,26 +139,26 @@ Bileşen şablonu: ```latte {snippet} {if !$article->liked} - <a n:href="like!" class=ajax>I like it</a> + <a n:href="like!" class=ajax>Beğen</a> {else} - <a n:href="unlike!" class=ajax>I don't like it anymore</a> + <a n:href="unlike!" class=ajax>Beğenmekten Vazgeç</a> {/if} {/snippet} ``` -Elbette görünüm şablonunu değiştireceğiz ve sunucuya bir fabrika eklememiz gerekecek. Bileşeni veritabanından makale aldıkça oluşturacağımız için, onu "çarpmak" için [Multiplier |application:Multiplier] sınıfını kullanacağız. +Tabii ki, görünüm şablonumuz değişecek ve presenter'a bir fabrika eklememiz gerekecek. Bileşeni veritabanından aldığımız makale sayısı kadar oluşturacağımız için, onu "çoğaltmak" için [application:Multiplier] sınıfını kullanacağız. ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); } ``` -Şablon görünümü gerekli minimum düzeye indirilmiştir (ve parçacıklardan tamamen arındırılmıştır!): +Görünüm şablonu gerekli minimuma indirildi (ve tamamen snippet'lerden arındırıldı!): ```latte <article n:foreach="$articles as $article"> @@ -168,7 +168,6 @@ protected function createComponentLikeControl() </article> ``` -Neredeyse bitirdik: uygulama artık AJAX'ta çalışacak. Burada da uygulamayı optimize etmek zorundayız, çünkü Nette Veritabanı kullanımı nedeniyle sinyal işleme gereksiz yere veritabanından bir yerine tüm makaleleri yükleyecektir. Bununla birlikte, avantajı, render işlemi olmayacağıdır, çünkü aslında sadece bileşenimiz render edilir. +Neredeyse bitti: uygulama artık AJAX ile çalışacak. Burada da uygulamayı optimize etmemiz gerekiyor, çünkü Nette Database kullanımı nedeniyle, sinyal işlenirken veritabanından tüm makaleler gereksiz yere yüklenir, oysa sadece bir tanesi yeterlidir. Ancak avantajı, bunların oluşturulmamasıdır, çünkü gerçekten sadece bizim bileşenimiz oluşturulur. {{priority: -1}} -{{sitename: En İyi Uygulamalar}} diff --git a/best-practices/tr/editors-and-tools.texy b/best-practices/tr/editors-and-tools.texy index 6c40de4d40..e1822a4311 100644 --- a/best-practices/tr/editors-and-tools.texy +++ b/best-practices/tr/editors-and-tools.texy @@ -1,40 +1,40 @@ -Editörler ve Araçlar -******************** +Editörler & Araçlar +******************* .[perex] -Yetenekli bir programcı olabilirsiniz, ancak sadece iyi araçlarla usta olabilirsiniz. Bu bölümde önemli araçlar, editörler ve eklentiler hakkında ipuçları bulacaksınız. +Yetenekli bir programcı olabilirsiniz, ancak ancak iyi araçlarla bir usta olursunuz. Bu bölümde önemli araçlar, editörler ve eklentiler hakkında ipuçları bulacaksınız. -IDE Editör .[#toc-ide-editor] -============================= +IDE Editörü +=========== -Geliştirme için sadece PHP desteği olan bir metin editörü değil, PhpStorm, NetBeans, VS Code gibi tam özellikli bir IDE kullanmanızı şiddetle tavsiye ederiz. Aradaki fark gerçekten çok önemlidir. Sözdizimi vurgulamalı klasik bir editörle yetinmek için hiçbir neden yoktur, çünkü doğru kod önerisine sahip, kodu yeniden düzenleyebilen ve daha fazlasını yapabilen bir IDE'nin yeteneklerine ulaşamaz. Bazı IDE'ler ücretli, bazıları ise ücretsizdir. +Geliştirme için kesinlikle PhpStorm, NetBeans, VS Code gibi tam özellikli bir IDE kullanmanızı öneririz, sadece PHP desteği olan bir metin editörü değil. Fark gerçekten çok büyük. Sadece sözdizimini renklendirebilen, ancak tam olarak ipucu veren, hataları kontrol eden, kodu yeniden düzenleyebilen ve çok daha fazlasını yapabilen birinci sınıf bir IDE'nin yeteneklerine ulaşamayan bir editörle yetinmek için hiçbir neden yok. Bazı IDE'ler ücretlidir, diğerleri ise ücretsizdir. -**NetBeans IDE** Nette, Latte ve NEON için yerleşik desteğe sahiptir. +**NetBeans IDE** Nette, Latte ve NEON desteği zaten yerleşiktir. -**PhpStorm**: bu eklentileri `Settings > Plugins > Marketplace` adresine yükleyin: -- Nette çerçeve yardımcıları +**PhpStorm**: `Settings > Plugins > Marketplace` bölümünden şu eklentileri yükleyin +- Nette framework helpers - Latte -- NEON desteği +- NEON support - Nette Tester -**VS Kodu**: pazar yerinde "Nette Latte + Neon" eklentisini bulun. +**VS Code**: marketplace'te "Nette Latte + Neon" eklentisini bulun. -Ayrıca Tracy'yi editör ile bağlayın. Hata sayfası görüntülendiğinde, dosya adlarına tıklayabilirsiniz ve imleç ilgili satırda olacak şekilde editörde açılırlar. [Sistemi nasıl yapılandıracağınızı |tracy:open-files-in-ide] öğrenin. +Ayrıca Tracy'yi editörünüzle bağlayın. Hata sayfası görüntülendiğinde, dosya adlarına tıklayabilir ve bunlar editörde ilgili satırda imleçle açılır. [Sistemi nasıl yapılandıracağınızı |tracy:open-files-in-ide] okuyun. -PHPStan .[#toc-phpstan] -======================= +PHPStan +======= -PHPStan, siz çalıştırmadan önce kodunuzdaki mantıksal hataları tespit eden bir araçtır. +PHPStan, kodu çalıştırmadan önce mantıksal hataları ortaya çıkaran bir araçtır. -Composer aracılığıyla yükleyin: +Composer kullanarak kurarız: ```shell composer require --dev phpstan/phpstan-nette ``` -Projede bir yapılandırma dosyası `phpstan.neon` oluşturun: +Projede `phpstan.neon` yapılandırma dosyasını oluştururuz: ```neon includes: @@ -47,40 +47,38 @@ parameters: level: 5 ``` -Ve sonra `app/` klasöründeki sınıfları analiz etmesine izin verin: +Ve ardından `app/` klasöründeki sınıfları analiz etmesini sağlarız: ```shell vendor/bin/phpstan analyse app ``` -Kapsamlı dokümantasyonu doğrudan [PHPStan'da |https://phpstan.org] bulabilirsiniz. +Kapsamlı belgeleri doğrudan [PHPStan web sitesinde |https://phpstan.org] bulabilirsiniz. -Kod Denetleyicisi .[#toc-code-checker] -====================================== +Code Checker +============ -[Kod Denetleyicisi |code-checker:], kaynak kodunuzdaki bazı biçimsel hataları kontrol eder ve muhtemelen onarır. +[Code Checker|code-checker:] kaynak kodlarınızdaki bazı biçimsel hataları kontrol eder ve gerekirse düzeltir: -- [BOM'u |nette:glossary#bom] kaldırır +- [BOM |nette:glossary#BOM] kaldırır - [Latte |latte:] şablonlarının geçerliliğini kontrol eder - `.neon`, `.php` ve `.json` dosyalarının geçerliliğini kontrol eder -- [kontrol karakterlerini kontrol eder|nette:glossary#control characters] +- [kontrol karakterlerinin |nette:glossary#Kontrol Karakterleri] varlığını kontrol eder - dosyanın UTF-8 olarak kodlanıp kodlanmadığını kontrol eder -- `/* @annotations */` adresinde yanlış yazılmış kontroller (ikinci yıldız işareti eksik) -- PHP dosyalarındaki `?>` PHP bitiş etiketlerini kaldırır -- bir dosyanın sonundaki boşlukları ve gereksiz boş satırları kaldırır -- satır sonlarını sistem varsayılanına normalleştirir ( `-l` parametresiyle) +- yanlış yazılmış `/* @anotace */` (yıldız eksik) kontrol eder +- PHP dosyalarındaki kapanış `?>` etiketini kaldırır +- dosya sonundaki sağdaki boşlukları ve gereksiz satırları kaldırır +- satır ayırıcılarını sistem varsayılanlarına normalleştirir (`-l` seçeneğini belirtirseniz) -Besteci .[#toc-composer] -======================== +Composer +======== -[Composer |Composer] PHP'de bağımlılıklarınızı yönetmek için bir araçtır. Kütüphane bağımlılıklarını bildirmemize izin verir ve bunları bizim için projemize yükler. +[Composer |Composer] PHP'de bağımlılıkları yönetmek için bir araçtır. Bireysel kütüphanelerin keyfi olarak karmaşık bağımlılıklarını bildirmemize ve ardından bunları projemize kurmamıza olanak tanır. -Gereksinim Denetleyicisi .[#toc-requirements-checker] -===================================================== +Requirements Checker +==================== -Sunucunun çalışma ortamını test eden ve çerçevenin kullanılıp kullanılamayacağını (ve ne ölçüde kullanılabileceğini) bildiren bir araçtı. Şu anda Nette, gerekli minimum PHP sürümüne sahip herhangi bir sunucuda kullanılabilir. - -{{sitename: En İyi Uygulamalar}} +Sunucunun çalışma zamanı ortamını test eden ve framework'ün kullanılıp kullanılamayacağını (ve ne ölçüde) bildiren bir araçtı. Şu anda Nette, minimum gerekli PHP sürümüne sahip her sunucuda kullanılabilir. diff --git a/best-practices/tr/form-reuse.texy b/best-practices/tr/form-reuse.texy index 338dee09ef..224e9f00d7 100644 --- a/best-practices/tr/form-reuse.texy +++ b/best-practices/tr/form-reuse.texy @@ -1,16 +1,16 @@ -Formları Birden Fazla Yerde Yeniden Kullanma -******************************************** +Formların Birden Fazla Yerde Yeniden Kullanımı +********************************************** .[perex] -Nette, aynı formu kod kopyalamadan birden fazla yerde yeniden kullanmak için çeşitli seçeneklere sahipsiniz. Bu makalede, kaçınmanız gerekenler de dahil olmak üzere farklı çözümlerin üzerinden geçeceğiz. +Nette'de, aynı formu birden fazla yerde kullanmak ve kodu tekrarlamamak için çeşitli seçenekleriniz vardır. Bu makalede, kaçınmanız gerekenler de dahil olmak üzere farklı çözümleri göstereceğiz. -Form Fabrikası .[#toc-form-factory] -=================================== +Form Fabrikası +============== -Aynı bileşeni birden fazla yerde kullanmaya yönelik temel yaklaşımlardan biri, bileşeni oluşturan bir yöntem veya sınıf oluşturmak ve daha sonra bu yöntemi uygulamanın farklı yerlerinde çağırmaktır. Böyle bir yöntem veya sınıf *factory* olarak adlandırılır. Lütfen fabrikaları kullanmanın belirli bir yolunu açıklayan ve bu konuyla ilgili olmayan *factory method* tasarım modeliyle karıştırmayın. +Aynı bileşeni birden fazla yerde kullanmanın temel yaklaşımlarından biri, bu bileşeni üreten bir metot veya sınıf oluşturmak ve ardından bu metodu uygulamanın farklı yerlerinde çağırmaktır. Böyle bir metoda veya sınıfa *fabrika* denir. Lütfen fabrikaların belirli bir kullanım şeklini tanımlayan ve bu konuyla ilgili olmayan *factory method* tasarım deseniyle karıştırmayın. -Örnek olarak, bir düzenleme formu oluşturacak bir fabrika oluşturalım: +Örnek olarak, bir düzenleme formu oluşturacak bir fabrika yaratacağız: ```php use Nette\Application\UI\Form; @@ -20,22 +20,22 @@ class FormFactory public function createEditForm(): Form { $form = new Form; - $form->addText('title', 'Title:'); - // ek form alanları buraya eklenir - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Başlık:'); + // buraya diğer form alanları eklenir + $form->addSubmit('send', 'Gönder'); return $form; } } ``` -Artık bu fabrikayı uygulamanızın farklı yerlerinde, örneğin sunumlarda veya bileşenlerde kullanabilirsiniz. Ve bunu [bir bağımlılık olarak talep |dependency-injection:passing-dependencies] ederek yapıyoruz. Bu yüzden önce sınıfı yapılandırma dosyasına yazacağız: +Şimdi bu fabrikayı uygulamanızın farklı yerlerinde, örneğin presenter'larda veya bileşenlerde kullanabilirsiniz. Bunu [bağımlılık olarak talep edeceğiz |dependency-injection:passing-dependencies]. Önce sınıfı yapılandırma dosyasına yazarız: ```neon services: - FormFactory ``` -Ve sonra bunu sunumda kullanıyoruz: +Ve sonra onu presenter'da kullanırız: ```php @@ -57,7 +57,7 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Uygulamanıza uygun diğer form türlerini oluşturmak için form fabrikasını ek yöntemlerle genişletebilirsiniz. Ve elbette, diğer yöntemlerin kullanacağı öğeler olmadan temel bir form oluşturan bir yöntem ekleyebilirsiniz: +Form fabrikasını, uygulamanızın ihtiyaçlarına göre diğer tür formları oluşturmak için ek metotlarla genişletebilirsiniz. Ve tabii ki, öğeler olmadan temel bir form oluşturan ve diğer metotların kullanacağı bir metot da ekleyebiliriz: ```php class FormFactory @@ -71,21 +71,21 @@ class FormFactory public function createEditForm(): Form { $form = $this->createForm(); - $form->addText('title', 'Title:'); - // ek form alanları buraya eklenir - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Başlık:'); + // buraya diğer form alanları eklenir + $form->addSubmit('send', 'Gönder'); return $form; } } ``` - `createForm()` yöntemi henüz yararlı bir şey yapmıyor, ancak bu hızla değişecek. +`createForm()` metodu henüz yararlı bir şey yapmıyor, ancak bu hızla değişecek. -Fabrika Bağımlılıkları .[#toc-factory-dependencies] -=================================================== +Fabrika Bağımlılıkları +====================== -Zamanla, formların çok dilli olması gerektiği ortaya çıkacaktır. Bu, tüm formlar için bir [çevirmen |forms:rendering#Translating] ayarlamamız gerektiği anlamına gelir. Bunu yapmak için `FormFactory` sınıfını, `Translator` nesnesini kurucuda bir bağımlılık olarak kabul edecek ve forma aktaracak şekilde değiştiriyoruz: +Zamanla, formların çok dilli olması gerektiği ortaya çıkacaktır. Bu, tüm formlara sözde [çevirmeni |forms:rendering#Çeviri] ayarlamamız gerektiği anlamına gelir. Bu amaçla, `FormFactory` sınıfını yapıcıda `Translator` nesnesini bir bağımlılık olarak kabul edecek şekilde değiştiririz ve onu forma iletiriz: ```php use Nette\Localization\Translator; @@ -104,18 +104,17 @@ class FormFactory return $form; } - //... + // ... } ``` - `createForm()` yöntemi, belirli formları oluşturan diğer yöntemler tarafından da çağrıldığından, çevirmeni yalnızca bu yöntemde ayarlamamız gerekir. Ve işimiz bitti. Herhangi bir sunumcu veya bileşen kodunu değiştirmeye gerek yok, ki bu harika. +`createForm()` metodu diğer belirli formları oluşturan metotlar tarafından da çağrıldığı için, çevirmeni yalnızca bu metotta ayarlamak yeterlidir. Ve işimiz bitti. Herhangi bir presenter veya bileşenin kodunu değiştirmeye gerek yok, ki bu harika. -Daha Fazla Fabrika Sınıfı .[#toc-more-factory-classes] -====================================================== +Birden Fazla Fabrika Sınıfı +=========================== -Alternatif olarak, uygulamanızda kullanmak istediğiniz her form için birden fazla sınıf oluşturabilirsiniz. -Bu yaklaşım kodun okunabilirliğini artırabilir ve formların yönetimini kolaylaştırabilir. Temel yapılandırmaya sahip (örneğin çeviri destekli) saf bir form oluşturmak için orijinal `FormFactory` adresini bırakın ve düzenleme formu için yeni bir fabrika `EditFormFactory` oluşturun. +Alternatif olarak, uygulamanızda kullanmak istediğiniz her form için birden fazla sınıf oluşturabilirsiniz. Bu yaklaşım, kod okunabilirliğini artırabilir ve form yönetimini kolaylaştırabilir. Orijinal `FormFactory`'yi yalnızca temel yapılandırmaya sahip (örneğin çeviri desteği ile) temiz bir form oluşturmak için bırakırız ve düzenleme formu için yeni bir `EditFormFactory` fabrikası oluştururuz. ```php class FormFactory @@ -145,40 +144,39 @@ class EditFormFactory public function create(): Form { $form = $this->formFactory->create(); - // ek form alanları buraya eklenir - $form->addSubmit('send', 'Save'); + // buraya diğer form alanları eklenir + $form->addSubmit('send', 'Gönder'); return $form; } } ``` - `FormFactory` ve `EditFormFactory` sınıfları arasındaki bağın nesne kalıtımı ile değil, bileşim ile uygulanması çok önemlidir: +`FormFactory` ve `EditFormFactory` sınıfları arasındaki bağın [nesne kalıtımı |nette:introduction-to-object-oriented-programming#Kompozisyon] yerine [kompozisyon |nette:introduction-to-object-oriented-programming#Kalıtım] ile gerçekleştirilmesi çok önemlidir: ```php -// ⛔ HAYIR! MİRAS BURAYA AİT DEĞİL +// ⛔ BU ŞEKİLDE DEĞİL! KALITIM BURAYA AİT DEĞİL class EditFormFactory extends FormFactory { public function create(): Form { $form = parent::create(); - $form->addText('title', 'Title:'); - // ek form alanları buraya eklenir - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Başlık:'); + // buraya diğer form alanları eklenir + $form->addSubmit('send', 'Gönder'); return $form; } } ``` -Bu durumda kalıtım kullanmak tamamen ters etki yaratacaktır. Çok hızlı bir şekilde sorunlarla karşılaşırsınız. Örneğin, `create()` yöntemine parametre eklemek isterseniz; PHP, imzasının ebeveynden farklı olduğuna dair bir hata bildirecektir. -Ya da `EditFormFactory` sınıfına yapıcı aracılığıyla bir bağımlılık aktarırken. Bu, yapıcı [cehennemi |dependency-injection:passing-dependencies#Constructor hell] dediğimiz şeye neden olur. +Bu durumda kalıtım kullanmak tamamen verimsiz olurdu. Çok hızlı bir şekilde sorunlarla karşılaşırdınız. Örneğin, `create()` metoduna parametreler eklemek istediğinizde; PHP, imzasının ebeveyninden farklı olduğuna dair bir hata bildirirdi. Veya `EditFormFactory` sınıfına yapıcı aracılığıyla bir bağımlılık iletirken. [Yapıcı cehennemi |dependency-injection:passing-dependencies#Constructor Hell] dediğimiz bir durum ortaya çıkardı. -Genel olarak, kalıtım yerine bileşimi tercih etmek daha iyidir. +Genel olarak, [kalıtım yerine kompozisyonu |dependency-injection:faq#Neden Kalıtım Yerine Kompozisyon Tercih Edilir] tercih etmek daha iyidir. -Form İşleme .[#toc-form-handling] -================================= +Form İşleme +=========== -Başarılı bir gönderim sonrasında çağrılan form işleyici de bir fabrika sınıfının parçası olabilir. Gönderilen verileri işlenmek üzere modele aktararak çalışacaktır. Herhangi bir hatayı forma [geri |forms:validation#Processing Errors] iletecektir. Aşağıdaki örnekteki model `Facade` sınıfı tarafından temsil edilmektedir: +Başarılı bir gönderimden sonra çağrılan form işleyicisi de fabrika sınıfının bir parçası olabilir. Gönderilen verileri işlenmek üzere modele ileterek çalışacaktır. Olası hataları forma [geri iletir |forms:validation#İşleme Sırasındaki Hatalar]. Aşağıdaki örnekte model, `Facade` sınıfı tarafından temsil edilmektedir: ```php class EditFormFactory @@ -192,9 +190,9 @@ class EditFormFactory public function create(): Form { $form = $this->formFactory->create(); - $form->addText('title', 'Title:'); - // ek form alanları buraya eklenir - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Başlık:'); + // buraya diğer form alanları eklenir + $form->addSubmit('send', 'Gönder'); $form->onSuccess[] = [$this, 'processForm']; return $form; } @@ -202,7 +200,7 @@ class EditFormFactory public function processForm(Form $form, array $data): void { try { - // sunulan veri̇leri̇n i̇şlenmesi̇ + // gönderilen verilerin işlenmesi $this->facade->process($data); } catch (AnyModelException $e) { @@ -212,7 +210,7 @@ class EditFormFactory } ``` -Sunucunun yeniden yönlendirmeyi kendisinin yapmasına izin verin. Yeniden yönlendirmeyi gerçekleştirecek olan `onSuccess` olayına başka bir işleyici ekleyecektir. Bu, formun farklı sunumcularda kullanılmasına olanak tanıyacak ve her biri farklı bir konuma yönlendirebilecektir. +Ancak yönlendirmeyi presenter'a bırakacağız. `onSuccess` olayına yönlendirmeyi gerçekleştirecek başka bir işleyici ekleyecektir. Bu sayede formu farklı presenter'larda kullanmak ve her birinde farklı bir yere yönlendirmek mümkün olacaktır. ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -226,7 +224,7 @@ class MyPresenter extends Nette\Application\UI\Presenter { $form = $this->formFactory->create(); $form->onSuccess[] = function () { - $this->flashMessage('Záznam byl uložen'); + $this->flashMessage('Kayıt kaydedildi'); $this->redirect('Homepage:'); }; return $form; @@ -234,39 +232,38 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Bu çözüm, bir form veya öğesi üzerinde `addError()` çağrıldığında, bir sonraki `onSuccess` işleyicisinin çağrılmaması şeklindeki form özelliğinden yararlanır. +Bu çözüm, form veya öğesi üzerinde `addError()` çağrıldığında sonraki `onSuccess` işleyicisinin çağrılmaması özelliğini kullanır. -Form Sınıfından Devralma .[#toc-inheriting-from-the-form-class] -=============================================================== +Form Sınıfından Kalıtım Alma +============================ -Oluşturulan bir formun bir formun çocuğu olmaması gerekir. Başka bir deyişle, bu çözümü kullanmayın: +Oluşturulan form, formun bir alt sınıfı olmamalıdır. Başka bir deyişle, bu çözümü kullanmayın: ```php -// ⛔ HAYIR! MİRAS BURAYA AİT DEĞİL +// ⛔ BU ŞEKİLDE DEĞİL! KALITIM BURAYA AİT DEĞİL class EditForm extends Form { public function __construct(Translator $translator) { parent::__construct(); - $form->addText('title', 'Title:'); - // ek form alanları buraya eklenir - $form->addSubmit('send', 'Save'); - $form->setTranslator($translator); + $this->addText('title', 'Başlık:'); + // buraya diğer form alanları eklenir + $this->addSubmit('send', 'Gönder'); + $this->setTranslator($translator); } } ``` -Formu yapıcıda oluşturmak yerine fabrikayı kullanın. +Formu yapıcıda oluşturmak yerine bir fabrika kullanın. - `Form` sınıfının öncelikle bir formu bir araya getirmek için bir araç, yani bir form oluşturucu olduğunu anlamak önemlidir. Ve birleştirilmiş form onun ürünü olarak düşünülebilir. Ancak, ürün oluşturucunun özel bir durumu değildir; aralarında kalıtımın temelini oluşturan *is a* ilişkisi yoktur. +`Form` sınıfının öncelikle bir form oluşturma aracı, yani bir *form oluşturucu* olduğu unutulmamalıdır. Ve oluşturulan form, onun bir ürünü olarak düşünülebilir. Ancak ürün, oluşturucunun özel bir durumu değildir, aralarında kalıtımın temelini oluşturan bir *is a* ilişkisi yoktur. -Form Bileşeni .[#toc-form-component] -==================================== +Form İçeren Bileşen +=================== -Tamamen farklı bir yaklaşım, bir form içeren bir [bileşen |application:components] oluşturmaktır. Bu, örneğin bileşen bir şablon içerdiğinden formu belirli bir şekilde oluşturmak gibi yeni olanaklar sağlar. -Veya sinyaller AJAX iletişimi ve forma bilgi yüklemek için kullanılabilir, örneğin ipucu vb. için. +Tamamen farklı bir yaklaşım, form içeren bir [bileşen |application:components] oluşturmaktır. Bu, örneğin formu belirli bir şekilde oluşturmak gibi yeni olanaklar sunar, çünkü bileşenin bir parçası olarak bir şablon da bulunur. Veya AJAX iletişimi ve forma bilgi yükleme, örneğin öneriler için sinyaller kullanılabilir, vb. ```php @@ -284,9 +281,9 @@ class EditControl extends Nette\Application\UI\Control protected function createComponentForm(): Form { $form = new Form; - $form->addText('title', 'Title:'); - // ek form alanları buraya eklenir - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Başlık:'); + // buraya diğer form alanları eklenir + $form->addSubmit('send', 'Gönder'); $form->onSuccess[] = [$this, 'processForm']; return $form; @@ -295,7 +292,7 @@ class EditControl extends Nette\Application\UI\Control public function processForm(Form $form, array $data): void { try { - // sunulan veri̇leri̇n i̇şlenmesi̇ + // gönderilen verilerin işlenmesi $this->facade->process($data); } catch (AnyModelException $e) { @@ -303,13 +300,13 @@ class EditControl extends Nette\Application\UI\Control return; } - // olay çağrısı + // olayın tetiklenmesi $this->onSave($this, $data); } } ``` -Bu bileşeni üretecek bir fabrika oluşturalım. [Arayüzünü yazmak |application:components#Components with Dependencies] yeterli: +Bu bileşeni üretecek bir fabrika da oluşturacağız. Sadece [arayüzünü yazmanız |application:components#Bağımlılıklara Sahip Bileşenler] yeterlidir: ```php interface EditControlFactory @@ -325,7 +322,7 @@ services: - EditControlFactory ``` -Ve şimdi fabrikayı talep edebilir ve sunumda kullanabiliriz: +Ve şimdi fabrikayı talep edebilir ve presenter'da kullanabiliriz: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -335,13 +332,13 @@ class MyPresenter extends Nette\Application\UI\Presenter ) { } - protected function createComponentEditForm(): Form + protected function createComponentEditForm(): EditControl { $control = $this->controlFactory->create(); $control->onSave[] = function (EditControl $control, $data) { $this->redirect('this'); - // veya düzenleme sonucuna yönlendirin, örn: + // veya düzenleme sonucuna yönlendiririz, örn.: // $this->redirect('detail', ['id' => $data->id]); }; @@ -349,5 +346,3 @@ class MyPresenter extends Nette\Application\UI\Presenter } } ``` - -{{sitename: En İyi Uygulamalar}} diff --git a/best-practices/tr/inject-method-attribute.texy b/best-practices/tr/inject-method-attribute.texy index 4a586935d6..95d08e2e06 100644 --- a/best-practices/tr/inject-method-attribute.texy +++ b/best-practices/tr/inject-method-attribute.texy @@ -1,21 +1,18 @@ -Inject Yöntemleri ve Öznitelikleri -********************************** +Inject Metotları ve Nitelikleri +******************************* .[perex] -Bu makalede, Nette çerçevesinde bağımlılıkları sunum yapanlara aktarmanın çeşitli yollarına odaklanacağız. Tercih edilen yöntem olan kurucu ile `inject` yöntemleri ve nitelikleri gibi diğer seçenekleri karşılaştıracağız. +Bu makalede, Nette framework'ünde presenter'lara bağımlılıkları iletmenin farklı yollarına odaklanacağız. Tercih edilen yöntem olan yapıcıyı, inject metotları ve nitelikleri gibi diğer seçeneklerle karşılaştıracağız. -Sunucular için de bağımlılıkların [kurucu |dependency-injection:passing-dependencies#Constructor Injection] kullanılarak aktarılması tercih edilen yoldur. -Ancak, diğer sunum yapanların miras aldığı ortak bir ata oluşturursanız (örn. BasePresenter) ve bu atanın da bağımlılıkları varsa, [yapıcı cehennemi |dependency-injection:passing-dependencies#Constructor hell] dediğimiz bir sorun ortaya çıkar. -Bu sorun, inject yöntemleri ve nitelikleri (ek açıklamalar) içeren alternatif yöntemler kullanılarak atlatılabilir. +Presenter'lar için de bağımlılıkların [yapıcı |dependency-injection:passing-dependencies#Yapıcı ile İletme] aracılığıyla iletilmesinin tercih edilen yol olduğu geçerlidir. Ancak, diğer presenter'ların miras aldığı ortak bir ata sınıf (örneğin `BasePresenter`) oluşturuyorsanız ve bu ata sınıfın da bağımlılıkları varsa, [yapıcı cehennemi |dependency-injection:passing-dependencies#Constructor Hell] dediğimiz bir sorun ortaya çıkar. Bu, inject metotları ve nitelikleri (eski adıyla anotasyonlar) olan alternatif yollarla aşılabilir. -`inject*()` Yöntemler .[#toc-inject-methods] -============================================ +`inject*()` Metotları +===================== -Bu, [ayarlayıcıları |dependency-injection:passing-dependencies#Setter Injection] kullanan bir bağımlılık aktarma biçimidir. Bu ayarlayıcıların adları inject önekiyle başlar. -Nette DI, sunum örneğini oluşturduktan hemen sonra bu tür adlandırılmış yöntemleri otomatik olarak çağırır ve gerekli tüm bağımlılıkları bunlara aktarır. Bu nedenle public olarak bildirilmelidirler. +Bu, bağımlılığın [ayarlayıcı |dependency-injection:passing-dependencies#Setter ile İletme] ile iletilmesinin bir şeklidir. Bu ayarlayıcıların adı `inject` önekiyle başlar. Nette DI, bu şekilde adlandırılan metotları presenter örneği oluşturulduktan hemen sonra otomatik olarak çağırır ve onlara tüm gerekli bağımlılıkları iletir. Bu nedenle `public` olarak bildirilmelidirler. -`inject*()` metotları, birden fazla metoda bir tür yapıcı uzantısı olarak düşünülebilir. Bu sayede, `BasePresenter` bağımlılıkları başka bir yöntem aracılığıyla alabilir ve yapıcıyı torunları için serbest bırakabilir: +`inject*()` metotları, yapıcının birden fazla metoda genişletilmiş bir türü olarak kabul edilebilir. Bu sayede `BasePresenter`, bağımlılıkları başka bir metot aracılığıyla alabilir ve yapıcıyı alt sınıfları için serbest bırakabilir: ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -39,18 +36,18 @@ class MyPresenter extends BasePresenter } ``` -Sunum yapan kişi herhangi bir sayıda `inject*()` yöntemi içerebilir ve her biri herhangi bir sayıda parametreye sahip olabilir. Bu, sunum yapan kişinin [özelliklerden oluştuğu |presenter-traits] ve her birinin kendi bağımlılığını gerektirdiği durumlar için de harikadır. +Bir presenter, keyfi sayıda `inject*()` metodu içerebilir ve her biri keyfi sayıda parametreye sahip olabilir. Bu, presenter'ın [trait'lerden oluştuğunda |presenter-traits] ve her birinin kendi bağımlılığını gerektirdiği durumlarda da harika çalışır. -`Inject` Nitelikler .[#toc-inject-attributes] -============================================= +`Inject` Nitelikleri +==================== -Bu, [özelliklere enjeksiyonun |dependency-injection:passing-dependencies#Property Injection] bir şeklidir. Hangi özelliklerin enjekte edilmesi gerektiğini belirtmek yeterlidir ve Nette DI, sunum örneğini oluşturduktan hemen sonra bağımlılıkları otomatik olarak geçirir. Bunları eklemek için public olarak bildirmek gerekir. +Bu, [özelliğe enjekte etme |dependency-injection:passing-dependencies#Değişken Ayarlayarak] şeklidir. Hangi değişkenlere enjekte edileceğini belirtmek yeterlidir ve Nette DI, presenter örneği oluşturulduktan hemen sonra bağımlılıkları otomatik olarak iletir. Bunları ekleyebilmesi için `public` olarak bildirilmelidirler. -Özellikler bir öznitelikle işaretlenir: (daha önce `/** @inject */` ek açıklaması kullanılıyordu) +Özellikleri nitelikle işaretleriz: (daha önce `/** @inject */` anotasyonu kullanılıyordu) ```php -use Nette\DI\Attributes\Inject; // bu satır önemlidir +use Nette\DI\Attributes\Inject; // bu satır önemlidir class MyPresenter extends Nette\Application\UI\Presenter { @@ -59,9 +56,6 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Bağımlılıkları aktarmanın bu yönteminin avantajı çok ekonomik bir gösterim biçimine sahip olmasıydı. Ancak, yapıcı [özellik tanıtımının |https://blog.nette.org/tr/php-8-0-haberlere-genel-bakis#toc-constructor-property-promotion] kullanılmaya başlanmasıyla birlikte, yapıcıyı kullanmak daha kolay görünmektedir. +Bu bağımlılık iletme yönteminin avantajı, çok kısa bir yazım şekli olmasıydı. Ancak, [constructor property promotion |https://blog.nette.org/tr/php-8-0-complete-overview-of-news#toc-constructor-property-promotion] 'ın gelişiyle, yapıcıyı kullanmak daha kolay görünüyor. -Öte yandan, bu yöntem genel olarak bağımlılıkları özelliklere aktarmakla aynı eksikliklerden muzdariptir: değişkendeki değişiklikler üzerinde hiçbir kontrolümüz yoktur ve aynı zamanda değişken sınıfın genel arayüzünün bir parçası haline gelir ki bu istenmeyen bir durumdur. - - -{{sitename: En İyi Uygulamalar}} +Tersine, bu yöntem, genel olarak özelliklere bağımlılık iletmeyle aynı dezavantajlara sahiptir: değişken üzerindeki değişiklikler üzerinde kontrolümüz yoktur ve aynı zamanda değişken, sınıfın genel arayüzünün bir parçası haline gelir, ki bu istenmeyen bir durumdur. diff --git a/best-practices/tr/lets-create-contact-form.texy b/best-practices/tr/lets-create-contact-form.texy index 52d307b8bf..042f486703 100644 --- a/best-practices/tr/lets-create-contact-form.texy +++ b/best-practices/tr/lets-create-contact-form.texy @@ -1,12 +1,12 @@ -Bir İletişim Formu Oluşturalım -****************************** +İletişim Formu Oluşturma +************************ .[perex] -Bir e-postaya göndermek de dahil olmak üzere Nette'de bir iletişim formunun nasıl oluşturulacağına bir göz atalım. Hadi yapalım o zaman! +Nette'de e-postaya gönderme dahil bir iletişim formunun nasıl oluşturulacağına bir göz atacağız. Öyleyse başlayalım! -Öncelikle yeni bir proje oluşturmamız gerekiyor. [Başlarken |nette:installation] sayfasında açıklandığı gibi. Ve sonra formu oluşturmaya başlayabiliriz. +Öncelikle yeni bir proje oluşturmamız gerekiyor. Bunun nasıl yapılacağını [Başlarken |nette:installation] sayfası açıklıyor. Ve sonra formu oluşturmaya başlayabiliriz. -En kolay yol, [formu doğrudan Presenter'da |forms:in-presenter] oluşturmaktır. Önceden hazırlanmış `HomePresenter` adresini kullanabiliriz. Formu temsil eden `contactForm` bileşenini ekleyeceğiz. Bunu `createComponentContactForm()` factory metodunu bileşeni üretecek kodun içine yazarak yapacağız: +En kolay yol, [doğrudan presenter içinde form |forms:in-presenter] oluşturmaktır. Önceden hazırlanmış `HomePresenter`'ı kullanabiliriz. Ona formu temsil eden `contactForm` bileşenini ekleyeceğiz. Bunu, bileşeni üreten `createComponentContactForm()` fabrika metodunu koda yazarak yapacağız: ```php use Nette\Application\UI\Form; @@ -17,37 +17,35 @@ class HomePresenter extends Presenter protected function createComponentContactForm(): Form { $form = new Form; - $form->addText('name', 'Name:') - ->setRequired('Enter your name'); - $form->addEmail('email', 'E-mail:') - ->setRequired('Enter your e-mail'); - $form->addTextarea('message', 'Message:') - ->setRequired('Enter message'); - $form->addSubmit('send', 'Send'); + $form->addText('name', 'İsim:') + ->setRequired('İsminizi girin'); + $form->addEmail('email', 'E-posta:') + ->setRequired('E-postanızı girin'); + $form->addTextarea('message', 'Mesaj:') + ->setRequired('Mesajınızı girin'); + $form->addSubmit('send', 'Gönder'); $form->onSuccess[] = [$this, 'contactFormSucceeded']; return $form; } public function contactFormSucceeded(Form $form, $data): void { - // sending an email + // e-posta gönderimi } } ``` -Gördüğünüz gibi iki metot oluşturduk. İlk yöntem `createComponentContactForm()` yeni bir form oluşturur. Bu formda `addText()`, `addEmail()` ve `addTextArea()` yöntemlerini kullanarak eklediğimiz isim, e-posta ve mesaj alanları bulunmaktadır. Ayrıca formu göndermek için bir düğme ekledik. -Peki ya kullanıcı bazı alanları doldurmazsa? Bu durumda, ona bunun gerekli bir alan olduğunu bildirmeliyiz. Bunu `setRequired()` metodu ile yaptık. -Son olarak, form başarıyla gönderildiğinde tetiklenen bir `onSuccess`[olayı |nette:glossary#events] da ekledik. Bizim durumumuzda, gönderilen formun işlenmesiyle ilgilenen `contactFormSucceeded` yöntemini çağırır. Bunu birazdan koda ekleyeceğiz. +Gördüğünüz gibi, iki metot oluşturduk. İlk metot `createComponentContactForm()` yeni bir form oluşturur. Bu formda isim, e-posta ve mesaj için `addText()`, `addEmail()` ve `addTextArea()` metotlarıyla eklediğimiz alanlar bulunur. Ayrıca formu göndermek için bir düğme ekledik. Peki ya kullanıcı bir alanı doldurmazsa? Bu durumda, bunun zorunlu bir alan olduğunu ona bildirmeliyiz. Bunu `setRequired()` metoduyla başardık. Son olarak, form başarıyla gönderildiğinde tetiklenecek olan [olay |nette:glossary#Olaylar Events] `onSuccess`'ı da ekledik. Bizim durumumuzda, gönderilen formu işlemekle ilgilenecek olan `contactFormSucceeded` metodunu çağırır. Bunu bir an sonra koda ekleyeceğiz. - `contantForm` bileşeninin `templates/Home/default.latte` şablonunda oluşturulmasına izin verin: +`contactForm` bileşenini `Home/default.latte` şablonunda oluşturulmasını sağlayacağız: ```latte {block content} -<h1>Contant Form</h1> +<h1>İletişim Formu</h1> {control contactForm} ``` -E-postanın kendisini göndermek için `ContactFacade` adında yeni bir sınıf oluşturuyoruz ve bunu `app/Model/ContactFacade.php` dosyasına yerleştiriyoruz: +E-postanın kendisini göndermek için `ContactFacade` adını vereceğimiz yeni bir sınıf oluşturacağız ve onu `app/Model/ContactFacade.php` dosyasına yerleştireceğiz: ```php <?php @@ -68,9 +66,9 @@ class ContactFacade public function sendMessage(string $email, string $name, string $message): void { $mail = new Message; - $mail->addTo('admin@example.com') // your email + $mail->addTo('admin@example.com') // sizin e-postanız ->setFrom($email, $name) - ->setSubject('Message from the contact form') + ->setSubject('İletişim formundan mesaj') ->setBody($message); $this->mailer->send($mail); @@ -78,9 +76,9 @@ class ContactFacade } ``` - `sendMessage()` yöntemi e-postayı oluşturur ve gönderir. Bunu yapmak için, yapıcı aracılığıyla bir bağımlılık olarak aktardığı sözde bir mailer kullanır. [E-posta gönderme |mail:] hakkında daha fazla bilgi edinin. +`sendMessage()` metodu bir e-posta oluşturur ve gönderir. Bunun için yapıcı aracılığıyla bir bağımlılık olarak aldığı sözde mailer'ı kullanır. [E-posta gönderme |mail:] hakkında daha fazla bilgi edinin. -Şimdi, sunucuya geri döneceğiz ve `contactFormSucceeded()` yöntemini tamamlayacağız. `ContactFacade` sınıfının `sendMessage()` metodunu çağırır ve form verilerini ona aktarır. Peki `ContactFacade` nesnesini nasıl elde edeceğiz? Yapıcı tarafından bize aktarılacak: +Şimdi presenter'a geri dönelim ve `contactFormSucceeded()` metodunu tamamlayalım. Bu metot, `ContactFacade` sınıfının `sendMessage()` metodunu çağıracak ve ona formdan gelen verileri iletecektir. Peki `ContactFacade` nesnesini nasıl elde ederiz? Yapıcı aracılığıyla bize iletilmesini sağlayacağız: ```php use App\Model\ContactFacade; @@ -102,36 +100,36 @@ class HomePresenter extends Presenter public function contactFormSucceeded(stdClass $data): void { $this->facade->sendMessage($data->email, $data->name, $data->message); - $this->flashMessage('The message has been sent'); + $this->flashMessage('Mesaj gönderildi'); $this->redirect('this'); } } ``` -E-posta gönderildikten sonra, kullanıcıya mesajın gönderildiğini onaylayan sözde [flaş mesajı |application:components#flash-messages] gösteriyoruz ve ardından formun tarayıcıda *yenile* kullanılarak yeniden gönderilememesi için bir sonraki sayfaya yönlendiriyoruz. +E-posta gönderildikten sonra, kullanıcıya mesajın gönderildiğini onaylayan sözde bir [flash mesajı |application:components#Flash Mesajları] göstereceğiz ve ardından formun tarayıcıda *yenile* ile tekrar tekrar gönderilmesini önlemek için bir sonraki sayfaya yönlendireceğiz. -Her şey çalışıyorsa, iletişim formunuzdan bir e-posta gönderebilmeniz gerekir. Tebrikler! +İşte bu kadar, her şey çalışıyorsa, iletişim formunuzdan bir e-posta gönderebilmelisiniz. Tebrikler! -HTML E-posta Şablonu .[#toc-html-email-template] ------------------------------------------------- +HTML E-posta Şablonu +-------------------- -Şimdilik, yalnızca form tarafından gönderilen mesajı içeren düz metin bir e-posta gönderiliyor. Ancak e-postada HTML kullanabilir ve daha çekici hale getirebiliriz. Bunun için Latte'de bir şablon oluşturacağız ve bu şablonu `app/Model/contactEmail.latte` adresine kaydedeceğiz: +Şu ana kadar, yalnızca form tarafından gönderilen mesajı içeren düz metin bir e-posta gönderiliyor. Ancak e-postada HTML kullanabilir ve görünümünü daha çekici hale getirebiliriz. Bunun için Latte'de bir şablon oluşturacağız ve onu `app/Model/contactEmail.latte` dosyasına yazacağız: ```latte <html> - <title>Message from the contact form + İletişim Formundan Mesaj -

    Name: {$name}

    -

    E-mail: {$email}

    -

    Message: {$message}

    +

    İsim: {$name}

    +

    E-posta: {$email}

    +

    Mesaj: {$message}

    ``` -Geriye bu şablonu kullanmak için `ContactFacade` adresini değiştirmek kalıyor. Yapıcıda, bir [Latte şablon işleyicisi |latte:develop#how-to-render-a-template] olan `Latte\Engine` nesnesini üretebilen `LatteFactory` sınıfını talep ediyoruz. Şablonu bir dosyaya işlemek için `renderToString()` yöntemini kullanırız, ilk parametre şablonun yolu ve ikincisi değişkenlerdir. +Geriye `ContactFacade`'i bu şablonu kullanacak şekilde düzenlemek kalıyor. Yapıcıda, `Latte\Engine` nesnesini, yani [Latte şablon oluşturucuyu |latte:develop#Bir Şablon Nasıl Oluşturulur] üretebilen `LatteFactory` sınıfını talep edeceğiz. `renderToString()` metoduyla şablonu bir dosyaya oluşturacağız, ilk parametre şablonun yolu ve ikincisi değişkenlerdir. ```php namespace App\Model; @@ -158,7 +156,7 @@ class ContactFacade ]); $mail = new Message; - $mail->addTo('admin@example.com') // your email + $mail->addTo('admin@example.com') // sizin e-postanız ->setFrom($email, $name) ->setHtmlBody($body); @@ -167,15 +165,15 @@ class ContactFacade } ``` -Daha sonra oluşturulan HTML e-postasını orijinal `setBody()` yerine `setHtmlBody()` yöntemine aktarıyoruz. Ayrıca `setSubject()` adresinde e-postanın konusunu belirtmemize gerek yoktur, çünkü kütüphane bunu `` Şablon içinde. +Oluşturulan HTML e-postayı daha sonra orijinal `setBody()` yerine `setHtmlBody()` metoduna ileteceğiz. Ayrıca `setSubject()` içinde e-posta konusunu belirtmemize gerek yok, çünkü kütüphane onu şablonun `<title>` öğesinden alacaktır. -Yapılandırma .[#toc-configuring] --------------------------------- +Yapılandırma +------------ - `ContactFacade` sınıf kodunda, yönetici e-postamız `admin@example.com` hala sabit kodludur. Bunu yapılandırma dosyasına taşımak daha iyi olacaktır. Nasıl Yapılır? +`ContactFacade` sınıfının kodunda, yönetici e-postamız `admin@example.com` hala sabit kodlanmıştır. Onu yapılandırma dosyasına taşımak daha iyi olurdu. Bunu nasıl yaparız? -İlk olarak, `ContactFacade` sınıfını değiştiriyoruz ve e-posta dizesini yapıcı tarafından geçirilen bir değişkenle değiştiriyoruz: +Önce `ContactFacade` sınıfını düzenleriz ve e-posta içeren karakter dizisini yapıcı tarafından iletilen bir değişkenle değiştiririz: ```php class ContactFacade @@ -199,28 +197,25 @@ class ContactFacade } ``` -İkinci adım ise bu değişkenin değerini yapılandırmaya koymaktır. `app/config/services.neon` dosyasına ekliyoruz: +Ve ikinci adım, bu değişkenin değerini yapılandırmada belirtmektir. `app/config/services.neon` dosyasına şunu yazarız: ```neon services: - App\Model\ContactFacade(adminEmail: admin@example.com) ``` -Ve işte bu kadar. `services` bölümünde çok sayıda öğe varsa ve e-postanın bunların arasında kaybolduğunu düşünüyorsanız, bunu bir değişken haline getirebiliriz. Girişi şu şekilde değiştireceğiz: +Ve işte bu kadar. `services` bölümündeki öğelerin sayısı çok fazlaysa ve e-postanın aralarında kaybolduğunu düşünüyorsanız, onu bir değişkene dönüştürebiliriz. Yazımı şu şekilde düzenleriz: ```neon services: - App\Model\ContactFacade(adminEmail: %adminEmail%) ``` -Ve bu değişkeni `app/config/common.neon` dosyasında tanımlayın: +Ve `app/config/common.neon` dosyasında bu değişkeni tanımlarız: ```neon parameters: adminEmail: admin@example.com ``` -Ve bitti! - - -{{sitename: En İyi Uygulamalar}} +Ve işimiz bitti! diff --git a/best-practices/tr/microsites.texy b/best-practices/tr/microsites.texy new file mode 100644 index 0000000000..fca2a1ebeb --- /dev/null +++ b/best-practices/tr/microsites.texy @@ -0,0 +1,63 @@ +Mikro web siteleri nasıl yazılır +******************************** + +Şirketinizin yaklaşan bir etkinliği için hızlı bir şekilde küçük bir web sitesi oluşturmanız gerektiğini hayal edin. Basit, hızlı ve gereksiz karmaşıklıklar olmadan olmalı. Böyle küçük bir proje için sağlam bir framework'e ihtiyacınız olmadığını düşünebilirsiniz. Peki ya Nette framework kullanmak bu süreci temelden basitleştirip hızlandırabilirse? + +Sonuçta, basit web siteleri oluştururken bile rahatlıktan vazgeçmek istemezsiniz. Bir kez çözülmüş olanı yeniden icat etmek istemezsiniz. Tembel olmaktan çekinmeyin ve şımartılmaya izin verin. Nette Framework, bir mikro framework olarak da mükemmel bir şekilde kullanılabilir. + +Böyle bir mikro site nasıl görünebilir? Örneğin, web sitesinin tüm kodunu genel klasördeki tek bir `index.php` dosyasına yerleştirerek: + +```php +<?php + +require __DIR__ . '/../vendor/autoload.php'; + +$configurator = new Nette\Bootstrap\Configurator; +$configurator->enableTracy(__DIR__ . '/../log'); +$configurator->setTempDirectory(__DIR__ . '/../temp'); + +// config.neon içindeki yapılandırmaya göre DI konteynerini oluştur +$configurator->addConfig(__DIR__ . '/../app/config.neon'); +$container = $configurator->createContainer(); + +// yönlendirmeyi ayarla +$router = new Nette\Application\Routers\RouteList; +$container->addService('router', $router); + +// https://example.com/ URL'si için rota +$router->addRoute('', function ($presenter, Nette\Http\Request $httpRequest) { + // tarayıcı dilini algıla ve /en veya /de vb. URL'ye yönlendir + $supportedLangs = ['en', 'de', 'cs']; + $lang = $httpRequest->detectLanguage($supportedLangs) ?: reset($supportedLangs); + $presenter->redirectUrl("/$lang"); +}); + +// https://example.com/cs veya https://example.com/en URL'si için rota +$router->addRoute('<lang cs|en>', function ($presenter, string $lang) { + // ilgili şablonu göster, örneğin ../templates/en.latte + $template = $presenter->createTemplate() + ->setFile(__DIR__ . '/../templates/' . $lang . '.latte'); + return $template; +}); + +// uygulamayı çalıştır! +$container->getByType(Nette\Application\Application::class)->run(); +``` + +Geri kalan her şey, üst klasör `/templates` içinde saklanan şablonlar olacaktır. + +`index.php` içindeki PHP kodu önce [ortamı hazırlar |bootstrap:], ardından [rotaları tanımlar |application:routing#Geri Çağrılarla Dinamik Yönlendirme] ve son olarak uygulamayı çalıştırır. Avantajı, `addRoute()` fonksiyonunun ikinci parametresinin, ilgili sayfa açıldığında yürütülecek bir callable olabilmesidir. + + +Neden mikro siteler için Nette kullanmalı? +------------------------------------------ + +- [Tracy|tracy:]'yi bir kez deneyen programcılar, bugün onsuz bir şey programlamayı hayal edemezler. +- Ama her şeyden önce, şablonlama sistemi [Latte|latte:]'yi kullanacaksınız, çünkü 2 sayfadan itibaren [düzeni ve içeriği|latte:template-inheritance] ayırmak isteyeceksiniz. +- Ve kesinlikle XSS güvenlik açığı oluşmaması için [otomatik kaçışa |latte:safety-first] güvenmek istersiniz. +- Nette ayrıca bir hata durumunda programcı hata mesajlarının PHP'de asla gösterilmemesini, bunun yerine kullanıcı dostu bir sayfanın gösterilmesini sağlar. +- Kullanıcılardan geri bildirim almak istiyorsanız, örneğin bir iletişim formu şeklinde, o zaman [formları|forms:] ve [veritabanını|database:] da eklersiniz. +- Doldurulmuş formları kolayca [e-posta ile gönderebilirsiniz|mail:]. +- Bazen [önbelleğe alma|caching:] işinize yarayabilir, örneğin beslemeleri indirip görüntülüyorsanız. + +Hız ve verimliliğin anahtar olduğu günümüzde, gereksiz gecikmeler olmadan sonuçlara ulaşmanızı sağlayan araçlara sahip olmak önemlidir. Nette framework size tam da bunu sunar - hızlı geliştirme, güvenlik ve süreci basitleştiren Tracy ve Latte gibi geniş bir araç yelpazesi. Sadece birkaç Nette paketi yükleyin ve böyle bir mikro site oluşturmak birdenbire çocuk oyuncağı haline gelir. Ve hiçbir yerde gizli bir güvenlik açığı olmadığını bilirsiniz. diff --git a/best-practices/tr/pagination.texy b/best-practices/tr/pagination.texy index 5ae4641731..92509706a7 100644 --- a/best-practices/tr/pagination.texy +++ b/best-practices/tr/pagination.texy @@ -1,17 +1,16 @@ -Veritabanı Sonuçlarını Sayfalandırma -************************************ +Veritabanı sonuçlarını sayfalama +******************************** .[perex] -Web uygulamaları geliştirirken, genellikle bir sayfada sınırlı sayıda kayıt yazdırma gereksinimiyle karşılaşırsınız. +Web uygulamaları geliştirirken, sayfada görüntülenen öğe sayısını sınırlama gereksinimiyle çok sık karşılaşırsınız. -Tüm verileri sayfalama yapmadan listelediğimizde durumdan çıkarız. Veritabanından veri seçmek için, yapıcı ve yayınlanma tarihine göre azalan sırada sıralanmış tüm yayınlanmış makaleleri döndüren `findPublishedArticles` yöntemini içeren ArticleRepository sınıfımız var. +Tüm verileri sayfalama olmadan listelediğimiz durumdan başlayalım. Veritabanından veri seçmek için, yapıcıya ek olarak, yayınlanan tüm makaleleri yayın tarihine göre azalan sırada döndüren `findPublishedArticles` metodunu içeren bir `ArticleRepository` sınıfımız var. ```php namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -31,10 +30,10 @@ class ArticleRepository } ``` -Presenter'da daha sonra model sınıfını enjekte edeceğiz ve render yönteminde şablona aktardığımız yayınlanmış makaleleri isteyeceğiz: +Presenter'da model sınıfını enjekte ederiz ve render metodunda yayınlanan makaleleri talep ederiz, bunları şablona iletiriz: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -53,11 +52,11 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -Şablonda, bir makale listesi oluşturmaya özen göstereceğiz: +`default.latte` şablonunda makalelerin listelenmesini sağlarız: ```latte {block content} -<h1>Articles</h1> +<h1>Makaleler</h1> <div class="articles"> {foreach $articles as $article} @@ -68,11 +67,11 @@ class HomePresenter extends Nette\Application\UI\Presenter ``` -Bu şekilde tüm makaleleri yazabiliriz, ancak makale sayısı arttığında bu sorunlara neden olacaktır. O noktada sayfalama mekanizmasını uygulamak faydalı olacaktır. +Bu şekilde tüm makaleleri listeleyebiliriz, ancak makale sayısı arttığında bu sorun yaratmaya başlar. Bu noktada bir sayfalama mekanizması uygulamak faydalı olacaktır. -Bu, tüm makalelerin birkaç sayfaya bölünmesini ve yalnızca geçerli bir sayfanın makalelerini göstermemizi sağlayacaktır. Toplam sayfa sayısı ve makalelerin dağılımı, toplam kaç makalemiz olduğuna ve sayfada kaç makale görüntülemek istediğimize bağlı olarak [Paginator |utils:Paginator] tarafından hesaplanır. +Bu, tüm makalelerin birkaç sayfaya bölünmesini ve yalnızca geçerli bir sayfanın makalelerini görüntülememizi sağlar. Toplam sayfa sayısı ve makalelerin dağılımı, toplamda kaç makalemiz olduğuna ve sayfa başına kaç makale görüntülemek istediğimize bağlı olarak [utils:Paginator | utils:Paginator] tarafından hesaplanır. -İlk adımda, repository sınıfındaki makaleleri alma yöntemini yalnızca tek sayfalık makaleleri döndürecek şekilde değiştireceğiz. Ayrıca, veritabanındaki toplam makale sayısını almak için yeni bir yöntem ekleyeceğiz, bu da bir Paginator ayarlamamız gerekecek: +İlk adımda, depodaki makaleleri almak için metodu, yalnızca bir sayfa için makaleleri döndürebilecek şekilde değiştiririz. Ayrıca, Paginator'u ayarlamak için ihtiyaç duyacağımız veritabanındaki toplam makale sayısını bulmak için bir metot ekleriz: ```php namespace App\Model; @@ -100,7 +99,7 @@ class ArticleRepository } /** - * Returns the total number of published articles + * Yayınlanan toplam makale sayısını döndürür */ public function getPublishedArticlesCount(): int { @@ -109,12 +108,12 @@ class ArticleRepository } ``` -Bir sonraki adım sunucuyu düzenlemektir. Şu anda görüntülenen sayfanın numarasını render yöntemine ileteceğiz. Bu numaranın URL'nin bir parçası olmaması durumunda, varsayılan değeri ilk sayfaya ayarlamamız gerekir. +Ardından, presenter'ı düzenlemeye başlarız. Render metoduna, görüntülenen geçerli sayfanın numarasını ileteceğiz. Bu numaranın URL'nin bir parçası olmadığı durumlar için, ilk sayfanın varsayılan değerini ayarlarız. -Ayrıca Paginator örneğini almak, ayarlamak ve şablonda görüntülenecek doğru makaleleri seçmek için render yöntemini genişletiyoruz. HomePresenter şu şekilde görünecektir: +Ayrıca, render metodunu Paginator örneğini almak, ayarlamak ve şablonda görüntülenecek doğru makaleleri seçmek için genişletiriz. HomePresenter, düzenlemelerden sonra şöyle görünecektir: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -128,31 +127,31 @@ class HomePresenter extends Nette\Application\UI\Presenter public function renderDefault(int $page = 1): void { - // Yayınlanan toplam makale sayısını bulacağız + // Yayınlanan toplam makale sayısını bulalım $articlesCount = $this->articleRepository->getPublishedArticlesCount(); - // Paginator örneğini oluşturacağız ve ayarlayacağız + // Paginator örneğini oluşturalım ve ayarlayalım $paginator = new Nette\Utils\Paginator; $paginator->setItemCount($articlesCount); // toplam makale sayısı - $paginator->setItemsPerPage(10); // sayfa başına öğe - $paginator->setPage($page); // gerçek sayfa numarası + $paginator->setItemsPerPage(10); // sayfa başına öğe sayısı + $paginator->setPage($page); // geçerli sayfa numarası - // Paginator'ın hesaplamalarına dayanarak veritabanından sınırlı sayıda makale bulacağız + // Veritabanından Paginator hesaplamasına göre sınırlı bir makale kümesi çekelim $articles = $this->articleRepository->findPublishedArticles($paginator->getLength(), $paginator->getOffset()); - // şablona aktardığımız + // bunu şablona iletelim $this->template->articles = $articles; - // ve ayrıca sayfalama seçeneklerini görüntülemek için Paginator'ın kendisi + // ve ayrıca sayfalama seçeneklerini görüntülemek için Paginator'ın kendisini $this->template->paginator = $paginator; } } ``` -Şablon zaten bir sayfadaki makaleleri yineliyor, sadece sayfalama bağlantıları ekleyin: +Şablonumuz artık yalnızca bir sayfanın makaleleri üzerinde yineleniyor, sadece sayfalama bağlantılarını eklememiz gerekiyor: ```latte {block content} -<h1>Articles</h1> +<h1>Makaleler</h1> <div class="articles"> {foreach $articles as $article} @@ -163,34 +162,33 @@ class HomePresenter extends Nette\Application\UI\Presenter <div class="pagination"> {if !$paginator->isFirst()} - <a n:href="default, 1">First</a> + <a n:href="default, 1">İlk</a>  |  - <a n:href="default, $paginator->page-1">Previous</a> + <a n:href="default, $paginator->page-1">Önceki</a>  |  {/if} - Page {$paginator->getPage()} of {$paginator->getPageCount()} + Sayfa {$paginator->getPage()} / {$paginator->getPageCount()} {if !$paginator->isLast()}  |  - <a n:href="default, $paginator->getPage() + 1">Next</a> + <a n:href="default, $paginator->getPage() + 1">Sonraki</a>  |  - <a n:href="default, $paginator->getPageCount()">Last</a> + <a n:href="default, $paginator->getPageCount()">Son</a> {/if} </div> ``` -Paginator kullanarak sayfalamayı bu şekilde ekledik. Veritabanı katmanı olarak Nette Database [Core |database:core] yerine Nette [Database Explorer |database:explorer] kullanılırsa, Paginator olmadan da sayfalama uygulayabiliriz. `Nette\Database\Table\Selection` sınıfı, Paginator'dan alınan sayfalama mantığı ile [sayfa |api:Nette\Database\Table\Selection::_ page] yöntemini içerir. +Bu şekilde, Paginator kullanarak sayfaya sayfalama yeteneği ekledik. Veritabanı katmanı olarak [Nette Database Core |database:sql-way] yerine [Nette Database Explorer |database:explorer] kullanırsak, Paginator kullanmadan da sayfalama uygulayabiliriz. `Nette\Database\Table\Selection` sınıfı, Paginator'dan alınan sayfalama mantığına sahip [page |api:Nette\Database\Table\Selection::_page] metodunu içerir. -Depo şu şekilde görünecektir: +Bu uygulama yöntemiyle depo şöyle görünecektir: ```php namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -198,7 +196,6 @@ class ArticleRepository ) { } - public function findPublishedArticles(): Nette\Database\Table\Selection { return $this->database->table('articles') @@ -208,10 +205,10 @@ class ArticleRepository } ``` -Presenter'da Paginator oluşturmak zorunda değiliz, bunun yerine repository tarafından döndürülen `Selection` nesnesinin yöntemini kullanacağız: +Presenter'da Paginator oluşturmamıza gerek yok, bunun yerine deponun döndürdüğü `Selection` sınıfının metodunu kullanırız: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -225,25 +222,25 @@ class HomePresenter extends Nette\Application\UI\Presenter public function renderDefault(int $page = 1): void { - // Yayınlanmış makaleler bulacağız + // Yayınlanan makaleleri çekelim $articles = $this->articleRepository->findPublishedArticles(); - // ve bunların şablona aktaracağımız sayfa yöntemi hesaplamasıyla sınırlı kısmı + // ve şablona yalnızca page metodunun hesaplamasına göre sınırlanmış bir kısmını gönderelim $lastPage = 0; $this->template->articles = $articles->page($page, 10, $lastPage); - // ve sayfalama seçeneklerini de görüntülemek için gerekli veriler + // ve ayrıca sayfalama seçeneklerini görüntülemek için gerekli verileri $this->template->page = $page; $this->template->lastPage = $lastPage; } } ``` -Paginator kullanmadığımız için sayfalama linklerini gösteren bölümü düzenlememiz gerekiyor: +Şimdi şablona Paginator göndermediğimiz için, sayfalama bağlantılarını gösteren kısmı düzenleriz: ```latte {block content} -<h1>Articles</h1> +<h1>Makaleler</h1> <div class="articles"> {foreach $articles as $article} @@ -254,24 +251,23 @@ Paginator kullanmadığımız için sayfalama linklerini gösteren bölümü dü <div class="pagination"> {if $page > 1} - <a n:href="default, 1">First</a> + <a n:href="default, 1">İlk</a>  |  - <a n:href="default, $page - 1">Previous</a> + <a n:href="default, $page - 1">Önceki</a>  |  {/if} - Page {$page} of {$lastPage} + Sayfa {$page} / {$lastPage} {if $page < $lastPage}  |  - <a n:href="default, $page + 1">Next</a> + <a n:href="default, $page + 1">Sonraki</a>  |  - <a n:href="default, $lastPage">Last</a> + <a n:href="default, $lastPage">Son</a> {/if} </div> ``` -Bu şekilde, Paginator kullanmadan bir sayfalama mekanizması uyguladık. +Bu şekilde, Paginator kullanmadan sayfalama mekanizmasını uyguladık. {{priority: -1}} -{{sitename: En İyi Uygulamalar}} diff --git a/best-practices/tr/passing-settings-to-presenters.texy b/best-practices/tr/passing-settings-to-presenters.texy index b156d5611b..b71ddc741d 100644 --- a/best-practices/tr/passing-settings-to-presenters.texy +++ b/best-practices/tr/passing-settings-to-presenters.texy @@ -1,10 +1,10 @@ -Ayarları Sunum Yapanlara İletme -******************************* +Ayarları presenter'lara iletme +****************************** .[perex] -Sunuculara nesne olmayan (örneğin hata ayıklama modunda çalışıp çalışmadığına ilişkin bilgiler, dizin yolları vb.) ve bu nedenle otomatik kablolama ile otomatik olarak aktarılamayan argümanlar aktarmanız mı gerekiyor? Çözüm, bunları bir `Settings` nesnesi içinde kapsüllemektir. +Presenter'lara nesne olmayan argümanları (örneğin, hata ayıklama modunda çalışıp çalışmadığı bilgisi, dizin yolları vb.) iletmeniz gerekiyor ve bu nedenle otomatik kablolama (autowiring) ile otomatik olarak iletilemiyorlar mı? Çözüm, bunları bir `Settings` nesnesine sarmaktır. -`Settings` hizmeti, çalışan bir uygulama hakkında sunum yapanlara bilgi sağlamanın çok kolay ama kullanışlı bir yoludur. Özel şekli tamamen sizin özel ihtiyaçlarınıza bağlıdır. Örnek: +`Settings` hizmeti, çalışan uygulama hakkındaki bilgileri presenter'lara sağlamanın çok kolay ve aynı zamanda kullanışlı bir yoludur. Somut biçimi tamamen özel ihtiyaçlarınıza bağlıdır. Örnek: ```php namespace App; @@ -12,10 +12,10 @@ namespace App; class Settings { public function __construct( - // PHP 8.1'den beri salt okunur olarak belirtmek mümkündür + // PHP 8.1'den itibaren readonly belirtilebilir public bool $debugMode, public string $appDir, - // ve böyle devam eder + // vb. ) {} } ``` @@ -30,7 +30,7 @@ services: ) ``` -Sunum yapan kişi bu hizmet tarafından sağlanan bilgiye ihtiyaç duyduğunda, bunu kurucudan istemesi yeterlidir: +Presenter bu hizmet tarafından sağlanan bilgilere ihtiyaç duyduğunda, yapıcıda basitçe ister: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -47,5 +47,3 @@ class MyPresenter extends Nette\Application\UI\Presenter } } ``` - -{{sitename: En İyi Uygulamalar}} diff --git a/best-practices/tr/post-links.texy b/best-practices/tr/post-links.texy new file mode 100644 index 0000000000..4d4ca45666 --- /dev/null +++ b/best-practices/tr/post-links.texy @@ -0,0 +1,56 @@ +POST bağlantıları nasıl doğru kullanılır +**************************************** + +.[perex] +Web uygulamalarında, özellikle yönetim arayüzlerinde, sunucu durumunu değiştiren eylemlerin HTTP GET metodu aracılığıyla gerçekleştirilmemesi temel bir kural olmalıdır. Metodun adından da anlaşılacağı gibi, GET yalnızca veri almak için kullanılmalı, değiştirmek için değil. Kayıt silme gibi eylemler için POST metodunu kullanmak daha uygundur. İdeal olan DELETE metodu olsa da, JavaScript olmadan çağrılamaz, bu nedenle tarihsel olarak POST kullanılır. + +Pratikte nasıl yapılır? Bu basit hileyi kullanın. Şablonun başında, `postForm` tanımlayıcısına sahip yardımcı bir form oluşturursunuz, bunu daha sonra silme düğmeleri için kullanırsınız: + +```latte .{file:@layout.latte} +<form method="post" id="postForm"></form> +``` + +Bu form sayesinde, klasik bir `<a>` bağlantısı yerine, görsel olarak normal bir bağlantı gibi görünecek şekilde ayarlanabilen bir `<button>` düğmesi kullanabilirsiniz. Örneğin, Bootstrap CSS framework'ü, düğmenin diğer bağlantılardan görsel olarak farklı olmamasını sağlayan `btn btn-link` sınıflarını sunar. `form="postForm"` niteliğini kullanarak onu önceden hazırlanmış formla ilişkilendiririz: + +```latte .{file:admin.latte} +<table> + <tr n:foreach="$posts as $post"> + <td>{$post->title}</td> + <td> + <button class="btn btn-link" form="postForm" formaction="{link delete $post->id}">sil</button> + <!-- <a n:href="delete $post->id">sil</a> yerine --> + </td> + </tr> +</table> +``` + +Bağlantıya tıklandığında, şimdi `delete` eylemi çağrılır. İsteklerin yalnızca POST metodu aracılığıyla ve aynı etki alanından kabul edilmesini sağlamak için (bu, CSRF saldırılarına karşı etkili bir savunmadır), `#[Requires]` niteliğini kullanın: + +```php .{file:AdminPresenter.php} +use Nette\Application\Attributes\Requires; + +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST', sameOrigin: true)] + public function actionDelete(int $id): void + { + $this->facade->deletePost($id); // kaydı silen varsayımsal kod + $this->redirect('default'); + } +} +``` + +Nitelik Nette Application 3.2'den beri mevcuttur ve yetenekleri hakkında daha fazla bilgiyi [Requires niteliği nasıl kullanılır |attribute-requires] sayfasında bulabilirsiniz. + +`actionDelete()` eylemi yerine `handleDelete()` sinyalini kullanıyorsanız, sinyallerin bu koruması örtük olarak ayarlandığından `sameOrigin: true` belirtmek gerekli değildir: + +```php .{file:AdminPresenter.php} +#[Requires(methods: 'POST')] +public function handleDelete(int $id): void +{ + $this->facade->deletePost($id); + $this->redirect('this'); +} +``` + +Bu yaklaşım yalnızca uygulamanızın güvenliğini artırmakla kalmaz, aynı zamanda doğru web standartlarına ve uygulamalarına uymaya da katkıda bulunur. Durumu değiştiren eylemler için POST yöntemlerini kullanarak daha sağlam ve güvenli bir uygulama elde edersiniz. diff --git a/best-practices/tr/presenter-traits.texy b/best-practices/tr/presenter-traits.texy index 1ef3223f6e..a75c15f500 100644 --- a/best-practices/tr/presenter-traits.texy +++ b/best-practices/tr/presenter-traits.texy @@ -1,14 +1,14 @@ -Özelliklerden Sunucu Oluşturma -****************************** +Presenter'ları trait'lerden oluşturma +************************************* .[perex] -Aynı kodu birden fazla sunucuda uygulamamız gerekiyorsa (örneğin, kullanıcının oturum açtığının doğrulanması), kodu ortak bir ataya yerleştirmek caziptir. İkinci seçenek ise tek amaçlı özellikler oluşturmaktır. +Birden fazla presenter'da aynı kodu uygulamamız gerekiyorsa (örneğin, kullanıcının oturum açıp açmadığını doğrulamak), kodu ortak bir ataya yerleştirmek bir seçenektir. İkinci seçenek, tek amaçlı [trait'ler |nette:introduction-to-object-oriented-programming#Traitler] oluşturmaktır. -Bu çözümün avantajı, PHP'de çoklu kalıtım mümkün değilken, her sunucunun yalnızca gerçekten ihtiyaç duyduğu özellikleri kullanabilmesidir. +Bu çözümün avantajı, her presenter'ın yalnızca gerçekten ihtiyaç duyduğu trait'leri kullanabilmesidir, oysa PHP'de çoklu kalıtım mümkün değildir. -Bu özellikler, sunum yapan kişi oluşturulduğunda tüm [inject yöntemlerinin |inject-method-attribute#inject methods] sırayla çağrılmasından yararlanabilir. Sadece her inject yönteminin adının benzersiz olduğundan emin olmanız gerekir. +Bu trait'ler, presenter oluşturulduğunda tüm [inject metotlarının |inject-method-attribute#inject Metotları] sırayla çağrılması gerçeğinden yararlanabilir. Yalnızca her inject metodunun adının benzersiz olduğundan emin olmak gerekir. -Traitler, başlatma kodunu [onStartup veya onRender |application:presenters#Events] olaylarına asabilir. +Trait'ler, başlatma kodunu [onStartup veya onRender |application:presenters#Olaylar] olaylarına bağlayabilir. Örnekler: @@ -36,7 +36,7 @@ trait StandardTemplateFilters } ``` -Sunum yapan kişi daha sonra bu özellikleri kullanır: +Presenter daha sonra bu trait'leri basitçe kullanır: ```php class ArticlePresenter extends Nette\Application\UI\Presenter @@ -45,6 +45,3 @@ class ArticlePresenter extends Nette\Application\UI\Presenter use RequireLoggedUser; } ``` - - -{{sitename: En İyi Uygulamalar}} diff --git a/best-practices/tr/restore-request.texy b/best-practices/tr/restore-request.texy index 93546e81dd..0316b1cc68 100644 --- a/best-practices/tr/restore-request.texy +++ b/best-practices/tr/restore-request.texy @@ -1,17 +1,16 @@ -Daha Önceki Bir Sayfaya Nasıl Dönülür? -************************************** +Önceki bir sayfaya nasıl dönülür? +********************************* .[perex] -Bir kullanıcı bir form doldurursa ve oturum açma süresi dolarsa ne olur? Veri kaybını önlemek için, oturum açma sayfasına yönlendirmeden önce verileri oturuma kaydederiz. Nette'de bu çocuk oyuncağıdır. +Bir kullanıcı bir form doldururken oturumu sona ererse ne olur? Verilerini kaybetmemek için, oturum açma sayfasına yönlendirmeden önce verileri oturumda saklarız. Nette'de bu çocuk oyuncağıdır. -Geçerli istek, tanımlayıcısını kısa bir dize olarak döndüren `storeRequest()` yöntemi kullanılarak oturumda saklanabilir. Yöntem geçerli sunucunun adını, görünümü ve parametrelerini saklar. -Bir form da gönderilmişse, alanların değerleri de (yüklenen dosyalar hariç) kaydedilir. +Geçerli istek, `storeRequest()` metodu kullanılarak oturumda saklanabilir, bu metot isteğin tanımlayıcısını kısa bir dize olarak döndürür. Metot, geçerli presenter'ın adını, görünümünü ve parametrelerini saklar. Bir form da gönderildiyse, alanların içeriği de saklanır (yüklenen dosyalar hariç). -İstek, alınan tanımlayıcıyı ilettiğimiz `restoreRequest($key)` yöntemi tarafından geri yüklenir. Bu, orijinal sunum yapan kişiye ve görünüme yönlendirir. Bununla birlikte, kaydedilen istek bir form gönderimi içeriyorsa, `forward()` yöntemini kullanarak orijinal sunucuya iletir, önceden doldurulmuş değerleri forma geçirir ve yeniden çizilmesine izin verir. Bu, kullanıcının formu yeniden göndermesine olanak tanır ve hiçbir veri kaybolmaz. +İsteğin geri yüklenmesi, elde edilen tanımlayıcıyı ilettiğimiz `restoreRequest($key)` metodu tarafından gerçekleştirilir. Bu metot, orijinal presenter'a ve görünüme yönlendirir. Ancak, saklanan istek bir form gönderimi içeriyorsa, orijinal presenter'a `forward()` metoduyla geçer, forma daha önce doldurulan değerleri iletir ve yeniden oluşturulmasını sağlar. Bu şekilde kullanıcı formu tekrar gönderme fırsatına sahip olur ve hiçbir veri kaybolmaz. -Daha da önemlisi, `restoreRequest()` yeni giriş yapan kullanıcının formu ilk dolduran kişiyle aynı olup olmadığını kontrol eder. Değilse, isteği atar ve hiçbir şey yapmaz. +Önemli olan, `restoreRequest()` metodunun yeni oturum açan kullanıcının formu başlangıçta dolduranla aynı olup olmadığını kontrol etmesidir. Değilse, isteği atar ve hiçbir şey yapmaz. -Her şeyi bir örnekle gösterelim. Verilerin düzenlendiği ve `startup()` metodu kullanıcının giriş yapıp yapmadığını kontrol eden bir `AdminPresenter` sunumcumuz olsun. Eğer değilse, onu `SignPresenter` adresine yönlendiriyoruz. Aynı zamanda, mevcut isteği kaydediyoruz ve anahtarını `SignPresenter` adresine gönderiyoruz. +Her şeyi bir örnekle gösterelim. Verilerin düzenlendiği ve `startup()` metodunda kullanıcının oturum açıp açmadığını doğruladığımız bir `AdminPresenter`'ımız olsun. Değilse, onu `SignPresenter`'a yönlendiririz. Aynı zamanda geçerli isteği saklarız ve anahtarını `SignPresenter`'a göndeririz. ```php class AdminPresenter extends Nette\Application\UI\Presenter @@ -27,7 +26,7 @@ class AdminPresenter extends Nette\Application\UI\Presenter } ``` -`SignPresenter` sunucusu, oturum açma formuna ek olarak anahtarın yazıldığı kalıcı bir `$backlink` parametresi içerecektir. Parametre kalıcı olduğundan, oturum açma formu gönderildikten sonra bile taşınacaktır. +`SignPresenter`, oturum açma formuna ek olarak, anahtarın yazılacağı kalıcı bir `$backlink` parametresi de içerecektir. Parametre kalıcı olduğu için, oturum açma formu gönderildikten sonra da aktarılacaktır. ```php @@ -41,14 +40,14 @@ class SignPresenter extends Nette\Application\UI\Presenter protected function createComponentSignInForm() { $form = new Nette\Application\UI\Form; - // ... form alanları ekliyoruz ... + // ... form alanlarını ekleyin ... $form->onSuccess[] = [$this, 'signInFormSubmitted']; return $form; } public function signInFormSubmitted($form) { - // ... burada kullanıcıyı oturum açıyoruz ... + // ... burada kullanıcıyı oturum açtırın ... $this->restoreRequest($this->backlink); $this->redirect('Admin:'); @@ -56,9 +55,8 @@ class SignPresenter extends Nette\Application\UI\Presenter } ``` -Kaydedilen isteğin anahtarını `restoreRequest()` yöntemine iletiriz ve bu yöntem orijinal sunucuya yönlendirir (veya iletir). +`restoreRequest()` metoduna saklanan isteğin anahtarını iletiriz ve o, orijinal presenter'a yönlendirir (veya geçer). -Ancak, anahtar geçersizse (örneğin, oturumda artık mevcut değilse), yöntem hiçbir şey yapmaz. Dolayısıyla bir sonraki çağrı `$this->redirect('Admin:')` olup `AdminPresenter` adresine yönlendirir. +Ancak anahtar geçersizse (örneğin, artık oturumda mevcut değilse), metot hiçbir şey yapmaz. Bu nedenle, `AdminPresenter`'a yönlendiren `$this->redirect('Admin:')` çağrısı takip eder. {{priority: -1}} -{{sitename: En İyi Uygulamalar}} diff --git a/best-practices/uk/@home.texy b/best-practices/uk/@home.texy index 25d1fa8493..f9d3510c2b 100644 --- a/best-practices/uk/@home.texy +++ b/best-practices/uk/@home.texy @@ -1,22 +1,24 @@ -Найкращі практики -***************** +Посібники та практики +********************* .[perex] -Навчальні посібники, розв'язання поширених проблем та найкращі практики для Nette. +Посібники, рішення поширених завдань та *best practices* для Nette. <div class=documentation> <div> -Додаток Nette -------------- -- [Інжектування методів і атрибутів |inject-method-attribute] -- [Складання презентерів з ознак |presenter-traits] -- [Передання параметрів у презентери |passing-settings-to-presenters] -- [Як повернутися на попередню сторінку |restore-request] -- [Пагінація результатів бази даних |Pagination] -- [Динамічні фрагменти |dynamic-snippets] +Застосунки Nette +---------------- +- [Методи та атрибути inject |inject-method-attribute] +- [Складання презентерів з трейтів |presenter-traits] +- [Передача налаштувань до презентерів |passing-settings-to-presenters] +- [Як повернутися до попередньої сторінки |restore-request] +- [Пагінація результатів бази даних |pagination] +- [Динамічні сніпети |dynamic-snippets] +- [Як використовувати атрибут #Requires |attribute-requires] +- [Як правильно використовувати POST-посилання |post-links] </div> <div> @@ -26,31 +28,33 @@ ----- - [Повторне використання форм |form-reuse] - [Форма для створення та редагування запису |creating-editing-form] -- [Створимо контактну форму |lets-create-contact-form] -- [Залежні поля вибору |https://blog.nette.org/uk/zalezni-selektboksi-elegantno-v-nette-i-cistomu-js] +- [Створюємо контактну форму |lets-create-contact-form] +- [Залежні селектбокси |https://blog.nette.org/uk/dependent-selectboxes-elegantly-in-nette-and-pure-js] </div> <div> -Спільне -------- +Загальне +-------- - [Як завантажити конфігураційний файл |bootstrap:] -- [Чому Nette використовує нотацію констант у регістрі PascalCase? |https://blog.nette.org/uk/sob-mense-kriku-v-kodi] -- [Чому Nette не використовує суфікс Interface? |https://blog.nette.org/uk/prefiksi-ta-sufiksi-ne-potribni-v-nazvah-interfejsiv] -- [Поради щодо використання Composer |composer] +- [Як писати мікро-сайти |microsites] +- [Чому Nette використовує PascalCase нотацію констант? |https://blog.nette.org/uk/for-less-screaming-in-the-code] +- [Чому Nette не використовує суфікс Interface? |https://blog.nette.org/uk/prefixes-and-suffixes-do-not-belong-in-interface-names] +- [Composer: поради щодо використання |composer] - [Поради щодо редакторів та інструментів |editors-and-tools] +- [Вступ до об'єктно-орієнтованого програмування |nette:introduction-to-object-oriented-programming] </div> <div> -Зразок рішення --------------- -- [Приклади Nette |https://github.com/nette-examples] -- [Доктрина та Nette |https://contributte.org/nettrine/] -- [Приклади Contributte |https://contributte.org/examples.html] -- [Сайт Doctrine ORM |https://github.com/MinecordNetwork/Website] +Приклади рішень +--------------- +- [Nette examples |https://github.com/nette-examples] +- [Doctrine & Nette |https://contributte.org/nettrine/] +- [Contributte examples |https://contributte.org/examples.html] +- [Doctrine ORM Website |https://github.com/MinecordNetwork/Website] - [Швидкий старт |quickstart:] </div> @@ -59,10 +63,7 @@ Відео ----- -На "YouTube-каналі Nette Framework":https://www.youtube.com/user/NetteFramework ви можете знайти сотні записів з Posobota і відео про Nette під одним дахом. +Сотні записів з Posledních sobot та відео про Nette ви знайдете під одним дахом на "Youtube каналі Nette Framework":https://www.youtube.com/user/NetteFramework. </div> </div> - -{{sitename: Найкращі практики}} -{{leftbar: www:@menu-common}} diff --git a/best-practices/uk/@meta.texy b/best-practices/uk/@meta.texy new file mode 100644 index 0000000000..5ad8bb9a6b --- /dev/null +++ b/best-practices/uk/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Посібники та практики}} +{{leftbar: www:@menu-common}} diff --git a/best-practices/uk/attribute-requires.texy b/best-practices/uk/attribute-requires.texy new file mode 100644 index 0000000000..f0d8d4a2d1 --- /dev/null +++ b/best-practices/uk/attribute-requires.texy @@ -0,0 +1,177 @@ +Як використовувати атрибут `#[Requires]` +**************************************** + +.[perex] +Під час написання веб-додатку часто виникає потреба обмежити доступ до певних частин вашого додатку. Можливо, ви хочете, щоб деякі запити могли надсилати дані лише за допомогою форми (тобто методом POST), або щоб вони були доступні лише для AJAX-викликів. У Nette Framework 3.2 з'явився новий інструмент, який дозволяє встановити такі обмеження дуже елегантно та зрозуміло: атрибут `#[Requires]`. + +Атрибут — це спеціальна позначка в PHP, яку ви додаєте перед визначенням класу або методу. Оскільки це фактично клас, щоб наступні приклади працювали, необхідно вказати оператор use: + +```php +use Nette\Application\Attributes\Requires; +``` + +Атрибут `#[Requires]` можна використовувати для самого класу presenter'а, а також для таких методів: + +- `action<Action>()` +- `render<View>()` +- `handle<Signal>()` +- `createComponent<Name>()` + +Останні два методи стосуються також компонентів, отже, атрибут можна використовувати і для них. + +Якщо умови, зазначені в атрибуті, не виконані, буде викликано HTTP-помилку 4xx. + + +Методи HTTP +----------- + +Ви можете вказати, які HTTP-методи (наприклад, GET, POST тощо) дозволені для доступу. Наприклад, якщо ви хочете дозволити доступ лише шляхом надсилання форми, встановіть: + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +Чому слід використовувати POST замість GET для дій, що змінюють стан, і як це зробити? [Прочитайте інструкцію |post-links]. + +Ви можете вказати метод або масив методів. Особливим випадком є значення `'*'`, яке дозволяє всі методи, що зазвичай presenter'и [з міркувань безпеки не дозволяють |application:presenters#Перевірка HTTP-методу]. + + +AJAX-виклики +------------ + +Якщо ви хочете, щоб presenter або метод був доступний лише для AJAX-запитів, використовуйте: + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Те саме походження +------------------ + +Для підвищення безпеки ви можете вимагати, щоб запит надходив з того самого домену. Це запобігає [вразливості CSRF |nette:vulnerability-protection#Cross-Site Request Forgery CSRF]: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Для методів `handle<Signal>()` доступ з того самого домену вимагається автоматично. Тому, якщо ви, навпаки, хочете дозволити доступ з будь-якого домену, вкажіть: + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Доступ через forward +-------------------- + +Іноді корисно обмежити доступ до presenter'а так, щоб він був доступний лише опосередковано, наприклад, за допомогою методу `forward()` або `switch()` з іншого presenter'а. Таким чином, наприклад, захищаються error-presenter'и, щоб їх не можна було викликати з URL: + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +На практиці часто буває необхідно позначити певні views, до яких можна отримати доступ лише на основі логіки в presenter'і. Тобто, знову ж таки, щоб їх не можна було відкрити безпосередньо: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = $this->facade->getProduct($id); + if (!$product) { + $this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Конкретні дії +------------- + +Ви також можете обмежити, щоб певний код, наприклад, створення компонента, був доступний лише для специфічних дій у presenter'і: + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +У випадку однієї дії не потрібно записувати масив: `#[Requires(actions: 'default')]` + + +Власні атрибути +--------------- + +Якщо ви хочете використовувати атрибут `#[Requires]` повторно з тими самими налаштуваннями, ви можете створити власний атрибут, який успадковуватиме `#[Requires]` і налаштує його відповідно до потреб. + +Наприклад, `#[SingleAction]` дозволить доступ лише через дію `default`: + +```php +#[\Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Або `#[RestMethods]` дозволить доступ через усі HTTP-методи, що використовуються для REST API: + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Висновок +-------- + +Атрибут `#[Requires]` надає вам велику гнучкість і контроль над тим, як доступні ваші веб-сторінки. За допомогою простих, але потужних правил ви можете підвищити безпеку та правильне функціонування вашого додатку. Як бачите, використання атрибутів у Nette може не тільки полегшити вашу роботу, але й зробити її безпечнішою. diff --git a/best-practices/uk/composer.texy b/best-practices/uk/composer.texy index 2eead46d70..a237caf401 100644 --- a/best-practices/uk/composer.texy +++ b/best-practices/uk/composer.texy @@ -1,44 +1,44 @@ -Поради щодо використання Composer -********************************* +Composer: поради щодо використання +********************************** <div class=perex> -Composer - це інструмент для управління залежностями в PHP. Він дає змогу вам оголосити бібліотеки, від яких залежить ваш проєкт, і він встановлюватиме та оновлюватиме їх за вас. Ми дізнаємося: +Composer — це інструмент для керування залежностями в PHP. Він дозволяє нам перерахувати бібліотеки, від яких залежить наш проект, і буде встановлювати та оновлювати їх за нас. Ми покажемо: - як встановити Composer -- використовувати його в новому або наявному проєкті +- його використання в новому або існуючому проекті </div> -Встановлення .[#toc-installation] -================================= +Встановлення +============ -Composer - це виконуваний файл `.phar`, який ви завантажуєте та встановлюєте таким чином. +Composer — це виконуваний файл `.phar`, який ви завантажуєте та встановлюєте наступним чином: -Windows .[#toc-windows] ------------------------ +Windows +------- -Використовуйте офіційну програму встановлення [Composer-Setup.exe |https://getcomposer.org/Composer-Setup.exe]. +Використовуйте офіційний інсталятор [Composer-Setup.exe |https://getcomposer.org/Composer-Setup.exe]. -Linux, macOS .[#toc-linux-macos] --------------------------------- +Linux, macOS +------------ -Усе, що вам потрібно, це 4 команди, які ви можете скопіювати з [цієї сторінки |https://getcomposer.org/download/]. +Достатньо 4 команд, які ви можете скопіювати з [цієї сторінки |https://getcomposer.org/download/]. -Ба більше, під час копіювання в папку, що знаходиться в системному `PATH`, Composer стає глобально доступним: +Потім, розмістивши його в папці, яка знаходиться в системному `PATH`, Composer стане доступним глобально: ```shell -$ mv ./composer.phar ~/bin/composer # или /usr/local/bin/composer +$ mv ./composer.phar ~/bin/composer # або /usr/local/bin/composer ``` -Використання в наявному проєкті .[#toc-use-in-project] -====================================================== +Використання в проекті +====================== -Щоб почати використовувати Composer у своєму проєкті, все, що вам потрібно, це файл `composer.json`. Цей файл описує залежності вашого проекту і може містити інші метадані. Найпростіший `composer.json` може виглядати наступним чином: +Щоб почати використовувати Composer у своєму проекті, вам потрібен лише файл `composer.json`. Він описує залежності вашого проекту і може також містити інші метадані. Базовий `composer.json` може виглядати так: ```js { @@ -48,17 +48,17 @@ $ mv ./composer.phar ~/bin/composer # или /usr/local/bin/composer } ``` -Тут ми говоримо, що наш додаток (або бібліотека) залежить від пакета `nette/database` (ім'я пакета складається з імені постачальника та імені проєкту), і йому потрібна версія, що відповідає обмеженню `^3.0`. +Тут ми вказуємо, що наш додаток (або бібліотека) вимагає пакет `nette/database` (назва пакета складається з назви організації та назви проекту) і хоче версію, яка відповідає умові `^3.0` (тобто найновішу версію 3). -Отже, коли у нас є файл `composer.json` у корені проєкту і ми запускаємо: +Отже, у нас є файл `composer.json` у корені проекту, і ми запускаємо встановлення: ```shell composer update ``` -Composer завантажить вихідні файли Nette в каталог `vendor`. Він також створює файл `composer.lock`, який містить інформацію про те, які саме версії бібліотек встановлені. +Composer завантажить Nette Database у папку `vendor/`. Потім він створить файл `composer.lock`, який містить інформацію про те, які саме версії бібліотек він встановив. -Composer генерує файл `vendor/autoload.php`. Ви можете просто включити цей файл і почати використовувати класи, які надають ці бібліотеки, без зайвої роботи: +Composer згенерує файл `vendor/autoload.php`, який ми можемо просто підключити і почати використовувати бібліотеки без будь-якої додаткової роботи: ```php require __DIR__ . '/vendor/autoload.php'; @@ -67,52 +67,52 @@ $db = new Nette\Database\Connection('sqlite::memory:'); ``` -Оновлення пакетів до останніх версій .[#toc-update-packages-to-the-latest-versions] -=================================================================================== +Оновлення пакетів до останніх версій +==================================== -Для оновлення всіх використовуваних пакетів до останньої версії відповідно до обмежень версій, визначених у файлі `composer.json`, використовуйте команду `composer update`. Наприклад, для залежності `"nette/database": "^3.0"` буде встановлено останню версію 3.x.x, але не версію 4. +Оновлення використовуваних бібліотек до останніх версій відповідно до умов, визначених у `composer.json`, здійснюється командою `composer update`. Наприклад, для залежності `"nette/database": "^3.0"` буде встановлена остання версія 3.x.x, але не версія 4. -Щоб оновити обмеження версії у файлі `composer.json` на, наприклад, "nette/database": "^4.1"`, для установки последней версии, используйте команду `composer require nette/database`. +Для оновлення умов у файлі `composer.json`, наприклад, до `"nette/database": "^4.1"`, щоб можна було встановити останню версію, використовуйте команду `composer require nette/database`. -Щоб оновити всі використовувані пакети Nette, необхідно перерахувати їх усі в командному рядку, наприклад: +Для оновлення всіх використовуваних пакетів Nette необхідно було б перерахувати їх усі в командному рядку, наприклад: ```shell composer require nette/application nette/forms latte/latte tracy/tracy ... ``` -Що непрактично. Тому використовуйте простий сценарій "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff, який зробить це за вас: +Що непрактично. Тому використовуйте простий скрипт "Composer Frontline":https://gist.github.com/dg/734bebf55cf28ad6a5de1156d3099bff, який зробить це за вас: ```shell php composer-frontline.php ``` -Створення нового проєкту .[#toc-creating-new-project] -===================================================== +Створення нового проекту +======================== -Новий проект Nette можна створити, виконавши просту команду: +Новий проект на Nette створюється за допомогою однієї команди: ```shell -composer create-project nette/web-project name-of-the-project +composer create-project nette/web-project nazev-projektu ``` -Замість `name-of-the-project` вкажіть ім'я каталогу для вашого проєкту і виконайте команду. Composer отримає репозиторій `nette/web-project` з GitHub, який вже містить файл `composer.json`, і відразу після цього встановить сам фреймворк Nette. Залишилося тільки [перевірити права на запис |nette:troubleshooting#Setting-Directory-Permissions] для директорій `temp/` і `log/`, і ви готові до роботи. +Як `nazev-projektu` введіть назву каталогу для свого проекту та підтвердіть. Composer завантажить репозиторій `nette/web-project` з GitHub, який вже містить файл `composer.json`, а потім одразу Nette Framework. Повинно бути достатньо лише [встановити права |nette:troubleshooting#Налаштування прав доступу до каталогів] на запис у папки `temp/` та `log/`, і проект має запрацювати. -Якщо ви знаєте, на якій версії PHP буде розміщено проект, обов'язково встановіть [її |#PHP Version]. +Якщо ви знаєте, на якій версії PHP буде розміщено проект, не забудьте [її встановити |#Версія PHP]. -Версія PHP .[#toc-php-version] -============================== +Версія PHP +========== -Composer завжди встановлює версії пакунків, сумісні з версією PHP, яку ви зараз використовуєте (точніше, з версією PHP, яка використовується у командному рядку під час запуску Composer). А це, ймовірно, не та версія, яку використовує ваш веб-хостинг. Ось чому дуже важливо додати інформацію про версію PHP на вашому хостингу до файлу `composer.json`. Після цього будуть встановлені лише версії пакунків, сумісні з хостом. +Composer завжди встановлює ті версії пакетів, які сумісні з версією PHP, яку ви зараз використовуєте (точніше, з версією PHP, що використовується в командному рядку під час запуску Composer). Однак це, швидше за все, не та сама версія, яку використовує ваш хостинг. Тому дуже важливо додати до файлу `composer.json` інформацію про версію PHP на хостингу. Після цього будуть встановлюватися лише ті версії пакетів, які сумісні з хостингом. -Наприклад, щоб налаштувати роботу проекту на PHP 8.2.3, скористайтеся командою: +Те, що проект працюватиме, наприклад, на PHP 8.2.3, встановлюється командою: ```shell composer config platform.php 8.2.3 ``` -Таким чином версія буде записана у файл `composer.json`: +Таким чином версія запишеться у файл `composer.json`: ```js { @@ -124,8 +124,7 @@ composer config platform.php 8.2.3 } ``` -Однак, номер версії PHP також вказано в іншому місці файлу, в розділі `require`. У той час як перше число вказує на версію, для якої будуть встановлені пакунки, друге число вказує на версію, для якої написано саму програму. -(Звичайно, немає сенсу, щоб ці версії відрізнялися, тому подвійний запис є надмірністю). Ви встановлюєте цю версію за допомогою команди: +Однак номер версії PHP вказується ще в одному місці файлу, а саме в секції `require`. У той час як перше число визначає, для якої версії будуть встановлюватися пакети, друге число вказує, для якої версії написаний сам додаток. І за ним, наприклад, PhpStorm встановлює *PHP language level*. (Звичайно, немає сенсу, щоб ці версії відрізнялися, тому подвійний запис є недоліком.) Цю версію встановлюють командою: ```shell composer require php 8.2.3 --no-update @@ -142,66 +141,72 @@ composer require php 8.2.3 --no-update ``` -Неправдиві повідомлення .[#toc-false-reports] -============================================= +Ігнорування версії PHP +====================== -При оновленні пакунків або зміні номерів версій виникають конфлікти. Один пакунок має вимоги, які конфліктують з іншим і так далі. Утім, іноді Composer видає хибні повідомлення. Він повідомляє про конфлікт, якого насправді не існує. У цьому випадку рекомендується видалити файл `composer.lock` і спробувати ще раз. +Пакети зазвичай вказують як найнижчу версію PHP, з якою вони сумісні, так і найвищу, з якою вони протестовані. Якщо ви збираєтеся використовувати ще новішу версію PHP, наприклад, для тестування, Composer відмовиться встановлювати такий пакет. Рішенням є опція `--ignore-platform-req=php+`, яка змусить Composer ігнорувати верхні межі необхідної версії PHP. -Якщо повідомлення про помилку не зникає, це означає, що проблема серйозна, і вам потрібно прочитати, що і як потрібно змінити. +Хибні повідомлення +================== -Packagist.org - глобальний репозиторій .[#toc-packagist-org-global-repository] -============================================================================== +Під час оновлення пакетів або зміни номерів версій трапляється, що виникає конфлікт. Один пакет має вимоги, які суперечать іншому, і так далі. Однак Composer іноді видає хибні повідомлення. Він повідомляє про конфлікт, якого насправді не існує. У такому випадку допоможе видалити файл `composer.lock` і спробувати ще раз. -[Packagist |https://packagist.org] - це основне сховище пакетів, в якому Composer намагається шукати пакети, якщо не сказано інакше. Ви також можете опублікувати тут свої власні пакети. +Якщо повідомлення про помилку залишається, то воно серйозне, і потрібно з нього зрозуміти, що і як виправити. -Що якщо нам не потрібен центральний репозиторій .[#toc-what-if-we-don-t-want-the-central-repository] ----------------------------------------------------------------------------------------------------- +Packagist.org - центральний репозиторій +======================================= -Якщо в нашій компанії є внутрішні додатки або бібліотеки, які не можуть бути розміщені публічно на Packagist, ми можемо створити власні репозиторії для цих проєктів. +[Packagist |https://packagist.org] — це головний репозиторій, у якому Composer намагається шукати пакети, якщо йому не вказано інше. Ми також можемо публікувати тут власні пакети. -Детальніше про репозиторії в [офіційній документації |https://getcomposer.org/doc/05-repositories.md#Репозитории]. +Що робити, якщо ми не хочемо використовувати центральний репозиторій? +--------------------------------------------------------------------- + +Якщо у нас є внутрішньокорпоративні додатки, які ми просто не можемо розміщувати публічно, то ми створимо для них корпоративний репозиторій. + +Більше на тему репозиторіїв [в офіційній документації |https://getcomposer.org/doc/05-repositories.md#repositories]. -Автозавантаження .[#toc-autoloading] -==================================== -Ключовою особливістю Composer є те, що він забезпечує автозавантаження для всіх класів, що встановлюються, яке ви запускаєте, увімкнувши файл `vendor/autoload.php`. +Автозавантаження +================ -Однак можна також використовувати Composer для завантаження інших класів поза папкою `vendor`. Перший варіант - дозволити Composer просканувати певні папки та підпапки, знайти всі класи та включити їх в автозавантаження. Для цього встановіть `autoload > classmap` у файлі `composer.json`: +Ключовою особливістю Composer є те, що він забезпечує автозавантаження для всіх встановлених ним класів, яке ви запускаєте, підключивши файл `vendor/autoload.php`. + +Однак можна використовувати Composer і для завантаження інших класів поза папкою `vendor`. Перший варіант — дозволити Composer просканувати визначені папки та підпапки, знайти всі класи та включити їх до автозавантажувача. Цього можна досягти, налаштувавши `autoload > classmap` у `composer.json`: ```js { "autoload": { "classmap": [ - "src/", # включает папку src/ со всеми вложенными директориями + "src/", # включить папку src/ та її підпапки ] } } ``` -Згодом необхідно виконувати команду `composer dumpautoload` при кожній зміні та дозволяти таблицям автозавантаження регенеруватися. Це вкрай незручно, і набагато краще довірити цю задачу [RobotLoader |robot-loader:], який виконує ту ж саму роботу автоматично у фоновому режимі і набагато швидше. +Після цього необхідно при кожній зміні запускати команду `composer dumpautoload` і перегенерувати таблиці автозавантаження. Це надзвичайно незручно, і набагато краще доручити це завдання [RobotLoader|robot-loader:], який виконує ту саму дію автоматично у фоновому режимі та набагато швидше. -Другий варіант - слідувати [PSR-4 |https://www.php-fig.org/psr/psr-4/]. Простіше кажучи, це система, в якій простори імен та імена класів відповідають структурі каталогів та іменам файлів, тобто `App\Router\RouterFactory` знаходиться у файлі `/path/to/App/Router/RouterFactory.php`. Приклад конфігурації: +Другий варіант — дотримуватися [PSR-4|https://www.php-fig.org/psr/psr-4/]. Спрощено кажучи, це система, де простори імен та назви класів відповідають структурі каталогів та назвам файлів, тобто, наприклад, `App\Core\RouterFactory` буде знаходитись у файлі `/path/to/App/Core/RouterFactory.php`. Приклад конфігурації: ```js { "autoload": { "psr-4": { - "App\\": "app/" # пространство имён App\ указывает на директорию app/ + "App\\": "app/" # простір імен App\ знаходиться в каталозі app/ } } } ``` -Як саме налаштувати цю поведінку, дивіться в [документації Composer |https://getcomposer.org/doc/04-schema.md#PSR-4]. +Як саме налаштувати поведінку, ви дізнаєтеся в [документації Composer|https://getcomposer.org/doc/04-schema.md#psr-4]. -Тестування нових версій .[#toc-testing-new-versions] -==================================================== +Тестування нових версій +======================= -Ви хочете протестувати нову версію пакета. Як це зробити? По-перше, додайте цю пару опцій до файлу `composer.json`, яка дозволить вам встановлювати версії пакетів для розробки, але зробить це тільки в тому випадку, якщо немає стабільної версії, що відповідає вимогам: +Ви хочете протестувати нову розробницьку версію пакета. Як це зробити? Спочатку додайте до файлу `composer.json` цю пару опцій, яка дозволить встановлювати розробницькі версії пакетів, але вдасться до цього лише в тому випадку, якщо не існує жодної комбінації стабільних версій, яка б задовольняла вимогам: ```js { @@ -210,9 +215,9 @@ Packagist.org - глобальний репозиторій .[#toc-packagist-org } ``` -Ми також рекомендуємо видалити файл `composer.lock`, оскільки іноді Composer незрозумілим чином відмовляється встановлюватися, і це вирішить проблему. +Далі рекомендуємо видалити файл `composer.lock`, іноді Composer незрозуміло відмовляється від встановлення, і це вирішує проблему. -Припустимо, пакет - `nette/utils` і нова версія - 4.0. Ви встановлюєте його за допомогою команди: +Припустимо, йдеться про пакет `nette/utils`, і нова версія має номер 4.0. Встановіть її командою: ```shell composer require nette/utils:4.0.x-dev @@ -224,20 +229,19 @@ composer require nette/utils:4.0.x-dev composer require nette/utils:4.0.0-RC2 ``` -Якщо інший пакет залежить від бібліотеки і заблокований на старішу версію (наприклад, `^3.1`), ідеально буде оновити пакет для роботи з новою версією. -Однак якщо ви просто хочете обійти обмеження і змусити Composer встановити версію розробки і прикинутися, що це стара версія (наприклад, 3.1.6), ви можете використовувати ключове слово `as`: +Але якщо від бібліотеки залежить інший пакет, який заблокований на старішій версії (наприклад, `^3.1`), то ідеально оновити цей пакет, щоб він працював з новою версією. Якщо ж ви хочете просто обійти обмеження і змусити Composer встановити розробницьку версію, видаючи її за старішу (наприклад, 3.1.6), ви можете використати ключове слово `as`: ```shell composer require nette/utils "4.0.x-dev as 3.1.6" ``` -Команди виклику .[#toc-calling-commands] -======================================== +Виклик команд +============= -Ви можете викликати свої власні користувацькі команди і сценарії через Composer, як якщо б це були рідні команди Composer. Скриптам, розташованим у папці `vendor/bin`, не потрібно вказувати цю папку. +Через Composer можна викликати власні підготовлені команди та скрипти, ніби це нативні команди Composer. Для скриптів, що знаходяться в папці `vendor/bin`, не потрібно вказувати цю папку. -Як приклад ми визначаємо сценарій у файлі `composer.json`, який використовує [Nette Tester |tester:] для запуску тестів: +Як приклад, визначимо в файлі `composer.json` скрипт, який за допомогою [Nette Tester|tester:] запустить тести: ```js { @@ -247,19 +251,19 @@ composer require nette/utils "4.0.x-dev as 3.1.6" } ``` -Потім ми запускаємо тести за допомогою `composer tester`. Ми можемо викликати команду, навіть якщо перебуваємо не в кореневій папці проєкту, а в підкаталозі. +Тести потім запустимо за допомогою `composer tester`. Команду можна викликати, навіть якщо ми не знаходимося в кореневій папці проекту, а в якомусь підкаталозі. -Надішліть подяку .[#toc-send-thanks] -==================================== +Надішліть подяку +================ -Ми покажемо вам трюк, який порадує авторів відкритих вихідних кодів. Ви можете легко присвоїти зірку на GitHub бібліотекам, які використовує ваш проект. Просто встановіть бібліотеку `symfony/thanks`: +Ми покажемо вам трюк, яким ви порадуєте авторів open source. Простим способом ви поставите зірочку на GitHub бібліотекам, які використовує ваш проект. Достатньо встановити бібліотеку `symfony/thanks`: ```shell composer global require symfony/thanks ``` -А потім наберіть: +А потім запустити: ```shell composer thanks @@ -268,13 +272,11 @@ composer thanks Спробуйте! -Конфігурація .[#toc-configuration] -================================== +Конфігурація +============ -Composer тісно інтегрований з інструментом контролю версій [Git |https://git-scm.com]. Якщо ви не використовуєте Git, необхідно повідомити про це Composer: +Composer тісно пов'язаний з інструментом версіонування [Git |https://git-scm.com]. Якщо він у вас не встановлений, потрібно сказати Composer, щоб він його не використовував: ```shell composer -g config preferred-install dist ``` - -{{sitename: Найкращі практики}} diff --git a/best-practices/uk/creating-editing-form.texy b/best-practices/uk/creating-editing-form.texy index a7b3ba4440..415142d3ae 100644 --- a/best-practices/uk/creating-editing-form.texy +++ b/best-practices/uk/creating-editing-form.texy @@ -2,15 +2,15 @@ ***************************************** .[perex] -Як правильно реалізувати додавання і редагування запису в Nette, використовуючи для цього одну і ту ж форму? +Як правильно реалізувати в Nette додавання та редагування запису, використовуючи для обох операцій одну й ту саму форму? -У багатьох випадках форми для додавання і редагування запису однакові, розрізняючись тільки міткою на кнопці. Ми покажемо приклади простих презентерів, де ми використовуємо форму спочатку для додавання запису, потім для його редагування, і, нарешті, об'єднуємо ці два рішення. +У багатьох випадках форми для додавання та редагування запису однакові, відрізняючись, наприклад, лише написом на кнопці. Ми покажемо приклади простих презентерів, де форму спочатку використаємо для додавання запису, потім для редагування, і нарешті об'єднаємо обидва рішення. -Додавання запису .[#toc-adding-a-record] ----------------------------------------- +Додавання запису +---------------- -Приклад презентера, використовуваного для додавання запису. Ми залишимо роботу з базою даних класу `Facade`, код якого не має відношення до цього прикладу. +Приклад презентера, що служить для додавання запису. Саму роботу з базою даних залишимо класу `Facade`, код якого не є суттєвим для прикладу. ```php @@ -27,7 +27,7 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $form = new Form; - // ... додати поля форми ... + // ... додамо поля форми ... $form->onSuccess[] = [$this, 'recordFormSucceeded']; return $form; @@ -35,8 +35,8 @@ class RecordPresenter extends Nette\Application\UI\Presenter public function recordFormSucceeded(Form $form, array $data): void { - $this->facade->add($data); // додаємо запис в базу даних - $this->flashMessage('Успішно додано'); + $this->facade->add($data); // додавання запису до бази даних + $this->flashMessage('Successfully added'); $this->redirect('...'); } @@ -48,10 +48,10 @@ class RecordPresenter extends Nette\Application\UI\Presenter ``` -Редагування запису .[#toc-editing-a-record] -------------------------------------------- +Редагування запису +------------------ -Тепер давайте подивимося, який вигляд матиме презентер, що використовується для редагування записів: +Тепер покажемо, як виглядав би презентер, що служить для редагування запису: ```php @@ -70,8 +70,8 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $record = $this->facade->get($id); if ( - !$record // перевірити існування запису - || !$this->facade->isEditAllowed(/*...*/) // перевірити права доступу + !$record // перевірка існування запису + || !$this->facade->isEditAllowed(/*...*/) // перевірка прав доступу ) { $this->error(); // помилка 404 } @@ -81,32 +81,32 @@ class RecordPresenter extends Nette\Application\UI\Presenter protected function createComponentRecordForm(): Form { - // перевірте, щоб дія була "редагування + // перевіримо, що дія є 'edit' if ($this->getAction() !== 'edit') { $this->error(); } $form = new Form; - // ... додати поля форми ... + // ... додамо поля форми ... - $form->setDefaults($this->record); // встановити значення за замовчуванням + $form->setDefaults($this->record); // встановлення значень за замовчуванням $form->onSuccess[] = [$this, 'recordFormSucceeded']; return $form; } public function recordFormSucceeded(Form $form, array $data): void { - $this->facade->update($this->record->id, $data); // оновлюємо запис - $this->flashMessage('Успішно оновлено'); + $this->facade->update($this->record->id, $data); // оновлення запису + $this->flashMessage('Successfully updated'); $this->redirect('...'); } } ``` -У методі *action*, який викликається на самому початку життєвого циклу [презентера |application:presenters#Life-Cycle-of-Presenter], ми перевіряємо існування запису і дозвіл користувача на його редагування. +У методі *action*, який запускається на самому початку [життєвого циклу презентера |application:presenters#Життєвий цикл презентера], ми перевіряємо існування запису та права користувача на його редагування. -Ми зберігаємо запис у властивості `$record`, щоб він був доступний у методі `createComponentRecordForm()` для встановлення значень за замовчуванням і `recordFormSucceeded()` для ідентифікатора. Альтернативним рішенням може бути встановлення значень за замовчуванням безпосередньо в `actionEdit()` і значення ID, який є частиною URL і витягується за допомогою `getParameter('id')`: +Запис ми зберігаємо у властивості `$record`, щоб мати до нього доступ у методі `createComponentRecordForm()` для встановлення значень за замовчуванням, та в `recordFormSucceeded()` для отримання ID. Альтернативним рішенням було б встановити значення за замовчуванням безпосередньо в `actionEdit()` та отримати значення ID, яке є частиною URL, за допомогою `getParameter('id')`: ```php @@ -114,12 +114,12 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $record = $this->facade->get($id); if ( - // перевірка існування та перевірка прав доступу + // перевірка існування та прав доступу ) { $this->error(); } - // встановити значення форми за замовчуванням + // встановлення значень за замовчуванням форми $this->getComponent('recordForm') ->setDefaults($record); } @@ -133,13 +133,13 @@ class RecordPresenter extends Nette\Application\UI\Presenter } ``` -Однак, і це має бути **найважливішим висновком з усього коду**, нам потрібно переконатися, що дія дійсно `edit`, коли ми створюємо форму. Тому що інакше валідація в методі `actionEdit()` взагалі не відбудеться! +Однак, і це має бути **найважливішим висновком усього коду**, ми повинні при створенні форми переконатися, що дія дійсно є `edit`. Бо інакше перевірка в методі `actionEdit()` взагалі не відбудеться! -Одна й та сама форма для додавання та редагування .[#toc-same-form-for-adding-and-editing] ------------------------------------------------------------------------------------------- +Однакова форма для додавання та редагування +------------------------------------------- -А зараз ми об'єднаємо обидва презентера в один. Або ми можемо відрізнити, яку дію задіяно в методі `createComponentRecordForm()` і налаштувати форму відповідним чином, або ми можемо залишити це безпосередньо action-методам і позбутися умови: +А тепер об'єднаємо обидва презентери в один. Ми могли б у методі `createComponentRecordForm()` розрізняти, про яку дію йдеться, і відповідно конфігурувати форму, або ж можемо залишити це безпосередньо для action-методів і позбутися умови: ```php @@ -160,47 +160,46 @@ class RecordPresenter extends Nette\Application\UI\Presenter { $record = $this->facade->get($id); if ( - !$record // перевірити існування запису - || !$this->facade->isEditAllowed(/*...*/) // перевірити права доступу + !$record // перевірка існування запису + || !$this->facade->isEditAllowed(/*...*/) // перевірка прав доступу ) { $this->error(); // помилка 404 } $form = $this->getComponent('recordForm'); - $form->setDefaults($record); // встановити значення за замовчуванням + $form->setDefaults($record); // встановлення значень за замовчуванням $form->onSuccess[] = [$this, 'editingFormSucceeded']; } protected function createComponentRecordForm(): Form { - // перевірте, щоб дія була "додати" або "редагувати + // перевіримо, що дія є 'add' або 'edit' if (!in_array($this->getAction(), ['add', 'edit'])) { $this->error(); } $form = new Form; - // ... додати поля форми ... + // ... додамо поля форми ... return $form; } public function addingFormSucceeded(Form $form, array $data): void { - $this->facade->add($data); // додаємо запис у базу даних - $this->flashMessage('Успішно додано'); + $this->facade->add($data); // додавання запису до бази даних + $this->flashMessage('Successfully added'); $this->redirect('...'); } public function editingFormSucceeded(Form $form, array $data): void { $id = (int) $this->getParameter('id'); - $this->facade->update($id, $data); // оновлюємо запис - $this->flashMessage('Успішно оновлено'); + $this->facade->update($id, $data); // оновлення запису + $this->flashMessage('Successfully updated'); $this->redirect('...'); } } ``` {{priority: -1}} -{{sitename: Найкращі практики}} diff --git a/best-practices/uk/dynamic-snippets.texy b/best-practices/uk/dynamic-snippets.texy index 40867a88cf..3a8f9f5f63 100644 --- a/best-practices/uk/dynamic-snippets.texy +++ b/best-practices/uk/dynamic-snippets.texy @@ -1,7 +1,7 @@ Динамічні сніпети ***************** -Досить часто при розробці додатків виникає необхідність виконання операцій AJAX, наприклад, в окремих рядках таблиці або елементах списку. Як приклад, ми можемо вибрати список статей, даючи змогу користувачеві, який увійшов у систему, вибрати "подобається/не подобається" для кожної з них. Код презентера і відповідного шаблону без AJAX матиме приблизно такий вигляд (я перераховую найважливіші фрагменти, код передбачає наявність сервісу для розмітки рейтингів і отримання колекції статей - конкретна реалізація не важлива для цілей цього посібника): +Досить часто під час розробки додатків виникає потреба виконувати AJAX-операції, наприклад, над окремими рядками таблиці або елементами списку. Для прикладу можемо взяти виведення статей, причому для кожної з них дозволимо зареєстрованому користувачеві вибрати оцінку "подобається/не подобається". Код презентера та відповідного шаблону без AJAX виглядатиме приблизно так (наводжу найважливіші фрагменти, код розраховує на існування сервісу для позначення оцінок та отримання колекції статей - конкретна реалізація не важлива для цілей цього посібника): ```php public function handleLike(int $articleId): void @@ -17,33 +17,33 @@ public function handleUnlike(int $articleId): void } ``` -Template: +Шаблон: ```latte <article n:foreach="$articles as $article"> <h2>{$article->title}</h2> <div class="content">{$article->content}</div> {if !$article->liked} - <a n:href="like! $article->id" class=ajax>Мне нравится</a> + <a n:href="like! $article->id" class=ajax>{* це мені подобається *}</a> {else} - <a n:href="unlike! $article->id" class=ajax>Мне не нравится</a> + <a n:href="unlike! $article->id" class=ajax>{* мені це вже не подобається *}</a> {/if} </article> ``` -Аяксизація .[#toc-ajaxization] -============================== +Аяксифікація +============ -Тепер давайте привнесемо AJAX у цей простий додаток. Зміна рейтингу статті не настільки важлива, щоб вимагати HTTP-запит із переспрямуванням, тому в ідеалі це має бути зроблено за допомогою AJAX у фоновому режимі. Ми будемо використовувати [скрипт обробника з додат |https://componette.org/vojtech-dobes/nette.ajax.js/] ків зі звичайною угодою, що AJAX-посилання мають CSS клас `ajax`. +Тепер давайте оснастимо цей простий додаток AJAX. Зміна оцінки статті не настільки важлива, щоб вимагати перенаправлення, тому ідеально було б, щоб вона відбувалася за допомогою AJAX у фоновому режимі. Ми використаємо [скрипт обробки з доповнень |application:ajax#Naja] зі звичайною конвенцією, що AJAX-посилання мають CSS-клас `ajax`. -Однак як це зробити конкретно? Nette пропонує 2 способи: спосіб динамічних фрагментів і спосіб компонентів. Обидва мають свої плюси та мінуси, тому ми покажемо їх по черзі. +Однак, як це зробити конкретно? Nette пропонує 2 шляхи: шлях так званих динамічних сніпетів та шлях компонентів. Обидва мають свої переваги та недоліки, тому ми розглянемо їх по черзі. -Шлях динамічних сніпетів .[#toc-the-dynamic-snippets-way] -========================================================= +Шлях динамічних сніпетів +======================== -У термінології Latte динамічний сніппет - це особливий випадок використання тега `{snippet}`, коли в імені сніппета використовується змінна. Такий сніппет не може бути знайдений просто в будь-якому місці шаблону - він має бути обгорнутий статичним сніппетом, тобто звичайний, або всередині `{snippetArea}`. Ми можемо змінити наш шаблон таким чином. +Динамічний сніпет в термінології Latte означає специфічний випадок використання тегу `{snippet}`, коли в назві сніпета використовується змінна. Такий сніпет не може знаходитися будь-де в шаблоні - він повинен бути обгорнутий статичним сніпетом, тобто звичайним, або всередині `{snippetArea}`. Наш шаблон можна було б змінити наступним чином. ```latte @@ -53,18 +53,18 @@ Template: <div class="content">{$article->content}</div> {snippet article-{$article->id}} {if !$article->liked} - <a n:href="like! $article->id" class=ajax>Мне нравится</a> + <a n:href="like! $article->id" class=ajax>{* це мені подобається *}</a> {else} - <a n:href="unlike! $article->id" class=ajax>Мне не нравится</a> + <a n:href="unlike! $article->id" class=ajax>{* мені це вже не подобається *}</a> {/if} {/snippet} </article> {/snippet} ``` -Кожна стаття тепер визначає один сніппет, який має ID статті в заголовку. Усі ці фрагменти потім об'єднуються в один фрагмент під назвою `articlesContainer`. Якщо ми опустимо цей фрагмент обгортки, Latte попередить нас про виключення. +Кожна стаття тепер визначає один сніпет, який має в назві ID статті. Всі ці сніпети потім разом обгорнуті одним сніпетом з назвою `articlesContainer`. Якби ми пропустили цей обгортаючий сніпет, Latte повідомить нас про це винятком. -Все, що залишилося зробити, це додати перемальовування в презентер - просто перемалювати статичну обгортку. +Залишається додати до презентера перемальовування - достатньо перемалювати статичну обгортку. ```php public function handleLike(int $articleId): void @@ -72,18 +72,18 @@ public function handleLike(int $articleId): void $this->ratingService->saveLike($articleId, $this->user->id); if ($this->isAjax()) { $this->redrawControl('articlesContainer'); - // $this->redrawControl('article-' . $articleId); -- нет необходимости + // $this->redrawControl('article-' . $articleId); -- не потрібно } else { $this->redirect('this'); } } ``` -Змініть споріднений метод `handleUnlike()` таким самим чином, і AJAX працюватиме! +Аналогічно змінимо і сестринський метод `handleUnlike()`, і AJAX запрацює! -Однак у цього рішення є і зворотний бік. Якщо ми докладніше розглянемо, як працює AJAX-запит, то виявимо, що хоча застосунок має ефективний зовнішній вигляд (він повертає тільки один сніпет для цієї статті), насправді він відображає всі сніпети на сервері. Він помістив потрібний фрагмент у наше корисне навантаження, а решту відкинув (таким чином, абсолютно без необхідності, він також витягнув їх із бази даних). +Однак рішення має один недолік. Якщо ми детальніше дослідимо, як відбувається AJAX-запит, то виявимо, що хоча зовні додаток виглядає економним (повертає лише один єдиний сніпет для даної статті), насправді на сервері він відрендерив усі сніпети. Потрібний сніпет він помістив у payload, а решту відкинув (отже, також абсолютно марно отримав їх із бази даних). -Щоб оптимізувати цей процес, нам знадобиться дія, під час якої ми передаємо колекцію `$articles` шаблону (скажімо, у методі `renderDefault()`). Ми скористаємося тим, що обробка сигналу відбувається до методів `render<Something>`: +Щоб оптимізувати цей процес, нам доведеться втрутитися там, де ми передаємо колекцію `$articles` до шаблону (скажімо, в методі `renderDefault()`). Ми скористаємося тим фактом, що обробка сигналів відбувається перед методами `render<Something>`: ```php public function handleLike(int $articleId): void @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connection->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,18 +101,18 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connection->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` -Тепер, коли сигнал обробляється, замість колекції з усіма статтями в шаблон передається тільки масив з однією статтею - тією, яку ми хочемо відобразити і відправити в корисному навантаженні браузеру. Таким чином, `{foreach}` буде виконано тільки один раз, і жодних додаткових сніпетів не буде виведено. +Тепер при обробці сигналу до шаблону передається замість колекції з усіма статтями лише масив з єдиною статтею - тією, яку ми хочемо відрендерити та надіслати в payload до браузера. `{foreach}` таким чином пройде лише один раз, і жодних зайвих сніпетів не відрендериться. -Компонентний спосіб .[#toc-component-way] -========================================= +Шлях компонентів +================ -Зовсім інше рішення використовує інший підхід, щоб уникнути динамічних сніпетів. Хитрість полягає в тому, щоб перенести всю логіку в окремий компонент - відтепер у нас не презентер, що піклуватиметься про введення рейтингу, а спеціальний `LikeControl`. Клас матиме такий вигляд (крім того, він також міститиме `render`, `handleUnlike` і т. д. методи): +Абсолютно інший спосіб вирішення уникає динамічних сніпетів. Трюк полягає в перенесенні всієї логіки в окремий компонент - відтепер про введення оцінки дбатиме не презентер, а спеціалізований `LikeControl`. Клас виглядатиме наступним чином (крім того, він міститиме також методи `render`, `handleUnlike` тощо): ```php class LikeControl extends Nette\Application\UI\Control @@ -139,26 +139,26 @@ class LikeControl extends Nette\Application\UI\Control ```latte {snippet} {if !$article->liked} - <a n:href="like!" class=ajax>Мне нравится</a> + <a n:href="like!" class=ajax>{* це мені подобається *}</a> {else} - <a n:href="unlike!" class=ajax>Мне не нравится</a> + <a n:href="unlike!" class=ajax>{* мені це вже не подобається *}</a> {/if} {/snippet} ``` -Звичайно, ми змінимо шаблон подання, і нам доведеться додати фабрику до презентера. Оскільки ми будемо створювати компонент стільки разів, скільки статей ми отримаємо з бази даних, ми будемо використовувати клас [Multiplier |application:Multiplier] для цього: +Звичайно, шаблон view зміниться, і нам доведеться додати до презентера фабрику. Оскільки ми створимо компонент стільки разів, скільки статей отримаємо з бази даних, ми використаємо для його "розмноження" клас [application:Multiplier]. ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); } ``` -Вигляд шаблону скорочено до необхідного мінімуму (і повністю вільний від сніпетів!): +Шаблон view зменшиться до необхідного мінімуму (і повністю позбавиться сніпетів!): ```latte <article n:foreach="$articles as $article"> @@ -168,7 +168,6 @@ protected function createComponentLikeControl() </article> ``` -Ми майже закінчили: додаток тепер працюватиме в AJAX. Тут також необхідно оптимізувати застосунок, оскільки через використання бази даних Nette, обробка сигналу буде надмірно завантажувати всі статті з бази даних замість однієї. Однак перевага в тому, що рендерінгу не буде, бо насправді рендерується лише наш компонент. +Майже готово: додаток тепер працюватиме за допомогою AJAX. Тут також нас чекає оптимізація додатку, оскільки через використання Nette Database при обробці сигналу марно завантажуються всі статті з бази даних замість однієї. Перевагою, однак, є те, що їх рендеринг не відбудеться, оскільки відрендериться дійсно лише наш компонент. {{priority: -1}} -{{sitename: Найкращі практики}} diff --git a/best-practices/uk/editors-and-tools.texy b/best-practices/uk/editors-and-tools.texy index 66372f474e..86380ff279 100644 --- a/best-practices/uk/editors-and-tools.texy +++ b/best-practices/uk/editors-and-tools.texy @@ -2,39 +2,39 @@ ************************ .[perex] -Ви можете бути вправним програмістом, але тільки з хорошими інструментами ви станете майстром. У цьому розділі ви знайдете поради щодо важливих інструментів, редакторів та плагінів. +Ви можете бути вправним програмістом, але лише з хорошими інструментами ви станете майстром. У цьому розділі ви знайдете поради щодо важливих інструментів, редакторів та плагінів. -Редактор IDE .[#toc-ide-editor] -=============================== +IDE редактор +============ -Ми наполегливо рекомендуємо використовувати для розробки повнофункціональну IDE, таку як PhpStorm, NetBeans, VS Code, а не просто текстовий редактор з підтримкою PHP. Різниця дійсно принципова. Немає причин задовольнятися класичним редактором із підсвічуванням синтаксису, тому що він не дотягує до можливостей IDE з точним реченням коду, можливістю рефакторінгу коду тощо. Деякі IDE є платними, інші - безкоштовними. +Ми наполегливо рекомендуємо використовувати для розробки повноцінне IDE, таке як PhpStorm, NetBeans, VS Code, а не просто текстовий редактор з підтримкою PHP. Різниця справді суттєва. Немає причин задовольнятися простим редактором, який хоч і вміє підсвічувати синтаксис, але не досягає можливостей топового IDE, яке точно підказує, відстежує помилки, вміє рефакторити код та багато іншого. Деякі IDE платні, інші навіть безкоштовні. **NetBeans IDE** має вбудовану підтримку Nette, Latte та NEON. -**PhpStorm**: встановіть ці плагіни за посиланням `Settings > Plugins > Marketplace`: +**PhpStorm**: встановіть ці плагіни в `Settings > Plugins > Marketplace` - Nette framework helpers - Latte - NEON support - Nette Tester -**VS Code**: знайдіть плагін "Nette Latte + Neon" у маркетплейсі. +**VS Code**: знайдіть у marketplace плагін "Nette Latte + Neon". -Також підключіть Трейсі до редактора. Коли відобразиться сторінка з помилками, ви можете натиснути на назви файлів, і вони відкриються в редакторі при наведенні курсору на відповідний рядок. Дізнайтеся, [як налаштувати систему |tracy:open-files-in-ide]. +Також зв'яжіть Tracy з редактором. При відображенні сторінки помилки можна буде клікнути на імена файлів, і вони відкриються в редакторі з курсором на відповідному рядку. Прочитайте, [як налаштувати систему|tracy:open-files-in-ide]. -PHPStan .[#toc-phpstan] -======================= +PHPStan +======= -PHPStan - це інструмент, який виявляє логічні помилки у вашому коді до того, як ви його запустите. +PHPStan — це інструмент, який виявляє логічні помилки в коді ще до його запуску. -Встановіть його через Composer: +Встановимо його за допомогою Composer: ```shell composer require --dev phpstan/phpstan-nette ``` -Створіть у проекті конфігураційний файл `phpstan.neon`: +Створимо в проекті конфігураційний файл `phpstan.neon`: ```neon includes: @@ -47,40 +47,38 @@ parameters: level: 5 ``` -А потім дозвольте йому проаналізувати класи в папці `app/`: +А потім запустимо аналіз класів у папці `app/`: ```shell vendor/bin/phpstan analyse app ``` -Ви можете знайти вичерпну документацію безпосередньо на сайті [PHPStan |https://phpstan.org]. +Вичерпну документацію ви знайдете безпосередньо на [сайті PHPStan |https://phpstan.org]. -Code Checker .[#toc-code-checker] -================================= +Code Checker +============ -[Code Checker |code-checker:] перевіряє і за можливості виправляє деякі формальні помилки у вашому вихідному коді. +[Code Checker|code-checker:] перевіряє та, за потреби, виправляє деякі формальні помилки у ваших вихідних кодах: -- видаляє [BOM |nette:glossary#bom]. -- перевіряє валідність шаблонів [Latte |latte:]. -- перевіряє валідність файлів `.neon`, `.php` і `.json`. -- перевіряє наявність [керуючих символів |nette:glossary#Control-Characters]. -- перевіряє, чи закодований файл у UTF-8 -- контролює правильність написання `/* @annotations */` (пропущена друга зірочка) -- видаляє завершальні теги PHP `?>` у файлах PHP -- видаляє з кінця файлу пробільні символи і непотрібні порожні рядки -- нормалізує закінчення рядків до системного значення за замовчуванням (з параметром `-l`) +- видаляє [BOM |nette:glossary#BOM] +- перевіряє валідність шаблонів [Latte |latte:] +- перевіряє валідність файлів `.neon`, `.php` та `.json` +- перевіряє наявність [контрольних символів |nette:glossary#Керуючі символи] +- перевіряє, чи файл закодований у UTF-8 +- перевіряє помилково записані `/* @anotace */` (відсутня зірочка) +- видаляє завершальний `?>` у PHP файлах +- видаляє пробіли в кінці рядків та зайві рядки в кінці файлу +- нормалізує роздільники рядків до системних (якщо вказати опцію `-l`) -Composer .[#toc-composer] -========================= +Composer +======== -[Composer |Composer] - це інструмент для управління залежностями в PHP. Він дозволяє нам оголосити залежності бібліотек, і він встановить їх за нас у наш проект. +[Composer | Composer] — це інструмент для керування залежностями в PHP. Він дозволяє нам декларувати довільно складні залежності окремих бібліотек, а потім встановлює їх для нас у наш проект. -Перевірка вимог .[#toc-requirements-checker] -============================================ +Requirements Checker +==================== -Це був інструмент, який тестував робоче середовище сервера і повідомляв, чи можна (і якою мірою) використовувати фреймворк. Наразі Nette можна використовувати на будь-якому сервері, на якому встановлено мінімально необхідну версію PHP. - -{{sitename: Найкращі практики}} +Це був інструмент, який тестував середовище виконання сервера та інформував, чи (і якою мірою) можна використовувати фреймворк. На даний момент Nette можна використовувати на будь-якому сервері, який має мінімально необхідну версію PHP. diff --git a/best-practices/uk/form-reuse.texy b/best-practices/uk/form-reuse.texy index 7993290a51..d75d20642b 100644 --- a/best-practices/uk/form-reuse.texy +++ b/best-practices/uk/form-reuse.texy @@ -1,16 +1,16 @@ -Повторне використання форм у різних місцях -****************************************** +Повторне використання форм у кількох місцях +******************************************* .[perex] -У Nette є кілька варіантів повторного використання однієї і тієї ж форми в різних місцях без дублювання коду. У цій статті ми розглянемо різні рішення, включаючи ті, яких варто уникати. +У Nette у вас є кілька варіантів використання однієї й тієї ж форми в кількох місцях без дублювання коду. У цій статті ми розглянемо різні рішення, включно з тими, яких слід уникати. -Фабрика форм .[#toc-form-factory] -================================= +Фабрика форм +============ -Один з основних підходів до використання одного і того ж компонента в різних місцях - це створення методу або класу, який генерує компонент, а потім виклик цього методу в різних місцях програми. Такий метод або клас називається *фабрикою*. Будь ласка, не плутайте з паттерном проектування *фабричний метод*, який описує специфічний спосіб використання фабрик і не має відношення до цієї теми. +Одним з основних підходів до використання одного й того ж компонента в кількох місцях є створення методу або класу, який генерує цей компонент, і подальше викликання цього методу в різних місцях програми. Такий метод або клас називається *фабрикою*. Будь ласка, не плутайте з патерном проектування *factory method*, який описує специфічний спосіб використання фабрик і не пов'язаний з цією темою. -Для прикладу, давайте створимо фабрику, яка буде створювати форму редагування: +Як приклад, створимо фабрику, яка буде збирати форму редагування: ```php use Nette\Application\UI\Form; @@ -20,22 +20,22 @@ class FormFactory public function createEditForm(): Form { $form = new Form; - $form->addText('title', 'Title:'); - // тут додаються додаткові поля форми - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Заголовок:'); + // тут додаються інші поля форми + $form->addSubmit('send', 'Надіслати'); return $form; } } ``` -Тепер ви можете використовувати цю фабрику в різних місцях вашого додатку, наприклад, у презентаторах або компонентах. І ми зробимо це, [запросивши її як залежність |dependency-injection:passing-dependencies]. Отже, спочатку ми запишемо клас до конфігураційного файлу: +Тепер ви можете використовувати цю фабрику в різних місцях вашої програми, наприклад, у презентерах або компонентах. Це робиться шляхом [запрошення її як залежності|dependency-injection:passing-dependencies]. Спочатку запишемо клас у конфігураційний файл: ```neon services: - FormFactory ``` -А потім використаємо його у презентаторі: +А потім використаємо її в презентері: ```php @@ -57,7 +57,7 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Ви можете розширити фабрику форм додатковими методами, щоб створювати інші типи форм відповідно до вашої програми. І, звичайно, ви можете додати метод, який створює базову форму без елементів, яку будуть використовувати інші методи: +Фабрику форм можна розширити додатковими методами для створення інших типів форм відповідно до потреб вашої програми. І, звичайно, ми можемо додати метод, який створить базову форму без елементів, і цей метод будуть використовувати інші методи: ```php class FormFactory @@ -71,9 +71,9 @@ class FormFactory public function createEditForm(): Form { $form = $this->createForm(); - $form->addText('title', 'Title:'); - // тут додаються додаткові поля форми - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Заголовок:'); + // тут додаються інші поля форми + $form->addSubmit('send', 'Надіслати'); return $form; } } @@ -82,10 +82,10 @@ class FormFactory Метод `createForm()` поки що не робить нічого корисного, але це швидко зміниться. -Заводські залежності .[#toc-factory-dependencies] -================================================= +Залежності фабрики +================== -З часом стане очевидно, що нам потрібно, щоб форми були багатомовними. Це означає, що нам потрібно налаштувати [перекладач |forms:rendering#Translating] для всіх форм. Для цього ми модифікуємо клас `FormFactory`, щоб він приймав об'єкт `Translator` як залежність у конструкторі, і передавав його формі: +З часом виявиться, що нам потрібно, щоб форми були багатомовними. Це означає, що всім формам потрібно встановити так званий [translator |forms:rendering#Переклад]. Для цього ми змінимо клас `FormFactory` так, щоб він приймав об'єкт `Translator` як залежність у конструкторі, і передамо його формі: ```php use Nette\Localization\Translator; @@ -104,18 +104,17 @@ class FormFactory return $form; } - //... + // ... } ``` -Оскільки метод `createForm()` також викликається іншими методами, які створюють конкретні форми, нам потрібно встановити транслятор тільки в цьому методі. І все готово. Не потрібно змінювати код доповідача або компонента, що дуже добре. +Оскільки метод `createForm()` викликають і інші методи, що створюють специфічні форми, достатньо встановити translator лише в ньому. І все готово. Не потрібно змінювати код жодного презентера чи компонента, що чудово. -Більше фабричних класів .[#toc-more-factory-classes] -==================================================== +Кілька фабричних класів +======================= -Крім того, ви можете створити кілька класів для кожної форми, яку хочете використовувати у своєму додатку. -Такий підхід може підвищити читабельність коду і полегшити керування формами. Залиште оригінальний `FormFactory` для створення простої форми з базовою конфігурацією (наприклад, з підтримкою перекладу) і створіть новий заводський `EditFormFactory` для форми редагування. +Альтернативно, ви можете створити кілька класів для кожної форми, яку хочете використовувати у вашій програмі. Цей підхід може підвищити читабельність коду та полегшити керування формами. Оригінальну `FormFactory` залишимо створювати лише чисту форму з базовою конфігурацією (наприклад, з підтримкою перекладів), а для форми редагування створимо нову фабрику `EditFormFactory`. ```php class FormFactory @@ -145,40 +144,39 @@ class EditFormFactory public function create(): Form { $form = $this->formFactory->create(); - // тут додаються додаткові поля форми - $form->addSubmit('send', 'Save'); + // тут додаються інші поля форми + $form->addSubmit('send', 'Надіслати'); return $form; } } ``` -Дуже важливо, щоб зв'язок між класами `FormFactory` і `EditFormFactory` був реалізований за допомогою композиції, а не успадкування об'єктів: +Дуже важливо, щоб зв'язок між класами `FormFactory` та `EditFormFactory` був реалізований [композицією |nette:introduction-to-object-oriented-programming#Композиція], а не [об'єктною спадковістю |nette:introduction-to-object-oriented-programming#Успадкування]: ```php -// НІ! СПАДЩИНА ТУТ НЕ МАЄ ЗНАЧЕННЯ +// ⛔ ТАК НЕ РОБИТИ! ТУТ СПАДКУВАННЯ НЕ ДО РЕЧІ class EditFormFactory extends FormFactory { public function create(): Form { $form = parent::create(); - $form->addText('title', 'Title:'); - // тут додаються додаткові поля форми - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Заголовок:'); + // тут додаються інші поля форми + $form->addSubmit('send', 'Надіслати'); return $form; } } ``` -Використання успадкування в цьому випадку було б абсолютно контрпродуктивним. Ви б дуже швидко зіткнулися з проблемами. Наприклад, якщо ви захочете додати параметри до методу `create()`; PHP повідомить про помилку, що його сигнатура відрізняється від батьківської. -Або при передачі залежності класу `EditFormFactory` через конструктор. Це призведе до того, що ми називаємо пеклом [конструктора |dependency-injection:passing-dependencies#Constructor hell]. +Використання спадковості в цьому випадку було б абсолютно контрпродуктивним. Ви б дуже швидко зіткнулися з проблемами. Наприклад, коли б ви захотіли додати параметри до методу `create()`; PHP повідомив би про помилку, що його сигнатура відрізняється від батьківської. Або при передачі залежності до класу `EditFormFactory` через конструктор. Виникла б ситуація, яку ми називаємо [constructor hell |dependency-injection:passing-dependencies#Пекло конструкторів]. -Загалом, краще надавати перевагу композиції, а не успадкуванню. +Загалом, краще надавати перевагу [композиції перед спадковістю |dependency-injection:faq#Чому композиції надається перевага перед успадкуванням]. -Обробка форм .[#toc-form-handling] -================================== +Обробка форми +============= -Обробник форми, який викликається після успішного відправлення, також може бути частиною фабричного класу. Він буде працювати, передаючи надіслані дані моделі для обробки. Будь-які помилки будуть передані [назад |forms:validation#Processing Errors] у форму. У наступному прикладі модель представлена класом `Facade`: +Обробник форми, який викликається після успішного надсилання, також може бути частиною фабричного класу. Він працюватиме так, що передасть надіслані дані моделі для обробки. Можливі помилки [передасть назад |forms:validation#Помилки під час обробки] до форми. Модель у наступному прикладі представляє клас `Facade`: ```php class EditFormFactory @@ -192,9 +190,9 @@ class EditFormFactory public function create(): Form { $form = $this->formFactory->create(); - $form->addText('title', 'Title:'); - // тут додаються додаткові поля форми - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Заголовок:'); + // тут додаються інші поля форми + $form->addSubmit('send', 'Надіслати'); $form->onSuccess[] = [$this, 'processForm']; return $form; } @@ -202,7 +200,7 @@ class EditFormFactory public function processForm(Form $form, array $data): void { try { - // обробка наданих даних + // обробка надісланих даних $this->facade->process($data); } catch (AnyModelException $e) { @@ -212,7 +210,7 @@ class EditFormFactory } ``` -Дозвольте доповідачу самому обробляти перенаправлення. Він додасть ще один обробник до події `onSuccess`, який виконає перенаправлення. Це дозволить використовувати форму в різних презентерах, і кожен з них зможе перенаправляти в інше місце. +Однак саме перенаправлення ми залишимо на презентері. Він додасть до події `onSuccess` ще один обробник, який виконає перенаправлення. Завдяки цьому форму можна буде використовувати в різних презентерах і в кожному перенаправляти в інше місце. ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -226,7 +224,7 @@ class MyPresenter extends Nette\Application\UI\Presenter { $form = $this->formFactory->create(); $form->onSuccess[] = function () { - $this->flashMessage('Záznam byl uložen'); + $this->flashMessage('Запис було збережено'); $this->redirect('Homepage:'); }; return $form; @@ -234,39 +232,38 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Це рішення використовує властивість форм, яка полягає в тому, що коли на формі або її елементі викликається `addError()`, наступний обробник `onSuccess` не викликається. +Це рішення використовує властивість форм, що коли над формою або її елементом викликається `addError()`, наступний обробник `onSuccess` вже не викликається. -Успадкування від класу Form .[#toc-inheriting-from-the-form-class] -================================================================== +Спадкування від класу Form +========================== -Побудована форма не повинна бути дочірньою формою. Іншими словами, не використовуйте це рішення: +Скомпонована форма не повинна бути нащадком форми. Іншими словами, не використовуйте це рішення: ```php -// НІ! СПАДЩИНА ТУТ НЕ МАЄ ЗНАЧЕННЯ +// ⛔ ТАК НЕ РОБИТИ! ТУТ СПАДКУВАННЯ НЕ ДО РЕЧІ class EditForm extends Form { public function __construct(Translator $translator) { parent::__construct(); - $form->addText('title', 'Title:'); - // тут додаються додаткові поля форми - $form->addSubmit('send', 'Save'); - $form->setTranslator($translator); + $this->addText('title', 'Заголовок:'); + // тут додаються інші поля форми + $this->addSubmit('send', 'Надіслати'); + $this->setTranslator($translator); } } ``` -Замість того, щоб створювати форму в конструкторі, скористайтеся фабрикою. +Замість того, щоб збирати форму в конструкторі, використовуйте фабрику. -Важливо розуміти, що клас `Form` - це насамперед інструмент для збирання форми, тобто конструктор форм. А зібрану форму можна вважати його продуктом. Однак продукт не є окремим випадком конструктора, між ними немає зв'язку *is a*, який лежить в основі успадкування. +Потрібно усвідомити, що клас `Form` є насамперед інструментом для побудови форми, тобто *form builder*. А зібрану форму можна розглядати як її продукт. Однак продукт не є специфічним випадком білдера, між ними немає зв'язку *is a*, що лежить в основі спадковості. -Компонент форми .[#toc-form-component] -====================================== +Компонент з формою +================== -Зовсім інший підхід - створити [компонент |application:components], який містить форму. Це дає нові можливості, наприклад, рендерити форму певним чином, оскільки компонент містить шаблон. -Або можна використовувати сигнали для AJAX-комунікації та завантаження інформації у форму, наприклад, для підказки тощо. +Абсолютно інший підхід представляє створення [компонента|application:components], частиною якого є форма. Це дає нові можливості, наприклад, рендерити форму специфічним чином, оскільки частиною компонента є і шаблон. Або можна використовувати сигнали для AJAX-комунікації та дозавантаження інформації у форму, наприклад, для підказок тощо. ```php @@ -284,9 +281,9 @@ class EditControl extends Nette\Application\UI\Control protected function createComponentForm(): Form { $form = new Form; - $form->addText('title', 'Title:'); - // тут додаються додаткові поля форми - $form->addSubmit('send', 'Save'); + $form->addText('title', 'Заголовок:'); + // тут додаються інші поля форми + $form->addSubmit('send', 'Надіслати'); $form->onSuccess[] = [$this, 'processForm']; return $form; @@ -309,7 +306,7 @@ class EditControl extends Nette\Application\UI\Control } ``` -Створимо фабрику, яка буде виробляти цей компонент. Достатньо [написати її інтерфейс |application:components#Components with Dependencies]: +Ще створимо фабрику, яка буде виробляти цей компонент. Достатньо [записати її інтерфейс |application:components#Компоненти із залежностями]: ```php interface EditControlFactory @@ -318,14 +315,14 @@ interface EditControlFactory } ``` -І додати його до конфігураційного файлу: +І додати до конфігураційного файлу: ```neon services: - EditControlFactory ``` -І тепер ми можемо запитувати фабрику і використовувати її в презентері: +А тепер вже можемо запросити фабрику та використати її в презентері: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -335,13 +332,13 @@ class MyPresenter extends Nette\Application\UI\Presenter ) { } - protected function createComponentEditForm(): Form + protected function createComponentEditForm(): EditControl { $control = $this->controlFactory->create(); $control->onSave[] = function (EditControl $control, $data) { $this->redirect('this'); - // або перенаправлення на результат редагування, наприклад + // або перенаправляємо на результат редагування, напр.: // $this->redirect('detail', ['id' => $data->id]); }; @@ -349,5 +346,3 @@ class MyPresenter extends Nette\Application\UI\Presenter } } ``` - -{{sitename: Найкращі практики}} diff --git a/best-practices/uk/inject-method-attribute.texy b/best-practices/uk/inject-method-attribute.texy index 5c6b4d9057..8e64ef27ab 100644 --- a/best-practices/uk/inject-method-attribute.texy +++ b/best-practices/uk/inject-method-attribute.texy @@ -1,21 +1,18 @@ -Методи та атрибути ін'єкції -*************************** +Методи та атрибути inject +************************* .[perex] -У цій статті ми розглянемо різні способи передачі залежностей доповідачам у фреймворку Nette. Ми порівняємо основний метод, яким є конструктор, з іншими варіантами, такими як `inject` методи та атрибути. +У цій статті ми розглянемо різні способи передачі залежностей у презентери у фреймворку Nette. Ми порівняємо бажаний спосіб, яким є конструктор, з іншими варіантами, такими як методи та атрибути `inject`. -Для доповідачів передача залежностей за допомогою [конструктора |dependency-injection:passing-dependencies#Constructor Injection] є найкращим способом. -Однак, якщо ви створюєте спільного предка, від якого успадковують інші доповідачі (наприклад, BasePresenter), і цей предок також має залежності, виникає проблема, яку ми називаємо пеклом [конструктора |dependency-injection:passing-dependencies#Constructor hell]. -Це можна обійти за допомогою альтернативних методів, до яких відносяться методи ін'єкції та атрибути (анотації). +Навіть для презентерів передача залежностей за допомогою [конструктора |dependency-injection:passing-dependencies#Передача конструктором] є бажаним шляхом. Однак, якщо ви створюєте спільного предка, від якого успадковуються інші презентери (наприклад, `BasePresenter`), і цей предок також має залежності, виникає проблема, яку ми називаємо [constructor hell |dependency-injection:passing-dependencies#Пекло конструкторів]. Її можна обійти за допомогою альтернативних шляхів, якими є методи та атрибути (анотації) `inject`. -`inject*()` Методи .[#toc-inject-methods] -========================================= +Методи `inject*()` +================== -Це форма передачі залежності за допомогою [сетерів |dependency-injection:passing-dependencies#Setter Injection]. Назви цих сеттерів починаються з префікса inject. -Nette DI автоматично викликає такі іменовані методи одразу після створення екземпляра презентатора і передає їм усі необхідні залежності. Тому вони повинні бути оголошені як загальнодоступні. +Це форма передачі залежності [сеттером |dependency-injection:passing-dependencies#Передача сеттером]. Назва цих сеттерів починається з префікса `inject`. Nette DI автоматично викликає методи з такою назвою одразу після створення екземпляра презентера та передає їм усі необхідні залежності. Тому вони повинні бути оголошені як public. -`inject*()` методи можна розглядати як своєрідне розширення конструктора на декілька методів. Завдяки цьому `BasePresenter` може отримувати залежності через інший метод і залишати конструктор вільним для своїх нащадків: +Методи `inject*()` можна вважати своєрідним розширенням конструктора на кілька методів. Завдяки цьому `BasePresenter` може приймати залежності через інший метод і залишати конструктор вільним для своїх нащадків: ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -39,18 +36,18 @@ class MyPresenter extends BasePresenter } ``` -Ведучий може містити будь-яку кількість методів `inject*()`, і кожен з них може мати будь-яку кількість параметрів. Це також чудово підходить для випадків, коли презентер [складається з ознак |presenter-traits], і для кожної з них потрібна своя залежність. +Презентер може містити будь-яку кількість методів `inject*()`, і кожен може мати будь-яку кількість параметрів. Це також чудово підходить у випадках, коли презентер [складається з трейтів |presenter-traits], і кожен з них вимагає власної залежності. -`Inject` Атрибути .[#toc-inject-attributes] -=========================================== +Атрибути `Inject` +================= -Це форма ін' [єкції у властивості |dependency-injection:passing-dependencies#Property Injection]. Достатньо вказати, які властивості потрібно вставити, і Nette DI автоматично передасть залежності одразу після створення екземпляра доповідача. Щоб вставити їх, необхідно оголосити їх загальнодоступними. +Це форма [ін'єкції у властивість |dependency-injection:passing-dependencies#Встановленням змінної]. Достатньо позначити, в які змінні слід ін'єктувати, і Nette DI автоматично передасть залежності одразу після створення екземпляра презентера. Щоб їх можна було вставити, необхідно оголосити їх як public. -Властивості позначаються атрибутом: (раніше використовувалася анотація `/** @inject */`) +Властивості позначимо атрибутом: (раніше використовувалася анотація `/** @inject */`) ```php -use Nette\DI\Attributes\Inject; // цей рядок важливий +use Nette\DI\Attributes\Inject; // цей рядок важливий class MyPresenter extends Nette\Application\UI\Presenter { @@ -59,9 +56,6 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Перевагою такого способу передачі залежностей була дуже економна форма запису. Однак, із запровадженням популяризації [властивостей конструк |https://blog.nette.org/uk/php-8-0-povnij-oglyad-novin#toc-constructor-property-promotion]тора, використання конструктора видається простішим. +Перевагою цього способу передачі залежностей була дуже лаконічна форма запису. Однак з появою [constructor property promotion |https://blog.nette.org/uk/php-8-0-complete-overview-of-news#toc-constructor-property-promotion] простіше використовувати конструктор. -З іншого боку, цей спосіб страждає тими ж недоліками, що і передача залежностей у властивості взагалі: ми не маємо контролю над змінами змінної, і в той же час змінна стає частиною публічного інтерфейсу класу, що є небажаним. - - -{{sitename: Найкращі практики}} +Навпаки, цей спосіб страждає тими ж недоліками, що й передача залежності у властивості загалом: ми не маємо контролю над змінами в змінній, і водночас змінна стає частиною публічного інтерфейсу класу, що є небажаним. diff --git a/best-practices/uk/lets-create-contact-form.texy b/best-practices/uk/lets-create-contact-form.texy index 724f0c58d7..dda0437ab6 100644 --- a/best-practices/uk/lets-create-contact-form.texy +++ b/best-practices/uk/lets-create-contact-form.texy @@ -2,11 +2,11 @@ ************************* .[perex] -Давайте розглянемо, як створити контактну форму в Nette, в тому числі відправити її на електронну пошту. Тож давайте зробимо це! +Розглянемо, як у Nette створити контактну форму, включно з надсиланням на електронну пошту. Отже, до справи! -Спочатку нам потрібно створити новий проект. Як пояснюється на сторінці [Початок |nette:installation] роботи. А потім ми можемо почати створювати форму. +Спочатку потрібно створити новий проект. Як це зробити, пояснюється на сторінці [Починаємо |nette:installation]. А потім вже можемо почати створювати форму. -Найпростіший спосіб - створити [форму безпосередньо у Presenter |forms:in-presenter]. Ми можемо використати готову форму `HomePresenter`. Ми додамо компонент `contactForm`, який представлятиме форму. Ми зробимо це, додавши фабричний метод `createComponentContactForm()` до коду, який буде створювати компонент: +Найпростіше створити [форму безпосередньо в презентері |forms:in-presenter]. Можемо використати заготовлений `HomePresenter`. До нього додамо компонент `contactForm`, що представляє форму. Зробимо це так: запишемо в код фабричний метод `createComponentContactForm()`, який створить компонент: ```php use Nette\Application\UI\Form; @@ -17,37 +17,35 @@ class HomePresenter extends Presenter protected function createComponentContactForm(): Form { $form = new Form; - $form->addText('name', 'Name:') - ->setRequired('Enter your name'); + $form->addText('name', "Ім'я:") + ->setRequired("Введіть ім'я"); $form->addEmail('email', 'E-mail:') - ->setRequired('Enter your e-mail'); - $form->addTextarea('message', 'Message:') - ->setRequired('Enter message'); - $form->addSubmit('send', 'Send'); + ->setRequired('Введіть e-mail'); + $form->addTextarea('message', 'Повідомлення:') + ->setRequired('Введіть повідомлення'); + $form->addSubmit('send', 'Надіслати'); $form->onSuccess[] = [$this, 'contactFormSucceeded']; return $form; } public function contactFormSucceeded(Form $form, $data): void { - // sending an email + // надсилання email } } ``` -Як бачите, ми створили два методи. Перший метод `createComponentContactForm()` створює нову форму. Вона має поля для імені, електронної пошти та повідомлення, які ми додаємо за допомогою методів `addText()`, `addEmail()` та `addTextArea()`. Ми також додали кнопку для відправки форми. -Але що, якщо користувач не заповнить деякі поля? У такому випадку ми повинні повідомити йому, що це поле є обов'язковим для заповнення. Ми зробили це за допомогою методу `setRequired()`. -Нарешті, ми також додали [подію |nette:glossary#events] `onSuccess`, яка спрацьовує в разі успішного відправлення форми. У нашому випадку вона викликає метод `contactFormSucceeded`, який відповідає за обробку надісланої форми. Ми додамо його до коду за мить. +Як бачите, ми створили два методи. Перший метод `createComponentContactForm()` створює нову форму. Вона має поля для імені, email та повідомлення, які ми додаємо методами `addText()`, `addEmail()` та `addTextArea()`. Також ми додали кнопку для надсилання форми. Але що, якщо користувач не заповнить якесь поле? У такому випадку ми повинні повідомити йому, що це обов'язкове поле. Цього ми досягли за допомогою методу `setRequired()`. Нарешті, ми також додали [подію |nette:glossary#Події události] `onSuccess`, яка спрацює, якщо форма успішно надіслана. У нашому випадку вона викличе метод `contactFormSucceeded`, який подбає про обробку надісланої форми. Це ми доповнимо в код за мить. -Нехай компонент `contantForm` рендериться в шаблоні `templates/Home/default.latte`: +Компонент `contactForm` виведемо в шаблоні `Home/default.latte`: ```latte {block content} -<h1>Contant Form</h1> +<h1>Контактна форма</h1> {control contactForm} ``` -Для відправки самого листа ми створюємо новий клас з ім'ям `ContactFacade` і розміщуємо його у файлі `app/Model/ContactFacade.php`: +Для самого надсилання email створимо новий клас, який назвемо `ContactFacade` і розмістимо його у файлі `app/Model/ContactFacade.php`: ```php <?php @@ -68,9 +66,9 @@ class ContactFacade public function sendMessage(string $email, string $name, string $message): void { $mail = new Message; - $mail->addTo('admin@example.com') // your email + $mail->addTo('admin@example.com') // ваш email ->setFrom($email, $name) - ->setSubject('Message from the contact form') + ->setSubject('Повідомлення з контактної форми') ->setBody($message); $this->mailer->send($mail); @@ -78,9 +76,9 @@ class ContactFacade } ``` -Метод `sendMessage()` буде створювати і відправляти лист. Для цього він використовує так званий мейлер, який передається як залежність через конструктор. Дізнайтеся більше про надсилання [електронних |mail:] листів. +Метод `sendMessage()` створює та надсилає email. Для цього він використовує так званий mailer, який отримує як залежність через конструктор. Дізнайтеся більше про [надсилання електронних листів |mail:]. -Тепер повернемося до доповідача і завершимо метод `contactFormSucceeded()`. Він викликає метод `sendMessage()` класу `ContactFacade` і передає йому дані форми. А як ми отримаємо об'єкт `ContactFacade`? Він буде переданий нам конструктором: +Тепер повернемося до презентера і завершимо метод `contactFormSucceeded()`. Він викличе метод `sendMessage()` класу `ContactFacade` і передасть йому дані з форми. А як отримати об'єкт `ContactFacade`? Отримаємо його через конструктор: ```php use App\Model\ContactFacade; @@ -102,36 +100,36 @@ class HomePresenter extends Presenter public function contactFormSucceeded(stdClass $data): void { $this->facade->sendMessage($data->email, $data->name, $data->message); - $this->flashMessage('The message has been sent'); + $this->flashMessage('Повідомлення було надіслано'); $this->redirect('this'); } } ``` -Після відправлення листа ми показуємо користувачеві так зване [флеш-повідомлення |application:components#flash-messages], підтверджуючи, що лист відправлено, а потім перенаправляємо на наступну сторінку, щоб форму не можна було повторно відправити за допомогою *refresh* в браузері. +Після надсилання email ми ще покажемо користувачеві так зване [flash-повідомлення |application:components#Flash-повідомлення], що підтверджує надсилання повідомлення, а потім перенаправимо на наступну сторінку, щоб не можна було повторно надіслати форму за допомогою *refresh* у браузері. -Що ж, якщо все працює, ви зможете відправити електронного листа зі своєї контактної форми. Вітаємо вас! +Отже, якщо все працює, ви повинні мати можливість надіслати email з вашої контактної форми. Вітаю! -HTML шаблон електронного листа .[#toc-html-email-template] ----------------------------------------------------------- +HTML-шаблон електронного листа +------------------------------ -Наразі надсилається звичайний текстовий лист, що містить лише повідомлення, надіслане за допомогою форми. Але ми можемо використовувати HTML в листі і зробити його більш привабливим. Ми створимо для цього шаблон в Latte, який збережемо в `app/Model/contactEmail.latte`: +Поки що надсилається простий текстовий email, що містить лише повідомлення, надіслане формою. Але в email ми можемо використовувати HTML і зробити його вигляд привабливішим. Створимо для нього шаблон у Latte, який запишемо до `app/Model/contactEmail.latte`: ```latte <html> - <title>Message from the contact form + Повідомлення з контактної форми -

    Name: {$name}

    +

    Ім'я: {$name}

    E-mail: {$email}

    -

    Message: {$message}

    +

    Повідомлення: {$message}

    ``` -Залишилося модифікувати `ContactFacade`, щоб використовувати цей шаблон. У конструкторі ми запитуємо клас `LatteFactory`, який може створити об'єкт `Latte\Engine`, [рендеринг шаблону Latte |latte:develop#how-to-render-a-template]. Ми використовуємо метод `renderToString()` для рендерингу шаблону у файл, першим параметром якого є шлях до шаблону, а другим - змінні. +Залишилося змінити `ContactFacade`, щоб він використовував цей шаблон. У конструкторі ми запросимо клас `LatteFactory`, який вміє створювати об'єкт `Latte\Engine`, тобто [рендер шаблонів Latte |latte:develop#Як відобразити шаблон]. За допомогою методу `renderToString()` ми відрендеримо шаблон у файл, першим параметром є шлях до шаблону, а другим – змінні. ```php namespace App\Model; @@ -158,7 +156,7 @@ class ContactFacade ]); $mail = new Message; - $mail->addTo('admin@example.com') // your email + $mail->addTo('admin@example.com') // ваш email ->setFrom($email, $name) ->setHtmlBody($body); @@ -167,15 +165,15 @@ class ContactFacade } ``` -Потім ми передаємо згенерований HTML-лист методу `setHtmlBody()` замість оригінального `setBody()`. Нам також не потрібно вказувати тему листа в `setSubject()`, оскільки бібліотека бере її з елемента `` в шаблоні. +Згенерований HTML email потім передамо методу `setHtmlBody()` замість початкового `setBody()`. Також нам не потрібно вказувати тему email у `setSubject()`, оскільки бібліотека візьме її з елемента `<title>` шаблону. -Налаштування .[#toc-configuring] --------------------------------- +Конфігурація +------------ -У коді класу `ContactFacade` наш email адміністратора `admin@example.com` все ще жорстко закодований. Було б краще перенести її в конфігураційний файл. Як це зробити? +У коді класу `ContactFacade` все ще жорстко прописаний наш адміністраторський email `admin@example.com`. Було б краще перенести його до конфігураційного файлу. Як це зробити? -Спочатку модифікуємо клас `ContactFacade` і замінюємо рядок email на змінну, що передається конструктором: +Спочатку змінимо клас `ContactFacade` і рядок з email замінимо змінною, переданою конструктором: ```php class ContactFacade @@ -199,28 +197,25 @@ class ContactFacade } ``` -А другим кроком ми вносимо значення цієї змінної в конфігурацію. У файлі `app/config/services.neon` додаємо: +А другим кроком є вказівка значення цієї змінної в конфігурації. До файлу `app/config/services.neon` запишемо: ```neon services: - App\Model\ContactFacade(adminEmail: admin@example.com) ``` -І все. Якщо в розділі `services` багато елементів, і ви відчуваєте, що лист губиться серед них, ми можемо зробити його змінною. Змінимо запис на: +І все. Якщо елементів у секції `services` буде багато і ви відчуватимете, що email серед них губиться, ми можемо зробити з нього змінну. Змінимо запис на: ```neon services: - App\Model\ContactFacade(adminEmail: %adminEmail%) ``` -І визначимо цю змінну у файлі `app/config/common.neon`: +А у файлі `app/config/common.neon` визначимо цю змінну: ```neon parameters: adminEmail: admin@example.com ``` -І все готово! - - -{{sitename: Найкращі практики}} +І готово! diff --git a/best-practices/uk/microsites.texy b/best-practices/uk/microsites.texy new file mode 100644 index 0000000000..19744c4b86 --- /dev/null +++ b/best-practices/uk/microsites.texy @@ -0,0 +1,63 @@ +Як створювати мікросайти +************************ + +Уявіть, що вам потрібно швидко створити невеликий веб-сайт для майбутньої події вашої компанії. Це має бути просто, швидко і без зайвих ускладнень. Можливо, ви думаєте, що для такого маленького проекту вам не потрібен потужний фреймворк. Але що, якщо використання фреймворку Nette може суттєво спростити та прискорити цей процес? + +Адже навіть при створенні простих веб-сайтів ви не хочете відмовлятися від зручності. Ви не хочете вигадувати те, що вже було одного разу вирішено. Будьте спокійно лінивими і дозвольте себе побалувати. Nette Framework можна чудово використовувати і як мікрофреймворк. + +Як може виглядати такий мікросайт? Наприклад, так, що весь код сайту ми розмістимо в єдиному файлі `index.php` у публічній папці: + +```php +<?php + +require __DIR__ . '/../vendor/autoload.php'; + +$configurator = new Nette\Bootstrap\Configurator; +$configurator->enableTracy(__DIR__ . '/../log'); +$configurator->setTempDirectory(__DIR__ . '/../temp'); + +// створи DI-контейнер на основі конфігурації в config.neon +$configurator->addConfig(__DIR__ . '/../app/config.neon'); +$container = $configurator->createContainer(); + +// налаштуємо маршрутизацію +$router = new Nette\Application\Routers\RouteList; +$container->addService('router', $router); + +// маршрут для URL https://example.com/ +$router->addRoute('', function ($presenter, Nette\Http\Request $httpRequest) { + // визначаємо мову браузера та перенаправляємо на URL /en або /de тощо. + $supportedLangs = ['en', 'de', 'cs']; + $lang = $httpRequest->detectLanguage($supportedLangs) ?: reset($supportedLangs); + $presenter->redirectUrl("/$lang"); +}); + +// маршрут для URL https://example.com/cs або https://example.com/en +$router->addRoute('<lang cs|en>', function ($presenter, string $lang) { + // відобразимо відповідний шаблон, наприклад ../templates/en.latte + $template = $presenter->createTemplate() + ->setFile(__DIR__ . '/../templates/' . $lang . '.latte'); + return $template; +}); + +// запустіть додаток! +$container->getByType(Nette\Application\Application::class)->run(); +``` + +Все інше будуть шаблони, збережені в батьківській папці `/templates`. + +PHP-код в `index.php` спочатку [підготує середовище |bootstrap:], потім визначає [маршрути |application:routing#Динамічна маршрутизація з callback-функціями] і нарешті запускає додаток. Перевагою є те, що другий параметр функції `addRoute()` може бути callable, який виконається після відкриття відповідної сторінки. + + +Чому варто використовувати Nette для мікросайтів? +------------------------------------------------- + +- Програмісти, які колись спробували [Tracy|tracy:], сьогодні не уявляють, як програмувати без неї. +- Перш за все, ви скористаєтеся системою шаблонів [Latte|latte:], оскільки вже з 2 сторінок вам захочеться мати розділений [макет та вміст|latte:template-inheritance]. +- І ви точно хочете покладатися на [автоматичне екранування |latte:safety-first], щоб не виникла вразливість XSS. +- Nette також гарантує, що при помилці ніколи не відобразяться повідомлення про помилки PHP для програмістів, а зрозуміла для користувача сторінка. +- Якщо ви хочете отримувати зворотній зв'язок від користувачів, наприклад, у вигляді контактної форми, то ще додасте [форми|forms:] та [базу даних|database:]. +- Заповнені форми ви також можете легко [надсилати електронною поштою|mail:]. +- Іноді вам може знадобитися [кешування|caching:], наприклад, якщо ви завантажуєте та відображаєте стрічки новин. + +У наш час, коли швидкість та ефективність є ключовими, важливо мати інструменти, які дозволять вам досягти результатів без зайвих затримок. Фреймворк Nette пропонує саме це - швидку розробку, безпеку та широкий спектр інструментів, таких як Tracy та Latte, які спрощують процес. Достатньо встановити кілька пакетів Nette, і створення такого мікросайту раптом стає зовсім простою справою. І ви знаєте, що ніде не ховається жодна дірка в безпеці. diff --git a/best-practices/uk/pagination.texy b/best-practices/uk/pagination.texy index b0883298b5..e16f7e4ad4 100644 --- a/best-practices/uk/pagination.texy +++ b/best-practices/uk/pagination.texy @@ -1,17 +1,16 @@ -Пагінація результатів запиту до бази даних -****************************************** +Пагінація результатів бази даних +******************************** .[perex] -Під час розробки веб-додатків ви часто стикаєтеся з вимогою виводити на сторінці обмежену кількість записів. +При створенні веб-додатків дуже часто виникає вимога обмежити кількість виведених елементів на сторінці. -Ми виходимо зі стану, коли перераховуємо всі дані без пагінації. Для вибору даних із бази даних у нас є клас ArticleRepository, який містить конструктор і метод `findPublishedArticles`, що повертає всі опубліковані статті, відсортовані за зменшенням дати публікації. +Почнемо зі стану, коли ми виводимо всі дані без пагінації. Для вибору даних з бази даних у нас є клас ArticleRepository, який, крім конструктора, містить метод `findPublishedArticles`, що повертає всі опубліковані статті, відсортовані за спаданням дати публікації. ```php namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -31,10 +30,10 @@ class ArticleRepository } ``` -Потім у презентері ми вводимо клас моделі і в методі `render` запитуємо опубліковані статті, які передаємо в шаблон: +У презентері ми потім ін'єктуємо клас моделі, а в методі render запитуємо опубліковані статті, які передаємо до шаблону: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -53,11 +52,11 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -У шаблоні ми подбаємо про виведення списку статей: +У шаблоні `default.latte` ми потім подбаємо про виведення статей: ```latte {block content} -<h1>Статьи</h1> +<h1>Статті</h1> <div class="articles"> {foreach $articles as $article} @@ -68,11 +67,11 @@ class HomePresenter extends Nette\Application\UI\Presenter ``` -Таким чином, ми можемо написати всі статті, але це викличе проблеми, коли кількість статей зросте. У цей момент буде корисно реалізувати механізм пагінації. +Таким чином ми можемо вивести всі статті, що, однак, почне створювати проблеми, коли кількість статей зросте. У цей момент стане в нагоді реалізація механізму пагінації. -Це забезпечить розбиття всіх статей на кілька сторінок, і ми показуватимемо тільки статті однієї поточної сторінки. Загальна кількість сторінок і розподіл статей розраховується самим [Paginator |utils:Paginator], залежно від того, скільки статей у нас всього і скільки статей ми хочемо відобразити на сторінці. +Він забезпечить, що всі статті будуть розділені на кілька сторінок, і ми відобразимо лише статті однієї поточної сторінки. Загальну кількість сторінок та розподіл статей обчислить [utils:Paginator] сам, залежно від того, скільки статей у нас загалом і скільки статей на сторінку ми хочемо відобразити. -На першому етапі ми змінимо метод отримання статей у класі репозиторію, щоб він повертав тільки односторінкові статті. Ми також додамо новий метод для отримання загальної кількості статей у базі даних, який нам знадобиться для встановлення Paginator: +На першому кроці ми змінимо метод для отримання статей у класі репозиторію так, щоб він міг повертати лише статті для однієї сторінки. Також додамо метод для визначення загальної кількості статей у базі даних, який нам знадобиться для налаштування Paginator: ```php namespace App\Model; @@ -100,7 +99,7 @@ class ArticleRepository } /** - * Returns the total number of published articles + * Повертає загальну кількість опублікованих статей */ public function getPublishedArticlesCount(): int { @@ -109,12 +108,12 @@ class ArticleRepository } ``` -Наступним кроком буде редагування презентера. Ми передамо номер поточної відображуваної сторінки в метод `render`. У разі, якщо цей номер не є частиною URL, нам потрібно встановити значення за замовчуванням для першої сторінки. +Потім перейдемо до змін у презентері. У метод render ми будемо передавати номер поточної відображуваної сторінки. У випадку, якщо цей номер не буде частиною URL, встановимо значення за замовчуванням першої сторінки. -Ми також розширюємо метод `render` для отримання екземпляра Paginator, його налаштування та вибору потрібних статей для відображення в шаблоні. HomePresenter матиме такий вигляд: +Далі також розширимо метод render отриманням екземпляра Paginator, його налаштуванням та вибором правильних статей для відображення в шаблоні. HomePresenter після змін виглядатиме так: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -128,19 +127,19 @@ class HomePresenter extends Nette\Application\UI\Presenter public function renderDefault(int $page = 1): void { - // Знайдемо загальну кількість опублікованих статей + // З'ясуємо загальну кількість опублікованих статей $articlesCount = $this->articleRepository->getPublishedArticlesCount(); - // Ми створимо екземпляр Paginator і налаштуємо його + // Створимо екземпляр Paginator і налаштуємо його $paginator = new Nette\Utils\Paginator; - $paginator->setItemCount($articlesCount); // total articles count - $paginator->setItemsPerPage(10); // items per page - $paginator->setPage($page); // фактичний номер сторінки + $paginator->setItemCount($articlesCount); // загальна кількість статей + $paginator->setItemsPerPage(10); // кількість елементів на сторінці + $paginator->setPage($page); // номер поточної сторінки - // Ми знайдемо обмежений набір статей із бази даних на основі розрахунків Paginator + // З бази даних витягнемо обмежену множину статей згідно з розрахунком Paginator $articles = $this->articleRepository->findPublishedArticles($paginator->getLength(), $paginator->getOffset()); - // який ми передаємо в шаблон + // яку передамо до шаблону $this->template->articles = $articles; // а також сам Paginator для відображення опцій пагінації $this->template->paginator = $paginator; @@ -148,11 +147,11 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -Шаблон уже ітерує статті на одній сторінці, просто додайте посилання пагінації: +Шаблон тепер уже ітерує лише над статтями однієї сторінки, нам залишається додати посилання для пагінації: ```latte {block content} -<h1>Articles</h1> +<h1>Статті</h1> <div class="articles"> {foreach $articles as $article} @@ -163,34 +162,33 @@ class HomePresenter extends Nette\Application\UI\Presenter <div class="pagination"> {if !$paginator->isFirst()} - <a n:href="default, 1">Первая</a> + <a n:href="default, 1">Перша</a>  |  - <a n:href="default, $paginator->page-1">Предыдущая</a> + <a n:href="default, $paginator->page-1">Попередня</a>  |  {/if} - Страница {$paginator->getPage()} из {$paginator->getPageCount()} + Сторінка {$paginator->getPage()} з {$paginator->getPageCount()} {if !$paginator->isLast()}  |  - <a n:href="default, $paginator->getPage() + 1">Следующая</a> + <a n:href="default, $paginator->getPage() + 1">Наступна</a>  |  - <a n:href="default, $paginator->getPageCount()">Последняя</a> + <a n:href="default, $paginator->getPageCount()">Остання</a> {/if} </div> ``` -Ось як ми додали пагінацію за допомогою Paginator. Якщо замість [Nette Database Explorer |database:explorer] як шар бази даних використовується [Nette Database Core |database:core], ми можемо реалізувати підкачку навіть без Paginator. Клас `Nette\Database\Table\Selection` містить метод [page |api:Nette\Database\Table\Selection::_ page] з логікою пагінації, взятою з Paginator. +Таким чином ми доповнили сторінку можливістю пагінації за допомогою Paginator. У випадку, коли замість [Nette Database Core |database:sql-way] як шар бази даних використовується [Nette Database Explorer |database:explorer], ми можемо реалізувати пагінацію і без використання Paginator. Клас `Nette\Database\Table\Selection` містить метод [page |api:Nette\Database\Table\Selection::_page] з логікою пагінації, взятою з Paginator. -Репозиторій матиме такий вигляд: +Репозиторій при такому способі реалізації виглядатиме так: ```php namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -198,7 +196,6 @@ class ArticleRepository ) { } - public function findPublishedArticles(): Nette\Database\Table\Selection { return $this->database->table('articles') @@ -208,10 +205,10 @@ class ArticleRepository } ``` -Нам не потрібно створювати Paginator у презентері, натомість ми використовуватимемо метод об'єкта `Selection`, який повертає сховище: +У презентері нам не потрібно створювати Paginator, замість нього ми використаємо метод класу `Selection`, який повертає репозиторій: ```php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; use App\Model\ArticleRepository; @@ -225,10 +222,10 @@ class HomePresenter extends Nette\Application\UI\Presenter public function renderDefault(int $page = 1): void { - // Знайдемо опубліковані статті + // Витягнемо опубліковані статті $articles = $this->articleRepository->findPublishedArticles(); - // і їхню частину, обмежену обчисленням методу page, яку ми передамо в шаблон + // а до шаблону надішлемо лише їх частину, обмежену згідно з розрахунком методу page $lastPage = 0; $this->template->articles = $articles->page($page, 10, $lastPage); @@ -239,11 +236,11 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -Оскільки ми не використовуємо Paginator, нам потрібно відредагувати розділ, що показує посилання пагінації: +Оскільки до шаблону ми тепер не надсилаємо Paginator, змінимо частину, що відображає посилання пагінації: ```latte {block content} -<h1>Статьи</h1> +<h1>Статті</h1> <div class="articles"> {foreach $articles as $article} @@ -254,24 +251,23 @@ class HomePresenter extends Nette\Application\UI\Presenter <div class="pagination"> {if $page > 1} - <a n:href="default, 1">Первая</a> + <a n:href="default, 1">Перша</a>  |  - <a n:href="default, $page - 1">Предыдущая</a> + <a n:href="default, $page - 1">Попередня</a>  |  {/if} - Страница {$page} из {$lastPage} + Сторінка {$page} з {$lastPage} {if $page < $lastPage}  |  - <a n:href="default, $page + 1">Следующая</a> + <a n:href="default, $page + 1">Наступна</a>  |  - <a n:href="default, $lastPage">Последняя</a> + <a n:href="default, $lastPage">Остання</a> {/if} </div> ``` -Таким чином, ми реалізували механізм пагінації без використання пагінатора. +Таким чином ми реалізували механізм пагінації без використання Paginator. {{priority: -1}} -{{sitename: Найкращі практики}} diff --git a/best-practices/uk/passing-settings-to-presenters.texy b/best-practices/uk/passing-settings-to-presenters.texy index 4f644be145..eab473dbda 100644 --- a/best-practices/uk/passing-settings-to-presenters.texy +++ b/best-practices/uk/passing-settings-to-presenters.texy @@ -1,10 +1,10 @@ -Передача параметрів презентаторам +Передача налаштувань у презентери ********************************* .[perex] -Вам потрібно передавати презентерам аргументи, які не є об'єктами (наприклад, інформацію про те, чи працює він у режимі налагодження, шляхи до каталогів тощо) і тому не можуть бути передані автоматично за допомогою автопідключення? Рішення - інкапсулювати їх в об'єкт `Settings`. +Вам потрібно передавати в презентери аргументи, які не є об'єктами (наприклад, інформацію про те, чи працює додаток у режимі налагодження, шляхи до каталогів тощо), і тому їх не можна передати автоматично за допомогою autowiring? Рішенням є інкапсуляція їх в об'єкт `Settings`. -Служба `Settings` - це дуже простий і корисний спосіб надання інформації про запущений додаток доповідачам. Його конкретна форма повністю залежить від ваших конкретних потреб. Приклад: +Сервіс `Settings` представляє дуже простий, але корисний спосіб надання інформації про запущений додаток презентерам. Його конкретна форма залежить виключно від ваших конкретних потреб. Приклад: ```php namespace App; @@ -12,7 +12,7 @@ namespace App; class Settings { public function __construct( - // починаючи з PHP 8.1 можна вказати readonly + // від PHP 8.1 можна вказати readonly public bool $debugMode, public string $appDir, // і так далі @@ -30,7 +30,7 @@ services: ) ``` -Коли ведучому потрібна інформація, яку надає ця служба, він просто запитує її в конструкторі: +Коли презентеру знадобиться інформація, що надається цим сервісом, він просто запросить її в конструкторі: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -47,5 +47,3 @@ class MyPresenter extends Nette\Application\UI\Presenter } } ``` - -{{sitename: Найкращі практики}} diff --git a/best-practices/uk/post-links.texy b/best-practices/uk/post-links.texy new file mode 100644 index 0000000000..d0c67a0506 --- /dev/null +++ b/best-practices/uk/post-links.texy @@ -0,0 +1,56 @@ +Як правильно використовувати POST-посилання +******************************************* + +.[perex] +У веб-додатках, особливо в адміністративних інтерфейсах, основним правилом має бути те, що дії, які змінюють стан сервера, не повинні виконуватися за допомогою HTTP-методу GET. Як випливає з назви методу, GET повинен використовуватися лише для отримання даних, а не для їх зміни. Для дій, таких як видалення записів, краще використовувати метод POST. Хоча ідеальним був би метод DELETE, але його не можна викликати без JavaScript, тому історично використовується POST. + +Як це зробити на практиці? Використовуйте цей простий трюк. На початку шаблону створіть допоміжну форму з ідентифікатором `postForm`, яку потім використовуйте для кнопок видалення: + +```latte .{file:@layout.latte} +<form method="post" id="postForm"></form> +``` + +Завдяки цій формі ви можете замість класичного посилання `<a>` використовувати кнопку `<button>`, яку можна візуально стилізувати так, щоб вона виглядала як звичайне посилання. Наприклад, CSS-фреймворк Bootstrap пропонує класи `btn btn-link`, за допомогою яких ви досягнете того, що кнопка не буде візуально відрізнятися від інших посилань. За допомогою атрибута `form="postForm"` ми пов'яжемо її з підготовленою формою: + +```latte .{file:admin.latte} +<table> + <tr n:foreach="$posts as $post"> + <td>{$post->title}</td> + <td> + <button class="btn btn-link" form="postForm" formaction="{link delete $post->id}">видалити</button> + <!-- замість <a n:href="delete $post->id">видалити</a> --> + </td> + </tr> +</table> +``` + +При натисканні на посилання тепер викликається дія `delete`. Щоб гарантувати, що запити будуть прийматися лише за допомогою методу POST і з того ж домену (що є ефективним захистом від CSRF-атак), використовуйте атрибут `#[Requires]`: + +```php .{file:AdminPresenter.php} +use Nette\Application\Attributes\Requires; + +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST', sameOrigin: true)] + public function actionDelete(int $id): void + { + $this->facade->deletePost($id); // гіпотетичний код, що видаляє запис + $this->redirect('default'); + } +} +``` + +Атрибут існує з Nette Application 3.2, і більше про його можливості ви дізнаєтеся на сторінці [Як використовувати атрибут #Requires |attribute-requires]. + +Якби ви замість дії `actionDelete()` використовували сигнал `handleDelete()`, не потрібно вказувати `sameOrigin: true`, оскільки сигнали мають цей захист встановлений неявно: + +```php .{file:AdminPresenter.php} +#[Requires(methods: 'POST')] +public function handleDelete(int $id): void +{ + $this->facade->deletePost($id); + $this->redirect('this'); +} +``` + +Цей підхід не тільки покращує безпеку вашого додатку, але й сприяє дотриманню правильних веб-стандартів та практик. Використовуючи методи POST для дій, що змінюють стан, ви досягнете більш надійного та безпечного додатку. diff --git a/best-practices/uk/presenter-traits.texy b/best-practices/uk/presenter-traits.texy index 9530556a56..3b4ce8a1cb 100644 --- a/best-practices/uk/presenter-traits.texy +++ b/best-practices/uk/presenter-traits.texy @@ -1,14 +1,14 @@ -Складання презентерів з ознак -***************************** +Компонування презентерів із трейтів +*********************************** .[perex] -Якщо нам потрібно реалізувати один і той самий код у кількох презентерах (наприклад, перевірка того, що користувач увійшов у систему), заманливо помістити цей код у спільного предка. Другий варіант - створити одноцільові трейти. +Якщо нам потрібно реалізувати однаковий код у кількох презентерах (наприклад, перевірка, чи користувач увійшов у систему), пропонується розмістити код у спільному предку. Другим варіантом є створення одноцільових [трейтів |nette:introduction-to-object-oriented-programming#Трейди]. -Перевага цього рішення полягає в тому, що кожен ведучий може використовувати тільки ті ознаки, які йому справді потрібні, тоді як множинне успадкування в PHP неможливе. +Перевага цього рішення полягає в тому, що кожен з презентерів може використовувати саме ті трейти, які йому дійсно потрібні, тоді як множинне успадкування в PHP неможливе. -Ці риси можуть використовувати той факт, що всі [методи inject |inject-method-attribute#inject-Methods] викликаються послідовно при створенні ведучого. Вам просто потрібно переконатися, що ім'я кожного методу inject унікальне. +Ці трейти можуть використовувати той факт, що при створенні презентера послідовно викликаються всі [inject-методи |inject-method-attribute#Методи inject]. Потрібно лише переконатися, що назва кожного inject-методу є унікальною. -Трейти можуть повісити код ініціалізації в події [onStartup або onRender |application:presenters#Events]. +Трейт може навішувати ініціалізаційний код на події [onStartup або onRender |application:presenters#Події]. Приклади: @@ -36,7 +36,7 @@ trait StandardTemplateFilters } ``` -Потім ведучий просто використовує ці риси: +Презентер потім просто використовує ці трейти: ```php class ArticlePresenter extends Nette\Application\UI\Presenter @@ -45,6 +45,3 @@ class ArticlePresenter extends Nette\Application\UI\Presenter use RequireLoggedUser; } ``` - - -{{sitename: Найкращі практики}} diff --git a/best-practices/uk/restore-request.texy b/best-practices/uk/restore-request.texy index 611d41a472..63446e2b95 100644 --- a/best-practices/uk/restore-request.texy +++ b/best-practices/uk/restore-request.texy @@ -2,16 +2,15 @@ ************************************* .[perex] -Що робити, якщо користувач заповнив форму, а термін дії його логіна закінчився? Щоб уникнути втрати даних, ми зберігаємо їх у сесії перед перенаправленням на сторінку входу в систему. У Nette це простіше простого. +Що робити, якщо користувач заповнює форму, а його сесія закінчується? Щоб дані не були втрачені, перед перенаправленням на сторінку входу ми збережемо дані в сесії. У Nette це зовсім просто. -Поточний запит може бути збережений у сесії за допомогою методу `storeRequest()`, який повертає його ідентифікатор у вигляді короткого рядка. Метод зберігає ім'я поточного презентера, подання та його параметри. -Якщо форма також була відправлена, значення полів (за винятком завантажених файлів) також зберігаються. +Поточний запит можна зберегти в сесії за допомогою методу `storeRequest()`, який поверне його ідентифікатор у вигляді короткого рядка. Метод зберігає назву поточного презентера, view та його параметри. У випадку, якщо була надіслана форма, також зберігається вміст полів (за винятком завантажених файлів). -Запит відновлюється методом `restoreRequest($key)`, якому ми передаємо витягнутий ідентифікатор. Це перенаправляє до вихідного презентера та подання. Однак, якщо збережений запит містить форму відправки, він буде перенаправлений до початкового презентера, використовуючи метод `forward()`, передайте раніше заповнені значення у форму і дозвольте їй перемалюватися. Це дозволяє користувачеві повторно надіслати форму, і дані не будуть втрачені. +Відновлення запиту виконує метод `restoreRequest($key)`, якому ми передаємо отриманий ідентифікатор. Він перенаправляє на початковий презентер та view. Однак, якщо збережений запит містить надсилання форми, на початковий презентер він перейде методом `forward()`, передасть формі раніше заповнені значення і дозволить її знову відрендерити. Таким чином, користувач має можливість повторно надіслати форму, і жодні дані не втрачаються. -Важливо зазначити, що `restoreRequest()` перевіряє, що користувач, який знову увійшов, є тим самим, який спочатку заповнив форму. Якщо ні, він відкидає запит і нічого не робить. +Важливо, що `restoreRequest()` перевіряє, чи новозареєстрований користувач є тим самим, хто спочатку заповнював форму. Якщо ні, запит відкидається, і нічого не відбувається. -Давайте продемонструємо все на прикладі. Нехай у нас є презентер `AdminPresenter`, в якому редагуються дані і метод якого `startup()` перевіряє, чи увійшов користувач у систему. Якщо це не так, ми перенаправляємо його на `SignPresenter`. Водночас, ми зберігаємо поточний запит і відправляємо його ключ у `SignPresenter`. +Покажемо все на прикладі. Маємо презентер `AdminPresenter`, в якому редагуються дані і в методі `startup()` якого перевіряється, чи користувач увійшов у систему. Якщо ні, перенаправляємо його на `SignPresenter`. Водночас зберігаємо поточний запит і його ключ надсилаємо до `SignPresenter`. ```php class AdminPresenter extends Nette\Application\UI\Presenter @@ -27,7 +26,7 @@ class AdminPresenter extends Nette\Application\UI\Presenter } ``` -Презентер `SignPresenter` міститиме постійний параметр `$backlink`, в який записується ключ, на додаток до форми входу в систему. Оскільки параметр є постійним, він буде перенесений навіть після надсилання форми входу. +Презентер `SignPresenter` міститиме, крім форми для входу, також персистентний параметр `$backlink`, до якого запишеться ключ. Оскільки параметр є персистентним, він передаватиметься і після надсилання форми входу. ```php @@ -41,14 +40,14 @@ class SignPresenter extends Nette\Application\UI\Presenter protected function createComponentSignInForm() { $form = new Nette\Application\UI\Form; - // ... додаємо поля форми ... + // ... додамо поля форми ... $form->onSuccess[] = [$this, 'signInFormSubmitted']; return $form; } public function signInFormSubmitted($form) { - // ... тут ми реєструємо користувача ... + // ... тут користувача авторизуємо ... $this->restoreRequest($this->backlink); $this->redirect('Admin:'); @@ -56,9 +55,8 @@ class SignPresenter extends Nette\Application\UI\Presenter } ``` -Ми передаємо ключ збереженого запиту методу `restoreRequest()`, і він перенаправляє (або переадресує) до вихідного презентера. +Методу `restoreRequest()` ми передаємо ключ збереженого запиту, і він перенаправляє (або переходить) на початковий презентер. -Однак, якщо ключ недійсний (наприклад, більше не існує в сесії), метод нічого не робить. Тому наступним викликом буде `$this->redirect('Admin:')`, який перенаправляє на `AdminPresenter`. +Однак, якщо ключ недійсний (наприклад, вже не існує в сесії), метод нічого не робить. Тому далі йде виклик `$this->redirect('Admin:')`, який перенаправляє на `AdminPresenter`. {{priority: -1}} -{{sitename: Найкращі практики}} diff --git a/bootstrap/bg/@home.texy b/bootstrap/bg/@home.texy index 4f36647a58..3f9b20f4a1 100644 --- a/bootstrap/bg/@home.texy +++ b/bootstrap/bg/@home.texy @@ -1,93 +1,91 @@ -Как да изтеглите конфигурационен файл -************************************* +Nette Bootstrap +*************** .[perex] -Отделните компоненти на Nette се конфигурират с помощта на конфигурационни файлове. Ще ви покажем как да заредите тези файлове. +Настройваме отделните компоненти на Nette с помощта на конфигурационни файлове. Ще ви покажем как да зареждате тези файлове. .[tip] -Ако използвате цялата рамка, не е необходимо да правите нищо друго. Имате предварително подготвена директория `config/` за конфигурационните файлове във вашия проект, а [зареждащото устройство на приложението |application:bootstrap#DI-Container-Configuration] отговаря за тяхното зареждане. -Тази статия е предназначена за потребители, които използват само една библиотека Nette и искат да се възползват от предимствата на конфигурационните файлове. +Ако използвате целия framework, не е необходимо да правите нищо повече. В проекта имате подготвена директория `config/` за конфигурационните файлове и зареждането им се управлява от [зареждащото устройство на приложението |application:bootstrapping#Конфигурация на DI контейнера]. Тази статия е за потребители, които използват само една библиотека на Nette и искат да използват възможностите на конфигурационните файлове. -Конфигурационните файлове обикновено са написани във формат [NEON |neon:format] и е най-добре да се редактират в [редактори, които го поддържат |best-practices:editors-and-tools#IDE-Editor]. Те могат да се разглеждат като инструкции за **създаване и конфигуриране** на обекти. По този начин резултатът от зареждането на конфигурация ще бъде т.нар. фабрика - обект, който при поискване ще създава други обекти за по-нататъшна употреба. Например връзка с база данни и т.н. +Конфигурационните файлове обикновено се записват във [формат NEON|neon:format] и най-добре се редактират в [редактори с неговата поддръжка |best-practices:editors-and-tools#IDE редактор]. Могат да се разглеждат като ръководства за **създаване и конфигуриране** на обекти. Следователно, резултатът от зареждането на конфигурацията ще бъде така наречената фабрика, която е обект, който по заявка ще ни създаде други обекти, които искаме да използваме. Например връзка с база данни и т.н. -Тази фабрика се нарича още *контейнер за инжектиране на зависимости* (DI-контейнер) и ако се интересувате от подробностите, прочетете главата [Инжектиране на зависимости |dependency-injection:]. +Тази фабрика се нарича още *dependency injection контейнер* (DI container) и ако се интересувате от подробности, прочетете главата за [dependency injection |dependency-injection:]. -Класът [api:Nette\Bootstrap\Configurator] е отговорен за зареждането на конфигурацията и създаването на контейнера, затова първо ще инсталираме неговия пакет `nette/bootstrap`: +Зареждането на конфигурацията и създаването на контейнера се извършва от класа [api:Nette\Bootstrap\Configurator], така че първо ще инсталираме неговия пакет `nette/bootstrap`: ```shell composer require nette/bootstrap ``` -И създайте инстанция на класа `Configurator`. Тъй като генерираният DI-контейнер ще бъде кеширан на диска, трябва да зададем пътя до директорията, в която ще бъде съхранен: +И създаваме инстанция на класа `Configurator`. Тъй като генерираният DI контейнер ще се кешира на диска, е необходимо да се зададе пътят до директорията, където ще се съхранява: ```php $configurator = new Nette\Bootstrap\Configurator; $configurator->setTempDirectory(__DIR__ . '/temp'); ``` -В Linux или macOS задайте [разрешение за запис |nette:troubleshooting#Setting-Directory-Permissions] за директорията `temp/`. +В Linux или macOS задайте на директорията `temp/` [права за запис |nette:troubleshooting#Настройка на правата на директориите]. -И преминаваме към самите конфигурационни файлове. Те се качват с помощта на функцията `addConfig()`: +И стигаме до самите конфигурационни файлове. Зареждаме ги с помощта на `addConfig()`: ```php $configurator->addConfig(__DIR__ . '/database.neon'); ``` -Ако искате да добавите повече конфигурационни файлове, можете да извикате функцията `addConfig()` няколко пъти. Ако във файловете се появят елементи с еднакви ключове, те ще бъдат презаписани (или [обединени в |dependency-injection:configuration#Merging] случай на масиви). По-късно добавеният файл е с по-висок приоритет от предишния. +Ако искаме да добавим повече конфигурационни файлове, можем да извикаме функцията `addConfig()` няколко пъти. Ако във файловете се появят елементи със същите ключове, те ще бъдат презаписани (или в случай на масиви [обединени |dependency-injection:configuration#Сливане]). По-късно вмъкнатият файл има по-висок приоритет от предишния. -Последната стъпка е да създадете контейнер DI: +Последната стъпка е създаването на DI контейнера: ```php $container = $configurator->createContainer(); ``` -И тя вече ще създаде желаните обекти за нас. Например, ако използвате конфигурацията за [Nette Database |database:configuration], можете да я помолите да създаде връзка към базата данни: +И той вече ще ни създаде желаните обекти. Ако например използвате конфигурация за [Nette Database|database:configuration], можете да го помолите да създаде връзки с базата данни: ```php $db = $container->getByType(Nette\Database\Connection::class); -//или +// или $explorer = $container->getByType(Nette\Database\Explorer::class); -//или чрез създаване на множество връзки +// или при създаване на повече връзки $db = $container->getByName('database.main.connection'); ``` -И сега можете да работите с базата данни! +И сега вече можете да работите с базата данни! -Режим на разработка и производствен режим .[#toc-development-vs-production-mode] --------------------------------------------------------------------------------- +Режим на разработка срещу производствен режим +--------------------------------------------- -В режим на разработка контейнерът се актуализира автоматично при всяка промяна на конфигурационните файлове. В производствен режим той се генерира само веднъж и промените не се проверяват. -По този начин режимът за разработчици цели максимално удобство за програмистите, докато производственият режим цели производителност. +В режим на разработка контейнерът се актуализира автоматично при всяка промяна на конфигурационните файлове. В производствен режим се генерира само веднъж и промените не се проверяват. Режимът на разработка е насочен към максимално удобство на програмиста, докато производственият режим е насочен към производителност и реално внедряване. -Режимът се избира чрез автоматично разпознаване, така че обикновено не е необходимо да конфигурирате или превключвате нещо ръчно. Режимът за разработчици се използва, когато приложението се изпълнява на локалния хост (т.е. IP адресът е `127.0.0.1` или `::1`) и няма прокси сървър (т.е. HTTP заглавието му). В противен случай приложението работи в производствен ("боен") режим. +Изборът на режим се извършва чрез автоматично откриване, така че обикновено не е необходимо да конфигурирате или превключвате ръчно. Режимът е разработващ, ако приложението се изпълнява на localhost (т.е. IP адрес `127.0.0.1` или `::1`) и няма налично прокси (т.е. негов HTTP хедър). В противен случай работи в производствен режим. -Ако искате да активирате режима за разработка в други случаи, например когато програмистите имат достъп от определен IP адрес, използвайте `setDebugMode()`: +Ако искаме да разрешим режима на разработка и в други случаи, например за програмисти, достъпващи от конкретен IP адрес, използваме `setDebugMode()`: ```php $configurator->setDebugMode('23.75.345.200'); // може да се зададе и масив от IP адреси ``` -Определено препоръчваме да комбинирате IP адреса с файл с бисквитки. Съхранявайте тайния токен, например `secret1234`, в "бисквитката" `nette-debug`, и по този начин ще активирате режима за разработка за програмисти, които имат достъп от определен IP адрес и също така разполагат с токена, посочен в "бисквитката": +Определено препоръчваме да комбинирате IP адрес с cookie. В cookie `nette-debug` съхраняваме таен токен, например `secret1234`, и по този начин активираме режима на разработка за програмисти, достъпващи от конкретен IP адрес и едновременно имащи споменатия токен в cookie: ```php $configurator->setDebugMode('secret1234@23.75.345.200'); ``` -Можете също така да деактивирате напълно режима за разработчици, дори за localhost: +Можем също така да изключим напълно режима на разработка, дори за localhost: ```php $configurator->setDebugMode(false); ``` -Опции .[#toc-parameters] ------------------------- +Параметри +--------- -Можете също така да използвате параметрите в конфигурационните файлове, които са дефинирани [под `параметры`. |dependency-injection:configuration#Parameters] +В конфигурационните файлове можете да използвате и параметри, които се дефинират [в секцията `parameters` |dependency-injection:configuration#Параметри]. -Те могат да се поставят и външно по метода `addDynamicParameters()`: +Те могат да бъдат вмъкнати и отвън с помощта на метода `addDynamicParameters()`: ```php $configurator->addDynamicParameters([ @@ -95,7 +93,4 @@ $configurator->addDynamicParameters([ ]); ``` -Параметърът `projectId` може да бъде посочен в конфигурацията с помощта на обозначението `%projectId%`. - - -{{leftbar: nette:@menu-topics}} +Параметърът `projectId` може да бъде рефериран в конфигурацията чрез запис `%projectId%`. diff --git a/bootstrap/bg/@meta.texy b/bootstrap/bg/@meta.texy new file mode 100644 index 0000000000..794cbc8522 --- /dev/null +++ b/bootstrap/bg/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Документация на Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/bootstrap/cs/@home.texy b/bootstrap/cs/@home.texy index ae657b5f0d..9e3f2acaa6 100644 --- a/bootstrap/cs/@home.texy +++ b/bootstrap/cs/@home.texy @@ -1,14 +1,13 @@ -Jak načíst konfigurační soubor -****************************** +Nette Bootstrap +*************** .[perex] Jednotlivé součásti Nette nastavujeme pomocí konfiguračních souborů. Ukážeme si, jak tyto soubory načítat. .[tip] -Pokud používate celý framework, není potřeba nic dalšího dělat. V projektu máte pro konfigurační soubory předpřipravený adresář `config/` a jejich načítání má na starosti [zavaděč aplikace|application:bootstrap#konfigurace-di-kontejneru]. -Tento článek je pro uživatele, kteří používají jen jednu knihovnu Nette a chtějí využít možnosti konfiguračních souborů. +Pokud používate celý framework, není potřeba nic dalšího dělat. V projektu máte pro konfigurační soubory předpřipravený adresář `config/` a jejich načítání má na starosti [zavaděč aplikace |application:bootstrapping#Konfigurace DI kontejneru]. Tento článek je pro uživatele, kteří používají jen jednu knihovnu Nette a chtějí využít možnosti konfiguračních souborů. -Konfigurační soubory se obvykle zapisují ve [formátu NEON|neon:format] a nejlépe se upravují v [editorech s jeho podporou|best-practices:editors-and-tools#ide-editor]. Lze je chápat jako návody, jak **vytvářet a konfigurovat** objekty. Tedy výsledkem načtení konfigurace bude tzv. továrna, což je objekt, který nám na požádání vytvoří další objekty, které chceme používat. Například databázové spojení apod. +Konfigurační soubory se obvykle zapisují ve [formátu NEON|neon:format] a nejlépe se upravují v [editorech s jeho podporou |best-practices:editors-and-tools#IDE editor]. Lze je chápat jako návody, jak **vytvářet a konfigurovat** objekty. Tedy výsledkem načtení konfigurace bude tzv. továrna, což je objekt, který nám na požádání vytvoří další objekty, které chceme používat. Například databázové spojení apod. Této továrně se také říká *dependency injection kontejner* (DI container) a pokud by vás zajímaly podrobnosti, přečtěte si kapitolu o [dependency injection |dependency-injection:]. @@ -57,8 +56,7 @@ A nyní už můžete s databází pracovat! Vývojářský vs produkční režim ----------------------------- -Ve vývojářském režimu se kontejner automaticky aktualizuje při každé změně konfiguračních souborů. V produkčním režimu se vygeneruje jen jednou a změny se nekontrolují. -Vývojářský je tedy zaměřen na maximální pohodlí programátora, produkční na výkon a ostré nasazení. +Ve vývojářském režimu se kontejner automaticky aktualizuje při každé změně konfiguračních souborů. V produkčním režimu se vygeneruje jen jednou a změny se nekontrolují. Vývojářský je tedy zaměřen na maximální pohodlí programátora, produkční na výkon a ostré nasazení. Volba režimu se provádí autodetekcí, takže obvykle není potřeba nic konfigurovat nebo ručně přepínat. Režim je vývojářský tehdy, pokud je aplikace spuštěna na localhostu (tj. IP adresa `127.0.0.1` nebo `::1`) a není přitomna proxy (tj. její HTTP hlavička). Jinak běží v produkčním režimu. @@ -85,7 +83,7 @@ $configurator->setDebugMode(false); Parametry --------- -V konfiguračním souborech můžete používat také parametry, které se definují [v sekci `parameters`|dependency-injection:configuration#parametry]. +V konfiguračním souborech můžete používat také parametry, které se definují [v sekci `parameters` |dependency-injection:configuration#Parametry]. Lze je také vkládat zvenčí pomocí metody `addDynamicParameters()`: @@ -96,6 +94,3 @@ $configurator->addDynamicParameters([ ``` Na parametr `projectId` se lze v konfiguraci odkázat zápisem `%projectId%`. - - -{{leftbar: nette:@menu-topics}} diff --git a/bootstrap/cs/@meta.texy b/bootstrap/cs/@meta.texy new file mode 100644 index 0000000000..08edde785b --- /dev/null +++ b/bootstrap/cs/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Dokumentace}} +{{leftbar: nette:@menu-topics}} diff --git a/bootstrap/de/@home.texy b/bootstrap/de/@home.texy index 09d1d438de..8f49650c62 100644 --- a/bootstrap/de/@home.texy +++ b/bootstrap/de/@home.texy @@ -1,47 +1,46 @@ -Laden der Konfigurationsdatei -***************************** +Nette Bootstrap +*************** .[perex] -Die einzelnen Komponenten von Nette werden über Konfigurationsdateien konfiguriert. Wir zeigen Ihnen, wie Sie diese Dateien laden können. +Einzelne Nette-Komponenten werden über Konfigurationsdateien eingerichtet. Wir zeigen Ihnen, wie Sie diese Dateien laden. .[tip] -Wenn Sie das gesamte Framework verwenden, müssen Sie nichts weiter tun. Im Projekt gibt es ein vorgefertigtes Verzeichnis `config/` für die Konfigurationsdateien, und der [Application Loader |application:bootstrap#DI Container Configuration] ist für das Laden dieser Dateien verantwortlich. -Dieser Artikel richtet sich an Benutzer, die nur eine Nette-Bibliothek verwenden und die Vorteile der Konfigurationsdateien nutzen möchten. +Wenn Sie das gesamte Framework verwenden, müssen Sie nichts weiter tun. In Ihrem Projekt gibt es ein vorbereitetes Verzeichnis `config/` für Konfigurationsdateien, und deren Laden wird vom [Anwendungs-Bootstrap |application:bootstrapping#Konfiguration des DI-Containers] übernommen. Dieser Artikel richtet sich an Benutzer, die nur eine Nette-Bibliothek verwenden und die Möglichkeiten der Konfigurationsdateien nutzen möchten. -Konfigurationsdateien sind normalerweise im [NEON|neon:format] geschrieben und werden am besten in [Editoren mit Unterstützung dafür|best-practices:editors-and-tools#ide-editor] bearbeitet. Man kann sie sich als Anweisungen vorstellen, wie man **Objekte erstellt und konfiguriert**. So wird das Ergebnis des Ladens einer Konfiguration eine so genannte Fabrik sein, ein Objekt, das bei Bedarf andere Objekte erzeugt, die Sie verwenden möchten. Zum Beispiel eine Datenbankverbindung, usw. +Konfigurationsdateien werden normalerweise im [NEON-Format|neon:format] geschrieben und am besten in [Editoren mit NEON-Unterstützung |best-practices:editors-and-tools#IDE-Editor] bearbeitet. Sie können als Anleitungen zum **Erstellen und Konfigurieren** von Objekten verstanden werden. Das Ergebnis des Ladens der Konfiguration ist also eine sogenannte Factory, ein Objekt, das auf Anfrage weitere Objekte erstellt, die wir verwenden möchten. Zum Beispiel eine Datenbankverbindung usw. -Diese Fabrik wird auch als *Dependency Injection Container* (DI-Container) bezeichnet. Wenn Sie an den Details interessiert sind, lesen Sie das Kapitel über [Dependency Injection |dependency-injection:]. +Dieser Factory wird auch *Dependency Injection Container* (DI-Container) genannt. Wenn Sie an Details interessiert sind, lesen Sie das Kapitel über [Dependency Injection |dependency-injection:]. -Das Laden der Konfiguration und das Erstellen des Containers wird von der Klasse [api:Nette\Bootstrap\Configurator] erledigt, also installieren wir zuerst ihr Paket `nette/bootstrap`: +Das Laden der Konfiguration und das Erstellen des Containers übernimmt die Klasse [api:Nette\Bootstrap\Configurator]. Installieren wir also zuerst ihr Paket `nette/bootstrap`: ```shell composer require nette/bootstrap ``` -Und erstellen Sie eine Instanz der Klasse `Configurator`. Da der erzeugte DI-Container auf der Festplatte zwischengespeichert wird, müssen Sie den Pfad zu dem Verzeichnis angeben, in dem er gespeichert werden soll: +Und wir erstellen eine Instanz der Klasse `Configurator`. Da der generierte DI-Container auf der Festplatte zwischengespeichert wird, ist es notwendig, den Pfad zum Verzeichnis festzulegen, in dem er gespeichert werden soll: ```php $configurator = new Nette\Bootstrap\Configurator; $configurator->setTempDirectory(__DIR__ . '/temp'); ``` -Unter Linux oder macOS setzen Sie die [Schreibrechte |nette:troubleshooting#Setting directory permissions] für das Verzeichnis `temp/`. +Unter Linux oder macOS setzen Sie für das Verzeichnis `temp/` [Schreibberechtigungen |nette:troubleshooting#Einstellung der Verzeichnisberechtigungen]. -Nun kommen wir zu den Konfigurationsdateien selbst. Diese werden über `addConfig()` geladen: +Und wir kommen zu den Konfigurationsdateien selbst. Wir laden sie mit `addConfig()`: ```php $configurator->addConfig(__DIR__ . '/database.neon'); ``` -Wenn Sie weitere Konfigurationsdateien hinzufügen möchten, können Sie die Funktion `addConfig()` mehrfach aufrufen. Wenn in den Dateien Elemente mit denselben Schlüsseln vorkommen, werden sie überschrieben (oder im Falle von Arrays [zusammengeführt |dependency-injection:configuration#Merging] ). Eine später eingefügte Datei hat eine höhere Priorität als die vorherige. +Wenn wir mehrere Konfigurationsdateien hinzufügen möchten, können wir die Funktion `addConfig()` mehrmals aufrufen. Wenn in den Dateien Elemente mit denselben Schlüsseln vorkommen, werden sie überschrieben (oder im Falle von Arrays [zusammengeführt |dependency-injection:configuration#Zusammenführen]). Eine später eingefügte Datei hat eine höhere Priorität als die vorherige. -Der letzte Schritt besteht darin, einen DI-Container zu erstellen: +Der letzte Schritt ist die Erstellung des DI-Containers: ```php $container = $configurator->createContainer(); ``` -Dieser wird bereits die gewünschten Objekte für uns erstellen. Wenn Sie zum Beispiel die Konfiguration für [Nette Database |database:configuration] verwenden, können Sie ihn bitten, Datenbankverbindungen zu erstellen: +Und dieser erstellt uns dann die gewünschten Objekte. Wenn Sie beispielsweise die Konfiguration für [Nette Database|database:configuration] verwenden, können Sie ihn bitten, Datenbankverbindungen zu erstellen: ```php $db = $container->getByType(Nette\Database\Connection::class); @@ -51,43 +50,42 @@ $explorer = $container->getByType(Nette\Database\Explorer::class); $db = $container->getByName('database.main.connection'); ``` -Und schon können Sie mit der Datenbank arbeiten! +Und jetzt können Sie mit der Datenbank arbeiten! -Entwicklungs- vs. Produktionsmodus .[#toc-development-vs-production-mode] -------------------------------------------------------------------------- +Entwicklungs- vs. Produktionsmodus +---------------------------------- -Im Entwicklungsmodus wird der Container automatisch aktualisiert, wenn die Konfigurationsdateien geändert werden. Im Produktionsmodus wird er nur einmal erstellt und Änderungen werden nicht überprüft. -Der Entwicklermodus zielt also auf maximale Bequemlichkeit für den Programmierer ab, während der Produktionsmodus auf Leistung ausgerichtet ist. +Im Entwicklungsmodus wird der Container bei jeder Änderung der Konfigurationsdateien automatisch aktualisiert. Im Produktionsmodus wird er nur einmal generiert, und Änderungen werden nicht überprüft. Der Entwicklungsmodus ist also auf maximalen Komfort für den Programmierer ausgerichtet, der Produktionsmodus auf Leistung und den Live-Einsatz. -Die Auswahl des Modus erfolgt durch automatische Erkennung, so dass es in der Regel nicht notwendig ist, etwas zu konfigurieren oder manuell umzuschalten. Der Entwicklungsmodus ist aktiviert, wenn die Anwendung auf einem lokalen Host (d. h. IP-Adresse `127.0.0.1` oder `::1`) läuft und kein Proxy (d. h. sein HTTP-Header) vorhanden ist. Ansonsten läuft sie im Produktionsmodus. +Die Modusauswahl erfolgt durch Autoerkennung, sodass normalerweise keine Konfiguration oder manuelles Umschalten erforderlich ist. Der Modus ist der Entwicklungsmodus, wenn die Anwendung auf Localhost (d.h. IP-Adresse `127.0.0.1` oder `::1`) ausgeführt wird und kein Proxy vorhanden ist (d.h. dessen HTTP-Header). Andernfalls läuft sie im Produktionsmodus. -Wenn Sie den Entwicklungsmodus in anderen Fällen aktivieren möchten, z. B. für Programmierer, die von einer bestimmten IP-Adresse aus zugreifen, verwenden Sie `setDebugMode()`: +Wenn wir den Entwicklungsmodus auch in anderen Fällen aktivieren möchten, zum Beispiel für Programmierer, die von einer bestimmten IP-Adresse zugreifen, verwenden wir `setDebugMode()`: ```php $configurator->setDebugMode('23.75.345.200'); -// es kann auch ein Array von IP-Adressen angegeben werden +// Es kann auch ein Array von IP-Adressen angegeben werden ``` -Wir empfehlen unbedingt, die IP-Adresse mit einem Cookie zu kombinieren. Speichern Sie ein geheimes Token, z. B. `secret1234`, im Cookie `nette-debug`. Auf diese Weise aktivieren Sie den Entwicklungsmodus für Programmierer, die von einer bestimmten IP-Adresse aus zugreifen, und haben auch das im Cookie erwähnte Token: +Wir empfehlen dringend, die IP-Adresse mit einem Cookie zu kombinieren. Wir speichern ein geheimes Token, z.B. `secret1234`, im Cookie `nette-debug` und aktivieren auf diese Weise den Entwicklungsmodus für Programmierer, die von einer bestimmten IP-Adresse zugreifen und gleichzeitig das erwähnte Token im Cookie haben: ```php $configurator->setDebugMode('secret1234@23.75.345.200'); ``` -Sie können den Entwicklermodus auch ganz deaktivieren, sogar für localhost: +Wir können den Entwicklungsmodus auch vollständig deaktivieren, sogar für Localhost: ```php $configurator->setDebugMode(false); ``` -Parameter .[#toc-parameters] ----------------------------- +Parameter +--------- -Sie können auch Parameter in Konfigurationsdateien verwenden, die [im Abschnitt `parameters` |dependency-injection:configuration#parameters`] definiert sind. +In Konfigurationsdateien können Sie auch Parameter verwenden, die [im Abschnitt `parameters` |dependency-injection:configuration#Parameter] definiert sind. -Sie können auch von außen mit der Methode `addDynamicParameters()` eingefügt werden: +Sie können auch von außen über die Methode `addDynamicParameters()` eingefügt werden: ```php $configurator->addDynamicParameters([ @@ -95,7 +93,4 @@ $configurator->addDynamicParameters([ ]); ``` -Der Parameter `projectId` kann in der Konfiguration mit der Notation `%projectId%` referenziert werden. - - -{{leftbar: nette:@menu-topics}} +Auf den Parameter `projectId` kann in der Konfiguration mit der Notation `%projectId%` verwiesen werden. diff --git a/bootstrap/de/@meta.texy b/bootstrap/de/@meta.texy new file mode 100644 index 0000000000..2cf383a5cf --- /dev/null +++ b/bootstrap/de/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Dokumentation}} +{{leftbar: nette:@menu-topics}} diff --git a/bootstrap/el/@home.texy b/bootstrap/el/@home.texy index 8f48c0f4ac..8b4cf46446 100644 --- a/bootstrap/el/@home.texy +++ b/bootstrap/el/@home.texy @@ -1,47 +1,46 @@ -Πώς να φορτώσετε το αρχείο ρυθμίσεων -************************************ +Nette Bootstrap +*************** .[perex] -Τα επιμέρους στοιχεία της Nette διαμορφώνονται με τη χρήση αρχείων διαμόρφωσης. Θα δείξουμε πώς να φορτώσετε αυτά τα αρχεία. +Τα διάφορα μέρη του Nette διαμορφώνονται χρησιμοποιώντας αρχεία διαμόρφωσης. Θα δείξουμε πώς να φορτώνετε αυτά τα αρχεία. .[tip] -Αν χρησιμοποιείτε ολόκληρο το πλαίσιο, δεν χρειάζεται να κάνετε τίποτα άλλο. Στο έργο, έχετε έναν προ-προετοιμασμένο κατάλογο `config/` για τα αρχεία διαμόρφωσης και ο [φορτωτής της εφαρμογής |application:bootstrap#DI Container Configuration] είναι υπεύθυνος για τη φόρτωσή τους. -Αυτό το άρθρο απευθύνεται σε χρήστες που χρησιμοποιούν μόνο μία βιβλιοθήκη Nette και θέλουν να επωφεληθούν από τα αρχεία διαμόρφωσης. +Αν χρησιμοποιείτε ολόκληρο το framework, δεν χρειάζεται να κάνετε τίποτα άλλο. Στο έργο σας, έχετε έναν προετοιμασμένο κατάλογο `config/` για αρχεία διαμόρφωσης, και η φόρτωσή τους αναλαμβάνεται από τον [φορτωτή της εφαρμογής |application:bootstrapping#Διαμόρφωση του DI Container]. Αυτό το άρθρο είναι για χρήστες που χρησιμοποιούν μόνο μία βιβλιοθήκη Nette και θέλουν να εκμεταλλευτούν τις δυνατότητες των αρχείων διαμόρφωσης. -Τα αρχεία διαμόρφωσης είναι συνήθως γραμμένα σε [NEON |neon:format] και είναι καλύτερο να τα επεξεργάζεστε σε [επεξεργαστές με υποστήριξη για αυτό |best-practices:editors-and-tools#ide-editor]. Μπορούν να θεωρηθούν ως οδηγίες για το πώς να **δημιουργήσετε και να διαμορφώσετε** αντικείμενα. Έτσι, το αποτέλεσμα της φόρτωσης μιας διαμόρφωσης θα είναι ένα λεγόμενο εργοστάσιο, το οποίο είναι ένα αντικείμενο που θα δημιουργεί κατ' απαίτηση άλλα αντικείμενα που θέλετε να χρησιμοποιήσετε. Για παράδειγμα, μια σύνδεση με βάση δεδομένων κ.λπ. +Τα αρχεία διαμόρφωσης συνήθως γράφονται σε [μορφή NEON |neon:format] και επεξεργάζονται καλύτερα σε [editors με υποστήριξη για αυτό |best-practices:editors-and-tools#IDE editor]. Μπορούν να θεωρηθούν ως οδηγίες για το πώς να **δημιουργείτε και να διαμορφώνετε** αντικείμενα. Έτσι, το αποτέλεσμα της φόρτωσης της διαμόρφωσης θα είναι ένα λεγόμενο factory, το οποίο είναι ένα αντικείμενο που, κατόπιν αιτήματος, θα δημιουργήσει άλλα αντικείμενα που θέλουμε να χρησιμοποιήσουμε. Για παράδειγμα, συνδέσεις βάσης δεδομένων κ.λπ. -Αυτό το εργοστάσιο ονομάζεται επίσης *περιέκτης έγχυσης εξαρτήσεων* (DI container) και αν σας ενδιαφέρουν οι λεπτομέρειες, διαβάστε το κεφάλαιο για την [έγχυση εξαρτήσεων |dependency-injection:]. +Αυτό το factory ονομάζεται επίσης *dependency injection container* (DI container) και αν σας ενδιαφέρουν οι λεπτομέρειες, διαβάστε το κεφάλαιο για το [dependency injection |dependency-injection:]. -Η φόρτωση των ρυθμίσεων και η δημιουργία του container γίνεται από την κλάση [api:Nette\Bootstrap\Configurator], οπότε θα εγκαταστήσουμε πρώτα το πακέτο της `nette/bootstrap`: +Η φόρτωση της διαμόρφωσης και η δημιουργία του container αναλαμβάνεται από την κλάση [api:Nette\Bootstrap\Configurator], οπότε πρώτα θα εγκαταστήσουμε το πακέτο της `nette/bootstrap`: ```shell composer require nette/bootstrap ``` -Και θα δημιουργήσουμε μια περίπτωση της κλάσης `Configurator`. Δεδομένου ότι το παραγόμενο δοχείο DI θα αποθηκευτεί στο δίσκο, πρέπει να ορίσετε τη διαδρομή προς τον κατάλογο όπου θα αποθηκευτεί: +Και θα δημιουργήσουμε ένα στιγμιότυπο της κλάσης `Configurator`. Επειδή ο παραγόμενος DI container θα αποθηκευτεί προσωρινά στον δίσκο, είναι απαραίτητο να ορίσουμε τη διαδρομή προς τον κατάλογο όπου θα αποθηκευτεί: ```php $configurator = new Nette\Bootstrap\Configurator; $configurator->setTempDirectory(__DIR__ . '/temp'); ``` -Σε Linux ή macOS, ορίστε τα [δικαιώματα εγγραφής |nette:troubleshooting#Setting directory permissions] για τον κατάλογο `temp/`. +Σε Linux ή macOS, ορίστε [δικαιώματα εγγραφής |nette:troubleshooting#Ρύθμιση δικαιωμάτων καταλόγου] στον κατάλογο `temp/`. -Και ερχόμαστε στα ίδια τα αρχεία ρυθμίσεων. Αυτά φορτώνονται χρησιμοποιώντας το `addConfig()`: +Και φτάνουμε στα ίδια τα αρχεία διαμόρφωσης. Τα φορτώνουμε χρησιμοποιώντας το `addConfig()`: ```php $configurator->addConfig(__DIR__ . '/database.neon'); ``` -Αν θέλετε να προσθέσετε περισσότερα αρχεία ρυθμίσεων, μπορείτε να καλέσετε τη συνάρτηση `addConfig()` πολλές φορές. Εάν στοιχεία με τα ίδια κλειδιά εμφανίζονται στα αρχεία, θα αντικατασταθούν (ή θα [συγχωνευθούν |dependency-injection:configuration#Merging] στην περίπτωση των πινάκων). Ένα αρχείο που εισάγεται αργότερα έχει υψηλότερη προτεραιότητα από το προηγούμενο. +Αν θέλουμε να προσθέσουμε περισσότερα αρχεία διαμόρφωσης, μπορούμε να καλέσουμε τη συνάρτηση `addConfig()` πολλές φορές. Αν εμφανιστούν στοιχεία με τα ίδια κλειδιά στα αρχεία, θα αντικατασταθούν (ή στην περίπτωση πινάκων [θα συγχωνευθούν |dependency-injection:configuration#Συγχώνευση]). Το αρχείο που εισάγεται αργότερα έχει υψηλότερη προτεραιότητα από το προηγούμενο. -Το τελευταίο βήμα είναι η δημιουργία ενός δοχείου DI: +Το τελευταίο βήμα είναι η δημιουργία του DI container: ```php $container = $configurator->createContainer(); ``` -Και αυτό θα δημιουργήσει ήδη τα επιθυμητά αντικείμενα για εμάς. Για παράδειγμα, αν χρησιμοποιείτε τη διαμόρφωση για [τη Nette Database |database:configuration], μπορείτε να του ζητήσετε να δημιουργήσει συνδέσεις βάσης δεδομένων: +Και αυτός θα δημιουργήσει για εμάς τα απαιτούμενα αντικείμενα. Για παράδειγμα, αν χρησιμοποιείτε τη διαμόρφωση για το [Nette Database |database:configuration], μπορείτε να του ζητήσετε να δημιουργήσει συνδέσεις βάσης δεδομένων: ```php $db = $container->getByType(Nette\Database\Connection::class); @@ -54,38 +53,37 @@ $db = $container->getByName('database.main.connection'); Και τώρα μπορείτε να εργαστείτε με τη βάση δεδομένων! -Λειτουργία ανάπτυξης έναντι παραγωγής .[#toc-development-vs-production-mode] ----------------------------------------------------------------------------- +Κατάσταση ανάπτυξης vs παραγωγής +-------------------------------- -Στη λειτουργία ανάπτυξης, ο περιέκτης ενημερώνεται αυτόματα κάθε φορά που αλλάζουν τα αρχεία ρυθμίσεων. Στη λειτουργία παραγωγής, δημιουργείται μόνο μία φορά και οι αλλαγές δεν ελέγχονται. -Έτσι, η λειτουργία ανάπτυξης στοχεύει στη μέγιστη ευκολία του προγραμματιστή, ενώ η λειτουργία παραγωγής στοχεύει στην απόδοση. +Στην κατάσταση ανάπτυξης, ο container ενημερώνεται αυτόματα κάθε φορά που αλλάζουν τα αρχεία διαμόρφωσης. Στην κατάσταση παραγωγής, δημιουργείται μόνο μία φορά και οι αλλαγές δεν ελέγχονται. Η κατάσταση ανάπτυξης επικεντρώνεται στην μέγιστη άνεση του προγραμματιστή, ενώ η κατάσταση παραγωγής στην απόδοση και την πραγματική ανάπτυξη. -Η επιλογή του τρόπου λειτουργίας γίνεται με αυτόματη ανίχνευση, οπότε συνήθως δεν χρειάζεται να ρυθμίσετε ή να αλλάξετε χειροκίνητα κάτι. Η κατάσταση λειτουργίας είναι development όταν η εφαρμογή εκτελείται σε ένα localhost (δηλ. διεύθυνση IP `127.0.0.1` ή `::1`) και δεν υπάρχει proxy (δηλ. η επικεφαλίδα HTTP του). Διαφορετικά εκτελείται σε κατάσταση παραγωγής. +Η επιλογή της κατάστασης γίνεται μέσω αυτόματης ανίχνευσης, οπότε συνήθως δεν χρειάζεται να διαμορφώσετε ή να αλλάξετε κάτι χειροκίνητα. Η κατάσταση είναι ανάπτυξης εάν η εφαρμογή εκτελείται σε localhost (δηλ. διεύθυνση IP `127.0.0.1` ή `::1`) και δεν υπάρχει proxy (δηλ. η κεφαλίδα HTTP του). Διαφορετικά, εκτελείται σε κατάσταση παραγωγής. -Αν θέλετε να ενεργοποιήσετε τη λειτουργία ανάπτυξης σε άλλες περιπτώσεις, όπως για παράδειγμα για προγραμματιστές που έχουν πρόσβαση από μια συγκεκριμένη διεύθυνση IP, χρησιμοποιήστε τη διεύθυνση `setDebugMode()`: +Αν θέλουμε να ενεργοποιήσουμε την κατάσταση ανάπτυξης και σε άλλες περιπτώσεις, για παράδειγμα για προγραμματιστές που έχουν πρόσβαση από μια συγκεκριμένη διεύθυνση IP, χρησιμοποιούμε το `setDebugMode()`: ```php $configurator->setDebugMode('23.75.345.200'); -// μπορεί επίσης να καθοριστεί ένας πίνακας διευθύνσεων IP +// μπορεί να δοθεί και ένας πίνακας διευθύνσεων IP ``` -Συνιστούμε οπωσδήποτε τον συνδυασμό της διεύθυνσης IP με ένα cookie. Αποθηκεύστε ένα μυστικό κουπόνι, π.χ. `secret1234`, στο cookie `nette-debug`, και με αυτόν τον τρόπο ενεργοποιείτε τη λειτουργία ανάπτυξης για προγραμματιστές που έχουν πρόσβαση από συγκεκριμένη διεύθυνση IP και έχουν επίσης το κουπόνι που αναφέρεται στο cookie: +Συνιστούμε ανεπιφύλακτα τον συνδυασμό της διεύθυνσης IP με ένα cookie. Στο cookie `nette-debug` αποθηκεύουμε ένα μυστικό token, π.χ. `secret1234`, και με αυτόν τον τρόπο ενεργοποιούμε την κατάσταση ανάπτυξης για προγραμματιστές που έχουν πρόσβαση από μια συγκεκριμένη διεύθυνση IP και ταυτόχρονα έχουν το αναφερόμενο token στο cookie: ```php $configurator->setDebugMode('secret1234@23.75.345.200'); ``` -Μπορείτε επίσης να απενεργοποιήσετε εντελώς τη λειτουργία ανάπτυξης, ακόμη και για το localhost: +Μπορούμε επίσης να απενεργοποιήσουμε εντελώς την κατάσταση ανάπτυξης, ακόμη και για localhost: ```php $configurator->setDebugMode(false); ``` -Παράμετροι .[#toc-parameters] ------------------------------ +Παράμετροι +---------- -Μπορείτε επίσης να χρησιμοποιήσετε παραμέτρους σε αρχεία ρυθμίσεων, οι οποίες ορίζονται [στην ενότητα `parameters` |dependency-injection:configuration#parameters`]. +Στα αρχεία διαμόρφωσης μπορείτε επίσης να χρησιμοποιήσετε παραμέτρους, οι οποίες ορίζονται [στην ενότητα `parameters` |dependency-injection:configuration#Παράμετροι]. Μπορούν επίσης να εισαχθούν από έξω χρησιμοποιώντας τη μέθοδο `addDynamicParameters()`: @@ -95,7 +93,4 @@ $configurator->addDynamicParameters([ ]); ``` -Η παράμετρος `projectId` μπορεί να αναφέρεται στη διαμόρφωση με τον συμβολισμό `%projectId%`. - - -{{leftbar: nette:@menu-topics}} +Στην παράμετρο `projectId` μπορεί να γίνει αναφορά στη διαμόρφωση με τη σύνταξη `%projectId%`. diff --git a/bootstrap/el/@meta.texy b/bootstrap/el/@meta.texy new file mode 100644 index 0000000000..a09ce5fe0d --- /dev/null +++ b/bootstrap/el/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Τεκμηρίωση}} +{{leftbar: nette:@menu-topics}} diff --git a/bootstrap/en/@home.texy b/bootstrap/en/@home.texy index 44045e6c32..0e80ab16c2 100644 --- a/bootstrap/en/@home.texy +++ b/bootstrap/en/@home.texy @@ -1,18 +1,17 @@ -How to Load Configuration File -****************************** +Nette Bootstrap +*************** .[perex] -The individual components of Nette are configured using configuration files. We will show how to load these files. +Individual Nette components are configured using configuration files. We will show how to load these files. .[tip] -If you are using the whole framework, there is no need to do anything else. In the project, you have a pre-prepared directory `config/` for the configuration files, and the [application loader|application:bootstrap#DI Container Configuration] is responsible for loading them. -This article is for users who use only one Nette library and want to take advantage of the configuration files. +If you are using the entire framework, there is no need to do anything else. Your project has a prepared `config/` directory for configuration files, and their loading is handled by the [application loader |application:bootstrapping#DI Container Configuration]. This article is for users who use only a single Nette library and want to take advantage of configuration files. -Configuration files are usually written in [NEON|neon:format] and are best edited in [editors with support for it|best-practices:editors-and-tools#ide-editor]. They can be thought of as instructions on how to **create and configure** objects. Thus, the result of loading a configuration will be a so-called factory, which is an object that will on demand create other objects you want to use. For example, a database connection, etc. +Configuration files are usually written in [NEON format|neon:format] and are best edited in [editors that support it |best-practices:editors-and-tools#IDE Editor]. They can be thought of as instructions on how to **create and configure** objects. Thus, the result of loading a configuration will be a so-called factory, which is an object that creates on demand other objects you want to use. For example, a database connection, etc. -This factory is also called a *dependency injection container* (DI container) and if you are interested in the details, read the chapter on [dependency injection |dependency-injection:]. +This factory is also called a *dependency injection container* (DI container), and if you are interested in the details, read the chapter on [dependency injection |dependency-injection:]. -Loading the configuration and creating the container is handled by the [api:Nette\Bootstrap\Configurator] class, so we'll install its `nette/bootstrap` package first: +Loading the configuration and creating the container is handled by the [api:Nette\Bootstrap\Configurator] class, so first, we install its `nette/bootstrap` package: ```shell composer require nette/bootstrap @@ -25,23 +24,23 @@ $configurator = new Nette\Bootstrap\Configurator; $configurator->setTempDirectory(__DIR__ . '/temp'); ``` -On Linux or macOS, set the [write permissions |nette:troubleshooting#Setting directory permissions] for directory `temp/`. +On Linux or macOS, set [write permissions |nette:troubleshooting#Setting Directory Permissions] for the `temp/` directory. -And we come to the configuration files themselves. These are loaded using `addConfig()`: +Now we get to the configuration files themselves. We load them using `addConfig()`: ```php $configurator->addConfig(__DIR__ . '/database.neon'); ``` -If you want to add more configuration files, you can call the `addConfig()` function multiple times. If elements with the same keys appear in the files, they will be overwritten (or [merged |dependency-injection:configuration#Merging] in the case of arrays). A later inserted file has a higher priority than the previous one. +If you want to add more configuration files, you can call the `addConfig()` function multiple times. If elements with the same keys appear in the files, they will be overwritten (or [merged |dependency-injection:configuration#Merging] in the case of arrays). The file added later has a higher priority than the previous one. -The last step is to create a DI container: +The final step is to create the DI container: ```php $container = $configurator->createContainer(); ``` -And it will already create the desired objects for us. For example, if you are using the configuration for [Nette Database|database:configuration], you can ask it to create database connections: +And this will create the desired objects for us. For example, if you are using the configuration for [Nette Database|database:configuration], you can ask it to create database connections: ```php $db = $container->getByType(Nette\Database\Connection::class); @@ -57,25 +56,24 @@ And now you can work with the database! Development vs Production Mode ------------------------------ -In development mode, the container is automatically updated whenever the configuration files are changed. In production mode, it is generated only once and changes are not checked. -So developer mode is aimed at maximum programmer convenience, production mode is aimed at performance. +In development mode, the container is automatically updated whenever the configuration files are changed. In production mode, it is generated only once, and changes are not checked. Thus, development mode is aimed at maximum programmer convenience, while production mode focuses on performance and production deployment. -Mode selection is done by autodetection, so there is usually no need to configure or manually switch anything. The mode is development when the application is running on a localhost (i.e., IP address `127.0.0.1` or `::1`) and no proxy (i.e., its HTTP header) is present. Otherwise it runs in production mode. +Mode selection is done by autodetection, so there is usually no need to configure or manually switch anything. The mode is development if the application is running on localhost (i.e., IP address `127.0.0.1` or `::1`) and no proxy (i.e., its HTTP header) is present. Otherwise, it runs in production mode. -If you want to enable development mode in other cases, such as programmers accessing from a specific IP address, use `setDebugMode()`: +If we want to enable development mode in other cases, for example, for programmers accessing from a specific IP address, use `setDebugMode()`: ```php $configurator->setDebugMode('23.75.345.200'); // an array of IP addresses can also be specified ``` -We definitely recommend combining the IP address with a cookie. Store a secret token, e.g. `secret1234`, in the `nette-debug` cookie, and this way you enable development mode for programmers accessing from a specific IP address and also having the token mentioned in the cookie: +We strongly recommend combining the IP address with a cookie. Store a secret token, e.g., `secret1234`, in the `nette-debug` cookie. This way, you enable development mode for programmers accessing from a specific IP address who also have the mentioned token in their cookie: ```php $configurator->setDebugMode('secret1234@23.75.345.200'); ``` -You can also disable developer mode altogether, even for localhost: +We can also disable development mode completely, even for localhost: ```php $configurator->setDebugMode(false); @@ -85,9 +83,9 @@ $configurator->setDebugMode(false); Parameters ---------- -You can also use parameters in configuration files, which are defined [in the `parameters` section|dependency-injection:configuration#parameters`]. +You can also use parameters in configuration files, which are defined [in the `parameters` section |dependency-injection:configuration#Parameters]. -They can also be inserted from outside using the `addDynamicParameters()` method: +They can also be inserted from the outside using the `addDynamicParameters()` method: ```php $configurator->addDynamicParameters([ @@ -95,7 +93,4 @@ $configurator->addDynamicParameters([ ]); ``` -The `projectId` parameter can be referenced in the configuration with the `%projectId%` notation. - - -{{leftbar: nette:@menu-topics}} +The `projectId` parameter can be referenced in the configuration using the `%projectId%` notation. diff --git a/bootstrap/en/@meta.texy b/bootstrap/en/@meta.texy new file mode 100644 index 0000000000..91205786e5 --- /dev/null +++ b/bootstrap/en/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Documentation}} +{{leftbar: nette:@menu-topics}} diff --git a/bootstrap/es/@home.texy b/bootstrap/es/@home.texy index 0183aa7aa9..25321bada6 100644 --- a/bootstrap/es/@home.texy +++ b/bootstrap/es/@home.texy @@ -1,91 +1,89 @@ -Cómo cargar el archivo de configuración -*************************************** +Nette Bootstrap +*************** .[perex] -Los componentes individuales de Nette se configuran utilizando archivos de configuración. Le mostraremos cómo cargar estos archivos. +Configuramos los componentes individuales de Nette usando archivos de configuración. Mostraremos cómo cargar estos archivos. .[tip] -Si estás utilizando todo el framework, no hay necesidad de hacer nada más. En el proyecto, tienes un directorio pre-preparado `config/` para los ficheros de configuración, y el [cargador de la |application:bootstrap#DI Container Configuration] aplicación se encarga de cargarlos. -Este artículo es para usuarios que utilizan sólo una librería Nette y quieren aprovechar los ficheros de configuración. +Si está utilizando todo el framework, no necesita hacer nada más. En su proyecto, tiene un directorio `config/` preparado para archivos de configuración, y la [carga de la aplicación |application:bootstrapping#Configuración del contenedor DI] se encarga de cargarlos. Este artículo es para usuarios que utilizan solo una librería de Nette y desean aprovechar las capacidades de los archivos de configuración. -Los archivos de configuración suelen estar escritos en [NEON |neon:format] y es mejor editarlos en [editores que lo soporten |best-practices:editors-and-tools#ide-editor]. Pueden ser considerados como instrucciones sobre como **crear y configurar** objetos. Así, el resultado de cargar una configuración será lo que se denomina una fábrica, que es un objeto que creará bajo demanda otros objetos que quieras utilizar. Por ejemplo, una conexión a una base de datos, etc. +Los archivos de configuración generalmente se escriben en [formato NEON|neon:format] y se editan mejor en [editores con soporte para él |best-practices:editors-and-tools#Editor IDE]. Pueden verse como instrucciones sobre cómo **crear y configurar** objetos. Por lo tanto, el resultado de cargar la configuración será una llamada fábrica, que es un objeto que creará otros objetos que queremos usar bajo demanda. Por ejemplo, una conexión a la base de datos, etc. -Esta fábrica también se llama *contenedor de inyección de dependencias* (DI container) y si estás interesado en los detalles, lee el capítulo sobre [inyección de dependencias |dependency-injection:]. +Esta fábrica también se llama *contenedor de inyección de dependencias* (contenedor DI) y si está interesado en los detalles, lea el capítulo sobre [inyección de dependencias |dependency-injection:]. -La carga de la configuración y la creación del contenedor son manejadas por la clase [api:Nette\Bootstrap\Configurator], así que instalaremos primero su paquete `nette/bootstrap`: +La carga de la configuración y la creación del contenedor son manejadas por la clase [api:Nette\Bootstrap\Configurator], así que primero instalaremos su paquete `nette/bootstrap`: ```shell composer require nette/bootstrap ``` -Y crearemos una instancia de la clase `Configurator`. Dado que el contenedor DI generado se almacenará en caché en el disco, es necesario establecer la ruta al directorio donde se guardará: +Y creamos una instancia de la clase `Configurator`. Dado que el contenedor DI generado se almacenará en caché en el disco, es necesario establecer la ruta al directorio donde se guardará: ```php $configurator = new Nette\Bootstrap\Configurator; $configurator->setTempDirectory(__DIR__ . '/temp'); ``` -En Linux o macOS, establece los [permisos de escritura |nette:troubleshooting#Setting directory permissions] para el directorio `temp/`. +En Linux o macOS, establezca los [permisos de escritura |nette:troubleshooting#Configuración de permisos de directorio] para el directorio `temp/`. -Y llegamos a los archivos de configuración propiamente dichos. Estos se cargan utilizando `addConfig()`: +Y llegamos a los propios archivos de configuración. Los cargamos usando `addConfig()`: ```php $configurator->addConfig(__DIR__ . '/database.neon'); ``` -Si desea añadir más ficheros de configuración, puede llamar a la función `addConfig()` varias veces. Si en los ficheros aparecen elementos con las mismas claves, se sobrescribirán (o [fusionarán |dependency-injection:configuration#Merging] en el caso de las matrices). Un fichero insertado posteriormente tiene mayor prioridad que el anterior. +Si queremos agregar más archivos de configuración, podemos llamar a la función `addConfig()` varias veces. Si aparecen elementos con las mismas claves en los archivos, se sobrescribirán (o se [fusionarán |dependency-injection:configuration#Fusión] en el caso de arrays). El archivo incluido más tarde tiene mayor prioridad que el anterior. -El último paso consiste en crear un contenedor DI: +El último paso es crear el contenedor DI: ```php $container = $configurator->createContainer(); ``` -Y ya nos creará los objetos deseados. Por ejemplo, si estás utilizando la configuración para [Nette Database |database:configuration], puedes pedirle que cree conexiones a bases de datos: +Y este ya creará los objetos requeridos para nosotros. Por ejemplo, si está utilizando la configuración para [Nette Database|database:configuration], puede pedirle que cree conexiones a la base de datos: ```php $db = $container->getByType(Nette\Database\Connection::class); -// or +// o $explorer = $container->getByType(Nette\Database\Explorer::class); -// or when creating multiple connections +// o al crear múltiples conexiones $db = $container->getByName('database.main.connection'); ``` -¡Y ya puedes trabajar con la base de datos! +¡Y ahora ya puede trabajar con la base de datos! -Modo de desarrollo frente a modo de producción .[#toc-development-vs-production-mode] -------------------------------------------------------------------------------------- +Modo de desarrollo vs producción +-------------------------------- -En el modo de desarrollo, el contenedor se actualiza automáticamente cada vez que se modifican los archivos de configuración. En el modo de producción, sólo se genera una vez y los cambios no se comprueban. -Así, el modo de desarrollo está orientado a la máxima comodidad del programador, mientras que el modo de producción está orientado al rendimiento. +En el modo de desarrollo, el contenedor se actualiza automáticamente cada vez que cambian los archivos de configuración. En el modo de producción, se genera solo una vez y no se verifican los cambios. Por lo tanto, el modo de desarrollo se centra en la máxima comodidad del programador, mientras que el modo de producción se centra en el rendimiento y el despliegue en vivo. -La selección del modo se hace por autodetección, por lo que normalmente no hay necesidad de configurar o cambiar manualmente nada. El modo es desarrollo cuando la aplicación se ejecuta en un host local (es decir, la dirección IP `127.0.0.1` o `::1`) y no hay ningún proxy (es decir, su cabecera HTTP) presente. En caso contrario, se ejecuta en modo de producción. +La selección del modo se realiza mediante autodetección, por lo que generalmente no es necesario configurar nada ni cambiar manualmente. El modo es de desarrollo si la aplicación se ejecuta en localhost (es decir, dirección IP `127.0.0.1` o `::1`) y no hay proxy presente (es decir, su cabecera HTTP). De lo contrario, se ejecuta en modo de producción. -Si desea activar el modo de desarrollo en otros casos, como cuando los programadores acceden desde una dirección IP específica, utilice `setDebugMode()`: +Si queremos habilitar el modo de desarrollo también en otros casos, por ejemplo, para programadores que acceden desde una dirección IP específica, usamos `setDebugMode()`: ```php $configurator->setDebugMode('23.75.345.200'); -// an array of IP addresses can also be specified +// también se puede especificar un array de direcciones IP ``` -Recomendamos encarecidamente combinar la dirección IP con una cookie. Almacene un token secreto, por ejemplo `secret1234`, en la cookie `nette-debug`, y de esta forma habilitará el modo de desarrollo para los programadores que accedan desde una dirección IP específica y también tendrá el token mencionado en la cookie: +Recomendamos encarecidamente combinar la dirección IP con una cookie. Guardaremos un token secreto, por ejemplo, `secret1234`, en la cookie `nette-debug`, y de esta manera activaremos el modo de desarrollo para los programadores que acceden desde una dirección IP específica y que también tienen el token mencionado en la cookie: ```php $configurator->setDebugMode('secret1234@23.75.345.200'); ``` -También puedes desactivar el modo de desarrollo por completo, incluso para localhost: +También podemos desactivar completamente el modo de desarrollo, incluso para localhost: ```php $configurator->setDebugMode(false); ``` -Parámetros .[#toc-parameters] ------------------------------ +Parámetros +---------- -También puede utilizar parámetros en los archivos de configuración, que se definen [en la sección `parameters` |dependency-injection:configuration#parameters`]. +En los archivos de configuración, también puede usar parámetros, que se definen [en la sección `parameters` |dependency-injection:configuration#Parámetros]. También se pueden insertar desde el exterior utilizando el método `addDynamicParameters()`: @@ -95,7 +93,4 @@ $configurator->addDynamicParameters([ ]); ``` -El parámetro `projectId` puede referenciarse en la configuración con la notación `%projectId%`. - - -{{leftbar: nette:@menu-topics}} +Se puede hacer referencia al parámetro `projectId` en la configuración usando la notación `%projectId%`. diff --git a/bootstrap/es/@meta.texy b/bootstrap/es/@meta.texy new file mode 100644 index 0000000000..25d506cde9 --- /dev/null +++ b/bootstrap/es/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Documentación}} +{{leftbar: nette:@menu-topics}} diff --git a/bootstrap/fr/@home.texy b/bootstrap/fr/@home.texy index 393a254c76..25f662ac6c 100644 --- a/bootstrap/fr/@home.texy +++ b/bootstrap/fr/@home.texy @@ -1,93 +1,91 @@ -Comment charger le fichier de configuration -******************************************* +Nette Bootstrap +*************** .[perex] -Les différents composants de Nette sont configurés à l'aide de fichiers de configuration. Nous allons montrer comment charger ces fichiers. +Les composants individuels de Nette sont configurés à l'aide de fichiers de configuration. Nous allons montrer comment charger ces fichiers. .[tip] -Si vous utilisez l'ensemble du framework, il n'y a rien d'autre à faire. Dans le projet, vous avez un répertoire préparé à l'avance `config/` pour les fichiers de configuration, et le [chargeur d'application |application:bootstrap#DI Container Configuration] est responsable de leur chargement. -Cet article est destiné aux utilisateurs qui n'utilisent qu'une seule bibliothèque Nette et qui souhaitent tirer parti des fichiers de configuration. +Si vous utilisez le framework complet, vous n'avez rien d'autre à faire. Votre projet dispose d'un répertoire `config/` préparé pour les fichiers de configuration, et leur chargement est géré par [le chargeur de l'application |application:bootstrapping#Configuration du Conteneur DI]. Cet article s'adresse aux utilisateurs qui n'utilisent qu'une seule bibliothèque Nette et souhaitent profiter des fonctionnalités des fichiers de configuration. -Les fichiers de configuration sont généralement écrits en [NEON |neon:format] et il est préférable de les éditer dans des [éditeurs qui le supportent |best-practices:editors-and-tools#ide-editor]. Ils peuvent être considérés comme des instructions sur la façon de **créer et configurer** des objets. Ainsi, le résultat du chargement d'une configuration sera ce que l'on appelle une fabrique, c'est-à-dire un objet qui créera à la demande d'autres objets que vous souhaitez utiliser. Par exemple, une connexion à une base de données, etc. +Les fichiers de configuration sont généralement écrits au [format NEON |neon:format] et sont mieux édités dans des [éditeurs qui le prennent en charge |best-practices:editors-and-tools#Éditeur IDE]. Ils peuvent être considérés comme des instructions sur la façon de **créer et configurer** des objets. Ainsi, le résultat du chargement de la configuration sera une soi-disant factory, qui est un objet qui créera d'autres objets que nous voulons utiliser à la demande. Par exemple, une connexion à une base de données, etc. -Cette fabrique est également appelée un *conteneur d'injection de dépendances* (conteneur DI) et si vous êtes intéressé par les détails, lisez le chapitre sur l'[injection de dépendances |dependency-injection:]. +Cette factory est également appelée *conteneur d'injection de dépendances* (conteneur DI), et si vous êtes intéressé par les détails, lisez le chapitre sur [l'injection de dépendances |dependency-injection:]. -Le chargement de la configuration et la création du conteneur sont gérés par la classe [api:Nette\Bootstrap\Configurator], nous allons donc d'abord installer son paquetage `nette/bootstrap`: +Le chargement de la configuration et la création du conteneur sont gérés par la classe [api:Nette\Bootstrap\Configurator], nous allons donc d'abord installer son paquet `nette/bootstrap` : ```shell composer require nette/bootstrap ``` -Et créer une instance de la classe `Configurator`. Comme le conteneur DI généré sera mis en cache sur le disque, vous devez définir le chemin d'accès au répertoire où il sera enregistré : +Et nous créons une instance de la classe `Configurator`. Comme le conteneur DI généré sera mis en cache sur le disque, il est nécessaire de définir le chemin d'accès au répertoire où il sera stocké : ```php $configurator = new Nette\Bootstrap\Configurator; $configurator->setTempDirectory(__DIR__ . '/temp'); ``` -Sous Linux ou macOS, définissez les [droits d'écriture |nette:troubleshooting#Setting directory permissions] pour le répertoire `temp/`. +Sous Linux ou macOS, définissez les [droits d'écriture |nette:troubleshooting#Configuration des permissions de répertoire] pour le répertoire `temp/`. -Et nous en arrivons aux fichiers de configuration eux-mêmes. Ils sont chargés à l'aide de `addConfig()`: +Et nous arrivons aux fichiers de configuration eux-mêmes. Nous les chargeons en utilisant `addConfig()` : ```php $configurator->addConfig(__DIR__ . '/database.neon'); ``` -Si vous voulez ajouter d'autres fichiers de configuration, vous pouvez appeler la fonction `addConfig()` plusieurs fois. Si des éléments ayant les mêmes clés apparaissent dans les fichiers, ils seront écrasés (ou [fusionnés |dependency-injection:configuration#Merging] dans le cas de tableaux). Un fichier inséré ultérieurement a une priorité plus élevée que le précédent. +Si nous voulons ajouter plusieurs fichiers de configuration, nous pouvons appeler la fonction `addConfig()` plusieurs fois. Si des éléments avec les mêmes clés apparaissent dans les fichiers, ils seront écrasés (ou dans le cas de tableaux, [fusionnés |dependency-injection:configuration#Fusion]). Un fichier inclus plus tard a une priorité plus élevée que le précédent. -La dernière étape consiste à créer un conteneur DI : +La dernière étape consiste à créer le conteneur DI : ```php $container = $configurator->createContainer(); ``` -Et il va déjà créer les objets souhaités pour nous. Par exemple, si vous utilisez la configuration pour [Nette Database |database:configuration], vous pouvez lui demander de créer des connexions de base de données : +Et il créera les objets requis pour nous. Par exemple, si vous utilisez la configuration pour [Nette Database |database:configuration], vous pouvez lui demander de créer des connexions à la base de données : ```php $db = $container->getByType(Nette\Database\Connection::class); // ou $explorer = $container->getByType(Nette\Database\Explorer::class); -// ou lors de la création de connexions multiples +// ou lors de la création de plusieurs connexions $db = $container->getByName('database.main.connection'); ``` Et maintenant vous pouvez travailler avec la base de données ! -Mode développement et mode production .[#toc-development-vs-production-mode] ----------------------------------------------------------------------------- +Mode développeur vs mode production +----------------------------------- -En mode développement, le conteneur est automatiquement mis à jour chaque fois que les fichiers de configuration sont modifiés. En mode production, il est généré une seule fois et les modifications ne sont pas vérifiées. -Le mode développement vise donc à faciliter au maximum la tâche du programmeur, tandis que le mode production vise les performances. +En mode développeur, le conteneur est automatiquement mis à jour à chaque fois que les fichiers de configuration sont modifiés. En mode production, il n'est généré qu'une seule fois et les modifications ne sont pas vérifiées. Le mode développeur est donc axé sur le confort maximal du programmeur, tandis que le mode production est axé sur la performance et le déploiement en production. -La sélection du mode se fait par autodétection, il n'est donc généralement pas nécessaire de configurer ou de changer manuellement quoi que ce soit. Le mode développement est utilisé lorsque l'application est exécutée sur un hôte local (c'est-à-dire l'adresse IP `127.0.0.1` ou `::1`) et qu'aucun proxy (c'est-à-dire son en-tête HTTP) n'est présent. Sinon, elle s'exécute en mode production. +La sélection du mode se fait par autodétection, il n'est donc généralement pas nécessaire de configurer quoi que ce soit ou de basculer manuellement. Le mode est développeur si l'application est exécutée sur localhost (c'est-à-dire l'adresse IP `127.0.0.1` ou `::1`) et qu'aucun proxy n'est présent (c'est-à-dire son en-tête HTTP). Sinon, elle s'exécute en mode production. -Si vous souhaitez activer le mode développement dans d'autres cas, comme les programmeurs accédant à partir d'une adresse IP spécifique, utilisez `setDebugMode()`: +Si nous voulons activer le mode développeur dans d'autres cas, par exemple pour les programmeurs accédant depuis une adresse IP spécifique, nous utilisons `setDebugMode()` : ```php $configurator->setDebugMode('23.75.345.200'); -// un tableau d'adresses IP peut également être spécifié +// vous pouvez également spécifier un tableau d'adresses IP ``` -Nous recommandons vivement de combiner l'adresse IP avec un cookie. Stockez un jeton secret, par exemple `secret1234`, dans le cookie `nette-debug`, et de cette façon vous activez le mode de développement pour les programmeurs accédant à partir d'une adresse IP spécifique et ayant également le jeton mentionné dans le cookie : +Nous recommandons vivement de combiner l'adresse IP avec un cookie. Nous stockons un jeton secret, par exemple `secret1234`, dans le cookie `nette-debug`, et activons ainsi le mode développeur pour les programmeurs accédant depuis une adresse IP spécifique et ayant également le jeton mentionné dans le cookie : ```php $configurator->setDebugMode('secret1234@23.75.345.200'); ``` -Vous pouvez également désactiver complètement le mode de développement, même pour localhost : +Nous pouvons également désactiver complètement le mode développeur, même pour localhost : ```php $configurator->setDebugMode(false); ``` -Paramètres .[#toc-parameters] ------------------------------ +Paramètres +---------- -Vous pouvez également utiliser des paramètres dans les fichiers de configuration, qui sont définis dans la [section `parameters` |dependency-injection:configuration#parameters`]. +Dans les fichiers de configuration, vous pouvez également utiliser des paramètres, qui sont définis [dans la section `parameters` |dependency-injection:configuration#Paramètres]. -Ils peuvent également être insérés de l'extérieur à l'aide de la méthode `addDynamicParameters()`: +Ils peuvent également être insérés de l'extérieur en utilisant la méthode `addDynamicParameters()` : ```php $configurator->addDynamicParameters([ @@ -95,7 +93,4 @@ $configurator->addDynamicParameters([ ]); ``` -Le paramètre `projectId` peut être référencé dans la configuration avec la notation `%projectId%`. - - -{{leftbar: nette:@menu-topics}} +Le paramètre `projectId` peut être référencé dans la configuration en utilisant la notation `%projectId%`. diff --git a/bootstrap/fr/@meta.texy b/bootstrap/fr/@meta.texy new file mode 100644 index 0000000000..95ec8a4ef6 --- /dev/null +++ b/bootstrap/fr/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Documentation Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/bootstrap/hu/@home.texy b/bootstrap/hu/@home.texy index 2fcead18eb..86a0ca10df 100644 --- a/bootstrap/hu/@home.texy +++ b/bootstrap/hu/@home.texy @@ -1,39 +1,38 @@ -Konfigurációs fájl betöltése -**************************** +Nette Bootstrap +*************** .[perex] -A Nette egyes összetevői konfigurációs fájlok segítségével kerülnek beállításra. Megmutatjuk, hogyan lehet betölteni ezeket a fájlokat. +A Nette egyes részeit konfigurációs fájlok segítségével állítjuk be. Megmutatjuk, hogyan kell ezeket a fájlokat betölteni. .[tip] -Ha a teljes keretrendszert használja, nincs szükség semmi másra. A projektben van egy előre elkészített `config/` könyvtár a konfigurációs fájloknak, és az [alkalmazás betöltője |application:bootstrap#DI Container Configuration] felelős ezek betöltéséért. -Ez a cikk azoknak a felhasználóknak szól, akik csak egy Nette könyvtárat használnak, és szeretnék kihasználni a konfigurációs fájlok előnyeit. +Ha a teljes keretrendszert használja, nincs szükség további teendőkre. A projektben van egy előkészített `config/` könyvtár a konfigurációs fájlok számára, és ezek betöltéséért az [alkalmazás betöltő |application:bootstrapping#DI konténer konfigurálása] felelős. Ez a cikk azoknak a felhasználóknak szól, akik csak egy Nette könyvtárat használnak, és ki szeretnék használni a konfigurációs fájlok lehetőségeit. -A konfigurációs fájlok általában [NEON |neon:format] nyelven íródnak, és a legjobb, ha [olyan szerkesztőkben |best-practices:editors-and-tools#ide-editor] szerkesztjük őket, amelyek támogatják ezt a nyelvet. Úgy lehet rájuk gondolni, mint az objektumok **létrehozására és konfigurálására** vonatkozó utasításokra. Így a konfiguráció betöltésének eredménye egy úgynevezett factory lesz, ami egy olyan objektum, amely igény szerint létrehozza a használni kívánt más objektumokat. Például egy adatbázis-kapcsolatot stb. +A konfigurációs fájlokat általában [NEON formátumban|neon:format] írják, és a legjobban [az azt támogató szerkesztőkben |best-practices:editors-and-tools#IDE szerkesztő] lehet szerkeszteni. Útmutatóként foghatók fel, hogyan **hozzunk létre és konfiguráljunk** objektumokat. Tehát a konfiguráció betöltésének eredménye egy úgynevezett factory lesz, ami egy olyan objektum, amely kérésre létrehozza számunkra a használni kívánt további objektumokat. Például adatbázis-kapcsolatokat stb. -Ezt a gyárat *függőségi injektáló konténernek* (DI konténer) is nevezik, és ha érdekelnek a részletek, olvassa el a [függőségi injektálásról |dependency-injection:] szóló fejezetet. +Ezt a factory-t *dependency injection konténernek* (DI konténer) is nevezik, és ha érdeklik a részletek, olvassa el a [dependency injection |dependency-injection:] fejezetet. -A konfiguráció betöltését és a konténer létrehozását a [api:Nette\Bootstrap\Configurator] osztály kezeli, ezért először telepítjük a `nette/bootstrap` csomagját: +A konfiguráció betöltését és a konténer létrehozását az [api:Nette\Bootstrap\Configurator] osztály végzi, ezért először telepítjük a `nette/bootstrap` csomagját: ```shell composer require nette/bootstrap ``` -És létrehozzuk a `Configurator` osztály egy példányát. Mivel a létrehozott DI konténer a lemezre lesz gyorsítótárazva, meg kell adnunk annak a könyvtárnak az elérési útvonalát, ahová a konténer el lesz mentve: +És létrehozunk egy `Configurator` osztály példányt. Mivel a generált DI konténer a lemezre lesz gyorsítótárazva, meg kell adni annak a könyvtárnak az elérési útját, ahová menteni fogja: ```php $configurator = new Nette\Bootstrap\Configurator; $configurator->setTempDirectory(__DIR__ . '/temp'); ``` -Linuxon vagy macOS-en állítsa be a `temp/` könyvtár [írási engedélyeit |nette:troubleshooting#Setting directory permissions]. +Linuxon vagy macOS-en állítson be [írási jogokat |nette:troubleshooting#Könyvtárjogosultságok beállítása] a `temp/` könyvtárnak. -És elérkeztünk magukhoz a konfigurációs fájlokhoz. Ezek betöltése a `addConfig()` segítségével történik: +És elérkeztünk magukhoz a konfigurációs fájlokhoz. Ezeket az `addConfig()` segítségével töltjük be: ```php $configurator->addConfig(__DIR__ . '/database.neon'); ``` -Ha több konfigurációs fájlt szeretne hozzáadni, akkor a `addConfig()` függvényt többször is meghívhatja. Ha a fájlokban azonos kulcsú elemek jelennek meg, akkor azok felülíródnak (vagy tömbök esetén [összevonásra kerülnek |dependency-injection:configuration#Merging] ). A később beillesztett fájlnak magasabb prioritása van, mint az előzőnek. +Ha több konfigurációs fájlt szeretnénk hozzáadni, többször is meghívhatjuk az `addConfig()` függvényt. Ha a fájlokban azonos kulcsú elemek jelennek meg, azok felülíródnak (vagy tömbök esetén [összevonódnak |dependency-injection:configuration#Összefésülés]). A később hozzáadott fájl magasabb prioritással rendelkezik, mint az előző. Az utolsó lépés a DI konténer létrehozása: @@ -41,7 +40,7 @@ Az utolsó lépés a DI konténer létrehozása: $container = $configurator->createContainer(); ``` -És ez máris létrehozza számunkra a kívánt objektumokat. Ha például a [Nette Database |database:configuration] konfigurációját használjuk, akkor megkérhetjük, hogy hozzon létre adatbázis-kapcsolatokat: +És ez már létrehozza számunkra a kívánt objektumokat. Ha például a [Nette Database|database:configuration] konfigurációját használja, kérheti tőle adatbázis-kapcsolatok létrehozását: ```php $db = $container->getByType(Nette\Database\Connection::class); @@ -51,43 +50,42 @@ $explorer = $container->getByType(Nette\Database\Explorer::class); $db = $container->getByName('database.main.connection'); ``` -És már dolgozhat is az adatbázissal! +És most már dolgozhat az adatbázissal! -Fejlesztés vs. Termelési mód .[#toc-development-vs-production-mode] -------------------------------------------------------------------- +Fejlesztői vs. éles üzemmód +--------------------------- -Fejlesztési üzemmódban a konténer automatikusan frissül, amikor a konfigurációs fájlok változnak. Termelési üzemmódban csak egyszer generálódik, és a módosítások nem kerülnek ellenőrzésre. -A fejlesztői mód tehát a programozó maximális kényelmét, a termelési mód a teljesítményt célozza. +Fejlesztői módban a konténer automatikusan frissül minden konfigurációs fájl módosításakor. Éles (produkciós) módban csak egyszer generálódik, és a változásokat nem ellenőrzi. A fejlesztői mód tehát a programozó maximális kényelmére összpontosít, az éles mód a teljesítményre és az éles bevetésre. -A mód kiválasztása automatikus felismeréssel történik, így általában nincs szükség konfigurálásra vagy kézi kapcsolásra. A mód fejlesztői, ha az alkalmazás egy localhoston fut (azaz a `127.0.0.1` vagy a `::1`) IP-címen, és nincs proxy (azaz annak HTTP fejléce). Egyébként termelési üzemmódban fut. +Az üzemmód kiválasztása automatikus felismeréssel történik, így általában nincs szükség semmit konfigurálni vagy manuálisan váltani. Az üzemmód fejlesztői, ha az alkalmazás localhoston fut (azaz IP-cím `127.0.0.1` vagy `::1`), és nincs jelen proxy (azaz annak HTTP fejléce). Ellenkező esetben éles módban fut. -Ha más esetekben is engedélyezni szeretné a fejlesztési üzemmódot, például ha a programozók egy adott IP-címről férnek hozzá, használja a `setDebugMode()` címet: +Ha engedélyezni szeretnénk a fejlesztői módot más esetekben is, például egy adott IP-címről hozzáférő programozók számára, használjuk a `setDebugMode()` metódust: ```php $configurator->setDebugMode('23.75.345.200'); -// IP-címek tömbje is megadható +// megadható IP-címek tömbje is ``` -Mindenképpen javasoljuk az IP-cím és egy cookie kombinálását. Tároljon egy titkos tokent, pl. `secret1234`, a `nette-debug` cookie-ban, és így engedélyezi a fejlesztési módot a programozók számára, akik egy adott IP-címről lépnek be, és a token is szerepel a cookie-ban: +Mindenképpen javasoljuk az IP-cím és egy cookie kombinálását. A `nette-debug` cookie-ba mentsünk egy titkos tokent, pl. `secret1234`, és így aktiváljuk a fejlesztői módot az adott IP-címről hozzáférő és a cookie-ban említett tokennel rendelkező programozók számára: ```php $configurator->setDebugMode('secret1234@23.75.345.200'); ``` -A fejlesztői üzemmódot akár teljesen is letilthatja, még a localhost számára is: +A fejlesztői módot teljesen ki is kapcsolhatjuk, még localhost esetén is: ```php $configurator->setDebugMode(false); ``` -Paraméterek: .[#toc-parameters] -------------------------------- +Paraméterek +----------- -A konfigurációs fájlokban is használhat paramétereket, amelyeket a <m id=37> `parameters` szakaszban definiálhat. +A konfigurációs fájlokban paramétereket is használhat, amelyeket [a `parameters` szekcióban |dependency-injection:configuration#Paraméterek] definiálunk. -Kívülről is beilleszthetők a</m> `addDynamicParameters()` módszerrel: +Ezeket kívülről is beilleszthetjük az `addDynamicParameters()` metódussal: ```php $configurator->addDynamicParameters([ @@ -95,7 +93,4 @@ $configurator->addDynamicParameters([ ]); ``` -A `projectId` paraméterre a konfigurációban a `%projectId%` jelöléssel lehet hivatkozni. - - -{{leftbar: nette:@menu-topics}} +A `projectId` paraméterre a konfigurációban a `%projectId%` jelöléssel hivatkozhatunk. diff --git a/bootstrap/hu/@meta.texy b/bootstrap/hu/@meta.texy new file mode 100644 index 0000000000..c00a2158aa --- /dev/null +++ b/bootstrap/hu/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette dokumentáció}} +{{leftbar: nette:@menu-topics}} diff --git a/bootstrap/it/@home.texy b/bootstrap/it/@home.texy index 8beb493b85..72a8b4d1f0 100644 --- a/bootstrap/it/@home.texy +++ b/bootstrap/it/@home.texy @@ -1,93 +1,91 @@ -Come caricare il file di configurazione -*************************************** +Nette Bootstrap +*************** .[perex] -I singoli componenti di Nette sono configurati tramite file di configurazione. Mostriamo come caricare questi file. +Le singole parti di Nette vengono impostate tramite file di configurazione. Vediamo come caricare questi file. .[tip] -Se si utilizza l'intero framework, non è necessario fare altro. Nel progetto, c'è una cartella preconfezionata `config/` per i file di configurazione e il [caricatore dell'applicazione |application:bootstrap#DI Container Configuration] è responsabile del loro caricamento. -Questo articolo è rivolto agli utenti che utilizzano una sola libreria Nette e vogliono sfruttare i file di configurazione. +Se si utilizza l'intero framework, non è necessario fare altro. Nel progetto è presente una directory `config/` preimpostata per i file di configurazione, e il loro caricamento è gestito dal [bootloader dell'applicazione |application:bootstrapping#Configurazione del Container DI]. Questo articolo è per gli utenti che utilizzano solo una libreria Nette e vogliono sfruttare le possibilità dei file di configurazione. -I file di configurazione sono di solito scritti in [NEON |neon:format] e sono meglio modificati in [editor che lo supportano |best-practices:editors-and-tools#ide-editor]. Possono essere considerati come istruzioni su come **creare e configurare** gli oggetti. Pertanto, il risultato del caricamento di una configurazione sarà un cosiddetto factory, ovvero un oggetto che creerà su richiesta altri oggetti che si desidera utilizzare. Per esempio, una connessione al database, ecc. +I file di configurazione sono solitamente scritti in [formato NEON|neon:format] e si modificano al meglio negli [editor con supporto per esso |best-practices:editors-and-tools#Editor IDE]. Possono essere visti come istruzioni su come **creare e configurare** oggetti. Quindi, il risultato del caricamento della configurazione sarà una cosiddetta factory, che è un oggetto che, su richiesta, ci creerà altri oggetti che vogliamo utilizzare. Ad esempio, connessioni al database, ecc. -Questo factory è anche chiamato *contenitore di iniezione di dipendenza* (DI container) e, se si è interessati ai dettagli, si può leggere il capitolo sull'[iniezione di dipendenza |dependency-injection:]. +Questa factory è anche chiamata *dependency injection container* (container DI) e se sei interessato ai dettagli, leggi il capitolo sulla [dependency injection |dependency-injection:]. -Il caricamento della configurazione e la creazione del contenitore sono gestiti dalla classe [api:Nette\Bootstrap\Configurator], quindi installeremo prima il suo pacchetto `nette/bootstrap`: +Il caricamento della configurazione e la creazione del container sono gestiti dalla classe [api:Nette\Bootstrap\Configurator], quindi installiamo prima il suo pacchetto `nette/bootstrap`: ```shell composer require nette/bootstrap ``` -e creare un'istanza della classe `Configurator`. Poiché il contenitore DI generato verrà memorizzato nella cache del disco, è necessario impostare il percorso della cartella in cui verrà salvato: +E creiamo un'istanza della classe `Configurator`. Poiché il container DI generato verrà memorizzato nella cache su disco, è necessario impostare il percorso della directory in cui verrà salvato: ```php $configurator = new Nette\Bootstrap\Configurator; $configurator->setTempDirectory(__DIR__ . '/temp'); ``` -Su Linux o macOS, impostare i [permessi di scrittura |nette:troubleshooting#Setting directory permissions] per la cartella `temp/`. +Su Linux o macOS, imposta i [permessi di scrittura |nette:troubleshooting#Impostazione dei permessi delle directory] per la directory `temp/`. -E veniamo ai file di configurazione veri e propri. Questi vengono caricati usando `addConfig()`: +E arriviamo ai file di configurazione stessi. Li carichiamo usando `addConfig()`: ```php $configurator->addConfig(__DIR__ . '/database.neon'); ``` -Se si vogliono aggiungere altri file di configurazione, si può chiamare la funzione `addConfig()` più volte. Se nei file compaiono elementi con le stesse chiavi, questi verranno sovrascritti (o [uniti nel |dependency-injection:configuration#Merging] caso di array). Un file inserito successivamente ha una priorità maggiore rispetto al precedente. +Se vogliamo aggiungere più file di configurazione, possiamo chiamare la funzione `addConfig()` più volte. Se nei file compaiono elementi con le stesse chiavi, verranno sovrascritti (o, nel caso degli array, [uniti |dependency-injection:configuration#Unione]). Il file inserito successivamente ha una priorità maggiore rispetto al precedente. -L'ultimo passo consiste nel creare un contenitore DI: +L'ultimo passo è la creazione del container DI: ```php $container = $configurator->createContainer(); ``` -E questo creerà già gli oggetti desiderati per noi. Ad esempio, se si utilizza la configurazione per [Nette Database |database:configuration], si può chiedere di creare le connessioni al database: +E questo ci creerà già gli oggetti richiesti. Se, ad esempio, utilizzi la configurazione per [Nette Database|database:configuration], puoi chiedergli di creare le connessioni al database: ```php $db = $container->getByType(Nette\Database\Connection::class); // oppure $explorer = $container->getByType(Nette\Database\Explorer::class); -// o quando si creano connessioni multiple +// oppure creando più connessioni $db = $container->getByName('database.main.connection'); ``` -E ora si può lavorare con il database! +E ora puoi già lavorare con il database! -Modalità di sviluppo e modalità di produzione .[#toc-development-vs-production-mode] ------------------------------------------------------------------------------------- +Modalità sviluppatore vs produzione +----------------------------------- -In modalità di sviluppo, il contenitore viene aggiornato automaticamente ogni volta che i file di configurazione vengono modificati. In modalità di produzione, viene generato solo una volta e le modifiche non vengono controllate. -Quindi la modalità sviluppatore mira alla massima comodità per il programmatore, mentre la modalità produzione mira alle prestazioni. +In modalità sviluppatore, il container si aggiorna automaticamente ad ogni modifica dei file di configurazione. In modalità produzione, viene generato solo una volta e le modifiche non vengono controllate. La modalità sviluppatore è quindi focalizzata sulla massima comodità del programmatore, la modalità produzione sulle prestazioni e sulla distribuzione in produzione. -La selezione della modalità avviene tramite il rilevamento automatico, quindi di solito non è necessario configurare o cambiare manualmente nulla. La modalità è di sviluppo quando l'applicazione è in esecuzione su un localhost (cioè, l'indirizzo IP `127.0.0.1` o `::1`) e non è presente alcun proxy (cioè, la sua intestazione HTTP). Altrimenti viene eseguita in modalità di produzione. +La scelta della modalità avviene tramite autodetect, quindi di solito non è necessario configurare nulla o passare manualmente. La modalità è sviluppatore se l'applicazione viene eseguita su localhost (cioè indirizzo IP `127.0.0.1` o `::1`) e non è presente una proxy (cioè la sua intestazione HTTP). Altrimenti, viene eseguita in modalità produzione. -Se si vuole abilitare la modalità di sviluppo in altri casi, come per i programmatori che accedono da un indirizzo IP specifico, utilizzare `setDebugMode()`: +Se vogliamo abilitare la modalità sviluppatore anche in altri casi, ad esempio per i programmatori che accedono da un indirizzo IP specifico, usiamo `setDebugMode()`: ```php $configurator->setDebugMode('23.75.345.200'); // è possibile specificare anche un array di indirizzi IP ``` -Si consiglia di combinare l'indirizzo IP con un cookie. Memorizzate un token segreto, ad esempio `secret1234`, nel cookie `nette-debug`: in questo modo abiliterete la modalità di sviluppo per i programmatori che accedono da un indirizzo IP specifico e avrete anche il token indicato nel cookie: +Raccomandiamo vivamente di combinare l'indirizzo IP con un cookie. Nel cookie `nette-debug` salviamo un token segreto, ad esempio `secret1234`, e in questo modo attiviamo la modalità sviluppatore per i programmatori che accedono da un indirizzo IP specifico e che hanno anche il token menzionato nel cookie: ```php $configurator->setDebugMode('secret1234@23.75.345.200'); ``` -Si può anche disabilitare del tutto la modalità sviluppatore, anche per localhost: +Possiamo anche disabilitare completamente la modalità sviluppatore, anche per localhost: ```php $configurator->setDebugMode(false); ``` -Parametri .[#toc-parameters] ----------------------------- +Parametri +--------- -Nei file di configurazione si possono usare anche dei parametri, definiti nella [sezione `parameters` |dependency-injection:configuration#parameters`]. +Nei file di configurazione è possibile utilizzare anche parametri, che vengono definiti [nella sezione `parameters` |dependency-injection:configuration#Parametri]. -Possono anche essere inseriti dall'esterno con il metodo `addDynamicParameters()`: +Possono anche essere inseriti dall'esterno tramite il metodo `addDynamicParameters()`: ```php $configurator->addDynamicParameters([ @@ -95,7 +93,4 @@ $configurator->addDynamicParameters([ ]); ``` -Il parametro `projectId` può essere referenziato nella configurazione con la notazione `%projectId%`. - - -{{leftbar: nette:@menu-topics}} +Al parametro `projectId` si può fare riferimento nella configurazione tramite la notazione `%projectId%`. diff --git a/bootstrap/it/@meta.texy b/bootstrap/it/@meta.texy new file mode 100644 index 0000000000..9d19e7312c --- /dev/null +++ b/bootstrap/it/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Documentazione Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/bootstrap/ja/@home.texy b/bootstrap/ja/@home.texy new file mode 100644 index 0000000000..80ea76d567 --- /dev/null +++ b/bootstrap/ja/@home.texy @@ -0,0 +1,96 @@ +Nette Bootstrap +*************** + +.[perex] +Nette の個々のコンポーネントは設定ファイルを使用して設定します。これらのファイルを読み込む方法を示します。 + +.[tip] +フレームワーク全体を使用している場合、追加の作業は必要ありません。プロジェクトには設定ファイル用の `config/` ディレクトリが用意されており、それらの読み込みは[アプリケーションブートローダー |application:bootstrapping#DIコンテナの設定]が担当します。 この記事は、Nette のライブラリを 1 つだけ使用し、設定ファイルの機能を利用したいユーザー向けです。 + +設定ファイルは通常[NEON 形式|neon:format]で記述され、[サポートされているエディタ |best-practices:editors-and-tools#IDEエディタ]で編集するのが最適です。これらはオブジェクトを**作成および設定**する方法の指示として理解できます。したがって、設定の読み込み結果はいわゆるファクトリであり、これはリクエストに応じて使用したい他のオブジェクト(データベース接続など)を作成するオブジェクトです。 + +このファクトリは*依存関係注入コンテナ*(DI コンテナ)とも呼ばれ、詳細に興味がある場合は[依存関係注入 |dependency-injection:]に関する章をお読みください。 + +設定の読み込みとコンテナの作成は[api:Nette\Bootstrap\Configurator]クラスが行うため、まずそのパッケージ `nette/bootstrap` をインストールします: + +```shell +composer require nette/bootstrap +``` + +そして `Configurator` クラスのインスタンスを作成します。生成されたDIコンテナはディスクにキャッシュされるため、保存先のディレクトリパスを設定する必要があります: + +```php +$configurator = new Nette\Bootstrap\Configurator; +$configurator->setTempDirectory(__DIR__ . '/temp'); +``` + +LinuxまたはmacOSでは、`temp/` ディレクトリに[書き込み権限を設定 |nette:troubleshooting#ディレクトリ権限の設定]してください。 + +そして、設定ファイル自体に移ります。これらは `addConfig()` を使用して読み込みます: + +```php +$configurator->addConfig(__DIR__ . '/database.neon'); +``` + +複数の設定ファイルを追加したい場合は、`addConfig()` 関数を複数回呼び出すことができます。ファイル内に同じキーを持つ要素が現れた場合、それらは上書きされます(または配列の場合は[マージされます |dependency-injection:configuration#マージ])。後から読み込まれたファイルは前のファイルよりも高い優先度を持ちます。 + +最後のステップはDIコンテナの作成です: + +```php +$container = $configurator->createContainer(); +``` + +そして、それは必要なオブジェクトを作成します。たとえば、[Nette Database|database:configuration]の設定を使用している場合、データベース接続の作成をリクエストできます: + +```php +$db = $container->getByType(Nette\Database\Connection::class); +// または +$explorer = $container->getByType(Nette\Database\Explorer::class); +// または複数の接続を作成する場合 +$db = $container->getByName('database.main.connection'); +``` + +これでデータベースを操作できます! + + +開発モード vs プロダクションモード +------------------- + +開発モードでは、設定ファイルが変更されるたびにコンテナが自動的に更新されます。プロダクションモードでは、一度だけ生成され、変更はチェックされません。 したがって、開発モードはプログラマの最大限の利便性に焦点を当てており、プロダクションモードはパフォーマンスと本番展開に焦点を当てています。 + +モードの選択は自動検出によって行われるため、通常は何も設定したり手動で切り替えたりする必要はありません。アプリケーションがlocalhost(つまりIPアドレス `127.0.0.1` または `::1`)で実行され、プロキシが存在しない(つまりそのHTTPヘッダーがない)場合、モードは開発モードになります。それ以外の場合はプロダクションモードで実行されます。 + +特定のIPアドレスからアクセスするプログラマなど、他の場合に開発モードを有効にしたい場合は、`setDebugMode()` を使用します: + +```php +$configurator->setDebugMode('23.75.345.200'); +// IPアドレスの配列も指定できます +``` + +IPアドレスとCookieを組み合わせることを強くお勧めします。`nette-debug` Cookieに秘密のトークン(例:`secret1234`)を保存し、この方法で特定のIPアドレスからアクセスし、かつCookieに言及されたトークンを持つプログラマに対して開発モードを有効にします: + +```php +$configurator->setDebugMode('secret1234@23.75.345.200'); +``` + +localhostに対しても、開発モードを完全に無効にすることもできます: + +```php +$configurator->setDebugMode(false); +``` + + +パラメータ +----- + +設定ファイルでは、[`parameters` セクション |dependency-injection:configuration#パラメータ]で定義されるパラメータも使用できます。 + +これらは `addDynamicParameters()` メソッドを使用して外部から挿入することもできます: + +```php +$configurator->addDynamicParameters([ + 'remoteIp' => $_SERVER['REMOTE_ADDR'], +]); +``` + +パラメータ `projectId` は、設定内で `%projectId%` と記述することで参照できます。 diff --git a/bootstrap/ja/@meta.texy b/bootstrap/ja/@meta.texy new file mode 100644 index 0000000000..7d67dcb7b8 --- /dev/null +++ b/bootstrap/ja/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette ドキュメンテーション}} +{{leftbar: nette:@menu-topics}} diff --git a/bootstrap/pl/@home.texy b/bootstrap/pl/@home.texy index e75ba0a14f..5089994f3b 100644 --- a/bootstrap/pl/@home.texy +++ b/bootstrap/pl/@home.texy @@ -1,47 +1,46 @@ -Jak załadować plik konfiguracyjny -********************************* +Nette Bootstrap +*************** .[perex] -Poszczególne składniki Nette konfigurujemy za pomocą plików konfiguracyjnych. Pokażemy, jak załadować te pliki. +Poszczególne części Nette ustawiamy za pomocą plików konfiguracyjnych. Pokażemy, jak te pliki wczytywać. .[tip] -Jeśli używasz całego frameworka, nie ma potrzeby robić nic więcej. Projekt posiada wstępnie zbudowany katalog dla plików konfiguracyjnych, `config/`, a za ich załadowanie odpowiada [program ładujący aplikacje |application:bootstrap#DI-Container-Configuration]. -Ten artykuł jest przeznaczony dla użytkowników, którzy używają tylko jednej biblioteki Nette i chcą skorzystać z plików konfiguracyjnych. +Jeśli używasz całego frameworka, nie trzeba nic więcej robić. W projekcie masz przygotowany katalog `config/` na pliki konfiguracyjne, a ich wczytywaniem zajmuje się [bootloader aplikacji |application:bootstrapping#Konfiguracja kontenera DI]. Ten artykuł jest dla użytkowników, którzy używają tylko jednej biblioteki Nette i chcą wykorzystać możliwości plików konfiguracyjnych. -Pliki konfiguracyjne są zazwyczaj zapisane w [formacie NEON |neon:format] i najlepiej edytować je w [edytorach, które go obsługują |best-practices:editors-and-tools#IDE-Editor]. Można o nich myśleć jako o instrukcjach, jak **tworzyć i konfigurować** obiekty. Tak więc wynikiem załadowania konfiguracji będzie tzw. fabryka, czyli obiekt, który na żądanie będzie tworzył inne obiekty, których chcemy użyć. Na przykład połączenie z bazą danych itp. +Pliki konfiguracyjne zazwyczaj zapisuje się w [formacie NEON|neon:format] i najlepiej edytuje się je w [edytorach z jego obsługą |best-practices:editors-and-tools#Edytor IDE]. Można je rozumieć jako instrukcje, jak **tworzyć i konfigurować** obiekty. Zatem wynikiem wczytania konfiguracji będzie tzw. fabryka, czyli obiekt, który na żądanie utworzy nam kolejne obiekty, których chcemy używać. Na przykład połączenie z bazą danych itp. -Fabrykę tę nazywa się również *kontenerem wstrzykiwania zależności* (kontenerem DI), a jeśli interesują Cię szczegóły, przeczytaj rozdział o [wstrzykiwaniu zależności |dependency-injection:]. +Ta fabryka nazywana jest również *kontenerem dependency injection* (kontenerem DI), a jeśli interesują Cię szczegóły, przeczytaj rozdział o [dependency injection |dependency-injection:]. -Wczytywaniem konfiguracji i tworzeniem kontenera zajmuje się klasa [api:Nette\Bootstrap\Configurator], więc najpierw zainstalujemy jej pakiet `nette/bootstrap`: +Wczytanie konfiguracji i utworzenie kontenera zapewnia klasa [api:Nette\Bootstrap\Configurator], więc najpierw zainstalujemy jej pakiet `nette/bootstrap`: ```shell composer require nette/bootstrap ``` -I utwórz instancję klasy `Configurator` Ponieważ wygenerowany kontener DI będzie buforowany na dysku, konieczne jest ustawienie ścieżki do katalogu, w którym będzie przechowywany: +I tworzymy instancję klasy `Configurator`. Ponieważ wygenerowany kontener DI będzie buforowany na dysku, konieczne jest ustawienie ścieżki do katalogu, w którym będzie przechowywany: ```php $configurator = new Nette\Bootstrap\Configurator; $configurator->setTempDirectory(__DIR__ . '/temp'); ``` -W systemie Linux lub macOS ustaw katalog `temp/` na uprawnienia do [zapisu |nette:troubleshooting#Setting-Directory-Permissions]. +Na Linuksie lub macOS ustaw katalogowi `temp/` [uprawnienia do zapisu |nette:troubleshooting#Ustawianie uprawnień do katalogów]. -I dochodzimy do samych plików konfiguracyjnych. Można je załadować za pomocą strony `addConfig()`: +Dochodzimy do samych plików konfiguracyjnych. Wczytujemy je za pomocą `addConfig()`: ```php $configurator->addConfig(__DIR__ . '/database.neon'); ``` -Jeśli chcemy dodać więcej plików konfiguracyjnych, możemy wywołać funkcję `addConfig()` wielokrotnie. Jeśli w plikach pojawią się elementy o tych samych kluczach, zostaną one nadpisane (lub [scalone |dependency-injection:configuration#merging] w przypadku tablic). Późniejszy plik ma wyższy priorytet niż poprzedni. +Jeśli chcemy dodać więcej plików konfiguracyjnych, możemy wywołać funkcję `addConfig()` wielokrotnie. Jeśli w plikach pojawią się elementy o tych samych kluczach, zostaną one nadpisane (lub w przypadku tablic [scalane |dependency-injection:configuration#Łączenie]). Później wczytany plik ma wyższy priorytet niż poprzedni. -Ostatnim krokiem jest stworzenie kontenera DI: +Ostatnim krokiem jest utworzenie kontenera DI: ```php $container = $configurator->createContainer(); ``` -I stworzy pożądane obiekty. Na przykład, jeśli używasz konfiguracji dla [Nette Database |database:configuration], możesz poprosić ją o utworzenie połączeń z bazą danych: +A ten już utworzy nam żądane obiekty. Jeśli na przykład używasz konfiguracji dla [Nette Database|database:configuration], możesz go poprosić o utworzenie połączeń z bazą danych: ```php $db = $container->getByType(Nette\Database\Connection::class); @@ -51,43 +50,42 @@ $explorer = $container->getByType(Nette\Database\Explorer::class); $db = $container->getByName('database.main.connection'); ``` -A teraz możesz pracować z bazą danych! +I teraz możesz już pracować z bazą danych! -Tryb deweloperski a produkcyjny .[#toc-vyvojarsky-vs-produkcni-rezim] ---------------------------------------------------------------------- +Tryb deweloperski vs produkcyjny +-------------------------------- -W trybie deweloperskim kontener jest automatycznie aktualizowany przy każdej zmianie plików konfiguracyjnych. W trybie produkcyjnym jest on generowany tylko raz, a zmiany nie są sprawdzane. -Tak więc tryb deweloperski ma na celu maksymalną wygodę programisty, tryb produkcyjny ma na celu wydajność i ostre wdrożenie. +W trybie deweloperskim kontener jest automatycznie aktualizowany przy każdej zmianie plików konfiguracyjnych. W trybie produkcyjnym generowany jest tylko raz, a zmiany nie są sprawdzane. Tryb deweloperski jest więc ukierunkowany na maksymalny komfort programisty, tryb produkcyjny na wydajność i wdrożenie produkcyjne. -Wybór trybu odbywa się poprzez autodetekcję, więc zazwyczaj nie ma potrzeby konfigurowania czy ręcznego przełączania czegokolwiek. Tryb jest rozwijany, gdy aplikacja jest uruchomiona na localhost (czyli na adresie IP `127.0.0.1` lub `::1`) i nie jest obecne żadne proxy (czyli jego nagłówek HTTP). W przeciwnym razie działa w trybie produkcyjnym. +Wybór trybu odbywa się poprzez autodetekcję, więc zazwyczaj nie ma potrzeby niczego konfigurować ani ręcznie przełączać. Tryb jest deweloperski, jeśli aplikacja jest uruchomiona na localhost (tj. adres IP `127.0.0.1` lub `::1`) i nie ma obecnego proxy (tj. jego nagłówka HTTP). W przeciwnym razie działa w trybie produkcyjnym. -Jeśli chcemy włączyć tryb deweloperski w innych przypadkach, takich jak programiści uzyskujący dostęp z określonego adresu IP, używamy `setDebugMode()`: +Jeśli chcemy włączyć tryb deweloperski również w innych przypadkach, na przykład dla programistów łączących się z określonego adresu IP, używamy `setDebugMode()`: ```php $configurator->setDebugMode('23.75.345.200'); -// można również określić pole adresu IP +// można również podać tablicę adresów IP ``` -Zdecydowanie zalecamy połączenie adresu IP z plikiem cookie. W pliku cookie `nette-debug` przechowujemy tajny token, np. `secret1234`, i w ten sposób umożliwiamy tryb deweloperski dla programistów uzyskujących dostęp z określonego adresu IP i posiadających token w pliku cookie: +Zdecydowanie zalecamy łączenie adresu IP z ciasteczkiem. W ciasteczku `nette-debug` zapiszemy tajny token, np. `secret1234`, i w ten sposób aktywujemy tryb deweloperski dla programistów łączących się z określonego adresu IP i jednocześnie posiadających w ciasteczku wspomniany token: ```php $configurator->setDebugMode('secret1234@23.75.345.200'); ``` -Możemy też całkowicie wyłączyć tryb deweloperski, nawet dla localhost: +Tryb deweloperski możemy również całkowicie wyłączyć, nawet dla localhost: ```php $configurator->setDebugMode(false); ``` -Parametry .[#toc-parametry] ---------------------------- +Parametry +--------- -Można również używać parametrów w plikach konfiguracyjnych, które są zdefiniowane [w sekcji `parameters` |dependency-injection:configuration#parameters]. +W plikach konfiguracyjnych możesz również używać parametrów, które są definiowane [w sekcji `parameters` |dependency-injection:configuration#Parametry]. -Mogą być również zakładane zewnętrznie metodą `addDynamicParameters()`: +Można je również wstawiać z zewnątrz za pomocą metody `addDynamicParameters()`: ```php $configurator->addDynamicParameters([ @@ -95,7 +93,4 @@ $configurator->addDynamicParameters([ ]); ``` -Do parametru `projectId` można się odwołać w konfiguracji poprzez wpisanie `%projectId%`. - - -{{leftbar: nette:@menu-topics}} +Do parametru `projectId` można się odwołać w konfiguracji zapisem `%projectId%`. diff --git a/bootstrap/pl/@meta.texy b/bootstrap/pl/@meta.texy new file mode 100644 index 0000000000..08f2227fb5 --- /dev/null +++ b/bootstrap/pl/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Dokumentacja Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/bootstrap/pt/@home.texy b/bootstrap/pt/@home.texy index 17bf30685f..0344fc188a 100644 --- a/bootstrap/pt/@home.texy +++ b/bootstrap/pt/@home.texy @@ -1,91 +1,89 @@ -Como carregar o arquivo de configuração -*************************************** +Nette Bootstrap +*************** .[perex] -Os componentes individuais da Nette são configurados usando arquivos de configuração. Mostraremos como carregar esses arquivos. +Os componentes individuais do Nette são configurados usando arquivos de configuração. Mostraremos como carregar esses arquivos. .[tip] -Se você estiver usando toda a estrutura, não há necessidade de fazer mais nada. No projeto, você tem um diretório pré-preparado `config/` para os arquivos de configuração, e o [carregador de aplicativos |application:bootstrap#DI Container Configuration] é responsável por carregá-los. -Este artigo é para usuários que utilizam apenas uma biblioteca Nette e querem tirar proveito dos arquivos de configuração. +Se você estiver usando todo o framework, não há necessidade de fazer mais nada. No seu projeto, você tem um diretório `config/` pré-preparado para arquivos de configuração, e o carregamento deles é responsabilidade do [carregador da aplicação |application:bootstrapping#Configuração do contêiner de DI]. Este artigo é para usuários que usam apenas uma biblioteca Nette e desejam aproveitar as opções dos arquivos de configuração. -Os arquivos de configuração são geralmente escritos em [NEON |neon:format] e são melhor editados em [editores com suporte para isso |best-practices:editors-and-tools#ide-editor]. Eles podem ser considerados como instruções sobre como **criar e configurar** objetos. Assim, o resultado do carregamento de uma configuração será uma chamada fábrica, que é um objeto que, sob demanda, criará outros objetos que você deseja usar. Por exemplo, uma conexão de banco de dados, etc. +Os arquivos de configuração são geralmente escritos no [formato NEON|neon:format] e são melhor editados em [editores com suporte a ele |best-practices:editors-and-tools#Editor IDE]. Eles podem ser entendidos como instruções sobre como **criar e configurar** objetos. Ou seja, o resultado do carregamento da configuração será uma chamada fábrica, que é um objeto que, sob demanda, criará outros objetos que queremos usar. Por exemplo, uma conexão de banco de dados, etc. -Esta fábrica também é chamada de *container de injeção de dependência* (Recipiente DI) e se você estiver interessado nos detalhes, leia o capítulo sobre [injeção de dependência |dependency-injection:]. +Essa fábrica também é chamada de *contêiner de injeção de dependência* (contêiner DI) e, se você estiver interessado em detalhes, leia o capítulo sobre [injeção de dependência |dependency-injection:]. -O carregamento da configuração e a criação do container é feita pela classe [api:Nette\Bootstrap\Configurator], portanto, instalaremos primeiro seu pacote `nette/bootstrap`: +O carregamento da configuração e a criação do contêiner são feitos pela classe [api:Nette\Bootstrap\Configurator], então primeiro instalaremos seu pacote `nette/bootstrap`: ```shell composer require nette/bootstrap ``` -E crie uma instância da classe `Configurator`. Como o recipiente DI gerado será colocado em cache no disco, você precisa definir o caminho para o diretório onde ele será salvo: +E criamos uma instância da classe `Configurator`. Como o contêiner DI gerado será armazenado em cache no disco, é necessário definir o caminho para o diretório onde ele será salvo: ```php $configurator = new Nette\Bootstrap\Configurator; $configurator->setTempDirectory(__DIR__ . '/temp'); ``` -No Linux ou macOS, defina as [permissões de escrita |nette:troubleshooting#Setting directory permissions] para o diretório `temp/`. +No Linux ou macOS, defina as [permissões de escrita |nette:troubleshooting#Configurando Permissões de Diretório] para o diretório `temp/`. -E nós mesmos chegamos aos arquivos de configuração. Estes são carregados usando `addConfig()`: +E chegamos aos próprios arquivos de configuração. Nós os carregamos usando `addConfig()`: ```php $configurator->addConfig(__DIR__ . '/database.neon'); ``` -Se você quiser adicionar mais arquivos de configuração, você pode chamar a função `addConfig()` várias vezes. Se elementos com as mesmas chaves aparecerem nos arquivos, eles serão sobregravados (ou [fundidos |dependency-injection:configuration#Merging] no caso de arrays). Um arquivo inserido posteriormente tem uma prioridade maior do que o anterior. +Se quisermos adicionar mais arquivos de configuração, podemos chamar a função `addConfig()` várias vezes. Se elementos com as mesmas chaves aparecerem nos arquivos, eles serão sobrescritos (ou, no caso de arrays, [mesclados |dependency-injection:configuration#Mesclagem]). O arquivo inserido posteriormente tem prioridade maior que o anterior. -O último passo é a criação de um recipiente DI: +O último passo é criar o contêiner de DI: ```php $container = $configurator->createContainer(); ``` -E já criará os objetos desejados para nós. Por exemplo, se você estiver usando a configuração para o [Nette Database |database:configuration], você pode solicitar a ele que crie conexões de banco de dados: +E ele já criará os objetos necessários para nós. Por exemplo, se você estiver usando a configuração para [Nette Database|database:configuration], pode pedir a ele para criar conexões de banco de dados: ```php -$db = $container->getByType(Nette\Database\Connection::classe); +$db = $container->getByType(Nette\Database\Connection::class); // ou $explorer = $container->getByType(Nette\Database\Explorer::class); // ou ao criar múltiplas conexões $db = $container->getByName('database.main.connection'); ``` -E agora você pode trabalhar com o banco de dados! +E agora você já pode trabalhar com o banco de dados! -Desenvolvimento vs Modo de Produção .[#toc-development-vs-production-mode] --------------------------------------------------------------------------- +Modo de desenvolvimento vs. produção +------------------------------------ -No modo de desenvolvimento, o recipiente é atualizado automaticamente sempre que os arquivos de configuração são alterados. No modo de produção, ele é gerado apenas uma vez e as mudanças não são verificadas. -Assim, o modo de desenvolvimento é voltado para a máxima conveniência do programador, o modo de produção é voltado para o desempenho. +No modo de desenvolvimento, o contêiner é atualizado automaticamente sempre que os arquivos de configuração são alterados. No modo de produção, ele é gerado apenas uma vez e as alterações não são verificadas. O modo de desenvolvimento é, portanto, focado no máximo conforto do programador, enquanto o modo de produção é focado no desempenho e na implantação em produção. -A seleção do modo é feita por autodetecção, de modo que normalmente não há necessidade de configurar ou trocar manualmente nada. O modo é desenvolvido quando a aplicação está sendo executada em um host local (ou seja, o endereço IP `127.0.0.1` ou `::1`) e não há nenhum proxy (ou seja, seu cabeçalho HTTP). Caso contrário, ele é executado em modo de produção. +A seleção do modo é feita por autodetecção, portanto, geralmente não é necessário configurar nada ou alternar manualmente. O modo é de desenvolvimento se a aplicação for executada em localhost (ou seja, endereço IP `127.0.0.1` ou `::1`) e não houver proxy presente (ou seja, seu cabeçalho HTTP). Caso contrário, ele é executado no modo de produção. -Se você quiser habilitar o modo de desenvolvimento em outros casos, como programadores acessando de um endereço IP específico, use `setDebugMode()`: +Se quisermos habilitar o modo de desenvolvimento também em outros casos, por exemplo, para programadores acessando de um endereço IP específico, usamos `setDebugMode()`: ```php $configurator->setDebugMode('23.75.345.200'); -// um conjunto de endereços IP também pode ser especificado +// também pode ser especificado um array de endereços IP ``` -Definitivamente, recomendamos combinar o endereço IP com um cookie. Armazene um token secreto, por exemplo `secret1234`, no cookie `nette-debug`, e desta forma você habilita o modo de desenvolvimento para programadores acessando de um endereço IP específico e também tendo o token mencionado no cookie: +Recomendamos enfaticamente combinar o endereço IP com um cookie. Armazenamos um token secreto no cookie `nette-debug`, por exemplo, `secret1234`, e dessa forma ativamos o modo de desenvolvimento para programadores acessando de um endereço IP específico e também tendo o token mencionado no cookie: ```php $configurator->setDebugMode('secret1234@23.75.345.200'); ``` -Você também pode desativar o modo desenvolvedor por completo, mesmo para o localhost: +Também podemos desativar completamente o modo de desenvolvimento, mesmo para localhost: ```php $configurator->setDebugMode(false); ``` -Parâmetros .[#toc-parameters] ------------------------------ +Parâmetros +---------- -Você também pode usar parâmetros em arquivos de configuração, que são definidos [na seção `parameters` |dependency-injection:configuration#parameters`]. +Nos arquivos de configuração, você também pode usar parâmetros, que são definidos [na seção `parameters` |dependency-injection:configuration#Parâmetros]. Eles também podem ser inseridos de fora usando o método `addDynamicParameters()`: @@ -95,7 +93,4 @@ $configurator->addDynamicParameters([ ]); ``` -O parâmetro `projectId` pode ser referenciado na configuração com a notação `%projectId%`. - - -{{leftbar: nette:@menu-topics}} +O parâmetro `projectId` pode ser referenciado na configuração usando a notação `%projectId%`. diff --git a/bootstrap/pt/@meta.texy b/bootstrap/pt/@meta.texy new file mode 100644 index 0000000000..e2566bcb44 --- /dev/null +++ b/bootstrap/pt/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Documentação Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/bootstrap/ro/@home.texy b/bootstrap/ro/@home.texy index 0508fb5617..16606ee9e5 100644 --- a/bootstrap/ro/@home.texy +++ b/bootstrap/ro/@home.texy @@ -1,93 +1,91 @@ -Cum se încarcă fișierul de configurare -************************************** +Nette Bootstrap +*************** .[perex] -Componentele individuale ale Nette sunt configurate cu ajutorul fișierelor de configurare. Vom arăta cum se încarcă aceste fișiere. +Componentele individuale Nette sunt configurate folosind fișiere de configurare. Vom arăta cum să încărcați aceste fișiere. .[tip] -Dacă utilizați întregul cadru, nu este nevoie să faceți nimic altceva. În proiect, aveți un director `config/` pregătit în prealabil pentru fișierele de configurare, iar [încărcătorul de aplicații |application:bootstrap#DI Container Configuration] este responsabil pentru încărcarea acestora. -Acest articol se adresează utilizatorilor care utilizează doar o singură bibliotecă Nette și doresc să profite de fișierele de configurare. +Dacă utilizați întregul framework, nu este nevoie să faceți nimic altceva. În proiect aveți un director `config/` pregătit pentru fișierele de configurare, iar încărcarea lor este gestionată de [încărcătorul aplicației |application:bootstrapping#Configurarea containerului DI]. Acest articol este pentru utilizatorii care folosesc doar o singură bibliotecă Nette și doresc să profite de posibilitățile fișierelor de configurare. -Fișierele de configurare sunt de obicei scrise în [NEON |neon:format] și sunt cel mai bine editate în [editori care oferă suport pentru acesta |best-practices:editors-and-tools#ide-editor]. Ele pot fi considerate ca fiind instrucțiuni privind modul de **creare și configurare** a obiectelor. Astfel, rezultatul încărcării unei configurații va fi o așa-numită fabrică, care este un obiect care va crea la cerere alte obiecte pe care doriți să le utilizați. De exemplu, o conexiune la o bază de date, etc. +Fișierele de configurare sunt de obicei scrise în [formatul NEON|neon:format] și cel mai bine se editează în [editori cu suport pentru acesta |best-practices:editors-and-tools#Editor IDE]. Ele pot fi înțelese ca instrucțiuni despre cum să **creați și configurați** obiecte. Prin urmare, rezultatul încărcării configurației va fi așa-numita fabrică (factory), care este un obiect ce ne va crea la cerere alte obiecte pe care dorim să le folosim. De exemplu, conexiuni la baze de date etc. -Această fabrică se mai numește și *container de injecție a dependențelor* (DI container) și dacă vă interesează detaliile, citiți capitolul despre [injecția dependențelor |dependency-injection:]. +Această fabrică se mai numește și *dependency injection container* (container DI) și, dacă sunteți interesat de detalii, citiți capitolul despre [dependency injection |dependency-injection:]. -Încărcarea configurației și crearea containerului sunt gestionate de clasa [api:Nette\Bootstrap\Configurator], așa că vom instala mai întâi pachetul său `nette/bootstrap`: +Încărcarea configurației și crearea containerului sunt gestionate de clasa [api:Nette\Bootstrap\Configurator], așa că mai întâi vom instala pachetul său `nette/bootstrap`: ```shell composer require nette/bootstrap ``` -Și vom crea o instanță a clasei `Configurator`. Deoarece containerul DI generat va fi stocat pe disc, trebuie să setați calea către directorul în care va fi salvat: +Și vom crea o instanță a clasei `Configurator`. Deoarece containerul DI generat va fi stocat în cache pe disc, este necesar să setați calea către directorul unde va fi salvat: ```php $configurator = new Nette\Bootstrap\Configurator; $configurator->setTempDirectory(__DIR__ . '/temp'); ``` -Pe Linux sau macOS, setați [permisiunile de scriere |nette:troubleshooting#Setting directory permissions] pentru directorul `temp/`. +Pe Linux sau macOS, setați [drepturi de scriere |nette:troubleshooting#Setarea permisiunilor pentru directoare] pentru directorul `temp/`. -Și ajungem la fișierele de configurare propriu-zise. Acestea se încarcă folosind `addConfig()`: +Și ajungem la fișierele de configurare în sine. Le încărcăm folosind `addConfig()`: ```php $configurator->addConfig(__DIR__ . '/database.neon'); ``` -Dacă doriți să adăugați mai multe fișiere de configurare, puteți apela funcția `addConfig()` de mai multe ori. În cazul în care în fișiere apar elemente cu aceleași chei, acestea vor fi suprascrise (sau [fuzionate |dependency-injection:configuration#Merging], în cazul array-urilor). Un fișier inserat ulterior are o prioritate mai mare decât cel anterior. +Dacă dorim să adăugăm mai multe fișiere de configurare, putem apela funcția `addConfig()` de mai multe ori. Dacă în fișiere apar elemente cu aceleași chei, acestea vor fi suprascrise (sau, în cazul array-urilor, [combinate |dependency-injection:configuration#Combinare]). Fișierul încărcat ulterior are prioritate mai mare decât cel anterior. -Ultimul pas constă în crearea unui container DI: +Ultimul pas este crearea containerului DI: ```php $container = $configurator->createContainer(); ``` -Și acesta va crea deja obiectele dorite pentru noi. De exemplu, dacă folosiți configurația pentru [Nette Database |database:configuration], îi puteți cere să creeze conexiuni la baza de date: +Și acesta ne va crea obiectele solicitate. De exemplu, dacă utilizați configurația pentru [Nette Database|database:configuration], îi puteți cere să creeze conexiuni la baza de date: ```php $db = $container->getByType(Nette\Database\Connection::class); // sau $explorer = $container->getByType(Nette\Database\Explorer::class); -// sau la crearea de conexiuni multiple +// sau la crearea mai multor conexiuni $db = $container->getByName('database.main.connection'); ``` Și acum puteți lucra cu baza de date! -Modul de dezvoltare vs. modul de producție .[#toc-development-vs-production-mode] ---------------------------------------------------------------------------------- +Mod dezvoltator vs mod producție +-------------------------------- -În modul de dezvoltare, containerul este actualizat automat ori de câte ori sunt modificate fișierele de configurare. În modul de producție, acesta este generat o singură dată, iar modificările nu sunt verificate. -Așadar, modul de dezvoltare urmărește confortul maxim al programatorului, iar modul de producție urmărește performanța. +În modul dezvoltator, containerul se actualizează automat la fiecare modificare a fișierelor de configurare. În modul producție, se generează o singură dată și modificările nu sunt verificate. Modul dezvoltator este, prin urmare, axat pe confortul maxim al programatorului, iar modul producție pe performanță și implementare live. -Selectarea modului se face prin autodetecție, astfel încât, de obicei, nu este nevoie să configurați sau să comutați manual nimic. Modul este de dezvoltare atunci când aplicația rulează pe o gazdă locală (adică adresa IP `127.0.0.1` sau `::1`) și nu este prezent niciun proxy (adică antetul său HTTP). În caz contrar, se execută în modul de producție. +Selectarea modului se face prin autodetecție, deci de obicei nu este nevoie să configurați sau să comutați manual nimic. Modul este dezvoltator dacă aplicația este rulată pe localhost (adică adresa IP `127.0.0.1` sau `::1`) și nu este prezent un proxy (adică antetul său HTTP). Altfel, rulează în modul producție. -Dacă doriți să activați modul de dezvoltare în alte cazuri, cum ar fi programatorii care accesează de la o anumită adresă IP, utilizați `setDebugMode()`: +Dacă dorim să activăm modul dezvoltator și în alte cazuri, de exemplu pentru programatorii care accesează de la o anumită adresă IP, folosim `setDebugMode()`: ```php $configurator->setDebugMode('23.75.345.200'); -// se poate specifica, de asemenea, o matrice de adrese IP +// se poate specifica și un array de adrese IP ``` -Vă recomandăm cu siguranță să combinați adresa IP cu un cookie. Stocați un token secret, de exemplu `secret1234`, în cookie-ul `nette-debug` și, în acest fel, activați modul de dezvoltare pentru programatorii care accesează de la o anumită adresă IP și, de asemenea, aveți token-ul menționat în cookie: +Recomandăm cu tărie combinarea adresei IP cu un cookie. Vom stoca un token secret în cookie-ul `nette-debug`, de exemplu `secret1234`, și astfel vom activa modul dezvoltator pentru programatorii care accesează de la o anumită adresă IP și au în același timp tokenul menționat în cookie: ```php $configurator->setDebugMode('secret1234@23.75.345.200'); ``` -De asemenea, puteți dezactiva complet modul de dezvoltare, chiar și pentru localhost: +Putem, de asemenea, să dezactivăm complet modul dezvoltator, chiar și pentru localhost: ```php $configurator->setDebugMode(false); ``` -Parametrii .[#toc-parameters] ------------------------------ +Parametri +--------- -Puteți utiliza, de asemenea, parametri în fișierele de configurare, care sunt definiți [în secțiunea `parameters` |dependency-injection:configuration#parameters`]. +În fișierele de configurare puteți utiliza și parametri, care sunt definiți [în secțiunea `parameters` |dependency-injection:configuration#Parametri]. -Aceștia pot fi, de asemenea, introduși din exterior folosind metoda `addDynamicParameters()`: +Aceștia pot fi, de asemenea, inserați din exterior folosind metoda `addDynamicParameters()`: ```php $configurator->addDynamicParameters([ @@ -95,7 +93,4 @@ $configurator->addDynamicParameters([ ]); ``` -Parametrul `projectId` poate fi referit în configurație cu ajutorul notației `%projectId%`. - - -{{leftbar: nette:@menu-topics}} +Parametrul `projectId` poate fi referențiat în configurație prin notația `%projectId%`. diff --git a/bootstrap/ro/@meta.texy b/bootstrap/ro/@meta.texy new file mode 100644 index 0000000000..6554692600 --- /dev/null +++ b/bootstrap/ro/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Documentație Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/bootstrap/ru/@home.texy b/bootstrap/ru/@home.texy index 34a794c8f8..ec99937fd8 100644 --- a/bootstrap/ru/@home.texy +++ b/bootstrap/ru/@home.texy @@ -1,47 +1,46 @@ -Как загрузить файл конфигурации -******************************* +Nette Bootstrap +*************** .[perex] -Отдельные компоненты Nette настраиваются с помощью конфигурационных файлов. Мы покажем, как загрузить эти файлы. +Отдельные компоненты Nette настраиваются с помощью конфигурационных файлов. Мы покажем вам, как загружать эти файлы. .[tip] -Если вы используете весь фреймворк, больше ничего делать не нужно. В проекте у вас есть заранее подготовленный каталог `config/` для файлов конфигурации, а за их загрузку отвечает [загрузчик приложения|application:bootstrap#DI-Container-Configuration]. -Эта статья предназначена для пользователей, которые используют только одну библиотеку Nette и хотят воспользоваться преимуществами конфигурационных файлов. +Если вы используете весь фреймворк, вам не нужно ничего делать. В проекте у вас есть подготовленный каталог `config/` для конфигурационных файлов, и их загрузкой занимается [загрузчик приложения |application:bootstrapping#Конфигурация DI-контейнера]. Эта статья предназначена для пользователей, которые используют только одну библиотеку Nette и хотят воспользоваться возможностями конфигурационных файлов. -Файлы конфигурации обычно пишутся в формате [NEON|neon:format] и лучше всего редактируются в [редакторах с его поддержкой|best-practices:editors-and-tools#IDE-Editor]. Их можно рассматривать как инструкции по **созданию и конфигурированию** объектов. Таким образом, результатом загрузки конфигурации будет так называемая фабрика, представляющая собой объект, который по требованию будет создавать другие объекты для дальнейшего использования. Например, подключение к базе данных и т. д. +Конфигурационные файлы обычно пишутся в [формате NEON|neon:format] и лучше всего редактируются в [редакторах с его поддержкой |best-practices:editors-and-tools#IDE редактор]. Их можно рассматривать как инструкции по **созданию и настройке** объектов. Таким образом, результатом загрузки конфигурации будет так называемая фабрика, то есть объект, который по запросу создаст для нас другие объекты, которые мы хотим использовать. Например, соединение с базой данных и т. д. -Эта фабрика также называется *контейнером инъекции зависимостей* (DI-контейнером), и если вас интересуют подробности, прочитайте главу [Внедрение зависимостей |dependency-injection:]. +Эта фабрика также называется *контейнером внедрения зависимостей* (DI container), и если вас интересуют подробности, прочитайте главу о [внедрении зависимостей |dependency-injection:]. -Загрузкой конфигурации и созданием контейнера занимается класс [api:Nette\Bootstrap\Configurator], поэтому сначала мы установим его пакет `nette/bootstrap`: +Загрузку конфигурации и создание контейнера выполняет класс [api:Nette\Bootstrap\Configurator], поэтому сначала установим его пакет `nette/bootstrap`: ```shell composer require nette/bootstrap ``` -И создайте экземпляр класса `Configurator`. Поскольку сгенерированный DI-контейнер будет кэшироваться на диск, необходимо задать путь к директории, в которой он будет сохранен: +И создадим экземпляр класса `Configurator`. Поскольку сгенерированный DI-контейнер будет кешироваться на диск, необходимо указать путь к каталогу, где он будет храниться: ```php $configurator = new Nette\Bootstrap\Configurator; $configurator->setTempDirectory(__DIR__ . '/temp'); ``` -В Linux или macOS установите [разрешение на запись |nette:troubleshooting#Setting-Directory-Permissions] для каталога `temp/`. +В Linux или macOS установите для каталога `temp/` [права на запись |nette:troubleshooting#Настройка прав доступа к каталогам]. -И мы переходим к самим конфигурационным файлам. Они загружаются с помощью функции `addConfig()`: +И мы подходим к самим конфигурационным файлам. Мы загружаем их с помощью `addConfig()`: ```php $configurator->addConfig(__DIR__ . '/database.neon'); ``` -Если вы хотите добавить больше конфигурационных файлов, вы можете вызвать функцию `addConfig()` несколько раз. Если в файлах появятся элементы с одинаковыми ключами, они будут перезаписаны (или [объединены |dependency-injection:configuration#Merging] в случае с массивами). Позже добавленный файл имеет более высокий приоритет, чем предыдущий. +Если мы хотим добавить несколько конфигурационных файлов, мы можем вызвать функцию `addConfig()` несколько раз. Если в файлах появятся элементы с одинаковыми ключами, они будут перезаписаны (или в случае массивов [объединены |dependency-injection:configuration#Слияние]). Файл, вставленный позже, имеет более высокий приоритет, чем предыдущий. -Последний шаг — создание контейнера DI: +Последний шаг — создание DI-контейнера: ```php $container = $configurator->createContainer(); ``` -А он уже создаст для нас желаемые объекты. Например, если вы используете конфигурацию для [Nette Database|database:configuration], вы можете попросить её создать соединение с базой данных: +И он уже создаст для нас нужные объекты. Например, если вы используете конфигурацию для [Nette Database|database:configuration], вы можете попросить его создать соединения с базой данных: ```php $db = $container->getByType(Nette\Database\Connection::class); @@ -54,40 +53,39 @@ $db = $container->getByName('database.main.connection'); И теперь вы можете работать с базой данных! -Режим разработки и режим производства .[#toc-development-vs-production-mode] ----------------------------------------------------------------------------- +Режим разработки vs production +------------------------------ -В режиме разработки контейнер автоматически обновляется при каждом изменении конфигурационных файлов. В режиме производства он генерируется только один раз, и изменения не проверяются. -Таким образом, режим разработчика нацелен на максимальное удобство программиста, а режим производства — на производительность. +В режиме разработки контейнер автоматически обновляется при каждом изменении конфигурационных файлов. В production-режиме он генерируется только один раз, и изменения не проверяются. Таким образом, режим разработки ориентирован на максимальное удобство программиста, а production — на производительность и развертывание. -Выбор режима осуществляется путем автоопределения, поэтому обычно нет необходимости настраивать или переключать что-либо вручную. Режим разработки используется, когда приложение запущено на локальном хосте (т. е. IP-адрес `127.0.0.1` или `::1`) и отсутствует прокси-сервер (т. е. его HTTP-заголовок). В противном случае приложение работает в производственном («боевом») режиме. +Выбор режима осуществляется автоматически, поэтому обычно нет необходимости что-либо настраивать или переключать вручную. Режим является режимом разработки, если приложение запущено на localhost (т. е. IP-адрес `127.0.0.1` или `::1`) и отсутствует прокси (т. е. его HTTP-заголовок). В противном случае оно работает в production-режиме. -Если вы хотите включить режим разработки в других случаях, например, когда программисты получают доступ с определенного IP-адреса, используйте `setDebugMode()`: +Если мы хотим включить режим разработки и в других случаях, например, для программистов, обращающихся с определенного IP-адреса, мы используем `setDebugMode()`: ```php $configurator->setDebugMode('23.75.345.200'); -// также может быть указан массив IP-адресов +// можно также указать массив IP-адресов ``` -Мы определенно рекомендуем сочетать IP-адрес с файлом куки. Храните секретный токен, например, `secret1234`, в куки `nette-debug`, и таким образом вы включите режим разработки для программистов, получающих доступ с определенного IP-адреса и также имеющих токен, указанный в куки: +Мы настоятельно рекомендуем сочетать IP-адрес с cookie. В cookie `nette-debug` мы сохраняем секретный токен, например, `secret1234`, и таким образом активируем режим разработки для программистов, обращающихся с определенного IP-адреса и одновременно имеющих указанный токен в cookie: ```php $configurator->setDebugMode('secret1234@23.75.345.200'); ``` -Вы также можете полностью отключить режим разработчика, даже для localhost: +Мы также можем полностью отключить режим разработки, даже для localhost: ```php $configurator->setDebugMode(false); ``` -Параметры .[#toc-parameters] ----------------------------- +Параметры +--------- -Вы также можете использовать параметры в конфигурационных файлах, которые определяются [в разделе `параметры`|dependency-injection:configuration#Parameters]. +В конфигурационных файлах вы также можете использовать параметры, которые определяются [в разделе `parameters` |dependency-injection:configuration#Параметры]. -Они также могут быть вставлены извне с помощью метода `addDynamicParameters()`: +Их также можно вставлять извне с помощью метода `addDynamicParameters()`: ```php $configurator->addDynamicParameters([ @@ -95,7 +93,4 @@ $configurator->addDynamicParameters([ ]); ``` -На параметр `projectId` можно ссылаться в конфигурации с помощью нотации `%projectId%`. - - -{{leftbar: nette:@menu-topics}} +На параметр `projectId` можно ссылаться в конфигурации с помощью записи `%projectId%`. diff --git a/bootstrap/ru/@meta.texy b/bootstrap/ru/@meta.texy new file mode 100644 index 0000000000..61577d6323 --- /dev/null +++ b/bootstrap/ru/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Документация Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/bootstrap/sl/@home.texy b/bootstrap/sl/@home.texy index b189c42dbe..76641a1cce 100644 --- a/bootstrap/sl/@home.texy +++ b/bootstrap/sl/@home.texy @@ -1,47 +1,46 @@ -Kako naložiti konfiguracijsko datoteko -************************************** +Nette Bootstrap +*************** .[perex] -Posamezne komponente sistema Nette se konfigurirajo s konfiguracijskimi datotekami. Prikazali bomo, kako naložiti te datoteke. +Posamezne komponente Nette nastavljamo s pomočjo konfiguracijskih datotek. Pokazali bomo, kako te datoteke nalagati. .[tip] -Če uporabljate celotno ogrodje, vam ni treba storiti ničesar drugega. V projektu imate vnaprej pripravljen imenik `config/` za konfiguracijske datoteke, za njihovo nalaganje pa je odgovoren [nalagalnik aplikacije |application:bootstrap#DI Container Configuration]. -Ta članek je namenjen uporabnikom, ki uporabljajo samo eno knjižnico Nette in želijo izkoristiti prednosti konfiguracijskih datotek. +Če uporabljate celotno ogrodje, ni treba storiti ničesar dodatnega. V projektu imate za konfiguracijske datoteke pripravljen imenik `config/` in za njihovo nalaganje skrbi [zavajalec aplikacije |application:bootstrapping#Konfiguracija DI vsebnika]. Ta članek je za uporabnike, ki uporabljajo samo eno knjižnico Nette in želijo izkoristiti možnosti konfiguracijskih datotek. -Konfiguracijske datoteke so običajno napisane v jeziku [NEON |neon:format] in jih je najbolje urejati v [urejevalnikih s podporo zanj |best-practices:editors-and-tools#ide-editor]. Lahko si jih predstavljamo kot navodila za **ustvarjanje in konfiguriranje** objektov. Tako bo rezultat nalaganja konfiguracije tako imenovana tovarna, ki je objekt, ki bo na zahtevo ustvaril druge objekte, ki jih želite uporabiti. Na primer povezavo s podatkovno bazo itd. +Konfiguracijske datoteke se običajno pišejo v [formatu NEON|neon:format] in se najbolje urejajo v [urejevalnikih z njegovo podporo |best-practices:editors-and-tools#IDE urejevalnik]. Lahko jih razumemo kot navodila, kako **ustvarjati in konfigurirati** objekte. Torej bo rezultat nalaganja konfiguracije tako imenovana tovarna, kar je objekt, ki nam na zahtevo ustvari druge objekte, ki jih želimo uporabljati. Na primer povezavo s podatkovno bazo itd. -Ta tovarna se imenuje tudi *kontejner za vbrizgavanje odvisnosti* (DI container) in če vas zanimajo podrobnosti, preberite poglavje o [vbrizgavanju odvisnosti |dependency-injection:]. +Tej tovarni se tudi reče *dependency injection vsebnik* (DI vsebnik) in če vas zanimajo podrobnosti, preberite poglavje o [dependency injection |dependency-injection:]. -Za nalaganje konfiguracije in ustvarjanje vsebnika skrbi razred [api:Nette\Bootstrap\Configurator], zato bomo najprej namestili njegov paket `nette/bootstrap`: +Nalaganje konfiguracije in ustvarjanje vsebnika opravi razred [api:Nette\Bootstrap\Configurator], zato najprej namestimo njegov paket `nette/bootstrap`: ```shell composer require nette/bootstrap ``` -In ustvarimo primerek razreda `Configurator`. Ker bo ustvarjeni vsebnik DI shranjen v predpomnilniku na disku, morate določiti pot do imenika, kamor bo shranjen: +In ustvarimo instanco razreda `Configurator`. Ker se bo generirani DI vsebnik predpomnil na disk, je treba nastaviti pot do imenika, kamor se bo shranjeval: ```php $configurator = new Nette\Bootstrap\Configurator; $configurator->setTempDirectory(__DIR__ . '/temp'); ``` -V operacijskem sistemu Linux ali macOS nastavite [dovoljenja za pisanje za |nette:troubleshooting#Setting directory permissions] imenik `temp/`. +Na Linuxu ali macOS nastavite imeniku `temp/` [pravice za pisanje |nette:troubleshooting#Nastavitev pravic map]. -Nato preidemo na same konfiguracijske datoteke. Te se naložijo z uporabo `addConfig()`: +In pridemo do samih konfiguracijskih datotek. Te naložimo s pomočjo `addConfig()`: ```php $configurator->addConfig(__DIR__ . '/database.neon'); ``` -Če želite dodati več konfiguracijskih datotek, lahko funkcijo `addConfig()` pokličete večkrat. Če se v datotekah pojavijo elementi z istimi ključi, se prepišejo (ali [združijo |dependency-injection:configuration#Merging] v primeru polj). Kasneje vstavljena datoteka ima višjo prioriteto kot prejšnja. +Če želimo dodati več konfiguracijskih datotek, lahko funkcijo `addConfig()` pokličemo večkrat. Če se v datotekah pojavijo elementi z enakimi ključi, bodo prepisani (ali v primeru polj [združeni |dependency-injection:configuration#Združevanje]). Kasneje vstavljena datoteka ima višjo prioriteto kot prejšnja. -Zadnji korak je ustvarjanje vsebnika DI: +Zadnji korak je ustvarjanje DI vsebnika: ```php $container = $configurator->createContainer(); ``` -In ta bo že ustvaril želene predmete za nas. Če na primer uporabljate konfiguracijo za [podatkovno zbirko Nette |database:configuration], jo lahko prosite, naj ustvari povezave s podatkovno zbirko: +In ta nam bo že ustvaril zahtevane objekte. Če na primer uporabljate konfiguracijo za [Nette Database|database:configuration], ga lahko prosite za ustvarjanje povezav s podatkovno bazo: ```php $db = $container->getByType(Nette\Database\Connection::class); @@ -51,43 +50,42 @@ $explorer = $container->getByType(Nette\Database\Explorer::class); $db = $container->getByName('database.main.connection'); ``` -In zdaj lahko delate s podatkovno zbirko! +In zdaj lahko že delate s podatkovno bazo! -Razvojni in produkcijski način .[#toc-development-vs-production-mode] ---------------------------------------------------------------------- +Razvojni vs produkcijski način +------------------------------ -V razvojnem načinu se vsebnik samodejno posodablja ob vsaki spremembi konfiguracijskih datotek. V produkcijskem načinu se ustvari samo enkrat, spremembe pa se ne preverjajo. -Razvojni način je torej namenjen čim večjemu programerskemu udobju, produkcijski način pa zmogljivosti. +V razvojnem načinu se vsebnik samodejno posodablja ob vsaki spremembi konfiguracijskih datotek. V produkcijskem načinu se generira samo enkrat in spremembe se ne preverjajo. Razvojni je torej usmerjen v maksimalno udobje programerja, produkcijski pa v zmogljivost in ostro uvajanje. -Izbira načina poteka s samodejnim zaznavanjem, zato običajno ni treba ničesar konfigurirati ali ročno preklapljati. Način je razvojni, kadar aplikacija teče na lokalnem gostitelju (tj. na naslovu IP `127.0.0.1` ali `::1`) in ni prisoten noben posrednik (tj. njegova glava HTTP). V nasprotnem primeru deluje v produkcijskem načinu. +Izbira načina se izvaja s samodejnim zaznavanjem, zato običajno ni treba ničesar konfigurirati ali ročno preklapljati. Način je razvojni takrat, ko je aplikacija zagnana na localhostu (tj. IP naslov `127.0.0.1` ali `::1`) in ni prisotna proxy (tj. njena HTTP glava). Sicer teče v produkcijskem načinu. -Če želite omogočiti razvojni način v drugih primerih, na primer pri programerjih, ki dostopajo z določenega naslova IP, uporabite `setDebugMode()`: +Če želimo razvojni način omogočiti tudi v drugih primerih, na primer programerjem, ki dostopajo iz določenega IP naslova, uporabimo `setDebugMode()`: ```php $configurator->setDebugMode('23.75.345.200'); -// lahko se določi tudi niz naslovov IP. +// lahko se navede tudi polje IP naslovov ``` -Vsekakor priporočamo kombinacijo naslova IP s piškotkom. V piškotek `nette-debug` shranite skrivni žeton, npr. `secret1234`, in tako omogočite razvojni način za programerje, ki dostopajo z določenega naslova IP in imajo v piškotku naveden tudi žeton: +Vsekakor priporočamo kombiniranje IP naslova s piškotkom. V piškotek `nette-debug` shranimo skrivni žeton, npr. `secret1234`, in na ta način aktiviramo razvojni način za programerje, ki dostopajo iz določenega IP naslova in hkrati imajo v piškotku omenjeni žeton: ```php $configurator->setDebugMode('secret1234@23.75.345.200'); ``` -Razvojni način lahko tudi popolnoma onemogočite, tudi za lokalni gostitelj: +Razvojni način lahko tudi popolnoma izklopimo, tudi za localhost: ```php $configurator->setDebugMode(false); ``` -Parametri .[#toc-parameters] ----------------------------- +Parametri +--------- -V konfiguracijskih datotekah lahko uporabite tudi parametre, ki so opredeljeni [v razdelku `parameters` |dependency-injection:configuration#parameters`]. +V konfiguracijskih datotekah lahko uporabljate tudi parametre, ki se definirajo [v sekciji `parameters` |dependency-injection:configuration#Parametri]. -Lahko jih vstavite tudi od zunaj z uporabo metode `addDynamicParameters()`: +Lahko jih vstavljate tudi od zunaj s pomočjo metode `addDynamicParameters()`: ```php $configurator->addDynamicParameters([ @@ -95,7 +93,4 @@ $configurator->addDynamicParameters([ ]); ``` -Na parameter `projectId` se lahko v konfiguraciji sklicujemo z zapisom `%projectId%`. - - -{{leftbar: nette:@menu-topics}} +Na parameter `projectId` se lahko v konfiguraciji sklicujete z zapisom `%projectId%`. diff --git a/bootstrap/sl/@meta.texy b/bootstrap/sl/@meta.texy new file mode 100644 index 0000000000..282883a3d6 --- /dev/null +++ b/bootstrap/sl/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Dokumentacija}} +{{leftbar: nette:@menu-topics}} diff --git a/bootstrap/tr/@home.texy b/bootstrap/tr/@home.texy index 2f3016ead6..732122fed8 100644 --- a/bootstrap/tr/@home.texy +++ b/bootstrap/tr/@home.texy @@ -1,47 +1,46 @@ -Yapılandırma Dosyası Nasıl Yüklenir -*********************************** +Nette Bootstrap +*************** .[perex] -Nette'nin münferit bileşenleri yapılandırma dosyaları kullanılarak yapılandırılır. Bu dosyaların nasıl yükleneceğini göstereceğiz. +Nette'nin bireysel bileşenlerini yapılandırma dosyaları kullanarak ayarlıyoruz. Bu dosyaların nasıl yükleneceğini göstereceğiz. .[tip] -Tüm çerçeveyi kullanıyorsanız, başka bir şey yapmanıza gerek yoktur. Projede, yapılandırma dosyaları için önceden hazırlanmış bir dizine `config/` sahipsiniz ve [uygulama yükleyici |application:bootstrap#DI Container Configuration] bunları yüklemekten sorumludur. -Bu makale, yalnızca bir Nette kütüphanesi kullanan ve yapılandırma dosyalarından yararlanmak isteyen kullanıcılar içindir. +Eğer tüm framework'ü kullanıyorsanız, başka bir şey yapmanıza gerek yoktur. Projenizde yapılandırma dosyaları için önceden hazırlanmış bir `config/` dizini bulunur ve bunların yüklenmesinden [uygulama yükleyicisi |application:bootstrapping#DI Konteyner Yapılandırması] sorumludur. Bu makale, yalnızca bir Nette kütüphanesi kullanan ve yapılandırma dosyalarının olanaklarından yararlanmak isteyen kullanıcılar içindir. -Konfigürasyon dosyaları genellikle [NEON |neon:format] dilinde yazılır ve en iyi [bu dili destekleyen editörlerde |best-practices:editors-and-tools#ide-editor] düzenlenir. Bunlar nesnelerin nasıl **oluşturulacağı ve yapılandırılacağı** ile ilgili talimatlar olarak düşünülebilir. Bu nedenle, bir yapılandırmanın yüklenmesinin sonucu, talep üzerine kullanmak istediğiniz diğer nesneleri oluşturacak bir nesne olan fabrika olarak adlandırılan bir nesne olacaktır. Örneğin, bir veritabanı bağlantısı vb. +Yapılandırma dosyaları genellikle [NEON formatında|neon:format] yazılır ve en iyi şekilde [destekleyen düzenleyicilerde |best-practices:editors-and-tools#IDE Editörü] düzenlenir. Bunları, nesnelerin **nasıl oluşturulacağı ve yapılandırılacağı** konusunda talimatlar olarak düşünebiliriz. Yani, yapılandırmanın yüklenmesinin sonucu, istek üzerine kullanmak istediğimiz diğer nesneleri (örneğin, veritabanı bağlantısı vb.) oluşturacak olan fabrika olarak adlandırılan bir nesne olacaktır. -Bu fabrikaya *bağımlılık enjeksiyonu konteyneri* (DI konteyneri) de denir ve ayrıntılarla ilgileniyorsanız [bağımlılık |dependency-injection:] enjeksiyonu ile ilgili bölümü okuyun. +Bu fabrikaya aynı zamanda *dependency injection konteyneri* (DI konteyneri) denir ve ayrıntılarla ilgileniyorsanız, [dependency injection |dependency-injection:] bölümünü okuyun. -Yapılandırmanın yüklenmesi ve konteynerin oluşturulması [api:Nette\Bootstrap\Configurator] sınıfı tarafından gerçekleştirilir, bu nedenle önce `nette/bootstrap` paketini yükleyeceğiz: +Yapılandırmanın yüklenmesi ve konteynerin oluşturulması [api:Nette\Bootstrap\Configurator] sınıfı tarafından gerçekleştirilir, bu nedenle önce `nette/bootstrap` paketini kuracağız: ```shell composer require nette/bootstrap ``` -Ve `Configurator` sınıfının bir örneğini oluşturun. Oluşturulan DI konteyneri diske önbelleğe alınacağından, kaydedileceği dizinin yolunu ayarlamanız gerekir: +Ve `Configurator` sınıfının bir örneğini oluşturacağız. Oluşturulan DI konteyneri diske önbelleğe alınacağından, kaydedileceği dizinin yolunu ayarlamak gerekir: ```php $configurator = new Nette\Bootstrap\Configurator; $configurator->setTempDirectory(__DIR__ . '/temp'); ``` -Linux veya macOS üzerinde, `temp/` dizini için [yazma izinlerini |nette:troubleshooting#Setting directory permissions] ayarlayın. +Linux veya macOS'ta, `temp/` dizinine [yazma izinleri |nette:troubleshooting#Dizin İzinlerini Ayarlama] ayarlayın. -Ve yapılandırma dosyalarının kendilerine geliyoruz. Bunlar `addConfig()` kullanılarak yüklenir: +Ve yapılandırma dosyalarına geliyoruz. Bunları `addConfig()` kullanarak yükleyeceğiz: ```php $configurator->addConfig(__DIR__ . '/database.neon'); ``` -Daha fazla yapılandırma dosyası eklemek istiyorsanız, `addConfig()` işlevini birden çok kez çağırabilirsiniz. Dosyalarda aynı anahtarlara sahip öğeler varsa, bunların üzerine yazılır (veya diziler söz konusu olduğunda [birleştirilir |dependency-injection:configuration#Merging] ). Daha sonra eklenen bir dosya öncekinden daha yüksek önceliğe sahiptir. +Daha fazla yapılandırma dosyası eklemek istiyorsak, `addConfig()` fonksiyonunu birden çok kez çağırabiliriz. Dosyalarda aynı anahtarlara sahip öğeler görünürse, bunlar üzerine yazılır (veya diziler durumunda [birleştirilir |dependency-injection:configuration#Birleştirme]). Daha sonra eklenen dosya, öncekinden daha yüksek önceliğe sahiptir. -Son adım bir DI konteyneri oluşturmaktır: +Son adım DI konteynerini oluşturmaktır: ```php $container = $configurator->createContainer(); ``` -Ve zaten bizim için istenen nesneleri oluşturacaktır. Örneğin, [Nette Database |database:configuration] için yapılandırmayı kullanıyorsanız, veritabanı bağlantılarını oluşturmasını isteyebilirsiniz: +Ve bu bize istenen nesneleri oluşturacaktır. Örneğin, [Nette Database|database:configuration] için yapılandırma kullanıyorsanız, veritabanı bağlantıları oluşturmasını isteyebilirsiniz: ```php $db = $container->getByType(Nette\Database\Connection::class); @@ -51,43 +50,42 @@ $explorer = $container->getByType(Nette\Database\Explorer::class); $db = $container->getByName('database.main.connection'); ``` -Ve şimdi veritabanı ile çalışabilirsiniz! +Ve şimdi veritabanıyla çalışabilirsiniz! -Geliştirme ve Üretim Modu .[#toc-development-vs-production-mode] ----------------------------------------------------------------- +Geliştirme vs Üretim Modu +------------------------- -Geliştirme modunda, yapılandırma dosyaları her değiştirildiğinde kapsayıcı otomatik olarak güncellenir. Üretim modunda, yalnızca bir kez oluşturulur ve değişiklikler kontrol edilmez. -Yani geliştirici modu maksimum programcı kolaylığını, üretim modu ise performansı hedefler. +Geliştirme modunda, konteyner yapılandırma dosyaları her değiştiğinde otomatik olarak güncellenir. Üretim modunda, yalnızca bir kez oluşturulur ve değişiklikler kontrol edilmez. Bu nedenle geliştirme modu, programcının maksimum rahatlığına odaklanırken, üretim modu performansa ve canlı dağıtıma odaklanır. -Mod seçimi otomatik algılama ile yapılır, bu nedenle genellikle herhangi bir şeyi yapılandırmaya veya manuel olarak değiştirmeye gerek yoktur. Uygulama bir localhost (yani, IP adresi `127.0.0.1` veya `::1`) üzerinde çalışıyorsa ve proxy (yani, HTTP başlığı) yoksa mod geliştirme olur. Aksi takdirde üretim modunda çalışır. +Mod seçimi otomatik algılama ile yapılır, bu nedenle genellikle bir şey yapılandırmaya veya manuel olarak değiştirmeye gerek yoktur. Uygulama localhost'ta (yani IP adresi `127.0.0.1` veya `::1`) çalıştırılıyorsa ve bir proxy mevcut değilse (yani HTTP başlığı yoksa) mod geliştirme modudur. Aksi takdirde, üretim modunda çalışır. -Belirli bir IP adresinden erişen programcılar gibi diğer durumlarda geliştirme modunu etkinleştirmek istiyorsanız `setDebugMode()` adresini kullanın: +Geliştirme modunu diğer durumlarda da etkinleştirmek istiyorsak, örneğin belirli bir IP adresinden erişen programcılar için `setDebugMode()` kullanırız: ```php $configurator->setDebugMode('23.75.345.200'); -// bir dizi IP adresi de belirtilebilir +// IP adresleri dizisi de belirtilebilir ``` -IP adresini bir çerez ile birleştirmenizi kesinlikle öneririz. `nette-debug` çerezinde `secret1234` gibi gizli bir belirteç saklayın ve bu şekilde belirli bir IP adresinden erişen programcılar için geliştirme modunu etkinleştirin ve ayrıca çerezde belirtilen belirtece sahip olun: +Kesinlikle bir IP adresini bir çerezle birleştirmenizi öneririz. `nette-debug` çerezine gizli bir belirteç, örneğin `secret1234` kaydedeceğiz ve bu şekilde belirli bir IP adresinden erişen ve aynı zamanda çerezde belirtilen belirtece sahip olan programcılar için geliştirme modunu etkinleştireceğiz: ```php $configurator->setDebugMode('secret1234@23.75.345.200'); ``` -Ayrıca localhost için bile geliştirici modunu tamamen devre dışı bırakabilirsiniz: +Geliştirme modunu localhost için bile tamamen devre dışı bırakabiliriz: ```php $configurator->setDebugMode(false); ``` -Parametreler .[#toc-parameters] -------------------------------- +Parametreler +------------ -Ayrıca [`parameters` bölümünde |dependency-injection:configuration#parameters`] tanımlanan yapılandırma dosyalarındaki parametreleri de kullanabilirsiniz. +Yapılandırma dosyalarında, [`parameters` bölümünde |dependency-injection:configuration#Parametreler] tanımlanan parametreleri de kullanabilirsiniz. -Ayrıca `addDynamicParameters()` yöntemi kullanılarak dışarıdan da yerleştirilebilirler: +Bunlar ayrıca `addDynamicParameters()` yöntemi kullanılarak dışarıdan da eklenebilir: ```php $configurator->addDynamicParameters([ @@ -95,7 +93,4 @@ $configurator->addDynamicParameters([ ]); ``` -`projectId` parametresine yapılandırmada `%projectId%` gösterimiyle başvurulabilir. - - -{{leftbar: nette:@menu-topics}} +`projectId` parametresine yapılandırmada `%projectId%` yazılarak başvurulabilir. diff --git a/bootstrap/tr/@meta.texy b/bootstrap/tr/@meta.texy new file mode 100644 index 0000000000..e5c5cea355 --- /dev/null +++ b/bootstrap/tr/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Dokümantasyonu}} +{{leftbar: nette:@menu-topics}} diff --git a/bootstrap/uk/@home.texy b/bootstrap/uk/@home.texy index 480a986e9d..1320bdf9fa 100644 --- a/bootstrap/uk/@home.texy +++ b/bootstrap/uk/@home.texy @@ -1,93 +1,91 @@ -Як завантажити файл конфігурації -******************************** +Nette Bootstrap +*************** .[perex] -Окремі компоненти Nette налаштовуються за допомогою конфігураційних файлів. Ми покажемо, як завантажити ці файли. +Окремі компоненти Nette налаштовуються за допомогою конфігураційних файлів. Ми покажемо, як завантажувати ці файли. .[tip] -Якщо ви використовуєте весь фреймворк, більше нічого робити не потрібно. У проєкті у вас є заздалегідь підготовлений каталог `config/` для файлів конфігурації, а за їхнє завантаження відповідає [завантажувач програми |application:bootstrap#DI-Container-Configuration]. -Ця стаття призначена для користувачів, які використовують тільки одну бібліотеку Nette і хочуть скористатися перевагами конфігураційних файлів. +Якщо ви використовуєте весь фреймворк, нічого додаткового робити не потрібно. У проекті є підготовлений каталог `config/` для конфігураційних файлів, а за їх завантаження відповідає [завантажувач застосунку |application:bootstrapping#Конфігурація DI-контейнера]. Ця стаття призначена для користувачів, які використовують лише одну бібліотеку Nette і хочуть скористатися можливостями конфігураційних файлів. -Файли конфігурації зазвичай пишуться у форматі [NEON |neon:format] і найкраще редагуються в [редакторах з його підтримкою |best-practices:editors-and-tools#IDE-Editor]. Їх можна розглядати як інструкції щодо **створення та конфігурування** об'єктів. Таким чином, результатом завантаження конфігурації буде так звана фабрика, що являє собою об'єкт, який на вимогу буде створювати інші об'єкти для подальшого використання. Наприклад, підключення до бази даних тощо. +Конфігураційні файли зазвичай записуються у [форматі NEON|neon:format] і найкраще редагуються в [редакторах з його підтримкою |best-practices:editors-and-tools#IDE редактор]. Їх можна розглядати як інструкції щодо **створення та конфігурації** об'єктів. Отже, результатом завантаження конфігурації буде так звана фабрика, тобто об'єкт, який за запитом створить для нас інші об'єкти, які ми хочемо використовувати. Наприклад, з'єднання з базою даних тощо. -Ця фабрика також називається *контейнером ін'єкції залежностей* (DI-контейнером), і якщо вас цікавлять подробиці, прочитайте главу [Впровадження залежностей |dependency-injection:]. +Ця фабрика також називається *dependency injection контейнером* (DI container), і якщо вас цікавлять подробиці, прочитайте розділ про [dependency injection |dependency-injection:]. -Завантаженням конфігурації та створенням контейнера займається клас [api:Nette\Bootstrap\Configurator], тому спочатку ми встановимо його пакет `nette/bootstrap`: +Завантаження конфігурації та створення контейнера забезпечує клас [api:Nette\Bootstrap\Configurator], тому спочатку встановимо його пакет `nette/bootstrap`: ```shell composer require nette/bootstrap ``` -І створіть екземпляр класу `Configurator`. Оскільки згенерований DI-контейнер буде кешуватися на диск, необхідно задати шлях до директорії, в якій він буде збережений: +І створимо екземпляр класу `Configurator`. Оскільки згенерований DI-контейнер буде кешуватися на диск, необхідно вказати шлях до каталогу, де він буде зберігатися: ```php $configurator = new Nette\Bootstrap\Configurator; $configurator->setTempDirectory(__DIR__ . '/temp'); ``` -У Linux або macOS встановіть [дозвіл на запис |nette:troubleshooting#Setting-Directory-Permissions] для каталогу `temp/`. +На Linux або macOS встановіть для каталогу `temp/` [права на запис |nette:troubleshooting#Налаштування прав доступу до каталогів]. -І ми переходимо до самих конфігураційних файлів. Вони завантажуються за допомогою функції `addConfig()`: +І ми підходимо до самих конфігураційних файлів. Їх завантажуємо за допомогою `addConfig()`: ```php $configurator->addConfig(__DIR__ . '/database.neon'); ``` -Якщо ви хочете додати більше конфігураційних файлів, ви можете викликати функцію `addConfig()` кілька разів. Якщо у файлах з'являться елементи з однаковими ключами, вони будуть перезаписані (або [об'єднані |dependency-injection:configuration#Merging] у випадку з масивами). Пізніше доданий файл має вищий пріоритет, ніж попередній. +Якщо ми хочемо додати більше конфігураційних файлів, можемо викликати функцію `addConfig()` кілька разів. Якщо у файлах з'являться елементи з однаковими ключами, вони будуть перезаписані (або у випадку масивів [об'єднані |dependency-injection:configuration#Об єднання]). Файл, вставлений пізніше, має вищий пріоритет, ніж попередній. -Останній крок - створення контейнера DI: +Останнім кроком є створення DI-контейнера: ```php $container = $configurator->createContainer(); ``` -А він уже створить для нас бажані об'єкти. Наприклад, якщо ви використовуєте конфігурацію для [Nette Database |database:configuration], ви можете попросити її створити з'єднання з базою даних: +І він уже створить для нас необхідні об'єкти. Наприклад, якщо ви використовуєте конфігурацію для [Nette Database|database:configuration], ви можете попросити його створити з'єднання з базою даних: ```php $db = $container->getByType(Nette\Database\Connection::class); // або $explorer = $container->getByType(Nette\Database\Explorer::class); -// або при створенні декількох з'єднань +// або при створенні кількох з'єднань $db = $container->getByName('database.main.connection'); ``` І тепер ви можете працювати з базою даних! -Режим розробки та режим виробництва .[#toc-development-vs-production-mode] --------------------------------------------------------------------------- +Режим розробки проти робочого режиму +------------------------------------ -У режимі розробки контейнер автоматично оновлюється при кожній зміні конфігураційних файлів. У режимі виробництва він генерується тільки один раз, і зміни не перевіряються. -Таким чином, режим розробника націлений на максимальну зручність програміста, а режим виробництва - на продуктивність. +У режимі розробки контейнер автоматично оновлюється при кожній зміні конфігураційних файлів. У робочому режимі він генерується лише один раз, і зміни не перевіряються. Отже, режим розробки орієнтований на максимальну зручність програміста, а робочий — на швидкодію та розгортання. -Вибір режиму здійснюється шляхом автовизначення, тому зазвичай немає необхідності налаштовувати або перемикати що-небудь вручну. Режим розробки використовується, коли застосунок запущено на локальному хості (тобто IP-адресу `127.0.0.1` або `::1`) і відсутній проксі-сервер (тобто його HTTP-заголовок). В іншому разі застосунок працює у виробничому ("бойовому") режимі. +Вибір режиму здійснюється автовизначенням, тому зазвичай не потрібно нічого конфігурувати або вручну перемикати. Режим є розробницьким, якщо застосунок запущено на localhost (тобто IP-адреса `127.0.0.1` або `::1`) і немає проксі (тобто його HTTP-заголовка). В іншому випадку він працює в робочому режимі. -Якщо ви хочете ввімкнути режим розроблення в інших випадках, наприклад, коли програмісти отримують доступ із певної IP-адреси, використовуйте `setDebugMode()`: +Якщо ми хочемо увімкнути режим розробки і в інших випадках, наприклад, для програмістів, які підключаються з конкретної IP-адреси, використовуємо `setDebugMode()`: ```php $configurator->setDebugMode('23.75.345.200'); -// також може бути вказано масив IP-адрес +// можна також вказати масив IP-адрес ``` -Ми безумовно рекомендуємо поєднувати IP-адресу з файлом кукі. Зберігайте секретний токен, наприклад, `secret1234`, у кукі `nette-debug`, і в такий спосіб ви ввімкнете режим розроблення для програмістів, які отримують доступ із певної IP-адреси і також мають токен, зазначений у кукі: +Ми наполегливо рекомендуємо поєднувати IP-адресу з cookie. У cookie `nette-debug` збережемо секретний токен, наприклад, `secret1234`, і таким чином активуємо режим розробки для програмістів, які підключаються з конкретної IP-адреси і водночас мають зазначений токен у cookie: ```php $configurator->setDebugMode('secret1234@23.75.345.200'); ``` -Ви також можете повністю вимкнути режим розробника, навіть для localhost: +Режим розробки можна також повністю вимкнути, навіть для localhost: ```php $configurator->setDebugMode(false); ``` -Параметри .[#toc-parameters] ----------------------------- +Параметри +--------- -Ви також можете використовувати параметри в конфігураційних файлах, які визначаються [в розділі `параметры`. |dependency-injection:configuration#Parameters] +У конфігураційних файлах ви також можете використовувати параметри, які визначаються [у секції `parameters` |dependency-injection:configuration#Параметри]. -Вони також можуть бути вставлені ззовні за допомогою методу `addDynamicParameters()`: +Їх також можна вставляти ззовні за допомогою методу `addDynamicParameters()`: ```php $configurator->addDynamicParameters([ @@ -95,7 +93,4 @@ $configurator->addDynamicParameters([ ]); ``` -На параметр `projectId` можна посилатися в конфігурації за допомогою нотації `%projectId%`. - - -{{leftbar: nette:@menu-topics}} +На параметр `projectId` можна посилатися в конфігурації записом `%projectId%`. diff --git a/bootstrap/uk/@meta.texy b/bootstrap/uk/@meta.texy new file mode 100644 index 0000000000..083a8ab9f7 --- /dev/null +++ b/bootstrap/uk/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Документація Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/caching/bg/@home.texy b/caching/bg/@home.texy index 5d26a7cd5c..8c04a7e659 100644 --- a/caching/bg/@home.texy +++ b/caching/bg/@home.texy @@ -1,90 +1,90 @@ -Кеширане -******** +Nette Caching +************* <div class=perex> -Кеширането ускорява приложението ви, като запазва данни - веднъж извлечени - за бъдеща употреба. Ние ви показваме: +Кешът ускорява вашето приложение, като съхранява данни, които са били трудно получени веднъж, за бъдеща употреба. Ще ви покажем: -- Как да използвате кеша -- Как да промените съхранението на кеша -- Как да обявите кеша за невалиден правилно +- как да използвате кеша +- как да промените хранилището +- как правилно да инвалидирате кеша </div> -Използването на кеша в Nette е много просто, като същевременно покрива и много сложни нужди, свързани с кеша. Той е проектиран с оглед на производителността и 100% издръжливост. Основно ще намерите адаптери за най-често срещаните вътрешни хранилища. Позволява обезсилване на базата на тагове, защита на кеша, времеви интервал и др. +Използването на кеша в Nette е много лесно, като същевременно покрива и много напреднали нужди. Той е проектиран за производителност и 100% устойчивост. В основата му ще намерите адаптери за най-често срещаните бекенд хранилища. Позволява инвалидация, базирана на тагове, изтичане на времето, има защита срещу cache stampede и др. -Инсталация .[#toc-installation] -=============================== +Инсталация +========== -Изтеглете и инсталирайте пакета с помощта на [Composer |best-practices:composer]: +Изтеглете и инсталирайте библиотеката с помощта на [Composer|best-practices:composer]: ```shell composer require nette/caching ``` -Използване на .[#toc-basic-usage] -================================= - -Центърът на операцията за кеширане е обектът [api:Nette\Caching\Cache]. Създаваме негова инстанция и подаваме така нареченото хранилище като параметър на конструктора. Това е обект, представляващ мястото, където ще се съхраняват физически данните (база данни, Memcached, файлове на диска, ...). Получавате обект на хранилище, като му предавате [имплементация на зависимост |dependency-injection:passing-dependencies] с тип `Nette\Caching\Storage`. Всичко необходимо ще намерите в [раздел Складове |#Хранилища]. - -За следващите примери нека предположим, че имаме псевдоним `Cache` и хранилище в променливата `$storage`. +Основна употреба +================ +Централният елемент на работата с кеша е обектът [api:Nette\Caching\Cache]. Създаваме негова инстанция и предаваме на конструктора така нареченото хранилище като параметър. Това е обект, представляващ мястото, където данните ще се съхраняват физически (база данни, Memcached, файлове на диска, ...). Достъп до хранилището получаваме, като го поискаме чрез [dependency injection |dependency-injection:passing-dependencies] с тип `Nette\Caching\Storage`. Всичко съществено ще научите в [раздела Хранилища |#Хранилища]. +.[warning] +Във версия 3.0 интерфейсът все още имаше префикс `I`, така че името беше `Nette\Caching\IStorage`. Освен това константите на класа `Cache` бяха написани с главни букви, така че например `Cache::EXPIRE` вместо `Cache::Expire`. +За следващите примери да предположим, че имаме създаден псевдоним `Cache` и в променливата `$storage` - хранилище. ```php use Nette\Caching\Cache; -$storage = /* ... */; // екземпляр Nette\Caching\Storage +$storage = /* ... */; // инстанция на Nette\Caching\Storage ``` -Кешът на практика е тип хранилище *ключ-стойност*, така че четем и записваме данни по ключове по същия начин, както при асоциативните масиви. Приложенията се състоят от няколко независими части и ако всички те използват едно и също хранилище (напр. една директория на диска), рано или късно ще се стигне до сблъсък на ключове. Рамката Nette решава този проблем, като разделя цялото пространство на пространства от имена (поддиректории). В този случай всяка част от програмата използва свое собствено пространство с уникално име и не се получават колизии. +Кешът е всъщност *key–value store*, тоест четем и записваме данни под ключове, точно както при асоциативните масиви. Приложенията се състоят от редица независими части и ако всички те използват едно хранилище (представете си една директория на диска), рано или късно ще възникне колизия на ключове. Nette Framework решава проблема, като разделя цялото пространство на именни пространства (поддиректории). Всяка част от програмата използва свое пространство с уникално име и вече не може да възникне колизия. -Името на пространството се задава като втори параметър на конструктора на класа Cache: +Името на пространството се указва като втори параметър на конструктора на класа Cache: ```php $cache = new Cache($storage, 'Full Html Pages'); ``` -Сега можем да използваме обекта `$cache`, за да четем и записваме от кеша. Методът `load()` се използва както за . Първият аргумент е ключът, а вторият е обратна връзка на PHP, която се извиква, когато ключът не е намерен в кеша. Обратното извикване генерира стойност, връща я и я кешира: +Сега можем да използваме обекта `$cache` за четене и запис в кеша. За двете цели се използва методът `load()`. Първият аргумент е ключът, а вторият е PHP callback, който се извиква, когато ключът не е намерен в кеша. Callback генерира стойността, връща я и тя се записва в кеша: ```php $value = $cache->load($key, function () use ($key) { - $computedValue = /* ... */; // тежки изчисления + $computedValue = /* ... */; // сложно изчисление return $computedValue; }); ``` -Ако вторият аргумент не е посочен (`$value = $cache->load($key)`), се връща `null`, ако елементът не е в кеша. +Ако вторият параметър не е указан `$value = $cache->load($key)`, ще се върне `null`, ако елементът не е в кеша. .[tip] -Чудесното е, че може да се кешира всяка сериализируема структура, а не само низове. Същото важи и за ключовете. +Страхотно е, че в кеша могат да се съхраняват всякакви сериализуеми структури, не само низове. Същото важи дори и за ключовете. -Елементът се премахва от кеша с помощта на метода `remove()`: +Изтриваме елемент от кеша с метода `remove()`: ```php $cache->remove($key); ``` -Можете също така да кеширате елементи, като използвате метода `$cache->save($key, $value, array $dependencies = [])`. Предпочитан е обаче горепосоченият метод с използване на `load()`. +Записването на елемент в кеша може да се извърши и с метода `$cache->save($key, $value, array $dependencies = [])`. Предпочитаният начин обаче е горепосоченият чрез `load()`. -Мемоализацията .[#toc-memoization] -================================== +Мемоизация +========== -Запаметяването означава кеширане на резултата от функция или метод, за да можете да го използвате следващия път, вместо да изчислявате едно и също нещо отново и отново. +Мемоизацията означава кеширане на резултата от извикване на функция или метод, така че да можете да го използвате следващия път, без да изчислявате същото нещо отново и отново. -Методите и функциите могат да бъдат извиквани в паметта с помощта на `call(callable $callback, ...$args)`: +Методи и функции могат да бъдат извиквани мемоизирано с помощта на `call(callable $callback, ...$args)`: ```php $result = $cache->call('gethostbyaddr', $ip); ``` -Функцията `gethostbyaddr()` се извиква само веднъж за всеки параметър `$ip` и при следващия път ще бъде върната стойността от кеша. +Функцията `gethostbyaddr()` се извиква само веднъж за всеки параметър `$ip`, а следващия път стойността се връща от кеша. -Възможно е също така да се създаде мемори обвивка за метод или функция, която може да бъде извикана по-късно: +Също така е възможно да се създаде мемоизирана обвивка над метод или функция, която може да бъде извикана по-късно: ```php function factorial($num) @@ -94,17 +94,17 @@ function factorial($num) $memoizedFactorial = $cache->wrap('factorial'); -$result = $memoizedFactorial(5); // изчислява -$result = $memoizedFactorial(5); // връща се от кеша +$result = $memoizedFactorial(5); // изчислява за първи път +$result = $memoizedFactorial(5); // втори път от кеша ``` -Изтичане на срока на валидност и анулиране .[#toc-expiration-invalidation] -========================================================================== +Изтичане & инвалидация +====================== -Кеширането трябва да се справи с проблема, че някои от предварително съхранените данни в крайна сметка ще станат невалидни. Рамката Nette предоставя механизъм за ограничаване на валидността на данните и за тяхното изтриване по контролиран начин ("обезсилване" според терминологията на рамката). +При съхраняването в кеш е необходимо да се реши въпросът кога по-рано съхранените данни стават невалидни. Nette Framework предлага механизъм за ограничаване на валидността на данните или за тяхното контролирано изтриване (в терминологията на framework-а „инвалидиране“). -Валидността на данните се задава в момента на записването с помощта на третия параметър на метода `save()`, напр: +Валидността на данните се задава в момента на записване чрез третия параметър на метода `save()`, напр.: ```php $cache->save($key, $value, [ @@ -112,7 +112,7 @@ $cache->save($key, $value, [ ]); ``` -Или чрез използване на параметъра `$dependencies`, предаден като референция в обратната връзка към метода `load()`, напр: +Или чрез параметъра `$dependencies`, предаден по референция към callback-а на метода `load()`, напр.: ```php $value = $cache->load($key, function (&$dependencies) { @@ -121,7 +121,7 @@ $value = $cache->load($key, function (&$dependencies) { }); ``` -Или като използвате третия параметър в метода `load()`, напр: +Или чрез 3-тия параметър в метода `load()`, напр: ```php $value = $cache->load($key, function () { @@ -129,26 +129,26 @@ $value = $cache->load($key, function () { }, [Cache::Expire => '20 minutes']); ``` -В следващите примери ще приемем втория вариант и следователно съществуването на променливата `$dependencies`. +В следващите примери ще предположим втория вариант и следователно съществуването на променливата `$dependencies`. -Срок .[#toc-expiration] ------------------------ +Изтичане +-------- -Най-простото изключение е ограничението във времето. Ето как да кеширате данни, валидни за 20 минути: +Най-простото изтичане е времевият лимит. По този начин съхраняваме данни в кеша с валидност 20 минути: ```php -// можете също да подадете брой секунди или времеви печат на UNIX +// приема също брой секунди или UNIX timestamp $dependencies[Cache::Expire] = '20 minutes'; ``` -Ако искаме да увеличим периода на валидност за всяко четене, това може да се постигне по този начин, но имайте предвид, че това ще увеличи натоварването на кеша: +Ако искаме да удължим срока на валидност при всяко четене, това може да се постигне по следния начин, но внимавайте, режийните разходи на кеша ще се увеличат: ```php $dependencies[Cache::Sliding] = true; ``` -Удобна опция е да позволите данните да изтичат, когато определен файл или един от няколко файла бъде променен. Това може да се използва например за кеширане на данни, получени при обработката на тези файлове. Използвайте абсолютни пътища: +Удобна е възможността данните да изтекат в момента, в който се промени файл или някой от няколко файла. Това може да се използва например при съхраняване на данни, възникнали при обработката на тези файлове, в кеша. Използвайте абсолютни пътища. ```php $dependencies[Cache::Files] = '/path/to/data.yaml'; @@ -156,13 +156,13 @@ $dependencies[Cache::Files] = '/path/to/data.yaml'; $dependencies[Cache::Files] = ['/path/to/data1.yaml', '/path/to/data2.yaml']; ``` -Можем да позволим на даден елемент в кеша да изтече, когато изтече срокът на друг елемент (или на един от няколко други). Това може да се използва, когато кешираме цялата HTML страница и нейните фрагменти под други ключове. Щом фрагментът се промени, цялата страница става невалидна. Ако имаме фрагменти, съхранени под ключове като `frag1` и `frag2`, ще използваме +Можем да накараме елемент в кеша да изтече в момента, в който изтече друг елемент (или някой от няколко други). Това може да се използва, когато съхраняваме в кеша например цяла HTML страница и под други ключове нейните фрагменти. Щом фрагментът се промени, цялата страница се инвалидира. Ако фрагментите са съхранени под ключове напр. `frag1` и `frag2`, използваме: ```php $dependencies[Cache::Items] = ['frag1', 'frag2']; ``` -Валидността може да се контролира и с потребителски функции или статични методи, които винаги решават дали елементът е валиден при четене. Например можем да позволим на даден елемент да изтече, когато версията на PHP се промени. Ще създадем функция, която сравнява текущата версия с параметър, а при запазване добавяме масив като `[имя функции, ...аргументы]` към зависимостите: +Изтичането може да се контролира и с помощта на персонализирани функции или статични методи, които при всяко четене решават дали елементът е все още валиден. По този начин например можем да накараме елемент да изтече винаги, когато се промени версията на PHP. Създаваме функция, която сравнява текущата версия с параметъра, и при записване добавяме към зависимостите масив във формат `[име на функция, ...аргументи]`: ```php function checkPhpVersion($ver): bool @@ -171,11 +171,11 @@ function checkPhpVersion($ver): bool } $dependencies[Cache::Callbacks] = [ - ['checkPhpVersion', PHP_VERSION_ID] // истекает, когато checkPhpVersion(...) === false + ['checkPhpVersion', PHP_VERSION_ID] // изтече, когато checkPhpVersion(...) === false ]; ``` -Разбира се, всички критерии могат да се комбинират. Кешът изтича, ако поне един критерий не е изпълнен. +Всички критерии, разбира се, могат да се комбинират. Кешът тогава изтича, когато поне един критерий не е изпълнен. ```php $dependencies[Cache::Expire] = '20 minutes'; @@ -183,16 +183,16 @@ $dependencies[Cache::Files] = '/path/to/data.yaml'; ``` -Инвалидиране с помощта на етикети .[#toc-invalidation-using-tags] ------------------------------------------------------------------ +Инвалидация чрез тагове +----------------------- -Етикетите са много полезен инструмент за обезсилване. Можем да зададем списък с тагове, които са произволни низове, на всеки елемент, съхраняван в кеша. Например, да предположим, че имаме HTML страница със статия и коментари, която искаме да кешираме. Така че посочваме таговете, когато го записваме в кеша: +Много полезен инструмент за инвалидация са така наречените тагове. Към всеки елемент в кеша можем да присвоим списък с тагове, които са произволни низове. Да вземем например HTML страница със статия и коментари, която ще кешираме. При записване посочваме таговете: ```php $dependencies[Cache::Tags] = ["article/$articleId", "comments/$articleId"]; ``` -Сега нека преминем към администрацията. Тук имаме форма за редактиране на статията. Заедно със записването на статията в базата данни извикваме командата `clean()`, която премахва кешираните елементи чрез маркиране: +Да се преместим в администрацията. Тук намираме форма за редактиране на статия. Заедно със записването на статията в базата данни извикваме командата `clean()`, която изтрива от кеша елементи според тага: ```php $cache->clean([ @@ -200,7 +200,7 @@ $cache->clean([ ]); ``` -По подобен начин, в момента на добавяне на нов коментар (или редактиране на коментар), ще не забравяме да отменим съответния таг: +По същия начин, на мястото на добавяне на нов коментар (или редактиране на коментар) не забравяме да инвалидираме съответния таг: ```php $cache->clean([ @@ -208,22 +208,22 @@ $cache->clean([ ]); ``` -Какво сме постигнали? Че нашият HTML кеш ще бъде анулиран (изтрит) всеки път, когато променим статия или коментар. При редактиране на статия с ID = 10 тагът `article/10` се обезсилва принудително и HTML страницата, съдържаща този таг, се премахва от кеша. Същото се случва, когато се вмъква нов коментар под съответната статия. +Какво постигнахме с това? Че HTML кешът ще се инвалидира (изтрива), когато статията или коментарите се променят. Когато се редактира статия с ID = 10, се извършва принудителна инвалидация на тага `article/10` и HTML страницата, която носи посочения таг, се изтрива от кеша. Същото се случва и при вмъкване на нов коментар под съответната статия. .[note] -Таговете се изискват от [списанието |#Журнал]. +Таговете изискват така наречения [#Journal]. -Инвалидност по приоритет .[#toc-invalidation-by-priority] ---------------------------------------------------------- +Инвалидация чрез приоритет +-------------------------- -Можем да зададем приоритет на отделните елементи в кеша и те да бъдат премахвани контролирано, когато например кешът надхвърли определен размер: +На отделните елементи в кеша можем да зададем приоритет, с който ще може да ги изтриваме, когато например кешът надхвърли определен размер: ```php $dependencies[Cache::Priority] = 50; ``` -Изтриване на всички елементи с приоритет, равен на или по-малък от 100: +Изтриваме всички елементи с приоритет равен или по-малък от 100: ```php $cache->clean([ @@ -232,13 +232,13 @@ $cache->clean([ ``` .[note] -Приоритетите също изискват [дневник |#Журнал]. +Приоритетите изискват така наречения [#Journal]. -Почистване на кеша .[#toc-clear-cache] --------------------------------------- +Изтриване на кеша +----------------- -Параметърът `Cache::All` изчиства всичко: +Параметърът `Cache::All` изтрива всичко: ```php $cache->clean([ @@ -247,86 +247,105 @@ $cache->clean([ ``` -Масово четене .[#toc-bulk-reading] -================================== +Групово четене +============== -Методът `bulkLoad()`, при който подаваме масив от ключове и получаваме масив от стойности, се използва за масово четене и запис в кеша: +За групово четене и запис в кеша се използва методът `bulkLoad()`, на който предаваме масив от ключове и получаваме масив от стойности: ```php $values = $cache->bulkLoad($keys); ``` -Методът `bulkLoad()` работи подобно на `load()` с втори параметър за обратно извикване, на който се предава ключът на генерирания елемент: +Методът `bulkLoad()` работи подобно на `load()` и с втория параметър callback, на който се предава ключът на генерирания елемент: ```php $values = $cache->bulkLoad($keys, function ($key, &$dependencies) { - $computedValue = /* ... */; // тяжёлые вычисления + $computedValue = /* ... */; // сложно изчисление return $computedValue; }); ``` -Изходно кеширане .[#toc-output-caching] -======================================= +Използване с PSR-16 .{data-version:3.3.1} +========================================= + +За използване на Nette Cache с интерфейса PSR-16 можете да използвате адаптера `PsrCacheAdapter`. Той позволява безпроблемна интеграция между Nette Cache и всеки код или библиотека, която очаква PSR-16 съвместим кеш. + +```php +$psrCache = new Nette\Bridges\Psr\PsrCacheAdapter($storage); +``` + +Сега можете да използвате `$psrCache` като PSR-16 кеш: + +```php +$psrCache->set('key', 'value', 3600); // съхранява стойността за 1 час +$value = $psrCache->get('key', 'default'); +``` + +Адаптерът поддържа всички методи, дефинирани в PSR-16, включително `getMultiple()`, `setMultiple()` и `deleteMultiple()`. + + +Кеширане на изхода +================== -Изходните данни могат да бъдат улавяни и кеширани много елегантно: +Много елегантно може да се улавя и кешира изходът: ```php if ($capture = $cache->capture($key)) { - echo ... // извеждане на някои данни + echo ... // изписваме данни - $capture->end(); // съхранявайте резултатите в кеша + $capture->end(); // записваме изхода в кеша } ``` -В случай че изходът вече присъства в кеша, методът `capture()` го отпечатва и връща `null`, така че условието няма да бъде изпълнено. В противен случай той започва да буферира изхода и връща обекта `$capture`, с който накрая записваме данните в кеша. +В случай, че изходът вече е съхранен в кеша, методът `capture()` го изписва и връща `null`, така че условието не се изпълнява. В противен случай започва да улавя изхода и връща обект `$capture`, с помощта на който накрая записваме изписаните данни в кеша. -/--comment -\-- +.[note] +Във версия 3.0 методът се наричаше `$cache->start()`. -Кеширане в Latte .[#toc-caching-in-latte] -========================================= +Кеширане в Latte +================ -Създаването на кеширане в шаблоните [Latte |latte:] е много лесно, просто обвийте част от шаблона с тагове `{cache}...{/cache}`. Кешът се анулира автоматично при промяна на оригиналния шаблон (включително всички включени шаблони в таговете `{cache}`). Етикетите `{cache}` могат да бъдат вложени и когато вложен блок бъде обезсилен (напр. чрез етикет), родителският блок също се обезсилва. +Кеширането в шаблоните [Latte|latte:] е много лесно, достатъчно е част от шаблона да се обвие в тагове `{cache}...{/cache}`. Кешът се инвалидира автоматично в момента, в който се промени изходният шаблон (включително евентуални включени шаблони вътре в кеш блока). Таговете `{cache}` могат да се влагат един в друг и когато вложен блок се инвалидира (например с таг), се инвалидира и родителският блок. -В тага можете да посочите ключовете, към които ще бъде прикрепен кешът (тук променлива `$id`), и да зададете периода на валидност и [тага за обезсилване |#Инвалидация с использованием тегов]. +В тага е възможно да се посочат ключове, към които ще се обвърже кешът (тук променливата `$id`) и да се зададе изтичане и [тагове за инвалидация |#Инвалидация чрез тагове] ```latte -{cache $id, expire => '20 minutes', tags => [tag1, tag2]} +{cache $id, expire: '20 minutes', tags: [tag1, tag2]} ... {/cache} ``` -Всички опции не са задължителни, така че не е необходимо да посочвате дата на изтичане, тагове или ключове. +Всички елементи са незадължителни, така че не е необходимо да посочваме нито изтичане, нито тагове, нито дори ключове. -Използването на кеша може да бъде обусловено и от `if` - съдържанието ще бъде кеширано само ако условието е изпълнено: +Използването на кеша може да бъде обусловено и с помощта на `if` - съдържанието тогава ще се кешира само ако условието е изпълнено: ```latte -{cache $id, if => !$form->isSubmitted()} +{cache $id, if: !$form->isSubmitted()} {$form} {/cache} ``` -Магазини .[#toc-hranilisa] -========================== +Хранилища +========= -Хранилището е обект, който представлява мястото, където данните се съхраняват физически. Можем да използваме база данни, сървър Memcached или най-достъпното хранилище - файловете на диска. +Хранилището е обект, представляващ мястото, където данните се съхраняват физически. Можем да използваме база данни, сървър Memcached или най-достъпното хранилище, което са файлове на диска. -|---------------------- -| Съхранение | Описание -|---------------------- -| [FileStorage |#FileStorage] | съхранение по подразбиране на файлове на диска -| [MemcachedStorage |#MemcachedStorage] | използване на сървър `Memcached -| [MemoryStorage |#MemoryStorage] | данните се съхраняват временно в паметта -| [SQLiteStorage |#SQLiteStorage] | данните се съхраняват в база данни SQLite -| [DevNullStorage |#DevNullStorage] | не се съхраняват никакви данни - за целите на тестването +|----------------- +| Хранилище | Описание +|----------------- +| [#FileStorage] | хранилище по подразбиране със съхранение във файлове на диска +| [#MemcachedStorage] | използва `Memcached` сървър +| [#MemoryStorage] | данните са временно в паметта +| [#SQLiteStorage] | данните се съхраняват в SQLite база данни +| [#DevNullStorage] | данните не се съхраняват, подходящо за тестване -Получавате обект за съхранение, като го предавате чрез [реализиране на зависимости |dependency-injection:passing-dependencies] с тип `Nette\Caching\Storage`. По подразбиране Nette предоставя обект FileStorage, който съхранява данни в подпапка `cache` в директорията за [временни файлове |application:bootstrap#Temporary-Files]. +Достъп до обекта на хранилището получавате, като го поискате чрез [dependency injection |dependency-injection:passing-dependencies] с тип `Nette\Caching\Storage`. Като хранилище по подразбиране Nette предоставя обект FileStorage, съхраняващ данни в поддиректория `cache` в директорията за [временни файлове |application:bootstrapping#Временни файлове]. -Можете да промените мястото за съхранение в конфигурацията: +Можете да промените хранилището в конфигурацията: ```neon services: @@ -334,31 +353,28 @@ services: ``` -Съхранение на файлове .[#toc-filestorage] ------------------------------------------ +FileStorage +----------- -Записва кеш във файлове на диска. FileStorage `Nette\Caching\Storages\FileStorage` е много добре оптимизиран за производителност и преди всичко осигурява пълна атомичност на операциите. Какво означава това? За да не се окаже, че при използване на кеша ще прочетем файл, който все още не е бил изцяло записан от друга нишка, или че някой го е изтрил "на ръка". Ето защо използването на кеша е напълно безопасно. +Записва кеша във файлове на диска. Хранилището `Nette\Caching\Storages\FileStorage` е много добре оптимизирано за производителност и преди всичко осигурява пълна атомарност на операциите. Какво означава това? Че при използване на кеша не може да се случи да прочетем файл, който все още не е напълно записан от друг поток, или някой да го изтрие "под носа ни". Използването на кеша е напълно безопасно. -Това хранилище има и важна вградена функция, която предотвратява екстремно увеличаване на натоварването на процесора, когато кешът е изчистен или охладен (т.е. не е създаден). Това е "профилактично" "избутване на кеша:https://en.wikipedia.org/wiki/Cache_stampede_". -Случва се в един момент да постъпят няколко едновременни заявки, които искат да получат едно и също нещо от кеша (например резултата от голяма SQL заявка), и тъй като той не е кеширан, всички процеси започват да изпълняват същата SQL заявка. -Натоварването на процесора се увеличава неколкократно и дори може да се случи така, че нито една нишка да не отговори в определеното време, кешът да не бъде създаден и приложението да се срине. -За щастие кешът в Nette работи по такъв начин, че ако има няколко едновременни заявки за един и същ елемент, той се генерира само от първата нишка, а останалите изчакват и след това използват генерирания резултат. +Това хранилище има и вградена важна функция, която предотвратява екстремно нарастване на използването на CPU в момента, когато кешът се изтрие или все още не е загрят (т.е. създаден). Това е превенция срещу "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Случва се в един момент да се съберат по-голям брой едновременни заявки, които искат от кеша едно и също нещо (например резултат от скъпа SQL заявка) и тъй като то не е в кеша, всички процеси започват да изпълняват същата SQL заявка. Натоварването се умножава и дори може да се случи нито един поток да не успее да отговори в рамките на времевия лимит, кешът да не се създаде и приложението да се срине. За щастие, кешът в Nette работи така, че при повече едновременни заявки за един елемент, той се генерира само от първия поток, останалите чакат и след това използват генерирания резултат. Пример за създаване на FileStorage: ```php -//съхранението ще бъде директорията '/path/to/temp' на устройството +// хранилището ще бъде директория '/path/to/temp' на диска $storage = new Nette\Caching\Storages\FileStorage('/path/to/temp'); ``` -MemcachedStorage .[#toc-memcachedstorage] ------------------------------------------ +MemcachedStorage +---------------- -Сървърът [Memcached |https://memcached.org] е високопроизводителна разпределена система за съхранение, чийто адаптер е `Nette\Caching\Storages\MemcachedStorage`. В конфигурацията посочете IP адреса и порта, ако са различни от стандартните 11211. +Сървърът [Memcached|https://memcached.org] е високопроизводителна система за съхранение в разпределена памет, чийто адаптер е `Nette\Caching\Storages\MemcachedStorage`. В конфигурацията посочваме IP адрес и порт, ако се различават от стандартния 11211. .[caution] -Изисква разширението на PHP `memcached`. +Изисква PHP разширение `memcached`. ```neon services: @@ -366,19 +382,19 @@ services: ``` -ПаметЗапаметяване .[#toc-memorystorage] ---------------------------------------- +MemoryStorage +------------- -`Nette\Caching\Storages\MemoryStorage` е хранилище, което съхранява данни в масив на PHP и по този начин се губи при прекратяване на заявката. +`Nette\Caching\Storages\MemoryStorage` е хранилище, което съхранява данни в PHP масив, и следователно те се губят с прекратяването на заявката. -SQLiteStorage .[#toc-memorystorage] ------------------------------------ +SQLiteStorage +------------- -Базата данни SQLite и адаптерът `Nette\Caching\Storages\SQLiteStorage` предлагат начин за кеширане в един файл на диска. В конфигурацията ще бъде посочен пътят до този файл. +Базата данни SQLite и адаптерът `Nette\Caching\Storages\SQLiteStorage` предлагат начин за съхраняване на кеша в един файл на диска. В конфигурацията посочваме пътя до този файл. .[caution] -Необходими са разширения на PHP `pdo` и `pdo_sqlite`. +Изисква PHP разширения `pdo` и `pdo_sqlite`. ```neon services: @@ -386,16 +402,16 @@ services: ``` -DevNullStorage .[#toc-devnullstorage] -------------------------------------- +DevNullStorage +-------------- -Специална реализация на съхранението е `Nette\Caching\Storages\DevNullStorage`, която всъщност не съхранява никакви данни. Следователно той е подходящ за тестване, ако искаме да елиминираме влиянието на кеша. +Специална имплементация на хранилище е `Nette\Caching\Storages\DevNullStorage`, което всъщност изобщо не съхранява данни. Подходящо е за тестване, когато искаме да елиминираме влиянието на кеша. -Използване на кеша в кода .[#toc-devnullstorage] -================================================ +Използване на кеша в кода +========================= -Когато използвате кеширане в кода, имате два начина да го направите. Първият е, че получавате обекта на хранилището, като го предавате чрез [инжектиране на зависимости |dependency-injection:passing-dependencies], и след това създавате обект `Cache`: +При използване на кеша в кода имаме два начина да го направим. Първият е да поискаме хранилището чрез [dependency injection |dependency-injection:passing-dependencies] и да създадем обект `Cache`: ```php use Nette; @@ -411,7 +427,7 @@ class ClassOne } ``` -Вторият начин е да получите обекта за съхранение `Cache`: +Втората възможност е директно да поискаме обект `Cache`: ```php class ClassTwo @@ -423,7 +439,7 @@ class ClassTwo } ``` -След това обектът `Cache` се създава директно в конфигурацията, както следва +Обектът `Cache` след това се създава директно в конфигурацията по следния начин: ```neon services: @@ -431,12 +447,12 @@ services: ``` -Log .[#toc-zurnal] -================== +Journal +======= -Nette съхранява етикетите и приоритетите в т.нар. регистрационен файл. По подразбиране се използва SQLite и файлът `journal.s3db`. Освен това разширенията на PHP `pdo` и `pdo_sqlite` са **задължителни. +Nette съхранява тагове и приоритети в така наречения journal. Стандартно за това се използва SQLite и файл `journal.s3db` и **се изискват PHP разширения `pdo` и `pdo_sqlite`.** -Можете да промените дневника в конфигурацията: +Можете да промените journal-а в конфигурацията: ```neon services: @@ -444,4 +460,25 @@ services: ``` -{{leftbar: nette:@menu-topics}} +DI Сървиси +========== + +Тези сървиси се добавят към DI контейнера: + +| Име | Тип | Описание +|---------------------------------------------------------- +| `cache.journal` | [api:Nette\Caching\Storages\Journal] | journal +| `cache.storage` | [api:Nette\Caching\Storage] | хранилище + + +Изключване на кеша +================== + +Една от възможностите за изключване на кеша в приложението е да се зададе като хранилище [#DevNullStorage]: + +```neon +services: + cache.storage: Nette\Caching\Storages\DevNullStorage +``` + +Тази настройка не влияе на кеширането на шаблони в Latte или DI контейнера, тъй като тези библиотеки не използват услугите на nette/caching и управляват кеша си самостоятелно. Техният кеш впрочем [не е необходимо да се изключва |nette:troubleshooting#Как да изключите кеша по време на разработка] в режим на разработка. diff --git a/caching/bg/@meta.texy b/caching/bg/@meta.texy new file mode 100644 index 0000000000..794cbc8522 --- /dev/null +++ b/caching/bg/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Документация на Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/caching/cs/@home.texy b/caching/cs/@home.texy index da7442c05c..17ddcaa49f 100644 --- a/caching/cs/@home.texy +++ b/caching/cs/@home.texy @@ -1,9 +1,9 @@ -Cache -***** +Nette Caching +************* <div class=perex> -Cache `[keš]` zrychlí vaši aplikaci tím, že jednou náročně získaná data uloží pro příští použití. Ukážeme si: +Cache zrychlí vaši aplikaci tím, že jednou náročně získaná data uloží pro příští použití. Ukážeme si: - jak používat cache - jak změnit úložiště @@ -17,7 +17,7 @@ Používání cache je v Nette velmi snadné, přitom pokrývá i velmi pokroči Instalace ========= -Knihovnu stáhěte a nainstalujete pomocí nástroje [Composer|best-practices:composer]: +Knihovnu stáhnete a nainstalujete pomocí nástroje [Composer|best-practices:composer]: ```shell composer require nette/caching @@ -27,7 +27,7 @@ composer require nette/caching Základní použití ================ -Středobodem práce s cache neboli mezipamětí představuje objekt [api:Nette\Caching\Cache]. Vytvoříme si jeho instanci a jako parametr předáme konstruktoru tzv. úložiště. Což je objekt reprezentující místo, kam se budou data fyzicky ukládat (databáze, Memcached, soubory na disku, ...). K úložišti se dostaneme tak, že si jej necháte předat pomocí [dependency injection |dependency-injection:passing-dependencies] s typem `Nette\Caching\Storage`. Vše podstatné se dozvíte v [části Úložiště|#Úložiště]. +Středobodem práce s cache neboli mezipamětí představuje objekt [api:Nette\Caching\Cache]. Vytvoříme si jeho instanci a jako parametr předáme konstruktoru tzv. úložiště. Což je objekt reprezentující místo, kam se budou data fyzicky ukládat (databáze, Memcached, soubory na disku, ...). K úložišti se dostaneme tak, že si jej necháme předat pomocí [dependency injection |dependency-injection:passing-dependencies] s typem `Nette\Caching\Storage`. Vše podstatné se dozvíte v [části Úložiště |#Úložiště]. .[warning] Ve verzi 3.0 mělo rozhraní ještě prefix `I`, takže název byl `Nette\Caching\IStorage`. A dále konstanty třídy `Cache` byly psané velkými písmeny, takže třeba `Cache::EXPIRE` místo `Cache::Expire`. @@ -135,7 +135,7 @@ V dalších ukázkách budeme předpokládat druhou variantu a tedy existenci pr Expirace -------- -Nejjednodušší exirace představuje časový limit. Takto uložíme do cache data s platností 20 minut: +Nejjednodušší expirace představuje časový limit. Takto uložíme do cache data s platností 20 minut: ```php // akceptuje i počet sekund nebo UNIX timestamp @@ -175,7 +175,7 @@ $dependencies[Cache::Callbacks] = [ ]; ``` -Všechny kritéria je samozřejmě možné kombinovat. Cache pak vyexpiruje, když alespoň jedno kritérium není splněno. +Všechna kritéria je samozřejmě možné kombinovat. Cache pak vyexpiruje, když alespoň jedno kritérium není splněno. ```php $dependencies[Cache::Expire] = '20 minutes'; @@ -256,7 +256,7 @@ Pro hromadné čtení a zápisy do cache slouží metoda `bulkLoad()`, které p $values = $cache->bulkLoad($keys); ``` -Metoda `bulkLoad()` funguje podobně jako `load()` i s druhým parameterm callbackem, kterému se předává klíč generované položky: +Metoda `bulkLoad()` funguje podobně jako `load()` i s druhým parametrem callbackem, kterému se předává klíč generované položky: ```php $values = $cache->bulkLoad($keys, function ($key, &$dependencies) { @@ -266,6 +266,25 @@ $values = $cache->bulkLoad($keys, function ($key, &$dependencies) { ``` +Použití s PSR-16 .{data-version:3.3.1} +====================================== + +Pro použití Nette Cache s rozhraním PSR-16 můžete využít adaptér `PsrCacheAdapter`. Umožňuje bezešvou integraci mezi Nette Cache a jakýmkoli kódem nebo knihovnou, která očekává PSR-16 kompatibilní cache. + +```php +$psrCache = new Nette\Bridges\Psr\PsrCacheAdapter($storage); +``` + +Nyní můžete používat `$psrCache` jako PSR-16 cache: + +```php +$psrCache->set('key', 'value', 3600); // uloží hodnotu na 1 hodinu +$value = $psrCache->get('key', 'default'); +``` + +Adaptér podporuje všechny metody definované v PSR-16, včetně `getMultiple()`, `setMultiple()`, a `deleteMultiple()`. + + Cachování výstupu ================= @@ -324,7 +343,7 @@ Použití cache lze také podmínit pomocí `if` - obsah se pak bude cachovat po | [#SQLiteStorage] | data se ukládají do SQLite databáze | [#DevNullStorage] | data se neukládají, vhodné pro testování -K objektu úložiště se dostanete tak, že si jej necháte předat pomocí [dependency injection |dependency-injection:passing-dependencies] s typem `Nette\Caching\Storage`. Jako výchozí úložiště poskytuje Nette objekt FileStorage ukládající data do podsložky `cache` v adresáři pro [dočasné soubory|application:bootstrap#dočasné soubory]. +K objektu úložiště se dostanete tak, že si jej necháte předat pomocí [dependency injection |dependency-injection:passing-dependencies] s typem `Nette\Caching\Storage`. Jako výchozí úložiště poskytuje Nette objekt FileStorage ukládající data do podsložky `cache` v adresáři pro [dočasné soubory |application:bootstrapping#Dočasné soubory]. Změnit úložiště můžete v konfiguraci: @@ -339,10 +358,7 @@ FileStorage Zapisuje cache do souborů na disku. Úložiště `Nette\Caching\Storages\FileStorage` je velmi dobře optimalizované pro výkon a především zajišťuje plnou atomicitu operací. Co to znamená? Že při použití cache se nemůže stát, že přečteme soubor, který ještě není jiným vláknem kompletně zapsaný, nebo že by vám jej někdo "pod rukama" smazal. Použití cache je tedy zcela bezpečné. -Toto úložiště má také vestavěnou důležitou funkci, která brání před extrémním nárůstem využití CPU ve chvíli, kdy se cache smaže nebo ještě není zahřátá (tj. vytvořená). Jedná se o prevenci před "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. -Stává se, že v jednu chvíli se sejde větší počet souběžných požadavků, které chtějí z cache stejnou věc (např. výsledek drahého SQL dotazu) a protože v mezipaměti není, začnou všechny procesy vykonávat stejný SQL dotaz. -Vytížení se tak násobí a může se dokonce stát, že žádné vlákno nestihne odpovědět v časovém limitu, cache se nevytvoří a aplikace zkolabuje. -Naštěstí cache v Nette funguje tak, že při více souběžných požadavcích na jednu položku ji generuje pouze první vlákno, ostatní čekají a následně využíjí vygenerovaný výsledek. +Toto úložiště má také vestavěnou důležitou funkci, která brání před extrémním nárůstem využití CPU ve chvíli, kdy se cache smaže nebo ještě není zahřátá (tj. vytvořená). Jedná se o prevenci před "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Stává se, že v jednu chvíli se sejde větší počet souběžných požadavků, které chtějí z cache stejnou věc (např. výsledek drahého SQL dotazu) a protože v mezipaměti není, začnou všechny procesy vykonávat stejný SQL dotaz. Vytížení se tak násobí a může se dokonce stát, že žádné vlákno nestihne odpovědět v časovém limitu, cache se nevytvoří a aplikace zkolabuje. Naštěstí cache v Nette funguje tak, že při více souběžných požadavcích na jednu položku ji generuje pouze první vlákno, ostatní čekají a následně využíjí vygenerovaný výsledek. Příklad vytvoření FileStorage: @@ -395,7 +411,7 @@ Speciální implementací úložiště je `Nette\Caching\Storages\DevNullStorage Použití cache v kódu ==================== -Při používání cache v kódu máme dva způsoby, jak na to. První z nich je ten, že si necháme předat pomocí [dependency injection |dependency-injection:passing-dependencies] úložište a vytvoříme objekt `Cache`: +Při používání cache v kódu máme dva způsoby, jak na to. První z nich je ten, že si necháme předat pomocí [dependency injection |dependency-injection:passing-dependencies] úložiště a vytvoříme objekt `Cache`: ```php use Nette; @@ -444,4 +460,25 @@ services: ``` -{{leftbar: nette:@menu-topics}} +Služby DI +========= + +Tyto služby se přidávají do DI kontejneru: + +| Název | Typ | Popis +|---------------------------------------------------------- +| `cache.journal` | [api:Nette\Caching\Storages\Journal] | journal +| `cache.storage` | [api:Nette\Caching\Storage] | úložiště + + +Vypnutí cache +============= + +Jednou z možností, jak vypnout cache v aplikaci, je nastavit jako úložiště [#DevNullStorage]: + +```neon +services: + cache.storage: Nette\Caching\Storages\DevNullStorage +``` + +Toto nastavení nemá vliv na kešování šablon v Latte nebo DI kontejeru, protože tyto knihovny nevyužívají služeb nette/caching a spravují si cache samostatně. Jejich cache ostatně [není potřeba |nette:troubleshooting#Jak vypnout cache během vývoje] ve vývojářském režimu vypínat. diff --git a/caching/cs/@meta.texy b/caching/cs/@meta.texy new file mode 100644 index 0000000000..08edde785b --- /dev/null +++ b/caching/cs/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Dokumentace}} +{{leftbar: nette:@menu-topics}} diff --git a/caching/de/@home.texy b/caching/de/@home.texy index 70fab1381e..d2c2c5186a 100644 --- a/caching/de/@home.texy +++ b/caching/de/@home.texy @@ -1,38 +1,38 @@ -Zwischenspeichern -***************** +Nette Caching +************* <div class=perex> -Der Cache beschleunigt Ihre Anwendung, indem er Daten, die einmal hart abgerufen wurden, für die spätere Verwendung speichert. Wir zeigen es Ihnen: +Der Cache beschleunigt Ihre Anwendung, indem er einmal aufwendig abgerufene Daten für die zukünftige Verwendung speichert. Wir zeigen Ihnen: -- Wie Sie den Cache nutzen -- Wie Sie den Cache-Speicher ändern -- Wie man den Cache richtig ungültig macht +- wie man den Cache verwendet +- wie man den Speicher (Storage) ändert +- wie man den Cache korrekt invalidiert </div> -Die Verwendung des Cache ist in Nette sehr einfach, deckt aber auch sehr fortgeschrittene Cache-Anforderungen ab. Er ist auf Leistung und 100%ige Haltbarkeit ausgelegt. Im Grunde finden Sie Adapter für die gängigsten Backend-Speicher. Ermöglicht Tag-basierte Invalidierung, Cache-Stampede-Schutz, Zeitablauf, etc. +Die Verwendung des Caches ist in Nette sehr einfach und deckt dennoch auch sehr fortgeschrittene Anforderungen ab. Er ist auf Leistung und 100%ige Stabilität ausgelegt. Standardmäßig finden Sie Adapter für die gängigsten Backend-Speicher. Er ermöglicht die auf Tags basierende Invalidierung, Zeitablauf, Schutz vor Cache Stampede usw. -Installation .[#toc-installation] -================================= +Installation +============ -Laden Sie das Paket herunter und installieren Sie es mit [Composer |best-practices:composer]: +Sie können die Bibliothek mit dem Werkzeug [Composer|best-practices:composer] herunterladen und installieren: ```shell composer require nette/caching ``` -Grundlegende Verwendung .[#toc-basic-usage] -=========================================== +Grundlegende Verwendung +======================= -Im Mittelpunkt der Arbeit mit dem Cache steht das Objekt [api:Nette\Caching\Cache]. Wir erstellen seine Instanz und übergeben dem Konstruktor als Parameter den sogenannten Speicher. Dabei handelt es sich um ein Objekt, das den Ort repräsentiert, an dem die Daten physisch gespeichert werden (Datenbank, Memcached, Dateien auf der Festplatte, ...). Sie erhalten das Storage-Objekt, indem Sie es per [Dependency Injection |dependency-injection:passing-dependencies] mit dem Typ `Nette\Caching\Storage` übergeben. Alles Wesentliche erfahren Sie im [Abschnitt Storage |#Storages]. +Der zentrale Punkt der Arbeit mit dem Cache oder Zwischenspeicher ist das Objekt [api:Nette\Caching\Cache]. Wir erstellen eine Instanz davon und übergeben dem Konstruktor als Parameter einen sogenannten Speicher (Storage). Dies ist ein Objekt, das den Ort darstellt, an dem die Daten physisch gespeichert werden (Datenbank, Memcached, Dateien auf der Festplatte, ...). Zum Speicher gelangen wir, indem wir ihn uns mittels [Dependency Injection |dependency-injection:passing-dependencies] mit dem Typ `Nette\Caching\Storage` übergeben lassen. Alles Wesentliche erfahren Sie im [Abschnitt Speicher |#Speicher Storage]. .[warning] -In Version 3.0 hatte die Schnittstelle noch den `I` prefix, so the name was `Nette\Caching\IStorage`. Außerdem wurden die Konstanten der Klasse `Cache` großgeschrieben, also zum Beispiel `Cache::EXPIRE` statt `Cache::Expire`. +In Version 3.0 hatte das Interface noch das Präfix `I`, der Name war also `Nette\Caching\IStorage`. Außerdem wurden die Konstanten der Klasse `Cache` großgeschrieben, also z.B. `Cache::EXPIRE` statt `Cache::Expire`. -Für die folgenden Beispiele nehmen wir an, wir haben einen Alias `Cache` und eine Speicherung in der Variablen `$storage`. +Für die folgenden Beispiele nehmen wir an, dass wir einen Alias `Cache` erstellt haben und der Speicher in der Variablen `$storage` vorhanden ist. ```php use Nette\Caching\Cache; @@ -40,49 +40,49 @@ use Nette\Caching\Cache; $storage = /* ... */; // Instanz von Nette\Caching\Storage ``` -Der Cache ist eigentlich ein *Schlüsselwertspeicher*, d. h. wir lesen und schreiben Daten unter Schlüsseln, genau wie bei assoziativen Arrays. Anwendungen bestehen aus einer Reihe unabhängiger Teile, und wenn sie alle einen Speicher verwenden würden (z. B. ein Verzeichnis auf einer Festplatte), käme es früher oder später zu einer Schlüsselkollision. Das Nette Framework löst das Problem, indem es den gesamten Speicherplatz in Namensräume (Unterverzeichnisse) unterteilt. Jeder Teil des Programms verwendet dann seinen eigenen Bereich mit einem eindeutigen Namen, und es können keine Kollisionen auftreten. +Der Cache ist eigentlich ein *Key-Value-Store*, das heißt, wir lesen und schreiben Daten unter Schlüsseln, genau wie bei assoziativen Arrays. Anwendungen bestehen aus einer Reihe unabhängiger Teile, und wenn alle denselben Speicher verwenden (stellen Sie sich ein Verzeichnis auf der Festplatte vor), würde es früher oder später zu Schlüsselkollisionen kommen. Das Nette Framework löst dieses Problem, indem es den gesamten Speicherplatz in Namensräume (Unterverzeichnisse) aufteilt. Jeder Teil des Programms verwendet dann seinen eigenen Namensraum mit einem eindeutigen Namen, und es kann keine Kollision mehr auftreten. -Der Name des Spaces wird als zweiter Parameter des Konstruktors der Cache-Klasse angegeben: +Den Namen des Namensraums geben wir als zweiten Parameter des Konstruktors der Cache-Klasse an: ```php $cache = new Cache($storage, 'Full Html Pages'); ``` -Wir können nun das Objekt `$cache` verwenden, um aus dem Cache zu lesen und zu schreiben. Die Methode `load()` wird für beides verwendet. Das erste Argument ist der Schlüssel und das zweite ist der PHP-Callback, der aufgerufen wird, wenn der Schlüssel nicht im Cache gefunden wird. Der Callback generiert einen Wert, gibt ihn zurück und speichert ihn im Cache: +Jetzt können wir mit dem Objekt `$cache` aus dem Cache lesen und schreiben. Für beides dient die Methode `load()`. Das erste Argument ist der Schlüssel und das zweite ein PHP-Callback, der aufgerufen wird, wenn der Schlüssel nicht im Cache gefunden wird. Der Callback generiert den Wert, gibt ihn zurück und dieser wird im Cache gespeichert: ```php $value = $cache->load($key, function () use ($key) { - $computedValue = /* ... */; // schwere Berechnungen + $computedValue = /* ... */; // aufwendige Berechnung return $computedValue; }); ``` -Wenn der zweite Parameter nicht angegeben wird `$value = $cache->load($key)`, wird `null` zurückgegeben, wenn sich das Element nicht im Cache befindet. +Wenn wir den zweiten Parameter nicht angeben `$value = $cache->load($key)`, wird `null` zurückgegeben, wenn das Element nicht im Cache vorhanden ist. .[tip] -Das Tolle daran ist, dass beliebige serialisierbare Strukturen zwischengespeichert werden können, nicht nur Zeichenketten. Dasselbe gilt auch für Schlüssel. +Das Tolle ist, dass Sie beliebige serialisierbare Strukturen im Cache speichern können, nicht nur Strings. Und dasselbe gilt sogar für die Schlüssel. -Das Element wird mit der Methode `remove()` aus dem Cache geleert: +Ein Element aus dem Cache löschen wir mit der Methode `remove()`: ```php $cache->remove($key); ``` -Sie können ein Element auch mit der Methode `$cache->save($key, $value, array $dependencies = [])` zwischenspeichern. Die obige Methode mit `load()` ist jedoch vorzuziehen. +Ein Element kann auch mit der Methode `$cache->save($key, $value, array $dependencies = [])` im Cache gespeichert werden. Die bevorzugte Methode ist jedoch die oben beschriebene Verwendung von `load()`. -Zwischenspeicherung .[#toc-memoization] -======================================= +Memoization +=========== -Memoisierung bedeutet, dass das Ergebnis einer Funktion oder Methode zwischengespeichert wird, so dass Sie es beim nächsten Mal verwenden können, anstatt immer wieder dasselbe zu berechnen. +Memoization bedeutet das Cachen des Ergebnisses eines Funktions- oder Methodenaufrufs, sodass Sie es beim nächsten Mal verwenden können, ohne dasselbe erneut berechnen zu müssen. -Methoden und Funktionen können mit `call(callable $callback, ...$args)` memoisiert aufgerufen werden: +Methoden und Funktionen können memoisiert mit `call(callable $callback, ...$args)` aufgerufen werden: ```php $result = $cache->call('gethostbyaddr', $ip); ``` -Die Funktion `gethostbyaddr()` wird nur einmal für jeden Parameter `$ip` aufgerufen und beim nächsten Mal wird der Wert aus dem Cache zurückgegeben. +Die Funktion `gethostbyaddr()` wird somit für jeden Parameter `$ip` nur einmal aufgerufen, und beim nächsten Mal wird der Wert aus dem Cache zurückgegeben. Es ist auch möglich, einen memoisierten Wrapper für eine Methode oder Funktion zu erstellen, der später aufgerufen werden kann: @@ -94,17 +94,17 @@ function factorial($num) $memoizedFactorial = $cache->wrap('factorial'); -$result = $memoizedFactorial(5); // zählt es -$result = $memoizedFactorial(5); // gibt es aus dem Cache zurück +$result = $memoizedFactorial(5); // berechnet beim ersten Mal +$result = $memoizedFactorial(5); // beim zweiten Mal aus dem Cache ``` -Verfall & Ungültigmachung .[#toc-expiration-invalidation] -========================================================= +Ablauf & Invalidierung +====================== -Bei der Zwischenspeicherung ist es notwendig, sich mit der Frage zu befassen, dass einige der zuvor gespeicherten Daten mit der Zeit ungültig werden. Nette Framework bietet einen Mechanismus, um die Gültigkeit von Daten zu begrenzen und sie auf kontrollierte Weise zu löschen ("sie ungültig zu machen", um die Terminologie des Frameworks zu verwenden). +Beim Speichern im Cache muss die Frage geklärt werden, wann die zuvor gespeicherten Daten ungültig werden. Das Nette Framework bietet einen Mechanismus, um die Gültigkeit von Daten zu begrenzen oder sie kontrolliert zu löschen (in der Terminologie des Frameworks „invalidieren“). -Die Gültigkeit der Daten wird zum Zeitpunkt des Speicherns mit dem dritten Parameter der Methode `save()` festgelegt, z. B: +Die Gültigkeit der Daten wird zum Zeitpunkt des Speicherns festgelegt, und zwar mit dem dritten Parameter der Methode `save()`, z.B.: ```php $cache->save($key, $value, [ @@ -112,7 +112,7 @@ $cache->save($key, $value, [ ]); ``` -Oder über den Parameter `$dependencies`, der als Referenz an den Callback in der Methode `load()` übergeben wird, z. B: +Oder mithilfe des Parameters `$dependencies`, der per Referenz an den Callback der Methode `load()` übergeben wird, z.B.: ```php $value = $cache->load($key, function (&$dependencies) { @@ -121,7 +121,7 @@ $value = $cache->load($key, function (&$dependencies) { }); ``` -Oder unter Verwendung des 3. Parameters in der Methode `load()`, z. B: +Oder mithilfe des 3. Parameters in der Methode `load()`, z.B: ```php $value = $cache->load($key, function () { @@ -129,26 +129,26 @@ $value = $cache->load($key, function () { }, [Cache::Expire => '20 minutes']); ``` -In den folgenden Beispielen gehen wir von der zweiten Variante und damit vom Vorhandensein einer Variablen `$dependencies` aus. +In den weiteren Beispielen gehen wir von der zweiten Variante und somit der Existenz der Variablen `$dependencies` aus. -Verfall .[#toc-expiration] --------------------------- +Ablauf (Expiration) +------------------- -Die einfachste Verfallszeit ist das Zeitlimit. Hier sehen Sie, wie Sie Daten mit einer Gültigkeit von 20 Minuten zwischenspeichern können: +Der einfachste Ablauf ist ein Zeitlimit. So speichern wir Daten für 20 Minuten im Cache: ```php -// er akzeptiert auch die Anzahl der Sekunden oder den UNIX-Zeitstempel +// akzeptiert auch die Anzahl der Sekunden oder einen UNIX-Zeitstempel $dependencies[Cache::Expire] = '20 minutes'; ``` -Wenn wir die Gültigkeitsdauer bei jedem Lesen verlängern wollen, kann dies auf diese Weise erreicht werden, aber Achtung, dies erhöht den Cache-Overhead: +Wenn wir die Gültigkeitsdauer bei jedem Lesevorgang verlängern möchten (Sliding Expiration), kann dies wie folgt erreicht werden, aber Vorsicht, der Overhead des Caches steigt dadurch: ```php $dependencies[Cache::Sliding] = true; ``` -Die praktische Option ist die Möglichkeit, die Daten ablaufen zu lassen, wenn eine bestimmte Datei oder eine von mehreren Dateien geändert wird. Dies kann z.B. für die Zwischenspeicherung von Daten verwendet werden, die aus der Verarbeitung dieser Dateien resultieren. Absolute Pfade verwenden. +Eine praktische Möglichkeit besteht darin, die Daten verfallen zu lassen, wenn sich eine Datei oder eine von mehreren Dateien ändert. Dies kann beispielsweise beim Speichern von Daten im Cache genutzt werden, die durch die Verarbeitung dieser Dateien entstanden sind. Verwenden Sie absolute Pfade. ```php $dependencies[Cache::Files] = '/path/to/data.yaml'; @@ -156,13 +156,13 @@ $dependencies[Cache::Files] = '/path/to/data.yaml'; $dependencies[Cache::Files] = ['/path/to/data1.yaml', '/path/to/data2.yaml']; ``` -Wir können ein Element im Cache ablaufen lassen, wenn ein anderes Element (oder eines von mehreren anderen) abläuft. Dies kann verwendet werden, wenn wir die gesamte HTML-Seite und Fragmente davon unter anderen Schlüsseln zwischenspeichern. Sobald sich das Snippet ändert, wird die gesamte Seite ungültig. Wenn wir Fragmente unter Schlüsseln wie `frag1` und `frag2` gespeichert haben, verwenden wir: +Wir können ein Element im Cache verfallen lassen, wenn ein anderes Element (oder eines von mehreren anderen) verfällt. Dies kann nützlich sein, wenn wir beispielsweise eine ganze HTML-Seite im Cache speichern und ihre Fragmente unter anderen Schlüsseln ablegen. Sobald sich ein Fragment ändert, wird die gesamte Seite invalidiert. Wenn die Fragmente beispielsweise unter den Schlüsseln `frag1` und `frag2` gespeichert sind, verwenden wir: ```php $dependencies[Cache::Items] = ['frag1', 'frag2']; ``` -Der Ablauf kann auch über benutzerdefinierte Funktionen oder statische Methoden gesteuert werden, die immer beim Lesen entscheiden, ob das Element noch gültig ist. Zum Beispiel können wir das Element verfallen lassen, wenn sich die PHP-Version ändert. Wir erstellen eine Funktion, die die aktuelle Version mit dem Parameter vergleicht, und beim Speichern fügen wir ein Array in der Form `[function name, ...arguments]` zu den Abhängigkeiten: +Der Ablauf kann auch über benutzerdefinierte Funktionen oder statische Methoden gesteuert werden, die bei jedem Lesevorgang entscheiden, ob das Element noch gültig ist. Auf diese Weise können wir beispielsweise ein Element immer dann verfallen lassen, wenn sich die PHP-Version ändert. Wir erstellen eine Funktion, die die aktuelle Version mit dem Parameter vergleicht, und beim Speichern fügen wir unter den Abhängigkeiten ein Array im Format `[Funktionsname, ...Argumente]` hinzu: ```php function checkPhpVersion($ver): bool @@ -171,7 +171,7 @@ function checkPhpVersion($ver): bool } $dependencies[Cache::Callbacks] = [ - ['checkPhpVersion', PHP_VERSION_ID] // ablaufen, wenn checkPhpVersion(...) === false + ['checkPhpVersion', PHP_VERSION_ID] // verfallen lassen, wenn checkPhpVersion(...) === false ist ]; ``` @@ -183,16 +183,16 @@ $dependencies[Cache::Files] = '/path/to/data.yaml'; ``` -Invalidierung mit Tags .[#toc-invalidation-using-tags] ------------------------------------------------------- +Invalidierung mittels Tags +-------------------------- -Tags sind ein sehr nützliches Werkzeug zur Invalidierung. Wir können jedem im Cache gespeicherten Element eine Liste von Tags zuweisen, bei denen es sich um beliebige Zeichenfolgen handelt. Nehmen wir zum Beispiel an, wir haben eine HTML-Seite mit einem Artikel und Kommentaren, die wir zwischenspeichern wollen. Wir geben also beim Speichern im Cache Tags an: +Ein sehr nützliches Invalidierungswerkzeug sind die sogenannten Tags. Jedem Element im Cache können wir eine Liste von Tags zuweisen, bei denen es sich um beliebige Strings handelt. Nehmen wir zum Beispiel eine HTML-Seite mit einem Artikel und Kommentaren, die wir cachen möchten. Beim Speichern geben wir die Tags an: ```php $dependencies[Cache::Tags] = ["article/$articleId", "comments/$articleId"]; ``` -Wechseln wir nun zur Verwaltung. Hier haben wir ein Formular zur Bearbeitung von Artikeln. Zusammen mit dem Speichern des Artikels in einer Datenbank rufen wir den Befehl `clean()` auf, der zwischengespeicherte Artikel nach Tags löscht: +Wechseln wir zur Administration. Hier finden wir ein Formular zur Bearbeitung des Artikels. Zusammen mit dem Speichern des Artikels in der Datenbank rufen wir den Befehl `clean()` auf, der die Cache-Elemente entsprechend dem Tag löscht: ```php $cache->clean([ @@ -200,7 +200,7 @@ $cache->clean([ ]); ``` -An der Stelle, an der wir einen neuen Kommentar hinzufügen (oder einen Kommentar bearbeiten), vergessen wir nicht, das entsprechende Tag zu deaktivieren: +Ebenso vergessen wir an der Stelle, an der ein neuer Kommentar hinzugefügt (oder ein Kommentar bearbeitet) wird, nicht, den entsprechenden Tag zu invalidieren: ```php $cache->clean([ @@ -208,22 +208,22 @@ $cache->clean([ ]); ``` -Was haben wir erreicht? Dass unser HTML-Cache ungültig gemacht (gelöscht) wird, wenn sich der Artikel oder die Kommentare ändern. Wenn Sie einen Artikel mit ID = 10 bearbeiten, wird der Tag `article/10` zwangsweise ungültig gemacht und die HTML-Seite, die den Tag enthält, aus dem Cache gelöscht. Dasselbe geschieht, wenn Sie einen neuen Kommentar unter dem betreffenden Artikel einfügen. +Was haben wir damit erreicht? Dass unser HTML-Cache immer dann invalidiert (gelöscht) wird, wenn sich der Artikel oder die Kommentare ändern. Wenn der Artikel mit der ID = 10 bearbeitet wird, wird der Tag `article/10` zwangsweise invalidiert, und die HTML-Seite, die diesen Tag trägt, wird aus dem Cache gelöscht. Dasselbe geschieht beim Einfügen eines neuen Kommentars unter dem entsprechenden Artikel. .[note] -Tags erfordern [Journal |#Journal]. +Tags erfordern das sogenannte [#journal]. -Invalidierung nach Priorität .[#toc-invalidation-by-priority] -------------------------------------------------------------- +Invalidierung mittels Priorität +------------------------------- -Wir können die Priorität für einzelne Elemente im Cache festlegen, und es wird möglich sein, sie kontrolliert zu löschen, wenn der Cache z. B. eine bestimmte Größe überschreitet: +Einzelnen Elementen im Cache können wir eine Priorität zuweisen, mit der sie gelöscht werden können, wenn der Cache beispielsweise eine bestimmte Größe überschreitet: ```php $dependencies[Cache::Priority] = 50; ``` -Löschen Sie alle Objekte mit einer Priorität gleich oder kleiner als 100: +Wir löschen alle Elemente mit einer Priorität von 100 oder weniger: ```php $cache->clean([ @@ -232,11 +232,11 @@ $cache->clean([ ``` .[note] -Prioritäten erfordern das sogenannte [Journal |#Journal]. +Prioritäten erfordern das sogenannte [#journal]. -Cache löschen .[#toc-clear-cache] ---------------------------------- +Löschen des Caches +------------------ Der Parameter `Cache::All` löscht alles: @@ -247,51 +247,70 @@ $cache->clean([ ``` -Bulk Reading .[#toc-bulk-reading] -================================= +Massenlesen (Bulk Read) +======================= -Zum massenhaften Lesen und Schreiben in den Cache wird die Methode `bulkLoad()` verwendet, bei der wir ein Array von Schlüsseln übergeben und ein Array von Werten erhalten: +Für das Massenlesen und -schreiben in den Cache dient die Methode `bulkLoad()`, der wir ein Array von Schlüsseln übergeben und ein Array von Werten erhalten: ```php $values = $cache->bulkLoad($keys); ``` -Die Methode `bulkLoad()` funktioniert ähnlich wie `load()` mit dem zweiten Callback-Parameter, an den der Schlüssel des erzeugten Elements übergeben wird: +Die Methode `bulkLoad()` funktioniert ähnlich wie `load()` auch mit dem zweiten Parameter, einem Callback, dem der Schlüssel des generierten Elements übergeben wird: ```php $values = $cache->bulkLoad($keys, function ($key, &$dependencies) { - $computedValue = /* ... */; // schwere Berechnungen + $computedValue = /* ... */; // aufwendige Berechnung return $computedValue; }); ``` -Zwischenspeicherung der Ausgabe .[#toc-output-caching] -====================================================== +Verwendung mit PSR-16 .{data-version:3.3.1} +=========================================== + +Zur Verwendung von Nette Cache mit der PSR-16-Schnittstelle können Sie den Adapter `Nette\Bridges\Psr\PsrCacheAdapter` nutzen. Er ermöglicht eine nahtlose Integration zwischen Nette Cache und jedem Code oder jeder Bibliothek, die einen PSR-16-kompatiblen Cache erwartet. + +```php +$psrCache = new Nette\Bridges\Psr\PsrCacheAdapter($storage); +``` + +Jetzt können Sie `$psrCache` als PSR-16-Cache verwenden: + +```php +$psrCache->set('key', 'value', 3600); // speichert den Wert für 1 Stunde +$value = $psrCache->get('key', 'default'); +``` + +Der Adapter unterstützt alle in PSR-16 definierten Methoden, einschließlich `getMultiple()`, `setMultiple()` und `deleteMultiple()`. + + +Caching der Ausgabe +=================== -Die Ausgabe kann auf sehr elegante Weise erfasst und zwischengespeichert werden: +Die Ausgabe kann sehr elegant abgefangen und gecached werden: ```php if ($capture = $cache->capture($key)) { - echo ... // Drucken einiger Daten + echo ... // wir geben Daten aus - $capture->end(); // Speichern der Ausgabe im Cache + $capture->end(); // wir speichern die Ausgabe im Cache } ``` -Falls die Ausgabe bereits im Cache vorhanden ist, druckt die Methode `capture()` sie aus und gibt `null` zurück, so dass die Bedingung nicht ausgeführt wird. Andernfalls beginnt sie, die Ausgabe zu puffern und gibt das Objekt `$capture` zurück, mit dem wir die Daten schließlich im Cache speichern. +Falls die Ausgabe bereits im Cache gespeichert ist, gibt die Methode `capture()` sie aus und gibt `null` zurück, sodass die Bedingung nicht ausgeführt wird. Andernfalls beginnt sie mit dem Abfangen der Ausgabe und gibt das Objekt `$capture` zurück, mit dessen Hilfe wir die ausgegebenen Daten schließlich im Cache speichern. .[note] In Version 3.0 hieß die Methode `$cache->start()`. -Caching in Latte .[#toc-caching-in-latte] -========================================= +Caching in Latte +================ -Das Zwischenspeichern in [Latte-Vorlagen |latte:] ist sehr einfach, indem man einen Teil der Vorlage mit Tags umgibt `{cache}...{/cache}`. Der Cache wird automatisch ungültig gemacht, wenn sich die Quellvorlage ändert (einschließlich aller in den `{cache}` -Tags enthaltenen Vorlagen). Tags `{cache}` können verschachtelt werden, und wenn ein verschachtelter Block ungültig wird (z. B. durch ein Tag), wird auch der übergeordnete Block ungültig. +Das Caching in [Latte|latte:]-Templates ist sehr einfach, es genügt, einen Teil des Templates mit den Tags `{cache}...{/cache}` zu umschließen. Der Cache wird automatisch invalidiert, sobald sich das Quelltemplate ändert (einschließlich eventuell eingebundener Templates innerhalb des Cache-Blocks). Die `{cache}`-Tags können ineinander verschachtelt werden, und wenn ein verschachtelter Block ungültig wird (z. B. durch ein Tag), wird auch der übergeordnete Block ungültig. -Im Tag können die Schlüssel angegeben werden, an die der Cache gebunden werden soll (hier die Variable `$id`), und die Ablauf- und [Ungültigkeits-Tags |#Invalidation using Tags] gesetzt werden +Im Tag können Schlüssel angegeben werden, an die der Cache gebunden wird (hier die Variable `$id`), sowie der Ablauf und [Tags zur Invalidierung |#Invalidierung mittels Tags] eingestellt werden. ```latte {cache $id, expire: '20 minutes', tags: [tag1, tag2]} @@ -299,9 +318,9 @@ Im Tag können die Schlüssel angegeben werden, an die der Cache gebunden werden {/cache} ``` -Alle Parameter sind optional, d.h. Sie müssen weder Verfallsdatum noch Tags oder Schlüssel angeben. +Alle Parameter sind optional, sodass wir weder den Ablauf noch die Tags und letztendlich nicht einmal die Schlüssel angeben müssen. -Die Verwendung des Cache kann auch durch `if` bedingt werden - der Inhalt wird dann nur dann zwischengespeichert, wenn die Bedingung erfüllt ist: +Die Verwendung des Caches kann auch mit `if` bedingt werden - der Inhalt wird dann nur gecached, wenn die Bedingung erfüllt ist: ```latte {cache $id, if: !$form->isSubmitted()} @@ -310,23 +329,23 @@ Die Verwendung des Cache kann auch durch `if` bedingt werden - der Inhalt wird d ``` -Speicherplätze .[#toc-storages] -=============================== +Speicher (Storage) +================== -Ein Speicher ist ein Objekt, das darstellt, wo Daten physisch gespeichert werden. Wir können eine Datenbank, einen Memcached-Server oder den am häufigsten verwendeten Speicher, nämlich Dateien auf der Festplatte, verwenden. +Ein Speicher (Storage) ist ein Objekt, das den Ort darstellt, an dem Daten physisch gespeichert werden. Wir können eine Datenbank, einen Memcached-Server oder den am leichtesten verfügbaren Speicher verwenden, nämlich Dateien auf der Festplatte. -|---------------------- +|----------------- | Speicher | Beschreibung -|---------------------- -| [FileStorage |#FileStorage] | Standardspeicher mit Speicherung in Dateien auf der Festplatte -| [MemcachedStorage |#MemcachedStorage] | verwendet den `Memcached` Server -| [MemoryStorage |#MemoryStorage] | Daten werden temporär im Speicher abgelegt -| [SQLiteStorage |#SQLiteStorage] | Daten werden in einer SQLite-Datenbank gespeichert -| [DevNullStorage |#DevNullStorage] | Daten werden nicht gespeichert - für Testzwecke +|----------------- +| [#FileStorage] | Standardspeicher mit Speicherung in Dateien auf der Festplatte +| [#MemcachedStorage] | verwendet einen `Memcached`-Server +| [#MemoryStorage] | Daten werden temporär im Speicher gehalten +| [#SQLiteStorage] | Daten werden in einer SQLite-Datenbank gespeichert +| [#DevNullStorage] | Daten werden nicht gespeichert, geeignet zum Testen -Sie erhalten das Speicherobjekt, indem Sie es mittels [Dependency Injection |dependency-injection:passing-dependencies] mit dem Typ `Nette\Caching\Storage` übergeben. Standardmäßig bietet Nette ein FileStorage-Objekt, das Daten in einem Unterordner `cache` im Verzeichnis für [temporäre Dateien |application:bootstrap#Temporary Files] speichert. +Zum Speicherobjekt gelangen Sie, indem Sie es sich mittels [Dependency Injection |dependency-injection:passing-dependencies] mit dem Typ `Nette\Caching\Storage` übergeben lassen. Als Standardspeicher stellt Nette das `FileStorage`-Objekt bereit, das Daten im Unterordner `cache` im Verzeichnis für [temporäre Dateien |application:bootstrapping#Temporäre Dateien] speichert. -Sie können den Speicherort in der Konfiguration ändern: +Den Speicher können Sie in der Konfiguration ändern: ```neon services: @@ -334,28 +353,25 @@ services: ``` -FileStorage .[#toc-filestorage] -------------------------------- +FileStorage +----------- -Schreibt den Cache in Dateien auf der Festplatte. Der Speicher `Nette\Caching\Storages\FileStorage` ist sehr leistungsoptimiert und gewährleistet vor allem eine vollständige Atomisierung der Operationen. Was bedeutet das? Dass es bei der Verwendung des Caches nicht passieren kann, dass wir eine Datei lesen, die von einem anderen Thread noch nicht vollständig geschrieben wurde, oder dass jemand sie "unter der Hand" löscht. Die Nutzung des Caches ist also völlig sicher. +Schreibt den Cache in Dateien auf der Festplatte. Der Speicher `Nette\Caching\Storages\FileStorage` ist sehr gut für die Leistung optimiert und gewährleistet vor allem die volle Atomizität der Operationen. Was bedeutet das? Dass bei Verwendung des Caches nicht passieren kann, dass wir eine Datei lesen, die von einem anderen Thread noch nicht vollständig geschrieben wurde, oder dass sie jemand „unter den Händen“ löscht. Die Verwendung des Caches ist also absolut sicher. -Dieser Speicher hat auch eine wichtige eingebaute Funktion, die einen extremen Anstieg der CPU-Auslastung verhindert, wenn der Cache geleert oder kalt (d.h. nicht erstellt) ist. Dies ist die "Cache-Stampede"-P:https://en.wikipedia.org/wiki/Cache_stampede rävention. -Es kommt vor, dass zu einem Zeitpunkt mehrere gleichzeitige Anfragen das Gleiche aus dem Cache wollen (z. B. das Ergebnis einer teuren SQL-Abfrage), und da es nicht zwischengespeichert ist, beginnen alle Prozesse mit der Ausführung derselben SQL-Abfrage. -Die Prozessorlast vervielfacht sich und es kann sogar passieren, dass kein Thread innerhalb des Zeitlimits antworten kann, der Cache nicht erstellt wird und die Anwendung abstürzt. -Glücklicherweise funktioniert der Cache in Nette so, dass bei mehreren gleichzeitigen Anfragen für ein Element dieses nur vom ersten Thread erzeugt wird, die anderen warten und verwenden dann das erzeugte Ergebnis. +Dieser Speicher verfügt auch über eine wichtige integrierte Funktion, die einen extremen Anstieg der CPU-Auslastung verhindert, wenn der Cache gelöscht wird oder noch nicht „aufgewärmt“ (d.h. erstellt) ist. Dies ist eine Prävention gegen den "Cache Stampede":https://en.wikipedia.org/wiki/Cache_stampede. Es kommt vor, dass zu einem Zeitpunkt eine größere Anzahl gleichzeitiger Anfragen eingeht, die dasselbe Element aus dem Cache abrufen möchten (z. B. das Ergebnis einer teuren SQL-Abfrage), und da es nicht im Cache vorhanden ist, beginnen alle Prozesse, dieselbe SQL-Abfrage auszuführen. Die Auslastung vervielfacht sich, und es kann sogar vorkommen, dass kein Thread innerhalb des Zeitlimits antworten kann, der Cache nicht erstellt wird und die Anwendung zusammenbricht. Glücklicherweise funktioniert der Cache in Nette so, dass bei mehreren gleichzeitigen Anfragen für ein Element nur der erste Thread dieses generiert, die anderen warten und anschließend das generierte Ergebnis verwenden. -Beispiel für die Erstellung eines FileStorage: +Beispiel für die Erstellung von `FileStorage`: ```php -// der Speicher wird das Verzeichnis '/pfad/zu/temp' auf der Festplatte sein +// Der Speicher wird das Verzeichnis '/path/to/temp' auf der Festplatte sein $storage = new Nette\Caching\Storages\FileStorage('/path/to/temp'); ``` -MemcachedStorage .[#toc-memcachedstorage] ------------------------------------------ +MemcachedStorage +---------------- -Der Server [Memcached |https://memcached.org] ist ein hochleistungsfähiges verteiltes Speichersystem, dessen Adapter `Nette\Caching\Storages\MemcachedStorage` ist. Geben Sie in der Konfiguration die IP-Adresse und den Port an, falls dieser vom Standard 11211 abweicht. +Der [Memcached|https://memcached.org]-Server ist ein hochleistungsfähiges verteiltes In-Memory-Speichersystem, dessen Adapter `Nette\Caching\Storages\MemcachedStorage` ist. In der Konfiguration geben wir die IP-Adresse und den Port an, falls dieser vom Standardport 11211 abweicht. .[caution] Erfordert die PHP-Erweiterung `memcached`. @@ -366,16 +382,16 @@ services: ``` -SpeicherStorage .[#toc-memorystorage] -------------------------------------- +MemoryStorage +------------- -`Nette\Caching\Storages\MemoryStorage` ist ein Speicher, der Daten in einem PHP-Array speichert und daher verloren geht, wenn die Anfrage beendet wird. +`Nette\Caching\Storages\MemoryStorage` ist ein Speicher, der Daten in einem PHP-Array ablegt und somit mit dem Ende der Anfrage verloren gehen. -SQLiteStorage .[#toc-sqlitestorage] ------------------------------------ +SQLiteStorage +------------- -Die SQLite-Datenbank und der SQLite-Adapter `Nette\Caching\Storages\SQLiteStorage` bieten eine Möglichkeit, in einer einzigen Datei auf der Festplatte zu speichern. In der Konfiguration wird der Pfad zu dieser Datei angegeben. +Die SQLite-Datenbank und der Adapter `Nette\Caching\Storages\SQLiteStorage` bieten eine Möglichkeit, den Cache in einer einzigen Datei auf der Festplatte zu speichern. In der Konfiguration geben wir den Pfad zu dieser Datei an. .[caution] Erfordert die PHP-Erweiterungen `pdo` und `pdo_sqlite`. @@ -386,16 +402,16 @@ services: ``` -DevNullStorage .[#toc-devnullstorage] -------------------------------------- +DevNullStorage +-------------- -Eine spezielle Implementierung des Speichers ist `Nette\Caching\Storages\DevNullStorage`, der eigentlich gar keine Daten speichert. Sie eignet sich daher zum Testen, wenn wir die Wirkung des Cache ausschalten wollen. +Eine spezielle Implementierung des Speichers ist `Nette\Caching\Storages\DevNullStorage`, das tatsächlich überhaupt keine Daten speichert. Es eignet sich daher zum Testen, wenn wir den Einfluss des Caches eliminieren möchten. -Verwendung von Cache im Code .[#toc-using-cache-in-code] -======================================================== +Verwendung des Caches im Code +============================= -Bei der Verwendung von Caching im Code gibt es zwei Möglichkeiten, wie man es machen kann. Die erste ist, dass Sie das Speicherobjekt erhalten, indem Sie es mittels [Dependency Injection |dependency-injection:passing-dependencies] übergeben und dann ein Objekt `Cache` erstellen: +Bei der Verwendung des Caches im Code gibt es zwei Möglichkeiten. Die erste besteht darin, sich den Speicher mittels [Dependency Injection |dependency-injection:passing-dependencies] übergeben zu lassen und ein `Cache`-Objekt zu erstellen: ```php use Nette; @@ -411,7 +427,7 @@ class ClassOne } ``` -Die zweite Möglichkeit ist, dass Sie das Speicherobjekt `Cache` erhalten: +Die zweite Möglichkeit besteht darin, sich direkt das `Cache`-Objekt übergeben zu lassen: ```php class ClassTwo @@ -423,7 +439,7 @@ class ClassTwo } ``` -Das Objekt `Cache` wird dann direkt in der Konfiguration wie folgt erstellt: +Das `Cache`-Objekt wird dann direkt in der Konfiguration auf diese Weise erstellt: ```neon services: @@ -431,12 +447,12 @@ services: ``` -Journal .[#toc-journal] -======================= +Journal +======= -Nette speichert Tags und Prioritäten in einem sogenannten Journal. Standardmäßig werden dafür SQLite und die Datei `journal.s3db` verwendet, und **PHP-Erweiterungen `pdo` und `pdo_sqlite` sind erforderlich.** +Nette speichert Tags und Prioritäten im sogenannten Journal. Standardmäßig wird dafür SQLite und die Datei `journal.s3db` verwendet, und **es sind die PHP-Erweiterungen `pdo` und `pdo_sqlite` erforderlich.** -Sie können das Journal in der Konfiguration ändern: +Das Journal können Sie in der Konfiguration ändern: ```neon services: @@ -444,4 +460,25 @@ services: ``` -{{leftbar: nette:@menu-topics}} +DI-Dienste +========== + +Diese Dienste werden dem DI-Container hinzugefügt: + +| Name | Typ | Beschreibung +|---------------------------------------------------------- +| `cache.journal` | [api:Nette\Caching\Storages\Journal] | Journal +| `cache.storage` | [api:Nette\Caching\Storage] | Speicher + + +Deaktivieren des Caches +======================= + +Eine Möglichkeit, den Cache in der Anwendung zu deaktivieren, besteht darin, [#DevNullStorage] als Speicher festzulegen: + +```neon +services: + cache.storage: Nette\Caching\Storages\DevNullStorage +``` + +Diese Einstellung hat keinen Einfluss auf das Caching von Templates in Latte oder des DI-Containers, da diese Bibliotheken die Dienste von `nette/caching` nicht nutzen und ihren Cache selbst verwalten. Ihr Cache muss im Entwicklermodus übrigens [nicht deaktiviert werden |nette:troubleshooting#Wie schaltet man den Cache während der Entwicklung aus]. diff --git a/caching/de/@meta.texy b/caching/de/@meta.texy new file mode 100644 index 0000000000..2cf383a5cf --- /dev/null +++ b/caching/de/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Dokumentation}} +{{leftbar: nette:@menu-topics}} diff --git a/caching/el/@home.texy b/caching/el/@home.texy index 4386bfa431..a9c9f774d5 100644 --- a/caching/el/@home.texy +++ b/caching/el/@home.texy @@ -1,90 +1,90 @@ -Caching -******* +Nette Caching +************* <div class=perex> -Η κρυφή μνήμη επιταχύνει την εφαρμογή σας αποθηκεύοντας δεδομένα - αφού ανακτηθούν με δυσκολία - για μελλοντική χρήση. Θα σας δείξουμε: +Η Cache επιταχύνει την εφαρμογή σας αποθηκεύοντας δεδομένα που αποκτήθηκαν με κόπο μία φορά για μελλοντική χρήση. Θα δείξουμε: -- Πώς να χρησιμοποιήσετε την κρυφή μνήμη -- Πώς να αλλάξετε την αποθήκευση της κρυφής μνήμης -- Πώς να ακυρώνετε σωστά την κρυφή μνήμη +- πώς να χρησιμοποιήσετε την cache +- πώς να αλλάξετε την αποθήκη +- πώς να ακυρώσετε σωστά την cache </div> -Η χρήση της κρυφής μνήμης είναι πολύ εύκολη στη Nette, ενώ καλύπτει και πολύ προχωρημένες ανάγκες κρυφής μνήμης. Είναι σχεδιασμένη για απόδοση και 100% ανθεκτικότητα. Βασικά, θα βρείτε προσαρμογείς για τους πιο συνηθισμένους backend αποθηκευτικούς χώρους. Επιτρέπει την ακύρωση με βάση τις ετικέτες, την προστασία από το stampede της κρυφής μνήμης, τη χρονική λήξη κ.λπ. +Η χρήση της cache στο Nette είναι πολύ εύκολη, ενώ καλύπτει και πολύ προηγμένες ανάγκες. Είναι σχεδιασμένη για απόδοση και 100% ανθεκτικότητα. Στη βάση θα βρείτε προσαρμογείς για τις πιο συνηθισμένες αποθήκες backend. Επιτρέπει την ακύρωση βάσει tags, την λήξη βάσει χρόνου, έχει προστασία έναντι cache stampede κ.λπ. -Εγκατάσταση .[#toc-installation] -================================ +Εγκατάσταση +=========== -Κατεβάστε και εγκαταστήστε το πακέτο χρησιμοποιώντας το [Composer |best-practices:composer]: +Κατεβάστε και εγκαταστήστε τη βιβλιοθήκη χρησιμοποιώντας το εργαλείο [Composer|best-practices:composer]: ```shell composer require nette/caching ``` -Βασική χρήση .[#toc-basic-usage] -================================ +Βασική Χρήση +============ -Το κέντρο της εργασίας με την κρυφή μνήμη είναι το αντικείμενο [api:Nette\Caching\Cache]. Δημιουργούμε την εμφάνισή του και παραδίδουμε στον κατασκευαστή ως παράμετρο τη λεγόμενη αποθήκευση. Το οποίο είναι ένα αντικείμενο που αντιπροσωπεύει το μέρος όπου θα αποθηκευτούν φυσικά τα δεδομένα (βάση δεδομένων, Memcached, αρχεία στο δίσκο, ...). Παίρνετε το αντικείμενο storage περνώντας το χρησιμοποιώντας [dependency injection |dependency-injection:passing-dependencies] με τύπο `Nette\Caching\Storage`. Θα μάθετε όλα τα βασικά στοιχεία στην [ενότητα Storage (Αποθήκευση |#Storages]). +Ο πυρήνας της εργασίας με την cache, ή την προσωρινή μνήμη, είναι το αντικείμενο [api:Nette\Caching\Cache]. Δημιουργούμε ένα στιγμιότυπό του και περνάμε στον κατασκευαστή την λεγόμενη αποθήκη ως παράμετρο. Αυτό είναι ένα αντικείμενο που αντιπροσωπεύει τον τόπο όπου τα δεδομένα θα αποθηκευτούν φυσικά (βάση δεδομένων, Memcached, αρχεία στον δίσκο, ...). Έχουμε πρόσβαση στην αποθήκη αφήνοντάς την να περάσει μέσω [dependency injection |dependency-injection:passing-dependencies] με τον τύπο `Nette\Caching\Storage`. Όλα τα απαραίτητα θα τα μάθετε στην [ενότητα Αποθήκες |#Αποθήκες]. .[warning] -Στην έκδοση 3.0, η διεπαφή είχε ακόμα το `I` prefix, so the name was `Nette\Caching\IStorage`. Επίσης, οι σταθερές της κλάσης `Cache` γράφονταν με κεφαλαίο, έτσι για παράδειγμα `Cache::EXPIRE` αντί για `Cache::Expire`. +Στην έκδοση 3.0, το interface είχε ακόμα το πρόθεμα `I`, οπότε το όνομα ήταν `Nette\Caching\IStorage`. Επιπλέον, οι σταθερές της κλάσης `Cache` γράφονταν με κεφαλαία γράμματα, οπότε για παράδειγμα `Cache::EXPIRE` αντί για `Cache::Expire`. -Για τα παρακάτω παραδείγματα, ας υποθέσουμε ότι έχουμε ένα ψευδώνυμο `Cache` και μια αποθήκευση στη μεταβλητή `$storage`. +Για τα παρακάτω παραδείγματα, ας υποθέσουμε ότι έχουμε δημιουργήσει ένα alias `Cache` και στην μεταβλητή `$storage` την αποθήκη. ```php use Nette\Caching\Cache; -$storage = /* ... */; // instance of Nette\Caching\Storage +$storage = /* ... */; // στιγμιότυπο του Nette\Caching\Storage ``` -Η κρυφή μνήμη είναι στην πραγματικότητα ένας *αποθηκευτής κλειδιών-τιμών*, οπότε διαβάζουμε και γράφουμε δεδομένα κάτω από κλειδιά ακριβώς όπως οι συσχετιστικοί πίνακες. Οι εφαρμογές αποτελούνται από έναν αριθμό ανεξάρτητων τμημάτων, και αν όλα χρησιμοποιούσαν έναν αποθηκευτικό χώρο (για ιδέα: έναν κατάλογο σε ένα δίσκο), αργά ή γρήγορα θα υπήρχε σύγκρουση κλειδιών. Το Nette Framework λύνει το πρόβλημα χωρίζοντας ολόκληρο το χώρο σε χώρους ονομάτων (υποκαταλόγους). Κάθε μέρος του προγράμματος χρησιμοποιεί τότε τον δικό του χώρο με ένα μοναδικό όνομα και δεν μπορούν να προκύψουν συγκρούσεις. +Η cache είναι στην πραγματικότητα ένα *key–value store*, δηλαδή διαβάζουμε και γράφουμε δεδομένα υπό κλειδιά, όπως και με τους συσχετιστικούς πίνακες. Οι εφαρμογές αποτελούνται από μια σειρά ανεξάρτητων τμημάτων και αν όλα χρησιμοποιούσαν μία αποθήκη (φανταστείτε έναν κατάλογο στον δίσκο), αργά ή γρήγορα θα προέκυπτε σύγκρουση κλειδιών. Το Nette Framework λύνει το πρόβλημα χωρίζοντας ολόκληρο τον χώρο σε namespaces (υποκαταλόγους). Κάθε τμήμα του προγράμματος χρησιμοποιεί τότε τον δικό του χώρο με ένα μοναδικό όνομα και δεν μπορεί πλέον να υπάρξει καμία σύγκρουση. -Το όνομα του χώρου καθορίζεται ως δεύτερη παράμετρος του κατασκευαστή της κλάσης Cache: +Το όνομα του χώρου αναφέρεται ως η δεύτερη παράμετρος του κατασκευαστή της κλάσης Cache: ```php $cache = new Cache($storage, 'Full Html Pages'); ``` -Μπορούμε τώρα να χρησιμοποιήσουμε το αντικείμενο `$cache` για να διαβάσουμε και να γράψουμε από την κρυφή μνήμη. Η μέθοδος `load()` χρησιμοποιείται και για τα δύο. Το πρώτο όρισμα είναι το κλειδί και το δεύτερο είναι το callback της PHP, το οποίο καλείται όταν το κλειδί δεν βρεθεί στην κρυφή μνήμη. Το callback παράγει μια τιμή, την επιστρέφει και την αποθηκεύει στην προσωρινή μνήμη: +Τώρα μπορούμε να χρησιμοποιήσουμε το αντικείμενο `$cache` για να διαβάσουμε και να γράψουμε στην προσωρινή μνήμη. Η μέθοδος `load()` χρησιμοποιείται και για τα δύο. Το πρώτο όρισμα είναι το κλειδί και το δεύτερο είναι ένα PHP callback που καλείται όταν το κλειδί δεν βρίσκεται στην cache. Το callback παράγει την τιμή, την επιστρέφει και αποθηκεύεται στην cache: ```php $value = $cache->load($key, function () use ($key) { - $computedValue = /* ... */; // βαριούς υπολογισμούς + $computedValue = /* ... */; // απαιτητικός υπολογισμός return $computedValue; }); ``` -Εάν το δεύτερο όρισμα δεν έχει καθοριστεί `$value = $cache->load($key)`, επιστρέφεται το `null` εάν το στοιχείο δεν υπάρχει στην κρυφή μνήμη. +Αν η δεύτερη παράμετρος δεν καθοριστεί `$value = $cache->load($key)`, θα επιστραφεί `null` αν το στοιχείο δεν υπάρχει στην cache. .[tip] -Το σπουδαίο είναι ότι μπορούν να αποθηκευτούν στην προσωρινή μνήμη οποιεσδήποτε σειριοποιήσιμες δομές, όχι μόνο συμβολοσειρές. Και το ίδιο ισχύει και για τα κλειδιά. +Είναι υπέροχο που οποιαδήποτε σειριοποιήσιμη δομή μπορεί να αποθηκευτεί στην cache, όχι μόνο συμβολοσειρές. Και το ίδιο ισχύει ακόμη και για τα κλειδιά. -Το στοιχείο διαγράφεται από την κρυφή μνήμη χρησιμοποιώντας τη μέθοδο `remove()`: +Ένα στοιχείο διαγράφεται από την προσωρινή μνήμη χρησιμοποιώντας τη μέθοδο `remove()`: ```php $cache->remove($key); ``` -Μπορείτε επίσης να αποθηκεύσετε ένα στοιχείο στην προσωρινή μνήμη χρησιμοποιώντας τη μέθοδο `$cache->save($key, $value, array $dependencies = [])`. Ωστόσο, προτιμάται η παραπάνω μέθοδος που χρησιμοποιεί το `load()`. +Η αποθήκευση ενός στοιχείου στην προσωρινή μνήμη μπορεί επίσης να γίνει με τη μέθοδο `$cache->save($key, $value, array $dependencies = [])`. Ωστόσο, προτιμάται η παραπάνω μέθοδος χρησιμοποιώντας το `load()`. -Απομνημόνευση .[#toc-memoization] -================================= +Memoization +=========== -Η απομνημόνευση σημαίνει την προσωρινή αποθήκευση του αποτελέσματος μιας συνάρτησης ή μεθόδου, ώστε να μπορείτε να το χρησιμοποιήσετε την επόμενη φορά αντί να υπολογίζετε το ίδιο πράγμα ξανά και ξανά. +Memoization σημαίνει την προσωρινή αποθήκευση του αποτελέσματος μιας κλήσης συνάρτησης ή μεθόδου, ώστε να μπορείτε να το χρησιμοποιήσετε την επόμενη φορά χωρίς να υπολογίζετε ξανά το ίδιο πράγμα. -Οι μέθοδοι και οι συναρτήσεις μπορούν να κληθούν memoized χρησιμοποιώντας το `call(callable $callback, ...$args)`: +Μέθοδοι και συναρτήσεις μπορούν να κληθούν με memoization χρησιμοποιώντας το `call(callable $callback, ...$args)`: ```php $result = $cache->call('gethostbyaddr', $ip); ``` -Η συνάρτηση `gethostbyaddr()` καλείται μόνο μία φορά για κάθε παράμετρο `$ip` και την επόμενη φορά θα επιστραφεί η τιμή από την κρυφή μνήμη. +Η συνάρτηση `gethostbyaddr()` καλείται έτσι μόνο μία φορά για κάθε παράμετρο `$ip`, και την επόμενη φορά η τιμή επιστρέφεται από την cache. -Είναι επίσης δυνατό να δημιουργηθεί ένα μνημονικό περιτύλιγμα για μια μέθοδο ή συνάρτηση που μπορεί να κληθεί αργότερα: +Είναι επίσης δυνατό να δημιουργηθεί ένα memoized wrapper γύρω από μια μέθοδο ή συνάρτηση που μπορεί να κληθεί αργότερα: ```php function factorial($num) @@ -94,17 +94,17 @@ function factorial($num) $memoizedFactorial = $cache->wrap('factorial'); -$result = $memoizedFactorial(5); // το μετράει -$result = $memoizedFactorial(5); // το επιστρέφει από την κρυφή μνήμη +$result = $memoizedFactorial(5); // υπολογίζει την πρώτη φορά +$result = $memoizedFactorial(5); // τη δεύτερη φορά από την cache ``` -Λήξη & Ακύρωση .[#toc-expiration-invalidation] -============================================== +Λήξη & Ακύρωση +============== -Με την προσωρινή αποθήκευση, είναι απαραίτητο να αντιμετωπιστεί το ζήτημα ότι ορισμένα από τα δεδομένα που έχουν αποθηκευτεί προηγουμένως θα καταστούν άκυρα με την πάροδο του χρόνου. Το Nette Framework παρέχει έναν μηχανισμό, με τον οποίο μπορείτε να περιορίσετε την εγκυρότητα των δεδομένων και να τα διαγράψετε με ελεγχόμενο τρόπο ("να τα ακυρώσετε", χρησιμοποιώντας την ορολογία του πλαισίου). +Με την αποθήκευση στην cache, είναι απαραίτητο να αντιμετωπιστεί το ζήτημα του πότε τα προηγουμένως αποθηκευμένα δεδομένα καθίστανται άκυρα. Το Nette Framework προσφέρει έναν μηχανισμό για τον περιορισμό της εγκυρότητας των δεδομένων ή την ελεγχόμενη διαγραφή τους (στην ορολογία του framework "ακύρωση"). -Η εγκυρότητα των δεδομένων ορίζεται κατά τη στιγμή της αποθήκευσης με τη χρήση της τρίτης παραμέτρου της μεθόδου `save()`, π.χ: +Η εγκυρότητα των δεδομένων ορίζεται τη στιγμή της αποθήκευσης χρησιμοποιώντας την τρίτη παράμετρο της μεθόδου `save()`, π.χ.: ```php $cache->save($key, $value, [ @@ -112,7 +112,7 @@ $cache->save($key, $value, [ ]); ``` -Ή με τη χρήση της παραμέτρου `$dependencies` που περνά μέσω αναφοράς στην επανάκληση της μεθόδου `load()`, π.χ: +Ή χρησιμοποιώντας την παράμετρο `$dependencies` που περνιέται με αναφορά στο callback της μεθόδου `load()`, π.χ.: ```php $value = $cache->load($key, function (&$dependencies) { @@ -129,26 +129,26 @@ $value = $cache->load($key, function () { }, [Cache::Expire => '20 minutes']); ``` -Στα παραδείγματα που ακολουθούν, θα υποθέσουμε τη δεύτερη παραλλαγή και συνεπώς την ύπαρξη μιας μεταβλητής `$dependencies`. +Στα επόμενα παραδείγματα, θα υποθέσουμε τη δεύτερη παραλλαγή και συνεπώς την ύπαρξη της μεταβλητής `$dependencies`. -Λήξη .[#toc-expiration] ------------------------ +Λήξη +---- -Η απλούστερη λήξη είναι το χρονικό όριο. Εδώ είναι ο τρόπος για να αποθηκεύσετε δεδομένα που ισχύουν για 20 λεπτά: +Η απλούστερη λήξη είναι ένα χρονικό όριο. Έτσι αποθηκεύουμε δεδομένα στην cache με ισχύ 20 λεπτών: ```php -// δέχεται επίσης τον αριθμό των δευτερολέπτων ή τη χρονοσφραγίδα UNIX +// δέχεται επίσης τον αριθμό των δευτερολέπτων ή UNIX timestamp $dependencies[Cache::Expire] = '20 minutes'; ``` -Αν θέλουμε να επεκτείνουμε την περίοδο ισχύος με κάθε ανάγνωση, αυτό μπορεί να επιτευχθεί με αυτόν τον τρόπο, αλλά προσέξτε, αυτό θα αυξήσει την επιβάρυνση της κρυφής μνήμης: +Αν θέλαμε να παρατείνουμε την περίοδο ισχύος με κάθε ανάγνωση, αυτό μπορεί να επιτευχθεί ως εξής, αλλά προσέξτε, το overhead της cache αυξάνεται: ```php $dependencies[Cache::Sliding] = true; ``` -Η εύχρηστη επιλογή είναι η δυνατότητα να αφήνουμε τα δεδομένα να λήγουν όταν αλλάζει ένα συγκεκριμένο αρχείο ή ένα από πολλά αρχεία. Αυτό μπορεί να χρησιμοποιηθεί, για παράδειγμα, για την προσωρινή αποθήκευση δεδομένων που προκύπτουν από την επεξεργασία αυτών των αρχείων. Χρήση απόλυτων διαδρομών. +Είναι χρήσιμη η δυνατότητα να λήξουν τα δεδομένα τη στιγμή που αλλάζει ένα αρχείο ή κάποιο από τα περισσότερα αρχεία. Αυτό μπορεί να χρησιμοποιηθεί, για παράδειγμα, κατά την αποθήκευση δεδομένων που προκύπτουν από την επεξεργασία αυτών των αρχείων στην cache. Χρησιμοποιήστε απόλυτες διαδρομές. ```php $dependencies[Cache::Files] = '/path/to/data.yaml'; @@ -156,13 +156,13 @@ $dependencies[Cache::Files] = '/path/to/data.yaml'; $dependencies[Cache::Files] = ['/path/to/data1.yaml', '/path/to/data2.yaml']; ``` -Μπορούμε να αφήσουμε ένα στοιχείο της κρυφής μνήμης να λήξει όταν λήξει ένα άλλο στοιχείο (ή ένα από πολλά άλλα). Αυτό μπορεί να χρησιμοποιηθεί όταν αποθηκεύουμε στην κρυφή μνήμη ολόκληρη τη σελίδα HTML και τμήματα αυτής κάτω από άλλα κλειδιά. Μόλις αλλάξει το απόσπασμα, ολόκληρη η σελίδα καθίσταται άκυρη. Αν έχουμε αποθηκευμένα αποσπάσματα κάτω από κλειδιά όπως `frag1` και `frag2`, θα χρησιμοποιήσουμε: +Μπορούμε να αφήσουμε ένα στοιχείο στην cache να λήξει τη στιγμή που λήγει ένα άλλο στοιχείο (ή κάποιο από τα περισσότερα άλλα). Αυτό μπορεί να χρησιμοποιηθεί όταν αποθηκεύουμε, για παράδειγμα, ολόκληρη τη σελίδα HTML στην cache και τα τμήματά της κάτω από άλλα κλειδιά. Μόλις αλλάξει ένα τμήμα, ακυρώνεται ολόκληρη η σελίδα. Αν έχουμε αποθηκεύσει τα τμήματα κάτω από κλειδιά π.χ. `frag1` και `frag2`, χρησιμοποιούμε: ```php $dependencies[Cache::Items] = ['frag1', 'frag2']; ``` -Η λήξη μπορεί επίσης να ελεγχθεί με τη χρήση προσαρμοσμένων συναρτήσεων ή στατικών μεθόδων, οι οποίες αποφασίζουν πάντα κατά την ανάγνωση αν το στοιχείο είναι ακόμα έγκυρο. Για παράδειγμα, μπορούμε να αφήσουμε το στοιχείο να λήξει κάθε φορά που αλλάζει η έκδοση της PHP. Θα δημιουργήσουμε μια συνάρτηση που θα συγκρίνει την τρέχουσα έκδοση με την παράμετρο και κατά την αποθήκευση θα προσθέσουμε έναν πίνακα της μορφής `[function name, ...arguments]` στις εξαρτήσεις: +Η λήξη μπορεί επίσης να ελεγχθεί χρησιμοποιώντας προσαρμοσμένες συναρτήσεις ή στατικές μεθόδους, οι οποίες αποφασίζουν πάντα κατά την ανάγνωση αν το στοιχείο είναι ακόμα έγκυρο. Έτσι, για παράδειγμα, μπορούμε να αφήσουμε ένα στοιχείο να λήξει κάθε φορά που αλλάζει η έκδοση της PHP. Δημιουργούμε μια συνάρτηση που συγκρίνει την τρέχουσα έκδοση με την παράμετρο, και κατά την αποθήκευση προσθέτουμε μεταξύ των εξαρτήσεων έναν πίνακα της μορφής `[όνομα συνάρτησης, ...ορίσματα]`: ```php function checkPhpVersion($ver): bool @@ -171,11 +171,11 @@ function checkPhpVersion($ver): bool } $dependencies[Cache::Callbacks] = [ - ['checkPhpVersion', PHP_VERSION_ID] // λήγει όταν checkPhpVersion(...) === false + ['checkPhpVersion', PHP_VERSION_ID] // λήξη όταν checkPhpVersion(...) === false ]; ``` -Φυσικά, όλα τα κριτήρια μπορούν να συνδυαστούν. Η κρυφή μνήμη τότε λήγει όταν δεν ικανοποιείται τουλάχιστον ένα κριτήριο. +Όλα τα κριτήρια μπορούν φυσικά να συνδυαστούν. Η cache θα λήξει τότε όταν τουλάχιστον ένα κριτήριο δεν πληρείται. ```php $dependencies[Cache::Expire] = '20 minutes'; @@ -183,16 +183,16 @@ $dependencies[Cache::Files] = '/path/to/data.yaml'; ``` -Ακύρωση με χρήση ετικετών .[#toc-invalidation-using-tags] ---------------------------------------------------------- +Ακύρωση με χρήση tags +--------------------- -Οι ετικέτες είναι ένα πολύ χρήσιμο εργαλείο ακύρωσης. Μπορούμε να αντιστοιχίσουμε μια λίστα ετικετών, οι οποίες είναι αυθαίρετες συμβολοσειρές, σε κάθε στοιχείο που είναι αποθηκευμένο στην κρυφή μνήμη. Για παράδειγμα, ας υποθέσουμε ότι έχουμε μια σελίδα HTML με ένα άρθρο και σχόλια, την οποία θέλουμε να αποθηκεύσουμε στην κρυφή μνήμη. Οπότε καθορίζουμε ετικέτες κατά την αποθήκευση στην κρυφή μνήμη: +Ένα πολύ χρήσιμο εργαλείο ακύρωσης είναι τα λεγόμενα tags. Μπορούμε να αντιστοιχίσουμε σε κάθε στοιχείο της cache μια λίστα από tags, που είναι οποιεσδήποτε συμβολοσειρές. Ας υποθέσουμε ότι έχουμε μια HTML σελίδα με ένα άρθρο και σχόλια, την οποία θα αποθηκεύσουμε στην cache. Κατά την αποθήκευση, καθορίζουμε τα tags: ```php $dependencies[Cache::Tags] = ["article/$articleId", "comments/$articleId"]; ``` -Τώρα, ας προχωρήσουμε στη διαχείριση. Εδώ έχουμε μια φόρμα για την επεξεργασία άρθρων. Μαζί με την αποθήκευση του άρθρου σε μια βάση δεδομένων, καλούμε την εντολή `clean()`, η οποία θα διαγράψει τα αποθηκευμένα στοιχεία ανά ετικέτα: +Ας μεταφερθούμε στη διαχείριση. Εδώ βρίσκουμε μια φόρμα για την επεξεργασία του άρθρου. Μαζί με την αποθήκευση του άρθρου στη βάση δεδομένων, καλούμε την εντολή `clean()`, η οποία διαγράφει από την cache τα στοιχεία σύμφωνα με το tag: ```php $cache->clean([ @@ -200,7 +200,7 @@ $cache->clean([ ]); ``` -Ομοίως, στη θέση της προσθήκης ενός νέου σχολίου (ή της επεξεργασίας ενός σχολίου), δεν θα ξεχάσουμε να ακυρώσουμε τη σχετική ετικέτα: +Ομοίως, στο σημείο προσθήκης νέου σχολίου (ή επεξεργασίας σχολίου), δεν παραλείπουμε να ακυρώσουμε το σχετικό tag: ```php $cache->clean([ @@ -208,22 +208,22 @@ $cache->clean([ ]); ``` -Τι πετύχαμε; Ότι η κρυφή μνήμη HTML μας θα ακυρώνεται (διαγράφεται) κάθε φορά που το άρθρο ή τα σχόλια αλλάζουν. Κατά την επεξεργασία ενός άρθρου με ID = 10, η ετικέτα `article/10` αναγκάζεται να ακυρωθεί και η σελίδα HTML που φέρει την ετικέτα διαγράφεται από την κρυφή μνήμη. Το ίδιο συμβαίνει όταν εισάγετε ένα νέο σχόλιο κάτω από το σχετικό άρθρο. +Τι πετύχαμε με αυτό; Ότι η HTML cache μας θα ακυρώνεται (διαγράφεται) κάθε φορά που αλλάζει το άρθρο ή τα σχόλια. Όταν επεξεργάζεται το άρθρο με ID = 10, γίνεται αναγκαστική ακύρωση του tag `article/10` και η HTML σελίδα που φέρει το εν λόγω tag διαγράφεται από την cache. Το ίδιο συμβαίνει κατά την εισαγωγή νέου σχολίου κάτω από το σχετικό άρθρο. .[note] -Οι ετικέτες απαιτούν [Journal |#Journal]. +Τα tags απαιτούν το λεγόμενο [#Journal]. -Ακύρωση κατά προτεραιότητα .[#toc-invalidation-by-priority] ------------------------------------------------------------ +Ακύρωση με χρήση προτεραιότητας +------------------------------- -Μπορούμε να ορίσουμε την προτεραιότητα για μεμονωμένα στοιχεία στην κρυφή μνήμη, και θα είναι δυνατή η διαγραφή τους με ελεγχόμενο τρόπο όταν, για παράδειγμα, η κρυφή μνήμη υπερβαίνει ένα συγκεκριμένο μέγεθος: +Μπορούμε να ορίσουμε μια προτεραιότητα για μεμονωμένα στοιχεία στην cache, με βάση την οποία θα μπορούν να διαγραφούν όταν, για παράδειγμα, η cache υπερβεί ένα συγκεκριμένο μέγεθος: ```php $dependencies[Cache::Priority] = 50; ``` -Διαγραφή όλων των στοιχείων με προτεραιότητα ίση ή μικρότερη από 100: +Διαγράφουμε όλα τα στοιχεία με προτεραιότητα ίση ή μικρότερη από 100: ```php $cache->clean([ @@ -232,13 +232,13 @@ $cache->clean([ ``` .[note] -Οι προτεραιότητες απαιτούν το λεγόμενο [Journal |#Journal]. +Οι προτεραιότητες απαιτούν το λεγόμενο [#Journal]. -Εκκαθάριση προσωρινής μνήμης .[#toc-clear-cache] ------------------------------------------------- +Διαγραφή της cache +------------------ -Η παράμετρος `Cache::All` καθαρίζει τα πάντα: +Η παράμετρος `Cache::All` διαγράφει τα πάντα: ```php $cache->clean([ @@ -247,51 +247,70 @@ $cache->clean([ ``` -Bulk Reading .[#toc-bulk-reading] -================================= +Μαζική ανάγνωση +=============== -Για μαζική ανάγνωση και εγγραφή στην κρυφή μνήμη χρησιμοποιείται η μέθοδος `bulkLoad()`, όπου περνάμε έναν πίνακα κλειδιών και λαμβάνουμε έναν πίνακα τιμών: +Για μαζικές αναγνώσεις και εγγραφές στην cache χρησιμοποιείται η μέθοδος `bulkLoad()`, στην οποία περνάμε έναν πίνακα κλειδιών και λαμβάνουμε έναν πίνακα τιμών: ```php $values = $cache->bulkLoad($keys); ``` -Η μέθοδος `bulkLoad()` λειτουργεί παρόμοια με τη μέθοδο `load()` με τη δεύτερη παράμετρο επανάκλησης, στην οποία περνάει το κλειδί του παραγόμενου στοιχείου: +Η μέθοδος `bulkLoad()` λειτουργεί παρόμοια με το `load()` και με τη δεύτερη παράμετρο callback, στην οποία περνιέται το κλειδί του παραγόμενου στοιχείου: ```php $values = $cache->bulkLoad($keys, function ($key, &$dependencies) { - $computedValue = /* ... */; // βαριοί υπολογισμοί + $computedValue = /* ... */; // απαιτητικός υπολογισμός return $computedValue; }); ``` -Προσωρινή αποθήκευση εξόδου .[#toc-output-caching] -================================================== +Χρήση με PSR-16 .{data-version:3.3.1} +===================================== + +Για να χρησιμοποιήσετε την Nette Cache με το interface PSR-16, μπορείτε να χρησιμοποιήσετε τον προσαρμογέα `PsrCacheAdapter`. Επιτρέπει την απρόσκοπτη ενσωμάτωση μεταξύ της Nette Cache και οποιουδήποτε κώδικα ή βιβλιοθήκης που αναμένει μια cache συμβατή με PSR-16. + +```php +$psrCache = new Nette\Bridges\Psr\PsrCacheAdapter($storage); +``` + +Τώρα μπορείτε να χρησιμοποιήσετε το `$psrCache` ως PSR-16 cache: + +```php +$psrCache->set('key', 'value', 3600); // αποθηκεύει την τιμή για 1 ώρα +$value = $psrCache->get('key', 'default'); +``` + +Ο προσαρμογέας υποστηρίζει όλες τις μεθόδους που ορίζονται στο PSR-16, συμπεριλαμβανομένων των `getMultiple()`, `setMultiple()`, και `deleteMultiple()`. + + +Caching εξόδου +============== -Η έξοδος μπορεί να συλληφθεί και να αποθηκευτεί στην προσωρινή μνήμη πολύ κομψά: +Μπορείτε να συλλάβετε και να αποθηκεύσετε στην cache την έξοδο πολύ κομψά: ```php if ($capture = $cache->capture($key)) { - echo ... // εκτύπωση ορισμένων δεδομένων + echo ... // εκτυπώνουμε δεδομένα - $capture->end(); // αποθήκευση της εξόδου στην κρυφή μνήμη + $capture->end(); // αποθηκεύουμε την έξοδο στην cache } ``` -Σε περίπτωση που η έξοδος υπάρχει ήδη στην κρυφή μνήμη, η μέθοδος `capture()` την εκτυπώνει και επιστρέφει `null`, οπότε η συνθήκη δεν θα εκτελεστεί. Διαφορετικά, αρχίζει να αποθηκεύει την έξοδο και επιστρέφει το αντικείμενο `$capture` με το οποίο τελικά αποθηκεύουμε τα δεδομένα στην κρυφή μνήμη. +Σε περίπτωση που η έξοδος είναι ήδη αποθηκευμένη στην cache, η μέθοδος `capture()` την εκτυπώνει και επιστρέφει `null`, οπότε η συνθήκη δεν εκτελείται. Διαφορετικά, αρχίζει να συλλαμβάνει την έξοδο και επιστρέφει το αντικείμενο `$capture`, με το οποίο τελικά αποθηκεύουμε τα εκτυπωμένα δεδομένα στην cache. .[note] -Στην έκδοση 3.0 η μέθοδος ονομαζόταν `$cache->start()`. +Στην έκδοση 3.0, η μέθοδος ονομαζόταν `$cache->start()`. -Προσωρινή αποθήκευση στο Latte .[#toc-caching-in-latte] -======================================================= +Caching στο Latte +================= -Η προσωρινή αποθήκευση σε πρότυπα [Latte |latte:] είναι πολύ εύκολη, απλά τυλίξτε μέρος του προτύπου με ετικέτες `{cache}...{/cache}`. Η προσωρινή αποθήκευση ακυρώνεται αυτόματα όταν αλλάζει το πηγαίο πρότυπο (συμπεριλαμβανομένων τυχόν περιεχόμενων προτύπων μέσα στις ετικέτες `{cache}` ). Οι ετικέτες `{cache}` μπορούν να είναι φωλιασμένες, και όταν ένα φωλιασμένο μπλοκ ακυρώνεται (για παράδειγμα, από μια ετικέτα), το γονικό μπλοκ ακυρώνεται επίσης. +Το caching στα πρότυπα [Latte |latte:] είναι πολύ εύκολο, αρκεί να περιβάλλετε ένα μέρος του προτύπου με τα tags `{cache}...{/cache}`. Η cache ακυρώνεται αυτόματα τη στιγμή που αλλάζει το πρότυπο προέλευσης (συμπεριλαμβανομένων τυχόν ενσωματωμένων προτύπων εντός του μπλοκ cache). Τα tags `{cache}` μπορούν να ενσωματωθούν το ένα μέσα στο άλλο, και όταν ένα ενσωματωμένο μπλοκ ακυρωθεί (για παράδειγμα, με ένα tag), ακυρώνεται και το γονικό μπλοκ. -Στην ετικέτα είναι δυνατόν να καθοριστούν τα κλειδιά στα οποία θα δεσμεύεται η κρυφή μνήμη (εδώ η μεταβλητή `$id`) και να οριστούν οι ετικέτες λήξης και [ακύρωσης |#Invalidation using Tags] +Στο tag είναι δυνατό να αναφερθούν κλειδιά στα οποία θα συνδεθεί η cache (εδώ η μεταβλητή `$id`) και να οριστεί η λήξη και τα [tags για ακύρωση |#Ακύρωση με χρήση tags] ```latte {cache $id, expire: '20 minutes', tags: [tag1, tag2]} @@ -299,9 +318,9 @@ if ($capture = $cache->capture($key)) { {/cache} ``` -Όλες οι παράμετροι είναι προαιρετικές, οπότε δεν χρειάζεται να καθορίσετε τη λήξη, τις ετικέτες ή τα κλειδιά. +Όλα τα στοιχεία είναι προαιρετικά, οπότε δεν χρειάζεται να καθορίσουμε ούτε λήξη, ούτε tags, ούτε καν κλειδιά. -Η χρήση της κρυφής μνήμης μπορεί επίσης να εξαρτάται από τη διεύθυνση `if` - το περιεχόμενο θα αποθηκευτεί στην κρυφή μνήμη μόνο εάν πληρούται η συνθήκη: +Η χρήση της cache μπορεί επίσης να εξαρτηθεί από συνθήκη χρησιμοποιώντας το `if` - το περιεχόμενο θα αποθηκευτεί στην cache μόνο αν η συνθήκη πληρείται: ```latte {cache $id, if: !$form->isSubmitted()} @@ -310,23 +329,23 @@ if ($capture = $cache->capture($key)) { ``` -Αποθήκες .[#toc-storages] -========================= +Αποθήκες +======== -Ένας αποθηκευτικός χώρος είναι ένα αντικείμενο που αντιπροσωπεύει τον τόπο όπου αποθηκεύονται φυσικά τα δεδομένα. Μπορούμε να χρησιμοποιήσουμε μια βάση δεδομένων, έναν διακομιστή Memcached ή τον πιο διαθέσιμο αποθηκευτικό χώρο, που είναι τα αρχεία στο δίσκο. +Μια αποθήκη είναι ένα αντικείμενο που αντιπροσωπεύει τον τόπο όπου τα δεδομένα αποθηκεύονται φυσικά. Μπορούμε να χρησιμοποιήσουμε μια βάση δεδομένων, έναν διακομιστή Memcached, ή την πιο προσιτή αποθήκη, που είναι τα αρχεία στον δίσκο. -|---------------------- -| Αποθήκευση | Περιγραφή -|---------------------- -| [FileStorage |#FileStorage] | προεπιλεγμένη αποθήκευση με αποθήκευση σε αρχεία στο δίσκο -| [MemcachedStorage |#MemcachedStorage] | χρησιμοποιεί τον διακομιστή `Memcached` -| [MemoryStorage |#MemoryStorage] | τα δεδομένα βρίσκονται προσωρινά στη μνήμη -| [SQLiteStorage |#SQLiteStorage] | τα δεδομένα αποθηκεύονται σε βάση δεδομένων SQLite -| [DevNullStorage |#DevNullStorage] | τα δεδομένα δεν αποθηκεύονται - για δοκιμαστικούς σκοπούς +|----------------- +| Αποθήκη | Περιγραφή +|----------------- +| [#FileStorage] | προεπιλεγμένη αποθήκη με αποθήκευση σε αρχεία στον δίσκο +| [#MemcachedStorage] | χρησιμοποιεί τον διακομιστή `Memcached` +| [#MemoryStorage] | τα δεδομένα είναι προσωρινά στη μνήμη +| [#SQLiteStorage] | τα δεδομένα αποθηκεύονται σε βάση δεδομένων SQLite +| [#DevNullStorage] | τα δεδομένα δεν αποθηκεύονται, κατάλληλο για testing -Λαμβάνετε το αντικείμενο αποθήκευσης περνώντας το χρησιμοποιώντας [dependency injection |dependency-injection:passing-dependencies] με τον τύπο `Nette\Caching\Storage`. Από προεπιλογή, η Nette παρέχει ένα αντικείμενο FileStorage που αποθηκεύει δεδομένα σε έναν υποφάκελο `cache` στον κατάλογο για [προσωρινά αρχεία |application:bootstrap#Temporary Files]. +Μπορείτε να αποκτήσετε πρόσβαση στο αντικείμενο αποθήκης αφήνοντάς το να περάσει μέσω [dependency injection |dependency-injection:passing-dependencies] με τον τύπο `Nette\Caching\Storage`. Ως προεπιλεγμένη αποθήκη, το Nette παρέχει το αντικείμενο FileStorage που αποθηκεύει δεδομένα στον υποκατάλογο `cache` στον κατάλογο για [προσωρινά αρχεία |application:bootstrapping#Προσωρινά Αρχεία]. -Μπορείτε να αλλάξετε την αποθήκευση στη διαμόρφωση: +Μπορείτε να αλλάξετε την αποθήκη στη διαμόρφωση: ```neon services: @@ -334,28 +353,25 @@ services: ``` -FileStorage .[#toc-filestorage] -------------------------------- +FileStorage +----------- -Γράφει την κρυφή μνήμη σε αρχεία στο δίσκο. Η αποθήκευση `Nette\Caching\Storages\FileStorage` είναι πολύ καλά βελτιστοποιημένη για την απόδοση και πάνω απ' όλα εξασφαλίζει πλήρη ατομικότητα των λειτουργιών. Τι σημαίνει αυτό; Ότι κατά τη χρήση της κρυφής μνήμης δεν μπορεί να συμβεί να διαβάσουμε ένα αρχείο που δεν έχει ακόμα γραφτεί πλήρως από κάποιο άλλο νήμα ή να το διαγράψει κάποιος "κάτω από τα χέρια σας". Η χρήση της κρυφής μνήμης είναι επομένως απολύτως ασφαλής. +Γράφει την cache σε αρχεία στον δίσκο. Η αποθήκη `Nette\Caching\Storages\FileStorage` είναι πολύ καλά βελτιστοποιημένη για απόδοση και κυρίως εξασφαλίζει πλήρη ατομικότητα των λειτουργιών. Τι σημαίνει αυτό; Ότι κατά τη χρήση της cache, δεν μπορεί να συμβεί να διαβάσουμε ένα αρχείο που δεν έχει ακόμη γραφτεί πλήρως από άλλο νήμα, ή να το διαγράψει κάποιος "κάτω από τα χέρια μας". Η χρήση της cache είναι επομένως απολύτως ασφαλής. -Αυτή η αποθήκευση έχει επίσης ένα σημαντικό ενσωματωμένο χαρακτηριστικό που αποτρέπει την ακραία αύξηση της χρήσης της CPU όταν η κρυφή μνήμη διαγράφεται ή ψύχεται (δηλαδή δεν δημιουργείται). Αυτή είναι η πρόληψη "stampede cache":https://en.wikipedia.org/wiki/Cache_stampede. -Συμβαίνει ότι σε μια στιγμή υπάρχουν πολλές ταυτόχρονες αιτήσεις που θέλουν το ίδιο πράγμα από την κρυφή μνήμη (π.χ. το αποτέλεσμα ενός ακριβού ερωτήματος SQL) και επειδή αυτό δεν έχει αποθηκευτεί στην κρυφή μνήμη, όλες οι διεργασίες αρχίζουν να εκτελούν το ίδιο ερώτημα SQL. -Το φορτίο του επεξεργαστή πολλαπλασιάζεται και μπορεί να συμβεί ακόμη και να μην μπορεί κανένα νήμα να ανταποκριθεί εντός του χρονικού ορίου, να μη δημιουργηθεί η κρυφή μνήμη και να καταρρεύσει η εφαρμογή. -Ευτυχώς, η κρυφή μνήμη στο Nette λειτουργεί με τέτοιο τρόπο ώστε όταν υπάρχουν πολλαπλές ταυτόχρονες αιτήσεις για ένα στοιχείο, αυτό δημιουργείται μόνο από το πρώτο νήμα, τα υπόλοιπα περιμένουν και στη συνέχεια χρησιμοποιούν το παραγόμενο αποτέλεσμα. +Αυτή η αποθήκη έχει επίσης ενσωματωμένη μια σημαντική λειτουργία που εμποδίζει την ακραία αύξηση της χρήσης της CPU τη στιγμή που η cache διαγράφεται ή δεν έχει ακόμη θερμανθεί (δηλ. δημιουργηθεί). Πρόκειται για πρόληψη έναντι του "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Συμβαίνει ότι σε μία στιγμή συγκεντρώνεται μεγαλύτερος αριθμός ταυτόχρονων αιτημάτων που θέλουν το ίδιο πράγμα από την cache (π.χ. το αποτέλεσμα ενός ακριβού ερωτήματος SQL) και επειδή δεν υπάρχει στην προσωρινή μνήμη, όλες οι διεργασίες αρχίζουν να εκτελούν το ίδιο ερώτημα SQL. Η φόρτωση έτσι πολλαπλασιάζεται και μπορεί ακόμη και να συμβεί καμία διεργασία να μην προλάβει να απαντήσει εντός του χρονικού ορίου, η cache να μην δημιουργηθεί και η εφαρμογή να καταρρεύσει. Ευτυχώς, η cache στο Nette λειτουργεί έτσι ώστε κατά τη διάρκεια πολλαπλών ταυτόχρονων αιτημάτων για ένα στοιχείο, το παράγει μόνο το πρώτο νήμα, τα υπόλοιπα περιμένουν και στη συνέχεια χρησιμοποιούν το παραγόμενο αποτέλεσμα. -Παράδειγμα δημιουργίας ενός FileStorage: +Παράδειγμα δημιουργίας FileStorage: ```php -// η αποθήκευση θα είναι ο κατάλογος '/path/to/temp' στο δίσκο +// η αποθήκη θα είναι ο κατάλογος '/path/to/temp' στον δίσκο $storage = new Nette\Caching\Storages\FileStorage('/path/to/temp'); ``` -MemcachedStorage .[#toc-memcachedstorage] ------------------------------------------ +MemcachedStorage +---------------- -Ο διακομιστής [Memcached |https://memcached.org] είναι ένα κατανεμημένο σύστημα αποθήκευσης υψηλής απόδοσης, του οποίου ο προσαρμογέας είναι `Nette\Caching\Storages\MemcachedStorage`. Στη διαμόρφωση, καθορίστε τη διεύθυνση IP και τη θύρα, εάν διαφέρουν από το πρότυπο 11211. +Ο διακομιστής [Memcached |https://memcached.org] είναι ένα σύστημα αποθήκευσης υψηλής απόδοσης σε κατανεμημένη μνήμη, του οποίου ο προσαρμογέας είναι ο `Nette\Caching\Storages\MemcachedStorage`. Στη διαμόρφωση, αναφέρουμε τη διεύθυνση IP και τη θύρα, αν διαφέρει από την προεπιλεγμένη 11211. .[caution] Απαιτεί την επέκταση PHP `memcached`. @@ -366,16 +382,16 @@ services: ``` -MemoryStorage .[#toc-memorystorage] ------------------------------------ +MemoryStorage +------------- -`Nette\Caching\Storages\MemoryStorage` είναι ένας αποθηκευτικός χώρος που αποθηκεύει δεδομένα σε έναν πίνακα PHP και έτσι χάνονται όταν τερματιστεί η αίτηση. +Το `Nette\Caching\Storages\MemoryStorage` είναι μια αποθήκη που αποθηκεύει δεδομένα σε έναν πίνακα PHP, και επομένως χάνονται με τον τερματισμό του αιτήματος. -SQLiteStorage .[#toc-sqlitestorage] ------------------------------------ +SQLiteStorage +------------- -Η βάση δεδομένων SQLite και ο προσαρμογέας `Nette\Caching\Storages\SQLiteStorage` προσφέρουν έναν τρόπο προσωρινής αποθήκευσης σε ένα μόνο αρχείο στο δίσκο. Η ρύθμιση παραμέτρων θα καθορίσει τη διαδρομή προς αυτό το αρχείο. +Η βάση δεδομένων SQLite και ο προσαρμογέας `Nette\Caching\Storages\SQLiteStorage` προσφέρουν έναν τρόπο αποθήκευσης της cache σε ένα μόνο αρχείο στον δίσκο. Στη διαμόρφωση, αναφέρουμε τη διαδρομή προς αυτό το αρχείο. .[caution] Απαιτεί τις επεκτάσεις PHP `pdo` και `pdo_sqlite`. @@ -386,16 +402,16 @@ services: ``` -DevNullStorage .[#toc-devnullstorage] -------------------------------------- +DevNullStorage +-------------- -Μια ειδική υλοποίηση της αποθήκευσης είναι η `Nette\Caching\Storages\DevNullStorage`, η οποία στην πραγματικότητα δεν αποθηκεύει καθόλου δεδομένα. Επομένως, είναι κατάλληλη για δοκιμές αν θέλουμε να εξαλείψουμε την επίδραση της κρυφής μνήμης. +Μια ειδική υλοποίηση αποθήκης είναι η `Nette\Caching\Storages\DevNullStorage`, η οποία στην πραγματικότητα δεν αποθηκεύει καθόλου δεδομένα. Είναι επομένως κατάλληλη για testing, όταν θέλουμε να εξαλείψουμε την επίδραση της cache. -Χρήση της κρυφής μνήμης στον κώδικα .[#toc-using-cache-in-code] -=============================================================== +Χρήση της cache στον κώδικα +=========================== -Όταν χρησιμοποιείτε προσωρινή αποθήκευση στον κώδικα, έχετε δύο τρόπους για να το κάνετε. Ο πρώτος είναι ότι παίρνετε το αντικείμενο αποθήκευσης περνώντας το με τη χρήση [dependency injection |dependency-injection:passing-dependencies] και στη συνέχεια δημιουργείτε ένα αντικείμενο `Cache`: +Κατά τη χρήση της cache στον κώδικα, έχουμε δύο τρόπους για να το κάνουμε. Ο πρώτος είναι να αφήσουμε την αποθήκη να περάσει μέσω [dependency injection |dependency-injection:passing-dependencies] και να δημιουργήσουμε ένα αντικείμενο `Cache`: ```php use Nette; @@ -411,7 +427,7 @@ class ClassOne } ``` -Ο δεύτερος τρόπος είναι ότι παίρνετε το αντικείμενο αποθήκευσης `Cache`: +Η δεύτερη επιλογή είναι να αφήσουμε το αντικείμενο `Cache` να περάσει απευθείας: ```php class ClassTwo @@ -423,7 +439,7 @@ class ClassTwo } ``` -Το αντικείμενο `Cache` δημιουργείται στη συνέχεια απευθείας στη διαμόρφωση ως εξής: +Το αντικείμενο `Cache` δημιουργείται στη συνέχεια απευθείας στη διαμόρφωση με αυτόν τον τρόπο: ```neon services: @@ -431,12 +447,12 @@ services: ``` -Περιοδικό .[#toc-journal] -========================= +Journal +======= -Η Nette αποθηκεύει ετικέτες και προτεραιότητες σε ένα λεγόμενο ημερολόγιο. Από προεπιλογή, χρησιμοποιείται το SQLite και το αρχείο `journal.s3db` γι' αυτό, ενώ απαιτούνται οι επεκτάσεις **PHP `pdo` και `pdo_sqlite`. ** +Το Nette αποθηκεύει τα tags και τις προτεραιότητες στο λεγόμενο journal. Για αυτό χρησιμοποιείται συνήθως το SQLite και το αρχείο `journal.s3db` και **απαιτούνται οι επεκτάσεις PHP `pdo` και `pdo_sqlite`.** -Μπορείτε να αλλάξετε το ημερολόγιο στη διαμόρφωση: +Μπορείτε να αλλάξετε το journal στη διαμόρφωση: ```neon services: @@ -444,4 +460,25 @@ services: ``` -{{leftbar: nette:@menu-topics}} +Υπηρεσίες DI +============ + +Αυτές οι υπηρεσίες προστίθενται στον DI container: + +| Όνομα | Τύπος | Περιγραφή +|---------------------------------------------------------- +| `cache.journal` | [api:Nette\Caching\Storages\Journal] | journal +| `cache.storage` | [api:Nette\Caching\Storage] | αποθήκη + + +Απενεργοποίηση της cache +======================== + +Μία από τις επιλογές για την απενεργοποίηση της cache στην εφαρμογή είναι να ορίσετε ως αποθήκη την [#DevNullStorage]: + +```neon +services: + cache.storage: Nette\Caching\Storages\DevNullStorage +``` + +Αυτή η ρύθμιση δεν επηρεάζει το caching των προτύπων στο Latte ή τον DI container, καθώς αυτές οι βιβλιοθήκες δεν χρησιμοποιούν τις υπηρεσίες nette/caching και διαχειρίζονται την cache τους ανεξάρτητα. Εξάλλου, η cache τους [δεν χρειάζεται να απενεργοποιηθεί |nette:troubleshooting#Πώς να απενεργοποιήσετε την cache κατά την ανάπτυξη] στη λειτουργία ανάπτυξης. diff --git a/caching/el/@meta.texy b/caching/el/@meta.texy new file mode 100644 index 0000000000..a09ce5fe0d --- /dev/null +++ b/caching/el/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Τεκμηρίωση}} +{{leftbar: nette:@menu-topics}} diff --git a/caching/en/@home.texy b/caching/en/@home.texy index 70e505f5f1..d8a7533643 100644 --- a/caching/en/@home.texy +++ b/caching/en/@home.texy @@ -1,17 +1,17 @@ -Caching -******* +Nette Caching +************* <div class=perex> -Cache accelerates your application by storing data - once hard retrieved - for future use. We will show you: +Cache speeds up your application by storing data that was once computationally expensive to retrieve, allowing for faster access in the future. We will cover: -- How to use the cache -- How to change the cache storage -- How to properly invalidate the cache +- how to use the cache +- how to change the storage backend +- how to correctly invalidate the cache </div> -Using the cache is very easy in Nette, while it also covers very advanced caching needs. It is designed for performance and 100% durability. Basically, you will find adapters for the most common backend storage. Allows tags-based invalidation, cache stampede protection, time expiration, etc. +Using the cache in Nette is very straightforward, yet it covers sophisticated caching needs. It's designed for performance and 100% durability. It includes adapters for the most common storage backends. It supports tag-based invalidation, time expiration, protection against cache stampede, and more. Installation @@ -27,12 +27,12 @@ composer require nette/caching Basic Usage =========== -The center of work with the cache is the object [api:Nette\Caching\Cache]. We create its instance and pass the so-called storage to the constructor as a parameter. Which is an object representing the place where the data will be physically stored (database, Memcached, files on disk, ...). You get the storage object by passing it using [dependency injection |dependency-injection:passing-dependencies] with type `Nette\Caching\Storage`. You will find out all the essentials in [section Storage |#Storages]. +The core element for working with the cache is the [api:Nette\Caching\Cache] object. We create an instance of it, passing a storage backend object to the constructor. This storage object represents the physical location where data will be stored (database, Memcached, files on disk, etc.). You typically obtain the storage object via [dependency injection |dependency-injection:passing-dependencies] by requesting the type `Nette\Caching\Storage`. You'll learn the essentials in the [Storages section |#Storages]. .[warning] -In version 3.0, the interface still had the `I` prefix, so the name was `Nette\Caching\IStorage`. Also, the constants of the `Cache` class were capitalized, so for example `Cache::EXPIRE` instead of `Cache::Expire`. +In version 3.0, the interface still had the `I` prefix, so the name was `Nette\Caching\IStorage`. Furthermore, constants of the `Cache` class were written in uppercase, e.g., `Cache::EXPIRE` instead of `Cache::Expire`. -For the following examples, suppose we have an alias `Cache` and a storage in the variable `$storage`. +For the following examples, assume we have an alias `Cache` and a storage instance in the `$storage` variable. ```php use Nette\Caching\Cache; @@ -40,51 +40,51 @@ use Nette\Caching\Cache; $storage = /* ... */; // instance of Nette\Caching\Storage ``` -The cache is actually a *key–value store*, so we read and write data under keys just like associative arrays. Applications consist of a number of independent parts, and if they all used one storage (for idea: one directory on a disk), sooner or later there would be a key collision. The Nette Framework solves the problem by dividing the entire space into namespaces (subdirectories). Each part of the program then uses its own space with a unique name and no collisions can occur. +The cache is essentially a *key-value store*, meaning we read and write data using keys, similar to associative arrays. Applications consist of multiple independent parts. If all parts used a single storage (imagine a single directory on disk), key collisions would eventually occur. The Nette Framework addresses this by partitioning the storage space into namespaces (conceptually like subdirectories). Each part of the application then works within its own namespace using a unique name, preventing any collisions. -The name of the space is specified as the second parameter of the constructor of the Cache class: +Specify the namespace name as the second argument to the `Cache` class constructor: ```php $cache = new Cache($storage, 'Full Html Pages'); ``` -We can now use object `$cache` to read and write from the cache. The method `load()` is used for both. The first argument is the key and the second is the PHP callback, which is called when the key is not found in the cache. The callback generates a value, returns it and caches it: +Now, we can use the `$cache` object to read from and write to the cache. The `load()` method serves both purposes. The first argument is the key, and the second is a PHP callback that gets invoked if the key is not found in the cache. The callback generates the value, returns it, and the `load()` method caches it: ```php $value = $cache->load($key, function () use ($key) { - $computedValue = /* ... */; // heavy computations + $computedValue = /* ... */; // expensive computation return $computedValue; }); ``` -If the second parameter is not specified `$value = $cache->load($key)`, the `null` is returned if the item is not in the cache. +If the second parameter is omitted (`$value = $cache->load($key)`), `load()` returns `null` if the item is not found in the cache. .[tip] -The great thing is that any serializable structures can be cached, not only strings. And the same applies for keys. +It's great that any serializable structures can be cached, not just strings. The same applies to keys. -The item is cleared from the cache using method `remove()`: +To delete an item from the cache, use the `remove()` method: ```php $cache->remove($key); ``` -You can also cache an item using method `$cache->save($key, $value, array $dependencies = [])`. However, the above method using `load()` is preferred. +You can also save an item to the cache using the `$cache->save($key, $value, array $dependencies = [])` method. However, the `load()` approach shown above is generally preferred. Memoization =========== -Memoization means caching the result of a function or method so you can use it next time instead of calculating the same thing again and again. +Memoization involves caching the result of a function or method call, so the next time it's called with the same arguments, the cached result is returned instead of recalculating it. -Methods and functions can be called memoized using `call(callable $callback, ...$args)`: +Methods and functions can be called in a memoized way using `call(callable $callback, ...$args)`: ```php $result = $cache->call('gethostbyaddr', $ip); ``` -The function `gethostbyaddr()` is called only once for each parameter `$ip` and the next time the value from the cache will be returned. +The `gethostbyaddr()` function is thus called only once for each unique `$ip` argument. Subsequent calls with the same `$ip` will return the cached value. -It is also possible to create a memoized wrapper for a method or function that can be called later: +It's also possible to create a memoized wrapper around a method or function, which can then be called later: ```php function factorial($num) @@ -94,17 +94,17 @@ function factorial($num) $memoizedFactorial = $cache->wrap('factorial'); -$result = $memoizedFactorial(5); // counts it -$result = $memoizedFactorial(5); // returns it from cache +$result = $memoizedFactorial(5); // calculates it the first time +$result = $memoizedFactorial(5); // returns from cache the second time ``` Expiration & Invalidation ========================= -With caching, it is necessary to address the question that some of the previously saved data will become invalid over time. Nette Framework provides a mechanism, how to limit the validity of data and how to delete them in a controlled way ("to invalidate them", using the framework's terminology). +When using caching, it's necessary to address the issue of when previously stored data becomes invalid. Nette Framework provides mechanisms to limit data validity or delete it explicitly (referred to as "invalidation" in the framework's terminology). -The validity of the data is set at the time of saving using the third parameter of the method `save()`, eg: +Data validity is set at the time of saving, typically using the third parameter of the `save()` method, e.g.: ```php $cache->save($key, $value, [ @@ -112,7 +112,7 @@ $cache->save($key, $value, [ ]); ``` -Or using the `$dependencies` parameter passed by reference to the callback in the `load()` method, eg: +Alternatively, it can be set using the `$dependencies` parameter passed by reference to the callback in the `load()` method, e.g.: ```php $value = $cache->load($key, function (&$dependencies) { @@ -121,7 +121,7 @@ $value = $cache->load($key, function (&$dependencies) { }); ``` -Or using the 3rd parameter in the `load()` method, eg: +Or by using the 3rd parameter of the `load()` method itself, e.g.: ```php $value = $cache->load($key, function () { @@ -129,26 +129,26 @@ $value = $cache->load($key, function () { }, [Cache::Expire => '20 minutes']); ``` -In the following examples, we will assume the second variant and thus the existence of a variable `$dependencies`. +In the following examples, we'll assume the second variant, utilizing the `$dependencies` variable within the callback. Expiration ---------- -The simplest expiration is the time limit. Here's how to cache data valid for 20 minutes: +The simplest form of expiration is a time limit. This caches data with a validity of 20 minutes: ```php -// it also accepts the number of seconds or the UNIX timestamp +// accepts number of seconds or a UNIX timestamp as well $dependencies[Cache::Expire] = '20 minutes'; ``` -If we want to extend the validity period with each reading, it can be achieved this way, but beware, this will increase the cache overhead: +If you want the validity period to extend with each read (sliding expiration), you can achieve this as follows, but be aware that this increases cache overhead: ```php $dependencies[Cache::Sliding] = true; ``` -The handy option is the ability to let the data expire when a particular file is changed or one of several files. This can be used, for example, for caching data resulting from procession these files. Use absolute paths. +A useful option is to have data expire when a specific file or one of several files is modified. This is useful, for example, when caching data derived from processing these files. Use absolute paths. ```php $dependencies[Cache::Files] = '/path/to/data.yaml'; @@ -156,13 +156,13 @@ $dependencies[Cache::Files] = '/path/to/data.yaml'; $dependencies[Cache::Files] = ['/path/to/data1.yaml', '/path/to/data2.yaml']; ``` -We can let an item in the cache expired when another item (or one of several others) expires. This can be used when we cache the entire HTML page and fragments of it under other keys. Once the snippet changes, the entire page becomes invalid. If we have fragments stored under keys such as `frag1` and `frag2`, we will use: +We can make a cache item expire when another specific item (or one of several others) expires. This is useful when caching, for instance, an entire HTML page and its fragments under different keys. When a fragment changes, the entire page should be invalidated. If the fragments are stored under keys like `frag1` and `frag2`, use: ```php $dependencies[Cache::Items] = ['frag1', 'frag2']; ``` -Expiration can also be controlled using custom functions or static methods, which always decide when reading whether the item is still valid. For example, we can let the item expire whenever the PHP version changes. We will create a function that compares the current version with the parameter, and when saving we will add an array in the form `[function name, ...arguments]` to the dependencies: +Expiration can also be controlled using custom functions or static methods. These are called upon each read to determine if the item is still valid. For example, we can make an item expire whenever the PHP version changes. Create a function that compares the current version with a parameter, and when saving, add an array in the format `[function name, ...arguments]` to the dependencies: ```php function checkPhpVersion($ver): bool @@ -175,7 +175,7 @@ $dependencies[Cache::Callbacks] = [ ]; ``` -Of course, all criteria can be combined. The cache then expires when at least one criterion is not met. +Naturally, all these criteria can be combined. The cache item expires if at least one criterion is no longer met. ```php $dependencies[Cache::Expire] = '20 minutes'; @@ -186,13 +186,13 @@ $dependencies[Cache::Files] = '/path/to/data.yaml'; Invalidation Using Tags ----------------------- -Tags are a very useful invalidation tool. We can assign a list of tags, which are arbitrary strings, to each item stored in the cache. For example, suppose we have an HTML page with an article and comments, which we want to cache. So we specify tags when saving to cache: +Tags provide a very useful invalidation mechanism. We can assign a list of tags (arbitrary strings) to each item stored in the cache. For example, suppose we have an HTML page displaying an article and its comments, which we want to cache. When saving, we specify the relevant tags: ```php $dependencies[Cache::Tags] = ["article/$articleId", "comments/$articleId"]; ``` -Now, let's move to the administration. Here we have a form for article editing. Together with saving the article to a database, we call the `clean()` command, which will delete cached items by tag: +Now, let's move to the administration section. Here, we have a form for editing articles. Along with saving the article to the database, we call the `clean()` method to delete cached items based on their tag: ```php $cache->clean([ @@ -200,7 +200,7 @@ $cache->clean([ ]); ``` -Likewise, in the place of adding a new comment (or editing a comment), we will not forget to invalidate the relevant tag: +Similarly, when adding a new comment (or editing one), we must remember to invalidate the corresponding tag: ```php $cache->clean([ @@ -208,22 +208,22 @@ $cache->clean([ ]); ``` -What have we achieved? That our HTML cache will be invalidated (deleted) whenever the article or comments change. When editing an article with ID = 10, the tag `article/10` is forced to be invalidated and the HTML page carrying the tag is deleted from the cache. The same happens when you insert a new comment under the relevant article. +What have we achieved? Our HTML cache will now be invalidated (deleted) whenever the associated article or its comments change. When editing the article with ID = 10, the tag `article/10` is invalidated, and the cached HTML page carrying this tag is deleted. The same occurs when a new comment is added under the respective article. .[note] -Tags require [#Journal]. +Tags require a [#Journal]. Invalidation by Priority ------------------------ -We can set the priority for individual items in the cache, and it will be possible to delete them in a controlled way when, for example, the cache exceeds a certain size: +We can assign priorities to individual cache items. This allows for controlled deletion, for example, when the cache exceeds a certain size limit: ```php $dependencies[Cache::Priority] = 50; ``` -Delete all items with a priority equal to or less than 100: +To delete all items with a priority equal to or less than 100: ```php $cache->clean([ @@ -232,7 +232,7 @@ $cache->clean([ ``` .[note] -Priorities require so-called [#Journal]. +Priorities require a so-called [#Journal]. Clear Cache @@ -250,26 +250,45 @@ $cache->clean([ Bulk Reading ============ -For bulk reading and writing to cache, the `bulkLoad()` method is used, where we pass an array of keys and obtain an array of values: +For bulk reading and writing to the cache, use the `bulkLoad()` method. Pass it an array of keys, and it returns an array of corresponding values: ```php $values = $cache->bulkLoad($keys); ``` -Method `bulkLoad()` works similarly to `load()` with the second callback parameter, to which the key of the generated item is passed: +The `bulkLoad()` method works similarly to `load()`, also accepting a second callback parameter. This callback receives the key of the item being generated: ```php $values = $cache->bulkLoad($keys, function ($key, &$dependencies) { - $computedValue = /* ... */; // heavy computations + $computedValue = /* ... */; // expensive computation return $computedValue; }); ``` +Using with PSR-16 .{data-version:3.3.1} +======================================= + +To use Nette Cache with a PSR-16 interface, you can utilize the `PsrCacheAdapter`. It enables seamless integration between Nette Cache and any code or library expecting a PSR-16 compatible cache implementation. + +```php +$psrCache = new Nette\Bridges\Psr\PsrCacheAdapter($storage); +``` + +Now you can use `$psrCache` as a standard PSR-16 cache: + +```php +$psrCache->set('key', 'value', 3600); // stores the value for 1 hour +$value = $psrCache->get('key', 'default'); +``` + +The adapter supports all methods defined in PSR-16, including `getMultiple()`, `setMultiple()`, and `deleteMultiple()`. + + Output Caching ============== -The output can be captured and cached very elegantly: +Output can be captured and cached very elegantly: ```php if ($capture = $cache->capture($key)) { @@ -280,18 +299,18 @@ if ($capture = $cache->capture($key)) { } ``` -In case that the output is already present in the cache, the `capture()` method prints it and returns `null`, so the condition will not be executed. Otherwise, it starts to buffer the output and returns the `$capture` object using which we finally save the data to the cache. +If the output is already present in the cache, the `capture()` method prints it and returns `null`, so the `if` condition block is skipped. Otherwise, it starts buffering the output and returns a `$capture` object, which you use to finally save the captured data to the cache via its `end()` method. .[note] -In version 3.0 the method was called `$cache->start()`. +In version 3.0, this method was named `$cache->start()`. Caching in Latte ================ -Caching in [Latte|latte:] templates is very easy, just wrap part of the template with tags `{cache}...{/cache}`. The cache is automatically invalidated when the source template changes (including any included templates within the `{cache}` tags). Tags `{cache}` can be nested, and when a nested block is invalidated (for example, by a tag), the parent block is also invalidated. +Caching in [Latte|latte:] templates is very simple. Just wrap the portion of the template you want to cache with the `{cache}...{/cache}` tags. The cache is automatically invalidated whenever the source template file changes (including any templates included within the cached block). The `{cache}` tags can be nested. When a nested block is invalidated (e.g., via a tag), its parent block is also invalidated. -In the tag it is possible to specify the keys to which the cache will be bound (here the variable `$id`) and set the expiration and [invalidation tags |#Invalidation using Tags] +Within the tag, you can specify keys to which the cache entry will be bound (here, the variable `$id`), set an expiration time, and define [invalidation tags |#Invalidation Using Tags]. ```latte {cache $id, expire: '20 minutes', tags: [tag1, tag2]} @@ -299,9 +318,9 @@ In the tag it is possible to specify the keys to which the cache will be bound ( {/cache} ``` -All parameters are optional, so you don't have to specify expiration, tags, or keys. +All these parameters are optional, so you don't need to specify expiration, tags, or even keys. -The use of the cache can also be conditioned by `if` - the content will then be cached only if the condition is met: +The use of caching can also be made conditional using `if` – the content will only be cached if the condition is met: ```latte {cache $id, if: !$form->isSubmitted()} @@ -313,20 +332,20 @@ The use of the cache can also be conditioned by `if` - the content will then be Storages ======== -A storage is an object that represents where data is physically stored. We can use a database, a Memcached server, or the most available storage, which are files on disk. +A storage is an object representing the physical location where data is stored. We can use a database, a Memcached server, or the most readily available storage: files on disk. |---------------------- | Storage | Description |---------------------- -| [#FileStorage] | default storage with saving to files on disk -| [#MemcachedStorage] | uses the `Memcached` server -| [#MemoryStorage] | data are temporarily in memory -| [#SQLiteStorage] | data is stored in SQLite database -| [#DevNullStorage] | data aren't stored - for testing purposes +| [#FileStorage] | Default storage, saves cache to files on disk. +| [#MemcachedStorage] | Uses a `Memcached` server for storage. +| [#MemoryStorage] | Data is stored temporarily in memory (lost on request end). +| [#SQLiteStorage] | Data is stored in an SQLite database file. +| [#DevNullStorage] | Data isn't actually stored; useful for testing. -You get the storage object by passing it using [dependency injection |dependency-injection:passing-dependencies] with the `Nette\Caching\Storage` type. By default, Nette provides a FileStorage object that stores data in a subfolder `cache` in the directory for [temporary files |application:bootstrap#Temporary Files] . +You obtain the storage object via [dependency injection |dependency-injection:passing-dependencies] by requesting the type `Nette\Caching\Storage`. By default, Nette provides a `FileStorage` object that stores data in the `cache` subdirectory within the directory for [temporary files |application:bootstrapping#Temporary Files]. -You can change the storage in the configuration: +You can change the default storage in the configuration: ```neon services: @@ -337,17 +356,14 @@ services: FileStorage ----------- -Writes the cache to files on disk. The storage `Nette\Caching\Storages\FileStorage` is very well optimized for performance and above all ensures full atomicity of operations. What does it mean? That when using the cache, it cannot happen that we read a file that has not yet been completely written by another thread, or that someone would delete it "under your hands". The use of the cache is therefore completely safe. +Writes cache entries to files on disk. The `Nette\Caching\Storages\FileStorage` storage is highly optimized for performance and, crucially, ensures full atomicity of operations. What does this mean? When using the cache, it cannot happen that you read a file that hasn't been completely written by another thread yet, or that someone deletes it while you are reading it. Therefore, using this cache storage is completely safe. -This storage also has an important built-in feature that prevents an extreme increase in CPU usage when the cache is cleared or cold (ie not created). This is "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede prevention. -It happens that at one moment there are several concurrent requests that want the same thing from the cache (eg the result of an expensive SQL query) and because it is not cached, all processes start executing the same SQL query. -The processor load is multiplied and it can even happen that no thread can respond within the time limit, the cache is not created and the application crashes. -Fortunately, the cache in Nette works in such a way that when there are multiple concurrent requests for one item, it is generated only by the first thread, the others wait and then use the generated result. +This storage also includes an important built-in feature that prevents an extreme surge in CPU usage when the cache is cleared or is still "cold" (i.e., not yet created). This is known as "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede prevention. It occurs when multiple concurrent requests simultaneously ask for the same cached item (e.g., the result of an expensive SQL query). If the item isn't currently cached, all these processes might start executing the same expensive operation (like the SQL query). This multiplies the server load, and it can even happen that no thread manages to respond within the time limit, the cache doesn't get created, and the application may crash. Fortunately, Nette's cache handles this: when multiple concurrent requests are made for the same item, only the first thread generates it. The other threads wait and then use the result generated by the first one. -Example of creating a FileStorage: +Example of creating a `FileStorage`: ```php -// the storage will be the directory '/path/to/temp' on the disk +// the storage will be the directory '/path/to/temp' on disk $storage = new Nette\Caching\Storages\FileStorage('/path/to/temp'); ``` @@ -355,10 +371,10 @@ $storage = new Nette\Caching\Storages\FileStorage('/path/to/temp'); MemcachedStorage ---------------- -The server [Memcached |https://memcached.org] is a high-performance distributed storage system whose adapter is `Nette\Caching\Storages\MemcachedStorage`. In the configuration, specify the IP address and port if it differs from the standard 11211. +The [Memcached |https://memcached.org] server is a high-performance distributed memory object caching system. Its adapter in Nette is `Nette\Caching\Storages\MemcachedStorage`. In the configuration, specify the server's IP address and port if it differs from the standard 11211. .[caution] -Requires PHP extension `memcached`. +Requires the `memcached` PHP extension. ```neon services: @@ -369,16 +385,16 @@ services: MemoryStorage ------------- -`Nette\Caching\Storages\MemoryStorage` is a storage that stores data in a PHP array and is thus lost when the request is terminated. +`Nette\Caching\Storages\MemoryStorage` is a storage that holds data within a PHP array. Consequently, the data is lost when the request ends. SQLiteStorage ------------- -The SQLite database and adapter `Nette\Caching\Storages\SQLiteStorage` offer a way to cache in a single file on disk. The configuration will specify the path to this file. +The SQLite database, along with the `Nette\Caching\Storages\SQLiteStorage` adapter, provides a method for caching data within a single file on disk. The configuration specifies the path to this database file. .[caution] -Requires PHP extensions `pdo` and `pdo_sqlite`. +Requires the `pdo` and `pdo_sqlite` PHP extensions. ```neon services: @@ -389,13 +405,13 @@ services: DevNullStorage -------------- -A special implementation of storage is `Nette\Caching\Storages\DevNullStorage`, which does not actually store data at all. It is therefore suitable for testing if we want to eliminate the effect of the cache. +A special storage implementation is `Nette\Caching\Storages\DevNullStorage`, which doesn't actually store any data. It is therefore suitable for testing purposes when you want to eliminate the effects of caching. Using Cache in Code =================== -When using caching in code, you have two ways how to do it. The first is that you get the storage object by passing it using [dependency injection |dependency-injection:passing-dependencies] and then create an object `Cache`: +When using caching in your code, there are two main approaches. The first is to obtain the storage object via [dependency injection |dependency-injection:passing-dependencies] and then create the `Cache` object yourself: ```php use Nette; @@ -411,7 +427,7 @@ class ClassOne } ``` -The second way is that you get the storage object `Cache`: +The second option is to request the `Cache` object directly: ```php class ClassTwo @@ -423,7 +439,7 @@ class ClassTwo } ``` -The `Cache` object is then created directly in the configuration as follows: +The `Cache` object must then be defined in the configuration, for example like this: ```neon services: @@ -434,9 +450,9 @@ services: Journal ======= -Nette stores tags and priorities in a so-called journal. By default, SQLite and file `journal.s3db` are used for this, and **PHP extensions `pdo` and `pdo_sqlite` are required.** +Nette stores tags and priorities information in a so-called journal. By default, SQLite is used for this purpose via the file `journal.s3db`, and **the `pdo` and `pdo_sqlite` PHP extensions are required.** -You can change the journal in the configuration: +You can change the journal implementation in the configuration: ```neon services: @@ -444,4 +460,25 @@ services: ``` -{{leftbar: nette:@menu-topics}} +DI Services +=========== + +These services are added to the DI container: + +| Name | Type | Description +|---------------------------------------------------------- +| `cache.journal` | [api:Nette\Caching\Storages\Journal] | The cache journal storage +| `cache.storage` | [api:Nette\Caching\Storage] | The primary cache storage + + +Turning Off Cache +================= + +One way to disable caching in your application is to set the storage backend to [#DevNullStorage]: + +```neon +services: + cache.storage: Nette\Caching\Storages\DevNullStorage +``` + +This setting does not affect the caching of Latte templates or the DI container, as these libraries do not utilize `nette/caching` services and manage their caches independently. Furthermore, their caches [do not typically need to be disabled |nette:troubleshooting#How to Disable Cache During Development] during development mode. diff --git a/caching/en/@meta.texy b/caching/en/@meta.texy new file mode 100644 index 0000000000..91205786e5 --- /dev/null +++ b/caching/en/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Documentation}} +{{leftbar: nette:@menu-topics}} diff --git a/caching/es/@home.texy b/caching/es/@home.texy index 3ea650d408..ce53dca55f 100644 --- a/caching/es/@home.texy +++ b/caching/es/@home.texy @@ -1,90 +1,90 @@ -Almacenamiento en caché -*********************** +Nette Caching +************* <div class=perex> -La caché acelera tu aplicación almacenando datos -una vez recuperados con dificultad- para su uso futuro. Se lo mostraremos: +La caché acelera su aplicación al guardar datos obtenidos con esfuerzo una vez para su uso futuro. Mostraremos: -- Cómo utilizar la caché -- Cómo cambiar el almacenamiento en caché -- Cómo invalidar correctamente la caché +- cómo usar la caché +- cómo cambiar el almacenamiento +- cómo invalidar correctamente la caché </div> -Utilizar la caché es muy fácil en Nette, a la vez que cubre necesidades de caché muy avanzadas. Está diseñado para ofrecer rendimiento y durabilidad al 100%. Básicamente, encontrará adaptadores para los almacenamientos backend más comunes. Permite la invalidación basada en etiquetas, protección de estampida de caché, expiración de tiempo, etc. +Usar la caché en Nette es muy fácil, pero cubre incluso necesidades muy avanzadas. Está diseñada para el rendimiento y una resistencia del 100%. En su núcleo, encontrará adaptadores para los almacenamientos backend más comunes. Permite la invalidación basada en etiquetas, la expiración por tiempo, tiene protección contra la estampida de caché, etc. -Instalación .[#toc-installation] -================================ +Instalación +=========== -Descargue e instale el paquete utilizando [Composer |best-practices:composer]: +Puede descargar e instalar la librería usando [Composer|best-practices:composer]: ```shell composer require nette/caching ``` -Uso básico .[#toc-basic-usage] -============================== - -El centro de trabajo con la caché es el objeto [api:Nette\Caching\Cache]. Creamos su instancia y pasamos al constructor como parámetro el llamado storage. Que es un objeto que representa el lugar donde se almacenarán físicamente los datos (base de datos, Memcached, ficheros en disco, ...). El objeto storage se obtiene pasándolo mediante [inyección de dependencia |dependency-injection:passing-dependencies] con el tipo `Nette\Caching\Storage`. Encontrarás todo lo esencial en [la sección Storage |#Storages]. - -Para los siguientes ejemplos, supongamos que tenemos un alias `Cache` y un almacenamiento en la variable `$storage`. +Uso básico +========== +El centro del trabajo con la caché es el objeto [api:Nette\Caching\Cache]. Creamos su instancia y pasamos el llamado almacenamiento como parámetro al constructor. Este es un objeto que representa el lugar donde los datos se almacenarán físicamente (base de datos, Memcached, archivos en disco, ...). Accedemos al almacenamiento pidiéndolo mediante [inyección de dependencias |dependency-injection:passing-dependencies] con el tipo `Nette\Caching\Storage`. Aprenderá todo lo esencial en la [sección Almacenamientos |#Almacenamientos]. +.[warning] +En la versión 3.0, la interfaz todavía tenía el prefijo `I`, por lo que el nombre era `Nette\Caching\IStorage`. Además, las constantes de la clase `Cache` estaban escritas en mayúsculas, así que, por ejemplo, `Cache::EXPIRE` en lugar de `Cache::Expire`. +Para los siguientes ejemplos, supongamos que tenemos un alias `Cache` creado y el almacenamiento en la variable `$storage`. ```php use Nette\Caching\Cache; -$storage = /* ... */; // instance of Nette\Caching\Storage +$storage = /* ... */; // instancia de Nette\Caching\Storage ``` -La caché es en realidad un *almacenamiento clave-valor*, por lo que leemos y escribimos datos bajo claves como si fueran matrices asociativas. Las aplicaciones constan de varias partes independientes, y si todas ellas utilizaran un mismo almacenamiento (por ejemplo: un directorio en un disco), tarde o temprano se produciría una colisión de claves. Nette Framework resuelve el problema dividiendo todo el espacio en espacios de nombres (subdirectorios). Así, cada parte del programa utiliza su propio espacio con un nombre único y no pueden producirse colisiones. +La caché es básicamente un *key-value store*, lo que significa que leemos y escribimos datos bajo claves al igual que con los arrays asociativos. Las aplicaciones consisten en varias partes independientes, y si todas usaran un solo almacenamiento (imagine un directorio en el disco), tarde o temprano ocurriría una colisión de claves. Nette Framework resuelve este problema dividiendo todo el espacio en espacios de nombres (subdirectorios). Cada parte del programa luego usa su propio espacio con un nombre único, y no puede ocurrir ninguna colisión. -El nombre del espacio se especifica como segundo parámetro del constructor de la clase Cache: +Especificamos el nombre del espacio como el segundo parámetro del constructor de la clase Cache: ```php $cache = new Cache($storage, 'Full Html Pages'); ``` -Ahora podemos utilizar el objeto `$cache` para leer y escribir desde la caché. El método `load()` se utiliza para ambas cosas. El primer argumento es la clave y el segundo es el callback de PHP, que se llama cuando la clave no se encuentra en la caché. El callback genera un valor, lo devuelve y lo almacena en caché: +Ahora podemos usar el objeto `$cache` para leer y escribir en la caché. El método `load()` sirve para ambos propósitos. El primer argumento es la clave y el segundo es un callback de PHP, que se llama cuando la clave no se encuentra en la caché. El callback genera el valor, lo devuelve y se almacena en la caché: ```php $value = $cache->load($key, function () use ($key) { - $computedValue = /* ... */; // cálculos pesados + $computedValue = /* ... */; // cálculo costoso return $computedValue; }); ``` -Si no se especifica el segundo parámetro `$value = $cache->load($key)`, se devuelve `null` si el elemento no se encuentra en la caché. +Si no especificamos el segundo parámetro `$value = $cache->load($key)`, devuelve `null` si el elemento no está en la caché. .[tip] -Lo bueno es que se puede almacenar en caché cualquier estructura serializable, no sólo cadenas. Y lo mismo se aplica a las claves. +Lo bueno es que se pueden almacenar en la caché cualquier estructura serializable, no solo cadenas. Y lo mismo se aplica incluso a las claves. -El elemento se borra de la caché utilizando el método `remove()`: +Eliminamos un elemento de la caché usando el método `remove()`: ```php $cache->remove($key); ``` -También puede almacenar un elemento en caché utilizando el método `$cache->save($key, $value, array $dependencies = [])`. Sin embargo, es preferible utilizar el método `load()`. +También es posible guardar un elemento en la caché usando el método `$cache->save($key, $value, array $dependencies = [])`. Sin embargo, se prefiere el método mencionado anteriormente usando `load()`. -Memoización .[#toc-memoization] -=============================== +Memoización +=========== -Memoización significa almacenar en caché el resultado de una función o método para poder utilizarlo la próxima vez en lugar de calcular lo mismo una y otra vez. +La memoización significa almacenar en caché el resultado de una llamada a una función o método para que pueda usarlo la próxima vez sin calcular lo mismo una y otra vez. -Métodos y funciones pueden ser llamados memoized usando `call(callable $callback, ...$args)`: +Se pueden llamar a métodos y funciones de forma memoizada usando `call(callable $callback, ...$args)`: ```php $result = $cache->call('gethostbyaddr', $ip); ``` -La función `gethostbyaddr()` se llama una sola vez para cada parámetro `$ip` y la próxima vez se devolverá el valor de la caché. +La función `gethostbyaddr()` se llamará solo una vez para cada parámetro `$ip`, y la próxima vez se devolverá el valor de la caché. -También es posible crear una envoltura memoized para un método o función que puede ser llamado más tarde: +También es posible crear un envoltorio memoizado sobre un método o función que se puede llamar más tarde: ```php function factorial($num) @@ -94,17 +94,17 @@ function factorial($num) $memoizedFactorial = $cache->wrap('factorial'); -$result = $memoizedFactorial(5); // lo cuenta -$result = $memoizedFactorial(5); // lo devuelve de la caché +$result = $memoizedFactorial(5); // calcula la primera vez +$result = $memoizedFactorial(5); // la segunda vez desde la caché ``` -Expiración e invalidación .[#toc-expiration-invalidation] -========================================================= +Expiración e Invalidación +========================= -Con el almacenamiento en caché, es necesario abordar la cuestión de que algunos de los datos previamente guardados pierdan validez con el tiempo. Nette Framework proporciona un mecanismo, cómo limitar la validez de los datos y cómo borrarlos de forma controlada ("invalidarlos", utilizando la terminología del framework). +Al almacenar en caché, es necesario abordar la cuestión de cuándo los datos previamente almacenados se vuelven inválidos. Nette Framework ofrece un mecanismo para limitar la validez de los datos o eliminarlos de forma controlada (en la terminología del framework, "invalidar"). -La validez de los datos se establece en el momento de guardarlos mediante el tercer parámetro del método `save()`, p. ej: +La validez de los datos se establece en el momento del almacenamiento utilizando el tercer parámetro del método `save()`, por ejemplo: ```php $cache->save($key, $value, [ @@ -112,7 +112,7 @@ $cache->save($key, $value, [ ]); ``` -O utilizando el parámetro `$dependencies` pasado por referencia a la llamada de retorno en el método `load()`, por ejemplo: +O usando el parámetro `$dependencies` pasado por referencia al callback del método `load()`, por ejemplo: ```php $value = $cache->load($key, function (&$dependencies) { @@ -121,7 +121,7 @@ $value = $cache->load($key, function (&$dependencies) { }); ``` -O utilizando el 3er parámetro en el método `load()`, ej: +O usando el tercer parámetro en el método `load()`, por ejemplo: ```php $value = $cache->load($key, function () { @@ -129,40 +129,40 @@ $value = $cache->load($key, function () { }, [Cache::Expire => '20 minutes']); ``` -En los siguientes ejemplos, supondremos la segunda variante y, por tanto, la existencia de una variable `$dependencies`. +En los siguientes ejemplos, asumiremos la segunda variante y, por lo tanto, la existencia de la variable `$dependencies`. -Vencimiento .[#toc-expiration] ------------------------------- +Expiración +---------- -La expiración más sencilla es la de tiempo. He aquí cómo almacenar en caché datos válidos durante 20 minutos: +La expiración más simple es un límite de tiempo. Así es como almacenamos en caché datos válidos durante 20 minutos: ```php -// también acepta el número de segundos o la marca de tiempo UNIX +// también acepta número de segundos o timestamp UNIX $dependencies[Cache::Expire] = '20 minutes'; ``` -Si queremos extender el período de validez con cada lectura, se puede lograr de esta manera, pero cuidado, esto aumentará la sobrecarga de la caché: +Si quisiéramos extender el período de validez con cada lectura, se puede lograr de la siguiente manera, pero tenga cuidado, la sobrecarga de la caché aumentará: ```php $dependencies[Cache::Sliding] = true; ``` -La opción más práctica es la posibilidad de dejar que los datos caduquen cuando se modifica un fichero en particular o uno de varios ficheros. Esto se puede utilizar, por ejemplo, para almacenar en caché los datos resultantes de la procesión de estos archivos. Utilizar rutas absolutas. +Una opción útil es dejar que los datos expiren cuando cambia un archivo o uno de varios archivos. Esto se puede usar, por ejemplo, al almacenar en caché datos resultantes del procesamiento de estos archivos. Use rutas absolutas. ```php $dependencies[Cache::Files] = '/path/to/data.yaml'; -// or +// o $dependencies[Cache::Files] = ['/path/to/data1.yaml', '/path/to/data2.yaml']; ``` -Podemos dejar que un elemento de la caché caduque cuando caduque otro elemento (o uno de varios). Esto puede utilizarse cuando almacenamos en caché la página HTML completa y fragmentos de ella bajo otras claves. Una vez que el fragmento cambia, toda la página deja de ser válida. Si tenemos fragmentos almacenados bajo claves como `frag1` y `frag2`, utilizaremos: +Podemos hacer que un elemento de la caché expire cuando otro elemento (o uno de varios otros) expire. Esto se puede usar cuando almacenamos en caché, por ejemplo, una página HTML completa y sus fragmentos bajo otras claves. Tan pronto como cambia un fragmento, toda la página se invalida. Si tenemos fragmentos almacenados bajo claves como `frag1` y `frag2`, usamos: ```php $dependencies[Cache::Items] = ['frag1', 'frag2']; ``` -La expiración también puede controlarse usando funciones personalizadas o métodos estáticos, que siempre deciden al leer si el elemento sigue siendo válido. Por ejemplo, podemos dejar que el elemento expire cada vez que cambie la versión de PHP. Crearemos una función que compare la versión actual con el parámetro, y al guardar añadiremos un array de la forma `[function name, ...arguments]` a las dependencias: +La expiración también se puede controlar mediante funciones personalizadas o métodos estáticos, que deciden cada vez que se lee si el elemento sigue siendo válido. De esta manera, por ejemplo, podemos hacer que un elemento expire siempre que cambie la versión de PHP. Creamos una función que compara la versión actual con un parámetro, y al guardar, agregamos un array con el formato `[nombre de la función, ...argumentos]` entre las dependencias: ```php function checkPhpVersion($ver): bool @@ -171,11 +171,11 @@ function checkPhpVersion($ver): bool } $dependencies[Cache::Callbacks] = [ - ['checkPhpVersion', PHP_VERSION_ID] // expiran cuando checkPhpVersion(...) === false + ['checkPhpVersion', PHP_VERSION_ID] // expira cuando checkPhpVersion(...) === false ]; ``` -Por supuesto, se pueden combinar todos los criterios. La caché expira entonces cuando no se cumple al menos un criterio. +Por supuesto, todos los criterios se pueden combinar. La caché expirará cuando al menos un criterio no se cumpla. ```php $dependencies[Cache::Expire] = '20 minutes'; @@ -183,16 +183,16 @@ $dependencies[Cache::Files] = '/path/to/data.yaml'; ``` -Invalidación mediante etiquetas .[#toc-invalidation-using-tags] ---------------------------------------------------------------- +Invalidación mediante etiquetas +------------------------------- -Las etiquetas son una herramienta de invalidación muy útil. Podemos asignar una lista de etiquetas, que son cadenas arbitrarias, a cada elemento almacenado en la caché. Por ejemplo, supongamos que tenemos una página HTML con un artículo y comentarios, que queremos guardar en caché. Entonces especificamos las etiquetas al guardar en la caché: +Una herramienta de invalidación muy útil son las llamadas etiquetas. Podemos asignar una lista de etiquetas, que son cadenas arbitrarias, a cada elemento de la caché. Por ejemplo, tengamos una página HTML con un artículo y comentarios que almacenaremos en caché. Al guardar, especificamos las etiquetas: ```php $dependencies[Cache::Tags] = ["article/$articleId", "comments/$articleId"]; ``` -Ahora, pasemos a la administración. Aquí tenemos un formulario para la edición de artículos. Junto con guardar el artículo en una base de datos, llamamos al comando `clean()`, que borrará los artículos en caché por etiqueta: +Pasemos a la administración. Aquí encontramos un formulario para editar el artículo. Junto con guardar el artículo en la base de datos, llamamos al comando `clean()`, que elimina elementos de la caché según la etiqueta: ```php $cache->clean([ @@ -200,7 +200,7 @@ $cache->clean([ ]); ``` -Asimismo, en el lugar de añadir un nuevo comentario (o editar un comentario), no olvidaremos invalidar la etiqueta correspondiente: +Del mismo modo, en el lugar de agregar un nuevo comentario (o editar un comentario), no olvidemos invalidar la etiqueta correspondiente: ```php $cache->clean([ @@ -208,22 +208,22 @@ $cache->clean([ ]); ``` -¿Qué hemos conseguido? Que nuestra caché HTML será invalidada (borrada) cada vez que el artículo o los comentarios cambien. Al editar un artículo con ID = 10, se fuerza la invalidación de la etiqueta `article/10` y la página HTML que lleva la etiqueta se borra de la caché. Lo mismo ocurre al insertar un nuevo comentario en el artículo correspondiente. +¿Qué hemos logrado con esto? Que nuestra caché HTML se invalidará (eliminará) cada vez que cambie el artículo o los comentarios. Cuando se edita un artículo con ID = 10, se fuerza la invalidación de la etiqueta `article/10` y la página HTML que lleva esa etiqueta se elimina de la caché. Lo mismo ocurre al insertar un nuevo comentario bajo el artículo correspondiente. .[note] -Las etiquetas requieren [Journal |#Journal]. +Las etiquetas requieren el llamado [#Journal]. -Invalidación por prioridad .[#toc-invalidation-by-priority] ------------------------------------------------------------ +Invalidación mediante prioridad +------------------------------- -Podemos establecer la prioridad para elementos individuales de la caché, y será posible eliminarlos de forma controlada cuando, por ejemplo, la caché supere un determinado tamaño: +Podemos establecer una prioridad para los elementos individuales en la caché, que se puede usar para eliminarlos cuando, por ejemplo, la caché exceda un cierto tamaño: ```php $dependencies[Cache::Priority] = 50; ``` -Borrar todos los elementos con una prioridad igual o inferior a 100: +Eliminaremos todos los elementos con una prioridad igual o menor que 100: ```php $cache->clean([ @@ -232,13 +232,13 @@ $cache->clean([ ``` .[note] -Las prioridades requieren el llamado [Diario |#Journal]. +Las prioridades requieren el llamado [#Journal]. -Borrar caché .[#toc-clear-cache] --------------------------------- +Limpiar la caché +---------------- -El parámetro `Cache::All` borra todo: +El parámetro `Cache::All` elimina todo: ```php $cache->clean([ @@ -247,51 +247,70 @@ $cache->clean([ ``` -Lectura masiva .[#toc-bulk-reading] -=================================== +Lectura masiva +============== -Para la lectura y escritura masiva en la caché, se utiliza el método `bulkLoad()`, donde pasamos un array de claves y obtenemos un array de valores: +Para la lectura y escritura masiva en la caché, se utiliza el método `bulkLoad()`, al que pasamos un array de claves y obtenemos un array de valores: ```php $values = $cache->bulkLoad($keys); ``` -El método `bulkLoad()` funciona de forma similar a `load()` con el segundo parámetro de callback, al que se le pasa la clave del elemento generado: +El método `bulkLoad()` funciona de manera similar a `load()` también con el segundo parámetro callback, al que se pasa la clave del elemento generado: ```php $values = $cache->bulkLoad($keys, function ($key, &$dependencies) { - $computedValue = /* ... */; // cálculos pesados + $computedValue = /* ... */; // cálculo costoso return $computedValue; }); ``` -Caché de salida .[#toc-output-caching] -====================================== +Uso con PSR-16 .{data-version:3.3.1} +==================================== + +Para usar Nette Cache con la interfaz PSR-16, puede utilizar el adaptador `PsrCacheAdapter`. Permite una integración perfecta entre Nette Cache y cualquier código o librería que espere una caché compatible con PSR-16. + +```php +$psrCache = new Nette\Bridges\Psr\PsrCacheAdapter($storage); +``` + +Ahora puede usar `$psrCache` como una caché PSR-16: + +```php +$psrCache->set('key', 'value', 3600); // guarda el valor durante 1 hora +$value = $psrCache->get('key', 'default'); +``` + +El adaptador admite todos los métodos definidos en PSR-16, incluidos `getMultiple()`, `setMultiple()` y `deleteMultiple()`. + -La salida puede capturarse y almacenarse en caché de forma muy elegante: +Almacenamiento en caché de la salida +==================================== + +La salida se puede capturar y almacenar en caché de forma muy elegante: ```php if ($capture = $cache->capture($key)) { - echo ... // imprimir algunos datos + echo ... // imprimimos datos - $capture->end(); // guardar la salida en la caché + $capture->end(); // guardamos la salida en la caché } ``` -En caso de que la salida ya esté presente en la caché, el método `capture()` la imprime y devuelve `null`, por lo que la condición no se ejecutará. En caso contrario, comienza a almacenar la salida y devuelve el objeto `$capture` con el que finalmente guardamos los datos en la caché. +Si la salida ya está almacenada en la caché, el método `capture()` la imprimirá y devolverá `null`, por lo que la condición no se ejecutará. De lo contrario, comenzará a capturar la salida y devolverá el objeto `$capture`, con el que finalmente guardaremos los datos impresos en la caché. .[note] -En la versión 3.0 el método se llamaba `$cache->start()`. +En la versión 3.0, el método se llamaba `$cache->start()`. -Almacenamiento en caché en Latte .[#toc-caching-in-latte] -========================================================= +Almacenamiento en caché en Latte +================================ -El almacenamiento en caché en las plantillas [Latte |latte:] es muy fácil, basta con envolver parte de la plantilla con etiquetas `{cache}...{/cache}`. La caché se invalida automáticamente cuando cambia la plantilla de origen (incluyendo cualquier plantilla incluida dentro de las etiquetas `{cache}` ). Las etiquetas `{cache}` pueden anidarse, y cuando un bloque anidado se invalida (por ejemplo, por una etiqueta), el bloque padre también se invalida. +El almacenamiento en caché en las plantillas [Latte|latte:] es muy fácil, simplemente envuelva una parte de la plantilla con las etiquetas `{cache}...{/cache}`. La caché se invalida automáticamente cuando cambia la plantilla de origen (incluidas las plantillas incluidas dentro del bloque de caché). Las etiquetas `{cache}` se pueden anidar, y cuando un bloque anidado se invalida (por ejemplo, por una etiqueta), el bloque padre también se invalida. -En la etiqueta es posible especificar las claves a las que se vinculará la caché (en este caso la variable `$id`) y establecer las [etiquetas de caducidad|#Invalidation using Tags] e [invalidación |#Invalidation using Tags] +En la etiqueta, es posible especificar claves a las que se vinculará la caché (aquí la variable `$id`) y establecer la expiración y las [etiquetas para la invalidación |#Invalidación mediante etiquetas]. ```latte {cache $id, expire: '20 minutes', tags: [tag1, tag2]} @@ -299,9 +318,9 @@ En la etiqueta es posible especificar las claves a las que se vinculará la cach {/cache} ``` -Todos los parámetros son opcionales, por lo que no es necesario especificar la caducidad, las etiquetas o las claves. +Todos los elementos son opcionales, por lo que no tenemos que especificar ni la expiración, ni las etiquetas, ni siquiera las claves. -El uso de la caché también puede condicionarse mediante `if` - el contenido se almacenará en caché sólo si se cumple la condición: +El uso de la caché también se puede condicionar usando `if`: el contenido se almacenará en caché solo si se cumple la condición: ```latte {cache $id, if: !$form->isSubmitted()} @@ -310,21 +329,21 @@ El uso de la caché también puede condicionarse mediante `if` - el contenido se ``` -Almacenes .[#toc-storages] -========================== +Almacenamientos +=============== -Un almacenamiento es un objeto que representa dónde se guardan físicamente los datos. Podemos utilizar una base de datos, un servidor Memcached, o el almacenamiento más disponible, que son archivos en disco. +Un almacenamiento es un objeto que representa el lugar donde se almacenan físicamente los datos. Podemos usar una base de datos, un servidor Memcached o el almacenamiento más accesible, que son archivos en disco. -|---------------------- +|----------------- | Almacenamiento | Descripción -|---------------------- -| [FileStorage |#FileStorage] | Almacenamiento por defecto con guardado en ficheros en disco -| [MemcachedStorage |#MemcachedStorage] | utiliza el servidor `Memcached` -| [MemoryStorage |#MemoryStorage] | los datos se almacenan temporalmente en memoria. -| [SQLiteStorage |#SQLiteStorage] | los datos se almacenan en una base de datos SQLite -| [DevNullStorage |#DevNullStorage] | los datos no se almacenan, con fines de prueba. +|----------------- +| [#FileStorage] | almacenamiento predeterminado con guardado en archivos en disco +| [#MemcachedStorage] | utiliza un servidor `Memcached` +| [#MemoryStorage] | los datos están temporalmente en memoria +| [#SQLiteStorage] | los datos se guardan en una base de datos SQLite +| [#DevNullStorage] | los datos no se guardan, adecuado para pruebas -El objeto de almacenamiento se obtiene pasándolo mediante [inyección de dependencia |dependency-injection:passing-dependencies] con el tipo `Nette\Caching\Storage`. Por defecto, Nette proporciona un objeto FileStorage que almacena los datos en una subcarpeta `cache` en el directorio para [archivos |application:bootstrap#Temporary Files] temporales . +Accede al objeto de almacenamiento pidiéndolo mediante [inyección de dependencias |dependency-injection:passing-dependencies] con el tipo `Nette\Caching\Storage`. Como almacenamiento predeterminado, Nette proporciona el objeto FileStorage que guarda los datos en la subcarpeta `cache` en el directorio para [archivos temporales |application:bootstrapping#Archivos temporales]. Puede cambiar el almacenamiento en la configuración: @@ -334,28 +353,25 @@ services: ``` -Almacenamiento de archivos .[#toc-filestorage] ----------------------------------------------- +FileStorage +----------- -Escribe la caché en archivos del disco. El almacenamiento `Nette\Caching\Storages\FileStorage` está muy bien optimizado para el rendimiento y, sobre todo, garantiza la atomicidad total de las operaciones. ¿Qué significa esto? Que al utilizar la caché, no puede ocurrir que leamos un fichero que aún no haya sido completamente escrito por otro hilo, o que alguien lo borre "bajo sus manos". Por tanto, el uso de la caché es completamente seguro. +Escribe la caché en archivos en disco. El almacenamiento `Nette\Caching\Storages\FileStorage` está muy bien optimizado para el rendimiento y, sobre todo, garantiza la atomicidad completa de las operaciones. ¿Qué significa eso? Que al usar la caché, no puede suceder que leamos un archivo que otro hilo aún no ha escrito por completo, o que alguien lo elimine "debajo de nuestras manos". Por lo tanto, el uso de la caché es completamente seguro. -Este almacenamiento también tiene una importante característica incorporada que evita un aumento extremo del uso de la CPU cuando la caché se borra o se enfría (es decir, no se crea). Se trata de la prevención de la "estampida de":https://en.wikipedia.org/wiki/Cache_stampede la caché. -Ocurre que en un momento dado hay varias peticiones concurrentes que quieren lo mismo de la caché (por ejemplo, el resultado de una consulta SQL costosa) y, como no se almacena en caché, todos los procesos empiezan a ejecutar la misma consulta SQL. -La carga del procesador se multiplica e incluso puede ocurrir que ningún proceso pueda responder dentro del tiempo límite, la caché no se cree y la aplicación se cuelgue. -Afortunadamente, la caché en Nette funciona de tal manera que cuando hay varias peticiones concurrentes para un mismo elemento, sólo lo genera el primer hilo, los demás esperan y luego utilizan el resultado generado. +Este almacenamiento también tiene una función importante incorporada que evita un aumento extremo del uso de la CPU cuando se borra la caché o aún no se ha calentado (es decir, creado). Esta es una prevención contra la "estampida de caché":https://en.wikipedia.org/wiki/Cache_stampede. Sucede que en un momento dado, un gran número de solicitudes concurrentes llegan queriendo lo mismo de la caché (por ejemplo, el resultado de una consulta SQL costosa) y como no está en la memoria caché, todos los procesos comienzan a ejecutar la misma consulta SQL. La carga se multiplica y puede incluso suceder que ningún hilo logre responder dentro del límite de tiempo, la caché no se crea y la aplicación colapsa. Afortunadamente, la caché en Nette funciona de tal manera que cuando hay múltiples solicitudes concurrentes para un elemento, solo el primer hilo lo genera, los demás esperan y luego usan el resultado generado. -Ejemplo de creación de un FileStorage: +Ejemplo de creación de FileStorage: ```php -// el almacenamiento será el directorio '/ruta/a/temp' del disco +// el almacenamiento será el directorio '/path/to/temp' en el disco $storage = new Nette\Caching\Storages\FileStorage('/path/to/temp'); ``` -MemcachedStorage .[#toc-memcachedstorage] ------------------------------------------ +MemcachedStorage +---------------- -El servidor [Memcached |https://memcached.org] es un sistema de almacenamiento distribuido de alto rendimiento cuyo adaptador es `Nette\Caching\Storages\MemcachedStorage`. En la configuración, especifique la dirección IP y el puerto si difiere del estándar 11211. +El servidor [Memcached|https://memcached.org] es un sistema de almacenamiento en memoria distribuida de alto rendimiento, cuyo adaptador es `Nette\Caching\Storages\MemcachedStorage`. En la configuración, especificamos la dirección IP y el puerto, si es diferente del estándar 11211. .[caution] Requiere la extensión PHP `memcached`. @@ -366,16 +382,16 @@ services: ``` -MemoryStorage .[#toc-memorystorage] ------------------------------------ +MemoryStorage +------------- -`Nette\Caching\Storages\MemoryStorage` es un almacenamiento que guarda los datos en un array PHP y por lo tanto se pierde cuando se termina la petición. +`Nette\Caching\Storages\MemoryStorage` es un almacenamiento que guarda los datos en un array PHP y, por lo tanto, se pierden al finalizar la solicitud. -Almacenamiento SQLite .[#toc-sqlitestorage] -------------------------------------------- +SQLiteStorage +------------- -La base de datos SQLite y el adaptador `Nette\Caching\Storages\SQLiteStorage` ofrecen una forma de almacenar en caché en un único archivo en disco. La configuración especificará la ruta a este archivo. +La base de datos SQLite y el adaptador `Nette\Caching\Storages\SQLiteStorage` ofrecen una forma de almacenar la caché en un solo archivo en el disco. En la configuración, especificamos la ruta a este archivo. .[caution] Requiere las extensiones PHP `pdo` y `pdo_sqlite`. @@ -386,16 +402,16 @@ services: ``` -DevNullStorage .[#toc-devnullstorage] -------------------------------------- +DevNullStorage +-------------- -Una implementación especial de almacenamiento es `Nette\Caching\Storages\DevNullStorage`, que en realidad no almacena datos en absoluto. Por lo tanto, es adecuada para realizar pruebas si queremos eliminar el efecto de la caché. +Una implementación especial de almacenamiento es `Nette\Caching\Storages\DevNullStorage`, que en realidad no guarda datos en absoluto. Por lo tanto, es adecuado para pruebas cuando queremos eliminar la influencia de la caché. -Uso de la caché en el código .[#toc-using-cache-in-code] -======================================================== +Uso de la caché en el código +============================ -Cuando se utiliza el almacenamiento en caché en el código, tienes dos maneras de cómo hacerlo. La primera es que obtengas el objeto de almacenamiento pasándolo mediante [inyección de dependencia |dependency-injection:passing-dependencies] y luego crees un objeto `Cache`: +Al usar la caché en el código, tenemos dos formas de hacerlo. La primera es que nos pasen el almacenamiento mediante [inyección de dependencias |dependency-injection:passing-dependencies] y creemos un objeto `Cache`: ```php use Nette; @@ -411,7 +427,7 @@ class ClassOne } ``` -La segunda forma es que obtengas el objeto de almacenamiento `Cache`: +La segunda opción es que nos pasen directamente el objeto `Cache`: ```php class ClassTwo @@ -423,7 +439,7 @@ class ClassTwo } ``` -El objeto `Cache` se crea entonces directamente en la configuración de la siguiente manera: +El objeto `Cache` se crea luego directamente en la configuración de esta manera: ```neon services: @@ -431,12 +447,12 @@ services: ``` -Diario .[#toc-journal] -====================== +Journal +======= -Nette almacena las etiquetas y prioridades en un diario. Por defecto, se utiliza SQLite y el archivo `journal.s3db` para ello, y **se requieren las extensiones de PHP `pdo` y `pdo_sqlite`.** +Nette guarda las etiquetas y prioridades en el llamado journal. Por defecto, se utiliza SQLite y el archivo `journal.s3db` para esto, y **se requieren las extensiones PHP `pdo` y `pdo_sqlite`.** -Puede cambiar el diario en la configuración: +Puede cambiar el journal en la configuración: ```neon services: @@ -444,4 +460,25 @@ services: ``` -{{leftbar: nette:@menu-topics}} +Servicios DI +============ + +Estos servicios se agregan al contenedor DI: + +| Nombre | Tipo | Descripción +|---------------------------------------------------------- +| `cache.journal` | [api:Nette\Caching\Storages\Journal] | journal +| `cache.storage` | [api:Nette\Caching\Storage] | almacenamiento + + +Desactivar la caché +=================== + +Una de las formas de desactivar la caché en la aplicación es establecer el almacenamiento en [#DevNullStorage]: + +```neon +services: + cache.storage: Nette\Caching\Storages\DevNullStorage +``` + +Esta configuración no afecta el almacenamiento en caché de plantillas en Latte o el contenedor DI, ya que estas librerías no utilizan los servicios de nette/caching y gestionan su caché de forma independiente. Además, su caché [no necesita ser desactivada |nette:troubleshooting#Cómo desactivar la caché durante el desarrollo] en el modo de desarrollo. diff --git a/caching/es/@meta.texy b/caching/es/@meta.texy new file mode 100644 index 0000000000..25d506cde9 --- /dev/null +++ b/caching/es/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Documentación}} +{{leftbar: nette:@menu-topics}} diff --git a/caching/fr/@home.texy b/caching/fr/@home.texy index 053c4d11e4..f0dabcdc54 100644 --- a/caching/fr/@home.texy +++ b/caching/fr/@home.texy @@ -1,38 +1,38 @@ -Mise en cache +Nette Caching ************* <div class=perex> -La mise en cache accélère votre application en stockant les données - une fois qu'elles ont été récupérées - pour une utilisation future. Nous allons vous montrer : +Le cache accélère votre application en stockant les données coûteuses à obtenir pour une utilisation ultérieure. Nous allons vous montrer : -- Comment utiliser le cache -- Comment modifier le stockage du cache -- Comment invalider correctement le cache +- comment utiliser le cache +- comment changer le stockage +- comment invalider correctement le cache </div> -L'utilisation du cache est très simple dans Nette, mais elle couvre également des besoins de cache très avancés. Il est conçu pour la performance et une durabilité à 100%. Fondamentalement, vous trouverez des adaptateurs pour le stockage backend le plus courant. Il permet l'invalidation basée sur les balises, la protection du cache, l'expiration du temps, etc. +L'utilisation du cache est très facile dans Nette, tout en couvrant des besoins très avancés. Il est conçu pour la performance et une résilience à 100%. Par défaut, vous trouverez des adaptateurs pour les stockages backend les plus courants. Il permet l'invalidation basée sur les tags, l'expiration temporelle, dispose d'une protection contre le cache stampede, etc. -Installation .[#toc-installation] -================================= +Installation +============ -Téléchargez et installez le paquet en utilisant [Composer |best-practices:composer]: +La bibliothèque peut être téléchargée et installée en utilisant l'outil [Composer|best-practices:composer] : ```shell composer require nette/caching ``` -Utilisation de base .[#toc-basic-usage] -======================================= +Utilisation de base +=================== -Le centre du travail avec le cache est l'objet [api:Nette\Caching\Cache]. Nous créons son instance et passons en paramètre au constructeur ce que l'on appelle le stockage. Il s'agit d'un objet représentant l'endroit où les données seront physiquement stockées (base de données, Memcached, fichiers sur disque, ...). Vous obtenez l'objet storage en le passant en utilisant l'[injection de dépendance |dependency-injection:passing-dependencies] avec le type `Nette\Caching\Storage`. Vous découvrirez tous les éléments essentiels dans la [section Storage |#Storages]. +Le cœur du travail avec le cache est l'objet [api:Nette\Caching\Cache]. Nous créons son instance et passons au constructeur ce qu'on appelle un stockage. C'est un objet représentant l'endroit où les données seront physiquement stockées (base de données, Memcached, fichiers sur disque, ...). Nous accédons au stockage en le faisant passer via [l'injection de dépendances |dependency-injection:passing-dependencies] avec le type `Nette\Caching\Storage`. Vous trouverez tout ce qui est essentiel dans la [section Stockages |#Stockages]. .[warning] -Dans la version 3.0, l'interface avait toujours le type `I` prefix, so the name was `Nette\Caching\IStorage`. De plus, les constantes de la classe `Cache` prenaient la majuscule, donc par exemple `Cache::EXPIRE` au lieu de `Cache::Expire`. +Dans la version 3.0, l'interface avait encore le préfixe `I`, donc le nom était `Nette\Caching\IStorage`. De plus, les constantes de la classe `Cache` étaient écrites en majuscules, donc par exemple `Cache::EXPIRE` au lieu de `Cache::Expire`. -Pour les exemples suivants, supposons que nous ayons un alias `Cache` et un stockage dans la variable `$storage`. +Pour les exemples suivants, supposons que nous avons créé un alias `Cache` et que la variable `$storage` contient le stockage. ```php use Nette\Caching\Cache; @@ -40,51 +40,51 @@ use Nette\Caching\Cache; $storage = /* ... */; // instance de Nette\Caching\Storage ``` -Le cache est en fait un *magasin de clés-valeurs*, nous lisons et écrivons donc les données sous les clés comme dans les tableaux associatifs. Les applications sont constituées d'un certain nombre de parties indépendantes, et si elles utilisaient toutes un seul stockage (par exemple, un répertoire sur un disque), il y aurait tôt ou tard une collision de clés. Le Framework Nette résout le problème en divisant l'espace entier en espaces de noms (sous-répertoires). Chaque partie du programme utilise alors son propre espace avec un nom unique et aucune collision ne peut se produire. +Le cache est en fait un *key–value store*, c'est-à-dire que nous lisons et écrivons des données sous des clés, tout comme avec les tableaux associatifs. Les applications sont composées de nombreuses parties indépendantes, et si toutes utilisaient un seul stockage (imaginez un seul répertoire sur le disque), tôt ou tard, une collision de clés se produirait. Le framework Nette résout ce problème en divisant tout l'espace en espaces de noms (sous-répertoires). Chaque partie du programme utilise alors son propre espace avec un nom unique, et aucune collision ne peut plus se produire. -Le nom de l'espace est spécifié comme deuxième paramètre du constructeur de la classe Cache : +Nous spécifions le nom de l'espace comme deuxième paramètre du constructeur de la classe Cache : ```php $cache = new Cache($storage, 'Full Html Pages'); ``` -Nous pouvons maintenant utiliser l'objet `$cache` pour lire et écrire dans le cache. La méthode `load()` est utilisée pour les deux. Le premier argument est la clé et le second est le callback PHP, qui est appelé lorsque la clé n'est pas trouvée dans le cache. Le callback génère une valeur, la renvoie et la met en cache : +Maintenant, nous pouvons utiliser l'objet `$cache` pour lire et écrire dans le cache. La méthode `load()` est utilisée pour les deux. Le premier argument est la clé et le second est un callback PHP, qui est appelé lorsque la clé n'est pas trouvée dans le cache. Le callback génère la valeur, la retourne et elle est stockée dans le cache : ```php $value = $cache->load($key, function () use ($key) { - $computedValue = /* ... */; // calculs lourds + $computedValue = /* ... */; // calcul coûteux return $computedValue; }); ``` -Si le deuxième paramètre n'est pas spécifié `$value = $cache->load($key)`, la valeur `null` est retournée si l'élément n'est pas dans le cache. +Si le deuxième paramètre n'est pas fourni `$value = $cache->load($key)`, `null` est retourné si l'élément n'est pas dans le cache. .[tip] -Ce qui est bien, c'est que toutes les structures sérialisables peuvent être mises en cache, pas seulement les chaînes de caractères. Et il en va de même pour les clés. +Ce qui est génial, c'est que vous pouvez stocker n'importe quelle structure sérialisable dans le cache, pas seulement des chaînes de caractères. Et la même chose s'applique même aux clés. -L'élément est effacé du cache à l'aide de la méthode `remove()`: +Nous supprimons un élément du cache en utilisant la méthode `remove()` : ```php $cache->remove($key); ``` -Vous pouvez également mettre un élément en cache en utilisant la méthode `$cache->save($key, $value, array $dependencies = [])`. Cependant, la méthode ci-dessus utilisant `load()` est préférable. +Vous pouvez également enregistrer un élément dans le cache en utilisant la méthode `$cache->save($key, $value, array $dependencies = [])`. Cependant, la méthode préférée est celle mentionnée ci-dessus en utilisant `load()`. -Mémorisation .[#toc-memoization] -================================ +Memoization +=========== -La mémorisation consiste à mettre en cache le résultat d'une fonction ou d'une méthode afin de pouvoir l'utiliser la prochaine fois au lieu de calculer la même chose encore et encore. +La mémoïsation signifie la mise en cache du résultat d'un appel de fonction ou de méthode afin que vous puissiez l'utiliser la prochaine fois sans recalculer la même chose encore et encore. -Les méthodes et les fonctions peuvent être appelées mémoïsées en utilisant `call(callable $callback, ...$args)`: +Les méthodes et les fonctions peuvent être appelées de manière mémoïsée en utilisant `call(callable $callback, ...$args)` : ```php $result = $cache->call('gethostbyaddr', $ip); ``` -La fonction `gethostbyaddr()` n'est appelée qu'une seule fois pour chaque paramètre `$ip` et la fois suivante, la valeur du cache sera retournée. +La fonction `gethostbyaddr()` ne sera appelée qu'une seule fois pour chaque paramètre `$ip`, et la prochaine fois, la valeur sera retournée depuis le cache. -Il est également possible de créer une enveloppe mémorisée pour une méthode ou une fonction qui peut être appelée ultérieurement : +Il est également possible de créer un wrapper mémoïsé autour d'une méthode ou d'une fonction, qui peut être appelé plus tard : ```php function factorial($num) @@ -94,17 +94,17 @@ function factorial($num) $memoizedFactorial = $cache->wrap('factorial'); -$result = $memoizedFactorial(5); // le comptabilise -$result = $memoizedFactorial(5); // le retourne du cache +$result = $memoizedFactorial(5); // calcule la première fois +$result = $memoizedFactorial(5); // la deuxième fois depuis le cache ``` -Expiration et invalidation .[#toc-expiration-invalidation] -========================================================== +Expiration & Invalidation +========================= -Avec la mise en cache, il est nécessaire d'aborder la question de l'invalidation de certaines des données précédemment enregistrées au fil du temps. Nette Framework fournit un mécanisme permettant de limiter la validité des données et de les supprimer d'une manière contrôlée ("les invalider", selon la terminologie du framework). +Lors de la mise en cache, il est nécessaire de résoudre la question de savoir quand les données précédemment stockées deviennent invalides. Le framework Nette propose un mécanisme pour limiter la validité des données ou les supprimer de manière contrôlée (dans la terminologie du framework, "invalider"). -La validité des données est définie au moment de l'enregistrement en utilisant le troisième paramètre de la méthode `save()`, par exemple : +La validité des données est définie au moment de l'enregistrement à l'aide du troisième paramètre de la méthode `save()`, par exemple : ```php $cache->save($key, $value, [ @@ -112,7 +112,7 @@ $cache->save($key, $value, [ ]); ``` -Ou à l'aide du paramètre `$dependencies` passé par référence à la callback de la méthode `load()`, par ex : +Ou en utilisant le paramètre `$dependencies` passé par référence au callback de la méthode `load()`, par exemple : ```php $value = $cache->load($key, function (&$dependencies) { @@ -129,26 +129,26 @@ $value = $cache->load($key, function () { }, [Cache::Expire => '20 minutes']); ``` -Dans les exemples suivants, nous supposerons la deuxième variante et donc l'existence d'une variable `$dependencies`. +Dans les exemples suivants, nous supposerons la deuxième variante et donc l'existence de la variable `$dependencies`. -Expiration .[#toc-expiration] ------------------------------ +Expiration +---------- -L'expiration la plus simple est la limite de temps. Voici comment mettre en cache des données valables pendant 20 minutes : +L'expiration la plus simple est une limite de temps. Voici comment nous mettons en cache les données avec une validité de 20 minutes : ```php -// il accepte également le nombre de secondes ou le timestamp UNIX +// accepte également le nombre de secondes ou un timestamp UNIX $dependencies[Cache::Expire] = '20 minutes'; ``` -Si nous voulons prolonger la période de validité à chaque lecture, c'est possible de cette façon, mais attention, cela augmentera l'overhead du cache : +Si nous voulions prolonger la période de validité à chaque lecture, cela peut être réalisé comme suit, mais attention, la surcharge du cache augmentera : ```php $dependencies[Cache::Sliding] = true; ``` -L'option la plus pratique est la possibilité de laisser les données expirer lorsqu'un fichier particulier est modifié ou l'un de plusieurs fichiers. Cela peut être utilisé, par exemple, pour mettre en cache les données résultant de la procession de ces fichiers. Utilisez des chemins absolus. +Une option pratique est de laisser les données expirer lorsqu'un fichier ou l'un des multiples fichiers change. Cela peut être utilisé, par exemple, lors de la mise en cache de données résultant du traitement de ces fichiers. Utilisez des chemins absolus. ```php $dependencies[Cache::Files] = '/path/to/data.yaml'; @@ -156,13 +156,13 @@ $dependencies[Cache::Files] = '/path/to/data.yaml'; $dependencies[Cache::Files] = ['/path/to/data1.yaml', '/path/to/data2.yaml']; ``` -On peut laisser un élément du cache expirer lorsqu'un autre élément (ou un parmi plusieurs autres) expire. Cela peut être utilisé lorsque nous mettons en cache la page HTML entière et des fragments de celle-ci sous d'autres clés. Dès que le fragment change, la page entière devient invalide. Si nous avons des fragments stockés sous des clés telles que `frag1` et `frag2`, nous utiliserons : +Nous pouvons laisser un élément du cache expirer lorsqu'un autre élément (ou l'un de plusieurs autres) expire. Cela peut être utilisé lorsque nous mettons en cache, par exemple, une page HTML entière et ses fragments sous d'autres clés. Dès qu'un fragment change, toute la page est invalidée. Si nous avons des fragments stockés sous les clés `frag1` et `frag2`, par exemple, nous utilisons : ```php $dependencies[Cache::Items] = ['frag1', 'frag2']; ``` -L'expiration peut également être contrôlée à l'aide de fonctions personnalisées ou de méthodes statiques, qui décident toujours à la lecture si l'élément est toujours valide. Par exemple, nous pouvons laisser l'élément expirer lorsque la version de PHP change. Nous allons créer une fonction qui compare la version actuelle avec le paramètre, et lors de la sauvegarde, nous ajouterons un tableau de la forme `[function name, ...arguments]` aux dépendances : +L'expiration peut également être contrôlée à l'aide de fonctions personnalisées ou de méthodes statiques, qui décident à chaque lecture si l'élément est toujours valide. De cette façon, nous pouvons laisser un élément expirer chaque fois que la version de PHP change. Nous créons une fonction qui compare la version actuelle avec un paramètre, et lors de l'enregistrement, nous ajoutons un tableau de la forme `[nom de la fonction, ...arguments]` aux dépendances : ```php function checkPhpVersion($ver): bool @@ -171,11 +171,11 @@ function checkPhpVersion($ver): bool } $dependencies[Cache::Callbacks] = [ - ['checkPhpVersion', PHP_VERSION_ID] // expire lorsque checkPhpVersion(...) === false + ['checkPhpVersion', PHP_VERSION_ID] // expire quand checkPhpVersion(...) === false ]; ``` -Bien sûr, tous les critères peuvent être combinés. Le cache expire alors lorsqu'au moins un critère n'est pas rempli. +Bien sûr, tous les critères peuvent être combinés. Le cache expirera alors si au moins un critère n'est pas rempli. ```php $dependencies[Cache::Expire] = '20 minutes'; @@ -183,16 +183,16 @@ $dependencies[Cache::Files] = '/path/to/data.yaml'; ``` -Invalidation à l'aide de balises .[#toc-invalidation-using-tags] ----------------------------------------------------------------- +Invalidation par tags +--------------------- -Les balises sont un outil d'invalidation très utile. Nous pouvons attribuer une liste de balises, qui sont des chaînes de caractères arbitraires, à chaque élément stocké dans le cache. Par exemple, supposons que nous ayons une page HTML avec un article et des commentaires, que nous voulons mettre en cache. Nous spécifions donc des balises lors de l'enregistrement dans le cache : +Les tags sont un outil d'invalidation très utile. Nous pouvons assigner une liste de tags, qui sont des chaînes arbitraires, à chaque élément du cache. Par exemple, supposons que nous ayons une page HTML avec un article et des commentaires que nous allons mettre en cache. Lors de l'enregistrement, nous spécifions les tags : ```php $dependencies[Cache::Tags] = ["article/$articleId", "comments/$articleId"]; ``` -Maintenant, passons à l'administration. Ici, nous avons un formulaire pour l'édition des articles. En même temps que la sauvegarde de l'article dans une base de données, nous appelons la commande `clean()`, qui supprimera les articles mis en cache par étiquette : +Passons à l'administration. Ici, nous trouvons un formulaire pour éditer l'article. En même temps que l'enregistrement de l'article dans la base de données, nous appelons la commande `clean()`, qui supprime les éléments du cache par tag : ```php $cache->clean([ @@ -200,7 +200,7 @@ $cache->clean([ ]); ``` -De même, au lieu d'ajouter un nouveau commentaire (ou de modifier un commentaire), nous n'oublierons pas d'invalider la balise correspondante : +De même, à l'endroit où un nouveau commentaire est ajouté (ou un commentaire est édité), nous n'oublions pas d'invalider le tag correspondant : ```php $cache->clean([ @@ -208,22 +208,22 @@ $cache->clean([ ]); ``` -Qu'avons-nous obtenu ? Que notre cache HTML sera invalidé (supprimé) à chaque fois que l'article ou les commentaires changeront. Lorsque vous modifiez un article avec ID = 10, la balise `article/10` est forcée d'être invalidée et la page HTML portant la balise est supprimée du cache. La même chose se produit lorsque vous insérez un nouveau commentaire sous l'article concerné. +Qu'avons-nous accompli ? Que notre cache HTML sera invalidé (supprimé) chaque fois que l'article ou les commentaires changent. Lorsqu'un article avec l'ID = 10 est édité, le tag `article/10` est invalidé de force, et la page HTML portant ce tag est supprimée du cache. La même chose se produit lors de l'insertion d'un nouveau commentaire sous l'article correspondant. .[note] -Les tags nécessitent [Journal |#Journal]. +Les tags nécessitent ce qu'on appelle un [#Journal]. -Invalidation par priorité .[#toc-invalidation-by-priority] ----------------------------------------------------------- +Invalidation par priorité +------------------------- -Nous pouvons définir la priorité des éléments individuels dans le cache, et il sera possible de les supprimer de manière contrôlée lorsque, par exemple, le cache dépasse une certaine taille : +Nous pouvons définir une priorité pour les éléments individuels dans le cache, qui peut être utilisée pour les supprimer lorsque, par exemple, le cache dépasse une certaine taille : ```php $dependencies[Cache::Priority] = 50; ``` -Supprimer tous les éléments dont la priorité est égale ou inférieure à 100 : +Nous supprimons tous les éléments avec une priorité égale ou inférieure à 100 : ```php $cache->clean([ @@ -232,13 +232,13 @@ $cache->clean([ ``` .[note] -Les priorités nécessitent ce qu'on appelle un [journal |#Journal]. +Les priorités nécessitent ce qu'on appelle un [#Journal]. -Effacer le cache .[#toc-clear-cache] ------------------------------------- +Suppression du cache +-------------------- -Le paramètre `Cache::All` efface tout : +Le paramètre `Cache::All` supprime tout : ```php $cache->clean([ @@ -247,51 +247,70 @@ $cache->clean([ ``` -Lecture en vrac .[#toc-bulk-reading] -==================================== +Lecture en masse +================ -Pour la lecture et l'écriture en masse dans le cache, on utilise la méthode `bulkLoad()`, où l'on passe un tableau de clés et on obtient un tableau de valeurs : +Pour lire et écrire en masse dans le cache, utilisez la méthode `bulkLoad()`, à laquelle nous passons un tableau de clés et obtenons un tableau de valeurs : ```php $values = $cache->bulkLoad($keys); ``` -La méthode `bulkLoad()` fonctionne de manière similaire à `load()` avec le deuxième paramètre de rappel, auquel on passe la clé de l'élément généré : +La méthode `bulkLoad()` fonctionne de manière similaire à `load()` avec le deuxième paramètre callback, auquel la clé de l'élément généré est passée : ```php $values = $cache->bulkLoad($keys, function ($key, &$dependencies) { - $computedValue = /* ... */; // calculs lourds + $computedValue = /* ... */; // calcul coûteux basé sur $key return $computedValue; }); ``` -Mise en cache de la sortie .[#toc-output-caching] -================================================= +Utilisation avec PSR-16 .{data-version:3.3.1} +============================================= + +Pour utiliser Nette Cache avec l'interface PSR-16, vous pouvez utiliser l'adaptateur `PsrCacheAdapter`. Il permet une intégration transparente entre Nette Cache et tout code ou bibliothèque qui attend un cache compatible PSR-16. + +```php +$psrCache = new Nette\Bridges\Psr\PsrCacheAdapter($storage); +``` + +Vous pouvez maintenant utiliser `$psrCache` comme un cache PSR-16 : + +```php +$psrCache->set('key', 'value', 3600); // stocke la valeur pendant 1 heure +$value = $psrCache->get('key', 'default'); +``` + +L'adaptateur prend en charge toutes les méthodes définies dans PSR-16, y compris `getMultiple()`, `setMultiple()` et `deleteMultiple()`. + + +Mise en cache de la sortie +========================== La sortie peut être capturée et mise en cache de manière très élégante : ```php if ($capture = $cache->capture($key)) { - echo ... // impression de quelques données + echo ... // affichage des données - $capture->end(); // sauvegarde de la sortie dans le cache + $capture->end(); // enregistre la sortie dans le cache } ``` -Dans le cas où la sortie est déjà présente dans le cache, la méthode `capture()` l'imprime et renvoie `null`, de sorte que la condition ne sera pas exécutée. Sinon, elle commence à mettre en mémoire tampon la sortie et renvoie l'objet `$capture` à l'aide duquel nous sauvegardons finalement les données dans le cache. +Si la sortie est déjà stockée dans le cache, la méthode `capture()` l'affiche et renvoie `null`, donc la condition n'est pas exécutée. Sinon, elle commence à capturer la sortie et renvoie l'objet `$capture`, que nous utilisons pour finalement enregistrer les données affichées dans le cache. .[note] -Dans la version 3.0, cette méthode s'appelait `$cache->start()`. +Dans la version 3.0, la méthode s'appelait `$cache->start()`. -Mise en cache dans Latte .[#toc-caching-in-latte] -================================================= +Mise en cache dans Latte +======================== -La mise en cache dans les modèles [Latte |latte:] est très facile, il suffit d'envelopper une partie du modèle avec des balises `{cache}...{/cache}`. Le cache est automatiquement invalidé lorsque le modèle source est modifié (y compris tout modèle inclus dans les balises `{cache}` ). Les balises `{cache}` peuvent être imbriquées, et lorsqu'un bloc imbriqué est invalidé (par exemple, par une balise), le bloc parent est également invalidé. +La mise en cache dans les templates [Latte |latte:] est très simple, il suffit d'envelopper une partie du template avec les balises `{cache}...{/cache}`. Le cache est automatiquement invalidé lorsque le template source change (y compris les templates inclus dans le bloc de cache). Les balises `{cache}` peuvent être imbriquées, et lorsqu'un bloc imbriqué est invalidé (par exemple, par un tag), le bloc parent est également invalidé. -Dans la balise, il est possible de spécifier les clés auxquelles le cache sera lié (ici la variable `$id`) et de définir les [balises d' |#Invalidation using Tags] expiration et [d'invalidation |#Invalidation using Tags]. +Dans la balise, il est possible de spécifier les clés auxquelles le cache sera lié (ici la variable `$id`) et de définir l'expiration et les [tags pour l'invalidation |#Invalidation par tags]. ```latte {cache $id, expire: '20 minutes', tags: [tag1, tag2]} @@ -299,9 +318,9 @@ Dans la balise, il est possible de spécifier les clés auxquelles le cache sera {/cache} ``` -Tous les paramètres sont facultatifs, il n'est donc pas nécessaire de spécifier l'expiration, les balises ou les clés. +Tous les paramètres sont facultatifs, nous n'avons donc pas besoin de spécifier l'expiration, les tags ou même les clés. -L'utilisation du cache peut également être conditionnée par `if` - le contenu sera alors mis en cache uniquement si la condition est remplie : +L'utilisation du cache peut également être conditionnée à l'aide de `if` - le contenu ne sera alors mis en cache que si la condition est remplie : ```latte {cache $id, if: !$form->isSubmitted()} @@ -310,21 +329,21 @@ L'utilisation du cache peut également être conditionnée par `if` - le contenu ``` -Stockages .[#toc-storages] -========================== +Stockages +========= -Un stockage est un objet qui représente l'endroit où les données sont physiquement stockées. Nous pouvons utiliser une base de données, un serveur Memcached, ou le stockage le plus disponible, qui sont les fichiers sur le disque. +Un stockage est un objet représentant l'endroit où les données sont physiquement stockées. Nous pouvons utiliser une base de données, un serveur Memcached ou le stockage le plus accessible, qui sont des fichiers sur disque. -|---------------------- -| Stockage | Description -|---------------------- -| [FileStorage |#FileStorage]: stockage par défaut avec sauvegarde dans des fichiers sur le disque. -| [MemcachedStorage |#MemcachedStorage] | utilise le serveur `Memcached` -| [MemoryStorage |#MemoryStorage] - Les données sont stockées temporairement en mémoire. -| [SQLiteStorage |#SQLiteStorage] - Les données sont stockées dans une base de données SQLite. -| [DevNullStorage |#DevNullStorage] - Les données ne sont pas stockées - à des fins de test. +|----------------- +| Stockage | Description +|----------------- +| [#FileStorage] | stockage par défaut avec enregistrement dans des fichiers sur disque +| [#MemcachedStorage]| utilise un serveur `Memcached` +| [#MemoryStorage] | les données sont temporairement en mémoire +| [#SQLiteStorage] | les données sont stockées dans une base de données SQLite +| [#DevNullStorage] | les données ne sont pas stockées, adapté aux tests -Vous obtenez l'objet de stockage en le passant en utilisant l'[injection de dépendance |dependency-injection:passing-dependencies] avec le type `Nette\Caching\Storage`. Par défaut, Nette fournit un objet FileStorage qui stocke les données dans un sous-dossier `cache` dans le répertoire des [fichiers temporaires |application:bootstrap#Temporary Files]. +Vous accédez à l'objet de stockage en le faisant passer via [l'injection de dépendances |dependency-injection:passing-dependencies] avec le type `Nette\Caching\Storage`. Nette fournit par défaut l'objet `FileStorage`, qui stocke les données dans le sous-répertoire `cache` du répertoire des [fichiers temporaires |application:bootstrapping#Fichiers Temporaires]. Vous pouvez modifier le stockage dans la configuration : @@ -334,31 +353,28 @@ services: ``` -FileStorage .[#toc-filestorage] -------------------------------- +FileStorage +----------- -Ecrit le cache dans des fichiers sur le disque. Le stockage `Nette\Caching\Storages\FileStorage` est très bien optimisé pour les performances et assure surtout une atomicité totale des opérations. Qu'est-ce que cela signifie ? Que lors de l'utilisation du cache, il ne peut pas arriver que l'on lise un fichier qui n'a pas encore été complètement écrit par un autre thread, ou que quelqu'un le supprime "sous vos mains". L'utilisation du cache est donc totalement sûre. +Écrit le cache dans des fichiers sur disque. Le stockage `Nette\Caching\Storages\FileStorage` est très bien optimisé pour les performances et garantit surtout une atomicité complète des opérations. Qu'est-ce que cela signifie ? Que lors de l'utilisation du cache, il ne peut pas arriver que nous lisions un fichier qui n'a pas encore été complètement écrit par un autre thread, ou qu'il soit supprimé "sous nos yeux". L'utilisation du cache est donc totalement sûre. -Ce stockage possède également une importante fonctionnalité intégrée qui empêche une augmentation extrême de l'utilisation du CPU lorsque le cache est effacé ou froid (c'est-à-dire non créé). Il s'agit de la prévention de la "ruée vers le cache":https://en.wikipedia.org/wiki/Cache_stampede. -Il arrive qu'à un moment donné, plusieurs requêtes concurrentes souhaitent obtenir la même chose du cache (par exemple, le résultat d'une requête SQL coûteuse) et, comme il n'est pas mis en cache, tous les processus commencent à exécuter la même requête SQL. -La charge du processeur est multipliée et il peut même arriver qu'aucun thread ne puisse répondre dans le délai imparti, que le cache ne soit pas créé et que l'application plante. -Heureusement, le cache de Nette fonctionne de telle manière que lorsqu'il y a plusieurs demandes simultanées pour un même élément, il est généré uniquement par le premier thread, les autres attendent et utilisent ensuite le résultat généré. +Ce stockage dispose également d'une fonction intégrée importante qui empêche une augmentation extrême de l'utilisation du processeur lorsque le cache est supprimé ou n'est pas encore chaud (c'est-à-dire créé). C'est une prévention contre le "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Il arrive qu'un grand nombre de requêtes simultanées arrivent en même temps, demandant la même chose au cache (par exemple, le résultat d'une requête SQL coûteuse), et comme il n'est pas dans le cache, tous les processus commencent à exécuter la même requête SQL. La charge se multiplie ainsi et il peut même arriver qu'aucun thread ne parvienne à répondre dans le délai imparti, que le cache ne soit pas créé et que l'application plante. Heureusement, le cache de Nette fonctionne de telle manière que lors de plusieurs requêtes simultanées pour un même élément, seul le premier thread le génère, les autres attendent et utilisent ensuite le résultat généré. -Exemple de création d'un FileStorage : +Exemple de création de `FileStorage` : ```php -// le stockage sera le répertoire '/path/to/temp' sur le disque. +// le stockage sera le répertoire '/path/to/temp' sur le disque $storage = new Nette\Caching\Storages\FileStorage('/path/to/temp'); ``` -MemcachedStorage .[#toc-memcachedstorage] ------------------------------------------ +MemcachedStorage +---------------- -Le serveur [Memcached |https://memcached.org] est un système de stockage distribué haute performance dont l'adaptateur est `Nette\Caching\Storages\MemcachedStorage`. Dans la configuration, spécifiez l'adresse IP et le port s'ils diffèrent du standard 11211. +Le serveur [Memcached |https://memcached.org] est un système de stockage distribué en mémoire très performant, dont l'adaptateur est `Nette\Caching\Storages\MemcachedStorage`. Dans la configuration, nous spécifions l'adresse IP et le port, s'ils diffèrent du port standard 11211. .[caution] -Requiert l'extension PHP `memcached`. +Nécessite l'extension PHP `memcached`. ```neon services: @@ -366,16 +382,16 @@ services: ``` -MemoryStorage .[#toc-memorystorage] ------------------------------------ +MemoryStorage +------------- -`Nette\Caching\Storages\MemoryStorage` est un stockage qui enregistre les données dans un tableau PHP et qui est donc perdu lorsque la requête est terminée. +`Nette\Caching\Storages\MemoryStorage` est un stockage qui enregistre les données dans un tableau PHP, et donc elles sont perdues à la fin de la requête. -SQLiteStorage .[#toc-sqlitestorage] ------------------------------------ +SQLiteStorage +------------- -La base de données SQLite et l'adaptateur `Nette\Caching\Storages\SQLiteStorage` offrent un moyen de mettre en cache un seul fichier sur le disque. La configuration spécifiera le chemin vers ce fichier. +La base de données SQLite et l'adaptateur `Nette\Caching\Storages\SQLiteStorage` offrent un moyen de stocker le cache dans un seul fichier sur disque. Dans la configuration, nous spécifions le chemin d'accès à ce fichier. .[caution] Nécessite les extensions PHP `pdo` et `pdo_sqlite`. @@ -386,16 +402,16 @@ services: ``` -DevNullStorage .[#toc-devnullstorage] -------------------------------------- +DevNullStorage +-------------- -Une implémentation spéciale du stockage est `Nette\Caching\Storages\DevNullStorage`, qui ne stocke pas du tout de données. Elle convient donc aux tests si l'on veut éliminer l'effet du cache. +Une implémentation spéciale de stockage est `Nette\Caching\Storages\DevNullStorage`, qui en réalité ne stocke aucune donnée. Il est donc adapté aux tests lorsque nous voulons éliminer l'influence du cache. -Utilisation du cache dans le code .[#toc-using-cache-in-code] -============================================================= +Utilisation du cache dans le code +================================= -Lorsque vous utilisez la mise en cache dans le code, vous avez deux façons de procéder. La première consiste à obtenir l'objet de stockage en le passant à l'aide de l'[injection de dépendances |dependency-injection:passing-dependencies], puis à créer un objet `Cache`: +Lors de l'utilisation du cache dans le code, nous avons deux façons de procéder. La première consiste à recevoir le stockage via [l'injection de dépendances |dependency-injection:passing-dependencies] et à créer un objet `Cache` : ```php use Nette; @@ -411,7 +427,7 @@ class ClassOne } ``` -La deuxième façon est que vous obtenez l'objet de stockage `Cache`: +La deuxième option est de recevoir directement l'objet `Cache` : ```php class ClassTwo @@ -423,7 +439,7 @@ class ClassTwo } ``` -L'objet `Cache` est alors créé directement dans la configuration comme suit : +L'objet `Cache` est ensuite créé directement dans la configuration de cette manière : ```neon services: @@ -431,12 +447,12 @@ services: ``` -Journal .[#toc-journal] -======================= +Journal +======= -Nette stocke les tags et les priorités dans un journal. Par défaut, SQLite et le fichier `journal.s3db` sont utilisés pour cela, et **les extensions PHP `pdo` et `pdo_sqlite` sont nécessaires.** +Nette stocke les tags et les priorités dans ce qu'on appelle un journal. Par défaut, SQLite et le fichier `journal.s3db` sont utilisés pour cela, et **les extensions PHP `pdo` et `pdo_sqlite` sont requises.** -Vous pouvez changer le journal dans la configuration : +Vous pouvez modifier le journal dans la configuration : ```neon services: @@ -444,4 +460,25 @@ services: ``` -{{leftbar: nette:@menu-topics}} +Services DI +=========== + +Ces services sont ajoutés au conteneur DI : + +| Nom | Type | Description +|-----------------|------------------------------------------|---------------------------------------------------------- +| `cache.journal` | `Nette\Caching\Storages\Journal` | Le journal utilisé pour les tags et priorités (par défaut SQLiteJournal) +| `cache.storage` | `Nette\Caching\Storage` | Le stockage de cache par défaut (par défaut FileStorage) + + +Désactivation du cache +====================== + +Une des options pour désactiver le cache dans l'application est de définir le stockage sur [#DevNullStorage] : + +```neon +services: + cache.storage: Nette\Caching\Storages\DevNullStorage +``` + +Ce paramètre n'affecte pas la mise en cache des templates dans Latte ou du conteneur DI, car ces bibliothèques n'utilisent pas les services nette/caching et gèrent leur propre cache indépendamment. D'ailleurs, leur cache [n'a pas besoin d'être désactivé |nette:troubleshooting#Comment désactiver le cache pendant le développement] en mode développeur. diff --git a/caching/fr/@meta.texy b/caching/fr/@meta.texy new file mode 100644 index 0000000000..95ec8a4ef6 --- /dev/null +++ b/caching/fr/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Documentation Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/caching/hu/@home.texy b/caching/hu/@home.texy index 5f404391e6..25518b3721 100644 --- a/caching/hu/@home.texy +++ b/caching/hu/@home.texy @@ -1,46 +1,46 @@ -Caching -******* +Nette Caching +************* <div class=perex> -A gyorsítótár felgyorsítja az alkalmazást azáltal, hogy az egyszer már nehezen lekérdezett adatokat tárolja a későbbi felhasználásra. Megmutatjuk neked: +A Cache felgyorsítja az alkalmazást azáltal, hogy az egyszer nehezen megszerzett adatokat elmenti a későbbi felhasználásra. Megmutatjuk: -- Hogyan használjuk a gyorsítótárat? -- Hogyan változtathatja meg a gyorsítótár tárolását -- Hogyan kell megfelelően érvényteleníteni a gyorsítótárat +- hogyan használjuk a cache-t +- hogyan változtassuk meg a tárolót +- hogyan érvénytelenítsük helyesen a cache-t </div> -A gyorsítótár használata nagyon egyszerű a Nette-ben, miközben nagyon fejlett gyorsítótárazási igényeket is lefed. Teljesítményre és 100%-os tartósságra tervezték. Alapvetően a leggyakoribb háttértárolókhoz talál adaptereket. Lehetővé teszi a címkén alapuló érvénytelenítést, a gyorsítótár stampedivédelmet, az időbeli lejáratot stb. +A cache használata a Nette-ben nagyon egyszerű, miközben nagyon fejlett igényeket is lefed. Teljesítményre és 100%-os ellenállóságra tervezték. Alapból adaptereket talál a leggyakoribb háttértárolókhoz. Lehetővé teszi a tag-ek alapján történő érvénytelenítést, az időbeli lejárást, védelmet nyújt a cache stampede ellen stb. -Telepítés .[#toc-installation] -============================== +Telepítés +========= -Töltse le és telepítse a csomagot a [Composer |best-practices:composer] segítségével: +A könyvtárat a [Composer|best-practices:composer] eszközzel töltheti le és telepítheti: ```shell composer require nette/caching ``` -Alapvető használat .[#toc-basic-usage] -====================================== +Alapvető használat +================== -A gyorsítótárral végzett munka középpontjában a [api:Nette\Caching\Cache] objektum áll. Létrehozzuk a példányát, és paraméterként átadjuk a konstruktornak az úgynevezett tárolót. Ami egy olyan objektum, amely azt a helyet képviseli, ahol az adatokat fizikailag tárolni fogjuk (adatbázis, Memcached, fájlok a lemezen, ...). A tároló objektumot a `Nette\Caching\Storage` típusú [függőségi injektálással |dependency-injection:passing-dependencies] kapjuk meg a [függőségi injektálással |dependency-injection:passing-dependencies] történő átadással. Minden lényeges dolgot megtudhat a [Storage (tárolás |#Storages]) című részben. +A cache-sel vagy gyorsítótárral való munka középpontjában az [api:Nette\Caching\Cache] objektum áll. Létrehozunk egy példányt belőle, és paraméterként átadjuk a konstruktornak az úgynevezett tárolót. Ez egy olyan objektum, amely azt a helyet képviseli, ahol az adatok fizikailag tárolódnak (adatbázis, Memcached, fájlok a lemezen, ...). A tárolóhoz úgy juthatunk hozzá, hogy [dependency injection |dependency-injection:passing-dependencies] segítségével kérjük át a `Nette\Caching\Storage` típussal. Minden lényegeset megtudhat a [Tárolók szakaszban |#Tárolók]. .[warning] -A 3.0-ás verzióban az interfész még a `I` prefix, so the name was `Nette\Caching\IStorage`. Emellett a `Cache` osztály konstansait nagybetűvel írták, így például `Cache::EXPIRE` helyett `Cache::Expire`. +A 3.0-s verzióban az interfésznek még volt `I` előtagja, tehát a neve `Nette\Caching\IStorage` volt. Továbbá a `Cache` osztály konstansai nagybetűkkel voltak írva, tehát például `Cache::EXPIRE` a `Cache::Expire` helyett. -A következő példákban tegyük fel, hogy van egy `Cache` aliasunk és egy tárolónk a `$storage` változóban. +A következő példákhoz feltételezzük, hogy létrehoztunk egy `Cache` aliast, és a `$storage` változóban van a tároló. ```php use Nette\Caching\Cache; -$storage = /* ... */; // a Nette\Caching\Storage példánya +$storage = /* ... */; // instance of Nette\Caching\Storage ``` -A gyorsítótár tulajdonképpen egy *kulcs-érték tároló*, tehát az adatokat kulcsok alatt olvassuk és írjuk, mint az asszociatív tömböknél. Az alkalmazások több független részből állnak, és ha ezek mind egy tárolót használnának (ötletként: egy könyvtárat a lemezen), előbb-utóbb kulcsütközésre kerülne sor. A Nette keretrendszer úgy oldja meg a problémát, hogy a teljes tárhelyet névterekre (alkönyvtárakra) osztja. Így a program minden egyes része a saját, egyedi névvel ellátott tárhelyét használja, és nem fordulhat elő ütközés. +A cache valójában egy *key–value store*, tehát az adatokat kulcsok alatt olvassuk és írjuk, ugyanúgy, mint az asszociatív tömböknél. Az alkalmazások számos független részből állnak, és ha mindegyik ugyanazt a tárolót használná (képzeljünk el egyetlen könyvtárat a lemezen), előbb-utóbb kulcsütközés következne be. A Nette Framework ezt a problémát úgy oldja meg, hogy az egész teret névtérekre (alkönyvtárakra) osztja. Minden programrész ezután a saját, egyedi nevű terét használja, és így már nem fordulhat elő ütközés. A névtér nevét a Cache osztály konstruktorának második paramétereként adjuk meg: @@ -48,43 +48,43 @@ A névtér nevét a Cache osztály konstruktorának második paramétereként ad $cache = new Cache($storage, 'Full Html Pages'); ``` -Most már használhatjuk a `$cache` objektumot a gyorsítótárból való olvasáshoz és íráshoz. Mindkettőhöz a `load()` metódust használjuk. Az első argumentum a kulcs, a második pedig a PHP callback, amelyet akkor hívunk meg, ha a kulcsot nem találjuk a gyorsítótárban. A callback generál egy értéket, visszaadja azt, és a gyorsítótárba helyezi: +Most már a `$cache` objektum segítségével olvashatunk a gyorsítótárból és írhatunk bele. Mindkettőre a `load()` metódus szolgál. Az első argumentum a kulcs, a második pedig egy PHP callback, amely akkor hívódik meg, ha a kulcs nem található a cache-ben. A callback generálja az értéket, visszaadja, és az elmentődik a cache-be: ```php $value = $cache->load($key, function () use ($key) { - $computedValue = /* ... */; // nehéz számítások + $computedValue = /* ... */; // költséges számítás return $computedValue; }); ``` -Ha a második paraméter nincs megadva: `$value = $cache->load($key)`, a `null` visszaadja, ha az elem nincs a gyorsítótárban. +Ha a második paramétert nem adjuk meg `$value = $cache->load($key)`, akkor `null`-t ad vissza, ha az elem nincs a cache-ben. .[tip] -A nagyszerű dolog az, hogy bármilyen szerializálható struktúrát lehet gyorsítótárba helyezni, nem csak stringeket. És ugyanez vonatkozik a kulcsokra is. +Nagyszerű, hogy a cache-be bármilyen szerializálható struktúrát tárolhatunk, nem csak stringeket. És ugyanez igaz még a kulcsokra is. -Az elemet a `remove()` metódus segítségével töröljük a gyorsítótárból: +Az elemet a gyorsítótárból a `remove()` metódussal töröljük: ```php $cache->remove($key); ``` -Egy elemet a `$cache->save($key, $value, array $dependencies = [])` módszerrel is gyorsítótárba helyezhet. A fenti, `load()` módszer azonban előnyösebb. +Elemet a gyorsítótárba a `$cache->save($key, $value, array $dependencies = [])` metódussal is menthetünk. Azonban a fentebb bemutatott `load()` használata preferált. -Memoization .[#toc-memoization] -=============================== +Memoizáció +========== -A memoizálás egy függvény vagy metódus eredményének gyorsítótárazását jelenti, hogy legközelebb is használhassa azt, ahelyett, hogy újra és újra kiszámolná ugyanazt a dolgot. +A memoizáció egy függvény vagy metódus hívásának eredményének gyorsítótárazását jelenti, hogy legközelebb újra felhasználhassuk anélkül, hogy újra kiszámítanánk ugyanazt. -A módszereket és függvényeket a `call(callable $callback, ...$args)` segítségével lehet memoizálni: +Metódusokat és függvényeket memoizáltan hívhatunk a `call(callable $callback, ...$args)` segítségével: ```php $result = $cache->call('gethostbyaddr', $ip); ``` -A `gethostbyaddr()` függvényt csak egyszer hívjuk meg a `$ip` minden egyes paraméterére, és a következő alkalommal a gyorsítótárból származó értéket kapjuk vissza. +A `gethostbyaddr()` függvény így minden `$ip` paraméterre csak egyszer hívódik meg, és legközelebb már a cache-ből adódik vissza az érték. -Lehetőség van arra is, hogy egy memoizált burkolatot hozzunk létre egy metódushoz vagy függvényhez, amelyet később hívhatunk meg: +Lehetőség van arra is, hogy egy memoizált burkolót hozzunk létre egy metódus vagy függvény köré, amelyet később hívhatunk meg: ```php function factorial($num) @@ -94,17 +94,17 @@ function factorial($num) $memoizedFactorial = $cache->wrap('factorial'); -$result = $memoizedFactorial(5); // megszámolja. -$result = $memoizedFactorial(5); // visszaadja a cache-ből +$result = $memoizedFactorial(5); // először kiszámítja +$result = $memoizedFactorial(5); // másodszor a cache-ből ``` -Lejárat és érvénytelenítés .[#toc-expiration-invalidation] -========================================================== +Lejárat & érvénytelenítés +========================= -A gyorsítótárazással foglalkozni kell azzal a kérdéssel, hogy a korábban elmentett adatok egy része idővel érvénytelenné válik. A Nette keretrendszer biztosít egy mechanizmust, amellyel az adatok érvényességét korlátozni lehet, és az adatok ellenőrzött módon törölhetők ("érvényteleníthetők", a keretrendszer terminológiáját használva). +A cache-be való mentéskor felmerül a kérdés, hogy a korábban elmentett adatok mikor válnak érvénytelenné. A Nette Framework egy mechanizmust kínál az adatok érvényességének korlátozására vagy azok irányított törlésére (a keretrendszer terminológiájában „érvénytelenítésére”). -Az adatok érvényességét a mentéskor a `save()` módszer harmadik paraméterével lehet beállítani, pl: +Az adatok érvényességét a mentéskor állítjuk be a `save()` metódus harmadik paraméterével, pl.: ```php $cache->save($key, $value, [ @@ -112,7 +112,7 @@ $cache->save($key, $value, [ ]); ``` -Vagy a `$dependencies` paraméter használatával, amelyet hivatkozással adunk át a `load()` metódus visszahívásának, pl: +Vagy a `load()` metódus callbackjének referenciaként átadott `$dependencies` paraméterével, pl.: ```php $value = $cache->load($key, function (&$dependencies) { @@ -121,7 +121,7 @@ $value = $cache->load($key, function (&$dependencies) { }); ``` -Vagy a `load()` módszer 3. paraméterének használatával, pl: +Vagy a `load()` metódus 3. paraméterével, pl.: ```php $value = $cache->load($key, function () { @@ -129,26 +129,26 @@ $value = $cache->load($key, function () { }, [Cache::Expire => '20 minutes']); ``` -A következő példákban a második változatot és így a `$dependencies` változó létezését feltételezzük. +A további példákban a második változatot feltételezzük, és így a `$dependencies` változó létezését. -Lejárat .[#toc-expiration] --------------------------- +Lejárat +------- -A legegyszerűbb lejárat az időkorlát. Íme, hogyan lehet 20 percig érvényes adatokat gyorsítótárba helyezni: +A legegyszerűbb lejárat az időkorlát. Így 20 perces érvényességgel mentünk adatokat a cache-be: ```php -// elfogadja a másodpercek számát vagy a UNIX időbélyeget is. +// elfogadja a másodpercek számát vagy UNIX timestamp-et is $dependencies[Cache::Expire] = '20 minutes'; ``` -Ha minden egyes olvasással meg akarjuk hosszabbítani az érvényességi időt, akkor ez így is megvalósítható, de vigyázat, ez növeli a cache overheadet: +Ha minden olvasással meg szeretnénk hosszabbítani az érvényességi időt, azt a következőképpen érhetjük el, de vigyázat, a cache rezsije ezzel megnő: ```php $dependencies[Cache::Sliding] = true; ``` -A praktikus lehetőség, hogy egy adott fájl vagy több fájl közül az egyik módosításakor az adatok érvényessége lejárjon. Ez például az ilyen fájlok feldolgozásából származó adatok gyorsítótárazására használható. Abszolút elérési utak használata. +Ügyes lehetőség, hogy az adatokat akkor járassuk le, amikor egy fájl vagy több fájl közül valamelyik megváltozik. Ezt például akkor használhatjuk, ha ezeknek a fájloknak a feldolgozásából származó adatokat mentjük a cache-be. Használjon abszolút elérési utakat. ```php $dependencies[Cache::Files] = '/path/to/data.yaml'; @@ -156,13 +156,13 @@ $dependencies[Cache::Files] = '/path/to/data.yaml'; $dependencies[Cache::Files] = ['/path/to/data1.yaml', '/path/to/data2.yaml']; ``` -A gyorsítótár egy elemét akkor hagyhatjuk lejárni, amikor egy másik elem (vagy több másik közül valamelyik) lejár. Ezt akkor használhatjuk, ha a teljes HTML-oldalt és annak más kulcsok alatt lévő töredékeit is gyorsítótárba helyezzük. Amint a részlet megváltozik, az egész oldal érvénytelenné válik. Ha olyan kulcsok alatt tárolt töredékeink vannak, mint a `frag1` és a `frag2`, akkor használjuk: +Lejárathatunk egy elemet a cache-ben akkor, amikor egy másik elem (vagy több másik közül valamelyik) lejár. Ezt akkor használhatjuk, ha például egy egész HTML oldalt mentünk a cache-be, és más kulcsok alatt annak töredékeit. Amint egy töredék megváltozik, az egész oldal érvénytelenné válik. Ha a töredékeket pl. `frag1` és `frag2` kulcsok alatt tároljuk, használjuk ezt: ```php $dependencies[Cache::Items] = ['frag1', 'frag2']; ``` -A lejáratot egyéni függvényekkel vagy statikus metódusokkal is szabályozhatjuk, amelyek mindig az olvasáskor döntenek arról, hogy az elem még érvényes-e még. Például hagyhatjuk, hogy az elem lejárjon, amikor a PHP verziója megváltozik. Létrehozunk egy függvényt, amely összehasonlítja az aktuális verziót a paraméterrel, és mentéskor hozzáadunk egy tömböt a következő formában `[function name, ...arguments]` a függőségekhez: +A lejáratot saját függvényekkel vagy statikus metódusokkal is vezérelhetjük, amelyek minden olvasáskor eldöntik, hogy az elem még érvényes-e. Így például lejárathatunk egy elemet mindig, amikor a PHP verziója megváltozik. Létrehozunk egy függvényt, amely összehasonlítja az aktuális verziót a paraméterrel, és a mentéskor hozzáadjuk a függőségek közé a `[függvény neve, ...argumentumok]` formátumú tömböt: ```php function checkPhpVersion($ver): bool @@ -171,11 +171,11 @@ function checkPhpVersion($ver): bool } $dependencies[Cache::Callbacks] = [ - ['checkPhpVersion', PHP_VERSION_ID] // lejár, ha checkPhpVersion(...) === false + ['checkPhpVersion', PHP_VERSION_ID] // járjon le, ha checkPhpVersion(...) === false ]; ``` -Természetesen minden kritérium kombinálható. A gyorsítótár akkor jár le, ha legalább egy kritérium nem teljesül. +Természetesen minden kritérium kombinálható. A cache akkor jár le, ha legalább egy kritérium nem teljesül. ```php $dependencies[Cache::Expire] = '20 minutes'; @@ -183,16 +183,16 @@ $dependencies[Cache::Files] = '/path/to/data.yaml'; ``` -Érvénytelenítés címkékkel .[#toc-invalidation-using-tags] ---------------------------------------------------------- +Érvénytelenítés tag-ekkel +------------------------- -A címkék nagyon hasznos érvénytelenítési eszköz. A cache-ben tárolt minden egyes elemhez hozzárendelhetünk egy listát címkékből, amelyek tetszőleges karakterláncok. Tegyük fel például, hogy van egy HTML-oldalunk egy cikkel és kommentekkel, amelyet gyorsítótárba szeretnénk helyezni. Tehát címkéket adunk meg a gyorsítótárba mentéskor: +Nagyon hasznos érvénytelenítő eszközök az úgynevezett tag-ek. Minden cache-beli elemhez hozzárendelhetünk egy tag-listát, amelyek tetszőleges stringek. Legyen például egy HTML oldalunk egy cikkel és hozzászólásokkal, amelyet gyorsítótárazni fogunk. Mentéskor megadjuk a tag-eket: ```php $dependencies[Cache::Tags] = ["article/$articleId", "comments/$articleId"]; ``` -Most pedig térjünk át az adminisztrációra. Itt van egy űrlapunk a cikkek szerkesztéséhez. A cikk adatbázisba mentésével együtt hívjuk meg a `clean()` parancsot, amely címkék szerint törli a gyorsítótárazott elemeket: +Lépjünk át az adminisztrációba. Itt találunk egy űrlapot a cikk szerkesztéséhez. A cikk adatbázisba mentésével együtt meghívjuk a `clean()` parancsot, amely törli a cache-ből az elemeket a tag alapján: ```php $cache->clean([ @@ -200,7 +200,7 @@ $cache->clean([ ]); ``` -Hasonlóképpen, egy új megjegyzés hozzáadása (vagy egy megjegyzés szerkesztése) helyén nem felejtjük el érvényteleníteni a vonatkozó taget: +Ugyanígy az új hozzászólás hozzáadásának (vagy egy hozzászólás szerkesztésének) helyén ne felejtsük el érvényteleníteni a megfelelő tag-et: ```php $cache->clean([ @@ -208,22 +208,22 @@ $cache->clean([ ]); ``` -Mit értünk el? Azt, hogy a HTML-cache-ünk érvénytelenítve (törölve) lesz, amikor a cikk vagy a hozzászólások változnak. Egy ID = 10 azonosítóval rendelkező cikk szerkesztésekor a `article/10` címke érvénytelenítése kikényszerül, és a címkét tartalmazó HTML-oldal törlődik a gyorsítótárból. Ugyanez történik akkor is, ha új megjegyzést illesztünk be az adott cikk alá. +Mit értünk el ezzel? Azt, hogy a HTML cache érvénytelenné válik (törlődik), amikor a cikk vagy a hozzászólások megváltoznak. Ha egy 10-es ID-jú cikket szerkesztünk, akkor kényszerített érvénytelenítés történik az `article/10` tag-re, és a HTML oldal, amely ezt a tag-et hordozza, törlődik a cache-ből. Ugyanez történik egy új hozzászólás beszúrásakor a megfelelő cikk alá. .[note] -A címkékhez [Journal |#Journal] szükséges. +A tag-ekhez úgynevezett [#Journal] szükséges. -Érvénytelenítés prioritás szerint .[#toc-invalidation-by-priority] ------------------------------------------------------------------- +Érvénytelenítés prioritással +---------------------------- -A gyorsítótárban lévő egyes elemek prioritását beállíthatjuk, és lehetőségünk lesz arra, hogy ellenőrzött módon töröljük őket, amikor például a gyorsítótár meghalad egy bizonyos méretet: +Az egyes cache-elemekhez beállíthatunk prioritást, amellyel törölhetjük őket, ha például a cache meghalad egy bizonyos méretet: ```php $dependencies[Cache::Priority] = 50; ``` -Töröljük az összes olyan elemet, amelynek prioritása 100 vagy annál kisebb: +Töröljük az összes elemet, amelyek prioritása 100 vagy annál kisebb: ```php $cache->clean([ @@ -232,11 +232,11 @@ $cache->clean([ ``` .[note] -A prioritásokhoz úgynevezett [Naplóra |#Journal] van szükség. +A prioritásokhoz úgynevezett [#Journal] szükséges. -Cache törlése .[#toc-clear-cache] ---------------------------------- +Cache törlése +------------- A `Cache::All` paraméter mindent töröl: @@ -247,51 +247,70 @@ $cache->clean([ ``` -Bulk Reading .[#toc-bulk-reading] -================================= +Tömeges olvasás +=============== -A gyorsítótárba történő tömeges olvasáshoz és íráshoz a `bulkLoad()` módszert használjuk, ahol átadunk egy kulcsokból álló tömböt, és megkapunk egy értékekből álló tömböt: +A cache-ből való tömeges olvasásra és írásra a `bulkLoad()` metódus szolgál, amelynek átadunk egy kulcstömböt, és egy értéktömböt kapunk vissza: ```php $values = $cache->bulkLoad($keys); ``` -A `bulkLoad()` módszer a `load()` módszerhez hasonlóan működik a második visszahívási paraméterrel, amelynek a generált elem kulcsát adjuk át: +A `bulkLoad()` metódus hasonlóan működik, mint a `load()`, a második paraméter callbackkel is, amelynek átadódik a generált elem kulcsa: ```php $values = $cache->bulkLoad($keys, function ($key, &$dependencies) { - $computedValue = /* ... */; // nehéz számítások + $computedValue = /* ... */; // költséges számítás return $computedValue; }); ``` -Kimeneti gyorsítótárazás .[#toc-output-caching] -=============================================== +Használat PSR-16-tal .{data-version:3.3.1} +========================================== + +A Nette Cache PSR-16 interfésszel való használatához használhatja a `PsrCacheAdapter` adaptert. Lehetővé teszi a zökkenőmentes integrációt a Nette Cache és bármely olyan kód vagy könyvtár között, amely PSR-16 kompatibilis cache-t vár. + +```php +$psrCache = new Nette\Bridges\Psr\PsrCacheAdapter($storage); +``` + +Most már használhatja a `$psrCache`-t PSR-16 cache-ként: + +```php +$psrCache->set('key', 'value', 3600); // 1 órára menti az értéket +$value = $psrCache->get('key', 'default'); +``` -A kimenet nagyon elegánsan rögzíthető és gyorsítótárazható: +Az adapter támogatja az összes PSR-16-ban definiált metódust, beleértve a `getMultiple()`, `setMultiple()` és `deleteMultiple()` metódusokat is. + + +Kimenet gyorsítótárazása +======================== + +Nagyon elegánsan lehet a kimenetet elfogni és gyorsítótárazni: ```php if ($capture = $cache->capture($key)) { - echo ... // néhány adat kiírása + echo ... // kiírjuk az adatokat - $capture->end(); // a kimenet elmentése a gyorsítótárba + $capture->end(); // elmentjük a kimenetet a cache-be } ``` -Abban az esetben, ha a kimenet már a gyorsítótárban van, a `capture()` metódus kiírja azt, és visszaadja a `null` címet, így a feltétel nem kerül végrehajtásra. Ellenkező esetben elkezdi pufferelni a kimenetet, és visszaadja a `$capture` objektumot, amelynek segítségével végül elmentjük az adatokat a gyorsítótárba. +Abban az esetben, ha a kimenet már a cache-ben van, a `capture()` metódus kiírja azt és `null`-t ad vissza, tehát a feltétel nem teljesül. Ellenkező esetben elkezdi a kimenet elfogását és visszaadja a `$capture` objektumot, amelynek segítségével végül elmentjük a kiírt adatokat a cache-be. .[note] A 3.0-s verzióban a metódus neve `$cache->start()` volt. -Tárolás a Latte-ban .[#toc-caching-in-latte] -============================================ +Gyorsítótárazás Latte-ban +========================= -A [Latte |latte:] sablonokban a gyorsítótárazás nagyon egyszerű, csak csomagoljuk be a sablon egy részét címkékkel. `{cache}...{/cache}`. A gyorsítótár automatikusan érvénytelenítésre kerül, amikor a forrássablon megváltozik (beleértve a `{cache}` címkékben szereplő sablonokat is). A `{cache}` címkék egymásba ágyazhatók, és amikor egy egymásba ágyazott blokk érvénytelenítésre kerül (például egy címkével), a szülő blokk is érvénytelenítésre kerül. +A sablonokban való gyorsítótárazás a [Latte|latte:]-ban nagyon egyszerű, csak a sablon egy részét kell `{cache}...{/cache}` tagekkel körbevenni. A cache automatikusan érvénytelenné válik, amikor a forrás sablon megváltozik (beleértve az esetlegesen beillesztett sablonokat a cache blokkon belül). A `{cache}` tagek egymásba ágyazhatók, és ha egy beágyazott blokk érvénytelenné válik (például egy tag miatt), akkor a fölérendelt blokk is érvénytelenné válik. -A címkében meg lehet adni azokat a kulcsokat, amelyekhez a gyorsítótárat kötni kell (itt a `$id` változót), és be lehet állítani a lejárati és [érvénytelenítési címkéket |#Invalidation using Tags]. +A tagben megadhatók kulcsok, amelyekhez a cache kötődni fog (itt a `$id` változó), és beállítható a lejárat és a [címkék az érvénytelenítéshez |#Érvénytelenítés tag-ekkel]. ```latte {cache $id, expire: '20 minutes', tags: [tag1, tag2]} @@ -299,9 +318,9 @@ A címkében meg lehet adni azokat a kulcsokat, amelyekhez a gyorsítótárat k {/cache} ``` -Minden paraméter opcionális, tehát nem kell megadni a lejáratot, a címkéket vagy a kulcsokat. +Minden elem opcionális, így nem kell megadnunk sem a lejáratot, sem a címkéket, végül még a kulcsokat sem. -A gyorsítótár használata a `if` segítségével feltételekhez is köthető - a tartalom ekkor csak akkor kerül gyorsítótárba, ha a feltétel teljesül: +A cache használata feltételhez is köthető az `if` segítségével - a tartalom csak akkor lesz gyorsítótárazva, ha a feltétel teljesül: ```latte {cache $id, if: !$form->isSubmitted()} @@ -310,23 +329,23 @@ A gyorsítótár használata a `if` segítségével feltételekhez is köthető ``` -Tárolók .[#toc-storages] -======================== +Tárolók +======= -A tároló egy olyan objektum, amely az adatok fizikai tárolási helyét jelöli. Használhatunk adatbázist, Memcached kiszolgálót, vagy a legelérhetőbb tárolókat, amelyek a lemezen lévő fájlok. +A tároló egy objektum, amely azt a helyet képviseli, ahol az adatok fizikailag tárolódnak. Használhatunk adatbázist, Memcached szervert, vagy a leginkább elérhető tárolót, ami a lemezen lévő fájlok. -|---------------------- -| Tárolás | Leírás -|---------------------- -| [FileStorage |#FileStorage] | alapértelmezett tároló a lemezen lévő fájlokba történő mentéssel -| [MemcachedStorage |#MemcachedStorage] | a `Memcached` szervert használja. -| [MemoryStorage |#MemoryStorage] | az adatok ideiglenesen a memóriában vannak. -| [SQLiteStorage |#SQLiteStorage] | az adatok SQLite adatbázisban tárolódnak -| [DevNullStorage |#DevNullStorage] | az adatok nincsenek tárolva - tesztelési célokra. +|----------------- +| Tároló | Leírás +|----------------- +| [#FileStorage] | alapértelmezett tároló, amely a lemezen lévő fájlokba ment +| [#MemcachedStorage] | `Memcached` szervert használ +| [#MemoryStorage] | az adatok ideiglenesen a memóriában vannak +| [#SQLiteStorage] | az adatok SQLite adatbázisba mentődnek +| [#DevNullStorage] | az adatok nem mentődnek, tesztelésre alkalmas -A tárolási objektumot a `Nette\Caching\Storage` típus [függőségi injektálással |dependency-injection:passing-dependencies] történő átadásával kapja meg. Alapértelmezés szerint a Nette egy FileStorage objektumot biztosít, amely az adatokat az [ideiglenes fájlok |application:bootstrap#Temporary Files] könyvtárának `cache` almappájában tárolja . +A tároló objektumhoz úgy juthat hozzá, hogy [dependency injection |dependency-injection:passing-dependencies] segítségével kéri át a `Nette\Caching\Storage` típussal. Alapértelmezett tárolóként a Nette a FileStorage objektumot biztosítja, amely az adatokat az [ideiglenes fájlok |application:bootstrapping#Ideiglenes fájlok] könyvtárában lévő `cache` alkönyvtárba menti. -A tárolót a konfigurációban megváltoztathatja: +A tárolót a konfigurációban módosíthatja: ```neon services: @@ -334,31 +353,28 @@ services: ``` -FileStorage .[#toc-filestorage] -------------------------------- +FileStorage +----------- -A gyorsítótárat a lemezen lévő fájlokra írja. A tároló `Nette\Caching\Storages\FileStorage` nagyon jól optimalizált a teljesítményre, és mindenekelőtt biztosítja a műveletek teljes atomicitását. Mit jelent ez? Azt, hogy a gyorsítótár használatakor nem fordulhat elő, hogy olyan fájlt olvassunk, amelyet egy másik szál még nem írt ki teljesen, vagy hogy valaki "kezünk alatt" törölné azt. A gyorsítótár használata tehát teljesen biztonságos. +A cache-t fájlokba írja a lemezen. A `Nette\Caching\Storages\FileStorage` tároló nagyon jól optimalizált a teljesítményre, és mindenekelőtt biztosítja a műveletek teljes atomicitását. Mit jelent ez? Azt, hogy a cache használatakor nem fordulhat elő, hogy olyan fájlt olvassunk be, amelyet egy másik szál még nem írt ki teljesen, vagy hogy valaki "a kezünk alól" törölje azt. A cache használata tehát teljesen biztonságos. -Ez a tároló rendelkezik egy fontos beépített funkcióval is, amely megakadályozza a CPU-használat extrém mértékű növekedését, amikor a gyorsítótár törlődik vagy hideg (azaz nem jön létre). Ez a "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede megelőzése. -Előfordul, hogy egy pillanatban több egyidejű kérés is van, amelyek ugyanazt a dolgot akarják a gyorsítótárból (pl. egy drága SQL-lekérdezés eredményét), és mivel az nincs gyorsítótárban, minden folyamat ugyanazt az SQL-lekérdezést kezdi végrehajtani. -A processzorterhelés megsokszorozódik, és még az is előfordulhat, hogy egyetlen szál sem tud válaszolni az időkorláton belül, a gyorsítótár nem jön létre, és az alkalmazás összeomlik. -Szerencsére a Nette-ben a gyorsítótár úgy működik, hogy ha egy elemre több egyidejű kérés érkezik, akkor azt csak az első szál generálja, a többiek várnak, majd használják a generált eredményt. +Ez a tároló egy fontos beépített funkcióval is rendelkezik, amely megakadályozza a CPU extrém kihasználtságának növekedését abban a pillanatban, amikor a cache törlődik vagy még nincs felmelegítve (azaz létrehozva). Ez a "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede elleni védelem. Előfordul, hogy egy időben több párhuzamos kérés érkezik, amelyek ugyanazt a dolgot akarják a cache-ből (pl. egy drága SQL lekérdezés eredményét), és mivel az nincs a gyorsítótárban, minden folyamat ugyanazt az SQL lekérdezést kezdi el végrehajtani. A terhelés így megsokszorozódik, és akár az is előfordulhat, hogy egyetlen szál sem tud válaszolni az időkorláton belül, a cache nem jön létre, és az alkalmazás összeomlik. Szerencsére a Nette cache úgy működik, hogy több párhuzamos kérés esetén egy elemre csak az első szál generálja azt, a többiek várnak, majd felhasználják a generált eredményt. -Példa egy FileStorage létrehozására: +Példa a FileStorage létrehozására: ```php -// a tároló a lemezen lévő '/path/to/temp' könyvtár lesz. +// a tároló a '/path/to/temp' könyvtár lesz a lemezen $storage = new Nette\Caching\Storages\FileStorage('/path/to/temp'); ``` -MemcachedStorage .[#toc-memcachedstorage] ------------------------------------------ +MemcachedStorage +---------------- -A [Memcached |https://memcached.org] kiszolgáló egy nagy teljesítményű elosztott tárolórendszer, amelynek adaptere a `Nette\Caching\Storages\MemcachedStorage`. A konfigurációban adja meg az IP-címet és a portot, ha az eltér a szabványos 11211-től. +A [Memcached|https://memcached.org] szerver egy nagy teljesítményű, elosztott memóriában történő tárolási rendszer, amelynek adaptere a `Nette\Caching\Storages\MemcachedStorage`. A konfigurációban megadjuk az IP-címet és a portot, ha az eltér a standard 11211-től. .[caution] -PHP-bővítményt igényel `memcached`. +Szükséges a `memcached` PHP kiterjesztés. ```neon services: @@ -366,19 +382,19 @@ services: ``` -MemoryStorage .[#toc-memorystorage] ------------------------------------ +MemoryStorage +------------- -`Nette\Caching\Storages\MemoryStorage` egy olyan tároló, amely az adatokat egy PHP tömbben tárolja, és így a kérés befejezésekor elveszik. +A `Nette\Caching\Storages\MemoryStorage` egy olyan tároló, amely az adatokat egy PHP tömbben tárolja, és így a kérés befejeztével elvesznek. -SQLiteStorage .[#toc-sqlitestorage] ------------------------------------ +SQLiteStorage +------------- -Az SQLite adatbázis és az adapter `Nette\Caching\Storages\SQLiteStorage` lehetőséget kínál a lemezen lévő egyetlen fájlban történő gyorsítótárazásra. A konfiguráció megadja ennek a fájlnak az elérési útvonalát. +Az SQLite adatbázis és az `Nette\Caching\Storages\SQLiteStorage` adapter lehetőséget kínál a cache egyetlen fájlba történő mentésére a lemezen. A konfigurációban megadjuk ennek a fájlnak az elérési útját. .[caution] -Szükséges a `pdo` és a `pdo_sqlite` PHP-bővítményeket. +Szükséges a `pdo` és `pdo_sqlite` PHP kiterjesztés. ```neon services: @@ -386,16 +402,16 @@ services: ``` -DevNullStorage .[#toc-devnullstorage] -------------------------------------- +DevNullStorage +-------------- -A tárolás egy speciális megvalósítása a `Nette\Caching\Storages\DevNullStorage`, amely valójában egyáltalán nem tárol adatokat. Ezért alkalmas tesztelésre, ha ki akarjuk küszöbölni a gyorsítótár hatását. +A tároló speciális implementációja a `Nette\Caching\Storages\DevNullStorage`, amely valójában egyáltalán nem tárol adatokat. Így tesztelésre alkalmas, amikor ki akarjuk küszöbölni a cache hatását. -A gyorsítótár használata a kódban .[#toc-using-cache-in-code] -============================================================= +Cache használata a kódban +========================= -A gyorsítótárazást a kódban kétféleképpen használhatjuk. Az első az, hogy a tárolási objektumot [függőségi injektálással |dependency-injection:passing-dependencies] átadva megkapja, majd létrehoz egy objektumot `Cache`: +A cache kódban való használatakor kétféleképpen járhatunk el. Az első az, hogy [dependency injection |dependency-injection:passing-dependencies] segítségével átkérjük a tárolót, és létrehozunk egy `Cache` objektumot: ```php use Nette; @@ -411,7 +427,7 @@ class ClassOne } ``` -A második mód az, hogy megkapja a tárolási objektumot `Cache`: +A második lehetőség az, hogy közvetlenül a `Cache` objektumot kérjük át: ```php class ClassTwo @@ -423,7 +439,7 @@ class ClassTwo } ``` -A `Cache` objektum ezután közvetlenül a konfigurációban jön létre a következőképpen: +A `Cache` objektumot ezután közvetlenül a konfigurációban hozzuk létre ezzel a módszerrel: ```neon services: @@ -431,12 +447,12 @@ services: ``` -Folyóirat .[#toc-journal] -========================= +Journal +======= -A Nette a címkéket és prioritásokat egy úgynevezett naplóban tárolja. Ehhez alapértelmezés szerint az SQLite és a `journal.s3db` fájlt használja, és **a `pdo` és a `pdo_sqlite` kiterjesztések szükségesek.** +A Nette a címkéket és prioritásokat az úgynevezett journalba menti. Alapértelmezés szerint ehhez SQLite-ot és a `journal.s3db` fájlt használja, és **szükséges a `pdo` és `pdo_sqlite` PHP kiterjesztés.** -A naplót a konfigurációban megváltoztathatja: +A journalt a konfigurációban módosíthatja: ```neon services: @@ -444,4 +460,25 @@ services: ``` -{{leftbar: nette:@menu-topics}} +DI szolgáltatások +================= + +Ezek a szolgáltatások kerülnek hozzáadásra a DI konténerhez: + +| Név | Típus | Leírás +|---------------------------------------------------------- +| `cache.journal` | [api:Nette\Caching\Storages\Journal] | journal +| `cache.storage` | [api:Nette\Caching\Storage] | tároló + + +Cache kikapcsolása +================== + +Az alkalmazásban a cache kikapcsolásának egyik módja, ha a [#DevNullStorage]-t állítjuk be tárolóként: + +```neon +services: + cache.storage: Nette\Caching\Storages\DevNullStorage +``` + +Ez a beállítás nincs hatással a sablonok gyorsítótárazására a Latte-ban vagy a DI konténerben, mivel ezek a könyvtárak nem használják a nette/caching szolgáltatásait, és önállóan kezelik a cache-t. Egyébként a cache-üket [nem szükséges kikapcsolni |nette:troubleshooting#Hogyan kapcsoljuk ki a cache-t fejlesztés közben] fejlesztői módban. diff --git a/caching/hu/@meta.texy b/caching/hu/@meta.texy new file mode 100644 index 0000000000..c00a2158aa --- /dev/null +++ b/caching/hu/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette dokumentáció}} +{{leftbar: nette:@menu-topics}} diff --git a/caching/it/@home.texy b/caching/it/@home.texy index ede18085f8..3b9e0dba0b 100644 --- a/caching/it/@home.texy +++ b/caching/it/@home.texy @@ -1,90 +1,90 @@ -Caching -******* +Nette Caching +************* <div class=perex> -La cache accelera l'applicazione memorizzando i dati, una volta recuperati, per un uso futuro. Vi mostreremo: +La cache accelera la vostra applicazione salvando i dati ottenuti con fatica una volta per un uso futuro. Vedremo: -- Come utilizzare la cache -- Come modificare la memorizzazione nella cache -- Come invalidare correttamente la cache +- come usare la cache +- come cambiare lo storage +- come invalidare correttamente la cache </div> -L'uso della cache è molto semplice in Nette, ma copre anche esigenze di cache molto avanzate. È stato progettato per garantire prestazioni e durata al 100%. Di base, si trovano adattatori per i più comuni storage di backend. Consente l'invalidazione basata sui tag, la protezione della cache, la scadenza temporale, ecc. +L'uso della cache in Nette è molto semplice, ma copre anche esigenze molto avanzate. È progettato per le prestazioni e una resistenza del 100%. Di base, si trovano adattatori per gli storage di backend più comuni. Supporta l'invalidazione basata su tag, la scadenza temporale, ha protezione contro il cache stampede, ecc. -Installazione .[#toc-installation] -================================== +Installazione +============= -Scaricare e installare il pacchetto utilizzando [Composer |best-practices:composer]: +Potete scaricare e installare la libreria utilizzando lo strumento [Composer|best-practices:composer]: ```shell composer require nette/caching ``` -Uso di base .[#toc-basic-usage] -=============================== +Utilizzo di base +================ -Il centro del lavoro con la cache è l'oggetto [api:Nette\Caching\Cache]. Si crea la sua istanza e si passa al costruttore come parametro il cosiddetto storage. Si tratta di un oggetto che rappresenta il luogo in cui i dati saranno fisicamente memorizzati (database, Memcached, file su disco, ...). Si ottiene l'oggetto storage passandoglielo tramite [dependency injection |dependency-injection:passing-dependencies] con il tipo `Nette\Caching\Storage`. Si troveranno tutti gli elementi essenziali nella [sezione Storage |#Storages]. +Il fulcro del lavoro con la cache è l'oggetto [api:Nette\Caching\Cache]. Creiamo la sua istanza e passiamo al costruttore il cosiddetto storage come parametro. Questo è un oggetto che rappresenta il luogo in cui i dati verranno fisicamente salvati (database, Memcached, file su disco, ...). Possiamo accedere allo storage facendocelo passare tramite [dependency injection |dependency-injection:passing-dependencies] con il tipo `Nette\Caching\Storage`. Troverete tutto l'essenziale nella [sezione Storage |#Storage]. .[warning] -Nella versione 3.0, l'interfaccia aveva ancora il tipo `I` prefix, so the name was `Nette\Caching\IStorage`. Inoltre, le costanti della classe `Cache` erano maiuscole, quindi ad esempio `Cache::EXPIRE` invece di `Cache::Expire`. +Nella versione 3.0, l'interfaccia aveva ancora il prefisso `I`, quindi il nome era `Nette\Caching\IStorage`. Inoltre, le costanti della classe `Cache` erano scritte in maiuscolo, quindi ad esempio `Cache::EXPIRE` invece di `Cache::Expire`. -Per gli esempi seguenti, supponiamo di avere un alias `Cache` e una memoria nella variabile `$storage`. +Per gli esempi seguenti, supponiamo di avere creato un alias `Cache` e di avere lo storage nella variabile `$storage`. ```php use Nette\Caching\Cache; -$storage = /* ... */; // istanza di Nette\Caching\Storage +$storage = /* ... */; // instance of Nette\Caching\Storage ``` -La cache è in realtà un *magazzino chiave-valore*, quindi leggiamo e scriviamo i dati sotto le chiavi proprio come gli array associativi. Le applicazioni sono costituite da un certo numero di parti indipendenti e se tutte utilizzassero un unico archivio (ad esempio una directory su disco), prima o poi si verificherebbe una collisione di chiavi. Il framework Nette risolve il problema dividendo l'intero spazio in spazi dei nomi (sottodirectory). Ogni parte del programma utilizza quindi il proprio spazio con un nome unico e non si verificano collisioni. +La cache è essenzialmente un *key-value store*, quindi leggiamo e scriviamo dati sotto chiavi proprio come con gli array associativi. Le applicazioni sono composte da una serie di parti indipendenti e se tutte utilizzassero un unico storage (immaginate una singola directory su disco), prima o poi si verificherebbe una collisione di chiavi. Nette Framework risolve il problema dividendo l'intero spazio in namespace (sottodirectory). Ogni parte del programma utilizza quindi il proprio spazio con un nome univoco e non può più verificarsi alcuna collisione. -Il nome dello spazio viene specificato come secondo parametro del costruttore della classe Cache: +Il nome dello spazio è specificato come secondo parametro del costruttore della classe Cache: ```php $cache = new Cache($storage, 'Full Html Pages'); ``` -Ora possiamo usare l'oggetto `$cache` per leggere e scrivere dalla cache. Il metodo `load()` viene utilizzato per entrambi. Il primo parametro è la chiave e il secondo è il callback di PHP, che viene chiamato quando la chiave non viene trovata nella cache. La callback genera un valore, lo restituisce e lo mette in cache: +Ora possiamo usare l'oggetto `$cache` per leggere e scrivere dalla cache. A tal fine serve il metodo `load()`. Il primo argomento è la chiave e il secondo è un callback PHP che viene chiamato quando la chiave non viene trovata nella cache. Il callback genera il valore, lo restituisce e viene salvato nella cache: ```php $value = $cache->load($key, function () use ($key) { - $computedValue = /* ... */; // calcoli pesanti + $computedValue = /* ... */; // calcolo complesso return $computedValue; }); ``` -Se il secondo parametro non è specificato `$value = $cache->load($key)`, viene restituito `null` se l'elemento non è presente nella cache. +Se il secondo parametro non viene specificato `$value = $cache->load($key)`, verrà restituito `null` se l'elemento non è nella cache. .[tip] -Il bello è che qualsiasi struttura serializzabile può essere messa in cache, non solo le stringhe. Lo stesso vale per le chiavi. +Fantastico è che nella cache è possibile salvare qualsiasi struttura serializzabile, non devono essere solo stringhe. E lo stesso vale anche per le chiavi. -L'elemento viene cancellato dalla cache con il metodo `remove()`: +L'elemento dalla cache viene cancellato con il metodo `remove()`: ```php $cache->remove($key); ``` -È possibile memorizzare nella cache un elemento anche con il metodo `$cache->save($key, $value, array $dependencies = [])`. Tuttavia, è preferibile utilizzare il metodo `load()`. +È anche possibile salvare un elemento nella cache con il metodo `$cache->save($key, $value, array $dependencies = [])`. Tuttavia, è preferibile il metodo sopra menzionato che utilizza `load()`. -Memorizzazione .[#toc-memoization] -================================== +Memoizzazione +============= -Memorizzazione significa memorizzare il risultato di una funzione o di un metodo, in modo da poterlo utilizzare la volta successiva invece di calcolare sempre la stessa cosa. +La memoizzazione significa memorizzare nella cache il risultato di una chiamata a una funzione o a un metodo, in modo da poterlo utilizzare la prossima volta senza calcolare nuovamente la stessa cosa. -I metodi e le funzioni possono essere chiamati memoizzati usando `call(callable $callback, ...$args)`: +È possibile chiamare metodi e funzioni in modo memoizzato usando `call(callable $callback, ...$args)`: ```php $result = $cache->call('gethostbyaddr', $ip); ``` -La funzione `gethostbyaddr()` viene chiamata una sola volta per ogni parametro `$ip` e la volta successiva verrà restituito il valore dalla cache. +La funzione `gethostbyaddr()` viene chiamata solo una volta per ogni parametro `$ip` e la prossima volta verrà restituito il valore dalla cache. -È anche possibile creare un wrapper memoizzato per un metodo o una funzione che può essere richiamato successivamente: +È anche possibile creare un wrapper memoizzato su un metodo o una funzione che può essere chiamato in seguito: ```php function factorial($num) @@ -94,17 +94,17 @@ function factorial($num) $memoizedFactorial = $cache->wrap('factorial'); -$result = $memoizedFactorial(5); // lo conta -$result = $memoizedFactorial(5); // lo restituisce dalla cache +$result = $memoizedFactorial(5); // calcola la prima volta +$result = $memoizedFactorial(5); // la seconda volta dalla cache ``` -Scadenza e invalidazione .[#toc-expiration-invalidation] -======================================================== +Scadenza & Invalidazione +======================== -Con la cache, è necessario affrontare il problema che alcuni dei dati precedentemente salvati diventino non validi nel tempo. Nette Framework fornisce un meccanismo per limitare la validità dei dati e per cancellarli in modo controllato ("invalidarli", secondo la terminologia del framework). +Con il salvataggio nella cache, è necessario risolvere la questione di quando i dati precedentemente salvati diventano non validi. Nette Framework offre un meccanismo per limitare la validità dei dati o cancellarli in modo controllato (nella terminologia del framework "invalidare"). -La validità dei dati viene impostata al momento del salvataggio utilizzando il terzo parametro del metodo `save()`, ad esempio: +La validità dei dati viene impostata al momento del salvataggio tramite il terzo parametro del metodo `save()`, ad esempio: ```php $cache->save($key, $value, [ @@ -112,7 +112,7 @@ $cache->save($key, $value, [ ]); ``` -Oppure utilizzando il parametro `$dependencies` passato come riferimento al callback del metodo `load()`, ad es: +Oppure tramite il parametro `$dependencies` passato per riferimento al callback del metodo `load()`, ad esempio: ```php $value = $cache->load($key, function (&$dependencies) { @@ -121,7 +121,7 @@ $value = $cache->load($key, function (&$dependencies) { }); ``` -Oppure utilizzando il terzo parametro del metodo `load()`, ad es: +Oppure tramite il 3° parametro nel metodo `load()`, ad esempio: ```php $value = $cache->load($key, function () { @@ -129,26 +129,26 @@ $value = $cache->load($key, function () { }, [Cache::Expire => '20 minutes']); ``` -Negli esempi seguenti, assumeremo la seconda variante e quindi l'esistenza di una variabile `$dependencies`. +Negli esempi successivi, assumeremo la seconda variante e quindi l'esistenza della variabile `$dependencies`. -Scadenza .[#toc-expiration] ---------------------------- +Scadenza +-------- -La scadenza più semplice è quella temporale. Ecco come memorizzare nella cache dati validi per 20 minuti: +La scadenza più semplice è un limite di tempo. In questo modo salviamo nella cache i dati con validità di 20 minuti: ```php // accetta anche il numero di secondi o il timestamp UNIX $dependencies[Cache::Expire] = '20 minutes'; ``` -Se si vuole estendere il periodo di validità a ogni lettura, è possibile farlo in questo modo, ma attenzione, questo aumenterà l'overhead della cache: +Se volessimo estendere il periodo di validità ad ogni lettura, è possibile farlo nel modo seguente, ma attenzione, il sovraccarico della cache aumenterà: ```php $dependencies[Cache::Sliding] = true; ``` -L'opzione più comoda è la possibilità di far scadere i dati quando un particolare file viene modificato o uno di più file. Questo può essere utilizzato, ad esempio, per la memorizzazione nella cache dei dati risultanti dalla processione di questi file. Utilizzare percorsi assoluti. +È utile la possibilità di far scadere i dati nel momento in cui cambia un file o uno dei più file. Questo può essere utilizzato, ad esempio, per salvare nella cache i dati derivanti dall'elaborazione di questi file. Utilizzare percorsi assoluti. ```php $dependencies[Cache::Files] = '/path/to/data.yaml'; @@ -156,13 +156,13 @@ $dependencies[Cache::Files] = '/path/to/data.yaml'; $dependencies[Cache::Files] = ['/path/to/data1.yaml', '/path/to/data2.yaml']; ``` -È possibile far scadere un elemento della cache quando un altro elemento (o uno di diversi altri) scade. Questo può essere usato quando si mette in cache l'intera pagina HTML e frammenti di essa sotto altre chiavi. Quando lo snippet cambia, l'intera pagina diventa non valida. Se abbiamo frammenti memorizzati sotto chiavi come `frag1` e `frag2`, useremo: +Possiamo far scadere un elemento nella cache nel momento in cui scade un altro elemento (o uno dei più altri). Questo può essere utilizzato quando salviamo nella cache, ad esempio, un'intera pagina HTML e, sotto altre chiavi, i suoi frammenti. Non appena un frammento cambia, l'intera pagina viene invalidata. Se abbiamo i frammenti salvati sotto le chiavi, ad esempio `frag1` e `frag2`, useremo: ```php $dependencies[Cache::Items] = ['frag1', 'frag2']; ``` -La scadenza può anche essere controllata usando funzioni personalizzate o metodi statici, che decidono sempre al momento della lettura se l'elemento è ancora valido. Per esempio, possiamo far scadere l'elemento ogni volta che la versione di PHP cambia. Creeremo una funzione che confronta la versione corrente con il parametro e al momento del salvataggio aggiungeremo un array nella forma `[function name, ...arguments]` alle dipendenze: +La scadenza può essere controllata anche tramite funzioni personalizzate o metodi statici, che decidono sempre alla lettura se l'elemento è ancora valido. In questo modo, ad esempio, possiamo far scadere l'elemento ogni volta che cambia la versione di PHP. Creiamo una funzione che confronta la versione attuale con il parametro e, durante il salvataggio, aggiungiamo tra le dipendenze un array nella forma `[nome funzione, ...argomenti]`: ```php function checkPhpVersion($ver): bool @@ -171,11 +171,11 @@ function checkPhpVersion($ver): bool } $dependencies[Cache::Callbacks] = [ - ['checkPhpVersion', PHP_VERSION_ID] // scadono quando checkPhpVersion(...) === false + ['checkPhpVersion', PHP_VERSION_ID] // scade quando checkPhpVersion(...) === false ]; ``` -Naturalmente, tutti i criteri possono essere combinati. La cache scade quando almeno un criterio non è soddisfatto. +Ovviamente è possibile combinare tutti i criteri. La cache scadrà quindi quando almeno un criterio non è soddisfatto. ```php $dependencies[Cache::Expire] = '20 minutes'; @@ -183,16 +183,16 @@ $dependencies[Cache::Files] = '/path/to/data.yaml'; ``` -Invalidazione tramite tag .[#toc-invalidation-using-tags] ---------------------------------------------------------- +Invalidazione tramite tag +------------------------- -I tag sono uno strumento di invalidazione molto utile. Si può assegnare un elenco di tag, che sono stringhe arbitrarie, a ogni elemento memorizzato nella cache. Per esempio, supponiamo di avere una pagina HTML con un articolo e dei commenti, che vogliamo mettere in cache. Quindi specifichiamo i tag quando salviamo nella cache: +Uno strumento di invalidazione molto utile sono i cosiddetti tag. A ogni elemento nella cache possiamo assegnare un elenco di tag, che sono stringhe arbitrarie. Supponiamo di avere una pagina HTML con un articolo e commenti, che memorizzeremo nella cache. Durante il salvataggio, specifichiamo i tag: ```php $dependencies[Cache::Tags] = ["article/$articleId", "comments/$articleId"]; ``` -Passiamo ora all'amministrazione. Qui abbiamo un modulo per la modifica degli articoli. Insieme al salvataggio dell'articolo nel database, chiamiamo il comando `clean()`, che cancellerà gli articoli in cache per tag: +Spostiamoci nell'amministrazione. Qui troviamo un form per modificare l'articolo. Insieme al salvataggio dell'articolo nel database, chiamiamo il comando `clean()`, che elimina gli elementi dalla cache in base al tag: ```php $cache->clean([ @@ -200,7 +200,7 @@ $cache->clean([ ]); ``` -Allo stesso modo, quando aggiungiamo un nuovo commento (o modifichiamo un commento), non dimentichiamo di invalidare il relativo tag: +Allo stesso modo, nel punto di aggiunta di un nuovo commento (o modifica di un commento), non dimentichiamo di invalidare il tag corrispondente: ```php $cache->clean([ @@ -208,22 +208,22 @@ $cache->clean([ ]); ``` -Cosa abbiamo ottenuto? Che la nostra cache HTML sarà invalidata (cancellata) ogni volta che l'articolo o i commenti cambiano. Quando si modifica un articolo con ID = 10, il tag `article/10` viene forzatamente invalidato e la pagina HTML che contiene il tag viene cancellata dalla cache. Lo stesso accade quando si inserisce un nuovo commento nell'articolo in questione. +Cosa abbiamo ottenuto con questo? Che la nostra cache HTML verrà invalidata (cancellata) ogni volta che l'articolo o i commenti cambiano. Quando viene modificato l'articolo con ID = 10, viene forzata l'invalidazione del tag `article/10` e la pagina HTML che porta il tag specificato viene eliminata dalla cache. Lo stesso accade quando viene inserito un nuovo commento sotto l'articolo corrispondente. .[note] -I tag richiedono [Journal |#Journal]. +I tag richiedono il cosiddetto [#Journal]. -Invalidazione per priorità .[#toc-invalidation-by-priority] ------------------------------------------------------------ +Invalidazione tramite priorità +------------------------------ -Possiamo impostare la priorità dei singoli elementi della cache e sarà possibile eliminarli in modo controllato quando, ad esempio, la cache supera una certa dimensione: +Ai singoli elementi nella cache possiamo impostare una priorità, tramite la quale sarà possibile eliminarli, ad esempio, quando la cache supera una certa dimensione: ```php $dependencies[Cache::Priority] = 50; ``` -Elimina tutti gli elementi con una priorità uguale o inferiore a 100: +Eliminiamo tutti gli elementi con priorità pari o inferiore a 100: ```php $cache->clean([ @@ -232,11 +232,11 @@ $cache->clean([ ``` .[note] -Le priorità richiedono il cosiddetto [Journal |#Journal]. +Le priorità richiedono il cosiddetto [#Journal]. -Cancellare la cache .[#toc-clear-cache] ---------------------------------------- +Cancellazione della cache +------------------------- Il parametro `Cache::All` cancella tutto: @@ -247,51 +247,70 @@ $cache->clean([ ``` -Lettura massiva .[#toc-bulk-reading] -==================================== +Lettura di massa +================ -Per la lettura e la scrittura in massa nella cache, si utilizza il metodo `bulkLoad()`, in cui si passa un array di chiavi e si ottiene un array di valori: +Per la lettura e la scrittura di massa nella cache serve il metodo `bulkLoad()`, al quale passiamo un array di chiavi e otteniamo un array di valori: ```php $values = $cache->bulkLoad($keys); ``` -Il metodo `bulkLoad()` funziona in modo simile a `load()` con il secondo parametro di callback, al quale viene passata la chiave dell'elemento generato: +Il metodo `bulkLoad()` funziona in modo simile a `load()` anche con il secondo parametro callback, al quale viene passata la chiave dell'elemento generato: ```php $values = $cache->bulkLoad($keys, function ($key, &$dependencies) { - $computedValue = /* ... */; // calcoli pesanti + $computedValue = /* ... */; // calcolo complesso return $computedValue; }); ``` -Caching dell'output .[#toc-output-caching] -========================================== +Utilizzo con PSR-16 .{data-version:3.3.1} +========================================= + +Per utilizzare Nette Cache con l'interfaccia PSR-16, è possibile utilizzare l'adattatore `PsrCacheAdapter`. Consente un'integrazione senza soluzione di continuità tra Nette Cache e qualsiasi codice o libreria che si aspetta una cache compatibile con PSR-16. + +```php +$psrCache = new Nette\Bridges\Psr\PsrCacheAdapter($storage); +``` + +Ora è possibile usare `$psrCache` come cache PSR-16: + +```php +$psrCache->set('key', 'value', 3600); // salva il valore per 1 ora +$value = $psrCache->get('key', 'default'); +``` + +L'adattatore supporta tutti i metodi definiti in PSR-16, inclusi `getMultiple()`, `setMultiple()` e `deleteMultiple()`. -L'output può essere catturato e messo in cache in modo molto elegante: + +Caching dell'output +=================== + +È possibile catturare e memorizzare nella cache l'output in modo molto elegante: ```php if ($capture = $cache->capture($key)) { - echo ... // stampa alcuni dati + echo ... // stampiamo i dati - $capture->end(); // salva l'output nella cache + $capture->end(); // salviamo l'output nella cache } ``` -Nel caso in cui l'output sia già presente nella cache, il metodo `capture()` lo stampa e restituisce `null`, quindi la condizione non verrà eseguita. Altrimenti, inizia a bufferizzare l'output e restituisce l'oggetto `$capture`, con il quale si salvano finalmente i dati nella cache. +Nel caso in cui l'output sia già memorizzato nella cache, il metodo `capture()` lo stamperà e restituirà `null`, quindi la condizione non verrà eseguita. In caso contrario, inizierà a catturare l'output e restituirà l'oggetto `$capture`, tramite il quale infine salveremo i dati stampati nella cache. .[note] -Nella versione 3.0 il metodo si chiamava `$cache->start()`. +Nella versione 3.0, il metodo si chiamava `$cache->start()`. -La cache in Latte .[#toc-caching-in-latte] -========================================== +Caching in Latte +================ -La cache nei template di [Latte |latte:] è molto semplice, basta avvolgere parte del template con i tag `{cache}...{/cache}`. La cache viene automaticamente invalidata quando il modello sorgente cambia (compresi i modelli inclusi nei tag `{cache}` ). I tag `{cache}` possono essere annidati e quando un blocco annidato viene invalidato (per esempio da un tag), anche il blocco padre viene invalidato. +Il caching nei template [Latte|latte:] è molto semplice, basta racchiudere una parte del template con i tag `{cache}...{/cache}`. La cache viene invalidata automaticamente nel momento in cui cambia il template sorgente (inclusi eventuali template inclusi all'interno del blocco cache). I tag `{cache}` possono essere annidati e quando un blocco annidato viene invalidato (ad esempio tramite un tag), viene invalidato anche il blocco genitore. -Nel tag è possibile specificare le chiavi a cui sarà legata la cache (in questo caso la variabile `$id`) e impostare i [tag |#Invalidation using Tags] di scadenza e [invalidazione |#Invalidation using Tags] +Nel tag è possibile specificare le chiavi a cui la cache sarà legata (qui la variabile `$id`) e impostare la scadenza e i [tag per l'invalidazione |#Invalidazione tramite tag]. ```latte {cache $id, expire: '20 minutes', tags: [tag1, tag2]} @@ -299,9 +318,9 @@ Nel tag è possibile specificare le chiavi a cui sarà legata la cache (in quest {/cache} ``` -Tutti i parametri sono opzionali, quindi non è necessario specificare scadenza, tag o chiavi. +Tutti gli elementi sono opzionali, quindi non dobbiamo specificare né la scadenza, né i tag, e infine nemmeno le chiavi. -L'uso della cache può anche essere condizionato da `if` - il contenuto sarà messo in cache solo se la condizione è soddisfatta: +L'uso della cache può anche essere condizionato tramite `if` - il contenuto verrà quindi memorizzato nella cache solo se la condizione è soddisfatta: ```latte {cache $id, if: !$form->isSubmitted()} @@ -310,21 +329,21 @@ L'uso della cache può anche essere condizionato da `if` - il contenuto sarà me ``` -Archivi .[#toc-storages] -======================== +Storage +======= -Uno storage è un oggetto che rappresenta il luogo in cui i dati vengono fisicamente memorizzati. Si può usare un database, un server Memcached o lo storage più disponibile, ovvero i file su disco. +Lo storage è un oggetto che rappresenta il luogo in cui i dati vengono fisicamente salvati. Possiamo usare un database, un server Memcached, o lo storage più accessibile, che sono i file su disco. -|---------------------- -| Memorizzazione | Descrizione -|---------------------- -| [FileStorage |#FileStorage] | archiviazione predefinita con salvataggio su file su disco -| [MemcachedStorage |#MemcachedStorage] | utilizza il server `Memcached` -| [MemoryStorage |#MemoryStorage] | i dati sono temporaneamente in memoria -| [SQLiteStorage |#SQLiteStorage] | i dati sono memorizzati nel database SQLite -| [DevNullStorage |#DevNullStorage] | i dati non sono memorizzati - a scopo di test +|----------------- +| Storage | Descrizione +|----------------- +| [#FileStorage] | storage predefinito con salvataggio in file su disco +| [#MemcachedStorage] | utilizza il server `Memcached` +| [#MemoryStorage] | i dati sono temporaneamente in memoria +| [#SQLiteStorage] | i dati vengono salvati in un database SQLite +| [#DevNullStorage] | i dati non vengono salvati, adatto per i test -Si ottiene l'oggetto storage passandoglielo tramite [dependency injection |dependency-injection:passing-dependencies] con il tipo `Nette\Caching\Storage`. Per impostazione predefinita, Nette fornisce un oggetto FileStorage che memorizza i dati in una sottocartella `cache` nella directory dei [file temporanei |application:bootstrap#Temporary Files]. +Si accede all'oggetto storage facendoselo passare tramite [dependency injection |dependency-injection:passing-dependencies] con il tipo `Nette\Caching\Storage`. Come storage predefinito, Nette fornisce l'oggetto FileStorage che salva i dati nella sottodirectory `cache` nella directory per i [file temporanei |application:bootstrapping#File Temporanei]. È possibile modificare lo storage nella configurazione: @@ -334,28 +353,25 @@ services: ``` -FileStorage .[#toc-filestorage] -------------------------------- +FileStorage +----------- -Scrive la cache in file su disco. Lo storage `Nette\Caching\Storages\FileStorage` è molto ben ottimizzato per le prestazioni e soprattutto garantisce la piena atomicità delle operazioni. Che cosa significa? Che quando si utilizza la cache, non può accadere che si legga un file che non è stato ancora completamente scritto da un altro thread, o che qualcuno lo cancelli "sotto le sue mani". L'uso della cache è quindi completamente sicuro. +Scrive la cache in file su disco. Lo storage `Nette\Caching\Storages\FileStorage` è molto ben ottimizzato per le prestazioni e soprattutto garantisce la piena atomicità delle operazioni. Cosa significa? Che quando si utilizza la cache, non può accadere di leggere un file che non è ancora stato completamente scritto da un altro thread, o che qualcuno ve lo cancelli "sotto il naso". L'uso della cache è quindi completamente sicuro. -Questa memoria ha anche un'importante funzione integrata che impedisce un aumento estremo dell'utilizzo della CPU quando la cache viene cancellata o raffreddata (cioè non creata). Si tratta della prevenzione del "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. -Succede che in un momento ci sono diverse richieste concorrenti che vogliono la stessa cosa dalla cache (ad esempio il risultato di una query SQL costosa) e poiché non è presente nella cache, tutti i processi iniziano a eseguire la stessa query SQL. -Il carico del processore si moltiplica e può anche accadere che nessun thread riesca a rispondere entro il tempo limite, la cache non viene creata e l'applicazione si blocca. -Fortunatamente, la cache in Nette funziona in modo tale che quando ci sono più richieste simultanee per un elemento, questo viene generato solo dal primo thread, gli altri aspettano e poi usano il risultato generato. +Questo storage ha anche una funzione importante integrata che previene un aumento estremo dell'utilizzo della CPU nel momento in cui la cache viene cancellata o non è ancora "calda" (cioè creata). Si tratta di una prevenzione contro il "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Succede che, in un dato momento, un gran numero di richieste simultanee arrivino, chiedendo la stessa cosa dalla cache (ad esempio, il risultato di una costosa query SQL) e poiché non è nella cache, tutti i processi iniziano a eseguire la stessa query SQL. Il carico si moltiplica e può persino accadere che nessun thread riesca a rispondere entro il limite di tempo, la cache non viene creata e l'applicazione collassa. Fortunatamente, la cache in Nette funziona in modo tale che, in caso di più richieste simultanee per un singolo elemento, viene generato solo dal primo thread, gli altri aspettano e successivamente utilizzano il risultato generato. -Esempio di creazione di un FileStorage: +Esempio di creazione di FileStorage: ```php -// lo storage sarà la directory '/path/to/temp' sul disco +// lo storage sarà la directory '/path/to/temp' su disco $storage = new Nette\Caching\Storages\FileStorage('/path/to/temp'); ``` -MemcachedStorage .[#toc-memcachedstorage] ------------------------------------------ +MemcachedStorage +---------------- -Il server [Memcached |https://memcached.org] è un sistema di archiviazione distribuita ad alte prestazioni il cui adattatore è `Nette\Caching\Storages\MemcachedStorage`. Nella configurazione, specificare l'indirizzo IP e la porta se differisce dallo standard 11211. +Il server [Memcached|https://memcached.org] è un sistema di memorizzazione distribuita ad alte prestazioni, il cui adattatore è `Nette\Caching\Storages\MemcachedStorage`. Nella configurazione, specifichiamo l'indirizzo IP e la porta, se diversa dalla standard 11211. .[caution] Richiede l'estensione PHP `memcached`. @@ -366,16 +382,16 @@ services: ``` -MemoriaStorage .[#toc-memorystorage] ------------------------------------- +MemoryStorage +------------- -`Nette\Caching\Storages\MemoryStorage` è una memoria che memorizza i dati in un array PHP e che quindi viene persa quando la richiesta viene terminata. +`Nette\Caching\Storages\MemoryStorage` è uno storage che salva i dati in un array PHP, e quindi si perdono alla fine della richiesta. -SQLiteStorage .[#toc-sqlitestorage] ------------------------------------ +SQLiteStorage +------------- -Il database SQLite e l'adattatore `Nette\Caching\Storages\SQLiteStorage` offrono un modo per memorizzare la cache in un singolo file su disco. La configurazione specificherà il percorso di questo file. +Il database SQLite e l'adattatore `Nette\Caching\Storages\SQLiteStorage` offrono un modo per salvare la cache in un unico file su disco. Nella configurazione, specifichiamo il percorso di questo file. .[caution] Richiede le estensioni PHP `pdo` e `pdo_sqlite`. @@ -386,16 +402,16 @@ services: ``` -DevNullStorage .[#toc-devnullstorage] -------------------------------------- +DevNullStorage +-------------- -Un'implementazione speciale di storage è `Nette\Caching\Storages\DevNullStorage`, che non memorizza affatto i dati. È quindi adatta per i test se si vuole eliminare l'effetto della cache. +Un'implementazione speciale dello storage è `Nette\Caching\Storages\DevNullStorage`, che in realtà non salva affatto i dati. È quindi adatto per i test, quando vogliamo eliminare l'influenza della cache. -Utilizzo della cache nel codice .[#toc-using-cache-in-code] -=========================================================== +Utilizzo della cache nel codice +=============================== -Quando si usa la cache nel codice, ci sono due modi per farlo. Il primo è quello di ottenere l'oggetto di memorizzazione passandoglielo tramite [dependency injection |dependency-injection:passing-dependencies] e poi creare un oggetto `Cache`: +Quando si utilizza la cache nel codice, abbiamo due modi per farlo. Il primo è farsi passare lo storage tramite [dependency injection |dependency-injection:passing-dependencies] e creare l'oggetto `Cache`: ```php use Nette; @@ -411,7 +427,7 @@ class ClassOne } ``` -Il secondo modo è quello di ottenere l'oggetto di memorizzazione `Cache`: +La seconda opzione è farsi passare direttamente l'oggetto `Cache`: ```php class ClassTwo @@ -423,7 +439,7 @@ class ClassTwo } ``` -L'oggetto `Cache` viene quindi creato direttamente nella configurazione come segue: +L'oggetto `Cache` viene quindi creato direttamente nella configurazione in questo modo: ```neon services: @@ -431,12 +447,12 @@ services: ``` -Giornale .[#toc-journal] -======================== +Journal +======= -Nette memorizza i tag e le priorità in un cosiddetto journal. Per impostazione predefinita, vengono utilizzati SQLite e il file `journal.s3db`, mentre sono necessarie le estensioni **PHP `pdo` e `pdo_sqlite` **. +Nette salva i tag e le priorità nel cosiddetto journal. Standardmente, per questo viene utilizzato SQLite e il file `journal.s3db` e **sono richieste le estensioni PHP `pdo` e `pdo_sqlite`.** -È possibile modificare il diario nella configurazione: +È possibile modificare il journal nella configurazione: ```neon services: @@ -444,4 +460,25 @@ services: ``` -{{leftbar: nette:@menu-topics}} +Servizi DI +========== + +Questi servizi vengono aggiunti al container DI: + +| Nome | Tipo | Descrizione +|---------------------------------------------------------- +| `cache.journal` | [api:Nette\Caching\Storages\Journal] | journal +| `cache.storage` | [api:Nette\Caching\Storage] | storage + + +Disattivazione della cache +========================== + +Una delle opzioni per disattivare la cache nell'applicazione è impostare [#DevNullStorage] come storage: + +```neon +services: + cache.storage: Nette\Caching\Storages\DevNullStorage +``` + +Questa impostazione non influisce sulla cache dei template in Latte o del container DI, poiché queste librerie non utilizzano i servizi di nette/caching e gestiscono la cache autonomamente. La loro cache, del resto, [non è necessario disattivare |nette:troubleshooting#Come disattivare la cache durante lo sviluppo] in modalità sviluppatore. diff --git a/caching/it/@meta.texy b/caching/it/@meta.texy new file mode 100644 index 0000000000..9d19e7312c --- /dev/null +++ b/caching/it/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Documentazione Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/caching/ja/@home.texy b/caching/ja/@home.texy new file mode 100644 index 0000000000..50ae8d3d98 --- /dev/null +++ b/caching/ja/@home.texy @@ -0,0 +1,484 @@ +Nette Caching +************* + +<div class=perex> + +キャッシュ は、一度取得に時間のかかったデータを次回の使用のために保存することで、アプリケーションを高速化します。以下を示します: + +- キャッシュの使用方法 +- ストレージの変更方法 +- キャッシュを正しく無効化する方法 + +</div> + +Netteでのキャッシュの使用は非常に簡単ですが、非常に高度なニーズにも対応しています。パフォーマンスと100%の耐障害性を考慮して設計されています。基本的には、最も一般的なバックエンドストレージ用のアダプタが含まれています。タグベースの無効化、時間ベースの有効期限、キャッシュスタンピードからの保護などをサポートしています。 + + +インストール +====== + +[Composer|best-practices:composer]を使用してライブラリをダウンロードし、インストールします: + +```shell +composer require nette/caching +``` + + +基本的な使用法 +======= + +キャッシュ(またはメモリキャッシュ)の操作の中心は[api:Nette\Caching\Cache]オブジェクトです。そのインスタンスを作成し、コンストラクタにいわゆるストレージをパラメータとして渡します。これは、データが物理的に保存される場所(データベース、Memcached、ディスク上のファイルなど)を表すオブジェクトです。ストレージには、`Nette\Caching\Storage` 型で[dependency injection |dependency-injection:passing-dependencies]を使用して渡してもらうことでアクセスできます。必要なことはすべて[ストレージのセクション |#ストレージ]で説明します。 + +.[warning] +バージョン3.0では、インターフェースにはまだ接頭辞 `I` が付いていたため、名前は `Nette\Caching\IStorage` でした。また、`Cache` クラスの定数は大文字で書かれていたため、例えば `Cache::Expire` の代わりに `Cache::EXPIRE` でした。 + +以下の例では、エイリアス `Cache` が作成され、変数 `$storage` にストレージが含まれていると仮定します。 + +```php +use Nette\Caching\Cache; + +$storage = /* ... */; // Nette\Caching\Storage のインスタンス +``` + +キャッシュは実際には *key-valueストア* であり、連想配列のようにキーを使用してデータを読み書きします。アプリケーションは多数の独立した部分で構成されており、すべてが1つのストレージ(ディスク上の1つのディレクトリを想像してください)を使用すると、遅かれ早かれキーの衝突が発生します。Nette Frameworkはこの問題を、スペース全体を名前空間(サブディレクトリ)に分割することで解決します。プログラムの各部分は、一意の名前を持つ独自のスペースを使用するため、衝突は発生しません。 + +スペース名はCacheクラスのコンストラクタの2番目のパラメータとして指定します: + +```php +$cache = new Cache($storage, 'Full Html Pages'); +``` + +これで、`$cache` オブジェクトを使用してキャッシュから読み書きできます。両方の操作には `load()` メソッドを使用します。最初の引数はキーで、2番目の引数はキーがキャッシュに見つからない場合に呼び出されるPHPコールバックです。コールバックは値を生成して返し、キャッシュに保存されます: + +```php +$value = $cache->load($key, function () use ($key) { + $computedValue = /* ... */; // 時間のかかる計算 + return $computedValue; +}); +``` + +2番目のパラメータを指定しない場合 `$value = $cache->load($key)`、キャッシュにアイテムがない場合は `null` が返されます。 + +.[tip] +素晴らしいことに、キャッシュにはシリアライズ可能な任意の構造を保存でき、文字列だけである必要はありません。そして、これはキーにも当てはまります(ただし、通常は文字列または単純な配列が推奨されます)。 + +キャッシュからアイテムを削除するには `remove()` メソッドを使用します: + +```php +$cache->remove($key); +``` + +キャッシュにアイテムを保存するには `$cache->save($key, $value, array $dependencies = [])` メソッドも使用できます。ただし、上記で説明した `load()` を使用する方法が推奨されます。 + + +メモ化 +=== + +メモ化とは、関数やメソッドの呼び出し結果をキャッシュして、同じことを何度も計算することなく次回使用できるようにすることです。 + +メソッドや関数は `call(callable $callback, ...$args)` を使用してメモ化して呼び出すことができます: + +```php +$result = $cache->call('gethostbyaddr', $ip); +``` + +`gethostbyaddr()` 関数は、各 `$ip` パラメータに対して一度だけ呼び出され、次回はキャッシュから値が返されます。 + +後で呼び出すことができるメソッドや関数のメモ化されたラッパーを作成することも可能です: + +```php +function factorial($num) +{ + return /* ... */; +} + +$memoizedFactorial = $cache->wrap('factorial'); + +$result = $memoizedFactorial(5); // 初回計算 +$result = $memoizedFactorial(5); // 2回目はキャッシュから +``` + + +有効期限と無効化 +======== + +キャッシュに保存する際には、以前に保存されたデータがいつ無効になるかという問題を解決する必要があります。Nette Frameworkは、データの有効期間を制限したり、制御された方法で削除(フレームワークの用語では「無効化」)したりするメカニズムを提供します。 + +データの有効期間は、保存時に `save()` メソッドの3番目のパラメータを使用して設定します。例: + +```php +$cache->save($key, $value, [ + $cache::Expire => '20 minutes', +]); +``` + +または、`load()` メソッドのコールバックに参照渡しされる `$dependencies` パラメータを使用します。例: + +```php +$value = $cache->load($key, function (&$dependencies) { + $dependencies[Cache::Expire] = '20 minutes'; + return /* ... */; +}); +``` + +または、`load()` メソッドの3番目のパラメータを使用します。例: + +```php +$value = $cache->load($key, function () { + return ...; +}, [Cache::Expire => '20 minutes']); +``` + +以下の例では、2番目のバリアントを想定し、したがって変数 `$dependencies` が存在すると仮定します。 + + +有効期限 +---- + +最も単純な有効期限は時間制限です。このようにして、20分間有効なデータをキャッシュに保存します: + +```php +// 秒数またはUNIXタイムスタンプも受け付けます +$dependencies[Cache::Expire] = '20 minutes'; +``` + +読み取りごとに有効期間を延長したい場合は、次のように実現できますが、注意してください。これによりキャッシュのオーバーヘッドが増加します: + +```php +$dependencies[Cache::Sliding] = true; +``` + +ファイルまたは複数のファイルのいずれかが変更されたときにデータを期限切れにするオプションは便利です。これは、たとえば、これらのファイルを処理して生成されたデータをキャッシュに保存する場合に使用できます。絶対パスを使用してください。 + +```php +$dependencies[Cache::Files] = '/path/to/data.yaml'; +// または +$dependencies[Cache::Files] = ['/path/to/data1.yaml', '/path/to/data2.yaml']; +``` + +別のアイテム(または複数の他のアイテムのいずれか)が期限切れになったときに、キャッシュ内のアイテムを期限切れにすることができます。これは、たとえば、HTMLページ全体をキャッシュし、そのフラグメントを他のキーの下に保存する場合に使用できます。フラグメントが変更されると、ページ全体が無効になります。フラグメントがキー `frag1` と `frag2` の下に保存されている場合、次のように使用します: + +```php +$dependencies[Cache::Items] = ['frag1', 'frag2']; +``` + +有効期限は、独自の関数または静的メソッドを使用して制御することもできます。これらの関数またはメソッドは、読み取り時にアイテムがまだ有効かどうかを常に決定します。このようにして、たとえば、PHPのバージョンが変更されたときに常にアイテムを期限切れにすることができます。現在のバージョンをパラメータと比較する関数を作成し、保存時に依存関係の間に `[関数名, ...引数]` の形式の配列を追加します: + +```php +function checkPhpVersion($ver): bool +{ + return $ver === PHP_VERSION_ID; +} + +$dependencies[Cache::Callbacks] = [ + ['checkPhpVersion', PHP_VERSION_ID] // checkPhpVersion(...) === false の場合に期限切れにする +]; +``` + +もちろん、すべての基準を組み合わせることができます。キャッシュは、少なくとも1つの基準が満たされない場合に期限切れになります。 + +```php +$dependencies[Cache::Expire] = '20 minutes'; +$dependencies[Cache::Files] = '/path/to/data.yaml'; +``` + + +タグによる無効化 +-------- + +非常に便利な無効化ツールは、いわゆるタグです。キャッシュ内の各アイテムに、任意の文字列であるタグのリストを割り当てることができます。たとえば、記事とコメントを含むHTMLページがあり、それをキャッシュするとします。保存時にタグを指定します: + +```php +$dependencies[Cache::Tags] = ["article/$articleId", "comments/$articleId"]; +``` + +管理画面に移動しましょう。ここに記事を編集するためのフォームがあります。記事をデータベースに保存すると同時に、タグに従ってキャッシュからアイテムを削除する `clean()` コマンドを呼び出します: + +```php +$cache->clean([ + $cache::Tags => ["article/$articleId"], +]); +``` + +同様に、新しいコメントを追加する場所(またはコメントを編集する場所)で、適切なタグを無効化することを忘れないでください: + +```php +$cache->clean([ + $cache::Tags => ["comments/$articleId"], +]); +``` + +これで何が達成されたのでしょうか?記事やコメントが変更されるたびにHTMLキャッシュが無効化(削除)されるようになります。ID = 10の記事が編集されると、タグ `article/10` が強制的に無効化され、指定されたタグを持つHTMLページがキャッシュから削除されます。同じことが、対応する記事の下に新しいコメントが挿入された場合にも発生します。 + +.[note] +タグにはいわゆる[#Journal]が必要です。 + + +優先度による無効化 +--------- + +キャッシュ内の個々のアイテムに優先度を設定できます。これを使用して、たとえばキャッシュが特定のサイズを超えた場合にアイテムを削除できます: + +```php +$dependencies[Cache::Priority] = 50; +``` + +優先度が100以下のすべてのアイテムを削除します: + +```php +$cache->clean([ + $cache::Priority => 100, +]); +``` + +.[note] +優先度にはいわゆる[#Journal]が必要です。 + + +キャッシュの削除 +-------- + +パラメータ `Cache::All` はすべてを削除します: + +```php +$cache->clean([ + $cache::All => true, +]); +``` + + +一括読み取り +====== + +キャッシュへの一括読み取りおよび書き込みには `bulkLoad()` メソッドを使用します。これにキーの配列を渡し、値の配列を取得します: + +```php +$values = $cache->bulkLoad($keys); +``` + +`bulkLoad()` メソッドは `load()` と同様に、生成されるアイテムのキーを渡される2番目のパラメータコールバックでも機能します: + +```php +$values = $cache->bulkLoad($keys, function ($key, &$dependencies) { + $computedValue = /* ... */; // 時間のかかる計算 + return $computedValue; +}); +``` + + +PSR-16との使用 .{data-version:3.3.1} +================================ + +PSR-16インターフェースでNette Cacheを使用するには、`PsrCacheAdapter` アダプタを使用できます。これにより、Nette CacheとPSR-16互換キャッシュを期待する任意のコードまたはライブラリとの間でシームレスな統合が可能になります。 + +```php +$psrCache = new Nette\Bridges\Psr\PsrCacheAdapter($storage); +``` + +これで `$psrCache` をPSR-16キャッシュとして使用できます: + +```php +$psrCache->set('key', 'value', 3600); // 値を1時間保存します +$value = $psrCache->get('key', 'default'); +``` + +アダプタは、`getMultiple()`、`setMultiple()`、`deleteMultiple()` を含む、PSR-16で定義されているすべてのメソッドをサポートしています。 + + +出力のキャッシュ +======== + +出力をキャプチャしてキャッシュすることは非常にエレガントに行えます: + +```php +if ($capture = $cache->capture($key)) { + + echo ... // データを出力します + + $capture->end(); // 出力をキャッシュに保存します +} +``` + +出力がすでにキャッシュに保存されている場合、`capture()` メソッドはそれを表示して `null` を返し、したがって条件は実行されません。それ以外の場合、出力のキャプチャを開始し、最終的に表示されたデータをキャッシュに保存するために使用される `$capture` オブジェクトを返します。 + +.[note] +バージョン3.0では、メソッド名は `$cache->start()` でした。 + + +Latteでのキャッシュ +============ + +[Latte|latte:]テンプレートでのキャッシュは非常に簡単です。テンプレートの一部を `{cache}...{/cache}` タグで囲むだけです。ソーステンプレート(キャッシュブロック内のインクルードされたテンプレートを含む)が変更されると、キャッシュは自動的に無効化されます。`{cache}` タグはネストでき、ネストされたブロックが無効化されると(たとえばタグによって)、親ブロックも無効化されます。 + +タグ内では、キャッシュがバインドされるキー(ここでは変数 `$id`)を指定し、有効期限と[無効化タグ |#タグによる無効化]を設定できます。 + +```latte +{cache $id, expire: '20 minutes', tags: [tag1, tag2]} + ... +{/cache} +``` + +すべてのパラメータはオプションなので、有効期限もタグも、最終的にはキーも指定する必要はありません。 + +キャッシュの使用は `if` を使用して条件付きにすることもできます - コンテンツは条件が満たされた場合にのみキャッシュされます: + +```latte +{cache $id, if: !$form->isSubmitted()} + {$form} +{/cache} +``` + + +ストレージ +===== + +ストレージは、データが物理的に保存される場所を表すオブジェクトです。データベース、Memcachedサーバー、または最も利用しやすいストレージであるディスク上のファイルを使用できます。 + +|----------------- +| ストレージ | 説明 +|----------------- +| [#FileStorage] | ディスク上のファイルに保存するデフォルトのストレージ +| [#MemcachedStorage] | `Memcached` サーバーを使用します +| [#MemoryStorage] | データは一時的にメモリに保存されます +| [#SQLiteStorage] | データはSQLiteデータベースに保存されます +| [#DevNullStorage] | データは保存されません、テストに適しています + +ストレージオブジェクトには、`Nette\Caching\Storage` 型で[dependency injection |dependency-injection:passing-dependencies]を使用して渡してもらうことでアクセスできます。デフォルトのストレージとして、Netteは[一時ファイル |application:bootstrapping#一時ファイル]用のディレクトリの `cache` サブディレクトリにデータを保存するFileStorageオブジェクトを提供します。 + +ストレージは設定で変更できます: + +```neon +services: + cache.storage: Nette\Caching\Storages\DevNullStorage +``` + + +FileStorage +----------- + +キャッシュをディスク上のファイルに書き込みます。`Nette\Caching\Storages\FileStorage` ストレージはパフォーマンスに非常に最適化されており、特に操作の完全な原子性を保証します。これはどういう意味でしょうか?キャッシュを使用する場合、別のスレッドによってまだ完全に書き込まれていないファイルを読み取ったり、誰かが「手元で」削除したりすることはできません。したがって、キャッシュの使用は完全に安全です。 + +このストレージには、キャッシュが削除されたり、まだウォームアップされていない(つまり作成されていない)ときにCPU使用率が極端に増加するのを防ぐ重要な機能も組み込まれています。これは「キャッシュスタンピード」:https://en.wikipedia.org/wiki/Cache_stampede に対する予防策です。 同時に多数の同時リクエストが発生し、それらがすべてキャッシュから同じもの(たとえば、高価なSQLクエリの結果)を要求し、キャッシュにないため、すべてのプロセスが同じSQLクエリを実行し始めることがあります。 これにより負荷が倍増し、どのスレッドも時間制限内に応答できず、キャッシュが作成されず、アプリケーションがクラッシュすることさえあります。 幸いなことに、Netteのキャッシュは、1つのアイテムに対して複数の同時リクエストがある場合、最初のスレッドのみがそれを生成し、他のスレッドは待機し、その後生成された結果を使用するように機能します。 + +FileStorageの作成例: + +```php +// ストレージはディスク上の '/path/to/temp' ディレクトリになります +$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp'); +``` + + +MemcachedStorage +---------------- + +[Memcached|https://memcached.org]サーバーは、高性能な分散メモリキャッシュシステムであり、そのアダプタは `Nette\Caching\Storages\MemcachedStorage` です。設定では、標準の11211と異なる場合はIPアドレスとポートを指定します。 + +.[caution] +PHP拡張機能 `memcached` が必要です。 + +```neon +services: + cache.storage: Nette\Caching\Storages\MemcachedStorage('10.0.0.5') +``` + + +MemoryStorage +------------- + +`Nette\Caching\Storages\MemoryStorage` は、データをPHP配列に保存するストレージであり、したがってリクエストの終了とともに失われます。 + + +SQLiteStorage +------------- + +SQLiteデータベースと `Nette\Caching\Storages\SQLiteStorage` アダプタは、キャッシュをディスク上の単一ファイルに保存する方法を提供します。設定では、このファイルへのパスを指定します。 + +.[caution] +PHP拡張機能 `pdo` および `pdo_sqlite` が必要です。 + +```neon +services: + cache.storage: Nette\Caching\Storages\SQLiteStorage('%tempDir%/cache.db') +``` + + +DevNullStorage +-------------- + +ストレージの特別な実装は `Nette\Caching\Storages\DevNullStorage` であり、実際にはデータをまったく保存しません。したがって、キャッシュの影響を排除したいテストに適しています。 + + +コードでのキャッシュの使用 +============= + +コードでキャッシュを使用する場合、2つの方法があります。1つ目は、[dependency injection |dependency-injection:passing-dependencies]を使用してストレージを渡し、`Cache` オブジェクトを作成する方法です: + +```php +use Nette; + +class ClassOne +{ + private Nette\Caching\Cache $cache; + + public function __construct(Nette\Caching\Storage $storage) + { + $this->cache = new Nette\Caching\Cache($storage, 'my-namespace'); + } +} +``` + +2番目のオプションは、`Cache` オブジェクトを直接渡してもらうことです: + +```php +class ClassTwo +{ + public function __construct( + private Nette\Caching\Cache $cache, + ) { + } +} +``` + +`Cache` オブジェクトは、次のように設定で直接作成されます: + +```neon +services: + - ClassTwo( Nette\Caching\Cache(namespace: 'my-namespace') ) +``` + + +Journal +======= + +Netteはタグと優先度をいわゆるジャーナルに保存します。標準では、SQLiteとファイル `journal.s3db` が使用され、**PHP拡張機能 `pdo` と `pdo_sqlite` が必要です。** + +ジャーナルは設定 (`config/services.neon`) で変更できます: + +```neon +services: + cache.journal: MyJournal +``` + + +DIサービス +====== + +これらのサービスはDIコンテナに追加されます: + +| 名前 | 型 | 説明 +|---------------------------------------------------------- +| `cache.journal` | [api:Nette\Caching\Storages\Journal] | キャッシュのジャーナル (タグや優先度管理用) +| `cache.storage` | [api:Nette\Caching\Storage] | デフォルトのキャッシュストレージ + + +キャッシュの無効化 +========= + +アプリケーションでキャッシュを無効にする1つの方法は、ストレージとして[#DevNullStorage]を設定することです: + +```neon +services: + cache.storage: Nette\Caching\Storages\DevNullStorage +``` + +この設定は、LatteのテンプレートやDIコンテナのキャッシュには影響しません。これらのライブラリはnette/cachingサービスを使用せず、独自のキャッシュを管理するためです。また、開発モードでは[これらのキャッシュを無効にする必要はありません |nette:troubleshooting#開発中にキャッシュを無効にする方法は]。 diff --git a/caching/ja/@meta.texy b/caching/ja/@meta.texy new file mode 100644 index 0000000000..7d67dcb7b8 --- /dev/null +++ b/caching/ja/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette ドキュメンテーション}} +{{leftbar: nette:@menu-topics}} diff --git a/caching/pl/@home.texy b/caching/pl/@home.texy index 70e60ec45f..61fdbc3275 100644 --- a/caching/pl/@home.texy +++ b/caching/pl/@home.texy @@ -1,38 +1,38 @@ -Cache -***** +Nette Caching +************* <div class=perex> -Pamięć podręczna `[keš]` przyspiesza działanie aplikacji poprzez przechowywanie raz zużytych danych do przyszłego wykorzystania. Zobaczymy: +Cache przyspieszy twoją aplikację, przechowując raz uzyskane dane do przyszłego użytku. Pokażemy: -- jak korzystać z pamięci podręcznej -- jak zmienić sposób przechowywania -- jak prawidłowo unieważnić pamięć podręczną +- jak używać cache +- jak zmienić magazyn +- jak poprawnie unieważniać cache </div> -Używanie cachingu jest w Nette bardzo proste, a jednocześnie pokrywa bardzo zaawansowane potrzeby. Został zaprojektowany z myślą o wydajności i 100% odporności. Baza zawiera adaptery dla większości popularnych pamięci masowych backend. Pozwala na unieważnienie oparte na tagach, wygaśnięcie czasu, ma ochronę przed cache stampede, itp. +Korzystanie z cache w Nette jest bardzo łatwe, a jednocześnie obejmuje bardzo zaawansowane potrzeby. Został zaprojektowany z myślą o wydajności i 100% odporności. W standardzie znajdziesz adaptery do najczęstszych magazynów backendowych. Umożliwia unieważnianie oparte na tagach, wygaśnięcie czasowe, posiada ochronę przed zjawiskiem *cache stampede* itp. -Instalacja .[#toc-installation] -=============================== +Instalacja +========== -Pobierz i zainstaluj bibliotekę za pomocą [Composera |best-practices:composer]: +Bibliotekę pobierzesz i zainstalujesz za pomocą narzędzia [Composer|best-practices:composer]: ```shell composer require nette/caching ``` -Podstawowe zastosowanie .[#toc-basic-usage] -=========================================== +Podstawowe użycie +================= -Obiekt [api:Nette\Caching\Cache] jest centrum pracy z pamięcią podręczną lub cache. Tworzymy jego instancję i przekazujemy tzw. storage jako parametr do konstruktora. Jest to obiekt reprezentujący miejsce, gdzie dane będą fizycznie przechowywane (baza danych, Memcached, pliki na dysku, ...). Aby dostać się do magazynu, przekazujesz go za pomocą [dependency injection |dependency-injection:passing-dependencies] z typem `Nette\Caching\Storage`. Wszystkiego ważnego dowiesz się w sekcji [Storage |#User-Storage]. +Centrum pracy z cache, czyli pamięcią podręczną, stanowi obiekt [api:Nette\Caching\Cache]. Tworzymy jego instancję i jako parametr przekazujemy konstruktorowi tzw. magazyn (storage). Jest to obiekt reprezentujący miejsce, gdzie dane będą fizycznie przechowywane (baza danych, Memcached, pliki na dysku, ...). Do magazynu dostaniemy się, prosząc o jego przekazanie za pomocą [dependency injection |dependency-injection:passing-dependencies] z typem `Nette\Caching\Storage`. Wszystko, co istotne, dowiesz się w [sekcji Magazyny |#Magazyny]. .[warning] -W wersji 3.0 interfejs miał jeszcze prefiks `I`, takže název byl `Nette\Caching\IStorage`. Również stałe klasy `Cache` były pisane wielką literą, więc na przykład `Cache::EXPIRE` zamiast `Cache::Expire`. +W wersji 3.0 interfejs miał jeszcze prefiks `I`, więc nazwa brzmiała `Nette\Caching\IStorage`. Ponadto stałe klasy `Cache` były pisane wielkimi literami, więc na przykład `Cache::EXPIRE` zamiast `Cache::Expire`. -Dla poniższych przykładów załóżmy, że utworzyliśmy alias `Cache` oraz zmienną składową `$storage`. +W poniższych przykładach zakładamy, że mamy utworzony alias `Cache` i w zmiennej `$storage` magazyn. ```php use Nette\Caching\Cache; @@ -40,51 +40,51 @@ use Nette\Caching\Cache; $storage = /* ... */; // instancja Nette\Caching\Storage ``` -Cache jest tak naprawdę *key-value store*, więc czytamy i zapisujemy dane pod kluczami tak jak w przypadku tablic asocjacyjnych. Aplikacje składają się z wielu niezależnych części i jeśli wszystkie korzystają z tej samej pamięci masowej (wyobraźmy sobie jeden katalog na dysku), prędzej czy później doszłoby do kolizji kluczy. Nette Framework rozwiązuje ten problem poprzez podział całej przestrzeni na przestrzenie nazw (podkatalogi). Każda część programu korzysta wtedy z własnej przestrzeni o unikalnej nazwie i nie może już dojść do kolizji. +Cache jest właściwie *key–value store*, czyli dane odczytujemy i zapisujemy pod kluczami, tak jak w tablicach asocjacyjnych. Aplikacje składają się z wielu niezależnych części i jeśli wszystkie będą używać jednego magazynu (wyobraź sobie jeden katalog na dysku), wcześniej czy później doszłoby do kolizji kluczy. Nette Framework rozwiązuje ten problem, dzieląc całą przestrzeń na przestrzenie nazw (podkatalogi). Każda część programu używa wtedy swojej przestrzeni z unikalną nazwą i żadna kolizja już nie może wystąpić. -Nazwa przestrzeni nazw podawana jest jako drugi parametr konstruktora klasy Cache: +Nazwę przestrzeni podajemy jako drugi parametr konstruktora klasy Cache: ```php $cache = new Cache($storage, 'Full Html Pages'); ``` -Teraz możemy użyć obiektu `$cache` do odczytu z i zapisu do cache. Metoda `load()` służy do wykonania obu tych czynności. Pierwszym argumentem jest klucz, a drugim jest wywołanie zwrotne PHP, które jest wywoływane, gdy klucz nie zostanie znaleziony w pamięci podręcznej. Callback generuje wartość, zwraca ją i przechowuje w pamięci podręcznej: +Teraz możemy za pomocą obiektu `$cache` odczytywać i zapisywać do pamięci podręcznej. Do obu służy metoda `load()`. Pierwszym argumentem jest klucz, a drugim PHP callback, który jest wywoływany, gdy klucz nie zostanie znaleziony w cache. Callback generuje wartość, zwraca ją, a ta jest zapisywana w cache: ```php $value = $cache->load($key, function () use ($key) { - $computedValue = /* ... */; // náročný výpočet + $computedValue = /* ... */; // kosztowne obliczenia return $computedValue; }); ``` -Jeśli drugi argument nie określa `$value = $cache->load($key)`, `null` zostanie zwrócony, jeśli element nie jest w pamięci podręcznej. +Jeśli drugi parametr nie zostanie podany `$value = $cache->load($key)`, zwrócony zostanie `null`, jeśli elementu nie ma w cache. .[tip] -Miłą rzeczą jest to, że każda serializowalna struktura może być buforowana, nie muszą to być ciągi. I to samo dotyczy nawet kluczy. +Świetne jest to, że w cache można przechowywać dowolne serializowalne struktury, nie muszą to być tylko ciągi znaków. To samo dotyczy nawet kluczy. -Możemy wyczyścić element z pamięci podręcznej za pomocą metody `remove()`: +Element z pamięci podręcznej usuwamy metodą `remove()`: ```php $cache->remove($key); ``` -Możesz również buforować element za pomocą metody `$cache->save($key, $value, array $dependencies = [])`. Jednak preferowaną metodą powyżej jest użycie `load()`. +Zapisać element do pamięci podręcznej można również metodą `$cache->save($key, $value, array $dependencies = [])`. Preferowany jest jednak powyższy sposób za pomocą `load()`. -Memoizacja .[#toc-memoization] -============================== +Memoizacja +========== -Memoization oznacza buforowanie wyniku wywołania funkcji lub metody, aby można było użyć go następnym razem bez obliczania tego samego w kółko. +Memoizacja oznacza buforowanie wyniku wywołania funkcji lub metody, aby móc go użyć następnym razem bez ponownego obliczania tej samej rzeczy. -Możesz wywołać metody i funkcje w sposób memoized za pomocą `call(callable $callback, ...$args)`: +Memoizowanie można wywoływać dla metod i funkcji za pomocą `call(callable $callback, ...$args)`: ```php $result = $cache->call('gethostbyaddr', $ip); ``` -Funkcja `gethostbyaddr()` jest wywoływana tylko raz dla każdego parametru `$ip` i następnym razem wartość jest zwracana z pamięci podręcznej. +Funkcja `gethostbyaddr()` zostanie wywołana dla każdego parametru `$ip` tylko raz, a następnym razem zostanie zwrócona wartość z cache. -Możliwe jest również utworzenie memoized wrapper nad metodą lub funkcją, która może zostać wywołana później: +Możliwe jest również utworzenie memoizowanego opakowania nad metodą lub funkcją, które można wywołać później: ```php function factorial($num) @@ -95,16 +95,16 @@ function factorial($num) $memoizedFactorial = $cache->wrap('factorial'); $result = $memoizedFactorial(5); // oblicza po raz pierwszy -$result = $memoizedFactorial(5); // drugi raz z pamięci podręcznej +$result = $memoizedFactorial(5); // po raz drugi z cache ``` -Wygaśnięcie i unieważnienie .[#toc-expiration-invalidation] -=========================================================== +Wygaśnięcie i unieważnienie +=========================== -W przypadku buforowania musisz zająć się kwestią, kiedy wcześniej zbuforowane dane stają się nieważne. Nette Framework zapewnia mechanizm ograniczania ważności danych lub ich usuwania w kontrolowany sposób (w terminologii frameworka - "invalidate"). +Przy zapisywaniu do cache trzeba rozwiązać kwestię, kiedy wcześniej zapisane dane stają się nieaktualne. Nette Framework oferuje mechanizm, jak ograniczyć ważność danych lub je kontrolowanie usuwać (w terminologii frameworka „unieważniać“). -Ważność danych jest ustawiana w momencie przechowywania za pomocą trzeciego parametru metody `save()`, np: +Ważność danych ustawia się w momencie zapisywania za pomocą trzeciego parametru metody `save()`, np.: ```php $cache->save($key, $value, [ @@ -112,7 +112,7 @@ $cache->save($key, $value, [ ]); ``` -Lub używając parametru `$dependencies` przekazanego przez odniesienie do callbacku metody `load()`, np: +Lub za pomocą parametru `$dependencies` przekazywanego przez referencję do callbacku metody `load()`, np.: ```php $value = $cache->load($key, function (&$dependencies) { @@ -121,7 +121,7 @@ $value = $cache->load($key, function (&$dependencies) { }); ``` -Lub poprzez 3. parametr w metodzie `load()`, np: +Lub za pomocą trzeciego parametru w metodzie `load()`, np.: ```php $value = $cache->load($key, function () { @@ -132,37 +132,37 @@ $value = $cache->load($key, function () { W kolejnych przykładach będziemy zakładać drugą opcję, a więc istnienie zmiennej `$dependencies`. -Wygaśnięcie .[#toc-expiration] ------------------------------- +Wygaśnięcie +----------- -Najprostszym wygaszeniem jest timeout. W ten sposób buforujemy dane o ważności 20 minut: +Najprostszym wygaśnięciem jest limit czasowy. W ten sposób zapisujemy dane do cache z ważnością 20 minut: ```php -// akceptuje również liczbę sekund lub UNIX-owy timestamp +// akceptuje również liczbę sekund lub timestamp UNIX $dependencies[Cache::Expire] = '20 minutes'; ``` -Gdybyśmy chcieli wydłużyć czas wygaśnięcia przy każdym odczycie, można to zrobić w następujący sposób, ale należy pamiętać, że wzrośnie narzut pamięci podręcznej: +Jeśli chcielibyśmy przedłużyć okres ważności przy każdym odczycie, można to osiągnąć w następujący sposób, ale uwaga, narzut cache przez to wzrośnie: ```php $dependencies[Cache::Sliding] = true; ``` -Poręczną funkcją jest możliwość wyodrębnienia danych w momencie zmiany pliku lub jednego z kilku plików. Można to wykorzystać np. przy buforowaniu danych powstałych w wyniku przetwarzania tych plików. Użyj ścieżek bezwzględnych. +Przydatna jest możliwość wygaśnięcia danych w momencie zmiany pliku lub jednego z wielu plików. Można to wykorzystać na przykład przy zapisywaniu do cache danych powstałych w wyniku przetwarzania tych plików. Używaj ścieżek absolutnych. ```php $dependencies[Cache::Files] = '/path/to/data.yaml'; -// nebo +// lub $dependencies[Cache::Files] = ['/path/to/data1.yaml', '/path/to/data2.yaml']; ``` -Możemy pozwolić, aby zbuforowany element był eksmitowany, gdy inny element (lub jeden z kilku innych) jest eksmitowany. Można to wykorzystać, gdy buforujemy np. całą stronę HTML i jej fragmenty pod różnymi kluczami. Gdy fragment się zmieni, cała strona zostaje unieważniona. Jeśli fragmenty są buforowane pod kluczami takimi jak `frag1` i `frag2`, używamy: +Możemy pozwolić, aby element w cache wygasł w momencie, gdy wygaśnie inny element (lub jeden z wielu innych). Można to wykorzystać, gdy przechowujemy w cache na przykład całą stronę HTML, a pod innymi kluczami jej fragmenty. Gdy fragment się zmieni, unieważniona zostanie cała strona. Jeśli fragmenty mamy zapisane pod kluczami np. `frag1` i `frag2`, użyjemy: ```php $dependencies[Cache::Items] = ['frag1', 'frag2']; ``` -Wygaśnięcie może być również kontrolowane za pomocą funkcji niestandardowych lub metod statycznych, które zawsze decydują o odczytaniu, czy element jest nadal ważny. W ten sposób, na przykład, możemy mieć element wygasający przy każdej zmianie wersji PHP. Tworzymy funkcję, która porównuje aktualną wersję z parametrem, a przy zapisie dodajemy pole pomiędzy zależnościami w formularzu `[nazev funkce, ...argumenty]`: +Wygaśnięcie można również kontrolować za pomocą własnych funkcji lub metod statycznych, które zawsze przy odczycie decydują, czy element jest jeszcze ważny. W ten sposób możemy na przykład pozwolić, aby element wygasł zawsze, gdy zmieni się wersja PHP. Tworzymy funkcję, która porównuje aktualną wersję z parametrem, a przy zapisywaniu dodajemy do zależności tablicę w formacie `[nazwa funkcji, ...argumenty]`: ```php function checkPhpVersion($ver): bool @@ -171,11 +171,11 @@ function checkPhpVersion($ver): bool } $dependencies[Cache::Callbacks] = [ - ['checkPhpVersion', PHP_VERSION_ID] // expiruj když checkPhpVersion(...) === false + ['checkPhpVersion', PHP_VERSION_ID] // wygaśnij, gdy checkPhpVersion(...) === false ]; ``` -Oczywiście wszystkie kryteria można łączyć. Następnie cache wygasa, jeśli przynajmniej jedno kryterium nie zostanie spełnione. +Wszystkie kryteria można oczywiście łączyć. Cache wygaśnie wtedy, gdy przynajmniej jedno kryterium nie jest spełnione. ```php $dependencies[Cache::Expire] = '20 minutes'; @@ -183,16 +183,16 @@ $dependencies[Cache::Files] = '/path/to/data.yaml'; ``` -Unieważnienie znacznika .[#toc-invalidation-using-tags] -------------------------------------------------------- +Unieważnianie za pomocą tagów +----------------------------- -Tagi są bardzo przydatnym narzędziem unieważniania. Każdemu elementowi w cache można przypisać listę tagów, które są dowolnymi ciągami znaków. Dla przykładu, miejmy stronę HTML z artykułem i komentarzami, którą będziemy buforować. Podczas buforowania określamy tagi: +Bardzo użytecznym narzędziem do unieważniania są tzw. tagi. Każdemy elementowi w cache możemy przypisać listę tagów, które są dowolnymi ciągami znaków. Załóżmy, że mamy stronę HTML z artykułem i komentarzami, którą będziemy buforować. Przy zapisywaniu określamy tagi: ```php $dependencies[Cache::Tags] = ["article/$articleId", "comments/$articleId"]; ``` -Przejdźmy teraz do administracji. Tutaj znajdziemy formularz do edycji artykułu. Wraz z zapisaniem artykułu do bazy danych wywołamy polecenie `clean()`, które usunie zbuforowane artykuły zgodnie ze znacznikiem: +Przejdźmy do administracji. Tutaj znajdziemy formularz do edycji artykułu. Wraz z zapisaniem artykułu do bazy danych wywołamy polecenie `clean()`, które usunie z cache elementy według tagu: ```php $cache->clean([ @@ -200,7 +200,7 @@ $cache->clean([ ]); ``` -W ten sam sposób, dodając nowy komentarz (lub edytując komentarz), nie zapomnij unieważnić odpowiedniego tagu: +Podobnie, w miejscu dodawania nowego komentarza (lub edycji komentarza) nie zapomnijmy unieważnić odpowiedniego tagu: ```php $cache->clean([ @@ -208,22 +208,22 @@ $cache->clean([ ]); ``` -Co udało nam się osiągnąć? Że nasz cache HTML zostanie unieważniony (usunięty) przy każdej zmianie artykułu lub komentarzy. Kiedy artykuł o ID = 10 jest edytowany, znacznik `article/10` jest przymusowo unieważniany, a strona HTML, która nosi ten znacznik, jest usuwana z pamięci podręcznej. Podobnie dzieje się w przypadku wstawienia nowego komentarza pod artykułem. +Co przez to osiągnęliśmy? Że nasza pamięć podręczna HTML będzie unieważniana (usuwana), gdy tylko zmieni się artykuł lub komentarze. Kiedy edytowany jest artykuł o ID = 10, następuje wymuszone unieważnienie tagu `article/10`, a strona HTML, która nosi ten tag, zostanie usunięta z cache. To samo nastąpi przy wstawieniu nowego komentarza pod odpowiedni artykuł. .[note] -Tagi wymagają tak zwanego [Dziennika |#Journal]. +Tagi wymagają tzw. [#Journal]. -Unieważnienie według priorytetu .[#toc-invalidation-by-priority] ----------------------------------------------------------------- +Unieważnianie za pomocą priorytetu +---------------------------------- -Możesz ustawić priorytet dla poszczególnych elementów w pamięci podręcznej, co pozwoli na ich usunięcie, jeśli pamięć podręczna przekroczy określony rozmiar: +Poszczególnym elementom w cache możemy ustawić priorytet, za pomocą którego będzie można je usuwać, gdy na przykład cache przekroczy określoną wielkość: ```php $dependencies[Cache::Priority] = 50; ``` -Usuń wszystkie elementy o priorytecie równym lub mniejszym niż 100: +Usuniemy wszystkie elementy o priorytecie równym lub mniejszym niż 100: ```php $cache->clean([ @@ -232,11 +232,11 @@ $cache->clean([ ``` .[note] -Priorytety wymagają tzw. [dziennika |#Journal]. +Priorytety wymagają tzw. [#Journal]. -Usuwanie pamięci podręcznej .[#toc-clear-cache] ------------------------------------------------ +Czyszczenie cache +----------------- Parametr `Cache::All` usuwa wszystko: @@ -247,51 +247,70 @@ $cache->clean([ ``` -Masowe czytanie .[#toc-bulk-reading] -==================================== +Odczyt masowy +============= -Do masowego odczytu i zapisu do cache'u służy metoda `bulkLoad()`, która przekazuje tablicę kluczy i otrzymuje tablicę wartości: +Do masowego odczytu i zapisu do cache służy metoda `bulkLoad()`, której przekazujemy tablicę kluczy i otrzymujemy tablicę wartości: ```php $values = $cache->bulkLoad($keys); ``` -Metoda `bulkLoad()` działa podobnie jak `load()` z drugim parametrem callback, któremu przekazywany jest klucz wygenerowanego elementu: +Metoda `bulkLoad()` działa podobnie jak `load()` również z drugim parametrem callbackiem, któremu przekazywany jest klucz generowanego elementu: ```php $values = $cache->bulkLoad($keys, function ($key, &$dependencies) { - $computedValue = /* ... */; // náročný výpočet + $computedValue = /* ... */; // kosztowne obliczenia return $computedValue; }); ``` -Buforowanie wyjścia .[#toc-output-caching] -========================================== +Użycie z PSR-16 .{data-version:3.3.1} +===================================== -Możesz przechwytywać i buforować dane wyjściowe bardzo elegancko: +Aby użyć Nette Cache z interfejsem PSR-16, możesz wykorzystać adapter `PsrCacheAdapter`. Umożliwia on bezproblemową integrację między Nette Cache a dowolnym kodem lub biblioteką, która oczekuje cache zgodnego z PSR-16. + +```php +$psrCache = new Nette\Bridges\Psr\PsrCacheAdapter($storage); +``` + +Teraz możesz używać `$psrCache` jako cache PSR-16: + +```php +$psrCache->set('key', 'value', 3600); // zapisuje wartość na 1 godzinę +$value = $psrCache->get('key', 'default'); +``` + +Adapter obsługuje wszystkie metody zdefiniowane w PSR-16, w tym `getMultiple()`, `setMultiple()` i `deleteMultiple()`. + + +Buforowanie wyjścia +=================== + +Bardzo elegancko można przechwytywać i buforować wyjście: ```php if ($capture = $cache->capture($key)) { - echo ... // zrzucanie danych + echo ... // wypisujemy dane - $capture->end(); // buforowanie wyjścia + $capture->end(); // zapisujemy wyjście do cache } ``` -Jeśli wyjście jest już zbuforowane, metoda `capture()` wydrukuje je i zwróci `null`, więc warunek nie zostanie wykonany. W przeciwnym razie rozpoczyna przechwytywanie danych wyjściowych i zwraca obiekt `$capture`, który jest używany do ostatecznego buforowania danych wyjściowych. +W przypadku, gdy wyjście jest już zapisane w cache, metoda `capture()` je wypisuje i zwraca `null`, więc warunek `if` się nie wykona. W przeciwnym razie zaczyna przechwytywać wyjście i zwraca obiekt `$capture`, za pomocą którego ostatecznie zapisujemy wypisane dane do cache. .[note] -W wersji 3.0 metoda ta nosiła nazwę `$cache->start()`. +W wersji 3.0 metoda nazywała się `$cache->start()`. -Buforowanie w Latte .[#toc-caching-in-latte] -============================================ +Buforowanie w Latte +=================== -Buforowanie w szablonach [Latte |latte:] jest bardzo proste, wystarczy owinąć część szablonu tagami `{cache}...{/cache}`. Cache jest automatycznie unieważniany w momencie zmiany szablonu źródłowego (włączając w to wszelkie inlined szablony wewnątrz bloku cache). Znaczniki `{cache}` mogą być zagnieżdżone wewnątrz siebie, a kiedy zagnieżdżony blok zostanie unieważniony (na przykład przez znacznik), blok nadrzędny również zostanie unieważniony. +Buforowanie w szablonach [Latte|latte:] jest bardzo łatwe, wystarczy część szablonu otoczyć znacznikami `{cache}...{/cache}`. Cache jest automatycznie unieważniany w momencie zmiany szablonu źródłowego (w tym ewentualnych dołączonych szablonów wewnątrz bloku cache). Znaczniki `{cache}` można zagnieżdżać w sobie, a gdy zagnieżdżony blok zostanie unieważniony (na przykład przez tag), unieważniony zostanie również blok nadrzędny. -W tagu można określić klucze, z którymi będzie związany cache (tutaj zmienna `$id`) oraz ustawić [znaczniki |#Invalidation-Using-Tags] wygaśnięcia i [unieważnienia |#Invalidation-Using-Tags] +W znaczniku można podać klucze, do których będzie powiązany cache (tutaj zmienna `$id`) i ustawić wygaśnięcie oraz [tagi do unieważnienia |#Unieważnianie za pomocą tagów]. ```latte {cache $id, expire: '20 minutes', tags: [tag1, tag2]} @@ -299,9 +318,9 @@ W tagu można określić klucze, z którymi będzie związany cache (tutaj zmien {/cache} ``` -Wszystkie elementy są opcjonalne, więc nie musimy określać wygaśnięcia, tagów, czy wreszcie kluczy. +Wszystkie parametry są opcjonalne, więc nie musimy podawać ani wygaśnięcia, ani tagów, ani nawet kluczy. -Użycie cachingu może być również uwarunkowane przy użyciu `if` - zawartość będzie wtedy buforowana tylko wtedy, gdy warunek zostanie spełniony: +Użycie cache można również uzależnić za pomocą `if` - zawartość będzie wtedy buforowana tylko wtedy, gdy warunek zostanie spełniony: ```latte {cache $id, if: !$form->isSubmitted()} @@ -310,23 +329,23 @@ Użycie cachingu może być również uwarunkowane przy użyciu `if` - zawartoś ``` -Przechowywanie .[#toc-storages] -=============================== +Magazyny +======== -Repozytorium to obiekt reprezentujący miejsce, w którym dane są fizycznie przechowywane. Możemy wykorzystać bazę danych, serwer Memcached lub najbardziej dostępny storage, jakim są pliki na dysku. +Magazyn to obiekt reprezentujący miejsce, gdzie dane są fizycznie przechowywane. Możemy użyć bazy danych, serwera Memcached lub najłatwiej dostępnego magazynu, jakim są pliki na dysku. |----------------- -| Pamięć masowa | Opis +| Magazyn | Opis |----------------- -| [FileStorage |#FileStorage] | domyślny magazyn z przechowywaniem plików na dysku -| [MemcachedStorage |#MemcachedStorage] | używa serwera `Memcached` -| [PamięćZapasowa |#MemoryStorage] | dane są tymczasowo w pamięci -| [SQLiteStorage |#SQLiteStorage] | dane są przechowywane w bazie danych SQLite -| [DevNullStorage |#DevNullStorage] | dane nie są przechowywane, nadają się do testowania +| [#FileStorage] | domyślny magazyn z zapisem do plików na dysku +| [#MemcachedStorage] | wykorzystuje serwer `Memcached` +| [#MemoryStorage] | dane są tymczasowo w pamięci +| [#SQLiteStorage] | dane są zapisywane do bazy danych SQLite +| [#DevNullStorage] | dane nie są zapisywane, odpowiednie do testowania -Możesz uzyskać dostęp do obiektu storage, przekazując go przez [dependency injection |dependency-injection:passing-dependencies] z typem `Nette\Caching\Storage`. Jako domyślny magazyn, Nette dostarcza obiekt FileStorage, który przechowuje dane w podfolderze `cache` katalogu [plików tymczasowych |application:bootstrap#Temporary-Files]. +Do obiektu magazynu dostaniesz się, prosząc o jego przekazanie za pomocą [dependency injection |dependency-injection:passing-dependencies] z typem `Nette\Caching\Storage`. Jako domyślny magazyn Nette dostarcza obiekt `FileStorage` zapisujący dane do podkatalogu `cache` w katalogu dla [plików tymczasowych |application:bootstrapping#Pliki tymczasowe]. -Możesz zmienić repozytorium w konfiguracji: +Zmienić magazyn można w konfiguracji: ```neon services: @@ -334,31 +353,28 @@ services: ``` -FileStorage .[#toc-filestorage] -------------------------------- +FileStorage +----------- -Zapisuje cache do plików na dysku. Repozytorium `Nette\Caching\Storages\FileStorage` jest bardzo dobrze zoptymalizowane pod kątem wydajności i co najważniejsze zapewnia pełną atomowość operacji. Co to oznacza? Że podczas korzystania z cache nie może się zdarzyć, że czytasz plik, który nie jest jeszcze do końca napisany przez inny wątek, albo że ktoś "spod ręki" go skasuje. Tak więc korzystanie z pamięci podręcznej jest całkowicie bezpieczne. +Zapisuje cache do plików na dysku. Magazyn `Nette\Caching\Storages\FileStorage` jest bardzo dobrze zoptymalizowany pod kątem wydajności i przede wszystkim zapewnia pełną atomowość operacji. Co to oznacza? Że podczas używania cache nie może się zdarzyć, że odczytamy plik, który jeszcze nie został w całości zapisany przez inny wątek, lub że ktoś nam go "pod ręką" usunie. Użycie cache jest więc całkowicie bezpieczne. -Ta pamięć masowa ma również ważną wbudowaną funkcję, która zapobiega ekstremalnym skokom zużycia procesora, gdy pamięć podręczna jest usuwana lub jeszcze nie rozgrzana (tj. Utworzona). Jest to profilaktyka przed "stemplem pamięci podręcznej":https://en.wikipedia.org/wiki/Cache_stampede. -Dzieje się tak, że w jednym momencie zbiera się duża liczba współbieżnych żądań, które chcą tego samego z cache'u (np. wyniku drogiego zapytania SQL), a ponieważ nie ma cache'u, wszystkie procesy zaczynają wykonywać to samo zapytanie SQL. -Obciążenie mnoży się i może się nawet zdarzyć, że żaden wątek nie będzie w stanie odpowiedzieć w wyznaczonym czasie, pamięć podręczna nie zostanie utworzona i aplikacja się zawiesi. -Na szczęście cache w Nette działa w taki sposób, że gdy jest wiele współbieżnych żądań dla jednego elementu, tylko pierwszy wątek go generuje, pozostałe czekają, a następnie używają wygenerowanego wyniku. +Ten magazyn ma również wbudowaną ważną funkcję, która zapobiega ekstremalnemu wzrostowi zużycia procesora w momencie, gdy cache zostanie usunięty lub jeszcze nie jest rozgrzany (tj. utworzony). Jest to prewencja przed zjawiskiem "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Zdarza się, że w jednym momencie pojawi się większa liczba równoczesnych żądań, które chcą z cache tej samej rzeczy (np. wyniku kosztownego zapytania SQL), a ponieważ w pamięci podręcznej jej nie ma, wszystkie procesy zaczynają wykonywać to samo zapytanie SQL. Obciążenie się w ten sposób mnoży i może nawet dojść do sytuacji, że żaden wątek nie zdąży odpowiedzieć w limicie czasowym, cache się nie utworzy, a aplikacja ulegnie awarii. Na szczęście cache w Nette działa tak, że przy wielu równoczesnych żądaniach o jeden element generuje go tylko pierwszy wątek, pozostałe czekają, a następnie wykorzystują wygenerowany wynik. -Przykład tworzenia FileStorage: +Przykład utworzenia FileStorage: ```php -// repozytorium będzie katalog '/path/to/temp' na dysku +// magazynem będzie katalog '/path/to/temp' na dysku $storage = new Nette\Caching\Storages\FileStorage('/path/to/temp'); ``` -MemcachedStorage .[#toc-memcachedstorage] ------------------------------------------ +MemcachedStorage +---------------- -Serwer [Memcached |https://memcached.org] jest wysokowydajnym systemem rozproszonego składowania, którego adapterem jest `Nette\Caching\Storages\MemcachedStorage`. W konfiguracji należy podać adres IP i port, jeśli różni się od standardowego 11211. +Serwer [Memcached|https://memcached.org] to wysokowydajny system przechowywania w rozproszonej pamięci, którego adapterem jest `Nette\Caching\Storages\MemcachedStorage`. W konfiguracji podajemy adres IP i port, jeśli różni się od standardowego 11211. .[caution] -Wymaga on rozszerzenia PHP `memcached`. +Wymaga rozszerzenia PHP `memcached`. ```neon services: @@ -366,19 +382,19 @@ services: ``` -MemoryStorage .[#toc-memorystorage] ------------------------------------ +MemoryStorage +------------- -`Nette\Caching\Storages\MemoryStorage` jest repozytorium, które przechowuje dane w tablicy PHP, a zatem jest tracone po zakończeniu żądania. +`Nette\Caching\Storages\MemoryStorage` to magazyn, który przechowuje dane w tablicy PHP, a więc znikają one wraz z zakończeniem żądania. Jest przydatny głównie do testów lub w specyficznych przypadkach, gdy cache ma żyć tylko przez czas trwania jednego żądania. -SQLiteStorage .[#toc-sqlitestorage] ------------------------------------ +SQLiteStorage +------------- -Baza danych SQLite i adapter `Nette\Caching\Storages\SQLiteStorage` oferuje sposób przechowywania pamięci podręcznej w pojedynczym pliku na dysku. W konfiguracji podajemy ścieżkę do tego pliku. +Baza danych SQLite i adapter `Nette\Caching\Storages\SQLiteStorage` oferują sposób na przechowywanie cache w jednym pliku na disku. W konfiguracji podajemy ścieżkę do tego pliku. .[caution] -Wymaga on rozszerzeń PHP `pdo` i `pdo_sqlite`. +Wymaga rozszerzeń PHP `pdo` i `pdo_sqlite`. ```neon services: @@ -386,16 +402,16 @@ services: ``` -DevNullStorage .[#toc-devnullstorage] -------------------------------------- +DevNullStorage +-------------- -Specjalną implementacją repozytorium jest `Nette\Caching\Storages\DevNullStorage`, która w rzeczywistości w ogóle nie przechowuje danych. Dzięki temu nadaje się do testów, gdy chcemy wyeliminować efekt buforowania. +Specjalną implementacją magazynu jest `Nette\Caching\Storages\DevNullStorage`, który w rzeczywistości w ogóle nie przechowuje danych. Jest więc odpowiedni do testowania, gdy chcemy wyeliminować wpływ cache. -Używanie cache w kodzie .[#toc-using-cache-in-code] -=================================================== +Użycie cache w kodzie +===================== -Stosując caching w kodzie, mamy do dyspozycji dwa sposoby. Pierwszy polega na tym, że przekazujemy do repozytorium [dependency injection |dependency-injection:passing-dependencies] i tworzymy obiekt `Cache`: +Przy używaniu cache w kodzie mamy dwa sposoby. Pierwszy z nich polega na tym, że prosimy o przekazanie magazynu za pomocą [dependency injection |dependency-injection:passing-dependencies] i tworzymy obiekt `Cache`: ```php use Nette; @@ -411,7 +427,7 @@ class ClassOne } ``` -Drugi polega na tym, że każemy im przekazać bezpośrednio obiekt `Cache`: +Druga możliwość polega na tym, że prosimy o przekazanie od razu obiektu `Cache`: ```php class ClassTwo @@ -423,7 +439,7 @@ class ClassTwo } ``` -Obiekt `Cache` jest w ten sposób tworzony bezpośrednio w konfiguracji: +Obiekt `Cache` jest następnie tworzony bezpośrednio w konfiguracji w ten sposób: ```neon services: @@ -431,12 +447,12 @@ services: ``` -Dziennik .[#toc-journal] -======================== +Journal +======= -Nette przechowuje tagi i priorytety w dzienniku. Domyślnie używany jest do tego SQLite i plik `journal.s3db`, a ** wymagane są rozszerzenia PHP `pdo` i `pdo_sqlite`.** +Nette przechowuje tagi i priorytety w tzw. journalu. Standardowo używa się do tego SQLite i pliku `journal.s3db` i **wymagane są rozszerzenia PHP `pdo` i `pdo_sqlite`.** -Dziennik można zmienić w konfiguracji: +Zmienić journal można w konfiguracji: ```neon services: @@ -444,4 +460,25 @@ services: ``` -{{leftbar: nette:@menu-topics}} +Usługi DI +========= + +Te usługi są dodawane do kontenera DI: + +| Nazwa | Typ | Opis +|---------------------------------------------------------- +| `cache.journal` | [api:Nette\Caching\Storages\Journal] | journal +| `cache.storage` | [api:Nette\Caching\Storage] | magazyn + + +Wyłączenie cache +================ + +Jedną z możliwości wyłączenia cache w aplikacji jest ustawienie jako magazynu [#DevNullStorage]: + +```neon +services: + cache.storage: Nette\Caching\Storages\DevNullStorage +``` + +To ustawienie nie ma wpływu na buforowanie szablonów w Latte ani kontenera DI, ponieważ te biblioteki nie korzystają z usług `nette/caching` i zarządzają swoją pamięcią podręczną samodzielnie. Ich cache zresztą [nie trzeba wyłączać |nette:troubleshooting#Jak wyłączyć cache podczas developmentu] w trybie deweloperskim. diff --git a/caching/pl/@meta.texy b/caching/pl/@meta.texy new file mode 100644 index 0000000000..08f2227fb5 --- /dev/null +++ b/caching/pl/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Dokumentacja Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/caching/pt/@home.texy b/caching/pt/@home.texy index d98693b714..82c8cbb29f 100644 --- a/caching/pt/@home.texy +++ b/caching/pt/@home.texy @@ -1,46 +1,46 @@ -Caching -******* +Nette Caching +************* <div class=perex> -O Cache acelera sua aplicação armazenando os dados - uma vez que sejam difíceis de recuperar - para uso futuro. Nós lhe mostraremos: +A Cache acelera sua aplicação armazenando dados obtidos com dificuldade uma vez para uso futuro. Mostraremos: -- Como usar o cache -- Como mudar o armazenamento do cache -- Como invalidar adequadamente o cache +- como usar a cache +- como alterar o armazenamento +- como invalidar corretamente a cache </div> -O uso do cache é muito fácil em Nette, enquanto que ele também cobre necessidades de cache muito avançadas. Ele é projetado para desempenho e 100% de durabilidade. Basicamente, você encontrará adaptadores para o armazenamento backend mais comum. Permite a invalidação baseada em tags, proteção de carimbos de cache, expiração de tempo, etc. +Usar a cache no Nette é muito fácil, mas cobre até mesmo as necessidades mais avançadas. É projetado para desempenho e 100% de resiliência. Basicamente, você encontrará adaptadores para os armazenamentos de backend mais comuns. Permite invalidação baseada em tags, expiração por tempo, tem proteção contra cache stampede, etc. -Instalação .[#toc-installation] -=============================== +Instalação +========== -Baixe e instale o pacote usando [o Composer |best-practices:composer]: +Faça o download e instale a biblioteca usando o [Composer|best-practices:composer]: ```shell composer require nette/caching ``` -Utilização básica .[#toc-basic-usage] -===================================== +Uso Básico +========== -O centro de trabalho com o cache é o objeto [api:Nette\Caching\Cache]. Criamos sua instância e passamos o chamado armazenamento para o construtor como parâmetro. Que é um objeto que representa o local onde os dados serão armazenados fisicamente (banco de dados, Memcached, arquivos em disco, ...). Você obtém o objeto de armazenamento passando-o usando a [injeção de dependência |dependency-injection:passing-dependencies] com o tipo `Nette\Caching\Storage`. Você encontrará tudo o que é essencial na [seção Armazenamento |#Storages]. +O centro do trabalho com a cache é o objeto [api:Nette\Caching\Cache]. Criamos sua instância e passamos o chamado armazenamento como parâmetro para o construtor. Este é um objeto que representa o local onde os dados serão fisicamente armazenados (banco de dados, Memcached, arquivos em disco, ...). Acessamos o armazenamento pedindo que ele seja passado usando [injeção de dependência |dependency-injection:passing-dependencies] com o tipo `Nette\Caching\Storage`. Tudo o essencial pode ser encontrado na [seção Armazenamentos |#Armazenamentos]. .[warning] -Na versão 3.0, a interface ainda tinha o `I` prefix, so the name was `Nette\Caching\IStorage`. Além disso, as constantes da classe `Cache` foram capitalizadas, portanto, por exemplo `Cache::EXPIRE` em vez de `Cache::Expire`. +Na versão 3.0, a interface ainda tinha o prefixo `I`, então o nome era `Nette\Caching\IStorage`. Além disso, as constantes da classe `Cache` eram escritas em maiúsculas, como `Cache::EXPIRE` em vez de `Cache::Expire`. -Para os exemplos a seguir, suponha que tenhamos um pseudônimo `Cache` e um armazenamento na variável `$storage`. +Para os exemplos a seguir, suponha que temos um alias `Cache` criado e o armazenamento na variável `$storage`. ```php use Nette\Caching\Cache; -$storage = /* ... */; // instância de armazenamento Nette +$storage = /* ... */; // instance of Nette\Caching\Storage ``` -O cache é, na verdade, uma *loja de valores-chave*, portanto, lemos e escrevemos dados sob chaves, assim como as matrizes associativas. As aplicações consistem em várias partes independentes, e se todas elas usassem um armazenamento (para idéia: um diretório em um disco), mais cedo ou mais tarde haveria uma colisão de chaves. O Nette Framework resolve o problema dividindo o espaço inteiro em espaços de nomes (subdiretórios). Cada parte do programa utiliza então seu próprio espaço com um nome único e nenhuma colisão pode ocorrer. +A cache é na verdade um *key–value store*, ou seja, lemos e escrevemos dados sob chaves, assim como em arrays associativos. As aplicações consistem em várias partes independentes e, se todas usassem um único armazenamento (imagine um único diretório no disco), mais cedo ou mais tarde ocorreria uma colisão de chaves. O Nette Framework resolve o problema dividindo todo o espaço em namespaces (subdiretórios). Cada parte do programa então usa seu próprio espaço com um nome único e nenhuma colisão pode ocorrer. O nome do espaço é especificado como o segundo parâmetro do construtor da classe Cache: @@ -48,43 +48,43 @@ O nome do espaço é especificado como o segundo parâmetro do construtor da cla $cache = new Cache($storage, 'Full Html Pages'); ``` -Agora podemos usar o objeto `$cache` para ler e escrever a partir do cache. O método `load()` é usado para ambos. O primeiro argumento é a chave e o segundo é a chamada de retorno do PHP, que é chamada quando a chave não é encontrada no cache. A chamada de retorno gera um valor, devolve-o e o armazena em cache: +Agora podemos usar o objeto `$cache` para ler e escrever na cache. O método `load()` serve para ambos. O primeiro argumento é a chave e o segundo é um callback PHP, que é chamado quando a chave não é encontrada na cache. O callback gera o valor, retorna-o e ele é armazenado na cache: ```php $value = $cache->load($key, function () use ($key) { - $computedValue = /* ... */; // cálculos pesados + $computedValue = /* ... */; // cálculo intensivo return $computedValue; }); ``` -Se o segundo parâmetro não for especificado `$value = $cache->load($key)`, o `null` é devolvido se o item não estiver no cache. +Se o segundo parâmetro não for especificado `$value = $cache->load($key)`, `null` será retornado se o item não estiver na cache. .[tip] -O grande problema é que quaisquer estruturas serializáveis podem ser colocadas em cache, não apenas cordas. E o mesmo se aplica às chaves. +O bom é que qualquer estrutura serializável pode ser armazenada na cache, não precisa ser apenas strings. E o mesmo se aplica até mesmo às chaves. -O item é removido do cache usando o método `remove()`: +Removemos um item da cache usando o método `remove()`: ```php $cache->remove($key); ``` -Você também pode armazenar um item usando o método `$cache->save($key, $value, array $dependencies = [])`. Entretanto, o método acima usando `load()` é o preferido. +Também é possível salvar um item na cache usando o método `$cache->save($key, $value, array $dependencies = [])`. No entanto, o método preferido é o mencionado acima usando `load()`. -Memoization .[#toc-memoization] -=============================== +Memoização +========== -Memoization significa memorizar o resultado de uma função ou método para que você possa usá-lo da próxima vez em vez de calcular a mesma coisa repetidamente. +Memoização significa armazenar em cache o resultado de uma chamada de função ou método para que você possa usá-lo na próxima vez sem calcular a mesma coisa repetidamente. -Os métodos e funções podem ser chamados de memotize utilizando `call(callable $callback, ...$args)`: +Métodos e funções podem ser chamados com memoização usando `call(callable $callback, ...$args)`: ```php $result = $cache->call('gethostbyaddr', $ip); ``` -A função `gethostbyaddr()` é chamada apenas uma vez para cada parâmetro `$ip` e na próxima vez o valor do cache será devolvido. +A função `gethostbyaddr()` será chamada apenas uma vez para cada parâmetro `$ip` e, na próxima vez, o valor da cache será retornado. -Também é possível criar uma embalagem memorizada para um método ou função que pode ser chamada mais tarde: +Também é possível criar um invólucro memoizado em torno de um método ou função que pode ser chamado posteriormente: ```php function factorial($num) @@ -94,17 +94,17 @@ function factorial($num) $memoizedFactorial = $cache->wrap('factorial'); -$result = $memoizedFactorial(5); // conta-o -$result = $memoizedFactorial(5); // devolve-o do cache +$result = $memoizedFactorial(5); // calcula pela primeira vez +$result = $memoizedFactorial(5); // pela segunda vez, da cache ``` -Expiração & Invalidação .[#toc-expiration-invalidation] -======================================================= +Expiração & Invalidação +======================= -Com o cache, é necessário abordar a questão de que alguns dos dados salvos anteriormente se tornarão inválidos com o tempo. Nette Framework fornece um mecanismo, como limitar a validade dos dados e como apagá-los de forma controlada ("invalidá-los", usando a terminologia do framework). +Ao armazenar em cache, é necessário resolver a questão de quando os dados armazenados anteriormente se tornam inválidos. O Nette Framework oferece um mecanismo para limitar a validade dos dados ou excluí-los de forma controlada (na terminologia do framework, "invalidar"). -A validade dos dados é definida no momento da gravação usando o terceiro parâmetro do método `save()`, por exemplo: +A validade dos dados é definida no momento do armazenamento usando o terceiro parâmetro do método `save()`, por exemplo: ```php $cache->save($key, $value, [ @@ -112,7 +112,7 @@ $cache->save($key, $value, [ ]); ``` -Ou usando o parâmetro `$dependencies` passado por referência ao retorno de chamada no método `load()`, por exemplo: +Ou usando o parâmetro `$dependencies` passado por referência para o callback do método `load()`, por exemplo: ```php $value = $cache->load($key, function (&$dependencies) { @@ -121,7 +121,7 @@ $value = $cache->load($key, function (&$dependencies) { }); ``` -Ou usando o 3º parâmetro no método `load()`, por exemplo +Ou usando o 3º parâmetro no método `load()`, que define as dependências se o item for gerado: ```php $value = $cache->load($key, function () { @@ -129,40 +129,40 @@ $value = $cache->load($key, function () { }, [Cache::Expire => '20 minutes']); ``` -Nos exemplos a seguir, assumiremos a segunda variante e, portanto, a existência de uma variável `$dependencies`. +Nos exemplos a seguir, assumiremos a segunda variante e, portanto, a existência da variável `$dependencies`. -Validade .[#toc-expiration] ---------------------------- +Expiração +--------- -A expiração mais simples é o limite de tempo. Veja aqui como armazenar dados válidos por 20 minutos: +A expiração mais simples é um limite de tempo. Desta forma, armazenamos dados na cache com validade de 20 minutos: ```php -// também aceita o número de segundos ou o timestamp UNIX +// também aceita número de segundos ou timestamp UNIX $dependencies[Cache::Expire] = '20 minutes'; ``` -Se quisermos prolongar o período de validade com cada leitura, isto pode ser conseguido desta forma, mas cuidado, isto aumentará a sobrecarga do cache: +Se quisermos estender o período de validade a cada leitura, isso pode ser alcançado da seguinte forma, mas atenção, a sobrecarga da cache aumentará: ```php $dependencies[Cache::Sliding] = true; ``` -A opção útil é a capacidade de deixar os dados expirarem quando um determinado arquivo é alterado ou um de vários arquivos. Isto pode ser usado, por exemplo, para armazenar os dados resultantes da procissão destes arquivos. Use caminhos absolutos. +Uma opção útil é deixar os dados expirarem quando um arquivo ou um de vários arquivos for alterado. Isso pode ser usado, por exemplo, ao armazenar na cache dados gerados pelo processamento desses arquivos. Use caminhos absolutos. ```php -$dependencies[Cache::Files] = '/caminho/para/dados.yaml'; +$dependencies[Cache::Files] = '/path/to/data.yaml'; // ou -$dependencies[Cache::Files] = ['/caminho/para/dados1.yaml', '/caminho/para/dados2.yaml']; +$dependencies[Cache::Files] = ['/path/to/data1.yaml', '/path/to/data2.yaml']; ``` -Podemos deixar um item no cache expirar quando outro item (ou um de vários outros) expirar. Isto pode ser usado quando armazenamos a página HTML inteira e fragmentos dela em cache sob outras chaves. Uma vez que o snippet muda, a página inteira se torna inválida. Se tivermos fragmentos armazenados sob chaves como `frag1` e `frag2`, nós usaremos: +Podemos deixar um item na cache expirar quando outro item (ou um de vários outros) expirar. Isso pode ser usado quando armazenamos, por exemplo, uma página HTML inteira na cache e seus fragmentos sob outras chaves. Assim que um fragmento muda, a página inteira é invalidada. Se tivermos fragmentos armazenados sob chaves como `frag1` e `frag2`, usamos: ```php $dependencies[Cache::Items] = ['frag1', 'frag2']; ``` -A expiração também pode ser controlada usando funções personalizadas ou métodos estáticos, que sempre decidem ao ler se o item ainda é válido. Por exemplo, podemos deixar o item expirar sempre que a versão PHP mudar. Criaremos uma função que compara a versão atual com o parâmetro, e ao salvar adicionaremos um array na forma `[function name, ...arguments]` para as dependências: +A expiração também pode ser controlada usando funções personalizadas ou métodos estáticos, que sempre decidem na leitura se o item ainda é válido. Desta forma, por exemplo, podemos deixar um item expirar sempre que a versão do PHP mudar. Criamos uma função que compara a versão atual com um parâmetro e, ao salvar, adicionamos um array no formato `[callable, ...argumentos]` entre as dependências: ```php function checkPhpVersion($ver): bool @@ -171,11 +171,11 @@ function checkPhpVersion($ver): bool } $dependencies[Cache::Callbacks] = [ - ['checkPhpVersion', PHP_VERSION_ID] // expira quando checkPhpVersion(...) === falso + ['checkPhpVersion', PHP_VERSION_ID] // expirar quando checkPhpVersion(...) === false ]; ``` -Naturalmente, todos os critérios podem ser combinados. O cache então expira quando pelo menos um critério não é atendido. +Todos os critérios podem, obviamente, ser combinados. A cache então expirará quando pelo menos um critério não for atendido. ```php $dependencies[Cache::Expire] = '20 minutes'; @@ -183,16 +183,16 @@ $dependencies[Cache::Files] = '/path/to/data.yaml'; ``` -Invalidação usando etiquetas .[#toc-invalidation-using-tags] ------------------------------------------------------------- +Invalidação usando tags +----------------------- -As etiquetas são uma ferramenta de invalidação muito útil. Podemos atribuir uma lista de tags, que são strings arbitrárias, a cada item armazenado no cache. Por exemplo, suponha que tenhamos uma página HTML com um artigo e comentários, que desejamos que seja armazenada em cache. Assim, especificamos as tags ao salvar em cache: +Uma ferramenta de invalidação muito útil são as chamadas tags. Podemos atribuir uma lista de tags a cada item na cache, que são strings arbitrárias. Por exemplo, tenhamos uma página HTML com um artigo e comentários que iremos armazenar em cache. Ao salvar, especificamos as tags: ```php $dependencies[Cache::Tags] = ["article/$articleId", "comments/$articleId"]; ``` -Agora, vamos passar para a administração. Aqui temos um formulário para a edição de artigos. Junto com salvar o artigo em um banco de dados, chamamos o comando `clean()`, que apagará os artigos em cache por tag: +Vamos para a administração. Aqui encontramos um formulário para editar o artigo. Juntamente com o salvamento do artigo no banco de dados, chamamos o comando `clean()`, que exclui itens da cache por tag: ```php $cache->clean([ @@ -200,7 +200,7 @@ $cache->clean([ ]); ``` -Da mesma forma, no lugar de acrescentar um novo comentário (ou editar um comentário), não esqueceremos de invalidar a etiqueta relevante: +Da mesma forma, no local de adição de um novo comentário (ou edição de um comentário), não nos esquecemos de invalidar a tag apropriada: ```php $cache->clean([ @@ -208,22 +208,22 @@ $cache->clean([ ]); ``` -O que conseguimos? Que nosso cache HTML será invalidado (excluído) sempre que o artigo ou comentários forem alterados. Ao editar um artigo com ID = 10, a tag `article/10` é forçada a ser invalidada e a página HTML com a tag é excluída do cache. O mesmo acontece quando você insere um novo comentário sob o artigo relevante. +O que alcançamos com isso? Que nossa cache HTML será invalidada (excluída) sempre que o artigo ou os comentários forem alterados. Quando um artigo com ID = 123 é editado, a tag `article/123` é invalidada à força e a página HTML que carrega a tag mencionada é excluída da cache. O mesmo acontece ao inserir um novo comentário sob o artigo relevante. .[note] -As etiquetas exigem o [Journal |#Journal]. +Tags requerem o chamado [#Journal]. -Invalidação por Prioridade .[#toc-invalidation-by-priority] ------------------------------------------------------------ +Invalidação usando prioridade +----------------------------- -Podemos definir a prioridade para itens individuais no cache, e será possível apagá-los de forma controlada quando, por exemplo, o cache exceder um determinado tamanho: +Podemos definir uma prioridade para itens individuais na cache, que pode ser usada para excluí-los quando, por exemplo, a cache exceder um determinado tamanho: ```php $dependencies[Cache::Priority] = 50; ``` -Eliminar todos os itens com prioridade igual ou inferior a 100: +Excluiremos todos os itens com prioridade igual ou menor que 100: ```php $cache->clean([ @@ -232,13 +232,13 @@ $cache->clean([ ``` .[note] -As prioridades exigem o chamado [Diário |#Journal]. +Prioridades requerem o chamado [#Journal]. -Cache claro .[#toc-clear-cache] -------------------------------- +Limpar a cache +-------------- -O parâmetro `Cache::All` limpa tudo: +O parâmetro `Cache::All` exclui tudo: ```php $cache->clean([ @@ -247,51 +247,70 @@ $cache->clean([ ``` -Leitura a granel .[#toc-bulk-reading] -===================================== +Leitura em massa +================ -Para leitura e escrita em massa em cache, é usado o método `bulkLoad()`, onde passamos uma série de chaves e obtemos uma série de valores: +Para leituras e escritas em massa na cache, usamos o método `bulkLoad()`, ao qual passamos um array de chaves e obtemos um array de valores (chave => valor): ```php $values = $cache->bulkLoad($keys); ``` -O método `bulkLoad()` funciona de forma semelhante ao `load()` com o segundo parâmetro de retorno de chamada, para o qual a chave do item gerado é passada: +O método `bulkLoad()` funciona de forma semelhante a `load()`, também com o segundo parâmetro callback, ao qual é passada a chave do item gerado: ```php $values = $cache->bulkLoad($keys, function ($key, &$dependencies) { - $computedValue = /* ... */; // cálculos pesados + $computedValue = /* ... */; // cálculo intensivo return $computedValue; }); ``` -Caching de saída .[#toc-output-caching] -======================================= +Uso com PSR-16 .{data-version:3.3.1} +==================================== + +Para usar a Nette Cache com a interface PSR-16, você pode utilizar o adaptador `PsrCacheAdapter`. Ele permite uma integração perfeita entre a Nette Cache e qualquer código ou biblioteca que espera uma cache compatível com PSR-16. + +```php +$psrCache = new Nette\Bridges\Psr\PsrCacheAdapter($storage); +``` + +Agora você pode usar `$psrCache` como uma cache PSR-16: + +```php +$psrCache->set('key', 'value', 3600); // armazena o valor por 1 hora +$value = $psrCache->get('key', 'default'); +``` + +O adaptador suporta todos os métodos definidos em PSR-16, incluindo `getMultiple()`, `setMultiple()` e `deleteMultiple()`. Note que namespaces e dependências complexas (tags, prioridade, etc.) do Nette Cache não são diretamente expostos pela interface PSR-16. + + +Armazenamento em cache da saída +=============================== -A saída pode ser capturada e armazenada em cache de forma muito elegante: +É muito elegante capturar e armazenar em cache a saída: ```php if ($capture = $cache->capture($key)) { - echo ... // imprimir alguns dados + echo ... // imprimimos os dados - $capture->end(); // salvar a saída para o cache + $capture->end(); // salvamos a saída na cache } ``` -Caso a saída já esteja presente no cache, o método `capture()` imprime-a e retorna `null`, de modo que a condição não será executada. Caso contrário, ele começa a armazenar a saída e retorna o objeto `$capture`, usando o qual finalmente salvamos os dados no cache. +Caso a saída já esteja armazenada na cache, o método `capture()` a imprimirá e retornará `null`, portanto a condição não será executada. Caso contrário, ele começará a capturar a saída e retornará o objeto `$capture`, com o qual finalmente salvamos os dados impressos na cache. .[note] -Na versão 3.0, o método foi chamado `$cache->start()`. +Na versão 3.0, o método era chamado `$cache->start()`. -Caching em Latte .[#toc-caching-in-latte] -========================================= +Armazenamento em cache no Latte +=============================== -O cache em modelos [Latte |latte:] é muito fácil, basta embrulhar parte do modelo com tags `{cache}...{/cache}`. O cache é automaticamente invalidado quando o modelo fonte muda (incluindo quaisquer modelos incluídos dentro das tags `{cache}` ). As tags `{cache}` podem ser aninhadas, e quando um bloco aninhado é invalidado (por exemplo, por uma tag), o bloco pai também é invalidado. +Armazenar em cache nos templates [Latte|latte:] é muito fácil, basta envolver a parte do template com as tags `{cache}...{/cache}`. A cache é invalidada automaticamente quando o template de origem é alterado (incluindo quaisquer templates incluídos dentro do bloco de cache). As tags `{cache}` podem ser aninhadas e, quando um bloco aninhado é invalidado (por exemplo, por uma tag), o bloco pai também é invalidado. -Na etiqueta é possível especificar as chaves às quais o cache será vinculado (aqui a variável `$id`) e definir as [etiquetas de |#Invalidation using Tags] expiração e [invalidação |#Invalidation using Tags] +Na tag, é possível especificar as chaves às quais a cache estará vinculada (aqui a variável `$id`) e definir a expiração e as [tags para invalidação |#Invalidação usando tags]. ```latte {cache $id, expire: '20 minutes', tags: [tag1, tag2]} @@ -299,9 +318,9 @@ Na etiqueta é possível especificar as chaves às quais o cache será vinculado {/cache} ``` -Todos os parâmetros são opcionais, portanto não é necessário especificar a expiração, etiquetas ou chaves. +Todos os itens são opcionais, portanto não precisamos especificar nem a expiração, nem as tags, e finalmente nem as chaves. -O uso do cache também pode ser condicionado por `if` - o conteúdo será então armazenado em cache somente se a condição for atendida: +O uso da cache também pode ser condicionado usando `if` - o conteúdo será então armazenado em cache apenas se a condição for atendida: ```latte {cache $id, if: !$form->isSubmitted()} @@ -310,21 +329,21 @@ O uso do cache também pode ser condicionado por `if` - o conteúdo será então ``` -Armazenagens .[#toc-storages] -============================= +Armazenamentos +============== -Um armazenamento é um objeto que representa o local onde os dados são fisicamente armazenados. Podemos usar um banco de dados, um servidor Memcached ou o armazenamento mais disponível, que são arquivos em disco. +Um armazenamento é um objeto que representa o local onde os dados são fisicamente armazenados. Podemos usar um banco de dados, um servidor Memcached ou o armazenamento mais acessível, que são arquivos em disco. -|---------------------- -| Armazenamento | Descrição -|---------------------- -| [FileStorage |#FileStorage] | armazenamento padrão com gravação em arquivos em disco -| [MemcachedStorage |#MemcachedStorage] | utiliza o servidor `Memcached` -| [MemoryStorage |#MemoryStorage] | os dados estão temporariamente na memória -| [SQLite |#SQLiteStorage] Os dados são armazenados no banco de dados -| [DevNullStorage |#DevNullStorage] | os dados não são armazenados - para fins de teste +|--------------------- |------------------------------------------------------- +| Armazenamento | Descrição +|--------------------- |------------------------------------------------------- +| [#FileStorage] | Armazenamento padrão, salva em arquivos no disco. +| [#MemcachedStorage] | Utiliza um servidor [Memcached|https://memcached.org]. +| [#MemoryStorage] | Os dados ficam temporariamente na memória (por requisição). +| [#SQLiteStorage] | Os dados são salvos em um banco de dados SQLite. +| [#DevNullStorage] | Os dados não são salvos, útil para testes. -Você obtém o objeto de armazenamento passando-o usando a [injeção de dependência |dependency-injection:passing-dependencies] com o tipo `Nette\Caching\Storage`. Por padrão, a Nette fornece um objeto FileStorage que armazena dados em uma subpasta `cache` no diretório para [arquivos temporários |application:bootstrap#Temporary Files]. +Você acessa o objeto de armazenamento padrão pedindo que ele seja passado usando [injeção de dependência |dependency-injection:passing-dependencies] com o tipo `Nette\Caching\Storage`. Como armazenamento padrão, o Nette fornece o objeto `FileStorage` que armazena dados no subdiretório `cache` no diretório para [arquivos temporários |application:bootstrapping#Arquivos temporários]. Você pode alterar o armazenamento na configuração: @@ -334,31 +353,28 @@ services: ``` -FileStorage .[#toc-filestorage] -------------------------------- +FileStorage +----------- -Escreve o cache em arquivos em disco. O armazenamento `Nette\Caching\Storages\FileStorage` é muito bem otimizado para o desempenho e, acima de tudo, garante a atomicidade total das operações. O que isso significa? Que ao usar o cache, não pode acontecer que lemos um arquivo que ainda não tenha sido completamente escrito por outro fio, ou que alguém o apagaria "sob suas mãos". O uso do cache é, portanto, completamente seguro. +Grava a cache em arquivos no disco. O armazenamento `Nette\Caching\Storages\FileStorage` é muito bem otimizado para desempenho e, acima de tudo, garante total atomicidade das operações. O que isso significa? Que ao usar a cache, não pode acontecer de lermos um arquivo que ainda não foi completamente escrito por outro processo, ou que alguém o exclua "enquanto estamos usando". O uso da cache é, portanto, completamente seguro. -Este armazenamento também tem uma importante característica incorporada que impede um aumento extremo no uso da CPU quando o cache é limpo ou frio (ou seja, não criado). Isto é prevenção de "debandada do cache:https://en.wikipedia.org/wiki/Cache_stampede ". -Acontece que em um momento há várias solicitações simultâneas que querem a mesma coisa do cache (por exemplo, o resultado de uma consulta SQL cara) e, como não está em cache, todos os processos começam a executar a mesma consulta SQL. -A carga do processador é multiplicada e pode até acontecer que nenhuma thread possa responder dentro do limite de tempo, o cache não é criado e a aplicação trava. -Felizmente, o cache em Nette funciona de tal forma que quando há várias solicitações simultâneas para um item, ele é gerado apenas pelo primeiro thread, os outros esperam e depois usam o resultado gerado. +Este armazenamento também possui uma função importante integrada que evita um aumento extremo no uso da CPU quando a cache é excluída ou ainda não está aquecida (ou seja, criada). Esta é uma prevenção contra o "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Acontece que, em um determinado momento, um número maior de requisições simultâneas chega, querendo a mesma coisa da cache (por exemplo, o resultado de uma consulta SQL cara) e, como não está na cache, todos os processos começam a executar a mesma consulta SQL. A carga é assim multiplicada e pode até acontecer que nenhum processo consiga responder dentro do limite de tempo, a cache não seja criada e a aplicação entre em colapso. Felizmente, a cache no Nette funciona de forma que, com várias requisições simultâneas para um item, ele é gerado apenas pelo primeiro processo, os outros esperam e então usam o resultado gerado. -Exemplo de criação de um FileStorage: +Exemplo de criação manual de FileStorage (geralmente feito via DI): ```php -// o armazenamento será o diretório '/caminho/para/temp' no disco +// o armazenamento será o diretório '/path/to/temp' no disco $storage = new Nette\Caching\Storages\FileStorage('/path/to/temp'); ``` -MemcachedStorage .[#toc-memcachedstorage] ------------------------------------------ +MemcachedStorage +---------------- -O servidor [Memcached |https://memcached.org] é um sistema de armazenamento distribuído de alto desempenho cujo adaptador é `Nette\Caching\Storages\MemcachedStorage`. Na configuração, especifique o endereço IP e a porta, caso seja diferente do padrão 11211. +O servidor [Memcached|https://memcached.org] é um sistema de armazenamento em memória distribuída de alto desempenho, cujo adaptador é `Nette\Caching\Storages\MemcachedStorage`. Na configuração, especificamos o endereço IP e a porta, se for diferente do padrão 11211. .[caution] -Requer extensão PHP `memcached`. +Requer a extensão PHP `memcached`. ```neon services: @@ -366,19 +382,19 @@ services: ``` -MemoryStorage .[#toc-memorystorage] ------------------------------------ +MemoryStorage +------------- -`Nette\Caching\Storages\MemoryStorage` é um armazenamento que armazena dados em uma matriz PHP e, portanto, se perde quando a solicitação é encerrada. +`Nette\Caching\Storages\MemoryStorage` é um armazenamento que guarda dados em um array PHP e, portanto, são perdidos com o término da requisição. -SQLiteStorage .[#toc-sqlitestorage] ------------------------------------ +SQLiteStorage +------------- -O banco de dados SQLite e o adaptador `Nette\Caching\Storages\SQLiteStorage` oferecem uma maneira de fazer o cache em um único arquivo em disco. A configuração irá especificar o caminho para este arquivo. +O banco de dados SQLite e o adaptador `Nette\Caching\Storages\SQLiteStorage` oferecem uma maneira de armazenar a cache em um único arquivo no disco. Na configuração, especificamos o caminho para este arquivo. .[caution] -Requer extensões PHP `pdo` e `pdo_sqlite`. +Requer as extensões PHP `pdo` e `pdo_sqlite`. ```neon services: @@ -386,16 +402,16 @@ services: ``` -DevNullStorage .[#toc-devnullstorage] -------------------------------------- +DevNullStorage +-------------- -Uma implementação especial de armazenamento é `Nette\Caching\Storages\DevNullStorage`, que na verdade não armazena dados de forma alguma. Portanto, é adequado para testes se quisermos eliminar o efeito do cache. +Uma implementação especial de armazenamento é `Nette\Caching\Storages\DevNullStorage`, que na verdade não armazena dados. É, portanto, adequado para testes ou para desativar completamente a cache. -Usando Cache em Código .[#toc-using-cache-in-code] -================================================== +Uso da cache no código +====================== -Ao utilizar o cache em código, você tem duas maneiras de fazer isso. A primeira é que você obtém o objeto de armazenamento passando-o usando a [injeção de dependência |dependency-injection:passing-dependencies] e depois cria um objeto `Cache`: +Ao usar a cache no código, temos duas maneiras de fazer isso. A primeira é pedir que o armazenamento seja passado usando [injeção de dependência |dependency-injection:passing-dependencies] e criar o objeto `Cache`: ```php use Nette; @@ -411,7 +427,7 @@ class ClassOne } ``` -A segunda maneira é que você obtenha o objeto de armazenamento `Cache`: +A segunda opção é pedir que o objeto `Cache` seja passado diretamente: ```php class ClassTwo @@ -423,7 +439,7 @@ class ClassTwo } ``` -O objeto `Cache` é então criado diretamente na configuração como se segue: +O objeto `Cache` é então criado diretamente na configuração desta forma: ```neon services: @@ -431,12 +447,12 @@ services: ``` -Jornal .[#toc-journal] -====================== +Journal +======= -A Nette armazena etiquetas e prioridades em uma chamada revista. Por padrão, SQLite e arquivo `journal.s3db` são usados para isso, e **São necessárias extensões PHP `pdo` e `pdo_sqlite`.** +Nette armazena tags e prioridades no chamado journal. Por padrão, o SQLite e o arquivo `journal.s3db` são usados para isso e **são necessárias as extensões PHP `pdo` e `pdo_sqlite`.** -Você pode alterar a configuração da revista: +Você pode alterar o journal na configuração: ```neon services: @@ -444,4 +460,25 @@ services: ``` -{{leftbar: nette:@menu-topics}} +Serviços DI +=========== + +Estes serviços são adicionados ao contêiner DI: + +| Nome | Tipo | Descrição +|-----------------|------------------------------------------|--------------------------------------------------- +| `cache.storage` | `Nette\Caching\Storage` | O serviço de armazenamento de cache padrão (geralmente FileStorage). +| `cache.journal` | `Nette\Caching\Storages\Journal` | O journal padrão (geralmente SQLiteJournal). + + +Desativar a cache +================= + +Uma das opções para desativar a cache na aplicação é definir o armazenamento como [#DevNullStorage]: + +```neon +services: + cache.storage: Nette\Caching\Storages\DevNullStorage +``` + +Esta configuração não afeta o armazenamento em cache de templates no Latte ou no contêiner DI, pois essas bibliotecas não usam os serviços nette/caching e gerenciam sua própria cache de forma independente. Afinal, a cache delas [não precisa ser desativada |nette:troubleshooting#Como desativar o cache durante o desenvolvimento] no modo de desenvolvimento. diff --git a/caching/pt/@meta.texy b/caching/pt/@meta.texy new file mode 100644 index 0000000000..e2566bcb44 --- /dev/null +++ b/caching/pt/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Documentação Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/caching/ro/@home.texy b/caching/ro/@home.texy index 54178354e6..2bff23a31e 100644 --- a/caching/ro/@home.texy +++ b/caching/ro/@home.texy @@ -1,38 +1,38 @@ -Caching -******* +Nette Caching +************* <div class=perex> -Memoria cache accelerează aplicația dumneavoastră prin stocarea datelor - odată ce au fost recuperate - pentru utilizare ulterioară. Vă vom arăta: +Cache-ul accelerează aplicația dvs. salvând datele obținute cu efort pentru utilizare ulterioară. Vom arăta: -- Cum se utilizează memoria cache -- Cum să modificați memoria cache -- Cum să invalidați corect memoria cache +- cum să utilizați cache-ul +- cum să schimbați stocarea +- cum să invalidați corect cache-ul </div> -Utilizarea memoriei cache este foarte simplă în Nette, dar acoperă și nevoile foarte avansate de stocare cache. Este concepută pentru performanță și durabilitate 100%. Practic, veți găsi adaptoare pentru cele mai comune tipuri de stocare backend. Permite invalidarea bazată pe etichete, protecția cache stampede, expirarea timpului etc. +Utilizarea cache-ului în Nette este foarte ușoară, acoperind în același timp și nevoi foarte avansate. Este proiectat pentru performanță și rezistență 100%. În mod implicit, veți găsi adaptoare pentru cele mai comune stocări backend. Permite invalidarea bazată pe tag-uri, expirarea în timp, are protecție împotriva cache stampede etc. -Instalare .[#toc-installation] -============================== +Instalare +========= -Descărcați și instalați pachetul folosind [Composer |best-practices:composer]: +Descărcați și instalați biblioteca folosind [Composer |best-practices:composer]: ```shell composer require nette/caching ``` -Utilizare de bază .[#toc-basic-usage] -===================================== +Utilizare de bază +================= -Centrul de lucru cu memoria cache este obiectul [api:Nette\Caching\Cache]. Creăm instanța acestuia și transmitem constructorului ca parametru așa-numita stocare. Acesta este un obiect care reprezintă locul în care vor fi stocate fizic datele (bază de date, Memcached, fișiere pe disc, ...). Obiectul de stocare se obține prin trecerea acestuia folosind [injecția de dependență |dependency-injection:passing-dependencies] cu tipul `Nette\Caching\Storage`. Veți afla toate elementele esențiale în [secțiunea Stocare |#Storages]. +Centrul lucrului cu cache-ul este obiectul [Cache |api:Nette\Caching\Cache]. Creăm o instanță a acestuia și îi transmitem constructorului așa-numita stocare (storage). Acesta este un obiect care reprezintă locul unde datele vor fi stocate fizic (bază de date, Memcached, fișiere pe disc, ...). Ajungem la stocare lăsându-ne să o primim prin [dependency injection |dependency-injection:passing-dependencies] cu tipul `Nette\Caching\Storage`. Veți afla tot ce este esențial în [secțiunea Stocări |#Stocări]. .[warning] -În versiunea 3.0, interfața avea încă tipul `I` prefix, so the name was `Nette\Caching\IStorage`. De asemenea, constantele clasei `Cache` au fost scrise cu majuscule, deci, de exemplu, `Cache::EXPIRE` în loc de `Cache::Expire`. +În versiunea 3.0, interfața avea încă prefixul `I`, deci numele era `Nette\Caching\IStorage`. De asemenea, constantele clasei `Cache` erau scrise cu majuscule, deci, de exemplu, `Cache::EXPIRE` în loc de `Cache::Expire`. -Pentru exemplele următoare, să presupunem că avem un alias `Cache` și o stocare în variabila `$storage`. +Pentru următoarele exemple, presupunem că avem un alias `Cache` creat și stocarea în variabila `$storage`. ```php use Nette\Caching\Cache; @@ -40,51 +40,51 @@ use Nette\Caching\Cache; $storage = /* ... */; // instanță de Nette\Caching\Storage ``` -Memoria cache este de fapt un *magazin cu valori cheie*, astfel încât citim și scriem date sub chei la fel ca în cazul array-urilor asociative. Aplicațiile sunt formate din mai multe părți independente și, dacă toate acestea ar folosi o singură memorie (de exemplu, un director pe un disc), mai devreme sau mai târziu ar exista o coliziune de chei. Cadrul Nette Framework rezolvă problema prin împărțirea întregului spațiu în spații de nume (subdirectoare). Astfel, fiecare parte a programului utilizează propriul spațiu cu un nume unic și nu pot apărea coliziuni. +Cache-ul este de fapt un *key–value store*, adică citim și scriem date sub chei la fel ca în array-urile asociative. Aplicațiile sunt compuse din mai multe părți independente și dacă toate ar folosi o singură stocare (imaginați-vă un singur director pe disc), mai devreme sau mai târziu ar apărea o coliziune de chei. Nette Framework rezolvă problema împărțind întregul spațiu în spații de nume (subdirectoare). Fiecare parte a programului folosește apoi propriul spațiu cu un nume unic și nu mai poate apărea nicio coliziune. -Numele spațiului este specificat ca al doilea parametru al constructorului clasei Cache: +Numele spațiului îl specificăm ca al doilea parametru al constructorului clasei Cache: ```php $cache = new Cache($storage, 'Full Html Pages'); ``` -Acum putem utiliza obiectul `$cache` pentru a citi și a scrie din memoria cache. Metoda `load()` este utilizată pentru ambele. Primul argument este cheia, iar al doilea este callback-ul PHP, care este apelat atunci când cheia nu este găsită în memoria cache. Callback-ul generează o valoare, o returnează și o pune în cache: +Acum putem folosi obiectul `$cache` pentru a citi și scrie în cache. Pentru ambele servește metoda `load()`. Primul argument este cheia și al doilea este un callback PHP, care este apelat atunci când cheia nu este găsită în cache. Callback-ul generează valoarea, o returnează și aceasta este salvată în cache: ```php $value = $cache->load($key, function () use ($key) { - $computedValue = /* ... */; // calcule grele + $computedValue = /* ... */; // calcul costisitor return $computedValue; }); ``` -Dacă al doilea parametru nu este specificat `$value = $cache->load($key)`, se returnează `null` în cazul în care elementul nu se află în memoria cache. +Dacă nu specificăm al doilea parametru `$value = $cache->load($key)`, se va returna `null` dacă elementul nu este în cache. .[tip] -Lucrul grozav este că orice structuri serializabile pot fi puse în cache, nu numai șirurile de caractere. Și același lucru este valabil și pentru chei. +Este grozav că în cache pot fi stocate orice structuri serializabile, nu trebuie să fie doar șiruri de caractere. Și același lucru este valabil chiar și pentru chei. -Elementul este eliminat din memoria cache folosind metoda `remove()`: +Elementul din cache îl ștergem cu metoda `remove()`: ```php $cache->remove($key); ``` -De asemenea, puteți stoca un element în memoria cache utilizând metoda `$cache->save($key, $value, array $dependencies = [])`. Cu toate acestea, este preferabilă metoda de mai sus, care utilizează `load()`. +Salvarea unui element în cache se poate face și cu metoda `$cache->save($key, $value, array $dependencies = [])`. Cu toate acestea, metoda preferată este cea menționată mai sus, folosind `load()`, deoarece gestionează atomic generarea și salvarea datelor. -Memoizare .[#toc-memoization] -============================= +Memoizare +========= -Memoizarea înseamnă stocarea în memoria cache a rezultatului unei funcții sau metode, astfel încât să îl puteți utiliza data viitoare, în loc să calculați același lucru din nou și din nou. +Memoizarea înseamnă stocarea în cache a rezultatului apelării unei funcții sau metode, astfel încât să îl puteți utiliza data viitoare fără a calcula același lucru din nou și din nou. -Metodele și funcțiile pot fi numite memoized folosind `call(callable $callback, ...$args)`: +Metodele și funcțiile pot fi apelate memoizat folosind `call(callable $callback, ...$args)`: ```php $result = $cache->call('gethostbyaddr', $ip); ``` -Funcția `gethostbyaddr()` este apelată o singură dată pentru fiecare parametru `$ip`, iar data următoare va fi returnată valoarea din memoria cache. +Funcția `gethostbyaddr()` va fi astfel apelată pentru fiecare parametru `$ip` o singură dată, iar data viitoare se va returna valoarea din cache. -De asemenea, este posibil să se creeze un înveliș memoizat pentru o metodă sau o funcție care poate fi apelată ulterior: +De asemenea, este posibil să creați un wrapper memoizat peste o metodă sau funcție, care poate fi apelat ulterior: ```php function factorial($num) @@ -94,17 +94,17 @@ function factorial($num) $memoizedFactorial = $cache->wrap('factorial'); -$result = $memoizedFactorial(5); // o numără -$result = $memoizedFactorial(5); // o returnează din memoria cache +$result = $memoizedFactorial(5); // calculează prima dată +$result = $memoizedFactorial(5); // a doua oară din cache ``` -Expirare și invalidare .[#toc-expiration-invalidation] -====================================================== +Expirare & invalidare +===================== -În cazul memoriei cache, este necesar să se abordeze problema faptului că unele dintre datele salvate anterior vor deveni invalide în timp. Cadrul Nette Framework oferă un mecanism prin care se poate limita valabilitatea datelor și prin care acestea pot fi șterse într-un mod controlat ("invalidarea lor", folosind terminologia cadrului). +Cu stocarea în cache, trebuie rezolvată problema când datele stocate anterior devin invalide. Nette Framework oferă un mecanism pentru a limita validitatea datelor sau pentru a le șterge controlat (în terminologia framework-ului „a invalida”). -Valabilitatea datelor se stabilește în momentul salvării, folosind al treilea parametru al metodei `save()`, de exemplu, "Validitatea datelor": +Validitatea datelor se setează în momentul salvării, folosind al treilea parametru al metodei `save()`, de exemplu: ```php $cache->save($key, $value, [ @@ -112,7 +112,7 @@ $cache->save($key, $value, [ ]); ``` -Sau utilizând parametrul `$dependencies` transmis prin referință la callback-ul din metoda `load()`, de exemplu: +Sau folosind parametrul `$dependencies` transmis prin referință la callback-ul metodei `load()`, de exemplu: ```php $value = $cache->load($key, function (&$dependencies) { @@ -129,26 +129,26 @@ $value = $cache->load($key, function () { }, [Cache::Expire => '20 minutes']); ``` -În exemplele următoare, vom presupune cea de-a doua variantă și, prin urmare, existența unei variabile `$dependencies`. +În următoarele exemple, vom presupune a doua variantă și, prin urmare, existența variabilei `$dependencies`. -Expirarea .[#toc-expiration] ----------------------------- +Expirare +-------- -Cea mai simplă expirare este limita de timp. Iată cum să puneți în cache date valabile timp de 20 de minute: +Cea mai simplă expirare este limita de timp. Astfel stocăm date în cache cu o valabilitate de 20 de minute: ```php -// acceptă, de asemenea, numărul de secunde sau timestamp-ul UNIX +// acceptă și numărul de secunde sau timestamp UNIX $dependencies[Cache::Expire] = '20 minutes'; ``` -Dacă dorim să extindem perioada de valabilitate la fiecare citire, acest lucru se poate realiza în acest mod, dar atenție, acest lucru va crește sarcina de gestionare a cache-ului: +Dacă am dori să prelungim perioada de valabilitate la fiecare citire, se poate realiza astfel, dar atenție, costul cache-ului va crește: ```php $dependencies[Cache::Sliding] = true; ``` -Opțiunea cea mai la îndemână este posibilitatea de a lăsa datele să expire atunci când un anumit fișier este modificat sau unul dintre mai multe fișiere. Acest lucru poate fi utilizat, de exemplu, pentru a pune în cache datele rezultate din procesarea acestor fișiere. Utilizați căi absolute. +Este utilă posibilitatea de a lăsa datele să expire în momentul în care se modifică un fișier sau unul dintre mai multe fișiere. Acest lucru poate fi utilizat, de exemplu, la stocarea în cache a datelor rezultate din procesarea acestor fișiere. Utilizați căi absolute. ```php $dependencies[Cache::Files] = '/path/to/data.yaml'; @@ -156,13 +156,13 @@ $dependencies[Cache::Files] = '/path/to/data.yaml'; $dependencies[Cache::Files] = ['/path/to/data1.yaml', '/path/to/data2.yaml']; ``` -Putem lăsa un element din memoria cache să expire atunci când expiră un alt element (sau unul dintre mai multe altele). Acest lucru poate fi utilizat atunci când punem în cache întreaga pagină HTML și fragmente din ea sub alte chei. Odată ce fragmentul se schimbă, întreaga pagină devine invalidă. Dacă avem fragmente stocate sub chei precum `frag1` și `frag2`, vom folosi: +Putem lăsa un element din cache să expire în momentul în care expiră un alt element (sau unul dintre mai multe altele). Acest lucru poate fi utilizat atunci când stocăm în cache, de exemplu, o întreagă pagină HTML și sub alte chei fragmentele sale. De îndată ce un fragment se modifică, întreaga pagină este invalidată. Dacă fragmentele sunt stocate sub cheile, de exemplu, `frag1` și `frag2`, folosim: ```php $dependencies[Cache::Items] = ['frag1', 'frag2']; ``` -Expirarea poate fi, de asemenea, controlată folosind funcții personalizate sau metode statice, care decid întotdeauna la citire dacă elementul este încă valabil. De exemplu, putem lăsa elementul să expire ori de câte ori se schimbă versiunea PHP. Vom crea o funcție care compară versiunea curentă cu parametrul, iar la salvare vom adăuga o matrice de forma `[function name, ...arguments]` la dependențe: +Expirarea poate fi controlată și prin funcții proprii sau metode statice, care decid întotdeauna la citire dacă elementul este încă valid. Astfel, de exemplu, putem lăsa un element să expire ori de câte ori se schimbă versiunea PHP. Creăm o funcție care compară versiunea curentă cu parametrul și, la salvare, adăugăm între dependențe un array de forma `[nume functie, ...argumente]`: ```php function checkPhpVersion($ver): bool @@ -171,11 +171,11 @@ function checkPhpVersion($ver): bool } $dependencies[Cache::Callbacks] = [ - ['checkPhpVersion', PHP_VERSION_ID] // expiră atunci când checkPhpVersion(...) === false + ['checkPhpVersion', PHP_VERSION_ID] // expiră când checkPhpVersion(...) === false ]; ``` -Bineînțeles, toate criteriile pot fi combinate. Memoria cache expiră atunci când cel puțin un criteriu nu este îndeplinit. +Toate criteriile pot fi, desigur, combinate. Cache-ul va expira atunci când cel puțin un criteriu nu este îndeplinit. ```php $dependencies[Cache::Expire] = '20 minutes'; @@ -183,16 +183,16 @@ $dependencies[Cache::Files] = '/path/to/data.yaml'; ``` -Invalidarea cu ajutorul etichetelor .[#toc-invalidation-using-tags] -------------------------------------------------------------------- +Invalidare prin tag-uri +----------------------- -Etichetele sunt un instrument de invalidare foarte util. Putem atribui o listă de etichete, care sunt șiruri de caractere arbitrare, fiecărui element stocat în memoria cache. De exemplu, să presupunem că avem o pagină HTML cu un articol și comentarii, pe care dorim să o stocăm în memoria cache. Deci, specificăm etichete atunci când salvăm în memoria cache: +Un instrument de invalidare foarte util sunt așa-numitele tag-uri. Fiecărui element din cache îi putem atribui la salvare o listă de tag-uri, care sunt șiruri de caractere arbitrare. Să avem, de exemplu, o pagină HTML cu un articol și comentarii, pe care o vom stoca în cache. La salvare, specificăm tag-urile: ```php $dependencies[Cache::Tags] = ["article/$articleId", "comments/$articleId"]; ``` -Acum, să trecem la administrare. Aici avem un formular pentru editarea articolelor. Împreună cu salvarea articolului într-o bază de date, apelăm comanda `clean()`, care va șterge articolele din memoria cache în funcție de etichetă: +Să ne mutăm în administrare. Aici găsim un formular pentru editarea articolului. Împreună cu salvarea articolului în baza de date, vom apela comanda `clean()`, care va șterge din cache elementele conform tag-ului: ```php $cache->clean([ @@ -200,7 +200,7 @@ $cache->clean([ ]); ``` -De asemenea, în locul adăugării unui nou comentariu (sau al editării unui comentariu), nu vom uita să invalidăm tag-ul relevant: +La fel, în locul adăugării unui nou comentariu (sau editării unui comentariu), nu vom omite invalidarea tag-ului corespunzător: ```php $cache->clean([ @@ -208,22 +208,22 @@ $cache->clean([ ]); ``` -Ce am realizat? Că memoria noastră cache HTML va fi invalidată (ștearsă) ori de câte ori articolul sau comentariile se modifică. La editarea unui articol cu ID = 10, tag-ul `article/10` este forțat să fie invalidat și pagina HTML care poartă tag-ul este ștearsă din memoria cache. Același lucru se întâmplă atunci când introduceți un nou comentariu sub articolul respectiv. +Ce am obținut prin asta? Că cache-ul nostru HTML se va invalida (șterge) ori de câte ori se modifică articolul sau comentariile. Când se editează articolul cu ID = 10, se va forța invalidarea tag-ului `article/10` și pagina HTML care poartă tag-ul menționat se va șterge din cache. Același lucru se întâmplă la inserarea unui nou comentariu sub articolul respectiv. .[note] -Etichetele necesită [Journal |#Journal]. +Tag-urile necesită așa-numitul [#Journal]. -Invalidarea în funcție de prioritate .[#toc-invalidation-by-priority] ---------------------------------------------------------------------- +Invalidare prin prioritate +-------------------------- -Putem seta prioritatea pentru elementele individuale din memoria cache și va fi posibilă ștergerea lor într-un mod controlat atunci când, de exemplu, memoria cache depășește o anumită dimensiune: +Fiecărui element din cache îi putem seta o prioritate, cu ajutorul căreia va fi posibil să le ștergem, de exemplu, când cache-ul depășește o anumită dimensiune: ```php $dependencies[Cache::Priority] = 50; ``` -Ștergeți toate elementele cu o prioritate mai mică sau egală cu 100: +Ștergem toate elementele cu prioritate egală sau mai mică de 100: ```php $cache->clean([ @@ -232,13 +232,13 @@ $cache->clean([ ``` .[note] -Prioritățile necesită așa-numitul [Jurnal |#Journal]. +Prioritățile necesită așa-numitul [#Journal]. -Ștergeți memoria cache .[#toc-clear-cache] ------------------------------------------- +Ștergerea cache-ului +-------------------- -Parametrul `Cache::All` șterge totul: +Parametrul `Cache::All` șterge tot: ```php $cache->clean([ @@ -247,51 +247,70 @@ $cache->clean([ ``` -Citire în masă .[#toc-bulk-reading] -=================================== +Citire în masă +============== -Pentru citirea și scrierea în masă în memoria cache, se utilizează metoda `bulkLoad()`, prin care se trece un tablou de chei și se obține un tablou de valori: +Pentru citirea și scrierea în masă în cache servește metoda `bulkLoad()`, căreia îi transmitem un array de chei și obținem un array de valori: ```php $values = $cache->bulkLoad($keys); ``` -Metoda `bulkLoad()` funcționează în mod similar cu `load()` cu al doilea parametru de apelare, la care se transmite cheia elementului generat: +Metoda `bulkLoad()` funcționează similar cu `load()`, inclusiv cu al doilea parametru callback, căruia i se transmite cheia elementului generat: ```php $values = $cache->bulkLoad($keys, function ($key, &$dependencies) { - $computedValue = /* ... */; // calcule grele + $computedValue = /* ... */; // calcul costisitor return $computedValue; }); ``` -Stocarea în memoria cache de ieșire .[#toc-output-caching] -========================================================== +Utilizare cu PSR-16 .{data-version:3.3.1} +========================================= + +Pentru a utiliza Nette Cache cu interfața PSR-16 (Simple Cache), puteți folosi adaptorul `Nette\Bridges\Psr\PsrCacheAdapter`. Acesta permite integrarea fără probleme între Nette Cache (`Nette\Caching\Storage`) și orice cod sau bibliotecă care așteaptă un cache compatibil PSR-16. + +```php +$psrCache = new Nette\Bridges\Psr\PsrCacheAdapter($storage); +``` + +Acum puteți utiliza `$psrCache` ca un cache PSR-16: + +```php +$psrCache->set('key', 'value', 3600); // salvează valoarea pentru 1 oră +$value = $psrCache->get('key', 'default'); +``` + +Adaptorul suportă toate metodele definite în PSR-16, inclusiv `getMultiple()`, `setMultiple()` și `deleteMultiple()`. + + +Stocarea în cache a ieșirii +=========================== -Ieșirea poate fi capturată și pusă în cache foarte elegant: +Se poate captura și stoca în cache ieșirea foarte elegant: ```php if ($capture = $cache->capture($key)) { - echo ... // imprimarea unor date + echo ... // afișăm date - $capture->end(); // salvați rezultatul în memoria cache + $capture->end(); // salvăm ieșirea în cache } ``` -În cazul în care ieșirea este deja prezentă în memoria cache, metoda `capture()` o imprimă și returnează `null`, astfel încât condiția nu va fi executată. În caz contrar, metoda începe să stocheze ieșirea și returnează obiectul `$capture` cu ajutorul căruia salvăm în final datele în memoria cache. +În cazul în care ieșirea este deja stocată în cache, metoda `capture()` o va afișa și va returna `null`, deci condiția nu se va executa. În caz contrar, va începe să captureze ieșirea și va returna obiectul `$capture`, cu ajutorul căruia vom salva în final datele afișate în cache. .[note] În versiunea 3.0, metoda se numea `$cache->start()`. -Caching în Latte .[#toc-caching-in-latte] -========================================= +Stocarea în cache în Latte +========================== -Caching-ul în șabloanele [Latte |latte:] este foarte ușor, trebuie doar să înfășurați o parte din șablon cu tag-uri `{cache}...{/cache}`. Memoria cache este invalidată automat atunci când șablonul sursă se modifică (inclusiv orice șablon inclus în cadrul etichetelor `{cache}` ). Etichetele `{cache}` pot fi imbricate, iar atunci când un bloc imbricate este invalidat (de exemplu, de o etichetă), blocul părinte este, de asemenea, invalidat. +Stocarea în cache în șabloanele [Latte |latte:] este foarte ușoară, este suficient să încadrați o parte a șablonului cu tag-urile `{cache}...{/cache}`. Cache-ul se invalidează automat în momentul în care se modifică șablonul sursă (inclusiv eventualele șabloane incluse în interiorul blocului `{cache}`). Tag-urile `{cache}` pot fi imbricate, iar când un bloc imbricat devine invalid (de exemplu, printr-un tag), blocul părinte devine și el invalid. -În etichetă este posibil să se specifice cheile la care va fi legat cache-ul (aici variabila `$id`) și să se stabilească etichetele de expirare și [invalidare |#Invalidation using Tags] +În tag se pot specifica chei suplimentare de care va depinde cache-ul (aici variabila `$id`), se poate seta expirarea și [tag-urile pentru invalidare |#Invalidare prin tag-uri]. ```latte {cache $id, expire: '20 minutes', tags: [tag1, tag2]} @@ -299,9 +318,9 @@ Caching-ul în șabloanele [Latte |latte:] este foarte ușor, trebuie doar să {/cache} ``` -Toți parametrii sunt opționali, astfel încât nu este necesar să se precizeze expirarea, etichetele sau cheile. +Toate elementele sunt opționale, deci nu trebuie să specificăm nici expirarea, nici tag-urile, și nici măcar cheile. -Utilizarea memoriei cache poate fi, de asemenea, condiționată de `if` - conținutul va fi pus în memoria cache numai dacă este îndeplinită condiția: +Utilizarea cache-ului poate fi, de asemenea, condiționată folosind `if` - conținutul va fi stocat în cache doar dacă condiția este îndeplinită: ```latte {cache $id, if: !$form->isSubmitted()} @@ -310,23 +329,23 @@ Utilizarea memoriei cache poate fi, de asemenea, condiționată de `if` - conți ``` -Stocuri .[#toc-storages] -======================== +Stocări +======= -Un spațiu de stocare este un obiect care reprezintă locul unde sunt stocate fizic datele. Putem folosi o bază de date, un server Memcached sau cea mai disponibilă stocare, care sunt fișierele de pe disc. +Stocarea este un obiect care reprezintă locul unde datele sunt stocate fizic. Putem folosi o bază de date, un server Memcached sau cea mai accesibilă stocare, care sunt fișierele pe disc. -|---------------------- +|----------------- | Stocare | Descriere -|---------------------- -| [FileStorage |#FileStorage] | stocare implicită cu salvare în fișiere pe disc -| [MemcachedStorage |#MemcachedStorage] | utilizează serverul `Memcached` -| [MemoryStorage |#MemoryStorage] | datele sunt stocate temporar în memorie -| [SQLiteStorage |#SQLiteStorage] | datele sunt stocate în baza de date SQLite -| [DevNullStorage |#DevNullStorage] | datele nu sunt stocate - în scopuri de testare +|----------------- +| [#FileStorage] | stocare implicită cu salvare în fișiere pe disc +| [#MemcachedStorage] | utilizează serverul `Memcached` +| [#MemoryStorage] | datele sunt temporar în memorie +| [#SQLiteStorage] | datele se salvează într-o bază de date SQLite +| [#DevNullStorage] | datele nu se salvează, potrivit pentru testare -Obțineți obiectul de stocare prin transmiterea acestuia folosind [injecția de dependență |dependency-injection:passing-dependencies] cu tipul `Nette\Caching\Storage`. În mod implicit, Nette furnizează un obiect FileStorage care stochează datele într-un subfolder `cache` în directorul pentru [fișiere temporare |application:bootstrap#Temporary Files]. +La obiectul de stocare ajungeți lăsându-vă să vi-l transmită prin [dependency injection |dependency-injection:passing-dependencies] cu tipul `Nette\Caching\Storage`. Ca stocare implicită, Nette oferă obiectul `FileStorage` care salvează datele în subdirectorul `cache` din directorul pentru [fișiere temporare |application:bootstrapping#Fișiere temporare]. -Puteți modifica stocarea în configurație: +Puteți schimba stocarea implicită în configurația `services.neon`: ```neon services: @@ -334,28 +353,25 @@ services: ``` -Stocare fișiere .[#toc-filestorage] ------------------------------------ +FileStorage +----------- -Scrie memoria cache în fișierele de pe disc. Stocarea `Nette\Caching\Storages\FileStorage` este foarte bine optimizată pentru performanță și, mai presus de toate, asigură atomicitatea completă a operațiunilor. Ce înseamnă acest lucru? Că atunci când folosim memoria cache, nu se poate întâmpla să citim un fișier care nu a fost încă complet scris de un alt fir de execuție sau ca cineva să îl șteargă "pe sub mână". Prin urmare, utilizarea memoriei cache este complet sigură. +Scrie cache-ul în fișiere pe disc. Stocarea `Nette\Caching\Storages\FileStorage` este foarte bine optimizată pentru performanță și, mai presus de toate, asigură atomicitatea completă a operațiunilor. Ce înseamnă asta? Că la utilizarea cache-ului nu se poate întâmpla să citim un fișier care nu a fost încă scris complet de un alt fir de execuție, sau ca cineva să ni-l șteargă „sub nas”. Utilizarea cache-ului este, prin urmare, complet sigură. -Această stocare are, de asemenea, o caracteristică importantă încorporată care previne o creștere extremă a utilizării CPU atunci când memoria cache este ștearsă sau rece (adică nu este creată). Aceasta este prevenirea "stampedei cache":https://en.wikipedia.org/wiki/Cache_stampede. -Se întâmplă ca, la un moment dat, să existe mai multe cereri concurente care doresc același lucru din memoria cache (de exemplu, rezultatul unei interogări SQL costisitoare) și, deoarece aceasta nu se află în memoria cache, toate procesele încep să execute aceeași interogare SQL. -Sarcina procesorului este multiplicată și se poate întâmpla chiar ca niciun fir să nu poată răspunde în limita de timp, memoria cache să nu fie creată și aplicația să se blocheze. -Din fericire, memoria cache din Nette funcționează în așa fel încât, atunci când există mai multe cereri concurente pentru un element, acesta este generat doar de primul fir, celelalte așteaptă și apoi utilizează rezultatul generat. +Această stocare are, de asemenea, o funcție importantă încorporată, care previne creșterea extremă a utilizării CPU în momentul în care cache-ul este șters sau nu este încă încălzit (adică creat) - fenomen cunoscut sub numele de "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Se întâmplă ca, la un moment dat, să apară un număr mai mare de cereri concurente care doresc același lucru din cache (de exemplu, rezultatul unei interogări SQL costisitoare) și, deoarece nu se află în cache, toate procesele încep să execute aceeași interogare SQL. Sarcina se multiplică astfel și se poate chiar întâmpla ca niciun fir de execuție să nu reușească să răspundă în limita de timp, cache-ul să nu se creeze și aplicația să se prăbușească. Din fericire, cache-ul din Nette (cu `FileStorage`) funcționează astfel încât, în cazul mai multor cereri concurente pentru un singur element, acesta este generat doar de primul fir de execuție, celelalte așteaptă și apoi utilizează rezultatul generat. -Exemplu de creare a unui FileStorage: +Exemplu de creare a FileStorage: ```php -// stocarea va fi directorul "/path/to/temp" de pe disc. +// stocarea va fi directorul '/path/to/temp' pe disc $storage = new Nette\Caching\Storages\FileStorage('/path/to/temp'); ``` -MemcachedStorage .[#toc-memcachedstorage] ------------------------------------------ +MemcachedStorage +---------------- -Serverul [Memcached |https://memcached.org] este un sistem de stocare distribuită de înaltă performanță al cărui adaptor este `Nette\Caching\Storages\MemcachedStorage`. În configurație, specificați adresa IP și portul, dacă diferă de standardul 11211. +Serverul [Memcached |https://memcached.org] este un sistem de înaltă performanță pentru stocarea în memorie distribuită, al cărui adaptor este `Nette\Caching\Storages\MemcachedStorage`. În configurație specificăm adresa IP și portul, dacă diferă de cel standard 11211. .[caution] Necesită extensia PHP `memcached`. @@ -366,16 +382,16 @@ services: ``` -MemoryStorage .[#toc-memorystorage] ------------------------------------ +MemoryStorage +------------- -`Nette\Caching\Storages\MemoryStorage` este o memorie care stochează datele într-o matrice PHP și care este astfel pierdută atunci când cererea este terminată. +`Nette\Caching\Storages\MemoryStorage` este o stocare care salvează datele într-un array PHP și, prin urmare, se pierd la terminarea cererii. -SQLiteStorage .[#toc-sqlitestorage] ------------------------------------ +SQLiteStorage +------------- -Baza de date SQLite și adaptorul `Nette\Caching\Storages\SQLiteStorage` oferă o modalitate de stocare în memoria cache într-un singur fișier pe disc. Configurația va specifica calea către acest fișier. +Baza de date SQLite și adaptorul `Nette\Caching\Storages\SQLiteStorage` oferă o modalitate de a stoca cache-ul într-un singur fișier pe disc. În configurație specificăm calea către acest fișier. .[caution] Necesită extensiile PHP `pdo` și `pdo_sqlite`. @@ -386,16 +402,16 @@ services: ``` -DevNullStorage .[#toc-devnullstorage] -------------------------------------- +DevNullStorage +-------------- -O implementare specială a stocării este `Nette\Caching\Storages\DevNullStorage`, care nu stochează de fapt deloc date. Prin urmare, este potrivită pentru testare dacă dorim să eliminăm efectul cache-ului. +O implementare specială a stocării este `Nette\Caching\Storages\DevNullStorage`, care de fapt nu stochează deloc datele. Este astfel potrivită pentru testare, când dorim să eliminăm influența cache-ului. -Utilizarea memoriei cache în cod .[#toc-using-cache-in-code] -============================================================ +Utilizarea cache-ului în cod +============================ -Atunci când folosiți caching în cod, aveți două moduri de a face acest lucru. Prima este că obțineți obiectul de stocare prin trecerea acestuia folosind [injecția de dependență |dependency-injection:passing-dependencies] și apoi creați un obiect `Cache`: +La utilizarea cache-ului în cod, avem două moduri de a proceda. Primul este să ne lăsăm să primim stocarea prin [dependency injection |dependency-injection:passing-dependencies] și să creăm obiectul `Cache`: ```php use Nette; @@ -411,7 +427,7 @@ class ClassOne } ``` -Al doilea mod este acela de a obține obiectul de stocare `Cache`: +A doua opțiune este să ne lăsăm să primim direct obiectul `Cache`: ```php class ClassTwo @@ -423,7 +439,7 @@ class ClassTwo } ``` -Obiectul `Cache` este apoi creat direct în configurație, după cum urmează: +Obiectul `Cache` este apoi creat direct în configurație în acest mod: ```neon services: @@ -431,12 +447,12 @@ services: ``` -Jurnal .[#toc-journal] -====================== +Journal +======= -Nette stochează etichetele și prioritățile într-un așa-numit jurnal. În mod implicit, pentru aceasta se utilizează SQLite și fișierul `journal.s3db`, fiind necesare extensiile **PHP `pdo` și `pdo_sqlite`.** +Nette stochează tag-urile și prioritățile în așa-numitul journal. În mod standard, se utilizează SQLite și fișierul `journal.s3db` și **sunt necesare extensiile PHP `pdo` și `pdo_sqlite`.** -Puteți schimba jurnalul în configurare: +Puteți schimba journal-ul în configurație: ```neon services: @@ -444,4 +460,25 @@ services: ``` -{{leftbar: nette:@menu-topics}} +Servicii DI +=========== + +Aceste servicii sunt adăugate implicit în containerul DI de către extensia `nette/caching`: + +| Nume | Tip | Descriere +|---------------------------------------------------------- +| `cache.journal` | [api:Nette\Caching\Storages\Journal] | journal +| `cache.storage` | [api:Nette\Caching\Storage] | stocare + + +Dezactivarea cache-ului +======================= + +Una dintre opțiunile pentru a dezactiva *efectiv* cache-ul gestionat de `nette/caching` în aplicație este să setați ca stocare implicită [#DevNullStorage]: + +```neon +services: + cache.storage: Nette\Caching\Storages\DevNullStorage +``` + +Această setare nu afectează stocarea în cache a șabloanelor în Latte sau a containerului DI, deoarece aceste biblioteci nu utilizează serviciile nette/caching și își gestionează cache-ul independent. Cache-ul lor, de altfel, [nu trebuie dezactivat |nette:troubleshooting#Cum să dezactivați cache-ul în timpul dezvoltării] în modul dezvoltator. diff --git a/caching/ro/@meta.texy b/caching/ro/@meta.texy new file mode 100644 index 0000000000..6554692600 --- /dev/null +++ b/caching/ro/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Documentație Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/caching/ru/@home.texy b/caching/ru/@home.texy index 43873fa2f2..784e86d219 100644 --- a/caching/ru/@home.texy +++ b/caching/ru/@home.texy @@ -1,38 +1,38 @@ -Кэширование -*********** +Nette Caching +************* <div class=perex> -Кэш ускоряет работу вашего приложения, сохраняя данные — однажды с трудом извлеченные — для использования в будущем. Мы покажем вам: +Кеш ускоряет ваше приложение, сохраняя данные, полученные с трудом один раз, для последующего использования. Мы покажем вам: -- Как использовать кэш -- Как изменить хранилище кэша -- Как правильно аннулировать кэш +- как использовать кеш +- как изменить хранилище +- как правильно инвалидировать кеш </div> -Использование кэша в Nette очень простое, при этом он также покрывает очень сложные потребности в кэшировании. Он разработан для обеспечения производительности и 100% долговечности. В основном, вы найдете адаптеры для наиболее распространенных внутренних хранилищ. Позволяет аннулирование на основе тегов, защиту кэш-памяти, истечение времени и т. д. +Использование кеша в Nette очень просто, при этом оно покрывает даже очень продвинутые потребности. Он разработан для производительности и 100% отказоустойчивости. В основе вы найдете адаптеры для самых распространенных бэкенд-хранилищ. Позволяет инвалидацию на основе тегов, истечение срока действия по времени, имеет защиту от cache stampede и т. д. -Установка .[#toc-installation] -============================== +Установка +========= -Загрузите и установите пакет с помощью [Composer|best-practices:composer]: +Скачать и установить библиотеку можно с помощью [Composer|best-practices:composer]: ```shell composer require nette/caching ``` -Использование .[#toc-basic-usage] -================================= - -Центром работы с кэшем является объект [api:Nette\Caching\Cache]. Мы создаем его экземпляр и передаем конструктору в качестве параметра так называемое хранилище. Это объект, представляющий место, где данные будут физически храниться (база данных, Memcached, файлы на диске, ...). Вы получаете объект хранилища, передавая его с помощью [внедрения зависимостей |dependency-injection:passing-dependencies] с типом `Nette\Caching\Storage`. Всё самое необходимое вы найдете в [разделе Хранилища |#Хранилища]. - -Для следующих примеров предположим, что у нас есть псевдоним `Cache` и хранилище в переменной `$storage`. +Основное использование +====================== +Центром работы с кешем является объект [api:Nette\Caching\Cache]. Мы создаем его экземпляр и передаем конструктору так называемое хранилище. Это объект, представляющий место, где данные будут физически храниться (база данных, Memcached, файлы на диске, ...). К хранилищу мы получаем доступ, запросив его с помощью [внедрения зависимостей |dependency-injection:passing-dependencies] с типом `Nette\Caching\Storage`. Все существенное вы узнаете в [разделе Хранилища |#Хранилища]. +.[warning] +В версии 3.0 интерфейс еще имел префикс `I`, поэтому название было `Nette\Caching\IStorage`. А также константы класса `Cache` были написаны заглавными буквами, например, `Cache::EXPIRE` вместо `Cache::Expire`. +Для следующих примеров предположим, что у нас есть созданный псевдоним `Cache` и в переменной `$storage` хранилище. ```php use Nette\Caching\Cache; @@ -40,7 +40,7 @@ use Nette\Caching\Cache; $storage = /* ... */; // экземпляр Nette\Caching\Storage ``` -Кэш фактически является хранилищем типа *ключ-значение*, поэтому мы читаем и записываем данные по ключам так же, как и ассоциативные массивы. Приложения состоят из нескольких независимых частей, и если бы все они использовали одно хранилище (например, один каталог на диске), рано или поздно произойдет столкновение ключей. Nette Framework решает эту проблему путем разделения всего пространства на пространства имен (подкаталоги). В этом случае каждая часть программы использует свое собственное пространство с уникальным именем, и коллизии не возникают. +Кеш — это, по сути, *key–value store*, то есть мы читаем и записываем данные под ключами так же, как в ассоциативных массивах. Приложения состоят из ряда независимых частей, и если все они будут использовать одно хранилище (представьте себе один каталог на диске), рано или поздно произойдет коллизия ключей. Nette Framework решает эту проблему, разделяя все пространство на пространства имен (подкаталоги). Каждая часть программы затем использует свое пространство с уникальным именем, и коллизий больше не происходит. Имя пространства указывается в качестве второго параметра конструктора класса Cache: @@ -48,43 +48,43 @@ $storage = /* ... */; // экземпляр Nette\Caching\Storage $cache = new Cache($storage, 'Full Html Pages'); ``` -Теперь мы можем использовать объект `$cache` для чтения и записи из кэша. Для обоих используется метод `load()`. Первый аргумент — ключ, а второй — обратный вызов PHP, который вызывается, когда ключ не найден в кэше. Обратный вызов генерирует значение, возвращает его и кэширует: +Теперь мы можем с помощью объекта `$cache` читать и записывать в кеш. Для обоих действий служит метод `load()`. Первым аргументом является ключ, а вторым — PHP-callback, который вызывается, если ключ не найден в кеше. Callback генерирует значение, возвращает его, и оно сохраняется в кеше: ```php $value = $cache->load($key, function () use ($key) { - $computedValue = /* ... */; // тяжёлые вычисления + $computedValue = /* ... */; // сложный расчет return $computedValue; }); ``` -Если второй параметр не указан (`$value = $cache->load($key)`), возвращается `null`, если элемента нет в кэше. +Если второй параметр не указан `$value = $cache->load($key)`, вернется `null`, если элемент отсутствует в кеше. .[tip] -Замечательно то, что кэшировать можно любые сериализуемые структуры, а не только строки. То же самое относится и к ключам. +Здорово, что в кеш можно сохранять любые сериализуемые структуры, не обязательно только строки. И то же самое относится даже к ключам. -Элемент удаляется из кэша с помощью метода `remove()`: +Элемент из кеша удаляется методом `remove()`: ```php $cache->remove($key); ``` -Вы также можете кэшировать элементы с помощью метода `$cache->save($key, $value, array $dependencies = [])`. Однако вышеописанный метод с использованием `load()` предпочтительнее. +Сохранить элемент в кеше можно также методом `$cache->save($key, $value, array $dependencies = [])`. Однако предпочтительным является вышеуказанный способ с использованием `load()`. -Мемоизация .[#toc-memoization] -============================== +Мемоизация +========== -Мемоизация означает кэширование результата функции или метода, чтобы вы могли использовать его в следующий раз вместо того, чтобы вычислять одно и то же снова и снова. +Мемоизация означает кеширование результата вызова функции или метода, чтобы вы могли использовать его в следующий раз, не вычисляя то же самое снова и снова. -Методы и функции могут быть вызваны мемоизированно с помощью `call(callable $callback, ...$args)`: +Мемоизированно можно вызывать методы и функции с помощью `call(callable $callback, ...$args)`: ```php $result = $cache->call('gethostbyaddr', $ip); ``` -Функция `gethostbyaddr()` вызывается только один раз для каждого параметра `$ip` и в следующий раз будет возвращено значение из кэша. +Функция `gethostbyaddr()` таким образом вызывается для каждого параметра `$ip` только один раз, а в следующий раз уже возвращается значение из кеша. -Также можно создать мемоизированную обёртку для метода или функции, которая может быть вызвана позже: +Также можно создать мемоизированную обертку над методом или функцией, которую можно вызвать позже: ```php function factorial($num) @@ -94,17 +94,17 @@ function factorial($num) $memoizedFactorial = $cache->wrap('factorial'); -$result = $memoizedFactorial(5); // подсчитывает -$result = $memoizedFactorial(5); // возвращает из кэша +$result = $memoizedFactorial(5); // вычисляет в первый раз +$result = $memoizedFactorial(5); // во второй раз из кеша ``` -Истечение срока действия и аннулирование .[#toc-expiration-invalidation] -======================================================================== +Истечение срока действия и инвалидация +====================================== -При кэшировании необходимо решить вопрос о том, что некоторые из ранее сохраненных данных со временем станут недействительными. Nette Framework предоставляет механизм, как ограничить действительность данных и как удалить их контролируемым образом («сделать их недействительными», используя терминологию фреймворка). +При сохранении в кеш необходимо решить вопрос, когда ранее сохраненные данные станут недействительными. Nette Framework предлагает механизм для ограничения срока действия данных или их управляемого удаления (в терминологии фреймворка — «инвалидации»). -Действительность данных устанавливается в момент сохранения с помощью третьего параметра метода `save()`, например: +Срок действия данных устанавливается в момент сохранения с помощью третьего параметра метода `save()`, например: ```php $cache->save($key, $value, [ @@ -112,7 +112,7 @@ $cache->save($key, $value, [ ]); ``` -Или используя параметр `$dependencies`, переданный по ссылке в обратный вызов в методе `load()`, например: +Или с помощью параметра `$dependencies`, передаваемого по ссылке в callback метода `load()`, например: ```php $value = $cache->load($key, function (&$dependencies) { @@ -121,7 +121,7 @@ $value = $cache->load($key, function (&$dependencies) { }); ``` -Или используя 3-й параметр в методе `load()`, например: +Или с помощью 3-го параметра в методе `load()`, например: ```php $value = $cache->load($key, function () { @@ -132,23 +132,23 @@ $value = $cache->load($key, function () { В следующих примерах мы будем предполагать второй вариант и, следовательно, существование переменной `$dependencies`. -Срок действия .[#toc-expiration] --------------------------------- +Истечение срока действия +------------------------ -Самое простое исключение — это ограничение по времени. Вот как кэшировать данные, действительные в течение 20 минут: +Самое простое истечение срока действия — это временной лимит. Таким образом, мы сохраняем данные в кеше на 20 минут: ```php -// также можно передать число секунд или временную метку UNIX +// принимает также количество секунд или UNIX timestamp $dependencies[Cache::Expire] = '20 minutes'; ``` -Если мы хотим увеличивать срок действия при каждом чтении, этого можно добиться таким образом, но учтите, что это увеличит накладные расходы кэша: +Если бы мы хотели продлить срок действия при каждом чтении, этого можно достичь следующим образом, но будьте осторожны, накладные расходы на кеш при этом возрастут: ```php $dependencies[Cache::Sliding] = true; ``` -Удобной опцией является возможность разрешить истечение срока действия данных при изменении конкретного файла или одного из нескольких файлов. Это можно использовать, например, для кэширования данных, полученных в результате обработки этих файлов. Используйте абсолютные пути: +Удобна возможность сделать так, чтобы данные истекли в момент изменения файла или одного из нескольких файлов. Это можно использовать, например, при сохранении в кеше данных, полученных в результате обработки этих файлов. Используйте абсолютные пути. ```php $dependencies[Cache::Files] = '/path/to/data.yaml'; @@ -156,13 +156,13 @@ $dependencies[Cache::Files] = '/path/to/data.yaml'; $dependencies[Cache::Files] = ['/path/to/data1.yaml', '/path/to/data2.yaml']; ``` -Мы можем позволить элементу в кэше истечь, когда истекает срок действия другого элемента (или одного из нескольких других). Это можно использовать, когда мы кэшируем всю HTML-страницу и её фрагменты под другими ключами. Как только сниппет изменяется, вся страница становится недействительной. Если у нас есть фрагменты, хранящиеся под такими ключами, как `frag1` и `frag2`, мы будем использовать: +Мы можем сделать так, чтобы элемент в кеше истек в тот момент, когда истекает другой элемент (или один из нескольких других). Это можно использовать, когда мы сохраняем в кеше, например, целую HTML-страницу, а под другими ключами — ее фрагменты. Как только фрагмент изменяется, вся страница инвалидируется. Если фрагменты сохранены под ключами, например, `frag1` и `frag2`, используем: ```php $dependencies[Cache::Items] = ['frag1', 'frag2']; ``` -Срок действия также можно контролировать с помощью пользовательских функций или статических методов, которые при чтении всегда решают, действителен ли ещё элемент. Например, мы можем позволить элементу истекать всякий раз, когда меняется версия PHP. Мы создадим функцию, которая сравнивает текущую версию с параметром, и при сохранении добавим массив в виде `[имя функции, ...аргументы]` к зависимостям: +Истечение срока действия можно контролировать и с помощью пользовательских функций или статических методов, которые при каждом чтении решают, действителен ли еще элемент. Таким образом, мы можем, например, сделать так, чтобы элемент истек всегда, когда изменяется версия PHP. Создадим функцию, которая сравнивает текущую версию с параметром, и при сохранении добавим в зависимости массив вида `[имя функции, ...аргументы]`: ```php function checkPhpVersion($ver): bool @@ -175,7 +175,7 @@ $dependencies[Cache::Callbacks] = [ ]; ``` -Конечно, все критерии могут быть объединены. Срок действия кэша истекает, если хотя бы один критерий не выполнен. +Все критерии, конечно, можно комбинировать. Кеш тогда истечет, когда хотя бы один критерий не выполнен. ```php $dependencies[Cache::Expire] = '20 minutes'; @@ -183,16 +183,16 @@ $dependencies[Cache::Files] = '/path/to/data.yaml'; ``` -Инвалидация с использованием тегов .[#toc-invalidation-using-tags] ------------------------------------------------------------------- +Инвалидация с помощью тегов +--------------------------- -Теги являются очень полезным инструментом признания недействительности. Мы можем назначить список тегов, которые являются произвольными строками, каждому элементу, хранящемуся в кэше. Например, предположим, что у нас есть HTML-страница со статьей и комментариями, которую мы хотим кэшировать. Поэтому мы указываем теги при сохранении в кэш: +Очень полезным инструментом инвалидации являются так называемые теги. Каждому элементу в кеше мы можем присвоить список тегов, которые являются произвольными строками. Допустим, у нас есть HTML-страница со статьей и комментариями, которую мы будем кешировать. При сохранении указываем теги: ```php $dependencies[Cache::Tags] = ["article/$articleId", "comments/$articleId"]; ``` -Теперь перейдем к администрированию. Здесь у нас есть форма для редактирования статьи. Вместе с сохранением статьи в базе данных мы вызываем команду `clean()`, которая удаляет кэшированные элементы по тегам: +Перейдем в админку. Здесь мы найдем форму для редактирования статьи. Вместе с сохранением статьи в базу данных вызовем команду `clean()`, которая удалит из кеша элементы по тегу: ```php $cache->clean([ @@ -200,7 +200,7 @@ $cache->clean([ ]); ``` -Аналогичным образом, в месте добавления нового комментария (или редактирования комментария) мы не забудем аннулировать соответствующий тег: +Точно так же в месте добавления нового комментария (или редактирования комментария) не забудем инвалидировать соответствующий тег: ```php $cache->clean([ @@ -208,22 +208,22 @@ $cache->clean([ ]); ``` -Чего мы достигли? Что наш HTML-кэш будет аннулирован (удален) при каждом изменении статьи или комментариев. При редактировании статьи с ID = 10 тег `article/10` принудительно аннулируется, а HTML-страница, содержащая этот тег, удаляется из кэша. То же самое происходит при вставке нового комментария под соответствующей статьей. +Чего мы этим достигли? Того, что наш HTML-кеш будет инвалидироваться (удаляться) всякий раз, когда изменяется статья или комментарии. При редактировании статьи с ID = 10 произойдет принудительная инвалидация тега `article/10`, и HTML-страница, несущая указанный тег, будет удалена из кеша. То же самое произойдет при добавлении нового комментария к соответствующей статье. .[note] -Тегам требуется [#Журнал]. +Теги требуют так называемый [#Journal]. -Инвалидация по приоритету .[#toc-invalidation-by-priority] ----------------------------------------------------------- +Инвалидация с помощью приоритета +-------------------------------- -Мы можем установить приоритет для отдельных элементов в кэше, и их можно будет удалять контролируемым образом, когда, например, кэш превысит определенный размер: +Отдельным элементам в кеше мы можем установить приоритет, с помощью которого их можно будет удалять, например, когда кеш превысит определенный размер: ```php $dependencies[Cache::Priority] = 50; ``` -Удаляем все элементы с приоритетом, равным или меньшим 100: +Удалим все элементы с приоритетом, равным или меньшим 100: ```php $cache->clean([ @@ -232,13 +232,13 @@ $cache->clean([ ``` .[note] -Приоритетам также требуется [#Журнал]. +Приоритеты требуют так называемый [журнал |#Journal]. -Очистка кэша .[#toc-clear-cache] --------------------------------- +Очистка кеша +------------ -Параметр `Cache::All` очищает всё: +Параметр `Cache::All` удаляет все: ```php $cache->clean([ @@ -247,64 +247,83 @@ $cache->clean([ ``` -Массовое чтение .[#toc-bulk-reading] -==================================== +Массовое чтение +=============== -Для массового чтения и записи в кэш используется метод `bulkLoad()`, в котором мы передаем массив ключей и получаем массив значений: +Для массового чтения и записи в кеш служит метод `bulkLoad()`, которому мы передаем массив ключей и получаем массив значений: ```php $values = $cache->bulkLoad($keys); ``` -Метод `bulkLoad()` работает аналогично `load()` со вторым параметром обратного вызова, которому передается ключ сгенерированного элемента: +Метод `bulkLoad()` работает аналогично `load()` и со вторым параметром-callback'ом, которому передается ключ генерируемого элемента: ```php $values = $cache->bulkLoad($keys, function ($key, &$dependencies) { - $computedValue = /* ... */; // тяжёлые вычисления + $computedValue = /* ... */; // сложный расчет return $computedValue; }); ``` -Кэширование вывода .[#toc-output-caching] -========================================= +Использование с PSR-16 .{data-version:3.3.1} +============================================ + +Для использования Nette Cache с интерфейсом PSR-16 вы можете использовать адаптер `PsrCacheAdapter`. Он позволяет бесшовно интегрировать Nette Cache с любым кодом или библиотекой, которая ожидает PSR-16-совместимый кеш. + +```php +$psrCache = new Nette\Bridges\Psr\PsrCacheAdapter($storage); +``` + +Теперь вы можете использовать `$psrCache` как PSR-16 кеш: + +```php +$psrCache->set('key', 'value', 3600); // сохраняет значение на 1 час +$value = $psrCache->get('key', 'default'); +``` + +Адаптер поддерживает все методы, определенные в PSR-16, включая `getMultiple()`, `setMultiple()` и `deleteMultiple()`. + + +Кеширование вывода +================== -Выходные данные можно перехватывать и кэшировать очень элегантно: +Очень элегантно можно перехватывать и кешировать вывод: ```php if ($capture = $cache->capture($key)) { - echo ... // выводим некоторые данные + echo ... // выводим данные - $capture->end(); // сохраняем вывод в кэш + $capture->end(); // сохраняем вывод в кеш } ``` -В случае если вывод уже присутствует в кэше, метод `capture()` печатает его и возвращает `null`, поэтому условие не будет выполнено. В противном случае он начинает буферизацию вывода и возвращает объект `$capture`, с помощью которого мы окончательно сохраняем данные в кэш. +В случае, если вывод уже сохранен в кеше, метод `capture()` выведет его и вернет `null`, то есть условие не выполнится. В противном случае он начнет перехватывать вывод и вернет объект `$capture`, с помощью которого мы в конечном итоге сохраним выведенные данные в кеш. -/--comment -\-- +.[note] +В версии 3.0 метод назывался `$cache->start()`. -Кэширование в Latte .[#toc-caching-in-latte] -============================================ +Кеширование в Latte +=================== -Кэширование в шаблонах [Latte|latte:] очень легко настраивается, достаточно обернуть часть шаблона тегами `{cache}...{/cache}`. Кэш автоматически аннулируется при изменении исходного шаблона (включая любые включенные шаблоны в тегах `{cache}`). Теги `{cache}` могут быть вложенными, и когда вложенный блок аннулируется (например, тегом), родительский блок также аннулируется. +Кеширование в шаблонах [Latte|latte:] очень просто, достаточно обернуть часть шаблона тегами `{cache}...{/cache}`. Кеш автоматически инвалидируется в момент изменения исходного шаблона (включая возможные включенные шаблоны внутри блока cache). Теги `{cache}` можно вкладывать друг в друга, и когда вложенный блок становится недействительным (например, по тегу), недействительным становится и родительский блок. -В теге можно указать ключи, к которым будет привязан кэш (здесь переменная `$id`) и установить срок действия и [теги аннулирования |#Инвалидация с использованием тегов]. +В теге можно указать ключи, к которым будет привязан кеш (здесь переменная `$id`), и установить срок действия и [теги для инвалидации |#Инвалидация с помощью тегов]. ```latte -{cache $id, expire => '20 minutes', tags => [tag1, tag2]} +{cache $id, expire: '20 minutes', tags: [tag1, tag2]} ... {/cache} ``` -Все параметры являются необязательными, поэтому вам не нужно указывать срок действия, теги или ключи. +Все параметры необязательны, поэтому мы можем не указывать ни срок действия, ни теги, ни даже ключи. -Использование кэша также может быть обусловлено `if` — содержимое будет кэшироваться только при выполнении условия: +Использование кеша также можно сделать условным с помощью `if` - содержимое тогда будет кешироваться только при выполнении условия: ```latte -{cache $id, if => !$form->isSubmitted()} +{cache $id, if: !$form->isSubmitted()} {$form} {/cache} ``` @@ -313,20 +332,20 @@ if ($capture = $cache->capture($key)) { Хранилища ========= -Хранилище — это объект, который представляет собой место физического хранения данных. Мы можем использовать базу данных, сервер Memcached или наиболее доступное хранилище, которым являются файлы на диске. +Хранилище — это объект, представляющий место, где данные физически хранятся. Мы можем использовать базу данных, сервер Memcached или самое доступное хранилище — файлы на диске. -|---------------------- -| Хранение | Описание -|---------------------- -| [#FileStorage] | хранение по умолчанию с сохранением в файлы на диске -| [#MemcachedStorage] | используется сервер `Memcached -| [#MemoryStorage] | данные временно находятся в памяти -| [#SQLiteStorage] | данные хранятся в базе данных SQLite -| [#DevNullStorage] | данные не хранятся — в целях тестирования +|----------------- +| Хранилище | Описание +|----------------- +| [#FileStorage] | хранилище по умолчанию с сохранением в файлы на диск +| [#MemcachedStorage] | использует сервер `Memcached` +| [#MemoryStorage] | данные временно хранятся в памяти +| [#SQLiteStorage] | данные сохраняются в базу данных SQLite +| [#DevNullStorage] | данные не сохраняются, подходит для тестирования -Вы получаете объект хранилища, передавая его с помощью [внедрения зависимостей |dependency-injection:passing-dependencies] с типом `Nette\Caching\Storage`. По умолчанию Nette предоставляет объект FileStorage, который хранит данные в подпапке `cache` в каталоге для [временных файлов |application:bootstrap#Temporary-Files] . +К объекту хранилища вы получаете доступ, запросив его с помощью [внедрения зависимостей |dependency-injection:passing-dependencies] с типом `Nette\Caching\Storage`. В качестве хранилища по умолчанию Nette предоставляет объект `FileStorage`, сохраняющий данные в подкаталог `cache` в каталоге для [временных файлов |application:bootstrapping#Временные файлы]. -Вы можете изменить хранилище в конфигурации: +Изменить хранилище можно в конфигурации: ```neon services: @@ -337,12 +356,9 @@ services: FileStorage ----------- -Записывает кэш в файлы на диске. Хранилище `Nette\Caching\Storages\FileStorage` очень хорошо оптимизировано для производительности и, прежде всего, обеспечивает полную атомарность операций. Что это значит? Чтобы при использовании кэша не получилось так, что мы читаем файл, который ещё не был полностью записан другим потоком, или чтобы кто-то удалил его «из-под руки». Поэтому использование кэша полностью безопасно. +Записывает кеш в файлы на диске. Хранилище `Nette\Caching\Storages\FileStorage` очень хорошо оптимизировано для производительности и, прежде всего, обеспечивает полную атомарность операций. Что это значит? Что при использовании кеша не может случиться так, что мы прочитаем файл, который еще не полностью записан другим потоком, или что кто-то удалит его "под рукой". Использование кеша, таким образом, полностью безопасно. -Это хранилище также имеет важную встроенную функцию, которая предотвращает экстремальное увеличение загрузки процессора, когда кэш очищается или охлаждается (т. е. не создается). Это «профилактика» "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede_. -Бывает так, что в один момент поступает несколько одновременных запросов, которые хотят получить из кэша одно и то же (например, результат большого SQL-запроса), а поскольку он не кэшируется, все процессы начинают выполнять один и тот же SQL-запрос. -Нагрузка на процессор увеличивается в несколько раз, и может даже случиться так, что ни один поток не сможет ответить в отведенное время, кэш не будет создан, и приложение аварийно завершит работу. -К счастью, кэш в Nette работает таким образом, что при наличии нескольких одновременных запросов на один элемент, он генерируется только первым потоком, остальные ждут и затем используют сгенерированный результат. +Это хранилище также имеет встроенную важную функцию, которая предотвращает экстремальный рост использования ЦП в момент, когда кеш удаляется или еще не прогрет (т. е. не создан). Это предотвращение "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Бывает, что в один момент сходится большое количество одновременных запросов, которые хотят из кеша одно и то же (например, результат дорогого SQL-запроса), и поскольку в кеше его нет, все процессы начинают выполнять один и тот же SQL-запрос. Нагрузка таким образом умножается, и может даже случиться так, что ни один поток не успеет ответить в течение временного лимита, кеш не создастся, и приложение рухнет. К счастью, кеш в Nette работает так, что при нескольких одновременных запросах к одному элементу его генерирует только первый поток, остальные ждут и затем используют сгенерированный результат. Пример создания FileStorage: @@ -355,10 +371,10 @@ $storage = new Nette\Caching\Storages\FileStorage('/path/to/temp'); MemcachedStorage ---------------- -Сервер [Memcached |https://memcached.org] — это высокопроизводительная распределенная система хранения данных, адаптером которой является `Nette\Caching\Storages\MemcachedStorage`. В конфигурации укажите IP-адрес и порт, если он отличается от стандартного 11211. +Сервер [Memcached|https://memcached.org] — это высокопроизводительная система хранения в распределенной памяти, адаптером для которой является `Nette\Caching\Storages\MemcachedStorage`. В конфигурации укажем IP-адрес и порт, если он отличается от стандартного 11211. .[caution] -Требуется PHP-расширение `memcached`. +Требуется расширение PHP `memcached`. ```neon services: @@ -369,16 +385,16 @@ services: MemoryStorage ------------- -`Nette\Caching\Storages\MemoryStorage` это хранилище, которое хранит данные в массиве PHP и, таким образом, теряется при завершении запроса. +`Nette\Caching\Storages\MemoryStorage` — это хранилище, которое сохраняет данные в массив PHP, и, следовательно, они теряются при завершении запроса. -SQLiteStorage .[#toc-memorystorage] ------------------------------------ +SQLiteStorage +------------- -The SQLite database and adapter `Nette\Caching\Storages\SQLiteStorage` offer a way to cache in a single file on disk. The configuration will specify the path to this file. +База данных SQLite и адаптер `Nette\Caching\Storages\SQLiteStorage` предлагают способ хранения кеша в одном файле на диске. В конфигурации укажем путь к этому файлу. .[caution] -Requires PHP extensions `pdo` and `pdo_sqlite`. +Требуются расширения PHP `pdo` и `pdo_sqlite`. ```neon services: @@ -389,13 +405,13 @@ services: DevNullStorage -------------- -Особой реализацией хранилища является `Nette\Caching\Storages\DevNullStorage`, которая на самом деле не хранит данные вообще. Поэтому она подходит для тестирования, если мы хотим исключить влияние кэша. +Специальной реализацией хранилища является `Nette\Caching\Storages\DevNullStorage`, которое на самом деле вообще не сохраняет данные. Оно подходит для тестирования, когда мы хотим исключить влияние кеша. -Использование кэша в коде .[#toc-devnullstorage] -================================================ +Использование кеша в коде +========================= -При использовании кэширования в коде у вас есть два способа, как это сделать. Первый заключается в том, что вы получаете объект хранилища, передавая его с помощью [внедрения зависимостей |dependency-injection:passing-dependencies], а затем создаете объект `Cache`: +При использовании кеша в коде у нас есть два способа. Первый из них заключается в том, что мы запрашиваем хранилище с помощью [внедрения зависимостей |dependency-injection:passing-dependencies] и создаем объект `Cache`: ```php use Nette; @@ -411,7 +427,7 @@ class ClassOne } ``` -Второй способ заключается в том, что вы получаете объект хранения `Cache`: +Второй вариант — запросить сразу объект `Cache`: ```php class ClassTwo @@ -423,7 +439,7 @@ class ClassTwo } ``` -Затем объект `Cache` создается непосредственно в конфигурации следующим образом: +Объект `Cache` затем создается непосредственно в конфигурации следующим образом: ```neon services: @@ -431,12 +447,12 @@ services: ``` -Журнал -====== +Journal +======= -Nette хранит теги и приоритеты в так называемом журнале. По умолчанию для этого используются SQLite и файл `journal.s3db`. Кроме того, **требуются PHP расширения `pdo` и `pdo_sqlite`**. +Nette сохраняет теги и приоритеты в так называемый журнал. По умолчанию для этого используется SQLite и файл `journal.s3db`, и **требуются расширения PHP `pdo` и `pdo_sqlite`.** -Вы можете изменить журнал в конфигурации: +Изменить журнал можно в конфигурации: ```neon services: @@ -444,4 +460,25 @@ services: ``` -{{leftbar: nette:@menu-topics}} +Сервисы DI +========== + +Эти сервисы добавляются в DI-контейнер: + +| Название | Тип | Описание +|---------------------------------------------------------- +| `cache.journal` | [api:Nette\Caching\Storages\Journal] | журнал +| `cache.storage` | [api:Nette\Caching\Storage] | хранилище + + +Отключение кеша +=============== + +Одним из способов отключения кеша в приложении является установка в качестве хранилища [#DevNullStorage]: + +```neon +services: + cache.storage: Nette\Caching\Storages\DevNullStorage +``` + +Эта настройка не влияет на кеширование шаблонов в Latte или DI-контейнера, поскольку эти библиотеки не используют сервисы nette/caching и управляют своим кешем самостоятельно. Их кеш, впрочем, [нет необходимости |nette:troubleshooting#Как отключить кеш во время разработки] отключать в режиме разработки. diff --git a/caching/ru/@meta.texy b/caching/ru/@meta.texy new file mode 100644 index 0000000000..61577d6323 --- /dev/null +++ b/caching/ru/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Документация Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/caching/sl/@home.texy b/caching/sl/@home.texy index 018aa3653c..f87e7a5f6e 100644 --- a/caching/sl/@home.texy +++ b/caching/sl/@home.texy @@ -1,90 +1,90 @@ -Predpomnilnik +Nette Caching ************* <div class=perex> -Predpomnilnik pospešuje aplikacijo s shranjevanjem podatkov, ki so enkrat težko pridobljeni, za prihodnjo uporabo. Predstavili vam bomo: +Predpomnilnik pospeši vašo aplikacijo tako, da enkrat težko pridobljene podatke shrani za naslednjo uporabo. Pokazali bomo: -- Kako uporabljati predpomnilnik -- kako spremeniti shranjevanje predpomnilnika -- kako pravilno razveljaviti predpomnilnik +- kako uporabljati predpomnilnik +- kako spremeniti shrambo +- kako pravilno invalidirati predpomnilnik </div> -Uporaba predpomnilnika je v programu Nette zelo preprosta, pokriva pa tudi zelo napredne potrebe po predpomnilniku. Zasnovan je za zmogljivost in 100-odstotno trajnost. V osnovi boste našli adapterje za najpogostejše zaledne shrambe. Omogoča razveljavljanje na podlagi oznak, zaščito predpomnilnika pred stampedo, časovno iztekanje itd. +Uporaba predpomnilnika je v Nette zelo enostavna, hkrati pa pokriva tudi zelo napredne potrebe. Zasnovan je za zmogljivost in 100% odpornost. V osnovi najdete adapterje za najpogostejše zaledne shrambe. Omogoča invalidacijo, temelječo na značkah, časovni potek, ima zaščito pred cache stampede itd. -Namestitev .[#toc-installation] -=============================== +Namestitev +========== -Prenesite in namestite paket s [programom Composer |best-practices:composer]: +Knjižnico prenesete in namestite z orodjem [Composer|best-practices:composer]: ```shell composer require nette/caching ``` -Osnovna uporaba .[#toc-basic-usage] -=================================== +Osnovna uporaba +=============== -Središče dela s predpomnilnikom je objekt [api:Nette\Caching\Cache]. Ustvarimo njegov primerek in konstruktorju kot parameter posredujemo tako imenovano shrambo. Ki je objekt, ki predstavlja mesto, kjer bodo podatki fizično shranjeni (zbirka podatkov, Memcached, datoteke na disku, ...). Objekt shrambe dobimo tako, da ga posredujemo z uporabo [vbrizgavanja odvisnosti |dependency-injection:passing-dependencies] s tipom `Nette\Caching\Storage`. Vse bistvene informacije boste našli v [razdelku Shranjevanje |#Storages]. +Središče dela s predpomnilnikom predstavlja objekt [api:Nette\Caching\Cache]. Ustvarimo si njegovo instanco in kot parameter konstruktorju posredujemo t.i. shrambo. To je objekt, ki predstavlja mesto, kamor se bodo podatki fizično shranjevali (podatkovna baza, Memcached, datoteke na disku, ...). Do shrambe pridemo tako, da si jo pustimo posredovati s pomočjo [dependency injection |dependency-injection:passing-dependencies] s tipom `Nette\Caching\Storage`. Vse bistveno boste izvedeli v [odseku Shrambe |#Shrambe]. .[warning] -V različici 3.0 je imel vmesnik še vedno tip `I` prefix, so the name was `Nette\Caching\IStorage`. Tudi konstante razreda `Cache` so se pisale z velikimi črkami, torej na primer `Cache::EXPIRE` namesto `Cache::Expire`. +V različici 3.0 je imel vmesnik še predpono `I`, zato je bilo ime `Nette\Caching\IStorage`. Poleg tega so bile konstante razreda `Cache` zapisane z velikimi črkami, torej na primer `Cache::EXPIRE` namesto `Cache::Expire`. -V naslednjih primerih predpostavimo, da imamo vzdevek `Cache` in shrambo v spremenljivki `$storage`. +Za naslednje primere predpostavimo, da imamo ustvarjen alias `Cache` in v spremenljivki `$storage` shrambo. ```php use Nette\Caching\Cache; -$storage = /* ... */; // primerek predpomnilnika Nette\Caching\Storage +$storage = /* ... */; // instance of Nette\Caching\Storage ``` -Shramba je pravzaprav *skladišče ključev*, zato podatke pod ključi beremo in pišemo tako kot asociativna polja. Aplikacije so sestavljene iz več neodvisnih delov, in če bi vsi uporabljali eno shrambo (za predstavo: en imenik na disku), bi prej ali slej prišlo do trka ključev. Okvir Nette to težavo reši tako, da celoten prostor razdeli na imenske prostore (podimenike). Vsak del programa tako uporablja svoj prostor z edinstvenim imenom in do trkov ne more priti. +Predpomnilnik je pravzaprav *key–value store*, torej podatke beremo in zapisujemo pod ključi enako kot pri asociativnih poljih. Aplikacije so sestavljene iz vrste neodvisnih delov in če bi vsi uporabljali eno shrambo (predstavljajte si en imenik na disku), bi prej ali slej prišlo do kolizije ključev. Nette Framework problem rešuje tako, da celoten prostor deli na imenske prostore (podimenike). Vsak del programa nato uporablja svoj prostor z edinstvenim imenom in do nobene kolizije več ne more priti. -Ime prostora je določeno kot drugi parameter konstruktorja razreda Cache: +Ime prostora navedemo kot drugi parameter konstruktorja razreda Cache: ```php $cache = new Cache($storage, 'Full Html Pages'); ``` -Zdaj lahko za branje in pisanje iz predpomnilnika uporabimo objekt `$cache`. Za oboje se uporablja metoda `load()`. Prvi argument je ključ, drugi pa povratni klic PHP, ki se pokliče, ko ključa ne najdemo v predpomnilniku. Povratni klic ustvari vrednost, jo vrne in jo shrani v predpomnilnik: +Zdaj lahko s pomočjo objekta `$cache` iz predpomnilnika beremo in vanj zapisujemo. Za oboje služi metoda `load()`. Prvi argument je ključ in drugi PHP povratni klic (callback), ki se pokliče, ko ključ ni najden v predpomnilniku. Povratni klic vrednost generira, vrne in ta se shrani v predpomnilnik: ```php $value = $cache->load($key, function () use ($key) { - $computedValue = /* ... */; // težka računanja. + $computedValue = /* ... */; // zahteven izračun return $computedValue; }); ``` -Če drugi parameter ni naveden `$value = $cache->load($key)`, se vrne `null`, če elementa ni v predpomnilniku. +Če drugega parametra ne navedemo `$value = $cache->load($key)`, se vrne `null`, če elementa v predpomnilniku ni. .[tip] -Odlično je, da je mogoče v predpomnilnik shraniti vse serializabilne strukture, ne le nizov. Enako velja tudi za ključe. +Odlično je, da lahko v predpomnilnik shranjujemo kakršnekoli serializabilne strukture, ni nujno, da so to samo nizi. In enako velja celo za ključe. -Element se iz predpomnilnika izbriše z metodo `remove()`: +Element iz predpomnilnika izbrišemo z metodo `remove()`: ```php $cache->remove($key); ``` -Element lahko iz predpomnilnika izbrišete tudi z metodo `$cache->save($key, $value, array $dependencies = [])`. Vendar je zgornja metoda z uporabo `load()` boljša. +Shranjevanje elementa v predpomnilnik je mogoče tudi z metodo `$cache->save($key, $value, array $dependencies = [])`. Vendar je prednostni zgoraj navedeni način s pomočjo `load()`. -Memoiziranje .[#toc-memoization] -================================ +Memoizacija +=========== -Memoizacija pomeni predpomnjenje rezultata funkcije ali metode, tako da ga lahko uporabite naslednjič, namesto da vedno znova izračunavate isto stvar. +Memoizacija pomeni predpomnjenje rezultata klica funkcije ali metode, da ga lahko uporabite naslednjič brez ponovnega izračunavanja iste stvari. -Metode in funkcije lahko memoizirate z uporabo `call(callable $callback, ...$args)`: +Memoizirano lahko kličemo metode in funkcije s pomočjo `call(callable $callback, ...$args)`: ```php $result = $cache->call('gethostbyaddr', $ip); ``` -Funkcija `gethostbyaddr()` se za vsak parameter `$ip` pokliče samo enkrat, naslednjič pa se vrne vrednost iz predpomnilnika. +Funkcija `gethostbyaddr()` se tako pokliče za vsak parameter `$ip` samo enkrat in naslednjič se že vrne vrednost iz predpomnilnika. -Prav tako je mogoče ustvariti memoiziran ovoj za metodo ali funkcijo, ki ga lahko pokličemo pozneje: +Prav tako je mogoče ustvariti memoiziran ovoj nad metodo ali funkcijo, ki ga lahko kličemo kasneje: ```php function factorial($num) @@ -94,17 +94,17 @@ function factorial($num) $memoizedFactorial = $cache->wrap('factorial'); -$result = $memoizedFactorial(5); // ga šteje. -$result = $memoizedFactorial(5); // ga vrne iz predpomnilnika. +$result = $memoizedFactorial(5); // prvič izračuna +$result = $memoizedFactorial(5); // drugič iz predpomnilnika ``` -Iztek veljavnosti in razveljavitev .[#toc-expiration-invalidation] -================================================================== +Potek & invalidacija +==================== -Pri uporabi predpomnilnika je treba obravnavati vprašanje, da bodo nekateri predhodno shranjeni podatki sčasoma postali neveljavni. Okvir Nette zagotavlja mehanizem, kako omejiti veljavnost podatkov in jih nadzorovano izbrisati ("razveljaviti", če uporabimo terminologijo okvira). +Pri shranjevanju v predpomnilnik je treba rešiti vprašanje, kdaj prej shranjeni podatki postanejo neveljavni. Nette Framework ponuja mehanizem, kako omejiti veljavnost podatkov ali jih nadzorovano brisati (v terminologiji ogrodja "invalidirati"). -Veljavnost podatkov se določi ob shranjevanju s tretjim parametrom metode `save()`, npr: +Veljavnost podatkov se nastavi v trenutku shranjevanja in sicer s pomočjo tretjega parametra metode `save()`, npr.: ```php $cache->save($key, $value, [ @@ -112,7 +112,7 @@ $cache->save($key, $value, [ ]); ``` -ali z uporabo parametra `$dependencies`, ki se s sklicevanjem posreduje povratnemu klicu v metodi `load()`, npr: +Ali s pomočjo parametra `$dependencies`, posredovanega z referenco v povratni klic metode `load()`, npr.: ```php $value = $cache->load($key, function (&$dependencies) { @@ -121,7 +121,7 @@ $value = $cache->load($key, function (&$dependencies) { }); ``` -Ali z uporabo tretjega parametra v metodi `load()`, npr: +Ali s pomočjo 3. parametra v metodi `load()`, npr: ```php $value = $cache->load($key, function () { @@ -129,26 +129,26 @@ $value = $cache->load($key, function () { }, [Cache::Expire => '20 minutes']); ``` -V naslednjih primerih bomo predpostavili drugo varianto in s tem obstoj spremenljivke `$dependencies`. +V nadaljnjih primerih bomo predpostavljali drugo varianto in torej obstoj spremenljivke `$dependencies`. -Iztek veljavnosti .[#toc-expiration] ------------------------------------- +Potek +----- -Najpreprostejše prenehanje veljavnosti je časovna omejitev. Tukaj je prikazan način za shranjevanje podatkov v predpomnilnik, ki velja 20 minut: +Najenostavnejši potek predstavlja časovna omejitev. Tako shranimo v predpomnilnik podatke z veljavnostjo 20 minut: ```php -// sprejme tudi število sekund ali časovni žig UNIX +// sprejema tudi število sekund ali UNIX časovni žig $dependencies[Cache::Expire] = '20 minutes'; ``` -Če želimo z vsakim branjem podaljšati obdobje veljavnosti, lahko to dosežemo na ta način, vendar bodite pozorni, to bo povečalo režijske stroške predpomnilnika: +Če bi želeli podaljšati dobo veljavnosti z vsakim branjem, lahko to dosežemo na naslednji način, vendar pozor, režija predpomnilnika se s tem poveča: ```php $dependencies[Cache::Sliding] = true; ``` -Priročna možnost je možnost, da se podatki iztečejo, ko se spremeni določena datoteka ali ena od več datotek. To lahko na primer uporabite za predpomnjenje podatkov, ki so rezultat procesiranja teh datotek. Uporaba absolutnih poti. +Priročna je možnost, da podatki potečejo v trenutku, ko se spremeni datoteka ali katera od več datotek. To lahko izkoristimo na primer pri shranjevanju podatkov, nastalih z obdelavo teh datotek, v predpomnilnik. Uporabljajte absolutne poti. ```php $dependencies[Cache::Files] = '/path/to/data.yaml'; @@ -156,13 +156,13 @@ $dependencies[Cache::Files] = '/path/to/data.yaml'; $dependencies[Cache::Files] = ['/path/to/data1.yaml', '/path/to/data2.yaml']; ``` -Elementu v predpomnilniku lahko dovolimo, da poteče, ko poteče rok veljavnosti drugega elementa (ali enega od več drugih). To lahko uporabimo, kadar v predpomnilnik shranimo celotno stran HTML in njene dele pod drugimi ključi. Ko se odlomek spremeni, celotna stran postane neveljavna. Če imamo fragmente shranjene pod ključi, kot sta `frag1` in `frag2`, bomo uporabili: +Element v predpomnilniku lahko pustimo poteči v trenutku, ko poteče drug element (ali kateri od več drugih). To lahko izkoristimo takrat, ko v predpomnilnik shranjujemo na primer celotno HTML stran in pod drugimi ključi njene fragmente. Takoj ko se fragment spremeni, se invalidira celotna stran. Če imamo fragmente shranjene pod ključi npr. `frag1` in `frag2`, uporabimo: ```php $dependencies[Cache::Items] = ['frag1', 'frag2']; ``` -Iztek veljavnosti lahko nadzorujemo tudi s funkcijami po meri ali statičnimi metodami, ki ob branju vedno odločijo, ali je element še veljaven. Elementu lahko na primer dovolimo, da poteče, kadar koli se spremeni različica PHP. Ustvarili bomo funkcijo, ki bo primerjala trenutno različico s parametrom, pri shranjevanju pa bomo dodali polje v obliki `[function name, ...arguments]` k odvisnostim: +Potek lahko nadzorujemo tudi s pomočjo lastnih funkcij ali statičnih metod, ki vedno ob branju odločijo, ali je element še veljaven. Tako lahko na primer pustimo element poteči vedno, ko se spremeni različica PHP. Ustvarimo funkcijo, ki primerja trenutno različico s parametrom, in pri shranjevanju dodamo med odvisnosti polje v obliki `[ime funkcije, ...argumenti]`: ```php function checkPhpVersion($ver): bool @@ -175,7 +175,7 @@ $dependencies[Cache::Callbacks] = [ ]; ``` -Seveda lahko vsa merila kombiniramo. Predpomnilnik se izteče, če vsaj eno merilo ni izpolnjeno. +Vsa merila je seveda mogoče kombinirati. Predpomnilnik potem poteče, ko vsaj eno merilo ni izpolnjeno. ```php $dependencies[Cache::Expire] = '20 minutes'; @@ -183,16 +183,16 @@ $dependencies[Cache::Files] = '/path/to/data.yaml'; ``` -Neveljavnost z uporabo oznak .[#toc-invalidation-using-tags] ------------------------------------------------------------- +Invalidacija s pomočjo značk +---------------------------- -Oznake so zelo uporabno orodje za razveljavitev. Vsakemu elementu, ki je shranjen v predpomnilniku, lahko dodelimo seznam oznak, ki so poljubni nizi. Predpostavimo na primer, da imamo stran HTML s člankom in komentarji, ki jo želimo shraniti v predpomnilnik. Pri shranjevanju v predpomnilnik določimo oznake: +Zelo uporabno orodje za invalidacijo so t.i. značke. Vsakemu elementu v predpomnilniku lahko ob shranjevanju dodelimo seznam značk, ki so poljubni nizi. Imejmo na primer HTML stran s člankom in komentarji, ki jo bomo predpomnili. Pri shranjevanju specificiramo značke: ```php $dependencies[Cache::Tags] = ["article/$articleId", "comments/$articleId"]; ``` -Sedaj pa preidimo k upravljanju. Tu imamo obrazec za urejanje člankov. Hkrati s shranjevanjem članka v zbirko podatkov pokličemo ukaz `clean()`, ki bo izbrisal predpomnilniške elemente po oznakah: +Premaknimo se v administracijo. Tu najdemo obrazec za urejanje članka. Skupaj s shranjevanjem članka v podatkovno bazo pokličemo ukaz `clean()`, ki izbriše iz predpomnilnika elemente glede na značko: ```php $cache->clean([ @@ -200,7 +200,7 @@ $cache->clean([ ]); ``` -Prav tako na mestu dodajanja novega komentarja (ali urejanja komentarja) ne bomo pozabili razveljaviti ustrezne oznake: +Enako tako na mestu dodajanja novega komentarja (ali urejanja komentarja) ne pozabimo invalidirati ustrezne značke: ```php $cache->clean([ @@ -208,22 +208,22 @@ $cache->clean([ ]); ``` -Kaj smo dosegli? Da bo naš predpomnilnik HTML razveljavljen (izbrisan) vsakič, ko se članek ali komentarji spremenijo. Pri urejanju članka z ID = 10 se oznaka `article/10` razveljavi in stran HTML z oznako se izbriše iz predpomnilnika. Enako se zgodi, ko pod ustrezen članek vstavite nov komentar. +Kaj smo s tem dosegli? Da se nam bo HTML predpomnilnik invalidiral (brisal), kadarkoli se spremeni članek ali komentarji. Ko se ureja članek z ID = 10, pride do prisilne invalidacije značke `article/10` in HTML stran, ki nosi navedeno značko, se izbriše iz predpomnilnika. Enako se zgodi pri vstavljanju novega komentarja pod ustrezen članek. .[note] -Oznake zahtevajo [Journal |#Journal]. +Značke zahtevajo t.i. [#Dnevnik Journal]. -Neveljavnost po prednostni nalogi .[#toc-invalidation-by-priority] ------------------------------------------------------------------- +Invalidacija s pomočjo prioritete +--------------------------------- -Posameznim elementom v predpomnilniku lahko določimo prioriteto in tako jih bo mogoče nadzorovano izbrisati, ko na primer predpomnilnik preseže določeno velikost: +Posameznim elementom v predpomnilniku lahko nastavimo prioriteto, s pomočjo katere jih bo mogoče brisati, ko na primer predpomnilnik preseže določeno velikost: ```php $dependencies[Cache::Priority] = 50; ``` -Izbriši vse elemente s prioriteto, ki je enaka ali manjša od 100: +Izbrišemo vse elemente s prioriteto enako ali manjšo od 100: ```php $cache->clean([ @@ -232,11 +232,11 @@ $cache->clean([ ``` .[note] -Prednostne naloge zahtevajo tako imenovani [dnevnik |#Journal]. +Prioritete zahtevajo t.i. [#Dnevnik Journal]. -Počisti predpomnilnik .[#toc-clear-cache] ------------------------------------------ +Brisanje predpomnilnika +----------------------- Parameter `Cache::All` izbriše vse: @@ -247,51 +247,70 @@ $cache->clean([ ``` -Množično branje .[#toc-bulk-reading] -==================================== +Množično branje +=============== -Za množično branje in pisanje v predpomnilnik se uporablja metoda `bulkLoad()`, pri kateri posredujemo polje ključev in dobimo polje vrednosti: +Za množično branje in pisanje v predpomnilnik služi metoda `bulkLoad()`, kateri posredujemo polje ključev in dobimo polje vrednosti: ```php $values = $cache->bulkLoad($keys); ``` -Metoda `bulkLoad()` deluje podobno kot `load()` z drugim parametrom povratne zveze, ki mu posredujemo ključ ustvarjenega elementa: +Metoda `bulkLoad()` deluje podobno kot `load()` tudi z drugim parametrom povratnim klicem, kateremu se posreduje ključ generiranega elementa: ```php $values = $cache->bulkLoad($keys, function ($key, &$dependencies) { - $computedValue = /* ... */; // težka računanja. + $computedValue = /* ... */; // zahteven izračun return $computedValue; }); ``` -Izhodno predpomnjenje .[#toc-output-caching] -============================================ +Uporaba s PSR-16 .{data-version:3.3.1} +====================================== -Izhod lahko zelo elegantno zajamete in shranite v predpomnilnik: +Za uporabo Nette Cache z vmesnikom PSR-16 lahko uporabite adapter `PsrCacheAdapter`. Omogoča brezšivno integracijo med Nette Cache in katerokoli kodo ali knjižnico, ki pričakuje PSR-16 združljiv predpomnilnik. + +```php +$psrCache = new Nette\Bridges\Psr\PsrCacheAdapter($storage); +``` + +Zdaj lahko uporabljate `$psrCache` kot PSR-16 predpomnilnik: + +```php +$psrCache->set('key', 'value', 3600); // shrani vrednost za 1 uro +$value = $psrCache->get('key', 'default'); +``` + +Adapter podpira vse metode, definirane v PSR-16, vključno z `getMultiple()`, `setMultiple()` in `deleteMultiple()`. + + +Predpomnjenje izpisa +==================== + +Zelo elegantno lahko zajamemo in predpomnimo izpis: ```php if ($capture = $cache->capture($key)) { - echo ... // tiskanje nekaterih podatkov + echo ... // izpisujemo podatke - $capture->end(); // shranite izpis v predpomnilnik + $capture->end(); // shranimo izpis v predpomnilnik } ``` -V primeru, da je izhod že v predpomnilniku, ga metoda `capture()` izpiše in vrne `null`, tako da se pogoj ne bo izvedel. V nasprotnem primeru prične z bufferiranjem izpisa in vrne objekt `$capture`, s pomočjo katerega na koncu shranimo podatke v predpomnilnik. +V primeru, da je izpis že shranjen v predpomnilniku, ga metoda `capture()` izpiše in vrne `null`, torej se pogoj ne izvede. V nasprotnem primeru začne zajemati izpis in vrne objekt `$capture`, s pomočjo katerega na koncu izpisane podatke shranimo v predpomnilnik. .[note] V različici 3.0 se je metoda imenovala `$cache->start()`. -Predpomnjenje v Latte .[#toc-caching-in-latte] -============================================== +Predpomnjenje v Latte +===================== -Predpomnjenje v predlogah [Latte |latte:] je zelo enostavno, le del predloge ovijte z oznakami `{cache}...{/cache}`. Predpomnilnik se samodejno razveljavi, ko se spremeni izvorna predloga (vključno z vsemi vključenimi predlogami znotraj oznak `{cache}` ). Značke `{cache}` so lahko vgnezdene, in ko je vgnezdeni blok razveljavljen (na primer z oznako), je razveljavljen tudi nadrejeni blok. +Predpomnjenje v predlogah [Latte|latte:] je zelo enostavno, dovolj je, da del predloge ovijemo z značkami `{cache}...{/cache}`. Predpomnilnik se samodejno invalidira v trenutku, ko se spremeni izvorna predloga (vključno z morebitnimi vključenimi predlogami znotraj bloka cache). Značke `{cache}` lahko gnezdijo ena v drugo in ko se vgnezden blok razveljavi (na primer z značko), se razveljavi tudi nadrejeni blok. -V oznaki je mogoče določiti ključe, na katere bo vezan predpomnilnik (tukaj spremenljivka `$id`), ter nastaviti potek veljavnosti in [razveljavitev oznake |#Invalidation using Tags] +V znački je mogoče navesti ključe, na katere bo predpomnilnik vezan (tu spremenljivka `$id`) in nastaviti potek ter [značke za razveljavitev |#Invalidacija s pomočjo značk] ```latte {cache $id, expire: '20 minutes', tags: [tag1, tag2]} @@ -299,9 +318,9 @@ V oznaki je mogoče določiti ključe, na katere bo vezan predpomnilnik (tukaj s {/cache} ``` -Vsi parametri so neobvezni, zato vam ni treba navesti poteka veljavnosti, oznak ali ključev. +Vsi elementi so neobvezni, zato nam ni treba navajati niti poteka, niti značk, na koncu niti ključev. -Uporabo predpomnilnika lahko tudi pogojite s spletno stranjo `if` - vsebina se nato predpomeni samo, če je pogoj izpolnjen: +Uporabo predpomnilnika lahko tudi pogojimo s pomočjo `if` - vsebina se bo potem predpomnila samo, če bo pogoj izpolnjen: ```latte {cache $id, if: !$form->isSubmitted()} @@ -310,23 +329,23 @@ Uporabo predpomnilnika lahko tudi pogojite s spletno stranjo `if` - vsebina se n ``` -Shrambe .[#toc-storages] -======================== +Shrambe +======= -Skladišče je objekt, ki predstavlja mesto, kjer so podatki fizično shranjeni. Uporabimo lahko podatkovno zbirko, strežnik Memcached ali najbolj razpoložljivo shrambo, ki so datoteke na disku. +Shramba je objekt, ki predstavlja mesto, kamor se podatki fizično shranjujejo. Lahko uporabimo podatkovno bazo, strežnik Memcached ali najdostopnejšo shrambo, kar so datoteke na disku. -|---------------------- -| Shranjevanje | Opis -|---------------------- -| [FileStorage |#FileStorage] | privzeto shranjevanje s shranjevanjem v datoteke na disku -| [MemcachedStorage |#MemcachedStorage] | uporablja strežnik `Memcached` -| [MemoryStorage |#MemoryStorage] | podatki so začasno v pomnilniku -| [SQLiteStorage |#SQLiteStorage] | podatki so shranjeni v podatkovni zbirki SQLite -| [DevNullStorage |#DevNullStorage] | podatki niso shranjeni - za namene testiranja +|----------------- +| Shramba | Opis +|----------------- +| [#FileStorage] | privzeta shramba s shranjevanjem v datoteke na disk +| [#MemcachedStorage] | uporablja `Memcached` strežnik +| [#MemoryStorage] | podatki so začasno v pomnilniku +| [#SQLiteStorage] | podatki se shranjujejo v SQLite podatkovno bazo +| [#DevNullStorage] | podatki se ne shranjujejo, primerno za testiranje -Objekt shrambe dobite tako, da ga posredujete z uporabo [vbrizgavanja odvisnosti |dependency-injection:passing-dependencies] s tipom `Nette\Caching\Storage`. Nette privzeto zagotavlja objekt FileStorage, ki shranjuje podatke v podmapo `cache` v imeniku za [začasne datoteke |application:bootstrap#Temporary Files]. +Do objekta shrambe pridete tako, da si ga pustite posredovati s pomočjo [dependency injection |dependency-injection:passing-dependencies] s tipom `Nette\Caching\Storage`. Kot privzeto shrambo Nette ponuja objekt FileStorage, ki shranjuje podatke v podimenik `cache` v imeniku za [začasne datoteke |application:bootstrapping#Začasne datoteke]. -Shranjevanje lahko spremenite v konfiguraciji: +Shrambo lahko spremenite v konfiguraciji: ```neon services: @@ -334,31 +353,28 @@ services: ``` -FileStorage .[#toc-filestorage] -------------------------------- +FileStorage +----------- -Predpomnilnik zapiše v datoteke na disku. Shramba `Nette\Caching\Storages\FileStorage` je zelo dobro optimizirana za zmogljivost, predvsem pa zagotavlja popolno atomičnost operacij. Kaj to pomeni? Da se pri uporabi predpomnilnika ne more zgoditi, da bi prebrali datoteko, ki je druga nit še ni v celoti zapisala, ali da bi jo kdo izbrisal "pod rokami". Uporaba predpomnilnika je torej popolnoma varna. +Zapisuje predpomnilnik v datoteke na disku. Shramba `Nette\Caching\Storages\FileStorage` je zelo dobro optimizirana za zmogljivost in predvsem zagotavlja polno atomičnost operacij. Kaj to pomeni? Da se pri uporabi predpomnilnika ne more zgoditi, da bi prebrali datoteko, ki še ni bila popolnoma zapisana s strani druge niti, ali da bi vam jo kdo "pod roko" izbrisal. Uporaba predpomnilnika je torej popolnoma varna. -Ta shramba ima vgrajeno tudi pomembno funkcijo, ki preprečuje izjemno povečanje porabe procesorja, ko je predpomnilnik izbrisan ali hladen (tj. ni ustvarjen). To je preprečevanje "stampeda predpomnilnika":https://en.wikipedia.org/wiki/Cache_stampede. -Zgodi se, da v nekem trenutku obstaja več hkratnih zahtevkov, ki želijo isto stvar iz predpomnilnika (npr. rezultat drage poizvedbe SQL), in ker ta ni shranjena v predpomnilniku, vsi procesi začnejo izvajati isto poizvedbo SQL. -Obremenitev procesorja se pomnoži in lahko se celo zgodi, da se nobena nit ne more odzvati v časovnem roku, predpomnilnik se ne ustvari in aplikacija se sesuje. -Na srečo predpomnilnik v Nette deluje tako, da ob več hkratnih zahtevah za en element le-tega ustvari le prva nit, druge počakajo in nato uporabijo ustvarjen rezultat. +Ta shramba ima tudi vgrajeno pomembno funkcijo, ki preprečuje ekstremno povečanje uporabe CPU v trenutku, ko se predpomnilnik izbriše ali še ni ogret (tj. ustvarjen). Gre za preprečevanje "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Zgodi se, da se v enem trenutku zbere večje število sočasnih zahtev, ki želijo iz predpomnilnika isto stvar (npr. rezultat drage SQL poizvedbe) in ker v predpomnilniku ni, začnejo vsi procesi izvajati isto SQL poizvedbo. Obremenitev se tako množi in lahko se celo zgodi, da nobena nit ne uspe odgovoriti v časovni omejitvi, predpomnilnik se ne ustvari in aplikacija propade. Na srečo predpomnilnik v Nette deluje tako, da pri več sočasnih zahtevah za en element ga generira samo prva nit, ostale čakajo in nato uporabijo generirani rezultat. -Primer ustvarjanja shrambe datotek: +Primer ustvarjanja FileStorage: ```php -// shranjevanje bo imenik '/path/to/temp' na disku +// shramba bo imenik '/path/to/temp' na disku $storage = new Nette\Caching\Storages\FileStorage('/path/to/temp'); ``` -MemcachedStorage .[#toc-memcachedstorage] ------------------------------------------ +MemcachedStorage +---------------- -Strežnik [Memcached |https://memcached.org] je visoko zmogljiv porazdeljeni sistem za shranjevanje, katerega adapter je `Nette\Caching\Storages\MemcachedStorage`. V konfiguraciji določite naslov IP in vrata, če se razlikujejo od standardnih 11211. +Strežnik [Memcached|https://memcached.org] je visoko zmogljiv sistem shranjevanja v porazdeljenem pomnilniku, katerega adapter je `Nette\Caching\Storages\MemcachedStorage`. V konfiguraciji navedemo IP naslov in vrata, če se razlikujejo od standardnih 11211. .[caution] -Zahteva razširitev PHP `memcached`. +Zahteva PHP razširitev `memcached`. ```neon services: @@ -366,19 +382,19 @@ services: ``` -MemoryStorage .[#toc-memorystorage] ------------------------------------ +MemoryStorage +------------- -`Nette\Caching\Storages\MemoryStorage` je pomnilnik, ki podatke hrani v polju PHP in se tako izgubi, ko se zahteva konča. +`Nette\Caching\Storages\MemoryStorage` je shramba, ki podatke shranjuje v PHP polje, in se torej z zaključkom zahteve izgubijo. -Shramba SQLiteStorage .[#toc-sqlitestorage] -------------------------------------------- +SQLiteStorage +------------- -Podatkovna baza SQLite in adapter `Nette\Caching\Storages\SQLiteStorage` ponujata način predpomnjenja v eni sami datoteki na disku. V konfiguraciji je določena pot do te datoteke. +Podatkovna baza SQLite in adapter `Nette\Caching\Storages\SQLiteStorage` ponujata način, kako shranjevati predpomnilnik v eno datoteko na disku. V konfiguraciji navedemo pot do te datoteke. .[caution] -Zahteva razširitvi PHP `pdo` in `pdo_sqlite`. +Zahteva PHP razširitvi `pdo` in `pdo_sqlite`. ```neon services: @@ -386,16 +402,16 @@ services: ``` -DevNullStorage .[#toc-devnullstorage] -------------------------------------- +DevNullStorage +-------------- -Posebna izvedba shrambe je `Nette\Caching\Storages\DevNullStorage`, ki dejansko sploh ne shranjuje podatkov. Zato je primerna za testiranje, če želimo odpraviti učinek predpomnilnika. +Posebna implementacija shrambe je `Nette\Caching\Storages\DevNullStorage`, ki dejansko podatkov sploh ne shranjuje. Je tako primerna za testiranje, ko želimo eliminirati vpliv predpomnilnika. -Uporaba predpomnilnika v kodi .[#toc-using-cache-in-code] -========================================================= +Uporaba predpomnilnika v kodi +============================= -Pri uporabi predpomnilnika v kodi lahko to storite na dva načina. Prvi je, da objekt za shranjevanje dobite tako, da ga posredujete z uporabo [injekcije odvisnosti |dependency-injection:passing-dependencies], nato pa ustvarite objekt `Cache`: +Pri uporabi predpomnilnika v kodi imamo dva načina, kako to storiti. Prvi je ta, da si pustimo posredovati s pomočjo [dependency injection |dependency-injection:passing-dependencies] shrambo in ustvarimo objekt `Cache`: ```php use Nette; @@ -411,7 +427,7 @@ class ClassOne } ``` -Drugi način je, da pridobite objekt za shranjevanje `Cache`: +Druga možnost je, da si pustimo neposredno posredovati objekt `Cache`: ```php class ClassTwo @@ -423,7 +439,7 @@ class ClassTwo } ``` -Objekt `Cache` se nato ustvari neposredno v konfiguraciji na naslednji način: +Objekt `Cache` se potem ustvari neposredno v konfiguraciji na ta način: ```neon services: @@ -431,10 +447,10 @@ services: ``` -Dnevnik .[#toc-journal] -======================= +Dnevnik (Journal) +================= -Nette shranjuje oznake in prednostne naloge v tako imenovani dnevnik. Privzeto se za to uporabljata SQLite in datoteka `journal.s3db`, zahtevani pa sta **razširitvi PHP `pdo` in `pdo_sqlite`.** +Nette si značke in prioritete shranjuje v t.i. dnevnik (journal). Standardno se za to uporablja SQLite in datoteka `journal.s3db` ter **zahtevata se PHP razširitvi `pdo` in `pdo_sqlite`.** Dnevnik lahko spremenite v konfiguraciji: @@ -444,4 +460,25 @@ services: ``` -{{leftbar: nette:@menu-topics}} +Storitve DI +=========== + +Te storitve se dodajo v DI vsebnik: + +| Ime | Tip | Opis +|---------------------------------------------------------- +| `cache.journal` | [api:Nette\Caching\Storages\Journal] | dnevnik +| `cache.storage` | [api:Nette\Caching\Storage] | shramba + + +Izklop predpomnilnika +===================== + +Ena od možnosti, kako izklopiti predpomnilnik v aplikaciji, je nastaviti kot shrambo [#DevNullStorage]: + +```neon +services: + cache.storage: Nette\Caching\Storages\DevNullStorage +``` + +Ta nastavitev nima vpliva na predpomnjenje predlog v Latte ali DI vsebnika, ker te knjižnice ne uporabljajo storitev nette/caching in si upravljajo predpomnilnik samostojno. Njihovega predpomnilnika sicer [ni treba |nette:troubleshooting#Kako izklopiti predpomnilnik med razvojem] v razvojnem načinu izklapljati. diff --git a/caching/sl/@meta.texy b/caching/sl/@meta.texy new file mode 100644 index 0000000000..282883a3d6 --- /dev/null +++ b/caching/sl/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Dokumentacija}} +{{leftbar: nette:@menu-topics}} diff --git a/caching/tr/@home.texy b/caching/tr/@home.texy index 8c25321fab..a14067b97e 100644 --- a/caching/tr/@home.texy +++ b/caching/tr/@home.texy @@ -1,80 +1,80 @@ -Önbellekleme -************ +Nette Caching +************* <div class=perex> -Önbellek, bir kez zor alınan verileri gelecekte kullanılmak üzere depolayarak uygulamanızı hızlandırır. Size göstereceğiz: +Önbellek, bir kez zorlukla elde edilen verileri bir sonraki kullanım için saklayarak uygulamanızı hızlandırır. Göstereceğiz: -- Önbellek nasıl kullanılır -- Önbellek depolama alanı nasıl değiştirilir -- Önbellek nasıl düzgün şekilde geçersiz kılınır +- önbellek nasıl kullanılır +- depolama nasıl değiştirilir +- önbellek nasıl doğru bir şekilde geçersiz kılınır </div> -Nette'de önbelleği kullanmak çok kolaydır, aynı zamanda çok gelişmiş önbellekleme ihtiyaçlarını da karşılar. Performans ve %100 dayanıklılık için tasarlanmıştır. Temel olarak, en yaygın arka uç depolama için adaptörler bulacaksınız. Etiket tabanlı geçersiz kılma, önbellek izdiham koruması, zaman aşımı vb. sağlar. +Nette'de önbellek kullanımı çok kolaydır, ancak çok gelişmiş ihtiyaçları bile karşılar. Performans ve %100 dayanıklılık için tasarlanmıştır. Temelde en yaygın arka uç depolama alanları için adaptörler bulacaksınız. Etiket tabanlı geçersizleştirmeyi, zaman aşımını destekler, önbellek izdihamına karşı koruması vardır vb. -Kurulum .[#toc-installation] -============================ +Kurulum +======= -[Composer'ı |best-practices:composer] kullanarak paketi indirin ve yükleyin: +Kütüphaneyi [Composer|best-practices:composer] aracını kullanarak indirip kurabilirsiniz: ```shell composer require nette/caching ``` -Temel Kullanım .[#toc-basic-usage] -================================== +Temel Kullanım +============== -Önbellek ile çalışmanın merkezi [api:Nette\Caching\Cache] nesnesidir. Örneğini oluştururuz ve depolama alanını yapıcıya parametre olarak aktarırız. Bu, verilerin fiziksel olarak depolanacağı yeri temsil eden bir nesnedir (veritabanı, Memcached, diskteki dosyalar, ...). Depolama nesnesini `Nette\Caching\Storage` tipi ile [bağımlılık enjeksiyonu |dependency-injection:passing-dependencies] kullanarak geçirerek elde edersiniz. [Depolama bölümündeki |#Storages] tüm temel bilgileri öğreneceksiniz. +Önbellekle çalışmanın merkezi noktası [api:Nette\Caching\Cache] nesnesidir. Bir örneğini oluştururuz ve kurucuya parametre olarak depolama adı verilen bir nesne geçiririz. Bu, verilerin fiziksel olarak depolanacağı yeri (veritabanı, Memcached, diskteki dosyalar, ...) temsil eden bir nesnedir. Depolamaya, `Nette\Caching\Storage` türüyle [dependency injection |dependency-injection:passing-dependencies] kullanarak geçirmemizi isteyerek erişiriz. Tüm önemli bilgileri [Depolama bölümünde |#Depolama] bulacaksınız. .[warning] -Sürüm 3.0'da arayüz hala `I` prefix, so the name was `Nette\Caching\IStorage` şeklindeydi. Ayrıca, `Cache` sınıfının sabitleri büyük harfle yazılıyordu, yani örneğin `Cache::Expire` yerine `Cache::EXPIRE`. +Sürüm 3.0'da, arayüzün hala `I` öneki vardı, bu nedenle adı `Nette\Caching\IStorage` idi. Ayrıca, `Cache` sınıfının sabitleri büyük harflerle yazılmıştı, örneğin `Cache::Expire` yerine `Cache::EXPIRE`. -Aşağıdaki örnekler için, bir `Cache` takma adımız ve `$storage` değişkeninde bir depolama alanımız olduğunu varsayalım. +Aşağıdaki örnekler için, `Cache` takma adını oluşturduğumuzu ve `$storage` değişkeninde bir depolama alanına sahip olduğumuzu varsayalım. ```php use Nette\Caching\Cache; -$storage = /* ... */; // instance of Nette\Caching\Storage +$storage = /* ... */; // Nette\Caching\Storage örneği ``` -Önbellek aslında bir *anahtar-değer deposudur*, bu nedenle anahtarlar altındaki verileri tıpkı ilişkisel diziler gibi okur ve yazarız. Uygulamalar bir dizi bağımsız parçadan oluşur ve hepsi tek bir depolama alanı kullansaydı (fikir için: bir diskteki bir dizin), er ya da geç bir anahtar çarpışması olurdu. Nette Framework bu sorunu tüm alanı isim alanlarına (alt dizinler) bölerek çözer. Böylece programın her bir parçası kendi alanını benzersiz bir isimle kullanır ve hiçbir çarpışma meydana gelmez. +Önbellek aslında bir *anahtar-değer deposudur*, yani verileri ilişkisel dizilerde olduğu gibi anahtarlar altında okur ve yazarız. Uygulamalar bir dizi bağımsız bölümden oluşur ve hepsi tek bir depolama alanı kullanırsa (diskte tek bir dizin düşünün), er ya da geç anahtar çakışmaları meydana gelir. Nette Framework, tüm alanı ad alanlarına (alt dizinlere) bölerek sorunu çözer. Programın her bölümü daha sonra benzersiz bir ada sahip kendi alanını kullanır ve artık çakışma olmaz. -Alanın adı, Cache sınıfının yapıcısının ikinci parametresi olarak belirtilir: +Alan adını Cache sınıfının kurucusunun ikinci parametresi olarak belirtiriz: ```php $cache = new Cache($storage, 'Full Html Pages'); ``` -Artık önbellekten okumak ve yazmak için `$cache` nesnesini kullanabiliriz. Her ikisi için de `load()` yöntemi kullanılır. İlk argüman anahtar, ikincisi ise anahtar önbellekte bulunamadığında çağrılan PHP geri çağrısıdır. Geri arama bir değer üretir, geri döndürür ve önbelleğe alır: +Şimdi `$cache` nesnesini kullanarak önbellekten okuyabilir ve ona yazabiliriz. Her ikisi için de `load()` yöntemi kullanılır. İlk argüman anahtardır ve ikincisi, anahtar önbellekte bulunamadığında çağrılan bir PHP geri çağrısıdır. Geri çağrı değeri oluşturur, döndürür ve önbelleğe kaydedilir: ```php $value = $cache->load($key, function () use ($key) { - $computedValue = /* ... */; // ağır hesaplamalar + $computedValue = /* ... */; // pahalı hesaplama return $computedValue; }); ``` -İkinci parametre belirtilmezse `$value = $cache->load($key)`, öğe önbellekte değilse `null` döndürülür. +İkinci parametreyi belirtmezsek `$value = $cache->load($key)`, öğe önbellekte yoksa `null` döndürülür. .[tip] -Harika olan şey, yalnızca dizelerin değil, serileştirilebilir tüm yapıların önbelleğe alınabilmesidir. Ve aynı şey anahtarlar için de geçerlidir. +Harika olan şey, önbelleğe herhangi bir serileştirilebilir yapının kaydedilebilmesidir, yalnızca dizeler olması gerekmez. Ve aynı şey anahtarlar için bile geçerlidir. -Öğe, `remove()` yöntemi kullanılarak önbellekten temizlenir: +Öğeyi önbellekten `remove()` yöntemiyle sileriz: ```php $cache->remove($key); ``` -Bir öğeyi `$cache->save($key, $value, array $dependencies = [])` yöntemini kullanarak da önbelleğe alabilirsiniz. Ancak, `load()` adresini kullanan yukarıdaki yöntem tercih edilir. +Bir öğeyi önbelleğe kaydetmek için `$cache->save($key, $value, array $dependencies = [])` yöntemi de kullanılabilir. Ancak, yukarıda belirtilen `load()` yöntemini kullanmak tercih edilir. -Memoizasyon .[#toc-memoization] -=============================== +Memoizasyon +=========== -Hafızaya alma, aynı şeyi tekrar tekrar hesaplamak yerine bir dahaki sefere kullanabilmeniz için bir işlevin veya yöntemin sonucunu önbelleğe almak anlamına gelir. +Memoizasyon, bir fonksiyon veya metodun çağrısının sonucunu önbelleğe almak anlamına gelir, böylece aynı şeyi tekrar tekrar hesaplamadan bir dahaki sefere kullanabilirsiniz. Metotlar ve fonksiyonlar `call(callable $callback, ...$args)` kullanılarak memoize edilebilir: @@ -82,9 +82,9 @@ Metotlar ve fonksiyonlar `call(callable $callback, ...$args)` kullanılarak memo $result = $cache->call('gethostbyaddr', $ip); ``` -`gethostbyaddr()` işlevi her parametre için yalnızca bir kez çağrılır `$ip` ve bir sonraki sefer önbellekten değer döndürülür. +`gethostbyaddr()` fonksiyonu böylece her `$ip` parametresi için yalnızca bir kez çağrılır ve bir dahaki sefere değer önbellekten döndürülür. -Daha sonra çağrılabilecek bir yöntem veya fonksiyon için memoize edilmiş bir sarmalayıcı oluşturmak da mümkündür: +Ayrıca, daha sonra çağrılabilecek bir metot veya fonksiyon üzerinde memoize edilmiş bir sarmalayıcı oluşturmak da mümkündür: ```php function factorial($num) @@ -94,17 +94,17 @@ function factorial($num) $memoizedFactorial = $cache->wrap('factorial'); -$result = $memoizedFactorial(5); // sayar -$result = $memoizedFactorial(5); // önbellekten döndürür +$result = $memoizedFactorial(5); // ilk kez hesaplar +$result = $memoizedFactorial(5); // ikinci kez önbellekten ``` -Sona Erme ve Geçersiz Kılma .[#toc-expiration-invalidation] -=========================================================== +Sona Erme & Geçersizleştirme +============================ -Önbellekleme ile, daha önce kaydedilen verilerin bazılarının zaman içinde geçersiz hale geleceği sorusunu ele almak gerekir. Nette Framework, verilerin geçerliliğinin nasıl sınırlandırılacağı ve kontrollü bir şekilde nasıl silineceği ("geçersiz kılmak", framework'ün terminolojisini kullanarak) konusunda bir mekanizma sağlar. +Önbelleğe kaydetme ile birlikte, daha önce kaydedilen verilerin ne zaman geçersiz hale geleceği sorusunu çözmek gerekir. Nette Framework, verilerin geçerliliğini sınırlamak veya kontrollü bir şekilde silmek (framework terminolojisinde "geçersiz kılmak") için bir mekanizma sunar. -Verilerin geçerliliği, `save()` yönteminin üçüncü parametresi kullanılarak kaydetme sırasında ayarlanır, örn: +Verilerin geçerliliği, kaydetme anında `save()` yönteminin üçüncü parametresi kullanılarak ayarlanır, örneğin: ```php $cache->save($key, $value, [ @@ -112,7 +112,7 @@ $cache->save($key, $value, [ ]); ``` -Ya da `load()` yöntemindeki geri aramaya referans olarak aktarılan `$dependencies` parametresini kullanarak, örn: +Veya `load()` yönteminin geri çağrısına referansla iletilen `$dependencies` parametresi kullanılarak, örneğin: ```php $value = $cache->load($key, function (&$dependencies) { @@ -121,7 +121,7 @@ $value = $cache->load($key, function (&$dependencies) { }); ``` -Veya `load()` yönteminde 3. parametreyi kullanarak, örn: +Veya `load()` yöntemindeki 3. parametre kullanılarak, örneğin: ```php $value = $cache->load($key, function () { @@ -129,26 +129,26 @@ $value = $cache->load($key, function () { }, [Cache::Expire => '20 minutes']); ``` -Aşağıdaki örneklerde, ikinci varyantı ve dolayısıyla `$dependencies` değişkeninin varlığını varsayacağız. +Sonraki örneklerde, ikinci varyantı ve dolayısıyla `$dependencies` değişkeninin varlığını varsayacağız. -Son kullanma tarihi .[#toc-expiration] --------------------------------------- +Sona Erme +--------- -En basit sona erme zaman sınırıdır. İşte 20 dakika boyunca geçerli verileri nasıl önbelleğe alacağınız: +En basit sona erme, bir zaman sınırıdır. Bu şekilde verileri 20 dakika geçerlilik süresiyle önbelleğe kaydederiz: ```php // saniye sayısını veya UNIX zaman damgasını da kabul eder $dependencies[Cache::Expire] = '20 minutes'; ``` -Her okumada geçerlilik süresini uzatmak istiyorsak, bu şekilde elde edilebilir, ancak dikkat edin, bu önbellek yükünü artıracaktır: +Her okumada geçerlilik süresini uzatmak istersek, bunu aşağıdaki gibi yapabiliriz, ancak dikkatli olun, önbellek ek yükü artacaktır: ```php $dependencies[Cache::Sliding] = true; ``` -Kullanışlı seçenek, belirli bir dosya veya birkaç dosyadan biri değiştirildiğinde verilerin süresinin dolmasına izin verme yeteneğidir. Bu, örneğin, bu dosyaların işlenmesinden kaynaklanan verileri önbelleğe almak için kullanılabilir. Mutlak yolları kullanın. +Bir dosya veya birden fazla dosyadan herhangi biri değiştiğinde verilerin süresinin dolmasına izin verme seçeneği kullanışlıdır. Bu, örneğin bu dosyaların işlenmesinden kaynaklanan verileri önbelleğe kaydederken kullanılabilir. Mutlak yolları kullanın. ```php $dependencies[Cache::Files] = '/path/to/data.yaml'; @@ -156,13 +156,13 @@ $dependencies[Cache::Files] = '/path/to/data.yaml'; $dependencies[Cache::Files] = ['/path/to/data1.yaml', '/path/to/data2.yaml']; ``` -Başka bir öğenin (veya diğerlerinden birinin) süresi dolduğunda önbellekteki bir öğenin süresinin dolmasına izin verebiliriz. Bu, HTML sayfasının tamamını ve diğer anahtarlar altındaki parçalarını önbelleğe aldığımızda kullanılabilir. Parçacık değiştiğinde, sayfanın tamamı geçersiz hale gelir. `frag1` ve `frag2` gibi anahtarlar altında depolanan parçalarımız varsa, kullanacağız: +Bir öğenin süresinin başka bir öğenin (veya birden fazla öğeden herhangi birinin) süresi dolduğunda dolmasına izin verebiliriz. Bu, örneğin tüm bir HTML sayfasını önbelleğe kaydettiğimizde ve parçalarını başka anahtarlar altında sakladığımızda kullanılabilir. Parça değiştiğinde, tüm sayfa geçersiz kılınır. Parçaları örneğin `frag1` ve `frag2` anahtarları altında sakladıysak, şunu kullanırız: ```php $dependencies[Cache::Items] = ['frag1', 'frag2']; ``` -Süre dolumu, öğenin hala geçerli olup olmadığına okuma sırasında her zaman karar veren özel işlevler veya statik yöntemler kullanılarak da kontrol edilebilir. Örneğin, PHP sürümü her değiştiğinde öğenin süresinin dolmasına izin verebiliriz. Geçerli sürümü parametre ile karşılaştıran bir işlev oluşturacağız ve kaydederken şu biçimde bir dizi ekleyeceğiz `[function name, ...arguments]` bağımlılıklara: +Sona erme, her okumada öğenin hala geçerli olup olmadığına karar veren özel fonksiyonlar veya statik metotlar kullanılarak da kontrol edilebilir. Bu şekilde, örneğin PHP sürümü değiştiğinde öğenin süresinin dolmasına izin verebiliriz. Mevcut sürümü parametreyle karşılaştıran bir fonksiyon oluştururuz ve kaydederken bağımlılıklar arasına `[fonksiyon adı, ...argümanlar]` şeklinde bir dizi ekleriz: ```php function checkPhpVersion($ver): bool @@ -171,11 +171,11 @@ function checkPhpVersion($ver): bool } $dependencies[Cache::Callbacks] = [ - ['checkPhpVersion', PHP_VERSION_ID] // checkPhpVersion(...) === false olduğunda sona erer + ['checkPhpVersion', PHP_VERSION_ID] // checkPhpVersion(...) === false olduğunda süresi dolar ]; ``` -Elbette tüm kriterler birleştirilebilir. En az bir kriter karşılanmadığında önbellek sona erer. +Tüm kriterler elbette birleştirilebilir. Önbellek daha sonra en az bir kriter karşılanmadığında sona erer. ```php $dependencies[Cache::Expire] = '20 minutes'; @@ -183,16 +183,16 @@ $dependencies[Cache::Files] = '/path/to/data.yaml'; ``` -Etiket Kullanarak Geçersiz Kılma .[#toc-invalidation-using-tags] ----------------------------------------------------------------- +Etiketlerle Geçersizleştirme +---------------------------- -Etiketler çok kullanışlı bir geçersiz kılma aracıdır. Önbellekte depolanan her bir öğeye rastgele dizeler olan bir etiket listesi atayabiliriz. Örneğin, önbelleğe almak istediğimiz bir makale ve yorumlar içeren bir HTML sayfamız olduğunu varsayalım. Bu yüzden önbelleğe kaydederken etiketleri belirtiriz: +Çok kullanışlı bir geçersizleştirme aracı etiketlerdir. Önbellekteki her öğeye, herhangi bir dize olabilen bir etiket listesi atayabiliriz. Örneğin, önbelleğe alacağımız bir makale ve yorumları içeren bir HTML sayfamız olsun. Kaydederken etiketleri belirtiriz: ```php $dependencies[Cache::Tags] = ["article/$articleId", "comments/$articleId"]; ``` -Şimdi, yönetime geçelim. Burada makale düzenleme için bir formumuz var. Makaleyi bir veritabanına kaydetmekle birlikte, önbelleğe alınmış öğeleri etikete göre silecek olan `clean()` komutunu çağırıyoruz: +Yönetim paneline geçelim. Burada makaleyi düzenlemek için bir form bulacağız. Makaleyi veritabanına kaydetmekle birlikte, etikete göre önbellekten öğeleri silen `clean()` komutunu çağıracağız: ```php $cache->clean([ @@ -200,7 +200,7 @@ $cache->clean([ ]); ``` -Aynı şekilde, yeni bir yorum ekleme (veya bir yorumu düzenleme) yerine, ilgili etiketi geçersiz kılmayı unutmayacağız: +Benzer şekilde, yeni bir yorum ekleme (veya bir yorumu düzenleme) yerinde, ilgili etiketi geçersiz kılmayı unutmayacağız: ```php $cache->clean([ @@ -208,22 +208,22 @@ $cache->clean([ ]); ``` -Ne elde ettik? Makale veya yorumlar her değiştiğinde HTML önbelleğimizin geçersiz kılınmasını (silinmesini). ID = 10 olan bir makaleyi düzenlerken, `article/10` etiketi geçersiz kılınmaya zorlanır ve etiketi taşıyan HTML sayfası önbellekten silinir. Aynı şey ilgili makalenin altına yeni bir yorum eklediğinizde de gerçekleşir. +Bununla ne başardık? Makale veya yorumlar değiştiğinde HTML önbelleğimizin geçersiz kılınmasını (silinmesini) sağladık. ID = 10 olan bir makale düzenlendiğinde, `article/10` etiketinin zorunlu geçersizleştirilmesi gerçekleşir ve belirtilen etiketi taşıyan HTML sayfası önbellekten silinir. Aynı şey, ilgili makalenin altına yeni bir yorum eklendiğinde de olur. .[note] -Etiketler [Dergi |#Journal] gerektirir. +Etiketler [#Journal] gerektirir. -Önceliğe Göre Geçersiz Kılma .[#toc-invalidation-by-priority] -------------------------------------------------------------- +Öncelikle Geçersizleştirme +-------------------------- -Önbellekteki her bir öğe için önceliği ayarlayabiliriz ve örneğin önbellek belirli bir boyutu aştığında bunları kontrollü bir şekilde silmek mümkün olacaktır: +Önbellekteki bireysel öğelere bir öncelik ayarlayabiliriz, bu sayede örneğin önbellek belirli bir boyutu aştığında bunları silebiliriz: ```php $dependencies[Cache::Priority] = 50; ``` -Önceliği 100'e eşit veya daha az olan tüm öğeleri silin: +100'e eşit veya daha düşük önceliğe sahip tüm öğeleri sileceğiz: ```php $cache->clean([ @@ -232,13 +232,13 @@ $cache->clean([ ``` .[note] -Öncelikler sözde [Dergi |#Journal] gerektirir. +Öncelikler [#Journal] gerektirir. -Önbelleği Temizle .[#toc-clear-cache] -------------------------------------- +Önbelleği Silme +--------------- -`Cache::All` parametresi her şeyi temizler: +`Cache::All` parametresi her şeyi siler: ```php $cache->clean([ @@ -247,51 +247,70 @@ $cache->clean([ ``` -Toplu Okuma .[#toc-bulk-reading] -================================ +Toplu Okuma +=========== -Önbelleğe toplu okuma ve yazma için, bir anahtar dizisi ilettiğimiz ve bir değer dizisi elde ettiğimiz `bulkLoad()` yöntemi kullanılır: +Önbelleğe toplu okuma ve yazma işlemleri için `bulkLoad()` yöntemi kullanılır, buna anahtar dizisini geçiririz ve değer dizisini alırız: ```php $values = $cache->bulkLoad($keys); ``` -`bulkLoad()` yöntemi, oluşturulan öğenin anahtarının aktarıldığı ikinci geri arama parametresiyle `load()` yöntemine benzer şekilde çalışır: +`bulkLoad()` yöntemi, oluşturulan öğenin anahtarını alan ikinci bir geri çağırma parametresiyle `load()` yöntemine benzer şekilde çalışır: ```php $values = $cache->bulkLoad($keys, function ($key, &$dependencies) { - $computedValue = /* ... */; // ağır hesaplamalar + $computedValue = /* ... */; // pahalı hesaplama return $computedValue; }); ``` -Çıktı Önbelleğe Alma .[#toc-output-caching] -=========================================== +PSR-16 ile Kullanım .{data-version:3.3.1} +========================================= + +Nette Cache'i PSR-16 arayüzüyle kullanmak için `PsrCacheAdapter` adaptörünü kullanabilirsiniz. Nette Cache ile PSR-16 uyumlu bir önbellek bekleyen herhangi bir kod veya kütüphane arasında sorunsuz entegrasyon sağlar. + +```php +$psrCache = new Nette\Bridges\Psr\PsrCacheAdapter($storage); +``` + +Şimdi `$psrCache`'i PSR-16 önbelleği olarak kullanabilirsiniz: + +```php +$psrCache->set('key', 'value', 3600); // değeri 1 saatliğine kaydeder +$value = $psrCache->get('key', 'default'); +``` + +Adaptör, `getMultiple()`, `setMultiple()` ve `deleteMultiple()` dahil olmak üzere PSR-16'da tanımlanan tüm yöntemleri destekler. -Çıktı çok zarif bir şekilde yakalanabilir ve önbelleğe alınabilir: + +Çıktıyı Önbelleğe Alma +====================== + +Çıktıyı yakalamak ve önbelleğe almak çok zarif bir şekilde yapılabilir: ```php if ($capture = $cache->capture($key)) { - echo ... // bazı verileri yazdırıyor + echo ... // verileri yazdırıyoruz - $capture->end(); // çıktıyı önbelleğe kaydet + $capture->end(); // çıktıyı önbelleğe kaydediyoruz } ``` -Çıktının önbellekte zaten mevcut olması durumunda, `capture()` yöntemi çıktıyı yazdırır ve `null` döndürür, böylece koşul yürütülmez. Aksi takdirde, çıktıyı tamponlamaya başlar ve sonunda verileri önbelleğe kaydettiğimiz `$capture` nesnesini döndürür. +Çıktı zaten önbellekteyse, `capture()` yöntemi onu yazdırır ve `null` döndürür, bu nedenle koşul yürütülmez. Aksi takdirde, çıktıyı yakalamaya başlar ve sonunda yazdırılan verileri önbelleğe kaydettiğimiz `$capture` nesnesini döndürür. .[note] -Sürüm 3.0'da bu yöntem `$cache->start()` olarak adlandırılmıştır. +Sürüm 3.0'da yöntemin adı `$cache->start()` idi. -Latte'de Önbellekleme .[#toc-caching-in-latte] -============================================== +Latte'de Önbelleğe Alma +======================= -[Latte |latte:] şablonlarında önbelleğe almak çok kolaydır, şablonun bir kısmını etiketlerle sarmanız yeterlidir `{cache}...{/cache}`. Kaynak şablon değiştiğinde önbellek otomatik olarak geçersiz kılınır ( `{cache}` etiketleri içinde bulunan tüm şablonlar dahil). Etiketler `{cache}` iç içe geçebilir ve iç içe geçmiş bir blok geçersiz kılındığında (örneğin, bir etiket tarafından), üst blok da geçersiz kılınır. +[Latte|latte:] şablonlarında önbelleğe alma çok kolaydır, şablonun bir bölümünü `{cache}...{/cache}` etiketleriyle sarmak yeterlidir. Kaynak şablon değiştiğinde (önbellek bloğu içindeki dahil edilen şablonlar dahil) önbellek otomatik olarak geçersiz kılınır. `{cache}` etiketleri iç içe yerleştirilebilir ve iç içe geçmiş bir blok geçersiz kılındığında (örneğin bir etiketle), üst blok da geçersiz kılınır. -Etikette, önbelleğin bağlanacağı anahtarları (burada `$id` değişkeni) belirtmek ve sona erme ve [geçersiz |#Invalidation using Tags] kılma etiketlerini ayarlamak mümkündür +Etikette, önbelleğin bağlanacağı anahtarları (burada `$id` değişkeni) belirtebilir ve sona erme süresini ve [geçersizleştirme etiketlerini |#Etiketlerle Geçersizleştirme] ayarlayabilirsiniz. ```latte {cache $id, expire: '20 minutes', tags: [tag1, tag2]} @@ -299,9 +318,9 @@ Etikette, önbelleğin bağlanacağı anahtarları (burada `$id` değişkeni) be {/cache} ``` -Tüm parametreler isteğe bağlıdır, bu nedenle süre sonu, etiket veya anahtar belirtmeniz gerekmez. +Tüm öğeler isteğe bağlıdır, bu nedenle ne sona erme süresini ne de etiketleri, hatta anahtarları bile belirtmemiz gerekmez. -Önbelleğin kullanımı `if` tarafından da koşullandırılabilir - içerik yalnızca koşul karşılandığında önbelleğe alınacaktır: +Önbellek kullanımı ayrıca `if` kullanılarak koşullandırılabilir - içerik yalnızca koşul karşılanırsa önbelleğe alınır: ```latte {cache $id, if: !$form->isSubmitted()} @@ -310,23 +329,23 @@ Tüm parametreler isteğe bağlıdır, bu nedenle süre sonu, etiket veya anahta ``` -Depolar .[#toc-storages] -======================== +Depolama +======== -Depolama alanı, verilerin fiziksel olarak depolandığı yeri temsil eden bir nesnedir. Bir veritabanı, bir Memcached sunucusu veya diskteki dosyalar olan en kullanılabilir depolama alanını kullanabiliriz. +Depolama, verilerin fiziksel olarak depolandığı yeri temsil eden bir nesnedir. Bir veritabanı, Memcached sunucusu veya en erişilebilir depolama alanı olan diskteki dosyaları kullanabiliriz. -|---------------------- +|----------------- | Depolama | Açıklama -|---------------------- -| [FileStorage |#FileStorage] | diskteki dosyalara kaydetme ile varsayılan depolama -| [MemcachedStorage |#MemcachedStorage] | `Memcached` sunucusunu kullanır -| [MemoryStorage |#MemoryStorage] | veriler geçici olarak bellektedir -| [SQLiteStorage |#SQLiteStorage] | veriler SQLite veritabanında saklanır -| [DevNullStorage |#DevNullStorage] | veriler saklanmıyor - test amaçlı +|----------------- +| [#FileStorage] | diske dosyalara kaydeden varsayılan depolama +| [#MemcachedStorage] | `Memcached` sunucusunu kullanır +| [#MemoryStorage] | veriler geçici olarak bellekte tutulur +| [#SQLiteStorage] | veriler SQLite veritabanına kaydedilir +| [#DevNullStorage] | veriler kaydedilmez, test için uygundur -Depolama nesnesini `Nette\Caching\Storage` türüyle [bağımlılık enjeksiyonu |dependency-injection:passing-dependencies] kullanarak geçirerek elde edersiniz. Varsayılan olarak Nette, verileri [geçici dosyalar |application:bootstrap#Temporary Files] için dizindeki `cache` alt klasöründe depolayan bir FileStorage nesnesi sağlar. +Depolama nesnesine, `Nette\Caching\Storage` türüyle [dependency injection |dependency-injection:passing-dependencies] kullanarak geçirmemizi isteyerek erişirsiniz. Nette, varsayılan depolama olarak verileri [geçici dosyalar |application:bootstrapping#Geçici Dosyalar] dizinindeki `cache` alt dizinine kaydeden bir FileStorage nesnesi sağlar. -Yapılandırmadaki depolama alanını değiştirebilirsiniz: +Depolamayı yapılandırmada değiştirebilirsiniz: ```neon services: @@ -334,31 +353,28 @@ services: ``` -FileStorage .[#toc-filestorage] -------------------------------- +FileStorage +----------- -Önbelleği diskteki dosyalara yazar. Depolama `Nette\Caching\Storages\FileStorage` performans için çok iyi optimize edilmiştir ve her şeyden önce işlemlerin tam atomikliğini sağlar. Bu ne anlama gelmektedir? Önbelleği kullanırken, başka bir iş parçacığı tarafından henüz tamamen yazılmamış bir dosyayı okumamız veya birinin onu "ellerinizin altında" silmesi mümkün değildir. Bu nedenle önbellek kullanımı tamamen güvenlidir. +Önbelleği diskteki dosyalara yazar. `Nette\Caching\Storages\FileStorage` depolama alanı, performans için çok iyi optimize edilmiştir ve özellikle işlemlerin tam atomikliğini sağlar. Bu ne anlama geliyor? Önbelleği kullanırken, başka bir iş parçacığı tarafından henüz tamamen yazılmamış bir dosyayı okumanız veya birinin onu "ellerinizin altından" silmesi mümkün değildir. Bu nedenle önbellek kullanımı tamamen güvenlidir. -Bu depolama alanı ayrıca, önbellek temizlendiğinde veya soğuk olduğunda (yani oluşturulmadığında) CPU kullanımında aşırı bir artışı önleyen önemli bir yerleşik özelliğe sahiptir. Bu "önbellek izdihamı":https://en.wikipedia.org/wiki/Cache_stampede önlemesidir. -Bir anda önbellekten aynı şeyi isteyen birkaç eşzamanlı istek olur (örneğin pahalı bir SQL sorgusunun sonucu) ve önbelleğe alınmadığı için tüm işlemler aynı SQL sorgusunu yürütmeye başlar. -İşlemci yükü katlanır ve hatta hiçbir iş parçacığı zaman sınırı içinde yanıt veremez, önbellek oluşturulmaz ve uygulama çökebilir. -Neyse ki, Nette'deki önbellek, bir öğe için birden fazla eşzamanlı istek olduğunda, yalnızca ilk iş parçacığı tarafından oluşturulacak, diğerleri bekleyecek ve ardından oluşturulan sonucu kullanacak şekilde çalışır. +Bu depolama alanı ayrıca, önbellek silindiğinde veya henüz ısınmadığında (yani oluşturulmadığında) CPU kullanımında aşırı artışı önleyen önemli bir yerleşik işleve sahiptir. Bu, "önbellek izdihamı":https://en.wikipedia.org/wiki/Cache_stampede önlemesidir. Bazen, aynı anda daha fazla sayıda eşzamanlı istek, önbellekten aynı şeyi (örneğin pahalı bir SQL sorgusunun sonucu) ister ve önbellekte olmadığı için tüm işlemler aynı SQL sorgusunu yürütmeye başlar. Yük böylece katlanır ve hatta hiçbir iş parçacığının zaman sınırında yanıt verememesi, önbelleğin oluşturulmaması ve uygulamanın çökmesi bile olabilir. Neyse ki, Nette'deki önbellek, bir öğe için birden fazla eşzamanlı istek olduğunda, onu yalnızca ilk iş parçacığının oluşturduğu, diğerlerinin beklediği ve ardından oluşturulan sonucu kullandığı şekilde çalışır. -Bir FileStorage oluşturma örneği: +FileStorage oluşturma örneği: ```php -// depolama alanı diskteki '/path/to/temp' dizini olacaktır +// depolama alanı diskteki '/path/to/temp' dizini olacak $storage = new Nette\Caching\Storages\FileStorage('/path/to/temp'); ``` -MemcachedStorage .[#toc-memcachedstorage] ------------------------------------------ +MemcachedStorage +---------------- -[Memcached |https://memcached.org] sunucusu, bağdaştırıcısı `Nette\Caching\Storages\MemcachedStorage` olan yüksek performanslı bir dağıtılmış depolama sistemidir. Yapılandırmada, standart 11211'den farklıysa IP adresini ve bağlantı noktasını belirtin. +[Memcached|https://memcached.org] sunucusu, adaptörü `Nette\Caching\Storages\MemcachedStorage` olan yüksek performanslı bir dağıtılmış bellek depolama sistemidir. Yapılandırmada, standart 11211'den farklıysa IP adresini ve bağlantı noktasını belirtiriz. .[caution] -PHP uzantısı gerektirir `memcached`. +PHP `memcached` uzantısı gerektirir. ```neon services: @@ -366,19 +382,19 @@ services: ``` -MemoryStorage .[#toc-memorystorage] ------------------------------------ +MemoryStorage +------------- -`Nette\Caching\Storages\MemoryStorage` verileri bir PHP dizisinde saklayan ve böylece istek sonlandırıldığında kaybolan bir depolama alanıdır. +`Nette\Caching\Storages\MemoryStorage`, verileri bir PHP dizisinde saklayan ve bu nedenle istek sona erdiğinde kaybolan bir depolama alanıdır. -SQLiteStorage .[#toc-sqlitestorage] ------------------------------------ +SQLiteStorage +------------- -SQLite veritabanı ve bağdaştırıcı `Nette\Caching\Storages\SQLiteStorage` disk üzerinde tek bir dosyada önbellekleme için bir yol sunar. Yapılandırma bu dosyanın yolunu belirtecektir. +SQLite veritabanı ve `Nette\Caching\Storages\SQLiteStorage` adaptörü, önbelleği diskteki tek bir dosyaya kaydetmenin bir yolunu sunar. Yapılandırmada bu dosyanın yolunu belirtiriz. .[caution] -`pdo` ve `pdo_sqlite` PHP uzantılarını gerektirir. +PHP `pdo` ve `pdo_sqlite` uzantılarını gerektirir. ```neon services: @@ -386,16 +402,16 @@ services: ``` -DevNullStorage .[#toc-devnullstorage] -------------------------------------- +DevNullStorage +-------------- -Özel bir depolama uygulaması olan `Nette\Caching\Storages\DevNullStorage`, aslında hiç veri depolamaz. Bu nedenle, önbelleğin etkisini ortadan kaldırmak istiyorsak test etmek için uygundur. +Depolamanın özel bir uygulaması, aslında verileri hiç saklamayan `Nette\Caching\Storages\DevNullStorage`'dır. Bu nedenle, önbelleğin etkisini ortadan kaldırmak istediğimizde test için uygundur. -Kodda Önbellek Kullanımı .[#toc-using-cache-in-code] -==================================================== +Kodda Önbellek Kullanımı +======================== -Kodda önbellekleme kullanırken, bunu yapmanın iki yolu vardır. Birincisi, [bağımlılık enjeksiyonu |dependency-injection:passing-dependencies] kullanarak depolama nesnesini geçirerek elde etmeniz ve ardından `Cache` adresinde bir nesne oluşturmanızdır: +Kodda önbellek kullanırken, bunu yapmanın iki yolu vardır. Birincisi, [dependency injection |dependency-injection:passing-dependencies] kullanarak depolamayı geçirmemizi istemek ve bir `Cache` nesnesi oluşturmaktır: ```php use Nette; @@ -411,7 +427,7 @@ class ClassOne } ``` -İkinci yol ise depolama nesnesini `Cache` adresinden almanızdır: +İkinci seçenek, doğrudan bir `Cache` nesnesi geçirmemizi istemektir: ```php class ClassTwo @@ -423,7 +439,7 @@ class ClassTwo } ``` -`Cache` nesnesi daha sonra aşağıdaki gibi doğrudan yapılandırmada oluşturulur: +`Cache` nesnesi daha sonra doğrudan yapılandırmada şu şekilde oluşturulur: ```neon services: @@ -431,12 +447,12 @@ services: ``` -Dergi .[#toc-journal] -===================== +Journal +======= -Nette etiketleri ve öncelikleri günlük olarak adlandırılan bir dosyada saklar. Bunun için varsayılan olarak SQLite ve `journal.s3db` dosyası kullanılır ve **PHP uzantıları `pdo` ve `pdo_sqlite` gereklidir.** +Nette, etiketleri ve öncelikleri journal adı verilen bir yerde saklar. Standart olarak bunun için SQLite ve `journal.s3db` dosyası kullanılır ve **PHP `pdo` ve `pdo_sqlite` uzantıları gereklidir.** -Yapılandırmada günlüğü değiştirebilirsiniz: +Journal'ı yapılandırmada değiştirebilirsiniz: ```neon services: @@ -444,4 +460,25 @@ services: ``` -{{leftbar: nette:@menu-topics}} +DI Servisleri +============= + +Bu servisler DI konteynerine eklenir: + +| Ad | Tür | Açıklama +|---------------------------------------------------------- +| `cache.journal` | [api:Nette\Caching\Storages\Journal] | journal +| `cache.storage` | [api:Nette\Caching\Storage] | depolama + + +Önbelleği Devre Dışı Bırakma +============================ + +Uygulamada önbelleği devre dışı bırakmanın bir yolu, depolama olarak [#DevNullStorage] ayarlamaktır: + +```neon +services: + cache.storage: Nette\Caching\Storages\DevNullStorage +``` + +Bu ayarın Latte'deki şablonların veya DI konteynerinin önbelleğe alınması üzerinde bir etkisi yoktur, çünkü bu kütüphaneler nette/caching servislerini kullanmaz ve kendi önbelleklerini yönetirler. Ayrıca, geliştirme modunda [önbelleklerini devre dışı bırakmaya gerek yoktur |nette:troubleshooting#Geliştirme Sırasında Önbellek Nasıl Kapatılır]. diff --git a/caching/tr/@meta.texy b/caching/tr/@meta.texy new file mode 100644 index 0000000000..e5c5cea355 --- /dev/null +++ b/caching/tr/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Dokümantasyonu}} +{{leftbar: nette:@menu-topics}} diff --git a/caching/uk/@home.texy b/caching/uk/@home.texy index 6b9dd9c2be..14e19a212f 100644 --- a/caching/uk/@home.texy +++ b/caching/uk/@home.texy @@ -1,38 +1,38 @@ -Кешування -********* +Nette Caching +************* <div class=perex> -Кеш прискорює роботу вашого додатка, зберігаючи дані - одного разу насилу витягнуті - для використання в майбутньому. Ми покажемо вам: +Кеш прискорить ваш застосунок, зберігаючи дані, отримані з великими витратами, для майбутнього використання. Ми покажемо: -- Як використовувати кеш -- Як змінити сховище кешу -- Як правильно анулювати кеш +- як використовувати кеш +- як змінити сховище +- як правильно інвалідувати кеш </div> -Використання кешу в Nette дуже просте, при цьому він також покриває дуже складні потреби в кешуванні. Він розроблений для забезпечення продуктивності та 100% довговічності. В основному, ви знайдете адаптери для найпоширеніших внутрішніх сховищ. Дозволяє анулювання на основі тегів, захист кеш-пам'яті, закінчення часу тощо. +Використання кешу в Nette дуже просте, водночас воно покриває навіть дуже складні потреби. Він розроблений для продуктивності та 100% стійкості. В основі ви знайдете адаптери для найпоширеніших бекенд-сховищ. Дозволяє інвалідацію на основі тегів, часову експірацію, має захист від cache stampede тощо. -Встановлення .[#toc-installation] -================================= +Встановлення +============ -Завантажте та встановіть пакет за допомогою [Composer |best-practices:composer]: +Бібліотеку можна завантажити та встановити за допомогою інструменту [Composer|best-practices:composer]: ```shell composer require nette/caching ``` -Використання .[#toc-basic-usage] -================================ - -Центром роботи з кешем є об'єкт [api:Nette\Caching\Cache]. Ми створюємо його екземпляр і передаємо конструктору як параметр так зване сховище. Це об'єкт, що представляє місце, де дані будуть фізично зберігатися (база даних, Memcached, файли на диску, ...). Ви отримуєте об'єкт сховища, передаючи його за допомогою [впровадження залежностей |dependency-injection:passing-dependencies] з типом `Nette\Caching\Storage`. Усе найнеобхідніше ви знайдете в [розділі С ховища|#Хранилища]. - -Для наступних прикладів припустимо, що у нас є псевдонім `Cache` і сховище у змінній `$storage`. +Базове використання +=================== +Центром роботи з кешем є об'єкт [api:Nette\Caching\Cache]. Створимо його екземпляр і передамо конструктору так зване сховище. Це об'єкт, що представляє місце, де дані будуть фізично зберігатися (база даних, Memcached, файли на диску, ...). До сховища можна отримати доступ, попросивши передати його за допомогою [dependency injection |dependency-injection:passing-dependencies] з типом `Nette\Caching\Storage`. Все важливе ви дізнаєтеся в [розділі Сховища |#Сховища]. +.[warning] +У версії 3.0 інтерфейс ще мав префікс `I`, тому назва була `Nette\Caching\IStorage`. Також константи класу `Cache` були написані великими літерами, наприклад, `Cache::EXPIRE` замість `Cache::Expire`. +Для наступних прикладів припустимо, що ми створили псевдонім `Cache` і маємо сховище у змінній `$storage`. ```php use Nette\Caching\Cache; @@ -40,51 +40,51 @@ use Nette\Caching\Cache; $storage = /* ... */; // екземпляр Nette\Caching\Storage ``` -Кеш фактично є сховищем типу *ключ-значення*, тому ми читаємо і записуємо дані за ключами так само, як і асоціативні масиви. Додатки складаються з декількох незалежних частин, і якби всі вони використовували одне сховище (наприклад, один каталог на диску), рано чи пізно відбудеться зіткнення ключів. Nette Framework вирішує цю проблему шляхом поділу всього простору на простори імен (підкаталоги). У цьому разі кожна частина програми використовує свій власний простір з унікальним ім'ям, і колізії не виникають. +Кеш — це, по суті, *key–value store*, тобто ми читаємо та записуємо дані за ключами так само, як у асоціативних масивах. Застосунки складаються з низки незалежних частин, і якщо всі вони будуть використовувати одне сховище (уявіть собі один каталог на диску), рано чи пізно виникне колізія ключів. Nette Framework вирішує цю проблему, розділяючи весь простір на простори імен (підкаталоги). Кожна частина програми використовує свій простір з унікальною назвою, і колізій більше не виникає. -Ім'я простору вказується як другий параметр конструктора класу Cache: +Назву простору вказуємо як другий параметр конструктора класу Cache: ```php $cache = new Cache($storage, 'Full Html Pages'); ``` -Тепер ми можемо використовувати об'єкт `$cache` для читання і запису з кешу. Для обох використовується метод `load()`. Перший аргумент - ключ, а другий - зворотний виклик PHP, який викликається, коли ключ не знайдено в кеші. Зворотний виклик генерує значення, повертає його і кешує: +Тепер за допомогою об'єкта `$cache` ми можемо читати з кешу та записувати в нього. Для обох дій служить метод `load()`. Першим аргументом є ключ, а другим — PHP callback, який викликається, якщо ключ не знайдено в кеші. Callback генерує значення, повертає його, і воно зберігається в кеші: ```php $value = $cache->load($key, function () use ($key) { - $computedValue = /* ... */; // важкі обчислення + $computedValue = /* ... */; // складне обчислення return $computedValue; }); ``` -Якщо другий параметр не вказано (`$value = $cache->load($key)`), повертається `null`, якщо елемента немає в кеші. +Якщо другий параметр не вказано `$value = $cache->load($key)`, повернеться `null`, якщо елемент відсутній у кеші. .[tip] -Чудово те, що кешувати можна будь-які серіалізовані структури, а не тільки рядки. Те ж саме стосується і ключів. +Чудово те, що в кеш можна зберігати будь-які серіалізовані структури, не обов'язково лише рядки. Те саме стосується навіть ключів. -Елемент видаляється з кешу за допомогою методу `remove()`: +Елемент з кешу видаляємо методом `remove()`: ```php $cache->remove($key); ``` -Ви також можете кешувати елементи за допомогою методу `$cache->save($key, $value, array $dependencies = [])`. Однак описаний вище метод з використанням `load()` є кращим. +Зберегти елемент у кеші можна також методом `$cache->save($key, $value, array $dependencies = [])`. Однак перевага надається вищезгаданому способу за допомогою `load()`. -Мемоїзація .[#toc-memoization] -============================== +Мемоізація +========== -Мемоїзація означає кешування результату функції або методу, щоб ви могли використати його наступного разу, а не обчислювати те саме знову і знову. +Мемоізація означає кешування результату виклику функції або методу, щоб ви могли використовувати його наступного разу без повторного обчислення того самого. -Методи та функції можна викликати мемоїзовано за допомогою `call(callable $callback, ...$args)`: +Мемоізовано можна викликати методи та функції за допомогою `call(callable $callback, ...$args)`: ```php $result = $cache->call('gethostbyaddr', $ip); ``` -Функція `gethostbyaddr()` викликається тільки один раз для кожного параметра `$ip` і наступного разу буде повернуто значення з кешу. +Функція `gethostbyaddr()` таким чином викликається для кожного параметра `$ip` лише один раз, а наступного разу повертається значення з кешу. -Також можна створити мемоїзовану обгортку для методу або функції, яка може бути викликана пізніше: +Також можна створити мемоізовану обгортку над методом або функцією, яку можна викликати пізніше: ```php function factorial($num) @@ -94,17 +94,17 @@ function factorial($num) $memoizedFactorial = $cache->wrap('factorial'); -$result = $memoizedFactorial(5); // підраховує -$result = $memoizedFactorial(5); // повертає з кешу +$result = $memoizedFactorial(5); // обчислює вперше +$result = $memoizedFactorial(5); // вдруге з кешу ``` -Закінчення терміну дії та анулювання .[#toc-expiration-invalidation] -==================================================================== +Експірація та інвалідація +========================= -Під час кешування необхідно вирішити питання про те, що деякі з раніше збережених даних із часом стануть недійсними. Nette Framework надає механізм, як обмежити дійсність даних і як видалити їх контрольованим чином ("зробити їх недійсними", використовуючи термінологію фреймворку). +При зберіганні даних у кеші необхідно вирішувати питання, коли раніше збережені дані стануть недійсними. Nette Framework пропонує механізм для обмеження терміну дії даних або їх керованого видалення (в термінології фреймворку — «інвалідації»). -Дійсність даних встановлюється в момент збереження за допомогою третього параметра методу `save()`, наприклад: +Термін дії даних встановлюється в момент збереження за допомогою третього параметра методу `save()`, наприклад: ```php $cache->save($key, $value, [ @@ -112,7 +112,7 @@ $cache->save($key, $value, [ ]); ``` -Або використовуючи параметр `$dependencies`, переданий за посиланням у зворотний виклик у методі `load()`, наприклад: +Або за допомогою параметра `$dependencies`, переданого за посиланням до callback-функції методу `load()`, наприклад: ```php $value = $cache->load($key, function (&$dependencies) { @@ -121,7 +121,7 @@ $value = $cache->load($key, function (&$dependencies) { }); ``` -Або використовуючи 3-й параметр у методі `load()`, наприклад: +Або за допомогою 3-го параметра в методі `load()`, наприклад: ```php $value = $cache->load($key, function () { @@ -132,37 +132,37 @@ $value = $cache->load($key, function () { У наступних прикладах ми будемо припускати другий варіант і, отже, існування змінної `$dependencies`. -Термін дії .[#toc-expiration] ------------------------------ +Експірація +---------- -Найпростіший виняток - це обмеження за часом. Ось як кешувати дані, дійсні протягом 20 хвилин: +Найпростіша експірація — це часовий ліміт. Таким чином ми зберігаємо дані в кеші з терміном дії 20 хвилин: ```php -// також можна передати число секунд або тимчасову мітку UNIX +// приймає також кількість секунд або UNIX timestamp $dependencies[Cache::Expire] = '20 minutes'; ``` -Якщо ми хочемо збільшувати термін дії під час кожного читання, цього можна домогтися таким чином, але врахуйте, що це збільшить накладні витрати кешу: +Якщо ми хочемо продовжити термін дії при кожному читанні, це можна зробити наступним чином, але будьте обережні, накладні витрати кешу при цьому зростуть: ```php $dependencies[Cache::Sliding] = true; ``` -Зручною опцією є можливість дозволити закінчення терміну дії даних під час зміни конкретного файлу або одного з декількох файлів. Це можна використовувати, наприклад, для кешування даних, отриманих у результаті обробки цих файлів. Використовуйте абсолютні шляхи: +Зручною є можливість дозволити даним закінчитися в момент зміни файлу або одного з кількох файлів. Це можна використовувати, наприклад, при зберіганні в кеші даних, отриманих в результаті обробки цих файлів. Використовуйте абсолютні шляхи. ```php -$dependencies[Cache::Files] = '/путь/до/data.yaml'; +$dependencies[Cache::Files] = '/path/to/data.yaml'; // або -$dependencies[Cache::Files] = ['/путь/до/data1.yaml', '/путь/до/data2.yaml']; +$dependencies[Cache::Files] = ['/path/to/data1.yaml', '/path/to/data2.yaml']; ``` -Ми можемо дозволити елементу в кеші закінчитися, коли закінчується термін дії іншого елемента (або одного з декількох інших). Це можна використовувати, коли ми кешуємо всю HTML-сторінку та її фрагменти під іншими ключами. Щойно сніпет змінюється, вся сторінка стає недійсною. Якщо у нас є фрагменти, що зберігаються під такими ключами, як `frag1` і `frag2`, ми будемо використовувати: +Ми можемо дозволити елементу в кеші закінчитися в момент, коли закінчується інший елемент (або один з кількох інших). Це можна використовувати, наприклад, коли ми зберігаємо в кеші цілу HTML-сторінку, а під іншими ключами — її фрагменти. Як тільки фрагмент змінюється, вся сторінка інвалідується. Якщо фрагменти збережені під ключами, наприклад, `frag1` та `frag2`, використовуємо: ```php $dependencies[Cache::Items] = ['frag1', 'frag2']; ``` -Термін дії також можна контролювати за допомогою користувацьких функцій або статичних методів, які під час читання завжди вирішують, чи дійсний ще елемент. Наприклад, ми можемо дозволити елементу спливати щоразу, коли змінюється версія PHP. Ми створимо функцію, яка порівнює поточну версію з параметром, і під час збереження додамо масив у вигляді `[имя функции, ...аргументы]` до залежностей: +Експірацію можна контролювати також за допомогою власних функцій або статичних методів, які завжди при читанні вирішують, чи є елемент ще дійсним. Таким чином, наприклад, ми можемо дозволити елементу закінчитися щоразу, коли змінюється версія PHP. Створимо функцію, яка порівнює поточну версію з параметром, і при збереженні додамо серед залежностей масив у форматі `[назва функції, ...аргументи]`: ```php function checkPhpVersion($ver): bool @@ -171,11 +171,11 @@ function checkPhpVersion($ver): bool } $dependencies[Cache::Callbacks] = [ - ['checkPhpVersion', PHP_VERSION_ID] // закінчується, коли checkPhpVersion(...) === false + ['checkPhpVersion', PHP_VERSION_ID] // закінчити, коли checkPhpVersion(...) === false ]; ``` -Звичайно, всі критерії можуть бути об'єднані. Термін дії кешу закінчується, якщо хоча б один критерій не виконано. +Всі критерії, звичайно, можна комбінувати. Кеш тоді закінчується, коли принаймні один критерій не виконується. ```php $dependencies[Cache::Expire] = '20 minutes'; @@ -183,16 +183,16 @@ $dependencies[Cache::Files] = '/path/to/data.yaml'; ``` -Інвалідація з використанням тегів .[#toc-invalidation-using-tags] ------------------------------------------------------------------ +Інвалідація за допомогою тегів +------------------------------ -Теги є дуже корисним інструментом визнання недійсності. Ми можемо призначити список тегів, які є довільними рядками, кожному елементу, що зберігається в кеші. Наприклад, припустимо, що в нас є HTML-сторінка зі статтею та коментарями, яку ми хочемо кешувати. Тому ми вказуємо теги під час збереження в кеш: +Дуже корисним інструментом інвалідації є так звані теги. Кожному елементу в кеші ми можемо призначити список тегів, які є довільними рядками. Наприклад, маємо HTML-сторінку зі статтею та коментарями, яку будемо кешувати. При збереженні вказуємо теги: ```php $dependencies[Cache::Tags] = ["article/$articleId", "comments/$articleId"]; ``` -Тепер перейдемо до адміністрування. Тут у нас є форма для редагування статті. Разом зі збереженням статті в базі даних ми викликаємо команду `clean()`, яка видаляє кешовані елементи за тегами: +Перейдемо до адміністративної частини. Тут знайдемо форму для редагування статті. Разом зі збереженням статті в базу даних викличемо команду `clean()`, яка видалить з кешу елементи за тегом: ```php $cache->clean([ @@ -200,7 +200,7 @@ $cache->clean([ ]); ``` -Аналогічним чином, у місці додавання нового коментаря (або редагування коментаря) ми не забудемо анулювати відповідний тег: +Так само в місці додавання нового коментаря (або редагування коментаря) не забудемо інвалідувати відповідний тег: ```php $cache->clean([ @@ -208,22 +208,22 @@ $cache->clean([ ]); ``` -Чого ми досягли? Що наш HTML-кеш буде анульовано (видалено) при кожній зміні статті або коментарів. Під час редагування статті з ID = 10 тег `article/10` примусово анулюється, а HTML-сторінка, що містить цей тег, видаляється з кешу. Те ж саме відбувається при вставці нового коментаря під відповідною статтею. +Чого ми цим досягли? Що наш HTML-кеш буде інвалідуватися (видалятися), коли змінюється стаття або коментарі. При редагуванні статті з ID = 10 відбувається примусова інвалідація тегу `article/10`, і HTML-сторінка, яка несе цей тег, видаляється з кешу. Те саме відбувається при додаванні нового коментаря до відповідної статті. .[note] -Тегам потрібен [Журнал |#Журнал]. +Теги вимагають так званого [#Journal]. -Інвалідація за пріоритетом .[#toc-invalidation-by-priority] ------------------------------------------------------------ +Інвалідація за допомогою пріоритету +----------------------------------- -Ми можемо встановити пріоритет для окремих елементів у кеші, і їх можна буде видаляти контрольованим чином, коли, наприклад, кеш перевищить певний розмір: +Окремим елементам у кеші ми можемо встановити пріоритет, за допомогою якого їх можна буде видаляти, наприклад, коли кеш перевищить певний розмір: ```php $dependencies[Cache::Priority] = 50; ``` -Видаляємо всі елементи з пріоритетом, що дорівнює або менший за 100: +Видалимо всі елементи з пріоритетом, рівним або меншим за 100: ```php $cache->clean([ @@ -232,13 +232,13 @@ $cache->clean([ ``` .[note] -Пріоритетам також потрібен [Журнал |#Журнал]. +Пріоритети вимагають так званого [#Journal]. -Очищення кешу .[#toc-clear-cache] ---------------------------------- +Видалення кешу +-------------- -Параметр `Cache::All` очищає все: +Параметр `Cache::All` видаляє все: ```php $cache->clean([ @@ -247,86 +247,105 @@ $cache->clean([ ``` -Масове читання .[#toc-bulk-reading] -=================================== +Масове читання +============== -Для масового читання і запису в кеш використовується метод `bulkLoad()`, в якому ми передаємо масив ключів і отримуємо масив значень: +Для масового читання та запису в кеш служить метод `bulkLoad()`, якому ми передаємо масив ключів і отримуємо масив значень: ```php $values = $cache->bulkLoad($keys); ``` -Метод `bulkLoad()` працює аналогічно `load()` з другим параметром зворотного виклику, якому передається ключ згенерованого елемента: +Метод `bulkLoad()` працює подібно до `load()` і з другим параметром callback, якому передається ключ генерованого елемента: ```php $values = $cache->bulkLoad($keys, function ($key, &$dependencies) { - $computedValue = /* ... */; // важкі обчислення + $computedValue = /* ... */; // складне обчислення return $computedValue; }); ``` -Кешування виведення .[#toc-output-caching] -========================================== +Використання з PSR-16 .{data-version:3.3.1} +=========================================== + +Для використання Nette Cache з інтерфейсом PSR-16 ви можете скористатися адаптером `PsrCacheAdapter`. Він дозволяє безшовну інтеграцію між Nette Cache та будь-яким кодом або бібліотекою, яка очікує PSR-16 сумісний кеш. + +```php +$psrCache = new Nette\Bridges\Psr\PsrCacheAdapter($storage); +``` + +Тепер ви можете використовувати `$psrCache` як PSR-16 кеш: + +```php +$psrCache->set('key', 'value', 3600); // зберігає значення на 1 годину +$value = $psrCache->get('key', 'default'); +``` + +Адаптер підтримує всі методи, визначені в PSR-16, включаючи `getMultiple()`, `setMultiple()` та `deleteMultiple()`. + -Вихідні дані можна перехоплювати і кешувати дуже елегантно: +Кешування виводу +================ + +Дуже елегантно можна перехоплювати та кешувати вивід: ```php if ($capture = $cache->capture($key)) { - echo ... // виводимо деякі дані + echo ... // виводимо дані - $capture->end(); // зберігаємо виведення в кеш + $capture->end(); // зберігаємо вивід у кеш } ``` -У разі якщо виведення вже присутнє в кеші, метод `capture()` друкує його і повертає `null`, тому умова не буде виконана. В іншому разі він починає буферизацію виводу і повертає об'єкт `$capture`, за допомогою якого ми остаточно зберігаємо дані в кеш. +У випадку, якщо вивід вже збережено в кеші, метод `capture()` виведе його і поверне `null`, отже умова не виконається. В іншому випадку він почне перехоплювати вивід і поверне об'єкт `$capture`, за допомогою якого ми врешті-решт збережемо виведені дані в кеш. .[note] У версії 3.0 метод називався `$cache->start()`. -Кешування в Latte .[#toc-caching-in-latte] -========================================== +Кешування в Latte +================= -Кешування в шаблонах [Latte |latte:] дуже легко налаштовується, достатньо обернути частину шаблону тегами `{cache}...{/cache}`. Кеш автоматично анулюється при зміні вихідного шаблону (включаючи будь-які включені шаблони в тегах `{cache}`). Теги `{cache}` можуть бути вкладеними, і коли вкладений блок анулюється (наприклад, тегом), батьківський блок також анулюється. +Кешування в шаблонах [Latte|latte:] дуже просте, достатньо частину шаблону обернути тегами `{cache}...{/cache}`. Кеш автоматично інвалідується в момент, коли змінюється вихідний шаблон (включаючи можливі включені шаблони всередині блоку кешу). Теги `{cache}` можна вкладати один в одного, і коли вкладений блок стає недійсним (наприклад, за допомогою тегу), батьківський блок також стає недійсним. -У тезі можна вказати ключі, до яких буде прив'язано кеш (тут змінна `$id`) і встановити термін дії та [теги анулювання |#Инвалидация с использованием тегов]. +У тегу можна вказати ключі, до яких буде прив'язаний кеш (тут змінна `$id`), і встановити термін дії та [теги для інвалідації |#Інвалідація за допомогою тегів]. ```latte -{cache $id, expire => '20 minutes', tags => [tag1, tag2]} +{cache $id, expire: '20 minutes', tags: [tag1, tag2]} ... {/cache} ``` -Усі параметри є необов'язковими, тому вам не потрібно вказувати термін дії, теги або ключі. +Усі параметри є необов'язковими, тому ми не повинні вказувати ні термін дії, ні теги, ні навіть ключі. -Використання кешу також може бути обумовлено `if` - вміст буде кешуватися тільки при виконанні умови: +Використання кешу також можна обумовити за допомогою `if` - вміст тоді буде кешуватися лише за умови виконання умови: ```latte -{cache $id, if => !$form->isSubmitted()} +{cache $id, if: !$form->isSubmitted()} {$form} {/cache} ``` -Сховища .[#toc-hranilisa] -========================= +Сховища +======= -Сховище - це об'єкт, який являє собою місце фізичного зберігання даних. Ми можемо використовувати базу даних, сервер Memcached або найбільш доступне сховище, яким є файли на диску. +Сховище — це об'єкт, що представляє місце, де дані фізично зберігаються. Ми можемо використовувати базу даних, сервер Memcached або найдоступніше сховище — файли на диску. -|---------------------- -| Зберігання | Опис -|---------------------- -| [FileStorage |#FileStorage] | зберігання за замовчуванням зі збереженням у файли на диску -| [MemcachedStorage |#MemcachedStorage] | використовується сервер `Memcached -| [MemoryStorage |#MemoryStorage] | дані тимчасово знаходяться в пам'яті -| [SQLiteStorage |#SQLiteStorage] | дані зберігаються в базі даних SQLite -| [DevNullStorage |#DevNullStorage] | дані не зберігаються - з метою тестування +|----------------- +| Сховище | Опис +|----------------- +| [#FileStorage] | сховище за замовчуванням зі збереженням у файли на диску +| [#MemcachedStorage] | використовує сервер `Memcached` +| [#MemoryStorage] | дані тимчасово зберігаються в пам'яті +| [#SQLiteStorage] | дані зберігаються в базі даних SQLite +| [#DevNullStorage] | дані не зберігаються, підходить для тестування -Ви отримуєте об'єкт сховища, передаючи його за допомогою [впровадження залежностей |dependency-injection:passing-dependencies] з типом `Nette\Caching\Storage`. За замовчуванням Nette надає об'єкт FileStorage, який зберігає дані в підпапці `cache` в каталозі для [тимчасових файлів |application:bootstrap#Temporary-Files]. +До об'єкта сховища можна отримати доступ, попросивши передати його за допомогою [dependency injection |dependency-injection:passing-dependencies] з типом `Nette\Caching\Storage`. Як сховище за замовчуванням Nette надає об'єкт FileStorage, що зберігає дані в підкаталозі `cache` в каталозі для [тимчасових файлів |application:bootstrapping#Тимчасові файли]. -Ви можете змінити сховище в конфігурації: +Змінити сховище можна в конфігурації: ```neon services: @@ -334,15 +353,12 @@ services: ``` -FileStorage .[#toc-filestorage] -------------------------------- +FileStorage +----------- -Записує кеш у файли на диску. Сховище `Nette\Caching\Storages\FileStorage` дуже добре оптимізоване для продуктивності і, перш за все, забезпечує повну атомарність операцій. Що це означає? Щоб під час використання кешу не вийшло так, що ми читаємо файл, який ще не був повністю записаний іншим потоком, або щоб хтось видалив його "з-під руки". Тому використання кешу повністю безпечне. +Записує кеш у файли на диску. Сховище `Nette\Caching\Storages\FileStorage` дуже добре оптимізоване для продуктивності і, перш за все, забезпечує повну атомарність операцій. Що це означає? Що при використанні кешу не може статися так, що ми прочитаємо файл, який ще не повністю записаний іншим потоком, або що хтось його "під руками" видалить. Використання кешу, таким чином, є абсолютно безпечним. -Це сховище також має важливу вбудовану функцію, яка запобігає екстремальному збільшенню завантаження процесора, коли кеш очищається або охолоджується (тобто не створюється). Це профілактика "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede_. -Буває так, що в один момент надходить кілька одночасних запитів, які хочуть отримати з кешу одне й те саме (наприклад, результат великого SQL-запиту), а оскільки він не кешується, усі процеси починають виконувати один і той самий SQL-запит. -Навантаження на процесор збільшується в кілька разів, і може навіть трапитися так, що жоден потік не зможе відповісти у відведений час, кеш не буде створено, і додаток аварійно завершить роботу. -На щастя, кеш у Nette працює таким чином, що за наявності декількох одночасних запитів на один елемент, він генерується тільки першим потоком, інші чекають і потім використовують згенерований результат. +Це сховище також має вбудовану важливу функцію, яка запобігає екстремальному зростанню використання ЦП у момент, коли кеш видаляється або ще не прогрітий (тобто створений). Це запобігання "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Стається так, що в один момент збігається велика кількість одночасних запитів, які хочуть отримати з кешу одну й ту саму річ (наприклад, результат дорогого SQL-запиту), і оскільки в кеші її немає, всі процеси починають виконувати той самий SQL-запит. Навантаження таким чином множиться, і може навіть статися, що жоден потік не встигне відповісти в часовому ліміті, кеш не створиться, і застосунок звалиться. На щастя, кеш у Nette працює так, що при кількох одночасних запитах на один елемент його генерує лише перший потік, інші чекають і потім використовують згенерований результат. Приклад створення FileStorage: @@ -352,13 +368,13 @@ $storage = new Nette\Caching\Storages\FileStorage('/path/to/temp'); ``` -MemcachedStorage .[#toc-memcachedstorage] ------------------------------------------ +MemcachedStorage +---------------- -Сервер [Memcached |https://memcached.org] - це високопродуктивна розподілена система зберігання даних, адаптером якої є `Nette\Caching\Storages\MemcachedStorage`. У конфігурації вкажіть IP-адресу та порт, якщо він відрізняється від стандартного 11211. +Сервер [Memcached|https://memcached.org] — це високопродуктивна система зберігання в розподіленій пам'яті, адаптером якої є `Nette\Caching\Storages\MemcachedStorage`. У конфігурації вказуємо IP-адресу та порт, якщо він відрізняється від стандартного 11211. .[caution] -Потрібно PHP-розширення `memcached`. +Вимагає PHP-розширення `memcached`. ```neon services: @@ -366,19 +382,19 @@ services: ``` -MemoryStorage .[#toc-memorystorage] ------------------------------------ +MemoryStorage +------------- -`Nette\Caching\Storages\MemoryStorage` це сховище, яке зберігає дані в масиві PHP і, таким чином, втрачається при завершенні запиту. +`Nette\Caching\Storages\MemoryStorage` — це сховище, яке зберігає дані в масиві PHP, і тому вони втрачаються після завершення запиту. -SQLiteStorage .[#toc-memorystorage] ------------------------------------ +SQLiteStorage +------------- -База даних SQLite та адаптер `Nette\Caching\Storages\SQLiteStorage` пропонують спосіб кешування в єдиному файлі на диску. У конфігурації буде вказано шлях до цього файлу. +База даних SQLite та адаптер `Nette\Caching\Storages\SQLiteStorage` пропонують спосіб зберігання кешу в одному файлі на диску. У конфігурації вказуємо шлях до цього файлу. .[caution] -Вимагає PHP-розширень `pdo` і `pdo_sqlite`. +Вимагає PHP-розширень `pdo` та `pdo_sqlite`. ```neon services: @@ -386,16 +402,16 @@ services: ``` -DevNullStorage .[#toc-devnullstorage] -------------------------------------- +DevNullStorage +-------------- -Особливою реалізацією сховища є `Nette\Caching\Storages\DevNullStorage`, яка насправді не зберігає дані взагалі. Тому вона підходить для тестування, якщо ми хочемо виключити вплив кешу. +Спеціальною реалізацією сховища є `Nette\Caching\Storages\DevNullStorage`, яке насправді взагалі не зберігає дані. Тому воно підходить для тестування, коли ми хочемо усунути вплив кешу. -Використання кешу в коді .[#toc-devnullstorage] -=============================================== +Використання кешу в коді +======================== -При використанні кешування в коді у вас є два способи, як це зробити. Перший полягає в тому, що ви отримуєте об'єкт сховища, передаючи його за допомогою [впровадження залежностей |dependency-injection:passing-dependencies], а потім створюєте об'єкт `Cache`: +При використанні кешу в коді є два способи це зробити. Перший полягає в тому, що ми просимо передати сховище за допомогою [dependency injection |dependency-injection:passing-dependencies] і створюємо об'єкт `Cache`: ```php use Nette; @@ -411,7 +427,7 @@ class ClassOne } ``` -Другий спосіб полягає в тому, що ви отримуєте об'єкт зберігання `Cache`: +Другий варіант — ми просимо передати об'єкт `Cache` безпосередньо: ```php class ClassTwo @@ -423,7 +439,7 @@ class ClassTwo } ``` -Потім об'єкт `Cache` створюється безпосередньо в конфігурації наступним чином: +Об'єкт `Cache` потім створюється безпосередньо в конфігурації таким чином: ```neon services: @@ -431,12 +447,12 @@ services: ``` -Журнал .[#toc-zurnal] -===================== +Journal +======= -Nette зберігає теги та пріоритети в так званому журналі. За замовчуванням для цього використовуються SQLite і файл `journal.s3db`. Крім того, **потрібні PHP-розширення `pdo` і `pdo_sqlite`**. +Nette зберігає теги та пріоритети у так званому журналі. Стандартно для цього використовується SQLite та файл `journal.s3db`, і **вимагаються PHP-розширення `pdo` та `pdo_sqlite`.** -Ви можете змінити журнал у конфігурації: +Змінити журнал можна в конфігурації: ```neon services: @@ -444,4 +460,25 @@ services: ``` -{{leftbar: nette:@menu-topics}} +Сервіси DI +========== + +Ці сервіси додаються до DI-контейнера: + +| Назва | Тип | Опис +|---------------------------------------------------------- +| `cache.journal` | [api:Nette\Caching\Storages\Journal] | журнал +| `cache.storage` | [api:Nette\Caching\Storage] | сховище + + +Вимкнення кешу +============== + +Одним із способів вимкнути кеш у застосунку є встановлення [#DevNullStorage] як сховища: + +```neon +services: + cache.storage: Nette\Caching\Storages\DevNullStorage +``` + +Це налаштування не впливає на кешування шаблонів у Latte або DI-контейнера, оскільки ці бібліотеки не використовують сервіси nette/caching і керують своїм кешем самостійно. Їхній кеш, до речі, [не потрібно |nette:troubleshooting#Як вимкнути кеш під час розробки] вимикати в режимі розробки. diff --git a/caching/uk/@meta.texy b/caching/uk/@meta.texy new file mode 100644 index 0000000000..083a8ab9f7 --- /dev/null +++ b/caching/uk/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Документація Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/code-checker/bg/@home.texy b/code-checker/bg/@home.texy index 865a284727..ab284bfe7e 100644 --- a/code-checker/bg/@home.texy +++ b/code-checker/bg/@home.texy @@ -1,30 +1,30 @@ -Проверка на кода -**************** +Nette Code Checker +****************** .[perex] -Инструмент, наречен [Code Checker |https://github.com/nette/code-checker], проверява и, ако е възможно, поправя някои формални грешки в изходния ви код. +Инструментът [Code Checker |https://github.com/nette/code-checker] проверява и евентуално коригира някои от формалните грешки във вашия изходен код. Инсталация ========== -Code Checker трябва да бъде инсталиран като проект, не го използвайте като зависимост. +Code Checker не трябва да се добавя към зависимостите, а да се инсталира като проект. ```shell composer create-project nette/code-checker ``` -Или го инсталирайте глобално чрез: +Или го инсталирайте глобално с помощта на: ```shell composer global require nette/code-checker ``` -и се уверете, че глобалната директория с двоични файлове на доставчика е в [променливата на средата `$PATH`. |https://getcomposer.org/doc/03-cli.md#global] +и се уверете, че вашата глобална директория `vendor/bin` е в [променливата на средата $PATH |https://getcomposer.org/doc/03-cli.md#global]. -Използване на -============= +Употреба +======== ``` Usage: php code-checker [options] @@ -38,28 +38,28 @@ Options: --strict-types Checks whether PHP 7.0 directive strict_types is enabled ``` -Без параметри проверява текущата работна директория в режим само за четене, а с параметъра `-f` фиксира файловете. +Без параметри проверява текущата директория в режим само за четене, с параметъра `-f` коригира файловете. -Не забравяйте да направите резервно копие на файловете си, преди да се запознаете с този инструмент. +Преди да се запознаете с него, определено първо архивирайте файловете си. -Можете да създадете пакетен файл, например `code.bat`, за да стартирате Code-Checker под Windows по-лесно: +За по-лесно стартиране можем да създадем файл `code.bat`: ```shell -php path_to\Nette_tools\Code-Checker\code-checker %* +php path_to_Nette_tools\Code-Checker\code-checker %* ``` -Какво прави Code-Checker? -========================= +Какво прави всичко това? +======================== -- премахва [BOM |nette:glossary#bom]. -- Проверява валидността на шаблоните [Latte |latte:]. -- Проверява валидността на `.neon`, `.php` и `.json`. -- проверява за наличието на [контролни знаци |nette:glossary#Control-Characters]. -- проверява дали файлът е кодиран в UTF-8. -- проверява дали `/* @annotations */` е изписан правилно (липсва втората звездичка) -- премахва таговете за край на PHP `?>` в PHP файлове -- премахва белите полета и ненужните празни редове от края на файла -- нормализира окончанията на редовете до системните по подразбиране (с `-l`) +- премахва [BOM |nette:glossary#BOM] +- проверява валидността на [Latte |latte:] шаблони +- проверява валидността на файлове `.neon`, `.php` и `.json` +- проверява за наличието на [контролни знаци |nette:glossary#Контролни знаци] +- проверява дали файлът е кодиран в UTF-8 +- проверява неправилно записани `/* @anotace */` (липсва звездичка) +- премахва завършващия `?>` при PHP файлове +- премахва десните интервали и излишните редове в края на файла +- нормализира разделителите на редове до системните (ако посочите опцията `-l`) {{leftbar: www:@menu-common}} diff --git a/code-checker/cs/@home.texy b/code-checker/cs/@home.texy index 1a7cb5a165..a4d62f10d6 100644 --- a/code-checker/cs/@home.texy +++ b/code-checker/cs/@home.texy @@ -1,5 +1,5 @@ -Code Checker -************ +Nette Code Checker +****************** .[perex] Nástroj [Code Checker |https://github.com/nette/code-checker] zkontroluje a případně opraví některé z formálních chyb ve vašich zdrojových kódech. @@ -52,10 +52,10 @@ php cesta_k_Nette_tools\Code-Checker\code-checker %* Co všechno dělá? ================ -- odstraňuje [BOM |nette:glossary#bom] +- odstraňuje [BOM |nette:glossary#BOM] - kontroluje validitu [Latte |latte:] šablon - kontroluje validitu souborů `.neon`, `.php` a `.json` -- kontroluje výskyt [kontrolních znaků |nette:glossary#kontrolní znaky] +- kontroluje výskyt [kontrolních znaků |nette:glossary#Kontrolní znaky] - kontroluje, zda je soubor kódován v UTF-8 - kontroluje chybně zapsané `/* @anotace */` (chybí hvězdička) - odstraňuje ukončovací `?>` u PHP souborů diff --git a/code-checker/de/@home.texy b/code-checker/de/@home.texy index 305e73dc27..598b407465 100644 --- a/code-checker/de/@home.texy +++ b/code-checker/de/@home.texy @@ -1,26 +1,26 @@ -Code-Prüfer -*********** +Nette Code Checker +****************** .[perex] -Das Tool namens [Code Checker |https://github.com/nette/code-checker] prüft und repariert möglicherweise einige der formalen Fehler in Ihrem Quellcode. +Das Werkzeug [Code Checker |https://github.com/nette/code-checker] überprüft und korrigiert gegebenenfalls einige formale Fehler in Ihrem Quellcode. Installation ============ -Code Checker sollte als Projekt installiert werden, verwenden Sie es nicht als Abhängigkeit. +Code Checker sollten Sie nicht zu Ihren Abhängigkeiten hinzufügen, sondern als Projekt installieren. ```shell composer create-project nette/code-checker ``` -Oder installieren Sie ihn global über: +Oder installieren Sie es global mit: ```shell composer global require nette/code-checker ``` -und stellen Sie sicher, dass Ihr globales Vendor Binarys-Verzeichnis in [Ihrer `$PATH` Umgebungsvariable |https://getcomposer.org/doc/03-cli.md#global] enthalten ist. +und stellen Sie sicher, dass Ihr globales Verzeichnis `vendor/bin` in der [Umgebungsvariablen $PATH |https://getcomposer.org/doc/03-cli.md#global] enthalten ist. Verwendung @@ -30,36 +30,36 @@ Verwendung Usage: php code-checker [options] Options: - -d <path> Folder or file to scan (default: current directory) - -i | --ignore <mask> Files to ignore - -f | --fix Fixes files - -l | --eol Convert newline characters - --no-progress Do not show progress dots - --strict-types Checks whether PHP 7.0 directive strict_types is enabled + -d <path> Zu scannender Ordner oder Datei (Standard: aktuelles Verzeichnis) + -i | --ignore <mask> Zu ignorierende Dateien + -f | --fix Korrigiert Dateien + -l | --eol Konvertiert Zeilenumbruchzeichen + --no-progress Keine Fortschrittspunkte anzeigen + --strict-types Prüft, ob die PHP 7.0-Direktive strict_types aktiviert ist ``` -Ohne Parameter wird das aktuelle Arbeitsverzeichnis im Nur-Lese-Modus geprüft, mit dem Parameter `-f` werden Dateien korrigiert. +Ohne Parameter prüft es das aktuelle Verzeichnis im schreibgeschützten Modus, mit dem Parameter `-f` korrigiert es die Dateien. -Bevor Sie sich mit dem Tool vertraut machen, sollten Sie Ihre Dateien sichern. +Bevor Sie sich damit vertraut machen, sichern Sie unbedingt zuerst Ihre Dateien. -Zur einfacheren Ausführung von Code-Checker unter Windows können Sie eine Batch-Datei erstellen, z.B. `code.bat`: +Für einen einfacheren Start können wir eine Datei `code.bat` erstellen: ```shell -php path_to\Nette_tools\Code-Checker\code-checker %* +php pfad_zu_Nette_tools\Code-Checker\code-checker %* ``` -Was macht Code-Checker? -======================= +Was macht es alles? +=================== -- Entfernt [BOM |nette:glossary#bom] -- prüft die Gültigkeit von [Latte-Vorlagen |latte:] -- prüft die Gültigkeit der Dateien `.neon`, `.php` und `.json` -- prüft auf [Steuerzeichen |nette:glossary#control characters] +- entfernt das [BOM |nette:glossary#BOM] +- prüft die Gültigkeit von [Latte |latte:]-Templates +- prüft die Gültigkeit von `.neon`-, `.php`- und `.json`-Dateien +- prüft das Vorkommen von [Steuerzeichen |nette:glossary#Steuerzeichen] - prüft, ob die Datei in UTF-8 kodiert ist -- prüft falsch geschriebene `/* @annotations */` (zweites Sternchen fehlt) -- entfernt PHP-Endungstags `?>` in PHP-Dateien -- entfernt nachstehende Leerzeichen und unnötige Leerzeilen am Ende einer Datei -- normalisiert die Zeilenenden auf die Systemvorgabe (mit dem Parameter `-l` ) +- prüft falsch geschriebene `/* @anotace */` (fehlendes Sternchen) +- entfernt das schließende `?>` bei PHP-Dateien +- entfernt Leerzeichen am Zeilenende und unnötige Leerzeilen am Dateiende +- normalisiert Zeilentrennzeichen auf Systemstandard (wenn Sie die Option `-l` angeben) {{leftbar: www:@menu-common}} diff --git a/code-checker/el/@home.texy b/code-checker/el/@home.texy index b77630e567..de1f6401a0 100644 --- a/code-checker/el/@home.texy +++ b/code-checker/el/@home.texy @@ -1,26 +1,26 @@ -Έλεγχος κώδικα -************** +Nette Code Checker +****************** .[perex] -Το εργαλείο που ονομάζεται [Code Checker |https://github.com/nette/code-checker] ελέγχει και ενδεχομένως επιδιορθώνει ορισμένα από τα τυπικά σφάλματα στον πηγαίο σας κώδικα. +Το εργαλείο [Code Checker |https://github.com/nette/code-checker] ελέγχει και ενδεχομένως διορθώνει ορισμένα από τα τυπικά σφάλματα στους πηγαίους κώδικές σας. Εγκατάσταση =========== -Το Code Checker πρέπει να εγκατασταθεί ως έργο, μην το χρησιμοποιείτε ως εξάρτηση. +Δεν πρέπει να προσθέσετε το Code Checker στις εξαρτήσεις, αλλά να το εγκαταστήσετε ως έργο. ```shell composer create-project nette/code-checker ``` -Ή εγκαταστήστε τον παγκοσμίως μέσω: +Ή εγκαταστήστε το καθολικά χρησιμοποιώντας: ```shell composer global require nette/code-checker ``` -και βεβαιωθείτε ότι ο παγκόσμιος κατάλογος binaries του προμηθευτή βρίσκεται στη [μεταβλητή περιβάλλοντος `$PATH` |https://getcomposer.org/doc/03-cli.md#global]. +και βεβαιωθείτε ότι ο καθολικός σας κατάλογος `vendor/bin` βρίσκεται στη [μεταβλητή περιβάλλοντος $PATH |https://getcomposer.org/doc/03-cli.md#global]. Χρήση @@ -38,28 +38,28 @@ Options: --strict-types Checks whether PHP 7.0 directive strict_types is enabled ``` -Χωρίς παραμέτρους, ελέγχει τον τρέχοντα κατάλογο εργασίας σε λειτουργία μόνο για ανάγνωση, ενώ με την παράμετρο `-f` διορθώνει αρχεία. +Χωρίς παραμέτρους ελέγχει τον τρέχοντα κατάλογο σε κατάσταση μόνο ανάγνωσης, με την παράμετρο `-f` διορθώνει τα αρχεία. -Πριν γνωρίσετε το εργαλείο, φροντίστε να δημιουργήσετε πρώτα αντίγραφα ασφαλείας των αρχείων σας. +Πριν εξοικειωθείτε μαζί του, φροντίστε να δημιουργήσετε αντίγραφα ασφαλείας των αρχείων σας πρώτα. -Μπορείτε να δημιουργήσετε ένα αρχείο δέσμης, π.χ. `code.bat`, για την ευκολότερη εκτέλεση του Code-Checker κάτω από τα Windows: +Για ευκολότερη εκτέλεση, μπορούμε να δημιουργήσουμε ένα αρχείο `code.bat`: ```shell -php path_to\Nette_tools\Code-Checker\code-checker %* +php path_to_Nette_tools\Code-Checker\code-checker %* ``` -Τι κάνει ο Code-Checker; -======================== +Τι κάνει; +========= -- αφαιρεί [BOM |nette:glossary#bom] -- ελέγχει την εγκυρότητα των προτύπων [Latte |latte:] +- αφαιρεί το [BOM |nette:glossary#BOM] +- ελέγχει την εγκυρότητα των templates [Latte |latte:] - ελέγχει την εγκυρότητα των αρχείων `.neon`, `.php` και `.json` -- Έλεγχοι για [χαρακτήρες ελέγχου |nette:glossary#control characters] -- ελέγχει αν το αρχείο είναι κωδικοποιημένο σε UTF-8 -- ελέγχει το ορθογραφικό λάθος `/* @annotations */` (λείπει ο δεύτερος αστερίσκος) -- αφαιρεί τις ετικέτες κατάληξης PHP `?>` σε αρχεία PHP -- αφαιρεί τα κενά διαστήματα και τις περιττές κενές γραμμές από το τέλος ενός αρχείου -- ομαλοποιεί τις καταλήξεις γραμμών στην προεπιλογή του συστήματος (με την παράμετρο `-l` ) +- ελέγχει την παρουσία [χαρακτήρων ελέγχου |nette:glossary#Control characters] +- ελέγχει εάν το αρχείο είναι κωδικοποιημένο σε UTF-8 +- ελέγχει για λανθασμένα γραμμένα `/* @anotace */` (λείπει ο αστερίσκος) +- αφαιρεί το τελικό `?>` από τα αρχεία PHP +- αφαιρεί τα δεξιά κενά και τις περιττές γραμμές στο τέλος του αρχείου +- κανονικοποιεί τους διαχωριστές γραμμών σε συστήματος (εάν δώσετε την επιλογή `-l`) {{leftbar: www:@menu-common}} diff --git a/code-checker/en/@home.texy b/code-checker/en/@home.texy index b40eb32fc6..2bbaba1c33 100644 --- a/code-checker/en/@home.texy +++ b/code-checker/en/@home.texy @@ -1,14 +1,14 @@ -Code Checker -************ +Nette Code Checker +****************** .[perex] -The tool called [Code Checker |https://github.com/nette/code-checker] checks and possibly repairs some of the formal errors in your source code. +The tool called [Code Checker |https://github.com/nette/code-checker] checks and optionally repairs some of the formal errors in your source code. Installation ============ -Code Checker should be installed as project, don't use it as dependency. +Code Checker should be installed as a project, not added as a dependency. ```shell composer create-project nette/code-checker @@ -38,28 +38,28 @@ Options: --strict-types Checks whether PHP 7.0 directive strict_types is enabled ``` -Without parameters, it checks the current working directory in a read-only mode, with `-f` parameter it fixes files. +Without parameters, it checks the current working directory in read-only mode; with the `-f` parameter, it fixes files. -Before you get to know the tool, be sure to backup your files first. +Before you get familiar with the tool, be sure to back up your files first. -You can create a batch file, e.g. `code.bat`, for easier execution of Code-Checker under Windows: +You can create a batch file, e.g., `code.bat`, for easier execution of Code Checker under Windows: ```shell php path_to\Nette_tools\Code-Checker\code-checker %* ``` -What Code-Checker Does? -======================= +What Does Code Checker Do? +========================== -- removes [BOM |nette:glossary#bom] -- checks validity of [Latte |latte:] templates -- checks validity of `.neon`, `.php` and `.json` files -- checks for [control characters |nette:glossary#control characters] +- removes [BOM |nette:glossary#BOM] +- checks the validity of [Latte |latte:] templates +- checks the validity of `.neon`, `.php`, and `.json` files +- checks for [control characters |nette:glossary#Control Characters] - checks whether the file is encoded in UTF-8 -- controls misspelled `/* @annotations */` (second asterisk missing) +- checks for misspelled `/* @annotations */` (missing second asterisk) - removes PHP ending tags `?>` in PHP files - removes trailing whitespace and unnecessary blank lines from the end of a file -- normalizes line endings to system-default (with the `-l` parameter) +- normalizes line endings to the system default (with the `-l` parameter) {{leftbar: www:@menu-common}} diff --git a/code-checker/es/@home.texy b/code-checker/es/@home.texy index e60aef144f..c6770a3010 100644 --- a/code-checker/es/@home.texy +++ b/code-checker/es/@home.texy @@ -1,30 +1,30 @@ -Comprobador de códigos -********************** +Nette Code Checker +****************** .[perex] -La herramienta llamada Code [Checker |https://github.com/nette/code-checker] comprueba y posiblemente repara algunos de los errores formales de su código fuente. +La herramienta [Code Checker |https://github.com/nette/code-checker] comprueba y, opcionalmente, corrige algunos de los errores formales en sus códigos fuente. Instalación =========== -Code Checker debe ser instalado como proyecto, no lo use como dependencia. +No debe agregar Code Checker a las dependencias, sino instalarlo como un proyecto. ```shell composer create-project nette/code-checker ``` -O instalarlo globalmente a través de: +O instálelo globalmente usando: ```shell composer global require nette/code-checker ``` -y asegúrese de que el directorio global de binarios del proveedor está en [su variable de entorno `$PATH` |https://getcomposer.org/doc/03-cli.md#global]. +y asegúrese de que su directorio global `vendor/bin` esté en la [variable de entorno $PATH |https://getcomposer.org/doc/03-cli.md#global]. -Utilización -=========== +Uso +=== ``` Usage: php code-checker [options] @@ -38,28 +38,28 @@ Options: --strict-types Checks whether PHP 7.0 directive strict_types is enabled ``` -Sin parámetros, comprueba el directorio de trabajo actual en modo sólo lectura, con el parámetro `-f` fija los archivos. +Sin parámetros, comprueba el directorio actual en modo de solo lectura, con el parámetro `-f` corrige los archivos. -Antes de conocer la herramienta, asegúrese primero de hacer una copia de seguridad de sus archivos. +Antes de familiarizarse con él, asegúrese de hacer una copia de seguridad de sus archivos primero. -Puede crear un archivo por lotes, por ejemplo `code.bat`, para facilitar la ejecución de Code-Checker en Windows: +Para facilitar la ejecución, podemos crear un archivo `code.bat`: ```shell -php path_to\Nette_tools\Code-Checker\code-checker %* +php path_to_Nette_tools\Code-Checker\code-checker %* ``` -¿Qué hace Code-Checker? -======================= +¿Qué hace todo esto? +==================== -- elimina [la lista de materiales |nette:glossary#bom] +- elimina el [BOM |nette:glossary#BOM] - comprueba la validez de las plantillas [Latte |latte:] - comprueba la validez de los archivos `.neon`, `.php` y `.json` -- comprueba los [caracteres de control |nette:glossary#control characters] -- comprueba si el fichero está codificado en UTF-8 -- controla los errores ortográficos `/* @annotations */` (falta el segundo asterisco) -- elimina las etiquetas finales de PHP `?>` en los archivos PHP -- elimina los espacios en blanco finales y las líneas en blanco innecesarias al final de un archivo -- normaliza los finales de línea a los predeterminados por el sistema (con el parámetro `-l` ) +- comprueba la presencia de [caracteres de control |nette:glossary#Caracteres de control] +- comprueba si el archivo está codificado en UTF-8 +- comprueba las `/* @anotaciones */` escritas incorrectamente (falta el asterisco) +- elimina el cierre `?>` de los archivos PHP +- elimina los espacios finales y las líneas innecesarias al final del archivo +- normaliza los separadores de línea a los del sistema (si especifica la opción `-l`) {{leftbar: www:@menu-common}} diff --git a/code-checker/fr/@home.texy b/code-checker/fr/@home.texy index 26bf013fbd..13c595ee8c 100644 --- a/code-checker/fr/@home.texy +++ b/code-checker/fr/@home.texy @@ -1,26 +1,26 @@ -Vérificateur de code -******************** +Nette Code Checker +****************** .[perex] -L'outil appelé [Code Checker |https://github.com/nette/code-checker] vérifie et répare éventuellement certaines des erreurs formelles de votre code source. +L'outil [Code Checker |https://github.com/nette/code-checker] vérifie et corrige éventuellement certaines erreurs formelles dans vos codes sources. Installation ============ -Code Checker doit être installé comme projet, ne l'utilisez pas comme dépendance. +Code Checker ne doit pas être ajouté aux dépendances, mais installé en tant que projet. ```shell composer create-project nette/code-checker ``` -Ou installez-le globalement via : +Ou installez-le globalement en utilisant : ```shell composer global require nette/code-checker ``` -et assurez-vous que le répertoire des binaires du fournisseur global se trouve dans [votre variable d'environnement `$PATH` |https://getcomposer.org/doc/03-cli.md#global]. +et assurez-vous que votre répertoire global `vendor/bin` est dans [la variable d'environnement $PATH |https://getcomposer.org/doc/03-cli.md#global]. Utilisation @@ -30,36 +30,36 @@ Utilisation Usage: php code-checker [options] Options: - -d <path> Folder or file to scan (default: current directory) - -i | --ignore <mask> Files to ignore - -f | --fix Fixes files - -l | --eol Convert newline characters - --no-progress Do not show progress dots - --strict-types Checks whether PHP 7.0 directive strict_types is enabled + -d <path> Dossier ou fichier à analyser (par défaut : répertoire courant) + -i | --ignore <mask> Fichiers à ignorer + -f | --fix Corrige les fichiers + -l | --eol Convertit les caractères de nouvelle ligne + --no-progress N'affiche pas les points de progression + --strict-types Vérifie si la directive PHP 7.0 strict_types est activée ``` -Sans paramètres, il vérifie le répertoire de travail actuel en mode lecture seule, avec le paramètre `-f` il fixe les fichiers. +Sans paramètres, il vérifie le répertoire actuel en mode lecture seule, avec le paramètre `-f`, il corrige les fichiers. -Avant de vous familiariser avec l'outil, assurez-vous d'abord de sauvegarder vos fichiers. +Avant de vous familiariser avec lui, assurez-vous de sauvegarder d'abord vos fichiers. -Vous pouvez créer un fichier batch, par exemple `code.bat`, pour faciliter l'exécution de Code-Checker sous Windows : +Pour faciliter l'exécution, nous pouvons créer un fichier `code.bat` : ```shell -php path_to\Nette_tools\Code-Checker\code-checker %* +php chemin_vers_Nette_tools\Code-Checker\code-checker %* ``` -Que fait Code-Checker ? -======================= +Que fait-il ? +============= -- supprime la [nomenclature |nette:glossary#bom] -- vérifie la validité des modèles [Latte |latte:] -- contrôle la validité des fichiers `.neon`, `.php` et `.json` -- vérification des [caractères de contrôle |nette:glossary#control characters] -- vérifie si le fichier est codé en UTF-8 -- contrôle les fautes d'orthographe de `/* @annotations */` (deuxième astérisque manquant) -- supprime les balises de fin PHP `?>` dans les fichiers PHP -- supprime les espaces et les lignes vides inutiles à la fin d'un fichier -- normalise les fins de ligne par rapport au système par défaut (avec le paramètre `-l` ) +- supprime le [BOM |nette:glossary#BOM] +- vérifie la validité des templates [Latte |latte:] +- vérifie la validité des fichiers `.neon`, `.php` et `.json` +- vérifie la présence de [caractères de contrôle |nette:glossary#Caractères de contrôle] +- vérifie si le fichier est encodé en UTF-8 +- vérifie les `/* @annotations */` mal écrites (étoile manquante) +- supprime le `?>` de fin des fichiers PHP +- supprime les espaces de fin et les lignes vides inutiles à la fin du fichier +- normalise les séparateurs de lignes en séparateurs système (si vous spécifiez l'option `-l`) {{leftbar: www:@menu-common}} diff --git a/code-checker/hu/@home.texy b/code-checker/hu/@home.texy index ea9f70f8a0..e676665b2f 100644 --- a/code-checker/hu/@home.texy +++ b/code-checker/hu/@home.texy @@ -1,26 +1,26 @@ -Kódellenőrző -************ +Nette Code Checker +****************** .[perex] -A [Code Checker |https://github.com/nette/code-checker] nevű eszköz ellenőrzi és esetleg kijavítja a forráskódodban található formális hibákat. +A [Code Checker |https://github.com/nette/code-checker] eszköz ellenőrzi és szükség esetén kijavítja a forráskódjaiban található néhány formai hibát. Telepítés ========= -A Code Checker-t projektként kell telepíteni, ne használd függőségként. +A Code Checkert nem szabad a függőségekhez hozzáadni, hanem projektként kell telepíteni. ```shell composer create-project nette/code-checker ``` -Vagy telepítsük globálisan a: +Vagy telepítse globálisan a következővel: ```shell composer global require nette/code-checker ``` -és győződjön meg róla, hogy a globális vendor binárisok könyvtára szerepel a [`$PATH` környezeti változóban |https://getcomposer.org/doc/03-cli.md#global]. +és győződjön meg róla, hogy a globális `vendor/bin` könyvtára benne van a [$PATH környezeti változóban |https://getcomposer.org/doc/03-cli.md#global]. Használat @@ -30,36 +30,36 @@ Használat Usage: php code-checker [options] Options: - -d <path> Folder or file to scan (default: current directory) - -i | --ignore <mask> Files to ignore - -f | --fix Fixes files - -l | --eol Convert newline characters - --no-progress Do not show progress dots - --strict-types Checks whether PHP 7.0 directive strict_types is enabled + -d <path> Szkennelendő mappa vagy fájl (alapértelmezett: aktuális könyvtár) + -i | --ignore <mask> Figyelmen kívül hagyandó fájlok + -f | --fix Javítja a fájlokat + -l | --eol Újsor karakterek konvertálása + --no-progress Ne jelenítse meg a folyamatjelző pontokat + --strict-types Ellenőrzi, hogy a PHP 7.0 strict_types direktíva engedélyezve van-e ``` -Paraméterek nélkül az aktuális munkakönyvtárat ellenőrzi csak olvasási módban, a `-f` paraméterrel pedig a fájlokat javítja. +Paraméterek nélkül az aktuális könyvtárat ellenőrzi read-only módban, a `-f` paraméterrel javítja a fájlokat. -Mielőtt megismerkedne az eszközzel, először mindenképpen készítsen biztonsági mentést a fájljairól. +Mielőtt megismerkedne vele, mindenképpen készítsen biztonsági másolatot a fájlokról. -A Code-Checker könnyebb futtatásához Windows alatt létrehozhat egy kötegelt fájlt, pl. `code.bat`: +A könnyebb indítás érdekében létrehozhatunk egy `code.bat` fájlt: ```shell -php path_to\Nette_tools\Code-Checker\code-checker %* +php path_to_Nette_tools\Code-Checker\code-checker %* ``` -Mit csinál a Code-Checker? -========================== +Mit csinál pontosan? +==================== -- eltávolítja a [BOM-ot |nette:glossary#bom] +- eltávolítja a [BOM |nette:glossary#BOM]-ot - ellenőrzi a [Latte |latte:] sablonok érvényességét -- ellenőrzi a `.neon`, `.php` és `.json` fájlok érvényességét. -- [vezérlő karakterek ellenőrzése|nette:glossary#control characters] +- ellenőrzi a `.neon`, `.php` és `.json` fájlok érvényességét +- ellenőrzi a [vezérlőkarakterek |nette:glossary#Vezérlő karakterek] előfordulását - ellenőrzi, hogy a fájl UTF-8 kódolású-e -- ellenőrzi a hibásan írt `/* @annotations */` címet (hiányzik a második csillag). -- eltávolítja a `?>` PHP végződésű címkéket a PHP-fájlokban. -- eltávolítja a fájl végéről a hátul lévő szóközöket és a felesleges üres sorokat -- normalizálja a sorvégeket a rendszer alapértelmezéséhez (a `-l` paraméterrel) +- ellenőrzi a hibásan írt `/* @anotace */` (hiányzik a csillag) +- eltávolítja a záró `?>` taget a PHP fájlokból +- eltávolítja a jobb oldali szóközöket és a felesleges sorokat a fájl végéről +- normalizálja a sorelválasztókat a rendszer alapértelmezettjére (ha megadja a `-l` opciót) {{leftbar: www:@menu-common}} diff --git a/code-checker/it/@home.texy b/code-checker/it/@home.texy index a01ed69648..4529024b15 100644 --- a/code-checker/it/@home.texy +++ b/code-checker/it/@home.texy @@ -1,30 +1,30 @@ -Controllore di codice -********************* +Nette Code Checker +****************** .[perex] -Lo strumento chiamato [Code Checker |https://github.com/nette/code-checker] controlla ed eventualmente ripara alcuni errori formali del codice sorgente. +Lo strumento [Code Checker |https://github.com/nette/code-checker] controlla e, se necessario, corregge alcuni degli errori formali nei vostri codici sorgente. Installazione ============= -Code Checker deve essere installato come progetto, non deve essere usato come dipendenza. +Code Checker non dovrebbe essere aggiunto alle dipendenze, ma installato come progetto. ```shell composer create-project nette/code-checker ``` -Oppure installarlo globalmente tramite: +Oppure installatelo globalmente tramite: ```shell composer global require nette/code-checker ``` -e assicurarsi che la cartella dei binari del fornitore globale sia nella [variabile d'ambiente `$PATH` |https://getcomposer.org/doc/03-cli.md#global]. +e assicuratevi che la vostra directory globale `vendor/bin` sia nella [variabile d'ambiente $PATH |https://getcomposer.org/doc/03-cli.md#global]. -Uso -=== +Utilizzo +======== ``` Usage: php code-checker [options] @@ -38,28 +38,28 @@ Options: --strict-types Checks whether PHP 7.0 directive strict_types is enabled ``` -Senza parametri, controlla la directory di lavoro corrente in modalità di sola lettura, con il parametro `-f` corregge i file. +Senza parametri, controlla la directory corrente in modalità di sola lettura, con il parametro `-f` corregge i file. -Prima di conoscere lo strumento, assicuratevi di eseguire un backup dei vostri file. +Prima di familiarizzare con esso, assicuratevi di eseguire il backup dei file. -È possibile creare un file batch, ad esempio `code.bat`, per facilitare l'esecuzione di Code-Checker in Windows: +Per un avvio più semplice, possiamo creare un file `code.bat`: ```shell -php path_to\Nette_tools\Code-Checker\code-checker %* +php percorso_a_Nette_tools\Code-Checker\code-checker %* ``` -Cosa fa Code-Checker? -===================== +Cosa fa? +======== -- rimuove la [distinta base |nette:glossary#bom] -- controlla la validità dei modelli [Latte |latte:] +- rimuove il [BOM |nette:glossary#BOM] +- controlla la validità dei template [Latte |latte:] - controlla la validità dei file `.neon`, `.php` e `.json` -- verifica la presenza di [caratteri di controllo |nette:glossary#control characters] +- controlla la presenza di [caratteri di controllo |nette:glossary#Caratteri di controllo] - controlla se il file è codificato in UTF-8 -- controlla gli errori di scrittura di `/* @annotations */` (manca il secondo asterisco) -- rimuove i tag finali PHP `?>` nei file PHP -- rimuove gli spazi bianchi di coda e le righe vuote non necessarie dalla fine di un file -- normalizza le terminazioni di riga ai valori predefiniti dal sistema (con il parametro `-l` ). +- controlla le annotazioni `/* @anotace */` scritte male (manca l'asterisco) +- rimuove i tag di chiusura `?>` dai file PHP +- rimuove gli spazi finali e le righe vuote alla fine del file +- normalizza i separatori di riga a quelli di sistema (se si specifica l'opzione `-l`) {{leftbar: www:@menu-common}} diff --git a/code-checker/ja/@home.texy b/code-checker/ja/@home.texy new file mode 100644 index 0000000000..b01ce40115 --- /dev/null +++ b/code-checker/ja/@home.texy @@ -0,0 +1,65 @@ +Nette Code Checker +****************** + +.[perex] +[Code Checker |https://github.com/nette/code-checker]ツールは、ソースコード内のいくつかの形式的なエラーをチェックし、必要に応じて修正します。 + + +インストール +====== + +Code Checkerは依存関係に追加するのではなく、プロジェクトとしてインストールする必要があります。 + +```shell +composer create-project nette/code-checker +``` + +または、次のようにグローバルにインストールします: + +```shell +composer global require nette/code-checker +``` + +そして、グローバルな `vendor/bin` ディレクトリが[$PATH 環境変数 |https://getcomposer.org/doc/03-cli.md#global]に含まれていることを確認してください。 + + +使用法 +=== + +``` +Usage: php code-checker [options] + +Options: + -d <path> スキャンするフォルダまたはファイル(デフォルト:現在のディレクトリ) + -i | --ignore <mask> 無視するファイル + -f | --fix ファイルを修正 + -l | --eol 改行文字を変換 + --no-progress 進捗ドットを表示しない + --strict-types PHP 7.0 ディレクティブ strict_types が有効かどうかをチェック +``` + +パラメータなしでは、現在のディレクトリを読み取り専用モードでチェックします。パラメータ `-f` を指定すると、ファイルを修正します。 + +慣れる前に、必ずファイルをバックアップしてください。 + +簡単に実行するために、`code.bat` ファイルを作成できます: + +```shell +php path_to_Nette_tools\Code-Checker\code-checker %* +``` + + +何をするのですか? +========= + +- [BOM |nette:glossary#BOM]を削除します +- [Latte |latte:] テンプレートの有効性をチェックします +- `.neon`、`.php`、`.json` ファイルの有効性をチェックします +- [制御文字 |nette:glossary#制御文字]の出現をチェックします +- ファイルがUTF-8でエンコードされているかチェックします +- 誤って記述された `/* @anotation */`(アスタリスクが欠けている)をチェックします +- PHP ファイルの末尾の `?>` を削除します +- ファイルの末尾の右側のスペースと不要な行を削除します +- (オプション `-l` を指定した場合)改行文字をシステム標準に正規化します + +{{leftbar: www:@menu-common}} diff --git a/code-checker/pl/@home.texy b/code-checker/pl/@home.texy index 695d9e59a4..23a99c3807 100644 --- a/code-checker/pl/@home.texy +++ b/code-checker/pl/@home.texy @@ -1,8 +1,8 @@ -Code Checker -************ +Nette Code Checker +****************** .[perex] -[Code Checker |https://github.com/nette/code-checker] sprawdzi i ewentualnie naprawi niektóre błędy formalne w Twoim kodzie źródłowym. +Narzędzie [Code Checker |https://github.com/nette/code-checker] sprawdza i ewentualnie naprawia niektóre formalne błędy w Twoich kodach źródłowych. Instalacja @@ -14,7 +14,7 @@ Code Checker nie powinien być dodawany do zależności, ale instalowany jako pr composer create-project nette/code-checker ``` -Lub zainstaluj go globalnie używając: +Lub zainstaluj go globalnie za pomocą: ```shell composer global require nette/code-checker @@ -23,43 +23,43 @@ composer global require nette/code-checker i upewnij się, że twój globalny katalog `vendor/bin` jest w [zmiennej środowiskowej $PATH |https://getcomposer.org/doc/03-cli.md#global]. -Korzystanie z -============= +Użycie +====== ``` Usage: php code-checker [options] Options: - -d <path> Folder or file to scan (default: current directory) - -i | --ignore <mask> Files to ignore - -f | --fix Fixes files - -l | --eol Convert newline characters - --no-progress Do not show progress dots - --strict-types Checks whether PHP 7.0 directive strict_types is enabled + -d <path> Katalog lub plik do skanowania (domyślnie: bieżący katalog) + -i | --ignore <mask> Pliki do ignorowania + -f | --fix Naprawia pliki + -l | --eol Konwertuj znaki nowej linii + --no-progress Nie pokazuj kropek postępu + --strict-types Sprawdza, czy dyrektywa PHP 7.0 strict_types jest włączona ``` -Bez parametrów sprawdza bieżący katalog w trybie tylko do odczytu, z `-f`, naprawia pliki. +Bez parametrów sprawdza bieżący katalog w trybie tylko do odczytu, z parametrem `-f` naprawia pliki. -Pamiętaj, aby najpierw wykonać kopię zapasową swoich plików, zanim się z nim zapoznasz. +Zanim się z nim zapoznasz, na pewno najpierw zrób kopię zapasową plików. -Aby ułatwić jego uruchomienie, możemy stworzyć plik `code.bat`: +Dla łatwiejszego uruchamiania możemy utworzyć plik `code.bat`: ```shell -php cesta_k_Nette_tools\Code-Checker\code-checker %* +php sciezka_do_Nette_tools\Code-Checker\code-checker %* ``` -Co to robi? -=========== +Co wszystko robi? +================= -- usuwa [BOM |nette:glossary#bom] -- sprawdza ważność szablonów [Latte |latte:] -- sprawdza ważność stron `.neon`, `.php` oraz `.json` -- sprawdza obecność [znaków sterujących |nette:glossary#Control-Characters] -- sprawdza czy plik jest zakodowany w UTF-8 -- sprawdza, czy nie ma błędnie napisanej strony `/* @anotace */` (brak gwiazdki) -- usuwa terminator `?>` z plików PHP -- usuwa prawe spacje i niepotrzebne linie na końcu pliku -- normalizuje separatory linii do systemowych separatorów linii (jeśli podano opcję `-l`) +- usuwa [BOM |nette:glossary#BOM] +- sprawdza poprawność szablonów [Latte |latte:] +- sprawdza poprawność plików `.neon`, `.php` i `.json` +- sprawdza występowanie [znaków kontrolnych |nette:glossary#Znaki kontrolne] +- sprawdza, czy plik jest zakodowany w UTF-8 +- sprawdza błędnie zapisane `/* @adnotacje */` (brakuje gwiazdki) +- usuwa kończące `?>` w plikach PHP +- usuwa prawostronne spacje i zbędne linie na końcu pliku +- normalizuje separatory linii do systemowych (jeśli podasz opcję `-l`) {{leftbar: www:@menu-common}} diff --git a/code-checker/pt/@home.texy b/code-checker/pt/@home.texy index 94d1a1c77b..c50e0603c1 100644 --- a/code-checker/pt/@home.texy +++ b/code-checker/pt/@home.texy @@ -1,65 +1,65 @@ -Verificador de código -********************* +Nette Code Checker +****************** .[perex] -A ferramenta chamada [Code Checker |https://github.com/nette/code-checker] verifica e possivelmente repara alguns dos erros formais em seu código fonte. +A ferramenta [Code Checker |https://github.com/nette/code-checker] verifica e, opcionalmente, corrige alguns dos erros formais nos seus códigos-fonte. Instalação ========== -O Code Checker deve ser instalado como projeto, não o utilize como dependência. +Você não deve adicionar o Code Checker às suas dependências, mas instalá-lo como um projeto. ```shell composer create-project nette/code-checker ``` -Ou instalá-lo globalmente via: +Ou instale-o globalmente usando: ```shell composer global require nette/code-checker ``` -e certifique-se de que seu diretório global de binários de fornecedores esteja em [sua variável de ambiente `$PATH` |https://getcomposer.org/doc/03-cli.md#global]. +e certifique-se de que seu diretório global `vendor/bin` esteja na [variável de ambiente $PATH |https://getcomposer.org/doc/03-cli.md#global]. -Utilização -========== +Uso +=== ``` Usage: php code-checker [options] Options: - -d <path> Folder or file to scan (default: current directory) - -i | --ignore <mask> Files to ignore - -f | --fix Fixes files - -l | --eol Convert newline characters - --no-progress Do not show progress dots - --strict-types Checks whether PHP 7.0 directive strict_types is enabled + -d <path> Pasta ou arquivo para escanear (padrão: diretório atual) + -i | --ignore <mask> Arquivos a ignorar + -f | --fix Corrige arquivos + -l | --eol Converte caracteres de nova linha + --no-progress Não mostrar pontos de progresso + --strict-types Verifica se a diretiva strict_types do PHP 7.0 está habilitada ``` -Sem parâmetros, ele verifica o diretório de trabalho atual em um modo somente leitura, com o parâmetro `-f` ele corrige arquivos. +Sem parâmetros, verifica o diretório atual no modo somente leitura; com o parâmetro `-f`, corrige os arquivos. -Antes de conhecer a ferramenta, certifique-se de fazer backup de seus arquivos primeiro. +Antes de se familiarizar com ele, certifique-se de fazer backup dos seus arquivos primeiro. -Você pode criar um arquivo de lote, por exemplo `code.bat`, para facilitar a execução do Code-Checker no Windows: +Para facilitar a execução, podemos criar um arquivo `code.bat`: ```shell -php path_to\Nette_tools\Code-Checker\code-checker %* +php caminho_para_Nette_tools\Code-Checker\code-checker %* ``` -O que o Code-Checker faz? -========================= +O que ele faz? +============== -- remove a [lista técnica |nette:glossary#bom] -- verifica a validade dos modelos [Latte |latte:] +- remove o [BOM |nette:glossary#BOM] +- verifica a validade dos templates [Latte |latte:] - verifica a validade dos arquivos `.neon`, `.php` e `.json` -- verifica os [caracteres de controle |nette:glossary#control characters] +- verifica a ocorrência de [caracteres de controle |nette:glossary#Caracteres de controle] - verifica se o arquivo está codificado em UTF-8 -- controles mal soletrados `/* @annotations */` (falta o segundo asterisco) -- remove as tags finais do PHP `?>` em arquivos PHP -- remove o espaço em branco e as linhas em branco desnecessárias do final de um arquivo -- normaliza as terminações da linha para o sistema por defeito (com o parâmetro `-l` ) +- verifica `/* @anotações */` mal escritas (falta asterisco) +- remove `?>` de fechamento em arquivos PHP +- remove espaços em branco à direita e linhas desnecessárias no final do arquivo +- normaliza os separadores de linha para o padrão do sistema (se você usar a opção `-l`) {{leftbar: www:@menu-common}} diff --git a/code-checker/ro/@home.texy b/code-checker/ro/@home.texy index 98b283b353..c73570354d 100644 --- a/code-checker/ro/@home.texy +++ b/code-checker/ro/@home.texy @@ -1,26 +1,26 @@ -Verificator de coduri -********************* +Nette Code Checker +****************** .[perex] -Instrumentul numit [Code Checker |https://github.com/nette/code-checker] verifică și, eventual, repară unele dintre erorile formale din codul dvs. sursă. +Instrumentul [Code Checker |https://github.com/nette/code-checker] verifică și, eventual, corectează unele dintre erorile formale din codurile dvs. sursă. Instalare ========= -Code Checker trebuie instalat ca proiect, nu îl utilizați ca dependență. +Code Checker nu ar trebui adăugat la dependențe, ci instalat ca proiect. ```shell composer create-project nette/code-checker ``` -Sau instalați-l la nivel global prin: +Sau instalați-l global folosind: ```shell composer global require nette/code-checker ``` -și asigurați-vă că directorul global al binarelor furnizorului este în [variabila de mediu `$PATH` |https://getcomposer.org/doc/03-cli.md#global]. +și asigurați-vă că directorul dvs. global `vendor/bin` se află în [variabila de mediu $PATH |https://getcomposer.org/doc/03-cli.md#global]. Utilizare @@ -30,36 +30,36 @@ Utilizare Usage: php code-checker [options] Options: - -d <path> Folder or file to scan (default: current directory) - -i | --ignore <mask> Files to ignore - -f | --fix Fixes files - -l | --eol Convert newline characters - --no-progress Do not show progress dots - --strict-types Checks whether PHP 7.0 directive strict_types is enabled + -d <path> Director sau fișier de scanat (implicit: directorul curent) + -i | --ignore <mask> Fișiere de ignorat + -f | --fix Corectează fișierele + -l | --eol Convertește caracterele de sfârșit de linie + --no-progress Nu afișa punctele de progres + --strict-types Verifică dacă directiva PHP 7.0 strict_types este activată ``` -Fără parametri, verifică directorul de lucru curent în modul doar pentru citire, iar cu parametrul `-f` repară fișierele. +Fără parametri, verifică directorul curent în modul read-only, cu parametrul `-f` corectează fișierele. -Înainte de a cunoaște instrumentul, asigurați-vă că faceți mai întâi o copie de rezervă a fișierelor dvs. +Înainte de a vă familiariza cu el, asigurați-vă că faceți mai întâi o copie de rezervă a fișierelor. -Puteți crea un fișier batch, de exemplu `code.bat`, pentru o execuție mai ușoară a Code-Checker sub Windows: +Pentru o rulare mai ușoară, putem crea un fișier `code.bat`: ```shell -php path_to\Nette_tools\Code-Checker\code-checker %* +php cale_catre_Nette_tools\Code-Checker\code-checker %* ``` -Ce face Code-Checker? -===================== +Ce face? +======== -- elimină [BOM |nette:glossary#bom] +- elimină [BOM |nette:glossary#BOM] - verifică validitatea șabloanelor [Latte |latte:] - verifică validitatea fișierelor `.neon`, `.php` și `.json` -- verifică dacă există [caractere de control |nette:glossary#control characters] +- verifică prezența [caracterelor de control |nette:glossary#Caractere de control] - verifică dacă fișierul este codificat în UTF-8 -- controlează dacă `/* @annotations */` este scris greșit (lipsește al doilea asterisc) -- elimină etichetele finale PHP `?>` din fișierele PHP -- elimină spațiile albe și liniile goale inutile de la sfârșitul unui fișier -- normalizează terminațiile de linie la valoarea implicită a sistemului (cu parametrul `-l` ) +- verifică `/* @adnotari */` scrise incorect (lipsește asteriscul) +- elimină `?>` de la sfârșitul fișierelor PHP +- elimină spațiile de la sfârșitul rândului și rândurile goale inutile de la sfârșitul fișierului +- normalizează separatorii de rând la cei de sistem (dacă specificați opțiunea `-l`) {{leftbar: www:@menu-common}} diff --git a/code-checker/ru/@home.texy b/code-checker/ru/@home.texy index f40f5e5416..911ba624aa 100644 --- a/code-checker/ru/@home.texy +++ b/code-checker/ru/@home.texy @@ -1,26 +1,26 @@ -Code Checker -************ +Nette Code Checker +****************** .[perex] -Инструмент под названием [Code Checker |https://github.com/nette/code-checker] проверяет и, по возможности, исправляет некоторые формальные ошибки в вашем исходном коде. +Инструмент [Code Checker |https://github.com/nette/code-checker] проверяет и при необходимости исправляет некоторые формальные ошибки в ваших исходных кодах. Установка ========= -Code Checker должен быть установлен как проект, не используйте его как зависимость. +Code Checker не следует добавлять в зависимости, а устанавливать как проект. ```shell composer create-project nette/code-checker ``` -Или установите его глобально через: +Или установите его глобально с помощью: ```shell composer global require nette/code-checker ``` -и убедитесь, что каталог глобальных двоичных файлов поставщика находится в [вашей переменной окружения `$PATH`|https://getcomposer.org/doc/03-cli.md#global]. +и убедитесь, что ваш глобальный каталог `vendor/bin` находится в [переменной окружения $PATH |https://getcomposer.org/doc/03-cli.md#global]. Использование @@ -38,28 +38,28 @@ Options: --strict-types Checks whether PHP 7.0 directive strict_types is enabled ``` -Без параметров проверяет текущий рабочий каталог в режиме только для чтения, с параметром `-f` исправляет файлы. +Без параметров проверяет текущий каталог в режиме только для чтения, с параметром `-f` исправляет файлы. -Прежде чем знакомиться с этим инструментом, обязательно сделайте резервную копию своих файлов. +Прежде чем ознакомиться с ним, обязательно сделайте резервную копию файлов. -Вы можете создать пакетный файл, например, `code.bat`, для более удобного запуска Code-Checker под Windows: +Для более легкого запуска можно создать файл `code.bat`: ```shell -php path_to\Nette_tools\Code-Checker\code-checker %* +php path_to_Nette_tools\Code-Checker\code-checker %* ``` -Что делает Code-Checker? -======================== +Что он делает? +============== -- удаляет [BOM |nette:glossary#bom]. -- проверяет валидность шаблонов [Latte |latte:]. -- проверяет валидность файлов `.neon`, `.php` и `.json`. -- проверяет наличие [управляющих символов |nette:glossary#Control-Characters]. +- удаляет [BOM |nette:glossary#BOM] +- проверяет валидность шаблонов [Latte |latte:] +- проверяет валидность файлов `.neon`, `.php` и `.json` +- проверяет наличие [управляющих символов |nette:glossary#Управляющие символы] - проверяет, закодирован ли файл в UTF-8 -- контролирует правильность написания `/* @annotations */` (пропущена вторая звездочка) -- удаляет завершающие теги PHP `?>` в файлах PHP -- удаляет из конца файла пробельные символы и ненужные пустые строки -- нормализует окончания строк к системному значению по умолчанию (с параметром `-l`) +- проверяет неправильно записанные `/* @anotace */` (отсутствует звездочка) +- удаляет завершающий `?>` у PHP-файлов +- удаляет пробелы в конце строк и лишние строки в конце файла +- нормализует разделители строк до системных (если указана опция `-l`) {{leftbar: www:@menu-common}} diff --git a/code-checker/sl/@home.texy b/code-checker/sl/@home.texy index 29caf09d7d..27cbc98ca0 100644 --- a/code-checker/sl/@home.texy +++ b/code-checker/sl/@home.texy @@ -1,26 +1,26 @@ -Preverjanje kode -**************** +Nette Code Checker +****************** .[perex] -Orodje, imenovano [Code Checker |https://github.com/nette/code-checker], preveri in po možnosti popravi nekatere formalne napake v vaši izvorni kodi. +Orodje [Code Checker |https://github.com/nette/code-checker] preveri in po potrebi popravi nekatere formalne napake v vaših izvornih kodah. Namestitev ========== -Code Checker je treba namestiti kot projekt, ne uporabljajte ga kot odvisnost. +Code Checkerja ne bi smeli dodajati med odvisnosti, ampak ga namestiti kot projekt. ```shell composer create-project nette/code-checker ``` -Ali pa ga namestite globalno prek: +Ali pa ga namestite globalno s pomočjo: ```shell composer global require nette/code-checker ``` -in se prepričajte, da je imenik z binarnimi datotekami globalnega prodajalca v [spremenljivki okolja `$PATH` |https://getcomposer.org/doc/03-cli.md#global]. +in se prepričajte, da je vaš globalni imenik `vendor/bin` v [okoljski spremenljivki $PATH |https://getcomposer.org/doc/03-cli.md#global]. Uporaba @@ -38,28 +38,28 @@ Options: --strict-types Checks whether PHP 7.0 directive strict_types is enabled ``` -Brez parametrov preveri trenutni delovni imenik v načinu samo za branje, s parametrom `-f` pa popravi datoteke. +Brez parametrov preveri trenutni imenik v načinu samo za branje, s parametrom `-f` popravlja datoteke. -Preden se seznanite z orodjem, najprej naredite varnostno kopijo svojih datotek. +Preden se z njim seznanite, si vsekakor najprej varnostno kopirajte datoteke. -Za lažje izvajanje programa Code-Checker v operacijskem sistemu Windows lahko ustvarite paketno datoteko, npr. `code.bat`: +Za lažje zaganjanje si lahko ustvarimo datoteko `code.bat`: ```shell -php path_to\Nette_tools\Code-Checker\code-checker %* +php pot_do_Nette_tools\Code-Checker\code-checker %* ``` -Kaj počne program Code-Checker? -=============================== +Kaj vse počne? +============== -- odstrani [BOM |nette:glossary#bom] -- preveri veljavnost predlog [Latte |latte:] -- preveri veljavnost datotek `.neon`, `.php` in `.json` -- preveri [kontrolne znake |nette:glossary#control characters] -- preveri, ali je datoteka kodirana v UTF-8 -- preveri napačno zapisano `/* @annotations */` (manjka druga zvezdica) -- odstrani končne oznake PHP `?>` v datotekah PHP -- na koncu datoteke odstrani zaključni beli prostor in nepotrebne prazne vrstice -- normalizira konce vrstic na privzete sistemske (s parametrom `-l` ) +- odstranjuje [BOM |nette:glossary#BOM] +- preverja veljavnost [Latte |latte:] predlog +- preverja veljavnost datotek `.neon`, `.php` in `.json` +- preverja pojav [kontrolnih znakov |nette:glossary#Kontrolni znaki] +- preverja, ali je datoteka kodirana v UTF-8 +- preverja napačno zapisane `/* @anotace */` (manjka zvezdica) +- odstranjuje zaključne `?>` pri PHP datotekah +- odstranjuje desne presledke in nepotrebne vrstice na koncu datoteke +- normalizira ločila vrstic na sistemske (če navedete opcijo `-l`) {{leftbar: www:@menu-common}} diff --git a/code-checker/tr/@home.texy b/code-checker/tr/@home.texy index cf4217fe43..ea2e1ecb22 100644 --- a/code-checker/tr/@home.texy +++ b/code-checker/tr/@home.texy @@ -1,26 +1,26 @@ -Kod Denetleyicisi -***************** +Nette Code Checker +****************** .[perex] -[Code Checker |https://github.com/nette/code-checker] adlı araç, kaynak kodunuzdaki bazı biçimsel hataları kontrol eder ve muhtemelen onarır. +[Kod Denetleyicisi |https://github.com/nette/code-checker] aracı, kaynak kodlarınızdaki bazı biçimsel hataları kontrol eder ve gerekirse düzeltir. Kurulum ======= -Code Checker proje olarak yüklenmelidir, bağımlılık olarak kullanmayın. +Kod Denetleyicisini bağımlılıklara eklememeli, bir proje olarak kurmalısınız. ```shell composer create-project nette/code-checker ``` -Ya da global olarak şu yolla yükleyin: +Veya küresel olarak kurun: ```shell composer global require nette/code-checker ``` -ve global satıcı ikili dosyaları dizininizin [`$PATH` ortam değişkeninizde |https://getcomposer.org/doc/03-cli.md#global] olduğundan emin olun. +ve küresel `vendor/bin` dizininizin [$PATH ortam değişkeninde |https://getcomposer.org/doc/03-cli.md#global] olduğundan emin olun. Kullanım @@ -30,36 +30,36 @@ Kullanım Usage: php code-checker [options] Options: - -d <path> Folder or file to scan (default: current directory) - -i | --ignore <mask> Files to ignore - -f | --fix Fixes files - -l | --eol Convert newline characters - --no-progress Do not show progress dots - --strict-types Checks whether PHP 7.0 directive strict_types is enabled + -d <path> Taranacak klasör veya dosya (varsayılan: geçerli dizin) + -i | --ignore <mask> Yoksayılacak dosyalar + -f | --fix Dosyaları düzeltir + -l | --eol Yeni satır karakterlerini dönüştürür + --no-progress İlerleme noktalarını gösterme + --strict-types PHP 7.0 direktifi strict_types'ın etkin olup olmadığını kontrol eder ``` -Parametreler olmadan, geçerli çalışma dizinini salt okunur modda kontrol eder, `-f` parametresi ile dosyaları düzeltir. +Parametresiz olarak geçerli dizini salt okunur modda kontrol eder, `-f` parametresiyle dosyaları düzeltir. -Aracı tanımaya başlamadan önce dosyalarınızı yedeklediğinizden emin olun. +Tanışmadan önce dosyalarınızı mutlaka yedekleyin. -Windows altında Code-Checker'ı daha kolay çalıştırmak için `code.bat` gibi bir toplu iş dosyası oluşturabilirsiniz: +Daha kolay çalıştırmak için bir `code.bat` dosyası oluşturabiliriz: ```shell -php path_to\Nette_tools\Code-Checker\code-checker %* +php nette_araçlarının_yolu\Code-Checker\code-checker %* ``` -Code-Checker Ne İşe Yarar? -========================== +Ne yapar? +========= -- [BOM'u |nette:glossary#bom] kaldırır +- [BOM |nette:glossary#BOM] kaldırır - [Latte |latte:] şablonlarının geçerliliğini kontrol eder - `.neon`, `.php` ve `.json` dosyalarının geçerliliğini kontrol eder -- [kontrol karakterlerini kontrol eder|nette:glossary#control characters] -- dosyanın UTF-8 olarak kodlanıp kodlanmadığını kontrol eder -- `/* @annotations */` adresinde yanlış yazılmış kontroller (ikinci yıldız işareti eksik) -- PHP dosyalarındaki `?>` PHP bitiş etiketlerini kaldırır -- bir dosyanın sonundaki boşlukları ve gereksiz boş satırları kaldırır -- satır sonlarını sistem varsayılanına normalleştirir ( `-l` parametresiyle) +- [Kontrol karakterlerinin |nette:glossary#Kontrol Karakterleri] varlığını kontrol eder +- Dosyanın UTF-8 olarak kodlanıp kodlanmadığını kontrol eder +- Yanlış yazılmış `/* @anotace */` (yıldız eksik) kontrol eder +- PHP dosyalarındaki kapanış `?>` etiketini kaldırır +- Sağdaki boşlukları ve dosyanın sonundaki gereksiz satırları kaldırır +- Satır ayırıcılarını sistem varsayılanına normalleştirir (`-l` seçeneğini belirtirseniz) {{leftbar: www:@menu-common}} diff --git a/code-checker/uk/@home.texy b/code-checker/uk/@home.texy index 961b43c231..261ea7550c 100644 --- a/code-checker/uk/@home.texy +++ b/code-checker/uk/@home.texy @@ -1,26 +1,26 @@ -Code Checker -************ +Nette Code Checker +****************** .[perex] -Інструмент під назвою [Code Checker |https://github.com/nette/code-checker] перевіряє і, за можливості, виправляє деякі формальні помилки у вашому вихідному коді. +Інструмент [Code Checker |https://github.com/nette/code-checker] перевіряє та, за потреби, виправляє деякі формальні помилки у ваших вихідних кодах. Встановлення ============ -Code Checker має бути встановлений як проект, не використовуйте його як залежність. +Code Checker не слід додавати до залежностей, а встановлювати як проект. ```shell composer create-project nette/code-checker ``` -Або встановіть його глобально через: +Або встановіть його глобально за допомогою: ```shell composer global require nette/code-checker ``` -і переконайтеся, що каталог глобальних двійкових файлів постачальника знаходиться у [вашій змінній оточення `$PATH`. |https://getcomposer.org/doc/03-cli.md#global] +і переконайтеся, що ваш глобальний каталог `vendor/bin` знаходиться у [змінній середовища $PATH |https://getcomposer.org/doc/03-cli.md#global]. Використання @@ -30,36 +30,36 @@ composer global require nette/code-checker Usage: php code-checker [options] Options: - -d <path> Folder or file to scan (default: current directory) - -i | --ignore <mask> Files to ignore - -f | --fix Fixes files - -l | --eol Convert newline characters - --no-progress Do not show progress dots - --strict-types Checks whether PHP 7.0 directive strict_types is enabled + -d <path> Папка або файл для сканування (за замовчуванням: поточний каталог) + -i | --ignore <mask> Файли, які слід ігнорувати + -f | --fix Виправляє файли + -l | --eol Перетворює символи нового рядка + --no-progress Не показувати точки прогресу + --strict-types Перевіряє, чи увімкнена директива PHP 7.0 strict_types ``` -Без параметрів перевіряє поточний робочий каталог у режимі тільки для читання, з параметром `-f` виправляє файли. +Без параметрів перевіряє поточний каталог у режимі лише для читання, з параметром `-f` виправляє файли. -Перш ніж знайомитися з цим інструментом, обов'язково зробіть резервну копію своїх файлів. +Перш ніж ознайомитися з ним, обов'язково зробіть резервну копію файлів. -Ви можете створити пакетний файл, наприклад, `code.bat`, для зручнішого запуску Code-Checker під Windows: +Для полегшення запуску можна створити файл `code.bat`: ```shell -php path_to\Nette_tools\Code-Checker\code-checker %* +php шлях_до_Nette_tools\Code-Checker\code-checker %* ``` -Що робить Code-Checker? -======================= +Що він робить? +============== -- видаляє [BOM |nette:glossary#bom]. -- перевіряє валідність шаблонів [Latte |latte:]. -- перевіряє валідність файлів `.neon`, `.php` і `.json`. -- перевіряє наявність [керуючих символів |nette:glossary#Control-Characters]. -- перевіряє, чи закодований файл у UTF-8 -- контролює правильність написання `/* @annotations */` (пропущена друга зірочка) -- видаляє завершальні теги PHP `?>` у файлах PHP -- видаляє з кінця файлу пробільні символи і непотрібні порожні рядки -- нормалізує закінчення рядків до системного значення за замовчуванням (з параметром `-l`) +- видаляє [BOM |nette:glossary#BOM] +- перевіряє валідність [Latte |latte:] шаблонів +- перевіряє валідність файлів `.neon`, `.php` та `.json` +- перевіряє наявність [керуючих символів |nette:glossary#Керуючі символи] +- перевіряє, чи файл закодований у UTF-8 +- перевіряє неправильно записані `/* @anotace */` (відсутня зірочка) +- видаляє завершальний `?>` у PHP файлах +- видаляє пробіли в кінці рядка та зайві рядки в кінці файлу +- нормалізує роздільники рядків до системних (якщо вказано опцію `-l`) {{leftbar: www:@menu-common}} diff --git a/component-model/bg/@home.texy b/component-model/bg/@home.texy index 133570acc4..2209fed3e8 100644 --- a/component-model/bg/@home.texy +++ b/component-model/bg/@home.texy @@ -1,57 +1,53 @@ -Модел на компонента -******************* +Компонентен модел +***************** .[perex] -Важно понятие в Nette е компонентът. Вмъкваме [визуални интерактивни компоненти |application:components] в страници, формуляри или всички техни елементи също са компоненти. Има два основни класа, от които всички тези компоненти наследяват, те са част от пакета `nette/component-model` и отговарят за създаването на йерархията на дървото на компонентите. +Важно понятие в Nette е компонентът. В страниците вмъкваме [визуални интерактивни компоненти |application:components], компоненти са и формите или всички техни елементи. Основните два класа, от които всички тези компоненти наследяват, са част от пакета `nette/component-model` и имат за цел да създават дървовидна йерархия на компоненти. Component ========= -[api:Nette\ComponentModel\Component] е общият предшественик на всички компоненти. Той съдържа метод `getName()`, който връща името на компонента, и метод `getParent()`, който връща неговия родител. И двата параметъра могат да бъдат зададени чрез метода `setParent()` - първият параметър е родителят, а вторият е името на компонента. +[api:Nette\ComponentModel\Component] е общият предтеча на всички компоненти. Съдържа методи `getName()`, връщащ името на компонента, и метод `getParent()`, връщащ неговия родител. И двете могат да бъдат зададени с метода `setParent()` - първият параметър е родителят, а вторият - името на компонента. lookup(string $type): ?Component .[method] ------------------------------------------ -Търси в йерархията обект от желания клас или интерфейс. Например `$component->lookup(Nette\Application\UI\Presenter::class)` връща presenter, ако компонент е свързан с него въпреки няколко нива. +Търси в йерархията нагоре обект от желания клас или интерфейс. Например `$component->lookup(Nette\Application\UI\Presenter::class)` връща презентер, ако компонентът е свързан с него, дори през няколко нива. lookupPath(string $type): ?string .[method] ------------------------------------------- -Връща т.нар. път, който представлява низ, образуван от сбиването на имената на всички компоненти в пътя между текущия компонент и търсения компонент. Така например `$component->lookupPath(Nette\Application\UI\Presenter::class)` връща уникален идентификатор на компонент спрямо главния. +Връща така наречения път, който е низ, получен чрез свързване на имената на всички компоненти по пътя между текущия и търсения компонент. Така например `$component->lookupPath(Nette\Application\UI\Presenter::class)` връща уникален идентификатор на компонента спрямо презентера. Container ========= -[api:Nette\ComponentModel\Container] е родителски компонент, т.е. компонент, който съдържа дъщерни компоненти и по този начин образува дървовидна структура. Той разполага с методи за лесно добавяне, извличане и отстраняване на компоненти. Той е родоначалник например на формата или класовете `Control` и `Presenter`. +[api:Nette\ComponentModel\Container] е родителският компонент, т.е. компонент, съдържащ наследници и така образуващ дървовидна структура. Разполага с методи за лесно добавяне, получаване и премахване на обекти. Той е предтеча например на формата или на класовете `Control` и `Presenter`. getComponent(string $name): ?Component .[method] ------------------------------------------------ -Връща компонент. Опитът за извикване на недефиниран дъщерен компонент води до извикване на factory [createComponent($name |api:Nette\ComponentModel\Container::createComponent()]). Методът `createComponent($name)` извиква метода `createComponent<component name>` в текущия компонент и предава името на компонента като параметър. След това създаденият компонент се предава на текущия компонент като негов дъщерен компонент. Наричаме тези фабрики за компоненти, като те могат да бъдат реализирани в класове, наследени от `Container`. +Връща компонент. При опит за получаване на недефиниран наследник се извиква фабриката `createComponent($name)`. Методът `createComponent($name)` извиква в текущия компонент метода `createComponent<име на компонента>` и като параметър му предава името на компонента. Създаденият компонент след това се добавя към текущия компонент като негов наследник. Тези методи наричаме фабрики за компоненти и могат да бъдат имплементирани от наследниците на класа `Container`. -Итерация над подчинени компоненти .[#toc-iterating-over-children] ------------------------------------------------------------------ +getComponents(): array .[method] +-------------------------------- +Връща преките наследници като масив. Ключовете съдържат имената на тези компоненти. Забележка: във версия 3.0.x методът връщаше итератор вместо масив и първият му параметър определяше дали компонентите да се обхождат в дълбочина, а вторият представляваше типов филтър. Тези параметри са deprecated. -Методът [getComponents($deep = false, $type = null |api:Nette\ComponentModel\Container::getComponents()] ) се използва за итерация. Първият параметър определя дали компонентите да се итерират в дълбочина (или рекурсивно). Ако се използва `true`, се итерират не само всички негови дъщерни компоненти, но и всички деца на неговите дъщерни компоненти и т.н. Вторият параметър служи за допълнителен филтър по клас или интерфейс. -```php -foreach ($form->getComponents(true, Nette\Forms\IControl::class) as $control) { - if (!$control->getRules()->validate()) { - // ... - } -} -``` +getComponentTree(): array .[method]{data-version:3.1.0} +------------------------------------------------------- +Получава цялата йерархия на компоненти, включително всички вложени подчинени компоненти, като индексиран масив. Търсенето се извършва първо в дълбочина. -Мониторинг на предците .[#toc-monitoring-of-ancestors] -====================================================== +Наблюдение на предците +====================== -Компонентният модел на Nette позволява много динамична работа с дървото (можем да изтриваме, преместваме, добавяме компоненти), така че би било грешка да разчитаме на факта, че след създаването на компонент родителят, родителят и т.н. са известни веднага (в конструктора). Обикновено родителят изобщо не е известен при създаването на компонента. +Компонентният модел на Nette позволява много динамична работа с дървото (можем да премахваме, преместваме, добавяме компоненти), затова би било грешка да се разчита, че след създаването на компонента веднага (в конструктора) е известен родителят, родителят на родителя и т.н. Най-често родителят изобщо не е известен при създаването. -Как да разберем, че даден компонент е бил добавен в дървото на презентатора? Не е достатъчно да се следи промяната на родителя, защото той може да е бил прикрепен към водещия, например. За това може да помогне методът [monitor($type, $attached, $detached) |api:Nette\ComponentModel\Component::monitor()]. Всеки компонент може да наблюдава произволен брой класове и интерфейси. Връзката или прекъсването на връзката се декларира чрез извикване на обратните извиквания `$attached` и `$detached`, съответно, и предаване на обект от наблюдавания клас. +Как да разберем кога компонентът е бил прикрепен към дървото на презентера? Наблюдението на промяната на родителя не е достатъчно, тъй като към презентера може да е бил прикрепен например родителят на родителя. Помага методът [monitor($type, $attached, $detached)|api:Nette\ComponentModel\Component::monitor()]. Всеки компонент може да наблюдава произволен брой класове и интерфейси. Прикрепването или откачането се съобщава чрез извикване на callback `$attached`, респ. `$detached`, и предаване на обекта на наблюдавания клас. -Пример: Класът `UploadControl`, който представлява елемент на формата за качване на файлове в Nette Forms, трябва да зададе атрибута на формата `enctype` на `multipart/form-data`. Но не трябва да е обвързан с никаква форма в момента на създаване на обекта. Кога да модифицирате формуляра? Решението е просто - създайте заявка за наблюдение в конструктора: +За по-добро разбиране, пример: класът `UploadControl`, представляващ формулярен елемент за качване на файлове в Nette Forms, трябва да зададе на формата атрибут `enctype` на стойност `multipart/form-data`. В момента на създаване на обекта обаче той може да не е прикрепен към никаква форма. В кой момент тогава да се модифицира формата? Решението е просто - в конструктора се иска наблюдение: ```php class UploadControl extends Nette\Forms\Controls\BaseControl @@ -68,7 +64,4 @@ class UploadControl extends Nette\Forms\Controls\BaseControl } ``` -, а когато формулярът стане достъпен, се извиква обратна връзка. (По-рано вместо това се използваха обичайните методи `attached` и `detached`.) - - -{{leftbar: nette:@menu-topics}} +и щом формата е налична, се извиква callback. (Преди това вместо него се използваха общите методи `attached`, респ. `detached`). diff --git a/component-model/bg/@meta.texy b/component-model/bg/@meta.texy new file mode 100644 index 0000000000..794cbc8522 --- /dev/null +++ b/component-model/bg/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Документация на Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/component-model/cs/@home.texy b/component-model/cs/@home.texy index 28e3d11fe5..7aa17b402f 100644 --- a/component-model/cs/@home.texy +++ b/component-model/cs/@home.texy @@ -30,18 +30,14 @@ getComponent(string $name): ?Component .[method] Vrací komponentu. Při pokusu o získání nedefinovaného potomka je zavolána továrna `createComponent($name)`. Metoda `createComponent($name)` zavolá v aktuální komponentě metodu `createComponent<název komponenty>` a jako parametr jí předá název komponenty. Vytvořená komponenta je poté přidána do aktuální komponenty jako její potomek. Těmto metodám říkáme továrny na komponenty a mohou je implementovat potomci třídy `Container`. -Iterování nad potomky ---------------------- +getComponents(): array .[method] +-------------------------------- +Vrací přímé potomky jako pole. Klíče obsahují názvy těchto komponent. Poznámka: ve verzi 3.0.x metoda namísto pole vracela iterátor a její první parametr určoval, zda se mají komponenty procházet do hloubky, a druhý představoval typový filtr. Tyto parametry jsou deprecated. -K iterování slouží metoda [getComponents($deep = false, $type = null)|api:Nette\ComponentModel\Container::getComponents()]. První parametr určuje, zda se mají komponenty procházet do hloubky (neboli rekurzivně). S hodnotou `true` tedy nejen projde všechny komponenty, jichž je rodičem, ale také potomky svých potomků atd. Druhý parametr slouží jako volitelný filtr podle tříd nebo rozhraní. -```php -foreach ($form->getComponents(true, Nette\Forms\IControl::class) as $control) { - if (!$control->getRules()->validate()) { - // ... - } -} -``` +getComponentTree(): array .[method]{data-version:3.1.0} +------------------------------------------------------- +Získá celou hierarchii komponent včetně všech vnořených podřízených komponent jako indexované pole. Prohledávání jde nejprve do hloubky. Monitorování předků @@ -69,6 +65,3 @@ class UploadControl extends Nette\Forms\Controls\BaseControl ``` a jakmile je formulář k dispozici, zavolá se callback. (Dříve se místo něj používala společná metoda `attached` resp. `detached`). - - -{{leftbar: nette:@menu-topics}} diff --git a/component-model/cs/@meta.texy b/component-model/cs/@meta.texy new file mode 100644 index 0000000000..08edde785b --- /dev/null +++ b/component-model/cs/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Dokumentace}} +{{leftbar: nette:@menu-topics}} diff --git a/component-model/de/@home.texy b/component-model/de/@home.texy index 55372b2be6..680880c8f3 100644 --- a/component-model/de/@home.texy +++ b/component-model/de/@home.texy @@ -1,57 +1,53 @@ -Komponenten-Modell -****************** +Komponentenmodell +***************** .[perex] -Ein wichtiges Konzept in Nette ist die Komponente. Wir fügen [visuelle interaktive Komponenten |application:components] in Seiten, Formulare oder alle ihre Elemente sind ebenfalls Komponenten. Es gibt zwei Basisklassen, von denen alle diese Komponenten erben, die Teil des Pakets `nette/component-model` sind und für die Erstellung der Komponentenbaumhierarchie verantwortlich sind. +Ein wichtiger Begriff in Nette ist die Komponente. Wir fügen [visuelle interaktive Komponenten |application:components] in Seiten ein, auch Formulare oder alle ihre Elemente sind Komponenten. Die beiden Basisklassen, von denen alle diese Komponenten erben, sind Teil des Pakets `nette/component-model` und haben die Aufgabe, eine Baumhierarchie von Komponenten zu erstellen. Component ========= -[api:Nette\ComponentModel\Component] ist der gemeinsame Vorfahre aller Komponenten. Sie enthält die Methode `getName()`, die den Namen der Komponente zurückgibt, und die Methode `getParent()`, die die übergeordnete Komponente zurückgibt. Beide können mit der Methode `setParent()` gesetzt werden - der erste Parameter ist der Elternteil und der zweite der Komponentenname. +[api:Nette\ComponentModel\Component] ist der gemeinsame Vorfahre aller Komponenten. Es enthält die Methoden `getName()`, die den Namen der Komponente zurückgibt, und die Methode `getParent()`, die ihren Elternteil zurückgibt. Beides kann mit der Methode `setParent()` eingestellt werden - der erste Parameter ist der Elternteil und der zweite der Komponentenname. lookup(string $type): ?Component .[method] ------------------------------------------ -Sucht in der Hierarchie nach einem Objekt der gewünschten Klasse oder Schnittstelle. Zum Beispiel gibt `$component->lookup(Nette\Application\UI\Presenter::class)` presenter zurück, wenn die Komponente trotz mehrerer Ebenen mit ihm verbunden ist. +Sucht in der Hierarchie nach oben nach einem Objekt der gewünschten Klasse oder Schnittstelle. Zum Beispiel gibt `$component->lookup(Nette\Application\UI\Presenter::class)` den Presenter zurück, wenn die Komponente, auch über mehrere Ebenen hinweg, mit ihm verbunden ist. lookupPath(string $type): ?string .[method] ------------------------------------------- -Gibt den so genannten Pfad zurück, d. h. eine Zeichenkette, die aus der Verkettung der Namen aller Komponenten auf dem Pfad zwischen der aktuellen Komponente und der gesuchten Komponente besteht. So gibt z. B. `$component->lookupPath(Nette\Application\UI\Presenter::class)` den eindeutigen Bezeichner der Komponente relativ zum Präsentator zurück. +Gibt den sogenannten Pfad zurück, eine Zeichenkette, die durch die Verkettung der Namen aller Komponenten auf dem Weg zwischen der aktuellen und der gesuchten Komponente entsteht. Zum Beispiel gibt `$component->lookupPath(Nette\Application\UI\Presenter::class)` einen eindeutigen Bezeichner der Komponente relativ zum Presenter zurück. Container ========= -[api:Nette\ComponentModel\Container] ist die übergeordnete Komponente, d. h. die Komponente, die die Kinder enthält und somit die Baumstruktur bildet. Er verfügt über Methoden zum einfachen Hinzufügen, Abrufen und Entfernen von Komponenten. Er ist der Vorfahre z.B. des Formulars oder der Klassen `Control` und `Presenter`. +[api:Nette\ComponentModel\Container] ist die Elternkomponente, d.h. eine Komponente, die Nachkommen enthält und somit eine Baumstruktur bildet. Sie verfügt über Methoden zum einfachen Hinzufügen, Abrufen und Entfernen von Objekten. Sie ist beispielsweise der Vorfahre von Formularen oder den Klassen `Control` und `Presenter`. getComponent(string $name): ?Component .[method] ------------------------------------------------ -Gibt eine Komponente zurück. Der Versuch, ein undefiniertes Kind aufzurufen, führt zum Aufruf der Fabrik [createComponent($name) |api:Nette\ComponentModel\Container::createComponent()]. Die Methode `createComponent($name)` ruft die Methode `createComponent<component name>` in der aktuellen Komponente auf und übergibt den Namen der Komponente als Parameter. Die erstellte Komponente wird dann an die aktuelle Komponente als ihr Kind übergeben. Wir nennen diese Komponentenfabriken, sie können in von `Container` geerbten Klassen implementiert werden. +Gibt die Komponente zurück. Beim Versuch, einen undefinierten Nachkommen abzurufen, wird die Factory `createComponent($name)` aufgerufen. Die Methode `createComponent($name)` ruft in der aktuellen Komponente die Methode `createComponent<Komponentenname>` auf und übergibt ihr den Komponentennamen als Parameter. Die erstellte Komponente wird dann der aktuellen Komponente als ihr Nachkomme hinzugefügt. Diese Methoden nennen wir Komponentenfabriken und sie können von Nachkommen der Klasse `Container` implementiert werden. -Iteration über Kinder .[#toc-iterating-over-children] ------------------------------------------------------ +getComponents(): array .[method] +-------------------------------- +Gibt die direkten Nachkommen als Array zurück. Die Schlüssel enthalten die Namen dieser Komponenten. Hinweis: In Version 3.0.x gab die Methode anstelle eines Arrays einen Iterator zurück, und ihr erster Parameter bestimmte, ob die Komponenten in die Tiefe durchlaufen werden sollten, und der zweite stellte einen Typfilter dar. Diese Parameter sind deprecated. -Die Methode [getComponents($deep = false, $type = null) |api:Nette\ComponentModel\Container::getComponents()] wird zur Iteration verwendet. Der erste Parameter gibt an, ob die Komponenten in der Tiefe (oder rekursiv) durchlaufen werden sollen. Bei `true` werden nicht nur alle untergeordneten Komponenten durchlaufen, sondern auch alle untergeordneten Komponenten der untergeordneten Komponenten, usw. Der zweite Parameter dient als optionaler Filter nach Klasse oder Schnittstelle. -```php -foreach ($form->getComponents(true, Nette\Forms\IControl::class) as $control) { - if (!$control->getRules()->validate()) { - // ... - } -} -``` +getComponentTree(): array .[method]{data-version:3.1.0} +------------------------------------------------------- +Ruft die gesamte Komponenten-Hierarchie einschließlich aller verschachtelten untergeordneten Komponenten als indiziertes Array ab. Die Suche erfolgt zuerst in die Tiefe. -Überwachung der Vorfahren .[#toc-monitoring-of-ancestors] -========================================================= +Überwachung der Vorfahren +========================= -Das Nette-Komponentenmodell erlaubt eine sehr dynamische Baumarbeit (wir können Komponenten entfernen, verschieben, hinzufügen), so dass es ein Fehler wäre, sich darauf zu verlassen, dass nach dem Erstellen einer Komponente der Elternteil, der Elternteil des Elternteils usw. sofort bekannt sind (im Konstruktor). Normalerweise ist das Elternteil bei der Erstellung der Komponente überhaupt nicht bekannt. +Das Komponentenmodell von Nette ermöglicht eine sehr dynamische Arbeit mit dem Baum (Komponenten können entfernt, verschoben, hinzugefügt werden), daher wäre es ein Fehler, sich darauf zu verlassen, dass nach dem Erstellen einer Komponente sofort (im Konstruktor) der Elternteil, der Elternteil des Elternteils usw. bekannt ist. Meistens ist der Elternteil zum Zeitpunkt der Erstellung überhaupt nicht bekannt. -Wie kann man herausfinden, wann eine Komponente zum Präsentationsbaum hinzugefügt wurde? Es reicht nicht aus, die Änderung des Parents zu verfolgen, denn der Parent des Parents könnte z.B. an den Presenter angehängt worden sein. Die [monitor($type, $attached, $detached) |api:Nette\ComponentModel\Component::monitor()] Methode kann hier helfen. Jede Komponente kann eine beliebige Anzahl von Klassen und Schnittstellen überwachen. Die Verbindung oder Trennung wird durch den Aufruf der Callbacks `$attached` bzw. `$detached` angekündigt, wobei das Objekt der überwachten Klasse übergeben wird. +Wie erkennt man, wann eine Komponente in den Presenter-Baum eingehängt wurde? Die Änderung des Elternteils zu beobachten reicht nicht aus, da möglicherweise der Elternteil des Elternteils mit dem Presenter verbunden wurde. Die Methode [monitor($type, $attached, $detached)|api:Nette\ComponentModel\Component::monitor()] hilft dabei. Jede Komponente kann eine beliebige Anzahl von Klassen und Schnittstellen überwachen. Das Einhängen oder Aushängen wird durch den Aufruf des Callbacks `$attached` bzw. `$detached` gemeldet, wobei das Objekt der überwachten Klasse übergeben wird. -Ein Beispiel: Die Klasse `UploadControl`, die das Formularelement zum Hochladen von Dateien in Nette Forms repräsentiert, muss das Attribut `enctype` des Formulars auf den Wert `multipart/form-data` setzen. Zum Zeitpunkt der Erstellung des Objekts muss es jedoch an kein Formular angehängt sein. Wann soll das Formular geändert werden? Die Lösung ist einfach - wir erstellen eine Anfrage zur Überwachung im Konstruktor: +Zum besseren Verständnis ein Beispiel: Die Klasse `UploadControl`, die ein Formularelement für den Datei-Upload in Nette Forms darstellt, muss das Attribut `enctype` des Formulars auf den Wert `multipart/form-data` setzen. Zum Zeitpunkt der Objekterstellung muss sie jedoch nicht mit einem Formular verbunden sein. Wann soll das Formular also modifiziert werden? Die Lösung ist einfach - im Konstruktor wird die Überwachung angefordert: ```php class UploadControl extends Nette\Forms\Controls\BaseControl @@ -68,7 +64,4 @@ class UploadControl extends Nette\Forms\Controls\BaseControl } ``` -und wenn das Formular verfügbar ist, wird der Callback aufgerufen. (Zuvor wurden stattdessen die üblichen Methoden `attached` und `detached` verwendet). - - -{{leftbar: nette:@menu-topics}} +und sobald das Formular verfügbar ist, wird der Callback aufgerufen. (Früher wurde stattdessen die gemeinsame Methode `attached` bzw. `detached` verwendet). diff --git a/component-model/de/@meta.texy b/component-model/de/@meta.texy new file mode 100644 index 0000000000..2cf383a5cf --- /dev/null +++ b/component-model/de/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Dokumentation}} +{{leftbar: nette:@menu-topics}} diff --git a/component-model/el/@home.texy b/component-model/el/@home.texy index d26b6d5527..b38ccc1262 100644 --- a/component-model/el/@home.texy +++ b/component-model/el/@home.texy @@ -1,57 +1,53 @@ -Μοντέλο συστατικών -****************** +Μοντέλο Component +***************** .[perex] -Μια σημαντική έννοια στη Nette είναι το συστατικό. Εισάγουμε [οπτικά διαδραστικά στοιχεία |application:components] σε σελίδες, φόρμες ή όλα τα στοιχεία τους είναι επίσης στοιχεία. Υπάρχουν δύο βασικές κλάσεις από τις οποίες κληρονομούν όλα αυτά τα συστατικά, αποτελούν μέρος του πακέτου `nette/component-model` και είναι υπεύθυνες για τη δημιουργία της ιεραρχίας του δέντρου των συστατικών. +Ένας σημαντικός όρος στο Nette είναι το component. Στις σελίδες εισάγουμε [οπτικά διαδραστικά components |application:components], τα components είναι επίσης φόρμες ή όλα τα στοιχεία τους. Οι δύο βασικές κλάσεις από τις οποίες κληρονομούν όλα αυτά τα components αποτελούν μέρος του πακέτου `nette/component-model` και έχουν ως αποστολή τη δημιουργία μιας ιεραρχικής δενδροειδούς δομής components. -Συστατικό .[#toc-component] -=========================== -[api:Nette\ComponentModel\Component] είναι ο κοινός πρόγονος όλων των συστατικών. Περιέχει τη μέθοδο `getName()` που επιστρέφει το όνομα του συστατικού και τη μέθοδο `getParent()` που επιστρέφει τον γονέα του. Και τα δύο μπορούν να οριστούν με τη μέθοδο `setParent()` - η πρώτη παράμετρος είναι ο γονέας και η δεύτερη το όνομα του συστατικού. +Component +========= +Η [api:Nette\ComponentModel\Component] είναι ο κοινός πρόγονος όλων των components. Περιέχει τις μεθόδους `getName()` που επιστρέφει το όνομα του component και τη μέθοδο `getParent()` που επιστρέφει τον γονέα του. Και τα δύο μπορούν να οριστούν με τη μέθοδο `setParent()` - η πρώτη παράμετρος είναι ο γονέας και η δεύτερη το όνομα του component. lookup(string $type): ?Component .[method] ------------------------------------------ -Αναζητά στην ιεραρχία ένα αντικείμενο της επιθυμητής κλάσης ή διεπαφής. Για παράδειγμα, το `$component->lookup(Nette\Application\UI\Presenter::class)` επιστρέφει το presenter αν το συστατικό είναι συνδεδεμένο με αυτό, παρά τα πολλά επίπεδα. +Αναζητά στην ιεραρχία προς τα πάνω ένα αντικείμενο της ζητούμενης κλάσης ή interface. Για παράδειγμα, το `$component->lookup(Nette\Application\UI\Presenter::class)` επιστρέφει τον presenter, εάν το component είναι συνδεδεμένο με αυτόν, ακόμη και μέσω πολλών επιπέδων. lookupPath(string $type): ?string .[method] ------------------------------------------- -Επιστρέφει τη λεγόμενη διαδρομή, η οποία είναι μια συμβολοσειρά που σχηματίζεται από τη συνένωση των ονομάτων όλων των στοιχείων στη διαδρομή μεταξύ του τρέχοντος στοιχείου και του στοιχείου που αναζητείται. Έτσι, για παράδειγμα, το `$component->lookupPath(Nette\Application\UI\Presenter::class)` επιστρέφει το μοναδικό αναγνωριστικό του συστατικού σε σχέση με τον παρουσιαστή. +Επιστρέφει τη λεγόμενη διαδρομή, η οποία είναι μια συμβολοσειρά που δημιουργείται από τη συνένωση των ονομάτων όλων των components στη διαδρομή μεταξύ του τρέχοντος και του αναζητούμενου component. Έτσι, π.χ., το `$component->lookupPath(Nette\Application\UI\Presenter::class)` επιστρέφει ένα μοναδικό αναγνωριστικό του component σε σχέση με τον presenter. -Δοχείο .[#toc-container] -======================== -[api:Nette\ComponentModel\Container] είναι το γονικό συστατικό, δηλαδή το συστατικό που περιέχει τα παιδιά και έτσι σχηματίζει τη δενδρική δομή. Διαθέτει μεθόδους για την εύκολη προσθήκη, ανάκτηση και αφαίρεση στοιχείων. Είναι ο πρόγονος, για παράδειγμα, της φόρμας ή των κλάσεων `Control` και `Presenter`. +Container +========= +Η [api:Nette\ComponentModel\Container] είναι το γονικό component, δηλ. ένα component που περιέχει απογόνους και σχηματίζει έτσι μια δενδροειδή δομή. Διαθέτει μεθόδους για εύκολη προσθήκη, ανάκτηση και αφαίρεση αντικειμένων. Είναι ο πρόγονος, για παράδειγμα, της φόρμας ή των κλάσεων `Control` και `Presenter`. getComponent(string $name): ?Component .[method] ------------------------------------------------ -Επιστρέφει ένα συστατικό. Η απόπειρα κλήσης απροσδιόριστου παιδιού προκαλεί κλήση του εργοστασίου [createComponent($name) |api:Nette\ComponentModel\Container::createComponent()]. Η μέθοδος `createComponent($name)` καλεί τη μέθοδο `createComponent<component name>` στο τρέχον συστατικό και περνά το όνομα του συστατικού ως παράμετρο. Το συστατικό που δημιουργήθηκε περνάει στη συνέχεια στο τρέχον συστατικό ως παιδί του. Ονομάζουμε αυτά τα εργοστάσια συστατικών, μπορούν να υλοποιηθούν σε κλάσεις που κληρονομούνται από το `Container`. +Επιστρέφει το component. Κατά την προσπάθεια ανάκτησης ενός μη ορισμένου απογόνου, καλείται το factory `createComponent($name)`. Η μέθοδος `createComponent($name)` καλεί στο τρέχον component τη μέθοδο `createComponent<όνομα_component>` και της περνά ως παράμετρο το όνομα του component. Το δημιουργημένο component προστίθεται στη συνέχεια στο τρέχον component ως απόγονός του. Αυτές οι μέθοδοι ονομάζονται factories component και μπορούν να υλοποιηθούν από απογόνους της κλάσης `Container`. -Επανάληψη σε παιδιά .[#toc-iterating-over-children] ---------------------------------------------------- +getComponents(): array .[method] +-------------------------------- +Επιστρέφει τους άμεσους απογόνους ως πίνακα. Τα κλειδιά περιέχουν τα ονόματα αυτών των components. Σημείωση: στην έκδοση 3.0.x η μέθοδος επέστρεφε έναν iterator αντί για πίνακα και η πρώτη της παράμετρος καθόριζε αν τα components έπρεπε να διασχιστούν σε βάθος, και η δεύτερη αντιπροσώπευε ένα φίλτρο τύπου. Αυτές οι παράμετροι είναι deprecated. -Η μέθοδος [getComponents($deep = false, $type = null) |api:Nette\ComponentModel\Container::getComponents()] χρησιμοποιείται για την επανάληψη. Η πρώτη παράμετρος καθορίζει αν θα διατρέξει τα συστατικά σε βάθος (ή αναδρομικά). Με την `true`, δεν επαναλαμβάνει μόνο όλα τα παιδιά της, αλλά και όλα τα παιδιά των παιδιών της, κ.λπ. Η δεύτερη παράμετρος εξυπηρετεί ως προαιρετικό φίλτρο ανά κλάση ή διεπαφή. -```php -foreach ($form->getComponents(true, Nette\Forms\IControl::class) as $control) { - if (!$control->getRules()->validate()) { - // ... - } -} -``` +getComponentTree(): array .[method]{data-version:3.1.0} +------------------------------------------------------- +Ανακτά ολόκληρη την ιεραρχία των components, συμπεριλαμβανομένων όλων των ενσωματωμένων θυγατρικών components, ως ευρετηριασμένο πίνακα. Η αναζήτηση γίνεται πρώτα σε βάθος. -Παρακολούθηση των προγόνων .[#toc-monitoring-of-ancestors] -========================================================== +Παρακολούθηση προγόνων +====================== -Το μοντέλο συστατικών της Nette επιτρέπει πολύ δυναμική εργασία στο δέντρο (μπορούμε να αφαιρούμε, να μετακινούμε, να προσθέτουμε συστατικά), οπότε θα ήταν λάθος να βασιστούμε στο γεγονός ότι μετά τη δημιουργία ενός συστατικού, ο γονέας, ο γονέας του γονέα κ.λπ. είναι αμέσως γνωστά (στον κατασκευαστή). Συνήθως ο γονέας δεν είναι καθόλου γνωστός όταν δημιουργείται το συστατικό. +Το μοντέλο component του Nette επιτρέπει πολύ δυναμική εργασία με το δέντρο (μπορούμε να αφαιρούμε, να μετακινούμε, να προσθέτουμε components), επομένως θα ήταν λάθος να βασιζόμαστε στο γεγονός ότι μετά τη δημιουργία ενός component είναι αμέσως γνωστός ο γονέας, ο γονέας του γονέα κ.λπ. (στον κατασκευαστή). Τις περισσότερες φορές, ο γονέας δεν είναι καθόλου γνωστός κατά τη δημιουργία. -Πώς μπορείτε να μάθετε πότε ένα συστατικό έχει προστεθεί στο δέντρο του παρουσιαστή; Η παρακολούθηση της αλλαγής του γονέα δεν είναι αρκετή, επειδή ο γονέας του γονέα θα μπορούσε να έχει προσαρτηθεί στον παρουσιαστή, για παράδειγμα. Η μέθοδος [monitor($type, $attached, $detached) |api:Nette\ComponentModel\Component::monitor()] μπορεί να βοηθήσει. Κάθε στοιχείο μπορεί να παρακολουθεί οποιονδήποτε αριθμό κλάσεων και διεπαφών. Η σύνδεση ή η αποσύνδεση ανακοινώνεται με την κλήση των callbacks `$attached` και `$detached`, αντίστοιχα, και περνώντας το αντικείμενο της παρακολουθούμενης κλάσης. +Πώς να αναγνωρίσετε πότε ένα component συνδέθηκε στο δέντρο του presenter; Η παρακολούθηση της αλλαγής του γονέα δεν αρκεί, γιατί μπορεί να έχει συνδεθεί στον presenter ο γονέας του γονέα, για παράδειγμα. Η μέθοδος [monitor($type, $attached, $detached)|api:Nette\ComponentModel\Component::monitor()] βοηθάει. Κάθε component μπορεί να παρακολουθεί οποιονδήποτε αριθμό κλάσεων και interfaces. Η σύνδεση ή η αποσύνδεση ανακοινώνεται με την κλήση του callback `$attached` ή `$detached` αντίστοιχα, και την παράδοση του αντικειμένου της παρακολουθούμενης κλάσης. -Ένα παράδειγμα: Η κλάση `UploadControl`, που αντιπροσωπεύει το στοιχείο φόρμας για το ανέβασμα αρχείων στη Nette Forms, πρέπει να θέσει το χαρακτηριστικό `enctype` της φόρμας στην τιμή `multipart/form-data`. Αλλά κατά τη στιγμή της δημιουργίας του αντικειμένου δεν χρειάζεται να συνδεθεί με καμία φόρμα. Πότε πρέπει να τροποποιηθεί η φόρμα; Η λύση είναι απλή - δημιουργούμε ένα αίτημα παρακολούθησης στον κατασκευαστή: +Για καλύτερη κατανόηση, ένα παράδειγμα: η κλάση `UploadControl`, που αντιπροσωπεύει ένα στοιχείο φόρμας για την αποστολή αρχείων στο Nette Forms, πρέπει να ορίσει το attribute `enctype` της φόρμας στην τιμή `multipart/form-data`. Κατά τη στιγμή της δημιουργίας του αντικειμένου, όμως, μπορεί να μην είναι συνδεδεμένο με καμία φόρμα. Πότε λοιπόν πρέπει να τροποποιηθεί η φόρμα; Η λύση είναι απλή - στον κατασκευαστή ζητείται η παρακολούθηση: ```php class UploadControl extends Nette\Forms\Controls\BaseControl @@ -68,7 +64,4 @@ class UploadControl extends Nette\Forms\Controls\BaseControl } ``` -και όταν η φόρμα είναι διαθέσιμη, καλείται το callback. (Προηγουμένως, αντί αυτού χρησιμοποιούνταν οι κοινές μέθοδοι `attached` και `detached` ). - - -{{leftbar: nette:@menu-topics}} +και μόλις η φόρμα είναι διαθέσιμη, καλείται το callback. (Παλαιότερα, χρησιμοποιούνταν αντί αυτού η κοινή μέθοδος `attached` ή `detached`). diff --git a/component-model/el/@meta.texy b/component-model/el/@meta.texy new file mode 100644 index 0000000000..a09ce5fe0d --- /dev/null +++ b/component-model/el/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Τεκμηρίωση}} +{{leftbar: nette:@menu-topics}} diff --git a/component-model/en/@home.texy b/component-model/en/@home.texy index 76a116ce1c..ec18f76c32 100644 --- a/component-model/en/@home.texy +++ b/component-model/en/@home.texy @@ -2,17 +2,17 @@ Component Model *************** .[perex] -An important concept in Nette is the component. We insert [visual interactive components |application:components] into pages, forms or all their elements are also components. There are two basic classes from which all these components inherit, are part of the `nette/component-model` package and are responsible for creating the component tree hierarchy. +An important concept in Nette is the component. We insert [visual interactive components |application:components] into pages; forms and all their elements are also components. The two basic classes from which all these components inherit are part of the `nette/component-model` package and are responsible for creating the component tree hierarchy. Component ========= -[api:Nette\ComponentModel\Component] is the common ancestor of all components. It contains the `getName()` method returning the name of the component and the `getParent()` method returning its parent. Both can be set with the `setParent()` method - the first parameter is the parent and the second is the component name. +[api:Nette\ComponentModel\Component] is the common ancestor of all components. It contains the `getName()` method returning the name of the component and the `getParent()` method returning its parent. Both can be set using the `setParent()` method - the first parameter is the parent, and the second is the component name. lookup(string $type): ?Component .[method] ------------------------------------------ -Searches up the hierarchy for an object of the desired class or interface. For example, `$component->lookup(Nette\Application\UI\Presenter::class)` returns presenter if the component is connected to it, despite several levels. +Searches up the hierarchy for an object of the desired class or interface. For example, `$component->lookup(Nette\Application\UI\Presenter::class)` returns the presenter if the component is connected to it, even across several levels. lookupPath(string $type): ?string .[method] @@ -22,36 +22,32 @@ Returns the so-called path, which is a string formed by concatenating the names Container ========= -[api:Nette\ComponentModel\Container] is the parent component, i.e. the component containing the children and thus forming the tree structure. It has methods for easily adding, retrieving and removing components. It is the ancestor of, for example, the form or classes `Control` and `Presenter`. +[api:Nette\ComponentModel\Container] is the parent component, i.e., the component containing children and thus forming the tree structure. It has methods for easily adding, retrieving, and removing objects. It is the ancestor of, for example, the form or classes `Control` and `Presenter`. getComponent(string $name): ?Component .[method] ------------------------------------------------ -Returns a component. Attempt to call undefined child causes invoking of factory [createComponent($name)|api:Nette\ComponentModel\Container::createComponent()]. Method `createComponent($name)` invokes method `createComponent<component name>` in current component and it passes name of the component as a parameter. Created component is then passed to current component as its child. We call theese component factories, they can be implemented in classes inherited from `Container`. +Returns a component. Attempting to retrieve an undefined child invokes the factory method `createComponent($name)`. The `createComponent($name)` method calls the method `createComponent<component name>` in the current component, passing the component name as a parameter. The created component is then added to the current component as its child. We call these methods component factories, and they can be implemented in classes inherited from `Container`. -Iterating over Children ------------------------ +getComponents(): array .[method] +-------------------------------- +Returns direct descendants as an array. The keys contain the names of these components. Note: in version 3.0.x, the method returned an iterator instead of an array, and its first parameter specified whether to iterate through the components in depth, and the second represented a type filter. These parameters are deprecated. -The [getComponents($deep = false, $type = null) |api:Nette\ComponentModel\Container::getComponents()] method is used for iteration. The first parameter specifies whether to traverse the components in depth (or recursively). With `true`, it not only iterates all its children, but also all children of its children, etc. Second parameter servers as an optional filter by class or interface. -```php -foreach ($form->getComponents(true, Nette\Forms\IControl::class) as $control) { - if (!$control->getRules()->validate()) { - // ... - } -} -``` +getComponentTree(): array .[method]{data-version:3.1.0} +------------------------------------------------------- +Gets the entire hierarchy of components, including all nested child components, as an indexed array. The search is depth-first. -Monitoring of Ancestors -======================= +Monitoring Ancestors +==================== -The Nette component model allows for very dynamic tree work (we can remove, move, add components), so it would be a mistake to rely on the fact that after creating a component, the parent, parent's parent, etc. are known immediately (in the constructor). Usually the parent is not known at all when the component is created. +The Nette component model allows for very dynamic work with the tree (we can remove, move, add components), so it would be a mistake to rely on the fact that after creating a component, the parent, parent's parent, etc., are known immediately (in the constructor). Usually, the parent is not known at all when the component is created. How to find out when a component has been added to the presenter tree? Keeping track of the parent change is not enough, because the parent of the parent could have been attached to the presenter, for example. The [monitor($type, $attached, $detached) |api:Nette\ComponentModel\Component::monitor()] method can help. Each component can monitor any number of classes and interfaces. Connection or disconnection is announced by calling the callbacks `$attached` and `$detached`, respectively, and passing the object of the monitored class. -An example: Class `UploadControl`, representing form element for uploading files in Nette Forms, has to set form's attribute `enctype` to value `multipart/form-data`. But in the time of the creation of the object it does not have to be attached to any form. When to modify the form? The solution is simple - we create a request for monitoring in the constructor: +For better understanding, here's an example: The `UploadControl` class, representing the form control for uploading files in Nette Forms, must set the form's `enctype` attribute to `multipart/form-data`. However, at the time the object is created, it might not be attached to any form. So, at what point should the form be modified? The solution is simple - a request for monitoring is made in the constructor: ```php class UploadControl extends Nette\Forms\Controls\BaseControl @@ -68,7 +64,4 @@ class UploadControl extends Nette\Forms\Controls\BaseControl } ``` -and when the form is available, the callback is called. (Previously, the common methods `attached` and `detached` were used instead.) - - -{{leftbar: nette:@menu-topics}} +and as soon as the form becomes available, the callback is invoked. (Previously, the common methods `attached` and `detached` were used for this purpose.) diff --git a/component-model/en/@meta.texy b/component-model/en/@meta.texy new file mode 100644 index 0000000000..91205786e5 --- /dev/null +++ b/component-model/en/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Documentation}} +{{leftbar: nette:@menu-topics}} diff --git a/component-model/es/@home.texy b/component-model/es/@home.texy index 8e315b6f16..515782fc79 100644 --- a/component-model/es/@home.texy +++ b/component-model/es/@home.texy @@ -2,56 +2,52 @@ Modelo de componentes ********************* .[perex] -Un concepto importante en Nette es el de componente. Insertamos [componentes visuales interactivos |application:components] en páginas, formularios o todos sus elementos son también componentes. Hay dos clases básicas de las que heredan todos estos componentes, forman parte del paquete `nette/component-model` y se encargan de crear la jerarquía del árbol de componentes. +Un concepto importante en Nette es el componente. Insertamos [componentes interactivos visuales |application:components] en las páginas, los formularios o todos sus elementos también son componentes. Las dos clases base de las que heredan todos estos componentes forman parte del paquete `nette/component-model` y su propósito es crear una jerarquía de componentes en forma de árbol. Component ========= -[api:Nette\ComponentModel\Component] es el ancestro común de todos los componentes. Contiene el método `getName()` que devuelve el nombre del componente y el método `getParent()` que devuelve su padre. Ambos pueden establecerse con el método `setParent()` - el primer parámetro es el padre y el segundo es el nombre del componente. +[api:Nette\ComponentModel\Component] es el ancestro común de todos los componentes. Contiene los métodos `getName()` que devuelven el nombre del componente y el método `getParent()` que devuelve su padre. Ambos se pueden establecer con el método `setParent()`: el primer parámetro es el padre y el segundo es el nombre del componente. lookup(string $type): ?Component .[method] ------------------------------------------ -Busca en la jerarquía un objeto de la clase o interfaz deseada. Por ejemplo, `$component->lookup(Nette\Application\UI\Presenter::class)` devuelve presentador si el componente está conectado a él, a pesar de varios niveles. +Busca un objeto de la clase o interfaz requerida hacia arriba en la jerarquía. Por ejemplo, `$component->lookup(Nette\Application\UI\Presenter::class)` devuelve el presenter si el componente está adjunto a él, incluso a través de varios niveles. lookupPath(string $type): ?string .[method] ------------------------------------------- -Devuelve la llamada ruta, que es una cadena formada por la concatenación de los nombres de todos los componentes de la ruta entre el componente actual y el componente buscado. Así, por ejemplo, `$component->lookupPath(Nette\Application\UI\Presenter::class)` devuelve el identificador único del componente relativo al presentador. +Devuelve la llamada ruta, que es una cadena creada concatenando los nombres de todos los componentes en la ruta entre el componente actual y el buscado. Así, por ejemplo, `$component->lookupPath(Nette\Application\UI\Presenter::class)` devuelve un identificador único del componente en relación con el presenter. Container ========= -[api:Nette\ComponentModel\Container] es el componente padre, es decir, el componente que contiene a los hijos y, por tanto, forma la estructura de árbol. Dispone de métodos para añadir, recuperar y eliminar componentes fácilmente. Es el ancestro de, por ejemplo, el formulario o las clases `Control` y `Presenter`. +[api:Nette\ComponentModel\Container] es el componente padre, es decir, un componente que contiene hijos y, por lo tanto, forma una estructura de árbol. Dispone de métodos para agregar, obtener y eliminar objetos fácilmente. Es el ancestro, por ejemplo, del formulario o de las clases `Control` y `Presenter`. getComponent(string $name): ?Component .[method] ------------------------------------------------ -Devuelve un componente. El intento de llamar a un hijo no definido provoca la invocación de la fábrica [createComponent($nombre) |api:Nette\ComponentModel\Container::createComponent()]. El método `createComponent($name)` invoca el método `createComponent<component name>` en el componente actual y pasa el nombre del componente como parámetro. El componente creado se pasa al componente actual como hijo. Llamamos a estas fábricas de componentes, pueden ser implementadas en clases heredadas de `Container`. +Devuelve un componente. Al intentar obtener un hijo indefinido, se llama a la fábrica `createComponent($name)`. El método `createComponent($name)` llama al método `createComponent<nombre del componente>` en el componente actual y le pasa el nombre del componente como parámetro. El componente creado se agrega luego al componente actual como su hijo. Llamamos a estos métodos fábricas de componentes y pueden ser implementados por descendientes de la clase `Container`. -Iterando sobre hijos .[#toc-iterating-over-children] ----------------------------------------------------- +getComponents(): array .[method] +-------------------------------- +Devuelve los hijos directos como un array. Las claves contienen los nombres de estos componentes. Nota: en la versión 3.0.x, el método devolvía un iterador en lugar de un array, y su primer parámetro determinaba si los componentes debían recorrerse en profundidad, y el segundo representaba un filtro de tipo. Estos parámetros están obsoletos. -El método [getComponents($deep = false, $type = null) |api:Nette\ComponentModel\Container::getComponents()] se utiliza para la iteración. El primer parámetro especifica si se recorren los componentes en profundidad (o recursivamente). Con `true`, no sólo itera todos sus hijos, sino también todos los hijos de sus hijos, etc. El segundo parámetro sirve como filtro opcional por clase o interfaz. -```php -foreach ($form->getComponents(true, Nette\Forms\IControl::class) as $control) { - if (!$control->getRules()->validate()) { - // ... - } -} -``` +getComponentTree(): array .[method]{data-version:3.1.0} +------------------------------------------------------- +Obtiene toda la jerarquía de componentes, incluidos todos los subcomponentes anidados, como un array indexado. La búsqueda va primero en profundidad. -Supervisión de antepasados .[#toc-monitoring-of-ancestors] -========================================================== +Monitorización de ancestros +=========================== -El modelo de componentes de Nette permite trabajar con árboles muy dinámicos (podemos eliminar, mover, añadir componentes), por lo que sería un error confiar en el hecho de que después de crear un componente, el padre, el padre del padre, etc. se conocen inmediatamente (en el constructor). Normalmente el padre no se conoce en absoluto cuando se crea el componente. +El modelo de componentes de Nette permite un trabajo muy dinámico con el árbol (podemos eliminar, mover, agregar componentes), por lo que sería un error confiar en que después de crear un componente, el padre, el padre del padre, etc., se conozcan inmediatamente (en el constructor). En la mayoría de los casos, el padre no se conoce en absoluto en el momento de la creación. -¿Cómo saber cuándo se ha añadido un componente al árbol del presentador? No basta con hacer un seguimiento del cambio de padre, porque el padre del padre podría haber sido adjuntado al presentador, por ejemplo. El método [monitor($type, $attached, $detached) |api:Nette\ComponentModel\Component::monitor()] puede ayudar. Cada componente puede monitorizar cualquier número de clases e interfaces. La conexión o desconexión se anuncia llamando a las llamadas de retorno `$attached` y `$detached`, respectivamente, y pasando el objeto de la clase monitorizada. +¿Cómo saber cuándo se adjuntó un componente al árbol del presenter? Observar el cambio del padre no es suficiente, porque el padre del padre podría haber sido adjuntado al presenter, por ejemplo. El método [monitor($type, $attached, $detached)|api:Nette\ComponentModel\Component::monitor()] ayuda. Cada componente puede monitorizar cualquier número de clases e interfaces. El adjunto o desadjunto se anuncia llamando al callback `$attached` o `$detached`, respectivamente, y pasando el objeto de la clase monitorizada. -Un ejemplo: La clase `UploadControl`, que representa el elemento de formulario para subir archivos en Nette Forms, tiene que establecer el atributo del formulario `enctype` al valor `multipart/form-data`. Pero en el momento de la creación del objeto no tiene que estar unido a ningún formulario. ¿Cuándo modificar el formulario? La solución es simple - creamos una solicitud de control en el constructor: +Para una mejor comprensión, un ejemplo: la clase `UploadControl`, que representa el elemento de formulario para la carga de archivos en Nette Forms, debe establecer el atributo `enctype` del formulario en el valor `multipart/form-data`. Sin embargo, en el momento de la creación del objeto, es posible que no esté adjunto a ningún formulario. ¿En qué momento, entonces, modificar el formulario? La solución es simple: en el constructor, solicite la monitorización: ```php class UploadControl extends Nette\Forms\Controls\BaseControl @@ -68,7 +64,4 @@ class UploadControl extends Nette\Forms\Controls\BaseControl } ``` -y cuando el formulario está disponible, se llama al callback. (Anteriormente, se utilizaban en su lugar los métodos comunes `attached` y `detached` ). - - -{{leftbar: nette:@menu-topics}} +y tan pronto como el formulario esté disponible, se llama al callback. (Anteriormente, se usaba el método común `attached` o `detached` en su lugar). diff --git a/component-model/es/@meta.texy b/component-model/es/@meta.texy new file mode 100644 index 0000000000..25d506cde9 --- /dev/null +++ b/component-model/es/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Documentación}} +{{leftbar: nette:@menu-topics}} diff --git a/component-model/fr/@home.texy b/component-model/fr/@home.texy index 2b03e9e9cb..706b8f46ce 100644 --- a/component-model/fr/@home.texy +++ b/component-model/fr/@home.texy @@ -2,56 +2,52 @@ Modèle de composant ******************* .[perex] -Un concept important dans Nette est le composant. Nous insérons des [composants interactifs visuels |application:components] dans les pages, les formulaires ou tous leurs éléments sont également des composants. Il existe deux classes de base dont héritent tous ces composants, elles font partie du paquetage `nette/component-model` et sont responsables de la création de la hiérarchie de l'arbre des composants. +Un concept important dans Nette est le composant. Nous insérons des [composants interactifs visuels |application:components] dans les pages ; les formulaires ou tous leurs éléments sont également des composants. Les deux classes de base dont tous ces composants héritent font partie du paquet `nette/component-model` et leur rôle est de créer une hiérarchie arborescente de composants. Component ========= -[api:Nette\ComponentModel\Component] est l'ancêtre commun de tous les composants. Il contient la méthode `getName()` qui renvoie le nom du composant et la méthode `getParent()` qui renvoie son parent. Les deux peuvent être définis avec la méthode `setParent()` - le premier paramètre est le parent et le second est le nom du composant. +[api:Nette\ComponentModel\Component] est l'ancêtre commun de tous les composants. Il contient les méthodes `getName()` retournant le nom du composant et la méthode `getParent()` retournant son parent. Les deux peuvent être définis à l'aide de la méthode `setParent()` - le premier paramètre est le parent et le second est le nom du composant. lookup(string $type): ?Component .[method] ------------------------------------------ -Recherche dans la hiérarchie un objet de la classe ou de l'interface souhaitée. Par exemple, `$component->lookup(Nette\Application\UI\Presenter::class)` renvoie présentateur si le composant est relié à celui-ci, malgré plusieurs niveaux. +Recherche dans la hiérarchie vers le haut un objet de la classe ou de l'interface demandée. Par exemple, `$component->lookup(Nette\Application\UI\Presenter::class)` retourne le presenter, si le composant y est attaché, même à travers plusieurs niveaux. lookupPath(string $type): ?string .[method] ------------------------------------------- -Renvoie ce que l'on appelle le chemin, qui est une chaîne formée par la concaténation des noms de tous les composants sur le chemin entre le composant actuel et le composant recherché. Ainsi, par exemple, `$component->lookupPath(Nette\Application\UI\Presenter::class)` renvoie l'identifiant unique du composant par rapport au présentateur. +Retourne ce qu'on appelle le chemin, qui est une chaîne formée en joignant les noms de tous les composants sur le chemin entre le composant actuel et le composant recherché. Ainsi, par exemple, `$component->lookupPath(Nette\Application\UI\Presenter::class)` retourne l'identifiant unique du composant par rapport au presenter. Container ========= -[api:Nette\ComponentModel\Container] est le composant parent, c'est-à-dire le composant contenant les enfants et formant ainsi l'arborescence. Il dispose de méthodes pour ajouter, récupérer et supprimer facilement des composants. Il est l'ancêtre, par exemple, du formulaire ou des classes `Control` et `Presenter`. +[api:Nette\ComponentModel\Container] est le composant parent, c'est-à-dire un composant contenant des enfants et formant ainsi une structure arborescente. Il dispose de méthodes pour ajouter, obtenir et supprimer facilement des objets. C'est l'ancêtre, par exemple, du formulaire ou des classes `Control` et `Presenter`. getComponent(string $name): ?Component .[method] ------------------------------------------------ -Renvoie un composant. La tentative d'appeler un enfant non défini entraîne l'invocation de la fabrique [createComponent($name) |api:Nette\ComponentModel\Container::createComponent()]. La méthode `createComponent($name)` invoque la méthode `createComponent<component name>` dans le composant courant et passe le nom du composant comme paramètre. Le composant créé est ensuite transmis au composant courant en tant que son enfant. Nous appelons ces usines de composants, elles peuvent être implémentées dans des classes héritées de `Container`. +Retourne un composant. Lors d'une tentative d'obtention d'un enfant non défini, la factory `createComponent($name)` est appelée. La méthode `createComponent($name)` appelle la méthode `createComponent<NomDuComposant>` dans le composant actuel et lui passe le nom du composant en paramètre. Le composant créé est ensuite ajouté au composant actuel en tant qu'enfant. Nous appelons ces méthodes des factories de composants, et elles peuvent être implémentées par les descendants de la classe `Container`. -Itération sur les enfants .[#toc-iterating-over-children] ---------------------------------------------------------- +getComponents(): array .[method] +-------------------------------- +Retourne les enfants directs sous forme de tableau. Les clés contiennent les noms de ces composants. Note : dans la version 3.0.x, la méthode retournait un itérateur au lieu d'un tableau, et son premier paramètre déterminait si les composants devaient être parcourus en profondeur, et le second représentait un filtre de type. Ces paramètres sont obsolètes. -La méthode [getComponents($deep = false, $type = null) |api:Nette\ComponentModel\Container::getComponents()] est utilisée pour l'itération. Le premier paramètre indique s'il faut parcourir les composants en profondeur (ou de manière récursive). Avec `true`, l'itération porte non seulement sur tous ses enfants, mais aussi sur tous les enfants de ses enfants, etc. Le deuxième paramètre sert de filtre optionnel par classe ou interface. -```php -foreach ($form->getComponents(true, Nette\Forms\IControl::class) as $control) { - if (!$control->getRules()->validate()) { - // ... - } -} -``` +getComponentTree(): array .[method]{data-version:3.1.0} +------------------------------------------------------- +Obtient toute la hiérarchie des composants, y compris tous les composants enfants imbriqués, sous forme de tableau indexé. La recherche se fait d'abord en profondeur. -Surveillance des ancêtres .[#toc-monitoring-of-ancestors] -========================================================= +Surveillance des ancêtres +========================= -Le modèle de composant Nette permet un travail très dynamique sur l'arbre (nous pouvons supprimer, déplacer, ajouter des composants), ce serait donc une erreur de se fier au fait qu'après avoir créé un composant, le parent, le parent du parent, etc. sont connus immédiatement (dans le constructeur). En général, le parent n'est pas connu du tout lorsque le composant est créé. +Le modèle de composant de Nette permet un travail très dynamique avec l'arborescence (nous pouvons retirer, déplacer, ajouter des composants), il serait donc erroné de supposer qu'après la création d'un composant, le parent, le parent du parent, etc., sont immédiatement connus (dans le constructeur). La plupart du temps, le parent n'est pas du tout connu lors de la création. -Comment savoir quand un composant a été ajouté à l'arbre du présentateur ? Suivre le changement de parent n'est pas suffisant, car le parent du parent pourrait avoir été attaché au présentateur, par exemple. La méthode [monitor($type, $attached, $detached) |api:Nette\ComponentModel\Component::monitor()] peut vous aider. Chaque composant peut surveiller un nombre quelconque de classes et d'interfaces. La connexion ou la déconnexion est annoncée en appelant les callbacks `$attached` et `$detached`, respectivement, et en passant l'objet de la classe surveillée. +Comment savoir quand un composant a été attaché à l'arborescence du presenter ? Surveiller le changement de parent ne suffit pas, car le parent du parent, par exemple, aurait pu être attaché au presenter. La méthode [monitor($type, $attached, $detached)|api:Nette\ComponentModel\Component::monitor()] aide. Chaque composant peut surveiller n'importe quel nombre de classes et d'interfaces. L'attachement ou le détachement est signalé en appelant le callback `$attached` ou `$detached`, respectivement, et en passant l'objet de la classe surveillée. -Un exemple : La classe `UploadControl`, représentant l'élément de formulaire pour le téléchargement de fichiers dans Nette Forms, doit définir l'attribut du formulaire `enctype` à la valeur `multipart/form-data`. Mais au moment de la création de l'objet, elle ne doit être attachée à aucun formulaire. Quand modifier le formulaire ? La solution est simple - nous créons une demande de surveillance dans le constructeur : +Pour une meilleure compréhension, voici un exemple : la classe `UploadControl`, représentant l'élément de formulaire pour le téléchargement de fichiers dans Nette Forms, doit définir l'attribut `enctype` du formulaire sur `multipart/form-data`. Cependant, au moment de la création de l'objet, il se peut qu'il ne soit attaché à aucun formulaire. À quel moment alors modifier le formulaire ? La solution est simple - dans le constructeur, demandez la surveillance : ```php class UploadControl extends Nette\Forms\Controls\BaseControl @@ -68,7 +64,4 @@ class UploadControl extends Nette\Forms\Controls\BaseControl } ``` -et lorsque le formulaire est disponible, le callback est appelé. (Auparavant, les méthodes communes `attached` et `detached` étaient utilisées à la place). - - -{{leftbar: nette:@menu-topics}} +et dès que le formulaire est disponible, le callback est appelé. (Auparavant, les méthodes communes `attached` ou `detached` étaient utilisées à la place). diff --git a/component-model/fr/@meta.texy b/component-model/fr/@meta.texy new file mode 100644 index 0000000000..95ec8a4ef6 --- /dev/null +++ b/component-model/fr/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Documentation Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/component-model/hu/@home.texy b/component-model/hu/@home.texy index 6614800ff1..5ea05ef999 100644 --- a/component-model/hu/@home.texy +++ b/component-model/hu/@home.texy @@ -2,56 +2,52 @@ Komponens modell **************** .[perex] -A Nette egyik fontos fogalma a komponens. [Vizuális interaktív komponenseket |application:components] illesztünk be az oldalakba, űrlapokba, illetve azok minden eleme is komponens. Két alaposztály van, amelyektől az összes ilyen komponens öröklődik, a `nette/component-model` csomag részét képezik, és a komponensfa hierarchiájának létrehozásáért felelősek. +A Nette fontos fogalma a komponens. Az oldalakra [vizuális interaktív komponenseket |application:components] illesztünk be, komponensek az űrlapok vagy azok összes eleme is. A két alapvető osztály, amelyektől ezek a komponensek öröklődnek, a `nette/component-model` csomag részét képezik, és feladatuk a komponensek fa hierarchiájának létrehozása. Component ========= -[api:Nette\ComponentModel\Component] az összes komponens közös őse. Tartalmazza a `getName()` metódust, amely a komponens nevét adja vissza, és a `getParent()` metódust, amely a szülőjét adja vissza. Mindkettő a `setParent()` metódussal állítható be - az első paraméter a szülő, a második pedig a komponens neve. +Az [api:Nette\ComponentModel\Component] az összes komponens közös őse. Tartalmazza a `getName()` metódust, amely visszaadja a komponens nevét, és a `getParent()` metódust, amely visszaadja a szülőjét. Mindkettőt a `setParent()` metódussal lehet beállítani - az első paraméter a szülő, a második a komponens neve. lookup(string $type): ?Component .[method] ------------------------------------------ -A hierarchiában felfelé keres egy objektumot a kívánt osztályból vagy interfészből. Például a `$component->lookup(Nette\Application\UI\Presenter::class)` visszaadja a presenter-t, ha a komponens több szint ellenére is kapcsolódik hozzá. +Felkeresi a hierarchiában felfelé a kívánt osztály vagy interfész objektumát. Például a `$component->lookup(Nette\Application\UI\Presenter::class)` visszaadja a presentert, ha a komponens hozzá van csatolva, akár több szinten keresztül is. lookupPath(string $type): ?string .[method] ------------------------------------------- -Visszaadja az úgynevezett útvonalat, amely az aktuális komponens és a keresett komponens közötti útvonalon lévő összes komponens nevének összekapcsolásával képzett karakterlánc. Így például a `$component->lookupPath(Nette\Application\UI\Presenter::class)` a komponens egyedi azonosítóját adja vissza a bemutatóhoz képest. +Visszaadja az úgynevezett utat, amely egy string, ami az aktuális és a keresett komponens közötti útvonalon lévő összes komponens nevének összekapcsolásával jön létre. Tehát pl. a `$component->lookupPath(Nette\Application\UI\Presenter::class)` visszaadja a komponens egyedi azonosítóját a presenterhez képest. Container ========= -[api:Nette\ComponentModel\Container] a szülő komponens, azaz az a komponens, amely a gyermekeket tartalmazza, és így a fa szerkezetét alkotja. Módszerekkel rendelkezik a komponensek egyszerű hozzáadásához, visszakereséséhez és eltávolításához. Őse például az űrlapnak vagy a `Control` és a `Presenter` osztályoknak. +Az [api:Nette\ComponentModel\Container] a szülő komponens, azaz a leszármazottakat tartalmazó komponens, amely fa struktúrát alkot. Metódusokkal rendelkezik az objektumok egyszerű hozzáadásához, lekéréséhez és eltávolításához. Például az űrlap vagy a `Control` és `Presenter` osztályok őse. getComponent(string $name): ?Component .[method] ------------------------------------------------ -Visszaad egy komponenst. A nem definiált gyermek hívásának kísérlete a factory [createComponent($name) |api:Nette\ComponentModel\Container::createComponent()] meghívását okozza. A `createComponent($name)` metódus meghívja a metódust `createComponent<component name>` az aktuális komponensben, és paraméterként átadja a komponens nevét. A létrehozott komponens ezután átadásra kerül az aktuális komponensnek, mint annak gyermeke. Ezeket a komponens gyáraknak nevezzük, a `Container`-tól örökölt osztályokban implementálhatók. +Visszaadja a komponenst. Egy nem definiált leszármazott lekérésekor a `createComponent($name)` factory hívódik meg. A `createComponent($name)` metódus meghívja az aktuális komponensben a `createComponent<komponens neve>` metódust, és paraméterként átadja neki a komponens nevét. A létrehozott komponens ezután hozzáadódik az aktuális komponenshez annak leszármazottjaként. Ezeket a metódusokat komponens factory-knak nevezzük, és a `Container` osztály leszármazottai implementálhatják őket. -Iterálás a gyermekeken .[#toc-iterating-over-children] ------------------------------------------------------- +getComponents(): array .[method] +-------------------------------- +Visszaadja a közvetlen leszármazottakat tömbként. A kulcsok ezeknek a komponenseknek a neveit tartalmazzák. Megjegyzés: a 3.0.x verzióban a metódus tömb helyett iterátort adott vissza, és az első paramétere határozta meg, hogy a komponenseket mélységében kell-e bejárni, a második pedig egy típus szűrőt jelentett. Ezek a paraméterek elavultak. -Az iterációhoz a [getComponents($deep = false, $type = null) |api:Nette\ComponentModel\Container::getComponents()] metódust használjuk. Az első paraméter megadja, hogy a komponensek mélységben (vagy rekurzívan) legyenek-e végigjárva. A `true` esetén nem csak az összes gyermekét iterálja, hanem a gyermekeinek összes gyermekét is, stb. A második paraméter kiszolgál, mint opcionális szűrő osztály vagy interfész szerint. -```php -foreach ($form->getComponents(true, Nette\Forms\IControl::class) as $control) { - if (!$control->getRules()->validate()) { - // ... - } -} -``` +getComponentTree(): array .[method]{data-version:3.1.0} +------------------------------------------------------- +Lekéri a teljes komponens hierarchiát, beleértve az összes beágyazott alárendelt komponenst is, indexelt tömbként. A keresés először mélységében történik. -Az ősök figyelése .[#toc-monitoring-of-ancestors] -================================================= +Ősök monitorozása +================= -A Nette komponensmodell nagyon dinamikus famunkát tesz lehetővé (komponenseket távolíthatunk el, mozgathatunk, adhatunk hozzá), ezért hiba lenne arra hagyatkozni, hogy egy komponens létrehozása után azonnal (a konstruktorban) ismert a szülő, a szülő szülője, stb. Általában a szülő egyáltalán nem ismert a komponens létrehozásakor. +A Nette komponens modellje nagyon dinamikus munkát tesz lehetővé a fával (komponenseket kivehetünk, áthelyezhetünk, hozzáadhatunk), ezért hiba lenne arra támaszkodni, hogy a komponens létrehozása után azonnal (a konstruktorban) ismert a szülő, a szülő szülője stb. Legtöbbször ugyanis a szülő a létrehozáskor egyáltalán nem ismert. -Hogyan lehet megtudni, hogy egy komponens mikor került hozzá a prezenterfához? A szülő változásának nyomon követése nem elegendő, mert a szülő szülője például a prezenterhez csatolódhatott. A [monitor($type, $attached, $detached) |api:Nette\ComponentModel\Component::monitor()] metódus segíthet. Minden komponens tetszőleges számú osztályt és interfészt monitorozhat. A csatlakozást vagy szétkapcsolódást a `$attached` illetve a `$detached` visszahívások meghívásával és a megfigyelt osztály objektumának átadásával jelentjük be. +Hogyan lehet tudni, mikor csatlakozott a komponens a presenter fájához? A szülő változásának figyelése nem elegendő, mert a presenterhez például a szülő szülője is csatlakozhatott. Segít a [monitor($type, $attached, $detached)|api:Nette\ComponentModel\Component::monitor()] metódus. Minden komponens tetszőleges számú osztályt és interfészt monitorozhat. A csatlakozást vagy leválasztást a `$attached`, illetve `$detached` callback meghívása jelzi, átadva a figyelt osztály objektumát. -Egy példa: A `UploadControl` osztály, amely a Nette Forms-ben a fájlok feltöltésére szolgáló űrlapelemet képviseli, az űrlap `enctype` attribútumát a `multipart/form-data` értékre kell állítani. Az objektum létrehozásakor azonban nem kell semmilyen űrlaphoz csatolni. Mikor kell módosítani az űrlapot? A megoldás egyszerű - a konstruktorban létrehozunk egy megfigyelési kérelmet: +A jobb megértés érdekében egy példa: az `UploadControl` osztály, amely a Nette Forms fájlfeltöltési űrlap elemét képviseli, be kell állítania az űrlap `enctype` attribútumát `multipart/form-data` értékre. Az objektum létrehozásakor azonban nem feltétlenül kell csatlakoznia semmilyen űrlaphoz. Melyik pillanatban kell tehát módosítani az űrlapot? A megoldás egyszerű - a konstruktorban kérjük a monitorozást: ```php class UploadControl extends Nette\Forms\Controls\BaseControl @@ -68,7 +64,4 @@ class UploadControl extends Nette\Forms\Controls\BaseControl } ``` -és amikor az űrlap elérhetővé válik, a visszahívást hívjuk meg. (Korábban a `attached` és a `detached` közös metódusokat használtuk helyette.) - - -{{leftbar: nette:@menu-topics}} +és amint az űrlap elérhetővé válik, a callback meghívódik. (Korábban helyette a közös `attached`, illetve `detached` metódust használták). diff --git a/component-model/hu/@meta.texy b/component-model/hu/@meta.texy new file mode 100644 index 0000000000..c00a2158aa --- /dev/null +++ b/component-model/hu/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette dokumentáció}} +{{leftbar: nette:@menu-topics}} diff --git a/component-model/it/@home.texy b/component-model/it/@home.texy index aec7bd942c..77d7b09509 100644 --- a/component-model/it/@home.texy +++ b/component-model/it/@home.texy @@ -1,57 +1,53 @@ -Modello di componente -********************* +Modello a componenti +******************** .[perex] -Un concetto importante in Nette è quello di componente. Inseriamo [componenti interattivi visivi |application:components] nelle pagine, i moduli o tutti i loro elementi sono anch'essi componenti. Esistono due classi di base da cui ereditano tutti i componenti, che fanno parte del pacchetto `nette/component-model` e sono responsabili della creazione della gerarchia ad albero dei componenti. +Un concetto importante in Nette è il componente. Inseriamo nelle pagine [componenti visivi interattivi |application:components], i form sono componenti, così come tutti i loro elementi. Le due classi base da cui tutti questi componenti ereditano fanno parte del pacchetto `nette/component-model` e hanno il compito di creare una gerarchia ad albero di componenti. Component ========= -[api:Nette\ComponentModel\Component] è l'antenato comune di tutti i componenti. Contiene il metodo `getName()` che restituisce il nome del componente e il metodo `getParent()` che restituisce il suo genitore. Entrambi possono essere impostati con il metodo `setParent()`: il primo parametro è il genitore e il secondo è il nome del componente. +[api:Nette\ComponentModel\Component] è l'antenato comune di tutti i componenti. Contiene i metodi `getName()` che restituisce il nome del componente e il metodo `getParent()` che restituisce il suo genitore. Entrambi possono essere impostati con il metodo `setParent()` - il primo parametro è il genitore e il secondo il nome del componente. lookup(string $type): ?Component .[method] ------------------------------------------ -Cerca nella gerarchia un oggetto della classe o dell'interfaccia desiderata. Ad esempio, `$component->lookup(Nette\Application\UI\Presenter::class)` restituisce presenter se il componente è collegato ad esso, nonostante i diversi livelli. +Cerca nella gerarchia verso l'alto un oggetto della classe o interfaccia richiesta. Ad esempio, `$component->lookup(Nette\Application\UI\Presenter::class)` restituisce il presenter, se il componente è collegato ad esso, anche attraverso diversi livelli. lookupPath(string $type): ?string .[method] ------------------------------------------- -Restituisce il cosiddetto percorso, che è una stringa formata dalla concatenazione dei nomi di tutti i componenti del percorso tra il componente corrente e il componente cercato. Quindi, ad esempio, `$component->lookupPath(Nette\Application\UI\Presenter::class)` restituisce l'identificatore univoco del componente relativo al presentatore. +Restituisce il cosiddetto percorso, che è una stringa creata concatenando i nomi di tutti i componenti nel percorso tra il componente corrente e quello cercato. Quindi, ad esempio, `$component->lookupPath(Nette\Application\UI\Presenter::class)` restituisce l'identificatore univoco del componente rispetto al presenter. Container ========= -[api:Nette\ComponentModel\Container] è il componente genitore, cioè il componente che contiene i figli e che quindi forma la struttura ad albero. Dispone di metodi per aggiungere, recuperare e rimuovere facilmente i componenti. È l'antenato, ad esempio, del modulo o delle classi `Control` e `Presenter`. +[api:Nette\ComponentModel\Container] è il componente genitore, cioè un componente che contiene discendenti e forma così una struttura ad albero. Dispone di metodi per aggiungere, ottenere e rimuovere facilmente oggetti. È l'antenato, ad esempio, del form o delle classi `Control` e `Presenter`. getComponent(string $name): ?Component .[method] ------------------------------------------------ -Restituisce un componente. Il tentativo di chiamare un figlio non definito causa l'invocazione del factory [createComponent($nome) |api:Nette\ComponentModel\Container::createComponent()]. Il metodo `createComponent($name)` invoca il metodo `createComponent<component name>` nel componente corrente e passa il nome del componente come parametro. Il componente creato viene quindi passato al componente corrente come suo figlio. Questi factory di componenti possono essere implementati in classi ereditate da `Container`. +Restituisce un componente. Quando si tenta di ottenere un discendente non definito, viene chiamata la factory `createComponent($name)`. Il metodo `createComponent($name)` chiama nel componente corrente il metodo `createComponent<nomeComponente>` e gli passa come parametro il nome del componente. Il componente creato viene quindi aggiunto al componente corrente come suo discendente. Questi metodi sono chiamati factory di componenti e possono essere implementati dai discendenti della classe `Container`. -Iterazione sui figli .[#toc-iterating-over-children] ----------------------------------------------------- +getComponents(): array .[method] +-------------------------------- +Restituisce i discendenti diretti come array. Le chiavi contengono i nomi di questi componenti. Nota: nella versione 3.0.x il metodo restituiva un iteratore invece di un array e il suo primo parametro specificava se i componenti dovevano essere attraversati in profondità, e il secondo rappresentava un filtro di tipo. Questi parametri sono deprecati. -Il metodo [getComponents($deep = false, $type = null) |api:Nette\ComponentModel\Container::getComponents()] è usato per l'iterazione. Il primo parametro specifica se si deve attraversare i componenti in profondità (o in modo ricorsivo). Con `true`, non solo vengono iterati tutti i suoi figli, ma anche tutti i figli dei suoi figli, ecc. Il secondo parametro serve come filtro opzionale per classe o interfaccia. -```php -foreach ($form->getComponents(true, Nette\Forms\IControl::class) as $control) { - if (!$control->getRules()->validate()) { - // ... - } -} -``` +getComponentTree(): array .[method]{data-version:3.1.0} +------------------------------------------------------- +Ottiene l'intera gerarchia dei componenti, inclusi tutti i componenti figli annidati, come un array indicizzato. La ricerca va prima in profondità. -Monitoraggio degli antenati .[#toc-monitoring-of-ancestors] -=========================================================== +Monitoraggio degli antenati +=========================== -Il modello dei componenti di Nette consente di lavorare in modo molto dinamico sull'albero (si possono rimuovere, spostare, aggiungere componenti), quindi sarebbe un errore fare affidamento sul fatto che dopo la creazione di un componente, il genitore, il genitore del genitore, ecc. siano noti immediatamente (nel costruttore). Di solito il genitore non è noto al momento della creazione del componente. +Il modello a componenti di Nette consente un lavoro molto dinamico con l'albero (possiamo rimuovere, spostare, aggiungere componenti), quindi sarebbe un errore fare affidamento sul fatto che dopo la creazione del componente sia immediatamente noto (nel costruttore) il genitore, il genitore del genitore, ecc. Di solito, infatti, il genitore non è affatto noto al momento della creazione. -Come scoprire quando un componente è stato aggiunto all'albero del presentatore? Tenere traccia del cambiamento del genitore non è sufficiente, perché il genitore del genitore potrebbe essere stato aggiunto al presentatore, ad esempio. Il metodo [monitor($type, $attached, $detached) |api:Nette\ComponentModel\Component::monitor()] può aiutare. Ogni componente può monitorare un numero qualsiasi di classi e interfacce. La connessione o la disconnessione vengono annunciate chiamando le callback `$attached` e `$detached`, rispettivamente, e passando l'oggetto della classe monitorata. +Come sapere quando un componente è stato collegato all'albero del presenter? Monitorare il cambiamento del genitore non è sufficiente, perché al presenter potrebbe essere stato collegato, ad esempio, il genitore del genitore. Il metodo [monitor($type, $attached, $detached)|api:Nette\ComponentModel\Component::monitor()] aiuta. Ogni componente può monitorare un numero qualsiasi di classi e interfacce. L'allegamento o lo scollegamento viene segnalato chiamando il callback `$attached` rispettivamente `$detached`, e passando l'oggetto della classe monitorata. -Un esempio: La classe `UploadControl`, che rappresenta l'elemento modulo per il caricamento dei file in Nette Forms, deve impostare l'attributo `enctype` del modulo sul valore `multipart/form-data`. Ma al momento della creazione dell'oggetto non deve essere collegata ad alcun modulo. Quando modificare il modulo? La soluzione è semplice: si crea una richiesta di monitoraggio nel costruttore: +Per una migliore comprensione, un esempio: la classe `UploadControl`, che rappresenta l'elemento del form per l'upload di file in Nette Forms, deve impostare l'attributo `enctype` del form sul valore `multipart/form-data`. Al momento della creazione dell'oggetto, tuttavia, potrebbe non essere collegata a nessun form. In quale momento, quindi, modificare il form? La soluzione è semplice: nel costruttore si richiede il monitoraggio: ```php class UploadControl extends Nette\Forms\Controls\BaseControl @@ -68,7 +64,4 @@ class UploadControl extends Nette\Forms\Controls\BaseControl } ``` -e quando il modulo è disponibile, viene richiamato il callback. (In precedenza, si usavano invece i metodi comuni `attached` e `detached` ). - - -{{leftbar: nette:@menu-topics}} +e non appena il form è disponibile, viene chiamato il callback. (In passato, al suo posto venivano usati i metodi comuni `attached` rispettivamente `detached`). diff --git a/component-model/it/@meta.texy b/component-model/it/@meta.texy new file mode 100644 index 0000000000..9d19e7312c --- /dev/null +++ b/component-model/it/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Documentazione Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/component-model/ja/@home.texy b/component-model/ja/@home.texy new file mode 100644 index 0000000000..6f62c85626 --- /dev/null +++ b/component-model/ja/@home.texy @@ -0,0 +1,67 @@ +コンポーネントモデル +********** + +.[perex] +Netteにおける重要な概念はコンポーネントです。ページには[視覚的なインタラクティブコンポーネント |application:components]を挿入し、フォームやすべてのフォーム要素もコンポーネントです。これらすべてのコンポーネントが継承する基本的な2つのクラスは、`nette/component-model` パッケージの一部であり、コンポーネントの木構造階層を作成する役割を担っています。 + + +Component +========= +[api:Nette\ComponentModel\Component]は、すべてのコンポーネントの共通の祖先です。コンポーネントの名前を返す `getName()` メソッドと、その親を返す `getParent()` メソッドを含みます。両方は `setParent()` メソッドで設定できます - 最初のパラメータは親で、2番目のパラメータはコンポーネントの名前です。 + + +lookup(string $type): ?Component .[method] +------------------------------------------ +階層を上方向に検索し、要求されたクラスまたはインターフェースのオブジェクトを見つけます。たとえば、`$component->lookup(Nette\Application\UI\Presenter::class)` は、コンポーネントが(数レベルを介してでも)Presenterに接続されている場合、Presenterを返します。 + + +lookupPath(string $type): ?string .[method] +------------------------------------------- +いわゆるパスを返します。これは、現在のコンポーネントと検索対象のコンポーネントの間のパスにあるすべてのコンポーネントの名前を結合して作成された文字列です。したがって、たとえば `$component->lookupPath(Nette\Application\UI\Presenter::class)` は、Presenterに対するコンポーネントの一意の識別子を返します。 + + +Container +========= +[api:Nette\ComponentModel\Container]は親コンポーネント、つまり子を含むコンポーネントであり、木構造を形成します。オブジェクトを簡単に追加、取得、削除するためのメソッドを備えています。これは、たとえばフォームや `Control` および `Presenter` クラスの祖先です。 + + +getComponent(string $name): ?Component .[method] +------------------------------------------------ +コンポーネントを返します。未定義の子を取得しようとすると、ファクトリ `createComponent($name)` が呼び出されます。`createComponent($name)` メソッドは、現在のコンポーネントで `createComponent<コンポーネント名>` メソッドを呼び出し、パラメータとしてコンポーネント名を渡します。作成されたコンポーネントは、その後、現在の子として現在のコンポーネントに追加されます。これらのメソッドをコンポーネントファクトリと呼び、`Container` クラスの子孫で実装できます。 + + +getComponents(): array .[method] +-------------------------------- +直接の子を配列として返します。キーにはこれらのコンポーネントの名前が含まれます。注:バージョン3.0.xでは、このメソッドは配列の代わりにイテレータを返し、最初のパラメータはコンポーネントを深く走査するかどうかを指定し、2番目のパラメータは型フィルタを表していました。これらのパラメータは非推奨です。 + + +getComponentTree(): array .[method]{data-version:3.1.0} +------------------------------------------------------- +すべてのネストされた子コンポーネントを含む完全なコンポーネント階層をインデックス付き配列として取得します。検索は最初に深さ優先で行われます。 + + +祖先の監視 +===== + +Netteコンポーネントモデルは、ツリーとの非常に動的な作業を可能にします(コンポーネントを削除、移動、追加できます)。したがって、コンポーネントが作成された直後(コンストラクタ内)に親、親の親などがわかっていると頼るのは間違いです。ほとんどの場合、作成時に親はまったくわかりません。 + +コンポーネントがPresenterツリーに接続されたことをいつ知るにはどうすればよいですか?親の変更を監視するだけでは不十分です。たとえば、親の親がPresenterに接続されている可能性があるためです。メソッド[monitor($type, $attached, $detached)|api:Nette\ComponentModel\Component::monitor()]が役立ちます。各コンポーネントは、任意の数のクラスとインターフェースを監視できます。接続または切断は、コールバック `$attached` または `$detached` を呼び出し、監視対象クラスのオブジェクトを渡すことによって通知されます。 + +よりよく理解するための例:Nette Formsのファイルアップロード用のフォーム要素を表す `UploadControl` クラスは、フォームの `enctype` 属性を `multipart/form-data` に設定する必要があります。しかし、オブジェクトが作成された時点では、どのフォームにも接続されていない可能性があります。では、どの時点でフォームを変更すればよいでしょうか?解決策は簡単です - コンストラクタで監視を要求します: + +```php +class UploadControl extends Nette\Forms\Controls\BaseControl +{ + public function __construct($label) + { + $this->monitor(Nette\Forms\Form::class, function ($form): void { + $form->setHtmlAttribute('enctype', 'multipart/form-data'); + }); + // ... + } + + // ... +} +``` + +そして、フォームが利用可能になるとすぐに、コールバックが呼び出されます。(以前は、代わりに共通のメソッド `attached` または `detached` が使用されていました)。 diff --git a/component-model/ja/@meta.texy b/component-model/ja/@meta.texy new file mode 100644 index 0000000000..7d67dcb7b8 --- /dev/null +++ b/component-model/ja/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette ドキュメンテーション}} +{{leftbar: nette:@menu-topics}} diff --git a/component-model/meta.json b/component-model/meta.json index b4b89b8c38..245d650e59 100644 --- a/component-model/meta.json +++ b/component-model/meta.json @@ -1,5 +1,5 @@ { - "version": "4.0", + "version": "3.x", "repo": "nette/component-model", "composer": "nette/component-model" } diff --git a/component-model/pl/@home.texy b/component-model/pl/@home.texy index 54dab24b99..b20f94a1f3 100644 --- a/component-model/pl/@home.texy +++ b/component-model/pl/@home.texy @@ -1,57 +1,53 @@ -Model komponentu -**************** +Model komponentów +***************** .[perex] -Ważnym pojęciem w Nette jest komponent. Do stron wstawiamy [wizualne interaktywne kompon |application:components] enty, formularze lub wszystkie ich elementy to również komponenty. Istnieją dwie podstawowe klasy, z których dziedziczą wszystkie te komponenty, wchodzą w skład pakietu `nette/component-model` i odpowiadają za tworzenie hierarchii drzewa komponentów. +Ważnym pojęciem w Nette jest komponent. Do stron wstawiamy [wizualne komponenty interaktywne |application:components], komponentami są również formularze lub wszystkie ich elementy. Podstawowe dwie klasy, od których dziedziczą wszystkie te komponenty, są częścią pakietu `nette/component-model` i mają za zadanie tworzyć hierarchię drzewa komponentów. Component ========= -[api:Nette\ComponentModel\Component] jest wspólnym przodkiem wszystkich komponentów. Zawiera ona metodę `getName()` zwracającą nazwę komponentu oraz metodę `getParent()` zwracającą jego rodzica. Oba można ustawić za pomocą metody `setParent()` - pierwszy parametr to rodzic, a drugi to nazwa komponentu. +[api:Nette\ComponentModel\Component] jest wspólnym przodkiem wszystkich komponentów. Zawiera metody `getName()` zwracającą nazwę komponentu i metodę `getParent()` zwracającą jego rodzica. Oboje można ustawić metodą `setParent()` - pierwszy parametr to rodzic, a drugi nazwa komponentu. lookup(string $type): ?Component .[method] ------------------------------------------ -Przeszukuje hierarchię w poszukiwaniu obiektu żądanej klasy lub interfejsu. Na przykład `$component->lookup(Nette\Application\UI\Presenter::class)` zwraca prezentera, jeśli komponent jest z nim połączony, pomimo kilku poziomów. +Wyszukuje w hierarchii w górę obiekt żądanej klasy lub interfejsu. Na przykład `$component->lookup(Nette\Application\UI\Presenter::class)` zwraca presenter, jeśli komponent jest do niego dołączony, nawet przez kilka poziomów. lookupPath(string $type): ?string .[method] ------------------------------------------- -Zwraca tzw. ścieżkę, czyli ciąg utworzony przez konkatenację nazw wszystkich komponentów na ścieżce pomiędzy bieżącym komponentem a poszukiwanym komponentem. Tak więc, na przykład, `$component->lookupPath(Nette\Application\UI\Presenter::class)` zwraca unikalny identyfikator komponentu względem prezentera. +Zwraca tzw. ścieżkę, czyli ciąg znaków powstały przez połączenie nazw wszystkich komponentów na ścieżce między bieżącym a szukanym komponentem. Zatem np. `$component->lookupPath(Nette\Application\UI\Presenter::class)` zwraca unikalny identyfikator komponentu względem presentera. Container ========= -[api:Nette\ComponentModel\Container] jest składnikiem nadrzędnym, czyli składnikiem zawierającym dzieci, a więc tworzącym strukturę drzewa. Posiada metody umożliwiające łatwe dodawanie, pobieranie i usuwanie obiektów. Jest przodkiem np. formy lub klas `Control` i `Presenter`. +[api:Nette\ComponentModel\Container] jest komponentem nadrzędnym, tj. komponentem zawierającym potomków i tworzącym w ten sposób strukturę drzewa. Dysponuje metodami do łatwego dodawania, pobierania i usuwania obiektów. Jest przodkiem na przykład formularza czy klas `Control` i `Presenter`. getComponent(string $name): ?Component .[method] ------------------------------------------------ -Zwraca element. Podczas próby uzyskania niezdefiniowanego dziecka wywoływana jest fabryka `createComponent($name)`. Metoda `createComponent($name)` wywołuje metodę w bieżącym komponencie `createComponent<název komponenty>` i przekazuje nazwę komponentu jako parametr. Utworzony komponent jest następnie dodawany do bieżącego komponentu jako jego dziecko. Metody te nazywane są fabrykami komponentów i mogą być implementowane przez potomków klasy `Container`. +Zwraca komponent. Przy próbie uzyskania niezdefiniowanego potomka jest wywoływana fabryka `createComponent($name)`. Metoda `createComponent($name)` wywołuje w bieżącym komponencie metodę `createComponent<nazwa komponentu>` i jako parametr przekazuje jej nazwę komponentu. Utworzony komponent jest następnie dodawany do bieżącego komponentu jako jego potomek. Te metody nazywamy fabrykami komponentów i mogą je implementować potomkowie klasy `Container`. -Iterowanie po potomkach .[#toc-iterovani-nad-potomky] ------------------------------------------------------ +getComponents(): array .[method] +-------------------------------- +Zwraca bezpośrednich potomków jako tablicę. Klucze zawierają nazwy tych komponentów. Uwaga: w wersji 3.0.x metoda zamiast tablicy zwracała iterator, a jej pierwszy parametr określał, czy komponenty mają być przeglądane wgłąb, a drugi reprezentował filtr typów. Te parametry są przestarzałe. -Do iteracji służy metoda [getComponents($deep = false, $type = null) |api:Nette\ComponentModel\Container::getComponents()]. Pierwszy parametr określa, czy przeglądać elementy w głębi (lub rekurencyjnie). Tak więc, z wartością `true`, nie tylko przemierza wszystkie komponenty, których jest rodzicem, ale także dzieci swoich dzieci, itd. Drugi parametr służy jako opcjonalny filtr według klasy lub interfejsu. -```php -foreach ($form->getComponents(true, Nette\Forms\IControl::class) as $control) { - if (!$control->getRules()->validate()) { - // ... - } -} -``` +getComponentTree(): array .[method]{data-version:3.1.0} +------------------------------------------------------- +Pobiera całą hierarchię komponentów, w tym wszystkie zagnieżdżone komponenty podrzędne, jako tablicę indeksowaną. Przeszukiwanie odbywa się najpierw wgłąb. -Monitorowanie przodków .[#toc-monitoring-of-ancestors] -====================================================== +Monitorowanie przodków +====================== -Model komponentów Nette pozwala na bardzo dynamiczną pracę z drzewem (możemy usuwać, przenosić, dodawać komponenty), więc błędem byłoby poleganie na tym, że po utworzeniu komponentu od razu (w konstruktorze) znany jest jego rodzic, rodzic rodzica itd. Zazwyczaj rodzic nie jest w ogóle znany w momencie tworzenia komponentu. +Model komponentów Nette umożliwia bardzo dynamiczną pracę z drzewem (komponenty możemy usuwać, przenosić, dodawać), dlatego błędem byłoby polegać na tym, że po utworzeniu komponentu od razu (w konstruktorze) znany jest rodzic, rodzic rodzica itd. Zazwyczaj bowiem rodzic przy tworzeniu w ogóle nie jest znany. -Jak dowiedzieć się, kiedy komponent został dodany do drzewa prezentera? Śledzenie zmiany rodzica nie wystarcza, bo rodzic rodzica mógł zostać dołączony np. do prezentera. Pomóc może metoda [monitor($type, $attached, $detached) |api:Nette\ComponentModel\Component::monitor()]. Każdy komponent może monitorować dowolną liczbę klas i interfejsów. Połączenie lub rozłączenie ogłaszane jest poprzez wywołanie callbacków odpowiednio `$attached` i `$detached`, i przekazanie obiektu monitorowanej klasy. +Jak rozpoznać, kiedy komponent został dołączony do drzewa presentera? Śledzenie zmiany rodzica nie wystarczy, ponieważ do presentera mógł zostać dołączony na przykład rodzic rodzica. Pomocna jest metoda [monitor($type, $attached, $detached)|api:Nette\ComponentModel\Component::monitor()]. Każdy komponent może monitorować dowolną liczbę klas i interfejsów. Dołączenie lub odłączenie jest sygnalizowane wywołaniem callbacku `$attached` lub `$detached`, i przekazaniem obiektu śledzonej klasy. -Aby lepiej zrozumieć przykład, klasa `UploadControl`, reprezentująca element formularza do przesyłania plików w Nette Forms, musi ustawić atrybut formularza `enctype` na `multipart/form-data`. Nie musi jednak być dołączona do żadnego formularza w momencie tworzenia obiektu. W jakim więc momencie modyfikować formularz? Rozwiązanie jest proste - konstruktor prosi o monitorowanie: +Dla lepszego zrozumienia przykład: klasa `UploadControl`, reprezentująca element formularza do przesyłania plików w Nette Forms, musi ustawić atrybut `enctype` formularza na wartość `multipart/form-data`. W momencie tworzenia obiektu nie musi być jednak dołączona do żadnego formularza. W którym momencie więc zmodyfikować formularz? Rozwiązanie jest proste - w konstruktorze żąda się monitorowania: ```php class UploadControl extends Nette\Forms\Controls\BaseControl @@ -68,7 +64,4 @@ class UploadControl extends Nette\Forms\Controls\BaseControl } ``` -i gdy formularz jest dostępny, wywoływane jest wywołanie zwrotne. (Wcześniej zamiast tego używano wspólnych metod `attached` i `detached`). - - -{{leftbar: nette:@menu-topics}} +a gdy formularz jest dostępny, wywoływany jest callback. (Wcześniej zamiast niego używano wspólnej metody `attached` lub `detached`). diff --git a/component-model/pl/@meta.texy b/component-model/pl/@meta.texy new file mode 100644 index 0000000000..08f2227fb5 --- /dev/null +++ b/component-model/pl/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Dokumentacja Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/component-model/pt/@home.texy b/component-model/pt/@home.texy index 5782d43f3c..2e57ace611 100644 --- a/component-model/pt/@home.texy +++ b/component-model/pt/@home.texy @@ -1,57 +1,53 @@ -Modelo do componente +Modelo de Componente ******************** .[perex] -Um conceito importante em Nette é o componente. Inserimos [componentes visuais interativos |application:components] em páginas, formulários ou todos os seus elementos são também componentes. Há duas classes básicas das quais todos esses componentes herdam, fazem parte do pacote `nette/component-model` e são responsáveis pela criação da hierarquia da árvore de componentes. +Um conceito importante no Nette é o componente. Inserimos [componentes interativos visuais |application:components] nas páginas, formulários são componentes, assim como todos os seus elementos. As duas classes base das quais todos esses componentes herdam fazem parte do pacote `nette/component-model` e têm a tarefa de criar uma hierarquia de componentes em árvore. Component ========= -[api:Nette\ComponentModel\Component] é o ancestral comum de todos os componentes. Ele contém o método `getName()` devolvendo o nome do componente e o método `getParent()` devolvendo seu pai. Ambos podem ser definidos com o método `setParent()` - o primeiro parâmetro é o pai e o segundo é o nome do componente. +[api:Nette\ComponentModel\Component] é o ancestral comum de todos os componentes. Contém os métodos `getName()` que retorna o nome do componente e `getParent()` que retorna seu pai. Ambos podem ser definidos usando o método `setParent()` - o primeiro parâmetro é o pai e o segundo é o nome do componente. lookup(string $type): ?Component .[method] ------------------------------------------ -Procura na hierarquia por um objeto da classe ou interface desejada. Por exemplo, `$component->lookup(Nette\Application\UI\Presenter::class)` retorna apresentador se o componente estiver conectado a ele, apesar de vários níveis. +Procura na hierarquia para cima um objeto da classe ou interface solicitada. Por exemplo, `$component->lookup(Nette\Application\UI\Presenter::class)` retorna o presenter, se o componente estiver anexado a ele, mesmo através de vários níveis. lookupPath(string $type): ?string .[method] ------------------------------------------- -Retorna o chamado caminho, que é uma cadeia formada por concatenar os nomes de todos os componentes no caminho entre o componente atual e o componente que está sendo procurado. Assim, por exemplo, `$component->lookupPath(Nette\Application\UI\Presenter::class)` retorna o identificador único do componente em relação ao apresentador. +Retorna o chamado caminho, que é uma string formada pela concatenação dos nomes de todos os componentes no caminho entre o componente atual e o componente procurado. Assim, por exemplo, `$component->lookupPath(Nette\Application\UI\Presenter::class)` retorna um identificador único do componente em relação ao presenter. Container ========= -[api:Nette\ComponentModel\Container] é o componente pai, ou seja, o componente que contém as crianças e assim formando a estrutura em árvore. Possui métodos para facilmente adicionar, recuperar e remover componentes. É o ancestral, por exemplo, da forma ou classes `Control` e `Presenter`. +[api:Nette\ComponentModel\Container] é o componente pai, ou seja, um componente que contém descendentes e forma assim uma estrutura em árvore. Possui métodos para fácil adição, obtenção e remoção de objetos. É o ancestral, por exemplo, do formulário ou das classes `Control` e `Presenter`. getComponent(string $name): ?Component .[method] ------------------------------------------------ -Devolve um componente. Tentativa de chamar de criança indefinida causa invocação de fábrica [criarComponente($nome) |api:Nette\ComponentModel\Container::createComponent()]. Método `createComponent($name)` invoca método `createComponent<component name>` no componente atual e ele passa o nome do componente como parâmetro. O componente criado é então passado para o componente atual como seu filho. Chamamos essas fábricas de componentes, elas podem ser implementadas em classes herdadas de `Container`. +Retorna um componente. Ao tentar obter um descendente indefinido, a fábrica `createComponent($name)` é chamada. O método `createComponent($name)` chama o método `createComponent<nome do componente>` no componente atual e passa o nome do componente como parâmetro. O componente criado é então adicionado ao componente atual como seu descendente. Chamamos esses métodos de fábricas de componentes e eles podem ser implementados por descendentes da classe `Container`. -Iterando sobre as crianças .[#toc-iterating-over-children] ----------------------------------------------------------- +getComponents(): array .[method] +-------------------------------- +Retorna os descendentes diretos como um array. As chaves contêm os nomes desses componentes. Nota: na versão 3.0.x, o método retornava um iterador em vez de um array, e seu primeiro parâmetro determinava se os componentes deveriam ser percorridos em profundidade, e o segundo representava um filtro de tipo. Esses parâmetros estão obsoletos. -O método [getComponents($deep = falso, $type = nulo) |api:Nette\ComponentModel\Container::getComponents()] é usado para iteração. O primeiro parâmetro especifica se os componentes devem ser percorridos em profundidade (ou recursivamente). Com `true`, ele não só itera todos os seus filhos, mas também todos os filhos de seus filhos, etc. Segundo parâmetro servidores como um filtro opcional por classe ou interface. -```php -foreach ($form->getComponents(true, Nette\Forms\IControl::class) as $control) { - if (!$control->getRules()->validate()) { - // ... - } -} -``` +getComponentTree(): array .[method]{data-version:3.1.0} +------------------------------------------------------- +Obtém toda a hierarquia de componentes, incluindo todos os componentes filhos aninhados, como um array indexado. A busca é feita primeiro em profundidade. -Monitoramento de Ancestrais .[#toc-monitoring-of-ancestors] -=========================================================== +Monitoramento de Ancestrais +=========================== -O modelo de componentes Nette permite um trabalho em árvore muito dinâmico (podemos remover, mover, adicionar componentes), portanto seria um erro confiar no fato de que, depois de criar um componente, o pai, o pai da mãe, etc. são conhecidos imediatamente (no construtor). Normalmente, o pai não é conhecido de forma alguma quando o componente é criado. +O modelo de componente Nette permite um trabalho muito dinâmico com a árvore (podemos remover, mover, adicionar componentes), portanto seria um erro confiar que, após a criação de um componente, o pai, o pai do pai, etc., sejam imediatamente conhecidos (no construtor). Na maioria das vezes, o pai não é conhecido durante a criação. -Como descobrir quando um componente foi adicionado à árvore apresentadora? Não basta acompanhar a mudança dos pais, porque os pais dos pais poderiam ter sido anexados ao apresentador, por exemplo. O método do [monitor ($type, $attached, $detached) |api:Nette\ComponentModel\Component::monitor()] pode ajudar. Cada componente pode monitorar qualquer número de classes e interfaces. A conexão ou desconexão é anunciada ligando para `$attached` e `$detached`, respectivamente, e passando o objeto da classe monitorada. +Como saber quando um componente foi anexado à árvore do presenter? Observar a mudança do pai não é suficiente, porque o pai do pai pode ter sido anexado ao presenter, por exemplo. O método [monitor($type, $attached, $detached)|api:Nette\ComponentModel\Component::monitor()] ajuda. Cada componente pode monitorar qualquer número de classes e interfaces. A anexação ou desanexação é sinalizada chamando o callback `$attached` ou `$detached`, respectivamente, e passando o objeto da classe monitorada. -Um exemplo: A classe `UploadControl`, que representa o elemento do formulário para carregar arquivos nos Formulários Nette, tem que definir o atributo do formulário `enctype` para valorizar `multipart/form-data`. Mas no momento da criação do objeto, ele não precisa ser anexado a nenhum formulário. Quando modificar o formulário? A solução é simples - nós criamos um pedido de monitoramento no construtor: +Para melhor compreensão, um exemplo: a classe `UploadControl`, que representa o elemento de formulário para upload de arquivos no Nette Forms, precisa definir o atributo `enctype` do formulário para o valor `multipart/form-data`. No entanto, no momento da criação do objeto, ele pode não estar anexado a nenhum formulário. Em que momento, então, modificar o formulário? A solução é simple - no construtor, solicita-se o monitoramento: ```php class UploadControl extends Nette\Forms\Controls\BaseControl @@ -68,7 +64,4 @@ class UploadControl extends Nette\Forms\Controls\BaseControl } ``` -e quando o formulário está disponível, a ligação de retorno é chamada. (Anteriormente, os métodos comuns `attached` e `detached` eram usados em seu lugar). - - -{{leftbar: nette:@menu-topics}} +e assim que o formulário estiver disponível, o callback é chamado. (Anteriormente, os métodos comuns `attached` e `detached` eram usados em seu lugar). diff --git a/component-model/pt/@meta.texy b/component-model/pt/@meta.texy new file mode 100644 index 0000000000..e2566bcb44 --- /dev/null +++ b/component-model/pt/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Documentação Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/component-model/ro/@home.texy b/component-model/ro/@home.texy index e2f31f4fa1..6bf15157b2 100644 --- a/component-model/ro/@home.texy +++ b/component-model/ro/@home.texy @@ -1,57 +1,53 @@ -Modelul componentelor +Modelul de componente ********************* .[perex] -Un concept important în Nette este cel de componentă. Inserăm [componente vizuale interactive |application:components] în pagini, formulare sau toate elementele acestora sunt, de asemenea, componente. Există două clase de bază din care moștenesc toate aceste componente, fac parte din pachetul `nette/component-model` și sunt responsabile pentru crearea ierarhiei arborelui de componente. +Un concept important în Nette este componenta. În pagini inserăm [componente vizuale interactive |application:components], componente sunt și formularele sau toate elementele lor. Cele două clase de bază, din care moștenesc toate aceste componente, fac parte din pachetul `nette/component-model` și au rolul de a crea o ierarhie arborescentă de componente. -Componenta .[#toc-component] -============================ -[api:Nette\ComponentModel\Component] este strămoșul comun al tuturor componentelor. Acesta conține metoda `getName()` care returnează numele componentei și metoda `getParent()` care returnează părintele acesteia. Ambele pot fi setate cu metoda `setParent()` - primul parametru este părintele, iar al doilea este numele componentei. +Component +========= +[api:Nette\ComponentModel\Component] este strămoșul comun al tuturor componentelor. Conține metodele `getName()` care returnează numele componentei și metoda `getParent()` care returnează părintele său. Ambele pot fi setate cu metoda `setParent()` - primul parametru este părintele și al doilea este numele componentei. lookup(string $type): ?Component .[method] ------------------------------------------ -Caută în ierarhie un obiect din clasa sau interfața dorită. De exemplu, `$component->lookup(Nette\Application\UI\Presenter::class)` returnează presenter dacă componenta este conectată la acesta, în ciuda mai multor niveluri. +Caută în ierarhie în sus un obiect de clasa sau interfața dorită. De exemplu, `$component->lookup(Nette\Application\UI\Presenter::class)` returnează presenter-ul, dacă componenta este atașată la acesta, chiar și prin mai multe niveluri. lookupPath(string $type): ?string .[method] ------------------------------------------- -Returnează așa-numita cale, care este un șir format prin concatenarea numelor tuturor componentelor de pe calea dintre componenta curentă și componenta căutată. Astfel, de exemplu, `$component->lookupPath(Nette\Application\UI\Presenter::class)` returnează identificatorul unic al componentei în raport cu prezentatorul. +Returnează așa-numita cale, care este un șir de caractere format prin concatenarea numelor tuturor componentelor de pe calea dintre componenta curentă și cea căutată. Deci, de exemplu, `$component->lookupPath(Nette\Application\UI\Presenter::class)` returnează un identificator unic al componentei față de presenter. -Container .[#toc-container] -=========================== -[api:Nette\ComponentModel\Container] este componenta părinte, adică componenta care conține copiii și care formează astfel structura arborescentă. Acesta dispune de metode pentru adăugarea, recuperarea și eliminarea cu ușurință a componentelor. Este strămoșul, de exemplu, al formularului sau al claselor `Control` și `Presenter`. +Container +========= +[api:Nette\ComponentModel\Container] este componenta părinte, adică o componentă care conține descendenți și formează astfel o structură arborescentă. Dispune de metode pentru adăugarea, obținerea și eliminarea ușoară a obiectelor. Este strămoșul, de exemplu, al formularului sau al claselor `Control` și `Presenter`. getComponent(string $name): ?Component .[method] ------------------------------------------------ -Returnează o componentă. Încercarea de a apela un copil nedefinit determină invocarea fabricii [createComponent($name) |api:Nette\ComponentModel\Container::createComponent()]. Metoda `createComponent($name)` invocă metoda `createComponent<component name>` în componenta curentă și transmite numele componentei ca parametru. Componenta creată este apoi transmisă componentei curente ca fiind copilul său. Numim aceste fabrici de componente, ele pot fi implementate în clase moștenite de la `Container`. +Returnează componenta. La încercarea de a obține un descendent nedefinit, este apelată fabrica `createComponent($name)`. Metoda `createComponent($name)` apelează în componenta curentă metoda `createComponent<nume componenta>` și îi transmite ca parametru numele componentei. Componenta creată este apoi adăugată la componenta curentă ca descendent al acesteia. Aceste metode le numim fabrici de componente și pot fi implementate de descendenții clasei `Container`. -Iterarea peste copii .[#toc-iterating-over-children] ----------------------------------------------------- +getComponents(): array .[method] +-------------------------------- +Returnează descendenții direcți ca array. Cheile conțin numele acestor componente. Notă: în versiunea 3.0.x, metoda returna un iterator în loc de array, iar primul său parametru specifica dacă componentele trebuie parcurse în adâncime, iar al doilea reprezenta un filtru de tip. Acești parametri sunt depreciați. -Metoda [getComponents($deep = false, $type = null) |api:Nette\ComponentModel\Container::getComponents()] este utilizată pentru iterație. Primul parametru specifică dacă se parcurge componentele în profunzime (sau recursiv). Cu `true`, nu numai că se iteră toți copiii săi, ci și toți copiii copiilor săi, etc. Al doilea parametru servește ca filtru opțional în funcție de clasă sau interfață. -```php -foreach ($form->getComponents(true, Nette\Forms\IControl::class) as $control) { - if (!$control->getRules()->validate()) { - // ... - } -} -``` +getComponentTree(): array .[method]{data-version:3.1.0} +------------------------------------------------------- +Obține întreaga ierarhie de componente, inclusiv toate componentele subordonate imbricate, ca un array indexat. Căutarea se face mai întâi în adâncime. -Monitorizarea strămoșilor .[#toc-monitoring-of-ancestors] -========================================================= +Monitorizarea strămoșilor +========================= -Modelul de componente Nette permite lucrul foarte dinamic cu arborele (putem elimina, muta, adăuga componente), astfel încât ar fi o greșeală să ne bazăm pe faptul că, după crearea unei componente, părintele, părintele părintelui etc. sunt cunoscute imediat (în constructor). De obicei, părintele nu este cunoscut deloc atunci când este creată componenta. +Modelul de componente Nette permite o muncă foarte dinamică cu arborele (putem elimina, muta, adăuga componente), de aceea ar fi o greșeală să ne bazăm pe faptul că, după crearea componentei, părintele, părintele părintelui etc. sunt imediat cunoscuți (în constructor). De obicei, părintele nu este deloc cunoscut la creare. -Cum se poate afla când o componentă a fost adăugată în arborele de prezentare? Urmărirea modificării părintelui nu este suficientă, deoarece părintele părintelui ar fi putut fi atașat la prezentator, de exemplu. Metoda [monitor($type, $attached, $detached) |api:Nette\ComponentModel\Component::monitor()] vă poate ajuta. Fiecare componentă poate monitoriza orice număr de clase și interfețe. Conectarea sau deconectarea este anunțată prin apelarea callback-urilor `$attached` și, respectiv, `$detached`, și prin transmiterea obiectului clasei monitorizate. +Cum să aflăm când a fost componenta atașată la arborele presenter-ului? Urmărirea schimbării părintelui nu este suficientă, deoarece la presenter ar fi putut fi atașat, de exemplu, părintele părintelui. Ajută metoda [monitor($type, $attached, $detached)|api:Nette\ComponentModel\Component::monitor()]. Fiecare componentă poate monitoriza orice număr de clase și interfețe. Atașarea sau detașarea este anunțată prin apelarea callback-ului `$attached` respectiv `$detached`, și transmiterea obiectului clasei monitorizate. -Un exemplu: Clasa `UploadControl`, care reprezintă elementul formular pentru încărcarea fișierelor în Nette Forms, trebuie să seteze atributul formularului `enctype` la valoarea `multipart/form-data`. Dar în momentul creării obiectului nu trebuie să fie atașată la nici un formular. Când trebuie modificat formularul? Soluția este simplă - creăm o cerere de monitorizare în constructor: +Pentru o mai bună înțelegere, un exemplu: clasa `UploadControl`, reprezentând elementul de formular pentru încărcarea fișierelor în Nette Forms, trebuie să seteze atributul `enctype` al formularului la valoarea `multipart/form-data`. Dar în momentul creării obiectului, este posibil să nu fie atașată la niciun formular. În ce moment, deci, să modificăm formularul? Soluția este simplă - în constructor se solicită monitorizarea: ```php class UploadControl extends Nette\Forms\Controls\BaseControl @@ -68,7 +64,4 @@ class UploadControl extends Nette\Forms\Controls\BaseControl } ``` -iar atunci când formularul este disponibil, se apelează callback-ul. (Anterior, în schimb, se foloseau metodele comune `attached` și `detached` ). - - -{{leftbar: nette:@menu-topics}} +și de îndată ce formularul este disponibil, se apelează callback-ul. (Anterior, în locul său se folosea metoda comună `attached` respectiv `detached`). diff --git a/component-model/ro/@meta.texy b/component-model/ro/@meta.texy new file mode 100644 index 0000000000..6554692600 --- /dev/null +++ b/component-model/ro/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Documentație Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/component-model/ru/@home.texy b/component-model/ru/@home.texy index 1e7b97e436..2b89ffd9fb 100644 --- a/component-model/ru/@home.texy +++ b/component-model/ru/@home.texy @@ -1,57 +1,53 @@ -Модель компонента -***************** +Компонентная модель +******************* .[perex] -Важным понятием в Nette является компонент. Мы вставляем [визуальные интерактивные компоненты |application:components] на страницы, формы или все их элементы также являются компонентами. Есть два основных класса, от которых наследуются все эти компоненты, они входят в пакет `nette/component-model` и отвечают за создание иерархии дерева компонентов. +Важным понятием в Nette является компонент. В страницы мы вставляем [визуальные интерактивные компоненты |application:components], компонентами также являются формы или все их элементы. Два базовых класса, от которых наследуются все эти компоненты, являются частью пакета `nette/component-model` и отвечают за создание древовидной иерархии компонентов. Component ========= -[api:Nette\ComponentModel\Component] является общим предком всех компонентов. Он содержит метод `getName()`, возвращающий имя компонента, и метод `getParent()`, возвращающий его родителя. Оба параметра могут быть заданы с помощью метода `setParent()` - первый параметр - родитель, второй - имя компонента. +[api:Nette\ComponentModel\Component] является общим предком всех компонентов. Он содержит методы `getName()`, возвращающий имя компонента, и метод `getParent()`, возвращающий его родителя. Оба можно установить методом `setParent()` - первый параметр - родитель, а второй - имя компонента. lookup(string $type): ?Component .[method] ------------------------------------------ -Ищет в иерархии объект нужного класса или интерфейса. Например, `$component->lookup(Nette\Application\UI\Presenter::class)` возвращает presenter, если компонент связан с ним, несмотря на несколько уровней. +Ищет в иерархии вверх объект требуемого класса или интерфейса. Например, `$component->lookup(Nette\Application\UI\Presenter::class)` возвращает презентер, если компонент присоединен к нему, даже через несколько уровней. lookupPath(string $type): ?string .[method] ------------------------------------------- -Возвращает так называемый путь, который представляет собой строку, образованную путем конкатенации имен всех компонентов на пути между текущим компонентом и искомым компонентом. Так, например, `$component->lookupPath(Nette\Application\UI\Presenter::class)` возвращает уникальный идентификатор компонента относительно ведущего. +Возвращает так называемый путь, который представляет собой строку, образованную соединением имен всех компонентов на пути между текущим и искомым компонентом. Так, например, `$component->lookupPath(Nette\Application\UI\Presenter::class)` возвращает уникальный идентификатор компонента относительно презентера. Container ========= -[api:Nette\ComponentModel\Container] является родительским компонентом, т.е. компонентом, содержащим дочерние компоненты и, таким образом, формирующим древовидную структуру. Он имеет методы для легкого добавления, извлечения и удаления компонентов. Он является предком, например, формы или классов `Control` и `Presenter`. +[api:Nette\ComponentModel\Container] является родительским компонентом, т.е. компонентом, содержащим потомков и образующим таким образом древовидную структуру. Он располагает методами для легкого добавления, получения и удаления объектов. Является предком, например, формы или классов `Control` и `Presenter`. getComponent(string $name): ?Component .[method] ------------------------------------------------ -Возвращает компонент. Попытка вызова неопределенного дочернего компонента приводит к вызову фабрики [createComponent($name) |api:Nette\ComponentModel\Container::createComponent()]. Метод `createComponent($name)` вызывает метод `createComponent<component name>` в текущем компоненте и передает имя компонента в качестве параметра. Созданный компонент затем передается текущему компоненту как его дочерний компонент. Мы называем эти фабрики компонентов, они могут быть реализованы в классах, унаследованных от `Container`. +Возвращает компонент. При попытке получить неопределенного потомка вызывается фабрика `createComponent($name)`. Метод `createComponent($name)` вызывает в текущем компоненте метод `createComponent<ИмяКомпонента>` и передает ему в качестве параметра имя компонента. Созданный компонент затем добавляется к текущему компоненту как его потомок. Эти методы мы называем фабриками компонентов, и их могут реализовывать потомки класса `Container`. -Итерация над дочерними компонентами .[#toc-iterating-over-children] -------------------------------------------------------------------- +getComponents(): array .[method] +-------------------------------- +Возвращает прямых потомков в виде массива. Ключи содержат имена этих компонентов. Примечание: в версии 3.0.x метод вместо массива возвращал итератор, и его первый параметр определял, следует ли проходить компоненты вглубь, а второй представлял собой фильтр по типу. Эти параметры устарели. -Метод [getComponents($deep = false, $type = null) |api:Nette\ComponentModel\Container::getComponents()] используется для итерации. Первый параметр определяет, следует ли обходить компоненты в глубину (или рекурсивно). При использовании `true` итерируется не только все его дочерние компоненты, но и все дочерние компоненты его дочерних компонентов и т.д. Второй параметр служит дополнительным фильтром по классу или интерфейсу. -```php -foreach ($form->getComponents(true, Nette\Forms\IControl::class) as $control) { - if (!$control->getRules()->validate()) { - // ... - } -} -``` +getComponentTree(): array .[method]{data-version:3.1.0} +------------------------------------------------------- +Получает всю иерархию компонентов, включая все вложенные дочерние компоненты, в виде индексированного массива. Поиск идет сначала вглубь. -Мониторинг предков .[#toc-monitoring-of-ancestors] -================================================== +Мониторинг предков +================== -Модель компонентов Nette позволяет очень динамично работать с деревом (мы можем удалять, перемещать, добавлять компоненты), поэтому было бы ошибкой полагаться на то, что после создания компонента родитель, родитель родителя и т.д. известны сразу (в конструкторе). Обычно родитель вообще не известен в момент создания компонента. +Компонентная модель Nette позволяет очень динамично работать с деревом (компоненты можно извлекать, перемещать, добавлять), поэтому было бы ошибкой полагаться на то, что после создания компонента сразу (в конструкторе) известен родитель, родитель родителя и т. д. В большинстве случаев родитель при создании вообще неизвестен. -Как узнать, когда компонент был добавлен в дерево презентера? Следить за изменением родителя недостаточно, потому что родитель родителя мог быть присоединен, например, к презентеру. В этом может помочь метод [monitor($type, $attached, $detached) |api:Nette\ComponentModel\Component::monitor()]. Каждый компонент может контролировать любое количество классов и интерфейсов. Подключение или отключение объявляется вызовом обратных вызовов `$attached` и `$detached`, соответственно, и передачей объекта контролируемого класса. +Как узнать, когда компонент был присоединен к дереву презентера? Отслеживать изменение родителя недостаточно, так как к презентеру мог быть присоединен, например, родитель родителя. Поможет метод [monitor($type, $attached, $detached)|api:Nette\ComponentModel\Component::monitor()]. Каждый компонент может отслеживать любое количество классов и интерфейсов. Присоединение или отсоединение сообщается вызовом callback-функции `$attached` соответственно `$detached`, и передачей объекта отслеживаемого класса. -Пример: Класс `UploadControl`, представляющий элемент формы для загрузки файлов в Nette Forms, должен установить атрибут формы `enctype` в значение `multipart/form-data`. Но во время создания объекта он не должен быть привязан ни к какой форме. Когда модифицировать форму? Решение простое - создаем запрос на мониторинг в конструкторе: +Для лучшего понимания пример: класс `UploadControl`, представляющий элемент формы для загрузки файлов в Nette Forms, должен установить атрибут `enctype` формы на значение `multipart/form-data`. Однако в момент создания объекта он может не быть присоединен ни к какой форме. В какой момент тогда модифицировать форму? Решение простое - в конструкторе запрашивается мониторинг: ```php class UploadControl extends Nette\Forms\Controls\BaseControl @@ -68,7 +64,4 @@ class UploadControl extends Nette\Forms\Controls\BaseControl } ``` -, и когда форма становится доступной, вызывается обратный вызов. (Ранее вместо этого использовались обычные методы `attached` и `detached` ). - - -{{leftbar: nette:@menu-topics}} +и как только форма становится доступной, вызывается callback. (Раньше вместо него использовался общий метод `attached` соответственно `detached`). diff --git a/component-model/ru/@meta.texy b/component-model/ru/@meta.texy new file mode 100644 index 0000000000..61577d6323 --- /dev/null +++ b/component-model/ru/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Документация Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/component-model/sl/@home.texy b/component-model/sl/@home.texy index 6577d3b5b4..cc2bd1f48c 100644 --- a/component-model/sl/@home.texy +++ b/component-model/sl/@home.texy @@ -1,57 +1,53 @@ -Model komponente -**************** +Komponentni model +***************** .[perex] -Pomemben koncept v Nette je komponenta. [Vizualne interaktivne komponente |application:components] vstavljamo v strani, obrazci ali vsi njihovi elementi so prav tako komponente. Obstajata dva osnovna razreda, od katerih dedujejo vse te komponente, ki sta del paketa `nette/component-model` in sta odgovorna za ustvarjanje drevesne hierarhije komponent. +Pomemben pojem v Nette je komponenta. V strani vstavljamo [vizualne interaktivne komponente |application:components], komponente so tudi obrazci ali vsi njihovi elementi. Osnovna dva razreda, od katerih vse te komponente dedujejo, sta del paketa `nette/component-model` in imata nalogo ustvarjati drevesno hierarhijo komponent. -Komponenta .[#toc-component] -============================ -[api:Nette\ComponentModel\Component] je skupni prednik vseh komponent. Vsebuje metodo `getName()`, ki vrača ime komponente, in metodo `getParent()`, ki vrača njenega starša. Oba lahko nastavite z metodo `setParent()` - prvi parameter je starš, drugi pa ime komponente. +Component +========= +[api:Nette\ComponentModel\Component] je skupni prednik vseh komponent. Vsebuje metodi `getName()`, ki vrača ime komponente, in metodo `getParent()`, ki vrača njenega starša. Oboje lahko nastavimo z metodo `setParent()` - prvi parameter je starš in drugi ime komponente. lookup(string $type): ?Component .[method] ------------------------------------------ -V hierarhiji poišče objekt želenega razreda ali vmesnika. Na primer, `$component->lookup(Nette\Application\UI\Presenter::class)` vrne presenter, če je komponenta kljub več ravnem povezana z njim. +V hierarhiji navzgor poišče objekt zahtevanega razreda ali vmesnika. Na primer `$component->lookup(Nette\Application\UI\Presenter::class)` vrne presenter, če je komponenta nanj, tudi preko več nivojev, priključena. lookupPath(string $type): ?string .[method] ------------------------------------------- -Vrne tako imenovano pot, ki je niz, sestavljen z združitvijo imen vseh komponent na poti med trenutno komponento in iskano komponento. Tako na primer `$component->lookupPath(Nette\Application\UI\Presenter::class)` vrne enolični identifikator komponente glede na predvajalnik. +Vrača t.i. pot, kar je niz, nastal s spajanjem imen vseh komponent na poti med trenutno in iskano komponento. Torej npr. `$component->lookupPath(Nette\Application\UI\Presenter::class)` vrača edinstven identifikator komponente glede na presenter. -Kontejner .[#toc-container] -=========================== -[api:Nette\ComponentModel\Container] je nadrejena komponenta, tj. komponenta, ki vsebuje otroke in tako tvori drevesno strukturo. Ima metode za enostavno dodajanje, iskanje in odstranjevanje komponent. Je prednik na primer obrazca ali razredov `Control` in `Presenter`. +Container +========= +[api:Nette\ComponentModel\Container] je starševska komponenta, tj. komponenta, ki vsebuje potomce in tako tvori drevesno strukturo. Ima metode za enostavno dodajanje, pridobivanje in odstranjevanje objektov. Je prednik na primer obrazca ali razredov `Control` in `Presenter`. getComponent(string $name): ?Component .[method] ------------------------------------------------ -Vrne komponento. Poskus klica nedefiniranega otroka povzroči klic tovarne [createComponent($name |api:Nette\ComponentModel\Container::createComponent()]). Metoda `createComponent($name)` prikliče metodo `createComponent<component name>` v trenutni komponenti in ji kot parameter posreduje ime komponente. Ustvarjena komponenta se nato posreduje trenutni komponenti kot njen otrok. Te tovarne komponent imenujemo tovarne komponent, ki jih lahko implementiramo v razredih, podedovanih iz `Container`. +Vrača komponento. Pri poskusu pridobivanja nedefiniranega potomca se pokliče tovarna `createComponent($name)`. Metoda `createComponent($name)` v trenutni komponenti pokliče metodo `createComponent<ime komponente>` in ji kot parameter posreduje ime komponente. Ustvarjena komponenta se nato doda v trenutno komponento kot njen potomec. Tem metodam rečemo tovarne komponent in jih lahko implementirajo potomci razreda `Container`. -Iteriranje nad otroki .[#toc-iterating-over-children] ------------------------------------------------------ +getComponents(): array .[method] +-------------------------------- +Vrača neposredne potomce kot polje. Ključi vsebujejo imena teh komponent. Opomba: v različici 3.0.x je metoda namesto polja vračala iterator in njen prvi parameter je določal, ali naj se komponente prehajajo v globino, drugi pa je predstavljal tipski filter. Ti parametri so zastareli. -Metoda [getComponents($deep = false, $type = null |api:Nette\ComponentModel\Container::getComponents()] ) se uporablja za iteracijo. Prvi parameter določa, ali naj se komponente preletijo po globini (ali rekurzivno). Pri `true` se ne iterira le po vseh njegovih otrocih, temveč tudi po vseh otrocih njegovih otrok itd. Drugi parameter služi kot izbirni filter po razredu ali vmesniku. -```php -foreach ($form->getComponents(true, Nette\Forms\IControl::class) as $control) { - if (!$control->getRules()->validate()) { - // ... - } -} -``` +getComponentTree(): array .[method]{data-version:3.1.0} +------------------------------------------------------- +Pridobi celotno hierarhijo komponent, vključno z vsemi gnezdenimi podrejenimi komponentami, kot indeksirano polje. Iskanje gre najprej v globino. -Spremljanje prednikov .[#toc-monitoring-of-ancestors] -===================================================== +Spremljanje prednikov +===================== -Model komponent Nette omogoča zelo dinamično delo z drevesom (komponente lahko odstranjujemo, premikamo, dodajamo), zato bi bilo napačno, če bi se zanašali na to, da so po ustvarjanju komponente takoj (v konstruktorju) znani starši, starši staršev itd. Običajno starš ob ustvarjanju komponente sploh ni znan. +Komponentni model Nette omogoča zelo dinamično delo z drevesom (komponente lahko odstranjujemo, premikamo, dodajamo), zato bi bila napaka zanašati se na to, da je po ustvarjanju komponente takoj (v konstruktorju) znan starš, starš starša itd. Večinoma namreč starš ob ustvarjanju sploh ni znan. -Kako ugotoviti, kdaj je bila komponenta dodana v drevo predstavitve? Spremljanje spremembe starša ni dovolj, saj bi bil lahko na primer starš starša priložen predstavniku. V pomoč je lahko metoda [monitor($type, $attached, $detached) |api:Nette\ComponentModel\Component::monitor()]. Vsaka komponenta lahko spremlja poljubno število razredov in vmesnikov. Priključitev ali odklop se najavi s klicem povratnih klicev `$attached` oziroma `$detached`, pri čemer se posreduje objekt spremljanega razreda. +Kako ugotoviti, kdaj je bila komponenta priključena v drevo presenterja? Spremljanje spremembe starša ni dovolj, saj je bil lahko k presenterju priključen na primer starš starša. Pomaga metoda [monitor($type, $attached, $detached)|api:Nette\ComponentModel\Component::monitor()]. Vsaka komponenta lahko spremlja poljubno število razredov in vmesnikov. Priključitev ali odklop je sporočen s klicem povratnega klica `$attached` oz. `$detached`, in posredovanjem objekta spremljanega razreda. -Primer: Razred `UploadControl`, ki predstavlja element obrazca za nalaganje datotek v Nette Forms, mora nastaviti atribut obrazca `enctype` na vrednost `multipart/form-data`. Toda v času ustvarjanja objekta mu ni treba biti priključen na noben obrazec. Kdaj spremeniti obrazec? Rešitev je preprosta - v konstruktorju ustvarimo zahtevo za spremljanje: +Za boljše razumevanje primer: razred `UploadControl`, ki predstavlja obrazčevni element za nalaganje datotek v Nette Forms, mora obrazcu nastaviti atribut `enctype` na vrednost `multipart/form-data`. V času ustvarjanja objekta pa ni nujno, da je priključen na kakršenkoli obrazec. V katerem trenutku torej modificirati obrazec? Rešitev je enostavna - v konstruktorju se zahteva spremljanje: ```php class UploadControl extends Nette\Forms\Controls\BaseControl @@ -68,7 +64,4 @@ class UploadControl extends Nette\Forms\Controls\BaseControl } ``` -in ko je obrazec na voljo, se pokliče povratni klic. (Prej smo namesto tega uporabljali običajni metodi `attached` in `detached`.) - - -{{leftbar: nette:@menu-topics}} +in takoj ko je obrazec na voljo, se pokliče povratni klic. (Prej se je namesto njega uporabljala skupna metoda `attached` oz. `detached`). diff --git a/component-model/sl/@meta.texy b/component-model/sl/@meta.texy new file mode 100644 index 0000000000..282883a3d6 --- /dev/null +++ b/component-model/sl/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Dokumentacija}} +{{leftbar: nette:@menu-topics}} diff --git a/component-model/tr/@home.texy b/component-model/tr/@home.texy index 799cbbd8e9..8b8015cab4 100644 --- a/component-model/tr/@home.texy +++ b/component-model/tr/@home.texy @@ -2,56 +2,52 @@ Bileşen Modeli ************** .[perex] -Nette önemli bir kavram da bileşendir. Sayfalara [görsel etkileşimli bileşenler |application:components] ekleriz, formlar veya tüm öğeleri de bileşenlerdir. Tüm bu bileşenlerin miras aldığı, `nette/component-model` paketinin bir parçası olan ve bileşen ağacı hiyerarşisini oluşturmaktan sorumlu olan iki temel sınıf vardır. +Nette'de önemli bir kavram bileşendir. Sayfalara [görsel etkileşimli bileşenler |application:components] ekleriz, formlar veya tüm öğeleri de bileşenlerdir. Tüm bu bileşenlerin miras aldığı temel iki sınıf, `nette/component-model` paketinin bir parçasıdır ve bileşenlerin ağaç hiyerarşisini oluşturmaktan sorumludur. Component ========= -[api:Nette\ComponentModel\Component] tüm bileşenlerin ortak atasıdır. Bileşenin adını döndüren `getName()` yöntemini ve ebeveynini döndüren `getParent()` yöntemini içerir. Her ikisi de `setParent()` yöntemiyle ayarlanabilir - ilk parametre ebeveyn, ikincisi ise bileşen adıdır. +[api:Nette\ComponentModel\Component], tüm bileşenlerin ortak atasıdır. Bileşenin adını döndüren `getName()` yöntemini ve ebeveynini döndüren `getParent()` yöntemini içerir. Her ikisi de `setParent()` yöntemiyle ayarlanabilir - ilk parametre ebeveyn, ikincisi bileşenin adıdır. lookup(string $type): ?Component .[method] ------------------------------------------ -İstenen sınıf veya arayüzün bir nesnesi için hiyerarşide arama yapar. Örneğin, `$component->lookup(Nette\Application\UI\Presenter::class)`, birkaç seviyeye rağmen bileşen kendisine bağlıysa presenter öğesini döndürür. +Hiyerarşide yukarı doğru istenen sınıf veya arayüzün nesnesini arar. Örneğin, `$component->lookup(Nette\Application\UI\Presenter::class)`, bileşen ona birkaç seviye üzerinden bile bağlıysa presenter'ı döndürür. lookupPath(string $type): ?string .[method] ------------------------------------------- -Geçerli bileşen ile aranan bileşen arasındaki yoldaki tüm bileşenlerin adlarının birleştirilmesiyle oluşturulan bir dize olan sözde yolu döndürür. Örneğin, `$component->lookupPath(Nette\Application\UI\Presenter::class)`, sunum yapan kişiye göre bileşenin benzersiz tanımlayıcısını döndürür. +Yol adı verilen, geçerli ve aranan bileşen arasındaki yoldaki tüm bileşenlerin adlarının birleştirilmesiyle oluşan bir dize döndürür. Yani, örneğin `$component->lookupPath(Nette\Application\UI\Presenter::class)`, bileşenin presenter'a göre benzersiz tanımlayıcısını döndürür. Container ========= -[api:Nette\ComponentModel\Container] ana bileşendir, yani çocukları içeren ve böylece ağaç yapısını oluşturan bileşendir. Bileşenleri kolayca eklemek, almak ve kaldırmak için yöntemleri vardır. Örneğin formun veya `Control` ve `Presenter` sınıflarının atasıdır. +[api:Nette\ComponentModel\Container], ebeveyn bileşendir, yani alt öğeleri içeren ve böylece bir ağaç yapısı oluşturan bir bileşendir. Nesneleri kolayca eklemek, almak ve kaldırmak için yöntemlere sahiptir. Örneğin formun veya `Control` ve `Presenter` sınıflarının atasıdır. getComponent(string $name): ?Component .[method] ------------------------------------------------ -Bir bileşen döndürür. Tanımsız alt öğeyi çağırma girişimi, [createComponent($name) |api:Nette\ComponentModel\Container::createComponent()] fabrikasının çağrılmasına neden olur. Yöntem `createComponent($name)` yöntemi çağırır `createComponent<component name>` ve bileşenin adını bir parametre olarak geçirir. Oluşturulan bileşen daha sonra mevcut bileşene çocuğu olarak aktarılır. Bunlara bileşen fabrikaları diyoruz, `Container` adresinden miras alınan sınıflarda uygulanabilirler. +Bileşeni döndürür. Tanımlanmamış bir alt öğeyi almaya çalışırken, `createComponent($name)` fabrikası çağrılır. `createComponent($name)` yöntemi, geçerli bileşende `createComponent<bileşen adı>` yöntemini çağırır ve parametre olarak bileşenin adını geçirir. Oluşturulan bileşen daha sonra geçerli bileşene alt öğesi olarak eklenir. Bu yöntemlere bileşen fabrikaları diyoruz ve `Container` sınıfının alt sınıfları tarafından uygulanabilirler. -Çocuklar üzerinde yineleme .[#toc-iterating-over-children] ----------------------------------------------------------- +getComponents(): array .[method] +-------------------------------- +Doğrudan alt öğeleri bir dizi olarak döndürür. Anahtarlar bu bileşenlerin adlarını içerir. Not: 3.0.x sürümünde, yöntem bir dizi yerine bir yineleyici döndürüyordu ve ilk parametresi bileşenlerin derinlemesine taranıp taranmayacağını belirtiyordu ve ikincisi bir tür filtresi temsil ediyordu. Bu parametreler kullanımdan kaldırılmıştır. -[getComponents($deep = false, $type = null) |api:Nette\ComponentModel\Container::getComponents()] yöntemi yineleme için kullanılır. İlk parametre, bileşenlerin derinlemesine (veya özyinelemeli olarak) dolaşılıp dolaşılmayacağını belirtir. `true` ile, yalnızca tüm çocuklarını değil, aynı zamanda çocuklarının tüm çocuklarını vb. yineler. İkinci parametre, sınıf veya arayüze göre isteğe bağlı bir filtre olarak sunucular. -```php -foreach ($form->getComponents(true, Nette\Forms\IControl::class) as $control) { - if (!$control->getRules()->validate()) { - // ... - } -} -``` +getComponentTree(): array .[method]{data-version:3.1.0} +------------------------------------------------------- +Tüm iç içe geçmiş alt bileşenler dahil olmak üzere tüm bileşen hiyerarşisini dizinlenmiş bir dizi olarak alır. Arama önce derinlemesine yapılır. -Ataların İzlenmesi .[#toc-monitoring-of-ancestors] -================================================== +Ataları İzleme +============== -Nette bileşen modeli çok dinamik ağaç çalışmasına izin verir (bileşenleri kaldırabilir, taşıyabilir, ekleyebiliriz), bu nedenle bir bileşen oluşturduktan sonra ebeveynin, ebeveynin ebeveyninin vb. hemen (yapıcıda) bilindiği gerçeğine güvenmek hata olur. Genellikle bileşen oluşturulduğunda ebeveyn hiç bilinmez. +Nette bileşen modeli, ağaçla çok dinamik çalışmaya olanak tanır (bileşenleri kaldırabilir, taşıyabilir, ekleyebiliriz), bu nedenle bir bileşen oluşturulduktan sonra ebeveynin, ebeveynin ebeveyninin vb. hemen (kurucuda) bilindiğine güvenmek bir hata olur. Çoğu zaman, ebeveyn oluşturma sırasında hiç bilinmez. -Bir bileşenin sunum ağacına ne zaman eklendiğini nasıl öğrenebilirim? Ebeveyn değişikliğini takip etmek yeterli değildir, çünkü örneğin ebeveynin ebeveyni sunum yapan kişiye eklenmiş olabilir. [monitor($type, $attached, $detached) |api:Nette\ComponentModel\Component::monitor()] yöntemi yardımcı olabilir. Her bileşen istediği sayıda sınıfı ve arayüzü izleyebilir. Bağlantı veya bağlantının kesilmesi, sırasıyla `$attached` ve `$detached` geri çağrılarının çağrılması ve izlenen sınıfın nesnesinin aktarılmasıyla bildirilir. +Bir bileşenin presenter ağacına ne zaman bağlandığını nasıl anlarız? Ebeveyn değişikliğini izlemek yeterli değildir, çünkü örneğin ebeveynin ebeveyni presenter'a bağlanmış olabilir. [monitor($type, $attached, $detached)|api:Nette\ComponentModel\Component::monitor()] yöntemi yardımcı olur. Her bileşen, herhangi bir sayıda sınıfı ve arayüzü izleyebilir. Bağlanma veya ayrılma, `$attached` veya `$detached` geri çağrısının çağrılmasıyla ve izlenen sınıfın nesnesinin geçirilmesiyle bildirilir. -Bir örnek: Nette Forms'ta dosya yüklemek için form öğesini temsil eden `UploadControl` sınıfı, formun `enctype` özniteliğini `multipart/form-data` değerine ayarlamalıdır. Ancak nesnenin oluşturulması sırasında herhangi bir forma eklenmesi gerekmez. Form ne zaman değiştirilir? Çözüm basittir - yapıcıda izleme için bir istek oluştururuz: +Daha iyi anlamak için bir örnek: Nette Forms'daki dosya yükleme form öğesini temsil eden `UploadControl` sınıfı, formun `enctype` niteliğini `multipart/form-data` değerine ayarlamalıdır. Ancak nesne oluşturulduğunda herhangi bir forma bağlı olmayabilir. Öyleyse formu hangi noktada değiştirmeli? Çözüm basittir - kurucuda izleme istenir: ```php class UploadControl extends Nette\Forms\Controls\BaseControl @@ -68,7 +64,4 @@ class UploadControl extends Nette\Forms\Controls\BaseControl } ``` -ve form kullanılabilir olduğunda, geri arama çağrılır. (Önceden bunun yerine `attached` ve `detached` ortak yöntemleri kullanılıyordu). - - -{{leftbar: nette:@menu-topics}} +ve form kullanılabilir olduğunda, geri çağrı çağrılır. (Daha önce bunun yerine ortak `attached` veya `detached` yöntemi kullanılıyordu). diff --git a/component-model/tr/@meta.texy b/component-model/tr/@meta.texy new file mode 100644 index 0000000000..e5c5cea355 --- /dev/null +++ b/component-model/tr/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Dokümantasyonu}} +{{leftbar: nette:@menu-topics}} diff --git a/component-model/uk/@home.texy b/component-model/uk/@home.texy index 0a5621f301..34b27ba85e 100644 --- a/component-model/uk/@home.texy +++ b/component-model/uk/@home.texy @@ -1,57 +1,53 @@ -Модель компонента -***************** +Компонентна модель +****************** .[perex] -Важливим поняттям у Nette є компонент. Ми вставляємо [візуальні інтерактивні компоненти |application:components] на сторінки, форми або всі їх елементи також є компонентами. Існує два базових класи, від яких успадковуються всі ці компоненти, вони є частиною пакета `nette/component-model` і відповідають за створення ієрархії дерева компонентів. +Важливим поняттям у Nette є компонент. На сторінки ми вставляємо [візуальні інтерактивні компоненти |application:components], компонентами є також форми або всі їхні елементи. Основні два класи, від яких успадковуються всі ці компоненти, є частиною пакету `nette/component-model` і мають на меті створення ієрархії компонентів у вигляді дерева. Component ========= -[api:Nette\ComponentModel\Component] є спільним предком усіх компонентів. Він містить метод `getName()`, який повертає ім'я компонента, і метод `getParent()`, який повертає його батька. Обидва методи можна задати за допомогою методу `setParent()` - перший параметр - це батько, а другий - назва компонента. +[api:Nette\ComponentModel\Component] є спільним предком усіх компонентів. Він містить методи `getName()`, що повертає назву компонента, та метод `getParent()`, що повертає його батька. Обидва можна встановити методом `setParent()` - перший параметр - батько, а другий - назва компонента. lookup(string $type): ?Component .[method] ------------------------------------------ -Шукає в ієрархії об'єкт потрібного класу або інтерфейсу. Наприклад, `$component->lookup(Nette\Application\UI\Presenter::class)` повертає presenter, якщо компонент підключений до нього, незважаючи на кілька рівнів. +Шукає в ієрархії вгору об'єкт потрібного класу або інтерфейсу. Наприклад, `$component->lookup(Nette\Application\UI\Presenter::class)` повертає presenter, якщо компонент приєднаний до нього, навіть через кілька рівнів. lookupPath(string $type): ?string .[method] ------------------------------------------- -Повертає так званий шлях - рядок, утворений конкатенацією назв усіх компонентів на шляху між поточним компонентом і компонентом, який шукається. Так, наприклад, `$component->lookupPath(Nette\Application\UI\Presenter::class)` повертає унікальний ідентифікатор компонента відносно ведучого. +Повертає так званий шлях, який є рядком, утвореним з'єднанням імен усіх компонентів на шляху між поточним та шуканим компонентом. Так, наприклад, `$component->lookupPath(Nette\Application\UI\Presenter::class)` повертає унікальний ідентифікатор компонента відносно presenter. Container ========= -[api:Nette\ComponentModel\Container] є батьківським компонентом, тобто компонентом, що містить дочірні компоненти і таким чином утворює деревоподібну структуру. Він має методи для легкого додавання, пошуку та видалення компонентів. Він є предком, наприклад, форми або класів `Control` та `Presenter`. +[api:Nette\ComponentModel\Container] є батьківським компонентом, тобто компонентом, що містить нащадків і таким чином утворює деревоподібну структуру. Він має методи для легкого додавання, отримання та видалення об'єктів. Він є предком, наприклад, форми або класів `Control` та `Presenter`. getComponent(string $name): ?Component .[method] ------------------------------------------------ -Повертає компонент. Спроба викликати невизначений дочірній компонент призводить до виклику фабричного [createComponent($name |api:Nette\ComponentModel\Container::createComponent()]). Метод `createComponent($name)` викликає метод `createComponent<component name>` в поточному компоненті і передає йому ім'я компонента як параметр. Створений компонент потім передається поточному компоненту як його дочірній компонент. Ми називаємо ці фабрики компонентів фабриками, вони можуть бути реалізовані в класах, успадкованих від `Container`. +Повертає компонент. При спробі отримати невизначеного нащадка викликається фабрика `createComponent($name)`. Метод `createComponent($name)` викликає в поточному компоненті метод `createComponent<назва компонента>` і передає йому як параметр назву компонента. Створений компонент потім додається до поточного компонента як його нащадок. Ці методи називаються фабриками компонентів і можуть бути реалізовані нащадками класу `Container`. -Ітерація над дочірніми компонентами .[#toc-iterating-over-children] -------------------------------------------------------------------- +getComponents(): array .[method] +-------------------------------- +Повертає прямих нащадків у вигляді масиву. Ключі містять назви цих компонентів. Примітка: у версії 3.0.x метод повертав ітератор замість масиву, а його перший параметр визначав, чи слід проходити компоненти в глибину, а другий представляв фільтр типів. Ці параметри є застарілими. -Для ітерації використовується метод [getComponents($deep = false, $type = null |api:Nette\ComponentModel\Container::getComponents()] ). Перший параметр вказує, чи потрібно обходити компоненти вглиб (або рекурсивно). У випадку з `true` ітерація відбувається не лише по всіх дочірніх компонентах, але й по всіх дочірніх компонентах і т.д. Другий параметр слугує додатковим фільтром за класом або інтерфейсом. -```php -foreach ($form->getComponents(true, Nette\Forms\IControl::class) as $control) { - if (!$control->getRules()->validate()) { - // ... - } -} -``` +getComponentTree(): array .[method]{data-version:3.1.0} +------------------------------------------------------- +Отримує всю ієрархію компонентів, включаючи всі вкладені дочірні компоненти, у вигляді індексованого масиву. Пошук спочатку йде в глибину. -Моніторинг предків .[#toc-monitoring-of-ancestors] -================================================== +Моніторинг предків +================== -Компонентна модель Nette дозволяє дуже динамічно працювати з деревами (ми можемо видаляти, переміщувати, додавати компоненти), тому було б помилкою покладатися на те, що після створення компонента відразу (в конструкторі) відомі батько, батько батька і т.д. Зазвичай батько взагалі не відомий на момент створення компонента. +Компонентна модель Nette дозволяє дуже динамічно працювати з деревом (компоненти можна видаляти, переміщати, додавати), тому було б помилкою покладатися на те, що після створення компонента відразу (в конструкторі) відомий батько, батько батька і т.д. Зазвичай батько при створенні взагалі не відомий. -Як дізнатися, коли компонент було додано до дерева доповідача? Відстежувати зміну батька недостатньо, тому що батько батька міг бути приєднаний, наприклад, до доповідача. Метод [monitor($type, $attached, $detached) |api:Nette\ComponentModel\Component::monitor()] може допомогти. Кожен компонент може відстежувати будь-яку кількість класів та інтерфейсів. Про підключення або відключення повідомляється викликом колбеків `$attached` і `$detached` відповідно, з передачею об'єкта класу, що моніториться. +Як дізнатися, коли компонент був приєднаний до дерева presenter? Спостерігати за зміною батька недостатньо, оскільки до presenter міг бути приєднаний, наприклад, батько батька. Допоможе метод [monitor($type, $attached, $detached)|api:Nette\ComponentModel\Component::monitor()]. Кожен компонент може моніторити будь-яку кількість класів та інтерфейсів. Приєднання або від'єднання повідомляється викликом callback-функції `$attached` або `$detached` відповідно, і передачею об'єкта відстежуваного класу. -Приклад: Клас `UploadControl`, що представляє елемент форми для завантаження файлів в Nette Forms, повинен встановити атрибут форми `enctype` в значення `multipart/form-data`. Але під час створення об'єкта він не повинен бути приєднаний до жодної форми. Коли ж модифікувати форму? Рішення просте - створюємо запит на моніторинг в конструкторі: +Для кращого розуміння приклад: клас `UploadControl`, що представляє елемент форми для завантаження файлів у Nette Forms, повинен встановити атрибут `enctype` форми на значення `multipart/form-data`. Однак у момент створення об'єкта він може не бути приєднаним до жодної форми. В який момент тоді модифікувати форму? Рішення просте - в конструкторі запитується моніторинг: ```php class UploadControl extends Nette\Forms\Controls\BaseControl @@ -68,7 +64,4 @@ class UploadControl extends Nette\Forms\Controls\BaseControl } ``` -і коли форма стає доступною, викликаємо коллбек. (Раніше замість цього використовувалися звичайні методи `attached` і `detached` ). - - -{{leftbar: nette:@menu-topics}} +і як тільки форма стає доступною, викликається callback. (Раніше замість нього використовувалися спільні методи `attached` або `detached`). diff --git a/component-model/uk/@meta.texy b/component-model/uk/@meta.texy new file mode 100644 index 0000000000..083a8ab9f7 --- /dev/null +++ b/component-model/uk/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Документація Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/contributing/bg/@home.texy b/contributing/bg/@home.texy index 51b043522f..628390697b 100644 --- a/contributing/bg/@home.texy +++ b/contributing/bg/@home.texy @@ -2,16 +2,16 @@ ************************** .[perex] -Разгледайте как можете да се включите в нашия проект с отворен код. Научете стъпките за принос към изходния код и документацията и се присъединете към мрежата от разработчици, които са се посветили на подобряването на Nette. +Научете как можете да се включите в нашия open source проект. Овладейте процедурите за принос към изходния код и документацията и станете част от общността на разработчиците, които активно участват в подобряването на Nette. **Код** -- [Принос към кода |code] -- [Стандарти за кодиране |coding-standard] +- [Как да допринесем към кода? |code] +- [Стандарт за кодиране |coding-standard] **Документация** -- [Принос към документацията |documentation] +- [Как да допринесем към документацията? |documentation] - [Синтаксис на документацията |syntax] - "Редактор за предварителен преглед":https://editor.nette.org diff --git a/contributing/bg/@left-menu.texy b/contributing/bg/@left-menu.texy index bb21e23464..111ad4339d 100644 --- a/contributing/bg/@left-menu.texy +++ b/contributing/bg/@left-menu.texy @@ -1,10 +1,10 @@ Код *** -- [Принос към кода |code] -- [Стандарти за кодиране |coding-standard] +- [Как да допринесем към кода? |code] +- [Стандарт за кодиране |coding-standard] -Документиране -************* -- [Принос към документацията |documentation] +Документация +************ +- [Как да допринесем към документацията? |documentation] - [Синтаксис на документацията |syntax] - "Редактор за предварителен преглед":https://editor.nette.org diff --git a/contributing/bg/code.texy b/contributing/bg/code.texy index b4105cf6ce..9e609453a3 100644 --- a/contributing/bg/code.texy +++ b/contributing/bg/code.texy @@ -1,87 +1,87 @@ -Принос към кода -*************** +Как да допринесете към кода +*************************** .[perex] -Планирате да дадете своя принос към рамката на Nette и трябва да се запознаете с правилата и процедурите? Това ръководство за начинаещи ще ви преведе през стъпките за ефективно допринасяне към кода, работа с хранилища и въвеждане на промени. +Подготвяте се да допринесете към Nette Framework и трябва да се ориентирате в правилата и процедурите? Този наръчник за начинаещи ще ви покаже стъпка по стъпка как ефективно да допринасяте към кода, да работите с хранилища и да внедрявате промени. -Процедура .[#toc-procedure] -=========================== +Процедура +========= -За да допринесете към кода, е необходимо да имате акаунт в [GitHub |https://github.com] и да сте запознати с основите на работата със системата за контрол на версиите Git. Ако не сте запознати с Git, можете да разгледате [ръководство git - the simple guide |https://rogerdudler.github.io/git-guide/] и да помислите за използване на някой от многото [графични клиенти |https://git-scm.com/downloads/guis]. +За да допринесете към кода, е необходимо да имате акаунт в [GitHub|https://github.com] и да сте запознати с основите на работа със системата за контрол на версиите Git. Ако не владеете работата с Git, можете да разгледате ръководството [git - the simple guide |https://rogerdudler.github.io/git-guide/] и евентуално да използвате някой от многото [графични клиенти |https://git-scm.com/downloads/guis]. -Подготовка на средата и хранилището .[#toc-preparing-the-environment-and-repository] ------------------------------------------------------------------------------------- +Подготовка на средата и хранилището +----------------------------------- -1) В GitHub създайте [разклонение на |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] [хранилището на пакета |www:packages], който възнамерявате да модифицирате -2) [Клонирайте |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] това хранилище на вашия компютър -3) Инсталирайте зависимостите, включително [Nette Tester |tester:], като използвате командата `composer install` +1) В GitHub си създайте [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] на хранилището на [пакета |www:packages], който се готвите да промените +2) [Клонирайте |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] това хранилище на своя компютър +3) Инсталирайте зависимостите, включително [Nette Tester |tester:], с командата `composer install` 4) Проверете дали тестовете работят, като стартирате `composer tester` -5) Създайте [нов клон |#New Branch], базиран на последната публикувана версия +5) Създайте си [#нов branch] базиран на последната издадена версия -Внедряване на собствени промени .[#toc-implementing-your-own-changes] ---------------------------------------------------------------------- +Внедряване на собствени промени +------------------------------- -Сега можете да направите собствени промени в кода: +Сега можете да направите своите собствени промени в кода: -1) Извършете желаните промени и не забравяйте за тестовете -2) Уверете се, че тестовете се изпълняват успешно, като използвате `composer tester` -3) Проверете дали кодът отговаря на [стандартите за кодиране |#coding standards] -4) Запазете (commit) промените с описание в [този формат |#Commit Description] +1) програмирайте желаните промени и не забравяйте тестовете +2) уверете се, че тестовете преминават успешно, с помощта на `composer tester` +3) проверете дали кодът отговаря на [стандарта за кодиране |#Стандарти за кодиране] +4) запазете промените (commit) с описание в [този формат |#Описание на commit] -Можете да създадете няколко коммита, по един за всяка логическа стъпка. Всеки commit трябва да е смислен сам по себе си. +Можете да създадете няколко commit-а, по един за всяка логическа стъпка. Всеки commit трябва да бъде смислен сам по себе си. -Изпращане на промени .[#toc-submitting-changes] ------------------------------------------------ +Изпращане на промените +---------------------- След като сте доволни от промените, можете да ги изпратите: -1) Изпратете промените в GitHub към вашето разклонение -2) Оттам ги изпратете в хранилището на Nette, като създадете [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) -3) Предоставете [достатъчно информация |#pull request description] в описанието +1) изпратете (push) промените в GitHub към вашия fork +2) оттам ги изпратете към Nette хранилището, като създадете [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) +3) посочете в описанието [достатъчно информация |#Описание на pull request] -Включване на обратна връзка .[#toc-incorporating-feedback] ----------------------------------------------------------- +Обработване на забележките +-------------------------- -Вашите ангажименти вече са видими за другите. Обичайно е да получавате коментари с предложения: +Вашите commit-и сега ще бъдат видени и от други. Обичайно е да получите коментари със забележки: -1) Следете предложените промени -2) Включете ги като нови коммити или [ги слейте с предишните |https://help.github.com/en/github/using-git/about-git-rebase] -3) Изпратете отново коммитите в GitHub и те автоматично ще се появят в заявката за изтегляне +1) следете предложените корекции +2) обработете ги като нови commit-и или ги [обединете с предишните |https://help.github.com/en/github/using-git/about-git-rebase] +3) отново изпратете commit-ите в GitHub и те автоматично ще се появят в pull request-а -Никога не създавайте нова заявка за изтегляне, за да промените съществуваща. +Никога не създавайте нов pull request за корекция на съществуващ. -Документация .[#toc-documentation] ----------------------------------- +Документация +------------ -Ако сте променили функционалност или сте добавили нова, не забравяйте да [я добавите и в документацията |documentation]. +Ако сте променили функционалност или сте добавили нова, не забравяйте да я [добавите и в документацията |documentation]. -Нов клон .[#toc-new-branch] -=========================== +Нов branch +========== -Ако е възможно, направете промени спрямо последната публикувана версия, т.е. последния таг в клона. За тага v3.2.1 създайте клон, като използвате тази команда: +Ако е възможно, правете промените спрямо последната издадена версия, т.е. последния таг в дадения branch. За таг `v3.2.1` ще създадете branch с тази команда: ```shell git checkout -b new_branch_name v3.2.1 ``` -Стандарти за кодиране .[#toc-coding-standards] -============================================== +Стандарти за кодиране +===================== -Вашият код трябва да отговаря на [стандартите за кодиране, |coding standard] използвани в Nette Framework. Налице е автоматичен инструмент за проверка и поправка на кода. Можете да го инсталирате **глобално** чрез Composer в папка по ваш избор: +Вашият код трябва да отговаря на [стандарта за кодиране |coding-standard], използван в Nette Framework. За проверка и корекция на кода е наличен автоматичен инструмент. Може да бъде инсталиран чрез Composer **глобално** във ваша избрана папка: ```shell composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Сега трябва да можете да стартирате инструмента в терминала. Първата команда проверява, а втората поправя кода в папките `src` и `tests` в текущата директория: +Сега трябва да можете да стартирате инструмента в терминала. С първата команда ще проверите, а с втората и ще коригирате кода в папките `src` и `tests` в текущата директория: ```shell /path/to/nette-coding-standard/ecs check @@ -89,29 +89,29 @@ composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Ангажимент Описание .[#toc-commit-description] -============================================== +Описание на commit +================== -В Nette темите на ангажиментите имат следния формат: `Presenter: fixed AJAX detection [Closes #69]` +В Nette темите на commit-ите имат формат: `Presenter: fixed AJAX detection [Closes #69]` - област, последвана от двоеточие -- цел на ангажимента в минало време; ако е възможно, започвайте с думи като: added, fixed, refactored, changed, removed -- ако ангажиментът нарушава обратната съвместимост, добавете "BC break". -- всякаква връзка с органа за проследяване на проблеми, като например `(#123)` или `[Closes #69]` -- след темата може да има един празен ред, последван от по-подробно описание, включително например връзки към форума +- целта на commit-а в минало време; ако е възможно, започнете с думата: "added" (добавена нова функционалност), "fixed" (корекция), "refactored" (промяна в кода без промяна на поведението), "changed", "removed" +- ако commit-ът нарушава обратната съвместимост, добавете "BC break" +- евентуална връзка към issue tracker като `(#123)` или `[Closes #69]` +- след темата може да последва един празен ред и след това по-подробно описание, включително например връзки към форума -Описание на заявката за изтегляне .[#toc-pull-request-description] -================================================================== +Описание на pull request +======================== -Когато създавате заявка за изтегляне, интерфейсът на GitHub ще ви позволи да въведете заглавие и описание. Посочете кратко заглавие, а в описанието включете възможно най-много информация за причините за вашата промяна. +При създаване на pull request интерфейсът на GitHub ще ви позволи да въведете заглавие и описание. Посочете описателно заглавие и в описанието предоставете колкото се може повече информация за причините за вашата промяна. -Също така посочете в заглавието дали става въпрос за нова функция или за поправка на грешка и дали тя може да предизвика проблеми с обратната съвместимост (BC break). Ако има свързан проблем, поставете връзка към него, така че той да бъде затворен при одобряване на заявката за промяна. +Ще се покаже и заглавие, където да посочите дали става въпрос за нова функция или корекция на грешка и дали може да настъпи нарушаване на обратната съвместимост (BC break). Ако има свързан проблем (issue), посочете го, за да бъде затворен след одобрение на pull request-а. ``` -- bug fix / new feature? <!-- #issue numbers, if any --> +- bug fix / new feature? <!-- #issue номера, ако има --> - BC break? yes/no -- doc PR: nette/docs#? <!-- highly welcome, see https://nette.org/en/writing --> +- doc PR: nette/docs#? <!-- силно приветствано, вижте https://nette.org/en/writing --> ``` diff --git a/contributing/bg/coding-standard.texy b/contributing/bg/coding-standard.texy index 463792fdbc..a6371c01dd 100644 --- a/contributing/bg/coding-standard.texy +++ b/contributing/bg/coding-standard.texy @@ -1,44 +1,44 @@ Стандарт за кодиране ******************** -Този документ описва правилата и насоките за разработване на Nette. Когато предоставяте код за Nette, трябва да ги спазвате. Най-лесният начин да направите това е да имитирате съществуващ код. -Идеята е целият код да изглежда така, сякаш го е написал един човек. +.[perex] +Този документ описва правилата и препоръките за разработка на Nette. При допринасяне на код към Nette трябва да ги спазвате. Най-лесният начин да го направите е да имитирате съществуващия код. Целта е целият код да изглежда така, сякаш е написан от един човек. -Стандартът за кодиране на Nette следва [разширения стил на кодиране PSR-12 |https://www.php-fig.org/psr/psr-12/] с две основни изключения: използва [табулации вместо интервали |#tabs вместо пробелов] за отстъпите и използва [PascalCase за константите на класовете |https://blog.nette.org/bg/za-po-malko-kresene-v-koda]. +Стандартът за кодиране на Nette отговаря на [PSR-12 Extended Coding Style |https://www.php-fig.org/psr/psr-12/], с две основни изключения: за отстъп използва [#табулатори вместо интервали] и за [константи на класове използва PascalCase|https://blog.nette.org/bg/for-less-screaming-in-the-code]. -Общи правила .[#toc-general-rules] -================================== +Общи правила +============ - Всеки PHP файл трябва да съдържа `declare(strict_types=1)` -- Два празни реда се използват за разделяне на методите с цел по-добра четливост. -- Причината за използване на оператора shut-up трябва да бъде документирана: `@mkdir($dir); // @ - directory may exist` -- Ако се използва оператор за сравнение със слаб шрифт (т.е. `==`, `!=`, ...), трябва да се документира намерението за това: `// == to accept null` -- Можете да запишете повече изключения в един файл `exceptions.php` -- Видимостта на методите не се определя за интерфейсите, тъй като те винаги са публични. -- Всяко свойство, връщана стойност и параметър трябва да имат посочен тип. От друга страна, за крайните константи никога не посочваме типа, защото той е очевиден. -- Трябва да се използват единични кавички за ограничаване на низ, освен когато самият литерал съдържа апострофи. +- Два празни реда се използват за разделяне на методи за по-добра четливост. +- Причината за използване на shut-up оператора трябва да бъде документирана: `@mkdir($dir); // @ - директорията може да съществува`. +- Ако се използва оператор за сравнение със слабо типизиране (т.е. `==`, `!=`, ...), намерението трябва да бъде документирано: `// == приема null` +- В един файл `exceptions.php` можете да запишете няколко изключения. +- При интерфейсите не се специфицира видимостта на методите, тъй като те винаги са публични. +- Всяко свойство, върната стойност и параметър трябва да имат посочен тип. Обратно, при `final` константи никога не посочваме тип, тъй като той е очевиден. +- За ограждане на низ трябва да се използват единични кавички, с изключение на случаите, когато самият литерал съдържа апострофи. -Конвенции за именуване .[#toc-naming-conventions] -================================================= +Конвенции за именуване +====================== -- Не използвайте съкращения, освен ако пълното име не е твърде дълго. -- Използвайте главни букви за двубуквени съкращения, а паскал/камела за по-дълги съкращения. -- Използвайте съществително име или съществително словосъчетание за името на класа. -- Имената на класовете трябва да съдържат не само конкретност (`Array`), но и обобщеност (`ArrayIterator`). PHP атрибутите са изключение. -- "Константите на класовете и енумите трябва да използват PascalCaps":https://blog.nette.org/bg/za-po-malko-kresene-v-koda. -- "Интерфейсите и абстрактните класове не трябва да съдържат префикси или суфикси":https://blog.nette.org/bg/prefiksite-i-sufiksite-ne-prinadlezat-na-imenata-na-interfejsite като `Abstract`, `Interface` или `I`. +- Не използвайте съкращения, освен ако цялото име не е твърде дълго. +- При двубуквени съкращения използвайте главни букви, при по-дълги съкращения PascalCase/camelCase. +- За име на клас използвайте съществително име или словосъчетание. +- Имената на класовете трябва да съдържат не само спецификата (`Array`), но и общността (`ArrayIterator`). Изключение са PHP атрибутите. +- "Константите на класове и енумите трябва да използват PascalCaps":https://blog.nette.org/bg/for-less-screaming-in-the-code. +- "Интерфейсите и абстрактните класове не трябва да съдържат префикси или суфикси":https://blog.nette.org/bg/prefixes-and-suffixes-do-not-belong-in-interface-names като `Abstract`, `Interface` или `I`. -Обвивки и скоби .[#toc-wrapping-and-braces] -=========================================== +Обвиване и скоби +================ -Стандартът за кодиране на Nette съответства на PSR-12 (или PER Coding Style), като в някои точки той го усъвършенства повече или го променя: +Стандартът за кодиране на Nette отговаря на PSR-12 (респ. PER Coding Style), в някои точки го допълва или променя: -- Функциите със стрелки се записват без интервал преди скобата, т.е. `fn($a) => $b`. -- не се изисква празен ред между различните видове оператори за внос `use` -- типът на връщане на функцията/метода и отварящата скоба трябва да са на отделни редове за по-добра четливост: +- arrow функциите се пишат без интервал преди скобата, т.е. `fn($a) => $b`. +- не се изисква празен ред между различните типове `use` импортиращи изрази. +- типът на връщаната стойност на функция/метод и началната фигурна скоба винаги са на отделни редове: ```php public function find( @@ -46,24 +46,28 @@ array $options, ): array { - // тело метода + // тяло на метода } ``` +Началната фигурна скоба на отделен ред е важна за визуалното разделяне на сигнатурата на функцията/метода от тялото. Ако сигнатурата е на един ред, разделянето е ясно (изображение вляво), ако е на няколко реда, в PSR сигнатурата и тялото се сливат (в средата), докато в стандарта на Nette те продължават да бъдат разделени (вдясно): -Блокове за документация (phpDoc) .[#toc-documentation-blocks-phpdoc] -==================================================================== +[* new-line-after.webp *] -Основно правило: никога не дублирайте информация за сигнатурата, като например тип на параметъра или тип на връщане, без допълнителна цел. -Блок за документация на дефиницията на класа: +Документационни блокове (phpDoc) +================================ + +Основно правило: Никога не дублирайте никаква информация в сигнатурата, като тип на параметър или тип на връщаната стойност, без добавена стойност. + +Документационен блок за дефиниция на клас: - Започва с описание на класа. -- След това се поставя празен ред. -- След това се добавят една след друга анотациите `@property` (или `@property-read`, `@property-write`). Синтаксисът е: annotation, space, type, space, $name. -- След това се добавят една след друга анотациите `@method`. Синтаксисът е следният: annotation, space, return type, space, name(type $param, ...). -- Анотацията `@author` е пропусната. Авторството се съхранява в историята на изходния код. -- Можете да използвате анотации `@internal` или `@deprecated`. +- Следва празен ред. +- Следват анотации `@property` (или `@property-read`, `@property-write`), една след друга. Синтаксисът е: анотация, интервал, тип, интервал, `$име`. +- Следват анотации `@method`, една след друга. Синтаксисът е: анотация, интервал, тип на връщаната стойност, интервал, име(тип $param, ...). +- Анотацията `@author` се пропуска. Авторството се съхранява в историята на изходния код. +- Могат да се използват анотации `@internal` или `@deprecated`. ```php /** @@ -76,27 +80,27 @@ */ ``` -Блокът с документация за свойство, съдържащ само анотацията `@var`, трябва да бъде на един ред: +Документационен блок за свойство, който съдържа само анотация `@var`, трябва да бъде едноредов: ```php /** @var string[] */ private array $name; ``` -Блок за документация за дефиниция на метод: +Документационен блок за дефиниция на метод: - Започва с кратко описание на метода. -- Няма празен ред. -- Анотации `@param`, една след друга. +- Без празен ред. +- Анотации `@param` на отделни редове. - Анотация `@return`. - Анотации `@throws`, една след друга. -- Можете да използвате анотациите `@internal` или `@deprecated`. +- Могат да се използват анотации `@internal` или `@deprecated`. -Всяка анотация е последвана от един интервал, с изключение на `@param`, последван от два интервала за по-добра четливост. +След всяка анотация следва един интервал, с изключение на `@param`, след която за по-добра четливост следват два интервала. ```php /** - * Finds a file in directory. + * Намира файл в директория. * @param string[] $options * @return string[] * @throws DirectoryNotFoundException @@ -105,21 +109,20 @@ public function find(string $dir, array $options): array ``` -Табулация вместо интервали .[#toc-tabs-instead-of-spaces] -========================================================= +Табулатори вместо интервали +=========================== -Таблиците имат редица предимства пред пространствата: +Табулаторите имат няколко предимства пред интервалите: -- размерът на отстъпа може да се конфигурира в редакторите и в "уеб":https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size -- те не налагат размер на отстъпите в кода, така че кодът е по-преносим. -- могат да се въвеждат с едно натискане на клавиш (навсякъде, а не само в редактори, които превръщат табулаторите в интервали). -- отстъпът е тяхната цел -- да зачитат нуждите на незрящите и слепите колеги. +- размерът на отстъпа може да се персонализира в редакторите и в "уеб":https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size +- не налагат на кода предпочитанията на потребителя за размера на отстъпа, така че кодът е по-преносим +- могат да се напишат с едно натискане на клавиш (навсякъде, не само в редактори, които превръщат табулаторите в интервали) +- отстъпването е тяхната цел +- уважават нуждите на колегите със зрителни увреждания и незрящите -Използвайки табове в нашите проекти, ние ви позволяваме да регулирате ширината, което може да изглежда ненужно за повечето хора, но е от съществено значение за хората със зрителни увреждания. +Чрез използването на табулатори в нашите проекти позволяваме персонализиране на ширината, което може да изглежда като излишно за повечето хора, но за хората със зрителни увреждания е необходимо. -За незрящите програмисти, които използват брайлови дисплеи, всеки интервал е представен с брайлова клетка и заема ценно място. Така че, докато отстъпът по подразбиране е 4 интервала, отстъпът от трето ниво отнема 12 брайлови клетки преди началото на кода. -При 40-клетъчния дисплей, който най-често се използва в лаптопите, това означава, че повече от една четвърт от наличните клетки се губят без никаква информация. +За незрящите програмисти, които използват брайлови дисплеи, всеки интервал представлява една брайлова клетка. Така че, ако отстъпът по подразбиране е 4 интервала, отстъпът от 3-то ниво губи 12 ценни брайлови клетки още преди началото на кода. На 40-клетъчен дисплей, който се използва най-често при лаптопи, това е повече от една четвърт от наличните клетки, които са пропилени без никаква информация. {{priority: -1}} diff --git a/contributing/bg/documentation.texy b/contributing/bg/documentation.texy index 309d6cd434..4f03aca70a 100644 --- a/contributing/bg/documentation.texy +++ b/contributing/bg/documentation.texy @@ -1,69 +1,68 @@ -Принос към документацията -************************* +Как да допринесете към документацията +************************************* .[perex] -Приносът към документацията е една от най-ценните дейности, тъй като помага на другите да разберат рамката. +Допринасянето към документацията е една от най-полезните дейности, тъй като помагате на другите да разберат framework-а. -Как да пишем? .[#toc-how-to-write] ----------------------------------- +Как да пишем? +------------- -Документацията е предназначена предимно за хора, които са нови по темата. Затова тя трябва да отговаря на няколко важни момента: +Документацията е предназначена предимно за хора, които се запознават с темата. Затова трябва да отговаря на няколко важни точки: -- Започнете с прости и общи теми. В края преминете към по-напреднали теми -- Опитайте се да обясните темата възможно най-ясно. Например, опитайте се първо да обясните темата на колега -- Предоставяйте само информацията, която потребителят действително трябва да знае за дадена тема -- Уверете се, че информацията ви е точна. Тествайте всеки код -- Бъдете кратки - съкратете написаното наполовина. И след това не се колебайте да го направите отново -- Използвайте пестеливо подчертаване, от удебелени шрифтове до рамки като `.[note]` -- Спазвайте [стандарта за кодиране |Coding Standard] в кода +- Започнете от простото и общото. Към по-напредналите теми преминете едва накрая +- Опитайте се да обясните нещата възможно най-добре. Опитайте например първо да обясните темата на колега +- Посочвайте само тази информация, която потребителят действително трябва да знае по дадената тема +- Проверете дали вашата информация е наистина вярна. Тествайте всеки код +- Бъдете кратки - това, което напишете, съкратете наполовина. А след това спокойно още веднъж +- Пестете всякакви видове подчертавания, от удебелен шрифт до рамки като `.[note]` +- В кодовете спазвайте [Стандарта за кодиране |coding-standard] -Също така научете [синтаксиса |syntax]. За предварителен преглед на статията по време на писане можете да използвате [редактора за предварителен преглед |https://editor.nette.org/]. +Освойте също [синтаксиса |syntax]. За преглед на статията по време на писането й можете да използвате [редактор с преглед |https://editor.nette.org/]. -Мутации на езика .[#toc-language-mutations] -------------------------------------------- +Езикови версии +-------------- -Английският е основният език, така че промените трябва да бъдат на английски. Ако английският не е силната ви страна, използвайте [DeepL Translator |https://www.deepl.com/translator] и други ще проверят текста ви. +Основният език е английският, така че вашите промени трябва да бъдат на чешки и английски. Ако английският не е вашата силна страна, използвайте [DeepL Translator |https://www.deepl.com/translator] и другите ще проверят текста ви. -Преводът на други езици ще бъде извършен автоматично след одобрение и прецизиране на вашата редакция. +Преводът на други езици ще бъде извършен автоматично след одобрение и финализиране на вашата корекция. -Тривиални редакции .[#toc-trivial-edits] ----------------------------------------- +Тривиални корекции +------------------ -За да допринесете за документацията, трябва да имате акаунт в [GitHub |https://github.com]. +За да допринесете към документацията, е необходимо да имате акаунт в [GitHub|https://github.com]. -Най-лесният начин да направите малка промяна в документацията е да използвате връзките в края на всяка страница: +Най-лесният начин да направите дребна промяна в документацията е да използвате връзките в края на всяка страница: -- *Покажи в GitHub* отваря изходната версия на страницата в GitHub. След това просто натиснете бутона `E` и можете да започнете да редактирате (трябва да сте влезли в GitHub) -- *Отваряне на визуализацията* отваря редактор, в който можете веднага да видите окончателния визуален вид +- *Покажи в GitHub* отваря изходния вид на дадената страница в GitHub. След това е достатъчно да натиснете бутона `E` и можете да започнете да редактирате (необходимо е да сте влезли в GitHub) +- *Отвори преглед* отваря редактор, където веднага виждате и крайния визуален вид -Тъй като [редакторът за предварителен преглед |https://editor.nette.org/] няма възможност за запазване на промените директно в GitHub, трябва да копирате изходния текст в клипборда (с помощта на бутона *Копирай в клипборда*) и след това да го поставите в редактора в GitHub. -Под полето за редактиране се намира форма за изпращане. Тук не забравяйте да обобщите накратко и да обясните причината за вашата редакция. След подаване се създава т.нар. заявка за теглене (PR), която може да се редактира допълнително. +Тъй като [редакторът с преглед |https://editor.nette.org/] няма възможност да запазва промените директно в GitHub, е необходимо след завършване на корекциите да копирате изходния текст в клипборда (с бутона *Copy to clipboard*) и след това да го поставите в редактора в GitHub. Под полето за редактиране има формуляр за изпращане. Тук не забравяйте да обобщите накратко и да обясните причината за вашата корекция. След изпращане се създава т.нар. pull request (PR), който може да бъде редактиран допълнително. -По-големи редакции .[#toc-larger-edits] ---------------------------------------- +По-големи корекции +------------------ -По-подходящо е да се запознаете с основите на работата със системата за контрол на версиите Git, вместо да разчитате единствено на интерфейса на GitHub. Ако не сте запознати с Git, можете да се обърнете към [ръководство git - the simple |https://rogerdudler.github.io/git-guide/] и да помислите за използване на някой от многото налични [графични клиенти |https://git-scm.com/downloads/guis]. +По-подходящо, отколкото да използвате интерфейса на GitHub, е да сте запознати с основите на работа със системата за контрол на версиите Git. Ако не владеете работата с Git, можете да разгледате ръководството [git - the simple guide |https://rogerdudler.github.io/git-guide/] и евентуално да използвате някой от многото [графични клиенти |https://git-scm.com/downloads/guis]. -Редактирайте документацията по следния начин: +Редактирайте документацията по този начин: -1) в GitHub създайте [разклонение на |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] хранилището [nette/docs |https://github.com/nette/docs] -2) [клонирайте |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] това хранилище на вашия компютър -3) след това направете промени в [съответния клон |#Documentation Structure] -4) проверете за допълнителни интервали в текста с помощта на инструмента [Code-Checker |code-checker:] -5) запишете (commit) промените -6) ако сте доволни от промените, изпратете ги в GitHub във вашето разклонение -7) оттам ги изпратете в хранилището `nette/docs`, като създадете [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) +1) В GitHub си създайте [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] на хранилището [nette/docs |https://github.com/nette/docs] +2) [Клонирайте |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] това хранилище на своя компютър +3) След това в [съответния branch |#Структура на документацията] направете промените +4) Проверете за излишни интервали в текста с помощта на инструмента [Code-Checker |code-checker:] +4) Запазете промените (commit) +6) Ако сте доволни от промените, изпратете ги (push) в GitHub към вашия fork +7) Оттам ги изпратете към хранилището `nette/docs`, като създадете [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) -Обичайно е да получавате коментари с предложения. Следете за предложените промени и ги включвайте. Добавете предложените промени като нови коммити и ги изпратете отново в GitHub. Никога не създавайте нова заявка за привличане само за да промените съществуваща такава. +Обичайно е да получавате коментари със забележки. Следете предложените промени и ги обработете. Добавете предложените промени като нови commit-и и отново ги изпратете в GitHub. Никога не създавайте нов pull request заради корекция на съществуващ pull request. -Структура на документацията .[#toc-documentation-structure] ------------------------------------------------------------ +Структура на документацията +--------------------------- -Цялата документация се намира в GitHub в хранилището [nette/docs |https://github.com/nette/docs]. Текущата версия се намира в главния клон, а по-старите версии са разположени в клонове като `doc-3.x`, `doc-2.x`. +Цялата документация е разположена в GitHub в хранилището [nette/docs |https://github.com/nette/docs]. Текущата версия е в `master`, по-старите версии са разположени в branch-ове като `doc-3.x`, `doc-2.x`. -Съдържанието на всеки клон е разделено на основни папки, представляващи отделните области на документацията. Например `application/` съответства на https://doc.nette.org/en/application, `latte/` съответства на https://latte.nette.org, и т.н. Всяка от тези папки съдържа подпапки, представляващи езиковите мутации (`cs`, `en`, ...), и по желание подпапка `files` с изображения, които могат да се вмъкват в страниците в документацията. +Съдържанието на всеки branch се разделя на основни папки, представляващи отделните области на документацията. Например `application/` отговаря на https://doc.nette.org/bg/application, `latte/` отговаря на https://latte.nette.org и т.н. Всяка такава папка съдържа подпапки, представляващи езиковите версии (`cs`, `en`, `bg`, ...) и евентуално подпапка `files` с изображения, които могат да бъдат вмъквани в страниците на документацията. diff --git a/contributing/bg/syntax.texy b/contributing/bg/syntax.texy index e62c117680..94bbb02a7a 100644 --- a/contributing/bg/syntax.texy +++ b/contributing/bg/syntax.texy @@ -1,59 +1,59 @@ -Синтаксис на Уикипедия -********************** +Синтаксис на документацията +*************************** -Документация използва [синтаксиса на |https://texy.info/en/syntax] Markdown и [Texy |https://texy.info/en/syntax] с няколко подобрения. +Документацията използва Markdown & [синтаксис на Texy |https://texy.nette.org/syntax] с някои разширения. -Връзки .[#toc-links] -==================== +Връзки +====== -За вътрешните препратки се използва обозначението в квадратни скоби `[link]` се използва. Това е или под формата на вертикална лента `[link text |link target]`, или в съкратен вид `[link text]` ако целта е същата като текста (след преобразуване в малки букви и тирета): +За вътрешни връзки се използва запис в квадратни скоби `[връзка |odkaz]`. И това е или във формата с вертикална черта `[текст на връзката |цел на връзката]`, или съкратено `[текст на връзката]`, ако целта е същата като текста (след трансформация в малки букви и тирета): -- `[Page name]` -> `<a href="/en/page-name">Page name</a>` -- `[link text |Page name]` -> `<a href="/en/page-name">link text</a>` +- `[Page name]` -> `<a href="/bg/page-name">Page name</a>` +- `[текст на връзка |Page name]` -> `<a href="/bg/page-name">текст на връзка</a>` -Можем да се свържем с друг език или друг раздел. Разделът е библиотека на Nette (например `forms`, `latte` и т.н.) или специални раздели като `best-practices`, `quickstart` и т.н: +Можем да правим връзки към друга езикова версия или към друга секция. Под секция се разбира Nette библиотека (напр. `forms`, `latte` и др.) или специални секции като `best-practices`, `quickstart` и т.н.: -- `[cs:Page name]` -> `<a href="/en/page-name">Page name</a>` (същият раздел, различен език) -- `[tracy:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (друг раздел, същият език) -- `[tracy:cs:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (различен раздел и език) +- `[cs:Page name]` -> `<a href="/cs/page-name">Page name</a>` (същата секция, друг език) +- `[tracy:Page name]` -> `<a href="//tracy.nette.org/bg/page-name">Page name</a>` (друга секция, същия език) +- `[tracy:cs:Page name]` -> `<a href="//tracy.nette.org/cs/page-name">Page name</a>` (друга секция и език) -Също така е възможно да се насочите към определено заглавие на страницата с помощта на `#`. +С помощта на `#` е възможно също така да се насочи към конкретно заглавие на страницата. - `[#Heading]` -> `<a href="#toc-heading">Heading</a>` (заглавие на текущата страница) -- `[Page name#Heading]` -> `<a href="/en/page-name#toc-heading">Page name</a>` +- `[Page name#Heading]` -> `<a href="/bg/page-name#toc-heading">Page name</a>` -Връзка към началната страница на раздела: (`@home` е специален термин за началната страница на раздела) +Връзка към началната страница на секцията: (`@home` е специален израз за началната страница на секцията) -- `[link text |@home]` -> `<a href="/en/">link text</a>` -- `[link text |tracy:]` -> `<a href="//tracy.nette.org/en/">link text</a>` +- `[текст на връзка |@home]` -> `<a href="/bg/">текст на връзка</a>` +- `[текст на връзка |tracy:]` -> `<a href="//tracy.nette.org/bg/">текст на връзка</a>` -Връзки към документацията на API .[#toc-links-to-api-documentation] -------------------------------------------------------------------- +Връзки към API документацията +----------------------------- -Винаги използвайте следните означения: +Винаги посочвайте само с този запис: - `[api:Nette\SmartObject]` -> [api:Nette\SmartObject] - `[api:Nette\Forms\Form::setTranslator()]` -> [api:Nette\Forms\Form::setTranslator()] - `[api:Nette\Forms\Form::$onSubmit]` -> [api:Nette\Forms\Form::$onSubmit] - `[api:Nette\Forms\Form::Required]` -> [api:Nette\Forms\Form::Required] -Напълно квалифицираните имена се използват само при първото споменаване. За останалите връзки използвайте опростено име: +Използвайте напълно квалифицирани имена само при първото споменаване. За следващи връзки използвайте опростено име: -- `[Form::setTranslator() |api:Nette\Forms\Form::setTranslator()]` -> [Form::setTranslator( |api:Nette\Forms\Form::setTranslator()]) +- `[Form::setTranslator() |api:Nette\Forms\Form::setTranslator()]` -> [Form::setTranslator() |api:Nette\Forms\Form::setTranslator()] -Връзки към документацията на PHP .[#toc-links-to-php-documentation] -------------------------------------------------------------------- +Връзки към PHP документацията +----------------------------- - `[php:substr]` -> [php:substr] -Изходен код .[#toc-source-code] -=============================== +Изходен код +=========== -Блокът с код започва с <code>```lang</code> и завършва с <code>```</code> Поддържаните езици са `php`, `latte`, `neon`, `html`, `css`, `js` и `sql`. Винаги използвайте табулатори за отстъпите. +Блокът с код започва с <code>```lang</code> и завършва с <code>```</code>. Поддържаните езици са `php`, `latte`, `neon`, `html`, `css`, `js` и `sql`. За отстъп винаги използвайте табулатори. ``` ```php @@ -63,7 +63,7 @@ ``` ``` -Можете също така да посочите името на файла като <code>```php .{file: ArrayTest.php}</code> и блокът с код ще бъде визуализиран по този начин: +Можете също така да посочите името на файла като <code>```php .{file: ArrayTest.php}</code> и блокът с код ще се рендира по този начин: ```php .{file: ArrayTest.php} public function renderPage($id) @@ -72,71 +72,71 @@ public function renderPage($id) ``` -Заглавия .[#toc-headings] -========================= +Заглавия +======== -Горното заглавие (името на страницата) се подчертава със звездички (`*`). For normal headings use equal signs (`=`) and then hyphens (`-`). +Най-високото заглавие (т.е. името на страницата) подчертайте със звездички (`***`). За разделяне на секции използвайте знаци за равенство (`===`). Заглавията от по-ниско ниво подчертавайте със знаци за равенство (`===`) и след това с тирета (`---`): ``` -MVC Applications & Presenters -***************************** +MVC Приложения & презентери +*************************** ... -Link Creation -============= +Създаване на връзки +=================== ... -Links in Templates +Връзки в шаблоните ------------------ ... ``` -Кутии и стилове .[#toc-boxes-and-styles] -======================================== +Рамки и стилове +=============== -Водещ параграф, маркиран с клас `.[perex]` .[perex] +Perex обозначаваме с клас `.[perex]` .[perex] -Бележки, отбелязани с клас `.[note]` .[note] +Бележка обозначаваме с клас `.[note]` .[note] -Съвет, отбелязан с клас `.[tip]` .[tip] +Съвет обозначаваме с клас `.[tip]` .[tip] -Предупреждение, отбелязано с клас `.[caution]` .[caution] +Предупреждение обозначаваме с клас `.[caution]` .[caution] -Силно предупреждение, отбелязано с клас `.[warning]` .[warning] +По-силно предупреждение обозначаваме с клас `.[warning]` .[warning] -Номер на версията `.{data-version:2.4.10}` .{data-version:2.4.10} +Номер на версия `.{data-version:2.4.10}` .{data-version:2.4.10} -Класовете трябва да се изписват преди съответния ред: +Записвайте класовете преди реда: ``` -.[note] -This is a note. +.[perex] +Това е perex. ``` -Моля, обърнете внимание, че полета като `.[tip]` привличат вниманието и затова трябва да се използват за подчертаване, а не за по-малко важна информация. +Моля, имайте предвид, че рамки като `.[tip]` "привличат" очите, следователно се използват за подчертаване, а не за по-малко съществена информация. Затова използвайте ги максимално пестеливо. -Съдържание .[#toc-table-of-contents] -==================================== +Съдържание +========== -Съдържанието (връзките в страничната лента) се генерира автоматично, когато страницата е по-дълга от 4 000 байта. Това поведение по подразбиране може да бъде променено с `{{toc}}` [мета тага |#meta-tags]. Текстът за TOC се взема по подразбиране от заглавието, но е възможно да се използва различен текст с помощта на `.{toc}` модификатор. Това е особено полезно за по-дълги заглавия. +Съдържанието (връзките в дясното меню) се генерира автоматично за всички страници, чийто размер надхвърля 4 000 байта, като това поведение по подразбиране може да бъде променено с помощта на [мета таг |#Мета тагове] `{{toc}}`. Текстът, формиращ съдържанието, се взема стандартно директно от текста на заглавията, но с помощта на модификатора `.{toc}` е възможно да се покаже в съдържанието друг текст, което е полезно главно за по-дълги заглавия. ``` -Long and Intelligent Heading .{toc: A Different Text for TOC} -============================================================= +Дълго и интелигентно заглавие .{toc: Произволен друг текст, показан в съдържанието} +=================================================================================== ``` -Метаетикети .[#toc-meta-tags] -============================= +Мета тагове +=========== -- задаване на собствено заглавие на страницата (в `<title>` и трохи) `{{title: Another name}}` -- пренасочване `{{redirect: pla:cs}}` - показване на [връзки |#links] -- налагане `{{toc}}` или деактивиране `{{toc: no}}` таблица на съдържанието +- настройка на собствено име на страницата (в `<title>` и навигацията тип "хлебни трохи") `{{title: Друго име}}` +- пренасочване `{{redirect: pla:cs}}` - виж [#връзки] +- принудително `{{toc}}` или забрана `{{toc: no}}` на автоматичното съдържание (кутийка с връзки към отделните заглавия) {{priority: -1}} diff --git a/contributing/cs/code.texy b/contributing/cs/code.texy index 67d5b97ead..39dba729b8 100644 --- a/contributing/cs/code.texy +++ b/contributing/cs/code.texy @@ -28,8 +28,8 @@ Nyní můžete provést své vlastní úpravy kódu: 1) naprogramujte požadované změny a nezapomeňte na testy 2) ujistěte se, že testy proběhnou úspěšně, pomocí `composer tester` -3) zkontrolujte, zda kód splňuje [kódovací standard|#Coding Standards] -4) změny uložte (commitněte) s popisem v [tomto formátu|#Popis komitu] +3) zkontrolujte, zda kód splňuje [kódovací standard |#Coding Standards] +4) změny uložte (commitněte) s popisem v [tomto formátu |#Popis komitu] Můžete vytvořit několik commitů, jeden pro každý logický krok. Každý commit by měl být smysluplný samostatně. @@ -41,7 +41,7 @@ Jakmile budete se změnami spokojeni, můžete je odeslat: 1) odešlete (pushněte) změny na GitHub do vašeho forku 2) odtud je odešlete do Nette repositáře vytvořením [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) -3) uveďte v popisu [dostatek informací|#popis pull requestu] +3) uveďte v popisu [dostatek informací |#Popis pull requestu] Zapracování připomínek diff --git a/contributing/cs/coding-standard.texy b/contributing/cs/coding-standard.texy index d3c8e37eff..aaef617655 100644 --- a/contributing/cs/coding-standard.texy +++ b/contributing/cs/coding-standard.texy @@ -1,8 +1,8 @@ Kódovací standard ***************** -Tento dokument popisuje pravidla a doporučení pro vývoj Nette. Při přispívání kódu do Nette je musíte dodržovat. Nejjednodušší způsob, jak to udělat, je napodobit existující kód. -Jde o to, aby veškerý kód vypadal, jako by ho napsal jeden člověk . +.[perex] +Tento dokument popisuje pravidla a doporučení pro vývoj Nette. Při přispívání kódu do Nette je musíte dodržovat. Nejjednodušší způsob, jak to udělat, je napodobit existující kód. Jde o to, aby veškerý kód vypadal, jako by ho napsal jeden člověk . Nette Coding Standard odpovídá [PSR-12 Extended Coding Style |https://www.php-fig.org/psr/psr-12/] se dvěma hlavními výjimkami: pro odsazení používá [#tabulátory místo mezer] a pro [konstanty tříd používá PascalCase|https://blog.nette.org/cs/za-mene-kriku-v-kodu]. @@ -38,7 +38,7 @@ Nette Coding Standard odpovídá PSR-12 (resp. PER Coding Style), v některých - arrow funkce se píší bez mezery před závorkou, tj. `fn($a) => $b` - nevyžaduje se prázdný řádek mezi různými typy `use` import statements -- návratový typ funkce/metody a úvodní složená závorka by měly být umístěny na samostatných řádcích pro lepší čitelnost: +- návratový typ funkce/metody a úvodní složená závorka jsou vždy na samostatných řádcích: ```php public function find( @@ -50,6 +50,10 @@ Nette Coding Standard odpovídá PSR-12 (resp. PER Coding Style), v některých } ``` +Úvodní složená závorka na samostatném řádku je důležitá pro vizuální oddělení signatury funkce/metody od těla. Pokud je signatura na jednom řádku, je oddělení zřetelné (obrázek vlevo), pokud je na více řádcích, v PSR signatury a těla splývají (uprostřed), zatímco v Nette standardu jsou nadále oddělené (vpravo): + +[* new-line-after.webp *] + Bloky dokumentace (phpDoc) ========================== @@ -105,6 +109,23 @@ public function find(string $dir, array $options): array ``` +Globální funkce a konstanty +=========================== + +Globální funkce a konstanty se píší bez úvodního zpětného lomítka, tedy `count($arr)` nikoliv `\count($arr)`. Pro funkce, které umí PHP optimalizovat, uvedeme na začátku souboru `use function`, aby je kompilátor mohl přeložit efektivněji. Jedná se zejména o funkce jako `count`, `strlen`, `is_array`, `is_string`, `is_scalar`, `sprintf` aj. Funkce se uvádějí na jednom řádku, aby úvodní blok importů nebyl zbytečně velký: + +```php +use Nette; +use function count, is_array, is_scalar, sprintf; +``` + +Výjimečně takto uvádíme i konstanty, u kterých může znalost hodnoty posloužit kompilátoru: + +```php +use const PHP_OS_FAMILY; +``` + + Tabulátory místo mezer ====================== @@ -118,8 +139,7 @@ Tabulátory mají oproti mezerám několik výhod: Používáním tabulátorů v našich projektech umožňujeme přizpůsobení šířky, které se může většině lidí zdát jako zbytečnost, ale pro lidi se zrakovým postižením je nezbytné. -Pro nevidomé programátory, kteří používají braillské displeje, představuje každá mezera jednu braillskou buňkou. Pokud je tedy výchozí odsazení 4 mezery, odsazení 3. úrovně plýtvá 12 cennými braillskými buňkami ještě před začátkem kódu. -Na 40buňkovém displeji, který se u notebooků používá nejčastěji, je to více než čtvrtina dostupných buněk, které jsou promrhány bez jakékoliv informace. +Pro nevidomé programátory, kteří používají braillské displeje, představuje každá mezera jednu braillskou buňkou. Pokud je tedy výchozí odsazení 4 mezery, odsazení 3. úrovně plýtvá 12 cennými braillskými buňkami ještě před začátkem kódu. Na 40buňkovém displeji, který se u notebooků používá nejčastěji, je to více než čtvrtina dostupných buněk, které jsou promrhány bez jakékoliv informace. {{priority: -1}} diff --git a/contributing/cs/documentation.texy b/contributing/cs/documentation.texy index 2e4da280da..c02587a359 100644 --- a/contributing/cs/documentation.texy +++ b/contributing/cs/documentation.texy @@ -39,8 +39,7 @@ Nejjednodušší způsob, jak provést drobnou změnu v dokumentaci, je využít - *Ukaž na GitHubu* otevře zdrojovou podobu dané stránky na GitHubu. Poté stačí stisknout tlačítko `E` a můžete začít editovat (je nutné být na GitHubu přihlášený) - *Otevři náhled* otevře editor, kde rovnou vidíte i výslednou vizuální podobu -Protože [editor s náhledem |https://editor.nette.org/] nemá možnost ukládat změny přímo na GitHub, je nutné po dokončení úprav zdrojový text zkopírovat do schránky (tlačítkem *Copy to clipboard*) a poté jej vložit do editoru na GitHubu. -Pod editačním polem je formulář pro odeslání. Zde nezapomeňte stručně shrnout a vysvětlit důvod vaší úpravy. Po odeslání vznikne tzv. pull request (PR), který je možné dále editovat. +Protože [editor s náhledem |https://editor.nette.org/] nemá možnost ukládat změny přímo na GitHub, je nutné po dokončení úprav zdrojový text zkopírovat do schránky (tlačítkem *Copy to clipboard*) a poté jej vložit do editoru na GitHubu. Pod editačním polem je formulář pro odeslání. Zde nezapomeňte stručně shrnout a vysvětlit důvod vaší úpravy. Po odeslání vznikne tzv. pull request (PR), který je možné dále editovat. Větší úpravy @@ -52,7 +51,7 @@ Dokumentaci upravujte tímto způsobem: 1) na GitHubu si vytvořte [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] repositáře [nette/docs |https://github.com/nette/docs] 2) tento repositář [naklonujete |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] na svůj počítač -3) poté v [příslušné větvi|#Struktura dokumentace] proveďte změny +3) poté v [příslušné větvi |#Struktura dokumentace] proveďte změny 4) zkontroluje přebytečné mezery v textu pomocí nástroje [Code-Checker |code-checker:] 4) změny uložte (commitněte) 6) pokud jste se změnami spokojeni, odešlete (pushněte) je na GitHub do vašeho forku diff --git a/contributing/cs/syntax.texy b/contributing/cs/syntax.texy index ad8f3ea4c1..039f8c32ac 100644 --- a/contributing/cs/syntax.texy +++ b/contributing/cs/syntax.texy @@ -1,7 +1,7 @@ Dokumentační syntax ******************* -Dokumentace používá Markdown & [Texy syntaxi |https://texy.info/cs/syntax] s některými rozšířeními. +Dokumentace používá Markdown & [Texy syntaxi |https://texy.nette.org/syntax] s některými rozšířeními. Odkazy diff --git a/contributing/de/@home.texy b/contributing/de/@home.texy index 1efe675d35..37bab1fa5d 100644 --- a/contributing/de/@home.texy +++ b/contributing/de/@home.texy @@ -1,17 +1,17 @@ -Werden Sie Contributor bei Nette -******************************** +Werden Sie ein Nette-Mitwirkender +********************************* .[perex] -Sehen Sie sich an, wie Sie sich an unserem Open-Source-Projekt beteiligen können. Erfahren Sie, wie Sie zum Quellcode und zur Dokumentation beitragen können, und werden Sie Teil des Netzwerks von Entwicklern, die sich für die Verbesserung von Nette einsetzen. +Erfahren Sie, wie Sie sich an unserem Open-Source-Projekt beteiligen können. Erlernen Sie die Verfahren zur Mitwirkung am Quellcode und an der Dokumentation und werden Sie Teil der Entwicklergemeinschaft, die aktiv an der Verbesserung von Nette mitwirkt. **Code** -- [Beitrag zum Code |code] -- [Kodierungsstandards |coding-standard] +- [Wie kann man zum Code beitragen? |code] +- [Codierungsstandard |coding-standard] **Dokumentation** -- [Beitrag zur Dokumentation |documentation] -- [Syntax der Dokumentation |syntax] +- [Wie kann man zur Dokumentation beitragen? |documentation] +- [Dokumentationssyntax |syntax] - "Vorschau-Editor":https://editor.nette.org diff --git a/contributing/de/@left-menu.texy b/contributing/de/@left-menu.texy index 84c4e448a9..a807ebcaf2 100644 --- a/contributing/de/@left-menu.texy +++ b/contributing/de/@left-menu.texy @@ -1,10 +1,10 @@ Code **** -- [Zum Code beitragen |code] -- [Kodierungsstandards |coding-standard] +- [Wie man zum Code beiträgt? |code] +- [Codierungsstandard |coding-standard] Dokumentation ************* -- [Beitrag zur Dokumentation |documentation] -- [Syntax der Dokumentation |syntax] +- [Wie man zur Dokumentation beiträgt? |documentation] +- [Dokumentationssyntax |syntax] - "Vorschau-Editor":https://editor.nette.org diff --git a/contributing/de/code.texy b/contributing/de/code.texy index 820a1d7d88..b26457ddb5 100644 --- a/contributing/de/code.texy +++ b/contributing/de/code.texy @@ -1,117 +1,117 @@ -Zum Code beitragen -****************** +Wie man zum Code beiträgt +************************* .[perex] -Planen Sie, zum Nette Framework beizutragen, und müssen Sie sich mit den Regeln und Verfahren vertraut machen? Dieser Leitfaden für Anfänger führt Sie durch die Schritte, um effektiv zum Code beizutragen, mit Repositories zu arbeiten und Änderungen zu implementieren. +Sie möchten zum Nette Framework beitragen und benötigen eine Orientierung über die Regeln und Verfahren? Dieser Leitfaden für Anfänger zeigt Ihnen Schritt für Schritt, wie Sie effektiv zum Code beitragen, mit Repositories arbeiten und Änderungen implementieren können. -Verfahren .[#toc-procedure] -=========================== +Vorgehensweise +============== -Um zum Code beizutragen, ist es wichtig, ein Konto auf [GitHub |https://github.com] zu haben und mit den Grundlagen der Arbeit mit dem Versionskontrollsystem Git vertraut zu sein. Wenn Sie mit Git nicht vertraut sind, können Sie sich den [Leitfaden "Git - die einfache Anleitung |https://rogerdudler.github.io/git-guide/] " ansehen und die Verwendung eines der vielen [grafischen Clients |https://git-scm.com/downloads/guis] in Betracht ziehen. +Um zum Code beizutragen, ist es unerlässlich, ein Konto auf [GitHub|https://github.com] zu haben und mit den Grundlagen der Arbeit mit dem Versionskontrollsystem Git vertraut zu sein. Wenn Sie nicht mit Git vertraut sind, können Sie sich den Leitfaden [git - the simple guide |https://rogerdudler.github.io/git-guide/] ansehen und gegebenenfalls einen der vielen [grafischen Clients |https://git-scm.com/downloads/guis] nutzen. -Vorbereiten der Umgebung und des Repositorys .[#toc-preparing-the-environment-and-repository] ---------------------------------------------------------------------------------------------- +Vorbereitung der Umgebung und des Repositorys +--------------------------------------------- -1) Erstellen Sie auf GitHub einen [Fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] des [Paket-Repositorys |www:packages], das Sie ändern möchten. -2) [Klonen |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] Sie dieses Repository auf Ihren Computer -3) Installieren Sie die Abhängigkeiten, einschließlich [Nette Tester |tester:], mit dem Befehl `composer install` -4) Überprüfen Sie, ob die Tests funktionieren, indem Sie den Befehl `composer tester` -5) Erstellen Sie eine [neue Verzweigung |#New Branch] auf der Grundlage der letzten veröffentlichten Version +1) Erstellen Sie auf GitHub einen [Fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] des Repositorys des [Pakets |www:packages], das Sie bearbeiten möchten. +2) [Klonen |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] Sie dieses Repository auf Ihren Computer. +3) Installieren Sie die Abhängigkeiten, einschließlich des [Nette Testers |tester:], mit dem Befehl `composer install`. +4) Überprüfen Sie, ob die Tests funktionieren, indem Sie `composer tester` ausführen. +5) Erstellen Sie einen [neuen Branch |#Neuer Branch] basierend auf der letzten veröffentlichten Version. -Eigene Änderungen implementieren .[#toc-implementing-your-own-changes] ----------------------------------------------------------------------- +Implementierung eigener Änderungen +---------------------------------- -Jetzt können Sie Ihre eigenen Code-Anpassungen vornehmen: +Nun können Sie Ihre eigenen Codeänderungen vornehmen: -1) Implementieren Sie die gewünschten Änderungen und vergessen Sie dabei nicht die Tests -2) Stellen Sie sicher, dass die Tests erfolgreich laufen. `composer tester` -3) Prüfen Sie, ob der Code den [Kodierungsstandards |#coding standards]entspricht -4) Speichern (Commit) Sie die Änderungen mit einer Beschreibung in [diesem Format |#Commit Description] +1) Programmieren Sie die gewünschten Änderungen und vergessen Sie nicht die Tests. +2) Stellen Sie sicher, dass die Tests erfolgreich verlaufen, indem Sie `composer tester` verwenden. +3) Überprüfen Sie, ob der Code dem [Codierungsstandard |#Coding Standards] entspricht. +4) Speichern (committen) Sie die Änderungen mit einer Beschreibung in [diesem Format |#Commit-Beschreibung]. -Sie können mehrere Übertragungen erstellen, eine für jeden logischen Schritt. Jeder Commit sollte für sich genommen sinnvoll sein. +Sie können mehrere Commits erstellen, einen für jeden logischen Schritt. Jeder Commit sollte für sich genommen sinnvoll sein. -Einreichen von Änderungen .[#toc-submitting-changes] ----------------------------------------------------- +Senden der Änderungen +--------------------- -Wenn Sie mit den Änderungen zufrieden sind, können Sie sie übermitteln: +Sobald Sie mit den Änderungen zufrieden sind, können Sie sie senden: -1) Verschieben Sie die Änderungen auf GitHub in Ihren Fork -2) Übermitteln Sie sie von dort aus an das Nette-Repository, indem Sie einen [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) erstellen -3) Geben Sie [ausreichende Informationen |#pull request description] in der Beschreibung an +1) Senden (pushen) Sie die Änderungen auf GitHub in Ihren Fork. +2) Senden Sie sie von dort an das Nette-Repository, indem Sie einen [Pull Request|https://help.github.com/articles/creating-a-pull-request] (PR) erstellen. +3) Geben Sie in der Beschreibung [ausreichend Informationen |#Beschreibung des Pull Requests] an. -Feedback einbeziehen .[#toc-incorporating-feedback] ---------------------------------------------------- +Einarbeitung von Anmerkungen +---------------------------- -Ihre Übertragungen sind nun für andere sichtbar. Es ist üblich, dass Sie Kommentare mit Vorschlägen erhalten: +Ihre Commits werden nun auch von anderen gesehen. Es ist üblich, dass Sie Kommentare mit Anmerkungen erhalten: -1) Behalten Sie die vorgeschlagenen Änderungen im Auge -2) Fügen Sie sie als neue Commits ein oder [fügen Sie sie mit früheren zusammen |https://help.github.com/en/github/using-git/about-git-rebase] -3) Senden Sie die Commits erneut an GitHub, und sie erscheinen automatisch in der Pull-Anfrage +1) Verfolgen Sie die vorgeschlagenen Änderungen. +2) Arbeiten Sie sie als neue Commits ein oder [fügen Sie sie mit den vorherigen zusammen |https://help.github.com/en/github/using-git/about-git-rebase]. +3) Senden Sie die Commits erneut auf GitHub, und sie erscheinen automatisch im Pull Request. -Erstellen Sie niemals einen neuen Pull Request, um einen bestehenden zu ändern. +Erstellen Sie niemals einen neuen Pull Request, um einen bestehenden zu bearbeiten. -Dokumentation .[#toc-documentation] ------------------------------------ +Dokumentation +------------- -Wenn Sie eine Funktion geändert oder eine neue hinzugefügt haben, vergessen Sie nicht, [diese auch in die Dokumentation aufzunehmen |documentation]. +Wenn Sie die Funktionalität geändert oder eine neue hinzugefügt haben, vergessen Sie nicht, sie auch [zur Dokumentation hinzuzufügen |documentation]. -Neuer Zweig .[#toc-new-branch] -============================== +Neuer Branch +============ -Wenn möglich, nehmen Sie Änderungen an der letzten veröffentlichten Version vor, d.h. am letzten Tag im Zweig. Für das Tag v3.2.1 erstellen Sie einen Zweig mit diesem Befehl: +Wenn möglich, führen Sie Änderungen gegenüber der letzten veröffentlichten Version durch, d.h. dem letzten Tag in diesem Branch. Für den Tag `v3.2.1` erstellen Sie einen Branch mit diesem Befehl: ```shell -git checkout -b new_branch_name v3.2.1 +git checkout -b neuer_branch_name v3.2.1 ``` -Kodierungsstandards .[#toc-coding-standards] -============================================ +Coding Standards +================ -Ihr Code muss den im Nette Framework verwendeten [Kodierungsstandards |coding standard] entsprechen. Es gibt ein automatisches Tool zur Überprüfung und Korrektur des Codes. Sie können es **global** über Composer in einen Ordner Ihrer Wahl installieren: +Ihr Code muss dem [Codierungsstandard |Coding Standard] entsprechen, der im Nette Framework verwendet wird. Zur Überprüfung und Korrektur des Codes steht ein automatisches Werkzeug zur Verfügung. Es kann über Composer **global** in einem von Ihnen gewählten Ordner installiert werden: ```shell -composer create-project nette/coding-standard /path/to/nette-coding-standard +composer create-project nette/coding-standard /pfad/zu/nette-coding-standard ``` -Nun sollten Sie in der Lage sein, das Tool im Terminal auszuführen. Der erste Befehl überprüft und der zweite korrigiert den Code in den Ordnern `src` und `tests` im aktuellen Verzeichnis: +Nun sollten Sie das Werkzeug im Terminal starten können. Mit dem ersten Befehl überprüfen Sie und mit dem zweiten korrigieren Sie den Code in den Ordnern `src` und `tests` im aktuellen Verzeichnis: ```shell -/path/to/nette-coding-standard/ecs check -/path/to/nette-coding-standard/ecs check --fix +/pfad/zu/nette-coding-standard/ecs check +/pfad/zu/nette-coding-standard/ecs check --fix ``` -Commit Beschreibung .[#toc-commit-description] -============================================== +Commit-Beschreibung +=================== -In Nette haben Commit-Themen das folgende Format: `Presenter: fixed AJAX detection [Closes #69]` +In Nette haben die Betreffzeilen von Commits das Format: `Presenter: fixed AJAX detection [Closes #69]` -- Bereich, gefolgt von einem Doppelpunkt -- Zweck des Commits in der Vergangenheitsform; wenn möglich, beginnen Sie mit Worten wie: added, fixed, refactored, changed, removed -- wenn der Commit die Abwärtskompatibilität bricht, fügen Sie "BC break" hinzu -- jede Verbindung zum Issue Tracker, wie `(#123)` oder `[Closes #69]` -- nach dem Betreff kann eine Leerzeile folgen, gefolgt von einer detaillierteren Beschreibung, z.B. mit Links zum Forum +- Bereich gefolgt von einem Doppelpunkt +- Zweck des Commits in der Vergangenheitsform; beginnen Sie nach Möglichkeit mit einem der folgenden Worte: `added` (neue Funktion hinzugefügt), `fixed` (Fehlerbehebung), `refactored` (Codeänderung ohne Verhaltensänderung), `changed`, `removed` +- Wenn der Commit die Abwärtskompatibilität bricht, fügen Sie `BC break` hinzu +- Eine optionale Verknüpfung zum Issue Tracker wie `(#123)` oder `[Closes #69]` +- Nach dem Betreff kann eine Leerzeile folgen und dann eine detailliertere Beschreibung, einschließlich z.B. Links zum Forum -Pull Request Beschreibung .[#toc-pull-request-description] -========================================================== +Beschreibung des Pull Requests +============================== -Wenn Sie eine Pull-Anfrage erstellen, können Sie über die GitHub-Schnittstelle einen Titel und eine Beschreibung eingeben. Geben Sie einen prägnanten Titel an und fügen Sie in der Beschreibung so viele Informationen wie möglich über die Gründe für Ihre Änderung ein. +Beim Erstellen eines Pull Requests ermöglicht Ihnen die GitHub-Oberfläche die Eingabe eines Titels und einer Beschreibung. Geben Sie einen aussagekräftigen Titel an und liefern Sie in der Beschreibung so viele Informationen wie möglich über die Gründe für Ihre Änderung. -Geben Sie außerdem in der Kopfzeile an, ob es sich um eine neue Funktion oder eine Fehlerbehebung handelt und ob sie zu Problemen mit der Abwärtskompatibilität führen kann (BC-Break). Falls es ein verwandtes Problem gibt, verlinken Sie es, damit es bei Genehmigung des Pull Requests geschlossen wird. +Es wird auch eine Kopfzeile angezeigt, in der Sie angeben, ob es sich um eine neue Funktion oder eine Fehlerbehebung handelt und ob die Abwärtskompatibilität beeinträchtigt werden könnte (BC break). Wenn ein zugehöriges Problem (Issue) vorhanden ist, verweisen Sie darauf, damit es nach Genehmigung des Pull Requests geschlossen wird. ``` - bug fix / new feature? <!-- #issue numbers, if any --> - BC break? yes/no -- doc PR: nette/docs#? <!-- highly welcome, see https://nette.org/en/writing --> +- doc PR: nette/docs#? <!-- highly welcome, see https://nette.org/de/writing --> ``` diff --git a/contributing/de/coding-standard.texy b/contributing/de/coding-standard.texy index 218b0f5c4d..6109abb5c7 100644 --- a/contributing/de/coding-standard.texy +++ b/contributing/de/coding-standard.texy @@ -1,44 +1,44 @@ -Kodierungsstandard +Codierungsstandard ****************** -Dieses Dokument beschreibt Regeln und Empfehlungen für die Entwicklung von Nette. Wenn Sie Code zu Nette beisteuern, müssen Sie diese befolgen. Der einfachste Weg, dies zu tun, ist, den vorhandenen Code zu imitieren. -Die Idee ist, den gesamten Code so aussehen zu lassen, als wäre er von einer Person geschrieben worden. .[perex] +.[perex] +Dieses Dokument beschreibt die Regeln und Empfehlungen für die Entwicklung von Nette. Wenn Sie Code zu Nette beitragen, müssen Sie diese einhalten. Der einfachste Weg, dies zu tun, ist, den vorhandenen Code nachzuahmen. Ziel ist es, dass der gesamte Code so aussieht, als wäre er von einer einzigen Person geschrieben worden. -Der Nette Coding Standard entspricht dem [PSR-12 Extended Coding Style |https://www.php-fig.org/psr/psr-12/] mit zwei wesentlichen Ausnahmen: Er verwendet [Tabulatoren anstelle von Leerzeichen |#tabs instead of spaces] für die Einrückung und [PascalCase für Klassenkonstanten |https://blog.nette.org/de/fuer-weniger-geschrei-im-code]. +Der Nette Codierungsstandard entspricht dem [PSR-12 Extended Coding Style |https://www.php-fig.org/psr/psr-12/] mit zwei Hauptausnahmen: Er verwendet [#Tabulatoren statt Leerzeichen] für die Einrückung und [PascalCase für Klassenkonstanten|https://blog.nette.org/de/fuer-weniger-geschrei-im-code]. -Allgemeine Regeln .[#toc-general-rules] -======================================= +Allgemeine Regeln +================= -- Jede PHP-Datei muss enthalten `declare(strict_types=1)` -- Zur besseren Lesbarkeit werden die Methoden durch zwei Leerzeilen getrennt. -- Der Grund für die Verwendung des shut-up-Operators muss dokumentiert werden: `@mkdir($dir); // @ - directory may exist` -- Wenn ein schwach typisierter Vergleichsoperator verwendet wird (z. B. `==`, `!=`, ...), muss die Absicht dokumentiert werden: `// == to accept null` -- Sie können mehrere Ausnahmen in eine Datei schreiben `exceptions.php` -- Die Sichtbarkeit von Methoden wird bei Schnittstellen nicht angegeben, da sie immer öffentlich sind. -- Für jede Eigenschaft, jeden Rückgabewert und jeden Parameter muss ein Typ angegeben werden. Bei endgültigen Konstanten hingegen wird der Typ nicht angegeben, da er offensichtlich ist. -- Einfache Anführungszeichen sollten zur Abgrenzung der Zeichenkette verwendet werden, es sei denn, das Literal selbst enthält Apostrophe. +- Jede PHP-Datei muss `declare(strict_types=1)` enthalten. +- Zwei Leerzeilen werden verwendet, um Methoden zur besseren Lesbarkeit voneinander zu trennen. +- Der Grund für die Verwendung des Shut-up-Operators (`@`) muss dokumentiert werden: `@mkdir($dir); // @ - Verzeichnis kann bereits existieren`. +- Wenn ein schwach typisierter Vergleichsoperator verwendet wird (d. h. `==`, `!=`, ...), muss die Absicht dokumentiert werden: `// == null akzeptieren` +- In eine einzige Datei `exceptions.php` können mehrere Ausnahmeklassen geschrieben werden. +- Bei Schnittstellen wird die Sichtbarkeit von Methoden nicht angegeben, da sie immer `public` sind. +- Jede Eigenschaft, jeder Rückgabewert und jeder Parameter muss einen Typ angegeben haben. Bei `final` Konstanten geben wir den Typ jedoch nie an, da er offensichtlich ist. +- Zum Begrenzen von Zeichenketten sollten einfache Anführungszeichen verwendet werden, es sei denn, das Literal selbst enthält Apostrophe. -Benennungskonventionen .[#toc-naming-conventions] -================================================= +Benennungskonventionen +====================== -- Vermeiden Sie die Verwendung von Abkürzungen, es sei denn, der vollständige Name ist zu lang. -- Verwenden Sie Großbuchstaben für zweibuchstabige Abkürzungen und Klein-/Kleinschreibung für längere Abkürzungen. -- Verwenden Sie ein Substantiv oder eine Substantivphrase als Klassenname. -- Klassennamen müssen nicht nur die Besonderheit (`Array`), sondern auch die Allgemeinheit (`ArrayIterator`) enthalten. Die Ausnahme sind PHP-Attribute. -- "Klassenkonstanten und Enums sollten PascalCaps verwenden":https://blog.nette.org/de/fuer-weniger-geschrei-im-code. -- "Schnittstellen und abstrakte Klassen sollten keine Präfixe oder Postfixe enthalten":https://blog.nette.org/de/praefixe-und-suffixe-gehoeren-nicht-in-schnittstellennamen wie `Abstract`, `Interface` oder `I`. +- Verwenden Sie keine Abkürzungen, es sei denn, der vollständige Name ist zu lang. +- Verwenden Sie bei zweibuchstabigen Abkürzungen Großbuchstaben (z.B. `IO`), bei längeren Abkürzungen PascalCase oder camelCase (z.B. `XmlRpc`). +- Verwenden Sie für den Klassennamen ein Substantiv oder eine Wortgruppe. +- Klassennamen müssen nicht nur die Spezifität (`Array`), sondern auch die Allgemeinheit (`ArrayIterator`) enthalten. Ausnahmen sind PHP-Attribute. +- [Klassenkonstanten und Enums sollten PascalCase verwenden |https://blog.nette.org/de/fuer-weniger-geschrei-im-code]. +- [Schnittstellen und abstrakte Klassen sollten keine Präfixe oder Suffixe enthalten |https://blog.nette.org/de/praefixe-und-suffixe-gehoeren-nicht-in-interface-namen] wie `Abstract`, `Interface` oder `I`. -Wrapping und Klammern .[#toc-wrapping-and-braces] -================================================= +Umbrüche und Klammern +===================== -Nette Coding Standard entspricht der PSR-12 (oder PER Coding Style), in einigen Punkten spezifiziert er sie weiter oder modifiziert sie: +Der Nette Codierungsstandard entspricht PSR-12 (bzw. PER Coding Style), ergänzt oder modifiziert ihn jedoch in einigen Punkten: -- Pfeilfunktionen werden ohne ein Leerzeichen vor der Klammer geschrieben, d.h. `fn($a) => $b` -- zwischen verschiedenen Arten von `use` import-Anweisungen ist keine Leerzeile erforderlich -- der Rückgabetyp der Funktion/Methode und die öffnende Klammer sollten zur besseren Lesbarkeit in getrennten Zeilen stehen: +- Pfeilfunktionen werden ohne Leerzeichen vor der öffnenden Klammer geschrieben, d.h. `fn($a) => $b` +- Es ist keine Leerzeile zwischen verschiedenen Typen von `use`-Importanweisungen erforderlich. +- Der Rückgabetyp einer Funktion/Methode und die öffnende geschweifte Klammer stehen immer auf separaten Zeilen: ```php public function find( @@ -46,28 +46,32 @@ Nette Coding Standard entspricht der PSR-12 (oder PER Coding Style), in einigen array $options, ): array { - // method body + // Methodenkörper } ``` +Die öffnende geschweifte Klammer auf einer separaten Zeile ist wichtig für die visuelle Trennung der Signatur der Funktion/Methode vom Körper. Wenn die Signatur auf einer Zeile steht, ist die Trennung deutlich (Bild links). Wenn sie auf mehreren Zeilen steht, verschmelzen in PSR Signatur und Körper (Mitte), während sie im Nette-Standard weiterhin getrennt sind (rechts): -Dokumentationsblöcke (phpDoc) .[#toc-documentation-blocks-phpdoc] -================================================================= +[* new-line-after.webp *] -Die wichtigste Regel: Duplizieren Sie niemals Signaturinformationen wie Parametertyp oder Rückgabetyp ohne zusätzlichen Wert. -Dokumentationsblock für die Klassendefinition: +Dokumentationsblöcke (phpDoc) +============================= -- Beginnt mit einer Klassenbeschreibung. -- Es folgt eine leere Zeile. -- Es folgen die Anmerkungen `@property` (oder `@property-read`, `@property-write`), eine nach der anderen. Die Syntax lautet: annotation, space, type, space, $name. -- Es folgen die `@method` Anmerkungen, eine nach der anderen. Die Syntax lautet: annotation, space, return type, space, name(type $param, ...). -- Die `@author` -Anmerkung wird weggelassen. Die Urheberschaft wird in einer Quellcode-Historie festgehalten. -- Die Anmerkungen `@internal` oder `@deprecated` können verwendet werden. +Hauptregel: Duplizieren Sie niemals Informationen aus der Signatur (wie Parametertyp oder Rückgabetyp) im Docblock, es sei denn, Sie fügen zusätzliche Informationen hinzu. + +Dokumentationsblock für eine Klassendefinition: + +- Beginnt mit der Beschreibung der Klasse. +- Gefolgt von einer Leerzeile. +- Gefolgt von `@property`-Annotationen (oder `@property-read`, `@property-write`), eine nach der anderen. Syntax: Annotation, Leerzeichen, Typ, Leerzeichen, `$name`. +- Gefolgt von `@method`-Annotationen, eine nach der anderen. Syntax: Annotation, Leerzeichen, Rückgabetyp, Leerzeichen, `methodName(Typ $param, ...)`. +- Die `@author`-Annotation wird weggelassen. Die Autorschaft wird in der Quellcode-Historie gespeichert. +- Die Annotationen `@internal` oder `@deprecated` können verwendet werden. ```php /** - * MIME message part. + * Repräsentiert einen Teil einer MIME-Nachricht. * * @property string $encoding * @property-read array $headers @@ -76,27 +80,27 @@ Dokumentationsblock für die Klassendefinition: */ ``` -Der Dokumentationsblock für eine Eigenschaft, die nur den Vermerk `@var` enthält, sollte aus einer einzigen Zeile bestehen: +Ein Dokumentationsblock für eine Eigenschaft, der nur die Annotation `@var` enthält, sollte einzeilig sein: ```php /** @var string[] */ private array $name; ``` -Dokumentationsblock für Methodendefinition: +Dokumentationsblock für eine Methodendefinition: -- Beginnt mit einer kurzen Methodenbeschreibung. -- Keine Leerzeile. -- Die `@param` Annotationen, eine nach der anderen. -- Die `@return` -Anmerkung. -- Die `@throws` -Anmerkungen, eine nach der anderen. -- Die Anmerkungen `@internal` oder `@deprecated` können verwendet werden. +- Beginnt mit einer kurzen Beschreibung der Methode. +- Keine Leerzeile danach. +- `@param`-Annotationen, jede in einer eigenen Zeile. +- `@return`-Annotation. +- `@throws`-Annotationen, eine nach der anderen. +- Die Annotationen `@internal` oder `@deprecated` können verwendet werden. -Nach jeder Anmerkung folgt ein Leerzeichen, mit Ausnahme der Anmerkung `@param`, die zur besseren Lesbarkeit mit zwei Leerzeichen versehen ist. +Auf jede Annotation folgt ein Leerzeichen, mit Ausnahme von `@param`, auf das zur besseren Lesbarkeit zwei Leerzeichen folgen. ```php /** - * Finds a file in directory. + * Findet eine Datei in einem Verzeichnis. * @param string[] $options * @return string[] * @throws DirectoryNotFoundException @@ -105,21 +109,20 @@ public function find(string $dir, array $options): array ``` -Tabulatoren anstelle von Leerzeichen .[#toc-tabs-instead-of-spaces] -=================================================================== +Tabulatoren statt Leerzeichen +============================= -Tabulatoren haben mehrere Vorteile gegenüber Leerzeichen: +Tabulatoren haben gegenüber Leerzeichen mehrere Vorteile: -- die Größe der Einrückung ist in Editoren und im "Web anpassbar":https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size -- sie zwingen dem Code nicht die vom Benutzer bevorzugte Einrückungsgröße auf, so dass der Code besser portabel ist -- man kann sie mit einem Tastendruck eingeben (überall, nicht nur in Editoren, die Tabulatoren in Leerzeichen umwandeln) -- die Einrückung ist ihr Zweck -- die Bedürfnisse von sehbehinderten und blinden Kollegen zu respektieren +- Die Größe der Einrückung kann in Editoren und im [Web |https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size] angepasst werden. +- Sie zwingen dem Code nicht die vom Benutzer bevorzugte Einrückungsgröße auf, sodass der Code besser portierbar ist. +- Sie können mit einem einzigen Tastendruck geschrieben werden (überall, nicht nur in Editoren, die Tabulatoren in Leerzeichen umwandeln). +- Einrückung ist ihr Zweck. +- Sie respektieren die Bedürfnisse von sehbehinderten und blinden Kollegen. -Durch die Verwendung von Tabulatoren in unseren Projekten ermöglichen wir die Anpassung der Breite, was den meisten Menschen unnötig erscheinen mag, für Menschen mit Sehbehinderungen aber unerlässlich ist. +Durch die Verwendung von Tabulatoren in unseren Projekten ermöglichen wir die Anpassung der Breite, was den meisten Menschen als unnötig erscheinen mag, aber für Menschen mit Sehbehinderungen unerlässlich ist. -Für blinde Programmierer, die Braillezeilen verwenden, wird jedes Leerzeichen durch eine Braille-Zelle dargestellt und nimmt wertvollen Platz in Anspruch. Wenn also die Standardeinrückung 4 Leerzeichen beträgt, verschwendet eine Einrückung der dritten Ebene 12 Braille-Zellen, bevor der Code beginnt. -Auf einer 40-Zellen-Anzeige, die am häufigsten auf Laptops verwendet wird, ist das mehr als ein Viertel der verfügbaren Zellen, die ohne jegliche Information verschwendet werden. +Für blinde Programmierer, die Braillezeilen verwenden, stellt jedes Leerzeichen eine Braillezelle dar. Wenn also die Standardeinrückung 4 Leerzeichen beträgt, verschwendet die Einrückung der 3. Ebene 12 wertvolle Braillezellen, noch bevor der Code beginnt. Auf einem 40-Zellen-Display, das bei Laptops am häufigsten verwendet wird, ist das mehr als ein Viertel der verfügbaren Zellen, die ohne jegliche Information verschwendet werden. {{priority: -1}} diff --git a/contributing/de/documentation.texy b/contributing/de/documentation.texy index 4c925709ec..1461cd05c5 100644 --- a/contributing/de/documentation.texy +++ b/contributing/de/documentation.texy @@ -1,69 +1,68 @@ -Zur Dokumentation beitragen -*************************** +Wie man zur Dokumentation beiträgt +********************************** .[perex] -Die Mitarbeit an der Dokumentation ist eine der wertvollsten Aktivitäten, da sie anderen hilft, den Rahmen zu verstehen. +Beiträge zur Dokumentation sind eine der lohnendsten Tätigkeiten, da Sie anderen helfen, das Framework zu verstehen. -Wie schreibt man? .[#toc-how-to-write] --------------------------------------- +Wie schreibt man? +----------------- -Die Dokumentation richtet sich in erster Linie an Personen, die mit dem Thema noch nicht vertraut sind. Daher sollte sie mehrere wichtige Punkte erfüllen: +Die Dokumentation richtet sich vor allem an Personen, die sich mit dem Thema vertraut machen. Daher sollte sie mehrere wichtige Punkte erfüllen: -- Beginnen Sie mit einfachen und allgemeinen Themen. Gehen Sie am Ende zu fortgeschritteneren Themen über -- Versuchen Sie, das Thema so klar wie möglich zu erklären. Versuchen Sie zum Beispiel, das Thema zuerst einem Kollegen zu erklären. -- Geben Sie nur die Informationen, die der Benutzer für ein bestimmtes Thema tatsächlich benötigt -- Stellen Sie sicher, dass Ihre Informationen korrekt sind. Testen Sie jeden Code -- Seien Sie prägnant - kürzen Sie, was Sie schreiben, um die Hälfte. Und dann können Sie es gerne wiederholen -- Verwenden Sie Hervorhebungen sparsam, von fetten Schriftarten bis hin zu Rahmen wie `.[note]` -- Befolgen Sie den [Kodierungsstandard |Coding Standard] im Code +- Beginnen Sie mit dem Einfachen und Allgemeinen. Gehen Sie erst am Ende zu fortgeschritteneren Themen über. +- Versuchen Sie, die Sache so gut wie möglich zu erklären. Versuchen Sie zum Beispiel, das Thema zuerst einem Kollegen zu erklären. +- Geben Sie nur die Informationen an, die der Benutzer tatsächlich zum jeweiligen Thema wissen muss. +- Überprüfen Sie, ob Ihre Informationen tatsächlich wahr sind. Testen Sie jeden Code. +- Seien Sie prägnant - kürzen Sie, was Sie schreiben, auf die Hälfte. Und dann ruhig noch einmal. +- Sparen Sie mit Hervorhebungen aller Art, von Fettdruck bis hin zu Rahmen wie `.[note]`. +- Halten Sie sich in den Codebeispielen an den [Codierungsstandard |Coding Standard]. -Lernen Sie auch die [Syntax |syntax]. Für eine Vorschau des Artikels während des Schreibens, können Sie die [Vorschau-Editor |https://editor.nette.org/] verwenden. +Machen Sie sich auch mit der [Syntax |Syntax] vertraut. Für die Vorschau eines Artikels während des Schreibens können Sie den [Editor mit Vorschau |https://editor.nette.org/] verwenden. -Sprachmutationen .[#toc-language-mutations] -------------------------------------------- +Sprachversionen +--------------- -Englisch ist die Hauptsprache, daher sollten Ihre Änderungen auf Englisch erfolgen. Wenn Englisch nicht Ihre Stärke ist, verwenden Sie [DeepL Translator |https://www.deepl.com/translator] und andere werden Ihren Text überprüfen. +Die Hauptsprache ist Englisch. Ihre Änderungen sollten daher idealerweise sowohl auf Tschechisch als auch auf Englisch erfolgen. Wenn Englisch nicht Ihre Stärke ist, verwenden Sie den [DeepL Translator |https://www.deepl.com/translator] und andere werden den Text für Sie überprüfen. -Die Übersetzung in andere Sprachen erfolgt automatisch nach der Genehmigung und Feinabstimmung Ihrer Bearbeitung. +Die Übersetzung in andere Sprachen erfolgt automatisch nach Genehmigung und Feinabstimmung Ihrer Änderung. -Triviale Bearbeitungen .[#toc-trivial-edits] --------------------------------------------- +Triviale Änderungen +------------------- -Um zur Dokumentation beizutragen, müssen Sie ein Konto auf [GitHub |https://github.com] haben. +Um zur Dokumentation beizutragen, ist ein Konto auf [GitHub|https://github.com] erforderlich. -Der einfachste Weg, eine kleine Änderung an der Dokumentation vorzunehmen, ist die Verwendung der Links am Ende jeder Seite: +Der einfachste Weg, eine kleine Änderung in der Dokumentation vorzunehmen, ist die Verwendung der Links am Ende jeder Seite: -- *Auf GitHub anzeigen* öffnet die Quellversion der Seite auf GitHub. Klicken Sie dann einfach auf die Schaltfläche `E` und Sie können mit der Bearbeitung beginnen (Sie müssen bei GitHub angemeldet sein) -- *Vorschau öffnen* öffnet einen Editor, in dem Sie sofort die endgültige visuelle Form sehen können +- *Auf GitHub anzeigen* öffnet die Quellcodedatei der jeweiligen Seite auf GitHub. Drücken Sie dann einfach die Taste `E` und Sie können mit der Bearbeitung beginnen (Sie müssen bei GitHub angemeldet sein). +- *Vorschau öffnen* öffnet den Editor, in dem Sie auch gleich die resultierende visuelle Darstellung sehen. -Da der [Vorschau-Editor |https://editor.nette.org/] nicht die Möglichkeit bietet, Änderungen direkt auf GitHub zu speichern, müssen Sie den Quelltext in die Zwischenablage kopieren (mit der Schaltfläche *In die Zwischenablage kopieren*) und dann in den Editor auf GitHub einfügen. -Unterhalb des Bearbeitungsfeldes befindet sich ein Formular zum Einreichen. Vergessen Sie hier nicht, den Grund für Ihre Bearbeitung kurz zusammenzufassen und zu erläutern. Nach dem Einreichen wird ein sogenannter Pull Request (PR) erstellt, der weiter bearbeitet werden kann. +Da der [Editor mit Vorschau |https://editor.nette.org/] keine Möglichkeit hat, Änderungen direkt auf GitHub zu speichern, müssen Sie nach Abschluss der Bearbeitung den Quelltext in die Zwischenablage kopieren (mit der Schaltfläche *Copy to clipboard*) und ihn dann in den Editor auf GitHub einfügen. Unter dem Bearbeitungsfeld befindet sich ein Formular zum Senden. Vergessen Sie hier nicht, den Grund für Ihre Änderung kurz zusammenzufassen und zu erklären. Nach dem Senden entsteht ein sogenannter Pull Request (PR), der weiter bearbeitet werden kann. -Größere Bearbeitungen .[#toc-larger-edits] ------------------------------------------- +Größere Änderungen +------------------ -Es ist sinnvoller, mit den Grundlagen der Arbeit mit dem Versionskontrollsystem Git vertraut zu sein, als sich ausschließlich auf die GitHub-Schnittstelle zu verlassen. Wenn Sie mit Git nicht vertraut sind, können Sie den Leitfaden [Git - die einfache Anleitung |https://rogerdudler.github.io/git-guide/] zu Rate ziehen und die Verwendung eines der vielen verfügbaren [grafischen Clients |https://git-scm.com/downloads/guis] in Betracht ziehen. +Besser als die Nutzung der GitHub-Oberfläche ist es, mit den Grundlagen der Arbeit mit dem Versionskontrollsystem Git vertraut zu sein. Wenn Sie nicht mit Git vertraut sind, können Sie sich den Leitfaden [git - the simple guide |https://rogerdudler.github.io/git-guide/] ansehen und gegebenenfalls einen der vielen [grafischen Clients |https://git-scm.com/downloads/guis] nutzen. -Bearbeiten Sie die Dokumentation auf die folgende Weise: +Bearbeiten Sie die Dokumentation auf diese Weise: -1) Erstellen Sie auf GitHub einen [Fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] des [nette/docs-Repository |https://github.com/nette/docs] -2) [klonen |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] Sie dieses Repository auf Ihren Computer -3) nehmen Sie dann Änderungen im [entsprechenden Zweig |#Documentation Structure]vor -4) Prüfen Sie mit dem [Code-Checker |code-checker:] auf zusätzliche Leerzeichen im Text -5) speichern (committen) Sie die Änderungen -6) wenn du mit den Änderungen zufrieden bist, veröffentliche sie auf GitHub in deinem Fork -7) übermitteln Sie sie von dort aus an das Repository `nette/docs`, indem Sie einen [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) erstellen +1) Erstellen Sie auf GitHub einen [Fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] des Repositorys [nette/docs |https://github.com/nette/docs]. +2) [Klonen |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] Sie dieses Repository auf Ihren Computer. +3) Nehmen Sie dann im [entsprechenden Branch |#Struktur der Dokumentation] die Änderungen vor. +4) Überprüfen Sie überflüssige Leerzeichen im Text mit dem Werkzeug [Code-Checker |code-checker:]. +5) Speichern (committen) Sie die Änderungen. +6) Wenn Sie mit den Änderungen zufrieden sind, senden (pushen) Sie sie auf GitHub in Ihren Fork. +7) Senden Sie sie von dort an das Repository `nette/docs`, indem Sie einen [Pull Request|https://help.github.com/articles/creating-a-pull-request] (PR) erstellen. -Es ist üblich, dass Sie Kommentare mit Vorschlägen erhalten. Behalten Sie die vorgeschlagenen Änderungen im Auge und nehmen Sie sie auf. Fügen Sie die vorgeschlagenen Änderungen als neue Commits hinzu und senden Sie sie erneut an GitHub. Erstellen Sie niemals einen neuen Pull Request, nur um einen bestehenden zu ändern. +Es ist üblich, dass Sie Kommentare mit Anmerkungen erhalten. Verfolgen Sie die vorgeschlagenen Änderungen und arbeiten Sie sie ein. Fügen Sie die vorgeschlagenen Änderungen als neue Commits hinzu und senden Sie sie erneut auf GitHub. Erstellen Sie niemals einen neuen Pull Request, um einen bestehenden Pull Request zu bearbeiten. -Struktur der Dokumentation .[#toc-documentation-structure] ----------------------------------------------------------- +Struktur der Dokumentation +-------------------------- -Die gesamte Dokumentation befindet sich auf GitHub im Repository [nette/docs |https://github.com/nette/docs]. Die aktuelle Version befindet sich im Master-Zweig, während ältere Versionen in Zweigen wie `doc-3.x`, `doc-2.x` zu finden sind. +Die gesamte Dokumentation befindet sich auf GitHub im Repository [nette/docs |https://github.com/nette/docs]. Die aktuelle Version befindet sich im `master`-Branch, ältere Versionen befinden sich in Branches wie `doc-3.x`, `doc-2.x`. -Der Inhalt jedes Zweigs ist in Hauptordner unterteilt, die einzelne Bereiche der Dokumentation repräsentieren. So entspricht beispielsweise `application/` dem Ordner https://doc.nette.org/en/application, `latte/` dem Ordner https://latte.nette.org, usw. Jeder dieser Ordner enthält Unterordner für Sprachmutationen (`cs`, `en`, ...) und optional einen Unterordner `files` mit Bildern, die in die Seiten der Dokumentation eingefügt werden können. +Der Inhalt jedes Branches ist in Hauptordner unterteilt, die die einzelnen Bereiche der Dokumentation repräsentieren. Zum Beispiel entspricht `application/` https://doc.nette.org/de/application, `latte/` entspricht https://latte.nette.org usw. Jeder dieser Ordner enthält Unterordner, die die Sprachversionen (`cs`, `en`, ...) darstellen, und gegebenenfalls einen Unterordner `files` mit Bildern, die in die Seiten der Dokumentation eingefügt werden können. diff --git a/contributing/de/syntax.texy b/contributing/de/syntax.texy index 8b5293df8f..02b8af3554 100644 --- a/contributing/de/syntax.texy +++ b/contributing/de/syntax.texy @@ -1,59 +1,59 @@ -Wiki-Syntax -*********** +Dokumentationssyntax +******************** -Wiki verwendet die Markdown- und [Texy-Syntax |https://texy.info/en/syntax] mit einigen Erweiterungen. +Die Dokumentation verwendet Markdown & [Texy-Syntax |https://texy.nette.org/syntax] mit einigen Erweiterungen. -Links .[#toc-links] -=================== +Links +===== -Für interne Verweise wird die Notation in eckigen Klammern `[link]` verwendet. Dies geschieht entweder in der Form mit einem senkrechten Strich `[link text |link target]`oder in der abgekürzten Form `[link text]` wenn das Ziel dasselbe ist wie der Text (nach Umwandlung in Kleinbuchstaben und Bindestrichen): +Für interne Links wird die Notation in eckigen Klammern `[...]` verwendet. Entweder in der Form mit einem senkrechten Strich `[Linktext |Linkziel]` oder verkürzt `[Linktext als Ziel]`, wenn der Linktext mit dem Ziel übereinstimmt (nach Umwandlung in Kleinbuchstaben und Ersetzung von Leerzeichen durch Bindestriche): -- `[Page name]` -> `<a href="/en/page-name">Page name</a>` -- `[link text |Page name]` -> `<a href="/en/page-name">link text</a>` +- `[Page name]` -> `<a href="/de/page-name">Page name</a>` +- `[Linktext |Page name]` -> `<a href="/de/page-name">Linktext</a>` -Wir können auf eine andere Sprache oder einen anderen Abschnitt verweisen. Ein Abschnitt ist eine Nette-Bibliothek (z. B. `forms`, `latte`, usw.) oder spezielle Abschnitte wie `best-practices`, `quickstart`, usw: +Wir können auf eine andere Sprachversion oder einen anderen Abschnitt verlinken. Ein Abschnitt ist eine Nette-Bibliothek (z. B. `forms`, `latte` usw.) oder spezielle Abschnitte wie `best-practices`, `quickstart` usw.: -- `[cs:Page name]` -> `<a href="/en/page-name">Page name</a>` (gleicher Abschnitt, andere Sprache) -- `[tracy:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (anderer Abschnitt, gleiche Sprache) -- `[tracy:cs:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (andere Sektion und Sprache) +- `[cs:Page name]` -> `<a href="/cs/page-name">Page name</a>` (gleicher Abschnitt, andere Sprache) +- `[tracy:Page name]` -> `<a href="//tracy.nette.org/de/page-name">Page name</a>` (anderer Abschnitt, gleiche Sprache) +- `[tracy:cs:Page name]` -> `<a href="//tracy.nette.org/cs/page-name">Page name</a>` (anderer Abschnitt und Sprache) -Es ist auch möglich, eine bestimmte Überschrift auf der Seite mit `#` anzusteuern. +Mit `#` ist es auch möglich, auf eine bestimmte Überschrift auf der Seite zu zielen. - `[#Heading]` -> `<a href="#toc-heading">Heading</a>` (Überschrift auf der aktuellen Seite) -- `[Page name#Heading]` -> `<a href="/en/page-name#toc-heading">Page name</a>` +- `[Page name#Heading]` -> `<a href="/de/page-name#toc-heading">Page name</a>` -Link zur Homepage der Sektion: (`@home` ist ein spezieller Begriff für die Homepage der Sektion) +Link zur Startseite des Abschnitts: (`@home` ist ein spezieller Ausdruck für die Startseite des Abschnitts) -- `[link text |@home]` -> `<a href="/en/">link text</a>` -- `[link text |tracy:]` -> `<a href="//tracy.nette.org/en/">link text</a>` +- `[Linktext |@home]` -> `<a href="/de/">Linktext</a>` +- `[Linktext |tracy:]` -> `<a href="//tracy.nette.org/de/">Linktext</a>` -Links zur API-Dokumentation .[#toc-links-to-api-documentation] --------------------------------------------------------------- +Links zur API-Dokumentation +--------------------------- -Verwenden Sie immer die folgenden Bezeichnungen: +Verwenden Sie immer nur diese Notation: - `[api:Nette\SmartObject]` -> [api:Nette\SmartObject] - `[api:Nette\Forms\Form::setTranslator()]` -> [api:Nette\Forms\Form::setTranslator()] - `[api:Nette\Forms\Form::$onSubmit]` -> [api:Nette\Forms\Form::$onSubmit] - `[api:Nette\Forms\Form::Required]` -> [api:Nette\Forms\Form::Required] -Vollständig qualifizierte Namen nur bei der ersten Erwähnung verwenden. Für andere Links ist ein vereinfachter Name zu verwenden: +Verwenden Sie vollqualifizierte Namen nur bei der ersten Erwähnung. Für weitere Links verwenden Sie den vereinfachten Namen: -- `[Form::setTranslator() |api:Nette\Forms\Form::setTranslator()]` -> [Formular::setTranslator() |api:Nette\Forms\Form::setTranslator()] +- `[Form::setTranslator() |api:Nette\Forms\Form::setTranslator()]` -> [Form::setTranslator() |api:Nette\Forms\Form::setTranslator()] -Links zur PHP-Dokumentation .[#toc-links-to-php-documentation] --------------------------------------------------------------- +Links zur PHP-Dokumentation +--------------------------- - `[php:substr]` -> [php:substr] -Quellcode .[#toc-source-code] -============================= +Quellcode +========= -Der Codeblock beginnt mit <code>```lang</code> und endet mit <code>```</code> Unterstützte Sprachen sind `php`, `latte`, `neon`, `html`, `css`, `js` und `sql`. Verwenden Sie immer Tabulatoren für die Einrückung. +Ein Codeblock beginnt mit <code>```sprache</code> und endet mit <code>```</code>. Unterstützte Sprachen sind `php`, `latte`, `neon`, `html`, `css`, `js` und `sql`. Verwenden Sie für die Einrückung immer Tabulatoren. ``` ```php @@ -63,7 +63,7 @@ Der Codeblock beginnt mit <code>```lang</code> und endet mit <code>& ``` ``` -Sie können den Dateinamen auch als <code>```php .{file: ArrayTest.php}</code> angeben und der Codeblock wird auf diese Weise gerendert: +Sie können auch den Dateinamen als <code>```php .{file: ArrayTest.php}</code> angeben und der Codeblock wird auf diese Weise gerendert: ```php .{file: ArrayTest.php} public function renderPage($id) @@ -72,19 +72,19 @@ public function renderPage($id) ``` -Überschriften .[#toc-headings] -============================== +Überschriften +============= -Obere Überschrift (Seitenname) unterstrichen mit Sternen (`*`). For normal headings use equal signs (`=`) and then hyphens (`-`). +Die oberste Überschrift (also der Seitentitel) wird mit Sternchen unterstrichen. Zur Trennung von Abschnitten verwenden Sie Gleichheitszeichen. Überschriften unterstreichen Sie mit Gleichheitszeichen und dann mit Bindestrichen: ``` -MVC Applications & Presenters -***************************** +MVC-Anwendungen & Presenter +*************************** ... -Link Creation -============= +Linkerstellung +============== ... @@ -94,49 +94,49 @@ Links in Templates ``` -Boxen und Stile .[#toc-boxes-and-styles] -======================================== +Rahmen und Stile +================ -Mit Klasse markierter Lead-Absatz `.[perex]` .[perex] +Der Perex (Einleitungstext) wird mit der Klasse `.[perex]` gekennzeichnet. .[perex] -Mit Klasse gekennzeichnete Anmerkungen `.[note]` .[note] +Eine Anmerkung wird mit der Klasse `.[note]` gekennzeichnet. .[note] -Mit Klasse markierter Tipp `.[tip]` .[tip] +Ein Tipp wird mit der Klasse `.[tip]` gekennzeichnet. .[tip] -Warnung markiert mit Klasse `.[caution]` .[caution] +Eine Warnung wird mit der Klasse `.[caution]` gekennzeichnet. .[caution] -Starke Warnung mit Klasse gekennzeichnet `.[warning]` .[warning] +Eine stärkere Warnung wird mit der Klasse `.[warning]` gekennzeichnet. .[warning] Versionsnummer `.{data-version:2.4.10}` .{data-version:2.4.10} -Die Klassen sollten vor der entsprechenden Zeile geschrieben werden: +Schreiben Sie Klassen vor die Zeile, zu der sie gehören: ``` -.[note] -This is a note. +.[perex] +Dies ist der Perex. ``` -Bitte beachten Sie, dass Kästen wie `.[tip]` Aufmerksamkeit erregen und daher zur Hervorhebung und nicht für weniger wichtige Informationen verwendet werden sollten. +Bitte beachten Sie, dass Rahmen wie `.[tip]` die Aufmerksamkeit auf sich ziehen. Verwenden Sie sie daher zur Hervorhebung wichtiger Informationen und nicht für Nebensächlichkeiten. Gehen Sie äußerst sparsam damit um. -Inhaltsübersicht .[#toc-table-of-contents] -========================================== +Inhaltsverzeichnis +================== -Das Inhaltsverzeichnis (Links in der Seitenleiste) wird automatisch erstellt, wenn die Seite länger als 4 000 Bytes ist. Dieses Standardverhalten kann mit einem `{{toc}}` [Meta-Tag |#meta-tags] geändert werden. Der Text für das Inhaltsverzeichnis wird standardmäßig aus der Überschrift übernommen, aber es ist möglich, einen anderen Text mit einem `.{toc}` Modifikator zu verwenden. Dies ist besonders für längere Überschriften nützlich. +Das Inhaltsverzeichnis (Links im rechten Menü) wird automatisch für alle Seiten generiert, deren Größe 4.000 Byte überschreitet. Dieses Standardverhalten kann mit dem [Meta-Tag |#Meta-Tags] `{{toc}}` angepasst werden. Der Text für das Inhaltsverzeichnis wird standardmäßig direkt aus den Überschriften übernommen. Mit dem Modifikator `.{toc}` kann jedoch ein anderer Text angezeigt werden, was besonders bei längeren Überschriften nützlich ist. ``` -Long and Intelligent Heading .{toc: A Different Text for TOC} -============================================================= +Lange und intelligente Überschrift .{toc: Beliebiger anderer Text für das Inhaltsverzeichnis} +============================================================================================= ``` -Meta-Tags .[#toc-meta-tags] -=========================== +Meta-Tags +========= -- Einstellen des eigenen Seitentitels (in `<title>` und Breadcrumbs) `{{title: Another name}}` -- Weiterleiten `{{redirect: pla:cs}}` - [Links anzeigen|#links] -- Erzwingen `{{toc}}` oder Deaktivieren `{{toc: no}}` Inhaltsübersicht +- Einstellung eines benutzerdefinierten Seitentitels (im `<title>`-Tag und in der Breadcrumb-Navigation): `{{title: Anderer Titel}}` +- Weiterleitung: `{{redirect: pla:cs}}` - siehe [#Links] +- Erzwingen `{{toc}}` oder Deaktivieren `{{toc: no}}` des automatischen Inhaltsverzeichnisses (Box mit Links zu einzelnen Überschriften) {{priority: -1}} diff --git a/contributing/el/@home.texy b/contributing/el/@home.texy index 919b9e502d..a3a78869e2 100644 --- a/contributing/el/@home.texy +++ b/contributing/el/@home.texy @@ -1,17 +1,17 @@ -Γίνε Συνεργάτης στο Nette -************************* +Γίνετε συνεισφέρων στο Nette +**************************** .[perex] -Ρίξτε μια ματιά στο πώς μπορείτε να συμμετάσχετε στο έργο μας ανοικτού κώδικα. Μάθετε τα βήματα για τη συνεισφορά στον πηγαίο κώδικα και την τεκμηρίωση και γίνετε μέλος του δικτύου προγραμματιστών που είναι αφοσιωμένοι στη βελτίωση του Nette. +Μάθετε πώς μπορείτε να συμμετάσχετε στο open source έργο μας. Εξοικειωθείτε με τις διαδικασίες συνεισφοράς στον πηγαίο κώδικα και την τεκμηρίωση και γίνετε μέλος της κοινότητας των προγραμματιστών που συμμετέχουν ενεργά στη βελτίωση του Nette. **Κώδικας** -- [Συμβολή στον κώδικα |code] -- [Πρότυπα κωδικοποίησης |coding-standard] +- [Πώς να συνεισφέρετε στον κώδικα; |code] +- [Πρότυπο κωδικοποίησης |coding-standard] -**Τοποθέτηση εγγράφων** +**Τεκμηρίωση** -- [Συμβολή στην τεκμηρίωση |documentation] +- [Πώς να συνεισφέρετε στην τεκμηρίωση; |documentation] - [Σύνταξη τεκμηρίωσης |syntax] -- "Προεπισκόπηση επεξεργαστή":https://editor.nette.org +- "Επεξεργαστής προεπισκόπησης":https://editor.nette.org diff --git a/contributing/el/@left-menu.texy b/contributing/el/@left-menu.texy index a3e264a18c..8c9742c5a3 100644 --- a/contributing/el/@left-menu.texy +++ b/contributing/el/@left-menu.texy @@ -1,10 +1,10 @@ -Κωδικός +Κώδικας ******* -- [Συμβολή στον κώδικα |code] -- [Πρότυπα κωδικοποίησης |coding-standard] +- [Πώς να συνεισφέρετε στον κώδικα; |code] +- [Πρότυπο κωδικοποίησης |coding-standard] Τεκμηρίωση ********** -- [Συμβολή στην τεκμηρίωση |documentation] +- [Πώς να συνεισφέρετε στην τεκμηρίωση; |documentation] - [Σύνταξη τεκμηρίωσης |syntax] -- "Προεπισκόπηση επεξεργαστή":https://editor.nette.org +- "Επεξεργαστής προεπισκόπησης":https://editor.nette.org diff --git a/contributing/el/code.texy b/contributing/el/code.texy index 782bb06fea..e6c6cc8513 100644 --- a/contributing/el/code.texy +++ b/contributing/el/code.texy @@ -1,87 +1,87 @@ -Συμβολή στον κώδικα -******************* +Πώς να συνεισφέρετε στον κώδικα +******************************* .[perex] -Σχεδιάζετε να συνεισφέρετε στο Nette Framework και πρέπει να εξοικειωθείτε με τους κανόνες και τις διαδικασίες; Αυτός ο οδηγός για αρχάριους θα σας καθοδηγήσει στα βήματα για να συνεισφέρετε αποτελεσματικά στον κώδικα, να εργαστείτε με αποθετήρια και να εφαρμόσετε αλλαγές. +Ετοιμάζεστε να συνεισφέρετε στο Nette Framework και χρειάζεστε καθοδήγηση σχετικά με τους κανόνες και τις διαδικασίες; Αυτός ο οδηγός για αρχάριους θα σας δείξει βήμα προς βήμα πώς να συνεισφέρετε αποτελεσματικά στον κώδικα, να εργάζεστε με αποθετήρια (repositories) και να υλοποιείτε αλλαγές. -Διαδικασία .[#toc-procedure] -============================ +Διαδικασία +========== -Για να συνεισφέρετε στον κώδικα, είναι απαραίτητο να έχετε έναν λογαριασμό στο [GitHub |https://github.com] και να είστε εξοικειωμένοι με τα βασικά στοιχεία της εργασίας με το σύστημα ελέγχου εκδόσεων Git. Αν δεν είστε εξοικειωμένοι με το Git, μπορείτε να διαβάσετε το [git - the simple |https://rogerdudler.github.io/git-guide/] guide και να εξετάσετε το ενδεχόμενο να χρησιμοποιήσετε έναν από τους πολλούς [γραφικούς πελάτες |https://git-scm.com/downloads/guis]. +Για να συνεισφέρετε στον κώδικα, είναι απαραίτητο να έχετε λογαριασμό στο [GitHub |https://github.com] και να είστε εξοικειωμένοι με τα βασικά του συστήματος ελέγχου εκδόσεων Git. Αν δεν γνωρίζετε πώς να χρησιμοποιείτε το Git, μπορείτε να ανατρέξετε στον οδηγό [git - the simple guide |https://rogerdudler.github.io/git-guide/] και ενδεχομένως να χρησιμοποιήσετε έναν από τους πολλούς [γραφικούς clients |https://git-scm.com/downloads/guis]. -Προετοιμασία του περιβάλλοντος και του αποθετηρίου .[#toc-preparing-the-environment-and-repository] ---------------------------------------------------------------------------------------------------- +Προετοιμασία περιβάλλοντος και αποθετηρίου +------------------------------------------ -1) Στο GitHub, δημιουργήστε μια [διακλάδωση |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] του [αποθετηρίου πακέτων |www:packages] που σκοπεύετε να τροποποιήσετε -2) [Κλωνοποιήστε |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] αυτό το αποθετήριο στον υπολογιστή σας -3) Εγκαταστήστε τις εξαρτήσεις, συμπεριλαμβανομένου του [Nette Tester |tester:], χρησιμοποιώντας την εντολή `composer install` -4) Επαληθεύστε ότι οι δοκιμές λειτουργούν εκτελώντας την εντολή `composer tester` -5) Δημιουργήστε έναν [νέο κλάδο |#New Branch] με βάση την τελευταία έκδοση που κυκλοφόρησε +1) Στο GitHub, δημιουργήστε ένα [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] του αποθετηρίου του [πακέτου |www:packages] που πρόκειται να τροποποιήσετε. +2) [Κλωνοποιήστε |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] αυτό το αποθετήριο στον υπολογιστή σας. +3) Εγκαταστήστε τις εξαρτήσεις, συμπεριλαμβανομένου του [Nette Tester |tester:], χρησιμοποιώντας την εντολή `composer install`. +4) Ελέγξτε ότι οι δοκιμές λειτουργούν, εκτελώντας το `composer tester`. +5) Δημιουργήστε έναν [νέο κλάδο |#Νέος Κλάδος] βασισμένο στην τελευταία δημοσιευμένη έκδοση. -Εφαρμογή των δικών σας αλλαγών .[#toc-implementing-your-own-changes] --------------------------------------------------------------------- +Υλοποίηση των δικών σας αλλαγών +------------------------------- -Τώρα μπορείτε να κάνετε τις δικές σας προσαρμογές στον κώδικα: +Τώρα μπορείτε να πραγματοποιήσετε τις δικές σας τροποποιήσεις στον κώδικα: -1) Εφαρμόστε τις επιθυμητές αλλαγές και μην ξεχνάτε τις δοκιμές -2) Βεβαιωθείτε ότι οι δοκιμές εκτελούνται με επιτυχία χρησιμοποιώντας `composer tester` -3) Ελέγξτε αν ο κώδικας πληροί τα [πρότυπα κωδικοποίησης |#coding standards] -4) Αποθηκεύστε (commit) τις αλλαγές με μια περιγραφή [αυτής της μορφής |#Commit Description] +1) Προγραμματίστε τις απαιτούμενες αλλαγές και μην ξεχάσετε τις δοκιμές. +2) Βεβαιωθείτε ότι οι δοκιμές εκτελούνται επιτυχώς, χρησιμοποιώντας το `composer tester`. +3) Ελέγξτε αν ο κώδικας πληροί τα [#πρότυπα κωδικοποίησης]. +4) Αποθηκεύστε τις αλλαγές (commit) με περιγραφή σε [αυτή τη μορφή |#Περιγραφή του Commit]. -Μπορείτε να δημιουργήσετε πολλαπλές δεσμεύσεις, μία για κάθε λογικό βήμα. Κάθε commit θα πρέπει να έχει νόημα από μόνο του. +Μπορείτε να δημιουργήσετε πολλαπλά commits, ένα για κάθε λογικό βήμα. Κάθε commit θα πρέπει να έχει νόημα από μόνο του. -Υποβολή αλλαγών .[#toc-submitting-changes] ------------------------------------------- +Υποβολή των αλλαγών +------------------- -Αφού είστε ικανοποιημένοι με τις αλλαγές, μπορείτε να τις υποβάλετε: +Μόλις είστε ικανοποιημένοι με τις αλλαγές, μπορείτε να τις υποβάλετε: -1) Σπρώξτε τις αλλαγές στο GitHub στο fork σας -2) Από εκεί, υποβάλετε τις στο αποθετήριο Nette δημιουργώντας ένα [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) -3) Παρέχετε [επαρκείς πληροφορίες |#pull request description] στην περιγραφή +1) Στείλτε (push) τις αλλαγές στο GitHub στο δικό σας fork. +2) Από εκεί, υποβάλετέ τις στο αποθετήριο του Nette δημιουργώντας ένα [pull request |https://help.github.com/articles/creating-a-pull-request] (PR). +3) Παρέχετε [επαρκείς πληροφορίες |#Περιγραφή του Pull Request] στην περιγραφή. -Ενσωμάτωση ανατροφοδότησης .[#toc-incorporating-feedback] ---------------------------------------------------------- +Ενσωμάτωση σχολίων +------------------ -Οι δεσμεύσεις σας είναι τώρα ορατές στους άλλους. Είναι σύνηθες να λαμβάνετε σχόλια με προτάσεις: +Τα commits σας θα είναι πλέον ορατά και σε άλλους. Είναι σύνηθες να λαμβάνετε σχόλια με παρατηρήσεις: -1) Να παρακολουθείτε τις προτεινόμενες αλλαγές -2) Ενσωματώστε τις ως νέες commits ή [συγχωνεύστε τις με προηγούμενες |https://help.github.com/en/github/using-git/about-git-rebase] -3) Υποβάλετε εκ νέου τα commits στο GitHub, και θα εμφανιστούν αυτόματα στο pull request +1) Παρακολουθήστε τις προτεινόμενες τροποποιήσεις. +2) Ενσωματώστε τις ως νέα commits ή [συγχωνεύστε τα με τα προηγούμενα |https://help.github.com/en/github/using-git/about-git-rebase]. +3) Στείλτε ξανά τα commits στο GitHub, και θα εμφανιστούν αυτόματα στο pull request. -Ποτέ μην δημιουργείτε νέο pull request για να τροποποιήσετε ένα υπάρχον. +Ποτέ μην δημιουργείτε νέο pull request για την τροποποίηση ενός υπάρχοντος. -Τεκμηρίωση .[#toc-documentation] --------------------------------- +Τεκμηρίωση +---------- -Εάν έχετε αλλάξει τη λειτουργικότητα ή έχετε προσθέσει μια νέα, μην ξεχάσετε να [την προσθέσετε και στην τεκμηρίωση |documentation]. +Αν αλλάξατε τη λειτουργικότητα ή προσθέσατε νέα, μην ξεχάσετε να την [προσθέσετε και στην τεκμηρίωση |documentation]. -Νέος κλάδος .[#toc-new-branch] -============================== +Νέος Κλάδος +=========== -Αν είναι δυνατόν, κάντε αλλαγές σε σχέση με την τελευταία έκδοση που κυκλοφόρησε, δηλαδή την τελευταία ετικέτα του κλάδου. Για την ετικέτα v3.2.1, δημιουργήστε έναν κλάδο χρησιμοποιώντας αυτή την εντολή: +Αν είναι δυνατόν, πραγματοποιήστε τις αλλαγές έναντι της τελευταίας δημοσιευμένης έκδοσης, δηλαδή του τελευταίου tag στον συγκεκριμένο κλάδο. Για το tag `v3.2.1`, δημιουργείτε έναν κλάδο με αυτή την εντολή: ```shell git checkout -b new_branch_name v3.2.1 ``` -Πρότυπα κωδικοποίησης .[#toc-coding-standards] -============================================== +Πρότυπα Κωδικοποίησης +===================== -Ο κώδικάς σας πρέπει να πληροί τα [πρότυπα κωδικοποίησης |coding standard] που χρησιμοποιούνται στο Nette Framework. Υπάρχει διαθέσιμο ένα αυτόματο εργαλείο για τον έλεγχο και τη διόρθωση του κώδικα. Μπορείτε να το εγκαταστήσετε **σφαιρικά** μέσω του Composer σε έναν φάκελο της επιλογής σας: +Ο κώδικάς σας πρέπει να πληροί τα [πρότυπα κωδικοποίησης |coding-standard] που χρησιμοποιούνται στο Nette Framework. Για τον έλεγχο και τη διόρθωση του κώδικα είναι διαθέσιμο ένα αυτόματο εργαλείο. Μπορεί να εγκατασταθεί μέσω Composer **global** στον φάκελο της επιλογής σας: ```shell composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Τώρα θα πρέπει να μπορείτε να εκτελέσετε το εργαλείο στο τερματικό. Η πρώτη εντολή ελέγχει και η δεύτερη διορθώνει τον κώδικα στους φακέλους `src` και `tests` στον τρέχοντα κατάλογο: +Τώρα θα πρέπει να μπορείτε να εκτελέσετε το εργαλείο στο τερματικό. Με την πρώτη εντολή ελέγχετε και με τη δεύτερη διορθώνετε τον κώδικα στους φακέλους `src` και `tests` στον τρέχοντα κατάλογο: ```shell /path/to/nette-coding-standard/ecs check @@ -89,24 +89,24 @@ composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Περιγραφή δέσμευσης .[#toc-commit-description] -============================================== +Περιγραφή του Commit +==================== -Στη Nette, τα θέματα των δεσμεύσεων έχουν την ακόλουθη μορφή: `Presenter: fixed AJAX detection [Closes #69]` +Στο Nette, τα θέματα των commits έχουν τη μορφή: `Presenter: fixed AJAX detection [Closes #69]` -- περιοχή ακολουθούμενη από άνω και κάτω τελεία -- σκοπός της δέσμευσης σε παρελθοντικό χρόνο- αν είναι δυνατόν, αρχίστε με λέξεις όπως: "added .(νέο χαρακτηριστικό)", "fixed .(διόρθωση)", "refactored .(αλλαγή κώδικα χωρίς αλλαγή συμπεριφοράς)", changed, removed -- αν η δέσμευση σπάει την προς τα πίσω συμβατότητα, προσθέστε "BC break" -- οποιαδήποτε σύνδεση με τον ανιχνευτή ζητημάτων, όπως `(#123)` ή `[Closes #69]` -- μετά το θέμα, μπορεί να υπάρχει μία κενή γραμμή, ακολουθούμενη από μία πιο λεπτομερή περιγραφή, συμπεριλαμβανομένων, για παράδειγμα, συνδέσμων προς το φόρουμ +- Περιοχή ακολουθούμενη από άνω και κάτω τελεία. +- Σκοπός του commit σε παρελθοντικό χρόνο, αν είναι δυνατόν, ξεκινήστε με τη λέξη: "added (προστέθηκε νέα δυνατότητα)", "fixed (διόρθωση)", "refactored (αλλαγή στον κώδικα χωρίς αλλαγή συμπεριφοράς)", "changed", "removed". +- Αν το commit διακόπτει την προς τα πίσω συμβατότητα, προσθέστε "BC break". +- Πιθανή σύνδεση με το issue tracker όπως `(#123)` ή `[Closes #69]`. +- Μετά το θέμα μπορεί να ακολουθεί μία κενή γραμμή και στη συνέχεια λεπτομερέστερη περιγραφή, συμπεριλαμβανομένων, για παράδειγμα, συνδέσμων στο φόρουμ. -Περιγραφή αιτήματος μετακίνησης .[#toc-pull-request-description] -================================================================ +Περιγραφή του Pull Request +========================== -Κατά τη δημιουργία ενός pull request, η διεπαφή του GitHub θα σας επιτρέψει να εισαγάγετε έναν τίτλο και μια περιγραφή. Δώστε έναν συνοπτικό τίτλο και συμπεριλάβετε όσο το δυνατόν περισσότερες πληροφορίες στην περιγραφή σχετικά με τους λόγους της αλλαγής σας. +Κατά τη δημιουργία ενός pull request, η διεπαφή του GitHub σας επιτρέπει να εισάγετε έναν τίτλο και μια περιγραφή. Δώστε έναν περιεκτικό τίτλο και στην περιγραφή παρέχετε όσο το δυνατόν περισσότερες πληροφορίες σχετικά με τους λόγους της αλλαγής σας. -Επίσης, προσδιορίστε στην επικεφαλίδα αν πρόκειται για νέο χαρακτηριστικό ή διόρθωση σφάλματος και αν μπορεί να προκαλέσει προβλήματα συμβατότητας προς τα πίσω (BC break). Αν υπάρχει σχετικό θέμα, παραπέμψτε σε αυτό, ώστε να κλείσει με την έγκριση του pull request. +Θα εμφανιστεί επίσης μια επικεφαλίδα, όπου θα καθορίσετε αν πρόκειται για νέα λειτουργία ή διόρθωση σφάλματος και αν μπορεί να προκύψει παραβίαση της προς τα πίσω συμβατότητας (BC break). Αν υπάρχει σχετικό πρόβλημα (issue), αναφερθείτε σε αυτό, ώστε να κλείσει μετά την έγκριση του pull request. ``` - bug fix / new feature? <!-- #issue numbers, if any --> diff --git a/contributing/el/coding-standard.texy b/contributing/el/coding-standard.texy index 7bd7523916..6454785629 100644 --- a/contributing/el/coding-standard.texy +++ b/contributing/el/coding-standard.texy @@ -1,44 +1,44 @@ -Πρότυπο κωδικοποίησης +Πρότυπο Κωδικοποίησης ********************* -Αυτό το έγγραφο περιγράφει κανόνες και συστάσεις για την ανάπτυξη της Nette. Όταν συνεισφέρετε κώδικα στη Nette, πρέπει να τους ακολουθείτε. Ο ευκολότερος τρόπος για να το κάνετε είναι να μιμηθείτε τον υπάρχοντα κώδικα. -Η ιδέα είναι να κάνετε όλο τον κώδικα να μοιάζει σαν να έχει γραφτεί από ένα άτομο. .[perex] +.[perex] +Αυτό το έγγραφο περιγράφει τους κανόνες και τις συστάσεις για την ανάπτυξη του Nette. Κατά τη συνεισφορά κώδικα στο Nette, πρέπει να τους τηρείτε. Ο ευκολότερος τρόπος για να το κάνετε αυτό είναι να μιμηθείτε τον υπάρχοντα κώδικα. Στόχος είναι όλος ο κώδικας να φαίνεται σαν να τον έγραψε ένα άτομο. -Το Πρότυπο Κωδικοποίησης Nette αντιστοιχεί στο [Εκτεταμένο Στυλ Κωδικοποίησης PSR-12 |https://www.php-fig.org/psr/psr-12/] με δύο κύριες εξαιρέσεις: χρησιμοποιεί [tabs αντί για κενά |#tabs instead of spaces] για εσοχές και χρησιμοποιεί [PascalCase για τις σταθερές κλάσεων |https://blog.nette.org/el/gia-ligoteres-krauges-ston-kodika]. +Το Nette Coding Standard αντιστοιχεί στο [PSR-12 Extended Coding Style |https://www.php-fig.org/psr/psr-12/] με δύο κύριες εξαιρέσεις: για την εσοχή χρησιμοποιεί [tabs αντί για κενά |#Tabulators αντί για Κενά] και για τις [σταθερές κλάσεων χρησιμοποιεί PascalCase |https://blog.nette.org/el/for-less-screaming-in-the-code]. -Γενικοί κανόνες .[#toc-general-rules] -===================================== +Γενικοί Κανόνες +=============== -- Κάθε αρχείο PHP πρέπει να περιέχει `declare(strict_types=1)` -- Δύο κενές γραμμές χρησιμοποιούνται για το διαχωρισμό των μεθόδων για καλύτερη αναγνωσιμότητα. -- Ο λόγος χρήσης του τελεστή shut-up πρέπει να τεκμηριώνεται: `@mkdir($dir); // @ - directory may exist` -- Εάν χρησιμοποιείται ασθενής τυποποιημένος τελεστής σύγκρισης (π.χ. `==`, `!=`, ...), η πρόθεση πρέπει να τεκμηριώνεται: `// == to accept null` -- Μπορείτε να γράψετε περισσότερες εξαιρέσεις σε ένα αρχείο `exceptions.php` -- Η ορατότητα των μεθόδων δεν καθορίζεται για τις διασυνδέσεις επειδή είναι πάντα δημόσιες. -- Κάθε ιδιότητα, τιμή επιστροφής και παράμετρος πρέπει να έχει καθορισμένο τύπο. Από την άλλη πλευρά, για τις τελικές σταθερές, δεν καθορίζουμε ποτέ τον τύπο επειδή είναι προφανής. -- Τα απλά εισαγωγικά πρέπει να χρησιμοποιούνται για να οριοθετούν τη συμβολοσειρά, εκτός αν το ίδιο το λεκτικό περιέχει αποσιωπητικά. +- Κάθε αρχείο PHP πρέπει να περιέχει `declare(strict_types=1)`. +- Δύο κενές γραμμές χρησιμοποιούνται για τον διαχωρισμό μεθόδων για καλύτερη αναγνωσιμότητα. +- Ο λόγος χρήσης του τελεστή σίγασης (@) πρέπει να τεκμηριώνεται: `@mkdir($dir); // @ - ο κατάλογος μπορεί ήδη να υπάρχει`. +- Αν χρησιμοποιείται τελεστής σύγκρισης με αδύναμη τυποποίηση (δηλ. `==`, `!=`, ...), πρέπει να τεκμηριώνεται η πρόθεση: `// == αποδοχή null`. +- Σε ένα αρχείο `exceptions.php` μπορείτε να γράψετε πολλαπλές εξαιρέσεις. +- Στα interfaces δεν καθορίζεται η ορατότητα των μεθόδων, επειδή είναι πάντα public. +- Κάθε ιδιότητα, τιμή επιστροφής και παράμετρος πρέπει να έχει δηλωμένο τύπο. Αντίθετα, στις τελικές σταθερές δεν δηλώνουμε ποτέ τον τύπο, επειδή είναι προφανής. +- Για τον οριοθέτηση ενός string θα πρέπει να χρησιμοποιούνται απλά εισαγωγικά, εκτός από τις περιπτώσεις όπου το ίδιο το literal περιέχει αποστρόφους. -Συμβάσεις ονοματοδοσίας .[#toc-naming-conventions] -================================================== +Συμβάσεις Ονοματοδοσίας +======================= -- Αποφύγετε τη χρήση συντομογραφιών, εκτός αν το πλήρες όνομα είναι υπερβολικό. -- Χρησιμοποιήστε κεφαλαία γράμματα για συντομογραφίες δύο γραμμάτων και πασαλείμματα/καμηλόγραμμα για μεγαλύτερες συντομογραφίες. -- Χρησιμοποιήστε ένα ουσιαστικό ή μια ουσιαστική φράση για το όνομα της τάξης. -- Τα ονόματα κλάσεων πρέπει να περιέχουν όχι μόνο εξειδίκευση (`Array`) αλλά και γενικότητα (`ArrayIterator`). Εξαίρεση αποτελούν τα χαρακτηριστικά της PHP. -- "Οι σταθερές κλάσεων και τα enums πρέπει να χρησιμοποιούν PascalCaps":https://blog.nette.org/el/gia-ligoteres-krauges-ston-kodika. -- "Οι διεπαφές και οι αφηρημένες κλάσεις δεν πρέπει να περιέχουν προθέματα ή μεταθέματα":https://blog.nette.org/el/ta-prothemata-kai-ta-epithemata-den-anekoun-sta-onomata-diasyndeses όπως `Abstract`, `Interface` ή `I`. +- Μην χρησιμοποιείτε συντομογραφίες, εκτός αν το πλήρες όνομα είναι πολύ μεγάλο. +- Για διγράμματες συντομογραφίες χρησιμοποιήστε κεφαλαία γράμματα, για μεγαλύτερες συντομογραφίες PascalCase/camelCase. +- Για το όνομα της κλάσης χρησιμοποιήστε ουσιαστικό ή φράση ουσιαστικού. +- Τα ονόματα των κλάσεων πρέπει να περιέχουν όχι μόνο την εξειδίκευση (`Array`), αλλά και τη γενικότητα (`ArrayIterator`). Εξαίρεση αποτελούν τα attributes της γλώσσας PHP. +- "Οι σταθερές κλάσεων και τα enums πρέπει να χρησιμοποιούν PascalCase":https://blog.nette.org/el/for-less-screaming-in-the-code. +- "Τα Interfaces και οι abstract κλάσεις δεν πρέπει να περιέχουν προθέματα ή επιθήματα":https://blog.nette.org/el/prefixes-and-suffixes-do-not-belong-in-interface-names όπως `Abstract`, `Interface` ή `I`. -Περιτύλιξη και αγκύλες .[#toc-wrapping-and-braces] -================================================== +Αναδίπλωση και Άγκιστρα +======================= -Το πρότυπο κωδικοποίησης Nette αντιστοιχεί στο PSR-12 (ή PER Coding Style), σε ορισμένα σημεία το διευκρινίζει περισσότερο ή το τροποποιεί: +Το Nette Coding Standard αντιστοιχεί στο PSR-12 (ή PER Coding Style), σε ορισμένα σημεία το συμπληρώνει ή το τροποποιεί: -- Οι συναρτήσεις βέλους γράφονται χωρίς κενό πριν από την παρένθεση, δηλ. `fn($a) => $b` -- δεν απαιτείται κενή γραμμή μεταξύ διαφορετικών τύπων δηλώσεων εισαγωγής `use` -- ο τύπος επιστροφής της συνάρτησης/μεθόδου και η εισαγωγική παρένθεση πρέπει να τοποθετούνται σε ξεχωριστές γραμμές για καλύτερη αναγνωσιμότητα: +- Οι arrow functions γράφονται χωρίς κενό πριν την παρένθεση, δηλ. `fn($a) => $b`. +- Δεν απαιτείται κενή γραμμή μεταξύ διαφορετικών τύπων `use` import statements. +- Ο τύπος επιστροφής της συνάρτησης/μεθόδου και το αρχικό άγκιστρο `{` είναι πάντα σε ξεχωριστές γραμμές: ```php public function find( @@ -46,24 +46,28 @@ array $options, ): array { - // σώμα μεθόδου + // σώμα της μεθόδου } ``` +Το αρχικό άγκιστρο σε ξεχωριστή γραμμή είναι σημαντικό για τον οπτικό διαχωρισμό της υπογραφής της συνάρτησης/μεθόδου από το σώμα. Αν η υπογραφή είναι σε μία γραμμή, ο διαχωρισμός είναι σαφής (εικόνα αριστερά). Αν είναι σε πολλές γραμμές, στο PSR οι υπογραφές και το σώμα συγχωνεύονται (μέση), ενώ στο πρότυπο Nette παραμένουν διαχωρισμένα (δεξιά): -Μπλοκ τεκμηρίωσης (phpDoc) .[#toc-documentation-blocks-phpdoc] -============================================================== +[* new-line-after.webp *] -Ο κύριος κανόνας: ποτέ μην αναπαράγετε πληροφορίες υπογραφής όπως ο τύπος παραμέτρου ή ο τύπος επιστροφής χωρίς να προστίθεται αξία. -Μπλοκ τεκμηρίωσης για τον ορισμό της κλάσης: +Μπλοκ Τεκμηρίωσης (phpDoc) +========================== -- Ξεκινά με μια περιγραφή της κλάσης. -- Ακολουθεί κενή γραμμή. -- Ακολουθούν οι επισημειώσεις `@property` (ή `@property-read`, `@property-write`), μία προς μία. Η σύνταξη είναι: annotation, space, type, space, $name. -- Ακολουθούν οι επισημειώσεις `@method`, μία προς μία. Η σύνταξη είναι: annotation, space, return type, space, name(type $param, ...). -- Ο σχολιασμός `@author` παραλείπεται. Η συγγραφή διατηρείται στο ιστορικό του πηγαίου κώδικα. -- Μπορούν να χρησιμοποιηθούν οι σχολιασμοί `@internal` ή `@deprecated`. +Κύριος κανόνας: Ποτέ μην επαναλαμβάνετε καμία πληροφορία που υπάρχει ήδη στην υπογραφή, όπως τον τύπο παραμέτρου ή τον τύπο επιστροφής, χωρίς να προσθέτετε αξία. + +Μπλοκ τεκμηρίωσης για ορισμό κλάσης: + +- Ξεκινά με την περιγραφή της κλάσης. +- Ακολουθεί μια κενή γραμμή. +- Ακολουθούν οι annotations `@property` (ή `@property-read`, `@property-write`), μία μετά την άλλη. Η σύνταξη είναι: annotation, κενό, τύπος, κενό, `$name`. +- Ακολουθούν οι annotations `@method`, μία μετά την άλλη. Η σύνταξη είναι: annotation, κενό, τύπος επιστροφής, κενό, `name(type $param, ...)` . +- Η annotation `@author` παραλείπεται. Η πατρότητα διατηρείται στην ιστορία του πηγαίου κώδικα. +- Μπορούν να χρησιμοποιηθούν οι annotations `@internal` ή `@deprecated`. ```php /** @@ -76,23 +80,23 @@ */ ``` -Το μπλοκ τεκμηρίωσης για την ιδιότητα που περιέχει μόνο τον σχολιασμό `@var` θα πρέπει να είναι μονής γραμμής: +Το μπλοκ τεκμηρίωσης για μια ιδιότητα, που περιέχει μόνο την annotation `@var`, θα πρέπει να είναι σε μία γραμμή: ```php /** @var string[] */ private array $name; ``` -μπλοκ τεκμηρίωσης για τον ορισμό μεθόδου: +Μπλοκ τεκμηρίωσης για ορισμό μεθόδου: -- Αρχίζει με μια σύντομη περιγραφή της μεθόδου. -- Δεν υπάρχει κενή γραμμή. -- Οι σημειώσεις `@param`, μία ανά γραμμή. -- Ο σχολιασμός `@return`. -- Οι σχολιασμοί `@throws`, ένας προς μία γραμμή. -- Μπορούν να χρησιμοποιηθούν οι σχολιασμοί `@internal` ή `@deprecated`. +- Ξεκινά με μια σύντομη περιγραφή της μεθόδου. +- Καμία κενή γραμμή. +- Annotations `@param` σε ξεχωριστές γραμμές. +- Annotation `@return`. +- Annotations `@throws`, μία μετά την άλλη. +- Μπορούν να χρησιμοποιηθούν οι annotations `@internal` ή `@deprecated`. -Κάθε σχολιασμός ακολουθείται από ένα κενό, εκτός από το `@param` το οποίο ακολουθείται από δύο κενά για καλύτερη αναγνωσιμότητα. +Μετά από κάθε annotation ακολουθεί ένα κενό, εκτός από το `@param`, μετά το οποίο για καλύτερη αναγνωσιμότητα ακολουθούν δύο κενά. ```php /** @@ -105,21 +109,20 @@ public function find(string $dir, array $options): array ``` -Καρτέλες αντί για κενά .[#toc-tabs-instead-of-spaces] -===================================================== +Tabulators αντί για Κενά +======================== -Οι καρτέλες έχουν πολλά πλεονεκτήματα έναντι των διαστημάτων: +Οι tabulators έχουν αρκετά πλεονεκτήματα έναντι των κενών: -- Το μέγεθος της εσοχής μπορεί να προσαρμοστεί στους επεξεργαστές & στο "web":https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size. -- δεν επιβάλλουν την προτίμηση του μεγέθους εσοχής του χρήστη στον κώδικα, οπότε ο κώδικας είναι πιο φορητός -- μπορείτε να τις πληκτρολογήσετε με ένα πάτημα του πλήκτρου (οπουδήποτε, όχι μόνο σε επεξεργαστές που μετατρέπουν τα tabs σε κενά) -- η εσοχή είναι ο σκοπός τους -- σέβονται τις ανάγκες των συναδέλφων με προβλήματα όρασης και των τυφλών +- Το μέγεθος της εσοχής μπορεί να προσαρμοστεί στους επεξεργαστές κειμένου και στον "web":https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size. +- Δεν επιβάλλουν στον κώδικα την προτίμηση του χρήστη για το μέγεθος της εσοχής, οπότε ο κώδικας είναι πιο φορητός. +- Μπορούν να γραφτούν με ένα πάτημα πλήκτρου (οπουδήποτε, όχι μόνο σε επεξεργαστές που μετατρέπουν τους tabulators σε κενά). +- Η εσοχή είναι ο σκοπός τους. +- Σέβονται τις ανάγκες των συναδέλφων με προβλήματα όρασης και των τυφλών. -Χρησιμοποιώντας καρτέλες στα έργα μας, επιτρέπουμε την προσαρμογή του πλάτους, κάτι που μπορεί να φαίνεται περιττό στους περισσότερους ανθρώπους, αλλά είναι απαραίτητο για τα άτομα με προβλήματα όρασης. +Χρησιμοποιώντας tabulators στα έργα μας, επιτρέπουμε την προσαρμογή του πλάτους, η οποία μπορεί να φαίνεται περιττή στους περισσότερους ανθρώπους, αλλά είναι απαραίτητη για άτομα με προβλήματα όρασης. -Για τους τυφλούς προγραμματιστές που χρησιμοποιούν οθόνες Braille, κάθε διάστημα αντιπροσωπεύεται από ένα κελί Braille και καταλαμβάνει πολύτιμο χώρο. Έτσι, αν η προεπιλεγμένη εσοχή είναι 4 διαστήματα, μια εσοχή 3ου επιπέδου σπαταλά 12 κελιά braille πριν αρχίσει ο κώδικας. -Σε μια οθόνη 40 κελιών, η οποία χρησιμοποιείται συνήθως στους φορητούς υπολογιστές, αυτό είναι περισσότερο από το ένα τέταρτο των διαθέσιμων κελιών που σπαταλούνται χωρίς καμία πληροφορία. +Για τους τυφλούς προγραμματιστές που χρησιμοποιούν οθόνες Braille, κάθε κενό αντιπροσωπεύει ένα κελί Braille. Αν λοιπόν η προεπιλεγμένη εσοχή είναι 4 κενά, η εσοχή 3ου επιπέδου σπαταλά 12 πολύτιμα κελιά Braille πριν καν αρχίσει ο κώδικας. Σε μια οθόνη 40 κελιών, η οποία χρησιμοποιείται συχνότερα σε φορητούς υπολογιστές, αυτό είναι περισσότερο από το ένα τέταρτο των διαθέσιμων κελιών που σπαταλούνται χωρίς καμία πληροφορία. {{priority: -1}} diff --git a/contributing/el/documentation.texy b/contributing/el/documentation.texy index 7040e029fe..28e360e165 100644 --- a/contributing/el/documentation.texy +++ b/contributing/el/documentation.texy @@ -1,69 +1,68 @@ -Συμβολή στην τεκμηρίωση -*********************** +Πώς να Συνεισφέρετε στην Τεκμηρίωση +*********************************** .[perex] -Η συνεισφορά στην τεκμηρίωση είναι μια από τις πιο πολύτιμες δραστηριότητες, καθώς βοηθάει τους άλλους να κατανοήσουν το πλαίσιο. +Η συνεισφορά στην τεκμηρίωση είναι μία από τις πιο ωφέλιμες δραστηριότητες, καθώς βοηθάτε άλλους να κατανοήσουν το framework. -Πώς να γράψετε; .[#toc-how-to-write] ------------------------------------- +Πώς να Γράφετε; +--------------- -Η τεκμηρίωση απευθύνεται κυρίως σε άτομα που είναι καινούργια στο θέμα. Ως εκ τούτου, θα πρέπει να ανταποκρίνεται σε διάφορα σημαντικά σημεία: +Η τεκμηρίωση προορίζεται κυρίως για άτομα που εξοικειώνονται με το θέμα. Επομένως, θα πρέπει να πληροί αρκετά σημαντικά σημεία: -- Ξεκινήστε με απλά και γενικά θέματα. Να προχωράτε σε πιο προχωρημένα θέματα στο τέλος -- Προσπαθήστε να εξηγείτε το θέμα όσο το δυνατόν πιο ξεκάθαρα. Για παράδειγμα, προσπαθήστε να εξηγήσετε το θέμα πρώτα σε έναν συνάδελφο -- Παρέχετε μόνο τις πληροφορίες που πραγματικά χρειάζεται να γνωρίζει ο χρήστης για ένα συγκεκριμένο θέμα -- Βεβαιωθείτε ότι οι πληροφορίες σας είναι ακριβείς. Δοκιμάστε κάθε κώδικα -- Να είστε συνοπτικοί - κόψτε αυτά που γράφετε στη μέση. Και μετά μη διστάσετε να το ξανακάνετε -- Χρησιμοποιήστε με φειδώ την επισήμανση, από έντονες γραμματοσειρές μέχρι πλαίσια όπως `.[note]` -- Ακολουθήστε το [πρότυπο κωδικοποίησης |Coding Standard] στον κώδικα +- Ξεκινήστε από το απλό και το γενικό. Προχωρήστε σε πιο προχωρημένα θέματα μόνο στο τέλος. +- Προσπαθήστε να εξηγήσετε το θέμα όσο το δυνατόν καλύτερα. Δοκιμάστε, για παράδειγμα, να εξηγήσετε πρώτα το θέμα σε έναν συνάδελφο. +- Αναφέρετε μόνο τις πληροφορίες που ο χρήστης πραγματικά χρειάζεται να γνωρίζει για το συγκεκριμένο θέμα. +- Επαληθεύστε ότι οι πληροφορίες σας είναι όντως αληθείς. Δοκιμάστε κάθε κώδικα. +- Να είστε συνοπτικοί - ό,τι γράψετε, συντομεύστε το στο μισό. Και μετά, αν θέλετε, ξανά. +- Χρησιμοποιήστε με φειδώ τα στοιχεία έμφασης κάθε είδους, από έντονα γράμματα μέχρι πλαίσια όπως `.[note]`. +- Στον κώδικα, τηρήστε τα [Πρότυπα Κωδικοποίησης |coding-standard]. -Επίσης, μάθετε τη [σύνταξη |syntax]. Για μια προεπισκόπηση του άρθρου κατά τη διάρκεια της συγγραφής, μπορείτε να χρησιμοποιήσετε τον [επεξεργαστή προεπισκόπησης |https://editor.nette.org/]. +Εξοικειωθείτε επίσης με τη [σύνταξη |syntax]. Για προεπισκόπηση του άρθρου κατά τη συγγραφή του, μπορείτε να χρησιμοποιήσετε τον [επεξεργαστή με προεπισκόπηση |https://editor.nette.org/]. -Μεταλλάξεις της γλώσσας .[#toc-language-mutations] --------------------------------------------------- +Γλωσσικές Εκδόσεις +------------------ -Τα αγγλικά είναι η κύρια γλώσσα, οπότε οι αλλαγές σας θα πρέπει να είναι στα αγγλικά. Αν τα αγγλικά δεν είναι το δυνατό σας σημείο, χρησιμοποιήστε [το DeepL Translator |https://www.deepl.com/translator] και άλλοι θα ελέγξουν το κείμενό σας. +Η κύρια γλώσσα είναι τα Αγγλικά. Οι αλλαγές σας θα πρέπει ιδανικά να γίνονται και στα Αγγλικά. Αν τα Αγγλικά δεν είναι το δυνατό σας σημείο, χρησιμοποιήστε τον [DeepL Translator |https://www.deepl.com/translator] και οι άλλοι θα ελέγξουν το κείμενό σας. -Η μετάφραση σε άλλες γλώσσες θα γίνει αυτόματα μετά την έγκριση και την τελειοποίηση της επεξεργασίας σας. +Η μετάφραση στις άλλες γλώσσες θα γίνει αυτόματα μετά την έγκριση και την τελειοποίηση της τροποποίησής σας. -Trivial Edits .[#toc-trivial-edits] ------------------------------------ +Ασήμαντες Τροποποιήσεις +----------------------- -Για να συνεισφέρετε στην τεκμηρίωση, πρέπει να έχετε έναν λογαριασμό στο [GitHub |https://github.com]. +Για να συνεισφέρετε στην τεκμηρίωση, είναι απαραίτητο να έχετε λογαριασμό στο [GitHub |https://github.com]. Ο ευκολότερος τρόπος για να κάνετε μια μικρή αλλαγή στην τεκμηρίωση είναι να χρησιμοποιήσετε τους συνδέσμους στο τέλος κάθε σελίδας: -- *Εμφάνιση στο GitHub* ανοίγει την πηγαία έκδοση της σελίδας στο GitHub. Στη συνέχεια, απλά πατήστε το κουμπί `E` και μπορείτε να ξεκινήσετε την επεξεργασία (πρέπει να είστε συνδεδεμένοι στο GitHub) -- *Άνοιγμα προεπισκόπησης* ανοίγει έναν επεξεργαστή όπου μπορείτε να δείτε αμέσως την τελική οπτική μορφή +- Το *Εμφάνιση στο GitHub* ανοίγει την πηγαία μορφή της συγκεκριμένης σελίδας στο GitHub. Στη συνέχεια, αρκεί να πατήσετε το κουμπί `E` και μπορείτε να αρχίσετε την επεξεργασία (πρέπει να είστε συνδεδεμένοι στο GitHub). +- Το *Άνοιγμα προεπισκόπησης* ανοίγει τον επεξεργαστή, όπου βλέπετε αμέσως και την τελική οπτική μορφή. -Επειδή ο [επεξεργαστής προεπισκόπησης |https://editor.nette.org/] δεν έχει τη δυνατότητα να αποθηκεύσετε τις αλλαγές απευθείας στο GitHub, πρέπει να αντιγράψετε το πηγαίο κείμενο στο πρόχειρο (χρησιμοποιώντας το κουμπί *Κοπή στο πρόχειρο*) και στη συνέχεια να το επικολλήσετε στον επεξεργαστή στο GitHub. -Κάτω από το πεδίο επεξεργασίας υπάρχει μια φόρμα για την υποβολή. Εδώ, μην ξεχάσετε να συνοψίσετε εν συντομία και να εξηγήσετε τον λόγο της επεξεργασίας σας. Μετά την υποβολή, δημιουργείται ένα λεγόμενο pull request (PR), το οποίο μπορείτε να επεξεργαστείτε περαιτέρω. +Επειδή ο [επεξεργαστής με προεπισκόπηση |https://editor.nette.org/] δεν έχει τη δυνατότητα αποθήκευσης αλλαγών απευθείας στο GitHub, είναι απαραίτητο μετά την ολοκλήρωση των τροποποιήσεων να αντιγράψετε το πηγαίο κείμενο στο πρόχειρο (με το κουμπί *Copy to clipboard*) και στη συνέχεια να το επικολλήσετε στον επεξεργαστή στο GitHub. Κάτω από το πεδίο επεξεργασίας υπάρχει μια φόρμα για την υποβολή. Εδώ μην ξεχάσετε να συνοψίσετε και να εξηγήσετε σύντομα τον λόγο της τροποποίησής σας. Μετά την υποβολή δημιουργείται ένα λεγόμενο pull request (PR), το οποίο μπορεί να επεξεργαστεί περαιτέρω. -Μεγαλύτερες επεξεργασίες .[#toc-larger-edits] ---------------------------------------------- +Μεγαλύτερες Τροποποιήσεις +------------------------- -Είναι προτιμότερο να είστε εξοικειωμένοι με τα βασικά στοιχεία της εργασίας με το σύστημα ελέγχου εκδόσεων Git παρά να βασίζεστε αποκλειστικά στη διεπαφή του GitHub. Αν δεν είστε εξοικειωμένοι με το Git, μπορείτε να ανατρέξετε στον οδηγό [git - the simple |https://rogerdudler.github.io/git-guide/] και να εξετάσετε το ενδεχόμενο να χρησιμοποιήσετε έναν από τους πολλούς διαθέσιμους [γραφικούς πελάτες |https://git-scm.com/downloads/guis]. +Πιο κατάλληλο από τη χρήση της διεπαφής του GitHub, είναι να είστε εξοικειωμένοι με τα βασικά της εργασίας με το σύστημα ελέγχου εκδόσεων Git. Αν δεν γνωρίζετε πώς να χρησιμοποιείτε το Git, μπορείτε να δείτε τον οδηγό [git - the simple guide |https://rogerdudler.github.io/git-guide/] και ενδεχομένως να χρησιμοποιήσετε έναν από τους πολλούς [γραφικούς clients |https://git-scm.com/downloads/guis]. -Επεξεργαστείτε την τεκμηρίωση με τον ακόλουθο τρόπο: +Τροποποιήστε την τεκμηρίωση με αυτόν τον τρόπο: -1) στο GitHub, δημιουργήστε μια [διακλάδωση |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] του αποθετηρίου [nette/docs |https://github.com/nette/docs] -2) [κλωνοποιήστε |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] αυτό το αποθετήριο στον υπολογιστή σας -3) στη συνέχεια, κάντε αλλαγές στον [κατάλληλο κλάδο |#Documentation Structure] -4) ελέγξτε για επιπλέον κενά στο κείμενο χρησιμοποιώντας το εργαλείο [Code-Checker |code-checker:] -5) αποθηκεύστε (commit) τις αλλαγές -6) αν είστε ικανοποιημένοι με τις αλλαγές, προωθήστε τις στο GitHub στο fork σας -7) από εκεί, υποβάλετε τις στο αποθετήριο `nette/docs` δημιουργώντας ένα [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) +1) Στο GitHub, δημιουργήστε ένα [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] του αποθετηρίου [nette/docs |https://github.com/nette/docs]. +2) [Κλωνοποιήστε |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] αυτό το αποθετήριο στον υπολογιστή σας. +3) Στη συνέχεια, στον [κατάλληλο κλάδο |#Δομή της Τεκμηρίωσης] πραγματοποιήστε τις αλλαγές. +4) Ελέγξτε για περιττά κενά στο κείμενο χρησιμοποιώντας το εργαλείο [Code-Checker |code-checker:]. +5) Αποθηκεύστε τις αλλαγές (commit). +6) Αν είστε ικανοποιημένοι με τις αλλαγές, στείλτε (push) τις στο GitHub στο δικό σας fork. +7) Από εκεί, υποβάλετέ τις στο αποθετήριο `nette/docs` δημιουργώντας ένα [pull request |https://help.github.com/articles/creating-a-pull-request] (PR). -Είναι σύνηθες να λαμβάνετε σχόλια με προτάσεις. Παρακολουθήστε τις προτεινόμενες αλλαγές και ενσωματώστε τις. Προσθέστε τις προτεινόμενες αλλαγές ως νέες δεσμεύσεις και στείλτε τις εκ νέου στο GitHub. Ποτέ μην δημιουργείτε ένα νέο pull request μόνο και μόνο για να τροποποιήσετε ένα υπάρχον. +Είναι σύνηθες να λαμβάνετε σχόλια με παρατηρήσεις. Παρακολουθήστε τις προτεινόμενες αλλαγές και ενσωματώστε τις. Προσθέστε τις προτεινόμενες αλλαγές ως νέα commits και στείλτε τις ξανά στο GitHub. Ποτέ μην δημιουργείτε νέο pull request για την τροποποίηση ενός υπάρχοντος pull request. -Δομή τεκμηρίωσης .[#toc-documentation-structure] ------------------------------------------------- +Δομή της Τεκμηρίωσης +-------------------- -Ολόκληρη η τεκμηρίωση βρίσκεται στο GitHub στο αποθετήριο [nette/docs |https://github.com/nette/docs]. Η τρέχουσα έκδοση βρίσκεται στον κλάδο master, ενώ παλαιότερες εκδόσεις βρίσκονται σε κλάδους όπως `doc-3.x`, `doc-2.x`. +Ολόκληρη η τεκμηρίωση βρίσκεται στο GitHub στο αποθετήριο [nette/docs |https://github.com/nette/docs]. Η τρέχουσα έκδοση βρίσκεται στον κλάδο `master`, ενώ οι παλαιότερες εκδόσεις βρίσκονται σε κλάδους όπως `doc-3.x`, `doc-2.x`. -Το περιεχόμενο κάθε κλάδου χωρίζεται σε κύριους φακέλους που αντιπροσωπεύουν μεμονωμένες περιοχές της τεκμηρίωσης. Για παράδειγμα, το `application/` αντιστοιχεί στο https://doc.nette.org/en/application, το `latte/` αντιστοιχεί στο https://latte.nette.org, κ.λπ. Κάθε ένας από αυτούς τους φακέλους περιέχει υποφακέλους που αντιπροσωπεύουν γλωσσικές μεταλλάξεις (`cs`, `en`, ...) και προαιρετικά έναν υποφάκελο `files` με εικόνες που μπορούν να εισαχθούν στις σελίδες της τεκμηρίωσης. +Το περιεχόμενο κάθε κλάδου χωρίζεται σε κύριους φακέλους που αντιπροσωπεύουν τις επιμέρους ενότητες της τεκμηρίωσης. Για παράδειγμα, το `application/` αντιστοιχεί στο https://doc.nette.org/cs/application, το `latte/` αντιστοιχεί στο https://latte.nette.org κ.λπ. Κάθε τέτοιος φάκελος περιέχει υποφακέλους που αντιπροσωπεύουν τις γλωσσικές εκδόσεις (`cs`, `en`, ...) και ενδεχομένως τον υποφάκελο `files` με εικόνες, τις οποίες είναι δυνατόν να εισαγάγετε στις σελίδες της τεκμηρίωσης. diff --git a/contributing/el/syntax.texy b/contributing/el/syntax.texy index a98ae51f15..2f46730e28 100644 --- a/contributing/el/syntax.texy +++ b/contributing/el/syntax.texy @@ -1,59 +1,59 @@ -Σύνταξη Wiki -************ +Σύνταξη Τεκμηρίωσης +******************* -Το Wiki χρησιμοποιεί [σύνταξη |https://texy.info/en/syntax] Markdown & [Texy |https://texy.info/en/syntax] με αρκετές βελτιώσεις. +Η τεκμηρίωση χρησιμοποιεί Markdown & [σύνταξη Texy |https://texy.nette.org/syntax] με ορισμένες επεκτάσεις. -Σύνδεσμοι .[#toc-links] -======================= +Σύνδεσμοι +========= -Για εσωτερικές αναφορές, η σημειογραφία σε αγκύλες `[link]` χρησιμοποιείται. Αυτό γίνεται είτε με τη μορφή με κάθετη γραμμή `[link text |link target]`, είτε με τη συντομευμένη μορφή `[link text]` εάν ο στόχος είναι ο ίδιος με το κείμενο (μετά από μετατροπή σε πεζά γράμματα και παύλες): +Για εσωτερικούς συνδέσμους χρησιμοποιείται η γραφή σε αγκύλες `[σύνδεσμος]`. Αυτό μπορεί να γίνει είτε με κάθετη γραμμή `[κείμενο συνδέσμου |στόχος συνδέσμου]`, είτε συντομευμένα `[κείμενο συνδέσμου]`, αν ο στόχος είναι ίδιος με το κείμενο (μετά από μετατροπή σε πεζά γράμματα και παύλες): - `[Page name]` -> `<a href="/en/page-name">Page name</a>` - `[link text |Page name]` -> `<a href="/en/page-name">link text</a>` -Μπορούμε να συνδεθούμε με άλλη γλώσσα ή άλλο τμήμα. Ένα τμήμα είναι μια βιβλιοθήκη Nette (π.χ. `forms`, `latte`, κ.λπ.) ή ειδικά τμήματα όπως `best-practices`, `quickstart`, κ.λπ: +Μπορούμε να συνδέσουμε σε άλλη γλωσσική έκδοση ή σε άλλη ενότητα. Ενότητα νοείται η βιβλιοθήκη Nette (π.χ. `forms`, `latte`, κ.λπ.) ή ειδικές ενότητες όπως `best-practices`, `quickstart` κ.λπ.: -- `[cs:Page name]` -> `<a href="/en/page-name">Page name</a>` (ίδια ενότητα, διαφορετική γλώσσα) -- `[tracy:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (διαφορετικό τμήμα, ίδια γλώσσα) -- `[tracy:cs:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (διαφορετικό τμήμα και γλώσσα) +- `[cs:Page name]` -> `<a href="/cs/page-name">Page name</a>` (ίδια ενότητα, άλλη γλώσσα) +- `[tracy:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (άλλη ενότητα, ίδια γλώσσα) +- `[tracy:cs:Page name]` -> `<a href="//tracy.nette.org/cs/page-name">Page name</a>` (άλλη ενότητα και γλώσσα) -Είναι επίσης δυνατό να στοχεύσετε συγκεκριμένη επικεφαλίδα στη σελίδα με `#`. +Με τη χρήση του `#` είναι επίσης δυνατό να στοχεύσουμε σε μια συγκεκριμένη επικεφαλίδα στη σελίδα. - `[#Heading]` -> `<a href="#toc-heading">Heading</a>` (επικεφαλίδα στην τρέχουσα σελίδα) - `[Page name#Heading]` -> `<a href="/en/page-name#toc-heading">Page name</a>` -Σύνδεσμος προς την αρχική σελίδα της ενότητας: (`@home` είναι ειδικός όρος για την αρχική σελίδα του τμήματος) +Σύνδεσμος στην αρχική σελίδα της ενότητας: (`@home` είναι μια ειδική έκφραση για την αρχική σελίδα της ενότητας) - `[link text |@home]` -> `<a href="/en/">link text</a>` - `[link text |tracy:]` -> `<a href="//tracy.nette.org/en/">link text</a>` -Σύνδεσμοι στην τεκμηρίωση API .[#toc-links-to-api-documentation] ----------------------------------------------------------------- +Σύνδεσμοι στην Τεκμηρίωση API +----------------------------- -Χρησιμοποιείτε πάντα τους ακόλουθους συμβολισμούς: +Πάντα να τους αναφέρετε μόνο χρησιμοποιώντας αυτή τη γραφή: - `[api:Nette\SmartObject]` -> [api:Nette\SmartObject] - `[api:Nette\Forms\Form::setTranslator()]` -> [api:Nette\Forms\Form::setTranslator()] - `[api:Nette\Forms\Form::$onSubmit]` -> [api:Nette\Forms\Form::$onSubmit] - `[api:Nette\Forms\Form::Required]` -> [api:Nette\Forms\Form::Required] -Πλήρως προσδιορισμένα ονόματα χρησιμοποιούνται μόνο στην πρώτη αναφορά. Για άλλους συνδέσμους, χρησιμοποιήστε ένα απλουστευμένο όνομα: +Χρησιμοποιήστε πλήρως προσδιορισμένα ονόματα μόνο στην πρώτη αναφορά. Για επόμενους συνδέσμους χρησιμοποιήστε το απλοποιημένο όνομα: - `[Form::setTranslator() |api:Nette\Forms\Form::setTranslator()]` -> [Form::setTranslator() |api:Nette\Forms\Form::setTranslator()] -Σύνδεσμοι στην τεκμηρίωση PHP .[#toc-links-to-php-documentation] ----------------------------------------------------------------- +Σύνδεσμοι στην Τεκμηρίωση PHP +----------------------------- - `[php:substr]` -> [php:substr] -Πηγαίος κώδικας .[#toc-source-code] -=================================== +Πηγαίος Κώδικας +=============== -Το μπλοκ κώδικα αρχίζει με <code>```lang</code> και τελειώνει με <code>```</code>. Οι υποστηριζόμενες γλώσσες είναι `php`, `latte`, `neon`, `html`, `css`, `js` και `sql`. Χρησιμοποιείτε πάντα tabs για εσοχή. +Ένα μπλοκ κώδικα ξεκινά με <code>```lang</code> και τελειώνει με <code>```</code>. Οι υποστηριζόμενες γλώσσες είναι `php`, `latte`, `neon`, `html`, `css`, `js` και `sql`. Για την εσοχή χρησιμοποιείτε πάντα tabulators. ``` ```php @@ -63,7 +63,7 @@ ``` ``` -Μπορείτε επίσης να καθορίσετε το όνομα του αρχείου ως<code>```php .{file: ArrayTest.php}</code> και το μπλοκ κώδικα θα αποδοθεί με αυτόν τον τρόπο: +Μπορείτε επίσης να αναφέρετε το όνομα του αρχείου ως <code>```php .{file: ArrayTest.php}</code> και το μπλοκ κώδικα θα αποδοθεί με αυτόν τον τρόπο: ```php .{file: ArrayTest.php} public function renderPage($id) @@ -72,71 +72,71 @@ public function renderPage($id) ``` -. .[#toc-headings] -================== +Επικεφαλίδες +============ -Κορυφαία επικεφαλίδα (όνομα σελίδας) υπογραμμισμένη με αστέρια (`*`). For normal headings use equal signs (`=`) and then hyphens (`-`). +Την υψηλότερη επικεφαλίδα (δηλαδή τον τίτλο της σελίδας) υπογραμμίστε την με αστερίσκους (`***`). Για τον διαχωρισμό ενοτήτων χρησιμοποιήστε ίσον (`===`). Τις υπόλοιπες επικεφαλίδες υπογραμμίστε τις με ίσον (`===`) και στη συνέχεια με παύλες (`---`): ``` -MVC Applications & Presenters -***************************** +Εφαρμογές MVC & Presenters +************************** ... -Link Creation -============= +Δημιουργία Συνδέσμων +==================== ... -Links in Templates ------------------- +Σύνδεσμοι στα Templates +----------------------- ... ``` -Πλαίσια και στυλ .[#toc-boxes-and-styles] -========================================= +Πλαίσια και Στυλ +================ -Επικεφαλής παράγραφος που επισημαίνεται με κλάση `.[perex]` .[perex] +Το perex το επισημαίνουμε με την κλάση `.[perex]` .[perex] -Σημειώσεις σημειωμένες με τάξη `.[note]` .[note] +Τη σημείωση την επισημαίνουμε με την κλάση `.[note]` .[note] -Συμβουλή σημειωμένη με class `.[tip]` .[tip] +Τη συμβουλή την επισημαίνουμε με την κλάση `.[tip]` .[tip] -Προειδοποίηση επισημαίνεται με την κλάση `.[caution]` .[caution] +Την προειδοποίηση την επισημαίνουμε με την κλάση `.[caution]` .[caution] -Έντονη προειδοποίηση με την ένδειξη class `.[warning]` .[warning] +Μια πιο έντονη προειδοποίηση την επισημαίνουμε με την κλάση `.[warning]` .[warning] Αριθμός έκδοσης `.{data-version:2.4.10}` .{data-version:2.4.10} -Οι κλάσεις πρέπει να γράφονται πριν από τη σχετική γραμμή: +Γράψτε τις κλάσεις πριν από τη γραμμή: ``` -.[note] -This is a note. +.[perex] +Αυτό είναι το perex. ``` -Σημειώστε ότι πλαίσια όπως `.[tip]` τραβούν την προσοχή και επομένως θα πρέπει να χρησιμοποιούνται για την έμφαση και όχι για λιγότερο σημαντικές πληροφορίες. +Παρακαλούμε λάβετε υπόψη ότι τα πλαίσια όπως το `.[tip]` "τραβούν" τα μάτια, επομένως χρησιμοποιούνται για έμφαση, όχι για λιγότερο σημαντικές πληροφορίες. Γι' αυτό χρησιμοποιήστε τα με τη μέγιστη φειδώ. -Πίνακας περιεχομένων .[#toc-table-of-contents] -============================================== +Πίνακας Περιεχομένων +==================== -Ο πίνακας περιεχομένων (σύνδεσμοι στην πλαϊνή γραμμή) δημιουργείται αυτόματα όταν η σελίδα είναι μεγαλύτερη από 4 000 bytes. Αυτή η προεπιλεγμένη συμπεριφορά μπορεί να αλλάξει με ένα `{{toc}}` [meta tag |#meta-tags]. Το κείμενο για το TOC λαμβάνεται από προεπιλογή από την επικεφαλίδα, αλλά είναι δυνατόν να χρησιμοποιηθεί διαφορετικό κείμενο με ένα `.{toc}` τροποποιητή. Αυτό είναι ιδιαίτερα χρήσιμο για μεγαλύτερες επικεφαλίδες. +Ο πίνακας περιεχομένων (σύνδεσμοι στο δεξί μενού) δημιουργείται αυτόματα για όλες τις σελίδες των οποίων το μέγεθος υπερβαίνει τα 4.000 bytes. Αυτή η προεπιλεγμένη συμπεριφορά μπορεί να τροποποιηθεί χρησιμοποιώντας τα [#meta tags] `{{toc}}`. Το κείμενο που αποτελεί τα περιεχόμενα λαμβάνεται συνήθως απευθείας από το κείμενο των επικεφαλίδων, αλλά με τον τροποποιητή `.{toc}` είναι δυνατό να εμφανιστεί στα περιεχόμενα διαφορετικό κείμενο, πράγμα που είναι χρήσιμο κυρίως για μακροσκελείς επικεφαλίδες. ``` -Long and Intelligent Heading .{toc: A Different Text for TOC} -============================================================= +Μακροσκελής και Έξυπνη Επικεφαλίδα .{toc: Οποιοδήποτε άλλο κείμενο εμφανίζεται στα περιεχόμενα} +=============================================================================================== ``` -Ετικέτες μετα .[#toc-meta-tags] -=============================== +Meta Tags +========= -- ορίζοντας τον τίτλο της σελίδας σας (σε `<title>` και breadcrumbs) `{{title: Another name}}` -- ανακατεύθυνση `{{redirect: pla:cs}}` - δείτε [συνδέσμους |#links] -- επιβολή `{{toc}}` ή απενεργοποίηση `{{toc: no}}` πίνακας περιεχομένου +- Ορισμός προσαρμοσμένου τίτλου σελίδας (στο `<title>` και στην πλοήγηση breadcrumb) `{{title: Άλλος τίτλος}}` +- Ανακατεύθυνση `{{redirect: pla:cs}}` - βλ. [#Σύνδεσμοι] +- Επιβολή `{{toc}}` ή απενεργοποίηση `{{toc: no}}` του αυτόματου πίνακα περιεχομένων (πλαίσιο με συνδέσμους στις επιμέρους επικεφαλίδες) {{priority: -1}} diff --git a/contributing/en/@home.texy b/contributing/en/@home.texy index f7656b64ca..8816352b0f 100644 --- a/contributing/en/@home.texy +++ b/contributing/en/@home.texy @@ -2,16 +2,16 @@ Become a Contributor to Nette ***************************** .[perex] -Take a look at how you can get involved in our open source project. Learn the steps for contributing to the source code and documentation, and join the network of developers who are dedicated to improving Nette. +Find out how you can get involved in our open source project. Learn the procedures for contributing to the source code and documentation, and become part of the community of developers who actively participate in improving Nette. **Code** -- [Contributing to code |code] +- [Contributing to Code |code] - [Coding Standards |coding-standard] **Documentation** -- [Contributing to documentation |documentation] -- [Documentation syntax |syntax] +- [Contributing to Documentation |documentation] +- [Documentation Syntax |syntax] - "Preview editor":https://editor.nette.org diff --git a/contributing/en/@left-menu.texy b/contributing/en/@left-menu.texy index e4773a2437..967653c863 100644 --- a/contributing/en/@left-menu.texy +++ b/contributing/en/@left-menu.texy @@ -1,10 +1,10 @@ Code **** -- [Contributing to code |code] +- [Contributing to Code |code] - [Coding Standards |coding-standard] Documentation ************* -- [Contributing to documentation |documentation] -- [Documentation syntax |syntax] -- "Preview editor":https://editor.nette.org +- [Contributing to Documentation |documentation] +- [Documentation Syntax |syntax] +- "Preview Editor":https://editor.nette.org diff --git a/contributing/en/code.texy b/contributing/en/code.texy index 6714b027ad..d99f100765 100644 --- a/contributing/en/code.texy +++ b/contributing/en/code.texy @@ -2,13 +2,13 @@ Contributing to Code ******************** .[perex] -Are you planning to contribute to the Nette Framework and need to familiarize yourself with the rules and procedures? This beginner's guide will walk you through the steps to effectively contribute to the code, work with repositories, and implement changes. +Are you planning to contribute to the Nette Framework and need to familiarize yourself with the rules and procedures? This beginner's guide will walk you through the steps to effectively contribute code, work with repositories, and implement changes. Procedure ========= -To contribute to the code, it is essential to have an account on [GitHub|https://github.com] and be familiar with the basics of working with the Git version control system. If you are not familiar with Git, you can check out the [git - the simple guide|https://rogerdudler.github.io/git-guide/] and consider using one of the many [graphical clients|https://git-scm.com/downloads/guis]. +To contribute code, it is essential to have an account on [GitHub|https://github.com] and be familiar with the basics of working with the Git version control system. If you are not familiar with Git, you can check out the [git - the simple guide|https://rogerdudler.github.io/git-guide/] and consider using one of the many [graphical clients|https://git-scm.com/downloads/guis]. Preparing the Environment and Repository @@ -18,7 +18,7 @@ Preparing the Environment and Repository 2) [Clone|https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] this repository to your computer 3) Install the dependencies, including [Nette Tester|tester:], using the `composer install` command 4) Verify that the tests are working by running `composer tester` -5) Create a [new branch|#New Branch] based on the latest released version +5) Create a [#new branch] based on the latest released version Implementing Your Own Changes @@ -29,7 +29,7 @@ Now you can make your own code adjustments: 1) Implement the desired changes and do not forget about the tests 2) Make sure the tests run successfully using `composer tester` 3) Check if the code meets the [#coding standards] -4) Save (commit) the changes with a description in [this format|#Commit Description] +4) Save (commit) the changes with a description in [this format |#Commit Description] You can create multiple commits, one for each logical step. Each commit should be meaningful on its own. @@ -41,7 +41,7 @@ Once you are satisfied with the changes, you can submit them: 1) Push the changes to GitHub to your fork 2) From there, submit them to the Nette repository by creating a [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) -3) Provide [sufficient information|#pull request description] in the description +3) Provide [sufficient information |#Pull Request Description] in the description Incorporating Feedback @@ -65,7 +65,7 @@ If you have changed functionality or added a new one, don't forget to [add it to New Branch ========== -If possible, make changes against the latest released version, i.e., the last tag in the branch. For the tag v3.2.1, create a branch using this command: +If possible, make changes against the latest released version, i.e., the last tag in the branch. For the tag `v3.2.1`, create a branch using this command: ```shell git checkout -b new_branch_name v3.2.1 @@ -75,13 +75,13 @@ git checkout -b new_branch_name v3.2.1 Coding Standards ================ -Your code must meet the [coding standard] used in the Nette Framework. There is an automatic tool available for checking and fixing the code. You can install it **globally** through Composer to a folder of your choice: +Your code must meet the [coding standard] used in the Nette Framework. An automatic tool is available for checking and fixing the code. You can install it **globally** via Composer into a folder of your choice: ```shell composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Now you should be able to run the tool in the terminal. The first command checks and the second one fixes the code in the `src` and `tests` folders in the current directory: +Now you should be able to run the tool in the terminal. The first command checks, and the second one fixes the code in the `src` and `tests` folders in the current directory: ```shell /path/to/nette-coding-standard/ecs check @@ -94,11 +94,11 @@ Commit Description In Nette, commit subjects have the following format: `Presenter: fixed AJAX detection [Closes #69]` -- area followed by a colon -- purpose of the commit in the past tense; if possible, start with words like: "added .(new feature)", "fixed .(correction)", "refactored .(code change without behavior change)", changed, removed -- if the commit breaks backward compatibility, add "BC break" -- any connection to the issue tracker, such as `(#123)` or `[Closes #69]` -- after the subject, there can be one blank line followed by a more detailed description, including, for example, links to the forum +- Area followed by a colon +- Purpose of the commit in the past tense; if possible, start with words like: "added (new feature)", "fixed (correction)", "refactored (code change without behavior change)", "changed", "removed" +- If the commit breaks backward compatibility, add "BC break" +- Any link to the issue tracker, such as `(#123)` or `[Closes #69]` +- After the subject, there can be one blank line followed by a more detailed description, including, for example, links to the forum Pull Request Description diff --git a/contributing/en/coding-standard.texy b/contributing/en/coding-standard.texy index 81a8a48176..df9a0a86cb 100644 --- a/contributing/en/coding-standard.texy +++ b/contributing/en/coding-standard.texy @@ -1,8 +1,8 @@ Coding Standard *************** -This document describes rules and recommendations for developing Nette. When contributing code to Nette, you must follow them. The easiest way how to do it is to imitate the existing code. -The idea is to make all the code look like it was written by one person. .[perex] +.[perex] +This document describes the rules and recommendations for developing Nette. When contributing code to Nette, you must follow them. The easiest way to do this is to imitate the existing code. The goal is to make all the code look as if it were written by one person. Nette Coding Standard corresponds to [PSR-12 Extended Coding Style |https://www.php-fig.org/psr/psr-12/] with two main exceptions: it uses [#tabs instead of spaces] for indentation, and uses [PascalCase for class constants|https://blog.nette.org/en/for-less-screaming-in-the-code]. @@ -11,34 +11,34 @@ General Rules ============= - Every PHP file must contain `declare(strict_types=1)` -- Two empty lines are used to separate methods for better readability. -- The reason for using shut-up operator must be documented: `@mkdir($dir); // @ - directory may exist` -- If weak typed comparison operator is used (ie. `==`, `!=`, ...), the intention must be documented: `// == to accept null` -- You can write more exceptions into one file `exceptions.php` -- The visibility of methods is not specified for interfaces because they are always public. -- Each property, return value, and parameter must have a type specified. On the other hand, for final constants, we never specify the type because it is obvious. -- Single quotes should be used to delimit the string, except when the literal itself contains apostrophes. +- Two empty lines are used to separate methods for better readability +- The reason for using the shut-up operator (`@`) must be documented: `@mkdir($dir); // @ - directory may exist` +- If a weak typed comparison operator is used (i.e., `==`, `!=`, ...), the intention must be documented: `// == to accept null` +- You can write multiple exception classes into a single file named `exceptions.php` +- The visibility of methods is not specified for interfaces because they are always public +- Each property, return value, and parameter must have a type specified. Conversely, for final constants, we never specify the type because it is obvious +- Single quotes should be used to delimit strings, except when the literal itself contains apostrophes Naming Conventions ================== -- Avoid using abbreviations unless the full name is excessive. -- Use uppercase for two-letter abbreviations, and pascal/camel case for longer abbreviations. -- Use a noun or noun phrase for class name. -- Class names must contain not only specificity (`Array`) but also generality (`ArrayIterator`). The exception are PHP attributes. -- "Class constants and enums should use PascalCaps":https://blog.nette.org/en/for-less-screaming-in-the-code. -- "Interfaces and abstract classes should not contain prefixes or postfixes":https://blog.nette.org/en/prefixes-and-suffixes-do-not-belong-in-interface-names like `Abstract`, `Interface` or `I`. +- Avoid using abbreviations unless the full name is excessive +- Use uppercase for two-letter abbreviations, and PascalCase/camelCase for longer abbreviations +- Use a noun or noun phrase for the class name +- Class names must contain not only specificity (`Array`) but also generality (`ArrayIterator`). The exception are PHP attributes +- "Class constants and enums should use PascalCaps":https://blog.nette.org/en/for-less-screaming-in-the-code +- "Interfaces and abstract classes should not contain prefixes or suffixes":https://blog.nette.org/en/prefixes-and-suffixes-do-not-belong-in-interface-names like `Abstract`, `Interface` or `I` Wrapping and Braces =================== -Nette Coding Standard corresponds to PSR-12 (or PER Coding Style), in some points it specifies it more or modifies it: +Nette Coding Standard corresponds to PSR-12 (or PER Coding Style), but specifies or modifies it in some points: -- arrow functions are written without a space before the parenthesis, i.e. `fn($a) => $b` -- no empty line is required between different types of `use` import statements -- the return type of the function/method and the opening parenthesis should be placed on separate lines for better readability: +- Arrow functions are written without a space before the parenthesis, i.e., `fn($a) => $b` +- No empty line is required between different types of `use` import statements +- The return type of a function/method and the opening curly brace are always on separate lines: ```php public function find( @@ -50,20 +50,24 @@ Nette Coding Standard corresponds to PSR-12 (or PER Coding Style), in some point } ``` +The opening curly brace on a separate line is important for visually separating the function/method signature from the body. If the signature is on one line, the separation is clear (image on the left). If it's on multiple lines, in PSR the signature and body blend together (middle), while in the Nette standard they remain separated (right): + +[* new-line-after.webp *] + Documentation Blocks (phpDoc) ============================= -The main rule: never duplicate any signature information like parameter type or return type with no added value. +The main rule: **Never duplicate** any signature information like parameter type or return type without adding value. -Documentation block for class definition: +Documentation block for a class definition: -- Starts by a class description. -- Empty line follows. -- The `@property` (or `@property-read`, `@property-write`) annotations follow, one by line. Syntax is: annotation, space, type, space, $name. -- The `@method` annotations follow, one by line. Syntax is: annotation, space, return type, space, name(type $param, ...). -- The `@author` annotation is omitted. The authorship is kept in a source code history. -- The `@internal` or `@deprecated` annotations can be used. +- Starts with a description of the class +- Followed by an empty line +- Followed by `@property` (or `@property-read`, `@property-write`) annotations, one per line. Syntax: annotation, space, type, space, `$name` +- Followed by `@method` annotations, one per line. Syntax: annotation, space, return type, space, `name(type $param, ...)` +- The `@author` annotation is omitted. Authorship is kept in the source code history +- The `@internal` or `@deprecated` annotations can be used ```php /** @@ -76,23 +80,23 @@ Documentation block for class definition: */ ``` -Documentation block for property that contains only `@var` annotation should be in single line: +A documentation block for a property containing only the `@var` annotation should be on a single line: ```php /** @var string[] */ private array $name; ``` -Documentation block for method definition: +Documentation block for a method definition: -- Starts by a short method description. -- No empty line. -- The `@param` annotations, one by line. -- The `@return` annotation. -- The `@throws` annotations, one by line. -- The `@internal` or `@deprecated` annotations can be used. +- Starts with a short description of the method +- No empty line +- `@param` annotations, one per line +- `@return` annotation +- `@throws` annotations, one per line +- The `@internal` or `@deprecated` annotations can be used -Every annotation is followed by one space, except for the `@param` which is followed by two spaces for better readability. +Every annotation is followed by one space, except for `@param`, which is followed by two spaces for better readability. ```php /** @@ -105,21 +109,37 @@ public function find(string $dir, array $options): array ``` +Global Functions and Constants +============================== + +Global functions and constants are written without a leading backslash, i.e., `count($arr)` not `\count($arr)`. For functions that PHP can optimize, add `use function` at the beginning of the file so the compiler can translate them more efficiently. These include functions like `count`, `strlen`, `is_array`, `is_string`, `is_scalar`, `sprintf`, etc. Functions are listed on a single line to keep the import block compact: + +```php +use Nette; +use function count, is_array, is_scalar, sprintf; +``` + +Occasionally, we also import constants whose value knowledge may help the compiler: + +```php +use const PHP_OS_FAMILY; +``` + + Tabs Instead of Spaces ====================== Tabs have several advantages over spaces: -- indent size is customisable in editors & "web":https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size -- they do not impose the user's indentation size preference on the code, so the code is more portable -- you can type them with one keystroke (anywhere, not just in editors that turn tabs into spaces) -- indentation is their purpose -- respect the needs of visually impaired and blind colleagues +- The size of the indentation is customizable in editors and on the "web":https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size +- They do not impose the user's indentation size preference on the code, making the code more portable +- They can be typed with a single keystroke (anywhere, not just in editors that convert tabs to spaces) +- Indentation is their purpose +- They respect the needs of visually impaired and blind colleagues By using tabs in our projects, we allow for width customization, which may seem unnecessary to most people, but is essential for people with visual impairments. -For blind programmers who use braille displays, each space is represented by a braille cell and takes up valuable space. So if the default indentation is 4 spaces, a 3rd level indentation wastes 12 braille cells before the code begins. -On a 40-cell display, which is the most commonly used on laptops, that's more than a quarter of the available cells wasted without any information. +For blind programmers who use braille displays, each space represents a braille cell. So if the default indentation is 4 spaces, a 3rd level indentation wastes 12 valuable braille cells before the code even begins. On a 40-cell display, the most common for laptops, that's more than a quarter of the available cells wasted without providing any information. {{priority: -1}} diff --git a/contributing/en/documentation.texy b/contributing/en/documentation.texy index 0aec6761dc..3d2353444b 100644 --- a/contributing/en/documentation.texy +++ b/contributing/en/documentation.texy @@ -2,7 +2,7 @@ Contributing to Documentation ***************************** .[perex] -Contributing to documentation is one of the most valuable activities, as it helps others understand the framework. +Contributing to the documentation is one of the most valuable activities, as it helps others understand the framework. How to Write? @@ -10,23 +10,23 @@ How to Write? Documentation is primarily intended for people who are new to the topic. Therefore, it should meet several important points: -- Start with simple and general topics. Move on to more advanced topics at the end -- Try to explain the topic as clearly as possible. For example, try explaining the topic to a colleague first -- Only provide information that the user actually needs to know for a given topic -- Make sure your information is accurate. Test every code -- Be concise - cut what you write in half. And then feel free to do it again -- Use highlighting sparingly, from bold fonts to frames like `.[note]` -- Follow the [Coding Standard] in the code +- Start with simple and general concepts. Move on to more advanced topics only at the end. +- Try to explain the topic as clearly as possible. For example, try explaining it to a colleague first. +- Provide only the information that the user actually needs for the given topic. +- Verify that your information is accurate. Test every piece of code. +- Be concise – cut what you write in half. Then feel free to do it again. +- Use highlighting sparingly, from bold text to boxes like `.[note]`. +- Follow the [Coding Standard] in the code examples. -Also, learn the [syntax]. For a preview of the article during writing, you can use the [preview editor |https://editor.nette.org/]. +Also, learn the [syntax]. To preview the article while writing, you can use the [preview editor |https://editor.nette.org/]. -Language Mutations ------------------- +Language Versions +----------------- -English is the primary language, so your changes should be in English. If English is not your strong suit, use [DeepL Translator |https://www.deepl.com/translator] and others will check your text. +English is the primary language, so your changes should ideally be in English. If English is not your strong suit, use [DeepL Translator |https://www.deepl.com/translator] and others will review your text. -Translation into other languages will be done automatically after approval and fine-tuning of your edit. +Translation into other languages will be done automatically after your edit is approved and finalized. Trivial Edits @@ -36,34 +36,33 @@ To contribute to the documentation, you need to have an account on [GitHub |http The easiest way to make a small change in the documentation is to use the links at the end of each page: -- *Show on GitHub* opens the source version of the page on GitHub. Then just press the `E` button and you can start editing (you must be logged in to GitHub) -- *Open preview* opens an editor where you can immediately see the final visual form +- *Show on GitHub* opens the source version of the page on GitHub. Then just press the `E` key to start editing (you must be logged in to GitHub). +- *Open preview* opens an editor where you can immediately see the final visual appearance. -Because the [preview editor |https://editor.nette.org/] does not have the ability to save changes directly to GitHub, you need to copy the source text to the clipboard (using the *Copy to clipboard button*) and then paste it into the editor on GitHub. -Below the editing field is a form for submission. Here, don't forget to briefly summarize and explain the reason for your edit. After submitting, a so-called pull request (PR) is created, which can be further edited. +Since the [preview editor |https://editor.nette.org/] cannot save changes directly to GitHub, after finishing your edits, you need to copy the source text to the clipboard (using the *Copy to clipboard* button) and then paste it into the editor on GitHub. Below the editing field is a submission form. Here, don't forget to briefly summarize and explain the reason for your edit. After submitting, a pull request (PR) is created, which can be further edited. Larger Edits ------------ -It is more appropriate to be familiar with the basics of working with the Git version control system rather than relying solely on the GitHub interface. If you're not familiar with Git, you can refer to the [git - the simple guide |https://rogerdudler.github.io/git-guide/] and consider using one of the many [graphical clients |https://git-scm.com/downloads/guis] available. +Instead of relying solely on the GitHub interface, it's better to be familiar with the basics of working with the Git version control system. If you're not familiar with Git, you can refer to the [git - the simple guide |https://rogerdudler.github.io/git-guide/] and consider using one of the many available [graphical clients |https://git-scm.com/downloads/guis]. -Edit the documentation in the following way: +Edit the documentation as follows: -1) on GitHub, create a [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] of the [nette/docs |https://github.com/nette/docs] repository -2) [clone |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] this repository to your computer -3) then, make changes in the [appropriate branch|#Documentation Structure] -4) check for extra spaces in the text using the [Code-Checker |code-checker:] tool -5) save (commit) the changes -6) if you are satisfied with the changes, push them to GitHub to your fork -7) from there, submit them to the `nette/docs` repository by creating a [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) +1) On GitHub, create a [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] of the [nette/docs |https://github.com/nette/docs] repository. +2) [Clone |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] this repository to your computer. +3) Then, make changes in the [appropriate branch |#Documentation Structure]. +4) Check for extra spaces in the text using the [Code-Checker |code-checker:] tool. +5) Save (commit) the changes. +6) If you are satisfied with the changes, push them to GitHub to your fork. +7) From there, submit them to the `nette/docs` repository by creating a [pull request|https://help.github.com/articles/creating-a-pull-request] (PR). -It is common to receive comments with suggestions. Keep track of the proposed changes and incorporate them. Add the suggested changes as new commits and resend them to GitHub. Never create a new pull request just to modify an existing one. +It is common to receive comments with suggestions. Keep track of the proposed changes and incorporate them. Add the suggested changes as new commits and push them to GitHub again. Never create a new pull request just to modify an existing one. Documentation Structure ----------------------- -The entire documentation is located on GitHub in the [nette/docs |https://github.com/nette/docs] repository. The current version is in the master branch, while older versions are located in branches like `doc-3.x`, `doc-2.x`. +The entire documentation is located on GitHub in the [nette/docs |https://github.com/nette/docs] repository. The current version is in the `master` branch, while older versions are located in branches like `doc-3.x`, `doc-2.x`. -The content of each branch is divided into main folders representing individual areas of documentation. For example, `application/` corresponds to https://doc.nette.org/cs/application, `latte/` corresponds to https://latte.nette.org, etc. Each of these folders contains subfolders representing language mutations (`cs`, `en`, ...) and optionally a `files` subfolder with images that can be inserted into the pages in the documentation. +The content of each branch is divided into main folders representing individual documentation areas. For example, `application/` corresponds to `https://doc.nette.org/en/application`, `latte/` corresponds to `https://latte.nette.org`, etc. Each of these folders contains subfolders representing language versions (`cs`, `en`, ...) and optionally a `files` subfolder with images that can be included in the documentation pages. diff --git a/contributing/en/syntax.texy b/contributing/en/syntax.texy index 74d5bc499f..c7d81ac54f 100644 --- a/contributing/en/syntax.texy +++ b/contributing/en/syntax.texy @@ -1,29 +1,29 @@ Documentation Syntax ******************** -Documentation uses Markdown & [Texy syntax |https://texy.info/en/syntax] with several enhancements. +Documentation uses Markdown & [Texy syntax |https://texy.nette.org/syntax] with several enhancements. Links ===== -For internal references, the notation in square brackets `[link]` is used. This is either in the form with a vertical bar `[link text |link target]`, or in the abbreviated form `[link text]` if the target is the same as the text (after transformation to lower case and hyphens): +For internal links, notation in square brackets `[link]` is used. This is either in the form with a pipe `[link text |link target]`, or in the abbreviated form `[link text]` if the target is the same as the text (after transformation to lowercase and hyphens): - `[Page name]` -> `<a href="/en/page-name">Page name</a>` - `[link text |Page name]` -> `<a href="/en/page-name">link text</a>` -We can link to another language or another section. A section is a Nette library (e.g. `forms`, `latte`, etc.) or special sections like `best-practices`, `quickstart`, etc: +We can link to another language version or another section. A section refers to a Nette library (e.g., `forms`, `latte`, etc.) or special sections like `best-practices`, `quickstart`, etc.: - `[cs:Page name]` -> `<a href="/cs/page-name">Page name</a>` (same section, different language) - `[tracy:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (different section, same language) - `[tracy:cs:Page name]` -> `<a href="//tracy.nette.org/cs/page-name">Page name</a>` (different section and language) -It's also possible to target specific heading on page with `#`. +It's also possible to target a specific heading on the page using `#`. - `[#Heading]` -> `<a href="#toc-heading">Heading</a>` (heading on the current page) - `[Page name#Heading]` -> `<a href="/en/page-name#toc-heading">Page name</a>` -Link to section's home page: (`@home` is special term for section's home page) +Link to the section's home page: (`@home` is a special term for the section's home page) - `[link text |@home]` -> `<a href="/en/">link text</a>` - `[link text |tracy:]` -> `<a href="//tracy.nette.org/en/">link text</a>` @@ -32,14 +32,14 @@ Link to section's home page: (`@home` is special term for section's home page) Links to API Documentation -------------------------- -Always use the following notations: +Always use the following notation: - `[api:Nette\SmartObject]` -> [api:Nette\SmartObject] - `[api:Nette\Forms\Form::setTranslator()]` -> [api:Nette\Forms\Form::setTranslator()] - `[api:Nette\Forms\Form::$onSubmit]` -> [api:Nette\Forms\Form::$onSubmit] - `[api:Nette\Forms\Form::Required]` -> [api:Nette\Forms\Form::Required] -Fully qualified names use only in the first mention. For other links, use a simplified name: +Use fully qualified names only in the first mention. For subsequent links, use a simplified name: - `[Form::setTranslator() |api:Nette\Forms\Form::setTranslator()]` -> [Form::setTranslator() |api:Nette\Forms\Form::setTranslator()] @@ -53,7 +53,7 @@ Links to PHP Documentation Source Code =========== -Code block starts with <code>```lang</code> and ends with <code>```</code>. Supported languages are `php`, `latte`, `neon`, `html`, `css`, `js` and `sql`. Always use tabs for indenting. +A code block starts with <code>```lang</code> and ends with <code>```</code>. Supported languages are `php`, `latte`, `neon`, `html`, `css`, `js`, and `sql`. Always use tabs for indentation. ``` ```php @@ -63,7 +63,7 @@ Code block starts with <code>```lang</code> and ends with <code>` ``` ``` -You can also specify the filename as <code>```php .{file: ArrayTest.php}</code> and the code block will be rendered this way: +You can also specify the filename as <code>```php .{file: ArrayTest.php}</code>, and the code block will be rendered this way: ```php .{file: ArrayTest.php} public function renderPage($id) @@ -75,7 +75,7 @@ public function renderPage($id) Headings ======== -Top heading (page name) underline with stars (`*`). For normal headings use equal signs (`=`) and then hyphens (`-`). +Underline the top heading (page name) with asterisks (`*`). Use equal signs (`=`) to separate sections. Underline headings first with equal signs (`=`) and then with hyphens (`-`): ``` MVC Applications & Presenters @@ -97,32 +97,32 @@ Links in Templates Boxes and Styles ================ -Lead paragraph marked with class `.[perex]` .[perex] +Perex marked with class `.[perex]` .[perex] -Notes marked with class `.[note]` .[note] +Note marked with class `.[note]` .[note] Tip marked with class `.[tip]` .[tip] -Warning marked with class `.[caution]` .[caution] +Caution marked with class `.[caution]` .[caution] Strong warning marked with class `.[warning]` .[warning] Version number `.{data-version:2.4.10}` .{data-version:2.4.10} -Classes should be written before the related line: +Classes should be written before the line they apply to: ``` -.[note] -This is a note. +.[perex] +This is the perex. ``` -Please note that boxes such as `.[tip]` draws attention and therefore should be used for emphasizing, not for less important information. +Please note that boxes like `.[tip]` draw attention and therefore should be used for emphasizing important information, not for less significant details. Use them sparingly. Table of Contents ================= -Table of contents (links in the sidebar) is automatically generated when page is longer than 4 000 bytes. This default behavior can be changed with a `{{toc}}` [meta tag |#meta-tags]. The text for TOC is taken by default from the heading but it is possible to use a different text with a `.{toc}` modifier. This is especially useful for longer headings. +A table of contents (links in the right sidebar) is automatically generated for all pages exceeding 4,000 bytes in size. This default behavior can be modified using the `{{toc}}` [#Meta Tags]. The text for the TOC is taken directly from the headings by default, but it's possible to display different text using the `.{toc}` modifier, which is useful for longer headings. ``` @@ -135,8 +135,8 @@ Long and Intelligent Heading .{toc: A Different Text for TOC} Meta Tags ========= -- setting your own page title (in `<title>` and breadcrumbs) `{{title: Another name}}` -- redirecting `{{redirect: pla:cs}}` - see [#links] -- enforcing `{{toc}}` or disabling `{{toc: no}}` table of content +- Set a custom page title (in `<title>` and breadcrumbs): `{{title: Another name}}` +- Redirect: `{{redirect: pla:cs}}` - see [#Links] +- Force `{{toc}}` or disable `{{toc: no}}` the automatic table of contents (box with links to headings). {{priority: -1}} diff --git a/contributing/es/@home.texy b/contributing/es/@home.texy index 4c5fd90d45..ce1d46d7d1 100644 --- a/contributing/es/@home.texy +++ b/contributing/es/@home.texy @@ -1,17 +1,17 @@ -Conviértase en colaborador de Nette -*********************************** +Conviértase en un contribuyente de Nette +**************************************** .[perex] -Echa un vistazo a cómo puedes participar en nuestro proyecto de código abierto. Conozca los pasos para contribuir al código fuente y a la documentación, y únase a la red de desarrolladores que se dedican a mejorar Nette. +Descubra cómo puede participar en nuestro proyecto de código abierto. Aprenda los procedimientos para contribuir al código fuente y la documentación, y forme parte de la comunidad de desarrolladores que participan activamente en la mejora de Nette. **Código** -- [Contribuir al código |code] -- [Normas de codificación |coding-standard] +- [¿Cómo contribuir al código? |code] +- [Estándar de codificación |coding-standard] **Documentación** -- [Contribución a la documentación|documentation] -- [Sintaxis de la documentación|syntax] -- "Vista previa del editor":https://editor.nette.org +- [¿Cómo contribuir a la documentación? |documentation] +- [Sintaxis de documentación |syntax] +- "Editor de vista previa":https://editor.nette.org diff --git a/contributing/es/@left-menu.texy b/contributing/es/@left-menu.texy index 40297ee9ed..ac70975ecc 100644 --- a/contributing/es/@left-menu.texy +++ b/contributing/es/@left-menu.texy @@ -1,10 +1,10 @@ Código ****** -- [Contribuir al código|code] -- [Normas de codificación |coding-standard] +- [¿Cómo contribuir al código? |code] +- [Estándar de codificación |coding-standard] Documentación ************* -- [Contribuir a la documentación |documentation] -- [Sintaxis de la documentación|syntax] -- "Vista previa del editor":https://editor.nette.org +- [¿Cómo contribuir a la documentación? |documentation] +- [Sintaxis de documentación |syntax] +- "Editor de vista previa":https://editor.nette.org diff --git a/contributing/es/code.texy b/contributing/es/code.texy index 4d35b1187d..3fdf50969b 100644 --- a/contributing/es/code.texy +++ b/contributing/es/code.texy @@ -1,112 +1,112 @@ -Contribuir al código -******************** +Cómo contribuir al código +************************* .[perex] -¿Está pensando en contribuir al marco de Nette y necesita familiarizarse con las normas y procedimientos? Esta guía para principiantes le guiará a través de los pasos necesarios para contribuir eficazmente al código, trabajar con repositorios e implementar cambios. +¿Te preparas para contribuir a Nette Framework y necesitas orientarte sobre las reglas y procedimientos? Esta guía para principiantes te mostrará paso a paso cómo contribuir eficazmente al código, trabajar con repositorios e implementar cambios. -Procedimiento .[#toc-procedure] -=============================== +Procedimiento +============= -Para contribuir al código, es esencial tener una cuenta en [GitHub |https://github.com] y estar familiarizado con los conceptos básicos de trabajo con el sistema de control de versiones Git. Si no estás familiarizado con Git, puedes consultar la [guía git - the simple guide |https://rogerdudler.github.io/git-guide/] y considerar el uso de uno de los muchos [clientes gráficos |https://git-scm.com/downloads/guis]. +Para contribuir al código, es esencial tener una cuenta en [GitHub|https://github.com] y estar familiarizado con los fundamentos del trabajo con el sistema de control de versiones Git. Si no dominas Git, puedes consultar la guía [git - the simple guide |https://rogerdudler.github.io/git-guide/] y, opcionalmente, utilizar alguno de los muchos [clientes gráficos |https://git-scm.com/downloads/guis]. -Preparar el entorno y el repositorio .[#toc-preparing-the-environment-and-repository] -------------------------------------------------------------------------------------- +Preparación del entorno y del repositorio +----------------------------------------- -1) En GitHub, crea un [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] del repositorio del [paquete |www:packages] que pretendes modificar -2) [Clone |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] este repositorio en su ordenador -3) Instale las dependencias, incluyendo [Nette Tester |tester:], utilizando el comando `composer install` -4) Compruebe que las pruebas funcionan ejecutando `composer tester` -5) Cree una [nueva rama |#New Branch] basada en la última versión publicada +1) en GitHub, crea un [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] del repositorio del [paquete |www:packages] que vas a modificar +2) [clona |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] este repositorio en tu ordenador +3) instala las dependencias, incluido [Nette Tester |tester:], mediante el comando `composer install` +4) comprueba que las pruebas funcionan ejecutando `composer tester` +5) crea una [#nueva rama] basada en la última versión publicada -Implementar tus propios cambios .[#toc-implementing-your-own-changes] ---------------------------------------------------------------------- +Implementación de tus propios cambios +------------------------------------- -Ahora puede realizar sus propios ajustes en el código: +Ahora puedes realizar tus propias modificaciones en el código: -1) Implementa los cambios deseados y no te olvides de las pruebas -2) Asegúrate de que las pruebas se ejecutan correctamente `composer tester` -3) Comprueba si el código cumple las [normas de codificación |#coding standards] -4) Guarda (confirma) los cambios con una descripción en [este formato |#Commit Description] +1) programa los cambios deseados y no olvides las pruebas +2) asegúrate de que las pruebas se ejecutan correctamente usando `composer tester` +3) comprueba que el código cumple con los [#estándares de codificación] +4) guarda los cambios (haz commit) con una descripción en [este formato |#Descripción del commit] -Puedes crear varios commits, uno para cada paso lógico. Cada commit debe tener sentido por sí mismo. +Puedes crear varios commits, uno para cada paso lógico. Cada commit debe tener sentido por sí solo. -Envío de cambios .[#toc-submitting-changes] -------------------------------------------- +Envío de cambios +---------------- -Una vez que esté satisfecho con los cambios, puede enviarlos: +Una vez que estés satisfecho con los cambios, puedes enviarlos: -1) Empuje los cambios a GitHub a su bifurcación. -2) Desde allí, envíalos al repositorio de Nette creando un [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) -3) Proporcione [información suficiente |#pull request description] en la descripción +1) envía (haz push) los cambios a GitHub a tu fork +2) desde allí, envíalos al repositorio de Nette creando una [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) +3) proporciona [suficiente información |#Descripción de la pull request] en la descripción -Incorporación de comentarios .[#toc-incorporating-feedback] ------------------------------------------------------------ +Incorporación de comentarios +---------------------------- -Tus commits son ahora visibles para los demás. Es habitual recibir comentarios con sugerencias: +Tus commits ahora serán visibles para otros. Es común recibir comentarios con sugerencias: -1) Hacer un seguimiento de los cambios propuestos -2) Incorpórelos como nuevos commits o [fusiónelos con los |https://help.github.com/en/github/using-git/about-git-rebase]anteriores -3) Vuelve a enviar los commits a GitHub, y aparecerán automáticamente en el pull request +1) sigue las modificaciones propuestas +2) incorpóralas como nuevos commits o [combínalas con los anteriores |https://help.github.com/en/github/using-git/about-git-rebase] +3) vuelve a enviar los commits a GitHub y aparecerán automáticamente en la pull request -Nunca crees un nuevo pull request para modificar uno ya existente. +Nunca crees una nueva pull request para modificar una existente. -Documentación .[#toc-documentation] ------------------------------------ +Documentación +------------- -Si has cambiado alguna funcionalidad o añadido una nueva, no olvides [añadirla también a la |documentation] documentación. +Si has cambiado la funcionalidad o añadido una nueva, no olvides [añadirla también a la documentación |documentation]. -Nueva rama .[#toc-new-branch] -============================= +Nueva rama +========== -Si es posible, realice los cambios contra la última versión publicada, es decir, la última etiqueta de la rama. Para la etiqueta v3.2.1, cree una rama utilizando este comando: +Si es posible, realiza los cambios sobre la última versión publicada, es decir, la última etiqueta en la rama correspondiente. Para la etiqueta `v3.2.1`, crea una rama con este comando: ```shell -git checkout -b new_branch_name v3.2.1 +git checkout -b nombre_nueva_rama v3.2.1 ``` -Normas de codificación .[#toc-coding-standards] -=============================================== +Estándares de codificación +========================== -Tu código debe cumplir las [normas |coding standard] de codificación utilizadas en Nette Framework. Existe una herramienta automática para comprobar y corregir el código. Puedes instalarla **globalmente** a través de Composer en una carpeta de tu elección: +Tu código debe cumplir con el [estándar de codificación |coding-standard] utilizado en Nette Framework. Hay disponible una herramienta automática para comprobar y corregir el código. Se puede instalar a través de Composer **globalmente** en la carpeta que elijas: ```shell -composer create-project nette/coding-standard /path/to/nette-coding-standard +composer create-project nette/coding-standard /ruta/a/nette-coding-standard ``` -Ahora deberías poder ejecutar la herramienta en el terminal. El primer comando comprueba y el segundo corrige el código en las carpetas `src` y `tests` en el directorio actual: +Ahora deberías poder ejecutar la herramienta en la terminal. El primer comando comprueba y el segundo también corrige el código en las carpetas `src` y `tests` del directorio actual: ```shell -/path/to/nette-coding-standard/ecs check -/path/to/nette-coding-standard/ecs check --fix +/ruta/a/nette-coding-standard/ecs check +/ruta/a/nette-coding-standard/ecs check --fix ``` -Descripción del compromiso .[#toc-commit-description] -===================================================== +Descripción del commit +====================== -En Nette, los asuntos de commit tienen el siguiente formato: `Presenter: fixed AJAX detection [Closes #69]` +En Nette, los asuntos de los commits tienen el formato: `Presenter: fixed AJAX detection [Closes #69]` -- área seguida de dos puntos -- propósito del commit en pasado; si es posible, comience con palabras como: added, fixed, refactored, changed, removed -- si la confirmación rompe la compatibilidad con versiones anteriores, añada "BC break". -- cualquier conexión con el gestor de incidencias, como `(#123)` o `[Closes #69]` -- después del asunto, puede haber una línea en blanco seguida de una descripción más detallada, incluyendo, por ejemplo, enlaces al foro +- Área seguida de dos puntos. +- Propósito del commit en tiempo pasado; si es posible, comienza con una palabra como: "added (nueva característica añadida)", "fixed (corrección)", "refactored (cambio en el código sin cambio de comportamiento)", changed, removed. +- Si el commit rompe la compatibilidad hacia atrás, añade "BC break". +- Posible vínculo con el issue tracker como `(#123)` o `[Closes #69]`. +- Después del asunto puede seguir una línea vacía y luego una descripción más detallada, incluyendo, por ejemplo, enlaces al foro. -Descripción de la solicitud .[#toc-pull-request-description] -============================================================ +Descripción de la pull request +============================== -Al crear una pull request, la interfaz de GitHub te permitirá introducir un título y una descripción. Proporcione un título conciso e incluya tanta información como sea posible en la descripción sobre las razones de su cambio. +Al crear una pull request, la interfaz de GitHub te permitirá introducir un título y una descripción. Proporciona un título descriptivo y en la descripción ofrece la mayor cantidad de información posible sobre las razones de tu cambio. -Especifica también en la cabecera si se trata de una nueva función o de una corrección de errores y si puede causar problemas de retrocompatibilidad (BC break). Si existe una incidencia relacionada, vincúlela para que se cierre cuando se apruebe la solicitud de extracción. +También se mostrará un encabezado donde especificarás si se trata de una nueva función o una corrección de error y si puede haber una ruptura de la compatibilidad hacia atrás (BC break). Si hay un problema relacionado (issue), enlaza a él para que se cierre después de la aprobación de la pull request. ``` - bug fix / new feature? <!-- #issue numbers, if any --> diff --git a/contributing/es/coding-standard.texy b/contributing/es/coding-standard.texy index 37b8b1dba7..8bb007f4cc 100644 --- a/contributing/es/coding-standard.texy +++ b/contributing/es/coding-standard.texy @@ -1,44 +1,44 @@ -Norma de codificación -********************* +Estándar de codificación +************************ -Este documento describe las normas y recomendaciones para el desarrollo de Nette. Cuando contribuya con código a Nette, debe seguirlas. La forma más fácil de hacerlo es imitar el código existente. -La idea es hacer que todo el código parezca escrito por una sola persona. .[perex] +.[perex] +Este documento describe las reglas y recomendaciones para el desarrollo de Nette. Al contribuir con código a Nette, debes seguirlas. La forma más sencilla de hacerlo es imitar el código existente. El objetivo es que todo el código parezca escrito por una sola persona. -El Estándar de Codificación de Nette corresponde al [Estilo de Codificación Extendido PSR-12 |https://www.php-fig.org/psr/psr-12/] con dos excepciones principales: utiliza [tabuladores en lugar de espacios |#tabs instead of spaces] para la sangría, y utiliza [PascalCase para las constantes de clase |https://blog.nette.org/es/para-menos-gritos-en-el-codigo]. +El Estándar de Codificación de Nette corresponde al [PSR-12 Extended Coding Style |https://www.php-fig.org/psr/psr-12/] con dos excepciones principales: utiliza [#tabuladores en lugar de espacios] para la indentación y utiliza [PascalCase para las constantes de clase |https://blog.nette.org/es/for-less-screaming-in-the-code]. -Normas generales .[#toc-general-rules] -====================================== +Reglas generales +================ -- Cada archivo PHP debe contener `declare(strict_types=1)` -- Dos líneas vacías se utilizan para separar los métodos para una mejor legibilidad. -- La razón para usar el operador de cierre debe estar documentada: `@mkdir($dir); // @ - directory may exist` -- Si se usa un operador de comparación de tipo débil (ie. `==`, `!=`, ...), debe documentarse la intención: `// == to accept null` -- Puede escribir más excepciones en un archivo `exceptions.php` -- En las interfaces no se especifica la visibilidad de los métodos porque siempre son públicos. -- Cada propiedad, valor de retorno y parámetro debe tener un tipo especificado. En cambio, para las constantes finales, nunca se especifica el tipo porque es obvio. -- Deben utilizarse comillas simples para delimitar la cadena, excepto cuando el propio literal contiene apóstrofes. +- Cada archivo PHP debe contener `declare(strict_types=1)`. +- Se utilizan dos líneas vacías para separar métodos para una mejor legibilidad. +- La razón para usar el operador de silencio (`@`) debe documentarse: `@mkdir($dir); // @ - el directorio puede existir`. +- Si se utiliza un operador de comparación de tipo débil (es decir, `==`, `!=`, ...), la intención debe documentarse: `// == aceptar null`. +- Puedes escribir múltiples excepciones en un solo archivo `exceptions.php`. +- La visibilidad de los métodos no se especifica para las interfaces, ya que siempre son públicas. +- Cada propiedad, valor de retorno y parámetro debe tener un tipo especificado. Por el contrario, nunca especificamos el tipo para las constantes `final`, ya que es obvio. +- Se deben usar comillas simples para delimitar cadenas, excepto en los casos en que el literal mismo contenga apóstrofes. -Convenciones de denominación .[#toc-naming-conventions] -======================================================= +Convenciones de nomenclatura +============================ -- Evite utilizar abreviaturas a menos que el nombre completo sea excesivo. -- Utilice mayúsculas para las abreviaturas de dos letras, y pascal/camel para las abreviaturas más largas. -- Utilice un sustantivo o una frase nominal para el nombre de la clase. -- Los nombres de clase deben contener no sólo especificidad (`Array`) sino también generalidad (`ArrayIterator`). La excepción son los atributos PHP. -- "Las constantes de clase y los enums deben usar PascalCaps":https://blog.nette.org/es/para-menos-gritos-en-el-codigo. -- "Las interfaces y clases abstractas no deben contener prefijos o postfijos":https://blog.nette.org/es/los-prefijos-y-sufijos-no-pertenecen-a-los-nombres-de-interfaz como `Abstract`, `Interface` o `I`. +- No uses abreviaturas a menos que el nombre completo sea demasiado largo. +- Usa mayúsculas para acrónimos de dos letras, PascalCase/camelCase para acrónimos más largos. +- Usa un sustantivo o una frase nominal para el nombre de la clase. +- Los nombres de las clases deben contener no solo la especificidad (`Array`), sino también la generalidad (`ArrayIterator`). La excepción son los atributos del lenguaje PHP. +- [Las constantes de clase y los enums deben usar PascalCaps |https://blog.nette.org/es/for-less-screaming-in-the-code]. +- [Las interfaces y las clases abstractas no deben contener prefijos o sufijos |https://blog.nette.org/es/prefixes-and-suffixes-do-not-belong-in-interface-names] como `Abstract`, `Interface` o `I`. -Envolturas y llaves .[#toc-wrapping-and-braces] -=============================================== +Envoltura y llaves +================== -La Norma de Codificación Nette corresponde a la PSR-12 (o Estilo de Codificación PER), en algunos puntos la especifica más o la modifica: +El Estándar de Codificación de Nette corresponde a PSR-12 (o PER Coding Style), en algunos puntos lo complementa o modifica: -- las funciones de flecha se escriben sin espacio antes del paréntesis, es decir `fn($a) => $b` -- no se requiere una línea vacía entre los distintos tipos de sentencias import de `use` -- el tipo de retorno de la función/método y el paréntesis de apertura deben colocarse en líneas separadas para una mejor legibilidad: +- Las funciones de flecha se escriben sin espacio antes del paréntesis, es decir, `fn($a) => $b`. +- No se requiere una línea vacía entre diferentes tipos de declaraciones de importación `use`. +- El tipo de retorno de la función/método y la llave de apertura siempre están en líneas separadas: ```php public function find( @@ -46,28 +46,32 @@ La Norma de Codificación Nette corresponde a la PSR-12 (o Estilo de Codificaci array $options, ): array { - // method body + // cuerpo del método } ``` +La llave de apertura en una línea separada es importante para la separación visual de la firma de la función/método del cuerpo. Si la firma está en una línea, la separación es clara (imagen izquierda); si está en varias líneas, en PSR las firmas y el cuerpo se fusionan (medio), mientras que en el estándar de Nette siguen separados (derecha): -Bloques de documentación (phpDoc) .[#toc-documentation-blocks-phpdoc] -===================================================================== +[* new-line-after.webp *] -La regla principal: nunca duplique información de la firma como el tipo de parámetro o el tipo de retorno sin ningún valor añadido. -Bloque de documentación para la definición de clases: +Bloques de documentación (phpDoc) +================================= -- Comienza con una descripción de la clase. +Regla principal: Nunca dupliques ninguna información en la firma, como el tipo de parámetro o el tipo de retorno, sin valor añadido. + +Bloque de documentación para la definición de clase: + +- Comienza con la descripción de la clase. - Sigue una línea vacía. -- Siguen las anotaciones `@property` (o `@property-read`, `@property-write`), una por línea. La sintaxis es: anotación, espacio, tipo, espacio, $nombre. -- Las anotaciones `@method` siguen, una por línea. La sintaxis es: anotación, espacio, tipo de retorno, espacio, nombre(tipo $param, ...). -- La anotación `@author` se omite. La autoría se guarda en un historial del código fuente. -- Se pueden utilizar las anotaciones `@internal` o `@deprecated`. +- Siguen las anotaciones `@property` (o `@property-read`, `@property-write`), una tras otra. La sintaxis es: anotación, espacio, tipo, espacio, `$nombre`. +- Siguen las anotaciones `@method`, una tras otra. La sintaxis es: anotación, espacio, tipo de retorno, espacio, nombre(tipo $param, ...). +- La anotación `@author` se omite. La autoría se conserva en el historial del código fuente. +- Se pueden usar las anotaciones `@internal` o `@deprecated`. ```php /** - * MIME message part. + * Parte del mensaje MIME. * * @property string $encoding * @property-read array $headers @@ -76,27 +80,27 @@ Bloque de documentación para la definición de clases: */ ``` -El bloque de documentación de una propiedad que sólo contenga la anotación `@var` debe ser de una sola línea: +El bloque de documentación para una propiedad, que contiene solo la anotación `@var`, debe ser de una sola línea: ```php /** @var string[] */ private array $name; ``` -Bloque de documentación para la definición de un método: +Bloque de documentación para la definición de método: - Comienza con una breve descripción del método. -- Ninguna línea vacía. -- Las anotaciones `@param`, una por línea. -- La anotación `@return`. -- Las anotaciones `@throws`, una por línea. -- Pueden utilizarse las anotaciones `@internal` o `@deprecated`. +- Sin línea vacía. +- Anotaciones `@param` en líneas individuales. +- Anotación `@return`. +- Anotaciones `@throws`, una tras otra. +- Se pueden usar las anotaciones `@internal` o `@deprecated`. -Cada anotación va seguida de un espacio, excepto la de `@param`, que va seguida de dos espacios para mejorar la legibilidad. +Cada anotación va seguida de un espacio, excepto `@param`, que va seguida de dos espacios para una mejor legibilidad. ```php /** - * Finds a file in directory. + * Encuentra un archivo en el directorio. * @param string[] $options * @return string[] * @throws DirectoryNotFoundException @@ -105,21 +109,20 @@ public function find(string $dir, array $options): array ``` -Tabulaciones en lugar de espacios .[#toc-tabs-instead-of-spaces] -================================================================ +Tabuladores en lugar de espacios +================================ -Las tabulaciones tienen varias ventajas sobre los espacios: +Los tabuladores tienen varias ventajas sobre los espacios: -- el tamaño de la sangría es personalizable en editores y "web":https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size -- no imponen al código el tamaño de sangría preferido por el usuario, por lo que el código es más portable -- se pueden teclear con una sola pulsación (en cualquier lugar, no sólo en editores que convierten los tabuladores en espacios) -- la indentación es su propósito -- respeta las necesidades de los compañeros invidentes o con problemas de visión +- El tamaño del espaciado se puede personalizar en los editores y en la [web |https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size]. +- No imponen al código la preferencia del usuario sobre el tamaño de la indentación, por lo que el código es más portátil. +- Se pueden escribir con una sola pulsación de tecla (en cualquier lugar, no solo en editores que convierten tabuladores en espacios). +- La indentación es su propósito. +- Respetan las necesidades de los compañeros con discapacidad visual y ciegos. -Al utilizar tabuladores en nuestros proyectos, permitimos personalizar la anchura, lo que puede parecer innecesario para la mayoría de la gente, pero es esencial para las personas con deficiencias visuales. +Al usar tabuladores en nuestros proyectos, permitimos la personalización del ancho, lo que puede parecer innecesario para la mayoría de las personas, pero es esencial para las personas con discapacidad visual. -Para los programadores ciegos que utilizan pantallas braille, cada espacio está representado por una celda braille y ocupa un espacio valioso. Así, si la sangría por defecto es de 4 espacios, una sangría de 3er nivel desperdicia 12 celdas braille antes de que comience el código. -En una pantalla de 40 celdas, que es la más utilizada en los ordenadores portátiles, eso es más de una cuarta parte de las celdas disponibles desperdiciadas sin ninguna información. +Para los programadores ciegos que usan pantallas Braille, cada espacio representa una celda Braille. Por lo tanto, si la indentación predeterminada es de 4 espacios, la indentación de tercer nivel desperdicia 12 valiosas celdas Braille incluso antes de que comience el código. En una pantalla de 40 celdas, que es la más utilizada en portátiles, esto es más de una cuarta parte de las celdas disponibles que se desperdician sin ninguna información. {{priority: -1}} diff --git a/contributing/es/documentation.texy b/contributing/es/documentation.texy index d259f9d79b..f3e0e63004 100644 --- a/contributing/es/documentation.texy +++ b/contributing/es/documentation.texy @@ -1,69 +1,68 @@ -Contribuir a la documentación -***************************** +Cómo contribuir a la documentación +********************************** .[perex] -Contribuir a la documentación es una de las actividades más valiosas, ya que ayuda a otros a entender el marco. +Contribuir a la documentación es una de las actividades más gratificantes, ya que ayudas a otros a comprender el framework. -¿Cómo escribir? .[#toc-how-to-write] ------------------------------------- +¿Cómo escribir? +--------------- -La documentación está destinada principalmente a personas que se inician en el tema. Por lo tanto, debe cumplir varios puntos importantes: +La documentación está destinada principalmente a personas que se están familiarizando con el tema. Por lo tanto, debe cumplir varios puntos importantes: -- Empezar con temas sencillos y generales. Pasar al final a temas más avanzados. -- Intente explicar el tema con la mayor claridad posible. Por ejemplo, intente explicárselo primero a un colega. -- Proporcione sólo la información que el usuario realmente necesita saber sobre un tema determinado. -- Asegúrese de que la información es exacta. Pruebe cada código -- Sea conciso: reduzca lo que escribe a la mitad. Y luego no dude en volver a hacerlo. -- Utilice el resaltado con moderación, desde fuentes en negrita hasta marcos como `.[note]` -- Siga la [norma de codificación |Coding Standard] en el código +- Comienza por lo simple y general. Pasa a temas más avanzados solo al final. +- Intenta explicar el asunto lo mejor posible. Por ejemplo, intenta explicar primero el tema a un colega. +- Proporciona solo la información que el usuario realmente necesita saber sobre el tema dado. +- Verifica que tu información sea realmente veraz. Prueba cada fragmento de código. +- Sé conciso: reduce a la mitad lo que escribas. Y luego, si es necesario, hazlo de nuevo. +- Ahorra en todo tipo de resaltados, desde negrita hasta recuadros como `.[note]`. +- En los códigos, sigue el [Estándar de codificación |coding-standard]. -Aprenda también la [sintaxis |syntax]. Para obtener una vista previa del artículo durante la escritura, puede utilizar el [editor de vista previa |https://editor.nette.org/]. +Adopta también la [sintaxis |syntax]. Para previsualizar el artículo mientras lo escribes, puedes usar el [editor con vista previa |https://editor.nette.org/]. -Mutaciones lingüísticas .[#toc-language-mutations] --------------------------------------------------- +Versiones lingüísticas +---------------------- -El inglés es el idioma principal, así que tus cambios deben ser en inglés. Si el inglés no es tu fuerte, utiliza [DeepL Translator |https://www.deepl.com/translator] y otros revisarán tu texto. +El idioma principal es el inglés. Si el inglés no es tu punto fuerte, usa [DeepL Translator |https://www.deepl.com/translator] y otros revisarán tu texto. Los cambios deben enviarse en inglés. -La traducción a otros idiomas se hará automáticamente tras la aprobación y puesta a punto de tu edición. +La traducción a otros idiomas se realizará automáticamente después de la aprobación y ajuste de tu modificación. -Ediciones triviales .[#toc-trivial-edits] ------------------------------------------ +Ediciones triviales +------------------- -Para contribuir a la documentación, es necesario tener una cuenta en [GitHub |https://github.com]. +Para contribuir a la documentación, es esencial tener una cuenta en [GitHub |https://github.com]. -La forma más sencilla de hacer un pequeño cambio en la documentación es utilizar los enlaces que aparecen al final de cada página: +La forma más sencilla de realizar un pequeño cambio en la documentación es utilizar los enlaces al final de cada página: -- *Mostrar en GitHub* abre la versión fuente de la página en GitHub. A continuación, sólo tienes que pulsar el botón `E` y podrás empezar a editar (debes haber iniciado sesión en GitHub) -- *Abrir vista previa* abre un editor donde puedes ver inmediatamente la forma visual final +- *Mostrar en GitHub* abre la versión fuente de la página dada en GitHub. Luego, simplemente presiona el botón `E` y puedes comenzar a editar (es necesario haber iniciado sesión en GitHub). +- *Abrir vista previa* abre el editor, donde puedes ver directamente la apariencia visual resultante. -Como el editor de vista [previa |https://editor.nette.org/] no tiene la capacidad de guardar los cambios directamente en GitHub, necesitas copiar el texto fuente al portapapeles (usando el botón *Copiar al portapapeles*) y luego pegarlo en el editor en GitHub. -Debajo del campo de edición hay un formulario de envío. Aquí no olvides resumir y explicar brevemente el motivo de tu edición. Tras el envío, se crea una solicitud de extracción (pull request, PR) que puede seguir editándose. +Dado que el [editor con vista previa |https://editor.nette.org/] no tiene la opción de guardar cambios directamente en GitHub, es necesario, después de completar las ediciones, copiar el texto fuente al portapapeles (con el botón *Copy to clipboard*) y luego pegarlo en el editor de GitHub. Debajo del campo de edición hay un formulario para enviar. Aquí, no olvides resumir brevemente y explicar el motivo de tu modificación. Después de enviar, se crea una llamada pull request (PR), que se puede editar más. -Ediciones más extensas .[#toc-larger-edits] -------------------------------------------- +Ediciones mayores +----------------- -Es más apropiado estar familiarizado con los fundamentos del trabajo con el sistema de control de versiones Git que confiar únicamente en la interfaz de GitHub. Si no estás familiarizado con Git, puedes consultar la [guía git - the simple guide |https://rogerdudler.github.io/git-guide/] y considerar el uso de uno de los muchos [clientes gráficos |https://git-scm.com/downloads/guis] disponibles. +Más apropiado que usar la interfaz de GitHub es estar familiarizado con los fundamentos del trabajo con el sistema de control de versiones Git. Si no dominas Git, puedes consultar la guía [git - the simple guide |https://rogerdudler.github.io/git-guide/] y, opcionalmente, utilizar alguno de los muchos [clientes gráficos |https://git-scm.com/downloads/guis]. -Edita la documentación de la siguiente manera: +Modifica la documentación de esta manera: 1) en GitHub, crea un [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] del repositorio [nette/docs |https://github.com/nette/docs] 2) [clona |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] este repositorio en tu ordenador -3) a continuación, realice cambios en la [rama correspondiente |#Documentation Structure] -4) compruebe si hay espacios de más en el texto utilizando la herramienta [Code-Checker |code-checker:] -5) guarde (confirme) los cambios -6) si estás satisfecho con los cambios, envíalos a tu bifurcación en GitHub -7) desde allí, envíalos al repositorio `nette/docs` creando un [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) +3) luego, en la [rama apropiada |#Estructura de la documentación], realiza los cambios +4) comprueba si hay espacios sobrantes en el texto usando la herramienta [Code-Checker |code-checker:] +5) guarda los cambios (haz commit) +6) si estás satisfecho con los cambios, envíalos (haz push) a GitHub a tu fork +7) desde allí, envíalos al repositorio `nette/docs` creando una [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) -Es habitual recibir comentarios con sugerencias. No pierdas de vista los cambios propuestos e incorpóralos. Añade los cambios sugeridos como nuevos commits y reenvíalos a GitHub. Nunca crees un nuevo pull request sólo para modificar uno ya existente. +Es común recibir comentarios con sugerencias. Sigue los cambios propuestos e incorpóralos. Añade los cambios propuestos como nuevos commits y vuelve a enviarlos a GitHub. Nunca crees una nueva pull request para modificar una pull request existente. -Estructura de la documentación .[#toc-documentation-structure] --------------------------------------------------------------- +Estructura de la documentación +------------------------------ -Toda la documentación se encuentra en GitHub, en el repositorio [nette/docs |https://github.com/nette/docs]. La versión actual se encuentra en la rama maestra, mientras que las versiones anteriores se encuentran en ramas como `doc-3.x`, `doc-2.x`. +Toda la documentación se encuentra en GitHub en el repositorio [nette/docs |https://github.com/nette/docs]. La versión actual está en `master`, las versiones anteriores se encuentran en ramas como `doc-3.x`, `doc-2.x`. -El contenido de cada rama se divide en carpetas principales que representan áreas individuales de la documentación. Por ejemplo, `application/` corresponde a https://doc.nette.org/en/application, `latte/` corresponde a https://latte.nette.org, etc. Cada una de estas carpetas contiene subcarpetas que representan mutaciones lingüísticas (`cs`, `en`, ...) y, opcionalmente, una subcarpeta `files` con imágenes que pueden insertarse en las páginas de la documentación. +El contenido de cada rama se divide en carpetas principales que representan las áreas individuales de la documentación. Por ejemplo, `application/` corresponde a https://doc.nette.org/es/application, `latte/` corresponde a https://latte.nette.org, etc. Cada una de estas carpetas contiene subcarpetas que representan las versiones lingüísticas (`cs`, `en`, ...) y, opcionalmente, una subcarpeta `files` con imágenes que se pueden insertar en las páginas de la documentación. diff --git a/contributing/es/syntax.texy b/contributing/es/syntax.texy index f29f79c98f..559dc80217 100644 --- a/contributing/es/syntax.texy +++ b/contributing/es/syntax.texy @@ -1,59 +1,59 @@ -Sintaxis Wiki -************* +Sintaxis de la documentación +**************************** -Wiki utiliza la [sintaxis |https://texy.info/en/syntax] Markdown y [Texy |https://texy.info/en/syntax] con varias mejoras. +La documentación utiliza Markdown y la [sintaxis Texy |https://texy.nette.org/syntax] con algunas extensiones. -Enlaces .[#toc-links] -===================== +Enlaces +======= -Para las referencias internas, se utiliza la notación entre corchetes `[link]` . Se utiliza la forma con barra vertical `[link text |link target]`o en la forma abreviada `[link text]` si el destino es el mismo que el texto (previa transformación a minúsculas y guiones): +Para los enlaces internos se utiliza la notación entre corchetes `[enlace]`. Ya sea en la forma con barra vertical `[texto del enlace |destino del enlace]`, o de forma abreviada `[texto del enlace]`, si el destino es idéntico al texto (después de la transformación a minúsculas y guiones): -- `[Page name]` -> `<a href="/en/page-name">Page name</a>` -- `[link text |Page name]` -> `<a href="/en/page-name">link text</a>` +- `[Page name |Page name]` -> `<a href="/es/page-name">Page name</a>` +- `[texto del enlace |Page name]` -> `<a href="/es/page-name">texto del enlace</a>` -Podemos enlazar con otro idioma o con otra sección. Una sección es una biblioteca Nette (por ejemplo, `forms`, `latte`, etc.) o secciones especiales como `best-practices`, `quickstart`, etc: +Podemos enlazar a otra versión lingüística o a otra sección. Por sección se entiende una librería de Nette (p. ej., `forms`, `latte`, etc.) o secciones especiales como `best-practices`, `quickstart`, etc.: -- `[cs:Page name]` -> `<a href="/en/page-name">Page name</a>` (misma sección, distinto idioma) -- `[tracy:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (sección diferente, mismo idioma) -- `[tracy:cs:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (sección e idioma diferentes) +- `[cs:Page name |cs:Page name]` -> `<a href="/cs/page-name">Page name</a>` (misma sección, diferente idioma) +- `[tracy:Page name |tracy:Page name]` -> `<a href="//tracy.nette.org/es/page-name">Page name</a>` (diferente sección, mismo idioma) +- `[tracy:cs:Page name |tracy:cs:Page name]` -> `<a href="//tracy.nette.org/cs/page-name">Page name</a>` (diferente sección e idioma) -También es posible seleccionar un título específico de la página con `#`. +Usando `#` también es posible apuntar a un encabezado específico en la página. -- `[#Heading]` -> `<a href="#toc-heading">Heading</a>` (título de la página actual) -- `[Page name#Heading]` -> `<a href="/en/page-name#toc-heading">Page name</a>` +- `[#Heading]` -> `<a href="#toc-heading">Heading</a>` (encabezado en la página actual) +- `[Page name#Heading |Page name#Heading]` -> `<a href="/es/page-name#toc-heading">Page name</a>` -Enlace a la página de inicio de la sección: (`@home` es un término especial para la página de inicio de la sección) +Enlace a la página de inicio de la sección: (`@home` es una expresión especial para la página de inicio de la sección) -- `[link text |@home]` -> `<a href="/en/">link text</a>` -- `[link text |tracy:]` -> `<a href="//tracy.nette.org/en/">link text</a>` +- `[texto del enlace |@home]` -> `<a href="/es/">texto del enlace</a>` +- `[texto del enlace |tracy:]` -> `<a href="//tracy.nette.org/es/">texto del enlace</a>` -Enlaces a la documentación de la API .[#toc-links-to-api-documentation] ------------------------------------------------------------------------ +Enlaces a la documentación de la API +------------------------------------ -Utilice siempre las siguientes notaciones: +Siempre indíquelos únicamente mediante esta notación: - `[api:Nette\SmartObject]` -> [api:Nette\SmartObject] - `[api:Nette\Forms\Form::setTranslator()]` -> [api:Nette\Forms\Form::setTranslator()] - `[api:Nette\Forms\Form::$onSubmit]` -> [api:Nette\Forms\Form::$onSubmit] - `[api:Nette\Forms\Form::Required]` -> [api:Nette\Forms\Form::Required] -Los nombres completos sólo se utilizan en la primera mención. Para los demás enlaces, utilice un nombre simplificado: +Use nombres completamente calificados solo en la primera mención. Para enlaces posteriores, use el nombre simplificado: -- `[Form::setTranslator() |api:Nette\Forms\Form::setTranslator()]` -> [Formulario::setTranslator() |api:Nette\Forms\Form::setTranslator()] +- `[Form::setTranslator() |api:Nette\Forms\Form::setTranslator()]` -> [Form::setTranslator() |api:Nette\Forms\Form::setTranslator()] -Enlaces a la documentación PHP .[#toc-links-to-php-documentation] ------------------------------------------------------------------ +Enlaces a la documentación de PHP +--------------------------------- - `[php:substr]` -> [php:substr] -Código fuente .[#toc-source-code] -================================= +Código fuente +============= -El bloque de código comienza con <code>```lang</code> y termina con <code>```</code> Los lenguajes soportados son `php`, `latte`, `neon`, `html`, `css`, `js` y `sql`. Utilice siempre tabuladores para la sangría. +Un bloque de código comienza con <code>```lang</code> y termina con <code>```</code>. Los lenguajes admitidos son `php`, `latte`, `neon`, `html`, `css`, `js` y `sql`. Para la indentación, use siempre tabuladores. ``` ```php @@ -63,7 +63,7 @@ El bloque de código comienza con <code>```lang</code> y termina con ``` ``` -También puede especificar el nombre del archivo como <code>```php .{file: ArrayTest.php}</code> y el bloque de código se mostrará de esta forma: +También puede especificar el nombre del archivo como <code>```php .{file: ArrayTest.php}</code> y el bloque de código se renderizará de esta manera: ```php .{file: ArrayTest.php} public function renderPage($id) @@ -72,71 +72,71 @@ public function renderPage($id) ``` -Encabezados .[#toc-headings] -============================ +Encabezados +=========== -Encabezamiento superior (nombre de la página) subrayado con estrellas (`*`). For normal headings use equal signs (`=`) and then hyphens (`-`). +El encabezado más alto (es decir, el nombre de la página) debe subrayarse con asteriscos (`***`). Para separar secciones, use signos de igual (`===`). Subraye los encabezados de nivel inferior con signos de igual (`===`) y luego con guiones (`---`): ``` -MVC Applications & Presenters +Aplicaciones MVC y presenters ***************************** ... -Link Creation -============= +Creación de enlaces +=================== ... -Links in Templates ------------------- +Enlaces en plantillas +--------------------- ... ``` -Cajas y estilos .[#toc-boxes-and-styles] -======================================== +Recuadros y estilos +=================== -Párrafo principal marcado con la clase `.[perex]` .[perex] +Marcamos el perex con la clase `.[perex]` .[perex] -Notas marcadas con clase `.[note]` .[note] +Marcamos una nota con la clase `.[note]` .[note] -Consejo marcado con clase `.[tip]` .[tip] +Marcamos un consejo con la clase `.[tip]` .[tip] -Advertencia marcada con clase `.[caution]` .[caution] +Marcamos una advertencia con la clase `.[caution]` .[caution] -Advertencia firme señalada con una clase `.[warning]` .[warning] +Marcamos una advertencia más fuerte con la clase `.[warning]` .[warning] Número de versión `.{data-version:2.4.10}` .{data-version:2.4.10} -Las clases deben escribirse antes de la línea correspondiente: +Escriba las clases antes de la línea: ``` -.[note] -This is a note. +.[perex] +Este es el perex. ``` -Tenga en cuenta que los recuadros como `.[tip]` llaman la atención y, por lo tanto, deben utilizarse para resaltar, no para información menos importante. +Tenga en cuenta que los recuadros como `.[tip]` "atraen" la vista, por lo que se utilizan para enfatizar, no para información menos importante. Por lo tanto, úselos con la máxima moderación. -Índice .[#toc-table-of-contents] -================================ +Tabla de contenidos +=================== -La tabla de contenidos (enlaces en la barra lateral) se genera automáticamente cuando la página tiene más de 4 000 bytes. Este comportamiento predeterminado puede modificarse con una etiqueta `{{toc}}`[etiqueta meta |#meta-tags]. El texto de la tabla de contenido se toma por defecto del encabezamiento, pero es posible utilizar un texto diferente con un modificador `.{toc}` modificador. Esto es especialmente útil para encabezados más largos. +La tabla de contenidos (enlaces en el menú derecho) se genera automáticamente para todas las páginas cuyo tamaño supere los 4000 bytes, aunque este comportamiento predeterminado se puede modificar mediante la [metaetiqueta |#Metaetiquetas] `{{toc}}`. El texto que forma la tabla de contenidos se toma por defecto directamente del texto de los encabezados, pero mediante el modificador `.{toc}` es posible mostrar un texto diferente en la tabla de contenidos, lo cual es útil principalmente para encabezados más largos. ``` -Long and Intelligent Heading .{toc: A Different Text for TOC} -============================================================= +Encabezado largo e inteligente .{toc: Cualquier otro texto mostrado en la tabla de contenidos} +============================================================================================== ``` -Metaetiquetas .[#toc-meta-tags] -=============================== +Metaetiquetas +============= -- establecer su propio título de página (en `<title>` y migas de pan) `{{title: Another name}}` -- redirigir `{{redirect: pla:cs}}` - ver [enlaces |#links] -- forzar `{{toc}}` o desactivar `{{toc: no}}` tabla de contenido +- establecer un título de página personalizado (en `<title>` y navegación de migas de pan) `{{title: Otro título}}` +- redirección `{{redirect: pla:cs}}` - ver [#enlaces] +- forzar `{{toc}}` o deshabilitar `{{toc: no}}` la tabla de contenidos automática (recuadro con enlaces a encabezados individuales) {{priority: -1}} diff --git a/contributing/files/new-line-after.webp b/contributing/files/new-line-after.webp new file mode 100644 index 0000000000..419492d01f Binary files /dev/null and b/contributing/files/new-line-after.webp differ diff --git a/contributing/fr/@home.texy b/contributing/fr/@home.texy index a6b1d4de32..9abf985f3b 100644 --- a/contributing/fr/@home.texy +++ b/contributing/fr/@home.texy @@ -1,17 +1,17 @@ -Devenez un contributeur de Nette -******************************** +Devenez contributeur de Nette +***************************** .[perex] -Découvrez comment vous pouvez participer à notre projet open source. Apprenez les étapes pour contribuer au code source et à la documentation, et rejoignez le réseau de développeurs qui se consacrent à l'amélioration de Nette. +Découvrez comment vous pouvez vous impliquer dans notre projet open source. Apprenez les procédures pour contribuer au code source et à la documentation et devenez membre de la communauté de développeurs qui participent activement à l'amélioration de Nette. **Code** -- [Contribuer au code |code] -- [Normes de codage |coding-standard] +- [Comment contribuer au code ? |code] +- [Standard de codage |coding-standard] **Documentation** -- [Contribuer à la documentation |documentation] +- [Comment contribuer à la documentation ? |documentation] - [Syntaxe de la documentation |syntax] - "Éditeur de prévisualisation":https://editor.nette.org diff --git a/contributing/fr/@left-menu.texy b/contributing/fr/@left-menu.texy index 53e66cdfe8..ca09218bbd 100644 --- a/contributing/fr/@left-menu.texy +++ b/contributing/fr/@left-menu.texy @@ -1,10 +1,10 @@ Code **** -- [Contribuer au code |code] -- [Normes de codage |coding-standard] +- [Comment contribuer au code ? |code] +- [Standard de codage |coding-standard] Documentation ************* -- [Contribuer à la documentation |documentation] +- [Comment contribuer à la documentation ? |documentation] - [Syntaxe de la documentation |syntax] - "Éditeur de prévisualisation":https://editor.nette.org diff --git a/contributing/fr/code.texy b/contributing/fr/code.texy index 16e2c5f41e..39c6b6bf19 100644 --- a/contributing/fr/code.texy +++ b/contributing/fr/code.texy @@ -1,112 +1,112 @@ -Contribuer au code -****************** +Comment contribuer au code +************************** .[perex] -Vous envisagez de contribuer au Nette Framework et avez besoin de vous familiariser avec les règles et les procédures ? Ce guide du débutant vous guidera à travers les étapes pour contribuer efficacement au code, travailler avec les dépôts et mettre en œuvre les changements. +Vous êtes sur le point de contribuer à Nette Framework et avez besoin de vous familiariser avec les règles et procédures ? Ce guide pour débutants vous montrera étape par étape comment contribuer efficacement au code, travailler avec les dépôts et implémenter des changements. -Procédure .[#toc-procedure] -=========================== +Procédure +========= -Pour contribuer au code, il est essentiel d'avoir un compte sur [GitHub |https://github.com] et d'être familiarisé avec les bases du système de contrôle de version Git. Si vous n'êtes pas familier avec Git, vous pouvez consulter [git - the simple guide |https://rogerdudler.github.io/git-guide/] et envisager d'utiliser l'un des nombreux [clients graphiques |https://git-scm.com/downloads/guis]. +Pour contribuer au code, il est nécessaire d'avoir un compte sur [GitHub|https://github.com] et d'être familiarisé avec les bases du travail avec le système de contrôle de version Git. Si vous ne maîtrisez pas Git, vous pouvez consulter le guide [git - le guide simple |https://rogerdudler.github.io/git-guide/] ou utiliser l'un des nombreux [clients graphiques |https://git-scm.com/downloads/guis]. -Préparation de l'environnement et du dépôt .[#toc-preparing-the-environment-and-repository] -------------------------------------------------------------------------------------------- +Préparation de l'environnement et du dépôt +------------------------------------------ -1) Sur GitHub, créez un [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] du [dépôt de paquets |www:packages] que vous avez l'intention de modifier. -2) [Clonez |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] ce dépôt sur votre ordinateur -3) Installez les dépendances, y compris [Nette Tester |tester:], en utilisant la commande `composer install` -4) Vérifiez que les tests fonctionnent en exécutant la commande `composer tester` -5) Créer une [nouvelle branche |#New Branch] basée sur la dernière version publiée. +1) Sur GitHub, créez un [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] du dépôt du [paquet |www:packages] que vous vous apprêtez à modifier. +2) [Clonez |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] ce dépôt sur votre ordinateur. +3) Installez les dépendances, y compris [Nette Tester |tester:], à l'aide de la commande `composer install`. +4) Vérifiez que les tests fonctionnent en exécutant `composer tester`. +5) Créez une [#nouvelle branche] basée sur la dernière version publiée. -Mise en œuvre de vos propres modifications .[#toc-implementing-your-own-changes] --------------------------------------------------------------------------------- +Implémentation de vos propres changements +----------------------------------------- -Vous pouvez maintenant apporter vos propres modifications au code : +Vous pouvez maintenant effectuer vos propres modifications de code : -1) Mettez en œuvre les changements souhaités et n'oubliez pas les tests. -2) Assurez-vous que les tests s'exécutent avec succès en utilisant `composer tester` -3) Vérifier si le code répond aux [normes de codage |#coding standards] -4) Sauvegarder (commit) les changements avec une description dans [ce format |#Commit Description] +1) Programmez les changements souhaités et n'oubliez pas les tests. +2) Assurez-vous que les tests réussissent en utilisant `composer tester`. +3) Vérifiez que le code respecte le [standard de codage |#Standards de codage]. +4) Enregistrez (commitez) les changements avec une description dans [ce format |#Description du commit]. -Vous pouvez créer plusieurs livraisons, une pour chaque étape logique. Chaque validation doit être significative en soi. +Vous pouvez créer plusieurs commits, un pour chaque étape logique. Chaque commit doit être autonome et avoir un sens. -Soumettre des modifications .[#toc-submitting-changes] ------------------------------------------------------- +Envoi des changements +--------------------- -Une fois que vous êtes satisfait des modifications, vous pouvez les soumettre : +Une fois que vous êtes satisfait des changements, vous pouvez les envoyer : -1) Pousser les changements sur GitHub vers votre fork -2) De là, soumettez-les au dépôt Nette en créant une [pull request|https://help.github.com/articles/creating-a-pull-request] (PR). -3) Fournissez [suffisamment d'informations |#pull request description] dans la description +1) Envoyez (pushez) les changements sur GitHub vers votre fork. +2) De là, envoyez-les au dépôt Nette en créant une [pull request|https://help.github.com/articles/creating-a-pull-request] (PR). +3) Fournissez [suffisamment d'informations |#Description de la pull request] dans la description. -Incorporer le retour d'information .[#toc-incorporating-feedback] ------------------------------------------------------------------ +Intégration des commentaires +---------------------------- -Vos modifications sont maintenant visibles par les autres. Il est courant de recevoir des commentaires contenant des suggestions : +Vos commits seront désormais visibles par les autres. Il est courant de recevoir des commentaires avec des remarques : -1) Garder une trace des changements proposés -2) Incorporez-les en tant que nouveaux commits ou [fusionnez-les avec les précédents |https://help.github.com/en/github/using-git/about-git-rebase] -3) Resoumettez les commits à GitHub, et ils apparaîtront automatiquement dans la demande d'extraction. +1) Suivez les modifications suggérées. +2) Intégrez-les comme de nouveaux commits ou [fusionnez-les avec les précédents |https://help.github.com/en/github/using-git/about-git-rebase]. +3) Renvoyez les commits sur GitHub ; ils apparaîtront automatiquement dans la pull request. -Ne créez jamais une nouvelle demande d'extraction pour modifier une demande existante. +Ne créez jamais une nouvelle pull request pour modifier une pull request existante. -Documentation .[#toc-documentation] ------------------------------------ +Documentation +------------- -Si vous avez modifié une fonctionnalité ou en avez ajouté une nouvelle, n'oubliez pas de l'ajouter également [à la documentation |documentation]. +Si vous avez modifié une fonctionnalité ou en avez ajouté une nouvelle, n'oubliez pas de l'[ajouter à la documentation |documentation]. -Nouvelle branche .[#toc-new-branch] -=================================== +Nouvelle branche +================ -Si possible, effectuez les modifications par rapport à la dernière version publiée, c'est-à-dire la dernière balise de la branche. Pour l'étiquette v3.2.1, créez une branche en utilisant cette commande : +Si possible, effectuez les changements par rapport à la dernière version publiée, c'est-à-dire le dernier tag dans la branche concernée. Pour le tag `v3.2.1`, vous créez une branche avec cette commande : ```shell -git checkout -b new_branch_name v3.2.1 +git checkout -b nom_nouvelle_branche v3.2.1 ``` -Normes de codage .[#toc-coding-standards] -========================================= +Standards de codage +=================== -Votre code doit respecter les [normes de codage |coding standard] utilisées dans le Nette Framework. Il existe un outil automatique pour vérifier et corriger le code. Vous pouvez l'installer **globalement** via Composer dans un dossier de votre choix : +Votre code doit respecter le [standard de codage |coding standard] utilisé dans Nette Framework. Un outil automatique est disponible pour vérifier et corriger le code. Il peut être installé via Composer **globalement** dans le dossier de votre choix : ```shell -composer create-project nette/coding-standard /path/to/nette-coding-standard +composer create-project nette/coding-standard /chemin/vers/nette-coding-standard ``` -Vous devriez maintenant être en mesure de lancer l'outil dans le terminal. La première commande vérifie et la seconde corrige le code dans les dossiers `src` et `tests` dans le répertoire courant : +Vous devriez maintenant pouvoir exécuter l'outil dans le terminal. La première commande vérifie et la seconde corrige également le code dans les dossiers `src` et `tests` du répertoire courant : ```shell -/path/to/nette-coding-standard/ecs check -/path/to/nette-coding-standard/ecs check --fix +/chemin/vers/nette-coding-standard/ecs check +/chemin/vers/nette-coding-standard/ecs check --fix ``` -Description de l'engagement .[#toc-commit-description] -====================================================== +Description du commit +===================== -Dans Nette, les sujets de commit ont le format suivant : `Presenter: fixed AJAX detection [Closes #69]` +Dans Nette, les sujets des commits ont le format : `Presenter: fixed AJAX detection [Closes #69]` -- domaine suivi de deux points -- l'objet du commit au passé ; si possible, commencer par des mots comme : added, fixed, refactored, changed, removed -- si le commit rompt la compatibilité ascendante, ajouter "BC break" (rupture de la compatibilité ascendante) -- toute connexion à l'outil de suivi des problèmes, comme `(#123)` ou `[Closes #69]` -- après le sujet, il peut y avoir une ligne vide suivie d'une description plus détaillée, y compris, par exemple, des liens vers le forum. +- la zone suivie de deux-points +- l'objectif du commit au passé, si possible, commencez par le mot : "added (nouvelle fonctionnalité ajoutée)", "fixed (correction)", "refactored (modification du code sans changement de comportement)", changed, removed +- si le commit rompt la compatibilité ascendante, ajoutez "BC break" +- une éventuelle liaison avec le suivi des problèmes comme `(#123)` ou `[Closes #69]` +- après le sujet, une ligne vide peut suivre, puis une description plus détaillée incluant par exemple des liens vers le forum -Description de la demande de retrait .[#toc-pull-request-description] -===================================================================== +Description de la pull request +============================== -Lors de la création d'une demande d'extraction, l'interface GitHub vous permet de saisir un titre et une description. Fournissez un titre concis et incluez autant d'informations que possible dans la description sur les raisons de votre changement. +Lors de la création d'une pull request, l'interface GitHub vous permettra de saisir un titre et une description. Donnez un titre concis et dans la description, fournissez autant d'informations que possible sur les raisons de votre changement. -Précisez également dans l'en-tête s'il s'agit d'une nouvelle fonctionnalité ou d'une correction de bogue et si elle peut entraîner des problèmes de compatibilité ascendante (BC break). S'il existe un problème connexe, créez un lien vers celui-ci afin qu'il soit fermé lors de l'approbation de la demande d'extraction. +Un en-tête s'affichera également, où vous spécifierez s'il s'agit d'une nouvelle fonctionnalité ou d'une correction de bug et si cela peut entraîner une rupture de compatibilité ascendante (BC break). S'il existe un problème lié (issue), référencez-le afin qu'il soit fermé après l'approbation de la pull request. ``` - bug fix / new feature? <!-- #issue numbers, if any --> diff --git a/contributing/fr/coding-standard.texy b/contributing/fr/coding-standard.texy index a765bc0a32..e9d450f247 100644 --- a/contributing/fr/coding-standard.texy +++ b/contributing/fr/coding-standard.texy @@ -1,44 +1,44 @@ -Norme de codage -*************** +Standard de codage +****************** -Ce document décrit les règles et recommandations pour le développement de Nette. Lorsque vous contribuez au code de Nette, vous devez les suivre. La façon la plus simple de le faire est d'imiter le code existant. -L'idée est de faire en sorte que tout le code semble avoir été écrit par une seule personne. .[perex] +.[perex] +Ce document décrit les règles et recommandations pour le développement de Nette. Lorsque vous contribuez au code de Nette, vous devez les respecter. La manière la plus simple de le faire est d'imiter le code existant. L'objectif est que tout le code ait l'air d'avoir été écrit par une seule personne. -La norme de codage de Nette correspond au [style de codage étendu PSR-12 |https://www.php-fig.org/psr/psr-12/] avec deux exceptions principales : il utilise des [tabulations au lieu d'espaces |#tabs instead of spaces] pour l'indentation, et utilise [PascalCase pour les constantes de classe |https://blog.nette.org/fr/pour-moins-crier-dans-le-code]. +Le standard de codage Nette correspond au [PSR-12 Extended Coding Style |https://www.php-fig.org/psr/psr-12/] avec deux exceptions principales : il utilise des [#Tabulations au lieu d'espaces] pour l'indentation et [utilise PascalCase pour les constantes de classe |https://blog.nette.org/fr/for-less-screaming-in-the-code]. -Règles générales .[#toc-general-rules] -====================================== +Règles générales +================ - Chaque fichier PHP doit contenir `declare(strict_types=1)` - Deux lignes vides sont utilisées pour séparer les méthodes pour une meilleure lisibilité. -- La raison de l'utilisation de l'opérateur de fermeture doit être documentée : `@mkdir($dir); // @ - directory may exist` -- Si un opérateur de comparaison faiblement typé est utilisé (par exemple `==`, `!=`, ...), l'intention doit être documentée : `// == to accept null` -- Vous pouvez écrire plusieurs exceptions dans un seul fichier `exceptions.php` -- La visibilité des méthodes n'est pas spécifiée pour les interfaces car elles sont toujours publiques. -- Chaque propriété, valeur de retour et paramètre doit avoir un type spécifié. En revanche, pour les constantes finales, on ne précise jamais le type car il est évident. -- Les guillemets simples doivent être utilisés pour délimiter la chaîne de caractères, sauf si le littéral lui-même contient des apostrophes. +- La raison de l'utilisation de l'opérateur silence (`@`) doit être documentée : `@mkdir($dir); // @ - le répertoire peut exister`. +- Si un opérateur de comparaison faiblement typé est utilisé (c.-à-d. `==`, `!=`, ...), l'intention doit être documentée : `// == accepter null` +- Vous pouvez écrire plusieurs exceptions dans un seul fichier `exceptions.php`. +- La visibilité des méthodes n'est pas spécifiée pour les interfaces, car elles sont toujours publiques. +- Chaque propriété, valeur de retour et paramètre doit avoir un type spécifié. Inversement, pour les constantes `final`, nous ne spécifions jamais le type, car il est évident. +- Les guillemets simples (`'`) doivent être utilisés pour délimiter les chaînes de caractères, sauf lorsque le littéral lui-même contient des apostrophes. -Conventions de dénomination .[#toc-naming-conventions] -====================================================== +Conventions de nommage +====================== -- Évitez d'utiliser des abréviations, sauf si le nom complet est excessif. -- Utilisez les majuscules pour les abréviations de deux lettres, et les majuscules pascal/camel pour les abréviations plus longues. +- N'utilisez pas d'abréviations, sauf si le nom complet est trop long. +- Utilisez des majuscules pour les abréviations de deux lettres, pascal/camel pour les abréviations plus longues. - Utilisez un nom ou une expression nominale pour le nom de la classe. -- Les noms de classe doivent contenir non seulement la spécificité (`Array`) mais aussi la généralité (`ArrayIterator`). Les attributs PHP constituent une exception. -- "Les constantes de classe et les enums doivent utiliser les PascalCaps":https://blog.nette.org/fr/pour-moins-crier-dans-le-code. -- Les interfaces et les classes abstraites ne doivent pas contenir de préfixes ou de postfixes":https://blog.nette.org/fr/les-prefixes-et-les-suffixes-n-ont-pas-leur-place-dans-les-noms-d-interface comme `Abstract`, `Interface` ou `I`. +- Les noms de classe doivent contenir non seulement la spécificité (`Array`), mais aussi la généralité (`ArrayIterator`). Les attributs du langage PHP font exception. +- "Les constantes de classe et les énumérations doivent utiliser PascalCaps":https://blog.nette.org/fr/for-less-screaming-in-the-code. +- "Les interfaces et les classes abstraites ne doivent pas contenir de préfixes ou de suffixes":https://blog.nette.org/fr/prefixes-and-suffixes-do-not-belong-in-interface-names comme `Abstract`, `Interface` ou `I`. -Encadrement et accolades .[#toc-wrapping-and-braces] -==================================================== +Retours à la ligne et accolades +=============================== -La norme de codage Nette correspond au PSR-12 (ou style de codage PER), en certains points elle le précise davantage ou le modifie : +Le standard de codage Nette correspond à PSR-12 (resp. PER Coding Style), le complète ou le modifie sur certains points : -- les fonctions flèches sont écrites sans espace avant la parenthèse, c'est-à-dire que `fn($a) => $b` -- aucune ligne vide n'est requise entre les différents types d'instructions d'importation `use` -- le type de retour de la fonction/méthode et la parenthèse ouvrante doivent être placés sur des lignes séparées pour une meilleure lisibilité : +- les fonctions fléchées s'écrivent sans espace avant la parenthèse, c.-à-d. `fn($a) => $b` +- une ligne vide n'est pas requise entre différents types d'instructions d'importation `use` +- le type de retour de la fonction/méthode et l'accolade ouvrante sont toujours sur des lignes séparées : ```php public function find( @@ -46,28 +46,32 @@ La norme de codage Nette correspond au PSR-12 (ou style de codage PER), en certa array $options, ): array { - // method body + // corps de la méthode } ``` +L'accolade ouvrante sur une ligne séparée est importante pour la séparation visuelle de la signature de la fonction/méthode du corps. Si la signature est sur une seule ligne, la séparation est claire (image de gauche), si elle est sur plusieurs lignes, dans PSR les signatures et le corps se confondent (au milieu), tandis que dans le standard Nette, ils restent séparés (à droite) : -Blocs de documentation (phpDoc) .[#toc-documentation-blocks-phpdoc] -=================================================================== +[* new-line-after.webp *] -La règle principale : ne jamais dupliquer une information de signature comme le type de paramètre ou le type de retour sans valeur ajoutée. -Bloc de documentation pour la définition d'une classe : +Blocs de documentation (phpDoc) +=============================== -- Commence par une description de la classe. -- Une ligne vide suit. -- Les annotations `@property` (ou `@property-read`, `@property-write`) suivent, une par ligne. La syntaxe est : annotation, espace, type, espace, $name. -- Les annotations `@method` suivent, une par une. La syntaxe est : annotation, espace, return type, espace, name(type $param, ...). -- L'annotation `@author` est omise. L'auteur est conservé dans un historique du code source. +Règle principale : Ne dupliquez jamais aucune information déjà présente dans la signature, comme le type de paramètre ou le type de retour, sans apporter une valeur ajoutée (par exemple, une description plus détaillée du type). + +Bloc de documentation pour la définition de classe : + +- Commence par la description de la classe. +- Suivi d'une ligne vide. +- Suivi des annotations `@property` (ou `@property-read`, `@property-write`), une par ligne. La syntaxe est : annotation, espace, type, espace, `$nom`. +- Suivi des annotations `@method`, une par ligne. La syntaxe est : annotation, espace, type de retour, espace, `nom(type $param, ...)`. +- L'annotation `@author` est omise. La paternité est conservée dans l'historique du code source. - Les annotations `@internal` ou `@deprecated` peuvent être utilisées. ```php /** - * MIME message part. + * Partie de message MIME. * * @property string $encoding * @property-read array $headers @@ -76,27 +80,27 @@ Bloc de documentation pour la définition d'une classe : */ ``` -Le bloc de documentation pour une propriété qui ne contient que l'annotation `@var` doit être sur une seule ligne : +Un bloc de documentation pour une propriété, qui ne contient que l'annotation `@var`, doit être sur une seule ligne : ```php /** @var string[] */ private array $name; ``` -Bloc de documentation pour la définition d'une méthode : +Bloc de documentation pour la définition de méthode : - Commence par une brève description de la méthode. - Pas de ligne vide. -- Les annotations `@param`, une par ligne. -- L'annotation `@return`. -- Les annotations de `@throws`, une par ligne. +- Annotations `@param` sur des lignes individuelles. +- Annotation `@return`. +- Annotations `@throws`, une par une. - Les annotations `@internal` ou `@deprecated` peuvent être utilisées. -Chaque annotation est suivie d'un espace, sauf pour le `@param` qui est suivi de deux espaces pour une meilleure lisibilité. +Chaque annotation est suivie d'un espace, à l'exception de `@param`, qui est suivie de deux espaces pour une meilleure lisibilité. ```php /** - * Finds a file in directory. + * Trouve un fichier dans le répertoire. * @param string[] $options * @return string[] * @throws DirectoryNotFoundException @@ -105,21 +109,20 @@ public function find(string $dir, array $options): array ``` -Tabulation à la place des espaces .[#toc-tabs-instead-of-spaces] -================================================================ +Tabulations au lieu d'espaces +============================= Les tabulations présentent plusieurs avantages par rapport aux espaces : -- la taille de l'indentation est personnalisable dans les éditeurs et sur le "web":https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size. -- elles n'imposent pas au code les préférences de l'utilisateur en matière de taille d'indentation, ce qui rend le code plus portable -- vous pouvez les taper avec une seule touche (partout, pas seulement dans les éditeurs qui transforment les tabulations en espaces) -- l'indentation est leur objectif -- respecter les besoins des collègues malvoyants et aveugles +- la taille de l'indentation peut être personnalisée dans les éditeurs et sur le "web":https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size +- elles n'imposent pas au code la préférence de l'utilisateur en matière de taille d'indentation, de sorte que le code est plus portable +- elles peuvent être écrites en une seule touche (partout, pas seulement dans les éditeurs qui transforment les tabulations en espaces) +- l'indentation est leur fonction première +- elles respectent les besoins des collègues malvoyants et aveugles -En utilisant les tabulations dans nos projets, nous permettons la personnalisation de la largeur, ce qui peut sembler inutile à la plupart des gens, mais qui est essentiel pour les personnes souffrant de déficiences visuelles. +En utilisant des tabulations dans nos projets, nous permettons une personnalisation de la largeur, ce qui peut sembler superflu pour la plupart des gens, mais est essentiel pour les personnes ayant une déficience visuelle. -Pour les programmeurs aveugles qui utilisent des écrans braille, chaque espace est représenté par une cellule braille et occupe un espace précieux. Ainsi, si l'indentation par défaut est de 4 espaces, une indentation de 3e niveau gaspille 12 cellules braille avant le début du code. -Sur un écran de 40 cellules, qui est le plus couramment utilisé sur les ordinateurs portables, c'est plus d'un quart des cellules disponibles qui est gaspillé sans aucune information. +Pour les programmeurs aveugles qui utilisent des afficheurs braille, chaque espace représente une cellule braille. Ainsi, si l'indentation par défaut est de 4 espaces, une indentation de 3ème niveau gaspille 12 précieuses cellules braille avant même le début du code. Sur un afficheur de 40 cellules, qui est le plus couramment utilisé sur les ordinateurs portables, cela représente plus d'un quart des cellules disponibles gaspillées sans aucune information. {{priority: -1}} diff --git a/contributing/fr/documentation.texy b/contributing/fr/documentation.texy index 7fa839dfdc..87d2fa6640 100644 --- a/contributing/fr/documentation.texy +++ b/contributing/fr/documentation.texy @@ -1,69 +1,68 @@ -Contribuer à la documentation -***************************** +Comment contribuer à la documentation +************************************* .[perex] -Contribuer à la documentation est l'une des activités les plus utiles, car elle aide les autres à comprendre le cadre. +Contribuer à la documentation est l'une des activités les plus enrichissantes, car vous aidez les autres à comprendre le framework. -Comment écrire ? .[#toc-how-to-write] -------------------------------------- +Comment écrire ? +---------------- -La documentation est principalement destinée aux personnes qui découvrent le sujet. Elle doit donc répondre à plusieurs points importants : +La documentation est principalement destinée aux personnes qui découvrent le sujet. Par conséquent, elle doit respecter plusieurs points importants : -- Commencer par des sujets simples et généraux. Passer à des sujets plus avancés à la fin -- Essayez d'expliquer le sujet aussi clairement que possible. Par exemple, essayez d'abord d'expliquer le sujet à un collègue. -- Ne fournissez que les informations que l'utilisateur a réellement besoin de connaître pour un sujet donné. -- Assurez-vous que vos informations sont exactes. Testez chaque code -- Soyez concis - coupez ce que vous écrivez en deux. Et n'hésitez pas à recommencer -- Utilisez la mise en évidence avec parcimonie, qu'il s'agisse de polices en gras ou de cadres tels que `.[note]` -- Respectez la [norme de codage |Coding Standard] dans le code +- Commencez par le simple et le général. N'abordez les sujets plus avancés qu'à la fin. +- Essayez d'expliquer les choses le mieux possible. Essayez par exemple d'expliquer d'abord le sujet à un collègue. +- Ne fournissez que les informations dont l'utilisateur a réellement besoin sur le sujet donné. +- Vérifiez que vos informations sont réellement exactes. Testez chaque exemple de code. +- Soyez concis - réduisez de moitié ce que vous écrivez. Et puis n'hésitez pas à le refaire. +- Économisez toutes sortes de mises en évidence, du texte en gras aux cadres comme `.[note]`. +- Respectez le [Standard de codage |Coding Standard] dans les exemples de code. -Apprenez également la [syntaxe |syntax]. Pour avoir un aperçu de l'article en cours de rédaction, vous pouvez utiliser l'[éditeur de prévisualisation |https://editor.nette.org/]. +Maîtrisez également la [syntaxe |syntax]. Pour prévisualiser l'article pendant son écriture, vous pouvez utiliser l'[éditeur avec aperçu |https://editor.nette.org/]. -Mutations linguistiques .[#toc-language-mutations] --------------------------------------------------- +Versions linguistiques +---------------------- -L'anglais étant la langue principale, vos modifications doivent être rédigées en anglais. Si l'anglais n'est pas votre fort, utilisez [DeepL Translator |https://www.deepl.com/translator] et d'autres personnes vérifieront votre texte. +La langue principale est l'anglais. Vos modifications devraient donc idéalement être apportées aux versions anglaise et tchèque de la documentation. Si l'anglais n'est pas votre point fort, utilisez [DeepL Translator |https://www.deepl.com/translator] et les autres vérifieront votre texte. -La traduction dans d'autres langues se fera automatiquement après l'approbation et la mise au point de votre texte. +La traduction dans les autres langues sera effectuée automatiquement après approbation et finalisation de votre modification. -Modifications mineures .[#toc-trivial-edits] --------------------------------------------- +Modifications triviales +----------------------- -Pour contribuer à la documentation, vous devez avoir un compte sur [GitHub |https://github.com]. +Pour contribuer à la documentation, il est nécessaire d'avoir un compte sur [GitHub|https://github.com]. -La manière la plus simple d'apporter une petite modification à la documentation est d'utiliser les liens qui se trouvent à la fin de chaque page : +La manière la plus simple d'apporter une petite modification à la documentation est d'utiliser les liens à la fin de chaque page : -- *Show on GitHub* ouvre la version source de la page sur GitHub. Il suffit ensuite d'appuyer sur le bouton `E` pour commencer à éditer (vous devez être connecté à GitHub). -- *Open preview* ouvre un éditeur où vous pouvez immédiatement voir la forme visuelle finale. +- *Afficher sur GitHub* ouvre la version source de la page donnée sur GitHub. Ensuite, il suffit d'appuyer sur le bouton `E` pour commencer à éditer (il faut être connecté à GitHub). +- *Ouvrir l'aperçu* ouvre l'éditeur, où vous voyez directement le rendu visuel final. -Comme l'[éditeur de prévisualisation |https://editor.nette.org/] ne permet pas d'enregistrer les modifications directement sur GitHub, vous devez copier le texte source dans le presse-papiers (à l'aide du bouton *Copier dans le presse-papiers*) et le coller ensuite dans l'éditeur sur GitHub. -Sous le champ d'édition se trouve un formulaire de soumission. N'oubliez pas de résumer et d'expliquer brièvement la raison de votre modification. Après la soumission, une demande d'extraction (PR) est créée, qui peut être modifiée ultérieurement. +Comme l'[éditeur avec aperçu |https://editor.nette.org/] n'a pas la possibilité d'enregistrer les modifications directement sur GitHub, il est nécessaire, après avoir terminé les modifications, de copier le texte source dans le presse-papiers (bouton *Copy to clipboard*) puis de le coller dans l'éditeur sur GitHub. Sous le champ d'édition se trouve un formulaire d'envoi. N'oubliez pas d'y résumer brièvement et d'expliquer la raison de votre modification. Après l'envoi, une pull request (PR) est créée, qui peut être éditée ultérieurement. -Modifications plus importantes .[#toc-larger-edits] ---------------------------------------------------- +Modifications plus importantes +------------------------------ -Il est plus approprié de se familiariser avec les bases du travail avec le système de contrôle de version Git plutôt que de se fier uniquement à l'interface GitHub. Si vous n'êtes pas familier avec Git, vous pouvez vous référer à [git - le guide simple |https://rogerdudler.github.io/git-guide/] et envisager d'utiliser l'un des nombreux [clients graphiques |https://git-scm.com/downloads/guis] disponibles. +Plutôt que d'utiliser l'interface GitHub, il est préférable d'être familiarisé avec les bases du travail avec le système de contrôle de version Git. Si vous ne maîtrisez pas Git, vous pouvez consulter le guide [git - le guide simple |https://rogerdudler.github.io/git-guide/] ou utiliser l'un des nombreux [clients graphiques |https://git-scm.com/downloads/guis]. -Modifiez la documentation de la manière suivante : +Modifiez la documentation de cette manière : -1) sur GitHub, créez une [fourche |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] du dépôt [nette/docs |https://github.com/nette/docs] -2) [clonez |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] ce dépôt sur votre ordinateur -3) ensuite, faites des changements dans la [branche appropriée |#Documentation Structure] -4) vérifiez qu'il n'y a pas d'espaces supplémentaires dans le texte à l'aide de l'outil [Code-Checker |code-checker:] -5) sauvegarder (commit) les changements -6) si vous êtes satisfait des changements, poussez-les sur GitHub vers votre fork -7) à partir de là, soumettez-les au dépôt `nette/docs` en créant une [pull request|https://help.github.com/articles/creating-a-pull-request] (PR). +1) Sur GitHub, créez un [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] du dépôt [nette/docs |https://github.com/nette/docs]. +2) [Clonez |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] ce dépôt sur votre ordinateur. +3) Ensuite, dans la [branche appropriée |#Structure de la documentation], effectuez les modifications. +4) Vérifiez les espaces superflus dans le texte à l'aide de l'outil [Code-Checker |code-checker:]. +5) Enregistrez (commitez) les changements. +6) Si vous êtes satisfait des changements, envoyez-les (pushez) sur GitHub vers votre fork. +7) De là, envoyez-les au dépôt `nette/docs` en créant une [pull request|https://help.github.com/articles/creating-a-pull-request] (PR). -Il est courant de recevoir des commentaires contenant des suggestions. Gardez une trace des changements proposés et incorporez-les. Ajoutez les modifications suggérées en tant que nouveaux commits et renvoyez-les à GitHub. Ne créez jamais une nouvelle demande d'extraction juste pour modifier une demande existante. +Il est courant que vous receviez des commentaires avec des remarques. Suivez les modifications suggérées et intégrez-les. Ajoutez les modifications suggérées comme de nouveaux commits et renvoyez-les sur GitHub. Ne créez jamais une nouvelle pull request pour modifier une pull request existante. -Structure de la documentation .[#toc-documentation-structure] -------------------------------------------------------------- +Structure de la documentation +----------------------------- -L'ensemble de la documentation se trouve sur GitHub dans le dépôt [nette/docs |https://github.com/nette/docs]. La version actuelle se trouve dans la branche master, tandis que les versions plus anciennes se trouvent dans des branches telles que `doc-3.x`, `doc-2.x`. +Toute la documentation est hébergée sur GitHub dans le dépôt [nette/docs |https://github.com/nette/docs]. La version actuelle est dans la branche `master`, les versions plus anciennes sont situées dans des branches comme `doc-3.x`, `doc-2.x`. -Le contenu de chaque branche est divisé en dossiers principaux représentant les différents domaines de la documentation. Par exemple, `application/` correspond à https://doc.nette.org/en/application, `latte/` correspond à https://latte.nette.org, etc. Chacun de ces dossiers contient des sous-dossiers représentant les mutations linguistiques (`cs`, `en`, ...) et éventuellement un sous-dossier `files` contenant des images qui peuvent être insérées dans les pages de la documentation. +Le contenu de chaque branche est divisé en dossiers principaux représentant les différentes sections de la documentation. Par exemple, `application/` correspond à https://doc.nette.org/fr/application, `latte/` correspond à https://latte.nette.org, etc. Chacun de ces dossiers contient des sous-dossiers représentant les versions linguistiques (`cs`, `en`, `fr`, ...) et éventuellement un sous-dossier `files` avec des images, qui peuvent être insérées dans les pages de la documentation. diff --git a/contributing/fr/syntax.texy b/contributing/fr/syntax.texy index 6a9bd63ae0..9b6df676f7 100644 --- a/contributing/fr/syntax.texy +++ b/contributing/fr/syntax.texy @@ -1,59 +1,59 @@ -Syntaxe du wiki -*************** +Syntaxe de la documentation +*************************** -Wiki utilise la [syntaxe |https://texy.info/en/syntax] Markdown & [Texy |https://texy.info/en/syntax] avec plusieurs améliorations. +La documentation utilise Markdown & la [syntaxe Texy |https://texy.nette.org/syntax] avec quelques extensions. -Liens .[#toc-links] -=================== +Liens +===== -Pour les références internes, la notation entre crochets `[link]` est utilisée. Elle se présente soit sous la forme avec une barre verticale `[link text |link target]`ou sous la forme abrégée `[link text]` si la cible est la même que le texte (après transformation en minuscules et traits d'union) : +Pour les liens internes, on utilise la notation entre crochets `[...]`. Soit sous la forme avec une barre verticale `[texte du lien |cible du lien]`, soit en abrégé `[Cible du lien]` si la cible est identique au texte (après transformation en minuscules et tirets) : - `[Page name]` -> `<a href="/en/page-name">Page name</a>` - `[link text |Page name]` -> `<a href="/en/page-name">link text</a>` -On peut créer un lien vers une autre langue ou une autre section. Une section est une bibliothèque Nette (par exemple `forms`, `latte`, etc.) ou des sections spéciales comme `best-practices`, `quickstart`, etc : +Nous pouvons lier à une autre version linguistique ou à une autre section. Une section désigne une bibliothèque Nette (par ex. `forms`, `latte`, etc.) ou des sections spéciales comme `best-practices`, `quickstart` etc. : -- `[cs:Page name]` -> `<a href="/en/page-name">Page name</a>` (même section, autre langue) +- `[cs:Page name]` -> `<a href="/cs/page-name">Page name</a>` (même section, langue différente) - `[tracy:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (section différente, même langue) -- `[tracy:cs:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (section et langue différentes) +- `[tracy:cs:Page name]` -> `<a href="//tracy.nette.org/cs/page-name">Page name</a>` (section et langue différentes) -Il est également possible de cibler une rubrique spécifique de la page avec `#`. +Avec `#`, il est également possible de cibler un titre spécifique sur la page. -- `[#Heading]` -> `<a href="#toc-heading">Heading</a>` (titre de la page actuelle) +- `[#Heading]` -> `<a href="#toc-heading">Heading</a>` (titre sur la page actuelle) - `[Page name#Heading]` -> `<a href="/en/page-name#toc-heading">Page name</a>` -Lien vers la page d'accueil de la section : (`@home` est un terme spécial pour la page d'accueil de la section) +Lien vers la page d'accueil de la section : (`@home` est une expression spéciale pour la page d'accueil de la section) - `[link text |@home]` -> `<a href="/en/">link text</a>` - `[link text |tracy:]` -> `<a href="//tracy.nette.org/en/">link text</a>` -Liens vers la documentation de l'API .[#toc-links-to-api-documentation] ------------------------------------------------------------------------ +Liens vers la documentation API +------------------------------- -Utilisez toujours les notations suivantes : +Utilisez toujours uniquement cette notation : - `[api:Nette\SmartObject]` -> [api:Nette\SmartObject] - `[api:Nette\Forms\Form::setTranslator()]` -> [api:Nette\Forms\Form::setTranslator()] - `[api:Nette\Forms\Form::$onSubmit]` -> [api:Nette\Forms\Form::$onSubmit] - `[api:Nette\Forms\Form::Required]` -> [api:Nette\Forms\Form::Required] -Les noms entièrement qualifiés ne sont utilisés que dans la première mention. Pour les autres liens, utiliser un nom simplifié : +Utilisez les noms pleinement qualifiés uniquement lors de la première mention. Pour les liens suivants, utilisez le nom simplifié : -- `[Form::setTranslator() |api:Nette\Forms\Form::setTranslator()]` -> [Formulaire::setTranslator() |api:Nette\Forms\Form::setTranslator()] +- `[Form::setTranslator() |api:Nette\Forms\Form::setTranslator()]` -> [Form::setTranslator() |api:Nette\Forms\Form::setTranslator()] -Liens vers la documentation PHP .[#toc-links-to-php-documentation] ------------------------------------------------------------------- +Liens vers la documentation PHP +------------------------------- - `[php:substr]` -> [php:substr] -Code source .[#toc-source-code] -=============================== +Code source +=========== -Le bloc de code commence par <code>```lang</code> et se termine par <code>```</code> Les langues prises en charge sont `php`, `latte`, `neon`, `html`, `css`, `js` et `sql`. Utilisez toujours des tabulations pour l'indentation. +Un bloc de code commence par <code>```lang</code> et se termine par <code>```</code>. Les langues prises en charge sont `php`, `latte`, `neon`, `html`, `css`, `js` et `sql`. Utilisez toujours des tabulations pour l'indentation. ``` ```php @@ -63,7 +63,7 @@ Le bloc de code commence par <code>```lang</code> et se termine par ``` ``` -Vous pouvez également spécifier le nom du fichier comme <code>```php .{file: ArrayTest.php}</code> et le bloc de code sera rendu de cette façon : +Vous pouvez également indiquer un nom de fichier comme <code>```php .{file: ArrayTest.php}</code> et le bloc de code sera rendu de cette manière : ```php .{file: ArrayTest.php} public function renderPage($id) @@ -72,71 +72,71 @@ public function renderPage($id) ``` -Intitulés .[#toc-headings] -========================== +Titres +====== -Titre supérieur (nom de la page) souligné par des étoiles (`*`). For normal headings use equal signs (`=`) and then hyphens (`-`). +Le titre le plus élevé (c'est-à-dire le nom de la page) doit être souligné par des étoiles. Pour séparer les sections, utilisez des signes égal. Soulignez les titres avec des signes égal, puis avec des tirets : ``` -MVC Applications & Presenters +Applications MVC & Presenters ***************************** ... -Link Creation -============= +Création de liens +================= ... -Links in Templates ------------------- +Liens dans les templates +------------------------ ... ``` -Boîtes et styles .[#toc-boxes-and-styles] -========================================= +Cadres et styles +================ -Paragraphe principal marqué d'une classe `.[perex]` .[perex] +Le perex est marqué avec la classe `.[perex]` .[perex] -Notes marquées avec la classe `.[note]` .[note] +Une note est marquée avec la classe `.[note]` .[note] -Conseil marqué avec la classe `.[tip]` .[tip] +Un conseil est marqué avec la classe `.[tip]` .[tip] -Avertissement marqué d'une classe `.[caution]` .[caution] +Un avertissement est marqué avec la classe `.[caution]` .[caution] -Avertissement fort marqué d'une classe `.[warning]` .[warning] +Un avertissement plus important est marqué avec la classe `.[warning]` .[warning] Numéro de version `.{data-version:2.4.10}` .{data-version:2.4.10} -Les classes doivent être écrites avant la ligne correspondante : +Écrivez les classes avant la ligne : ``` -.[note] -This is a note. +.[perex] +Ceci est l'introduction. ``` -Veuillez noter que les encadrés tels que `.[tip]` attirent l'attention et doivent donc être utilisés pour mettre l'accent, et non pour des informations moins importantes. +Veuillez noter que les cadres comme `.[tip]` attirent l'attention, ils sont donc utilisés pour mettre en évidence des informations importantes, et non pour des détails secondaires. Par conséquent, utilisez-les avec parcimonie. -Table des matières .[#toc-table-of-contents] -============================================ +Table des matières +================== -La table des matières (liens dans la barre latérale) est automatiquement générée lorsque la page dépasse 4 000 octets. Ce comportement par défaut peut être modifié à l'aide d'une balise `{{toc}}` ce comportement par défaut peut être modifié à l'aide d'un [mét de |#meta-tags] ce type. Le texte de la table des matières est repris par défaut de l'en-tête, mais il est possible d'utiliser un texte différent avec un modificateur `.{toc}` pour utiliser un autre texte. Ceci est particulièrement utile pour les titres longs. +La table des matières (liens dans le menu de droite) est générée automatiquement pour toutes les pages dont la taille dépasse 4 000 octets. Ce comportement par défaut peut être modifié à l'aide de la [Balise méta |#Balises méta] `{{toc}}`. Le texte affiché dans la table des matières est pris par défaut directement dans le texte des titres. Cependant, à l'aide du modificateur `.{toc}`, il est possible d'afficher un texte différent, ce qui est particulièrement utile pour les titres plus longs. ``` -Long and Intelligent Heading .{toc: A Different Text for TOC} -============================================================= +Titre long et potentiellement complexe .{toc: Titre court pour la table des matières} +===================================================================================== ``` -Balises Méta .[#toc-meta-tags] -============================== +Balises méta +============ -- définir votre propre titre de page (dans `<title>` et le fil d'Ariane) `{{title: Another name}}` -- redirection `{{redirect: pla:cs}}` - voir les [liens |#links] -- renforcer `{{toc}}` ou la désactivation `{{toc: no}}` table des matières +- définir le titre de la page personnalisée (dans `<title>` et le fil d'Ariane) `{{title : Autre titre}}`` +- redirection `{{redirect : pla:cs}}` - voir [#Liens] +- forcer `{{toc}}` ou désactiver `{{toc : no}}` le contenu automatique (boîte avec des liens vers les titres individuels) {{priority: -1}} diff --git a/contributing/hu/@home.texy b/contributing/hu/@home.texy index be891ac041..ea18ed53fa 100644 --- a/contributing/hu/@home.texy +++ b/contributing/hu/@home.texy @@ -1,17 +1,17 @@ -Legyen a Nette támogatója -************************* +Legyen Ön is Nette hozzájáruló +****************************** .[perex] -Nézze meg, hogyan vehet részt nyílt forráskódú projektünkben. Ismerje meg a forráskódhoz és a dokumentációhoz való hozzájárulás lépéseit, és csatlakozzon a Nette fejlesztőinek hálózatához, akik elkötelezettek a Nette fejlesztése iránt. +Tudja meg, hogyan vehet részt nyílt forráskódú projektünkben. Sajátítsa el a forráskódhoz és a dokumentációhoz való hozzájárulás eljárásait, és váljon a Nette fejlesztését aktívan segítő fejlesztői közösség részévé. **Kód** -- [Hozzájárulás a kódhoz |code] -- [Kódolási szabványok |coding-standard] +- [Hogyan járulhat hozzá a kódhoz? |code] +- [Kódolási szabvány |coding-standard] **Dokumentáció** -- [Hozzájárulás a dokumentációhoz |documentation] -- [Dokumentáció szintaxis |syntax] +- [Hogyan járulhat hozzá a dokumentációhoz? |documentation] +- [Dokumentációs szintaxis |syntax] - "Előnézeti szerkesztő":https://editor.nette.org diff --git a/contributing/hu/@left-menu.texy b/contributing/hu/@left-menu.texy index 794ed535ec..0775d11f06 100644 --- a/contributing/hu/@left-menu.texy +++ b/contributing/hu/@left-menu.texy @@ -1,10 +1,10 @@ -Kód: -**** -- [Hozzájárulás a kódhoz |code] -- [Kódolási szabványok |coding-standard] +Kód +*** +- [Hogyan járulhat hozzá a kódhoz? |code] +- [Kódolási szabvány |coding-standard] Dokumentáció ************ -- [Hozzájárulás a dokumentációhoz |documentation] -- [Dokumentáció szintaxis |syntax] +- [Hogyan járulhat hozzá a dokumentációhoz? |documentation] +- [Dokumentációs szintaxis |syntax] - "Előnézeti szerkesztő":https://editor.nette.org diff --git a/contributing/hu/code.texy b/contributing/hu/code.texy index f18bb335bc..97a520de2b 100644 --- a/contributing/hu/code.texy +++ b/contributing/hu/code.texy @@ -1,87 +1,87 @@ -Hozzájárulás a kódhoz -********************* +Hogyan járuljunk hozzá a kódhoz +******************************* .[perex] -Tervezi, hogy hozzájárul a Nette keretrendszerhez, és meg kell ismerkednie a szabályokkal és eljárásokkal? Ez a kezdő útmutató végigvezet a kódhoz való hatékony hozzájárulás, a tárolókkal való munka és a változtatások megvalósításának lépésein. +Készülsz hozzájárulni a Nette Frameworkhöz, és szükséged van eligazodásra a szabályokban és eljárásokban? Ez a kezdőknek szóló útmutató lépésről lépésre megmutatja, hogyan járulhatsz hozzá hatékonyan a kódhoz, hogyan dolgozz a repository-kkal és hogyan implementáld a változtatásokat. -Eljárás .[#toc-procedure] -========================= +Eljárás +======= -A kódhoz való hozzájáruláshoz elengedhetetlen, hogy rendelkezz egy fiókkal a [GitHubon |https://github.com], és ismerd a Git verziókezelő rendszerrel való munka alapjait. Ha nem ismered a Git-et, akkor nézd meg a [git - az egyszerű útmutatót |https://rogerdudler.github.io/git-guide/], és fontold meg a számos [grafikus kliens |https://git-scm.com/downloads/guis] egyikének használatát. +A kódhoz való hozzájáruláshoz elengedhetetlen egy [GitHub |https://github.com] fiók és a Git verziókezelő rendszer alapjainak ismerete. Ha nem ismered a Git használatát, megnézheted a [git - the simple guide |https://rogerdudler.github.io/git-guide/] útmutatót, és esetleg használhatod a számos [grafikus kliens |https://git-scm.com/downloads/guis] egyikét. -A környezet és a tároló előkészítése .[#toc-preparing-the-environment-and-repository] -------------------------------------------------------------------------------------- +Környezet és repository előkészítése +------------------------------------ -1) A GitHubon hozzon létre egy [elágazást |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] a [csomagtárolóból |www:packages], amelyet módosítani kíván. -2) [Klónozzuk |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] ezt a tárolót a számítógépünkre. -3) Telepítse a függőségeket, beleértve a [Nette Tester-t |tester:] is, a `composer install` parancs segítségével. -4) Ellenőrizze, hogy a tesztek működnek-e a következő futtatással `composer tester` -5) Hozzon létre egy [új ágat |#New Branch] a legfrissebb kiadott verzió alapján. +1) a GitHubon hozz létre egy [forkot |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] annak a [csomagnak |www:packages] a repository-jából, amelyet módosítani készülsz +2) ezt a repository-t [klónozd |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] a számítógépedre +3) telepítsd a függőségeket, beleértve a [Nette Testert |tester:] is, a `composer install` paranccsal +4) ellenőrizd, hogy a tesztek működnek-e, a `composer tester` futtatásával +5) hozz létre egy [új ágat |#Új ág] az utolsó kiadott verzió alapján -Saját változtatások végrehajtása .[#toc-implementing-your-own-changes] ----------------------------------------------------------------------- +Saját változtatások implementálása +---------------------------------- -Most már elvégezheti saját kódjainak módosítását: +Most végrehajthatod a saját kódmódosításaidat: -1) Végezze el a kívánt változtatásokat, és ne feledkezzen meg a tesztekről. -2) Győződjön meg róla, hogy a tesztek sikeresen futnak a `composer tester` -3) Ellenőrizze, hogy a kód megfelel-e a [kódolási szabványoknak |#coding standards]. -4) Mentse (commit) a változtatásokat egy leírással [ebben a formátumban |#Commit Description] +1) programozd le a kívánt változtatásokat, és ne feledkezz meg a tesztekről +2) győződj meg róla, hogy a tesztek sikeresen lefutnak, a `composer tester` segítségével +3) ellenőrizd, hogy a kód megfelel-e a [kódolási szabványnak |#Kódolási szabványok] +4) mentsd el a változtatásokat (commitold) egy leírással [ebben a formátumban |#Commit leírása] -Több commitot is létrehozhat, egyet-egyet minden egyes logikai lépéshez. Minden commitnak önmagában is értelmesnek kell lennie. +Létrehozhatsz több commitot, egyet minden logikai lépéshez. Minden commitnak önmagában értelmesnek kell lennie. -Változások elküldése .[#toc-submitting-changes] ------------------------------------------------ +Változtatások elküldése +----------------------- -Ha elégedett a módosításokkal, elküldheti azokat: +Amint elégedett vagy a változtatásokkal, elküldheted őket: -1) Tolja a változtatásokat a GitHubra a saját elágazásához -2) Onnan küldje el őket a Nette tárolóba egy [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) létrehozásával. -3) Adjon meg [elegendő információt |#pull request description] a leírásban +1) küldd el (pushold) a változtatásokat a GitHubra a saját forkodba +2) onnan küldd el őket a Nette repository-ba egy [pull request |https://help.github.com/articles/creating-a-pull-request] (PR) létrehozásával +3) adj meg a leírásban [elegendő információt |#Pull request leírása] -Visszajelzések beépítése .[#toc-incorporating-feedback] -------------------------------------------------------- +Észrevételek beépítése +---------------------- -A commitjaid most már mások számára is láthatóak. Gyakori, hogy javaslatokat tartalmazó megjegyzéseket kapunk: +A commitjaidat most már mások is látni fogják. Gyakori, hogy észrevételeket tartalmazó kommenteket kapsz: -1) Tartsa nyomon a javasolt változtatásokat -2) építse be őket új commitként vagy [egyesítse őket a korábbiakkal |https://help.github.com/en/github/using-git/about-git-rebase] -3) Küldje el újra a commitokat a GitHubra, és azok automatikusan megjelennek a pull requestben. +1) kövesd nyomon a javasolt módosításokat +2) építsd be őket új commitokként, vagy [olvaszd össze őket a korábbiakkal |https://help.github.com/en/github/using-git/about-git-rebase] +3) küldd el újra a commitokat a GitHubra, és automatikusan megjelennek a pull requestben -Soha ne hozzon létre új pull requestet egy meglévő módosításához. +Soha ne hozz létre új pull requestet egy meglévő módosítása miatt. -Dokumentáció .[#toc-documentation] ----------------------------------- +Dokumentáció +------------ -Ha megváltoztattál egy funkciót, vagy új funkciót adtál hozzá, ne felejtsd el [azt is hozzáadni a dokumentációhoz |documentation]. +Ha megváltoztattad a funkcionalitást vagy újat adtál hozzá, ne felejtsd el [hozzáadni a dokumentációhoz |documentation] is. -Új ág .[#toc-new-branch] -======================== +Új ág +===== -Ha lehetséges, végezze el a változtatásokat a legutolsó kiadott verzióval, azaz az ág utolsó tagjével szemben. A v3.2.1 címkéhez hozzon létre egy ágat ezzel a paranccsal: +Ha lehetséges, a változtatásokat az utolsó kiadott verzióhoz képest végezd, azaz az adott ág utolsó tagjéhez. A `v3.2.1` taghez ezzel a paranccsal hozhatsz létre ágat: ```shell git checkout -b new_branch_name v3.2.1 ``` -Kódolási szabványok .[#toc-coding-standards] -============================================ +Kódolási szabványok +=================== -A kódnak meg kell felelnie a Nette Frameworkben használt [kódolási szabványnak |coding standard]. A kód ellenőrzésére és javítására automatikus eszköz áll rendelkezésre. Ezt **globálisan** telepítheted a Composer segítségével egy általad választott mappába: +A kódodnak meg kell felelnie a Nette Frameworkben használt [kódolási szabványnak |coding-standard]. A kód ellenőrzésére és javítására rendelkezésre áll egy automatikus eszköz. Telepíthető a Composer segítségével **globálisan** egy általad választott mappába: ```shell composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Most már képesnek kell lennie az eszköz futtatására a terminálban. Az első parancs ellenőrzi, a második pedig javítja a kódot az aktuális könyvtárban lévő `src` és `tests` mappákban: +Most már képesnek kell lenned futtatni az eszközt a terminálban. Az első parancs ellenőrzi, a második pedig javítja is a kódot az `src` és `tests` mappákban az aktuális könyvtárban: ```shell /path/to/nette-coding-standard/ecs check @@ -89,29 +89,29 @@ Most már képesnek kell lennie az eszköz futtatására a terminálban. Az els ``` -Kötelezettségvállalás Leírás .[#toc-commit-description] -======================================================= +Commit leírása +============== -A Nette-ben a commit témák a következő formátumúak: `Presenter: fixed AJAX detection [Closes #69]` +A Nette-ben a commit tárgyak formátuma: `Presenter: fixed AJAX detection [Closes #69]` - terület, amelyet kettőspont követ -- a kötelezettségvállalás célja múlt időben; ha lehetséges, kezdje a következő szavakkal: added, fixed, refactored, changed, removed -- ha a commit megszakítja a visszafelé kompatibilitást, írja be, hogy "BC break". -- bármilyen kapcsolat a hibakövetővel, például `(#123)` vagy `[Closes #69]` -- a tárgy után egy üres sor következhet, amelyet egy részletesebb leírás követhet, beleértve például a fórumra mutató linkeket is. +- a commit célja múlt időben, ha lehetséges, kezdődjön a következő szavakkal: `added` (új funkció hozzáadva), `fixed` (javítás), `refactored` (kódváltozás viselkedésváltozás nélkül), `changed`, `removed` +- ha a commit megszakítja a visszamenőleges kompatibilitást, add hozzá a "BC break" jelzést +- esetleges kapcsolat az issue trackerrel, mint `(#123)` vagy `[Closes #69]` +- a tárgy után következhet egy üres sor, majd részletesebb leírás, beleértve például a fórumra mutató linkeket -Pull Request leírása .[#toc-pull-request-description] -===================================================== +Pull request leírása +==================== -A GitHub felületén a pull request létrehozásakor megadhat egy címet és egy leírást. Adjon tömör címet, és a leírásban adjon meg minél több információt a változtatás okairól. +Pull request létrehozásakor a GitHub felülete lehetővé teszi egy név és leírás megadását. Adj meg egy kifejező nevet, és a leírásban adj meg minél több információt a változtatásod okairól. -A fejlécben azt is adja meg, hogy új funkcióról vagy hibajavításról van-e szó, és hogy okozhat-e visszafelé kompatibilitási problémákat (BC break). Ha van kapcsolódó probléma, hivatkozzon rá, hogy az a pull request jóváhagyásakor lezárásra kerüljön. +Megjelenik egy fejléc is, ahol meg kell adnod, hogy új funkcióról vagy hibajavításról van-e szó, és hogy okozhat-e visszamenőleges kompatibilitási törést (BC break). Ha van kapcsolódó probléma (issue), hivatkozz rá, hogy a pull request jóváhagyása után lezárásra kerüljön. ``` -- bug fix / new feature? <!-- #issue numbers, if any --> +- bug fix / new feature? <!-- #issue számok, ha vannak --> - BC break? yes/no -- doc PR: nette/docs#? <!-- highly welcome, see https://nette.org/en/writing --> +- doc PR: nette/docs#? <!-- nagyon szívesen látjuk, lásd https://nette.org/en/writing --> ``` diff --git a/contributing/hu/coding-standard.texy b/contributing/hu/coding-standard.texy index 870513ab8d..48c1ec05cb 100644 --- a/contributing/hu/coding-standard.texy +++ b/contributing/hu/coding-standard.texy @@ -1,44 +1,44 @@ Kódolási szabvány ***************** -Ez a dokumentum a Nette fejlesztésére vonatkozó szabályokat és ajánlásokat írja le. Amikor kódot adsz hozzá a Nette-hez, ezeket be kell tartanod. A legegyszerűbb módja, hogy hogyan lehet ezt megtenni, ha utánozza a meglévő kódot. -Az ötlet lényege, hogy az egész kód úgy nézzen ki, mintha egy személy írta volna. .[perex] +.[perex] +Ez a dokumentum leírja a Nette fejlesztésére vonatkozó szabályokat és ajánlásokat. Amikor kódot járulsz hozzá a Nette-hez, be kell tartanod őket. Ennek legegyszerűbb módja a meglévő kód utánzása. A lényeg az, hogy minden kód úgy nézzen ki, mintha egyetlen ember írta volna. -A Nette kódolási szabvány megfelel a [PSR-12 kiterjesztett kódolási stílusnak |https://www.php-fig.org/psr/psr-12/], két fő kivétellel: a behúzásnál [szóközök helyett tabulátorokat |#tabs instead of spaces] használ, és [PascalCase-t |https://blog.nette.org/hu/kevesebb-sikolyert-a-kodban] használ az [osztálykonstansoknál |https://blog.nette.org/hu/kevesebb-sikolyert-a-kodban]. +A Nette Kódolási Szabvány megfelel a [PSR-12 Extended Coding Style |https://www.php-fig.org/psr/psr-12/]-nak, két fő kivétellel: a behúzáshoz [tabulátorokat használ szóközök helyett |#Tabulátorok szóközök helyett], és az "osztály konstansokhoz PascalCase-t használ":https://blog.nette.org/hu/for-less-screaming-in-the-code. -Általános szabályok .[#toc-general-rules] -========================================= +Általános szabályok +=================== -- Minden PHP fájlnak tartalmaznia kell `declare(strict_types=1)` -- Két üres sort használunk a módszerek elválasztására a jobb olvashatóság érdekében. -- A shut-up operátor használatának okát dokumentálni kell: `@mkdir($dir); // @ - directory may exist` -- Ha gyenge tipizált összehasonlító operátort használunk (pl. `==`, `!=`, ...), akkor a szándékot dokumentálni kell: `// == to accept null` -- Több kivételt is írhat egy fájlba `exceptions.php` -- A metódusok láthatósága nincs megadva az interfészek esetében, mivel azok mindig nyilvánosak. -- Minden tulajdonságnak, visszatérési értéknek és paraméternek meg kell adni a típusát. Másrészt a végleges konstansoknál soha nem adjuk meg a típust, mert az nyilvánvaló. -- A karakterlánc elhatárolására szimpla idézőjeleket kell használni, kivéve, ha maga a literál aposztrófokat tartalmaz. +- Minden PHP fájlnak tartalmaznia kell a `declare(strict_types=1)` deklarációt. +- Két üres sort használunk a metódusok elválasztására a jobb olvashatóság érdekében. +- A shut-up operátor használatának okát dokumentálni kell: `@mkdir($dir); // @ - a könyvtár létezhet`. +- Ha gyengén típusos összehasonlító operátort használunk (pl. `==`, `!=`, ...), a szándékot dokumentálni kell: `// == elfogadja a null-t` +- Egy `exceptions.php` fájlba több kivételt is írhatsz. +- Az interfészeknél nem adjuk meg a metódusok láthatóságát, mivel azok mindig public-ok. +- Minden property-nek, visszatérési értéknek és paraméternek meg kell adni a típusát. Ezzel szemben a final konstansoknál soha nem adjuk meg a típust, mert az nyilvánvaló. +- A stringek határolására aposztrófokat kell használni, kivéve, ha maga a literál tartalmaz aposztrófokat. -Elnevezési konvenciók .[#toc-naming-conventions] -================================================ +Elnevezési konvenciók +===================== -- Kerülje a rövidítések használatát, kivéve, ha a teljes név túlzó. -- A kétbetűs rövidítéseknél használjon nagybetűket, a hosszabb rövidítéseknél pedig pascal/camel betűket. -- Használjon főnevet vagy főnévi kifejezést az osztálynévhez. -- Az osztályneveknek nemcsak a specifikusságot (`Array`), hanem az általánosságot (`ArrayIterator`) is tartalmazniuk kell. Kivételt képeznek a PHP attribútumok. -- "Az osztályállandók és enumok PascalCaps-t használjanak":https://blog.nette.org/hu/kevesebb-sikolyert-a-kodban. -- "Az interfészek és absztrakt osztályok nem tartalmazhatnak előtagokat vagy utótagokat":https://blog.nette.org/hu/az-elotagok-es-utotagok-nem-tartoznak-az-interfesznevekbe, mint például `Abstract`, `Interface` vagy `I`. +- Ne használj rövidítéseket, hacsak a teljes név nem túl hosszú. +- Kétbetűs rövidítéseknél használj nagybetűket, hosszabb rövidítéseknél pascal/camel case-t. +- Az osztály nevéhez használj főnevet vagy szókapcsolatot. +- Az osztályneveknek nemcsak a specifikusságot (`Array`), hanem az általánosságot (`ArrayIterator`) is tartalmazniuk kell. Kivételt képeznek a PHP nyelvi attribútumok. +- "Az osztály konstansoknak és enumoknak PascalCaps-t kell használniuk":https://blog.nette.org/hu/for-less-screaming-in-the-code. +- "Az interfészeknek és absztrakt osztályoknak nem szabad előtagokat vagy utótagokat tartalmazniuk":https://blog.nette.org/hu/prefixes-and-suffixes-do-not-belong-in-interface-names, mint például `Abstract`, `Interface` vagy `I`. -Bekeretezés és zárójelek .[#toc-wrapping-and-braces] -==================================================== +Tördelés és zárójelek +===================== -A Nette kódolási szabvány megfelel a PSR-12-nek (vagy PER kódolási stílusnak), néhány ponton jobban meghatározza vagy módosítja azt: +A Nette Kódolási Szabvány megfelel a PSR-12-nek (illetve a PER Coding Style-nak), néhány pontban kiegészíti vagy módosítja azt: -- A nyílfüggvényeket a zárójelek előtt szóköz nélkül írjuk, pl. `fn($a) => $b` -- nincs szükség üres sorra a különböző típusú `use` import utasítások között. -- a függvény/módszer visszatérési típusát és a nyitó zárójelet a jobb olvashatóság érdekében külön sorokban kell elhelyezni: +- az arrow függvényeket szóköz nélkül írjuk a zárójel előtt, azaz `fn($a) => $b` +- nem szükséges üres sor a különböző típusú `use` import utasítások között +- a függvény/metódus visszatérési típusa és a nyitó kapcsos zárójel mindig külön sorokban vannak: ```php public function find( @@ -46,28 +46,32 @@ A Nette kódolási szabvány megfelel a PSR-12-nek (vagy PER kódolási stílusn array $options, ): array { - // metódus teste + // metódus törzse } ``` +A nyitó kapcsos zárójel külön sorban fontos a függvény/metódus szignatúrájának és törzsének vizuális elválasztásához. Ha a szignatúra egy sorban van, az elválasztás egyértelmű (bal oldali kép), ha több sorban van, a PSR-ben a szignatúra és a törzs egybefolyik (középen), míg a Nette szabványban továbbra is elkülönülnek (jobbra): -Dokumentációs blokkok (phpDoc) .[#toc-documentation-blocks-phpdoc] -================================================================== +[* new-line-after.webp *] -A fő szabály: soha ne duplikálj semmilyen aláírás információt, mint például a paraméter típusát vagy a visszatérési típust hozzáadott érték nélkül. -Dokumentációs blokk az osztály definíciójához: +Dokumentációs blokkok (phpDoc) +============================== + +Fő szabály: Soha ne duplikálj semmilyen információt a szignatúrában, mint például a paraméter típusa vagy a visszatérési típus, hozzáadott érték nélkül. + +Dokumentációs blokk egy osztály definíciójához: - Az osztály leírásával kezdődik. - Üres sor következik. -- A `@property` (vagy `@property-read`, `@property-write`) megjegyzések soronként következnek. Szintaxis: annotáció, szóköz, típus, szóköz, $név. -- A `@method` megjegyzések soronként következnek. A szintaxis a következő: annotation, space, return type, space, name(type $param, ...). -- A `@author` megjegyzést elhagyjuk. A szerzőséget a forráskód-történetben tartjuk nyilván. -- A `@internal` vagy a `@deprecated` annotáció használható. +- Az `@property` (vagy `@property-read`, `@property-write`) annotációk következnek, egymás után. Szintaxis: annotáció, szóköz, típus, szóköz, $név. +- Az `@method` annotációk következnek, egymás után. Szintaxis: annotáció, szóköz, visszatérési típus, szóköz, név(típus $param, ...). +- Az `@author` annotációt kihagyjuk. A szerzőiséget a forráskód története őrzi meg. +- Használhatók az `@internal` vagy `@deprecated` annotációk. ```php /** - * MIME message part. + * MIME üzenet rész. * * @property string $encoding * @property-read array $headers @@ -76,27 +80,27 @@ Dokumentációs blokk az osztály definíciójához: */ ``` -A csak `@var` megjegyzést tartalmazó tulajdonság dokumentációs blokkjának egysorosnak kell lennie: +Egy property dokumentációs blokkja, amely csak az `@var` annotációt tartalmazza, egysoros legyen: ```php /** @var string[] */ private array $name; ``` -Dokumentációs blokk a módszer definíciójához: +Dokumentációs blokk egy metódus definíciójához: -- A módszer rövid leírásával kezdődik. +- Rövid metódusleírással kezdődik. - Nincs üres sor. -- A `@param` megjegyzések, soronként. -- A `@return` megjegyzés. -- A `@throws` megjegyzések, soronként. -- A `@internal` vagy a `@deprecated` megjegyzések használhatók. +- Az `@param` annotációk külön sorokban. +- Az `@return` annotáció. +- Az `@throws` annotációk, egymás után. +- Használhatók az `@internal` vagy `@deprecated` annotációk. -Minden megjegyzést egy szóköz követ, kivéve a `@param` megjegyzést, amelyet a jobb olvashatóság érdekében két szóköz követ. +Minden annotációt egy szóköz követ, kivéve az `@param`-ot, amelyet a jobb olvashatóság érdekében két szóköz követ. ```php /** - * Finds a file in directory. + * Fájlt keres egy könyvtárban. * @param string[] $options * @return string[] * @throws DirectoryNotFoundException @@ -105,21 +109,20 @@ public function find(string $dir, array $options): array ``` -Tabulátorok szóközök helyett .[#toc-tabs-instead-of-spaces] -=========================================================== +Tabulátorok szóközök helyett +============================ -A tabulátoroknak több előnye is van a szóközökkel szemben: +A tabulátoroknak számos előnyük van a szóközökkel szemben: -- A behúzás mérete testre szabható a szerkesztőkben és a "web" -ben:https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size. -- nem kényszerítik rá a felhasználó behúzási méretének preferenciáját a kódra, így a kód jobban hordozható. -- egyetlen billentyűleütéssel beírhatók (bárhol, nem csak a tabulátorokat szóközökké alakító szerkesztőkben). +- a behúzás mérete a szerkesztőkben és a "weben":https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size testreszabható +- nem erőltetik a kódra a felhasználó behúzásméret-preferenciáját, így a kód jobban hordozható +- egyetlen billentyűleütéssel írhatók (bárhol, nem csak azokban a szerkesztőkben, amelyek a tabulátorokat szóközökre cserélik) - a behúzás a céljuk -- tiszteletben tartják a látássérült és vak munkatársak igényeit +- tiszteletben tartják a látássérült és vak kollégák igényeit -Azzal, hogy tabulátorokat használunk a projektjeinkben, lehetővé tesszük a szélesség testreszabását, ami a legtöbb ember számára szükségtelennek tűnhet, de a látássérült emberek számára elengedhetetlen. +A tabulátorok használatával projektjeinkben lehetővé tesszük a szélesség testreszabását, ami a legtöbb ember számára feleslegesnek tűnhet, de a látássérültek számára elengedhetetlen. -A vak programozók számára, akik Braille-kijelzőt használnak, minden egyes szóköz egy Braille-cellával van ábrázolva, és értékes helyet foglal el. Tehát ha az alapértelmezett behúzás 4 szóköz, a 3. szintű behúzás 12 braille-cellát pazarol a kód kezdete előtt. -Egy 40 cellás kijelzőn, amelyet a laptopokon leggyakrabban használnak, ez a rendelkezésre álló cellák több mint egynegyedét jelenti, amelyet információ nélkül elpazarolnak. +A Braille-kijelzőket használó vak programozók számára minden szóköz egy Braille-cellát jelent. Tehát ha az alapértelmezett behúzás 4 szóköz, a 3. szintű behúzás 12 értékes Braille-cellát pazarol el még a kód kezdete előtt. Egy 40 cellás kijelzőn, amelyet a laptopoknál leggyakrabban használnak, ez a rendelkezésre álló cellák több mint negyede, amelyet információ nélkül pazarolnak el. {{priority: -1}} diff --git a/contributing/hu/documentation.texy b/contributing/hu/documentation.texy index 7e28cbe78c..7ca946186c 100644 --- a/contributing/hu/documentation.texy +++ b/contributing/hu/documentation.texy @@ -1,69 +1,68 @@ -Hozzájárulás a dokumentációhoz -****************************** +Hogyan járuljunk hozzá a dokumentációhoz +**************************************** .[perex] -A dokumentációhoz való hozzájárulás az egyik legértékesebb tevékenység, mivel segít másoknak megérteni a keretrendszert. +A dokumentációhoz való hozzájárulás az egyik leghasznosabb tevékenység, mivel segít másoknak megérteni a keretrendszert. -Hogyan írjunk? .[#toc-how-to-write] ------------------------------------ +Hogyan írjunk? +-------------- -A dokumentáció elsősorban azoknak szól, akik újak a témában. Ezért több fontos pontnak kell megfelelnie: +A dokumentáció elsősorban azoknak szól, akik most ismerkednek a témával. Ezért több fontos pontnak kell megfelelnie: -- Kezdje egyszerű és általános témákkal. A végén térjen át a haladóbb témákra -- Igyekezzen a lehető legvilágosabban elmagyarázni a témát. Például próbálja meg először elmagyarázni a témát egy kollégának. -- Csak olyan információkat adjon meg, amelyeket a felhasználónak valóban tudnia kell az adott témához. -- Győződjön meg arról, hogy az információi pontosak. Teszteljen minden kódot -- Legyen tömör - vágja félbe, amit ír. És aztán nyugodtan csináld újra -- Használjon takarékosan kiemelést, a félkövér betűtípusoktól kezdve a keretekig, mint pl. `.[note]` -- Kövesse a [kódolási szabványt |Coding Standard] a kódban +- Kezdje az egyszerűtől és általánostól. Csak a végén térjen át a haladóbb témákra. +- Próbálja meg a lehető legjobban elmagyarázni a dolgot. Például próbálja meg először elmagyarázni a témát egy kollégának. +- Csak azokat az információkat közölje, amelyekre a felhasználónak valóban szüksége van az adott témához. +- Ellenőrizze, hogy az információi valóban igazak-e. Minden kódot teszteljen le. +- Legyen tömör - amit ír, rövidítse le a felére. Aztán nyugodtan még egyszer. +- Takarékoskodjon mindenféle kiemeléssel, a félkövér betűktől az olyan keretekig, mint a `.[note]`. +- A kódokban tartsa be a [Kódolási Szabványt |Coding Standard]. -Tanuld meg a [szintaxist |syntax] is. A cikk írás közbeni előnézetéhez használhatja az [előnézeti szerkesztőt |https://editor.nette.org/]. +Sajátítsa el a [szintaxist |syntax] is. A cikk írása közbeni előnézethez használhatja az [előnézeti szerkesztőt |https://editor.nette.org/]. -Nyelvi mutációk .[#toc-language-mutations] ------------------------------------------- +Nyelvi változatok +----------------- -Az angol az elsődleges nyelv, ezért a módosításoknak angolul kell történniük. Ha az angol nem az erősséged, használd a [DeepL fordítót |https://www.deepl.com/translator], és mások ellenőrzik a szövegedet. +Az elsődleges nyelv az angol, tehát a változtatásainak csehül és angolul is meg kell lenniük. Ha az angol nem az erőssége, használja a [DeepL Translator |https://www.deepl.com/translator]-t, és a többiek ellenőrzik a szövegét. -A más nyelvekre történő fordítás automatikusan megtörténik a szerkesztésed jóváhagyása és finomhangolása után. +A többi nyelvre történő fordítás automatikusan megtörténik a módosítás jóváhagyása és finomítása után. -Triviális szerkesztések .[#toc-trivial-edits] ---------------------------------------------- +Apróbb módosítások +------------------ -A dokumentációhoz való hozzájáruláshoz egy fiókkal kell rendelkeznie a [GitHubon |https://github.com]. +A dokumentációhoz való hozzájáruláshoz elengedhetetlen egy [GitHub |https://github.com] fiók. -A legegyszerűbb módja annak, hogy egy apró változtatást végezzünk a dokumentációban, ha az egyes oldalak végén található linkeket használjuk: +A legegyszerűbb módja egy apróbb változtatás végrehajtásának a dokumentációban az, ha kihasználja az egyes oldalak végén található linkeket: -- *Megjelenítés a GitHubon* megnyitja az oldal forrásváltozatát a GitHubon. Ezután csak nyomd meg a `E` gombot, és máris kezdheted a szerkesztést (be kell jelentkezned a GitHubra). -- *Előnézet megnyitása* megnyit egy szerkesztőt, ahol azonnal láthatja a végleges vizuális formát +- *Megjelenítés GitHubon* megnyitja az adott oldal forráskódját a GitHubon. Ezután elég megnyomni az `E` gombot, és elkezdheti a szerkesztést (szükséges bejelentkezni a GitHubra). +- *Előnézet megnyitása* megnyitja a szerkesztőt, ahol rögtön láthatja a végső vizuális megjelenést is. -Mivel [az előnézeti szerkesztő |https://editor.nette.org/] nem képes a változtatások közvetlen mentésére a GitHubra, a forrásszöveget a vágólapra kell másolnia (a *Másolás a vágólapra* gomb segítségével), majd beillesztenie a GitHubon lévő szerkesztőbe. -A szerkesztőmező alatt található egy űrlap a beküldéshez. Itt ne felejtse el röviden összefoglalni és megmagyarázni a szerkesztés okát. A beküldés után egy úgynevezett pull request (PR) jön létre, amelyet tovább lehet szerkeszteni. +Mivel az [előnézeti szerkesztő |https://editor.nette.org/] nem tudja közvetlenül a GitHubra menteni a változtatásokat, a módosítások befejezése után a forrásszöveget a vágólapra kell másolni (a *Copy to clipboard* gombbal), majd beilleszteni a GitHub szerkesztőjébe. A szerkesztőmező alatt található az elküldési űrlap. Itt ne felejtse el röviden összefoglalni és elmagyarázni a módosítás okát. Az elküldés után létrejön egy úgynevezett pull request (PR), amelyet tovább lehet szerkeszteni. -Nagyobb szerkesztések .[#toc-larger-edits] ------------------------------------------- +Nagyobb módosítások +------------------- -Célszerűbb a Git verziókezelő rendszerrel való munka alapjait ismerni, mint kizárólag a GitHub felületére hagyatkozni. Ha nem ismeri a Gitet, olvassa el a [git - az egyszerű útmutatót |https://rogerdudler.github.io/git-guide/], és fontolja meg a számos elérhető [grafikus kliens |https://git-scm.com/downloads/guis] egyikének használatát. +A GitHub felületének használata helyett célszerűbb tisztában lenni a Git verziókezelő rendszer alapjaival. Ha nem ismeri a Git használatát, megnézheti a [git - the simple guide |https://rogerdudler.github.io/git-guide/] útmutatót, és esetleg használhatja a számos [grafikus kliens |https://git-scm.com/downloads/guis] egyikét. -Szerkessze a dokumentációt a következő módon: +A dokumentációt a következő módon szerkessze: -1) a GitHubon hozzon létre egy [elágazást |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] a [nette/docs |https://github.com/nette/docs] tárolóból. -2) [klónozza |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] ezt a tárolót a számítógépére -3) ezután végezzen módosításokat a [megfelelő ágban |#Documentation Structure] -4) a [Code-Checker |code-checker:] eszközzel ellenőrizze, hogy nincsenek-e extra szóközök a szövegben. -5) mentse (commit) a változtatásokat -6) ha elégedett vagy a változtatásokkal, tedd fel a GitHubra a saját elágazásodba. -7) onnan küldje el őket a `nette/docs` tárolóba egy [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) létrehozásával. +1) a GitHubon hozzon létre egy [forkot |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] a [nette/docs |https://github.com/nette/docs] repository-ból +2) ezt a repository-t [klónozza |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] a számítógépére +3) ezután a [megfelelő ágban |#Dokumentáció struktúrája] végezze el a változtatásokat +4) ellenőrizze a felesleges szóközöket a szövegben a [Code-Checker |code-checker:] eszközzel +5) mentse el a változtatásokat (commitolja) +6) ha elégedett a változtatásokkal, küldje el (pusholja) őket a GitHubra a saját forkjába +7) onnan küldje el őket a `nette/docs` repository-ba egy [pull request |https://help.github.com/articles/creating-a-pull-request] (PR) létrehozásával -Gyakori, hogy javaslatokat tartalmazó megjegyzéseket kapunk. Tartsd szemmel a javasolt változtatásokat és építsd be őket. A javasolt változtatásokat új commitként add hozzá, és küldd el újra a GitHubra. Soha ne hozzon létre új pull requestet csak azért, hogy egy meglévőt módosítson. +Gyakori, hogy észrevételeket tartalmazó kommenteket fog kapni. Kövesse nyomon a javasolt változtatásokat, és építse be őket. A javasolt változtatásokat adja hozzá új commitokként, és küldje el újra a GitHubra. Soha ne hozzon létre új pull requestet egy pull request módosítása miatt. -Dokumentáció szerkezete .[#toc-documentation-structure] -------------------------------------------------------- +Dokumentáció struktúrája +------------------------ -A teljes dokumentáció a GitHubon található a [nette/docs |https://github.com/nette/docs] tárolóban. Az aktuális verzió a master ágban található, míg a régebbi verziók a `doc-3.x`, `doc-2.x` ágakban találhatók. +Az egész dokumentáció a GitHubon található a [nette/docs |https://github.com/nette/docs] repository-ban. Az aktuális verzió a master ágban van, a régebbi verziók olyan ágakban találhatók, mint a `doc-3.x`, `doc-2.x`. -Az egyes ágak tartalma fő mappákra van osztva, amelyek a dokumentáció egyes területeit képviselik. Például a `application/` megfelel a https://doc.nette.org/en/application, a `latte/` megfelel a https://latte.nette.org, stb. Mindegyik mappa tartalmaz nyelvi mutációkat képviselő almappákat (`cs`, `en`, ...) és opcionálisan egy `files` almappát a dokumentáció oldalaira beilleszthető képekkel. +Minden ág tartalma fő mappákra oszlik, amelyek a dokumentáció egyes területeit képviselik. Például az `application/` megfelel a https://doc.nette.org/hu/application címnek, a `latte/` megfelel a https://latte.nette.org címnek stb. Minden ilyen mappa tartalmaz almappákat, amelyek a nyelvi változatokat (`hu`, `en`, ...) képviselik, és esetleg egy `files` almappát képekkel, amelyeket be lehet illeszteni a dokumentáció oldalaira. diff --git a/contributing/hu/syntax.texy b/contributing/hu/syntax.texy index cb04cc84f4..11e46f6417 100644 --- a/contributing/hu/syntax.texy +++ b/contributing/hu/syntax.texy @@ -1,59 +1,59 @@ -Wiki szintaxis -************** +Dokumentációs szintaxis +*********************** -A Wiki a Markdown és [Texy szintaxist |https://texy.info/en/syntax] használja, számos fejlesztéssel. +A dokumentáció Markdown & [Texy szintaxist |https://texy.nette.org/syntax] használ néhány kiterjesztéssel. -Linkek .[#toc-links] -==================== +Linkek +====== -Belső hivatkozások esetén a szögletes zárójelben lévő jelölés `[link]` használjuk. Ez vagy a függőleges vonallal ellátott formában történik `[link text |link target]`, vagy rövidített formában `[link text]` ha a cél megegyezik a szöveggel (kisbetűvé és kötőjelekké való átalakítás után): +Belső linkekhez szögletes zárójelekben `[link |odkaz]` írásmódot használunk. Vagy függőleges vonallal elválasztott formában `[link szövege |link célja]`, vagy rövidítve `[link szövege]`, ha a cél megegyezik a szöveggel (kisbetűssé és kötőjelessé alakítás után): -- `[Page name]` -> `<a href="/en/page-name">Page name</a>` -- `[link text |Page name]` -> `<a href="/en/page-name">link text</a>` +- `[Page name]` -> `<a href="/hu/page-name">Page name</a>` +- `[link szövege |Page name]` -> `<a href="/hu/page-name">link szövege</a>` -Egy másik nyelvre vagy egy másik szekcióra is hivatkozhatunk. A szekció egy Nette könyvtár (pl. `forms`, `latte`, stb.) vagy speciális szekciók, mint `best-practices`, `quickstart`, stb: +Hivatkozhatunk más nyelvi változatra vagy más szekcióra. Szekció alatt Nette könyvtárat értünk (pl. `forms`, `latte`, stb.) vagy speciális szekciókat, mint `best-practices`, `quickstart` stb.: -- `[cs:Page name]` -> `<a href="/en/page-name">Page name</a>` (ugyanaz a szekció, más nyelv) -- `[tracy:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (másik szakasz, ugyanaz a nyelv) -- `[tracy:cs:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (más szekció és más nyelv) +- `[cs:Page name]` -> `<a href="/cs/page-name">Page name</a>` (ugyanaz a szekció, más nyelv) +- `[tracy:Page name]` -> `<a href="//tracy.nette.org/hu/page-name">Page name</a>` (más szekció, ugyanaz a nyelv) +- `[tracy:cs:Page name]` -> `<a href="//tracy.nette.org/cs/page-name">Page name</a>` (más szekció és más nyelv) -Lehetőség van arra is, hogy a `#` segítségével egy adott címsorra célozzunk az oldalon. +A `#` segítségével egy adott címsorra is lehet célozni az oldalon. -- `[#Heading]` -> `<a href="#toc-heading">Heading</a>` (az aktuális oldal fejléce) -- `[Page name#Heading]` -> `<a href="/en/page-name#toc-heading">Page name</a>` +- `[#Heading]` -> `<a href="#toc-heading">Heading</a>` (címsor az aktuális oldalon) +- `[Page name#Heading]` -> `<a href="/hu/page-name#toc-heading">Page name</a>` -Link a szakasz kezdőlapjára: (`@home` a szekció kezdőlapjának speciális kifejezése) +Link a szekció kezdőoldalára: (`@home` egy speciális kifejezés a szekció kezdőoldalára) -- `[link text |@home]` -> `<a href="/en/">link text</a>` -- `[link text |tracy:]` -> `<a href="//tracy.nette.org/en/">link text</a>` +- `[link szövege |@home]` -> `<a href="/hu/">link szövege</a>` +- `[link szövege |tracy:]` -> `<a href="//tracy.nette.org/hu/">link szövege</a>` -Linkek az API dokumentációhoz .[#toc-links-to-api-documentation] ----------------------------------------------------------------- +Linkek az API dokumentációba +---------------------------- -Mindig a következő jelöléseket használja: +Mindig csak ezzel az írásmóddal adjuk meg: - `[api:Nette\SmartObject]` -> [api:Nette\SmartObject] - `[api:Nette\Forms\Form::setTranslator()]` -> [api:Nette\Forms\Form::setTranslator()] - `[api:Nette\Forms\Form::$onSubmit]` -> [api:Nette\Forms\Form::$onSubmit] - `[api:Nette\Forms\Form::Required]` -> [api:Nette\Forms\Form::Required] -A teljesen minősített nevek csak az első említésben használatosak. A többi hivatkozásnál használjon egyszerűsített nevet: +Teljesen minősített neveket csak az első említéskor használjunk. További hivatkozásokhoz használjunk egyszerűsített nevet: - `[Form::setTranslator() |api:Nette\Forms\Form::setTranslator()]` -> [Form::setTranslator() |api:Nette\Forms\Form::setTranslator()] -Linkek a PHP dokumentációhoz .[#toc-links-to-php-documentation] ---------------------------------------------------------------- +Linkek a PHP dokumentációba +--------------------------- - `[php:substr]` -> [php:substr] -Forráskód .[#toc-source-code] -============================= +Forráskód +========= -A kódblokk a <code>```lang</code> kezdődik és <code>```</code> végződik. A támogatott nyelvek: `php`, `latte`, `neon`, `html`, `css`, `js` és `sql`. A behúzáshoz mindig használjon tabulátorokat. +A kódblokk <code>```lang</code>-gal kezdődik és <code>```</code>-gal végződik. Támogatott nyelvek: `php`, `latte`, `neon`, `html`, `css`, `js` és `sql`. A behúzáshoz mindig tabulátorokat használjunk. ``` ```php @@ -63,7 +63,7 @@ A kódblokk a <code>```lang</code> kezdődik és <code>`` ``` ``` -A fájlnevet megadhatja <code>```php .{file: ArrayTest.php}</code> néven is, és a kódblokk így lesz megjelenítve: +Megadhatja a fájlnevet is, mint <code>```php .{file: ArrayTest.php}</code>, és a kódblokk így fog megjelenni: ```php .{file: ArrayTest.php} public function renderPage($id) @@ -72,71 +72,71 @@ public function renderPage($id) ``` -Címsorok .[#toc-headings] -========================= +Címsorok +======== -Felső címsor (oldal neve) csillagokkal aláhúzva (`*`). For normal headings use equal signs (`=`) and then hyphens (`-`). +A legfelső címsort (azaz az oldal nevét) csillagokkal húzza alá. A szekciók elválasztásához használjon egyenlőségjeleket. A címsorokat egyenlőségjelekkel, majd kötőjelekkel húzza alá: ``` -MVC Applications & Presenters -***************************** +MVC Alkalmazások & presenterek +****************************** ... -Link Creation -============= +Linkek létrehozása +================== ... -Links in Templates +Linkek sablonokban ------------------ ... ``` -Keretek és stílusok .[#toc-boxes-and-styles] -============================================ +Keretek és stílusok +=================== -A vezető bekezdés osztállyal jelölve `.[perex]` .[perex] +A perexet a `.[perex]` osztállyal jelöljük. .[perex] -Osztállyal jelölt megjegyzések `.[note]` .[note] +A megjegyzést a `.[note]` osztállyal jelöljük. .[note] -Osztállyal jelölt tipp `.[tip]` .[tip] +A tippet a `.[tip]` osztállyal jelöljük. .[tip] -Figyelmeztetés osztállyal jelölve `.[caution]` .[caution] +A figyelmeztetést a `.[caution]` osztállyal jelöljük. .[caution] -Erős figyelmeztetés osztállyal jelölve `.[warning]` .[warning] +Az erősebb figyelmeztetést a `.[warning]` osztállyal jelöljük. .[warning] Verziószám `.{data-version:2.4.10}` .{data-version:2.4.10} -Az osztályokat a kapcsolódó sor elé kell írni: +Az osztályokat a sor elé írja: ``` -.[note] -This is a note. +.[perex] +Ez a perex. ``` -Kérjük, vegye figyelembe, hogy az olyan dobozok, mint `.[tip]` felhívja a figyelmet, és ezért hangsúlyozásra kell használni, nem pedig kevésbé fontos információkra. +Kérjük, vegye figyelembe, hogy az olyan keretek, mint a `.[tip]`, "vonzzák" a szemet, ezért kiemelésre használják őket, nem pedig kevésbé fontos információkra. Ezért használatukkal maximálisan takarékoskodjon. -Tartalomjegyzék .[#toc-table-of-contents] -========================================= +Tartalomjegyzék +=============== -A tartalomjegyzék (linkek az oldalsávban) automatikusan generálódik, ha az oldal hosszabb, mint 4 000 bájt. Ez az alapértelmezett viselkedés megváltoztatható a `{{toc}}` [meta taggel |#meta-tags] módosítható. A tartalomjegyzék szövege alapértelmezés szerint a címsorból származik, de lehetőség van más szöveget is használni egy `.{toc}` módosítóval. Ez különösen a hosszabb címsorok esetében hasznos. +A tartalomjegyzék (linkek a jobb oldali menüben) automatikusan generálódik minden olyan oldalhoz, amelynek mérete meghaladja a 4000 bájtot, de ez az alapértelmezett viselkedés módosítható a [#Meta tagek] `{{toc}}` segítségével. A tartalomjegyzéket alkotó szöveg alapértelmezés szerint közvetlenül a címsorok szövegéből származik, de a `.{toc}` módosítóval lehetőség van más szöveg megjelenítésére a tartalomjegyzékben, ami különösen hosszabb címsorok esetén hasznos. ``` -Long and Intelligent Heading .{toc: A Different Text for TOC} -============================================================= +Hosszú és intelligens címsor .{toc: Tetszőleges más szöveg a tartalomjegyzékben} +================================================================================ ``` -Meta címkék .[#toc-meta-tags] -============================= +Meta tagek +========== -- saját oldalcím beállítása (a `<title>` és morzsák) `{{title: Another name}}` -- átirányítás `{{redirect: pla:cs}}` - [linkekmegtekintése |#links] -- kikényszerítés `{{toc}}` vagy letiltása `{{toc: no}}` tartalomjegyzék +- saját oldalnév beállítása (a `<title>`-ben és a morzsamenüben) `{{title: Másik név}}` +- átirányítás `{{redirect: pla:cs}}` - lásd [#Linkek] +- az automatikus tartalomjegyzék (a linkeket tartalmazó doboz az egyes címsorokra) kényszerítése `{{toc}}` vagy letiltása `{{toc: no}}` {{priority: -1}} diff --git a/contributing/it/@home.texy b/contributing/it/@home.texy index 8630aa1a74..797897d6e6 100644 --- a/contributing/it/@home.texy +++ b/contributing/it/@home.texy @@ -1,17 +1,17 @@ -Diventa un collaboratore di Nette -********************************* +Diventa un contributore di Nette +******************************** .[perex] -Date un'occhiata a come potete partecipare al nostro progetto open source. Scoprite i passaggi per contribuire al codice sorgente e alla documentazione e unitevi alla rete di sviluppatori che si dedicano a migliorare Nette. +Scopri come puoi partecipare al nostro progetto open source. Impara le procedure per contribuire al codice sorgente e alla documentazione e diventa parte della comunità di sviluppatori che partecipano attivamente al miglioramento di Nette. **Codice** -- [Contribuire al codice |code] +- [Come contribuire al codice? |code] - [Standard di codifica |coding-standard] **Documentazione** -- [Contribuire alla documentazione |documentation] +- [Come contribuire alla documentazione? |documentation] - [Sintassi della documentazione |syntax] - "Editor di anteprima":https://editor.nette.org diff --git a/contributing/it/@left-menu.texy b/contributing/it/@left-menu.texy index aff3098056..3c77da0d39 100644 --- a/contributing/it/@left-menu.texy +++ b/contributing/it/@left-menu.texy @@ -1,10 +1,10 @@ Codice ****** -- [Contribuire al codice |code] +- [Come contribuire al codice? |code] - [Standard di codifica |coding-standard] Documentazione ************** -- [Contribuire alla documentazione |documentation] +- [Come contribuire alla documentazione? |documentation] - [Sintassi della documentazione |syntax] - "Editor di anteprima":https://editor.nette.org diff --git a/contributing/it/code.texy b/contributing/it/code.texy index 1e1cb654a4..7a6e6363b1 100644 --- a/contributing/it/code.texy +++ b/contributing/it/code.texy @@ -1,87 +1,87 @@ -Contribuire al codice -********************* +Come contribuire al codice +************************** .[perex] -Avete intenzione di contribuire al framework Nette e avete bisogno di familiarizzare con le regole e le procedure? Questa guida per principianti vi guiderà attraverso i passaggi per contribuire efficacemente al codice, lavorare con i repository e implementare le modifiche. +State pensando di contribuire a Nette Framework e avete bisogno di orientarvi tra le regole e le procedure? Questa guida per principianti vi mostrerà passo dopo passo come contribuire efficacemente al codice, lavorare con i repository e implementare le modifiche. -Procedura .[#toc-procedure] -=========================== +Procedura +========= -Per contribuire al codice, è essenziale avere un account su [GitHub |https://github.com] e conoscere le basi del sistema di controllo di versione Git. Se non si ha familiarità con Git, si può consultare la [guida git - the simple guide |https://rogerdudler.github.io/git-guide/] e prendere in considerazione l'utilizzo di uno dei tanti [client grafici |https://git-scm.com/downloads/guis]. +Per contribuire al codice è indispensabile avere un account su [GitHub|https://github.com] ed essere familiari con le basi del lavoro con il sistema di versionamento Git. Se non conoscete il lavoro con Git, potete consultare la guida [git - the simple guide |https://rogerdudler.github.io/git-guide/] ed eventualmente utilizzare uno dei tanti [client grafici |https://git-scm.com/downloads/guis]. -Preparazione dell'ambiente e del repository .[#toc-preparing-the-environment-and-repository] --------------------------------------------------------------------------------------------- +Preparazione dell'ambiente e del repository +------------------------------------------- -1) Su GitHub, creare un [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] del [repository del pacchetto |www:packages] che si intende modificare -2) [Clonare |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] questo repository sul proprio computer -3) Installare le dipendenze, compreso [Nette Tester |tester:], usando il comando `composer install`. -4) Verificate che i test funzionino eseguendo `composer tester` -5) Creare un [nuovo ramo |#New Branch] basato sull'ultima versione rilasciata +1) su GitHub, create un [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] del repository del [pacchetto |www:packages] che intendete modificare +2) [clonate |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] questo repository sul vostro computer +3) installate le dipendenze, incluso [Nette Tester |tester:], tramite il comando `composer install` +4) verificate che i test funzionino eseguendo `composer tester` +5) create un [#nuovo ramo] basato sull'ultima versione rilasciata -Implementare le proprie modifiche .[#toc-implementing-your-own-changes] ------------------------------------------------------------------------ +Implementazione delle proprie modifiche +--------------------------------------- -Ora è possibile apportare le proprie modifiche al codice: +Ora potete apportare le vostre modifiche al codice: -1) Implementare le modifiche desiderate e non dimenticare i test. -2) Assicurarsi che i test vengano eseguiti con successo `composer tester` -3) Verificare se il codice è conforme agli standard di [codifica |#coding standards] -4) Salvare (commit) le modifiche con una descrizione in [questo formato |#Commit Description] +1) programmate le modifiche richieste e non dimenticate i test +2) assicuratevi che i test vengano eseguiti con successo tramite `composer tester` +3) verificate che il codice soddisfi lo [#standard di codifica] +4) salvate (committate) le modifiche con una descrizione in [questo formato |#Descrizione del commit] -È possibile creare più commit, uno per ogni fase logica. Ogni commit deve essere significativo di per sé. +Potete creare più commit, uno per ogni passaggio logico. Ogni commit dovrebbe avere senso da solo. -Invio delle modifiche .[#toc-submitting-changes] ------------------------------------------------- +Invio delle modifiche +--------------------- -Una volta soddisfatti delle modifiche, è possibile inviarle: +Una volta soddisfatti delle modifiche, potete inviarle: -1) Spingere le modifiche su GitHub al proprio fork -2) Da lì, inviarle al repository di Nette creando una [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) -3) Fornire [informazioni sufficienti |#pull request description] nella descrizione +1) inviate (push) le modifiche su GitHub al vostro fork +2) da lì, inviatele al repository Nette creando una [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) +3) fornite nella descrizione [informazioni sufficienti |#Descrizione della pull request] -Incorporare il feedback .[#toc-incorporating-feedback] ------------------------------------------------------- +Integrazione dei commenti +------------------------- -I vostri commit sono ora visibili agli altri. È frequente ricevere commenti con suggerimenti: +I vostri commit ora saranno visibili anche agli altri. È comune ricevere commenti con suggerimenti: -1) Tenere traccia delle modifiche proposte -2) incorporarle come nuovi commit o [unirle a quelle precedenti |https://help.github.com/en/github/using-git/about-git-rebase] -3) Ripresentare i commit a GitHub, che appariranno automaticamente nella richiesta di pull. +1) seguite le modifiche proposte +2) integrateli come nuovi commit o [uniteli ai precedenti |https://help.github.com/en/github/using-git/about-git-rebase] +3) inviate nuovamente i commit su GitHub e appariranno automaticamente nella pull request -Non creare mai una nuova richiesta di pull per modificarne una esistente. +Non create mai una nuova pull request per modificare una esistente. -Documentazione .[#toc-documentation] ------------------------------------- +Documentazione +-------------- -Se avete modificato una funzionalità o ne avete aggiunta una nuova, non dimenticate di [aggiungerla |documentation] anche [alla documentazione |documentation]. +Se avete modificato la funzionalità o ne avete aggiunta una nuova, non dimenticate di [aggiungerla anche alla documentazione |documentation]. -Nuovo ramo .[#toc-new-branch] -============================= +Nuovo ramo +========== -Se possibile, apportare le modifiche all'ultima versione rilasciata, cioè all'ultimo tag del ramo. Per il tag v3.2.1, creare un ramo usando questo comando: +Se possibile, apportate le modifiche rispetto all'ultima versione rilasciata, ovvero l'ultimo tag nel ramo corrispondente. Per il tag `v3.2.1`, create un ramo con questo comando: ```shell git checkout -b new_branch_name v3.2.1 ``` -Standard di codifica .[#toc-coding-standards] -============================================= +Standard di codifica +==================== -Il codice deve essere conforme agli [standard di codifica |coding standard] utilizzati da Nette Framework. È disponibile uno strumento automatico per la verifica e la correzione del codice. È possibile installarlo **globalmente** tramite Composer in una cartella a scelta: +Il vostro codice deve soddisfare lo [standard di codifica |coding standard] utilizzato in Nette Framework. Per controllare e correggere il codice è disponibile uno strumento automatico. Può essere installato tramite Composer **globalmente** nella cartella da voi scelta: ```shell composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Ora dovreste essere in grado di eseguire lo strumento nel terminale. Il primo comando controlla e il secondo corregge il codice nelle cartelle `src` e `tests` nella directory corrente: +Ora dovreste essere in grado di eseguire lo strumento nel terminale. Con il primo comando controllerete e con il secondo correggerete anche il codice nelle cartelle `src` e `tests` nella directory corrente: ```shell /path/to/nette-coding-standard/ecs check @@ -89,29 +89,29 @@ Ora dovreste essere in grado di eseguire lo strumento nel terminale. Il primo co ``` -Descrizione dell'impegno .[#toc-commit-description] -=================================================== +Descrizione del commit +====================== -In Nette, gli argomenti dei commit hanno il seguente formato: `Presenter: fixed AJAX detection [Closes #69]` +In Nette, gli oggetti dei commit hanno il formato: `Presenter: fixed AJAX detection [Closes #69]` - area seguita da due punti -- scopo del commit al passato; se possibile, iniziare con parole come: added, fixed, refactored, changed, removed -- se il commit rompe la compatibilità all'indietro, aggiungere "BC break". -- qualsiasi collegamento al tracker dei problemi, come `(#123)` o `[Closes #69]` -- dopo l'oggetto, può esserci una riga vuota seguita da una descrizione più dettagliata, che includa, per esempio, collegamenti al forum +- scopo del commit al passato, se possibile, iniziate con la parola: `added` (nuova funzionalità aggiunta), `fixed` (correzione), `refactored` (modifica del codice senza modifica del comportamento), `changed`, `removed` +- se il commit interrompe la compatibilità all'indietro, aggiungete "BC break" +- eventuale collegamento all'issue tracker come `(#123)` o `[Closes #69]` +- dopo l'oggetto può seguire una riga vuota e poi una descrizione più dettagliata, inclusi ad esempio link al forum -Descrizione della richiesta .[#toc-pull-request-description] -============================================================ +Descrizione della pull request +============================== -Quando si crea una richiesta di pull, l'interfaccia di GitHub consente di inserire un titolo e una descrizione. Fornire un titolo conciso e includere nella descrizione il maggior numero possibile di informazioni sulle ragioni della modifica. +Durante la creazione di una pull request, l'interfaccia di GitHub vi consentirà di inserire un titolo e una descrizione. Fornite un titolo conciso e nella descrizione fornite quante più informazioni possibili sui motivi della vostra modifica. -Inoltre, specificare nell'intestazione se si tratta di una nuova funzionalità o di una correzione di un bug e se può causare problemi di retrocompatibilità (BC break). Se esiste un problema correlato, collegarlo ad esso in modo che venga chiuso dopo l'approvazione della richiesta di pull. +Verrà visualizzata anche un'intestazione in cui specificare se si tratta di una nuova funzionalità o di una correzione di bug e se può verificarsi un'interruzione della compatibilità all'indietro (BC break). Se è disponibile un problema correlato (issue), fatevi riferimento in modo che venga chiuso dopo l'approvazione della pull request. ``` -- bug fix / new feature? <!-- #issue numbers, if any --> -- BC break? yes/no -- doc PR: nette/docs#? <!-- highly welcome, see https://nette.org/en/writing --> +- correzione bug / nuova funzionalità? <!-- #numeri issue, se presenti --> +- BC break? sì/no +- doc PR: nette/docs#? <!-- molto gradito, vedi https://nette.org/en/writing --> ``` diff --git a/contributing/it/coding-standard.texy b/contributing/it/coding-standard.texy index 974b96db96..ad904f5bb0 100644 --- a/contributing/it/coding-standard.texy +++ b/contributing/it/coding-standard.texy @@ -1,44 +1,44 @@ Standard di codifica ******************** -Questo documento descrive le regole e le raccomandazioni per lo sviluppo di Nette. Quando si contribuisce al codice di Nette, è necessario seguirle. Il modo più semplice per farlo è imitare il codice esistente. -L'idea è quella di far sembrare che tutto il codice sia stato scritto da una sola persona. .[perex] +.[perex] +Questo documento descrive le regole e le raccomandazioni per lo sviluppo di Nette. Quando contribuite con codice a Nette, dovete seguirle. Il modo più semplice per farlo è imitare il codice esistente. L'obiettivo è che tutto il codice sembri scritto da una sola persona. -Lo standard di codifica di Nette corrisponde allo [stile di codifica esteso PSR-12 |https://www.php-fig.org/psr/psr-12/], con due eccezioni principali: utilizza le [tabulazioni invece degli spazi |#tabs instead of spaces] per l'indentazione e usa [PascalCase per le costanti di classe |https://blog.nette.org/it/per-non-urlare-nel-codice]. +Lo Standard di Codifica Nette corrisponde a [PSR-12 Extended Coding Style |https://www.php-fig.org/psr/psr-12/] con due eccezioni principali: utilizza [#tabulazioni invece di spazi] per l'indentazione e [PascalCase per le costanti di classe|https://blog.nette.org/it/for-less-screaming-in-the-code]. -Regole generali .[#toc-general-rules] -===================================== +Regole generali +=============== - Ogni file PHP deve contenere `declare(strict_types=1)` -- Due righe vuote sono usate per separare i metodi per una migliore leggibilità. -- La ragione dell'uso dell'operatore shut-up deve essere documentata: `@mkdir($dir); // @ - directory may exist` -- Se viene utilizzato un operatore di confronto a tipizzazione debole (ad esempio `==`, `!=`, ...), l'intenzione deve essere documentata: `// == to accept null` -- È possibile scrivere più eccezioni in un file `exceptions.php` -- La visibilità dei metodi non è specificata per le interfacce, perché sono sempre pubblici. -- Ogni proprietà, valore di ritorno e parametro deve avere un tipo specificato. Per le costanti finali, invece, non si specifica mai il tipo perché è ovvio. -- Gli apici singoli devono essere usati per delimitare la stringa, tranne quando il letterale stesso contiene apostrofi. +- Due righe vuote vengono utilizzate per separare i metodi per una migliore leggibilità. +- Il motivo dell'uso dell'operatore shut-up deve essere documentato: `@mkdir($dir); // @ - la directory potrebbe esistere`. +- Se viene utilizzato un operatore di confronto debolmente tipizzato (cioè `==`, `!=`, ...), l'intenzione deve essere documentata: `// == accetta null` +- In un unico file `exceptions.php` è possibile scrivere più eccezioni. +- Per le interfacce non viene specificata la visibilità dei metodi, poiché sono sempre pubblici. +- Ogni proprietà, valore di ritorno e parametro deve avere un tipo specificato. Al contrario, per le costanti finali non specifichiamo mai il tipo, poiché è ovvio. +- Per delimitare una stringa dovrebbero essere usate le virgolette singole, ad eccezione dei casi in cui il letterale stesso contiene apostrofi. -Convenzioni di denominazione .[#toc-naming-conventions] -======================================================= +Convenzioni di denominazione +============================ -- Evitare l'uso di abbreviazioni, a meno che il nome completo non sia eccessivo. -- Usare il maiuscolo per le abbreviazioni di due lettere e il pascal/camel case per le abbreviazioni più lunghe. -- Usare un sostantivo o una frase sostantiva per il nome della classe. -- I nomi delle classi devono contenere non solo la specificità (`Array`) ma anche la generalità (`ArrayIterator`). Fanno eccezione gli attributi PHP. -- "Le costanti di classe e gli enum dovrebbero usare il PascalCaps":https://blog.nette.org/it/per-non-urlare-nel-codice. -- "Le interfacce e le classi astratte non devono contenere prefissi o postfissi":https://blog.nette.org/it/i-prefissi-e-i-suffissi-non-appartengono-ai-nomi-delle-interfacce come `Abstract`, `Interface` o `I`. +- Non utilizzate abbreviazioni, a meno che il nome completo non sia troppo lungo. +- Per le abbreviazioni di due lettere utilizzate lettere maiuscole, per le abbreviazioni più lunghe pascal/camel case. +- Per il nome di una classe utilizzate un sostantivo o una frase nominale. +- I nomi delle classi devono contenere non solo la specificità (`Array`), ma anche la generalità (`ArrayIterator`). Fanno eccezione gli attributi del linguaggio PHP. +- "Le costanti di classe e gli enum dovrebbero usare PascalCaps":https://blog.nette.org/it/for-less-screaming-in-the-code. +- "Le interfacce e le classi astratte non dovrebbero contenere prefissi o suffissi":https://blog.nette.org/it/prefixes-and-suffixes-do-not-belong-in-interface-names come `Abstract`, `Interface` o `I`. -Avvolgimenti e parentesi graffe .[#toc-wrapping-and-braces] -=========================================================== +A capo e parentesi graffe +========================= -Lo standard di codifica Nette corrisponde al PSR-12 (o stile di codifica PER), in alcuni punti lo specifica maggiormente o lo modifica: +Lo Standard di Codifica Nette corrisponde a PSR-12 (risp. PER Coding Style), in alcuni punti lo completa o lo modifica: -- le funzioni freccia sono scritte senza uno spazio prima delle parentesi, cioè `fn($a) => $b` -- non è richiesta una riga vuota tra i diversi tipi di dichiarazioni di importazione `use` -- il tipo di ritorno della funzione/metodo e la parentesi di apertura dovrebbero essere posti su righe separate per una migliore leggibilità: +- le arrow function si scrivono senza spazio prima della parentesi, cioè `fn($a) => $b` +- non è richiesta una riga vuota tra diversi tipi di `use` import statements +- il tipo di ritorno della funzione/metodo e la parentesi graffa di apertura sono sempre su righe separate: ```php public function find( @@ -50,24 +50,28 @@ Lo standard di codifica Nette corrisponde al PSR-12 (o stile di codifica PER), i } ``` +La parentesi graffa di apertura su una riga separata è importante per la separazione visiva della firma della funzione/metodo dal corpo. Se la firma è su una riga, la separazione è chiara (immagine a sinistra), se è su più righe, in PSR la firma e il corpo si fondono (al centro), mentre nello standard Nette rimangono separati (a destra): -Blocchi di documentazione (phpDoc) .[#toc-documentation-blocks-phpdoc] -====================================================================== +[* new-line-after.webp *] -La regola principale: non duplicare mai le informazioni della firma, come il tipo di parametro o il tipo di ritorno, senza alcun valore aggiunto. -Blocco di documentazione per la definizione della classe: +Blocchi di documentazione (phpDoc) +================================== -- Inizia con una descrizione della classe. +Regola principale: Non duplicare mai alcuna informazione nella firma, come il tipo di parametro o il tipo di ritorno, senza un valore aggiunto. + +Blocco di documentazione per la definizione di una classe: + +- Inizia con la descrizione della classe. - Segue una riga vuota. -- Seguono le annotazioni `@property` (o `@property-read`, `@property-write`), una per riga. La sintassi è: annotazione, spazio, tipo, spazio, $nome. -- Seguono le annotazioni di `@method`, una per riga. La sintassi è: annotazione, spazio, tipo di ritorno, spazio, nome(tipo $param, ...). -- L'annotazione `@author` è omessa. La paternità è conservata nella cronologia del codice sorgente. -- È possibile utilizzare le annotazioni `@internal` o `@deprecated`. +- Seguono le annotazioni `@property` (o `@property-read`, `@property-write`), una dopo l'altra. La sintassi è: annotazione, spazio, tipo, spazio, $nome. +- Seguono le annotazioni `@method`, una dopo l'altra. La sintassi è: annotazione, spazio, tipo di ritorno, spazio, nome(tipo $param, ...). +- L'annotazione `@author` viene omessa. L'autorialità viene conservata nella cronologia del codice sorgente. +- Possono essere utilizzate le annotazioni `@internal` o `@deprecated`. ```php /** - * MIME message part. + * Parte del messaggio MIME. * * @property string $encoding * @property-read array $headers @@ -76,27 +80,27 @@ Blocco di documentazione per la definizione della classe: */ ``` -Il blocco di documentazione per la proprietà che contiene solo l'annotazione `@var` deve essere a riga singola: +Un blocco di documentazione per una proprietà, che contiene solo l'annotazione `@var`, dovrebbe essere su una sola riga: ```php /** @var string[] */ private array $name; ``` -Blocco di documentazione per la definizione del metodo: +Blocco di documentazione per la definizione di un metodo: - Inizia con una breve descrizione del metodo. - Nessuna riga vuota. -- Le annotazioni di `@param`, una per riga. -- L'annotazione `@return`. -- Le annotazioni di `@throws`, una per riga. -- È possibile utilizzare le annotazioni `@internal` o `@deprecated`. +- Annotazioni `@param` su righe separate. +- Annotazione `@return`. +- Annotazioni `@throws`, una dopo l'altra. +- Possono essere utilizzate le annotazioni `@internal` o `@deprecated`. -Ogni annotazione è seguita da uno spazio, tranne quella di `@param` che è seguita da due spazi per una migliore leggibilità. +Dopo ogni annotazione segue uno spazio, ad eccezione di `@param`, dopo la quale seguono due spazi per una migliore leggibilità. ```php /** - * Finds a file in directory. + * Trova un file nella directory. * @param string[] $options * @return string[] * @throws DirectoryNotFoundException @@ -105,21 +109,20 @@ public function find(string $dir, array $options): array ``` -Tabulazioni al posto degli spazi .[#toc-tabs-instead-of-spaces] -=============================================================== +Tabulazioni invece di spazi +=========================== -Le tabulazioni presentano diversi vantaggi rispetto agli spazi: +Le tabulazioni hanno diversi vantaggi rispetto agli spazi: -- la dimensione dell'indentazione è personalizzabile negli editor e nel web:https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size -- non impongono al codice le preferenze di indentazione dell'utente, quindi il codice è più portabile -- si possono digitare con un solo tasto (ovunque, non solo negli editor che trasformano le tabulazioni in spazi) +- la dimensione dell'indentazione può essere personalizzata negli editor e sul "web":https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size +- non impongono al codice la preferenza dell'utente sulla dimensione dell'indentazione, quindi il codice è più portabile +- possono essere scritte con un solo tasto (ovunque, non solo negli editor che trasformano le tabulazioni in spazi) - l'indentazione è il loro scopo -- rispettare le esigenze dei colleghi ipovedenti e non vedenti +- rispettano le esigenze dei colleghi ipovedenti e non vedenti -Utilizzando le tabulazioni nei nostri progetti, consentiamo la personalizzazione della larghezza, che può sembrare superflua per la maggior parte delle persone, ma che è essenziale per le persone con problemi di vista. +Utilizzando le tabulazioni nei nostri progetti, consentiamo la personalizzazione della larghezza, che può sembrare superflua alla maggior parte delle persone, ma è essenziale per le persone con disabilità visive. -Per i programmatori non vedenti che utilizzano display braille, ogni spazio è rappresentato da una cella braille e occupa spazio prezioso. Quindi, se l'indentazione predefinita è di 4 spazi, un'indentazione di terzo livello spreca 12 celle braille prima dell'inizio del codice. -Su un display a 40 celle, che è quello più comunemente usato sui computer portatili, si tratta di più di un quarto delle celle disponibili sprecate senza alcuna informazione. +Per i programmatori non vedenti che utilizzano display Braille, ogni spazio rappresenta una cella Braille. Quindi, se l'indentazione predefinita è di 4 spazi, l'indentazione di 3° livello spreca 12 preziose celle Braille prima ancora che inizi il codice. Su un display a 40 celle, che è il più comune per i notebook, questo rappresenta più di un quarto delle celle disponibili sprecate senza alcuna informazione. {{priority: -1}} diff --git a/contributing/it/documentation.texy b/contributing/it/documentation.texy index d1ef26f5d3..93d0597b18 100644 --- a/contributing/it/documentation.texy +++ b/contributing/it/documentation.texy @@ -1,69 +1,68 @@ -Contribuire alla documentazione -******************************* +Come contribuire alla documentazione +************************************ .[perex] -Contribuire alla documentazione è una delle attività più preziose, perché aiuta gli altri a comprendere il framework. +Contribuire alla documentazione è una delle attività più gratificanti, poiché aiutate gli altri a comprendere il framework. -Come scrivere? .[#toc-how-to-write] ------------------------------------ +Come scrivere? +-------------- -La documentazione è destinata principalmente a persone che non conoscono l'argomento. Pertanto, deve soddisfare diversi punti importanti: +La documentazione è destinata principalmente alle persone che si avvicinano all'argomento. Pertanto, dovrebbe soddisfare diversi punti importanti: -- Iniziare con argomenti semplici e generali. Passare ad argomenti più avanzati alla fine -- Cercare di spiegare l'argomento nel modo più chiaro possibile. Ad esempio, provate prima a spiegare l'argomento a un collega. -- Fornite solo le informazioni che l'utente ha effettivamente bisogno di conoscere per un determinato argomento. -- Assicuratevi che le informazioni siano accurate. Testate ogni codice -- Siate concisi: tagliate a metà ciò che scrivete. E poi sentitevi liberi di rifarlo -- Usate con parsimonia l'evidenziazione, dai caratteri in grassetto ai riquadri, come ad esempio `.[note]` -- Seguire lo [standard di codifica |Coding Standard] nel codice +- Iniziate dal semplice e generale. Passate ad argomenti più avanzati solo alla fine. +- Cercate di spiegare la cosa nel miglior modo possibile. Provate, ad esempio, a spiegare prima l'argomento a un collega. +- Fornite solo le informazioni di cui l'utente ha effettivamente bisogno per l'argomento specifico. +- Verificate che le vostre informazioni siano effettivamente vere. Testate ogni codice. +- Siate concisi - dimezzate ciò che scrivete. E poi, se necessario, ancora una volta. +- Risparmiate sugli evidenziatori di ogni tipo, dal grassetto alle cornici come `.[note]`. +- Nel codice, rispettate lo [Standard di codifica |Coding Standard]. -Imparate anche la [sintassi |syntax]. Per avere un'anteprima dell'articolo durante la scrittura, si può usare l'[editor di anteprima |https://editor.nette.org/]. +Imparate anche la [sintassi |syntax]. Per visualizzare l'anteprima dell'articolo durante la scrittura, potete utilizzare l'[editor con anteprima |https://editor.nette.org/]. -Mutazioni linguistiche .[#toc-language-mutations] -------------------------------------------------- +Versioni linguistiche +--------------------- -L'inglese è la lingua principale, quindi le modifiche devono essere in inglese. Se l'inglese non è il vostro forte, usate [DeepL Translator |https://www.deepl.com/translator] e altri controlleranno il vostro testo. +La lingua principale è l'inglese, quindi le vostre modifiche dovrebbero essere sia in ceco che in inglese. Se l'inglese non è il vostro forte, utilizzate [DeepL Translator |https://www.deepl.com/translator] e gli altri controlleranno il testo per voi. -La traduzione in altre lingue avverrà automaticamente dopo l'approvazione e la messa a punto delle modifiche. +La traduzione nelle altre lingue verrà eseguita automaticamente dopo l'approvazione e la messa a punto della vostra modifica. -Modifiche banali .[#toc-trivial-edits] --------------------------------------- +Modifiche triviali +------------------ -Per contribuire alla documentazione, è necessario avere un account su [GitHub |https://github.com]. +Per contribuire alla documentazione è indispensabile avere un account su [GitHub|https://github.com]. -Il modo più semplice per apportare piccole modifiche alla documentazione è utilizzare i link alla fine di ogni pagina: +Il modo più semplice per apportare una piccola modifica alla documentazione è utilizzare i link alla fine di ogni pagina: -- *Mostra su GitHub* apre la versione sorgente della pagina su GitHub. Poi basta premere il pulsante `E` e si può iniziare a modificare (è necessario aver effettuato l'accesso a GitHub). -- *Apri anteprima* apre un editor in cui è possibile vedere immediatamente la forma visiva finale +- *Mostra su GitHub* apre la versione sorgente della pagina data su GitHub. Successivamente, è sufficiente premere il pulsante `E` e potete iniziare a modificare (è necessario essere loggati su GitHub). +- *Apri anteprima* apre l'editor, dove vedete subito anche l'aspetto visivo risultante. -Poiché l'[editor di anteprima |https://editor.nette.org/] non ha la possibilità di salvare le modifiche direttamente su GitHub, è necessario copiare il testo sorgente negli appunti (usando il pulsante *Copia negli appunti*) e poi incollarlo nell'editor su GitHub. -Sotto il campo di modifica c'è un modulo per l'invio. Qui, non dimenticate di riassumere brevemente e spiegare il motivo della vostra modifica. Dopo l'invio, viene creata una cosiddetta richiesta di pull (PR), che può essere ulteriormente modificata. +Poiché l'[editor con anteprima |https://editor.nette.org/] non ha la possibilità di salvare le modifiche direttamente su GitHub, è necessario, dopo aver completato le modifiche, copiare il testo sorgente negli appunti (con il pulsante *Copy to clipboard*) e quindi incollarlo nell'editor su GitHub. Sotto il campo di modifica c'è un modulo per l'invio. Qui non dimenticate di riassumere brevemente e spiegare il motivo della vostra modifica. Dopo l'invio, verrà creata una cosiddetta pull request (PR), che potrà essere ulteriormente modificata. -Modifiche più ampie .[#toc-larger-edits] ----------------------------------------- +Modifiche più grandi +-------------------- -È più opportuno conoscere le basi del lavoro con il sistema di controllo di versione Git, piuttosto che affidarsi esclusivamente all'interfaccia di GitHub. Se non si ha familiarità con Git, si può fare riferimento alla [guida git - the simple |https://rogerdudler.github.io/git-guide/] e prendere in considerazione l'utilizzo di uno dei tanti [client grafici |https://git-scm.com/downloads/guis] disponibili. +Più appropriato che utilizzare l'interfaccia di GitHub, è essere familiari con le basi del lavoro con il sistema di versionamento Git. Se non conoscete il lavoro con Git, potete consultare la guida [git - the simple guide |https://rogerdudler.github.io/git-guide/] ed eventualmente utilizzare uno dei tanti [client grafici |https://git-scm.com/downloads/guis]. -Modificare la documentazione nel modo seguente: +Modificate la documentazione in questo modo: -1) su GitHub, creare un [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] del repository [nette/docs |https://github.com/nette/docs] -2) [clonare |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] questo repository sul proprio computer -3) quindi, apportare le modifiche nel [ramo appropriato |#Documentation Structure] -4) verificare la presenza di spazi extra nel testo utilizzando lo strumento [Code-Checker |code-checker:] -5) salvare (commit) le modifiche -6) se si è soddisfatti delle modifiche, inviarle su GitHub al proprio fork -7) da lì, inviarle al repository `nette/docs` creando una [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) +1) su GitHub, create un [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] del repository [nette/docs |https://github.com/nette/docs] +2) [clonate |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] questo repository sul vostro computer +3) successivamente, nel [ramo appropriato |#Struttura della documentazione], apportate le modifiche +4) controllate gli spazi superflui nel testo tramite lo strumento [Code-Checker |code-checker:] +4) salvate (committate) le modifiche +6) se siete soddisfatti delle modifiche, inviatele (push) su GitHub al vostro fork +7) da lì, inviatele al repository `nette/docs` creando una [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) -È frequente ricevere commenti con suggerimenti. Tenere traccia delle modifiche proposte e incorporarle. Aggiungere le modifiche suggerite come nuovi commit e inviarle nuovamente a GitHub. Non creare mai una nuova richiesta di pull solo per modificare una richiesta esistente. +È comune ricevere commenti con suggerimenti. Seguite le modifiche proposte e integrateli. Aggiungete le modifiche proposte come nuovi commit e inviatele nuovamente su GitHub. Non create mai una nuova pull request per modificare una pull request esistente. -Struttura della documentazione .[#toc-documentation-structure] --------------------------------------------------------------- +Struttura della documentazione +------------------------------ -L'intera documentazione si trova su GitHub nel repository [nette/docs |https://github.com/nette/docs]. La versione attuale si trova nel ramo master, mentre le versioni precedenti si trovano nei rami `doc-3.x`, `doc-2.x`. +L'intera documentazione si trova su GitHub nel repository [nette/docs |https://github.com/nette/docs]. La versione attuale è nel master, le versioni precedenti si trovano in rami come `doc-3.x`, `doc-2.x`. -Il contenuto di ogni ramo è suddiviso in cartelle principali che rappresentano le singole aree della documentazione. Per esempio, `application/` corrisponde a https://doc.nette.org/en/application, `latte/` corrisponde a https://latte.nette.org, ecc. Ognuna di queste cartelle contiene sottocartelle che rappresentano le mutazioni linguistiche (`cs`, `en`, ...) e, opzionalmente, una sottocartella `files` con immagini che possono essere inserite nelle pagine della documentazione. +Il contenuto di ogni ramo è diviso in cartelle principali che rappresentano le singole aree della documentazione. Ad esempio, `application/` corrisponde a https://doc.nette.org/cs/application, `latte/` corrisponde a https://latte.nette.org ecc. Ognuna di queste cartelle contiene sottocartelle che rappresentano le versioni linguistiche (`cs`, `en`, ...) ed eventualmente una sottocartella `files` con immagini che possono essere inserite nelle pagine della documentazione. diff --git a/contributing/it/syntax.texy b/contributing/it/syntax.texy index a6bdcb5a28..c60a00fb10 100644 --- a/contributing/it/syntax.texy +++ b/contributing/it/syntax.texy @@ -1,59 +1,59 @@ -Sintassi Wiki -************* +Sintassi della documentazione +***************************** -Wiki utilizza la [sintassi |https://texy.info/en/syntax] Markdown e [Texy |https://texy.info/en/syntax] con diversi miglioramenti. +La documentazione utilizza Markdown e la [sintassi Texy |https://texy.nette.org/syntax] con alcune estensioni. -Collegamenti .[#toc-links] -========================== +Link +==== -Per i riferimenti interni, si utilizza la notazione tra parentesi quadre `[link]` è utilizzata. Si tratta della forma con barra verticale `[link text |link target]`oppure nella forma abbreviata `[link text]` se il target è lo stesso del testo (dopo la trasformazione in minuscolo e i trattini): +Per i link interni si utilizza la notazione tra parentesi quadre `[link]`. E questo o nella forma con la barra verticale `[testo del link |destinazione del link]`, o abbreviata `[testo del link]`, se la destinazione è identica al testo (dopo la trasformazione in minuscolo e trattini): -- `[Page name]` -> `<a href="/en/page-name">Page name</a>` -- `[link text |Page name]` -> `<a href="/en/page-name">link text</a>` +- `[Page name |Page name]` -> `<a href="/it/page-name">Page name</a>` +- `[testo del link |Page name]` -> `<a href="/it/page-name">testo del link</a>` -Possiamo collegarci a un'altra lingua o a un'altra sezione. Una sezione è una libreria Nette (ad esempio `forms`, `latte`, ecc.) o sezioni speciali come `best-practices`, `quickstart`, ecc: +Possiamo creare link a un'altra versione linguistica o a un'altra sezione. Per sezione si intende una libreria Nette (ad es. `forms`, `latte`, ecc.) o sezioni speciali come `best-practices`, `quickstart` ecc.: -- `[cs:Page name]` -> `<a href="/en/page-name">Page name</a>` (stessa sezione, lingua diversa) -- `[tracy:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (sezione diversa, stessa lingua) -- `[tracy:cs:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (sezione e lingua diversa) +- `[cs:Page name |cs:Page name]` -> `<a href="/cs/page-name">Page name</a>` (stessa sezione, lingua diversa) +- `[tracy:Page name |tracy:Page name]` -> `<a href="//tracy.nette.org/it/page-name">Page name</a>` (sezione diversa, stessa lingua) +- `[tracy:cs:Page name |tracy:cs:Page name]` -> `<a href="//tracy.nette.org/cs/page-name">Page name</a>` (sezione e lingua diverse) -È anche possibile puntare a un'intestazione specifica della pagina con `#`. +Tramite `#` è anche possibile puntare a un titolo specifico sulla pagina. -- `[#Heading]` -> `<a href="#toc-heading">Heading</a>` (titolo della pagina corrente) -- `[Page name#Heading]` -> `<a href="/en/page-name#toc-heading">Page name</a>` +- `[Titolo |#Heading]` -> `<a href="#toc-heading">Titolo</a>` (titolo sulla pagina corrente) +- `[Page name#Heading |Page name#Heading]` -> `<a href="/it/page-name#toc-heading">Page name</a>` -Collegamento alla pagina iniziale della sezione: (`@home` è un termine speciale per indicare la home page della sezione) +Link alla pagina iniziale della sezione: (`@home` è un'espressione speciale per la home page della sezione) -- `[link text |@home]` -> `<a href="/en/">link text</a>` -- `[link text |tracy:]` -> `<a href="//tracy.nette.org/en/">link text</a>` +- `[testo del link |@home]` -> `<a href="/it/">testo del link</a>` +- `[testo del link |tracy:]` -> `<a href="//tracy.nette.org/it/">testo del link</a>` -Link alla documentazione API .[#toc-links-to-api-documentation] ---------------------------------------------------------------- +Link alla documentazione API +---------------------------- -Utilizzare sempre le seguenti notazioni: +Indicare sempre solo utilizzando questa notazione: - `[api:Nette\SmartObject]` -> [api:Nette\SmartObject] - `[api:Nette\Forms\Form::setTranslator()]` -> [api:Nette\Forms\Form::setTranslator()] - `[api:Nette\Forms\Form::$onSubmit]` -> [api:Nette\Forms\Form::$onSubmit] - `[api:Nette\Forms\Form::Required]` -> [api:Nette\Forms\Form::Required] -I nomi completamente qualificati si usano solo nella prima menzione. Per gli altri collegamenti, utilizzare un nome semplificato: +Utilizzate i nomi completamente qualificati solo alla prima menzione. Per i link successivi, utilizzate il nome semplificato: - `[Form::setTranslator() |api:Nette\Forms\Form::setTranslator()]` -> [Form::setTranslator() |api:Nette\Forms\Form::setTranslator()] -Collegamenti alla documentazione PHP .[#toc-links-to-php-documentation] ------------------------------------------------------------------------ +Link alla documentazione PHP +---------------------------- - `[php:substr]` -> [php:substr] -Codice sorgente .[#toc-source-code] -=================================== +Codice sorgente +=============== -Il blocco di codice inizia con <code>```lang</code> e termina con <code>```</code> I linguaggi supportati sono `php`, `latte`, `neon`, `html`, `css`, `js` e `sql`. Usare sempre le tabulazioni per l'indentazione. +Un blocco di codice inizia con <code>```lang</code> e termina con <code>```</code>. Le lingue supportate sono `php`, `latte`, `neon`, `html`, `css`, `js` e `sql`. Per l'indentazione utilizzate sempre le tabulazioni. ``` ```php @@ -63,7 +63,7 @@ Il blocco di codice inizia con <code>```lang</code> e termina con <c ``` ``` -Si può anche specificare il nome del file come <code>```php .{file: ArrayTest.php}</code> e il blocco di codice sarà reso in questo modo: +Potete anche specificare il nome del file come <code>```php .{file: ArrayTest.php}</code> e il blocco di codice verrà renderizzato in questo modo: ```php .{file: ArrayTest.php} public function renderPage($id) @@ -72,71 +72,71 @@ public function renderPage($id) ``` -Titoli .[#toc-headings] -======================= +Titoli +====== -Titolo superiore (nome della pagina) sottolineato con stelle (`*`). For normal headings use equal signs (`=`) and then hyphens (`-`). +Il titolo più alto (cioè il nome della pagina) sottolineatelo con asterischi. Per separare le sezioni utilizzate gli uguali. Sottolineate i titoli con gli uguali e poi con i trattini: ``` -MVC Applications & Presenters -***************************** +Applicazioni MVC e presenter +**************************** ... -Link Creation -============= +Creazione di link +================= ... -Links in Templates ------------------- +Link nei template +----------------- ... ``` -Caselle e stili .[#toc-boxes-and-styles] -======================================== +Cornici e stili +=============== -Paragrafo di testa contrassegnato dalla classe `.[perex]` .[perex] +Il perex lo contrassegniamo con la classe `.[perex]` .[perex] -Note contrassegnate con la classe `.[note]` .[note] +Una nota la contrassegniamo con la classe `.[note]` .[note] -Suggerimento contrassegnato con la classe `.[tip]` .[tip] +Un suggerimento lo contrassegniamo con la classe `.[tip]` .[tip] -Avviso contrassegnato con la classe `.[caution]` .[caution] +Un avvertimento lo contrassegniamo con la classe `.[caution]` .[caution] -Avviso forte contrassegnato con la classe `.[warning]` .[warning] +Un avvertimento più forte lo contrassegniamo con la classe `.[warning]` .[warning] Numero di versione `.{data-version:2.4.10}` .{data-version:2.4.10} -Le classi devono essere scritte prima della relativa riga: +Scrivete le classi prima della riga: ``` -.[note] -This is a note. +.[perex] +Questo è il perex. ``` -Si noti che i riquadri come `.[tip]` attirano l'attenzione e quindi dovrebbero essere usati per enfatizzare, non per informazioni meno importanti. +Si prega di notare che le cornici come `.[tip]` "attirano" gli occhi, quindi vengono utilizzate per enfatizzare, non per informazioni meno importanti. Pertanto, utilizzateli con la massima parsimonia. -Indice dei contenuti .[#toc-table-of-contents] -============================================== +Contenuto +========= -L'indice (link nella barra laterale) viene generato automaticamente quando la pagina è più lunga di 4 000 byte. Questo comportamento predefinito può essere modificato con un tag `{{toc}}`[tag meta |#meta-tags]. Il testo per il TOC è preso per impostazione predefinita dall'intestazione, ma è possibile usare un testo diverso con un `.{toc}` modificatore. Questo è particolarmente utile per le intestazioni più lunghe. +Il contenuto (link nel menu a destra) viene generato automaticamente per tutte le pagine la cui dimensione supera i 4.000 byte, tuttavia questo comportamento predefinito può essere modificato tramite il [#meta tag] `{{toc}}`. Il testo che forma il contenuto viene preso standard direttamente dal testo dei titoli, ma tramite il modificatore `.{toc}` è possibile visualizzare nel contenuto un testo diverso, il che è utile soprattutto per i titoli più lunghi. ``` -Long and Intelligent Heading .{toc: A Different Text for TOC} -============================================================= +Titolo lungo e intelligente .{toc: Qualsiasi altro testo visualizzato nel contenuto} +==================================================================================== ``` -Tag Meta .[#toc-meta-tags] -========================== +Meta tag +======== -- impostare il titolo della propria pagina (in `<title>` e briciole di pane) `{{title: Another name}}` -- reindirizzamento `{{redirect: pla:cs}}` - vedere i [link |#links] -- imporre `{{toc}}` o disabilitare `{{toc: no}}` tabella dei contenuti +- impostazione di un nome di pagina personalizzato (in `<title>` e nella navigazione breadcrumb) `{{title: Altro nome}}` +- reindirizzamento `{{redirect: pla:cs}}` - [vedi #link |#Link] +- forzatura `{{toc}}` o disabilitazione `{{toc: no}}` del contenuto automatico (riquadro con link ai singoli titoli) {{priority: -1}} diff --git a/contributing/ja/@home.texy b/contributing/ja/@home.texy new file mode 100644 index 0000000000..9be5c11a00 --- /dev/null +++ b/contributing/ja/@home.texy @@ -0,0 +1,17 @@ +Netteの貢献者になる +************ + +.[perex] +私たちのオープンソースプロジェクトにどのように参加できるかをご覧ください。ソースコードとドキュメントへの貢献の手順を学び、Netteの改善に積極的に参加している開発者コミュニティの一員になりましょう。 + + +**コード** + +- [コードに貢献する方法は? |code] +- [コーディング標準 |coding-standard] + +**ドキュメント** + +- [ドキュメントに貢献する方法は? |documentation] +- [ドキュメントの構文 |syntax] +- "プレビューエディタ":https://editor.nette.org diff --git a/contributing/ja/@left-menu.texy b/contributing/ja/@left-menu.texy new file mode 100644 index 0000000000..cebe170ec6 --- /dev/null +++ b/contributing/ja/@left-menu.texy @@ -0,0 +1,10 @@ +コード +*** +- [コードに貢献する方法は? |code] +- [コーディング標準 |coding-standard] + +ドキュメント +****** +- [ドキュメントに貢献する方法は? |documentation] +- [ドキュメントの構文 |syntax] +- "プレビューエディタ":https://editor.nette.org diff --git a/contributing/ja/code.texy b/contributing/ja/code.texy new file mode 100644 index 0000000000..b068678c3d --- /dev/null +++ b/contributing/ja/code.texy @@ -0,0 +1,118 @@ +コードへの貢献方法 +********* + +.[perex] +Netteフレームワークに貢献しようとしていて、ルールや手順を理解する必要がありますか?この初心者向けガイドでは、コードに効果的に貢献し、リポジトリで作業し、変更を実装する方法をステップバイステップで示します。 + + +手順 +====== + +コードに貢献するには、[GitHub|https://github.com] アカウントを持ち、Gitバージョン管理システムの基本に精通している必要があります。Gitの操作に慣れていない場合は、[git - the simple guide |https://rogerdudler.github.io/git-guide/] ガイドを参照したり、多くの [グラフィカルクライアント |https://git-scm.com/downloads/guis] のいずれかを利用したりできます。 + + +環境とリポジトリの準備 +----------- + +1) GitHubで、編集する [パッケージ |www:packages] のリポジトリの [フォーク |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] を作成します +2) このリポジトリを自分のコンピュータに [クローン |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] します +3) `composer install` コマンドを使用して、[Nette Tester |tester:] を含む依存関係をインストールします +4) `composer tester` を実行してテストが機能することを確認します +5) 最新のリリースバージョンに基づいて [#新しいブランチ] を作成します + + +独自の変更の実装 +-------- + +これで、独自のコード変更を行うことができます: + +1) 必要な変更をプログラムし、テストを忘れないでください +2) `composer tester` を使用してテストが正常に実行されることを確認します +3) コードが [#コーディング規約] を満たしているか確認します +4) [この形式 |#コミットの説明] の説明とともに変更を保存(コミット)します + +各論理ステップごとに1つのコミットを作成できます。各コミットは単独で意味があるべきです。 + + +変更の送信 +----- + +変更に満足したら、送信できます: + +1) 変更をGitHubの自分のフォークに送信(プッシュ)します +2) そこから、[プルリクエスト |https://help.github.com/articles/creating-a-pull-request] (PR) を作成してNetteリポジトリに送信します +3) 説明に [十分な情報 |#プルリクエストの説明] を記載します + + +コメントの反映 +------- + +あなたのコミットは他の人にも見られるようになります。コメントで指摘を受けることは一般的です: + +1) 提案された変更を追跡します +2) 新しいコミットとして反映するか、[以前のものとマージ |https://help.github.com/en/github/using-git/about-git-rebase] します +3) コミットを再度GitHubに送信すると、自動的にプルリクエストに表示されます + +既存のプルリクエストを修正するために新しいプルリクエストを作成しないでください。 + + +ドキュメント +------ + +機能性を変更したり、新しい機能を追加したりした場合は、それを [ドキュメントに追加 |documentation] することも忘れないでください。 + + +新しいブランチ +======= + +可能であれば、最新のリリースバージョン、つまり特定のブランチの最新のタグに対して変更を行ってください。タグ `v3.2.1` の場合、次のコマンドでブランチを作成します: + +```shell +git checkout -b new_branch_name v3.2.1 +``` + + +コーディング規約 +======== + +あなたのコードは、Netteフレームワークで使用されている [コーディング規約 |coding standard] に準拠する必要があります。コードのチェックと修正には自動ツールが利用可能です。Composerを介して、選択したフォルダに **グローバルに** インストールできます: + +```shell +composer create-project nette/coding-standard /path/to/nette-coding-standard +``` + +これで、ターミナルでツールを実行できるようになるはずです。最初のコマンドでチェックし、2番目のコマンドで現在のディレクトリの `src` および `tests` フォルダ内のコードを修正します: + +```shell +/path/to/nette-coding-standard/ecs check +/path/to/nette-coding-standard/ecs check --fix +``` + + +コミットの説明 +======= + +Netteでは、コミットの件名は次の形式です:`Presenter: fixed AJAX detection [Closes #69]` + +- コロンが続く領域 +- 可能であれば過去形のコミットの目的、可能であれば次の単語で始めます:"added .(新しい機能の追加)", "fixed .(修正)", "refactored .(動作を変更しないコードの変更)", changed, removed +- コミットが後方互換性を壊す場合は、"BC break" を追加します +- `(#123)` や `[Closes #69]` のような課題トラッカーへの可能な関連付け +- 件名の後には、1行の空行が続き、その後、フォーラムへのリンクなどを含む詳細な説明が続くことがあります + + +プルリクエストの説明 +========== + +プルリクエストを作成する際、GitHubインターフェースではタイトルと説明を入力できます。わかりやすいタイトルを付け、説明には変更の理由についてできるだけ多くの情報を提供してください。 + +また、ヘッダーも表示され、それが新機能なのかバグ修正なのか、後方互換性の破壊(BC break)が発生する可能性があるかどうかを指定します。関連する問題(issue)がある場合は、プルリクエストが承認された後に閉じられるようにリンクしてください。 + +``` +- bug fix / new feature? <!-- #issue番号、もしあれば --> +- BC break? yes/no +- doc PR: nette/docs#? <!-- 大歓迎、https://nette.org/en/writing を参照 --> +``` + + +{{priority: -1}} diff --git a/contributing/ja/coding-standard.texy b/contributing/ja/coding-standard.texy new file mode 100644 index 0000000000..ab7a4b103e --- /dev/null +++ b/contributing/ja/coding-standard.texy @@ -0,0 +1,128 @@ +コーディング規約 +******** + +.[perex] +このドキュメントでは、Nette開発のためのルールと推奨事項について説明します。Netteにコードを貢献する際には、これらを遵守する必要があります。最も簡単な方法は、既存のコードを模倣することです。 目標は、すべてのコードが一人の人間によって書かれたかのように見えるようにすることです。 + +Netteコーディング規約は、[PSR-12 Extended Coding Style |https://www.php-fig.org/psr/psr-12/] に準拠していますが、2つの主な例外があります:インデントには [#スペースの代わりにタブ] を使用し、[クラス定数にPascalCaseを使用 |https://blog.nette.org/en/for-less-screaming-in-the-code] します。 + + +一般規則 +==== + +- 各PHPファイルには `declare(strict_types=1)` を含める必要があります +- 読みやすさ向上のため、メソッドを区切るために2つの空行を使用します。 +- シャットアップ演算子を使用する理由は文書化する必要があります:`@mkdir($dir); // @ - ディレクトリは存在する可能性があります`。 +- 弱い型付けの比較演算子(例:`==`, `!=`, ...)を使用する場合、意図を文書化する必要があります:`// == nullを受け入れる` +- 複数の例外を1つの `exceptions.php` ファイルに記述できます。 +- インターフェースではメソッドの可視性を指定しません。常にpublicだからです。 +- 各プロパティ、戻り値、パラメータには型を指定する必要があります。逆に、final定数には型を記述しません。明らかだからです。 +- 文字列リテラル自体にアポストロフィが含まれていない限り、文字列を囲むには単一引用符を使用する必要があります。 + + +命名規則 +==== + +- 全体が長すぎない限り、略語を使用しないでください。 +- 2文字の略語には大文字を使用し、長い略語にはpascal/camelケースを使用します。 +- クラス名には名詞または名詞句を使用します。 +- クラス名には、具体性(`Array`)だけでなく、一般性(`ArrayIterator`)も含める必要があります。PHP言語属性は例外です。 +- "クラス定数とenumはPascalCapsを使用する必要があります":https://blog.nette.org/en/for-less-screaming-in-the-code。 +- "インターフェースと抽象クラスには、`Abstract`、`Interface`、`I`のような接頭辞や接尾辞を含めるべきではありません":https://blog.nette.org/en/prefixes-and-suffixes-do-not-belong-in-interface-names。 + + +折り返しと波括弧 +======== + +Netteコーディング規約はPSR-12(またはPER Coding Style)に準拠しており、いくつかの点で補足または変更されています: + +- アロー関数は括弧の前にスペースを入れずに記述します。つまり `fn($a) => $b` +- 異なるタイプの `use` インポートステートメントの間に空行は必要ありません +- 関数/メソッドの戻り値の型と開始波括弧は常に別々の行に記述します: + +```php + public function find( + string $dir, + array $options, + ): array + { + // メソッド本体 + } +``` + +開始波括弧を別々の行に置くことは、関数/メソッドのシグネチャと本体を視覚的に区別するために重要です。シグネチャが1行の場合、区別は明確です(左の画像)。複数行の場合、PSRではシグネチャと本体が混ざり合いますが(中央)、Nette標準では引き続き区別されます(右): + +[* new-line-after.webp *] + + +ドキュメンテーションブロック (phpDoc) +======================= + +主なルール:付加価値なしに、パラメータの型や戻り値の型など、シグネチャ内の情報を決して複製しないでください。 + +クラス定義のドキュメンテーションブロック: + +- クラスの説明で始まります。 +- 空行が続きます。 +- `@property` (または `@property-read`, `@property-write`)アノテーションが続きます。構文は:アノテーション、スペース、型、スペース、$名前。 +- `@method` アノテーションが続きます。構文は:アノテーション、スペース、戻り値の型、スペース、名前(型 $param, ...)。 +- `@author` アノテーションは省略されます。作者情報はソースコードの履歴に保存されます。 +- `@internal` または `@deprecated` アノテーションを使用できます。 + +```php +/** + * MIMEメッセージパート。 + * + * @property string $encoding + * @property-read array $headers + * @method string getSomething(string $name) + * @method static bool isEnabled() + */ +``` + +`@var` アノテーションのみを含むプロパティのドキュメンテーションブロックは、1行であるべきです: + +```php +/** @var string[] */ +private array $name; +``` + +メソッド定義のドキュメンテーションブロック: + +- メソッドの簡単な説明で始まります。 +- 空行はありません。 +- `@param` アノテーションは個別の行に記述します。 +- `@return` アノテーション。 +- `@throws` アノテーションは個別の行に記述します。 +- `@internal` または `@deprecated` アノテーションを使用できます。 + +各アノテーションの後には1つのスペースが続きますが、`@param` の後には読みやすさ向上のために2つのスペースが続きます。 + +```php +/** + * ディレクトリ内のファイルを検索します。 + * @param string[] $options + * @return string[] + * @throws DirectoryNotFoundException + */ +public function find(string $dir, array $options): array +``` + + +スペースの代わりにタブ +=========== + +タブはスペースに比べていくつかの利点があります: + +- インデントのサイズはエディタや [ウェブ|https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size] で調整できます +- ユーザーのインデントサイズの好みをコードに強制しないため、コードの移植性が向上します +- 1回のキーストロークで入力できます(タブをスペースに変換するエディタだけでなく、どこでも) +- インデントはその目的です +- 視覚障害のある同僚や盲目の同僚のニーズを尊重します + +私たちのプロジェクトでタブを使用することにより、幅のカスタマイズが可能になります。これはほとんどの人にとっては不要に見えるかもしれませんが、視覚障害のある人々にとっては不可欠です。 + +点字ディスプレイを使用する盲目のプログラマにとって、各スペースは1つの点字セルを表します。したがって、デフォルトのインデントが4スペースの場合、第3レベルのインデントはコードが始まる前に12個の貴重な点字セルを浪費します。 ノートパソコンで最も一般的に使用される40セルのディスプレイでは、利用可能なセルの4分の1以上が情報なしで浪費されます。 + + +{{priority: -1}} diff --git a/contributing/ja/documentation.texy b/contributing/ja/documentation.texy new file mode 100644 index 0000000000..052c6485fd --- /dev/null +++ b/contributing/ja/documentation.texy @@ -0,0 +1,68 @@ +ドキュメントへの貢献方法 +************ + +.[perex] +ドキュメントへの貢献は、他の人がフレームワークを理解するのを助けるため、最も有益な活動の1つです。 + + +書き方 +--- + +ドキュメントは主に、トピックに慣れていない人々を対象としています。したがって、いくつかの重要な点を満たす必要があります: + +- 簡単で一般的なことから始めます。より高度なトピックには最後に進みます +- 物事をできるだけよく説明するように努めます。たとえば、まず同僚にトピックを説明してみてください +- ユーザーが特定のトピックについて実際に知る必要がある情報のみを提供します +- あなたの情報が本当に真実であることを確認します。すべてのコードをテストします +- 簡潔に - 書いたものを半分に短縮します。そして、必要であればもう一度 +- 太字から `.[note]` のようなボックスまで、あらゆる種類の強調表示を控えめに使用します +- コードでは [コーディング規約 |Coding Standard] を遵守します + +また、[構文 |syntax] を習得してください。執筆中に記事をプレビューするには、[プレビュー付きエディタ |https://editor.nette.org/] を使用できます。 + + +言語バージョン +------- + +主要言語は英語です。したがって、あなたの変更はチェコ語と英語の両方であるべきです。英語が得意でない場合は、[DeepL Translator |https://www.deepl.com/translator] を使用し、他の人がテキストをチェックします。 + +他の言語への翻訳は、あなたの修正が承認され、微調整された後に自動的に行われます。 + + +簡単な編集 +----- + +ドキュメントに貢献するには、[GitHub|https://github.com] アカウントが必要です。 + +ドキュメントに小さな変更を加える最も簡単な方法は、各ページの最後にあるリンクを利用することです: + +- *GitHubで表示* は、GitHub上の特定のページのソース形式を開きます。その後、`E` ボタンを押すだけで編集を開始できます(GitHubにログインしている必要があります) +- *プレビューを開く* はエディタを開き、最終的な視覚的な外観もすぐに確認できます + +[プレビュー付きエディタ |https://editor.nette.org/] には変更を直接GitHubに保存するオプションがないため、編集が完了したら、ソーステキストをクリップボードにコピーし(*クリップボードにコピー* ボタンを使用)、それをGitHubのエディタに貼り付ける必要があります。編集フィールドの下には送信フォームがあります。ここで、修正の理由を簡単に要約して説明することを忘れないでください。送信後、いわゆるプルリクエスト(PR)が作成され、さらに編集できます。 + + +より大きな編集 +------- + +GitHubインターフェースを利用するよりも、Gitバージョン管理システムの基本に精通している方が適しています。Gitの操作に慣れていない場合は、[git - the simple guide |https://rogerdudler.github.io/git-guide/] ガイドを参照したり、多くの [グラフィカルクライアント |https://git-scm.com/downloads/guis] のいずれかを利用したりできます。 + +ドキュメントを次のように編集します: + +1) GitHubで、[nette/docs |https://github.com/nette/docs] リポジトリの [フォーク |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] を作成します +2) このリポジトリを自分のコンピュータに [クローン |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] します +3) 次に、[関連するブランチ |#ドキュメントの構造] で変更を行います +4) [Code-Checker |code-checker:] ツールを使用して、テキスト内の余分なスペースをチェックします +4) 変更を保存(コミット)します +6) 変更に満足したら、GitHubの自分のフォークに送信(プッシュ)します +7) そこから、[プルリクエスト |https://help.github.com/articles/creating-a-pull-request] (PR) を作成して `nette/docs` リポジトリに送信します + +コメントで指摘を受けることは一般的です。提案された変更を追跡し、反映します。提案された変更を新しいコミットとして追加し、再度GitHubに送信します。プルリクエストの修正のために新しいプルリクエストを作成しないでください。 + + +ドキュメントの構造 +--------- + +ドキュメント全体は、GitHubの [nette/docs |https://github.com/nette/docs] リポジトリにあります。現在のバージョンはマスターにあり、古いバージョンは `doc-3.x`、`doc-2.x` のようなブランチにあります。 + +各ブランチの内容は、ドキュメントの個々の領域を表す主要なフォルダに分割されます。たとえば、`application/` は https://doc.nette.org/cs/application に対応し、`latte/` は https://latte.nette.org に対応します。これらの各フォルダには、言語バージョン(`cs`、`en`、...)を表すサブフォルダと、オプションでドキュメントページに挿入できる画像を含む `files` サブフォルダが含まれています。 diff --git a/contributing/ja/syntax.texy b/contributing/ja/syntax.texy new file mode 100644 index 0000000000..2fe4e0c515 --- /dev/null +++ b/contributing/ja/syntax.texy @@ -0,0 +1,142 @@ +ドキュメント構文 +******** + +ドキュメントはMarkdownと [Texy構文 |https://texy.nette.org/syntax] を使用し、いくつかの拡張機能があります。 + + +リンク +=== + +内部リンクには角括弧 `[]` を使用します。これは、パイプ記号 `[リンクテキスト |リンクターゲット]` を使用する形式、またはターゲットがテキストと同じ場合(小文字とハイフンに変換後)の省略形 `[リンクテキスト |元のリンクテキスト]` のいずれかです。 + +- `[Page name |Page name]` -> `<a href="/en/page-name">Page name</a>` +- `[link text |Page name]` -> `<a href="/en/page-name">link text</a>` + +異なる言語バージョンまたは異なるセクションにリンクできます。セクションとは、Netteライブラリ(例:`forms`、`latte`など)または`best-practices`、`quickstart`などの特別なセクションを意味します。 + +- `[cs:Page name |cs:Page name]` -> `<a href="/cs/page-name">Page name</a>` (同じセクション、異なる言語) +- `[tracy:Page name |tracy:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (異なるセクション、同じ言語) +- `[tracy:cs:Page name |tracy:cs:Page name]` -> `<a href="//tracy.nette.org/cs/page-name">Page name</a>` (異なるセクションと言語) + +`#` を使用して、ページ上の特定のヘッダーをターゲットにすることもできます。 + +- `[#Heading]` -> `<a href="#toc-heading">Heading</a>` (現在のページのヘッダー) +- `[Page name#Heading]` -> `<a href="/en/page-name#toc-heading">Page name</a>` + +セクションの開始ページへのリンク:(`@home` はセクションのホームページの特別な表現です) + +- `[link text |@home]` -> `<a href="/en/">link text</a>` +- `[link text |tracy:]` -> `<a href="//tracy.nette.org/en/">link text</a>` + + +APIドキュメントへのリンク +-------------- + +常にこの表記法のみを使用してください: + +- `[api:Nette\SmartObject]` -> [api:Nette\SmartObject] +- `[api:Nette\Forms\Form::setTranslator()]` -> [api:Nette\Forms\Form::setTranslator()] +- `[api:Nette\Forms\Form::$onSubmit]` -> [api:Nette\Forms\Form::$onSubmit] +- `[api:Nette\Forms\Form::Required]` -> [api:Nette\Forms\Form::Required] + +完全修飾名は最初の言及でのみ使用してください。後続のリンクには簡略化された名前を使用してください: + +- `[Form::setTranslator() |api:Nette\Forms\Form::setTranslator()]` -> [Form::setTranslator() |api:Nette\Forms\Form::setTranslator()] + + +PHPドキュメントへのリンク +-------------- + +- `[php:substr]` -> [php:substr] + + +ソースコード +====== + +コードブロックは <code>```lang</code> で始まり、<code>```</code> で終わります。サポートされている言語は `php`、`latte`、`neon`、`html`、`css`、`js`、`sql` です。インデントには常にタブを使用してください。 + +``` + ```php + public function renderPage($id) + { + } + ``` +``` + +ファイル名を <code>```php .{file: ArrayTest.php}</code> のように指定することもでき、コードブロックはこのようにレンダリングされます: + +```php .{file: ArrayTest.php} +public function renderPage($id) +{ +} +``` + + +見出し +=== + +最上位の見出し(つまりページタイトル)はアスタリスクで下線を引きます。セクションを区切るには等号を使用します。見出しには等号、次にハイフンで下線を引きます: + +``` +MVCアプリケーションとPresenter +********************* +... + + +リンクの作成 +====== +... + + +テンプレート内のリンク +----------- +... +``` + + +ボックスとスタイル +========= + +Perexは `.[perex]` クラスでマークします .[perex] + +注釈は `.[note]` クラスでマークします .[note] + +ヒントは `.[tip]` クラスでマークします .[tip] + +注意は `.[caution]` クラスでマークします .[caution] + +より強い警告は `.[warning]` クラスでマークします .[warning] + +バージョン番号 `.{data-version:2.4.10}` .{data-version:2.4.10} + +クラスを行の前に記述します: + +``` +.[perex] +これはペレックスです。 +``` + +`.[tip]` のようなボックスは目を引くため、重要でない情報ではなく、強調のために使用されることに注意してください。したがって、その使用は最大限に控えてください。 + + +目次 +===== + +目次(右側のメニューのリンク)は、サイズが4,000バイトを超えるすべてのページに対して自動的に生成されます。このデフォルトの動作は、[#メタタグ] `{{toc}}` を使用して変更できます。目次を構成するテキストは、通常、見出しのテキストから直接取得されますが、`{toc}` 修飾子を使用すると、目次に異なるテキストを表示できます。これは、特に長い見出しに便利です。 + +``` + + +長くて賢い見出し .{toc: 目次に表示される任意の他のテキスト} +================================== +``` + + +メタタグ +==== + +- カスタムページタイトル(`<title>` とパンくずナビゲーション内)の設定 `{{title: 別のタイトル}}` +- リダイレクト `{{redirect: pla:cs}}` - [#リンク] を参照 +- 自動目次(個々の見出しへのリンクを含むボックス)の強制 `{{toc}}` または無効化 `{{toc: no}}` + +{{priority: -1}} diff --git a/contributing/pl/@home.texy b/contributing/pl/@home.texy index 79c6f42f0f..fe566dfdef 100644 --- a/contributing/pl/@home.texy +++ b/contributing/pl/@home.texy @@ -1,17 +1,17 @@ -Zostań współtwórcą Nette -************************ +Zostań kontrybutorem Nette +************************** .[perex] -Zobacz, jak możesz zaangażować się w nasz projekt open source. Dowiedz się, jakie kroki należy podjąć, aby przyczynić się do rozwoju kodu źródłowego i dokumentacji, i dołącz do sieci programistów, którzy są oddani ulepszaniu Nette. +Dowiedz się, jak możesz zaangażować się w nasz projekt open source. Opanuj procedury dotyczące wnoszenia wkładu w kod źródłowy i dokumentację i stań się częścią społeczności programistów, którzy aktywnie uczestniczą w ulepszaniu Nette. **Kod** -- [Przyczynianie się do powstania kodu|code] -- [Standardy kodowania |coding-standard] +- [Jak wnieść wkład w kod? |code] +- [Standard kodowania |coding-standard] **Dokumentacja** -- [Wkład w dokumentację |documentation] +- [Jak wnieść wkład w dokumentację? |documentation] - [Składnia dokumentacji |syntax] - "Edytor podglądu":https://editor.nette.org diff --git a/contributing/pl/@left-menu.texy b/contributing/pl/@left-menu.texy index 10555652ba..da4eb76448 100644 --- a/contributing/pl/@left-menu.texy +++ b/contributing/pl/@left-menu.texy @@ -1,10 +1,10 @@ Kod *** -- [Przyczynianie się do powstania kodu |code] -- [Standardy kodowania |coding-standard] +- [Jak wnieść wkład w kod? |code] +- [Standard kodowania |coding-standard] Dokumentacja ************ -- [Wkład w dokumentację |documentation] +- [Jak wnieść wkład w dokumentację? |documentation] - [Składnia dokumentacji |syntax] - "Edytor podglądu":https://editor.nette.org diff --git a/contributing/pl/code.texy b/contributing/pl/code.texy index 6b340f4f81..7546159296 100644 --- a/contributing/pl/code.texy +++ b/contributing/pl/code.texy @@ -1,87 +1,87 @@ -Wkład do kodu -************* +Jak współtworzyć kod +******************** .[perex] -Planujesz wnieść swój wkład do Nette Framework i potrzebujesz zapoznać się z zasadami i procedurami? Ten przewodnik dla początkujących poprowadzi Cię przez kroki, które pozwolą Ci efektywnie współtworzyć kod, pracować z repozytoriami i wprowadzać zmiany. +Zamierzasz współtworzyć Nette Framework i potrzebujesz zorientować się w zasadach i procedurach? Ten przewodnik dla początkujących krok po kroku pokaże Ci, jak efektywnie współtworzyć kod, pracować z repozytoriami i implementować zmiany. -Procedura .[#toc-procedure] -=========================== +Procedura +========= -Aby wnieść wkład do kodu, konieczne jest posiadanie konta na [GitHubie |https://github.com] i znajomość podstaw pracy z systemem kontroli wersji Git. Jeśli nie jesteś zaznajomiony z Gitem, możesz sprawdzić [git - prosty przewodnik |https://rogerdudler.github.io/git-guide/] i rozważyć użycie jednego z wielu [graficznych klientów |https://git-scm.com/downloads/guis]. +Aby współtworzyć kod, niezbędne jest posiadanie konta na [GitHub|https://github.com] i znajomość podstaw pracy z systemem kontroli wersji Git. Jeśli nie znasz pracy z Gitem, możesz zapoznać się z przewodnikiem [git - the simple guide |https://rogerdudler.github.io/git-guide/] i ewentualnie skorzystać z jednego z wielu [klientów graficznych |https://git-scm.com/downloads/guis]. -Przygotowanie środowiska i repozytorium .[#toc-preparing-the-environment-and-repository] ----------------------------------------------------------------------------------------- +Przygotowanie środowiska i repozytorium +--------------------------------------- -1) Na GitHubie utwórz [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] [repozytorium pakietów |www:packages], które zamierzasz zmodyfikować -2) [Sklonuj |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] to repozytorium na swój komputer -3) Zainstaluj zależności, w tym [Nette Tester |tester:], używając polecenia `composer install` -4) Sprawdź, czy testy działają, uruchamiając `composer tester` -5) Utwórz [nową gałąź |#New Branch] opartą na najnowszej wydanej wersji +1) na GitHubie utwórz [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] repozytorium [pakietu |www:packages], który zamierzasz zmodyfikować +2) to repozytorium [sklonujesz |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] na swój komputer +3) zainstaluj zależności, w tym [Nette Testera |tester:], za pomocą polecenia `composer install` +4) sprawdź, czy testy działają, uruchamiając `composer tester` +5) utwórz [#nową gałąź] opartą na ostatniej wydanej wersji -Wdrażanie własnych zmian .[#toc-implementing-your-own-changes] --------------------------------------------------------------- +Implementacja własnych zmian +---------------------------- -Teraz możesz wprowadzić własne poprawki do kodu: +Teraz możesz wprowadzić własne modyfikacje kodu: -1) Zaimplementuj pożądane zmiany i nie zapomnij o testach -2) Upewnij się, że testy przebiegają pomyślnie używając `composer tester` -3) Sprawdź, czy kod spełnia [standardy kodowania |#coding standards] -4) Zapisz (commit) zmiany z opisem w [tym formacie |#Commit Description] +1) zaprogramuj wymagane zmiany i nie zapomnij o testach +2) upewnij się, że testy przechodzą pomyślnie, za pomocą `composer tester` +3) sprawdź, czy kod spełnia [standard kodowania |#Standardy kodowania] +4) zmiany zapisz (commituj) z opisem w [tym formacie |#Opis commita] -Możesz stworzyć wiele commitów, po jednym dla każdego logicznego kroku. Każdy commit powinien być znaczący sam w sobie. +Możesz utworzyć kilka commitów, jeden dla każdego logicznego kroku. Każdy commit powinien być sensowny samodzielnie. -Przesyłanie zmian .[#toc-submitting-changes] --------------------------------------------- +Wysyłanie zmian +--------------- -Gdy zmiany są zadowalające, można je przesłać: +Gdy będziesz zadowolony ze zmian, możesz je wysłać: -1) Przesuń zmiany na GitHub do swojego forka -2) Stamtąd prześlij je do repozytorium Nette, tworząc [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) -3) Podaj [wystarczającą ilość informacji |#pull request description] w opisie +1) wyślij (pushnij) zmiany na GitHub do swojego forka +2) stamtąd wyślij je do repozytorium Nette, tworząc [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) +3) podaj w opisie [wystarczająco informacji |#Opis pull requesta] -Uwzględnianie informacji zwrotnych .[#toc-incorporating-feedback] ------------------------------------------------------------------ +Wprowadzanie uwag +----------------- -Twój commit jest teraz widoczny dla innych. Powszechne jest otrzymywanie komentarzy z sugestiami: +Twoje commity teraz zobaczą również inni. Jest to normalne, że otrzymasz komentarze z uwagami: -1) Śledzić proponowane zmiany -2) Włącz je jako nowe commity lub [połącz z poprzednimi |https://help.github.com/en/github/using-git/about-git-rebase] -3) Prześlij ponownie commit na GitHub, a automatycznie pojawi się on w żądaniu ściągnięcia. +1) śledź proponowane modyfikacje +2) wprowadź je jako nowe commity lub [połącz z poprzednimi |https://help.github.com/en/github/using-git/about-git-rebase] +3) ponownie wyślij commity na GitHub, a automatycznie pojawią się w pull requeście -Nigdy nie twórz nowego pull requesta, aby zmodyfikować istniejący. +Nigdy nie twórz nowego pull requesta w celu modyfikacji istniejącego. -Dokumentacja .[#toc-documentation] ----------------------------------- +Dokumentacja +------------ -Jeśli zmieniłeś funkcjonalność lub dodałeś nową, nie zapomnij [dodać jej |documentation] również [do dokumentacji |documentation]. +Jeśli zmieniłeś funkcjonalność lub dodałeś nową, nie zapomnij jej również [dodać do dokumentacji |documentation]. -Nowa gałąź .[#toc-new-branch] -============================= +Nowa gałąź +========== -Jeśli to możliwe, dokonuj zmian względem najnowszej wydanej wersji, czyli ostatniego tagu w gałęzi. Dla tagu v3.2.1 utwórz gałąź używając tego polecenia: +Jeśli to możliwe, wprowadzaj zmiany względem ostatniej wydanej wersji, tj. ostatniego tagu w danej gałęzi. Dla tagu `v3.2.1` utworzysz gałąź tym poleceniem: ```shell git checkout -b new_branch_name v3.2.1 ``` -Standardy kodowania .[#toc-coding-standards] -============================================ +Standardy kodowania +=================== -Twój kod musi spełniać [standardy kodowania |coding standard] stosowane w Nette Framework. Dostępne jest automatyczne narzędzie do sprawdzania i poprawiania kodu. Możesz je zainstalować **globalnie** poprzez Composera do wybranego przez siebie folderu: +Twój kod musi spełniać [standard kodowania |coding standard] używany w Nette Framework. Do kontroli i poprawy kodu dostępne jest automatyczne narzędzie. Można je zainstalować za pomocą Composera **globalnie** w wybranym przez siebie folderze: ```shell composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Teraz powinieneś być w stanie uruchomić narzędzie w terminalu. Pierwsze polecenie sprawdza, a drugie naprawia kod w folderach `src` i `tests` w bieżącym katalogu: +Teraz powinieneś móc uruchomić narzędzie w terminalu. Pierwszym poleceniem sprawdzisz, a drugim również poprawisz kod w folderach `src` i `tests` w bieżącym katalogu: ```shell /path/to/nette-coding-standard/ecs check @@ -89,29 +89,29 @@ Teraz powinieneś być w stanie uruchomić narzędzie w terminalu. Pierwsze pole ``` -Opis zobowiązania .[#toc-commit-description] -============================================ +Opis commita +============ -W Nette, tematy commitów mają następujący format: `Presenter: fixed AJAX detection [Closes #69]` +W Nette tematy commitów mają format: `Presenter: fixed AJAX detection [Closes #69]` - obszar, po którym następuje dwukropek -- cel commitu w czasie przeszłym; jeśli to możliwe, zacznij od słów takich jak: added, fixed, refactored, changed, removed -- jeśli commit łamie wsteczną kompatybilność, dodaj "BC break" -- wszelkie połączenia z issue trackerem, takie jak `(#123)` lub `[Closes #69]` -- po temacie może być jedna pusta linia, po której następuje bardziej szczegółowy opis, zawierający np. linki do forum +- cel commita w czasie przeszłym, jeśli to możliwe, zacznij od słowa: "added (dodana nowa właściwość)", "fixed (poprawka)", "refactored (zmiana w kodzie bez zmiany zachowania)", changed, removed +- jeśli commit przerywa kompatybilność wsteczną, dodaj "BC break" +- ewentualne powiązanie z issue trackerem, jak `(#123)` lub `[Closes #69]` +- po temacie może nastąpić jedna wolna linia, a następnie bardziej szczegółowy opis, w tym np. linki do forum -Opis Pull Request .[#toc-pull-request-description] -================================================== +Opis pull requesta +================== -Podczas tworzenia pull request, interfejs GitHub pozwoli Ci wprowadzić tytuł i opis. Podaj zwięzły tytuł i zawrzyj jak najwięcej informacji w opisie o powodach zmiany. +Podczas tworzenia pull requesta interfejs GitHubu pozwoli Ci wprowadzić tytuł i opis. Podaj zwięzły tytuł, a w opisie dostarcz jak najwięcej informacji o powodach Twojej zmiany. -Określ również w nagłówku, czy jest to nowa funkcja czy poprawka błędu i czy może spowodować problemy z kompatybilnością wsteczną (BC break). Jeśli istnieje powiązany problem, umieść do niego link, aby został zamknięty po zatwierdzeniu pull requesta. +Wyświetli się również nagłówek, w którym określ, czy jest to nowa funkcja, czy poprawka błędu i czy może dojść do naruszenia kompatybilności wstecznej (BC break). Jeśli istnieje powiązany problem (issue), odwołaj się do niego, aby został zamknięty po zatwierdzeniu pull requesta. ``` -- bug fix / new feature? <!-- #issue numbers, if any --> +- bug fix / new feature? <!-- #numery issue, jeśli istnieją --> - BC break? yes/no -- doc PR: nette/docs#? <!-- highly welcome, see https://nette.org/en/writing --> +- doc PR: nette/docs#? <!-- bardzo mile widziane, zobacz https://nette.org/en/writing --> ``` diff --git a/contributing/pl/coding-standard.texy b/contributing/pl/coding-standard.texy index bdf2a84a68..0d2237d70b 100644 --- a/contributing/pl/coding-standard.texy +++ b/contributing/pl/coding-standard.texy @@ -1,44 +1,44 @@ Standard kodowania ****************** -Dokument ten opisuje zasady i zalecenia dotyczące rozwoju Nette. Musisz ich przestrzegać, gdy dodajesz kod do Nette. Najprostszym sposobem na to jest emulacja istniejącego kodu. -Chodzi o to, aby cały kod wyglądał tak, jakby został napisany przez jedną osobę . +.[perex] +Ten dokument opisuje zasady i zalecenia dotyczące rozwoju Nette. Przy współtworzeniu kodu do Nette musisz ich przestrzegać. Najprostszym sposobem, aby to zrobić, jest naśladowanie istniejącego kodu. Chodzi o to, aby cały kod wyglądał, jakby napisała go jedna osoba. -Standard kodowania Nette jest zgodny z [rozszerzonym stylem kodowania PSR-12 |https://www.php-fig.org/psr/psr-12/] z dwoma głównymi wyjątkami: używa [tabulatorów zamiast spacji |#Tabs-Instead-of-Spaces] dla wcięć i [używa PascalCase dla stałych klas |https://blog.nette.org/pl/aby-mniej-krzyczec-w-kodzie]. +Standard kodowania Nette odpowiada [PSR-12 Extended Coding Style |https://www.php-fig.org/psr/psr-12/] z dwoma głównymi wyjątkami: do wcięć używa [#tabulatory zamiast spacji] i dla [stałych klas używa PascalCase|https://blog.nette.org/pl/for-less-screaming-in-the-code]. -Zasady ogólne .[#toc-general-rules] -=================================== +Ogólne zasady +============= - Każdy plik PHP musi zawierać `declare(strict_types=1)` - Dwie puste linie są używane do oddzielenia metod dla lepszej czytelności. -- Powód użycia operatora zamknięcia musi być udokumentowany: `@mkdir($dir); // @ - directory may exist` -- Jeśli używany jest operator porównania typu weak typed (ie. `==`, `!=`, ...), intencja musi być udokumentowana: `// == to accept null` -- Możesz zapisać więcej wyjątków w jednym pliku `exceptions.php` -- Widoczność metod nie jest określona dla interfejsów, ponieważ są one zawsze publiczne. -- Każda właściwość, wartość zwracana i parametr muszą mieć określony typ. Natomiast dla stałych finalnych nigdy nie określamy typu, ponieważ jest on oczywisty. -- Do delimitacji ciągu znaków należy używać pojedynczych cudzysłowów, z wyjątkiem sytuacji, gdy sam literał zawiera apostrofy. +- Powód użycia operatora wyciszenia musi być udokumentowany: `@mkdir($dir); // @ - katalog może istnieć`. +- Jeśli używany jest operator porównania słabo typizowanego (tj. `==`, `!=`, ...), musi być udokumentowany zamiar: `// == akceptuj null` +- Do jednego pliku `exceptions.php` możesz zapisać wiele wyjątków. +- W interfejsach nie określa się widoczności metod, ponieważ są zawsze publiczne. +- Każda właściwość, wartość zwracana i parametr musi mieć podany typ. Natomiast przy stałych finalnych typu nigdy nie podajemy, ponieważ jest oczywisty. +- Do ograniczenia ciągu znaków należy używać pojedynczych cudzysłowów, z wyjątkiem przypadków, gdy sam literał zawiera apostrofy. -Konwencje nazewnicze .[#toc-naming-conventions] -=============================================== +Konwencje nazewnictwa +===================== -- Nie należy używać skrótów, chyba że pełna nazwa jest zbyt długa. -- Dla skrótów dwuliterowych należy używać dużych liter, dla dłuższych skrótów - pascal/camel. -- Użyj rzeczownika lub frazy rzeczownikowej dla nazwy klasy. -- Nazwy klas muszą zawierać nie tylko specyfikę (`Array`), ale także ogólność (`ArrayIterator`). Wyjątkiem są atrybuty PHP. -- "Stałe klasy i enum powinny używać PascalCaps":https://blog.nette.org/pl/aby-mniej-krzyczec-w-kodzie. -- "Interfejsy i klasy abstrakcyjne nie powinny zawierać przedrostków ani przyrostków":https://blog.nette.org/pl/przedrostki-i-przyrostki-nie-sa-czescia-nazw-interfejsow takich jak `Abstract`, `Interface` czy `I`. +- Nie używaj skrótów, chyba że pełna nazwa jest zbyt długa. +- W przypadku dwuliterowych skrótów używaj wielkich liter, w przypadku dłuższych skrótów pascal/camel. +- Dla nazwy klasy używaj rzeczownika lub wyrażenia rzeczownikowego. +- Nazwy klas muszą zawierać nie tylko specyficzność (`Array`), ale także ogólność (`ArrayIterator`). Wyjątkiem są atrybuty języka PHP. +- "Stałe klas i enumy powinny używać PascalCaps":https://blog.nette.org/pl/for-less-screaming-in-the-code. +- "Interfejsy i klasy abstrakcyjne nie powinny zawierać prefiksów ani sufiksów":https://blog.nette.org/pl/prefixes-and-suffixes-do-not-belong-in-interface-names jak `Abstract`, `Interface` lub `I`. -Owijki i szelki .[#toc-wrapping-and-braces] -=========================================== +Zawijanie i nawiasy klamrowe +============================ -Standard Kodowania Nette odpowiada PSR-12 (czyli stylowi kodowania PER), w niektórych punktach uzupełnia go lub modyfikuje: +Standard kodowania Nette odpowiada PSR-12 (resp. PER Coding Style), w niektórych punktach go uzupełnia lub modyfikuje: -- Funkcje strzałkowe zapisujemy bez spacji przed nawiasem, tzn. `fn($a) => $b` -- nie jest wymagany pusty wiersz pomiędzy różnymi typami `use` deklaracji importowych -- typ zwrotny funkcji/metody oraz wiodący nawias złożony powinny być umieszczone w oddzielnych liniach dla lepszej czytelności: +- funkcje strzałkowe pisze się bez spacji przed nawiasem, tj. `fn($a) => $b` +- nie wymaga się pustej linii między różnymi typami instrukcji importu `use` +- typ zwracany funkcji/metody i otwierający nawias klamrowy są zawsze na osobnych liniach: ```php public function find( @@ -46,28 +46,32 @@ Standard Kodowania Nette odpowiada PSR-12 (czyli stylowi kodowania PER), w niekt array $options, ): array { - // tělo metody + // ciało metody } ``` +Otwierający nawias klamrowy na osobnej linii jest ważny dla wizualnego oddzielenia sygnatury funkcji/metody od ciała. Jeśli sygnatura jest na jednej linii, oddzielenie jest wyraźne (rysunek po lewej), jeśli jest na wielu liniach, w PSR sygnatury i ciała zlewają się (w środku), podczas gdy w standardzie Nette są nadal oddzielone (po prawej): -Bloki dokumentacji (phpDoc) .[#toc-documentation-blocks-phpdoc] -=============================================================== +[* new-line-after.webp *] -Główna zasada: Nigdy nie powielaj żadnych informacji w podpisie, takich jak typ parametru lub typ powrotu, bez dodawania wartości. -Blok dokumentacji definicji klasy: +Bloki dokumentacyjne (phpDoc) +============================= -- Zaczyna się od opisu zajęć. -- Po czym następuje pusta linia. -- Adnotacje `@property` (lub `@property-read`, `@property-write`) następują jedna po drugiej. Składnia to: annotacja, spacja, typ, spacja, $name. -- Następujące adnotacje są `@method`, jedna po drugiej. Składnia to: adnotacja, spacja, typ zwrotny, spacja, nazwa($param typ, ...). -- Pomija się adnotację `@author`. Autorstwo jest zachowane w historii kodu źródłowego. -- Można stosować adnotacje `@internal` lub `@deprecated`. +Główna zasada: Nigdy nie duplikuj żadnych informacji w sygnaturze, takich jak typ parametru lub typ zwracany, bez dodanej wartości. + +Blok dokumentacyjny dla definicji klasy: + +- Zaczyna się opisem klasy. +- Następuje pusta linia. +- Następują adnotacje `@property` (lub `@property-read`, `@property-write`), jedna po drugiej. Składnia to: adnotacja, spacja, typ, spacja, $nazwa. +- Następują adnotacje `@method`, jedna po drugiej. Składnia to: adnotacja, spacja, typ zwracany, spacja, nazwa(typ $param, ...). +- Adnotacja `@author` jest pomijana. Autorstwo jest przechowywane w historii kodu źródłowego. +- Można użyć adnotacji `@internal` lub `@deprecated`. ```php /** - * MIME message part. + * Część wiadomości MIME. * * @property string $encoding * @property-read array $headers @@ -76,27 +80,27 @@ Blok dokumentacji definicji klasy: */ ``` -Blok dokumentacji dla właściwości, która zawiera tylko adnotację `@var` powinien być pojedynczym wierszem: +Blok dokumentacyjny dla właściwości, który zawiera tylko adnotację `@var`, powinien być jednoliniowy: ```php /** @var string[] */ private array $name; ``` -Blok dokumentacji dla definicji metody: +Blok dokumentacyjny dla definicji metody: -- Zaczyna się od krótkiego opisu metody. +- Zaczyna się krótkim opisem metody. - Brak pustej linii. -- Adnotacja `@param` linia po linii. -- Annotacja `@return`. -- Adnotacja `@throws`, jeden po drugim. -- Można stosować adnotacje `@internal` lub `@deprecated`. +- Adnotacje `@param` w osobnych liniach. +- Adnotacja `@return`. +- Adnotacje `@throws`, jedna po drugiej. +- Można użyć adnotacji `@internal` lub `@deprecated`. -Po każdej adnotacji następuje jedna spacja, z wyjątkiem `@param`, po której następują dwie spacje dla czytelności. +Po każdej adnotacji następuje jedna spacja, z wyjątkiem `@param`, po której dla lepszej czytelności następują dwie spacje. ```php /** - * Finds a file in directory. + * Znajduje plik w katalogu. * @param string[] $options * @return string[] * @throws DirectoryNotFoundException @@ -105,21 +109,20 @@ public function find(string $dir, array $options): array ``` -Tabulatory zamiast spacji .[#toc-tabs-instead-of-spaces] -======================================================== +Tabulatory zamiast spacji +========================= -Tabulatory mają kilka zalet w stosunku do spacji: +Tabulatory mają w porównaniu ze spacjami kilka zalet: -- odstępy można regulować w edytorach i w "sieci:https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size " -- nie nakładają na kod preferencji użytkownika co do wielkości wcięcia, więc kod jest bardziej przenośny -- można je wpisać jednym naciśnięciem klawisza (wszędzie, nie tylko w edytorach zamieniających tabulatory na spacje) +- rozmiar wcięcia można dostosować w edytorach i na "webie":https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size +- nie narzucają kodowi preferencji użytkownika co do rozmiaru wcięcia, dzięki czemu kod jest lepiej przenośny +- można je napisać jednym naciśnięciem klawisza (wszędzie, nie tylko w edytorach, które zamieniają tabulatory na spacje) - wcięcie jest ich celem -- szanują potrzeby kolegów niedowidzących i niewidomych +- szanują potrzeby kolegów z wadami wzroku i niewidomych -Stosując zakładki w naszych projektach, umożliwiamy regulację szerokości, co dla większości osób może wydawać się stratą, ale dla osób z wadami wzroku jest niezbędne. +Używając tabulatorów w naszych projektach, umożliwiamy dostosowanie szerokości, co większości ludzi może wydawać się zbędne, ale dla osób z wadami wzroku jest niezbędne. -Dla niewidomych programistów, którzy używają wyświetlaczy brajlowskich, każda spacja reprezentuje jedną komórkę brajlowską. Jeśli więc domyślne wcięcie to 4 spacje, wcięcie na poziomie 3 marnuje 12 cennych komórek brajla, zanim jeszcze rozpocznie się kod. -Na 40-komorowym wyświetlaczu, który jest najczęściej stosowany w laptopach, to ponad jedna czwarta dostępnych komórek marnuje się bez żadnych informacji. +Dla niewidomych programistów, którzy używają monitorów brajlowskich, każda spacja stanowi jedną komórkę brajlowską. Jeśli więc domyślne wcięcie to 4 spacje, wcięcie 3. poziomu marnuje 12 cennych komórek brajlowskich jeszcze przed rozpoczęciem kodu. Na 40-komórkowym monitorze, który jest najczęściej używany w laptopach, to ponad ćwierć dostępnych komórek, które są marnowane bez żadnej informacji. {{priority: -1}} diff --git a/contributing/pl/documentation.texy b/contributing/pl/documentation.texy index bc8e0c101a..d34ecbffc2 100644 --- a/contributing/pl/documentation.texy +++ b/contributing/pl/documentation.texy @@ -1,69 +1,68 @@ -Wkład w dokumentację -******************** +Jak współtworzyć dokumentację +***************************** .[perex] -Wkład w dokumentację jest jednym z najbardziej wartościowych działań, ponieważ pomaga innym zrozumieć framework. +Współtworzenie dokumentacji jest jedną z najbardziej wartościowych czynności, ponieważ pomagasz innym zrozumieć framework. -Jak pisać? .[#toc-how-to-write] -------------------------------- +Jak pisać? +---------- -Dokumentacja jest przeznaczona przede wszystkim dla osób, które są nowe w temacie. Dlatego powinna spełniać kilka ważnych punktów: +Dokumentacja jest przeznaczona przede wszystkim dla osób, które zapoznają się z tematem. Dlatego powinna spełniać kilka ważnych punktów: -- Zacznij od prostych i ogólnych tematów. Przejdź do bardziej zaawansowanych tematów na końcu -- Staraj się wyjaśnić temat tak jasno, jak to możliwe. Na przykład, spróbuj najpierw wytłumaczyć temat koledze. -- Podawaj tylko te informacje, które użytkownik rzeczywiście musi znać dla danego tematu -- Upewnij się, że informacje są dokładne. Testuj każdy kod -- Bądź zwięzły - skróć to, co piszesz o połowę. A potem nie krępuj się zrobić tego ponownie -- Oszczędnie używaj wyróżnień, od pogrubionych czcionek po ramki typu `.[note]` -- Stosuj się do [Standardów Kodowania |Coding Standard] w kodzie +- Zacznij od prostego i ogólnego. Do bardziej zaawansowanych tematów przejdź dopiero na końcu +- Staraj się jak najlepiej wyjaśnić sprawę. Spróbuj na przykład najpierw wyjaśnić temat koledze +- Podawaj tylko te informacje, które użytkownik rzeczywiście potrzebuje wiedzieć na dany temat +- Sprawdź, czy twoje informacje są rzeczywiście prawdziwe. Każdy kod przetestuj +- Bądź zwięzły - to, co napiszesz, skróć o połowę. A potem spokojnie jeszcze raz +- Oszczędzaj na wszelkiego rodzaju wyróżnieniach, od pogrubienia po ramki jak `.[note]` +- W kodach przestrzegaj [Standard kodowania |Coding Standard] -Ucz się również [składni |syntax]. Aby uzyskać podgląd artykułu podczas pisania, możesz użyć [edytora |https://editor.nette.org/] podglądu. +Opanuj również [składnia |syntax]. Do podglądu artykułu podczas jego pisania możesz użyć [edytor z podglądem |https://editor.nette.org/]. -Mutacje językowe .[#toc-language-mutations] -------------------------------------------- +Wersje językowe +--------------- -Angielski jest językiem podstawowym, więc twoje zmiany powinny być w języku angielskim. Jeśli angielski nie jest twoją mocną stroną, użyj [DeepL Translator |https://www.deepl.com/translator] i inni sprawdzą twój tekst. +Podstawowym językiem jest angielski, Twoje zmiany powinny więc być w języku czeskim i angielskim. Jeśli angielski nie jest Twoją mocną stroną, użyj [DeepL Translator |https://www.deepl.com/translator], a inni sprawdzą Twój tekst. -Tłumaczenie na inne języki zostanie wykonane automatycznie po zatwierdzeniu i dopracowaniu twojej edycji. +Tłumaczenie na inne języki zostanie wykonane automatycznie po zatwierdzeniu i dopracowaniu Twojej modyfikacji. -Trywialne edycje .[#toc-trivial-edits] --------------------------------------- +Trywialne poprawki +------------------ -Aby wnieść swój wkład w dokumentację, musisz mieć konto na [GitHubie |https://github.com]. +Aby współtworzyć dokumentację, niezbędne jest posiadanie konta na [GitHub|https://github.com]. -Najłatwiejszym sposobem na dokonanie niewielkiej zmiany w dokumentacji jest użycie linków na końcu każdej strony: +Najprostszym sposobem na wprowadzenie drobnej zmiany w dokumentacji jest skorzystanie z linków na końcu każdej strony: -- *Show on GitHub* otwiera wersję źródłową strony na GitHubie. Następnie wystarczy nacisnąć przycisk `E` i można rozpocząć edycję (trzeba być zalogowanym na GitHubie) -- *Open preview* otwiera edytor, w którym można od razu zobaczyć ostateczną formę wizualną +- *Ukaž na GitHubu* otworzy źródłową postać danej strony na GitHubie. Następnie wystarczy nacisnąć przycisk `E` i można zacząć edytować (konieczne jest bycie zalogowanym na GitHubie) +- *Otevři náhled* otworzy edytor, w którym od razu widzisz również wynikowy wygląd wizualny -Ponieważ [edytor |https://editor.nette.org/] podglądu nie ma możliwości zapisywania zmian bezpośrednio na GitHubie, należy skopiować tekst źródłowy do schowka (za pomocą przycisku *Copy to clipboard*), a następnie wkleić go do edytora na GitHubie. -Poniżej pola edycyjnego znajduje się formularz do przesłania. Tutaj nie zapomnij krótko podsumować i wyjaśnić powodu swojej edycji. Po przesłaniu tworzony jest tzw. pull request (PR), który można dalej edytować. +Ponieważ [edytor z podglądem |https://editor.nette.org/] nie ma możliwości zapisywania zmian bezpośrednio na GitHubie, konieczne jest po zakończeniu edycji skopiowanie tekstu źródłowego do schowka (przyciskiem *Copy to clipboard*), a następnie wklejenie go do edytora na GitHubie. Pod polem edycyjnym znajduje się formularz do wysłania. Tutaj nie zapomnij krótko podsumować i wyjaśnić powód swojej modyfikacji. Po wysłaniu powstanie tzw. pull request (PR), który można dalej edytować. -Większe edycje .[#toc-larger-edits] ------------------------------------ +Większe poprawki +---------------- -Bardziej właściwe jest zapoznanie się z podstawami pracy z systemem kontroli wersji Git, niż poleganie wyłącznie na interfejsie GitHub. Jeśli nie jesteś zaznajomiony z Gitem, możesz zapoznać się z [git - prostym przewodnikiem |https://rogerdudler.github.io/git-guide/] i rozważyć użycie jednego z wielu dostępnych [klientów graficznych |https://git-scm.com/downloads/guis]. +Bardziej odpowiednie niż korzystanie z interfejsu GitHubu jest zapoznanie się z podstawami pracy z systemem kontroli wersji Git. Jeśli nie znasz pracy z Gitem, możesz zapoznać się z przewodnikiem [git - the simple guide |https://rogerdudler.github.io/git-guide/] i ewentualnie skorzystać z jednego z wielu [klientów graficznych |https://git-scm.com/downloads/guis]. -Edytuj dokumentację w następujący sposób: +Dokumentację modyfikuj w ten sposób: 1) na GitHubie utwórz [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] repozytorium [nette/docs |https://github.com/nette/docs] -2) [sklonuj |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] to repozytorium na swój komputer -3) następnie wprowadź zmiany w [odpowiedniej gałęzi |#Documentation Structure] -4) sprawdzić, czy w tekście nie ma dodatkowych spacji za pomocą narzędzia [Code-Checker |code-checker:] -5) zapisz (commit) zmiany -6) jeśli jesteś zadowolony ze zmian, wepchnij je na GitHub do swojego forka -7) stamtąd prześlij je do repozytorium `nette/docs` tworząc [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) +2) to repozytorium [sklonujesz |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] na swój komputer +3) następnie w [odpowiedniej gałęzi |#Struktura dokumentacji] wprowadź zmiany +4) sprawdź zbędne spacje w tekście za pomocą narzędzia [Code-Checker |code-checker:] +4) zmiany zapisz (commituj) +6) jeśli jesteś zadowolony ze zmian, wyślij (pushnij) je na GitHub do swojego forka +7) stamtąd wyślij je do repozytorium `nette/docs`, tworząc [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) -Powszechne jest otrzymywanie komentarzy z sugestiami. Śledź proponowane zmiany i włączaj je. Dodaj sugerowane zmiany jako nowe commity i wyślij je ponownie do GitHub. Nigdy nie twórz nowego pull requesta tylko po to, by zmodyfikować istniejący. +Jest to normalne, że będziesz otrzymywać komentarze z uwagami. Śledź proponowane zmiany i wprowadź je. Proponowane zmiany dodaj jako nowe commity i ponownie wyślij na GitHub. Nigdy nie twórz nowego pull requesta w celu modyfikacji istniejącego pull requesta. -Struktura dokumentacji .[#toc-documentation-structure] ------------------------------------------------------- +Struktura dokumentacji +---------------------- -Cała dokumentacja znajduje się na GitHubie w repozytorium [nette/docs |https://github.com/nette/docs]. Aktualna wersja znajduje się w gałęzi master, natomiast starsze wersje znajdują się w gałęziach takich jak `doc-3.x`, `doc-2.x`. +Cała dokumentacja znajduje się na GitHubie w repozytorium [nette/docs |https://github.com/nette/docs]. Aktualna wersja jest w gałęzi master, starsze wersje znajdują się w gałęziach takich jak `doc-3.x`, `doc-2.x`. -Zawartość każdej gałęzi podzielona jest na główne foldery reprezentujące poszczególne obszary dokumentacji. Na przykład `application/` odpowiada https://doc.nette.org/en/application, `latte/` odpowiada https://latte.nette.org, itd. Każdy z tych folderów zawiera podfoldery reprezentujące mutacje językowe (`cs`, `en`, ...) oraz opcjonalnie podfolder `files` z obrazkami, które można wstawić na strony w dokumentacji. +Zawartość każdej gałęzi dzieli się na główne foldery reprezentujące poszczególne obszary dokumentacji. Na przykład `application/` odpowiada https://doc.nette.org/cs/application, `latte/` odpowiada https://latte.nette.org itd. Każdy taki folder zawiera podfoldery reprezentujące wersje językowe (`cs`, `en`, ...) oraz ewentualnie podfolder `files` z obrazkami, które można wstawiać do stron w dokumentacji. diff --git a/contributing/pl/syntax.texy b/contributing/pl/syntax.texy index cc0e3734a8..08b4227f42 100644 --- a/contributing/pl/syntax.texy +++ b/contributing/pl/syntax.texy @@ -1,59 +1,59 @@ -Składnia Wiki -************* +Składnia dokumentacji +********************* -Wiki używa [składni |https://texy.info/en/syntax] Markdown i [Texy |https://texy.info/en/syntax] z pewnymi rozszerzeniami. +Dokumentacja używa składni Markdown & [składni Texy |https://texy.nette.org/syntax] z niektórymi rozszerzeniami. -Linki .[#toc-links] -=================== +Linki +===== -Dla linków wewnętrznych stosuje się zapis w nawiasach kwadratowych `[odkaz]`. Albo w formie z pionowym paskiem `[text odkazu |cíl odkazu]`, lub w skrócie `[text odkazu]`jeśli cel jest identyczny z tekstem (po przekształceniu na małe litery i myślniki): +Do linków wewnętrznych używa się zapisu w nawiasach kwadratowych `[link |odkaz]`. I to albo w postaci z pionową kreską `[tekst linku |cíl odkazu]`, albo skróconej `[tekst linku |text odkazu]`, jeśli cel jest zgodny z tekstem (po transformacji na małe litery i myślniki): -- `[Page name]` -> `<a href="/en/page-name">Page name</a>` -- `[link text |Page name]` -> `<a href="/en/page-name">link text</a>` +- `[Page name |Page name]` -> `<a href="/en/page-name">Page name</a>` +- `[tekst linku |Page name]` -> `<a href="/en/page-name">link text</a>` -Możemy linkować do innego języka lub innego działu. Sekcja oznacza bibliotekę Nette (np. `forms`, `latte`, itd.) lub sekcje specjalne jak `best-practices`, `quickstart`, itd: +Możemy linkować do innej wersji językowej lub do innej sekcji. Sekcją rozumie się bibliotekę Nette (np. `forms`, `latte`, itp.) lub specjalne sekcje jak `best-practices`, `quickstart` itd.: -- `[cs:Page name]` -> `<a href="/en/page-name">Page name</a>` (ta sama sekcja, inny język) -- `[tracy:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (inny dział, ten sam język) -- `[tracy:cs:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (inny dział i język) +- `[cs:Page name |cs:Page name]` -> `<a href="/cs/page-name">Page name</a>` (ta sama sekcja, inny język) +- `[tracy:Page name |tracy:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (inna sekcja, ten sam język) +- `[tracy:cs:Page name |tracy:cs:Page name]` -> `<a href="//tracy.nette.org/cs/page-name">Page name</a>` (inna sekcja i język) -Możesz również użyć `#`, aby skierować się do konkretnego nagłówka na stronie. +Za pomocą `#` można również celować w konkretny nagłówek na stronie. -- `[#Heading]` -> `<a href="#toc-heading">Heading</a>` (nagłówek na bieżącej stronie) -- `[Page name#Heading]` -> `<a href="/en/page-name#toc-heading">Page name</a>` +- `[Heading |#Heading]` -> `<a href="#toc-heading">Heading</a>` (nagłówek na bieżącej stronie) +- `[Page name#Heading |Page name#Heading]` -> `<a href="/en/page-name#toc-heading">Page name</a>` -Link do strony głównej sekcji: (`@home` to specjalne określenie dla strony głównej sekcji) +Link do strony głównej sekcji: (`@home` to specjalne wyrażenie dla strony głównej sekcji) -- `[link text |@home]` -> `<a href="/en/">link text</a>` -- `[link text |tracy:]` -> `<a href="//tracy.nette.org/en/">link text</a>` +- `[tekst linku |@home]` -> `<a href="/en/">link text</a>` +- `[tekst linku |tracy:]` -> `<a href="//tracy.nette.org/en/">link text</a>` -Linki do dokumentacji API .[#toc-odkazy-do-api-dokumentace] ------------------------------------------------------------ +Linki do dokumentacji API +------------------------- -Zawsze odwołuj się wyłącznie do tego zapisu: +Zawsze podawaj tylko za pomocą tego zapisu: - `[api:Nette\SmartObject]` -> [api:Nette\SmartObject] - `[api:Nette\Forms\Form::setTranslator()]` -> [api:Nette\Forms\Form::setTranslator()] - `[api:Nette\Forms\Form::$onSubmit]` -> [api:Nette\Forms\Form::$onSubmit] - `[api:Nette\Forms\Form::Required]` -> [api:Nette\Forms\Form::Required] -Używaj w pełni kwalifikowanych nazw tylko w pierwszej wzmiance. Do dalszych odniesień używaj uproszczonej nazwy: +Pełne kwalifikowane nazwy używaj tylko przy pierwszej wzmiance. Do kolejnych linków użyj uproszczonej nazwy: - `[Form::setTranslator() |api:Nette\Forms\Form::setTranslator()]` -> [Form::setTranslator() |api:Nette\Forms\Form::setTranslator()] -Linki do dokumentacji PHP .[#toc-odkazy-do-php-dokumentace] ------------------------------------------------------------ +Linki do dokumentacji PHP +------------------------- - `[php:substr]` -> [php:substr] -Kod źródłowy .[#toc-links-to-php-documentation] -=============================================== +Kod źródłowy +============ -Blok kodu zaczyna się od <code>```lang</code> i kończy na <code>```</code>. Obsługiwane języki to: `php`, `latte`, `neon`, `html`, `css`, `js` oraz `sql`. Zawsze używaj tabulatorów do wcięć. +Blok kodu zaczyna się od <code>```lang</code> i kończy <code>```</code>. Obsługiwane języki to `php`, `latte`, `neon`, `html`, `css`, `js` i `sql`. Do wcięć zawsze używaj tabulatorów. ``` ```php @@ -63,7 +63,7 @@ Blok kodu zaczyna się od <code>```lang</code> i kończy na <code>&# ``` ``` -Możesz również podać nazwę pliku jak <code>```php .{file: ArrayTest.php}</code> i blok kodu zostanie wyrenderowany w ten sposób: +Możesz również podać nazwę pliku jako <code>```php .{file: ArrayTest.php}</code>, a blok kodu zostanie wyrenderowany w ten sposób: ```php .{file: ArrayTest.php} public function renderPage($id) @@ -72,71 +72,71 @@ public function renderPage($id) ``` -Nagłówki .[#toc-headings] -========================= +Nagłówki +======== -Podkreśl gwiazdkami górny nagłówek (czyli tytuł strony). Użyj nawiasów, aby oddzielić sekcje. Podkreśl nagłówki za pomocą nawiasów klamrowych, a następnie myślników: +Najwyższy nagłówek (czyli tytuł strony) podkreśl gwiazdkami. Do oddzielenia sekcji używaj znaków równości. Nagłówki podkreślaj znakami równości, a następnie myślnikami: ``` -MVC Aplikace & presentery .[#toc-mvc-applications-presenters] -************************************************************* +Aplikacje MVC i presentery +************************** ... -Tvorba odkazů .[#toc-link-creation] -=================================== +Tworzenie linków +================ ... -Odkazy v šablonách .[#toc-links-in-templates] ---------------------------------------------- +Linki w szablonach +------------------ ... ``` -Ramy i style .[#toc-boxes-and-styles] -===================================== +Ramki i style +============= -Perex zostanie oznaczony klasą `.[perex]` .[perex] +Perex oznaczamy klasą `.[perex]` .[perex] -Oznacz notatkę klasą `.[note]` .[note] +Notatkę oznaczamy klasą `.[note]` .[note] -Oznaczyć podpowiedź klasą `.[tip]` .[tip] +Wskazówkę oznaczamy klasą `.[tip]` .[tip] -Oznaczaj ostrzeżenia klasą `.[caution]` .[caution] +Ostrzeżenie oznaczamy klasą `.[caution]` .[caution] -Silniejsze ostrzeżenia są oznaczone klasą `.[warning]` .[warning] +Mocniejsze ostrzeżenie oznaczamy klasą `.[warning]` .[warning] Numer wersji `.{data-version:2.4.10}` .{data-version:2.4.10} -Napisz klasy przed linią: +Klasy zapisuj przed linią: ``` .[perex] -Tohle je perex. +To jest perex. ``` -Należy pamiętać, że ramki np. `.[tip]` "ciągnie" za sobą oczy, dzięki czemu są one używane do podkreślania, a nie do mniej istotnych informacji. Dlatego w miarę możliwości należy używać ich oszczędnie. +Proszę pamiętać, że ramki takie jak `.[tip]` "przyciągają" wzrok, dlatego używa się ich do podkreślenia, a nie do mniej istotnych informacji. Dlatego ich użyciem maksymalnie oszczędzaj. -Treść .[#toc-table-of-contents] -=============================== +Spis treści +=========== -Treść (linki w prawym menu) jest generowana automatycznie dla wszystkich stron, których rozmiar przekracza 4000 bajtów, a to domyślne zachowanie można zmienić za pomocą [meta tagu |#Meta-Tags] `{{toc}}`. Tekst tworzący treść jest domyślnie pobierany bezpośrednio z tekstu nagłówków, ale przy użyciu modyfikatora `.{toc}` modyfikator, możliwe jest wyświetlenie innego tekstu w spisie treści, co jest szczególnie przydatne w przypadku dłuższych nagłówków. +Spis treści (linki w prawym menu) jest automatycznie generowany dla wszystkich stron, których rozmiar przekroczy 4 000 bajtów, przy czym to domyślne zachowanie można zmodyfikować za pomocą [#znaczniki meta] `{{toc}}`. Tekst tworzący spis treści jest standardowo brany bezpośrednio z tekstu nagłówków, ale za pomocą modyfikatora `.{toc}` można wyświetlić w spisie treści inny tekst, co przydaje się głównie przy dłuższych nagłówkach. ``` -Dlouhý a inteligentní nadpis .{toc: Libovolný jiný text zobrazený v obsahu} -=========================================================================== +Długi i inteligentny nagłówek .{toc: Dowolny inny tekst wyświetlany w spisie treści} +==================================================================================== ``` -Meta tagi .[#toc-meta-tags] -=========================== +Znaczniki meta +============== -- ustawienie własnej nazwy strony (w `<title>` i nawigacji breadcrumb) `{{title: Jiný název}}` -- przekierowanie `{{redirect: pla:cs}}` - zobacz [linki |#Links] -- wykonanie `{{toc}}` lub wyłączenie `{{toc: no}}` zawartość automatyczna (ramka z linkami do poszczególnych nagłówków) +- ustawienie własnego tytułu strony (w `<title>` i nawigacji okruszkowej) `{{title: Inny tytuł}}` +- przekierowanie `{{redirect: pla:cs}}` - zobacz [#linki] +- wymuszenie `{{toc}}` lub zakazanie `{{toc: no}}` automatycznego spisu treści (ramka z linkami do poszczególnych nagłówków) {{priority: -1}} diff --git a/contributing/pt/@home.texy b/contributing/pt/@home.texy index ae3f45ca9a..a1e6b6466d 100644 --- a/contributing/pt/@home.texy +++ b/contributing/pt/@home.texy @@ -1,17 +1,17 @@ -Torne-se um contribuinte da Nette +Torne-se um contribuidor do Nette ********************************* .[perex] -Dê uma olhada em como você pode se envolver em nosso projeto open source. Conheça os passos para contribuir com o código fonte e a documentação, e junte-se à rede de desenvolvedores que se dedicam a melhorar a Nette. +Descubra como você pode se envolver em nosso projeto de código aberto. Aprenda os procedimentos para contribuir com o código-fonte e a documentação e faça parte da comunidade de desenvolvedores que participam ativamente no aprimoramento do Nette. -**Código*** +**Código** -- [Contribuindo para o código |code] -- [Normas de Codificação |coding-standard] +- [Como contribuir para o código? |code] +- [Padrão de codificação |coding-standard] **Documentação** -- [Contribuição para a documentação |documentation] +- [Como contribuir para a documentação? |documentation] - [Sintaxe da documentação |syntax] -- "Preview editor":https://editor.nette.org +- "Editor de pré-visualização":https://editor.nette.org diff --git a/contributing/pt/@left-menu.texy b/contributing/pt/@left-menu.texy index 029de52346..bca571a642 100644 --- a/contributing/pt/@left-menu.texy +++ b/contributing/pt/@left-menu.texy @@ -1,10 +1,10 @@ Código ****** -- [Contribuindo para o código |code] -- [Normas de Codificação |coding-standard] +- [Como contribuir para o código? |code] +- [Padrão de codificação |coding-standard] Documentação ************ -- [Contribuição para a documentação |documentation] +- [Como contribuir para a documentação? |documentation] - [Sintaxe da documentação |syntax] -- "Preview editor":https://editor.nette.org +- "Editor de pré-visualização":https://editor.nette.org diff --git a/contributing/pt/code.texy b/contributing/pt/code.texy index 72f3a22d40..2a264be5cf 100644 --- a/contributing/pt/code.texy +++ b/contributing/pt/code.texy @@ -1,87 +1,87 @@ -Contribuindo para o Código -************************** +Como contribuir para o código +***************************** .[perex] -Você está planejando contribuir para a Estrutura Nette e precisa se familiarizar com as regras e procedimentos? Este guia para iniciantes irá guiá-lo através dos passos para contribuir efetivamente com o código, trabalhar com os repositórios e implementar mudanças. +Você está prestes a contribuir para o Nette Framework e precisa se orientar sobre as regras e procedimentos? Este guia para iniciantes mostrará passo a passo como contribuir eficazmente para o código, trabalhar com repositórios e implementar alterações. -Procedimento .[#toc-procedure] -============================== +Procedimento +============ -Para contribuir com o código, é essencial ter uma conta no [GitHub |https://github.com] e estar familiarizado com os fundamentos do trabalho com o sistema de controle de versões Git. Se você não está familiarizado com Git, você pode verificar o [git - o guia simples |https://rogerdudler.github.io/git-guide/] e considerar o uso de um dos muitos [clientes gráficos |https://git-scm.com/downloads/guis]. +Para contribuir para o código, é essencial ter uma conta no [GitHub|https://github.com] e estar familiarizado com os fundamentos do trabalho com o sistema de controle de versão Git. Se você não domina o trabalho com o Git, pode consultar o guia [git - the simple guide |https://rogerdudler.github.io/git-guide/] e, opcionalmente, usar um dos muitos [clientes gráficos |https://git-scm.com/downloads/guis]. -Preparação do Meio Ambiente e Repositório .[#toc-preparing-the-environment-and-repository] ------------------------------------------------------------------------------------------- +Preparação do ambiente e do repositório +--------------------------------------- -1) No GitHub, crie um [garfo |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] do [repositório de pacotes |www:packages] que você pretende modificar -2) [Clonar |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] este repositório em seu computador -3) Instale as dependências, incluindo o [Nette Tester |tester:], usando o comando `composer install` -4) Verificar se os testes estão funcionando `composer tester` -5) Criar uma [nova filial |#New Branch] com base na última versão lançada +1) No GitHub, crie um [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] do repositório do [pacote |www:packages] que você pretende modificar. +2) [Clone |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] este repositório para o seu computador. +3) Instale as dependências, incluindo o [Nette Tester |tester:], utilizando o comando `composer install`. +4) Verifique se os testes funcionam executando `composer tester`. +5) Crie um [#novo branch] baseado na última versão lançada. -Implementando suas próprias mudanças .[#toc-implementing-your-own-changes] --------------------------------------------------------------------------- +Implementação das suas próprias alterações +------------------------------------------ -Agora você pode fazer seus próprios ajustes de código: +Agora você pode fazer as suas próprias modificações no código: -1) Implementar as mudanças desejadas e não esquecer os testes -2) Certifique-se de que os testes sejam executados com sucesso usando `composer tester` -3) Verificar se o código atende às [normas de codificação |#coding standards] -4) Salvar (comprometer) as mudanças com uma descrição [neste formato |#Commit Description] +1) Implemente as alterações desejadas e não se esqueça dos testes. +2) Certifique-se de que os testes são executados com sucesso, utilizando `composer tester`. +3) Verifique se o código cumpre os [#padrões de codificação]. +4) Salve (commit) as alterações com uma descrição [neste formato |#Descrição do commit]. -Você pode criar vários compromissos, um para cada passo lógico. Cada compromisso deve ser significativo por si só. +Você pode criar vários commits, um para cada passo lógico. Cada commit deve ser significativo por si só. -Submetendo mudanças .[#toc-submitting-changes] ----------------------------------------------- +Envio das alterações +-------------------- -Uma vez satisfeitos com as mudanças, você pode apresentá-las: +Assim que estiver satisfeito com as alterações, pode enviá-las: -1) Empurre as mudanças no GitHub para o seu garfo -2) A partir daí, submetê-los ao repositório Nette, criando uma [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) -3) Forneça [informações suficientes |#pull request description] na descrição +1) Envie (push) as alterações para o GitHub no seu fork. +2) A partir daí, envie-as para o repositório Nette criando um [pull request|https://help.github.com/articles/creating-a-pull-request] (PR). +3) Forneça [informações suficientes |#Descrição do pull request] na descrição. -Incorporando Feedback .[#toc-incorporating-feedback] ----------------------------------------------------- +Incorporação de comentários +--------------------------- -Seus compromissos agora são visíveis para os outros. É comum receber comentários com sugestões: +Os seus commits serão agora vistos por outros. É comum receber comentários com sugestões: -1) Acompanhe as mudanças propostas -2) Incorporá-los como novos compromissos ou [fundi-los com os anteriores |https://help.github.com/en/github/using-git/about-git-rebase] -3) Reenviar os compromissos ao GitHub, e eles aparecerão automaticamente no pedido de puxar +1) Acompanhe as modificações propostas. +2) Incorpore-as como novos commits ou [faça rebase com os anteriores |https://help.github.com/en/github/using-git/about-git-rebase]. +3) Envie novamente os commits para o GitHub e eles aparecerão automaticamente no pull request. -Nunca criar um novo pedido de puxar para modificar um já existente. +Nunca crie um novo pull request para modificar um existente. -Documentação .[#toc-documentation] ----------------------------------- +Documentação +------------ -Se você mudou de funcionalidade ou adicionou uma nova, não se esqueça de [adicioná-la |documentation] também [à documentação |documentation]. +Se você alterou a funcionalidade ou adicionou uma nova, não se esqueça de a [adicionar também à documentação |documentation]. -Nova filial .[#toc-new-branch] -============================== +Novo branch +=========== -Se possível, faça alterações em relação à última versão lançada, ou seja, a última tag no ramo. Para a tag v3.2.1, criar um ramo usando este comando: +Se possível, faça as alterações em relação à última versão lançada, ou seja, a última tag no branch correspondente. Para a tag `v3.2.1`, crie um branch com este comando: ```shell git checkout -b new_branch_name v3.2.1 ``` -Normas de Codificação .[#toc-coding-standards] -============================================== +Padrões de Codificação +====================== -Seu código deve atender ao [padrão de codificação |coding standard] utilizado no Nette Framework. Há uma ferramenta automática disponível para verificar e fixar o código. Você pode instalá-lo **globalmente** através do Composer em uma pasta de sua escolha: +O seu código deve cumprir os [padrões de codificação |coding-standard] utilizados no Nette Framework. Existe uma ferramenta automática disponível para verificar e corrigir o código. Pode ser instalada via Composer **globalmente** na pasta da sua escolha: ```shell composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Agora você deve ser capaz de executar a ferramenta no terminal. O primeiro comando verifica e o segundo conserta o código nas pastas `src` e `tests` no diretório atual: +Agora você deve conseguir executar a ferramenta no terminal. O primeiro comando verifica e o segundo também corrige o código nas pastas `src` e `tests` no diretório atual: ```shell /path/to/nette-coding-standard/ecs check @@ -89,29 +89,29 @@ Agora você deve ser capaz de executar a ferramenta no terminal. O primeiro coma ``` -Descrição do compromisso .[#toc-commit-description] -=================================================== +Descrição do commit +=================== -Em Nette, os assuntos de compromisso têm o seguinte formato: `Presenter: fixed AJAX detection [Closes #69]` +No Nette, os assuntos dos commits têm o formato: `Presenter: fixed AJAX detection [Closes #69]` -- área seguida por um cólon -- objetivo do compromisso no passado; se possível, comece com palavras como: added, fixed, refactored, changed, removed -- se o compromisso quebra a compatibilidade para trás, adicionar "BC break" -- qualquer conexão com o rastreador de problemas, como `(#123)` ou `[Closes #69]` -- após o assunto, pode haver uma linha em branco seguida por uma descrição mais detalhada, incluindo, por exemplo, links para o fórum +- área seguida por dois pontos +- propósito do commit no tempo passado; se possível, comece com uma palavra como: "added" (nova funcionalidade adicionada), "fixed" (correção), "refactored" (alteração no código sem alteração de comportamento), "changed", "removed" +- se o commit quebrar a compatibilidade retroativa, adicione "BC break" +- possível vínculo com o gestor de issues como `(#123)` ou `[Closes #69]` +- após o assunto, pode seguir uma linha em branco e depois uma descrição mais detalhada, incluindo, por exemplo, links para o fórum -Descrição do Pedido de Puxar .[#toc-pull-request-description] -============================================================= +Descrição do pull request +========================= -Ao criar um pedido de puxar, a interface GitHub permitirá que você insira um título e uma descrição. Forneça um título conciso e inclua o máximo de informações possíveis na descrição sobre os motivos de sua mudança. +Ao criar um pull request, a interface do GitHub permitirá que você insira um título e uma descrição. Forneça um título conciso e, na descrição, forneça o máximo de informações possível sobre os motivos da sua alteração. -Além disso, especificar no cabeçalho se é uma nova característica ou uma correção de bug e se pode causar problemas de retrocompatibilidade (BC break). Se houver um problema relacionado, estabeleça um link com ele para que seja fechado após a aprovação do pedido de puxar. +Também será exibido um cabeçalho onde você especifica se é uma nova funcionalidade ou correção de erro e se pode haver quebra de compatibilidade retroativa (BC break). Se houver um problema relacionado (issue), crie um link para ele para que seja fechado após a aprovação do pull request. ``` -- bug fix / new feature? <!-- #issue numbers, if any --> +- bug fix / new feature? <!-- #números das issues, se houver --> - BC break? yes/no -- doc PR: nette/docs#? <!-- highly welcome, see https://nette.org/en/writing --> +- doc PR: nette/docs#? <!-- altamente bem-vindo, veja https://nette.org/en/writing --> ``` diff --git a/contributing/pt/coding-standard.texy b/contributing/pt/coding-standard.texy index 0a3eaec9d3..fcc737696b 100644 --- a/contributing/pt/coding-standard.texy +++ b/contributing/pt/coding-standard.texy @@ -1,44 +1,44 @@ -Padrão de codificação -********************* +Padrões de Codificação +********************** -Este documento descreve as regras e recomendações para o desenvolvimento da Nette. Ao contribuir com o código para Nette, você deve segui-las. A maneira mais fácil de fazer isso é imitar o código existente. -A idéia é fazer com que todo o código pareça ter sido escrito por uma só pessoa. .[perex] +.[perex] +Este documento descreve as regras e recomendações para o desenvolvimento do Nette. Ao contribuir com código para o Nette, você deve segui-las. A forma mais fácil de o fazer é imitar o código existente. O objetivo é fazer com que todo o código pareça ter sido escrito por uma única pessoa. -Nette Coding Standard corresponde ao [PSR-12 Extended Coding Style |https://www.php-fig.org/psr/psr-12/] com duas exceções principais: utiliza [abas em vez de espaços |#tabs instead of spaces] para recuo, e utiliza [PascalCase para constantes de classe |https://blog.nette.org/pt/por-menos-gritos-no-codigo]. +Os Padrões de Codificação Nette correspondem ao [PSR-12 Extended Coding Style |https://www.php-fig.org/psr/psr-12/] com duas exceções principais: utiliza [#tabulações em vez de espaços] para indentação e utiliza [PascalCase para constantes de classe|https://blog.nette.org/en/less-noise-in-code]. -Regras gerais .[#toc-general-rules] -=================================== +Regras gerais +============= -- Todo arquivo PHP deve conter `declare(strict_types=1)` -- Duas linhas vazias são usadas para separar métodos para uma melhor legibilidade. -- O motivo do uso do operador de fechamento deve ser documentado: `@mkdir($dir); // @ - directory may exist` -- Se for usado um operador de comparação de digitação fraca (ou seja, `==`, `!=`, ...), a intenção deve ser documentada: `// == to accept null` -- Você pode escrever mais exceções em um arquivo `exceptions.php` -- A visibilidade dos métodos não é especificada para as interfaces, pois elas são sempre públicas. -- Cada propriedade, valor de retorno e parâmetro deve ter um tipo especificado. Por outro lado, para as constantes finais, nunca especificamos o tipo porque ele é óbvio. -- Citações simples devem ser usadas para delimitar a cadeia, exceto quando o próprio literal contém apóstrofos. +- Cada arquivo PHP deve conter `declare(strict_types=1)` +- Duas linhas em branco são usadas para separar métodos para melhor legibilidade. +- O motivo para usar o operador shut-up (@) deve ser documentado: `@mkdir($dir); // @ - o diretório pode já existir`. +- Se for usado um operador de comparação de tipo fraco (ou seja, `==`, `!=`, ...), a intenção deve ser documentada: `// == aceita null` +- Você pode escrever várias exceções num único arquivo `exceptions.php`. +- A visibilidade do método não é especificada para interfaces, pois são sempre públicas. +- Cada propriedade, valor de retorno e parâmetro deve ter um tipo especificado. Por outro lado, nunca especificamos o tipo para constantes finais (`final const`), pois é óbvio. +- Aspas simples devem ser usadas para delimitar strings, exceto quando o próprio literal contém apóstrofos. -Convenções de Nomeação .[#toc-naming-conventions] -================================================= +Convenções de Nomenclatura +========================== -- Evite usar abreviações, a menos que o nome completo seja excessivo. -- Use maiúsculas para abreviações de duas letras, e pascal/camelo para abreviações mais longas. -- Use um substantivo ou frase de substantivo para nome de classe. -- Os nomes de classe devem conter não somente especificidade (`Array`), mas também generalidade (`ArrayIterator`). A exceção são os atributos PHP. -- "Constantes de classe e enumeração devem usar PascalCaps":https://blog.nette.org/pt/por-menos-gritos-no-codigo. -- "Interfaces e classes abstratas não devem conter prefixos ou postfixos":https://blog.nette.org/pt/prefixos-e-sufixos-nao-pertencem-a-nomes-de-interface como `Abstract`, `Interface` ou `I`. +- Não use abreviações, a menos que o nome completo seja muito longo. +- Use letras maiúsculas para abreviações de duas letras, Pascal/CamelCase para abreviações mais longas. +- Use um substantivo ou frase nominal para o nome da classe. +- Os nomes das classes devem conter não apenas a especificidade (`Array`), mas também a generalidade (`ArrayIterator`). Exceções são atributos da linguagem PHP. +- "Constantes de classe e enums devem usar PascalCaps":https://blog.nette.org/en/less-noise-in-code. +- "Interfaces e classes abstratas não devem conter prefixos ou sufixos":https://blog.nette.org/pt/prefixes-and-suffixes-do-not-belong-in-interface-names como `Abstract`, `Interface` ou `I`. -Embalagem e suportes .[#toc-wrapping-and-braces] -================================================ +Quebra de Linha e Chaves +======================== -O Nette Coding Standard corresponde ao PSR-12 (ou PER Coding Style), em alguns pontos ele o especifica mais ou o modifica: +Os Padrões de Codificação Nette correspondem ao PSR-12 (ou PER Coding Style), em alguns pontos complementam-no ou modificam-no: -- As funções da seta são escritas sem um espaço antes do parêntese, ou seja `fn($a) => $b` -- não é necessária nenhuma linha vazia entre os diferentes tipos de declarações de importação `use` -- o tipo de retorno da função/método e o parêntese de abertura devem ser colocados em linhas separadas para uma melhor legibilidade: +- arrow functions são escritas sem espaço antes do parêntese, ou seja, `fn($a) => $b` +- não é necessária uma linha em branco entre diferentes tipos de declarações de importação `use` +- o tipo de retorno da função/método e a chave de abertura `{` estão sempre em linhas separadas: ```php public function find( @@ -50,24 +50,28 @@ O Nette Coding Standard corresponde ao PSR-12 (ou PER Coding Style), em alguns p } ``` +A chave de abertura `{` numa linha separada é importante para a separação visual da assinatura da função/método do corpo. Se a assinatura estiver numa única linha, a separação é clara (imagem à esquerda). Se estiver em várias linhas, no PSR as assinaturas e o corpo fundem-se (meio), enquanto no padrão Nette permanecem separados (direita): -Blocos de Documentação (phpDoc) .[#toc-documentation-blocks-phpdoc] -=================================================================== +[* new-line-after.webp *] -A regra principal: nunca duplicar nenhuma informação de assinatura como tipo de parâmetro ou tipo de retorno sem nenhum valor agregado. + +Blocos de Documentação (phpDoc) +=============================== + +Regra principal: Nunca duplique informações da assinatura, como o tipo do parâmetro ou o tipo de retorno, sem adicionar valor (por exemplo, uma descrição). Bloco de documentação para definição de classe: -- Começa por uma descrição de classe. -- Segue a linha vazia. -- As anotações `@property` (ou `@property-read`, `@property-write`) seguem, uma por linha. A sintaxe é: anotação, espaço, tipo, espaço, $name. -- Seguem as anotações `@method`, uma a uma linha. A sintaxe é: anotação, espaço, tipo de retorno, espaço, nome(tipo $param, ...). -- A anotação `@author` é omitida. A autoria é mantida em um histórico do código fonte. -- As anotações `@internal` ou `@deprecated` podem ser usadas. +- Começa com a descrição da classe. +- Seguido por uma linha em branco. +- Seguem-se as anotações `@property` (ou `@property-read`, `@property-write`), uma por linha. A sintaxe é: anotação, espaço, tipo, espaço, `$nome`. +- Seguem-se as anotações `@method`, uma por linha. A sintaxe é: anotação, espaço, tipo de retorno, espaço, `nome(tipo $param, ...)`. +- A anotação `@author` é omitida. A autoria é mantida no histórico do código-fonte. +- Podem ser usadas as anotações `@internal` ou `@deprecated`. ```php /** - * MIME message part. + * Parte da mensagem MIME. * * @property string $encoding * @property-read array $headers @@ -76,27 +80,27 @@ Bloco de documentação para definição de classe: */ ``` -O bloco de documentação para bens que contém apenas a anotação `@var` deve estar em uma única linha: +Um bloco de documentação para uma propriedade, que contém apenas a anotação `@var`, deve ser de linha única: ```php /** @var string[] */ private array $name; ``` -Bloco de documentação para definição do método: +Bloco de documentação para definição de método: -- Começa por uma breve descrição do método. -- Nenhuma linha vazia. -- As anotações `@param`, uma por linha. -- A anotação `@return`. -- As anotações `@throws`, uma a uma linha. -- As anotações `@internal` ou `@deprecated` podem ser usadas. +- Começa com uma breve descrição do método. +- Sem linha em branco entre a descrição e as anotações. +- Anotações `@param`, uma por linha. +- Anotação `@return`. +- Anotações `@throws`, uma por linha. +- Podem ser usadas as anotações `@internal` ou `@deprecated`. -Toda anotação é seguida por um espaço, exceto a `@param` que é seguida por dois espaços para uma melhor legibilidade. +Cada anotação (`@return`, `@throws`, etc.) é seguida por um espaço. A exceção é `@param`, que é seguida por dois espaços para melhor legibilidade. ```php /** - * Finds a file in directory. + * Encontra um arquivo no diretório. * @param string[] $options * @return string[] * @throws DirectoryNotFoundException @@ -105,21 +109,20 @@ public function find(string $dir, array $options): array ``` -Separadores em vez de Espaços .[#toc-tabs-instead-of-spaces] -============================================================ +Tabulações em Vez de Espaços +============================ -As abas têm várias vantagens sobre os espaços: +As tabulações têm várias vantagens sobre os espaços: -- O tamanho do travessão é personalizável em editores e "web":https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size. -- eles não impõem a preferência do tamanho do recuo do usuário ao código, portanto o código é mais portátil -- você pode digitá-las com um toque de tecla (em qualquer lugar, não apenas nos editores que transformam as abas em espaços) -- A indentação é seu propósito -- respeitar as necessidades dos colegas deficientes visuais e cegos +- o tamanho do recuo pode ser ajustado em editores e na "web":https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size +- não impõem a preferência de tamanho de indentação do utilizador ao código, tornando o código mais portátil +- podem ser digitadas com um único toque de tecla (em qualquer lugar, não apenas em editores que convertem tabulações em espaços) +- a indentação é o seu propósito +- respeitam as necessidades de colegas com deficiência visual e cegos -Ao utilizar abas em nossos projetos, permitimos a personalização da largura, que pode parecer desnecessária para a maioria das pessoas, mas é essencial para as pessoas com deficiências visuais. +Ao usar tabulações nos nossos projetos, permitimos o ajuste da largura, o que pode parecer supérfluo para a maioria das pessoas, mas é essencial para pessoas com deficiência visual. -Para programadores cegos que usam displays braille, cada espaço é representado por uma célula braille e ocupa um espaço valioso. Portanto, se o recuo padrão for de 4 espaços, um recuo de 3º nível desperdiça 12 células braile antes do início do código. -Em um display de 40 células, que é o mais comumente usado em laptops, isso é mais de um quarto das células disponíveis desperdiçadas sem nenhuma informação. +Para programadores cegos que usam displays Braille, cada espaço representa uma célula Braille. Portanto, se a indentação padrão for de 4 espaços, uma indentação de 3º nível desperdiça 12 valiosas células Braille antes mesmo do início do código. Num display de 40 células, que é o mais comum em portáteis, isso representa mais de um quarto das células disponíveis sendo desperdiçadas sem qualquer informação. {{priority: -1}} diff --git a/contributing/pt/documentation.texy b/contributing/pt/documentation.texy index 8a1b434f8c..4cf9f50cbb 100644 --- a/contributing/pt/documentation.texy +++ b/contributing/pt/documentation.texy @@ -1,69 +1,68 @@ -Contribuição para a documentação -******************************** +Como Contribuir para a Documentação +*********************************** .[perex] -Contribuir com a documentação é uma das atividades mais valiosas, pois ajuda os outros a entender a estrutura. +Contribuir para a documentação é uma das atividades mais gratificantes, pois ajuda outros a entender o framework. -Como Escrever? .[#toc-how-to-write] ------------------------------------ +Como Escrever? +-------------- -A documentação é destinada principalmente às pessoas que são novatas no assunto. Portanto, ela deve atender a vários pontos importantes: +A documentação destina-se principalmente a pessoas que estão a familiarizar-se com o tópico. Portanto, deve cumprir vários pontos importantes: -- Comece com temas simples e gerais. Passar para tópicos mais avançados no final -- Tente explicar o tópico da maneira mais clara possível. Por exemplo, tente explicar o tópico a um colega primeiro -- Fornecer apenas informações que o usuário realmente precisa saber para um determinado tópico -- Certifique-se de que suas informações sejam precisas. Teste cada código -- Seja conciso - corte o que você escreve pela metade. E depois sinta-se livre para fazê-lo novamente -- Use o destaque com moderação, desde fontes ousadas até molduras como `.[note]` -- Siga a [Norma de Codificação |Coding Standard] no código +- Comece pelo simples e geral. Avance para tópicos mais complexos apenas no final. +- Tente explicar o assunto da melhor forma possível. Por exemplo, tente explicar primeiro o tópico a um colega. +- Forneça apenas as informações que o utilizador realmente precisa saber sobre o tópico em questão. +- Verifique se as suas informações são realmente verdadeiras. Teste cada trecho de código. +- Seja conciso - reduza o que escreveu pela metade. E depois, se necessário, novamente. +- Use com moderação todos os tipos de destaque, desde negrito até caixas como `.[note]`. +- No código, siga os [Padrões de Codificação |coding-standard]. -Além disso, aprenda a [sintaxe |syntax]. Para uma prévia do artigo durante a redação, você pode usar o [editor de prévia |https://editor.nette.org/]. +Aprenda também a [sintaxe |syntax]. Para pré-visualizar o artigo enquanto o escreve, pode usar o [editor com pré-visualização |https://editor.nette.org/]. -Mutações de idiomas .[#toc-language-mutations] ----------------------------------------------- +Versões de Idioma +----------------- -O inglês é o idioma principal, portanto, suas mudanças devem ser em inglês. Se o inglês não for o seu forte, use [DeepL Tradutor |https://www.deepl.com/translator] e outros verificarão seu texto. +O idioma principal é o inglês. As suas alterações devem ser, portanto, em inglês. Se o inglês não for o seu forte, use o [DeepL Translator |https://www.deepl.com/translator] e outros irão rever o seu texto. -A tradução para outros idiomas será feita automaticamente após a aprovação e o ajuste fino de sua edição. +A tradução para outros idiomas será feita automaticamente após a aprovação e ajuste da sua modificação. -Edições triviais .[#toc-trivial-edits] --------------------------------------- +Modificações Triviais +--------------------- -Para contribuir com a documentação, você precisa ter uma conta no [GitHub |https://github.com]. +Para contribuir para a documentação, é essencial ter uma conta no [GitHub|https://github.com]. -A maneira mais fácil de fazer uma pequena mudança na documentação é usar os links no final de cada página: +A forma mais fácil de fazer uma pequena alteração na documentação é usar os links no final de cada página: -- *Show on GitHub* abre a versão de origem da página no GitHub. Depois basta pressionar o botão `E` e você pode começar a editar (você deve estar logado no GitHub) -- *Abrir visualização* abre um editor onde você pode ver imediatamente a forma visual final +- *Mostrar no GitHub* abre a versão do código-fonte da página no GitHub. Depois, basta pressionar o botão `E` para começar a editar (é necessário estar autenticado no GitHub). +- *Abrir pré-visualização* abre o editor, onde pode ver imediatamente a aparência visual resultante. -Como o [editor de visualização |https://editor.nette.org/] não tem a capacidade de salvar as mudanças diretamente no GitHub, você precisa copiar o texto fonte para a área de transferência (usando o botão *Copy to clipboard*) e depois colá-lo no editor no GitHub. -Abaixo do campo de edição, há um formulário para submissão. Aqui, não se esqueça de resumir e explicar brevemente o motivo de sua edição. Após a submissão, é criado um chamado pull request (PR), que pode ser editado posteriormente. +Como o [editor com pré-visualização |https://editor.nette.org/] não tem a opção de guardar alterações diretamente no GitHub, é necessário, após concluir as edições, copiar o texto fonte para a área de transferência (botão *Copy to clipboard*) e depois colá-lo no editor do GitHub. Abaixo do campo de edição existe um formulário para envio. Não se esqueça de resumir brevemente e explicar o motivo da sua modificação. Após o envio, é criado um chamado pull request (PR), que pode ser editado posteriormente. -Maiores edições .[#toc-larger-edits] ------------------------------------- +Modificações Maiores +-------------------- -É mais apropriado estar familiarizado com o básico de trabalhar com o sistema de controle de versões Git, em vez de depender apenas da interface GitHub. Se você não estiver familiarizado com Git, você pode se referir ao [git - o guia simples |https://rogerdudler.github.io/git-guide/] e considerar o uso de um dos muitos [clientes gráficos |https://git-scm.com/downloads/guis] disponíveis. +Mais adequado do que usar a interface do GitHub é estar familiarizado com os fundamentos do trabalho com o sistema de controlo de versões Git. Se não domina o trabalho com o Git, pode consultar o guia [git - the simple guide |https://rogerdudler.github.io/git-guide/] e, opcionalmente, usar um dos muitos [clientes gráficos |https://git-scm.com/downloads/guis]. -Edite a documentação da seguinte maneira: +Edite a documentação desta forma: -1) no GitHub, criar um [garfo |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] do repositório [nette/docs |https://github.com/nette/docs] -2) [clone |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] este repositório em seu computador -3) então, fazer mudanças no [ramo apropriado |#Documentation Structure] -4) verificar se há espaços extras no texto usando a ferramenta [Code-Checker |code-checker:] -5) salvar (comprometer) as mudanças -6) se você estiver satisfeito com as mudanças, empurre-as para GitHub até sua bifurcação -7) a partir daí, submetê-los ao repositório `nette/docs`, criando um [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) +1) No GitHub, crie um [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] do repositório [nette/docs |https://github.com/nette/docs]. +2) [Clone |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] este repositório para o seu computador. +3) Em seguida, no [branch apropriado |#Estrutura da Documentação], faça as alterações. +4) Verifique se há espaços em branco extras no texto usando a ferramenta [Code-Checker |code-checker:]. +5) Salve (commit) as alterações. +6) Se estiver satisfeito com as alterações, envie-as (push) para o GitHub no seu fork. +7) A partir daí, envie-as para o repositório `nette/docs` criando um [pull request|https://help.github.com/articles/creating-a-pull-request] (PR). -É comum receber comentários com sugestões. Mantenha-se informado sobre as mudanças propostas e incorpore-as. Adicione as mudanças sugeridas como novos compromissos e reenvie-as ao GitHub. Nunca crie um novo pedido de puxar só para modificar um já existente. +É comum receber comentários com sugestões. Acompanhe as alterações propostas e incorpore-as. Adicione as alterações propostas como novos commits e envie novamente para o GitHub. Nunca crie um novo pull request para modificar um pull request existente. -Estrutura de documentação .[#toc-documentation-structure] ---------------------------------------------------------- +Estrutura da Documentação +------------------------- -Toda a documentação está localizada no GitHub, no repositório [nette/docs |https://github.com/nette/docs]. A versão atual está na filial principal, enquanto as versões mais antigas estão localizadas em filiais como `doc-3.x`, `doc-2.x`. +Toda a documentação está localizada no GitHub no repositório [nette/docs |https://github.com/nette/docs]. A versão atual está no branch `master`, versões mais antigas estão localizadas em branches como `doc-3.x`, `doc-2.x`. -O conteúdo de cada ramo é dividido em pastas principais que representam áreas individuais de documentação. Por exemplo, `application/` corresponde a https://doc.nette.org/en/application, `latte/` corresponde a https://latte.nette.org, etc. Cada uma destas pastas contém subpastas representando as mutações lingüísticas (`cs`, `en`, ...) e opcionalmente uma subpasta `files` com imagens que podem ser inseridas nas páginas da documentação. +O conteúdo de cada branch é dividido em pastas principais que representam as diferentes áreas da documentação. Por exemplo, `application/` corresponde a `https://doc.nette.org/pt/application`, `latte/` corresponde a `https://latte.nette.org`, etc. Cada uma destas pastas contém subpastas que representam as versões de idioma (`pt`, `en`, `cs`, ...) e, opcionalmente, a subpasta `files` com imagens que podem ser inseridas nas páginas da documentação. diff --git a/contributing/pt/syntax.texy b/contributing/pt/syntax.texy index 8e7bba981d..e810926f67 100644 --- a/contributing/pt/syntax.texy +++ b/contributing/pt/syntax.texy @@ -1,59 +1,59 @@ -Sintaxe do Wiki -*************** +Sintaxe da Documentação +*********************** -Wiki usa a [sintaxe |https://texy.info/en/syntax] Markdown & [Texy |https://texy.info/en/syntax] com vários aprimoramentos. +A documentação usa Markdown e a [sintaxe Texy |https://texy.nette.org/syntax] com algumas extensões. -Links .[#toc-links] -=================== +Links +===== -Para referências internas, a notação entre parênteses rectos `[link]` é utilizado. Isto é na forma com uma barra vertical `[link text |link target]`ou na forma abreviada `[link text]` se o alvo for o mesmo que o texto (após transformação para minúsculas e hífens): +Para links internos, utiliza-se a notação em colchetes `[...]`. Seja na forma com barra vertical `[texto do link |destino do link]`, ou abreviada `[texto do link]`, se o destino for idêntico ao texto (após transformação para minúsculas e hífens): -- `[Page name]` -> `<a href="/en/page-name">Page name</a>` -- `[link text |Page name]` -> `<a href="/en/page-name">link text</a>` +- `[Page name|Page name]` -> `<a href="/en/page-name">Page name</a>` +- `[texto do link |Page name]` -> `<a href="/en/page-name">link text</a>` -Podemos fazer um link para outro idioma ou para outra seção. Uma seção é uma biblioteca Nette (por exemplo, `forms`, `latte`, etc.) ou seções especiais como `best-practices`, `quickstart`, etc: +Podemos criar links para uma versão de idioma diferente ou para uma seção diferente. Uma seção significa uma biblioteca Nette (por exemplo, `forms`, `latte`, etc.) ou seções especiais como `best-practices`, `quickstart`, etc.: -- `[cs:Page name]` -> `<a href="/en/page-name">Page name</a>` (mesma seção, idioma diferente) +- `[cs:Page name]` -> `<a href="/cs/page-name">Page name</a>` (mesma seção, idioma diferente) - `[tracy:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (seção diferente, mesmo idioma) -- `[tracy:cs:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (seção e idioma diferentes) +- `[tracy:cs:Page name]` -> `<a href="//tracy.nette.org/cs/page-name">Page name</a>` (seção e idioma diferentes) -Também é possível direcionar um título específico na página com `#``. +Usando `#`, também é possível direcionar para um título específico na página. - `[#Heading]` -> `<a href="#toc-heading">Heading</a>` (título na página atual) - `[Page name#Heading]` -> `<a href="/en/page-name#toc-heading">Page name</a>` -Link para a página inicial da seção: (`@home` é um termo especial para a página inicial da seção) +Link para a página inicial da seção: (`@home` é uma expressão especial para a página inicial da seção) -- `[link text |@home]` -> `<a href="/en/">link text</a>` -- `[link text |tracy:]` -> `<a href="//tracy.nette.org/en/">link text</a>` +- `[texto do link |@home]` -> `<a href="/en/">link text</a>` +- `[texto do link |tracy:]` -> `<a href="//tracy.nette.org/en/">link text</a>` -Links para a documentação API .[#toc-links-to-api-documentation] ----------------------------------------------------------------- +Links para a Documentação da API +-------------------------------- -Use sempre as seguintes notações: +Utilize sempre apenas esta notação: - `[api:Nette\SmartObject]` -> [api:Nette\SmartObject] - `[api:Nette\Forms\Form::setTranslator()]` -> [api:Nette\Forms\Form::setTranslator()] - `[api:Nette\Forms\Form::$onSubmit]` -> [api:Nette\Forms\Form::$onSubmit] - `[api:Nette\Forms\Form::Required]` -> [api:Nette\Forms\Form::Required] -Os nomes totalmente qualificados são utilizados apenas na primeira menção. Para outros links, use um nome simplificado: +Use nomes totalmente qualificados apenas na primeira menção. Para links subsequentes, use o nome simplificado: -- `[Form::setTranslator() |api:Nette\Forms\Form::setTranslator()]` -> [Formulário::setTranslator() |api:Nette\Forms\Form::setTranslator()] +- `[Form::setTranslator() |api:Nette\Forms\Form::setTranslator()]` -> [Form::setTranslator() |api:Nette\Forms\Form::setTranslator()] -Links para documentação PHP .[#toc-links-to-php-documentation] --------------------------------------------------------------- +Links para a Documentação do PHP +-------------------------------- - `[php:substr]` -> [php:substr] -Código fonte .[#toc-source-code] -================================ +Código-Fonte +============ -O bloco de código começa com <code>```lang</code> e termina com <code>```</code> Os idiomas suportados são `php`, `latte`, `neon`, `css`, `html`, , `js` e `sql`. Utilize sempre as abas para recuo. +Um bloco de código começa com ` ```lang ` e termina com ` ``` `. Os idiomas suportados são `php`, `latte`, `neon`, `html`, `css`, `js` e `sql`. Use sempre tabulações para a indentação. ``` ```php @@ -63,7 +63,7 @@ O bloco de código começa com <code>```lang</code> e termina com <c ``` ``` -Você também pode especificar o nome do arquivo como <code>```php .{file: ArrayTest.php}</code> e o bloco de código será renderizado desta forma: +Também pode especificar o nome do arquivo como ` ```php .{file: ArrayTest.php} ` e o bloco de código será renderizado desta forma: ```php .{file: ArrayTest.php} public function renderPage($id) @@ -72,71 +72,71 @@ public function renderPage($id) ``` -Cabeçalhos .[#toc-headings] -=========================== +Títulos +======= -Título superior (nome da página) sublinhado com estrelas (`*`). For normal headings use equal signs (`=`) and then hyphens (`-`). +Sublinhe o título mais alto (ou seja, o nome da página) com asteriscos (`***`). Use sinais de igual (`===`) para separar secções principais. Sublinhe os títulos de nível inferior com sinais de igual (`===`) e depois com hífens (`---`): ``` -MVC Applications & Presenters -***************************** +Aplicações MVC & Presenters +*************************** ... -Link Creation -============= +Criação de Links +================ ... -Links in Templates +Links em Templates ------------------ ... ``` -Caixas e Estilos .[#toc-boxes-and-styles] -========================================= +Caixas e Estilos +================ -Parágrafo de chumbo marcado com classe `.[perex]` .[perex] +Marcamos o perex com a classe `.[perex]` .[perex] -Notas marcadas com classe `.[note]` .[note] +Marcamos uma nota com a classe `.[note]` .[note] -Dica marcada com classe `.[tip]` .[tip] +Marcamos uma dica com a classe `.[tip]` .[tip] -Advertência marcada com classe `.[caution]` .[caution] +Marcamos um aviso com a classe `.[caution]` .[caution] -Aviso forte marcado com classe `.[warning]` .[warning] +Marcamos um aviso mais forte com a classe `.[warning]` .[warning] Número da versão `.{data-version:2.4.10}` .{data-version:2.4.10} -As aulas devem ser escritas antes da linha relacionada: +Escreva as classes antes da linha: ``` -.[note] -This is a note. +.[perex] +Este é o perex. ``` -Por favor, note que caixas como `.[tip]` chama a atenção e, portanto, deve ser usada para enfatizar, não para informações menos importantes. +Por favor, esteja ciente de que caixas como `.[tip]` chamam a atenção, portanto, são usadas para enfatizar, e não para informações menos importantes. Use-as com moderação. -Tabela de Conteúdos .[#toc-table-of-contents] -============================================= +Sumário +======= -O índice (links na barra lateral) é gerado automaticamente quando a página é maior que 4 000 bytes. Este comportamento padrão pode ser alterado com um `{{toc}}` [meta tag |#meta-tags]. O texto para TOC é retirado por padrão do título, mas é possível usar um texto diferente com uma meta tag. `.{toc}` modificador. Isto é especialmente útil para títulos mais longos. +O sumário (links no menu direito) é gerado automaticamente para todas as páginas cujo tamanho exceda 4 000 bytes. Este comportamento padrão pode ser modificado usando a [meta tag |#Meta Tags] `{{toc}}`. O texto que forma o sumário é retirado por padrão diretamente do texto dos títulos, mas usando o modificador `.{toc}`, é possível exibir um texto diferente no sumário, o que é útil principalmente para títulos mais longos. ``` -Long and Intelligent Heading .{toc: A Different Text for TOC} -============================================================= +Título longo e inteligente .{toc: Qualquer outro texto exibido no sumário} +========================================================================== ``` -Meta Tags .[#toc-meta-tags] -=========================== +Meta Tags +========= -- definindo seu próprio título de página (em `<title>` e migalhas de pão) `{{title: Another name}}` -- redirecionamento `{{redirect: pla:cs}}` - ver [links |#links] -- fazendo cumprir `{{toc}}` ou desativação `{{toc: no}}` tabela de conteúdo +- definir um título de página personalizado (em `<title>` e na navegação breadcrumb) `{{title: Outro título}}` +- redirecionamento `{{redirect: pla:cs}}` - veja [#Links] +- forçar `{{toc}}` ou desabilitar `{{toc: no}}` o sumário automático (caixa com links para títulos individuais) {{priority: -1}} diff --git a/contributing/ro/@home.texy b/contributing/ro/@home.texy index 94730c5bef..aa47120625 100644 --- a/contributing/ro/@home.texy +++ b/contributing/ro/@home.texy @@ -1,17 +1,17 @@ -Deveniți un contribuabil la Nette -********************************* +Deveniți un contribuitor Nette +****************************** .[perex] -Aruncați o privire la modul în care vă puteți implica în proiectul nostru open source. Aflați care sunt pașii pentru a contribui la codul sursă și la documentație și alăturați-vă rețelei de dezvoltatori care se dedică îmbunătățirii Nette. +Aflați cum vă puteți implica în proiectul nostru open source. Însușiți-vă procedurile pentru contribuția la codul sursă și documentație și deveniți parte a comunității de dezvoltatori care participă activ la îmbunătățirea Nette. **Cod** -- [Contribuția la cod |code] -- [Standarde de codare |coding-standard] +- [Cum să contribuiți la cod? |code] +- [Standard de codificare |coding-standard] **Documentație** -- [Contribuția la documentație |documentation] +- [Cum să contribuiți la documentație? |documentation] - [Sintaxa documentației |syntax] - "Editor de previzualizare":https://editor.nette.org diff --git a/contributing/ro/@left-menu.texy b/contributing/ro/@left-menu.texy index 207f1cbdb9..ddea62842c 100644 --- a/contributing/ro/@left-menu.texy +++ b/contributing/ro/@left-menu.texy @@ -1,10 +1,10 @@ Cod *** -- [Contribuția la cod |code] -- [Standarde de codare |coding-standard] +- [Cum să contribuiți la cod? |code] +- [Standard de codificare |coding-standard] Documentație ************ -- [Contribuția la documentație |documentation] +- [Cum să contribuiți la documentație? |documentation] - [Sintaxa documentației |syntax] - "Editor de previzualizare":https://editor.nette.org diff --git a/contributing/ro/code.texy b/contributing/ro/code.texy index c75032d3c9..16fade07ae 100644 --- a/contributing/ro/code.texy +++ b/contributing/ro/code.texy @@ -1,87 +1,87 @@ -Contribuția la cod -****************** +Cum să contribuiți la cod +************************* .[perex] -Intenționați să contribuiți la Nette Framework și trebuie să vă familiarizați cu regulile și procedurile? Acest ghid pentru începători vă va ghida prin pașii pentru a contribui eficient la cod, a lucra cu depozitele și a implementa modificări. +Vă pregătiți să contribuiți la Nette Framework și aveți nevoie să vă orientați în reguli și proceduri? Acest ghid pentru începători vă va arăta pas cu pas cum să contribuiți eficient la cod, să lucrați cu depozite și să implementați modificări. -Procedura .[#toc-procedure] -=========================== +Procedura +========= -Pentru a contribui la cod, este esențial să aveți un cont pe [GitHub |https://github.com] și să fiți familiarizați cu elementele de bază ale lucrului cu sistemul de control al versiunilor Git. Dacă nu sunteți familiarizat cu Git, puteți consulta [Ghidul git - the simple guide |https://rogerdudler.github.io/git-guide/] și puteți lua în considerare utilizarea unuia dintre numeroșii [clienți grafici |https://git-scm.com/downloads/guis]. +Pentru a contribui la cod este necesar să aveți un cont pe [GitHub |https://github.com] și să fiți familiarizat cu elementele de bază ale lucrului cu sistemul de versionare Git. Dacă nu stăpâniți lucrul cu Git, puteți consulta ghidul [git - the simple guide |https://rogerdudler.github.io/git-guide/] și eventual să utilizați unul dintre multele [clienți grafici |https://git-scm.com/downloads/guis]. -Pregătirea mediului și a depozitului .[#toc-preparing-the-environment-and-repository] -------------------------------------------------------------------------------------- +Pregătirea mediului și a depozitului +------------------------------------ -1) Pe GitHub, creați un [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] al [depozitului de pachete |www:packages] pe care intenționați să îl modificați -2) [Clonați |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] acest depozit pe computerul dvs. -3) Instalați dependențele, inclusiv [Nette Tester |tester:], utilizând comanda `composer install` -4) Verificați dacă testele funcționează, rulând `composer tester` -5) Creați o [nouă ramură |#New Branch] bazată pe cea mai recentă versiune lansată +1) pe GitHub creați un [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] al depozitului [pachetului |www:packages], pe care urmează să-l modificați +2) [clonați |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] acest depozit pe computerul dvs. +3) instalați dependențele, inclusiv [Nette Tester |tester:], folosind comanda `composer install` +4) verificați dacă testele funcționează, rulând `composer tester` +5) creați o [nouă ramură |#Ramură nouă] bazată pe ultima versiune lansată -Implementarea propriilor modificări .[#toc-implementing-your-own-changes] -------------------------------------------------------------------------- +Implementarea propriilor modificări +----------------------------------- -Acum puteți face propriile modificări de cod: +Acum puteți efectua propriile modificări de cod: -1) Implementați modificările dorite și nu uitați de teste -2) Asigurați-vă că testele se execută cu succes folosind `composer tester` -3) Verificați dacă codul respectă [standardele de codare |#coding standards] -4) Salvați (confirmați) modificările cu o descriere în [acest format |#Commit Description] +1) programați modificările dorite și nu uitați de teste +2) asigurați-vă că testele rulează cu succes, folosind `composer tester` +3) verificați dacă codul respectă [standardul de codificare |#Standarde de codificare] +4) salvați modificările (commit) cu o descriere în [acest format |#Descrierea commit-ului] -Puteți crea mai multe comenzi, câte una pentru fiecare etapă logică. Fiecare commit ar trebui să fie semnificativ în sine. +Puteți crea mai multe commit-uri, unul pentru fiecare pas logic. Fiecare commit ar trebui să aibă sens de sine stătător. -Trimiterea modificărilor .[#toc-submitting-changes] ---------------------------------------------------- +Trimiterea modificărilor +------------------------ -După ce sunteți mulțumit de modificări, le puteți trimite: +Odată ce sunteți mulțumit de modificări, le puteți trimite: -1) Împingeți modificările pe GitHub în furculița dvs. -2) De acolo, trimiteți-le la depozitul Nette prin crearea unei [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) -3) Furnizați [informații suficiente |#pull request description] în descriere +1) trimiteți (push) modificările pe GitHub în fork-ul dvs. +2) de acolo le trimiteți către depozitul Nette creând un [pull request |https://help.github.com/articles/creating-a-pull-request] (PR) +3) furnizați în descriere [suficiente informații |#Descrierea pull request-ului] -Încorporarea feedback-ului .[#toc-incorporating-feedback] ---------------------------------------------------------- +Incorporarea comentariilor +-------------------------- -Modificările dvs. sunt acum vizibile pentru ceilalți. Este obișnuit să primiți comentarii cu sugestii: +Commit-urile dvs. vor fi acum vizibile și pentru alții. Este obișnuit să primiți comentarii cu observații: -1) Țineți evidența modificărilor propuse -2) Încorporați-le ca noi comenzi sau [fuzionați-le cu cele anterioare |https://help.github.com/en/github/using-git/about-git-rebase] -3) Trimiteți din nou comentariile pe GitHub, iar acestea vor apărea automat în cererea de extragere (pull request) +1) urmăriți modificările propuse +2) încorporați-le ca noi commit-uri sau [combinați-le cu cele anterioare |https://help.github.com/en/github/using-git/about-git-rebase] +3) retrimiteți commit-urile pe GitHub și acestea vor apărea automat în pull request Nu creați niciodată un nou pull request pentru a modifica unul existent. -Documentație .[#toc-documentation] ----------------------------------- +Documentație +------------ -Dacă ați modificat o funcționalitate sau ați adăugat una nouă, nu uitați să [o adăugați și în documentație |documentation]. +Dacă ați modificat funcționalitatea sau ați adăugat una nouă, nu uitați să o [adăugați și în documentație |documentation]. -Ramură nouă .[#toc-new-branch] -============================== +Ramură nouă +=========== -Dacă este posibil, efectuați modificările în raport cu ultima versiune publicată, adică ultima etichetă din ramură. Pentru eticheta v3.2.1, creați o ramură folosind această comandă: +Dacă este posibil, efectuați modificările față de ultima versiune lansată, adică ultimul tag din ramura respectivă. Pentru tag-ul `v3.2.1` creați o ramură cu această comandă: ```shell git checkout -b new_branch_name v3.2.1 ``` -Standarde de codificare .[#toc-coding-standards] -================================================ +Standarde de codificare +======================= -Codul dumneavoastră trebuie să respecte [standardele de codare |coding standard] utilizate în cadrul Nette Framework. Există un instrument automat disponibil pentru verificarea și corectarea codului. Îl puteți instala **global** prin Composer într-un dosar la alegere: +Codul dvs. trebuie să respecte [standardul de codificare |coding-standard] utilizat în Nette Framework. Pentru verificarea și corectarea codului este disponibil un instrument automat. Acesta poate fi instalat prin Composer **global** în directorul ales de dvs.: ```shell composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Acum ar trebui să puteți rula instrumentul în terminal. Prima comandă verifică, iar cea de-a doua corectează codul din dosarele `src` și `tests` din directorul curent: +Acum ar trebui să puteți rula instrumentul în terminal. Prima comandă verifică și a doua corectează codul din directoarele `src` și `tests` din directorul curent: ```shell /path/to/nette-coding-standard/ecs check @@ -89,24 +89,24 @@ Acum ar trebui să puteți rula instrumentul în terminal. Prima comandă verifi ``` -Angajare Descriere .[#toc-commit-description] -============================================= +Descrierea commit-ului +====================== -În Nette, subiectele de commit au următorul format: `Presenter: fixed AJAX detection [Closes #69]` +În Nette, subiectele commit-urilor au formatul: `Presenter: fixed AJAX detection [Closes #69]` -- area urmată de două puncte -- scopul angajamentului la timpul trecut; dacă este posibil, începeți cu cuvinte de genul: added, fixed, refactored, changed, removed -- în cazul în care confirmarea încalcă compatibilitatea retroactivă, adăugați "BC break" -- orice legătură cu sistemul de urmărire a problemelor, cum ar fi `(#123)` sau `[Closes #69]` -- după subiect, poate exista o linie goală, urmată de o descriere mai detaliată, incluzând, de exemplu, linkuri către forum +- zona urmată de două puncte +- scopul commit-ului la timpul trecut, dacă este posibil, începeți cu cuvântul: "added .(proprietate nouă adăugată)", "fixed .(corecție)", "refactored .(modificare în cod fără schimbarea comportamentului)", changed, removed +- dacă commit-ul întrerupe compatibilitatea inversă, adăugați "BC break" +- eventuală legătură cu issue tracker-ul precum `(#123)` sau `[Closes #69]` +- după subiect poate urma o linie goală și apoi o descriere mai detaliată, inclusiv, de exemplu, linkuri către forum -Descrierea cererii de tip pull request .[#toc-pull-request-description] -======================================================================= +Descrierea pull request-ului +============================ -Atunci când creați un pull request, interfața GitHub vă va permite să introduceți un titlu și o descriere. Furnizați un titlu concis și includeți în descriere cât mai multe informații despre motivele modificării dumneavoastră. +La crearea unui pull request, interfața GitHub vă permite să introduceți un titlu și o descriere. Furnizați un titlu descriptiv și în descriere oferiți cât mai multe informații despre motivele modificării dvs. -De asemenea, precizați în antet dacă este o caracteristică nouă sau o remediere a unei erori și dacă poate cauza probleme de compatibilitate retroactivă (BC break). Dacă există o problemă conexă, creați un link către aceasta, astfel încât aceasta să fie închisă în momentul aprobării cererii de modificare. +Se va afișa și un antet, unde specificați dacă este vorba despre o nouă funcție sau o corecție de eroare și dacă poate apărea o întrerupere a compatibilității inverse (BC break). Dacă există o problemă (issue) asociată, faceți referire la ea, astfel încât să fie închisă după aprobarea pull request-ului. ``` - bug fix / new feature? <!-- #issue numbers, if any --> diff --git a/contributing/ro/coding-standard.texy b/contributing/ro/coding-standard.texy index 0aed6a115b..a933882b59 100644 --- a/contributing/ro/coding-standard.texy +++ b/contributing/ro/coding-standard.texy @@ -1,44 +1,44 @@ Standard de codificare ********************** -Acest document descrie regulile și recomandările pentru dezvoltarea Nette. Atunci când contribuiți cu cod la Nette, trebuie să le respectați. Cel mai simplu mod de a face acest lucru este să imitați codul existent. -Ideea este de a face ca tot codul să arate ca și cum ar fi fost scris de o singură persoană. .[perex] +.[perex] +Acest document descrie regulile și recomandările pentru dezvoltarea Nette. Atunci când contribuiți cu cod la Nette, trebuie să le respectați. Cea mai simplă modalitate de a face acest lucru este să imitați codul existent. Ideea este ca tot codul să arate ca și cum ar fi fost scris de o singură persoană. -Standardul de codare Nette corespunde [stilului de codare PSR-12 Extended Coding Style |https://www.php-fig.org/psr/psr-12/], cu două excepții principale: folosește [tabulatoare în loc de spații |#tabs instead of spaces] pentru indentare și folosește [PascalCase pentru constantele de clasă |https://blog.nette.org/ro/pentru-mai-putine-tipete-in-cod]. +Standardul de codificare Nette corespunde [PSR-12 Extended Coding Style |https://www.php-fig.org/psr/psr-12/] cu două excepții principale: pentru indentare folosește [#Tabulatori în loc de spații] în loc de spații și pentru [constantele de clasă folosește PascalCase |https://blog.nette.org/en/a-bit-less-screaming-in-code]. -Reguli generale .[#toc-general-rules] -===================================== +Reguli generale +=============== - Fiecare fișier PHP trebuie să conțină `declare(strict_types=1)` -- Două linii goale sunt folosite pentru a separa metodele pentru o mai bună lizibilitate. -- Motivul pentru utilizarea operatorului shut-up trebuie să fie documentat: `@mkdir($dir); // @ - directory may exist` -- În cazul în care se utilizează operatorul de comparație slab tipizat (de exemplu, `==`, `!=`, ...), intenția trebuie să fie documentată: `// == to accept null` -- Puteți scrie mai multe excepții într-un singur fișier `exceptions.php` -- Vizibilitatea metodelor nu este specificată pentru interfețe, deoarece acestea sunt întotdeauna publice. -- Fiecare proprietate, valoare de returnare și parametru trebuie să aibă un tip specificat. Pe de altă parte, pentru constantele finale, nu se specifică niciodată tipul, deoarece este evident. -- Ghilimelele simple trebuie utilizate pentru a delimita șirul de caractere, cu excepția cazului în care literalul conține el însuși apostrofuri. +- Două rânduri goale sunt folosite pentru a separa metodele pentru o mai bună lizibilitate. +- Motivul utilizării operatorului shut-up trebuie documentat: `@mkdir($dir); // @ - directorul poate exista`. +- Dacă este utilizat un operator de comparație slab tipizat (adică `==`, `!=`, ...), intenția trebuie documentată: `// == acceptă null` +- Într-un singur fișier `exceptions.php` puteți scrie mai multe excepții. +- Pentru interfețe nu se specifică vizibilitatea metodelor, deoarece sunt întotdeauna publice. +- Fiecare proprietate, valoare returnată și parametru trebuie să aibă tipul specificat. În schimb, la constantele finale nu specificăm niciodată tipul, deoarece este evident. +- Pentru delimitarea șirurilor de caractere ar trebui folosite ghilimele simple, cu excepția cazurilor în care literalul însuși conține apostrofuri. -Convenții de denumire .[#toc-naming-conventions] -================================================ +Convenții de denumire +===================== -- Evitați utilizarea abrevierilor, cu excepția cazului în care numele complet este excesiv. -- Folosiți majusculele pentru abrevierile din două litere și majusculele pascale/cămilă pentru abrevierile mai lungi. -- Utilizați un substantiv sau o expresie substantivală pentru numele clasei. -- Numele claselor trebuie să conțină nu numai specificitate (`Array`), ci și generalitate (`ArrayIterator`). Excepție fac atributele PHP. -- "Constantele de clasă și enumerațiile trebuie să utilizeze PascalCaps":https://blog.nette.org/ro/pentru-mai-putine-tipete-in-cod. -- "Interfețele și clasele abstracte nu trebuie să conțină prefixe sau postfixe":https://blog.nette.org/ro/prefixele-si-sufixele-nu-se-regasesc-in-numele-interfetelor precum `Abstract`, `Interface` sau `I`. +- Nu utilizați abrevieri, cu excepția cazului în care numele complet este prea lung. +- Pentru abrevierile de două litere utilizați majuscule, pentru abrevierile mai lungi pascal/camel case. +- Pentru numele clasei utilizați un substantiv sau o sintagmă. +- Numele claselor trebuie să conțină nu numai specificitatea (`Array`), ci și generalitatea (`ArrayIterator`). Excepție fac atributele limbajului PHP. +- "Constantele de clasă și enum-urile ar trebui să utilizeze PascalCaps":https://blog.nette.org/en/a-bit-less-screaming-in-code. +- "Interfețele și clasele abstracte nu ar trebui să conțină prefixe sau sufixe":https://blog.nette.org/ro/prefixes-and-suffixes-do-not-belong-in-interface-names precum `Abstract`, `Interface` sau `I`. -Înfășurare și paranteze .[#toc-wrapping-and-braces] -=================================================== +Wrapping and Braces +=================== -Standardul de codificare Nette corespunde PSR-12 (sau stilului de codificare PER), dar în unele puncte îl specifică mai mult sau îl modifică: +Standardul de codificare Nette corespunde PSR-12 (respectiv PER Coding Style), în unele puncte îl completează sau îl modifică: -- funcțiile săgeată se scriu fără spațiu înaintea parantezei, adică `fn($a) => $b` -- nu este necesară o linie goală între diferitele tipuri de instrucțiuni de import `use` -- tipul de returnare al funcției/metodei și paranteza de deschidere trebuie plasate pe linii separate pentru o mai bună lizibilitate: +- funcțiile arrow se scriu fără spațiu înainte de paranteză, adică `fn($a) => $b` +- nu se cere un rând gol între diferite tipuri de `use` import statements +- tipul returnat al funcției/metodei și acolada de deschidere sunt întotdeauna pe rânduri separate: ```php public function find( @@ -50,19 +50,23 @@ Standardul de codificare Nette corespunde PSR-12 (sau stilului de codificare PER } ``` +Acolada de deschidere pe un rând separat este importantă pentru separarea vizuală a semnăturii funcției/metodei de corp. Dacă semnătura este pe un singur rând, separarea este clară (imaginea din stânga), dacă este pe mai multe rânduri, în PSR semnăturile și corpurile se contopesc (mijloc), în timp ce în standardul Nette sunt în continuare separate (dreapta): -Blocuri de documentație (phpDoc) .[#toc-documentation-blocks-phpdoc] -==================================================================== +[* new-line-after.webp *] -Regula principală: nu duplicați niciodată informații din semnătură, cum ar fi tipul de parametru sau tipul de retur, fără nicio valoare adăugată. -Bloc de documentație pentru definirea clasei: +Blocuri de documentație (phpDoc) +================================ -- Începe cu o descriere a clasei. -- Urmează o linie goală. -- Urmează, rând pe rând, adnotările `@property` (sau `@property-read`, `@property-write`). Sintaxa este: adnotare, spațiu, tip, spațiu, $nume. -- Urmează, rând pe rând, adnotările `@method`. Sintaxa este: annotation, space, return type, space, name(type $param, ...). -- Adnotarea `@author` este omisă. Autorul este păstrat într-un istoric al codului sursă. +Regula principală: Nu duplicați niciodată informații în semnătură, cum ar fi tipul parametrului sau tipul returnat, fără valoare adăugată. + +Blocul de documentație pentru definirea clasei: + +- Începe cu descrierea clasei. +- Urmează un rând gol. +- Urmează adnotările `@property` (sau `@property-read`, `@property-write`), una după alta. Sintaxa este: adnotare, spațiu, tip, spațiu, $nume. +- Urmează adnotările `@method`, una după alta. Sintaxa este: adnotare, spațiu, tip returnat, spațiu, nume(tip $param, ...). +- Adnotarea `@author` se omite. Autoritatea este păstrată în istoricul codului sursă. - Se pot utiliza adnotările `@internal` sau `@deprecated`. ```php @@ -76,27 +80,27 @@ Bloc de documentație pentru definirea clasei: */ ``` -Blocul de documentație pentru proprietatea care conține doar adnotarea `@var` trebuie să fie pe o singură linie: +Blocul de documentație pentru o proprietate, care conține doar adnotarea `@var`, ar trebui să fie pe un singur rând: ```php /** @var string[] */ private array $name; ``` -Blocul de documentație pentru definiția metodei: +Blocul de documentație pentru definirea metodei: - Începe cu o scurtă descriere a metodei. -- Nu există linii goale. -- Adnotările `@param`, rând pe rând. +- Niciun rând gol. +- Adnotările `@param` pe rânduri separate. - Adnotarea `@return`. -- Adnotările `@throws`, rând pe rând. +- Adnotările `@throws`, una după alta. - Se pot utiliza adnotările `@internal` sau `@deprecated`. -Fiecare adnotare este urmată de un spațiu, cu excepția `@param`, care este urmată de două spații pentru o mai bună lizibilitate. +După fiecare adnotare urmează un singur spațiu, cu excepția `@param`, după care, pentru o mai bună lizibilitate, urmează două spații. ```php /** - * Finds a file in directory. + * Găsește un fișier în director. * @param string[] $options * @return string[] * @throws DirectoryNotFoundException @@ -105,21 +109,20 @@ public function find(string $dir, array $options): array ``` -Tabulații în loc de spații .[#toc-tabs-instead-of-spaces] -========================================================= +Tabulatori în loc de spații +=========================== -Tabulațiile au mai multe avantaje față de spații: +Tabulatorii au mai multe avantaje față de spații: -- dimensiunea liniuței este personalizabilă în editori și "web":https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size. -- nu impun dimensiunea de indentare preferată de utilizator asupra codului, astfel încât codul este mai portabil -- se pot tasta cu o singură apăsare de tastă (oriunde, nu doar în editorii care transformă filele în spații) -- scopul lor este indentarea -- respectați nevoile colegilor cu deficiențe de vedere și nevăzători +- dimensiunea indentării poate fi personalizată în editoare și pe "web":https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size +- nu impun codului preferința utilizatorului privind dimensiunea indentării, astfel încât codul este mai portabil +- pot fi scrise cu o singură apăsare de tastă (oriunde, nu doar în editoarele care transformă tabulatorii în spații) +- indentarea este scopul lor +- respectă nevoile colegilor cu deficiențe de vedere și nevăzători -Prin utilizarea tabulațiilor în proiectele noastre, permitem personalizarea lățimii, ceea ce poate părea inutil pentru majoritatea oamenilor, dar este esențial pentru persoanele cu deficiențe de vedere. +Utilizând tabulatori în proiectele noastre, permitem personalizarea lățimii, ceea ce poate părea o inutilitate pentru majoritatea oamenilor, dar este esențială pentru persoanele cu deficiențe de vedere. -Pentru programatorii nevăzători care folosesc afișaje braille, fiecare spațiu este reprezentat de o celulă braille și ocupă un spațiu prețios. Astfel, dacă indentarea implicită este de 4 spații, o indentare de nivelul 3 irosește 12 celule braille înainte de începerea codului. -Pe un afișaj cu 40 de celule, care este cel mai des utilizat pe laptopuri, înseamnă mai mult de un sfert din celulele disponibile irosite fără nicio informație. +Pentru programatorii nevăzători care utilizează afișaje Braille, fiecare spațiu reprezintă o celulă Braille. Deci, dacă indentarea implicită este de 4 spații, indentarea de nivel 3 irosește 12 celule Braille valoroase chiar înainte de începutul codului. Pe un afișaj de 40 de celule, care este cel mai frecvent utilizat la laptopuri, aceasta reprezintă mai mult de un sfert din celulele disponibile, care sunt irosite fără nicio informație. {{priority: -1}} diff --git a/contributing/ro/documentation.texy b/contributing/ro/documentation.texy index 78df00669c..c51ce18e62 100644 --- a/contributing/ro/documentation.texy +++ b/contributing/ro/documentation.texy @@ -1,69 +1,68 @@ -Contribuția la documentație -*************************** +Cum să contribuiți la documentație +********************************** .[perex] -Contribuția la documentație este una dintre cele mai valoroase activități, deoarece îi ajută pe ceilalți să înțeleagă cadrul. +Contribuția la documentație este una dintre cele mai benefice activități, deoarece îi ajutați pe alții să înțeleagă framework-ul. -Cum se scrie? .[#toc-how-to-write] ----------------------------------- +Cum să scrieți? +--------------- -Documentația este destinată în primul rând persoanelor care nu cunosc acest subiect. Prin urmare, ar trebui să îndeplinească mai multe puncte importante: +Documentația este destinată în principal persoanelor care se familiarizează cu subiectul. Prin urmare, ar trebui să îndeplinească câteva puncte importante: -- Începeți cu subiecte simple și generale. Treceți la subiecte mai avansate la sfârșit -- Încercați să explicați subiectul cât mai clar posibil. De exemplu, încercați să explicați mai întâi subiectul unui coleg -- Furnizați numai informațiile pe care utilizatorul trebuie să le știe cu adevărat pentru un anumit subiect -- Asigurați-vă că informațiile pe care le furnizați sunt corecte. Testați fiecare cod -- Fiți concis - reduceți la jumătate ceea ce scrieți. Și apoi nu ezitați să o faceți din nou -- Folosiți evidențierea cu moderație, de la fonturi îngroșate la cadre de genul `.[note]` -- Respectați [standardul de codi |Coding Standard] ficare în cod +- Începeți de la simplu și general. Treceți la subiecte mai avansate abia la sfârșit. +- Încercați să explicați lucrul cât mai bine posibil. Încercați, de exemplu, să explicați mai întâi subiectul unui coleg. +- Furnizați doar informațiile de care utilizatorul are nevoie cu adevărat pentru subiectul respectiv. +- Verificați dacă informațiile dvs. sunt într-adevăr adevărate. Testați fiecare cod. +- Fiți concis - scurtați ceea ce scrieți la jumătate. Și apoi, dacă este necesar, încă o dată. +- Economisiți evidențiatoarele de orice fel, de la text îngroșat la cadre precum `.[note]`. +- În coduri respectați [Coding Standard |coding-standard]. -De asemenea, învățați [sintaxa |syntax]. Pentru o previzualizare a articolului în timpul scrierii, puteți utiliza [editorul de previzualizare |https://editor.nette.org/]. +Însușiți-vă și [sintaxa |syntax]. Pentru previzualizarea articolului în timpul scrierii, puteți utiliza [editorul cu previzualizare |https://editor.nette.org/]. -Mutații lingvistice .[#toc-language-mutations] ----------------------------------------------- +Versiuni lingvistice +-------------------- -Engleza este limba principală, așa că modificările trebuie să fie în limba engleză. Dacă engleza nu este punctul dumneavoastră forte, utilizați [DeepL Translator |https://www.deepl.com/translator] și alții vă vor verifica textul. +Limba principală este engleza, modificările dvs. ar trebui deci să fie și în engleză. Dacă engleza nu este punctul dvs. forte, utilizați [DeepL Translator |https://www.deepl.com/translator] și ceilalți vă vor verifica textul. -Traducerea în alte limbi se va face automat după aprobarea și punerea la punct a modificării dumneavoastră. +Traducerea în celelalte limbi va fi efectuată automat după aprobarea și finisarea modificării dvs. -Modificări triviale .[#toc-trivial-edits] ------------------------------------------ +Modificări triviale +------------------- -Pentru a contribui la documentație, trebuie să aveți un cont pe [GitHub |https://github.com]. +Pentru a contribui la documentație este necesar să aveți un cont pe [GitHub |https://github.com]. -Cel mai simplu mod de a face o mică modificare în documentație este să folosiți linkurile de la sfârșitul fiecărei pagini: +Cel mai simplu mod de a efectua o mică modificare în documentație este să utilizați linkurile de la sfârșitul fiecărei pagini: -- *Show on GitHub* deschide versiunea sursă a paginii pe GitHub. Apoi, trebuie doar să apăsați butonul `E` și puteți începe să editați (trebuie să fiți logat pe GitHub) -- *Open preview* deschide un editor în care puteți vedea imediat forma vizuală finală +- *Arată pe GitHub* deschide forma sursă a paginii respective pe GitHub. Apoi este suficient să apăsați butonul `E` și puteți începe editarea (este necesar să fiți autentificat pe GitHub). +- *Deschide previzualizarea* deschide editorul, unde vedeți imediat și forma vizuală finală. -Deoarece [editorul de previzualizare |https://editor.nette.org/] nu are posibilitatea de a salva modificările direct pe GitHub, trebuie să copiați textul sursă în clipboard (folosind butonul *Copy to clipboard*) și apoi să îl lipiți în editorul de pe GitHub. -Sub câmpul de editare se află un formular pentru trimitere. Aici, nu uitați să rezumați pe scurt și să explicați motivul modificării dvs. După trimitere, se creează o așa-numită pull request (PR), care poate fi editată în continuare. +Deoarece [editorul cu previzualizare |https://editor.nette.org/] nu are posibilitatea de a salva modificările direct pe GitHub, este necesar ca după finalizarea modificărilor să copiați textul sursă în clipboard (cu butonul *Copy to clipboard*) și apoi să-l lipiți în editorul de pe GitHub. Sub câmpul de editare se află formularul de trimitere. Aici nu uitați să rezumați pe scurt și să explicați motivul modificării dvs. După trimitere se creează așa-numitul pull request (PR), care poate fi editat ulterior. -Modificări mai mari .[#toc-larger-edits] ----------------------------------------- +Modificări mai mari +------------------- -Este mai indicat să vă familiarizați cu elementele de bază ale lucrului cu sistemul de control al versiunilor Git decât să vă bazați exclusiv pe interfața GitHub. Dacă nu sunteți familiarizat cu Git, puteți consulta [ghidul git - the simple guide |https://rogerdudler.github.io/git-guide/] și puteți lua în considerare utilizarea unuia dintre numeroșii [clienți grafici |https://git-scm.com/downloads/guis] disponibili. +Mai potrivit decât utilizarea interfeței GitHub este să fiți familiarizat cu elementele de bază ale lucrului cu sistemul de versionare Git. Dacă nu stăpâniți lucrul cu Git, puteți consulta ghidul [git - the simple guide |https://rogerdudler.github.io/git-guide/] și eventual să utilizați unul dintre multele [clienți grafici |https://git-scm.com/downloads/guis]. -Modificați documentația în felul următor: +Modificați documentația în acest mod: -1) pe GitHub, creați o [bifurcație |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] a depozitului [nette/docs |https://github.com/nette/docs] +1) pe GitHub creați un [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] al depozitului [nette/docs |https://github.com/nette/docs] 2) [clonați |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] acest depozit pe computerul dvs. -3) apoi, efectuați modificări în [ramura corespunzătoare |#Documentation Structure] -4) verificați dacă există spații în plus în text cu ajutorul instrumentului [Code-Checker |code-checker:] -5) salvați (confirmați) modificările -6) dacă sunteți mulțumit de modificări, împingeți-le pe GitHub în bifurcația dvs. -7) de acolo, trimiteți-le la depozitul `nette/docs` creând o [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) +3) apoi în [ramura corespunzătoare |#Structura documentației] efectuați modificările +4) verificați spațiile în exces din text folosind instrumentul [Code-Checker |code-checker:] +4) salvați modificările (commit) +6) dacă sunteți mulțumit de modificări, trimiteți-le (push) pe GitHub în fork-ul dvs. +7) de acolo le trimiteți către depozitul `nette/docs` creând un [pull request |https://help.github.com/articles/creating-a-pull-request] (PR) -Este obișnuit să primiți comentarii cu sugestii. Țineți evidența modificărilor propuse și încorporați-le. Adăugați modificările sugerate ca noi comisioane și retrimiteți-le la GitHub. Nu creați niciodată un nou pull request doar pentru a modifica unul existent. +Este obișnuit să primiți comentarii cu observații. Urmăriți modificările propuse și încorporați-le. Adăugați modificările propuse ca noi commit-uri și retrimiteți-le pe GitHub. Nu creați niciodată un nou pull request pentru a modifica unul existent. -Structura documentației .[#toc-documentation-structure] -------------------------------------------------------- +Structura documentației +----------------------- -Întreaga documentație se găsește pe GitHub, în depozitul [nette/docs |https://github.com/nette/docs]. Versiunea curentă se află în ramura master, în timp ce versiunile mai vechi se află în ramuri precum `doc-3.x`, `doc-2.x`. +Întreaga documentație este găzduită pe GitHub în depozitul [nette/docs |https://github.com/nette/docs]. Versiunea curentă este în master, versiunile mai vechi sunt plasate în ramuri precum `doc-3.x`, `doc-2.x`. -Conținutul fiecărei ramuri este împărțit în dosare principale care reprezintă domenii individuale ale documentației. De exemplu, `application/` corespunde la https://doc.nette.org/en/application, `latte/` corespunde la https://latte.nette.org, etc. Fiecare dintre aceste dosare conține subdosare care reprezintă mutații lingvistice (`cs`, `en`, ...) și, opțional, un subdosar `files` cu imagini care pot fi inserate în paginile din documentație. +Conținutul fiecărei ramuri este împărțit în directoare principale reprezentând domeniile individuale ale documentației. De exemplu, `application/` corespunde https://doc.nette.org/ro/application, `latte/` corespunde https://latte.nette.org etc. Fiecare dintre aceste directoare conține subdirectoare reprezentând versiunile lingvistice (`cs`, `en`, ...) și eventual subdirectorul `files` cu imagini, care pot fi inserate în paginile din documentație. diff --git a/contributing/ro/syntax.texy b/contributing/ro/syntax.texy index 4400f3e244..d756e4e2b0 100644 --- a/contributing/ro/syntax.texy +++ b/contributing/ro/syntax.texy @@ -1,59 +1,59 @@ -Sintaxa Wiki -************ +Sintaxa documentației +********************* -Wiki folosește [sintaxa |https://texy.info/en/syntax] Markdown & [Texy |https://texy.info/en/syntax] cu câteva îmbunătățiri. +Documentația utilizează Markdown & [sintaxa Texy |https://texy.nette.org/syntax] cu unele extensii. -Legături .[#toc-links] -====================== +Linkuri +======= -Pentru referințele interne, notația din paranteze pătrate `[link]` este utilizată. Aceasta se prezintă fie sub forma cu o bară verticală `[link text |link target]`, fie în forma prescurtată `[link text]` în cazul în care ținta este aceeași cu cea din text (după transformarea în minuscule și cu cratimă): +Pentru linkurile interne se utilizează notația în paranteze drepte `[link]`. Fie în forma cu bară verticală `[text link |țintă link]`, fie prescurtat `[text link]`, dacă ținta este identică cu textul (după transformarea în litere mici și cratime): -- `[Page name]` -> `<a href="/en/page-name">Page name</a>` -- `[link text |Page name]` -> `<a href="/en/page-name">link text</a>` +- `[Page name]` -> `<a href="/ro/page-name">Page name</a>` +- `[text link |Page name]` -> `<a href="/ro/page-name">text link</a>` -Putem face un link către o altă limbă sau o altă secțiune. O secțiune este o bibliotecă Nette (de exemplu, `forms`, `latte`, etc.) sau secțiuni speciale precum `best-practices`, `quickstart`, etc: +Putem face link către o altă versiune lingvistică sau către o altă secțiune. Prin secțiune se înțelege o bibliotecă Nette (de ex. `forms`, `latte`, etc.) sau secțiuni speciale precum `best-practices`, `quickstart` etc.: -- `[cs:Page name]` -> `<a href="/en/page-name">Page name</a>` (aceeași secțiune, limbă diferită) -- `[tracy:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (secțiune diferită, aceeași limbă) -- `[tracy:cs:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (secțiune și limbă diferite) +- `[cs:Page name]` -> `<a href="/cs/page-name">Page name</a>` (aceeași secțiune, altă limbă) +- `[tracy:Page name]` -> `<a href="//tracy.nette.org/ro/page-name">Page name</a>` (altă secțiune, aceeași limbă) +- `[tracy:cs:Page name]` -> `<a href="//tracy.nette.org/cs/page-name">Page name</a>` (altă secțiune și limbă) -De asemenea, este posibil să vizați o anumită rubrică din pagină cu `#`. +Folosind `#` este de asemenea posibil să țintim un anumit titlu de pe pagină. -- `[#Heading]` -> `<a href="#toc-heading">Heading</a>` (titlul de pe pagina curentă) -- `[Page name#Heading]` -> `<a href="/en/page-name#toc-heading">Page name</a>` +- `[#Heading]` -> `<a href="#toc-heading">Heading</a>` (titlu pe pagina curentă) +- `[Page name#Heading]` -> `<a href="/ro/page-name#toc-heading">Page name</a>` -Legătură către pagina principală a secțiunii: (`@home` este un termen special pentru pagina principală a secțiunii) +Link către pagina de start a secțiunii: (`@home` este o expresie specială pentru pagina de start a secțiunii) -- `[link text |@home]` -> `<a href="/en/">link text</a>` -- `[link text |tracy:]` -> `<a href="//tracy.nette.org/en/">link text</a>` +- `[link text |@home]` -> `<a href="/ro/">link text</a>` +- `[link text |tracy:]` -> `<a href="//tracy.nette.org/ro/">link text</a>` -Link-uri către documentația API .[#toc-links-to-api-documentation] ------------------------------------------------------------------- +Linkuri către documentația API +------------------------------ -Utilizați întotdeauna următoarele notații: +Specificați întotdeauna doar folosind această notație: - `[api:Nette\SmartObject]` -> [api:Nette\SmartObject] - `[api:Nette\Forms\Form::setTranslator()]` -> [api:Nette\Forms\Form::setTranslator()] - `[api:Nette\Forms\Form::$onSubmit]` -> [api:Nette\Forms\Form::$onSubmit] - `[api:Nette\Forms\Form::Required]` -> [api:Nette\Forms\Form::Required] -Numele complet calificate se utilizează numai în prima mențiune. Pentru celelalte linkuri, utilizați un nume simplificat: +Utilizați nume complet calificate doar la prima mențiune. Pentru linkurile ulterioare utilizați numele simplificat: - `[Form::setTranslator() |api:Nette\Forms\Form::setTranslator()]` -> [Form::setTranslator() |api:Nette\Forms\Form::setTranslator()] -Legături către documentația PHP .[#toc-links-to-php-documentation] ------------------------------------------------------------------- +Linkuri către documentația PHP +------------------------------ - `[php:substr]` -> [php:substr] -Codul sursă .[#toc-source-code] -=============================== +Cod sursă +========= -Blocul de cod începe cu <code>```lang</code> și se termină cu <code>```</code> Limbajele acceptate sunt `php`, `latte`, `neon`, `html`, `css`, `js` și `sql`. Folosiți întotdeauna tabulatoare pentru indentare. +Blocul de cod începe cu <code>```lang</code> și se termină cu <code>```</code>. Limbajele suportate sunt `php`, `latte`, `neon`, `html`, `css`, `js` și `sql`. Pentru indentare utilizați întotdeauna tabulatori. ``` ```php @@ -63,7 +63,7 @@ Blocul de cod începe cu <code>```lang</code> și se termină cu <co ``` ``` -De asemenea, puteți specifica numele fișierului ca <code>```php .{file: ArrayTest.php}</code> și blocul de cod va fi redat în acest mod: +Puteți specifica și numele fișierului ca <code>```php .{file: ArrayTest.php}</code> și blocul de cod se va reda în acest mod: ```php .{file: ArrayTest.php} public function renderPage($id) @@ -72,71 +72,71 @@ public function renderPage($id) ``` -Rubrici .[#toc-headings] -======================== +Titluri +======= -Titlul de sus (numele paginii) subliniat cu stele (`*`). For normal headings use equal signs (`=`) and then hyphens (`-`). +Titlul cel mai înalt (adică numele paginii) subliniați-l cu asteriscuri. Pentru separarea secțiunilor utilizați semne de egal. Subliniați titlurile cu semne de egal și apoi cu cratime: ``` -MVC Applications & Presenters -***************************** +Aplicații MVC & presenteri +************************** ... -Link Creation -============= +Crearea linkurilor +================== ... -Links in Templates ------------------- +Linkuri în șabloane +------------------- ... ``` -Casete și stiluri .[#toc-boxes-and-styles] -========================================== +Cadre și stiluri +================ -Paragraf principal marcat cu clasa `.[perex]` .[perex] +Perexul îl marcăm cu clasa `.[perex]` .[perex] -Note marcate cu clasa `.[note]` .[note] +Nota o marcăm cu clasa `.[note]` .[note] -Sfat marcat cu clasa `.[tip]` .[tip] +Sfatul îl marcăm cu clasa `.[tip]` .[tip] -Avertisment marcat cu clasa `.[caution]` .[caution] +Avertismentul îl marcăm cu clasa `.[caution]` .[caution] -Avertisment puternic marcat cu o clasă `.[warning]` .[warning] +Avertismentul mai accentuat îl marcăm cu clasa `.[warning]` .[warning] Numărul versiunii `.{data-version:2.4.10}` .{data-version:2.4.10} -Clasele trebuie scrise înainte de linia corespunzătoare: +Scrieți clasele înainte de rând: ``` -.[note] -This is a note. +.[perex] +Acesta este perexul. ``` -Vă rugăm să rețineți că rubricile de tipul `.[tip]` atrag atenția și, prin urmare, ar trebui folosite pentru evidențiere, nu pentru informații mai puțin importante. +Vă rugăm să rețineți că cadrele precum `.[tip]` "atrag" ochii, deci se utilizează pentru accentuare, nu pentru informații mai puțin importante. Prin urmare, utilizați-le cu maximă economie. -Cuprins .[#toc-table-of-contents] -================================= +Cuprins +======= -Cuprinsul (linkurile din bara laterală) este generat automat atunci când pagina are o lungime mai mare de 4 000 de octeți. Acest comportament implicit poate fi modificat cu ajutorul unui `{{toc}}` [meta tag |#meta-tags]. Textul pentru TOC este preluat în mod implicit din titlu, dar este posibil să se utilizeze un text diferit cu o tag `.{toc}` modificator. Acest lucru este util în special pentru titlurile mai lungi. +Cuprinsul (linkurile din meniul din dreapta) este generat automat pentru toate paginile a căror dimensiune depășește 4 000 de octeți, acest comportament implicit putând fi modificat folosind [#Meta tag-uri] `{{toc}}`. Textul care formează cuprinsul este preluat standard direct din textul titlurilor, dar folosind modificatorul `.{toc}` este posibil să se afișeze în cuprins un alt text, ceea ce este util în special pentru titlurile mai lungi. ``` -Long and Intelligent Heading .{toc: A Different Text for TOC} -============================================================= +Titlu lung și inteligent .{toc: Orice alt text afișat în cuprins} +================================================================= ``` -Etichete meta .[#toc-meta-tags] -=============================== +Meta tag-uri +============ -- setarea propriului titlu al paginii (în `<title>` și breadcrumbs) `{{title: Another name}}` -- redirecționarea `{{redirect: pla:cs}}` - vezi [linkuri |#links] -- aplicarea `{{toc}}` sau dezactivarea `{{toc: no}}` tabelul de conținut +- setarea unui nume personalizat pentru pagină (în `<title>` și navigarea breadcrumb) `{{title: Alt nume}}` +- redirecționare `{{redirect: pla:cs}}` - vezi [#Linkuri] +- forțarea `{{toc}}` sau interzicerea `{{toc: no}}` cuprinsului automat (căsuța cu linkuri către titlurile individuale) {{priority: -1}} diff --git a/contributing/ru/@home.texy b/contributing/ru/@home.texy index bde49c5235..2714687ce7 100644 --- a/contributing/ru/@home.texy +++ b/contributing/ru/@home.texy @@ -1,17 +1,17 @@ -Стать вкладчиком Nette -********************** +Станьте контрибьютором Nette +**************************** .[perex] -Посмотрите, как вы можете принять участие в нашем проекте с открытым исходным кодом. Узнайте, как внести свой вклад в исходный код и документацию, и присоединитесь к сети разработчиков, которые стремятся улучшить Nette. +Узнайте, как вы можете принять участие в нашем open source проекте. Освойте процедуры внесения вклада в исходный код и документацию и станьте частью сообщества разработчиков, активно участвующих в совершенствовании Nette. **Код** -- [Вклад в разработку кода |code] -- [Стандарты кодирования |coding-standard] +- [Как внести вклад в код? |code] +- [Стандарт кодирования |coding-standard] **Документация** -- [Вклад в документацию |documentation] +- [Как внести вклад в документацию? |documentation] - [Синтаксис документации |syntax] - "Редактор предварительного просмотра":https://editor.nette.org diff --git a/contributing/ru/@left-menu.texy b/contributing/ru/@left-menu.texy index 472ef9f1cd..97c6e23d70 100644 --- a/contributing/ru/@left-menu.texy +++ b/contributing/ru/@left-menu.texy @@ -1,10 +1,10 @@ Код *** -- [Вклад в код |code] -- [Стандарты кодирования |coding-standard] +- [Как внести вклад в код? |code] +- [Стандарт кодирования |coding-standard] Документация ************ -- [Вклад в документацию |documentation] +- [Как внести вклад в документацию? |documentation] - [Синтаксис документации |syntax] - "Редактор предварительного просмотра":https://editor.nette.org diff --git a/contributing/ru/code.texy b/contributing/ru/code.texy index 9e2239ab22..b6ed03248e 100644 --- a/contributing/ru/code.texy +++ b/contributing/ru/code.texy @@ -1,87 +1,87 @@ -Вклад в код -*********** +Как внести вклад в код +********************** .[perex] -Вы планируете внести свой вклад в Nette Framework и вам необходимо ознакомиться с правилами и процедурами? Это руководство для начинающих расскажет вам о том, как эффективно вносить вклад в код, работать с репозиториями и внедрять изменения. +Вы собираетесь внести свой вклад в Nette Framework и вам нужно разобраться в правилах и процедурах? Это руководство для начинающих шаг за шагом покажет вам, как эффективно вносить вклад в код, работать с репозиториями и внедрять изменения. -Процедура .[#toc-procedure] -=========================== +Процедура +========= -Чтобы внести свой вклад в код, необходимо иметь учетную запись на [GitHub |https://github.com] и быть знакомым с основами работы с системой контроля версий Git. Если вы не знакомы с Git, вы можете ознакомиться с [git - простым руководством |https://rogerdudler.github.io/git-guide/] и рассмотреть возможность использования одного из многочисленных [графических клиентов |https://git-scm.com/downloads/guis]. +Для внесения вклада в код необходимо иметь учетную запись на [GitHub|https://github.com] и быть знакомым с основами работы с системой контроля версий Git. Если вы не владеете работой с Git, вы можете ознакомиться с руководством [git - простое руководство |https://rogerdudler.github.io/git-guide/] и, при необходимости, использовать один из множества [графических клиентов |https://git-scm.com/downloads/guis]. -Подготовка среды и репозитория .[#toc-preparing-the-environment-and-repository] -------------------------------------------------------------------------------- +Подготовка среды и репозитория +------------------------------ -1) На GitHub создайте [форк |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] [репозитория пакета |www:packages], который вы собираетесь изменить -2) [Клонируйте |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] этот репозиторий на свой компьютер -3) Установите зависимости, включая [Nette Tester |tester:], с помощью команды `composer install`. -4) Убедитесь, что тесты работают, выполнив команду `composer tester` -5) Создайте [новую ветку |#New Branch] на основе последней выпущенной версии +1) на GitHub создайте [форк |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] репозитория [пакета |www:packages], который вы собираетесь изменить +2) этот репозиторий [клонируйте |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] на свой компьютер +3) установите зависимости, включая [Nette Tester |tester:], с помощью команды `composer install` +4) проверьте, что тесты работают, запустив `composer tester` +5) создайте [новую ветку |#Новая ветка], основанную на последней выпущенной версии -Внедрение собственных изменений .[#toc-implementing-your-own-changes] ---------------------------------------------------------------------- +Реализация собственных изменений +-------------------------------- -Теперь вы можете внести собственные изменения в код: +Теперь вы можете внести свои собственные изменения в код: -1) Внесите желаемые изменения и не забудьте о тестах -2) Убедитесь, что тесты успешно выполняются `composer tester` -3) Проверьте, соответствует ли код [стандартам кодирования |#coding standards] -4) Сохраните (зафиксируйте) изменения с описанием в [таком формате |#Commit Description] +1) реализуйте необходимые изменения и не забудьте о тестах +2) убедитесь, что тесты успешно проходят, с помощью `composer tester` +3) проверьте, соответствует ли код [стандартам кодирования |#Стандарты кодирования] +4) сохраните изменения (сделайте коммит) с описанием в [этом формате |#Описание коммита] -Вы можете создать несколько коммитов, по одному для каждого логического шага. Каждый коммит должен быть значимым сам по себе. +Вы можете создать несколько коммитов, по одному для каждого логического шага. Каждый коммит должен быть осмысленным сам по себе. -Представление изменений .[#toc-submitting-changes] --------------------------------------------------- +Отправка изменений +------------------ -После того как вы будете удовлетворены изменениями, вы можете отправить их: +Как только вы будете удовлетворены изменениями, вы можете их отправить: -1) Внесите изменения на GitHub в свой форк. -2) Оттуда отправьте их в репозиторий Nette, создав [pull request|https://help.github.com/articles/creating-a-pull-request] внесение изменений (PR). -3) Предоставьте [достаточную информацию |#pull request description] в описании +1) отправьте (push) изменения на GitHub в ваш форк +2) оттуда отправьте их в репозиторий Nette, создав [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) +3) укажите в описании [достаточно информации |#Описание pull request] -Включение обратной связи .[#toc-incorporating-feedback] -------------------------------------------------------- +Учет замечаний +-------------- -Теперь ваши коммиты видны другим. Часто можно получить комментарии с предложениями: +Ваши коммиты теперь увидят и другие. Обычно вы получаете комментарии с замечаниями: -1) Следить за предлагаемыми изменениями -2) Включить их в новые коммиты или [объединить с предыдущими |https://help.github.com/en/github/using-git/about-git-rebase] -3) Повторно отправить исправления на GitHub, и они автоматически появятся в запросе на исправление. +1) следите за предлагаемыми изменениями +2) внесите их как новые коммиты или [слейте с предыдущими |https://help.github.com/en/github/using-git/about-git-rebase] +3) снова отправьте коммиты на GitHub, и они автоматически появятся в pull request Никогда не создавайте новый pull request для изменения существующего. -Документация .[#toc-documentation] ----------------------------------- +Документация +------------ Если вы изменили функциональность или добавили новую, не забудьте также [добавить ее в документацию |documentation]. -Новый филиал .[#toc-new-branch] -=============================== +Новая ветка +=========== -Если возможно, вносите изменения в последнюю выпущенную версию, т.е. в последний тег в ветке. Для тега v3.2.1 создайте ветку с помощью этой команды: +Если возможно, вносите изменения относительно последней выпущенной версии, т.е. последнего тега в данной ветке. Для тега `v3.2.1` вы создадите ветку этой командой: ```shell git checkout -b new_branch_name v3.2.1 ``` -Стандарты кодирования .[#toc-coding-standards] -============================================== +Стандарты кодирования +===================== -Ваш код должен соответствовать [стандартам кодирования |coding standard], используемым в Nette Framework. Существует автоматический инструмент для проверки и исправления кода. Вы можете установить его **глобально** через Composer в выбранную вами папку: +Ваш код должен соответствовать [стандарту кодирования |coding standard], используемому в Nette Framework. Для проверки и исправления кода доступен автоматический инструмент. Его можно установить через Composer **глобально** в выбранную вами папку: ```shell composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Теперь вы должны быть в состоянии запустить инструмент в терминале. Первая команда проверяет, а вторая исправляет код в папках `src` и `tests` в текущем каталоге: +Теперь вы должны иметь возможность запустить инструмент в терминале. Первой командой вы проверите, а второй — исправите код в папках `src` и `tests` в текущем каталоге: ```shell /path/to/nette-coding-standard/ecs check @@ -89,24 +89,24 @@ composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Описание обязательств .[#toc-commit-description] -================================================ +Описание коммита +================ -В Nette темы коммитов имеют следующий формат: `Presenter: fixed AJAX detection [Closes #69]` +В Nette темы коммитов имеют формат: `Presenter: fixed AJAX detection [Closes #69]` - область, за которой следует двоеточие -- цель фиксации в прошедшем времени; если возможно, начинайте с таких слов, как: added, fixed, refactored, changed, removed +- цель коммита в прошедшем времени, если возможно, начните со слова: "added .(добавлено новое свойство)", "fixed .(исправление)", "refactored .(изменение в коде без изменения поведения)", changed, removed - если коммит нарушает обратную совместимость, добавьте "BC break" -- любая связь с трекером проблем, например, `(#123)` или `[Closes #69]` -- после темы может быть одна пустая строка, за которой следует более подробное описание, включая, например, ссылки на форум +- возможная связь с трекером issue, например `(#123)` или `[Closes #69]` +- за темой может следовать одна пустая строка, а затем более подробное описание, включая, например, ссылки на форум -Описание Pull Request .[#toc-pull-request-description] -====================================================== +Описание pull request +===================== -При создании pull request интерфейс GitHub позволит вам ввести название и описание. Укажите лаконичное название и включите в описание как можно больше информации о причинах вашего изменения. +При создании pull request интерфейс GitHub позволит вам указать название и описание. Укажите информативное название и в описании предоставьте как можно больше информации о причинах вашего изменения. -Также укажите в заголовке, является ли это новой функцией или исправлением ошибки, и может ли это привести к проблемам обратной совместимости (BC break). Если существует связанная с ним проблема, укажите ссылку на нее, чтобы она была закрыта после одобрения запроса. +Также отобразится заголовок, где укажите, является ли это новой функцией или исправлением ошибки, и может ли произойти нарушение обратной совместимости (BC break). Если есть связанная проблема (issue), ссылайтесь на нее, чтобы она была закрыта после одобрения pull request. ``` - bug fix / new feature? <!-- #issue numbers, if any --> diff --git a/contributing/ru/coding-standard.texy b/contributing/ru/coding-standard.texy index 169b2b7c88..2a4a960d9f 100644 --- a/contributing/ru/coding-standard.texy +++ b/contributing/ru/coding-standard.texy @@ -1,44 +1,44 @@ Стандарт кодирования ******************** -В этом документе описаны правила и рекомендации по разработке Nette. Предоставляя код для Nette, вы должны следовать им. Самый простой способ сделать это — имитировать существующий код. -Идея заключается в том, чтобы весь код выглядел так, как будто его написал один человек. +.[perex] +Этот документ описывает правила и рекомендации для разработки Nette. При внесении вклада в код Nette вы должны их соблюдать. Самый простой способ сделать это — подражать существующему коду. Цель в том, чтобы весь код выглядел так, как будто его написал один человек. -Стандарт кодирования Nette соответствует [PSR-12 Extended Coding Style |https://www.php-fig.org/psr/psr-12/] с двумя основными исключениями: он использует [#tabs вместо пробелов] для отступов и использует [PascalCase для констант классов|https://blog.nette.org/ru/ctoby-men-se-kricat-v-kode]. +Стандарт кодирования Nette соответствует [PSR-12 Extended Coding Style |https://www.php-fig.org/psr/psr-12/] с двумя основными исключениями: для отступов он использует [#Табуляции вместо пробелов] и для [констант классов использует PascalCase|https://blog.nette.org/ru/for-less-screaming-in-the-code]. -Общие правила .[#toc-general-rules] -=================================== +Общие правила +============= -- Каждый файл PHP должен содержать `declare(strict_types=1)` -- Две пустые строки используются для разделения методов для лучшей читабельности. -- Причина использования оператора закрытия должна быть задокументирована: `@mkdir($dir); // @ - directory may exist` -- Если используется слабо типизированный оператор сравнения (т.е. `==`, `!=`, ...), то намерение должно быть задокументировано: `// == to accept null` -- Вы можете записать больше исключений в один файл `exceptions.php` -- Видимость методов не указывается для интерфейсов, поскольку они всегда являются общедоступными. -- Для каждого свойства, возвращаемого значения и параметра должен быть указан тип. С другой стороны, для конечных констант мы никогда не указываем тип, потому что он очевиден. -- Для разделения строки следует использовать одинарные кавычки, за исключением случаев, когда сам литерал содержит апострофы. +- Каждый PHP-файл должен содержать `declare(strict_types=1)` +- Две пустые строки используются для разделения методов для лучшей читаемости. +- Причина использования оператора подавления ошибок `@` должна быть задокументирована: `@mkdir($dir); // @ - каталог может существовать`. +- Если используется оператор сравнения со слабой типизацией (т.е. `==`, `!=`, ...), намерение должно быть задокументировано: `// == принять null` +- В один файл `exceptions.php` можно записать несколько исключений. +- У интерфейсов не указывается видимость методов, так как они всегда публичные. +- Каждое свойство, возвращаемое значение и параметр должны иметь указанный тип. Напротив, у финальных констант тип никогда не указываем, так как он очевиден. +- Для обрамления строки следует использовать одинарные кавычки, за исключением случаев, когда сам литерал содержит апострофы. -Соглашения об именовании .[#toc-naming-conventions] -=================================================== +Соглашения об именовании +======================== -- Не используйте аббревиатуры, если только полное имя не слишком длинное. -- Используйте прописные буквы для двухбуквенных аббревиатур, паскаль/камель для более длинных аббревиатур. -- Используйте существительное или словосочетание для названия класса. -- Имена классов должны содержать не только конкретику (`Array`), но и обобщение (`ArrayIterator`). Исключением являются атрибуты PHP. -- "Константы классов и перечисления должны использовать PascalCaps":https://blog.nette.org/ru/ctoby-men-se-kricat-v-kode. -- "Интерфейсы и абстрактные классы не должны содержать префиксы или суффиксы":https://blog.nette.org/ru/prefiksy-i-suffiksy-ne-dolzny-prisutstvovat-v-imenah-interfejsov, такие как `Abstract`, `Interface` или `I`. +- Не используйте сокращения, если полное имя не слишком длинное. +- Для двухбуквенных аббревиатур используйте заглавные буквы, для более длинных аббревиатур — PascalCase/camelCase. +- Для имени класса используйте существительное или словосочетание. +- Имена классов должны содержать не только специфичность (`Array`), но и общность (`ArrayIterator`). Исключением являются атрибуты языка PHP. +- "Константы классов и перечисления должны использовать PascalCaps":https://blog.nette.org/ru/for-less-screaming-in-the-code. +- "Интерфейсы и абстрактные классы не должны содержать префиксы или суффиксы":https://blog.nette.org/ru/prefixes-and-suffixes-do-not-belong-in-interface-names, такие как `Abstract`, `Interface` или `I`. -Обертывание и брекеты .[#toc-wrapping-and-braces] -================================================= +Перенос строк и скобки +====================== -Nette Coding Standard соответствует PSR-12 (или PER Coding Style), в некоторых пунктах он уточняет его больше или модифицирует: +Стандарт кодирования Nette соответствует PSR-12 (или PER Coding Style), в некоторых пунктах он его дополняет или изменяет: -- стрелочные функции записываются без пробела перед скобкой, т.е. `fn($a) => $b`. -- между различными типами операторов импорта `use` не требуется пустая строка -- возвращаемый тип функции/метода и открывающая скобка должны располагаться на отдельных строках для лучшей читабельности: +- стрелочные функции пишутся без пробела перед скобкой, т.е. `fn($a) => $b` +- не требуется пустая строка между различными типами импортов `use` +- возвращаемый тип функции/метода и открывающая фигурная скобка всегда находятся на отдельных строках: ```php public function find( @@ -50,19 +50,23 @@ Nette Coding Standard соответствует PSR-12 (или PER Coding Style } ``` +Открывающая фигурная скобка на отдельной строке важна для визуального разделения сигнатуры функции/метода от тела. Если сигнатура находится на одной строке, разделение очевидно (рисунок слева), если на нескольких строках, в PSR сигнатуры и тела сливаются (посередине), в то время как в стандарте Nette они остаются разделенными (справа): -Блоки документации (phpDoc) .[#toc-documentation-blocks-phpdoc] -=============================================================== +[* new-line-after.webp *] -Главное правило: никогда не дублируйте информацию о сигнатуре, например, тип параметра или тип возврата, не имея никакой дополнительной цели. + +Блоки документации (phpDoc) +=========================== + +Основное правило: Никогда не дублируйте никакую информацию в сигнатуре, такую как тип параметра или возвращаемый тип, без добавленной ценности. Блок документации для определения класса: - Начинается с описания класса. -- Далее следует пустая строка. -- Далее следуют аннотации `@property` (или `@property-read`, `@property-write`), одна за другой. Синтаксис следующий: аннотация, пробел, тип, пробел, $name. -- Далее следуют аннотации `@method`, одна за другой. Синтаксис следующий: аннотация, пробел, возвращаемый тип, пробел, имя(тип $param, ...). -- Аннотация `@author` опущена. Авторство сохраняется в истории исходного кода. +- Следует пустая строка. +- Следуют аннотации `@property` (или `@property-read`, `@property-write`), одна за другой. Синтаксис: аннотация, пробел, тип, пробел, $имя. +- Следуют аннотации `@method`, одна за другой. Синтаксис: аннотация, пробел, возвращаемый тип, пробел, имя(тип $param, ...). +- Аннотация `@author` опускается. Авторство сохраняется в истории исходного кода. - Можно использовать аннотации `@internal` или `@deprecated`. ```php @@ -76,7 +80,7 @@ Nette Coding Standard соответствует PSR-12 (или PER Coding Style */ ``` -Блок документации для свойства, содержащего только аннотацию `@var`, должен быть однострочным: +Блок документации для свойства, содержащий только аннотацию `@var`, должен быть однострочным: ```php /** @var string[] */ @@ -87,12 +91,12 @@ private array $name; - Начинается с краткого описания метода. - Нет пустой строки. -- Аннотации `@param`, одна за другой. +- Аннотации `@param` по отдельным строкам. - Аннотация `@return`. - Аннотации `@throws`, одна за другой. - Можно использовать аннотации `@internal` или `@deprecated`. -За каждой аннотацией следует один пробел, за исключением `@param`, за которым следуют два пробела для лучшей читабельности. +За каждой аннотацией следует один пробел, за исключением `@param`, за которой для лучшей читаемости следуют два пробела. ```php /** @@ -105,21 +109,20 @@ public function find(string $dir, array $options): array ``` -Табуляция вместо пробелов .[#toc-tabs-instead-of-spaces] -======================================================== +Табуляции вместо пробелов +========================= -Табуляция имеет ряд преимуществ перед пробелами: +Табуляции имеют несколько преимуществ перед пробелами: -- размер отступа настраивается в редакторах и "веб":https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size -- они не навязывают коду предпочтения пользователя по размеру отступа, поэтому код более переносим -- их можно набрать одним нажатием клавиши (где угодно, а не только в редакторах, которые превращают табуляцию в пробел) -- отступы являются их целью -- уважать потребности слабовидящих и слепых коллег. +- размер отступа можно настроить в редакторах и на "веб-сайте":https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size +- они не навязывают коду предпочтение пользователя в размере отступа, поэтому код лучше переносим +- их можно написать одним нажатием клавиши (где угодно, не только в редакторах, которые заменяют табуляции на пробелы) +- отступы — это их смысл +- они уважают потребности коллег с нарушениями зрения и незрячих -Используя табуляции в наших проектах, мы позволяем настраивать ширину, что может показаться ненужным большинству людей, но крайне важно для людей с нарушениями зрения. +Используя табуляции в наших проектах, мы позволяем настраивать ширину, что большинству людей может показаться излишеством, но для людей с нарушениями зрения это необходимо. -Для слепых программистов, использующих дисплеи Брайля, каждый пробел представлен ячейкой Брайля и занимает ценное пространство. Так, если отступ по умолчанию составляет 4 пробела, отступ третьего уровня отнимает 12 ячеек Брайля перед началом кода. -На 40-ячеечном дисплее, который чаще всего используется на ноутбуках, это более четверти доступных ячеек, потраченных впустую без какой-либо информации. +Для незрячих программистов, использующих дисплеи Брайля, каждый пробел представляет собой одну ячейку Брайля. Если, таким образом, отступ по умолчанию составляет 4 пробела, отступ 3-го уровня тратит 12 ценных ячеек Брайля еще до начала кода. На 40-ячеечном дисплее, который чаще всего используется в ноутбуках, это более четверти доступных ячеек, которые тратятся без какой-либо информации. {{priority: -1}} diff --git a/contributing/ru/documentation.texy b/contributing/ru/documentation.texy index 0cd5eaf805..dab56db27d 100644 --- a/contributing/ru/documentation.texy +++ b/contributing/ru/documentation.texy @@ -1,69 +1,68 @@ -Вклад в документацию -******************** +Как внести вклад в документацию +******************************* .[perex] -Вклад в документацию является одним из наиболее ценных видов деятельности, поскольку он помогает другим понять концепцию. +Внесение вклада в документацию — одно из самых полезных занятий, поскольку вы помогаете другим понять фреймворк. -Как писать? .[#toc-how-to-write] --------------------------------- +Как писать? +----------- -Документация в первую очередь предназначена для людей, которые впервые сталкиваются с этой темой. Поэтому она должна отвечать нескольким важным требованиям: +Документация предназначена в первую очередь для людей, которые знакомятся с темой. Поэтому она должна соответствовать нескольким важным пунктам: -- Начинайте с простых и общих тем. В конце переходите к более сложным темам. -- Старайтесь объяснять тему как можно понятнее. Например, попробуйте сначала объяснить тему коллеге. -- Предоставляйте только ту информацию, которую пользователю действительно необходимо знать по данной теме. -- Убедитесь, что ваша информация точна. Тестируйте каждый код -- Будьте лаконичны - сократите то, что вы пишете, вдвое. А затем не стесняйтесь сделать это снова. -- Используйте выделение экономно, от жирного шрифта до рамок типа `.[note]` -- Следуйте [стандарту кодирования |Coding Standard] в коде +- Начните с простого и общего. К более сложным темам переходите только в конце +- Старайтесь объяснить вещь как можно лучше. Попробуйте, например, сначала объяснить тему коллеге +- Приводите только ту информацию, которая действительно нужна пользователю по данной теме +- Убедитесь, что ваша информация действительно верна. Каждый код протестируйте +- Будьте краткими - то, что вы напишете, сократите наполовину. А потом, возможно, еще раз +- Экономьте на выделениях всех видов, от жирного шрифта до рамок типа `.[note]` +- В коде соблюдайте [Стандарт кодирования |Coding Standard] -Также изучите [синтаксис |syntax]. Для предварительного просмотра статьи во время написания вы можете использовать [редактор предварительного просмотра |https://editor.nette.org/]. +Освойте также [синтаксис |syntax]. Для предварительного просмотра статьи во время ее написания вы можете использовать [редактор с предпросмотром |https://editor.nette.org/]. -Мутации языка .[#toc-language-mutations] ----------------------------------------- +Языковые версии +--------------- -Английский является основным языком, поэтому ваши изменения должны быть на английском. Если английский не является вашей сильной стороной, используйте [DeepL Переводчик |https://www.deepl.com/translator], и другие проверят ваш текст. +Основным языком является английский, поэтому ваши изменения должны быть на английском. Если английский не является вашей сильной стороной, используйте [DeepL Translator |https://www.deepl.com/translator], и другие проверят ваш текст. -Перевод на другие языки будет выполнен автоматически после одобрения и доработки вашей правки. +Перевод на другие языки будет выполнен автоматически после утверждения и доработки вашего изменения. -Тривиальные правки .[#toc-trivial-edits] ----------------------------------------- +Незначительные правки +--------------------- -Чтобы внести свой вклад в документацию, вам необходимо иметь учетную запись на [GitHub |https://github.com]. +Для внесения вклада в документацию необходимо иметь учетную запись на [GitHub|https://github.com]. -Самый простой способ внести небольшие изменения в документацию - воспользоваться ссылками в конце каждой страницы: +Самый простой способ внести небольшое изменение в документацию — использовать ссылки в конце каждой страницы: -- *Показать на GitHub* открывает исходную версию страницы на GitHub. Затем просто нажмите кнопку `E`, и вы сможете начать редактирование (вы должны быть зарегистрированы на GitHub). -- *Открыть предварительный просмотр* открывает редактор, где вы можете сразу увидеть окончательный визуальный вид +- *Показать на GitHub* откроет исходную версию данной страницы на GitHub. Затем достаточно нажать кнопку `E` и можно начинать редактировать (необходимо быть авторизованным на GitHub) +- *Открыть предпросмотр* откроет редактор, где вы сразу увидите и итоговый визуальный вид -Поскольку [редактор предварительного просмотра |https://editor.nette.org/] не имеет возможности сохранять изменения непосредственно на GitHub, вам необходимо скопировать исходный текст в буфер обмена (с помощью кнопки *Копировать в буфер обмена*), а затем вставить его в редактор на GitHub. -Ниже поля редактирования находится форма для отправки. Здесь не забудьте кратко изложить и объяснить причину вашей правки. После отправки создается так называемый pull request (PR), который можно в дальнейшем редактировать. +Поскольку [редактор с предпросмотром |https://editor.nette.org/] не имеет возможности сохранять изменения прямо на GitHub, необходимо после завершения правок скопировать исходный текст в буфер обмена (кнопкой *Copy to clipboard*), а затем вставить его в редактор на GitHub. Под полем редактирования находится форма для отправки. Здесь не забудьте кратко изложить и объяснить причину вашей правки. После отправки создается так называемый pull request (PR), который можно дальше редактировать. -Более крупные правки .[#toc-larger-edits] ------------------------------------------ +Более крупные правки +-------------------- -Целесообразнее ознакомиться с основами работы с системой контроля версий Git, чем полагаться только на интерфейс GitHub. Если вы не знакомы с Git, вы можете обратиться к [git - простому руководству |https://rogerdudler.github.io/git-guide/] и рассмотреть возможность использования одного из множества доступных [графических клиентов |https://git-scm.com/downloads/guis]. +Более подходящим, чем использование интерфейса GitHub, является знакомство с основами работы с системой контроля версий Git. Если вы не владеете работой с Git, вы можете ознакомиться с руководством [git - простое руководство |https://rogerdudler.github.io/git-guide/] и, при необходимости, использовать один из множества [графических клиентов |https://git-scm.com/downloads/guis]. -Отредактируйте документацию следующим образом: +Документацию редактируйте следующим образом: 1) на GitHub создайте [форк |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] репозитория [nette/docs |https://github.com/nette/docs] -2) [клонируйте |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] этот репозиторий на свой компьютер -3) затем внесите изменения в [соответствующую ветку |#Documentation Structure] -4) проверьте наличие лишних пробелов в тексте с помощью инструмента [Code-Checker |code-checker:] -5) сохраните (зафиксируйте) изменения -6) если вы удовлетворены изменениями, отправьте их на GitHub в свой форк -7) оттуда отправьте их в репозиторий `nette/docs`, создав [pull request|https://help.github.com/articles/creating-a-pull-request] исправление (PR). +2) этот репозиторий [клонируйте |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] на свой компьютер +3) затем в [соответствующей ветке |#Структура документации] внесите изменения +4) проверьте лишние пробелы в тексте с помощью инструмента [Code-Checker |code-checker:] +4) сохраните изменения (сделайте коммит) +6) если вы удовлетворены изменениями, отправьте (push) их на GitHub в ваш форк +7) оттуда отправьте их в репозиторий `nette/docs`, создав [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) -Обычно вы получаете комментарии с предложениями. Отслеживайте предложенные изменения и учитывайте их. Добавьте предложенные изменения как новые коммиты и повторно отправьте их в GitHub. Никогда не создавайте новый pull request только для того, чтобы изменить существующий. +Обычно вы будете получать комментарии с замечаниями. Следите за предлагаемыми изменениями и вносите их. Предлагаемые изменения добавляйте как новые коммиты и снова отправляйте на GitHub. Никогда не создавайте новый pull request для изменения существующего pull request. -Структура документации .[#toc-documentation-structure] ------------------------------------------------------- +Структура документации +---------------------- -Вся документация находится на GitHub в репозитории [nette/docs |https://github.com/nette/docs]. Текущая версия находится в ветке master, а старые версии расположены в ветках `doc-3.x`, `doc-2.x`. +Вся документация размещена на GitHub в репозитории [nette/docs |https://github.com/nette/docs]. Актуальная версия находится в ветке master, старые версии размещены в ветках, таких как `doc-3.x`, `doc-2.x`. -Содержимое каждой ветки разделено на основные папки, представляющие отдельные области документации. Например, `application/` соответствует https://doc.nette.org/en/application, `latte/` соответствует https://latte.nette.org, и т.д. Каждая из этих папок содержит вложенные папки, представляющие языковые мутации (`cs`, `en`, ...) и, по желанию, вложенную папку `files` с изображениями, которые могут быть вставлены в страницы документации. +Содержимое каждой ветки делится на основные папки, представляющие отдельные области документации. Например, `application/` соответствует https://doc.nette.org/ru/application, `latte/` соответствует https://latte.nette.org и т.д. Каждая такая папка содержит подпапки, представляющие языковые версии (`en`, `ru`, ...), и, возможно, подпапку `files` с изображениями, которые можно вставлять на страницы документации. diff --git a/contributing/ru/syntax.texy b/contributing/ru/syntax.texy index c1827ea5d4..af9411a740 100644 --- a/contributing/ru/syntax.texy +++ b/contributing/ru/syntax.texy @@ -1,59 +1,59 @@ -Синтаксис вики -************** +Синтаксис документации +********************** -Wiki использует [синтаксис |https://texy.info/en/syntax] Markdown & [Texy |https://texy.info/en/syntax] с некоторыми улучшениями. +Документация использует синтаксис Markdown и [синтаксис Texy |https://texy.nette.org/syntax] с некоторыми расширениями. -Ссылки .[#toc-links] -==================== +Ссылки +====== -Для внутренних ссылок используется обозначение в квадратных скобках `[link]` используется. Это либо в форме с вертикальной полосой `[link text |link target]`, либо в сокращенной форме `[link text]` если цель совпадает с текстом (после преобразования в нижний регистр и дефисов): +Для внутренних ссылок используется запись в квадратных скобках `[ссылка |odkaz]`. Либо в виде с вертикальной чертой `[текст ссылки |цель ссылки]`, либо сокращенно `[текст ссылки |text odkazu]`, если цель совпадает с текстом (после преобразования в нижний регистр и дефисы): -- `[Page name]` -> `<a href="/en/page-name">Page name</a>` -- `[link text |Page name]` -> `<a href="/en/page-name">link text</a>` +- `[Page name]` -> `<a href="/ru/page-name">Page name</a>` +- `[текст ссылки |Page name]` -> `<a href="/ru/page-name">текст ссылки</a>` -Мы можем сделать ссылку на другой язык или на другой раздел. Раздел - это библиотека Nette (например, `forms`, `latte`, и т.д.) или специальные разделы, такие как `best-practices`, `quickstart`, и т.д: +Мы можем ссылаться на другую языковую версию или на другой раздел. Разделом считается библиотека Nette (например, `forms`, `latte` и т.д.) или специальные разделы, такие как `best-practices`, `quickstart` и т.д.: -- `[cs:Page name]` -> `<a href="/en/page-name">Page name</a>` (тот же раздел, другой язык) -- `[tracy:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (другой раздел, тот же язык) -- `[tracy:cs:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (другой раздел и язык) +- `[cs:Page name]` -> `<a href="/cs/page-name">Page name</a>` (тот же раздел, другой язык) +- `[tracy:Page name]` -> `<a href="//tracy.nette.org/ru/page-name">Page name</a>` (другой раздел, тот же язык) +- `[tracy:cs:Page name]` -> `<a href="//tracy.nette.org/cs/page-name">Page name</a>` (другой раздел и язык) -Также можно нацелить определенный заголовок на странице с помощью `#`. +С помощью `#` также можно нацелиться на конкретный заголовок на странице. - `[#Heading]` -> `<a href="#toc-heading">Heading</a>` (заголовок на текущей странице) -- `[Page name#Heading]` -> `<a href="/en/page-name#toc-heading">Page name</a>` +- `[Page name#Heading]` -> `<a href="/ru/page-name#toc-heading">Page name</a>` -Ссылка на главную страницу раздела: (`@home` - специальный термин для домашней страницы раздела) +Ссылка на главную страницу раздела: (`@home` — это специальное выражение для домашней страницы раздела) -- `[link text |@home]` -> `<a href="/en/">link text</a>` -- `[link text |tracy:]` -> `<a href="//tracy.nette.org/en/">link text</a>` +- `[текст ссылки |@home]` -> `<a href="/ru/">текст ссылки</a>` +- `[текст ссылки |tracy:]` -> `<a href="//tracy.nette.org/ru/">текст ссылки</a>` -Ссылки на документацию API .[#toc-links-to-api-documentation] -------------------------------------------------------------- +Ссылки на документацию API +-------------------------- -Всегда используйте следующие обозначения: +Всегда указывайте только с помощью этой записи: - `[api:Nette\SmartObject]` -> [api:Nette\SmartObject] - `[api:Nette\Forms\Form::setTranslator()]` -> [api:Nette\Forms\Form::setTranslator()] - `[api:Nette\Forms\Form::$onSubmit]` -> [api:Nette\Forms\Form::$onSubmit] - `[api:Nette\Forms\Form::Required]` -> [api:Nette\Forms\Form::Required] -Полные имена используются только в первом упоминании. Для остальных ссылок используйте упрощенное название: +Полностью квалифицированные имена используйте только при первом упоминании. Для последующих ссылок используйте упрощенное имя: - `[Form::setTranslator() |api:Nette\Forms\Form::setTranslator()]` -> [Form::setTranslator() |api:Nette\Forms\Form::setTranslator()] -Ссылки на документацию по PHP .[#toc-links-to-php-documentation] ----------------------------------------------------------------- +Ссылки на документацию PHP +-------------------------- - `[php:substr]` -> [php:substr] -Исходный код .[#toc-source-code] -================================ +Исходный код +============ -Блок кода начинается с <code>```lang</code> и заканчивается <code>```</code> Поддерживаются следующие языки: `php`, `latte`, `neon`, `html`, `css`, `js` и `sql`. Всегда используйте табуляцию для отступов. +Блок кода начинается с <code>```lang</code> и заканчивается <code>```</code>. Поддерживаемые языки: `php`, `latte`, `neon`, `html`, `css`, `js` и `sql`. Для отступов всегда используйте табуляции. ``` ```php @@ -63,7 +63,7 @@ Wiki использует [синтаксис |https://texy.info/en/syntax] Mark ``` ``` -Вы также можете указать имя файла как <code>```php .{file: ArrayTest.php}</code> и блок кода будет отображаться таким образом: +Вы также можете указать имя файла как <code>```php .{file: ArrayTest.php}</code>, и блок кода будет отображен следующим образом: ```php .{file: ArrayTest.php} public function renderPage($id) @@ -72,71 +72,71 @@ public function renderPage($id) ``` -Заголовки .[#toc-headings] -========================== +Заголовки +========= -Верхний заголовок (название страницы) подчеркните звездочками (`*`). For normal headings use equal signs (`=`) and then hyphens (`-`). +Самый верхний заголовок (т.е. название страницы) подчеркните звездочками. Для разделения секций используйте знаки равенства. Заголовки подчеркивайте знаками равенства, а затем дефисами: ``` -MVC Applications & Presenters -***************************** +MVC Приложения и презентеры +*************************** ... -Link Creation -============= +Создание ссылок +=============== ... -Links in Templates ------------------- +Ссылки в шаблонах +----------------- ... ``` -Вставки и стили .[#toc-boxes-and-styles] -======================================== +Рамки и стили +============= -Ведущий абзац, помеченный классом `.[perex]` .[perex] +Вступление обозначаем классом `.[perex]` .[perex] -Примечания с пометкой класс `.[note]` .[note] +Примечание обозначаем классом `.[note]` .[note] -Совет, отмеченный классом `.[tip]` .[tip] +Совет обозначаем классом `.[tip]` .[tip] -Предупреждение, отмеченное классом `.[caution]` .[caution] +Предостережение обозначаем классом `.[caution]` .[caution] -Строгое предупреждение, отмеченное классом `.[warning]` .[warning] +Серьезное предупреждение обозначаем классом `.[warning]` .[warning] Номер версии `.{data-version:2.4.10}` .{data-version:2.4.10} -Классы должны быть написаны перед связанной строкой: +Классы записывайте перед строкой: ``` -.[note] -This is a note. +.[perex] +Это вступление. ``` -Обратите внимание, что такие поля, как `.[tip]` привлекают внимание и поэтому должны использоваться для подчеркивания, а не для менее важной информации. +Пожалуйста, учтите, что рамки типа `.[tip]` "притягивают" взгляд, поэтому они используются для выделения, а не для менее важной информации. Поэтому максимально экономьте их использование. -Оглавление .[#toc-table-of-contents] -==================================== +Содержание +========== -Оглавление (ссылки в боковой панели) автоматически генерируется, если длина страницы превышает 4 000 байт. Это поведение по умолчанию можно изменить с помощью `{{toc}}` [метатега |#meta-tags]. Текст для TOC по умолчанию берется из заголовка, но можно использовать другой текст с помощью модификатора `.{toc}` модификатора. Это особенно полезно для длинных заголовков. +Содержание (ссылки в правом меню) генерируется автоматически для всех страниц, размер которых превышает 4 000 байт, при этом это поведение по умолчанию можно изменить с помощью [#Мета-теги] `{{toc}}`. Текст, составляющий содержание, берется стандартно прямо из текста заголовков, но с помощью модификатора `.{toc}` можно отобразить в содержании другой текст, что полезно в основном для длинных заголовков. ``` -Long and Intelligent Heading .{toc: A Different Text for TOC} -============================================================= +Длинный и умный заголовок .{toc: Любой другой текст, отображаемый в содержании} +=============================================================================== ``` -Мета-теги .[#toc-meta-tags] -=========================== +Мета-теги +========= -- установка собственного заголовка страницы (в `<title>` и хлебных крошках) `{{title: Another name}}` -- перенаправление `{{redirect: pla:cs}}` - [ссылки для просмотра|#links] -- принудительный `{{toc}}` или отключение `{{toc: no}}` таблица содержания +- установка собственного названия страницы (в `<title>` и хлебных крошках) `{{title: Другое название}}` +- перенаправление `{{redirect: pla:cs}}` - см. [#Ссылки] +- принудительное `{{toc}}` или запрет `{{toc: no}}` автоматического содержания (блок со ссылками на отдельные заголовки) {{priority: -1}} diff --git a/contributing/sl/@home.texy b/contributing/sl/@home.texy index fbea1ec169..00b95b7b0d 100644 --- a/contributing/sl/@home.texy +++ b/contributing/sl/@home.texy @@ -1,17 +1,17 @@ -Postanite sodelavec Nette -************************* +Postanite prispevalec k Nette +***************************** .[perex] -Oglejte si, kako lahko sodelujete pri našem odprtokodnem projektu. Spoznajte korake za prispevanje k izvorni kodi in dokumentaciji ter se pridružite mreži razvijalcev, ki si prizadevajo izboljšati Nette. +Ugotovite, kako se lahko vključite v naš odprtokodni projekt. Osvojite postopke za prispevanje k izvorni kodi in dokumentaciji ter postanite del skupnosti razvijalcev, ki aktivno sodelujejo pri izboljševanju Nette. **Koda** -- [Prispevek h kodi |code] -- [Standardi kodiranja |coding-standard] +- [Kako prispevati h kodi? |code] +- [Standard kodiranja |coding-standard] **Dokumentacija** -- [Prispevek k dokumentaciji |documentation] +- [Kako prispevati k dokumentaciji? |documentation] - [Sintaksa dokumentacije |syntax] -- "Urejevalnik predogledov":https://editor.nette.org +- "Predogledni urejevalnik":https://editor.nette.org diff --git a/contributing/sl/@left-menu.texy b/contributing/sl/@left-menu.texy index 97b9de9b72..87eefc02fa 100644 --- a/contributing/sl/@left-menu.texy +++ b/contributing/sl/@left-menu.texy @@ -1,10 +1,10 @@ Koda **** -- [Prispevek h kodi |code] -- [Standardi kodiranja |coding-standard] +- [Kako prispevati h kodi? |code] +- [Standard kodiranja |coding-standard] Dokumentacija ************* -- [Prispevek k dokumentaciji |documentation] +- [Kako prispevati k dokumentaciji? |documentation] - [Sintaksa dokumentacije |syntax] -- "Urejevalnik predogledov":https://editor.nette.org +- "Predogledni urejevalnik":https://editor.nette.org diff --git a/contributing/sl/code.texy b/contributing/sl/code.texy index 968d13cd56..6bd373891b 100644 --- a/contributing/sl/code.texy +++ b/contributing/sl/code.texy @@ -1,87 +1,87 @@ -Prispevek h kodi -**************** +Kako prispevati h kodi +********************** .[perex] -Ali nameravate prispevati k ogrodju Nette in se morate seznaniti s pravili in postopki? Ta priročnik za začetnike vas bo popeljal skozi korake za učinkovito prispevanje h kodi, delo z repozitoriji in izvajanje sprememb. +Se pripravljate prispevati k Nette Frameworku in potrebujete orientacijo glede pravil in postopkov? Ta vodnik za začetnike vam bo korak za korakom pokazal, kako učinkovito prispevati h kodi, delati z repozitoriji in implementirati spremembe. -Postopek .[#toc-procedure] -========================== +Postopek +======== -Če želite prispevati h kodi, morate imeti račun na [GitHubu |https://github.com] in poznati osnove dela s sistemom za nadzor različic Git. Če sistema Git ne poznate, si lahko ogledate priročnik [git - the simple guide |https://rogerdudler.github.io/git-guide/] in razmislite o uporabi enega od številnih [grafičnih odjemalcev |https://git-scm.com/downloads/guis]. +Za prispevanje h kodi je nujno imeti račun na [GitHub|https://github.com] in biti seznanjen z osnovami dela z verzijskim sistemom Git. Če ne obvladate dela z Gitom, si lahko ogledate vodnik [git - the simple guide |https://rogerdudler.github.io/git-guide/] in po potrebi uporabite katerega od mnogih [grafičnih klientov |https://git-scm.com/downloads/guis]. -Priprava okolja in skladišča .[#toc-preparing-the-environment-and-repository] ------------------------------------------------------------------------------ +Priprava okolja in repozitorija +------------------------------- -1) Na spletnem mestu GitHub ustvarite [vilico |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] [skladišča paketov |www:packages], ki ga nameravate spremeniti -2) [Klonirate |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] to shrambo v svoj računalnik -3) Z ukazom `composer install` namestite odvisnosti, vključno s [programom Nette Tester |tester:]. -4) Preverite, ali testi delujejo, tako da zaženete `composer tester` -5) Ustvarite [novo vejo, ki |#New Branch] temelji na zadnji izdani različici +1) na GitHubu si ustvarite [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] repozitorija [paketa |www:packages], ki ga nameravate urejati +2) ta repozitorij [klonirajte |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] na svoj računalnik +3) namestite odvisnosti, vključno z [Nette Testerjem |tester:], z ukazom `composer install` +4) preverite, ali testi delujejo, z zagonom `composer tester` +5) ustvarite si [novo vejo |#Nova veja], ki temelji na zadnji izdani različici -Izvajanje lastnih sprememb .[#toc-implementing-your-own-changes] ----------------------------------------------------------------- +Implementacija lastnih sprememb +------------------------------- -Zdaj lahko izvedete lastne prilagoditve kode: +Zdaj lahko izvedete svoje lastne prilagoditve kode: -1) Izvedite želene spremembe in ne pozabite na teste -2) Poskrbite, da se testi uspešno izvedejo z uporabo `composer tester` -3) Preverite, ali koda ustreza [standardom kodiranja |#coding standards] -4) Shranite (commit) spremembe z opisom v [tej |#Commit Description]obliki +1) sprogramirajte zahtevane spremembe in ne pozabite na teste +2) prepričajte se, da testi uspešno potekajo, z uporabo `composer tester` +3) preverite, ali koda ustreza [standardom kodiranja |#Standardi kodiranja] +4) spremembe shranite (commitnite) z opisom v [tem formatu |#Opis commita] -Ustvarite lahko več zavez, po eno za vsak logični korak. Vsaka oddaja mora biti smiselna sama po sebi. +Lahko ustvarite več commitov, enega za vsak logični korak. Vsak commit bi moral biti smiseln sam po sebi. -Oddaja sprememb .[#toc-submitting-changes] ------------------------------------------- +Pošiljanje sprememb +------------------- -Ko ste s spremembami zadovoljni, jih lahko predložite: +Ko boste s spremembami zadovoljni, jih lahko pošljete: -1) Spremembe prenesite na GitHub v svojo vilico -2) Od tam jih predložite v skladišče Nette tako, da ustvarite [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) -3) V opisu navedite [dovolj informacij |#pull request description] +1) pošljite (pushnite) spremembe na GitHub v vaš fork +2) od tam jih pošljite v Nette repozitorij z ustvarjanjem [pull requesta|https://help.github.com/articles/creating-a-pull-request] (PR) +3) v opisu navedite [dovolj informacij |#Opis pull requesta] -Vključevanje povratnih informacij .[#toc-incorporating-feedback] ----------------------------------------------------------------- +Vključevanje pripomb +-------------------- -Vaše oddaje so zdaj vidne drugim. Pogosto prejmete komentarje s predlogi: +Vaše commite bodo zdaj videli tudi drugi. Običajno je, da boste prejeli komentarje s pripombami: -1) Spremljajte predlagane spremembe -2) Vključite jih kot nove spremembe ali [jih združite s prejšnjimi |https://help.github.com/en/github/using-git/about-git-rebase] -3) Ponovno pošljite spremembe v GitHub in samodejno se bodo pojavile v zahtevi za povlečenje. +1) spremljajte predlagane prilagoditve +2) vključite jih kot nove commite ali jih [združite s prejšnjimi |https://help.github.com/en/github/using-git/about-git-rebase] +3) ponovno pošljite commite na GitHub in samodejno se bodo pojavili v pull requestu -Nikoli ne ustvarjajte nove zahteve za prenos, da bi spremenili obstoječo zahtevo. +Nikoli ne ustvarjajte novega pull requesta zaradi urejanja obstoječega. -Dokumentacija .[#toc-documentation] ------------------------------------ +Dokumentacija +------------- -Če ste spremenili funkcionalnost ali dodali novo, ne pozabite [tega dodati tudi v dokumentacijo |documentation]. +Če ste spremenili funkcionalnost ali dodali novo, je ne pozabite tudi [dodati v dokumentacijo |documentation]. -Nova veja .[#toc-new-branch] -============================ +Nova veja +========= -Če je mogoče, spremembe izvedite glede na zadnjo izdano različico, tj. zadnjo oznako v veji. Za oznako v3.2.1 ustvarite vejo s tem ukazom: +Če je mogoče, izvajajte spremembe glede na zadnjo izdano različico, tj. zadnjo oznako (tag) v dani veji. Za oznako `v3.2.1` ustvarite vejo s tem ukazom: ```shell git checkout -b new_branch_name v3.2.1 ``` -Standardi kodiranja .[#toc-coding-standards] -============================================ +Standardi kodiranja +=================== -Vaša koda mora ustrezati [standardom kodiranja, |coding standard] ki se uporabljajo v okviru Nette. Za preverjanje in popravljanje kode je na voljo samodejno orodje. Namestite ga lahko **globalno** prek programa Composer v izbrano mapo: +Vaša koda mora ustrezati [standardu kodiranja |coding standard], ki se uporablja v Nette Frameworku. Za preverjanje in popravljanje kode je na voljo samodejno orodje. Lahko ga namestite prek Composerja **globalno** v mapo po vaši izbiri: ```shell composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Orodje lahko zaženete v terminalu. Prvi ukaz preveri, drugi pa popravi kodo v mapah `src` in `tests` v trenutnem imeniku: +Zdaj bi morali imeti možnost zagnati orodje v terminalu. S prvim ukazom preverite in z drugim tudi popravite kodo v mapah `src` in `tests` v trenutnem imeniku: ```shell /path/to/nette-coding-standard/ecs check @@ -89,29 +89,29 @@ Orodje lahko zaženete v terminalu. Prvi ukaz preveri, drugi pa popravi kodo v m ``` -Obveznost Opis .[#toc-commit-description] -========================================= +Opis commita +============ -V sistemu Nette imajo predmeti sprememb naslednjo obliko: `Presenter: fixed AJAX detection [Closes #69]` +V Nette imajo predmeti commitov format: `Presenter: fixed AJAX detection [Closes #69]` -- sledi dvopičje -- namen zaveze v preteklem času; če je mogoče, začnite z besedami, kot so: added, fixed, refactored, changed, removed -- če oddaja krši združljivost za nazaj, dodajte "BC break" -- kakršno koli povezavo s programom za sledenje težavam, na primer `(#123)` ali `[Closes #69]` -- za temo je lahko ena prazna vrstica, ki ji sledi podrobnejši opis, na primer vključno s povezavami do foruma +- področje, ki mu sledi dvopičje +- namen commita v preteklem času, če je mogoče, začnite z besedo: »added« (dodana nova lastnost), »fixed« (popravek), »refactored« (sprememba v kodi brez spremembe obnašanja), changed, removed +- če commit prekine povratno združljivost, dodajte »BC break« +- morebitna povezava z issue trackerjem kot `(#123)` ali `[Closes #69]` +- za subjektom lahko sledi ena prosta vrstica in nato podrobnejši opis, vključno na primer s povezavami na forum -Opis zahtevka za izvleček .[#toc-pull-request-description] -========================================================== +Opis pull requesta +================== -Pri ustvarjanju zahteve za prenos vam vmesnik GitHub omogoča vnos naslova in opisa. Navedite jedrnat naslov in v opis vključite čim več informacij o razlogih za vašo spremembo. +Pri ustvarjanju pull requesta vam vmesnik GitHub omogoča vnos naslova in opisa. Navedite jedrnat naslov in v opisu podajte čim več informacij o razlogih za vašo spremembo. -V naslovu navedite tudi, ali gre za novo funkcijo ali popravek napake in ali lahko povzroči težave s povratno združljivostjo (BC break). Če obstaja povezana težava, se nanjo povežite, tako da bo po odobritvi zahteve za spremembo zaprta. +Prikazala se bo tudi glava, kjer določite, ali gre za novo funkcijo ali popravek napake in ali lahko pride do prekinitve povratne združljivosti (BC break). Če obstaja povezan problem (issue), se nanj sklicujte, da bo zaprt po odobritvi pull requesta. ``` -- bug fix / new feature? <!-- #issue numbers, if any --> +- bug fix / new feature? <!-- #številke issue-jev, če obstajajo --> - BC break? yes/no -- doc PR: nette/docs#? <!-- highly welcome, see https://nette.org/en/writing --> +- doc PR: nette/docs#? <!-- zelo dobrodošlo, glej https://nette.org/en/writing --> ``` diff --git a/contributing/sl/coding-standard.texy b/contributing/sl/coding-standard.texy index 395eb2dd01..8b4e2ffb2b 100644 --- a/contributing/sl/coding-standard.texy +++ b/contributing/sl/coding-standard.texy @@ -1,44 +1,44 @@ Standard kodiranja ****************** -V tem dokumentu so opisana pravila in priporočila za razvoj Nette. Ko prispevate kodo za Nette, jih morate upoštevati. To najlažje storite tako, da posnemate obstoječo kodo. -Ideja je, da je vsa koda videti, kot da jo je napisala ena oseba. .[perex] +.[perex] +Ta dokument opisuje pravila in priporočila za razvoj Nette. Pri prispevanju kode k Nette jih morate upoštevati. Najlažji način za to je posnemanje obstoječe kode. Gre za to, da vsa koda izgleda, kot da jo je napisala ena oseba. -Kodirni standard Nette ustreza [razširjenemu kodirnemu slogu PSR-12 |https://www.php-fig.org/psr/psr-12/] z dvema glavnima izjemama: za alineje uporablja [tabulatorje namesto presledkov |#tabs instead of spaces] in za [konstante razredov |https://blog.nette.org/sl/za-manj-kricanja-v-kodi] uporablja [PascalCase |https://blog.nette.org/sl/za-manj-kricanja-v-kodi]. +Nette Coding Standard ustreza [PSR-12 Extended Coding Style |https://www.php-fig.org/psr/psr-12/] z dvema glavnima izjemama: za zamikanje uporablja [zavihke namesto presledkov |#Zavihki namesto presledkov] in za [konstante razredov uporablja PascalCase|https://blog.nette.org/sl/for-less-screaming-in-the-code]. -Splošna pravila .[#toc-general-rules] -===================================== +Splošna pravila +=============== -- Vsaka datoteka PHP mora vsebovati `declare(strict_types=1)` -- Dve prazni vrstici se uporabljata za ločevanje metod zaradi boljše berljivosti. -- Razlog za uporabo operatorja shut-up mora biti dokumentiran: `@mkdir($dir); // @ - directory may exist` -- Če se uporablja šibko tipiziran primerjalni operator (tj. `==`, `!=`, ...), je treba namen dokumentirati: `// == to accept null` -- V eno datoteko lahko zapišete več izjem `exceptions.php` -- Vidnost metod za vmesnike ni določena, ker so vedno javne. -- Vsaka lastnost, povratna vrednost in parameter morajo imeti določen tip. Po drugi strani pa za končne konstante nikoli ne določimo tipa, ker je to očitno. -- Za razmejitev niza je treba uporabiti enojne narekovaje, razen kadar sam literal vsebuje apostrofe. +- Vsaka PHP datoteka mora vsebovati `declare(strict_types=1)` +- Dve prazni vrstici se uporabljata za ločevanje metod za boljšo berljivost. +- Razlog za uporabo operatorja za utišanje (shut-up operator) mora biti dokumentiran: `@mkdir($dir); // @ - mapa lahko obstaja`. +- Če je uporabljen šibko tipiziran primerjalni operator (tj. `==`, `!=`, ...), mora biti namen dokumentiran: `// == sprejmi null` +- V eno datoteko `exceptions.php` lahko zapišete več izjem. +- Pri vmesnikih se ne določa vidnost metod, ker so vedno javne. +- Vsaka lastnost, vračana vrednost in parameter morajo imeti naveden tip. Nasprotno pa pri končnih konstantah tipa nikoli ne navajamo, ker je očiten. +- Za omejevanje niza naj se uporabljajo enojni narekovaji, razen v primerih, ko sam literal vsebuje apostrofe. -Konvencije za poimenovanje .[#toc-naming-conventions] -===================================================== +Poimenovalne konvencije +======================= -- Izogibajte se uporabi kratic, razen če je polno ime pretirano. -- Za dvočrkovne kratice uporabljajte velike črke, za daljše kratice pa pascal/camel črke. -- Za ime razreda uporabite samostalnik ali samostalniško besedno zvezo. -- Imena razredov morajo poleg specifičnosti (`Array`) vsebovati tudi splošnost (`ArrayIterator`). Izjema so atributi PHP. -- "Konstante razredov in enumi morajo uporabljati PascalCaps":https://blog.nette.org/sl/za-manj-kricanja-v-kodi -- "Vmesniki in abstraktni razredi ne smejo vsebovati predpon ali postfiksov":https://blog.nette.org/sl/predpone-in-pripone-ne-sodijo-v-imena-vmesnikov kot so `Abstract`, `Interface` ali `I`. +- Ne uporabljajte okrajšav, razen če je celotno ime predolgo. +- Pri dvočrkovnih okrajšavah uporabljajte velike črke, pri daljših okrajšavah pascal/camel. +- Za ime razreda uporabljajte samostalnik ali besedno zvezo. +- Imena razredov morajo vsebovati ne samo specifičnost (`Array`), ampak tudi splošnost (`ArrayIterator`). Izjema so atributi jezika PHP. +- "Konstante razredov in enumeracije naj uporabljajo PascalCaps":https://blog.nette.org/sl/for-less-screaming-in-the-code. +- "Vmesniki in abstraktni razredi ne smejo vsebovati predpon ali pripon":https://blog.nette.org/sl/prefixes-and-suffixes-do-not-belong-in-interface-names kot `Abstract`, `Interface` ali `I`. -Ovijanje in oglati oklepaji .[#toc-wrapping-and-braces] -======================================================= +Oblikovanje in oklepaji +======================= -Nette Coding Standard ustreza PSR-12 (ali PER Coding Style), v nekaterih točkah pa ga podrobneje opredeljuje ali spreminja: +Nette Coding Standard ustreza PSR-12 (oz. PER Coding Style), v nekaterih točkah ga dopolnjuje ali spreminja: -- puščice so zapisane brez presledka pred oklepajem, tj. `fn($a) => $b` -- med različnimi vrstami stavkov za uvoz `use` ni potrebna prazna vrstica -- tip vrnitve funkcije/metode in uvodni oklepaj morata biti zaradi boljše berljivosti postavljena v ločenih vrsticah: +- puščične funkcije se pišejo brez presledka pred oklepajem, tj. `fn($a) => $b` +- ne zahteva se prazna vrstica med različnimi tipi `use` import stavkov +- vračani tip funkcije/metode in začetni zaviti oklepaj sta vedno na ločenih vrsticah: ```php public function find( @@ -50,24 +50,28 @@ Nette Coding Standard ustreza PSR-12 (ali PER Coding Style), v nekaterih točkah } ``` +Začetni zaviti oklepaj na ločeni vrstici je pomemben za vizualno ločevanje signature funkcije/metode od telesa. Če je signatura na eni vrstici, je ločitev očitna (slika levo), če je na več vrsticah, se v PSR signaturi in telesi zlivata (sredina), medtem ko sta v Nette standardu še naprej ločeni (desno): -Bloki dokumentacije (phpDoc) .[#toc-documentation-blocks-phpdoc] -================================================================ +[* new-line-after.webp *] -Glavno pravilo: nikoli ne podvajajte informacij o podpisu, kot sta vrsta parametra ali vrsta vrnitve, brez dodane vrednosti. + +Bloki dokumentacije (phpDoc) +============================ + +Glavno pravilo: Nikoli ne podvajajte nobenih informacij v signaturi, kot je tip parametra ali vračani tip, brez dodane vrednosti. Dokumentacijski blok za definicijo razreda: - Začne se z opisom razreda. - Sledi prazna vrstica. -- Sledijo opombe `@property` (ali `@property-read`, `@property-write`), ena za drugo. Sintaksa je: anotacija, presledek, tip, presledek, $name. -- Sledijo `@method` anotacije, ena za drugo. Sintaksa je: annotation, space, return type, space, name(type $param, ...). -- Anotacija `@author` je izpuščena. Avtorstvo se hrani v zgodovini izvorne kode. -- Uporabita se lahko opombi `@internal` ali `@deprecated`. +- Sledijo anotacije `@property` (ali `@property-read`, `@property-write`), ena za drugo. Sintaksa je: anotacija, presledek, tip, presledek, $ime. +- Sledijo anotacije `@method`, ena za drugo. Sintaksa je: anotacija, presledek, vračani tip, presledek, ime(tip $param, ...). +- Anotacija `@author` se izpušča. Avtorstvo se hrani v zgodovini izvorne kode. +- Lahko se uporabita anotaciji `@internal` ali `@deprecated`. ```php /** - * MIME message part. + * MIME del sporočila. * * @property string $encoding * @property-read array $headers @@ -76,7 +80,7 @@ Dokumentacijski blok za definicijo razreda: */ ``` -Dokumentacijski blok za lastnost, ki vsebuje samo opombo `@var`, mora biti v eni vrstici: +Dokumentacijski blok za lastnost, ki vsebuje samo anotacijo `@var`, bi moral biti enovrstičen: ```php /** @var string[] */ @@ -87,16 +91,16 @@ Dokumentacijski blok za definicijo metode: - Začne se s kratkim opisom metode. - Brez prazne vrstice. -- Anotacije `@param`, ena za drugo. -- Opomba `@return`. +- Anotacije `@param` po posameznih vrsticah. +- Anotacija `@return`. - Anotacije `@throws`, ena za drugo. -- Uporabijo se lahko anotacije `@internal` ali `@deprecated`. +- Lahko se uporabita anotaciji `@internal` ali `@deprecated`. -Vsaki anotaciji sledi en presledek, razen `@param`, ki ji zaradi boljše berljivosti sledita dva presledka. +Vsaki anotaciji sledi en presledek, z izjemo `@param`, za katero za boljšo berljivost sledita dva presledka. ```php /** - * Finds a file in directory. + * Najde datoteko v imeniku. * @param string[] $options * @return string[] * @throws DirectoryNotFoundException @@ -105,21 +109,20 @@ public function find(string $dir, array $options): array ``` -Tabelatorji namesto presledkov .[#toc-tabs-instead-of-spaces] -============================================================= +Zavihki namesto presledkov +========================== -Zavihki imajo več prednosti pred presledki: +Zavihki imajo v primerjavi s presledki več prednosti: -- velikost alineje je mogoče prilagoditi v urejevalnikih in "spletu":https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size -- v kodo ne vsiljujejo uporabnikovih preferenc glede velikosti alineje, zato je koda bolj prenosljiva -- lahko jih vnesete z enim pritiskom tipke (kjer koli, ne le v urejevalnikih, ki zavihke spremenijo v presledke) -- njihov namen je izrezovanje -- spoštujte potrebe slabovidnih in slepih sodelavcev +- velikost zamika je mogoče prilagoditi v urejevalnikih in na "spletu":https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size +- kodi ne vsiljujejo uporabnikove preference glede velikosti zamika, zato je koda bolje prenosljiva +- lahko jih napišemo z enim pritiskom tipke (kjerkoli, ne samo v urejevalnikih, ki spreminjajo zavihke v presledke) +- zamikanje je njihov namen +- spoštujejo potrebe slabovidnih in slepih kolegov -Z uporabo zavihkov v naših projektih omogočamo prilagajanje širine, kar se večini ljudi morda zdi nepotrebno, vendar je bistvenega pomena za ljudi z okvarami vida. +Z uporabo zavihkov v naših projektih omogočamo prilagajanje širine, kar se večini ljudi morda zdi nepotrebno, vendar je za ljudi z okvaro vida nujno. -Za slepe programerje, ki uporabljajo brajeve zaslone, je vsak presledek predstavljen z brajevo celico in zavzema dragocen prostor. Če je privzeta alineja 4 presledki, se pri alineji tretje stopnje pred začetkom kode porabi 12 celic brajeve pisave. -Na 40-celičnem zaslonu, ki se najpogosteje uporablja na prenosnih računalnikih, je to več kot četrtina razpoložljivih celic, ki so zapravljene brez kakršne koli informacije. +Za slepe programerje, ki uporabljajo braillove zaslone, vsak presledek predstavlja eno braillovo celico. Če je torej privzeti zamik 4 presledki, zamik 3. stopnje zapravi 12 dragocenih braillovih celic, še preden se koda začne. Na 40-celičnem zaslonu, ki se najpogosteje uporablja pri prenosnikih, je to več kot četrtina razpoložljivih celic, ki so zapravljene brez kakršnekoli informacije. {{priority: -1}} diff --git a/contributing/sl/documentation.texy b/contributing/sl/documentation.texy index 261c76bd9a..8ae7191d0c 100644 --- a/contributing/sl/documentation.texy +++ b/contributing/sl/documentation.texy @@ -1,69 +1,68 @@ -Prispevek k dokumentaciji -************************* +Kako prispevati k dokumentaciji +******************************* .[perex] -Prispevek k dokumentaciji je ena izmed najbolj dragocenih dejavnosti, saj drugim pomaga razumeti ogrodje. +Prispevanje k dokumentaciji je ena najbolj koristnih dejavnosti, saj pomagate drugim razumeti ogrodje. -Kako pisati? .[#toc-how-to-write] ---------------------------------- +Kako pisati? +------------ -Dokumentacija je v prvi vrsti namenjena ljudem, ki se s tem področjem šele spoznavajo. Zato mora izpolnjevati več pomembnih točk: +Dokumentacija je namenjena predvsem ljudem, ki se s temo seznanjajo. Zato bi morala izpolnjevati nekaj pomembnih točk: -- Začnite s preprostimi in splošnimi temami. Na koncu preidite na naprednejše teme -- Poskusite temo razložiti čim bolj jasno. Na primer, poskusite temo najprej razložiti sodelavcu. -- Navedite le informacije, ki jih uporabnik dejansko potrebuje za določeno temo -- Prepričajte se, da so vaše informacije točne. Preizkusite vsako kodo -- Bodite jedrnati - napišite manj kot polovico. In potem to še enkrat ponovite. -- Varčno uporabljajte poudarke, od krepkih pisav do okvirjev, kot so `.[note]` -- V kodi upoštevajte [standard kodiranja |Coding Standard] +- Začnite s preprostim in splošnim. K naprednejšim temam preidite šele na koncu. +- Poskusite stvar čim bolje pojasniti. Poskusite na primer temo najprej pojasniti kolegu. +- Navajajte samo tiste informacije, ki jih uporabnik dejansko potrebuje vedeti o dani temi. +- Preverite, ali so vaše informacije resnično pravilne. Vsako kodo preizkusite. +- Bodite jedrnati - kar napišete, skrajšajte na polovico. In potem mirno še enkrat. +- Varčujte z vsemi vrstami poudarkov, od krepke pisave do okvirjev kot `.[note]`. +- V kodah upoštevajte [Standard kodiranja |Coding Standard]. -Naučite se tudi [sintakse |syntax]. Za predogled članka med pisanjem lahko uporabite [urejevalnik za predogled |https://editor.nette.org/]. +Osvojite tudi [sintakso |syntax]. Za predogled članka med pisanjem lahko uporabite [urejevalnik s predogledom |https://editor.nette.org/]. -Jezikovne mutacije .[#toc-language-mutations] ---------------------------------------------- +Jezikovne različice +------------------- -Osnovni jezik je angleščina, zato morajo biti vaše spremembe v angleščini. Če angleščina ni vaša močna stran, uporabite [prevajalnik DeepL |https://www.deepl.com/translator] in drugi bodo preverili vaše besedilo. +Primarni jezik je angleščina, zato bi morale biti vaše spremembe najprej v angleščini. Če angleščina ni vaša močna stran, uporabite [DeepL Translator |https://www.deepl.com/translator] in drugi vam bodo besedilo preverili. -Prevod v druge jezike bo opravljen samodejno po odobritvi in dodelavi vašega urejanja. +Prevod v ostale jezike bo izveden samodejno po odobritvi in dodelavi vaše prilagoditve. -Trivialna urejanja .[#toc-trivial-edits] ----------------------------------------- +Manjše prilagoditve +------------------- -Če želite prispevati k dokumentaciji, morate imeti račun na [GitHubu |https://github.com]. +Za prispevanje k dokumentaciji je nujno imeti račun na [GitHub|https://github.com]. -Najlažji način za majhne spremembe v dokumentaciji je uporaba povezav na koncu vsake strani: +Najlažji način za manjšo spremembo v dokumentaciji je uporaba povezav na koncu vsake strani: -- *Pokaži na GitHubu* odpre izvorno različico strani na GitHubu. Nato samo pritisnite gumb `E` in lahko začnete urejati (prijavljeni morate biti v GitHub). -- *Open preview* odpre urejevalnik, v katerem si lahko takoj ogledate končno vizualno obliko +- *Pokaži na GitHubu* odpre izvorno obliko dane strani na GitHubu. Nato samo pritisnite gumb `E` in lahko začnete urejati (potrebno je biti prijavljen na GitHubu). +- *Odpri predogled* odpre urejevalnik, kjer takoj vidite tudi končno vizualno podobo. -Ker [urejevalnik predogleda |https://editor.nette.org/] nima možnosti neposrednega shranjevanja sprememb v GitHub, morate izvorno besedilo kopirati v odložišče (z gumbom *Kopiraj v odložišče*) in ga nato prilepiti v urejevalnik na GitHubu. -Pod poljem za urejanje je obrazec za oddajo. Tu ne pozabite na kratko povzeti in pojasniti razloga za svoje urejanje. Po oddaji se ustvari tako imenovana zahteva za povleko (pull request, PR), ki jo lahko še naprej urejate. +Ker [urejevalnik s predogledom |https://editor.nette.org/] nima možnosti shranjevanja sprememb neposredno na GitHub, je treba po končanem urejanju izvorni tekst kopirati v odložišče (z gumbom *Copy to clipboard*) in ga nato prilepiti v urejevalnik na GitHubu. Pod urejevalnim poljem je obrazec za pošiljanje. Tukaj ne pozabite na kratko povzeti in pojasniti razlog vaše prilagoditve. Po pošiljanju nastane t.i. pull request (PR), ki ga je mogoče nadalje urejati. -Večja urejanja .[#toc-larger-edits] ------------------------------------ +Večje prilagoditve +------------------ -Primerneje je poznati osnove dela s sistemom za nadzor različic Git in se ne zanašati samo na vmesnik GitHub. Če sistema Git ne poznate, si lahko ogledate [priročnik git - the simple guide |https://rogerdudler.github.io/git-guide/] in razmislite o uporabi enega od številnih [grafičnih odjemalcev |https://git-scm.com/downloads/guis], ki so na voljo. +Primernejše kot uporaba vmesnika GitHub je biti seznanjen z osnovami dela z verzijskim sistemom Git. Če ne obvladate dela z Gitom, si lahko ogledate vodnik [git - the simple guide |https://rogerdudler.github.io/git-guide/] in po potrebi uporabite katerega od mnogih [grafičnih klientov |https://git-scm.com/downloads/guis]. -Dokumentacijo uredite na naslednji način: +Dokumentacijo urejajte na ta način: -1) na spletnem mestu GitHub ustvarite [vilico |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] skladišča [nette/docs |https://github.com/nette/docs] -2) to skladišče [klonirajte |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] v svoj računalnik -3) nato vnesite spremembe v [ustrezno vejo |#Documentation Structure] -4) z orodjem [Code-Checker |code-checker:] preverite, ali so v besedilu dodatni presledki -5) shranite (commit) spremembe -6) če ste s spremembami zadovoljni, jih potisnite v GitHub v svojo vilico -7) od tam jih pošljite v repozitorij `nette/docs` tako, da ustvarite [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) +1) na GitHubu si ustvarite [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] repozitorija [nette/docs |https://github.com/nette/docs] +2) ta repozitorij [klonirajte |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] na svoj računalnik +3) nato v [ustrezni veji |#Struktura dokumentacije] izvedite spremembe +4) preverite odvečne presledke v besedilu z orodjem [Code-Checker |code-checker:] +4) spremembe shranite (commitnite) +6) če ste s spremembami zadovoljni, jih pošljite (pushnite) na GitHub v vaš fork +7) od tam jih pošljite v repozitorij `nette/docs` z ustvarjanjem [pull requesta|https://help.github.com/articles/creating-a-pull-request] (PR) -Pogosto se zgodi, da prejmete komentarje s predlogi. Spremljajte predlagane spremembe in jih vključite. Predlagane spremembe dodajte kot nove spremembe in jih ponovno pošljite v GitHub. Nikoli ne ustvarjajte nove zahteve za prenos samo zato, da bi spremenili obstoječo zahtevo. +Običajno je, da boste prejemali komentarje s pripombami. Spremljajte predlagane spremembe in jih vključite. Predlagane spremembe dodajte kot nove commite in ponovno pošljite na GitHub. Nikoli ne ustvarjajte novega pull requesta zaradi urejanja pull requesta. -Struktura dokumentacije .[#toc-documentation-structure] -------------------------------------------------------- +Struktura dokumentacije +----------------------- -Celotna dokumentacija se nahaja na GitHubu v skladišču [nette/docs |https://github.com/nette/docs]. Trenutna različica je v glavni veji, starejše različice pa se nahajajo v vejah, kot so `doc-3.x`, `doc-2.x`. +Celotna dokumentacija je nameščena na GitHubu v repozitoriju [nette/docs |https://github.com/nette/docs]. Trenutna različica je v veji `master`, starejše različice so nameščene v vejah kot `doc-3.x`, `doc-2.x`. -Vsebina vsake veje je razdeljena na glavne mape, ki predstavljajo posamezna področja dokumentacije. Na primer, `application/` ustreza https://doc.nette.org/en/application, `latte/` ustreza https://latte.nette.org itd. Vsaka od teh map vsebuje podmape, ki predstavljajo jezikovne mutacije (`cs`, `en`, ...), in po želji podmapo `files` s slikami, ki jih je mogoče vstaviti na strani v dokumentaciji. +Vsebina vsake veje se deli na glavne mape, ki predstavljajo posamezna področja dokumentacije. Na primer `application/` ustreza https://doc.nette.org/sl/application, `latte/` ustreza https://latte.nette.org/sl itd. Vsaka ta mapa vsebuje podmape, ki predstavljajo jezikovne različice (`sl`, `en`, ...) in po potrebi podmapo `files` s slikami, ki jih je mogoče vstavljati na strani v dokumentaciji. diff --git a/contributing/sl/syntax.texy b/contributing/sl/syntax.texy index 81c058cf86..55aed5ac18 100644 --- a/contributing/sl/syntax.texy +++ b/contributing/sl/syntax.texy @@ -1,59 +1,59 @@ -Skladnja Wiki -************* +Sintaksa dokumentacije +********************** -Wiki uporablja [sintakso |https://texy.info/en/syntax] Markdown in [Texy |https://texy.info/en/syntax] z več izboljšavami. +Dokumentacija uporablja Markdown & [sintakso Texy |https://texy.nette.org/syntax] z nekaterimi razširitvami. -Povezave .[#toc-links] -====================== +Povezave +======== -Pri notranjih sklicih se uporablja zapis v oglatih oklepajih `[link]` se uporablja. Ta je bodisi v obliki z navpično črto `[link text |link target]`ali v skrajšani obliki `[link text]` če je cilj enak besedilu (po pretvorbi v male črke in pomišljaje): +Za notranje povezave se uporablja zapis v oglatih oklepajih `[povezava]`. In sicer bodisi v obliki z navpičnico `[besedilo povezave |cilj povezave]`, bodisi skrajšano `[besedilo povezave]`, če je cilj enak besedilu (po pretvorbi v male črke in pomišljaje): -- `[Page name]` -> `<a href="/en/page-name">Page name</a>` -- `[link text |Page name]` -> `<a href="/en/page-name">link text</a>` +- `[Ime strani |Page name]` -> `<a href="/en/page-name">Ime strani</a>` +- `[besedilo povezave |Page name]` -> `<a href="/en/page-name">besedilo povezave</a>` -Lahko se povežemo z drugim jezikom ali drugim razdelkom. Razdelek je knjižnica Nette (npr. `forms`, `latte` itd.) ali posebni razdelki, kot so `best-practices`, `quickstart` itd: +Povezujemo lahko v drugo jezikovno različico ali v drugo sekcijo. Sekcija pomeni Nette knjižnico (npr. `forms`, `latte`, ipd.) ali posebne sekcije kot `best-practices`, `quickstart` itd.: -- `[cs:Page name]` -> `<a href="/en/page-name">Page name</a>` (isti razdelek, drug jezik) -- `[tracy:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (drugo poglavje, isti jezik) -- `[tracy:cs:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (druga sekcija in jezik) +- `[cs:Ime strani |cs:Page name]` -> `<a href="/cs/page-name">cs:Ime strani</a>` (ista sekcija, drug jezik) +- `[tracy:Ime strani |tracy:Page name]` -> `<a href="//tracy.nette.org/en/page-name">tracy:Ime strani</a>` (druga sekcija, isti jezik) +- `[tracy:cs:Ime strani |tracy:cs:Page name]` -> `<a href="//tracy.nette.org/cs/page-name">tracy:cs:Ime strani</a>` (druga sekcija in jezik) -Z `#` lahko ciljate tudi na določen naslov na strani. +S pomočjo `#` je mogoče tudi ciljati na določen naslov na strani. -- `[#Heading]` -> `<a href="#toc-heading">Heading</a>` (naslov na trenutni strani) -- `[Page name#Heading]` -> `<a href="/en/page-name#toc-heading">Page name</a>` +- `[Naslov |#Heading]` -> `<a href="#toc-heading">Naslov</a>` (naslov na trenutni strani) +- `[Ime strani#Naslov |Page name#Heading]` -> `<a href="/en/page-name#toc-heading">Ime strani#Naslov</a>` -Povezava na domačo stran oddelka: (`@home` je poseben izraz za domačo stran sekcije) +Povezava na uvodno stran sekcije: (`@home` je poseben izraz za domačo stran sekcije) -- `[link text |@home]` -> `<a href="/en/">link text</a>` -- `[link text |tracy:]` -> `<a href="//tracy.nette.org/en/">link text</a>` +- `[besedilo povezave |@home]` -> `<a href="/en/">besedilo povezave</a>` +- `[besedilo povezave |tracy:]` -> `<a href="//tracy.nette.org/en/">besedilo povezave</a>` -Povezave do dokumentacije API .[#toc-links-to-api-documentation] ----------------------------------------------------------------- +Povezave do API dokumentacije +----------------------------- -Vedno uporabljajte naslednje zapise: +Vedno navajajte samo s tem zapisom: - `[api:Nette\SmartObject]` -> [api:Nette\SmartObject] - `[api:Nette\Forms\Form::setTranslator()]` -> [api:Nette\Forms\Form::setTranslator()] - `[api:Nette\Forms\Form::$onSubmit]` -> [api:Nette\Forms\Form::$onSubmit] - `[api:Nette\Forms\Form::Required]` -> [api:Nette\Forms\Form::Required] -Popolnoma kvalificirana imena uporabite samo v prvi omembi. Za druge povezave uporabite poenostavljeno ime: +Popolnoma kvalificirana imena uporabljajte samo ob prvi omembi. Za nadaljnje povezave uporabite poenostavljeno ime: -- `[Form::setTranslator() |api:Nette\Forms\Form::setTranslator()]` -> [Form::setTranslator( |api:Nette\Forms\Form::setTranslator()]) +- `[Form::setTranslator() |api:Nette\Forms\Form::setTranslator()]` -> [Form::setTranslator() |api:Nette\Forms\Form::setTranslator()] -Povezave do dokumentacije PHP .[#toc-links-to-php-documentation] ----------------------------------------------------------------- +Povezave do PHP dokumentacije +----------------------------- - `[php:substr]` -> [php:substr] -Izvorna koda .[#toc-source-code] -================================ +Izvorna koda +============ -Blok kode se začne z <code>```lang</code> in konča z <code>```</code> Podprti jeziki so `php`, `latte`, `neon`, `html`, `css`, `js` in `sql`. Za alinejo vedno uporabljajte tabulatorje. +Blok kode se začne z <code>```lang</code> in konča z <code>```</code>. Podprti jeziki so `php`, `latte`, `neon`, `html`, `css`, `js` in `sql`. Za zamikanje vedno uporabljajte zavihke. ``` ```php @@ -63,7 +63,7 @@ Blok kode se začne z <code>```lang</code> in konča z <code>`&# ``` ``` -Ime datoteke lahko določite tudi kot <code>```php .{file: ArrayTest.php}</code> in blok kode bo prikazan na ta način: +Lahko tudi navedete ime datoteke kot <code>```php .{file: ArrayTest.php}</code> in blok kode se bo izrisal na ta način: ```php .{file: ArrayTest.php} public function renderPage($id) @@ -72,71 +72,71 @@ public function renderPage($id) ``` -Naslovi .[#toc-headings] -======================== +Naslovi +======= -Zgornji naslov (ime strani) podčrtajte z zvezdicami (`*`). For normal headings use equal signs (`=`) and then hyphens (`-`). +Najvišji naslov (torej ime strani) podčrtajte z zvezdicami. Za ločevanje sekcij uporabljajte enačaje. Naslove podčrtajte z enačaji in nato s pomišljaji: ``` -MVC Applications & Presenters -***************************** +MVC Aplikacije & presenterji +**************************** ... -Link Creation -============= +Ustvarjanje povezav +=================== ... -Links in Templates ------------------- +Povezave v predlogah +-------------------- ... ``` -Okviri in slogi .[#toc-boxes-and-styles] -======================================== +Okvirji in stili +================ -Vodilni odstavek, označen z razredom `.[perex]` .[perex] +Perex označimo z razredom `.[perex]` -Opombe, označene z razredom `.[note]` .[note] +Opombo označimo z razredom `.[note]` -Nasvet, označen z razredom `.[tip]` .[tip] +Nasvet označimo z razredom `.[tip]` -Opozorilo označeno z razredom `.[caution]` .[caution] +Opozorilo označimo z razredom `.[caution]` -Močno opozorilo, označeno z razredom `.[warning]` .[warning] +Močnejše opozorilo označimo z razredom `.[warning]` -Številka različice `.{data-version:2.4.10}` .{data-version:2.4.10} +Številko različice `.{data-version:2.4.10}` -Razredi morajo biti zapisani pred povezano vrstico: +Razrede zapišite pred vrstico: ``` -.[note] -This is a note. +.[perex] +To je perex. ``` -Upoštevajte, da se polja, kot so `.[tip]` pritegnejo pozornost, zato jih je treba uporabljati za poudarjanje in ne za manj pomembne informacije. +Zavedajte se prosim, da okvirji kot `.[tip]` »pritegnejo« oči, zato se uporabljajo za poudarjanje, ne pa za manj pomembne informacije. Zato z njihovo uporabo maksimalno varčujte. -Kazalo vsebine .[#toc-table-of-contents] -======================================== +Vsebina +======= -Kazalo vsebine (povezave v stranski vrstici) se samodejno ustvari, če je stran daljša od 4 000 bajtov. To privzeto obnašanje lahko spremenite z ukazom `{{toc}}` [meta značko |#meta-tags]. Besedilo za TOC se privzeto prevzame iz naslova, vendar je mogoče uporabiti drugačno besedilo z `.{toc}` spremenljivko. To je še posebej uporabno za daljše naslove. +Vsebina (povezave v desnem meniju) je samodejno generirana za vse strani, katerih velikost presega 4.000 bajtov, pri čemer je to privzeto obnašanje mogoče prilagoditi s pomočjo [meta oznak |#Meta značke] `{{toc}}`. Besedilo, ki tvori vsebino, se standardno vzame neposredno iz besedila naslovov, vendar je s pomočjo modifikatorja `.{toc}` mogoče v vsebini prikazati drugo besedilo, kar je koristno predvsem za daljše naslove. ``` -Long and Intelligent Heading .{toc: A Different Text for TOC} -============================================================= +Dolg in inteligenten naslov .{toc: Poljubno drugo besedilo, prikazano v vsebini} +================================================================================ ``` -Meta oznake .[#toc-meta-tags] -============================= +Meta značke +=========== -- določanje naslova svoje strani (v `<title>` in drobtinicah) `{{title: Another name}}` -- preusmerjanje `{{redirect: pla:cs}}` - glej [povezave |#links] -- uveljavljanje `{{toc}}` ali onemogočanje `{{toc: no}}` preglednica vsebine +- nastavitev lastnega imena strani (v `<title>` in drobtinicah) `{{title: Drugo ime}}` +- preusmeritev `{{redirect: pla:cs}}` - glej [#povezave] +- vsiljenje `{{toc}}` ali prepoved `{{toc: no}}` samodejne vsebine (okvirček s povezavami na posamezne naslove) {{priority: -1}} diff --git a/contributing/tr/@home.texy b/contributing/tr/@home.texy index a6e34438cb..837975c5b5 100644 --- a/contributing/tr/@home.texy +++ b/contributing/tr/@home.texy @@ -1,17 +1,17 @@ -Nette'ye Katkıda Bulunun -************************ +Nette'ye Katkıda Bulunan Olun +***************************** .[perex] -Açık kaynak projemize nasıl dahil olabileceğinize bir göz atın. Kaynak koduna ve belgelere katkıda bulunma adımlarını öğrenin ve Nette'i geliştirmeye kendini adamış geliştiriciler ağına katılın. +Açık kaynak projemize nasıl katılabileceğinizi öğrenin. Kaynak koduna ve dokümantasyona katkıda bulunma prosedürlerini öğrenin ve Nette'yi geliştirmeye aktif olarak katılan geliştiriciler topluluğunun bir parçası olun. **Kod** -- [Koda katkıda bulunmak|code] -- [Kodlama Standartları |coding-standard] +- [Koda nasıl katkıda bulunulur? |code] +- [Kodlama standardı |coding-standard] **Dokümantasyon** -- [Dokümantasyona katkıda bulunmak|documentation] +- [Dokümantasyona nasıl katkıda bulunulur? |documentation] - [Dokümantasyon sözdizimi |syntax] -- "Önizleme editörü":https://editor.nette.org +- "Önizleme düzenleyicisi":https://editor.nette.org diff --git a/contributing/tr/@left-menu.texy b/contributing/tr/@left-menu.texy index fbe8811b84..471105271a 100644 --- a/contributing/tr/@left-menu.texy +++ b/contributing/tr/@left-menu.texy @@ -1,10 +1,10 @@ Kod *** -- [Koda katkıda bulunmak|code] -- [Kodlama Standartları |coding-standard] +- [Koda nasıl katkıda bulunulur? |code] +- [Kodlama standardı |coding-standard] Dokümantasyon ************* -- [Dokümantasyona katkıda bulunmak|documentation] +- [Dokümantasyona nasıl katkıda bulunulur? |documentation] - [Dokümantasyon sözdizimi |syntax] -- "Önizleme editörü":https://editor.nette.org +- "Önizleme düzenleyicisi":https://editor.nette.org diff --git a/contributing/tr/code.texy b/contributing/tr/code.texy index ad80a6f0e0..c9cecd6add 100644 --- a/contributing/tr/code.texy +++ b/contributing/tr/code.texy @@ -1,87 +1,87 @@ -Koda Katkıda Bulunma -******************** +Koda nasıl katkıda bulunulur +**************************** .[perex] -Nette Framework'e katkıda bulunmayı planlıyor ve kural ve prosedürlere aşina olmanız mı gerekiyor? Bu başlangıç kılavuzu, koda etkili bir şekilde katkıda bulunma, depolarla çalışma ve değişiklikleri uygulama adımlarında size yol gösterecektir. +Nette Framework'e katkıda bulunmaya hazırlanıyor ve kurallar ve prosedürler hakkında bilgiye mi ihtiyacınız var? Yeni başlayanlar için bu kılavuz, koda etkili bir şekilde nasıl katkıda bulunacağınızı, depolarla nasıl çalışacağınızı ve değişiklikleri nasıl uygulayacağınızı adım adım gösterecektir. -Prosedür .[#toc-procedure] -========================== +Prosedür +======== -Koda katkıda bulunmak için [GitHub |https://github.com] 'da bir hesabınızın olması ve Git sürüm kontrol sistemiyle çalışmanın temellerine aşina olmanız gerekir. Git'e aşina değilseniz, [git - the simple guide |https://rogerdudler.github.io/git-guide/] 'a göz atabilir ve birçok [grafik istemciden |https://git-scm.com/downloads/guis] birini kullanmayı düşünebilirsiniz. +Koda katkıda bulunmak için [GitHub|https://github.com] üzerinde bir hesabınızın olması ve Git sürüm kontrol sistemi ile çalışmanın temellerine aşina olmanız gerekir. Git ile çalışmayı bilmiyorsanız, [Git - basit kılavuz |https://rogerdudler.github.io/git-guide/] kılavuzuna bakabilir ve gerekirse birçok [grafik istemciden biri |https://git-scm.com/downloads/guis]ni kullanabilirsiniz. -Ortamın ve Deponun Hazırlanması .[#toc-preparing-the-environment-and-repository] --------------------------------------------------------------------------------- +Ortam ve depo hazırlığı +----------------------- -1) GitHub'da, değiştirmeyi düşündüğünüz [paket deposunun |www:packages] bir [çatalını |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] oluşturun +1) GitHub'da, düzenlemeyi planladığınız [paketin |www:packages] deposunun bir [forkunu |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] oluşturun 2) Bu depoyu bilgisayarınıza [klonlayın |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] 3) `composer install` komutunu kullanarak [Nette Tester |tester:] dahil olmak üzere bağımlılıkları yükleyin -4) Çalıştırarak testlerin çalıştığını doğrulayın `composer tester` -5) Yayınlanan en son sürüme göre [yeni |#New Branch] bir [dal |#New Branch] oluşturun +4) `composer tester` komutunu çalıştırarak testlerin çalıştığını kontrol edin +5) Son yayınlanan sürüme dayalı [yeni bir dal |#Yeni dal] oluşturun -Kendi Değişikliklerinizin Uygulanması .[#toc-implementing-your-own-changes] ---------------------------------------------------------------------------- +Kendi değişikliklerinizi uygulama +--------------------------------- -Artık kendi kod ayarlamalarınızı yapabilirsiniz: +Şimdi kendi kod değişikliklerinizi yapabilirsiniz: -1) İstenen değişiklikleri uygulayın ve testleri unutmayın -2) Testlerin başarıyla çalıştığından emin olmak için `composer tester` -3) Kodun [kodlama standartlarını |#coding standards]karşılayıp karşılamadığını kontrol edin -4) Değişiklikleri [aşağıdaki biçimde |#Commit Description]bir açıklama ile kaydedin (işleyin) +1) Gerekli değişiklikleri programlayın ve testleri unutmayın +2) `composer tester` kullanarak testlerin başarıyla geçtiğinden emin olun +3) Kodun [kodlama standardına |#Kodlama Standartları] uygun olup olmadığını kontrol edin +4) Değişiklikleri [bu formatta |#Commit açıklaması] bir açıklama ile kaydedin (commit edin) -Her mantıksal adım için bir tane olmak üzere birden fazla taahhüt oluşturabilirsiniz. Her commit kendi başına anlamlı olmalıdır. +Her mantıksal adım için bir tane olmak üzere birkaç commit oluşturabilirsiniz. Her commit kendi başına anlamlı olmalıdır. -Değişikliklerin Gönderilmesi .[#toc-submitting-changes] -------------------------------------------------------- +Değişiklikleri gönderme +----------------------- -Değişikliklerden memnun kaldığınızda, bunları gönderebilirsiniz: +Değişikliklerden memnun olduğunuzda, gönderebilirsiniz: -1) Değişiklikleri GitHub'a çatalınıza gönderin -2) Oradan, bir [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) oluşturarak bunları Nette deposuna gönderin -3) Açıklamada [yeterli bilgi |#pull request description] sağlayın +1) Değişiklikleri GitHub'daki fork'unuza gönderin (push) +2) Oradan, bir [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) oluşturarak Nette deposuna gönderin +3) Açıklamada [yeterli bilgi |#Pull request açıklaması] sağlayın -Geri Bildirimin Dahil Edilmesi .[#toc-incorporating-feedback] -------------------------------------------------------------- +Geri bildirimi dahil etme +------------------------- -Taahhütleriniz artık başkaları tarafından görülebilir. Öneriler içeren yorumlar almak yaygındır: +Commit'leriniz artık başkaları tarafından görülecektir. Yorumlarla geri bildirim almanız yaygındır: 1) Önerilen değişiklikleri takip edin -2) Bunları yeni taahhütler olarak dahil edin veya [öncekilerle birleştirin |https://help.github.com/en/github/using-git/about-git-rebase] -3) İşlemleri GitHub'a yeniden gönderin; otomatik olarak çekme isteğinde görüneceklerdir +2) Bunları yeni commit'ler olarak dahil edin veya [öncekilerle birleştirin |https://help.github.com/en/github/using-git/about-git-rebase] +3) Commit'leri tekrar GitHub'a gönderin, otomatik olarak pull request'te görüneceklerdir -Var olanı değiştirmek için asla yeni bir çekme isteği oluşturmayın. +Mevcut bir pull request'i düzenlemek için asla yeni bir pull request oluşturmayın. -Dokümantasyon .[#toc-documentation] ------------------------------------ +Dokümantasyon +------------- -İşlevselliği değiştirdiyseniz veya yeni bir işlev eklediyseniz, [bunu belgelere eklem |documentation] eyi de unutmayın. +İşlevselliği değiştirdiyseniz veya yeni bir tane eklediyseniz, bunu [dokümantasyona eklemeyi |documentation] de unutmayın. -Yeni Şube .[#toc-new-branch] -============================ +Yeni dal +======== -Mümkünse, değişiklikleri en son yayınlanan sürüme, yani daldaki son etikete göre yapın. v3.2.1 etiketi için bu komutu kullanarak bir dal oluşturun: +Mümkünse, değişiklikleri son yayınlanan sürüme, yani ilgili daldaki son etikete göre yapın. `v3.2.1` etiketi için şu komutla bir dal oluşturursunuz: ```shell git checkout -b new_branch_name v3.2.1 ``` -Kodlama Standartları .[#toc-coding-standards] -============================================= +Kodlama Standartları +==================== -Kodunuz Nette Framework'te kullanılan [kodlama standardını |coding standard] karşılamalıdır. Kodu kontrol etmek ve düzeltmek için otomatik bir araç mevcuttur. Composer aracılığıyla **global olarak** istediğiniz bir klasöre yükleyebilirsiniz: +Kodunuz, Nette Framework'te kullanılan [kodlama standardı |coding standard]na uymalıdır. Kodu kontrol etmek ve düzeltmek için otomatik bir araç mevcuttur. Composer aracılığıyla seçtiğiniz bir klasöre **global olarak** kurulabilir: ```shell composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Şimdi aracı terminalde çalıştırabilmelisiniz. İlk komut kontrol eder ve ikincisi geçerli dizindeki `src` ve `tests` klasörlerindeki kodu düzeltir: +Şimdi aracı terminalde çalıştırabilmelisiniz. İlk komut kontrol eder ve ikinci komut geçerli dizindeki `src` ve `tests` klasörlerindeki kodu düzeltir: ```shell /path/to/nette-coding-standard/ecs check @@ -89,24 +89,24 @@ composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Taahhüt Açıklaması .[#toc-commit-description] -============================================= +Commit açıklaması +================= -Nette'de taahhüt konuları aşağıdaki formata sahiptir: `Presenter: fixed AJAX detection [Closes #69]` +Nette'de commit konularının formatı şöyledir: `Presenter: fixed AJAX detection [Closes #69]` -- alanı ve ardından iki nokta üst üste -- geçmiş zamanda commit'in amacı; mümkünse aşağıdaki gibi kelimelerle başlayın: "added .(yeni özellik)", "fixed .(düzeltme)", "refactored .(davranış değişikliği olmadan kod değişikliği)", changed, removed -- eğer taahhüt geriye dönük uyumluluğu bozuyorsa, "BC break" ekleyin -- sorun izleyiciyle herhangi bir bağlantı, örneğin `(#123)` veya `[Closes #69]` -- konudan sonra bir boş satır ve ardından, örneğin forum bağlantıları da dahil olmak üzere, daha ayrıntılı bir açıklama gelebilir +- İki nokta üst üste ile takip edilen alan +- Mümkünse geçmiş zamanda commit'in amacı, şu kelimelerle başlayın: "added .(yeni özellik eklendi)", "fixed .(hata düzeltildi)", "refactored .(davranış değişikliği olmadan kod yeniden düzenlendi)", changed, removed +- Commit geriye dönük uyumluluğu bozarsa, "BC break" ekleyin +- `(#123)` veya `[Closes #69]` gibi isteğe bağlı sorun izleyici bağlantısı +- Konudan sonra bir boş satır ve ardından forum bağlantıları gibi daha ayrıntılı bir açıklama gelebilir -Çekme Talebi Açıklaması .[#toc-pull-request-description] -======================================================== +Pull request açıklaması +======================= -Bir çekme isteği oluştururken, GitHub arayüzü bir başlık ve açıklama girmenize izin verecektir. Kısa ve öz bir başlık girin ve açıklamada değişikliğinizin nedenleri hakkında mümkün olduğunca fazla bilgi ekleyin. +Bir pull request oluştururken, GitHub arayüzü bir başlık ve açıklama girmenize izin verir. Açıklayıcı bir başlık girin ve açıklama bölümünde değişikliğinizin nedenleri hakkında mümkün olduğunca fazla bilgi sağlayın. -Ayrıca, başlıkta yeni bir özellik mi yoksa bir hata düzeltmesi mi olduğunu ve geriye dönük uyumluluk sorunlarına (BC break) neden olup olmayacağını belirtin. İlgili bir sorun varsa, çekme isteğinin onaylanması üzerine kapatılması için ona bağlantı verin. +Ayrıca, bunun yeni bir özellik mi yoksa bir hata düzeltmesi mi olduğunu ve geriye dönük uyumluluğun bozulup bozulmayacağını (BC break) belirttiğiniz bir başlık da görünecektir. İlgili bir sorun (issue) varsa, pull request onaylandıktan sonra kapatılması için ona bağlantı verin. ``` - bug fix / new feature? <!-- #issue numbers, if any --> diff --git a/contributing/tr/coding-standard.texy b/contributing/tr/coding-standard.texy index aeaeee5225..c932e44d9b 100644 --- a/contributing/tr/coding-standard.texy +++ b/contributing/tr/coding-standard.texy @@ -1,44 +1,44 @@ -Kodlama Standardı +Kodlama standardı ***************** -Bu belgede Nette'i geliştirmek için kurallar ve öneriler açıklanmaktadır. Nette'ye kod katkısında bulunurken bunlara uymalısınız. Bunu yapmanın en kolay yolu mevcut kodu taklit etmektir. -Buradaki fikir, tüm kodun tek bir kişi tarafından yazılmış gibi görünmesini sağlamaktır. .[perex] +.[perex] +Bu belge, Nette geliştirme için kuralları ve önerileri açıklar. Nette'ye kod katkısında bulunurken bunlara uymalısınız. Bunu yapmanın en kolay yolu, mevcut kodu taklit etmektir. Amaç, tüm kodun tek bir kişi tarafından yazılmış gibi görünmesidir. -Nette Kodlama Standardı, iki ana istisna dışında [PSR-12 Genişletilmiş Kodlama Stiline |https://www.php-fig.org/psr/psr-12/] karşılık gelir: girinti için [boşluk yerine sekme |#tabs instead of spaces] kullanır ve [sınıf sabitleri için PascalCase |https://blog.nette.org/tr/kodda-daha-az-ciglik-atmak-icin] kullanır. +Nette Kodlama Standardı, iki ana istisna dışında [PSR-12 Genişletilmiş Kodlama Stili |https://www.php-fig.org/psr/psr-12/] ile uyumludur: girinti için [#boşluklar yerine sekmeler] kullanır ve "sınıf sabitleri için PascalCase kullanır":https://blog.nette.org/tr/for-less-screaming-in-the-code. -Genel Kurallar .[#toc-general-rules] -==================================== +Genel kurallar +============== -- Her PHP dosyası şunları içermelidir `declare(strict_types=1)` -- Daha iyi okunabilirlik için yöntemleri ayırmak için iki boş satır kullanılır. -- Susma operatörünün kullanım nedeni belgelenmelidir: `@mkdir($dir); // @ - directory may exist` -- Zayıf tipli karşılaştırma operatörü kullanılırsa (örn. `==`, `!=`, ...), niyet belgelenmelidir: `// == to accept null` -- Bir dosyaya daha fazla istisna yazabilirsiniz `exceptions.php` -- Yöntemlerin görünürlüğü arayüzler için belirtilmez çünkü bunlar her zaman public'tir. -- Her özellik, geri dönüş değeri ve parametre için bir tür belirtilmelidir. Öte yandan, nihai sabitler için türü asla belirtmeyiz çünkü bu açıktır. -- Değişmezin kendisi kesme işareti içermediği sürece, dizeyi sınırlandırmak için tek tırnak kullanılmalıdır. +- Her PHP dosyası `declare(strict_types=1)` içermelidir +- Daha iyi okunabilirlik için metotları ayırmak için iki boş satır kullanılır. +- Susturma operatörünün (@) kullanım nedeni belgelenmelidir: `@mkdir($dir); // @ - dizin mevcut olabilir`. +- Zayıf tipli bir karşılaştırma operatörü (yani `==`, `!=`, ...) kullanılıyorsa, amaç belgelenmelidir: `// == null kabul et` +- Tek bir `exceptions.php` dosyasına birden fazla istisna yazabilirsiniz. +- Arayüzler için metot görünürlüğü belirtilmez, çünkü her zaman public'tirler. +- Her özellik, dönüş değeri ve parametre için bir tip belirtilmelidir. Tersine, nihai sabitler için tipi asla belirtmeyiz, çünkü açıktır. +- Bir karakter dizisini sınırlamak için, değişmez değerin kendisi kesme işareti içermediği sürece tek tırnak işaretleri kullanılmalıdır. -Adlandırma Kuralları .[#toc-naming-conventions] -=============================================== +Adlandırma kuralları +==================== -- Tam ad aşırı olmadığı sürece kısaltma kullanmaktan kaçının. -- İki harfli kısaltmalar için büyük harf, daha uzun kısaltmalar için küçük/büyük harf kullanın. -- Sınıf adı için bir isim veya isim cümlesi kullanın. -- Sınıf adları sadece özgüllük (`Array`) değil, aynı zamanda genellik (`ArrayIterator`) de içermelidir. PHP öznitelikleri istisnadır. -- "Sınıf sabitleri ve enumlar PascalCaps kullanmalıdır":https://blog.nette.org/tr/kodda-daha-az-ciglik-atmak-icin. -- "Arayüzler ve soyut sınıflar `Abstract`, `Interface` veya `I` gibi ön ekler veya son ekler içermemelidir":https://blog.nette.org/tr/oenek-ve-sonekler-arayuez-adlarina-ait-degildir. +- Tam ad çok uzun olmadıkça kısaltmalar kullanmayın. +- İki harfli kısaltmalar için büyük harfler, daha uzun kısaltmalar için pascal/camel case kullanın. +- Sınıf adı için bir isim veya isim tamlaması kullanın. +- Sınıf adları yalnızca özgüllüğü (`Array`) değil, aynı zamanda genelliği de (`ArrayIterator`) içermelidir. PHP dil nitelikleri bir istisnadır. +- "Sınıf sabitleri ve enumlar PascalCaps kullanmalıdır":https://blog.nette.org/tr/for-less-screaming-in-the-code. +- "Arayüzler ve soyut sınıflar, Abstract, Interface veya I gibi önekler veya sonekler içermemelidir":https://blog.nette.org/tr/prefixes-and-suffixes-do-not-belong-in-interface-names. -Sargı ve Teller .[#toc-wrapping-and-braces] -=========================================== +Sarma ve Parantezler +==================== -Nette Kodlama Standardı PSR-12'ye (veya PER Kodlama Stiline) karşılık gelir, bazı noktalarda onu daha fazla belirtir veya değiştirir: +Nette Kodlama Standardı, PSR-12 (veya PER Kodlama Stili) ile uyumludur, bazı noktalarda onu tamamlar veya değiştirir: -- ok fonksiyonları parantezden önce boşluk bırakılmadan yazılır, yani `fn($a) => $b` -- `use` import ifadelerinin farklı türleri arasında boş satır gerekmez -- fonksiyonun/metodun dönüş tipi ve açılış parantezi daha iyi okunabilirlik için ayrı satırlara yerleştirilmelidir: +- ok fonksiyonları parantezden önce boşluk olmadan yazılır, yani `fn($a) => $b` +- farklı `use` import ifadeleri türleri arasında boş bir satır gerekli değildir +- fonksiyon/metot dönüş tipi ve açılış küme parantezi her zaman ayrı satırlardadır: ```php public function find( @@ -46,28 +46,32 @@ Nette Kodlama Standardı PSR-12'ye (veya PER Kodlama Stiline) karşılık gelir, array $options, ): array { - // yöntem gövdesi + // metot gövdesi } ``` +Ayrı bir satırdaki açılış küme parantezi, fonksiyon/metot imzasını gövdeden görsel olarak ayırmak için önemlidir. İmza tek bir satırdaysa, ayırma açıktır (soldaki resim), birden çok satırdaysa, PSR'de imzalar ve gövde birleşir (ortada), Nette standardında ise ayrı kalırlar (sağda): -Dokümantasyon Blokları (phpDoc) .[#toc-documentation-blocks-phpdoc] -=================================================================== +[* new-line-after.webp *] -Ana kural: parametre tipi veya dönüş tipi gibi hiçbir imza bilgisini ek bir değer olmadan asla çoğaltmayın. -Sınıf tanımı için dokümantasyon bloğu: +Belgelendirme blokları (phpDoc) +=============================== -- Bir sınıf tanımıyla başlar. -- Boş satır takip eder. -- `@property` (veya `@property-read`, `@property-write`) ek açıklamaları satır satır takip eder. Sözdizimi şöyledir: açıklama, boşluk, tür, boşluk, $name. -- `@method` ek açıklamaları satır satır takip eder. Sözdizimi: açıklama, boşluk, dönüş tipi, boşluk, isim(tip $param, ...). -- `@author` ek açıklaması atlanmıştır. Yazarlık bir kaynak kodu geçmişinde tutulur. +Ana kural: Ek bir değer olmadan parametre tipi veya dönüş tipi gibi imzadaki hiçbir bilgiyi asla tekrarlamayın. + +Sınıf tanımı için belgelendirme bloğu: + +- Sınıfın bir açıklamasıyla başlar. +- Ardından boş bir satır gelir. +- Ardından `@property` (veya `@property-read`, `@property-write`) ek açıklamaları gelir, birbiri ardına. Sözdizimi: ek açıklama, boşluk, tip, boşluk, $name. +- Ardından `@method` ek açıklamaları gelir, birbiri ardına. Sözdizimi: ek açıklama, boşluk, dönüş tipi, boşluk, name(tip $param, ...). +- `@author` ek açıklaması atlanır. Yazarlık, kaynak kodu geçmişinde tutulur. - `@internal` veya `@deprecated` ek açıklamaları kullanılabilir. ```php /** - * MIME message part. + * MIME mesaj bölümü. * * @property string $encoding * @property-read array $headers @@ -76,27 +80,27 @@ Sınıf tanımı için dokümantasyon bloğu: */ ``` -Yalnızca `@var` ek açıklamasını içeren özellik için dokümantasyon bloğu tek satırda olmalıdır: +Yalnızca `@var` ek açıklamasını içeren bir özellik için belgelendirme bloğu tek satırlık olmalıdır: ```php /** @var string[] */ private array $name; ``` -Yöntem tanımı için dokümantasyon bloğu: +Metot tanımı için belgelendirme bloğu: -- Kısa bir yöntem açıklaması ile başlar. -- Boş satır yoktur. -- `@param` ek açıklamaları, tek tek satır. +- Metodun kısa bir açıklamasıyla başlar. +- Boş satır yok. +- Ayrı satırlarda `@param` ek açıklamaları. - `@return` ek açıklaması. -- `@throws` ek açıklamaları, tek tek satır. +- `@throws` ek açıklamaları, birbiri ardına. - `@internal` veya `@deprecated` ek açıklamaları kullanılabilir. -Daha iyi okunabilirlik için iki boşluk ile takip edilen `@param` haricinde her ek açıklamadan sonra bir boşluk bırakılır. +Her ek açıklamayı bir boşluk takip eder, `@param` hariç, daha iyi okunabilirlik için bunu iki boşluk takip eder. ```php /** - * Finds a file in directory. + * Dizinde bir dosya bulur. * @param string[] $options * @return string[] * @throws DirectoryNotFoundException @@ -105,21 +109,20 @@ public function find(string $dir, array $options): array ``` -Boşluklar Yerine Sekmeler .[#toc-tabs-instead-of-spaces] -======================================================== +Boşluklar yerine sekmeler +========================= -Sekmelerin boşluklara göre çeşitli avantajları vardır: +Sekmelerin boşluklara göre birkaç avantajı vardır: -- girinti boyutu editörlerde ve "webde":https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size özelleştirilebilir -- kullanıcının girinti boyutu tercihini koda empoze etmezler, bu nedenle kod daha taşınabilirdir -- bunları tek bir tuşa basarak yazabilirsiniz (sadece sekmeleri boşluğa dönüştüren editörlerde değil, her yerde) -- girinti onların amacıdır -- görme engelli ve görme engelli meslektaşlarının ihtiyaçlarına saygı duymak +- girinti boyutu düzenleyicilerde ve "web'de":https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size ayarlanabilir +- kodun kullanıcının girinti boyutu tercihini zorlamazlar, bu nedenle kod daha taşınabilirdir +- tek bir tuş vuruşuyla yazılabilirler (sadece sekmeleri boşluklara dönüştüren düzenleyicilerde değil, her yerde) +- girintileme onların amacıdır +- görme engelli ve kör meslektaşlarımızın ihtiyaçlarına saygı duyarlar -Projelerimizde sekmeler kullanarak, çoğu insan için gereksiz görünebilecek, ancak görme engelli kişiler için çok önemli olan genişlik özelleştirmesine izin veriyoruz. +Projelerimizde sekmeler kullanarak, çoğu insan için gereksiz görünebilecek genişlik ayarlamasına izin veriyoruz, ancak görme engelli insanlar için bu zorunludur. -Braille ekran kullanan görme engelli programcılar için her boşluk bir braille hücresi ile temsil edilir ve değerli bir alan kaplar. Yani varsayılan girinti 4 boşluksa, 3. seviye bir girinti kod başlamadan önce 12 braille hücresini boşa harcar. -Dizüstü bilgisayarlarda en yaygın olarak kullanılan 40 hücreli bir ekranda, bu, mevcut hücrelerin dörtte birinden fazlasının herhangi bir bilgi olmadan boşa harcanması anlamına gelir. +Braille ekranları kullanan kör programcılar için her boşluk bir Braille hücresini temsil eder. Bu nedenle, varsayılan girinti 4 boşluksa, 3. seviye girinti, kod başlamadan önce 12 değerli Braille hücresini boşa harcar. Dizüstü bilgisayarlarda en sık kullanılan 40 hücreli bir ekranda, bu, herhangi bir bilgi olmadan boşa harcanan mevcut hücrelerin dörtte birinden fazlasıdır. {{priority: -1}} diff --git a/contributing/tr/documentation.texy b/contributing/tr/documentation.texy index cad1144972..b9efcf74e4 100644 --- a/contributing/tr/documentation.texy +++ b/contributing/tr/documentation.texy @@ -1,69 +1,68 @@ -Dokümantasyona Katkıda Bulunma -****************************** +Dokümantasyona nasıl katkıda bulunulur +************************************** .[perex] -Dokümantasyona katkıda bulunmak, başkalarının çerçeveyi anlamasına yardımcı olduğu için en değerli faaliyetlerden biridir. +Dokümantasyona katkıda bulunmak, başkalarının framework'ü anlamasına yardımcı olduğunuz için en faydalı faaliyetlerden biridir. -Nasıl Yazılır? .[#toc-how-to-write] ------------------------------------ +Nasıl yazılır? +-------------- -Dokümantasyon öncelikle konuya yeni başlayan kişilere yöneliktir. Bu nedenle, birkaç önemli noktayı karşılamalıdır: +Dokümantasyon öncelikle konuyla yeni tanışan kişilere yöneliktir. Bu nedenle birkaç önemli noktayı karşılamalıdır: -- Basit ve genel konularla başlayın. Sonunda daha gelişmiş konulara geçin -- Konuyu mümkün olduğunca açık bir şekilde anlatmaya çalışın. Örneğin, konuyu önce bir meslektaşınıza açıklamayı deneyin -- Yalnızca kullanıcının belirli bir konu için gerçekten bilmesi gereken bilgileri sağlayın -- Bilgilerinizin doğru olduğundan emin olun. Her kodu test edin -- Kısa ve öz olun - yazdıklarınızı ikiye bölün. Ve sonra tekrar yapmaktan çekinmeyin -- Kalın yazı tiplerinden aşağıdaki gibi çerçevelere kadar vurgulamayı idareli kullanın `.[note]` -- Kodda [Kodlama Standardını |Coding Standard] Takip Edin +- Basit ve genelden başlayın. Daha gelişmiş konulara ancak sonunda geçin +- Konuyu olabildiğince iyi açıklamaya çalışın. Örneğin, konuyu önce bir meslektaşınıza açıklamayı deneyin +- Yalnızca kullanıcının konu hakkında gerçekten bilmesi gereken bilgileri sağlayın +- Bilgilerinizin gerçekten doğru olduğunu doğrulayın. Her kodu test edin +- Kısa ve öz olun - yazdıklarınızı yarıya indirin. Ve sonra gerekirse bir kez daha +- Kalın yazıdan `.[note]` gibi çerçevelere kadar her türlü vurgulayıcıdan tasarruf edin +- Kodlarda [Kodlama Standardı |Coding Standard]na uyun -Ayrıca, [sözdizimini |syntax] öğrenin. Yazım sırasında makalenin önizlemesi için önizleme [düzenleyicisini |https://editor.nette.org/] kullanabilirsiniz. +Ayrıca [sözdizimi |syntax]ni öğrenin. Yazarken makaleyi önizlemek için [önizlemeli düzenleyiciyi |https://editor.nette.org/] kullanabilirsiniz. -Dil Mutasyonları .[#toc-language-mutations] -------------------------------------------- +Dil sürümleri +------------- -İngilizce birincil dildir, bu nedenle değişiklikleriniz İngilizce olmalıdır. Eğer İngilizce sizin için uygun değilse, [DeepL Translator |https://www.deepl.com/translator] 'ı kullanın ve diğerleri metninizi kontrol etsin. +Birincil dil İngilizce'dir, bu nedenle değişiklikleriniz hem Çekçe hem de İngilizce olmalıdır. İngilizce güçlü yanınız değilse, [DeepL Translator |https://www.deepl.com/translator] kullanın ve diğerleri metninizi kontrol edecektir. -Düzenlemenizin onaylanması ve ince ayarlarının yapılmasının ardından diğer dillere çeviri otomatik olarak yapılacaktır. +Diğer dillere çeviri, düzenlemeniz onaylandıktan ve ince ayar yapıldıktan sonra otomatik olarak yapılacaktır. -Önemsiz Düzenlemeler .[#toc-trivial-edits] ------------------------------------------- +Önemsiz düzenlemeler +-------------------- -Belgelere katkıda bulunmak için [GitHub |https://github.com]'da bir hesabınızın olması gerekir. +Dokümantasyona katkıda bulunmak için [GitHub|https://github.com] üzerinde bir hesabınızın olması gerekir. -Belgelerde küçük bir değişiklik yapmanın en kolay yolu, her sayfanın sonundaki bağlantıları kullanmaktır: +Dokümantasyonda küçük bir değişiklik yapmanın en kolay yolu, her sayfanın sonundaki bağlantıları kullanmaktır: -- *GitHub'da göster* sayfanın GitHub'daki kaynak sürümünü açar. Ardından `E` düğmesine basın ve düzenlemeye başlayabilirsiniz (GitHub'da oturum açmış olmanız gerekir) -- Önizlemeyi aç* son görsel formu hemen görebileceğiniz bir düzenleyici açar +- *GitHub'da göster* ilgili sayfanın kaynak sürümünü GitHub'da açar. Ardından `E` düğmesine basmanız yeterlidir ve düzenlemeye başlayabilirsiniz (GitHub'da oturum açmış olmanız gerekir) +- *Önizlemeyi aç* düzenleyiciyi açar, burada sonuçtaki görsel görünümü de hemen görürsünüz - [Önizleme düzenleyicisi |https://editor.nette.org/] değişiklikleri doğrudan GitHub'a kaydetme özelliğine sahip olmadığından, kaynak metni panoya kopyalamanız (*Panoya kopyala düğmesini* kullanarak) ve ardından GitHub'daki düzenleyiciye yapıştırmanız gerekir. -Düzenleme alanının altında gönderim için bir form bulunmaktadır. Burada, düzenlemenizin nedenini kısaca özetlemeyi ve açıklamayı unutmayın. Gönderdikten sonra, daha fazla düzenlenebilen bir çekme isteği (PR) oluşturulur. +[Önizlemeli düzenleyici |https://editor.nette.org/] değişiklikleri doğrudan GitHub'a kaydetme seçeneğine sahip olmadığından, düzenlemeyi bitirdikten sonra kaynak metni panoya kopyalamak (*Copy to clipboard* düğmesiyle) ve ardından GitHub'daki düzenleyiciye yapıştırmak gerekir. Düzenleme alanının altında gönderme formu bulunur. Burada düzenlemenizin nedenini kısaca özetlemeyi ve açıklamayı unutmayın. Gönderdikten sonra, daha fazla düzenlenebilen bir pull request (PR) oluşturulur. -Daha Büyük Düzenlemeler .[#toc-larger-edits] --------------------------------------------- +Daha büyük düzenlemeler +----------------------- -Yalnızca GitHub arayüzüne güvenmek yerine Git sürüm kontrol sistemi ile çalışmanın temellerine aşina olmak daha uygundur. Git'e aşina değilseniz, [git - the simple guide |https://rogerdudler.github.io/git-guide/] 'a başvurabilir ve mevcut birçok [grafik istemciden |https://git-scm.com/downloads/guis] birini kullanmayı düşünebilirsiniz. +GitHub arayüzünü kullanmak yerine, Git sürüm kontrol sistemi ile çalışmanın temellerine aşina olmak daha uygundur. Git ile çalışmayı bilmiyorsanız, [Git - basit kılavuz |https://rogerdudler.github.io/git-guide/] kılavuzuna bakabilir ve gerekirse birçok [grafik istemciden biri |https://git-scm.com/downloads/guis]ni kullanabilirsiniz. -Belgeleri aşağıdaki şekilde düzenleyin: +Dokümantasyonu şu şekilde düzenleyin: -1) GitHub'da [nette/docs |https://github.com/nette/docs] deposunun bir [çatalını |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] oluşturun -2) bu depoyu bilgisayarınıza [klonlayın |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] -3) daha sonra, [uygun dalda |#Documentation Structure]değişiklikler yapın -4) [Code-Checker |code-checker:] aracını kullanarak metinde fazladan boşluk olup olmadığını kontrol edin -5) değişiklikleri kaydedin (işleyin) -6) değişikliklerden memnunsanız, bunları GitHub'a çatalınıza gönderin -7) oradan, bir [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) oluşturarak bunları `nette/docs` deposuna gönderin +1) GitHub'da [nette/docs |https://github.com/nette/docs] deposunun bir [forkunu |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] oluşturun +2) Bu depoyu bilgisayarınıza [klonlayın |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] +3) Ardından [ilgili dalda |#Dokümantasyon yapısı] değişiklikleri yapın +4) [Code-Checker |code-checker:] aracını kullanarak metindeki fazla boşlukları kontrol edin +5) Değişiklikleri kaydedin (commit) +6) Değişikliklerden memnunsanız, bunları GitHub'daki fork'unuza gönderin (push) +7) Oradan, bir [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) oluşturarak `nette/docs` deposuna gönderin -Öneriler içeren yorumlar almak yaygındır. Önerilen değişiklikleri takip edin ve bunları dahil edin. Önerilen değişiklikleri yeni taahhütler olarak ekleyin ve GitHub'a yeniden gönderin. Asla sadece mevcut bir talebi değiştirmek için yeni bir talep oluşturmayın. +Yorumlarla geri bildirim almanız yaygındır. Önerilen değişiklikleri takip edin ve dahil edin. Önerilen değişiklikleri yeni commit'ler olarak ekleyin ve tekrar GitHub'a gönderin. Bir pull request'i düzenlemek için asla yeni bir pull request oluşturmayın. -Dokümantasyon Yapısı .[#toc-documentation-structure] ----------------------------------------------------- +Dokümantasyon yapısı +-------------------- -Belgelerin tamamı GitHub'da [nette/docs |https://github.com/nette/docs] deposunda yer almaktadır. Güncel sürüm master dalında, eski sürümler ise `doc-3.x`, `doc-2.x` gibi dallarda yer almaktadır. +Tüm dokümantasyon GitHub'da [nette/docs |https://github.com/nette/docs] deposunda bulunur. Geçerli sürüm master dalındadır, eski sürümler `doc-3.x`, `doc-2.x` gibi dallarda bulunur. -Her şubenin içeriği, ayrı dokümantasyon alanlarını temsil eden ana klasörlere bölünmüştür. Örneğin, `application/` https://doc.nette.org/en/application 'a, `latte/` https://latte.nette.org 'a, vb. karşılık gelir. Bu klasörlerin her biri dil mutasyonlarını temsil eden alt klasörler (`cs`, `en`, ...) ve isteğe bağlı olarak dokümantasyondaki sayfalara eklenebilecek resimler içeren bir `files` alt klasörü içerir. +Her dalın içeriği, dokümantasyonun ayrı alanlarını temsil eden ana klasörlere ayrılmıştır. Örneğin, `application/` https://doc.nette.org/cs/application adresine karşılık gelir, `latte/` https://latte.nette.org adresine karşılık gelir vb. Bu klasörlerin her biri dil sürümlerini temsil eden alt klasörler (`cs`, `en`, ...) ve isteğe bağlı olarak dokümantasyon sayfalarına eklenebilen resimleri içeren `files` alt klasörünü içerir. diff --git a/contributing/tr/syntax.texy b/contributing/tr/syntax.texy index aadae960de..41bbf226ef 100644 --- a/contributing/tr/syntax.texy +++ b/contributing/tr/syntax.texy @@ -1,59 +1,59 @@ -Wiki Sözdizimi -************** +Dokümantasyon sözdizimi +*********************** -Wiki, çeşitli geliştirmelerle birlikte Markdown ve [Texy sözdizimini |https://texy.info/en/syntax] kullanır. +Dokümantasyon, bazı uzantılarla birlikte Markdown & [Texy sözdizimini |https://texy.nette.org/syntax] kullanır. -Bağlantılar .[#toc-links] -========================= +Bağlantılar +=========== -Dahili referanslar için köşeli parantez içindeki gösterim `[link]` kullanılır. Bu ya dikey çubuk şeklinde olur `[link text |link target]`veya kısaltılmış biçimde `[link text]` hedef metinle aynıysa (küçük harfe ve tire işaretlerine dönüştürüldükten sonra): +Dahili bağlantılar için köşeli parantez `[bağlantı]` gösterimi kullanılır. Ya dikey çizgi ile `[bağlantı metni |bağlantı hedefi]` şeklinde ya da hedef metinle aynıysa (küçük harflere ve tirelere dönüştürüldükten sonra) kısaltılmış olarak `[bağlantı metni | Orijinal bağlantı metni]` şeklinde: -- `[Page name]` -> `<a href="/en/page-name">Page name</a>` -- `[link text |Page name]` -> `<a href="/en/page-name">link text</a>` +- `[Sayfa adı | Page name]` -> `<a href="/tr/page-name">Sayfa adı</a>` +- `[bağlantı metni |Page name]` -> `<a href="/tr/page-name">bağlantı metni</a>` -Başka bir dile veya başka bir bölüme bağlantı verebiliriz. Bir bölüm bir Nette kütüphanesidir (örneğin `forms`, `latte`, vb.) veya `best-practices`, `quickstart`, vb. gibi özel bölümlerdir: +Farklı bir dil sürümüne veya farklı bir bölüme bağlantı verebiliriz. Bölüm, bir Nette kütüphanesi (örneğin `forms`, `latte`, vb.) veya `best-practices`, `quickstart` gibi özel bölümler anlamına gelir: -- `[cs:Page name]` -> `<a href="/en/page-name">Page name</a>` (aynı bölüm, farklı dil) -- `[tracy:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (farklı bölüm, aynı dil) -- `[tracy:cs:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (farklı bölüm ve dil) +- `[cs:Sayfa adı | cs:Page name]` -> `<a href="/cs/page-name">Sayfa adı</a>` (aynı bölüm, farklı dil) +- `[tracy:Sayfa adı | tracy:Page name]` -> `<a href="//tracy.nette.org/tr/page-name">Sayfa adı</a>` (farklı bölüm, aynı dil) +- `[tracy:cs:Sayfa adı | tracy:cs:Page name]` -> `<a href="//tracy.nette.org/cs/page-name">Sayfa adı</a>` (farklı bölüm ve dil) -Ayrıca `#` ile sayfadaki belirli bir başlığı hedeflemek de mümkündür. +`#` kullanarak sayfadaki belirli bir başlığa da hedef belirlemek mümkündür. -- `[#Heading]` -> `<a href="#toc-heading">Heading</a>` (geçerli sayfadaki başlık) -- `[Page name#Heading]` -> `<a href="/en/page-name#toc-heading">Page name</a>` +- `[#Heading]` -> `<a href="#toc-heading">Başlık</a>` (geçerli sayfadaki başlık) +- `[Page name#Heading]` -> `<a href="/tr/page-name#toc-heading">Sayfa adı</a>` -Bölümün ana sayfasına bağlantı: (`@home` bölümün ana sayfası için kullanılan özel bir terimdir) +Bölümün giriş sayfasına bağlantı: (`@home`, bölümün ana sayfası için özel bir terimdir) -- `[link text |@home]` -> `<a href="/en/">link text</a>` -- `[link text |tracy:]` -> `<a href="//tracy.nette.org/en/">link text</a>` +- `[bağlantı metni |@home]` -> `<a href="/tr/">bağlantı metni</a>` +- `[bağlantı metni |tracy:]` -> `<a href="//tracy.nette.org/tr/">bağlantı metni</a>` -API Dokümantasyonuna Bağlantılar .[#toc-links-to-api-documentation] -------------------------------------------------------------------- +API dokümantasyonuna bağlantılar +-------------------------------- -Her zaman aşağıdaki notasyonları kullanın: +Her zaman yalnızca bu gösterimi kullanarak belirtin: - `[api:Nette\SmartObject]` -> [api:Nette\SmartObject] - `[api:Nette\Forms\Form::setTranslator()]` -> [api:Nette\Forms\Form::setTranslator()] - `[api:Nette\Forms\Form::$onSubmit]` -> [api:Nette\Forms\Form::$onSubmit] - `[api:Nette\Forms\Form::Required]` -> [api:Nette\Forms\Form::Required] -Tam nitelikli adlar yalnızca ilk bahiste kullanılır. Diğer bağlantılar için basitleştirilmiş bir ad kullanın: +Tam nitelikli adları yalnızca ilk bahsedişte kullanın. Sonraki bağlantılar için basitleştirilmiş adı kullanın: - `[Form::setTranslator() |api:Nette\Forms\Form::setTranslator()]` -> [Form::setTranslator() |api:Nette\Forms\Form::setTranslator()] -PHP Belgelerine Bağlantılar .[#toc-links-to-php-documentation] --------------------------------------------------------------- +PHP dokümantasyonuna bağlantılar +-------------------------------- - `[php:substr]` -> [php:substr] -Kaynak Kodu .[#toc-source-code] -=============================== +Kaynak Kodu +=========== -Kod bloğu <code>```lang</code> ile başlar ve <code>```</code> ile biter. Desteklenen diller `php`, `latte`, `neon`, `html`, `css`, `js` ve `sql`. Girinti için her zaman sekme kullanın. +Kod bloğu <code>```lang</code> ile başlar ve <code>```</code> ile biter. Desteklenen diller `php`, `latte`, `neon`, `html`, `css`, `js` ve `sql`'dir. Girintileme için her zaman sekmeleri kullanın. ``` ```php @@ -63,7 +63,7 @@ Kod bloğu <code>```lang</code> ile başlar ve <code>``` ``` ``` -Dosya adını <code>```php .{file: ArrayTest.php}</code> olarak da belirtebilirsiniz ve kod bloğu bu şekilde oluşturulacaktır: +Ayrıca dosya adını <code>```php .{file: ArrayTest.php}</code> olarak belirtebilirsiniz ve kod bloğu şu şekilde oluşturulur: ```php .{file: ArrayTest.php} public function renderPage($id) @@ -72,71 +72,71 @@ public function renderPage($id) ``` -Başlıklar .[#toc-headings] -========================== +Başlıklar +========= -Üst başlık (sayfa adı) yıldızlarla altı çizili (`*`). For normal headings use equal signs (`=`) and then hyphens (`-`). +En üst başlığın (yani sayfa adının) altını yıldızlarla çizin. Bölümleri ayırmak için eşittir işaretleri kullanın. Başlıkların altını eşittir işaretleri ve ardından tirelerle çizin: ``` -MVC Applications & Presenters -***************************** +MVC Uygulamaları & presenterlar +******************************* ... -Link Creation -============= +Bağlantı oluşturma +================== ... -Links in Templates ------------------- +Şablonlardaki bağlantılar +------------------------- ... ``` -Kutular ve Stiller .[#toc-boxes-and-styles] -=========================================== +Çerçeveler ve stiller +===================== -Sınıf ile işaretlenmiş baş paragraf `.[perex]` .[perex] +Perex'i `.[perex]` sınıfıyla işaretleriz .[perex] -Sınıf ile işaretlenmiş notlar `.[note]` .[note] +Notu `.[note]` sınıfıyla işaretleriz .[note] -Sınıf ile işaretlenmiş uç `.[tip]` .[tip] +İpucunu `.[tip]` sınıfıyla işaretleriz .[tip] -Sınıf ile işaretlenmiş uyarı `.[caution]` .[caution] +Uyarıyı `.[caution]` sınıfıyla işaretleriz .[caution] -Sınıf ile işaretlenmiş güçlü uyarı `.[warning]` .[warning] +Daha güçlü bir uyarıyı `.[warning]` sınıfıyla işaretleriz .[warning] Sürüm numarası `.{data-version:2.4.10}` .{data-version:2.4.10} -Sınıflar ilgili satırdan önce yazılmalıdır: +Sınıfları satırdan önce yazın: ``` -.[note] -This is a note. +.[perex] +Bu perex'tir. ``` -Lütfen aşağıdaki gibi kutulara dikkat edin `.[tip]` dikkat çeker ve bu nedenle daha az önemli bilgiler için değil, vurgulamak için kullanılmalıdır. +Lütfen `.[tip]` gibi çerçevelerin gözleri "çektiğini" unutmayın, bu nedenle daha az önemli bilgiler için değil, vurgulamak için kullanılırlar. Bu nedenle, kullanımlarını en aza indirin. -İçindekiler .[#toc-table-of-contents] -===================================== +İçindekiler +=========== -Sayfa 4 000 bayttan uzun olduğunda içindekiler tablosu (kenar çubuğundaki bağlantılar) otomatik olarak oluşturulur. Bu varsayılan davranış bir `{{toc}}` [meta etiketi |#meta-tags]. TOC için metin varsayılan olarak başlıktan alınır, ancak farklı bir metin kullanmak mümkündür. `.{toc}` değiştirici. Bu özellikle uzun başlıklar için kullanışlıdır. +İçindekiler (sağ menüdeki bağlantılar), boyutu 4.000 baytı aşan tüm sayfalar için otomatik olarak oluşturulur ve bu varsayılan davranış [#Meta etiketleri] `{{toc}}` kullanılarak ayarlanabilir. İçeriği oluşturan metin standart olarak doğrudan başlıkların metninden alınır, ancak `.{toc}` değiştiricisi kullanılarak içerikte farklı bir metin görüntülenebilir, bu özellikle daha uzun başlıklar için kullanışlıdır. ``` -Long and Intelligent Heading .{toc: A Different Text for TOC} -============================================================= +Uzun ve akıllı başlık .{toc: İçerikte gösterilen herhangi bir başka metin} +========================================================================== ``` -Meta Etiketler .[#toc-meta-tags] -================================ +Meta etiketleri +=============== -- kendi sayfa başlığınızı ayarlama (içinde `<title>` ve ekmek kırıntıları) `{{title: Another name}}` -- yeniden yönlendirme `{{redirect: pla:cs}}` - [bağlantılara bakınız|#links] -- zorlama `{{toc}}` veya devre dışı bırakma `{{toc: no}}` i̇çeri̇k tablosu +- özel sayfa başlığı ayarlama (`<title>` ve içerik haritası navigasyonunda) `{{title: Başka bir başlık}}` +- yönlendirme `{{redirect: pla:cs}}` - bkz. [#Bağlantılar] +- otomatik içeriği (tek tek başlıklara bağlantılar içeren kutu) zorlama `{{toc}}` veya devre dışı bırakma `{{toc: no}}` {{priority: -1}} diff --git a/contributing/uk/@home.texy b/contributing/uk/@home.texy index 68ed5dd868..befb74c6c5 100644 --- a/contributing/uk/@home.texy +++ b/contributing/uk/@home.texy @@ -1,17 +1,17 @@ -Станьте дописувачем Nette -************************* +Станьте контриб'ютором Nette +**************************** .[perex] -Подивіться, як ви можете долучитися до нашого проекту з відкритим вихідним кодом. Дізнайтеся, як зробити свій внесок у вихідний код і документацію, і приєднайтеся до мережі розробників, які присвятили себе вдосконаленню Nette. +Дізнайтеся, як ви можете долучитися до нашого open source проекту. Ознайомтеся з процедурами внесення внеску у вихідний код та документацію та станьте частиною спільноти розробників, які активно беруть участь у вдосконаленні Nette. **Код** -- [Долучення до коду |code] -- [Стандарти кодування |coding-standard] +- [Як зробити внесок у код? |code] +- [Стандарт кодування |coding-standard] **Документація** -- [Внесок у документацію |documentation] +- [Як зробити внесок у документацію? |documentation] - [Синтаксис документації |syntax] - "Редактор попереднього перегляду":https://editor.nette.org diff --git a/contributing/uk/@left-menu.texy b/contributing/uk/@left-menu.texy index 5ff2833e33..6a23fcbcb1 100644 --- a/contributing/uk/@left-menu.texy +++ b/contributing/uk/@left-menu.texy @@ -1,10 +1,10 @@ Код *** -- [Внесок до коду |code] -- [Стандарти кодування |coding-standard] +- [Як зробити внесок у код? |code] +- [Стандарт кодування |coding-standard] Документація ************ -- [Внесок у документацію |documentation] +- [Як зробити внесок у документацію? |documentation] - [Синтаксис документації |syntax] - "Редактор попереднього перегляду":https://editor.nette.org diff --git a/contributing/uk/code.texy b/contributing/uk/code.texy index 0461320b30..5e0e04479c 100644 --- a/contributing/uk/code.texy +++ b/contributing/uk/code.texy @@ -1,87 +1,87 @@ -Внесок до Коду -************** +Як зробити внесок у код +*********************** .[perex] -Ви плануєте зробити свій внесок у Nette Framework і хочете ознайомитися з правилами та процедурами? Цей посібник для початківців допоможе вам зробити ефективний внесок у код, працювати з репозиторіями та впроваджувати зміни. +Ви збираєтеся зробити внесок у Nette Framework і вам потрібно розібратися в правилах та процедурах? Цей посібник для початківців крок за кроком покаже вам, як ефективно робити внесок у код, працювати з репозиторіями та впроваджувати зміни. -Порядок дій .[#toc-procedure] -============================= +Процедура +========= -Щоб долучитися до коду, необхідно мати обліковий запис на [GitHub |https://github.com] і бути знайомим з основами роботи з системою контролю версій Git. Якщо ви не знайомі з Git'ом, ви можете ознайомитися з [git - простим керівництвом |https://rogerdudler.github.io/git-guide/] і розглянути можливість використання одного з багатьох [графічних клієнтів |https://git-scm.com/downloads/guis]. +Для того, щоб зробити внесок у код, необхідно мати обліковий запис на [GitHub|https://github.com] та бути знайомим з основами роботи з системою контролю версій Git. Якщо ви не володієте роботою з Git, можете ознайомитися з посібником [git - the simple guide |https://rogerdudler.github.io/git-guide/] та, за потреби, скористатися одним з багатьох [графічних клієнтів |https://git-scm.com/downloads/guis]. -Підготовка середовища та репозиторію .[#toc-preparing-the-environment-and-repository] -------------------------------------------------------------------------------------- +Підготовка середовища та репозиторію +------------------------------------ -1) На GitHub створіть [форк |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] [сховища пакунків |www:packages], який ви збираєтеся модифікувати -2) [Клонуйте |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] цей репозиторій на свій комп'ютер -3) Встановіть залежності, включаючи [Nette Tester |tester:], за допомогою команди `composer install` -4) Переконайтеся, що тести працюють, запустивши їх `composer tester` -5) Створіть [нову гі |#New Branch] лку на основі останньої випущеної версії +1) на GitHub створіть [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] репозиторію [пакета |www:packages], який ви збираєтеся змінити +2) цей репозиторій [клонуєте |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] на свій комп'ютер +3) встановіть залежності, включно з [Nette Tester |tester:], за допомогою команди `composer install` +4) перевірте, чи працюють тести, запустивши `composer tester` +5) створіть [нову гілку |#Нова гілка] на основі останньої випущеної версії -Впровадження власних змін .[#toc-implementing-your-own-changes] ---------------------------------------------------------------- +Реалізація власних змін +----------------------- -Тепер ви можете вносити власні корективи в код: +Тепер ви можете внести свої власні зміни до коду: -1) Впроваджуйте бажані зміни і не забувайте про тести -2) Переконайтеся, що тести успішно запускаються за допомогою `composer tester` -3) Перевірте, чи відповідає код [стандартам кодування |#coding standards] -4) Збережіть (зафіксуйте) зміни з описом у [такому форматі |#Commit Description] +1) запрограмуйте необхідні зміни та не забудьте про тести +2) переконайтеся, що тести проходять успішно, за допомогою `composer tester` +3) перевірте, чи код відповідає [стандарту кодування |#Стандарти кодування] +4) збережіть зміни (зробіть коміт) з описом у [цьому форматі |#Опис коміту] -Ви можете створити декілька комітів, по одному для кожного логічного кроку. Кожен коміт повинен бути значущим сам по собі. +Ви можете створити кілька комітів, по одному для кожного логічного кроку. Кожен коміт повинен бути осмисленим сам по собі. -Подання змін .[#toc-submitting-changes] ---------------------------------------- +Надсилання змін +--------------- -Якщо ви задоволені змінами, ви можете відправити їх: +Як тільки ви будете задоволені змінами, можете їх надіслати: -1) Перенесіть зміни на GitHub у свій форк -2) Звідти відправте їх до репозиторію Nette, створивши [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) -3) Надайте [достатньо інформації |#pull request description] в описі +1) надішліть (push) зміни на GitHub у ваш форк +2) звідти надішліть їх до репозиторію Nette, створивши [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) +3) надайте в описі [достатньо інформації |#Опис pull request] -Включення зворотного зв'язку .[#toc-incorporating-feedback] ------------------------------------------------------------ +Врахування зауважень +-------------------- -Ваші комміти тепер видимі для інших. Ми часто отримуємо коментарі з пропозиціями: +Ваші коміти тепер побачать і інші. Зазвичай ви отримуватимете коментарі із зауваженнями: -1) Відстежуйте запропоновані зміни -2) Включіть їх як нові комміти або об'єднайте [з попередніми |https://help.github.com/en/github/using-git/about-git-rebase] -3) Повторно надішліть коміти на GitHub, і вони автоматично з'являться в запиті на витягування +1) слідкуйте за запропонованими змінами +2) врахуйте їх як нові коміти або [об'єднайте з попередніми |https://help.github.com/en/github/using-git/about-git-rebase] +3) знову надішліть коміти на GitHub, і вони автоматично з'являться в pull request -Ніколи не створюйте новий пул-запит для зміни існуючого. +Ніколи не створюйте новий pull request для зміни існуючого. -Документація .[#toc-documentation] ----------------------------------- +Документація +------------ -Якщо ви змінили функціонал або додали новий, не забудьте [додати його до документації |documentation]. +Якщо ви змінили функціональність або додали нову, не забудьте також [додати це до документації |documentation]. -Нова гілка .[#toc-new-branch] -============================= +Нова гілка +========== -Якщо можливо, вносьте зміни відповідно до останньої випущеної версії, тобто останнього тегу у гілці. Для тегу v3.2.1 створіть гілку за допомогою цієї команди: +Якщо це можливо, вносьте зміни щодо останньої випущеної версії, тобто останнього тегу в даній гілці. Для тегу `v3.2.1` ви створите гілку цією командою: ```shell git checkout -b new_branch_name v3.2.1 ``` -Стандарти кодування .[#toc-coding-standards] -============================================ +Стандарти кодування +=================== -Ваш код повинен відповідати [стандарту кодування |coding standard], що використовується в Nette Framework. Існує автоматичний інструмент для перевірки та виправлення коду. Ви можете встановити його **глобально** через Composer до папки на ваш вибір: +Ваш код повинен відповідати [стандарту кодування |Coding Standard], що використовується в Nette Framework. Для перевірки та виправлення коду доступний автоматичний інструмент. Його можна встановити через Composer **глобально** у вибрану вами папку: ```shell composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Тепер ви зможете запустити інструмент у терміналі. Перша команда перевіряє, а друга - виправляє код у теках `src` і `tests` у поточному каталозі: +Тепер ви повинні мати можливість запустити інструмент у терміналі. Першою командою ви перевірите, а другою – виправите код у папках `src` та `tests` у поточному каталозі: ```shell /path/to/nette-coding-standard/ecs check @@ -89,29 +89,29 @@ composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Опис комміту .[#toc-commit-description] -======================================= +Опис коміту +=========== -У Nette теми коммітів мають наступний формат: `Presenter: fixed AJAX detection [Closes #69]` +У Nette теми комітів мають формат: `Presenter: виправлено виявлення AJAX [Closes #69]` - область, за якою слідує двокрапка -- мета комміту в минулому часі; якщо можливо, починайте зі слів на кшталт added, fixed, refactored, changed, removed -- якщо комміт порушує зворотну сумісність, додайте "BC break" -- будь-яке з'єднання з трекером випусків, наприклад, `(#123)` або `[Closes #69]` -- після теми може бути один порожній рядок, за яким слідує більш детальний опис, включаючи, наприклад, посилання на форум +- мета коміту в минулому часі, якщо можливо, почніть зі слова: `added` (додано нову властивість), `fixed` (виправлення), `refactored` (зміна в коді без зміни поведінки), `changed`, `removed` +- якщо коміт порушує зворотну сумісність, додайте "BC break" +- можливий зв'язок з трекером проблем, як `(#123)` або `[Closes #69]` +- за темою може слідувати один порожній рядок, а потім детальніший опис, включно з, наприклад, посиланнями на форум -Опис запиту на витягування .[#toc-pull-request-description] -=========================================================== +Опис pull request +================= -При створенні pull request інтерфейс GitHub дозволить вам ввести заголовок і опис. Надайте лаконічний заголовок і включіть якомога більше інформації в опис про причини вашої зміни. +При створенні pull request інтерфейс GitHub дозволить вам ввести назву та опис. Вкажіть змістовну назву, а в описі надайте якомога більше інформації про причини вашої зміни. -Також вкажіть у заголовку, чи це нова функція, чи виправлення помилки, і чи може це спричинити проблеми зворотної сумісності (BC break). Якщо є пов'язана проблема, зробіть посилання на неї, щоб вона була закрита після схвалення запиту на вилучення. +Також відобразиться заголовок, де вкажіть, чи це нова функція, чи виправлення помилки, і чи може відбутися порушення зворотної сумісності (BC break). Якщо є пов'язана проблема (issue), посилайтеся на неї, щоб її було закрито після схвалення pull request. ``` -- bug fix / new feature? <!-- #issue numbers, if any --> -- BC break? yes/no -- doc PR: nette/docs#? <!-- highly welcome, see https://nette.org/en/writing --> +- виправлення помилки / нова функція? <!-- #номери issue, якщо є --> +- BC break? так/ні +- doc PR: nette/docs#? <!-- дуже вітається, див. https://nette.org/en/writing --> ``` diff --git a/contributing/uk/coding-standard.texy b/contributing/uk/coding-standard.texy index 059161cb33..20e5a5e9a9 100644 --- a/contributing/uk/coding-standard.texy +++ b/contributing/uk/coding-standard.texy @@ -1,44 +1,44 @@ Стандарт кодування ****************** -У цьому документі описано правила та рекомендації щодо розробки Nette. Надаючи код для Nette, ви повинні дотримуватися їх. Найпростіший спосіб зробити це - імітувати наявний код. -Ідея полягає в тому, щоб увесь код виглядав так, ніби його написала одна людина. +.[perex] +Цей документ описує правила та рекомендації для розробки Nette. При внесенні коду до Nette ви повинні їх дотримуватися. Найпростіший спосіб зробити це – наслідувати існуючий код. Мета полягає в тому, щоб весь код виглядав так, ніби його написала одна людина. -Стандарт кодування Nette відповідає [PSR-12 Extended Coding Style |https://www.php-fig.org/psr/psr-12/] з двома основними винятками: він використовує [tabs замість пробілів |#tabs вместо пробелов] для відступів і використовує [PascalCase для констант класів |https://blog.nette.org/uk/sob-mense-kriku-v-kodi]. +Стандарт кодування Nette відповідає [PSR-12 Extended Coding Style |https://www.php-fig.org/psr/psr-12/] з двома основними винятками: для відступів він використовує [#табуляції замість пробілів] та для [констант класів використовує PascalCase|https://blog.nette.org/uk/for-less-screaming-in-the-code]. -Загальні правила .[#toc-general-rules] -====================================== +Загальні правила +================ -- Кожен PHP-файл повинен містити `declare(strict_types=1)` -- Два порожніх рядки використовуються для розділення методів для кращої читабельності. -- Причина використання оператора shut-up повинна бути задокументована: `@mkdir($dir); // @ - directory may exist` -- Якщо використовується слабкий типізований оператор порівняння (тобто `==`, `!=`, ...), необхідно задокументувати намір: `// == to accept null` -- Ви можете записати більше виключень в одному файлі `exceptions.php` -- Для інтерфейсів не вказується видимість методів, оскільки вони завжди є загальнодоступними. -- Кожна властивість, значення, що повертається, та параметр повинні мати вказаний тип. З іншого боку, для фінальних констант ми ніколи не вказуємо тип, оскільки він є очевидним. -- Для відокремлення рядка слід використовувати одинарні лапки, за винятком випадків, коли сам літерал містить апострофи. +- Кожен файл PHP повинен містити `declare(strict_types=1)` +- Два порожні рядки використовуються для розділення методів для кращої читабельності. +- Причина використання оператора приглушення помилок (`@`) повинна бути задокументована: `@mkdir($dir); // @ - каталог може існувати`. +- Якщо використовується оператор порівняння зі слабкою типізацією (тобто `==`, `!=`, ...), намір повинен бути задокументований: `// == прийняти null` +- В один файл `exceptions.php` можна записати кілька винятків. +- Для інтерфейсів не вказується видимість методів, оскільки вони завжди публічні. +- Кожна властивість, повернене значення та параметр повинні мати вказаний тип. Навпаки, для фінальних констант тип ніколи не вказуємо, оскільки він очевидний. +- Для обмеження рядка слід використовувати одинарні лапки (`'`), за винятком випадків, коли сам літерал містить апострофи. -Угоди про іменування .[#toc-naming-conventions] -=============================================== +Угоди про іменування +==================== -- Не використовуйте абревіатури, якщо тільки повна назва не є занадто довгою. -- Використовуйте великі літери для двобуквених абревіатур, паскаль/верблюд для довших абревіатур. -- Використовуйте іменник або іменникове словосполучення для назви класу. -- Назви класів повинні містити не тільки специфіку (`Array`), але й узагальнення (`ArrayIterator`). Атрибути PHP є винятком. -- "Для констант класу та зчислень слід використовувати PascalCaps":https://blog.nette.org/uk/sob-mense-kriku-v-kodi. -- "Інтерфейси та абстрактні класи не повинні містити префіксів або суфіксів":https://blog.nette.org/uk/prefiksi-ta-sufiksi-ne-potribni-v-nazvah-interfejsiv, таких як `Abstract`, `Interface` або `I`. +- Не використовуйте скорочення, якщо повна назва не надто довга. +- Для дволітерних скорочень використовуйте великі літери, для довших скорочень – Pascal/camelCase. +- Для назви класу використовуйте іменник або словосполучення. +- Назви класів повинні містити не лише специфічність (`Array`), але й загальність (`ArrayIterator`). Винятком є атрибути мови PHP. +- "Константи класів та enum-и повинні використовувати PascalCase":https://blog.nette.org/uk/for-less-screaming-in-the-code. +- "Інтерфейси та абстрактні класи не повинні містити префіксів або суфіксів":https://blog.nette.org/uk/prefixes-and-suffixes-do-not-belong-in-interface-names як `Abstract`, `Interface` або `I`. -Обгортання та брекети .[#toc-wrapping-and-braces] -================================================= +Перенесення та фігурні дужки +============================ -Nette Coding Standard відповідає PSR-12 (або PER Coding Style), у деяких пунктах він уточнює його більше або модифікує: +Стандарт кодування Nette відповідає PSR-12 (або PER Coding Style), в деяких пунктах доповнює або змінює його: -- стрілочні функції записуються без пробілу перед дужкою, тобто `fn($a) => $b`. -- між різними типами операторів імпорту `use` не потрібен порожній рядок -- тип функції/методу, що повертається, і дужка, що відкриває, повинні розташовуватися на окремих рядках для кращої читабельності: +- стрілкові функції пишуться без пробілу перед дужкою, тобто `fn($a) => $b` +- не вимагається порожній рядок між різними типами імпортів `use` +- тип повернення функції/методу та початкова фігурна дужка завжди знаходяться на окремих рядках: ```php public function find( @@ -50,19 +50,23 @@ Nette Coding Standard відповідає PSR-12 (або PER Coding Style), у } ``` +Початкова фігурна дужка на окремому рядку важлива для візуального розділення сигнатури функції/методу від тіла. Якщо сигнатура знаходиться на одному рядку, розділення очевидне (зображення зліва), якщо на кількох рядках, у PSR сигнатури та тіла зливаються (посередині), тоді як у стандарті Nette вони залишаються розділеними (праворуч): -Блоки документації (phpDoc) .[#toc-documentation-blocks-phpdoc] -=============================================================== +[* new-line-after.webp *] -Головне правило: ніколи не дублюйте інформацію про сигнатуру, наприклад, тип параметра або тип повернення, не маючи жодної додаткової мети. + +Блоки документації (phpDoc) +=========================== + +Основне правило: Ніколи не дублюйте жодної інформації в сигнатурі, такої як тип параметра або тип повернення, без доданої вартості. Блок документації для визначення класу: - Починається з опису класу. -- Далі йде порожній рядок. -- Далі йдуть анотації `@property` (або `@property-read`, `@property-write`), одна за одною. Синтаксис такий: анотація, пробіл, тип, пробіл, $name. -- Далі йдуть анотації `@method`, одна за одною. Синтаксис такий: анотація, пробіл, тип, що повертається, пробіл, ім'я(тип $param, ...). -- Анотацію `@author` опущено. Авторство зберігається в історії вихідного коду. +- Слідує порожній рядок. +- Слідують анотації `@property` (або `@property-read`, `@property-write`), одна за одною. Синтаксис: анотація, пробіл, тип, пробіл, `$ім'я`. +- Слідують анотації `@method`, одна за одною. Синтаксис: анотація, пробіл, тип повернення, пробіл, ім'я(тип $param, ...). +- Анотація `@author` пропускається. Авторство зберігається в історії вихідного коду. - Можна використовувати анотації `@internal` або `@deprecated`. ```php @@ -76,7 +80,7 @@ Nette Coding Standard відповідає PSR-12 (або PER Coding Style), у */ ``` -Блок документації для властивості, що містить тільки анотацію `@var`, має бути однорядковим: +Блок документації для властивості, який містить лише анотацію `@var`, повинен бути однорядковим: ```php /** @var string[] */ @@ -86,17 +90,17 @@ private array $name; Блок документації для визначення методу: - Починається з короткого опису методу. -- Немає порожнього рядка. -- Анотації `@param`, одна за одною. +- Жодного порожнього рядка. +- Анотації `@param` по окремих рядках. - Анотація `@return`. - Анотації `@throws`, одна за одною. - Можна використовувати анотації `@internal` або `@deprecated`. -За кожною анотацією слідує один пробіл, за винятком `@param`, за яким слідують два пробіли для кращої читабельності. +За кожною анотацією слідує один пробіл, за винятком `@param`, за якою для кращої читабельності слідують два пробіли. ```php /** - * Finds a file in directory. + * Знаходить файл у каталозі. * @param string[] $options * @return string[] * @throws DirectoryNotFoundException @@ -105,21 +109,20 @@ public function find(string $dir, array $options): array ``` -Табуляція замість пробілів .[#toc-tabs-instead-of-spaces] -========================================================= +Табуляції замість пробілів +========================== -Табуляція має низку переваг перед пробілами: +Табуляції мають кілька переваг перед пробілами: -- розмір відступу налаштовується в редакторах і "веб":https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size -- вони не нав'язують коду вподобання користувача за розміром відступу, тому код більш переносимий -- їх можна набрати одним натисканням клавіші (де завгодно, а не тільки в редакторах, які перетворюють табуляцію на пробіл) -- відступи є їхньою метою -- поважати потреби слабозорих і сліпих колег. +- розмір відступу можна налаштувати в редакторах та на `"веб":https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size` +- не нав'язують коду переваги користувача щодо розміру відступу, тому код краще переноситься +- їх можна написати одним натисканням клавіші (будь-де, не тільки в редакторах, які перетворюють табуляції на пробіли) +- відступи – це їхній сенс +- поважають потреби колег з вадами зору та незрячих -Використовуючи табуляції в наших проєктах, ми даємо змогу налаштовувати ширину, що може здатися непотрібним більшості людей, але вкрай важливим для людей із порушеннями зору. +Використовуючи табуляції в наших проектах, ми дозволяємо налаштовувати ширину, що може здатися більшості людей зайвим, але для людей з вадами зору є необхідним. -Для сліпих програмістів, які використовують дисплеї Брайля, кожен пробіл представлений коміркою Брайля і займає цінний простір. Так, якщо відступ за замовчуванням становить 4 пробіли, відступ третього рівня забирає 12 комірок Брайля перед початком коду. -На 40-осередковому дисплеї, який найчастіше використовується на ноутбуках, це понад чверть доступних осередків, витрачених даремно без будь-якої інформації. +Для незрячих програмістів, які використовують брайлівські дисплеї, кожен пробіл представляє одну брайлівську комірку. Якщо стандартний відступ становить 4 пробіли, відступ 3-го рівня марнує 12 цінних брайлівських комірок ще до початку коду. На 40-комірковому дисплеї, який найчастіше використовується для ноутбуків, це більше чверті доступних комірок, які марнуються без будь-якої інформації. {{priority: -1}} diff --git a/contributing/uk/documentation.texy b/contributing/uk/documentation.texy index f70e43a7c8..95b53fa278 100644 --- a/contributing/uk/documentation.texy +++ b/contributing/uk/documentation.texy @@ -1,69 +1,68 @@ -Долучайтеся до створення документації -************************************* +Як зробити внесок у документацію +******************************** .[perex] -Внесок у документацію є одним з найцінніших видів діяльності, оскільки він допомагає іншим зрозуміти структуру. +Внесок у документацію є однією з найкорисніших діяльностей, оскільки ви допомагаєте іншим зрозуміти фреймворк. -Як писати? .[#toc-how-to-write] -------------------------------- +Як писати? +---------- -Документація в першу чергу призначена для людей, які є новачками в темі. Тому вона повинна відповідати декільком важливим пунктам: +Документація призначена насамперед для людей, які знайомляться з темою. Тому вона повинна відповідати кільком важливим пунктам: -- Починайте з простих і загальних тем. Переходьте до більш складних тем наприкінці -- Намагайтеся пояснювати тему якомога зрозуміліше. Наприклад, спробуйте спочатку пояснити тему колезі -- Надавайте лише ту інформацію, яку користувачеві дійсно потрібно знати з даної теми -- Переконайтеся, що ваша інформація точна. Тестуйте кожен код -- Будьте лаконічними - скорочуйте те, що пишете, вдвічі. А потім не соромтеся робити це ще раз -- Економно використовуйте виділення, від напівжирного шрифту до рамок, таких як `.[note]` -- Дотримуйтесь [стандарту кодування |Coding Standard] в коді +- Починайте з простого та загального. До більш складних тем переходьте лише наприкінці. +- Намагайтеся пояснити річ якомога краще. Спробуйте, наприклад, спочатку пояснити тему колезі. +- Наводьте лише ту інформацію, яка дійсно потрібна користувачеві для даної теми. +- Перевірте, чи ваша інформація дійсно правдива. Кожен код протестуйте. +- Будьте лаконічними - те, що напишете, скоротіть наполовину. А потім, можливо, ще раз. +- Економте на виділеннях усіх видів, від жирного шрифту до рамок типу `.[note]`. +- У кодах дотримуйтесь [стандарту кодування |Coding Standard]. -Також вивчайте [синтаксис |syntax]. Для попереднього перегляду статті під час написання ви можете скористатися [редактором попереднього перегляду |https://editor.nette.org/]. +Освойте також [синтаксис |syntax]. Для попереднього перегляду статті під час її написання можете використовувати [редактор з попереднім переглядом |https://editor.nette.org/]. -Мовні мутації .[#toc-language-mutations] ----------------------------------------- +Мовні версії +------------ -Англійська є основною мовою, тому ваші зміни повинні бути англійською. Якщо англійська не є вашою сильною стороною, скористайтеся [DeepL Перекладачем |https://www.deepl.com/translator], і інші перевірять ваш текст. +Основною мовою є англійська, тому ваші зміни повинні бути як чеською, так і англійською. Якщо англійська не є вашою сильною стороною, використовуйте [DeepL Translator |https://www.deepl.com/translator], а інші перевірять ваш текст. -Переклад на інші мови буде зроблено автоматично після затвердження та уточнення вашої правки. +Переклад на інші мови буде виконано автоматично після схвалення та доопрацювання вашої правки. -Тривіальні правки .[#toc-trivial-edits] ---------------------------------------- +Тривіальні правки +----------------- -Щоб зробити свій внесок у документацію, вам потрібно мати обліковий запис на [GitHub |https://github.com]. +Для внесення внеску в документацію необхідно мати обліковий запис на [GitHub|https://github.com]. -Найпростіший спосіб внести невелику зміну в документацію - скористатися посиланнями в кінці кожної сторінки: +Найпростіший спосіб внести невелику зміну в документацію – скористатися посиланнями в кінці кожної сторінки: -- *Показати на GitHub* відкриває вихідну версію сторінки на GitHub. Потім просто натисніть кнопку `E` і ви можете почати редагування (ви повинні бути зареєстровані на GitHub) -- *Відкрити попередній перегляд* відкриває редактор, де ви можете одразу побачити остаточний візуальний вигляд +- *Показати на GitHub* відкриє вихідний код даної сторінки на GitHub. Потім достатньо натиснути кнопку `E`, і ви можете почати редагувати (необхідно бути авторизованим на GitHub). +- *Відкрити попередній перегляд* відкриє редактор, де ви одразу побачите і кінцевий візуальний вигляд. -Оскільки [редактор поперед |https://editor.nette.org/] нього перегляду не має можливості зберігати зміни безпосередньо на GitHub, вам потрібно скопіювати вихідний текст в буфер обміну (за допомогою кнопки *Копіювати в буфер обміну*), а потім вставити його в редактор на GitHub. -Під полем для редагування знаходиться форма для відправки. Тут не забудьте коротко підсумувати і пояснити причину вашого редагування. Після відправки створюється так званий pull request (PR), який можна надалі редагувати. +Оскільки [редактор з попереднім переглядом |https://editor.nette.org/] не має можливості зберігати зміни безпосередньо на GitHub, необхідно після завершення редагування скопіювати вихідний текст у буфер обміну (кнопкою *Copy to clipboard*), а потім вставити його в редактор на GitHub. Під полем редагування є форма для надсилання. Тут не забудьте коротко підсумувати та пояснити причину вашої правки. Після надсилання створюється так званий pull request (PR), який можна далі редагувати. -Більші редагування .[#toc-larger-edits] ---------------------------------------- +Більші правки +------------- -Доцільніше бути знайомим з основами роботи з системою контролю версій Git, ніж покладатися виключно на інтерфейс GitHub. Якщо ви не знайомі з Git'ом, ви можете звернутися до [git - простий |https://rogerdudler.github.io/git-guide/] посібник і розглянути можливість використання одного з багатьох доступних [графічних клієнтів |https://git-scm.com/downloads/guis]. +Більш доцільно, ніж використовувати інтерфейс GitHub, бути знайомим з основами роботи з системою контролю версій Git. Якщо ви не володієте роботою з Git, можете ознайомитися з посібником [git - the simple guide |https://rogerdudler.github.io/git-guide/] та, за потреби, скористатися одним з багатьох [графічних клієнтів |https://git-scm.com/downloads/guis]. -Редагуйте документацію наступним чином: +Документацію редагуйте таким чином: -1) на GitHub створіть [форк |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] сховища [nette/docs |https://github.com/nette/docs] -2) [клонуйте |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] цей репозиторій на свій комп'ютер -3) потім внесіть зміни у [відповідній гілці |#Documentation Structure] -4) перевірте наявність зайвих пробілів у тексті за допомогою інструменту [Code-Checker |code-checker:] -5) збережіть (зафіксуйте) зміни -6) якщо зміни вас влаштовують, перенесіть їх на GitHub у свій форк -7) звідти відправте їх до репозиторію `nette/docs`, створивши [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) +1) на GitHub створіть [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] репозиторію [nette/docs |https://github.com/nette/docs] +2) цей репозиторій [клонуєте |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] на свій комп'ютер +3) потім у [відповідній гілці |#Структура документації] внесіть зміни +4) перевірте зайві пробіли в тексті за допомогою інструменту [Code-Checker |code-checker:] +4) збережіть зміни (зробіть коміт) +6) якщо ви задоволені змінами, надішліть (push) їх на GitHub у ваш форк +7) звідти надішліть їх до репозиторію `nette/docs`, створивши [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) -Зазвичай ви отримуєте коментарі з пропозиціями. Відстежуйте запропоновані зміни та вносьте їх. Додайте запропоновані зміни як нові коміти і повторно надішліть їх на GitHub. Ніколи не створюйте новий пул-запит лише для того, щоб змінити існуючий. +Зазвичай ви отримуватимете коментарі із зауваженнями. Слідкуйте за запропонованими змінами та враховуйте їх. Запропоновані зміни додайте як нові коміти та знову надішліть на GitHub. Ніколи не створюйте новий pull request для зміни існуючого pull request. -Структура документації .[#toc-documentation-structure] ------------------------------------------------------- +Структура документації +---------------------- -Вся документація знаходиться на GitHub в репозиторії [nette/docs |https://github.com/nette/docs]. Поточна версія знаходиться в головній гілці, тоді як старіші версії знаходяться в гілках `doc-3.x`, `doc-2.x`. +Вся документація розміщена на GitHub у репозиторії [nette/docs |https://github.com/nette/docs]. Поточна версія знаходиться в гілці `master`, старіші версії розміщені в гілках, таких як `doc-3.x`, `doc-2.x`. -Вміст кожної гілки розділено на основні папки, що представляють окремі області документації. Наприклад, `application/` відповідає https://doc.nette.org/en/application, `latte/` відповідає https://latte.nette.org і т.д. Кожна з цих папок містить підпапки, що представляють мовні мутації (`cs`, `en`, ...) і додатково підпапку `files` з зображеннями, які можна вставляти на сторінки документації. +Вміст кожної гілки поділяється на основні папки, що представляють окремі області документації. Наприклад, `application/` відповідає https://doc.nette.org/cs/application, `latte/` відповідає https://latte.nette.org тощо. Кожна така папка містить підпапки, що представляють мовні версії (`cs`, `en`, ...), та, за потреби, підпапку `files` із зображеннями, які можна вставляти на сторінки документації. diff --git a/contributing/uk/syntax.texy b/contributing/uk/syntax.texy index 1247014d59..c9d1b66d60 100644 --- a/contributing/uk/syntax.texy +++ b/contributing/uk/syntax.texy @@ -1,59 +1,59 @@ -Вікі-синтаксис -************** +Синтаксис документації +********************** -Вікі використовує [синтаксис |https://texy.info/en/syntax] Markdown & [Texy |https://texy.info/en/syntax] з деякими покращеннями. +Документація використовує Markdown та [синтаксис Texy |https://texy.nette.org/syntax] з деякими розширеннями. -Посилання .[#toc-links] -======================= +Посилання +========= -Для внутрішніх посилань використовується позначення у квадратних дужках `[link]` використовується позначення у квадратних дужках. Це або у формі з вертикальною рискою `[link text |link target]`або у скороченому вигляді `[link text]` якщо ціль збігається з текстом (після перетворення на малі літери та дефіси): +Для внутрішніх посилань використовується запис у квадратних дужках `[посилання]`. Це може бути або у формі з вертикальною рискою `[текст посилання |ціль посилання]`, або скорочено `[текст посилання]`, якщо ціль збігається з текстом (після перетворення на малі літери та дефіси): -- `[Page name]` -> `<a href="/en/page-name">Page name</a>` -- `[link text |Page name]` -> `<a href="/en/page-name">link text</a>` +- `[Назва сторінки |Page name]` -> `<a href="/uk/page-name">Назва сторінки</a>` +- `[текст посилання |Page name]` -> `<a href="/uk/page-name">текст посилання</a>` -Ми можемо посилатися на іншу мову або інший розділ. Розділ - це бібліотека Nette (наприклад, `forms`, `latte` тощо) або спеціальні розділи, такі як `best-practices`, `quickstart` тощо: +Ми можемо посилатися на іншу мовну версію або інший розділ. Розділом вважається бібліотека Nette (наприклад, `forms`, `latte` тощо) або спеціальні розділи, такі як `best-practices`, `quickstart` тощо: -- `[cs:Page name]` -> `<a href="/en/page-name">Page name</a>` (той самий розділ, іншою мовою) -- `[tracy:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (інший розділ, та сама мова) -- `[tracy:cs:Page name]` -> `<a href="//tracy.nette.org/en/page-name">Page name</a>` (інший розділ, інша мова) +- `[cs:Назва сторінки |cs:Page name]` -> `<a href="/cs/page-name">Назва сторінки</a>` (той самий розділ, інша мова) +- `[tracy:Назва сторінки |tracy:Page name]` -> `<a href="//tracy.nette.org/uk/page-name">Назва сторінки</a>` (інший розділ, та сама мова) +- `[tracy:cs:Назва сторінки |tracy:cs:Page name]` -> `<a href="//tracy.nette.org/cs/page-name">Назва сторінки</a>` (інший розділ та мова) -Також можна націлити таргетинг на певний заголовок на сторінці за допомогою `#`. +За допомогою `#` також можна націлитися на конкретний заголовок на сторінці. -- `[#Heading]` -> `<a href="#toc-heading">Heading</a>` (заголовок на поточній сторінці) -- `[Page name#Heading]` -> `<a href="/en/page-name#toc-heading">Page name</a>` +- `[Заголовок |#Heading]` -> `<a href="#toc-heading">Заголовок</a>` (заголовок на поточній сторінці) +- `[Назва сторінки#Заголовок |Page name#Heading]` -> `<a href="/uk/page-name#toc-heading">Назва сторінки</a>` -Посилання на домашню сторінку розділу: (`@home` - спеціальний термін для позначення домашньої сторінки розділу) +Посилання на головну сторінку розділу: (`@home` – це спеціальний вираз для домашньої сторінки розділу) -- `[link text |@home]` -> `<a href="/en/">link text</a>` -- `[link text |tracy:]` -> `<a href="//tracy.nette.org/en/">link text</a>` +- `[текст посилання |@home]` -> `<a href="/uk/">текст посилання</a>` +- `[текст посилання |tracy:]` -> `<a href="//tracy.nette.org/uk/">текст посилання</a>` -Посилання на документацію API .[#toc-links-to-api-documentation] ----------------------------------------------------------------- +Посилання на документацію API +----------------------------- -Завжди використовуйте наступні позначення: +Завжди вказуйте лише за допомогою цього запису: - `[api:Nette\SmartObject]` -> [api:Nette\SmartObject] - `[api:Nette\Forms\Form::setTranslator()]` -> [api:Nette\Forms\Form::setTranslator()] - `[api:Nette\Forms\Form::$onSubmit]` -> [api:Nette\Forms\Form::$onSubmit] - `[api:Nette\Forms\Form::Required]` -> [api:Nette\Forms\Form::Required] -Повні назви використовуйте лише у першій згадці. Для інших посилань використовуйте спрощену назву: +Повністю кваліфіковані назви використовуйте лише при першій згадці. Для подальших посилань використовуйте спрощену назву: - `[Form::setTranslator() |api:Nette\Forms\Form::setTranslator()]` -> [Form::setTranslator() |api:Nette\Forms\Form::setTranslator()] -Посилання на документацію по PHP .[#toc-links-to-php-documentation] -------------------------------------------------------------------- +Посилання на документацію PHP +----------------------------- - `[php:substr]` -> [php:substr] -Вихідний код .[#toc-source-code] -================================ +Вихідний код +============ -Блок коду починається з <code>```lang</code> і закінчується <code>```</code>. Підтримувані мови: `php`, `latte`, `neon`, `html`, `css`, `js` і `sql`. Завжди використовуйте табуляцію для відступів. +Блок коду починається з <code>```lang</code> і закінчується <code>```</code>. Підтримувані мови: `php`, `latte`, `neon`, `html`, `css`, `js` та `sql`. Для відступів завжди використовуйте табуляції. ``` ```php @@ -63,7 +63,7 @@ ``` ``` -Ви також можете вказати ім'я файлу як <code>```php .{file: ArrayTest.php}</code> і блок коду буде відображено саме так: +Ви також можете вказати ім'я файлу як <code>```php .{file: ArrayTest.php}</code>, і блок коду буде відрендерено таким чином: ```php .{file: ArrayTest.php} public function renderPage($id) @@ -72,71 +72,71 @@ public function renderPage($id) ``` -Заголовки .[#toc-headings] -========================== +Заголовки +========= -Верхній заголовок (назва сторінки) підкреслити зірочками (`*`). For normal headings use equal signs (`=`) and then hyphens (`-`). +Найвищий заголовок (тобто назву сторінки) підкресліть зірочками. Для розділення секцій використовуйте знаки рівності. Заголовки підкреслюйте знаками рівності, а потім дефісами: ``` -MVC Applications & Presenters -***************************** +MVC Додатки & презентери +************************ ... -Link Creation -============= +Створення посилань +================== ... -Links in Templates ------------------- +Посилання в шаблонах +-------------------- ... ``` -Поля та стилі .[#toc-boxes-and-styles] -====================================== +Рамки та стилі +============== -Головний абзац, позначений класом `.[perex]` .[perex] +Перекс позначимо класом `.[perex]` .[perex] -Примітки, позначені класом `.[note]` .[note] +Примітку позначимо класом `.[note]` .[note] -Порада, позначена класом `.[tip]` .[tip] +Пораду позначимо класом `.[tip]` .[tip] -Попередження, позначене класом `.[caution]` .[caution] +Застереження позначимо класом `.[caution]` .[caution] -Сильне попередження, позначене класом `.[warning]` .[warning] +Більш сильне застереження позначимо класом `.[warning]` .[warning] Номер версії `.{data-version:2.4.10}` .{data-version:2.4.10} -Класи слід писати перед відповідним рядком: +Класи записуйте перед рядком: ``` -.[note] -This is a note. +.[perex] +Це перекс. ``` -Зверніть увагу, що такі поля, як `.[tip]` привертають увагу і тому повинні використовуватися для підкреслення, а не для менш важливої інформації. +Будь ласка, усвідомте, що рамки, такі як `.[tip]`, "притягують" очі, тому їх використовують для підкреслення, а не для менш важливої інформації. Тому максимально економте їх використання. -Зміст .[#toc-table-of-contents] -=============================== +Зміст +===== -Зміст (посилання в бічній панелі) автоматично генерується, коли сторінка довша за 4 000 байт. Цю поведінку за замовчуванням можна змінити за допомогою мета-тега `{{toc}}` [мета-тегом |#meta-tags]. Текст для TOC за замовчуванням береться з заголовка, але можна використовувати інший текст за допомогою модифікатора `.{toc}` модифікатором. Це особливо корисно для довгих заголовків. +Зміст (посилання в правому меню) генерується автоматично для всіх сторінок, розмір яких перевищує 4 000 байт, причому цю стандартну поведінку можна змінити за допомогою [#Метатеги] `{{toc}}`. Текст, що утворює зміст, стандартно береться безпосередньо з тексту заголовків, але за допомогою модифікатора `.{toc}` можна відобразити в змісті інший текст, що особливо корисно для довших заголовків. ``` -Long and Intelligent Heading .{toc: A Different Text for TOC} -============================================================= +Довгий та розумний заголовок .{toc: Будь-який інший текст, відображений у змісті} +================================================================================= ``` -Мета-теги .[#toc-meta-tags] -=========================== +Метатеги +======== -- встановлення власного заголовка сторінки (в `<title>` та хлібними крихтами) `{{title: Another name}}` -- перенаправлення `{{redirect: pla:cs}}` - див. [посилання |#links] -- примусове виконання `{{toc}}` або відключення `{{toc: no}}` зміст +- встановлення власної назви сторінки (у `<title>` та навігаційному ланцюжку) `{{title: Інша назва}}` +- перенаправлення `{{redirect: pla:cs}}` - див. [#Посилання] +- примусове `{{toc}}` або заборона `{{toc: no}}` автоматичного змісту (блок з посиланнями на окремі заголовки) {{priority: -1}} diff --git a/database/bg/@home.texy b/database/bg/@home.texy index 6125424c61..7eccf1677b 100644 --- a/database/bg/@home.texy +++ b/database/bg/@home.texy @@ -1,20 +1,21 @@ -Поддържани сървъри -================== +Поддържани бази данни +===================== -Поддържат се следните сървъри за бази данни: +Nette поддържа следните бази данни: -|* Сървър |* Име на DSN |* Поддръжка на ядро |* Поддръжка на Explorer -|* MySQL (>= 5.1) | mysql | YES | YES -|* PostgreSQL (>= 9.0) | pgsql | ДА -| Sqlite 3 (>= 3.8) | sqlite | ДА ДА -| Oracle | oci | YES YES - -| MS SQL (PDO_SQLSRV) | sqlsrv | ДА | ДА -| MS SQL (PDO_DBLIB) | mssql | ДА -| ODBC | odbc | ДА | - +|* Сървър на база данни |* DSN име |* Поддръжка в Core |* Поддръжка в Explorer +| MySQL (>= 5.1) | mysql | ДА | ДА +| PostgreSQL (>= 9.0) | pgsql | ДА | ДА +| Sqlite 3 (>= 3.8) | sqlite | ДА | ДА +| Oracle | oci | ДА | - +| MS SQL (PDO_SQLSRV) | sqlsrv | ДА | ДА +| MS SQL (PDO_DBLIB) | mssql | ДА | - +| ODBC | odbc | ДА | - -{{title: Nette Database}} -{{description: Nette Database опростява извличането на данни от базата данни, без да е необходимо да се пишат SQL заявки. Той задава ефективни заявки и не прехвърля ненужни данни.}} + +{{maintitle: Nette Database - awesome database layer for PHP}} +{{description: Nette Database значително улеснява извличането на данни от базата данни, без да е необходимо да се пишат SQL заявки. Изпълнява ефективни заявки и не прехвърля излишни данни.}} diff --git a/database/bg/@left-menu.texy b/database/bg/@left-menu.texy index 28c7090670..928b1f73ea 100644 --- a/database/bg/@left-menu.texy +++ b/database/bg/@left-menu.texy @@ -1,5 +1,12 @@ -База данни -********** -- [Ядро |Core] -- [Изследовател |Explorer] -- [Настройване |configuration] +Nette Database +************** +- [Въведение |guide] +- [SQL достъп |sql way] +- [Explorer |Explorer] +- [Трансакции |transactions] +- [Изключения |exceptions] +- [Рефлексия |reflection] +- [Мапинг |mapping] +- [Конфигурация |configuration] +- [Рискове за сигурността |security] +- [Надграждане |en:upgrading] diff --git a/database/bg/@meta.texy b/database/bg/@meta.texy new file mode 100644 index 0000000000..57804a1127 --- /dev/null +++ b/database/bg/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документация на Nette}} diff --git a/database/bg/configuration.texy b/database/bg/configuration.texy index d259bfd5f4..68d49c5011 100644 --- a/database/bg/configuration.texy +++ b/database/bg/configuration.texy @@ -1,73 +1,79 @@ -Конфигуриране на базата данни -***************************** +Конфигурация на базата данни +**************************** .[perex] -Преглед на опциите за конфигуриране на базата данни Nette. +Преглед на конфигурационните опции за Nette Database. -Ако не използвате цялата рамка, а само тази библиотека, прочетете [Как да изтеглите конфигурационния файл |bootstrap:]. +Ако не използвате целия framework, а само тази библиотека, прочетете [как да заредите конфигурацията|bootstrap:]. -Една връзка .[#toc-single-connection] -------------------------------------- +Една връзка +----------- -Създайте единична връзка към базата данни: +Конфигурация на една връзка към база данни: ```neon database: - # DSN, един задължителен ключ. + # DSN, единственият задължителен ключ dsn: "sqlite:%appDir%/Model/demo.db" user: ... password: ... ``` -Той създава услуги като `Nette\Database\Connection`, и `Nette\Database\Explorer` за ниво [Database Explorer |explorer]. Връзката с базата данни обикновено се свързва автоматично, но ако това не е възможно, използвайте имената на услугите `@database.default.connection` и `@database.default.context`. +Създава сървисите `Nette\Database\Connection` и `Nette\Database\Explorer`, които обикновено предаваме чрез [autowiring |dependency-injection:autowiring], или чрез връзка към [тяхното име |#DI Сървиси]. Други настройки: ```neon database: - # показва панела База данни в лентата Tracy Bar + # покажи панела на базата данни в Tracy Bar? debugger: ... # (bool) по подразбиране е true - # извежда заявка EXPLAIN в лентата на Tracy + # покажи EXPLAIN на заявките в Tracy Bar? explain: ... # (bool) по подразбиране е true - # разрешаване на автоматичното свързване за тази връзка - autowired: ... # (bool) по подразбиране е true за първата връзка + # разреши autowiring за тази връзка? + autowired: ... # (bool) по подразбиране е true при първата връзка - # конвенции за таблици: име на открит, статичен или клас - conventions: discovered # (string) 'discovered' по подразбиране + # конвенции за таблици: discovered, static или име на клас + conventions: discovered # (string) по подразбиране е 'discovered' options: - # свързване с базата данни само при необходимост? - lazy: ... # (bool) по подразбиране е false + # свързване към базата данни само когато е необходимо? + lazy: ... # (bool) по подразбиране е false - # Клас на PHP драйвера за бази данни - driverClass: # (string) + # PHP клас на драйвера на базата данни + driverClass: # (string) - # само за MySQL: # задава sql_mode - sqlmode: # (string) + # само MySQL: задава sql_mode + sqlmode: # (string) - # само за MySQL: set SET NAMES - charset: # (string) по подразбиране е 'utf8mb4' ('utf8' преди v5.5.3) + # само MySQL: задава SET NAMES + charset: # (string) по подразбиране е 'utf8mb4' - # само за Oracle и SQLite: formatDate - formatDateTime: # (string) по подразбиране е 'U' + # само MySQL: преобразува TINYINT(1) в bool + convertBoolean: # (bool) по подразбиране е false + + # връща колони с дата като immutable обекти (от версия 3.2.1) + newDateTime: # (bool) по подразбиране е false + + # само Oracle и SQLite: формат за съхранение на дата + formatDateTime: # (string) по подразбиране е 'U' ``` -Ключът `options` може да съдържа и други опции, които можете да намерите в [документацията на PDO драйвера |https://www.php.net/manual/en/pdo.drivers.php], напр: +В ключа `options` могат да се посочват други опции, които ще намерите в [документацията на PDO драйверите |https://www.php.net/manual/en/pdo.drivers.php], като например: ```neon -база данных: +database: options: PDO::MYSQL_ATTR_COMPRESS: true ``` -Множество връзки .[#toc-multiple-connections] ---------------------------------------------- +Множество връзки +---------------- -В конфигурацията можем да дефинираме повече връзки към базата данни, като ги разделим на именувани секции: +В конфигурацията можем да дефинираме и множество връзки към бази данни, като ги разделим на именувани секции: ```neon database: @@ -80,9 +86,23 @@ database: dsn: 'sqlite::memory:' ``` -Всяка дефинирана връзка създава услуги, включващи името на раздела в името си, т.е. `@database.main.connection` & `@database.main.context` и по-нататък `@database.another.connection` & `@database.another.context`. +Autowiring е включен само за сървисите от първата секция. Това може да се промени с помощта на `autowired: false` или `autowired: true`. + + +DI Сървиси +---------- + +Тези сървиси се добавят към DI контейнера, където `###` представлява името на връзката: + +| Име | Тип | Описание +|---------------------------------------------------------- +| `database.###.connection` | [api:Nette\Database\Connection] | връзка с базата данни +| `database.###.explorer` | [api:Nette\Database\Explorer] | [Database Explorer |database:explorer] + + +Ако дефинираме само една връзка, имената на сървисите ще бъдат `database.default.connection` и `database.default.explorer`. Ако дефинираме повече връзки, както в примера по-горе, имената ще отговарят на секциите, т.е. `database.main.connection`, `database.main.explorer` и след това `database.another.connection` и `database.another.explorer`. -Автоматичното свързване е разрешено само за услугите от първия раздел. Това може да се промени с помощта на `autowired: false` или `autowired: true`. Неавтоматичните услуги се предават по име: +Сървисите, които не са autowired, предаваме изрично чрез връзка към тяхното име: ```neon services: diff --git a/database/bg/core.texy b/database/bg/core.texy deleted file mode 100644 index 3f1ed5af5c..0000000000 --- a/database/bg/core.texy +++ /dev/null @@ -1,350 +0,0 @@ -Ядро на базата данни -******************** - -.[perex] -Ядрото на базата данни на Nette е слой за абстракция на базата данни и осигурява основни функции. - - -Инсталация .[#toc-installation] -=============================== - -Изтеглете и инсталирайте пакета с помощта на [Composer |best-practices:composer]: - -```shell -composer require nette/database -``` - - -Свързване и конфигуриране .[#toc-connection-and-configuration] -============================================================== - -За да се свържете с базата данни, просто създайте инстанция на класа [api:Nette\Database\Connection]: - -```php -$database = new Nette\Database\Connection($dsn, $user, $password); -``` - -Параметърът `$dsn` (име на източника на данни) е [същият, който се използва в PDO |https://www.php.net/manual/ru/pdo.construct.php#refsect1-pdo.construct-parameters], например `host=127.0.0.1;dbname=test`. Ако не успее, се изхвърля изключение `Nette\Database\ConnectionException`. - -[Конфигурацията на приложението |configuration] обаче предлага по-сложен начин. Ще добавим раздел `database`, а той ще създаде необходимите обекти и панел `Database` в панела за отстраняване на грешки на [Tracy |tracy:]. - -```neon -database: - dsn: 'mysql:host=127.0.0.1;dbname=test' - user: root - password: password -``` - -Обектът за връзка, който [получаваме като услуга от DI-контейнера |dependency-injection:passing-dependencies], напр: - -```php -class Model -{ - // подайте Nette\Database\Explorer, за да работите със слоя Database Explorer - public function __construct( - private Nette\Database\Connection $database, - ) { - } -} -``` - -За повече информация вижте [Конфигурация на базата данни |configuration]. - - -Запитвания .[#toc-queries] -========================== - -За да направите заявка към базата данни, използвайте метода `query()`, който връща [ResultSet |api:Nette\Database\ResultSet]. - -```php -$result = $database->query('SELECT * FROM users'); - -foreach ($result as $row) { - echo $row->id; - echo $row->name; -} - -echo $result->getRowCount(); // връща броя на редовете, ако е известен -``` - -.[note] -Можете да итерирате над `ResultSet` само веднъж, ако трябва да итерирате повече от веднъж, трябва да преобразувате резултата в масив, като използвате метода `fetchAll()`. - -Можете лесно да добавяте параметри към заявката, като обърнете внимание на въпросителния знак: - -```php -$database->query('SELECT * FROM users WHERE name = ?', $name); - -$database->query('SELECT * FROM users WHERE name = ? AND active = ?', $name, $active); - -$database->query('SELECT * FROM users WHERE id IN (?)', $ids); // $ids - массив -``` -<div class=warning> - -ПРЕДУПРЕЖДЕНИЕ Никога не конкатенирайте низове, за да избегнете [уязвимост чрез SQL инжекция |https://ru.wikipedia.org/wiki/%D0%92%D0%BD%D0%B5%D0%B4%D1%80%D0%B5%D0%BD%D0%B8%D0%B5_SQL-%D0%BA%D0%BE%D0%B4%D0%B0]! -/-- -$db->query('SELECT * FROM users WHERE name = ' . $name); // НЕПРАВИЛЬНО!!! -\-- -</div> - -Ако не успее, `query()` изхвърля изключението `Nette\Database\DriverException` или някое от неговите подчинени изключения: - -- [ConstraintViolationException |api:Nette\Database\ConstraintViolationException] - нарушение на някое от условията -- [ForeignKeyConstraintViolationException |api:Nette\Database\ForeignKeyConstraintViolationException] - невалиден чужд ключ -- [NotNullConstraintViolationException |api:Nette\Database\NotNullConstraintViolationException] - нарушение на условие NOT NULL -- [UniqueConstraintViolationException |api:Nette\Database\UniqueConstraintViolationException] - конфликт на уникален индекс - -Освен `query()`, има и други полезни методи: - -```php -// Връща асоциативен масив id => name -$pairs = $database->fetchPairs('SELECT id, name FROM users'); - -//връщане на всички редове като масив -$rows = $database->fetchAll('SELECT * FROM users'); - -//връща един ред -$row = $database->fetch('SELECT * FROM users WHERE id = ?', $id); - -// връщане на едно поле -$name = $database->fetchField('SELECT name FROM users WHERE id = ?', $id); -``` - -При неуспешен опит всички тези методи хвърлят изключение `Nette\Database\DriverException`. - - -Вмъкване, актуализиране и изтриване .[#toc-insert-update-delete] -================================================================ - -Параметърът, който вмъкваме в SQL заявката, може да бъде и масив (в този случай можем да пропуснем заместващия символ `?`), что может быть полезно для оператора `INSERT`: - -```php -$database->query('INSERT INTO users ?', [ // тук може да се пропусне въпросителен знак - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) - -$id = $database->getInsertId(); // връща автоматичното увеличение на вмъкнатия низ - -$id = $database->getInsertId($последователност); // или стойност на последователността -``` - -Вмъкване на няколко стойности: - -```php -$database->query('INSERT INTO users', [ - [ - 'name' => 'Jim', - 'year' => 1978, - ], [ - 'name' => 'Jack', - 'year' => 1987, - ], -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) -``` - -Можем също така да предаваме файлове, обекти DateTime или [изброявания |https://www.php.net/enumerations]: - -```php -$database->query('INSERT INTO users', [ - 'name' => $name, - 'created' => new DateTime, // или $database::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // вмъква съдържанието на файла - 'status' => State::New, // enum State -]); -``` - -Струни за актуализация: - -```php -$result = $database->query('UPDATE users SET', [ - 'name' => $name, - 'year' => $year, -], 'WHERE id = ?', $id); -// UPDATE users SET `name` = 'Jim', `year` = 1978 WHERE id = 123 - -echo $result->getRowCount(); // връща броя на засегнатите редове -``` - -За UPDATE можем да използваме операторите `+=` и `-=`: - -```php -$database->query('UPDATE users SET', [ - 'age+=' => 1, // note += -], 'WHERE id = ?', $id); -// UPDATE users SET `age` = `age` + 1 -``` - -Изтриване: - -```php -$result = $database->query('DELETE FROM users WHERE id = ?', $id); -echo $result->getRowCount(); // връща броя на засегнатите редове -``` - - -Разширени заявки .[#toc-advanced-queries] -========================================= - -Вмъкнете или актуализирайте, ако даден запис вече съществува: - -```php -$database->query('INSERT INTO users', [ - 'id' => $id, - 'name' => $name, - 'year' => $year, -], 'ON DUPLICATE KEY UPDATE', [ - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) -// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 -``` - -Обърнете внимание, че Nette Database разпознава SQL контекста, в който е вмъкнат параметърът на масива, и изгражда SQL кода по съответния начин. Така той генерира `(id, name, year) VALUES (123, 'Jim', 1978)` от първия масив и преобразува втория масив в `name = 'Jim', year = 1978`. - -Можем също така да опишем сортирането с помощта на масив, в който ключовете са имена на колони, а стойностите са булеви стойности, които определят дали да се сортират във възходящ ред: - -```php -$database->query('SELECT id FROM author ORDER BY', [ - 'id' => true, // възходящо - 'name' => false, // низходящо -]); -// SELECT id FROM author ORDER BY `id`, `name` DESC -``` - -Ако откриването е неуспешно, можете да посочите формата на асемблито, като използвате заместителя `?`, последван от подсказка. Поддържат се следните команди: - -| ?values | (key1, key2, ...) VALUES (value1, value2, ...) -| ?set | key1 = value1, key2 = value2, ... -| ?and | key1 = value1 AND key2 = value2 ... -| ?or | ключ1 = стойност1 ИЛИ ключ2 = стойност2 ... -| ?order | key1 ASC, key2 DESC - -В декларацията WHERE се използва операторът `?and`, така че условията са свързани `AND`: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND `year` = 1978 -``` - -Което може лесно да се промени на `OR`, като се използва заместителният символ `?or`: - -```php -$result = $database->query('SELECT * FROM users WHERE ?or', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' OR `year` = 1978 -``` - -Можем да използваме оператори в условията: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name <>' => $name, - 'year >' => $year, -]); -// SELECT * FROM users WHERE `name` <> 'Jim' AND `year` > 1978 -``` - -както и трансфери: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => ['Jim', 'Jack'], - 'role NOT IN' => ['admin', 'owner'], // изброяване + оператор NOT IN -]); -// SELECT * FROM users WHERE -// `name` IN ('Jim', 'Jack') AND `role` NOT IN ('admin', 'owner') -``` - -Можем също така да включим част от персонализирания SQL код, като използваме така наречения SQL литерал: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year >' => $database::literal('YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) -``` - -Алтернативно: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) -``` - -Литералът на SQL може да има и свои собствени параметри: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > ? AND year < ?', $min, $max), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) -``` - -Благодарение на това можем да създаваме интересни комбинации: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('?or', [ - 'active' => true, - 'role' => $role, - ]), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') -``` - - -Име на променливата .[#toc-variable-name] -========================================= - -Има заместващ символ `?name', който се използва, ако името на таблица или колона е променлива. (Внимавайте да не позволите на потребителя да манипулира съдържанието на такава променлива): - -```php -$table = 'blog.users'; -$column = 'name'; -$database->query('SELECT * FROM ?name WHERE ?name = ?', $table, $column, $name); -// SELECT * FROM `blog`.`users` WHERE `name` = 'Jim' -``` - - -Транзакции .[#toc-transactions] -=============================== - -Съществуват три метода за обработка на транзакции: - -```php -$database->beginTransaction(); - -$database->commit(); - -$database->rollback(); -``` - -Един елегантен метод предлага методът `transaction()`. Предавате обратно извикване, което се изпълнява в транзакция. Ако по време на изпълнението възникне изключение, транзакцията се прекратява, а ако всичко е наред, транзакцията се предава. - -```php -$id = $database->transaction(function ($database) { - $database->query('DELETE FROM ...'); - $database->query('INSERT INTO ...'); - // ... - return $database->getInsertId(); -}); -``` - -Както можете да видите, методът `transaction()` връща стойността на обратната връзка. - -Функцията Transaction() също може да бъде вложена, което опростява прилагането на независими хранилища. diff --git a/database/bg/exceptions.texy b/database/bg/exceptions.texy new file mode 100644 index 0000000000..92a7983581 --- /dev/null +++ b/database/bg/exceptions.texy @@ -0,0 +1,34 @@ +Изключения +********** + +Nette Database използва йерархия от изключения. Основният клас е `Nette\Database\DriverException`, който наследява от `PDOException` и предоставя разширени възможности за работа с грешки в базата данни: + +- Методът `getDriverCode()` връща кода на грешката от драйвера на базата данни +- Методът `getSqlState()` връща SQLSTATE кода +- Методите `getQueryString()` и `getParameters()` позволяват да се получи оригиналната заявка и нейните параметри + +От `DriverException` наследяват следните специализирани изключения: + +- `ConnectionException` - сигнализира за неуспешно свързване към сървъра на базата данни +- `ConstraintViolationException` - основен клас за нарушаване на ограниченията на базата данни, от който наследяват: + - `ForeignKeyConstraintViolationException` - нарушаване на външен ключ + - `NotNullConstraintViolationException` - нарушаване на ограничението NOT NULL + - `UniqueConstraintViolationException` - нарушаване на уникалността на стойността + + +Пример за прихващане на изключение `UniqueConstraintViolationException`, което възниква, когато се опитваме да вмъкнем потребител с имейл, който вече съществува в базата данни (при условие, че колоната email има уникален индекс). + +```php +try { + $database->query('INSERT INTO users', [ + 'email' => 'john@example.com', + 'name' => 'John Doe', + 'password' => $hashedPassword, + ]); +} catch (Nette\Database\UniqueConstraintViolationException $e) { + echo 'Потребител с този имейл вече съществува.'; + +} catch (Nette\Database\DriverException $e) { + echo 'Възникна грешка при регистрацията: ' . $e->getMessage(); +} +``` diff --git a/database/bg/explorer.texy b/database/bg/explorer.texy index 28fc39699a..b8d1c4a799 100644 --- a/database/bg/explorer.texy +++ b/database/bg/explorer.texy @@ -1,550 +1,912 @@ -Изследовател на бази данни -************************** +Database Explorer +***************** <div class=perex> -Nette Database Explorer улеснява много извличането на данни от база данни, без да се налага да се пишат SQL заявки. +Explorer предлага интуитивен и ефективен начин за работа с базата данни. Той автоматично се грижи за релациите между таблиците и оптимизацията на заявките, така че можете да се съсредоточите върху своето приложение. Работи веднага без настройка. Ако се нуждаете от пълен контрол над SQL заявките, можете да използвате [SQL достъп |database:sql-way]. -- използва ефективни заявки -- данните не се прехвърлят ненужно. -- предлага елегантен синтаксис +- Работата с данни е естествена и лесно разбираема +- Генерира оптимизирани SQL заявки, които зареждат само необходимите данни +- Позволява лесен достъп до свързани данни без необходимост от писане на JOIN заявки +- Работи незабавно без каквато и да е конфигурация или генериране на ентитита </div> -За да използвате Database Explorer, започнете с таблица - извикайте `table()` на обекта [api:Nette\Database\Explorer]. Най-лесният начин за получаване на екземпляр на обекта на контекста е [описан тук |core#Connection-and-Configuration] или, в случай че Nette Database Explorer се използва като отделен инструмент, той може да бъде [създаден ръчно |#Creating-Explorer-Manually]. + +С Explorer започвате с извикване на метода `table()` на обекта [api:Nette\Database\Explorer] (подробности за връзката ще намерите в главата [Връзка и конфигурация |database:configuration]): ```php -$books = $explorer->table('book'); // името на таблицата в базата данни е 'book' +$books = $explorer->table('book'); // 'book' е името на таблицата ``` -Извикването връща екземпляр на обекта [Selection |api:Nette\Database\Table\Selection], който може да бъде итериран, за да се извлекат всички книги. Всеки елемент (ред) е представен от екземпляр на [ActiveRow |api:Nette\Database\Table\ActiveRow] с данните, показани в неговите свойства: +Методът връща обект [Selection |api:Nette\Database\Table\Selection], който представлява SQL заявка. Към този обект можем да навързваме други методи за филтриране и сортиране на резултатите. Заявката се съставя и изпълнява едва в момента, когато започнем да изискваме данни. Например, чрез преминаване през цикъл `foreach`. Всеки ред е представен от обект [ActiveRow |api:Nette\Database\Table\ActiveRow]: ```php foreach ($books as $book) { - echo $book->title; - echo $book->author_id; + echo $book->title; // извеждане на колона 'title' + echo $book->author_id; // извеждане на колона 'author_id' } ``` -Извличането само на един конкретен ред се извършва чрез метода `get()`, който директно връща инстанция на ActiveRow. +Explorer значително улеснява работата с [#релации между таблици]. Следващият пример показва колко лесно можем да изведем данни от свързани таблици (книги и техните автори). Обърнете внимание, че не е необходимо да пишем никакви JOIN заявки, Nette ги създава за нас: ```php -$book = $explorer->table('book')->get(2); // връща книга с ID 2 -echo $book->title; -echo $book->author_id; +$books = $explorer->table('book'); + +foreach ($books as $book) { + echo 'Книга: ' . $book->title; + echo 'Автор: ' . $book->author->name; // създава JOIN към таблица 'author' +} ``` -Нека разгледаме един често срещан случай на употреба. Трябва да се запознаете с книгите и техните автори. Това е обичайното съотношение 1:N. Често използвано решение е извличането на данните чрез една SQL заявка с обединяване на таблици. Вторият вариант е да получите данните поотделно, да стартирате една заявка, за да получите книгите, и след това да получите автора за всяка книга с друга заявка (напр. в цикъл foreach). Това може лесно да се оптимизира, за да се изпълняват само две заявки - една за книгите и една за желаните автори - и точно това прави Nette Database Explorer. +Nette Database Explorer оптимизира заявките, за да бъдат възможно най-ефективни. Горепосоченият пример ще изпълни само две SELECT заявки, независимо дали обработваме 10 или 10 000 книги. -В примерите по-долу ще работим със схемата на базата данни, показана на фигурата. Съществуват връзки OneHasMany (1:N) (авторът на книгата `author_id` и евентуален преводач `translator_id`, който може да бъде `null`) и ManyHasMany (M:N) между книгата и нейните етикети. +Освен това Explorer следи кои колони се използват в кода и зарежда от базата данни само тях, като по този начин спестява допълнителна производителност. Това поведение е напълно автоматично и адаптивно. Ако по-късно промените кода и започнете да използвате други колони, Explorer автоматично ще промени заявките. Не е необходимо нищо да настройвате, нито да мислите кои колони ще ви трябват - оставете това на Nette. -[Пример, включващ схема, може да бъде намерен в GitHub |https://github.com/nette-examples/books]. -[* db-schema-1-.webp *] *** Структура на базата данни, използвана в примерите .<> +Филтриране и сортиране +====================== -Следващият код изброява името на автора за всяка книга и всички нейни етикети. [По-долу ще разгледаме |#Working-with-Relationships] как работи това вътрешно. +Класът `Selection` предоставя методи за филтриране и сортиране на избора на данни. -```php -$books = $explorer->table('book'); +.[language-php] +| `where($condition, ...$params)` | Добавя условие WHERE. Множество условия се свързват с оператор AND +| `whereOr(array $conditions)` | Добавя група условия WHERE, свързани с оператор OR +| `wherePrimary($value)` | Добавя условие WHERE по първичен ключ +| `order($columns, ...$params)` | Задава сортиране ORDER BY +| `select($columns, ...$params)` | Специфицира колоните, които трябва да бъдат заредени +| `limit($limit, $offset = null)` | Ограничава броя на редовете (LIMIT) и опционално задава OFFSET +| `page($page, $itemsPerPage, &$total = null)` | Задава пагиниране +| `group($columns, ...$params)` | Групира редове (GROUP BY) +| `having($condition, ...$params)` | Добавя условие HAVING за филтриране на групирани редове -foreach ($books as $book) { - echo 'title: ' . $book->title; - echo 'написано от: ' . $book->author->name; // $book->author е низ от таблицата 'author' +Методите могат да се навързват (т.нар. [fluent interface |nette:introduction-to-object-oriented-programming#Fluent Interfaces]): `$table->where(...)->order(...)->limit(...)`. - echo 'tags: '; - foreach ($book->related('book_tag') as $bookTag) { - echo $bookTag->tag->name . ', '; // $bookTag->tag е низ от таблицата 'tag' - } -} -``` +В тези методи можете също да използвате специална нотация за достъп до [данни от свързани таблици |#Заявки през свързани таблици]. -Ще останете доволни от ефективността на работата на слоя с бази данни. Примерът по-горе прави постоянен брой заявки, които изглеждат по следния начин -```sql -SELECT * FROM `book` -SELECT * FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT * FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) -``` +Екраниране и идентификатори +--------------------------- -Ако използвате [кеша |caching:] (разрешен по подразбиране), няма да се правят ненужни заявки за колони. След първата заявка имената на използваните колони ще се съхранят в кеша и Nette Database Explorer ще изпълнява само заявки с необходимите колони: +Методите автоматично екранират параметрите и ограждат идентификаторите (имената на таблици и колони) с кавички, като по този начин предотвратяват SQL injection. За правилното функциониране е необходимо да се спазват няколко правила: -```sql -SELECT `id`, `title`, `author_id` FROM `book` -SELECT `id`, `name` FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT `book_id`, `tag_id` FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT `id`, `name` FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) +- Ключовите думи, имената на функции, процедури и т.н. пишете с **главни букви**. +- Имената на колони и таблици пишете с **малки букви**. +- Низовете винаги вмъквайте чрез **параметри**. + +```php +where('name = ' . $name); // КРИТИЧНА УЯЗВИМОСТ: SQL injection +where('name LIKE "%search%"'); // ГРЕШНО: усложнява автоматичното ограждане с кавички +where('name LIKE ?', '%search%'); // ПРАВИЛНО: стойност, вмъкната чрез параметър + +where('name like ?', $name); // ГРЕШНО: генерира: `name` `like` ? +where('name LIKE ?', $name); // ПРАВИЛНО: генерира: `name` LIKE ? +where('LOWER(name) = ?', $value);// ПРАВИЛНО: LOWER(`name`) = ? ``` -Избори .[#toc-selections] -========================= +where(string|array $condition, ...$parameters): static .[method] +---------------------------------------------------------------- -Вижте опции за филтриране и ограничения на низове [api:Nette\Database\Table\Selection]: +Филтрира резултатите с помощта на условия WHERE. Силната му страна е интелигентната работа с различни типове стойности и автоматичният избор на SQL оператори. -.[language-php] -| `$table->where($where[, $param[, ...]])` | Задайте WHERE, като използвате AND като свързващо звено, ако са зададени две или повече условия -| `$table->whereOr($where)` | Задайте WHERE, като използвате OR като свързващо звено, ако са зададени две или повече условия -| `$table->order($columns)` | Задайте ORDER BY, например като използвате израза `('column DESC, id DESC')`. -| `$table->select($columns)` | Задайте извлечените колони, например с помощта на израза `('col, MD5(col) AS hash')`. -| `$table->limit($limit[, $offset])` | Задаване на LIMIT и OFFSET -| `$table->page($page, $itemsPerPage[, &$lastPage])` | включване на странирането. -| `$table->group($columns)` | set GROUP BY -| `$table->having($having)` | set HAVING +Основно използване: -Можете да използвате Fluent, например `$table->where(...)->order(...)->limit(...)`. Няколко условия `where` или `whereOr` се свързват с помощта на оператора `AND`. +```php +$table->where('id', $value); // WHERE `id` = 123 +$table->where('id > ?', $value); // WHERE `id` > 123 +$table->where('id = ? OR name = ?', $id, $name); // WHERE `id` = 1 OR `name` = 'Jon Snow' +``` +Благодарение на автоматичното откриване на подходящи оператори не е необходимо да се занимаваме с различни специални случаи. Nette ги решава за нас: -където() .[#toc-where] ----------------------- +```php +$table->where('id', 1); // WHERE `id` = 1 +$table->where('id', null); // WHERE `id` IS NULL +$table->where('id', [1, 2, 3]); // WHERE `id` IN (1, 2, 3) +// може да се използва и заместващ въпросителен знак без оператор: +$table->where('id ?', 1); // WHERE `id` = 1 +``` -Nette Database Explorer може автоматично да добави необходимите оператори за предадените стойности: +Методът правилно обработва и отрицателни условия и празни масиви: -.[language-php] -| `$table->where('field', $value)` | поле = $стойност -| `$table->where('field', null)` | полето е NULL -| `$table->where('field > ?', $val)` | поле > $стойност -| `$table->where('field', [1, 2])` | поле IN (1, 2) -| `$table->where('id = ? OR name = ?', 1, $name)` | id = 1 ИЛИ име = "Джон Сноу -| `$table->where('field', $explorer->table($tableName))` | field IN (SELECT $primary FROM $tableName) -| `$table->where('field', $explorer->table($tableName)->select('col'))` | field IN (SELECT col FROM $tableName) +```php +$table->where('id', []); // WHERE `id` IS NULL AND FALSE -- нищо не намира +$table->where('id NOT', []); // WHERE `id` IS NULL OR TRUE -- намира всичко +$table->where('NOT (id ?)', []); // WHERE NOT (`id` IS NULL AND FALSE) -- намира всичко +// $table->where('NOT id ?', $ids); Внимание - този синтаксис не се поддържа +``` -Можете да посочите заместител дори без оператора за колони. Тези обаждания са едни и същи. +Като параметър можем да предадем и резултат от друга таблица - създава се подзаявка: ```php -$table->where('id = ? OR id = ?', 1, 2); -$table->where('id ? OR id ?', 1, 2); +// WHERE `id` IN (SELECT `id` FROM `tableName`) +$table->where('id', $explorer->table($tableName)); + +// WHERE `id` IN (SELECT `col` FROM `tableName`) +$table->where('id', $explorer->table($tableName)->select('col')); ``` -Тази функция ви позволява да генерирате правилния оператор въз основа на стойността: +Условията можем да предадем и като масив, чиито елементи се свързват с AND: ```php -$table->where('id ?', 2); // id = 2 -$table->where('id ?', null); // id IS NULL -$table->where('id', $ids); // id IN (...) +// WHERE (`price_final` < `price_original`) AND (`stock_count` > `min_stock`) +$table->where([ + 'price_final < price_original', + 'stock_count > min_stock', +]); ``` -Изборът обработва правилно и отрицателни условия и работи за празни масиви: +В масива можем да използваме двойки ключ => стойност и Nette отново автоматично избира правилните оператори: ```php -$table->where('id', []); // id IS NULL AND FALSE -$table->where('id NOT', []); // id IS NULL OR TRUE -$table->where('NOT (id ?)', $ids); // NOT (id IS NULL AND FALSE) +// WHERE (`status` = 'active') AND (`id` IN (1, 2, 3)) +$table->where([ + 'status' => 'active', + 'id' => [1, 2, 3], +]); +``` -// това ще доведе до изключение, този синтаксис не се поддържа -$table->where('NOT id ?', $ids); +В масива можем да комбинираме SQL изрази със заместващи въпросителни знаци и множество параметри. Това е подходящо за комплексни условия с точно дефинирани оператори: + +```php +// WHERE (`age` > 18) AND (ROUND(`score`, 2) > 75.5) +$table->where([ + 'age > ?' => 18, + 'ROUND(score, ?) > ?' => [2, 75.5], // два параметъра предаваме като масив +]); ``` +Многократното извикване на `where()` автоматично свързва условията с AND. -whereOr() .[#toc-whereor] -------------------------- -Пример за използване без параметри: +whereOr(array $parameters): static .[method] +-------------------------------------------- + +Подобно на `where()` добавя условия, но с тази разлика, че ги свързва с OR: ```php -// WHERE (user_id IS NULL) OR (SUM(`field1`) > SUM(`field2`)) +// WHERE (`status` = 'active') OR (`deleted` = 1) $table->whereOr([ - 'user_id IS NULL', - 'SUM(field1) > SUM(field2)', + 'status' => 'active', + 'deleted' => true, ]); ``` -Използваме параметри. Ако не посочите оператор, Nette Database Explorer автоматично ще добави подходящия оператор: +И тук можем да използваме по-комплексни изрази: ```php -// WHERE (`field1` IS NULL) OR (`field2` IN (3, 5)) OR (`amount` > 11) +// WHERE (`price` > 1000) OR (`price_with_tax` > 1500) $table->whereOr([ - 'field1' => null, - 'field2' => [3, 5], - 'amount >' => 11, + 'price > ?' => 1000, + 'price_with_tax > ?' => 1500, ]); ``` -Ключът може да съдържа израз, съдържащ заместващи символи, и след това да предаде параметрите в стойността: + +wherePrimary(mixed $key): static .[method] +------------------------------------------ + +Добавя условие за първичния ключ на таблицата: ```php -// WHERE (`id` > 12) OR (ROUND(`id`, 5) = 3) -$table->whereOr([ - 'id > ?' => 12, - 'ROUND(id, ?) = ?' => [5, 3], -]); +// WHERE `id` = 123 +$table->wherePrimary(123); + +// WHERE `id` IN (1, 2, 3) +$table->wherePrimary([1, 2, 3]); +``` + +Ако таблицата има композитен първичен ключ (напр. `foo_id`, `bar_id`), предаваме го като масив: + +```php +// WHERE `foo_id` = 1 AND `bar_id` = 5 +$table->wherePrimary(['foo_id' => 1, 'bar_id' => 5])->fetch(); + +// WHERE (`foo_id`, `bar_id`) IN ((1, 5), (2, 3)) +$table->wherePrimary([ + ['foo_id' => 1, 'bar_id' => 5], + ['foo_id' => 2, 'bar_id' => 3], +])->fetchAll(); ``` -поръчка() .[#toc-order] ------------------------ +order(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- -Примери за употреба: +Определя реда, в който ще бъдат върнати редовете. Можем да сортираме по една или повече колони, в низходящ или възходящ ред, или по собствен израз: ```php -$table->order('field1'); // ORDER BY `field1` -$table->order('field1 DESC, field2'); // ORDER BY `field1` DESC, `field2` -$table->order('field = ? DESC', 123); // ORDER BY `field` = 123 DESC +$table->order('created'); // ORDER BY `created` +$table->order('created DESC'); // ORDER BY `created` DESC +$table->order('priority DESC, created'); // ORDER BY `priority` DESC, `created` +$table->order('status = ? DESC', 'active'); // ORDER BY `status` = 'active' DESC ``` -изберете() .[#toc-select] -------------------------- +select(string $columns, ...$parameters): static .[method] +--------------------------------------------------------- -Примери за употреба: +Специфицира колоните, които трябва да бъдат върнати от базата данни. По подразбиране Nette Database Explorer връща само тези колони, които реално се използват в кода. Методът `select()` така използваме в случаите, когато трябва да върнем специфични изрази: ```php -$table->select('field1'); // SELECT `field1` -$table->select('col, UPPER(col) AS abc'); // SELECT `col`, UPPER(`col`) AS abc -$table->select('SUBSTR(title, ?)', 3); // SELECT SUBSTR(`title`, 3) +// SELECT *, DATE_FORMAT(`created_at`, "%d.%m.%Y") AS `formatted_date` +$table->select('*, DATE_FORMAT(created_at, ?) AS formatted_date', '%d.%m.%Y'); ``` +Псевдонимите, дефинирани с `AS`, след това са достъпни като свойства на обекта ActiveRow: -ограничение() .[#toc-limit] ---------------------------- +```php +foreach ($table as $row) { + echo $row->formatted_date; // достъп до псевдонима +} +``` -Примери за употреба: + +limit(?int $limit, ?int $offset = null): static .[method] +--------------------------------------------------------- + +Ограничава броя на върнатите редове (LIMIT) и опционално позволява да се зададе offset: ```php -$table->limit(1); // LIMIT 1 -$table->limit(1, 10); // LIMIT 1 OFFSET 10 +$table->limit(10); // LIMIT 10 (връща първите 10 реда) +$table->limit(10, 20); // LIMIT 10 OFFSET 20 ``` +За пагиниране е по-подходящо да се използва методът `page()`. + -страница() .[#toc-page] ------------------------ +page(int $page, int $itemsPerPage, &$numOfPages = null): static .[method] +------------------------------------------------------------------------- -Алтернативен начин за задаване на границата и отместването: +Улеснява пагинирането на резултатите. Приема номер на страницата (изчисляван от 1) и брой елементи на страница. Опционално може да се предаде референция към променлива, в която ще се съхрани общият брой страници: ```php -$page = 5; -$itemsPerPage = 10; -$table->page($page, $itemsPerPage); // LIMIT 10 OFFSET 40 +$numOfPages = null; +$table->page(page: 3, itemsPerPage: 10, $numOfPages); +echo "Общо страници: $numOfPages"; ``` -Получаване на последния номер на страница, предаден на променливата `$lastPage`: + +group(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- + +Групира редове според зададените колони (GROUP BY). Обикновено се използва във връзка с агрегатни функции: ```php -$table->page($page, $itemsPerPage, $lastPage); +// Преброява броя на продуктите във всяка категория +$table->select('category_id, COUNT(*) AS count') + ->group('category_id'); ``` -група() .[#toc-group] ---------------------- +having(string $having, ...$parameters): static .[method] +-------------------------------------------------------- -Примери за употреба: +Задава условие за филтриране на групирани редове (HAVING). Може да се използва във връзка с метода `group()` и агрегатни функции: ```php -$table->group('field1'); // GROUP BY `field1` -$table->group('field1, field2'); // GROUP BY `field1`, `field2` +// Намира категории, които имат повече от 100 продукта +$table->select('category_id, COUNT(*) AS count') + ->group('category_id') + ->having('count > ?', 100); ``` -като() .[#toc-having] ---------------------- +Четене на данни +=============== + +За четене на данни от базата данни имаме на разположение няколко полезни метода: -Примери за употреба: +.[language-php] +| `foreach ($table as $key => $row)` | Итерира през всички редове, `$key` е стойността на първичния ключ, `$row` е обект ActiveRow +| `$row = $table->get($key)` | Връща един ред според първичния ключ +| `$row = $table->fetch()` | Връща текущия ред и премества указателя към следващия +| `$array = $table->fetchPairs()` | Създава асоциативен масив от резултатите +| `$array = $table->fetchAll()` | Връща всички редове като масив +| `count($table)` | Връща броя на редовете в обекта Selection + +Обектът [ActiveRow |api:Nette\Database\Table\ActiveRow] е предназначен само за четене. Това означава, че не може да се променят стойностите на неговите свойства. Това ограничение гарантира консистенцията на данните и предотвратява неочаквани странични ефекти. Данните се зареждат от базата данни и всяка промяна трябва да бъде извършена изрично и контролирано. + + +`foreach` - итерация през всички редове +--------------------------------------- + +Най-лесният начин да изпълните заявка и да получите редове е чрез итерация в цикъл `foreach`. Автоматично стартира SQL заявка. ```php -$table->having('COUNT(items) >', 100); // HAVING COUNT(`items`) > 100 +$books = $explorer->table('book'); +foreach ($books as $key => $book) { + // $key е стойността на първичния ключ, $book е ActiveRow + echo "$book->title ({$book->author->name})"; +} ``` -Филтриране по стойност на друга таблица .[#toc-joining-key] ------------------------------------------------------------ +get($key): ?ActiveRow .[method] +------------------------------- + +Изпълнява SQL заявка и връща ред според първичния ключ, или `null`, ако не съществува. -Много често искате да филтрирате резултатите по някакво условие, което включва друга таблица в базата данни. Такива условия изискват обединяване на таблици. Въпреки това вече не е необходимо да ги пишете. +```php +$book = $explorer->table('book')->get(123); // връща ActiveRow с ID 123 или null +if ($book) { + echo $book->title; +} +``` -Например, да предположим, че искате да получите всички книги, чието име на автор е "Jon". Необходимо е да напишете само ключа за присъединяване на връзката и името на колоната в таблицата за присъединяване. Ключът за обединяване се взема от колоната, която се отнася до таблицата, която искате да обедините. В нашия пример (вж. схемата на db) това е колоната `author_id`, като е достатъчно да се използва само първата ѝ част - `author` (суфиксът `_id` може да се пропусне). `name` - е колоната в таблицата `author`, която искаме да използваме. Условието за преводач на книги (което е свързано с колоната `translator_id`) може да бъде създадено също толкова лесно. + +fetch(): ?ActiveRow .[method] +----------------------------- + +Връща ред и премества вътрешния указател към следващия. Ако вече не съществуват други редове, връща `null`. ```php $books = $explorer->table('book'); -$books->where('author.name LIKE ?', '%Jon%'); -$books->where('translator.name', 'David Grudl'); +while ($book = $books->fetch()) { + $this->processBook($book); +} ``` -Логиката на свързващите ключове се определя от прилагането на [Conventions |api:Nette\Database\Conventions]. Препоръчваме ви да използвате [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions], която анализира чуждите ключове и улеснява работата с тези връзки. -Връзката между една книга и нейния автор е 1:N. Възможна е и обратна зависимост. Наричаме това **обратна връзка**. Вижте друг пример. Искаме да привлечем всички автори, които са написали повече от 3 книги. За да направим връзката обратна, използваме `:` (двоеточие). Двоеточие означает, что объединенное отношение имеет значение hasMany (и это вполне логично, так как две точки больше, чем одна). К сожалению, класс Selection недостаточно умен, поэтому мы должны помочь с агрегацией и предоставить оператор `GROUP BY`, също така условието трябва да се запише като оператор `HAVING`. +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Връща резултатите като асоциативен масив. Първият аргумент определя името на колоната, която ще се използва като ключ в масива, вторият аргумент определя името на колоната, която ще се използва като стойност: ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book.id) > 3'); +$authors = $explorer->table('author')->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] ``` -Може би сте забелязали, че декларацията за присъединяване се отнася до книга, но не е ясно дали се присъединяваме чрез `author_id` или `translator_id`. В горния пример Selection се присъединява чрез колоната `author_id`, тъй като има съвпадение с оригиналната таблица, таблицата `author`. Ако няма такова съвпадение и има повече възможности, Nette ще хвърли [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. +Ако посочим само първия параметър, стойността ще бъде целият ред, т.е. обект `ActiveRow`: + +```php +$authors = $explorer->table('author')->fetchPairs('id'); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` -За да извършите сливането чрез колона `translator_id`, въведете незадължителен параметър в израза за сливане. +В случай на дублиращи се ключове се използва стойността от последния ред. При използване на `null` като ключ масивът ще бъде индексиран числово от нула (тогава не възникват колизии): ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book(translator).id) > 3'); +$authors = $explorer->table('author')->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] ``` -Нека разгледаме един по-сложен израз за обединяване. -Искаме да намерим всички автори, които са написали нещо за PHP. Всички книги имат тагове, така че трябва да изберем онези автори, които са написали някоя книга с таг PHP. +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Алтернативно можете като параметър да посочите callback, който за всеки ред ще връща или самата стойност, или двойка ключ-стойност. ```php -$authors = $explorer->table('author'); -$authors->where(':book:book_tags.tag.name', 'PHP') - ->group('author.id') - ->having('COUNT(:book:book_tags.tag.id) > 0'); +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => "$row->title ({$row->author->name})"); +// ['Първа книга (Ян Новак)', ...] + +// Callback може също да връща масив с двойка ключ & стойност: +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => [$row->title, $row->author->name]); +// ['Първа книга' => 'Ян Новак', ...] ``` -Обобщени заявки .[#toc-aggregate-queries] ------------------------------------------ +fetchAll(): array .[method] +--------------------------- -| `$table->count('*')` | получаване на брой редове -| `$table->count("DISTINCT $column")` | получаване на броя на отделните стойности -| `$table->min($column)` | получаване на минималната стойност -| `$table->max($column)` | получаване на максималната стойност -| `$table->sum($column)` | получаване на сумата от всички стойности -| `$table->aggregation("GROUP_CONCAT($column)")` | Изпълнение на всяка функция за агрегиране +Връща всички редове като асоциативен масив от обекти `ActiveRow`, където ключовете са стойностите на първичните ключове. -.[caution] -Методът `count()` без посочване на параметри избира всички записи и връща размера на масива, което е много неефективно. Например, ако трябва да изчислите броя на редовете за страниране, винаги посочвайте първия аргумент. +```php +$allBooks = $explorer->table('book')->fetchAll(); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` + + +count(): int .[method] +---------------------- + +Методът `count()` без параметър връща броя на редовете в обекта `Selection`: + +```php +$table->where('category', 1); +$count = $table->count(); +$count = count($table); // алтернатива +``` + +Внимание, `count()` с параметър извършва агрегатна функция COUNT в базата данни, вижте по-долу. + + +ActiveRow::toArray(): array .[method] +------------------------------------- + +Преобразува обект `ActiveRow` в асоциативен масив, където ключовете са имената на колоните, а стойностите са съответните данни. + +```php +$book = $explorer->table('book')->get(1); +$bookArray = $book->toArray(); +// $bookArray ще бъде ['id' => 1, 'title' => '...', 'author_id' => ..., ...] +``` + + +Агрегиране +========== + +Класът `Selection` предоставя методи за лесно извършване на агрегатни функции (COUNT, SUM, MIN, MAX, AVG и т.н.). + +.[language-php] +| `count($expr)` | Преброява броя на редовете +| `min($expr)` | Връща минималната стойност в колоната +| `max($expr)` | Връща максималната стойност в колоната +| `sum($expr)` | Връща сумата на стойностите в колоната +| `aggregation($function)` | Позволява да се извърши произволна агрегатна функция. Напр. `AVG()`, `GROUP_CONCAT()` + + +count(string $expr): int .[method] +---------------------------------- + +Изпълнява SQL заявка с функцията COUNT и връща резултата. Методът се използва за установяване колко реда отговарят на определено условие: + +```php +$count = $table->count('*'); // SELECT COUNT(*) FROM `table` +$count = $table->count('DISTINCT column'); // SELECT COUNT(DISTINCT `column`) FROM `table` +``` + +Внимание, [#count()] без параметър само връща броя на редовете в обекта `Selection`. + + +min(string $expr) и max(string $expr) .[method] +----------------------------------------------- + +Методите `min()` и `max()` връщат минималната и максималната стойност в специфицираната колона или израз: + +```php +// SELECT MAX(`price`) FROM `products` WHERE `active` = 1 +$maxPrice = $products->where('active', true) + ->max('price'); +``` -Стенография и цитати .[#toc-escaping-quoting] -============================================= +sum(string $expr) .[method] +--------------------------- + +Връща сумата на стойностите в специфицираната колона или израз: + +```php +// SELECT SUM(`price` * `items_in_stock`) FROM `products` WHERE `active` = 1 +$totalPrice = $products->where('active', true) + ->sum('price * items_in_stock'); +``` + -Database Explorer е интелигентен и ще премахне параметрите и идентификаторите на цитати вместо вас. Трябва обаче да се спазват следните основни правила: +aggregation(string $function, ?string $groupFunction = null) .[method] +---------------------------------------------------------------------- -- ключовите думи, функциите и процедурите трябва да са с главни букви -- колони и таблици с малки букви -- предавайте променливи като параметри, не ги конкатенирайте. +Позволява да се извърши произволна агрегатна функция. ```php -->where('name like ?', 'John'); // НЕПРАВИЛЬНО! Генерирует: `name` `like` ? -->where('name LIKE ?', 'John'); // ПРАВИЛЬНО +// средна цена на продуктите в категория +$avgPrice = $products->where('category_id', 1) + ->aggregation('AVG(price)'); -->where('KEY = ?', $value); // НЕПРАВИЛЬНО! КЛЮЧ - это ключевое слово -->where('key = ?', $value); // ПРАВИЛЬНО. Генерирует: `key` = ? +// свързва етикетите на продукта в един низ +$tags = $products->where('id', 1) + ->aggregation('GROUP_CONCAT(tag.name) AS tags') + ->fetch() + ->tags; +``` -->where('name = ' . $name); // Неправильно! sql-инъекция! -->where('name = ?', $name); // ПРАВИЛЬНО +Ако трябва да агрегираме резултати, които вече сами по себе си са произлезли от някаква агрегатна функция и групиране (напр. `SUM(стойност)` върху групирани редове), като втори аргумент посочваме агрегатната функция, която трябва да се приложи върху тези междинни резултати: -->select('DATE_FORMAT(created, "%d.%m.%Y")'); // НЕПРАВИЛЬНО! Передавайте переменные как параметры, не конкатенируйте -->select('DATE_FORMAT(created, ?)', '%d.%m.%Y'); // ПРАВИЛЬНО +```php +// Изчислява общата цена на продуктите на склад за отделните категории и след това сумира тези цени заедно. +$totalPrice = $products->select('category_id, SUM(price * stock) AS category_total') + ->group('category_id') + ->aggregation('SUM(category_total)', 'SUM'); ``` -.[warning] -Неправилната употреба може да доведе до пропуски в сигурността +В този пример първо изчисляваме общата цена на продуктите във всяка категория (`SUM(price * stock) AS category_total`) и групираме резултатите по `category_id`. След това използваме `aggregation('SUM(category_total)', 'SUM')` за сумиране на тези междинни суми `category_total`. Вторият аргумент `'SUM'` казва, че върху междинните резултати трябва да се приложи функцията SUM. -Извличане на данни .[#toc-fetching-data] -======================================== +Insert, Update & Delete +======================= -| `foreach ($table as $id => $row)` | итерация над всички редове с резултати -| `$row = $table->get($id)` | извличане на един низ с id $id от таблица -| `$row = $table->fetch()` | извличане на следващия низ от резултата -| `$array = $table->fetchPairs($key, $value)` | Изберете всички стойности като асоциативен масив -| `$array = $table->fetchPairs($key)` | получаване на всички записи в асоциативен масив -| `count($table)` | Получаване на броя на низовете в набора от резултати +Nette Database Explorer опростява вмъкването, актуализирането и изтриването на данни. Всички посочени методи в случай на грешка изхвърлят изключение `Nette\Database\DriverException`. -Вмъкване, актуализиране и изтриване .[#toc-insert-update-delete] -================================================================ +Selection::insert(iterable $data) .[method] +------------------------------------------- -Методът `insert()` приема масив от обекти Traversable (напр. [ArrayHash |utils:arrays#ArrayHash], който връща [форми |forms:]): +Вмъква нови записи в таблицата. + +**Вмъкване на един запис:** + +Новият запис предаваме като асоциативен масив или iterable обект (например ArrayHash, използван във [формите |forms:]), където ключовете отговарят на имената на колоните в таблицата. + +Ако таблицата има дефиниран първичен ключ, методът връща обект `ActiveRow`, който се презарежда от базата данни, за да се отразят евентуалните промени, извършени на ниво база данни (тригери, стойности по подразбиране на колони, изчисления на auto-increment колони). По този начин се гарантира консистенцията на данните и обектът винаги съдържа актуалните данни от базата данни. Ако няма еднозначен първичен ключ, връща предадените данни под формата на масив. ```php $row = $explorer->table('users')->insert([ - 'name' => $name, - 'year' => $year, + 'name' => 'John Doe', + 'email' => 'john.doe@example.com', ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) +// $row е инстанция на ActiveRow и съдържа пълните данни на вмъкнатия ред, +// включително автоматично генерираното ID и евентуалните промени, извършени от тригери +echo $row->id; // Извежда ID на нововмъкнатия потребител +echo $row->created_at; // Извежда времето на създаване, ако е зададено от тригер ``` -Ако за таблицата е дефиниран първичен ключ, се връща обект ActiveRow, съдържащ вмъкнатия ред. +**Вмъкване на няколко записа едновременно:** -Вмъкване на няколко стойности: +Методът `insert()` позволява да се вмъкнат няколко записа с една SQL заявка. В този случай връща броя на вмъкнатите редове. ```php -$explorer->table('users')->insert([ +$insertedRows = $explorer->table('users')->insert([ + [ + 'name' => 'John', + 'year' => 1994, + ], [ - 'name' => 'Jim', - 'year' => 1978, - ], [ 'name' => 'Jack', - 'year' => 1987, + 'year' => 1995, ], ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) +// INSERT INTO `users` (`name`, `year`) VALUES ('John', 1994), ('Jack', 1995) +// $insertedRows ще бъде 2 ``` -Като параметри могат да се предават файлове или обекти DateTime: +Като параметър може също да се предаде обект `Selection` с избор на данни. + +```php +$newUsers = $explorer->table('potential_users') + ->where('approved', 1) + ->select('name, email'); + +$insertedRows = $explorer->table('users')->insert($newUsers); +``` + +**Вмъкване на специални стойности:** + +Като стойности можем да предаваме и файлове, обекти DateTime или SQL литерали: ```php $explorer->table('users')->insert([ - 'name' => $name, - 'created' => new DateTime, // или $explorer::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // вмъква файл + 'name' => 'John', + 'created_at' => new DateTime, // преобразува в база данни формат + 'avatar' => fopen('image.jpg', 'rb'), // вмъква бинарно съдържание на файла + 'uuid' => $explorer::literal('UUID()'), // извиква функцията UUID() ]); ``` -Актуализиране (връща броя на засегнатите редове): + +Selection::update(iterable $data): int .[method] +------------------------------------------------ + +Актуализира редове в таблицата според зададения филтър. Връща броя на действително променените редове. + +Променяните колони предаваме като асоциативен масив или iterable обект (например ArrayHash, използван във [формите |forms:]), където ключовете отговарят на имената на колоните в таблицата: ```php -$count = $explorer->table('users') - ->where('id', 10) // трябва да се извика преди update() +$affected = $explorer->table('users') + ->where('id', 10) ->update([ - 'name' => 'Ned Stark' + 'name' => 'John Smith', + 'year' => 1994, ]); -// UPDATE `users` SET `name`='Ned Stark' WHERE (`id` = 10) +// UPDATE `users` SET `name` = 'John Smith', `year` = 1994 WHERE `id` = 10 ``` -Можем да използваме операторите `+=` и `-=`, за да актуализираме: +За промяна на числови стойности можем да използваме операторите `+=` и `-=`: ```php $explorer->table('users') + ->where('id', 10) ->update([ - 'age+=' => 1, // see += + 'points+=' => 1, // увеличава стойността на колоната 'points' с 1 + 'coins-=' => 1, // намалява стойността на колоната 'coins' с 1 ]); -// UPDATE users SET `age` = `age` + 1 +// UPDATE `users` SET `points` = `points` + 1, `coins` = `coins` - 1 WHERE `id` = 10 ``` -Delete (връща броя на изтритите редове): + +Selection::delete(): int .[method] +---------------------------------- + +Изтрива редове от таблицата според зададения филтър. Връща броя на изтритите редове. ```php $count = $explorer->table('users') ->where('id', 10) ->delete(); -// DELETE FROM `users` WHERE (`id` = 10) +// DELETE FROM `users` WHERE `id` = 10 ``` +.[caution] +При извикване на `update()` и `delete()` не забравяйте с помощта на `where()` да специфицирате редовете, които трябва да се променят/изтрият. Ако `where()` не използвате, операцията ще се извърши върху цялата таблица! + -Работа с взаимоотношения .[#toc-working-with-relationships] -=========================================================== +ActiveRow::update(iterable $data): bool .[method] +------------------------------------------------- +Актуализира данни в реда на базата данни, представен от обекта `ActiveRow`. Като параметър приема iterable с данни, които трябва да се актуализират (ключовете са имената на колоните). За промяна на числови стойности можем да използваме операторите `+=` и `-=`: -Един към един ("има един") .[#toc-has-one-relation] ---------------------------------------------------- -Връзката "Един към един" е често срещан случай на употреба. Една книга има един автор. Книгата има един* преводач. Получаването на свързания низ се извършва главно чрез метода `ref()`. Тя приема два аргумента: името на целевата таблица и колоната на изходното присъединяване. Вижте пример: +След извършване на актуализацията `ActiveRow` автоматично се презарежда от базата данни, за да се отразят евентуалните промени, извършени на ниво база данни (напр. тригери). Методът връща `true` само ако е настъпила действителна промяна на данните. ```php -$book = $explorer->table('book')->get(1); -$book->ref('author', 'author_id'); +$article = $explorer->table('article')->get(1); +$article->update([ + 'views += 1', // увеличаваме броя на показванията +]); +echo $article->views; // Извежда текущия брой показвания ``` -В горния пример извличаме свързания запис на автор от таблицата `author`, търсим първичния ключ на автора чрез колоната `book.author_id`. Методът Ref() връща екземпляр на ActiveRow или null, ако няма съответстващ запис. Върнатият ред е екземпляр на ActiveRow, така че можем да работим с него по същия начин, както със запис в книга. +Този метод актуализира само един конкретен ред в базата данни. За масова актуализация на повече редове използвайте метода [#Selection::update()]. + + +ActiveRow::delete() .[method] +----------------------------- + +Изтрива реда от базата данни, който е представен от обекта `ActiveRow`. ```php -$author = $book->ref('author', 'author_id'); -$author->name; -$author->born; +$book = $explorer->table('book')->get(1); +$book->delete(); // Изтрива книга с ID 1 +``` + +Този метод изтрива само един конкретен ред в базата данни. За масово изтриване на повече редове използвайте метода [#Selection::delete()]. + + +Релации между таблици +===================== + +В релационните бази данни данните са разделени на няколко таблици и са взаимно свързани с помощта на външни ключове. Nette Database Explorer предлага революционен начин за работа с тези релации - без писане на JOIN заявки и без необходимост от каквото и да е конфигуриране или генериране. + +За илюстрация на работата с релации ще използваме примерна база данни с книги ([ще я намерите в GitHub |https://github.com/nette-examples/books]). В базата данни имаме таблици: + +- `author` - писатели и преводачи (колони `id`, `name`, `web`, `born`) +- `book` - книги (колони `id`, `author_id`, `translator_id`, `title`, `sequel_id`) +- `tag` - етикети (колони `id`, `name`) +- `book_tag` - свързваща таблица между книги и етикети (колони `book_id`, `tag_id`) + +[* db-schema-1-.webp *] *** Структура на базата данни, използвана в примерите + +В нашия пример с база данни за книги намираме няколко типа връзки (въпреки че моделът е опростен спрямо реалността): + +- Едно към много (1:N) – всяка книга **има един** автор, авторът може да напише **няколко** книги. +- Нула към много (0:N) – книгата **може да има** преводач, преводачът може да преведе **няколко** книги. +- Нула към едно (0:1) – книгата **може да има** следващ том. +- Много към много (M:N) – книгата **може да има няколко** етикета и етикетът може да бъде присвоен на **няколко** книги. -// или директно -$book->ref('author', 'author_id')->name; -$book->ref('author', 'author_id')->born; +В тези връзки винаги съществува родителска и дъщерна таблица. Например във връзката между автор и книга таблицата `author` е родителска, а `book` е дъщерна - можем да си го представим така, че книгата винаги "принадлежи" на някой автор. Това се проявява и в структурата на базата данни: дъщерната таблица `book` съдържа външен ключ `author_id`, който сочи към родителската таблица `author`. + +Ако трябва да изведем книгите, включително имената на техните автори, имаме две възможности. Или да получим данните с една SQL заявка с помощта на JOIN: + +```sql +SELECT book.*, author.name FROM book LEFT JOIN author ON book.author_id = author.id +``` + +Или да заредим данните на две стъпки - първо книгите, а след това техните автори - и след това да ги съберем в PHP: + +```sql +SELECT * FROM book; +SELECT * FROM author WHERE id IN (1, 2, 3); -- id на авторите на получените книги ``` -В книгата има и един интерпретатор, така че е доста лесно да се открие името на интерпретатора. +Вторият подход всъщност е по-ефективен, въпреки че това може да е изненадващо. Данните се зареждат само веднъж и могат да бъдат по-добре използвани в кеша. Точно по този начин работи Nette Database Explorer - всичко решава под повърхността и ви предлага елегантно API: + ```php -$book->ref('author', 'translator_id')->name +$books = $explorer->table('book'); +foreach ($books as $book) { + echo 'заглавие: ' . $book->title; + echo 'написано от: ' . $book->author->name; // $book->author е запис от таблица 'author' + echo 'преведено от: ' . $book->translator?->name; +} ``` -Всичко това е добре, но е малко тромаво, не мислите ли? Database Explorer вече съдържа дефиниции на чужди ключове, така че защо да не ги използвате автоматично? Да го направим! -Ако извикаме свойство, което не съществува, ActiveRow се опитва да разреши името на извикващото свойство като връзката "има такъв". Извличането на това свойство е подобно на извикването на метода ref() само с един аргумент. Ще наричаме единичния аргумент **ключ**. Ключът ще бъде преобразуван в конкретна релация с външен ключ. Предаденият ключ се съпоставя с колоните на реда и ако съвпадне, чуждият ключ, дефиниран в съпоставената колона, се използва за извличане на данни от свързаната целева таблица. Вижте пример: +Достъп до родителска таблица +---------------------------- + +Достъпът до родителската таблица е пряк. Става въпрос за връзки като *книгата има автор* или *книгата може да има преводач*. Свързаният запис получаваме чрез свойството на обекта ActiveRow - неговото име отговаря на името на колоната с външния ключ без суфикса `_id`: ```php -$book->author->name; -// същото -$book->ref('author')->name; +$book = $explorer->table('book')->get(1); +echo $book->author->name; // намира автора според колоната author_id +echo $book->translator?->name; // намира преводача според translator_id ``` -Екземплярът на ActiveRow няма колона за автор. Всички колони в книгата се претърсват за съответствие с *ключ*. Съвпадение в този случай означава, че името на колоната трябва да съдържа ключ. Така че в примера по-горе колоната `author_id` съдържа символа "author" и следователно се съпоставя с ключа "author". Ако искате да получите преводача на книгата, можете да използвате например "translator" като ключ, тъй като ключът "translator" ще съответства на колоната `translator_id`. Можете да прочетете повече за логиката на съпоставяне на ключовете в глава [Съединяване на изрази |#joining-key]. +Когато достъпим свойството `$book->author`, Explorer в таблицата `book` търси колона, чието име съдържа низа `author` (т.е. `author_id`). Според стойността в тази колона зарежда съответния запис от таблицата `author` и го връща като `ActiveRow`. Подобно работи и `$book->translator`, който използва колоната `translator_id`. Тъй като колоната `translator_id` може да съдържа `null`, използваме в кода оператора `?->`. + +Алтернативен път предлага методът `ref()`, който приема два аргумента, името на целевата таблица и името на свързващата колона, и връща инстанция на `ActiveRow` или `null`: ```php -echo $book->title . ': '; -echo $book->author->name; -if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; -} +echo $book->ref('author', 'author_id')->name; // връзка към автора +echo $book->ref('author', 'translator_id')->name; // връзка към преводача ``` -Ако искате да изтеглите няколко книги, използвайте същия подход. С помощта на Nette Database Explorer можете да намерите автори и преводачи за всички намерени книги едновременно. +Методът `ref()` е подходящ, ако не може да се използва достъп чрез свойство, тъй като таблицата съдържа колона със същото име (т.е. `author`). В останалите случаи се препоръчва използването на достъп чрез свойство, който е по-четлив. + +Explorer автоматично оптимизира заявките към базата данни. Когато преминаваме през книгите в цикъл и достъпваме техните свързани записи (автори, преводачи), Explorer не генерира заявка за всяка книга поотделно. Вместо това изпълнява само една SELECT заявка за всеки тип връзка, като по този начин значително намалява натоварването на базата данни. Например: ```php $books = $explorer->table('book'); foreach ($books as $book) { echo $book->title . ': '; echo $book->author->name; - if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; - } + echo $book->translator?->name; } ``` -Кодът ще изпълнява само тези 3 заявки: +Този код ще извика само тези три светкавични заявки към базата данни: + ```sql SELECT * FROM `book`; -SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- ids of fetched books from author_id column -SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- ids of fetched books from translator_id column +SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- id от колоната author_id на избраните книги +SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- id от колоната translator_id на избраните книги ``` +.[note] +Логиката за намиране на свързващата колона е дадена от имплементацията на [Conventions |api:Nette\Database\Conventions]. Препоръчваме използването на [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions], които анализират външните ключове и позволяват лесно да се работи със съществуващите връзки между таблиците. -От един към много ("има много") .[#toc-has-many-relation] ---------------------------------------------------------- -Отношението "един към много" е просто обратното на отношението "един към един". Авторът *написал* *много* книги. Авторът е превел *много* книги. Както виждате, този тип релация е малко по-сложна, тъй като връзката е "поименна" ("написано", "преведено"). Инстанцията ActiveRow има метод `related()`, който връща масив от свързани записи. Записите също са екземпляри на ActiveRow. Вижте примера по-долу: +Достъп до дъщерна таблица +------------------------- + +Достъпът до дъщерната таблица работи в обратна посока. Сега питаме *какви книги е написал този автор* или *превел този преводач*. За този тип заявка използваме метода `related()`, който връща `Selection` със свързаните записи. Нека разгледаме пример: ```php -$author = $explorer->table('author')->get(11); -echo $author->name . ' написал:'; +$author = $explorer->table('author')->get(1); +// Извежда всички книги от автора foreach ($author->related('book.author_id') as $book) { - echo $book->title; + echo "Написал: $book->title"; } -echo 'и перевёл:'; +// Извежда всички книги, които авторът е превел foreach ($author->related('book.translator_id') as $book) { - echo $book->title; + echo "Превел: $book->title"; } ``` -Методът `related()` приема пълно описание на връзката, предадено като два аргумента или като един аргумент, свързан с точка. Първият аргумент е целевата таблица, а вторият - целевата колона. +Методът `related()` приема описанието на връзката като един аргумент с точкова нотация или като два отделни аргумента: ```php -$author->related('book.translator_id'); -// то же самое -$author->related('book', 'translator_id'); +$author->related('book.translator_id'); // един аргумент +$author->related('book', 'translator_id'); // два аргумента ``` -Можете да използвате евристиката на Nette Database Explorer, базирана на чужди ключове, и да посочите само аргумента **ключ**. Ключът ще бъде съпоставен с всички чужди ключове, сочещи към текущата таблица (таблица `author`). Ако има съвпадение, Nette Database Explorer ще използва този външен ключ, в противен случай ще хвърли [Nette\InvalidArgumentException |api:Nette\InvalidArgumentException] или [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. Можете да прочетете повече за логиката на съпоставяне на ключовете в глава [Съединяване на изрази |#joining-key]. +Explorer може автоматично да открие правилната свързваща колона въз основа на името на родителската таблица. В този случай се свързва чрез колоната `book.author_id`, тъй като името на изходната таблица е `author`: -Разбира се, можете да извикате свързаните методи за всички намерени автори и Nette Database Explorer ще извлече отново съответните книги наведнъж. +```php +$author->related('book'); // използва book.author_id +``` + +Ако съществуват няколко възможни връзки, Explorer ще изхвърли изключение [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. + +Методът `related()` можем, разбира се, да използваме и при преминаване през повече записи в цикъл и Explorer и в този случай автоматично оптимизира заявките: ```php $authors = $explorer->table('author'); foreach ($authors as $author) { echo $author->name . ' написал:'; foreach ($author->related('book') as $book) { - $book->title; + echo $book->title; } } ``` -В горния пример ще бъдат извършени само две заявки: +Този код ще генерира само две светкавични SQL заявки: ```sql SELECT * FROM `author`; -SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- идентификаторы найденных авторов +SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- id на избраните автори ``` -Ръчно създаване на Explorer .[#toc-creating-explorer-manually] -============================================================== +Връзка Много към много +---------------------- -Връзката с база данни може да бъде създадена чрез конфигурацията на приложението. В такива случаи се създава услуга `Nette\Database\Explorer`, която може да бъде предадена като зависимост с помощта на DI-контейнера. +За връзка много към много (M:N) е необходимо съществуването на свързваща таблица (в нашия случай `book_tag`), която съдържа две колони с външни ключове (`book_id`, `tag_id`). Всяка от тези колони сочи към първичния ключ на една от свързваните таблици. За получаване на свързаните данни първо получаваме записите от свързващата таблица с помощта на `related('book_tag')` и след това продължаваме към целевите данни: -Ако обаче Nette Database Explorer се използва като самостоятелен инструмент, трябва ръчно да се създаде екземпляр на обекта `Nette\Database\Explorer`. +```php +$book = $explorer->table('book')->get(1); +// извежда имената на етикетите, присвоени към книгата +foreach ($book->related('book_tag') as $bookTag) { + echo $bookTag->tag->name; // извежда името на етикета през свързващата таблица +} + +$tag = $explorer->table('tag')->get(1); +// или обратно: извежда имената на книгите, означени с този етикет +foreach ($tag->related('book_tag') as $bookTag) { + echo $bookTag->book->title; // извежда името на книгата +} +``` + +Explorer отново оптимизира SQL заявките до ефективна форма: + +```sql +SELECT * FROM `book`; +SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 2, ...)); -- id на избраните книги +SELECT * FROM `tag` WHERE (`tag`.`id` IN (1, 2, ...)); -- id на етикетите, намерени в book_tag +``` + + +Заявки през свързани таблици +---------------------------- + +В методите `where()`, `select()`, `order()` и `group()` можем да използваме специални нотации за достъп до колони от други таблици. Explorer автоматично създава необходимите JOIN-ове. + +**Точкова нотация** (`родителска_таблица.колона`) се използва за връзка 1:N от гледна точка на дъщерната таблица: ```php -// $storage имплементира Nette\Caching\Storage: -$storage = new Nette\Caching\Storages\FileStorage($tempDir); -$connection = new Nette\Database\Connection($dsn, $user, $password); -$structure = new Nette\Database\Structure($connection, $storage); -$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); -$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +$books = $explorer->table('book'); + +// Намира книги, чийто автор има име, започващо с 'Jon' +$books->where('author.name LIKE ?', 'Jon%'); + +// Сортира книгите по името на автора низходящо +$books->order('author.name DESC'); + +// Извежда името на книгата и името на автора +$books->select('book.title, author.name'); +``` + +**Нотация с двоеточие** (`:дъщерна_таблица.колона`) се използва за връзка 1:N от гледна точка на родителската таблица: + +```php +$authors = $explorer->table('author'); + +// Намира автори, които са написали книга с 'PHP' в заглавието +$authors->where(':book.title LIKE ?', '%PHP%'); + +// Преброява броя на книгите за всеки автор +$authors->select('*, COUNT(:book.id) AS book_count') + ->group('author.id'); +``` + +В горепосочения пример с нотация с двоеточие (`:book.title`) не е специфицирана колоната с външния ключ. Explorer автоматично открива правилната колона въз основа на името на родителската таблица. В този случай се свързва чрез колоната `book.author_id`, тъй като името на изходната таблица е `author`. Ако съществуват няколко възможни връзки, Explorer ще изхвърли изключение [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. + +Свързващата колона може да бъде изрично посочена в скоби: + +```php +// Намира автори, които са превели книга с 'PHP' в заглавието +$authors->where(':book(translator_id).title LIKE ?', '%PHP%'); +``` + +Нотациите могат да се навързват за достъп през няколко таблици: + +```php +// Намира автори на книги, означени с етикета 'PHP' +$authors->where(':book:book_tag.tag.name', 'PHP') + ->group('author.id'); ``` + + +Разширяване на условията за JOIN +-------------------------------- + +Методът `joinWhere()` разширява условията, които се посочват при свързване на таблици в SQL след ключовата дума `ON`. + +Да предположим, че искаме да намерим книги, преведени от конкретен преводач: + +```php +// Намира книги, преведени от преводач на име 'David' +$books = $explorer->table('book') + ->joinWhere('translator', 'translator.name', 'David'); +// LEFT JOIN author translator ON book.translator_id = translator.id AND (translator.name = 'David') +``` + +В условието `joinWhere()` можем да използваме същите конструкции като в метода `where()` - оператори, заместващи въпросителни знаци, масиви от стойности или SQL изрази. + +За по-сложни заявки с повече JOIN-ове можем да дефинираме псевдоними на таблици: + +```php +$tags = $explorer->table('tag') + ->joinWhere(':book_tag.book.author', 'book_author.born < ?', 1950) + ->alias(':book_tag.book.author', 'book_author'); +// LEFT JOIN `book_tag` ON `tag`.`id` = `book_tag`.`tag_id` +// LEFT JOIN `book` ON `book_tag`.`book_id` = `book`.`id` +// LEFT JOIN `author` `book_author` ON `book`.`author_id` = `book_author`.`id` +// AND (`book_author`.`born` < 1950) +``` + +Обърнете внимание, че докато методът `where()` добавя условия към клаузата `WHERE`, методът `joinWhere()` разширява условията в клаузата `ON` при свързване на таблици. diff --git a/database/bg/guide.texy b/database/bg/guide.texy new file mode 100644 index 0000000000..e4775b3ff2 --- /dev/null +++ b/database/bg/guide.texy @@ -0,0 +1,216 @@ +Nette Database +************** + +.[perex] +Nette Database е мощно и елегантно ниво за работа с бази данни за PHP с акцент върху простотата и интелигентните функции. Предлага два начина за работа с базата данни - [Explorer |Explorer] за бърза разработка на приложения или [SQL достъп |SQL way] за директна работа със заявки. + +<div class="grid gap-3"> +<div> + + +[SQL достъп |SQL way] +===================== +- Безопасни параметризирани заявки +- Прецизен контрол върху формата на SQL заявките +- Когато пишете сложни заявки с разширени функции +- Оптимизирате производителността с помощта на специфични SQL функции + +</div> + +<div> + + +[Explorer |Explorer] +==================== +- Разработвате бързо без писане на SQL +- Интуитивна работа с релациите между таблиците +- Ще оцените автоматичната оптимизация на заявките +- Подходящо за бърза и удобна работа с базата данни + +</div> + +</div> + + +Инсталация +========== + +Можете да изтеглите и инсталирате библиотеката с помощта на инструмента [Composer|best-practices:composer]: + +```shell +composer require nette/database +``` + + +Поддържани бази данни +===================== + +Nette Database поддържа следните бази данни: + +|* Сървър на база данни |* DSN име |* Поддръжка в Explorer +|---------------------|-------------|----------------------- +| MySQL (>= 5.1) | mysql | ДА +| PostgreSQL (>= 9.0) | pgsql | ДА +| Sqlite 3 (>= 3.8) | sqlite | ДА +| Oracle | oci | - +| MS SQL (PDO_SQLSRV) | sqlsrv | ДА +| MS SQL (PDO_DBLIB) | mssql | - +| ODBC | odbc | - + + +Два подхода към базата данни +============================ + +Nette Database ви дава избор: можете или да пишете SQL заявки директно (SQL достъп), или да ги оставите да се генерират автоматично (Explorer). Нека видим как двата подхода решават едни и същи задачи: + +[SQL достъп|sql way] - SQL заявки + +```php +// вмъкване на запис +$database->query('INSERT INTO books', [ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// получаване на записи: автори на книги +$result = $database->query(' + SELECT authors.*, COUNT(books.id) AS books_count + FROM authors + LEFT JOIN books ON authors.id = books.author_id + WHERE authors.active = 1 + GROUP BY authors.id +'); + +// изход (не е оптимален, генерира N допълнителни заявки) +foreach ($result as $author) { + $books = $database->query(' + SELECT * FROM books + WHERE author_id = ? + ORDER BY published_at DESC + ', $author->id); + + echo "Автор $author->name е написал $author->books_count книги:\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +[Explorer достъп|explorer] - автоматично генериране на SQL + +```php +// вмъкване на запис +$database->table('books')->insert([ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// получаване на записи: автори на книги +$authors = $database->table('authors') + ->where('active', 1); + +// изход (автоматично генерира само 2 оптимизирани заявки) +foreach ($authors as $author) { + $books = $author->related('books') + ->order('published_at DESC'); + + echo "Автор $author->name е написал {$books->count()} книги:\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +Explorer достъпът генерира и оптимизира SQL заявките автоматично. В дадения пример SQL достъпът ще генерира N+1 заявки (една за авторите и след това по една за книгите на всеки автор), докато Explorer автоматично оптимизира заявките и изпълнява само две - една за авторите и една за всички техни книги. + +Двата подхода могат да се комбинират свободно в приложението според нуждите. + + +Свързване и конфигурация +======================== + +За да се свържете с базата данни, е достатъчно да създадете инстанция на класа [api:Nette\Database\Connection]: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password); +``` + +Параметърът `$dsn` (data source name) е същият, [както се използва от PDO |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], напр. `mysql:host=127.0.0.1;dbname=test`. В случай на неуспех, хвърля изключение `Nette\Database\ConnectionException`. + +Въпреки това, по-удобен начин предлага [конфигурацията на приложението |configuration], където е достатъчно да добавите секция `database` и ще се създадат необходимите обекти, както и панелът за база данни в лентата на [Tracy |tracy:] . + +```neon +database: + dsn: 'mysql:host=127.0.0.1;dbname=test' + user: root + password: password +``` + +След това [получаваме обекта на връзката като сървис от DI контейнера |dependency-injection:passing-dependencies], напр.: + +```php +class Model +{ + public function __construct( + // или Nette\Database\Explorer + private Nette\Database\Connection $database, + ) { + } +} +``` + +Повече информация за [конфигурацията на базата данни |configuration]. + + +Ръчно създаване на Explorer +--------------------------- + +Ако не използвате Nette DI контейнер, можете да създадете инстанция на `Nette\Database\Explorer` ръчно: + +```php +// свързване с базата данни +$connection = new Nette\Database\Connection('mysql:host=127.0.0.1;dbname=mydatabase', 'user', 'password'); +// хранилище за кеш, имплементира Nette\Caching\Storage, напр.: +$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp/dir'); +// грижи се за рефлексията на структурата на базата данни +$structure = new Nette\Database\Structure($connection, $storage); +// дефинира правила за мапиране на имената на таблици, колони и външни ключове +$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); +$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +``` + + +Управление на връзката +====================== + +При създаване на обект `Connection` автоматично се осъществява връзка. Ако искате да отложите връзката, използвайте lazy режим - можете да го включите в [конфигурацията |configuration], като зададете `lazy: true`, или по следния начин: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password, ['lazy' => true]); +``` + +За управление на връзката използвайте методите `connect()`, `disconnect()` и `reconnect()`. +- `connect()` създава връзка, ако все още не съществува, като може да хвърли изключение `Nette\Database\ConnectionException`. +- `disconnect()` прекъсва текущата връзка с базата данни. +- `reconnect()` извършва прекъсване и последващо повторно свързване с базата данни. Този метод също може да хвърли изключение `Nette\Database\ConnectionException`. + +Освен това можете да следите събитията, свързани с връзката, с помощта на събитието `onConnect`, което е масив от callback-ове, които се извикват след установяване на връзка с базата данни. + +```php +// изпълнява се след свързване с базата данни +$database->onConnect[] = function($database) { + echo "Свързано с базата данни"; +}; +``` + + +Tracy Debug Bar +=============== + +Ако използвате [Tracy |tracy:], автоматично се активира панелът Database в Debug лентата, който показва всички изпълнени заявки, техните параметри, времето за изпълнение и мястото в кода, където са били извикани. + +[* db-panel.webp *] diff --git a/database/bg/mapping.texy b/database/bg/mapping.texy new file mode 100644 index 0000000000..c56310b380 --- /dev/null +++ b/database/bg/mapping.texy @@ -0,0 +1,55 @@ +Преобразуване на типове +*********************** + +.[perex] +Nette Database автоматично преобразува стойностите, върнати от базата данни, в съответните PHP типове. + + +Дата и час +---------- + +Данните за време се преобразуват в обекти `Nette\Utils\DateTime`. Ако искате данните за време да се преобразуват в immutable обекти `Nette\Database\DateTime`, задайте опцията `newDateTime: true` в [конфигурацията |configuration]. + +```php +$row = $database->fetch('SELECT created_at FROM articles'); +echo $row->created_at instanceof DateTime; // true +echo $row->created_at->format('j. n. Y'); +``` + +В случай на MySQL, преобразува типа данни `TIME` в обекти `DateInterval`. + + +Булеви стойности +---------------- + +Булевите стойности автоматично се преобразуват в `true` или `false`. При MySQL се преобразува `TINYINT(1)`, ако зададем `convertBoolean: true` в [конфигурацията |configuration]. + +```php +$row = $database->fetch('SELECT is_published FROM articles'); +echo gettype($row->is_published); // 'boolean' +``` + + +Числови стойности +----------------- + +Числовите стойности се преобразуват в `int` или `float` според типа на колоната в базата данни: + +```php +$row = $database->fetch('SELECT id, price FROM products'); +echo gettype($row->id); // integer +echo gettype($row->price); // float +``` + + +Персонализирана нормализация +---------------------------- + +С помощта на метода `setRowNormalizer(?callable $normalizer)` можете да зададете персонализирана функция за трансформиране на редовете от базата данни. Това е полезно например за автоматично преобразуване на типове данни. + +```php +$database->setRowNormalizer(function(array $row, ResultSet $resultSet): array { + // тук се извършва преобразуването на типове + return $row; +}); +``` diff --git a/database/bg/reflection.texy b/database/bg/reflection.texy new file mode 100644 index 0000000000..496557ba0e --- /dev/null +++ b/database/bg/reflection.texy @@ -0,0 +1,125 @@ +Рефлексия на структурата +************************ + +.{data-version:3.2.1} +Nette Database предоставя инструменти за интроспекция на структурата на базата данни с помощта на класа [api:Nette\Database\Reflection]. Тя позволява получаване на информация за таблици, колони, индекси и външни ключове. Можете да използвате рефлексията за генериране на схеми, създаване на гъвкави приложения, работещи с база данни, или общи инструменти за бази данни. + +Получаваме обекта на рефлексията от инстанцията на връзката с базата данни: + +```php +$reflection = $database->getReflection(); +``` + + +Получаване на таблици +--------------------- + +Readonly свойството `$reflection->tables` съдържа асоциативен масив на всички таблици в базата данни: + +```php +// Извеждане на имената на всички таблици +foreach ($reflection->tables as $name => $table) { + echo $name . "\n"; +} +``` + +Налични са още два метода: + +```php +// Проверка за съществуване на таблица +if ($reflection->hasTable('users')) { + echo "Таблицата users съществува"; +} + +// Връща обект на таблицата; ако не съществува, хвърля изключение +$table = $reflection->getTable('users'); +``` + + +Информация за таблицата +----------------------- + +Таблицата е представена от обект [Table|api:Nette\Database\Reflection\Table], който предоставя следните readonly свойства: + +- `$name: string` – име на таблицата +- `$view: bool` – дали е изглед +- `$fullName: ?string` – пълно име на таблицата, включително схемата (ако съществува) +- `$columns: array<string, Column>` – асоциативен масив от колоните на таблицата +- `$indexes: Index[]` – масив от индексите на таблицата +- `$primaryKey: ?Index` – първичен ключ на таблицата или null +- `$foreignKeys: ForeignKey[]` – масив от външните ключове на таблицата + + +Колони +------ + +Свойството `columns` на таблицата предоставя асоциативен масив от колони, където ключът е името на колоната, а стойността е инстанция на [Column|api:Nette\Database\Reflection\Column] със следните свойства: + +- `$name: string` – име на колоната +- `$table: ?Table` – референция към таблицата на колоната +- `$nativeType: string` – нативен тип данни на базата данни +- `$size: ?int` – размер/дължина на типа +- `$nullable: bool` – дали колоната може да съдържа NULL +- `$default: mixed` – стойност по подразбиране на колоната +- `$autoIncrement: bool` – дали колоната е auto-increment +- `$primary: bool` – дали е част от първичния ключ +- `$vendor: array` – допълнителни метаданни, специфични за дадената система за бази данни + +```php +foreach ($table->columns as $name => $column) { + echo "Колона: $name\n"; + echo "Тип: {$column->nativeType}\n"; + echo "Nullable: " . ($column->nullable ? 'Да' : 'Не') . "\n"; +} +``` + + +Индекси +------- + +Свойството `indexes` на таблицата предоставя масив от индекси, където всеки индекс е инстанция на [Index|api:Nette\Database\Reflection\Index] със следните свойства: + +- `$columns: Column[]` – масив от колони, образуващи индекса +- `$unique: bool` – дали индексът е уникален +- `$primary: bool` – дали е първичен ключ +- `$name: ?string` – име на индекса + +Първичният ключ на таблицата може да бъде получен с помощта на свойството `primaryKey`, което връща или обект `Index`, или `null` в случай, че таблицата няма първичен ключ. + +```php +// Извеждане на индекси +foreach ($table->indexes as $index) { + $columns = implode(', ', array_map(fn($col) => $col->name, $index->columns)); + echo "Индекс" . ($index->name ? " {$index->name}" : '') . ":\n"; + echo " Колони: $columns\n"; + echo " Unique: " . ($index->unique ? 'Да' : 'Не') . "\n"; +} + +// Извеждане на първичния ключ +if ($primaryKey = $table->primaryKey) { + $columns = implode(', ', array_map(fn($col) => $col->name, $primaryKey->columns)); + echo "Първичен ключ: $columns\n"; +} +``` + + +Външни ключове +-------------- + +Свойството `foreignKeys` на таблицата предоставя масив от външни ключове, където всеки външен ключ е инстанция на [ForeignKey|api:Nette\Database\Reflection\ForeignKey] със следните свойства: + +- `$foreignTable: Table` – реферирана таблица +- `$localColumns: Column[]` – масив от локални колони +- `$foreignColumns: Column[]` – масив от реферирани колони +- `$name: ?string` – име на външния ключ + +```php +// Извеждане на външни ключове +foreach ($table->foreignKeys as $fk) { + $localCols = implode(', ', array_map(fn($col) => $col->name, $fk->localColumns)); + $foreignCols = implode(', ', array_map(fn($col) => $col->name, $fk->foreignColumns)); + + echo "FK" . ($fk->name ? " {$fk->name}" : '') . ":\n"; + echo " $localCols -> {$fk->foreignTable->name}($foreignCols)\n"; +} +``` diff --git a/database/bg/security.texy b/database/bg/security.texy new file mode 100644 index 0000000000..8cd001caa2 --- /dev/null +++ b/database/bg/security.texy @@ -0,0 +1,185 @@ +Рискове за сигурността +********************** + +<div class=perex> + +Базата данни често съдържа чувствителни данни и позволява извършването на опасни операции. За безопасна работа с Nette Database е ключово: + +- Да се разбира разликата между безопасно и опасно API +- Да се използват параметризирани заявки +- Да се валидират правилно входните данни + +</div> + + +Какво е SQL Injection? +====================== + +SQL инжекцията е най-сериозният риск за сигурността при работа с база данни. Възниква, когато необработен вход от потребител стане част от SQL заявка. Нападателят може да вмъкне собствени SQL команди и по този начин: +- Да получи неоторизиран достъп до данни +- Да модифицира или изтрие данни в базата данни +- Да заобиколи автентикацията + +```php +// ❌ ОПАСЕН КОД - уязвим към SQL инжекция +$database->query("SELECT * FROM users WHERE name = '$_GET[name]'"); + +// Нападателят може да въведе например стойност: ' OR '1'='1 +// Резултатната заявка ще бъде: SELECT * FROM users WHERE name = '' OR '1'='1' +// Което ще върне всички потребители +``` + +Същото се отнася и за Database Explorer: + +```php +// ❌ ОПАСЕН КОД - уязвим към SQL инжекция +$table->where('name = ' . $_GET['name']); +$table->where("name = '$_GET[name]'"); +``` + + +Параметризирани заявки +====================== + +Основната защита срещу SQL инжекция са параметризираните заявки. Nette Database предлага няколко начина за тяхното използване. + +Най-простият начин е използването на **заместващи въпросителни знаци**: + +```php +// ✅ Безопасна параметризирана заявка +$database->query('SELECT * FROM users WHERE name = ?', $name); + +// ✅ Безопасно условие в Explorer +$table->where('name = ?', $name); +``` + +Това важи за всички други методи в [Database Explorer|explorer], които позволяват вмъкване на изрази със заместващи въпросителни знаци и параметри. + +За командите INSERT, UPDATE или клаузата WHERE можем да предадем стойности в масив: + +```php +// ✅ Безопасен INSERT +$database->query('INSERT INTO users', [ + 'name' => $name, + 'email' => $email, +]); + +// ✅ Безопасен INSERT в Explorer +$table->insert([ + 'name' => $name, + 'email' => $email, +]); +``` + + +Валидация на стойностите на параметрите +======================================= + +Параметризираните заявки са основният градивен елемент за безопасна работа с базата данни. Въпреки това, стойностите, които вмъкваме в тях, трябва да преминат през няколко нива на проверка: + + +Проверка на типа +---------------- + +**Най-важното е да се гарантира правилният тип данни на параметрите** - това е необходимо условие за безопасното използване на Nette Database. Базата данни предполага, че всички входни данни имат правилния тип данни, съответстващ на дадената колона. + +Например, ако `$name` в предишните примери неочаквано беше масив вместо низ, Nette Database щеше да се опита да вмъкне всички негови елементи в SQL заявката, което би довело до грешка. Затова **никога не използвайте** невалидирани данни от `$_GET`, `$_POST` или `$_COOKIE` директно в заявките към базата данни. + + +Проверка на формата +------------------- + +На второ ниво проверяваме формата на данните - например дали низовете са в UTF-8 кодиране и тяхната дължина съответства на дефиницията на колоната, или дали числовите стойности са в допустимия диапазон за дадения тип данни на колоната. + +На това ниво на валидация можем частично да разчитаме и на самата база данни - много бази данни ще отхвърлят невалидни данни. Въпреки това, поведението може да варира, някои могат тихо да скъсят дълги низове или да отрежат числа извън диапазона. + + +Домейн проверка +--------------- + +Третото ниво представляват логически проверки, специфични за вашето приложение. Например, проверка дали стойностите от select полетата съответстват на предлаганите опции, дали числата са в очаквания диапазон (напр. възраст 0-150 години) или дали взаимните зависимости между стойностите имат смисъл. + + +Препоръчителни начини за валидация +---------------------------------- + +- Използвайте [Nette Forms|forms:], които автоматично осигуряват правилната валидация на всички входове +- Използвайте [Presenters|application:] и посочвайте типовете данни за параметрите в методите `action*()` и `render*()` +- Или реализирайте собствен слой за валидация с помощта на стандартни PHP инструменти като `filter_var()` + + +Безопасна работа с колони +========================= + +В предишната секция показахме как правилно да валидираме стойностите на параметрите. При използване на масиви в SQL заявки обаче трябва да обърнем същото внимание и на техните ключове. + +```php +// ❌ ОПАСЕН КОД - ключовете в масива не са обработени +$database->query('INSERT INTO users', $_POST); +``` + +При командите INSERT и UPDATE това е критична грешка в сигурността - нападателят може да вмъкне или промени всяка колона в базата данни. Може например да зададе `is_admin = 1` или да вмъкне произволни данни в чувствителни колони (т.нар. Mass Assignment Vulnerability). + +В условията WHERE е още по-опасно, тъй като те могат да съдържат оператори: + +```php +// ❌ ОПАСЕН КОД - ключовете в масива не са обработени +$_POST['salary >'] = 100000; +$database->query('SELECT * FROM users WHERE', $_POST); +// изпълнява заявка WHERE (`salary` > 100000) +``` + +Нападателят може да използва този подход за систематично откриване на заплатите на служителите. Започва например със заявка за заплати над 100 000, след това под 50 000 и чрез постепенно стесняване на диапазона може да разкрие приблизителните заплати на всички служители. Този тип атака се нарича SQL enumeration. + +Методите `where()` и `whereOr()` са още [много по-гъвкави |explorer#where] и поддържат SQL изрази в ключовете и стойностите, включително оператори и функции. Това дава възможност на нападателя да извърши SQL инжекция: + +```php +// ❌ ОПАСЕН КОД - нападателят може да вмъкне собствен SQL +$_POST = ['0) UNION SELECT name, salary FROM users WHERE (1']; +$table->where($_POST); +// изпълнява заявка WHERE (0) UNION SELECT name, salary FROM users WHERE (1) +``` + +Тази атака прекратява първоначалното условие с помощта на `0)`, добавя собствена `SELECT` команда с помощта на `UNION`, за да получи чувствителни данни от таблицата `users`, и затваря синтактично правилната заявка с помощта на `WHERE (1)`. + + +Бял списък на колони +-------------------- + +За безопасна работа с имената на колони се нуждаем от механизъм, който да гарантира, че потребителят може да работи само с разрешени колони и не може да добавя собствени. Можем да се опитаме да открием и блокираме опасни имена на колони (черен списък), но този подход е ненадежден - нападателят винаги може да измисли нов начин да запише опасно име на колона, който не сме предвидили. + +Затова е много по-безопасно да обърнем логиката и да дефинираме изричен списък с разрешени колони (бял списък): + +```php +// Колони, които потребителят може да редактира +$allowedColumns = ['name', 'email', 'active']; + +// Премахваме всички неразрешени колони от входа +$filteredData = array_intersect_key($userData, array_flip($allowedColumns)); + +// ✅ Сега можем безопасно да използваме в заявки, като например: +$database->query('INSERT INTO users', $filteredData); +$table->update($filteredData); +$table->where($filteredData); +``` + + +Динамични идентификатори +======================== + +За динамични имена на таблици и колони използвайте заместващия символ `?name`. Той осигурява правилното екраниране на идентификаторите според синтаксиса на дадената база данни (напр. с помощта на обратни кавички в MySQL): + +```php +// ✅ Безопасно използване на доверени идентификатори +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name', $column, $table); +// Резултат в MySQL: SELECT `name` FROM `users` +``` + +Важно: използвайте символа `?name` само за доверени стойности, дефинирани в кода на приложението. За стойности от потребителя използвайте отново [бял списък |#Бял списък на колони]. В противен случай се излагате на рискове за сигурността: + +```php +// ❌ ОПАСНО - никога не използвайте вход от потребител +$database->query('SELECT ?name FROM users', $_GET['column']); +``` diff --git a/database/bg/sql-way.texy b/database/bg/sql-way.texy new file mode 100644 index 0000000000..6833ea9af8 --- /dev/null +++ b/database/bg/sql-way.texy @@ -0,0 +1,513 @@ +SQL достъп +********** + +.[perex] +Nette Database предлага два начина: можете да пишете SQL заявки сами (SQL достъп) или да ги оставите да се генерират автоматично (вижте [Explorer |explorer]). SQL достъпът ви дава пълен контрол над заявките, като същевременно гарантира тяхното безопасно изграждане. + +.[note] +Подробности за свързването и конфигурацията на базата данни можете да намерите в глава [Свързване и конфигурация |guide#Свързване и конфигурация]. + + +Основно запитване +================= + +За запитвания към базата данни се използва методът `query()`. Той връща обект [ResultSet |api:Nette\Database\ResultSet], който представлява резултата от заявката. В случай на неуспех, методът [хвърля изключение |exceptions]. Можем да обходим резултата от заявката с помощта на цикъл `foreach` или да използваме някоя от [помощните функции |#Получаване на данни]. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; +} +``` + +За безопасно вмъкване на стойности в SQL заявки използваме параметризирани заявки. Nette Database ги прави максимално прости - достатъчно е да добавите запетая и стойност след SQL заявката: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +При повече параметри имате две опции за запис. Можете или да "вмъквате" параметри в SQL заявката: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age); +``` + +Или първо да напишете цялата SQL заявка и след това да добавите всички параметри: + +```php +$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age); +``` + + +Защита от SQL injection +======================= + +Защо е важно да се използват параметризирани заявки? Защото те ви защитават от атака, наречена SQL injection, при която нападателят може да вмъкне собствени SQL команди и по този начин да получи или повреди данни в базата данни. + +.[warning] +**Никога не вмъквайте променливи директно в SQL заявката!** Винаги използвайте параметризирани заявки, които ви защитават от SQL injection. + +```php +// ❌ ОПАСЕН КОД - уязвим към SQL injection +$database->query("SELECT * FROM users WHERE name = '$name'"); + +// ✅ Безопасна параметризирана заявка +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Запознайте се с [възможните рискове за сигурността |security]. + + +Техники за запитване +==================== + + +Условия WHERE +------------- + +Можете да запишете условията WHERE като асоциативен масив, където ключовете са имената на колоните, а стойностите са данните за сравнение. Nette Database автоматично избира най-подходящия SQL оператор според типа на стойността. + +```php +$database->query('SELECT * FROM users WHERE', [ + 'name' => 'John', + 'active' => true, +]); +// WHERE `name` = 'John' AND `active` = 1 +``` + +В ключа можете също изрично да посочите оператора за сравнение: + +```php +$database->query('SELECT * FROM users WHERE', [ + 'age >' => 25, // използва оператор > + 'name LIKE' => '%John%', // използва оператор LIKE + 'email NOT LIKE' => '%example.com%', // използва оператор NOT LIKE +]); +// WHERE `age` > 25 AND `name` LIKE '%John%' AND `email` NOT LIKE '%example.com%' +``` + +Nette автоматично обработва специални случаи като `null` стойности или масиви. + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name' => 'Laptop', // използва оператор = + 'category_id' => [1, 2, 3], // използва IN + 'description' => null, // използва IS NULL +]); +// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL +``` + +За отрицателни условия използвайте оператора `NOT`: + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name NOT' => 'Laptop', // използва оператор <> + 'category_id NOT' => [1, 2, 3], // използва NOT IN + 'description NOT' => null, // използва IS NOT NULL + 'id' => [], // пропуска се +]); +// WHERE `name` <> 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL +``` + +За свързване на условия се използва операторът `AND`. Това може да се промени с помощта на [заместващия символ ?or |#Подсказки за изграждане на SQL]. + + +Правила ORDER BY +---------------- + +Сортирането `ORDER BY` може да се запише с помощта на масив. В ключовете посочваме колоните, а стойността ще бъде булева променлива, определяща дали да се сортира възходящо: + +```php +$database->query('SELECT id FROM author ORDER BY', [ + 'id' => true, // възходящо + 'name' => false, // низходящо +]); +// SELECT id FROM author ORDER BY `id`, `name` DESC +``` + + +Вмъкване на данни (INSERT) +-------------------------- + +За вмъкване на записи се използва SQL инструкцията `INSERT`. + +```php +$values = [ + 'name' => 'John Doe', + 'email' => 'john@example.com', +]; +$database->query('INSERT INTO users ?', $values); +$userId = $database->getInsertId(); +``` + +Методът `getInsertId()` връща ID на последния вмъкнат ред. При някои бази данни (напр. PostgreSQL) е необходимо да се посочи като параметър името на последователността, от която трябва да се генерира ID, с помощта на `$database->getInsertId($sequenceId)`. + +Като параметри можем да предаваме и [#специални стойности] като файлове, обекти DateTime или enum типове. + +Вмъкване на няколко записа наведнъж: + +```php +$database->query('INSERT INTO users ?', [ + ['name' => 'User 1', 'email' => 'user1@mail.com'], + ['name' => 'User 2', 'email' => 'user2@mail.com'], +]); +``` + +Многократното INSERT е много по-бързо, тъй като се изпълнява една единствена заявка към базата данни, вместо много отделни. + +**Предупреждение за сигурност:** Никога не използвайте невалидирани данни като `$values`. Запознайте се с [възможните рискове |security#Безопасна работа с колони]. + + +Актуализация на данни (UPDATE) +------------------------------ + +За актуализация на записи се използва SQL инструкцията `UPDATE`. + +```php +// Актуализация на един запис +$values = [ + 'name' => 'John Smith', +]; +$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1); +``` + +Броят на засегнатите редове се връща от `$result->getRowCount()`. + +За UPDATE можем да използваме операторите `+=` и `-=`: + +```php +$database->query('UPDATE users SET ? WHERE id = ?', [ + 'login_count+=' => 1, // инкрементиране на login_count +], 1); +``` + +Пример за вмъкване или редактиране на запис, ако вече съществува. Ще използваме техниката `ON DUPLICATE KEY UPDATE`: + +```php +$values = [ + 'name' => $name, + 'year' => $year, +]; +$database->query('INSERT INTO users ? ON DUPLICATE KEY UPDATE ?', + $values + ['id' => $id], + $values, +); +// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) +// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 +``` + +Забележете, че Nette Database разпознава в какъв контекст на SQL инструкцията вмъкваме параметъра с масив и съответно изгражда SQL кода от него. Така от първия масив е изградил `(id, name, year) VALUES (123, 'Jim', 1978)`, докато втория е преобразувал във формата `name = 'Jim', year = 1978`. Разглеждаме това по-подробно в секцията [#Подсказки за изграждане на SQL]. + + +Изтриване на данни (DELETE) +--------------------------- + +За изтриване на записи се използва SQL инструкцията `DELETE`. Пример за получаване на броя на изтритите редове: + +```php +$count = $database->query('DELETE FROM users WHERE id = ?', 1) + ->getRowCount(); +``` + + +Подсказки за изграждане на SQL +------------------------------ + +Подсказката е специален placeholder в SQL заявката, който указва как стойността на параметъра трябва да се преобразува в SQL израз: + +| Подсказка | Описание | Автоматично се използва +|-----------|-------------------------------------------------|----------------------------- +| `?name` | използва се за вмъкване на име на таблица или колона | - +| `?values` | генерира `(key, ...) VALUES (value, ...)` | `INSERT ... ?`, `REPLACE ... ?` +| `?set` | генерира присвояване `key = value, ...` | `SET ?`, `KEY UPDATE ?` +| `?and` | свързва условията в масива с оператор `AND` | `WHERE ?`, `HAVING ?` +| `?or` | свързва условията в масива с оператор `OR` | - +| `?order` | генерира клауза `ORDER BY` | `ORDER BY ?`, `GROUP BY ?` + +За динамично вмъкване на имена на таблици и колони в заявката се използва placeholder-ът `?name`. Nette Database се грижи за правилното обработване на идентификаторите според конвенциите на дадената база данни (напр. затваряне в обратни кавички в MySQL). + +```php +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table); +// SELECT `name` FROM `users` WHERE id = 1 (в MySQL) +``` + +**Внимание:** използвайте символа `?name` само за имена на таблици и колони от валидирани входове, в противен случай се излагате на [риск за сигурността |security#Динамични идентификатори]. + +Обикновено не е необходимо да се посочват другите подсказки, тъй като Nette използва интелигентно автоматично откриване при съставянето на SQL заявката (вижте третата колона на таблицата). Но можете да ги използвате например в ситуация, когато искате да свържете условията с `OR` вместо с `AND`: + +```php +$database->query('SELECT * FROM users WHERE ?or', [ + 'name' => 'John', + 'email' => 'john@example.com', +]); +// SELECT * FROM users WHERE `name` = 'John' OR `email` = 'john@example.com' +``` + + +Специални стойности +------------------- + +Освен обичайните скаларни типове (string, int, bool), можете да предавате като параметри и специални стойности: + +- файлове: `fopen('image.gif', 'r')` вмъква бинарното съдържание на файла +- дата и час: обекти `DateTime` се преобразуват в база данни формат +- enum типове: инстанции на `enum` се преобразуват в тяхната стойност +- SQL литерали: създадени с помощта на `Connection::literal('NOW()')` се вмъкват директно в заявката + +```php +$database->query('INSERT INTO articles ?', [ + 'title' => 'My Article', + 'published_at' => new DateTime, + 'content' => fopen('image.png', 'r'), + 'state' => Status::Draft, +]); +``` + +При бази данни, които нямат нативна поддръжка за типа данни `datetime` (като SQLite и Oracle), `DateTime` се преобразува в стойност, определена в [конфигурацията на базата данни |configuration] чрез елемента `formatDateTime` (стойността по подразбиране е `U` - unix timestamp). + + +SQL литерали +------------ + +В някои случаи трябва да посочите директно SQL код като стойност, който обаче не трябва да се разбира като низ и да се екранира. За това служат обектите от класа `Nette\Database\SqlLiteral`. Те се създават от метода `Connection::literal()`. + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + 'year >' => $database::literal('YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) +``` + +Или алтернативно: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) +``` + +SQL литералите могат да съдържат параметри: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > ? AND year < ?', $min, $max), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) +``` + +Благодарение на което можем да създаваме интересни комбинации: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('?or', [ + 'active' => true, + 'role' => $role, + ]), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') +``` + + +Получаване на данни +=================== + + +Кратки пътища за SELECT заявки +------------------------------ + +За опростяване на извличането на данни `Connection` предлага няколко кратки пътя, които комбинират извикването на `query()` със следващо `fetch*()`. Тези методи приемат същите параметри като `query()`, т.е. SQL заявка и незадължителни параметри. Пълно описание на методите `fetch*()` ще намерите [по-долу |#fetch]. + +| `fetch($sql, ...$params): ?Row` | Изпълнява заявка и връща първия ред като обект `Row` +| `fetchAll($sql, ...$params): array` | Изпълнява заявка и връща всички редове като масив от обекти `Row` +| `fetchPairs($sql, ...$params): array` | Изпълнява заявка и връща асоциативен масив, където първата колона представлява ключ, а втората - стойност +| `fetchField($sql, ...$params): mixed` | Изпълнява заявка и връща стойността на първото поле от първия ред +| `fetchList($sql, ...$params): ?array` | Изпълнява заявка и връща първия ред като индексиран масив + +Пример: + +```php +// fetchField() - връща стойността на първата клетка +$count = $database->query('SELECT COUNT(*) FROM articles') + ->fetchField(); +``` + + +`foreach` - итерация през редове +-------------------------------- + +След изпълнение на заявката се връща обект [ResultSet|api:Nette\Database\ResultSet], който позволява обхождане на резултатите по няколко начина. Най-лесният начин да изпълните заявка и да получите редовете е чрез итерация в цикъл `foreach`. Този начин е най-икономичен откъм памет, тъй като връща данните постепенно и не ги съхранява всички наведнъж в паметта. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; + // ... +} +``` + +.[note] +`ResultSet` може да се итерира само веднъж. Ако трябва да итерирате многократно, първо трябва да заредите данните в масив, например с помощта на метода `fetchAll()`. + + +fetch(): ?Row .[method] +----------------------- + +Връща ред като обект `Row`. Ако няма повече редове, връща `null`. Премества вътрешния указател към следващия ред. + +```php +$result = $database->query('SELECT * FROM users'); +$row = $result->fetch(); // зарежда първия ред +if ($row) { + echo $row->name; +} +``` + + +fetchAll(): array .[method] +--------------------------- + +Връща всички останали редове от `ResultSet` като масив от обекти `Row`. + +```php +$result = $database->query('SELECT * FROM users'); +$rows = $result->fetchAll(); // зарежда всички редове +foreach ($rows as $row) { + echo $row->name; +} +``` + + +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Връща резултатите като асоциативен масив. Първият аргумент определя името на колоната, която ще се използва като ключ в масива, вторият аргумент определя името на колоната, която ще се използва като стойност: + +```php +$result = $database->query('SELECT id, name FROM users'); +$names = $result->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] +``` + +Ако посочим само първия параметър, стойността ще бъде целият ред, т.е. обект `Row`: + +```php +$rows = $result->fetchPairs('id'); +// [1 => Row(id: 1, name: 'John'), 2 => Row(id: 2, name: 'Jane'), ...] +``` + +В случай на дублиращи се ключове, се използва стойността от последния ред. При използване на `null` като ключ, масивът ще бъде индексиран числово от нула (тогава не възникват колизии): + +```php +$names = $result->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] +``` + + +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Алтернативно, можете да посочите като параметър callback, който за всеки ред ще връща или самата стойност, или двойка ключ-стойност. + +```php +$result = $database->query('SELECT * FROM users'); +$items = $result->fetchPairs(fn($row) => "$row->id - $row->name"); +// ['1 - John', '2 - Jane', ...] + +// Callback може също да връща масив с двойка ключ & стойност: +$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]); +// ['John' => 46, 'Jane' => 21, ...] +``` + + +fetchField(): mixed .[method] +----------------------------- + +Връща стойността на първото поле от текущия ред. Ако няма повече редове, връща `null`. Премества вътрешния указател към следващия ред. + +```php +$result = $database->query('SELECT name FROM users'); +$name = $result->fetchField(); // зарежда името от първия ред +``` + + +fetchList(): ?array .[method] +----------------------------- + +Връща ред като индексиран масив. Ако няма повече редове, връща `null`. Премества вътрешния указател към следващия ред. + +```php +$result = $database->query('SELECT name, email FROM users'); +$row = $result->fetchList(); // ['John', 'john@example.com'] +``` + + +getRowCount(): ?int .[method] +----------------------------- + +Връща броя на засегнатите редове от последната заявка `UPDATE` или `DELETE`. За `SELECT` това е броят на върнатите редове, но той може да не е известен - в такъв случай методът връща `null`. + + +getColumnCount(): ?int .[method] +-------------------------------- + +Връща броя на колоните в `ResultSet`. + + +Информация за заявките +====================== + +За целите на дебъгването можем да получим информация за последната изпълнена заявка: + +```php +echo $database->getLastQueryString(); // извежда SQL заявката + +$result = $database->query('SELECT * FROM articles'); +echo $result->getQueryString(); // извежда SQL заявката +echo $result->getTime(); // извежда времето за изпълнение в секунди +``` + +За показване на резултата като HTML таблица може да се използва: + +```php +$result = $database->query('SELECT * FROM articles'); +$result->dump(); +``` + +ResultSet предлага информация за типовете на колоните: + +```php +$result = $database->query('SELECT * FROM articles'); +$types = $result->getColumnTypes(); + +foreach ($types as $column => $type) { + echo "$column е тип $type->type"; // напр. 'id е тип int' +} +``` + + +Логване на заявки +----------------- + +Можем да реализираме собствено логване на заявки. Събитието `onQuery` е масив от callback-ове, които се извикват след всяка изпълнена заявка: + +```php +$database->onQuery[] = function ($database, $result) use ($logger) { + $logger->info('Заявка: ' . $result->getQueryString()); + $logger->info('Време: ' . $result->getTime()); + + if ($result->getRowCount() > 1000) { + $logger->warning('Голям резултатен набор: ' . $result->getRowCount() . ' реда'); + } +}; +``` diff --git a/database/bg/transactions.texy b/database/bg/transactions.texy new file mode 100644 index 0000000000..dd532f0a4a --- /dev/null +++ b/database/bg/transactions.texy @@ -0,0 +1,43 @@ +Транзакции +********** + +.[perex] +Транзакциите гарантират, че или всички операции в рамките на трансакцията ще бъдат изпълнени, или нито една няма да бъде изпълнена. Те са полезни за осигуряване на консистентност на данните при по-сложни операции. + +Най-лесният начин за използване на транзакции изглежда така: + +```php +$database->beginTransaction(); +try { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); + $database->commit(); +} catch (\Exception $e) { + $database->rollBack(); + throw $e; +} +``` + +Можете да запишете същото много по-елегантно с помощта на метода `transaction()`. Той приема като параметър callback, който изпълнява в транзакция. Ако callback-ът премине без изключение, транзакцията се потвърждава автоматично. Ако възникне изключение, транзакцията се отменя (rollback) и изключението се разпространява по-нататък. + +```php +$database->transaction(function ($database) use ($id) { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); +}); +``` + +Методът `transaction()` може също да връща стойности: + +```php +$count = $database->transaction(function ($database) { + $result = $database->query('UPDATE users SET active = ?', true); + return $result->getRowCount(); // връща броя на актуализираните редове +}); +``` diff --git a/database/cs/@home.texy b/database/cs/@home.texy index 20cc87170b..58162d133d 100644 --- a/database/cs/@home.texy +++ b/database/cs/@home.texy @@ -17,5 +17,5 @@ Nette podporuje následující databáze: -{{title: Nette Database}} +{{maintitle: Nette Database - awesome database layer for PHP}} {{description: Nette Database zásadním způsobem zjednodušuje získávání dat z databáze bez nutnosti psát SQL dotazy. Pokládá efektivní dotazy a nepřenáší zbytečná data.}} diff --git a/database/cs/@left-menu.texy b/database/cs/@left-menu.texy index 039cedfdf8..6cb0892efa 100644 --- a/database/cs/@left-menu.texy +++ b/database/cs/@left-menu.texy @@ -1,5 +1,12 @@ -Databáze -******** -- [Core] +Nette Database +************** +- [Úvod |guide] +- [SQL přístup |sql way] - [Explorer] +- [Transakce |transactions] +- [Výjimky |exceptions] +- [Reflexe |reflection] +- [Mapování |mapping] - [Konfigurace |configuration] +- [Bezpečnostní rizika |security] +- [Upgrade |upgrading] diff --git a/database/cs/@meta.texy b/database/cs/@meta.texy new file mode 100644 index 0000000000..462d9add80 --- /dev/null +++ b/database/cs/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokumentace}} diff --git a/database/cs/configuration.texy b/database/cs/configuration.texy index 0bae753b56..021ee3cd11 100644 --- a/database/cs/configuration.texy +++ b/database/cs/configuration.texy @@ -20,7 +20,7 @@ database: password: ... ``` -Vytvoří službu `Nette\Database\Connection` a `Nette\Database\Explorer` pro vrstvu [Database Explorer|explorer]. Databáze se obvykle předává [autowiringem |dependency-injection:autowiring], není-li to možné, použijte názvy služeb `@database.default.connection` resp. `@database.default.explorer`. +Vytvoří služby `Nette\Database\Connection` a `Nette\Database\Explorer`, které si obvykle předáváme [autowiringem |dependency-injection:autowiring], případně odkazem na [jejich název |#Služby DI]. Další nastavení: @@ -49,7 +49,13 @@ database: sqlmode: # (string) # pouze MySQL: nastaví SET NAMES - charset: # (string) výchozí je 'utf8mb4' ('utf8' před verzí 5.5.3) + charset: # (string) výchozí je 'utf8mb4' + + # pouze MySQL: převádí TINYINT(1) na bool + convertBoolean: # (bool) výchozí je false + + # vrací sloupce s datem jako immutable objekty (od verze 3.2.1) + newDateTime: # (bool) výchozí je false # pouze Oracle a SQLite: formát pro ukládání data formatDateTime: # (string) výchozí je 'U' @@ -80,9 +86,23 @@ database: dsn: 'sqlite::memory:' ``` -Každé takto definované spojení vytváří služby zahrnující v názvu i název sekce, tj. `@database.main.connection` & `@database.main.explorer` a dále `@database.another.connection` & `@database.another.explorer`. +Autowiring je zapnutý jen u služeb z první sekce. Lze to změnit pomocí `autowired: false` nebo `autowired: true`. + + +Služby DI +--------- + +Tyto služby se přidávají do DI kontejneru, kde `###` představuje název spojení: + +| Název | Typ | Popis +|---------------------------------------------------------- +| `database.###.connection` | [api:Nette\Database\Connection] | spojení s databází +| `database.###.explorer` | [api:Nette\Database\Explorer] | [Database Explorer |explorer] + + +Pokud definujeme jen jedno spojení, názvy služeb budou `database.default.connection` a `database.default.explorer`. Pokud definujeme více spojení jako v příkladu výše, názvy budou odpovídat sekcím, tj. `database.main.connection`, `database.main.explorer` a dále `database.another.connection` a `database.another.explorer`. -Autowiring je zapnutý jen u služeb z první sekce. Lze to změnit pomocí `autowired: false` nebo `autowired: true`. Neautowirované služby předáváme explicitně: +Neautowirované služby předáváme explicitně odkazem na jejich název: ```neon services: diff --git a/database/cs/core.texy b/database/cs/core.texy deleted file mode 100644 index 02c3104e43..0000000000 --- a/database/cs/core.texy +++ /dev/null @@ -1,350 +0,0 @@ -Database Core -************* - -.[perex] -Nette Database Core je základní vrstva pro přístup k databázi, tzv. database abstraction layer. - - -Instalace -========= - -Knihovnu stáhnete a nainstalujete pomocí nástroje [Composer|best-practices:composer]: - -```shell -composer require nette/database -``` - - -Připojení a konfigurace -======================= - -Pro připojení k databázi stačí vytvořit instanci třídy [api:Nette\Database\Connection]: - -```php -$database = new Nette\Database\Connection($dsn, $user, $password); -``` - -Parametr `$dsn` (data source name) je stejný, [jaký používá PDO |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], např. `host=127.0.0.1;dbname=test`. V případě selhání vyhodí výjimku `Nette\Database\ConnectionException`. - -Nicméně šikovnější způsob nabízí [aplikační konfigurace |configuration], kam stačí přidat sekci `database` a vytvoří se potřebné objekty a také databázový panel v [Tracy |tracy:] baru. - -```neon -database: - dsn: 'mysql:host=127.0.0.1;dbname=test' - user: root - password: password -``` - -Poté objekt spojení [získáme jako službu z DI kontejneru |dependency-injection:passing-dependencies], např.: - -```php -class Model -{ - // pro práci s vrstvou Database Explorer si předáme Nette\Database\Explorer - public function __construct( - private Nette\Database\Connection $database, - ) { - } -} -``` - -Více informací o [konfiguraci databáze|configuration]. - - -Dotazy -====== - -Databázové dotazy pokládáme metodou `query()`, která vrací [ResultSet |api:Nette\Database\ResultSet]. - -```php -$result = $database->query('SELECT * FROM users'); - -foreach ($result as $row) { - echo $row->id; - echo $row->name; -} - -echo $result->getRowCount(); // vrací počet řádků výsledku, pokud je znám -``` - -.[note] -Nad `ResultSet` je možné iterovat pouze jednou, pokud potřebujeme iterovat vícekrát, je nutno výsledek převést na pole metodou `fetchAll()`. - -Do dotazu lze velmi snadno přidávat i parametry, všimněte si otazníku: - -```php -$database->query('SELECT * FROM users WHERE name = ?', $name); - -$database->query('SELECT * FROM users WHERE name = ? AND active = ?', $name, $active); - -$database->query('SELECT * FROM users WHERE id IN (?)', $ids); // $ids je pole -``` - -<div class=warning> -POZOR, nikdy dotazy neskládejte jako řetězce, vznikla by zranitelnost [SQL injection |https://cs.wikipedia.org/wiki/SQL_injection] -/-- -$db->query('SELECT * FROM users WHERE name = ' . $name); // ŠPATNĚ!!! -\-- -</div> - -V případě selhání `query()` vyhodí buď `Nette\Database\DriverException` nebo některého z potomků: - -- [ConstraintViolationException |api:Nette\Database\ConstraintViolationException] - porušení nějakého omezení pro tabulku -- [ForeignKeyConstraintViolationException |api:Nette\Database\ForeignKeyConstraintViolationException] - neplatný cizí klíč -- [NotNullConstraintViolationException |api:Nette\Database\NotNullConstraintViolationException] - porušení podmínky NOT NULL -- [UniqueConstraintViolationException |api:Nette\Database\UniqueConstraintViolationException] - koliduje unikátní index - -Kromě `query()` jsou tu další užitečné funkce: - -```php -// vrátí asociativní pole id => name -$pairs = $database->fetchPairs('SELECT id, name FROM users'); - -// vrátí všechny záznamy jako pole -$rows = $database->fetchAll('SELECT * FROM users'); - -// vrátí jeden záznam -$row = $database->fetch('SELECT * FROM users WHERE id = ?', $id); - -// vrátí přímo hodnotu buňky -$name = $database->fetchField('SELECT name FROM users WHERE id = ?', $id); -``` - -V případě selhání všechny tyto metody vyhodí `Nette\Database\DriverException`. - - -Insert, Update & Delete -======================= - -Parameterem, který vkládáme do SQL dotazu, může být i pole (v takovém případě je navíc možné zástupný symbol `?` vynechat), což se hodí třeba pro sestavení příkazu `INSERT`: - -```php -$database->query('INSERT INTO users ?', [ // tady můžeme otazník vynechat - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) - -$id = $database->getInsertId(); // vrátí auto-increment vloženého záznamu - -$id = $database->getInsertId($sequence); // nebo hodnotu sekvence -``` - -Vícenásobný INSERT: - -```php -$database->query('INSERT INTO users', [ - [ - 'name' => 'Jim', - 'year' => 1978, - ], [ - 'name' => 'Jack', - 'year' => 1987, - ], -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) -``` - -Jako parametry můžeme předávat i soubory, objekty DateTime nebo [výčtové typy |https://www.php.net/enumerations]: - -```php -$database->query('INSERT INTO users', [ - 'name' => $name, - 'created' => new DateTime, // nebo $database::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // vloží soubor - 'status' => State::New, // enum State -]); -``` - -Úprava záznamů: - -```php -$result = $database->query('UPDATE users SET', [ - 'name' => $name, - 'year' => $year, -], 'WHERE id = ?', $id); -// UPDATE users SET `name` = 'Jim', `year` = 1978 WHERE id = 123 - -echo $result->getRowCount(); // vrací počet ovlivněných řádků -``` - -Pro UPDATE můžeme využít operátorů `+=` a `-=`: - -```php -$database->query('UPDATE users SET', [ - 'age+=' => 1, // všimněte si += -], 'WHERE id = ?', $id); -// UPDATE users SET `age` = `age` + 1 -``` - -Mazání: - -```php -$result = $database->query('DELETE FROM users WHERE id = ?', $id); -echo $result->getRowCount(); // vrací počet ovlivněných řádků -``` - - -Pokročilé dotazy -================ - -Vložení, nebo úprava záznamu, pokud již existuje: - -```php -$database->query('INSERT INTO users', [ - 'id' => $id, - 'name' => $name, - 'year' => $year, -], 'ON DUPLICATE KEY UPDATE', [ - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) -// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 -``` - -Všimněte si, že Nette Database pozná, v jakém kontextu SQL příkazu parametr s polem vkládáme a podle toho z něj sestaví SQL kód. Takže z prvního pole sestavil `(id, name, year) VALUES (123, 'Jim', 1978)`, zatímco druhé převedl do podoby `name = 'Jim', year = 1978`. - -Také řazení můžeme ovlivnit polem, v klíčích uvedeme sloupce a hodnotou bude boolean určující, zda řadit vzestupně: - -```php -$database->query('SELECT id FROM author ORDER BY', [ - 'id' => true, // vzestupně - 'name' => false, // sestupně -]); -// SELECT id FROM author ORDER BY `id`, `name` DESC -``` - -Pokud by u neobvyklé konstrukce detekce nezafungovala, můžete formu sestavení určit zástupným symbolem `?` doplněným o hint. Podporovány jsou tyto hinty: - -| ?values | (key1, key2, ...) VALUES (value1, value2, ...) -| ?set | key1 = value1, key2 = value2, ... -| ?and | key1 = value1 AND key2 = value2 ... -| ?or | key1 = value1 OR key2 = value2 ... -| ?order | key1 ASC, key2 DESC - -V klauzuli WHERE se používá operátor `?and`, takže podmínky se spojují operátorem `AND`: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND `year` = 1978 -``` - -Což můžeme snadno změnit na `OR` tím, že uvedeme zástupný symbol `?or`: - -```php -$result = $database->query('SELECT * FROM users WHERE ?or', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' OR `year` = 1978 -``` - -V podmínkách můžeme používat operátory: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name <>' => $name, - 'year >' => $year, -]); -// SELECT * FROM users WHERE `name` <> 'Jim' AND `year` > 1978 -``` - -A také výčty: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => ['Jim', 'Jack'], - 'role NOT IN' => ['admin', 'owner'], // výčet + operátor NOT IN -]); -// SELECT * FROM users WHERE -// `name` IN ('Jim', 'Jack') AND `role` NOT IN ('admin', 'owner') -``` - -Do podmínky také můžeme vložit kus vlastního SQL kódu pomocí tzv. SQL literálu: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year >' => $database::literal('YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) -``` - -Nebo alternativě: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) -``` - -SQL literál také může mít své parametry: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > ? AND year < ?', $min, $max), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) -``` - -Díky čemuž můžeme vytvářet zajímavé kombinace: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('?or', [ - 'active' => true, - 'role' => $role, - ]), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') -``` - - -Proměnný název -============== - -Ještě existuje zástupný symbol `?name`, který využijete v případě, že název tabulky nebo sloupce je proměnnou. (Pozor, nedovolte uživateli manipulovat s obsahem takové proměnné): - -```php -$table = 'blog.users'; -$column = 'name'; -$database->query('SELECT * FROM ?name WHERE ?name = ?', $table, $column, $name); -// SELECT * FROM `blog`.`users` WHERE `name` = 'Jim' -``` - - -Transakce -========= - -Pro práci s transakcemi slouží trojice metod: - -```php -$database->beginTransaction(); // zahájení transakce - -$database->commit(); // potvrzení - -$database->rollback(); // vrácení zpět -``` - -Elegantní způsob nabízí metoda `transaction()`, které předáme callback, který se vykoná v transakci. Pokud během vykonávání dojde k vyhození výjimky, transakce se zahodí, pokud vše proběhne v pořádku, transakce se potvrdí. - -```php -$id = $database->transaction(function ($database) { - $database->query('DELETE FROM ...'); - $database->query('INSERT INTO ...'); - // ... - return $database->getInsertId(); -}); -``` - -Jak vidíte, metoda `transaction()` vrací návratovou hodnotu callbacku. - -Volání `transaction()` může být i zanořeno, což zjednodušuje implementaci nezávislých repozitářů. diff --git a/database/cs/exceptions.texy b/database/cs/exceptions.texy new file mode 100644 index 0000000000..e78dd0108d --- /dev/null +++ b/database/cs/exceptions.texy @@ -0,0 +1,34 @@ +Výjimky +******* + +Nette Database používá hierarchii výjimek. Základní třídou je `Nette\Database\DriverException`, která dědí z `PDOException` a poskytuje rozšířené možnosti pro práci s chybami databáze: + +- Metoda `getDriverCode()` vrací kód chyby od databázového driveru +- Metoda `getSqlState()` vrací SQLSTATE kód +- Metody `getQueryString()` a `getParameters()` umožňují získat původní dotaz a jeho parametry + +Z `DriverException` dědí následující specializované výjimky: + +- `ConnectionException` - signalizuje selhání připojení k databázovému serveru +- `ConstraintViolationException` - základní třída pro porušení databázových omezení, ze které dědí: + - `ForeignKeyConstraintViolationException` - porušení cizího klíče + - `NotNullConstraintViolationException` - porušení NOT NULL omezení + - `UniqueConstraintViolationException` - porušení unikátnosti hodnoty + + +Příklad zachytávání výjimky `UniqueConstraintViolationException`, která nastane, když se snažíme vložit uživatele s emailem, který už v databázi existuje (za předpokladu, že sloupec email má unikátní index). + +```php +try { + $database->query('INSERT INTO users', [ + 'email' => 'john@example.com', + 'name' => 'John Doe', + 'password' => $hashedPassword, + ]); +} catch (Nette\Database\UniqueConstraintViolationException $e) { + echo 'Uživatel s tímto emailem již existuje.'; + +} catch (Nette\Database\DriverException $e) { + echo 'Došlo k chybě při registraci: ' . $e->getMessage(); +} +``` diff --git a/database/cs/explorer.texy b/database/cs/explorer.texy index 9a3acc8ea0..24044ef827 100644 --- a/database/cs/explorer.texy +++ b/database/cs/explorer.texy @@ -3,529 +3,798 @@ Database Explorer <div class=perex> -Nette Database Explorer (dříve Nette Database Table, NDBT) zásadním způsobem zjednodušuje získávání dat z databáze bez nutnosti psát SQL dotazy. +Explorer nabízí intuitivní a efektivní způsob práce s databází. Stará se automaticky o vazby mezi tabulkami a optimalizaci dotazů, takže se můžete soustředit na svou aplikaci. Funguje ihned bez nastavování. Pokud potřebujete plnou kontrolu nad SQL dotazy, můžete využít [SQL přístup |SQL way]. -- pokládá efektivní dotazy -- nepřenáší zbytečná data -- má elegantní syntax +- Práce s daty je přirozená a snadno pochopitelná +- Generuje optimalizované SQL dotazy, které načítají pouze potřebná data +- Umožňuje snadný přístup k souvisejícím datům bez nutnosti psát JOIN dotazy +- Funguje okamžitě bez jakékoliv konfigurace či generování entit </div> -Používání Database Explorer začíná od tabulky a to zavoláním metody `table()` nad objektem [api:Nette\Database\Explorer]. Jak ho nejsnadněji získat je [popsáno tady |core#Připojení a konfigurace], pokud však používáme Nette Database Explorer samostatně, lze jej [vytvořit i ručně|#Ruční vytvoření Explorer]. + +S Explorerem začnete voláním metody `table()` objektu [api:Nette\Database\Explorer] (detaily k připojení najdete v kapitole [Připojení a konfigurace |guide#Připojení a konfigurace]): ```php -$books = $explorer->table('book'); // jméno tabulky je 'book' +$books = $explorer->table('book'); // 'book' je jméno tabulky ``` -Vrací nám objekt [Selection |api:Nette\Database\Table\Selection], nad kterým můžeme iterovat a projít tak všechny knihy. Řádky jsou instance [ActiveRow |api:Nette\Database\Table\ActiveRow] a data z nich můžeme přímo číst. +Metoda vrací objekt [Selection |api:Nette\Database\Table\Selection], který představuje SQL dotaz. Na tento objekt můžeme navazovat další metody pro filtrování a řazení výsledků. Dotaz se sestaví a spustí až ve chvíli, kdy začneme požadovat data. Například procházením cyklem `foreach`. Každý řádek je reprezentován objektem [ActiveRow |api:Nette\Database\Table\ActiveRow]: ```php foreach ($books as $book) { - echo $book->title; - echo $book->author_id; + echo $book->title; // výpis sloupce 'title' + echo $book->author_id; // výpis sloupce 'author_id' } ``` -Výběr jednoho konkrétního řádku se provádí pomocí metody `get()`, která vrací přímo instanci ActiveRow. +Explorer zásadním způsobem usnadňuje práci s [vazbami mezi tabulkami |#Vazby mezi tabulkami]. Následující příklad ukazuje, jak snadno můžeme vypsat data z provázaných tabulek (knihy a jejich autoři). Všimněte si, že nemusíme psát žádné JOIN dotazy, Nette je vytvoří za nás: ```php -$book = $explorer->table('book')->get(2); // vrátí knihu s id 2 -echo $book->title; -echo $book->author_id; +$books = $explorer->table('book'); + +foreach ($books as $book) { + echo 'Kniha: ' . $book->title; + echo 'Autor: ' . $book->author->name; // vytvoří JOIN na tabulku 'author' +} ``` -Pojďme si vyzkoušet jednoduchý příklad. Potřebujeme z databáze vybrat knihy a jejich autory. To je jednoduchý příklad vazby 1:N. Časté řešení je vybrat data jedním SQL dotazem se spojením tabulek pomocí JOINu. Druhou možností je vybrat data odděleně, jedním dotazem knihy, a poté pro každou knihu vybrat jejího autora (např. pomocí foreach cyklu). To může být optimalizováno do dvou požadavků do databáze, jeden pro knihy a druhý pro autory - a přesně takto to dělá Nette Database Explorer. +Nette Database Explorer optimalizuje dotazy, aby byly co nejefektivnější. Výše uvedený příklad provede pouze dva SELECT dotazy, bez ohledu na to, jestli zpracováváme 10 nebo 10 000 knih. -V níže uvedených příkladech budeme pracovat s databázovým schématem na obrázku. Jsou v něm vazby OneHasMany (1:N) (autor knihy `author_id` a případný překladatel `translator_id`, který může mít hodnotu `null`) a vazba ManyHasMany (M:N) mezi knihou a jejími tagy. +Navíc Explorer sleduje, které sloupce se v kódu používají, a načítá z databáze pouze ty, čímž šetří další výkon. Toto chování je plně automatické a adaptivní. Pokud později upravíte kód a začnete používat další sloupce, Explorer automaticky upraví dotazy. Nemusíte nic nastavovat, ani přemýšlet nad tím, které sloupce budete potřebovat - nechte to na Nette. -[Příklad včetně schématu najdete na GitHubu |https://github.com/nette-examples/books]. -[* db-schema-1-.webp *] *** Struktura databáze pro uvedené příklady .<> +Filtrování a řazení +=================== -Následující kód vypíše jméno autora každé knihy a všechny její tagy. Jak přesně to funguje si [povíme za chvíli|#Vazby mezi tabulkami]. +Třída `Selection` poskytuje metody pro filtrování a řazení výběru dat. -```php -$books = $explorer->table('book'); +.[language-php] +| `where($condition, ...$params)` | Přidá podmínku WHERE. Více podmínek je spojeno operátorem AND +| `whereOr(array $conditions)` | Přidá skupinu podmínek WHERE spojených operátorem OR +| `wherePrimary($value)` | Přidá podmínku WHERE podle primárního klíče +| `order($columns, ...$params)` | Nastaví řazení ORDER BY +| `select($columns, ...$params)` | Specifikuje sloupce, které se mají načíst +| `limit($limit, $offset = null)` | Omezí počet řádků (LIMIT) a volitelně nastaví OFFSET +| `page($page, $itemsPerPage, &$total = null)` | Nastaví stránkování +| `group($columns, ...$params)` | Seskupí řádky (GROUP BY) +| `having($condition, ...$params)` | Přidá podmínku HAVING pro filtrování seskupených řádků -foreach ($books as $book) { - echo 'title: ' . $book->title; - echo 'written by: ' . $book->author->name; // $book->author je řádek z tabulky 'author' +Metody lze řetězit (tzv. [fluent interface |nette:introduction-to-object-oriented-programming#Fluent Interfaces]): `$table->where(...)->order(...)->limit(...)`. - echo 'tags: '; - foreach ($book->related('book_tag') as $bookTag) { - echo $bookTag->tag->name . ', '; // $bookTag->tag je řádek z tabulky 'tag' - } -} -``` +V těchto metodách můžete také používat speciální notaci pro přístup k [datům ze souvisejících tabulek |#Dotazování přes související tabulky]. -Příjemně vás překvapí, jak efektivně databázová vrstva pracuje. Výše uvedený příklad provede konstantní počet požadavků, které vypadají takto: -```sql -SELECT * FROM `book` -SELECT * FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT * FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) -``` +Escapování a identifikátory +--------------------------- -Pokud použijete [cache |caching:] (ve výchozím nastavení je zapnutá), nebudou z databáze načítány žádné nepotřebné sloupce. Po prvním dotazu se do cache uloží jména použitých sloupců a dále budou z databáze vybírány pouze ty sloupce, které skutečně použijete: +Metody automaticky escapují parametry a uvozují identifikátory (názvy tabulek a sloupců), čímž zabraňuje SQL injection. Pro správné fungování je nutné dodržovat několik pravidel: -```sql -SELECT `id`, `title`, `author_id` FROM `book` -SELECT `id`, `name` FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT `book_id`, `tag_id` FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT `id`, `name` FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) +- Klíčová slova, názvy funkcí, procedur apod. pište **velkými písmeny**. +- Názvy sloupců a tabulek pište **malými písmeny**. +- Řetězce vždy dosazujte přes **parametry**. + +```php +where('name = ' . $name); // KRITICKÁ ZRANITELNOST: SQL injection +where('name LIKE "%search%"'); // ŠPATNĚ: komplikuje automatické uvozování +where('name LIKE ?', '%search%'); // SPRÁVNĚ: hodnota dosazená přes parametr + +where('name like ?', $name); // ŠPATNĚ: vygeneruje: `name` `like` ? +where('name LIKE ?', $name); // SPRÁVNĚ: vygeneruje: `name` LIKE ? +where('LOWER(name) = ?', $value);// SPRÁVNĚ: LOWER(`name`) = ? ``` -Výběry -====== +where(string|array $condition, ...$parameters): static .[method] +---------------------------------------------------------------- -Podívejme se na možnosti filtrování a omezování výběru pomocí třídy [api:Nette\Database\Table\Selection]: +Filtruje výsledky pomocí podmínek WHERE. Její silnou stránkou je inteligentní práce s různými typy hodnot a automatická volba SQL operátorů. -.[language-php] -| `$table->where($where[, $param[, ...]])` | Nastaví WHERE s použitím AND jako spojovatele při více než jedné podmínce -| `$table->whereOr($where)` | Nastaví WHERE s použitím OR jako spojovatele při více než jedné podmínce -| `$table->order($columns)` | Nastaví ORDER BY, může být výraz `('column DESC, id DESC')` -| `$table->select($columns)` | Nastaví vrácené sloupce, může být výraz `('col, MD5(col) AS hash')` -| `$table->limit($limit[, $offset])` | Nastaví LIMIT a OFFSET -| `$table->page($page, $itemsPerPage[, &$lastPage])` | Nastaví stránkování -| `$table->group($columns)` | Nastaví GROUP BY -| `$table->having($having)` | Nastaví HAVING +Základní použití: -Můžeme použít tzv. fluent interface, například `$table->where(...)->order(...)->limit(...)`. Vícenásobné `where` nebo `whereOr` podmínky je spojeny operátorem `AND`. +```php +$table->where('id', $value); // WHERE `id` = 123 +$table->where('id > ?', $value); // WHERE `id` > 123 +$table->where('id = ? OR name = ?', $id, $name); // WHERE `id` = 1 OR `name` = 'Jon Snow' +``` +Díky automatické detekci vhodných operátorů nemusíme řešit různé speciální případy. Nette je vyřeší za nás: -where() -------- +```php +$table->where('id', 1); // WHERE `id` = 1 +$table->where('id', null); // WHERE `id` IS NULL +$table->where('id', [1, 2, 3]); // WHERE `id` IN (1, 2, 3) +// lze použít i zástupný otazník bez operátoru: +$table->where('id ?', 1); // WHERE `id` = 1 +``` -Nette Database Explorer automaticky přidá vhodné operátory podle toho, jaká data dostane: +Metoda správně zpracovává i záporné podmínky a prázdné pole: -.[language-php] -| `$table->where('field', $value)` | field = $value -| `$table->where('field', null)` | field IS NULL -| `$table->where('field > ?', $val)` | field > $val -| `$table->where('field', [1, 2])` | field IN (1, 2) -| `$table->where('id = ? OR name = ?', 1, $name)` | id = 1 OR name = 'Jon Snow' -| `$table->where('field', $explorer->table($tableName))` | field IN (SELECT $primary FROM $tableName) -| `$table->where('field', $explorer->table($tableName)->select('col'))` | field IN (SELECT col FROM $tableName) +```php +$table->where('id', []); // WHERE `id` IS NULL AND FALSE -- nic nenalezne +$table->where('id NOT', []); // WHERE `id` IS NULL OR TRUE -- nalezene vše +$table->where('NOT (id ?)', []); // WHERE NOT (`id` IS NULL AND FALSE) -- nalezene vše +// $table->where('NOT id ?', $ids); Pozor - tato syntaxe není podporovaná +``` -Zástupný symbol (otazník) funguje i bez sloupcového operátoru. Následující volání jsou stejná: +Jako parametr můžeme předat také výsledek z jiné tabulky - vytvoří se poddotaz: ```php -$table->where('id = ? OR id = ?', 1, 2); -$table->where('id ? OR id ?', 1, 2); +// WHERE `id` IN (SELECT `id` FROM `tableName`) +$table->where('id', $explorer->table($tableName)); + +// WHERE `id` IN (SELECT `col` FROM `tableName`) +$table->where('id', $explorer->table($tableName)->select('col')); ``` -Díky tomu lze generovat správný operátor na základě hodnoty: +Podmínky můžeme předat také jako pole, jehož položky se spojí pomocí AND: ```php -$table->where('id ?', 2); // id = 2 -$table->where('id ?', null); // id IS NULL -$table->where('id', $ids); // id IN (...) +// WHERE (`price_final` < `price_original`) AND (`stock_count` > `min_stock`) +$table->where([ + 'price_final < price_original', + 'stock_count > min_stock', +]); ``` -Selection správně zpracovává i záporné podmínky a umí pracovat také s prázdnými poli: +V poli můžeme použít dvojice klíč => hodnota a Nette opět automaticky zvolí správné operátory: ```php -$table->where('id', []); // id IS NULL AND FALSE -$table->where('id NOT', []); // id IS NULL OR TRUE -$table->where('NOT (id ?)', $ids); // NOT (id IS NULL AND FALSE) +// WHERE (`status` = 'active') AND (`id` IN (1, 2, 3)) +$table->where([ + 'status' => 'active', + 'id' => [1, 2, 3], +]); +``` + +V poli můžeme kombinovat SQL výrazy se zástupnými otazníky a více parametry. To je vhodné pro komplexní podmínky s přesně definovanými operátory: -// toto způsobí výjimku, tato syntax není podporovaná -$table->where('NOT id ?', $ids); +```php +// WHERE (`age` > 18) AND (ROUND(`score`, 2) > 75.5) +$table->where([ + 'age > ?' => 18, + 'ROUND(score, ?) > ?' => [2, 75.5], // dva parametry předáme jako pole +]); ``` +Vícenásobné volání `where()` podmínky automaticky spojuje pomocí AND. -whereOr() ---------- -Příklad použití bez parametrů: +whereOr(array $parameters): static .[method] +-------------------------------------------- + +Podobně jako `where()` přidává podmínky, ale s tím rozdílem, že je spojuje pomocí OR: ```php -// WHERE (user_id IS NULL) OR (SUM(`field1`) > SUM(`field2`)) +// WHERE (`status` = 'active') OR (`deleted` = 1) $table->whereOr([ - 'user_id IS NULL', - 'SUM(field1) > SUM(field2)', + 'status' => 'active', + 'deleted' => true, ]); ``` -Použijeme parametry. Pokud neuvedeme operátor, Nette Database Explorer automaticky přidá vhodný: +I zde můžeme použít komplexnější výrazy: ```php -// WHERE (`field1` IS NULL) OR (`field2` IN (3, 5)) OR (`amount` > 11) +// WHERE (`price` > 1000) OR (`price_with_tax` > 1500) $table->whereOr([ - 'field1' => null, - 'field2' => [3, 5], - 'amount >' => 11, + 'price > ?' => 1000, + 'price_with_tax > ?' => 1500, ]); ``` -V klíči lze uvést výraz obsahující zástupné otazníky a v hodnotě pak předáme parametry: + +wherePrimary(mixed $key): static .[method] +------------------------------------------ + +Přidá podmínku pro primární klíč tabulky: ```php -// WHERE (`id` > 12) OR (ROUND(`id`, 5) = 3) -$table->whereOr([ - 'id > ?' => 12, - 'ROUND(id, ?) = ?' => [5, 3], -]); +// WHERE `id` = 123 +$table->wherePrimary(123); + +// WHERE `id` IN (1, 2, 3) +$table->wherePrimary([1, 2, 3]); +``` + +Pokud má tabulka kompozitní primární klíč (např. `foo_id`, `bar_id`), předáme jej jako pole: + +```php +// WHERE `foo_id` = 1 AND `bar_id` = 5 +$table->wherePrimary(['foo_id' => 1, 'bar_id' => 5])->fetch(); + +// WHERE (`foo_id`, `bar_id`) IN ((1, 5), (2, 3)) +$table->wherePrimary([ + ['foo_id' => 1, 'bar_id' => 5], + ['foo_id' => 2, 'bar_id' => 3], +])->fetchAll(); ``` -order() -------- +order(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- -Příklady použití: +Určuje pořadí, v jakém budou řádky vráceny. Můžeme řadit podle jednoho či více sloupců, v sestupném či vzestupném pořadí, nebo podle vlastního výrazu: ```php -$table->order('field1'); // ORDER BY `field1` -$table->order('field1 DESC, field2'); // ORDER BY `field1` DESC, `field2` -$table->order('field = ? DESC', 123); // ORDER BY `field` = 123 DESC +$table->order('created'); // ORDER BY `created` +$table->order('created DESC'); // ORDER BY `created` DESC +$table->order('priority DESC, created'); // ORDER BY `priority` DESC, `created` +$table->order('status = ? DESC', 'active'); // ORDER BY `status` = 'active' DESC ``` -select() --------- +select(string $columns, ...$parameters): static .[method] +--------------------------------------------------------- -Příklady použití: +Specifikuje sloupce, které se mají vrátit z databáze. Ve výchozím stavu Nette Database Explorer vrací pouze ty sloupce, které se reálně použijí v kódu. Metodu `select()` tak používáme v případech, kdy potřebujeme vrátit specifické výrazy: ```php -$table->select('field1'); // SELECT `field1` -$table->select('col, UPPER(col) AS abc'); // SELECT `col`, UPPER(`col`) AS abc -$table->select('SUBSTR(title, ?)', 3); // SELECT SUBSTR(`title`, 3) +// SELECT *, DATE_FORMAT(`created_at`, "%d.%m.%Y") AS `formatted_date` +$table->select('*, DATE_FORMAT(created_at, ?) AS formatted_date', '%d.%m.%Y'); ``` +Aliasy definované pomocí `AS` jsou pak dostupné jako vlastnosti objektu ActiveRow: + +```php +foreach ($table as $row) { + echo $row->formatted_date; // přístup k aliasu +} +``` -limit() -------- -Příklady použití: +limit(?int $limit, ?int $offset = null): static .[method] +--------------------------------------------------------- + +Omezuje počet vrácených řádků (LIMIT) a volitelně umožňuje nastavit offset: ```php -$table->limit(1); // LIMIT 1 -$table->limit(1, 10); // LIMIT 1 OFFSET 10 +$table->limit(10); // LIMIT 10 (vrátí prvních 10 řádků) +$table->limit(10, 20); // LIMIT 10 OFFSET 20 ``` +Pro stránkování je vhodnější použít metodu `page()`. + -page() ------- +page(int $page, int $itemsPerPage, &$numOfPages = null): static .[method] +------------------------------------------------------------------------- -Alternativní způsob pro nastavení limitu a offsetu: +Usnadňuje stránkování výsledků. Přijímá číslo stránky (počítané od 1) a počet položek na stránku. Volitelně lze předat referenci na proměnnou, do které se uloží celkový počet stránek: ```php -$page = 5; -$itemsPerPage = 10; -$table->page($page, $itemsPerPage); // LIMIT 10 OFFSET 40 +$numOfPages = null; +$table->page(page: 3, itemsPerPage: 10, $numOfPages); +echo "Celkem stránek: $numOfPages"; ``` -Získání čísla poslední stránky, předá se do proměnné `$lastPage`: + +group(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- + +Seskupuje řádky podle zadaných sloupců (GROUP BY). Používá se obvykle ve spojení s agregačními funkcemi: ```php -$table->page($page, $itemsPerPage, $lastPage); +// Spočítá počet produktů v každé kategorii +$table->select('category_id, COUNT(*) AS count') + ->group('category_id'); ``` -group() -------- +having(string $having, ...$parameters): static .[method] +-------------------------------------------------------- -Příklady použití: +Nastavuje podmínku pro filtrování seskupených řádků (HAVING). Lze ji použít ve spojení s metodou `group()` a agregačními funkcemi: ```php -$table->group('field1'); // GROUP BY `field1` -$table->group('field1, field2'); // GROUP BY `field1`, `field2` +// Nalezne kategorie, které mají více než 100 produktů +$table->select('category_id, COUNT(*) AS count') + ->group('category_id') + ->having('count > ?', 100); ``` -having() --------- +Čtení dat +========= + +Pro čtení dat z databáze máme k dispozici několik užitečných metod: + +.[language-php] +| `foreach ($table as $key => $row)` | Iteruje přes všechny řádky, `$key` je hodnota primárního klíče, `$row` je objekt ActiveRow +| `$row = $table->get($key)` | Vrátí jeden řádek podle primárního klíče +| `$row = $table->fetch()` | Vrátí aktuální řádek a posune ukazatel na další +| `$array = $table->fetchPairs()` | Vytvoří asociativní pole z výsledků +| `$array = $table->fetchAll()` | Vráti všechny řádky jako pole +| `count($table)` | Vrátí počet řádků v objektu Selection + +Objekt [ActiveRow |api:Nette\Database\Table\ActiveRow] je určen pouze pro čtení. To znamená, že nelze měnit hodnoty jeho properties. Toto omezení zajišťuje konzistenci dat a zabraňuje neočekávaným vedlejším efektům. Data se načítají z databáze a jakákoliv změna by měla být provedena explicitně a kontrolovaně. + -Příklady použití: +`foreach` - iterace přes všechny řádky +-------------------------------------- + +Nejsnazší způsob, jak vykonat dotaz a získat řádky, je iterováním v cyklu `foreach`. Automaticky spouští SQL dotaz. ```php -$table->having('COUNT(items) >', 100); // HAVING COUNT(`items`) > 100 +$books = $explorer->table('book'); +foreach ($books as $key => $book) { + // $key je hodnota primárního klíče, $book je ActiveRow + echo "$book->title ({$book->author->name})"; +} ``` -Výběry hodnotou z jiné tabulky .[#toc-joining-key] --------------------------------------------------- +get($key): ?ActiveRow .[method] +------------------------------- + +Vykoná SQL dotaz a vrátí řádek podle primárního klíče, nebo `null`, pokud neexistuje. -Často potřebujeme filtrovat výsledky pomocí podmínky, která zahrnuje jinou databázovou tabulku. Tento typ podmínek vyžaduje spojení tabulek, s Nette Database Explorer už je ale nikdy nemusíme psát ručně. +```php +$book = $explorer->table('book')->get(123); // vrátí ActiveRow s ID 123 nebo null +if ($book) { + echo $book->title; +} +``` -Řekněme, že chceme vybrat všechny knihy, které napsal autor jménem `Jon`. Musíme napsat pouze jméno spojovacího klíče relace a název sloupce spojené tabulky. Spojovací klíč je odvozen od jména sloupce, který odkazuje na tabulku, se kterou se chceme spojit. V našem příkladu (viz databázové schéma) je to sloupec `author_id`, ze kterého stačí použít část - `author`. `name` je název sloupce v tabulce `author`. Můžeme vytvořit podmínku také pro překladatele knihy, který je připojen sloupcem `translator_id`. + +fetch(): ?ActiveRow .[method] +----------------------------- + +Vrací řádek a posune interní ukazatel na další. Pokud už neexistují další řádky, vrací `null`. ```php $books = $explorer->table('book'); -$books->where('author.name LIKE ?', '%Jon%'); -$books->where('translator.name', 'David Grudl'); +while ($book = $books->fetch()) { + $this->processBook($book); +} ``` -Logika vytváření spojovacího klíče je dána implementací [Conventions |api:Nette\Database\Conventions]. Doporučujeme použití [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions], které analyzuje cizí klíče a umožňuje jednoduše pracovat se vztahy mezi tabulkami. -Vztah mezi knihou a autorem je 1:N. Obrácený vztah je také možný, nazýváme ho **backjoin**. Podívejme se na následující příklad. Chceme vybrat všechny autory, kteří napsali více než tři knihy. Pro vytvoření obráceného spojení použijeme `:` (dvojtečku). Dvojtečka znamená, že jde o vztah hasMany (a je to logické, dvě tečky jsou více než jedna). Bohužel třída Selection není dostatečně chytrá a musíme mu pomoci s agregací výsledků a předat mu část `GROUP BY`, také podmínka musí být zapsaná jako `HAVING`. +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Vrátí výsledky jako asociativní pole. První argument určuje název sloupce, který se použije jako klíč v poli, druhý argument určuje název sloupce, který se použije jako hodnota: ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book.id) > 3'); +$authors = $explorer->table('author')->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] +``` + +Pokud uvedeme pouze první parametr, bude hodnotou celý řadek, tedy objekt `ActiveRow`: + +```php +$authors = $explorer->table('author')->fetchPairs('id'); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` + +V případě duplicitních klíčů se použije hodnota z posledního řádku. Při použití `null` jako klíče bude pole indexováno numericky od nuly (pak ke kolizím nedochází): + +```php +$authors = $explorer->table('author')->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] ``` -Možná jste si všimli, že spojovací výraz odkazuje na `book`, ale není jasné, jestli spojujeme přes `author_id` nebo `translator_id`. Ve výše uvedeném příkladu Selection spojuje přes sloupec `author_id`, protože byla nalezena shoda se jménem zdrojové tabulky - tabulky `author`. Pokud by neexistovala shoda a existovalo více možností, Nette vyhodí výjimku [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. -Abychom mohli spojovat přes `translator_id`, stačí přidat volitelný parametr do spojovacího výrazu. +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Alternativně můžete jako parametr uvést callback, který bude pro každý řádek vracet buď samotnou hodnotu, nebo dvojici klíč-hodnota. ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book(translator).id) > 3'); +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => "$row->title ({$row->author->name})"); +// ['První kniha (Jan Novák)', ...] + +// Callback může také vracet pole s dvojicí klíč & hodnota: +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => [$row->title, $row->author->name]); +// ['První kniha' => 'Jan Novák', ...] ``` -Teď se podívejme na složitější příklad na skládání tabulek. -Chceme vybrat všechny autory, kteří napsali něco o PHP. Všechny knihy mají štítky, takže chceme vybrat všechny autory, kteří napsali knihu se štítkem 'PHP'. +fetchAll(): array .[method] +--------------------------- + +Vrátí všechny řádky jako asociativní pole objektů `ActiveRow`, kde klíče jsou hodnoty primárních klíčů. ```php -$authors = $explorer->table('author'); -$authors->where(':book:book_tags.tag.name', 'PHP') - ->group('author.id') - ->having('COUNT(:book:book_tags.tag.id) > 0'); +$allBooks = $explorer->table('book')->fetchAll(); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] ``` -Agregace výsledků ------------------ +count(): int .[method] +---------------------- -| `$table->count('*')` | Vrátí počet řádků -| `$table->count("DISTINCT $column")` | Vrátí počet odlišných hodnot -| `$table->min($column)` | Vrátí minimální hodnotu -| `$table->max($column)` | Vrátí maximální hodnotu -| `$table->sum($column)` | Vrátí součet všech hodnot -| `$table->aggregation("GROUP_CONCAT($column)")` | Pro jakoukoliv jinou agregační funkci +Metoda `count()` bez parametru vrací počet řádků v objektu `Selection`: -.[caution] -Metoda `count()` bez uvedeného parametru vybere všechny záznamy a vrátí velikost pole, což je velmi neefektivní. Pokud potřebujete například spočítat počet řádků pro stránkování, vždy první argument uveďte. +```php +$table->where('category', 1); +$count = $table->count(); +$count = count($table); // alternativa +``` + +Pozor, `count()` s parametrem provádí agregační funkci COUNT v databázi, viz níže. -Escapování a uvozovky -===================== +ActiveRow::toArray(): array .[method] +------------------------------------- -Database Explorer umí chytře escapovat parametry a identifikátory. Pro správnou funkčnost je ale nutno dodržovat několik pravidel: +Převede objekt `ActiveRow` na asociativní pole, kde klíče jsou názvy sloupců a hodnoty jsou odpovídající data. -- klíčová slova, názvy funkcí, procedur apod. psát velkými písmeny -- názvy sloupečků a tabulek psát malými písmeny -- hodnoty dosazovat přes parametry +```php +$book = $explorer->table('book')->get(1); +$bookArray = $book->toArray(); +// $bookArray bude ['id' => 1, 'title' => '...', 'author_id' => ..., ...] +``` + + +Agregace +======== + +Třída `Selection` poskytuje metody pro snadné provádění agregačních funkcí (COUNT, SUM, MIN, MAX, AVG atd.). + +.[language-php] +| `count($expr)` | Spočítá počet řádků +| `min($expr)` | Vrátí minimální hodnotu ve sloupci +| `max($expr)` | Vrátí maximální hodnotu ve sloupci +| `sum($expr)` | Vrátí součet hodnot ve sloupci +| `aggregation($function)` | Umožňuje provést libovolnou agregační funkci. Např. `AVG()`, `GROUP_CONCAT()` + + +count(string $expr): int .[method] +---------------------------------- + +Provede SQL dotaz s funkcí COUNT a vrátí výsledek. Metoda se používá k zjištění, kolik řádků odpovídá určité podmínce: + +```php +$count = $table->count('*'); // SELECT COUNT(*) FROM `table` +$count = $table->count('DISTINCT column'); // SELECT COUNT(DISTINCT `column`) FROM `table` +``` + +Pozor, [#count()] bez parametru pouze vrací počet řádků v objektu `Selection`. + + +min(string $expr) a max(string $expr) .[method] +----------------------------------------------- + +Metody `min()` a `max()` vrací minimální a maximální hodnotu ve specifikovaném sloupci nebo výrazu: ```php -->where('name like ?', 'John'); // ŠPATNĚ! vygeneruje: `name` `like` ? -->where('name LIKE ?', 'John'); // SPRÁVNĚ +// SELECT MAX(`price`) FROM `products` WHERE `active` = 1 +$maxPrice = $products->where('active', true) + ->max('price'); +``` -->where('KEY = ?', $value); // ŠPATNĚ! KEY je klíčové slovo -->where('key = ?', $value); // SPRÁVNĚ. vygeneruje: `key` = ? -->where('name = ' . $name); // ŠPATNĚ! sql injection! -->where('name = ?', $name); // SPRÁVNĚ +sum(string $expr) .[method] +--------------------------- -->select('DATE_FORMAT(created, "%d.%m.%Y")'); // ŠPATNĚ! hodnoty dosazujeme přes parametr -->select('DATE_FORMAT(created, ?)', '%d.%m.%Y'); // SPRÁVNĚ +Vrací součet hodnot ve specifikovaném sloupci nebo výrazu: + +```php +// SELECT SUM(`price` * `items_in_stock`) FROM `products` WHERE `active` = 1 +$totalPrice = $products->where('active', true) + ->sum('price * items_in_stock'); ``` -.[warning] -Špatné použití může vést k bezpečnostním dírám v aplikaci. +aggregation(string $function, ?string $groupFunction = null) .[method] +---------------------------------------------------------------------- -Čtení dat -========= +Umožňuje provést libovolnou agregační funkci. -| `foreach ($table as $id => $row)` | Iteruje přes všechny řádky výsledku -| `$row = $table->get($id)` | Vrátí jeden řádek s ID $id -| `$row = $table->fetch()` | Vrátí další řádek výsledku -| `$array = $table->fetchPairs($key, $value)` | Vrátí všechny výsledky jako asociativní pole -| `$array = $table->fetchPairs($key)` | Vrátí všechny řádky jako asociativní pole -| `count($table)` | Vrátí počet řádků výsledku +```php +// průměrná cena produktů v kategorii +$avgPrice = $products->where('category_id', 1) + ->aggregation('AVG(price)'); + +// spojí štítky produktu do jednoho řetězce +$tags = $products->where('id', 1) + ->aggregation('GROUP_CONCAT(tag.name) AS tags') + ->fetch() + ->tags; +``` + +Pokud potřebujeme agregovat výsledky, které už samy o sobě vzešly z nějaké agregační funkce a seskupení (např. `SUM(hodnota)` přes seskupené řádky), jako druhý argument uvedeme agregační funkci, která se má na tyto mezivýsledky aplikovat: + +```php +// Vypočítá celkovou cenu produktů na skladě pro jednotlivé kategorie a poté sečte tyto ceny dohromady. +$totalPrice = $products->select('category_id, SUM(price * stock) AS category_total') + ->group('category_id') + ->aggregation('SUM(category_total)', 'SUM'); +``` + +V tomto příkladu nejprve vypočítáme celkovou cenu produktů v každé kategorii (`SUM(price * stock) AS category_total`) a seskupíme výsledky podle `category_id`. Poté použijeme `aggregation('SUM(category_total)', 'SUM')` k sečtení těchto mezisoučtů `category_total`. Druhý argument `'SUM'` říká, že se má na mezivýsledky aplikovat funkce SUM. Insert, Update & Delete ======================= -Metoda `insert()` přijímá pole nebo Traversable objekty (například [ArrayHash |utils:arrays#ArrayHash] se kterým pracují [formuláře |forms:]): +Nette Database Explorer zjednodušuje vkládání, aktualizaci a mazání dat. Všechny uvedené metody v případě vyhodí výjimku `Nette\Database\DriverException`. + + +Selection::insert(iterable $data) .[method] +------------------------------------------- + +Vloží nové záznamy do tabulky. + +**Vkládání jednoho záznamu:** + +Nový záznam předáme jako asociativní pole nebo iterable objekt (například ArrayHash používaný ve [formulářích |forms:]), kde klíče odpovídají názvům sloupců v tabulce. + +Pokud má tabulka definovaný primární klíč, metoda vrací objekt `ActiveRow`, který se znovunačte z databáze, aby se zohlednily případné změny provedené na úrovni databáze (triggery, výchozí hodnoty sloupců, výpočty auto-increment sloupců). Tím je zajištěna konzistence dat a objekt vždy obsahuje aktuální data z databáze. Pokud jednoznačný primární klíč nemá, vrací předaná data ve formě pole. ```php $row = $explorer->table('users')->insert([ - 'name' => $name, - 'year' => $year, + 'name' => 'John Doe', + 'email' => 'john.doe@example.com', ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) +// $row je instance ActiveRow a obsahuje kompletní data vloženého řádku, +// včetně automaticky generovaného ID a případných změn provedených triggery +echo $row->id; // Vypíše ID nově vloženého uživatele +echo $row->created_at; // Vypíše čas vytvoření, pokud je nastaven triggerem ``` -Má-li tabulka definovaný primární klíč, vrací nový řádek jako objekt ActiveRow. +**Vkládání více záznamů najednou:** -Vícenásobný insert: +Metoda `insert()` umožňuje vložit více záznamů pomocí jednoho SQL dotazu. V tomto případě vrací počet vložených řádků. ```php -$explorer->table('users')->insert([ +$insertedRows = $explorer->table('users')->insert([ + [ + 'name' => 'John', + 'year' => 1994, + ], [ - 'name' => 'Jim', - 'year' => 1978, - ], [ 'name' => 'Jack', - 'year' => 1987, + 'year' => 1995, ], ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) +// INSERT INTO `users` (`name`, `year`) VALUES ('John', 1994), ('Jack', 1995) +// $insertedRows bude 2 +``` + +Jako parametr lze také předat objekt `Selection` s výběrem dat. + +```php +$newUsers = $explorer->table('potential_users') + ->where('approved', 1) + ->select('name, email'); + +$insertedRows = $explorer->table('users')->insert($newUsers); ``` -Jako parametry můžeme předávat i soubory nebo objekty DateTime: +**Vkládání speciálních hodnot:** + +Jako hodnoty můžeme předávat i soubory, objekty DateTime nebo SQL literály: ```php $explorer->table('users')->insert([ - 'name' => $name, - 'created' => new DateTime, // nebo $explorer::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // vloží soubor + 'name' => 'John', + 'created_at' => new DateTime, // převede na databázový formát + 'avatar' => fopen('image.jpg', 'rb'), // vloží binární obsah souboru + 'uuid' => $explorer::literal('UUID()'), // zavolá funkci UUID() ]); ``` -Úprava záznamů (vrací počet změněných řádků): + +Selection::update(iterable $data): int .[method] +------------------------------------------------ + +Aktualizuje řádky v tabulce podle zadaného filtru. Vrací počet skutečně změněných řádků. + +Měněné sloupce předáme jako asociativní pole nebo iterable objekt (například ArrayHash používaný ve [formulářích |forms:]), kde klíče odpovídají názvům sloupců v tabulce: ```php -$count = $explorer->table('users') - ->where('id', 10) // musí se volat před update() +$affected = $explorer->table('users') + ->where('id', 10) ->update([ - 'name' => 'Ned Stark' + 'name' => 'John Smith', + 'year' => 1994, ]); -// UPDATE `users` SET `name`='Ned Stark' WHERE (`id` = 10) +// UPDATE `users` SET `name` = 'John Smith', `year` = 1994 WHERE `id` = 10 ``` -Pro update můžeme využít operátorů `+=` a `-=`: +Pro změnu číselných hodnot můžeme použít operátory `+=` a `-=`: ```php $explorer->table('users') + ->where('id', 10) ->update([ - 'age+=' => 1, // všimněte si += + 'points+=' => 1, // zvýší hodnotu sloupce 'points' o 1 + 'coins-=' => 1, // sníží hodnotu sloupce 'coins' o 1 ]); -// UPDATE users SET `age` = `age` + 1 +// UPDATE `users` SET `points` = `points` + 1, `coins` = `coins` - 1 WHERE `id` = 10 ``` -Mazání záznamů (vrací počet smazaných řádků): + +Selection::delete(): int .[method] +---------------------------------- + +Maže řádky z tabulky podle zadaného filtru. Vrací počet smazaných řádků. ```php $count = $explorer->table('users') ->where('id', 10) ->delete(); -// DELETE FROM `users` WHERE (`id` = 10) +// DELETE FROM `users` WHERE `id` = 10 ``` +.[caution] +Při volání `update()` a `delete()` nezapomeňte pomocí `where()` specifikovat řádky, které se mají upravit/smazat. Pokud `where()` nepoužijete, operace se provede na celé tabulce! -Vazby mezi tabulkami -==================== +ActiveRow::update(iterable $data): bool .[method] +------------------------------------------------- -Relace Has one --------------- -Relace has one je velmi běžná. Kniha *má jednoho* autora. Kniha *má jednoho* překladatele. Řádek, který je ve vztahu has one získáme pomocí metody `ref()`. Ta přijímá dva argumenty: jméno cílové tabulky a název spojovacího sloupce. Viz příklad: +Aktualizuje data v databázovém řádku reprezentovaném objektem `ActiveRow`. Jako parametr přijímá iterable s daty, která se mají aktualizovat (klíče jsou názvy sloupců). Pro změnu číselných hodnot můžeme použít operátory `+=` a `-=`: + +Po provedení aktualizace se `ActiveRow` automaticky znovu načte z databáze, aby se zohlednily případné změny provedené na úrovni databáze (např. triggery). Metoda vrací true pouze pokud došlo ke skutečné změně dat. ```php -$book = $explorer->table('book')->get(1); -$book->ref('author', 'author_id'); +$article = $explorer->table('article')->get(1); +$article->update([ + 'views += 1', // zvýšíme počet zobrazení +]); +echo $article->views; // Vypíše aktuální počet zobrazení ``` -V příkladu výše vybíráme souvisejícího autora z tabulky `author`. Primární klíč tabulky `author` je hledán podle sloupce `book.author_id`. Metoda `ref()` vrací instanci `ActiveRow` nebo `null`, pokud hledaný záznam neexistuje. Vrácený řádek je instance `ActiveRow`, takže s ním můžeme pracovat stejně jako se záznamem knihy. +Tato metoda aktualizuje pouze jeden konkrétní řádek v databázi. Pro hromadnou aktualizaci více řádků použijte metodu [#Selection::update()]. + + +ActiveRow::delete() .[method] +----------------------------- + +Smaže řádek z databáze, který je reprezentován objektem `ActiveRow`. ```php -$author = $book->ref('author', 'author_id'); -$author->name; -$author->born; +$book = $explorer->table('book')->get(1); +$book->delete(); // Smaže knihu s ID 1 +``` + +Tato metoda maže pouze jeden konkrétní řádek v databázi. Pro hromadné smazání více řádků použijte metodu [#Selection::delete()]. + + +Vazby mezi tabulkami +==================== + +V relačních databázích jsou data rozdělena do více tabulek a navzájem propojená pomocí cizích klíčů. Nette Database Explorer přináší revoluční způsob, jak s těmito vazbami pracovat - bez psaní JOIN dotazů a nutnosti cokoliv konfigurovat nebo generovat. + +Pro ilustraci práce s vazbami použijeme příklad databáze knih ([najdete jej na GitHubu |https://github.com/nette-examples/books]). V databázi máme tabulky: + +- `author` - spisovatelé a překladatelé (sloupce `id`, `name`, `web`, `born`) +- `book` - knihy (sloupce `id`, `author_id`, `translator_id`, `title`, `sequel_id`) +- `tag` - štítky (sloupce `id`, `name`) +- `book_tag` - vazební tabulka mezi knihami a štítky (sloupce `book_id`, `tag_id`) + +[* db-schema-1-.webp *] *** Struktura databáze .<> + +V našem příkladu databáze knih najdeme několik typů vztahů (byť model je zjednodušený oproti realitě): -// nebo přímo -$book->ref('author', 'author_id')->name; -$book->ref('author', 'author_id')->born; +- One-to-many 1:N – každá kniha **má jednoho** autora, autor může napsat **několik** knih +- Zero-to-many 0:N – kniha **může mít** překladatele, překladatel může přeložit **několik** knih +- Zero-to-one 0:1 – kniha **může mít** další díl +- Many-to-many M:N – kniha **může mít několik** tagů a tag může být přiřazen **několika** knihám + +V těchto vztazích vždy existuje tabulka nadřazená a podřízená. Například ve vztahu mezi autorem a knihou je tabulka `author` nadřazená a `book` podřízená - můžeme si to představit tak, že kniha vždy "patří" nějakému autorovi. To se projevuje i ve struktuře databáze: podřízená tabulka `book` obsahuje cizí klíč `author_id`, který odkazuje na nadřazenou tabulku `author`. + +Potřebujeme-li vypsat knihy včetně jmen jejich autorů, máme dvě možnosti. Buď data získáme jediným SQL dotazem pomocí JOIN: + +```sql +SELECT book.*, author.name FROM book LEFT JOIN author ON book.author_id = author.id +``` + +Nebo načteme data ve dvou krocích - nejprve knihy a pak jejich autory - a potom je v PHP poskládáme: + +```sql +SELECT * FROM book; +SELECT * FROM author WHERE id IN (1, 2, 3); -- ids autorů získaných knih ``` -Kniha má také jednoho překladatele, jeho jméno získáme snadno. +Druhý přístup je ve skutečnosti efektivnější, i když to může být překvapivé. Data jsou načtena pouze jednou a mohou být lépe využita v cache. Právě tímto způsobem pracuje Nette Database Explorer - vše řeší pod povrchem a vám nabízí elegantní API: + ```php -$book->ref('author', 'translator_id')->name +$books = $explorer->table('book'); +foreach ($books as $book) { + echo 'title: ' . $book->title; + echo 'written by: ' . $book->author->name; // $book->author je záznam z tabulky 'author' + echo 'translated by: ' . $book->translator?->name; +} ``` -Tento přístup je funkční, ale pořád trochu zbytečně těžkopádný, nemyslíte? Databáze už obsahuje definice cizích klíčů, tak proč je nepoužít automaticky. Pojďme to vyzkoušet. -Pokud přistoupíme k členské proměnné, která neexistuje, ActiveRow se pokusí použít jméno této proměnné pro relaci 'has one'. Čtení této proměnné je stejné jako volání metody `ref()` pouze s jedním parametrem. Tomuto parametru budeme říkat **klíč**. Tento klíč bude použit pro vyhledání cizího klíče v tabulce. Předaný klíč je porovnán se sloupci, a pokud odpovídá pravidlům, je cizí klíč na daném sloupci použit pro čtení dat z příbuzné tabulky. Viz příklad: +Přístup k nadřazené tabulce +--------------------------- + +Přístup k nadřazené tabulce je přímočarý. Jde o vztahy jako *kniha má autora* nebo *kniha může mít překladatele*. Související záznam získáme přes property objektu ActiveRow - její název odpovídá názvu sloupce s cizím klíčem bez `id`: ```php -$book->author->name; -// je stejné jako -$book->ref('author')->name; +$book = $explorer->table('book')->get(1); +echo $book->author->name; // najde autora podle sloupce author_id +echo $book->translator?->name; // najde překladatele podle translator_id ``` -Instance ActiveRow nemá žádný sloupec `author`. Všechny sloupce tabulky `book` jsou prohledány na shodu s *klíčem*. Shoda v tomto případě znamená, že jméno sloupce musí obsahovat klíč. V příkladu výše sloupec `author_id` obsahuje řetězec 'author' a tedy odpovídá klíči 'author'. Pokud chceme přistoupit k záznamu překladatele, obdobným způsobem použijeme klíč 'translator', protože bude odpovídat sloupci `translator_id`. Více o logice párování klíčů si můžete přečíst v části [Joining expressions |#joining-key]. +Když přistoupíme k property `$book->author`, Explorer v tabulce `book` hledá sloupec, jehož název obsahuje řetězec `author` (tedy `author_id`). Podle hodnoty v tomto sloupci načte odpovídající záznam z tabulky `author` a vrátí jej jako `ActiveRow`. Podobně funguje i `$book->translator`, který využije sloupec `translator_id`. Protože sloupec `translator_id` může obsahovat `null`, použijeme v kódu operátor `?->`. + +Alternativní cestu nabízí metoda `ref()`, která přijímá dva argumenty, název cílové tabulky a název spojovacího sloupce, a vrací instanci `ActiveRow` nebo `null`: ```php -echo $book->title . ': '; -echo $book->author->name; -if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; -} +echo $book->ref('author', 'author_id')->name; // vazba na autora +echo $book->ref('author', 'translator_id')->name; // vazba na překladatele ``` -Pokud chceme získat autora více knih, použijeme stejný přístup. Nette Database Explorer za nás z databáze záznamy autorů a překladatelů pro všechny knihy najednou. +Metoda `ref()` se hodí, pokud nelze použít přístup přes property, protože tabulka obsahuje sloupec se stejným názvem (tj. `author`). V ostatních případech je doporučeno používat přístup přes property, který je čitelnější. + +Explorer automaticky optimalizuje databázové dotazy. Když procházíme knihy v cyklu a přistupujeme k jejich souvisejícím záznamům (autorům, překladatelům), Explorer negeneruje dotaz pro každou knihu zvlášť. Místo toho provede pouze jeden SELECT pro každý typ vazby, čímž výrazně snižuje zátěž databáze. Například: ```php $books = $explorer->table('book'); foreach ($books as $book) { echo $book->title . ': '; echo $book->author->name; - if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; - } + echo $book->translator?->name; } ``` -Tento kód zavolá pouze tyto tři dotazy do databáze: +Tento kód zavolá pouze tyto tři bleskové dotazy do databáze: + ```sql SELECT * FROM `book`; SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- id ze sloupce author_id vybraných knih SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- id ze sloupce translator_id vybraných knih ``` +.[note] +Logika dohledávání spojovacího sloupce je dána implementací [Conventions |api:Nette\Database\Conventions]. Doporučujeme použití [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions], které analyzuje cizí klíče a umožňuje jednoduše pracovat s existujícími vztahy mezi tabulkami. -Relace Has many ---------------- -Relace 'has many' je pouze obrácená 'has one' relace. Autor napsal několik (*many*) knih. Autor přeložil několik (*many*) knih. Tento typ relace je obtížnější, protože vztah je pojmenovaný ('napsal', 'přeložil'). ActiveRow má metodu `related()`, která vrací pole souvisejících záznamů. Záznamy jsou opět instance ActiveRow. Viz příklad: +Přístup k podřízené tabulce +--------------------------- + +Přístup k podřízené tabulce funguje v opačném směru. Nyní se ptáme *jaké knihy napsal tento autor* nebo *přeložil tento překladatel*. Pro tento typ dotazu používáme metodu `related()`, která vrátí `Selection` se souvisejícími záznamy. Podívejme se na příklad: ```php -$author = $explorer->table('author')->get(11); -echo $author->name . ' napsal:'; +$author = $explorer->table('author')->get(1); +// Vypíše všechny knihy od autora foreach ($author->related('book.author_id') as $book) { - echo $book->title; + echo "Napsal: $book->title"; } -echo 'a přeložil:'; +// Vypíše všechny knihy, které autor přeložil foreach ($author->related('book.translator_id') as $book) { - echo $book->title; + echo "Přeložil: $book->title"; } ``` -Metoda `related()` přijímá popis spojení jako dva argumenty, nebo jako jeden argument spojený tečkou. První argument je cílová tabulka, druhý je sloupec. +Metoda `related()` přijímá popis spojení jako jeden argument s tečkovou notací nebo jako dva samostatné argumenty: ```php -$author->related('book.translator_id'); -// je stejné jako -$author->related('book', 'translator_id'); +$author->related('book.translator_id'); // jeden argument +$author->related('book', 'translator_id'); // dva argumenty ``` -Můžeme použít heuristiku Nette Database Explorer založenou na cizích klíčích a použít pouze **klíč**. Klíč bude porovnán s cizími klíči, které odkazují do aktuální tabulky (tabulka `author`). Pokud je nalezena shoda, Nette Database Explorer použije tento cizí klíč, v opačném případě vyhodí výjimku [Nette\InvalidArgumentException |api:Nette\InvalidArgumentException] nebo [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. Více o logice párování klíčů si můžete přečíst v části [Joining expressions |#joining-key]. +Explorer dokáže automaticky detekovat správný spojovací sloupec na základě názvu nadřazené tabulky. V tomto případě se spojuje přes sloupec `book.author_id`, protože název zdrojové tabulky je `author`: -Metodu `related()` může samozřejmě volat na všechny získané autory a Nette Database Explorer načte všechny odpovídající knihy najednou. +```php +$author->related('book'); // použije book.author_id +``` + +Pokud by existovalo více možných spojení, Explorer vyhodí výjimku [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. + +Metodu `related()` můžeme samozřejmě použít i při procházení více záznamů v cyklu a Explorer i v tomto případě automaticky optimalizuje dotazy: ```php $authors = $explorer->table('author'); foreach ($authors as $author) { echo $author->name . ' napsal:'; foreach ($author->related('book') as $book) { - $book->title; + echo $book->title; } } ``` -Příklad uvedený výše spustí pouze tyto dva dotazy do databáze: +Tento kód vygeneruje pouze dva bleskové SQL dotazy: ```sql SELECT * FROM `author`; @@ -533,18 +802,111 @@ SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- id vybraných autorů ``` -Ruční vytvoření Explorer -======================== +Vazba Many-to-many +------------------ + +Pro vazbu many-to-many (M:N) je potřeba existence vazební tabulky (v našem případě `book_tag`), která obsahuje dva sloupce s cizími klíči (`book_id`, `tag_id`). Každý z těchto sloupců odkazuje na primární klíč jedné z propojovaných tabulek. Pro získání souvisejících dat nejprve získáme záznamy z vazební tabulky pomocí `related('book_tag')` a dále pokračujeme k cílovým datům: + +```php +$book = $explorer->table('book')->get(1); +// vypíše názvy tagů přiřazených ke knize +foreach ($book->related('book_tag') as $bookTag) { + echo $bookTag->tag->name; // vypíše název tagu přes vazební tabulku +} + +$tag = $explorer->table('tag')->get(1); +// nebo opačně: vypíše názvy knih označených tímto tagem +foreach ($tag->related('book_tag') as $bookTag) { + echo $bookTag->book->title; // vypíše název knihy +} +``` + +Explorer opět optimalizuje SQL dotazy do efektivní podoby: + +```sql +SELECT * FROM `book`; +SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 2, ...)); -- id vybraných knih +SELECT * FROM `tag` WHERE (`tag`.`id` IN (1, 2, ...)); -- id tagů nalezených v book_tag +``` + -Pokud jsme si vytvořili databázové spojení pomocí aplikační konfigurace, nemusíme se o nic starat. Vytvořila se nám totiž i služba typu `Nette\Database\Explorer`, kterou si můžeme předat pomocí DI. +Dotazování přes související tabulky +----------------------------------- -Pokud ale používáme Nette Database Explorer samostatně, musíme instanci `Nette\Database\Explorer` vytvořit ručně. +V metodách `where()`, `select()`, `order()` a `group()` můžeme používat speciální notace pro přístup k sloupcům z jiných tabulek. Explorer automaticky vytvoří potřebné JOINy. + +**Tečková notace** (`nadřazená_tabulka.sloupec`) se používá pro vztah 1:N z pohledu podřízené tabulky: ```php -// $storage obsahuje implementaci Nette\Caching\Storage, např.: -$storage = new Nette\Caching\Storages\FileStorage($tempDir); -$connection = new Nette\Database\Connection($dsn, $user, $password); -$structure = new Nette\Database\Structure($connection, $storage); -$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); -$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +$books = $explorer->table('book'); + +// Najde knihy, jejichž autor má jméno začínající na 'Jon' +$books->where('author.name LIKE ?', 'Jon%'); + +// Seřadí knihy podle jména autora sestupně +$books->order('author.name DESC'); + +// Vypíše název knihy a jméno autora +$books->select('book.title, author.name'); ``` + +**Dvojtečková notace** (`:podřízená_tabulka.sloupec`) se používá pro vztah 1:N z pohledu nadřazené tabulky: + +```php +$authors = $explorer->table('author'); + +// Najde autory, kteří napsali knihu s 'PHP' v názvu +$authors->where(':book.title LIKE ?', '%PHP%'); + +// Spočítá počet knih pro každého autora +$authors->select('*, COUNT(:book.id) AS book_count') + ->group('author.id'); +``` + +Ve výše uvedeném příkladu s dvojtečkovou notací (`:book.title`) není specifikován sloupec s cizím klíčem. Explorer automaticky detekuje správný sloupec na základě názvu nadřazené tabulky. V tomto případě se spojuje přes sloupec `book.author_id`, protože název zdrojové tabulky je `author`. Pokud by existovalo více možných spojení, Explorer vyhodí výjimku [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. + +Spojovací sloupec lze explicitně uvést v závorce: + +```php +// Najde autory, kteří přeložili knihu s 'PHP' v názvu +$authors->where(':book(translator_id).title LIKE ?', '%PHP%'); +``` + +Notace lze řetězit pro přístup přes více tabulek: + +```php +// Najde autory knih označených tagem 'PHP' +$authors->where(':book:book_tag.tag.name', 'PHP') + ->group('author.id'); +``` + + +Rozšíření podmínek pro JOIN +--------------------------- + +Metoda `joinWhere()` rozšiřuje podmínky, které se uvádějí při propojování tabulek v SQL za klíčovým slovem `ON`. + +Dejme tomu, že chceme najít knihy přeložené konkrétním překladatelem: + +```php +// Najde knihy přeložené překladatelem jménem 'David' +$books = $explorer->table('book') + ->joinWhere('translator', 'translator.name', 'David'); +// LEFT JOIN author translator ON book.translator_id = translator.id AND (translator.name = 'David') +``` + +V podmínce `joinWhere()` můžeme používat stejné konstrukce jako v metodě `where()` - operátory, zástupné otazníky, pole hodnot či SQL výrazy. + +Pro složitější dotazy s více JOINy můžeme definovat aliasy tabulek: + +```php +$tags = $explorer->table('tag') + ->joinWhere(':book_tag.book.author', 'book_author.born < ?', 1950) + ->alias(':book_tag.book.author', 'book_author'); +// LEFT JOIN `book_tag` ON `tag`.`id` = `book_tag`.`tag_id` +// LEFT JOIN `book` ON `book_tag`.`book_id` = `book`.`id` +// LEFT JOIN `author` `book_author` ON `book`.`author_id` = `book_author`.`id` +// AND (`book_author`.`born` < 1950) +``` + +Všimněte si, že zatímco metoda `where()` přidává podmínky do klauzule `WHERE`, metoda `joinWhere()` rozšiřuje podmínky v klauzuli `ON` při spojování tabulek. diff --git a/database/cs/guide.texy b/database/cs/guide.texy new file mode 100644 index 0000000000..f70849ed6c --- /dev/null +++ b/database/cs/guide.texy @@ -0,0 +1,216 @@ +Nette Database +************** + +.[perex] +Nette Database je výkonná a elegantní databázová vrstva pro PHP s důrazem na jednoduchost a chytré funkce. Nabízí dva způsoby práce s databází - [Explorer] pro rychlý vývoj aplikací, nebo [SQL přístup |SQL way] pro přímou práci s dotazy. + +<div class="grid gap-3"> +<div> + + +[SQL přístup |SQL way] +====================== +- Bezpečné parametrizované dotazy +- Přesná kontrola nad podobou SQL dotazů +- Když píšete komplexní dotazy s pokročilými funkcemi +- Optimalizujete výkon pomocí specifických SQL funkcí + +</div> + +<div> + + +[Explorer] +========== +- Vyvíjíte rychle bez psaní SQL +- Intuitivní práce s relacemi mezi tabulkami +- Oceníte automatickou optimalizaci dotazů +- Vhodné pro rychlou a pohodlnout práci s databází + +</div> + +</div> + + +Instalace +========= + +Knihovnu stáhnete a nainstalujete pomocí nástroje [Composer|best-practices:composer]: + +```shell +composer require nette/database +``` + + +Podporované databáze +==================== + +Nette Database podporuje následující databáze: + +|* Databázový server |* DSN jméno |* Podpora v Explorer +|---------------------|-------------|----------------------- +| MySQL (>= 5.1) | mysql | ANO +| PostgreSQL (>= 9.0) | pgsql | ANO +| Sqlite 3 (>= 3.8) | sqlite | ANO +| Oracle | oci | - +| MS SQL (PDO_SQLSRV) | sqlsrv | ANO +| MS SQL (PDO_DBLIB) | mssql | - +| ODBC | odbc | - + + +Dva přístupy k databázi +======================= + +Nette Database vám dává na výběr: můžete buď psát SQL dotazy přímo (SQL přístup), nebo je nechat generovat automaticky (Explorer). Podívejme se, jak oba přístupy řeší stejné úkoly: + +[SQL přístup|sql way] - SQL dotazy + +```php +// vložení záznamu +$database->query('INSERT INTO books', [ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// získání záznamů: autoři knih +$result = $database->query(' + SELECT authors.*, COUNT(books.id) AS books_count + FROM authors + LEFT JOIN books ON authors.id = books.author_id + WHERE authors.active = 1 + GROUP BY authors.id +'); + +// výpis (není optimální, generuje N dalších dotazů) +foreach ($result as $author) { + $books = $database->query(' + SELECT * FROM books + WHERE author_id = ? + ORDER BY published_at DESC + ', $author->id); + + echo "Autor $author->name napsal $author->books_count knih:\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +[Explorer přístup|explorer] - automatické generování SQL + +```php +// vložení záznamu +$database->table('books')->insert([ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// získání záznamů: autoři knih +$authors = $database->table('authors') + ->where('active', 1); + +// výpis (automaticky generuje jen 2 optimalizované dotazy) +foreach ($authors as $author) { + $books = $author->related('books') + ->order('published_at DESC'); + + echo "Autor $author->name napsal {$books->count()} knih:\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +Explorer přístup generuje a optimalizuje SQL dotazy automaticky. V uvedeném příkladu SQL přístup vygeneruje N+1 dotazů (jeden pro autory a pak jeden pro knihy každého autora), zatímco Explorer automaticky optimalizuje dotazy a provede pouze dva - jeden pro autory a jeden pro všechny jejich knihy. + +Oba přístupy lze v aplikaci libovolně kombinovat podle potřeby. + + +Připojení a konfigurace +======================= + +Pro připojení k databázi stačí vytvořit instanci třídy [api:Nette\Database\Connection]: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password); +``` + +Parametr `$dsn` (data source name) je stejný, [jaký používá PDO |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], např. `host=127.0.0.1;dbname=test`. V případě selhání vyhodí výjimku `Nette\Database\ConnectionException`. + +Nicméně šikovnější způsob nabízí [aplikační konfigurace |configuration], kam stačí přidat sekci `database` a vytvoří se potřebné objekty a také databázový panel v [Tracy |tracy:] baru. + +```neon +database: + dsn: 'mysql:host=127.0.0.1;dbname=test' + user: root + password: password +``` + +Poté objekt spojení [získáme jako službu z DI kontejneru |dependency-injection:passing-dependencies], např.: + +```php +class Model +{ + public function __construct( + // nebo Nette\Database\Explorer + private Nette\Database\Connection $database, + ) { + } +} +``` + +Více informací o [konfiguraci databáze|configuration]. + + +Ruční vytvoření Explorer +------------------------ + +Pokud nepoužíváte Nette DI kontejner, můžete instanci `Nette\Database\Explorer` vytvořit ručně: + +```php +// připojení k databázi +$connection = new Nette\Database\Connection('mysql:host=127.0.0.1;dbname=mydatabase', 'user', 'password'); +// úložiště pro cache, implementuje Nette\Caching\Storage, např.: +$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp/dir'); +// stará se o reflexi databázové struktury +$structure = new Nette\Database\Structure($connection, $storage); +// definuje pravidla pro mapování názvů tabulek, sloupců a cizích klíčů +$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); +$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +``` + + +Správa připojení +================ + +Při vytvoření objektu `Connection` dojde automaticky k připojení. Pokud chcete připojení odložit, použijte lazy režim - ten zapnete v [konfiguraci|configuration] nastavením `lazy`, nebo takto: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password, ['lazy' => true]); +``` + +Pro správu připojení využijte metody `connect()`, `disconnect()` a `reconnect()`. +- `connect()` vytvoří připojení, pokud ještě neexistuje, přičemž může vyvolat výjimku `Nette\Database\ConnectionException`. +- `disconnect()` odpojí aktuální připojení k databázi. +- `reconnect()` provede odpojení a následné znovu připojení k databázi. Tato metoda může rovněž vyvolat výjimku `Nette\Database\ConnectionException`. + +Kromě toho můžete sledovat události spojené s připojením pomocí události `onConnect`, což je pole callbacků, které se zavolají po navázání spojení s databází. + +```php +// proběhne po připojení k databázi +$database->onConnect[] = function($database) { + echo "Připojeno k databázi"; +}; +``` + + +Tracy Debug Bar +=============== + +Pokud používáte [Tracy |tracy:], aktivuje se automaticky panel Database v Debug baru, který zobrazuje všechny provedené dotazy, jejich parametry, dobu vykonání a místo v kódu, kde byly zavolány. + +[* db-panel.webp *] diff --git a/database/cs/mapping.texy b/database/cs/mapping.texy new file mode 100644 index 0000000000..07bf97e6a9 --- /dev/null +++ b/database/cs/mapping.texy @@ -0,0 +1,55 @@ +Konverze typů +************* + +.[perex] +Nette Database automaticky konvertuje hodnoty vrácené z databáze na odpovídající PHP typy. + + +Datum a čas +----------- + +Časové údaje jsou převáděny na objekty `Nette\Utils\DateTime`. Pokud chcete, aby byly časové údaje převáděny na immutable objekty `Nette\Database\DateTime`, nastavte v [konfiguraci|configuration] volbu `newDateTime` na true. + +```php +$row = $database->fetch('SELECT created_at FROM articles'); +echo $row->created_at instanceof DateTime; // true +echo $row->created_at->format('j. n. Y'); +``` + +V případě MySQL převádí datový typ `TIME` na objekty `DateInterval`. + + +Booleovské hodnoty +------------------ + +Booleovské hodnoty jsou automaticky převedeny na `true` nebo `false`. U MySQL se převádí `TINYINT(1)` pokud nastavíme v [konfiguraci|configuration] `convertBoolean`. + +```php +$row = $database->fetch('SELECT is_published FROM articles'); +echo gettype($row->is_published); // 'boolean' +``` + + +Číselné hodnoty +--------------- + +Číselné hodnoty jsou převedeny na `int` nebo `float` podle typu sloupce v databázi: + +```php +$row = $database->fetch('SELECT id, price FROM products'); +echo gettype($row->id); // integer +echo gettype($row->price); // float +``` + + +Vlastní normalizace +------------------- + +Pomocí metody `setRowNormalizer(?callable $normalizer)` můžete nastavit vlastní funkci pro transformaci řádků z databáze. To se hodí například pro automatický převod datových typů. + +```php +$database->setRowNormalizer(function(array $row, ResultSet $resultSet): array { + // tady proběhne konverze typů + return $row; +}); +``` diff --git a/database/cs/reflection.texy b/database/cs/reflection.texy new file mode 100644 index 0000000000..43a83b88d3 --- /dev/null +++ b/database/cs/reflection.texy @@ -0,0 +1,125 @@ +Reflexe struktury +***************** + +.{data-version:3.2.1} +Nette Database poskytuje nástroje pro introspekci databázové struktury pomocí třídy [api:Nette\Database\Reflection]. Ta umožňuje získávat informace o tabulkách, sloupcích, indexech a cizích klíčích. Reflexi můžete využít ke generování schémat, vytváření flexibilních aplikací pracujících s databází nebo obecných databázových nástrojů. + +Objekt reflexe získáme z instance připojení k databázi: + +```php +$reflection = $database->getReflection(); +``` + + +Získání tabulek +--------------- + +Readonly vlastnost `$reflection->tables` obsahuje asociativní pole všech tabulek v databázi: + +```php +// Výpis názvů všech tabulek +foreach ($reflection->tables as $name => $table) { + echo $name . "\n"; +} +``` + +K dispozici jsou ještě dvě metody: + +```php +// Ověření existence tabulky +if ($reflection->hasTable('users')) { + echo "Tabulka users existuje"; +} + +// Vrátí objekt tabulky; pokud neexistuje, vyhodí výjimku +$table = $reflection->getTable('users'); +``` + + +Informace o tabulce +------------------- + +Tabulka je reprezentována objektem [Table|api:Nette\Database\Reflection\Table], který poskytuje následující readonly vlastnosti: + +- `$name: string` – název tabulky +- `$view: bool` – zda se jedná o pohled +- `$fullName: ?string` – plný název tabulky včetně schématu (pokud existuje) +- `$columns: array<string, Column>` – asociativní pole sloupců tabulky +- `$indexes: Index[]` – pole indexů tabulky +- `$primaryKey: ?Index` – primární klíč tabulky nebo null +- `$foreignKeys: ForeignKey[]` – pole cizích klíčů tabulky + + +Sloupce +------- + +Vlastnost `columns` tabulky poskytuje asociativní pole sloupců, kde klíčem je název sloupce a hodnotou instance [Column|api:Nette\Database\Reflection\Column] s těmito vlastnostmi: + +- `$name: string` – název sloupce +- `$table: ?Table` – reference na tabulku sloupce +- `$nativeType: string` – nativní databázový typ +- `$size: ?int` – velikost/délka typu +- `$nullable: bool` – zda může sloupec obsahovat NULL +- `$default: mixed` – výchozí hodnota sloupce +- `$autoIncrement: bool` – zda je sloupec auto-increment +- `$primary: bool` – zda je součástí primárního klíče +- `$vendor: array` – dodatečná metadata specifická pro daný databázový systém + +```php +foreach ($table->columns as $name => $column) { + echo "Sloupec: $name\n"; + echo "Typ: {$column->nativeType}\n"; + echo "Nullable: " . ($column->nullable ? 'Ano' : 'Ne') . "\n"; +} +``` + + +Indexy +------ + +Vlastnost `indexes` tabulky poskytuje pole indexů, kde každý index je instance [Index|api:Nette\Database\Reflection\Index] s těmito vlastnostmi: + +- `$columns: Column[]` – pole sloupců tvořících index +- `$unique: bool` – zda je index unikátní +- `$primary: bool` – zda jde o primární klíč +- `$name: ?string` – název indexu + +Primární klíč tabulky lze získat pomocí vlastnosti `primaryKey`, která vrací buď objekt `Index`, nebo `null` v případě, že tabulka nemá primární klíč. + +```php +// Výpis indexů +foreach ($table->indexes as $index) { + $columns = implode(', ', array_map(fn($col) => $col->name, $index->columns)); + echo "Index" . ($index->name ? " {$index->name}" : '') . ":\n"; + echo " Sloupce: $columns\n"; + echo " Unique: " . ($index->unique ? 'Ano' : 'Ne') . "\n"; +} + +// Výpis primárního klíče +if ($primaryKey = $table->primaryKey) { + $columns = implode(', ', array_map(fn($col) => $col->name, $primaryKey->columns)); + echo "Primární klíč: $columns\n"; +} +``` + + +Cizí klíče +---------- + +Vlastnost `foreignKeys` tabulky poskytuje pole cizích klíčů, kde každý cizí klíč je instance [ForeignKey|api:Nette\Database\Reflection\ForeignKey] s těmito vlastnostmi: + +- `$foreignTable: Table` – odkazovaná tabulka +- `$localColumns: Column[]` – pole lokálních sloupců +- `$foreignColumns: Column[]` – pole odkazovaných sloupců +- `$name: ?string` – název cizího klíče + +```php +// Výpis cizích klíčů +foreach ($table->foreignKeys as $fk) { + $localCols = implode(', ', array_map(fn($col) => $col->name, $fk->localColumns)); + $foreignCols = implode(', ', array_map(fn($col) => $col->name, $fk->foreignColumns)); + + echo "FK" . ($fk->name ? " {$fk->name}" : '') . ":\n"; + echo " $localCols -> {$fk->foreignTable->name}($foreignCols)\n"; +} +``` diff --git a/database/cs/security.texy b/database/cs/security.texy new file mode 100644 index 0000000000..89077c54a2 --- /dev/null +++ b/database/cs/security.texy @@ -0,0 +1,185 @@ +Bezpečnostní rizika +******************* + +<div class=perex> + +Databáze často obsahuje citlivá data a umožňuje provádět nebezpečné operace. Pro bezpečnou práci s Nette Database je klíčové: + +- Porozumět rozdílu mezi bezpečným a nebezpečným API +- Používat parametrizované dotazy +- Správně validovat vstupní data + +</div> + + +Co je SQL Injection? +==================== + +SQL injection je nejzávažnější bezpečnostní riziko při práci s databází. Vzniká, když se neošetřený vstup od uživatele stane součástí SQL dotazu. Útočník může vložit vlastní SQL příkazy a tím: +- Získat neoprávněný přístup k datům +- Modifikovat nebo smazat data v databázi +- Obejít autentizaci + +```php +// ❌ NEBEZPEČNÝ KÓD - zranitelný vůči SQL injection +$database->query("SELECT * FROM users WHERE name = '$_GET[name]'"); + +// Útočník může zadat například hodnotu: ' OR '1'='1 +// Výsledný dotaz pak bude: SELECT * FROM users WHERE name = '' OR '1'='1' +// Což vrátí všechny uživatele +``` + +Totéž se týká i Database Explorer: + +```php +// ❌ NEBEZPEČNÝ KÓD - zranitelný vůči SQL injection +$table->where('name = ' . $_GET['name']); +$table->where("name = '$_GET[name]'"); +``` + + +Parametrizované dotazy +====================== + +Základní obranou proti SQL injection jsou parametrizované dotazy. Nette Database nabízí několik způsobů jejich použití. + +Nejjednodušší způsob je použití **zástupných otazníků**: + +```php +// ✅ Bezpečný parametrizovaný dotaz +$database->query('SELECT * FROM users WHERE name = ?', $name); + +// ✅ Bezpečná podmínka v Exploreru +$table->where('name = ?', $name); +``` + +Tohle platí pro všechny další metody v [Database Explorer|explorer], které umožňují vkládat výrazy se zástupnými otazníky a parametry. + +Pro příkazy INSERT, UPDATE nebo klauzuli WHERE můžeme předat hodnoty v poli: + +```php +// ✅ Bezpečný INSERT +$database->query('INSERT INTO users', [ + 'name' => $name, + 'email' => $email, +]); + +// ✅ Bezpečný INSERT v Exploreru +$table->insert([ + 'name' => $name, + 'email' => $email, +]); +``` + + +Validace hodnot parametrů +========================= + +Parametrizované dotazy jsou základním stavebním kamenem bezpečné práce s databází. Avšak hodnoty, které do nich vkládáme, musí projít několika úrovněmi kontrol: + + +Typová kontrola +--------------- + +**Nejdůležitější je zajistit správný datový typ parametrů** - to je nutná podmínka pro bezpečné použití Nette Database. Databáze předpokládá, že všechna vstupní data mají správný datový typ odpovídající danému sloupci. + +Například pokud by `$name` v předchozích příkladech bylo neočekávaně pole místo řetězce, Nette Database by se pokusilo vložit všechny jeho prvky do SQL dotazu, což by vedlo k chybě. Proto **nikdy nepoužívejte** nevalidovaná data z `$_GET`, `$_POST` nebo `$_COOKIE` přímo v databázových dotazech. + + +Formátová kontrola +------------------ + +Na druhé úrovni kontrolujeme formát dat - například zda jsou řetězce v UTF-8 kódování a jejich délka odpovídá definici sloupce, nebo zda jsou číselné hodnoty v povoleném rozsahu pro daný datový typ sloupce. + +U této úrovně validace se můžeme částečně spolehnout i na databázi samotnou - mnoho databází odmítne nevalidní data. Nicméně chování se může lišit, některé mohou dlouhé řetězce tiše zkrátit nebo čísla mimo rozsah oříznout. + + +Doménová kontrola +----------------- + +Třetí úroveň představují logické kontroly specifické pro vaši aplikaci. Například ověření, že hodnoty ze select boxů odpovídají nabízeným možnostem, že čísla jsou v očekávaném rozsahu (např. věk 0-150 let) nebo že vzájemné závislosti mezi hodnotami dávají smysl. + + +Doporučené způsoby validace +--------------------------- + +- Používejte [Nette Formuláře|forms:], které automaticky zajistí správnou validaci všech vstupů +- Používejte [Presentery|application:] a uvádějte u parametrů v `action*()` a `render*()` metodách datové typy +- Nebo implementujte vlastní validační vrstvu pomocí standardních PHP nástrojů jako `filter_var()` + + +Bezpečná práce se sloupci +========================= + +V předchozí sekci jsme si ukázali, jak správně validovat hodnoty parametrů. Při použití polí v SQL dotazech však musíme věnovat stejnou pozornost i jejich klíčům. + +```php +// ❌ NEBEZPEČNÝ KÓD - nejsou ošetřené klíče v poli +$database->query('INSERT INTO users', $_POST); +``` + +U příkazů INSERT a UPDATE je to zásadní bezpečnostní chyba - útočník může do databáze vložit nebo změnit jakýkoliv sloupec. Mohl by si například nastavit `is_admin = 1` nebo vložit libovolná data do citlivých sloupců (tzv Mass Assignment Vulnerability). + +Ve WHERE podmínkách je to ještě nebezpečnější, protože mohou obsahovat operátory: + +```php +// ❌ NEBEZPEČNÝ KÓD - nejsou ošetřené klíče v poli +$_POST['salary >'] = 100000; +$database->query('SELECT * FROM users WHERE', $_POST); +// vykoná dotaz WHERE (`salary` > 100000) +``` + +Útočník může tento přístup využít k systematickému zjišťování platů zaměstnanců. Začne například dotazem na platy nad 100.000, pak pod 50.000 a postupným zužováním rozsahu může odhalit přibližné platy všech zaměstnanců. Tento typ útoku se nazývá SQL enumeration. + +Metody `where()` a `whereOr()` jsou ještě [mnohem flexibilnější |explorer#where] a podporují v klíčích a hodnotách SQL výrazy včetně operátorů a funkcí. To dává útočníkovi možnost provést SQL injection: + +```php +// ❌ NEBEZPEČNÝ KÓD - útočník může vložit vlastní SQL +$_POST = ['0) UNION SELECT name, salary FROM users WHERE (1']; +$table->where($_POST); +// vykoná dotaz WHERE (0) UNION SELECT name, salary FROM users WHERE (1) +``` + +Tento útok ukončí původní podmínku pomocí `0)`, připojí vlastní `SELECT` pomocí `UNION` aby získal citlivá data z tabulky `users` a uzavře syntakticky správný dotaz pomocí `WHERE (1)`. + + +Whitelist sloupců +----------------- + +Pro bezpečnou práci s názvy sloupců potřebujeme mechanismus, který zajistí, že uživatel může pracovat pouze s povolenými sloupci a nemůže přidat vlastní. Mohli bychom se pokusit detekovat a blokovat nebezpečné názvy sloupců (blacklist), ale tento přístup je nespolehlivý - útočník může vždy přijít s novým způsobem, jak nebezpečný název sloupce zapsat, který jsme nepředvídali. + +Proto je mnohem bezpečnější obrátit logiku a definovat explicitní seznam povolených sloupců (whitelist): + +```php +// Sloupce, které může uživatel upravovat +$allowedColumns = ['name', 'email', 'active']; + +// Odstraníme všechny nepovolené sloupce ze vstupu +$filteredData = array_intersect_key($userData, array_flip($allowedColumns)); + +// ✅ Nyní můžeme bezpečně použít v dotazech, jako například: +$database->query('INSERT INTO users', $filteredData); +$table->update($filteredData); +$table->where($filteredData); +``` + + +Dynamické identifikátory +======================== + +Pro dynamické názvy tabulek a sloupců použijte zástupný symbol `?name`. Ten zajistí správné escapování identifikátorů podle syntaxe dané databáze (např. pomocí zpětných uvozovek v MySQL): + +```php +// ✅ Bezpečné použití důvěryhodných identifikátorů +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name', $column, $table); +// Výsledek v MySQL: SELECT `name` FROM `users` +``` + +Důležité: symbol `?name` používejte pouze pro důvěryhodné hodnoty definované v kódu aplikace. Pro hodnoty od uživatele použijte opět [whitelist |#Whitelist sloupců]. Jinak se vystavujete bezpečnostním rizikům: + +```php +// ❌ NEBEZPEČNÉ - nikdy nepoužívejte vstup od uživatele +$database->query('SELECT ?name FROM users', $_GET['column']); +``` diff --git a/database/cs/sql-way.texy b/database/cs/sql-way.texy new file mode 100644 index 0000000000..5838a7478c --- /dev/null +++ b/database/cs/sql-way.texy @@ -0,0 +1,513 @@ +SQL přístup +*********** + +.[perex] +Nette Database nabízí dvě cesty: můžete psát SQL dotazy sami (SQL přístup), nebo je nechat generovat automaticky (viz [Explorer |explorer]). SQL přístup vám dává plnou kontrolu nad dotazy a přitom zajišťuje jejich bezpečné sestavení. + +.[note] +Detaily k připojení a konfiguraci databáze najdete v kapitole [Připojení a konfigurace |guide#Připojení a konfigurace]. + + +Základní dotazování +=================== + +Pro dotazování do databáze slouží metoda `query()`. Ta vrací objekt [ResultSet |api:Nette\Database\ResultSet], který reprezentuje výsledek dotazu. V případě selhání metoda [vyhodí výjimku|exceptions]. Výsledek dotazu můžeme procházet pomocí cyklu `foreach`, nebo použít některou z [pomocných funkcí |#Získání dat]. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; +} +``` + +Pro bezpečné vkládání hodnot do SQL dotazů používáme parametrizované dotazy. Nette Database je dělá maximálně jednoduché - stačí za SQL dotaz přidat čárku a hodnotu: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Při více parametrech máte dvě možnosti zápisu. Buď můžete SQL dotaz "prokládat" parametry: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age); +``` + +Nebo napsat nejdříve celý SQL dotaz a pak připojit všechny parametry: + +```php +$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age); +``` + + +Ochrana před SQL injection +========================== + +Proč je důležité používat parametrizované dotazy? Protože vás chrání před útokem zvaným SQL injection, při kterém by útočník mohl podstrčit vlastní SQL příkazy a tím získat nebo poškodit data v databázi. + +.[warning] +**Nikdy nevkládejte proměnné přímo do SQL dotazu!** Vždy používejte parametrizované dotazy, které vás ochrání před SQL injection. + +```php +// ❌ NEBEZPEČNÝ KÓD - zranitelný vůči SQL injection +$database->query("SELECT * FROM users WHERE name = '$name'"); + +// ✅ Bezpečný parametrizovaný dotaz +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Seznamte se s [možnými bezpečnostními riziky |security]. + + +Techniky dotazování +=================== + + +Podmínky WHERE +-------------- + +Podmínky WHERE můžete zapsat jako asociativní pole, kde klíče jsou názvy sloupců a hodnoty jsou data pro porovnání. Nette Database automaticky vybere nejvhodnější SQL operátor podle typu hodnoty. + +```php +$database->query('SELECT * FROM users WHERE', [ + 'name' => 'John', + 'active' => true, +]); +// WHERE `name` = 'John' AND `active` = 1 +``` + +V klíči můžete také explicitně specifikovat operátor pro porovnání: + +```php +$database->query('SELECT * FROM users WHERE', [ + 'age >' => 25, // použije operátor > + 'name LIKE' => '%John%', // použije operátor LIKE + 'email NOT LIKE' => '%example.com%', // použije operátor NOT LIKE +]); +// WHERE `age` > 25 AND `name` LIKE '%John%' AND `email` NOT LIKE '%example.com%' +``` + +Nette automaticky ošetřuje speciální případy jako `null` hodnoty nebo pole. + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name' => 'Laptop', // použije operátor = + 'category_id' => [1, 2, 3], // použije IN + 'description' => null, // použije IS NULL +]); +// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL +``` + +Pro negativní podmínky použijte operátor `NOT`: + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name NOT' => 'Laptop', // použije operátor <> + 'category_id NOT' => [1, 2, 3], // použije NOT IN + 'description NOT' => null, // použije IS NOT NULL + 'id' => [], // vynechá se +]); +// WHERE `name` <> 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL +``` + +Pro spojování podmínek se používá operátor `AND`. To lze změnit pomocí [zástupného symbolu ?or |#Hinty pro sestavování SQL]. + + +Pravidla ORDER BY +----------------- + +Řazení `ORDER BY` se dá zapsat pomocí pole. V klíčích uvedeme sloupce a hodnotou bude boolean určující, zda řadit vzestupně: + +```php +$database->query('SELECT id FROM author ORDER BY', [ + 'id' => true, // vzestupně + 'name' => false, // sestupně +]); +// SELECT id FROM author ORDER BY `id`, `name` DESC +``` + + +Vkládání dat (INSERT) +--------------------- + +Pro vkládání záznamů se používá SQL příkaz `INSERT`. + +```php +$values = [ + 'name' => 'John Doe', + 'email' => 'john@example.com', +]; +$database->query('INSERT INTO users ?', $values); +$userId = $database->getInsertId(); +``` + +Metoda `getInsertId()` vrátí ID naposledy vloženého řádku. U některých databází (např. PostgreSQL) je nutné jako parametr specifikovat název sekvence, ze které se má ID generovat pomocí `$database->getInsertId($sequenceId)`. + +Jako parametry můžeme předávat i [#speciální hodnoty] jako soubory, objekty DateTime nebo výčtové typy. + +Vložení více záznamů najednou: + +```php +$database->query('INSERT INTO users ?', [ + ['name' => 'User 1', 'email' => 'user1@mail.com'], + ['name' => 'User 2', 'email' => 'user2@mail.com'], +]); +``` + +Vícenásobný INSERT je mnohem rychlejší, protože se provede jediný databázový dotaz, namísto mnoha jednotlivých. + +**Bezpečnostní upozornění:** Nikdy nepoužívejte jako `$values` nevalidovaná data. Seznamte se s [možnými riziky |security#Bezpečná práce se sloupci]. + + +Aktualizace dat (UPDATE) +------------------------ + +Pro aktualizacizáznamů se používá SQL příkaz `UPDATE`. + +```php +// Aktualizace jednoho záznamu +$values = [ + 'name' => 'John Smith', +]; +$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1); +``` + +Počet ovlivněných řádků vrátí `$result->getRowCount()`. + +Pro UPDATE můžeme využít operátorů `+=` a `-=`: + +```php +$database->query('UPDATE users SET ? WHERE id = ?', [ + 'login_count+=' => 1, // inkrementace login_count +], 1); +``` + +Příklad vložení, nebo úpravy záznamu, pokud již existuje. Použijeme techniku `ON DUPLICATE KEY UPDATE`: + +```php +$values = [ + 'name' => $name, + 'year' => $year, +]; +$database->query('INSERT INTO users ? ON DUPLICATE KEY UPDATE ?', + $values + ['id' => $id], + $values, +); +// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) +// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 +``` + +Všimněte si, že Nette Database pozná, v jakém kontextu SQL příkazu parametr s polem vkládáme a podle toho z něj sestaví SQL kód. Takže z prvního pole sestavil `(id, name, year) VALUES (123, 'Jim', 1978)`, zatímco druhé převedl do podoby `name = 'Jim', year = 1978`. Podroběji se tomu věnujeme v části [#Hinty pro sestavování SQL]. + + +Mazání dat (DELETE) +------------------- + +Pro mazání záznamů se používá SQL příkaz `DELETE`. Příklad se získáním počtu smazaných řádků: + +```php +$count = $database->query('DELETE FROM users WHERE id = ?', 1) + ->getRowCount(); +``` + + +Hinty pro sestavování SQL +------------------------- + +Hint je speciální zástupný symbol v SQL dotazu, který říká, jak se má hodnota parametru přepsat do SQL výrazu: + +| Hint | Popis | Automaticky se použije +|-----------|-------------------------------------------------|----------------------------- +| `?name` | použije pro vložení názvu tabulky nebo sloupce | - +| `?values` | vygeneruje `(key, ...) VALUES (value, ...)` | `INSERT ... ?`, `REPLACE ... ?` +| `?set` | vygeneruje přiřazení `key = value, ...` | `SET ?`, `KEY UPDATE ?` +| `?and` | spojí podmínky v poli operátorem `AND` | `WHERE ?`, `HAVING ?` +| `?or` | spojí podmínky v poli operátorem `OR` | - +| `?order` | vygeneruje klauzuli `ORDER BY` | `ORDER BY ?`, `GROUP BY ?` + +Pro dynamické vkládání názvů tabulek a sloupců do dotazu slouží zástupný symbol `?name`. Nette Database se postará o správné ošetření identifikátorů podle konvencí dané databáze (např. uzavření do zpětných uvozovek v MySQL). + +```php +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table); +// SELECT `name` FROM `users` WHERE id = 1 (v MySQL) +``` + +**Upozornění:** symbol `?name` používejte pouze pro názvy tabulek a sloupců z validovaných vstupů, jinak se vystavujete [bezpečnostnímu riziku |security#Dynamické identifikátory]. + +Ostatní hinty obvykle není potřeba uvádět, neboť Nette používá při skládání SQL dotazu chytrou autodetekci (viz třetí sloupec tabulky). Ale můžete jej využít například v situaci, kdy chcete spojit podmínky pomocí `OR` namísto `AND`: + +```php +$database->query('SELECT * FROM users WHERE ?or', [ + 'name' => 'John', + 'email' => 'john@example.com', +]); +// SELECT * FROM users WHERE `name` = 'John' OR `email` = 'john@example.com' +``` + + +Speciální hodnoty +----------------- + +Kromě běžných skalárních typů (string, int, bool) můžete jako parametry předávat i speciální hodnoty: + +- soubory: `fopen('image.gif', 'r')` vloží binární obsah souboru +- datum a čas: objekty `DateTime` se převedou na databázový formát +- výčtové typy: instance `enum` se převedou na jejich hodnotu +- SQL literály: vytvořené pomocí `Connection::literal('NOW()')` se vloží přímo do dotazu + +```php +$database->query('INSERT INTO articles ?', [ + 'title' => 'My Article', + 'published_at' => new DateTime, + 'content' => fopen('image.png', 'r'), + 'state' => Status::Draft, +]); +``` + +U databází, které nemají nativní podporu pro datový typ `datetime` (jako SQLite a Oracle), se `DateTime` převádí na hodnotu určenou v [konfiguraci databáze|configuration] položkou `formatDateTime` (výchozí hodnota je `U` - unix timestamp). + + +SQL literály +------------ + +V některých případech potřebujete jako hodnotu uvést přímo SQL kód, který se ale nemá chápat jako řetězec a escapovat. K tomuto slouží objekty třídy `Nette\Database\SqlLiteral`. Vytváří je metoda `Connection::literal()`. + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + 'year >' => $database::literal('YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) +``` + +Nebo alternativě: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) +``` + +SQL literály mohou obsahovat parametry: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > ? AND year < ?', $min, $max), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) +``` + +Díky čemuž můžeme vytvářet zajímavé kombinace: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('?or', [ + 'active' => true, + 'role' => $role, + ]), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') +``` + + +Získání dat +=========== + + +Zkratky pro SELECT dotazy +------------------------- + +Pro zjednodušení načítání dat nabízí `Connection` několik zkratek, které kombinují volání `query()` s následujícím `fetch*()`. Tyto metody přijímají stejné parametry jako `query()`, tedy SQL dotaz a volitelné parametry. Plnohodnotný popis metod `fetch*()` najdete [níže |#fetch]. + +| `fetch($sql, ...$params): ?Row` | Provede dotaz a vrátí první řádek jako objekt `Row` +| `fetchAll($sql, ...$params): array` | Provede dotaz a vrátí všechny řádky jako pole objektů `Row` +| `fetchPairs($sql, ...$params): array` | Provede dotaz a vrátí asocitivní pole, kde první sloupec představuje klíč a druhý hodnotu +| `fetchField($sql, ...$params): mixed` | Provede dotaz a vrátí hodnotu prvního políčka z prvního řádku +| `fetchList($sql, ...$params): ?array` | Provede dotaz a vrací první řádek jako indexované pole + +Příklad: + +```php +// fetchField() - vrátí hodnotu první buňky +$count = $database->query('SELECT COUNT(*) FROM articles') + ->fetchField(); +``` + + +`foreach` - iterace přes řádky +------------------------------ + +Po vykonání dotazu se vrací objekt [ResultSet|api:Nette\Database\ResultSet], který umožňuje procházet výsledky několika způsoby. Nejsnazší způsob, jak vykonat dotaz a získat řádky, je iterováním v cyklu `foreach`. Tento způsob je paměťově nejúspornější, neboť vrací data postupně a neukládá si je do paměti najednou. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; + // ... +} +``` + +.[note] +`ResultSet` lze iterovat pouze jednou. Pokud potřebujete iterovat opakovaně, musíte nejprve načíst data do pole, například pomocí metody `fetchAll()`. + + +fetch(): ?Row .[method] +----------------------- + +Vrací řádek jako objekt `Row`. Pokud už neexistují další řádky, vrací `null`. Posune interní ukazatel na další řádek. + +```php +$result = $database->query('SELECT * FROM users'); +$row = $result->fetch(); // načte první řádek +if ($row) { + echo $row->name; +} +``` + + +fetchAll(): array .[method] +--------------------------- + +Vrací všechny zbývající řádky z `ResultSetu` jako pole objektů `Row`. + +```php +$result = $database->query('SELECT * FROM users'); +$rows = $result->fetchAll(); // načte všechny řádky +foreach ($rows as $row) { + echo $row->name; +} +``` + + +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Vrátí výsledky jako asociativní pole. První argument určuje název sloupce, který se použije jako klíč v poli, druhý argument určuje název sloupce, který se použije jako hodnota: + +```php +$result = $database->query('SELECT id, name FROM users'); +$names = $result->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] +``` + +Pokud uvedeme pouze první parametr, bude hodnotou celý řádek, tedy objekt `Row`: + +```php +$rows = $result->fetchPairs('id'); +// [1 => Row(id: 1, name: 'John'), 2 => Row(id: 2, name: 'Jane'), ...] +``` + +V případě duplicitních klíčů se použije hodnota z posledního řádku. Při použití `null` jako klíče bude pole indexováno numericky od nuly (pak ke kolizím nedochází): + +```php +$names = $result->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] +``` + + +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Alternativně můžete jako parametr uvést callback, který bude pro každý řádek vracet buď samotnou hodnotu, nebo dvojici klíč-hodnota. + +```php +$result = $database->query('SELECT * FROM users'); +$items = $result->fetchPairs(fn($row) => "$row->id - $row->name"); +// ['1 - John', '2 - Jane', ...] + +// Callback může také vracet pole s dvojicí klíč & hodnota: +$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]); +// ['John' => 46, 'Jane' => 21, ...] +``` + + +fetchField(): mixed .[method] +----------------------------- + +Vrací hodnotu prvního políčka z aktuálního řádku. Pokud už neexistují další řádky, vrací `null`. Posune interní ukazatel na další řádek. + +```php +$result = $database->query('SELECT name FROM users'); +$name = $result->fetchField(); // načte jméno z prvního řádku +``` + + +fetchList(): ?array .[method] +----------------------------- + +Vrací řádek jako indexované pole. Pokud už neexistují další řádky, vrací `null`. Posune interní ukazatel na další řádek. + +```php +$result = $database->query('SELECT name, email FROM users'); +$row = $result->fetchList(); // ['John', 'john@example.com'] +``` + + +getRowCount(): ?int .[method] +----------------------------- + +Vrací počet ovlivněných řádků posledním dotazem `UPDATE` nebo `DELETE`. Pro `SELECT` je to počet vrácených řádků, ale ten nemusí být znám - v takovém případě metoda vrátí `null`. + + +getColumnCount(): ?int .[method] +-------------------------------- + +Vrací počet sloupců v `ResultSetu`. + + +Informace o dotazech +==================== + +Pro ladicí účely můžeme získat informace o posledním provedeném dotazu: + +```php +echo $database->getLastQueryString(); // vypíše SQL dotaz + +$result = $database->query('SELECT * FROM articles'); +echo $result->getQueryString(); // vypíše SQL dotaz +echo $result->getTime(); // vypíše dobu vykonání v sekundách +``` + +Pro zobrazení výsledku jako HTML tabulky lze použít: + +```php +$result = $database->query('SELECT * FROM articles'); +$result->dump(); +``` + +ResultSet nabízí informace o typech sloupců: + +```php +$result = $database->query('SELECT * FROM articles'); +$types = $result->getColumnTypes(); + +foreach ($types as $column => $type) { + echo "$column je typu $type->type"; // např. 'id je typu int' +} +``` + + +Logování dotazů +--------------- + +Můžeme implementovat vlastní logování dotazů. Událost `onQuery` je pole callbacků, které se zavolají po každém provedeném dotazu: + +```php +$database->onQuery[] = function ($database, $result) use ($logger) { + $logger->info('Query: ' . $result->getQueryString()); + $logger->info('Time: ' . $result->getTime()); + + if ($result->getRowCount() > 1000) { + $logger->warning('Large result set: ' . $result->getRowCount() . ' rows'); + } +}; +``` diff --git a/database/cs/transactions.texy b/database/cs/transactions.texy new file mode 100644 index 0000000000..e91070c2fb --- /dev/null +++ b/database/cs/transactions.texy @@ -0,0 +1,43 @@ +Transakce +********* + +.[perex] +Transakce zaručují, že se buď provedou všechny operace v rámci transakce, nebo se neprovede žádná. Jsou užitečné pro zajištění konzistence dat při složitějších operacích. + +Nejjednodušší způsob použití transakcí vypadá takto: + +```php +$database->beginTransaction(); +try { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); + $database->commit(); +} catch (\Exception $e) { + $database->rollBack(); + throw $e; +} +``` + +Mnohem elegantněji můžete to samé zapsat pomocí metody `transaction()`. Jako parametr přijímá callback, který vykoná v transakci. Pokud callback proběhne bez výjimky, transakce se automaticky potvrdí. Pokud dojde k výjimce, transakce se zruší (rollback) a výjimka se šíří dál. + +```php +$database->transaction(function ($database) use ($id) { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); +}); +``` + +Metoda `transaction()` může také vracet hodnoty: + +```php +$count = $database->transaction(function ($database) { + $result = $database->query('UPDATE users SET active = ?', true); + return $result->getRowCount(); // vrátí počet aktualizovaných řádků +}); +``` diff --git a/database/cs/upgrading.texy b/database/cs/upgrading.texy new file mode 100644 index 0000000000..20bb26d94d --- /dev/null +++ b/database/cs/upgrading.texy @@ -0,0 +1,14 @@ +Upgrade +******* + + +Přechod z verze 3.1 na 3.2 +========================== + +Minimální požadovaná verze PHP je 8.1. + +Kód byl pečlivě vyladěn pro PHP 8.1. Byly doplněny všechny nové typehinty u metod a properties. Změny jsou jen drobné: + +- MySQL: nulové datum `0000-00-00` vrací jako `null` +- MySQL: decimal bez desetinných míst vrací jako int místo float +- typ `time` vrací jako DateTime s datumem `1. 1. 0001` místo aktuálního data diff --git a/database/de/@home.texy b/database/de/@home.texy index e30b18a076..45a96f08ed 100644 --- a/database/de/@home.texy +++ b/database/de/@home.texy @@ -1,20 +1,21 @@ -Unterstützte Server -=================== +Unterstützte Datenbanken +======================== -Die folgenden Datenbankserver werden unterstützt: +Nette unterstützt die folgenden Datenbanken: -|* Datenbankserver |* DSN-Name |* Core-Unterstützung |* Explorer-Unterstützung -| MySQL (>= 5.1) | mysql | YES | YES -| PostgreSQL (>= 9.0) | pgsql | YES | YES -| Sqlite 3 (>= 3.8) | sqlite | YES | YES -| Oracle | oci | YES | - -| MS SQL (PDO_SQLSRV) | sqlsrv | JA | JA -| MS SQL (PDO_DBLIB) | mssql | YES | - -| ODBC | odbc | YES | - +|* Datenbankserver |* DSN-Name |* Unterstützung im Core |* Unterstützung im Explorer +| MySQL (>= 5.1) | mysql | JA | JA +| PostgreSQL (>= 9.0) | pgsql | JA | JA +| Sqlite 3 (>= 3.8) | sqlite | JA | JA +| Oracle | oci | JA | - +| MS SQL (PDO_SQLSRV) | sqlsrv | JA | JA +| MS SQL (PDO_DBLIB) | mssql | JA | - +| ODBC | odbc | JA | - -{{title: Nette Database}} -{{description: Nette Database vereinfacht den Abruf von Daten aus der Datenbank, ohne dass SQL-Abfragen geschrieben werden müssen. Es stellt effiziente Abfragen und überträgt keine unnötigen Daten.}} + +{{maintitle: Nette Database - awesome database layer for PHP}} +{{description: Nette Database vereinfacht das Abrufen von Daten aus der Datenbank erheblich, ohne dass SQL-Abfragen geschrieben werden müssen. Es stellt effiziente Abfragen und überträgt keine unnötigen Daten.}} diff --git a/database/de/@left-menu.texy b/database/de/@left-menu.texy index 76a8232d3d..a174fb97e9 100644 --- a/database/de/@left-menu.texy +++ b/database/de/@left-menu.texy @@ -1,5 +1,12 @@ -Datenbank -********* -- [Kern |Core] -- [Entdecker |Explorer] -- [Konfiguration |Configuration] +Nette Database +************** +- [Einführung |guide] +- [SQL-Zugriff |sql way] +- [Explorer |Explorer] +- [Transaktionen |transactions] +- [Ausnahmen |exceptions] +- [Reflexion |reflection] +- [Mapping |mapping] +- [Konfiguration |configuration] +- [Sicherheitsrisiken |security] +- [Upgrade |en:upgrading] diff --git a/database/de/@meta.texy b/database/de/@meta.texy new file mode 100644 index 0000000000..b3b806b2ca --- /dev/null +++ b/database/de/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokumentation}} diff --git a/database/de/configuration.texy b/database/de/configuration.texy index dddaadf21f..f624f1bbb5 100644 --- a/database/de/configuration.texy +++ b/database/de/configuration.texy @@ -1,61 +1,67 @@ -Datenbank konfigurieren -*********************** +Datenbankkonfiguration +********************** .[perex] -Überblick über die Konfigurationsoptionen für die Nette-Datenbank. +Übersicht der Konfigurationsoptionen für Nette Database. -Wenn Sie nicht das gesamte Framework, sondern nur diese Bibliothek verwenden, lesen Sie [, wie Sie die Konfiguration laden |bootstrap:]. +Wenn Sie nicht das gesamte Framework verwenden, sondern nur diese Bibliothek, lesen Sie, [wie die Konfiguration geladen wird|bootstrap:]. -Einzelne Verbindung .[#toc-single-connection] ---------------------------------------------- +Einzelne Verbindung +------------------- -Konfigurieren Sie eine einzelne Datenbankverbindung: +Konfiguration einer einzelnen Datenbankverbindung: ```neon database: - # DSN, nur obligatorischer Schlüssel + # DSN, einziger Pflichtschlüssel dsn: "sqlite:%appDir%/Model/demo.db" user: ... password: ... ``` -Es werden Dienste vom Typ `Nette\Database\Connection` und auch `Nette\Database\Explorer` für die [Datenbank-Explorer-Schicht |explorer] erstellt. Die Datenbankverbindung wird in der Regel per [Autowiring |dependency-injection:autowiring] übergeben, falls dies nicht möglich ist, verwenden Sie die Dienstnamen `@database.default.connection` bzw. `@database.default.explorer`. +Erstellt die Dienste `Nette\Database\Connection` und `Nette\Database\Explorer`, die wir normalerweise über [Autowiring |dependency-injection:autowiring] übergeben, oder durch Verweis auf [ihren Namen |#DI-Dienste]. -Andere Einstellungen: +Weitere Einstellungen: ```neon database: - # zeigt Datenbank-Panel in Tracy Bar? - debugger: ... # (bool) ist standardmäßig true + # Datenbankpanel in der Tracy Bar anzeigen? + debugger: ... # (bool) Standard ist true - # zeigt die Abfrage EXPLAIN in der Tracy Bar? - explain: ... # (bool) ist standardmäßig true + # EXPLAIN von Abfragen in der Tracy Bar anzeigen? + explain: ... # (bool) Standard ist true - # um die automatische Verdrahtung für diese Verbindung zu aktivieren? - autowired: ... # (bool) ist standardmäßig true für die erste Verbindung + # Autowiring für diese Verbindung zulassen? + autowired: ... # (bool) Standard ist true bei der ersten Verbindung - # Tabellenkonventionen: entdeckt, statisch oder Klassenname - conventions: discovered # (string) Standardwert ist 'discovered'. + # Tabellenkonventionen: discovered, static oder Klassenname + conventions: discovered # (string) Standard ist 'discovered' options: - # nur bei Bedarf mit der Datenbank verbinden? - lazy: ... # (bool) standardmäßig false + # Erst verbinden, wenn nötig? + lazy: ... # (bool) Standard ist false - # PHP-Datenbanktreiberklasse + # PHP-Klasse des Datenbanktreibers driverClass: # (string) # nur MySQL: setzt sql_mode sqlmode: # (string) # nur MySQL: setzt SET NAMES - charset: # (string) Standardwert ist 'utf8mb4' ('utf8' vor v5.5.3) + charset: # (string) Standard ist 'utf8mb4' - # nur Oracle und SQLite: Datumsformat - formatDateTime: # (string) Standardwert ist 'U' + # nur MySQL: konvertiert TINYINT(1) in bool + convertBoolean: # (bool) Standard ist false + + # gibt Datumsspalten als unveränderliche Objekte zurück (ab Version 3.2.1) + newDateTime: # (bool) Standard ist false + + # nur Oracle und SQLite: Format zum Speichern von Datum/Uhrzeit + formatDateTime: # (string) Standard ist 'U' ``` -Der Schlüssel `options` kann weitere Optionen enthalten, die in der [Dokumentation des PDO-Treibers |https://www.php.net/manual/en/pdo.drivers.php] zu finden sind, wie z. B.: +Im Schlüssel `options` können weitere Optionen angegeben werden, die Sie in der [Dokumentation der PDO-Treiber |https://www.php.net/manual/en/pdo.drivers.php] finden, wie zum Beispiel: ```neon database: @@ -64,10 +70,10 @@ database: ``` -Mehrere Verbindungen .[#toc-multiple-connections] -------------------------------------------------- +Mehrere Verbindungen +-------------------- -In der Konfiguration können wir mehrere Datenbankverbindungen definieren, indem wir sie in benannte Abschnitte unterteilen: +In der Konfiguration können wir auch mehrere Datenbankverbindungen definieren, indem wir sie in benannte Abschnitte unterteilen: ```neon database: @@ -80,9 +86,23 @@ database: dsn: 'sqlite::memory:' ``` -Jede definierte Verbindung erzeugt Dienste, die den Namen des Abschnitts in ihrem Namen enthalten, z. B. `@database.main.connection` & `@database.main.explorer` und weiter `@database.another.connection` & `@database.another.explorer`. +Autowiring ist nur für Dienste aus dem ersten Abschnitt aktiviert. Dies kann mit `autowired: false` oder `autowired: true` geändert werden. + + +DI-Dienste +---------- + +Diese Dienste werden dem DI-Container hinzugefügt, wobei `###` den Namen der Verbindung darstellt: + +| Name | Typ | Beschreibung +|---------------------------------------------------------- +| `database.###.connection` | [api:Nette\Database\Connection] | Verbindung zur Datenbank +| `database.###.explorer` | [api:Nette\Database\Explorer] | [Database Explorer |explorer] + + +Wenn wir nur eine Verbindung definieren, lauten die Dienstnamen `database.default.connection` und `database.default.explorer`. Wenn wir mehrere Verbindungen wie im obigen Beispiel definieren, entsprechen die Namen den Abschnitten, d.h. `database.main.connection`, `database.main.explorer` und weiter `database.another.connection` und `database.another.explorer`. -Die automatische Verdrahtung ist nur für Dienste aus dem ersten Abschnitt aktiviert. Dies kann mit `autowired: false` oder `autowired: true` geändert werden. Nicht-autoverdrahtete Dienste werden namentlich übergeben: +Nicht automatisch verdrahtete Dienste übergeben wir explizit durch Verweis auf ihren Namen: ```neon services: diff --git a/database/de/core.texy b/database/de/core.texy deleted file mode 100644 index 75b7f21349..0000000000 --- a/database/de/core.texy +++ /dev/null @@ -1,350 +0,0 @@ -Datenbank-Kern -************** - -.[perex] -Nette Database Core ist eine Datenbankabstraktionsschicht und bietet Kernfunktionen. - - -Installation .[#toc-installation] -================================= - -Laden Sie das Paket herunter und installieren Sie es mit [Composer |best-practices:composer]: - -```shell -composer require nette/database -``` - - -Verbindung und Konfiguration .[#toc-connection-and-configuration] -================================================================= - -Um eine Verbindung zur Datenbank herzustellen, erstellen Sie einfach eine Instanz der Klasse [api:Nette\Database\Connection]: - -```php -$database = new Nette\Database\Connection($dsn, $user, $password); -``` - -Der Parameter `$dsn` (Name der Datenquelle) ist [derselbe, der auch von PDO verwendet wird |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], z. B. `host=127.0.0.1;dbname=test`. Im Falle eines Fehlers wird `Nette\Database\ConnectionException` ausgelöst. - -Eine anspruchsvollere Methode bietet jedoch die [Anwendungskonfiguration |configuration]. Wir fügen einen Abschnitt `database` hinzu, der die erforderlichen Objekte und ein Datenbank-Panel in der [Tracy-Leiste |tracy:] erstellt. - -```neon -database: - dsn: 'mysql:host=127.0.0.1;dbname=test' - user: root - password: password -``` - -Das Verbindungsobjekt [erhalten wir z. B. als Dienst von einem DI-Container |dependency-injection:passing-dependencies]: - -```php -class Model -{ - // übergibt Nette\Database\Explorer, um mit der Datenbank-Explorer-Schicht zu arbeiten - public function __construct( - private Nette\Database\Connection $database, - ) { - } -} -``` - -Weitere Informationen finden Sie unter [Datenbankkonfiguration |configuration]. - - -Abfragen .[#toc-queries] -======================== - -Für Datenbankabfragen verwenden Sie die Methode `query()`, die ein [ResultSet |api:Nette\Database\ResultSet] zurückgibt. - -```php -$result = $database->query('SELECT * FROM users'); - -foreach ($result as $row) { - echo $row->id; - echo $row->name; -} - -echo $result->getRowCount(); // gibt die Anzahl der Zeilen zurück, wenn diese bekannt ist -``` - -.[note] -Über die `ResultSet` ist es möglich, nur einmal zu iterieren, wenn wir mehrere Male iterieren müssen, ist es notwendig, das Ergebnis in das Array über `fetchAll()` Methode zu konvertieren. - -Sie können der Abfrage einfach Parameter hinzufügen, beachten Sie das Fragezeichen: - -```php -$database->query('SELECT * FROM users WHERE name = ?', $name); - -$database->query('SELECT * FROM users WHERE name = ? AND active = ?', $name, $active); - -$database->query('SELECT * FROM users WHERE id IN (?)', $ids); // $ids ist array -``` -<div class=warning> - -WARNUNG, verketten Sie niemals Zeichenketten, um eine [SQL-Injection-Schwachstelle |https://en.wikipedia.org/wiki/SQL_injection] zu vermeiden! -/-- -$db->query('SELECT * FROM users WHERE name = ' . $name); // WRONG!!! -\-- -</div> - -Im Falle eines Fehlers löst `query()` entweder `Nette\Database\DriverException` oder einen seiner Abkömmlinge aus: - -- [ConstraintViolationException |api:Nette\Database\ConstraintViolationException] - Verletzung einer Beschränkung -- [ForeignKeyConstraintViolationException |api:Nette\Database\ForeignKeyConstraintViolationException] - ungültiger Fremdschlüssel -- [NotNullConstraintViolationException |api:Nette\Database\NotNullConstraintViolationException] - Verstoß gegen die NOT NULL-Bedingung -- [UniqueConstraintViolationException |api:Nette\Database\UniqueConstraintViolationException] - Konflikt eines eindeutigen Index - -Zusätzlich zu `query()` gibt es weitere nützliche Methoden: - -```php -// gibt das assoziative Array id => name zurück -$pairs = $database->fetchPairs('SELECT id, name FROM users'); - -// gibt alle Zeilen als Array zurück -$rows = $database->fetchAll('SELECT * FROM users'); - -// gibt einzelne Zeile zurück -$row = $database->fetch('SELECT * FROM users WHERE id = ?', $id); - -// gibt einzelnes Feld zurück -$name = $database->fetchField('SELECT name FROM users WHERE id = ?', $id); -``` - -Im Falle eines Fehlers werfen alle diese Methoden `Nette\Database\DriverException.` - - -Einfügen, Aktualisieren & Löschen .[#toc-insert-update-delete] -============================================================== - -Der Parameter, den wir in die SQL-Abfrage einfügen, kann auch das Array sein (in diesem Fall ist es möglich, die Platzhalteranweisung `?`), which may be useful for the `INSERT` zu überspringen: - -```php -$database->query('INSERT INTO users ?', [ // hier kann das Fragezeichen weggelassen werden - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) - -$id = $database->getInsertId(); // gibt das automatische Inkrement der eingefügten Zeile zurück - -$id = $database->getInsertId($sequence); // oder Sequenzwert -``` - -Mehrfaches Einfügen: - -```php -$database->query('INSERT INTO users', [ - [ - 'name' => 'Jim', - 'year' => 1978, - ], [ - 'name' => 'Jack', - 'year' => 1987, - ], -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) -``` - -Wir können auch Dateien, DateTime-Objekte oder [Aufzählungen |https://www.php.net/enumerations] übergeben: - -```php -$database->query('INSERT INTO users', [ - 'name' => $name, - 'created' => new DateTime, // oder $datenbank::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // fügt Dateiinhalt ein - 'status' => State::New, // enum State -]); -``` - -Zeilen aktualisieren: - -```php -$result = $database->query('UPDATE users SET', [ - 'name' => $name, - 'year' => $year, -], 'WHERE id = ?', $id); -// UPDATE users SET `name` = 'Jim', `year` = 1978 WHERE id = 123 - -echo $result->getRowCount(); // gibt die Anzahl der betroffenen Zeilen zurück -``` - -Für UPDATE können wir die Operatoren `+=` und `-=` verwenden: - -```php -$database->query('UPDATE users SET', [ - 'age+=' => 1, // note += -], 'WHERE id = ?', $id); -// UPDATE users SET `age` = `age` + 1 -``` - -Löschen: - -```php -$result = $database->query('DELETE FROM users WHERE id = ?', $id); -echo $result->getRowCount(); // gibt die Anzahl der betroffenen Zeilen zurück -``` - - -Erweiterte Abfragen .[#toc-advanced-queries] -============================================ - -Einfügen oder Aktualisieren, wenn sie bereits existiert: - -```php -$database->query('INSERT INTO users', [ - 'id' => $id, - 'name' => $name, - 'year' => $year, -], 'ON DUPLICATE KEY UPDATE', [ - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) -// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 -``` - -Beachten Sie, dass Nette Database den SQL-Kontext erkennt, in den der Array-Parameter eingefügt wird, und den SQL-Code entsprechend aufbaut. So erzeugt er aus dem ersten Array `(id, name, year) VALUES (123, 'Jim', 1978)`, während der zweite in `name = 'Jim', year = 1978` umgewandelt wird. - -Wir können auch die Sortierung mit Hilfe eines Arrays beschreiben, wobei die Schlüssel Spaltennamen und die Werte Boolesche Werte sind, die festlegen, ob in aufsteigender Reihenfolge sortiert werden soll: - -```php -$database->query('SELECT id FROM author ORDER BY', [ - 'id' => true, // aufsteigend - 'name' => false, // absteigend -]); -// SELECT id FROM author ORDER BY `id`, `name` DESC -``` - -Wenn die Erkennung nicht funktioniert hat, können Sie die Form der Baugruppe mit einem Platzhalter `?` gefolgt von einem Hinweis angeben. Diese Hinweise werden unterstützt: - -| ?values | (key1, key2, ...) VALUES (value1, value2, ...) -| ?set | key1 = wert1, key2 = wert2, ... -| ?and | Schlüssel1 = Wert1 AND Schlüssel2 = Wert2 ... -| ?or | key1 = value1 OR key2 = value2 ... -| ?order | key1 ASC, key2 DESC - -Die WHERE-Klausel verwendet den Operator `?and`, so dass die Bedingungen durch `AND` verknüpft werden: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND `year` = 1978 -``` - -Das kann leicht in `OR` geändert werden, indem man den Platzhalter `?or` verwendet: - -```php -$result = $database->query('SELECT * FROM users WHERE ?or', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' OR `year` = 1978 -``` - -Wir können Operatoren in Bedingungen verwenden: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name <>' => $name, - 'year >' => $year, -]); -// SELECT * FROM users WHERE `name` <> 'Jim' AND `year` > 1978 -``` - -Und auch Aufzählungen: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => ['Jim', 'Jack'], - 'role NOT IN' => ['admin', 'owner'], // Aufzählung + Operator NOT IN -]); -// SELECT * FROM users WHERE -// `name` IN ('Jim', 'Jack') AND `role` NOT IN ('admin', 'owner') -``` - -Wir können auch ein Stück benutzerdefinierten SQL-Code einfügen, indem wir das so genannte SQL-Literal verwenden: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year >' => $database::literal('YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) -``` - -Alternativ dazu: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) -``` - -SQL-Literal kann auch seine Parameter haben: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > ? AND year < ?', $min, $max), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) -``` - -Dadurch können wir interessante Kombinationen erstellen: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('?or', [ - 'active' => true, - 'role' => $role, - ]), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') -``` - - -Variable Name .[#toc-variable-name] -=================================== - -Es gibt einen `?name` Platzhalter, den Sie verwenden, wenn der Tabellen- oder Spaltenname eine Variable ist. (Achtung, erlauben Sie dem Benutzer nicht, den Inhalt einer solchen Variablen zu manipulieren): - -```php -$table = 'blog.users'; -$column = 'name'; -$database->query('SELECT * FROM ?name WHERE ?name = ?', $table, $column, $name); -// SELECT * FROM `blog`.`users` WHERE `name` = 'Jim' -``` - - -Vorgänge .[#toc-transactions] -============================= - -Es gibt drei Methoden für den Umgang mit Transaktionen: - -```php -$database->beginTransaction(); - -$database->commit(); - -$database->rollback(); -``` - -Einen eleganten Weg bietet die Methode `transaction()`. Sie übergeben den Callback, der in der Transaktion ausgeführt wird. Wenn während der Ausführung eine Ausnahme auftritt, wird die Transaktion abgebrochen, wenn alles gut geht, wird die Transaktion bestätigt. - -```php -$id = $database->transaction(function ($database) { - $database->query('DELETE FROM ...'); - $database->query('INSERT INTO ...'); - // ... - return $database->getInsertId(); -}); -``` - -Wie Sie sehen können, gibt die Methode `transaction()` den Rückgabewert des Callbacks zurück. - -Die Transaktion() kann auch verschachtelt werden, was die Implementierung unabhängiger Repositories vereinfacht. diff --git a/database/de/exceptions.texy b/database/de/exceptions.texy new file mode 100644 index 0000000000..9dff39187f --- /dev/null +++ b/database/de/exceptions.texy @@ -0,0 +1,34 @@ +Ausnahmen +********* + +Nette Database verwendet eine Hierarchie von Ausnahmeklassen. Die Basisklasse ist `Nette\Database\DriverException`, die von `PDOException` erbt und erweiterte Möglichkeiten zur Fehlerbehandlung bei Datenbankfehlern bietet: + +- Die Methode `getDriverCode()` gibt den Fehlercode des Datenbanktreibers zurück. +- Die Methode `getSqlState()` gibt den SQLSTATE-Code zurück. +- Die Methoden `getQueryString()` und `getParameters()` ermöglichen es, die ursprüngliche Abfrage und ihre Parameter abzurufen. + +Von `DriverException` erben die folgenden spezialisierten Ausnahmeklassen: + +- `ConnectionException` - signalisiert einen Verbindungsfehler zum Datenbankserver. +- `ConstraintViolationException` - Basisklasse für Verletzungen von Datenbankbeschränkungen, von der erben: + - `ForeignKeyConstraintViolationException` - Verletzung eines Fremdschlüssels. + - `NotNullConstraintViolationException` - Verletzung einer NOT NULL-Beschränkung. + - `UniqueConstraintViolationException` - Verletzung der Eindeutigkeit eines Wertes. + + +Beispiel für das Abfangen der Ausnahme `UniqueConstraintViolationException`, die auftritt, wenn versucht wird, einen Benutzer mit einer E-Mail-Adresse einzufügen, die bereits in der Datenbank vorhanden ist (vorausgesetzt, die `email`-Spalte hat einen UNIQUE-Index). + +```php +try { + $database->query('INSERT INTO users', [ + 'email' => 'john@example.com', + 'name' => 'John Doe', + 'password' => $hashedPassword, + ]); +} catch (Nette\Database\UniqueConstraintViolationException $e) { + echo 'Ein Benutzer mit dieser E-Mail-Adresse existiert bereits.'; + +} catch (Nette\Database\DriverException $e) { + echo 'Bei der Registrierung ist ein Fehler aufgetreten: ' . $e->getMessage(); +} +``` diff --git a/database/de/explorer.texy b/database/de/explorer.texy index f1adf4fb40..0d7f2546fd 100644 --- a/database/de/explorer.texy +++ b/database/de/explorer.texy @@ -1,550 +1,912 @@ -Datenbank-Explorer -****************** +Database Explorer +***************** <div class=perex> -Der Nette Database Explorer vereinfacht das Abrufen von Daten aus der Datenbank erheblich, ohne dass SQL-Abfragen geschrieben werden müssen. +Der Explorer bietet eine intuitive und effiziente Methode zur Arbeit mit der Datenbank. Er kümmert sich automatisch um Beziehungen zwischen Tabellen und die Optimierung von Abfragen, sodass Sie sich auf Ihre Anwendung konzentrieren können. Er funktioniert sofort ohne Konfiguration. Wenn Sie die volle Kontrolle über SQL-Abfragen benötigen, können Sie den [SQL-Zugriff |SQL way] nutzen. -- verwendet effiziente Abfragen -- keine Daten werden unnötig übertragen -- verfügt über eine elegante Syntax +- Die Arbeit mit Daten ist natürlich und leicht verständlich +- Generiert optimierte SQL-Abfragen, die nur die benötigten Daten laden +- Ermöglicht einfachen Zugriff auf verwandte Daten ohne die Notwendigkeit, JOIN-Abfragen zu schreiben +- Funktioniert sofort ohne jegliche Konfiguration oder Generierung von Entitäten </div> -Um den Database Explorer zu verwenden, beginnen Sie mit einer Tabelle - rufen Sie `table()` auf einem [api:Nette\Database\Explorer] Objekt auf. Der einfachste Weg, um eine Kontextobjektinstanz zu erhalten, ist [hier beschrieben |core#Connection and Configuration], oder, für den Fall, dass Nette Database Explorer als eigenständiges Werkzeug verwendet wird, kann es [manuell erstellt |#Creating Explorer Manually] werden. + +Mit dem Explorer beginnen Sie, indem Sie die Methode `table()` des Objekts [api:Nette\Database\Explorer] aufrufen (Details zur Verbindung finden Sie im Kapitel [Verbindung und Konfiguration |guide#Verbindung und Konfiguration]): ```php -$books = $explorer->table('book'); // db table name is 'book' +$books = $explorer->table('book'); // 'book' ist der Tabellenname ``` -Der Aufruf gibt eine Instanz des [Selection-Objekts |api:Nette\Database\Table\Selection] zurück, das durchlaufen werden kann, um alle Bücher abzurufen. Jedes Element (eine Zeile) wird durch eine Instanz von [ActiveRow |api:Nette\Database\Table\ActiveRow] mit Daten dargestellt, die seinen Eigenschaften zugeordnet sind: +Die Methode gibt ein Objekt [Selection |api:Nette\Database\Table\Selection] zurück, das eine SQL-Abfrage repräsentiert. An dieses Objekt können weitere Methoden zur Filterung und Sortierung der Ergebnisse angehängt werden. Die Abfrage wird erst zusammengestellt und ausgeführt, wenn Sie Daten anfordern, beispielsweise durch Iteration mit einer `foreach`-Schleife. Jede Zeile wird durch ein Objekt [ActiveRow |api:Nette\Database\Table\ActiveRow] repräsentiert: ```php foreach ($books as $book) { - echo $book->title; - echo $book->author_id; + echo $book->title; // Ausgabe der Spalte 'title' + echo $book->author_id; // Ausgabe der Spalte 'author_id' } ``` -Das Abrufen einer bestimmten Zeile erfolgt über die Methode `get()`, die direkt eine ActiveRow-Instanz zurückgibt. +Der Explorer erleichtert die Arbeit mit [#Beziehungen zwischen Tabellen] erheblich. Das folgende Beispiel zeigt, wie einfach Sie Daten aus verknüpften Tabellen (Bücher und ihre Autoren) ausgeben können. Beachten Sie, dass Sie keine JOIN-Abfragen schreiben müssen; Nette erstellt sie für Sie: ```php -$book = $explorer->table('book')->get(2); // gibt das Buch mit der Nummer 2 zurück -echo $book->title; -echo $book->author_id; +$books = $explorer->table('book'); + +foreach ($books as $book) { + echo 'Buch: ' . $book->title; + echo 'Autor: ' . $book->author->name; // erstellt JOIN zur Tabelle 'author' +} ``` -Schauen wir uns einen häufigen Anwendungsfall an. Sie müssen Bücher und ihre Autoren abrufen. Dies ist eine übliche 1:N-Beziehung. Die häufig verwendete Lösung besteht darin, die Daten mit einer SQL-Abfrage mit Tabellen-Joins abzurufen. Die zweite Möglichkeit besteht darin, die Daten separat abzurufen, eine Abfrage zum Abrufen der Bücher auszuführen und dann mit einer anderen Abfrage (z. B. in Ihrem foreach-Zyklus) einen Autor für jedes Buch zu ermitteln. Dies könnte leicht so optimiert werden, dass nur zwei Abfragen ausgeführt werden, eine für die Bücher und eine weitere für die benötigten Autoren - und genau so macht es der Nette Database Explorer. +Nette Database Explorer optimiert Abfragen, um sie so effizient wie möglich zu gestalten. Das obige Beispiel führt nur zwei SELECT-Abfragen aus, unabhängig davon, ob Sie 10 oder 10.000 Bücher verarbeiten. -In den folgenden Beispielen werden wir mit dem Datenbankschema in der Abbildung arbeiten. Es gibt OneHasMany (1:N)-Verknüpfungen (Autor des Buches `author_id` und möglicher Übersetzer `translator_id`, der `null` sein kann) und ManyHasMany (M:N)-Verknüpfungen zwischen Buch und seinen Tags. +Zusätzlich verfolgt der Explorer, welche Spalten im Code verwendet werden, und lädt nur diese aus der Datenbank, wodurch weitere Leistung eingespart wird. Dieses Verhalten ist vollständig automatisch und adaptiv. Wenn Sie später den Code ändern und weitere Spalten verwenden, passt der Explorer die Abfragen automatisch an. Sie müssen nichts einstellen oder darüber nachdenken, welche Spalten Sie benötigen werden - überlassen Sie das Nette. -[Ein Beispiel, einschließlich eines Schemas, ist auf GitHub zu finden |https://github.com/nette-examples/books]. -[* db-schema-1-.webp *] *** In den Beispielen verwendete Datenbankstruktur .<> +Filterung und Sortierung +======================== -Der folgende Code listet den Namen des Autors für jedes Buch und alle seine Tags auf. Wir werden [gleich besprechen |#Working with relationships], wie dies intern funktioniert. +Die Klasse `Selection` bietet Methoden zur Filterung und Sortierung der Datenauswahl. -```php -$books = $explorer->table('book'); +.[language-php] +| `where($condition, ...$params)` | Fügt eine WHERE-Bedingung hinzu. Mehrere Bedingungen werden mit dem AND-Operator verknüpft +| `whereOr(array $conditions)` | Fügt eine Gruppe von WHERE-Bedingungen hinzu, die mit dem OR-Operator verknüpft sind +| `wherePrimary($value)` | Fügt eine WHERE-Bedingung nach dem Primärschlüssel hinzu +| `order($columns, ...$params)` | Legt die ORDER BY-Sortierung fest +| `select($columns, ...$params)` | Spezifiziert die Spalten, die geladen werden sollen +| `limit($limit, $offset = null)` | Begrenzt die Anzahl der Zeilen (LIMIT) und setzt optional den OFFSET +| `page($page, $itemsPerPage, &$total = null)` | Legt die Paginierung fest +| `group($columns, ...$params)` | Gruppiert Zeilen (GROUP BY) +| `having($condition, ...$params)` | Fügt eine HAVING-Bedingung zur Filterung gruppierter Zeilen hinzu -foreach ($books as $book) { - echo 'Titel: ' . $book->title; - echo 'geschrieben von: ' . $book->author->name; // $book->autor ist Zeile aus Tabelle 'autor' +Methoden können verkettet werden (sog. [Fluent Interface |nette:introduction-to-object-oriented-programming#Fluent Interfaces]): `$table->where(...)->order(...)->limit(...)`. - echo 'Tags: '; - foreach ($book->related('book_tag') as $bookTag) { - echo $bookTag->tag->name . ', '; // $bookTag->tag ist Zeile aus Tabelle 'tag' - } -} -``` +In diesen Methoden können Sie auch spezielle Notationen für den Zugriff auf [Daten aus verwandten Tabellen |#Abfragen über verwandte Tabellen] verwenden. -Sie werden erfreut sein, wie effizient die Datenbankschicht arbeitet. Das obige Beispiel stellt eine konstante Anzahl von Anfragen, die wie folgt aussehen: -```sql -SELECT * FROM `book` -SELECT * FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT * FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) -``` +Escaping und Bezeichner +----------------------- -Wenn Sie den [Cache |caching:] verwenden (standardmäßig eingeschaltet), werden keine Spalten unnötig abgefragt. Nach der ersten Abfrage speichert der Cache die verwendeten Spaltennamen und Nette Database Explorer führt nur Abfragen mit den benötigten Spalten aus: +Methoden escapen automatisch Parameter und setzen Bezeichner (Tabellen- und Spaltennamen) in Anführungszeichen, wodurch SQL-Injection verhindert wird. Für die korrekte Funktion müssen einige Regeln beachtet werden: -```sql -SELECT `id`, `title`, `author_id` FROM `book` -SELECT `id`, `name` FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT `book_id`, `tag_id` FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT `id`, `name` FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) -``` +- Schlüsselwörter, Funktionsnamen, Prozedurnamen usw. **großschreiben**. +- Spalten- und Tabellennamen **kleinschreiben**. +- Zeichenketten immer über **Parameter** einfügen. +```php +where('name = ' . $name); // KRITISCHE SCHWACHSTELLE: SQL-Injection +where('name LIKE "%search%"'); // FALSCH: erschwert das automatische Setzen von Anführungszeichen +where('name LIKE ?', '%search%'); // RICHTIG: Wert über Parameter eingefügt -Auswahlen .[#toc-selections] -============================ +where('name like ?', $name); // FALSCH: generiert: `name` `like` ? +where('name LIKE ?', $name); // RICHTIG: generiert: `name` LIKE ? +where('LOWER(name) = ?', $value);// RICHTIG: LOWER(`name`) = ? +``` -Siehe Möglichkeiten zum Filtern und Einschränken von Zeilen [api:Nette\Database\Table\Selection]: -.[language-php] -| `$table->where($where[, $param[, ...]])` | WHERE mit AND als Verknüpfung setzen, wenn zwei oder mehr Bedingungen angegeben werden -| `$table->whereOr($where)` | WHERE setzen und OR als Verknüpfung verwenden, wenn zwei oder mehr Bedingungen angegeben werden -| `$table->order($columns)` | ORDER BY setzen, kann Ausdruck sein `('column DESC, id DESC')` -| `$table->select($columns)` | Set abgerufenen Spalten, kann Ausdruck sein `('col, MD5(col) AS hash')` -| `$table->limit($limit[, $offset])` | LIMIT und OFFSET setzen -| `$table->page($page, $itemsPerPage[, &$lastPage])` | Aktiviert Umbruch -| `$table->group($columns)` | GROUP BY einstellen -| `$table->having($having)` | HAVING einstellen +where(string|array $condition, ...$parameters): static .[method] +---------------------------------------------------------------- -Es kann eine fließende Schnittstelle verwendet werden, z. B. `$table->where(...)->order(...)->limit(...)`. Mehrere `where` oder `whereOr` Bedingungen werden mit dem Operator `AND` verbunden. +Filtert Ergebnisse anhand von WHERE-Bedingungen. Ihre Stärke liegt in der intelligenten Verarbeitung verschiedener Wertetypen und der automatischen Wahl von SQL-Operatoren. +Grundlegende Verwendung: -wo() .[#toc-where] ------------------- +```php +$table->where('id', $value); // WHERE `id` = 123 +$table->where('id > ?', $value); // WHERE `id` > 123 +$table->where('id = ? OR name = ?', $id, $name); // WHERE `id` = 1 OR `name` = 'Jon Snow' +``` -Nette Database Explorer kann automatisch die benötigten Operatoren für übergebene Werte hinzufügen: +Dank der automatischen Erkennung geeigneter Operatoren müssen Sie sich nicht um verschiedene Sonderfälle kümmern. Nette löst sie für Sie: -.[language-php] -| `$table->where('field', $value)` | feld = $wert -| `$table->where('field', null)` | feld IST NULL -| `$table->where('field > ?', $val)` | feld > $wert -| `$table->where('field', [1, 2])` | feld IN (1, 2) -| `$table->where('id = ? OR name = ?', 1, $name)` | id = 1 OR name = 'Jon Snow' -| `$table->where('field', $explorer->table($tableName))` | feld IN (SELECT $primär FROM $tabellenname) -| `$table->where('field', $explorer->table($tableName)->select('col'))` | feld IN (SELECT col FROM $tabellenname) +```php +$table->where('id', 1); // WHERE `id` = 1 +$table->where('id', null); // WHERE `id` IS NULL +$table->where('id', [1, 2, 3]); // WHERE `id` IN (1, 2, 3) +// Es kann auch ein Fragezeichen-Platzhalter ohne Operator verwendet werden: +$table->where('id ?', 1); // WHERE `id` = 1 +``` -Sie können Platzhalter auch ohne Spaltenoperator angeben. Diese Aufrufe sind die gleichen. +Die Methode verarbeitet auch negative Bedingungen und leere Arrays korrekt: ```php -$table->where('id = ? OR id = ?', 1, 2); -$table->where('id ? OR id ?', 1, 2); +$table->where('id', []); // WHERE `id` IS NULL AND FALSE -- findet nichts +$table->where('id NOT', []); // WHERE `id` IS NULL OR TRUE -- findet alles +$table->where('NOT (id ?)', []); // WHERE NOT (`id` IS NULL AND FALSE) -- findet alles +// $table->where('NOT id ?', $ids); Achtung - diese Syntax wird nicht unterstützt ``` -Diese Funktion ermöglicht es, den richtigen Operator basierend auf dem Wert zu generieren: +Als Parameter können Sie auch das Ergebnis aus einer anderen Tabelle übergeben - es wird eine Unterabfrage erstellt: ```php -$table->where('id ?', 2); // id = 2 -$table->where('id ?', null); // id IS NULL -$table->where('id', $ids); // id IN (...) +// WHERE `id` IN (SELECT `id` FROM `tableName`) +$table->where('id', $explorer->table($tableName)); + +// WHERE `id` IN (SELECT `col` FROM `tableName`) +$table->where('id', $explorer->table($tableName)->select('col')); ``` -Die Auswahl behandelt auch negative Bedingungen korrekt und funktioniert auch bei leeren Feldern: +Bedingungen können Sie auch als Array übergeben, dessen Elemente mit AND verknüpft werden: ```php -$table->where('id', []); // id IS NULL AND FALSE -$table->where('id NOT', []); // id IS NULL OR TRUE -$table->where('NOT (id ?)', $ids); // NOT (id IS NULL AND FALSE) - -// dies führt zu einer Ausnahme, da diese Syntax nicht unterstützt wird -$table->where('NOT id ?', $ids); +// WHERE (`price_final` < `price_original`) AND (`stock_count` > `min_stock`) +$table->where([ + 'price_final < price_original', + 'stock_count > min_stock', +]); ``` +Im Array können Sie Schlüssel-Wert-Paare verwenden, und Nette wählt wieder automatisch die richtigen Operatoren: -whereOr() .[#toc-whereor] -------------------------- +```php +// WHERE (`status` = 'active') AND (`id` IN (1, 2, 3)) +$table->where([ + 'status' => 'active', + 'id' => [1, 2, 3], +]); +``` -Beispiel für die Verwendung ohne Parameter: +Im Array können Sie SQL-Ausdrücke mit Fragezeichen-Platzhaltern und mehreren Parametern kombinieren. Dies ist geeignet für komplexe Bedingungen mit genau definierten Operatoren: ```php -// WHERE (user_id IS NULL) OR (SUM(`field1`) > SUM(`field2`)) -$table->whereOr([ - 'user_id IS NULL', - 'SUM(field1) > SUM(field2)', +// WHERE (`age` > 18) AND (ROUND(`score`, 2) > 75.5) +$table->where([ + 'age > ?' => 18, + 'ROUND(score, ?) > ?' => [2, 75.5], // zwei Parameter als Array übergeben ]); ``` -Wir verwenden die Parameter. Wenn Sie keinen Operator angeben, fügt Nette Database Explorer automatisch den entsprechenden Operator hinzu: +Mehrfache Aufrufe von `where()` verknüpfen Bedingungen automatisch mit AND. + + +whereOr(array $parameters): static .[method] +-------------------------------------------- + +Fügt ähnlich wie `where()` Bedingungen hinzu, jedoch mit dem Unterschied, dass sie mit OR verknüpft werden: ```php -// WHERE (`field1` IS NULL) OR (`field2` IN (3, 5)) OR (`amount` > 11) +// WHERE (`status` = 'active') OR (`deleted` = 1) $table->whereOr([ - 'field1' => null, - 'field2' => [3, 5], - 'amount >' => 11, + 'status' => 'active', + 'deleted' => true, ]); ``` -Der Schlüssel kann einen Ausdruck enthalten, der Fragezeichen als Platzhalter enthält, und dann Parameter im Wert übergeben: +Auch hier können Sie komplexere Ausdrücke verwenden: ```php -// WHERE (`id` > 12) OR (ROUND(`id`, 5) = 3) +// WHERE (`price` > 1000) OR (`price_with_tax` > 1500) $table->whereOr([ - 'id > ?' => 12, - 'ROUND(id, ?) = ?' => [5, 3], + 'price > ?' => 1000, + 'price_with_tax > ?' => 1500, ]); ``` -order() .[#toc-order] ---------------------- +wherePrimary(mixed $key): static .[method] +------------------------------------------ -Beispiele für die Verwendung: +Fügt eine Bedingung für den Primärschlüssel der Tabelle hinzu: ```php -$table->order('field1'); // ORDER BY `field1` -$table->order('field1 DESC, field2'); // ORDER BY `field1` DESC, `field2` -$table->order('field = ? DESC', 123); // ORDER BY `field` = 123 DESC +// WHERE `id` = 123 +$table->wherePrimary(123); + +// WHERE `id` IN (1, 2, 3) +$table->wherePrimary([1, 2, 3]); ``` +Wenn die Tabelle einen zusammengesetzten Primärschlüssel hat (z. B. `foo_id`, `bar_id`), übergeben Sie ihn als Array: + +```php +// WHERE `foo_id` = 1 AND `bar_id` = 5 +$table->wherePrimary(['foo_id' => 1, 'bar_id' => 5])->fetch(); + +// WHERE (`foo_id`, `bar_id`) IN ((1, 5), (2, 3)) +$table->wherePrimary([ + ['foo_id' => 1, 'bar_id' => 5], + ['foo_id' => 2, 'bar_id' => 3], +])->fetchAll(); +``` -select() .[#toc-select] ------------------------ -Beispiele für die Verwendung: +order(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- + +Bestimmt die Reihenfolge, in der die Zeilen zurückgegeben werden. Sie können nach einer oder mehreren Spalten sortieren, in aufsteigender oder absteigender Reihenfolge oder nach einem eigenen Ausdruck: ```php -$table->select('field1'); // SELECT `field1` -$table->select('col, UPPER(col) AS abc'); // SELECT `col`, UPPER(`col`) AS abc -$table->select('SUBSTR(title, ?)', 3); // SELECT SUBSTR(`title`, 3) +$table->order('created'); // ORDER BY `created` +$table->order('created DESC'); // ORDER BY `created` DESC +$table->order('priority DESC, created'); // ORDER BY `priority` DESC, `created` +$table->order('status = ? DESC', 'active'); // ORDER BY `status` = 'active' DESC ``` -limit() .[#toc-limit] ---------------------- +select(string $columns, ...$parameters): static .[method] +--------------------------------------------------------- + +Spezifiziert die Spalten, die aus der Datenbank zurückgegeben werden sollen. Standardmäßig gibt Nette Database Explorer nur die Spalten zurück, die tatsächlich im Code verwendet werden. Die Methode `select()` verwenden Sie daher in Fällen, in denen Sie spezifische Ausdrücke zurückgeben müssen: + +```php +// SELECT *, DATE_FORMAT(`created_at`, "%d.%m.%Y") AS `formatted_date` +$table->select('*, DATE_FORMAT(created_at, ?) AS formatted_date', '%d.%m.%Y'); +``` -Beispiele für die Verwendung: +Aliase, die mit `AS` definiert wurden, sind dann als Eigenschaften des ActiveRow-Objekts verfügbar: ```php -$table->limit(1); // LIMIT 1 -$table->limit(1, 10); // LIMIT 1 OFFSET 10 +foreach ($table as $row) { + echo $row->formatted_date; // Zugriff auf den Alias +} ``` -page() .[#toc-page] -------------------- +limit(?int $limit, ?int $offset = null): static .[method] +--------------------------------------------------------- -Eine alternative Möglichkeit, die Grenze und den Versatz festzulegen: +Begrenzt die Anzahl der zurückgegebenen Zeilen (LIMIT) und ermöglicht optional die Einstellung eines Offsets: ```php -$page = 5; -$itemsPerPage = 10; -$table->page($page, $itemsPerPage); // LIMIT 10 OFFSET 40 +$table->limit(10); // LIMIT 10 (gibt die ersten 10 Zeilen zurück) +$table->limit(10, 20); // LIMIT 10 OFFSET 20 ``` -Abrufen der letzten Seitenzahl, die an die Variable `$lastPage` übergeben wird: +Für die Paginierung ist es besser, die Methode `page()` zu verwenden. + + +page(int $page, int $itemsPerPage, &$numOfPages = null): static .[method] +------------------------------------------------------------------------- + +Erleichtert die Paginierung von Ergebnissen. Akzeptiert die Seitennummer (beginnend bei 1) und die Anzahl der Elemente pro Seite. Optional kann eine Referenz auf eine Variable übergeben werden, in der die Gesamtzahl der Seiten gespeichert wird: ```php -$table->page($page, $itemsPerPage, $lastPage); +$numOfPages = null; +$table->page(page: 3, itemsPerPage: 10, $numOfPages); +echo "Gesamtzahl der Seiten: $numOfPages"; ``` -group() .[#toc-group] ---------------------- +group(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- -Beispiele für die Verwendung: +Gruppiert Zeilen nach den angegebenen Spalten (GROUP BY). Wird normalerweise in Verbindung mit Aggregationsfunktionen verwendet: ```php -$table->group('field1'); // GROUP BY `field1` -$table->group('field1, field2'); // GROUP BY `field1`, `field2` +// Zählt die Anzahl der Produkte in jeder Kategorie +$table->select('category_id, COUNT(*) AS count') + ->group('category_id'); ``` -having() .[#toc-having] ------------------------ +having(string $having, ...$parameters): static .[method] +-------------------------------------------------------- -Beispiele für die Verwendung: +Legt eine Bedingung zur Filterung gruppierter Zeilen fest (HAVING). Kann in Verbindung mit der Methode `group()` und Aggregationsfunktionen verwendet werden: ```php -$table->having('COUNT(items) >', 100); // HAVING COUNT(`items`) > 100 +// Findet Kategorien mit mehr als 100 Produkten +$table->select('category_id, COUNT(*) AS count') + ->group('category_id') + ->having('count > ?', 100); ``` -Filtern nach einem anderen Tabellenwert .[#toc-joining-key] ------------------------------------------------------------ +Daten lesen +=========== -Häufig müssen Sie Ergebnisse nach einer Bedingung filtern, die eine andere Datenbanktabelle betrifft. Diese Arten von Bedingungen erfordern einen Tabellen-Join. Sie brauchen sie jedoch nicht mehr zu schreiben. +Zum Lesen von Daten aus der Datenbank stehen Ihnen mehrere nützliche Methoden zur Verfügung: -Nehmen wir an, Sie wollen alle Bücher finden, deren Autor 'Jon' heißt. Alles, was Sie schreiben müssen, ist der Verknüpfungsschlüssel der Beziehung und der Spaltenname in der verknüpften Tabelle. Der Verknüpfungsschlüssel wird von der Spalte abgeleitet, die sich auf die Tabelle bezieht, die Sie verknüpfen wollen. In unserem Beispiel (siehe das DB-Schema) ist dies die Spalte `author_id`, und es genügt, nur den ersten Teil davon zu verwenden - `author` (das Suffix `_id` kann weggelassen werden). `name` ist eine Spalte in der Tabelle `author`, die wir verwenden möchten. Eine Bedingung für den Buchübersetzer (der mit der Spalte `translator_id` verbunden ist) kann ebenso einfach erstellt werden. +.[language-php] +| `foreach ($table as $key => $row)` | Iteriert über alle Zeilen, `$key` ist der Wert des Primärschlüssels, `$row` ist ein ActiveRow-Objekt +| `$row = $table->get($key)` | Gibt eine Zeile nach dem Primärschlüssel zurück +| `$row = $table->fetch()` | Gibt die aktuelle Zeile zurück und bewegt den Zeiger zur nächsten +| `$array = $table->fetchPairs()` | Erstellt ein assoziatives Array aus den Ergebnissen +| `$array = $table->fetchAll()` | Gibt alle Zeilen als Array zurück +| `count($table)` | Gibt die Anzahl der Zeilen im Selection-Objekt zurück + +Das Objekt [ActiveRow |api:Nette\Database\Table\ActiveRow] ist nur zum Lesen bestimmt. Das bedeutet, dass die Werte seiner Eigenschaften nicht geändert werden können. Diese Einschränkung gewährleistet die Datenkonsistenz und verhindert unerwartete Nebeneffekte. Daten werden aus der Datenbank geladen, und jede Änderung sollte explizit und kontrolliert erfolgen. + + +`foreach` - Iteration über alle Zeilen +-------------------------------------- + +Der einfachste Weg, eine Abfrage auszuführen und Zeilen zu erhalten, ist die Iteration in einer `foreach`-Schleife. Sie startet automatisch die SQL-Abfrage. ```php $books = $explorer->table('book'); -$books->where('author.name LIKE ?', '%Jon%'); -$books->where('translator.name', 'David Grudl'); +foreach ($books as $key => $book) { + // $key ist der Wert des Primärschlüssels, $book ist ActiveRow + echo "$book->title ({$book->author->name})"; +} ``` -Die Logik der Verbindungsschlüssel wird durch die Implementierung von [Conventions |api:Nette\Database\Conventions] bestimmt. Wir empfehlen die Verwendung von [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions], die Ihre Fremdschlüssel analysiert und es Ihnen ermöglicht, einfach mit diesen Beziehungen zu arbeiten. -Die Beziehung zwischen dem Buch und seinem Autor ist 1:N. Die umgekehrte Beziehung ist ebenfalls möglich. Wir nennen sie **backjoin**. Schauen Sie sich ein anderes Beispiel an. Wir möchten alle Autoren abrufen, die mehr als 3 Bücher geschrieben haben. Um die Verknüpfung umzukehren, verwenden wir die Anweisung `:` (colon). Colon means that the joined relationship means hasMany (and it's quite logical too, as two dots are more than one dot). Unfortunately, the Selection class isn't smart enough, so we have to help with the aggregation and provide a `GROUP BY`, und auch die Bedingung muss in Form der Anweisung `HAVING` geschrieben werden. +get($key): ?ActiveRow .[method] +------------------------------- + +Führt eine SQL-Abfrage aus und gibt eine Zeile nach dem Primärschlüssel zurück, oder `null`, wenn sie nicht existiert. ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book.id) > 3'); +$book = $explorer->table('book')->get(123); // gibt ActiveRow mit ID 123 oder null zurück +if ($book) { + echo $book->title; +} ``` -Sie haben vielleicht bemerkt, dass sich der Verknüpfungsausdruck auf das Buch bezieht, aber es ist nicht klar, ob wir über `author_id` oder `translator_id` verknüpfen. Im obigen Beispiel verknüpft Selection über die Spalte `author_id`, weil eine Übereinstimmung mit der Quelltabelle gefunden wurde - der Tabelle `author`. Wenn es keine solche Übereinstimmung gäbe und es mehrere Möglichkeiten gäbe, würde Nette eine [AmbiguousReferenceKeyException aus lösen|api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. -Um eine Verknüpfung über die Spalte `translator_id` herzustellen, geben Sie einen optionalen Parameter im Verknüpfungsausdruck an. +fetch(): ?ActiveRow .[method] +----------------------------- + +Gibt eine Zeile zurück und bewegt den internen Zeiger zur nächsten. Wenn keine weiteren Zeilen mehr existieren, gibt sie `null` zurück. ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book(translator).id) > 3'); +$books = $explorer->table('book'); +while ($book = $books->fetch()) { + $this->processBook($book); +} ``` -Werfen wir einen Blick auf einige schwierigere Verknüpfungsausdrücke. -Wir möchten alle Autoren finden, die etwas über PHP geschrieben haben. Alle Bücher haben Tags, also sollten wir die Autoren auswählen, die ein Buch mit dem Tag PHP geschrieben haben. +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Gibt Ergebnisse als assoziatives Array zurück. Das erste Argument bestimmt den Namen der Spalte, die als Schlüssel im Array verwendet wird, das zweite Argument bestimmt den Namen der Spalte, die als Wert verwendet wird: ```php -$authors = $explorer->table('author'); -$authors->where(':book:book_tags.tag.name', 'PHP') - ->group('author.id') - ->having('COUNT(:book:book_tags.tag.id) > 0'); +$authors = $explorer->table('author')->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] +``` + +Wenn nur der erste Parameter angegeben wird, ist der Wert die gesamte Zeile, also das `ActiveRow`-Objekt: + +```php +$authors = $explorer->table('author')->fetchPairs('id'); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` + +Bei doppelten Schlüsseln wird der Wert aus der letzten Zeile verwendet. Bei Verwendung von `null` als Schlüssel wird das Array numerisch von Null indiziert (dann treten keine Kollisionen auf): + +```php +$authors = $explorer->table('author')->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] ``` -Aggregierte Abfragen .[#toc-aggregate-queries] +fetchPairs(Closure $callback): array .[method] ---------------------------------------------- -| `$table->count('*')` | Anzahl der Zeilen ermitteln -| `$table->count("DISTINCT $column")` | Ermittelt die Anzahl der eindeutigen Werte -| `$table->min($column)` | Ermittelt den Mindestwert -| `$table->max($column)` | Ermittelt den Maximalwert -| `$table->sum($column)` | Ermittelt die Summe aller Werte -| `$table->aggregation("GROUP_CONCAT($column)")` | Ausführen einer beliebigen Aggregationsfunktion +Alternativ können Sie als Parameter einen Callback angeben, der für jede Zeile entweder den Wert selbst oder ein Schlüssel-Wert-Paar zurückgibt. -.[caution] -Die Methode `count()` ohne Angabe von Parametern wählt alle Datensätze aus und gibt die Array-Größe zurück, was sehr ineffizient ist. Wenn Sie zum Beispiel die Anzahl der Zeilen für das Paging berechnen müssen, geben Sie immer das erste Argument an. +```php +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => "$row->title ({$row->author->name})"); +// ['Erstes Buch (Jan Novák)', ...] + +// Der Callback kann auch ein Array mit einem Schlüssel-Wert-Paar zurückgeben: +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => [$row->title, $row->author->name]); +// ['Erstes Buch' => 'Jan Novák', ...] +``` + + +fetchAll(): array .[method] +--------------------------- + +Gibt alle Zeilen als assoziatives Array von `ActiveRow`-Objekten zurück, wobei die Schlüssel die Werte der Primärschlüssel sind. + +```php +$allBooks = $explorer->table('book')->fetchAll(); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` + + +count(): int .[method] +---------------------- + +Die Methode `count()` ohne Parameter gibt die Anzahl der Zeilen im `Selection`-Objekt zurück: + +```php +$table->where('category', 1); +$count = $table->count(); +$count = count($table); // Alternative +``` +Achtung: `count()` mit einem Parameter führt eine Aggregationsfunktion `COUNT()` in der Datenbank aus, siehe unten. -Escaping & Quoting .[#toc-escaping-quoting] -=========================================== -Database Explorer ist intelligent und bricht Parameter und Anführungszeichen für Sie ab. Diese Grundregeln müssen jedoch befolgt werden: +ActiveRow::toArray(): array .[method] +------------------------------------- -- Schlüsselwörter, Funktionen und Prozeduren müssen in Großbuchstaben geschrieben werden -- Spalten und Tabellen müssen klein geschrieben werden -- Übergabe von Variablen als Parameter, keine Verkettung +Konvertiert das `ActiveRow`-Objekt in ein assoziatives Array, wobei die Schlüssel die Spaltennamen und die Werte die entsprechenden Daten sind. ```php -->where('name like ?', 'John'); // FALSCH! generates: `name` `like` ? -->where('name LIKE ?', 'John'); // RICHTIG +$book = $explorer->table('book')->get(1); +$bookArray = $book->toArray(); +// $bookArray wird sein: ['id' => 1, 'title' => '...', 'author_id' => ..., ...] +``` + + +Aggregation +=========== + +Die Klasse `Selection` bietet Methoden zur einfachen Durchführung von Aggregationsfunktionen (COUNT, SUM, MIN, MAX, AVG usw.). + +.[language-php] +| `count($expr)` | Zählt die Anzahl der Zeilen +| `min($expr)` | Gibt den Minimalwert in der Spalte zurück +| `max($expr)` | Gibt den Maximalwert in der Spalte zurück +| `sum($expr)` | Gibt die Summe der Werte in der Spalte zurück +| `aggregation($function)` | Ermöglicht die Durchführung einer beliebigen Aggregationsfunktion. Z. B. `AVG()`, `GROUP_CONCAT()` + + +count(string $expr): int .[method] +---------------------------------- + +Führt eine SQL-Abfrage mit der `COUNT`-Funktion aus und gibt das Ergebnis zurück. Die Methode wird verwendet, um festzustellen, wie viele Zeilen einer bestimmten Bedingung entsprechen: + +```php +$count = $table->count('*'); // SELECT COUNT(*) FROM `table` +$count = $table->count('DISTINCT column'); // SELECT COUNT(DISTINCT `column`) FROM `table` +``` -->where('KEY = ?', $value); // FALSCH! KEY is a keyword -->where('key = ?', $value); // RICHTIG. generates: `key` = ? +Achtung: [#count()] ohne Parameter gibt nur die Anzahl der Zeilen im `Selection`-Objekt zurück. -->where('name = ' . $name); // FALSCH! sql injection! -->where('name = ?', $name); // RICHTIG -->select('DATE_FORMAT(created, "%d.%m.%Y")'); // FALSCH! Variablen als Parameter übergeben, nicht verketten -->select('DATE_FORMAT(created, ?)', '%d.%m.%Y'); // RICHTIG +min(string $expr) und max(string $expr) .[method] +------------------------------------------------- + +Die Methoden `min()` und `max()` geben den minimalen bzw. maximalen Wert in der angegebenen Spalte oder dem Ausdruck zurück: + +```php +// SELECT MAX(`price`) FROM `products` WHERE `active` = 1 +$maxPrice = $products->where('active', true) + ->max('price'); ``` -.[warning] -Falsche Verwendung kann zu Sicherheitslücken führen +sum(string $expr) .[method] +--------------------------- -Abrufen von Daten .[#toc-fetching-data] -======================================= +Gibt die Summe der Werte in der angegebenen Spalte oder dem Ausdruck zurück: -| `foreach ($table as $id => $row)` | Iterieren über alle Zeilen im Ergebnis -| `$row = $table->get($id)` | Einzelne Zeile mit ID $id aus Tabelle holen -| `$row = $table->fetch()` | Holt die nächste Zeile aus dem Ergebnis -| `$array = $table->fetchPairs($key, $value)` | Holt alle Werte in ein assoziatives Array -| `$array = $table->fetchPairs($key)` | Holt alle Zeilen in ein assoziatives Array -| `count($table)` | Anzahl der Zeilen in der Ergebnismenge ermitteln +```php +// SELECT SUM(`price` * `items_in_stock`) FROM `products` WHERE `active` = 1 +$totalPrice = $products->where('active', true) + ->sum('price * items_in_stock'); +``` -Einfügen, Aktualisieren & Löschen .[#toc-insert-update-delete] -============================================================== +aggregation(string $function, ?string $groupFunction = null) .[method] +---------------------------------------------------------------------- -Die Methode `insert()` akzeptiert ein Array von Traversable-Objekten (z. B. [ArrayHash |utils:arrays#ArrayHash], das [Formulare |forms:] zurückgibt): +Ermöglicht die Durchführung einer beliebigen Aggregationsfunktion. + +```php +// Durchschnittlicher Preis der Produkte in einer Kategorie +$avgPrice = $products->where('category_id', 1) + ->aggregation('AVG(price)'); + +// Verbindet Produkt-Tags zu einer Zeichenkette +$tags = $products->where('id', 1) + ->aggregation('GROUP_CONCAT(tag.name) AS tags') + ->fetch() + ->tags; +``` + +Wenn wir Ergebnisse aggregieren müssen, die bereits selbst aus einer Aggregationsfunktion und Gruppierung hervorgegangen sind (z. B. `SUM(wert)` über gruppierte Zeilen), geben wir als zweites Argument die Aggregationsfunktion an, die auf diese Zwischenergebnisse angewendet werden soll: + +```php +// Berechnet den Gesamtpreis der Produkte auf Lager für einzelne Kategorien und summiert dann diese Preise. +$totalPrice = $products->select('category_id, SUM(price * stock) AS category_total') + ->group('category_id') + ->aggregation('SUM(category_total)', 'SUM'); +``` + +In diesem Beispiel berechnen wir zuerst den Gesamtpreis der Produkte in jeder Kategorie (`SUM(price * stock) AS category_total`) und gruppieren die Ergebnisse nach `category_id`. Dann verwenden wir `aggregation('SUM(category_total)', 'SUM')`, um diese Zwischensummen `category_total` zu addieren. Das zweite Argument `'SUM'` gibt an, dass die `SUM`-Funktion auf die Zwischenergebnisse angewendet werden soll. + + +Insert, Update & Delete +======================= + +Nette Database Explorer vereinfacht das Einfügen, Aktualisieren und Löschen von Daten. Alle genannten Methoden werfen im Fehlerfall eine `Nette\Database\DriverException`. + + +Selection::insert(iterable $data) .[method] +------------------------------------------- + +Fügt neue Datensätze in die Tabelle ein. + +**Einfügen eines einzelnen Datensatzes:** + +Den neuen Datensatz übergeben wir als assoziatives Array oder iterable Objekt (zum Beispiel `ArrayHash`, das in [Formularen |forms:] verwendet wird), wobei die Schlüssel den Spaltennamen in der Tabelle entsprechen. + +Wenn die Tabelle einen definierten Primärschlüssel hat, gibt die Methode ein `ActiveRow`-Objekt zurück, das aus der Datenbank neu geladen wird, um eventuelle Änderungen auf Datenbankebene (Trigger, Standardwerte von Spalten, Berechnungen von Auto-Increment-Spalten) zu berücksichtigen. Dadurch wird die Datenkonsistenz gewährleistet und das Objekt enthält immer die aktuellen Daten aus der Datenbank. Wenn es keinen eindeutigen Primärschlüssel gibt, gibt sie die übergebenen Daten in Form eines Arrays zurück. ```php $row = $explorer->table('users')->insert([ - 'name' => $name, - 'year' => $year, + 'name' => 'John Doe', + 'email' => 'john.doe@example.com', ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) +// $row ist eine Instanz von ActiveRow und enthält die vollständigen Daten der eingefügten Zeile, +// einschließlich der automatisch generierten ID und eventueller durch Trigger vorgenommener Änderungen +echo $row->id; // Gibt die ID des neu eingefügten Benutzers aus +echo $row->created_at; // Gibt die Erstellungszeit aus, falls sie durch einen Trigger gesetzt wurde ``` -Wenn der Primärschlüssel in der Tabelle definiert ist, wird ein ActiveRow-Objekt zurückgegeben, das die eingefügte Zeile enthält. +**Einfügen mehrerer Datensätze auf einmal:** -Mehrfaches Einfügen: +Die Methode `insert()` ermöglicht das Einfügen mehrerer Datensätze mit einer einzigen SQL-Abfrage. In diesem Fall gibt sie die Anzahl der eingefügten Zeilen zurück. ```php -$explorer->table('users')->insert([ +$insertedRows = $explorer->table('users')->insert([ + [ + 'name' => 'John', + 'year' => 1994, + ], [ - 'name' => 'Jim', - 'year' => 1978, - ], [ 'name' => 'Jack', - 'year' => 1987, + 'year' => 1995, ], ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) +// INSERT INTO `users` (`name`, `year`) VALUES ('John', 1994), ('Jack', 1995) +// $insertedRows wird 2 sein +``` + +Als Parameter kann auch ein `Selection`-Objekt mit einer Datenauswahl übergeben werden. + +```php +$newUsers = $explorer->table('potential_users') + ->where('approved', 1) + ->select('name, email'); + +$insertedRows = $explorer->table('users')->insert($newUsers); ``` -Dateien oder DateTime-Objekte können als Parameter übergeben werden: +**Einfügen spezieller Werte:** + +Als Werte können wir auch Dateien, DateTime-Objekte oder SQL-Literale übergeben: ```php $explorer->table('users')->insert([ - 'name' => $name, - 'created' => new DateTime, // or $explorer::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // inserts the file + 'name' => 'John', + 'created_at' => new DateTime, // konvertiert in Datenbankformat + 'avatar' => fopen('image.jpg', 'rb'), // fügt binären Inhalt der Datei ein + 'uuid' => $explorer::literal('UUID()'), // ruft die Funktion UUID() auf ]); ``` -Aktualisieren (gibt die Anzahl der betroffenen Zeilen zurück): + +Selection::update(iterable $data): int .[method] +------------------------------------------------ + +Aktualisiert Zeilen in der Tabelle gemäß dem angegebenen Filter. Gibt die Anzahl der tatsächlich geänderten Zeilen zurück. + +Die zu ändernden Spalten übergeben wir als assoziatives Array oder iterable Objekt (zum Beispiel `ArrayHash`, das in [Formularen |forms:] verwendet wird), wobei die Schlüssel den Spaltennamen in der Tabelle entsprechen: ```php -$count = $explorer->table('users') - ->where('id', 10) // muss vor update() aufgerufen werden +$affected = $explorer->table('users') + ->where('id', 10) ->update([ - 'name' => 'Ned Stark' + 'name' => 'John Smith', + 'year' => 1994, ]); -// UPDATE `users` SET `name`='Ned Stark' WHERE (`id` = 10) +// UPDATE `users` SET `name` = 'John Smith', `year` = 1994 WHERE `id` = 10 ``` -Für die Aktualisierung können wir die Operatoren `+=` und `-=` verwenden: +Zur Änderung numerischer Werte können Sie die Operatoren `+=` und `-=` verwenden: ```php $explorer->table('users') + ->where('id', 10) ->update([ - 'age+=' => 1, // siehe += + 'points+=' => 1, // erhöht den Wert der Spalte 'points' um 1 + 'coins-=' => 1, // verringert den Wert der Spalte 'coins' um 1 ]); -// UPDATE users SET `age` = `age` + 1 +// UPDATE `users` SET `points` = `points` + 1, `coins` = `coins` - 1 WHERE `id` = 10 ``` -Löschen (gibt die Anzahl der gelöschten Zeilen zurück): + +Selection::delete(): int .[method] +---------------------------------- + +Löscht Zeilen aus der Tabelle gemäß dem angegebenen Filter. Gibt die Anzahl der gelöschten Zeilen zurück. ```php $count = $explorer->table('users') ->where('id', 10) ->delete(); -// DELETE FROM `users` WHERE (`id` = 10) +// DELETE FROM `users` WHERE `id` = 10 ``` +.[caution] +Vergessen Sie beim Aufrufen von `update()` und `delete()` nicht, mit `where()` die Zeilen anzugeben, die geändert bzw. gelöscht werden sollen. Wenn Sie `where()` nicht verwenden, wird die Operation auf die gesamte Tabelle angewendet! -Arbeiten mit Relationen .[#toc-working-with-relationships] -========================================================== +ActiveRow::update(iterable $data): bool .[method] +------------------------------------------------- -Hat eine Beziehung .[#toc-has-one-relation] -------------------------------------------- -Hat eine Beziehung ist ein häufiger Anwendungsfall. Buch *hat einen* Autor. Ein Buch *hat einen* Übersetzer. Das Abrufen von verwandten Zeilen wird hauptsächlich mit der Methode `ref()` durchgeführt. Sie akzeptiert zwei Argumente: den Namen der Zieltabelle und die Spalte der Quellverbindung. Siehe Beispiel: +Aktualisiert Daten in der Datenbankzeile, die durch das `ActiveRow`-Objekt repräsentiert wird. Als Parameter akzeptiert es ein Iterable mit Daten, die aktualisiert werden sollen (Schlüssel sind Spaltennamen). Zur Änderung numerischer Werte können Sie die Operatoren `+=` und `-=` verwenden: + +Nach der Durchführung der Aktualisierung wird `ActiveRow` automatisch aus der Datenbank neu geladen, um eventuelle Änderungen auf Datenbankebene (z. B. Trigger) zu berücksichtigen. Die Methode gibt `true` zurück, nur wenn tatsächlich Daten geändert wurden. ```php -$book = $explorer->table('book')->get(1); -$book->ref('author', 'author_id'); +$article = $explorer->table('article')->get(1); +$article->update([ + 'views += 1', // erhöhen die Anzahl der Ansichten +]); +echo $article->views; // Gibt die aktuelle Anzahl der Ansichten aus ``` -Im obigen Beispiel holen wir einen verwandten Autoreneintrag aus der Tabelle `author`, der Primärschlüssel des Autors wird über die Spalte `book.author_id` gesucht. Die Methode Ref() gibt eine ActiveRow-Instanz zurück oder null, wenn es keinen entsprechenden Eintrag gibt. Die zurückgegebene Zeile ist eine Instanz von ActiveRow, so dass wir mit ihr auf dieselbe Weise arbeiten können wie mit dem Bucheintrag. +Diese Methode aktualisiert nur eine bestimmte Zeile in der Datenbank. Für die Massenaktualisierung mehrerer Zeilen verwenden Sie die Methode [#Selection::update()]. + + +ActiveRow::delete() .[method] +----------------------------- + +Löscht die Zeile aus der Datenbank, die durch das `ActiveRow`-Objekt repräsentiert wird. ```php -$author = $book->ref('author', 'author_id'); -$author->name; -$author->born; +$book = $explorer->table('book')->get(1); +$book->delete(); // Löscht das Buch mit der ID 1 +``` + +Diese Methode löscht nur eine bestimmte Zeile in der Datenbank. Für das Massenlöschen mehrerer Zeilen verwenden Sie die Methode [#Selection::delete()]. + + +Beziehungen zwischen Tabellen +============================= + +In relationalen Datenbanken sind Daten auf mehrere Tabellen verteilt und über Fremdschlüssel miteinander verbunden. Nette Database Explorer bietet eine revolutionäre Möglichkeit, mit diesen Beziehungen zu arbeiten - ohne JOIN-Abfragen zu schreiben und ohne die Notwendigkeit, etwas zu konfigurieren oder zu generieren. + +Zur Veranschaulichung der Arbeit mit Beziehungen verwenden wir das Beispiel einer Buchdatenbank ([finden Sie auf GitHub |https://github.com/nette-examples/books]). In der Datenbank haben wir folgende Tabellen: + +- `author` - Schriftsteller und Übersetzer (Spalten `id`, `name`, `web`, `born`) +- `book` - Bücher (Spalten `id`, `author_id`, `translator_id`, `title`, `sequel_id`) +- `tag` - Schlagwörter (Spalten `id`, `name`) +- `book_tag` - Verknüpfungstabelle zwischen Büchern und Schlagwörtern (Spalten `book_id`, `tag_id`) + +[* db-schema-1-.webp *] *** Datenbankstruktur, die in den Beispielen verwendet wird .<> + +In unserem Beispiel der Buchdatenbank finden wir verschiedene Arten von Beziehungen (obwohl das Modell im Vergleich zur Realität vereinfacht ist): + +- One-to-many 1:N – jedes Buch **hat einen** Autor, ein Autor kann **mehrere** Bücher schreiben +- Zero-to-many 0:N – ein Buch **kann einen** Übersetzer haben, ein Übersetzer kann **mehrere** Bücher übersetzen +- Zero-to-one 0:1 – ein Buch **kann einen** weiteren Teil haben +- Many-to-many M:N – ein Buch **kann mehrere** Schlagwörter haben und ein Schlagwort kann **mehreren** Büchern zugeordnet sein -// or directly -$book->ref('author', 'author_id')->name; -$book->ref('author', 'author_id')->born; +In diesen Beziehungen gibt es immer eine übergeordnete und eine untergeordnete Tabelle. Zum Beispiel ist in der Beziehung zwischen Autor und Buch die Tabelle `author` übergeordnet und `book` untergeordnet - man kann sich vorstellen, dass ein Buch immer einem Autor "gehört". Dies spiegelt sich auch in der Datenbankstruktur wider: Die untergeordnete Tabelle `book` enthält den Fremdschlüssel `author_id`, der auf die übergeordnete Tabelle `author` verweist. + +Wenn wir Bücher einschließlich der Namen ihrer Autoren auflisten müssen, haben wir zwei Möglichkeiten. Entweder erhalten wir die Daten mit einer einzigen SQL-Abfrage mittels `LEFT JOIN`: + +```sql +SELECT book.*, author.name FROM book LEFT JOIN author ON book.author_id = author.id +``` + +Oder wir laden die Daten in zwei Schritten - zuerst die Bücher und dann ihre Autoren - und fügen sie dann in PHP zusammen: + +```sql +SELECT * FROM book; +SELECT * FROM author WHERE id IN (1, 2, 3); -- IDs der Autoren der abgerufenen Bücher ``` -Book hat auch einen Übersetzer, so dass der Name des Übersetzers recht einfach zu ermitteln ist. +Der zweite Ansatz ist tatsächlich effizienter, auch wenn das überraschend sein mag. Die Daten werden nur einmal pro Tabelle geladen und können besser im Cache genutzt werden. Genau auf diese Weise arbeitet Nette Database Explorer - alles wird unter der Haube gelöst und Ihnen wird eine elegante API geboten: + ```php -$book->ref('author', 'translator_id')->name +$books = $explorer->table('book'); +foreach ($books as $book) { + echo 'Titel: ' . $book->title; + echo 'geschrieben von: ' . $book->author->name; // $book->author ist der Datensatz aus der Tabelle 'author' + echo 'übersetzt von: ' . $book->translator?->name; +} ``` -All das ist gut, aber etwas umständlich, finden Sie nicht auch? Der Datenbank-Explorer enthält bereits die Definitionen der Fremdschlüssel, warum sie also nicht automatisch verwenden? Lassen Sie uns das tun! -Wenn wir eine Eigenschaft aufrufen, die nicht existiert, versucht ActiveRow, den Namen der aufrufenden Eigenschaft als 'hat eine' Beziehung aufzulösen. Das Abrufen dieser Eigenschaft ist dasselbe wie der Aufruf der ref()-Methode mit nur einem Argument. Wir nennen das einzige Argument **key**. Der Schlüssel wird in eine bestimmte Fremdschlüsselbeziehung aufgelöst. Der übergebene Schlüssel wird mit Zeilenspalten abgeglichen, und wenn er übereinstimmt, wird der in der entsprechenden Spalte definierte Fremdschlüssel verwendet, um Daten aus der zugehörigen Zieltabelle zu erhalten. Siehe Beispiel: +Zugriff auf die übergeordnete Tabelle +------------------------------------- + +Der Zugriff auf die übergeordnete Tabelle ist unkompliziert. Es handelt sich um Beziehungen wie *ein Buch hat einen Autor* oder *ein Buch kann einen Übersetzer haben*. Den zugehörigen Datensatz erhalten wir über eine Eigenschaft des `ActiveRow`-Objekts. Der Name der Eigenschaft entspricht dem Namen der Spalte mit dem Fremdschlüssel, jedoch ohne das Suffix `_id`: ```php -$book->author->name; -// gleich wie -$book->ref('author')->name; +$book = $explorer->table('book')->get(1); +echo $book->author->name; // findet den Autor über die Spalte author_id +echo $book->translator?->name; // findet den Übersetzer über translator_id (nullsafe) ``` -Die ActiveRow-Instanz hat keine Autorenspalte. Alle Buchspalten werden nach einer Übereinstimmung mit *key* durchsucht. Übereinstimmung bedeutet in diesem Fall, dass der Spaltenname den Schlüssel enthalten muss. Im obigen Beispiel enthält die Spalte `author_id` die Zeichenfolge "author" und wird daher mit dem Schlüssel "author" abgeglichen. Wenn Sie den Buchübersetzer abrufen möchten, können Sie z. B. "Übersetzer" als Schlüssel verwenden, da der Schlüssel "Übersetzer" mit der Spalte `translator_id` übereinstimmt. Mehr über die Logik der Schlüsselübereinstimmung finden Sie im Kapitel [Verknüpfung von Ausdrücken |#joining-key]. +Wenn Sie auf die Eigenschaft `$book->author` zugreifen, sucht der Explorer in der Tabelle `book` nach einer Spalte, deren Name auf `author` endet und auf `_id` endet (also `author_id`). Anhand des Wertes in dieser Spalte lädt er den entsprechenden Datensatz aus der Tabelle `author` und gibt ihn als `ActiveRow` zurück. Ähnlich funktioniert auch `$book->translator`, das die Spalte `translator_id` verwendet. Da die Spalte `translator_id` `NULL` enthalten kann, verwenden wir im Code den Nullsafe-Operator `?->`. + +Einen alternativen Weg bietet die Methode `ref()`, die zwei Argumente akzeptiert: den Namen der Zieltabelle und optional den Namen der Verbindungspalte. Sie gibt eine Instanz von `ActiveRow` oder `null` zurück: ```php -echo $book->title . ': '; -echo $book->author->name; -if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; -} +echo $book->ref('author', 'author_id')->name; // Beziehung zum Autor +echo $book->ref('author', 'translator_id')->name; // Beziehung zum Übersetzer ``` -Wenn Sie mehrere Bücher abrufen möchten, sollten Sie den gleichen Ansatz verwenden. Nette Database Explorer wird die Autoren und Übersetzer für alle abgerufenen Bücher auf einmal abrufen. +Die Methode `ref()` ist nützlich, wenn der Name der Beziehung nicht eindeutig aus dem Spaltennamen abgeleitet werden kann oder wenn Sie eine explizite Steuerung bevorzugen. + +Der Explorer optimiert Datenbankabfragen automatisch. Wenn Sie Bücher in einer Schleife durchlaufen und auf ihre zugehörigen Datensätze (Autoren, Übersetzer) zugreifen, generiert der Explorer nicht für jedes Buch eine separate Abfrage. Stattdessen führt er nur eine `SELECT`-Abfrage pro referenzierter Tabelle durch, was die Datenbanklast erheblich reduziert. Zum Beispiel: ```php $books = $explorer->table('book'); foreach ($books as $book) { echo $book->title . ': '; echo $book->author->name; - if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; - } + echo $book->translator?->name; } ``` -Der Code wird nur diese 3 Abfragen ausführen: +Dieser Code führt nur diese drei blitzschnellen Abfragen an die Datenbank aus: + ```sql SELECT * FROM `book`; -SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- ids of fetched books from author_id column -SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- ids of fetched books from translator_id column +SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- id aus der Spalte author_id der ausgewählten Bücher +SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- id aus der Spalte translator_id der ausgewählten Bücher ``` +.[note] +Die Logik zur Erkennung der Beziehung basiert auf den [Conventions |api:Nette\Database\Conventions]. Wir empfehlen die Verwendung von [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions], die Fremdschlüssel analysiert und die einfache Arbeit mit bestehenden Beziehungen zwischen Tabellen ermöglicht. -Hat viele Relationen .[#toc-has-many-relation] ----------------------------------------------- -Die Beziehung "hat viele" ist einfach die Umkehrung der Beziehung "hat einen". Autor *hat* *viele* Bücher geschrieben. Der Autor *hat* *viele* Bücher übersetzt. Wie Sie sehen können, ist diese Art von Beziehung etwas schwieriger, weil die Beziehung 'benannt' ist ('geschrieben', 'übersetzt'). Die ActiveRow-Instanz verfügt über die Methode `related()`, die ein Array mit verwandten Einträgen zurückgibt. Die Einträge sind ebenfalls ActiveRow Instanzen. Siehe Beispiel unten: +Zugriff auf die untergeordnete Tabelle +-------------------------------------- + +Der Zugriff auf die untergeordnete Tabelle funktioniert in umgekehrter Richtung. Nun fragen wir *welche Bücher hat dieser Autor geschrieben* oder *welche Bücher hat dieser Übersetzer übersetzt*. Für diesen Abfragetyp verwenden wir die Methode `related()`, die eine `Selection` mit den zugehörigen Datensätzen zurückgibt. Sehen wir uns ein Beispiel an: ```php -$author = $explorer->table('author')->get(11); -echo $author->name . ' has written:'; +$author = $explorer->table('author')->get(1); +// Gibt alle Bücher des Autors aus foreach ($author->related('book.author_id') as $book) { - echo $book->title; + echo "Geschrieben: $book->title"; } -echo 'and translated:'; +// Gibt alle Bücher aus, die der Autor übersetzt hat foreach ($author->related('book.translator_id') as $book) { - echo $book->title; + echo "Übersetzt: $book->title"; } ``` -Methode `related()` Methode akzeptiert eine vollständige Join-Beschreibung, die als zwei Argumente oder als ein durch Punkt verbundenes Argument übergeben wird. Das erste Argument ist die Zieltabelle, das zweite ist die Zielspalte. +Die Methode `related()` akzeptiert die Beschreibung der Beziehung als ein Argument mit Punktnotation (`Zieltabelle.Fremdschlüsselspalte`) oder als zwei separate Argumente (`Zieltabelle`, `Fremdschlüsselspalte`): ```php -$author->related('book.translator_id'); -// gleich wie -$author->related('book', 'translator_id'); +$author->related('book.translator_id'); // ein Argument +$author->related('book', 'translator_id'); // zwei Argumente ``` -Sie können die auf Fremdschlüsseln basierende Heuristik des Nette Database Explorer verwenden und nur das Argument **key** übergeben. Key wird mit allen Fremdschlüsseln abgeglichen, die auf die aktuelle Tabelle (`author` table) zeigen. Wenn es eine Übereinstimmung gibt, wird Nette Database Explorer diesen Fremdschlüssel verwenden, andernfalls wird [Nette\InvalidArgumentException |api:Nette\InvalidArgumentException] oder [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException] ausgelöst. Mehr über die Logik der Schlüsselübereinstimmung finden Sie im Kapitel [Joining-Ausdrücke |#joining-key]. +Der Explorer kann die korrekte Verbindungspalte oft automatisch erkennen, wenn sie den Konventionen folgt (z.B. `zieltabelle_id`). In diesem Fall würde die Verbindung über die Spalte `book.author_id` erfolgen, da der Name der Quelltabelle `author` ist und die Zieltabelle `book` heißt: -Natürlich können Sie die entsprechenden Methoden für alle abgerufenen Autoren aufrufen, der Nette Database Explorer wird dann die entsprechenden Bücher auf einmal abrufen. +```php +$author->related('book'); // verwendet book.author_id +``` + +Wenn mehrere mögliche Beziehungen bestehen oder die Spalte nicht den Konventionen entspricht, wirft der Explorer eine [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. + +Die Methode `related()` können Sie natürlich auch beim Durchlaufen mehrerer Datensätze in einer Schleife verwenden, und der Explorer optimiert auch in diesem Fall die Abfragen automatisch: ```php $authors = $explorer->table('author'); foreach ($authors as $author) { - echo $author->name . ' has written:'; + echo $author->name . ' hat geschrieben:'; foreach ($author->related('book') as $book) { - $book->title; + echo $book->title; } } ``` -Im obigen Beispiel werden nur zwei Abfragen ausgeführt: +Dieser Code generiert nur zwei blitzschnelle SQL-Abfragen: ```sql SELECT * FROM `author`; -SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- ids of fetched authors +SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- id der ausgewählten Autoren +``` + + +Many-to-Many-Beziehung +---------------------- + +Für eine Many-to-many-Beziehung (M:N) ist die Existenz einer Verknüpfungstabelle erforderlich (in unserem Fall `book_tag`), die zwei Spalten mit Fremdschlüsseln (`book_id`, `tag_id`) enthält. Jede dieser Spalten verweist auf den Primärschlüssel einer der verbundenen Tabellen. Um die zugehörigen Daten zu erhalten, holen wir zuerst die Datensätze aus der Verknüpfungstabelle mit `related('book_tag')` und fahren dann mit den Zieldaten fort: + +```php +$book = $explorer->table('book')->get(1); +// gibt die Namen der dem Buch zugewiesenen Tags aus +foreach ($book->related('book_tag') as $bookTag) { + echo $bookTag->tag->name; // gibt den Namen des Tags über die Verknüpfungstabelle aus +} + +$tag = $explorer->table('tag')->get(1); +// oder umgekehrt: gibt die Namen der mit diesem Tag gekennzeichneten Bücher aus +foreach ($tag->related('book_tag') as $bookTag) { + echo $bookTag->book->title; // gibt den Namen des Buches aus +} +``` + +Der Explorer optimiert die SQL-Abfragen wieder in eine effiziente Form: + +```sql +SELECT * FROM `book`; +SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 2, ...)); -- id der ausgewählten Bücher +SELECT * FROM `tag` WHERE (`tag`.`id` IN (1, 2, ...)); -- id der in book_tag gefundenen Tags +``` + + +Abfragen über verwandte Tabellen +-------------------------------- + +In den Methoden `where()`, `select()`, `order()` und `group()` können Sie spezielle Notationen verwenden, um auf Spalten aus verbundenen Tabellen zuzugreifen. Der Explorer erstellt automatisch die erforderlichen JOINs. + +**Punktnotation** (`referenzierte_tabelle.spalte`) wird für Many-to-One- oder One-to-One-Beziehungen verwendet (Zugriff auf die übergeordnete Tabelle): + +```php +$books = $explorer->table('book'); + +// Findet Bücher, deren Autorname mit 'Jon' beginnt +$books->where('author.name LIKE ?', 'Jon%'); + +// Sortiert Bücher nach Autorennamen absteigend +$books->order('author.name DESC'); + +// Gibt den Buchtitel und den Autorennamen aus +$books->select('book.title, author.name'); +``` + +**Doppelpunktnotation** (`:untergeordnete_tabelle.spalte`) wird für die 1:N-Beziehung aus Sicht der übergeordneten Tabelle verwendet: + +```php +$authors = $explorer->table('author'); + +// Findet Autoren, die ein Buch mit 'PHP' im Titel geschrieben haben +$authors->where(':book.title LIKE ?', '%PHP%'); + +// Zählt die Anzahl der Bücher für jeden Autor +$authors->select('*, COUNT(:book.id) AS book_count') + ->group('author.id'); ``` +Im obigen Beispiel mit der Doppelpunktnotation (`:book.title`) ist die Spalte mit dem Fremdschlüssel nicht angegeben. Der Explorer erkennt automatisch die richtige Spalte anhand des Namens der übergeordneten Tabelle. In diesem Fall wird über die Spalte `book.author_id` verbunden, da der Name der Quelltabelle `author` ist. Wenn mehrere mögliche Verbindungen bestehen, wirft der Explorer eine [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. -Manuelles Erstellen des Explorers .[#toc-creating-explorer-manually] -==================================================================== +Die Verbindungspalte kann explizit in Klammern angegeben werden: -Eine Datenbankverbindung kann über die Anwendungskonfiguration erstellt werden. In diesem Fall wird ein `Nette\Database\Explorer` Dienst erstellt, der über den DI-Container als Abhängigkeit übergeben werden kann. +```php +// Findet Autoren, die ein Buch mit 'PHP' im Titel übersetzt haben +$authors->where(':book(translator_id).title LIKE ?', '%PHP%'); +``` -Wenn der Nette Database Explorer jedoch als eigenständiges Tool verwendet wird, muss eine Instanz des `Nette\Database\Explorer` Objekts manuell erstellt werden. +Notationen können für den Zugriff über mehrere Tabellen verkettet werden: ```php -// $storage implementiert Nette\Caching\Storage: -$storage = new Nette\Caching\Storages\FileStorage($tempDir); -$connection = new Nette\Database\Connection($dsn, $user, $password); -$structure = new Nette\Database\Structure($connection, $storage); -$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); -$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +// Findet Autoren von Büchern, die mit dem Tag 'PHP' gekennzeichnet sind +$authors->where(':book:book_tag.tag.name', 'PHP') + ->group('author.id'); ``` + + +Erweiterung der Bedingungen für JOIN +------------------------------------ + +Die Methode `joinWhere()` erweitert die Bedingungen, die in der `ON`-Klausel beim Verknüpfen von Tabellen in SQL angegeben werden. + +Nehmen wir an, wir möchten Bücher finden, die von einem bestimmten Übersetzer übersetzt wurden, und wir möchten die Bedingung direkt in den `JOIN` einbauen: + +```php +// Findet Bücher, die vom Übersetzer namens 'David' übersetzt wurden +$books = $explorer->table('book') + ->joinWhere('translator', 'translator.name', 'David'); +// LEFT JOIN author translator ON book.translator_id = translator.id AND (translator.name = 'David') +``` + +In der `joinWhere()`-Bedingung können wir dieselben Konstrukte wie in der `where()`-Methode verwenden - Operatoren, Fragezeichen-Platzhalter, Wertearrays oder SQL-Ausdrücke. + +Für komplexere Abfragen mit mehreren JOINs können wir Tabellenaliase definieren: + +```php +$tags = $explorer->table('tag') + ->joinWhere(':book_tag.book.author', 'book_author.born < ?', 1950) + ->alias(':book_tag.book.author', 'book_author'); +// LEFT JOIN `book_tag` ON `tag`.`id` = `book_tag`.`tag_id` +// LEFT JOIN `book` ON `book_tag`.`book_id` = `book`.`id` +// LEFT JOIN `author` `book_author` ON `book`.`author_id` = `book_author`.`id` +// AND (`book_author`.`born` < 1950) +``` + +Beachten Sie den Unterschied: Während die `where()`-Methode Bedingungen zur `WHERE`-Klausel hinzufügt, erweitert die `joinWhere()`-Methode die Bedingungen in der `ON`-Klausel beim Verknüpfen von Tabellen. diff --git a/database/de/guide.texy b/database/de/guide.texy new file mode 100644 index 0000000000..26e4b8c297 --- /dev/null +++ b/database/de/guide.texy @@ -0,0 +1,216 @@ +Nette Database +************** + +.[perex] +Nette Database ist eine leistungsstarke und elegante Datenbankschicht für PHP mit Schwerpunkt auf Einfachheit und intelligenten Funktionen. Sie bietet zwei Möglichkeiten zur Arbeit mit der Datenbank: den [Explorer] für eine schnelle Anwendungsentwicklung oder den [SQL-Zugriff |SQL way] für die direkte Arbeit mit Abfragen. + +<div class="grid gap-3"> +<div> + + +[SQL-Zugriff |SQL way] +====================== +- Sichere parametrisierte Abfragen +- Präzise Kontrolle über die Form der SQL-Abfragen +- Ideal für komplexe Abfragen mit erweiterten Funktionen +- Optimierung der Leistung mithilfe spezifischer SQL-Funktionen + +</div> + +<div> + + +[Explorer] +========== +- Schnelle Entwicklung ohne manuelles Schreiben von SQL +- Intuitive Arbeit mit Beziehungen zwischen Tabellen +- Automatische Optimierung von Abfragen +- Geeignet für schnelle und bequeme Arbeit mit der Datenbank + +</div> + +</div> + + +Installation +============ + +Die Bibliothek wird mit dem Werkzeug [Composer|best-practices:composer] heruntergeladen und installiert: + +```shell +composer require nette/database +``` + + +Unterstützte Datenbanken +======================== + +Nette Database unterstützt die folgenden Datenbanken: + +|* Datenbankserver |* DSN-Name |* Unterstützung in Explorer +|---------------------|-------------|----------------------- +| MySQL (>= 5.1) | `mysql` | Ja +| PostgreSQL (>= 9.0) | `pgsql` | Ja +| SQLite 3 (>= 3.8) | `sqlite` | Ja +| Oracle | `oci` | Nein +| MS SQL (PDO_SQLSRV) | `sqlsrv` | Ja +| MS SQL (PDO_DBLIB) | `mssql` | Nein +| ODBC | `odbc` | Nein + + +Zwei Zugänge zur Datenbank +========================== + +Nette Database gibt Ihnen die Wahl: Sie können entweder SQL-Abfragen direkt schreiben (SQL-Zugriff) oder sie automatisch generieren lassen (Explorer). Sehen wir uns an, wie beide Ansätze dieselben Aufgaben lösen: + +[SQL-Zugriff|sql way] - Manuelle SQL-Abfragen + +```php +// Einfügen eines Datensatzes +$database->query('INSERT INTO books', [ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// Abrufen von Datensätzen: Aktive Autoren und ihre Buchanzahl +$result = $database->query(' + SELECT authors.*, COUNT(books.id) AS books_count + FROM authors + LEFT JOIN books ON authors.id = books.author_id + WHERE authors.active = 1 + GROUP BY authors.id +'); + +// Ausgabe (nicht optimal, generiert N weitere Abfragen - N+1 Problem) +foreach ($result as $author) { + $books = $database->query(' + SELECT * FROM books + WHERE author_id = ? + ORDER BY published_at DESC + ', $author->id); + + echo "Autor $author->name hat $author->books_count Bücher geschrieben:\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +[Explorer-Zugriff|explorer] - Automatische Generierung von SQL + +```php +// Einfügen eines Datensatzes +$database->table('books')->insert([ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// Abrufen von Datensätzen: Aktive Autoren +$authors = $database->table('authors') + ->where('active', true); + +// Ausgabe (automatisch optimiert, generiert nur 2 Abfragen) +foreach ($authors as $author) { + $books = $author->related('books') // Holt zugehörige Bücher + ->order('published_at DESC'); + + echo "Autor $author->name hat {$books->count()} Bücher geschrieben:\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +Der Explorer-Zugriff generiert und optimiert SQL-Abfragen automatisch. Im gezeigten Beispiel generiert der SQL-Zugriff N+1 Abfragen (eine für Autoren und dann eine für die Bücher jedes Autors), während der Explorer die Abfragen automatisch optimiert und nur zwei durchführt - eine für Autoren und eine für alle ihre Bücher auf einmal. + +Beide Ansätze können in der Anwendung nach Bedarf beliebig kombiniert werden. + + +Verbindung und Konfiguration +============================ + +Um eine Verbindung zur Datenbank herzustellen, genügt es, eine Instanz der Klasse [api:Nette\Database\Connection] zu erstellen: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password); +``` + +Der Parameter `$dsn` (Data Source Name) ist derselbe, [den PDO verwendet |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], z.B. `mysql:host=127.0.0.1;dbname=test`. Im Fehlerfall wird eine Ausnahme `Nette\Database\ConnectionException` ausgelöst. + +Ein eleganterer Weg ist jedoch die Verwendung der [Anwendungskonfiguration |configuration]. Fügen Sie einfach einen `database`-Abschnitt hinzu, und die erforderlichen Objekte (Connection und Explorer) werden erstellt. Außerdem wird ein Datenbankpanel in der [Tracy |tracy:] Debug-Leiste angezeigt. + +```neon +database: + dsn: 'mysql:host=127.0.0.1;dbname=test' + user: root + password: password +``` + +Danach erhalten Sie das Verbindungsobjekt oder den Explorer [als Dienst aus dem DI-Container |dependency-injection:passing-dependencies], z.B. über Constructor Injection: + +```php +class Model +{ + public function __construct( + // oder Nette\Database\Explorer + private Nette\Database\Connection $database, + ) { + } +} +``` + +Mehr Informationen zur [Datenbankkonfiguration|configuration]. + + +Manuelle Erstellung des Explorers +--------------------------------- + +Wenn Sie keinen Nette DI-Container verwenden, können Sie die Instanz `Nette\Database\Explorer` manuell erstellen: + +```php +// Verbindung zur Datenbank +$connection = new Nette\Database\Connection('mysql:host=127.0.0.1;dbname=mydatabase', 'user', 'password'); +// Speicher für Cache, implementiert Nette\Caching\Storage, z.B.: +$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp/dir'); +// kümmert sich um die Reflexion der Datenbankstruktur +$structure = new Nette\Database\Structure($connection, $storage); +// definiert Regeln für das Mapping von Tabellen-, Spalten- und Fremdschlüsselnamen +$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); +$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +``` + + +Verbindungsverwaltung +===================== + +Beim Erstellen des `Connection`-Objekts wird die Verbindung automatisch hergestellt. Wenn Sie die Verbindung verzögern möchten, verwenden Sie den Lazy-Modus - diesen aktivieren Sie in der [Konfiguration|configuration] durch Setzen von `lazy`, oder so: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password, ['lazy' => true]); +``` + +Zur Verwaltung der Verbindung nutzen Sie die Methoden `connect()`, `disconnect()` und `reconnect()`. +- `connect()` stellt die Verbindung her, falls sie noch nicht existiert, und kann eine Ausnahme `Nette\Database\ConnectionException` auslösen. +- `disconnect()` trennt die aktuelle Verbindung zur Datenbank. +- `reconnect()` führt eine Trennung und anschließende erneute Verbindung zur Datenbank durch. Diese Methode kann ebenfalls eine Ausnahme `Nette\Database\ConnectionException` auslösen. + +Darüber hinaus können Sie Ereignisse im Zusammenhang mit der Verbindung über das Ereignis `onConnect` verfolgen, ein Array von Callbacks, die nach dem Aufbau der Verbindung zur Datenbank aufgerufen werden. + +```php +// wird nach der Verbindung zur Datenbank ausgeführt +$database->onConnect[] = function($database) { + echo "Verbunden mit der Datenbank"; +}; +``` + + +Tracy Debug Bar +=============== + +Wenn Sie [Tracy |tracy:] verwenden, wird automatisch das Database-Panel in der Debug-Bar aktiviert, das alle ausgeführten Abfragen, ihre Parameter, die Ausführungszeit und den Ort im Code anzeigt, an dem sie aufgerufen wurden. + +[* db-panel.webp *] diff --git a/database/de/mapping.texy b/database/de/mapping.texy new file mode 100644 index 0000000000..ba543d9207 --- /dev/null +++ b/database/de/mapping.texy @@ -0,0 +1,55 @@ +Typkonvertierung +**************** + +.[perex] +Nette Database konvertiert automatisch Werte, die aus der Datenbank zurückgegeben werden, in die entsprechenden PHP-Typen. + + +Datum und Uhrzeit +----------------- + +Zeitangaben werden in `Nette\Utils\DateTime`-Objekte konvertiert. Wenn Sie möchten, dass Zeitangaben in unveränderliche `Nette\Database\DateTime`-Objekte konvertiert werden, setzen Sie in der [Konfiguration|configuration] die Option `newDateTime` auf true. + +```php +$row = $database->fetch('SELECT created_at FROM articles'); +echo $row->created_at instanceof DateTime; // true +echo $row->created_at->format('j. n. Y'); +``` + +Bei MySQL wird der Datentyp `TIME` in `DateInterval`-Objekte konvertiert. + + +Boolesche Werte +--------------- + +Boolesche Werte werden automatisch in `true` oder `false` konvertiert. Bei MySQL wird `TINYINT(1)` konvertiert, wenn wir in der [Konfiguration|configuration] `convertBoolean` setzen. + +```php +$row = $database->fetch('SELECT is_published FROM articles'); +echo gettype($row->is_published); // 'boolean' +``` + + +Numerische Werte +---------------- + +Numerische Werte werden je nach Spaltentyp in der Datenbank in `int` oder `float` konvertiert: + +```php +$row = $database->fetch('SELECT id, price FROM products'); +echo gettype($row->id); // integer +echo gettype($row->price); // float +``` + + +Eigene Normalisierung +--------------------- + +Mit der Methode `setRowNormalizer(?callable $normalizer)` können Sie eine eigene Funktion zur Transformation von Zeilen aus der Datenbank festlegen. Dies ist nützlich, zum Beispiel für die automatische Konvertierung von Datentypen. + +```php +$database->setRowNormalizer(function(array $row, ResultSet $resultSet): array { + // hier findet die Typkonvertierung statt + return $row; +}); +``` diff --git a/database/de/reflection.texy b/database/de/reflection.texy new file mode 100644 index 0000000000..e98fbe8bc8 --- /dev/null +++ b/database/de/reflection.texy @@ -0,0 +1,125 @@ +Strukturreflexion +***************** + +.{data-version:3.2.1} +Nette Database bietet Werkzeuge zur Introspektion der Datenbankstruktur mithilfe der Klasse [api:Nette\Database\Reflection]. Sie ermöglicht das Abrufen von Informationen über Tabellen, Spalten, Indizes und Fremdschlüssel. Die Reflexion können Sie zur Generierung von Schemata, zur Erstellung flexibler Anwendungen, die mit der Datenbank arbeiten, oder für allgemeine Datenbankwerkzeuge nutzen. + +Das Reflexionsobjekt erhalten wir aus der Instanz der Datenbankverbindung: + +```php +$reflection = $database->getReflection(); +``` + + +Abrufen von Tabellen +-------------------- + +Die readonly-Eigenschaft `$reflection->tables` enthält ein assoziatives Array aller Tabellen in der Datenbank: + +```php +// Ausgabe der Namen aller Tabellen +foreach ($reflection->tables as $name => $table) { + echo $name . "\n"; +} +``` + +Es stehen noch zwei weitere Methoden zur Verfügung: + +```php +// Überprüfung der Existenz einer Tabelle +if ($reflection->hasTable('users')) { + echo "Tabelle users existiert"; +} + +// Gibt das Tabellenobjekt zurück; wenn es nicht existiert, wird eine Ausnahme ausgelöst +$table = $reflection->getTable('users'); +``` + + +Informationen über eine Tabelle +------------------------------- + +Eine Tabelle wird durch das Objekt [Table|api:Nette\Database\Reflection\Table] repräsentiert, das die folgenden readonly-Eigenschaften bereitstellt: + +- `$name: string` – Name der Tabelle +- `$view: bool` – ob es sich um eine Ansicht handelt +- `$fullName: ?string` – vollständiger Name der Tabelle einschließlich Schema (falls vorhanden) +- `$columns: array<string, Column>` – assoziatives Array der Tabellenspalten +- `$indexes: Index[]` – Array der Tabellenindizes +- `$primaryKey: ?Index` – Primärschlüssel der Tabelle oder null +- `$foreignKeys: ForeignKey[]` – Array der Fremdschlüssel der Tabelle + + +Spalten +------- + +Die Eigenschaft `columns` der Tabelle liefert ein assoziatives Array von Spalten, wobei der Schlüssel der Spaltenname und der Wert eine Instanz von [Column|api:Nette\Database\Reflection\Column] mit diesen Eigenschaften ist: + +- `$name: string` – Name der Spalte +- `$table: ?Table` – Referenz auf die Tabelle der Spalte +- `$nativeType: string` – nativer Datenbanktyp +- `$size: ?int` – Größe/Länge des Typs +- `$nullable: bool` – ob die Spalte NULL enthalten kann +- `$default: mixed` – Standardwert der Spalte +- `$autoIncrement: bool` – ob die Spalte auto-increment ist +- `$primary: bool` – ob sie Teil des Primärschlüssels ist +- `$vendor: array` – zusätzliche Metadaten, die spezifisch für das jeweilige Datenbanksystem sind + +```php +foreach ($table->columns as $name => $column) { + echo "Spalte: $name\n"; + echo "Typ: {$column->nativeType}\n"; + echo "Nullable: " . ($column->nullable ? 'Ja' : 'Nein') . "\n"; +} +``` + + +Indizes +------- + +Die Eigenschaft `indexes` der Tabelle liefert ein Array von Indizes, wobei jeder Index eine Instanz von [Index|api:Nette\Database\Reflection\Index] mit diesen Eigenschaften ist: + +- `$columns: Column[]` – Array der Spalten, die den Index bilden +- `$unique: bool` – ob der Index eindeutig ist +- `$primary: bool` – ob es sich um den Primärschlüssel handelt +- `$name: ?string` – Name des Index + +Der Primärschlüssel der Tabelle kann über die Eigenschaft `primaryKey` abgerufen werden, die entweder ein `Index`-Objekt oder `null` zurückgibt, falls die Tabelle keinen Primärschlüssel hat. + +```php +// Ausgabe der Indizes +foreach ($table->indexes as $index) { + $columns = implode(', ', array_map(fn($col) => $col->name, $index->columns)); + echo "Index" . ($index->name ? " {$index->name}" : '') . ":\n"; + echo " Spalten: $columns\n"; + echo " Unique: " . ($index->unique ? 'Ja' : 'Nein') . "\n"; +} + +// Ausgabe des Primärschlüssels +if ($primaryKey = $table->primaryKey) { + $columns = implode(', ', array_map(fn($col) => $col->name, $primaryKey->columns)); + echo "Primärschlüssel: $columns\n"; +} +``` + + +Fremdschlüssel +-------------- + +Die Eigenschaft `foreignKeys` der Tabelle liefert ein Array von Fremdschlüsseln, wobei jeder Fremdschlüssel eine Instanz von [ForeignKey|api:Nette\Database\Reflection\ForeignKey] mit diesen Eigenschaften ist: + +- `$foreignTable: Table` – referenzierte Tabelle +- `$localColumns: Column[]` – Array der lokalen Spalten +- `$foreignColumns: Column[]` – Array der referenzierten Spalten +- `$name: ?string` – Name des Fremdschlüssels + +```php +// Ausgabe der Fremdschlüssel +foreach ($table->foreignKeys as $fk) { + $localCols = implode(', ', array_map(fn($col) => $col->name, $fk->localColumns)); + $foreignCols = implode(', ', array_map(fn($col) => $col->name, $fk->foreignColumns)); + + echo "FK" . ($fk->name ? " {$fk->name}" : '') . ":\n"; + echo " $localCols -> {$fk->foreignTable->name}($foreignCols)\n"; +} +``` diff --git a/database/de/security.texy b/database/de/security.texy new file mode 100644 index 0000000000..5d2c61e725 --- /dev/null +++ b/database/de/security.texy @@ -0,0 +1,185 @@ +Sicherheitsrisiken +****************** + +<div class=perex> + +Datenbanken enthalten oft sensible Daten und ermöglichen die Durchführung gefährlicher Operationen. Für die sichere Arbeit mit Nette Database ist es entscheidend: + +- Den Unterschied zwischen sicherer und unsicherer API zu verstehen +- Parametrisierte Abfragen zu verwenden +- Eingabedaten korrekt zu validieren + +</div> + + +Was ist SQL Injection? +====================== + +SQL Injection ist das schwerwiegendste Sicherheitsrisiko bei der Arbeit mit Datenbanken. Es entsteht, wenn unbehandelte Benutzereingaben Teil einer SQL-Abfrage werden. Ein Angreifer kann eigene SQL-Befehle einschleusen und dadurch: +- Unberechtigten Zugriff auf Daten erlangen +- Daten in der Datenbank modifizieren oder löschen +- Authentifizierung umgehen + +```php +// ❌ UNSICHERER CODE - anfällig für SQL-Injection +$database->query("SELECT * FROM users WHERE name = '$_GET[name]'"); + +// Ein Angreifer kann beispielsweise den Wert eingeben: ' OR '1'='1 +// Die resultierende Abfrage lautet dann: SELECT * FROM users WHERE name = '' OR '1'='1' +// Was alle Benutzer zurückgibt +``` + +Dasselbe gilt auch für den Database Explorer: + +```php +// ❌ UNSICHERER CODE - anfällig für SQL-Injection +$table->where('name = ' . $_GET['name']); +$table->where("name = '$_GET[name]'"); +``` + + +Parametrisierte Abfragen +======================== + +Die grundlegende Verteidigung gegen SQL-Injection sind parametrisierte Abfragen. Nette Database bietet mehrere Möglichkeiten, sie zu verwenden. + +Der einfachste Weg ist die Verwendung von **Fragezeichen-Platzhaltern**: + +```php +// ✅ Sichere parametrisierte Abfrage +$database->query('SELECT * FROM users WHERE name = ?', $name); + +// ✅ Sichere Bedingung im Explorer +$table->where('name = ?', $name); +``` + +Dies gilt für alle weiteren Methoden im [Database Explorer|explorer], die das Einfügen von Ausdrücken mit Fragezeichen-Platzhaltern und Parametern ermöglichen. + +Für INSERT-, UPDATE-Befehle oder die WHERE-Klausel können wir Werte in einem Array übergeben: + +```php +// ✅ Sicherer INSERT +$database->query('INSERT INTO users', [ + 'name' => $name, + 'email' => $email, +]); + +// ✅ Sicherer INSERT im Explorer +$table->insert([ + 'name' => $name, + 'email' => $email, +]); +``` + + +Validierung von Parameterwerten +=============================== + +Parametrisierte Abfragen sind der grundlegende Baustein für die sichere Arbeit mit Datenbanken. Dennoch müssen die Werte, die Sie als Parameter übergeben, sorgfältig validiert werden, um andere Arten von Fehlern und potenziellen Problemen zu vermeiden. + + +Typkontrolle +------------ + +**Stellen Sie sicher, dass Parameter den erwarteten Datentyp haben.** Nette Database versucht zwar, Typen zu konvertieren, aber die Übergabe eines völlig falschen Typs (z.B. ein Array, wo ein String erwartet wird) kann zu Fehlern oder unerwartetem Verhalten führen. + +Wenn beispielsweise `$name` in den vorherigen Beispielen unerwartet ein Array anstelle einer Zeichenkette wäre, könnte dies einen Fehler auslösen. Verwenden Sie daher **niemals** unvalidierte Rohdaten aus `$_GET`, `$_POST`, `$_COOKIE` oder anderen externen Quellen direkt in Datenbankabfragen. + + +Formale und Wertebereichs-Kontrolle +----------------------------------- + +Überprüfen Sie, ob die Daten das erwartete Format haben (z.B. gültige E-Mail-Adresse, UTF-8-Kodierung) und ob Werte innerhalb zulässiger Grenzen liegen (z.B. Länge einer Zeichenkette, Wertebereich einer Zahl). + +Obwohl die Datenbank selbst einige dieser Prüfungen durchführen kann (z.B. durch Spaltentypen und Constraints), ist es besser, ungültige Daten bereits in der Anwendung abzufangen. Das Verhalten der Datenbank bei ungültigen Daten kann variieren (Fehler, stilles Abschneiden, etc.). + + +Domänen-/Logik-Kontrolle +------------------------ + +Die dritte Ebene stellen logische Kontrollen dar, die spezifisch für Ihre Anwendung sind. Zum Beispiel die Überprüfung, ob Werte aus Select-Boxen den angebotenen Optionen entsprechen, ob Zahlen im erwarteten Bereich liegen (z. B. Alter 0-150 Jahre) oder ob gegenseitige Abhängigkeiten zwischen Werten sinnvoll sind. + + +Empfohlene Validierungsmethoden +------------------------------- + +- Verwenden Sie [Nette Forms|forms:], die eine robuste Validierung für Benutzereingaben bieten. +- Nutzen Sie Type Hints in [Presentern|application:] für `action*()` und `render*()` Methodenparameter. +- Implementieren Sie eine eigene Validierungsschicht mit PHP-Funktionen wie `filter_var()`, `mb_strlen()`, regulären Ausdrücken oder spezialisierten Validierungsbibliotheken. + + +Sichere Arbeit mit Spaltennamen (Bezeichnern) +============================================= + +Während Parameterwerte durch Parametrisierung geschützt sind, müssen **Spalten- und Tabellennamen (Bezeichner)**, wenn sie dynamisch sind, anders behandelt werden. Sie können nicht direkt durch Fragezeichen-Platzhalter ersetzt werden. + +```php +// ❌ UNSICHERER CODE - Schlüssel im Array sind nicht behandelt +$database->query('INSERT INTO users', $_POST); +``` + +Bei INSERT- und UPDATE-Befehlen ist dies ein kritischer Sicherheitsfehler - ein Angreifer kann jede beliebige Spalte in die Datenbank einfügen oder ändern. Er könnte sich beispielsweise `is_admin = 1` setzen oder beliebige Daten in sensible Spalten einfügen (sog. Mass Assignment Vulnerability). + +In WHERE-Bedingungen ist dies noch gefährlicher, da sie Operatoren enthalten können: + +```php +// ❌ UNSICHERER CODE - Schlüssel im Array sind nicht behandelt +$_POST['salary >'] = 100000; +$database->query('SELECT * FROM users WHERE', $_POST); +// führt die Abfrage WHERE (`salary` > 100000) aus +``` + +Ein Angreifer kann diesen Ansatz nutzen, um systematisch die Gehälter von Mitarbeitern zu ermitteln. Er beginnt beispielsweise mit einer Abfrage nach Gehältern über 100.000, dann unter 50.000 und durch schrittweise Eingrenzung des Bereichs kann er die ungefähren Gehälter aller Mitarbeiter aufdecken. Diese Art von Angriff wird als SQL-Enumeration bezeichnet. + +Die Methoden `where()` und `whereOr()` sind noch [viel flexibler |explorer#where] und unterstützen in Schlüsseln und Werten SQL-Ausdrücke einschließlich Operatoren und Funktionen. Dies gibt einem Angreifer die Möglichkeit, eine SQL-Injection durchzuführen: + +```php +// ❌ UNSICHERER CODE - Angreifer kann eigenes SQL einschleusen +$_POST = ['0) UNION SELECT name, salary FROM users WHERE (1']; +$table->where($_POST); +// führt die Abfrage WHERE (0) UNION SELECT name, salary FROM users WHERE (1) aus +``` + +Dieser Angriff beendet die ursprüngliche Bedingung mit `0)`, fügt mit `UNION` ein eigenes `SELECT` hinzu, um sensible Daten aus der Tabelle `users` zu erhalten, und schließt die syntaktisch korrekte Abfrage mit `WHERE (1)` ab. + + +Whitelist für Spalten +--------------------- + +Für die sichere Arbeit mit Spaltennamen benötigen wir einen Mechanismus, der sicherstellt, dass der Benutzer nur mit erlaubten Spalten arbeiten kann und keine eigenen hinzufügen kann. Wir könnten versuchen, gefährliche Spaltennamen zu erkennen und zu blockieren (Blacklist), aber dieser Ansatz ist unzuverlässig - ein Angreifer kann immer einen neuen Weg finden, einen gefährlichen Spaltennamen zu schreiben, den wir nicht vorhergesehen haben. + +Daher ist es viel sicherer, die Logik umzukehren und eine explizite Liste erlaubter Spalten zu definieren (Whitelist): + +```php +// Spalten, die der Benutzer bearbeiten darf +$allowedColumns = ['name', 'email', 'active']; + +// Wir entfernen alle nicht erlaubten Spalten aus der Eingabe +$filteredData = array_intersect_key($userData, array_flip($allowedColumns)); // Flip für O(1) lookup + +// ✅ Nun können wir sicher in Abfragen verwenden, wie zum Beispiel: +$database->query('INSERT INTO users', $filteredData); +$table->update($filteredData); +$table->where($filteredData); +``` + + +Dynamische Bezeichner +===================== + +Für dynamische Tabellen- und Spaltennamen verwenden Sie den Platzhalter `?name`. Dieser stellt das korrekte Escaping von Bezeichnern gemäß der Syntax der jeweiligen Datenbank sicher (z. B. durch Backticks in MySQL): + +```php +// ✅ Sichere Verwendung vertrauenswürdiger Bezeichner +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name', $column, $table); +// Ergebnis in MySQL: SELECT `name` FROM `users` +``` + +Wichtig: Verwenden Sie das Symbol `?name` nur für vertrauenswürdige Werte, die im Anwendungscode definiert sind. Für Werte vom Benutzer verwenden Sie wieder eine [Whitelist |#Whitelist für Spalten]. Andernfalls setzen Sie sich Sicherheitsrisiken aus: + +```php +// ❌ UNSICHER - verwenden Sie niemals Benutzereingaben +$database->query('SELECT ?name FROM users', $_GET['column']); +``` diff --git a/database/de/sql-way.texy b/database/de/sql-way.texy new file mode 100644 index 0000000000..3eb97494c8 --- /dev/null +++ b/database/de/sql-way.texy @@ -0,0 +1,513 @@ +SQL-Zugriff +*********** + +.[perex] +Nette Database bietet zwei Wege: Sie können SQL-Abfragen selbst schreiben (SQL-Zugriff) oder sie automatisch generieren lassen (siehe [Explorer |explorer]). Der SQL-Zugriff gibt Ihnen die volle Kontrolle über die Abfragen und gewährleistet gleichzeitig deren sichere Erstellung. + +.[note] +Details zur Verbindung und Konfiguration der Datenbank finden Sie im Kapitel [Verbindung und Konfiguration |guide#Verbindung und Konfiguration]. + + +Grundlegende Abfragen +===================== + +Für Abfragen an die Datenbank dient die Methode `query()`. Sie gibt ein [ResultSet |api:Nette\Database\ResultSet]-Objekt zurück, das das Ergebnis der Abfrage repräsentiert. Im Fehlerfall löst die Methode eine [Ausnahme aus|exceptions]. Das Ergebnis der Abfrage kann mit einer `foreach`-Schleife durchlaufen werden, oder es können einige der [Hilfsfunktionen |#Daten abrufen] verwendet werden. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; +} +``` + +Für das sichere Einfügen von Werten in SQL-Abfragen verwenden wir parametrisierte Abfragen. Nette Database macht diese maximal einfach – fügen Sie einfach ein Komma und den Wert nach der SQL-Abfrage hinzu: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Bei mehreren Parametern haben Sie zwei Schreibmöglichkeiten. Entweder können Sie die SQL-Abfrage mit Parametern „durchsetzen“: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age); +``` + +Oder schreiben Sie zuerst die gesamte SQL-Abfrage und fügen dann alle Parameter an: + +```php +$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age); +``` + + +Schutz vor SQL-Injection +======================== + +Warum ist es wichtig, parametrisierte Abfragen zu verwenden? Weil sie Sie vor einem Angriff namens SQL-Injection schützen, bei dem ein Angreifer eigene SQL-Befehle einschleusen und so Daten in der Datenbank gewinnen oder beschädigen könnte. + +.[warning] +**Fügen Sie Variablen niemals direkt in eine SQL-Abfrage ein!** Verwenden Sie immer parametrisierte Abfragen, die Sie vor SQL-Injection schützen. + +```php +// ❌ GEFÄHRLICHER CODE - anfällig für SQL-Injection +$database->query("SELECT * FROM users WHERE name = '$name'"); + +// ✅ Sichere parametrisierte Abfrage +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Machen Sie sich mit den [möglichen Sicherheitsrisiken |security] vertraut. + + +Abfragetechniken +================ + + +WHERE-Bedingungen +----------------- + +WHERE-Bedingungen können als assoziatives Array geschrieben werden, wobei die Schlüssel Spaltennamen und die Werte Daten zum Vergleich sind. Nette Database wählt automatisch den am besten geeigneten SQL-Operator basierend auf dem Werttyp aus. + +```php +$database->query('SELECT * FROM users WHERE', [ + 'name' => 'John', + 'active' => true, +]); +// WHERE `name` = 'John' AND `active` = 1 +``` + +Im Schlüssel können Sie auch explizit einen Operator für den Vergleich angeben: + +```php +$database->query('SELECT * FROM users WHERE', [ + 'age >' => 25, // verwendet Operator > + 'name LIKE' => '%John%', // verwendet Operator LIKE + 'email NOT LIKE' => '%example.com%', // verwendet Operator NOT LIKE +]); +// WHERE `age` > 25 AND `name` LIKE '%John%' AND `email` NOT LIKE '%example.com%' +``` + +Nette behandelt automatisch Sonderfälle wie `null`-Werte oder Arrays. + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name' => 'Laptop', // verwendet Operator = + 'category_id' => [1, 2, 3], // verwendet IN + 'description' => null, // verwendet IS NULL +]); +// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL +``` + +Für negative Bedingungen verwenden Sie den `NOT`-Operator: + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name NOT' => 'Laptop', // verwendet Operator <> + 'category_id NOT' => [1, 2, 3], // verwendet NOT IN + 'description NOT' => null, // verwendet IS NOT NULL + 'id' => [], // wird ausgelassen +]); +// WHERE `name` <> 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL +``` + +Zum Verknüpfen von Bedingungen wird der `AND`-Operator verwendet. Dies kann mit dem [Platzhalter ?or |#SQL-Erstellungs-Hinweise] geändert werden. + + +ORDER BY-Regeln +--------------- + +Die `ORDER BY`-Sortierung kann mithilfe eines Arrays angegeben werden. In den Schlüsseln geben wir die Spalten an, und der Wert ist ein Boolean, der angibt, ob aufsteigend sortiert werden soll: + +```php +$database->query('SELECT id FROM author ORDER BY', [ + 'id' => true, // aufsteigend + 'name' => false, // absteigend +]); +// SELECT id FROM author ORDER BY `id`, `name` DESC +``` + + +Einfügen von Daten (INSERT) +--------------------------- + +Zum Einfügen von Datensätzen wird der SQL-Befehl `INSERT` verwendet. + +```php +$values = [ + 'name' => 'John Doe', + 'email' => 'john@example.com', +]; +$database->query('INSERT INTO users ?', $values); +$userId = $database->getInsertId(); +``` + +Die Methode `getInsertId()` gibt die ID der zuletzt eingefügten Zeile zurück. Bei einigen Datenbanken (z. B. PostgreSQL) muss der Name der Sequenz, aus der die ID generiert werden soll, als Parameter mit `$database->getInsertId($sequenceId)` angegeben werden. + +Als Parameter können wir auch [#Spezielle Werte] wie Dateien, DateTime-Objekte oder Enum-Typen übergeben. + +Einfügen mehrerer Datensätze auf einmal: + +```php +$database->query('INSERT INTO users ?', [ + ['name' => 'User 1', 'email' => 'user1@mail.com'], + ['name' => 'User 2', 'email' => 'user2@mail.com'], +]); +``` + +Ein mehrfacher INSERT ist viel schneller, da nur eine Datenbankabfrage anstelle vieler einzelner ausgeführt wird. + +**Sicherheitshinweis:** Verwenden Sie niemals unvalidierte Daten als `$values`. Machen Sie sich mit den [möglichen Risiken |security#Sichere Arbeit mit Spaltennamen Bezeichnern] vertraut. + + +Aktualisieren von Daten (UPDATE) +-------------------------------- + +Zum Aktualisieren von Datensätzen wird der SQL-Befehl `UPDATE` verwendet. + +```php +// Aktualisierung eines einzelnen Datensatzes +$values = [ + 'name' => 'John Smith', +]; +$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1); +``` + +Die Anzahl der betroffenen Zeilen wird von `$result->getRowCount()` zurückgegeben. + +Für UPDATE können wir die Operatoren `+=` und `-=` verwenden: + +```php +$database->query('UPDATE users SET ? WHERE id = ?', [ + 'login_count+=' => 1, // Inkrementierung von login_count +], 1); +``` + +Beispiel für das Einfügen oder Ändern eines Datensatzes, falls er bereits existiert. Wir verwenden die Technik `ON DUPLICATE KEY UPDATE`: + +```php +$values = [ + 'name' => $name, + 'year' => $year, +]; +$database->query('INSERT INTO users ? ON DUPLICATE KEY UPDATE ?', + $values + ['id' => $id], + $values, +); +// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) +// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 +``` + +Beachten Sie, dass Nette Database erkennt, in welchem Kontext des SQL-Befehls wir den Parameter mit dem Array einfügen, und daraus den SQL-Code entsprechend erstellt. So wurde aus dem ersten Array `(id, name, year) VALUES (123, 'Jim', 1978)` erstellt, während das zweite in die Form `name = 'Jim', year = 1978` umgewandelt wurde. Wir gehen darauf im Abschnitt [#SQL-Erstellungs-Hinweise] näher ein. + + +Löschen von Daten (DELETE) +-------------------------- + +Zum Löschen von Datensätzen wird der SQL-Befehl `DELETE` verwendet. Beispiel zur Ermittlung der Anzahl gelöschter Zeilen: + +```php +$count = $database->query('DELETE FROM users WHERE id = ?', 1) + ->getRowCount(); +``` + + +SQL-Erstellungs-Hinweise +------------------------ + +Ein Hinweis ist ein spezieller Platzhalter in einer SQL-Abfrage, der angibt, wie der Wert des Parameters in einen SQL-Ausdruck umgeschrieben werden soll: + +| Hinweis | Beschreibung | Automatisch verwendet +|-----------|-------------------------------------------------------|----------------------------- +| `?name` | Wird zum Einfügen von Tabellen- oder Spaltennamen verwendet | - +| `?values` | Generiert `(key, ...) VALUES (value, ...)` | `INSERT ... ?`, `REPLACE ... ?` +| `?set` | Generiert Zuweisung `key = value, ...` | `SET ?`, `KEY UPDATE ?` +| `?and` | Verknüpft Bedingungen im Array mit dem `AND`-Operator | `WHERE ?`, `HAVING ?` +| `?or` | Verknüpft Bedingungen im Array mit dem `OR`-Operator | - +| `?order` | Generiert `ORDER BY`-Klausel | `ORDER BY ?`, `GROUP BY ?` + +Für das dynamische Einfügen von Tabellen- und Spaltennamen in die Abfrage dient der Platzhalter `?name`. Nette Database kümmert sich um die korrekte Behandlung von Bezeichnern gemäß den Konventionen der jeweiligen Datenbank (z. B. das Einschließen in Backticks in MySQL). + +```php +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table); +// SELECT `name` FROM `users` WHERE id = 1 (in MySQL) +``` + +**Warnung:** Verwenden Sie das Symbol `?name` nur für Tabellen- und Spaltennamen aus validierten Eingaben, andernfalls setzen Sie sich einem [Sicherheitsrisiko |security#Dynamische Bezeichner] aus. + +Andere Hinweise müssen normalerweise nicht angegeben werden, da Nette beim Erstellen der SQL-Abfrage eine intelligente Autoerkennung verwendet (siehe dritte Spalte der Tabelle). Sie können ihn jedoch beispielsweise in einer Situation verwenden, in der Sie Bedingungen mit `OR` anstelle von `AND` verknüpfen möchten: + +```php +$database->query('SELECT * FROM users WHERE ?or', [ + 'name' => 'John', + 'email' => 'john@example.com', +]); +// SELECT * FROM users WHERE `name` = 'John' OR `email` = 'john@example.com' +``` + + +Spezielle Werte +--------------- + +Neben den üblichen skalaren Typen (String, Int, Bool) können Sie auch spezielle Werte als Parameter übergeben: + +- Dateien: `fopen('image.gif', 'r')` fügt den binären Inhalt der Datei ein +- Datum und Uhrzeit: `DateTime`-Objekte werden in das Datenbankformat konvertiert +- Enum-Typen: `enum`-Instanzen werden in ihren Wert konvertiert +- SQL-Literale: Erstellt mit `Connection::literal('NOW()')` werden direkt in die Abfrage eingefügt + +```php +$database->query('INSERT INTO articles ?', [ + 'title' => 'My Article', + 'published_at' => new DateTime, + 'content' => fopen('image.png', 'r'), + 'state' => Status::Draft, +]); +``` + +Bei Datenbanken, die keine native Unterstützung für den Datentyp `datetime` haben (wie SQLite und Oracle), wird `DateTime` in den Wert konvertiert, der in der [Datenbankkonfiguration|configuration] durch den Eintrag `formatDateTime` festgelegt ist (Standardwert ist `U` – Unix-Timestamp). + + +SQL-Literale +------------ + +In einigen Fällen müssen Sie SQL-Code direkt als Wert angeben, der jedoch nicht als Zeichenkette verstanden und maskiert werden soll. Dafür dienen Objekte der Klasse `Nette\Database\SqlLiteral`. Sie werden durch die Methode `Connection::literal()` erstellt. + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + 'year >' => $database::literal('YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) +``` + +Oder alternativ: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) +``` + +SQL-Literale können Parameter enthalten: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > ? AND year < ?', $min, $max), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) +``` + +Dadurch können wir interessante Kombinationen erstellen: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('?or', [ + 'active' => true, + 'role' => $role, + ]), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') +``` + + +Daten abrufen +============= + + +Abkürzungen für SELECT-Abfragen +------------------------------- + +Zur Vereinfachung des Datenabrufs bietet `Connection` mehrere Abkürzungen, die den Aufruf von `query()` mit dem anschließenden `fetch*()` kombinieren. Diese Methoden akzeptieren die gleichen Parameter wie `query()`, d.h. die SQL-Abfrage und optionale Parameter. Eine vollständige Beschreibung der `fetch*()`-Methoden finden Sie [unten |#fetch]. + +| `fetch($sql, ...$params): ?Row` | Führt die Abfrage aus und gibt die erste Zeile als `Row`-Objekt zurück +| `fetchAll($sql, ...$params): array` | Führt die Abfrage aus und gibt alle Zeilen als Array von `Row`-Objekten zurück +| `fetchPairs($sql, ...$params): array` | Führt die Abfrage aus und gibt ein assoziatives Array zurück, wobei die erste Spalte den Schlüssel und die zweite den Wert darstellt +| `fetchField($sql, ...$params): mixed` | Führt die Abfrage aus und gibt den Wert des ersten Feldes der ersten Zeile zurück +| `fetchList($sql, ...$params): ?array` | Führt die Abfrage aus und gibt die erste Zeile als indiziertes Array zurück + +Beispiel: + +```php +// fetchField() - gibt den Wert der ersten Zelle zurück +$count = $database->query('SELECT COUNT(*) FROM articles') + ->fetchField(); +``` + + +`foreach` - Iteration über Zeilen +--------------------------------- + +Nach Ausführung der Abfrage wird ein [ResultSet|api:Nette\Database\ResultSet]-Objekt zurückgegeben, das es ermöglicht, die Ergebnisse auf verschiedene Arten zu durchlaufen. Der einfachste Weg, eine Abfrage auszuführen und Zeilen zu erhalten, ist die Iteration in einer `foreach`-Schleife. Diese Methode ist am speicherschonendsten, da die Daten schrittweise zurückgegeben und nicht auf einmal im Speicher abgelegt werden. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; + // ... +} +``` + +.[note] +`ResultSet` kann nur einmal iteriert werden. Wenn Sie wiederholt iterieren müssen, müssen Sie die Daten zuerst in ein Array laden, zum Beispiel mit der Methode `fetchAll()`. + + +fetch(): ?Row .[method] +----------------------- + +Gibt eine Zeile als `Row`-Objekt zurück. Wenn keine weiteren Zeilen existieren, wird `null` zurückgegeben. Verschiebt den internen Zeiger auf die nächste Zeile. + +```php +$result = $database->query('SELECT * FROM users'); +$row = $result->fetch(); // lädt die erste Zeile +if ($row) { + echo $row->name; +} +``` + + +fetchAll(): array .[method] +--------------------------- + +Gibt alle verbleibenden Zeilen aus dem `ResultSet` als Array von `Row`-Objekten zurück. + +```php +$result = $database->query('SELECT * FROM users'); +$rows = $result->fetchAll(); // lädt alle Zeilen +foreach ($rows as $row) { + echo $row->name; +} +``` + + +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Gibt die Ergebnisse als assoziatives Array zurück. Das erste Argument gibt den Namen der Spalte an, die als Schlüssel im Array verwendet wird, das zweite Argument gibt den Namen der Spalte an, die als Wert verwendet wird: + +```php +$result = $database->query('SELECT id, name FROM users'); +$names = $result->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] +``` + +Wenn wir nur den ersten Parameter angeben, ist der Wert die gesamte Zeile, d.h. das `Row`-Objekt: + +```php +$rows = $result->fetchPairs('id'); +// [1 => Row(id: 1, name: 'John'), 2 => Row(id: 2, name: 'Jane'), ...] +``` + +Bei doppelten Schlüsseln wird der Wert aus der letzten Zeile verwendet. Bei Verwendung von `null` als Schlüssel wird das Array numerisch ab Null indiziert (dann treten keine Kollisionen auf): + +```php +$names = $result->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] +``` + + +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Alternativ können Sie einen Callback als Parameter angeben, der für jede Zeile entweder den Wert selbst oder ein Schlüssel-Wert-Paar zurückgibt. + +```php +$result = $database->query('SELECT * FROM users'); +$items = $result->fetchPairs(fn($row) => "$row->id - $row->name"); +// ['1 - John', '2 - Jane', ...] + +// Der Callback kann auch ein Array mit einem Schlüssel-Wert-Paar zurückgeben: +$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]); +// ['John' => 46, 'Jane' => 21, ...] +``` + + +fetchField(): mixed .[method] +----------------------------- + +Gibt den Wert des ersten Feldes der aktuellen Zeile zurück. Wenn keine weiteren Zeilen existieren, wird `null` zurückgegeben. Verschiebt den internen Zeiger auf die nächste Zeile. + +```php +$result = $database->query('SELECT name FROM users'); +$name = $result->fetchField(); // lädt den Namen aus der ersten Zeile +``` + + +fetchList(): ?array .[method] +----------------------------- + +Gibt eine Zeile als indiziertes Array zurück. Wenn keine weiteren Zeilen existieren, wird `null` zurückgegeben. Verschiebt den internen Zeiger auf die nächste Zeile. + +```php +$result = $database->query('SELECT name, email FROM users'); +$row = $result->fetchList(); // ['John', 'john@example.com'] +``` + + +getRowCount(): ?int .[method] +----------------------------- + +Gibt die Anzahl der von der letzten `UPDATE`- oder `DELETE`-Abfrage betroffenen Zeilen zurück. Bei `SELECT` ist dies die Anzahl der zurückgegebenen Zeilen, diese ist jedoch möglicherweise nicht bekannt – in diesem Fall gibt die Methode `null` zurück. + + +getColumnCount(): ?int .[method] +-------------------------------- + +Gibt die Anzahl der Spalten im `ResultSet` zurück. + + +Informationen zu Abfragen +========================= + +Zu Debugging-Zwecken können wir Informationen über die zuletzt ausgeführte Abfrage abrufen: + +```php +echo $database->getLastQueryString(); // gibt die SQL-Abfrage aus + +$result = $database->query('SELECT * FROM articles'); +echo $result->getQueryString(); // gibt die SQL-Abfrage aus +echo $result->getTime(); // gibt die Ausführungszeit in Sekunden aus +``` + +Zur Anzeige des Ergebnisses als HTML-Tabelle kann verwendet werden: + +```php +$result = $database->query('SELECT * FROM articles'); +$result->dump(); +``` + +ResultSet bietet Informationen zu Spaltentypen: + +```php +$result = $database->query('SELECT * FROM articles'); +$types = $result->getColumnTypes(); + +foreach ($types as $column => $type) { + echo "$column ist vom Typ $type->type"; // z.B. 'id ist vom Typ int' +} +``` + + +Abfrage-Protokollierung +----------------------- + +Wir können eine eigene Abfrage-Protokollierung implementieren. Das Ereignis `onQuery` ist ein Array von Callbacks, die nach jeder ausgeführten Abfrage aufgerufen werden: + +```php +$database->onQuery[] = function ($database, $result) use ($logger) { + $logger->info('Query: ' . $result->getQueryString()); + $logger->info('Time: ' . $result->getTime()); + + if ($result->getRowCount() > 1000) { + $logger->warning('Large result set: ' . $result->getRowCount() . ' rows'); + } +}; +``` diff --git a/database/de/transactions.texy b/database/de/transactions.texy new file mode 100644 index 0000000000..dc8a1ba004 --- /dev/null +++ b/database/de/transactions.texy @@ -0,0 +1,43 @@ +Transaktionen +************* + +.[perex] +Transaktionen garantieren, dass entweder alle Operationen innerhalb der Transaktion ausgeführt werden oder keine. Sie sind nützlich, um die Datenkonsistenz bei komplexeren Operationen sicherzustellen. + +Die einfachste Art, Transaktionen zu verwenden, sieht so aus: + +```php +$database->beginTransaction(); +try { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); + $database->commit(); +} catch (\Exception $e) { + $database->rollBack(); + throw $e; +} +``` + +Viel eleganter können Sie dasselbe mit der Methode `transaction()` schreiben. Sie akzeptiert einen Callback als Parameter, der innerhalb der Transaktion ausgeführt wird. Wenn der Callback ohne Ausnahme durchläuft, wird die Transaktion automatisch bestätigt (commit). Wenn eine Ausnahme auftritt, wird die Transaktion zurückgerollt (rollback) und die Ausnahme weitergegeben. + +```php +$database->transaction(function ($database) use ($id) { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); +}); +``` + +Die Methode `transaction()` kann auch Werte zurückgeben: + +```php +$count = $database->transaction(function ($database) { + $result = $database->query('UPDATE users SET active = ?', true); + return $result->getRowCount(); // gibt die Anzahl der aktualisierten Zeilen zurück +}); +``` diff --git a/database/el/@home.texy b/database/el/@home.texy index 02d4b281c5..ff1b599908 100644 --- a/database/el/@home.texy +++ b/database/el/@home.texy @@ -1,20 +1,21 @@ -Υποστηριζόμενοι διακομιστές -=========================== +Υποστηριζόμενες βάσεις δεδομένων +================================ -Αυτοί οι διακομιστές βάσεων δεδομένων υποστηρίζονται: +Το Nette υποστηρίζει τις ακόλουθες βάσεις δεδομένων: -|* Διακομιστής βάσης δεδομένων |* Όνομα DSN |* Υποστήριξη πυρήνα |* Υποστήριξη Explorer -| MySQL (>= 5.1) | mysql | ΝΑΙ | ΝΑΙ -| PostgreSQL (>= 9.0) | pgsql | ΝΑΙ | ΝΑΙ -| Sqlite 3 (>= 3.8) | sqlite | ΝΑΙ | ΝΑΙ -| Oracle | oci | ΝΑΙ | - -| MS SQL (PDO_SQLSRV) | sqlsrv | ΝΑΙ | ΝΑΙ -| MS SQL (PDO_DBLIB) | mssql | ΝΑΙ | - -| ODBC | odbc | ΝΑΙ | - +|* Διακομιστής βάσης δεδομένων |* Όνομα DSN |* Υποστήριξη στον Core |* Υποστήριξη στον Explorer +| MySQL (>= 5.1) | mysql | ΝΑΙ | ΝΑΙ +| PostgreSQL (>= 9.0) | pgsql | ΝΑΙ | ΝΑΙ +| Sqlite 3 (>= 3.8) | sqlite | ΝΑΙ | ΝΑΙ +| Oracle | oci | ΝΑΙ | - +| MS SQL (PDO_SQLSRV) | sqlsrv | ΝΑΙ | ΝΑΙ +| MS SQL (PDO_DBLIB) | mssql | ΝΑΙ | - +| ODBC | odbc | ΝΑΙ | - -{{title: Nette Database}} -{{description: Nette Database απλοποιεί σημαντικά την ανάκτηση δεδομένων από τη βάση δεδομένων χωρίς τη συγγραφή ερωτημάτων SQL. Χρησιμοποιεί αποδοτικά ερωτήματα και δεν μεταδίδει περιττά δεδομένα.}} + +{{maintitle: Nette Database - awesome database layer for PHP}} +{{description: Η Nette Database απλοποιεί σημαντικά την ανάκτηση δεδομένων από τη βάση δεδομένων χωρίς την ανάγκη γραφής ερωτημάτων SQL. Θέτει αποτελεσματικά ερωτήματα και δεν μεταφέρει περιττά δεδομένα.}} diff --git a/database/el/@left-menu.texy b/database/el/@left-menu.texy index ee9577a584..2bfe03617b 100644 --- a/database/el/@left-menu.texy +++ b/database/el/@left-menu.texy @@ -1,5 +1,12 @@ -Βάση δεδομένων +Nette Database ************** -- [Πυρήνας |Core] -- [Εξερευνητής |Explorer] -- [Διαμόρφωση |Configuration] +- [Εισαγωγή |guide] +- [Πρόσβαση SQL |sql way] +- [Explorer] +- [Συναλλαγές |transactions] +- [Εξαιρέσεις |exceptions] +- [Reflection |reflection] +- [Αντιστοίχιση |mapping] +- [Διαμόρφωση |configuration] +- [Κίνδυνοι ασφαλείας |security] +- [Αναβάθμιση |en:upgrading] diff --git a/database/el/@meta.texy b/database/el/@meta.texy new file mode 100644 index 0000000000..88e29852c7 --- /dev/null +++ b/database/el/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Τεκμηρίωση}} diff --git a/database/el/configuration.texy b/database/el/configuration.texy index 45624beaa1..221c114176 100644 --- a/database/el/configuration.texy +++ b/database/el/configuration.texy @@ -1,61 +1,67 @@ -Διαμόρφωση της βάσης δεδομένων -****************************** +Διαμόρφωση βάσης δεδομένων +************************** .[perex] -Επισκόπηση των επιλογών διαμόρφωσης για τη βάση δεδομένων Nette. +Επισκόπηση των επιλογών διαμόρφωσης για το Nette Database. -Αν δεν χρησιμοποιείτε ολόκληρο το πλαίσιο, αλλά μόνο αυτή τη βιβλιοθήκη, διαβάστε [πώς να φορτώσετε τη διαμόρφωση |bootstrap:]. +Αν δεν χρησιμοποιείτε ολόκληρο το framework, αλλά μόνο αυτή τη βιβλιοθήκη, διαβάστε [πώς να φορτώσετε τη διαμόρφωση |bootstrap:]. -Ενιαία σύνδεση .[#toc-single-connection] ----------------------------------------- +Μία σύνδεση +----------- -Διαμορφώστε μια ενιαία σύνδεση βάσης δεδομένων: +Διαμόρφωση μιας σύνδεσης βάσης δεδομένων: ```neon database: - # DSN, μόνο υποχρεωτικό κλειδί + # DSN, το μοναδικό υποχρεωτικό κλειδί dsn: "sqlite:%appDir%/Model/demo.db" user: ... password: ... ``` -Δημιουργεί υπηρεσίες τύπου `Nette\Database\Connection` και επίσης `Nette\Database\Explorer` για το επίπεδο [Database Explorer |explorer]. Η σύνδεση με τη βάση δεδομένων συνήθως περνάει με [αυτόματη καλωδίωση |dependency-injection:autowiring], αν αυτό δεν είναι δυνατό, χρησιμοποιήστε τα ονόματα υπηρεσιών `@database.default.connection` ή `@database.default.explorer`. +Δημιουργεί τις υπηρεσίες `Nette\Database\Connection` και `Nette\Database\Explorer`, τις οποίες συνήθως περνάμε με [autowiring |dependency-injection:autowiring], ή με αναφορά στο [όνομά τους |#Υπηρεσίες DI]. -Άλλες ρυθμίσεις: +Περαιτέρω ρυθμίσεις: ```neon database: - # εμφανίζει πίνακα βάσεων δεδομένων στο Tracy Bar? - debugger: ... # (bool) προεπιλογή true + # εμφάνιση του πίνακα database στο Tracy Bar; + debugger: ... # (bool) προεπιλογή είναι true - # εμφανίζει το ερώτημα EXPLAIN στο Tracy Bar? - explain: ... # (bool) προεπιλογή σε true + # εμφάνιση EXPLAIN των queries στο Tracy Bar; + explain: ... # (bool) προεπιλογή είναι true - # για να ενεργοποιήσει την αυτόματη καλωδίωση για αυτή τη σύνδεση? - autowired: ... # (bool) εξ ορισμού true για την πρώτη σύνδεση + # ενεργοποίηση autowiring για αυτή τη σύνδεση; + autowired: ... # (bool) προεπιλογή είναι true στην πρώτη σύνδεση - # συμβάσεις πίνακα: ανακάλυψη, στατικό ή όνομα κλάσης - conventions: discovered # (string) προεπιλογή 'discovered'. + # συμβάσεις πινάκων: discovered, static ή όνομα κλάσης + conventions: discovered # (string) προεπιλογή είναι 'discovered' options: - # να συνδέεται στη βάση δεδομένων μόνο όταν χρειάζεται; - lazy: ... # (bool) προεπιλογή σε false + # σύνδεση στη βάση δεδομένων μόνο όταν χρειάζεται; + lazy: ... # (bool) προεπιλογή είναι false - # Κλάση προγράμματος οδήγησης βάσης δεδομένων PHP + # PHP κλάση του database driver driverClass: # (string) - # μόνο MySQL: ορίζει sql_mode + # μόνο MySQL: ορίζει το sql_mode sqlmode: # (string) - # μόνο MySQL: ορίζει SET NAMES - charset: # (string) προεπιλογή 'utf8mb4' ('utf8' πριν την έκδοση v5.5.3) + # μόνο MySQL: ορίζει το SET NAMES + charset: # (string) προεπιλογή είναι 'utf8mb4' - # μόνο Oracle και SQLite: μορφή ημερομηνίας - formatDateTime: # (string) προεπιλογή 'U' + # μόνο MySQL: μετατρέπει το TINYINT(1) σε bool + convertBoolean: # (bool) προεπιλογή είναι false + + # επιστρέφει στήλες με ημερομηνία ως immutable αντικείμενα (από την έκδοση 3.2.1) + newDateTime: # (bool) προεπιλογή είναι false + + # μόνο Oracle και SQLite: μορφή για αποθήκευση ημερομηνίας + formatDateTime: # (string) προεπιλογή είναι 'U' ``` -Το κλειδί `options` μπορεί να περιέχει και άλλες επιλογές που μπορείτε να βρείτε στην [τεκμηρίωση του προγράμματος οδήγησης PDO |https://www.php.net/manual/en/pdo.drivers.php], όπως: +Στο κλειδί `options` μπορούν να αναφερθούν και άλλες επιλογές, τις οποίες θα βρείτε στην [τεκμηρίωση των PDO drivers |https://www.php.net/manual/en/pdo.drivers.php], όπως για παράδειγμα: ```neon database: @@ -64,10 +70,10 @@ database: ``` -Πολλαπλές συνδέσεις .[#toc-multiple-connections] ------------------------------------------------- +Πολλαπλές συνδέσεις +------------------- -Στη διαμόρφωση μπορούμε να ορίσουμε περισσότερες συνδέσεις βάσης δεδομένων χωρίζοντάς τες σε ονομαστικές ενότητες: +Στη διαμόρφωση μπορούμε να ορίσουμε και πολλαπλές συνδέσεις βάσης δεδομένων χωρίζοντάς τις σε ονομασμένες ενότητες: ```neon database: @@ -80,9 +86,23 @@ database: dsn: 'sqlite::memory:' ``` -Κάθε καθορισμένη σύνδεση δημιουργεί υπηρεσίες που περιλαμβάνουν το όνομα του τμήματος στο όνομά τους, δηλαδή `@database.main.connection` & `@database.main.explorer` και περαιτέρω `@database.another.connection` & `@database.another.explorer`. +Το Autowiring είναι ενεργοποιημένο μόνο για τις υπηρεσίες από την πρώτη ενότητα. Μπορεί να αλλάξει χρησιμοποιώντας `autowired: false` ή `autowired: true`. + + +Υπηρεσίες DI +------------ + +Αυτές οι υπηρεσίες προστίθενται στο DI container, όπου το `###` αντιπροσωπεύει το όνομα της σύνδεσης: + +| Όνομα | Τύπος | Περιγραφή +|---------------------------------------------------------- +| `database.###.connection` | [api:Nette\Database\Connection] | σύνδεση με τη βάση δεδομένων +| `database.###.explorer` | [api:Nette\Database\Explorer] | [Database Explorer |explorer] + + +Αν ορίσουμε μόνο μία σύνδεση, τα ονόματα των υπηρεσιών θα είναι `database.default.connection` και `database.default.explorer`. Αν ορίσουμε πολλαπλές συνδέσεις όπως στο παραπάνω παράδειγμα, τα ονόματα θα αντιστοιχούν στις ενότητες, δηλ. `database.main.connection`, `database.main.explorer` και επιπλέον `database.another.connection` και `database.another.explorer`. -Η αυτόματη σύνδεση είναι ενεργοποιημένη μόνο για υπηρεσίες από το πρώτο τμήμα. Αυτό μπορεί να αλλάξει με τις επιλογές `autowired: false` ή `autowired: true`. Οι υπηρεσίες που δεν συνδέονται αυτόματα περνούν με το όνομα: +Τις μη-autowired υπηρεσίες τις περνάμε ρητά με αναφορά στο όνομά τους: ```neon services: diff --git a/database/el/core.texy b/database/el/core.texy deleted file mode 100644 index 8087562d37..0000000000 --- a/database/el/core.texy +++ /dev/null @@ -1,350 +0,0 @@ -Πυρήνας βάσης δεδομένων -*********************** - -.[perex] -Το Nette Database Core είναι ένα επίπεδο αφαίρεσης βάσεων δεδομένων και παρέχει βασική λειτουργικότητα. - - -Εγκατάσταση .[#toc-installation] -================================ - -Κατεβάστε και εγκαταστήστε το πακέτο χρησιμοποιώντας το [Composer |best-practices:composer]: - -```shell -composer require nette/database -``` - - -Composer: Σύνδεση και διαμόρφωση .[#toc-connection-and-configuration] -===================================================================== - -Για να συνδεθείτε στη βάση δεδομένων, απλώς δημιουργήστε μια περίπτωση της κλάσης [api:Nette\Database\Connection]: - -```php -$database = new Nette\Database\Connection($dsn, $user, $password); -``` - -Η παράμετρος `$dsn` (όνομα πηγής δεδομένων) είναι [η ίδια που χρησιμοποιείται από το PDO |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], π.χ. `host=127.0.0.1;dbname=test`. Σε περίπτωση αποτυχίας πετάει το `Nette\Database\ConnectionException`. - -Ωστόσο, ένας πιο εξελιγμένος τρόπος προσφέρει [διαμόρφωση της εφαρμογής |configuration]. Θα προσθέσουμε ένα τμήμα `database` και αυτό δημιουργεί τα απαιτούμενα αντικείμενα και ένα πάνελ βάσης δεδομένων στη γραμμή [Tracy |tracy:]. - -```neon -database: - dsn: 'mysql:host=127.0.0.1;dbname=test' - user: root - password: password -``` - -Το αντικείμενο σύνδεσης που [λαμβάνουμε ως υπηρεσία από ένα δοχείο DI |dependency-injection:passing-dependencies], για παράδειγμα: - -```php -class Model -{ - // περάστε το Nette\Database\Explorer για να εργαστείτε με το επίπεδο Database Explorer - public function __construct( - private Nette\Database\Connection $database, - ) { - } -} -``` - -Για περισσότερες πληροφορίες, ανατρέξτε στη [διαμόρφωση της βάσης δεδομένων |configuration]. - - -Ερωτήματα .[#toc-queries] -========================= - -Για να κάνετε ερώτημα στη βάση δεδομένων χρησιμοποιήστε τη μέθοδο `query()` που επιστρέφει [ResultSet |api:Nette\Database\ResultSet]. - -```php -$result = $database->query('SELECT * FROM users'); - -foreach ($result as $row) { - echo $row->id; - echo $row->name; -} - -echo $result->getRowCount(); // επιστρέφει τον αριθμό των γραμμών αν είναι γνωστός -``` - -.[note] -Πάνω από το `ResultSet` είναι δυνατόν να γίνει επανάληψη μόνο μία φορά, αν χρειαστεί να γίνει επανάληψη πολλές φορές, είναι απαραίτητο να μετατρέψουμε το αποτέλεσμα σε πίνακα μέσω της μεθόδου `fetchAll()`. - -Μπορείτε εύκολα να προσθέσετε παραμέτρους στο ερώτημα, σημειώστε το ερωτηματικό: - -```php -$database->query('SELECT * FROM users WHERE name = ?', $name); - -$database->query('SELECT * FROM users WHERE name = ? AND active = ?', $name, $active); - -$database->query('SELECT * FROM users WHERE id IN (?)', $ids); // $ids είναι πίνακας -``` -<div class=warning> - -ΠΡΟΕΙΔΟΠΟΙΗΣΗ, μην συνδέετε ποτέ συμβολοσειρές για να αποφύγετε [την ευπάθεια SQL injection |https://en.wikipedia.org/wiki/SQL_injection]! -/-- -$db->query('SELECT * FROM users WHERE name = ' . $name); // WRONG!!! -\-- -</div> - -Σε περίπτωση αποτυχίας το `query()` πετάει είτε το `Nette\Database\DriverException` είτε έναν από τους απογόνους του: - -- [ConstraintViolationException |api:Nette\Database\ConstraintViolationException] - παραβίαση οποιουδήποτε περιορισμού -- [ForeignKeyConstraintViolationException |api:Nette\Database\ForeignKeyConstraintViolationException] - μη έγκυρο ξένο κλειδί -- [NotNullConstraintViolationException |api:Nette\Database\NotNullConstraintViolationException] - παραβίαση της συνθήκης NOT NULL -- [UniqueConstraintViolationException |api:Nette\Database\UniqueConstraintViolationException] - σύγκρουση μοναδικού δείκτη - -Εκτός από το `query()`, υπάρχουν και άλλες χρήσιμες μέθοδοι: - -```php -// επιστρέφει τον συσχετιστικό πίνακα id => name -$pairs = $database->fetchPairs('SELECT id, name FROM users'); - -// επιστρέφει όλες τις γραμμές ως πίνακα -$rows = $database->fetchAll('SELECT * FROM users'); - -// επιστρέφει μία μόνο γραμμή -$row = $database->fetch('SELECT * FROM users WHERE id = ?', $id); - -// επιστρέφει ένα μόνο πεδίο -$name = $database->fetchField('SELECT name FROM users WHERE id = ?', $id); -``` - -Σε περίπτωση αποτυχίας, όλες αυτές οι μέθοδοι ρίχνουν `Nette\Database\DriverException.` - - -Εισαγωγή, ενημέρωση και διαγραφή .[#toc-insert-update-delete] -============================================================= - -Η παράμετρος που εισάγουμε στο ερώτημα SQL μπορεί επίσης να είναι ο πίνακας (σε αυτή την περίπτωση είναι δυνατόν να παραλείψουμε τη δήλωση μπαλαντέρ `?`), which may be useful for the `INSERT`: - -```php -$database->query('INSERT INTO users ?', [ // εδώ μπορεί να παραλειφθεί το ερωτηματικό - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) - -$id = $database->getInsertId(); // επιστρέφει την αυτόματη αύξηση της εισαγόμενης γραμμής - -$id = $database->getInsertId($sequence); // ή τιμή ακολουθίας -``` - -Πολλαπλή εισαγωγή: - -```php -$database->query('INSERT INTO users', [ - [ - 'name' => 'Jim', - 'year' => 1978, - ], [ - 'name' => 'Jack', - 'year' => 1987, - ], -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) -``` - -DateTime ή [απαριθμήσεις |https://www.php.net/enumerations]: - -```php -$database->query('INSERT INTO users', [ - 'name' => $name, - 'created' => new DateTime, // ή $database::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // Εισάγει τα περιεχόμενα του αρχείου - 'status' => State::New, // enum Κατάσταση -]); -``` - -Ενημέρωση γραμμών: - -```php -$result = $database->query('UPDATE users SET', [ - 'name' => $name, - 'year' => $year, -], 'WHERE id = ?', $id); -// UPDATE users SET `name` = 'Jim', `year` = 1978 WHERE id = 123 - -echo $result->getRowCount(); // επιστρέφει τον αριθμό των επηρεαζόμενων γραμμών -``` - -Για UPDATE, μπορούμε να χρησιμοποιήσουμε τους τελεστές `+=` και `-=`: - -```php -$database->query('UPDATE users SET', [ - 'age+=' => 1, // note += -], 'WHERE id = ?', $id); -// UPDATE users SET `age` = `age` + 1 -``` - -Διαγραφή: - -```php -$result = $database->query('DELETE FROM users WHERE id = ?', $id); -echo $result->getRowCount(); // επιστρέφει τον αριθμό των επηρεαζόμενων γραμμών -``` - - -Ερωτήματα για προχωρημένους .[#toc-advanced-queries] -==================================================== - -Εισαγωγή ή ενημέρωση, εάν υπάρχει ήδη: - -```php -$database->query('INSERT INTO users', [ - 'id' => $id, - 'name' => $name, - 'year' => $year, -], 'ON DUPLICATE KEY UPDATE', [ - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) -// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 -``` - -Σημειώστε ότι η Nette Database αναγνωρίζει το πλαίσιο SQL στο οποίο εισάγεται η παράμετρος του πίνακα και δημιουργεί τον κώδικα SQL ανάλογα. Έτσι, από τον πρώτο πίνακα παράγει το `(id, name, year) VALUES (123, 'Jim', 1978)`, ενώ ο δεύτερος μετατρέπεται σε `name = 'Jim', year = 1978`. - -Μπορούμε επίσης να περιγράψουμε την ταξινόμηση χρησιμοποιώντας array, στο keys είναι ονόματα στηλών και values είναι boolean που καθορίζει αν θα γίνει ταξινόμηση σε αύξουσα σειρά: - -```php -$database->query('SELECT id FROM author ORDER BY', [ - 'id' => true, // αύξουσα - 'name' => false, // φθίνουσα -]); -// SELECT id FROM author ORDER BY `id`, `name` DESC -``` - -Εάν η ανίχνευση δεν λειτούργησε, μπορείτε να καθορίσετε τη μορφή της συνέλευσης με ένα μπαλαντέρ `?` ακολουθούμενο από μια υπόδειξη. Αυτές οι υποδείξεις υποστηρίζονται: - -| ?values | (key1, key2, ...) VALUES (value1, value2, ...) -| ?set | key1 = value1, key2 = value2, ... -| ?and | key1 = value1 AND key2 = value2 ... -| ?or | key1 = value1 OR key2 = value2 ... -| ?order | key1 ASC, key2 DESC - -Η ρήτρα WHERE χρησιμοποιεί τον τελεστή `?and` έτσι ώστε οι συνθήκες να συνδέονται με το `AND`: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND `year` = 1978 -``` - -Το οποίο μπορεί εύκολα να αλλάξει σε `OR` με τη χρήση του μπαλαντέρ `?or`: - -```php -$result = $database->query('SELECT * FROM users WHERE ?or', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' OR `year` = 1978 -``` - -Μπορούμε να χρησιμοποιήσουμε τελεστές σε συνθήκες: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name <>' => $name, - 'year >' => $year, -]); -// SELECT * FROM users WHERE `name` <> 'Jim' AND `year` > 1978 -``` - -Και επίσης απαριθμήσεις: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => ['Jim', 'Jack'], - 'role NOT IN' => ['admin', 'owner'], // απαρίθμηση + τελεστής NOT IN -]); -// SELECT * FROM users WHERE -// `name` IN ('Jim', 'Jack') AND `role` NOT IN ('admin', 'owner') -``` - -Μπορούμε επίσης να συμπεριλάβουμε ένα κομμάτι προσαρμοσμένου κώδικα SQL χρησιμοποιώντας το λεγόμενο SQL literal: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year >' => $database::literal('YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) -``` - -Εναλλακτικά: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) -``` - -SQL literal μπορεί επίσης να έχει τις παραμέτρους του: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > ? AND year < ?', $min, $max), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) -``` - -Χάρη σε αυτό μπορούμε να δημιουργήσουμε ενδιαφέροντες συνδυασμούς: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('?or', [ - 'active' => true, - 'role' => $role, - ]), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') -``` - - -Όνομα μεταβλητής .[#toc-variable-name] -====================================== - -Υπάρχει ένα μπαλαντέρ `?name` που χρησιμοποιείτε αν το όνομα του πίνακα ή της στήλης είναι μεταβλητή. (Προσοχή, μην επιτρέψετε στο χρήστη να χειριστεί το περιεχόμενο μιας τέτοιας μεταβλητής): - -```php -$table = 'blog.users'; -$column = 'name'; -$database->query('SELECT * FROM ?name WHERE ?name = ?', $table, $column, $name); -// SELECT * FROM `blog`.`users` WHERE `name` = 'Jim' -``` - - -Συναλλαγές .[#toc-transactions] -=============================== - -Υπάρχουν τρεις μέθοδοι για την αντιμετώπιση των συναλλαγών: - -```php -$database->beginTransaction(); - -$database->commit(); - -$database->rollback(); -``` - -Η μέθοδος `transaction()` προσφέρει έναν κομψό τρόπο. Περνάτε το callback που εκτελείται στη συναλλαγή. Εάν κατά την εκτέλεση εκσφενδονιστεί μια εξαίρεση, η συναλλαγή εγκαταλείπεται, ενώ εάν όλα πάνε καλά, η συναλλαγή δεσμεύεται. - -```php -$id = $database->transaction(function ($database) { - $database->query('DELETE FROM ...'); - $database->query('INSERT INTO ...'); - // ... - return $database->getInsertId(); -}); -``` - -Όπως μπορείτε να δείτε, η μέθοδος `transaction()` επιστρέφει την τιμή επιστροφής του callback. - -Η transaction() μπορεί επίσης να είναι εμφωλευμένη, γεγονός που απλοποιεί την υλοποίηση ανεξάρτητων αποθετηρίων. diff --git a/database/el/exceptions.texy b/database/el/exceptions.texy new file mode 100644 index 0000000000..9948c5f939 --- /dev/null +++ b/database/el/exceptions.texy @@ -0,0 +1,34 @@ +Εξαιρέσεις +********** + +Το Nette Database χρησιμοποιεί μια ιεραρχία εξαιρέσεων. Η βασική κλάση είναι η `Nette\Database\DriverException`, η οποία κληρονομεί από την `PDOException` και παρέχει διευρυμένες δυνατότητες για την εργασία με σφάλματα βάσης δεδομένων: + +- Η μέθοδος `getDriverCode()` επιστρέφει τον κωδικό σφάλματος από τον οδηγό (driver) της βάσης δεδομένων. +- Η μέθοδος `getSqlState()` επιστρέφει τον κωδικό SQLSTATE. +- Οι μέθοδοι `getQueryString()` και `getParameters()` επιτρέπουν την απόκτηση του αρχικού ερωτήματος (query) και των παραμέτρων του. + +Από την `DriverException` κληρονομούν οι ακόλουθες εξειδικευμένες εξαιρέσεις: + +- `ConnectionException` - σηματοδοτεί αποτυχία σύνδεσης στον διακομιστή της βάσης δεδομένων. +- `ConstraintViolationException` - βασική κλάση για παραβίαση περιορισμών βάσης δεδομένων, από την οποία κληρονομούν: + - `ForeignKeyConstraintViolationException` - παραβίαση ξένου κλειδιού. + - `NotNullConstraintViolationException` - παραβίαση περιορισμού NOT NULL. + - `UniqueConstraintViolationException` - παραβίαση μοναδικότητας τιμής. + + +Παράδειγμα σύλληψης της εξαίρεσης `UniqueConstraintViolationException`, η οποία προκύπτει όταν προσπαθούμε να εισαγάγουμε έναν χρήστη με email που υπάρχει ήδη στη βάση δεδομένων (υποθέτοντας ότι η στήλη `email` έχει μοναδικό ευρετήριο - unique index). + +```php +try { + $database->query('INSERT INTO users', [ + 'email' => 'john@example.com', + 'name' => 'John Doe', + 'password' => $hashedPassword, + ]); +} catch (Nette\Database\UniqueConstraintViolationException $e) { + echo 'Υπάρχει ήδη χρήστης με αυτό το email.'; // User with this email already exists. + +} catch (Nette\Database\DriverException $e) { + echo 'Παρουσιάστηκε σφάλμα κατά την εγγραφή: ' . $e->getMessage(); // An error occurred during registration: +} +``` diff --git a/database/el/explorer.texy b/database/el/explorer.texy index 8b1cace70a..f09cee5fdd 100644 --- a/database/el/explorer.texy +++ b/database/el/explorer.texy @@ -1,550 +1,912 @@ -Εξερεύνηση βάσης δεδομένων -************************** +Database Explorer +***************** <div class=perex> -Ο Nette Database Explorer απλοποιεί σημαντικά την ανάκτηση δεδομένων από τη βάση δεδομένων χωρίς τη συγγραφή ερωτημάτων SQL. +Ο Explorer προσφέρει έναν διαισθητικό και αποτελεσματικό τρόπο εργασίας με τη βάση δεδομένων. Φροντίζει αυτόματα για τις σχέσεις μεταξύ των πινάκων και τη βελτιστοποίηση των ερωτημάτων (queries), ώστε να μπορείτε να επικεντρωθείτε στην εφαρμογή σας. Λειτουργεί αμέσως χωρίς καμία ρύθμιση. Αν χρειάζεστε πλήρη έλεγχο των ερωτημάτων SQL, μπορείτε να χρησιμοποιήσετε την [προσέγγιση SQL |sql-way]. -- χρησιμοποιεί αποδοτικά ερωτήματα -- δεν μεταδίδονται άσκοπα δεδομένα -- διαθέτει κομψή σύνταξη +- Η εργασία με τα δεδομένα είναι φυσική και εύκολα κατανοητή. +- Παράγει βελτιστοποιημένα ερωτήματα SQL που φορτώνουν μόνο τα απαραίτητα δεδομένα. +- Επιτρέπει εύκολη πρόσβαση σε σχετιζόμενα δεδομένα χωρίς την ανάγκη γραφής ερωτημάτων JOIN. +- Λειτουργεί άμεσα χωρίς καμία διαμόρφωση ή παραγωγή οντοτήτων (entities). </div> -Για να χρησιμοποιήσετε την Εξερεύνηση βάσης δεδομένων, ξεκινήστε με έναν πίνακα - καλέστε το `table()` σε ένα αντικείμενο [api:Nette\Database\Explorer]. Ο ευκολότερος τρόπος για να λάβετε μια περίπτωση αντικειμένου πλαισίου [περιγράφεται εδώ |core#Connection and Configuration], ή, για την περίπτωση που ο Nette Database Explorer χρησιμοποιείται ως αυτόνομο εργαλείο, μπορεί να [δημιουργηθεί χειροκίνητα |#Creating Explorer Manually]. + +Με τον Explorer ξεκινάτε καλώντας τη μέθοδο `table()` του αντικειμένου [api:Nette\Database\Explorer] (λεπτομέρειες για τη σύνδεση θα βρείτε στο κεφάλαιο [Σύνδεση και Διαμόρφωση |guide#Σύνδεση και Διαμόρφωση]): ```php -$books = $explorer->table('book'); // Το όνομα του πίνακα db είναι 'book' +$books = $explorer->table('book'); // 'book' είναι το όνομα του πίνακα ``` -Η κλήση επιστρέφει μια περίπτωση του αντικειμένου [Selection |api:Nette\Database\Table\Selection], η οποία μπορεί να επαναληφθεί για να ανακτηθούν όλα τα βιβλία. Κάθε στοιχείο (μια γραμμή) αναπαρίσταται από μια περίπτωση του [ActiveRow |api:Nette\Database\Table\ActiveRow] με δεδομένα που αντιστοιχίζονται στις ιδιότητές του: +Η μέθοδος επιστρέφει ένα αντικείμενο [Selection |api:Nette\Database\Table\Selection], το οποίο αντιπροσωπεύει ένα ερώτημα SQL. Σε αυτό το αντικείμενο μπορούμε να συνδέσουμε περαιτέρω μεθόδους για φιλτράρισμα και ταξινόμηση των αποτελεσμάτων. Το ερώτημα συντάσσεται και εκτελείται μόνο τη στιγμή που αρχίζουμε να ζητάμε δεδομένα, για παράδειγμα, με τη διέλευση ενός βρόχου `foreach`. Κάθε γραμμή αντιπροσωπεύεται από ένα αντικείμενο [ActiveRow |api:Nette\Database\Table\ActiveRow]: ```php foreach ($books as $book) { - echo $book->title; - echo $book->author_id; + echo $book->title; // εμφάνιση της στήλης 'title' + echo $book->author_id; // εμφάνιση της στήλης 'author_id' } ``` -Η απόκτηση μόνο μιας συγκεκριμένης γραμμής γίνεται με τη μέθοδο `get()`, η οποία επιστρέφει απευθείας μια περίπτωση ActiveRow. +Ο Explorer διευκολύνει θεμελιωδώς την εργασία με τις [#σχέσεις μεταξύ πινάκων]. Το ακόλουθο παράδειγμα δείχνει πόσο εύκολα μπορούμε να εμφανίσουμε δεδομένα από συνδεδεμένους πίνακες (βιβλία και οι συγγραφείς τους). Παρατηρήστε ότι δεν χρειάζεται να γράψουμε κανένα ερώτημα JOIN, το Nette τα δημιουργεί για εμάς: ```php -$book = $explorer->table('book')->get(2); // επιστρέφει βιβλίο με id 2 -echo $book->title; -echo $book->author_id; +$books = $explorer->table('book'); + +foreach ($books as $book) { + echo 'Βιβλίο: ' . $book->title; // Book: + echo 'Συγγραφέας: ' . $book->author->name; // δημιουργεί JOIN στον πίνακα 'author' // Author: +} ``` -Ας ρίξουμε μια ματιά σε μια κοινή περίπτωση χρήσης. Πρέπει να ανακτήσετε βιβλία και τους συγγραφείς τους. Πρόκειται για μια κοινή σχέση 1:N. Η συχνά χρησιμοποιούμενη λύση είναι η άντληση δεδομένων με τη χρήση ενός ερωτήματος SQL με ενώσεις πινάκων. Η δεύτερη δυνατότητα είναι να φέρνετε τα δεδομένα ξεχωριστά, να εκτελείτε ένα ερώτημα για τη λήψη βιβλίων και στη συνέχεια να παίρνετε έναν συγγραφέα για κάθε βιβλίο με ένα άλλο ερώτημα (π.χ. στον κύκλο foreach σας). Αυτό θα μπορούσε εύκολα να βελτιστοποιηθεί ώστε να εκτελούνται μόνο δύο ερωτήματα, ένα για τα βιβλία και ένα άλλο για τους απαιτούμενους συγγραφείς - και αυτός ακριβώς είναι ο τρόπος με τον οποίο το κάνει ο Nette Database Explorer. +Το Nette Database Explorer βελτιστοποιεί τα ερωτήματα ώστε να είναι όσο το δυνατόν πιο αποτελεσματικά. Το παραπάνω παράδειγμα εκτελεί μόνο δύο ερωτήματα SELECT, ανεξάρτητα από το αν επεξεργαζόμαστε 10 ή 10.000 βιβλία. -Στα παραδείγματα που ακολουθούν, θα εργαστούμε με το σχήμα της βάσης δεδομένων του σχήματος. Υπάρχουν σύνδεσμοι OneHasMany (1:N) (συγγραφέας του βιβλίου `author_id` και πιθανός μεταφραστής `translator_id`, ο οποίος μπορεί να είναι `null`) και σύνδεσμος ManyHasMany (M:N) μεταξύ του βιβλίου και των ετικετών του. +Επιπλέον, ο Explorer παρακολουθεί ποιες στήλες χρησιμοποιούνται στον κώδικα και φορτώνει από τη βάση δεδομένων μόνο αυτές, εξοικονομώντας έτσι περαιτέρω απόδοση. Αυτή η συμπεριφορά είναι πλήρως αυτόματη και προσαρμοστική. Αν αργότερα τροποποιήσετε τον κώδικα και αρχίσετε να χρησιμοποιείτε άλλες στήλες, ο Explorer προσαρμόζει αυτόματα τα ερωτήματα. Δεν χρειάζεται να ρυθμίσετε τίποτα, ούτε να σκεφτείτε ποιες στήλες θα χρειαστείτε - αφήστε το στο Nette. -[Ένα παράδειγμα, συμπεριλαμβανομένου ενός σχήματος, βρίσκεται στο GitHub |https://github.com/nette-examples/books]. -[* db-schema-1-.webp *] *** Δομή βάσης δεδομένων που χρησιμοποιείται στα παραδείγματα .<> +Φιλτράρισμα και Ταξινόμηση +========================== -Ο παρακάτω κώδικας παραθέτει το όνομα του συγγραφέα για κάθε βιβλίο και όλες τις ετικέτες του. Θα [συζητήσουμε σε λίγο |#Working with relationships] πώς αυτό λειτουργεί εσωτερικά. +Η κλάση `Selection` παρέχει μεθόδους για το φιλτράρισμα και την ταξινόμηση της επιλογής δεδομένων. -```php -$books = $explorer->table('book'); +.[language-php] +| `where($condition, ...$params)` | Προσθέτει συνθήκη WHERE. Πολλαπλές συνθήκες συνδέονται με τον τελεστή AND +| `whereOr(array $conditions)` | Προσθέτει μια ομάδα συνθηκών WHERE συνδεδεμένων με τον τελεστή OR +| `wherePrimary($value)` | Προσθέτει συνθήκη WHERE βάσει του πρωτεύοντος κλειδιού +| `order($columns, ...$params)` | Ορίζει την ταξινόμηση ORDER BY +| `select($columns, ...$params)` | Καθορίζει τις στήλες που πρέπει να φορτωθούν +| `limit($limit, $offset = null)` | Περιορίζει τον αριθμό των γραμμών (LIMIT) και προαιρετικά ορίζει το OFFSET +| `page($page, $itemsPerPage, &$total = null)` | Ορίζει τη σελίδωση +| `group($columns, ...$params)` | Ομαδοποιεί τις γραμμές (GROUP BY) +| `having($condition, ...$params)` | Προσθέτει συνθήκη HAVING για το φιλτράρισμα των ομαδοποιημένων γραμμών -foreach ($books as $book) { - echo 'title: ' . $book->title; - echo 'written by: ' . $book->author->name; // $book->author is row from table 'author' +Οι μέθοδοι μπορούν να αλυσιδωθούν (το λεγόμενο [fluent interface |nette:introduction-to-object-oriented-programming#Fluent Interfaces]): `$table->where(...)->order(...)->limit(...)`. - echo 'tags: '; - foreach ($book->related('book_tag') as $bookTag) { - echo $bookTag->tag->name . ', '; // $bookTag->tag is row from table 'tag' - } -} -``` +Σε αυτές τις μεθόδους μπορείτε επίσης να χρησιμοποιείτε ειδική σημειογραφία για την πρόσβαση σε [δεδομένα από σχετικούς πίνακες |#Ερωτήματα μέσω Σχετικών Πινάκων]. -Θα μείνετε ευχαριστημένοι από το πόσο αποτελεσματικά λειτουργεί το επίπεδο της βάσης δεδομένων. Το παραπάνω παράδειγμα πραγματοποιεί έναν σταθερό αριθμό αιτήσεων που μοιάζουν ως εξής: -```sql -SELECT * FROM `book` -SELECT * FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT * FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) -``` +Escaping και Αναγνωριστικά +-------------------------- -Αν χρησιμοποιείτε [κρυφή μνήμη |caching:] (προεπιλογή on), δεν θα ζητούνται άσκοπα στήλες. Μετά το πρώτο ερώτημα, η κρυφή μνήμη θα αποθηκεύσει τα χρησιμοποιούμενα ονόματα στηλών και ο Nette Database Explorer θα εκτελεί ερωτήματα μόνο με τις απαραίτητες στήλες: +Οι μέθοδοι κάνουν αυτόματα escaping τις παραμέτρους και περικλείουν σε εισαγωγικά τα αναγνωριστικά (ονόματα πινάκων και στηλών), αποτρέποντας έτσι το SQL injection. Για τη σωστή λειτουργία, είναι απαραίτητο να τηρούνται ορισμένοι κανόνες: -```sql -SELECT `id`, `title`, `author_id` FROM `book` -SELECT `id`, `name` FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT `book_id`, `tag_id` FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT `id`, `name` FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) +- Λέξεις-κλειδιά, ονόματα συναρτήσεων, διαδικασιών κ.λπ. γράφονται με **κεφαλαία γράμματα**. +- Ονόματα στηλών και πινάκων γράφονται με **μικρά γράμματα**. +- Τα strings πάντα εισάγονται μέσω **παραμέτρων**. + +```php +where('name = ' . $name); // ΚΡΙΣΙΜΗ ΕΥΠΑΘΕΙΑ: SQL injection +where('name LIKE "%search%"'); // ΛΑΘΟΣ: περιπλέκει την αυτόματη περικλείωση σε εισαγωγικά +where('name LIKE ?', '%search%'); // ΣΩΣΤΟ: η τιμή εισάγεται μέσω παραμέτρου + +where('name like ?', $name); // ΛΑΘΟΣ: παράγει: `name` `like` ? +where('name LIKE ?', $name); // ΣΩΣΤΟ: παράγει: `name` LIKE ? +where('LOWER(name) = ?', $value);// ΣΩΣΤΟ: LOWER(`name`) = ? ``` -Επιλογές .[#toc-selections] -=========================== +where(string|array $condition, ...$parameters): static .[method] +---------------------------------------------------------------- -Δείτε τις δυνατότητες φιλτραρίσματος και περιορισμού των γραμμών [api:Nette\Database\Table\Selection]: +Φιλτράρει τα αποτελέσματα χρησιμοποιώντας συνθήκες WHERE. Η ισχυρή της πλευρά είναι η έξυπνη διαχείριση διαφόρων τύπων τιμών και η αυτόματη επιλογή τελεστών SQL. -.[language-php] -| `$table->where($where[, $param[, ...]])` | Ορισμός WHERE με χρήση του AND ως συγκολλητικού στοιχείου εάν παρέχονται δύο ή περισσότερες συνθήκες -| `$table->whereOr($where)` | Ορισμός WHERE με χρήση OR ως συγκολλητικό στοιχείο εάν παρέχονται δύο ή περισσότερες συνθήκες -| `$table->order($columns)` | Ορισμός ORDER BY, μπορεί να είναι έκφραση `('column DESC, id DESC')` -| `$table->select($columns)` | Ορισμός ανακτημένων στηλών, μπορεί να είναι έκφραση `('col, MD5(col) AS hash')` -| `$table->limit($limit[, $offset])` | Ορισμός LIMIT και OFFSET -| `$table->page($page, $itemsPerPage[, &$lastPage])` | Ενεργοποιεί τη σελιδοποίηση -| `$table->group($columns)` | Ορισμός GROUP BY -| `$table->having($having)` | Ορισμός HAVING +Βασική χρήση: -Μπορεί να χρησιμοποιηθεί διεπαφή Fluent, για παράδειγμα `$table->where(...)->order(...)->limit(...)`. Πολλαπλές συνθήκες `where` ή `whereOr` συνδέονται με τον τελεστή `AND`. +```php +$table->where('id', $value); // WHERE `id` = 123 +$table->where('id > ?', $value); // WHERE `id` > 123 +$table->where('id = ? OR name = ?', $id, $name); // WHERE `id` = 1 OR `name` = 'Jon Snow' +``` +Χάρη στην αυτόματη ανίχνευση των κατάλληλων τελεστών, δεν χρειάζεται να ασχολούμαστε με διάφορες ειδικές περιπτώσεις. Το Nette τις λύνει για εμάς: -where() .[#toc-where] ---------------------- +```php +$table->where('id', 1); // WHERE `id` = 1 +$table->where('id', null); // WHERE `id` IS NULL +$table->where('id', [1, 2, 3]); // WHERE `id` IN (1, 2, 3) +// μπορεί να χρησιμοποιηθεί και το placeholder ερωτηματικό (?) χωρίς τελεστή: +$table->where('id ?', 1); // WHERE `id` = 1 +``` -Ο Nette Database Explorer μπορεί να προσθέσει αυτόματα τους απαραίτητους τελεστές για τις περασμένες τιμές: +Η μέθοδος επεξεργάζεται σωστά και τις αρνητικές συνθήκες και τους κενούς πίνακες: -.[language-php] -| `$table->where('field', $value)` | field = $value -| `$table->where('field', null)` | field IS NULL -| `$table->where('field > ?', $val)` | πεδίο > $val -| `$table->where('field', [1, 2])` | field IN (1, 2) -| `$table->where('id = ? OR name = ?', 1, $name)` | id = 1 OR name = 'Jon Snow' -| `$table->where('field', $explorer->table($tableName))` | field IN (SELECT $primary FROM $tableName) -| `$table->where('field', $explorer->table($tableName)->select('col'))` | field IN (SELECT col FROM $tableName) +```php +$table->where('id', []); // WHERE `id` IS NULL AND FALSE -- τίποτα δεν θα βρεθεί +$table->where('id NOT', []); // WHERE `id` IS NULL OR TRUE -- όλα θα βρεθούν +$table->where('NOT (id ?)', []); // WHERE NOT (`id` IS NULL AND FALSE) -- όλα θα βρεθούν +// $table->where('NOT id ?', $ids); Προσοχή - αυτή η σύνταξη δεν υποστηρίζεται +``` -Μπορείτε να δώσετε placeholder ακόμη και χωρίς τον τελεστή στήλης. Αυτές οι κλήσεις είναι οι ίδιες. +Ως παράμετρο μπορούμε να περάσουμε επίσης το αποτέλεσμα από έναν άλλο πίνακα - θα δημιουργηθεί ένα υποερώτημα (subquery): ```php -$table->where('id = ? OR id = ?', 1, 2); -$table->where('id ? OR id ?', 1, 2); +// WHERE `id` IN (SELECT `id` FROM `tableName`) +$table->where('id', $explorer->table($tableName)); + +// WHERE `id` IN (SELECT `col` FROM `tableName`) +$table->where('id', $explorer->table($tableName)->select('col')); ``` -Αυτή η λειτουργία επιτρέπει τη δημιουργία του σωστού τελεστή με βάση την τιμή: +Τις συνθήκες μπορούμε να τις περάσουμε επίσης ως πίνακα, τα στοιχεία του οποίου συνδέονται με AND: ```php -$table->where('id ?', 2); // id = 2 -$table->where('id ?', null); // id IS NULL -$table->where('id', $ids); // id IN (...) +// WHERE (`price_final` < `price_original`) AND (`stock_count` > `min_stock`) +$table->where([ + 'price_final < price_original', + 'stock_count > min_stock', +]); ``` -Λειτουργεί και για άδειους πίνακες: +Στον πίνακα μπορούμε να χρησιμοποιήσουμε ζεύγη κλειδί => τιμή και το Nette πάλι επιλέγει αυτόματα τους σωστούς τελεστές: ```php -$table->where('id', []); // id IS NULL AND FALSE -$table->where('id NOT', []); // id IS NULL OR TRUE -$table->where('NOT (id ?)', $ids); // NOT (id IS NULL AND FALSE) +// WHERE (`status` = 'active') AND (`id` IN (1, 2, 3)) +$table->where([ + 'status' => 'active', + 'id' => [1, 2, 3], +]); +``` + +Στον πίνακα μπορούμε να συνδυάσουμε εκφράσεις SQL με placeholders ερωτηματικά (?) και πολλαπλές παραμέτρους. Αυτό είναι κατάλληλο για πολύπλοκες συνθήκες με ακριβώς καθορισμένους τελεστές: -// αυτό θα προκαλέσει μια εξαίρεση, αυτή η σύνταξη δεν υποστηρίζεται -$table->where('NOT id ?', $ids); +```php +// WHERE (`age` > 18) AND (ROUND(`score`, 2) > 75.5) +$table->where([ + 'age > ?' => 18, + 'ROUND(score, ?) > ?' => [2, 75.5], // δύο παραμέτρους τις περνάμε ως πίνακα +]); ``` +Οι πολλαπλές κλήσεις `where()` συνδέουν αυτόματα τις συνθήκες με AND. -whereOr() .[#toc-whereor] -------------------------- -Παράδειγμα χρήσης χωρίς παραμέτρους: +whereOr(array $parameters): static .[method] +-------------------------------------------- + +Παρόμοια με το `where()`, προσθέτει συνθήκες, αλλά με τη διαφορά ότι τις συνδέει με OR: ```php -// WHERE (user_id IS NULL) OR (SUM(`field1`) > SUM(`field2`)) +// WHERE (`status` = 'active') OR (`deleted` = 1) $table->whereOr([ - 'user_id IS NULL', - 'SUM(field1) > SUM(field2)', + 'status' => 'active', + 'deleted' => true, ]); ``` -Χρησιμοποιούμε τις παραμέτρους. Εάν δεν καθορίσετε έναν τελεστή, ο Nette Database Explorer θα προσθέσει αυτόματα τον κατάλληλο: +Και εδώ μπορούμε να χρησιμοποιήσουμε πιο πολύπλοκες εκφράσεις: ```php -// WHERE (`field1` IS NULL) OR (`field2` IN (3, 5)) OR (`amount` > 11) +// WHERE (`price` > 1000) OR (`price_with_tax` > 1500) $table->whereOr([ - 'field1' => null, - 'field2' => [3, 5], - 'amount >' => 11, + 'price > ?' => 1000, + 'price_with_tax > ?' => 1500, ]); ``` -Το κλειδί μπορεί να περιέχει μια έκφραση που περιέχει ερωτηματικά μπαλαντέρ και στη συνέχεια να περάσετε παραμέτρους στην τιμή: + +wherePrimary(mixed $key): static .[method] +------------------------------------------ + +Προσθέτει συνθήκη για το πρωτεύον κλειδί του πίνακα: ```php -// WHERE (`id` > 12) OR (ROUND(`id`, 5) = 3) -$table->whereOr([ - 'id > ?' => 12, - 'ROUND(id, ?) = ?' => [5, 3], -]); +// WHERE `id` = 123 +$table->wherePrimary(123); + +// WHERE `id` IN (1, 2, 3) +$table->wherePrimary([1, 2, 3]); +``` + +Αν ο πίνακας έχει σύνθετο πρωτεύον κλειδί (π.χ. `foo_id`, `bar_id`), το περνάμε ως πίνακα: + +```php +// WHERE `foo_id` = 1 AND `bar_id` = 5 +$table->wherePrimary(['foo_id' => 1, 'bar_id' => 5])->fetch(); + +// WHERE (`foo_id`, `bar_id`) IN ((1, 5), (2, 3)) +$table->wherePrimary([ + ['foo_id' => 1, 'bar_id' => 5], + ['foo_id' => 2, 'bar_id' => 3], +])->fetchAll(); ``` -order() .[#toc-order] ---------------------- +order(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- -Παραδείγματα χρήσης: +Καθορίζει τη σειρά με την οποία θα επιστραφούν οι γραμμές. Μπορούμε να ταξινομήσουμε με βάση μία ή περισσότερες στήλες, σε φθίνουσα ή αύξουσα σειρά, ή με βάση μια δική μας έκφραση: ```php -$table->order('field1'); // ORDER BY `field1` -$table->order('field1 DESC, field2'); // ORDER BY `field1` DESC, `field2` -$table->order('field = ? DESC', 123); // ORDER BY `field` = 123 DESC +$table->order('created'); // ORDER BY `created` +$table->order('created DESC'); // ORDER BY `created` DESC +$table->order('priority DESC, created'); // ORDER BY `priority` DESC, `created` +$table->order('status = ? DESC', 'active'); // ORDER BY `status` = 'active' DESC ``` -select() .[#toc-select] ------------------------ +select(string $columns, ...$parameters): static .[method] +--------------------------------------------------------- + +Καθορίζει τις στήλες που θα επιστραφούν από τη βάση δεδομένων. Από προεπιλογή, το Nette Database Explorer επιστρέφει μόνο τις στήλες που χρησιμοποιούνται πραγματικά στον κώδικα. Τη μέθοδο `select()` τη χρησιμοποιούμε λοιπόν σε περιπτώσεις όπου χρειαζόμαστε να επιστρέψουμε συγκεκριμένες εκφράσεις: + +```php +// SELECT *, DATE_FORMAT(`created_at`, "%d.%m.%Y") AS `formatted_date` +$table->select('*, DATE_FORMAT(created_at, ?) AS formatted_date', '%d.%m.%Y'); +``` -Παραδείγματα χρήσης: +Τα ψευδώνυμα (aliases) που ορίζονται με `AS` είναι στη συνέχεια διαθέσιμα ως ιδιότητες του αντικειμένου ActiveRow: ```php -$table->select('field1'); // SELECT `field1` -$table->select('col, UPPER(col) AS abc'); // SELECT `col`, UPPER(`col`) AS abc -$table->select('SUBSTR(title, ?)', 3); // SELECT SUBSTR(`title`, 3) +foreach ($table as $row) { + echo $row->formatted_date; // πρόσβαση στο alias +} ``` -limit() .[#toc-limit] ---------------------- +limit(?int $limit, ?int $offset = null): static .[method] +--------------------------------------------------------- -Παραδείγματα χρήσης: +Περιορίζει τον αριθμό των επιστρεφόμενων γραμμών (LIMIT) και προαιρετικά επιτρέπει τον ορισμό της μετατόπισης (offset): ```php -$table->limit(1); // LIMIT 1 -$table->limit(1, 10); // LIMIT 1 OFFSET 10 +$table->limit(10); // LIMIT 10 (επιστρέφει τις πρώτες 10 γραμμές) +$table->limit(10, 20); // LIMIT 10 OFFSET 20 ``` +Για σελίδωση, είναι προτιμότερο να χρησιμοποιήσετε τη μέθοδο `page()`. -page() .[#toc-page] -------------------- -Ένας εναλλακτικός τρόπος για τον καθορισμό του ορίου και της μετατόπισης: +page(int $page, int $itemsPerPage, &$numOfPages = null): static .[method] +------------------------------------------------------------------------- + +Διευκολύνει τη σελίδωση των αποτελεσμάτων. Δέχεται τον αριθμό της σελίδας (μετρώντας από το 1) και τον αριθμό των στοιχείων ανά σελίδα. Προαιρετικά, μπορεί να περάσει μια αναφορά σε μια μεταβλητή, στην οποία θα αποθηκευτεί ο συνολικός αριθμός σελίδων: ```php -$page = 5; -$itemsPerPage = 10; -$table->page($page, $itemsPerPage); // LIMIT 10 OFFSET 40 +$numOfPages = null; +$table->page(page: 3, itemsPerPage: 10, $numOfPages); +echo "Συνολικά σελίδες: $numOfPages"; ``` - `$lastPage`: + +group(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- + +Ομαδοποιεί τις γραμμές με βάση τις καθορισμένες στήλες (GROUP BY). Χρησιμοποιείται συνήθως σε συνδυασμό με συναρτήσεις συγκέντρωσης (aggregation functions): ```php -$table->page($page, $itemsPerPage, $lastPage); +// Υπολογίζει τον αριθμό των προϊόντων σε κάθε κατηγορία +$table->select('category_id, COUNT(*) AS count') + ->group('category_id'); ``` -group() .[#toc-group] ---------------------- +having(string $having, ...$parameters): static .[method] +-------------------------------------------------------- -Παραδείγματα χρήσης: +Ορίζει συνθήκη για το φιλτράρισμα των ομαδοποιημένων γραμμών (HAVING). Μπορεί να χρησιμοποιηθεί σε συνδυασμό με τη μέθοδο `group()` και συναρτήσεις συγκέντρωσης: ```php -$table->group('field1'); // GROUP BY `field1` -$table->group('field1, field2'); // GROUP BY `field1`, `field2` +// Βρίσκει κατηγορίες που έχουν περισσότερα από 100 προϊόντα +$table->select('category_id, COUNT(*) AS count') + ->group('category_id') + ->having('count > ?', 100); ``` -having() .[#toc-having] ------------------------ +Ανάγνωση Δεδομένων +================== + +Για την ανάγνωση δεδομένων από τη βάση δεδομένων έχουμε στη διάθεσή μας αρκετές χρήσιμες μεθόδους: + +.[language-php] +| `foreach ($table as $key => $row)` | Επαναλαμβάνεται σε όλες τις γραμμές, το `$key` είναι η τιμή του πρωτεύοντος κλειδιού, το `$row` είναι αντικείμενο ActiveRow +| `$row = $table->get($key)` | Επιστρέφει μία γραμμή με βάση το πρωτεύον κλειδί +| `$row = $table->fetch()` | Επιστρέφει την τρέχουσα γραμμή και μετακινεί τον δείκτη στην επόμενη +| `$array = $table->fetchPairs()` | Δημιουργεί έναν συσχετιστικό πίνακα από τα αποτελέσματα +| `$array = $table->fetchAll()` | Επιστρέφει όλες τις γραμμές ως πίνακα +| `count($table)` | Επιστρέφει τον αριθμό των γραμμών στο αντικείμενο Selection + +Το αντικείμενο [ActiveRow |api:Nette\Database\Table\ActiveRow] προορίζεται μόνο για ανάγνωση. Αυτό σημαίνει ότι δεν μπορούν να αλλάξουν οι τιμές των ιδιοτήτων του. Αυτός ο περιορισμός εξασφαλίζει τη συνέπεια των δεδομένων και αποτρέπει απροσδόκητες παρενέργειες. Τα δεδομένα φορτώνονται από τη βάση δεδομένων και οποιαδήποτε αλλαγή θα πρέπει να γίνεται ρητά και ελεγχόμενα. -Παραδείγματα χρήσης: + +`foreach` - Επανάληψη σε Όλες τις Γραμμές +----------------------------------------- + +Ο ευκολότερος τρόπος για να εκτελέσετε ένα ερώτημα και να λάβετε γραμμές είναι με την επανάληψη σε έναν βρόχο `foreach`. Εκτελεί αυτόματα το ερώτημα SQL. ```php -$table->having('COUNT(items) >', 100); // HAVING COUNT(`items`) > 100 +$books = $explorer->table('book'); +foreach ($books as $key => $book) { + // το $key είναι η τιμή του πρωτεύοντος κλειδιού, το $book είναι ActiveRow + echo "$book->title ({$book->author->name})"; +} ``` -Φιλτράρισμα με βάση άλλη τιμή πίνακα .[#toc-joining-key] --------------------------------------------------------- +get($key): ?ActiveRow .[method] +------------------------------- -Αρκετά συχνά χρειάζεται να φιλτράρετε τα αποτελέσματα με βάση κάποια συνθήκη που αφορά έναν άλλο πίνακα της βάσης δεδομένων. Αυτού του είδους οι συνθήκες απαιτούν σύνδεση πινάκων. Ωστόσο, δεν χρειάζεται πλέον να τα γράφετε. +Εκτελεί το ερώτημα SQL και επιστρέφει τη γραμμή με βάση το πρωτεύον κλειδί, ή `null`, αν δεν υπάρχει. -Ας υποθέσουμε ότι πρέπει να πάρετε όλα τα βιβλία των οποίων το όνομα του συγγραφέα είναι 'Jon'. Το μόνο που χρειάζεται να γράψετε είναι το κλειδί σύνδεσης της σχέσης και το όνομα της στήλης στον συνδεδεμένο πίνακα. Το κλειδί σύνδεσης προκύπτει από τη στήλη που αναφέρεται στον πίνακα που θέλετε να συνδέσετε. Στο παράδειγμά μας (βλ. το σχήμα db) είναι η στήλη `author_id`, και αρκεί να χρησιμοποιήσετε μόνο το πρώτο μέρος της - `author` (η κατάληξη `_id` μπορεί να παραλειφθεί). `name` είναι μια στήλη του πίνακα `author` που θέλουμε να χρησιμοποιήσουμε. Μια συνθήκη για τον μεταφραστή βιβλίων (η οποία συνδέεται με τη στήλη `translator_id` ) μπορεί να δημιουργηθεί εξίσου εύκολα. +```php +$book = $explorer->table('book')->get(123); // επιστρέφει ActiveRow με ID 123 ή null +if ($book) { + echo $book->title; +} +``` + + +fetch(): ?ActiveRow .[method] +----------------------------- + +Επιστρέφει μια γραμμή και μετακινεί τον εσωτερικό δείκτη στην επόμενη. Αν δεν υπάρχουν άλλες γραμμές, επιστρέφει `null`. ```php $books = $explorer->table('book'); -$books->where('author.name LIKE ?', '%Jon%'); -$books->where('translator.name', 'David Grudl'); +while ($book = $books->fetch()) { + $this->processBook($book); +} ``` -Η λογική του κλειδιού σύνδεσης καθοδηγείται από την εφαρμογή των [συμβάσεων |api:Nette\Database\Conventions]. Σας ενθαρρύνουμε να χρησιμοποιήσετε το [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions], το οποίο αναλύει τα ξένα κλειδιά σας και σας επιτρέπει να εργάζεστε εύκολα με αυτές τις σχέσεις. -Η σχέση μεταξύ του βιβλίου και του συγγραφέα του είναι 1:N. Η αντίστροφη σχέση είναι επίσης δυνατή. Την ονομάζουμε **backjoin**. Ρίξτε μια ματιά σε ένα άλλο παράδειγμα. Θα θέλαμε να αντλήσουμε όλους τους συγγραφείς, οι οποίοι έχουν γράψει περισσότερα από 3 βιβλία. Για να κάνουμε την αντίστροφη σύνδεση χρησιμοποιούμε τη δήλωση `:` (colon). Colon means that the joined relationship means hasMany (and it's quite logical too, as two dots are more than one dot). Unfortunately, the Selection class isn't smart enough, so we have to help with the aggregation and provide a `GROUP BY`, επίσης η συνθήκη πρέπει να γραφτεί με τη μορφή της δήλωσης `HAVING`. +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Επιστρέφει τα αποτελέσματα ως συσχετιστικό πίνακα. Το πρώτο όρισμα καθορίζει το όνομα της στήλης που θα χρησιμοποιηθεί ως κλειδί στον πίνακα, το δεύτερο όρισμα καθορίζει το όνομα της στήλης που θα χρησιμοποιηθεί ως τιμή: ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book.id) > 3'); +$authors = $explorer->table('author')->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] ``` -Ίσως έχετε παρατηρήσει ότι η έκφραση σύνδεσης αναφέρεται στο βιβλίο, αλλά δεν είναι σαφές, αν κάνουμε σύνδεση μέσω του `author_id` ή του `translator_id`. Στο παραπάνω παράδειγμα, η Selection κάνει σύνδεση μέσω της στήλης `author_id` επειδή βρέθηκε μια αντιστοιχία με τον πίνακα προέλευσης - τον πίνακα `author`. Αν δεν υπήρχε τέτοια αντιστοιχία και υπήρχαν περισσότερες δυνατότητες, η Nette θα έριχνε [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. +Αν δώσουμε μόνο την πρώτη παράμετρο, η τιμή θα είναι ολόκληρη η γραμμή, δηλαδή το αντικείμενο `ActiveRow`: -Για να πραγματοποιήσετε μια σύνδεση μέσω της στήλης `translator_id`, δώστε μια προαιρετική παράμετρο εντός της έκφρασης σύνδεσης. +```php +$authors = $explorer->table('author')->fetchPairs('id'); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` + +Σε περίπτωση διπλότυπων κλειδιών, χρησιμοποιείται η τιμή από την τελευταία γραμμή. Κατά τη χρήση του `null` ως κλειδί, ο πίνακας θα ευρετηριαστεί αριθμητικά από το μηδέν (τότε δεν προκύπτουν συγκρούσεις): ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book(translator).id) > 3'); +$authors = $explorer->table('author')->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] ``` -Ας ρίξουμε μια ματιά σε μερικές πιο δύσκολες εκφράσεις σύνδεσης. -Θα θέλαμε να βρούμε όλους τους συγγραφείς που έχουν γράψει κάτι για την PHP. Όλα τα βιβλία έχουν ετικέτες, οπότε θα πρέπει να επιλέξουμε εκείνους τους συγγραφείς που έχουν γράψει κάποιο βιβλίο με την ετικέτα PHP. +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Εναλλακτικά, μπορείτε να δώσετε ως παράμετρο ένα callback, το οποίο θα επιστρέφει για κάθε γραμμή είτε την ίδια την τιμή, είτε ένα ζεύγος κλειδί-τιμή. ```php -$authors = $explorer->table('author'); -$authors->where(':book:book_tags.tag.name', 'PHP') - ->group('author.id') - ->having('COUNT(:book:book_tags.tag.id) > 0'); +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => "$row->title ({$row->author->name})"); +// ['Πρώτο βιβλίο (Γιάννης Νοβάκ)', ...] + +// Το Callback μπορεί επίσης να επιστρέφει έναν πίνακα με ένα ζεύγος κλειδί & τιμή: +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => [$row->title, $row->author->name]); +// ['Πρώτο βιβλίο' => 'Γιάννης Νοβάκ', ...] ``` -Συγκεντρωτικά ερωτήματα .[#toc-aggregate-queries] +fetchAll(): array .[method] +--------------------------- + +Επιστρέφει όλες τις γραμμές ως συσχετιστικό πίνακα αντικειμένων `ActiveRow`, όπου τα κλειδιά είναι οι τιμές των πρωτευόντων κλειδιών. + +```php +$allBooks = $explorer->table('book')->fetchAll(); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` + + +count(): int .[method] +---------------------- + +Η μέθοδος `count()` χωρίς παράμετρο επιστρέφει τον αριθμό των γραμμών στο αντικείμενο `Selection`: + +```php +$table->where('category', 1); +$count = $table->count(); +$count = count($table); // εναλλακτική +``` + +Προσοχή, το `count()` με παράμετρο εκτελεί τη συνάρτηση συγκέντρωσης COUNT στη βάση δεδομένων. + + +ActiveRow::toArray(): array .[method] +------------------------------------- + +Μετατρέπει το αντικείμενο `ActiveRow` σε συσχετιστικό πίνακα, όπου τα κλειδιά είναι τα ονόματα των στηλών και οι τιμές είναι τα αντίστοιχα δεδομένα. + +```php +$book = $explorer->table('book')->get(1); +$bookArray = $book->toArray(); +// το $bookArray θα είναι ['id' => 1, 'title' => '...', 'author_id' => ..., ...] +``` + + +Συγκέντρωση +=========== + +Η κλάση `Selection` παρέχει μεθόδους για εύκολη εκτέλεση συναρτήσεων συγκέντρωσης (COUNT, SUM, MIN, MAX, AVG κ.λπ.). + +.[language-php] +| `count($expr)` | Μετρά τον αριθμό των γραμμών +| `min($expr)` | Επιστρέφει την ελάχιστη τιμή στη στήλη +| `max($expr)` | Επιστρέφει τη μέγιστη τιμή στη στήλη +| `sum($expr)` | Επιστρέφει το άθροισμα των τιμών στη στήλη +| `aggregation($function)` | Επιτρέπει την εκτέλεση οποιασδήποτε συνάρτησης συγκέντρωσης. Π.χ. `AVG()`, `GROUP_CONCAT()` + + +count(string $expr): int .[method] +---------------------------------- + +Εκτελεί ένα ερώτημα SQL με τη συνάρτηση COUNT και επιστρέφει το αποτέλεσμα. Η μέθοδος χρησιμοποιείται για να διαπιστωθεί πόσες γραμμές αντιστοιχούν σε μια συγκεκριμένη συνθήκη: + +```php +$count = $table->count('*'); // SELECT COUNT(*) FROM `table` +$count = $table->count('DISTINCT column'); // SELECT COUNT(DISTINCT `column`) FROM `table` +``` + +Προσοχή, η μέθοδος [#count()] χωρίς παράμετρο επιστρέφει απλώς τον αριθμό των γραμμών στο αντικείμενο `Selection`. + + +min(string $expr) και max(string $expr) .[method] ------------------------------------------------- -| `$table->count('*')` | Λήψη αριθμού γραμμών -| `$table->count("DISTINCT $column")` | Λήψη αριθμού διαφορετικών τιμών -| `$table->min($column)` | Λήψη ελάχιστης τιμής -| `$table->max($column)` | Λήψη μέγιστης τιμής -| `$table->sum($column)` | Λήψη του αθροίσματος όλων των τιμών -| `$table->aggregation("GROUP_CONCAT($column)")` | Εκτέλεση οποιασδήποτε συνάρτησης συνάθροισης +Οι μέθοδοι `min()` και `max()` επιστρέφουν την ελάχιστη και τη μέγιστη τιμή στην καθορισμένη στήλη ή έκφραση: -.[caution] -Η μέθοδος `count()` χωρίς καθορισμένες παραμέτρους επιλέγει όλες τις εγγραφές και επιστρέφει το μέγεθος του πίνακα, κάτι που είναι πολύ αναποτελεσματικό. Για παράδειγμα, αν πρέπει να υπολογίσετε τον αριθμό των γραμμών για τη σελιδοποίηση, καθορίζετε πάντα το πρώτο όρισμα. +```php +// SELECT MAX(`price`) FROM `products` WHERE `active` = 1 +$maxPrice = $products->where('active', true) + ->max('price'); +``` + + +sum(string $expr) .[method] +--------------------------- +Επιστρέφει το άθροισμα των τιμών στην καθορισμένη στήλη ή έκφραση: -Διαφυγή & παραπομπή .[#toc-escaping-quoting] -============================================ +```php +// SELECT SUM(`price` * `items_in_stock`) FROM `products` WHERE `active` = 1 +$totalPrice = $products->where('active', true) + ->sum('price * items_in_stock'); +``` -Ο Database Explorer είναι έξυπνος και αποφεύγει τις παραμέτρους και τα αναγνωριστικά εισαγωγικά για εσάς. Ωστόσο, πρέπει να τηρούνται αυτοί οι βασικοί κανόνες: -- λέξεις-κλειδιά, συναρτήσεις, διαδικασίες πρέπει να είναι κεφαλαία. -- οι στήλες και οι πίνακες πρέπει να είναι πεζά -- να περνάτε μεταβλητές ως παραμέτρους, να μην τις συνυπολογίζετε +aggregation(string $function, ?string $groupFunction = null) .[method] +---------------------------------------------------------------------- + +Επιτρέπει την εκτέλεση οποιασδήποτε συνάρτησης συγκέντρωσης. ```php -->where('name like ?', 'John'); // ΛΑΘΟΣ! γενιές: `name` `like` ? -->where('name LIKE ?', 'John'); // ΣΩΣΤΟ +// μέση τιμή προϊόντων στην κατηγορία +$avgPrice = $products->where('category_id', 1) + ->aggregation('AVG(price)'); -->where('KEY = ?', $value); // ΛΑΘΟΣ! Το KEY είναι λέξη κλειδί -->where('key = ?', $value); // ΣΩΣΤΟ: `key` = ? +// συνδέει τις ετικέτες του προϊόντος σε ένα string +$tags = $products->where('id', 1) + ->aggregation('GROUP_CONCAT(tag.name) AS tags') + ->fetch() + ->tags; +``` -->where('name = ' . $name); // ΛΑΘΟΣ! sql injection! -->where('name = ?', $name); // ΣΩΣΤΟ +Αν χρειαζόμαστε να συγκεντρώσουμε αποτελέσματα που ήδη προέκυψαν από κάποια συνάρτηση συγκέντρωσης και ομαδοποίηση (π.χ. `SUM(value)` σε ομαδοποιημένες γραμμές), ως δεύτερο όρισμα δίνουμε τη συνάρτηση συγκέντρωσης που πρέπει να εφαρμοστεί σε αυτά τα ενδιάμεσα αποτελέσματα: -->select('DATE_FORMAT(created, "%d.%m.%Y")'); // Περνάτε μεταβλητές ως παραμέτρους, μην κάνετε συνένωση -->select('DATE_FORMAT(created, ?)', '%d.%m.%Y'); // ΣΩΣΤΟ +```php +// Υπολογίζει τη συνολική τιμή των προϊόντων στο απόθεμα για μεμονωμένες κατηγορίες και στη συνέχεια αθροίζει αυτές τις τιμές μαζί. +$totalPrice = $products->select('category_id, SUM(price * stock) AS category_total') + ->group('category_id') + ->aggregation('SUM(category_total)', 'SUM'); ``` -.[warning] -Η λανθασμένη χρήση μπορεί να δημιουργήσει κενά ασφαλείας +Σε αυτό το παράδειγμα, πρώτα υπολογίζουμε τη συνολική τιμή των προϊόντων σε κάθε κατηγορία (`SUM(price * stock) AS category_total`) και ομαδοποιούμε τα αποτελέσματα με βάση το `category_id`. Στη συνέχεια, χρησιμοποιούμε το `aggregation('SUM(category_total)', 'SUM')` για να αθροίσουμε αυτά τα ενδιάμεσα αθροίσματα `category_total`. Το δεύτερο όρισμα `'SUM'` δηλώνει ότι πρέπει να εφαρμοστεί η συνάρτηση SUM στα ενδιάμεσα αποτελέσματα. + +Εισαγωγή, Ενημέρωση & Διαγραφή +============================== -Λήψη δεδομένων .[#toc-fetching-data] -==================================== +Το Nette Database Explorer απλοποιεί την εισαγωγή, την ενημέρωση και τη διαγραφή δεδομένων. Όλες οι αναφερόμενες μέθοδοι, σε περίπτωση σφάλματος, θα προκαλέσουν την εξαίρεση `Nette\Database\DriverException`. -| `foreach ($table as $id => $row)` | Επανάληψη σε όλες τις γραμμές του αποτελέσματος -| `$row = $table->get($id)` | Λήψη μεμονωμένης γραμμής με αναγνωριστικό $id από τον πίνακα -| `$row = $table->fetch()` | Λήψη της επόμενης γραμμής από το αποτέλεσμα -| `$array = $table->fetchPairs($key, $value)` | Λήψη όλων των τιμών σε συσχετιστικό πίνακα -| `$array = $table->fetchPairs($key)` | Λήψη όλων των γραμμών σε συσχετιστικό πίνακα -| `count($table)` | Λήψη αριθμού γραμμών στο σύνολο αποτελεσμάτων +Selection::insert(iterable $data) .[method] +------------------------------------------- + +Εισάγει νέες εγγραφές στον πίνακα. -Εισαγωγή, ενημέρωση και διαγραφή .[#toc-insert-update-delete] -============================================================= +**Εισαγωγή μιας εγγραφής:** -Η μέθοδος `insert()` δέχεται πίνακα αντικειμένων Traversable (για παράδειγμα [ArrayHash |utils:arrays#ArrayHash] που επιστρέφει [μορφές |forms:]): +Τη νέα εγγραφή την περνάμε ως συσχετιστικό πίνακα ή iterable αντικείμενο (για παράδειγμα ArrayHash που χρησιμοποιείται στις [φόρμες |forms:]), όπου τα κλειδιά αντιστοιχούν στα ονόματα των στηλών στον πίνακα. + +Αν ο πίνακας έχει ορισμένο πρωτεύον κλειδί, η μέθοδος επιστρέφει ένα αντικείμενο `ActiveRow`, το οποίο επαναφορτώνεται από τη βάση δεδομένων, ώστε να ληφθούν υπόψη τυχόν αλλαγές που έγιναν σε επίπεδο βάσης δεδομένων (triggers, προεπιλεγμένες τιμές στηλών, υπολογισμοί auto-increment στηλών). Έτσι εξασφαλίζεται η συνέπεια των δεδομένων και το αντικείμενο περιέχει πάντα τα τρέχοντα δεδομένα από τη βάση δεδομένων. Αν δεν έχει μοναδικό πρωτεύον κλειδί, επιστρέφει τα παραδοθέντα δεδομένα με τη μορφή πίνακα. ```php $row = $explorer->table('users')->insert([ - 'name' => $name, - 'year' => $year, + 'name' => 'John Doe', + 'email' => 'john.doe@example.com', ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) +// το $row είναι παρουσία του ActiveRow και περιέχει τα πλήρη δεδομένα της εισαχθείσας γραμμής, +// συμπεριλαμβανομένου του αυτόματα παραγόμενου ID και τυχόν αλλαγών που έγιναν από triggers +echo $row->id; // Εμφανίζει το ID του νέου εισαχθέντος χρήστη +echo $row->created_at; // Εμφανίζει τον χρόνο δημιουργίας, αν έχει οριστεί από trigger ``` -Εάν έχει οριστεί πρωτεύον κλειδί στον πίνακα, επιστρέφεται ένα αντικείμενο ActiveRow που περιέχει την εισαγόμενη γραμμή. +**Εισαγωγή πολλαπλών εγγραφών ταυτόχρονα:** -Πολλαπλή εισαγωγή: +Η μέθοδος `insert()` επιτρέπει την εισαγωγή πολλαπλών εγγραφών με ένα μόνο ερώτημα SQL. Σε αυτή την περίπτωση, επιστρέφει τον αριθμό των εισαχθέντων γραμμών. ```php -$explorer->table('users')->insert([ +$insertedRows = $explorer->table('users')->insert([ + [ + 'name' => 'John', + 'year' => 1994, + ], [ - 'name' => 'Jim', - 'year' => 1978, - ], [ 'name' => 'Jack', - 'year' => 1987, + 'year' => 1995, ], ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) +// INSERT INTO `users` (`name`, `year`) VALUES ('John', 1994), ('Jack', 1995) +// το $insertedRows θα είναι 2 +``` + +Ως παράμετρο μπορεί επίσης να περάσει ένα αντικείμενο `Selection` με επιλογή δεδομένων. + +```php +$newUsers = $explorer->table('potential_users') + ->where('approved', 1) + ->select('name, email'); + +$insertedRows = $explorer->table('users')->insert($newUsers); ``` -DateTime μπορούν να περάσουν ως παράμετροι: +**Εισαγωγή ειδικών τιμών:** + +Ως τιμές μπορούμε να περάσουμε και αρχεία, αντικείμενα DateTime ή SQL literals: ```php $explorer->table('users')->insert([ - 'name' => $name, - 'created' => new DateTime, // ή $explorer::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // εισάγει το αρχείο + 'name' => 'John', + 'created_at' => new DateTime, // μετατρέπει σε μορφή βάσης δεδομένων + 'avatar' => fopen('image.jpg', 'rb'), // εισάγει το δυαδικό περιεχόμενο του αρχείου + 'uuid' => $explorer::literal('UUID()'), // καλεί τη συνάρτηση UUID() της βάσης δεδομένων ]); ``` -Ενημέρωση (επιστρέφει τον αριθμό των επηρεαζόμενων γραμμών): + +Selection::update(iterable $data): int .[method] +------------------------------------------------ + +Ενημερώνει γραμμές στον πίνακα σύμφωνα με το καθορισμένο φίλτρο. Επιστρέφει τον αριθμό των γραμμών που πραγματικά άλλαξαν. + +Τις στήλες που αλλάζουν τις περνάμε ως συσχετιστικό πίνακα ή iterable αντικείμενο (για παράδειγμα ArrayHash που χρησιμοποιείται στις [φόρμες |forms:]), όπου τα κλειδιά αντιστοιχούν στα ονόματα των στηλών στον πίνακα: ```php -$count = $explorer->table('users') - ->where('id', 10) // πρέπει να καλείται πριν από την update() +$affected = $explorer->table('users') + ->where('id', 10) ->update([ - 'name' => 'Ned Stark' + 'name' => 'John Smith', + 'year' => 1994, ]); -// UPDATE `users` SET `name`='Ned Stark' WHERE (`id` = 10) +// UPDATE `users` SET `name` = 'John Smith', `year` = 1994 WHERE `id` = 10 ``` -Για την ενημέρωση μπορούμε να χρησιμοποιήσουμε τους τελεστές `+=` a `-=`: +Για την αλλαγή αριθμητικών τιμών μπορούμε να χρησιμοποιήσουμε τους τελεστές `+=` και `-=`: ```php $explorer->table('users') + ->where('id', 10) ->update([ - 'age+=' => 1, // see += + 'points+=' => 1, // αυξάνει την τιμή της στήλης 'points' κατά 1 + 'coins-=' => 1, // μειώνει την τιμή της στήλης 'coins' κατά 1 ]); -// UPDATE users SET `age` = `age` + 1 +// UPDATE `users` SET `points` = `points` + 1, `coins` = `coins` - 1 WHERE `id` = 10 ``` -Διαγραφή (επιστρέφει τον αριθμό των διαγραμμένων γραμμών): + +Selection::delete(): int .[method] +---------------------------------- + +Διαγράφει γραμμές από τον πίνακα σύμφωνα με το καθορισμένο φίλτρο. Επιστρέφει τον αριθμό των διαγραμμένων γραμμών. ```php $count = $explorer->table('users') ->where('id', 10) ->delete(); -// DELETE FROM `users` WHERE (`id` = 10) +// DELETE FROM `users` WHERE `id` = 10 ``` +.[caution] +Κατά την κλήση `update()` και `delete()`, μην ξεχάσετε να καθορίσετε με το `where()` τις γραμμές που πρέπει να τροποποιηθούν/διαγραφούν. Αν δεν χρησιμοποιήσετε το `where()`, η λειτουργία θα εκτελεστεί σε ολόκληρο τον πίνακα! -Εργασία με σχέσεις .[#toc-working-with-relationships] -===================================================== +ActiveRow::update(iterable $data): bool .[method] +------------------------------------------------- + +Ενημερώνει τα δεδομένα στη γραμμή της βάσης δεδομένων που αντιπροσωπεύεται από το αντικείμενο `ActiveRow`. Ως παράμετρο δέχεται ένα iterable με τα δεδομένα που πρέπει να ενημερωθούν (τα κλειδιά είναι τα ονόματα των στηλών). Για την αλλαγή αριθμητικών τιμών μπορούμε να χρησιμοποιήσουμε τους τελεστές `+=` και `-=`: -Έχει μία σχέση .[#toc-has-one-relation] ---------------------------------------- -Το Has one relation είναι μια συνηθισμένη περίπτωση χρήσης. Το βιβλίο *έχει έναν* συγγραφέα. Το βιβλίο *έχει έναν* μεταφραστή. Η λήψη σχετικής σειράς γίνεται κυρίως με τη μέθοδο `ref()`. Δέχεται δύο ορίσματα: το όνομα του πίνακα-στόχου και τη στήλη σύνδεσης της πηγής. Βλέπε παράδειγμα: +Μετά την εκτέλεση της ενημέρωσης, το `ActiveRow` επαναφορτώνεται αυτόματα από τη βάση δεδομένων, ώστε να ληφθούν υπόψη τυχόν αλλαγές που έγιναν σε επίπεδο βάσης δεδομένων (π.χ. triggers). Η μέθοδος επιστρέφει `true` μόνο αν έγινε πραγματική αλλαγή δεδομένων. ```php -$book = $explorer->table('book')->get(1); -$book->ref('author', 'author_id'); +$article = $explorer->table('article')->get(1); +$article->update([ + 'views += 1', // αυξάνουμε τον αριθμό προβολών +]); +echo $article->views; // Εμφανίζει τον τρέχοντα αριθμό προβολών ``` - `author` Το πρωτεύον κλειδί του συγγραφέα αναζητείται από τη στήλη `book.author_id`. Η μέθοδος Ref() επιστρέφει την περίπτωση ActiveRow ή null αν δεν υπάρχει κατάλληλη εγγραφή. Η γραμμή που επιστρέφεται είναι μια περίπτωση της ActiveRow, οπότε μπορούμε να εργαστούμε με αυτήν με τον ίδιο τρόπο όπως με την εγγραφή του βιβλίου. +Αυτή η μέθοδος ενημερώνει μόνο μία συγκεκριμένη γραμμή στη βάση δεδομένων. Για μαζική ενημέρωση πολλαπλών γραμμών χρησιμοποιήστε τη μέθοδο [#Selection::update()]. + + +ActiveRow::delete() .[method] +----------------------------- + +Διαγράφει τη γραμμή από τη βάση δεδομένων, η οποία αντιπροσωπεύεται από το αντικείμενο `ActiveRow`. ```php -$author = $book->ref('author', 'author_id'); -$author->name; -$author->born; +$book = $explorer->table('book')->get(1); +$book->delete(); // Διαγράφει το βιβλίο με ID 1 +``` + +Αυτή η μέθοδος διαγράφει μόνο μία συγκεκριμένη γραμμή στη βάση δεδομένων. Για μαζική διαγραφή πολλαπλών γραμμών χρησιμοποιήστε τη μέθοδο [#Selection::delete()]. + + +Σχέσεις μεταξύ Πινάκων +====================== + +Σε σχεσιακές βάσεις δεδομένων, τα δεδομένα χωρίζονται σε πολλούς πίνακες και συνδέονται μεταξύ τους με ξένα κλειδιά. Το Nette Database Explorer φέρνει έναν επαναστατικό τρόπο εργασίας με αυτές τις σχέσεις - χωρίς να γράφετε ερωτήματα JOIN και χωρίς την ανάγκη να διαμορφώνετε ή να παράγετε οτιδήποτε. + +Για την απεικόνιση της εργασίας με τις σχέσεις θα χρησιμοποιήσουμε ένα παράδειγμα βάσης δεδομένων βιβλίων ([μπορείτε να το βρείτε στο GitHub |https://github.com/nette-examples/books]). Στη βάση δεδομένων έχουμε τους πίνακες: + +- `author` - συγγραφείς και μεταφραστές (στήλες `id`, `name`, `web`, `born`) +- `book` - βιβλία (στήλες `id`, `author_id`, `translator_id`, `title`, `sequel_id`) +- `tag` - ετικέτες (στήλες `id`, `name`) +- `book_tag` - πίνακας σύνδεσης μεταξύ βιβλίων και ετικετών (στήλες `book_id`, `tag_id`) + +[* db-schema-1-.webp *] *** Δομή της βάσης δεδομένων .<> + +Στο παράδειγμά μας της βάσης δεδομένων βιβλίων βρίσκουμε διάφορους τύπους σχέσεων (αν και το μοντέλο είναι απλοποιημένο σε σχέση με την πραγματικότητα): + +- One-to-many (1:N) – κάθε βιβλίο **έχει έναν** συγγραφέα, ο συγγραφέας μπορεί να γράψει **πολλά** βιβλία. +- Zero-to-many (0:N) – το βιβλίο **μπορεί να έχει** μεταφραστή, ο μεταφραστής μπορεί να μεταφράσει **πολλά** βιβλία. +- Zero-to-one (0:1) – το βιβλίο **μπορεί να έχει** επόμενο τόμο. +- Many-to-many (M:N) – το βιβλίο **μπορεί να έχει πολλές** ετικέτες και η ετικέτα μπορεί να αντιστοιχιστεί σε **πολλά** βιβλία. -// ή απευθείας -$book->ref('author', 'author_id')->name; -$book->ref('author', 'author_id')->born; +Σε αυτές τις σχέσεις υπάρχει πάντα ένας γονικός (parent) και ένας παιδικός (child) πίνακας. Για παράδειγμα, στη σχέση μεταξύ συγγραφέα και βιβλίου, ο πίνακας `author` είναι γονικός και ο `book` παιδικός - μπορούμε να το φανταστούμε έτσι ώστε το βιβλίο πάντα "ανήκει" σε κάποιον συγγραφέα. Αυτό εκδηλώνεται και στη δομή της βάσης δεδομένων: ο παιδικός πίνακας `book` περιέχει το ξένο κλειδί `author_id`, το οποίο αναφέρεται στον γονικό πίνακα `author`. + +Αν χρειαζόμαστε να εμφανίσουμε τα βιβλία συμπεριλαμβανομένων των ονομάτων των συγγραφέων τους, έχουμε δύο δυνατότητες. Είτε θα λάβουμε τα δεδομένα με ένα μόνο ερώτημα SQL χρησιμοποιώντας JOIN: + +```sql +SELECT book.*, author.name FROM book LEFT JOIN author ON book.author_id = author.id +``` + +Είτε θα φορτώσουμε τα δεδομένα σε δύο βήματα - πρώτα τα βιβλία και μετά τους συγγραφείς τους - και στη συνέχεια θα τα συνθέσουμε στην PHP: + +```sql +SELECT * FROM book; +SELECT * FROM author WHERE id IN (1, 2, 3); -- ids των συγγραφέων των ληφθέντων βιβλίων ``` -Το βιβλίο έχει επίσης έναν μεταφραστή, οπότε το να πάρουμε το όνομα του μεταφραστή είναι αρκετά εύκολο. +Η δεύτερη προσέγγιση είναι στην πραγματικότητα πιο αποτελεσματική, αν και αυτό μπορεί να προκαλεί έκπληξη. Τα δεδομένα φορτώνονται μόνο μία φορά και μπορούν να αξιοποιηθούν καλύτερα στην cache. Ακριβώς με αυτόν τον τρόπο λειτουργεί το Nette Database Explorer - λύνει τα πάντα κάτω από την επιφάνεια και σας προσφέρει ένα κομψό API: + ```php -$book->ref('author', 'translator_id')->name +$books = $explorer->table('book'); +foreach ($books as $book) { + echo 'τίτλος: ' . $book->title; + echo 'γράφτηκε από: ' . $book->author->name; // το $book->author είναι η εγγραφή από τον πίνακα 'author' + echo 'μεταφράστηκε από: ' . $book->translator?->name; +} ``` -Όλα αυτά είναι μια χαρά, αλλά είναι κάπως δυσκίνητα, δεν νομίζετε; Η Εξερεύνηση βάσης δεδομένων περιέχει ήδη τους ορισμούς των ξένων κλειδιών, οπότε γιατί να μην τους χρησιμοποιήσετε αυτόματα; Ας το κάνουμε αυτό! -Αν καλέσουμε ιδιότητα, η οποία δεν υπάρχει, το ActiveRow προσπαθεί να επιλύσει το όνομα της καλούσας ιδιότητας ως σχέση 'έχει μία'. Η λήψη αυτής της ιδιότητας είναι το ίδιο με την κλήση της μεθόδου ref() με ένα μόνο όρισμα. Θα ονομάσουμε το μοναδικό όρισμα **key**. Το κλειδί θα επιλυθεί σε συγκεκριμένη σχέση ξένου κλειδιού. Το περασμένο κλειδί συγκρίνεται με τις στήλες της γραμμής, και αν ταιριάζει, το ξένο κλειδί που ορίζεται στην αντίστοιχη στήλη χρησιμοποιείται για τη λήψη δεδομένων από τον σχετικό πίνακα-στόχο. Βλέπε παράδειγμα: +Πρόσβαση στον Γονικό Πίνακα +--------------------------- + +Η πρόσβαση στον γονικό πίνακα είναι άμεση. Πρόκειται για σχέσεις όπως *το βιβλίο έχει συγγραφέα* ή *το βιβλίο μπορεί να έχει μεταφραστή*. Την σχετιζόμενη εγγραφή την λαμβάνουμε μέσω της ιδιότητας του αντικειμένου ActiveRow - το όνομά της αντιστοιχεί στο όνομα της στήλης με το ξένο κλειδί, αφαιρώντας το `_id`: ```php -$book->author->name; -// το ίδιο με -$book->ref('author')->name; +$book = $explorer->table('book')->get(1); +echo $book->author->name; // βρίσκει τον συγγραφέα με βάση τη στήλη author_id +echo $book->translator?->name; // βρίσκει τον μεταφραστή με βάση τη στήλη translator_id ``` -Η περίπτωση ActiveRow δεν έχει στήλη author. Όλες οι στήλες βιβλίων αναζητούνται για μια αντιστοιχία με το *key*. Η αντιστοίχιση σε αυτή την περίπτωση σημαίνει ότι το όνομα της στήλης πρέπει να περιέχει το κλειδί. Έτσι, στο παραπάνω παράδειγμα, η στήλη `author_id` περιέχει τη συμβολοσειρά 'author' και επομένως ταιριάζει με το κλειδί 'author'. Αν θέλετε να βρείτε τον μεταφραστή του βιβλίου, μπορείτε απλώς να χρησιμοποιήσετε π.χ. το 'translator' ως κλειδί, επειδή το κλειδί 'translator' θα ταιριάζει με τη στήλη `translator_id`. Μπορείτε να μάθετε περισσότερα για τη λογική αντιστοίχισης κλειδιών στο κεφάλαιο [Joining expressions |#joining-key]. +Όταν αποκτούμε πρόσβαση στην ιδιότητα `$book->author`, ο Explorer στον πίνακα `book` αναζητά μια στήλη της οποίας το όνομα περιέχει το string `author` (δηλαδή `author_id`). Με βάση την τιμή σε αυτή τη στήλη, φορτώνει την αντίστοιχη εγγραφή από τον πίνακα `author` και την επιστρέφει ως `ActiveRow`. Παρόμοια λειτουργεί και το `$book->translator`, το οποίο χρησιμοποιεί τη στήλη `translator_id`. Επειδή η στήλη `translator_id` μπορεί να περιέχει `null`, χρησιμοποιούμε στον κώδικα τον τελεστή nullsafe `?->`. + +Μια εναλλακτική οδό προσφέρει η μέθοδος `ref()`, η οποία δέχεται δύο ορίσματα, το όνομα του πίνακα προορισμού και το όνομα της στήλης σύνδεσης, και επιστρέφει μια παρουσία `ActiveRow` ή `null`: ```php -echo $book->title . ': '; -echo $book->author->name; -if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; -} +echo $book->ref('author', 'author_id')->name; // σχέση με τον συγγραφέα +echo $book->ref('author', 'translator_id')->name; // σχέση με τον μεταφραστή ``` -Αν θέλετε να αντλήσετε πολλά βιβλία, θα πρέπει να χρησιμοποιήσετε την ίδια προσέγγιση. Ο Nette Database Explorer θα αντλήσει συγγραφείς και μεταφραστές για όλα τα βιβλία που θα αντλήσετε ταυτόχρονα. +Η μέθοδος `ref()` είναι χρήσιμη αν δεν μπορεί να χρησιμοποιηθεί η πρόσβαση μέσω ιδιότητας, επειδή ο πίνακας περιέχει στήλη με το ίδιο όνομα (δηλ. `author`). Στις υπόλοιπες περιπτώσεις, συνιστάται η χρήση της πρόσβασης μέσω ιδιότητας, η οποία είναι πιο ευανάγνωστη. + +Ο Explorer βελτιστοποιεί αυτόματα τα ερωτήματα της βάσης δεδομένων. Όταν διατρέχουμε τα βιβλία σε έναν βρόχο και αποκτούμε πρόσβαση στις σχετιζόμενες εγγραφές τους (συγγραφείς, μεταφραστές), ο Explorer δεν παράγει ένα ερώτημα για κάθε βιβλίο ξεχωριστά. Αντ' αυτού, εκτελεί μόνο ένα SELECT για κάθε τύπο σχέσης, μειώνοντας έτσι σημαντικά το φορτίο της βάσης δεδομένων. Για παράδειγμα: ```php $books = $explorer->table('book'); foreach ($books as $book) { echo $book->title . ': '; echo $book->author->name; - if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; - } + echo $book->translator?->name; } ``` -Ο κώδικας θα εκτελέσει μόνο αυτά τα 3 ερωτήματα: +Αυτός ο κώδικας θα καλέσει μόνο αυτά τα τρία αστραπιαία ερωτήματα στη βάση δεδομένων: + ```sql SELECT * FROM `book`; -SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- ids of fetched books from author_id column -SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- ids of fetched books from translator_id column +SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- id από τη στήλη author_id των επιλεγμένων βιβλίων +SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- id από τη στήλη translator_id των επιλεγμένων βιβλίων ``` +.[note] +Η λογική εύρεσης της στήλης σύνδεσης καθορίζεται από την υλοποίηση των [Conventions |api:Nette\Database\Conventions]. Συνιστούμε τη χρήση των [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions], οι οποίες αναλύουν τα ξένα κλειδιά και επιτρέπουν την εύκολη εργασία με τις υπάρχουσες σχέσεις μεταξύ των πινάκων. -Has Many Relation .[#toc-has-many-relation] -------------------------------------------- -Η σχέση 'Έχει πολλούς' είναι απλώς η αντίστροφη σχέση 'έχει έναν'. Ο συγγραφέας *έχει* γράψει *πολλά* βιβλία. Ο συγγραφέας *έχει* μεταφράσει *πολλά* βιβλία. Όπως μπορείτε να δείτε, αυτός ο τύπος σχέσης είναι λίγο πιο δύσκολος επειδή η σχέση είναι 'ονομαστική' ('έγραψε', 'μετέφρασε'). Η περίπτωση ActiveRow έχει τη μέθοδο `related()`, η οποία θα επιστρέψει πίνακα σχετικών καταχωρήσεων. Οι εγγραφές είναι επίσης περιπτώσεις ActiveRow. Δείτε το παράδειγμα παρακάτω: +Πρόσβαση στον Παιδικό Πίνακα +---------------------------- + +Η πρόσβαση στον παιδικό πίνακα λειτουργεί με την αντίστροφη κατεύθυνση. Τώρα ρωτάμε *ποια βιβλία έγραψε αυτός ο συγγραφέας* ή *μετέφρασε αυτός ο μεταφραστής*. Για αυτόν τον τύπο ερωτήματος χρησιμοποιούμε τη μέθοδο `related()`, η οποία επιστρέφει ένα `Selection` με τις σχετιζόμενες εγγραφές. Ας δούμε ένα παράδειγμα: ```php -$author = $explorer->table('author')->get(11); -echo $author->name . ' has written:'; +$author = $explorer->table('author')->get(1); +// Εμφανίζει όλα τα βιβλία του συγγραφέα foreach ($author->related('book.author_id') as $book) { - echo $book->title; + echo "Έγραψε: $book->title"; } -echo 'and translated:'; +// Εμφανίζει όλα τα βιβλία που μετέφρασε ο συγγραφέας foreach ($author->related('book.translator_id') as $book) { - echo $book->title; + echo "Μετέφρασε: $book->title"; } ``` -Η μέθοδος `related()` δέχεται την πλήρη περιγραφή join που περνάει ως δύο ορίσματα ή ως ένα όρισμα ενωμένο με τελεία. Το πρώτο όρισμα είναι ο πίνακας-στόχος, το δεύτερο είναι η στήλη-στόχος. +Η μέθοδος `related()` δέχεται την περιγραφή της σύνδεσης ως ένα όρισμα με σημειογραφία τελείας ή ως δύο ξεχωριστά ορίσματα: ```php -$author->related('book.translator_id'); -// το ίδιο με -$author->related('book', 'translator_id'); +$author->related('book.translator_id'); // ένα όρισμα +$author->related('book', 'translator_id'); // δύο ορίσματα ``` -Μπορείτε να χρησιμοποιήσετε τις ευρετικές λειτουργίες του Nette Database Explorer που βασίζονται σε ξένα κλειδιά και να δώσετε μόνο το όρισμα **key**. Το κλειδί θα συγκριθεί με όλα τα ξένα κλειδιά που δείχνουν στον τρέχοντα πίνακα (`author` table). Εάν υπάρχει ταύτιση, ο Nette Database Explorer θα χρησιμοποιήσει αυτό το ξένο κλειδί, διαφορετικά θα πετάξει [Nette\InvalidArgumentException |api:Nette\InvalidArgumentException] ή [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. Μπορείτε να βρείτε περισσότερα για τη λογική αντιστοίχισης κλειδιών στο κεφάλαιο [Joining expressions |#joining-key]. +Ο Explorer μπορεί να ανιχνεύσει αυτόματα τη σωστή στήλη σύνδεσης με βάση το όνομα του γονικού πίνακα. Σε αυτή την περίπτωση, η σύνδεση γίνεται μέσω της στήλης `book.author_id`, επειδή το όνομα του πίνακα πηγής είναι `author`: -Φυσικά, μπορείτε να καλέσετε τις σχετικές μεθόδους για όλους τους συγγραφείς που ανακτήθηκαν, ο Nette Database Explorer θα ανακτήσει και πάλι τα κατάλληλα βιβλία ταυτόχρονα. +```php +$author->related('book'); // χρησιμοποιεί το book.author_id +``` + +Αν υπήρχαν περισσότερες πιθανές συνδέσεις, ο Explorer θα προκαλούσε την εξαίρεση [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. + +Τη μέθοδο `related()` μπορούμε φυσικά να τη χρησιμοποιήσουμε και κατά τη διέλευση πολλαπλών εγγραφών σε έναν βρόχο και ο Explorer και σε αυτή την περίπτωση βελτιστοποιεί αυτόματα τα ερωτήματα: ```php $authors = $explorer->table('author'); foreach ($authors as $author) { - echo $author->name . ' has written:'; + echo $author->name . ' έγραψε:'; foreach ($author->related('book') as $book) { - $book->title; + echo $book->title; } } ``` -Το παραπάνω παράδειγμα θα εκτελέσει μόνο δύο ερωτήματα: +Αυτός ο κώδικας θα παράγει μόνο δύο αστραπιαία ερωτήματα SQL: ```sql SELECT * FROM `author`; -SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- ids of fetched authors +SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- id των επιλεγμένων συγγραφέων +``` + + +Σχέση Many-to-Many +------------------ + +Για τη σχέση many-to-many (M:N) είναι απαραίτητη η ύπαρξη ενός πίνακα σύνδεσης (στην περίπτωσή μας `book_tag`), ο οποίος περιέχει δύο στήλες με ξένα κλειδιά (`book_id`, `tag_id`). Κάθε μία από αυτές τις στήλες αναφέρεται στο πρωτεύον κλειδί ενός από τους συνδεόμενους πίνακες. Για να λάβουμε τα σχετιζόμενα δεδομένα, πρώτα λαμβάνουμε τις εγγραφές από τον πίνακα σύνδεσης χρησιμοποιώντας το `related('book_tag')` και στη συνέχεια συνεχίζουμε στα δεδομένα προορισμού: + +```php +$book = $explorer->table('book')->get(1); +// εμφανίζει τα ονόματα των ετικετών που έχουν αντιστοιχιστεί στο βιβλίο +foreach ($book->related('book_tag') as $bookTag) { + echo $bookTag->tag->name; // εμφανίζει το όνομα της ετικέτας μέσω του πίνακα σύνδεσης +} + +$tag = $explorer->table('tag')->get(1); +// ή αντίστροφα: εμφανίζει τα ονόματα των βιβλίων που έχουν επισημανθεί με αυτή την ετικέτα +foreach ($tag->related('book_tag') as $bookTag) { + echo $bookTag->book->title; // εμφανίζει το όνομα του βιβλίου +} ``` +Ο Explorer πάλι βελτιστοποιεί τα ερωτήματα SQL σε αποτελεσματική μορφή: + +```sql +SELECT * FROM `book`; +SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 2, ...)); -- id των επιλεγμένων βιβλίων +SELECT * FROM `tag` WHERE (`tag`.`id` IN (1, 2, ...)); -- id των ετικετών που βρέθηκαν στο book_tag +``` -Δημιουργία Explorer χειροκίνητα .[#toc-creating-explorer-manually] -================================================================== -Μια σύνδεση βάσης δεδομένων μπορεί να δημιουργηθεί χρησιμοποιώντας τη διαμόρφωση της εφαρμογής. Σε τέτοιες περιπτώσεις δημιουργείται μια υπηρεσία `Nette\Database\Explorer` και μπορεί να περάσει ως εξάρτηση χρησιμοποιώντας το DI container. +Ερωτήματα μέσω Σχετικών Πινάκων +------------------------------- -Ωστόσο, εάν ο Nette Database Explorer χρησιμοποιείται ως αυτόνομο εργαλείο, πρέπει να δημιουργηθεί χειροκίνητα μια περίπτωση του αντικειμένου `Nette\Database\Explorer`. +Στις μεθόδους `where()`, `select()`, `order()` και `group()` μπορούμε να χρησιμοποιούμε ειδικές σημειογραφίες για την πρόσβαση σε στήλες από άλλους πίνακες. Ο Explorer δημιουργεί αυτόματα τα απαραίτητα JOINs. + +**Σημειογραφία τελείας** (`parent_table.column`) χρησιμοποιείται για τη σχέση 1:N από την οπτική γωνία του παιδικού πίνακα: ```php -// $storage implements Nette\Caching\Storage: -$storage = new Nette\Caching\Storages\FileStorage($tempDir); -$connection = new Nette\Database\Connection($dsn, $user, $password); -$structure = new Nette\Database\Structure($connection, $storage); -$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); -$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +$books = $explorer->table('book'); + +// Βρίσκει βιβλία των οποίων ο συγγραφέας έχει όνομα που αρχίζει από 'Jon' +$books->where('author.name LIKE ?', 'Jon%'); + +// Ταξινομεί τα βιβλία με βάση το όνομα του συγγραφέα φθίνουσα +$books->order('author.name DESC'); + +// Εμφανίζει τον τίτλο του βιβλίου και το όνομα του συγγραφέα +$books->select('book.title, author.name'); ``` + +**Σημειογραφία άνω και κάτω τελείας** (`:child_table.column`) χρησιμοποιείται για τη σχέση 1:N από την οπτική γωνία του γονικού πίνακα: + +```php +$authors = $explorer->table('author'); + +// Βρίσκει συγγραφείς που έγραψαν βιβλίο με 'PHP' στον τίτλο +$authors->where(':book.title LIKE ?', '%PHP%'); + +// Μετρά τον αριθμό των βιβλίων για κάθε συγγραφέα +$authors->select('*, COUNT(:book.id) AS book_count') + ->group('author.id'); +``` + +Στο παραπάνω παράδειγμα με τη σημειογραφία άνω και κάτω τελείας (`:book.title`) δεν καθορίζεται η στήλη με το ξένο κλειδί. Ο Explorer ανιχνεύει αυτόματα τη σωστή στήλη με βάση το όνομα του γονικού πίνακα. Σε αυτή την περίπτωση, η σύνδεση γίνεται μέσω της στήλης `book.author_id`, επειδή το όνομα του πίνακα πηγής είναι `author`. Αν υπήρχαν περισσότερες πιθανές συνδέσεις, ο Explorer θα προκαλούσε την εξαίρεση [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. + +Η στήλη σύνδεσης μπορεί να δηλωθεί ρητά σε παρένθεση: + +```php +// Βρίσκει συγγραφείς που μετέφρασαν βιβλίο με 'PHP' στον τίτλο +$authors->where(':book(translator_id).title LIKE ?', '%PHP%'); +``` + +Οι σημειογραφίες μπορούν να αλυσιδωθούν για πρόσβαση μέσω πολλαπλών πινάκων: + +```php +// Βρίσκει συγγραφείς βιβλίων που έχουν επισημανθεί με την ετικέτα 'PHP' +$authors->where(':book:book_tag.tag.name', 'PHP') + ->group('author.id'); +``` + + +Επέκταση Συνθηκών για JOIN +-------------------------- + +Η μέθοδος `joinWhere()` επεκτείνει τις συνθήκες που αναφέρονται κατά τη σύνδεση πινάκων στο SQL μετά τη λέξη-κλειδί `ON`. + +Ας υποθέσουμε ότι θέλουμε να βρούμε βιβλία που μεταφράστηκαν από έναν συγκεκριμένο μεταφραστή: + +```php +// Βρίσκει βιβλία που μεταφράστηκαν από τον μεταφραστή με όνομα 'David' +$books = $explorer->table('book') + ->joinWhere('translator', 'translator.name', 'David'); +// LEFT JOIN author translator ON book.translator_id = translator.id AND (translator.name = 'David') +``` + +Στη συνθήκη `joinWhere()` μπορούμε να χρησιμοποιούμε τις ίδιες κατασκευές όπως στη μέθοδο `where()` - τελεστές, placeholders ερωτηματικά (?), πίνακες τιμών ή εκφράσεις SQL. + +Για πιο πολύπλοκα ερωτήματα με πολλαπλά JOINs, μπορούμε να ορίσουμε ψευδώνυμα (aliases) πινάκων: + +```php +$tags = $explorer->table('tag') + ->joinWhere(':book_tag.book.author', 'book_author.born < ?', 1950) + ->alias(':book_tag.book.author', 'book_author'); +// LEFT JOIN `book_tag` ON `tag`.`id` = `book_tag`.`tag_id` +// LEFT JOIN `book` ON `book_tag`.`book_id` = `book`.`id` +// LEFT JOIN `author` `book_author` ON `book`.`author_id` = `book_author`.`id` +// AND (`book_author`.`born` < 1950) +``` + +Παρατηρήστε ότι ενώ η μέθοδος `where()` προσθέτει συνθήκες στην πρόταση `WHERE`, η μέθοδος `joinWhere()` επεκτείνει τις συνθήκες στην πρόταση `ON` κατά τη σύνδεση των πινάκων. diff --git a/database/el/guide.texy b/database/el/guide.texy new file mode 100644 index 0000000000..c3515aba11 --- /dev/null +++ b/database/el/guide.texy @@ -0,0 +1,216 @@ +Nette Database +************** + +.[perex] +Το Nette Database είναι ένα ισχυρό και κομψό επίπεδο βάσης δεδομένων για PHP με έμφαση στην απλότητα και τις έξυπνες λειτουργίες. Προσφέρει δύο τρόπους εργασίας με τη βάση δεδομένων - [Explorer |Explorer] για γρήγορη ανάπτυξη εφαρμογών, ή [πρόσβαση SQL |SQL way] για άμεση εργασία με ερωτήματα. + +<div class="grid gap-3"> +<div> + + +[Πρόσβαση SQL |SQL way] +======================= +- Ασφαλή παραμετροποιημένα ερωτήματα +- Ακριβής έλεγχος της μορφής των ερωτημάτων SQL +- Όταν γράφετε σύνθετα ερωτήματα με προηγμένες λειτουργίες +- Βελτιστοποιείτε την απόδοση χρησιμοποιώντας συγκεκριμένες λειτουργίες SQL + +</div> + +<div> + + +[Explorer |Explorer] +==================== +- Αναπτύσσετε γρήγορα χωρίς να γράφετε SQL +- Διαισθητική εργασία με σχέσεις μεταξύ πινάκων +- Εκτιμάτε την αυτόματη βελτιστοποίηση ερωτημάτων +- Κατάλληλο για γρήγορη και άνετη εργασία με τη βάση δεδομένων + +</div> + +</div> + + +Εγκατάσταση +=========== + +Κατεβάστε και εγκαταστήστε τη βιβλιοθήκη χρησιμοποιώντας το εργαλείο [Composer|best-practices:composer]: + +```shell +composer require nette/database +``` + + +Υποστηριζόμενες Βάσεις Δεδομένων +================================ + +Το Nette Database υποστηρίζει τις ακόλουθες βάσεις δεδομένων: + +|* Διακομιστής Βάσης Δεδομένων |* Όνομα DSN |* Υποστήριξη στον Explorer +|-----------------------------|-------------|-------------------------- +| MySQL (>= 5.1) | mysql | ΝΑΙ +| PostgreSQL (>= 9.0) | pgsql | ΝΑΙ +| Sqlite 3 (>= 3.8) | sqlite | ΝΑΙ +| Oracle | oci | - +| MS SQL (PDO_SQLSRV) | sqlsrv | ΝΑΙ +| MS SQL (PDO_DBLIB) | mssql | - +| ODBC | odbc | - + + +Δύο Προσεγγίσεις στη Βάση Δεδομένων +=================================== + +Το Nette Database σας δίνει μια επιλογή: μπορείτε είτε να γράψετε απευθείας ερωτήματα SQL (πρόσβαση SQL), είτε να τα αφήσετε να δημιουργηθούν αυτόματα (Explorer). Ας δούμε πώς και οι δύο προσεγγίσεις επιλύουν τις ίδιες εργασίες: + +[Πρόσβαση SQL|sql way] - Ερωτήματα SQL + +```php +// εισαγωγή εγγραφής +$database->query('INSERT INTO books', [ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// λήψη εγγραφών: συγγραφείς βιβλίων +$result = $database->query(' + SELECT authors.*, COUNT(books.id) AS books_count + FROM authors + LEFT JOIN books ON authors.id = books.author_id + WHERE authors.active = 1 + GROUP BY authors.id +'); + +// έξοδος (δεν είναι βέλτιστη, δημιουργεί N+1 ερωτήματα) +foreach ($result as $author) { + $books = $database->query(' + SELECT * FROM books + WHERE author_id = ? + ORDER BY published_at DESC + ', $author->id); + + echo "Ο συγγραφέας $author->name έγραψε $author->books_count βιβλία:\n"; // Author $author->name wrote $author->books_count books:\n + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +[Πρόσβαση Explorer|explorer] - Αυτόματη δημιουργία SQL + +```php +// εισαγωγή εγγραφής +$database->table('books')->insert([ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// λήψη εγγραφών: συγγραφείς βιβλίων +$authors = $database->table('authors') + ->where('active', 1); + +// έξοδος (δημιουργεί αυτόματα μόνο 2 βελτιστοποιημένα ερωτήματα) +foreach ($authors as $author) { + $books = $author->related('books') + ->order('published_at DESC'); + + echo "Ο συγγραφέας $author->name έγραψε {$books->count()} βιβλία:\n"; // Author $author->name wrote {$books->count()} books:\n + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +Η προσέγγιση Explorer δημιουργεί και βελτιστοποιεί αυτόματα τα ερωτήματα SQL. Στο παραπάνω παράδειγμα, η πρόσβαση SQL δημιουργεί N+1 ερωτήματα (ένα για τους συγγραφείς και στη συνέχεια ένα για τα βιβλία κάθε συγγραφέα), ενώ ο Explorer βελτιστοποιεί αυτόματα τα ερωτήματα και εκτελεί μόνο δύο - ένα για τους συγγραφείς και ένα για όλα τα βιβλία τους. + +Και οι δύο προσεγγίσεις μπορούν να συνδυαστούν ελεύθερα στην εφαρμογή ανάλογα με τις ανάγκες. + + +Σύνδεση και Διαμόρφωση +====================== + +Για να συνδεθείτε στη βάση δεδομένων, απλώς δημιουργήστε μια παρουσία της κλάσης [api:Nette\Database\Connection]: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password); +``` + +Η παράμετρος `$dsn` (data source name) είναι η ίδια [που χρησιμοποιεί το PDO |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], π.χ. `mysql:host=127.0.0.1;dbname=test`. Σε περίπτωση αποτυχίας, θα προκαλέσει μια εξαίρεση `Nette\Database\ConnectionException`. + +Ωστόσο, ένας πιο βολικός τρόπος προσφέρεται από τη [διαμόρφωση εφαρμογής |configuration], όπου απλά προσθέτετε την ενότητα `database` και δημιουργούνται τα απαραίτητα αντικείμενα καθώς και ο πίνακας της βάσης δεδομένων στη γραμμή [Tracy |tracy:]. + +```neon +database: + dsn: 'mysql:host=127.0.0.1;dbname=test' + user: root + password: password +``` + +Στη συνέχεια, [λαμβάνουμε το αντικείμενο σύνδεσης ως υπηρεσία από το DI container |dependency-injection:passing-dependencies], π.χ.: + +```php +class Model +{ + public function __construct( + // ή Nette\Database\Explorer + private Nette\Database\Connection $database, + ) { + } +} +``` + +Περισσότερες πληροφορίες σχετικά με τη [διαμόρφωση της βάσης δεδομένων|configuration]. + + +Χειροκίνητη Δημιουργία του Explorer +----------------------------------- + +Εάν δεν χρησιμοποιείτε το Nette DI container, μπορείτε να δημιουργήσετε χειροκίνητα την παρουσία `Nette\Database\Explorer`: + +```php +// σύνδεση στη βάση δεδομένων +$connection = new Nette\Database\Connection('mysql:host=127.0.0.1;dbname=mydatabase', 'user', 'password'); +// αποθήκη για την cache, υλοποιεί το Nette\Caching\Storage, π.χ.: +$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp/dir'); +// φροντίζει για την αντανάκλαση της δομής της βάσης δεδομένων +$structure = new Nette\Database\Structure($connection, $storage); +// ορίζει κανόνες για την αντιστοίχιση ονομάτων πινάκων, στηλών και ξένων κλειδιών +$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); +$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +``` + + +Διαχείριση Σύνδεσης +=================== + +Κατά τη δημιουργία του αντικειμένου `Connection`, η σύνδεση πραγματοποιείται αυτόματα. Εάν θέλετε να καθυστερήσετε τη σύνδεση, χρησιμοποιήστε τη λειτουργία lazy - την ενεργοποιείτε στη [διαμόρφωση|configuration] ορίζοντας το `lazy: true`, ή ως εξής: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password, ['lazy' => true]); +``` + +Για τη διαχείριση της σύνδεσης, χρησιμοποιήστε τις μεθόδους `connect()`, `disconnect()` και `reconnect()`. +- `connect()`: δημιουργεί τη σύνδεση, εάν δεν υπάρχει ήδη. Μπορεί να προκαλέσει εξαίρεση `Nette\Database\ConnectionException`. +- `disconnect()`: αποσυνδέει την τρέχουσα σύνδεση με τη βάση δεδομένων. +- `reconnect()`: πραγματοποιεί αποσύνδεση και στη συνέχεια επανασύνδεση με τη βάση δεδομένων. Αυτή η μέθοδος μπορεί επίσης να προκαλέσει εξαίρεση `Nette\Database\ConnectionException`. + +Επιπλέον, μπορείτε να παρακολουθείτε τα συμβάντα που σχετίζονται με τη σύνδεση χρησιμοποιώντας το συμβάν `onConnect`, το οποίο είναι ένας πίνακας callbacks που καλούνται μετά την εγκατάσταση της σύνδεσης με τη βάση δεδομένων. + +```php +// εκτελείται μετά τη σύνδεση στη βάση δεδομένων +$database->onConnect[] = function($database) { + echo "Συνδεθήκατε στη βάση δεδομένων"; // Connected to the database +}; +``` + + +Tracy Debug Bar +=============== + +Εάν χρησιμοποιείτε το [Tracy |tracy:], ενεργοποιείται αυτόματα ο πίνακας Database στη γραμμή Debug, ο οποίος εμφανίζει όλα τα εκτελεσμένα ερωτήματα, τις παραμέτρους τους, τον χρόνο εκτέλεσης και το σημείο στον κώδικα όπου κλήθηκαν. + +[* db-panel.webp *] diff --git a/database/el/mapping.texy b/database/el/mapping.texy new file mode 100644 index 0000000000..0ab89d43e2 --- /dev/null +++ b/database/el/mapping.texy @@ -0,0 +1,55 @@ +Μετατροπή Τύπων +*************** + +.[perex] +Το Nette Database μετατρέπει αυτόματα τις τιμές που επιστρέφονται από τη βάση δεδομένων στους αντίστοιχους τύπους PHP. + + +Ημερομηνία και Ώρα +------------------ + +Οι χρονικές τιμές μετατρέπονται σε αντικείμενα `Nette\Utils\DateTime`. Εάν θέλετε οι χρονικές τιμές να μετατρέπονται σε αμετάβλητα (immutable) αντικείμενα `DateTimeImmutable`, ορίστε την επιλογή `newDateTime: true` στη [διαμόρφωση|configuration]. + +```php +$row = $database->fetch('SELECT created_at FROM articles'); +echo $row->created_at instanceof DateTime; // true +echo $row->created_at->format('j. n. Y'); +``` + +Στην περίπτωση της MySQL, ο τύπος δεδομένων `TIME` μετατρέπεται σε αντικείμενα `DateInterval`. + + +Boolean Τιμές +------------- + +Οι boolean τιμές μετατρέπονται αυτόματα σε `true` ή `false`. Στην MySQL, μετατρέπεται ο τύπος `TINYINT(1)` εάν ορίσουμε `convertBoolean: true` στη [διαμόρφωση|configuration]. + +```php +$row = $database->fetch('SELECT is_published FROM articles'); +echo gettype($row->is_published); // 'boolean' +``` + + +Αριθμητικές Τιμές +----------------- + +Οι αριθμητικές τιμές μετατρέπονται σε `int` ή `float` ανάλογα με τον τύπο της στήλης στη βάση δεδομένων: + +```php +$row = $database->fetch('SELECT id, price FROM products'); +echo gettype($row->id); // integer +echo gettype($row->price); // float +``` + + +Προσαρμοσμένη Κανονικοποίηση +---------------------------- + +Χρησιμοποιώντας τη μέθοδο `setRowNormalizer(?callable $normalizer)`, μπορείτε να ορίσετε μια προσαρμοσμένη συνάρτηση για τη μετατροπή των γραμμών από τη βάση δεδομένων. Αυτό είναι χρήσιμο, για παράδειγμα, για την αυτόματη μετατροπή τύπων δεδομένων. + +```php +$database->setRowNormalizer(function(array $row, ResultSet $resultSet): array { + // εδώ γίνεται η μετατροπή τύπων + return $row; +}); +``` diff --git a/database/el/reflection.texy b/database/el/reflection.texy new file mode 100644 index 0000000000..909276b244 --- /dev/null +++ b/database/el/reflection.texy @@ -0,0 +1,125 @@ +Αντανάκλαση Δομής +***************** + +.{data-version:3.2.1} +Το Nette Database παρέχει εργαλεία για την ενδοσκόπηση (introspection) της δομής της βάσης δεδομένων χρησιμοποιώντας την κλάση [api:Nette\Database\Structure]. Αυτή επιτρέπει τη λήψη πληροφοριών σχετικά με πίνακες, στήλες, ευρετήρια (indexes) και ξένα κλειδιά (foreign keys). Μπορείτε να χρησιμοποιήσετε την αντανάκλαση (reflection) για τη δημιουργία σχημάτων (schemas), τη δημιουργία ευέλικτων εφαρμογών που λειτουργούν με τη βάση δεδομένων ή γενικών εργαλείων βάσης δεδομένων. + +Λαμβάνουμε το αντικείμενο αντανάκλασης από την παρουσία της σύνδεσης με τη βάση δεδομένων: + +```php +$reflection = $database->getReflection(); +``` + + +Λήψη Πινάκων +------------ + +Η ιδιότητα readonly `$reflection->tables` περιέχει έναν συσχετιστικό πίνακα όλων των πινάκων στη βάση δεδομένων: + +```php +// Εμφάνιση ονομάτων όλων των πινάκων +foreach ($reflection->tables as $name => $table) { + echo $name . "\n"; +} +``` + +Υπάρχουν δύο ακόμη διαθέσιμες μέθοδοι: + +```php +// Έλεγχος ύπαρξης πίνακα +if ($reflection->hasTable('users')) { + echo "Ο πίνακας users υπάρχει"; // Table users exists +} + +// Επιστρέφει το αντικείμενο του πίνακα. αν δεν υπάρχει, προκαλεί εξαίρεση +$table = $reflection->getTable('users'); +``` + + +Πληροφορίες για τον Πίνακα +-------------------------- + +Ο πίνακας αντιπροσωπεύεται από το αντικείμενο [Table|api:Nette\Database\Reflection\Table], το οποίο παρέχει τις ακόλουθες ιδιότητες readonly: + +- `$name: string` – όνομα του πίνακα +- `$view: bool` – εάν πρόκειται για προβολή (view) +- `$fullName: ?string` – πλήρες όνομα του πίνακα συμπεριλαμβανομένου του σχήματος (εάν υπάρχει) +- `$columns: array<string, Column>` – συσχετιστικός πίνακας στηλών του πίνακα +- `$indexes: Index[]` – πίνακας ευρετηρίων του πίνακα +- `$primaryKey: ?Index` – πρωτεύον κλειδί του πίνακα ή null +- `$foreignKeys: ForeignKey[]` – πίνακας ξένων κλειδιών του πίνακα + + +Στήλες +------ + +Η ιδιότητα `columns` του πίνακα παρέχει έναν συσχετιστικό πίνακα στηλών, όπου το κλειδί είναι το όνομα της στήλης και η τιμή είναι μια παρουσία [Column|api:Nette\Database\Reflection\Column] με τις ακόλουθες ιδιότητες: + +- `$name: string` – όνομα της στήλης +- `$table: ?Table` – αναφορά στον πίνακα της στήλης +- `$nativeType: string` – εγγενής τύπος δεδομένων της βάσης δεδομένων +- `$size: ?int` – μέγεθος/μήκος του τύπου +- `$nullable: bool` – εάν η στήλη μπορεί να περιέχει NULL +- `$default: mixed` – προεπιλεγμένη τιμή της στήλης +- `$autoIncrement: bool` – εάν η στήλη είναι auto-increment +- `$primary: bool` – εάν αποτελεί μέρος του πρωτεύοντος κλειδιού +- `$vendor: array` – πρόσθετα μεταδεδομένα ειδικά για το συγκεκριμένο σύστημα βάσης δεδομένων + +```php +foreach ($table->columns as $name => $column) { + echo "Στήλη: $name\n"; // Column: + echo "Τύπος: {$column->nativeType}\n"; // Type: + echo "Nullable: " . ($column->nullable ? 'Ναι' : 'Όχι') . "\n"; // Nullable: Yes / No +} +``` + + +Ευρετήρια +--------- + +Η ιδιότητα `indexes` του πίνακα παρέχει έναν πίνακα ευρετηρίων, όπου κάθε ευρετήριο είναι μια παρουσία [Index|api:Nette\Database\Reflection\Index] με τις ακόλουθες ιδιότητες: + +- `$columns: Column[]` – πίνακας στηλών που αποτελούν το ευρετήριο +- `$unique: bool` – εάν το ευρετήριο είναι μοναδικό +- `$primary: bool` – εάν πρόκειται για πρωτεύον κλειδί +- `$name: ?string` – όνομα του ευρετηρίου + +Το πρωτεύον κλειδί του πίνακα μπορεί να ληφθεί χρησιμοποιώντας την ιδιότητα `primaryKey`, η οποία επιστρέφει είτε ένα αντικείμενο `Index`, είτε `null` στην περίπτωση που ο πίνακας δεν έχει πρωτεύον κλειδί. + +```php +// Εμφάνιση ευρετηρίων +foreach ($table->indexes as $index) { + $columns = implode(', ', array_map(fn($col) => $col->name, $index->columns)); + echo "Ευρετήριο" . ($index->name ? " {$index->name}" : '') . ":\n"; // Index + echo " Στήλες: $columns\n"; // Columns: + echo " Μοναδικό: " . ($index->unique ? 'Ναι' : 'Όχι') . "\n"; // Unique: Yes / No +} + +// Εμφάνιση πρωτεύοντος κλειδιού +if ($primaryKey = $table->primaryKey) { + $columns = implode(', ', array_map(fn($col) => $col->name, $primaryKey->columns)); + echo "Πρωτεύον κλειδί: $columns\n"; // Primary key: +} +``` + + +Ξένα κλειδιά +------------ + +Η ιδιότητα `foreignKeys` του πίνακα παρέχει έναν πίνακα ξένων κλειδιών, όπου κάθε ξένο κλειδί είναι μια παρουσία [ForeignKey|api:Nette\Database\Reflection\ForeignKey] με τις ακόλουθες ιδιότητες: + +- `$foreignTable: Table` – ο πίνακας στον οποίο γίνεται αναφορά +- `$localColumns: Column[]` – πίνακας τοπικών στηλών +- `$foreignColumns: Column[]` – πίνακας στηλών στις οποίες γίνεται αναφορά +- `$name: ?string` – όνομα του ξένου κλειδιού + +```php +// Εμφάνιση ξένων κλειδιών +foreach ($table->foreignKeys as $fk) { + $localCols = implode(', ', array_map(fn($col) => $col->name, $fk->localColumns)); + $foreignCols = implode(', ', array_map(fn($col) => $col->name, $fk->foreignColumns)); + + echo "FK" . ($fk->name ? " {$fk->name}" : '') . ":\n"; + echo " $localCols -> {$fk->foreignTable->name}($foreignCols)\n"; +} +``` diff --git a/database/el/security.texy b/database/el/security.texy new file mode 100644 index 0000000000..8fda175c50 --- /dev/null +++ b/database/el/security.texy @@ -0,0 +1,185 @@ +Κίνδυνοι Ασφαλείας +****************** + +<div class=perex> + +Η βάση δεδομένων συχνά περιέχει ευαίσθητα δεδομένα και επιτρέπει την εκτέλεση επικίνδυνων λειτουργιών. Για την ασφαλή εργασία με το Nette Database είναι κρίσιμο: + +- Να κατανοήσετε τη διαφορά μεταξύ ασφαλούς και μη ασφαλούς API +- Να χρησιμοποιείτε παραμετροποιημένα ερωτήματα +- Να επικυρώνετε σωστά τα δεδομένα εισόδου + +</div> + + +Τι είναι το SQL Injection; +========================== + +Το SQL injection είναι ο σοβαρότερος κίνδυνος ασφαλείας κατά την εργασία με τη βάση δεδομένων. Προκύπτει όταν η μη επεξεργασμένη είσοδος από τον χρήστη γίνεται μέρος ενός ερωτήματος SQL. Ο εισβολέας μπορεί να εισάγει δικές του εντολές SQL και έτσι: +- Να αποκτήσει μη εξουσιοδοτημένη πρόσβαση σε δεδομένα +- Να τροποποιήσει ή να διαγράψει δεδομένα στη βάση δεδομένων +- Να παρακάμψει τον έλεγχο ταυτότητας + +```php +// ❌ ΕΠΙΚΙΝΔΥΝΟΣ ΚΩΔΙΚΑΣ - ευάλωτος σε SQL injection +$database->query("SELECT * FROM users WHERE name = '$_GET[name]'"); + +// Ο εισβολέας μπορεί να εισάγει για παράδειγμα την τιμή: ' OR '1'='1 +// Το τελικό ερώτημα θα είναι: SELECT * FROM users WHERE name = '' OR '1'='1' +// Το οποίο επιστρέφει όλους τους χρήστες +``` + +Το ίδιο ισχύει και για το Database Explorer: + +```php +// ❌ ΕΠΙΚΙΝΔΥΝΟΣ ΚΩΔΙΚΑΣ - ευάλωτος σε SQL injection +$table->where('name = ' . $_GET['name']); +$table->where("name = '$_GET[name]'"); +``` + + +Παραμετροποιημένα Ερωτήματα +=========================== + +Η βασική άμυνα κατά του SQL injection είναι τα παραμετροποιημένα ερωτήματα. Το Nette Database προσφέρει διάφορους τρόπους χρήσης τους. + +Ο απλούστερος τρόπος είναι η χρήση **placeholders ερωτηματικών (?)**: + +```php +// ✅ Ασφαλές παραμετροποιημένο ερώτημα +$database->query('SELECT * FROM users WHERE name = ?', $name); + +// ✅ Ασφαλής συνθήκη στο Explorer +$table->where('name = ?', $name); +``` + +Αυτό ισχύει για όλες τις άλλες μεθόδους στο [Database Explorer|explorer], που επιτρέπουν την εισαγωγή εκφράσεων με placeholders ερωτηματικά και παραμέτρους. + +Για εντολές INSERT, UPDATE ή τη ρήτρα WHERE, μπορούμε να περάσουμε τις τιμές σε έναν πίνακα: + +```php +// ✅ Ασφαλές INSERT +$database->query('INSERT INTO users', [ + 'name' => $name, + 'email' => $email, +]); + +// ✅ Ασφαλές INSERT στο Explorer +$table->insert([ + 'name' => $name, + 'email' => $email, +]); +``` + + +Επικύρωση Τιμών Παραμέτρων +========================== + +Τα παραμετροποιημένα ερωτήματα είναι ο θεμελιώδης λίθος της ασφαλούς εργασίας με τη βάση δεδομένων. Ωστόσο, οι τιμές που εισάγουμε σε αυτά πρέπει να περάσουν από διάφορα επίπεδα ελέγχου: + + +Έλεγχος Τύπου +------------- + +**Το πιο σημαντικό είναι να διασφαλιστεί ο σωστός τύπος δεδομένων των παραμέτρων** - αυτό είναι απαραίτητη προϋπόθεση για την ασφαλή χρήση του Nette Database. Η βάση δεδομένων υποθέτει ότι όλα τα δεδομένα εισόδου έχουν τον σωστό τύπο δεδομένων που αντιστοιχεί στη συγκεκριμένη στήλη. + +Για παράδειγμα, εάν το `$name` στα προηγούμενα παραδείγματα ήταν απροσδόκητα ένας πίνακας αντί για μια συμβολοσειρά, το Nette Database θα προσπαθούσε να εισάγει όλα τα στοιχεία του στο ερώτημα SQL, οδηγώντας σε σφάλμα. Επομένως, **ποτέ μην χρησιμοποιείτε** μη επικυρωμένα δεδομένα από `$_GET`, `$_POST` ή `$_COOKIE` απευθείας σε ερωτήματα βάσης δεδομένων. + + +Έλεγχος Μορφής +-------------- + +Στο δεύτερο επίπεδο, ελέγχουμε τη μορφή των δεδομένων - για παράδειγμα, εάν οι συμβολοσειρές είναι σε κωδικοποίηση UTF-8 και το μήκος τους αντιστοιχεί στον ορισμό της στήλης, ή εάν οι αριθμητικές τιμές βρίσκονται εντός του επιτρεπόμενου εύρους για τον συγκεκριμένο τύπο δεδομένων της στήλης. + +Σε αυτό το επίπεδο επικύρωσης, μπορούμε εν μέρει να βασιστούμε και στην ίδια τη βάση δεδομένων - πολλές βάσεις δεδομένων απορρίπτουν μη έγκυρα δεδομένα. Ωστόσο, η συμπεριφορά μπορεί να διαφέρει, κάποιες μπορεί να περικόψουν σιωπηλά μακριές συμβολοσειρές ή να κόψουν αριθμούς εκτός εύρους. + + +Έλεγχος τομέα +------------- + +Το τρίτο επίπεδο αντιπροσωπεύουν οι λογικοί έλεγχοι που είναι ειδικοί για την εφαρμογή σας. Για παράδειγμα, η επαλήθευση ότι οι τιμές από τα select boxes αντιστοιχούν στις προσφερόμενες επιλογές, ότι οι αριθμοί βρίσκονται στο αναμενόμενο εύρος (π.χ. ηλικία 0-150 ετών) ή ότι οι αμοιβαίες εξαρτήσεις μεταξύ των τιμών έχουν νόημα. + + +Συνιστώμενοι Τρόποι Επικύρωσης +------------------------------ + +- Χρησιμοποιήστε [Nette Forms|forms:], που εξασφαλίζουν αυτόματα τη σωστή επικύρωση όλων των εισόδων +- Χρησιμοποιήστε [Presenters|application:] και δηλώστε τους τύπους δεδομένων για τις παραμέτρους στις μεθόδους `action*()` και `render*()` +- Ή υλοποιήστε το δικό σας επίπεδο επικύρωσης χρησιμοποιώντας τυπικά εργαλεία PHP όπως το `filter_var()` + + +Ασφαλής Εργασία με Στήλες +========================= + +Στην προηγούμενη ενότητα, δείξαμε πώς να επικυρώνουμε σωστά τις τιμές των παραμέτρων. Ωστόσο, κατά τη χρήση πινάκων σε ερωτήματα SQL, πρέπει να δώσουμε την ίδια προσοχή και στα κλειδιά τους. + +```php +// ❌ ΕΠΙΚΙΝΔΥΝΟΣ ΚΩΔΙΚΑΣ - τα κλειδιά στον πίνακα δεν έχουν υποστεί επεξεργασία +$database->query('INSERT INTO users', $_POST); +``` + +Στις εντολές INSERT και UPDATE, αυτό αποτελεί κρίσιμο σφάλμα ασφαλείας - ο εισβολέας μπορεί να εισάγει ή να αλλάξει οποιαδήποτε στήλη στη βάση δεδομένων. Θα μπορούσε, για παράδειγμα, να ορίσει `is_admin = 1` ή να εισάγει αυθαίρετα δεδομένα σε ευαίσθητες στήλες (η λεγόμενη Mass Assignment Vulnerability). + +Στις συνθήκες WHERE, είναι ακόμη πιο επικίνδυνο, επειδή μπορεί να περιέχουν τελεστές: + +```php +// ❌ ΕΠΙΚΙΝΔΥΝΟΣ ΚΩΔΙΚΑΣ - τα κλειδιά στον πίνακα δεν έχουν υποστεί επεξεργασία +$_POST['salary >'] = 100000; +$database->query('SELECT * FROM users WHERE', $_POST); +// εκτελεί το ερώτημα WHERE (`salary` > 100000) +``` + +Ο εισβολέας μπορεί να χρησιμοποιήσει αυτή την προσέγγιση για να ανακαλύψει συστηματικά τους μισθούς των υπαλλήλων. Μπορεί να ξεκινήσει, για παράδειγμα, με ένα ερώτημα για μισθούς πάνω από 100.000, στη συνέχεια κάτω από 50.000, και με σταδιακή στένωση του εύρους, μπορεί να αποκαλύψει τους κατά προσέγγιση μισθούς όλων των υπαλλήλων. Αυτός ο τύπος επίθεσης ονομάζεται SQL enumeration. + +Οι μέθοδοι `where()` και `whereOr()` είναι ακόμη [πολύ πιο ευέλικτες |explorer#where] και υποστηρίζουν εκφράσεις SQL στα κλειδιά και τις τιμές, συμπεριλαμβανομένων τελεστών και συναρτήσεων. Αυτό δίνει στον εισβολέα τη δυνατότητα να εκτελέσει SQL injection: + +```php +// ❌ ΕΠΙΚΙΝΔΥΝΟΣ ΚΩΔΙΚΑΣ - ο εισβολέας μπορεί να εισάγει δικό του SQL +$_POST = ['0) UNION SELECT name, salary FROM users WHERE (1']; +$table->where($_POST); +// εκτελεί το ερώτημα WHERE (0) UNION SELECT name, salary FROM users WHERE (1) +``` + +Αυτή η επίθεση τερματίζει την αρχική συνθήκη χρησιμοποιώντας `0)`, προσαρτά το δικό της `SELECT` χρησιμοποιώντας `UNION` για να αποκτήσει ευαίσθητα δεδομένα από τον πίνακα `users` και κλείνει το συντακτικά σωστό ερώτημα χρησιμοποιώντας `WHERE (1)`. + + +Whitelist Στηλών +---------------- + +Για την ασφαλή εργασία με ονόματα στηλών, χρειαζόμαστε έναν μηχανισμό που να διασφαλίζει ότι ο χρήστης μπορεί να εργαστεί μόνο με τις επιτρεπόμενες στήλες και δεν μπορεί να προσθέσει δικές του. Θα μπορούσαμε να προσπαθήσουμε να ανιχνεύσουμε και να μπλοκάρουμε επικίνδυνα ονόματα στηλών (blacklist), αλλά αυτή η προσέγγιση είναι αναξιόπιστη - ο εισβολέας μπορεί πάντα να βρει έναν νέο τρόπο να γράψει ένα επικίνδυνο όνομα στήλης που δεν είχαμε προβλέψει. + +Επομένως, είναι πολύ πιο ασφαλές να αντιστρέψουμε τη λογική και να ορίσουμε μια ρητή λίστα επιτρεπόμενων στηλών (whitelist): + +```php +// Στήλες που μπορεί να επεξεργαστεί ο χρήστης +$allowedColumns = ['name', 'email', 'active']; + +// Φιλτράρουμε τα δεδομένα εισόδου για να κρατήσουμε μόνο τα επιτρεπόμενα κλειδιά +$filteredData = array_intersect_key($userData, array_flip($allowedColumns)); + +// ✅ Τώρα μπορούμε να τα χρησιμοποιήσουμε με ασφάλεια σε ερωτήματα, όπως: +$database->query('INSERT INTO users', $filteredData); +$table->update($filteredData); +$table->where($filteredData); +``` + + +Δυναμικά Αναγνωριστικά +====================== + +Για δυναμικά ονόματα πινάκων και στηλών, χρησιμοποιήστε το placeholder `?name`. Αυτό εξασφαλίζει τη σωστή διαφυγή (escaping) των αναγνωριστικών σύμφωνα με τη σύνταξη της συγκεκριμένης βάσης δεδομένων (π.χ. χρησιμοποιώντας ανάποδα εισαγωγικά `` ` `` στην MySQL): + +```php +// ✅ Ασφαλής χρήση αξιόπιστων αναγνωριστικών +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name', $column, $table); +// Αποτέλεσμα στην MySQL: SELECT `name` FROM `users` +``` + +Σημαντικό: χρησιμοποιήστε το σύμβολο `?name` μόνο για αξιόπιστες τιμές που ορίζονται στον κώδικα της εφαρμογής. Για τιμές από τον χρήστη, χρησιμοποιήστε ξανά τη [whitelist |#Whitelist Στηλών]. Διαφορετικά, εκτίθεστε σε κινδύνους ασφαλείας: + +```php +// ❌ ΕΠΙΚΙΝΔΥΝΟ - ποτέ μην χρησιμοποιείτε είσοδο από τον χρήστη +$database->query('SELECT ?name FROM users', $_GET['column']); +``` diff --git a/database/el/sql-way.texy b/database/el/sql-way.texy new file mode 100644 index 0000000000..1cfab7b050 --- /dev/null +++ b/database/el/sql-way.texy @@ -0,0 +1,513 @@ +Πρόσβαση SQL +************ + +.[perex] +Η Nette Database προσφέρει δύο τρόπους: μπορείτε να γράψετε μόνοι σας ερωτήματα SQL (πρόσβαση SQL) ή να τα αφήσετε να δημιουργηθούν αυτόματα (βλ. [Explorer |explorer]). Η πρόσβαση SQL σάς δίνει πλήρη έλεγχο των ερωτημάτων, εξασφαλίζοντας ταυτόχρονα την ασφαλή σύνταξή τους. + +.[note] +Λεπτομέρειες σχετικά με τη σύνδεση και τη διαμόρφωση της βάσης δεδομένων θα βρείτε στο κεφάλαιο [Σύνδεση και διαμόρφωση |guide#Σύνδεση και Διαμόρφωση]. + + +Βασικά ερωτήματα +================ + +Για την υποβολή ερωτημάτων στη βάση δεδομένων, χρησιμοποιείται η μέθοδος `query()`. Αυτή επιστρέφει ένα αντικείμενο [ResultSet |api:Nette\Database\ResultSet], το οποίο αντιπροσωπεύει το αποτέλεσμα του ερωτήματος. Σε περίπτωση αποτυχίας, η μέθοδος [προκαλεί εξαίρεση |exceptions]. Μπορούμε να διατρέξουμε το αποτέλεσμα του ερωτήματος χρησιμοποιώντας έναν βρόχο `foreach` ή να χρησιμοποιήσουμε κάποια από τις [βοηθητικές συναρτήσεις |#Λήψη δεδομένων]. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; +} +``` + +Για την ασφαλή εισαγωγή τιμών σε ερωτήματα SQL, χρησιμοποιούμε παραμετροποιημένα ερωτήματα. Η Nette Database τα καθιστά εξαιρετικά απλά - αρκεί να προσθέσετε ένα κόμμα και την τιμή μετά το ερώτημα SQL: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Με περισσότερες παραμέτρους, έχετε δύο επιλογές σύνταξης. Μπορείτε είτε να "διανθίσετε" το ερώτημα SQL με παραμέτρους: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age); +``` + +Ή να γράψετε πρώτα ολόκληρο το ερώτημα SQL και στη συνέχεια να επισυνάψετε όλες τις παραμέτρους: + +```php +$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age); +``` + + +Προστασία από SQL injection +=========================== + +Γιατί είναι σημαντικό να χρησιμοποιείτε παραμετροποιημένα ερωτήματα; Επειδή σας προστατεύουν από την επίθεση που ονομάζεται SQL injection, κατά την οποία ο εισβολέας θα μπορούσε να εισάγει δικές του εντολές SQL και έτσι να αποκτήσει ή να καταστρέψει δεδομένα στη βάση δεδομένων. + +.[warning] +**Ποτέ μην εισάγετε μεταβλητές απευθείας στο ερώτημα SQL!** Πάντα να χρησιμοποιείτε παραμετροποιημένα ερωτήματα, τα οποία σας προστατεύουν από το SQL injection. + +```php +// ❌ ΕΠΙΚΙΝΔΥΝΟΣ ΚΩΔΙΚΑΣ - ευάλωτος σε SQL injection +$database->query("SELECT * FROM users WHERE name = '$name'"); + +// ✅ Ασφαλές παραμετροποιημένο ερώτημα +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Ενημερωθείτε για τους [πιθανούς κινδύνους ασφαλείας |security]. + + +Τεχνικές ερωτημάτων +=================== + + +Συνθήκες WHERE +-------------- + +Μπορείτε να γράψετε τις συνθήκες WHERE ως έναν συσχετιστικό πίνακα (associative array), όπου τα κλειδιά είναι τα ονόματα των στηλών και οι τιμές είναι τα δεδομένα για σύγκριση. Η Nette Database επιλέγει αυτόματα τον καταλληλότερο τελεστή SQL ανάλογα με τον τύπο της τιμής. + +```php +$database->query('SELECT * FROM users WHERE', [ + 'name' => 'John', + 'active' => true, +]); +// WHERE `name` = 'John' AND `active` = 1 +``` + +Στο κλειδί, μπορείτε επίσης να καθορίσετε ρητά τον τελεστή για σύγκριση: + +```php +$database->query('SELECT * FROM users WHERE', [ + 'age >' => 25, // χρησιμοποιεί τον τελεστή > + 'name LIKE' => '%John%', // χρησιμοποιεί τον τελεστή LIKE + 'email NOT LIKE' => '%example.com%', // χρησιμοποιεί τον τελεστή NOT LIKE +]); +// WHERE `age` > 25 AND `name` LIKE '%John%' AND `email` NOT LIKE '%example.com%' +``` + +Το Nette χειρίζεται αυτόματα ειδικές περιπτώσεις όπως τιμές `null` ή πίνακες. + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name' => 'Laptop', // χρησιμοποιεί τον τελεστή = + 'category_id' => [1, 2, 3], // χρησιμοποιεί το IN + 'description' => null, // χρησιμοποιεί το IS NULL +]); +// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL +``` + +Για αρνητικές συνθήκες, χρησιμοποιήστε τον τελεστή `NOT`: + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name NOT' => 'Laptop', // χρησιμοποιεί τον τελεστή <> + 'category_id NOT' => [1, 2, 3], // χρησιμοποιεί το NOT IN + 'description NOT' => null, // χρησιμοποιεί το IS NOT NULL + 'id' => [], // παραλείπεται +]); +// WHERE `name` <> 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL +``` + +Για τη σύνδεση συνθηκών, χρησιμοποιείται ο τελεστής `AND`. Αυτό μπορεί να αλλάξει χρησιμοποιώντας το [placeholder ?or |#Hints για τη σύνταξη SQL]. + + +Κανόνες ORDER BY +---------------- + +Η ταξινόμηση `ORDER BY` μπορεί να γραφτεί χρησιμοποιώντας έναν πίνακα. Στα κλειδιά, αναφέρουμε τις στήλες και η τιμή θα είναι μια boolean τιμή που καθορίζει εάν θα ταξινομηθεί αύξουσα: + +```php +$database->query('SELECT id FROM author ORDER BY', [ + 'id' => true, // αύξουσα + 'name' => false, // φθίνουσα +]); +// SELECT id FROM author ORDER BY `id`, `name` DESC +``` + + +Εισαγωγή δεδομένων (INSERT) +--------------------------- + +Για την εισαγωγή εγγραφών, χρησιμοποιείται η εντολή SQL `INSERT`. + +```php +$values = [ + 'name' => 'John Doe', + 'email' => 'john@example.com', +]; +$database->query('INSERT INTO users ?', $values); +$userId = $database->getInsertId(); +``` + +Η μέθοδος `getInsertId()` επιστρέφει το ID της τελευταίας εισαχθείσας γραμμής. Σε ορισμένες βάσεις δεδομένων (π.χ. PostgreSQL), είναι απαραίτητο να καθορίσετε ως παράμετρο το όνομα της ακολουθίας (sequence) από την οποία θα δημιουργηθεί το ID χρησιμοποιώντας `$database->getInsertId($sequenceId)`. + +Ως παραμέτρους μπορούμε επίσης να περάσουμε [#Ειδικές τιμές] όπως αρχεία, αντικείμενα DateTime ή τύπους enum. + +Εισαγωγή πολλαπλών εγγραφών ταυτόχρονα: + +```php +$database->query('INSERT INTO users ?', [ + ['name' => 'User 1', 'email' => 'user1@mail.com'], + ['name' => 'User 2', 'email' => 'user2@mail.com'], +]); +``` + +Η πολλαπλή INSERT είναι πολύ ταχύτερη, επειδή εκτελείται ένα μόνο ερώτημα βάσης δεδομένων, αντί για πολλά μεμονωμένα. + +**Προειδοποίηση ασφαλείας:** Ποτέ μην χρησιμοποιείτε μη επικυρωμένα δεδομένα ως `$values`. Ενημερωθείτε για τους [πιθανούς κινδύνους |security#Ασφαλής Εργασία με Στήλες]. + + +Ενημέρωση δεδομένων (UPDATE) +---------------------------- + +Για την ενημέρωση εγγραφών, χρησιμοποιείται η εντολή SQL `UPDATE`. + +```php +// Ενημέρωση μίας εγγραφής +$values = [ + 'name' => 'John Smith', +]; +$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1); +``` + +Ο αριθμός των επηρεασμένων γραμμών επιστρέφεται από το `$result->getRowCount()`. + +Για το UPDATE, μπορούμε να χρησιμοποιήσουμε τους τελεστές `+=` και `-=`: + +```php +$database->query('UPDATE users SET ? WHERE id = ?', [ + 'login_count+=' => 1, // αύξηση του login_count +], 1); +``` + +Παράδειγμα εισαγωγής ή τροποποίησης εγγραφής, εάν υπάρχει ήδη. Χρησιμοποιούμε την τεχνική `ON DUPLICATE KEY UPDATE`: + +```php +$values = [ + 'name' => $name, + 'year' => $year, +]; +$database->query('INSERT INTO users ? ON DUPLICATE KEY UPDATE ?', + $values + ['id' => $id], + $values, +); +// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) +// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 +``` + +Παρατηρήστε ότι η Nette Database αναγνωρίζει σε ποιο πλαίσιο της εντολής SQL εισάγουμε την παράμετρο με τον πίνακα και ανάλογα συνθέτει τον κώδικα SQL. Έτσι, από τον πρώτο πίνακα συνέθεσε `(id, name, year) VALUES (123, 'Jim', 1978)`, ενώ τον δεύτερο τον μετέτρεψε στη μορφή `name = 'Jim', year = 1978`. Αυτό το εξετάζουμε λεπτομερέστερα στην ενότητα [#Hints για τη σύνταξη SQL]. + + +Διαγραφή δεδομένων (DELETE) +--------------------------- + +Για τη διαγραφή εγγραφών, χρησιμοποιείται η εντολή SQL `DELETE`. Παράδειγμα με λήψη του αριθμού των διαγραμμένων γραμμών: + +```php +$count = $database->query('DELETE FROM users WHERE id = ?', 1) + ->getRowCount(); +``` + + +Hints για τη σύνταξη SQL +------------------------ + +Ένα hint είναι ένα ειδικό placeholder στο ερώτημα SQL που λέει πώς πρέπει να μεταγραφεί η τιμή της παραμέτρου σε έκφραση SQL: + +| Hint | Περιγραφή | Χρησιμοποιείται αυτόματα +|-----------|-------------------------------------------------|----------------------------- +| `?name` | χρησιμοποιείται για την εισαγωγή ονόματος πίνακα ή στήλης | - +| `?values` | δημιουργεί `(key, ...) VALUES (value, ...)` | `INSERT ... ?`, `REPLACE ... ?` +| `?set` | δημιουργεί ανάθεση `key = value, ...` | `SET ?`, `KEY UPDATE ?` +| `?and` | συνδέει συνθήκες στον πίνακα με τον τελεστή `AND` | `WHERE ?`, `HAVING ?` +| `?or` | συνδέει συνθήκες στον πίνακα με τον τελεστή `OR` | - +| `?order` | δημιουργεί τη ρήτρα `ORDER BY` | `ORDER BY ?`, `GROUP BY ?` + +Για τη δυναμική εισαγωγή ονομάτων πινάκων και στηλών στο ερώτημα, χρησιμοποιείται το placeholder `?name`. Η Nette Database φροντίζει για τη σωστή επεξεργασία των αναγνωριστικών σύμφωνα με τις συμβάσεις της συγκεκριμένης βάσης δεδομένων (π.χ. κλείσιμο σε ανάποδα εισαγωγικά `` ` `` στην MySQL). + +```php +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table); +// SELECT `name` FROM `users` WHERE id = 1 (στην MySQL) +``` + +**Προειδοποίηση:** χρησιμοποιήστε το σύμβολο `?name` μόνο για ονόματα πινάκων και στηλών από επικυρωμένες εισόδους, διαφορετικά εκτίθεστε σε [κίνδυνο ασφαλείας |security#Δυναμικά Αναγνωριστικά]. + +Τα υπόλοιπα hints συνήθως δεν χρειάζεται να αναφέρονται, καθώς το Nette χρησιμοποιεί έξυπνη αυτόματη ανίχνευση κατά τη σύνθεση του ερωτήματος SQL (βλ. τρίτη στήλη του πίνακα). Αλλά μπορείτε να το χρησιμοποιήσετε, για παράδειγμα, σε μια κατάσταση όπου θέλετε να συνδέσετε συνθήκες χρησιμοποιώντας `OR` αντί για `AND`: + +```php +$database->query('SELECT * FROM users WHERE ?or', [ + 'name' => 'John', + 'email' => 'john@example.com', +]); +// SELECT * FROM users WHERE `name` = 'John' OR `email` = 'john@example.com' +``` + + +Ειδικές τιμές +------------- + +Εκτός από τους συνήθεις σκαλωτούς τύπους (string, int, bool), μπορείτε να περάσετε και ειδικές τιμές ως παραμέτρους: + +- αρχεία: `fopen('image.gif', 'r')` εισάγει το δυαδικό περιεχόμενο του αρχείου +- ημερομηνία και ώρα: τα αντικείμενα `DateTime` μετατρέπονται στη μορφή της βάσης δεδομένων +- τύποι enum: οι παρουσίες `enum` μετατρέπονται στην τιμή τους +- SQL literals: δημιουργημένα με `Connection::literal('NOW()')` εισάγονται απευθείας στο ερώτημα + +```php +$database->query('INSERT INTO articles ?', [ + 'title' => 'My Article', + 'published_at' => new DateTime, + 'content' => fopen('image.png', 'r'), + 'state' => Status::Draft, +]); +``` + +Σε βάσεις δεδομένων που δεν έχουν εγγενή υποστήριξη για τον τύπο δεδομένων `datetime` (όπως SQLite και Oracle), το `DateTime` μετατρέπεται στην τιμή που καθορίζεται στη [διαμόρφωση της βάσης δεδομένων |configuration] με την επιλογή `formatDateTime` (η προεπιλεγμένη τιμή είναι `U` - unix timestamp). + + +SQL Literals +------------ + +Σε ορισμένες περιπτώσεις, πρέπει να αναφέρετε απευθείας κώδικα SQL ως τιμή, ο οποίος όμως δεν πρέπει να θεωρηθεί ως συμβολοσειρά και να υποστεί escaping. Για αυτό χρησιμεύουν τα αντικείμενα της κλάσης `Nette\Database\SqlLiteral`. Τα δημιουργεί η μέθοδος `Connection::literal()`. + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + 'year >' => $database::literal('YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) +``` + +Ή εναλλακτικά: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) +``` + +Τα SQL literals μπορούν να περιέχουν παραμέτρους: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > ? AND year < ?', $min, $max), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) +``` + +Χάρη σε αυτό, μπορούμε να δημιουργήσουμε ενδιαφέροντες συνδυασμούς: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('?or', [ + 'active' => true, + 'role' => $role, + ]), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') +``` + + +Λήψη δεδομένων +============== + + +Συντομεύσεις για ερωτήματα SELECT +--------------------------------- + +Για την απλοποίηση της ανάκτησης δεδομένων, το `Connection` προσφέρει αρκετές συντομεύσεις που συνδυάζουν την κλήση `query()` με την ακόλουθη `fetch*()`. Αυτές οι μέθοδοι δέχονται τις ίδιες παραμέτρους με το `query()`, δηλαδή το ερώτημα SQL και προαιρετικές παραμέτρους. Μια πλήρης περιγραφή των μεθόδων `fetch*()` βρίσκεται [παρακάτω |#fetch]. + +| `fetch($sql, ...$params): ?Row` | Εκτελεί το ερώτημα και επιστρέφει την πρώτη γραμμή ως αντικείμενο `Row` +| `fetchAll($sql, ...$params): array` | Εκτελεί το ερώτημα και επιστρέφει όλες τις γραμμές ως πίνακα αντικειμένων `Row` +| `fetchPairs($sql, ...$params): array` | Εκτελεί το ερώτημα και επιστρέφει έναν συσχετιστικό πίνακα, όπου η πρώτη στήλη αντιπροσωπεύει το κλειδί και η δεύτερη την τιμή +| `fetchField($sql, ...$params): mixed` | Εκτελεί το ερώτημα και επιστρέφει την τιμή του πρώτου πεδίου από την πρώτη γραμμή +| `fetchList($sql, ...$params): ?array` | Εκτελεί το ερώτημα και επιστρέφει την πρώτη γραμμή ως αριθμημένο πίνακα + +Παράδειγμα: + +```php +// fetchField() - επιστρέφει την τιμή του πρώτου κελιού +$count = $database->query('SELECT COUNT(*) FROM articles') + ->fetchField(); +``` + + +`foreach` - επανάληψη μέσω γραμμών +---------------------------------- + +Μετά την εκτέλεση του ερωτήματος, επιστρέφεται ένα αντικείμενο [ResultSet |api:Nette\Database\ResultSet], το οποίο επιτρέπει την περιήγηση στα αποτελέσματα με διάφορους τρόπους. Ο ευκολότερος τρόπος για να εκτελέσετε ένα ερώτημα και να λάβετε τις γραμμές είναι με επανάληψη σε έναν βρόχο `foreach`. Αυτός ο τρόπος είναι ο πιο αποδοτικός από πλευράς μνήμης, καθώς επιστρέφει τα δεδομένα σταδιακά και δεν τα αποθηκεύει όλα στη μνήμη ταυτόχρονα. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; + // ... +} +``` + +.[note] +Το `ResultSet` μπορεί να επαναληφθεί μόνο μία φορά. Εάν χρειάζεται να επαναλάβετε πολλές φορές, πρέπει πρώτα να φορτώσετε τα δεδομένα σε έναν πίνακα, για παράδειγμα χρησιμοποιώντας τη μέθοδο `fetchAll()`. + + +fetch(): ?Row .[method] +----------------------- + +Επιστρέφει μια γραμμή ως αντικείμενο `Row`. Εάν δεν υπάρχουν άλλες γραμμές, επιστρέφει `null`. Μετακινεί τον εσωτερικό δείκτη στην επόμενη γραμμή. + +```php +$result = $database->query('SELECT * FROM users'); +$row = $result->fetch(); // φορτώνει την πρώτη γραμμή +if ($row) { + echo $row->name; +} +``` + + +fetchAll(): array .[method] +--------------------------- + +Επιστρέφει όλες τις υπόλοιπες γραμμές από το `ResultSet` ως πίνακα αντικειμένων `Row`. + +```php +$result = $database->query('SELECT * FROM users'); +$rows = $result->fetchAll(); // φορτώνει όλες τις γραμμές +foreach ($rows as $row) { + echo $row->name; +} +``` + + +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Επιστρέφει τα αποτελέσματα ως συσχετιστικό πίνακα. Το πρώτο όρισμα καθορίζει το όνομα της στήλης που θα χρησιμοποιηθεί ως κλειδί στον πίνακα, το δεύτερο όρισμα καθορίζει το όνομα της στήλης που θα χρησιμοποιηθεί ως τιμή: + +```php +$result = $database->query('SELECT id, name FROM users'); +$names = $result->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] +``` + +Εάν αναφέρουμε μόνο την πρώτη παράμετρο, η τιμή θα είναι ολόκληρη η γραμμή, δηλαδή το αντικείμενο `Row`: + +```php +$rows = $result->fetchPairs('id'); +// [1 => Row(id: 1, name: 'John'), 2 => Row(id: 2, name: 'Jane'), ...] +``` + +Σε περίπτωση διπλότυπων κλειδιών, χρησιμοποιείται η τιμή από την τελευταία γραμμή. Κατά τη χρήση `null` ως κλειδί, ο πίνακας θα αριθμηθεί αριθμητικά από το μηδέν (τότε δεν προκύπτουν συγκρούσεις): + +```php +$names = $result->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] +``` + + +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Εναλλακτικά, μπορείτε να δώσετε ως παράμετρο ένα callback, το οποίο θα επιστρέφει για κάθε γραμμή είτε την ίδια την τιμή, είτε ένα ζεύγος κλειδιού-τιμής. + +```php +$result = $database->query('SELECT * FROM users'); +$items = $result->fetchPairs(fn($row) => "$row->id - $row->name"); +// ['1 - John', '2 - Jane', ...] + +// Το callback μπορεί επίσης να επιστρέψει έναν πίνακα με ένα ζεύγος κλειδιού & τιμής: +$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]); +// ['John' => 46, 'Jane' => 21, ...] +``` + + +fetchField(): mixed .[method] +----------------------------- + +Επιστρέφει την τιμή του πρώτου πεδίου από την τρέχουσα γραμμή. Εάν δεν υπάρχουν άλλες γραμμές, επιστρέφει `null`. Μετακινεί τον εσωτερικό δείκτη στην επόμενη γραμμή. + +```php +$result = $database->query('SELECT name FROM users'); +$name = $result->fetchField(); // φορτώνει το όνομα από την πρώτη γραμμή +``` + + +fetchList(): ?array .[method] +----------------------------- + +Επιστρέφει μια γραμμή ως αριθμημένο πίνακα. Εάν δεν υπάρχουν άλλες γραμμές, επιστρέφει `null`. Μετακινεί τον εσωτερικό δείκτη στην επόμενη γραμμή. + +```php +$result = $database->query('SELECT name, email FROM users'); +$row = $result->fetchList(); // ['John', 'john@example.com'] +``` + + +getRowCount(): ?int .[method] +----------------------------- + +Επιστρέφει τον αριθμό των επηρεασμένων γραμμών από το τελευταίο ερώτημα `UPDATE` ή `DELETE`. Για το `SELECT`, είναι ο αριθμός των επιστρεφόμενων γραμμών, αλλά αυτός μπορεί να μην είναι γνωστός - σε αυτή την περίπτωση, η μέθοδος επιστρέφει `null`. + + +getColumnCount(): ?int .[method] +-------------------------------- + +Επιστρέφει τον αριθμό των στηλών στο `ResultSet`. + + +Πληροφορίες για τα ερωτήματα +============================ + +Για σκοπούς εντοπισμού σφαλμάτων, μπορούμε να λάβουμε πληροφορίες σχετικά με το τελευταίο εκτελεσμένο ερώτημα: + +```php +echo $database->getLastQueryString(); // εκτυπώνει το ερώτημα SQL + +$result = $database->query('SELECT * FROM articles'); +echo $result->getQueryString(); // εκτυπώνει το ερώτημα SQL +echo $result->getTime(); // εκτυπώνει τον χρόνο εκτέλεσης σε δευτερόλεπτα +``` + +Για την εμφάνιση του αποτελέσματος ως πίνακα HTML, μπορείτε να χρησιμοποιήσετε: + +```php +$result = $database->query('SELECT * FROM articles'); +$result->dump(); +``` + +Το ResultSet προσφέρει πληροφορίες σχετικά με τους τύπους των στηλών: + +```php +$result = $database->query('SELECT * FROM articles'); +$types = $result->getColumnTypes(); + +foreach ($types as $column => $type) { + echo "$column είναι τύπου $type->type"; // π.χ. 'id είναι τύπου int' +} +``` + + +Καταγραφή ερωτημάτων +-------------------- + +Μπορούμε να υλοποιήσουμε τη δική μας καταγραφή ερωτημάτων. Το συμβάν `onQuery` είναι ένας πίνακας callbacks που καλούνται μετά από κάθε εκτελεσμένο ερώτημα: + +```php +$database->onQuery[] = function ($database, $result) use ($logger) { + $logger->info('Query: ' . $result->getQueryString()); + $logger->info('Time: ' . $result->getTime()); + + if ($result->getRowCount() > 1000) { + $logger->warning('Large result set: ' . $result->getRowCount() . ' rows'); + } +}; +``` diff --git a/database/el/transactions.texy b/database/el/transactions.texy new file mode 100644 index 0000000000..ffdd9514af --- /dev/null +++ b/database/el/transactions.texy @@ -0,0 +1,43 @@ +Συναλλαγές (Transactions) +************************* + +.[perex] +Οι συναλλαγές εγγυώνται ότι είτε όλες οι λειτουργίες εντός της συναλλαγής θα εκτελεστούν, είτε καμία. Είναι χρήσιμες για τη διασφάλιση της συνέπειας των δεδομένων κατά τη διάρκεια πιο σύνθετων λειτουργιών. + +Ο απλούστερος τρόπος χρήσης συναλλαγών μοιάζει με αυτό: + +```php +$database->beginTransaction(); +try { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); + $database->commit(); +} catch (\Exception $e) { + $database->rollBack(); + throw $e; +} +``` + +Μπορείτε να γράψετε το ίδιο πράγμα πολύ πιο κομψά χρησιμοποιώντας τη μέθοδο `transaction()`. Δέχεται μια επανάκληση (callback) ως παράμετρο, την οποία εκτελεί σε μια συναλλαγή. Εάν η επανάκληση εκτελεστεί χωρίς εξαίρεση, η συναλλαγή επιβεβαιώνεται αυτόματα (commit). Εάν προκύψει εξαίρεση, η συναλλαγή ακυρώνεται (rollback) και η εξαίρεση διαδίδεται περαιτέρω. + +```php +$database->transaction(function ($database) use ($id) { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); +}); +``` + +Η μέθοδος `transaction()` μπορεί επίσης να επιστρέψει τιμές: + +```php +$count = $database->transaction(function ($database) { + $result = $database->query('UPDATE users SET active = ?', true); + return $result->getRowCount(); // επιστρέφει τον αριθμό των ενημερωμένων γραμμών +}); +``` diff --git a/database/en/@home.texy b/database/en/@home.texy index e770287bac..7d7452d8d1 100644 --- a/database/en/@home.texy +++ b/database/en/@home.texy @@ -1,7 +1,7 @@ -Supported Servers -================= +Supported Databases +=================== These database servers are supported: @@ -16,5 +16,5 @@ These database servers are supported: -{{title: Nette Database}} -{{description: Nette Database significantly simplifies retrieving data from the database without writing SQL queries. It uses efficient queries and does not transmit unnecessary data.}} +{{maintitle: Nette Database - awesome database layer for PHP}} +{{description: Nette Database significantly simplifies retrieving data from the database without writing SQL queries. It executes efficient queries and does not transfer unnecessary data.}} diff --git a/database/en/@left-menu.texy b/database/en/@left-menu.texy index 3a2cf9ca88..f865f8027e 100644 --- a/database/en/@left-menu.texy +++ b/database/en/@left-menu.texy @@ -1,5 +1,12 @@ -Database -******** -- [Core] +Nette Database +************** +- [Getting Started |guide] +- [SQL Way] - [Explorer] +- [Transactions] +- [Exceptions] +- [Reflection] +- [Mapping] - [Configuration] +- [Security Risks |security] +- [Upgrading] diff --git a/database/en/@meta.texy b/database/en/@meta.texy new file mode 100644 index 0000000000..42471908b0 --- /dev/null +++ b/database/en/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Documentation}} diff --git a/database/en/configuration.texy b/database/en/configuration.texy index 0e8313c59e..8a11a1cbcb 100644 --- a/database/en/configuration.texy +++ b/database/en/configuration.texy @@ -1,10 +1,10 @@ -Configuring Database -******************** +Database Configuration +********************** .[perex] -Overview of configuration options for the Nette Database. +Overview of configuration options for Nette Database. -If you are not using the whole framework, but only this library, read [how to load the configuration|bootstrap:]. +If you are not using the entire framework, but only this library, read [how to load the configuration|bootstrap:]. Single Connection @@ -14,48 +14,54 @@ Configure a single database connection: ```neon database: - # DSN, only mandatory key + # DSN, the only mandatory key dsn: "sqlite:%appDir%/Model/demo.db" user: ... password: ... ``` -It creates services of type `Nette\Database\Connection` and also `Nette\Database\Explorer` for the [Database Explorer|explorer] layer. The database connection is usually passed by [autowiring |dependency-injection:autowiring], if this is not possible, use the service names `@database.default.connection` resp. `@database.default.explorer`. +This creates the `Nette\Database\Connection` and `Nette\Database\Explorer` services, which are usually passed via [autowiring |dependency-injection:autowiring] or by referring to [their name |#DI Services]. Other settings: ```neon database: - # shows database panel in Tracy Bar? + # show the database panel in Tracy Bar? debugger: ... # (bool) defaults to true - # shows query EXPLAIN in Tracy Bar? + # show query EXPLAIN in Tracy Bar? explain: ... # (bool) defaults to true - # to enable autowiring for this connection? - autowired: ... # (bool) defaults to true for first connection + # enable autowiring for this connection? + autowired: ... # (bool) defaults to true for the first connection # table conventions: discovered, static, or class name conventions: discovered # (string) defaults to 'discovered' options: - # to connect to the database only when needed? + # connect to the database only when needed? lazy: ... # (bool) defaults to false # PHP database driver class driverClass: # (string) - # only MySQL: sets sql_mode + # MySQL only: sets sql_mode sqlmode: # (string) - # only MySQL: sets SET NAMES - charset: # (string) defaults to 'utf8mb4' ('utf8' before v5.5.3) + # MySQL only: sets SET NAMES + charset: # (string) defaults to 'utf8mb4' - # only Oracle and SQLite: date format + # MySQL only: converts TINYINT(1) to bool + convertBoolean: # (bool) defaults to false + + # returns date columns as immutable objects (since version 3.2.1) + newDateTime: # (bool) defaults to false + + # only Oracle and SQLite: format for storing date formatDateTime: # (string) defaults to 'U' ``` -The `options` key can contain other options that can be found in the [PDO driver documentation |https://www.php.net/manual/en/pdo.drivers.php], such as: +The `options` key can contain other options found in the [PDO driver documentation |https://www.php.net/manual/en/pdo.drivers.php], such as: ```neon database: @@ -67,7 +73,7 @@ database: Multiple Connections -------------------- -In the configuration we can define more database connections by dividing them into named sections: +In the configuration, we can define multiple database connections by dividing them into named sections: ```neon database: @@ -80,9 +86,23 @@ database: dsn: 'sqlite::memory:' ``` -Each defined connection creates services that includes section name in their name, ie `@database.main.connection` & `@database.main.explorer` and further `@database.another.connection` & `@database.another.explorer`. +Autowiring is enabled only for services from the first section. This can be changed using `autowired: false` or `autowired: true`. + + +DI Services +----------- + +These services are added to the DI container, where `###` represents the connection name: + +| Name | Type | Description +|---------------------------|---------------------------------|--------------------------- +| `database.###.connection` | [api:Nette\Database\Connection] | database connection +| `database.###.explorer` | [api:Nette\Database\Explorer] | [Database Explorer |explorer] + + +If we define only one connection, the service names will be `database.default.connection` and `database.default.explorer`. If we define multiple connections as in the example above, the names will correspond to the sections, i.e., `database.main.connection`, `database.main.explorer`, and also `database.another.connection` and `database.another.explorer`. -Autowiring is enabled only for services from the first section. This can be changed with `autowired: false` or `autowired: true`. Non-autowired services are passed by name: +We pass non-autowired services explicitly by referencing their name: ```neon services: diff --git a/database/en/core.texy b/database/en/core.texy deleted file mode 100644 index d950f13509..0000000000 --- a/database/en/core.texy +++ /dev/null @@ -1,350 +0,0 @@ -Database Core -************* - -.[perex] -Nette Database Core is database abstraction layer and provides core functionality. - - -Installation -============ - -Download and install the package using [Composer|best-practices:composer]: - -```shell -composer require nette/database -``` - - -Connection and Configuration -============================ - -To connect to the database, simply create an instance of the [api:Nette\Database\Connection] class: - -```php -$database = new Nette\Database\Connection($dsn, $user, $password); -``` - -The `$dsn` (data source name) parameter is [the same as used by PDO |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], eg `host=127.0.0.1;dbname=test`. In the case of failure it throws `Nette\Database\ConnectionException`. - -However, a more sophisticated way offers [application configuration |configuration]. We will add a `database` section and it creates the required objects and a database panel in the [Tracy |tracy:] bar. - -```neon -database: - dsn: 'mysql:host=127.0.0.1;dbname=test' - user: root - password: password -``` - -The connection object we [receive as a service from a DI container |dependency-injection:passing-dependencies], for example: - -```php -class Model -{ - // pass Nette\Database\Explorer to work with the Database Explorer layer - public function __construct( - private Nette\Database\Connection $database, - ) { - } -} -``` - -For more information, see [database configuration|configuration]. - - -Queries -======= - -To query database use `query()` method that returns [ResultSet |api:Nette\Database\ResultSet]. - -```php -$result = $database->query('SELECT * FROM users'); - -foreach ($result as $row) { - echo $row->id; - echo $row->name; -} - -echo $result->getRowCount(); // returns the number of rows if is known -``` - -.[note] -Over the `ResultSet` is possible to iterate only once, if we need to iterate multiple times, it is necessary to convert the result to the array via `fetchAll()` method. - -You can easily add parameters to the query, note the question mark: - -```php -$database->query('SELECT * FROM users WHERE name = ?', $name); - -$database->query('SELECT * FROM users WHERE name = ? AND active = ?', $name, $active); - -$database->query('SELECT * FROM users WHERE id IN (?)', $ids); // $ids is array -``` - -<div class=warning> -WARNING, never concatenate strings to avoid [SQL injection vulnerability |https://en.wikipedia.org/wiki/SQL_injection]! -/-- -$db->query('SELECT * FROM users WHERE name = ' . $name); // WRONG!!! -\-- -</div> - -In the case of failure `query()` throws either `Nette\Database\DriverException` or one of its descendants: - -- [ConstraintViolationException |api:Nette\Database\ConstraintViolationException] - violation of any constraint -- [ForeignKeyConstraintViolationException |api:Nette\Database\ForeignKeyConstraintViolationException] - invalid foreign key -- [NotNullConstraintViolationException |api:Nette\Database\NotNullConstraintViolationException] - violation of the NOT NULL condition -- [UniqueConstraintViolationException |api:Nette\Database\UniqueConstraintViolationException] - conflict of unique index - -In addition to `query()`, there are other useful methods: - -```php -// returns the associative array id => name -$pairs = $database->fetchPairs('SELECT id, name FROM users'); - -// returns all rows as array -$rows = $database->fetchAll('SELECT * FROM users'); - -// returns single row -$row = $database->fetch('SELECT * FROM users WHERE id = ?', $id); - -// return single field -$name = $database->fetchField('SELECT name FROM users WHERE id = ?', $id); -``` - -In case of failure, all of these methods throw `Nette\Database\DriverException.` - - -Insert, Update & Delete -======================= - -The parameter that we insert into the SQL query can also be the array (in which case it is possible to skip the wildcard `?`), which may be useful for the `INSERT` statement: - -```php -$database->query('INSERT INTO users ?', [ // here can be omitted question mark - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) - -$id = $database->getInsertId(); // returns the auto-increment of inserted row - -$id = $database->getInsertId($sequence); // or sequence value -``` - -Multiple insert: - -```php -$database->query('INSERT INTO users', [ - [ - 'name' => 'Jim', - 'year' => 1978, - ], [ - 'name' => 'Jack', - 'year' => 1987, - ], -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) -``` - -We can also pass files, DateTime objects or [enumerations |https://www.php.net/enumerations]: - -```php -$database->query('INSERT INTO users', [ - 'name' => $name, - 'created' => new DateTime, // or $database::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // inserts file contents - 'status' => State::New, // enum State -]); -``` - -Updating rows: - -```php -$result = $database->query('UPDATE users SET', [ - 'name' => $name, - 'year' => $year, -], 'WHERE id = ?', $id); -// UPDATE users SET `name` = 'Jim', `year` = 1978 WHERE id = 123 - -echo $result->getRowCount(); // returns the number of affected rows -``` - -For UPDATE, we can use operators `+=` and `-=`: - -```php -$database->query('UPDATE users SET', [ - 'age+=' => 1, // note += -], 'WHERE id = ?', $id); -// UPDATE users SET `age` = `age` + 1 -``` - -Deleting: - -```php -$result = $database->query('DELETE FROM users WHERE id = ?', $id); -echo $result->getRowCount(); // returns the number of affected rows -``` - - -Advanced Queries -================ - -Insert or update, if it already exists: - -```php -$database->query('INSERT INTO users', [ - 'id' => $id, - 'name' => $name, - 'year' => $year, -], 'ON DUPLICATE KEY UPDATE', [ - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) -// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 -``` - -Note that Nette Database recognizes the SQL context in which the array parameter is inserted and builds the SQL code accordingly. So, from the first array he generates `(id, name, year) VALUES (123, 'Jim', 1978)`, while the second converts to `name = 'Jim', year = 1978`. - -We can also describe sorting using array, in keys are column names and values are boolean that determines whether to sort in ascending order: - -```php -$database->query('SELECT id FROM author ORDER BY', [ - 'id' => true, // ascending - 'name' => false, // descending -]); -// SELECT id FROM author ORDER BY `id`, `name` DESC -``` - -If the detection did not work, you can specify the form of the assembly with a wildcard `?` followed by a hint. These hints are supported: - -| ?values | (key1, key2, ...) VALUES (value1, value2, ...) -| ?set | key1 = value1, key2 = value2, ... -| ?and | key1 = value1 AND key2 = value2 ... -| ?or | key1 = value1 OR key2 = value2 ... -| ?order | key1 ASC, key2 DESC - -The WHERE clause uses the `?and` operator so conditions are linked by `AND`: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND `year` = 1978 -``` - -Which can easily be changed to `OR` by using the `?or` wildcard: - -```php -$result = $database->query('SELECT * FROM users WHERE ?or', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' OR `year` = 1978 -``` - -We can use operators in conditions: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name <>' => $name, - 'year >' => $year, -]); -// SELECT * FROM users WHERE `name` <> 'Jim' AND `year` > 1978 -``` - -And also enumerations: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => ['Jim', 'Jack'], - 'role NOT IN' => ['admin', 'owner'], // enumeration + operator NOT IN -]); -// SELECT * FROM users WHERE -// `name` IN ('Jim', 'Jack') AND `role` NOT IN ('admin', 'owner') -``` - -We can also include a piece of custom SQL code using the so-called SQL literal: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year >' => $database::literal('YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) -``` - -Alternatively: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) -``` - -SQL literal also can have its parameters: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > ? AND year < ?', $min, $max), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) -``` - -Thanks to which we can create interesting combinations: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('?or', [ - 'active' => true, - 'role' => $role, - ]), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') -``` - - -Variable Name -============= - -There is a `?name` wildcard that you use if the table name or column name is a variable. (Beware, do not allow the user to manipulate the content of such a variable): - -```php -$table = 'blog.users'; -$column = 'name'; -$database->query('SELECT * FROM ?name WHERE ?name = ?', $table, $column, $name); -// SELECT * FROM `blog`.`users` WHERE `name` = 'Jim' -``` - - -Transactions -============ - -There are three methods for dealing with transactions: - -```php -$database->beginTransaction(); - -$database->commit(); - -$database->rollback(); -``` - -An elegant way is offered by the `transaction()` method. You pass the callback that is executed in the transaction. If an exception is thrown during execution, the transaction is dropped, if everything goes well, the transaction is committed. - -```php -$id = $database->transaction(function ($database) { - $database->query('DELETE FROM ...'); - $database->query('INSERT INTO ...'); - // ... - return $database->getInsertId(); -}); -``` - -As you can see, the `transaction()` method returns the return value of the callback. - -The transaction() can also be nested, which simplifies the implementation of independent repositories. diff --git a/database/en/exceptions.texy b/database/en/exceptions.texy new file mode 100644 index 0000000000..d3780dcacc --- /dev/null +++ b/database/en/exceptions.texy @@ -0,0 +1,34 @@ +Exceptions +********** + +Nette Database uses an exception hierarchy. The base class is `Nette\Database\DriverException`, which extends `PDOException` and provides enhanced functionality for working with database errors: + +- The `getDriverCode()` method returns the error code from the database driver. +- The `getSqlState()` method returns the SQLSTATE code. +- The `getQueryString()` and `getParameters()` methods allow retrieving the original query and its parameters. + +The `DriverException` class is extended by the following specialized exceptions: + +- `ConnectionException` – indicates a failure to connect to the database server. +- `ConstraintViolationException` – the base class for database constraint violations, from which the following exceptions inherit: + - `ForeignKeyConstraintViolationException` – violation of a foreign key constraint. + - `NotNullConstraintViolationException` – violation of a NOT NULL constraint. + - `UniqueConstraintViolationException` – violation of a uniqueness constraint. + + +The following example demonstrates how to catch a `UniqueConstraintViolationException`, which occurs when trying to insert a user with an email that already exists in the database (assuming the `email` column has a unique index): + +```php +try { + $database->query('INSERT INTO users', [ + 'email' => 'john@example.com', + 'name' => 'John Doe', + 'password' => $hashedPassword, + ]); +} catch (Nette\Database\UniqueConstraintViolationException $e) { + echo 'A user with this email already exists.'; + +} catch (Nette\Database\DriverException $e) { + echo 'An error occurred during registration: ' . $e->getMessage(); +} +``` diff --git a/database/en/explorer.texy b/database/en/explorer.texy index fb1820797e..dc5e7ef7af 100644 --- a/database/en/explorer.texy +++ b/database/en/explorer.texy @@ -3,548 +3,910 @@ Database Explorer <div class=perex> -Nette Database Explorer significantly simplifies retrieving data from the database without writing SQL queries. +Explorer offers an intuitive and efficient way to work with your database. It automatically handles table relationships and optimizes queries, allowing you to focus on your application logic. It works immediately without configuration. If you need full control over SQL queries, you can use the [SQL way |SQL way]. -- uses efficient queries -- no data is transmitted unnecessarily -- features elegant syntax +- Working with data is natural and easy to understand +- Generates optimized SQL queries that fetch only the necessary data +- Provides easy access to related data without the need to write JOIN queries +- Works immediately without any configuration or entity generation </div> -To use Database Explorer, start with a table - call `table()` on a [api:Nette\Database\Explorer] object. The easiest way to get a context object instance is [described here |core#Connection and Configuration], or, for case when Nette Database Explorer is used as a standalone tool, it can be [created manually|#Creating Explorer Manually]. + +Working with the Explorer begins by calling the `table()` method on the [api:Nette\Database\Explorer] object (see [Connection and Configuration |guide#Connection and Configuration] for details on setting up the database connection): ```php -$books = $explorer->table('book'); // db table name is 'book' +$books = $explorer->table('book'); // 'book' is the table name ``` -The call returns an instance of [Selection |api:Nette\Database\Table\Selection] object, that can be iterated over to retrieve all the books. Each item (a row) is represented by an instance of [ActiveRow |api:Nette\Database\Table\ActiveRow] with data mapped to its properties: +The method returns a [Selection |api:Nette\Database\Table\Selection] object, which represents an SQL query. Additional methods can be chained to this object for filtering and sorting results. The query is assembled and executed only when the data is requested, for example, by iterating with `foreach`. Each row is represented by an [ActiveRow |api:Nette\Database\Table\ActiveRow] object: ```php foreach ($books as $book) { - echo $book->title; - echo $book->author_id; + echo $book->title; // outputs the 'title' column + echo $book->author_id; // outputs the 'author_id' column } ``` -Getting just one specific row is done by `get()` method, which directly returns an ActiveRow instance. +Explorer greatly simplifies working with [table relationships |#Relationships Between Tables]. The following example shows how easily we can output data from related tables (books and their authors). Notice that no JOIN queries need to be written; Nette generates them for us: ```php -$book = $explorer->table('book')->get(2); // returns book with id 2 -echo $book->title; -echo $book->author_id; +$books = $explorer->table('book'); + +foreach ($books as $book) { + echo 'Book: ' . $book->title; + echo 'Author: ' . $book->author->name; // creates a JOIN to the 'author' table +} ``` -Let's take a look at common use-case. You need to fetch books and their authors. It is a common 1:N relationship. The often used solution is to fetch data using one SQL query with table joins. The second possibility is to fetch data separately, run one query for getting books and then get an author for each book by another query (e.g. in your foreach cycle). This could be easily optimized to run only two queries, one for the books, and another for the needed authors - and that is exactly the way how Nette Database Explorer does it. +Nette Database Explorer optimizes queries for maximum efficiency. The above example performs only two SELECT queries, regardless of whether we process 10 or 10,000 books. -In the examples below, we will work with the database schema in the figure. There are OneHasMany (1:N) links (author of book `author_id` and possible translator `translator_id`, which may be `null`) and ManyHasMany (M:N) link between book and its tags. +Additionally, Explorer tracks which columns are used in the code and fetches only those from the database, saving further performance. This behavior is fully automatic and adaptive. If you later modify the code to use additional columns, Explorer automatically adjusts the queries. You don’t need to configure anything or think about which columns will be needed — leave that to Nette. -[An example, including a schema, is found on GitHub |https://github.com/nette-examples/books]. -[* db-schema-1-.webp *] *** Database structure used in the examples .<> +Filtering and Sorting +===================== -The following code lists the author's name for each book and all its tags. We will [discuss in a moment |#Working with relationships] how this works internally. +The `Selection` class provides methods for filtering and sorting data selections. -```php -$books = $explorer->table('book'); +.[language-php] +| `where($condition, ...$params)` | Adds a WHERE condition. Multiple conditions are combined using AND | +| `whereOr(array $conditions)` | Adds a group of WHERE conditions combined using OR | +| `wherePrimary($value)` | Adds a WHERE condition based on the primary key | +| `order($columns, ...$params)` | Sets sorting with ORDER BY | +| `select($columns, ...$params)` | Specifies which columns to fetch | +| `limit($limit, $offset = null)` | Limits the number of rows (LIMIT) and optionally sets OFFSET | +| `page($page, $itemsPerPage, &$total = null)` | Sets pagination | +| `group($columns, ...$params)` | Groups rows (GROUP BY) | +| `having($condition, ...$params)`| Adds a HAVING condition for filtering grouped rows | -foreach ($books as $book) { - echo 'title: ' . $book->title; - echo 'written by: ' . $book->author->name; // $book->author is row from table 'author' +Methods can be chained (the so-called [fluent interface |nette:introduction-to-object-oriented-programming#Fluent Interfaces]): `$table->where(...)->order(...)->limit(...)`. - echo 'tags: '; - foreach ($book->related('book_tag') as $bookTag) { - echo $bookTag->tag->name . ', '; // $bookTag->tag is row from table 'tag' - } -} -``` +In these methods, you can also use special notations for accessing [data from related tables |#Querying Through Related Tables]. -You will be pleased how efficiently the database layer works. The example above makes a constant number of requests that look like this: -```sql -SELECT * FROM `book` -SELECT * FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT * FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) -``` +Escaping and Identifiers +------------------------ -If you use [cache |caching:] (defaults on), no columns will be queried unnecessarily. After the first query, cache will store the used column names and Nette Database Explorer will run queries only with the needed columns: +The methods automatically escape parameters and quote identifiers (table and column names), preventing SQL injection. To ensure proper operation, a few rules must be followed: -```sql -SELECT `id`, `title`, `author_id` FROM `book` -SELECT `id`, `name` FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT `book_id`, `tag_id` FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT `id`, `name` FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) +- Write keywords, function names, procedures, etc., in **uppercase**. +- Write column and table names in **lowercase**. +- Always pass strings using **parameters**. + +```php +where('name = ' . $name); // CRITICAL VULNERABILITY: SQL injection +where('name LIKE "%search%"'); // WRONG: complicates automatic quoting +where('name LIKE ?', '%search%'); // CORRECT: value passed as a parameter + +where('name like ?', $name); // WRONG: generates: `name` `like` ? +where('name LIKE ?', $name); // CORRECT: generates: `name` LIKE ? +where('LOWER(name) = ?', $value);// CORRECT: LOWER(`name`) = ? ``` -Selections -========== +where(string|array $condition, ...$parameters): static .[method] +---------------------------------------------------------------- -See possibilities how to filter and restrict rows [api:Nette\Database\Table\Selection]: +Filters results using WHERE conditions. Its strength lies in intelligently handling various value types and automatically selecting appropriate SQL operators. -.[language-php] -| `$table->where($where[, $param[, ...]])` | Set WHERE using AND as a glue if two or more conditions are supplied -| `$table->whereOr($where)` | Set WHERE using OR as a glue if two or more conditions are supplied -| `$table->order($columns)` | Set ORDER BY, can be expression `('column DESC, id DESC')` -| `$table->select($columns)` | Set retrieved columns, can be expression `('col, MD5(col) AS hash')` -| `$table->limit($limit[, $offset])` | Set LIMIT and OFFSET -| `$table->page($page, $itemsPerPage[, &$lastPage])` | Enables pagination -| `$table->group($columns)` | Set GROUP BY -| `$table->having($having)` | Set HAVING +Basic usage: -Fluent interface can be used, for example `$table->where(...)->order(...)->limit(...)`. Multiple `where` or `whereOr` conditions are connected with the `AND` operator. +```php +$table->where('id', $value); // WHERE `id` = 123 +$table->where('id > ?', $value); // WHERE `id` > 123 +$table->where('id = ? OR name = ?', $id, $name); // WHERE `id` = 1 OR `name` = 'Jon Snow' +``` +Thanks to automatic detection of suitable operators, you don’t need to handle various special cases — Nette resolves them for you: -where() -------- +```php +$table->where('id', 1); // WHERE `id` = 1 +$table->where('id', null); // WHERE `id` IS NULL +$table->where('id', [1, 2, 3]); // WHERE `id` IN (1, 2, 3) +// You can also use the placeholder ? without an operator: +$table->where('id ?', 1); // WHERE `id` = 1 +``` -Nette Database Explorer can automatically add needed operators for passed values: +The method correctly handles negative conditions and empty arrays: -.[language-php] -| `$table->where('field', $value)` | field = $value -| `$table->where('field', null)` | field IS NULL -| `$table->where('field > ?', $val)` | field > $val -| `$table->where('field', [1, 2])` | field IN (1, 2) -| `$table->where('id = ? OR name = ?', 1, $name)` | id = 1 OR name = 'Jon Snow' -| `$table->where('field', $explorer->table($tableName))` | field IN (SELECT $primary FROM $tableName) -| `$table->where('field', $explorer->table($tableName)->select('col'))` | field IN (SELECT col FROM $tableName) +```php +$table->where('id', []); // WHERE `id` IS NULL AND FALSE -- finds nothing +$table->where('id NOT', []); // WHERE `id` IS NULL OR TRUE -- finds everything +$table->where('NOT (id ?)', []); // WHERE NOT (`id` IS NULL AND FALSE) -- finds everything +// $table->where('NOT id ?', $ids); // WARNING: This syntax is not supported +``` -You can provide placeholder even without column operator. These calls are the same. +You can also pass the result of another table query as a parameter, creating a subquery: ```php -$table->where('id = ? OR id = ?', 1, 2); -$table->where('id ? OR id ?', 1, 2); +// WHERE `id` IN (SELECT `id` FROM `tableName`) +$table->where('id', $explorer->table($tableName)); + +// WHERE `id` IN (SELECT `col` FROM `tableName`) +$table->where('id', $explorer->table($tableName)->select('col')); ``` -This feature allows to generate correct operator based on value: +Conditions can also be passed as an array, whose items are combined using AND: ```php -$table->where('id ?', 2); // id = 2 -$table->where('id ?', null); // id IS NULL -$table->where('id', $ids); // id IN (...) +// WHERE (`price_final` < `price_original`) AND (`stock_count` > `min_stock`) +$table->where([ + 'price_final < price_original', + 'stock_count > min_stock', +]); ``` -Selection correctly handles also negative conditions, works for empty arrays too: +In the array, you can use key => value pairs, and Nette will again automatically choose the correct operators: ```php -$table->where('id', []); // id IS NULL AND FALSE -$table->where('id NOT', []); // id IS NULL OR TRUE -$table->where('NOT (id ?)', $ids); // NOT (id IS NULL AND FALSE) +// WHERE (`status` = 'active') AND (`id` IN (1, 2, 3)) +$table->where([ + 'status' => 'active', + 'id' => [1, 2, 3], +]); +``` -// this will throws an exception, this syntax is not supported -$table->where('NOT id ?', $ids); +In the array, you can combine SQL expressions with placeholders and multiple parameters. This is suitable for complex conditions with precisely defined operators: + +```php +// WHERE (`age` > 18) AND (ROUND(`score`, 2) > 75.5) +$table->where([ + 'age > ?' => 18, + 'ROUND(score, ?) > ?' => [2, 75.5], // two parameters are passed as an array +]); ``` +Multiple calls to `where()` automatically combine conditions using AND. + -whereOr() ---------- +whereOr(array $parameters): static .[method] +-------------------------------------------- -Example of use without parameters: +Similar to `where()`, this adds conditions, but combines them using OR: ```php -// WHERE (user_id IS NULL) OR (SUM(`field1`) > SUM(`field2`)) +// WHERE (`status` = 'active') OR (`deleted` = 1) $table->whereOr([ - 'user_id IS NULL', - 'SUM(field1) > SUM(field2)', + 'status' => 'active', + 'deleted' => true, ]); ``` -We use the parameters. If you do not specify an operator, Nette Database Explorer will automatically add the appropriate one: +More complex expressions can also be used here: ```php -// WHERE (`field1` IS NULL) OR (`field2` IN (3, 5)) OR (`amount` > 11) +// WHERE (`price` > 1000) OR (`price_with_tax` > 1500) $table->whereOr([ - 'field1' => null, - 'field2' => [3, 5], - 'amount >' => 11, + 'price > ?' => 1000, + 'price_with_tax > ?' => 1500, ]); ``` -The key can contain an expression containing wildcard question marks and then pass parameters in the value: + +wherePrimary(mixed $key): static .[method] +------------------------------------------ + +Adds a condition for the table's primary key: ```php -// WHERE (`id` > 12) OR (ROUND(`id`, 5) = 3) -$table->whereOr([ - 'id > ?' => 12, - 'ROUND(id, ?) = ?' => [5, 3], -]); +// WHERE `id` = 123 +$table->wherePrimary(123); + +// WHERE `id` IN (1, 2, 3) +$table->wherePrimary([1, 2, 3]); ``` +If the table has a composite primary key (e.g., `foo_id`, `bar_id`), pass it as an array: + +```php +// WHERE `foo_id` = 1 AND `bar_id` = 5 +$table->wherePrimary(['foo_id' => 1, 'bar_id' => 5])->fetch(); + +// WHERE (`foo_id`, `bar_id`) IN ((1, 5), (2, 3)) +$table->wherePrimary([ + ['foo_id' => 1, 'bar_id' => 5], + ['foo_id' => 2, 'bar_id' => 3], +])->fetchAll(); +``` -order() -------- -Examples of use: +order(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- + +Specifies the order in which rows are returned. You can sort by one or more columns, in ascending or descending order, or according to a custom expression: ```php -$table->order('field1'); // ORDER BY `field1` -$table->order('field1 DESC, field2'); // ORDER BY `field1` DESC, `field2` -$table->order('field = ? DESC', 123); // ORDER BY `field` = 123 DESC +$table->order('created'); // ORDER BY `created` +$table->order('created DESC'); // ORDER BY `created` DESC +$table->order('priority DESC, created'); // ORDER BY `priority` DESC, `created` +$table->order('status = ? DESC', 'active'); // ORDER BY `status` = 'active' DESC ``` -select() --------- +select(string $columns, ...$parameters): static .[method] +--------------------------------------------------------- -Examples of use: +Specifies the columns to be returned from the database. By default, Nette Database Explorer returns only the columns that are actually used in the code. Use the `select()` method when you need to retrieve specific expressions: ```php -$table->select('field1'); // SELECT `field1` -$table->select('col, UPPER(col) AS abc'); // SELECT `col`, UPPER(`col`) AS abc -$table->select('SUBSTR(title, ?)', 3); // SELECT SUBSTR(`title`, 3) +// SELECT *, DATE_FORMAT(`created_at`, "%d.%m.%Y") AS `formatted_date` +$table->select('*, DATE_FORMAT(created_at, ?) AS formatted_date', '%d.%m.%Y'); ``` +Aliases defined using `AS` are then accessible as properties of the `ActiveRow` object: -limit() -------- +```php +foreach ($table as $row) { + echo $row->formatted_date; // access the alias +} +``` -Examples of use: + +limit(?int $limit, ?int $offset = null): static .[method] +--------------------------------------------------------- + +Limits the number of returned rows (LIMIT) and optionally allows setting an offset: ```php -$table->limit(1); // LIMIT 1 -$table->limit(1, 10); // LIMIT 1 OFFSET 10 +$table->limit(10); // LIMIT 10 (returns the first 10 rows) +$table->limit(10, 20); // LIMIT 10 OFFSET 20 ``` +For pagination, it is more appropriate to use the `page()` method. + -page() ------- +page(int $page, int $itemsPerPage, &$numOfPages = null): static .[method] +------------------------------------------------------------------------- -An alternative way to set the limit and offset: +Facilitates pagination of results. It accepts the page number (starting from 1) and the number of items per page. Optionally, you can pass a reference to a variable where the total number of pages will be stored: ```php -$page = 5; -$itemsPerPage = 10; -$table->page($page, $itemsPerPage); // LIMIT 10 OFFSET 40 +$numOfPages = null; +$table->page(page: 3, itemsPerPage: 10, $numOfPages); +echo "Total pages: $numOfPages"; ``` -Getting the last page number, passed to the `$lastPage` variable: + +group(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- + +Groups rows according to the specified columns (GROUP BY). It is typically used in conjunction with aggregate functions: ```php -$table->page($page, $itemsPerPage, $lastPage); +// Counts the number of products in each category +$table->select('category_id, COUNT(*) AS count') + ->group('category_id'); ``` -group() -------- +having(string $having, ...$parameters): static .[method] +-------------------------------------------------------- -Examples of use: +Sets a condition for filtering grouped rows (HAVING). It can be used in conjunction with the `group()` method and aggregate functions: ```php -$table->group('field1'); // GROUP BY `field1` -$table->group('field1, field2'); // GROUP BY `field1`, `field2` +// Finds categories that have more than 100 products +$table->select('category_id, COUNT(*) AS count') + ->group('category_id') + ->having('count > ?', 100); ``` -having() --------- +Reading Data +============ + +For reading data from the database, several useful methods are available: + +.[language-php] +| `foreach ($table as $key => $row)` | Iterates through all rows, `$key` is the primary key value, `$row` is an ActiveRow object | +| `$row = $table->get($key)` | Returns a single row by primary key | +| `$row = $table->fetch()` | Returns the current row and advances the pointer to the next one | +| `$array = $table->fetchPairs()` | Creates an associative array from the results | +| `$array = $table->fetchAll()` | Returns all rows as an array | +| `count($table)` | Returns the number of rows in the Selection object | + +The [ActiveRow |api:Nette\Database\Table\ActiveRow] object is read-only. This means you cannot change the values of its properties. This restriction ensures data consistency and prevents unexpected side effects. Data is loaded from the database, and any changes should be made explicitly and in a controlled manner. + -Examples of use: +`foreach` - Iterating Through All Rows +-------------------------------------- + +The easiest way to execute a query and retrieve rows is by iterating using a `foreach` loop. It automatically executes the SQL query. ```php -$table->having('COUNT(items) >', 100); // HAVING COUNT(`items`) > 100 +$books = $explorer->table('book'); +foreach ($books as $key => $book) { + // $key is the primary key value, $book is ActiveRow + echo "$book->title ({$book->author->name})"; +} ``` -Filtering by Another Table Value .[#toc-joining-key] ----------------------------------------------------- +get($key): ?ActiveRow .[method] +------------------------------- + +Executes the SQL query and returns the row by primary key, or `null` if it doesn't exist. -Quite often you need filter results by some condition which involves another database table. These types of condition require table join. However, you don't need to write them anymore. +```php +$book = $explorer->table('book')->get(123); // returns ActiveRow with ID 123 or null +if ($book) { + echo $book->title; +} +``` -Let's say you need to get all books whose author's name is 'Jon'. All you need to write is the joining key of the relation and the column name in the joined table. The joining key is derived from the column which refers to the table you want to join. In our example (see the db schema) it is the column `author_id`, and it is sufficient to use just the first part of it - `author` (the `_id` suffix can be omitted). `name` is a column in the `author` table we would like to use. A condition for book translator (which is connected by `translator_id` column) can be created just as easily. + +fetch(): ?ActiveRow .[method] +----------------------------- + +Returns the current row and advances the internal pointer to the next one. If there are no more rows, it returns `null`. ```php $books = $explorer->table('book'); -$books->where('author.name LIKE ?', '%Jon%'); -$books->where('translator.name', 'David Grudl'); +while ($book = $books->fetch()) { + $this->processBook($book); +} ``` -The joining key logic is driven by implementation of [Conventions |api:Nette\Database\Conventions]. We encourage to use [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions], which analyzes your foreign keys and allows you to easily work with these relationships. -The relationship between book and its author is 1:N. The reverse relationship is also possible. We call it **backjoin**. Take a look at another example. We would like to fetch all authors, who have written more than 3 books. To make the join reverse we use `:` (colon). Colon means that the joined relationship means hasMany (and it's quite logical too, as two dots are more than one dot). Unfortunately, the Selection class isn't smart enough, so we have to help with the aggregation and provide a `GROUP BY` statement, also the condition has to be written in form of `HAVING` statement. +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Returns results as an associative array. The first argument specifies the column name to be used as the key in the array, the second argument specifies the column name to be used as the value: ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book.id) > 3'); +$authors = $explorer->table('author')->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] ``` -You may have noticed that the joining expression refers to the book, but it's not clear, whether we are joining through `author_id` or `translator_id`. In the example above, Selection joins through the `author_id` column because a match with the source table has been found - the `author` table. If there was no such a match and there would be more possibilities, Nette would throw [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. +If only the first parameter is provided, the value will be the entire row, i.e., the `ActiveRow` object: -To make a join through `translator_id` column, provide an optional parameter within the joining expression. +```php +$authors = $explorer->table('author')->fetchPairs('id'); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` + +In case of duplicate keys, the value from the last row is used. When using `null` as the key, the array will be indexed numerically starting from zero (then no collisions occur): ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book(translator).id) > 3'); +$authors = $explorer->table('author')->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] ``` -Let's take a look at some more difficult joining expression. -We would like to find all authors who have written something about PHP. All books have tags so we should select those authors who have written any book with the PHP tag. +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Alternatively, you can pass a callback as a parameter, which will return either a single value or a key-value pair for each row. ```php -$authors = $explorer->table('author'); -$authors->where(':book:book_tags.tag.name', 'PHP') - ->group('author.id') - ->having('COUNT(:book:book_tags.tag.id) > 0'); +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => "$row->title ({$row->author->name})"); +// ['First Book (John Novak)', ...] + +// The callback can also return an array with a key & value pair: +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => [$row->title, $row->author->name]); +// ['First Book' => 'John Novak', ...] ``` -Aggregate Queries ------------------ +fetchAll(): array .[method] +--------------------------- -| `$table->count('*')` | Get number of rows -| `$table->count("DISTINCT $column")` | Get number of distinct values -| `$table->min($column)` | Get minimum value -| `$table->max($column)` | Get maximum value -| `$table->sum($column)` | Get the sum of all values -| `$table->aggregation("GROUP_CONCAT($column)")` | Run any aggregation function +Returns all rows as an associative array of `ActiveRow` objects, where the keys are the primary key values. -.[caution] -The `count()` method without any specified parameters selects all records and returns the array size, which is very inefficient. For example, if you need to calculate the number of rows for paging, always specify the first argument. +```php +$allBooks = $explorer->table('book')->fetchAll(); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` + + +count(): int .[method] +---------------------- + +The `count()` method without a parameter returns the number of rows in the `Selection` object: + +```php +$table->where('category', 1); +$count = $table->count(); +$count = count($table); // alternative +``` +Note: `count()` with a parameter performs the COUNT aggregate function in the database, see below. -Escaping & Quoting -================== -Database Explorer is smart and escape parameters and quotes identificators for you. These basic rules need to be followed, though: +ActiveRow::toArray(): array .[method] +------------------------------------- -- keywords, functions, procedures must be uppercase -- columns and tables must be lowercase -- pass variables as parameters, do not concatenate +Converts the `ActiveRow` object to an associative array, where keys are column names and values are the corresponding data. ```php -->where('name like ?', 'John'); // WRONG! generates: `name` `like` ? -->where('name LIKE ?', 'John'); // CORRECT +$book = $explorer->table('book')->get(1); +$bookArray = $book->toArray(); +// $bookArray will be ['id' => 1, 'title' => '...', 'author_id' => ..., ...] +``` + + +Aggregation +=========== + +The `Selection` class provides methods for easily performing aggregate functions (COUNT, SUM, MIN, MAX, AVG, etc.). + +.[language-php] +| `count($expr)` | Counts the number of rows | +| `min($expr)` | Returns the minimum value in a column | +| `max($expr)` | Returns the maximum value in a column | +| `sum($expr)` | Returns the sum of values in a column | +| `aggregation($function)` | Allows any aggregation function, such as `AVG()` or `GROUP_CONCAT()` | + -->where('KEY = ?', $value); // WRONG! KEY is a keyword -->where('key = ?', $value); // CORRECT. generates: `key` = ? +count(string $expr): int .[method] +---------------------------------- -->where('name = ' . $name); // WRONG! sql injection! -->where('name = ?', $name); // CORRECT +Executes an SQL query with the COUNT function and returns the result. The method is used to determine how many rows match a certain condition: -->select('DATE_FORMAT(created, "%d.%m.%Y")'); // WRONG! pass variables as parameters, do not concatenate -->select('DATE_FORMAT(created, ?)', '%d.%m.%Y'); // CORRECT +```php +$count = $table->count('*'); // SELECT COUNT(*) FROM `table` +$count = $table->count('DISTINCT column'); // SELECT COUNT(DISTINCT `column`) FROM `table` ``` -.[warning] -Wrong usage can produce security holes +Note: [#count()] without a parameter only returns the number of rows in the `Selection` object. + + +min(string $expr) and max(string $expr) .[method] +------------------------------------------------- + +The `min()` and `max()` methods return the minimum and maximum values in the specified column or expression: + +```php +// SELECT MAX(`price`) FROM `products` WHERE `active` = 1 +$maxPrice = $products->where('active', true) + ->max('price'); +``` -Fetching Data -============= +sum(string $expr): int .[method] +-------------------------------- -| `foreach ($table as $id => $row)` | Iterate over all rows in result -| `$row = $table->get($id)` | Get single row with ID $id from table -| `$row = $table->fetch()` | Get next row from the result -| `$array = $table->fetchPairs($key, $value)` | Fetch all values to associative array -| `$array = $table->fetchPairs($key)` | Fetch all rows to associative array -| `count($table)` | Get number of rows in result set +Returns the sum of values in the specified column or expression: + +```php +// SELECT SUM(`price` * `items_in_stock`) FROM `products` WHERE `active` = 1 +$totalPrice = $products->where('active', true) + ->sum('price * items_in_stock'); +``` + + +aggregation(string $function, ?string $groupFunction = null): mixed .[method] +----------------------------------------------------------------------------- + +Allows performing any aggregate function. + +```php +// average price of products in a category +$avgPrice = $products->where('category_id', 1) + ->aggregation('AVG(price)'); + +// joins product tags into a single string +$tags = $products->where('id', 1) + ->aggregation('GROUP_CONCAT(tag.name) AS tags') + ->fetch() + ->tags; +``` + +If we need to aggregate results that already result from some aggregate function and grouping (e.g., `SUM(value)` over grouped rows), we specify the aggregate function to be applied to these intermediate results as the second argument: + +```php +// Calculates the total price of products in stock for individual categories and then sums these prices together. +$totalPrice = $products->select('category_id, SUM(price * stock) AS category_total') + ->group('category_id') + ->aggregation('SUM(category_total)', 'SUM'); +``` + +In this example, we first calculate the total price of products in each category (`SUM(price * stock) AS category_total`) and group the results by `category_id`. Then, we use `aggregation('SUM(category_total)', 'SUM')` to sum these intermediate totals `category_total`. The second argument `'SUM'` specifies that the SUM function should be applied to the intermediate results. Insert, Update & Delete ======================= -Method `insert()` accepts array of Traversable objects (for example [ArrayHash |utils:arrays#ArrayHash] which returns [forms|forms:]): +Nette Database Explorer simplifies inserting, updating, and deleting data. All the methods mentioned throw a `Nette\Database\DriverException` in case of an error. + + +Selection::insert(iterable $data) .[method] +------------------------------------------- + +Inserts new records into the table. + +**Inserting a single record:** + +Pass the new record as an associative array or iterable object (like `ArrayHash` used in [forms |forms:]), where keys correspond to column names in the table. + +If the table has a defined primary key, the method returns an `ActiveRow` object, which is reloaded from the database to reflect any changes made at the database level (triggers, default column values, auto-increment column calculations). This ensures data consistency, and the object always contains the current data from the database. If it doesn't have a unique primary key, it returns the passed data as an array. ```php $row = $explorer->table('users')->insert([ - 'name' => $name, - 'year' => $year, + 'name' => 'John Doe', + 'email' => 'john.doe@example.com', ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) +// $row is an instance of ActiveRow and contains the complete data of the inserted row, +// including the automatically generated ID and any changes made by triggers +echo $row->id; // Outputs the ID of the newly inserted user +echo $row->created_at; // Outputs the creation time if set by a trigger ``` -If primary key is defined on the table, an ActiveRow object containing the inserted row is returned. +**Inserting multiple records at once:** -Multiple insert: +The `insert()` method allows inserting multiple records using a single SQL query. In this case, it returns the number of inserted rows. ```php -$explorer->table('users')->insert([ +$insertedRows = $explorer->table('users')->insert([ + [ + 'name' => 'John', + 'year' => 1994, + ], [ - 'name' => 'Jim', - 'year' => 1978, - ], [ 'name' => 'Jack', - 'year' => 1987, + 'year' => 1995, ], ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) +// INSERT INTO `users` (`name`, `year`) VALUES ('John', 1994), ('Jack', 1995) +// $insertedRows will be 2 ``` -Files or DateTime objects can be passed as parameters: +A `Selection` object with a data selection can also be passed as a parameter. + +```php +$newUsers = $explorer->table('potential_users') + ->where('approved', 1) + ->select('name, email'); + +$insertedRows = $explorer->table('users')->insert($newUsers); +``` + +**Inserting special values:** + +We can also pass files, `DateTime` objects, or SQL literals as values: ```php $explorer->table('users')->insert([ - 'name' => $name, - 'created' => new DateTime, // or $explorer::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // inserts the file + 'name' => 'John', + 'created_at' => new DateTime, // converts to database format + 'avatar' => fopen('image.jpg', 'rb'), // inserts binary content of the file + 'uuid' => $explorer::literal('UUID()'), // calls the UUID() function ]); ``` -Updating (returns the count of affected rows): + +Selection::update(iterable $data): int .[method] +------------------------------------------------ + +Updates rows in the table according to the specified filter. Returns the number of actually modified rows. + +Pass the columns to be changed as an associative array or iterable object (like `ArrayHash` used in [forms |forms:]), where keys correspond to column names in the table: ```php -$count = $explorer->table('users') - ->where('id', 10) // must be called before update() +$affected = $explorer->table('users') + ->where('id', 10) ->update([ - 'name' => 'Ned Stark' + 'name' => 'John Smith', + 'year' => 1994, ]); -// UPDATE `users` SET `name`='Ned Stark' WHERE (`id` = 10) +// UPDATE `users` SET `name` = 'John Smith', `year` = 1994 WHERE `id` = 10 ``` -For update we can use operators `+=` a `-=`: +To change numeric values, you can use the `+=` and `-=` operators: ```php $explorer->table('users') + ->where('id', 10) ->update([ - 'age+=' => 1, // see += + 'points+=' => 1, // increases the value of the 'points' column by 1 + 'coins-=' => 1, // decreases the value of the 'coins' column by 1 ]); -// UPDATE users SET `age` = `age` + 1 +// UPDATE `users` SET `points` = `points` + 1, `coins` = `coins` - 1 WHERE `id` = 10 ``` -Deleting (returns the count of deleted rows): + +Selection::delete(): int .[method] +---------------------------------- + +Deletes rows from the table according to the specified filter. Returns the number of deleted rows. ```php $count = $explorer->table('users') ->where('id', 10) ->delete(); -// DELETE FROM `users` WHERE (`id` = 10) +// DELETE FROM `users` WHERE `id` = 10 ``` +.[caution] +When calling `update()` or `delete()`, don't forget to use `where()` to specify the rows to be modified/deleted. If `where()` is not used, the operation will be performed on the entire table! + -Working with Relationships -========================== +ActiveRow::update(iterable $data): bool .[method] +------------------------------------------------- +Updates data in the database row represented by the `ActiveRow` object. It accepts an iterable with data to be updated (keys are column names). To change numeric values, you can use the `+=` and `-=` operators: -Has One Relation ----------------- -Has one relation is a common use-case. Book *has one* author. Book *has one* translator. Getting related row is mainly done by `ref()` method. It accepts two arguments: target table name and source joining column. See example: +After performing the update, the `ActiveRow` is automatically reloaded from the database to reflect any changes made at the database level (e.g., triggers). The method returns `true` only if a real data change occurred. ```php -$book = $explorer->table('book')->get(1); -$book->ref('author', 'author_id'); +$article = $explorer->table('article')->get(1); +$article->update([ + 'views += 1', // increments the view count +]); +echo $article->views; // Outputs the current view count ``` -In example above we fetch related author entry from `author` table, the author primary key is searched by `book.author_id` column. Ref() method returns ActiveRow instance or null if there is no appropriate entry. Returned row is an instance of ActiveRow so we can work with it the same way as with the book entry. +This method updates only one specific row in the database. For bulk updates of multiple rows, use the [#Selection::update()] method. + + +ActiveRow::delete(): int .[method] +---------------------------------- + +Deletes the row from the database represented by the `ActiveRow` object. Returns the number of deleted rows, which should be 1. ```php -$author = $book->ref('author', 'author_id'); -$author->name; -$author->born; +$book = $explorer->table('book')->get(1); +$book->delete(); // Deletes the book with ID 1 +``` + +This method deletes only one specific row in the database. For bulk deletion of multiple rows, use the [#Selection::delete()] method. + + +Relationships Between Tables +============================ + +In relational databases, data is divided into multiple tables and linked together using foreign keys. Nette Database Explorer provides a revolutionary way to work with these relationships – without writing JOIN queries and without needing to configure or generate anything. + +To illustrate working with relationships, we'll use an example book database ([find it on GitHub |https://github.com/nette-examples/books]). In the database, we have tables: + +- `author` – writers and translators (columns `id`, `name`, `web`, `born`) +- `book` – books (columns `id`, `author_id`, `translator_id`, `title`, `sequel_id`) +- `tag` – tags (columns `id`, `name`) +- `book_tag` – junction table between books and tags (columns `book_id`, `tag_id`) + +[* db-schema-1-.webp *] *** Database structure used in examples .<> + +In our example book database, we find several types of relationships (although the model is simplified compared to reality): + +- **One-to-many (1:N)** – Each book **has one** author; an author can write **multiple** books. +- **Zero-to-many (0:N)** – A book **can have** a translator; a translator can translate **multiple** books. +- **Zero-to-one (0:1)** – A book **can have** a sequel. +- **Many-to-many (M:N)** – A book **can have several** tags, and a tag can be assigned to **several** books. + +In these relationships, there is always a **parent table** and a **child table**. For example, in the relationship between authors and books, the `author` table is the parent, and the `book` table is the child – you can think of it as a book always "belonging" to an author. This is also reflected in the database structure: the child table `book` contains the foreign key `author_id`, which references the parent table `author`. + +If we need to list books including their authors' names, we have two options. Either retrieve the data with a single SQL query using JOIN: -// or directly -$book->ref('author', 'author_id')->name; -$book->ref('author', 'author_id')->born; +```sql +SELECT book.*, author.name FROM book LEFT JOIN author ON book.author_id = author.id; ``` -Book also has one translator, so getting translator name is quite easy. +Or retrieve the data in two steps – first the books, then their authors – and then assemble them in PHP: + +```sql +SELECT * FROM book; +SELECT * FROM author WHERE id IN (1, 2, 3); -- IDs of authors from the selected books +``` + +The second approach is actually **more efficient**, although it might be surprising. Data is fetched only once and can be better utilized in the cache. This is precisely how Nette Database Explorer works – it handles everything under the hood and offers you an elegant API: + ```php -$book->ref('author', 'translator_id')->name +$books = $explorer->table('book'); +foreach ($books as $book) { + echo 'title: ' . $book->title; + echo 'written by: ' . $book->author->name; // $book->author is a record from the 'author' table + echo 'translated by: ' . $book->translator?->name; +} ``` -All of this is fine, but it's somewhat cumbersome, don't you think? Database Explorer already contains the foreign keys definitions so why not use them automatically? Let's do that! -If we call property, which does not exist, ActiveRow tries to resolve the calling property name as 'has one' relation. Getting this property is the same as calling ref() method with just one argument. We will call the only argument the **key**. Key will be resolved to particular foreign key relation. The passed key is matched against row columns, and if it matches, foreign key defined on the matched column is used for getting data from related target table. See example: +Accessing the Parent Table +-------------------------- + +Accessing the parent table is straightforward. These are relationships like *a book has an author* or *a book may have a translator*. The related record is obtained via a property of the ActiveRow object – its name corresponds to the name of the foreign key column without the `_id` suffix: ```php -$book->author->name; -// same as -$book->ref('author')->name; +$book = $explorer->table('book')->get(1); +echo $book->author->name; // finds the author based on the author_id column +echo $book->translator?->name; // finds the translator based on the translator_id column ``` -The ActiveRow instance has no author column. All book columns are searched for a match with *key*. Matching in this case means the column name has to contain the key. So in the example above, the `author_id` column contains string 'author' and is therefore matched by 'author' key. If you want to get the book translator, just can use e.g. 'translator' as a key, because 'translator' key will match the `translator_id` column. You can find more about the key matching logic in [Joining expressions |#joining-key] chapter. +When accessing the `$book->author` property, Explorer looks in the `book` table for a column whose name contains the string `author` (i.e., `author_id`). Based on the value in this column, it loads the corresponding record from the `author` table and returns it as an `ActiveRow`. Similarly, `$book->translator` uses the `translator_id` column. Since the `translator_id` column can contain `null`, we use the nullsafe operator `?->` in the code. + +An alternative approach is offered by the `ref()` method, which accepts two arguments: the name of the target table and the name of the joining column, and returns an `ActiveRow` instance or `null`: ```php -echo $book->title . ': '; -echo $book->author->name; -if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; -} +echo $book->ref('author', 'author_id')->name; // relationship to the author +echo $book->ref('author', 'translator_id')->name; // relationship to the translator ``` -If you want to fetch multiple books, you should use the same approach. Nette Database Explorer will fetch authors and translators for all the fetched books at once. +The `ref()` method is useful if property-based access cannot be used, for example, because the table contains a column with the same name (i.e., `author`). In other cases, using property-based access is recommended for better readability. + +Explorer automatically optimizes database queries. When iterating through books in a loop and accessing their related records (authors, translators), Explorer does not generate a query for each book separately. Instead, it performs only **one SELECT query for each type of relationship**, significantly reducing the database load. For example: ```php $books = $explorer->table('book'); foreach ($books as $book) { echo $book->title . ': '; echo $book->author->name; - if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; - } + echo $book->translator?->name; } ``` -The code will run only these 3 queries: +This code executes only these three lightning-fast queries to the database: + ```sql SELECT * FROM `book`; -SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- ids of fetched books from author_id column -SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- ids of fetched books from translator_id column +SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- IDs from the author_id column of selected books +SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- IDs from the translator_id column of selected books ``` +.[note] +The logic for finding the joining column is determined by the implementation of [Conventions |api:Nette\Database\Conventions]. We recommend using [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions], which analyzes foreign keys and allows you to easily work with existing relationships between tables. -Has Many Relation ------------------ -'Has many' relation is just reversed 'has one' relation. Author *has* written *many* books. Author *has* translated *many* books. As you can see, this type of relation is a little bit more difficult because the relation is 'named' ('written', 'translated'). ActiveRow instance has `related()` method, which will return array of related entries. Entries are also ActiveRow instances. See example bellow: +Accessing the Child Table +------------------------- + +Accessing the child table works in the opposite direction. Now we ask *which books did this author write* or *which books did this translator translate*. For this type of query, we use the `related()` method, which returns a `Selection` with related records. Let's look at an example: ```php -$author = $explorer->table('author')->get(11); -echo $author->name . ' has written:'; +$author = $explorer->table('author')->get(1); +// Outputs all books by the author foreach ($author->related('book.author_id') as $book) { - echo $book->title; + echo "Wrote: $book->title"; } -echo 'and translated:'; +// Outputs all books translated by the author foreach ($author->related('book.translator_id') as $book) { - echo $book->title; + echo "Translated: $book->title"; } ``` -Method `related()` method accepts full join description passed as two arguments or as one argument joined by dot. The first argument is the target table, the second is the target column. +The `related()` method accepts the join description as a single argument with dot notation or as two separate arguments: ```php -$author->related('book.translator_id'); -// same as -$author->related('book', 'translator_id'); +$author->related('book.translator_id'); // single argument +$author->related('book', 'translator_id'); // two arguments ``` -You can use Nette Database Explorer heuristics based on foreign keys and provide only **key** argument. Key will be matched against all foreign keys pointing into the current table (`author` table). If there is a match, Nette Database Explorer will use this foreign key, otherwise it will throw [Nette\InvalidArgumentException|api:Nette\InvalidArgumentException] or [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. You can find more about the key matching logic in [Joining expressions |#joining-key] chapter. +Explorer can automatically detect the correct joining column based on the name of the parent table. In this case, it joins via the `book.author_id` column because the name of the source table is `author`: + +```php +$author->related('book'); // uses book.author_id +``` -Of course you can call related methods for all fetched authors, Nette Database Explorer will again fetch the appropriate books at once. +If multiple possible connections exist, Explorer will throw an [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. + +We can, of course, use the `related()` method when iterating through multiple records in a loop, and Explorer will automatically optimize the queries in this case as well: ```php $authors = $explorer->table('author'); foreach ($authors as $author) { - echo $author->name . ' has written:'; + echo $author->name . ' wrote:'; foreach ($author->related('book') as $book) { - $book->title; + echo $book->title; } } ``` -Example above will run only two queries: +This code generates only two lightning-fast SQL queries: ```sql SELECT * FROM `author`; -SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- ids of fetched authors +SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- IDs of the selected authors ``` -Creating Explorer Manually -========================== +Many-to-Many Relationship +------------------------- -A database connection can be created using the application configuration. In such cases a `Nette\Database\Explorer` service is created and can be passed as a dependency using the DI container. +For a many-to-many (M:N) relationship, a **junction table** (in our case, `book_tag`) is needed, containing two foreign key columns (`book_id`, `tag_id`). Each of these columns refers to the primary key of one of the linked tables. To retrieve related data, we first get the records from the junction table using `related('book_tag')` and then proceed to the target data: -However, if Nette Database Explorer is used as a standalone tool, an instance of `Nette\Database\Explorer` object needs to be created manually. +```php +$book = $explorer->table('book')->get(1); +// outputs the names of tags assigned to the book +foreach ($book->related('book_tag') as $bookTag) { + echo $bookTag->tag->name; // outputs the tag name via the junction table +} + +$tag = $explorer->table('tag')->get(1); +// or the opposite way: outputs the names of books marked with this tag +foreach ($tag->related('book_tag') as $bookTag) { + echo $bookTag->book->title; // outputs the book title +} +``` + +Explorer again optimizes the SQL queries into an efficient form: + +```sql +SELECT * FROM `book`; +SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 2, ...)); -- IDs of the selected books +SELECT * FROM `tag` WHERE (`tag`.`id` IN (1, 2, ...)); -- IDs of the tags found in book_tag +``` + + +Querying Through Related Tables +------------------------------- + +In the `where()`, `select()`, `order()`, and `group()` methods, you can use special notations to access columns from other tables. Explorer automatically creates the necessary JOINs. + +**Dot notation** (`parent_table.column`) is used for 1:N relationships from the perspective of the child table: ```php -// $storage implements Nette\Caching\Storage: -$storage = new Nette\Caching\Storages\FileStorage($tempDir); -$connection = new Nette\Database\Connection($dsn, $user, $password); -$structure = new Nette\Database\Structure($connection, $storage); -$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); -$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +$books = $explorer->table('book'); + +// Finds books whose author's name starts with 'Jon' +$books->where('author.name LIKE ?', 'Jon%'); + +// Sorts books by author name descending +$books->order('author.name DESC'); + +// Outputs the book title and author name +$books->select('book.title, author.name'); ``` + +**Colon notation** (`:child_table.column`) is used for 1:N relationships from the perspective of the parent table: + +```php +$authors = $explorer->table('author'); + +// Finds authors who wrote a book with 'PHP' in the title +$authors->where(':book.title LIKE ?', '%PHP%'); + +// Counts the number of books for each author +$authors->select('*, COUNT(:book.id) AS book_count') + ->group('author.id'); +``` + +In the example above with colon notation (`:book.title`), the foreign key column is not specified. Explorer automatically detects the correct column based on the name of the parent table. In this case, it joins via the `book.author_id` column because the name of the source table is `author`. If multiple possible connections exist, Explorer will throw an [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. + +The joining column can be explicitly specified in parentheses: + +```php +// Finds authors who translated a book with 'PHP' in the title +$authors->where(':book(translator_id).title LIKE ?', '%PHP%'); +``` + +Notations can be chained to access data across multiple tables: + +```php +// Finds authors of books tagged with 'PHP' +$authors->where(':book:book_tag.tag.name', 'PHP') + ->group('author.id'); +``` + + +Extending Conditions for JOIN +----------------------------- + +The `joinWhere()` method extends the conditions specified when joining tables in SQL after the `ON` keyword. + +Let's say we want to find books translated by a specific translator: + +```php +// Finds books translated by a translator named 'David' +$books = $explorer->table('book') + ->joinWhere('translator', 'translator.name', 'David'); +// LEFT JOIN author translator ON book.translator_id = translator.id AND (translator.name = 'David') +``` + +In the `joinWhere()` condition, you can use the same constructs as in the `where()` method – operators, placeholders, arrays of values, or SQL expressions. + +For more complex queries with multiple JOINs, you can define table aliases: + +```php +$tags = $explorer->table('tag') + ->joinWhere(':book_tag.book.author', 'book_author.born < ?', 1950) + ->alias(':book_tag.book.author', 'book_author'); +// LEFT JOIN `book_tag` ON `tag`.`id` = `book_tag`.`tag_id` +// LEFT JOIN `book` ON `book_tag`.`book_id` = `book`.`id` +// LEFT JOIN `author` `book_author` ON `book`.`author_id` = `book_author`.`id` +// AND (`book_author`.`born` < 1950) +``` + +Note that while the `where()` method adds conditions to the `WHERE` clause, the `joinWhere()` method extends the conditions in the `ON` clause when joining tables. diff --git a/database/en/guide.texy b/database/en/guide.texy new file mode 100644 index 0000000000..545eb1b656 --- /dev/null +++ b/database/en/guide.texy @@ -0,0 +1,216 @@ +Nette Database +************** + +.[perex] +Nette Database is a powerful and elegant database layer for PHP with a focus on simplicity and smart features. It offers two ways to work with your database: the [Explorer |explorer] for rapid application development, or the [SQL way |SQL way] for direct query manipulation. + +<div class="grid gap-3"> +<div> + + +[SQL way] +========= +- Safe, parameterized queries +- Precise control over SQL query structure +- When writing complex queries with advanced functions +- Optimize performance using specific SQL functions + +</div> + +<div> + + +[Explorer |explorer] +==================== +- Develop quickly without writing SQL +- Intuitive handling of relationships between tables +- Benefit from automatic query optimization +- Suitable for fast and convenient database work + +</div> + +</div> + + +Installation +============ + +Download and install the library using [Composer|best-practices:composer]: + +```shell +composer require nette/database +``` + + +Supported Databases +=================== + +Nette Database supports the following databases: + +|* Database Server |* DSN Name |* Explorer Support +|-----------------------|--------------|-----------------------| +| MySQL (>= 5.1) | mysql | YES | +| PostgreSQL (>= 9.0) | pgsql | YES | +| SQLite 3 (>= 3.8) | sqlite | YES | +| Oracle | oci | NO | +| MS SQL (PDO_SQLSRV) | sqlsrv | YES | +| MS SQL (PDO_DBLIB) | mssql | NO | +| ODBC | odbc | NO | + + +Two Approaches to Database Work +=============================== + +Nette Database gives you a choice: you can either write SQL queries directly (SQL way), or let them be generated automatically (Explorer). Let's see how both approaches handle the same tasks: + +[SQL way|sql-way] - SQL Queries + +```php +// Insert a record +$database->query('INSERT INTO books', [ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// Retrieve records: book authors +$result = $database->query(' + SELECT authors.*, COUNT(books.id) AS books_count + FROM authors + LEFT JOIN books ON authors.id = books.author_id + WHERE authors.active = 1 + GROUP BY authors.id +'); + +// Display (not optimal, generates N additional queries) +foreach ($result as $author) { + $books = $database->query(' + SELECT * FROM books + WHERE author_id = ? + ORDER BY published_at DESC + ', $author->id); + + echo "Author $author->name has written $author->books_count books:\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +[Explorer way|explorer] - Automatic SQL Generation + +```php +// Insert a record +$database->table('books')->insert([ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// Retrieve records: book authors +$authors = $database->table('authors') + ->where('active', 1); + +// Display (automatically generates only 2 optimized queries) +foreach ($authors as $author) { + $books = $author->related('books') + ->order('published_at DESC'); + + echo "Author $author->name has written {$books->count()} books:\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +The Explorer approach generates and optimizes SQL queries automatically. In the example above, the SQL way generates N+1 queries (one for authors and then one for the books of each author), while Explorer automatically optimizes queries and executes only two — one for authors and one for all their books. + +Both approaches can be freely combined in your application as needed. + + +Connection and Configuration +============================ + +To connect to the database, simply create an instance of the [api:Nette\Database\Connection] class: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password); +``` + +The `$dsn` (Data Source Name) parameter is the same as [used by PDO |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], e.g., `host=127.0.0.1;dbname=test`. In case of failure, it throws a `Nette\Database\ConnectionException`. + +However, a more convenient method is offered by [application configuration |configuration], where you just need to add a `database` section. This creates the necessary objects and also a database panel in the [Tracy |tracy:] bar. + +```neon +database: + dsn: 'mysql:host=127.0.0.1;dbname=test' + user: root + password: password +``` + +Then, the connection object can be [obtained as a service from the DI container |dependency-injection:passing-dependencies], e.g.: + +```php +class Model +{ + public function __construct( + // or Nette\Database\Explorer + private Nette\Database\Connection $database, + ) { + } +} +``` + +More information about [database configuration|configuration]. + + +Manual Creation of Explorer +--------------------------- + +If you are not using the Nette DI container, you can create an instance of `Nette\Database\Explorer` manually: + +```php +// database connection +$connection = new Nette\Database\Connection('mysql:host=127.0.0.1;dbname=mydatabase', 'user', 'password'); +// cache storage, implements Nette\Caching\Storage, e.g.: +$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp/dir'); +// handles database structure reflection +$structure = new Nette\Database\Structure($connection, $storage); +// defines rules for mapping table names, columns, and foreign keys +$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); +$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +``` + + +Connection Management +===================== + +When a `Connection` object is created, the connection is automatically established. If you want to delay the connection, use lazy mode - enable it in the [configuration|configuration] by setting `lazy`, or like this: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password, ['lazy' => true]); +``` + +To manage the connection, use the `connect()`, `disconnect()`, and `reconnect()` methods. +- `connect()` creates a connection if one does not already exist, and may throw a `Nette\Database\ConnectionException`. +- `disconnect()` disconnects the current database connection. +- `reconnect()` performs a disconnection and subsequent reconnection to the database. This method may also throw a `Nette\Database\ConnectionException`. + +Additionally, you can monitor events related to the connection using the `onConnect` event, which is an array of callbacks called after the database connection is established. + +```php +// runs after connecting to the database +$database->onConnect[] = function($database) { + echo "Connected to the database"; +}; +``` + + +Tracy Debug Bar +=============== + +If you use [Tracy |tracy:], the Database panel in the Debug Bar is automatically activated. It displays all executed queries, their parameters, execution time, and the location in the code where they were called. + +[* db-panel.webp *] diff --git a/database/en/mapping.texy b/database/en/mapping.texy new file mode 100644 index 0000000000..3a4162363d --- /dev/null +++ b/database/en/mapping.texy @@ -0,0 +1,55 @@ +Type Conversion +*************** + +.[perex] +Nette Database automatically converts values returned from the database to the corresponding PHP types. + + +Date and Time +------------- + +Time values are converted to `Nette\Utils\DateTime` objects. If you want time values to be converted to immutable `Nette\Database\DateTime` objects, set the `newDateTime` option to true in the [configuration |configuration]. + +```php +$row = $database->fetch('SELECT created_at FROM articles'); +echo $row->created_at instanceof DateTime; // true +echo $row->created_at->format('j. n. Y'); +``` + +In the case of MySQL, the `TIME` data type is converted to `DateInterval` objects. + + +Boolean Values +-------------- + +Boolean values are automatically converted to `true` or `false`. For MySQL, `TINYINT(1)` is converted if we set `convertBoolean` in the [configuration |configuration]. + +```php +$row = $database->fetch('SELECT is_published FROM articles'); +echo gettype($row->is_published); // 'boolean' +``` + + +Numeric Values +-------------- + +Numeric values are converted to `int` or `float` according to the column type in the database: + +```php +$row = $database->fetch('SELECT id, price FROM products'); +echo gettype($row->id); // integer +echo gettype($row->price); // float +``` + + +Custom Normalization +-------------------- + +Using the `setRowNormalizer(?callable $normalizer)` method, you can set a custom function for transforming rows from the database. This is useful, for example, for automatic data type conversion. + +```php +$database->setRowNormalizer(function(array $row, ResultSet $resultSet): array { + // type conversion happens here + return $row; +}); +``` diff --git a/database/en/reflection.texy b/database/en/reflection.texy new file mode 100644 index 0000000000..c8fdafd987 --- /dev/null +++ b/database/en/reflection.texy @@ -0,0 +1,125 @@ +Structure Reflection +******************** + +.{data-version:3.2.1} +Nette Database provides tools for database structure introspection using the [api:Nette\Database\Reflection] class. It allows obtaining information about tables, columns, indexes, and foreign keys. You can use reflection to generate schemas, create flexible applications working with the database, or general database tools. + +The reflection object is obtained from the database connection instance: + +```php +$reflection = $database->getReflection(); +``` + + +Retrieving Tables +----------------- + +The readonly property `$reflection->tables` contains an associative array of all tables in the database: + +```php +// Listing the names of all tables +foreach ($reflection->tables as $name => $table) { + echo $name . "\n"; +} +``` + +Two additional methods are available: + +```php +// Checking the existence of a table +if ($reflection->hasTable('users')) { + echo "Table users exists"; +} + +// Returns the table object; throws an exception if it does not exist +$table = $reflection->getTable('users'); +``` + + +Table Information +----------------- + +A table is represented by the [Table|api:Nette\Database\Reflection\Table] object, which provides the following readonly properties: + +- `$name: string` – name of the table +- `$view: bool` – whether it is a view +- `$fullName: ?string` – full name of the table including schema (if exists) +- `$columns: array<string, Column>` – associative array of table columns +- `$indexes: Index[]` – array of table indexes +- `$primaryKey: ?Index` – primary key of the table or null +- `$foreignKeys: ForeignKey[]` – array of table foreign keys + + +Columns +------- + +The `columns` property of the table provides an associative array of columns, where the key is the column name and the value is an instance of [Column|api:Nette\Database\Reflection\Column] with these properties: + +- `$name: string` – name of the column +- `$table: ?Table` – reference to the column's table +- `$nativeType: string` – native database type +- `$size: ?int` – size/length of the type +- `$nullable: bool` – whether the column can contain NULL +- `$default: mixed` – default value of the column +- `$autoIncrement: bool` – whether the column is auto-increment +- `$primary: bool` – whether it is part of the primary key +- `$vendor: array` – additional metadata specific to the given database system + +```php +foreach ($table->columns as $name => $column) { + echo "Column: $name\n"; + echo "Type: {$column->nativeType}\n"; + echo "Nullable: " . ($column->nullable ? 'Yes' : 'No') . "\n"; +} +``` + + +Indexes +------- + +The `indexes` property of the table provides an array of indexes, where each index is an instance of [Index|api:Nette\Database\Reflection\Index] with these properties: + +- `$columns: Column[]` – array of columns forming the index +- `$unique: bool` – whether the index is unique +- `$primary: bool` – whether it is a primary key +- `$name: ?string` – name of the index + +The primary key of the table can be obtained using the `primaryKey` property, which returns either an `Index` object or `null` if the table does not have a primary key. + +```php +// Listing indexes +foreach ($table->indexes as $index) { + $columns = implode(', ', array_map(fn($col) => $col->name, $index->columns)); + echo "Index" . ($index->name ? " {$index->name}" : '') . ":\n"; + echo " Columns: $columns\n"; + echo " Unique: " . ($index->unique ? 'Yes' : 'No') . "\n"; +} + +// Listing the primary key +if ($primaryKey = $table->primaryKey) { + $columns = implode(', ', array_map(fn($col) => $col->name, $primaryKey->columns)); + echo "Primary Key: $columns\n"; +} +``` + + +Foreign Keys +------------ + +The `foreignKeys` property of the table provides an array of foreign keys, where each foreign key is an instance of [ForeignKey|api:Nette\Database\Reflection\ForeignKey] with these properties: + +- `$foreignTable: Table` – the referenced table +- `$localColumns: Column[]` – array of local columns +- `$foreignColumns: Column[]` – array of referenced columns +- `$name: ?string` – name of the foreign key + +```php +// Listing foreign keys +foreach ($table->foreignKeys as $fk) { + $localCols = implode(', ', array_map(fn($col) => $col->name, $fk->localColumns)); + $foreignCols = implode(', ', array_map(fn($col) => $col->name, $fk->foreignColumns)); + + echo "FK" . ($fk->name ? " {$fk->name}" : '') . ":\n"; + echo " $localCols -> {$fk->foreignTable->name}($foreignCols)\n"; +} +``` diff --git a/database/en/security.texy b/database/en/security.texy new file mode 100644 index 0000000000..2632e630a1 --- /dev/null +++ b/database/en/security.texy @@ -0,0 +1,185 @@ +Security Risks +************** + +<div class=perex> + +Databases often contain sensitive data and allow performing dangerous operations. For secure work with Nette Database, it is crucial to: + +- Understand the difference between secure and insecure APIs +- Use parameterized queries +- Properly validate input data + +</div> + + +What is SQL Injection? +====================== + +SQL injection is the most serious security risk when working with databases. It occurs when unsanitized user input becomes part of an SQL query. An attacker can insert their own SQL commands and thereby: +- Gain unauthorized access to data +- Modify or delete data in the database +- Bypass authentication + +```php +// ❌ DANGEROUS CODE - vulnerable to SQL injection +$database->query("SELECT * FROM users WHERE name = '$_GET[name]'"); + +// An attacker might enter a value like: ' OR '1'='1 +// The resulting query would be: SELECT * FROM users WHERE name = '' OR '1'='1' +// Which returns all users +``` + +The same applies to Database Explorer: + +```php +// ❌ DANGEROUS CODE - vulnerable to SQL injection +$table->where('name = ' . $_GET['name']); +$table->where("name = '$_GET[name]'"); +``` + + +Parameterized Queries +===================== + +The fundamental defense against SQL injection is parameterized queries. Nette Database offers several ways to use them. + +The simplest way is to use **question mark placeholders**: + +```php +// ✅ Secure parameterized query +$database->query('SELECT * FROM users WHERE name = ?', $name); + +// ✅ Secure condition in Explorer +$table->where('name = ?', $name); +``` + +This applies to all other methods in [Database Explorer|explorer] that allow inserting expressions with question mark placeholders and parameters. + +For INSERT, UPDATE commands, or the WHERE clause, we can pass values in an array: + +```php +// ✅ Secure INSERT +$database->query('INSERT INTO users', [ + 'name' => $name, + 'email' => $email, +]); + +// ✅ Secure INSERT in Explorer +$table->insert([ + 'name' => $name, + 'email' => $email, +]); +``` + + +Parameter Value Validation +========================== + +Parameterized queries are the cornerstone of secure database work. However, the values we insert into them must undergo several levels of checks: + + +Type Checking +------------- + +**The most important thing is to ensure the correct data type of parameters** – this is a necessary condition for the safe use of Nette Database. The database assumes that all input data has the correct data type corresponding to the given column. + +For example, if `$name` in the previous examples were unexpectedly an array instead of a string, Nette Database would try to insert all its elements into the SQL query, leading to an error. Therefore, **never use** unvalidated data from `$_GET`, `$_POST`, or `$_COOKIE` directly in database queries. + + +Format Validation +----------------- + +At the second level, we check the format of the data – for example, whether strings are in UTF-8 encoding and their length corresponds to the column definition, or whether numerical values are within the allowed range for the given column data type. + +For this level of validation, we can partially rely on the database itself – many databases will reject invalid data. However, behavior can vary; some might silently truncate long strings or clip numbers outside the range. + + +Domain-Specific Validation +-------------------------- + +The third level involves logical checks specific to your application. For example, verifying that values from select boxes match the offered options, that numbers are within the expected range (e.g., age 0-150 years), or that mutual dependencies between values make sense. + + +Recommended Validation Methods +------------------------------ + +- Use [Nette Forms|forms:], which automatically ensure proper validation of all inputs. +- Use [Presenters|application:] and specify data types for parameters in `action*()` and `render*()` methods. +- Or implement your own validation layer using standard PHP tools like `filter_var()`. + + +Safe Work with Columns +====================== + +In the previous section, we showed how to properly validate parameter values. However, when using arrays in SQL queries, we must pay equal attention to their keys. + +```php +// ❌ DANGEROUS CODE - keys in the array are not sanitized +$database->query('INSERT INTO users', $_POST); +``` + +For INSERT and UPDATE commands, this is a critical security flaw – an attacker can insert or modify any column in the database. They could, for example, set `is_admin = 1` or insert arbitrary data into sensitive columns (the so-called Mass Assignment Vulnerability). + +In WHERE conditions, it is even more dangerous because they can contain operators: + +```php +// ❌ DANGEROUS CODE - keys in the array are not sanitized +$_POST['salary >'] = 100000; +$database->query('SELECT * FROM users WHERE', $_POST); +// executes the query WHERE (`salary` > 100000) +``` + +An attacker can use this approach to systematically discover employee salaries. They might start, for example, with a query for salaries above 100,000, then below 50,000, and by gradually narrowing the range, they can reveal the approximate salaries of all employees. This type of attack is called SQL enumeration. + +The `where()` and `whereOr()` methods are even [much more flexible |explorer#where] and support SQL expressions, including operators and functions, in keys and values. This gives an attacker the ability to perform SQL injection: + +```php +// ❌ DANGEROUS CODE - attacker can insert their own SQL +$_POST = ['0) UNION SELECT name, salary FROM users WHERE (1']; +$table->where($_POST); +// executes the query WHERE (0) UNION SELECT name, salary FROM users WHERE (1) +``` + +This attack terminates the original condition using `0)`, appends its own `SELECT` using `UNION` to obtain sensitive data from the `users` table, and closes the syntactically correct query using `WHERE (1)`. + + +Column Whitelist +---------------- + +For safe work with column names, we need a mechanism that ensures the user can only work with allowed columns and cannot add their own. We could try to detect and block dangerous column names (blacklist), but this approach is unreliable – an attacker can always come up with a new way to write a dangerous column name that we didn't anticipate. + +Therefore, it is much safer to reverse the logic and define an explicit list of allowed columns (whitelist): + +```php +// Columns the user is allowed to modify +$allowedColumns = ['name', 'email', 'active']; + +// Remove all unauthorized columns from the input +$filteredData = array_intersect_key($userData, array_flip($allowedColumns)); + +// ✅ Now safe to use in queries, such as: +$database->query('INSERT INTO users', $filteredData); +$table->update($filteredData); +$table->where($filteredData); +``` + + +Dynamic Identifiers +=================== + +For dynamic table and column names, use the `?name` placeholder. This ensures proper escaping of identifiers according to the syntax of the given database (e.g., using backticks in MySQL): + +```php +// ✅ Safe use of trusted identifiers +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name', $column, $table); +// Result in MySQL: SELECT `name` FROM `users` +``` + +Important: Use the `?name` symbol only for trusted values defined in the application code. For values from the user, use a [whitelist |#Column Whitelist] again. Otherwise, you expose yourself to security risks: + +```php +// ❌ DANGEROUS - never use user input +$database->query('SELECT ?name FROM users', $_GET['column']); +``` diff --git a/database/en/sql-way.texy b/database/en/sql-way.texy new file mode 100644 index 0000000000..f447354e3b --- /dev/null +++ b/database/en/sql-way.texy @@ -0,0 +1,513 @@ +SQL Way +******* + +.[perex] +Nette Database offers two ways of working: you can write SQL queries yourself (SQL way), or have them generated automatically (see [Explorer |explorer]). The SQL way gives you full control over the queries while ensuring they are constructed securely. + +.[note] +Details on database connection and configuration can be found in the [Connection and Configuration |guide#Connection and Configuration] chapter. + + +Basic Querying +============== + +The `query()` method is used for database querying. It returns a [ResultSet |api:Nette\Database\ResultSet] object, which represents the query result. If the query fails, the method [throws an exception|exceptions]. You can iterate through the query result using a `foreach` loop, or use one of the [helper methods |#Fetching Data]. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; +} +``` + +To securely insert values into SQL queries, use parameterized queries. Nette Database makes this extremely simple: just add a comma and the value after the SQL query: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +With multiple parameters, you have two options: You can either interleave the SQL query with parameters: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age); +``` + +Or write the entire SQL query first and then append all the parameters: + +```php +$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age); +``` + + +Protection Against SQL Injection +================================ + +Why is it important to use parameterized queries? Because they protect you from an attack called SQL injection, where an attacker could inject their own SQL commands and thereby gain access to or damage data in the database. + +.[warning] +**Never insert variables directly into an SQL query!** Always use parameterized queries, which protect you from SQL injection. + +```php +// ❌ DANGEROUS CODE - vulnerable to SQL injection +$database->query("SELECT * FROM users WHERE name = '$name'"); + +// ✅ Secure parameterized query +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Familiarize yourself with [potential security risks |security]. + + +Querying Techniques +=================== + + +WHERE Conditions +---------------- + +You can write `WHERE` conditions as an associative array, where keys are column names and values are the data for comparison. Nette Database automatically selects the most suitable SQL operator based on the value type. + +```php +$database->query('SELECT * FROM users WHERE', [ + 'name' => 'John', + 'active' => true, +]); +// WHERE `name` = 'John' AND `active` = 1 +``` + +You can also explicitly specify the comparison operator in the key: + +```php +$database->query('SELECT * FROM users WHERE', [ + 'age >' => 25, // uses the > operator + 'name LIKE' => '%John%', // uses the LIKE operator + 'email NOT LIKE' => '%example.com%', // uses the NOT LIKE operator +]); +// WHERE `age` > 25 AND `name` LIKE '%John%' AND `email` NOT LIKE '%example.com%' +``` + +Nette automatically handles special cases like `null` values or arrays. + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name' => 'Laptop', // uses the = operator + 'category_id' => [1, 2, 3], // uses IN + 'description' => null, // uses IS NULL +]); +// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL +``` + +For negative conditions, use the `NOT` operator: + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name NOT' => 'Laptop', // uses the <> operator + 'category_id NOT' => [1, 2, 3], // uses NOT IN + 'description NOT' => null, // uses IS NOT NULL + 'id' => [], // skipped +]); +// WHERE `name` <> 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL +``` + +By default, conditions are joined using the `AND` operator. This can be changed using the [?or placeholder |#SQL Construction Hints]. + + +ORDER BY Rules +-------------- + +The `ORDER BY` clause can be written using an array. Specify columns in the keys, and use a boolean value to indicate ascending (`true`) or descending (`false`) order: + +```php +$database->query('SELECT id FROM author ORDER BY', [ + 'id' => true, // ascending + 'name' => false, // descending +]); +// SELECT id FROM author ORDER BY `id`, `name` DESC +``` + + +Inserting Data (INSERT) +----------------------- + +The SQL `INSERT` command is used for inserting records. + +```php +$values = [ + 'name' => 'John Doe', + 'email' => 'john@example.com', +]; +$database->query('INSERT INTO users ?', $values); +$userId = $database->getInsertId(); +``` + +The `getInsertId()` method returns the ID of the last inserted row. For some databases (e.g., PostgreSQL), it is necessary to specify the name of the sequence from which the ID should be generated as a parameter, using `$database->getInsertId($sequenceId)`. + +You can also pass [#special values], such as files, DateTime objects, or enum types, as parameters. + +Inserting multiple records at once: + +```php +$database->query('INSERT INTO users ?', [ + ['name' => 'User 1', 'email' => 'user1@mail.com'], + ['name' => 'User 2', 'email' => 'user2@mail.com'], +]); +``` + +A multi-record INSERT is much faster because only a single database query is executed, instead of many individual ones. + +**Security Note:** Never use unvalidated data as `$values`. Familiarize yourself with [possible risks |security#Safe Work with Columns]. + + +Updating Data (UPDATE) +---------------------- + +The SQL `UPDATE` command is used for updating records. + +```php +// Update a single record +$values = [ + 'name' => 'John Smith', +]; +$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1); +``` + +The number of affected rows is returned by `$result->getRowCount()`. + +For `UPDATE`, we can use the `+=` and `-=` operators: + +```php +$database->query('UPDATE users SET ? WHERE id = ?', [ + 'login_count+=' => 1, // increment login_count +], 1); +``` + +Example of inserting or updating a record if it already exists. We use the `ON DUPLICATE KEY UPDATE` technique: + +```php +$values = [ + 'name' => $name, + 'year' => $year, +]; +$database->query('INSERT INTO users ? ON DUPLICATE KEY UPDATE ?', + $values + ['id' => $id], + $values, +); +// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) +// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 +``` + +Notice that Nette Database recognizes the context in which an array parameter is used within the SQL command and constructs the SQL code accordingly. So, from the first array, it constructed `(id, name, year) VALUES (123, 'Jim', 1978)`, while it converted the second into the form `name = 'Jim', year = 1978`. We discuss this in more detail in the section [#SQL Construction Hints]. + + +Deleting Data (DELETE) +---------------------- + +The SQL `DELETE` command is used for deleting records. Example of obtaining the number of deleted rows: + +```php +$count = $database->query('DELETE FROM users WHERE id = ?', 1) + ->getRowCount(); +``` + + +SQL Construction Hints +---------------------- + +A hint is a special placeholder in an SQL query that specifies how the parameter value should be converted into an SQL expression: + +| Hint | Description | Automatically Used For +|-----------|-------------------------------------------------|----------------------------- +| `?name` | Used for inserting table or column names | - +| `?values` | Generates `(key, ...) VALUES (value, ...)` | `INSERT ... ?`, `REPLACE ... ?` +| `?set` | Generates assignments `key = value, ...` | `SET ?`, `KEY UPDATE ?` +| `?and` | Joins conditions in an array with `AND` | `WHERE ?`, `HAVING ?` +| `?or` | Joins conditions in an array with `OR` | - +| `?order` | Generates the `ORDER BY` clause | `ORDER BY ?`, `GROUP BY ?` + +The `?name` placeholder is used for dynamically inserting table and column names into the query. Nette Database handles the correct quoting of identifiers according to the database conventions (e.g., enclosing in backticks in MySQL). + +```php +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table); +// SELECT `name` FROM `users` WHERE id = 1 (in MySQL) +``` + +**Warning:** Only use the `?name` placeholder for validated table and column names. Otherwise, you risk [security vulnerabilities |security#Dynamic Identifiers]. + +Other hints usually do not need to be specified, as Nette uses smart autodetection when constructing the SQL query (see the third column of the table). But you can use it, for example, in a situation where you want to join conditions using `OR` instead of `AND`: + +```php +$database->query('SELECT * FROM users WHERE ?or', [ + 'name' => 'John', + 'email' => 'john@example.com', +]); +// SELECT * FROM users WHERE `name` = 'John' OR `email` = 'john@example.com' +``` + + +Special Values +-------------- + +In addition to common scalar types (string, int, bool), you can also pass special values as parameters: + +- files: `fopen('image.gif', 'r')` inserts the binary content of the file +- date and time: `DateTime` and `DateTimeImmutable` objects are converted to the database format +- enum types: instances of `enum` are converted to their value +- SQL literals: created using `Connection::literal('NOW()')` are inserted directly into the query + +```php +$database->query('INSERT INTO articles ?', [ + 'title' => 'My Article', + 'published_at' => new DateTimeImmutable, // or new DateTime + 'content' => fopen('image.png', 'r'), + 'state' => Status::Draft, +]); +``` + +For databases that do not have native support for the `datetime` data type (like SQLite and Oracle), `DateTime` and `DateTimeImmutable` objects are converted to a value specified in the [database configuration|configuration] by the `formatDateTime` item (default value is `U` - Unix timestamp). + + +SQL Literals +------------ + +In some cases, you need to pass raw SQL code as a value, which should not be treated as a string and escaped. Objects of the `Nette\Database\SqlLiteral` class are used for this purpose. They are created by the `Connection::literal()` method. + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + 'year >' => $database::literal('YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) +``` + +Alternatively: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) +``` + +SQL literals can contain parameters: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > ? AND year < ?', $min, $max), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) +``` + +This allows for interesting combinations: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('?or', [ + 'active' => true, + 'role' => $role, + ]), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') +``` + + +Fetching Data +============= + + +Shortcuts for SELECT Queries +---------------------------- + +To simplify data retrieval, `Connection` offers several shortcuts that combine a `query()` call with a subsequent `fetch*()` call. These methods accept the same parameters as `query()`, i.e., an SQL query and optional parameters. A full description of the `fetch*()` methods can be found [below |#fetch]. + +| `fetch($sql, ...$params): ?Row` | Executes the query and returns the first row as a `Row` object or `null`. +| `fetchAll($sql, ...$params): array` | Executes the query and returns all rows as an array of `Row` objects. +| `fetchPairs($sql, ...$params): array` | Executes the query and returns an associative array (key => value pairs). +| `fetchField($sql, ...$params): mixed` | Executes the query and returns the value of the first column in the first row. +| `fetchList($sql, ...$params): ?array` | Executes the query and returns the first row as an indexed array or `null`. + +Example: + +```php +// fetchField() - returns the value of the first cell +$count = $database->query('SELECT COUNT(*) FROM articles') + ->fetchField(); +``` + + +`foreach` - Iterating Over Rows +------------------------------- + +After executing a query, a [ResultSet|api:Nette\Database\ResultSet] object is returned, which allows iterating through the results in several ways. The easiest way to execute a query and retrieve rows is by iterating in a `foreach` loop. This method is the most memory-efficient, as it fetches data row by row and does not load the entire result set into memory at once. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; + // ... +} +``` + +.[note] +The `ResultSet` can only be iterated once. If you need to iterate repeatedly, you must first load the data into an array, for example, using the `fetchAll()` method. + + +fetch(): ?Row .[method] +----------------------- + +Returns a row as a `Row` object. If no more rows exist, it returns `null`. Advances the internal pointer to the next row. + +```php +$result = $database->query('SELECT * FROM users'); +$row = $result->fetch(); // loads the first row +if ($row) { + echo $row->name; +} +``` + + +fetchAll(): array .[method] +--------------------------- + +Returns all remaining rows from the `ResultSet` as an array of `Row` objects. + +```php +$result = $database->query('SELECT * FROM users'); +$rows = $result->fetchAll(); // loads all rows +foreach ($rows as $row) { + echo $row->name; +} +``` + + +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Returns the result set as an associative array. The first argument specifies the column to use as keys, and the second argument specifies the column to use as values: + +```php +$result = $database->query('SELECT id, name FROM users'); +$names = $result->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] +``` + +If only the first parameter (`$key`) is provided, the entire row (`Row` object) will be used as the value: + +```php +$rows = $result->fetchPairs('id'); +// [1 => Row(id: 1, name: 'John'), 2 => Row(id: 2, name: 'Jane'), ...] +``` + +In case of duplicate keys, the value from the last row is used. Using `null` as the key results in a numerically indexed array (starting from zero), preventing key collisions: + +```php +$names = $result->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] +``` + + +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Alternatively, you can provide a callback that processes each row. The callback can return a single value or a key-value pair. + +```php +$result = $database->query('SELECT * FROM users'); +$items = $result->fetchPairs(fn($row) => "$row->id - $row->name"); +// ['1 - John', '2 - Jane', ...] + +// The callback can also return an array with a key & value pair: +$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]); +// ['John' => 46, 'Jane' => 21, ...] +``` + + +fetchField(): mixed .[method] +----------------------------- + +Returns the value of the first column from the current row. If no more rows exist, it returns `null`. Advances the internal pointer to the next row. + +```php +$result = $database->query('SELECT name FROM users'); +$name = $result->fetchField(); // loads the name from the first row +``` + + +fetchList(): ?array .[method] +----------------------------- + +Returns the row as an indexed array. If no more rows exist, it returns `null`. Advances the internal pointer to the next row. + +```php +$result = $database->query('SELECT name, email FROM users'); +$row = $result->fetchList(); // ['John', 'john@example.com'] +``` + + +getRowCount(): ?int .[method] +----------------------------- + +Returns the number of affected rows from the last `UPDATE` or `DELETE` query. For `SELECT` queries, it returns the number of rows in the result set. However, this might not always be known, in which case the method returns `null`. + + +getColumnCount(): ?int .[method] +-------------------------------- + +Returns the number of columns in the `ResultSet`. + + +Query Information +================= + +For debugging purposes, we can obtain information about the last executed query: + +```php +echo $database->getLastQueryString(); // prints the SQL query + +$result = $database->query('SELECT * FROM articles'); +echo $result->getQueryString(); // prints the SQL query +echo $result->getTime(); // prints the execution time in seconds +``` + +To display the result as an HTML table, you can use: + +```php +$result = $database->query('SELECT * FROM articles'); +$result->dump(); +``` + +`ResultSet` provides information about column types: + +```php +$result = $database->query('SELECT * FROM articles'); +$types = $result->getColumnTypes(); + +foreach ($types as $column => $type) { + echo "$column is of type $type->type"; // e.g., 'id is of type int' +} +``` + + +Query Logging +------------- + +We can implement custom query logging. The `onQuery` event is an array of callbacks that are called after each executed query: + +```php +$database->onQuery[] = function ($database, $result) use ($logger) { + $logger->info('Query: ' . $result->getQueryString()); + $logger->info('Time: ' . $result->getTime()); + + if ($result->getRowCount() > 1000) { + $logger->warning('Large result set: ' . $result->getRowCount() . ' rows'); + } +}; +``` diff --git a/database/en/transactions.texy b/database/en/transactions.texy new file mode 100644 index 0000000000..36b455a663 --- /dev/null +++ b/database/en/transactions.texy @@ -0,0 +1,43 @@ +Transactions +************ + +.[perex] +Transactions guarantee that either all operations within the transaction are executed, or none are. They are useful for ensuring data consistency during complex operations. + +The simplest way to use transactions looks like this: + +```php +$database->beginTransaction(); +try { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); + $database->commit(); +} catch (\Exception $e) { + $database->rollBack(); + throw $e; +} +``` + +You can achieve the same result much more elegantly using the `transaction()` method. It accepts a callback which is executed within the transaction. If the callback runs without an exception, the transaction is automatically committed. If an exception occurs, the transaction is rolled back, and the exception is propagated further. + +```php +$database->transaction(function ($database) use ($id) { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); +}); +``` + +The `transaction()` method can also return values: + +```php +$count = $database->transaction(function ($database) { + $result = $database->query('UPDATE users SET active = ?', true); + return $result->getRowCount(); // returns the number of updated rows +}); +``` diff --git a/database/en/upgrading.texy b/database/en/upgrading.texy new file mode 100644 index 0000000000..7bf5fa7f43 --- /dev/null +++ b/database/en/upgrading.texy @@ -0,0 +1,14 @@ +Upgrading +********* + + +Migrating from 3.1 to 3.2 +========================= + +The minimum required PHP version is 8.1. + +The code has been carefully tuned for PHP 8.1. All new type hints for methods and properties have been added. The changes are minor: + +- MySQL: zero date `0000-00-00` is returned as `null` +- MySQL: decimal without decimal places is returned as int instead of float +- The `time` type is returned as a `DateTimeImmutable` object with the date set to `0001-01-01` instead of the current date diff --git a/database/es/@home.texy b/database/es/@home.texy index 41871a890b..847f8e4d76 100644 --- a/database/es/@home.texy +++ b/database/es/@home.texy @@ -1,20 +1,21 @@ -Servidores compatibles -====================== +Bases de datos compatibles +========================== -Estos servidores de bases de datos son compatibles: +Nette soporta las siguientes bases de datos: -|* Servidor de base de datos |* Nombre DSN |* Soporte Core |* Soporte Explorer -| MySQL (>= 5.1) | mysql | SÍ | SÍ -| PostgreSQL (>= 9.0) | pgsql | SÍ | SÍ -| Sqlite 3 (>= 3.8) | sqlite | SÍ | SÍ -| Oracle | oci | SÍ | - -| MS SQL (PDO_SQLSRV) | sqlsrv | SÍ | SÍ -| MS SQL (PDO_DBLIB) | mssql | YES | - -| ODBC (PDO_DBLIB) (odbc) (SÍ) (-) +|* Servidor de base de datos |* Nombre DSN |* Soporte en Core |* Soporte en Explorer +| MySQL (>= 5.1) | mysql | SÍ | SÍ +| PostgreSQL (>= 9.0) | pgsql | SÍ | SÍ +| Sqlite 3 (>= 3.8) | sqlite | SÍ | SÍ +| Oracle | oci | SÍ | - +| MS SQL (PDO_SQLSRV) | sqlsrv | SÍ | SÍ +| MS SQL (PDO_DBLIB) | mssql | SÍ | - +| ODBC | odbc | SÍ | - -{{title: Nette Database}} -{{description: Nette Database simplifica la recuperación de datos de la base de datos sin necesidad de escribir consultas SQL. Realiza consultas eficaces y no transfiere datos innecesarios.}} + +{{maintitle: Nette Database - awesome database layer for PHP}} +{{description: Nette Database simplifica radicalmente la obtención de datos de la base de datos sin necesidad de escribir consultas SQL. Realiza consultas eficientes y no transfiere datos innecesarios.}} diff --git a/database/es/@left-menu.texy b/database/es/@left-menu.texy index c1919e7923..334a1f0ecd 100644 --- a/database/es/@left-menu.texy +++ b/database/es/@left-menu.texy @@ -1,5 +1,12 @@ -Base de datos -************* -- [Núcleo |Core] -- [Explorador |Explorer] -- [Configuración |Configuration] +Nette Database +************** +- [Introducción |guide] +- [Acceso SQL |sql way] +- [Explorer |Explorer] +- [Transacciones |transactions] +- [Excepciones |exceptions] +- [Reflexión |reflection] +- [Mapeo |mapping] +- [Configuración |configuration] +- [Riesgos de seguridad |security] +- [Actualización |en:upgrading] diff --git a/database/es/@meta.texy b/database/es/@meta.texy new file mode 100644 index 0000000000..1670b124ad --- /dev/null +++ b/database/es/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Documentación}} diff --git a/database/es/configuration.texy b/database/es/configuration.texy index e0295c6d46..17f31d5bf7 100644 --- a/database/es/configuration.texy +++ b/database/es/configuration.texy @@ -2,60 +2,66 @@ Configuración de la base de datos ********************************* .[perex] -Visión general de las opciones de configuración de la Base de Datos Nette. +Resumen de las opciones de configuración para Nette Database. -Si no está utilizando todo el framework, sino sólo esta librería, lea [cómo cargar la configuración |bootstrap:]. +Si no está utilizando todo el framework, sino sólo esta librería, lea [cómo cargar la configuración|bootstrap:]. -Conexión única .[#toc-single-connection] ----------------------------------------- +Una conexión +------------ -Configurar una única conexión a la base de datos: +Configuración de una única conexión a la base de datos: ```neon database: - # DSN, only mandatory key + # DSN, la única clave obligatoria dsn: "sqlite:%appDir%/Model/demo.db" user: ... password: ... ``` -Crea servicios del tipo `Nette\Database\Connection` y también `Nette\Database\Explorer` para la capa [Database Explorer |explorer]. La conexión a la base de datos se suele pasar por [autocableado |dependency-injection:autowiring]; si no es posible, utiliza los nombres de servicio `@database.default.connection` resp. `@database.default.explorer`. +Crea los servicios `Nette\Database\Connection` y `Nette\Database\Explorer`, que normalmente pasamos mediante [autowiring |dependency-injection:autowiring], o por referencia a [su nombre |#Servicios DI]. Otros ajustes: ```neon database: - # shows database panel in Tracy Bar? - debugger: ... # (bool) defaults to true + # ¿mostrar el panel de base de datos en Tracy Bar? + debugger: ... # (bool) por defecto es true - # shows query EXPLAIN in Tracy Bar? - explain: ... # (bool) defaults to true + # ¿mostrar EXPLAIN de las consultas en Tracy Bar? + explain: ... # (bool) por defecto es true - # to enable autowiring for this connection? - autowired: ... # (bool) defaults to true for first connection + # ¿habilitar autowiring para esta conexión? + autowired: ... # (bool) por defecto es true para la primera conexión - # table conventions: discovered, static, or class name - conventions: discovered # (string) defaults to 'discovered' + # convenciones de tabla: discovered, static o nombre de clase + conventions: discovered # (string) por defecto es 'discovered' options: - # to connect to the database only when needed? - lazy: ... # (bool) defaults to false + # ¿conectarse a la base de datos solo cuando sea necesario? + lazy: ... # (bool) por defecto es false - # PHP database driver class + # Clase PHP del controlador de base de datos driverClass: # (string) - # only MySQL: sets sql_mode + # solo MySQL: establece sql_mode sqlmode: # (string) - # only MySQL: sets SET NAMES - charset: # (string) defaults to 'utf8mb4' ('utf8' before v5.5.3) + # solo MySQL: establece SET NAMES + charset: # (string) por defecto es 'utf8mb4' - # only Oracle and SQLite: date format - formatDateTime: # (string) defaults to 'U' + # solo MySQL: convierte TINYINT(1) a bool + convertBoolean: # (bool) por defecto es false + + # devuelve columnas de fecha como objetos inmutables (desde la versión 3.2.1) + newDateTime: # (bool) por defecto es false + + # solo Oracle y SQLite: formato para almacenar la fecha + formatDateTime: # (string) por defecto es 'U' ``` -La clave `options` puede contener otras opciones que se pueden encontrar en [la documentación del controlador PDO |https://www.php.net/manual/en/pdo.drivers.php], tales como: +En la clave `options`, puede especificar otras opciones que se encuentran en la [documentación de los controladores PDO |https://www.php.net/manual/en/pdo.drivers.php], como por ejemplo: ```neon database: @@ -64,10 +70,10 @@ database: ``` -Conexiones Múltiples .[#toc-multiple-connections] -------------------------------------------------- +Múltiples conexiones +-------------------- -En la configuración podemos definir más conexiones a bases de datos dividiéndolas en secciones con nombre: +En la configuración, también podemos definir múltiples conexiones de base de datos dividiéndolas en secciones con nombre: ```neon database: @@ -80,9 +86,23 @@ database: dsn: 'sqlite::memory:' ``` -Cada conexión definida crea servicios que incluyen el nombre de la sección en su nombre, es decir `@database.main.connection` & `@database.main.explorer` y además `@database.another.connection` & `@database.another.explorer`. +El autowiring solo está habilitado para los servicios de la primera sección. Esto se puede cambiar usando `autowired: false` o `autowired: true`. + + +Servicios DI +------------ + +Estos servicios se añaden al contenedor DI, donde `###` representa el nombre de la conexión: + +| Nombre | Tipo | Descripción +|---------------------------------------------------------- +| `database.###.connection` | [api:Nette\Database\Connection] | conexión con la base de datos +| `database.###.explorer` | [api:Nette\Database\Explorer] | [Database Explorer |explorer] + + +Si definimos solo una conexión, los nombres de los servicios serán `database.default.connection` y `database.default.explorer`. Si definimos múltiples conexiones como en el ejemplo anterior, los nombres corresponderán a las secciones, es decir, `database.main.connection`, `database.main.explorer` y también `database.another.connection` y `database.another.explorer`. -El autocableado sólo está activado para los servicios de la primera sección. Esto puede cambiarse con `autowired: false` o `autowired: true`. Los servicios no autocableados se pasan por nombre: +Pasamos los servicios no autowired explícitamente por referencia a su nombre: ```neon services: diff --git a/database/es/core.texy b/database/es/core.texy deleted file mode 100644 index 9944ca9332..0000000000 --- a/database/es/core.texy +++ /dev/null @@ -1,350 +0,0 @@ -Base de datos -************* - -.[perex] -Nette Database Core es la capa de abstracción de la base de datos y proporciona la funcionalidad básica. - - -Instalación .[#toc-installation] -================================ - -Descargue e instale el paquete utilizando [Composer |best-practices:composer]: - -```shell -composer require nette/database -``` - - -Conexión y configuración .[#toc-connection-and-configuration] -============================================================= - -Para conectarse a la base de datos, basta con crear una instancia de la clase [api:Nette\Database\Connection]: - -```php -$database = new Nette\Database\Connection($dsn, $user, $password); -``` - -El parámetro `$dsn` (nombre de la fuente de datos) es el [mismo que utiliza PDO |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], por ejemplo `host=127.0.0.1;dbname=test`. En caso de fallo, lanza `Nette\Database\ConnectionException`. - -Sin embargo, una forma más sofisticada ofrece la [configuración de la aplicación |configuration]. Añadiremos una sección `database` y crea los objetos requeridos y un panel de base de datos en la barra [Tracy |tracy:]. - -```neon -database: - dsn: 'mysql:host=127.0.0.1;dbname=test' - user: root - password: password -``` - -El objeto de conexión que [recibimos como servicio de un contenedor DI |dependency-injection:passing-dependencies], por ejemplo: - -```php -class Model -{ - // pass Nette\Database\Explorer to work with the Database Explorer layer - public function __construct( - private Nette\Database\Connection $database, - ) { - } -} -``` - -Para más información, consulte [configuración de la base de datos |configuration]. - - -Consultas .[#toc-queries] -========================= - -Para consultar la base de datos utilice el método `query()` que devuelve [un ResultSet |api:Nette\Database\ResultSet]. - -```php -$result = $database->query('SELECT * FROM users'); - -foreach ($result as $row) { - echo $row->id; - echo $row->name; -} - -echo $result->getRowCount(); // returns the number of rows if is known -``` - -.[note] -Sobre el `ResultSet` es posible iterar una sola vez, si necesitamos iterar múltiples veces, es necesario convertir el resultado al array mediante el método `fetchAll()`. - -Usted puede agregar fácilmente parámetros a la consulta, tenga en cuenta el signo de interrogación: - -```php -$database->query('SELECT * FROM users WHERE name = ?', $name); - -$database->query('SELECT * FROM users WHERE name = ? AND active = ?', $name, $active); - -$database->query('SELECT * FROM users WHERE id IN (?)', $ids); // $ids is array -``` -<div class=warning> - -ADVERTENCIA, ¡nunca concatene cadenas para evitar [una vulnerabilidad de inyección SQL |https://en.wikipedia.org/wiki/SQL_injection]! -/-- -$db->query('SELECT * FROM users WHERE name = ' . $name); // WRONG!!! -\-- -</div> - -En caso de fallo `query()` lanza `Nette\Database\DriverException` o uno de sus descendientes: - -- [ConstraintViolationException |api:Nette\Database\ConstraintViolationException] - violación de cualquier restricción -- [ForeignKeyConstraintViolationException |api:Nette\Database\ForeignKeyConstraintViolationException] - clave externa no válida -- [NotNullConstraintViolationException |api:Nette\Database\NotNullConstraintViolationException] - violación de la condición NOT NULL -- [UniqueConstraintViolationException |api:Nette\Database\UniqueConstraintViolationException] - conflicto de índice único - -Además de `query()`, existen otros métodos útiles: - -```php -// returns the associative array id => name -$pairs = $database->fetchPairs('SELECT id, name FROM users'); - -// returns all rows as array -$rows = $database->fetchAll('SELECT * FROM users'); - -// returns single row -$row = $database->fetch('SELECT * FROM users WHERE id = ?', $id); - -// return single field -$name = $database->fetchField('SELECT name FROM users WHERE id = ?', $id); -``` - -En caso de fallo, todos estos métodos lanzan `Nette\Database\DriverException.` - - -Insertar, actualizar y eliminar .[#toc-insert-update-delete] -============================================================ - -El parámetro que insertamos en la consulta SQL también puede ser el array (en cuyo caso es posible omitir la sentencia comodín `?`), which may be useful for the `INSERT`: - -```php -$database->query('INSERT INTO users ?', [ // here can be omitted question mark - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) - -$id = $database->getInsertId(); // returns the auto-increment of inserted row - -$id = $database->getInsertId($sequence); // or sequence value -``` - -Inserción múltiple: - -```php -$database->query('INSERT INTO users', [ - [ - 'name' => 'Jim', - 'year' => 1978, - ], [ - 'name' => 'Jack', - 'year' => 1987, - ], -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) -``` - -También podemos pasar ficheros, objetos DateTime o [enumeraciones |https://www.php.net/enumerations]: - -```php -$database->query('INSERT INTO users', [ - 'name' => $name, - 'created' => new DateTime, // or $database::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // inserts file contents - 'status' => State::New, // enum State -]); -``` - -Actualización de filas: - -```php -$result = $database->query('UPDATE users SET', [ - 'name' => $name, - 'year' => $year, -], 'WHERE id = ?', $id); -// UPDATE users SET `name` = 'Jim', `year` = 1978 WHERE id = 123 - -echo $result->getRowCount(); // returns the number of affected rows -``` - -Para UPDATE, podemos utilizar los operadores `+=` y `-=`: - -```php -$database->query('UPDATE users SET', [ - 'age+=' => 1, // note += -], 'WHERE id = ?', $id); -// UPDATE users SET `age` = `age` + 1 -``` - -Borrado: - -```php -$result = $database->query('DELETE FROM users WHERE id = ?', $id); -echo $result->getRowCount(); // returns the number of affected rows -``` - - -Consultas avanzadas .[#toc-advanced-queries] -============================================ - -Insertar o actualizar, si ya existe: - -```php -$database->query('INSERT INTO users', [ - 'id' => $id, - 'name' => $name, - 'year' => $year, -], 'ON DUPLICATE KEY UPDATE', [ - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) -// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 -``` - -Nótese que Nette Database reconoce el contexto SQL en el que se inserta el parámetro del array y construye el código SQL en consecuencia. Así, a partir del primer array genera `(id, name, year) VALUES (123, 'Jim', 1978)`, mientras que el segundo lo convierte en `name = 'Jim', year = 1978`. - -También podemos describir la ordenación utilizando un array, en el que las claves son nombres de columnas y los valores son booleanos que determinan si se ordena en orden ascendente: - -```php -$database->query('SELECT id FROM author ORDER BY', [ - 'id' => true, // ascending - 'name' => false, // descending -]); -// SELECT id FROM author ORDER BY `id`, `name` DESC -``` - -Si la detección no ha funcionado, puede especificar la forma del conjunto con un comodín `?` seguido de una pista. Estas pistas son compatibles: - -| ?values | (clave1, clave2, ...) VALUES (valor1, valor2, ...) -| ?set | clave1 = valor1, clave2 = valor2, ... -| ?and | clave1 = valor1 Y clave2 = valor2 ... -| ?or | clave1 = valor1 OR clave2 = valor2 ... -| ?order | clave1 ASC, clave2 DESC - -La cláusula WHERE utiliza el operador `?and` por lo que las condiciones están vinculadas por `AND`: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND `year` = 1978 -``` - -Que puede cambiarse fácilmente a `OR` utilizando el comodín `?or`: - -```php -$result = $database->query('SELECT * FROM users WHERE ?or', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' OR `year` = 1978 -``` - -Podemos utilizar operadores en las condiciones: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name <>' => $name, - 'year >' => $year, -]); -// SELECT * FROM users WHERE `name` <> 'Jim' AND `year` > 1978 -``` - -Y también en enumeraciones: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => ['Jim', 'Jack'], - 'role NOT IN' => ['admin', 'owner'], // enumeration + operator NOT IN -]); -// SELECT * FROM users WHERE -// `name` IN ('Jim', 'Jack') AND `role` NOT IN ('admin', 'owner') -``` - -También podemos incluir un trozo de código SQL personalizado utilizando el llamado literal SQL: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year >' => $database::literal('YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) -``` - -Alternativamente: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) -``` - -SQL literal también puede tener sus parámetros: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > ? AND year < ?', $min, $max), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) -``` - -Gracias a los cuales podemos crear combinaciones interesantes: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('?or', [ - 'active' => true, - 'role' => $role, - ]), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') -``` - - -Nombre de la variable .[#toc-variable-name] -=========================================== - -Existe un comodín `?name` que se utiliza si el nombre de la tabla o de la columna es una variable. (Cuidado, no permita que el usuario manipule el contenido de dicha variable): - -```php -$table = 'blog.users'; -$column = 'name'; -$database->query('SELECT * FROM ?name WHERE ?name = ?', $table, $column, $name); -// SELECT * FROM `blog`.`users` WHERE `name` = 'Jim' -``` - - -Transacciones .[#toc-transactions] -================================== - -Existen tres métodos para tratar las transacciones: - -```php -$database->beginTransaction(); - -$database->commit(); - -$database->rollback(); -``` - -Una forma elegante es la que ofrece el método `transaction()`. Se pasa la llamada de retorno que se ejecuta en la transacción. Si se lanza una excepción durante la ejecución, la transacción se abandona, si todo va bien, la transacción se compromete. - -```php -$id = $database->transaction(function ($database) { - $database->query('DELETE FROM ...'); - $database->query('INSERT INTO ...'); - // ... - return $database->getInsertId(); -}); -``` - -Como puede ver, el método `transaction()` devuelve el valor de retorno de la llamada de retorno. - -La transacción() también se puede anidar, lo que simplifica la implementación de repositorios independientes. diff --git a/database/es/exceptions.texy b/database/es/exceptions.texy new file mode 100644 index 0000000000..67ced3eb4d --- /dev/null +++ b/database/es/exceptions.texy @@ -0,0 +1,34 @@ +Excepciones +*********** + +Nette Database utiliza una jerarquía de excepciones. La clase base es `Nette\Database\DriverException`, que hereda de `PDOException` y proporciona opciones extendidas para trabajar con errores de la base de datos: + +- El método `getDriverCode()` devuelve el código de error del driver de la base de datos +- El método `getSqlState()` devuelve el código SQLSTATE +- Los métodos `getQueryString()` y `getParameters()` permiten obtener la consulta original y sus parámetros + +De `DriverException` heredan las siguientes excepciones especializadas: + +- `ConnectionException` - señala un fallo de conexión al servidor de base de datos +- `ConstraintViolationException` - clase base para violaciones de restricciones de la base de datos, de la cual heredan: + - `ForeignKeyConstraintViolationException` - violación de clave foránea + - `NotNullConstraintViolationException` - violación de restricción NOT NULL + - `UniqueConstraintViolationException` - violación de unicidad de valor + + +Ejemplo de captura de excepción `UniqueConstraintViolationException`, que ocurre cuando intentamos insertar un usuario con un correo electrónico que ya existe en la base de datos (asumiendo que la columna `email` tiene un índice único). + +```php +try { + $database->query('INSERT INTO users', [ + 'email' => 'john@example.com', + 'name' => 'John Doe', + 'password' => $hashedPassword, + ]); +} catch (Nette\Database\UniqueConstraintViolationException $e) { + echo 'El usuario con este correo electrónico ya existe.'; + +} catch (Nette\Database\DriverException $e) { + echo 'Ocurrió un error durante el registro: ' . $e->getMessage(); +} +``` diff --git a/database/es/explorer.texy b/database/es/explorer.texy index 444c32521d..a7d283a7e0 100644 --- a/database/es/explorer.texy +++ b/database/es/explorer.texy @@ -1,550 +1,912 @@ -Explorador de bases de datos -**************************** +Database Explorer +***************** <div class=perex> -Nette Database Explorer simplifica significativamente la recuperación de datos de la base de datos sin escribir consultas SQL. +Explorer ofrece una forma intuitiva y eficiente de trabajar con la base de datos. Se encarga automáticamente de las relaciones entre tablas y la optimización de consultas, para que puedas concentrarte en tu aplicación. Funciona inmediatamente sin configuración. Si necesitas control total sobre las consultas SQL, puedes utilizar el [acceso SQL |sql-way]. -- utiliza consultas eficientes -- no se transmiten datos innecesariamente -- presenta una sintaxis elegante +- Trabajar con datos es natural y fácil de entender. +- Genera consultas SQL optimizadas que cargan solo los datos necesarios. +- Permite un fácil acceso a los datos relacionados sin necesidad de escribir consultas JOIN. +- Funciona instantáneamente sin ninguna configuración o generación de entidades. </div> -Para utilizar Database Explorer, comience con una tabla - llame a `table()` en un objeto [api:Nette\Database\Explorer]. La forma más sencilla de obtener una instancia de objeto de contexto se [describe aquí |core#Connection and Configuration], o, para el caso en que Nette Database Explorer se utilice como herramienta independiente, puede [crearse manualmente |#Creating Explorer Manually]. + +Empiezas con Explorer llamando al método `table()` del objeto [api:Nette\Database\Explorer] (los detalles de la conexión se pueden encontrar en el capítulo [Conexión y configuración |guide#Conexión y configuración]): ```php -$books = $explorer->table('book'); // db table name is 'book' +$books = $explorer->table('book'); // 'book' es el nombre de la tabla ``` -La llamada devuelve una instancia del objeto [Selección |api:Nette\Database\Table\Selection], sobre el que se puede iterar para recuperar todos los libros. Cada elemento (una fila) está representado por una instancia de [ActiveRow |api:Nette\Database\Table\ActiveRow] con datos asignados a sus propiedades: +El método devuelve un objeto [Selection |api:Nette\Database\Table\Selection], que representa una consulta SQL. A este objeto podemos encadenar otros métodos para filtrar y ordenar los resultados. La consulta se construye y ejecuta solo cuando empezamos a solicitar datos. Por ejemplo, iterando con un bucle `foreach`. Cada fila está representada por un objeto [ActiveRow |api:Nette\Database\Table\ActiveRow]: ```php foreach ($books as $book) { - echo $book->title; - echo $book->author_id; + echo $book->title; // Salida de la columna 'title' + echo $book->author_id; // Salida de la columna 'author_id' } ``` -La obtención de una fila específica se realiza mediante el método `get()`, que devuelve directamente una instancia de ActiveRow. +Explorer facilita fundamentalmente el trabajo con [#relaciones entre tablas]. El siguiente ejemplo muestra lo fácil que es mostrar datos de tablas relacionadas (libros y sus autores). Ten en cuenta que no necesitamos escribir ninguna consulta JOIN, Nette las crea por nosotros: ```php -$book = $explorer->table('book')->get(2); // returns book with id 2 -echo $book->title; -echo $book->author_id; +$books = $explorer->table('book'); + +foreach ($books as $book) { + echo 'Libro: ' . $book->title; + echo 'Autor: ' . $book->author->name; // Crea un JOIN a la tabla 'author' +} ``` -Veamos un caso de uso común. Necesita obtener libros y sus autores. Es una relación común 1:N. La solución más utilizada es obtener los datos mediante una consulta SQL con uniones de tablas. La segunda posibilidad es obtener los datos por separado, ejecutar una consulta para obtener los libros y luego obtener un autor para cada libro mediante otra consulta (por ejemplo, en su ciclo foreach). Esto podría optimizarse fácilmente para ejecutar sólo dos consultas, una para los libros y otra para los autores necesarios, y así es exactamente como lo hace Nette Database Explorer. +Nette Database Explorer optimiza las consultas para que sean lo más eficientes posible. El ejemplo anterior ejecuta solo dos consultas SELECT, independientemente de si procesamos 10 o 10,000 libros. -En los ejemplos siguientes, trabajaremos con el esquema de base de datos de la figura. Hay enlaces OneHasMany (1:N) (autor del libro `author_id` y posible traductor `translator_id`, que puede ser `null`) y enlaces ManyHasMany (M:N) entre el libro y sus etiquetas. +Además, Explorer rastrea qué columnas se utilizan en el código y carga solo esas desde la base de datos, ahorrando así rendimiento adicional. Este comportamiento es completamente automático y adaptativo. Si más tarde modificas el código y comienzas a usar columnas adicionales, Explorer ajustará automáticamente las consultas. No necesitas configurar nada, ni pensar qué columnas necesitarás - déjaselo a Nette. -[En GitHub se puede encontrar un ejemplo, que incluye un esquema |https://github.com/nette-examples/books]. -[* db-schema-1-.webp *] *** Estructura de la base de datos utilizada en los ejemplos .<> +Filtrado y ordenación +===================== -El siguiente código lista el nombre del autor de cada libro y todas sus etiquetas. [Discutiremos en un |#Working with relationships] momento cómo funciona esto internamente. +La clase `Selection` proporciona métodos para filtrar y ordenar la selección de datos. -```php -$books = $explorer->table('book'); +.[language-php] +| `where($condition, ...$params)` | Añade una condición WHERE. Múltiples condiciones se unen con el operador AND +| `whereOr(array $conditions)` | Añade un grupo de condiciones WHERE unidas por el operador OR +| `wherePrimary($value)` | Añade una condición WHERE por clave primaria +| `order($columns, ...$params)` | Establece el orden ORDER BY +| `select($columns, ...$params)` | Especifica las columnas que se deben cargar +| `limit($limit, $offset = null)` | Limita el número de filas (LIMIT) y opcionalmente establece OFFSET +| `page($page, $itemsPerPage, &$total = null)` | Establece la paginación +| `group($columns, ...$params)` | Agrupa las filas (GROUP BY) +| `having($condition, ...$params)` | Añade una condición HAVING para filtrar filas agrupadas -foreach ($books as $book) { - echo 'title: ' . $book->title; - echo 'written by: ' . $book->author->name; // $book->author is row from table 'author' +Los métodos se pueden encadenar (la llamada [interfaz fluida |nette:introduction-to-object-oriented-programming#Interfaces Fluidas]): `$table->where(...)->order(...)->limit(...)`. - echo 'tags: '; - foreach ($book->related('book_tag') as $bookTag) { - echo $bookTag->tag->name . ', '; // $bookTag->tag is row from table 'tag' - } -} -``` +En estos métodos también puedes usar notación especial para acceder a [datos de tablas relacionadas |#Consultas a través de tablas relacionadas]. -Le agradará la eficiencia con la que funciona la capa de base de datos. El ejemplo anterior hace un número constante de peticiones que se parecen a esto: -```sql -SELECT * FROM `book` -SELECT * FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT * FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) -``` +Escape e identificadores +------------------------ -Si utiliza [la caché |caching:] (activada por defecto), ninguna columna será consultada innecesariamente. Después de la primera consulta, la caché almacenará los nombres de las columnas utilizadas y Nette Database Explorer ejecutará consultas sólo con las columnas necesarias: +Los métodos escapan automáticamente los parámetros y entrecomillan los identificadores (nombres de tablas y columnas), previniendo así la inyección SQL. Para un funcionamiento correcto, es necesario seguir algunas reglas: -```sql -SELECT `id`, `title`, `author_id` FROM `book` -SELECT `id`, `name` FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT `book_id`, `tag_id` FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT `id`, `name` FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) +- Escribe las palabras clave, nombres de funciones, procedimientos, etc., en **MAYÚSCULAS**. +- Escribe los nombres de columnas y tablas en **minúsculas**. +- Siempre inserta cadenas a través de **parámetros**. + +```php +where('name = ' . $name); // VULNERABILIDAD CRÍTICA: Inyección SQL +where('name LIKE "%search%"'); // MAL: complica el entrecomillado automático +where('name LIKE ?', '%search%'); // CORRECTO: valor insertado mediante parámetro + +where('name like ?', $name); // MAL: genera: `name` `like` ? +where('name LIKE ?', $name); // CORRECTO: genera: `name` LIKE ? +where('LOWER(name) = ?', $value);// CORRECTO: LOWER(`name`) = ? ``` -Selecciones .[#toc-selections] -============================== +where(string|array $condition, ...$parameters): static .[method] +---------------------------------------------------------------- -Ver posibilidades cómo filtrar y restringir filas [api:Nette\Database\Table\Selection]: +Filtra los resultados usando condiciones WHERE. Su punto fuerte es el manejo inteligente de diferentes tipos de valores y la elección automática de operadores SQL. -.[language-php] -| `$table->where($where[, $param[, ...]])` | Establecer WHERE usando AND si se dan dos o más condiciones -| `$table->whereOr($where)` | Establecer WHERE usando OR como pegamento si se suministran dos o más condiciones -| `$table->order($columns)` Establecer ORDER BY, puede ser una expresión. `('column DESC, id DESC')` -| `$table->select($columns)` Establecer columnas recuperadas, puede ser una expresión. `('col, MD5(col) AS hash')` -| `$table->limit($limit[, $offset])` | Establecer LIMIT y OFFSET -| `$table->page($page, $itemsPerPage[, &$lastPage])` | Habilita la paginación -| `$table->group($columns)` Establecer GROUP BY -| `$table->having($having)` Establecer HAVING +Uso básico: -Se puede utilizar una interfaz fluida, por ejemplo `$table->where(...)->order(...)->limit(...)`. Las condiciones múltiples `where` o `whereOr` se conectan con el operador `AND`. +```php +$table->where('id', $value); // WHERE `id` = 123 +$table->where('id > ?', $value); // WHERE `id` > 123 +$table->where('id = ? OR name = ?', $id, $name); // WHERE `id` = 1 OR `name` = 'Jon Snow' +``` +Gracias a la detección automática de operadores apropiados, no tenemos que lidiar con varios casos especiales. Nette los resuelve por nosotros: -donde() .[#toc-where] ---------------------- +```php +$table->where('id', 1); // WHERE `id` = 1 +$table->where('id', null); // WHERE `id` IS NULL +$table->where('id', [1, 2, 3]); // WHERE `id` IN (1, 2, 3) +// También se puede usar un signo de interrogación de marcador de posición sin operador: +$table->where('id ?', 1); // WHERE `id` = 1 +``` -Nette Database Explorer puede añadir automáticamente los operadores necesarios para los valores pasados: +El método también maneja correctamente condiciones negativas y arrays vacíos: -.[language-php] -| `$table->where('field', $value)` Campo = $valor -| `$table->where('field', null)` Campo IS NULL -| `$table->where('field > ?', $val)` Campo > $valor -| `$table->where('field', [1, 2])` | campo EN (1, 2) -| `$table->where('id = ? OR name = ?', 1, $name)` | id = 1 OR name = 'Jon Snow' -| `$table->where('field', $explorer->table($tableName))` field IN (SELECT $primary FROM $tableName) -| `$table->where('field', $explorer->table($tableName)->select('col'))` | field IN (SELECT col FROM $tableName) +```php +$table->where('id', []); // WHERE `id` IS NULL AND FALSE -- No encontrará nada +$table->where('id NOT', []); // WHERE `id` IS NULL OR TRUE -- Encontrará todo +$table->where('NOT (id ?)', []); // WHERE NOT (`id` IS NULL AND FALSE) -- Encontrará todo +// $table->where('NOT id ?', $ids); Atención - esta sintaxis no es compatible +``` -Puede proporcionar un marcador de posición incluso sin el operador de columna. Estas llamadas son las mismas. +También podemos pasar el resultado de otra tabla como parámetro - se creará una subconsulta: ```php -$table->where('id = ? OR id = ?', 1, 2); -$table->where('id ? OR id ?', 1, 2); +// WHERE `id` IN (SELECT `id` FROM `tableName`) +$table->where('id', $explorer->table($tableName)); + +// WHERE `id` IN (SELECT `col` FROM `tableName`) +$table->where('id', $explorer->table($tableName)->select('col')); ``` -Esta función permite generar el operador correcto en función del valor: +También podemos pasar las condiciones como un array, cuyos elementos se unirán usando AND: ```php -$table->where('id ?', 2); // id = 2 -$table->where('id ?', null); // id IS NULL -$table->where('id', $ids); // id IN (...) +// WHERE (`price_final` < `price_original`) AND (`stock_count` > `min_stock`) +$table->where([ + 'price_final < price_original', + 'stock_count > min_stock', +]); ``` -Selección maneja correctamente también las condiciones negativas, funciona para matrices vacías también: +En el array podemos usar pares clave => valor y Nette elegirá automáticamente los operadores correctos de nuevo: ```php -$table->where('id', []); // id IS NULL AND FALSE -$table->where('id NOT', []); // id IS NULL OR TRUE -$table->where('NOT (id ?)', $ids); // NOT (id IS NULL AND FALSE) +// WHERE (`status` = 'active') AND (`id` IN (1, 2, 3)) +$table->where([ + 'status' => 'active', + 'id' => [1, 2, 3], +]); +``` + +En el array podemos combinar expresiones SQL con marcadores de posición de signo de interrogación y múltiples parámetros. Esto es adecuado para condiciones complejas con operadores definidos con precisión: -// this will throws an exception, this syntax is not supported -$table->where('NOT id ?', $ids); +```php +// WHERE (`age` > 18) AND (ROUND(`score`, 2) > 75.5) +$table->where([ + 'age > ?' => 18, + 'ROUND(score, ?) > ?' => [2, 75.5], // Pasamos dos parámetros como un array +]); ``` +Múltiples llamadas a `where()` unen automáticamente las condiciones usando AND. -whereOr() .[#toc-whereor] -------------------------- -Ejemplo de uso sin parámetros: +whereOr(array $parameters): static .[method] +-------------------------------------------- + +Similar a `where()`, añade condiciones, pero con la diferencia de que las une usando OR: ```php -// WHERE (user_id IS NULL) OR (SUM(`field1`) > SUM(`field2`)) +// WHERE (`status` = 'active') OR (`deleted` = 1) $table->whereOr([ - 'user_id IS NULL', - 'SUM(field1) > SUM(field2)', + 'status' => 'active', + 'deleted' => true, ]); ``` -Se utilizan los parámetros. Si no especifica un operador, Nette Database Explorer añadirá automáticamente el apropiado: +Aquí también podemos usar expresiones más complejas: ```php -// WHERE (`field1` IS NULL) OR (`field2` IN (3, 5)) OR (`amount` > 11) +// WHERE (`price` > 1000) OR (`price_with_tax` > 1500) $table->whereOr([ - 'field1' => null, - 'field2' => [3, 5], - 'amount >' => 11, + 'price > ?' => 1000, + 'price_with_tax > ?' => 1500, ]); ``` -La clave puede contener una expresión que contenga signos de interrogación comodín y luego pasar parámetros en el valor: + +wherePrimary(mixed $key): static .[method] +------------------------------------------ + +Añade una condición para la clave primaria de la tabla: ```php -// WHERE (`id` > 12) OR (ROUND(`id`, 5) = 3) -$table->whereOr([ - 'id > ?' => 12, - 'ROUND(id, ?) = ?' => [5, 3], -]); +// WHERE `id` = 123 +$table->wherePrimary(123); + +// WHERE `id` IN (1, 2, 3) +$table->wherePrimary([1, 2, 3]); +``` + +Si la tabla tiene una clave primaria compuesta (por ejemplo, `foo_id`, `bar_id`), la pasamos como un array: + +```php +// WHERE `foo_id` = 1 AND `bar_id` = 5 +$table->wherePrimary(['foo_id' => 1, 'bar_id' => 5])->fetch(); + +// WHERE (`foo_id`, `bar_id`) IN ((1, 5), (2, 3)) +$table->wherePrimary([ + ['foo_id' => 1, 'bar_id' => 5], + ['foo_id' => 2, 'bar_id' => 3], +])->fetchAll(); ``` -order() .[#toc-order] ---------------------- +order(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- -Ejemplos de uso: +Especifica el orden en que se devolverán las filas. Podemos ordenar por una o más columnas, en orden descendente o ascendente, o por una expresión personalizada: ```php -$table->order('field1'); // ORDER BY `field1` -$table->order('field1 DESC, field2'); // ORDER BY `field1` DESC, `field2` -$table->order('field = ? DESC', 123); // ORDER BY `field` = 123 DESC +$table->order('created'); // ORDER BY `created` +$table->order('created DESC'); // ORDER BY `created` DESC +$table->order('priority DESC, created'); // ORDER BY `priority` DESC, `created` +$table->order('status = ? DESC', 'active'); // ORDER BY `status` = 'active' DESC ``` -select() .[#toc-select] ------------------------ +select(string $columns, ...$parameters): static .[method] +--------------------------------------------------------- + +Especifica las columnas que deben devolverse de la base de datos. Por defecto, Nette Database Explorer devuelve solo aquellas columnas que se utilizan realmente en el código. Por lo tanto, usamos el método `select()` en casos donde necesitamos devolver expresiones específicas: + +```php +// SELECT *, DATE_FORMAT(`created_at`, "%d.%m.%Y") AS `formatted_date` +$table->select('*, DATE_FORMAT(created_at, ?) AS formatted_date', '%d.%m.%Y'); +``` -Ejemplos de uso: +Los alias definidos usando `AS` están entonces disponibles como propiedades del objeto ActiveRow: ```php -$table->select('field1'); // SELECT `field1` -$table->select('col, UPPER(col) AS abc'); // SELECT `col`, UPPER(`col`) AS abc -$table->select('SUBSTR(title, ?)', 3); // SELECT SUBSTR(`title`, 3) +foreach ($table as $row) { + echo $row->formatted_date; // Acceso al alias +} ``` -limit() .[#toc-limit] ---------------------- +limit(?int $limit, ?int $offset = null): static .[method] +--------------------------------------------------------- -Ejemplos de uso: +Limita el número de filas devueltas (LIMIT) y opcionalmente permite establecer un offset: ```php -$table->limit(1); // LIMIT 1 -$table->limit(1, 10); // LIMIT 1 OFFSET 10 +$table->limit(10); // LIMIT 10 (Devuelve las primeras 10 filas) +$table->limit(10, 20); // LIMIT 10 OFFSET 20 ``` +Para la paginación, es más adecuado usar el método `page()`. -page() .[#toc-page] -------------------- -Una forma alternativa de establecer el límite y el desplazamiento: +page(int $page, int $itemsPerPage, &$numOfPages = null): static .[method] +------------------------------------------------------------------------- + +Facilita la paginación de resultados. Acepta el número de página (contando desde 1) y el número de elementos por página. Opcionalmente, se puede pasar una referencia a una variable donde se almacenará el número total de páginas: ```php -$page = 5; -$itemsPerPage = 10; -$table->page($page, $itemsPerPage); // LIMIT 10 OFFSET 40 +$numOfPages = null; +$table->page(page: 3, itemsPerPage: 10, $numOfPages); +echo "Total de páginas: $numOfPages"; ``` -Obteniendo el último número de página, pasado a la variable `$lastPage`: + +group(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- + +Agrupa las filas por las columnas especificadas (GROUP BY). Se utiliza generalmente junto con funciones de agregación: ```php -$table->page($page, $itemsPerPage, $lastPage); +// Calcula el número de productos en cada categoría +$table->select('category_id, COUNT(*) AS count') + ->group('category_id'); ``` -group() .[#toc-group] ---------------------- +having(string $having, ...$parameters): static .[method] +-------------------------------------------------------- -Ejemplos de uso: +Establece la condición para filtrar filas agrupadas (HAVING). Se puede usar junto con el método `group()` y funciones de agregación: ```php -$table->group('field1'); // GROUP BY `field1` -$table->group('field1, field2'); // GROUP BY `field1`, `field2` +// Encuentra categorías que tienen más de 100 productos +$table->select('category_id, COUNT(*) AS count') + ->group('category_id') + ->having('count > ?', 100); ``` -having() .[#toc-having] ------------------------ +Lectura de datos +================ + +Para leer datos de la base de datos, tenemos varios métodos útiles disponibles: + +.[language-php] +| `foreach ($table as $key => $row)` | Itera sobre todas las filas, `$key` es el valor de la clave primaria, `$row` es un objeto ActiveRow +| `$row = $table->get($key)` | Devuelve una fila por clave primaria +| `$row = $table->fetch()` | Devuelve la fila actual y mueve el puntero a la siguiente +| `$array = $table->fetchPairs()` | Crea un array asociativo a partir de los resultados +| `$array = $table->fetchAll()` | Devuelve todas las filas como un array +| `count($table)` | Devuelve el número de filas en el objeto Selection + +El objeto [ActiveRow |api:Nette\Database\Table\ActiveRow] está destinado solo para lectura. Esto significa que no se pueden cambiar los valores de sus propiedades. Esta restricción asegura la consistencia de los datos y previene efectos secundarios inesperados. Los datos se cargan desde la base de datos y cualquier cambio debe realizarse de forma explícita y controlada. + -Ejemplos de uso: +`foreach` - iteración sobre todas las filas +------------------------------------------- + +La forma más fácil de ejecutar una consulta y obtener filas es iterando en un bucle `foreach`. Ejecuta automáticamente la consulta SQL. ```php -$table->having('COUNT(items) >', 100); // HAVING COUNT(`items`) > 100 +$books = $explorer->table('book'); +foreach ($books as $key => $book) { + // $key es el valor de la clave primaria, $book es ActiveRow + echo "$book->title ({$book->author->name})"; +} ``` -Filtrado por otro valor de tabla .[#toc-joining-key] ----------------------------------------------------- +get($key): ?ActiveRow .[method] +------------------------------- -A menudo es necesario filtrar los resultados por alguna condición que implique otra tabla de la base de datos. Este tipo de condiciones requieren la unión de tablas. Sin embargo, ya no es necesario escribirlas. +Ejecuta la consulta SQL y devuelve la fila por clave primaria, o `null` si no existe. -Supongamos que necesita obtener todos los libros cuyo autor se llame "Jon". Todo lo que tiene que escribir es la clave de unión de la relación y el nombre de la columna de la tabla unida. La clave de unión se deriva de la columna que hace referencia a la tabla que desea unir. En nuestro ejemplo (véase el esquema de la base de datos) se trata de la columna `author_id`, y basta con utilizar sólo la primera parte de la misma - `author` (el sufijo `_id` puede omitirse). `name` es una columna de la tabla `author` que queremos utilizar. Una condición para el traductor de libros (que está conectado por la columna `translator_id` ) se puede crear con la misma facilidad. +```php +$book = $explorer->table('book')->get(123); // Devuelve ActiveRow con ID 123 o null +if ($book) { + echo $book->title; +} +``` + + +fetch(): ?ActiveRow .[method] +----------------------------- + +Devuelve una fila y mueve el puntero interno a la siguiente. Si no existen más filas, devuelve `null`. ```php $books = $explorer->table('book'); -$books->where('author.name LIKE ?', '%Jon%'); -$books->where('translator.name', 'David Grudl'); +while ($book = $books->fetch()) { + $this->processBook($book); +} ``` -La lógica de la clave de unión se rige por la implementación de [Conventions |api:Nette\Database\Conventions]. Le animamos a utilizar [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions], que analiza sus claves externas y le permite trabajar fácilmente con estas relaciones. -La relación entre el libro y su autor es 1:N. La relación inversa también es posible. La llamamos **backjoin**. Veamos otro ejemplo. Queremos obtener todos los autores que han escrito más de 3 libros. Para hacer la unión inversa usamos la sentencia `:` (colon). Colon means that the joined relationship means hasMany (and it's quite logical too, as two dots are more than one dot). Unfortunately, the Selection class isn't smart enough, so we have to help with the aggregation and provide a `GROUP BY`, también la condición tiene que ser escrita en forma de sentencia `HAVING`. +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Devuelve los resultados como un array asociativo. El primer argumento especifica el nombre de la columna que se usará como clave en el array, el segundo argumento especifica el nombre de la columna que se usará como valor: ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book.id) > 3'); +$authors = $explorer->table('author')->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] +``` + +Si solo especificamos el primer parámetro, el valor será la fila completa, es decir, el objeto `ActiveRow`: + +```php +$authors = $explorer->table('author')->fetchPairs('id'); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` + +En caso de claves duplicadas, se utiliza el valor de la última fila. Al usar `null` como clave, el array se indexará numéricamente desde cero (entonces no ocurren colisiones): + +```php +$authors = $explorer->table('author')->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] ``` -Puede que haya observado que la expresión de unión hace referencia al libro, pero no está claro si estamos uniendo a través de `author_id` o `translator_id`. En el ejemplo anterior, Selection une a través de la columna `author_id` porque se ha encontrado una coincidencia con la tabla de origen: la tabla `author`. Si no hubiera tal coincidencia y hubiera más posibilidades, Nette lanzaría [una AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. -Para realizar una unión a través de la columna `translator_id`, proporcione un parámetro opcional dentro de la expresión de unión. +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Alternativamente, puedes pasar un callback como parámetro, que devolverá el valor en sí, o un par clave-valor para cada fila. ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book(translator).id) > 3'); +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => "$row->title ({$row->author->name})"); +// ['Primer libro (Jan Novák)', ...] + +// El callback también puede devolver un array con un par clave & valor: +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => [$row->title, $row->author->name]); +// ['Primer libro' => 'Jan Novák', ...] ``` -Veamos algunas expresiones de unión más difíciles. -Queremos encontrar todos los autores que han escrito algo sobre PHP. Todos los libros tienen etiquetas por lo que deberíamos seleccionar aquellos autores que hayan escrito algún libro con la etiqueta PHP. +fetchAll(): array .[method] +--------------------------- + +Devuelve todas las filas como un array asociativo de objetos `ActiveRow`, donde las claves son los valores de las claves primarias. ```php -$authors = $explorer->table('author'); -$authors->where(':book:book_tags.tag.name', 'PHP') - ->group('author.id') - ->having('COUNT(:book:book_tags.tag.id) > 0'); +$allBooks = $explorer->table('book')->fetchAll(); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] ``` -Consultas agregadas .[#toc-aggregate-queries] ---------------------------------------------- +count(): int .[method] +---------------------- -| `$table->count('*')` Obtener número de filas -| `$table->count("DISTINCT $column")` Obtener el número de valores distintos -| `$table->min($column)` Obtener valor mínimo -| `$table->max($column)` Obtener el valor máximo -| `$table->sum($column)` Obtener la suma de todos los valores -| `$table->aggregation("GROUP_CONCAT($column)")` | Ejecutar cualquier función de agregación +El método `count()` sin parámetro devuelve el número de filas en el objeto `Selection`: -.[caution] -El método `count()` sin ningún parámetro especificado selecciona todos los registros y devuelve el tamaño del array, lo cual es muy ineficiente. Por ejemplo, si necesita calcular el número de filas para la paginación, especifique siempre el primer argumento. +```php +$table->where('category', 1); +$count = $table->count(); +$count = count($table); // Alternativa +``` +Atención, `count()` con un parámetro realiza la función de agregación `COUNT` en la base de datos, ver más abajo. -Escapar y citar .[#toc-escaping-quoting] -======================================== -Database Explorer es inteligente y escapa parámetros y comillas identificadores para usted. Sin embargo, es necesario seguir estas reglas básicas: +ActiveRow::toArray(): array .[method] +------------------------------------- -- las palabras clave, funciones y procedimientos deben ir en mayúsculas -- columnas y tablas deben ir en minúsculas -- pase variables como parámetros, no las concatene +Convierte el objeto `ActiveRow` en un array asociativo, donde las claves son los nombres de las columnas y los valores son los datos correspondientes. ```php -->where('name like ?', 'John'); // WRONG! generates: `name` `like` ? -->where('name LIKE ?', 'John'); // CORRECT +$book = $explorer->table('book')->get(1); +$bookArray = $book->toArray(); +// $bookArray será ['id' => 1, 'title' => '...', 'author_id' => ..., ...] +``` + + +Agregación +========== + +La clase `Selection` proporciona métodos para realizar fácilmente funciones de agregación (COUNT, SUM, MIN, MAX, AVG, etc.). + +.[language-php] +| `count($expr)` | Cuenta el número de filas +| `min($expr)` | Devuelve el valor mínimo en la columna +| `max($expr)` | Devuelve el valor máximo en la columna +| `sum($expr)` | Devuelve la suma de los valores en la columna +| `aggregation($function)` | Permite ejecutar cualquier función de agregación. Por ejemplo, `AVG()`, `GROUP_CONCAT()` + + +count(string $expr): int .[method] +---------------------------------- + +Ejecuta una consulta SQL con la función `COUNT` y devuelve el resultado. El método se utiliza para determinar cuántas filas coinciden con una condición específica: + +```php +$count = $table->count('*'); // SELECT COUNT(*) FROM `table` +$count = $table->count('DISTINCT column'); // SELECT COUNT(DISTINCT `column`) FROM `table` +``` + +Atención, [#count()] sin parámetro solo devuelve el número de filas en el objeto `Selection`. -->where('KEY = ?', $value); // WRONG! KEY is a keyword -->where('key = ?', $value); // CORRECT. generates: `key` = ? -->where('name = ' . $name); // WRONG! sql injection! -->where('name = ?', $name); // CORRECT +min(string $expr) y max(string $expr) .[method] +----------------------------------------------- -->select('DATE_FORMAT(created, "%d.%m.%Y")'); // WRONG! pass variables as parameters, do not concatenate -->select('DATE_FORMAT(created, ?)', '%d.%m.%Y'); // CORRECT +Los métodos `min()` y `max()` devuelven el valor mínimo y máximo en la columna o expresión especificada: + +```php +// SELECT MAX(`price`) FROM `products` WHERE `active` = 1 +$maxPrice = $products->where('active', true) + ->max('price'); ``` -.[warning] -El uso incorrecto puede producir agujeros de seguridad +sum(string $expr) .[method] +--------------------------- -Obtención de datos .[#toc-fetching-data] -======================================== +Devuelve la suma de los valores en la columna o expresión especificada: -| `foreach ($table as $id => $row)` | Iterar sobre todas las filas del resultado -| `$row = $table->get($id)` Obtener una fila con ID $id de la tabla -| `$row = $table->fetch()` Obtener la siguiente fila del resultado -| `$array = $table->fetchPairs($key, $value)` Obtener todos los valores de la matriz asociativa -| `$array = $table->fetchPairs($key)` Obtener todas las filas de la matriz asociativa -| `count($table)` Obtener el número de filas del conjunto de resultados +```php +// SELECT SUM(`price` * `items_in_stock`) FROM `products` WHERE `active` = 1 +$totalPrice = $products->where('active', true) + ->sum('price * items_in_stock'); +``` -Insertar, Actualizar y Borrar .[#toc-insert-update-delete] -========================================================== +aggregation(string $function, ?string $groupFunction = null) .[method] +---------------------------------------------------------------------- -El método `insert()` acepta un array de objetos Traversable (por ejemplo [ArrayHash |utils:arrays#ArrayHash] que devuelve [formularios |forms:]): +Permite ejecutar cualquier función de agregación. + +```php +// Precio promedio de los productos en la categoría +$avgPrice = $products->where('category_id', 1) + ->aggregation('AVG(price)'); + +// Concatena las etiquetas del producto en una sola cadena +$tags = $products->where('id', 1) + ->aggregation('GROUP_CONCAT(tag.name) AS tags') + ->fetch() + ->tags; +``` + +Si necesitamos agregar resultados que ya provienen de alguna función de agregación y agrupación (por ejemplo, `SUM(valor)` sobre filas agrupadas), especificamos la función de agregación que se aplicará a estos resultados intermedios como segundo argumento: + +```php +// Calcula el precio total de los productos en stock para cada categoría y luego suma estos precios. +$totalPrice = $products->select('category_id, SUM(price * stock) AS category_total') + ->group('category_id') + ->aggregation('SUM(category_total)', 'SUM'); +``` + +En este ejemplo, primero calculamos el precio total de los productos en cada categoría (`SUM(price * stock) AS category_total`) y agrupamos los resultados por `category_id`. Luego usamos `aggregation('SUM(category_total)', 'SUM')` para sumar estos subtotales `category_total`. El segundo argumento `'SUM'` indica que la función `SUM` debe aplicarse a los resultados intermedios. + + +Insertar, Actualizar y Eliminar +=============================== + +Nette Database Explorer simplifica la inserción, actualización y eliminación de datos. Todos los métodos mencionados lanzarán una excepción `Nette\Database\DriverException` en caso de error. + + +Selection::insert(iterable $data) .[method] +------------------------------------------- + +Inserta nuevos registros en la tabla. + +**Insertar un solo registro:** + +Pasamos el nuevo registro como un array asociativo o un objeto iterable (por ejemplo, `ArrayHash` usado en [formularios |forms:]), donde las claves corresponden a los nombres de las columnas en la tabla. + +Si la tabla tiene una clave primaria definida, el método devuelve un objeto `ActiveRow`, que se recarga desde la base de datos para reflejar cualquier cambio realizado a nivel de base de datos (disparadores, valores de columna predeterminados, cálculos de columnas autoincrementales). Esto asegura la consistencia de los datos y el objeto siempre contiene los datos actuales de la base de datos. Si no tiene una clave primaria única, devuelve los datos pasados en forma de array. ```php $row = $explorer->table('users')->insert([ - 'name' => $name, - 'year' => $year, + 'name' => 'John Doe', + 'email' => 'john.doe@example.com', ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) +// $row es una instancia de ActiveRow y contiene los datos completos de la fila insertada, +// incluyendo el ID generado automáticamente y cualquier cambio realizado por disparadores +echo $row->id; // Imprime el ID del usuario recién insertado +echo $row->created_at; // Imprime la hora de creación si está establecida por un disparador ``` -Si la clave primaria está definida en la tabla, se devuelve un objeto ActiveRow que contiene la fila insertada. +**Insertar múltiples registros a la vez:** -Inserción múltiple: +El método `insert()` permite insertar múltiples registros usando una sola consulta SQL. En este caso, devuelve el número de filas insertadas. ```php -$explorer->table('users')->insert([ +$insertedRows = $explorer->table('users')->insert([ + [ + 'name' => 'John', + 'year' => 1994, + ], [ - 'name' => 'Jim', - 'year' => 1978, - ], [ 'name' => 'Jack', - 'year' => 1987, + 'year' => 1995, ], ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) +// INSERT INTO `users` (`name`, `year`) VALUES ('John', 1994), ('Jack', 1995) +// $insertedRows será 2 +``` + +También se puede pasar un objeto `Selection` con una selección de datos como parámetro. + +```php +$newUsers = $explorer->table('potential_users') + ->where('approved', 1) + ->select('name, email'); + +$insertedRows = $explorer->table('users')->insert($newUsers); ``` -Se pueden pasar como parámetros ficheros u objetos DateTime: +**Insertar valores especiales:** + +También podemos pasar archivos, objetos `DateTime` o literales SQL como valores: ```php $explorer->table('users')->insert([ - 'name' => $name, - 'created' => new DateTime, // or $explorer::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // inserts the file + 'name' => 'John', + 'created_at' => new DateTime, // Convierte al formato de base de datos + 'avatar' => fopen('image.jpg', 'rb'), // Inserta el contenido binario del archivo + 'uuid' => $explorer::literal('UUID()'), // Llama a la función UUID() ]); ``` -Actualización (devuelve el recuento de filas afectadas): + +Selection::update(iterable $data): int .[method] +------------------------------------------------ + +Actualiza las filas en la tabla según el filtro especificado. Devuelve el número de filas realmente cambiadas. + +Pasamos las columnas a cambiar como un array asociativo o un objeto iterable (por ejemplo, `ArrayHash` usado en [formularios |forms:]), donde las claves corresponden a los nombres de las columnas en la tabla: ```php -$count = $explorer->table('users') - ->where('id', 10) // must be called before update() +$affected = $explorer->table('users') + ->where('id', 10) ->update([ - 'name' => 'Ned Stark' + 'name' => 'John Smith', + 'year' => 1994, ]); -// UPDATE `users` SET `name`='Ned Stark' WHERE (`id` = 10) +// UPDATE `users` SET `name` = 'John Smith', `year` = 1994 WHERE `id` = 10 ``` -Para actualizar podemos utilizar los operadores `+=` a `-=`: +Para cambiar valores numéricos, podemos usar los operadores `+=` y `-=`: ```php $explorer->table('users') + ->where('id', 10) ->update([ - 'age+=' => 1, // see += + 'points+=' => 1, // Incrementa el valor de la columna 'points' en 1 + 'coins-=' => 1, // Decrementa el valor de la columna 'coins' en 1 ]); -// UPDATE users SET `age` = `age` + 1 +// UPDATE `users` SET `points` = `points` + 1, `coins` = `coins` - 1 WHERE `id` = 10 ``` -Borrado (devuelve el recuento de filas borradas): + +Selection::delete(): int .[method] +---------------------------------- + +Elimina filas de la tabla según el filtro especificado. Devuelve el número de filas eliminadas. ```php $count = $explorer->table('users') ->where('id', 10) ->delete(); -// DELETE FROM `users` WHERE (`id` = 10) +// DELETE FROM `users` WHERE `id` = 10 ``` +.[caution] +Al llamar a `update()` y `delete()`, no olvides especificar las filas a modificar/eliminar usando `where()`. Si no usas `where()`, ¡la operación se realizará en toda la tabla! -Trabajar con relaciones .[#toc-working-with-relationships] -========================================================== +ActiveRow::update(iterable $data): bool .[method] +------------------------------------------------- -Tiene Una Relación .[#toc-has-one-relation] -------------------------------------------- -Tiene una relación es un caso de uso común. Libro *tiene un* autor. Libro *tiene un* traductor. La obtención de una fila relacionada se realiza principalmente mediante el método `ref()`. Acepta dos argumentos: nombre de la tabla de destino y columna de unión de origen. Véase el ejemplo: +Actualiza los datos en la fila de la base de datos representada por el objeto `ActiveRow`. Acepta un iterable con los datos a actualizar como parámetro (las claves son los nombres de las columnas). Para cambiar valores numéricos, podemos usar los operadores `+=` y `-=`: + +Después de realizar la actualización, `ActiveRow` se recarga automáticamente desde la base de datos para reflejar cualquier cambio realizado a nivel de base de datos (por ejemplo, disparadores). El método devuelve `true` solo si los datos realmente cambiaron. ```php -$book = $explorer->table('book')->get(1); -$book->ref('author', 'author_id'); +$article = $explorer->table('article')->get(1); +$article->update([ + 'views += 1', // Incrementamos el número de vistas +]); +echo $article->views; // Imprime el número actual de vistas ``` -En el ejemplo anterior obtenemos la entrada de autor relacionada de la tabla `author`, la clave primaria de autor se busca en la columna `book.author_id`. El método Ref() devuelve una instancia de ActiveRow o null si no hay ninguna entrada apropiada. La fila devuelta es una instancia de ActiveRow, por lo que podemos trabajar con ella del mismo modo que con la entrada del libro. +Este método actualiza solo una fila específica en la base de datos. Para la actualización masiva de múltiples filas, usa el método [#Selection::update()]. + + +ActiveRow::delete() .[method] +----------------------------- + +Elimina la fila de la base de datos representada por el objeto `ActiveRow`. ```php -$author = $book->ref('author', 'author_id'); -$author->name; -$author->born; +$book = $explorer->table('book')->get(1); +$book->delete(); // Elimina el libro con ID 1 +``` + +Este método elimina solo una fila específica en la base de datos. Para la eliminación masiva de múltiples filas, usa el método [#Selection::delete()]. + + +Relaciones entre tablas +======================= + +En las bases de datos relacionales, los datos se dividen en múltiples tablas y se interconectan mediante claves foráneas. Nette Database Explorer introduce una forma revolucionaria de trabajar con estas relaciones, sin escribir consultas JOIN y sin necesidad de configurar o generar nada. + +Para ilustrar el trabajo con relaciones, usaremos un ejemplo de base de datos de libros ([puedes encontrarlo en GitHub |https://github.com/nette-examples/books]). En la base de datos tenemos tablas: + +- `author` - escritores y traductores (columnas `id`, `name`, `web`, `born`) +- `book` - libros (columnas `id`, `author_id`, `translator_id`, `title`, `sequel_id`) +- `tag` - etiquetas (columnas `id`, `name`) +- `book_tag` - tabla de unión entre libros y etiquetas (columnas `book_id`, `tag_id`) + +[* db-schema-1-.webp *] *** Estructura de la base de datos utilizada en los ejemplos *** + +En nuestro ejemplo de base de datos de libros, encontramos varios tipos de relaciones (aunque el modelo está simplificado en comparación con la realidad): + +- Uno a muchos (1:N) – cada libro **tiene un** autor, un autor puede escribir **varios** libros. +- Cero a muchos (0:N) – un libro **puede tener** un traductor, un traductor puede traducir **varios** libros. +- Cero a uno (0:1) – un libro **puede tener** una secuela. +- Muchos a muchos (M:N) – un libro **puede tener varias** etiquetas y una etiqueta puede asignarse a **varios** libros. + +En estas relaciones, siempre hay una tabla padre y una tabla hija. Por ejemplo, en la relación entre autor y libro, la tabla `author` es la padre y `book` es la hija - podemos imaginarlo como que un libro siempre "pertenece" a algún autor. Esto también se refleja en la estructura de la base de datos: la tabla hija `book` contiene la clave foránea `author_id`, que hace referencia a la tabla padre `author`. -// or directly -$book->ref('author', 'author_id')->name; -$book->ref('author', 'author_id')->born; +Si necesitamos listar libros incluyendo los nombres de sus autores, tenemos dos opciones. O bien obtenemos los datos con una única consulta SQL usando JOIN: + +```sql +SELECT book.*, author.name FROM book LEFT JOIN author ON book.author_id = author.id +``` + +O cargamos los datos en dos pasos - primero los libros y luego sus autores - y luego los ensamblamos en PHP: + +```sql +SELECT * FROM book; +SELECT * FROM author WHERE id IN (1, 2, 3); -- IDs de los autores de los libros obtenidos ``` -Book también tiene un traductor, por lo que obtener el nombre del traductor es bastante fácil. +El segundo enfoque es en realidad más eficiente, aunque pueda sorprender. Los datos se cargan solo una vez y pueden utilizarse mejor en la caché. Así es exactamente como funciona Nette Database Explorer: resuelve todo bajo el capó y te ofrece una API elegante: + ```php -$book->ref('author', 'translator_id')->name +$books = $explorer->table('book'); +foreach ($books as $book) { + echo 'título: ' . $book->title; + echo 'escrito por: ' . $book->author->name; // $book->author es un registro de la tabla 'author' + echo 'traducido por: ' . $book->translator?->name; +} ``` -Todo esto está muy bien, pero es algo engorroso, ¿no cree? Database Explorer ya contiene las definiciones de las claves foráneas, así que ¿por qué no utilizarlas automáticamente? Hagámoslo. -Si llamamos a una propiedad que no existe, ActiveRow intenta resolver el nombre de la propiedad llamante como relación 'tiene una'. Obtener esta propiedad es lo mismo que llamar al método ref() con un solo argumento. Llamaremos **clave** al único argumento. La clave se resolverá como una relación particular de clave externa. La clave pasada se compara con las columnas de la fila, y si coincide, la clave foránea definida en la columna coincidente se utiliza para obtener datos de la tabla de destino relacionada. Véase el ejemplo: +Acceso a la tabla padre +----------------------- + +El acceso a la tabla padre es directo. Se trata de relaciones como *un libro tiene un autor* o *un libro puede tener un traductor*. Obtenemos el registro relacionado a través de una propiedad del objeto `ActiveRow`; su nombre corresponde al nombre de la columna de clave foránea sin `_id`: ```php -$book->author->name; -// same as -$book->ref('author')->name; +$book = $explorer->table('book')->get(1); +echo $book->author->name; // Encuentra al autor por la columna author_id +echo $book->translator?->name; // Encuentra al traductor por translator_id ``` -La instancia ActiveRow no tiene columna de autor. Se buscan todas las columnas de libros que coincidan con *key*. En este caso, la coincidencia significa que el nombre de la columna debe contener la clave. Así, en el ejemplo anterior, la columna `author_id` contiene la cadena "author" y, por lo tanto, coincide con la clave "author". Si desea obtener el traductor del libro, sólo tiene que utilizar, por ejemplo, "traductor" como clave, porque la clave "traductor" coincidirá con la columna `translator_id`. Encontrará más información sobre la lógica de correspondencia de claves en el capítulo [Expresiones de unión |#joining-key]. +Cuando accedemos a la propiedad `$book->author`, Explorer busca en la tabla `book` una columna cuyo nombre contenga la cadena `author` (es decir, `author_id`). Según el valor en esta columna, carga el registro correspondiente de la tabla `author` y lo devuelve como `ActiveRow`. De manera similar funciona `$book->translator`, que utiliza la columna `translator_id`. Dado que la columna `translator_id` puede contener `null`, usamos el operador `?->` en el código. + +Una ruta alternativa la ofrece el método `ref()`, que acepta dos argumentos, el nombre de la tabla de destino y el nombre de la columna de unión, y devuelve una instancia de `ActiveRow` o `null`: ```php -echo $book->title . ': '; -echo $book->author->name; -if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; -} +echo $book->ref('author', 'author_id')->name; // Relación con el autor +echo $book->ref('author', 'translator_id')->name; // Relación con el traductor ``` -Si desea buscar varios libros, utilice el mismo método. Nette Database Explorer obtendrá los autores y traductores de todos los libros obtenidos a la vez. +El método `ref()` es útil si no se puede usar el acceso a través de la propiedad porque la tabla contiene una columna con el mismo nombre (es decir, `author`). En otros casos, se recomienda usar el acceso a través de la propiedad, que es más legible. + +Explorer optimiza automáticamente las consultas a la base de datos. Cuando iteramos sobre libros en un bucle y accedemos a sus registros relacionados (autores, traductores), Explorer no genera una consulta para cada libro por separado. En su lugar, ejecuta solo un SELECT para cada tipo de relación, reduciendo significativamente la carga de la base de datos. Por ejemplo: ```php $books = $explorer->table('book'); foreach ($books as $book) { echo $book->title . ': '; echo $book->author->name; - if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; - } + echo $book->translator?->name; } ``` -El código sólo ejecutará estas 3 consultas: +Este código solo llama a estas tres consultas rápidas a la base de datos: + ```sql SELECT * FROM `book`; -SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- ids of fetched books from author_id column -SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- ids of fetched books from translator_id column +SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- IDs de la columna author_id de los libros seleccionados +SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- IDs de la columna translator_id de los libros seleccionados ``` +.[note] +La lógica para encontrar la columna de unión viene dada por la implementación de [Conventions |api:Nette\Database\Conventions]. Recomendamos usar [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions], que analiza las claves foráneas y permite trabajar fácilmente con las relaciones existentes entre tablas. -Tiene mucha relación .[#toc-has-many-relation] ----------------------------------------------- -La relación "tiene muchos" es la relación inversa a "tiene uno". El autor *ha* escrito *muchos* libros. El autor *ha* traducido *muchos* libros. Como puedes ver, este tipo de relación es un poco más difícil porque la relación tiene nombre ('escrito', 'traducido'). La instancia ActiveRow tiene el método `related()`, que devolverá un array de entradas relacionadas. Las entradas también son instancias de ActiveRow. Véase el ejemplo siguiente: +Acceso a la tabla hija +---------------------- + +El acceso a la tabla hija funciona en la dirección opuesta. Ahora preguntamos *qué libros escribió este autor* o *tradujo este traductor*. Para este tipo de consulta, usamos el método `related()`, que devuelve una `Selection` con registros relacionados. Veamos un ejemplo: ```php -$author = $explorer->table('author')->get(11); -echo $author->name . ' has written:'; +$author = $explorer->table('author')->get(1); +// Imprime todos los libros del autor foreach ($author->related('book.author_id') as $book) { - echo $book->title; + echo "Escrito por: $book->title"; } -echo 'and translated:'; +// Imprime todos los libros que el autor tradujo foreach ($author->related('book.translator_id') as $book) { - echo $book->title; + echo "Traducido por: $book->title"; } ``` -Método `related()` El método acepta la descripción completa de la unión pasada como dos argumentos o como un argumento unido por un punto. El primer argumento es la tabla de destino, el segundo es la columna de destino. +El método `related()` acepta la descripción de la unión como un argumento con notación de puntos o como dos argumentos separados: ```php -$author->related('book.translator_id'); -// same as -$author->related('book', 'translator_id'); +$author->related('book.translator_id'); // Un argumento +$author->related('book', 'translator_id'); // Dos argumentos ``` -Puede utilizar la heurística de Nette Database Explorer basada en claves externas y proporcionar sólo el argumento **clave**. La clave se comparará con todas las claves externas que apunten a la tabla actual (`author` table). Si hay una coincidencia, Nette Database Explorer usará esta clave foránea, de lo contrario lanzará una [Nette\InvalidArgumentException |api:Nette\InvalidArgumentException] o [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. Puede encontrar más información sobre la lógica de coincidencia de claves en el capítulo [Expresiones de unión |#joining-key]. +Explorer puede detectar automáticamente la columna de unión correcta basándose en el nombre de la tabla padre. En este caso, la unión se realiza a través de la columna `book.author_id`, porque el nombre de la tabla de origen es `author`: -Por supuesto, puede llamar a métodos relacionados para todos los autores obtenidos, Nette Database Explorer obtendrá de nuevo los libros apropiados a la vez. +```php +$author->related('book'); // Usa book.author_id +``` + +Si hubiera múltiples uniones posibles, Explorer lanzaría una excepción [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. + +Por supuesto, también podemos usar el método `related()` al iterar sobre múltiples registros en un bucle, y Explorer también optimiza automáticamente las consultas en este caso: ```php $authors = $explorer->table('author'); foreach ($authors as $author) { - echo $author->name . ' has written:'; + echo $author->name . ' escribió:'; foreach ($author->related('book') as $book) { - $book->title; + echo $book->title; } } ``` -El ejemplo anterior sólo ejecutará dos consultas: +Este código genera solo dos consultas SQL rápidas: ```sql SELECT * FROM `author`; -SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- ids of fetched authors +SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- IDs de los autores seleccionados +``` + + +Relación Muchos a Muchos +------------------------ + +Para una relación muchos a muchos (M:N), se requiere una tabla de unión (en nuestro caso `book_tag`), que contiene dos columnas con claves foráneas (`book_id`, `tag_id`). Cada una de estas columnas hace referencia a la clave primaria de una de las tablas conectadas. Para obtener los datos relacionados, primero obtenemos los registros de la tabla de unión usando `related('book_tag')` y luego continuamos hacia los datos de destino: + +```php +$book = $explorer->table('book')->get(1); +// Imprime los nombres de las etiquetas asignadas al libro +foreach ($book->related('book_tag') as $bookTag) { + echo $bookTag->tag->name; // Imprime el nombre de la etiqueta a través de la tabla de unión +} + +$tag = $explorer->table('tag')->get(1); +// O viceversa: imprime los nombres de los libros etiquetados con esta etiqueta +foreach ($tag->related('book_tag') as $bookTag) { + echo $bookTag->book->title; // Imprime el título del libro +} ``` +Explorer optimiza de nuevo las consultas SQL a una forma eficiente: + +```sql +SELECT * FROM `book`; +SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 2, ...)); -- IDs de los libros seleccionados +SELECT * FROM `tag` WHERE (`tag`.`id` IN (1, 2, ...)); -- IDs de las etiquetas encontradas en book_tag +``` -Creación manual del explorador .[#toc-creating-explorer-manually] -================================================================= -Se puede crear una conexión a la base de datos utilizando la configuración de la aplicación. En estos casos se crea un servicio `Nette\Database\Explorer` que puede pasarse como dependencia utilizando el contenedor DI. +Consultas a través de tablas relacionadas +----------------------------------------- -Sin embargo, si Nette Database Explorer se utiliza como herramienta independiente, es necesario crear manualmente una instancia del objeto `Nette\Database\Explorer`. +En los métodos `where()`, `select()`, `order()` y `group()`, podemos usar notaciones especiales para acceder a columnas de otras tablas. Explorer crea automáticamente los `JOIN` necesarios. + +**La notación de puntos** (`tabla_padre.columna`) se utiliza para la relación 1:N desde la perspectiva de la tabla hija: ```php -// $storage implements Nette\Caching\Storage: -$storage = new Nette\Caching\Storages\FileStorage($tempDir); -$connection = new Nette\Database\Connection($dsn, $user, $password); -$structure = new Nette\Database\Structure($connection, $storage); -$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); -$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +$books = $explorer->table('book'); + +// Encuentra libros cuyo autor tiene un nombre que empieza por 'Jon' +$books->where('author.name LIKE ?', 'Jon%'); + +// Ordena los libros por nombre de autor en orden descendente +$books->order('author.name DESC'); + +// Imprime el título del libro y el nombre del autor +$books->select('book.title, author.name'); ``` + +**La notación de dos puntos** (`:tabla_hija.columna`) se utiliza para la relación 1:N desde la perspectiva de la tabla padre: + +```php +$authors = $explorer->table('author'); + +// Encuentra autores que escribieron un libro con 'PHP' en el título +$authors->where(':book.title LIKE ?', '%PHP%'); + +// Cuenta el número de libros para cada autor +$authors->select('*, COUNT(:book.id) AS book_count') + ->group('author.id'); +``` + +En el ejemplo anterior con notación de dos puntos (`:book.title`), no se especifica la columna de clave foránea. Explorer detecta automáticamente la columna correcta basándose en el nombre de la tabla padre. En este caso, la unión se realiza a través de la columna `book.author_id`, porque el nombre de la tabla de origen es `author`. Si hubiera múltiples uniones posibles, Explorer lanzaría una excepción [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. + +La columna de unión se puede especificar explícitamente entre paréntesis: + +```php +// Encuentra autores que tradujeron un libro con 'PHP' en el título +$authors->where(':book(translator_id).title LIKE ?', '%PHP%'); +``` + +Las notaciones se pueden encadenar para acceder a través de múltiples tablas: + +```php +// Encuentra autores de libros etiquetados con 'PHP' +$authors->where(':book:book_tag.tag.name', 'PHP') + ->group('author.id'); +``` + + +Extensión de condiciones para JOIN +---------------------------------- + +El método `joinWhere()` extiende las condiciones que se especifican al unir tablas en SQL después de la palabra clave `ON`. + +Supongamos que queremos encontrar libros traducidos por un traductor específico: + +```php +// Encuentra libros traducidos por el traductor llamado 'David' +$books = $explorer->table('book') + ->joinWhere('translator', 'translator.name', 'David'); +// LEFT JOIN author translator ON book.translator_id = translator.id AND (translator.name = 'David') +``` + +En la condición `joinWhere()`, podemos usar las mismas construcciones que en el método `where()` - operadores, marcadores de posición de signo de interrogación, arrays de valores o expresiones SQL. + +Para consultas más complejas con múltiples `JOIN`, podemos definir alias de tabla: + +```php +$tags = $explorer->table('tag') + ->joinWhere(':book_tag.book.author', 'book_author.born < ?', 1950) + ->alias(':book_tag.book.author', 'book_author'); +// LEFT JOIN `book_tag` ON `tag`.`id` = `book_tag`.`tag_id` +// LEFT JOIN `book` ON `book_tag`.`book_id` = `book`.`id` +// LEFT JOIN `author` `book_author` ON `book`.`author_id` = `book_author`.`id` +// AND (`book_author`.`born` < 1950) +``` + +Ten en cuenta que mientras el método `where()` añade condiciones a la cláusula `WHERE`, el método `joinWhere()` extiende las condiciones en la cláusula `ON` al unir tablas. diff --git a/database/es/guide.texy b/database/es/guide.texy new file mode 100644 index 0000000000..bf912ef423 --- /dev/null +++ b/database/es/guide.texy @@ -0,0 +1,216 @@ +Nette Database +************** + +.[perex] +Nette Database es una capa de base de datos potente y elegante para PHP con énfasis en la simplicidad y funciones inteligentes. Ofrece dos formas de trabajar con la base de datos: [Explorer |explorer] para el desarrollo rápido de aplicaciones, o [acceso SQL |sql-way] para el trabajo directo con consultas. + +<div class="grid gap-3"> +<div> + + +[Acceso SQL |sql-way] +===================== +- Consultas parametrizadas seguras +- Control preciso sobre la forma de las consultas SQL +- Cuando escribe consultas complejas con funciones avanzadas +- Optimiza el rendimiento utilizando funciones SQL específicas + +</div> + +<div> + + +[Explorer |explorer] +==================== +- Desarrolla rápidamente sin escribir SQL +- Trabajo intuitivo con relaciones entre tablas +- Apreciará la optimización automática de consultas +- Adecuado para un trabajo rápido y cómodo con la base de datos + +</div> + +</div> + + +Instalación +=========== + +Descarga e instala la librería usando la herramienta [Composer |best-practices:composer]: + +```shell +composer require nette/database +``` + + +Bases de datos soportadas +========================= + +Nette Database soporta las siguientes bases de datos: + +|* Servidor de base de datos |* Nombre DSN |* Soporte en Explorer +|---------------------|-------------|----------------------- +| MySQL (>= 5.1) | mysql | SÍ +| PostgreSQL (>= 9.0) | pgsql | SÍ +| Sqlite 3 (>= 3.8) | sqlite | SÍ +| Oracle | oci | - +| MS SQL (PDO_SQLSRV) | sqlsrv | SÍ +| MS SQL (PDO_DBLIB) | mssql | - +| ODBC | odbc | - + + +Dos enfoques para la base de datos +================================== + +Nette Database te da una opción: puedes escribir consultas SQL directamente (acceso SQL), o dejar que se generen automáticamente (Explorer). Veamos cómo ambos enfoques resuelven las mismas tareas: + +[Acceso SQL |sql-way] - Consultas SQL + +```php +// insertar registro +$database->query('INSERT INTO books', [ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// obtener registros: autores de libros +$result = $database->query(' + SELECT authors.*, COUNT(books.id) AS books_count + FROM authors + LEFT JOIN books ON authors.id = books.author_id + WHERE authors.active = 1 + GROUP BY authors.id +'); + +// listado (no óptimo, genera N consultas adicionales) +foreach ($result as $author) { + $books = $database->query(' + SELECT * FROM books + WHERE author_id = ? + ORDER BY published_at DESC + ', $author->id); + + echo "Autor $author->name escribió $author->books_count libros:\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +[Enfoque Explorer |explorer] - generación automática de SQL + +```php +// insertar registro +$database->table('books')->insert([ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// obtener registros: autores de libros +$authors = $database->table('authors') + ->where('active', 1); + +// listado (genera automáticamente solo 2 consultas optimizadas) +foreach ($authors as $author) { + $books = $author->related('books') + ->order('published_at DESC'); + + echo "Autor $author->name escribió {$books->count()} libros:\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +El enfoque Explorer genera y optimiza las consultas SQL automáticamente. En el ejemplo dado, el acceso SQL generará N+1 consultas (una para los autores y luego una para los libros de cada autor), mientras que Explorer optimiza automáticamente las consultas y realiza solo dos: una para los autores y otra para todos sus libros. + +Ambos enfoques se pueden combinar libremente en la aplicación según sea necesario. + + +Conexión y configuración +======================== + +Para conectarse a la base de datos, simplemente crea una instancia de la clase [api:Nette\Database\Connection]: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password); +``` + +El parámetro `$dsn` (data source name) es el mismo [que utiliza PDO |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], por ejemplo, `mysql:host=127.0.0.1;dbname=test`. En caso de fallo, lanza una excepción `Nette\Database\ConnectionException`. + +Sin embargo, una forma más conveniente es ofrecida por la [configuración de la aplicación |configuration], donde simplemente necesitas agregar la sección `database` y se crearán los objetos necesarios, así como el panel de base de datos en la barra [Tracy |tracy:]. + +```neon +database: + dsn: 'mysql:host=127.0.0.1;dbname=test' + user: root + password: password +``` + +Luego, [obtenemos el objeto de conexión como servicio del contenedor DI |dependency-injection:passing-dependencies], por ejemplo: + +```php +class Model +{ + public function __construct( + // o Nette\Database\Explorer + private Nette\Database\Connection $database, + ) { + } +} +``` + +Más información sobre la [configuración de la base de datos |configuration]. + + +Creación manual de Explorer +--------------------------- + +Si no utilizas el contenedor Nette DI, puedes crear manualmente una instancia de `Nette\Database\Explorer`: + +```php +// conexión a la base de datos +$connection = new Nette\Database\Connection('mysql:host=127.0.0.1;dbname=mydatabase', 'user', 'password'); +// almacenamiento para caché, implementa Nette\Caching\Storage, por ejemplo: +$storage = new Nette\Caching\Storages\FileStorage('/ruta/a/directorio/temp'); +// se encarga de la reflexión de la estructura de la base de datos +$structure = new Nette\Database\Structure($connection, $storage); +// define reglas para mapear nombres de tablas, columnas y claves foráneas +$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); +$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +``` + + +Gestión de la conexión +====================== + +Al crear un objeto `Connection`, la conexión se establece automáticamente. Si deseas posponer la conexión, utiliza el modo lazy; puedes activarlo en la [configuración |configuration] estableciendo `lazy: true`, o de esta manera: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password, ['lazy' => true]); +``` + +Para gestionar la conexión, utiliza los métodos `connect()`, `disconnect()` y `reconnect()`. +- `connect()` crea una conexión si aún no existe, y puede lanzar una excepción `Nette\Database\ConnectionException`. +- `disconnect()` desconecta la conexión actual a la base de datos. +- `reconnect()` realiza una desconexión y luego una reconexión a la base de datos. Este método también puede lanzar una excepción `Nette\Database\ConnectionException`. + +Además, puedes monitorear los eventos relacionados con la conexión utilizando el evento `onConnect`, que es un array de callbacks que se llaman después de establecer una conexión con la base de datos. + +```php +// se ejecuta después de conectarse a la base de datos +$database->onConnect[] = function($database) { + echo "Conectado a la base de datos"; +}; +``` + + +Tracy Debug Bar +=============== + +Si utilizas [Tracy |tracy:], el panel Database se activa automáticamente en la barra de depuración, mostrando todas las consultas ejecutadas, sus parámetros, el tiempo de ejecución y la ubicación en el código donde fueron llamadas. + +[* db-panel.webp *] diff --git a/database/es/mapping.texy b/database/es/mapping.texy new file mode 100644 index 0000000000..615c68b2e5 --- /dev/null +++ b/database/es/mapping.texy @@ -0,0 +1,55 @@ +Conversión de tipos +******************* + +.[perex] +Nette Database convierte automáticamente los valores devueltos de la base de datos a los tipos PHP correspondientes. + + +Fecha y hora +------------ + +Los datos de tiempo se convierten en objetos `Nette\Utils\DateTime`. Si desea que los datos de tiempo se conviertan en objetos inmutables `Nette\Database\DateTime`, establezca la opción `newDateTime` en true en la [configuración|configuration]. + +```php +$row = $database->fetch('SELECT created_at FROM articles'); +echo $row->created_at instanceof DateTime; // true +echo $row->created_at->format('j. n. Y'); +``` + +En el caso de MySQL, convierte el tipo de dato `TIME` en objetos `DateInterval`. + + +Valores booleanos +----------------- + +Los valores booleanos se convierten automáticamente a `true` o `false`. Para MySQL, `TINYINT(1)` se convierte si establecemos `convertBoolean: true` en la [configuración |configuration]. + +```php +$row = $database->fetch('SELECT is_published FROM articles'); +echo gettype($row->is_published); // 'boolean' +``` + + +Valores numéricos +----------------- + +Los valores numéricos se convierten a `int` o `float` según el tipo de columna en la base de datos: + +```php +$row = $database->fetch('SELECT id, price FROM products'); +echo gettype($row->id); // integer +echo gettype($row->price); // double (o float) +``` + + +Normalización personalizada +--------------------------- + +Usando el método `setRowNormalizer(?callable $normalizer)`, puedes establecer una función personalizada para transformar las filas de la base de datos. Esto es útil, por ejemplo, para la conversión automática de tipos de datos. + +```php +$database->setRowNormalizer(function(array $row, ResultSet $resultSet): array { + // aquí ocurre la conversión de tipos + return $row; +}); +``` diff --git a/database/es/reflection.texy b/database/es/reflection.texy new file mode 100644 index 0000000000..f948bdbf74 --- /dev/null +++ b/database/es/reflection.texy @@ -0,0 +1,125 @@ +Reflexión de la estructura +************************** + +.{data-version:3.2.1} +Nette Database proporciona herramientas para la introspección de la estructura de la base de datos utilizando la clase [api:Nette\Database\Reflection\Reflection]. Permite obtener información sobre tablas, columnas, índices y claves foráneas. Puedes utilizar la reflexión para generar esquemas, crear aplicaciones flexibles que trabajen con la base de datos o herramientas generales de base de datos. + +Obtenemos el objeto de reflexión de la instancia de conexión a la base de datos: + +```php +$reflection = $database->getReflection(); +``` + + +Obtención de tablas +------------------- + +La propiedad de solo lectura `$reflection->tables` contiene un array asociativo de todas las tablas en la base de datos: + +```php +// Listado de nombres de todas las tablas +foreach ($reflection->tables as $name => $table) { + echo $name . "\n"; +} +``` + +También hay dos métodos disponibles: + +```php +// Verificar la existencia de una tabla +if ($reflection->hasTable('users')) { + echo "La tabla users existe"; +} + +// Devuelve el objeto de la tabla; si no existe, lanza una excepción +$table = $reflection->getTable('users'); +``` + + +Información sobre la tabla +-------------------------- + +La tabla está representada por el objeto [Table |api:Nette\Database\Reflection\Table], que proporciona las siguientes propiedades de solo lectura: + +- `$name: string` – nombre de la tabla +- `$view: bool` – si es una vista +- `$fullName: ?string` – nombre completo de la tabla incluyendo el esquema (si existe) +- `$columns: array<string, Column>` – array asociativo de columnas de la tabla +- `$indexes: Index[]` – array de índices de la tabla +- `$primaryKey: ?Index` – clave primaria de la tabla o `null` +- `$foreignKeys: ForeignKey[]` – array de claves foráneas de la tabla + + +Columnas +-------- + +La propiedad `columns` de la tabla proporciona un array asociativo de columnas, donde la clave es el nombre de la columna y el valor es una instancia de [Column |api:Nette\Database\Reflection\Column] con estas propiedades: + +- `$name: string` – nombre de la columna +- `$table: ?Table` – referencia a la tabla de la columna +- `$nativeType: string` – tipo de dato nativo de la base de datos +- `$size: ?int` – tamaño/longitud del tipo +- `$nullable: bool` – si la columna puede contener NULL +- `$default: mixed` – valor por defecto de la columna +- `$autoIncrement: bool` – si la columna es auto-increment +- `$primary: bool` – si es parte de la clave primaria +- `$vendor: array` – metadatos adicionales específicos del sistema de base de datos + +```php +foreach ($table->columns as $name => $column) { + echo "Columna: $name\n"; + echo "Tipo: {$column->nativeType}\n"; + echo "Nullable: " . ($column->nullable ? 'Sí' : 'No') . "\n"; +} +``` + + +Índices +------- + +La propiedad `indexes` de la tabla proporciona un array de índices, donde cada índice es una instancia de [Index |api:Nette\Database\Reflection\Index] con estas propiedades: + +- `$columns: Column[]` – array de columnas que forman el índice +- `$unique: bool` – si el índice es único +- `$primary: bool` – si es la clave primaria +- `$name: ?string` – nombre del índice + +La clave primaria de la tabla se puede obtener usando la propiedad `primaryKey`, que devuelve un objeto `Index` o `null` si la tabla no tiene clave primaria. + +```php +// Listado de índices +foreach ($table->indexes as $index) { + $columns = implode(', ', array_map(fn($col) => $col->name, $index->columns)); + echo "Índice" . ($index->name ? " {$index->name}" : '') . ":\n"; + echo " Columnas: $columns\n"; + echo " Único: " . ($index->unique ? 'Sí' : 'No') . "\n"; +} + +// Listado de la clave primaria +if ($primaryKey = $table->primaryKey) { + $columns = implode(', ', array_map(fn($col) => $col->name, $primaryKey->columns)); + echo "Clave primaria: $columns\n"; +} +``` + + +Claves foráneas +--------------- + +La propiedad `foreignKeys` de la tabla proporciona un array de claves foráneas, donde cada clave foránea es una instancia de [ForeignKey |api:Nette\Database\Reflection\ForeignKey] con estas propiedades: + +- `$foreignTable: Table` – tabla referenciada +- `$localColumns: Column[]` – array de columnas locales +- `$foreignColumns: Column[]` – array de columnas referenciadas +- `$name: ?string` – nombre de la clave foránea + +```php +// Listado de claves foráneas +foreach ($table->foreignKeys as $fk) { + $localCols = implode(', ', array_map(fn($col) => $col->name, $fk->localColumns)); + $foreignCols = implode(', ', array_map(fn($col) => $col->name, $fk->foreignColumns)); + + echo "FK" . ($fk->name ? " {$fk->name}" : '') . ":\n"; + echo " $localCols -> {$fk->foreignTable->name}($foreignCols)\n"; +} +``` diff --git a/database/es/security.texy b/database/es/security.texy new file mode 100644 index 0000000000..b72f09669a --- /dev/null +++ b/database/es/security.texy @@ -0,0 +1,185 @@ +Riesgos de seguridad +******************** + +<div class=perex> + +La base de datos a menudo contiene datos sensibles y permite realizar operaciones peligrosas. Para trabajar de forma segura con Nette Database es clave: + +- Comprender la diferencia entre API segura y peligrosa +- Usar consultas parametrizadas +- Validar correctamente los datos de entrada + +</div> + + +¿Qué es SQL Injection? +====================== + +SQL injection es el riesgo de seguridad más grave al trabajar con bases de datos. Ocurre cuando la entrada no tratada del usuario se convierte en parte de una consulta SQL. Un atacante puede insertar sus propios comandos SQL y así: +- Obtener acceso no autorizado a los datos +- Modificar o eliminar datos en la base de datos +- Omitir la autenticación + +```php +// ❌ CÓDIGO PELIGROSO - vulnerable a inyección SQL +$database->query("SELECT * FROM users WHERE name = '$_GET[name]'"); + +// Un atacante puede introducir, por ejemplo, el valor: ' OR '1'='1 +// La consulta resultante será: SELECT * FROM users WHERE name = '' OR '1'='1' +// Lo que devolverá todos los usuarios +``` + +Lo mismo se aplica a Database Explorer: + +```php +// ❌ CÓDIGO PELIGROSO - vulnerable a inyección SQL +$table->where('name = ' . $_GET['name']); +$table->where("name = '$_GET[name]'"); +``` + + +Consultas parametrizadas +======================== + +La defensa básica contra la inyección SQL son las consultas parametrizadas. Nette Database ofrece varias formas de usarlas. + +La forma más sencilla es usar **signos de interrogación como marcadores de posición**: + +```php +// ✅ Consulta parametrizada segura +$database->query('SELECT * FROM users WHERE name = ?', $name); + +// ✅ Condición segura en Explorer +$table->where('name = ?', $name); +``` + +Esto se aplica a todos los demás métodos en [Database Explorer |explorer] que permiten insertar expresiones con marcadores de posición y parámetros. + +Para los comandos `INSERT`, `UPDATE` o la cláusula `WHERE`, podemos pasar los valores en un array: + +```php +// ✅ INSERT seguro +$database->query('INSERT INTO users', [ + 'name' => $name, + 'email' => $email, +]); + +// ✅ INSERT seguro en Explorer +$table->insert([ + 'name' => $name, + 'email' => $email, +]); +``` + + +Validación de valores de parámetros +=================================== + +Las consultas parametrizadas son la piedra angular del trabajo seguro con bases de datos. Sin embargo, los valores que insertamos en ellas deben pasar por varios niveles de control: + + +Control de tipo +--------------- + +**Lo más importante es asegurar el tipo de dato correcto de los parámetros** - esta es una condición necesaria para el uso seguro de Nette Database. La base de datos asume que todos los datos de entrada tienen el tipo de dato correcto correspondiente a la columna dada. + +Por ejemplo, si `$name` en los ejemplos anteriores fuera inesperadamente un array en lugar de una cadena, Nette Database intentaría insertar todos sus elementos en la consulta SQL, lo que llevaría a un error. Por lo tanto, **nunca uses** datos no validados de `$_GET`, `$_POST` o `$_COOKIE` directamente en las consultas de base de datos. + + +Control de formato +------------------ + +En el segundo nivel, verificamos el formato de los datos, por ejemplo, si las cadenas están en codificación UTF-8 y su longitud corresponde a la definición de la columna, o si los valores numéricos están dentro del rango permitido para el tipo de dato de la columna. + +En este nivel de validación, podemos confiar parcialmente en la propia base de datos: muchas bases de datos rechazarán datos no válidos. Sin embargo, el comportamiento puede variar, algunas pueden truncar silenciosamente cadenas largas o recortar números fuera de rango. + + +Control de dominio +------------------ + +El tercer nivel son los controles lógicos específicos de tu aplicación. Por ejemplo, verificar que los valores de los select boxes correspondan a las opciones ofrecidas, que los números estén en el rango esperado (por ejemplo, edad 0-150 años) o que las dependencias mutuas entre los valores tengan sentido. + + +Métodos de validación recomendados +---------------------------------- + +- Usa [Nette Forms |forms:], que aseguran automáticamente la validación correcta de todas las entradas. +- Usa [Presenters |application:] e indica los tipos de datos para los parámetros en los métodos `action*()` y `render*()`. +- O implementa tu propia capa de validación usando herramientas estándar de PHP como `filter_var()`. + + +Trabajo seguro con columnas +=========================== + +En la sección anterior, mostramos cómo validar correctamente los valores de los parámetros. Sin embargo, al usar arrays en consultas SQL, debemos prestar la misma atención a sus claves. + +```php +// ❌ CÓDIGO PELIGROSO - las claves en el array no están tratadas +$database->query('INSERT INTO users', $_POST); +``` + +Para los comandos `INSERT` y `UPDATE`, este es un error de seguridad fundamental: un atacante puede insertar o cambiar cualquier columna en la base de datos. Podría, por ejemplo, establecer `is_admin = 1` o insertar datos arbitrarios en columnas sensibles (la llamada Vulnerabilidad de Asignación Masiva). + +En las condiciones `WHERE`, es aún más peligroso, ya que pueden contener operadores: + +```php +// ❌ CÓDIGO PELIGROSO - las claves en el array no están tratadas +$_POST['salary >'] = 100000; +$database->query('SELECT * FROM users WHERE', $_POST); +// ejecuta la consulta WHERE (`salary` > 100000) +``` + +Un atacante puede usar este enfoque para averiguar sistemáticamente los salarios de los empleados. Comenzará, por ejemplo, con una consulta sobre salarios superiores a 100.000, luego inferiores a 50.000, y reduciendo gradualmente el rango, puede descubrir los salarios aproximados de todos los empleados. Este tipo de ataque se llama enumeración SQL. + +Los métodos `where()` y `whereOr()` son aún [mucho más flexibles |explorer#where] y admiten expresiones SQL, incluidos operadores y funciones, en claves y valores. Esto le da al atacante la posibilidad de realizar una inyección SQL: + +```php +// ❌ CÓDIGO PELIGROSO - el atacante puede insertar su propio SQL +$_POST = ['0) UNION SELECT name, salary FROM users WHERE (1']; +$table->where($_POST); +// ejecuta la consulta WHERE (0) UNION SELECT name, salary FROM users WHERE (1) +``` + +Este ataque finaliza la condición original con `0)`, adjunta su propio `SELECT` usando `UNION` para obtener datos sensibles de la tabla `users` y cierra la consulta sintácticamente correcta con `WHERE (1)`. + + +Lista blanca de columnas +------------------------ + +Para trabajar de forma segura con los nombres de las columnas, necesitamos un mecanismo que garantice que el usuario solo pueda trabajar con las columnas permitidas y no pueda agregar las suyas propias. Podríamos intentar detectar y bloquear nombres de columnas peligrosos (lista negra), pero este enfoque no es fiable: un atacante siempre puede encontrar una nueva forma de escribir un nombre de columna peligroso que no previmos. + +Por lo tanto, es mucho más seguro invertir la lógica y definir una lista explícita de columnas permitidas (lista blanca): + +```php +// Columnas que el usuario puede editar +$allowedColumns = ['name', 'email', 'active']; + +// Eliminamos todas las columnas no permitidas de la entrada +$filteredData = array_intersect_key($userData, array_flip($allowedColumns)); // Use array_flip for keys + +// ✅ Ahora podemos usar $filteredData de forma segura en consultas, como por ejemplo: +$database->query('INSERT INTO users', $filteredData); +$table->update($filteredData); +$table->where($filteredData); +``` + + +Identificadores dinámicos +========================= + +Para nombres dinámicos de tablas y columnas, usa el marcador de posición `?name`. Esto asegura el escape correcto de los identificadores según la sintaxis de la base de datos dada (por ejemplo, usando comillas invertidas en MySQL): + +```php +// ✅ Uso seguro de identificadores confiables definidos en la aplicación +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name', $column, $table); +// Resultado en MySQL: SELECT `name` FROM `users` +``` + +Importante: usa el símbolo `?name` solo para valores confiables definidos en el código de la aplicación. Para valores del usuario, usa nuevamente la [lista blanca |#Lista blanca de columnas]. De lo contrario, te expones a riesgos de seguridad: + +```php +// ❌ PELIGROSO - nunca uses la entrada del usuario para nombres de columnas/tablas +$database->query('SELECT ?name FROM users', $_GET['column']); +``` diff --git a/database/es/sql-way.texy b/database/es/sql-way.texy new file mode 100644 index 0000000000..9f8a66af5a --- /dev/null +++ b/database/es/sql-way.texy @@ -0,0 +1,513 @@ +Acceso SQL +********** + +.[perex] +Nette Database ofrece dos vías: puede escribir consultas SQL usted mismo (acceso SQL), o dejar que se generen automáticamente (consulte [Explorer |explorer]). El acceso SQL le da control total sobre las consultas y al mismo tiempo asegura su construcción segura. + +.[note] +Los detalles sobre la conexión y configuración de la base de datos se pueden encontrar en el capítulo [Conexión y configuración |guide#Conexión y configuración]. + + +Consultas básicas +================= + +Para consultar la base de datos, se utiliza el método `query()`. Devuelve un objeto [ResultSet |api:Nette\Database\ResultSet], que representa el resultado de la consulta. En caso de fallo, el método [lanza una excepción |exceptions]. Podemos recorrer el resultado de la consulta usando un bucle `foreach`, o usar una de las [funciones auxiliares |#Obtención de datos]. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; +} +``` + +Para insertar valores de forma segura en consultas SQL, usamos consultas parametrizadas. Nette Database las hace extremadamente simples: solo agregue una coma y el valor después de la consulta SQL: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Con múltiples parámetros, tiene dos opciones de sintaxis. Puede "intercalar" la consulta SQL con parámetros: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age); +``` + +O escribir primero toda la consulta SQL y luego adjuntar todos los parámetros: + +```php +$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age); +``` + + +Protección contra inyección SQL +=============================== + +¿Por qué es importante usar consultas parametrizadas? Porque lo protegen de un ataque llamado inyección SQL, en el que un atacante podría introducir sus propios comandos SQL y así obtener o dañar datos en la base de datos. + +.[warning] +**¡Nunca inserte variables directamente en la consulta SQL!** Siempre use consultas parametrizadas, que lo protegerán de la inyección SQL. + +```php +// ❌ CÓDIGO PELIGROSO - vulnerable a la inyección SQL +$database->query("SELECT * FROM users WHERE name = '$name'"); + +// ✅ Consulta parametrizada segura +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Familiarícese con los [posibles riesgos de seguridad |security]. + + +Técnicas de consulta +==================== + + +Condiciones WHERE +----------------- + +Puede escribir condiciones WHERE como un array asociativo, donde las claves son los nombres de las columnas y los valores son los datos para la comparación. Nette Database selecciona automáticamente el operador SQL más adecuado según el tipo de valor. + +```php +$database->query('SELECT * FROM users WHERE', [ + 'name' => 'John', + 'active' => true, +]); +// WHERE `name` = 'John' AND `active` = 1 +``` + +También puede especificar explícitamente el operador de comparación en la clave: + +```php +$database->query('SELECT * FROM users WHERE', [ + 'age >' => 25, // usa el operador > + 'name LIKE' => '%John%', // usa el operador LIKE + 'email NOT LIKE' => '%example.com%', // usa el operador NOT LIKE +]); +// WHERE `age` > 25 AND `name` LIKE '%John%' AND `email` NOT LIKE '%example.com%' +``` + +Nette maneja automáticamente casos especiales como valores `null` o arrays. + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name' => 'Laptop', // usa el operador = + 'category_id' => [1, 2, 3], // usa IN + 'description' => null, // usa IS NULL +]); +// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL +``` + +Para condiciones negativas, use el operador `NOT`: + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name NOT' => 'Laptop', // usa el operador <> + 'category_id NOT' => [1, 2, 3], // usa NOT IN + 'description NOT' => null, // usa IS NOT NULL + 'id' => [], // se omite +]); +// WHERE `name` <> 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL +``` + +Para unir condiciones, se utiliza el operador `AND`. Esto se puede cambiar usando el [marcador de posición ?or |#Pistas para construir SQL]. + + +Reglas ORDER BY +--------------- + +La ordenación `ORDER BY` se puede escribir usando un array. En las claves, especificamos las columnas y el valor será un booleano que indica si ordenar ascendentemente: + +```php +$database->query('SELECT id FROM author ORDER BY', [ + 'id' => true, // ascendente + 'name' => false, // descendente +]); +// SELECT id FROM author ORDER BY `id`, `name` DESC +``` + + +Inserción de datos (INSERT) +--------------------------- + +Para insertar registros, se utiliza el comando SQL `INSERT`. + +```php +$values = [ + 'name' => 'John Doe', + 'email' => 'john@example.com', +]; +$database->query('INSERT INTO users ?', $values); +$userId = $database->getInsertId(); +``` + +El método `getInsertId()` devuelve el ID de la última fila insertada. Para algunas bases de datos (por ejemplo, PostgreSQL), es necesario especificar como parámetro el nombre de la secuencia desde la cual se debe generar el ID usando `$database->getInsertId($sequenceId)`. + +También podemos pasar [#Valores especiales] como parámetros, como archivos, objetos DateTime o tipos enumerados. + +Insertar múltiples registros a la vez: + +```php +$database->query('INSERT INTO users ?', [ + ['name' => 'User 1', 'email' => 'user1@mail.com'], + ['name' => 'User 2', 'email' => 'user2@mail.com'], +]); +``` + +El INSERT múltiple es mucho más rápido porque se ejecuta una única consulta a la base de datos, en lugar de muchas individuales. + +**Advertencia de seguridad:** Nunca use datos no validados como `$values`. Familiarícese con los [posibles riesgos |security#Trabajo seguro con columnas]. + + +Actualización de datos (UPDATE) +------------------------------- + +Para actualizar registros, se utiliza el comando SQL `UPDATE`. + +```php +// Actualizar un registro +$values = [ + 'name' => 'John Smith', +]; +$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1); +``` + +El número de filas afectadas lo devuelve `$result->getRowCount()`. + +Para UPDATE, podemos usar los operadores `+=` y `-=`: + +```php +$database->query('UPDATE users SET ? WHERE id = ?', [ + 'login_count+=' => 1, // incrementa login_count +], 1); +``` + +Ejemplo de inserción o modificación de un registro si ya existe. Usamos la técnica `ON DUPLICATE KEY UPDATE`: + +```php +$values = [ + 'name' => $name, + 'year' => $year, +]; +$database->query('INSERT INTO users ? ON DUPLICATE KEY UPDATE ?', + $values + ['id' => $id], + $values, +); +// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) +// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 +``` + +Observe que Nette Database reconoce en qué contexto del comando SQL insertamos el parámetro con el array y construye el código SQL en consecuencia. Así, del primer array construyó `(id, name, year) VALUES (123, 'Jim', 1978)`, mientras que el segundo lo convirtió a la forma `name = 'Jim', year = 1978`. Nos ocupamos de esto con más detalle en la sección [#Pistas para construir SQL]. + + +Eliminación de datos (DELETE) +----------------------------- + +Para eliminar registros, se utiliza el comando SQL `DELETE`. Ejemplo con obtención del número de filas eliminadas: + +```php +$count = $database->query('DELETE FROM users WHERE id = ?', 1) + ->getRowCount(); +``` + + +Pistas para construir SQL +------------------------- + +Una pista es un marcador de posición especial en una consulta SQL que indica cómo se debe reescribir el valor del parámetro en una expresión SQL: + +| Pista | Descripción | Se usa automáticamente +|-----------|---------------------------------------------------|----------------------------- +| `?name` | se usa para insertar el nombre de tabla o columna | - +| `?values` | genera `(key, ...) VALUES (value, ...)` | `INSERT ... ?`, `REPLACE ... ?` +| `?set` | genera la asignación `key = value, ...` | `SET ?`, `KEY UPDATE ?` +| `?and` | une condiciones en el array con el operador `AND` | `WHERE ?`, `HAVING ?` +| `?or` | une condiciones en el array con el operador `OR` | - +| `?order` | genera la cláusula `ORDER BY` | `ORDER BY ?`, `GROUP BY ?` + +Para la inserción dinámica de nombres de tablas y columnas en la consulta, se utiliza el marcador de posición `?name`. Nette Database se encarga del tratamiento correcto de los identificadores según las convenciones de la base de datos dada (por ejemplo, encerrándolos entre comillas invertidas en MySQL). + +```php +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table); +// SELECT `name` FROM `users` WHERE id = 1 (en MySQL) +``` + +**Advertencia:** use el símbolo `?name` solo para nombres de tablas y columnas de entradas validadas, de lo contrario se expone a un [riesgo de seguridad |security#Identificadores dinámicos]. + +Otras pistas generalmente no necesitan ser especificadas, ya que Nette utiliza una detección automática inteligente al construir la consulta SQL (ver la tercera columna de la tabla). Pero puede usarla, por ejemplo, en una situación en la que desee unir condiciones usando `OR` en lugar de `AND`: + +```php +$database->query('SELECT * FROM users WHERE ?or', [ + 'name' => 'John', + 'email' => 'john@example.com', +]); +// SELECT * FROM users WHERE `name` = 'John' OR `email` = 'john@example.com' +``` + + +Valores especiales +------------------ + +Además de los tipos escalares comunes (string, int, bool), también puede pasar valores especiales como parámetros: + +- archivos: `fopen('image.gif', 'r')` inserta el contenido binario del archivo +- fecha y hora: los objetos `DateTime` se convierten al formato de la base de datos +- tipos enumerados: las instancias de `enum` se convierten a su valor +- literales SQL: creados usando `Connection::literal('NOW()')` se insertan directamente en la consulta + +```php +$database->query('INSERT INTO articles ?', [ + 'title' => 'My Article', + 'published_at' => new DateTime, + 'content' => fopen('image.png', 'r'), + 'state' => Status::Draft, +]); +``` + +Para bases de datos que no tienen soporte nativo para el tipo de dato `datetime` (como SQLite y Oracle), `DateTime` se convierte al valor especificado en la [configuración de la base de datos |configuration] por la entrada `formatDateTime` (el valor predeterminado es `U` - timestamp unix). + + +Literales SQL +------------- + +En algunos casos, necesita especificar directamente código SQL como valor, que no debe entenderse como una cadena y escaparse. Para esto sirven los objetos de la clase `Nette\Database\SqlLiteral`. Son creados por el método `Connection::literal()`. + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + 'year >' => $database::literal('YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) +``` + +O alternativamente: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) +``` + +Los literales SQL pueden contener parámetros: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > ? AND year < ?', $min, $max), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) +``` + +Gracias a lo cual podemos crear combinaciones interesantes: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('?or', [ + 'active' => true, + 'role' => $role, + ]), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') +``` + + +Obtención de datos +================== + + +Atajos para consultas SELECT +---------------------------- + +Para simplificar la recuperación de datos, `Connection` ofrece varios atajos que combinan la llamada a `query()` con la siguiente `fetch*()`. Estos métodos aceptan los mismos parámetros que `query()`, es decir, la consulta SQL y parámetros opcionales. Una descripción completa de los métodos `fetch*()` se puede encontrar [más abajo |#fetch]. + +| `fetch($sql, ...$params): ?Row` | Ejecuta la consulta y devuelve la primera fila como un objeto `Row` +| `fetchAll($sql, ...$params): array` | Ejecuta la consulta y devuelve todas las filas como un array de objetos `Row` +| `fetchPairs($sql, ...$params): array` | Ejecuta la consulta y devuelve un array asociativo, donde la primera columna representa la clave y la segunda el valor +| `fetchField($sql, ...$params): mixed` | Ejecuta la consulta y devuelve el valor del primer campo de la primera fila +| `fetchList($sql, ...$params): ?array` | Ejecuta la consulta y devuelve la primera fila como un array indexado + +Ejemplo: + +```php +// fetchField() - devuelve el valor de la primera celda +$count = $database->query('SELECT COUNT(*) FROM articles') + ->fetchField(); +``` + + +`foreach` - iteración sobre filas +--------------------------------- + +Después de ejecutar la consulta, se devuelve un objeto [ResultSet|api:Nette\Database\ResultSet], que permite recorrer los resultados de varias maneras. La forma más fácil de ejecutar una consulta y obtener filas es iterando en un bucle `foreach`. Este método es el más eficiente en cuanto a memoria, ya que devuelve los datos gradualmente y no los almacena todos en la memoria a la vez. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; + // ... +} +``` + +.[note] +`ResultSet` solo se puede iterar una vez. Si necesita iterar repetidamente, primero debe cargar los datos en un array, por ejemplo, usando el método `fetchAll()`. + + +fetch(): ?Row .[method] +----------------------- + +Devuelve una fila como un objeto `Row`. Si no hay más filas, devuelve `null`. Mueve el puntero interno a la siguiente fila. + +```php +$result = $database->query('SELECT * FROM users'); +$row = $result->fetch(); // carga la primera fila +if ($row) { + echo $row->name; +} +``` + + +fetchAll(): array .[method] +--------------------------- + +Devuelve todas las filas restantes del `ResultSet` como un array de objetos `Row`. + +```php +$result = $database->query('SELECT * FROM users'); +$rows = $result->fetchAll(); // carga todas las filas +foreach ($rows as $row) { + echo $row->name; +} +``` + + +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Devuelve los resultados como un array asociativo. El primer argumento especifica el nombre de la columna que se usará como clave en el array, el segundo argumento especifica el nombre de la columna que se usará como valor: + +```php +$result = $database->query('SELECT id, name FROM users'); +$names = $result->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] +``` + +Si solo especificamos el primer parámetro, el valor será la fila completa, es decir, el objeto `Row`: + +```php +$rows = $result->fetchPairs('id'); +// [1 => Row(id: 1, name: 'John'), 2 => Row(id: 2, name: 'Jane'), ...] +``` + +En caso de claves duplicadas, se utiliza el valor de la última fila. Al usar `null` como clave, el array se indexará numéricamente desde cero (entonces no hay colisiones): + +```php +$names = $result->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] +``` + + +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Alternativamente, puede pasar un callback como parámetro, que devolverá para cada fila ya sea el valor en sí, o un par clave-valor. + +```php +$result = $database->query('SELECT * FROM users'); +$items = $result->fetchPairs(fn($row) => "$row->id - $row->name"); +// ['1 - John', '2 - Jane', ...] + +// El callback también puede devolver un array con un par clave & valor: +$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]); +// ['John' => 46, 'Jane' => 21, ...] +``` + + +fetchField(): mixed .[method] +----------------------------- + +Devuelve el valor del primer campo de la fila actual. Si no hay más filas, devuelve `null`. Mueve el puntero interno a la siguiente fila. + +```php +$result = $database->query('SELECT name FROM users'); +$name = $result->fetchField(); // carga el nombre de la primera fila +``` + + +fetchList(): ?array .[method] +----------------------------- + +Devuelve una fila como un array indexado. Si no hay más filas, devuelve `null`. Mueve el puntero interno a la siguiente fila. + +```php +$result = $database->query('SELECT name, email FROM users'); +$row = $result->fetchList(); // ['John', 'john@example.com'] +``` + + +getRowCount(): ?int .[method] +----------------------------- + +Devuelve el número de filas afectadas por la última consulta `UPDATE` o `DELETE`. Para `SELECT`, es el número de filas devueltas, pero este puede no ser conocido; en tal caso, el método devuelve `null`. + + +getColumnCount(): ?int .[method] +-------------------------------- + +Devuelve el número de columnas en el `ResultSet`. + + +Información sobre consultas +=========================== + +Para fines de depuración, podemos obtener información sobre la última consulta ejecutada: + +```php +echo $database->getLastQueryString(); // imprime la consulta SQL + +$result = $database->query('SELECT * FROM articles'); +echo $result->getQueryString(); // imprime la consulta SQL +echo $result->getTime(); // imprime el tiempo de ejecución en segundos +``` + +Para mostrar el resultado como una tabla HTML, se puede usar: + +```php +$result = $database->query('SELECT * FROM articles'); +$result->dump(); +``` + +ResultSet ofrece información sobre los tipos de columnas: + +```php +$result = $database->query('SELECT * FROM articles'); +$types = $result->getColumnTypes(); + +foreach ($types as $column => $type) { + echo "$column es de tipo $type->type"; // por ejemplo, 'id es de tipo int' +} +``` + + +Registro de consultas +--------------------- + +Podemos implementar nuestro propio registro de consultas. El evento `onQuery` es un array de callbacks que se llaman después de cada consulta ejecutada: + +```php +$database->onQuery[] = function ($database, $result) use ($logger) { + $logger->info('Consulta: ' . $result->getQueryString()); + $logger->info('Tiempo: ' . $result->getTime()); + + if ($result->getRowCount() > 1000) { + $logger->warning('Conjunto de resultados grande: ' . $result->getRowCount() . ' filas'); + } +}; +``` diff --git a/database/es/transactions.texy b/database/es/transactions.texy new file mode 100644 index 0000000000..2b32093f77 --- /dev/null +++ b/database/es/transactions.texy @@ -0,0 +1,43 @@ +Transacciones +************* + +.[perex] +Las transacciones garantizan que todas las operaciones dentro de una transacción se ejecuten o que ninguna se ejecute. Son útiles para asegurar la consistencia de los datos en operaciones más complejas. + +La forma más sencilla de usar transacciones es la siguiente: + +```php +$database->beginTransaction(); +try { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); + $database->commit(); +} catch (\Exception $e) { + $database->rollBack(); + throw $e; +} +``` + +Puede escribir lo mismo de forma mucho más elegante usando el método `transaction()`. Acepta una devolución de llamada como parámetro, que ejecuta dentro de la transacción. Si la devolución de llamada se ejecuta sin excepciones, la transacción se confirma automáticamente (commit). Si ocurre una excepción, la transacción se cancela (rollback) y la excepción se propaga. + +```php +$database->transaction(function ($database) use ($id) { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); +}); +``` + +El método `transaction()` también puede devolver valores: + +```php +$count = $database->transaction(function ($database) { + $result = $database->query('UPDATE users SET active = ?', true); + return $result->getRowCount(); // devuelve el número de filas actualizadas +}); +``` diff --git a/database/files/db-schema-1-.webp b/database/files/db-schema-1-.webp index 29905706c0..6bd9b0598d 100644 Binary files a/database/files/db-schema-1-.webp and b/database/files/db-schema-1-.webp differ diff --git a/database/fr/@home.texy b/database/fr/@home.texy index 0a996496f9..f05b61a24f 100644 --- a/database/fr/@home.texy +++ b/database/fr/@home.texy @@ -1,20 +1,21 @@ -Serveurs pris en charge -======================= +Bases de données prises en charge +================================= -Les serveurs de base de données suivants sont pris en charge : +Nette prend en charge les bases de données suivantes : -|* Serveur de base de données |* Nom DSN |* Prise en charge de Core |* Prise en charge d'Explorer -| MySQL (>= 5.1) | mysql | OUI | OUI -| PostgreSQL (>= 9.0) | pgsql | YES | YES -| Sqlite 3 (>= 3.8) | sqlite | OUI | OUI -| Oracle | oci | OUI | - -| MS SQL (PDO_SQLSRV) | sqlsrv | OUI | OUI -| MS SQL (PDO_DBLIB) | mssql | OUI | - -| ODBC | odbc | OUI | - +|* Serveur de base de données |* Nom DSN |* Support dans Core |* Support dans Explorer +| MySQL (>= 5.1) | mysql | OUI | OUI +| PostgreSQL (>= 9.0) | pgsql | OUI | OUI +| Sqlite 3 (>= 3.8) | sqlite | OUI | OUI +| Oracle | oci | OUI | - +| MS SQL (PDO_SQLSRV) | sqlsrv | OUI | OUI +| MS SQL (PDO_DBLIB) | mssql | OUI | - +| ODBC | odbc | OUI | - -{{title: Nette Database}} -{{description: Nette Database simplifie l'extraction de données de la base de données sans qu'il soit nécessaire d'écrire des requêtes SQL. Il pose des requêtes efficaces et ne transfère pas de données inutiles.}} + +{{maintitle: Nette Database - awesome database layer for PHP}} +{{description: Nette Database simplifie considérablement l'obtention de données de la base de données sans avoir à écrire de requêtes SQL. Il exécute des requêtes efficaces et ne transfère pas de données inutiles.}} diff --git a/database/fr/@left-menu.texy b/database/fr/@left-menu.texy index f643a49749..bf2b375e4d 100644 --- a/database/fr/@left-menu.texy +++ b/database/fr/@left-menu.texy @@ -1,5 +1,12 @@ -Base de données -*************** -- [Le noyau |Core] -- [Explorer] -- [Configuration] +Nette Database +************** +- [Introduction |guide] +- [Accès SQL |sql way] +- [Explorer |Explorer] +- [Transactions |transactions] +- [Exceptions |exceptions] +- [Réflexion |reflection] +- [Mapping |mapping] +- [Configuration |configuration] +- [Risques de sécurité |security] +- [Mise à niveau |en:upgrading] diff --git a/database/fr/@meta.texy b/database/fr/@meta.texy new file mode 100644 index 0000000000..72ae4b8db8 --- /dev/null +++ b/database/fr/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentation Nette}} diff --git a/database/fr/configuration.texy b/database/fr/configuration.texy index f8e560e213..db34cc8f3c 100644 --- a/database/fr/configuration.texy +++ b/database/fr/configuration.texy @@ -2,60 +2,66 @@ Configuration de la base de données *********************************** .[perex] -Aperçu des options de configuration de la base de données Nette. +Aperçu des options de configuration pour Nette Database. -Si vous n'utilisez pas l'ensemble du framework, mais seulement cette bibliothèque, lisez [comment charger la configuration |bootstrap:]. +Si vous n'utilisez pas l'ensemble du framework, mais uniquement cette bibliothèque, lisez [comment charger la configuration|bootstrap:]. -Connexion unique .[#toc-single-connection] ------------------------------------------- +Connexion unique +---------------- -Configurez une connexion unique à la base de données : +Configuration d'une connexion unique à la base de données : ```neon database: - # DSN, uniquement la clé obligatoire + # DSN, seule clé obligatoire dsn: "sqlite:%appDir%/Model/demo.db" user: ... password: ... ``` -Il crée des services de type `Nette\Database\Connection` et aussi `Nette\Database\Explorer` pour la couche [Database Explorer |explorer]. La connexion à la base de données est généralement passée par un [câblage automatique |dependency-injection:autowiring], si cela n'est pas possible, utilisez les noms de services `@database.default.connection` resp. `@database.default.explorer`. +Crée les services `Nette\Database\Connection` et `Nette\Database\Explorer`, que nous transmettons généralement par [autowiring |dependency-injection:autowiring], ou par référence à [leur nom |#Services DI]. Autres paramètres : ```neon database: - # affiche le panneau de la base de données dans Tracy Bar ? - debugger: ... # (bool) vaut true par défaut + # afficher le panneau de base de données dans la barre Tracy ? + debugger: ... # (bool) la valeur par défaut est true - # affiche la requête EXPLAIN dans Tracy Bar ? - explain: ... # (bool) vaut true par défaut + # afficher EXPLAIN des requêtes dans la barre Tracy ? + explain: ... # (bool) la valeur par défaut est true - # activer le câblage automatique pour cette connexion ? - autowired: ... # (bool) vaut true par défaut pour la première connexion + # autoriser l'autowiring pour cette connexion ? + autowired: ... # (bool) la valeur par défaut est true pour la première connexion - # conventions de la table: découverte, statique, ou nom de classe - conventions: discovered # (string) Valeur par défaut: 'discovered'. + # conventions de table : discovered, static ou nom de classe + conventions: discovered # (string) la valeur par défaut est 'discovered' options: - # pour ne se connecter à la base de données qu'en cas de besoin ? - lazy: ... # (bool) a pour valeur par défaut false + # se connecter à la base de données uniquement lorsque c'est nécessaire ? + lazy: ... # (bool) la valeur par défaut est false - # Classe du pilote de base de données PHP + # classe PHP du pilote de base de données driverClass: # (string) - # seulement MySQL: définit sql_mode + # uniquement MySQL : définit sql_mode sqlmode: # (string) - # uniquement MySQL: sets SET NAMES - charset: # (string) par défaut 'utf8mb4' ('utf8' avant v5.5.3) + # uniquement MySQL : définit SET NAMES + charset: # (string) la valeur par défaut est 'utf8mb4' - # uniquement Oracle et SQLite: format de date - formatDateTime: # (string) vaut par défaut 'U'. + # uniquement MySQL : convertit TINYINT(1) en bool + convertBoolean: # (bool) la valeur par défaut est false + + # renvoie les colonnes de date comme objets immuables (depuis la version 3.2.1) + newDateTime: # (bool) la valeur par défaut est false + + # uniquement Oracle et SQLite : format pour stocker la date + formatDateTime: # (string) la valeur par défaut est 'U' ``` -La clé `options` peut contenir d'autres options qui peuvent être trouvées dans la [documentation du pilote PDO |https://www.php.net/manual/en/pdo.drivers.php], telles que : +Dans la clé `options`, vous pouvez spécifier d'autres options que vous trouverez dans la [documentation des pilotes PDO |https://www.php.net/manual/en/pdo.drivers.php], comme par exemple : ```neon database: @@ -64,10 +70,10 @@ database: ``` -Connexions multiples .[#toc-multiple-connections] -------------------------------------------------- +Connexions multiples +-------------------- -Dans la configuration, nous pouvons définir plusieurs connexions de base de données en les divisant en sections nommées : +Dans la configuration, nous pouvons également définir plusieurs connexions de base de données en les divisant en sections nommées : ```neon database: @@ -80,9 +86,23 @@ database: dsn: 'sqlite::memory:' ``` -Chaque connexion définie crée des services qui incluent le nom de la section dans leur nom, c'est-à-dire `@database.main.connection` & `@database.main.explorer` et ensuite `@database.another.connection` & `@database.another.explorer`. +L'autowiring n'est activé que pour les services de la première section. Cela peut être modifié à l'aide de `autowired: false` ou `autowired: true`. + + +Services DI +----------- + +Ces services sont ajoutés au conteneur DI, où `###` représente le nom de la connexion : + +| Nom | Type | Description +|--------------------------------------------------------------------------| +| `database.###.connection` | [api:Nette\Database\Connection] | connexion à la base de données +| `database.###.explorer` | [api:Nette\Database\Explorer] | [Database Explorer |explorer] + + +Si nous ne définissons qu'une seule connexion, les noms des services seront `database.default.connection` et `database.default.explorer`. Si nous définissons plusieurs connexions comme dans l'exemple ci-dessus, les noms correspondront aux sections, c'est-à-dire `database.main.connection`, `database.main.explorer` et ensuite `database.another.connection` et `database.another.explorer`. -Le câblage automatique est activé uniquement pour les services de la première section. Ceci peut être modifié avec `autowired: false` ou `autowired: true`. Les services non câblés sont transmis par nom : +Nous transmettons explicitement les services non autowirés par référence à leur nom : ```neon services: diff --git a/database/fr/core.texy b/database/fr/core.texy deleted file mode 100644 index 2703fcc41d..0000000000 --- a/database/fr/core.texy +++ /dev/null @@ -1,350 +0,0 @@ -Base de données Core -******************** - -.[perex] -Nette Database Core est une couche d'abstraction de base de données et fournit des fonctionnalités de base. - - -Installation .[#toc-installation] -================================= - -Téléchargez et installez le paquet en utilisant [Composer |best-practices:composer]: - -```shell -composer require nette/database -``` - - -Connexion et configuration .[#toc-connection-and-configuration] -=============================================================== - -Pour se connecter à la base de données, il suffit de créer une instance de la classe [api:Nette\Database\Connection]: - -```php -$database = new Nette\Database\Connection($dsn, $user, $password); -``` - -Le paramètre `$dsn` (nom de la source de données) est le [même que celui utilisé par PDO |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], par exemple `host=127.0.0.1;dbname=test`. En cas d'échec, il lance `Nette\Database\ConnectionException`. - -Cependant, une manière plus sophistiquée offre la [configuration de l'application |configuration]. Nous ajouterons une section `database` et elle créera les objets requis et un panneau de base de données dans la barre [Tracy |tracy:]. - -```neon -database: - dsn: 'mysql:host=127.0.0.1;dbname=test' - user: root - password: password -``` - -L'objet de connexion que nous [recevons comme un service d'un conteneur DI |dependency-injection:passing-dependencies], par exemple : - -```php -class Model -{ - // passer Nette\Database\Explorer pour travailler avec la couche Database Explorer - public function __construct( - private Nette\Database\Connection $database, - ) { - } -} -``` - -Pour plus d'informations, voir la [configuration de la base de données |configuration]. - - -Requêtes .[#toc-queries] -======================== - -Pour interroger la base de données, utilisez la méthode `query()` qui renvoie un [ResultSet |api:Nette\Database\ResultSet]. - -```php -$result = $database->query('SELECT * FROM users'); - -foreach ($result as $row) { - echo $row->id; - echo $row->name; -} - -echo $result->getRowCount(); // renvoie le nombre de lignes s'il est connu. -``` - -.[note] -Sur le site `ResultSet` il est possible d'itérer une seule fois, si nous avons besoin d'itérer plusieurs fois, il est nécessaire de convertir le résultat en tableau via la méthode `fetchAll()`. - -Vous pouvez facilement ajouter des paramètres à la requête, notez le point d'interrogation : - -```php -$database->query('SELECT * FROM users WHERE name = ?', $name); - -$database->query('SELECT * FROM users WHERE name = ? AND active = ?', $name, $active); - -$database->query('SELECT * FROM users WHERE id IN (?)', $ids); // $ids est un tableau -``` -<div class=warning> - -ATTENTION, ne jamais concaténer des chaînes de caractères pour éviter [une vulnérabilité d'injection SQL |https://en.wikipedia.org/wiki/SQL_injection]! -/-- -$db->query('SELECT * FROM users WHERE name = ' . $name); // WRONG!!! -\-- -</div> - -En cas d'échec, `query()` lance soit `Nette\Database\DriverException`, soit l'un de ses descendants : - -- [ConstraintViolationException |api:Nette\Database\ConstraintViolationException] - violation d'une contrainte quelconque -- [ForeignKeyConstraintViolationException |api:Nette\Database\ForeignKeyConstraintViolationException] - clé étrangère invalide -- [NotNullConstraintViolationException |api:Nette\Database\NotNullConstraintViolationException] - violation de la condition NOT NULL -- [UniqueConstraintViolationException |api:Nette\Database\UniqueConstraintViolationException] - conflit d'index unique - -En plus de `query()`, il existe d'autres méthodes utiles : - -```php -// renvoie le tableau associatif id => name -$pairs = $database->fetchPairs('SELECT id, name FROM users'); - -// renvoie toutes les lignes sous forme de tableau -$rows = $database->fetchAll('SELECT * FROM users'); - -// renvoie une seule ligne -$row = $database->fetch('SELECT * FROM users WHERE id = ?', $id); - -// renvoie un seul champ -$name = $database->fetchField('SELECT name FROM users WHERE id = ?', $id); -``` - -En cas d'échec, toutes ces méthodes jettent `Nette\Database\DriverException.` - - -Insertion, mise à jour et suppression .[#toc-insert-update-delete] -================================================================== - -Le paramètre que nous insérons dans la requête SQL peut également être un tableau (dans ce cas, il est possible de sauter l'instruction joker `?`), which may be useful for the `INSERT`: - -```php -$database->query('INSERT INTO users ?', [ // ici peut être omis le point d'interrogation - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) - -$id = $database->getInsertId(); // renvoie l'auto-incrément de la ligne insérée - -$id = $database->getInsertId($sequence); // ou valeur de la séquence -``` - -Insertion multiple : - -```php -$database->query('INSERT INTO users', [ - [ - 'name' => 'Jim', - 'year' => 1978, - ], [ - 'name' => 'Jack', - 'year' => 1987, - ], -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) -``` - -Nous pouvons également passer des fichiers, des objets DateTime ou des [énumérations |https://www.php.net/enumerations]: - -```php -$database->query('INSERT INTO users', [ - 'name' => $name, - 'created' => new DateTime, // ou $database::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // insère le contenu du fichier - 'status' => State::New, // enum State -]); -``` - -Mise à jour des rangs : - -```php -$result = $database->query('UPDATE users SET', [ - 'name' => $name, - 'year' => $year, -], 'WHERE id = ?', $id); -// UPDATE users SET `name` = 'Jim', `year` = 1978 WHERE id = 123 - -echo $result->getRowCount(); // renvoie le nombre de lignes affectées. -``` - -Pour UPDATE, nous pouvons utiliser les opérateurs `+=` et `-=`: - -```php -$database->query('UPDATE users SET', [ - 'age+=' => 1, // note += -], 'WHERE id = ?', $id); -// UPDATE users SET `age` = `age` + 1 -``` - -Suppression : - -```php -$result = $database->query('DELETE FROM users WHERE id = ?', $id); -echo $result->getRowCount(); // renvoie le nombre de lignes affectées. -``` - - -Requêtes avancées .[#toc-advanced-queries] -========================================== - -Insérer ou mettre à jour, s'il existe déjà : - -```php -$database->query('INSERT INTO users', [ - 'id' => $id, - 'name' => $name, - 'year' => $year, -], 'ON DUPLICATE KEY UPDATE', [ - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) -// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 -``` - -Notez que Nette Database reconnaît le contexte SQL dans lequel le paramètre du tableau est inséré et construit le code SQL en conséquence. Ainsi, à partir du premier tableau, il génère `(id, name, year) VALUES (123, 'Jim', 1978)`, tandis que le second se convertit en `name = 'Jim', year = 1978`. - -Nous pouvons également décrire le tri en utilisant un tableau, dans lequel les clés sont des noms de colonnes et les valeurs sont des booléens qui déterminent s'il faut trier par ordre croissant : - -```php -$database->query('SELECT id FROM author ORDER BY', [ - 'id' => true, // ascendant - 'name' => false, // descendant -]); -// SELECT id FROM author ORDER BY `id`, `name` DESC -``` - -Si la détection n'a pas fonctionné, vous pouvez spécifier la forme de l'assemblage avec un joker `?` suivi d'un hint. Ces hints sont supportés : - -| ?values | (key1, key2, ...) VALUES (value1, value2, ...) -| ?set | key1 = valeur1, key2 = valeur2, ... -| ?et | clé1 = valeur1 ET clé2 = valeur2 ... -| ?or | key1 = valeur1 OR key2 = valeur2 ... -| ?order | clé1 ASC, clé2 DESC - -La clause WHERE utilise l'opérateur `?and` de sorte que les conditions sont liées par `AND`: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND `year` = 1978 -``` - -Ce qui peut facilement être changé en `OR` en utilisant le caractère générique `?or`: - -```php -$result = $database->query('SELECT * FROM users WHERE ?or', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' OR `year` = 1978 -``` - -Nous pouvons utiliser des opérateurs dans les conditions : - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name <>' => $name, - 'year >' => $year, -]); -// SELECT * FROM users WHERE `name` <> 'Jim' AND `year` > 1978 -``` - -Et aussi les énumérations : - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => ['Jim', 'Jack'], - 'role NOT IN' => ['admin', 'owner'], // énumération + opérateur NOT IN -]); -// SELECT * FROM users WHERE -// `name` IN ('Jim', 'Jack') AND `role` NOT IN ('admin', 'owner') -``` - -Nous pouvons également inclure un morceau de code SQL personnalisé en utilisant ce que l'on appelle le littéral SQL : - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year >' => $database::literal('YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) -``` - -Alternativement : - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) -``` - -Le littéral SQL peut aussi avoir ses paramètres : - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > ? AND year < ?', $min, $max), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) -``` - -Grâce à quoi nous pouvons créer des combinaisons intéressantes : - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('?or', [ - 'active' => true, - 'role' => $role, - ]), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') -``` - - -Nom de la variable .[#toc-variable-name] -======================================== - -Il existe un joker `?name` que vous utilisez si le nom de la table ou de la colonne est une variable. (Attention, ne permettez pas à l'utilisateur de manipuler le contenu d'une telle variable) : - -```php -$table = 'blog.users'; -$column = 'name'; -$database->query('SELECT * FROM ?name WHERE ?name = ?', $table, $column, $name); -// SELECT * FROM `blog`.`users` WHERE `name` = 'Jim' -``` - - -Transactions .[#toc-transactions] -================================= - -Il existe trois méthodes pour traiter les transactions : - -```php -$database->beginTransaction(); - -$database->commit(); - -$database->rollback(); -``` - -Une manière élégante est offerte par la méthode `transaction()`. Vous passez le callback qui est exécuté dans la transaction. Si une exception est levée pendant l'exécution, la transaction est abandonnée, si tout se passe bien, la transaction est validée. - -```php -$id = $database->transaction(function ($database) { - $database->query('DELETE FROM ...'); - $database->query('INSERT INTO ...'); - // ... - return $database->getInsertId(); -}); -``` - -Comme vous pouvez le voir, la méthode `transaction()` renvoie la valeur de retour de la callback. - -La transaction() peut également être imbriquée, ce qui simplifie la mise en œuvre de référentiels indépendants. diff --git a/database/fr/exceptions.texy b/database/fr/exceptions.texy new file mode 100644 index 0000000000..a6f5aad0b3 --- /dev/null +++ b/database/fr/exceptions.texy @@ -0,0 +1,34 @@ +Exceptions +********** + +Nette Database utilise une hiérarchie d'exceptions. La classe de base est `Nette\Database\DriverException`, qui hérite de `PDOException` et offre des fonctionnalités étendues pour travailler avec les erreurs de base de données : + +- La méthode `getDriverCode()` renvoie le code d'erreur spécifique au pilote de base de données. +- La méthode `getSqlState()` renvoie le code SQLSTATE standard. +- Les méthodes `getQueryString()` et `getParameters()` permettent d'obtenir la requête SQL d'origine et ses paramètres. + +Les exceptions spécialisées suivantes héritent de `DriverException` : + +- `ConnectionException` - signale un échec de connexion au serveur de base de données +- `ConstraintViolationException` - classe de base pour la violation des contraintes de base de données, dont héritent : + - `ForeignKeyConstraintViolationException` - violation de clé étrangère + - `NotNullConstraintViolationException` - violation de contrainte NOT NULL + - `UniqueConstraintViolationException` - violation de l'unicité de la valeur + + +Exemple de capture de l'exception `UniqueConstraintViolationException`, qui se produit lorsque nous essayons d'insérer un utilisateur avec un e-mail qui existe déjà dans la base de données (en supposant que la colonne `email` a un index unique). + +```php +try { + $database->query('INSERT INTO users', [ + 'email' => 'john@example.com', + 'name' => 'John Doe', + 'password' => $hashedPassword, + ]); +} catch (Nette\Database\UniqueConstraintViolationException $e) { + echo 'Un utilisateur avec cet e-mail existe déjà.'; + +} catch (Nette\Database\DriverException $e) { + echo 'Une erreur s\'est produite lors de l\'inscription : ' . $e->getMessage(); +} +``` diff --git a/database/fr/explorer.texy b/database/fr/explorer.texy index 7d4796d7f8..c1862f2c42 100644 --- a/database/fr/explorer.texy +++ b/database/fr/explorer.texy @@ -1,550 +1,912 @@ -Explorateur de bases de données -******************************* +Database Explorer +***************** <div class=perex> -Nette Database Explorer simplifie considérablement l'extraction de données de la base de données sans avoir à écrire de requêtes SQL. +Explorer offre un moyen intuitif et efficace de travailler avec votre base de données. Il gère automatiquement les relations entre les tables et l'optimisation des requêtes, vous permettant de vous concentrer sur votre application. Il fonctionne immédiatement sans aucune configuration. Si vous avez besoin d'un contrôle total sur vos requêtes SQL, vous pouvez utiliser [l'approche SQL |SQL way]. -- utilise des requêtes efficaces -- aucune donnée n'est transmise inutilement -- présente une syntaxe élégante +- Le travail avec les données est naturel et facile à comprendre +- Génère des requêtes SQL optimisées qui ne chargent que les données nécessaires +- Permet un accès facile aux données liées sans avoir à écrire de requêtes JOIN +- Fonctionne immédiatement sans aucune configuration ni génération d'entités </div> -Pour utiliser Database Explorer, commencez par une table - appelez `table()` sur un objet [api:Nette\Database\Explorer]. La manière la plus simple d'obtenir une instance d'objet de contexte est [décrite ici |core#Connection and Configuration], ou, pour le cas où Nette Database Explorer est utilisé comme un outil autonome, elle peut être [créée manuellement |#Creating Explorer Manually]. + +Vous commencez avec Explorer en appelant la méthode `table()` sur l'objet [api:Nette\Database\Explorer] (les détails sur la connexion se trouvent dans le chapitre [Connexion et configuration |guide#Connexion et configuration]) : ```php -$books = $explorer->table('book'); // le nom de la table de la base de données est 'book'. +$books = $explorer->table('book'); // 'book' est le nom de la table ``` -L'appel renvoie une instance de l'objet [Selection |api:Nette\Database\Table\Selection], qui peut être itéré pour récupérer tous les livres. Chaque élément (une ligne) est représenté par une instance de [ActiveRow |api:Nette\Database\Table\ActiveRow] avec des données mappées à ses propriétés : +La méthode renvoie un objet [Selection |api:Nette\Database\Table\Selection], qui représente une requête SQL. Vous pouvez enchaîner d'autres méthodes sur cet objet pour filtrer et trier les résultats. La requête est construite et exécutée seulement au moment où vous commencez à demander des données, par exemple, en parcourant une boucle `foreach`. Chaque ligne est représentée par un objet [ActiveRow |api:Nette\Database\Table\ActiveRow] : ```php foreach ($books as $book) { - echo $book->title; - echo $book->author_id; + echo $book->title; // Affiche la colonne 'title' + echo $book->author_id; // Affiche la colonne 'author_id' } ``` -L'obtention d'une ligne spécifique se fait par la méthode `get()`, qui renvoie directement une instance ActiveRow. +Explorer facilite considérablement le travail avec les [#relations entre les tables]. L'exemple suivant montre avec quelle facilité vous pouvez afficher les données de tables liées (livres et leurs auteurs). Notez que vous n'avez pas besoin d'écrire de requêtes JOIN ; Nette les crée pour vous : ```php -$book = $explorer->table('book')->get(2); // renvoie le livre avec l'id 2 -echo $book->title; -echo $book->author_id; +$books = $explorer->table('book'); + +foreach ($books as $book) { + echo 'Livre : ' . $book->title; + echo 'Auteur : ' . $book->author->name; // Crée un JOIN sur la table 'author' +} ``` -Voyons un cas d'utilisation courant. Vous avez besoin de récupérer des livres et leurs auteurs. Il s'agit d'une relation 1:N courante. La solution la plus courante est de récupérer les données en utilisant une seule requête SQL avec des jointures de tables. La deuxième possibilité est de récupérer les données séparément, d'exécuter une requête pour obtenir les livres, puis d'obtenir un auteur pour chaque livre au moyen d'une autre requête (par exemple, dans votre cycle foreach). Cela pourrait être facilement optimisé pour n'exécuter que deux requêtes, une pour les livres, et une autre pour les auteurs nécessaires - et c'est exactement la façon dont Nette Database Explorer le fait. +Nette Database Explorer optimise les requêtes pour qu'elles soient aussi efficaces que possible. L'exemple ci-dessus n'exécute que deux requêtes SELECT, que vous traitiez 10 ou 10 000 livres. -Dans les exemples ci-dessous, nous allons travailler avec le schéma de base de données de la figure. Il existe des liens OneHasMany (1:N) (auteur du livre `author_id` et traducteur éventuel `translator_id`, qui peut être `null`) et ManyHasMany (M:N) entre le livre et ses étiquettes. +De plus, Explorer surveille les colonnes utilisées dans votre code et ne charge que celles-ci depuis la base de données, économisant ainsi des ressources. Ce comportement est entièrement automatique et adaptatif. Si vous modifiez ultérieurement votre code et commencez à utiliser d'autres colonnes, Explorer ajuste automatiquement les requêtes. Vous n'avez rien à configurer ni à vous soucier des colonnes dont vous aurez besoin – laissez Nette s'en charger. -[Un exemple, incluant un schéma, se trouve sur GitHub |https://github.com/nette-examples/books]. -[* db-schema-1-.webp *] *** Structure de la base de données utilisée dans les exemples .<> +Filtrage et tri +=============== -Le code suivant liste le nom de l'auteur pour chaque livre et toutes ses balises. Nous [verrons dans un instant |#Working with relationships] comment cela fonctionne en interne. +La classe `Selection` fournit des méthodes pour filtrer et trier la sélection de données. -```php -$books = $explorer->table('book'); +.[language-php] +| `where($condition, ...$params)` | Ajoute une condition WHERE. Plusieurs conditions sont liées par l'opérateur AND +| `whereOr(array $conditions)` | Ajoute un groupe de conditions WHERE liées par l'opérateur OR +| `wherePrimary($value)` | Ajoute une condition WHERE basée sur la clé primaire +| `order($columns, ...$params)` | Définit le tri ORDER BY +| `select($columns, ...$params)` | Spécifie les colonnes à charger +| `limit($limit, $offset = null)` | Limite le nombre de lignes (LIMIT) et définit éventuellement OFFSET +| `page($page, $itemsPerPage, &$total = null)` | Définit la pagination +| `group($columns, ...$params)` | Regroupe les lignes (GROUP BY) +| `having($condition, ...$params)` | Ajoute une condition HAVING pour filtrer les lignes groupées -foreach ($books as $book) { - echo 'titre: ' . $book->title; - echo 'écrit par: ' . $book->author->name; // $book->author est une ligne de la table 'author'. +Les méthodes peuvent être enchaînées (ce qu'on appelle une [interface fluide |nette:introduction-to-object-oriented-programming#Interfaces fluides]) : `$table->where(...)->order(...)->limit(...)`. - echo 'tags: '; - foreach ($book->related('book_tag') as $bookTag) { - echo $bookTag->tag->name . ', '; // $bookTag->tag est une ligne de la table 'tag'. - } -} -``` +Dans ces méthodes, vous pouvez également utiliser une notation spéciale pour accéder aux [données des tables liées |#Interrogation via les tables liées]. -Vous serez heureux de constater l'efficacité du fonctionnement de la couche de base de données. L'exemple ci-dessus fait un nombre constant de requêtes qui ressemblent à ceci : -```sql -SELECT * FROM `book` -SELECT * FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT * FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) -``` +Échappement et identifiants +--------------------------- -Si vous utilisez le [cache |caching:] (activé par défaut), aucune colonne ne sera interrogée inutilement. Après la première requête, le cache stockera les noms des colonnes utilisées et Nette Database Explorer exécutera les requêtes uniquement avec les colonnes nécessaires : +Les méthodes échappent automatiquement les paramètres et protègent les identifiants (noms de tables et de colonnes), prévenant ainsi les injections SQL. Pour un fonctionnement correct, il est nécessaire de respecter quelques règles : -```sql -SELECT `id`, `title`, `author_id` FROM `book` -SELECT `id`, `name` FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT `book_id`, `tag_id` FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT `id`, `name` FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) +- Écrivez les mots-clés SQL, noms de fonctions, procédures, etc. en **MAJUSCULES**. +- Écrivez les noms de colonnes et de tables en **minuscules**. +- Insérez toujours les chaînes de caractères via des **paramètres**. + +```php +where('name = ' . $name); // ⚠️ VULNÉRABILITÉ CRITIQUE : injection SQL +where('name LIKE "%search%"'); // ❌ MAUVAIS : complique la protection automatique des identifiants +where('name LIKE ?', '%search%'); // ✅ CORRECT : valeur insérée via un paramètre + +where('name like ?', $name); // ❌ MAUVAIS : génère : `name` `like` ? (mot-clé en minuscule) +where('name LIKE ?', $name); // ✅ CORRECT : génère : `name` LIKE ? +where('LOWER(name) = ?', $value);// ✅ CORRECT : génère : LOWER(`name`) = ? ``` -Sélections .[#toc-selections] -============================= +where(string|array $condition, ...$parameters): static .[method] +---------------------------------------------------------------- -Voir les possibilités de filtrer et de restreindre les lignes [api:Nette\Database\Table\Selection]: +Filtre les résultats à l'aide de conditions WHERE. Sa force réside dans sa gestion intelligente des différents types de valeurs et dans le choix automatique des opérateurs SQL appropriés. -.[language-php] -| `$table->where($where[, $param[, ...]])` | Définir WHERE en utilisant AND comme colle si deux conditions ou plus sont fournies. -| `$table->whereOr($where)` | Définir WHERE en utilisant OR comme colle si deux ou plusieurs conditions sont fournies -| `$table->order($columns)` | Définir ORDER BY, peut être une expression `('column DESC, id DESC')` -| `$table->select($columns)` | Définir les colonnes extraites, peut être une expression `('col, MD5(col) AS hash')` -| `$table->limit($limit[, $offset])` | Définir LIMIT et OFFSET -| `$table->page($page, $itemsPerPage[, &$lastPage])` | Activation de la pagination -| `$table->group($columns)` | Définir GROUP BY -| `$table->having($having)` | Définir HAVING +Utilisation de base : -Une interface fluide peut être utilisée, par exemple `$table->where(...)->order(...)->limit(...)`. Les conditions multiples de `where` ou `whereOr` sont connectées avec l'opérateur `AND`. +```php +$table->where('id', $value); // WHERE `id` = 123 +$table->where('id > ?', $value); // WHERE `id` > 123 +$table->where('id = ? OR name = ?', $id, $name); // WHERE `id` = 1 OR `name` = 'Jon Snow' +``` +Grâce à la détection automatique des opérateurs appropriés, vous n'avez pas besoin de gérer différents cas spéciaux. Nette s'en charge pour vous : -où() .[#toc-where] ------------------- +```php +$table->where('id', 1); // WHERE `id` = 1 +$table->where('id', null); // WHERE `id` IS NULL +$table->where('id', [1, 2, 3]); // WHERE `id` IN (1, 2, 3) +// Vous pouvez aussi utiliser un placeholder (?) sans opérateur : +$table->where('id ?', 1); // WHERE `id` = 1 +``` -Nette Database Explorer peut ajouter automatiquement les opérateurs nécessaires pour les valeurs passées : +La méthode gère correctement également les conditions négatives et les tableaux vides : -.[language-php] -| `$table->where('field', $value)` | field = $value -| `$table->where('field', null)` | field IS NULL -| `$table->where('field > ?', $val)` | field > $val -| `$table->where('field', [1, 2])` | champ IN (1, 2) -| `$table->where('id = ? OR name = ?', 1, $name)` | id = 1 OR name = 'Jon Snow' (nom de l'utilisateur) -| `$table->where('field', $explorer->table($tableName))` | champ IN (SELECT $primary FROM $tableName) -| `$table->where('field', $explorer->table($tableName)->select('col'))` | champ IN (SELECT col FROM $tableName) +```php +$table->where('id', []); // WHERE `id` IS NULL AND FALSE -- ne trouve rien +$table->where('id NOT', []); // WHERE `id` IS NULL OR TRUE -- trouve tout +$table->where('NOT (id ?)', []); // WHERE NOT (`id` IS NULL AND FALSE) -- trouve tout +// $table->where('NOT id ?', $ids); Attention - cette syntaxe n'est pas supportée +``` -Vous pouvez fournir un caractère de remplacement même sans opérateur de colonne. Ces appels sont les mêmes. +Comme paramètre, vous pouvez également passer le résultat d'une autre table (`Selection`) – une sous-requête sera créée : ```php -$table->where('id = ? OR id = ?', 1, 2); -$table->where('id ? OR id ?', 1, 2); +// WHERE `id` IN (SELECT `id` FROM `tableName`) +$table->where('id', $explorer->table($tableName)); + +// WHERE `id` IN (SELECT `col` FROM `tableName`) +$table->where('id', $explorer->table($tableName)->select('col')); ``` -Cette fonctionnalité permet de générer l'opérateur correct en fonction de la valeur : +Vous pouvez également passer les conditions sous forme de tableau associatif, dont les éléments seront liés par l'opérateur AND : ```php -$table->where('id ?', 2); // id = 2 -$table->where('id ?', null); // id IS NULL -$table->where('id', $ids); // id IN (...) +// WHERE (`price_final` < `price_original`) AND (`stock_count` > `min_stock`) +$table->where([ + 'price_final < price_original', + 'stock_count > min_stock', +]); ``` -La sélection gère correctement les conditions négatives, et fonctionne également pour les tableaux vides : +Dans le tableau, vous pouvez utiliser des paires `clé => valeur`, et Nette choisira à nouveau automatiquement les opérateurs corrects : ```php -$table->where('id', []); // id IS NULL AND FALSE -$table->where('id NOT', []); // id IS NULL OR TRUE -$table->where('NOT (id ?)', $ids); // NOT (id IS NULL AND FALSE) +// WHERE (`status` = 'active') AND (`id` IN (1, 2, 3)) +$table->where([ + 'status' => 'active', + 'id' => [1, 2, 3], +]); +``` + +Dans le tableau, vous pouvez combiner des expressions SQL avec des placeholders (`?`) et plusieurs paramètres. C'est utile pour des conditions complexes avec des opérateurs définis précisément : -// ceci lancera une exception, cette syntaxe n'est pas supportée. -$table->where('NOT id ?', $ids); +```php +// WHERE (`age` > 18) AND (ROUND(`score`, 2) > 75.5) +$table->where([ + 'age > ?' => 18, + 'ROUND(score, ?) > ?' => [2, 75.5], // Deux paramètres passés comme tableau +]); ``` +Les appels multiples à `where()` lient automatiquement les conditions avec l'opérateur AND. -whereOr() .[#toc-whereor] -------------------------- -Exemple d'utilisation sans paramètres : +whereOr(array $parameters): static .[method] +-------------------------------------------- + +Similaire à `where()`, cette méthode ajoute des conditions, mais les lie avec l'opérateur OR : ```php -// WHERE (user_id IS NULL) OR (SUM(`field1`) > SUM(`field2`)) +// WHERE (`status` = 'active') OR (`deleted` = 1) $table->whereOr([ - 'user_id IS NULL', - 'SUM(field1) > SUM(field2)', + 'status' => 'active', + 'deleted' => true, ]); ``` -On utilise les paramètres. Si vous ne spécifiez pas d'opérateur, Nette Database Explorer ajoutera automatiquement l'opérateur approprié : +Ici aussi, vous pouvez utiliser des expressions plus complexes : ```php -// WHERE (`field1` IS NULL) OR (`field2` IN (3, 5)) OR (`amount` > 11) +// WHERE (`price` > 1000) OR (`price_with_tax` > 1500) $table->whereOr([ - 'field1' => null, - 'field2' => [3, 5], - 'amount >' => 11, + 'price > ?' => 1000, + 'price_with_tax > ?' => 1500, ]); ``` -La clé peut contenir une expression contenant des points d'interrogation joker, puis passer des paramètres dans la valeur : + +wherePrimary(mixed $key): static .[method] +------------------------------------------ + +Ajoute une condition pour la clé primaire de la table. ```php -// WHERE (`id` > 12) OR (ROUND(`id`, 5) = 3) -$table->whereOr([ - 'id > ?' => 12, - 'ROUND(id, ?) = ?' => [5, 3], -]); +// WHERE `id` = 123 +$table->wherePrimary(123); + +// WHERE `id` IN (1, 2, 3) +$table->wherePrimary([1, 2, 3]); +``` + +Si la table a une clé primaire composite (par ex. `foo_id`, `bar_id`), passez-la comme un tableau associatif : + +```php +// WHERE `foo_id` = 1 AND `bar_id` = 5 +$table->wherePrimary(['foo_id' => 1, 'bar_id' => 5])->fetch(); + +// WHERE (`foo_id`, `bar_id`) IN ((1, 5), (2, 3)) +$table->wherePrimary([ + ['foo_id' => 1, 'bar_id' => 5], + ['foo_id' => 2, 'bar_id' => 3], +])->fetchAll(); ``` -commande() .[#toc-order] ------------------------- +order(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- -Exemples d'utilisation : +Détermine l'ordre dans lequel les lignes seront retournées. Nous pouvons trier par une ou plusieurs colonnes, par ordre décroissant ou croissant, ou selon une expression personnalisée : ```php -$table->order('field1'); // ORDER BY `field1` -$table->order('field1 DESC, field2'); // ORDER BY `field1` DESC, `field2` -$table->order('field = ? DESC', 123); // ORDER BY `field` = 123 DESC +$table->order('created'); // ORDER BY `created` +$table->order('created DESC'); // ORDER BY `created` DESC +$table->order('priority DESC, created'); // ORDER BY `priority` DESC, `created` +$table->order('status = ? DESC', 'active'); // ORDER BY `status` = 'active' DESC ``` -select() .[#toc-select] ------------------------ +select(string $columns, ...$parameters): static .[method] +--------------------------------------------------------- + +Spécifie les colonnes à retourner de la base de données. Par défaut, Nette Database Explorer ne retourne que les colonnes réellement utilisées dans le code. Nous utilisons donc la méthode `select()` dans les cas où nous avons besoin de retourner des expressions spécifiques : + +```php +// SELECT *, DATE_FORMAT(`created_at`, "%d.%m.%Y") AS `formatted_date` +$table->select('*, DATE_FORMAT(created_at, ?) AS formatted_date', '%d.%m.%Y'); +``` -Exemples d'utilisation : +Les alias définis à l'aide de `AS` sont alors accessibles comme propriétés de l'objet `ActiveRow` : ```php -$table->select('field1'); // SELECT `field1` -$table->select('col, UPPER(col) AS abc'); // SELECT `col`, UPPER(`col`) AS abc -$table->select('SUBSTR(title, ?)', 3); // SELECT SUBSTR(`title`, 3) +foreach ($table as $row) { + echo $row->formatted_date; // accès à l'alias +} ``` -limit() .[#toc-limit] ---------------------- +limit(?int $limit, ?int $offset = null): static .[method] +--------------------------------------------------------- -Exemples d'utilisation : +Limite le nombre de lignes retournées (LIMIT) et permet éventuellement de définir un offset : ```php -$table->limit(1); // LIMIT 1 -$table->limit(1, 10); // LIMIT 1 OFFSET 10 +$table->limit(10); // LIMIT 10 (retourne les 10 premières lignes) +$table->limit(10, 20); // LIMIT 10 OFFSET 20 ``` +Pour la pagination, il est préférable d'utiliser la méthode `page()`. -page() .[#toc-page] -------------------- -Une autre façon de définir la limite et le décalage : +page(int $page, int $itemsPerPage, &$numOfPages = null): static .[method] +------------------------------------------------------------------------- + +Facilite la pagination des résultats. Accepte le numéro de page (compté à partir de 1) et le nombre d'éléments par page. Il est possible de passer en option une référence à une variable dans laquelle sera stocké le nombre total de pages : ```php -$page = 5; -$itemsPerPage = 10; -$table->page($page, $itemsPerPage); // LIMIT 10 OFFSET 40 +$numOfPages = null; +$table->page(page: 3, itemsPerPage: 10, $numOfPages); +echo "Nombre total de pages : $numOfPages"; ``` -Récupérer le dernier numéro de page, passé à la variable `$lastPage`: + +group(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- + +Regroupe les lignes selon les colonnes spécifiées (GROUP BY). Est généralement utilisé en conjonction avec des fonctions d'agrégation : ```php -$table->page($page, $itemsPerPage, $lastPage); +// Compte le nombre de produits dans chaque catégorie +$table->select('category_id, COUNT(*) AS count') + ->group('category_id'); ``` -group() .[#toc-group] ---------------------- +having(string $having, ...$parameters): static .[method] +-------------------------------------------------------- -Exemples d'utilisation : +Définit une condition pour filtrer les lignes groupées (HAVING). Peut être utilisé en conjonction avec la méthode `group()` et les fonctions d'agrégation : ```php -$table->group('field1'); // GROUP BY `field1` -$table->group('field1, field2'); // GROUP BY `field1`, `field2` +// Trouve les catégories qui ont plus de 100 produits +$table->select('category_id, COUNT(*) AS count') + ->group('category_id') + ->having('count > ?', 100); ``` -ayant() .[#toc-having] ----------------------- +Lecture des données +=================== + +Pour lire les données de la base de données, vous disposez de plusieurs méthodes utiles : + +.[language-php] +| `foreach ($table as $key => $row)` | Itère sur toutes les lignes. `$key` est la valeur de la clé primaire, `$row` est l'objet `ActiveRow`. +| `$row = $table->get($key)` | Retourne une seule ligne identifiée par sa clé primaire. +| `$row = $table->fetch()` | Retourne la ligne suivante du résultat. +| `$array = $table->fetchPairs($key = null, $value = null)` | Crée un tableau associatif à partir des résultats. +| `$array = $table->fetchAll()` | Retourne toutes les lignes sous forme de tableau d'objets `ActiveRow`. +| `count($table)` | Retourne le nombre de lignes dans l'objet `Selection` (si déjà chargées) ou exécute `COUNT(*)` si non chargées. + +L'objet [ActiveRow |api:Nette\Database\Table\ActiveRow] est conçu pour la lecture seule. Cela signifie que vous ne pouvez pas modifier directement les valeurs de ses propriétés. Cette restriction garantit la cohérence des données et empêche les effets secondaires inattendus. Les données sont chargées depuis la base de données, et toute modification doit être effectuée explicitement (par exemple via la méthode `update()`). + -Exemples d'utilisation : +`foreach` - itération sur toutes les lignes +------------------------------------------- + +La manière la plus simple d'exécuter une requête et d'obtenir les lignes est d'itérer sur l'objet `Selection` avec une boucle `foreach`. Cela déclenche automatiquement l'exécution de la requête SQL. ```php -$table->having('COUNT(items) >', 100); // HAVING COUNT(`items`) > 100 +$books = $explorer->table('book'); +foreach ($books as $key => $book) { + // $key contient la valeur de la clé primaire, $book est un objet ActiveRow + echo "$book->title ({$book->author->name})"; +} ``` -Filtrage par une autre valeur de la table .[#toc-joining-key] -------------------------------------------------------------- +get($key): ?ActiveRow .[method] +------------------------------- -Vous avez souvent besoin de filtrer les résultats en fonction d'une condition qui implique une autre table de la base de données. Ces types de conditions nécessitent une jointure de table. Cependant, vous n'avez plus besoin de les écrire. +Exécute la requête SQL pour récupérer une seule ligne par sa clé primaire et la retourne sous forme d'objet `ActiveRow`, ou `null` si la ligne n'existe pas. -Disons que vous devez obtenir tous les livres dont l'auteur s'appelle "Jon". Tout ce que vous devez écrire est la clé de jointure de la relation et le nom de la colonne dans la table jointe. La clé de jointure est dérivée de la colonne qui fait référence à la table que vous voulez joindre. Dans notre exemple (voir le schéma de la base de données), il s'agit de la colonne `author_id`, et il suffit d'en utiliser la première partie - `author` (le suffixe `_id` peut être omis). `name` est une colonne de la table `author` que nous souhaitons utiliser. Il est tout aussi facile de créer une condition pour le traducteur de livres (qui est relié par la colonne `translator_id` ). +```php +$book = $explorer->table('book')->get(123); // Retourne ActiveRow avec l'ID 123 ou null +if ($book) { + echo $book->title; +} +``` + + +fetch(): ?ActiveRow .[method] +----------------------------- + +Retourne la ligne suivante du jeu de résultats sous forme d'objet `ActiveRow` et déplace le pointeur interne sur la suivante. S'il n'y a plus de lignes, retourne `null`. ```php $books = $explorer->table('book'); -$books->where('author.name LIKE ?', '%Jon%'); -$books->where('translator.name', 'David Grudl'); +while ($book = $books->fetch()) { + $this->processBook($book); +} ``` -La logique de la clé de jonction est pilotée par la mise en œuvre des [Conventions |api:Nette\Database\Conventions]. Nous vous encourageons à utiliser [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions], qui analyse vos clés étrangères et vous permet de travailler facilement avec ces relations. -La relation entre le livre et son auteur est 1:N. La relation inverse est également possible. Nous l'appelons **backjoin**. Prenons un autre exemple. Nous souhaitons récupérer tous les auteurs qui ont écrit plus de 3 livres. Pour que la jointure soit inversée, nous utilisons l'instruction `:` (colon). Colon means that the joined relationship means hasMany (and it's quite logical too, as two dots are more than one dot). Unfortunately, the Selection class isn't smart enough, so we have to help with the aggregation and provide a `GROUP BY`. La condition doit également être écrite sous la forme d'une instruction `HAVING`. +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Retourne les résultats sous forme de tableau associatif. Le premier argument `$key` spécifie le nom de la colonne à utiliser comme clé dans le tableau. Le second argument `$value` spécifie le nom de la colonne à utiliser comme valeur : ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book.id) > 3'); +$authors = $explorer->table('author')->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] +``` + +Si vous ne spécifiez que le premier paramètre `$key`, la valeur de chaque élément du tableau sera la ligne entière (objet `ActiveRow`) : + +```php +$authors = $explorer->table('author')->fetchPairs('id'); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` + +En cas de clés dupliquées, la valeur de la dernière ligne écrasera les précédentes. Si vous utilisez `null` comme `$key`, le tableau sera indexé numériquement à partir de zéro (évitant ainsi les collisions) : + +```php +$authors = $explorer->table('author')->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] ``` -Vous avez peut-être remarqué que l'expression de jointure fait référence au livre, mais il n'est pas clair si nous faisons la jointure par `author_id` ou `translator_id`. Dans l'exemple ci-dessus, Selection fait la jointure par la colonne `author_id` parce qu'une correspondance avec la table source a été trouvée - la table `author`. Si une telle correspondance n'avait pas été trouvée et qu'il y avait d'autres possibilités, Nette aurait lancé [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. -Pour effectuer une jointure via la colonne `translator_id`, fournissez un paramètre facultatif dans l'expression de jointure. +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Alternativement, vous pouvez passer un callback comme unique paramètre. Ce callback sera appelé pour chaque ligne et devra retourner soit la valeur à ajouter au tableau, soit une paire `[clé, valeur]`. ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book(translator).id) > 3'); +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => "$row->title ({$row->author->name})"); +// ['Premier livre (Jan Novák)', ...] + +// Le callback peut aussi retourner un tableau [clé, valeur] : +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => [$row->title, $row->author->name]); +// ['Premier livre' => 'Jan Novák', ...] ``` -Examinons maintenant des expressions de jointure plus complexes. -Nous aimerions trouver tous les auteurs qui ont écrit quelque chose sur PHP. Tous les livres ont une étiquette, nous devons donc sélectionner les auteurs qui ont écrit un livre avec l'étiquette PHP. +fetchAll(): array .[method] +--------------------------- + +Retourne toutes les lignes du résultat sous forme de tableau d'objets `ActiveRow`. Les clés du tableau sont les valeurs des clés primaires des lignes. ```php -$authors = $explorer->table('author'); -$authors->where(':book:book_tags.tag.name', 'PHP') - ->group('author.id') - ->having('COUNT(:book:book_tags.tag.id) > 0'); +$allBooks = $explorer->table('book')->fetchAll(); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] ``` -Requêtes agrégées .[#toc-aggregate-queries] -------------------------------------------- +count(): int .[method] +---------------------- -| `$table->count('*')` | Obtenir le nombre de lignes -| `$table->count("DISTINCT $column")` | Obtenir le nombre de valeurs distinctes -| `$table->min($column)` | Obtenir la valeur minimale -| `$table->max($column)` | Obtenir la valeur maximale -| `$table->sum($column)` | Obtenir la somme de toutes les valeurs -| `$table->aggregation("GROUP_CONCAT($column)")` | Exécuter une fonction d'agrégation quelconque +La méthode `count()` sans argument retourne le nombre de lignes dans l'objet `Selection` (si les données ont déjà été chargées) ou exécute une requête `SELECT COUNT(*)` pour obtenir le nombre total de lignes correspondant aux conditions définies : -.[caution] -La méthode `count()` sans aucun paramètre spécifié sélectionne tous les enregistrements et renvoie la taille du tableau, ce qui est très inefficace. Par exemple, si vous devez calculer le nombre de lignes pour la pagination, spécifiez toujours le premier argument. +```php +$selection = $explorer->table('book')->where('available', true); +$count = $selection->count(); +$count = count($selection); // Alternative, même comportement +``` + +Attention, `count()` avec un argument exécute une fonction d'agrégation `COUNT()` dans la base de données. + + +ActiveRow::toArray(): array .[method] +------------------------------------- + +Convertit l'objet `ActiveRow` en tableau associatif PHP standard, où les clés sont les noms des colonnes et les valeurs sont les données correspondantes. + +```php +$book = $explorer->table('book')->get(1); +$bookArray = $book->toArray(); +// $bookArray sera ['id' => 1, 'title' => '...', 'author_id' => ..., ...] +``` + + +Agrégation +========== + +La classe `Selection` fournit des méthodes pour effectuer facilement des fonctions d'agrégation SQL (COUNT, SUM, MIN, MAX, AVG, etc.). + +.[language-php] +| `count($expr)` | Compte le nombre de lignes correspondant à l'expression. +| `min($expr)` | Retourne la valeur minimale de la colonne/expression. +| `max($expr)` | Retourne la valeur maximale de la colonne/expression. +| `sum($expr)` | Retourne la somme des valeurs de la colonne/expression. +| `aggregation($function, $groupFunction = null)` | Permet d'effectuer une fonction d'agrégation SQL arbitraire (ex: `AVG()`, `GROUP_CONCAT()`). + + +count(string $expr): int .[method] +---------------------------------- + +Exécute une requête SQL avec la fonction `COUNT` et retourne le résultat. La méthode est utilisée pour compter le nombre de lignes correspondant à une condition ou une expression : + +```php +$count = $table->count('*'); // SELECT COUNT(*) FROM `table` +$count = $table->count('DISTINCT column'); // SELECT COUNT(DISTINCT `column`) FROM `table` +``` + +Attention, [#count()] sans argument retourne le nombre de lignes dans l'objet `Selection` (si déjà chargé) ou exécute `COUNT(*)` si les données ne sont pas chargées. + + +min(string $expr) et max(string $expr) .[method] +------------------------------------------------ + +Les méthodes `min()` et `max()` retournent la valeur minimale et maximale dans la colonne ou l'expression spécifiée pour les lignes sélectionnées : + +```php +// SELECT MAX(`price`) FROM `products` WHERE `active` = 1 +$maxPrice = $products->where('active', true) + ->max('price'); +``` + + +sum(string $expr) .[method] +--------------------------- +Retourne la somme des valeurs dans la colonne ou l'expression spécifiée pour les lignes sélectionnées : -Échappement et citation .[#toc-escaping-quoting] -================================================ +```php +// SELECT SUM(`price` * `items_in_stock`) FROM `products` WHERE `active` = 1 +$totalPrice = $products->where('active', true) + ->sum('price * items_in_stock'); +``` -Database Explorer est intelligent et échappe les paramètres et les identificateurs de citation pour vous. Ces règles de base doivent cependant être respectées : -- les mots-clés, fonctions, procédures doivent être en majuscules -- les colonnes et les tables doivent être en minuscules -- passer des variables comme paramètres, ne pas les concaténer +aggregation(string $function, ?string $groupFunction = null) .[method] +---------------------------------------------------------------------- + +Permet d'exécuter n'importe quelle fonction d'agrégation SQL. ```php -->where('name like ?', 'John'); // FAUX ! génère: `name` `like` ? -->where('name LIKE ?', 'John'); // CORRECT +// prix moyen des produits dans la catégorie +$avgPrice = $products->where('category_id', 1) + ->aggregation('AVG(price)'); -->where('KEY = ?', $value); // FAUX ! KEY est un mot-clé -->where('key = ?', $value); // CORRECT. génère: `key` = ? +// concatène les étiquettes du produit en une seule chaîne +$tags = $products->where('id', 1) + ->aggregation('GROUP_CONCAT(tag.name) AS tags') + ->fetch() + ->tags; +``` -->where('name = ' . $name); // FAUX ! injection sql ! -->where('name = ?', $name); // CORRECT +Si vous avez besoin d'agréger des résultats qui proviennent déjà eux-mêmes d'une fonction d'agrégation et d'un regroupement (par ex., calculer la somme de `SUM(valeur)` sur des lignes groupées), spécifiez comme deuxième argument `$groupFunction` la fonction d'agrégation à appliquer à ces résultats intermédiaires : -->select('DATE_FORMAT(created, "%d.%m.%Y")'); // FAUX ! passer des variables comme paramètres, ne pas concaténer -->select('DATE_FORMAT(created, ?)', '%d.%m.%Y'); // CORRECT +```php +// Calcule le prix total des produits en stock pour chaque catégorie, puis additionne ces prix. +$totalPrice = $products->select('category_id, SUM(price * stock) AS category_total') + ->group('category_id') + ->aggregation('SUM(category_total)', 'SUM'); ``` -.[warning] -Une mauvaise utilisation peut entraîner des failles de sécurité +Dans cet exemple, nous calculons d'abord le prix total des produits dans chaque catégorie (`SUM(price * stock) AS category_total`) et regroupons les résultats par `category_id`. Ensuite, nous utilisons `aggregation('SUM(category_total)', 'SUM')` pour additionner ces sous-totaux `category_total`. Le deuxième argument `'SUM'` indique que la fonction `SUM` doit être appliquée aux résultats intermédiaires (`category_total`). + +Insertion, Mise à jour & Suppression +==================================== -Récupération des données .[#toc-fetching-data] -============================================== +Nette Database Explorer simplifie l'insertion, la mise à jour et la suppression de données. Toutes les méthodes mentionnées lèvent une exception `Nette\Database\DriverException` en cas d'erreur de base de données. -| `foreach ($table as $id => $row)` | Itérer sur toutes les lignes du résultat -| `$row = $table->get($id)` | Récupérer une seule ligne avec l'ID $id dans le tableau. -| `$row = $table->fetch()` | Récupérer la ligne suivante dans le résultat -| `$array = $table->fetchPairs($key, $value)` | Récupérer toutes les valeurs dans un tableau associatif -| `$array = $table->fetchPairs($key)` | Récupère toutes les lignes dans un tableau associatif -| `count($table)` | Obtenir le nombre de lignes dans le jeu de résultats +Selection::insert(iterable $data) .[method] +------------------------------------------- + +Insère un ou plusieurs nouveaux enregistrements dans la table. -Insertion, mise à jour et suppression .[#toc-insert-update-delete] -================================================================== +**Insertion d'un seul enregistrement :** -La méthode `insert()` accepte un tableau d'objets Traversables (par exemple [ArrayHash |utils:arrays#ArrayHash] qui renvoie des [formes |forms:]) : +Passez le nouvel enregistrement sous forme de tableau associatif ou d'objet itérable (par exemple, `Nette\Utils\ArrayHash` utilisé par les [formulaires |forms:]), où les clés correspondent aux noms des colonnes de la table. + +Si la table a une clé primaire définie, la méthode retourne un objet `ActiveRow` représentant la ligne insérée. Cet objet est rechargé depuis la base de données pour refléter les éventuelles modifications effectuées au niveau de la base de données (triggers, valeurs par défaut, auto-incrément). Cela garantit la cohérence des données. Si la table n'a pas de clé primaire unique, la méthode retourne les données transmises sous forme de tableau. ```php $row = $explorer->table('users')->insert([ - 'name' => $name, - 'year' => $year, + 'name' => 'John Doe', + 'email' => 'john.doe@example.com', ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) +// $row est une instance de ActiveRow et contient les données complètes de la ligne insérée, +// y compris l'ID généré automatiquement (si applicable) et les éventuelles modifications par triggers. +echo $row->id; // Affiche l'ID de l'utilisateur nouvellement inséré +echo $row->created_at; // Affiche l'heure de création, si définie par un trigger ou une valeur par défaut ``` -Si la clé primaire est définie sur la table, un objet ActiveRow contenant la ligne insérée est retourné. +**Insertion de plusieurs enregistrements à la fois :** -Insertion multiple : +Passez un tableau de tableaux associatifs ou d'objets itérables. La méthode `insert()` exécute alors une seule requête SQL pour insérer toutes les lignes. Dans ce cas, elle retourne le nombre de lignes insérées. ```php -$explorer->table('users')->insert([ +$insertedRows = $explorer->table('users')->insert([ + [ + 'name' => 'John', + 'year' => 1994, + ], [ - 'name' => 'Jim', - 'year' => 1978, - ], [ 'name' => 'Jack', - 'year' => 1987, + 'year' => 1995, ], ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) +// INSERT INTO `users` (`name`, `year`) VALUES ('John', 1994), ('Jack', 1995) +// $insertedRows sera 2 +``` + +Comme paramètre, on peut également passer un objet `Selection` avec une sélection de données. + +```php +$newUsers = $explorer->table('potential_users') + ->where('approved', 1) + ->select('name, email'); + +$insertedRows = $explorer->table('users')->insert($newUsers); ``` -Des fichiers ou des objets DateTime peuvent être passés comme paramètres : +**Insertion de valeurs spéciales :** + +Comme valeurs, nous pouvons également passer des fichiers, des objets DateTime ou des littéraux SQL : ```php $explorer->table('users')->insert([ - 'name' => $name, - 'created' => new DateTime, // ou $explorer::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // insère le fichier + 'name' => 'John', + 'created_at' => new DateTime, // Converti au format de base de données + 'avatar' => fopen('image.jpg', 'rb'), // Insère le contenu binaire du fichier + 'uuid' => $explorer::literal('UUID()'), // Appelle la fonction SQL UUID() ]); ``` -Mise à jour (renvoie le nombre de lignes affectées) : + +Selection::update(iterable $data): int .[method] +------------------------------------------------ + +Met à jour les lignes de la table correspondant au filtre défini précédemment (par `where()`). Retourne le nombre de lignes réellement modifiées. + +Passez les colonnes à modifier sous forme de tableau associatif ou d'objet itérable (par exemple, `ArrayHash` des [formulaires |forms:]), où les clés correspondent aux noms des colonnes : ```php -$count = $explorer->table('users') - ->where('id', 10) // doit être appelé avant update() +$affected = $explorer->table('users') + ->where('id', 10) ->update([ - 'name' => 'Ned Stark' + 'name' => 'John Smith', + 'year' => 1994, ]); -// UPDATE `users` SET `name`='Ned Stark' WHERE (`id` = 10) +// UPDATE `users` SET `name` = 'John Smith', `year` = 1994 WHERE `id` = 10 ``` -Pour la mise à jour, nous pouvons utiliser les opérateurs `+=` a `-=`: +Pour incrémenter ou décrémenter des valeurs numériques, vous pouvez utiliser les opérateurs `+=` et `-=` dans les clés du tableau : ```php $explorer->table('users') + ->where('id', 10) ->update([ - 'age+=' => 1, // voir += + 'points+=' => 1, // augmente la valeur de la colonne 'points' de 1 + 'coins-=' => 1, // diminue la valeur de la colonne 'coins' de 1 ]); -// UPDATE users SET `age` = `age` + 1 +// UPDATE `users` SET `points` = `points` + 1, `coins` = `coins` - 1 WHERE `id` = 10 ``` -Suppression (renvoie le nombre de lignes supprimées) : + +Selection::delete(): int .[method] +---------------------------------- + +Supprime les lignes de la table selon le filtre spécifié. Retourne le nombre de lignes supprimées. ```php $count = $explorer->table('users') ->where('id', 10) ->delete(); -// DELETE FROM `users` WHERE (`id` = 10) +// DELETE FROM `users` WHERE `id` = 10 ``` +.[caution] +Lors de l'appel de `update()` et `delete()`, n'oubliez pas de spécifier les lignes à modifier/supprimer à l'aide de `where()`. Si vous n'utilisez pas `where()`, l'opération s'effectuera sur toute la table ! -Travailler avec des relations .[#toc-working-with-relationships] -================================================================ +ActiveRow::update(iterable $data): bool .[method] +------------------------------------------------- -A une relation .[#toc-has-one-relation] ---------------------------------------- -Une seule relation est un cas d'utilisation courant. Le livre *a un* auteur. Le livre *a un* traducteur. L'obtention d'une ligne liée se fait principalement par la méthode `ref()`. Elle accepte deux arguments : le nom de la table cible et la colonne de jonction source. Voir l'exemple : +Met à jour les données de la ligne de base de données représentée par l'objet `ActiveRow`. Accepte comme paramètre un itérable (tableau associatif ou objet) avec les données à mettre à jour (les clés sont les noms des colonnes). Pour incrémenter/décrémenter des valeurs numériques, utilisez les opérateurs `+=` et `-=` : + +Après l'exécution de la mise à jour, les propriétés de l'objet `ActiveRow` sont automatiquement mises à jour avec les nouvelles valeurs (elles sont rechargées depuis la base de données pour refléter d'éventuels triggers). La méthode retourne `true` si une modification a réellement eu lieu, `false` sinon. ```php -$book = $explorer->table('book')->get(1); -$book->ref('author', 'author_id'); +$article = $explorer->table('article')->get(1); +$article->update([ + 'views += 1', // augmentons le nombre de vues +]); +echo $article->views; // Affiche le nombre actuel de vues ``` -Dans l'exemple ci-dessus, nous récupérons l'entrée relative à l'auteur dans la table `author`, la clé primaire de l'auteur est recherchée dans la colonne `book.author_id`. La méthode Ref() retourne une instance ActiveRow ou null s'il n'y a pas d'entrée appropriée. La ligne retournée est une instance d'ActiveRow, nous pouvons donc travailler avec elle de la même manière qu'avec l'entrée du livre. +Cette méthode met à jour uniquement la ligne spécifique représentée par l'objet `ActiveRow`. Pour une mise à jour en masse de plusieurs lignes, utilisez la méthode [#`Selection::update()`]. + + +ActiveRow::delete() .[method] +----------------------------- + +Supprime la ligne de la base de données représentée par l'objet `ActiveRow`. ```php -$author = $book->ref('author', 'author_id'); -$author->name; -$author->born; +$book = $explorer->table('book')->get(1); +$book->delete(); // Supprime le livre avec l'ID 1 +``` + +Cette méthode supprime uniquement la ligne spécifique représentée par l'objet `ActiveRow`. Pour une suppression en masse de plusieurs lignes, utilisez la méthode [#`Selection::delete()`]. + + +Relations entre les tables +========================== + +Dans les bases de données relationnelles, les données sont réparties dans plusieurs tables et reliées entre elles par des clés étrangères. Nette Database Explorer apporte une manière révolutionnaire de travailler avec ces relations - sans écrire de requêtes JOIN et sans avoir besoin de configurer ou de générer quoi que ce soit. + +Pour illustrer le travail avec les relations, nous utiliserons l'exemple d'une base de données de livres ([vous le trouverez sur GitHub |https://github.com/nette-examples/books]). Dans la base de données, nous avons les tables : + +- `author` - écrivains et traducteurs (colonnes `id`, `name`, `web`, `born`) +- `book` - livres (colonnes `id`, `author_id`, `translator_id`, `title`, `sequel_id`) +- `tag` - étiquettes (colonnes `id`, `name`) +- `book_tag` - table de liaison entre les livres et les étiquettes (colonnes `book_id`, `tag_id`) + +[* db-schema-1-.webp *] *** Structure de la base de données utilisée dans les exemples *** + +Dans notre exemple de base de données de livres, nous trouvons plusieurs types de relations (bien que le modèle soit simplifié par rapport à la réalité) : + +- Un-à-plusieurs 1:N – chaque livre **a un** auteur, un auteur peut écrire **plusieurs** livres +- Zéro-à-plusieurs 0:N – un livre **peut avoir** un traducteur, un traducteur peut traduire **plusieurs** livres +- Zéro-à-un 0:1 – un livre **peut avoir** un tome suivant +- Plusieurs-à-plusieurs M:N – un livre **peut avoir plusieurs** étiquettes et une étiquette peut être attribuée à **plusieurs** livres -// ou directement -$book->ref('author', 'author_id')->name; -$book->ref('author', 'author_id')->born; +Dans ces relations, il existe toujours une table parente et une table enfant. Par exemple, dans la relation entre l'auteur et le livre, la table `author` est parente et `book` est enfant - on peut imaginer que le livre "appartient" toujours à un auteur. Cela se reflète également dans la structure de la base de données : la table enfant `book` contient une clé étrangère `author_id`, qui référence la table parente `author`. + +Si nous avons besoin d'afficher les livres y compris les noms de leurs auteurs, nous avons deux options. Soit nous obtenons les données avec une seule requête SQL à l'aide de JOIN : + +```sql +SELECT book.*, author.name FROM book LEFT JOIN author ON book.author_id = author.id +``` + +Soit nous chargeons les données en deux étapes - d'abord les livres, puis leurs auteurs - et ensuite nous les assemblons en PHP : + +```sql +SELECT * FROM book; +SELECT * FROM author WHERE id IN (1, 2, 3); -- ids des auteurs des livres obtenus ``` -Le livre a aussi un traducteur, donc obtenir le nom du traducteur est assez facile. +La deuxième approche est en fait plus efficace, même si cela peut être surprenant. Les données sont chargées une seule fois et peuvent être mieux utilisées dans le cache. C'est précisément de cette manière que fonctionne Nette Database Explorer - tout est géré en arrière-plan et vous offre une API élégante : + ```php -$book->ref('author', 'translator_id')->name +$books = $explorer->table('book'); +foreach ($books as $book) { + echo 'titre : ' . $book->title; + echo 'écrit par : ' . $book->author->name; // $book->author est l'enregistrement de la table 'author' + echo 'traduit par : ' . $book->translator?->name; +} ``` -Tout cela est bien, mais c'est un peu lourd, ne pensez-vous pas ? Database Explorer contient déjà les définitions des clés étrangères, alors pourquoi ne pas les utiliser automatiquement ? Faisons cela ! -Si nous appelons une propriété qui n'existe pas, ActiveRow essaie de résoudre le nom de la propriété appelante comme une relation 'has one'. Obtenir cette propriété est la même chose qu'appeler la méthode ref() avec un seul argument. Nous appellerons le seul argument **key**. La clé sera résolue en relation particulière de clé étrangère. La clé passée est comparée aux colonnes de la ligne, et si elle correspond, la clé étrangère définie sur la colonne correspondante est utilisée pour obtenir les données de la table cible correspondante. Voir l'exemple : +Accès à la table parente (Relation N:1) +--------------------------------------- + +Accéder à la table parente (la table référencée par une clé étrangère) est très simple. Cela correspond aux relations comme "un livre a un auteur" ou "un livre peut avoir un traducteur". Vous obtenez l'enregistrement lié via une propriété dynamique de l'objet `ActiveRow`. Le nom de cette propriété correspond au nom de la colonne contenant la clé étrangère, sans le suffixe `_id` (par convention) : ```php -$book->author->name; -// identique à -$book->ref('author')->name; +$book = $explorer->table('book')->get(1); +echo $book->author->name; // trouve l'auteur selon la colonne author_id +echo $book->translator?->name; // trouve le traducteur selon translator_id ``` -L'instance ActiveRow n'a pas de colonne auteur. Toutes les colonnes de livres sont recherchées pour une correspondance avec *key*. Dans ce cas, la correspondance signifie que le nom de la colonne doit contenir la clé. Ainsi, dans l'exemple ci-dessus, la colonne `author_id` contient la chaîne 'auteur' et est donc recherchée par la clé 'auteur'. Si vous voulez obtenir le traducteur du livre, vous pouvez utiliser par exemple 'translator' comme clé, car la clé 'translator' correspondra à la colonne `translator_id`. Vous trouverez plus d'informations sur la logique de correspondance des clés dans le chapitre sur les [expressions de jointure |#joining-key]. +Lorsque vous accédez à la propriété `$book->author`, Explorer recherche dans la table `book` une colonne dont le nom est dérivé de `author` (généralement `author_id`). Il utilise la valeur de cette colonne pour charger l'enregistrement correspondant dans la table `author` et le retourne sous forme d'objet `ActiveRow`. Le même mécanisme s'applique pour `$book->translator` via la colonne `translator_id`. Comme la colonne `translator_id` peut contenir `null`, nous utilisons l'opérateur `?->` dans le code. + +Une alternative est la méthode `ref()`, qui prend deux arguments : le nom de la table cible et (optionnellement) le nom de la colonne de liaison. Elle retourne l'instance `ActiveRow` ou `null` : ```php -echo $book->title . ': '; -echo $book->author->name; -if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; -} +echo $book->ref('author', 'author_id')->name; // liaison à l'auteur +echo $book->ref('author', 'translator_id')->name; // liaison au traducteur ``` -Si vous souhaitez récupérer plusieurs livres, vous devez utiliser la même approche. Nette Database Explorer recherchera les auteurs et les traducteurs pour tous les livres recherchés en une seule fois. +La méthode `ref()` est utile si le nom de la propriété dynamique entre en conflit avec un nom de colonne existant dans la table (`author` dans cet exemple). Sinon, l'accès via la propriété est généralement plus lisible et recommandé. + +Explorer optimise automatiquement les requêtes. Lorsque vous parcourez des livres dans une boucle et accédez à leurs enregistrements liés (auteurs, traducteurs), Explorer ne génère pas une requête distincte pour chaque livre. Au lieu de cela, il effectue **une seule requête SELECT par type de relation** pour récupérer tous les enregistrements liés nécessaires en une seule fois (technique connue sous le nom de "eager loading" implicite), réduisant considérablement la charge de la base de données : ```php $books = $explorer->table('book'); foreach ($books as $book) { echo $book->title . ': '; echo $book->author->name; - if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; - } + echo $book->translator?->name; } ``` -Le code n'exécutera que ces 3 requêtes : +Ce code n'exécutera que trois requêtes rapides, quel que soit le nombre de livres : + ```sql SELECT * FROM `book`; -SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- ids of fetched books from author_id column -SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- ids of fetched books from translator_id column +SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- id de la colonne author_id des livres sélectionnés +SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- id de la colonne translator_id des livres sélectionnés ``` +.[note] +La logique de détection de la colonne de liaison est gérée par l'implémentation des [Conventions |api:Nette\Database\Conventions]. Nous recommandons l'utilisation de [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions], qui analyse les clés étrangères définies dans votre base de données et permet de travailler facilement avec les relations existantes. -A plusieurs relations .[#toc-has-many-relation] ------------------------------------------------ -La relation "has many" est une relation inversée "has one". L'auteur *a* écrit *beaucoup* de livres. L'auteur *a* traduit *beaucoup* de livres. Comme vous pouvez le constater, ce type de relation est un peu plus difficile car la relation est "nommée" ("écrit", "traduit"). L'instance ActiveRow possède la méthode `related()`, qui renvoie un tableau d'entrées liées. Les entrées sont également des instances ActiveRow. Voir l'exemple ci-dessous : +Accès à la table enfant +----------------------- + +L'accès à la table enfant fonctionne dans le sens inverse. Nous demandons maintenant *quels livres cet auteur a-t-il écrits* ou *traduits*. Pour ce type de requête, nous utilisons la méthode `related()`, qui retourne une `Selection` avec les enregistrements liés. Regardons un exemple : ```php -$author = $explorer->table('author')->get(11); -echo $author->name . ' has written:'; +$author = $explorer->table('author')->get(1); +// Affiche tous les livres de l'auteur foreach ($author->related('book.author_id') as $book) { - echo $book->title; + echo "A écrit : $book->title"; } -echo 'and translated:'; +// Affiche tous les livres que l'auteur a traduits foreach ($author->related('book.translator_id') as $book) { - echo $book->title; + echo "A traduit : $book->title"; } ``` -Méthode `related()` La méthode accepte la description complète de la jointure passée comme deux arguments ou comme un argument joint par un point. Le premier argument est la table cible, le second est la colonne cible. +La méthode `related()` accepte la description de la liaison comme un seul argument avec la notation par points ou comme deux arguments séparés : ```php -$author->related('book.translator_id'); -// identique à -$author->related('book', 'translator_id'); +$author->related('book.translator_id'); // un argument +$author->related('book', 'translator_id'); // deux arguments ``` -Vous pouvez utiliser l'heuristique de Nette Database Explorer basée sur les clés étrangères et fournir uniquement l'argument **key**. La clé sera comparée à toutes les clés étrangères pointant vers la table actuelle (`author` table). S'il y a une correspondance, Nette Database Explorer utilisera cette clé étrangère, sinon, il lancera [Nette\InvalidArgumentException |api:Nette\InvalidArgumentException] ou [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. Vous pouvez trouver plus d'informations sur la logique de correspondance des clés dans le chapitre sur les [expressions de jointure |#joining-key]. +Explorer peut détecter automatiquement la colonne de liaison correcte en fonction du nom de la table parente. Dans ce cas, la liaison se fait via la colonne `book.author_id`, car le nom de la table source est `author` : -Bien sûr, vous pouvez appeler les méthodes connexes pour tous les auteurs récupérés, Nette Database Explorer récupérera à nouveau les livres appropriés en une seule fois. +```php +$author->related('book'); // utilise book.author_id +``` + +S'il existait plusieurs liaisons possibles, Explorer lèverait une exception [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. + +Nous pouvons bien sûr utiliser la méthode `related()` également lors du parcours de plusieurs enregistrements dans une boucle, et Explorer optimise également automatiquement les requêtes dans ce cas : ```php $authors = $explorer->table('author'); foreach ($authors as $author) { - echo $author->name . ' has written:'; + echo $author->name . ' a écrit :'; foreach ($author->related('book') as $book) { - $book->title; + echo $book->title; } } ``` -L'exemple ci-dessus n'exécutera que deux requêtes : +Ce code ne génère que deux requêtes SQL rapides : ```sql SELECT * FROM `author`; -SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- ids of fetched authors +SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- id des auteurs sélectionnés +``` + + +Relation Plusieurs-à-plusieurs +------------------------------ + +Pour une relation plusieurs-à-plusieurs (M:N), l'existence d'une table de liaison est nécessaire (dans notre cas `book_tag`), qui contient deux colonnes avec des clés étrangères (`book_id`, `tag_id`). Chacune de ces colonnes référence la clé primaire de l'une des tables liées. Pour obtenir les données liées, nous obtenons d'abord les enregistrements de la table de liaison à l'aide de `related('book_tag')` et continuons ensuite vers les données cibles : + +```php +$book = $explorer->table('book')->get(1); +// affiche les noms des étiquettes attribuées au livre +foreach ($book->related('book_tag') as $bookTag) { + echo $bookTag->tag->name; // affiche le nom de l'étiquette via la table de liaison +} + +$tag = $explorer->table('tag')->get(1); +// ou inversement : affiche les noms des livres marqués avec cette étiquette +foreach ($tag->related('book_tag') as $bookTag) { + echo $bookTag->book->title; // affiche le nom du livre +} +``` + +Explorer optimise à nouveau les requêtes SQL en une forme efficace : + +```sql +SELECT * FROM `book`; +SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 2, ...)); -- id des livres sélectionnés +SELECT * FROM `tag` WHERE (`tag`.`id` IN (1, 2, ...)); -- id des étiquettes trouvées dans book_tag +``` + + +Interrogation via les tables liées +---------------------------------- + +Dans les méthodes `where()`, `select()`, `order()` et `group()`, nous pouvons utiliser des notations spéciales pour accéder aux colonnes d'autres tables. Explorer crée automatiquement les JOIN nécessaires. + +**Notation par points** (`table_parente.colonne`) est utilisée pour la relation 1:N du point de vue de la table enfant : + +```php +$books = $explorer->table('book'); + +// Trouve les livres dont l'auteur a un nom commençant par 'Jon' +$books->where('author.name LIKE ?', 'Jon%'); + +// Trie les livres par nom d'auteur décroissant +$books->order('author.name DESC'); + +// Affiche le titre du livre et le nom de l'auteur +$books->select('book.title, author.name'); +``` + +**Notation par deux-points** (`:table_enfant.colonne`) est utilisée pour la relation 1:N du point de vue de la table parente : + +```php +$authors = $explorer->table('author'); + +// Trouve les auteurs qui ont écrit un livre avec 'PHP' dans le titre +$authors->where(':book.title LIKE ?', '%PHP%'); + +// Compte le nombre de livres pour chaque auteur +$authors->select('*, COUNT(:book.id) AS book_count') + ->group('author.id'); ``` +Dans l'exemple ci-dessus avec la notation par deux-points (`:book.title`), la colonne avec la clé étrangère n'est pas spécifiée. Explorer détecte automatiquement la colonne correcte en fonction du nom de la table parente. Dans ce cas, la liaison se fait via la colonne `book.author_id`, car le nom de la table source est `author`. S'il existait plusieurs liaisons possibles, Explorer lèverait une exception [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. -Création manuelle de l'explorateur .[#toc-creating-explorer-manually] -===================================================================== +La colonne de liaison peut être explicitement indiquée entre parenthèses : -Une connexion à la base de données peut être créée en utilisant la configuration de l'application. Dans ce cas, un service `Nette\Database\Explorer` est créé et peut être transmis comme dépendance à l'aide du conteneur DI. +```php +// Trouve les auteurs qui ont traduit un livre avec 'PHP' dans le titre +$authors->where(':book(translator_id).title LIKE ?', '%PHP%'); +``` -Cependant, si Nette Database Explorer est utilisé comme un outil autonome, une instance de l'objet `Nette\Database\Explorer` doit être créée manuellement. +Les notations peuvent être enchaînées pour accéder via plusieurs tables : ```php -// $storage implémente Nette\Caching\Storage: -$storage = new Nette\Caching\Storages\FileStorage($tempDir); -$connection = new Nette\Database\Connection($dsn, $user, $password); -$structure = new Nette\Database\Structure($connection, $storage); -$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); -$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +// Trouve les auteurs de livres marqués avec l'étiquette 'PHP' +$authors->where(':book:book_tag.tag.name', 'PHP') + ->group('author.id'); ``` + + +Extension des conditions pour JOIN +---------------------------------- + +La méthode `joinWhere()` étend les conditions qui sont spécifiées lors de la liaison des tables en SQL après le mot-clé `ON`. + +Supposons que nous voulions trouver les livres traduits par un traducteur spécifique : + +```php +// Trouve les livres traduits par le traducteur nommé 'David' +$books = $explorer->table('book') + ->joinWhere('translator', 'translator.name', 'David'); +// LEFT JOIN author translator ON book.translator_id = translator.id AND (translator.name = 'David') +``` + +Dans la condition `joinWhere()`, nous pouvons utiliser les mêmes constructions que dans la méthode `where()` - opérateurs, points d'interrogation, tableaux de valeurs ou expressions SQL. + +Pour des requêtes plus complexes avec plusieurs JOIN, nous pouvons définir des alias de tables : + +```php +$tags = $explorer->table('tag') + ->joinWhere(':book_tag.book.author', 'book_author.born < ?', 1950) + ->alias(':book_tag.book.author', 'book_author'); +// LEFT JOIN `book_tag` ON `tag`.`id` = `book_tag`.`tag_id` +// LEFT JOIN `book` ON `book_tag`.`book_id` = `book`.`id` +// LEFT JOIN `author` `book_author` ON `book`.`author_id` = `book_author`.`id` +// AND (`book_author`.`born` < 1950) +``` + +Notez que tandis que la méthode `where()` ajoute des conditions à la clause `WHERE`, la méthode `joinWhere()` étend les conditions dans la clause `ON` lors de la liaison des tables. diff --git a/database/fr/guide.texy b/database/fr/guide.texy new file mode 100644 index 0000000000..3bed959c52 --- /dev/null +++ b/database/fr/guide.texy @@ -0,0 +1,216 @@ +Nette Database +************** + +.[perex] +Nette Database est une couche de base de données puissante et élégante pour PHP, mettant l'accent sur la simplicité et les fonctionnalités intelligentes. Elle offre deux façons de travailler avec la base de données - [Explorer |Explorer] pour un développement rapide d'applications, ou [l'accès SQL |SQL way] pour travailler directement avec les requêtes. + +<div class="grid gap-3"> +<div> + + +[Accès SQL |SQL way] +==================== +- Requêtes paramétrées sécurisées +- Contrôle précis sur la forme des requêtes SQL +- Lorsque vous écrivez des requêtes complexes avec des fonctionnalités avancées +- Vous optimisez les performances en utilisant des fonctions SQL spécifiques + +</div> + +<div> + + +[Explorer |Explorer] +==================== +- Vous développez rapidement sans écrire de SQL +- Travail intuitif avec les relations entre les tables +- Vous apprécierez l'optimisation automatique des requêtes +- Convient pour un travail rapide et confortable avec la base de données + +</div> + +</div> + + +Installation +============ + +Téléchargez et installez la bibliothèque à l'aide de l'[outil Composer |best-practices:composer] : + +```shell +composer require nette/database +``` + + +Bases de données supportées +=========================== + +Nette Database supporte les bases de données suivantes : + +|* Serveur de base de données |* Nom DSN |* Support dans Explorer +|---------------------|-------------|----------------------- +| MySQL (>= 5.1) | mysql | OUI +| PostgreSQL (>= 9.0) | pgsql | OUI +| Sqlite 3 (>= 3.8) | sqlite | OUI +| Oracle | oci | - +| MS SQL (PDO_SQLSRV) | sqlsrv | OUI +| MS SQL (PDO_DBLIB) | mssql | - +| ODBC | odbc | - + + +Deux approches de la base de données +==================================== + +Nette Database vous donne le choix : écrire directement des requêtes SQL (accès SQL) ou laisser Explorer les générer automatiquement. Voyons comment les deux approches résolvent les mêmes tâches : + +[Accès SQL|sql way] - Requêtes SQL + +```php +// Insertion d'un enregistrement +$database->query('INSERT INTO books', [ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// Récupération des auteurs actifs avec le nombre de livres +$result = $database->query(' + SELECT authors.*, COUNT(books.id) AS books_count + FROM authors + LEFT JOIN books ON authors.id = books.author_id + WHERE authors.active = 1 + GROUP BY authors.id +'); + +// Affichage (problème N+1 : génère N requêtes supplémentaires pour les livres) +foreach ($result as $author) { + $books = $database->query(' + SELECT * FROM books + WHERE author_id = ? + ORDER BY published_at DESC + ', $author->id); + + echo "L'auteur $author->name a écrit $author->books_count livres :\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +[Approche Explorer|explorer] - Génération automatique de SQL + +```php +// Insertion d'un enregistrement +$database->table('books')->insert([ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// Récupération des auteurs actifs +$authors = $database->table('authors') + ->where('active', 1); + +// Affichage (optimisé : génère seulement 2 requêtes au total) +foreach ($authors as $author) { + $books = $author->related('books') + ->order('published_at DESC'); + + echo "L'auteur $author->name a écrit {$books->count()} livres :\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +L'approche Explorer génère et optimise automatiquement les requêtes SQL. Dans l'exemple ci-dessus, l'accès SQL souffre du problème "N+1" (une requête pour les auteurs, puis une requête par auteur pour ses livres), tandis qu'Explorer optimise cela en seulement deux requêtes au total : une pour les auteurs et une pour tous leurs livres associés. + +Vous pouvez combiner librement les deux approches dans votre application selon vos besoins. + + +Connexion et configuration +========================== + +Pour vous connecter à la base de données, il suffit de créer une instance de la classe [api:Nette\Database\Connection] : + +```php +$database = new Nette\Database\Connection($dsn, $user, $password); +``` + +Le paramètre `$dsn` (Data Source Name) est le même que celui [utilisé par PDO |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters]. En cas d'échec de connexion, une exception `Nette\Database\ConnectionException` est levée. + +Cependant, la méthode recommandée est d'utiliser la [configuration de l'application |configuration] (fichier NEON). Ajoutez simplement une section `database`, et Nette DI créera automatiquement les services nécessaires (`Connection` et `Explorer`), ainsi que le panneau de base de données dans la barre de débogage [Tracy |tracy:]. + +```neon +database: + dsn: 'mysql:host=127.0.0.1;dbname=test' + user: root + password: password +``` + +Ensuite, vous [obtenez l'objet de connexion ou l'Explorer en tant que service via l'injection de dépendances |dependency-injection:passing-dependencies] : + +```php +class Model +{ + public function __construct( + // ou Nette\Database\Explorer + private Nette\Database\Connection $database, + ) { + } +} +``` + +Consultez la section sur la [configuration de la base de données |configuration] pour plus de détails. + + +Création manuelle de l'Explorer +------------------------------- + +Si vous n'utilisez pas Nette DI, vous pouvez créer manuellement une instance de `Nette\Database\Explorer` : + +```php +// connexion à la base de données +$connection = new Nette\Database\Connection('mysql:host=127.0.0.1;dbname=mydatabase', 'user', 'password'); +// stockage pour le cache, implémente Nette\Caching\Storage, par ex. : +$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp/dir'); +// s'occupe de la réflexion de la structure de la base de données +$structure = new Nette\Database\Structure($connection, $storage); +// définit les règles de mappage des noms de tables, colonnes et clés étrangères +$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); +$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +``` + + +Gestion de la connexion +======================= + +Lors de la création de l'objet `Connection`, la connexion est établie automatiquement. Si vous souhaitez différer la connexion, utilisez le mode lazy - activez-le dans la [configuration |configuration] en définissant `lazy`, ou comme ceci : + +```php +$database = new Nette\Database\Connection($dsn, $user, $password, ['lazy' => true]); +``` + +Pour gérer la connexion, utilisez les méthodes `connect()`, `disconnect()` et `reconnect()`. +- `connect()` crée la connexion si elle n'existe pas encore, et peut lever une exception `Nette\Database\ConnectionException`. +- `disconnect()` déconnecte la connexion actuelle à la base de données. +- `reconnect()` effectue une déconnexion puis une reconnexion à la base de données. Cette méthode peut également lever une exception `Nette\Database\ConnectionException`. + +De plus, vous pouvez surveiller les événements liés à la connexion en utilisant l'événement `onConnect`, qui est un tableau de callbacks appelés après l'établissement de la connexion à la base de données. + +```php +// s'exécute après la connexion à la base de données +$database->onConnect[] = function($database) { + echo "Connecté à la base de données"; +}; +``` + + +Barre de débogage Tracy +======================= + +Si vous utilisez [Tracy |tracy:], le panneau Database s'active automatiquement dans la barre de débogage, affichant toutes les requêtes exécutées, leurs paramètres, leur temps d'exécution et l'endroit dans le code où elles ont été appelées. + +[* db-panel.webp *] diff --git a/database/fr/mapping.texy b/database/fr/mapping.texy new file mode 100644 index 0000000000..2ef1d96e1b --- /dev/null +++ b/database/fr/mapping.texy @@ -0,0 +1,55 @@ +Conversion de types +******************* + +.[perex] +Nette Database convertit automatiquement les valeurs renvoyées par la base de données en types PHP correspondants. + + +Date et heure +------------- + +Les données temporelles sont converties en objets `Nette\Utils\DateTime`. Si vous souhaitez que les données temporelles soient converties en objets immuables `Nette\Database\DateTime`, définissez l'option `newDateTime` sur true dans la [configuration |configuration]. + +```php +$row = $database->fetch('SELECT created_at FROM articles'); +echo $row->created_at instanceof DateTime; // true +echo $row->created_at->format('j. n. Y'); +``` + +Dans le cas de MySQL, le type de données `TIME` est converti en objets `DateInterval`. + + +Valeurs booléennes +------------------ + +Les valeurs booléennes sont automatiquement converties en `true` ou `false`. Pour MySQL, `TINYINT(1)` est converti si nous définissons `convertBoolean` dans la [configuration |configuration]. + +```php +$row = $database->fetch('SELECT is_published FROM articles'); +echo gettype($row->is_published); // 'boolean' +``` + + +Valeurs numériques +------------------ + +Les valeurs numériques sont converties en `int` ou `float` selon le type de colonne dans la base de données : + +```php +$row = $database->fetch('SELECT id, price FROM products'); +echo gettype($row->id); // integer +echo gettype($row->price); // float +``` + + +Normalisation personnalisée +--------------------------- + +Avec la méthode `setRowNormalizer(?callable $normalizer)`, vous pouvez définir votre propre fonction pour transformer les lignes de la base de données. Ceci est utile, par exemple, pour la conversion automatique des types de données. + +```php +$database->setRowNormalizer(function(array $row, ResultSet $resultSet): array { + // la conversion de type a lieu ici + return $row; +}); +``` diff --git a/database/fr/reflection.texy b/database/fr/reflection.texy new file mode 100644 index 0000000000..c594ffc8e1 --- /dev/null +++ b/database/fr/reflection.texy @@ -0,0 +1,125 @@ +Réflexion de la structure +************************* + +.{data-version:3.2.1} +Nette Database fournit des outils pour l'introspection de la structure de la base de données à l'aide de la classe [api:Nette\Database\Reflection]. Elle permet d'obtenir des informations sur les tables, les colonnes, les index et les clés étrangères. Vous pouvez utiliser la réflexion pour générer des schémas, créer des applications flexibles travaillant avec la base de données ou des outils de base de données généraux. + +Nous obtenons l'objet de réflexion à partir de l'instance de connexion à la base de données : + +```php +$reflection = $database->getReflection(); +``` + + +Obtention des tables +-------------------- + +La propriété en lecture seule `$reflection->tables` contient un tableau associatif de toutes les tables de la base de données : + +```php +// Liste des noms de toutes les tables +foreach ($reflection->tables as $name => $table) { + echo $name . "\n"; +} +``` + +Deux autres méthodes sont également disponibles : + +```php +// Vérification de l'existence de la table +if ($reflection->hasTable('users')) { + echo "La table users existe"; +} + +// Renvoie l'objet table ; lève une exception s'il n'existe pas +$table = $reflection->getTable('users'); +``` + + +Informations sur la table +------------------------- + +La table est représentée par l'objet [Table|api:Nette\Database\Reflection\Table], qui fournit les propriétés en lecture seule suivantes : + +- `$name: string` – nom de la table +- `$view: bool` – s'il s'agit d'une vue +- `$fullName: ?string` – nom complet de la table incluant le schéma (si existant) +- `$columns: array<string, Column>` – tableau associatif des colonnes de la table +- `$indexes: Index[]` – tableau des index de la table +- `$primaryKey: ?Index` – clé primaire de la table ou null +- `$foreignKeys: ForeignKey[]` – tableau des clés étrangères de la table + + +Colonnes +-------- + +La propriété `columns` de la table fournit un tableau associatif des colonnes, où la clé est le nom de la colonne et la valeur est une instance de [Column|api:Nette\Database\Reflection\Column] avec ces propriétés : + +- `$name: string` – nom de la colonne +- `$table: ?Table` – référence à la table de la colonne +- `$nativeType: string` – type de base de données natif +- `$size: ?int` – taille/longueur du type +- `$nullable: bool` – si la colonne peut contenir NULL +- `$default: mixed` – valeur par défaut de la colonne +- `$autoIncrement: bool` – si la colonne est auto-incrémentée +- `$primary: bool` – si elle fait partie de la clé primaire +- `$vendor: array` – métadonnées supplémentaires spécifiques au système de base de données donné + +```php +foreach ($table->columns as $name => $column) { + echo "Colonne : $name\n"; + echo "Type : {$column->nativeType}\n"; + echo "Nullable : " . ($column->nullable ? 'Oui' : 'Non') . "\n"; +} +``` + + +Index +----- + +La propriété `indexes` de la table fournit un tableau d'index, où chaque index est une instance de [Index|api:Nette\Database\Reflection\Index] avec ces propriétés : + +- `$columns: Column[]` – tableau des colonnes formant l'index +- `$unique: bool` – si l'index est unique +- `$primary: bool` – s'il s'agit de la clé primaire +- `$name: ?string` – nom de l'index + +La clé primaire de la table peut être obtenue à l'aide de la propriété `primaryKey`, qui renvoie soit un objet `Index`, soit `null` si la table n'a pas de clé primaire. + +```php +// Liste des index +foreach ($table->indexes as $index) { + $columns = implode(', ', array_map(fn($col) => $col->name, $index->columns)); + echo "Index" . ($index->name ? " {$index->name}" : '') . ":\n"; + echo " Colonnes : $columns\n"; + echo " Unique : " . ($index->unique ? 'Oui' : 'Non') . "\n"; +} + +// Liste de la clé primaire +if ($primaryKey = $table->primaryKey) { + $columns = implode(', ', array_map(fn($col) => $col->name, $primaryKey->columns)); + echo "Clé primaire : $columns\n"; +} +``` + + +Clés étrangères +--------------- + +La propriété `foreignKeys` de la table fournit un tableau de clés étrangères, où chaque clé étrangère est une instance de [ForeignKey|api:Nette\Database\Reflection\ForeignKey] avec ces propriétés : + +- `$foreignTable: Table` – table référencée +- `$localColumns: Column[]` – tableau des colonnes locales +- `$foreignColumns: Column[]` – tableau des colonnes référencées +- `$name: ?string` – nom de la clé étrangère + +```php +// Liste des clés étrangères +foreach ($table->foreignKeys as $fk) { + $localCols = implode(', ', array_map(fn($col) => $col->name, $fk->localColumns)); + $foreignCols = implode(', ', array_map(fn($col) => $col->name, $fk->foreignColumns)); + + echo "FK" . ($fk->name ? " {$fk->name}" : '') . ":\n"; + echo " $localCols -> {$fk->foreignTable->name}($foreignCols)\n"; +} +``` diff --git a/database/fr/security.texy b/database/fr/security.texy new file mode 100644 index 0000000000..fea9dda5c7 --- /dev/null +++ b/database/fr/security.texy @@ -0,0 +1,185 @@ +Risques de sécurité +******************* + +<div class=perex> + +La base de données contient souvent des données sensibles et permet d'effectuer des opérations dangereuses. Pour travailler en toute sécurité avec Nette Database, il est crucial de : + +- Comprendre la différence entre une API sécurisée et non sécurisée +- Utiliser des requêtes paramétrées +- Valider correctement les données d'entrée + +</div> + + +Qu'est-ce que l'injection SQL ? +=============================== + +L'injection SQL est le risque de sécurité le plus grave lors du travail avec une base de données. Elle se produit lorsque l'entrée non traitée d'un utilisateur fait partie d'une requête SQL. Un attaquant peut insérer ses propres commandes SQL et ainsi : +- Obtenir un accès non autorisé aux données +- Modifier ou supprimer des données dans la base de données +- Contourner l'authentification + +```php +// ❌ CODE DANGEREUX - vulnérable à l'injection SQL +$database->query("SELECT * FROM users WHERE name = '$_GET[name]'"); + +// L'attaquant peut entrer une valeur comme : ' OR '1'='1 +// La requête résultante sera : SELECT * FROM users WHERE name = '' OR '1'='1' +// Ce qui renvoie tous les utilisateurs +``` + +Il en va de même pour Database Explorer : + +```php +// ❌ CODE DANGEREUX - vulnérable à l'injection SQL +$table->where('name = ' . $_GET['name']); +$table->where("name = '$_GET[name]'"); +``` + + +Requêtes paramétrées +==================== + +La défense de base contre l'injection SQL consiste à utiliser des requêtes paramétrées. Nette Database offre plusieurs façons de les utiliser. + +La méthode la plus simple consiste à utiliser des **points d'interrogation comme placeholders** : + +```php +// ✅ Requête paramétrée sécurisée +$database->query('SELECT * FROM users WHERE name = ?', $name); + +// ✅ Condition sécurisée dans l'Explorer +$table->where('name = ?', $name); +``` + +Cela s'applique à toutes les autres méthodes de [Database Explorer |explorer] qui permettent d'insérer des expressions avec des points d'interrogation et des paramètres. + +Pour les commandes INSERT, UPDATE ou la clause WHERE, nous pouvons passer les valeurs dans un tableau : + +```php +// ✅ INSERT sécurisé +$database->query('INSERT INTO users', [ + 'name' => $name, + 'email' => $email, +]); + +// ✅ INSERT sécurisé dans l'Explorer +$table->insert([ + 'name' => $name, + 'email' => $email, +]); +``` + + +Validation des valeurs des paramètres +===================================== + +Les requêtes paramétrées sont la pierre angulaire d'un travail sécurisé avec la base de données. Cependant, les valeurs que nous y insérons doivent passer par plusieurs niveaux de contrôles : + + +Contrôle de type +---------------- + +**Le plus important est d'assurer le type de données correct des paramètres** - c'est une condition nécessaire pour une utilisation sécurisée de Nette Database. La base de données suppose que toutes les données d'entrée ont le type de données correct correspondant à la colonne donnée. + +Par exemple, si `$name` dans les exemples précédents était de manière inattendue un tableau au lieu d'une chaîne, Nette Database tenterait d'insérer tous ses éléments dans la requête SQL, ce qui entraînerait une erreur. Par conséquent, **n'utilisez jamais** de données non validées de `$_GET`, `$_POST` ou `$_COOKIE` directement dans les requêtes de base de données. + + +Contrôle de format +------------------ + +Au deuxième niveau, nous vérifions le format des données - par exemple, si les chaînes sont en encodage UTF-8 et si leur longueur correspond à la définition de la colonne, ou si les valeurs numériques sont dans la plage autorisée pour le type de données de la colonne donnée. + +Pour ce niveau de validation, nous pouvons également compter en partie sur la base de données elle-même - de nombreuses bases de données refuseront les données non valides. Cependant, le comportement peut varier, certaines peuvent tronquer silencieusement les longues chaînes ou couper les nombres hors plage. + + +Contrôle de domaine +------------------- + +Le troisième niveau représente les contrôles logiques spécifiques à votre application. Par exemple, vérifier que les valeurs des listes déroulantes correspondent aux options proposées, que les nombres sont dans la plage attendue (par exemple, âge 0-150 ans) ou que les dépendances mutuelles entre les valeurs ont un sens. + + +Méthodes de validation recommandées +----------------------------------- + +- Utilisez [Nette Forms |forms:], qui assurent automatiquement la validation correcte de toutes les entrées +- Utilisez les [Presenters |application:] et spécifiez les types de données pour les paramètres dans les méthodes `action*()` et `render*()` +- Ou implémentez votre propre couche de validation en utilisant des outils PHP standard comme `filter_var()` + + +Travailler en toute sécurité avec les colonnes +============================================== + +Dans la section précédente, nous avons montré comment valider correctement les valeurs des paramètres. Cependant, lors de l'utilisation de tableaux dans les requêtes SQL, nous devons accorder la même attention à leurs clés. + +```php +// ❌ CODE DANGEREUX - les clés du tableau ne sont pas traitées +$database->query('INSERT INTO users', $_POST); +``` + +Pour les commandes INSERT et UPDATE, il s'agit d'une faille de sécurité critique - un attaquant peut insérer ou modifier n'importe quelle colonne dans la base de données. Il pourrait, par exemple, définir `is_admin = 1` ou insérer des données arbitraires dans des colonnes sensibles (vulnérabilité dite Mass Assignment). + +Dans les conditions WHERE, c'est encore plus dangereux, car elles peuvent contenir des opérateurs : + +```php +// ❌ CODE DANGEREUX - les clés du tableau ne sont pas traitées +$_POST['salary >'] = 100000; +$database->query('SELECT * FROM users WHERE', $_POST); +// exécute la requête WHERE (`salary` > 100000) +``` + +Un attaquant peut utiliser cette approche pour découvrir systématiquement les salaires des employés. Il commence, par exemple, par une requête sur les salaires supérieurs à 100 000, puis inférieurs à 50 000, et en réduisant progressivement la plage, il peut révéler les salaires approximatifs de tous les employés. Ce type d'attaque est appelé énumération SQL. + +Les méthodes `where()` et `whereOr()` sont encore [beaucoup plus flexibles |explorer#where] et supportent dans les clés et les valeurs des expressions SQL incluant des opérateurs et des fonctions. Cela donne à l'attaquant la possibilité d'effectuer une injection SQL : + +```php +// ❌ CODE DANGEREUX - l'attaquant peut injecter son propre SQL +$_POST = ['0) UNION SELECT name, salary FROM users WHERE (1']; +$table->where($_POST); +// exécute la requête WHERE (0) UNION SELECT name, salary FROM users WHERE (1) +``` + +Cette attaque termine la condition d'origine avec `0)`, ajoute son propre `SELECT` en utilisant `UNION` pour obtenir des données sensibles de la table `users` et ferme la requête syntaxiquement correcte avec `WHERE (1)`. + + +Liste blanche de colonnes +------------------------- + +Pour travailler en toute sécurité avec les noms de colonnes, nous avons besoin d'un mécanisme qui garantit que l'utilisateur ne peut travailler qu'avec les colonnes autorisées et ne peut pas ajouter les siennes. Nous pourrions essayer de détecter et de bloquer les noms de colonnes dangereux (liste noire), mais cette approche n'est pas fiable - un attaquant peut toujours trouver une nouvelle façon d'écrire un nom de colonne dangereux que nous n'avions pas prévu. + +Par conséquent, il est beaucoup plus sûr d'inverser la logique et de définir une liste explicite des colonnes autorisées (liste blanche) : + +```php +// Colonnes que l'utilisateur peut modifier +$allowedColumns = ['name', 'email', 'active']; + +// Supprimer toutes les colonnes non autorisées de l'entrée +$filteredData = array_intersect_key($userData, array_flip($allowedColumns)); + +// ✅ Maintenant, nous pouvons l'utiliser en toute sécurité dans les requêtes, telles que : +$database->query('INSERT INTO users', $filteredData); +$table->update($filteredData); +$table->where($filteredData); +``` + + +Identifiants dynamiques +======================= + +Pour les noms de tables et de colonnes dynamiques, utilisez le placeholder `?name`. Cela garantit un échappement correct des identifiants selon la syntaxe de la base de données donnée (par exemple, en utilisant des backticks en MySQL) : + +```php +// ✅ Utilisation sécurisée d'identifiants fiables +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name', $column, $table); +// Résultat dans MySQL : SELECT `name` FROM `users` +``` + +Important : utilisez le symbole `?name` uniquement pour les valeurs fiables définies dans le code de l'application. Pour les valeurs provenant de l'utilisateur, utilisez à nouveau la [liste blanche |#Liste blanche de colonnes]. Sinon, vous vous exposez à des risques de sécurité : + +```php +// ❌ DANGEREUX - n'utilisez jamais l'entrée utilisateur +$database->query('SELECT ?name FROM users', $_GET['column']); +``` diff --git a/database/fr/sql-way.texy b/database/fr/sql-way.texy new file mode 100644 index 0000000000..5d815deebe --- /dev/null +++ b/database/fr/sql-way.texy @@ -0,0 +1,513 @@ +Accès SQL +********* + +.[perex] +Nette Database offre deux approches : vous pouvez écrire vous-même des requêtes SQL (accès SQL), ou les laisser générer automatiquement (voir [Explorer |explorer]). L'accès SQL vous donne un contrôle total sur les requêtes tout en assurant leur construction sécurisée. + +.[note] +Les détails sur la connexion et la configuration de la base de données se trouvent dans le chapitre [Connexion et configuration |guide#Connexion et configuration]. + + +Requêtes de base +================ + +Pour interroger la base de données, utilisez la méthode `query()`. Elle renvoie un objet [ResultSet |api:Nette\Database\ResultSet] qui représente le résultat de la requête. En cas d'échec, la méthode [lève une exception |exceptions]. Nous pouvons parcourir le résultat de la requête à l'aide d'une boucle `foreach`, ou utiliser l'une des [fonctions d'aide |#Obtention des données]. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; +} +``` + +Pour insérer des valeurs en toute sécurité dans les requêtes SQL, nous utilisons des requêtes paramétrées. Nette Database les rend extrêmement simples - il suffit d'ajouter une virgule et la valeur après la requête SQL : + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Avec plusieurs paramètres, vous avez deux options d'écriture. Vous pouvez soit "intercaler" les paramètres dans la requête SQL : + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age); +``` + +Soit écrire d'abord la requête SQL complète, puis joindre tous les paramètres : + +```php +$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age); +``` + + +Protection contre l'injection SQL +================================= + +Pourquoi est-il important d'utiliser des requêtes paramétrées ? Parce qu'elles vous protègent contre une attaque appelée injection SQL, où un attaquant pourrait injecter ses propres commandes SQL et ainsi obtenir ou endommager des données dans la base de données. + +.[warning] +**N'insérez jamais de variables directement dans la requête SQL !** Utilisez toujours des requêtes paramétrées, qui vous protègent contre l'injection SQL. + +```php +// ❌ CODE DANGEREUX - vulnérable à l'injection SQL +$database->query("SELECT * FROM users WHERE name = '$name'"); + +// ✅ Requête paramétrée sécurisée +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Familiarisez-vous avec les [risques de sécurité possibles |security]. + + +Techniques de requête +===================== + + +Conditions WHERE +---------------- + +Les conditions WHERE peuvent être écrites sous forme de tableau associatif, où les clés sont les noms des colonnes et les valeurs sont les données à comparer. Nette Database choisit automatiquement l'opérateur SQL le plus approprié en fonction du type de valeur. + +```php +$database->query('SELECT * FROM users WHERE', [ + 'name' => 'John', + 'active' => true, +]); +// WHERE `name` = 'John' AND `active` = 1 +``` + +Vous pouvez également spécifier explicitement l'opérateur de comparaison dans la clé : + +```php +$database->query('SELECT * FROM users WHERE', [ + 'age >' => 25, // utilise l'opérateur > + 'name LIKE' => '%John%', // utilise l'opérateur LIKE + 'email NOT LIKE' => '%example.com%', // utilise l'opérateur NOT LIKE +]); +// WHERE `age` > 25 AND `name` LIKE '%John%' AND `email` NOT LIKE '%example.com%' +``` + +Nette gère automatiquement les cas spéciaux comme les valeurs `null` ou les tableaux. + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name' => 'Laptop', // utilise l'opérateur = + 'category_id' => [1, 2, 3], // utilise IN + 'description' => null, // utilise IS NULL +]); +// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL +``` + +Pour les conditions négatives, utilisez l'opérateur `NOT` : + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name NOT' => 'Laptop', // utilise l'opérateur <> + 'category_id NOT' => [1, 2, 3], // utilise NOT IN + 'description NOT' => null, // utilise IS NOT NULL + 'id' => [], // sera omis +]); +// WHERE `name` <> 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL +``` + +Pour joindre des conditions, l'opérateur `AND` est utilisé. Cela peut être modifié à l'aide du [placeholder ?or |#Conseils pour la construction SQL]. + + +Règles ORDER BY +--------------- + +Le tri `ORDER BY` peut être écrit à l'aide d'un tableau. Dans les clés, nous indiquons les colonnes et la valeur sera un booléen indiquant s'il faut trier par ordre croissant : + +```php +$database->query('SELECT id FROM author ORDER BY', [ + 'id' => true, // croissant + 'name' => false, // décroissant +]); +// SELECT id FROM author ORDER BY `id`, `name` DESC +``` + + +Insertion de données (INSERT) +----------------------------- + +Pour insérer des enregistrements, la commande SQL `INSERT` est utilisée. + +```php +$values = [ + 'name' => 'John Doe', + 'email' => 'john@example.com', +]; +$database->query('INSERT INTO users ?', $values); +$userId = $database->getInsertId(); +``` + +La méthode `getInsertId()` renvoie l'ID de la dernière ligne insérée. Pour certaines bases de données (par exemple PostgreSQL), il est nécessaire de spécifier en paramètre le nom de la séquence à partir de laquelle l'ID doit être généré à l'aide de `$database->getInsertId($sequenceId)`. + +Nous pouvons également passer des [#valeurs spéciales] comme paramètres, telles que des fichiers, des objets DateTime ou des types enum. + +Insertion de plusieurs enregistrements à la fois : + +```php +$database->query('INSERT INTO users ?', [ + ['name' => 'User 1', 'email' => 'user1@mail.com'], + ['name' => 'User 2', 'email' => 'user2@mail.com'], +]); +``` + +L'INSERT multiple est beaucoup plus rapide car une seule requête de base de données est exécutée, au lieu de plusieurs requêtes individuelles. + +**Avertissement de sécurité :** N'utilisez jamais de données non validées comme `$values`. Familiarisez-vous avec les [risques possibles |security#Travailler en toute sécurité avec les colonnes]. + + +Mise à jour des données (UPDATE) +-------------------------------- + +Pour mettre à jour des enregistrements, la commande SQL `UPDATE` est utilisée. + +```php +// Mise à jour d'un seul enregistrement +$values = [ + 'name' => 'John Smith', +]; +$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1); +``` + +Le nombre de lignes affectées est renvoyé par `$result->getRowCount()`. + +Pour UPDATE, nous pouvons utiliser les opérateurs `+=` et `-=` : + +```php +$database->query('UPDATE users SET ? WHERE id = ?', [ + 'login_count+=' => 1, // incrémentation de login_count +], 1); +``` + +Exemple d'insertion ou de modification d'un enregistrement s'il existe déjà. Nous utilisons la technique `ON DUPLICATE KEY UPDATE` : + +```php +$values = [ + 'name' => $name, + 'year' => $year, +]; +$database->query('INSERT INTO users ? ON DUPLICATE KEY UPDATE ?', + $values + ['id' => $id], + $values, +); +// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) +// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 +``` + +Notez que Nette Database reconnaît le contexte dans lequel le paramètre avec le tableau est inséré dans la commande SQL et construit le code SQL en conséquence. Ainsi, à partir du premier tableau, il a construit `(id, name, year) VALUES (123, 'Jim', 1978)`, tandis que le second a été converti sous la forme `name = 'Jim', year = 1978`. Nous en discutons plus en détail dans la section [#Conseils pour la construction SQL]. + + +Suppression de données (DELETE) +------------------------------- + +Pour supprimer des enregistrements, la commande SQL `DELETE` est utilisée. Exemple d'obtention du nombre de lignes supprimées : + +```php +$count = $database->query('DELETE FROM users WHERE id = ?', 1) + ->getRowCount(); +``` + + +Conseils pour la construction SQL +--------------------------------- + +Un hint est un placeholder spécial dans la requête SQL qui indique comment la valeur du paramètre doit être réécrite en expression SQL : + +| Hint | Description | S'applique automatiquement +|-----------|-------------------------------------------------|----------------------------- +| `?name` | utilisé pour insérer le nom de la table ou de la colonne | - +| `?values` | génère `(key, ...) VALUES (value, ...)` | `INSERT ... ?`, `REPLACE ... ?` +| `?set` | génère l'affectation `key = value, ...` | `SET ?`, `KEY UPDATE ?` +| `?and` | joint les conditions du tableau avec l'opérateur `AND` | `WHERE ?`, `HAVING ?` +| `?or` | joint les conditions du tableau avec l'opérateur `OR` | - +| `?order` | génère la clause `ORDER BY` | `ORDER BY ?`, `GROUP BY ?` + +Pour insérer dynamiquement des noms de tables et de colonnes dans la requête, le placeholder `?name` est utilisé. Nette Database s'occupe du traitement correct des identifiants selon les conventions de la base de données donnée (par exemple, en les entourant de backticks en MySQL). + +```php +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table); +// SELECT `name` FROM `users` WHERE id = 1 (en MySQL) +``` + +**Avertissement :** utilisez le symbole `?name` uniquement pour les noms de tables et de colonnes provenant d'entrées validées, sinon vous vous exposez à un [risque de sécurité |security#Identifiants dynamiques]. + +Les autres hints n'ont généralement pas besoin d'être spécifiés, car Nette utilise une autodétection intelligente lors de la composition de la requête SQL (voir la troisième colonne du tableau). Mais vous pouvez l'utiliser, par exemple, dans une situation où vous souhaitez joindre des conditions à l'aide de `OR` au lieu de `AND` : + +```php +$database->query('SELECT * FROM users WHERE ?or', [ + 'name' => 'John', + 'email' => 'john@example.com', +]); +// SELECT * FROM users WHERE `name` = 'John' OR `email` = 'john@example.com' +``` + + +Valeurs spéciales +----------------- + +En plus des types scalaires courants (string, int, bool), vous pouvez également passer des valeurs spéciales comme paramètres : + +- fichiers : `fopen('image.gif', 'r')` insère le contenu binaire du fichier +- date et heure : les objets `DateTime` sont convertis au format de la base de données +- types enum : les instances `enum` sont converties en leur valeur +- littéraux SQL : créés à l'aide de `Connection::literal('NOW()')` sont insérés directement dans la requête + +```php +$database->query('INSERT INTO articles ?', [ + 'title' => 'My Article', + 'published_at' => new DateTime, + 'content' => fopen('image.png', 'r'), + 'state' => Status::Draft, +]); +``` + +Pour les bases de données qui n'ont pas de support natif pour le type de données `datetime` (comme SQLite et Oracle), `DateTime` est converti en une valeur spécifiée dans la [configuration de la base de données |configuration] par l'élément `formatDateTime` (la valeur par défaut est `U` - timestamp unix). + + +Littéraux SQL +------------- + +Dans certains cas, vous devez spécifier directement du code SQL comme valeur, mais celui-ci ne doit pas être interprété comme une chaîne et échappé. C'est à cela que servent les objets de la classe `Nette\Database\SqlLiteral`. Ils sont créés par la méthode `Connection::literal()`. + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + 'year >' => $database::literal('YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) +``` + +Ou alternativement : + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) +``` + +Les littéraux SQL peuvent contenir des paramètres : + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > ? AND year < ?', $min, $max), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) +``` + +Grâce à cela, nous pouvons créer des combinaisons intéressantes : + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('?or', [ + 'active' => true, + 'role' => $role, + ]), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') +``` + + +Obtention des données +===================== + + +Raccourcis pour les requêtes SELECT +----------------------------------- + +Pour simplifier la récupération des données, `Connection` propose plusieurs raccourcis qui combinent l'appel de `query()` avec le `fetch*()` suivant. Ces méthodes acceptent les mêmes paramètres que `query()`, c'est-à-dire la requête SQL et les paramètres optionnels. Une description complète des méthodes `fetch*()` se trouve [ci-dessous |#fetch]. + +| `fetch($sql, ...$params): ?Row` | Exécute la requête et renvoie la première ligne comme objet `Row` +| `fetchAll($sql, ...$params): array` | Exécute la requête et renvoie toutes les lignes comme tableau d'objets `Row` +| `fetchPairs($sql, ...$params): array` | Exécute la requête et renvoie un tableau associatif, où la première colonne représente la clé et la seconde la valeur +| `fetchField($sql, ...$params): mixed` | Exécute la requête et renvoie la valeur du premier champ de la première ligne +| `fetchList($sql, ...$params): ?array` | Exécute la requête et renvoie la première ligne comme tableau indexé + +Exemple : + +```php +// fetchField() - renvoie la valeur de la première cellule +$count = $database->query('SELECT COUNT(*) FROM articles') + ->fetchField(); +``` + + +`foreach` - itération sur les lignes +------------------------------------ + +Après l'exécution de la requête, un objet [ResultSet|api:Nette\Database\ResultSet] est renvoyé, permettant de parcourir les résultats de plusieurs manières. La manière la plus simple d'exécuter une requête et d'obtenir les lignes est d'itérer dans une boucle `foreach`. Cette méthode est la plus économe en mémoire car elle renvoie les données progressivement et ne les stocke pas toutes en mémoire en même temps. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; + // ... +} +``` + +.[note] +`ResultSet` ne peut être itéré qu'une seule fois. Si vous avez besoin d'itérer plusieurs fois, vous devez d'abord charger les données dans un tableau, par exemple à l'aide de la méthode `fetchAll()`. + + +fetch(): ?Row .[method] +----------------------- + +Renvoie une ligne comme objet `Row`. S'il n'y a plus de lignes, renvoie `null`. Déplace le pointeur interne à la ligne suivante. + +```php +$result = $database->query('SELECT * FROM users'); +$row = $result->fetch(); // lit la première ligne +if ($row) { + echo $row->name; +} +``` + + +fetchAll(): array .[method] +--------------------------- + +Renvoie toutes les lignes restantes du `ResultSet` sous forme de tableau d'objets `Row`. + +```php +$result = $database->query('SELECT * FROM users'); +$rows = $result->fetchAll(); // lit toutes les lignes +foreach ($rows as $row) { + echo $row->name; +} +``` + + +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Renvoie les résultats sous forme de tableau associatif. Le premier argument spécifie le nom de la colonne à utiliser comme clé dans le tableau, le second argument spécifie le nom de la colonne à utiliser comme valeur : + +```php +$result = $database->query('SELECT id, name FROM users'); +$names = $result->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] +``` + +Si nous ne spécifions que le premier paramètre, la valeur sera la ligne entière, c'est-à-dire l'objet `Row` : + +```php +$rows = $result->fetchPairs('id'); +// [1 => Row(id: 1, name: 'John'), 2 => Row(id: 2, name: 'Jane'), ...] +``` + +En cas de clés dupliquées, la valeur de la dernière ligne est utilisée. En utilisant `null` comme clé, le tableau sera indexé numériquement à partir de zéro (il n'y a alors pas de collisions) : + +```php +$names = $result->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] +``` + + +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Alternativement, vous pouvez spécifier un callback comme paramètre, qui renverra pour chaque ligne soit la valeur elle-même, soit une paire clé-valeur. + +```php +$result = $database->query('SELECT * FROM users'); +$items = $result->fetchPairs(fn($row) => "$row->id - $row->name"); +// ['1 - John', '2 - Jane', ...] + +// Le callback peut également renvoyer un tableau avec une paire clé & valeur : +$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]); +// ['John' => 46, 'Jane' => 21, ...] +``` + + +fetchField(): mixed .[method] +----------------------------- + +Renvoie la valeur du premier champ de la ligne actuelle. S'il n'y a plus de lignes, renvoie `null`. Déplace le pointeur interne à la ligne suivante. + +```php +$result = $database->query('SELECT name FROM users'); +$name = $result->fetchField(); // lit le nom de la première ligne +``` + + +fetchList(): ?array .[method] +----------------------------- + +Renvoie une ligne comme tableau indexé. S'il n'y a plus de lignes, renvoie `null`. Déplace le pointeur interne à la ligne suivante. + +```php +$result = $database->query('SELECT name, email FROM users'); +$row = $result->fetchList(); // ['John', 'john@example.com'] +``` + + +getRowCount(): ?int .[method] +----------------------------- + +Renvoie le nombre de lignes affectées par la dernière requête `UPDATE` ou `DELETE`. Pour `SELECT`, c'est le nombre de lignes renvoyées, mais celui-ci peut ne pas être connu - dans ce cas, la méthode renvoie `null`. + + +getColumnCount(): ?int .[method] +-------------------------------- + +Renvoie le nombre de colonnes dans le `ResultSet`. + + +Informations sur les requêtes +============================= + +À des fins de débogage, nous pouvons obtenir des informations sur la dernière requête exécutée : + +```php +echo $database->getLastQueryString(); // affiche la requête SQL + +$result = $database->query('SELECT * FROM articles'); +echo $result->getQueryString(); // affiche la requête SQL +echo $result->getTime(); // affiche le temps d'exécution en secondes +``` + +Pour afficher le résultat sous forme de tableau HTML, on peut utiliser : + +```php +$result = $database->query('SELECT * FROM articles'); +$result->dump(); +``` + +ResultSet fournit des informations sur les types de colonnes : + +```php +$result = $database->query('SELECT * FROM articles'); +$types = $result->getColumnTypes(); + +foreach ($types as $column => $type) { + echo "$column est de type $type->type"; // par ex. 'id est de type int' +} +``` + + +Journalisation des requêtes +--------------------------- + +Nous pouvons implémenter notre propre journalisation des requêtes. L'événement `onQuery` est un tableau de callbacks qui sont appelés après chaque requête exécutée : + +```php +$database->onQuery[] = function ($database, $result) use ($logger) { + $logger->info('Requête : ' . $result->getQueryString()); + $logger->info('Temps : ' . $result->getTime()); + + if ($result->getRowCount() > 1000) { + $logger->warning('Jeu de résultats volumineux : ' . $result->getRowCount() . ' lignes'); + } +}; +``` diff --git a/database/fr/transactions.texy b/database/fr/transactions.texy new file mode 100644 index 0000000000..7dce777453 --- /dev/null +++ b/database/fr/transactions.texy @@ -0,0 +1,43 @@ +Transactions +************ + +.[perex] +Les transactions garantissent que soit toutes les opérations au sein d'une transaction sont exécutées, soit aucune ne l'est. Elles sont utiles pour assurer la cohérence des données lors d'opérations plus complexes. + +La manière la plus simple d'utiliser les transactions ressemble à ceci : + +```php +$database->beginTransaction(); +try { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); + $database->commit(); +} catch (\Exception $e) { + $database->rollBack(); + throw $e; +} +``` + +Vous pouvez écrire la même chose de manière beaucoup plus élégante en utilisant la méthode `transaction()`. Elle accepte un callback en paramètre, qu'elle exécute dans une transaction. Si le callback se déroule sans exception, la transaction est automatiquement validée (commit). Si une exception se produit, la transaction est annulée (rollback) et l'exception est propagée. + +```php +$database->transaction(function ($database) use ($id) { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); +}); +``` + +La méthode `transaction()` peut également retourner des valeurs : + +```php +$count = $database->transaction(function ($database) { + $result = $database->query('UPDATE users SET active = ?', true); + return $result->getRowCount(); // retourne le nombre de lignes mises à jour +}); +``` diff --git a/database/hu/@home.texy b/database/hu/@home.texy index e47253581d..fc880e7b35 100644 --- a/database/hu/@home.texy +++ b/database/hu/@home.texy @@ -1,20 +1,21 @@ -Támogatott szerverek -==================== +Támogatott adatbázisok +====================== -Ezek az adatbázis-kiszolgálók támogatottak: +A Nette a következő adatbázisokat támogatja: -|* Adatbáziskiszolgáló |* DSN név |* Core támogatás |* Explorer támogatás -| MySQL (>= 5.1) | mysql | YES | YES | YES -| PostgreSQL (>= 9.0) | pgsql | IGEN | IGEN -| Sqlite 3 (>= 3.8) | sqlite | YES | YES | YES -| Oracle | oci | IGEN | - -| MS SQL (PDO_SQLSRV) | sqlsrv | YES | YES | YES -| MS SQL (PDO_DBLIB) | mssql | IGEN | - -| ODBC | odbc | IGEN | - +|* Adatbázis szerver |* DSN név |* Támogatás a Core-ban |* Támogatás az Explorerben +| MySQL (>= 5.1) | mysql | IGEN | IGEN +| PostgreSQL (>= 9.0) | pgsql | IGEN | IGEN +| Sqlite 3 (>= 3.8) | sqlite | IGEN | IGEN +| Oracle | oci | IGEN | - +| MS SQL (PDO_SQLSRV) | sqlsrv | IGEN | IGEN +| MS SQL (PDO_DBLIB) | mssql | IGEN | - +| ODBC | odbc | IGEN | - -{{title: Nette Database}} -{{description: A Nette Database leegyszerűsíti az adatok lekérdezését az adatbázisból anélkül, hogy SQL-lekérdezéseket kellene írni. Hatékony lekérdezéseket tesz fel, és nem továbbít felesleges adatokat.}} + +{{maintitle: Nette Database - awesome database layer for PHP}} +{{description: A Nette Database jelentősen leegyszerűsíti az adatok lekérdezését az adatbázisból SQL lekérdezések írása nélkül. Hatékony lekérdezéseket tesz és nem továbbít felesleges adatokat.}} diff --git a/database/hu/@left-menu.texy b/database/hu/@left-menu.texy index e5d3b6f24a..6bf3ca54b9 100644 --- a/database/hu/@left-menu.texy +++ b/database/hu/@left-menu.texy @@ -1,5 +1,12 @@ -Adatbázis -********* -- [Mag |Core] -- [Felfedező |Explorer] -- [Konfiguráció |Configuration] +Nette Database +************** +- [Bevezetés |guide] +- [SQL hozzáférés |sql way] +- [Explorer |Explorer] +- [Tranzakciók |transactions] +- [Kivételek |exceptions] +- [Reflexió |reflection] +- [Leképezés |mapping] +- [Konfiguráció |configuration] +- [Biztonsági kockázatok |security] +- [Frissítés |en:upgrading] diff --git a/database/hu/@meta.texy b/database/hu/@meta.texy new file mode 100644 index 0000000000..c172d1cda5 --- /dev/null +++ b/database/hu/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette dokumentáció}} diff --git a/database/hu/configuration.texy b/database/hu/configuration.texy index f1d69b97c5..7e539ca546 100644 --- a/database/hu/configuration.texy +++ b/database/hu/configuration.texy @@ -1,61 +1,67 @@ -Adatbázis konfigurálása -*********************** +Adatbázis konfiguráció +********************** .[perex] -A Nette adatbázis konfigurációs lehetőségeinek áttekintése. +A Nette Database konfigurációs lehetőségeinek áttekintése. -Ha nem a teljes keretrendszert, hanem csak ezt a könyvtárat használja, olvassa el [, hogyan töltse be a konfigurációt |bootstrap:]. +Ha nem a teljes keretrendszert használja, csak ezt a könyvtárat, olvassa el, [hogyan töltse be a konfigurációt |bootstrap:]. -Egyetlen kapcsolat .[#toc-single-connection] --------------------------------------------- +Egy kapcsolat +------------- -Egyetlen adatbázis-kapcsolat konfigurálása: +Egy adatbázis-kapcsolat konfigurációja: ```neon database: - # DSN, csak kötelező kulcs + # DSN, az egyetlen kötelező kulcs dsn: "sqlite:%appDir%/Model/demo.db" user: ... password: ... ``` -`Nette\Database\Connection` típusú szolgáltatásokat hoz létre, valamint `Nette\Database\Explorer` szolgáltatásokat az [Adatbázis-kutató |explorer] réteghez. Az adatbázis-kapcsolat átadása általában [autowiringgel |dependency-injection:autowiring] történik, ha ez nem lehetséges, használja a `@database.default.connection` illetve `@database.default.explorer` szolgáltatásneveket. +Létrehozza a `Nette\Database\Connection` és `Nette\Database\Explorer` szolgáltatásokat, amelyeket általában [autowiringgel |dependency-injection:autowiring] adunk át, vagy hivatkozással a [nevükre |#DI Szolgáltatások]. -Egyéb beállítások: +További beállítások: ```neon database: - # adatbázis panel jelenik meg a Tracy Barban? - debugger: ... # (bool) alapértelmezett értéke true + # megjelenítse az adatbázis panelt a Tracy Bar-ban? + debugger: ... # (bool) alapértelmezett true - # EXPLAIN lekérdezés megjelenítése a Tracy Bar? - explain: ... # (bool) alapértelmezés szerint true + # megjelenítse az EXPLAIN lekérdezéseket a Tracy Bar-ban? + explain: ... # (bool) alapértelmezett true - # engedélyezni az automatikus bekötést a kapcsolathoz? - autowired: ... # (bool) alapértelmezés szerint true az első kapcsolatnál + # engedélyezze az autowiringet ehhez a kapcsolathoz? + autowired: ... # (bool) alapértelmezett true az első kapcsolatnál - # táblázat konvenciók: felfedezett, statikus vagy osztálynév - conventions: discovered # (string) alapértelmezett értéke 'discovered'. + # tábla konvenciók: discovered, static vagy osztálynév + conventions: discovered # (string) alapértelmezett 'discovered' options: - # hogy csak akkor csatlakozzon az adatbázishoz, amikor szükséges? - lazy: ... # (bool) alapértelmezett értéke false + # csak akkor csatlakozzon az adatbázishoz, amikor szükséges? + lazy: ... # (bool) alapértelmezett false - # PHP adatbázis-illesztő osztály + # PHP adatbázis-illesztőprogram osztálya driverClass: # (string) - # csak MySQL: sql_mode beállítása + # csak MySQL: beállítja az sql_mode-ot sqlmode: # (string) - # csak MySQL: sets SET NAMES - charset: # (string) alapértelmezés szerint 'utf8mb4' ('utf8' a v5.5.3 előtt) + # csak MySQL: beállítja a SET NAMES-t + charset: # (string) alapértelmezett 'utf8mb4' - # csak Oracle és SQLite: dátumformátum - formatDateTime: # (string) alapértelmezett értéke 'U' + # csak MySQL: a TINYINT(1)-et bool-ra konvertálja + convertBoolean: # (bool) alapértelmezett false + + # a dátum oszlopokat immutable objektumokként adja vissza (3.2.1 verziótól) + newDateTime: # (bool) alapértelmezett false + + # csak Oracle és SQLite: dátum tárolási formátuma + formatDateTime: # (string) alapértelmezett 'U' ``` -A `options` kulcs tartalmazhat más beállításokat is, amelyek a [PDO-illesztőprogram dokumentációjában |https://www.php.net/manual/en/pdo.drivers.php] találhatók, például: +Az `options` kulcsban további opciókat adhat meg, amelyeket a [PDO illesztőprogramok dokumentációjában |https://www.php.net/manual/en/pdo.drivers.php] talál, például: ```neon database: @@ -64,10 +70,10 @@ database: ``` -Többszörös kapcsolatok .[#toc-multiple-connections] ---------------------------------------------------- +Több kapcsolat +-------------- -A konfigurációban több adatbázis-kapcsolatot is definiálhatunk, ha azokat nevesített szakaszokra osztjuk: +A konfigurációban több adatbázis-kapcsolatot is definiálhatunk, elnevezett szekciókra osztva: ```neon database: @@ -80,9 +86,23 @@ database: dsn: 'sqlite::memory:' ``` -Minden egyes definiált kapcsolat olyan szolgáltatásokat hoz létre, amelyek nevében szerepel a szakasz neve, azaz `@database.main.connection` & `@database.main.explorer` és tovább `@database.another.connection` & `@database.another.explorer`. +Az autowiring csak az első szekcióból származó szolgáltatásoknál van bekapcsolva. Ezt meg lehet változtatni az `autowired: false` vagy `autowired: true` segítségével. + + +DI Szolgáltatások +----------------- + +Ezek a szolgáltatások kerülnek hozzáadásra a DI konténerhez, ahol a `###` a kapcsolat nevét jelöli: + +| Név | Típus | Leírás +|---------------------------------------------------------- +| `database.###.connection` | [api:Nette\Database\Connection] | adatbázis-kapcsolat +| `database.###.explorer` | [api:Nette\Database\Explorer] | [Database Explorer |explorer] + + +Ha csak egy kapcsolatot definiálunk, a szolgáltatások nevei `database.default.connection` és `database.default.explorer` lesznek. Ha több kapcsolatot definiálunk, mint a fenti példában, a nevek megfelelnek a szekcióknak, azaz `database.main.connection`, `database.main.explorer`, valamint `database.another.connection` és `database.another.explorer`. -Az automatikus összekapcsolás csak az első szakasz szolgáltatásai esetében engedélyezett. Ez a `autowired: false` vagy a `autowired: true` segítségével módosítható. A nem automatikusan bekötött szolgáltatások név szerint kerülnek átadásra: +A nem autowire-olt szolgáltatásokat explicit módon, a nevükre való hivatkozással adjuk át: ```neon services: diff --git a/database/hu/core.texy b/database/hu/core.texy deleted file mode 100644 index 6c303bbcff..0000000000 --- a/database/hu/core.texy +++ /dev/null @@ -1,350 +0,0 @@ -Adatbázis mag -************* - -.[perex] -A Nette Database Core az adatbázis absztrakciós rétege, és alapvető funkciókat biztosít. - - -Telepítés .[#toc-installation] -============================== - -Töltse le és telepítse a csomagot a [Composer |best-practices:composer] segítségével: - -```shell -composer require nette/database -``` - - -Csatlakozás és konfiguráció .[#toc-connection-and-configuration] -================================================================ - -Az adatbázishoz való csatlakozáshoz egyszerűen hozzon létre egy példányt a [api:Nette\Database\Connection] osztályból: - -```php -$database = new Nette\Database\Connection($dsn, $user, $password); -``` - -A `$dsn` (adatforrás neve) paraméter [ugyanaz, mint amit a PDO használ |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], pl. `host=127.0.0.1;dbname=test`. Sikertelenség esetén a `Nette\Database\ConnectionException` dobja. - -Azonban egy kifinomultabb módot kínál az [alkalmazás konfigurálása |configuration]. Hozzáadunk egy `database` szakaszt, és ez létrehozza a szükséges objektumokat és egy adatbázis panelt a [Tracy |tracy:] sávban. - -```neon -database: - dsn: 'mysql:host=127.0.0.1;dbname=test' - user: root - password: password -``` - -A kapcsolati objektumot például [egy DI konténertől kapjuk szolgáltatásként |dependency-injection:passing-dependencies]: - -```php -class Model -{ - // Nette\Database\Explorer átadása az adatbázis-kutató réteggel való együttműködéshez. - public function __construct( - private Nette\Database\Connection $database, - ) { - } -} -``` - -További információért lásd: [adatbázis-konfiguráció |configuration]. - - -Lekérdezések .[#toc-queries] -============================ - -Az adatbázis lekérdezéséhez használja a `query()` módszert, amely [ResultSet-et |api:Nette\Database\ResultSet] ad vissza. - -```php -$result = $database->query('SELECT * FROM users'); - -foreach ($result as $row) { - echo $row->id; - echo $row->name; -} - -echo $result->getRowCount(); // visszaadja a sorok számát, ha ismert. -``` - -.[note] -A `ResultSet` felett csak egyszer lehet iterálni, ha többször kell iterálni, akkor az eredményt a `fetchAll()` metódussal kell tömbre konvertálni. - -A lekérdezéshez könnyen adhatunk paramétereket, figyeljük meg a kérdőjelet: - -```php -$database->query('SELECT * FROM users WHERE name = ?', $name); - -$database->query('SELECT * FROM users WHERE name = ? AND active = ?', $name, $active); - -$database->query('SELECT * FROM users WHERE id IN (?)', $ids); // $ids egy tömb -``` -<div class=warning> - -FIGYELMEZTETÉS, soha ne fűzzön össze karakterláncokat az [SQL injekciós sebezhetőség |https://en.wikipedia.org/wiki/SQL_injection] elkerülése érdekében! -/-- -$db->query('SELECT * FROM users WHERE name = ' . $name); // WRONG!!! -\-- -</div> - -Sikertelenség esetén a `query()` a `Nette\Database\DriverException` vagy valamelyik leszármazottját dobja: - -- [ConstraintViolationException |api:Nette\Database\ConstraintViolationException] - bármelyik megkötés megsértése. -- [ForeignKeyConstraintViolationException |api:Nette\Database\ForeignKeyConstraintViolationException] - érvénytelen idegen kulcs. -- [NotNullConstraintViolationException |api:Nette\Database\NotNullConstraintViolationException] - a NOT NULL feltétel megsértése. -- [UniqueConstraintViolationException |api:Nette\Database\UniqueConstraintViolationException] - az egyedi index konfliktusa. - -A `query()` mellett vannak más hasznos módszerek is: - -```php -// visszaadja az id => name asszociatív tömböt -$pairs = $database->fetchPairs('SELECT id, name FROM users'); - -// visszaadja az összes sort tömbként -$rows = $database->fetchAll('SELECT * FROM users'); - -// egyetlen sort ad vissza -$row = $database->fetch('SELECT * FROM users WHERE id = ?', $id); - -// egyetlen mezőt ad vissza -$name = $database->fetchField('SELECT name FROM users WHERE id = ?', $id); -``` - -Sikertelenség esetén ezek a módszerek mindegyike dobja a `Nette\Database\DriverException.` - - -Beszúrás, frissítés és törlés .[#toc-insert-update-delete] -========================================================== - -Az SQL-lekérdezésbe beszúrandó paraméter lehet a tömb is (ebben az esetben kihagyható a vadkártyás `?`), which may be useful for the `INSERT` utasítás: - -```php -$database->query('INSERT INTO users ?', [ // itt kihagyható a kérdőjel - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) - -$id = $database->getInsertId(); // visszaadja a beillesztett sor automatikus inkrementálását. - -$id = $database->getInsertId($sequence); // vagy a sor értéke -``` - -Többszörös beszúrás: - -```php -$database->query('INSERT INTO users', [ - [ - 'name' => 'Jim', - 'year' => 1978, - ], [ - 'name' => 'Jack', - 'year' => 1987, - ], -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) -``` - -DateTime objektumokat vagy [felsorolásokat |https://www.php.net/enumerations] is átadhatunk: - -```php -$database->query('INSERT INTO users', [ - 'name' => $name, - 'created' => new DateTime, // vagy $database::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // a fájl tartalmának beszúrása. - 'status' => State::New, // enum állapot -]); -``` - -Sorok frissítése: - -```php -$result = $database->query('UPDATE users SET', [ - 'name' => $name, - 'year' => $year, -], 'WHERE id = ?', $id); -// UPDATE users SET `name` = 'Jim', `year` = 1978 WHERE id = 123 - -echo $result->getRowCount(); // visszaadja az érintett sorok számát -``` - -UPDATE esetén a `+=` és a `-=` operátorokat használhatjuk: - -```php -$database->query('UPDATE users SET', [ - 'age+=' => 1, // note += -], 'WHERE id = ?', $id); -// UPDATE users SET `age` = `age` + 1 -``` - -Törlés: - -```php -$result = $database->query('DELETE FROM users WHERE id = ?', $id); -echo $result->getRowCount(); // visszaadja az érintett sorok számát. -``` - - -Speciális lekérdezések .[#toc-advanced-queries] -=============================================== - -Beszúrás vagy frissítés, ha már létezik: - -```php -$database->query('INSERT INTO users', [ - 'id' => $id, - 'name' => $name, - 'year' => $year, -], 'ON DUPLICATE KEY UPDATE', [ - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) -// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 -``` - -Vegye figyelembe, hogy a Nette Database felismeri az SQL-kontextust, amelybe a tömbparamétert beillesztik, és ennek megfelelően építi fel az SQL-kódot. Így az első tömbből a `(id, name, year) VALUES (123, 'Jim', 1978)`, míg a másodikból a `name = 'Jim', year = 1978` címet alakítja át. - -A rendezést is leírhatjuk tömb használatával, a kulcsok az oszlopok nevei, az értékek pedig boolean értékek, amelyek meghatározzák, hogy növekvő sorrendbe rendezzük-e: - -```php -$database->query('SELECT id FROM author ORDER BY', [ - 'id' => true, // felfelé növekvő - 'name' => false, // csökkenő -]); -// SELECT id FROM author ORDER BY `id`, `name` DESC -``` - -Ha a felismerés nem működött, akkor az összeállítás formáját megadhatjuk a `?` jokerjelzéssel, amelyet egy utalás követ. Ezek a tippek támogatottak: - -| ?values | (kulcs1, kulcs2, ...) VALUES (érték1, érték2, ...) -| ?set | kulcs1 = érték1, kulcs2 = érték2, ... -| ?and | kulcs1 = érték1 ÉS kulcs2 = érték2 ... -| ?or | kulcs1 = érték1 VAGY kulcs2 = érték2 ... -| ?order | kulcs1 ASC, kulcs2 DESC - -A WHERE záradék a `?and` operátort használja, így a feltételeket a `AND` kapcsolja össze: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND `year` = 1978 -``` - -Ami könnyen megváltoztatható `OR` -ra a `?or` joker használatával: - -```php -$result = $database->query('SELECT * FROM users WHERE ?or', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' OR `year` = 1978 -``` - -Használhatunk operátorokat a feltételekben: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name <>' => $name, - 'year >' => $year, -]); -// SELECT * FROM users WHERE `name` <> 'Jim' AND `year` > 1978 -``` - -És felsorolásokat is: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => ['Jim', 'Jack'], - 'role NOT IN' => ['admin', 'owner'], // enumeration + operator NOT IN -]); -// SELECT * FROM users WHERE -// `name` IN ('Jim', 'Jack') AND `role` NOT IN ('admin', 'owner') -``` - -Az úgynevezett SQL literál segítségével egy darab egyéni SQL-kódot is beilleszthetünk: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year >' => $database::literal('YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) -``` - -Alternatívaként: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) -``` - -SQL literál is lehet a paraméterei: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > ? AND year < ?', $min, $max), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) -``` - -Ennek köszönhetően érdekes kombinációkat hozhatunk létre: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('?or', [ - 'active' => true, - 'role' => $role, - ]), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') -``` - - -Változó neve .[#toc-variable-name] -================================== - -Van egy `?name` joker, amelyet akkor használhat, ha a táblázat neve vagy az oszlop neve egy változó. (Vigyázat, ne engedje, hogy a felhasználó manipulálja egy ilyen változó tartalmát): - -```php -$table = 'blog.users'; -$column = 'name'; -$database->query('SELECT * FROM ?name WHERE ?name = ?', $table, $column, $name); -// SELECT * FROM `blog`.`users` WHERE `name` = 'Jim' -``` - - -Tranzakciók .[#toc-transactions] -================================ - -A tranzakciók kezelésére három módszer áll rendelkezésre: - -```php -$database->beginTransaction(); - -$database->commit(); - -$database->rollback(); -``` - -A `transaction()` módszer elegáns megoldást kínál. Átadjuk a tranzakcióban végrehajtott visszahívást. Ha a végrehajtás során kivételt dobunk, a tranzakciót eldobjuk, ha minden rendben megy, a tranzakciót lekötjük. - -```php -$id = $database->transaction(function ($database) { - $database->query('DELETE FROM ...'); - $database->query('INSERT INTO ...'); - // ... - return $database->getInsertId(); -}); -``` - -Mint látható, a `transaction()` metódus a callback visszatérési értékét adja vissza. - -A tranzakció() is beágyazható, ami egyszerűsíti a független tárolók megvalósítását. diff --git a/database/hu/exceptions.texy b/database/hu/exceptions.texy new file mode 100644 index 0000000000..790fc06199 --- /dev/null +++ b/database/hu/exceptions.texy @@ -0,0 +1,34 @@ +Kivételek +********* + +A Nette Database kivétel-hierarchiát használ. Az alaposztály a `Nette\Database\DriverException`, amely a `PDOException`-ből öröklődik, és kibővített lehetőségeket biztosít az adatbázis-hibák kezelésére: + +- A `getDriverCode()` metódus visszaadja az adatbázis-driver hibakódját. +- A `getSqlState()` metódus visszaadja az SQLSTATE kódot. +- A `getQueryString()` és `getParameters()` metódusok lehetővé teszik az eredeti lekérdezés és paramétereinek lekérését. + +A `DriverException`-ből a következő specializált kivételek öröklődnek: + +- `ConnectionException` - jelzi az adatbázis-szerverhez való csatlakozás sikertelenségét. +- `ConstraintViolationException` - alaposztály az adatbázis-korlátozások megsértéséhez, amelyből öröklődnek: + - `ForeignKeyConstraintViolationException` - idegen kulcs megsértése. + - `NotNullConstraintViolationException` - NOT NULL korlátozás megsértése. + - `UniqueConstraintViolationException` - érték egyediségének megsértése. + + +Példa a `UniqueConstraintViolationException` kivétel elkapására, amely akkor következik be, ha olyan e-mail címmel próbálunk meg felhasználót beszúrni, amely már létezik az adatbázisban (feltéve, hogy az email oszlopnak egyedi indexe van). + +```php +try { + $database->query('INSERT INTO users', [ + 'email' => 'john@example.com', + 'name' => 'John Doe', + 'password' => $hashedPassword, + ]); +} catch (Nette\Database\UniqueConstraintViolationException $e) { + echo 'Már létezik felhasználó ezzel az e-mail címmel.'; + +} catch (Nette\Database\DriverException $e) { + echo 'Hiba történt a regisztráció során: ' . $e->getMessage(); +} +``` diff --git a/database/hu/explorer.texy b/database/hu/explorer.texy index 55b2296755..5b46016d0c 100644 --- a/database/hu/explorer.texy +++ b/database/hu/explorer.texy @@ -1,550 +1,912 @@ -Adatbázis-kutató -**************** +Database Explorer +***************** <div class=perex> -A Nette Database Explorer jelentősen leegyszerűsíti az adatok lekérdezését az adatbázisból SQL-lekérdezések írása nélkül. +Az Explorer intuitív és hatékony módot kínál az adatbázissal való munkára. Automatikusan gondoskodik a táblák közötti kapcsolatokról és a lekérdezések optimalizálásáról, így Ön az alkalmazására koncentrálhat. Azonnal működik beállítás nélkül. Ha teljes kontrollra van szüksége az SQL lekérdezések felett, használhatja az [SQL megközelítést |SQL way]. -- hatékony lekérdezéseket használ -- nem továbbít feleslegesen adatokat -- elegáns szintaxissal rendelkezik +- Az adatokkal való munka természetes és könnyen érthető. +- Optimalizált SQL lekérdezéseket generál, amelyek csak a szükséges adatokat töltik be. +- Lehetővé teszi a kapcsolódó adatokhoz való könnyű hozzáférést JOIN lekérdezések írása nélkül. +- Azonnal működik bármilyen konfiguráció vagy entitásgenerálás nélkül. </div> -Az Adatbázis-kutató használatához kezdje egy táblával - hívja meg a `table()` címet a [api:Nette\Database\Explorer] objektumon. A kontextusobjektum-példány megszerzésének legegyszerűbb módja [itt van leírva |core#Connection and Configuration], vagy abban az esetben, ha a Nette Database Explorer önálló eszközként használatos, [kézzel is létrehozható |#Creating Explorer Manually]. + +Az Explorerrel a [api:Nette\Database\Explorer] objektum `table()` metódusának meghívásával kezdhet (a csatlakozás részleteit a [Csatlakozás és konfiguráció |guide#Csatlakozás és konfiguráció] fejezetben találja): ```php -$books = $explorer->table('book'); // db tábla neve 'book' +$books = $explorer->table('book'); // 'book' a tábla neve ``` -A hívás a [Selection |api:Nette\Database\Table\Selection] objektum egy példányát adja vissza, amelyen iterálva lekérdezhetjük az összes könyvet. Minden egyes elemet (sort) egy [ActiveRow |api:Nette\Database\Table\ActiveRow] példány képvisel, amelynek tulajdonságaihoz adatokat rendelünk: +A metódus egy [Selection |api:Nette\Database\Table\Selection] objektumot ad vissza, amely egy SQL lekérdezést képvisel. Erre az objektumra további metódusokat láncolhatunk az eredmények szűrésére és rendezésére. A lekérdezés csak akkor áll össze és fut le, amikor elkezdjük kérni az adatokat. Például egy `foreach` ciklussal történő bejáráskor. Minden sort egy [ActiveRow |api:Nette\Database\Table\ActiveRow] objektum képvisel: ```php foreach ($books as $book) { - echo $book->title; - echo $book->author_id; + echo $book->title; // a 'title' oszlop kiírása + echo $book->author_id; // az 'author_id' oszlop kiírása } ``` -Csak egy adott sor kinyerése a `get()` metódussal történik, amely közvetlenül egy ActiveRow példányt ad vissza. +Az Explorer alapvetően megkönnyíti a [táblák közötti kapcsolatokkal |#Kapcsolatok a táblák között] való munkát. A következő példa bemutatja, milyen könnyen tudunk adatokat kiírni összekapcsolt táblákból (könyvek és szerzőik). Figyelje meg, hogy nem kell semmilyen JOIN lekérdezést írnunk, a Nette létrehozza őket helyettünk: ```php -$book = $explorer->table('book')->get(2); // visszaadja a 2 azonosítóval rendelkező könyvet. -echo $book->title; -echo $book->author_id; +$books = $explorer->table('book'); + +foreach ($books as $book) { + echo 'Könyv: ' . $book->title; + echo 'Szerző: ' . $book->author->name; // JOIN-t hoz létre az 'author' táblára +} ``` -Vessünk egy pillantást a gyakori felhasználási esetre. Könyveket és szerzőiket kell lekérni. Ez egy gyakori 1:N kapcsolat. A gyakran használt megoldás az adatok lekérdezése egyetlen SQL-lekérdezéssel, táblázat-összekötésekkel. A másik lehetőség, hogy az adatokat külön-külön lekérdezzük, egy lekérdezést futtatunk a könyvek lekérdezésére, majd egy másik lekérdezéssel (pl. a foreach ciklusban) minden könyvhöz megkapjuk a szerzőt. Ez könnyen optimalizálható úgy, hogy csak két lekérdezés fusson, egy a könyvekre és egy másik a szükséges szerzőkre - és a Nette Database Explorer pontosan így csinálja. +A Nette Database Explorer optimalizálja a lekérdezéseket, hogy a lehető leghatékonyabbak legyenek. A fenti példa csak két SELECT lekérdezést hajt végre, függetlenül attól, hogy 10 vagy 10 000 könyvet dolgozunk fel. -Az alábbi példákban az ábrán látható adatbázis-sémával fogunk dolgozni. Vannak OneHasMany (1:N) linkek (a könyv szerzője `author_id` és az esetleges fordító `translator_id`, amely lehet `null`) és ManyHasMany (M:N) link a könyv és a címkék között. +Ráadásul az Explorer figyeli, hogy mely oszlopokat használják a kódban, és csak azokat tölti be az adatbázisból, ezzel további teljesítményt takarítva meg. Ez a viselkedés teljesen automatikus és adaptív. Ha később módosítja a kódot, és elkezd további oszlopokat használni, az Explorer automatikusan módosítja a lekérdezéseket. Nem kell semmit beállítania, sem azon gondolkodnia, mely oszlopokra lesz szüksége - bízza ezt a Nette-re. -[Egy sémát is tartalmazó példa megtalálható a GitHubon |https://github.com/nette-examples/books]. -[* db-schema-1-.webp *] *** A példákban használt adatbázis-struktúra .<> +Szűrés és rendezés +================== -A következő kód minden könyvhöz felsorolja a szerző nevét és az összes címkét. [Mindjárt megbeszéljük |#Working with relationships], hogyan működik ez belsőleg. +A `Selection` osztály metódusokat biztosít az adatok kiválasztásának szűrésére és rendezésére. -```php -$books = $explorer->table('book'); +.[language-php] +| `where($condition, ...$params)` | WHERE feltételt ad hozzá. Több feltétel AND operátorral van összekötve. +| `whereOr(array $conditions)` | OR operátorral összekötött WHERE feltételek csoportját adja hozzá. +| `wherePrimary($value)` | WHERE feltételt ad hozzá az elsődleges kulcs alapján. +| `order($columns, ...$params)` | Beállítja az ORDER BY rendezést. +| `select($columns, ...$params)` | Meghatározza a betöltendő oszlopokat. +| `limit($limit, $offset = null)` | Korlátozza a sorok számát (LIMIT) és opcionálisan beállítja az OFFSET-et. +| `page($page, $itemsPerPage, &$total = null)` | Beállítja a lapozást. +| `group($columns, ...$params)` | Csoportosítja a sorokat (GROUP BY). +| `having($condition, ...$params)` | HAVING feltételt ad hozzá a csoportosított sorok szűréséhez. -foreach ($books as $book) { - echo 'title: ' . $book->title; - echo 'írta: ' . $book->author->name; // $book->szerző a 'szerző' táblázat sora. +A metódusok láncolhatók (ún. [fluent interface |nette:introduction-to-object-oriented-programming#Fluent Interfészek]): `$table->where(...)->order(...)->limit(...)`. - echo 'tags: '; - foreach ($book->related('book_tag') as $bookTag) { - echo $bookTag->tag->name . ', '; // $bookTag->tag egy sor a 'tag' táblázatból. - } -} -``` +Ezekben a metódusokban speciális jelölést is használhat a [kapcsolódó táblákból származó adatokhoz |#Lekérdezés kapcsolódó táblákon keresztül] való hozzáféréshez. -Örülni fog, hogy milyen hatékonyan működik az adatbázis-réteg. A fenti példa állandó számú kérést tesz, amelyek így néznek ki: -```sql -SELECT * FROM `book` -SELECT * FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT * FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) -``` +Escapelés és azonosítók +----------------------- -Ha a [gyorsítótárat |caching:] használja (alapértelmezés szerint be van kapcsolva), egyetlen oszlopot sem kérdez le feleslegesen. Az első lekérdezés után a gyorsítótár tárolja a használt oszlopneveket, és a Nette Database Explorer csak a szükséges oszlopokkal kapcsolatos lekérdezéseket hajtja végre: +A metódusok automatikusan escapelik a paramétereket és idézőjelek közé teszik az azonosítókat (tábla- és oszlopneveket), ezzel megakadályozva az SQL injectiont. A helyes működéshez néhány szabályt be kell tartani: -```sql -SELECT `id`, `title`, `author_id` FROM `book` -SELECT `id`, `name` FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT `book_id`, `tag_id` FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT `id`, `name` FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) +- A kulcsszavakat, függvényneveket, eljárásneveket stb. **nagybetűkkel** írja. +- Az oszlop- és táblaneveket **kisbetűkkel** írja. +- A stringeket mindig **paramétereken** keresztül adja át. + +```php +where('name = ' . $name); // KRITIKUS SEBEZHETŐSÉG: SQL injection +where('name LIKE "%search%"'); // ROSSZ: bonyolítja az automatikus idézőjelezést +where('name LIKE ?', '%search%'); // HELYES: érték paraméteren keresztül átadva + +where('name like ?', $name); // ROSSZ: generálja: `name` `like` ? +where('name LIKE ?', $name); // HELYES: generálja: `name` LIKE ? +where('LOWER(name) = ?', $value);// HELYES: LOWER(`name`) = ? ``` -Kiválasztások .[#toc-selections] -================================ +where(string|array $condition, ...$parameters): static .[method] +---------------------------------------------------------------- -Lásd a sorok szűrésének és korlátozásának lehetőségeit [api:Nette\Database\Table\Selection]: +Szűri az eredményeket WHERE feltételekkel. Erőssége az intelligens munka különböző típusú értékekkel és az SQL operátorok automatikus kiválasztása. -.[language-php] -| `$table->where($where[, $param[, ...]])` | WHERE beállítása AND ragasztóval, ha két vagy több feltétel van megadva -| `$table->whereOr($where)` | WHERE beállítása, amely két vagy több feltétel megadása esetén OR-t használ ragasztóként. -| `$table->order($columns)` | ORDER BY beállítása, lehet kifejezés. `('column DESC, id DESC')` -| `$table->select($columns)` | Letöltött oszlopok beállítása, lehet kifejezés is. `('col, MD5(col) AS hash')` -| `$table->limit($limit[, $offset])` | LIMIT és OFFSET beállítása -| `$table->page($page, $itemsPerPage[, &$lastPage])` | Engedélyezi a lapozást -| `$table->group($columns)` | GROUP BY beállítása -| `$table->having($having)` | HAVING beállítása +Alapvető használat: -Fluent interfész használható, például `$table->where(...)->order(...)->limit(...)`. Több `where` vagy `whereOr` feltétel a `AND` operátorral kapcsolódik. +```php +$table->where('id', $value); // WHERE `id` = 123 +$table->where('id > ?', $value); // WHERE `id` > 123 +$table->where('id = ? OR name = ?', $id, $name); // WHERE `id` = 1 OR `name` = 'Jon Snow' +``` +A megfelelő operátorok automatikus felismerésének köszönhetően nem kell különböző speciális esetekkel foglalkoznunk. A Nette megoldja őket helyettünk: -where() .[#toc-where] ---------------------- +```php +$table->where('id', 1); // WHERE `id` = 1 +$table->where('id', null); // WHERE `id` IS NULL +$table->where('id', [1, 2, 3]); // WHERE `id` IN (1, 2, 3) +// operátor nélküli helyettesítő kérdőjelet is használhatunk: +$table->where('id ?', 1); // WHERE `id` = 1 +``` -A Nette Database Explorer automatikusan hozzá tudja adni a szükséges operátorokat az átadott értékekhez: +A metódus helyesen kezeli a negált feltételeket és az üres tömböket is: -.[language-php] -| `$table->where('field', $value)` | field = $value -| `$table->where('field', null)` | field IS NULL -| `$table->where('field > ?', $val)` | mező > $val -| `$table->where('field', [1, 2])` | field IN (1, 2) -| `$table->where('id = ? OR name = ?', 1, $name)` | id = 1 VAGY név = 'Jon Snow' -| `$table->where('field', $explorer->table($tableName))` | field IN (SELECT $primary FROM $tableName) -| `$table->where('field', $explorer->table($tableName)->select('col'))` | field IN (SELECT col FROM $tableName) +```php +$table->where('id', []); // WHERE `id` IS NULL AND FALSE -- semmit sem talál +$table->where('id NOT', []); // WHERE `id` IS NULL OR TRUE -- mindent megtalál +$table->where('NOT (id ?)', []); // WHERE NOT (`id` IS NULL AND FALSE) -- mindent megtalál +// $table->where('NOT id ?', $ids); Figyelem - ez a szintaxis nem támogatott +``` -A helyőrzőt oszlopoperátor nélkül is megadhatja. Ezek a hívások ugyanazok. +Paraméterként átadhatunk egy másik tábla eredményét is - al-lekérdezés jön létre: ```php -$table->where('id = ? OR id = ?', 1, 2); -$table->where('id ? OR id ?', 1, 2); +// WHERE `id` IN (SELECT `id` FROM `tableName`) +$table->where('id', $explorer->table($tableName)); + +// WHERE `id` IN (SELECT `col` FROM `tableName`) +$table->where('id', $explorer->table($tableName)->select('col')); ``` -Ez a funkció lehetővé teszi a helyes operátor generálását az érték alapján: +A feltételeket tömbként is átadhatjuk, amelynek elemei AND-del lesznek összekötve: ```php -$table->where('id ?', 2); // id = 2 -$table->where('id ?', null); // id IS NULL -$table->where('id', $ids); // id IN (...) +// WHERE (`price_final` < `price_original`) AND (`stock_count` > `min_stock`) +$table->where([ + 'price_final < price_original', + 'stock_count > min_stock', +]); ``` -A kiválasztás helyesen kezeli a negatív feltételeket is, üres tömbök esetén is működik: +A tömbben használhatunk kulcs => érték párokat, és a Nette ismét automatikusan kiválasztja a megfelelő operátorokat: ```php -$table->where('id', []); // id IS NULL AND FALSE -$table->where('id NOT', []); // id IS NULL OR TRUE -$table->where('NOT (id ?)', $ids); // NOT (id IS NULL AND FALSE) +// WHERE (`status` = 'active') AND (`id` IN (1, 2, 3)) +$table->where([ + 'status' => 'active', + 'id' => [1, 2, 3], +]); +``` -// ez kivételt dob, ez a szintaxis nem támogatott. -$table->where('NOT id ?', $ids); +A tömbben kombinálhatunk SQL kifejezéseket helyettesítő kérdőjelekkel és több paraméterrel. Ez alkalmas komplex feltételekhez pontosan definiált operátorokkal: + +```php +// WHERE (`age` > 18) AND (ROUND(`score`, 2) > 75.5) +$table->where([ + 'age > ?' => 18, + 'ROUND(score, ?) > ?' => [2, 75.5], // két paramétert tömbként adunk át +]); ``` +A `where()` többszöri hívása automatikusan AND-del köti össze a feltételeket. + -whereOr() .[#toc-whereor] -------------------------- +whereOr(array $parameters): static .[method] +-------------------------------------------- -Példa a paraméterek nélküli használatra: +Hasonlóan a `where()`-hez, feltételeket ad hozzá, de azzal a különbséggel, hogy OR-ral köti össze őket: ```php -// WHERE (user_id IS NULL) OR (SUM(`field1`) > SUM(`field2`)) +// WHERE (`status` = 'active') OR (`deleted` = 1) $table->whereOr([ - 'user_id IS NULL', - 'SUM(field1) > SUM(field2)', + 'status' => 'active', + 'deleted' => true, ]); ``` -A paramétereket használjuk. Ha nem ad meg operátort, a Nette Database Explorer automatikusan hozzáadja a megfelelőt: +Itt is használhatunk komplexebb kifejezéseket: ```php -// WHERE (`field1` IS NULL) OR (`field2` IN (3, 5)) OR (`amount` > 11) +// WHERE (`price` > 1000) OR (`price_with_tax` > 1500) $table->whereOr([ - 'field1' => null, - 'field2' => [3, 5], - 'amount >' => 11, + 'price > ?' => 1000, + 'price_with_tax > ?' => 1500, ]); ``` -A kulcs tartalmazhat egy joker kérdőjeleket tartalmazó kifejezést, majd az értékben paramétereket adhat át: + +wherePrimary(mixed $key): static .[method] +------------------------------------------ + +Feltételt ad hozzá a tábla elsődleges kulcsához: ```php -// WHERE (`id` > 12) OR (ROUND(`id`, 5) = 3) -$table->whereOr([ - 'id > ?' => 12, - 'ROUND(id, ?) = ?' => [5, 3], -]); +// WHERE `id` = 123 +$table->wherePrimary(123); + +// WHERE `id` IN (1, 2, 3) +$table->wherePrimary([1, 2, 3]); +``` + +Ha a táblának összetett elsődleges kulcsa van (pl. `foo_id`, `bar_id`), tömbként adjuk át: + +```php +// WHERE `foo_id` = 1 AND `bar_id` = 5 +$table->wherePrimary(['foo_id' => 1, 'bar_id' => 5])->fetch(); + +// WHERE (`foo_id`, `bar_id`) IN ((1, 5), (2, 3)) +$table->wherePrimary([ + ['foo_id' => 1, 'bar_id' => 5], + ['foo_id' => 2, 'bar_id' => 3], +])->fetchAll(); ``` -order() .[#toc-order] ---------------------- +order(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- -Felhasználási példák: +Meghatározza a sorok visszaadási sorrendjét. Rendezhetünk egy vagy több oszlop szerint, csökkenő vagy növekvő sorrendben, vagy saját kifejezés szerint: ```php -$table->order('field1'); // ORDER BY `field1` -$table->order('field1 DESC, field2'); // ORDER BY `field1` DESC, `field2` -$table->order('field = ? DESC', 123); // ORDER BY `field` = 123 DESC +$table->order('created'); // ORDER BY `created` +$table->order('created DESC'); // ORDER BY `created` DESC +$table->order('priority DESC, created'); // ORDER BY `priority` DESC, `created` +$table->order('status = ? DESC', 'active'); // ORDER BY `status` = 'active' DESC ``` -select() .[#toc-select] ------------------------ +select(string $columns, ...$parameters): static .[method] +--------------------------------------------------------- + +Meghatározza az adatbázisból visszaadandó oszlopokat. Alapértelmezés szerint a Nette Database Explorer csak azokat az oszlopokat adja vissza, amelyeket ténylegesen használnak a kódban. A `select()` metódust olyan esetekben használjuk, amikor specifikus kifejezéseket kell visszaadnunk: + +```php +// SELECT *, DATE_FORMAT(`created_at`, "%d.%m.%Y") AS `formatted_date` +$table->select('*, DATE_FORMAT(created_at, ?) AS formatted_date', '%d.%m.%Y'); +``` -Felhasználási példák: +Az `AS` segítségével definiált aliasok ezután elérhetők az ActiveRow objektum tulajdonságaként: ```php -$table->select('field1'); // SELECT `field1` -$table->select('col, UPPER(col) AS abc'); // SELECT `col`, UPPER(`col`) AS abc -$table->select('SUBSTR(title, ?)', 3); // SELECT SUBSTR(`title`, 3) +foreach ($table as $row) { + echo $row->formatted_date; // hozzáférés az aliashoz +} ``` -limit() .[#toc-limit] ---------------------- +limit(?int $limit, ?int $offset = null): static .[method] +--------------------------------------------------------- -Felhasználási példák: +Korlátozza a visszaadott sorok számát (LIMIT), és opcionálisan lehetővé teszi az offset beállítását: ```php -$table->limit(1); // LIMIT 1 -$table->limit(1, 10); // LIMIT 1 OFFSET 10 +$table->limit(10); // LIMIT 10 (az első 10 sort adja vissza) +$table->limit(10, 20); // LIMIT 10 OFFSET 20 ``` +Lapozáshoz célszerűbb a `page()` metódust használni. + -page() .[#toc-page] -------------------- +page(int $page, int $itemsPerPage, &$numOfPages = null): static .[method] +------------------------------------------------------------------------- -A határérték és az eltolás beállításának alternatív módja: +Megkönnyíti az eredmények lapozását. Elfogadja az oldal számát (1-től számolva) és az oldalankénti elemek számát. Opcionálisan átadható egy referencia egy változóra, amelybe az oldalak teljes száma kerül mentésre: ```php -$page = 5; -$itemsPerPage = 10; -$table->page($page, $itemsPerPage); // LIMIT 10 OFFSET 40 +$numOfPages = null; +$table->page(page: 3, itemsPerPage: 10, $numOfPages); +echo "Összesen oldalak: $numOfPages"; ``` -A `$lastPage` változónak átadott utolsó oldalszám megadása: + +group(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- + +Csoportosítja a sorokat a megadott oszlopok szerint (GROUP BY). Általában aggregáló függvényekkel együtt használják: ```php -$table->page($page, $itemsPerPage, $lastPage); +// Megszámolja a termékek számát minden kategóriában +$table->select('category_id, COUNT(*) AS count') + ->group('category_id'); ``` -group() .[#toc-group] ---------------------- +having(string $having, ...$parameters): static .[method] +-------------------------------------------------------- -Felhasználási példák: +Feltételt állít be a csoportosított sorok szűréséhez (HAVING). Használható a `group()` metódussal és aggregáló függvényekkel együtt: ```php -$table->group('field1'); // GROUP BY `field1` -$table->group('field1, field2'); // GROUP BY `field1`, `field2` +// Megtalálja azokat a kategóriákat, amelyek több mint 100 termékkel rendelkeznek +$table->select('category_id, COUNT(*) AS count') + ->group('category_id') + ->having('count > ?', 100); ``` -having() .[#toc-having] ------------------------ +Adatok olvasása +=============== + +Az adatok adatbázisból történő olvasásához számos hasznos metódus áll rendelkezésre: + +.[language-php] +| `foreach ($table as $key => $row)` | Iterál az összes soron, `$key` az elsődleges kulcs értéke, `$row` egy ActiveRow objektum +| `$row = $table->get($key)` | Visszaad egy sort az elsődleges kulcs alapján +| `$row = $table->fetch()` | Visszaadja az aktuális sort és a mutatót a következőre lépteti +| `$array = $table->fetchPairs()` | Asszociatív tömböt hoz létre az eredményekből +| `$array = $table->fetchAll()` | Visszaadja az összes sort tömbként +| `count($table)` | Visszaadja a sorok számát a Selection objektumban + +Az [ActiveRow |api:Nette\Database\Table\ActiveRow] objektum csak olvasásra szolgál. Ez azt jelenti, hogy nem lehet módosítani a tulajdonságainak értékeit. Ez a korlátozás biztosítja az adatok konzisztenciáját és megakadályozza a váratlan mellékhatásokat. Az adatok az adatbázisból töltődnek be, és bármilyen változtatást explicit módon és ellenőrzötten kell végrehajtani. -Használati példák: + +`foreach` - iteráció az összes soron +------------------------------------ + +A legegyszerűbb módja a lekérdezés végrehajtásának és a sorok megszerzésének a `foreach` ciklussal történő iterálás. Automatikusan elindítja az SQL lekérdezést. ```php -$table->having('COUNT(items) >', 100); // HAVING COUNT(`items`) > 100 +$books = $explorer->table('book'); +foreach ($books as $key => $book) { + // $key az elsődleges kulcs értéke, $book egy ActiveRow + echo "$book->title ({$book->author->name})"; +} ``` -Szűrés egy másik táblázat értéke alapján .[#toc-joining-key] ------------------------------------------------------------- +get($key): ?ActiveRow .[method] +------------------------------- -Gyakran előfordul, hogy az eredményeket olyan feltétel alapján kell szűrni, amely egy másik adatbázis-táblát érint. Az ilyen típusú feltételekhez táblaösszekötésre van szükség. Ezeket azonban már nem kell megírni. +Végrehajtja az SQL lekérdezést és visszaadja a sort az elsődleges kulcs alapján, vagy `null`-t, ha nem létezik. -Tegyük fel, hogy az összes olyan könyvet meg kell szereznünk, amelynek szerzőjének neve 'Jon'. Mindössze a kapcsolat összekötő kulcsát és az összekapcsolt tábla oszlopnevét kell megírnia. Az összekötő kulcs abból az oszlopból származik, amely az összekötni kívánt táblára utal. Példánkban (lásd a db sémát) ez a `author_id` oszlop, és elegendő, ha csak az első részét használjuk - `author` (a `_id` utótag elhagyható). A `name` a `author` tábla egyik oszlopa, amelyet használni szeretnénk. A könyvfordítóra vonatkozó feltétel (amelyhez a `translator_id` oszlop kapcsolódik) ugyanilyen egyszerűen létrehozható. +```php +$book = $explorer->table('book')->get(123); // visszaadja az ActiveRow-t 123 ID-vel vagy null-t +if ($book) { + echo $book->title; +} +``` + + +fetch(): ?ActiveRow .[method] +----------------------------- + +Visszaadja a sort és a belső mutatót a következőre lépteti. Ha már nincsenek további sorok, `null`-t ad vissza. ```php $books = $explorer->table('book'); -$books->where('author.name LIKE ?', '%Jon%'); -$books->where('translator.name', 'David Grudl'); +while ($book = $books->fetch()) { + $this->processBook($book); +} ``` -Az összekötő kulcs logikáját a [Conventions |api:Nette\Database\Conventions] megvalósítása vezérli. Javasoljuk a [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions] használatát, amely elemzi az idegen kulcsokat, és lehetővé teszi, hogy könnyen dolgozzon ezekkel a kapcsolatokkal. -A könyv és a szerzője közötti kapcsolat 1:N. A fordított kapcsolat is lehetséges. Ezt **backjoin**-nak nevezzük. Nézzünk meg egy másik példát. Szeretnénk lekérdezni az összes olyan szerzőt, aki több mint 3 könyvet írt. Ahhoz, hogy a join fordított legyen, a `:` (colon). Colon means that the joined relationship means hasMany (and it's quite logical too, as two dots are more than one dot). Unfortunately, the Selection class isn't smart enough, so we have to help with the aggregation and provide a `GROUP BY` utasítást használjuk, a feltételt is `HAVING` utasítás formájában kell megírni. +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Visszaadja az eredményeket asszociatív tömbként. Az első argumentum határozza meg annak az oszlopnak a nevét, amely kulcsként lesz használva a tömbben, a második argumentum pedig annak az oszlopnak a nevét, amely értékként lesz használva: ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book.id) > 3'); +$authors = $explorer->table('author')->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] +``` + +Ha csak az első paramétert adjuk meg, az érték az egész sor lesz, azaz az `ActiveRow` objektum: + +```php +$authors = $explorer->table('author')->fetchPairs('id'); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` + +Duplikált kulcsok esetén az utolsó sor értéke lesz használva. Ha `null`-t használunk kulcsként, a tömb numerikusan lesz indexelve nullától kezdve (ekkor nem történik ütközés): + +```php +$authors = $explorer->table('author')->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] ``` -Észrevehette, hogy az összekötő kifejezés a könyvre utal, de nem egyértelmű, hogy a `author_id` vagy a `translator_id` oldalon keresztül kötünk-e. A fenti példában a Selection a `author_id` oszlopon keresztül köt, mert találtunk egyezést a forrás táblával - a `author` táblával. Ha nem lenne ilyen egyezés, és több lehetőség is lenne, a Nette [AmbiguousReferenceKeyExceptiont |api:Nette\Database\Conventions\AmbiguousReferenceKeyException] dobna. -A `translator_id` oszlopon keresztül történő csatlakozáshoz adjunk meg egy opcionális paramétert a csatlakozási kifejezésben. +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Alternatívaként megadhat egy callbacket paraméterként, amely minden sorhoz vagy magát az értéket, vagy egy kulcs-érték párt ad vissza. ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book(translator).id) > 3'); +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => "$row->title ({$row->author->name})"); +// ['Első könyv (János Novak)', ...] + +// A callback visszaadhat egy tömböt is kulcs & érték párral: +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => [$row->title, $row->author->name]); +// ['Első könyv' => 'János Novak', ...] ``` -Nézzünk meg néhány nehezebb csatlakozási kifejezést. -Szeretnénk megtalálni az összes szerzőt, aki írt valamit a PHP-ről. Minden könyvnek van címkéje, így ki kell választanunk azokat a szerzőket, akik PHP címkéjű könyvet írtak. +fetchAll(): array .[method] +--------------------------- + +Visszaadja az összes sort `ActiveRow` objektumok asszociatív tömbjeként, ahol a kulcsok az elsődleges kulcsok értékei. ```php -$authors = $explorer->table('author'); -$authors->where(':book:book_tags.tag.name', 'PHP') - ->group('author.id') - ->having('COUNT(:book:book_tags.tag.id) > 0'); +$allBooks = $explorer->table('book')->fetchAll(); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] ``` -Összesített lekérdezések .[#toc-aggregate-queries] --------------------------------------------------- +count(): int .[method] +---------------------- -| `$table->count('*')` | Sorok számának lekérdezése -| `$table->count("DISTINCT $column")` | Különálló értékek számának lekérdezése -| `$table->min($column)` | Minimális érték lekérdezése -| `$table->max($column)` | Maximális érték lekérdezése -| `$table->sum($column)` | Az összes érték összegének lekérdezése -| `$table->aggregation("GROUP_CONCAT($column)")` | Bármilyen aggregációs függvény futtatása +A `count()` metódus paraméter nélkül visszaadja a sorok számát a `Selection` objektumban: -.[caution] -A `count()` módszer megadott paraméterek nélkül kiválasztja az összes rekordot és visszaadja a tömb méretét, ami nagyon nem hatékony. Ha például a lapozáshoz szükséges sorok számát kell kiszámítania, mindig adja meg az első argumentumot. +```php +$table->where('category', 1); +$count = $table->count(); +$count = count($table); // alternatíva +``` +Figyelem, a `count()` paraméterrel aggregáló COUNT függvényt hajt végre az adatbázisban, lásd alább. -Kikerülés és idézés .[#toc-escaping-quoting] -============================================ -Az Adatbázis-kutató okos, és kikerüli a paramétereket és idézőjeleket azonosítókat az Ön helyett. Ezeket az alapvető szabályokat azonban be kell tartani: +ActiveRow::toArray(): array .[method] +------------------------------------- -- kulcsszavak, függvények, eljárások nagybetűsek legyenek. -- az oszlopoknak és táblázatoknak kisbetűsnek kell lenniük -- a változókat paraméterként kell átadni, nem szabad összekapcsolni. +Átalakítja az `ActiveRow` objektumot asszociatív tömbbé, ahol a kulcsok az oszlopnevek, az értékek pedig a megfelelő adatok. ```php -->where('name like ?', 'John'); // ROSSZ! generál: `név` `mint` ? -->where('name LIKE ?', 'John'); // HELYES +$book = $explorer->table('book')->get(1); +$bookArray = $book->toArray(); +// $bookArray lesz ['id' => 1, 'title' => '...', 'author_id' => ..., ...] +``` + + +Aggregáció +========== + +A `Selection` osztály metódusokat biztosít az aggregáló függvények (COUNT, SUM, MIN, MAX, AVG stb.) egyszerű végrehajtásához. + +.[language-php] +| `count($expr)` | Megszámolja a sorok számát +| `min($expr)` | Visszaadja a minimális értéket egy oszlopban +| `max($expr)` | Visszaadja a maximális értéket egy oszlopban +| `sum($expr)` | Visszaadja az értékek összegét egy oszlopban +| `aggregation($function)` | Lehetővé teszi tetszőleges aggregáló függvény végrehajtását. Pl. `AVG()`, `GROUP_CONCAT()` + -->where('KEY = ?', $value); // ROSSZ! KEY egy kulcsszó -->where('key = ?', $value); // HELYES. generál: `key` = ? +count(string $expr): int .[method] +---------------------------------- -->where('name = ' . $name); // ROSSZ! sql injection! -->where('name = ?', $name); // TÖRVÉNYES +Végrehajt egy SQL lekérdezést a COUNT függvénnyel és visszaadja az eredményt. A metódus arra használatos, hogy megállapítsuk, hány sor felel meg egy bizonyos feltételnek: -->select('DATE_FORMAT(created, "%d.%m.%m.%Y")'); // ROSSZ! változókat paraméterként átadni, ne kapcsoljuk össze! -->select('DATE_FORMAT(created, ?)', '%d.%m.%Y'); // HELYES +```php +$count = $table->count('*'); // SELECT COUNT(*) FROM `table` +$count = $table->count('DISTINCT column'); // SELECT COUNT(DISTINCT `column`) FROM `table` ``` -.[warning] -A helytelen használat biztonsági résekhez vezethet +Figyelem, a [#count()] paraméter nélkül csak a sorok számát adja vissza a `Selection` objektumban. + +min(string $expr) és max(string $expr) .[method] +------------------------------------------------ -Adatok lekérése .[#toc-fetching-data] -===================================== +A `min()` és `max()` metódusok visszaadják a minimális és maximális értéket a megadott oszlopban vagy kifejezésben: -| `foreach ($table as $id => $row)` | Az eredmény összes során való ismételt átfutás -| `$row = $table->get($id)` | Egyetlen sor kinyerése $id azonosítóval a táblázatból -| `$row = $table->fetch()` | Következő sor kinyerése az eredményből. -| `$array = $table->fetchPairs($key, $value)` | Az összes érték beemelése asszociatív tömbbe. -| `$array = $table->fetchPairs($key)` | Minden sor lekérése asszociatív tömbbe. -| `count($table)` | Az eredményhalmaz sorainak számának kinyerése +```php +// SELECT MAX(`price`) FROM `products` WHERE `active` = 1 +$maxPrice = $products->where('active', true) + ->max('price'); +``` -Beszúrás, frissítés és törlés .[#toc-insert-update-delete] -========================================================== +sum(string $expr) .[method] +--------------------------- -A `insert()` módszer Traversable objektumok tömbjét fogadja el (például [ArrayHash |utils:arrays#ArrayHash], amely [űrlapokat |forms:] ad vissza): +Visszaadja az értékek összegét a megadott oszlopban vagy kifejezésben: + +```php +// SELECT SUM(`price` * `items_in_stock`) FROM `products` WHERE `active` = 1 +$totalPrice = $products->where('active', true) + ->sum('price * items_in_stock'); +``` + + +aggregation(string $function, ?string $groupFunction = null) .[method] +---------------------------------------------------------------------- + +Lehetővé teszi tetszőleges aggregáló függvény végrehajtását. + +```php +// termékek átlagos ára egy kategóriában +$avgPrice = $products->where('category_id', 1) + ->aggregation('AVG(price)'); + +// összekapcsolja a termék címkéit egyetlen stringgé +$tags = $products->where('id', 1) + ->aggregation('GROUP_CONCAT(tag.name) AS tags') + ->fetch() + ->tags; +``` + +Ha olyan eredményeket kell aggregálnunk, amelyek már maguk is valamilyen aggregáló függvényből és csoportosításból származnak (pl. `SUM(érték)` csoportosított sorokon keresztül), második argumentumként megadjuk azt az aggregáló függvényt, amelyet ezekre a köztes eredményekre kell alkalmazni: + +```php +// Kiszámítja a raktáron lévő termékek teljes árát az egyes kategóriákra, majd összeadja ezeket az árakat. +$totalPrice = $products->select('category_id, SUM(price * stock) AS category_total') + ->group('category_id') + ->aggregation('SUM(category_total)', 'SUM'); +``` + +Ebben a példában először kiszámítjuk a termékek teljes árát minden kategóriában (`SUM(price * stock) AS category_total`), és csoportosítjuk az eredményeket a `category_id` szerint. Ezután az `aggregation('SUM(category_total)', 'SUM')` segítségével összeadjuk ezeket a `category_total` köztes összegeket. A második argumentum `'SUM'` azt mondja, hogy a köztes eredményekre a SUM függvényt kell alkalmazni. + + +Beszúrás, Frissítés és Törlés +============================= + +A Nette Database Explorer leegyszerűsíti az adatok beszúrását, frissítését és törlését. Minden említett metódus kivételt `Nette\Database\DriverException` dob hiba esetén. + + +Selection::insert(iterable $data) .[method] +------------------------------------------- + +Új rekordokat szúr be a táblába. + +**Egy rekord beszúrása:** + +Az új rekordot asszociatív tömbként vagy iterable objektumként (például az [űrlapokban |forms:] használt ArrayHash) adjuk át, ahol a kulcsok megfelelnek a tábla oszlopneveinek. + +Ha a táblának definiált elsődleges kulcsa van, a metódus egy `ActiveRow` objektumot ad vissza, amely újra betöltődik az adatbázisból, hogy figyelembe vegye az adatbázis szintjén végrehajtott esetleges változásokat (triggerek, oszlopok alapértelmezett értékei, auto-increment oszlopok számításai). Ez biztosítja az adatok konzisztenciáját, és az objektum mindig az aktuális adatokat tartalmazza az adatbázisból. Ha nincs egyértelmű elsődleges kulcsa, a átadott adatokat tömb formájában adja vissza. ```php $row = $explorer->table('users')->insert([ - 'name' => $name, - 'year' => $year, + 'name' => 'John Doe', + 'email' => 'john.doe@example.com', ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) +// $row egy ActiveRow példány, és tartalmazza a beszúrt sor teljes adatait, +// beleértve az automatikusan generált ID-t és a triggerek által végrehajtott esetleges változásokat +echo $row->id; // Kiírja az újonnan beszúrt felhasználó ID-ját +echo $row->created_at; // Kiírja a létrehozás idejét, ha trigger állította be ``` -Ha a táblázatban elsődleges kulcs van definiálva, akkor a rendszer egy ActiveRow objektumot ad vissza, amely a beillesztett sort tartalmazza. +**Több rekord beszúrása egyszerre:** -Többszörös beszúrás: +A `insert()` metódus lehetővé teszi több rekord beszúrását egyetlen SQL lekérdezéssel. Ebben az esetben a beszúrt sorok számát adja vissza. ```php -$explorer->table('users')->insert([ +$insertedRows = $explorer->table('users')->insert([ + [ + 'name' => 'John', + 'year' => 1994, + ], [ - 'name' => 'Jim', - 'year' => 1978, - ], [ 'name' => 'Jack', - 'year' => 1987, + 'year' => 1995, ], ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) +// INSERT INTO `users` (`name`, `year`) VALUES ('John', 1994), ('Jack', 1995) +// $insertedRows értéke 2 lesz +``` + +Paraméterként átadható egy `Selection` objektum is adatkiválasztással. + +```php +$newUsers = $explorer->table('potential_users') + ->where('approved', 1) + ->select('name, email'); + +$insertedRows = $explorer->table('users')->insert($newUsers); ``` -DateTime objektumok paraméterként átadhatók: +**Speciális értékek beszúrása:** + +Értékként átadhatunk fájlokat, DateTime objektumokat vagy SQL literálokat is: ```php $explorer->table('users')->insert([ - 'name' => $name, - 'created' => new DateTime, // vagy $explorer::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // beszúrja a fájlt. + 'name' => 'John', + 'created_at' => new DateTime, // adatbázis formátumra konvertálja + 'avatar' => fopen('image.jpg', 'rb'), // beszúrja a fájl bináris tartalmát + 'uuid' => $explorer::literal('UUID()'), // meghívja az UUID() függvényt ]); ``` -Frissítés (az érintett sorok számát adja vissza): + +Selection::update(iterable $data): int .[method] +------------------------------------------------ + +Frissíti a tábla sorait a megadott szűrő szerint. Visszaadja a ténylegesen megváltozott sorok számát. + +A módosítandó oszlopokat asszociatív tömbként vagy iterable objektumként (például az [űrlapokban |forms:] használt ArrayHash) adjuk át, ahol a kulcsok megfelelnek a tábla oszlopneveinek: ```php -$count = $explorer->table('users') - ->where('id', 10) // update() előtt kell meghívni. +$affected = $explorer->table('users') + ->where('id', 10) ->update([ - 'name' => 'Ned Stark' + 'name' => 'John Smith', + 'year' => 1994, ]); -// UPDATE `users` SET `name`='Ned Stark' WHERE (`id` = 10) +// UPDATE `users` SET `name` = 'John Smith', `year` = 1994 WHERE `id` = 10 ``` -A frissítéshez használhatjuk a `+=` a `-=` operátorokat: +Numerikus értékek módosításához használhatjuk a `+=` és `-=` operátorokat: ```php $explorer->table('users') + ->where('id', 10) ->update([ - 'age+=' => 1, // see += + 'points+=' => 1, // növeli a 'points' oszlop értékét 1-gyel + 'coins-=' => 1, // csökkenti a 'coins' oszlop értékét 1-gyel ]); -// UPDATE users SET `age` = `age` + 1 +// UPDATE `users` SET `points` = `points` + 1, `coins` = `coins` - 1 WHERE `id` = 10 ``` -Törlés (a törölt sorok számát adja vissza): + +Selection::delete(): int .[method] +---------------------------------- + +Törli a tábla sorait a megadott szűrő szerint. Visszaadja a törölt sorok számát. ```php $count = $explorer->table('users') ->where('id', 10) ->delete(); -// DELETE FROM `users` WHERE (`id` = 10) +// DELETE FROM `users` WHERE `id` = 10 ``` +.[caution] +Az `update()` és `delete()` hívásakor ne felejtse el a `where()` segítségével megadni a módosítandó/törlendő sorokat. Ha nem használja a `where()`-t, a művelet az egész táblán végrehajtódik! -Kapcsolatokkal való munka .[#toc-working-with-relationships] -============================================================ +ActiveRow::update(iterable $data): bool .[method] +------------------------------------------------- -Van egy kapcsolata .[#toc-has-one-relation] -------------------------------------------- -A Has one reláció egy gyakori felhasználási eset. A könyvnek *egy* szerzője van. A könyvnek *egy* fordítója van. A kapcsolódó sorok kinyerése főként a `ref()` módszerrel történik. Két argumentumot fogad el: a céltábla nevét és a forrás összekötő oszlopát. Lásd a példát: +Frissíti az adatokat az `ActiveRow` objektum által képviselt adatbázis-sorban. Paraméterként egy iterable-t fogad el a frissítendő adatokkal (a kulcsok az oszlopnevek). Numerikus értékek módosításához használhatjuk a `+=` és `-=` operátorokat: + +A frissítés végrehajtása után az `ActiveRow` automatikusan újra betöltődik az adatbázisból, hogy figyelembe vegye az adatbázis szintjén végrehajtott esetleges változásokat (pl. triggerek). A metódus csak akkor ad vissza true-t, ha tényleges adatváltozás történt. ```php -$book = $explorer->table('book')->get(1); -$book->ref('author', 'author_id'); +$article = $explorer->table('article')->get(1); +$article->update([ + 'views += 1', // növeljük a megtekintések számát +]); +echo $article->views; // Kiírja az aktuális megtekintések számát ``` -A fenti példában a `author` táblából hozzuk le a kapcsolódó szerzői bejegyzést, a szerző elsődleges kulcsát a `book.author_id` oszlop segítségével keressük. A Ref() metódus visszaadja az ActiveRow példányt vagy nullát, ha nincs megfelelő bejegyzés. A visszaadott sor az ActiveRow egy példánya, így ugyanúgy dolgozhatunk vele, mint a könyv bejegyzéssel. +Ez a metódus csak egyetlen konkrét sort frissít az adatbázisban. Több sor tömeges frissítéséhez használja a [#Selection::update()] metódust. + + +ActiveRow::delete() .[method] +----------------------------- + +Törli az adatbázisból azt a sort, amelyet az `ActiveRow` objektum képvisel. ```php -$author = $book->ref('author', 'author_id'); -$author->name; -$author->born; +$book = $explorer->table('book')->get(1); +$book->delete(); // Törli az 1-es ID-jű könyvet +``` + +Ez a metódus csak egyetlen konkrét sort töröl az adatbázisból. Több sor tömeges törléséhez használja a [#Selection::delete()] metódust. + + +Kapcsolatok a táblák között +=========================== + +Relációs adatbázisokban az adatok több táblára vannak osztva, és idegen kulcsok segítségével kapcsolódnak egymáshoz. A Nette Database Explorer forradalmi módot kínál ezekkel a kapcsolatokkal való munkára - JOIN lekérdezések írása és bármi konfigurálása vagy generálása nélkül. + +A kapcsolatokkal való munka illusztrálására egy könyvadatbázis példáját használjuk ([megtalálható a GitHubon |https://github.com/nette-examples/books]). Az adatbázisban a következő táblák vannak: + +- `author` - írók és fordítók (oszlopok: `id`, `name`, `web`, `born`) +- `book` - könyvek (oszlopok: `id`, `author_id`, `translator_id`, `title`, `sequel_id`) +- `tag` - címkék (oszlopok: `id`, `name`) +- `book_tag` - kapcsolótábla a könyvek és címkék között (oszlopok: `book_id`, `tag_id`) + +[* db-schema-1-.webp *] *** A példákban használt adatbázis struktúra *** + +A könyvadatbázis példánkban több típusú kapcsolatot találunk (bár a modell egyszerűsített a valósághoz képest): + +- Egy-a-többhöz 1:N – minden könyvnek **egy** szerzője van, egy szerző **több** könyvet írhat +- Nulla-a-többhöz 0:N – egy könyvnek **lehet** fordítója, egy fordító **több** könyvet fordíthat +- Nulla-az-egyhez 0:1 – egy könyvnek **lehet** folytatása +- Több-a-többhöz M:N – egy könyvnek **több** címkéje lehet, és egy címke **több** könyvhöz rendelhető + +Ezekben a kapcsolatokban mindig van egy szülő és egy gyermek tábla. Például a szerző és a könyv közötti kapcsolatban az `author` tábla a szülő, a `book` pedig a gyermek - elképzelhetjük úgy, hogy a könyv mindig "tartozik" valamilyen szerzőhöz. Ez megmutatkozik az adatbázis struktúrájában is: a gyermek `book` tábla tartalmaz egy `author_id` idegen kulcsot, amely a szülő `author` táblára hivatkozik. -// vagy közvetlenül -$book->ref('author', 'author_id')->name; -$book->ref('author', 'author_id')->born; +Ha ki kell listáznunk a könyveket a szerzőik nevével együtt, két lehetőségünk van. Vagy egyetlen SQL lekérdezéssel szerezzük meg az adatokat JOIN segítségével: + +```sql +SELECT book.*, author.name FROM book LEFT JOIN author ON book.author_id = author.id ``` -A könyvnek is van egy fordítója, így a fordító nevének megszerzése elég egyszerű. +Vagy két lépésben töltjük be az adatokat - először a könyveket, majd a szerzőiket - és utána PHP-ban összerakjuk őket: + +```sql +SELECT * FROM book; +SELECT * FROM author WHERE id IN (1, 2, 3); -- a megszerzett könyvek szerzőinek id-jai +``` + +A második megközelítés valójában hatékonyabb, bár ez meglepő lehet. Az adatok csak egyszer töltődnek be, és jobban felhasználhatók a cache-ben. Pontosan így működik a Nette Database Explorer - mindent a felszín alatt old meg, és elegáns API-t kínál Önnek: + ```php -$book->ref('author', 'translator_id')->name +$books = $explorer->table('book'); +foreach ($books as $book) { + echo 'cím: ' . $book->title; + echo 'írta: ' . $book->author->name; // $book->author egy rekord az 'author' táblából + echo 'fordította: ' . $book->translator?->name; +} ``` -Mindez rendben van, de kissé nehézkes, nem gondolja? Az adatbázis-kutató már tartalmazza az idegen kulcsok definícióit, miért ne használhatná őket automatikusan? Csináljuk meg! -Ha olyan tulajdonságot hívunk meg, amely nem létezik, az ActiveRow megpróbálja a hívó tulajdonság nevét 'van egy' relációként feloldani. Ennek a tulajdonságnak a megszerzése ugyanaz, mint a ref() metódus hívása egyetlen argumentummal. Az egyetlen argumentumot **key**-nek fogjuk hívni. A kulcsot egy adott idegen kulcs relációra fogjuk felbontani. Az átadott kulcsot összevetjük a sor oszlopokkal, és ha egyezik, akkor a megfelelő oszlopon definiált idegen kulcsot használjuk a kapcsolódó céltáblából való adatszerzéshez. Lásd a példát: +Hozzáférés a szülő táblához +--------------------------- + +A szülő táblához való hozzáférés egyszerű. Olyan kapcsolatokról van szó, mint *a könyvnek van szerzője* vagy *a könyvnek lehet fordítója*. A kapcsolódó rekordot az ActiveRow objektum property-jén keresztül érjük el - a neve megegyezik az idegen kulcsot tartalmazó oszlop nevével `id` nélkül: ```php -$book->author->name; -// ugyanaz, mint -$book->ref('author')->name; +$book = $explorer->table('book')->get(1); +echo $book->author->name; // megtalálja a szerzőt az author_id oszlop alapján +echo $book->translator?->name; // megtalálja a fordítót a translator_id alapján ``` -Az ActiveRow példánynak nincs szerző oszlopa. Az összes könyv oszlopban keresünk egyezést a *kulcs*-val. Az egyezés ebben az esetben azt jelenti, hogy az oszlop nevének tartalmaznia kell a kulcsot. A fenti példában tehát a `author_id` oszlop tartalmazza a 'szerző' karakterláncot, ezért a keresés a 'szerző' kulccsal történik. Ha a könyv fordítóját szeretné megkapni, akkor kulcsként használhatja pl. a 'translator' szót, mert a 'translator' kulcs a `translator_id` oszlopra fog illeszkedni. A kulcsillesztési logikáról bővebben a [Joining expressions |#joining-key] fejezetben olvashat. +Amikor hozzáférünk a `$book->author` property-hez, az Explorer a `book` táblában keres egy oszlopot, amelynek neve tartalmazza az `author` stringet (tehát `author_id`). Az ebben az oszlopban lévő érték alapján betölti a megfelelő rekordot az `author` táblából, és `ActiveRow`-ként adja vissza. Hasonlóan működik a `$book->translator` is, amely a `translator_id` oszlopot használja. Mivel a `translator_id` oszlop tartalmazhat `null`-t, a kódban a `?->` operátort használjuk. + +Alternatív utat kínál a `ref()` metódus, amely két argumentumot fogad el, a cél tábla nevét és a kapcsoló oszlop nevét, és egy `ActiveRow` példányt vagy `null`-t ad vissza: ```php -echo $book->title . ': '; -echo $book->author->name; -if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; -} +echo $book->ref('author', 'author_id')->name; // kapcsolat a szerzővel +echo $book->ref('author', 'translator_id')->name; // kapcsolat a fordítóval ``` -Ha több könyvet szeretne lekérdezni, ugyanezt a megközelítést kell alkalmaznia. A Nette Database Explorer egyszerre fogja lekérdezni az összes lekérdezett könyv szerzőit és fordítóit. +A `ref()` metódus akkor hasznos, ha nem lehet a property-n keresztüli hozzáférést használni, mert a tábla tartalmaz egy azonos nevű oszlopot (azaz `author`). Más esetekben a property-n keresztüli hozzáférés használata javasolt, amely olvashatóbb. + +Az Explorer automatikusan optimalizálja az adatbázis-lekérdezéseket. Amikor ciklusban járjuk be a könyveket, és hozzáférünk a kapcsolódó rekordjaikhoz (szerzők, fordítók), az Explorer nem generál lekérdezést minden egyes könyvhöz külön. Ehelyett csak egy SELECT-et hajt végre minden kapcsolattípushoz, ezzel jelentősen csökkentve az adatbázis terhelését. Például: ```php $books = $explorer->table('book'); foreach ($books as $book) { echo $book->title . ': '; echo $book->author->name; - if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; - } + echo $book->translator?->name; } ``` -A kód csak ezt a 3 lekérdezést fogja lefuttatni: +Ez a kód csak ezt a három villámgyors lekérdezést hívja meg az adatbázisba: + ```sql SELECT * FROM `book`; -SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- ids of fetched books from author_id column -SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- ids of fetched books from translator_id column +SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- id-k a kiválasztott könyvek author_id oszlopából +SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- id-k a kiválasztott könyvek translator_id oszlopából ``` +.[note] +A kapcsoló oszlop megtalálásának logikáját a [Conventions |api:Nette\Database\Conventions] implementációja határozza meg. Javasoljuk a [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions] használatát, amely elemzi az idegen kulcsokat, és lehetővé teszi a táblák közötti meglévő kapcsolatokkal való egyszerű munkát. -Has Many Relation .[#toc-has-many-relation] -------------------------------------------- -A 'Has many' reláció a 'has one' reláció fordítottja. A szerző *már* sok* könyvet írt. A szerző *mindegyik* könyvet lefordította. Amint láthatjuk, ez a fajta reláció egy kicsit nehezebb, mivel a reláció 'megnevezett' ('írt', 'fordított'). Az ActiveRow példány rendelkezik a `related()` metódussal, amely a kapcsolódó bejegyzések tömbjét adja vissza. A bejegyzések szintén ActiveRow példányok. Lásd az alábbi példát: +Hozzáférés a gyermek táblához +----------------------------- + +A gyermek táblához való hozzáférés fordított irányban működik. Most azt kérdezzük, *milyen könyveket írt ez a szerző* vagy *fordított ez a fordító*. Ehhez a lekérdezéstípushoz a `related()` metódust használjuk, amely egy `Selection`-t ad vissza a kapcsolódó rekordokkal. Nézzünk egy példát: ```php -$author = $explorer->table('author')->get(11); -echo $author->name . ' has written:'; +$author = $explorer->table('author')->get(1); +// Kiírja a szerző összes könyvét foreach ($author->related('book.author_id') as $book) { - echo $book->title; + echo "Írta: $book->title"; } -echo 'and translated:'; +// Kiírja az összes könyvet, amelyet a szerző fordított foreach ($author->related('book.translator_id') as $book) { - echo $book->title; + echo "Fordította: $book->title"; } ``` -A `related()` módszer elfogadja a két argumentumként vagy egy argumentumként, ponttal összekötve átadott teljes join leírást. Az első argumentum a céltábla, a második a céloszlop. +A `related()` metódus a kapcsolat leírását egyetlen argumentumként pont-jelöléssel vagy két különálló argumentumként fogadja el: ```php -$author->related('book.translator_id'); -// ugyanaz, mint -$author->related('book', 'translator_id'); +$author->related('book.translator_id'); // egy argumentum +$author->related('book', 'translator_id'); // két argumentum ``` -Használhatja a Nette Database Explorer idegen kulcsokon alapuló heurisztikáját, és csak a **key** argumentumot adhatja meg. A kulcsot az aktuális táblára (`author` tábla) mutató összes idegen kulccsal összeveti a rendszer. Ha van egyezés, a Nette Database Explorer ezt az idegen kulcsot fogja használni, ellenkező esetben [Nette\InvalidArgumentException |api:Nette\InvalidArgumentException] vagy [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException] hibát fog dobni. A kulcsillesztési logikáról bővebben a [Joining expressions |#joining-key] fejezetben olvashat. +Az Explorer képes automatikusan felismerni a helyes kapcsoló oszlopot a szülő tábla neve alapján. Ebben az esetben a `book.author_id` oszlopon keresztül kapcsolódik, mivel a forrástábla neve `author`: -Természetesen az összes lehívott szerzőhöz meghívhatja a kapcsolódó metódusokat, a Nette Database Explorer ismét egyszerre fogja lehívni a megfelelő könyveket. +```php +$author->related('book'); // a book.author_id-t használja +``` + +Ha több lehetséges kapcsolat létezne, az Explorer [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException] kivételt dob. + +A `related()` metódust természetesen használhatjuk több rekord ciklusban történő bejárásakor is, és az Explorer ebben az esetben is automatikusan optimalizálja a lekérdezéseket: ```php $authors = $explorer->table('author'); foreach ($authors as $author) { - echo $author->name . ' has written:'; + echo $author->name . ' írta:'; foreach ($author->related('book') as $book) { - $book->title; + echo $book->title; } } ``` -A fenti példa csak két lekérdezést futtat: +Ez a kód csak két villámgyors SQL lekérdezést generál: ```sql SELECT * FROM `author`; -SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- ids of fetched authors +SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- a kiválasztott szerzők id-jai +``` + + +Több-a-többhöz kapcsolat +------------------------ + +A több-a-többhöz (M:N) kapcsolathoz szükség van egy kapcsolótábla létezésére (esetünkben `book_tag`), amely két idegen kulcsot tartalmazó oszlopot (`book_id`, `tag_id`) tartalmaz. Ezen oszlopok mindegyike az összekapcsolt táblák egyikének elsődleges kulcsára hivatkozik. A kapcsolódó adatok megszerzéséhez először a kapcsolótábla rekordjait szerezzük meg a `related('book_tag')` segítségével, majd tovább haladunk a céladatokhoz: + +```php +$book = $explorer->table('book')->get(1); +// kiírja a könyvhöz rendelt címkék neveit +foreach ($book->related('book_tag') as $bookTag) { + echo $bookTag->tag->name; // kiírja a címke nevét a kapcsolótáblán keresztül +} + +$tag = $explorer->table('tag')->get(1); +// vagy fordítva: kiírja az ezzel a címkével megjelölt könyvek neveit +foreach ($tag->related('book_tag') as $bookTag) { + echo $bookTag->book->title; // kiírja a könyv nevét +} ``` +Az Explorer ismét optimalizálja az SQL lekérdezéseket hatékony formába: + +```sql +SELECT * FROM `book`; +SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 2, ...)); -- a kiválasztott könyvek id-jai +SELECT * FROM `tag` WHERE (`tag`.`id` IN (1, 2, ...)); -- a book_tag-ban talált címkék id-jai +``` -Az Explorer manuális létrehozása .[#toc-creating-explorer-manually] -=================================================================== -Az adatbázis-kapcsolat az alkalmazás konfigurációjának segítségével hozható létre. Ilyenkor létrejön egy `Nette\Database\Explorer` szolgáltatás, amelyet függőségként át lehet adni a DI konténer segítségével. +Lekérdezés kapcsolódó táblákon keresztül +---------------------------------------- -Ha azonban a Nette Database Explorer önálló eszközként használatos, akkor a `Nette\Database\Explorer` objektum példányát kézzel kell létrehozni. +A `where()`, `select()`, `order()` és `group()` metódusokban speciális jelöléseket használhatunk más táblák oszlopaihoz való hozzáféréshez. Az Explorer automatikusan létrehozza a szükséges JOIN-okat. + +**Pont-jelölés** (`szülő_tábla.oszlop`) a gyermek tábla szemszögéből nézett 1:N kapcsolathoz használatos: ```php -// $storage implementálja a Nette\Caching\Storage: -$storage = new Nette\Caching\Storages\FileStorage($tempDir); -$connection = new Nette\Database\Connection($dsn, $user, $password); -$structure = new Nette\Database\Structure($connection, $storage); -$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); -$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +$books = $explorer->table('book'); + +// Megtalálja azokat a könyveket, amelyek szerzőjének neve 'Jon'-nal kezdődik +$books->where('author.name LIKE ?', 'Jon%'); + +// Rendezi a könyveket a szerző neve szerint csökkenő sorrendben +$books->order('author.name DESC'); + +// Kiírja a könyv címét és a szerző nevét +$books->select('book.title, author.name'); ``` + +**Kettőspont-jelölés** (`:gyermek_tábla.oszlop`) a szülő tábla szemszögéből nézett 1:N kapcsolathoz használatos: + +```php +$authors = $explorer->table('author'); + +// Megtalálja azokat a szerzőket, akik 'PHP'-t tartalmazó című könyvet írtak +$authors->where(':book.title LIKE ?', '%PHP%'); + +// Megszámolja a könyvek számát minden szerzőhöz +$authors->select('*, COUNT(:book.id) AS book_count') + ->group('author.id'); +``` + +A fenti példában a kettőspont-jelöléssel (`:book.title`) nincs megadva az idegen kulcs oszlopa. Az Explorer automatikusan felismeri a helyes oszlopot a szülő tábla neve alapján. Ebben az esetben a `book.author_id` oszlopon keresztül kapcsolódik, mivel a forrástábla neve `author`. Ha több lehetséges kapcsolat létezne, az Explorer [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException] kivételt dob. + +A kapcsoló oszlopot explicit módon meg lehet adni zárójelben: + +```php +// Megtalálja azokat a szerzőket, akik 'PHP'-t tartalmazó című könyvet fordítottak +$authors->where(':book(translator_id).title LIKE ?', '%PHP%'); +``` + +A jelölések láncolhatók több táblán keresztüli hozzáféréshez: + +```php +// Megtalálja a 'PHP' címkével megjelölt könyvek szerzőit +$authors->where(':book:book_tag.tag.name', 'PHP') + ->group('author.id'); +``` + + +JOIN feltételek bővítése +------------------------ + +A `joinWhere()` metódus kibővíti azokat a feltételeket, amelyeket a táblák összekapcsolásakor az SQL-ben az `ON` kulcsszó után adunk meg. + +Tegyük fel, hogy egy adott fordító által fordított könyveket szeretnénk megtalálni: + +```php +// Megtalálja a 'David' nevű fordító által fordított könyveket +$books = $explorer->table('book') + ->joinWhere('translator', 'translator.name', 'David'); +// LEFT JOIN author translator ON book.translator_id = translator.id AND (translator.name = 'David') +``` + +A `joinWhere()` feltételben ugyanazokat a konstrukciókat használhatjuk, mint a `where()` metódusban - operátorokat, helyettesítő kérdőjeleket, értékek tömbjét vagy SQL kifejezéseket. + +Összetettebb lekérdezésekhez több JOIN-nal definiálhatunk tábla aliasokat: + +```php +$tags = $explorer->table('tag') + ->joinWhere(':book_tag.book.author', 'book_author.born < ?', 1950) + ->alias(':book_tag.book.author', 'book_author'); +// LEFT JOIN `book_tag` ON `tag`.`id` = `book_tag`.`tag_id` +// LEFT JOIN `book` ON `book_tag`.`book_id` = `book`.`id` +// LEFT JOIN `author` `book_author` ON `book`.`author_id` = `book_author`.`id` +// AND (`book_author`.`born` < 1950) +``` + +Figyelje meg, hogy míg a `where()` metódus feltételeket ad hozzá a `WHERE` záradékhoz, a `joinWhere()` metódus kibővíti a feltételeket az `ON` záradékban a táblák összekapcsolásakor. diff --git a/database/hu/guide.texy b/database/hu/guide.texy new file mode 100644 index 0000000000..b7e2e44944 --- /dev/null +++ b/database/hu/guide.texy @@ -0,0 +1,216 @@ +Nette Database +************** + +.[perex] +A Nette Database egy erőteljes és elegáns adatbázis réteg PHP számára, hangsúlyt fektetve az egyszerűségre és az okos funkciókra. Kétféle módot kínál az adatbázissal való munkára - [Explorer |Explorer] az alkalmazások gyors fejlesztéséhez, vagy [SQL megközelítés |SQL way] a lekérdezésekkel való közvetlen munkához. + +<div class="grid gap-3"> +<div> + + +[SQL megközelítés |SQL way] +=========================== +- Biztonságos paraméterezett lekérdezések +- Pontos ellenőrzés az SQL lekérdezések formája felett +- Amikor komplex lekérdezéseket ír haladó funkciókkal +- Optimalizálja a teljesítményt specifikus SQL funkciók segítségével + +</div> + +<div> + + +[Explorer |Explorer] +==================== +- Gyorsan fejleszthet SQL írása nélkül +- Intuitív munka a táblák közötti kapcsolatokkal +- Értékelni fogja a lekérdezések automatikus optimalizálását +- Alkalmas gyors és kényelmes adatbázis-kezelésre + +</div> + +</div> + + +Telepítés +========= + +A könyvtárat a [Composer|best-practices:composer] eszközzel töltheti le és telepítheti: + +```shell +composer require nette/database +``` + + +Támogatott adatbázisok +====================== + +A Nette Database a következő adatbázisokat támogatja: + +|* Adatbázis szerver |* DSN név |* Támogatás az Explorerben +|---------------------|-------------|----------------------- +| MySQL (>= 5.1) | mysql | IGEN +| PostgreSQL (>= 9.0) | pgsql | IGEN +| Sqlite 3 (>= 3.8) | sqlite | IGEN +| Oracle | oci | - +| MS SQL (PDO_SQLSRV) | sqlsrv | IGEN +| MS SQL (PDO_DBLIB) | mssql | - +| ODBC | odbc | - + + +Két megközelítés az adatbázishoz +================================ + +A Nette Database választási lehetőséget kínál: vagy közvetlenül írhat SQL lekérdezéseket (SQL megközelítés), vagy hagyhatja, hogy automatikusan generálódjanak (Explorer). Nézzük meg, hogyan oldják meg mindkét megközelítéssel ugyanazokat a feladatokat: + +[SQL megközelítés|sql way] - SQL lekérdezések + +```php +// rekord beszúrása +$database->query('INSERT INTO books', [ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// rekordok lekérése: könyvek szerzői +$result = $database->query(' + SELECT authors.*, COUNT(books.id) AS books_count + FROM authors + LEFT JOIN books ON authors.id = books.author_id + WHERE authors.active = 1 + GROUP BY authors.id +'); + +// listázás (nem optimális, N további lekérdezést generál) +foreach ($result as $author) { + $books = $database->query(' + SELECT * FROM books + WHERE author_id = ? + ORDER BY published_at DESC + ', $author->id); + + echo "Szerző $author->name írt $author->books_count könyvet:\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +[Explorer megközelítés |explorer] - automatikus SQL generálás + +```php +// rekord beszúrása +$database->table('books')->insert([ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// rekordok lekérése: könyvek szerzői +$authors = $database->table('authors') + ->where('active', 1); + +// listázás (automatikusan csak 2 optimalizált lekérdezést generál) +foreach ($authors as $author) { + $books = $author->related('books') + ->order('published_at DESC'); + + echo "Szerző $author->name írt {$books->count()} könyvet:\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +Az Explorer megközelítés automatikusan generálja és optimalizálja az SQL lekérdezéseket. A megadott példában az SQL megközelítés N+1 lekérdezést generál (egyet a szerzőkhöz, majd egyet minden szerző könyveihez), míg az Explorer automatikusan optimalizálja a lekérdezéseket, és csak kettőt hajt végre - egyet a szerzőkhöz és egyet az összes könyvükhöz. + +Mindkét megközelítés tetszés szerint kombinálható az alkalmazásban, igény szerint. + + +Csatlakozás és konfiguráció +=========================== + +Az adatbázishoz való csatlakozáshoz elegendő létrehozni egy [api:Nette\Database\Connection] osztálypéldányt: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password); +``` + +A `$dsn` (data source name) paraméter ugyanaz, [amit a PDO használ |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], pl. `host=127.0.0.1;dbname=test`. Hiba esetén `Nette\Database\ConnectionException` kivételt dob. + +Azonban egy ügyesebb módszert kínál az [alkalmazáskonfiguráció |configuration], ahová elegendő hozzáadni egy `database` szekciót, és létrejönnek a szükséges objektumok, valamint az adatbázis panel a [Tracy |tracy:] sávban. + +```neon +database: + dsn: 'mysql:host=127.0.0.1;dbname=test' + user: root + password: password +``` + +Ezután a kapcsolat objektumot [szolgáltatásként kapjuk meg a DI konténerből |dependency-injection:passing-dependencies], pl.: + +```php +class Model +{ + public function __construct( + // vagy Nette\Database\Explorer + private Nette\Database\Connection $database, + ) { + } +} +``` + +További információk az [adatbázis konfigurációjáról |configuration]. + + +Explorer manuális létrehozása +----------------------------- + +Ha nem használja a Nette DI konténert, manuálisan is létrehozhat egy `Nette\Database\Explorer` példányt: + +```php +// csatlakozás az adatbázishoz +$connection = new Nette\Database\Connection('mysql:host=127.0.0.1;dbname=mydatabase', 'user', 'password'); +// tároló a cache-hez, implementálja a Nette\Caching\Storage-ot, pl.: +$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp/dir'); +// gondoskodik az adatbázis struktúra reflexiójáról +$structure = new Nette\Database\Structure($connection, $storage); +// definiálja a táblanevek, oszlopnevek és idegen kulcsok leképezési szabályait +$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); +$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +``` + + +Kapcsolatkezelés +================ + +A `Connection` objektum létrehozásakor a csatlakozás automatikusan megtörténik. Ha késleltetni szeretné a csatlakozást, használja a lazy módot - ezt a [konfigurációban |configuration] a `lazy` beállításával, vagy így kapcsolhatja be: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password, ['lazy' => true]); +``` + +A kapcsolat kezeléséhez használja a `connect()`, `disconnect()` és `reconnect()` metódusokat. +- `connect()` létrehozza a kapcsolatot, ha még nem létezik, és `Nette\Database\ConnectionException` kivételt dobhat. +- `disconnect()` megszakítja az aktuális adatbázis-kapcsolatot. +- `reconnect()` megszakítja, majd újra csatlakoztatja az adatbázishoz. Ez a metódus szintén `Nette\Database\ConnectionException` kivételt dobhat. + +Ezenkívül figyelheti a csatlakozással kapcsolatos eseményeket az `onConnect` esemény segítségével, amely egy callback tömb, amely az adatbázissal való kapcsolat létrejötte után hívódik meg. + +```php +// az adatbázishoz való csatlakozás után fut le +$database->onConnect[] = function($database) { + echo "Csatlakozva az adatbázishoz"; +}; +``` + + +Tracy Debug Bar +=============== + +Ha [Tracy-t |tracy:] használ, a Database panel automatikusan aktiválódik a Debug sávban, amely megjeleníti az összes végrehajtott lekérdezést, azok paramétereit, végrehajtási idejét és a kódban való meghívásuk helyét. + +[* db-panel.webp *] diff --git a/database/hu/mapping.texy b/database/hu/mapping.texy new file mode 100644 index 0000000000..1e96f83fbb --- /dev/null +++ b/database/hu/mapping.texy @@ -0,0 +1,55 @@ +Típuskonverzió +************** + +.[perex] +A Nette Database automatikusan konvertálja az adatbázisból visszaadott értékeket a megfelelő PHP típusokra. + + +Dátum és idő +------------ + +Az időadatok `Nette\Utils\DateTime` objektumokká konvertálódnak. Ha azt szeretné, hogy az időadatok immutable `Nette\Database\DateTime` objektumokká konvertálódjanak, állítsa a `newDateTime` opciót true-ra a [konfigurációban |configuration]. + +```php +$row = $database->fetch('SELECT created_at FROM articles'); +echo $row->created_at instanceof DateTime; // true +echo $row->created_at->format('Y. n. j.'); +``` + +MySQL esetén a `TIME` adattípust `DateInterval` objektumokká konvertálja. + + +Logikai értékek +--------------- + +A logikai értékek automatikusan `true`-ra vagy `false`-ra konvertálódnak. MySQL esetén a `TINYINT(1)` konvertálódik, ha a [konfigurációban |configuration] beállítjuk a `convertBoolean`-t. + +```php +$row = $database->fetch('SELECT is_published FROM articles'); +echo gettype($row->is_published); // 'boolean' +``` + + +Numerikus értékek +----------------- + +A numerikus értékek `int`-re vagy `float`-ra konvertálódnak az adatbázis oszlopának típusa szerint: + +```php +$row = $database->fetch('SELECT id, price FROM products'); +echo gettype($row->id); // integer +echo gettype($row->price); // float +``` + + +Egyéni normalizálás +------------------- + +A `setRowNormalizer(?callable $normalizer)` metódussal beállíthat egy egyéni funkciót az adatbázisból származó sorok átalakítására. Ez hasznos lehet például az adattípusok automatikus konvertálásához. + +```php +$database->setRowNormalizer(function(array $row, ResultSet $resultSet): array { + // itt történik a típuskonverzió + return $row; +}); +``` diff --git a/database/hu/reflection.texy b/database/hu/reflection.texy new file mode 100644 index 0000000000..ad380ba29b --- /dev/null +++ b/database/hu/reflection.texy @@ -0,0 +1,125 @@ +Struktúra reflexió +****************** + +.{data-version:3.2.1} +A Nette Database eszközöket biztosít az adatbázis struktúrájának introspekciójához a [api:Nette\Database\Reflection] osztály segítségével. Ez lehetővé teszi információk lekérését táblákról, oszlopokról, indexekről és idegen kulcsokról. A reflexiót használhatja sémák generálásához, rugalmas, adatbázissal dolgozó alkalmazások létrehozásához vagy általános adatbázis-eszközök készítéséhez. + +A reflexiós objektumot az adatbázis-kapcsolat példányából kapjuk meg: + +```php +$reflection = $database->getReflection(); +``` + + +Táblák lekérése +--------------- + +A `$reflection->tables` readonly property tartalmazza az adatbázis összes táblájának asszociatív tömbjét: + +```php +// Az összes tábla nevének kiírása +foreach ($reflection->tables as $name => $table) { + echo $name . "\n"; +} +``` + +Két további metódus is rendelkezésre áll: + +```php +// Tábla létezésének ellenőrzése +if ($reflection->hasTable('users')) { + echo "A users tábla létezik"; +} + +// Visszaadja a tábla objektumot; ha nem létezik, kivételt dob +$table = $reflection->getTable('users'); +``` + + +Információ a tábláról +--------------------- + +A táblát egy [Table|api:Nette\Database\Reflection\Table] objektum reprezentálja, amely a következő readonly property-ket biztosítja: + +- `$name: string` – tábla neve +- `$view: bool` – hogy nézetről van-e szó +- `$fullName: ?string` – a tábla teljes neve, beleértve a sémát (ha létezik) +- `$columns: array<string, Column>` – a tábla oszlopainak asszociatív tömbje +- `$indexes: Index[]` – a tábla indexeinek tömbje +- `$primaryKey: ?Index` – a tábla elsődleges kulcsa vagy null +- `$foreignKeys: ForeignKey[]` – a tábla idegen kulcsainak tömbje + + +Oszlopok +-------- + +A tábla `columns` property-je az oszlopok asszociatív tömbjét adja meg, ahol a kulcs az oszlop neve, az érték pedig egy [Column|api:Nette\Database\Reflection\Column] példány a következő property-kkel: + +- `$name: string` – oszlop neve +- `$table: ?Table` – referencia az oszlop táblájára +- `$nativeType: string` – natív adatbázis típus +- `$size: ?int` – a típus mérete/hossza +- `$nullable: bool` – hogy az oszlop tartalmazhat-e NULL-t +- `$default: mixed` – az oszlop alapértelmezett értéke +- `$autoIncrement: bool` – hogy az oszlop auto-increment-e +- `$primary: bool` – hogy része-e az elsődleges kulcsnak +- `$vendor: array` – további, az adott adatbázis-rendszerre specifikus metaadatok + +```php +foreach ($table->columns as $name => $column) { + echo "Oszlop: $name\n"; + echo "Típus: {$column->nativeType}\n"; + echo "Nullable: " . ($column->nullable ? 'Igen' : 'Nem') . "\n"; +} +``` + + +Indexek +------- + +A tábla `indexes` property-je az indexek tömbjét adja meg, ahol minden index egy [Index|api:Nette\Database\Reflection\Index] példány a következő property-kkel: + +- `$columns: Column[]` – az indexet alkotó oszlopok tömbje +- `$unique: bool` – hogy az index egyedi-e +- `$primary: bool` – hogy elsődleges kulcsról van-e szó +- `$name: ?string` – az index neve + +A tábla elsődleges kulcsát a `primaryKey` property segítségével lehet lekérni, amely vagy egy `Index` objektumot ad vissza, vagy `null`-t, ha a táblának nincs elsődleges kulcsa. + +```php +// Indexek kiírása +foreach ($table->indexes as $index) { + $columns = implode(', ', array_map(fn($col) => $col->name, $index->columns)); + echo "Index" . ($index->name ? " {$index->name}" : '') . ":\n"; + echo " Oszlopok: $columns\n"; + echo " Unique: " . ($index->unique ? 'Igen' : 'Nem') . "\n"; +} + +// Elsődleges kulcs kiírása +if ($primaryKey = $table->primaryKey) { + $columns = implode(', ', array_map(fn($col) => $col->name, $primaryKey->columns)); + echo "Elsődleges kulcs: $columns\n"; +} +``` + + +Idegen kulcsok +-------------- + +A tábla `foreignKeys` property-je az idegen kulcsok tömbjét adja meg, ahol minden idegen kulcs egy [ForeignKey|api:Nette\Database\Reflection\ForeignKey] példány a következő property-kkel: + +- `$foreignTable: Table` – a hivatkozott tábla +- `$localColumns: Column[]` – a helyi oszlopok tömbje +- `$foreignColumns: Column[]` – a hivatkozott oszlopok tömbje +- `$name: ?string` – az idegen kulcs neve + +```php +// Idegen kulcsok kiírása +foreach ($table->foreignKeys as $fk) { + $localCols = implode(', ', array_map(fn($col) => $col->name, $fk->localColumns)); + $foreignCols = implode(', ', array_map(fn($col) => $col->name, $fk->foreignColumns)); + + echo "FK" . ($fk->name ? " {$fk->name}" : '') . ":\n"; + echo " $localCols -> {$fk->foreignTable->name}($foreignCols)\n"; +} +``` diff --git a/database/hu/security.texy b/database/hu/security.texy new file mode 100644 index 0000000000..1922a972d3 --- /dev/null +++ b/database/hu/security.texy @@ -0,0 +1,185 @@ +Biztonsági kockázatok +********************* + +<div class=perex> + +Az adatbázis gyakran tartalmaz érzékeny adatokat és lehetővé teszi veszélyes műveletek végrehajtását. A Nette Database biztonságos használatához kulcsfontosságú: + +- Megérteni a különbséget a biztonságos és a nem biztonságos API között +- Paraméterezett lekérdezéseket használni +- Helyesen validálni a bemeneti adatokat + +</div> + + +Mi az SQL Injection? +==================== + +Az SQL injection a legkomolyabb biztonsági kockázat az adatbázisokkal való munka során. Akkor keletkezik, ha a felhasználótól származó, nem kezelt bemenet az SQL lekérdezés részévé válik. A támadó saját SQL parancsokat illeszthet be, és ezzel: +- Jogosulatlan hozzáférést szerezhet az adatokhoz +- Módosíthatja vagy törölheti az adatokat az adatbázisban +- Megkerülheti az authentikációt + +```php +// ❌ VESZÉLYES KÓD - sebezhető az SQL injection-nel szemben +$database->query("SELECT * FROM users WHERE name = '$_GET[name]'"); + +// A támadó például megadhatja a következő értéket: ' OR '1'='1 +// Az eredményül kapott lekérdezés ez lesz: SELECT * FROM users WHERE name = '' OR '1'='1' +// Ami visszaadja az összes felhasználót +``` + +Ugyanez vonatkozik a [Database Explorer |explorer]-re is: + +```php +// ❌ VESZÉLYES KÓD - sebezhető az SQL injection-nel szemben +$table->where('name = ' . $_GET['name']); +$table->where("name = '$_GET[name]'"); +``` + + +Paraméterezett lekérdezések +=========================== + +Az SQL injection elleni alapvető védekezés a paraméterezett lekérdezések használata. A Nette Database több módszert is kínál ezek használatára. + +A legegyszerűbb módszer a **kérdőjeles helyettesítők** használata: + +```php +// ✅ Biztonságos paraméterezett lekérdezés +$database->query('SELECT * FROM users WHERE name = ?', $name); + +// ✅ Biztonságos feltétel az Explorerben +$table->where('name = ?', $name); +``` + +Ez érvényes minden további metódusra a [Database Explorerben |explorer], amelyek lehetővé teszik kifejezések beillesztését kérdőjeles helyettesítőkkel és paraméterekkel. + +Az INSERT, UPDATE parancsokhoz vagy a WHERE záradékhoz az értékeket tömbben adhatjuk át: + +```php +// ✅ Biztonságos INSERT +$database->query('INSERT INTO users', [ + 'name' => $name, + 'email' => $email, +]); + +// ✅ Biztonságos INSERT az Explorerben +$table->insert([ + 'name' => $name, + 'email' => $email, +]); +``` + + +Paraméterértékek validálása +=========================== + +A paraméterezett lekérdezések a biztonságos adatbázis-kezelés alapkövei. Azonban az értékeknek, amelyeket beléjük illesztünk, több ellenőrzési szinten kell átesniük: + + +Típusellenőrzés +--------------- + +**A legfontosabb a paraméterek helyes adattípusának biztosítása** - ez szükséges feltétele a Nette Database biztonságos használatának. Az adatbázis feltételezi, hogy minden bemeneti adat helyes adattípussal rendelkezik, amely megfelel az adott oszlopnak. + +Például, ha az előző példákban a `$name` váratlanul egy tömb lenne egy string helyett, a Nette Database megpróbálná az összes elemét beilleszteni az SQL lekérdezésbe, ami hibához vezetne. Ezért **soha ne használjon** validálatlan adatokat a `$_GET`, `$_POST` vagy `$_COOKIE` tömbökből közvetlenül az adatbázis lekérdezésekben. + + +Formátumellenőrzés +------------------ + +A második ellenőrzési szinten az adatok formátumát ellenőrizzük - például, hogy a stringek UTF-8 kódolásúak-e, és hosszuk megfelel-e az oszlop definíciójának, vagy hogy a numerikus értékek az adott oszlop adattípusához megengedett tartományban vannak-e. + +Ezen a validálási szinten részben magára az adatbázisra is támaszkodhatunk - sok adatbázis elutasítja az érvénytelen adatokat. Azonban a viselkedés eltérő lehet, némelyik csendben levághatja a hosszú stringeket, vagy a tartományon kívüli számokat. + + +Domain ellenőrzés +----------------- + +A harmadik szint az alkalmazásspecifikus logikai ellenőrzéseket jelenti. Például annak ellenőrzése, hogy a select boxokból származó értékek megfelelnek-e a kínált lehetőségeknek, hogy a számok a várt tartományban vannak-e (pl. életkor 0-150 év), vagy hogy az értékek közötti kölcsönös függőségek értelmesek-e. + + +Ajánlott validálási módszerek +----------------------------- + +- Használjon [Nette Űrlapokat |forms:], amelyek automatikusan biztosítják az összes bemenet helyes validálását +- Használjon [Presentereket |application:] és adja meg az adattípusokat a paramétereknél az `action*()` és `render*()` metódusokban +- Vagy implementáljon saját validálási réteget standard PHP eszközökkel, mint például a `filter_var()` + + +Biztonságos munka az oszlopokkal +================================ + +Az előző szakaszban megmutattuk, hogyan kell helyesen validálni a paraméterértékeket. Azonban az SQL lekérdezésekben tömbök használatakor ugyanolyan figyelmet kell fordítanunk a kulcsaikra is. + +```php +// ❌ VESZÉLYES KÓD - a tömb kulcsai nincsenek kezelve +$database->query('INSERT INTO users', $_POST); +``` + +Az INSERT és UPDATE parancsoknál ez alapvető biztonsági hiba - a támadó bármilyen oszlopot beilleszthet vagy módosíthat az adatbázisban. Például beállíthatná az `is_admin = 1`-et, vagy tetszőleges adatokat illeszthetne be érzékeny oszlopokba (ún. Mass Assignment Vulnerability). + +A WHERE feltételekben ez még veszélyesebb, mivel operátorokat tartalmazhatnak: + +```php +// ❌ VESZÉLYES KÓD - a tömb kulcsai nincsenek kezelve +$_POST['salary >'] = 100000; +$database->query('SELECT * FROM users WHERE', $_POST); +// végrehajtja a WHERE (`salary` > 100000) lekérdezést +``` + +A támadó ezt a megközelítést használhatja a munkavállalók fizetésének szisztematikus kiderítésére. Például elkezdheti a 100 000 feletti fizetések lekérdezésével, majd az 50 000 alattiakkal, és a tartomány fokozatos szűkítésével felfedheti az összes munkavállaló hozzávetőleges fizetését. Ezt a támadástípust SQL enumeration-nek nevezik. + +A `where()` és `whereOr()` metódusok még [sokkal rugalmasabbak |explorer#where], és támogatják az SQL kifejezéseket, beleértve az operátorokat és függvényeket a kulcsokban és értékekben. Ez lehetőséget ad a támadónak SQL injection végrehajtására: + +```php +// ❌ VESZÉLYES KÓD - a támadó saját SQL-t illeszthet be +$_POST = ['0) UNION SELECT name, salary FROM users WHERE (1']; +$table->where($_POST); +// végrehajtja a WHERE (0) UNION SELECT name, salary FROM users WHERE (1) lekérdezést +``` + +Ez a támadás lezárja az eredeti feltételt a `0)` segítségével, saját `SELECT`-et csatol a `UNION` segítségével, hogy érzékeny adatokat szerezzen a `users` táblából, és szintaktikailag helyes lekérdezést zár le a `WHERE (1)` segítségével. + + +Oszlopok Whitelistje +-------------------- + +Az oszlopnevekkel való biztonságos munkához szükségünk van egy mechanizmusra, amely biztosítja, hogy a felhasználó csak az engedélyezett oszlopokkal dolgozhasson, és ne tudjon sajátokat hozzáadni. Megpróbálhatnánk észlelni és blokkolni a veszélyes oszlopneveket (blacklist), de ez a megközelítés megbízhatatlan - a támadó mindig kitalálhat egy új módszert a veszélyes oszlopnév beírására, amit nem láttunk előre. + +Ezért sokkal biztonságosabb megfordítani a logikát, és explicit módon definiálni az engedélyezett oszlopok listáját (whitelist): + +```php +// Oszlopok, amelyeket a felhasználó módosíthat +$allowedColumns = ['name', 'email', 'active']; + +// Eltávolítjuk az összes nem engedélyezett oszlopot a bemenetből +$filteredData = array_intersect_key($userData, array_flip($allowedColumns)); + +// ✅ Most már biztonságosan használhatjuk a lekérdezésekben, például: +$database->query('INSERT INTO users', $filteredData); +$table->update($filteredData); +$table->where($filteredData); +``` + + +Dinamikus azonosítók +==================== + +Dinamikus tábla- és oszlopnevekhez használja a `?name` helyettesítő szimbólumot. Ez biztosítja az azonosítók helyes escapelését az adott adatbázis szintaxisa szerint (pl. backtickek használatával MySQL-ben): + +```php +// ✅ Megbízható azonosítók biztonságos használata +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name', $column, $table); +// Eredmény MySQL-ben: SELECT `name` FROM `users` +``` + +Fontos: a `?name` szimbólumot csak az alkalmazás kódjában definiált, megbízható értékekhez használja. Felhasználótól származó értékekhez használja újra a [whitelistet |#Oszlopok Whitelistje]. Ellenkező esetben biztonsági kockázatoknak teszi ki magát: + +```php +// ❌ VESZÉLYES - soha ne használjon felhasználói bemenetet +$database->query('SELECT ?name FROM users', $_GET['column']); +``` diff --git a/database/hu/sql-way.texy b/database/hu/sql-way.texy new file mode 100644 index 0000000000..ff318344a3 --- /dev/null +++ b/database/hu/sql-way.texy @@ -0,0 +1,513 @@ +SQL megközelítés +**************** + +.[perex] +A Nette Database két utat kínál: írhat SQL lekérdezéseket saját maga (SQL megközelítés), vagy hagyhatja, hogy automatikusan generálódjanak (lásd [Explorer |explorer]). Az SQL megközelítés teljes ellenőrzést biztosít a lekérdezések felett, miközben garantálja azok biztonságos összeállítását. + +.[note] +Az adatbázis csatlakozásának és konfigurálásának részleteit a [Csatlakozás és konfiguráció |guide#Csatlakozás és konfiguráció] fejezetben találja. + + +Alapvető lekérdezés +=================== + +Az adatbázis lekérdezéséhez a `query()` metódus szolgál. Ez egy [ResultSet |api:Nette\Database\ResultSet] objektumot ad vissza, amely a lekérdezés eredményét reprezentálja. Hiba esetén a metódus [kivételt dob|exceptions]. A lekérdezés eredményét `foreach` ciklussal járhatjuk be, vagy használhatunk néhány [segédfüggvényt |#Adatlekérés]. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; +} +``` + +Az értékek biztonságos beillesztéséhez az SQL lekérdezésekbe paraméterezett lekérdezéseket használunk. A Nette Database ezt maximálisan egyszerűvé teszi - elegendő az SQL lekérdezés után egy vesszőt és az értéket hozzáadni: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Több paraméter esetén kétféle írásmód lehetséges. Vagy "átszőheti" az SQL lekérdezést paraméterekkel: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age); +``` + +Vagy először megírhatja a teljes SQL lekérdezést, majd csatolhatja az összes paramétert: + +```php +$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age); +``` + + +Védelem az SQL injection ellen +============================== + +Miért fontos paraméterezett lekérdezéseket használni? Mert megvédenek az SQL injection nevű támadástól, amely során a támadó saját SQL parancsokat csempészhetne be, és ezzel adatokat szerezhetne vagy károsíthatna az adatbázisban. + +.[warning] +**Soha ne illesszen be változókat közvetlenül az SQL lekérdezésbe!** Mindig használjon paraméterezett lekérdezéseket, amelyek megvédenek az SQL injection ellen. + +```php +// ❌ VESZÉLYES KÓD - sebezhető az SQL injection-nel szemben +$database->query("SELECT * FROM users WHERE name = '$name'"); + +// ✅ Biztonságos paraméterezett lekérdezés +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Ismerkedjen meg a [lehetséges biztonsági kockázatokkal |security]. + + +Lekérdezési technikák +===================== + + +WHERE feltételek +---------------- + +A WHERE feltételeket asszociatív tömbként írhatja le, ahol a kulcsok az oszlopnevek, az értékek pedig az összehasonlítandó adatok. A Nette Database automatikusan kiválasztja a legmegfelelőbb SQL operátort az érték típusa alapján. + +```php +$database->query('SELECT * FROM users WHERE', [ + 'name' => 'John', + 'active' => true, +]); +// WHERE `name` = 'John' AND `active` = 1 +``` + +A kulcsban explicit módon is megadhatja az összehasonlítási operátort: + +```php +$database->query('SELECT * FROM users WHERE', [ + 'age >' => 25, // a > operátort használja + 'name LIKE' => '%John%', // a LIKE operátort használja + 'email NOT LIKE' => '%example.com%', // a NOT LIKE operátort használja +]); +// WHERE `age` > 25 AND `name` LIKE '%John%' AND `email` NOT LIKE '%example.com%' +``` + +A Nette automatikusan kezeli a speciális eseteket, mint a `null` értékek vagy tömbök. + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name' => 'Laptop', // az = operátort használja + 'category_id' => [1, 2, 3], // az IN-t használja + 'description' => null, // az IS NULL-t használja +]); +// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL +``` + +Negatív feltételekhez használja a `NOT` operátort: + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name NOT' => 'Laptop', // a <> operátort használja + 'category_id NOT' => [1, 2, 3], // a NOT IN-t használja + 'description NOT' => null, // az IS NOT NULL-t használja + 'id' => [], // kihagyja +]); +// WHERE `name` <> 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL +``` + +A feltételek összekapcsolásához az `AND` operátor használatos. Ezt a [?or helyettesítő karakterrel |#SQL összeállítási tippek] lehet megváltoztatni. + + +ORDER BY szabályok +------------------ + +Az `ORDER BY` rendezést tömb segítségével lehet leírni. A kulcsokban az oszlopokat adjuk meg, az érték pedig egy logikai érték lesz, amely meghatározza, hogy növekvő sorrendben kell-e rendezni: + +```php +$database->query('SELECT id FROM author ORDER BY', [ + 'id' => true, // növekvő + 'name' => false, // csökkenő +]); +// SELECT id FROM author ORDER BY `id`, `name` DESC +``` + + +Adatbeszúrás (INSERT) +--------------------- + +Rekordok beszúrásához az `INSERT` SQL parancsot használjuk. + +```php +$values = [ + 'name' => 'John Doe', + 'email' => 'john@example.com', +]; +$database->query('INSERT INTO users ?', $values); +$userId = $database->getInsertId(); +``` + +A `getInsertId()` metódus visszaadja az utoljára beszúrt sor ID-jét. Néhány adatbázisnál (pl. PostgreSQL) paraméterként meg kell adni annak a szekvenciának a nevét, amelyből az ID-t generálni kell a `$database->getInsertId($sequenceId)` segítségével. + +Paraméterként átadhatunk [#Speciális értékek] is, mint például fájlokat, DateTime objektumokat vagy enum típusokat. + +Több rekord beszúrása egyszerre: + +```php +$database->query('INSERT INTO users ?', [ + ['name' => 'User 1', 'email' => 'user1@mail.com'], + ['name' => 'User 2', 'email' => 'user2@mail.com'], +]); +``` + +A többszörös INSERT sokkal gyorsabb, mert egyetlen adatbázis-lekérdezés hajtódik végre, sok különálló helyett. + +**Biztonsági figyelmeztetés:** Soha ne használjon validálatlan adatokat `$values`-ként. Ismerkedjen meg a [lehetséges kockázatokkal |security#Biztonságos munka az oszlopokkal]. + + +Adatfrissítés (UPDATE) +---------------------- + +Rekordok frissítéséhez az `UPDATE` SQL parancsot használjuk. + +```php +// Egy rekord frissítése +$values = [ + 'name' => 'John Smith', +]; +$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1); +``` + +Az érintett sorok számát a `$result->getRowCount()` adja vissza. + +Az UPDATE-hez használhatjuk a `+=` és `-=` operátorokat: + +```php +$database->query('UPDATE users SET ? WHERE id = ?', [ + 'login_count+=' => 1, // a login_count inkrementálása +], 1); +``` + +Példa egy rekord beszúrására vagy módosítására, ha már létezik. Az `ON DUPLICATE KEY UPDATE` technikát használjuk: + +```php +$values = [ + 'name' => $name, + 'year' => $year, +]; +$database->query('INSERT INTO users ? ON DUPLICATE KEY UPDATE ?', + $values + ['id' => $id], + $values, +); +// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) +// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 +``` + +Figyelje meg, hogy a Nette Database felismeri, milyen kontextusban illesztjük be a tömböt tartalmazó paramétert az SQL parancsba, és ennek megfelelően állítja össze belőle az SQL kódot. Tehát az első tömbből `(id, name, year) VALUES (123, 'Jim', 1978)`-t állított össze, míg a másodikat `name = 'Jim', year = 1978` formára alakította át. Részletesebben ezzel az [#SQL összeállítási tippek] részben foglalkozunk. + + +Adattörlés (DELETE) +------------------- + +Rekordok törléséhez a `DELETE` SQL parancsot használjuk. Példa a törölt sorok számának lekérésével: + +```php +$count = $database->query('DELETE FROM users WHERE id = ?', 1) + ->getRowCount(); +``` + + +SQL összeállítási tippek +------------------------ + +A hint egy speciális helyettesítő karakter az SQL lekérdezésben, amely megmondja, hogyan kell a paraméter értékét SQL kifejezéssé átírni: + +| Hint | Leírás | Automatikusan használva +|-----------|-------------------------------------------------|----------------------------- +| `?name` | tábla vagy oszlop nevének beillesztésére használja | - +| `?values` | `(key, ...) VALUES (value, ...)`-t generál | `INSERT ... ?`, `REPLACE ... ?` +| `?set` | `key = value, ...` hozzárendelést generál | `SET ?`, `KEY UPDATE ?` +| `?and` | a tömb feltételeit `AND` operátorral köti össze | `WHERE ?`, `HAVING ?` +| `?or` | a tömb feltételeit `OR` operátorral köti össze | - +| `?order` | `ORDER BY` záradékot generál | `ORDER BY ?`, `GROUP BY ?` + +Táblák és oszlopok nevének dinamikus beillesztéséhez a lekérdezésbe a `?name` helyettesítő karakter szolgál. A Nette Database gondoskodik az azonosítók helyes kezeléséről az adott adatbázis konvenciói szerint (pl. backtickekbe zárás MySQL-ben). + +```php +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table); +// SELECT `name` FROM `users` WHERE id = 1 (MySQL-ben) +``` + +**Figyelmeztetés:** a `?name` szimbólumot csak validált bemenetekből származó tábla- és oszlopnevekhez használja, különben [biztonsági kockázatnak |security#Dinamikus azonosítók] teszi ki magát. + +A többi hintet általában nem szükséges megadni, mivel a Nette okos automatikus felismerést használ az SQL lekérdezés összeállításakor (lásd a táblázat harmadik oszlopát). De használhatja például olyan helyzetben, amikor a feltételeket `OR` helyett `AND`-del szeretné összekötni: + +```php +$database->query('SELECT * FROM users WHERE ?or', [ + 'name' => 'John', + 'email' => 'john@example.com', +]); +// SELECT * FROM users WHERE `name` = 'John' OR `email` = 'john@example.com' +``` + + +Speciális értékek +----------------- + +A szokásos skalár típusokon (string, int, bool) kívül speciális értékeket is átadhat paraméterként: + +- fájlok: `fopen('image.gif', 'r')` beilleszti a fájl bináris tartalmát +- dátum és idő: a `DateTime` objektumok adatbázis formátumra konvertálódnak +- enum típusok: az `enum` példányok értékükre konvertálódnak +- SQL literálok: a `Connection::literal('NOW()')` segítségével létrehozottak közvetlenül beillesztődnek a lekérdezésbe + +```php +$database->query('INSERT INTO articles ?', [ + 'title' => 'My Article', + 'published_at' => new DateTime, + 'content' => fopen('image.png', 'r'), + 'state' => Status::Draft, +]); +``` + +Azoknál az adatbázisoknál, amelyek nem rendelkeznek natív támogatással a `datetime` adattípushoz (mint a SQLite és az Oracle), a `DateTime` az [adatbázis konfigurációjában|configuration] a `formatDateTime` tétellel meghatározott értékre konvertálódik (az alapértelmezett érték `U` - unix timestamp). + + +SQL literálok +------------- + +Néhány esetben szükség van arra, hogy értékként közvetlenül SQL kódot adjunk meg, amelyet azonban nem szabad stringként értelmezni és escapelni. Erre szolgálnak a `Nette\Database\SqlLiteral` osztály objektumai. Ezeket a `Connection::literal()` metódus hozza létre. + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + 'year >' => $database::literal('YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) +``` + +Vagy alternatívaként: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) +``` + +Az SQL literálok tartalmazhatnak paramétereket: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > ? AND year < ?', $min, $max), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) +``` + +Ennek köszönhetően érdekes kombinációkat hozhatunk létre: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('?or', [ + 'active' => true, + 'role' => $role, + ]), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') +``` + + +Adatlekérés +=========== + + +Rövidítések SELECT lekérdezésekhez +---------------------------------- + +Az adatbetöltés egyszerűsítésére a `Connection` több rövidítést kínál, amelyek kombinálják a `query()` hívást a következő `fetch*()` hívásokkal. Ezek a metódusok ugyanazokat a paramétereket fogadják el, mint a `query()`, azaz az SQL lekérdezést és az opcionális paramétereket. A `fetch*()` metódusok teljes leírását [alább |#fetch] találja. + +| `fetch($sql, ...$params): ?Row` | Végrehajtja a lekérdezést és visszaadja az első sort `Row` objektumként +| `fetchAll($sql, ...$params): array` | Végrehajtja a lekérdezést és visszaadja az összes sort `Row` objektumok tömbjeként +| `fetchPairs($sql, ...$params): array` | Végrehajtja a lekérdezést és visszaad egy asszociatív tömböt, ahol az első oszlop a kulcs, a második az érték +| `fetchField($sql, ...$params): mixed` | Végrehajtja a lekérdezést és visszaadja az első sor első mezőjének értékét +| `fetchList($sql, ...$params): ?array` | Végrehajtja a lekérdezést és visszaadja az első sort indexelt tömbként + +Példa: + +```php +// fetchField() - visszaadja az első cella értékét +$count = $database->query('SELECT COUNT(*) FROM articles') + ->fetchField(); +``` + + +`foreach` - iteráció a sorokon +------------------------------ + +A lekérdezés végrehajtása után egy [ResultSet|api:Nette\Database\ResultSet] objektumot kapunk vissza, amely lehetővé teszi az eredmények több módon történő bejárását. A legegyszerűbb módja a lekérdezés végrehajtásának és a sorok lekérésének a `foreach` ciklussal történő iterálás. Ez a módszer a memóriatakarékosabb, mivel az adatokat fokozatosan adja vissza, és nem tárolja őket egyszerre a memóriában. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; + // ... +} +``` + +.[note] +A `ResultSet`-et csak egyszer lehet iterálni. Ha ismételten kell iterálni, először be kell tölteni az adatokat egy tömbbe, például a `fetchAll()` metódussal. + + +fetch(): ?Row .[method] +----------------------- + +Visszaad egy sort `Row` objektumként. Ha nincs több sor, `null`-t ad vissza. A belső mutatót a következő sorra mozgatja. + +```php +$result = $database->query('SELECT * FROM users'); +$row = $result->fetch(); // betölti az első sort +if ($row) { + echo $row->name; +} +``` + + +fetchAll(): array .[method] +--------------------------- + +Visszaadja a `ResultSet`-ből az összes fennmaradó sort `Row` objektumok tömbjeként. + +```php +$result = $database->query('SELECT * FROM users'); +$rows = $result->fetchAll(); // betölti az összes sort +foreach ($rows as $row) { + echo $row->name; +} +``` + + +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Visszaadja az eredményeket asszociatív tömbként. Az első argumentum határozza meg az oszlop nevét, amely a tömb kulcsaként lesz használva, a második argumentum határozza meg az oszlop nevét, amely értékként lesz használva: + +```php +$result = $database->query('SELECT id, name FROM users'); +$names = $result->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] +``` + +Ha csak az első paramétert adjuk meg, az érték a teljes sor lesz, azaz egy `Row` objektum: + +```php +$rows = $result->fetchPairs('id'); +// [1 => Row(id: 1, name: 'John'), 2 => Row(id: 2, name: 'Jane'), ...] +``` + +Duplikált kulcsok esetén az utolsó sor értéke lesz használva. Ha `null`-t használunk kulcsként, a tömb numerikusan lesz indexelve nullától kezdve (ekkor nem történik ütközés): + +```php +$names = $result->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] +``` + + +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Alternatívaként megadhat egy callbacket paraméterként, amely minden sorhoz vagy magát az értéket, vagy egy kulcs-érték párt ad vissza. + +```php +$result = $database->query('SELECT * FROM users'); +$items = $result->fetchPairs(fn($row) => "$row->id - $row->name"); +// ['1 - John', '2 - Jane', ...] + +// A callback visszaadhat egy tömböt is kulcs & érték párral: +$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]); +// ['John' => 46, 'Jane' => 21, ...] +``` + + +fetchField(): mixed .[method] +----------------------------- + +Visszaadja az aktuális sor első mezőjének értékét. Ha nincs több sor, `null`-t ad vissza. A belső mutatót a következő sorra mozgatja. + +```php +$result = $database->query('SELECT name FROM users'); +$name = $result->fetchField(); // betölti a nevet az első sorból +``` + + +fetchList(): ?array .[method] +----------------------------- + +Visszaad egy sort indexelt tömbként. Ha nincs több sor, `null`-t ad vissza. A belső mutatót a következő sorra mozgatja. + +```php +$result = $database->query('SELECT name, email FROM users'); +$row = $result->fetchList(); // ['John', 'john@example.com'] +``` + + +getRowCount(): ?int .[method] +----------------------------- + +Visszaadja az utolsó `UPDATE` vagy `DELETE` lekérdezés által érintett sorok számát. `SELECT` esetén ez a visszaadott sorok száma, de ez nem mindig ismert - ebben az esetben a metódus `null`-t ad vissza. + + +getColumnCount(): ?int .[method] +-------------------------------- + +Visszaadja az oszlopok számát a `ResultSet`-ben. + + +Információk a lekérdezésekről +============================= + +Debuggolási célokra lekérhetjük az utoljára végrehajtott lekérdezés információit: + +```php +echo $database->getLastQueryString(); // kiírja az SQL lekérdezést + +$result = $database->query('SELECT * FROM articles'); +echo $result->getQueryString(); // kiírja az SQL lekérdezést +echo $result->getTime(); // kiírja a végrehajtási időt másodpercben +``` + +Az eredmény HTML táblázatként való megjelenítéséhez használható: + +```php +$result = $database->query('SELECT * FROM articles'); +$result->dump(); +``` + +A ResultSet információkat kínál az oszloptípusokról: + +```php +$result = $database->query('SELECT * FROM articles'); +$types = $result->getColumnTypes(); + +foreach ($types as $column => $type) { + echo "$column típusa $type->type"; // pl. 'id típusa int' +} +``` + + +Lekérdezések naplózása +---------------------- + +Implementálhatunk saját lekérdezés-naplózást. Az `onQuery` esemény egy callback tömb, amely minden végrehajtott lekérdezés után meghívódik: + +```php +$database->onQuery[] = function ($database, $result) use ($logger) { + $logger->info('Lekérdezés: ' . $result->getQueryString()); + $logger->info('Idő: ' . $result->getTime()); + + if ($result->getRowCount() > 1000) { + $logger->warning('Nagy eredményhalmaz: ' . $result->getRowCount() . ' sor'); + } +}; +``` diff --git a/database/hu/transactions.texy b/database/hu/transactions.texy new file mode 100644 index 0000000000..accc0cc112 --- /dev/null +++ b/database/hu/transactions.texy @@ -0,0 +1,43 @@ +Tranzakciók +*********** + +.[perex] +A tranzakciók garantálják, hogy a tranzakción belüli összes művelet végrehajtásra kerül, vagy egyik sem. Hasznosak az adatok konzisztenciájának biztosítására összetettebb műveletek során. + +A tranzakciók használatának legegyszerűbb módja a következő: + +```php +$database->beginTransaction(); +try { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); + $database->commit(); +} catch (\Exception $e) { + $database->rollBack(); + throw $e; +} +``` + +Ugyanezt sokkal elegánsabban is megírhatja a `transaction()` metódussal. Paraméterként egy callbacket fogad el, amelyet a tranzakcióban hajt végre. Ha a callback kivétel nélkül lefut, a tranzakció automatikusan megerősítésre kerül. Ha kivétel történik, a tranzakció visszavonásra kerül (rollback), és a kivétel tovább terjed. + +```php +$database->transaction(function ($database) use ($id) { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); +}); +``` + +A `transaction()` metódus értékeket is visszaadhat: + +```php +$count = $database->transaction(function ($database) { + $result = $database->query('UPDATE users SET active = ?', true); + return $result->getRowCount(); // visszaadja a frissített sorok számát +}); +``` diff --git a/database/it/@home.texy b/database/it/@home.texy index 68af58f1d3..97f1e1e9db 100644 --- a/database/it/@home.texy +++ b/database/it/@home.texy @@ -1,20 +1,21 @@ -Server supportati -================= +Database supportati +=================== -Sono supportati i seguenti server di database: +Nette supporta i seguenti database: -|* Server di database |* Nome DSN |* Supporto Core |* Supporto Explorer -| MySQL (>= 5.1) | mysql | SÌ | SÌ -| PostgreSQL (>= 9.0) | pgsql | SÌ | SÌ -| Sqlite 3 (>= 3.8) | sqlite | SÌ | SÌ -| Oracle | oci | SI' | - -| MS SQL (PDO_SQLSRV) | sqlsrv | SÌ | SÌ | SÌ -MS SQL (PDO_DBLIB) | mssql | SÌ | SÌ | - -| ODBC | odbc | SÌ | - +|* Server di database |* Nome DSN |* Supporto in Core |* Supporto in Explorer +| MySQL (>= 5.1) | mysql | SÌ | SÌ +| PostgreSQL (>= 9.0) | pgsql | SÌ | SÌ +| Sqlite 3 (>= 3.8) | sqlite | SÌ | SÌ +| Oracle | oci | SÌ | - +| MS SQL (PDO_SQLSRV) | sqlsrv | SÌ | SÌ +| MS SQL (PDO_DBLIB) | mssql | SÌ | - +| ODBC | odbc | SÌ | - -{{title: Nette Database}} -{{description: Nette Database semplifica il recupero dei dati dal database senza la necessità di scrivere query SQL. Fa query efficienti e non trasferisce dati superflui.}} + +{{maintitle: Nette Database - awesome database layer for PHP}} +{{description: Nette Database semplifica notevolmente l'ottenimento di dati dal database senza la necessità di scrivere query SQL. Esegue query efficienti e non trasferisce dati inutili.}} diff --git a/database/it/@left-menu.texy b/database/it/@left-menu.texy index 0396cb3c39..58127bb77b 100644 --- a/database/it/@left-menu.texy +++ b/database/it/@left-menu.texy @@ -1,5 +1,12 @@ -Database -******** -- [Nucleo |Core] -- [Esploratore |Explorer] -- [Configurazione |Configuration] +Nette Database +************** +- [Introduzione |guide] +- [Approccio SQL |sql way] +- [Explorer |Explorer] +- [Transazioni |transactions] +- [Eccezioni |exceptions] +- [Riflessione |reflection] +- [Mappatura |mapping] +- [Configurazione |configuration] +- [Rischi per la sicurezza |security] +- [Aggiornamento |en:upgrading] diff --git a/database/it/@meta.texy b/database/it/@meta.texy new file mode 100644 index 0000000000..4647d0c8a2 --- /dev/null +++ b/database/it/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentazione Nette}} diff --git a/database/it/configuration.texy b/database/it/configuration.texy index 0001d46c9b..543819e346 100644 --- a/database/it/configuration.texy +++ b/database/it/configuration.texy @@ -2,60 +2,66 @@ Configurazione del database *************************** .[perex] -Panoramica delle opzioni di configurazione del database Nette. +Panoramica delle opzioni di configurazione per Nette Database. -Se non si utilizza l'intero framework, ma solo questa libreria, leggere [come caricare la configurazione |bootstrap:]. +Se non utilizzate l'intero framework, ma solo questa libreria, leggete [come caricare la configurazione|bootstrap:]. -Connessione singola .[#toc-single-connection] ---------------------------------------------- +Connessione singola +------------------- -Configurare una singola connessione al database: +Configurazione di una singola connessione al database: ```neon database: - # DSN, solo chiave obbligatoria - dsn: "sqlite:%appDir%/Modello/demo.db" + # DSN, unica chiave obbligatoria + dsn: "sqlite:%appDir%/Model/demo.db" user: ... password: ... ``` -Crea servizi del tipo `Nette\Database\Connection` e `Nette\Database\Explorer` per il livello [Database Explorer |explorer]. La connessione al database viene solitamente passata tramite [autocablaggio |dependency-injection:autowiring]; se ciò non è possibile, utilizzare i nomi dei servizi `@database.default.connection` e `@database.default.explorer`. +Crea i servizi `Nette\Database\Connection` e `Nette\Database\Explorer`, che di solito passiamo tramite [autowiring |dependency-injection:autowiring], oppure tramite riferimento al [loro nome |#Servizi DI]. Altre impostazioni: ```neon database: - # mostra il pannello del database nella barra Tracy? - debugger: ... # (bool) predefinito a true + # visualizzare il pannello del database nella Tracy Bar? + debugger: ... # (bool) il default è true - # mostra la query EXPLAIN nella barra Tracy? - explain: ... # (bool) predefinito a true + # visualizzare EXPLAIN delle query nella Tracy Bar? + explain: ... # (bool) il default è true - # abilitare il cablaggio automatico per questa connessione? - autowired: ... # (bool) predefinito a true per la prima connessione + # abilitare l'autowiring per questa connessione? + autowired: ... # (bool) il default è true per la prima connessione - # convenzioni della tabella: scoperta, statica o nome della classe - conventions: discovered # (string) predefinito a "discovered". + # convenzioni delle tabelle: discovered, static o nome della classe + conventions: discovered # (string) il default è 'discovered' options: - # per connettersi al database solo quando necessario? - lazy: ... # (bool) predefinito a false + # connettersi al database solo quando necessario? + lazy: ... # (bool) il default è false - # classe del driver del database PHP + # Classe PHP del driver del database driverClass: # (string) - # Solo MySQL: imposta sql_mode + # solo MySQL: imposta sql_mode sqlmode: # (string) # solo MySQL: imposta SET NAMES - charset: # (string) predefinito a 'utf8mb4' ('utf8' prima della v5.5.3) + charset: # (string) il default è 'utf8mb4' - # solo Oracle e SQLite: formato data - formatDateTime: # (string) predefinito a "U". + # solo MySQL: converte TINYINT(1) in bool + convertBoolean: # (bool) il default è false + + # restituisce le colonne data come oggetti immutabili (dalla versione 3.2.1) + newDateTime: # (bool) il default è false + + # solo Oracle e SQLite: formato per la memorizzazione della data + formatDateTime: # (string) il default è 'U' ``` -La chiave `options` può contenere altre opzioni che si possono trovare nella [documentazione |https://www.php.net/manual/en/pdo.drivers.php] del [driver PDO |https://www.php.net/manual/en/pdo.drivers.php], come ad esempio: +Nella chiave `options` è possibile specificare altre opzioni che trovate nella [documentazione dei driver PDO |https://www.php.net/manual/en/pdo.drivers.php], come ad esempio: ```neon database: @@ -64,10 +70,10 @@ database: ``` -Connessioni multiple .[#toc-multiple-connections] -------------------------------------------------- +Connessioni multiple +-------------------- -Nella configurazione si possono definire più connessioni al database dividendole in sezioni denominate: +Nella configurazione possiamo definire anche più connessioni al database dividendole in sezioni denominate: ```neon database: @@ -80,9 +86,23 @@ database: dsn: 'sqlite::memory:' ``` -Ogni connessione definita crea servizi che includono il nome della sezione nel loro nome, ad esempio `@database.main.connection` e `@database.main.explorer` e poi `@database.another.connection` e `@database.another.explorer`. +L'autowiring è abilitato solo per i servizi della prima sezione. È possibile modificarlo tramite `autowired: false` o `autowired: true`. + + +Servizi DI +---------- + +Questi servizi vengono aggiunti al container DI, dove `###` rappresenta il nome della connessione: + +| Nome | Tipo | Descrizione +|------------------------------------------------------------------------------------ +| `database.###.connection` | [api:Nette\Database\Connection] | connessione al database +| `database.###.explorer` | [api:Nette\Database\Explorer] | [Database Explorer |explorer] + + +Se definiamo solo una connessione, i nomi dei servizi saranno `database.default.connection` e `database.default.explorer`. Se definiamo più connessioni come nell'esempio sopra, i nomi corrisponderanno alle sezioni, cioè `database.main.connection`, `database.main.explorer` e inoltre `database.another.connection` e `database.another.explorer`. -Il cablaggio automatico è abilitato solo per i servizi della prima sezione. Questo può essere modificato con `autowired: false` o `autowired: true`. I servizi non autocablati vengono passati per nome: +I servizi non autowired li passiamo esplicitamente tramite riferimento al loro nome: ```neon services: diff --git a/database/it/core.texy b/database/it/core.texy deleted file mode 100644 index 562e9d941f..0000000000 --- a/database/it/core.texy +++ /dev/null @@ -1,350 +0,0 @@ -Nucleo del database -******************* - -.[perex] -Nette Database Core è il livello di astrazione del database e fornisce le funzionalità principali. - - -Installazione .[#toc-installation] -================================== - -Scaricare e installare il pacchetto utilizzando [Composer |best-practices:composer]: - -```shell -composer require nette/database -``` - - -Connessione e configurazione .[#toc-connection-and-configuration] -================================================================= - -Per connettersi al database, è sufficiente creare un'istanza della classe [api:Nette\Database\Connection]: - -```php -$database = new Nette\Database\Connection($dsn, $user, $password); -``` - -Il parametro `$dsn` (nome dell'origine dati) è lo [stesso utilizzato da PDO |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], ad esempio `host=127.0.0.1;dbname=test`. In caso di fallimento, viene lanciato `Nette\Database\ConnectionException`. - -Tuttavia, un modo più sofisticato offre la [configurazione dell'applicazione |configuration]. Aggiungiamo una sezione `database` che crea gli oggetti necessari e un pannello del database nella barra [Tracy |tracy:]. - -```neon -database: - dsn: 'mysql:host=127.0.0.1;dbname=test' - user: root - password: password -``` - -L'oggetto connessione che [riceviamo come servizio da un contenitore DI |dependency-injection:passing-dependencies], ad esempio: - -```php -class Model -{ - // passa Nette\Database\Explorer per lavorare con il livello Database Explorer - public function __construct( - private Nette\Database\Connection $database, - ) { - } -} -``` - -Per ulteriori informazioni, vedere [Configurazione del database |configuration]. - - -Query .[#toc-queries] -===================== - -Per interrogare il database utilizzare il metodo `query()` che restituisce [ResultSet |api:Nette\Database\ResultSet]. - -```php -$result = $database->query('SELECT * FROM users'); - -foreach ($result as $row) { - echo $row->id; - echo $row->name; -} - -echo $result->getRowCount(); // restituisce il numero di righe se è conosciuto -``` - -.[note] -Su `ResultSet` è possibile iterare una sola volta; se abbiamo bisogno di iterare più volte, è necessario convertire il risultato in array con il metodo `fetchAll()`. - -È possibile aggiungere facilmente dei parametri alla query, come il punto interrogativo: - -```php -$database->query('SELECT * FROM users WHERE name = ?', $name); - -$database->query('SELECT * FROM users WHERE name = ? AND active = ?', $name, $active); - -$database->query('SELECT * FROM users WHERE id IN (?)', $ids); // $ids è un array -``` -<div class=warning> - -ATTENZIONE, non concatenare mai le stringhe per evitare [vulnerabilità da SQL injection |https://en.wikipedia.org/wiki/SQL_injection]! -/-- -$db->query('SELECT * FROM users WHERE name = ' . $name); // WRONG!!! -\-- -</div> - -In caso di fallimento, `query()` lancia `Nette\Database\DriverException` o uno dei suoi discendenti: - -- [ConstraintViolationException |api:Nette\Database\ConstraintViolationException] - violazione di qualsiasi vincolo -- [ForeignKeyConstraintViolationException |api:Nette\Database\ForeignKeyConstraintViolationException] - chiave esterna non valida -- [NotNullConstraintViolationException |api:Nette\Database\NotNullConstraintViolationException] - violazione della condizione NOT NULL -- [UniqueConstraintViolationException |api:Nette\Database\UniqueConstraintViolationException] - conflitto di un indice univoco - -Oltre a `query()`, esistono altri metodi utili: - -```php -// restituisce l'array associativo id => nome -$pairs = $database->fetchPairs('SELECT id, name FROM users'); - -// restituisce tutte le righe come array -$rows = $database->fetchAll('SELECT * FROM users'); - -// restituisce una singola riga -$row = $database->fetch('SELECT * FROM users WHERE id = ?', $id); - -// restituisce un singolo campo -$name = $database->fetchField('SELECT name FROM users WHERE id = ?', $id); -``` - -In caso di fallimento, tutti questi metodi lanciano `Nette\Database\DriverException.` - - -Inserire, aggiornare e cancellare .[#toc-insert-update-delete] -============================================================== - -Il parametro che inseriamo nella query SQL può anche essere un array (in questo caso è possibile saltare l'istruzione jolly `?`), which may be useful for the `INSERT` ): - -```php -$database->query('INSERT INTO users ?', [ // qui si può omettere il punto interrogativo - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) - -$id = $database->getInsertId(); // restituisce l'autoincremento della riga inserita - -$id = $database->getInsertId($sequence); // o il valore della sequenza -``` - -Inserimento multiplo: - -```php -$database->query('INSERT INTO users', [ - [ - 'name' => 'Jim', - 'year' => 1978, - ], [ - 'name' => 'Jack', - 'year' => 1987, - ], -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) -``` - -Si possono passare anche file, oggetti DateTime o [enumerazioni |https://www.php.net/enumerations]: - -```php -$database->query('INSERT INTO users', [ - 'name' => $name, - 'created' => new DateTime, // o $database::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // inserisce il contenuto del file - 'status' => State::New, // enum State -]); -``` - -Aggiornamento delle righe: - -```php -$result = $database->query('UPDATE users SET', [ - 'name' => $name, - 'year' => $year, -], 'WHERE id = ?', $id); -// UPDATE users SET `name` = 'Jim', `year` = 1978 WHERE id = 123 - -echo $result->getRowCount(); // restituisce il numero delle righe interessate -``` - -Per l'UPDATE si possono utilizzare gli operatori `+=` e `-=`: - -```php -$database->query('UPDATE users SET', [ - 'age+=' => 1, // note += -], 'WHERE id = ?', $id); -// UPDATE users SET `age` = `age` + 1 -``` - -Eliminazione: - -```php -$result = $database->query('DELETE FROM users WHERE id = ?', $id); -echo $result->getRowCount(); // restituisce il numero di righe interessate -``` - - -Query avanzate .[#toc-advanced-queries] -======================================= - -Inserire o aggiornare, se esiste già: - -```php -$database->query('INSERT INTO users', [ - 'id' => $id, - 'name' => $name, - 'year' => $year, -], 'ON DUPLICATE KEY UPDATE', [ - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) -// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 -``` - -Si noti che Nette Database riconosce il contesto SQL in cui è inserito il parametro dell'array e costruisce il codice SQL di conseguenza. Quindi, dal primo array genera `(id, name, year) VALUES (123, 'Jim', 1978)`, mentre il secondo si converte in `name = 'Jim', year = 1978`. - -Possiamo anche descrivere l'ordinamento utilizzando un array, in cui le chiavi sono nomi di colonne e i valori sono booleani che determinano se ordinare in ordine crescente: - -```php -$database->query('SELECT id FROM author ORDER BY', [ - 'id' => true, // crescente - 'name' => false, // discendente -]); -// SELECT id FROM author ORDER BY `id`, `name` DESC -``` - -Se il rilevamento non funziona, è possibile specificare la forma dell'insieme con un carattere jolly `?` seguito da un suggerimento. Sono supportati questi suggerimenti: - -| ?values | (key1, key2, ...) VALUES (value1, value2, ...) -| ?set | key1 = valore1, key2 = valore2, ... -| ?and | key1 = valore1 AND key2 = valore2 ... -| ?or | key1 = valore1 OR key2 = valore2 ... -| ?order | key1 ASC, key2 DESC - -La clausola WHERE utilizza l'operatore `?and`, quindi le condizioni sono collegate da `AND`: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND `year` = 1978 -``` - -Che può essere facilmente modificato in `OR` utilizzando il carattere jolly `?or`: - -```php -$result = $database->query('SELECT * FROM users WHERE ?or', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' OR `year` = 1978 -``` - -Possiamo usare gli operatori nelle condizioni: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name <>' => $name, - 'year >' => $year, -]); -// SELECT * FROM users WHERE `name` <> 'Jim' AND `year` > 1978 -``` - -E anche le enumerazioni: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => ['Jim', 'Jack'], - 'role NOT IN' => ['admin', 'owner'], // enumeration + operator NOT IN -]); -// SELECT * FROM users WHERE -// `name` IN ('Jim', 'Jack') AND `role` NOT IN ('admin', 'owner') -``` - -Possiamo anche includere un pezzo di codice SQL personalizzato utilizzando il cosiddetto letterale SQL: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year >' => $database::literal('YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) -``` - -In alternativa: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) -``` - -Anche i letterali SQL possono avere i loro parametri: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > ? AND year < ?', $min, $max), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) -``` - -Grazie a questi si possono creare interessanti combinazioni: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('?or', [ - 'active' => true, - 'role' => $role, - ]), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') -``` - - -Nome della variabile .[#toc-variable-name] -========================================== - -Esiste un carattere jolly `?name` da utilizzare se il nome della tabella o della colonna è una variabile. (Attenzione, non permettete all'utente di manipolare il contenuto di tale variabile): - -```php -$table = 'blog.users'; -$column = 'name'; -$database->query('SELECT * FROM ?name WHERE ?name = ?', $table, $column, $name); -// SELECT * FROM `blog`.`users` WHERE `name` = 'Jim' -``` - - -Transazioni .[#toc-transactions] -================================ - -Esistono tre metodi per gestire le transazioni: - -```php -$database->beginTransaction(); - -$database->commit(); - -$database->rollback(); -``` - -Un modo elegante è offerto dal metodo `transaction()`. Si passa il callback che viene eseguito nella transazione. Se viene lanciata un'eccezione durante l'esecuzione, la transazione viene abbandonata; se tutto va bene, la transazione viene impegnata. - -```php -$id = $database->transaction(function ($database) { - $database->query('DELETE FROM ...'); - $database->query('INSERT INTO ...'); - // ... - return $database->getInsertId(); -}); -``` - -Come si può vedere, il metodo `transaction()` restituisce il valore di ritorno della callback. - -Il metodo transaction() può anche essere annidato, il che semplifica l'implementazione di repository indipendenti. diff --git a/database/it/exceptions.texy b/database/it/exceptions.texy new file mode 100644 index 0000000000..f7ebeb69c1 --- /dev/null +++ b/database/it/exceptions.texy @@ -0,0 +1,34 @@ +Eccezioni +********* + +Nette Database utilizza una gerarchia di eccezioni. La classe base è `Nette\Database\DriverException`, che eredita da `PDOException` e fornisce funzionalità estese per la gestione degli errori del database: + +- Il metodo `getDriverCode()` restituisce il codice di errore dal driver del database +- Il metodo `getSqlState()` restituisce il codice SQLSTATE +- I metodi `getQueryString()` e `getParameters()` consentono di ottenere la query originale e i suoi parametri + +Da `DriverException` ereditano le seguenti eccezioni specializzate: + +- `ConnectionException` - segnala un fallimento della connessione al server del database +- `ConstraintViolationException` - classe base per la violazione dei vincoli del database, da cui ereditano: + - `ForeignKeyConstraintViolationException` - violazione della chiave esterna + - `NotNullConstraintViolationException` - violazione del vincolo NOT NULL + - `UniqueConstraintViolationException` - violazione dell'unicità del valore + + +Esempio di cattura dell'eccezione `UniqueConstraintViolationException`, che si verifica quando cerchiamo di inserire un utente con un'email che esiste già nel database (presupponendo che la colonna email abbia un indice univoco). + +```php +try { + $database->query('INSERT INTO users', [ + 'email' => 'john@example.com', + 'name' => 'John Doe', + 'password' => $hashedPassword, + ]); +} catch (Nette\Database\UniqueConstraintViolationException $e) { + echo 'Esiste già un utente con questa email.'; + +} catch (Nette\Database\DriverException $e) { + echo 'Si è verificato un errore durante la registrazione: ' . $e->getMessage(); +} +``` diff --git a/database/it/explorer.texy b/database/it/explorer.texy index c462381ac0..155338a3f9 100644 --- a/database/it/explorer.texy +++ b/database/it/explorer.texy @@ -1,550 +1,912 @@ -Esploratore di database -*********************** +Database Explorer +***************** <div class=perex> -Nette Database Explorer semplifica notevolmente il recupero dei dati dal database senza dover scrivere query SQL. +Explorer offre un modo intuitivo ed efficiente di lavorare con il database. Si occupa automaticamente delle relazioni tra le tabelle e dell'ottimizzazione delle query, così potete concentrarvi sulla vostra applicazione. Funziona immediatamente senza alcuna impostazione. Se avete bisogno del pieno controllo sulle query SQL, potete utilizzare l'[approccio SQL |SQL way]. -- utilizza query efficienti -- non trasmette dati inutilmente -- presenta una sintassi elegante +- Il lavoro con i dati è naturale e facile da capire +- Genera query SQL ottimizzate che caricano solo i dati necessari +- Permette un facile accesso ai dati correlati senza la necessità di scrivere query JOIN +- Funziona immediatamente senza alcuna configurazione o generazione di entità </div> -Per utilizzare Database Explorer, iniziare con una tabella - chiamare `table()` su un oggetto [api:Nette\Database\Explorer]. Il modo più semplice per ottenere un'istanza dell'oggetto contesto è [descritto qui |core#Connection and Configuration], oppure, nel caso in cui Nette Database Explorer venga utilizzato come strumento autonomo, è possibile [crearlo manualmente |#Creating Explorer Manually]. + +Con Explorer iniziate chiamando il metodo `table()` dell'oggetto [api:Nette\Database\Explorer] (dettagli sulla connessione li trovate nel capitolo [Connessione e configurazione |guide#Connessione e configurazione]): ```php -$books = $explorer->table('book'); // il nome della tabella del db è 'libro' +$books = $explorer->table('book'); // 'book' è il nome della tabella ``` -La chiamata restituisce un'istanza dell'oggetto [Selection |api:Nette\Database\Table\Selection], che può essere iterata per recuperare tutti i libri. Ogni elemento (una riga) è rappresentato da un'istanza di [ActiveRow |api:Nette\Database\Table\ActiveRow] con i dati mappati sulle sue proprietà: +Il metodo restituisce un oggetto [Selection |api:Nette\Database\Table\Selection], che rappresenta una query SQL. A questo oggetto possiamo concatenare altri metodi per filtrare e ordinare i risultati. La query viene costruita ed eseguita solo nel momento in cui iniziamo a richiedere i dati. Ad esempio, iterando con un ciclo `foreach`. Ogni riga è rappresentata da un oggetto [ActiveRow |api:Nette\Database\Table\ActiveRow]: ```php foreach ($books as $book) { - echo $book->title; - echo $book->author_id; + echo $book->title; // stampa della colonna 'title' + echo $book->author_id; // stampa della colonna 'author_id' } ``` -Per ottenere una riga specifica si utilizza il metodo `get()`, che restituisce direttamente un'istanza di ActiveRow. +Explorer semplifica notevolmente il lavoro con le [#relazioni tra tabelle]. L'esempio seguente mostra quanto sia facile visualizzare i dati da tabelle correlate (libri e i loro autori). Notate che non dobbiamo scrivere alcuna query JOIN, Nette le crea per noi: ```php -$book = $explorer->table('book')->get(2); // restituisce il libro con id 2 -echo $book->title; -echo $book->author_id; +$books = $explorer->table('book'); + +foreach ($books as $book) { + echo 'Libro: ' . $book->title; + echo 'Autore: ' . $book->author->name; // crea un JOIN sulla tabella 'author' +} ``` -Vediamo un caso d'uso comune. È necessario recuperare i libri e i loro autori. Si tratta di una comune relazione 1:N. La soluzione spesso utilizzata è quella di recuperare i dati utilizzando un'unica query SQL con join di tabelle. La seconda possibilità è recuperare i dati separatamente, eseguire una query per ottenere i libri e poi ottenere un autore per ogni libro con un'altra query (ad esempio nel ciclo foreach). Questo potrebbe essere facilmente ottimizzato per eseguire solo due query, una per i libri e un'altra per gli autori necessari, e questo è esattamente il modo in cui Nette Database Explorer lo fa. +Nette Database Explorer ottimizza le query affinché siano il più efficienti possibile. L'esempio sopra esegue solo due query SELECT, indipendentemente dal fatto che stiamo elaborando 10 o 10.000 libri. -Negli esempi che seguono, lavoreremo con lo schema di database riportato in figura. Ci sono collegamenti OneHasMany (1:N) (autore del libro `author_id` e possibile traduttore `translator_id`, che può essere `null`) e ManyHasMany (M:N) tra il libro e i suoi tag. +Inoltre, Explorer tiene traccia di quali colonne vengono utilizzate nel codice e carica dal database solo quelle, risparmiando ulteriore performance. Questo comportamento è completamente automatico e adattivo. Se successivamente modificate il codice e iniziate a utilizzare altre colonne, Explorer adatterà automaticamente le query. Non dovete impostare nulla, né pensare a quali colonne vi serviranno - lasciate fare a Nette. -[Un esempio, comprensivo di schema, si trova su GitHub |https://github.com/nette-examples/books]. -[* db-schema-1-.webp *] *** Struttura del database utilizzata negli esempi .<> +Filtraggio e ordinamento +======================== -Il codice seguente elenca il nome dell'autore per ogni libro e tutti i suoi tag. Tra poco [vedremo |#Working with relationships] come funziona internamente. +La classe `Selection` fornisce metodi per filtrare e ordinare la selezione dei dati. -```php -$books = $explorer->table('book'); +.[language-php] +| `where($condition, ...$params)` | Aggiunge una condizione WHERE. Più condizioni sono unite dall'operatore AND +| `whereOr(array $conditions)` | Aggiunge un gruppo di condizioni WHERE unite dall'operatore OR +| `wherePrimary($value)` | Aggiunge una condizione WHERE in base alla chiave primaria +| `order($columns, ...$params)` | Imposta l'ordinamento ORDER BY +| `select($columns, ...$params)` | Specifica le colonne da caricare +| `limit($limit, $offset = null)` | Limita il numero di righe (LIMIT) e opzionalmente imposta OFFSET +| `page($page, $itemsPerPage, &$total = null)` | Imposta la paginazione +| `group($columns, ...$params)` | Raggruppa le righe (GROUP BY) +| `having($condition, ...$params)` | Aggiunge una condizione HAVING per filtrare le righe raggruppate -foreach ($books as $book) { - echo 'title: ' . $book->title; - echo 'written by: ' . $book->author->name; // $book->autore è una riga della tabella 'autore'. +I metodi possono essere concatenati (il cosiddetto [fluent interface |nette:introduction-to-object-oriented-programming#Interfacce fluenti]): `$table->where(...)->order(...)->limit(...)`. - echo 'tag: '; - foreach ($book->related('book_tag') as $bookTag) { - echo $bookTag->tag->name . ', '; // $bookTag->tag è una riga della tabella 'tag'. - } -} -``` +In questi metodi potete anche utilizzare una notazione speciale per accedere ai [dati da tabelle correlate |#Interrogazione tramite tabelle correlate]. -Si può notare l'efficienza con cui funziona il livello di database. L'esempio precedente effettua un numero costante di richieste che assomigliano a queste: -```sql -SELECT * FROM `book` -SELECT * FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT * FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) -``` +Escaping e identificatori +------------------------- -Se si utilizza la [cache |caching:] (per impostazione predefinita), nessuna colonna verrà interrogata inutilmente. Dopo la prima query, la cache memorizzerà i nomi delle colonne utilizzate e Nette Database Explorer eseguirà le query solo con le colonne necessarie: +I metodi eseguono automaticamente l'escaping dei parametri e racchiudono tra virgolette gli identificatori (nomi di tabelle e colonne), prevenendo così SQL injection. Per un corretto funzionamento è necessario rispettare alcune regole: -```sql -SELECT `id`, `title`, `author_id` FROM `book` -SELECT `id`, `name` FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT `book_id`, `tag_id` FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT `id`, `name` FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) +- Scrivete le parole chiave, i nomi di funzioni, procedure, ecc. in **maiuscolo**. +- Scrivete i nomi di colonne e tabelle in **minuscolo**. +- Inserite sempre le stringhe tramite **parametri**. + +```php +where('name = ' . $name); // VULNERABILITÀ CRITICA: SQL injection +where('name LIKE "%search%"'); // SBAGLIato: complica l'inserimento automatico delle virgolette +where('name LIKE ?', '%search%'); // CORRETTO: valore inserito tramite parametro + +where('name like ?', $name); // SBAGLIATO: genera: `name` `like` ? +where('name LIKE ?', $name); // CORRETTO: genera: `name` LIKE ? +where('LOWER(name) = ?', $value);// CORRETTO: LOWER(`name`) = ? ``` -Selezioni .[#toc-selections] -============================ +where(string|array $condition, ...$parameters): static .[method] +---------------------------------------------------------------- -Vedere le possibilità di filtrare e limitare le righe [api:Nette\Database\Table\Selection]: +Filtra i risultati tramite condizioni WHERE. Il suo punto di forza è il lavoro intelligente con diversi tipi di valori e la scelta automatica degli operatori SQL. -.[language-php] -| `$table->where($where[, $param[, ...]])` | Impostare WHERE utilizzando AND come collante se vengono fornite due o più condizioni -| `$table->whereOr($where)` | Impostare WHERE usando OR come collante se vengono fornite due o più condizioni -| `$table->order($columns)` | Impostare ORDER BY, può essere un'espressione `('column DESC, id DESC')` -| `$table->select($columns)` | Impostare le colonne recuperate, può essere un'espressione `('col, MD5(col) AS hash')` -| `$table->limit($limit[, $offset])` | Impostare LIMIT e OFFSET -| `$table->page($page, $itemsPerPage[, &$lastPage])` | Abilita la paginazione -| `$table->group($columns)` | Impostare GROUP BY -| `$table->having($having)` | Imposta HAVING +Uso base: -È possibile utilizzare l'interfaccia Fluent, ad esempio `$table->where(...)->order(...)->limit(...)`. Le condizioni multiple `where` o `whereOr` sono collegate con l'operatore `AND`. +```php +$table->where('id', $value); // WHERE `id` = 123 +$table->where('id > ?', $value); // WHERE `id` > 123 +$table->where('id = ? OR name = ?', $id, $name); // WHERE `id` = 1 OR `name` = 'Jon Snow' +``` +Grazie al rilevamento automatico degli operatori appropriati, non dobbiamo gestire diversi casi speciali. Nette li risolve per noi: -dove() .[#toc-where] --------------------- +```php +$table->where('id', 1); // WHERE `id` = 1 +$table->where('id', null); // WHERE `id` IS NULL +$table->where('id', [1, 2, 3]); // WHERE `id` IN (1, 2, 3) +// è possibile utilizzare anche il placeholder punto interrogativo senza operatore: +$table->where('id ?', 1); // WHERE `id` = 1 +``` -Nette Database Explorer può aggiungere automaticamente gli operatori necessari per i valori passati: +Il metodo gestisce correttamente anche le condizioni negative e gli array vuoti: -.[language-php] -| `$table->where('field', $value)` | campo = $valore -| `$table->where('field', null)` | campo IS NULL -| `$table->where('field > ?', $val)` | campo > $val -| `$table->where('field', [1, 2])` | campo IN (1, 2) -| `$table->where('id = ? OR name = ?', 1, $name)` | id = 1 OR nome = "Jon Snow -| `$table->where('field', $explorer->table($tableName))` | campo IN (SELECT $primario FROM $nometabella) -| `$table->where('field', $explorer->table($tableName)->select('col'))` | campo IN (SELECT col FROM $tableName) +```php +$table->where('id', []); // WHERE `id` IS NULL AND FALSE -- non trova nulla +$table->where('id NOT', []); // WHERE `id` IS NULL OR TRUE -- trova tutto +$table->where('NOT (id ?)', []); // WHERE NOT (`id` IS NULL AND FALSE) -- trova tutto +// $table->where('NOT id ?', $ids); Attenzione - questa sintassi non è supportata +``` -È possibile fornire un segnaposto anche senza l'operatore di colonna. Queste chiamate sono identiche. +Come parametro possiamo passare anche il risultato di un'altra tabella - verrà creata una sottoquery: ```php -$table->where('id = ? OR id = ?', 1, 2); -$table->where('id ? OR id ?', 1, 2); +// WHERE `id` IN (SELECT `id` FROM `tableName`) +$table->where('id', $explorer->table($tableName)); + +// WHERE `id` IN (SELECT `col` FROM `tableName`) +$table->where('id', $explorer->table($tableName)->select('col')); ``` -Questa funzione consente di generare l'operatore corretto in base al valore: +Possiamo passare le condizioni anche come array, i cui elementi verranno uniti tramite AND: ```php -$table->where('id ?', 2); // id = 2 -$table->where('id ?', null); // id IS NULL -$table->where('id', $ids); // id IN (...) +// WHERE (`price_final` < `price_original`) AND (`stock_count` > `min_stock`) +$table->where([ + 'price_final < price_original', + 'stock_count > min_stock', +]); ``` -La selezione gestisce correttamente anche le condizioni negative, funziona anche per gli array vuoti: +Nell'array possiamo utilizzare coppie chiave => valore e Nette sceglierà nuovamente automaticamente gli operatori corretti: ```php -$table->where('id', []); // id IS NULL AND FALSE -$table->where('id NOT', []); // id IS NULL OR TRUE -$table->where('NOT (id ?)', $ids); // NOT (id IS NULL AND FALSE) +// WHERE (`status` = 'active') AND (`id` IN (1, 2, 3)) +$table->where([ + 'status' => 'active', + 'id' => [1, 2, 3], +]); +``` -// questo lancerà un'eccezione, questa sintassi non è supportata -$table->where('NOT id ?', $ids); +Nell'array possiamo combinare espressioni SQL con placeholder punto interrogativo e più parametri. Questo è adatto per condizioni complesse con operatori definiti con precisione: + +```php +// WHERE (`age` > 18) AND (ROUND(`score`, 2) > 75.5) +$table->where([ + 'age > ?' => 18, + 'ROUND(score, ?) > ?' => [2, 75.5], // passiamo due parametri come array +]); ``` +Chiamate multiple a `where()` uniscono automaticamente le condizioni tramite AND. -whereOr() .[#toc-whereor] -------------------------- -Esempio di utilizzo senza parametri: +whereOr(array $parameters): static .[method] +-------------------------------------------- + +Simile a `where()`, aggiunge condizioni, ma con la differenza che le unisce tramite OR: ```php -// WHERE (user_id IS NULL) OR (SUM(`field1`) > SUM(`field2`)) +// WHERE (`status` = 'active') OR (`deleted` = 1) $table->whereOr([ - 'user_id IS NULL', - 'SUM(field1) > SUM(field2)', + 'status' => 'active', + 'deleted' => true, ]); ``` -Utilizziamo i parametri. Se non si specifica un operatore, Nette Database Explorer aggiungerà automaticamente quello appropriato: +Anche qui possiamo utilizzare espressioni più complesse: ```php -// WHERE (`field1` IS NULL) OR (`field2` IN (3, 5)) OR (`amount` > 11) +// WHERE (`price` > 1000) OR (`price_with_tax` > 1500) $table->whereOr([ - 'field1' => null, - 'field2' => [3, 5], - 'amount >' => 11, + 'price > ?' => 1000, + 'price_with_tax > ?' => 1500, ]); ``` -La chiave può contenere un'espressione contenente punti interrogativi jolly e poi passare i parametri nel valore: + +wherePrimary(mixed $key): static .[method] +------------------------------------------ + +Aggiunge una condizione per la chiave primaria della tabella: ```php -// WHERE (`id` > 12) OR (ROUND(`id`, 5) = 3) -$table->whereOr([ - 'id > ?' => 12, - 'ROUND(id, ?) = ?' => [5, 3], -]); +// WHERE `id` = 123 +$table->wherePrimary(123); + +// WHERE `id` IN (1, 2, 3) +$table->wherePrimary([1, 2, 3]); +``` + +Se la tabella ha una chiave primaria composita (ad es. `foo_id`, `bar_id`), la passiamo come array: + +```php +// WHERE `foo_id` = 1 AND `bar_id` = 5 +$table->wherePrimary(['foo_id' => 1, 'bar_id' => 5])->fetch(); + +// WHERE (`foo_id`, `bar_id`) IN ((1, 5), (2, 3)) +$table->wherePrimary([ + ['foo_id' => 1, 'bar_id' => 5], + ['foo_id' => 2, 'bar_id' => 3], +])->fetchAll(); ``` -order() .[#toc-order] ---------------------- +order(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- -Esempi di utilizzo: +Determina l'ordine in cui verranno restituite le righe. Possiamo ordinare per una o più colonne, in ordine ascendente o discendente, o secondo un'espressione personalizzata: ```php -$table->order('field1'); // ORDER BY `field1` -$table->order('field1 DESC, field2'); // ORDER BY `field1` DESC, `field2` -$table->order('field = ? DESC', 123); // ORDER BY `field` = 123 DESC +$table->order('created'); // ORDER BY `created` +$table->order('created DESC'); // ORDER BY `created` DESC +$table->order('priority DESC, created'); // ORDER BY `priority` DESC, `created` +$table->order('status = ? DESC', 'active'); // ORDER BY `status` = 'active' DESC ``` -select() .[#toc-select] ------------------------ +select(string $columns, ...$parameters): static .[method] +--------------------------------------------------------- + +Specifica le colonne che devono essere restituite dal database. Per impostazione predefinita, Nette Database Explorer restituisce solo le colonne che vengono effettivamente utilizzate nel codice. Il metodo `select()` viene quindi utilizzato nei casi in cui è necessario restituire espressioni specifiche: -Esempi di utilizzo: +```php +// SELECT *, DATE_FORMAT(`created_at`, "%d.%m.%Y") AS `formatted_date` +$table->select('*, DATE_FORMAT(created_at, ?) AS formatted_date', '%d.%m.%Y'); +``` + +Gli alias definiti tramite `AS` sono quindi disponibili come proprietà dell'oggetto ActiveRow: ```php -$table->select('field1'); // SELECT `field1` -$table->select('col, UPPER(col) AS abc'); // SELECT `col`, UPPER(`col`) AS abc -$table->select('SUBSTR(title, ?)', 3); // SELECT SUBSTR(`title`, 3) +foreach ($table as $row) { + echo $row->formatted_date; // accesso all'alias +} ``` -limit() .[#toc-limit] ---------------------- +limit(?int $limit, ?int $offset = null): static .[method] +--------------------------------------------------------- -Esempi di utilizzo: +Limita il numero di righe restituite (LIMIT) e opzionalmente consente di impostare un offset: ```php -$table->limit(1); // LIMIT 1 -$table->limit(1, 10); // LIMIT 1 OFFSET 10 +$table->limit(10); // LIMIT 10 (restituisce le prime 10 righe) +$table->limit(10, 20); // LIMIT 10 OFFSET 20 ``` +Per la paginazione è preferibile utilizzare il metodo `page()`. -page() .[#toc-page] -------------------- -Un modo alternativo per impostare il limite e l'offset: +page(int $page, int $itemsPerPage, &$numOfPages = null): static .[method] +------------------------------------------------------------------------- + +Facilita la paginazione dei risultati. Accetta il numero di pagina (contato da 1) e il numero di elementi per pagina. Opzionalmente è possibile passare un riferimento a una variabile in cui verrà memorizzato il numero totale di pagine: ```php -$page = 5; -$itemsPerPage = 10; -$table->page($page, $itemsPerPage); // LIMIT 10 OFFSET 40 +$numOfPages = null; +$table->page(page: 3, itemsPerPage: 10, $numOfPages); +echo "Pagine totali: $numOfPages"; ``` -Ottenere il numero dell'ultima pagina, passato alla variabile `$lastPage`: + +group(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- + +Raggruppa le righe in base alle colonne specificate (GROUP BY). Viene utilizzato solitamente in combinazione con funzioni di aggregazione: ```php -$table->page($page, $itemsPerPage, $lastPage); +// Conta il numero di prodotti in ogni categoria +$table->select('category_id, COUNT(*) AS count') + ->group('category_id'); ``` -group() .[#toc-group] ---------------------- +having(string $having, ...$parameters): static .[method] +-------------------------------------------------------- -Esempi di utilizzo: +Imposta una condizione per filtrare le righe raggruppate (HAVING). Può essere utilizzata in combinazione con il metodo `group()` e le funzioni di aggregazione: ```php -$table->group('field1'); // GROUP BY `field1` -$table->group('field1, field2'); // GROUP BY `field1`, `field2` +// Trova le categorie che hanno più di 100 prodotti +$table->select('category_id, COUNT(*) AS count') + ->group('category_id') + ->having('count > ?', 100); ``` -avere() .[#toc-having] ----------------------- +Lettura dei dati +================ + +Per leggere i dati dal database abbiamo a disposizione diversi metodi utili: + +.[language-php] +| `foreach ($table as $key => $row)` | Itera su tutte le righe, `$key` è il valore della chiave primaria, `$row` è l'oggetto ActiveRow +| `$row = $table->get($key)` | Restituisce una riga in base alla chiave primaria +| `$row = $table->fetch()` | Restituisce la riga corrente e sposta il puntatore alla successiva +| `$array = $table->fetchPairs()` | Crea un array associativo dai risultati +| `$array = $table->fetchAll()` | Restituisce tutte le righe come array +| `count($table)` | Restituisce il numero di righe nell'oggetto Selection -Esempi di utilizzo: +L'oggetto [ActiveRow |api:Nette\Database\Table\ActiveRow] è destinato solo alla lettura. Ciò significa che non è possibile modificare i valori delle sue proprietà. Questa limitazione garantisce la coerenza dei dati e previene effetti collaterali imprevisti. I dati vengono caricati dal database e qualsiasi modifica dovrebbe essere eseguita esplicitamente e in modo controllato. + + +`foreach` - iterazione su tutte le righe +---------------------------------------- + +Il modo più semplice per eseguire una query e ottenere le righe è iterare in un ciclo `foreach`. Avvia automaticamente la query SQL. ```php -$table->having('COUNT(items) >', 100); // HAVING COUNT(`items`) > 100 +$books = $explorer->table('book'); +foreach ($books as $key => $book) { + // $key è il valore della chiave primaria, $book è ActiveRow + echo "$book->title ({$book->author->name})"; +} ``` -Filtrare in base al valore di un'altra tabella .[#toc-joining-key] ------------------------------------------------------------------- +get($key): ?ActiveRow .[method] +------------------------------- -Molto spesso è necessario filtrare i risultati in base a una condizione che coinvolge un'altra tabella del database. Questi tipi di condizioni richiedono join di tabelle. Tuttavia, non è più necessario scriverle. +Esegue la query SQL e restituisce la riga in base alla chiave primaria, o `null` se non esiste. -Supponiamo di dover ottenere tutti i libri il cui autore si chiama 'Jon'. Tutto ciò che occorre scrivere è la chiave di unione della relazione e il nome della colonna nella tabella unita. La chiave di unione deriva dalla colonna che si riferisce alla tabella che si vuole unire. Nel nostro esempio (si veda lo schema del db) si tratta della colonna `author_id`, ed è sufficiente utilizzarne solo la prima parte - `author` (il suffisso `_id` può essere omesso). `name` è una colonna della tabella `author` che vogliamo utilizzare. Una condizione per il traduttore di libri (che è collegata alla colonna `translator_id` ) può essere creata altrettanto facilmente. +```php +$book = $explorer->table('book')->get(123); // restituisce ActiveRow con ID 123 o null +if ($book) { + echo $book->title; +} +``` + + +fetch(): ?ActiveRow .[method] +----------------------------- + +Restituisce una riga e sposta il puntatore interno alla successiva. Se non esistono più altre righe, restituisce `null`. ```php $books = $explorer->table('book'); -$books->where('author.name LIKE ?', '%Jon%'); -$books->where('translator.name', 'David Grudl'); +while ($book = $books->fetch()) { + $this->processBook($book); +} ``` -La logica delle chiavi di unione è guidata dall'implementazione delle [Convenzioni |api:Nette\Database\Conventions]. Si consiglia di utilizzare [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions], che analizza le chiavi esterne e consente di lavorare facilmente con queste relazioni. -La relazione tra il libro e il suo autore è 1:N. È possibile anche la relazione inversa. La chiamiamo **backjoin**. Vediamo un altro esempio. Vogliamo recuperare tutti gli autori che hanno scritto più di 3 libri. Per rendere la join inversa, utilizziamo l'istruzione `:` (colon). Colon means that the joined relationship means hasMany (and it's quite logical too, as two dots are more than one dot). Unfortunately, the Selection class isn't smart enough, so we have to help with the aggregation and provide a `GROUP BY` e anche la condizione deve essere scritta sotto forma di istruzione `HAVING`. +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Restituisce i risultati come array associativo. Il primo argomento specifica il nome della colonna che verrà utilizzata come chiave nell'array, il secondo argomento specifica il nome della colonna che verrà utilizzata come valore: ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book.id) > 3'); +$authors = $explorer->table('author')->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] ``` -Si sarà notato che l'espressione di join si riferisce al libro, ma non è chiaro se il join avvenga attraverso `author_id` o `translator_id`. Nell'esempio precedente, Selection si unisce attraverso la colonna `author_id` perché è stata trovata una corrispondenza con la tabella di origine - la tabella `author`. Se non ci fosse tale corrispondenza e ci fossero più possibilità, Nette lancerebbe [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. +Se specifichiamo solo il primo parametro, il valore sarà l'intera riga, ovvero l'oggetto `ActiveRow`: -Per effettuare un join attraverso la colonna `translator_id`, fornire un parametro opzionale all'interno dell'espressione di join. +```php +$authors = $explorer->table('author')->fetchPairs('id'); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` + +In caso di chiavi duplicate, verrà utilizzato il valore dell'ultima riga. Utilizzando `null` come chiave, l'array sarà indicizzato numericamente da zero (quindi non si verificano collisioni): ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book(translator).id) > 3'); +$authors = $explorer->table('author')->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] ``` -Vediamo alcune espressioni di unione più difficili. -Vogliamo trovare tutti gli autori che hanno scritto qualcosa su PHP. Tutti i libri hanno un tag, quindi dovremmo selezionare gli autori che hanno scritto un libro con il tag PHP. +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +In alternativa, potete specificare come parametro un callback, che per ogni riga restituirà o il valore stesso, o una coppia chiave-valore. ```php -$authors = $explorer->table('author'); -$authors->where(':book:book_tags.tag.name', 'PHP') - ->group('author.id') - ->having('COUNT(:book:book_tags.tag.id) > 0'); +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => "$row->title ({$row->author->name})"); +// ['Primo libro (Jan Novák)', ...] + +// Il callback può anche restituire un array con una coppia chiave & valore: +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => [$row->title, $row->author->name]); +// ['Primo libro' => 'Jan Novák', ...] ``` -Query aggregate .[#toc-aggregate-queries] ------------------------------------------ +fetchAll(): array .[method] +--------------------------- -| `$table->count('*')` | Ottenere il numero di righe -| `$table->count("DISTINCT $column")` | Ottieni il numero di valori distinti -| `$table->min($column)` | Ottieni il valore minimo -| `$table->max($column)` | Ottieni il valore massimo -| `$table->sum($column)` | Ottenere la somma di tutti i valori -| `$table->aggregation("GROUP_CONCAT($column)")` | Eseguire qualsiasi funzione di aggregazione +Restituisce tutte le righe come array associativo di oggetti `ActiveRow`, dove le chiavi sono i valori delle chiavi primarie. + +```php +$allBooks = $explorer->table('book')->fetchAll(); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` + + +count(): int .[method] +---------------------- + +Il metodo `count()` senza parametro restituisce il numero di righe nell'oggetto `Selection`: + +```php +$table->where('category', 1); +$count = $table->count(); +$count = count($table); // alternativa +``` + +Attenzione, `count()` con parametro esegue la funzione di aggregazione COUNT nel database, vedi sotto. + + +ActiveRow::toArray(): array .[method] +------------------------------------- + +Converte l'oggetto `ActiveRow` in un array associativo, dove le chiavi sono i nomi delle colonne e i valori sono i dati corrispondenti. + +```php +$book = $explorer->table('book')->get(1); +$bookArray = $book->toArray(); +// $bookArray sarà ['id' => 1, 'title' => '...', 'author_id' => ..., ...] +``` + + +Aggregazione +============ + +La classe `Selection` fornisce metodi per eseguire facilmente funzioni di aggregazione (COUNT, SUM, MIN, MAX, AVG ecc.). + +.[language-php] +| `count($expr)` | Conta il numero di righe +| `min($expr)` | Restituisce il valore minimo nella colonna +| `max($expr)` | Restituisce il valore massimo nella colonna +| `sum($expr)` | Restituisce la somma dei valori nella colonna +| `aggregation($function)` | Permette di eseguire qualsiasi funzione di aggregazione. Ad esempio, `AVG()`, `GROUP_CONCAT()` + + +count(string $expr): int .[method] +---------------------------------- + +Esegue una query SQL con la funzione COUNT e restituisce il risultato. Il metodo viene utilizzato per determinare quante righe corrispondono a una determinata condizione: + +```php +$count = $table->count('*'); // SELECT COUNT(*) FROM `table` +$count = $table->count('DISTINCT column'); // SELECT COUNT(DISTINCT `column`) FROM `table` +``` + +Attenzione, [#count()] senza parametro restituisce solo il numero di righe nell'oggetto `Selection`. + + +min(string $expr) e max(string $expr) .[method] +----------------------------------------------- + +I metodi `min()` e `max()` restituiscono il valore minimo e massimo nella colonna o espressione specificata: + +```php +// SELECT MAX(`price`) FROM `products` WHERE `active` = 1 +$maxPrice = $products->where('active', true) + ->max('price'); +``` -.[caution] -Il metodo `count()` senza parametri specificati seleziona tutti i record e restituisce la dimensione dell'array, il che è molto inefficiente. Ad esempio, se è necessario calcolare il numero di righe per la paginazione, specificare sempre il primo argomento. +sum(string $expr) .[method] +--------------------------- -Escaping e citazione .[#toc-escaping-quoting] -============================================= +Restituisce la somma dei valori nella colonna o espressione specificata: -Database Explorer è intelligente e consente di sfuggire ai parametri e agli identificatori di virgolette. Tuttavia, è necessario seguire queste regole di base: +```php +// SELECT SUM(`price` * `items_in_stock`) FROM `products` WHERE `active` = 1 +$totalPrice = $products->where('active', true) + ->sum('price * items_in_stock'); +``` + + +aggregation(string $function, ?string $groupFunction = null) .[method] +---------------------------------------------------------------------- -- le parole chiave, le funzioni e le procedure devono essere maiuscole -- le colonne e le tabelle devono essere minuscole -- Passare le variabili come parametri, non concatenarle. +Permette di eseguire qualsiasi funzione di aggregazione. ```php -->where('name like ?', 'John'); // WRONG! generates: `name` `like` ? -->where('name LIKE ?', 'John'); // CORRECT +// prezzo medio dei prodotti nella categoria +$avgPrice = $products->where('category_id', 1) + ->aggregation('AVG(price)'); -->where('KEY = ?', $value); // WRONG! KEY is a keyword -->where('key = ?', $value); // CORRECT. generates: `key` = ? +// unisce le etichette del prodotto in un'unica stringa +$tags = $products->where('id', 1) + ->aggregation('GROUP_CONCAT(tag.name) AS tags') + ->fetch() + ->tags; +``` -->where('name = ' . $name); // WRONG! sql injection! -->where('name = ?', $name); // CORRECT +Se abbiamo bisogno di aggregare risultati che sono già essi stessi derivati da qualche funzione di aggregazione e raggruppamento (ad es. `SUM(valore)` su righe raggruppate), come secondo argomento specifichiamo la funzione di aggregazione che deve essere applicata a questi risultati intermedi: -->select('DATE_FORMAT(created, "%d.%m.%Y")'); // WRONG! pass variables as parameters, do not concatenate -->select('DATE_FORMAT(created, ?)', '%d.%m.%Y'); // CORRECT +```php +// Calcola il prezzo totale dei prodotti in magazzino per ogni categoria e poi somma questi prezzi insieme. +$totalPrice = $products->select('category_id, SUM(price * stock) AS category_total') + ->group('category_id') + ->aggregation('SUM(category_total)', 'SUM'); ``` -.[warning] -Un uso errato può produrre falle nella sicurezza +In questo esempio, prima calcoliamo il prezzo totale dei prodotti in ogni categoria (`SUM(price * stock) AS category_total`) e raggruppiamo i risultati per `category_id`. Poi utilizziamo `aggregation('SUM(category_total)', 'SUM')` per sommare questi subtotali `category_total`. Il secondo argomento `'SUM'` dice che la funzione SUM deve essere applicata ai risultati intermedi. + + +Insert, Update & Delete +======================= +Nette Database Explorer semplifica l'inserimento, l'aggiornamento e l'eliminazione dei dati. Tutti i metodi menzionati lanciano un'eccezione `Nette\Database\DriverException` in caso di errore. -Recuperare i dati .[#toc-fetching-data] -======================================= -| `foreach ($table as $id => $row)` | Iterare su tutte le righe del risultato -| `$row = $table->get($id)` | Ottenere una singola riga con ID $id dalla tabella -| `$row = $table->fetch()` | Ottenere la riga successiva dal risultato -| `$array = $table->fetchPairs($key, $value)` | Recuperare tutti i valori in un array associativo -| `$array = $table->fetchPairs($key)` | Recupera tutte le righe nell'array associativo -| `count($table)` | Ottenere il numero di righe nell'insieme dei risultati +Selection::insert(iterable $data) .[method] +------------------------------------------- +Inserisce nuovi record nella tabella. -Inserire, aggiornare e cancellare .[#toc-insert-update-delete] -============================================================== +**Inserimento di un singolo record:** -Il metodo `insert()` accetta una serie di oggetti Traversable (ad esempio [ArrayHash |utils:arrays#ArrayHash] che restituisce i [moduli |forms:]): +Passiamo il nuovo record come array associativo o oggetto iterabile (ad esempio ArrayHash utilizzato nei [moduli |forms:]), dove le chiavi corrispondono ai nomi delle colonne nella tabella. + +Se la tabella ha una chiave primaria definita, il metodo restituisce un oggetto `ActiveRow`, che viene ricaricato dal database per tenere conto di eventuali modifiche apportate a livello di database (trigger, valori predefiniti delle colonne, calcoli delle colonne auto-increment). Questo garantisce la coerenza dei dati e l'oggetto contiene sempre i dati attuali dal database. Se non ha una chiave primaria univoca, restituisce i dati passati sotto forma di array. ```php $row = $explorer->table('users')->insert([ - 'name' => $name, - 'year' => $year, + 'name' => 'John Doe', + 'email' => 'john.doe@example.com', ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) +// $row è un'istanza di ActiveRow e contiene i dati completi della riga inserita, +// incluso l'ID generato automaticamente e eventuali modifiche apportate dai trigger +echo $row->id; // Stampa l'ID dell'utente appena inserito +echo $row->created_at; // Stampa l'ora di creazione, se impostata da un trigger ``` -Se la chiave primaria è definita sulla tabella, viene restituito un oggetto ActiveRow contenente la riga inserita. +**Inserimento di più record contemporaneamente:** -Inserimento multiplo: +Il metodo `insert()` consente di inserire più record tramite un'unica query SQL. In questo caso, restituisce il numero di righe inserite. ```php -$explorer->table('users')->insert([ +$insertedRows = $explorer->table('users')->insert([ + [ + 'name' => 'John', + 'year' => 1994, + ], [ - 'name' => 'Jim', - 'year' => 1978, - ], [ 'name' => 'Jack', - 'year' => 1987, + 'year' => 1995, ], ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) +// INSERT INTO `users` (`name`, `year`) VALUES ('John', 1994), ('Jack', 1995) +// $insertedRows sarà 2 +``` + +Come parametro è possibile passare anche un oggetto `Selection` con una selezione di dati. + +```php +$newUsers = $explorer->table('potential_users') + ->where('approved', 1) + ->select('name, email'); + +$insertedRows = $explorer->table('users')->insert($newUsers); ``` -I file o gli oggetti DateTime possono essere passati come parametri: +**Inserimento di valori speciali:** + +Come valori possiamo passare anche file, oggetti DateTime o letterali SQL: ```php $explorer->table('users')->insert([ - 'name' => $name, - 'created' => new DateTime, // or $explorer::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // inserts the file + 'name' => 'John', + 'created_at' => new DateTime, // converte nel formato del database + 'avatar' => fopen('image.jpg', 'rb'), // inserisce il contenuto binario del file + 'uuid' => $explorer::literal('UUID()'), // chiama la funzione UUID() ]); ``` -Aggiornamento (restituisce il conteggio delle righe interessate): + +Selection::update(iterable $data): int .[method] +------------------------------------------------ + +Aggiorna le righe nella tabella secondo il filtro specificato. Restituisce il numero di righe effettivamente modificate. + +Passiamo le colonne da modificare come array associativo o oggetto iterabile (ad esempio ArrayHash utilizzato nei [moduli |forms:]), dove le chiavi corrispondono ai nomi delle colonne nella tabella: ```php -$count = $explorer->table('users') - ->where('id', 10) // must be called before update() +$affected = $explorer->table('users') + ->where('id', 10) ->update([ - 'name' => 'Ned Stark' + 'name' => 'John Smith', + 'year' => 1994, ]); -// UPDATE `users` SET `name`='Ned Stark' WHERE (`id` = 10) +// UPDATE `users` SET `name` = 'John Smith', `year` = 1994 WHERE `id` = 10 ``` -Per l'aggiornamento si possono utilizzare gli operatori `+=` e `-=`: +Per modificare i valori numerici possiamo utilizzare gli operatori `+=` e `-=`: ```php $explorer->table('users') + ->where('id', 10) ->update([ - 'age+=' => 1, // see += + 'points+=' => 1, // aumenta il valore della colonna 'points' di 1 + 'coins-=' => 1, // diminuisce il valore della colonna 'coins' di 1 ]); -// UPDATE users SET `age` = `age` + 1 +// UPDATE `users` SET `points` = `points` + 1, `coins` = `coins` - 1 WHERE `id` = 10 ``` -Eliminazione (restituisce il conteggio delle righe eliminate): + +Selection::delete(): int .[method] +---------------------------------- + +Elimina le righe dalla tabella secondo il filtro specificato. Restituisce il numero di righe eliminate. ```php $count = $explorer->table('users') ->where('id', 10) ->delete(); -// DELETE FROM `users` WHERE (`id` = 10) +// DELETE FROM `users` WHERE `id` = 10 ``` +.[caution] +Quando si chiamano `update()` e `delete()`, non dimenticate di specificare tramite `where()` le righe che devono essere modificate/eliminate. Se non si utilizza `where()`, l'operazione verrà eseguita sull'intera tabella! -Lavorare con le relazioni .[#toc-working-with-relationships] -============================================================ +ActiveRow::update(iterable $data): bool .[method] +------------------------------------------------- -Ha una relazione .[#toc-has-one-relation] ------------------------------------------ -La relazione Has one è un caso d'uso comune. Il libro *ha un* autore. Il libro *ha un* traduttore. L'ottenimento di una riga correlata avviene principalmente con il metodo `ref()`. Accetta due argomenti: il nome della tabella di destinazione e la colonna di unione di origine. Vedere l'esempio: +Aggiorna i dati nella riga del database rappresentata dall'oggetto `ActiveRow`. Come parametro accetta un iterabile con i dati da aggiornare (le chiavi sono i nomi delle colonne). Per modificare i valori numerici possiamo utilizzare gli operatori `+=` e `-=`: + +Dopo l'esecuzione dell'aggiornamento, `ActiveRow` viene automaticamente ricaricato dal database per tenere conto di eventuali modifiche apportate a livello di database (ad es. trigger). Il metodo restituisce true solo se si è verificata una modifica effettiva dei dati. ```php -$book = $explorer->table('book')->get(1); -$book->ref('author', 'author_id'); +$article = $explorer->table('article')->get(1); +$article->update([ + 'views += 1', // aumentiamo il numero di visualizzazioni +]); +echo $article->views; // Stampa il numero attuale di visualizzazioni ``` -Nell'esempio precedente si recupera la voce relativa all'autore dalla tabella `author`; la chiave primaria dell'autore viene cercata dalla colonna `book.author_id`. Il metodo Ref() restituisce un'istanza di ActiveRow o null se non esiste una voce appropriata. La riga restituita è un'istanza di ActiveRow, quindi si può lavorare con essa come con la voce del libro. +Questo metodo aggiorna solo una riga specifica nel database. Per l'aggiornamento massivo di più righe, utilizzate il metodo [#Selection::update()]. + + +ActiveRow::delete() .[method] +----------------------------- + +Elimina la riga dal database rappresentata dall'oggetto `ActiveRow`. ```php -$author = $book->ref('author', 'author_id'); -$author->name; -$author->born; +$book = $explorer->table('book')->get(1); +$book->delete(); // Elimina il libro con ID 1 +``` + +Questo metodo elimina solo una riga specifica nel database. Per l'eliminazione massiva di più righe, utilizzate il metodo [#Selection::delete()]. + + +Relazioni tra tabelle +===================== + +Nei database relazionali, i dati sono divisi in più tabelle e collegati tra loro tramite chiavi esterne. Nette Database Explorer introduce un modo rivoluzionario per lavorare con queste relazioni - senza scrivere query JOIN e senza la necessità di configurare o generare nulla. + +Per illustrare il lavoro con le relazioni, utilizzeremo un esempio di database di libri ([lo trovate su GitHub |https://github.com/nette-examples/books]). Nel database abbiamo le tabelle: + +- `author` - scrittori e traduttori (colonne `id`, `name`, `web`, `born`) +- `book` - libri (colonne `id`, `author_id`, `translator_id`, `title`, `sequel_id`) +- `tag` - etichette (colonne `id`, `name`) +- `book_tag` - tabella di collegamento tra libri ed etichette (colonne `book_id`, `tag_id`) + +[* db-schema-1-.webp *] *** Struttura del database utilizzata negli esempi *** + +Nel nostro esempio di database di libri troviamo diversi tipi di relazioni (sebbene il modello sia semplificato rispetto alla realtà): + +- One-to-many 1:N – ogni libro **ha un** autore, un autore può scrivere **diversi** libri +- Zero-to-many 0:N – un libro **può avere** un traduttore, un traduttore può tradurre **diversi** libri +- Zero-to-one 0:1 – un libro **può avere** un seguito +- Many-to-many M:N – un libro **può avere diverse** etichette e un'etichetta può essere assegnata a **diversi** libri + +In queste relazioni esiste sempre una tabella padre e una figlia. Ad esempio, nella relazione tra autore e libro, la tabella `author` è padre e `book` è figlia - possiamo immaginarlo come se il libro "appartenesse" sempre a qualche autore. Questo si riflette anche nella struttura del database: la tabella figlia `book` contiene la chiave esterna `author_id`, che fa riferimento alla tabella padre `author`. -// o direttamente -$book->ref('author', 'author_id')->name; -$book->ref('author', 'author_id')->born; +Se abbiamo bisogno di visualizzare i libri inclusi i nomi dei loro autori, abbiamo due possibilità. O otteniamo i dati con un'unica query SQL tramite JOIN: + +```sql +SELECT book.*, author.name FROM book LEFT JOIN author ON book.author_id = author.id +``` + +Oppure carichiamo i dati in due passaggi - prima i libri e poi i loro autori - e poi li assembliamo in PHP: + +```sql +SELECT * FROM book; +SELECT * FROM author WHERE id IN (1, 2, 3); -- id degli autori dei libri ottenuti ``` -Il libro ha anche un traduttore, quindi ottenere il nome del traduttore è abbastanza facile. +Il secondo approccio è in realtà più efficiente, anche se può sorprendere. I dati vengono caricati solo una volta e possono essere utilizzati meglio nella cache. È proprio in questo modo che lavora Nette Database Explorer - risolve tutto sotto il cofano e vi offre un'API elegante: + ```php -$book->ref('author', 'translator_id')->name +$books = $explorer->table('book'); +foreach ($books as $book) { + echo 'title: ' . $book->title; + echo 'written by: ' . $book->author->name; // $book->author è un record della tabella 'author' + echo 'translated by: ' . $book->translator?->name; +} ``` -Tutto questo va bene, ma è un po' macchinoso, non credete? Database Explorer contiene già le definizioni delle chiavi esterne, quindi perché non usarle automaticamente? Facciamolo! -Se chiamiamo una proprietà che non esiste, ActiveRow cerca di risolvere il nome della proprietà chiamante come una relazione 'ha una'. Ottenere questa proprietà equivale a chiamare il metodo ref() con un solo argomento. Chiameremo l'unico argomento **chiave**. La chiave sarà risolta in una particolare relazione di chiave esterna. La chiave passata viene confrontata con le colonne della riga e, se corrisponde, la chiave esterna definita sulla colonna corrispondente viene utilizzata per ottenere i dati dalla tabella di destinazione. Vedere l'esempio: +Accesso alla tabella padre +-------------------------- + +L'accesso alla tabella padre è diretto. Si tratta di relazioni come *il libro ha un autore* o *il libro può avere un traduttore*. Otteniamo il record correlato tramite la proprietà dell'oggetto ActiveRow - il suo nome corrisponde al nome della colonna con la chiave esterna senza `id`: ```php -$book->author->name; -// come -$book->ref('author')->name; +$book = $explorer->table('book')->get(1); +echo $book->author->name; // trova l'autore in base alla colonna author_id +echo $book->translator?->name; // trova il traduttore in base a translator_id ``` -L'istanza ActiveRow non ha una colonna autore. Tutte le colonne dei libri vengono cercate per trovare una corrispondenza con *chiave*. In questo caso, la corrispondenza significa che il nome della colonna deve contenere la chiave. Quindi, nell'esempio precedente, la colonna `author_id` contiene la stringa 'autore' ed è quindi abbinata alla chiave 'autore'. Se si desidera ottenere il traduttore del libro, è sufficiente utilizzare, ad esempio, 'traduttore' come chiave, perché la chiave 'traduttore' corrisponderà alla colonna `translator_id`. Per ulteriori informazioni sulla logica di corrispondenza delle chiavi, consultare il capitolo [Espressioni di unione |#joining-key]. +Quando accediamo alla proprietà `$book->author`, Explorer cerca nella tabella `book` una colonna il cui nome contiene la stringa `author` (cioè `author_id`). In base al valore in questa colonna, carica il record corrispondente dalla tabella `author` e lo restituisce come `ActiveRow`. Funziona in modo simile anche `$book->translator`, che utilizza la colonna `translator_id`. Poiché la colonna `translator_id` può contenere `null`, utilizziamo nel codice l'operatore `?->`. + +Un percorso alternativo è offerto dal metodo `ref()`, che accetta due argomenti, il nome della tabella di destinazione e il nome della colonna di collegamento, e restituisce un'istanza di `ActiveRow` o `null`: ```php -echo $book->title . ': '; -echo $book->author->name; -if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; -} +echo $book->ref('author', 'author_id')->name; // relazione con l'autore +echo $book->ref('author', 'translator_id')->name; // relazione con il traduttore ``` -Se si desidera recuperare più libri, si deve utilizzare lo stesso approccio. Nette Database Explorer recupererà autori e traduttori per tutti i libri recuperati in una sola volta. +Il metodo `ref()` è utile se non è possibile utilizzare l'accesso tramite proprietà, perché la tabella contiene una colonna con lo stesso nome (cioè `author`). Negli altri casi è consigliato utilizzare l'accesso tramite proprietà, che è più leggibile. + +Explorer ottimizza automaticamente le query al database. Quando iteriamo sui libri in un ciclo e accediamo ai loro record correlati (autori, traduttori), Explorer non genera una query per ogni libro separatamente. Invece, esegue solo un SELECT per ogni tipo di relazione, riducendo significativamente il carico sul database. Ad esempio: ```php $books = $explorer->table('book'); foreach ($books as $book) { echo $book->title . ': '; echo $book->author->name; - if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; - } + echo $book->translator?->name; } ``` -Il codice eseguirà solo queste 3 query: +Questo codice chiamerà solo queste tre velocissime query al database: + ```sql SELECT * FROM `book`; -SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- ids of fetched books from author_id column -SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- ids of fetched books from translator_id column +SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- id dalla colonna author_id dei libri selezionati +SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- id dalla colonna translator_id dei libri selezionati ``` +.[note] +La logica di ricerca della colonna di collegamento è data dall'implementazione di [Conventions |api:Nette\Database\Conventions]. Consigliamo l'uso di [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions], che analizza le chiavi esterne e consente di lavorare semplicemente con le relazioni esistenti tra le tabelle. -Ha molte relazioni .[#toc-has-many-relation] --------------------------------------------- -La relazione "ha molti" è solo un'inversione della relazione "ha uno". L'autore *ha* scritto *molti* libri. L'autore *ha* tradotto *molti* libri. Come si può vedere, questo tipo di relazione è un po' più difficile perché la relazione è 'nominativa' ('scritto', 'tradotto'). L'istanza ActiveRow ha il metodo `related()`, che restituisce un array di voci correlate. Anche le voci sono istanze di ActiveRow. Vedere l'esempio qui sotto: +Accesso alla tabella figlia +--------------------------- + +L'accesso alla tabella figlia funziona nella direzione opposta. Ora ci chiediamo *quali libri ha scritto questo autore* o *ha tradotto questo traduttore*. Per questo tipo di query utilizziamo il metodo `related()`, che restituisce una `Selection` con i record correlati. Vediamo un esempio: ```php -$author = $explorer->table('author')->get(11); -echo $author->name . ' has written:'; +$author = $explorer->table('author')->get(1); +// Stampa tutti i libri dell'autore foreach ($author->related('book.author_id') as $book) { - echo $book->title; + echo "Ha scritto: $book->title"; } -echo 'and translated:'; +// Stampa tutti i libri tradotti dall'autore foreach ($author->related('book.translator_id') as $book) { - echo $book->title; + echo "Ha tradotto: $book->title"; } ``` -Metodo `related()` Il metodo accetta la descrizione completa del join passata come due argomenti o come un argomento unito da un punto. Il primo argomento è la tabella di destinazione, il secondo è la colonna di destinazione. +Il metodo `related()` accetta la descrizione della connessione come un unico argomento con notazione a punti o come due argomenti separati: ```php -$author->related('book.translator_id'); -// come -$author->related('book', 'translator_id'); +$author->related('book.translator_id'); // un argomento +$author->related('book', 'translator_id'); // due argomenti ``` -È possibile utilizzare l'euristica di Nette Database Explorer basata sulle chiavi esterne e fornire solo l'argomento **chiave**. La chiave verrà confrontata con tutte le chiavi esterne che puntano alla tabella corrente (tabella`author` ). Se c'è una corrispondenza, Nette Database Explorer utilizzerà questa chiave esterna, altrimenti lancerà [Nette\InvalidArgumentException |api:Nette\InvalidArgumentException] o [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. Per ulteriori informazioni sulla logica di corrispondenza delle chiavi, consultare il capitolo [Espressioni di unione |#joining-key]. +Explorer è in grado di rilevare automaticamente la colonna di collegamento corretta in base al nome della tabella padre. In questo caso, si collega tramite la colonna `book.author_id`, poiché il nome della tabella di origine è `author`: -Naturalmente è possibile chiamare i metodi correlati per tutti gli autori recuperati; Nette Database Explorer recupererà nuovamente i libri appropriati in una sola volta. +```php +$author->related('book'); // usa book.author_id +``` + +Se esistessero più connessioni possibili, Explorer lancerebbe un'eccezione [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. + +Il metodo `related()` può ovviamente essere utilizzato anche iterando su più record in un ciclo e Explorer anche in questo caso ottimizza automaticamente le query: ```php $authors = $explorer->table('author'); foreach ($authors as $author) { - echo $author->name . ' has written:'; + echo $author->name . ' ha scritto:'; foreach ($author->related('book') as $book) { - $book->title; + echo $book->title; } } ``` -L'esempio precedente eseguirà solo due query: +Questo codice genererà solo due velocissime query SQL: ```sql SELECT * FROM `author`; -SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- ids of fetched authors +SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- id degli autori selezionati +``` + + +Relazione Many-to-many +---------------------- + +Per la relazione many-to-many (M:N) è necessaria l'esistenza di una tabella di collegamento (nel nostro caso `book_tag`), che contiene due colonne con chiavi esterne (`book_id`, `tag_id`). Ciascuna di queste colonne fa riferimento alla chiave primaria di una delle tabelle collegate. Per ottenere i dati correlati, otteniamo prima i record dalla tabella di collegamento tramite `related('book_tag')` e poi proseguiamo verso i dati di destinazione: + +```php +$book = $explorer->table('book')->get(1); +// stampa i nomi dei tag assegnati al libro +foreach ($book->related('book_tag') as $bookTag) { + echo $bookTag->tag->name; // stampa il nome del tag tramite la tabella di collegamento +} + +$tag = $explorer->table('tag')->get(1); +// o viceversa: stampa i nomi dei libri contrassegnati da questo tag +foreach ($tag->related('book_tag') as $bookTag) { + echo $bookTag->book->title; // stampa il titolo del libro +} ``` +Explorer ottimizza nuovamente le query SQL in una forma efficiente: + +```sql +SELECT * FROM `book`; +SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 2, ...)); -- id dei libri selezionati +SELECT * FROM `tag` WHERE (`tag`.`id` IN (1, 2, ...)); -- id dei tag trovati in book_tag +``` -Creazione manuale di Explorer .[#toc-creating-explorer-manually] -================================================================ -È possibile creare una connessione al database utilizzando la configurazione dell'applicazione. In questi casi viene creato un servizio `Nette\Database\Explorer`, che può essere passato come dipendenza tramite il contenitore DI. +Interrogazione tramite tabelle correlate +---------------------------------------- -Tuttavia, se Nette Database Explorer viene utilizzato come strumento autonomo, è necessario creare manualmente un'istanza dell'oggetto `Nette\Database\Explorer`. +Nei metodi `where()`, `select()`, `order()` e `group()` possiamo utilizzare notazioni speciali per accedere alle colonne di altre tabelle. Explorer creerà automaticamente i JOIN necessari. + +La **notazione a punti** (`tabella_padre.colonna`) viene utilizzata per la relazione 1:N dal punto di vista della tabella figlia: ```php -// $storage implementa Nette\Caching\Storage: -$storage = new Nette\Caching\Storages\FileStorage($tempDir); -$connection = new Nette\Database\Connection($dsn, $user, $password); -$structure = new Nette\Database\Structure($connection, $storage); -$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); -$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +$books = $explorer->table('book'); + +// Trova i libri il cui autore ha un nome che inizia per 'Jon' +$books->where('author.name LIKE ?', 'Jon%'); + +// Ordina i libri per nome dell'autore in ordine decrescente +$books->order('author.name DESC'); + +// Stampa il titolo del libro e il nome dell'autore +$books->select('book.title, author.name'); ``` + +La **notazione a due punti** (`:tabella_figlia.colonna`) viene utilizzata per la relazione 1:N dal punto di vista della tabella padre: + +```php +$authors = $explorer->table('author'); + +// Trova gli autori che hanno scritto un libro con 'PHP' nel titolo +$authors->where(':book.title LIKE ?', '%PHP%'); + +// Conta il numero di libri per ogni autore +$authors->select('*, COUNT(:book.id) AS book_count') + ->group('author.id'); +``` + +Nell'esempio sopra con la notazione a due punti (`:book.title`), non è specificata la colonna con la chiave esterna. Explorer rileva automaticamente la colonna corretta in base al nome della tabella padre. In questo caso, si collega tramite la colonna `book.author_id`, poiché il nome della tabella di origine è `author`. Se esistessero più connessioni possibili, Explorer lancerebbe un'eccezione [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. + +La colonna di collegamento può essere specificata esplicitamente tra parentesi: + +```php +// Trova gli autori che hanno tradotto un libro con 'PHP' nel titolo +$authors->where(':book(translator_id).title LIKE ?', '%PHP%'); +``` + +Le notazioni possono essere concatenate per accedere tramite più tabelle: + +```php +// Trova gli autori dei libri contrassegnati con il tag 'PHP' +$authors->where(':book:book_tag.tag.name', 'PHP') + ->group('author.id'); +``` + + +Estensione delle condizioni per JOIN +------------------------------------ + +Il metodo `joinWhere()` estende le condizioni che vengono specificate durante il collegamento delle tabelle in SQL dopo la parola chiave `ON`. + +Supponiamo di voler trovare i libri tradotti da un traduttore specifico: + +```php +// Trova i libri tradotti dal traduttore di nome 'David' +$books = $explorer->table('book') + ->joinWhere('translator', 'translator.name', 'David'); +// LEFT JOIN author translator ON book.translator_id = translator.id AND (translator.name = 'David') +``` + +Nella condizione `joinWhere()` possiamo utilizzare le stesse costruzioni del metodo `where()` - operatori, placeholder punto interrogativo, array di valori o espressioni SQL. + +Per query più complesse con più JOIN, possiamo definire alias di tabelle: + +```php +$tags = $explorer->table('tag') + ->joinWhere(':book_tag.book.author', 'book_author.born < ?', 1950) + ->alias(':book_tag.book.author', 'book_author'); +// LEFT JOIN `book_tag` ON `tag`.`id` = `book_tag`.`tag_id` +// LEFT JOIN `book` ON `book_tag`.`book_id` = `book`.`id` +// LEFT JOIN `author` `book_author` ON `book`.`author_id` = `book_author`.`id` +// AND (`book_author`.`born` < 1950) +``` + +Notate che mentre il metodo `where()` aggiunge condizioni alla clausola `WHERE`, il metodo `joinWhere()` estende le condizioni nella clausola `ON` durante il collegamento delle tabelle. diff --git a/database/it/guide.texy b/database/it/guide.texy new file mode 100644 index 0000000000..946c94506a --- /dev/null +++ b/database/it/guide.texy @@ -0,0 +1,216 @@ +Nette Database +************** + +.[perex] +Nette Database è un layer di database potente ed elegante per PHP, con un'enfasi sulla semplicità e sulle funzionalità intelligenti. Offre due modi per lavorare con il database: [Explorer |Explorer] per lo sviluppo rapido di applicazioni, o [l'accesso SQL |SQL way] per lavorare direttamente con le query. + +<div class="grid gap-3"> +<div> + + +[Accesso SQL |SQL way] +====================== +- Query parametrizzate sicure +- Controllo preciso sulla forma delle query SQL +- Quando si scrivono query complesse con funzionalità avanzate +- Ottimizzazione delle performance utilizzando funzioni SQL specifiche + +</div> + +<div> + + +[Explorer |Explorer] +==================== +- Sviluppo rapido senza scrivere SQL +- Lavoro intuitivo con le relazioni tra tabelle +- Apprezzerete l'ottimizzazione automatica delle query +- Adatto per un lavoro rapido e comodo con il database + +</div> + +</div> + + +Installazione +============= + +Scarica e installa la libreria utilizzando lo strumento [Composer|best-practices:composer]: + +```shell +composer require nette/database +``` + + +Database supportati +=================== + +Nette Database supporta i seguenti database: + +|* Server Database |* Nome DSN |* Supporto in Explorer +|---------------------|-------------|----------------------- +| MySQL (>= 5.1) | mysql | SÌ +| PostgreSQL (>= 9.0) | pgsql | SÌ +| Sqlite 3 (>= 3.8) | sqlite | SÌ +| Oracle | oci | - +| MS SQL (PDO_SQLSRV) | sqlsrv | SÌ +| MS SQL (PDO_DBLIB) | mssql | - +| ODBC | odbc | - + + +Due approcci al database +======================== + +Nette Database ti dà una scelta: puoi scrivere direttamente le query SQL (accesso SQL) o lasciarle generare automaticamente (Explorer). Vediamo come entrambi gli approcci risolvono gli stessi compiti: + +[Accesso SQL|sql way] - Query SQL + +```php +// inserimento di un record +$database->query('INSERT INTO books', [ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// recupero dei record: autori dei libri +$result = $database->query(' + SELECT authors.*, COUNT(books.id) AS books_count + FROM authors + LEFT JOIN books ON authors.id = books.author_id + WHERE authors.active = 1 + GROUP BY authors.id +'); + +// output (non ottimale, genera N query aggiuntive) +foreach ($result as $author) { + $books = $database->query(' + SELECT * FROM books + WHERE author_id = ? + ORDER BY published_at DESC + ', $author->id); + + echo "Autore $author->name ha scritto $author->books_count libri:\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +[Approccio Explorer|explorer] - generazione automatica di SQL + +```php +// inserimento di un record +$database->table('books')->insert([ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// recupero dei record: autori dei libri +$authors = $database->table('authors') + ->where('active', 1); + +// output (genera automaticamente solo 2 query ottimizzate) +foreach ($authors as $author) { + $books = $author->related('books') + ->order('published_at DESC'); + + echo "Autore $author->name ha scritto {$books->count()} libri:\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +L'approccio Explorer genera e ottimizza automaticamente le query SQL. Nell'esempio fornito, l'accesso SQL genera N+1 query (una per gli autori e poi una per i libri di ciascun autore), mentre Explorer ottimizza automaticamente le query ed ne esegue solo due: una per gli autori e una per tutti i loro libri. + +Entrambi gli approcci possono essere combinati liberamente nell'applicazione secondo necessità. + + +Connessione e configurazione +============================ + +Per connettersi al database, è sufficiente creare un'istanza della classe [api:Nette\Database\Connection]: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password); +``` + +Il parametro `$dsn` (data source name) è lo stesso [quello utilizzato da PDO |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], ad esempio `host=127.0.0.1;dbname=test`. In caso di fallimento, lancia un'eccezione `Nette\Database\ConnectionException`. + +Tuttavia, un modo più pratico è offerto dalla [configurazione dell'applicazione |configuration], dove è sufficiente aggiungere la sezione `database` e verranno creati gli oggetti necessari e anche il pannello del database nella barra di [Tracy |tracy:]. + +```neon +database: + dsn: 'mysql:host=127.0.0.1;dbname=test' + user: root + password: password +``` + +Successivamente, [otteniamo come servizio dal container DI |dependency-injection:passing-dependencies] l'oggetto della connessione, ad esempio: + +```php +class Model +{ + public function __construct( + // o Nette\Database\Explorer + private Nette\Database\Connection $database, + ) { + } +} +``` + +Maggiori informazioni sulla [configurazione del database|configuration]. + + +Creazione manuale di Explorer +----------------------------- + +Se non si utilizza il container Nette DI, è possibile creare manualmente un'istanza di `Nette\Database\Explorer`: + +```php +// connessione al database +$connection = new Nette\Database\Connection('mysql:host=127.0.0.1;dbname=mydatabase', 'user', 'password'); +// storage per la cache, implementa Nette\Caching\Storage, ad esempio: +$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp/dir'); +// si occupa della riflessione della struttura del database +$structure = new Nette\Database\Structure($connection, $storage); +// definisce le regole per la mappatura dei nomi di tabelle, colonne e chiavi esterne +$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); +$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +``` + + +Gestione della connessione +========================== + +Quando viene creato un oggetto `Connection`, la connessione viene stabilita automaticamente. Se si desidera posticipare la connessione, utilizzare la modalità lazy - questa può essere abilitata nella [configurazione|configuration] impostando `lazy` su true, o in questo modo: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password, ['lazy' => true]); +``` + +Per gestire la connessione, utilizzare i metodi `connect()`, `disconnect()` e `reconnect()`. +- `connect()` crea una connessione se non esiste già, e può lanciare un'eccezione `Nette\Database\ConnectionException`. +- `disconnect()` disconnette la connessione corrente al database. +- `reconnect()` esegue la disconnessione e la successiva riconnessione al database. Questo metodo può anche lanciare un'eccezione `Nette\Database\ConnectionException`. + +Inoltre, è possibile monitorare gli eventi associati alla connessione utilizzando l'evento `onConnect`, che è un array di callback che vengono chiamati dopo aver stabilito una connessione al database. + +```php +// viene eseguito dopo la connessione al database +$database->onConnect[] = function($database) { + echo "Connesso al database"; +}; +``` + + +Tracy Debug Bar +=============== + +Se utilizzi [Tracy |tracy:], il pannello Database viene attivato automaticamente nella Debug Bar, mostrando tutte le query eseguite, i loro parametri, il tempo di esecuzione e la posizione nel codice in cui sono state chiamate. + +[* db-panel.webp *] diff --git a/database/it/mapping.texy b/database/it/mapping.texy new file mode 100644 index 0000000000..aa8c9b5d82 --- /dev/null +++ b/database/it/mapping.texy @@ -0,0 +1,55 @@ +Conversione dei tipi +******************** + +.[perex] +Nette Database converte automaticamente i valori restituiti dal database nei tipi PHP corrispondenti. + + +Data e ora +---------- + +I dati temporali vengono convertiti in oggetti `Nette\Utils\DateTime`. Se si desidera che i dati temporali vengano convertiti in oggetti immutabili `Nette\Database\DateTime`, impostare l'opzione `newDateTime` su true nella [configurazione|configuration]. + +```php +$row = $database->fetch('SELECT created_at FROM articles'); +echo $row->created_at instanceof DateTime; // true +echo $row->created_at->format('j. n. Y'); +``` + +Nel caso di MySQL, il tipo di dati `TIME` viene convertito in oggetti `DateInterval`. + + +Valori booleani +--------------- + +I valori booleani vengono automaticamente convertiti in `true` o `false`. Per MySQL, `TINYINT(1)` viene convertito se impostiamo `convertBoolean` nella [configurazione|configuration]. + +```php +$row = $database->fetch('SELECT is_published FROM articles'); +echo gettype($row->is_published); // 'boolean' +``` + + +Valori numerici +--------------- + +I valori numerici vengono convertiti in `int` o `float` in base al tipo di colonna nel database: + +```php +$row = $database->fetch('SELECT id, price FROM products'); +echo gettype($row->id); // integer +echo gettype($row->price); // float +``` + + +Normalizzazione personalizzata +------------------------------ + +Utilizzando il metodo `setRowNormalizer(?callable $normalizer)` è possibile impostare una funzione personalizzata per trasformare le righe dal database. Questo è utile, ad esempio, per la conversione automatica dei tipi di dati. + +```php +$database->setRowNormalizer(function(array $row, ResultSet $resultSet): array { + // qui avviene la conversione dei tipi + return $row; +}); +``` diff --git a/database/it/reflection.texy b/database/it/reflection.texy new file mode 100644 index 0000000000..b608b59dde --- /dev/null +++ b/database/it/reflection.texy @@ -0,0 +1,125 @@ +Riflessione della struttura +*************************** + +.{data-version:3.2.1} +Nette Database fornisce strumenti per l'introspezione della struttura del database utilizzando la classe [api:Nette\Database\Reflection]. Ciò consente di ottenere informazioni su tabelle, colonne, indici e chiavi esterne. È possibile utilizzare la riflessione per generare schemi, creare applicazioni flessibili che lavorano con il database o strumenti di database generici. + +Otteniamo l'oggetto di riflessione dall'istanza della connessione al database: + +```php +$reflection = $database->getReflection(); +``` + + +Ottenere le tabelle +------------------- + +La proprietà readonly `$reflection->tables` contiene un array associativo di tutte le tabelle nel database: + +```php +// Elenca i nomi di tutte le tabelle +foreach ($reflection->tables as $name => $table) { + echo $name . "\n"; +} +``` + +Sono disponibili anche due metodi: + +```php +// Verifica l'esistenza della tabella +if ($reflection->hasTable('users')) { + echo "La tabella users esiste"; +} + +// Restituisce l'oggetto tabella; se non esiste, lancia un'eccezione +$table = $reflection->getTable('users'); +``` + + +Informazioni sulla tabella +-------------------------- + +La tabella è rappresentata dall'oggetto [Table|api:Nette\Database\Reflection\Table], che fornisce le seguenti proprietà readonly: + +- `$name: string` – nome della tabella +- `$view: bool` – se si tratta di una vista +- `$fullName: ?string` – nome completo della tabella incluso lo schema (se esiste) +- `$columns: array<string, Column>` – array associativo delle colonne della tabella +- `$indexes: Index[]` – array degli indici della tabella +- `$primaryKey: ?Index` – chiave primaria della tabella o null +- `$foreignKeys: ForeignKey[]` – array delle chiavi esterne della tabella + + +Colonne +------- + +La proprietà `columns` della tabella fornisce un array associativo di colonne, dove la chiave è il nome della colonna e il valore è un'istanza di [Column|api:Nette\Database\Reflection\Column] con queste proprietà: + +- `$name: string` – nome della colonna +- `$table: ?Table` – riferimento alla tabella della colonna +- `$nativeType: string` – tipo di dato nativo del database +- `$size: ?int` – dimensione/lunghezza del tipo +- `$nullable: bool` – se la colonna può contenere NULL +- `$default: mixed` – valore predefinito della colonna +- `$autoIncrement: bool` – se la colonna è auto-increment +- `$primary: bool` – se fa parte della chiave primaria +- `$vendor: array` – metadati aggiuntivi specifici per il sistema di database dato + +```php +foreach ($table->columns as $name => $column) { + echo "Colonna: $name\n"; + echo "Tipo: {$column->nativeType}\n"; + echo "Nullable: " . ($column->nullable ? 'Sì' : 'No') . "\n"; +} +``` + + +Indici +------ + +La proprietà `indexes` della tabella fornisce un array di indici, dove ogni indice è un'istanza di [Index|api:Nette\Database\Reflection\Index] con queste proprietà: + +- `$columns: Column[]` – array di colonne che compongono l'indice +- `$unique: bool` – se l'indice è univoco +- `$primary: bool` – se è una chiave primaria +- `$name: ?string` – nome dell'indice + +La chiave primaria della tabella può essere ottenuta utilizzando la proprietà `primaryKey`, che restituisce un oggetto `Index` o `null` nel caso in cui la tabella non abbia una chiave primaria. + +```php +// Elenco degli indici +foreach ($table->indexes as $index) { + $columns = implode(', ', array_map(fn($col) => $col->name, $index->columns)); + echo "Indice" . ($index->name ? " {$index->name}" : '') . ":\n"; + echo " Colonne: $columns\n"; + echo " Unique: " . ($index->unique ? 'Sì' : 'No') . "\n"; +} + +// Elenco della chiave primaria +if ($primaryKey = $table->primaryKey) { + $columns = implode(', ', array_map(fn($col) => $col->name, $primaryKey->columns)); + echo "Chiave primaria: $columns\n"; +} +``` + + +Chiavi esterne +-------------- + +La proprietà `foreignKeys` della tabella fornisce un array di chiavi esterne, dove ogni chiave esterna è un'istanza di [ForeignKey|api:Nette\Database\Reflection\ForeignKey] con queste proprietà: + +- `$foreignTable: Table` – tabella referenziata +- `$localColumns: Column[]` – array di colonne locali +- `$foreignColumns: Column[]` – array di colonne referenziate +- `$name: ?string` – nome della chiave esterna + +```php +// Elenco delle chiavi esterne +foreach ($table->foreignKeys as $fk) { + $localCols = implode(', ', array_map(fn($col) => $col->name, $fk->localColumns)); + $foreignCols = implode(', ', array_map(fn($col) => $col->name, $fk->foreignColumns)); + + echo "FK" . ($fk->name ? " {$fk->name}" : '') . ":\n"; + echo " $localCols -> {$fk->foreignTable->name}($foreignCols)\n"; +} +``` diff --git a/database/it/security.texy b/database/it/security.texy new file mode 100644 index 0000000000..b22398b8eb --- /dev/null +++ b/database/it/security.texy @@ -0,0 +1,185 @@ +Rischi per la sicurezza +*********************** + +<div class=perex> + +Il database contiene spesso dati sensibili e consente di eseguire operazioni pericolose. Per lavorare in sicurezza con Nette Database è fondamentale: + +- Comprendere la differenza tra API sicure e non sicure +- Utilizzare query parametrizzate +- Validare correttamente i dati di input + +</div> + + +Cos'è SQL Injection? +==================== + +SQL injection è il rischio di sicurezza più grave quando si lavora con un database. Si verifica quando l'input non trattato di un utente diventa parte di una query SQL. Un attaccante può inserire i propri comandi SQL e quindi: +- Ottenere accesso non autorizzato ai dati +- Modificare o cancellare dati nel database +- Bypassare l'autenticazione + +```php +// ❌ CODICE PERICOLOSO - vulnerabile a SQL injection +$database->query("SELECT * FROM users WHERE name = '$_GET[name]'"); + +// L'attaccante può inserire ad esempio il valore: ' OR '1'='1 +// La query risultante sarà: SELECT * FROM users WHERE name = '' OR '1'='1' +// Che restituirà tutti gli utenti +``` + +Lo stesso vale per Database Explorer: + +```php +// ❌ CODICE PERICOLOSO - vulnerabile a SQL injection +$table->where('name = ' . $_GET['name']); +$table->where("name = '$_GET[name]'"); +``` + + +Query parametrizzate +==================== + +La difesa fondamentale contro SQL injection sono le query parametrizzate. Nette Database offre diversi modi per utilizzarle. + +Il modo più semplice è utilizzare **placeholder a punto interrogativo**: + +```php +// ✅ Query parametrizzata sicura +$database->query('SELECT * FROM users WHERE name = ?', $name); + +// ✅ Condizione sicura in Explorer +$table->where('name = ?', $name); +``` + +Questo vale per tutti gli altri metodi in [Database Explorer|explorer], che consentono di inserire espressioni con placeholder a punto interrogativo e parametri. + +Per i comandi INSERT, UPDATE o la clausola WHERE, possiamo passare i valori in un array: + +```php +// ✅ INSERT sicuro +$database->query('INSERT INTO users', [ + 'name' => $name, + 'email' => $email, +]); + +// ✅ INSERT sicuro in Explorer +$table->insert([ + 'name' => $name, + 'email' => $email, +]); +``` + + +Validazione dei valori dei parametri +==================================== + +Le query parametrizzate sono la pietra angolare del lavoro sicuro con il database. Tuttavia, i valori che inseriamo in esse devono passare attraverso diversi livelli di controllo: + + +Controllo del tipo +------------------ + +**La cosa più importante è garantire il tipo di dato corretto dei parametri** - questa è una condizione necessaria per l'uso sicuro di Nette Database. Il database presuppone che tutti i dati di input abbiano il tipo di dato corretto corrispondente alla colonna data. + +Ad esempio, se `$name` negli esempi precedenti fosse inaspettatamente un array invece di una stringa, Nette Database tenterebbe di inserire tutti i suoi elementi nella query SQL, il che porterebbe a un errore. Pertanto, **non utilizzare mai** dati non validati da `$_GET`, `$_POST` o `$_COOKIE` direttamente nelle query del database. + + +Controllo del formato +--------------------- + +Al secondo livello, controlliamo il formato dei dati - ad esempio, se le stringhe sono in codifica UTF-8 e la loro lunghezza corrisponde alla definizione della colonna, o se i valori numerici rientrano nell'intervallo consentito per il tipo di dato della colonna. + +A questo livello di validazione, possiamo parzialmente fare affidamento anche sul database stesso: molti database rifiuteranno dati non validi. Tuttavia, il comportamento può variare, alcuni potrebbero silenziosamente troncare stringhe lunghe o tagliare numeri fuori intervallo. + + +Controllo del dominio +--------------------- + +Il terzo livello rappresenta i controlli logici specifici della tua applicazione. Ad esempio, verificare che i valori delle caselle di selezione corrispondano alle opzioni offerte, che i numeri siano nell'intervallo previsto (ad es. età 0-150 anni) o che le dipendenze reciproche tra i valori abbiano senso. + + +Metodi di validazione consigliati +--------------------------------- + +- Utilizzare [Nette Forms|forms:], che garantiscono automaticamente la corretta validazione di tutti gli input +- Utilizzare i [Presenter|application:] e specificare i tipi di dati per i parametri nei metodi `action*()` e `render*()` +- Oppure implementare un proprio layer di validazione utilizzando strumenti PHP standard come `filter_var()` + + +Lavoro sicuro con le colonne +============================ + +Nella sezione precedente, abbiamo mostrato come validare correttamente i valori dei parametri. Tuttavia, quando si utilizzano array nelle query SQL, dobbiamo prestare la stessa attenzione anche alle loro chiavi. + +```php +// ❌ CODICE PERICOLOSO - le chiavi nell'array non sono trattate +$database->query('INSERT INTO users', $_POST); +``` + +Nei comandi INSERT e UPDATE, questo è un errore di sicurezza critico: un attaccante può inserire o modificare qualsiasi colonna nel database. Potrebbe, ad esempio, impostare `is_admin = 1` o inserire dati arbitrari in colonne sensibili (la cosiddetta Mass Assignment Vulnerability). + +Nelle condizioni WHERE, è ancora più pericoloso, perché possono contenere operatori: + +```php +// ❌ CODICE PERICOLOSO - le chiavi nell'array non sono trattate +$_POST['salary >'] = 100000; +$database->query('SELECT * FROM users WHERE', $_POST); +// esegue la query WHERE (`salary` > 100000) +``` + +Un attaccante può utilizzare questo approccio per scoprire sistematicamente gli stipendi dei dipendenti. Inizia, ad esempio, con una query sugli stipendi superiori a 100.000, poi inferiori a 50.000 e restringendo gradualmente l'intervallo, può rivelare gli stipendi approssimativi di tutti i dipendenti. Questo tipo di attacco è chiamato SQL enumeration. + +I metodi `where()` e `whereOr()` sono ancora [molto più flessibili |explorer#where] e supportano espressioni SQL nelle chiavi e nei valori, inclusi operatori e funzioni. Ciò dà all'attaccante la possibilità di eseguire SQL injection: + +```php +// ❌ CODICE PERICOLOSO - l'attaccante può inserire il proprio SQL +$_POST = ['0) UNION SELECT name, salary FROM users WHERE (1']; +$table->where($_POST); +// esegue la query WHERE (0) UNION SELECT name, salary FROM users WHERE (1) +``` + +Questo attacco termina la condizione originale con `0)`, aggiunge il proprio `SELECT` utilizzando `UNION` per ottenere dati sensibili dalla tabella `users` e chiude la query sintatticamente corretta con `WHERE (1)`. + + +Whitelist delle colonne +----------------------- + +Per lavorare in sicurezza con i nomi delle colonne, abbiamo bisogno di un meccanismo che garantisca che l'utente possa lavorare solo con le colonne consentite e non possa aggiungerne di proprie. Potremmo provare a rilevare e bloccare i nomi di colonna pericolosi (blacklist), ma questo approccio è inaffidabile: un attaccante può sempre trovare un nuovo modo per scrivere un nome di colonna pericoloso che non avevamo previsto. + +Pertanto, è molto più sicuro invertire la logica e definire un elenco esplicito di colonne consentite (whitelist): + +```php +// Colonne che l'utente può modificare +$allowedColumns = ['name', 'email', 'active']; + +// Rimuoviamo tutte le colonne non consentite dall'input +$filteredData = array_intersect_key($userData, array_flip($allowedColumns)); + +// ✅ Ora possiamo usarlo in sicurezza nelle query, come ad esempio: +$database->query('INSERT INTO users', $filteredData); +$table->update($filteredData); +$table->where($filteredData); +``` + + +Identificatori dinamici +======================= + +Per nomi dinamici di tabelle e colonne, utilizzare il placeholder `?name`. Questo garantisce il corretto escaping degli identificatori secondo la sintassi del database dato (ad esempio, utilizzando i backtick in MySQL): + +```php +// ✅ Uso sicuro di identificatori affidabili +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name', $column, $table); +// Risultato in MySQL: SELECT `name` FROM `users` +``` + +Importante: utilizzare il simbolo `?name` solo per valori affidabili definiti nel codice dell'applicazione. Per i valori provenienti dall'utente, utilizzare nuovamente la [whitelist |#Whitelist delle colonne]. Altrimenti, ci si espone a rischi per la sicurezza: + +```php +// ❌ PERICOLOSO - non utilizzare mai l'input dell'utente +$database->query('SELECT ?name FROM users', $_GET['column']); +``` diff --git a/database/it/sql-way.texy b/database/it/sql-way.texy new file mode 100644 index 0000000000..0b9bf6030a --- /dev/null +++ b/database/it/sql-way.texy @@ -0,0 +1,513 @@ +Accesso SQL +*********** + +.[perex] +Nette Database offre due approcci: puoi scrivere query SQL da solo (accesso SQL), oppure puoi farle generare automaticamente (vedi [Explorer |explorer]). L'accesso SQL ti dà il pieno controllo sulle query, garantendo al contempo la loro costruzione sicura. + +.[note] +I dettagli sulla connessione e la configurazione del database si trovano nel capitolo [Connessione e configurazione |guide#Connessione e configurazione]. + + +Query di base +============= + +Per interrogare il database, si usa il metodo `query()`. Questo restituisce un oggetto [ResultSet |api:Nette\Database\ResultSet], che rappresenta il risultato della query. In caso di fallimento, il metodo [lancia un'eccezione|exceptions]. Possiamo scorrere il risultato della query usando un ciclo `foreach`, oppure usare una delle [funzioni ausiliarie |#Recupero dati]. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; +} +``` + +Per inserire in modo sicuro i valori nelle query SQL, usiamo query parametrizzate. Nette Database le rende estremamente semplici: basta aggiungere una virgola e il valore dopo la query SQL: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Con più parametri, hai due opzioni di scrittura. Puoi "intervallare" la query SQL con i parametri: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age); +``` + +Oppure scrivere prima l'intera query SQL e poi aggiungere tutti i parametri: + +```php +$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age); +``` + + +Protezione contro SQL injection +=============================== + +Perché è importante usare query parametrizzate? Perché ti proteggono da un attacco chiamato SQL injection, in cui un attaccante potrebbe inserire i propri comandi SQL e quindi ottenere o danneggiare i dati nel database. + +.[warning] +**Non inserire mai variabili direttamente nella query SQL!** Usa sempre query parametrizzate, che ti proteggono da SQL injection. + +```php +// ❌ CODICE PERICOLOSO - vulnerabile a SQL injection +$database->query("SELECT * FROM users WHERE name = '$name'"); + +// ✅ Query parametrizzata sicura +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Familiarizza con i [possibili rischi per la sicurezza |security]. + + +Tecniche di query +================= + + +Condizioni WHERE +---------------- + +Puoi scrivere le condizioni WHERE come un array associativo, dove le chiavi sono i nomi delle colonne e i valori sono i dati per il confronto. Nette Database seleziona automaticamente l'operatore SQL più appropriato in base al tipo di valore. + +```php +$database->query('SELECT * FROM users WHERE', [ + 'name' => 'John', + 'active' => true, +]); +// WHERE `name` = 'John' AND `active` = 1 +``` + +Nella chiave puoi anche specificare esplicitamente l'operatore per il confronto: + +```php +$database->query('SELECT * FROM users WHERE', [ + 'age >' => 25, // usa l'operatore > + 'name LIKE' => '%John%', // usa l'operatore LIKE + 'email NOT LIKE' => '%example.com%', // usa l'operatore NOT LIKE +]); +// WHERE `age` > 25 AND `name` LIKE '%John%' AND `email` NOT LIKE '%example.com%' +``` + +Nette gestisce automaticamente casi speciali come valori `null` o array. + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name' => 'Laptop', // usa l'operatore = + 'category_id' => [1, 2, 3], // usa IN + 'description' => null, // usa IS NULL +]); +// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL +``` + +Per le condizioni negative, usa l'operatore `NOT`: + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name NOT' => 'Laptop', // usa l'operatore <> + 'category_id NOT' => [1, 2, 3], // usa NOT IN + 'description NOT' => null, // usa IS NOT NULL + 'id' => [], // viene omesso +]); +// WHERE `name` <> 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL +``` + +Per unire le condizioni si usa l'operatore `AND`. Questo può essere cambiato usando il [segnaposto ?or |#Hint per la costruzione di SQL]. + + +Regole ORDER BY +--------------- + +L'ordinamento `ORDER BY` può essere scritto usando un array. Nelle chiavi indichiamo le colonne e il valore sarà un booleano che determina se ordinare in modo ascendente: + +```php +$database->query('SELECT id FROM author ORDER BY', [ + 'id' => true, // ascendente + 'name' => false, // discendente +]); +// SELECT id FROM author ORDER BY `id`, `name` DESC +``` + + +Inserimento dati (INSERT) +------------------------- + +Per inserire record si usa l'istruzione SQL `INSERT`. + +```php +$values = [ + 'name' => 'John Doe', + 'email' => 'john@example.com', +]; +$database->query('INSERT INTO users ?', $values); +$userId = $database->getInsertId(); +``` + +Il metodo `getInsertId()` restituisce l'ID dell'ultima riga inserita. Per alcuni database (ad es. PostgreSQL), è necessario specificare come parametro il nome della sequenza da cui generare l'ID tramite `$database->getInsertId($sequenceId)`. + +Come parametri possiamo passare anche [#valori speciali] come file, oggetti DateTime o tipi enum. + +Inserimento di più record contemporaneamente: + +```php +$database->query('INSERT INTO users ?', [ + ['name' => 'User 1', 'email' => 'user1@mail.com'], + ['name' => 'User 2', 'email' => 'user2@mail.com'], +]); +``` + +L'INSERT multiplo è molto più veloce perché viene eseguita una singola query al database, invece di molte query individuali. + +**Avviso di sicurezza:** Non usare mai dati non validati come `$values`. Familiarizza con i [possibili rischi |security#Lavoro sicuro con le colonne]. + + +Aggiornamento dati (UPDATE) +--------------------------- + +Per aggiornare i record si usa l'istruzione SQL `UPDATE`. + +```php +// Aggiornamento di un singolo record +$values = [ + 'name' => 'John Smith', +]; +$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1); +``` + +Il numero di righe interessate viene restituito da `$result->getRowCount()`. + +Per UPDATE possiamo usare gli operatori `+=` e `-=`: + +```php +$database->query('UPDATE users SET ? WHERE id = ?', [ + 'login_count+=' => 1, // incrementa login_count +], 1); +``` + +Esempio di inserimento o modifica di un record, se esiste già. Usiamo la tecnica `ON DUPLICATE KEY UPDATE`: + +```php +$values = [ + 'name' => $name, + 'year' => $year, +]; +$database->query('INSERT INTO users ? ON DUPLICATE KEY UPDATE ?', + $values + ['id' => $id], + $values, +); +// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) +// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 +``` + +Nota che Nette Database riconosce in quale contesto dell'istruzione SQL viene inserito il parametro con l'array e costruisce il codice SQL di conseguenza. Quindi dal primo array ha costruito `(id, name, year) VALUES (123, 'Jim', 1978)`, mentre il secondo lo ha convertito nella forma `name = 'Jim', year = 1978`. Ne parliamo più dettagliatamente nella sezione [#Hint per la costruzione di SQL]. + + +Cancellazione dati (DELETE) +--------------------------- + +Per cancellare i record si usa l'istruzione SQL `DELETE`. Esempio con ottenimento del numero di righe cancellate: + +```php +$count = $database->query('DELETE FROM users WHERE id = ?', 1) + ->getRowCount(); +``` + + +Hint per la costruzione di SQL +------------------------------ + +Un hint è un segnaposto speciale nella query SQL che indica come il valore del parametro deve essere riscritto nell'espressione SQL: + +| Hint | Descrizione | Utilizzato automaticamente +|-----------|-------------------------------------------------|----------------------------- +| `?name` | usa per inserire il nome della tabella o della colonna | - +| `?values` | genera `(key, ...) VALUES (value, ...)` | `INSERT ... ?`, `REPLACE ... ?` +| `?set` | genera l'assegnazione `key = value, ...` | `SET ?`, `KEY UPDATE ?` +| `?and` | unisce le condizioni nell'array con l'operatore `AND` | `WHERE ?`, `HAVING ?` +| `?or` | unisce le condizioni nell'array con l'operatore `OR` | - +| `?order` | genera la clausola `ORDER BY` | `ORDER BY ?`, `GROUP BY ?` + +Per l'inserimento dinamico di nomi di tabelle e colonne nella query, si usa il segnaposto `?name`. Nette Database si occupa della corretta gestione degli identificatori secondo le convenzioni del database specifico (ad es. racchiudendoli tra backtick in MySQL). + +```php +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table); +// SELECT `name` FROM `users` WHERE id = 1 (in MySQL) +``` + +**Avviso:** usa il simbolo `?name` solo per nomi di tabelle e colonne provenienti da input validati, altrimenti ti esponi a un [rischio per la sicurezza |security#Identificatori dinamici]. + +Gli altri hint di solito non devono essere specificati, poiché Nette utilizza un'intelligente rilevazione automatica durante la composizione della query SQL (vedi la terza colonna della tabella). Ma puoi usarlo, ad esempio, in una situazione in cui vuoi unire le condizioni usando `OR` invece di `AND`: + +```php +$database->query('SELECT * FROM users WHERE ?or', [ + 'name' => 'John', + 'email' => 'john@example.com', +]); +// SELECT * FROM users WHERE `name` = 'John' OR `email` = 'john@example.com' +``` + + +Valori speciali +--------------- + +Oltre ai comuni tipi scalari (string, int, bool), puoi passare valori speciali come parametri: + +- file: `fopen('image.gif', 'r')` inserisce il contenuto binario del file +- data e ora: gli oggetti `DateTime` vengono convertiti nel formato del database +- tipi enum: le istanze `enum` vengono convertite nel loro valore +- letterali SQL: creati con `Connection::literal('NOW()')` vengono inseriti direttamente nella query + +```php +$database->query('INSERT INTO articles ?', [ + 'title' => 'My Article', + 'published_at' => new DateTime, + 'content' => fopen('image.png', 'r'), + 'state' => Status::Draft, +]); +``` + +Per i database che non hanno supporto nativo per il tipo di dati `datetime` (come SQLite e Oracle), `DateTime` viene convertito nel valore specificato nella [configurazione del database|configuration] tramite la voce `formatDateTime` (il valore predefinito è `U` - timestamp unix). + + +Letterali SQL +------------- + +In alcuni casi, è necessario specificare direttamente il codice SQL come valore, che però non deve essere interpretato come stringa ed escapato. A questo servono gli oggetti della classe `Nette\Database\SqlLiteral`. Li crea il metodo `Connection::literal()`. + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + 'year >' => $database::literal('YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) +``` + +O alternativamente: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) +``` + +I letterali SQL possono contenere parametri: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > ? AND year < ?', $min, $max), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) +``` + +Grazie a ciò possiamo creare combinazioni interessanti: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('?or', [ + 'active' => true, + 'role' => $role, + ]), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') +``` + + +Recupero dati +============= + + +Scorciatoie per query SELECT +---------------------------- + +Per semplificare il recupero dei dati, `Connection` offre diverse scorciatoie che combinano la chiamata `query()` con il successivo `fetch*()`. Questi metodi accettano gli stessi parametri di `query()`, ovvero la query SQL e parametri opzionali. Una descrizione completa dei metodi `fetch*()` si trova [sotto |#fetch]. + +| `fetch($sql, ...$params): ?Row` | Esegue la query e restituisce la prima riga come oggetto `Row` +| `fetchAll($sql, ...$params): array` | Esegue la query e restituisce tutte le righe come array di oggetti `Row` +| `fetchPairs($sql, ...$params): array` | Esegue la query e restituisce un array associativo, dove la prima colonna rappresenta la chiave e la seconda il valore +| `fetchField($sql, ...$params): mixed` | Esegue la query e restituisce il valore del primo campo della prima riga +| `fetchList($sql, ...$params): ?array` | Esegue la query e restituisce la prima riga come array indicizzato + +Esempio: + +```php +// fetchField() - restituisce il valore della prima cella +$count = $database->query('SELECT COUNT(*) FROM articles') + ->fetchField(); +``` + + +`foreach` - iterazione sulle righe +---------------------------------- + +Dopo l'esecuzione della query, viene restituito un oggetto [ResultSet|api:Nette\Database\ResultSet], che consente di scorrere i risultati in diversi modi. Il modo più semplice per eseguire una query e ottenere le righe è iterando in un ciclo `foreach`. Questo metodo è il più efficiente in termini di memoria, poiché restituisce i dati gradualmente e non li memorizza tutti in memoria contemporaneamente. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; + // ... +} +``` + +.[note] +`ResultSet` può essere iterato solo una volta. Se è necessario iterare ripetutamente, è necessario prima caricare i dati in un array, ad esempio utilizzando il metodo `fetchAll()`. + + +fetch(): ?Row .[method] +----------------------- + +Restituisce una riga come oggetto `Row`. Se non ci sono più righe, restituisce `null`. Sposta il puntatore interno alla riga successiva. + +```php +$result = $database->query('SELECT * FROM users'); +$row = $result->fetch(); // carica la prima riga +if ($row) { + echo $row->name; +} +``` + + +fetchAll(): array .[method] +--------------------------- + +Restituisce tutte le righe rimanenti dal `ResultSet` come un array di oggetti `Row`. + +```php +$result = $database->query('SELECT * FROM users'); +$rows = $result->fetchAll(); // carica tutte le righe +foreach ($rows as $row) { + echo $row->name; +} +``` + + +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Restituisce i risultati come un array associativo. Il primo argomento specifica il nome della colonna da utilizzare come chiave nell'array, il secondo argomento specifica il nome della colonna da utilizzare come valore: + +```php +$result = $database->query('SELECT id, name FROM users'); +$names = $result->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] +``` + +Se specifichiamo solo il primo parametro, il valore sarà l'intera riga, ovvero un oggetto `Row`: + +```php +$rows = $result->fetchPairs('id'); +// [1 => Row(id: 1, name: 'John'), 2 => Row(id: 2, name: 'Jane'), ...] +``` + +In caso di chiavi duplicate, viene utilizzato il valore dell'ultima riga. Utilizzando `null` come chiave, l'array sarà indicizzato numericamente a partire da zero (quindi non si verificano collisioni): + +```php +$names = $result->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] +``` + + +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +In alternativa, puoi specificare come parametro un callback, che per ogni riga restituirà o il valore stesso, o una coppia chiave-valore. + +```php +$result = $database->query('SELECT * FROM users'); +$items = $result->fetchPairs(fn($row) => "$row->id - $row->name"); +// ['1 - John', '2 - Jane', ...] + +// Il callback può anche restituire un array con una coppia chiave & valore: +$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]); +// ['John' => 46, 'Jane' => 21, ...] +``` + + +fetchField(): mixed .[method] +----------------------------- + +Restituisce il valore del primo campo della riga corrente. Se non ci sono più righe, restituisce `null`. Sposta il puntatore interno alla riga successiva. + +```php +$result = $database->query('SELECT name FROM users'); +$name = $result->fetchField(); // carica il nome dalla prima riga +``` + + +fetchList(): ?array .[method] +----------------------------- + +Restituisce una riga come array indicizzato. Se non ci sono più righe, restituisce `null`. Sposta il puntatore interno alla riga successiva. + +```php +$result = $database->query('SELECT name, email FROM users'); +$row = $result->fetchList(); // ['John', 'john@example.com'] +``` + + +getRowCount(): ?int .[method] +----------------------------- + +Restituisce il numero di righe interessate dall'ultima query `UPDATE` o `DELETE`. Per `SELECT`, è il numero di righe restituite, ma questo potrebbe non essere noto - in tal caso il metodo restituirà `null`. + + +getColumnCount(): ?int .[method] +-------------------------------- + +Restituisce il numero di colonne nel `ResultSet`. + + +Informazioni sulle query +======================== + +A scopo di debugging, possiamo ottenere informazioni sull'ultima query eseguita: + +```php +echo $database->getLastQueryString(); // stampa la query SQL + +$result = $database->query('SELECT * FROM articles'); +echo $result->getQueryString(); // stampa la query SQL +echo $result->getTime(); // stampa il tempo di esecuzione in secondi +``` + +Per visualizzare il risultato come tabella HTML, si può usare: + +```php +$result = $database->query('SELECT * FROM articles'); +$result->dump(); +``` + +ResultSet offre informazioni sui tipi di colonna: + +```php +$result = $database->query('SELECT * FROM articles'); +$types = $result->getColumnTypes(); + +foreach ($types as $column => $type) { + echo "$column è di tipo $type->type"; // ad es. 'id è di tipo int' +} +``` + + +Logging delle query +------------------- + +Possiamo implementare il nostro logging delle query personalizzato. L'evento `onQuery` è un array di callback che vengono chiamati dopo ogni query eseguita: + +```php +$database->onQuery[] = function ($database, $result) use ($logger) { + $logger->info('Query: ' . $result->getQueryString()); + $logger->info('Time: ' . $result->getTime()); + + if ($result->getRowCount() > 1000) { + $logger->warning('Large result set: ' . $result->getRowCount() . ' rows'); + } +}; +``` diff --git a/database/it/transactions.texy b/database/it/transactions.texy new file mode 100644 index 0000000000..b737f2bbb2 --- /dev/null +++ b/database/it/transactions.texy @@ -0,0 +1,43 @@ +Transazioni +*********** + +.[perex] +Le transazioni garantiscono che tutte le operazioni all'interno di una transazione vengano eseguite, oppure nessuna di esse. Sono utili per garantire la coerenza dei dati durante operazioni complesse. + +Il modo più semplice per utilizzare le transazioni è il seguente: + +```php +$database->beginTransaction(); +try { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); + $database->commit(); +} catch (\Exception $e) { + $database->rollBack(); + throw $e; +} +``` + +Potete scrivere la stessa cosa in modo molto più elegante usando il metodo `transaction()`. Accetta un callback come parametro, che esegue all'interno della transazione. Se il callback viene eseguito senza eccezioni, la transazione viene confermata automaticamente. Se si verifica un'eccezione, la transazione viene annullata (rollback) e l'eccezione si propaga ulteriormente. + +```php +$database->transaction(function ($database) use ($id) { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); +}); +``` + +Il metodo `transaction()` può anche restituire valori: + +```php +$count = $database->transaction(function ($database) { + $result = $database->query('UPDATE users SET active = ?', true); + return $result->getRowCount(); // restituisce il numero di righe aggiornate +}); +``` diff --git a/database/ja/@home.texy b/database/ja/@home.texy new file mode 100644 index 0000000000..965d3ecec8 --- /dev/null +++ b/database/ja/@home.texy @@ -0,0 +1,21 @@ + + +サポートされているデータベース +=============== + +Netteは以下のデータベースをサポートしています: + +|* データベースサーバー |* DSN名 |* コアでのサポート |* Explorerでのサポート +| MySQL (>= 5.1) | mysql | はい | はい +| PostgreSQL (>= 9.0) | pgsql | はい | はい +| Sqlite 3 (>= 3.8) | sqlite | はい | はい +| Oracle | oci | はい | - +| MS SQL (PDO_SQLSRV) | sqlsrv | はい | はい +| MS SQL (PDO_DBLIB) | mssql | はい | - +| ODBC | odbc | はい | - + + + + +{{maintitle: Nette Database - awesome database layer for PHP}} +{{description: Nette Databaseは、SQLクエリを記述する必要なく、データベースからデータを取得するプロセスを大幅に簡素化します。効率的なクエリを実行し、不要なデータを転送しません。}} diff --git a/database/ja/@left-menu.texy b/database/ja/@left-menu.texy new file mode 100644 index 0000000000..4b7d6758d8 --- /dev/null +++ b/database/ja/@left-menu.texy @@ -0,0 +1,12 @@ +Nette Database +************** +- [はじめに |guide] +- [SQL アクセス |sql way] +- [Explorer |Explorer] +- [トランザクション |transactions] +- [例外 |exceptions] +- [リフレクション |reflection] +- [マッピング |mapping] +- [設定 |configuration] +- [セキュリティリスク |security] +- [アップグレード |en:upgrading] diff --git a/database/ja/@meta.texy b/database/ja/@meta.texy new file mode 100644 index 0000000000..d3c41dc3d7 --- /dev/null +++ b/database/ja/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette ドキュメンテーション}} diff --git a/database/ja/configuration.texy b/database/ja/configuration.texy new file mode 100644 index 0000000000..d97526880e --- /dev/null +++ b/database/ja/configuration.texy @@ -0,0 +1,110 @@ +データベース設定 +******** + +.[perex] +Nette Databaseの設定オプションの概要。 + +フレームワーク全体ではなく、このライブラリのみを使用している場合は、[設定を読み込む方法|bootstrap:] を読んでください。 + + +単一接続 +---- + +単一のデータベース接続の設定: + +```neon +database: + # DSN、唯一の必須キー + dsn: "sqlite:%appDir%/Model/demo.db" + user: ... + password: ... +``` + +`Nette\Database\Connection` と `Nette\Database\Explorer` サービスを作成します。これらは通常、[autowiring |dependency-injection:autowiring] によって渡されるか、[その名前 |#DI サービス] への参照によって渡されます。 + +その他の設定: + +```neon +database: + # Tracy Bar にデータベースパネルを表示しますか? + debugger: ... # (bool) デフォルトはtrue + + # Tracy Bar にクエリの EXPLAIN を表示しますか? + explain: ... # (bool) デフォルトはtrue + + # この接続に対して autowiring を許可しますか? + autowired: ... # (bool) 最初の接続ではデフォルトでtrue + + # テーブルの命名規則: discovered, static またはクラス名 + conventions: discovered # (string) デフォルトは 'discovered' + + options: + # データベースへの接続は必要になったときのみ行いますか? + lazy: ... # (bool) デフォルトはfalse + + # PHP データベースドライバクラス + driverClass: # (string) + + # MySQL のみ: sql_mode を設定 + sqlmode: # (string) + + # MySQL のみ: SET NAMES を設定 + charset: # (string) デフォルトは 'utf8mb4' + + # MySQL のみ: TINYINT(1) を bool に変換 + convertBoolean: # (bool) デフォルトはfalse + + # 日付カラムを immutable オブジェクトとして返します (バージョン 3.2.1 以降) + newDateTime: # (bool) デフォルトはfalse + + # Oracle と SQLite のみ: 日付の保存形式 + formatDateTime: # (string) デフォルトは 'U' +``` + +`options` キーには、[PDO ドライバのドキュメント |https://www.php.net/manual/en/pdo.drivers.php] に記載されているその他のオプションを指定できます。例: + +```neon +database: + options: + PDO::MYSQL_ATTR_COMPRESS: true +``` + + +複数接続 +---- + +設定では、名前付きセクションに分割することで、複数のデータベース接続を定義することもできます: + +```neon +database: + main: + dsn: 'mysql:host=127.0.0.1;dbname=test' + user: root + password: password + + another: + dsn: 'sqlite::memory:' +``` + +Autowiringは最初のセクションのサービスに対してのみ有効です。これは `autowired: false` または `autowired: true` を使用して変更できます。 + + +DI サービス +------- + +これらのサービスはDIコンテナに追加されます。ここで `###` は接続名を表します: + +| 名前 | 型 | 説明 +|---------------------------------------------------------- +| `database.###.connection` | [api:Nette\Database\Connection] | データベース接続 +| `database.###.explorer` | [api:Nette\Database\Explorer] | [Database Explorer |explorer] + + +単一の接続のみを定義する場合、サービス名は `database.default.connection` と `database.default.explorer` になります。上記の例のように複数の接続を定義する場合、名前はセクションに対応します。つまり、`database.main.connection`、`database.main.explorer`、さらに `database.another.connection` と `database.another.explorer` です。 + +Autowiringされていないサービスは、その名前への参照によって明示的に渡します: + +```neon +services: + - UserFacade(@database.another.connection) +``` diff --git a/database/ja/exceptions.texy b/database/ja/exceptions.texy new file mode 100644 index 0000000000..e2764b7d45 --- /dev/null +++ b/database/ja/exceptions.texy @@ -0,0 +1,34 @@ +例外 +******* + +Nette Databaseは例外の階層を使用します。基本クラスは `Nette\Database\DriverException` で、これは `PDOException` を継承し、データベースエラーを処理するための拡張機能を提供します: + +- `getDriverCode()` メソッドは、データベースドライバからのエラーコードを返します +- `getSqlState()` メソッドは、SQLSTATEコードを返します +- `getQueryString()` および `getParameters()` メソッドは、元のクエリとそのパラメータを取得できます + +`DriverException` から、次の特殊な例外が継承されます: + +- `ConnectionException` - データベースサーバーへの接続失敗を示します +- `ConstraintViolationException` - データベース制約違反の基本クラスで、以下が継承されます: + - `ForeignKeyConstraintViolationException` - 外部キー制約違反 + - `NotNullConstraintViolationException` - NOT NULL制約違反 + - `UniqueConstraintViolationException` - 値の一意性制約違反 + + +`UniqueConstraintViolationException` 例外をキャッチする例。これは、データベースに既に存在するメールアドレスを持つユーザーを挿入しようとしたときに発生します(メールカラムに一意インデックスがあると仮定)。 + +```php +try { + $database->query('INSERT INTO users', [ + 'email' => 'john@example.com', + 'name' => 'John Doe', + 'password' => $hashedPassword, + ]); +} catch (Nette\Database\UniqueConstraintViolationException $e) { + echo 'このメールアドレスのユーザーは既に存在します。'; + +} catch (Nette\Database\DriverException $e) { + echo '登録中にエラーが発生しました: ' . $e->getMessage(); +} +``` diff --git a/database/ja/explorer.texy b/database/ja/explorer.texy new file mode 100644 index 0000000000..dc272d1b52 --- /dev/null +++ b/database/ja/explorer.texy @@ -0,0 +1,912 @@ +Database Explorer +***************** + +<div class=perex> + +Explorerは、データベースを扱うための直感的で効率的な方法を提供します。テーブル間のリレーションやクエリの最適化を自動的に処理するため、アプリケーションに集中できます。設定なしですぐに機能します。SQLクエリを完全に制御する必要がある場合は、[SQLアクセス |SQL way] を利用できます。 + +- データの操作は自然で理解しやすい +- 必要なデータのみを読み込む最適化されたSQLクエリを生成 +- JOINクエリを記述することなく、関連データに簡単にアクセス可能 +- 設定やエンティティの生成なしで即座に機能 + +</div> + + +Explorerの使用は、[api:Nette\Database\Explorer] オブジェクトの `table()` メソッドを呼び出すことから始めます(接続の詳細については、[接続と設定 |guide#接続と設定] の章を参照してください): + +```php +$books = $explorer->table('book'); // 'book' はテーブル名 +``` + +このメソッドは、SQLクエリを表す [Selection |api:Nette\Database\Table\Selection] オブジェクトを返します。このオブジェクトにさらにメソッドを連鎖させて、結果をフィルタリングおよびソートできます。クエリは、データを要求し始めたときにのみ構築および実行されます。たとえば、`foreach` ループで反復処理する場合です。各行は [ActiveRow |api:Nette\Database\Table\ActiveRow] オブジェクトによって表されます: + +```php +foreach ($books as $book) { + echo $book->title; // 'title' カラムの出力 + echo $book->author_id; // 'author_id' カラムの出力 +} +``` + +Explorerは、[#テーブル間のリレーション] の操作を大幅に簡素化します。次の例は、関連するテーブル(書籍とその著者)からデータを簡単に表示する方法を示しています。JOINクエリを記述する必要がないことに注意してください。Netteがそれらを自動的に作成します: + +```php +$books = $explorer->table('book'); + +foreach ($books as $book) { + echo '書籍: ' . $book->title; + echo '著者: ' . $book->author->name; // 'author' テーブルへのJOINを作成 +} +``` + +Nette Database Explorerは、クエリを可能な限り効率的に最適化します。上記の例では、処理する書籍が10冊であろうと10,000冊であろうと、2つのSELECTクエリのみを実行します。 + +さらに、Explorerはコード内で使用されているカラムを追跡し、データベースからそれらのみを読み込むことで、さらなるパフォーマンスを節約します。この動作は完全に自動的で適応的です。後でコードを変更して追加のカラムを使用し始めると、Explorerは自動的にクエリを調整します。何も設定する必要はなく、どのカラムが必要になるかを考える必要もありません - それはNetteに任せてください。 + + +フィルタリングとソート +=========== + +`Selection` クラスは、データ選択のフィルタリングとソートのためのメソッドを提供します。 + +.[language-php] +| `where($condition, ...$params)` | WHERE条件を追加します。複数の条件はAND演算子で結合されます +| `whereOr(array $conditions)` | OR演算子で結合されたWHERE条件のグループを追加します +| `wherePrimary($value)` | 主キーに基づいてWHERE条件を追加します +| `order($columns, ...$params)` | ORDER BYソートを設定します +| `select($columns, ...$params)` | 読み込むカラムを指定します +| `limit($limit, $offset = null)` | 行数を制限し(LIMIT)、オプションでOFFSETを設定します +| `page($page, $itemsPerPage, &$total = null)` | ページネーションを設定します +| `group($columns, ...$params)` | 行をグループ化します(GROUP BY) +| `having($condition, ...$params)` | グループ化された行をフィルタリングするためのHAVING条件を追加します + +メソッドは連鎖させることができます(いわゆる [fluent interface |nette:introduction-to-object-oriented-programming#Fluent Interface]):`$table->where(...)->order(...)->limit(...)`。 + +これらのメソッドでは、[関連テーブルのデータ |#関連テーブルを介したクエリ] にアクセスするための特別な表記法を使用することもできます。 + + +エスケープと識別子 +--------- + +メソッドはパラメータを自動的にエスケープし、識別子(テーブル名とカラム名)を引用符で囲むことで、SQLインジェクションを防ぎます。正しく機能させるためには、いくつかのルールに従う必要があります: + +- キーワード、関数名、プロシージャ名などは **大文字** で記述します。 +- カラム名とテーブル名は **小文字** で記述します。 +- 文字列は常に **パラメータ** を介して代入します。 + +```php +where('name = ' . $name); // 致命的な脆弱性: SQLインジェクション +where('name LIKE "%search%"'); // 悪い例: 自動引用符付けを複雑にする +where('name LIKE ?', '%search%'); // 正しい例: パラメータ経由で値が代入される + +where('name like ?', $name); // 悪い例: `name` `like` ? を生成 +where('name LIKE ?', $name); // 正しい例: `name` LIKE ? を生成 +where('LOWER(name) = ?', $value);// 正しい例: LOWER(`name`) = ? +``` + + +where(string|array $condition, ...$parameters): static .[method] +---------------------------------------------------------------- + +WHERE条件を使用して結果をフィルタリングします。その強力な点は、さまざまなタイプの値をインテリジェントに処理し、SQL演算子を自動的に選択することです。 + +基本的な使用法: + +```php +$table->where('id', $value); // WHERE `id` = 123 +$table->where('id > ?', $value); // WHERE `id` > 123 +$table->where('id = ? OR name = ?', $id, $name); // WHERE `id` = 1 OR `name` = 'Jon Snow' +``` + +適切な演算子の自動検出のおかげで、さまざまな特殊なケースに対処する必要はありません。Netteがそれらを処理します: + +```php +$table->where('id', 1); // WHERE `id` = 1 +$table->where('id', null); // WHERE `id` IS NULL +$table->where('id', [1, 2, 3]); // WHERE `id` IN (1, 2, 3) +// 演算子なしのプレースホルダー疑問符も使用できます: +$table->where('id ?', 1); // WHERE `id` = 1 +``` + +メソッドは、否定条件や空の配列も正しく処理します: + +```php +$table->where('id', []); // WHERE `id` IS NULL AND FALSE -- 何も見つからない +$table->where('id NOT', []); // WHERE `id` IS NULL OR TRUE -- すべて見つかる +$table->where('NOT (id ?)', []); // WHERE NOT (`id` IS NULL AND FALSE) -- すべて見つかる +// $table->where('NOT id ?', $ids); 注意 - この構文はサポートされていません +``` + +別のテーブルからの結果をパラメータとして渡すこともできます - サブクエリが作成されます: + +```php +// WHERE `id` IN (SELECT `id` FROM `tableName`) +$table->where('id', $explorer->table($tableName)); + +// WHERE `id` IN (SELECT `col` FROM `tableName`) +$table->where('id', $explorer->table($tableName)->select('col')); +``` + +条件を配列として渡すこともでき、その要素はANDで結合されます: + +```php +// WHERE (`price_final` < `price_original`) AND (`stock_count` > `min_stock`) +$table->where([ + 'price_final < price_original', + 'stock_count > min_stock', +]); +``` + +配列では、キー => 値のペアを使用でき、Netteは再び正しい演算子を自動的に選択します: + +```php +// WHERE (`status` = 'active') AND (`id` IN (1, 2, 3)) +$table->where([ + 'status' => 'active', + 'id' => [1, 2, 3], +]); +``` + +配列では、プレースホルダー疑問符と複数のパラメータを持つSQL式を組み合わせることができます。これは、正確に定義された演算子を持つ複雑な条件に適しています: + +```php +// WHERE (`age` > 18) AND (ROUND(`score`, 2) > 75.5) +$table->where([ + 'age > ?' => 18, + 'ROUND(score, ?) > ?' => [2, 75.5], // 2つのパラメータを配列として渡します +]); +``` + +`where()` の複数回の呼び出しは、条件を自動的にANDで結合します。 + + +whereOr(array $parameters): static .[method] +-------------------------------------------- + +`where()` と同様に条件を追加しますが、ORで結合する点が異なります: + +```php +// WHERE (`status` = 'active') OR (`deleted` = 1) +$table->whereOr([ + 'status' => 'active', + 'deleted' => true, +]); +``` + +ここでも、より複雑な式を使用できます: + +```php +// WHERE (`price` > 1000) OR (`price_with_tax` > 1500) +$table->whereOr([ + 'price > ?' => 1000, + 'price_with_tax > ?' => 1500, +]); +``` + + +wherePrimary(mixed $key): static .[method] +------------------------------------------ + +テーブルの主キーの条件を追加します: + +```php +// WHERE `id` = 123 +$table->wherePrimary(123); + +// WHERE `id` IN (1, 2, 3) +$table->wherePrimary([1, 2, 3]); +``` + +テーブルに複合主キー(例:`foo_id`, `bar_id`)がある場合は、配列として渡します: + +```php +// WHERE `foo_id` = 1 AND `bar_id` = 5 +$table->wherePrimary(['foo_id' => 1, 'bar_id' => 5])->fetch(); + +// WHERE (`foo_id`, `bar_id`) IN ((1, 5), (2, 3)) +$table->wherePrimary([ + ['foo_id' => 1, 'bar_id' => 5], + ['foo_id' => 2, 'bar_id' => 3], +])->fetchAll(); +``` + + +order(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- + +行が返される順序を指定します。1つまたは複数のカラム、昇順または降順、またはカスタム式でソートできます: + +```php +$table->order('created'); // ORDER BY `created` +$table->order('created DESC'); // ORDER BY `created` DESC +$table->order('priority DESC, created'); // ORDER BY `priority` DESC, `created` +$table->order('status = ? DESC', 'active'); // ORDER BY `status` = 'active' DESC +``` + + +select(string $columns, ...$parameters): static .[method] +--------------------------------------------------------- + +データベースから返すカラムを指定します。デフォルトでは、Nette Database Explorerはコードで実際に使用されるカラムのみを返します。`select()` メソッドは、特定の式を返す必要がある場合に使用します: + +```php +// SELECT *, DATE_FORMAT(`created_at`, "%d.%m.%Y") AS `formatted_date` +$table->select('*, DATE_FORMAT(created_at, ?) AS formatted_date', '%d.%m.%Y'); +``` + +`AS` で定義されたエイリアスは、ActiveRowオブジェクトのプロパティとして利用できます: + +```php +foreach ($table as $row) { + echo $row->formatted_date; // エイリアスへのアクセス +} +``` + + +limit(?int $limit, ?int $offset = null): static .[method] +--------------------------------------------------------- + +返される行数を制限し(LIMIT)、オプションでオフセットを設定できます: + +```php +$table->limit(10); // LIMIT 10 (最初の10行を返す) +$table->limit(10, 20); // LIMIT 10 OFFSET 20 +``` + +ページネーションには、`page()` メソッドを使用する方が適しています。 + + +page(int $page, int $itemsPerPage, &$numOfPages = null): static .[method] +------------------------------------------------------------------------- + +結果のページネーションを容易にします。ページ番号(1から数える)とページあたりの項目数を受け取ります。オプションで、合計ページ数が格納される変数への参照を渡すことができます: + +```php +$numOfPages = null; +$table->page(page: 3, itemsPerPage: 10, $numOfPages); +echo "合計ページ数: $numOfPages"; +``` + + +group(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- + +指定されたカラムに基づいて行をグループ化します(GROUP BY)。通常、集計関数と組み合わせて使用されます: + +```php +// 各カテゴリの製品数をカウント +$table->select('category_id, COUNT(*) AS count') + ->group('category_id'); +``` + + +having(string $having, ...$parameters): static .[method] +-------------------------------------------------------- + +グループ化された行をフィルタリングするための条件(HAVING)を設定します。`group()` メソッドと集計関数と組み合わせて使用できます: + +```php +// 100以上の製品を持つカテゴリを検索 +$table->select('category_id, COUNT(*) AS count') + ->group('category_id') + ->having('count > ?', 100); +``` + + +データの読み取り +======== + +データベースからデータを読み取るために、いくつかの便利なメソッドが利用可能です: + +.[language-php] +| `foreach ($table as $key => $row)` | 全行を反復処理します。`$key`は主キーの値、`$row`はActiveRowオブジェクトです +| `$row = $table->get($key)` | 主キーに基づいて1行を返します +| `$row = $table->fetch()` | 現在の行を返し、ポインタを次に進めます +| `$array = $table->fetchPairs()` | 結果から連想配列を作成します +| `$array = $table->fetchAll()` | 全行を配列として返します +| `count($table)` | Selectionオブジェクト内の行数を返します + +[ActiveRow |api:Nette\Database\Table\ActiveRow] オブジェクトは読み取り専用です。つまり、そのプロパティの値を変更することはできません。この制限により、データの整合性が保証され、予期しない副作用が防止されます。データはデータベースから読み込まれ、変更は明示的かつ制御された方法で行われるべきです。 + + +`foreach` - 全行の反復処理 +------------------- + +クエリを実行して行を取得する最も簡単な方法は、`foreach` ループで反復処理することです。SQLクエリを自動的に実行します。 + +```php +$books = $explorer->table('book'); +foreach ($books as $key => $book) { + // $keyは主キーの値、$bookはActiveRow + echo "$book->title ({$book->author->name})"; +} +``` + + +get($key): ?ActiveRow .[method] +------------------------------- + +SQLクエリを実行し、主キーに基づいて行を返します。存在しない場合は `null` を返します。 + +```php +$book = $explorer->table('book')->get(123); // ID 123のActiveRowまたはnullを返します +if ($book) { + echo $book->title; +} +``` + + +fetch(): ?ActiveRow .[method] +----------------------------- + +行を返し、内部ポインタを次に進めます。これ以上行がない場合は `null` を返します。 + +```php +$books = $explorer->table('book'); +while ($book = $books->fetch()) { + $this->processBook($book); +} +``` + + +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +結果を連想配列として返します。最初の引数は配列のキーとして使用されるカラム名を指定し、2番目の引数は値として使用されるカラム名を指定します: + +```php +$authors = $explorer->table('author')->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] +``` + +最初のパラメータのみを指定した場合、値は行全体、つまり `ActiveRow` オブジェクトになります: + +```php +$authors = $explorer->table('author')->fetchPairs('id'); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` + +キーが重複する場合、最後の行の値が使用されます。キーとして `null` を使用すると、配列はゼロから始まる数値インデックスになります(この場合、衝突は発生しません): + +```php +$authors = $explorer->table('author')->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] +``` + + +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +あるいは、パラメータとしてコールバックを指定することもできます。これは、各行に対して値自体、またはキーと値のペアのいずれかを返します。 + +```php +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => "$row->title ({$row->author->name})"); +// ['最初の本 (Jan Novák)', ...] + +// コールバックはキーと値のペアを持つ配列を返すこともできます: +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => [$row->title, $row->author->name]); +// ['最初の本' => 'Jan Novák', ...] +``` + + +fetchAll(): array .[method] +--------------------------- + +すべての行を `ActiveRow` オブジェクトの連想配列として返します。キーは主キーの値です。 + +```php +$allBooks = $explorer->table('book')->fetchAll(); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` + + +count(): int .[method] +---------------------- + +パラメータなしの `count()` メソッドは、`Selection` オブジェクト内の行数を返します: + +```php +$table->where('category', 1); +$count = $table->count(); +$count = count($table); // 代替 +``` + +注意:パラメータ付きの `count()` は、データベースで集計関数COUNTを実行します。下記参照。 + + +ActiveRow::toArray(): array .[method] +------------------------------------- + +`ActiveRow` オブジェクトを連想配列に変換します。キーはカラム名、値は対応するデータです。 + +```php +$book = $explorer->table('book')->get(1); +$bookArray = $book->toArray(); +// $bookArray は ['id' => 1, 'title' => '...', 'author_id' => ..., ...] になります +``` + + +集計 +======== + +`Selection` クラスは、集計関数(COUNT、SUM、MIN、MAX、AVGなど)を簡単に実行するためのメソッドを提供します。 + +.[language-php] +| `count($expr)` | 行数をカウントします +| `min($expr)` | カラム内の最小値を返します +| `max($expr)` | カラム内の最大値を返します +| `sum($expr)` | カラム内の値の合計を返します +| `aggregation($function)` | 任意の集計関数を実行できます。例: `AVG()`, `GROUP_CONCAT()` + + +count(string $expr): int .[method] +---------------------------------- + +COUNT関数を使用してSQLクエリを実行し、結果を返します。このメソッドは、特定の条件に一致する行数を調べるために使用されます: + +```php +$count = $table->count('*'); // SELECT COUNT(*) FROM `table` +$count = $table->count('DISTINCT column'); // SELECT COUNT(DISTINCT `column`) FROM `table` +``` + +注意:パラメータなしの [#count()] は、`Selection` オブジェクト内の行数を返すだけです。 + + +min(string $expr) a max(string $expr) .[method] +----------------------------------------------- + +`min()` および `max()` メソッドは、指定されたカラムまたは式の最小値と最大値を返します: + +```php +// SELECT MAX(`price`) FROM `products` WHERE `active` = 1 +$maxPrice = $products->where('active', true) + ->max('price'); +``` + + +sum(string $expr) .[method] +--------------------------- + +指定されたカラムまたは式の値の合計を返します: + +```php +// SELECT SUM(`price` * `items_in_stock`) FROM `products` WHERE `active` = 1 +$totalPrice = $products->where('active', true) + ->sum('price * items_in_stock'); +``` + + +aggregation(string $function, ?string $groupFunction = null) .[method] +---------------------------------------------------------------------- + +任意の集計関数を実行できます。 + +```php +// カテゴリ内の製品の平均価格 +$avgPrice = $products->where('category_id', 1) + ->aggregation('AVG(price)'); + +// 製品のタグを1つの文字列に結合します +$tags = $products->where('id', 1) + ->aggregation('GROUP_CONCAT(tag.name) AS tags') + ->fetch() + ->tags; +``` + +既に何らかの集計関数とグループ化から生じた結果(例:グループ化された行に対する `SUM(値)`)を集計する必要がある場合、2番目の引数として、これらの中間結果に適用する集計関数を指定します: + +```php +// 各カテゴリの在庫製品の合計価格を計算し、その後これらの価格を合計します。 +$totalPrice = $products->select('category_id, SUM(price * stock) AS category_total') + ->group('category_id') + ->aggregation('SUM(category_total)', 'SUM'); +``` + +この例では、まず各カテゴリの製品の合計価格(`SUM(price * stock) AS category_total`)を計算し、`category_id` で結果をグループ化します。次に、`aggregation('SUM(category_total)', 'SUM')` を使用して、これらの中間合計 `category_total` を合計します。2番目の引数 `'SUM'` は、中間結果にSUM関数を適用することを示します。 + + +Insert, Update & Delete +======================= + +Nette Database Explorerは、データの挿入、更新、削除を簡素化します。記載されているすべてのメソッドは、`Nette\Database\DriverException` 例外をスローします。 + + +Selection::insert(iterable $data) .[method] +------------------------------------------- + +テーブルに新しいレコードを挿入します。 + +**単一レコードの挿入:** + +新しいレコードを連想配列またはiterableオブジェクト(たとえば [フォーム |forms:] で使用されるArrayHash)として渡します。キーはテーブルのカラム名に対応します。 + +テーブルに主キーが定義されている場合、メソッドはデータベースから再読み込みされた `ActiveRow` オブジェクトを返します。これにより、データベースレベルで行われた変更(トリガー、カラムのデフォルト値、自動インクリメントカラムの計算)が反映されます。これにより、データの整合性が保証され、オブジェクトは常にデータベースからの最新データを含みます。一意の主キーがない場合は、渡されたデータを配列形式で返します。 + +```php +$row = $explorer->table('users')->insert([ + 'name' => 'John Doe', + 'email' => 'john.doe@example.com', +]); +// $rowはActiveRowのインスタンスであり、挿入された行の完全なデータを含みます。 +// 自動生成されたIDやトリガーによって行われた変更も含みます +echo $row->id; // 新しく挿入されたユーザーのIDを出力します +echo $row->created_at; // トリガーによって設定されている場合、作成時間を出力します +``` + +**複数のレコードを一度に挿入:** + +`insert()` メソッドを使用すると、単一のSQLクエリで複数のレコードを挿入できます。この場合、挿入された行数を返します。 + +```php +$insertedRows = $explorer->table('users')->insert([ + [ + 'name' => 'John', + 'year' => 1994, + ], + [ + 'name' => 'Jack', + 'year' => 1995, + ], +]); +// INSERT INTO `users` (`name`, `year`) VALUES ('John', 1994), ('Jack', 1995) +// $insertedRows は 2 になります +``` + +パラメータとして、データ選択を持つ `Selection` オブジェクトを渡すこともできます。 + +```php +$newUsers = $explorer->table('potential_users') + ->where('approved', 1) + ->select('name, email'); + +$insertedRows = $explorer->table('users')->insert($newUsers); +``` + +**特殊な値の挿入:** + +値として、ファイル、DateTimeオブジェクト、またはSQLリテラルを渡すこともできます: + +```php +$explorer->table('users')->insert([ + 'name' => 'John', + 'created_at' => new DateTime, // データベース形式に変換します + 'avatar' => fopen('image.jpg', 'rb'), // ファイルのバイナリコンテンツを挿入します + 'uuid' => $explorer::literal('UUID()'), // UUID() 関数を呼び出します +]); +``` + + +Selection::update(iterable $data): int .[method] +------------------------------------------------ + +指定されたフィルタに従ってテーブル内の行を更新します。実際に変更された行数を返します。 + +変更するカラムを連想配列またはiterableオブジェクト(たとえば [フォーム |forms:] で使用されるArrayHash)として渡します。キーはテーブルのカラム名に対応します: + +```php +$affected = $explorer->table('users') + ->where('id', 10) + ->update([ + 'name' => 'John Smith', + 'year' => 1994, + ]); +// UPDATE `users` SET `name` = 'John Smith', `year` = 1994 WHERE `id` = 10 +``` + +数値の値を変更するには、`+=` および `-=` 演算子を使用できます: + +```php +$explorer->table('users') + ->where('id', 10) + ->update([ + 'points+=' => 1, // 'points' カラムの値を1増やします + 'coins-=' => 1, // 'coins' カラムの値を1減らします + ]); +// UPDATE `users` SET `points` = `points` + 1, `coins` = `coins` - 1 WHERE `id` = 10 +``` + + +Selection::delete(): int .[method] +---------------------------------- + +指定されたフィルタに従ってテーブルから行を削除します。削除された行数を返します。 + +```php +$count = $explorer->table('users') + ->where('id', 10) + ->delete(); +// DELETE FROM `users` WHERE `id` = 10 +``` + +.[caution] +`update()` および `delete()` を呼び出すときは、`where()` を使用して変更/削除する行を指定することを忘れないでください。`where()` を使用しない場合、操作はテーブル全体に対して実行されます! + + +ActiveRow::update(iterable $data): bool .[method] +------------------------------------------------- + +`ActiveRow` オブジェクトによって表されるデータベース行のデータを更新します。パラメータとして、更新するデータを含むiterable(キーはカラム名)を受け取ります。数値の値を変更するには、`+=` および `-=` 演算子を使用できます: + +更新を実行した後、`ActiveRow` はデータベースから自動的に再読み込みされ、データベースレベルで行われた変更(例:トリガー)が反映されます。メソッドは、データが実際に変更された場合にのみtrueを返します。 + +```php +$article = $explorer->table('article')->get(1); +$article->update([ + 'views += 1', // 表示回数を増やします +]); +echo $article->views; // 現在の表示回数を出力します +``` + +このメソッドは、データベース内の特定の1行のみを更新します。複数の行を一括更新するには、[#Selection::update()] メソッドを使用します。 + + +ActiveRow::delete() .[method] +----------------------------- + +`ActiveRow` オブジェクトによって表されるデータベースから行を削除します。 + +```php +$book = $explorer->table('book')->get(1); +$book->delete(); // ID 1の書籍を削除します +``` + +このメソッドは、データベース内の特定の1行のみを削除します。複数の行を一括削除するには、[#Selection::delete()] メソッドを使用します。 + + +テーブル間のリレーション +============ + +リレーショナルデータベースでは、データは複数のテーブルに分割され、外部キーを使用して相互にリンクされています。Nette Database Explorerは、これらのリレーションを操作するための革新的な方法を提供します - JOINクエリを記述したり、何かを設定したり生成したりする必要はありません。 + +リレーションの操作を説明するために、書籍データベースの例を使用します([GitHubで見つけることができます |https://github.com/nette-examples/books])。データベースには次のテーブルがあります: + +- `author` - 作家と翻訳者(カラム `id`, `name`, `web`, `born`) +- `book` - 書籍(カラム `id`, `author_id`, `translator_id`, `title`, `sequel_id`) +- `tag` - タグ(カラム `id`, `name`) +- `book_tag` - 書籍とタグ間の関連テーブル(カラム `book_id`, `tag_id`) + +[* db-schema-1-.webp *] *** データベース構造 .<> + +書籍データベースの例では、いくつかのタイプの関係が見つかります(モデルは現実よりも単純化されていますが): + +- One-to-many 1:N – 各書籍には **1人の** 著者がおり、著者は **複数の** 書籍を書くことができます +- Zero-to-many 0:N – 書籍には翻訳者が **いる場合があり**、翻訳者は **複数の** 書籍を翻訳できます +- Zero-to-one 0:1 – 書籍には続編が **ある場合があります** +- Many-to-many M:N – 書籍には **複数の** タグがあり、タグは **複数の** 書籍に割り当てることができます + +これらの関係では、常に親テーブルと子テーブルが存在します。たとえば、著者と書籍の関係では、`author` テーブルが親で、`book` テーブルが子です - 書籍は常に何らかの著者に「属している」と考えることができます。これはデータベースの構造にも反映されています:子テーブル `book` には、親テーブル `author` を参照する外部キー `author_id` が含まれています。 + +著者名を含む書籍をリストする必要がある場合、2つの選択肢があります。JOINを使用して単一のSQLクエリでデータを取得する: + +```sql +SELECT book.*, author.name FROM book LEFT JOIN author ON book.author_id = author.id +``` + +または、データを2段階で読み込む - まず書籍、次にその著者 - そしてPHPでそれらを組み立てる: + +```sql +SELECT * FROM book; +SELECT * FROM author WHERE id IN (1, 2, 3); -- 取得した書籍の著者ID +``` + +2番目のアプローチは、驚くべきかもしれませんが、実際にはより効率的です。データは一度だけ読み込まれ、キャッシュでより良く利用できます。Nette Database Explorerはこの方法で動作します - すべてを内部で処理し、エレガントなAPIを提供します: + +```php +$books = $explorer->table('book'); +foreach ($books as $book) { + echo 'title: ' . $book->title; + echo 'written by: ' . $book->author->name; // $book->author は 'author' テーブルからのレコードです + echo 'translated by: ' . $book->translator?->name; +} +``` + + +親テーブルへのアクセス +----------- + +親テーブルへのアクセスは簡単です。これは *書籍には著者がいる* または *書籍には翻訳者がいる場合がある* のような関係です。関連するレコードは、ActiveRowオブジェクトのプロパティを介して取得します - その名前は、`id` を除いた外部キーのカラム名に対応します: + +```php +$book = $explorer->table('book')->get(1); +echo $book->author->name; // author_id カラムに基づいて著者を見つけます +echo $book->translator?->name; // translator_id に基づいて翻訳者を見つけます +``` + +プロパティ `$book->author` にアクセスすると、Explorerは `book` テーブルで文字列 `author` を含むカラム(つまり `author_id`)を探します。このカラムの値に基づいて、対応するレコードを `author` テーブルから読み込み、`ActiveRow` として返します。同様に、`$book->translator` も機能し、`translator_id` カラムを使用します。`translator_id` カラムは `null` を含む可能性があるため、コードで `?->` 演算子を使用します。 + +代替の方法として、`ref()` メソッドがあります。これは、ターゲットテーブルの名前と結合カラムの名前の2つの引数を受け取り、`ActiveRow` インスタンスまたは `null` を返します: + +```php +echo $book->ref('author', 'author_id')->name; // 著者へのリレーション +echo $book->ref('author', 'translator_id')->name; // 翻訳者へのリレーション +``` + +`ref()` メソッドは、テーブルに同じ名前のカラム(つまり `author`)が含まれているためにプロパティアクセスを使用できない場合に便利です。その他の場合、読みやすいプロパティアクセスを使用することをお勧めします。 + +Explorerはデータベースクエリを自動的に最適化します。ループで書籍を反復処理し、それらの関連レコード(著者、翻訳者)にアクセスする場合、Explorerは各書籍に対して個別にクエリを生成しません。代わりに、各リレーションタイプに対して1つのSELECTのみを実行し、データベースの負荷を大幅に削減します。たとえば: + +```php +$books = $explorer->table('book'); +foreach ($books as $book) { + echo $book->title . ': '; + echo $book->author->name; + echo $book->translator?->name; +} +``` + +このコードは、データベースに対してこれら3つの高速なクエリのみを呼び出します: + +```sql +SELECT * FROM `book`; +SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- 選択された書籍の author_id カラムからのID +SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- 選択された書籍の translator_id カラムからのID +``` + +.[note] +結合カラムの検索ロジックは、[Conventions |api:Nette\Database\Conventions] の実装によって決定されます。[DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions] の使用をお勧めします。これは外部キーを分析し、テーブル間の既存のリレーションを簡単に操作できます。 + + +子テーブルへのアクセス +----------- + +子テーブルへのアクセスは逆方向に機能します。今度は *この著者が書いた書籍は何か* または *この翻訳者が翻訳した書籍は何か* を尋ねます。このタイプのクエリには、関連レコードを持つ `Selection` を返す `related()` メソッドを使用します。例を見てみましょう: + +```php +$author = $explorer->table('author')->get(1); + +// 著者のすべての書籍を出力します +foreach ($author->related('book.author_id') as $book) { + echo "執筆: $book->title"; +} + +// 著者が翻訳したすべての書籍を出力します +foreach ($author->related('book.translator_id') as $book) { + echo "翻訳: $book->title"; +} +``` + +`related()` メソッドは、ドット表記の単一引数として、または2つの個別の引数として結合の説明を受け入れます: + +```php +$author->related('book.translator_id'); // 1つの引数 +$author->related('book', 'translator_id'); // 2つの引数 +``` + +Explorerは、親テーブルの名前に基づいて正しい結合カラムを自動的に検出できます。この場合、ソーステーブルの名前が `author` であるため、`book.author_id` カラムを介して結合されます: + +```php +$author->related('book'); // book.author_id を使用します +``` + +複数の可能な結合が存在する場合、Explorerは [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException] 例外をスローします。 + +`related()` メソッドは、もちろん、ループで複数のレコードを反復処理する場合にも使用でき、Explorerはこの場合でもクエリを自動的に最適化します: + +```php +$authors = $explorer->table('author'); +foreach ($authors as $author) { + echo $author->name . ' 執筆:'; + foreach ($author->related('book') as $book) { + echo $book->title; + } +} +``` + +このコードは、2つの高速なSQLクエリのみを生成します: + +```sql +SELECT * FROM `author`; +SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- 選択された著者のID +``` + + +多対多リレーション +--------- + +多対多(M:N)リレーションには、2つの外部キーカラム(`book_id`、`tag_id`)を含む関連テーブル(この場合は `book_tag`)が必要です。これらのカラムのそれぞれは、リンクされたテーブルの1つの主キーを参照します。関連データを取得するには、まず `related('book_tag')` を使用して関連テーブルからレコードを取得し、次にターゲットデータに進みます: + +```php +$book = $explorer->table('book')->get(1); +// 書籍に割り当てられたタグの名前を出力します +foreach ($book->related('book_tag') as $bookTag) { + echo $bookTag->tag->name; // 関連テーブルを介してタグの名前を出力します +} + +$tag = $explorer->table('tag')->get(1); +// または逆: このタグでマークされた書籍の名前を出力します +foreach ($tag->related('book_tag') as $bookTag) { + echo $bookTag->book->title; // 書籍の名前を出力します +} +``` + +Explorerは再びSQLクエリを効率的な形式に最適化します: + +```sql +SELECT * FROM `book`; +SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 2, ...)); -- 選択された書籍のID +SELECT * FROM `tag` WHERE (`tag`.`id` IN (1, 2, ...)); -- book_tagで見つかったタグのID +``` + + +関連テーブルを介したクエリ +------------- + +`where()`、`select()`、`order()`、`group()` メソッドでは、他のテーブルのカラムにアクセスするための特別な表記法を使用できます。Explorerは必要なJOINを自動的に作成します。 + +**ドット表記** (`親テーブル.カラム`) は、子テーブルの観点からの1:N関係に使用されます: + +```php +$books = $explorer->table('book'); + +// 著者の名前が 'Jon' で始まる書籍を見つけます +$books->where('author.name LIKE ?', 'Jon%'); + +// 著者の名前で書籍を降順にソートします +$books->order('author.name DESC'); + +// 書籍のタイトルと著者の名前を出力します +$books->select('book.title, author.name'); +``` + +**コロン表記** (`:子テーブル.カラム`) は、親テーブルの観点からの1:N関係に使用されます: + +```php +$authors = $explorer->table('author'); + +// タイトルに 'PHP' を含む書籍を書いた著者を見つけます +$authors->where(':book.title LIKE ?', '%PHP%'); + +// 各著者の書籍数をカウントします +$authors->select('*, COUNT(:book.id) AS book_count') + ->group('author.id'); +``` + +上記のコロン表記(`:book.title`)の例では、外部キーのカラムが指定されていません。Explorerは、親テーブルの名前に基づいて正しいカラムを自動的に検出します。この場合、ソーステーブルの名前が `author` であるため、`book.author_id` カラムを介して結合されます。複数の可能な結合が存在する場合、Explorerは [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException] 例外をスローします。 + +結合カラムは括弧内に明示的に指定できます: + +```php +// タイトルに 'PHP' を含む書籍を翻訳した著者を見つけます +$authors->where(':book(translator_id).title LIKE ?', '%PHP%'); +``` + +表記法は、複数のテーブルを介してアクセスするために連鎖させることができます: + +```php +// 'PHP' タグでマークされた書籍の著者を見つけます +$authors->where(':book:book_tag.tag.name', 'PHP') + ->group('author.id'); +``` + + +JOIN条件の拡張 +--------- + +`joinWhere()` メソッドは、SQLでテーブルを結合する際に `ON` キーワードの後に指定される条件を拡張します。 + +特定の翻訳者によって翻訳された書籍を見つけたいとしましょう: + +```php +// 'David' という名前の翻訳者によって翻訳された書籍を見つけます +$books = $explorer->table('book') + ->joinWhere('translator', 'translator.name', 'David'); +// LEFT JOIN author translator ON book.translator_id = translator.id AND (translator.name = 'David') +``` + +`joinWhere()` 条件では、`where()` メソッドと同じ構文を使用できます - 演算子、プレースホルダー疑問符、値の配列、またはSQL式。 + +複数のJOINを持つより複雑なクエリの場合、テーブルエイリアスを定義できます: + +```php +$tags = $explorer->table('tag') + ->joinWhere(':book_tag.book.author', 'book_author.born < ?', 1950) + ->alias(':book_tag.book.author', 'book_author'); +// LEFT JOIN `book_tag` ON `tag`.`id` = `book_tag`.`tag_id` +// LEFT JOIN `book` ON `book_tag`.`book_id` = `book`.`id` +// LEFT JOIN `author` `book_author` ON `book`.`author_id` = `book_author`.`id` +// AND (`book_author`.`born` < 1950) +``` + +`where()` メソッドが `WHERE` 句に条件を追加するのに対し、`joinWhere()` メソッドはテーブルを結合する際の `ON` 句の条件を拡張することに注意してください。 diff --git a/database/ja/guide.texy b/database/ja/guide.texy new file mode 100644 index 0000000000..26fd499ed4 --- /dev/null +++ b/database/ja/guide.texy @@ -0,0 +1,216 @@ +Nette Database +************** + +.[perex] +Nette Databaseは、シンプルさとスマートな機能に重点を置いた、PHP向けの強力でエレガントなデータベース層です。データベースを操作する2つの方法を提供します - アプリケーションの迅速な開発のための[Explorer |Explorer]、またはクエリを直接操作するための[SQLアクセス |SQL way]。 + +<div class="grid gap-3"> +<div> + + +[SQLアクセス |SQL way] +================== +- 安全なパラメータ化クエリ +- SQLクエリの形式に対する正確な制御 +- 高度な機能を持つ複雑なクエリを作成する場合 +- 特定のSQL機能を使用してパフォーマンスを最適化する場合 + +</div> + +<div> + + +[Explorer |Explorer] +==================== +- SQLを書かずに迅速に開発 +- テーブル間のリレーションを直感的に操作 +- クエリの自動最適化を評価 +- データベースを迅速かつ快適に操作するのに適しています + +</div> + +</div> + + +インストール +====== + +ライブラリは[Composer|best-practices:composer]ツールを使用してダウンロードおよびインストールします: + +```shell +composer require nette/database +``` + + +サポートされているデータベース +=============== + +Nette Databaseは以下のデータベースをサポートしています: + +|* データベースサーバ |* DSN名 |* Explorerでのサポート +|---------------------|-------------|----------------------- +| MySQL (>= 5.1) | mysql | はい +| PostgreSQL (>= 9.0) | pgsql | はい +| Sqlite 3 (>= 3.8) | sqlite | はい +| Oracle | oci | - +| MS SQL (PDO_SQLSRV) | sqlsrv | はい +| MS SQL (PDO_DBLIB) | mssql | - +| ODBC | odbc | - + + +データベースへの2つのアプローチ +================ + +Nette Databaseは選択肢を提供します:SQLクエリを直接記述する(SQLアクセス)か、自動的に生成させる(Explorer)かです。両方のアプローチが同じタスクをどのように解決するかを見てみましょう: + +[SQLアクセス |sql way] - SQLクエリ + +```php +// レコードの挿入 +$database->query('INSERT INTO books', [ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// レコードの取得: 本の著者 +$result = $database->query(' + SELECT authors.*, COUNT(books.id) AS books_count + FROM authors + LEFT JOIN books ON authors.id = books.author_id + WHERE authors.active = 1 + GROUP BY authors.id +'); + +// 出力 (最適ではない、N個の追加クエリを生成する) +foreach ($result as $author) { + $books = $database->query(' + SELECT * FROM books + WHERE author_id = ? + ORDER BY published_at DESC + ', $author->id); + + echo "著者 $author->name は $author->books_count 冊の本を書きました:\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +[Explorerアクセス |explorer] - SQLの自動生成 + +```php +// レコードの挿入 +$database->table('books')->insert([ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// レコードの取得: 本の著者 +$authors = $database->table('authors') + ->where('active', 1); + +// 出力 (自動的に最適化された2つのクエリのみを生成) +foreach ($authors as $author) { + $books = $author->related('books') + ->order('published_at DESC'); + + echo "著者 $author->name は {$books->count()} 冊の本を書きました:\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +ExplorerアクセスはSQLクエリを自動的に生成および最適化します。上記の例では、SQLアクセスはN+1個のクエリ(著者用に1つ、各著者の本用に1つ)を生成しますが、Explorerはクエリを自動的に最適化し、2つだけ実行します - 著者用に1つ、すべての本用に1つです。 + +両方のアプローチは、必要に応じてアプリケーション内で自由に組み合わせることができます。 + + +接続と設定 +===== + +データベースに接続するには、[api:Nette\Database\Connection]クラスのインスタンスを作成するだけです: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password); +``` + +パラメータ `$dsn`(データソース名)は、[PDOが使用するもの |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters]と同じです。例:`host=127.0.0.1;dbname=test`。失敗した場合、`Nette\Database\ConnectionException`例外をスローします。 + +ただし、より便利な方法は[アプリケーション設定 |configuration]を使用することです。ここに`database`セクションを追加するだけで、必要なオブジェクトと[Tracy |tracy:]バーのデータベースパネルが作成されます。 + +```neon +database: + dsn: 'mysql:host=127.0.0.1;dbname=test' + user: root + password: password +``` + +その後、接続オブジェクトを[DIコンテナからサービスとして取得 |dependency-injection:passing-dependencies]します。例: + +```php +class Model +{ + public function __construct( + // または Nette\Database\Explorer + private Nette\Database\Connection $database, + ) { + } +} +``` + +[データベース設定 |configuration]の詳細については、こちらをご覧ください。 + + +Explorerの手動作成 +------------- + +Nette DIコンテナを使用しない場合は、`Nette\Database\Explorer`インスタンスを手動で作成できます: + +```php +// データベースへの接続 +$connection = new Nette\Database\Connection('mysql:host=127.0.0.1;dbname=mydatabase', 'user', 'password'); +// キャッシュ用ストレージ、Nette\Caching\Storage を実装、例: +$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp/dir'); +// データベース構造のリフレクションを担当 +$structure = new Nette\Database\Structure($connection, $storage); +// テーブル名、カラム名、外部キーのマッピングルールを定義 +$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); +$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +``` + + +接続管理 +==== + +`Connection`オブジェクトを作成すると、接続が自動的に確立されます。接続を遅延させたい場合は、遅延モードを使用します - これは[設定 |configuration]で`lazy`を設定するか、次のようにして有効にします: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password, ['lazy' => true]); +``` + +接続を管理するには、`connect()`、`disconnect()`、`reconnect()`メソッドを使用します。 +- `connect()` は、まだ存在しない場合に接続を作成し、`Nette\Database\ConnectionException`例外をスローする可能性があります。 +- `disconnect()` は、現在のデータベース接続を切断します。 +- `reconnect()` は、データベースへの切断と再接続を実行します。このメソッドも`Nette\Database\ConnectionException`例外をスローする可能性があります。 + +さらに、`onConnect`イベントを使用して接続に関連するイベントを監視できます。これは、データベースとの接続が確立された後に呼び出されるコールバックの配列です。 + +```php +// データベースへの接続後に実行されます +$database->onConnect[] = function($database) { + echo "データベースに接続しました"; +}; +``` + + +Tracyデバッグバー +=========== + +[Tracy |tracy:]を使用している場合、デバッグバーにデータベースパネルが自動的にアクティブになり、実行されたすべてのクエリ、そのパラメータ、実行時間、およびコード内で呼び出された場所が表示されます。 + +[* db-panel.webp *] diff --git a/database/ja/mapping.texy b/database/ja/mapping.texy new file mode 100644 index 0000000000..7894b02a82 --- /dev/null +++ b/database/ja/mapping.texy @@ -0,0 +1,55 @@ +型変換 +*** + +.[perex] +Nette Databaseは、データベースから返された値を対応するPHP型に自動的に変換します。 + + +日付と時刻 +----- + +時間データは`Nette\Utils\DateTime`オブジェクトに変換されます。時間データを不変の`Nette\Database\DateTime`オブジェクトに変換したい場合は、[設定 |configuration]で`newDateTime`オプションをtrueに設定します。 + +```php +$row = $database->fetch('SELECT created_at FROM articles'); +echo $row->created_at instanceof DateTime; // true +echo $row->created_at->format('Y年n月j日'); +``` + +MySQLの場合、データ型`TIME`は`DateInterval`オブジェクトに変換されます。 + + +ブール値 +---- + +ブール値は自動的に`true`または`false`に変換されます。[設定 |configuration]で`convertBoolean`を設定すると、MySQLでは`TINYINT(1)`が変換されます。 + +```php +$row = $database->fetch('SELECT is_published FROM articles'); +echo gettype($row->is_published); // 'boolean' +``` + + +数値 +--------------- + +数値は、データベースのカラム型に応じて`int`または`float`に変換されます: + +```php +$row = $database->fetch('SELECT id, price FROM products'); +echo gettype($row->id); // integer +echo gettype($row->price); // float +``` + + +カスタム正規化 +------- + +`setRowNormalizer(?callable $normalizer)`メソッドを使用して、データベースからの行を変換するためのカスタム関数を設定できます。これは、たとえばデータ型の自動変換に役立ちます。 + +```php +$database->setRowNormalizer(function(array $row, ResultSet $resultSet): array { + // ここで型変換が行われます + return $row; +}); +``` diff --git a/database/ja/reflection.texy b/database/ja/reflection.texy new file mode 100644 index 0000000000..d7fa9dc883 --- /dev/null +++ b/database/ja/reflection.texy @@ -0,0 +1,125 @@ +構造リフレクション +********* + +.{data-version:3.2.1} +Nette Databaseは、[api:Nette\Database\Reflection]クラスを使用してデータベース構造をイントロスペクションするためのツールを提供します。これにより、テーブル、カラム、インデックス、および外部キーに関する情報を取得できます。リフレクションを使用して、スキーマの生成、データベースを操作する柔軟なアプリケーションの作成、または一般的なデータベースツールの作成を行うことができます。 + +リフレクションオブジェクトは、データベース接続インスタンスから取得します: + +```php +$reflection = $database->getReflection(); +``` + + +テーブルの取得 +------- + +読み取り専用プロパティ `$reflection->tables` には、データベース内のすべてのテーブルの連想配列が含まれています: + +```php +// すべてのテーブル名の出力 +foreach ($reflection->tables as $name => $table) { + echo $name . "\n"; +} +``` + +さらに2つのメソッドが利用可能です: + +```php +// テーブルの存在確認 +if ($reflection->hasTable('users')) { + echo "テーブル users は存在します"; +} + +// テーブルオブジェクトを返します。存在しない場合は例外をスローします +$table = $reflection->getTable('users'); +``` + + +テーブル情報 +------ + +テーブルは、以下の読み取り専用プロパティを提供する[Table|api:Nette\Database\Reflection\Table]オブジェクトによって表されます: + +- `$name: string` – テーブル名 +- `$view: bool` – ビューであるかどうか +- `$fullName: ?string` – スキーマを含む完全なテーブル名(存在する場合) +- `$columns: array<string, Column>` – テーブルのカラムの連想配列 +- `$indexes: Index[]` – テーブルのインデックスの配列 +- `$primaryKey: ?Index` – テーブルの主キーまたはnull +- `$foreignKeys: ForeignKey[]` – テーブルの外部キーの配列 + + +カラム +--- + +テーブルの`columns`プロパティは、キーがカラム名、値が以下のプロパティを持つ[Column|api:Nette\Database\Reflection\Column]インスタンスであるカラムの連想配列を提供します: + +- `$name: string` – カラム名 +- `$table: ?Table` – カラムのテーブルへの参照 +- `$nativeType: string` – ネイティブデータベース型 +- `$size: ?int` – 型のサイズ/長さ +- `$nullable: bool` – カラムがNULLを含むことができるかどうか +- `$default: mixed` – カラムのデフォルト値 +- `$autoIncrement: bool` – カラムが自動インクリメントであるかどうか +- `$primary: bool` – 主キーの一部であるかどうか +- `$vendor: array` – 特定のデータベースシステムに固有の追加メタデータ + +```php +foreach ($table->columns as $name => $column) { + echo "カラム: $name\n"; + echo "型: {$column->nativeType}\n"; + echo "Nullable: " . ($column->nullable ? 'はい' : 'いいえ') . "\n"; +} +``` + + +インデックス +------ + +テーブルの`indexes`プロパティは、各インデックスが以下のプロパティを持つ[Index|api:Nette\Database\Reflection\Index]インスタンスであるインデックスの配列を提供します: + +- `$columns: Column[]` – インデックスを構成するカラムの配列 +- `$unique: bool` – インデックスが一意であるかどうか +- `$primary: bool` – 主キーであるかどうか +- `$name: ?string` – インデックス名 + +テーブルの主キーは`primaryKey`プロパティを使用して取得でき、これは`Index`オブジェクトまたはテーブルに主キーがない場合は`null`を返します。 + +```php +// インデックスの出力 +foreach ($table->indexes as $index) { + $columns = implode(', ', array_map(fn($col) => $col->name, $index->columns)); + echo "インデックス" . ($index->name ? " {$index->name}" : '') . ":\n"; + echo " カラム: $columns\n"; + echo " Unique: " . ($index->unique ? 'はい' : 'いいえ') . "\n"; +} + +// 主キーの出力 +if ($primaryKey = $table->primaryKey) { + $columns = implode(', ', array_map(fn($col) => $col->name, $primaryKey->columns)); + echo "主キー: $columns\n"; +} +``` + + +外部キー +---- + +テーブルの`foreignKeys`プロパティは、各外部キーが以下のプロパティを持つ[ForeignKey|api:Nette\Database\Reflection\ForeignKey]インスタンスである外部キーの配列を提供します: + +- `$foreignTable: Table` – 参照されるテーブル +- `$localColumns: Column[]` – ローカルカラムの配列 +- `$foreignColumns: Column[]` – 参照されるカラムの配列 +- `$name: ?string` – 外部キー名 + +```php +// 外部キーの出力 +foreach ($table->foreignKeys as $fk) { + $localCols = implode(', ', array_map(fn($col) => $col->name, $fk->localColumns)); + $foreignCols = implode(', ', array_map(fn($col) => $col->name, $fk->foreignColumns)); + + echo "FK" . ($fk->name ? " {$fk->name}" : '') . ":\n"; + echo " $localCols -> {$fk->foreignTable->name}($foreignCols)\n"; +} +``` diff --git a/database/ja/security.texy b/database/ja/security.texy new file mode 100644 index 0000000000..cf61e7c60a --- /dev/null +++ b/database/ja/security.texy @@ -0,0 +1,185 @@ +セキュリティリスク +********* + +<div class=perex> + +データベースには機密データが含まれていることが多く、危険な操作を実行できます。Nette Databaseを安全に使用するためには、以下が重要です: + +- 安全なAPIと危険なAPIの違いを理解する +- パラメータ化されたクエリを使用する +- 入力データを正しく検証する + +</div> + + +SQLインジェクションとは? +============== + +SQLインジェクションは、データベースを操作する上で最も深刻なセキュリティリスクです。これは、ユーザーからの未処理の入力がSQLクエリの一部になったときに発生します。攻撃者は独自のSQLコマンドを挿入し、それによって: +- データへの不正アクセスを取得する +- データベース内のデータを変更または削除する +- 認証を回避する + +```php +// ❌ 危険なコード - SQLインジェクションに対して脆弱 +$database->query("SELECT * FROM users WHERE name = '$_GET[name]'"); + +// 攻撃者は例えば次の値を入力できます: ' OR '1'='1 +// 結果のクエリは次のようになります: SELECT * FROM users WHERE name = '' OR '1'='1' +// これによりすべてのユーザーが返されます +``` + +これはDatabase Explorerにも当てはまります: + +```php +// ❌ 危険なコード - SQLインジェクションに対して脆弱 +$table->where('name = ' . $_GET['name']); +$table->where("name = '$_GET[name]'"); +``` + + +パラメータ化されたクエリ +============ + +SQLインジェクションに対する基本的な防御策は、パラメータ化されたクエリです。Nette Databaseは、それらを使用するためのいくつかの方法を提供します。 + +最も簡単な方法は、**疑問符プレースホルダ**を使用することです: + +```php +// ✅ 安全なパラメータ化されたクエリ +$database->query('SELECT * FROM users WHERE name = ?', $name); + +// ✅ Explorerでの安全な条件 +$table->where('name = ?', $name); +``` + +これは、疑問符プレースホルダとパラメータを含む式を挿入できる[Database Explorer|explorer]の他のすべてのメソッドに適用されます。 + +INSERT、UPDATEコマンド、またはWHERE句の場合、値を配列で渡すことができます: + +```php +// ✅ 安全なINSERT +$database->query('INSERT INTO users', [ + 'name' => $name, + 'email' => $email, +]); + +// ✅ Explorerでの安全なINSERT +$table->insert([ + 'name' => $name, + 'email' => $email, +]); +``` + + +パラメータ値の検証 +========= + +パラメータ化されたクエリは、データベースを安全に操作するための基本的な構成要素です。ただし、それらに挿入する値は、いくつかのレベルのチェックを通過する必要があります: + + +型チェック +----- + +**最も重要なのは、パラメータの正しいデータ型を保証することです** - これはNette Databaseを安全に使用するための必須条件です。データベースは、すべての入力データが特定のカラムに対応する正しいデータ型を持っていることを前提としています。 + +たとえば、前の例で `$name` が文字列ではなく予期せず配列であった場合、Nette Databaseはそのすべての要素をSQLクエリに挿入しようとし、エラーが発生します。したがって、**決して** `$_GET`、`$_POST`、または `$_COOKIE` からの未検証のデータをデータベースクエリで直接使用しないでください。 + + +フォーマットチェック +---------- + +第2レベルでは、データのフォーマットをチェックします - たとえば、文字列がUTF-8エンコーディングであり、その長さがカラム定義に対応しているか、または数値が特定のカラムデータ型で許可されている範囲内にあるかどうか。 + +このレベルの検証では、データベース自体にも部分的に依存できます - 多くのデータベースは無効なデータを拒否します。ただし、動作は異なる場合があり、一部は長い文字列を黙って切り捨てたり、範囲外の数値を切り捨てたりする場合があります。 + + +ドメインチェック +-------- + +第3レベルは、アプリケーション固有の論理チェックを表します。たとえば、セレクトボックスの値が提供されたオプションに対応していること、数値が期待される範囲内にあること(例:年齢0〜150歳)、または値間の相互依存関係が意味をなすことの検証。 + + +推奨される検証方法 +--------- + +- すべての入力の正しい検証を自動的に保証する[Nette Forms |forms:]を使用します +- [Presenters |application:]を使用し、`action*()`および`render*()`メソッドのパラメータにデータ型を指定します +- または、`filter_var()`などの標準的なPHPツールを使用して独自の検証層を実装します + + +カラムの安全な操作 +========= + +前のセクションでは、パラメータ値を正しく検証する方法を示しました。ただし、SQLクエリで配列を使用する場合、そのキーにも同じ注意を払う必要があります。 + +```php +// ❌ 危険なコード - 配列内のキーが処理されていません +$database->query('INSERT INTO users', $_POST); +``` + +INSERTおよびUPDATEコマンドの場合、これは重大なセキュリティエラーです - 攻撃者はデータベース内の任意のカラムを挿入または変更できます。たとえば、`is_admin = 1` を設定したり、機密カラムに任意のデータを挿入したりできます(いわゆるマスアサインメント脆弱性)。 + +WHERE条件では、演算子を含めることができるため、さらに危険です: + +```php +// ❌ 危険なコード - 配列内のキーが処理されていません +$_POST['salary >'] = 100000; +$database->query('SELECT * FROM users WHERE', $_POST); +// クエリ WHERE (`salary` > 100000) を実行します +``` + +攻撃者はこのアプローチを使用して、従業員の給与を体系的に特定できます。たとえば、100,000を超える給与のクエリから始め、次に50,000未満のクエリを行い、範囲を徐々に狭めることで、すべての従業員のおおよその給与を明らかにすることができます。このタイプの攻撃はSQL列挙と呼ばれます。 + +`where()`および`whereOr()`メソッドは、[さらに柔軟 |explorer#where]であり、キーと値に演算子や関数を含むSQL式をサポートしています。これにより、攻撃者はSQLインジェクションを実行できます: + +```php +// ❌ 危険なコード - 攻撃者は独自のSQLを挿入できます +$_POST = ['0) UNION SELECT name, salary FROM users WHERE (1']; +$table->where($_POST); +// クエリ WHERE (0) UNION SELECT name, salary FROM users WHERE (1) を実行します +``` + +この攻撃は、`0)`を使用して元の条件を終了し、`UNION`を使用して独自の`SELECT`を追加して`users`テーブルから機密データを取得し、`WHERE (1)`を使用して構文的に正しいクエリを閉じます。 + + +カラムのホワイトリスト +----------- + +カラム名を安全に操作するには、ユーザーが許可されたカラムのみを操作でき、独自のカラムを追加できないようにするメカニズムが必要です。危険なカラム名を検出してブロックしようとする(ブラックリスト)こともできますが、このアプローチは信頼できません - 攻撃者は常に、予測していなかった危険なカラム名を記述する新しい方法を見つけることができます。 + +したがって、ロジックを逆にして、許可されたカラムの明示的なリスト(ホワイトリスト)を定義する方がはるかに安全です: + +```php +// ユーザーが編集できるカラム +$allowedColumns = ['name', 'email', 'active']; + +// 入力からすべての許可されていないカラムを削除します +$filteredData = array_intersect_key($userData, array_flip($allowedColumns)); + +// ✅ これで、次のようなクエリで安全に使用できます: +$database->query('INSERT INTO users', $filteredData); +$table->update($filteredData); +$table->where($filteredData); +``` + + +動的識別子 +===== + +テーブル名とカラム名を動的に指定するには、プレースホルダ `?name` を使用します。これにより、特定のデータベースの構文に従って識別子が正しくエスケープされます(たとえば、MySQLではバッククォートを使用): + +```php +// ✅ 信頼できる識別子の安全な使用 +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name', $column, $table); +// MySQLでの結果: SELECT `name` FROM `users` +``` + +重要:シンボル `?name` は、アプリケーションコードで定義された信頼できる値にのみ使用してください。ユーザーからの値には、再び[ホワイトリスト |#カラムのホワイトリスト]を使用してください。そうしないと、セキュリティリスクにさらされます: + +```php +// ❌ 危険 - ユーザーからの入力は絶対に使用しないでください +$database->query('SELECT ?name FROM users', $_GET['column']); +``` diff --git a/database/ja/sql-way.texy b/database/ja/sql-way.texy new file mode 100644 index 0000000000..7f2385f573 --- /dev/null +++ b/database/ja/sql-way.texy @@ -0,0 +1,513 @@ +SQLアクセス +******* + +.[perex] +Nette Databaseは2つの方法を提供します:SQLクエリを自分で記述する(SQLアクセス)、または自動的に生成させる([Explorer |explorer]を参照)。SQLアクセスはクエリを完全に制御でき、同時に安全な構築を保証します。 + +.[note] +データベース接続と設定の詳細については、[接続と設定 |guide#接続と設定]の章を参照してください。 + + +基本的なクエリ +======= + +データベースにクエリを実行するには、`query()`メソッドを使用します。これは、クエリの結果を表す[ResultSet |api:Nette\Database\ResultSet]オブジェクトを返します。失敗した場合、メソッドは[例外をスローします|exceptions]。 クエリの結果は`foreach`ループを使用して反復処理するか、[ヘルパー関数 |#データの取得]のいずれかを使用できます。 + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; +} +``` + +SQLクエリに値を安全に挿入するには、パラメータ化されたクエリを使用します。Nette Databaseはこれを最大限に簡単にします - SQLクエリの後にカンマと値を追加するだけです: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +複数のパラメータがある場合、2つの記述方法があります。SQLクエリにパラメータを「散りばめる」ことができます: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age); +``` + +または、まず完全なSQLクエリを記述し、次にすべてのパラメータを追加します: + +```php +$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age); +``` + + +SQLインジェクションからの保護 +================ + +パラメータ化されたクエリを使用することが重要なのはなぜでしょうか? なぜなら、SQLインジェクションと呼ばれる攻撃から保護してくれるからです。この攻撃では、攻撃者が独自のSQLコマンドを挿入し、それによってデータベース内のデータを取得または破損させる可能性があります。 + +.[warning] +**変数をSQLクエリに直接挿入しないでください!** SQLインジェクションから保護するために、常にパラメータ化されたクエリを使用してください。 + +```php +// ❌ 危険なコード - SQLインジェクションに対して脆弱 +$database->query("SELECT * FROM users WHERE name = '$name'"); + +// ✅ 安全なパラメータ化されたクエリ +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +[潜在的なセキュリティリスクについて理解してください |security]。 + + +クエリ技術 +===== + + +WHERE条件 +------- + +WHERE条件は連想配列として記述でき、キーはカラム名、値は比較データです。Nette Databaseは、値の型に基づいて最適なSQL演算子を自動的に選択します。 + +```php +$database->query('SELECT * FROM users WHERE', [ + 'name' => 'John', + 'active' => true, +]); +// WHERE `name` = 'John' AND `active` = 1 +``` + +キーで比較演算子を明示的に指定することもできます: + +```php +$database->query('SELECT * FROM users WHERE', [ + 'age >' => 25, // 演算子 > を使用 + 'name LIKE' => '%John%', // 演算子 LIKE を使用 + 'email NOT LIKE' => '%example.com%', // 演算子 NOT LIKE を使用 +]); +// WHERE `age` > 25 AND `name` LIKE '%John%' AND `email` NOT LIKE '%example.com%' +``` + +Netteは、`null`値や配列などの特殊なケースを自動的に処理します。 + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name' => 'Laptop', // 演算子 = を使用 + 'category_id' => [1, 2, 3], // IN を使用 + 'description' => null, // IS NULL を使用 +]); +// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL +``` + +否定条件には演算子 `NOT` を使用します: + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name NOT' => 'Laptop', // 演算子 <> を使用 + 'category_id NOT' => [1, 2, 3], // NOT IN を使用 + 'description NOT' => null, // IS NOT NULL を使用 + 'id' => [], // 省略されます +]); +// WHERE `name` <> 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL +``` + +条件を結合するには演算子 `AND` が使用されます。これは[プレースホルダ ?or |#SQL構築のヒント]を使用して変更できます。 + + +ORDER BYルール +----------- + +`ORDER BY`ソートは配列を使用して記述できます。キーにカラムを指定し、値は昇順でソートするかどうかを示すブール値になります: + +```php +$database->query('SELECT id FROM author ORDER BY', [ + 'id' => true, // 昇順 + 'name' => false, // 降順 +]); +// SELECT id FROM author ORDER BY `id`, `name` DESC +``` + + +データの挿入 (INSERT) +--------------- + +レコードを挿入するには、SQLコマンド `INSERT` を使用します。 + +```php +$values = [ + 'name' => 'John Doe', + 'email' => 'john@example.com', +]; +$database->query('INSERT INTO users ?', $values); +$userId = $database->getInsertId(); +``` + +`getInsertId()`メソッドは、最後に挿入された行のIDを返します。一部のデータベース(例:PostgreSQL)では、`$database->getInsertId($sequenceId)`を使用してIDを生成するシーケンス名をパラメータとして指定する必要があります。 + +パラメータとして、ファイル、DateTimeオブジェクト、または列挙型などの[#特別な値]を渡すこともできます。 + +複数のレコードを一度に挿入する: + +```php +$database->query('INSERT INTO users ?', [ + ['name' => 'User 1', 'email' => 'user1@mail.com'], + ['name' => 'User 2', 'email' => 'user2@mail.com'], +]); +``` + +複数INSERTは、多くの個別のクエリではなく単一のデータベースクエリが実行されるため、はるかに高速です。 + +**セキュリティ警告:** `$values`として検証されていないデータを使用しないでください。[潜在的なリスクについて理解してください |security#カラムの安全な操作]。 + + +データの更新 (UPDATE) +--------------- + +レコードを更新するには、SQLコマンド `UPDATE` を使用します。 + +```php +// 1つのレコードの更新 +$values = [ + 'name' => 'John Smith', +]; +$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1); +``` + +影響を受けた行数は `$result->getRowCount()` で返されます。 + +UPDATEには演算子 `+=` および `-=` を使用できます: + +```php +$database->query('UPDATE users SET ? WHERE id = ?', [ + 'login_count+=' => 1, // login_count をインクリメント +], 1); +``` + +レコードが存在する場合は挿入、存在しない場合は更新する例。`ON DUPLICATE KEY UPDATE`テクニックを使用します: + +```php +$values = [ + 'name' => $name, + 'year' => $year, +]; +$database->query('INSERT INTO users ? ON DUPLICATE KEY UPDATE ?', + $values + ['id' => $id], + $values, +); +// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) +// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 +``` + +Nette Databaseが、SQLコマンドのどのコンテキストに配列パラメータを挿入するかを認識し、それに応じてSQLコードを構築することに注意してください。したがって、最初の配列から`(id, name, year) VALUES (123, 'Jim', 1978)`を構築し、2番目の配列を`name = 'Jim', year = 1978`の形式に変換しました。これについては、[#SQL構築のヒント]セクションで詳しく説明します。 + + +データの削除 (DELETE) +--------------- + +レコードを削除するには、SQLコマンド `DELETE` を使用します。削除された行数を取得する例: + +```php +$count = $database->query('DELETE FROM users WHERE id = ?', 1) + ->getRowCount(); +``` + + +SQL構築のヒント +--------- + +ヒントは、SQLクエリ内の特別なプレースホルダーであり、パラメータ値をSQL式にどのように書き換えるかを示します: + +| ヒント | 説明 | 自動的に使用される +|-----------|-------------------------------------------------|----------------------------- +| `?name` | テーブル名またはカラム名の挿入に使用します | - +| `?values` | `(key, ...) VALUES (value, ...)` を生成します | `INSERT ... ?`, `REPLACE ... ?` +| `?set` | 割り当て `key = value, ...` を生成します | `SET ?`, `KEY UPDATE ?` +| `?and` | 配列内の条件を `AND` 演算子で結合します | `WHERE ?`, `HAVING ?` +| `?or` | 配列内の条件を `OR` 演算子で結合します | - +| `?order` | `ORDER BY` 句を生成します | `ORDER BY ?`, `GROUP BY ?` + +テーブル名とカラム名をクエリに動的に挿入するには、プレースホルダ `?name` を使用します。Nette Databaseは、特定のデータベースの規則に従って識別子を正しく処理します(たとえば、MySQLではバッククォートで囲む)。 + +```php +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table); +// SELECT `name` FROM `users` WHERE id = 1 (MySQLの場合) +``` + +**警告:** シンボル `?name` は、検証された入力からのテーブル名とカラム名にのみ使用してください。そうしないと、[セキュリティリスクにさらされます |security#動的識別子]。 + +他のヒントは通常、NetteがSQLクエリを構築する際に賢い自動検出を使用するため(表の3番目の列を参照)、指定する必要はありません。ただし、たとえば `AND` の代わりに `OR` を使用して条件を結合したい場合などに使用できます: + +```php +$database->query('SELECT * FROM users WHERE ?or', [ + 'name' => 'John', + 'email' => 'john@example.com', +]); +// SELECT * FROM users WHERE `name` = 'John' OR `email` = 'john@example.com' +``` + + +特別な値 +---- + +通常のスカラ型(string、int、bool)に加えて、パラメータとして特別な値を渡すこともできます: + +- ファイル:`fopen('image.gif', 'r')` はファイルのバイナリコンテンツを挿入します +- 日付と時刻:`DateTime`オブジェクトはデータベース形式に変換されます +- 列挙型:`enum`インスタンスはその値に変換されます +- SQLリテラル:`Connection::literal('NOW()')`を使用して作成されたものは、クエリに直接挿入されます + +```php +$database->query('INSERT INTO articles ?', [ + 'title' => 'My Article', + 'published_at' => new DateTime, + 'content' => fopen('image.png', 'r'), + 'state' => Status::Draft, +]); +``` + +`datetime`データ型をネイティブにサポートしていないデータベース(SQLiteやOracleなど)の場合、`DateTime`は[データベース設定|configuration]の`formatDateTime`項目で指定された値(デフォルト値は`U` - Unixタイムスタンプ)に変換されます。 + + +SQLリテラル +------- + +場合によっては、値として直接SQLコードを指定する必要がありますが、これは文字列として解釈されず、エスケープされるべきではありません。この目的のために、`Nette\Database\SqlLiteral`クラスのオブジェクトが使用されます。これらは`Connection::literal()`メソッドによって作成されます。 + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + 'year >' => $database::literal('YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) +``` + +または代替案: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) +``` + +SQLリテラルにはパラメータを含めることができます: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > ? AND year < ?', $min, $max), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) +``` + +これにより、興味深い組み合わせを作成できます: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('?or', [ + 'active' => true, + 'role' => $role, + ]), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') +``` + + +データの取得 +====== + + +SELECTクエリのショートカット +----------------- + +データ取得を簡略化するために、`Connection`は`query()`の呼び出しとそれに続く`fetch*()`を組み合わせたいくつかのショートカットを提供します。これらのメソッドは`query()`と同じパラメータ、つまりSQLクエリとオプションのパラメータを受け入れます。`fetch*()`メソッドの完全な説明は[以下 |#fetch]にあります。 + +| `fetch($sql, ...$params): ?Row` | クエリを実行し、最初の行を`Row`オブジェクトとして返します +| `fetchAll($sql, ...$params): array` | クエリを実行し、すべての行を`Row`オブジェクトの配列として返します +| `fetchPairs($sql, ...$params): array` | クエリを実行し、最初のカラムがキー、2番目のカラムが値である連想配列を返します +| `fetchField($sql, ...$params): mixed` | クエリを実行し、最初の行の最初のフィールドの値を返します +| `fetchList($sql, ...$params): ?array` | クエリを実行し、最初の行をインデックス付き配列として返します + +例: + +```php +// fetchField() - 最初のセルの値を返します +$count = $database->query('SELECT COUNT(*) FROM articles') + ->fetchField(); +``` + + +`foreach` - 行の反復処理 +------------------ + +クエリを実行した後、[ResultSet|api:Nette\Database\ResultSet]オブジェクトが返され、これにより結果をいくつかの方法で反復処理できます。クエリを実行して行を取得する最も簡単な方法は、`foreach`ループで反復処理することです。この方法は、データを段階的に返し、一度にメモリに保存しないため、メモリ効率が最も高くなります。 + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; + // ... +} +``` + +.[note] +`ResultSet`は一度しか反復処理できません。繰り返し反復処理する必要がある場合は、まず`fetchAll()`メソッドなどを使用してデータを配列に読み込む必要があります。 + + +fetch(): ?Row .[method] +----------------------- + +行を`Row`オブジェクトとして返します。これ以上行がない場合は`null`を返します。内部ポインタを次の行に進めます。 + +```php +$result = $database->query('SELECT * FROM users'); +$row = $result->fetch(); // 最初の行を読み込みます +if ($row) { + echo $row->name; +} +``` + + +fetchAll(): array .[method] +--------------------------- + +`ResultSet`から残りのすべての行を`Row`オブジェクトの配列として返します。 + +```php +$result = $database->query('SELECT * FROM users'); +$rows = $result->fetchAll(); // すべての行を読み込みます +foreach ($rows as $row) { + echo $row->name; +} +``` + + +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +結果を連想配列として返します。最初の引数は配列のキーとして使用されるカラム名を指定し、2番目の引数は値として使用されるカラム名を指定します: + +```php +$result = $database->query('SELECT id, name FROM users'); +$names = $result->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] +``` + +最初のパラメータのみを指定した場合、値は行全体、つまり`Row`オブジェクトになります: + +```php +$rows = $result->fetchPairs('id'); +// [1 => Row(id: 1, name: 'John'), 2 => Row(id: 2, name: 'Jane'), ...] +``` + +キーが重複する場合、最後の行の値が使用されます。キーとして`null`を使用すると、配列はゼロから数値でインデックス付けされます(衝突は発生しません): + +```php +$names = $result->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] +``` + + +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +あるいは、パラメータとしてコールバックを指定できます。これは、各行に対して値自体、またはキーと値のペアのいずれかを返します。 + +```php +$result = $database->query('SELECT * FROM users'); +$items = $result->fetchPairs(fn($row) => "$row->id - $row->name"); +// ['1 - John', '2 - Jane', ...] + +// コールバックはキーと値のペアを持つ配列を返すこともできます: +$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]); +// ['John' => 46, 'Jane' => 21, ...] +``` + + +fetchField(): mixed .[method] +----------------------------- + +現在の行の最初のフィールドの値を返します。これ以上行がない場合は`null`を返します。内部ポインタを次の行に進めます。 + +```php +$result = $database->query('SELECT name FROM users'); +$name = $result->fetchField(); // 最初の行から名前を読み込みます +``` + + +fetchList(): ?array .[method] +----------------------------- + +行をインデックス付き配列として返します。これ以上行がない場合は`null`を返します。内部ポインタを次の行に進めます。 + +```php +$result = $database->query('SELECT name, email FROM users'); +$row = $result->fetchList(); // ['John', 'john@example.com'] +``` + + +getRowCount(): ?int .[method] +----------------------------- + +最後の`UPDATE`または`DELETE`クエリによって影響を受けた行数を返します。`SELECT`の場合、これは返された行数ですが、これは不明な場合があり、その場合メソッドは`null`を返します。 + + +getColumnCount(): ?int .[method] +-------------------------------- + +`ResultSet`内のカラム数を返します。 + + +クエリ情報 +===== + +デバッグ目的で、最後に実行されたクエリに関する情報を取得できます: + +```php +echo $database->getLastQueryString(); // SQLクエリを出力します + +$result = $database->query('SELECT * FROM articles'); +echo $result->getQueryString(); // SQLクエリを出力します +echo $result->getTime(); // 実行時間を秒単位で出力します +``` + +結果をHTMLテーブルとして表示するには、次を使用できます: + +```php +$result = $database->query('SELECT * FROM articles'); +$result->dump(); +``` + +ResultSetはカラムの型に関する情報を提供します: + +```php +$result = $database->query('SELECT * FROM articles'); +$types = $result->getColumnTypes(); + +foreach ($types as $column => $type) { + echo "$column は型 $type->type です"; // 例:'id は型 int です' +} +``` + + +クエリのロギング +-------- + +独自のクエリロギングを実装できます。イベント`onQuery`は、実行された各クエリの後に呼び出されるコールバックの配列です: + +```php +$database->onQuery[] = function ($database, $result) use ($logger) { + $logger->info('Query: ' . $result->getQueryString()); + $logger->info('Time: ' . $result->getTime()); + + if ($result->getRowCount() > 1000) { + $logger->warning('Large result set: ' . $result->getRowCount() . ' rows'); + } +}; +``` diff --git a/database/ja/transactions.texy b/database/ja/transactions.texy new file mode 100644 index 0000000000..b30405b524 --- /dev/null +++ b/database/ja/transactions.texy @@ -0,0 +1,43 @@ +トランザクション +******** + +.[perex] +トランザクションは、トランザクション内のすべての操作が実行されるか、または何も実行されないかのいずれかを保証します。これらは、より複雑な操作でデータの整合性を確保するのに役立ちます。 + +トランザクションを使用する最も簡単な方法は次のようになります: + +```php +$database->beginTransaction(); +try { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); + $database->commit(); +} catch (\Exception $e) { + $database->rollBack(); + throw $e; +} +``` + +`transaction()` メソッドを使用すると、同じことをはるかにエレガントに記述できます。パラメータとしてコールバックを受け取り、それをトランザクション内で実行します。コールバックが例外なく実行されると、トランザクションは自動的にコミットされます。例外が発生した場合、トランザクションはキャンセル(ロールバック)され、例外はさらに伝播されます。 + +```php +$database->transaction(function ($database) use ($id) { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); +}); +``` + +`transaction()` メソッドは値を返すこともできます: + +```php +$count = $database->transaction(function ($database) { + $result = $database->query('UPDATE users SET active = ?', true); + return $result->getRowCount(); // 更新された行数を返します +}); +``` diff --git a/database/pl/@home.texy b/database/pl/@home.texy index 1a90574b78..4093fa6893 100644 --- a/database/pl/@home.texy +++ b/database/pl/@home.texy @@ -5,17 +5,17 @@ Obsługiwane bazy danych Nette obsługuje następujące bazy danych: -|* Serwer bazy danych |* Nazwa DSN |* Obsługa rdzenia |* Obsługa Eksploratora -| MySQL (>= 5.1) | mysql | TAK | TAK -| PostgreSQL (>= 9.0) | pgsql | TAK | TAK -| Sqlite 3 (>= 3.8) | sqlite | TAK | TAK -| Oracle | oczy | TAK | - -| MS SQL (PDO_SQLSRV) | sqlsrv | TAK | TAK -| MS SQL (PDO_DBLIB) | mssql | TAK | - -| ODBC | odbc | TAK | - +|* Serwer bazy danych |* Nazwa DSN |* Obsługa w Core |* Obsługa w Explorer +| MySQL (>= 5.1) | mysql | TAK | TAK +| PostgreSQL (>= 9.0) | pgsql | TAK | TAK +| Sqlite 3 (>= 3.8) | sqlite | TAK | TAK +| Oracle | oci | TAK | - +| MS SQL (PDO_SQLSRV) | sqlsrv | TAK | TAK +| MS SQL (PDO_DBLIB) | mssql | TAK | - +| ODBC | odbc | TAK | - -{{title: Nette Database}} -{{description: Nette Database upraszcza pobieranie danych z bazy bez konieczności pisania zapytań SQL. Zadaje wydajne zapytania i nie przekazuje zbędnych danych.}} +{{maintitle: Nette Database - awesome database layer for PHP}} +{{description: Nette Database w znaczący sposób upraszcza pobieranie danych z bazy danych bez konieczności pisania zapytań SQL. Składa efektywne zapytania i nie przesyła zbędnych danych.}} diff --git a/database/pl/@left-menu.texy b/database/pl/@left-menu.texy index fae0f77bbd..635d04210c 100644 --- a/database/pl/@left-menu.texy +++ b/database/pl/@left-menu.texy @@ -1,5 +1,12 @@ -Databáze -******** -- [Rdzeń |Core] -- [Odkrywca |Explorer] +Nette Database +************** +- [Wprowadzenie |guide] +- [Podejście SQL |sql way] +- [Explorer |Explorer] +- [Transakcje |transactions] +- [Wyjątki |exceptions] +- [Refleksja |reflection] +- [Mapowanie |mapping] - [Konfiguracja |configuration] +- [Zagrożenia bezpieczeństwa |security] +- [Aktualizacja |en:upgrading] diff --git a/database/pl/@meta.texy b/database/pl/@meta.texy new file mode 100644 index 0000000000..61ac92d1af --- /dev/null +++ b/database/pl/@meta.texy @@ -0,0 +1 @@ +{{sitename: Dokumentacja Nette}} diff --git a/database/pl/configuration.texy b/database/pl/configuration.texy index 7a5504c753..3ab8140579 100644 --- a/database/pl/configuration.texy +++ b/database/pl/configuration.texy @@ -4,58 +4,64 @@ Konfiguracja bazy danych .[perex] Przegląd opcji konfiguracyjnych dla Nette Database. -Jeśli nie używasz całego frameworka, a jedynie tej biblioteki, przeczytaj [jak załadować konfigurację |bootstrap:]. +Jeśli nie używasz całego frameworka, ale tylko tej biblioteki, przeczytaj, [jak wczytać konfigurację|bootstrap:]. -Jedno połączenie .[#toc-single-connection] ------------------------------------------- +Jedno połączenie +---------------- -Skonfiguruj pojedyncze połączenie z bazą danych: +Konfiguracja jednego połączenia z bazą danych: ```neon database: - # DSN, pojedynczy klucz obowiązkowy + # DSN, jedyny wymagany klucz dsn: "sqlite:%appDir%/Model/demo.db" user: ... password: ... ``` -Tworzy serwis `Nette\Database\Connection` oraz `Nette\Database\Explorer` dla warstwy [Database Explorer |explorer]. Baza danych jest zwykle przekazywana [przez autowiring |dependency-injection:autowiring], jeśli nie jest to możliwe, należy użyć odpowiednio nazw serwisów `@database.default.connection` i `@database.default.explorer`,. +Tworzy usługi `Nette\Database\Connection` i `Nette\Database\Explorer`, które zazwyczaj przekazujemy przez [autowiring |dependency-injection:autowiring], ewentualnie przez odwołanie do [ich nazwy |#Usługi DI]. -Inne ustawienia: +Dalsze ustawienia: ```neon database: - # pokazać panel bazy danych w Tracy Bar? - debugger: ... # (bool) domyślnie jest true + # wyświetlić panel bazy danych w Tracy Bar? + debugger: ... # (bool) domyślnie true - # pokazać zapytania EXPLAIN w Tracy Bar? - explain: ... # (bool) domyślnie jest true + # wyświetlić EXPLAIN zapytań w Tracy Bar? + explain: ... # (bool) domyślnie true - # enable autowiring for this connection? - autowired: ... # (bool) domyślnie przyjmuje wartość true dla pierwszego połączenia + # włączyć autowiring dla tego połączenia? + autowired: ... # (bool) domyślnie true dla pierwszego połączenia - # konwencja tablicy: odkryta, statyczna lub nazwa klasy - conventions: discovered # (string) default is 'discovered' + # konwencje tabel: discovered, static lub nazwa klasy + conventions: discovered # (string) domyślnie 'discovered' options: - # łączyć się z bazą danych tylko w razie potrzeby? - lazy: ... # (bool) domyślnie jest false + # łączyć się z bazą danych dopiero w razie potrzeby? + lazy: ... # (bool) domyślnie false - # Klasa sterownika bazy danych PHP + # klasa PHP sterownika bazy danych driverClass: # (string) - # Tylko dla MySQL: ustawia sql_mode + # tylko MySQL: ustawia sql_mode sqlmode: # (string) - # Tylko MySQL: ustawia SET NAMES - charset: # (string) default is 'utf8mb4' ('utf8' before 5.5.3) + # tylko MySQL: ustawia SET NAMES + charset: # (string) domyślnie 'utf8mb4' - # Tylko Oracle i SQLite: format przechowywania danych + # tylko MySQL: konwertuje TINYINT(1) na bool + convertBoolean: # (bool) domyślnie false + + # zwraca kolumny z datą jako obiekty immutable (od wersji 3.2.1) + newDateTime: # (bool) domyślnie false + + # tylko Oracle i SQLite: format przechowywania daty formatDateTime: # (string) domyślnie 'U' ``` -W kluczu `options` można określić dodatkowe opcje, które można znaleźć w [dokumentacji sterownika PDO |https://www.php.net/manual/en/pdo.drivers.php], takie jak: +W kluczu `options` można podawać inne opcje, które znajdziesz w [dokumentacji sterowników PDO |https://www.php.net/manual/en/pdo.drivers.php], takie jak: ```neon database: @@ -64,10 +70,10 @@ database: ``` -Więcej połączeń .[#toc-vice-spojeni] ------------------------------------- +Wiele połączeń +-------------- -W konfiguracji możemy zdefiniować więcej połączeń z bazą danych, dzieląc je na nazwane sekcje: +W konfiguracji możemy zdefiniować również wiele połączeń z bazą danych, dzieląc je na nazwane sekcje: ```neon database: @@ -80,9 +86,23 @@ database: dsn: 'sqlite::memory:' ``` -Każde tak zdefiniowane połączenie tworzy serwisy, które w nazwie zawierają nazwę sekcji, czyli `@database.main.connection` & `@database.main.explorer` a następnie `@database.another.connection` & `@database.another.explorer`. +Autowiring jest włączony tylko dla usług z pierwszej sekcji. Można to zmienić za pomocą `autowired: false` lub `autowired: true`. + + +Usługi DI +--------- + +Te usługi są dodawane do kontenera DI, gdzie `###` reprezentuje nazwę połączenia: + +| Nazwa | Typ | Opis +|---------------------------------------------------------- +| `database.###.connection` | [api:Nette\Database\Connection] | połączenie z bazą danych +| `database.###.explorer` | [api:Nette\Database\Explorer] | [Database Explorer |explorer] + + +Jeśli definiujemy tylko jedno połączenie, nazwy usług będą `database.default.connection` i `database.default.explorer`. Jeśli definiujemy więcej połączeń jak w przykładzie powyżej, nazwy będą odpowiadać sekcjom, tj. `database.main.connection`, `database.main.explorer` oraz `database.another.connection` i `database.another.explorer`. -Autowiring jest włączony tylko dla usług z pierwszej sekcji. Można to zmienić za pomocą `autowired: false` lub `autowired: true`. Usługi nieautoryzowane przekazujemy jawnie: +Usługi bez autowiringu przekazujemy jawnie przez odwołanie do ich nazwy: ```neon services: diff --git a/database/pl/core.texy b/database/pl/core.texy deleted file mode 100644 index 706713ee75..0000000000 --- a/database/pl/core.texy +++ /dev/null @@ -1,350 +0,0 @@ -Baza danych Rdzeń -***************** - -.[perex] -Nette Database Core to warstwa bazowa dostępu do bazy danych, tzw. warstwa abstrakcji bazy danych. - - -Instalacja .[#toc-installation] -=============================== - -Pobierz i zainstaluj bibliotekę za pomocą [Composera |best-practices:composer]: - -```shell -composer require nette/database -``` - - -Podłączenie i konfiguracja .[#toc-connection-and-configuration] -=============================================================== - -Aby połączyć się z bazą danych, wystarczy stworzyć instancję klasy [api:Nette\Database\Connection]: - -```php -$database = new Nette\Database\Connection($dsn, $user, $password); -``` - -Parametr `$dsn` (nazwa źródła danych) jest taki sam jak ten [używany przez PDO |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], np. `host=127.0.0.1;dbname=test`. W przypadku niepowodzenia rzuca wyjątek `Nette\Database\ConnectionException`. - -Bardziej poręczny sposób oferuje jednak [konfiguracja aplikacji |configuration], gdzie wystarczy dodać sekcję `database`, a ona sama utworzy potrzebne obiekty, a także pasek bazy danych w pasku [Tracy |tracy:]. - -```neon -database: - dsn: 'mysql:host=127.0.0.1;dbname=test' - user: root - password: password -``` - -Następnie [otrzymujemy |dependency-injection:passing-dependencies] obiekt połączenia [jako usługę z kontenera DI |dependency-injection:passing-dependencies], na przykład: - -```php -class Model -{ - // pro práci s vrstvou Database Explorer si předáme Nette\Database\Explorer - public function __construct( - private Nette\Database\Connection $database, - ) { - } -} -``` - -Więcej informacji na temat [konfiguracji bazy danych |configuration]. - - -Pytania .[#toc-queries] -======================= - -Zapytania do bazy danych są zadawane za pomocą metody `query()`, która zwraca [ResultSet |api:Nette\Database\ResultSet]. - -```php -$result = $database->query('SELECT * FROM users'); - -foreach ($result as $row) { - echo $row->id; - echo $row->name; -} - -echo $result->getRowCount(); // zwraca liczbę wierszy wyniku, jeśli jest znana -``` - -.[note] -Możesz iterować nad `ResultSet` tylko raz, jeśli potrzebujesz iterować więcej niż raz, musisz przekonwertować wynik na tablicę za pomocą metody `fetchAll()`. - -Bardzo łatwo jest również dodać parametry do zapytania, zauważ znak zapytania: - -```php -$database->query('SELECT * FROM users WHERE name = ?', $name); - -$database->query('SELECT * FROM users WHERE name = ? AND active = ?', $name, $active); - -$database->query('SELECT * FROM users WHERE id IN (?)', $ids); // $ids je pole -``` -<div class=warning> - -OSTRZEŻENIE, nigdy nie komponuj zapytań jako ciągów znaków, to stworzyłoby lukę w [SQL injection |https://cs.wikipedia.org/wiki/SQL_injection] -/-- -$db->query('SELECT * FROM users WHERE name = ' . $name); // ŠPATNĚ!!! -\-- -</div> - -Jeśli `query()` nie powiedzie się, rzuci albo `Nette\Database\DriverException` albo jeden z jego potomków: - -- [ConstraintViolationException |api:Nette\Database\ConstraintViolationException] - naruszenie jakiegoś ograniczenia dla tabeli -- [ForeignKeyConstraintViolationException |api:Nette\Database\ForeignKeyConstraintViolationException] - nieprawidłowy klucz obcy -- [NotNullConstraintViolationException |api:Nette\Database\NotNullConstraintViolationException] - naruszenie ograniczenia NOT NULL -- [UniqueConstraintViolationException |api:Nette\Database\UniqueConstraintViolationException] - sprzeczny unikalny indeks - -Oprócz strony `query()` znajdują się na niej inne przydatne funkcje: - -```php -// zwróć tablicę asocjacyjną id => nazwa -$pairs = $database->fetchPairs('SELECT id, name FROM users'); - -// zwraca wszystkie rekordy jako tablicę -$rows = $database->fetchAll('SELECT * FROM users'); - -// zwraca pojedynczy rekord -$row = $database->fetch('SELECT * FROM users WHERE id = ?', $id); - -// zwraca bezpośrednio wartość komórki -$name = $database->fetchField('SELECT name FROM users WHERE id = ?', $id); -``` - -W przypadku niepowodzenia, wszystkie te metody rzucą `Nette\Database\DriverException`. - - -Wstawianie, aktualizacja i usuwanie .[#toc-insert-update-delete] -================================================================ - -Parametr, który wstawiamy do zapytania SQL może być również tablicą (w tym przypadku możliwe jest również użycie placeholdera `?` vynechat), což se hodí třeba pro sestavení příkazu `INSERT`: - -```php -$database->query('INSERT INTO users ?', [ // możemy tutaj pominąć znak zapytania - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) - -$id = $database->getInsertId(); // zwróć auto-increment wstawionego rekordu - -$id = $database->getInsertId($sequence); // lub wartość sekwencji -``` - -Multiple INSERT: - -```php -$database->query('INSERT INTO users', [ - [ - 'name' => 'Jim', - 'year' => 1978, - ], [ - 'name' => 'Jack', - 'year' => 1987, - ], -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) -``` - -Jako parametry mogą być przekazywane pliki, obiekty DateTime lub [typy wyliczeniowe |https://www.php.net/enumerations]: - -```php -$database->query('INSERT INTO users', [ - 'name' => $name, - 'created' => new DateTime, // nebo $database::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // vloží soubor - 'status' => State::New, // enum State -]); -``` - -Edycja zapisów: - -```php -$result = $database->query('UPDATE users SET', [ - 'name' => $name, - 'year' => $year, -], 'WHERE id = ?', $id); -// UPDATE users SET `name` = 'Jim', `year` = 1978 WHERE id = 123 - -echo $result->getRowCount(); // zwraca liczbę dotkniętych wierszy -``` - -Dla UPDATE możemy użyć operatorów `+=` i `-=`: - -```php -$database->query('UPDATE users SET', [ - 'age+=' => 1, // uwaga += -], 'WHERE id = ?', $id); -// UPDATE users SET `age` = `age` + 1 -``` - -Usuwanie: - -```php -$result = $database->query('DELETE FROM users WHERE id = ?', $id); -echo $result->getRowCount(); // zwraca liczbę dotkniętych wierszy -``` - - -Pytania zaawansowane .[#toc-advanced-queries] -============================================= - -Wstawianie lub edycja rekordu, jeśli już istnieje: - -```php -$database->query('INSERT INTO users', [ - 'id' => $id, - 'name' => $name, - 'year' => $year, -], 'ON DUPLICATE KEY UPDATE', [ - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) -// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 -``` - -Zauważ, że Nette Database rozpoznaje kontekst instrukcji SQL, w której wstawiony jest parametr z polem i odpowiednio buduje kod SQL. Zbudował więc `(id, name, year) VALUES (123, 'Jim', 1978)` z pierwszego pola, jednocześnie przekształcając drugie pole do postaci `name = 'Jim', year = 1978`. - -Możemy również wpłynąć na sortowanie za pomocą tablicy, w kluczach podamy kolumny, a wartością będzie boolean określający czy sortować w porządku rosnącym: - -```php -$database->query('SELECT id FROM author ORDER BY', [ - 'id' => true, // vzestupně - 'name' => false, // sestupně -]); -// SELECT id FROM author ORDER BY `id`, `name` DESC -``` - -Jeśli wykrywanie nie działa dla nietypowej konstrukcji, możesz określić formę budowy przez symbol wieloznaczny `?`, po którym następuje podpowiedź. Obsługiwane są następujące wskazówki: - -| (klucz1, klucz2, ...) VALUES (wartość1, wartość2, ...) -| ?set | key1 = value1, key2 = value2, ... -| ?and | key1 = value1 AND key2 = value2 ... -| lub | key1 = value1 OR key2 = value2 ... -|?order | key1 ASC, key2 DESC - -Klauzula WHERE używa operatora `?and`, więc warunki są połączone operatorem `AND`: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND `year` = 1978 -``` - -Które możemy łatwo zmienić na `OR` podając placeholder `?or`: - -```php -$result = $database->query('SELECT * FROM users WHERE ?or', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' OR `year` = 1978 -``` - -W warunkach możemy używać operatorów: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name <>' => $name, - 'year >' => $year, -]); -// SELECT * FROM users WHERE `name` <> 'Jim' AND `year` > 1978 -``` - -A także wyliczenia: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => ['Jim', 'Jack'], - 'role NOT IN' => ['admin', 'owner'], // výčet + operátor NOT IN -]); -// SELECT * FROM users WHERE -// `name` IN ('Jim', 'Jack') AND `role` NOT IN ('admin', 'owner') -``` - -Możemy również wstawić do warunku fragment własnego kodu SQL, używając tzw. literału SQL: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year >' => $database::literal('YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) -``` - -Lub alternatywnie: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) -``` - -Literał SQL może mieć również parametry: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > ? AND year < ?', $min, $max), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) -``` - -Co pozwala nam na tworzenie ciekawych kombinacji: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('?or', [ - 'active' => true, - 'role' => $role, - ]), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') -``` - - -Nazwa zmiennej .[#toc-variable-name] -==================================== - -Istnieje również placeholder `?name`, którego używasz, jeśli nazwa tabeli lub kolumny jest zmienną. (Uważaj, aby nie pozwolić użytkownikowi manipulować zawartością takiej zmiennej): - -```php -$table = 'blog.users'; -$column = 'name'; -$database->query('SELECT * FROM ?name WHERE ?name = ?', $table, $column, $name); -// SELECT * FROM `blog`.`users` WHERE `name` = 'Jim' -``` - - -Transakcja .[#toc-transactions] -=============================== - -Istnieją trzy metody pracy z transakcjami: - -```php -$database->beginTransaction(); // rozpoczęcie transakcji - -$database->commit(); // commit - -$database->rollback(); // rollback -``` - -Metoda `transaction()` zapewnia elegancki sposób przekazywania wywołania zwrotnego, które jest wykonywane w transakcji. Jeśli podczas wykonywania zostanie rzucony wyjątek, transakcja jest odrzucana; jeśli wszystko pójdzie dobrze, transakcja jest popełniana. - -```php -$id = $database->transaction(function ($database) { - $database->query('DELETE FROM ...'); - $database->query('INSERT INTO ...'); - // ... - return $database->getInsertId(); -}); -``` - -Jak widać, metoda `transaction()` zwraca wartość zwrotną wywołania zwrotnego. - -Callback `transaction()` może być również zagnieżdżony, co upraszcza implementację niezależnych repozytoriów. diff --git a/database/pl/exceptions.texy b/database/pl/exceptions.texy new file mode 100644 index 0000000000..0883717760 --- /dev/null +++ b/database/pl/exceptions.texy @@ -0,0 +1,34 @@ +Wyjątki +******* + +Nette Database używa hierarchii wyjątków. Podstawową klasą jest `Nette\Database\DriverException`, która dziedziczy z `PDOException` i zapewnia rozszerzone możliwości pracy z błędami bazy danych: + +- Metoda `getDriverCode()` zwraca kod błędu od sterownika bazy danych +- Metoda `getSqlState()` zwraca kod SQLSTATE +- Metody `getQueryString()` i `getParameters()` umożliwiają uzyskanie pierwotnego zapytania i jego parametrów + +Z `DriverException` dziedziczą następujące wyspecjalizowane wyjątki: + +- `ConnectionException` - sygnalizuje niepowodzenie połączenia z serwerem bazy danych +- `ConstraintViolationException` - podstawowa klasa dla naruszenia ograniczeń bazy danych, z której dziedziczą: + - `ForeignKeyConstraintViolationException` - naruszenie klucza obcego + - `NotNullConstraintViolationException` - naruszenie ograniczenia NOT NULL + - `UniqueConstraintViolationException` - naruszenie unikalności wartości + + +Przykład przechwytywania wyjątku `UniqueConstraintViolationException`, który występuje, gdy próbujemy wstawić użytkownika z adresem e-mail, który już istnieje w bazie danych (zakładając, że kolumna email ma unikalny indeks). + +```php +try { + $database->query('INSERT INTO users', [ + 'email' => 'john@example.com', + 'name' => 'John Doe', + 'password' => $hashedPassword, + ]); +} catch (Nette\Database\UniqueConstraintViolationException $e) { + echo 'Użytkownik o tym adresie e-mail już istnieje.'; + +} catch (Nette\Database\DriverException $e) { + echo 'Wystąpił błąd podczas rejestracji: ' . $e->getMessage(); +} +``` diff --git a/database/pl/explorer.texy b/database/pl/explorer.texy index e479bb49aa..5541a0e211 100644 --- a/database/pl/explorer.texy +++ b/database/pl/explorer.texy @@ -1,550 +1,912 @@ -Eksplorator baz danych -********************** +Database Explorer +***************** <div class=perex> -Nette Database Explorer (dawniej Nette Database Table, NDBT) znacznie ułatwia pobieranie danych z bazy danych bez konieczności pisania zapytań SQL. +Explorer oferuje intuicyjny i efektywny sposób pracy z bazą danych. Dba automatycznie o relacje między tabelami i optymalizację zapytań, dzięki czemu możesz skupić się na swojej aplikacji. Działa od razu bez konieczności ustawiania. Jeśli potrzebujesz pełnej kontroli nad zapytaniami SQL, możesz wykorzystać [dostęp SQL |SQL way]. -- Zadaje skuteczne pytania -- nie przekazuje zbędnych danych -- ma elegancką składnię +- Praca z danymi jest naturalna i łatwa do zrozumienia +- Generuje zoptymalizowane zapytania SQL, które pobierają tylko potrzebne dane +- Umożliwia łatwy dostęp do powiązanych danych bez konieczności pisania zapytań JOIN +- Działa natychmiast bez jakiejkolwiek konfiguracji czy generowania encji </div> -Korzystanie z Database Explorera rozpoczyna się od tabeli poprzez wywołanie metody `table()` nad obiektem [api:Nette\Database\Explorer]. Najłatwiejszy sposób jej uzyskania [opisany |core#Connection-and-Configuration] jest [tutaj |core#Connection-and-Configuration], ale jeśli korzystasz z Nette Database Explorera samodzielnie, możesz również [utworzyć ją ręcznie |#Ruční vytvoření Explorer]. + +Z Explorerem zaczniesz od wywołania metody `table()` obiektu [api:Nette\Database\Explorer] (szczegóły dotyczące połączenia znajdziesz w rozdziale [Połączenie i konfiguracja |guide#Połączenie i konfiguracja]): ```php -$books = $explorer->table('book'); // nazwa tabeli to 'book' +$books = $explorer->table('book'); // 'book' to nazwa tabeli ``` -Zwraca obiekt [Selection |api:Nette\Database\Table\Selection], nad którym możemy iterować, aby przejść przez wszystkie książki. Wiersze są instancjami [ActiveRow |api:Nette\Database\Table\ActiveRow] i możemy z nich bezpośrednio odczytywać dane. +Metoda zwraca obiekt [Selection |api:Nette\Database\Table\Selection], który reprezentuje zapytanie SQL. Do tego obiektu możemy dołączać kolejne metody do filtrowania i sortowania wyników. Zapytanie jest budowane i uruchamiane dopiero w momencie, gdy zaczynamy żądać danych. Na przykład przez przechodzenie pętlą `foreach`. Każdy wiersz jest reprezentowany przez obiekt [ActiveRow |api:Nette\Database\Table\ActiveRow]: ```php foreach ($books as $book) { - echo $book->title; - echo $book->author_id; + echo $book->title; // wypisanie kolumny 'title' + echo $book->author_id; // wypisanie kolumny 'author_id' } ``` -Wybór jednego konkretnego wiersza odbywa się za pomocą metody `get()`, która zwraca bezpośrednio instancję ActiveRow. +Explorer zasadniczo ułatwia pracę z [relacjami między tabelami |#Relacje między tabelami]. Poniższy przykład pokazuje, jak łatwo możemy wypisać dane z powiązanych tabel (książki i ich autorzy). Zauważ, że nie musimy pisać żadnych zapytań JOIN, Nette stworzy je za nas: ```php -$book = $explorer->table('book')->get(2); // zwróć książkę o id 2 -echo $book->title; -echo $book->author_id; +$books = $explorer->table('book'); + +foreach ($books as $book) { + echo 'Książka: ' . $book->title; + echo 'Autor: ' . $book->author->name; // tworzy JOIN do tabeli 'author' +} ``` -Spróbujmy na prostym przykładzie. Musimy wybrać z bazy danych książki i ich autorów. Jest to prosty przykład wiązania 1:N. Często spotykanym rozwiązaniem jest wybieranie danych jednym zapytaniem SQL, łącząc tabele za pomocą JOIN. Inną opcją jest wybranie danych osobno, za pomocą jednego zapytania o książkę, a następnie dla każdej książki wybranie jej autora (np. za pomocą pętli foreach). Można to zoptymalizować do dwóch zapytań do bazy, jednego dla książek i jednego dla autorów - i tak właśnie robi to Nette Database Explorer. +Nette Database Explorer optymalizuje zapytania, aby były jak najbardziej efektywne. Powyższy przykład wykona tylko dwa zapytania SELECT, niezależnie od tego, czy przetwarzamy 10 czy 10 000 książek. -W poniższych przykładach będziemy pracować ze schematem bazy danych przedstawionym na rysunku. Istnieją wiązania OneHasMany (1:N) (autor książki, `author_id`, oraz dowolny tłumacz, `translator_id`, który może mieć wartość `null`) oraz ManyHasMany (M:N) pomiędzy książką a jej tagami. +Dodatkowo Explorer śledzi, które kolumny są używane w kodzie, i pobiera z bazy danych tylko te, oszczędzając tym samym dodatkową wydajność. To zachowanie jest w pełni automatyczne i adaptacyjne. Jeśli później zmodyfikujesz kod i zaczniesz używać innych kolumn, Explorer automatycznie dostosuje zapytania. Nie musisz niczego ustawiać ani zastanawiać się, które kolumny będziesz potrzebować - zostaw to Nette. -[Możesz znaleźć przykład, w tym schemat, na GitHubie |https://github.com/nette-examples/books]. -[* db-schema-1-.webp *] *** Struktura bazy danych dla powyższych przykładów .<> +Filtrowanie i sortowanie +======================== -Poniższy kod wymienia nazwę autora każdej książki i wszystkie jej tagi. Za chwilę [omówimy |#Working-with-Relationships] dokładnie jak to działa. +Klasa `Selection` dostarcza metody do filtrowania i sortowania wyboru danych. -```php -$books = $explorer->table('book'); +.[language-php] +| `where($condition, ...$params)` | Dodaje warunek WHERE. Wiele warunków jest łączonych operatorem AND +| `whereOr(array $conditions)` | Dodaje grupę warunków WHERE połączonych operatorem OR +| `wherePrimary($value)` | Dodaje warunek WHERE według klucza podstawowego +| `order($columns, ...$params)` | Ustawia sortowanie ORDER BY +| `select($columns, ...$params)` | Określa kolumny, które mają zostać załadowane +| `limit($limit, $offset = null)` | Ogranicza liczbę wierszy (LIMIT) i opcjonalnie ustawia OFFSET +| `page($page, $itemsPerPage, &$total = null)` | Ustawia paginację +| `group($columns, ...$params)` | Grupuje wiersze (GROUP BY) +| `having($condition, ...$params)` | Dodaje warunek HAVING do filtrowania zgrupowanych wierszy -foreach ($books as $book) { - echo 'tytuł: ' . $book->title; - echo 'napisane przez: ' . $book->author->name; // $book->author je řádek z tabulky 'autor' +Metody można łączyć łańcuchowo (tzw. [fluent interface |nette:introduction-to-object-oriented-programming#Fluent Interfaces]): `$table->where(...)->order(...)->limit(...)`. - echo 'tagi: '; - foreach ($book->related('book_tag') as $bookTag) { - echo $bookTag->tag->name . ', '; // $bookTag->tag je řádek z tabulky 'tag' - } -} -``` +W tych metodach możesz również używać specjalnej notacji do dostępu do [danych z powiązanych tabel |#Zapytania przez powiązane tabele]. -Będziesz mile zaskoczony tym, jak sprawnie działa warstwa bazodanowa. Powyższy przykład wykona stałą liczbę żądań, które wyglądają tak: -```sql -SELECT * FROM `book` -SELECT * FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT * FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) -``` +Escapowanie i identyfikatory +---------------------------- -Jeśli użyjesz pamięci [podręcznej |caching:] (jest ona domyślnie włączona), żadne niepotrzebne kolumny nie będą pobierane z bazy danych. Po pierwszym zapytaniu nazwy używanych kolumn będą przechowywane w pamięci podręcznej i tylko kolumny, których faktycznie używasz, będą pobierane z bazy danych: +Metody automatycznie escapują parametry i cytują identyfikatory (nazwy tabel i kolumn), zapobiegając w ten sposób SQL injection. Dla poprawnego działania konieczne jest przestrzeganie kilku zasad: -```sql -SELECT `id`, `title`, `author_id` FROM `book` -SELECT `id`, `name` FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT `book_id`, `tag_id` FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT `id`, `name` FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) +- Słowa kluczowe, nazwy funkcji, procedur itp. pisz **wielkimi literami**. +- Nazwy kolumn i tabel pisz **małymi literami**. +- Ciągi znaków zawsze wstawiaj przez **parametry**. + +```php +where('name = ' . $name); // KRYTYCZNA PODATNOŚĆ: SQL injection +where('name LIKE "%search%"'); // ŹLE: komplikuje automatyczne cytowanie +where('name LIKE ?', '%search%'); // POPRAWNIE: wartość wstawiona przez parametr + +where('name like ?', $name); // ŹLE: wygeneruje: `name` `like` ? +where('name LIKE ?', $name); // POPRAWNIE: wygeneruje: `name` LIKE ? +where('LOWER(name) = ?', $value);// POPRAWNIE: LOWER(`name`) = ? ``` -Wybór .[#toc-selections] -======================== +where(string|array $condition, ...$parameters): static .[method] +---------------------------------------------------------------- -Przyjrzyjmy się opcjom filtrowania i ograniczania selekcji przy użyciu klasy [api:Nette\Database\Table\Selection]: +Filtruje wyniki za pomocą warunków WHERE. Jej mocną stroną jest inteligentna praca z różnymi typami wartości i automatyczny wybór operatorów SQL. -.[language-php] -| `$table->where($where[, $param[, ...]])` | Ustawia WHERE używając AND jako łącznika, gdy występuje więcej niż jeden warunek -| `$table->whereOr($where)` | Ustawia WHERE używając OR jako łącznika w więcej niż jednym warunku -| `$table->order($columns)` | Ustawia ORDER BY, może być wyrażeniem `('column DESC, id DESC')` -| `$table->select($columns)` | Ustawia zwrócone kolumny, może być wyrażeniem `('col, MD5(col) AS hash')` -| `$table->limit($limit[, $offset])` | Ustawia Limit i OFFSET -| `$table->page($page, $itemsPerPage[, &$lastPage])` | Ustaw paginację -| `$table->group($columns)` | Set GROUP BY -| `$table->having($having)` | Set HAVING +Podstawowe użycie: -Możemy użyć tzw. interfejsu fluent, na przykład `$table->where(...)->order(...)->limit(...)`. Wiele warunków `where` lub `whereOr` łączy się za pomocą operatora `AND`. +```php +$table->where('id', $value); // WHERE `id` = 123 +$table->where('id > ?', $value); // WHERE `id` > 123 +$table->where('id = ? OR name = ?', $id, $name); // WHERE `id` = 1 OR `name` = 'Jon Snow' +``` +Dzięki automatycznej detekcji odpowiednich operatorów nie musimy zajmować się różnymi specjalnymi przypadkami. Nette rozwiąże je za nas: -gdzie() .[#toc-where] ---------------------- +```php +$table->where('id', 1); // WHERE `id` = 1 +$table->where('id', null); // WHERE `id` IS NULL +$table->where('id', [1, 2, 3]); // WHERE `id` IN (1, 2, 3) +// można również użyć znaku zapytania bez operatora: +$table->where('id ?', 1); // WHERE `id` = 1 +``` -Nette Database Explorer automatycznie dodaje odpowiednie operatory w zależności od otrzymanych danych: +Metoda poprawnie przetwarza również warunki negatywne i puste tablice: -.[language-php] -| `$table->where('field', $value)` | pole = $value -| `$table->where('field', null)` | field IS NULL -| `$table->where('field > ?', $val)` | pole > $val -| `$table->where('field', [1, 2])` | pole IN (1, 2) -| `$table->where('id = ? OR name = ?', 1, $name)` | id = 1 OR name = 'Jon Snow' -| `$table->where('field', $explorer->table($tableName))` | field IN (SELECT $primary FROM $tableName) -| `$table->where('field', $explorer->table($tableName)->select('col'))` | field IN (SELECT col FROM $tableName) +```php +$table->where('id', []); // WHERE `id` IS NULL AND FALSE -- nic nie znajdzie +$table->where('id NOT', []); // WHERE `id` IS NULL OR TRUE -- znajdzie wszystko +$table->where('NOT (id ?)', []); // WHERE NOT (`id` IS NULL AND FALSE) -- znajdzie wszystko +// $table->where('NOT id ?', $ids); Uwaga - ta składnia nie jest obsługiwana +``` -Placeholder (znak zapytania) działa nawet bez operatora kolumny. Kolejne połączenia są takie same: +Jako parametr możemy przekazać również wynik z innej tabeli - utworzy się podzapytanie: ```php -$table->where('id = ? OR id = ?', 1, 2); -$table->where('id ? OR id ?', 1, 2); +// WHERE `id` IN (SELECT `id` FROM `tableName`) +$table->where('id', $explorer->table($tableName)); + +// WHERE `id` IN (SELECT `col` FROM `tableName`) +$table->where('id', $explorer->table($tableName)->select('col')); ``` -Dzięki temu możliwe jest wygenerowanie właściwego operatora na podstawie wartości: +Warunki możemy przekazać również jako tablicę, której elementy zostaną połączone za pomocą AND: ```php -$table->where('id ?', 2); // id = 2 -$table->where('id ?', null); // id IS NULL -$table->where('id', $ids); // id IN (...) +// WHERE (`price_final` < `price_original`) AND (`stock_count` > `min_stock`) +$table->where([ + 'price_final < price_original', + 'stock_count > min_stock', +]); ``` -Wybór poprawnie obsługuje warunki negatywne i może również pracować z pustymi polami: +W tablicy możemy użyć par klucz => wartość, a Nette ponownie automatycznie wybierze odpowiednie operatory: ```php -$table->where('id', []); // id IS NULL AND FALSE -$table->where('id NOT', []); // id IS NULL OR TRUE -$table->where('NOT (id ?)', $ids); // NOT (id IS NULL AND FALSE) +// WHERE (`status` = 'active') AND (`id` IN (1, 2, 3)) +$table->where([ + 'status' => 'active', + 'id' => [1, 2, 3], +]); +``` -// powoduje to wyjątek, ta składnia nie jest obsługiwana -$table->where('NOT id ?', $ids); +W tablicy możemy łączyć wyrażenia SQL ze znakami zapytania i wieloma parametrami. Jest to odpowiednie dla złożonych warunków z precyzyjnie zdefiniowanymi operatorami: + +```php +// WHERE (`age` > 18) AND (ROUND(`score`, 2) > 75.5) +$table->where([ + 'age > ?' => 18, + 'ROUND(score, ?) > ?' => [2, 75.5], // dwa parametry przekazujemy jako tablicę +]); ``` +Wielokrotne wywołanie `where()` automatycznie łączy warunki za pomocą AND. + -gdzieOr() .[#toc-whereor] -------------------------- +whereOr(array $parameters): static .[method] +-------------------------------------------- -Przykład użycia bez parametrów: +Podobnie jak `where()` dodaje warunki, ale z tą różnicą, że łączy je za pomocą OR: ```php -// WHERE (user_id IS NULL) OR (SUM(`field1`) > SUM(`field2`)) +// WHERE (`status` = 'active') OR (`deleted` = 1) $table->whereOr([ - 'user_id IS NULL', - 'SUM(field1) > SUM(field2)', + 'status' => 'active', + 'deleted' => true, ]); ``` -Parametry użytkowe. Jeśli nie określisz operatora, Nette Database Explorer automatycznie doda odpowiedni: +Również tutaj możemy użyć bardziej złożonych wyrażeń: ```php -// WHERE (`field1` IS NULL) OR (`field2` IN (3, 5)) OR (`amount` > 11) +// WHERE (`price` > 1000) OR (`price_with_tax` > 1500) $table->whereOr([ - 'field1' => null, - 'field2' => [3, 5], - 'amount >' => 11, + 'price > ?' => 1000, + 'price_with_tax > ?' => 1500, ]); ``` -Możesz określić wyrażenie zawierające znaki zapytania wieloznaczne w kluczu, a następnie przekazać parametry w wartości: + +wherePrimary(mixed $key): static .[method] +------------------------------------------ + +Dodaje warunek dla klucza podstawowego tabeli: ```php -// WHERE (`id` > 12) OR (ROUND(`id`, 5) = 3) -$table->whereOr([ - 'id > ?' => 12, - 'ROUND(id, ?) = ?' => [5, 3], -]); +// WHERE `id` = 123 +$table->wherePrimary(123); + +// WHERE `id` IN (1, 2, 3) +$table->wherePrimary([1, 2, 3]); +``` + +Jeśli tabela ma złożony klucz podstawowy (np. `foo_id`, `bar_id`), przekażemy go jako tablicę: + +```php +// WHERE `foo_id` = 1 AND `bar_id` = 5 +$table->wherePrimary(['foo_id' => 1, 'bar_id' => 5])->fetch(); + +// WHERE (`foo_id`, `bar_id`) IN ((1, 5), (2, 3)) +$table->wherePrimary([ + ['foo_id' => 1, 'bar_id' => 5], + ['foo_id' => 2, 'bar_id' => 3], +])->fetchAll(); ``` -order() .[#toc-order] ---------------------- +order(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- -Przykłady użycia: +Określa kolejność, w jakiej będą zwracane wiersze. Możemy sortować według jednej lub więcej kolumn, w porządku malejącym lub rosnącym, lub według własnego wyrażenia: ```php -$table->order('field1'); // ORDER BY `field1` -$table->order('field1 DESC, field2'); // ORDER BY `field1` DESC, `field2` -$table->order('field = ? DESC', 123); // ORDER BY `field` = 123 DESC +$table->order('created'); // ORDER BY `created` +$table->order('created DESC'); // ORDER BY `created` DESC +$table->order('priority DESC, created'); // ORDER BY `priority` DESC, `created` +$table->order('status = ? DESC', 'active'); // ORDER BY `status` = 'active' DESC ``` -wybierz() .[#toc-select] ------------------------- +select(string $columns, ...$parameters): static .[method] +--------------------------------------------------------- + +Określa kolumny, które mają zostać zwrócone z bazy danych. Domyślnie Nette Database Explorer zwraca tylko te kolumny, które są rzeczywiście używane w kodzie. Metodę `select()` używamy więc w przypadkach, gdy potrzebujemy zwrócić specyficzne wyrażenia: -Przykłady użycia: +```php +// SELECT *, DATE_FORMAT(`created_at`, "%d.%m.%Y") AS `formatted_date` +$table->select('*, DATE_FORMAT(created_at, ?) AS formatted_date', '%d.%m.%Y'); +``` + +Aliasy zdefiniowane za pomocą `AS` są następnie dostępne jako właściwości obiektu ActiveRow: ```php -$table->select('field1'); // SELECT `field1` -$table->select('col, UPPER(col) AS abc'); // SELECT `col`, UPPER(`col`) AS abc -$table->select('SUBSTR(title, ?)', 3); // SELECT SUBSTR(`title`, 3) +foreach ($table as $row) { + echo $row->formatted_date; // dostęp do aliasu +} ``` -limit() .[#toc-limit] ---------------------- +limit(?int $limit, ?int $offset = null): static .[method] +--------------------------------------------------------- -Przykłady zastosowania: +Ogranicza liczbę zwracanych wierszy (LIMIT) i opcjonalnie pozwala ustawić offset: ```php -$table->limit(1); // LIMIT 1 -$table->limit(1, 10); // LIMIT 1 OFFSET 10 +$table->limit(10); // LIMIT 10 (zwraca pierwsze 10 wierszy) +$table->limit(10, 20); // LIMIT 10 OFFSET 20 ``` +Do paginacji bardziej odpowiednie jest użycie metody `page()`. -strona() .[#toc-page] ---------------------- -Alternatywny sposób ustawienia limitu i offsetu: +page(int $page, int $itemsPerPage, &$numOfPages = null): static .[method] +------------------------------------------------------------------------- + +Ułatwia paginację wyników. Przyjmuje numer strony (liczony od 1) i liczbę elementów na stronę. Opcjonalnie można przekazać referencję do zmiennej, do której zostanie zapisana całkowita liczba stron: ```php -$page = 5; -$itemsPerPage = 10; -$table->page($page, $itemsPerPage); // LIMIT 10 OFFSET 40 +$numOfPages = null; +$table->page(page: 3, itemsPerPage: 10, $numOfPages); +echo "Łącznie stron: $numOfPages"; ``` -Pobierz numer ostatniej strony, przekaż go do zmiennej `$lastPage`: + +group(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- + +Grupuje wiersze według podanych kolumn (GROUP BY). Używa się jej zazwyczaj w połączeniu z funkcjami agregującymi: ```php -$table->page($page, $itemsPerPage, $lastPage); +// Oblicza liczbę produktów w każdej kategorii +$table->select('category_id, COUNT(*) AS count') + ->group('category_id'); ``` -group() .[#toc-group] ---------------------- +having(string $having, ...$parameters): static .[method] +-------------------------------------------------------- -Przykłady użycia: +Ustawia warunek do filtrowania zgrupowanych wierszy (HAVING). Można ją użyć w połączeniu z metodą `group()` i funkcjami agregującymi: ```php -$table->group('field1'); // GROUP BY `field1` -$table->group('field1, field2'); // GROUP BY `field1`, `field2` +// Znajduje kategorie, które mają więcej niż 100 produktów +$table->select('category_id, COUNT(*) AS count') + ->group('category_id') + ->having('count > ?', 100); ``` -having() .[#toc-having] ------------------------ +Odczyt danych +============= + +Do odczytu danych z bazy danych mamy do dyspozycji kilka użytecznych metod: + +.[language-php] +| `foreach ($table as $key => $row)` | Iteruje po wszystkich wierszach, `$key` to wartość klucza podstawowego, `$row` to obiekt ActiveRow +| `$row = $table->get($key)` | Zwraca jeden wiersz według klucza podstawowego +| `$row = $table->fetch()` | Zwraca bieżący wiersz i przesuwa wskaźnik na następny +| `$array = $table->fetchPairs()` | Tworzy tablicę asocjacyjną z wyników +| `$array = $table->fetchAll()` | Zwraca wszystkie wiersze jako tablicę +| `count($table)` | Zwraca liczbę wierszy w obiekcie Selection + +Obiekt [ActiveRow |api:Nette\Database\Table\ActiveRow] jest przeznaczony tylko do odczytu. Oznacza to, że nie można zmieniać wartości jego właściwości. To ograniczenie zapewnia spójność danych i zapobiega nieoczekiwanym efektom ubocznym. Dane są ładowane z bazy danych, a jakakolwiek zmiana powinna być przeprowadzona jawnie i kontrolowanie. -Przykłady użycia: + +`foreach` - iteracja po wszystkich wierszach +-------------------------------------------- + +Najprostszy sposób na wykonanie zapytania i uzyskanie wierszy to iteracja w pętli `foreach`. Automatycznie uruchamia zapytanie SQL. ```php -$table->having('COUNT(items) >', 100); // HAVING COUNT(`items`) > 100 +$books = $explorer->table('book'); +foreach ($books as $key => $book) { + // $key to wartość klucza podstawowego, $book to ActiveRow + echo "$book->title ({$book->author->name})"; +} ``` -Wybór według wartości z innej tabeli .[#toc-filtering-by-another-table-value] ------------------------------------------------------------------------------ +get($key): ?ActiveRow .[method] +------------------------------- -Często potrzebujemy filtrować wyniki za pomocą warunku, który dotyczy innej tabeli bazy danych. Tego typu warunek wymaga łączenia tabel, ale dzięki Nette Database Explorer nigdy nie musimy pisać ich ręcznie. +Wykonuje zapytanie SQL i zwraca wiersz według klucza podstawowego, lub `null`, jeśli nie istnieje. -Załóżmy, że chcemy wybrać wszystkie książki napisane przez autora o nazwie `Jon`. Musimy jedynie wpisać nazwę klucza sesji join oraz nazwę kolumny połączonej tabeli. Klucz join pochodzi od nazwy kolumny, która odnosi się do tabeli, z którą chcemy się połączyć. W naszym przykładzie (patrz schemat bazy danych) jest to kolumna `author_id`, z której wystarczy użyć części - `author`. `name` to nazwa kolumny w tabeli `author`. Możemy również stworzyć warunek dla tłumacza książek, do którego dołączamy kolumnę `translator_id`. +```php +$book = $explorer->table('book')->get(123); // zwróci ActiveRow o ID 123 lub null +if ($book) { + echo $book->title; +} +``` + + +fetch(): ?ActiveRow .[method] +----------------------------- + +Zwraca wiersz i przesuwa wewnętrzny wskaźnik na następny. Jeśli nie ma już kolejnych wierszy, zwraca `null`. ```php $books = $explorer->table('book'); -$books->where('author.name LIKE ?', '%Jon%'); -$books->where('translator.name', 'David Grudl'); +while ($book = $books->fetch()) { + $this->processBook($book); +} ``` -Logika tworzenia klucza join jest podana przez implementację [Conventions |api:Nette\Database\Conventions]. Zalecamy użycie [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions], który parsuje klucze obce i pozwala łatwo pracować z relacjami między tabelami. -Relacja między książką a autorem wynosi 1:N. Możliwa jest również relacja odwrotna, nazywamy ją **backjoin**. Rozważmy następujący przykład. Chcemy wybrać wszystkich autorów, którzy napisali więcej niż trzy książki. Do stworzenia relacji odwrotnej używamy `:` (dvojtečku). Dvojtečka znamená, že jde o vztah hasMany (a je to logické, dvě tečky jsou více než jedna). Bohužel třída Selection není dostatečně chytrá a musíme mu pomoci s agregací výsledků a předat mu část `GROUP BY`, również warunek musi być zapisany jako `HAVING`. +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Zwraca wyniki jako tablicę asocjacyjną. Pierwszy argument określa nazwę kolumny, która zostanie użyta jako klucz w tablicy, drugi argument określa nazwę kolumny, która zostanie użyta jako wartość: ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book.id) > 3'); +$authors = $explorer->table('author')->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] ``` -Być może zauważyłeś, że wyrażenie join odwołuje się do `book`, ale nie jest jasne, czy łączymy się przez `author_id` czy `translator_id`. W powyższym przykładzie Selection łączy się przez kolumnę `author_id`, ponieważ znaleziono dopasowanie do nazwy tabeli źródłowej - tabeli `author`. Gdyby nie było dopasowania i istniało wiele możliwości, Nette rzuciłoby [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. +Jeśli podamy tylko pierwszy parametr, wartością będzie cały wiersz, czyli obiekt `ActiveRow`: + +```php +$authors = $explorer->table('author')->fetchPairs('id'); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` -Aby dołączyć poprzez `translator_id`, wystarczy dodać opcjonalny parametr do wyrażenia join. +W przypadku duplikujących się kluczy użyta zostanie wartość z ostatniego wiersza. Przy użyciu `null` jako klucza tablica będzie indeksowana numerycznie od zera (wtedy do kolizji nie dochodzi): ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book(translator).id) > 3'); +$authors = $explorer->table('author')->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] ``` -Przyjrzyjmy się teraz bardziej złożonemu przykładowi łączenia tabel. -Chcemy wybrać wszystkich autorów, którzy napisali coś o PHP. Wszystkie książki mają etykiety, więc chcemy wybrać wszystkich autorów, którzy napisali książkę z etykietą "PHP". +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Alternatywnie możesz jako parametr podać callback, który dla każdego wiersza będzie zwracał albo samą wartość, albo parę klucz-wartość. ```php -$authors = $explorer->table('author'); -$authors->where(':book:book_tags.tag.name', 'PHP') - ->group('author.id') - ->having('COUNT(:book:book_tags.tag.id) > 0'); +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => "$row->title ({$row->author->name})"); +// ['Pierwsza książka (Jan Nowak)', ...] + +// Callback może również zwracać tablicę z parą klucz & wartość: +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => [$row->title, $row->author->name]); +// ['Pierwsza książka' => 'Jan Nowak', ...] ``` -Agregacja wyników .[#toc-aggregate-queries] -------------------------------------------- +fetchAll(): array .[method] +--------------------------- -| `$table->count('*')` | Zwraca liczbę wierszy -| `$table->count("DISTINCT $column")` | Zwraca liczbę różnych wartości. -| `$table->min($column)` | Zwraca wartość minimalną -| `$table->max($column)` | Zwraca maksymalną wartość -| `$table->sum($column)` | Zwraca sumę wszystkich wartości -| `$table->aggregation("GROUP_CONCAT($column)")` | Dla każdej innej funkcji agregacji +Zwraca wszystkie wiersze jako tablicę asocjacyjną obiektów `ActiveRow`, gdzie klucze są wartościami kluczy podstawowych. -.[caution] -Metoda `count()` bez określonego parametru wybiera wszystkie rekordy i zwraca rozmiar tablicy, co jest bardzo nieefektywne. Na przykład, jeśli potrzebujesz policzyć liczbę wierszy dla paginacji, zawsze podaj pierwszy argument. +```php +$allBooks = $explorer->table('book')->fetchAll(); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` + + +count(): int .[method] +---------------------- +Metoda `count()` bez parametru zwraca liczbę wierszy w obiekcie `Selection`: + +```php +$table->where('category', 1); +$count = $table->count(); +$count = count($table); // alternatywa +``` -Ucieczka i cytaty .[#toc-escaping-quoting] -========================================== +Uwaga, `count()` z parametrem wykonuje funkcję agregującą COUNT w bazie danych, zobacz poniżej. -Database Explorer potrafi sprytnie wymykać się parametrom i identyfikatorom. Jednak dla prawidłowej funkcjonalności należy przestrzegać kilku zasad: -- wielkie litery w słowach kluczowych, nazwach funkcji, nazwach procedur itp. -- pisać nazwy kolumn i tabel małymi literami -- ustawianie wartości za pomocą parametrów +ActiveRow::toArray(): array .[method] +------------------------------------- + +Konwertuje obiekt `ActiveRow` na tablicę asocjacyjną, gdzie klucze są nazwami kolumn, a wartości odpowiadającymi danymi. ```php -->where('name like ?', 'John'); // ZŁY! generuje: `name` `like` ? -->where('name LIKE ?', 'John'); // PRAWDA +$book = $explorer->table('book')->get(1); +$bookArray = $book->toArray(); +// $bookArray będzie ['id' => 1, 'title' => '...', 'author_id' => ..., ...] +``` + + +Agregacja +========= + +Klasa `Selection` dostarcza metody do łatwego wykonywania funkcji agregujących (COUNT, SUM, MIN, MAX, AVG itd.). + +.[language-php] +| `count($expr)` | Oblicza liczbę wierszy +| `min($expr)` | Zwraca minimalną wartość w kolumnie +| `max($expr)` | Zwraca maksymalną wartość w kolumnie +| `sum($expr)` | Zwraca sumę wartości w kolumnie +| `aggregation($function)` | Umożliwia wykonanie dowolnej funkcji agregującej. Np. `AVG()`, `GROUP_CONCAT()` -->where('KEY = ?', $value); // PRAWDA! KEY jest słowem kluczowym -->where('key = ?', $value); // PRAWDA. generuje: `key` = ? -->where('name = ' . $name); // ZŁY! sql injection! -->where('name = ?', $name); // PRAWDA +count(string $expr): int .[method] +---------------------------------- -->select('DATE_FORMAT(created, "%d.%m.%Y")'); // ZŁY! wartości są wstawiane przez parametr -->select('DATE_FORMAT(created, ?)', '%d.%m.%Y'); // PRAWDA +Wykonuje zapytanie SQL z funkcją COUNT i zwraca wynik. Metoda jest używana do sprawdzenia, ile wierszy odpowiada określonemu warunkowi: + +```php +$count = $table->count('*'); // SELECT COUNT(*) FROM `table` +$count = $table->count('DISTINCT column'); // SELECT COUNT(DISTINCT `column`) FROM `table` ``` -.[warning] -Niewłaściwe użycie może prowadzić do powstania luk w zabezpieczeniach aplikacji. +Uwaga, [#count()] bez parametru tylko zwraca liczbę wierszy w obiekcie `Selection`. -Dane do odczytu .[#toc-fetching-data] -===================================== +min(string $expr) a max(string $expr) .[method] +----------------------------------------------- -| `foreach ($table as $id => $row)` | Iteruje przez wszystkie wiersze wyniku -| `$row = $table->get($id)` | Zwraca jeden wiersz o ID $id -| `$row = $table->fetch()` | Zwraca następny wiersz wyniku. -| `$array = $table->fetchPairs($key, $value)` | Zwraca wszystkie wyniki jako tablicę asocjacyjną -| `$array = $table->fetchPairs($key)` | Zwraca wszystkie wiersze jako tablicę asocjacyjną -| `count($table)` | Zwraca liczbę wierszy w wyniku. +Metody `min()` i `max()` zwracają minimalną i maksymalną wartość w określonej kolumnie lub wyrażeniu: +```php +// SELECT MAX(`price`) FROM `products` WHERE `active` = 1 +$maxPrice = $products->where('active', true) + ->max('price'); +``` -Wstawianie, aktualizacja i usuwanie .[#toc-insert-update-delete] -================================================================ -Metoda `insert()` akceptuje tablice lub obiekty Traversable (na przykład [ArrayHash |utils:arrays#ArrayHash], z którym pracują [formularze |forms:]): +sum(string $expr) .[method] +--------------------------- + +Zwraca sumę wartości w określonej kolumnie lub wyrażeniu: + +```php +// SELECT SUM(`price` * `items_in_stock`) FROM `products` WHERE `active` = 1 +$totalPrice = $products->where('active', true) + ->sum('price * items_in_stock'); +``` + + +aggregation(string $function, ?string $groupFunction = null) .[method] +---------------------------------------------------------------------- + +Umożliwia wykonanie dowolnej funkcji agregującej. + +```php +// średnia cena produktów w kategorii +$avgPrice = $products->where('category_id', 1) + ->aggregation('AVG(price)'); + +// łączy etykiety produktu w jeden ciąg +$tags = $products->where('id', 1) + ->aggregation('GROUP_CONCAT(tag.name) AS tags') + ->fetch() + ->tags; +``` + +Jeśli potrzebujemy agregować wyniki, które już same w sobie powstały z jakiejś funkcji agregującej i grupowania (np. `SUM(wartość)` przez zgrupowane wiersze), jako drugi argument podajemy funkcję agregującą, która ma być zastosowana do tych wyników pośrednich: + +```php +// Oblicza całkowitą cenę produktów w magazynie dla poszczególnych kategorii, a następnie sumuje te ceny. +$totalPrice = $products->select('category_id, SUM(price * stock) AS category_total') + ->group('category_id') + ->aggregation('SUM(category_total)', 'SUM'); +``` + +W tym przykładzie najpierw obliczamy całkowitą cenę produktów w każdej kategorii (`SUM(price * stock) AS category_total`) i grupujemy wyniki według `category_id`. Następnie używamy `aggregation('SUM(category_total)', 'SUM')` do zsumowania tych sum pośrednich `category_total`. Drugi argument `'SUM'` mówi, że na wyniki pośrednie ma być zastosowana funkcja SUM. + + +Insert, Update & Delete +======================= + +Nette Database Explorer upraszcza wstawianie, aktualizację i usuwanie danych. Wszystkie podane metody w przypadku błędu wyrzucą wyjątek `Nette\Database\DriverException`. + + +Selection::insert(iterable $data) .[method] +------------------------------------------- + +Wstawia nowe rekordy do tabeli. + +**Wstawianie jednego rekordu:** + +Nowy rekord przekazujemy jako tablicę asocjacyjną lub obiekt iterable (na przykład ArrayHash używany w [formularzach |forms:]), gdzie klucze odpowiadają nazwom kolumn w tabeli. + +Jeśli tabela ma zdefiniowany klucz podstawowy, metoda zwraca obiekt `ActiveRow`, który jest ponownie ładowany z bazy danych, aby uwzględnić ewentualne zmiany dokonane na poziomie bazy danych (triggery, wartości domyślne kolumn, obliczenia kolumn auto-increment). Zapewnia to spójność danych, a obiekt zawsze zawiera aktualne dane z bazy danych. Jeśli jednoznaczny klucz podstawowy nie istnieje, zwraca przekazane dane w formie tablicy. ```php $row = $explorer->table('users')->insert([ - 'name' => $name, - 'year' => $year, + 'name' => 'John Doe', + 'email' => 'john.doe@example.com', ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) +// $row jest instancją ActiveRow i zawiera kompletne dane wstawionego wiersza, +// w tym automatycznie generowane ID i ewentualne zmiany dokonane przez triggery +echo $row->id; // Wypisuje ID nowo wstawionego użytkownika +echo $row->created_at; // Wypisuje czas utworzenia, jeśli jest ustawiony przez trigger ``` -Jeśli tabela ma zdefiniowany klucz główny, zwraca nowy wiersz jako obiekt ActiveRow. +**Wstawianie wielu rekordów naraz:** -Wkładka wielokrotna: +Metoda `insert()` umożliwia wstawienie wielu rekordów za pomocą jednego zapytania SQL. W tym przypadku zwraca liczbę wstawionych wierszy. ```php -$explorer->table('users')->insert([ +$insertedRows = $explorer->table('users')->insert([ + [ + 'name' => 'John', + 'year' => 1994, + ], [ - 'name' => 'Jim', - 'year' => 1978, - ], [ 'name' => 'Jack', - 'year' => 1987, + 'year' => 1995, ], ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) +// INSERT INTO `users` (`name`, `year`) VALUES ('John', 1994), ('Jack', 1995) +// $insertedRows będzie 2 +``` + +Jako parametr można również przekazać obiekt `Selection` z wyborem danych. + +```php +$newUsers = $explorer->table('potential_users') + ->where('approved', 1) + ->select('name, email'); + +$insertedRows = $explorer->table('users')->insert($newUsers); ``` -Jako parametry możemy również przekazywać pliki lub obiekty DateTime: +**Wstawianie specjalnych wartości:** + +Jako wartości możemy przekazywać również pliki, obiekty DateTime lub literały SQL: ```php $explorer->table('users')->insert([ - 'name' => $name, - 'created' => new DateTime, // nebo $explorer::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // vloží soubor + 'name' => 'John', + 'created_at' => new DateTime, // konwertuje na format bazy danych + 'avatar' => fopen('image.jpg', 'rb'), // wstawia binarną zawartość pliku + 'uuid' => $explorer::literal('UUID()'), // wywołuje funkcję UUID() ]); ``` -Edycja rekordów (zwraca liczbę zmienionych wierszy): + +Selection::update(iterable $data): int .[method] +------------------------------------------------ + +Aktualizuje wiersze w tabeli według podanego filtra. Zwraca liczbę rzeczywiście zmienionych wierszy. + +Zmieniane kolumny przekazujemy jako tablicę asocjacyjną lub obiekt iterable (na przykład ArrayHash używany w [formularzach |forms:]), gdzie klucze odpowiadają nazwom kolumn w tabeli: ```php -$count = $explorer->table('users') - ->where('id', 10) // musí se volat před update() +$affected = $explorer->table('users') + ->where('id', 10) ->update([ - 'name' => 'Ned Stark' + 'name' => 'John Smith', + 'year' => 1994, ]); -// UPDATE `users` SET `name`='Ned Stark' WHERE (`id` = 10) +// UPDATE `users` SET `name` = 'John Smith', `year` = 1994 WHERE `id` = 10 ``` -Do aktualizacji możemy użyć operatorów `+=` i `-=`: +Do zmiany wartości liczbowych możemy użyć operatorów `+=` i `-=`: ```php $explorer->table('users') + ->where('id', 10) ->update([ - 'age+=' => 1, // všimněte si += + 'points+=' => 1, // zwiększa wartość kolumny 'points' o 1 + 'coins-=' => 1, // zmniejsza wartość kolumny 'coins' o 1 ]); -// UPDATE users SET `age` = `age` + 1 +// UPDATE `users` SET `points` = `points` + 1, `coins` = `coins` - 1 WHERE `id` = 10 ``` -Usuń rekordy (zwraca liczbę usuniętych wierszy): + +Selection::delete(): int .[method] +---------------------------------- + +Usuwa wiersze z tabeli według podanego filtra. Zwraca liczbę usuniętych wierszy. ```php $count = $explorer->table('users') ->where('id', 10) ->delete(); -// DELETE FROM `users` WHERE (`id` = 10) +// DELETE FROM `users` WHERE `id` = 10 ``` +.[caution] +Podczas wywoływania `update()` i `delete()` nie zapomnij za pomocą `where()` określić wierszy, które mają zostać zmodyfikowane/usunięte. Jeśli `where()` nie zostanie użyte, operacja zostanie przeprowadzona na całej tabeli! -Wiązania między tabelami .[#toc-working-with-relationships] -=========================================================== +ActiveRow::update(iterable $data): bool .[method] +------------------------------------------------- -Sesja Ma jeden .[#toc-has-one-relation] ---------------------------------------- -Bardzo często spotykana jest sesja has one. Książka *ma jednego* autora. Książka *ma jednego* tłumacza. Wiersz, który znajduje się w relacji has one uzyskujemy za pomocą metody `ref()` Przyjmuje ona dwa argumenty: nazwę tabeli docelowej oraz nazwę kolumny złączenia. Zobacz przykład: +Aktualizuje dane w wierszu bazy danych reprezentowanym przez obiekt `ActiveRow`. Jako parametr przyjmuje iterable z danymi, które mają zostać zaktualizowane (klucze są nazwami kolumn). Do zmiany wartości liczbowych możemy użyć operatorów `+=` i `-=`: + +Po wykonaniu aktualizacji `ActiveRow` jest automatycznie ponownie ładowany z bazy danych, aby uwzględnić ewentualne zmiany dokonane na poziomie bazy danych (np. triggery). Metoda zwraca true tylko jeśli doszło do rzeczywistej zmiany danych. ```php -$book = $explorer->table('book')->get(1); -$book->ref('author', 'author_id'); +$article = $explorer->table('article')->get(1); +$article->update([ + 'views += 1', // zwiększamy liczbę wyświetleń +]); +echo $article->views; // Wypisuje aktualną liczbę wyświetleń ``` -W powyższym przykładzie wybieramy powiązanego autora z tabeli `author`. Klucz główny tabeli `author` jest wyszukiwany przez kolumnę `book.author_id`. Metoda `ref()` zwraca instancję `ActiveRow` lub `null`, jeśli poszukiwany rekord nie istnieje. Zwrócony wiersz jest instancją `ActiveRow`, więc możemy z nim pracować tak jak z rekordem książki. +Ta metoda aktualizuje tylko jeden konkretny wiersz w bazie danych. Do masowej aktualizacji wielu wierszy użyj metody [#Selection::update()]. + + +ActiveRow::delete() .[method] +----------------------------- + +Usuwa wiersz z bazy danych, który jest reprezentowany przez obiekt `ActiveRow`. ```php -$author = $book->ref('author', 'author_id'); -$author->name; -$author->born; +$book = $explorer->table('book')->get(1); +$book->delete(); // Usuwa książkę o ID 1 +``` + +Ta metoda usuwa tylko jeden konkretny wiersz w bazie danych. Do masowego usunięcia wielu wierszy użyj metody [#Selection::delete()]. + + +Relacje między tabelami +======================= + +W relacyjnych bazach danych dane są podzielone na wiele tabel i wzajemnie powiązane za pomocą kluczy obcych. Nette Database Explorer wprowadza rewolucyjny sposób pracy z tymi relacjami - bez pisania zapytań JOIN i konieczności cokolwiek konfigurować czy generować. + +Do ilustracji pracy z relacjami użyjemy przykładu bazy danych książek ([znajdziesz go na GitHubie |https://github.com/nette-examples/books]). W bazie danych mamy tabele: + +- `author` - pisarze i tłumacze (kolumny `id`, `name`, `web`, `born`) +- `book` - książki (kolumny `id`, `author_id`, `translator_id`, `title`, `sequel_id`) +- `tag` - etykiety (kolumny `id`, `name`) +- `book_tag` - tabela łącząca między książkami a etykietami (kolumny `book_id`, `tag_id`) + +[* db-schema-1-.webp *] *** Struktura bazy danych użyta w przykładach *** + +W naszym przykładzie bazy danych książek znajdziemy kilka typów relacji (chociaż model jest uproszczony w porównaniu do rzeczywistości): + +- One-to-many 1:N – każda książka **ma jednego** autora, autor może napisać **kilka** książek +- Zero-to-many 0:N – książka **może mieć** tłumacza, tłumacz może przetłumaczyć **kilka** książek +- Zero-to-one 0:1 – książka **może mieć** kolejną część +- Many-to-many M:N – książka **może mieć kilka** tagów, a tag może być przypisany **kilku** książkom + +W tych relacjach zawsze istnieje tabela nadrzędna i podrzędna. Na przykład w relacji między autorem a książką tabela `author` jest nadrzędna, a `book` podrzędna - możemy to sobie wyobrazić tak, że książka zawsze "należy" do jakiegoś autora. Przejawia się to również w strukturze bazy danych: podrzędna tabela `book` zawiera klucz obcy `author_id`, który odnosi się do nadrzędnej tabeli `author`. -// nebo přímo -$book->ref('author', 'author_id')->name; -$book->ref('author', 'author_id')->born; +Jeśli potrzebujemy wypisać książki wraz z imionami ich autorów, mamy dwie możliwości. Albo uzyskamy dane jednym zapytaniem SQL za pomocą JOIN: + +```sql +SELECT book.*, author.name FROM book LEFT JOIN author ON book.author_id = author.id ``` -Książka ma też jednego tłumacza, łatwo możemy poznać jego nazwisko. +Albo załadujemy dane w dwóch krokach - najpierw książki, a potem ich autorów - a następnie złożymy je w PHP: + +```sql +SELECT * FROM book; +SELECT * FROM author WHERE id IN (1, 2, 3); -- id autorów pobranych książek +``` + +Drugie podejście jest w rzeczywistości bardziej efektywne, choć może to być zaskakujące. Dane są ładowane tylko raz i mogą być lepiej wykorzystane w cache. Właśnie w ten sposób działa Nette Database Explorer - wszystko rozwiązuje pod powierzchnią i oferuje Ci eleganckie API: + ```php -$book->ref('author', 'translator_id')->name +$books = $explorer->table('book'); +foreach ($books as $book) { + echo 'tytuł: ' . $book->title; + echo 'napisane przez: ' . $book->author->name; // $book->author to rekord z tabeli 'author' + echo 'przetłumaczone przez: ' . $book->translator?->name; +} ``` -Takie podejście jest funkcjonalne, ale wciąż nieco uciążliwe, nie sądzisz? Baza danych zawiera już definicje kluczy obcych, więc dlaczego nie używać ich automatycznie. Spróbujmy. -Jeśli uzyskamy dostęp do zmiennej członkowskiej, która nie istnieje, ActiveRow spróbuje użyć nazwy tej zmiennej dla sesji "ma jedną". Odczytanie tej zmiennej jest takie samo jak wywołanie metody `ref()` z tylko jednym parametrem. Parametr ten będziemy nazywać **kluczem**. Ten klucz zostanie użyty do znalezienia klucza obcego w tabeli. Przekazany klucz jest porównywany z kolumnami, a jeśli pasuje do reguł, klucz obcy na tej kolumnie jest używany do odczytu danych z powiązanej tabeli. Zobacz przykład: +Dostęp do tabeli nadrzędnej +--------------------------- + +Dostęp do tabeli nadrzędnej jest prosty. Chodzi o relacje takie jak *książka ma autora* lub *książka może mieć tłumacza*. Powiązany rekord uzyskujemy przez właściwość obiektu ActiveRow - jej nazwa odpowiada nazwie kolumny z kluczem obcym bez `id`: ```php -$book->author->name; -// to samo co -$book->ref('author')->name; +$book = $explorer->table('book')->get(1); +echo $book->author->name; // znajduje autora według kolumny author_id +echo $book->translator?->name; // znajduje tłumacza według translator_id ``` -Instancja ActiveRow nie ma kolumny `author`. Wszystkie kolumny tabeli `book` są skanowane w poszukiwaniu dopasowania do *klucza*. Dopasowanie w tym przypadku oznacza, że nazwa kolumny musi zawierać klucz. W powyższym przykładzie kolumna `author_id` zawiera ciąg "autor", a zatem pasuje do klucza "autor". Jeśli chcemy uzyskać dostęp do rekordu tłumacza, używamy klucza 'translator' w podobny sposób, ponieważ będzie on pasował do kolumny `translator_id` Więcej o logice dopasowywania kluczy można przeczytać w [Łączenie wyrażeń |#Filtering-by-Another-Table-Value]. +Gdy uzyskujemy dostęp do właściwości `$book->author`, Explorer w tabeli `book` szuka kolumny, której nazwa zawiera ciąg `author` (czyli `author_id`). Według wartości w tej kolumnie ładuje odpowiedni rekord z tabeli `author` i zwraca go jako `ActiveRow`. Podobnie działa `$book->translator`, który wykorzystuje kolumnę `translator_id`. Ponieważ kolumna `translator_id` może zawierać `null`, użyjemy w kodzie operatora `?->`. + +Alternatywną ścieżkę oferuje metoda `ref()`, która przyjmuje dwa argumenty, nazwę tabeli docelowej i nazwę kolumny łączącej, i zwraca instancję `ActiveRow` lub `null`: ```php -echo $book->title . ': '; -echo $book->author->name; -if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; -} +echo $book->ref('author', 'author_id')->name; // relacja do autora +echo $book->ref('author', 'translator_id')->name; // relacja do tłumacza ``` -Jeśli chcemy zdobyć autora wielu książek, stosujemy to samo podejście. Nette Database Explorer pobierze dla nas rekordy autora i tłumacza dla wszystkich książek naraz. +Metoda `ref()` przydaje się, jeśli nie można użyć dostępu przez właściwość, ponieważ tabela zawiera kolumnę o tej samej nazwie (tj. `author`). W pozostałych przypadkach zaleca się używanie dostępu przez właściwość, który jest bardziej czytelny. + +Explorer automatycznie optymalizuje zapytania do bazy danych. Kiedy przechodzimy przez książki w pętli i uzyskujemy dostęp do ich powiązanych rekordów (autorów, tłumaczy), Explorer nie generuje zapytania dla każdej książki osobno. Zamiast tego wykonuje tylko jedno zapytanie SELECT dla każdego typu relacji, co znacznie zmniejsza obciążenie bazy danych. Na przykład: ```php $books = $explorer->table('book'); foreach ($books as $book) { echo $book->title . ': '; echo $book->author->name; - if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; - } + echo $book->translator?->name; } ``` -Ten kod wywoła tylko te trzy zapytania do bazy danych: +Ten kod wywoła tylko te trzy błyskawiczne zapytania do bazy danych: + ```sql SELECT * FROM `book`; -SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- id ze sloupce author_id vybraných knih -SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- id ze sloupce translator_id vybraných knih +SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- id z kolumny author_id wybranych książek +SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- id z kolumny translator_id wybranych książek ``` +.[note] +Logika wyszukiwania kolumny łączącej jest określona przez implementację [Conventions |api:Nette\Database\Conventions]. Zalecamy użycie [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions], które analizuje klucze obce i pozwala łatwo pracować z istniejącymi relacjami między tabelami. -Sesja Ma wiele .[#toc-has-many-relation] ----------------------------------------- -Sesja "ma wielu" jest po prostu odwrotnością sesji "ma jednego". Autor napisał kilka (*wiele*) książek. Autorka przetłumaczyła kilka (*wiele*) książek. Ten typ relacji jest trudniejszy, ponieważ relacja jest nazwana ("napisał", "przetłumaczył"). ActiveRow posiada metodę `related()`, która zwraca tablicę powiązanych rekordów. Rekordy są ponownie instancjami ActiveRow. Zobacz przykład: +Dostęp do tabeli podrzędnej +--------------------------- + +Dostęp do tabeli podrzędnej działa w przeciwnym kierunku. Teraz pytamy *jakie książki napisał ten autor* lub *przetłumaczył ten tłumacz*. Do tego typu zapytania używamy metody `related()`, która zwraca `Selection` z powiązanymi rekordami. Spójrzmy na przykład: ```php -$author = $explorer->table('author')->get(11); -echo $author->name . ' napsal:'; +$author = $explorer->table('author')->get(1); +// Wypisuje wszystkie książki autora foreach ($author->related('book.author_id') as $book) { - echo $book->title; + echo "Napisał: $book->title"; } -echo 'a přeložil:'; +// Wypisuje wszystkie książki, które autor przetłumaczył foreach ($author->related('book.translator_id') as $book) { - echo $book->title; + echo "Przetłumaczył: $book->title"; } ``` -Metoda `related()` przyjmuje opis połączenia jako dwa argumenty lub jako pojedynczy argument połączony kropką. Pierwszy argument to tabela docelowa, drugi to kolumna. +Metoda `related()` przyjmuje opis połączenia jako jeden argument z notacją kropkową lub jako dwa osobne argumenty: ```php -$author->related('book.translator_id'); -// je stejné jako -$author->related('book', 'translator_id'); +$author->related('book.translator_id'); // jeden argument +$author->related('book', 'translator_id'); // dwa argumenty ``` -Możemy wykorzystać heurystykę Nette Database Explorer opartą na kluczach obcych i użyć tylko **klucza**. Klucz zostanie porównany z kluczami obcymi, które odwołują się do bieżącej tabeli (tabela `author`). Jeśli zostanie znalezione dopasowanie, Nette Database Explorer użyje tego klucza obcego, w przeciwnym razie rzuci [Nette\InvalidArgumentException |api:Nette\InvalidArgumentException] lub [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. Więcej o logice dopasowania kluczy można przeczytać w [Łączenie wyrażeń |#Filtering-by-Another-Table-Value]. +Explorer potrafi automatycznie wykryć poprawną kolumnę łączącą na podstawie nazwy tabeli nadrzędnej. W tym przypadku łączy się przez kolumnę `book.author_id`, ponieważ nazwa tabeli źródłowej to `author`: -Oczywiście metodę `related()` można wywołać na wszystkich wyszukanych autorach, a Nette Database Explorer załaduje wszystkie pasujące książki jednocześnie. +```php +$author->related('book'); // użyje book.author_id +``` + +Gdyby istniało więcej możliwych połączeń, Explorer wyrzuci wyjątek [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. + +Metodę `related()` możemy oczywiście użyć również przy przechodzeniu przez wiele rekordów w pętli, a Explorer również w tym przypadku automatycznie optymalizuje zapytania: ```php $authors = $explorer->table('author'); foreach ($authors as $author) { - echo $author->name . ' napsal:'; + echo $author->name . ' napisał:'; foreach ($author->related('book') as $book) { - $book->title; + echo $book->title; } } ``` -W powyższym przykładzie uruchomiono tylko te dwa zapytania do bazy danych: +Ten kod wygeneruje tylko dwa błyskawiczne zapytania SQL: ```sql SELECT * FROM `author`; -SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- id vybraných autorů +SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- id wybranych autorów +``` + + +Relacja Many-to-many +-------------------- + +Dla relacji many-to-many (M:N) potrzebna jest istnienie tabeli łączącej (w naszym przypadku `book_tag`), która zawiera dwie kolumny z kluczami obcymi (`book_id`, `tag_id`). Każda z tych kolumn odnosi się do klucza podstawowego jednej z łączonych tabel. Aby uzyskać powiązane dane, najpierw uzyskujemy rekordy z tabeli łączącej za pomocą `related('book_tag')`, a następnie przechodzimy do danych docelowych: + +```php +$book = $explorer->table('book')->get(1); +// wypisuje nazwy tagów przypisanych do książki +foreach ($book->related('book_tag') as $bookTag) { + echo $bookTag->tag->name; // wypisuje nazwę tagu przez tabelę łączącą +} + +$tag = $explorer->table('tag')->get(1); +// lub odwrotnie: wypisuje nazwy książek oznaczonych tym tagiem +foreach ($tag->related('book_tag') as $bookTag) { + echo $bookTag->book->title; // wypisuje nazwę książki +} ``` +Explorer ponownie optymalizuje zapytania SQL do efektywnej postaci: + +```sql +SELECT * FROM `book`; +SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 2, ...)); -- id wybranych książek +SELECT * FROM `tag` WHERE (`tag`.`id` IN (1, 2, ...)); -- id tagów znalezionych w book_tag +``` -Ręczny Eksplorator Tworzenia .[#toc-creating-explorer-manually] -=============================================================== -Jeśli utworzyliśmy połączenie z bazą danych za pomocą konfiguracji aplikacji, nie musimy się o nic martwić. Stworzyliśmy również usługę taką jak `Nette\Database\Explorer`, którą możemy przekazać za pomocą DI. +Zapytania przez powiązane tabele +-------------------------------- -Jeśli jednak korzystamy z Nette Database Explorer osobno, musimy ręcznie utworzyć instancję `Nette\Database\Explorer`. +W metodach `where()`, `select()`, `order()` i `group()` możemy używać specjalnych notacji do dostępu do kolumn z innych tabel. Explorer automatycznie utworzy potrzebne JOINy. + +**Notacja kropkowa** (`tabela_nadrzędna.kolumna`) jest używana dla relacji 1:N z perspektywy tabeli podrzędnej: ```php -// $storage obsahuje implementację Nette\Caching\Storage, např..: -$storage = new Nette\Caching\Storages\FileStorage($tempDir); -$connection = new Nette\Database\Connection($dsn, $user, $password); -$structure = new Nette\Database\Structure($connection, $storage); -$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); -$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +$books = $explorer->table('book'); + +// Znajduje książki, których autor ma imię zaczynające się na 'Jon' +$books->where('author.name LIKE ?', 'Jon%'); + +// Sortuje książki według imienia autora malejąco +$books->order('author.name DESC'); + +// Wypisuje tytuł książki i imię autora +$books->select('book.title, author.name'); ``` + +**Notacja dwukropkowa** (`:tabela_podrzędna.kolumna`) jest używana dla relacji 1:N z perspektywy tabeli nadrzędnej: + +```php +$authors = $explorer->table('author'); + +// Znajduje autorów, którzy napisali książkę z 'PHP' w tytule +$authors->where(':book.title LIKE ?', '%PHP%'); + +// Oblicza liczbę książek dla każdego autora +$authors->select('*, COUNT(:book.id) AS book_count') + ->group('author.id'); +``` + +W powyższym przykładzie z notacją dwukropkową (`:book.title`) nie jest określona kolumna z kluczem obcym. Explorer automatycznie wykrywa poprawną kolumnę na podstawie nazwy tabeli nadrzędnej. W tym przypadku łączy się przez kolumnę `book.author_id`, ponieważ nazwa tabeli źródłowej to `author`. Gdyby istniało więcej możliwych połączeń, Explorer wyrzuci wyjątek [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. + +Kolumnę łączącą można jawnie podać w nawiasie: + +```php +// Znajduje autorów, którzy przetłumaczyli książkę z 'PHP' w tytule +$authors->where(':book(translator_id).title LIKE ?', '%PHP%'); +``` + +Notacje można łączyć łańcuchowo dla dostępu przez wiele tabel: + +```php +// Znajduje autorów książek oznaczonych tagiem 'PHP' +$authors->where(':book:book_tag.tag.name', 'PHP') + ->group('author.id'); +``` + + +Rozszerzenie warunków dla JOIN +------------------------------ + +Metoda `joinWhere()` rozszerza warunki, które podaje się przy łączeniu tabel w SQL za słowem kluczowym `ON`. + +Załóżmy, że chcemy znaleźć książki przetłumaczone przez konkretnego tłumacza: + +```php +// Znajduje książki przetłumaczone przez tłumacza o imieniu 'David' +$books = $explorer->table('book') + ->joinWhere('translator', 'translator.name', 'David'); +// LEFT JOIN author translator ON book.translator_id = translator.id AND (translator.name = 'David') +``` + +W warunku `joinWhere()` możemy używać tych samych konstrukcji co w metodzie `where()` - operatorów, znaków zapytania, tablic wartości czy wyrażeń SQL. + +Dla bardziej złożonych zapytań z wieloma JOINami możemy zdefiniować aliasy tabel: + +```php +$tags = $explorer->table('tag') + ->joinWhere(':book_tag.book.author', 'book_author.born < ?', 1950) + ->alias(':book_tag.book.author', 'book_author'); +// LEFT JOIN `book_tag` ON `tag`.`id` = `book_tag`.`tag_id` +// LEFT JOIN `book` ON `book_tag`.`book_id` = `book`.`id` +// LEFT JOIN `author` `book_author` ON `book`.`author_id` = `book_author`.`id` +// AND (`book_author`.`born` < 1950) +``` + +Zauważ, że podczas gdy metoda `where()` dodaje warunki do klauzuli `WHERE`, metoda `joinWhere()` rozszerza warunki w klauzuli `ON` podczas łączenia tabel. diff --git a/database/pl/guide.texy b/database/pl/guide.texy new file mode 100644 index 0000000000..e3e5d284ef --- /dev/null +++ b/database/pl/guide.texy @@ -0,0 +1,216 @@ +Nette Database +************** + +.[perex] +Nette Database to wydajna i elegancka warstwa bazodanowa dla PHP z naciskiem na prostotę i inteligentne funkcje. Oferuje dwa sposoby pracy z bazą danych - [Explorer |Explorer] dla szybkiego rozwoju aplikacji, lub [Dostęp SQL |SQL way] dla bezpośredniej pracy z zapytaniami. + +<div class="grid gap-3"> +<div> + + +[Dostęp SQL |SQL way] +===================== +- Bezpieczne sparametryzowane zapytania +- Dokładna kontrola nad formą zapytań SQL +- Gdy piszesz złożone zapytania z zaawansowanymi funkcjami +- Optymalizujesz wydajność za pomocą specyficznych funkcji SQL + +</div> + +<div> + + +[Explorer |Explorer] +==================== +- Rozwijasz szybko bez pisania SQL +- Intuicyjna praca z relacjami między tabelami +- Docenisz automatyczną optymalizację zapytań +- Odpowiednie do szybkiej i wygodnej pracy z bazą danych + +</div> + +</div> + + +Instalacja +========== + +Bibliotekę można pobrać i zainstalować za pomocą narzędzia [Composer|best-practices:composer]: + +```shell +composer require nette/database +``` + + +Obsługiwane bazy danych +======================= + +Nette Database obsługuje następujące bazy danych: + +|* Serwer bazy danych |* Nazwa DSN |* Wsparcie w Explorer +|---------------------|-------------|----------------------- +| MySQL (>= 5.1) | mysql | TAK +| PostgreSQL (>= 9.0) | pgsql | TAK +| Sqlite 3 (>= 3.8) | sqlite | TAK +| Oracle | oci | - +| MS SQL (PDO_SQLSRV) | sqlsrv | TAK +| MS SQL (PDO_DBLIB) | mssql | - +| ODBC | odbc | - + + +Dwa podejścia do bazy danych +============================ + +Nette Database daje Ci wybór: możesz pisać zapytania SQL bezpośrednio (dostęp SQL) lub pozwolić na ich automatyczne generowanie (Explorer). Zobaczmy, jak oba podejścia rozwiązują te same zadania: + +[Dostęp SQL|sql way] - Zapytania SQL + +```php +// wstawienie rekordu +$database->query('INSERT INTO books', [ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// pobranie rekordów: autorzy książek +$result = $database->query(' + SELECT authors.*, COUNT(books.id) AS books_count + FROM authors + LEFT JOIN books ON authors.id = books.author_id + WHERE authors.active = 1 + GROUP BY authors.id +'); + +// wypisanie (nie jest optymalne, generuje N dodatkowych zapytań) +foreach ($result as $author) { + $books = $database->query(' + SELECT * FROM books + WHERE author_id = ? + ORDER BY published_at DESC + ', $author->id); + + echo "Autor $author->name napisał $author->books_count książek:\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +[Podejście Explorer|explorer] - automatyczne generowanie SQL + +```php +// wstawienie rekordu +$database->table('books')->insert([ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// pobranie rekordów: autorzy książek +$authors = $database->table('authors') + ->where('active', 1); + +// wypisanie (automatycznie generuje tylko 2 zoptymalizowane zapytania) +foreach ($authors as $author) { + $books = $author->related('books') + ->order('published_at DESC'); + + echo "Autor $author->name napisał {$books->count()} książek:\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +Podejście Explorer generuje i optymalizuje zapytania SQL automatycznie. W podanym przykładzie podejście SQL wygeneruje N+1 zapytań (jedno dla autorów, a następnie jedno dla książek każdego autora), podczas gdy Explorer automatycznie optymalizuje zapytania i wykonuje tylko dwa - jedno dla autorów i jedno dla wszystkich ich książek. + +Oba podejścia można dowolnie łączyć w aplikacji w zależności od potrzeb. + + +Połączenie i konfiguracja +========================= + +Aby połączyć się z bazą danych, wystarczy utworzyć instancję klasy [api:Nette\Database\Connection]: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password); +``` + +Parametr `$dsn` (data source name) jest taki sam, [jakiego używa PDO |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], np. `host=127.0.0.1;dbname=test`. W przypadku niepowodzenia rzuca wyjątek `Nette\Database\ConnectionException`. + +Jednak wygodniejszy sposób oferuje [konfiguracja aplikacji |configuration], gdzie wystarczy dodać sekcję `database`, a zostaną utworzone potrzebne obiekty oraz panel bazy danych w pasku [Tracy |tracy:]. + +```neon +database: + dsn: 'mysql:host=127.0.0.1;dbname=test' + user: root + password: password +``` + +Następnie obiekt połączenia [uzyskujemy jako usługę z kontenera DI |dependency-injection:passing-dependencies], np.: + +```php +class Model +{ + public function __construct( + // lub Nette\Database\Explorer + private Nette\Database\Connection $database, + ) { + } +} +``` + +Więcej informacji o [konfiguracji bazy danych|configuration]. + + +Ręczne tworzenie Explorera +-------------------------- + +Jeśli nie używasz kontenera Nette DI, możesz utworzyć instancję `Nette\Database\Explorer` ręcznie: + +```php +// połączenie z bazą danych +$connection = new Nette\Database\Connection('mysql:host=127.0.0.1;dbname=mydatabase', 'user', 'password'); +// magazyn dla cache, implementuje Nette\Caching\Storage, np.: +$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp/dir'); +// zajmuje się refleksją struktury bazy danych +$structure = new Nette\Database\Structure($connection, $storage); +// definiuje reguły mapowania nazw tabel, kolumn i kluczy obcych +$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); +$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +``` + + +Zarządzanie połączeniem +======================= + +Podczas tworzenia obiektu `Connection` połączenie jest nawiązywane automatycznie. Jeśli chcesz odłożyć połączenie, użyj trybu lazy - włączysz go w [konfiguracji|configuration] ustawiając `lazy`, lub w ten sposób: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password, ['lazy' => true]); +``` + +Do zarządzania połączeniem użyj metod `connect()`, `disconnect()` i `reconnect()`. +- `connect()` tworzy połączenie, jeśli jeszcze nie istnieje, przy czym może rzucić wyjątek `Nette\Database\ConnectionException`. +- `disconnect()` rozłącza bieżące połączenie z bazą danych. +- `reconnect()` wykonuje rozłączenie i ponowne połączenie z bazą danych. Ta metoda również może rzucić wyjątek `Nette\Database\ConnectionException`. + +Ponadto możesz śledzić zdarzenia związane z połączeniem za pomocą zdarzenia `onConnect`, które jest tablicą callbacków wywoływanych po nawiązaniu połączenia z bazą danych. + +```php +// wykonuje się po połączeniu z bazą danych +$database->onConnect[] = function($database) { + echo "Połączono z bazą danych"; +}; +``` + + +Pasek Debugowania Tracy +======================= + +Jeśli używasz [Tracy |tracy:], panel Database w pasku Debugowania aktywuje się automatycznie, wyświetlając wszystkie wykonane zapytania, ich parametry, czas wykonania oraz miejsce w kodzie, gdzie zostały wywołane. + +[* db-panel.webp *] diff --git a/database/pl/mapping.texy b/database/pl/mapping.texy new file mode 100644 index 0000000000..4eea807bb4 --- /dev/null +++ b/database/pl/mapping.texy @@ -0,0 +1,55 @@ +Konwersja typów +*************** + +.[perex] +Nette Database automatycznie konwertuje wartości zwrócone z bazy danych na odpowiednie typy PHP. + + +Data i czas +----------- + +Dane czasowe są konwertowane na obiekty `Nette\Utils\DateTime`. Jeśli chcesz, aby dane czasowe były konwertowane na niemutowalne obiekty `Nette\Database\DateTime`, ustaw w [konfiguracji|configuration] opcję `newDateTime` na true. + +```php +$row = $database->fetch('SELECT created_at FROM articles'); +echo $row->created_at instanceof DateTime; // true +echo $row->created_at->format('j. n. Y'); +``` + +W przypadku MySQL konwertuje typ danych `TIME` na obiekty `DateInterval`. + + +Wartości logiczne +----------------- + +Wartości logiczne są automatycznie konwertowane na `true` lub `false`. W MySQL konwertuje się `TINYINT(1)`, jeśli ustawimy w [konfiguracji|configuration] `convertBoolean`. + +```php +$row = $database->fetch('SELECT is_published FROM articles'); +echo gettype($row->is_published); // 'boolean' +``` + + +Wartości liczbowe +----------------- + +Wartości liczbowe są konwertowane na `int` lub `float` w zależności od typu kolumny w bazie danych: + +```php +$row = $database->fetch('SELECT id, price FROM products'); +echo gettype($row->id); // integer +echo gettype($row->price); // float +``` + + +Własna normalizacja +------------------- + +Za pomocą metody `setRowNormalizer(?callable $normalizer)` możesz ustawić własną funkcję do transformacji wierszy z bazy danych. Jest to przydatne na przykład do automatycznej konwersji typów danych. + +```php +$database->setRowNormalizer(function(array $row, ResultSet $resultSet): array { + // tutaj następuje konwersja typów + return $row; +}); +``` diff --git a/database/pl/reflection.texy b/database/pl/reflection.texy new file mode 100644 index 0000000000..0f09769793 --- /dev/null +++ b/database/pl/reflection.texy @@ -0,0 +1,125 @@ +Refleksja struktury +******************* + +.{data-version:3.2.1} +Nette Database dostarcza narzędzi do introspekcji struktury bazy danych za pomocą klasy [api:Nette\Database\Reflection]. Umożliwia ona uzyskiwanie informacji o tabelach, kolumnach, indeksach i kluczach obcych. Refleksję można wykorzystać do generowania schematów, tworzenia elastycznych aplikacji pracujących z bazą danych lub ogólnych narzędzi bazodanowych. + +Obiekt refleksji uzyskujemy z instancji połączenia z bazą danych: + +```php +$reflection = $database->getReflection(); +``` + + +Pobieranie tabel +---------------- + +Właściwość readonly `$reflection->tables` zawiera tablicę asocjacyjną wszystkich tabel w bazie danych: + +```php +// Wypisanie nazw wszystkich tabel +foreach ($reflection->tables as $name => $table) { + echo $name . "\n"; +} +``` + +Dostępne są jeszcze dwie metody: + +```php +// Sprawdzenie istnienia tabeli +if ($reflection->hasTable('users')) { + echo "Tabela users istnieje"; +} + +// Zwraca obiekt tabeli; jeśli nie istnieje, rzuca wyjątek +$table = $reflection->getTable('users'); +``` + + +Informacje o tabeli +------------------- + +Tabela jest reprezentowana przez obiekt [Table|api:Nette\Database\Reflection\Table], który udostępnia następujące właściwości readonly: + +- `$name: string` – nazwa tabeli +- `$view: bool` – czy jest to widok +- `$fullName: ?string` – pełna nazwa tabeli wraz ze schematem (jeśli istnieje) +- `$columns: array<string, Column>` – tablica asocjacyjna kolumn tabeli +- `$indexes: Index[]` – tablica indeksów tabeli +- `$primaryKey: ?Index` – klucz podstawowy tabeli lub null +- `$foreignKeys: ForeignKey[]` – tablica kluczy obcych tabeli + + +Kolumny +------- + +Właściwość `columns` tabeli udostępnia tablicę asocjacyjną kolumn, gdzie kluczem jest nazwa kolumny, a wartością instancja [Column|api:Nette\Database\Reflection\Column] z następującymi właściwościami: + +- `$name: string` – nazwa kolumny +- `$table: ?Table` – referencja do tabeli kolumny +- `$nativeType: string` – natywny typ bazodanowy +- `$size: ?int` – rozmiar/długość typu +- `$nullable: bool` – czy kolumna może zawierać NULL +- `$default: mixed` – domyślna wartość kolumny +- `$autoIncrement: bool` – czy kolumna jest auto-increment +- `$primary: bool` – czy jest częścią klucza podstawowego +- `$vendor: array` – dodatkowe metadane specyficzne dla danego systemu bazodanowego + +```php +foreach ($table->columns as $name => $column) { + echo "Kolumna: $name\n"; + echo "Typ: {$column->nativeType}\n"; + echo "Nullable: " . ($column->nullable ? 'Tak' : 'Nie') . "\n"; +} +``` + + +Indeksy +------- + +Właściwość `indexes` tabeli udostępnia tablicę indeksów, gdzie każdy indeks jest instancją [Index|api:Nette\Database\Reflection\Index] z następującymi właściwościami: + +- `$columns: Column[]` – tablica kolumn tworzących indeks +- `$unique: bool` – czy indeks jest unikalny +- `$primary: bool` – czy jest to klucz podstawowy +- `$name: ?string` – nazwa indeksu + +Klucz podstawowy tabeli można uzyskać za pomocą właściwości `primaryKey`, która zwraca obiekt `Index` lub `null` w przypadku, gdy tabela nie ma klucza podstawowego. + +```php +// Wypisanie indeksów +foreach ($table->indexes as $index) { + $columns = implode(', ', array_map(fn($col) => $col->name, $index->columns)); + echo "Indeks" . ($index->name ? " {$index->name}" : '') . ":\n"; + echo " Kolumny: $columns\n"; + echo " Unikalny: " . ($index->unique ? 'Tak' : 'Nie') . "\n"; +} + +// Wypisanie klucza podstawowego +if ($primaryKey = $table->primaryKey) { + $columns = implode(', ', array_map(fn($col) => $col->name, $primaryKey->columns)); + echo "Klucz podstawowy: $columns\n"; +} +``` + + +Klucze obce +----------- + +Właściwość `foreignKeys` tabeli udostępnia tablicę kluczy obcych, gdzie każdy klucz obcy jest instancją [ForeignKey|api:Nette\Database\Reflection\ForeignKey] z następującymi właściwościami: + +- `$foreignTable: Table` – tabela referencyjna +- `$localColumns: Column[]` – tablica kolumn lokalnych +- `$foreignColumns: Column[]` – tablica kolumn referencyjnych +- `$name: ?string` – nazwa klucza obcego + +```php +// Wypisanie kluczy obcych +foreach ($table->foreignKeys as $fk) { + $localCols = implode(', ', array_map(fn($col) => $col->name, $fk->localColumns)); + $foreignCols = implode(', ', array_map(fn($col) => $col->name, $fk->foreignColumns)); + + echo "FK" . ($fk->name ? " {$fk->name}" : '') . ":\n"; + echo " $localCols -> {$fk->foreignTable->name}($foreignCols)\n"; +} +``` diff --git a/database/pl/security.texy b/database/pl/security.texy new file mode 100644 index 0000000000..2a6aba415e --- /dev/null +++ b/database/pl/security.texy @@ -0,0 +1,185 @@ +Ryzyka bezpieczeństwa +********************* + +<div class=perex> + +Baza danych często zawiera wrażliwe dane i umożliwia wykonywanie niebezpiecznych operacji. Dla bezpiecznej pracy z Nette Database kluczowe jest: + +- Zrozumienie różnicy między bezpiecznym a niebezpiecznym API +- Używanie sparametryzowanych zapytań +- Poprawna walidacja danych wejściowych + +</div> + + +Co to jest SQL Injection? +========================= + +SQL injection jest najpoważniejszym ryzykiem bezpieczeństwa podczas pracy z bazą danych. Powstaje, gdy nieprzetworzone dane wejściowe od użytkownika stają się częścią zapytania SQL. Atakujący może wstrzyknąć własne polecenia SQL i tym samym: +- Uzyskać nieautoryzowany dostęp do danych +- Zmodyfikować lub usunąć dane w bazie danych +- Ominąć uwierzytelnianie + +```php +// ❌ NIEBEZPIECZNY KOD - podatny na SQL injection +$database->query("SELECT * FROM users WHERE name = '$_GET[name]'"); + +// Atakujący może podać na przykład wartość: ' OR '1'='1 +// Wynikowe zapytanie będzie wtedy: SELECT * FROM users WHERE name = '' OR '1'='1' +// Co zwróci wszystkich użytkowników +``` + +To samo dotyczy Database Explorer: + +```php +// ❌ NIEBEZPIECZNY KOD - podatny na SQL injection +$table->where('name = ' . $_GET['name']); +$table->where("name = '$_GET[name]'"); +``` + + +Zapytania sparametryzowane +========================== + +Podstawową obroną przed SQL injection są zapytania sparametryzowane. Nette Database oferuje kilka sposobów ich użycia. + +Najprostszym sposobem jest użycie **symboli zastępczych (placeholderów) w postaci znaków zapytania**: + +```php +// ✅ Bezpieczne zapytanie sparametryzowane +$database->query('SELECT * FROM users WHERE name = ?', $name); + +// ✅ Bezpieczny warunek w Explorerze +$table->where('name = ?', $name); +``` + +Dotyczy to wszystkich innych metod w [Database Explorer|explorer], które umożliwiają wstawianie wyrażeń z symbolami zastępczymi i parametrami. + +Dla poleceń INSERT, UPDATE lub klauzuli WHERE możemy przekazać wartości w tablicy: + +```php +// ✅ Bezpieczny INSERT +$database->query('INSERT INTO users', [ + 'name' => $name, + 'email' => $email, +]); + +// ✅ Bezpieczny INSERT w Explorerze +$table->insert([ + 'name' => $name, + 'email' => $email, +]); +``` + + +Walidacja wartości parametrów +============================= + +Zapytania sparametryzowane są podstawowym elementem bezpiecznej pracy z bazą danych. Jednak wartości, które do nich wstawiamy, muszą przejść przez kilka poziomów kontroli: + + +Kontrola typów +-------------- + +**Najważniejsze jest zapewnienie poprawnego typu danych parametrów** - jest to warunek konieczny do bezpiecznego używania Nette Database. Baza danych zakłada, że wszystkie dane wejściowe mają poprawny typ danych odpowiadający danej kolumnie. + +Na przykład, jeśli `$name` w poprzednich przykładach byłoby niespodziewanie tablicą zamiast stringiem, Nette Database próbowałoby wstawić wszystkie jej elementy do zapytania SQL, co doprowadziłoby do błędu. Dlatego **nigdy nie używaj** niezweryfikowanych danych z `$_GET`, `$_POST` lub `$_COOKIE` bezpośrednio w zapytaniach bazodanowych. + + +Kontrola formatu +---------------- + +Na drugim poziomie kontrolujemy format danych - na przykład, czy ciągi znaków są w kodowaniu UTF-8 i ich długość odpowiada definicji kolumny, lub czy wartości liczbowe mieszczą się w dozwolonym zakresie dla danego typu danych kolumny. + +Na tym poziomie walidacji możemy częściowo polegać na samej bazie danych - wiele baz danych odrzuci nieprawidłowe dane. Jednak zachowanie może się różnić, niektóre mogą cicho skrócić długie ciągi znaków lub przyciąć liczby spoza zakresu. + + +Kontrola domenowa +----------------- + +Trzeci poziom stanowią kontrole logiczne specyficzne dla Twojej aplikacji. Na przykład weryfikacja, czy wartości z pól wyboru odpowiadają oferowanym opcjom, czy liczby mieszczą się w oczekiwanym zakresie (np. wiek 0-150 lat) lub czy wzajemne zależności między wartościami mają sens. + + +Zalecane sposoby walidacji +-------------------------- + +- Używaj [Formularzy Nette|forms:], które automatycznie zapewniają poprawną walidację wszystkich danych wejściowych +- Używaj [Presenterów|application:] i podawaj typy danych dla parametrów w metodach `action*()` i `render*()` +- Lub zaimplementuj własną warstwę walidacji za pomocą standardowych narzędzi PHP, takich jak `filter_var()` + + +Bezpieczna praca z kolumnami +============================ + +W poprzedniej sekcji pokazaliśmy, jak poprawnie walidować wartości parametrów. Jednak przy użyciu tablic w zapytaniach SQL musimy poświęcić taką samą uwagę ich kluczom. + +```php +// ❌ NIEBEZPIECZNY KOD - klucze w tablicy nie są sprawdzane +$database->query('INSERT INTO users', $_POST); +``` + +W przypadku poleceń INSERT i UPDATE jest to fundamentalny błąd bezpieczeństwa - atakujący może wstawić lub zmienić dowolną kolumnę w bazie danych. Mógłby na przykład ustawić `is_admin = 1` lub wstawić dowolne dane do wrażliwych kolumn (tzw. Mass Assignment Vulnerability). + +W warunkach WHERE jest to jeszcze bardziej niebezpieczne, ponieważ mogą zawierać operatory: + +```php +// ❌ NIEBEZPIECZNY KOD - klucze w tablicy nie są sprawdzane +$_POST['salary >'] = 100000; +$database->query('SELECT * FROM users WHERE', $_POST); +// wykonuje zapytanie WHERE (`salary` > 100000) +``` + +Atakujący może wykorzystać to podejście do systematycznego odkrywania wynagrodzeń pracowników. Zacznie na przykład od zapytania o wynagrodzenia powyżej 100 000, następnie poniżej 50 000 i stopniowo zawężając zakres, może odkryć przybliżone wynagrodzenia wszystkich pracowników. Ten typ ataku nazywa się SQL enumeration. + +Metody `where()` i `whereOr()` są jeszcze [znacznie bardziej elastyczne |explorer#where] i obsługują w kluczach i wartościach wyrażenia SQL, w tym operatory i funkcje. Daje to atakującemu możliwość przeprowadzenia SQL injection: + +```php +// ❌ NIEBEZPIECZNY KOD - atakujący może wstrzyknąć własny SQL +$_POST = ['0) UNION SELECT name, salary FROM users WHERE (1']; +$table->where($_POST); +// wykonuje zapytanie WHERE (0) UNION SELECT name, salary FROM users WHERE (1) +``` + +Ten atak kończy pierwotny warunek za pomocą `0)`, dołącza własne `SELECT` za pomocą `UNION`, aby uzyskać wrażliwe dane z tabeli `users` i zamyka składniowo poprawne zapytanie za pomocą `WHERE (1)`. + + +Biała lista kolumn +------------------ + +Do bezpiecznej pracy z nazwami kolumn potrzebujemy mechanizmu, który zapewni, że użytkownik może pracować tylko z dozwolonymi kolumnami i nie może dodać własnych. Moglibyśmy próbować wykrywać i blokować niebezpieczne nazwy kolumn (czarna lista), ale to podejście jest zawodne - atakujący zawsze może wymyślić nowy sposób zapisu niebezpiecznej nazwy kolumny, którego nie przewidzieliśmy. + +Dlatego znacznie bezpieczniejsze jest odwrócenie logiki i zdefiniowanie jawnej listy dozwolonych kolumn (biała lista): + +```php +// Kolumny, które użytkownik może edytować +$allowedColumns = ['name', 'email', 'active']; + +// Usuwamy wszystkie niedozwolone kolumny z danych wejściowych +$filteredData = array_intersect_key($userData, array_flip($allowedColumns)); // array_flip for PHP < 8.1 + +// ✅ Teraz możemy bezpiecznie używać w zapytaniach, na przykład: +$database->query('INSERT INTO users', $filteredData); +$table->update($filteredData); +$table->where($filteredData); +``` + + +Dynamiczne identyfikatory +========================= + +Dla dynamicznych nazw tabel i kolumn użyj symbolu zastępczego `?name`. Zapewni on poprawne escapowanie identyfikatorów zgodnie ze składnią danej bazy danych (np. za pomocą odwrotnych apostrofów w MySQL): + +```php +// ✅ Bezpieczne użycie zaufanych identyfikatorów +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name', $column, $table); +// Wynik w MySQL: SELECT `name` FROM `users` +``` + +Ważne: symbol `?name` używaj tylko dla zaufanych wartości zdefiniowanych w kodzie aplikacji. Dla wartości od użytkownika użyj ponownie [białej listy |#Biała lista kolumn]. W przeciwnym razie narażasz się na ryzyko bezpieczeństwa: + +```php +// ❌ NIEBEZPIECZNE - nigdy nie używaj danych wejściowych od użytkownika +$database->query('SELECT ?name FROM users', $_GET['column']); +``` diff --git a/database/pl/sql-way.texy b/database/pl/sql-way.texy new file mode 100644 index 0000000000..860a12a8f2 --- /dev/null +++ b/database/pl/sql-way.texy @@ -0,0 +1,513 @@ +Dostęp SQL +********** + +.[perex] +Nette Database oferuje dwie ścieżki: możesz pisać zapytania SQL samodzielnie (dostęp SQL) lub pozwolić na ich automatyczne generowanie (zobacz [Explorer |explorer]). Dostęp SQL daje Ci pełną kontrolę nad zapytaniami, jednocześnie zapewniając ich bezpieczne tworzenie. + +.[note] +Szczegóły dotyczące połączenia i konfiguracji bazy danych znajdziesz w rozdziale [Połączenie i konfiguracja |guide#Połączenie i konfiguracja]. + + +Podstawowe zapytania +==================== + +Do wykonywania zapytań do bazy danych służy metoda `query()`. Zwraca ona obiekt [ResultSet |api:Nette\Database\ResultSet], który reprezentuje wynik zapytania. W przypadku niepowodzenia metoda [rzuci wyjątek|exceptions]. Wynik zapytania możemy przeglądać za pomocą pętli `foreach` lub użyć jednej z [funkcji pomocniczych |#Pobieranie danych]. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; +} +``` + +Do bezpiecznego wstawiania wartości do zapytań SQL używamy zapytań sparametryzowanych. Nette Database czyni je maksymalnie prostymi - wystarczy dodać przecinek i wartość po zapytaniu SQL: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Przy większej liczbie parametrów masz dwie możliwości zapisu. Możesz albo "przeplatać" zapytanie SQL parametrami: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age); +``` + +Albo napisać najpierw całe zapytanie SQL, a następnie dołączyć wszystkie parametry: + +```php +$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age); +``` + + +Ochrona przed SQL injection +=========================== + +Dlaczego ważne jest używanie zapytań sparametryzowanych? Ponieważ chronią Cię przed atakiem zwanym SQL injection, w którym atakujący mógłby podrzucić własne polecenia SQL i tym samym uzyskać lub uszkodzić dane w bazie danych. + +.[warning] +**Nigdy nie wstawiaj zmiennych bezpośrednio do zapytania SQL!** Zawsze używaj zapytań sparametryzowanych, które chronią Cię przed SQL injection. + +```php +// ❌ NIEBEZPIECZNY KOD - podatny na SQL injection +$database->query("SELECT * FROM users WHERE name = '$name'"); + +// ✅ Bezpieczne zapytanie sparametryzowane +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Zapoznaj się z [możliwymi ryzykami bezpieczeństwa |security]. + + +Techniki zapytań +================ + + +Warunki WHERE +------------- + +Warunki WHERE możesz zapisać jako tablicę asocjacyjną, gdzie klucze to nazwy kolumn, a wartości to dane do porównania. Nette Database automatycznie wybierze najodpowiedniejszy operator SQL w zależności od typu wartości. + +```php +$database->query('SELECT * FROM users WHERE', [ + 'name' => 'John', + 'active' => true, +]); +// WHERE `name` = 'John' AND `active` = 1 +``` + +W kluczu możesz również jawnie określić operator do porównania: + +```php +$database->query('SELECT * FROM users WHERE', [ + 'age >' => 25, // użyje operatora > + 'name LIKE' => '%John%', // użyje operatora LIKE + 'email NOT LIKE' => '%example.com%', // użyje operatora NOT LIKE +]); +// WHERE `age` > 25 AND `name` LIKE '%John%' AND `email` NOT LIKE '%example.com%' +``` + +Nette automatycznie obsługuje specjalne przypadki, takie jak wartości `null` lub tablice. + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name' => 'Laptop', // użyje operatora = + 'category_id' => [1, 2, 3], // użyje IN + 'description' => null, // użyje IS NULL +]); +// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL +``` + +Dla warunków negatywnych użyj operatora `NOT`: + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name NOT' => 'Laptop', // użyje operatora <> + 'category_id NOT' => [1, 2, 3], // użyje NOT IN + 'description NOT' => null, // użyje IS NOT NULL + 'id' => [], // zostanie pominięte +]); +// WHERE `name` <> 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL +``` + +Do łączenia warunków używa się operatora `AND`. Można to zmienić za pomocą [symbolu zastępczego ?or |#Wskazówki dotyczące tworzenia SQL]. + + +Reguły ORDER BY +--------------- + +Sortowanie `ORDER BY` można zapisać za pomocą tablicy. W kluczach podajemy kolumny, a wartością będzie boolean określający, czy sortować rosnąco: + +```php +$database->query('SELECT id FROM author ORDER BY', [ + 'id' => true, // rosnąco + 'name' => false, // malejąco +]); +// SELECT id FROM author ORDER BY `id`, `name` DESC +``` + + +Wstawianie danych (INSERT) +-------------------------- + +Do wstawiania rekordów używa się polecenia SQL `INSERT`. + +```php +$values = [ + 'name' => 'John Doe', + 'email' => 'john@example.com', +]; +$database->query('INSERT INTO users ?', $values); +$userId = $database->getInsertId(); +``` + +Metoda `getInsertId()` zwraca ID ostatnio wstawionego wiersza. W niektórych bazach danych (np. PostgreSQL) konieczne jest podanie jako parametru nazwy sekwencji, z której ma być generowane ID za pomocą `$database->getInsertId($sequenceId)`. + +Jako parametry możemy przekazywać również [#wartości specjalne], takie jak pliki, obiekty DateTime lub typy wyliczeniowe. + +Wstawienie wielu rekordów naraz: + +```php +$database->query('INSERT INTO users ?', [ + ['name' => 'User 1', 'email' => 'user1@mail.com'], + ['name' => 'User 2', 'email' => 'user2@mail.com'], +]); +``` + +Wielokrotny INSERT jest znacznie szybszy, ponieważ wykonuje się jedno zapytanie do bazy danych, zamiast wielu pojedynczych. + +**Ostrzeżenie dotyczące bezpieczeństwa:** Nigdy nie używaj jako `$values` niezweryfikowanych danych. Zapoznaj się z [możliwymi ryzykami |security#Bezpieczna praca z kolumnami]. + + +Aktualizacja danych (UPDATE) +---------------------------- + +Do aktualizacji rekordów używa się polecenia SQL `UPDATE`. + +```php +// Aktualizacja jednego rekordu +$values = [ + 'name' => 'John Smith', +]; +$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1); +``` + +Liczbę zmienionych wierszy zwraca `$result->getRowCount()`. + +Dla UPDATE możemy wykorzystać operatory `+=` i `-=`: + +```php +$database->query('UPDATE users SET ? WHERE id = ?', [ + 'login_count+=' => 1, // inkrementacja login_count +], 1); +``` + +Przykład wstawienia lub aktualizacji rekordu, jeśli już istnieje. Użyjemy techniki `ON DUPLICATE KEY UPDATE`: + +```php +$values = [ + 'name' => $name, + 'year' => $year, +]; +$database->query('INSERT INTO users ? ON DUPLICATE KEY UPDATE ?', + $values + ['id' => $id], + $values, +); +// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) +// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 +``` + +Zauważ, że Nette Database rozpoznaje, w jakim kontekście polecenia SQL wstawiamy parametr z tablicą i odpowiednio tworzy z niego kod SQL. Tak więc z pierwszej tablicy utworzył `(id, name, year) VALUES (123, 'Jim', 1978)`, podczas gdy drugą przekształcił do postaci `name = 'Jim', year = 1978`. Szczegółowiej omówimy to w części [#Wskazówki dotyczące tworzenia SQL]. + + +Usuwanie danych (DELETE) +------------------------ + +Do usuwania rekordów używa się polecenia SQL `DELETE`. Przykład z uzyskaniem liczby usuniętych wierszy: + +```php +$count = $database->query('DELETE FROM users WHERE id = ?', 1) + ->getRowCount(); +``` + + +Wskazówki dotyczące tworzenia SQL +--------------------------------- + +Wskazówka (hint) to specjalny symbol zastępczy w zapytaniu SQL, który mówi, jak wartość parametru ma zostać przepisana na wyrażenie SQL: + +| Wskazówka | Opis | Używa się automatycznie +|-----------|-------------------------------------------------|----------------------------- +| `?name` | używa do wstawienia nazwy tabeli lub kolumny | - +| `?values` | generuje `(key, ...) VALUES (value, ...)` | `INSERT ... ?`, `REPLACE ... ?` +| `?set` | generuje przypisanie `key = value, ...` | `SET ?`, `KEY UPDATE ?` +| `?and` | łączy warunki w tablicy operatorem `AND` | `WHERE ?`, `HAVING ?` +| `?or` | łączy warunki w tablicy operatorem `OR` | - +| `?order` | generuje klauzulę `ORDER BY` | `ORDER BY ?`, `GROUP BY ?` + +Do dynamicznego wstawiania nazw tabel i kolumn do zapytania służy symbol zastępczy `?name`. Nette Database zadba o poprawne przetworzenie identyfikatorów zgodnie z konwencjami danej bazy danych (np. zamknięcie w odwrotnych apostrofach w MySQL). + +```php +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table); +// SELECT `name` FROM `users` WHERE id = 1 (w MySQL) +``` + +**Ostrzeżenie:** symbol `?name` używaj tylko dla nazw tabel i kolumn z zweryfikowanych danych wejściowych, w przeciwnym razie narażasz się na [ryzyko bezpieczeństwa |security#Dynamiczne identyfikatory]. + +Pozostałych wskazówek zwykle nie trzeba podawać, ponieważ Nette używa inteligentnej autodetekcji podczas składania zapytania SQL (zobacz trzecią kolumnę tabeli). Ale możesz jej użyć na przykład w sytuacji, gdy chcesz połączyć warunki za pomocą `OR` zamiast `AND`: + +```php +$database->query('SELECT * FROM users WHERE ?or', [ + 'name' => 'John', + 'email' => 'john@example.com', +]); +// SELECT * FROM users WHERE `name` = 'John' OR `email` = 'john@example.com' +``` + + +Wartości specjalne +------------------ + +Oprócz zwykłych typów skalarnych (string, int, bool) możesz przekazywać jako parametry również wartości specjalne: + +- pliki: `fopen('image.gif', 'r')` wstawia binarną zawartość pliku +- data i czas: obiekty `DateTime` są konwertowane na format bazodanowy +- typy wyliczeniowe: instancje `enum` są konwertowane na ich wartość +- literały SQL: utworzone za pomocą `Connection::literal('NOW()')` są wstawiane bezpośrednio do zapytania + +```php +$database->query('INSERT INTO articles ?', [ + 'title' => 'My Article', + 'published_at' => new DateTime, + 'content' => fopen('image.png', 'r'), + 'state' => Status::Draft, +]); +``` + +W bazach danych, które nie mają natywnego wsparcia dla typu danych `datetime` (jak SQLite i Oracle), `DateTime` jest konwertowany na wartość określoną w [konfiguracji bazy danych|configuration] za pomocą opcji `formatDateTime` (domyślna wartość to `U` - unix timestamp). + + +Literały SQL +------------ + +W niektórych przypadkach musisz podać jako wartość bezpośrednio kod SQL, który nie powinien być traktowany jako ciąg znaków i escapowany. Do tego służą obiekty klasy `Nette\Database\SqlLiteral`. Tworzy je metoda `Connection::literal()`. + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + 'year >' => $database::literal('YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) +``` + +Lub alternatywnie: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) +``` + +Literały SQL mogą zawierać parametry: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > ? AND year < ?', $min, $max), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) +``` + +Dzięki czemu możemy tworzyć ciekawe kombinacje: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('?or', [ + 'active' => true, + 'role' => $role, + ]), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') +``` + + +Pobieranie danych +================= + + +Skróty dla zapytań SELECT +------------------------- + +Aby uprościć pobieranie danych, `Connection` oferuje kilka skrótów, które łączą wywołanie `query()` z następującym `fetch*()`. Metody te przyjmują te same parametry co `query()`, czyli zapytanie SQL i opcjonalne parametry. Pełny opis metod `fetch*()` znajdziesz [poniżej |#fetch]. + +| `fetch($sql, ...$params): ?Row` | Wykonuje zapytanie i zwraca pierwszy wiersz jako obiekt `Row` +| `fetchAll($sql, ...$params): array` | Wykonuje zapytanie i zwraca wszystkie wiersze jako tablicę obiektów `Row` +| `fetchPairs($sql, ...$params): array` | Wykonuje zapytanie i zwraca tablicę asocjacyjną, gdzie pierwsza kolumna reprezentuje klucz, a druga wartość +| `fetchField($sql, ...$params): mixed` | Wykonuje zapytanie i zwraca wartość pierwszej komórki z pierwszego wiersza +| `fetchList($sql, ...$params): ?array` | Wykonuje zapytanie i zwraca pierwszy wiersz jako tablicę indeksowaną + +Przykład: + +```php +// fetchField() - zwraca wartość pierwszej komórki +$count = $database->query('SELECT COUNT(*) FROM articles') + ->fetchField(); +``` + + +`foreach` - iteracja po wierszach +--------------------------------- + +Po wykonaniu zapytania zwracany jest obiekt [ResultSet|api:Nette\Database\ResultSet], który umożliwia przeglądanie wyników na kilka sposobów. Najłatwiejszym sposobem wykonania zapytania i pobrania wierszy jest iteracja w pętli `foreach`. Ten sposób jest najbardziej oszczędny pod względem pamięci, ponieważ zwraca dane stopniowo i nie przechowuje ich wszystkich w pamięci naraz. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; + // ... +} +``` + +.[note] +`ResultSet` można iterować tylko raz. Jeśli potrzebujesz iterować wielokrotnie, musisz najpierw załadować dane do tablicy, na przykład za pomocą metody `fetchAll()`. + + +fetch(): ?Row .[method] +----------------------- + +Zwraca wiersz jako obiekt `Row`. Jeśli nie ma już więcej wierszy, zwraca `null`. Przesuwa wewnętrzny wskaźnik na następny wiersz. + +```php +$result = $database->query('SELECT * FROM users'); +$row = $result->fetch(); // wczytuje pierwszy wiersz +if ($row) { + echo $row->name; +} +``` + + +fetchAll(): array .[method] +--------------------------- + +Zwraca wszystkie pozostałe wiersze z `ResultSet` jako tablicę obiektów `Row`. + +```php +$result = $database->query('SELECT * FROM users'); +$rows = $result->fetchAll(); // wczytuje wszystkie wiersze +foreach ($rows as $row) { + echo $row->name; +} +``` + + +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Zwraca wyniki jako tablicę asocjacyjną. Pierwszy argument określa nazwę kolumny, która zostanie użyta jako klucz w tablicy, drugi argument określa nazwę kolumny, która zostanie użyta jako wartość: + +```php +$result = $database->query('SELECT id, name FROM users'); +$names = $result->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] +``` + +Jeśli podamy tylko pierwszy parametr, wartością będzie cały wiersz, czyli obiekt `Row`: + +```php +$rows = $result->fetchPairs('id'); +// [1 => Row(id: 1, name: 'John'), 2 => Row(id: 2, name: 'Jane'), ...] +``` + +W przypadku zduplikowanych kluczy użyta zostanie wartość z ostatniego wiersza. Przy użyciu `null` jako klucza, tablica będzie indeksowana numerycznie od zera (wtedy nie dochodzi do kolizji): + +```php +$names = $result->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] +``` + + +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Alternatywnie możesz podać jako parametr callback, który dla każdego wiersza zwróci albo samą wartość, albo parę klucz-wartość. + +```php +$result = $database->query('SELECT * FROM users'); +$items = $result->fetchPairs(fn($row) => "$row->id - $row->name"); +// ['1 - John', '2 - Jane', ...] + +// Callback może również zwracać tablicę z parą klucz & wartość: +$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]); +// ['John' => 46, 'Jane' => 21, ...] +``` + + +fetchField(): mixed .[method] +----------------------------- + +Zwraca wartość pierwszej komórki z bieżącego wiersza. Jeśli nie ma już więcej wierszy, zwraca `null`. Przesuwa wewnętrzny wskaźnik na następny wiersz. + +```php +$result = $database->query('SELECT name FROM users'); +$name = $result->fetchField(); // wczytuje imię z pierwszego wiersza +``` + + +fetchList(): ?array .[method] +----------------------------- + +Zwraca wiersz jako tablicę indeksowaną. Jeśli nie ma już więcej wierszy, zwraca `null`. Przesuwa wewnętrzny wskaźnik na następny wiersz. + +```php +$result = $database->query('SELECT name, email FROM users'); +$row = $result->fetchList(); // ['John', 'john@example.com'] +``` + + +getRowCount(): ?int .[method] +----------------------------- + +Zwraca liczbę zmienionych wierszy przez ostatnie zapytanie `UPDATE` lub `DELETE`. Dla `SELECT` jest to liczba zwróconych wierszy, ale ta może nie być znana - w takim przypadku metoda zwróci `null`. + + +getColumnCount(): ?int .[method] +-------------------------------- + +Zwraca liczbę kolumn w `ResultSet`. + + +Informacje o zapytaniach +======================== + +Do celów debugowania możemy uzyskać informacje o ostatnim wykonanym zapytaniu: + +```php +echo $database->getLastQueryString(); // wypisuje zapytanie SQL + +$result = $database->query('SELECT * FROM articles'); +echo $result->getQueryString(); // wypisuje zapytanie SQL +echo $result->getTime(); // wypisuje czas wykonania w sekundach +``` + +Do wyświetlenia wyniku jako tabeli HTML można użyć: + +```php +$result = $database->query('SELECT * FROM articles'); +$result->dump(); +``` + +ResultSet oferuje informacje o typach kolumn: + +```php +$result = $database->query('SELECT * FROM articles'); +$types = $result->getColumnTypes(); + +foreach ($types as $column => $type) { + echo "$column jest typu $type->type"; // np. 'id jest typu int' +} +``` + + +Logowanie zapytań +----------------- + +Możemy zaimplementować własne logowanie zapytań. Zdarzenie `onQuery` jest tablicą callbacków, które są wywoływane po każdym wykonanym zapytaniu: + +```php +$database->onQuery[] = function ($database, $result) use ($logger) { + $logger->info('Zapytanie: ' . $result->getQueryString()); + $logger->info('Czas: ' . $result->getTime()); + + if ($result->getRowCount() > 1000) { + $logger->warning('Duży zestaw wyników: ' . $result->getRowCount() . ' wierszy'); + } +}; +``` diff --git a/database/pl/transactions.texy b/database/pl/transactions.texy new file mode 100644 index 0000000000..37e8dde5cb --- /dev/null +++ b/database/pl/transactions.texy @@ -0,0 +1,43 @@ +Transakcje +********** + +.[perex] +Transakcje gwarantują, że albo wszystkie operacje w ramach transakcji zostaną wykonane, albo żadna z nich nie zostanie wykonana. Są one przydatne do zapewnienia spójności danych podczas bardziej złożonych operacji. + +Najprostszy sposób użycia transakcji wygląda następująco: + +```php +$database->beginTransaction(); +try { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); + $database->commit(); +} catch (\Exception $e) { + $database->rollBack(); + throw $e; +} +``` + +Znacznie bardziej elegancko można to samo zapisać za pomocą metody `transaction()`. Jako parametr przyjmuje ona callback, który wykonuje w transakcji. Jeśli callback przebiegnie bez wyjątku, transakcja jest automatycznie zatwierdzana. Jeśli wystąpi wyjątek, transakcja jest anulowana (rollback), a wyjątek jest propagowany dalej. + +```php +$database->transaction(function ($database) use ($id) { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); +}); +``` + +Metoda `transaction()` może również zwracać wartości: + +```php +$count = $database->transaction(function ($database) { + $result = $database->query('UPDATE users SET active = ?', true); + return $result->getRowCount(); // zwraca liczbę zaktualizowanych wierszy +}); +``` diff --git a/database/pt/@home.texy b/database/pt/@home.texy index 3010f0db2a..dc4308eeae 100644 --- a/database/pt/@home.texy +++ b/database/pt/@home.texy @@ -1,20 +1,21 @@ -Servidores Suportados -===================== +Bancos de dados suportados +========================== -Estes servidores de banco de dados são suportados: +Nette suporta os seguintes bancos de dados: -|* Servidor de banco de dados |* Nome DSN |* Suporte do núcleo |* Suporte do explorador -| MySQL (>= 5.1) | mysql | SIM | SIM -| PostgreSQL (>= 9.0) | pgsql | SIM | SIM -| Sqlite 3 (>= 3.8) | sqlite | SIM | SIM -| Oracle | oci | YES | - -| MS SQL (PDO_SQLSRV) | sqlsrv | SIM | SIM -| MS SQL (PDO_DBLIB) | mssql | YES | - -| ODBC | odbc | YES | - +|* Servidor de banco de dados |* Nome DSN |* Suporte no Core |* Suporte no Explorer +| MySQL (>= 5.1) | mysql | SIM | SIM +| PostgreSQL (>= 9.0) | pgsql | SIM | SIM +| Sqlite 3 (>= 3.8) | sqlite | SIM | SIM +| Oracle | oci | SIM | - +| MS SQL (PDO_SQLSRV) | sqlsrv | SIM | SIM +| MS SQL (PDO_DBLIB) | mssql | SIM | - +| ODBC | odbc | SIM | - -{{title: Nette Database}} -{{description: O Nette Database simplifica a recuperação de dados do banco de dados sem a necessidade de escrever consultas SQL. Ela pede consultas eficientes e não transfere dados desnecessários.}} + +{{maintitle: Nette Database - awesome database layer for PHP}} +{{description: Nette Database simplifica significativamente a obtenção de dados do banco de dados sem a necessidade de escrever consultas SQL. Ele faz consultas eficientes e não transfere dados desnecessários.}} diff --git a/database/pt/@left-menu.texy b/database/pt/@left-menu.texy index eb02a99613..b1a45ab3c0 100644 --- a/database/pt/@left-menu.texy +++ b/database/pt/@left-menu.texy @@ -1,5 +1,12 @@ -Base de dados -************* -- [Núcleo |Core] -- [Explorador |Explorer] -- [Configuração |Configuration] +Nette Database +************** +- [Introdução |guide] +- [Acesso SQL |sql way] +- [Explorer |Explorer] +- [Transações |transactions] +- [Exceções |exceptions] +- [Reflexão |reflection] +- [Mapeamento |mapping] +- [Configuração |configuration] +- [Riscos de segurança |security] +- [Atualização |en:upgrading] diff --git a/database/pt/@meta.texy b/database/pt/@meta.texy new file mode 100644 index 0000000000..41a853b6aa --- /dev/null +++ b/database/pt/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentação Nette}} diff --git a/database/pt/configuration.texy b/database/pt/configuration.texy index 9f4c1861d9..b327ef196d 100644 --- a/database/pt/configuration.texy +++ b/database/pt/configuration.texy @@ -2,60 +2,66 @@ Configuração do banco de dados ****************************** .[perex] -Visão geral das opções de configuração para o banco de dados Nette. +Visão geral das opções de configuração para Nette Database. -Se você não estiver usando toda a estrutura, mas apenas esta biblioteca, leia [como carregar a configuração |bootstrap:]. +Se você não estiver usando o framework completo, mas apenas esta biblioteca, leia [como carregar a configuração|bootstrap:]. -Conexão única .[#toc-single-connection] ---------------------------------------- +Conexão única +------------- -Configurar uma única conexão de banco de dados: +Configuração de uma única conexão de banco de dados: ```neon database: - # DSN, somente chave obrigatória - dsn: "sqlite:%appDir%/Modelo/demo.db" + # DSN, a única chave obrigatória + dsn: "sqlite:%appDir%/Model/demo.db" user: ... password: ... ``` -Ela cria serviços do tipo `Nette\Database\Connection` e também `Nette\Database\Explorer` para a camada [Database Explorer |explorer]. A conexão de banco de dados é geralmente passada por [autowiring |dependency-injection:autowiring], se isso não for possível, use os nomes dos serviços `@database.default.connection` resp. `@database.default.explorer`. +Cria os serviços `Nette\Database\Connection` e `Nette\Database\Explorer`, que geralmente passamos por [autowiring |dependency-injection:autowiring], ou por referência ao [seu nome |#Serviços DI]. Outras configurações: ```neon database: - # mostra painel de banco de dados em Tracy Bar? - debugger: ... # (bool) por omissão + # exibir o painel do banco de dados na Tracy Bar? + debugger: ... # (bool) padrão é true - # mostra consulta EXPLAIN em Tracy Bar? - explain: ... # (bool) por omissão + # exibir EXPLAIN das consultas na Tracy Bar? + explain: ... # (bool) padrão é true - # para habilitar a fiação automática para esta conexão? - autowired: ... # (bool) por omissão para a primeira conexão + # permitir autowiring para esta conexão? + autowired: ... # (bool) padrão é true na primeira conexão - # convenções de tabela: descoberta, estática, ou nome de classe - conventions: descoberto # (string) padrão para "descoberto + # convenções de tabela: discovered, static ou nome da classe + conventions: discovered # (string) padrão é 'discovered' options: - # para conectar ao banco de dados somente quando necessário? - lazy: ... # (bool) padrão a falso + # conectar ao banco de dados apenas quando necessário? + lazy: ... # (bool) padrão é false - # classe de driver de banco de dados PHP + # classe PHP do driver do banco de dados driverClass: # (string) - # somente MySQL: sets sql_mode + # apenas MySQL: define sql_mode sqlmode: # (string) - # somente MySQL: define o SET NAMES - charset: # (string) padrão para 'utf8mb4' ('utf8' antes da v5.5.3) + # apenas MySQL: define SET NAMES + charset: # (string) padrão é 'utf8mb4' - # somente Oracle e SQLite: formato de data - formatDateTime: # (string) padrão para 'U' + # apenas MySQL: converte TINYINT(1) para bool + convertBoolean: # (bool) padrão é false + + # retorna colunas de data como objetos imutáveis (desde a versão 3.2.1) + newDateTime: # (bool) padrão é false + + # apenas Oracle e SQLite: formato para armazenar data + formatDateTime: # (string) padrão é 'U' ``` -A chave `options` pode conter outras opções que podem ser encontradas na [documentação do motorista da DOP |https://www.php.net/manual/en/pdo.drivers.php], como por exemplo: +Na chave `options`, você pode especificar outras opções encontradas na [documentação dos drivers PDO |https://www.php.net/manual/en/pdo.drivers.php], como por exemplo: ```neon database: @@ -64,10 +70,10 @@ database: ``` -Conexões múltiplas .[#toc-multiple-connections] ------------------------------------------------ +Múltiplas conexões +------------------ -Na configuração, podemos definir mais conexões de banco de dados dividindo-as em seções nomeadas: +Na configuração, também podemos definir várias conexões de banco de dados dividindo-as em seções nomeadas: ```neon database: @@ -80,9 +86,23 @@ database: dsn: 'sqlite::memory:' ``` -Cada conexão definida cria serviços que incluem o nome da seção em seu nome, ou seja, `@database.main.connection` & `@database.main.explorer` e ainda `@database.another.connection` & `@database.another.explorer`. +O autowiring está habilitado apenas para os serviços da primeira seção. Isso pode ser alterado usando `autowired: false` ou `autowired: true`. + + +Serviços DI +----------- + +Estes serviços são adicionados ao contêiner de DI, onde `###` representa o nome da conexão: + +| Nome | Tipo | Descrição +|---------------------------------------------------------- +| `database.###.connection` | [api:Nette\Database\Connection] | conexão com o banco de dados +| `database.###.explorer` | [api:Nette\Database\Explorer] | [Database Explorer |explorer] + + +Se definirmos apenas uma conexão, os nomes dos serviços serão `database.default.connection` e `database.default.explorer`. Se definirmos várias conexões como no exemplo acima, os nomes corresponderão às seções, ou seja, `database.main.connection`, `database.main.explorer` e também `database.another.connection` e `database.another.explorer`. -O cabeamento automático é habilitado somente para serviços da primeira seção. Isto pode ser alterado com `autowired: false` ou `autowired: true`. Os serviços sem fio são passados pelo nome: +Passamos serviços não autowired explicitamente por referência ao seu nome: ```neon services: diff --git a/database/pt/core.texy b/database/pt/core.texy deleted file mode 100644 index a3a5df1e0a..0000000000 --- a/database/pt/core.texy +++ /dev/null @@ -1,350 +0,0 @@ -Núcleo do banco de dados -************************ - -.[perex] -Nette Database Core é a camada de abstração do banco de dados e fornece a funcionalidade do núcleo. - - -Instalação .[#toc-installation] -=============================== - -Baixe e instale o pacote usando [o Composer |best-practices:composer]: - -```shell -composer require nette/database -``` - - -Conexão e configuração .[#toc-connection-and-configuration] -=========================================================== - -Para conectar-se ao banco de dados, basta criar uma instância da classe [api:Nette\Database\Connection]: - -```php -$database = new Nette\Database\Connection($dsn, $user, $password); -``` - -O parâmetro `$dsn` (nome da fonte de dados) é [o mesmo que o utilizado pela DOP |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], por exemplo `host=127.0.0.1;dbname=test`. Em caso de falha, ele lança `Nette\Database\ConnectionException`. - -Entretanto, uma maneira mais sofisticada oferece [configuração de aplicação |configuration]. Acrescentaremos uma seção `database` e ela cria os objetos necessários e um painel de banco de dados na barra [Tracy |tracy:]. - -```neon -database: - dsn: 'mysql:host=127.0.0.1;dbname=test' - user: root - password: password -``` - -O objeto de conexão que [recebemos como um serviço de um container DI |dependency-injection:passing-dependencies], por exemplo: - -```php -class Model -{ - // pass Nette\Database\Explorer para trabalhar com a camada Database Explorer\Database - public function __construct( - private Nette\Database\Connection $database, - ) { - } -} -``` - -Para mais informações, consulte a [configuração do banco de dados |configuration]. - - -Consultas .[#toc-queries] -========================= - -Para consultar o banco de dados utilize o método `query()` que retorna o [ResultSet |api:Nette\Database\ResultSet]. - -```php -$result = $database->query('SELECT * FROM users'); - -foreach ($result as $row) { - echo $row->id; - echo $row->name; -} - -echo $result->getRowCount(); // devolve o número de filas se for conhecido -``` - -.[note] -Sobre o `ResultSet` é possível iterar apenas uma vez, se precisarmos iterar várias vezes, é necessário converter o resultado para a matriz através do método `fetchAll()`. - -Você pode facilmente adicionar parâmetros à consulta, anote o ponto de interrogação: - -```php -$database->query('SELECT * FROM users WHERE name = ?', $name); - -$database->query('SELECT * FROM users WHERE name = ? AND active = ?', $name, $active); - -$database->query('SELECT * FROM users WHERE id IN (?)', $ids); // $ids is array -``` -<div class=warning> - -AVISO, nunca concatenar cordas para evitar [vulnerabilidade de injeção SQL |https://en.wikipedia.org/wiki/SQL_injection]! -/-- -$db->query('SELECT * FROM users WHERE name = ' . $name); // WRONG!!! -\-- -</div> - -Em caso de falha `query()` lança ou `Nette\Database\DriverException` ou um de seus descendentes: - -- [ConstraintViolationException |api:Nette\Database\ConstraintViolationException] - violação de qualquer restrição -- [ForeignKeyConstraintViolationException |api:Nette\Database\ForeignKeyConstraintViolationException] - chave estrangeira inválida -- [NotNullConstraintViolationException |api:Nette\Database\NotNullConstraintViolationException] - violação da condição NOT NULL -- [UniqueConstraintViolationException |api:Nette\Database\UniqueConstraintViolationException] - conflito de índice único - -Além de `query()`, existem outros métodos úteis: - -```php -// retorna a matriz associativa id => nome -$pairs = $database->fetchPairs('SELECT id, name FROM users'); - -// devolve todas as filas como matriz -$rows = $database->fetchAll('SELECT * FROM users'); - -// retorna em fila única -$row = $database->fetch('SELECT * FROM users WHERE id = ?', $id); - -// retornar campo único -$name = $database->fetchField('SELECT name FROM users WHERE id = ?', $id); -``` - -Em caso de falha, todos esses métodos jogam `Nette\Database\DriverException.` - - -Inserir, atualizar e excluir .[#toc-insert-update-delete] -========================================================= - -O parâmetro que inserimos na consulta SQL também pode ser o array (nesse caso é possível pular a declaração wildcard `?`), which may be useful for the `INSERT`: - -```php -$database->query('INSERT INTO users ?', [ // aqui pode ser omitido o ponto de interrogação - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) - -$id = $database->getInsertId(); // devolve o auto-incremento da linha inserida - -$id = $database->getInsertId($seqüência); // ou valor da seqüência -``` - -Inserção múltipla: - -```php -$database->query('INSERT INTO users', [ - [ - 'name' => 'Jim', - 'year' => 1978, - ], [ - 'name' => 'Jack', - 'year' => 1987, - ], -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) -``` - -Também podemos passar arquivos, objetos DateTime ou [enumerações |https://www.php.net/enumerations]: - -```php -$database->query('INSERT INTO users', [ - 'name' => $name, - 'created' => new DateTime, // or $database::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // inserts file contents - 'status' => State::New, // enum State -]); -``` - -Atualização de linhas: - -```php -$result = $database->query('UPDATE users SET', [ - 'name' => $name, - 'year' => $year, -], 'WHERE id = ?', $id); -// UPDATE users SET `name` = 'Jim', `year` = 1978 WHERE id = 123 - -echo $result->getRowCount(); // devolve o número de filas afetadas -``` - -Para a atualização, podemos utilizar os operadores `+=` e `-=`: - -```php -$database->query('UPDATE users SET', [ - 'age+=' => 1, // note += -], 'WHERE id = ?', $id); -// UPDATE users SET `age` = `age` + 1 -``` - -Eliminação: - -```php -$result = $database->query('DELETE FROM users WHERE id = ?', $id); -echo $result->getRowCount(); // returns the number of affected rows -``` - - -Consultas avançadas .[#toc-advanced-queries] -============================================ - -Inserir ou atualizar, se já existir: - -```php -$database->query('INSERT INTO users', [ - 'id' => $id, - 'name' => $name, - 'year' => $year, -], 'ON DUPLICATE KEY UPDATE', [ - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) -// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 -``` - -Observe que o Nette Database reconhece o contexto SQL no qual o parâmetro da matriz é inserido e constrói o código SQL de acordo. Assim, a partir do primeiro array ele gera `(id, name, year) VALUES (123, 'Jim', 1978)`, enquanto o segundo converte para `name = 'Jim', year = 1978`. - -Também podemos descrever a ordenação usando matriz, em chaves estão os nomes das colunas e os valores são booleanos que determinam se a ordenação deve ser feita em ordem ascendente: - -```php -$database->query('SELECT id FROM author ORDER BY', [ - 'id' => true, // ascendente - 'name' => false, // decrescente -]); -// SELECT id FROM author ORDER BY `id`, `name` DESC -``` - -Se a detecção não funcionou, você pode especificar a forma da montagem com um curinga `?` seguido de uma dica. Estas dicas são suportadas: - -| ?valores | (chave1, chave2, ...) VALORES (valor1, valor2, ...) -| ?set | chave1 = valor1, chave2 = valor2, ... -| ?e | chave1 = valor1 E chave2 = valor2 ... -| ?ou | chave1 = valor1 OU chave2 = valor2 ... -| ?ordem | chave1 ASC, chave2 DESC - -A cláusula WHERE utiliza o operador `?and`, portanto as condições estão vinculadas por `AND`: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND `year` = 1978 -``` - -O que pode ser facilmente alterado para `OR`, utilizando o wildcard `?or`: - -```php -$result = $database->query('SELECT * FROM users WHERE ?or', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' OR `year` = 1978 -``` - -Podemos utilizar os operadores em condições: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name <>' => $name, - 'year >' => $year, -]); -// SELECT * FROM users WHERE `name` <> 'Jim' AND `year` > 1978 -``` - -E também enumerações: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => ['Jim', 'Jack'], - 'role NOT IN' => ['admin', 'owner'], // enumeration + operator NOT IN -]); -// SELECT * FROM users WHERE -// `name` IN ('Jim', 'Jack') AND `role` NOT IN ('admin', 'owner') -``` - -Também podemos incluir uma peça de código SQL personalizada usando o chamado SQL literal: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year >' => $database::literal('YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) -``` - -Alternativamente: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) -``` - -SQL literal também pode ter seus parâmetros: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > ? AND year < ?', $min, $max), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) -``` - -Graças a isso, podemos criar combinações interessantes: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('?or', [ - 'active' => true, - 'role' => $role, - ]), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') -``` - - -Nome da variável .[#toc-variable-name] -====================================== - -Há um wildcard `?name` que você utiliza se o nome da tabela ou nome da coluna for uma variável. (Cuidado, não permita que o usuário manipule o conteúdo de tal variável): - -```php -$table = 'blog.users'; -$column = 'name'; -$database->query('SELECT * FROM ?name WHERE ?name = ?', $table, $column, $name); -// SELECT * FROM `blog`.`users` WHERE `name` = 'Jim' -``` - - -Transações .[#toc-transactions] -=============================== - -Há três métodos para lidar com as transações: - -```php -$database->beginTransaction(); - -$database->commit(); - -$database->rollback(); -``` - -Uma maneira elegante é oferecida pelo método `transaction()`. Você passa o callback que é executado na transação. Se for lançada uma exceção durante a execução, a transação é descartada, se tudo correr bem, a transação é comprometida. - -```php -$id = $database->transaction(function ($database) { - $database->query('DELETE FROM ...'); - $database->query('INSERT INTO ...'); - // ... - return $database->getInsertId(); -}); -``` - -Como você pode ver, o método `transaction()` retorna o valor de retorno da ligação de retorno. - -A transação() também pode ser aninhada, o que simplifica a implementação de repositórios independentes. diff --git a/database/pt/exceptions.texy b/database/pt/exceptions.texy new file mode 100644 index 0000000000..1882eaf2d8 --- /dev/null +++ b/database/pt/exceptions.texy @@ -0,0 +1,34 @@ +Exceções +******** + +O Nette Database utiliza uma hierarquia de exceções. A classe base é `Nette\Database\DriverException`, que herda de `PDOException` e fornece opções estendidas para trabalhar com erros do banco de dados: + +- O método `getDriverCode()` retorna o código de erro do driver do banco de dados +- O método `getSqlState()` retorna o código SQLSTATE +- Os métodos `getQueryString()` e `getParameters()` permitem obter a consulta original e os seus parâmetros + +As seguintes exceções especializadas herdam de `DriverException`: + +- `ConnectionException` - sinaliza falha na conexão com o servidor de banco de dados +- `ConstraintViolationException` - classe base para violação de restrições do banco de dados, da qual herdam: + - `ForeignKeyConstraintViolationException` - violação de chave estrangeira + - `NotNullConstraintViolationException` - violação da restrição NOT NULL + - `UniqueConstraintViolationException` - violação da unicidade do valor + + +Exemplo de captura da exceção `UniqueConstraintViolationException`, que ocorre quando tentamos inserir um utilizador com um e-mail que já existe no banco de dados (assumindo que a coluna `email` tenha um índice único). + +```php +try { + $database->query('INSERT INTO users', [ + 'email' => 'john@example.com', + 'name' => 'John Doe', + 'password' => $hashedPassword, + ]); +} catch (Nette\Database\UniqueConstraintViolationException $e) { + echo 'Um utilizador com este e-mail já existe.'; + +} catch (Nette\Database\DriverException $e) { + echo 'Ocorreu um erro durante o registo: ' . $e->getMessage(); +} +``` diff --git a/database/pt/explorer.texy b/database/pt/explorer.texy index 7e6205c677..714a4d7719 100644 --- a/database/pt/explorer.texy +++ b/database/pt/explorer.texy @@ -1,550 +1,912 @@ -Explorador de banco de dados -**************************** +Database Explorer +***************** <div class=perex> -O Nette Database Explorer simplifica significativamente a recuperação de dados do banco de dados sem a escrita de consultas SQL. +O Explorer oferece uma forma intuitiva e eficiente de trabalhar com o banco de dados. Ele trata automaticamente das relações entre tabelas e da otimização de consultas, para que você possa se concentrar na sua aplicação. Funciona imediatamente sem configuração. Se precisar de controle total sobre as consultas SQL, pode utilizar o [acesso SQL |sql-way]. -- utiliza consultas eficientes -- nenhum dado é transmitido desnecessariamente -- apresenta uma sintaxe elegante +- O trabalho com dados é natural e fácil de entender +- Gera consultas SQL otimizadas que carregam apenas os dados necessários +- Permite acesso fácil a dados relacionados sem a necessidade de escrever consultas JOIN +- Funciona imediatamente sem qualquer configuração ou geração de entidades </div> -Para usar o Database Explorer, comece com uma tabela - ligue para `table()` em um objeto [api:Nette\Database\Explorer]. A maneira mais fácil de obter uma instância de objeto de contexto é [descrita aqui |core#Connection and Configuration], ou, no caso em que o Nette Database Explorer é usado como uma ferramenta autônoma, ele pode ser [criado manualmente |#Creating Explorer Manually]. + +Começa-se com o Explorer chamando o método `table()` do objeto [api:Nette\Database\Explorer] (detalhes sobre a conexão podem ser encontrados no capítulo [Conexão e configuração |guide#Conexão e configuração]): ```php -$books = $explorer->table('book'); // nome da tabela db é 'livro'. +$books = $explorer->table('book'); // 'book' é o nome da tabela ``` -A chamada retorna uma instância de objeto de [Seleção |api:Nette\Database\Table\Selection], que pode ser iterada para recuperar todos os livros. Cada item (uma linha) é representado por uma instância do [ActiveRow |api:Nette\Database\Table\ActiveRow] com dados mapeados para suas propriedades: +O método retorna um objeto [Selection |api:Nette\Database\Table\Selection], que representa uma consulta SQL. A este objeto, podemos encadear outros métodos para filtrar e ordenar os resultados. A consulta é construída e executada apenas quando começamos a solicitar os dados, por exemplo, percorrendo um ciclo `foreach`. Cada linha é representada por um objeto [ActiveRow |api:Nette\Database\Table\ActiveRow]: ```php foreach ($books as $book) { - echo $book->title; - echo $book->author_id; + echo $book->title; // exibe a coluna 'title' + echo $book->author_id; // exibe a coluna 'author_id' } ``` -A obtenção de apenas uma fila específica é feita pelo método `get()`, que retorna diretamente uma instância ActiveRow. +O Explorer facilita fundamentalmente o trabalho com [#relações entre tabelas]. O exemplo seguinte mostra como podemos facilmente exibir dados de tabelas relacionadas (livros e seus autores). Note que não precisamos escrever nenhuma consulta JOIN, o Nette cria-as por nós: ```php -$book = $explorer->table('book')->get(2); // devolve livro com id 2 -echo $book->title; -echo $book->author_id; +$books = $explorer->table('book'); + +foreach ($books as $book) { + echo 'Livro: ' . $book->title; + echo 'Autor: ' . $book->author->name; // cria JOIN na tabela 'author' +} ``` -Vamos dar uma olhada no caso de uso comum. Você precisa ir buscar livros e seus autores. É uma relação 1:N comum. A solução freqüentemente usada é buscar dados usando uma consulta SQL com joins de tabela. A segunda possibilidade é buscar dados separadamente, executar uma consulta para obter livros e depois obter um autor para cada livro por outra consulta (por exemplo, em seu ciclo foreach). Isto poderia ser facilmente otimizado para executar apenas duas consultas, uma para os livros e outra para os autores necessários - e esta é exatamente a maneira como o Nette Database Explorer o faz. +O Nette Database Explorer otimiza as consultas para serem o mais eficientes possível. O exemplo acima executa apenas duas consultas SELECT, independentemente de estarmos a processar 10 ou 10 000 livros. -Nos exemplos abaixo, trabalharemos com o esquema do banco de dados na figura. Há links OneHasMany (1:N) (autor do livro `author_id` e possível tradutor `translator_id`, que pode ser `null`) e ManyHasMany (M:N) link entre o livro e suas tags. +Além disso, o Explorer monitoriza quais colunas são usadas no código e carrega do banco de dados apenas essas, economizando ainda mais desempenho. Este comportamento é totalmente automático e adaptativo. Se modificar o código posteriormente e começar a usar outras colunas, o Explorer ajustará automaticamente as consultas. Não precisa de configurar nada, nem pensar em quais colunas precisará - deixe isso para o Nette. -[Um exemplo, incluindo um esquema, é encontrado no GitHub |https://github.com/nette-examples/books]. -[* db-schema-1-.webp *] *** Estrutura da base de dados utilizada nos exemplos .<> +Filtragem e Ordenação +===================== -O seguinte código lista o nome do autor de cada livro e todas as suas etiquetas. [Discutiremos em breve |#Working with relationships] como isto funciona internamente. +A classe `Selection` fornece métodos para filtrar e ordenar a seleção de dados. -```php -$books = $explorer->table('book'); +.[language-php] +| `where($condition, ...$params)` | Adiciona uma condição WHERE. Múltiplas condições são unidas pelo operador AND +| `whereOr(array $conditions)` | Adiciona um grupo de condições WHERE unidas pelo operador OR +| `wherePrimary($value)` | Adiciona uma condição WHERE pela chave primária +| `order($columns, ...$params)` | Define a ordenação ORDER BY +| `select($columns, ...$params)` | Especifica as colunas que devem ser carregadas +| `limit($limit, $offset = null)` | Limita o número de linhas (LIMIT) e opcionalmente define OFFSET +| `page($page, $itemsPerPage, &$total = null)` | Define a paginação +| `group($columns, ...$params)` | Agrupa linhas (GROUP BY) +| `having($condition, ...$params)` | Adiciona uma condição HAVING para filtrar linhas agrupadas -foreach ($books como $book) { - echo 'title: ' . $book->title; - echo 'escrito por: . $book->author->name; // $book->autor é linha da tabela 'autor'. +Os métodos podem ser encadeados (a chamada [fluent interface |nette:introduction-to-object-oriented-programming#Interfaces Fluentes]): `$table->where(...)->order(...)->limit(...)`. - echo 'tags: '; - foreach ($book->related('book_tag') as $bookTag) { - echo $bookTag->tag->name . ...', '; // $bookTag->tag é linha da tabela 'tag'. - } -} -``` +Nestes métodos, também pode usar notação especial para aceder a [dados de tabelas relacionadas |#Consulta através de tabelas relacionadas]. -Você ficará satisfeito com a eficiência com que a camada de banco de dados funciona. O exemplo acima faz um número constante de solicitações que se assemelham a este: -```sql -SELECT * FROM `book` -SELECT * FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT * FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) -``` +Escaping e Identificadores +-------------------------- -Se você usar o [cache |caching:] (default on), nenhuma coluna será consultada desnecessariamente. Após a primeira consulta, o cache armazenará os nomes das colunas usadas e o Nette Database Explorer executará as consultas somente com as colunas necessárias: +Os métodos escapam automaticamente os parâmetros e colocam aspas nos identificadores (nomes de tabelas e colunas), prevenindo assim a injeção de SQL. Para o funcionamento correto, é necessário seguir algumas regras: -```sql -SELECT `id`, `title`, `author_id` FROM `book` -SELECT `id`, `name` FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT `book_id`, `tag_id` FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT `id`, `name` FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) +- Palavras-chave, nomes de funções, procedimentos, etc., escreva em **MAIÚSCULAS**. +- Nomes de colunas e tabelas escreva em **minúsculas**. +- Strings sempre insira através de **parâmetros**. + +```php +where('name = ' . $name); // VULNERABILIDADE CRÍTICA: injeção de SQL +where('name LIKE "%search%"'); // ERRADO: complica o quoting automático +where('name LIKE ?', '%search%'); // CORRETO: valor inserido via parâmetro + +where('name like ?', $name); // ERRADO: gera: `name` `like` ? +where('name LIKE ?', $name); // CORRETO: gera: `name` LIKE ? +where('LOWER(name) = ?', $value);// CORRETO: LOWER(`name`) = ? ``` -Seleções .[#toc-selections] -=========================== +where(string|array $condition, ...$parameters): static .[method] +---------------------------------------------------------------- -Veja as possibilidades de filtragem e restrição de linhas [api:Nette\Database\Table\Selection]: +Filtra os resultados usando condições WHERE. A sua força reside no trabalho inteligente com diferentes tipos de valores e na escolha automática de operadores SQL. -.[language-php] -| `$table->where($where[, $param[, ...]])` | Colar ONDE usar AND como cola se duas ou mais condições forem fornecidas -| `$table->whereOr($where)` | Definir ONDE usar OU como cola se duas ou mais condições forem fornecidas -| `$table->order($columns)` | Definir ORDEM POR, pode ser expressão `('column DESC, id DESC')` -| `$table->select($columns)` | Conjunto de colunas recuperadas, pode ser expressão `('col, MD5(col) AS hash')` -| `$table->limit($limit[, $offset])` | Definir LIMITES e OFFSET -| `$table->page($page, $itemsPerPage[, &$lastPage])` | Permite a paginação -| `$table->group($columns)` | Set GROUP BY -| `$table->having($having)` | Set HAVING +Uso básico: -A interface fluente pode ser utilizada, por exemplo `$table->where(...)->order(...)->limit(...)`. Múltiplas condições `where` ou `whereOr` são conectadas com o operador `AND`. +```php +$table->where('id', $value); // WHERE `id` = 123 +$table->where('id > ?', $value); // WHERE `id` > 123 +$table->where('id = ? OR name = ?', $id, $name); // WHERE `id` = 1 OR `name` = 'Jon Snow' +``` +Graças à deteção automática de operadores apropriados, não precisamos de lidar com vários casos especiais. O Nette resolve-os por nós: -onde() .[#toc-where] --------------------- +```php +$table->where('id', 1); // WHERE `id` = 1 +$table->where('id', null); // WHERE `id` IS NULL +$table->where('id', [1, 2, 3]); // WHERE `id` IN (1, 2, 3) +// também é possível usar o placeholder de interrogação sem operador: +$table->where('id ?', 1); // WHERE `id` = 1 +``` -O Nette Database Explorer pode adicionar automaticamente os operadores necessários para os valores passados: +O método também processa corretamente condições negativas e arrays vazios: -.[language-php] -| `$table->where('field', $value)` | campo = $value -| `$table->where('field', null)` | campo IS NULL -| `$table->where('field > ?', $val)` | campo > $val -| `$table->where('field', [1, 2])` | campo IN (1, 2) -| `$table->where('id = ? OR name = ?', 1, $name)` | id = 1 OU nome = 'Jon Snow' -| `$table->where('field', $explorer->table($tableName))` | campo IN (SELECT $primary FROM $tableName) -| `$table->where('field', $explorer->table($tableName)->select('col'))` | field IN (SELECT col FROM $tableName) +```php +$table->where('id', []); // WHERE `id` IS NULL AND FALSE -- não encontra nada +$table->where('id NOT', []); // WHERE `id` IS NULL OR TRUE -- encontra tudo +$table->where('NOT (id ?)', []); // WHERE NOT (`id` IS NULL AND FALSE) -- encontra tudo +// $table->where('NOT id ?', $ids); Atenção - esta sintaxe não é suportada +``` -Você pode fornecer o espaço reservado mesmo sem operador de coluna. Estas chamadas são as mesmas. +Como parâmetro, também podemos passar o resultado de outra tabela - será criada uma subconsulta: ```php -$table->where('id = ? OR id = ?', 1, 2); -$table->where('id ? OR id ?', 1, 2); +// WHERE `id` IN (SELECT `id` FROM `tableName`) +$table->where('id', $explorer->table($tableName)); + +// WHERE `id` IN (SELECT `col` FROM `tableName`) +$table->where('id', $explorer->table($tableName)->select('col')); ``` -Esta característica permite gerar um operador correto com base no valor: +As condições também podem ser passadas como um array, cujos itens são unidos por AND: ```php -$table->where('id ?', 2); // id = 2 -$table->where('id ?', null); // id IS NULL -$table->where('id', $ids); // id IN (...) +// WHERE (`price_final` < `price_original`) AND (`stock_count` > `min_stock`) +$table->where([ + 'price_final < price_original', + 'stock_count > min_stock', +]); ``` -A seleção também trata corretamente as condições negativas, funciona também para matrizes vazias: +No array, podemos usar pares chave => valor e o Nette escolherá novamente, de forma automática, os operadores corretos: ```php -$table->where('id', []); // id IS NULL AND FALSE -$table->where('id NOT', []); // id IS NULL OR TRUE -$table->where('NOT (id ?)', $ids); // NOT (id IS NULL AND FALSE) +// WHERE (`status` = 'active') AND (`id` IN (1, 2, 3)) +$table->where([ + 'status' => 'active', + 'id' => [1, 2, 3], +]); +``` -// isto abrirá uma exceção, esta sintaxe não é suportada -$table->where('NOT id ?', $ids); +No array, podemos combinar expressões SQL com placeholders de interrogação e múltiplos parâmetros. Isto é adequado para condições complexas com operadores definidos com precisão: + +```php +// WHERE (`age` > 18) AND (ROUND(`score`, 2) > 75.5) +$table->where([ + 'age > ?' => 18, + 'ROUND(score, ?) > ?' => [2, 75.5], // dois parâmetros passados como array +]); ``` +Chamadas múltiplas de `where()` unem automaticamente as condições com AND. + -ondeOr() .[#toc-whereor] ------------------------- +whereOr(array $parameters): static .[method] +-------------------------------------------- -Exemplo de uso sem parâmetros: +Semelhante a `where()`, adiciona condições, mas com a diferença de que as une usando OR: ```php -// WHERE (user_id IS NULL) OR (SUM(`field1`) > SUM(`field2`)) +// WHERE (`status` = 'active') OR (`deleted` = 1) $table->whereOr([ - 'user_id IS NULL', - 'SUM(field1) > SUM(field2)', + 'status' => 'active', + 'deleted' => true, ]); ``` -Nós usamos os parâmetros. Se você não especificar um operador, o Nette Database Explorer adicionará automaticamente o apropriado: +Aqui também podemos usar expressões mais complexas: ```php -// WHERE (`field1` IS NULL) OR (`field2` IN (3, 5)) OR (`amount` > 11) +// WHERE (`price` > 1000) OR (`price_with_tax` > 1500) $table->whereOr([ - 'field1' => null, - 'field2' => [3, 5], - 'amount >' => 11, + 'price > ?' => 1000, + 'price_with_tax > ?' => 1500, ]); ``` -A chave pode conter uma expressão contendo pontos de interrogação de curinga e depois passar parâmetros no valor: + +wherePrimary(mixed $key): static .[method] +------------------------------------------ + +Adiciona uma condição para a chave primária da tabela: ```php -// WHERE (`id` > 12) OR (ROUND(`id`, 5) = 3) -$table->whereOr([ - 'id > ?' => 12, - 'ROUND(id, ?) = ?' => [5, 3], -]); +// WHERE `id` = 123 +$table->wherePrimary(123); + +// WHERE `id` IN (1, 2, 3) +$table->wherePrimary([1, 2, 3]); ``` +Se a tabela tiver uma chave primária composta (por exemplo, `foo_id`, `bar_id`), passamo-la como um array: -ordem() .[#toc-order] ---------------------- +```php +// WHERE `foo_id` = 1 AND `bar_id` = 5 +$table->wherePrimary(['foo_id' => 1, 'bar_id' => 5])->fetch(); + +// WHERE (`foo_id`, `bar_id`) IN ((1, 5), (2, 3)) +$table->wherePrimary([ + ['foo_id' => 1, 'bar_id' => 5], + ['foo_id' => 2, 'bar_id' => 3], +])->fetchAll(); +``` + + +order(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- -Exemplos de uso: +Determina a ordem em que as linhas serão retornadas. Podemos ordenar por uma ou mais colunas, em ordem ascendente ou descendente, ou por uma expressão personalizada: ```php -$table->order('field1'); // ORDER BY `field1` -$table->order('field1 DESC, field2'); // ORDER BY `field1` DESC, `field2` -$table->order('field = ? DESC', 123); // ORDER BY `field` = 123 DESC +$table->order('created'); // ORDER BY `created` +$table->order('created DESC'); // ORDER BY `created` DESC +$table->order('priority DESC, created'); // ORDER BY `priority` DESC, `created` +$table->order('status = ? DESC', 'active'); // ORDER BY `status` = 'active' DESC ``` -selecione() .[#toc-select] --------------------------- +select(string $columns, ...$parameters): static .[method] +--------------------------------------------------------- -Exemplos de uso: +Especifica as colunas que devem ser retornadas do banco de dados. Por padrão, o Nette Database Explorer retorna apenas as colunas que são realmente usadas no código. O método `select()` é, portanto, usado nos casos em que precisamos retornar expressões específicas: ```php -$table->select('field1'); // SELECT `field1` -$table->select('col, UPPER(col) AS abc'); // SELECT `col`, UPPER(`col`) AS abc -$table->select('SUBSTR(title, ?)', 3); // SELECT SUBSTR(`title`, 3) +// SELECT *, DATE_FORMAT(`created_at`, ?) AS formatted_date +$table->select('*, DATE_FORMAT(created_at, ?) AS formatted_date', '%d.%m.%Y'); ``` +Os aliases definidos usando `AS` ficam então disponíveis como propriedades do objeto ActiveRow: + +```php +foreach ($table as $row) { + echo $row->formatted_date; // acesso ao alias +} +``` -limite() .[#toc-limit] ----------------------- -Exemplos de uso: +limit(?int $limit, ?int $offset = null): static .[method] +--------------------------------------------------------- + +Limita o número de linhas retornadas (LIMIT) e opcionalmente permite definir um offset: ```php -$table->limit(1); // LIMIT 1 -$table->limit(1, 10); // LIMIT 1 OFFSET 10 +$table->limit(10); // LIMIT 10 (retorna as primeiras 10 linhas) +$table->limit(10, 20); // LIMIT 10 OFFSET 20 ``` +Para paginação, é mais adequado usar o método `page()`. -página() .[#toc-page] ---------------------- -Uma maneira alternativa de estabelecer o limite e a compensação: +page(int $page, int $itemsPerPage, &$numOfPages = null): static .[method] +------------------------------------------------------------------------- + +Facilita a paginação dos resultados. Aceita o número da página (contado a partir de 1) e o número de itens por página. Opcionalmente, pode-se passar uma referência a uma variável na qual o número total de páginas será armazenado: ```php -$page = 5; -$itemsPerPage = 10; -$table->page($page, $itemsPerPage); // LIMIT 10 OFFSET 40 +$numOfPages = null; +$table->page(page: 3, itemsPerPage: 10, $numOfPages); +echo "Total de páginas: $numOfPages"; ``` -Obtendo o número da última página, passado para a variável `$lastPage`: + +group(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- + +Agrupa linhas de acordo com as colunas especificadas (GROUP BY). É geralmente usado em conjunto com funções de agregação: ```php -$table->page($page, $itemsPerPage, $lastPage); +// Conta o número de produtos em cada categoria +$table->select('category_id, COUNT(*) AS count') + ->group('category_id'); ``` -grupo() .[#toc-group] ---------------------- +having(string $having, ...$parameters): static .[method] +-------------------------------------------------------- -Exemplos de uso: +Define uma condição para filtrar linhas agrupadas (HAVING). Pode ser usado em conjunto com o método `group()` e funções de agregação: ```php -$table->group('field1'); // GROUP BY `field1` -$table->group('field1, field2'); // GROUP BY `field1`, `field2` +// Encontra categorias que têm mais de 100 produtos +$table->select('category_id, COUNT(*) AS count') + ->group('category_id') + ->having('count > ?', 100); ``` -tendo() .[#toc-having] ----------------------- +Leitura de Dados +================ + +Para ler dados do banco de dados, temos vários métodos úteis disponíveis: -Exemplos de uso: +.[language-php] +| `foreach ($table as $key => $row)` | Itera sobre todas as linhas, `$key` é o valor da chave primária, `$row` é o objeto ActiveRow +| `$row = $table->get($key)` | Retorna uma única linha pela chave primária +| `$row = $table->fetch()` | Retorna a linha atual e move o ponteiro para a próxima +| `$array = $table->fetchPairs()` | Cria um array associativo a partir dos resultados +| `$array = $table->fetchAll()` | Retorna todas as linhas como um array +| `count($table)` | Retorna o número de linhas no objeto Selection + +O objeto [ActiveRow |api:Nette\Database\Table\ActiveRow] destina-se apenas à leitura. Isto significa que não é possível alterar os valores das suas propriedades. Esta restrição garante a consistência dos dados e evita efeitos colaterais inesperados. Os dados são carregados do banco de dados e qualquer alteração deve ser feita explicitamente e de forma controlada. + + +`foreach` - Iteração Sobre Todas as Linhas +------------------------------------------ + +A forma mais fácil de executar uma consulta e obter linhas é iterando num ciclo `foreach`. Ele executa automaticamente a consulta SQL. ```php -$table->having('COUNT(items) >', 100); // HAVING COUNT(`items`) > 100 +$books = $explorer->table('book'); +foreach ($books as $key => $book) { + // $key é o valor da chave primária, $book é ActiveRow + echo "$book->title ({$book->author->name})"; +} ``` -Filtragem por outro valor de tabela .[#toc-joining-key] -------------------------------------------------------- +get($key): ?ActiveRow .[method] +------------------------------- -Muitas vezes você precisa de resultados de filtragem por alguma condição que envolve outra tabela de banco de dados. Estes tipos de condição exigem a união de tabelas. Entretanto, você não precisa mais escrevê-las. +Executa a consulta SQL e retorna a linha pela chave primária, ou `null` se não existir. -Digamos que você precisa obter todos os livros cujo nome do autor é 'Jon'. Tudo o que você precisa escrever é a chave de união da relação e o nome da coluna na tabela unida. A chave de união é derivada da coluna que se refere à tabela na qual você quer se juntar. Em nosso exemplo (veja o esquema db) é a coluna `author_id`, e é suficiente utilizar apenas a primeira parte dela - `author` (o sufixo `_id` pode ser omitido). `name` é uma coluna na tabela `author` que gostaríamos de utilizar. Uma condição para tradutor de livros (que é conectada pela coluna `translator_id` ) pode ser criada com a mesma facilidade. +```php +$book = $explorer->table('book')->get(123); // retorna ActiveRow com ID 123 ou null +if ($book) { + echo $book->title; +} +``` + + +fetch(): ?ActiveRow .[method] +----------------------------- + +Retorna uma linha e move o ponteiro interno para a próxima. Se não houver mais linhas, retorna `null`. ```php $books = $explorer->table('book'); -$books->where('author.name LIKE ?', '%Jon%'); -$books->where('translator.name', 'David Grudl'); +while ($book = $books->fetch()) { + $this->processBook($book); +} ``` -A lógica da união é impulsionada pela implementação das [Convenções |api:Nette\Database\Conventions]. Incentivamos a utilização da [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions], que analisa suas chaves estrangeiras e permite que você trabalhe facilmente com essas relações. -A relação entre o livro e seu autor é de 1:N. A relação inversa também é possível. Nós a chamamos de **backjoin***. Dê uma olhada em outro exemplo. Gostaríamos de ir buscar todos os autores, que escreveram mais de 3 livros. Para fazer o verso da união usamos `:` (colon). Colon means that the joined relationship means hasMany (and it's quite logical too, as two dots are more than one dot). Unfortunately, the Selection class isn't smart enough, so we have to help with the aggregation and provide a `GROUP BY` declaração, também a condição tem que ser escrita na forma de `HAVING` declaração. +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Retorna os resultados como um array associativo. O primeiro argumento especifica o nome da coluna que será usada como chave no array, o segundo argumento especifica o nome da coluna que será usada como valor: ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book.id) > 3'); +$authors = $explorer->table('author')->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] ``` -Você deve ter notado que a expressão de adesão se refere ao livro, mas não está claro, se vamos aderir através de `author_id` ou `translator_id`. No exemplo acima, Selection se une através da coluna `author_id` porque foi encontrada uma correspondência com a tabela de origem - a tabela `author`. Se não houvesse tal correspondência e houvesse mais possibilidades, a Nette lançaria a [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. +Se especificarmos apenas o primeiro parâmetro, o valor será a linha inteira, ou seja, o objeto `ActiveRow`: + +```php +$authors = $explorer->table('author')->fetchPairs('id'); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` -Para fazer uma junção através da coluna `translator_id`, forneça um parâmetro opcional dentro da expressão de junção. +Em caso de chaves duplicadas, o valor da última linha será usado. Ao usar `null` como chave, o array será indexado numericamente a partir de zero (neste caso, não ocorrem colisões): ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book(translator).id) > 3'); +$authors = $explorer->table('author')->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] ``` -Vamos dar uma olhada em alguma expressão de união mais difícil. -Gostaríamos de encontrar todos os autores que tenham escrito algo sobre PHP. Todos os livros têm tags, então devemos selecionar os autores que escreveram qualquer livro com a tag PHP. +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Alternativamente, pode fornecer um callback como parâmetro, que retornará para cada linha ou o próprio valor, ou um par chave-valor. ```php -$authors = $explorer->table('author'); -$authors->where(':book:book_tags.tag.name', 'PHP') - ->group('author.id') - ->having('COUNT(:book:book_tags.tag.id) > 0'); +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => "$row->title ({$row->author->name})"); +// ['Primeiro livro (Jan Novák)', ...] + +// O callback também pode retornar um array com o par chave & valor: +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => [$row->title, $row->author->name]); +// ['Primeiro livro' => 'Jan Novák', ...] ``` -Consultas agregadas .[#toc-aggregate-queries] ---------------------------------------------- +fetchAll(): array .[method] +--------------------------- -| `$table->count('*')` | Obter número de filas -| `$table->count("DISTINCT $column")` | Obter número de valores distintos -| `$table->min($column)` | Obtenha um valor mínimo -| `$table->max($column)` | Obtenha o valor máximo -| `$table->sum($column)` | Obtenha a soma de todos os valores -| `$table->aggregation("GROUP_CONCAT($column)")` | Executar qualquer função de agregação +Retorna todas as linhas como um array associativo de objetos `ActiveRow`, onde as chaves são os valores das chaves primárias. -.[caution] -O método `count()` sem nenhum parâmetro especificado seleciona todos os registros e retorna o tamanho da matriz, o que é muito ineficiente. Por exemplo, se você precisar calcular o número de linhas para paginação, especifique sempre o primeiro argumento. +```php +$allBooks = $explorer->table('book')->fetchAll(); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` + + +count(): int .[method] +---------------------- + +O método `count()` sem parâmetro retorna o número de linhas no objeto `Selection`: +```php +$table->where('category', 1); +$count = $table->count(); +$count = count($table); // alternativa +``` + +Atenção, `count()` com parâmetro executa a função de agregação COUNT no banco de dados. + + +ActiveRow::toArray(): array .[method] +------------------------------------- + +Converte o objeto `ActiveRow` num array associativo, onde as chaves são os nomes das colunas e os valores são os dados correspondentes. + +```php +$book = $explorer->table('book')->get(1); +$bookArray = $book->toArray(); +// $bookArray será ['id' => 1, 'title' => '...', 'author_id' => ..., ...] +``` + + +Agregação +========= + +A classe `Selection` fornece métodos para executar facilmente funções de agregação (COUNT, SUM, MIN, MAX, AVG, etc.). + +.[language-php] +| `count($expr)` | Conta o número de linhas +| `min($expr)` | Retorna o valor mínimo na coluna +| `max($expr)` | Retorna o valor máximo na coluna +| `sum($expr)` | Retorna a soma dos valores na coluna +| `aggregation($function)` | Permite executar qualquer função de agregação. Ex: `AVG()`, `GROUP_CONCAT()` -Fuga e Cotação .[#toc-escaping-quoting] -======================================= -O Explorador de banco de dados é inteligente e tem parâmetros de escape e identificadores de citações para você. Estas regras básicas precisam ser seguidas, porém: +count(string $expr): int .[method] +---------------------------------- -- palavras-chave, funções, procedimentos devem ser maiúsculas -- colunas e tabelas devem ser em letras minúsculas -- passar variáveis como parâmetros, não concatenar +Executa uma consulta SQL com a função COUNT e retorna o resultado. O método é usado para descobrir quantas linhas correspondem a uma determinada condição: ```php -->where('name like ?', 'John'); // WRONG! gera: `name` `like` ? -->where('name LIKE ?', 'John'); // CORRETO +$count = $table->count('*'); // SELECT COUNT(*) FROM `table` +$count = $table->count('DISTINCT column'); // SELECT COUNT(DISTINCT `column`) FROM `table` +``` + +Atenção, `count()` sem parâmetro apenas retorna o número de linhas no objeto `Selection`, veja [#count()]. -->where('KEY = ?', $value); // ERRADO! KEY é uma palavra-chave -->where('key = ?', $value); // CORRET. gera: `key` = ? -->where('name = ' . $name); // WRONG! sql injection! -->where('name = ?', $name); // CORRETO! +min(string $expr) e max(string $expr) .[method] +----------------------------------------------- -->select('DATE_FORMAT(created, "%d.%m.%Y")'); // WRONG! passar variáveis como parâmetros, não concatenar -->select('DATE_FORMAT(created, ?)', '%d.%m.%Y'); // CORRETO +Os métodos `min()` e `max()` retornam o valor mínimo e máximo na coluna ou expressão especificada: + +```php +// SELECT MAX(`price`) FROM `products` WHERE `active` = 1 +$maxPrice = $products->where('active', true) + ->max('price'); ``` -.[warning] -O uso errado pode produzir furos de segurança +sum(string $expr) .[method] +--------------------------- -Obtenção de dados .[#toc-fetching-data] -======================================= +Retorna a soma dos valores na coluna ou expressão especificada: -| `foreach ($table as $id => $row)` | Iterate over all lines in result -| `$row = $table->get($id)` | Obtenha uma única linha com ID $id da tabela -| `$row = $table->fetch()` | Obtenha a próxima fileira do resultado -| `$array = $table->fetchPairs($key, $value)` | Buscar todos os valores para a matriz associativa -| `$array = $table->fetchPairs($key)` | Traga todas as filas para a matriz associativa -| `count($table)` | Obter o número de filas no conjunto de resultados +```php +// SELECT SUM(`price` * `items_in_stock`) FROM `products` WHERE `active` = 1 +$totalPrice = $products->where('active', true) + ->sum('price * items_in_stock'); +``` -Inserir, atualizar e excluir .[#toc-insert-update-delete] -========================================================= +aggregation(string $function, ?string $groupFunction = null) .[method] +---------------------------------------------------------------------- -O método `insert()` aceita um conjunto de objetos Traversable (por exemplo, [ArrayHash |utils:arrays#ArrayHash] que devolve [formulários |forms:]): +Permite executar qualquer função de agregação. + +```php +// preço médio dos produtos na categoria +$avgPrice = $products->where('category_id', 1) + ->aggregation('AVG(price)'); + +// concatena as tags do produto em uma única string +$tags = $products->where('id', 1) + ->aggregation('GROUP_CONCAT(tag.name) AS tags') + ->fetch() + ->tags; +``` + +Se precisarmos agregar resultados que já resultaram de alguma função de agregação e agrupamento (por exemplo, `SUM(valor)` sobre linhas agrupadas), como segundo argumento, especificamos a função de agregação que deve ser aplicada a esses resultados intermediários: + +```php +// Calcula o preço total dos produtos em estoque para categorias individuais e, em seguida, soma esses preços. +$totalPrice = $products->select('category_id, SUM(price * stock) AS category_total') + ->group('category_id') + ->aggregation('SUM(category_total)', 'SUM'); +``` + +Neste exemplo, primeiro calculamos o preço total dos produtos em cada categoria (`SUM(price * stock) AS category_total`) e agrupamos os resultados por `category_id`. Em seguida, usamos `aggregation('SUM(category_total)', 'SUM')` para somar esses subtotais `category_total`. O segundo argumento `'SUM'` diz que a função SUM deve ser aplicada aos resultados intermediários. + + +Inserir, Atualizar & Excluir +============================ + +O Nette Database Explorer simplifica a inserção, atualização e exclusão de dados. Todos os métodos listados abaixo lançarão uma exceção `Nette\Database\DriverException` em caso de erro. + + +Selection::insert(iterable $data) .[method] +------------------------------------------- + +Insere novos registros na tabela. + +**Inserindo um único registro:** + +Passamos o novo registro como um array associativo ou objeto iterável (por exemplo, ArrayHash usado em [formulários |forms:]), onde as chaves correspondem aos nomes das colunas na tabela. + +Se a tabela tiver uma chave primária definida, o método retorna um objeto `ActiveRow`, que é recarregado do banco de dados para refletir quaisquer alterações feitas no nível do banco de dados (gatilhos, valores padrão de colunas, cálculos de colunas auto-increment). Isso garante a consistência dos dados e o objeto sempre contém os dados atuais do banco de dados. Se não houver uma chave primária única, ele retorna os dados passados na forma de um array. ```php $row = $explorer->table('users')->insert([ - 'name' => $name, - 'year' => $year, + 'name' => 'John Doe', + 'email' => 'john.doe@example.com', ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) +// $row é uma instância de ActiveRow e contém os dados completos da linha inserida, +// incluindo o ID gerado automaticamente e quaisquer alterações feitas por gatilhos +echo $row->id; // Exibe o ID do usuário recém-inserido +echo $row->created_at; // Exibe a hora de criação, se definida por um gatilho ``` -Se a chave primária estiver definida na tabela, um objeto ActiveRow contendo a linha inserida é devolvido. +**Inserindo múltiplos registros de uma vez:** -Inserção múltipla: +O método `insert()` permite inserir vários registros usando uma única consulta SQL. Neste caso, retorna o número de linhas inseridas. ```php -$explorer->table('users')->insert([ +$insertedRows = $explorer->table('users')->insert([ + [ + 'name' => 'John', + 'year' => 1994, + ], [ - 'name' => 'Jim', - 'year' => 1978, - ], [ 'name' => 'Jack', - 'year' => 1987, + 'year' => 1995, ], ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) +// INSERT INTO `users` (`name`, `year`) VALUES ('John', 1994), ('Jack', 1995) +// $insertedRows será 2 +``` + +Como parâmetro, também pode ser passado um objeto `Selection` com uma seleção de dados. + +```php +$newUsers = $explorer->table('potential_users') + ->where('approved', 1) + ->select('name, email'); + +$insertedRows = $explorer->table('users')->insert($newUsers); ``` -Os arquivos ou objetos DateTime podem ser passados como parâmetros: +**Inserindo valores especiais:** + +Como valores, também podemos passar arquivos, objetos DateTime ou literais SQL: ```php $explorer->table('users')->insert([ - 'name' => $name, - 'created' => new DateTime, // or $explorer::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // inserts the file + 'name' => 'John', + 'created_at' => new DateTime, // converte para formato de banco de dados + 'avatar' => fopen('image.jpg', 'rb'), // insere o conteúdo binário do arquivo + 'uuid' => $explorer::literal('UUID()'), // chama a função UUID() ]); ``` -Atualização (retorna a contagem das filas afetadas): + +Selection::update(iterable $data): int .[method] +------------------------------------------------ + +Atualiza linhas na tabela de acordo com o filtro especificado. Retorna o número de linhas realmente alteradas. + +Passamos as colunas a serem alteradas como um array associativo ou objeto iterável (por exemplo, ArrayHash usado em [formulários |forms:]), onde as chaves correspondem aos nomes das colunas na tabela: ```php -$count = $explorer->table('users') - ->where('id', 10) // must be called before update() +$affected = $explorer->table('users') + ->where('id', 10) ->update([ - 'name' => 'Ned Stark' + 'name' => 'John Smith', + 'year' => 1994, ]); -// UPDATE `users` SET `name`='Ned Stark' WHERE (`id` = 10) +// UPDATE `users` SET `name` = 'John Smith', `year` = 1994 WHERE `id` = 10 ``` -Para atualização, podemos utilizar os operadores `+=` a `-=`: +Para alterar valores numéricos, podemos usar os operadores `+=` e `-=`: ```php $explorer->table('users') + ->where('id', 10) ->update([ - 'age+=' => 1, // see += + 'points+=' => 1, // aumenta o valor da coluna 'points' em 1 + 'coins-=' => 1, // diminui o valor da coluna 'coins' em 1 ]); -// UPDATE users SET `age` = `age` + 1 +// UPDATE `users` SET `points` = `points` + 1, `coins` = `coins` - 1 WHERE `id` = 10 ``` -Eliminação (retorna a contagem das linhas eliminadas): + +Selection::delete(): int .[method] +---------------------------------- + +Exclui linhas da tabela de acordo com o filtro especificado. Retorna o número de linhas excluídas. ```php $count = $explorer->table('users') ->where('id', 10) ->delete(); -// DELETE FROM `users` WHERE (`id` = 10) +// DELETE FROM `users` WHERE `id` = 10 ``` +.[caution] +Ao chamar `update()` e `delete()`, não se esqueça de especificar as linhas a serem modificadas/excluídas usando `where()`. Se `where()` não for usado, a operação será realizada em toda a tabela! -Trabalhando com Relacionamentos .[#toc-working-with-relationships] -================================================================== +ActiveRow::update(iterable $data): bool .[method] +------------------------------------------------- -Tem Uma Relação .[#toc-has-one-relation] ----------------------------------------- -Tem uma relação é um caso de uso comum. O livro * tem um* autor. Livro *faz um* tradutor. A obtenção da linha relacionada é feita principalmente pelo método `ref()`. Aceita dois argumentos: nome da tabela de destino e coluna de junção de fonte. Veja o exemplo: +Atualiza os dados na linha do banco de dados representada pelo objeto `ActiveRow`. Como parâmetro, aceita um iterável com os dados a serem atualizados (as chaves são os nomes das colunas). Para alterar valores numéricos, podemos usar os operadores `+=` e `-=`: + +Após a execução da atualização, o `ActiveRow` é automaticamente recarregado do banco de dados para refletir quaisquer alterações feitas no nível do banco de dados (por exemplo, gatilhos). O método retorna true apenas se houve uma alteração real nos dados. ```php -$book = $explorer->table('book')->get(1); -$book->ref('author', 'author_id'); +$article = $explorer->table('article')->get(1); +$article->update([ + 'views += 1', // aumentamos o número de visualizações +]); +echo $article->views; // Exibe o número atual de visualizações ``` -No exemplo acima, buscamos a entrada do autor relacionado na tabela `author`, a chave primária do autor é pesquisada pela coluna `book.author_id`. O método Ref() retorna a instância ActiveRow ou nula se não houver entrada apropriada. A linha retornada é uma instância do ActiveRow para que possamos trabalhar com ela da mesma forma que com a entrada do livro. +Este método atualiza apenas uma linha específica no banco de dados. Para atualização em massa de várias linhas, use o método [#Selection::update()]. + + +ActiveRow::delete() .[method] +----------------------------- + +Exclui a linha do banco de dados representada pelo objeto `ActiveRow`. ```php -$author = $book->ref('author', 'author_id'); -$author->name; -$author->born; +$book = $explorer->table('book')->get(1); +$book->delete(); // Exclui o livro com ID 1 +``` + +Este método exclui apenas uma linha específica no banco de dados. Para exclusão em massa de várias linhas, use o método [#Selection::delete()]. + + +Relações entre tabelas +====================== + +Em bancos de dados relacionais, os dados são divididos em várias tabelas e interligados por chaves estrangeiras. O Nette Database Explorer traz uma maneira revolucionária de trabalhar com essas relações - sem escrever consultas JOIN e sem a necessidade de configurar ou gerar nada. + +Para ilustrar o trabalho com relações, usaremos o exemplo de um banco de dados de livros ([você pode encontrá-lo no GitHub |https://github.com/nette-examples/books]). No banco de dados, temos as tabelas: + +- `author` - escritores e tradutores (colunas `id`, `name`, `web`, `born`) +- `book` - livros (colunas `id`, `author_id`, `translator_id`, `title`, `sequel_id`) +- `tag` - tags (colunas `id`, `name`) +- `book_tag` - tabela de ligação entre livros e tags (colunas `book_id`, `tag_id`) + +[* db-schema-1-.webp *] *** Estrutura do banco de dados usada nos exemplos *** + +Em nosso exemplo de banco de dados de livros, encontramos vários tipos de relacionamentos (embora o modelo seja simplificado em comparação com a realidade): + +- Um-para-muitos 1:N – cada livro **tem um** autor, um autor pode escrever **vários** livros +- Zero-para-muitos 0:N – um livro **pode ter** um tradutor, um tradutor pode traduzir **vários** livros +- Zero-para-um 0:1 – um livro **pode ter** uma sequência +- Muitos-para-muitos M:N – um livro **pode ter várias** tags e uma tag pode ser atribuída a **vários** livros -// or directly -$book->ref('author', 'author_id')->name; -$book->ref('author', 'author_id')->born; +Nesses relacionamentos, sempre existe uma tabela pai e uma tabela filho. Por exemplo, no relacionamento entre autor e livro, a tabela `author` é a pai e `book` é a filho - podemos imaginar que o livro sempre "pertence" a algum autor. Isso também se reflete na estrutura do banco de dados: a tabela filho `book` contém a chave estrangeira `author_id`, que referencia a tabela pai `author`. + +Se precisarmos listar os livros incluindo os nomes de seus autores, temos duas opções. Ou obtemos os dados com uma única consulta SQL usando JOIN: + +```sql +SELECT book.*, author.name FROM book LEFT JOIN author ON book.author_id = author.id +``` + +Ou carregamos os dados em duas etapas - primeiro os livros e depois seus autores - e depois os montamos em PHP: + +```sql +SELECT * FROM book; +SELECT * FROM author WHERE id IN (1, 2, 3); -- ids dos autores dos livros obtidos ``` -O livro também tem um tradutor, portanto, obter o nome do tradutor é bastante fácil. +A segunda abordagem é, na verdade, mais eficiente, embora possa ser surpreendente. Os dados são carregados apenas uma vez e podem ser melhor utilizados no cache. É precisamente desta forma que o Nette Database Explorer funciona - ele resolve tudo nos bastidores e oferece uma API elegante: + ```php -$book->ref('author', 'translator_id')->name +$books = $explorer->table('book'); +foreach ($books as $book) { + echo 'título: ' . $book->title; + echo 'escrito por: ' . $book->author->name; // $book->author é o registro da tabela 'author' + echo 'traduzido por: ' . $book->translator?->name; +} ``` -Tudo isso está bem, mas é um pouco incômodo, não acha? O Database Explorer já contém as definições de chaves estrangeiras, então por que não usá-las automaticamente? Vamos fazer isso! -Se chamarmos propriedade, que não existe, a ActiveRow tenta resolver o nome da propriedade chamadora como 'tem uma' relação. Obter esta propriedade é o mesmo que chamar o método ref() com apenas um argumento. Chamaremos o único argumento de **key***. A chave será resolvida para determinada relação de chave estrangeira. A chave passada é comparada com as colunas de linha, e se corresponder, a chave estrangeira definida na coluna correspondente é usada para obter dados da tabela de destino relacionada. Veja o exemplo: +Acesso à tabela pai +------------------- + +O acesso à tabela pai é direto. Trata-se de relacionamentos como *livro tem um autor* ou *livro pode ter um tradutor*. Obtemos o registro relacionado através da propriedade do objeto ActiveRow - seu nome corresponde ao nome da coluna com a chave estrangeira sem `_id`: ```php -$book->author->name; -// o mesmo que -$book->ref('author')->name; +$book = $explorer->table('book')->get(1); +echo $book->author->name; // encontra o autor pela coluna author_id +echo $book->translator?->name; // encontra o tradutor pela coluna translator_id ``` -A instância ActiveRow não tem coluna de autor. Todas as colunas de livros são pesquisadas para uma correspondência com *chave*. A correspondência, neste caso, significa que o nome da coluna tem que conter a chave. Assim, no exemplo acima, a coluna `author_id` contém a string 'autor' e, portanto, é correspondida pela chave 'autor'. Se você quiser obter o tradutor do livro, basta usar, por exemplo, 'tradutor' como chave, porque a chave 'tradutor' corresponderá à coluna `translator_id`. Você pode encontrar mais sobre a lógica de correspondência da chave no capítulo [Juntando expressões |#joining-key]. +Quando acessamos a propriedade `$book->author`, o Explorer procura na tabela `book` por uma coluna cujo nome contenha a string `author` (ou seja, `author_id`). Com base no valor nesta coluna, ele carrega o registro correspondente da tabela `author` e o retorna como `ActiveRow`. Da mesma forma funciona `$book->translator`, que usa a coluna `translator_id`. Como a coluna `translator_id` pode conter `null`, usamos o operador `?->` no código. + +Um caminho alternativo é oferecido pelo método `ref()`, que aceita dois argumentos, o nome da tabela de destino e o nome da coluna de ligação, e retorna uma instância de `ActiveRow` ou `null`: ```php -echo $book->title . ': '; -echo $book->author->name; -if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; -} +echo $book->ref('author', 'author_id')->name; // relação com o autor +echo $book->ref('author', 'translator_id')->name; // relação com o tradutor ``` -Se você quiser ir buscar vários livros, você deve usar a mesma abordagem. O Nette Database Explorer buscará autores e tradutores para todos os livros buscados de uma só vez. +O método `ref()` é útil se o acesso via propriedade não puder ser usado porque a tabela contém uma coluna com o mesmo nome (ou seja, `author`). Nos outros casos, recomenda-se usar o acesso via propriedade, que é mais legível. + +O Explorer otimiza automaticamente as consultas ao banco de dados. Quando percorremos os livros em um loop e acessamos seus registros relacionados (autores, tradutores), o Explorer não gera uma consulta para cada livro separadamente. Em vez disso, ele executa apenas um SELECT para cada tipo de relacionamento, reduzindo significativamente a carga no banco de dados. Por exemplo: ```php $books = $explorer->table('book'); foreach ($books as $book) { echo $book->title . ': '; echo $book->author->name; - if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; - } + echo $book->translator?->name; } ``` -O código executará apenas estas 3 consultas: +Este código chamará apenas estas três consultas rápidas ao banco de dados: + ```sql SELECT * FROM `book`; -SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- ids of fetched books from author_id column -SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- ids of fetched books from translator_id column +SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- id da coluna author_id dos livros selecionados +SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- id da coluna translator_id dos livros selecionados ``` +.[note] +A lógica para encontrar a coluna de ligação é dada pela implementação de [Conventions |api:Nette\Database\Conventions]. Recomendamos o uso de [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions], que analisa as chaves estrangeiras e permite trabalhar facilmente com os relacionamentos existentes entre as tabelas. + -Tem muitas relações .[#toc-has-many-relation] ---------------------------------------------- +Acesso à tabela filho +--------------------- -A relação "tem muitos" é apenas invertida "tem uma" relação. O autor * tem* escrito *many* livros. O autor * tem* traduzido *homens* livros. Como você pode ver, este tipo de relação é um pouco mais difícil porque a relação é 'nomeada' ('escrita', 'traduzida'). A instância ActiveRow tem o método `related()`, que retornará uma série de entradas relacionadas. As entradas também são instâncias do ActiveRow. Veja o exemplo abaixo: +O acesso à tabela filho funciona na direção oposta. Agora perguntamos *quais livros este autor escreveu* ou *este tradutor traduziu*. Para este tipo de consulta, usamos o método `related()`, que retorna uma `Selection` com os registros relacionados. Vejamos um exemplo: ```php -$author = $explorer->table('author')->get(11); -echo $author->name . ' has written:'; +$author = $explorer->table('author')->get(1); +// Exibe todos os livros do autor foreach ($author->related('book.author_id') as $book) { - echo $book->title; + echo "Escreveu: $book->title"; } -echo 'and translated:'; +// Exibe todos os livros que o autor traduziu foreach ($author->related('book.translator_id') as $book) { - echo $book->title; + echo "Traduziu: $book->title"; } ``` -Método `related()` método aceita a descrição completa de união passada como dois argumentos ou como um argumento unido por ponto. O primeiro argumento é a tabela de destino, o segundo é a coluna de destino. +O método `related()` aceita a descrição da ligação como um único argumento com notação de ponto ou como dois argumentos separados: ```php -$author->related('book.translator_id'); -// o mesmo que -$author->related('book', 'translator_id'); +$author->related('book.translator_id'); // um argumento +$author->related('book', 'translator_id'); // dois argumentos ``` -Você pode usar a heurística do Nette Database Explorer baseada em chaves estrangeiras e fornecer apenas **key*** argumento. A chave será comparada com todas as chaves estrangeiras que apontam para a tabela atual (`author` tabela). Se houver uma correspondência, o Nette Database Explorer utilizará esta chave estrangeira, caso contrário lançará [Nette\InvalidArgumentException |api:Nette\InvalidArgumentException] ou [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. Você pode encontrar mais sobre a lógica de correspondência de chaves no capítulo [Junção de expressões |#joining-key]. +O Explorer pode detectar automaticamente a coluna de ligação correta com base no nome da tabela pai. Neste caso, a ligação é feita através da coluna `book.author_id`, porque o nome da tabela de origem é `author`: -É claro que você pode chamar métodos relacionados para todos os autores buscados, o Nette Database Explorer buscará novamente os livros apropriados de uma só vez. +```php +$author->related('book'); // usa book.author_id +``` + +Se existissem várias ligações possíveis, o Explorer lançaria uma exceção [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. + +O método `related()` pode, obviamente, ser usado também ao percorrer vários registros em um loop, e o Explorer, neste caso, também otimiza automaticamente as consultas: ```php $authors = $explorer->table('author'); foreach ($authors as $author) { - echo $author->name . ' has written:'; + echo $author->name . ' escreveu:'; foreach ($author->related('book') as $book) { - $book->title; + echo $book->title; } } ``` -O exemplo acima só vai dar duas consultas: +Este código gerará apenas duas consultas SQL rápidas: ```sql SELECT * FROM `author`; -SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- ids of fetched authors +SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- id dos autores selecionados +``` + + +Relacionamento Muitos-para-Muitos +--------------------------------- + +Para o relacionamento muitos-para-muitos (M:N), é necessária a existência de uma tabela de ligação (no nosso caso `book_tag`), que contém duas colunas com chaves estrangeiras (`book_id`, `tag_id`). Cada uma dessas colunas referencia a chave primária de uma das tabelas interligadas. Para obter os dados relacionados, primeiro obtemos os registros da tabela de ligação usando `related('book_tag')` e, em seguida, prosseguimos para os dados de destino: + +```php +$book = $explorer->table('book')->get(1); +// exibe os nomes das tags atribuídas ao livro +foreach ($book->related('book_tag') as $bookTag) { + echo $bookTag->tag->name; // exibe o nome da tag através da tabela de ligação +} + +$tag = $explorer->table('tag')->get(1); +// ou o inverso: exibe os nomes dos livros marcados com esta tag +foreach ($tag->related('book_tag') as $bookTag) { + echo $bookTag->book->title; // exibe o nome do livro +} +``` + +O Explorer novamente otimiza as consultas SQL para uma forma eficiente: + +```sql +SELECT * FROM `book`; +SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 2, ...)); -- id dos livros selecionados +SELECT * FROM `tag` WHERE (`tag`.`id` IN (1, 2, ...)); -- id das tags encontradas em book_tag +``` + + +Consulta através de tabelas relacionadas +---------------------------------------- + +Nos métodos `where()`, `select()`, `order()` e `group()`, podemos usar notações especiais para acessar colunas de outras tabelas. O Explorer cria automaticamente os JOINs necessários. + +**Notação de ponto** (`tabela_pai.coluna`) é usada para o relacionamento 1:N do ponto de vista da tabela filho: + +```php +$books = $explorer->table('book'); + +// Encontra livros cujo autor tem nome começando com 'Jon' +$books->where('author.name LIKE ?', 'Jon%'); + +// Ordena os livros pelo nome do autor em ordem decrescente +$books->order('author.name DESC'); + +// Exibe o título do livro e o nome do autor +$books->select('book.title, author.name'); +``` + +**Notação de dois pontos** (`:tabela_filho.coluna`) é usada para o relacionamento 1:N do ponto de vista da tabela pai: + +```php +$authors = $explorer->table('author'); + +// Encontra autores que escreveram um livro com 'PHP' no título +$authors->where(':book.title LIKE ?', '%PHP%'); + +// Conta o número de livros para cada autor +$authors->select('*, COUNT(:book.id) AS book_count') + ->group('author.id'); ``` +No exemplo acima com notação de dois pontos (`:book.title`), a coluna com a chave estrangeira não é especificada. O Explorer detecta automaticamente a coluna correta com base no nome da tabela pai. Neste caso, a ligação é feita através da coluna `book.author_id`, porque o nome da tabela de origem é `author`. Se existissem várias ligações possíveis, o Explorer lançaria uma exceção [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. -Criação manual do Explorer .[#toc-creating-explorer-manually] -============================================================= +A coluna de ligação pode ser explicitamente especificada entre parênteses: -Uma conexão de banco de dados pode ser criada usando a configuração da aplicação. Nesses casos, um serviço `Nette\Database\Explorer` é criado e pode ser passado como uma dependência usando o container DI. +```php +// Encontra autores que traduziram um livro com 'PHP' no título +$authors->where(':book(translator_id).title LIKE ?', '%PHP%'); +``` -Entretanto, se o Nette Database Explorer for usado como uma ferramenta autônoma, uma instância de objeto `Nette\Database\Explorer` precisa ser criada manualmente. +As notações podem ser encadeadas para acesso através de múltiplas tabelas: ```php -// $storage implementa Nette\Caching\Storage -$storage = new Nette\Caching\Storages\FileStorage($tempDir); -$connection = new Nette\Database\Connection($dsn, $user, $password); -$structure = new Nette\Database\Structure($connection, $storage); -$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); -$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +// Encontra autores de livros marcados com a tag 'PHP' +$authors->where(':book:book_tag.tag.name', 'PHP') + ->group('author.id'); ``` + + +Extensão de condições para JOIN +------------------------------- + +O método `joinWhere()` estende as condições que são especificadas ao ligar tabelas em SQL após a palavra-chave `ON`. + +Digamos que queremos encontrar livros traduzidos por um tradutor específico: + +```php +// Encontra livros traduzidos pelo tradutor chamado 'David' +$books = $explorer->table('book') + ->joinWhere('translator', 'translator.name', 'David'); +// LEFT JOIN author translator ON book.translator_id = translator.id AND (translator.name = 'David') +``` + +Na condição `joinWhere()`, podemos usar as mesmas construções que no método `where()` - operadores, placeholders de interrogação, arrays de valores ou expressões SQL. + +Para consultas mais complexas com múltiplos JOINs, podemos definir aliases de tabela: + +```php +$tags = $explorer->table('tag') + ->joinWhere(':book_tag.book.author', 'book_author.born < ?', 1950) + ->alias(':book_tag.book.author', 'book_author'); +// LEFT JOIN `book_tag` ON `tag`.`id` = `book_tag`.`tag_id` +// LEFT JOIN `book` ON `book_tag`.`book_id` = `book`.`id` +// LEFT JOIN `author` `book_author` ON `book`.`author_id` = `book_author`.`id` +// AND (`book_author`.`born` < 1950) +``` + +Observe que, enquanto o método `where()` adiciona condições à cláusula `WHERE`, o método `joinWhere()` estende as condições na cláusula `ON` ao ligar tabelas. diff --git a/database/pt/guide.texy b/database/pt/guide.texy new file mode 100644 index 0000000000..366588d064 --- /dev/null +++ b/database/pt/guide.texy @@ -0,0 +1,216 @@ +Nette Database +************** + +.[perex] +Nette Database é uma camada de banco de dados poderosa e elegante para PHP com ênfase na simplicidade e recursos inteligentes. Oferece duas formas de trabalhar com o banco de dados - [Explorer |explorer] para desenvolvimento rápido de aplicações, ou [Acesso SQL |sql-way] para trabalho direto com consultas. + +<div class="grid gap-3"> +<div> + + +[Acesso SQL |sql-way] +===================== +- Consultas parametrizadas seguras +- Controle preciso sobre a forma das consultas SQL +- Quando você escreve consultas complexas com recursos avançados +- Otimiza o desempenho usando funções SQL específicas + +</div> + +<div> + + +[Explorer |explorer] +==================== +- Desenvolve rapidamente sem escrever SQL +- Trabalho intuitivo com relações entre tabelas +- Você apreciará a otimização automática de consultas +- Adequado para trabalho rápido e confortável com o banco de dados + +</div> + +</div> + + +Instalação +========== + +A biblioteca pode ser baixada e instalada usando a ferramenta [Composer|best-practices:composer]: + +```shell +composer require nette/database +``` + + +Bancos de dados suportados +========================== + +Nette Database suporta os seguintes bancos de dados: + +|* Servidor de banco de dados |* Nome DSN |* Suporte no Explorer +|---------------------|-------------|----------------------- +| MySQL (>= 5.1) | mysql | SIM +| PostgreSQL (>= 9.0) | pgsql | SIM +| Sqlite 3 (>= 3.8) | sqlite | SIM +| Oracle | oci | - +| MS SQL (PDO_SQLSRV) | sqlsrv | SIM +| MS SQL (PDO_DBLIB) | mssql | - +| ODBC | odbc | - + + +Duas abordagens ao banco de dados +================================= + +Nette Database oferece uma escolha: você pode escrever consultas SQL diretamente (acesso SQL) ou deixá-las serem geradas automaticamente (Explorer). Vejamos como ambas as abordagens resolvem as mesmas tarefas: + +[Acesso SQL|sql-way] - Consultas SQL + +```php +// inserção de registro +$database->query('INSERT INTO books', [ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// obtenção de registros: autores de livros +$result = $database->query(' + SELECT authors.*, COUNT(books.id) AS books_count + FROM authors + LEFT JOIN books ON authors.id = books.author_id + WHERE authors.active = 1 + GROUP BY authors.id +'); + +// listagem (não otimizada, gera N consultas adicionais) +foreach ($result as $author) { + $books = $database->query(' + SELECT * FROM books + WHERE author_id = ? + ORDER BY published_at DESC + ', $author->id); + + echo "Autor $author->name escreveu $author->books_count livros:\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +[Acesso Explorer|explorer] - Geração automática de SQL + +```php +// inserção de registro +$database->table('books')->insert([ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// obtenção de registros: autores de livros +$authors = $database->table('authors') + ->where('active', 1); + +// listagem (gera automaticamente apenas 2 consultas otimizadas) +foreach ($authors as $author) { + $books = $author->related('books') + ->order('published_at DESC'); + + echo "Autor $author->name escreveu {$books->count()} livros:\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +A abordagem Explorer gera e otimiza consultas SQL automaticamente. No exemplo fornecido, a abordagem SQL gera N+1 consultas (uma para os autores e depois uma para os livros de cada autor), enquanto o Explorer otimiza automaticamente as consultas e executa apenas duas - uma para os autores e uma para todos os seus livros. + +Ambas as abordagens podem ser combinadas livremente na aplicação conforme necessário. + + +Conexão e configuração +====================== + +Para conectar ao banco de dados, basta criar uma instância da classe [api:Nette\Database\Connection]: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password); +``` + +O parâmetro `$dsn` (data source name) é o mesmo [que o PDO usa |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], por exemplo, `host=127.0.0.1;dbname=test`. Em caso de falha, lança a exceção `Nette\Database\ConnectionException`. + +No entanto, uma maneira mais conveniente é oferecida pela [configuração da aplicação |configuration], onde basta adicionar a seção `database` e os objetos necessários serão criados, assim como o painel do banco de dados na barra [Tracy |tracy:]. + +```neon +database: + dsn: 'mysql:host=127.0.0.1;dbname=test' + user: root + password: password +``` + +Depois, o objeto de conexão [pode ser obtido como um serviço do contêiner DI |dependency-injection:passing-dependencies], por exemplo: + +```php +class Model +{ + public function __construct( + // ou Nette\Database\Explorer + private Nette\Database\Connection $database, + ) { + } +} +``` + +Mais informações sobre a [configuração do banco de dados|configuration]. + + +Criação manual do Explorer +-------------------------- + +Se você não usa o contêiner Nette DI, pode criar a instância `Nette\Database\Explorer` manualmente: + +```php +// conexão com o banco de dados +$connection = new Nette\Database\Connection('mysql:host=127.0.0.1;dbname=mydatabase', 'user', 'password'); +// armazenamento para cache, implementa Nette\Caching\Storage, por exemplo: +$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp/dir'); +// cuida da reflexão da estrutura do banco de dados +$structure = new Nette\Database\Structure($connection, $storage); +// define regras para mapear nomes de tabelas, colunas e chaves estrangeiras +$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); +$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +``` + + +Gerenciamento de conexão +======================== + +Ao criar o objeto `Connection`, a conexão é estabelecida automaticamente. Se você deseja adiar a conexão, use o modo lazy - ative-o na [configuração|configuration] definindo `lazy` como `true`, ou assim: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password, ['lazy' => true]); +``` + +Para gerenciar a conexão, use os métodos `connect()`, `disconnect()` e `reconnect()`. +- `connect()` cria a conexão se ela ainda não existir, podendo lançar a exceção `Nette\Database\ConnectionException`. +- `disconnect()` desconecta a conexão atual com o banco de dados. +- `reconnect()` desconecta e, em seguida, reconecta ao banco de dados. Este método também pode lançar a exceção `Nette\Database\ConnectionException`. + +Além disso, você pode monitorar eventos relacionados à conexão usando o evento `onConnect`, que é um array de callbacks chamados após o estabelecimento da conexão com o banco de dados. + +```php +// ocorre após a conexão com o banco de dados +$database->onConnect[] = function($database) { + echo "Conectado ao banco de dados"; +}; +``` + + +Tracy Debug Bar +=============== + +Se você usa [Tracy |tracy:], o painel Database é ativado automaticamente na Debug Bar, exibindo todas as consultas executadas, seus parâmetros, tempo de execução e o local no código onde foram chamadas. + +[* db-panel.webp *] diff --git a/database/pt/mapping.texy b/database/pt/mapping.texy new file mode 100644 index 0000000000..d6c415f5ed --- /dev/null +++ b/database/pt/mapping.texy @@ -0,0 +1,55 @@ +Conversão de tipos +****************** + +.[perex] +Nette Database converte automaticamente os valores retornados do banco de dados para os tipos PHP correspondentes. + + +Data e hora +----------- + +Os dados de tempo são convertidos em objetos `Nette\Utils\DateTime`. Se você deseja que os dados de tempo sejam convertidos em objetos imutáveis `Nette\Database\DateTime`, defina a opção `newDateTime` como `true` na [configuração|configuration]. + +```php +$row = $database->fetch('SELECT created_at FROM articles'); +echo $row->created_at instanceof DateTime; // true +echo $row->created_at->format('j. n. Y'); +``` + +No caso do MySQL, o tipo de dados `TIME` é convertido em objetos `DateInterval`. + + +Valores booleanos +----------------- + +Os valores booleanos são automaticamente convertidos para `true` ou `false`. No MySQL, `TINYINT(1)` é convertido se definirmos `convertBoolean` como `true` na [configuração|configuration]. + +```php +$row = $database->fetch('SELECT is_published FROM articles'); +echo gettype($row->is_published); // 'boolean' +``` + + +Valores numéricos +----------------- + +Os valores numéricos são convertidos para `int` ou `float` de acordo com o tipo da coluna no banco de dados: + +```php +$row = $database->fetch('SELECT id, price FROM products'); +echo gettype($row->id); // integer +echo gettype($row->price); // float +``` + + +Normalização personalizada +-------------------------- + +Usando o método `setRowNormalizer(?callable $normalizer)`, você pode definir uma função personalizada para transformar linhas do banco de dados. Isso é útil, por exemplo, para a conversão automática de tipos de dados. + +```php +$database->setRowNormalizer(function(array $row, ResultSet $resultSet): array { + // a conversão de tipos ocorre aqui + return $row; +}); +``` diff --git a/database/pt/reflection.texy b/database/pt/reflection.texy new file mode 100644 index 0000000000..fba8302010 --- /dev/null +++ b/database/pt/reflection.texy @@ -0,0 +1,125 @@ +Reflexão da estrutura +********************* + +.{data-version:3.2.1} +Nette Database fornece ferramentas para introspecção da estrutura do banco de dados usando a classe [api:Nette\Database\Reflection]. Ela permite obter informações sobre tabelas, colunas, índices e chaves estrangeiras. Você pode usar a reflexão para gerar esquemas, criar aplicações flexíveis que trabalham com o banco de dados ou ferramentas gerais de banco de dados. + +Obtemos o objeto de reflexão da instância de conexão com o banco de dados: + +```php +$reflection = $database->getReflection(); +``` + + +Obtenção de tabelas +------------------- + +A propriedade readonly `$reflection->tables` contém um array associativo de todas as tabelas no banco de dados: + +```php +// Listagem dos nomes de todas as tabelas +foreach ($reflection->tables as $name => $table) { + echo $name . "\n"; +} +``` + +Existem mais dois métodos disponíveis: + +```php +// Verificação da existência da tabela +if ($reflection->hasTable('users')) { + echo "A tabela users existe"; +} + +// Retorna o objeto da tabela; se não existir, lança uma exceção +$table = $reflection->getTable('users'); +``` + + +Informações sobre a tabela +-------------------------- + +A tabela é representada pelo objeto [Table|api:Nette\Database\Reflection\Table], que fornece as seguintes propriedades readonly: + +- `$name: string` – nome da tabela +- `$view: bool` – se é uma view +- `$fullName: ?string` – nome completo da tabela incluindo o esquema (se existir) +- `$columns: array<string, Column>` – array associativo das colunas da tabela +- `$indexes: Index[]` – array de índices da tabela +- `$primaryKey: ?Index` – chave primária da tabela ou null +- `$foreignKeys: ForeignKey[]` – array de chaves estrangeiras da tabela + + +Colunas +------- + +A propriedade `columns` da tabela fornece um array associativo de colunas, onde a chave é o nome da coluna e o valor é uma instância de [Column|api:Nette\Database\Reflection\Column] com estas propriedades: + +- `$name: string` – nome da coluna +- `$table: ?Table` – referência à tabela da coluna +- `$nativeType: string` – tipo de dados nativo do banco de dados +- `$size: ?int` – tamanho/comprimento do tipo +- `$nullable: bool` – se a coluna pode conter NULL +- `$default: mixed` – valor padrão da coluna +- `$autoIncrement: bool` – se a coluna é auto-increment +- `$primary: bool` – se faz parte da chave primária +- `$vendor: array` – metadados adicionais específicos do sistema de banco de dados + +```php +foreach ($table->columns as $name => $column) { + echo "Coluna: $name\n"; + echo "Tipo: {$column->nativeType}\n"; + echo "Nullable: " . ($column->nullable ? 'Sim' : 'Não') . "\n"; +} +``` + + +Índices +------- + +A propriedade `indexes` da tabela fornece um array de índices, onde cada índice é uma instância de [Index|api:Nette\Database\Reflection\Index] com estas propriedades: + +- `$columns: Column[]` – array de colunas que formam o índice +- `$unique: bool` – se o índice é único +- `$primary: bool` – se é a chave primária +- `$name: ?string` – nome do índice + +A chave primária da tabela pode ser obtida usando a propriedade `primaryKey`, que retorna ou um objeto `Index`, ou `null` caso a tabela não tenha chave primária. + +```php +// Listagem de índices +foreach ($table->indexes as $index) { + $columns = implode(', ', array_map(fn($col) => $col->name, $index->columns)); + echo "Índice" . ($index->name ? " {$index->name}" : '') . ":\n"; + echo " Colunas: $columns\n"; + echo " Unique: " . ($index->unique ? 'Sim' : 'Não') . "\n"; +} + +// Listagem da chave primária +if ($primaryKey = $table->primaryKey) { + $columns = implode(', ', array_map(fn($col) => $col->name, $primaryKey->columns)); + echo "Chave primária: $columns\n"; +} +``` + + +Chaves estrangeiras +------------------- + +A propriedade `foreignKeys` da tabela fornece um array de chaves estrangeiras, onde cada chave estrangeira é uma instância de [ForeignKey|api:Nette\Database\Reflection\ForeignKey] com estas propriedades: + +- `$foreignTable: Table` – tabela referenciada +- `$localColumns: Column[]` – array de colunas locais +- `$foreignColumns: Column[]` – array de colunas referenciadas +- `$name: ?string` – nome da chave estrangeira + +```php +// Listagem de chaves estrangeiras +foreach ($table->foreignKeys as $fk) { + $localCols = implode(', ', array_map(fn($col) => $col->name, $fk->localColumns)); + $foreignCols = implode(', ', array_map(fn($col) => $col->name, $fk->foreignColumns)); + + echo "FK" . ($fk->name ? " {$fk->name}" : '') . ":\n"; + echo " $localCols -> {$fk->foreignTable->name}($foreignCols)\n"; +} +``` diff --git a/database/pt/security.texy b/database/pt/security.texy new file mode 100644 index 0000000000..e225154bed --- /dev/null +++ b/database/pt/security.texy @@ -0,0 +1,185 @@ +Riscos de segurança +******************* + +<div class=perex> + +O banco de dados frequentemente contém dados sensíveis e permite a execução de operações perigosas. Para trabalhar com segurança com Nette Database, é crucial: + +- Compreender a diferença entre API segura e insegura +- Usar consultas parametrizadas +- Validar corretamente os dados de entrada + +</div> + + +O que é SQL Injection? +====================== + +SQL injection é o risco de segurança mais grave ao trabalhar com um banco de dados. Ocorre quando uma entrada não tratada do usuário se torna parte de uma consulta SQL. Um invasor pode inserir seus próprios comandos SQL e, assim: +- Obter acesso não autorizado aos dados +- Modificar ou excluir dados no banco de dados +- Contornar a autenticação + +```php +// ❌ CÓDIGO PERIGOSO - vulnerável a SQL injection +$database->query("SELECT * FROM users WHERE name = '$_GET[name]'"); + +// O invasor pode inserir, por exemplo, o valor: ' OR '1'='1 +// A consulta resultante será: SELECT * FROM users WHERE name = '' OR '1'='1' +// O que retorna todos os usuários +``` + +O mesmo se aplica ao Database Explorer: + +```php +// ❌ CÓDIGO PERIGOSO - vulnerável a SQL injection +$table->where('name = ' . $_GET['name']); +$table->where("name = '$_GET[name]'"); +``` + + +Consultas parametrizadas +======================== + +A defesa básica contra SQL injection são as consultas parametrizadas. Nette Database oferece várias maneiras de usá-las. + +A maneira mais simples é usar **placeholders de interrogação**: + +```php +// ✅ Consulta parametrizada segura +$database->query('SELECT * FROM users WHERE name = ?', $name); + +// ✅ Condição segura no Explorer +$table->where('name = ?', $name); +``` + +Isso se aplica a todos os outros métodos no [Database Explorer|explorer] que permitem inserir expressões com placeholders de interrogação e parâmetros. + +Para comandos INSERT, UPDATE ou a cláusula WHERE, podemos passar valores em um array: + +```php +// ✅ INSERT seguro +$database->query('INSERT INTO users', [ + 'name' => $name, + 'email' => $email, +]); + +// ✅ INSERT seguro no Explorer +$table->insert([ + 'name' => $name, + 'email' => $email, +]); +``` + + +Validação dos valores dos parâmetros +==================================== + +Consultas parametrizadas são o pilar fundamental do trabalho seguro com bancos de dados. No entanto, os valores que inserimos nelas devem passar por vários níveis de verificação: + + +Verificação de tipo +------------------- + +**O mais importante é garantir o tipo de dados correto dos parâmetros** - esta é uma condição necessária para o uso seguro do Nette Database. O banco de dados assume que todos os dados de entrada têm o tipo de dados correto correspondente à coluna específica. + +Por exemplo, se `$name` nos exemplos anteriores fosse inesperadamente um array em vez de uma string, o Nette Database tentaria inserir todos os seus elementos na consulta SQL, o que levaria a um erro. Portanto, **nunca use** dados não validados de `$_GET`, `$_POST` ou `$_COOKIE` diretamente em consultas de banco de dados. + + +Verificação de formato +---------------------- + +No segundo nível, verificamos o formato dos dados - por exemplo, se as strings estão na codificação UTF-8 e seu comprimento corresponde à definição da coluna, ou se os valores numéricos estão dentro do intervalo permitido para o tipo de dados da coluna. + +Neste nível de validação, podemos confiar parcialmente no próprio banco de dados - muitos bancos de dados rejeitarão dados inválidos. No entanto, o comportamento pode variar, alguns podem truncar silenciosamente strings longas ou cortar números fora do intervalo. + + +Verificação de domínio +---------------------- + +O terceiro nível representa verificações lógicas específicas da sua aplicação. Por exemplo, verificar se os valores das caixas de seleção correspondem às opções oferecidas, se os números estão no intervalo esperado (por exemplo, idade 0-150 anos) ou se as dependências mútuas entre os valores fazem sentido. + + +Métodos de validação recomendados +--------------------------------- + +- Use [Nette Forms |forms:], que garantem automaticamente a validação correta de todas as entradas +- Use [Presenters |application:] e especifique os tipos de dados para os parâmetros nos métodos `action*()` e `render*()` +- Ou implemente sua própria camada de validação usando ferramentas PHP padrão como `filter_var()` + + +Trabalho seguro com colunas +=========================== + +Na seção anterior, mostramos como validar corretamente os valores dos parâmetros. No entanto, ao usar arrays em consultas SQL, devemos prestar a mesma atenção às suas chaves. + +```php +// ❌ CÓDIGO PERIGOSO - as chaves no array não são tratadas +$database->query('INSERT INTO users', $_POST); +``` + +Para comandos INSERT e UPDATE, isso é uma falha de segurança crítica - um invasor pode inserir ou alterar qualquer coluna no banco de dados. Ele poderia, por exemplo, definir `is_admin = 1` ou inserir dados arbitrários em colunas sensíveis (a chamada Mass Assignment Vulnerability). + +Nas condições WHERE, é ainda mais perigoso, pois podem conter operadores: + +```php +// ❌ CÓDIGO PERIGOSO - as chaves no array não são tratadas +$_POST['salary >'] = 100000; +$database->query('SELECT * FROM users WHERE', $_POST); +// executa a consulta WHERE (`salary` > 100000) +``` + +Um invasor pode usar essa abordagem para descobrir sistematicamente os salários dos funcionários. Ele pode começar, por exemplo, com uma consulta por salários acima de 100.000, depois abaixo de 50.000 e, estreitando gradualmente o intervalo, pode revelar os salários aproximados de todos os funcionários. Esse tipo de ataque é chamado de SQL enumeration. + +Os métodos `where()` e `whereOr()` são ainda [muito mais flexíveis |explorer#where] e suportam expressões SQL, incluindo operadores e funções, nas chaves e valores. Isso dá ao invasor a possibilidade de realizar SQL injection: + +```php +// ❌ CÓDIGO PERIGOSO - o invasor pode inserir seu próprio SQL +$_POST = ['0) UNION SELECT name, salary FROM users WHERE (1']; +$table->where($_POST); +// executa a consulta WHERE (0) UNION SELECT name, salary FROM users WHERE (1) +``` + +Este ataque encerra a condição original com `0)`, anexa seu próprio `SELECT` usando `UNION` para obter dados sensíveis da tabela `users` e fecha a consulta sintaticamente correta com `WHERE (1)`. + + +Whitelist de colunas +-------------------- + +Para trabalhar com segurança com nomes de colunas, precisamos de um mecanismo que garanta que o usuário só possa trabalhar com colunas permitidas e não possa adicionar as suas próprias. Poderíamos tentar detectar e bloquear nomes de colunas perigosos (blacklist), mas essa abordagem não é confiável - um invasor sempre pode encontrar uma nova maneira de escrever um nome de coluna perigoso que não previmos. + +Portanto, é muito mais seguro inverter a lógica e definir uma lista explícita de colunas permitidas (whitelist): + +```php +// Colunas que o usuário pode editar +$allowedColumns = ['name', 'email', 'active']; + +// Removemos todas as colunas não permitidas da entrada +$filteredData = array_intersect_key($userData, array_flip($allowedColumns)); + +// ✅ Agora podemos usar com segurança em consultas, como por exemplo: +$database->query('INSERT INTO users', $filteredData); +$table->update($filteredData); +$table->where($filteredData); +``` + + +Identificadores dinâmicos +========================= + +Para nomes dinâmicos de tabelas e colunas, use o placeholder `?name`. Isso garante o escape correto dos identificadores de acordo com a sintaxe do banco de dados específico (por exemplo, usando crases no MySQL): + +```php +// ✅ Uso seguro de identificadores confiáveis +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name', $column, $table); +// Resultado no MySQL: SELECT `name` FROM `users` +``` + +Importante: use o símbolo `?name` apenas para valores confiáveis definidos no código da aplicação. Para valores do usuário, use novamente a [whitelist |#Whitelist de colunas]. Caso contrário, você se expõe a riscos de segurança: + +```php +// ❌ PERIGOSO - nunca use entrada do usuário +$database->query('SELECT ?name FROM users', $_GET['column']); +``` diff --git a/database/pt/sql-way.texy b/database/pt/sql-way.texy new file mode 100644 index 0000000000..11652311b3 --- /dev/null +++ b/database/pt/sql-way.texy @@ -0,0 +1,513 @@ +Acesso SQL +********** + +.[perex] +A Nette Database oferece dois caminhos: você pode escrever consultas SQL você mesmo (acesso SQL), ou deixá-las serem geradas automaticamente (veja [Explorer |explorer]). O acesso SQL dá a você controle total sobre as consultas, garantindo ao mesmo tempo sua construção segura. + +.[note] +Detalhes sobre conexão e configuração do banco de dados podem ser encontrados no capítulo [Conexão e configuração |guide#Conexão e configuração]. + + +Consultas básicas +================= + +Para consultar o banco de dados, use o método `query()`. Ele retorna um objeto [ResultSet |api:Nette\Database\ResultSet], que representa o resultado da consulta. Em caso de falha, o método [lança uma exceção|exceptions]. Podemos percorrer o resultado da consulta usando um loop `foreach`, ou usar uma das [funções auxiliares |#Obtenção de dados]. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; +} +``` + +Para inserir valores com segurança em consultas SQL, usamos consultas parametrizadas. A Nette Database torna isso o mais simples possível - basta adicionar uma vírgula e o valor após a consulta SQL: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Com múltiplos parâmetros, você tem duas opções de escrita. Você pode "intercalar" a consulta SQL com parâmetros: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age); +``` + +Ou escrever a consulta SQL inteira primeiro e depois anexar todos os parâmetros: + +```php +$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age); +``` + + +Proteção contra SQL injection +============================= + +Por que é importante usar consultas parametrizadas? Porque elas protegem você contra um ataque chamado SQL injection, no qual um invasor poderia injetar seus próprios comandos SQL e, assim, obter ou danificar dados no banco de dados. + +.[warning] +**Nunca insira variáveis diretamente na consulta SQL!** Sempre use consultas parametrizadas, que protegem você contra SQL injection. + +```php +// ❌ CÓDIGO PERIGOSO - vulnerável a SQL injection +$database->query("SELECT * FROM users WHERE name = '$name'"); + +// ✅ Consulta parametrizada segura +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Familiarize-se com os [possíveis riscos de segurança |security]. + + +Técnicas de consulta +==================== + + +Condições WHERE +--------------- + +As condições WHERE podem ser escritas como um array associativo, onde as chaves são os nomes das colunas e os valores são os dados para comparação. A Nette Database seleciona automaticamente o operador SQL mais apropriado com base no tipo de valor. + +```php +$database->query('SELECT * FROM users WHERE', [ + 'name' => 'John', + 'active' => true, +]); +// WHERE `name` = 'John' AND `active` = 1 +``` + +Na chave, você também pode especificar explicitamente o operador para comparação: + +```php +$database->query('SELECT * FROM users WHERE', [ + 'age >' => 25, // usa o operador > + 'name LIKE' => '%John%', // usa o operador LIKE + 'email NOT LIKE' => '%example.com%', // usa o operador NOT LIKE +]); +// WHERE `age` > 25 AND `name` LIKE '%John%' AND `email` NOT LIKE '%example.com%' +``` + +Nette trata automaticamente casos especiais como valores `null` ou arrays. + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name' => 'Laptop', // usa o operador = + 'category_id' => [1, 2, 3], // usa IN + 'description' => null, // usa IS NULL +]); +// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL +``` + +Para condições negativas, use o operador `NOT`: + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name NOT' => 'Laptop', // usa o operador <> + 'category_id NOT' => [1, 2, 3], // usa NOT IN + 'description NOT' => null, // usa IS NOT NULL + 'id' => [], // será omitido +]); +// WHERE `name` <> 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL +``` + +Para combinar condições, o operador `AND` é usado. Isso pode ser alterado usando o [placeholder ?or |#Dicas para construir SQL]. + + +Regras ORDER BY +--------------- + +A ordenação `ORDER BY` pode ser escrita usando um array. Nas chaves, especificamos as colunas e o valor será um booleano indicando se a ordenação é ascendente: + +```php +$database->query('SELECT id FROM author ORDER BY', [ + 'id' => true, // ascendente + 'name' => false, // descendente +]); +// SELECT id FROM author ORDER BY `id`, `name` DESC +``` + + +Inserção de dados (INSERT) +-------------------------- + +Para inserir registros, usa-se o comando SQL `INSERT`. + +```php +$values = [ + 'name' => 'John Doe', + 'email' => 'john@example.com', +]; +$database->query('INSERT INTO users ?', $values); +$userId = $database->getInsertId(); +``` + +O método `getInsertId()` retorna o ID da última linha inserida. Em alguns bancos de dados (por exemplo, PostgreSQL), é necessário especificar como parâmetro o nome da sequência da qual o ID deve ser gerado usando `$database->getInsertId($sequenceId)`. + +Como parâmetros, também podemos passar [#valores especiais] como arquivos, objetos DateTime ou tipos enumerados. + +Inserção de múltiplos registros de uma vez: + +```php +$database->query('INSERT INTO users ?', [ + ['name' => 'User 1', 'email' => 'user1@mail.com'], + ['name' => 'User 2', 'email' => 'user2@mail.com'], +]); +``` + +Um INSERT múltiplo é muito mais rápido porque uma única consulta ao banco de dados é executada, em vez de muitas individuais. + +**Aviso de segurança:** Nunca use dados não validados como `$values`. Familiarize-se com os [possíveis riscos |security#Trabalho seguro com colunas]. + + +Atualização de dados (UPDATE) +----------------------------- + +Para atualizar registros, usa-se o comando SQL `UPDATE`. + +```php +// Atualização de um único registro +$values = [ + 'name' => 'John Smith', +]; +$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1); +``` + +O número de linhas afetadas é retornado por `$result->getRowCount()`. + +Para UPDATE, podemos usar os operadores `+=` e `-=`: + +```php +$database->query('UPDATE users SET ? WHERE id = ?', [ + 'login_count+=' => 1, // incrementa login_count +], 1); +``` + +Exemplo de inserção ou atualização de um registro, se ele já existir. Usamos a técnica `ON DUPLICATE KEY UPDATE`: + +```php +$values = [ + 'name' => $name, + 'year' => $year, +]; +$database->query('INSERT INTO users ? ON DUPLICATE KEY UPDATE ?', + $values + ['id' => $id], + $values, +); +// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) +// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 +``` + +Observe que a Nette Database reconhece em qual contexto do comando SQL o parâmetro com o array é inserido e constrói o código SQL a partir dele de acordo. Assim, do primeiro array, ele construiu `(id, name, year) VALUES (123, 'Jim', 1978)`, enquanto o segundo foi convertido para a forma `name = 'Jim', year = 1978`. Discutimos isso em mais detalhes na seção [#Dicas para construir SQL]. + + +Exclusão de dados (DELETE) +-------------------------- + +Para excluir registros, usa-se o comando SQL `DELETE`. Exemplo com obtenção do número de linhas excluídas: + +```php +$count = $database->query('DELETE FROM users WHERE id = ?', 1) + ->getRowCount(); +``` + + +Dicas para construir SQL +------------------------ + +Uma dica é um placeholder especial na consulta SQL que diz como o valor do parâmetro deve ser reescrito em uma expressão SQL: + +| Dica | Descrição | Usado automaticamente +|-----------|-------------------------------------------------|----------------------------- +| `?name` | usa para inserir nome da tabela ou coluna | - +| `?values` | gera `(key, ...) VALUES (value, ...)` | `INSERT ... ?`, `REPLACE ... ?` +| `?set` | gera atribuição `key = value, ...` | `SET ?`, `KEY UPDATE ?` +| `?and` | combina condições no array com o operador `AND` | `WHERE ?`, `HAVING ?` +| `?or` | combina condições no array com o operador `OR` | - +| `?order` | gera a cláusula `ORDER BY` | `ORDER BY ?`, `GROUP BY ?` + +Para inserir dinamicamente nomes de tabelas e colunas na consulta, use o placeholder `?name`. A Nette Database cuida do tratamento correto dos identificadores de acordo com as convenções do banco de dados específico (por exemplo, envolvendo em crases no MySQL). + +```php +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table); +// SELECT `name` FROM `users` WHERE id = 1 (no MySQL) +``` + +**Aviso:** use o símbolo `?name` apenas para nomes de tabelas e colunas de entradas validadas, caso contrário, você se expõe a um [risco de segurança |security#Identificadores dinâmicos]. + +Outras dicas geralmente não precisam ser especificadas, pois Nette usa detecção automática inteligente ao montar a consulta SQL (veja a terceira coluna da tabela). Mas você pode usá-la, por exemplo, em uma situação em que deseja combinar condições usando `OR` em vez de `AND`: + +```php +$database->query('SELECT * FROM users WHERE ?or', [ + 'name' => 'John', + 'email' => 'john@example.com', +]); +// SELECT * FROM users WHERE `name` = 'John' OR `email` = 'john@example.com' +``` + + +Valores especiais +----------------- + +Além dos tipos escalares comuns (string, int, bool), você também pode passar valores especiais como parâmetros: + +- arquivos: `fopen('image.gif', 'r')` insere o conteúdo binário do arquivo +- data e hora: objetos `DateTime` são convertidos para o formato do banco de dados +- tipos enumerados: instâncias `enum` são convertidas para seu valor +- literais SQL: criados com `Connection::literal('NOW()')` são inseridos diretamente na consulta + +```php +$database->query('INSERT INTO articles ?', [ + 'title' => 'My Article', + 'published_at' => new DateTime, + 'content' => fopen('image.png', 'r'), + 'state' => Status::Draft, +]); +``` + +Para bancos de dados que não têm suporte nativo para o tipo de dados `datetime` (como SQLite e Oracle), `DateTime` é convertido para o valor especificado na [configuração do banco de dados|configuration] pelo item `formatDateTime` (o valor padrão é `U` - timestamp Unix). + + +Literais SQL +------------ + +Em alguns casos, você precisa especificar diretamente o código SQL como um valor, que não deve ser entendido como uma string e escapado. Para isso, servem os objetos da classe `Nette\Database\SqlLiteral`. Eles são criados pelo método `Connection::literal()`. + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + 'year >' => $database::literal('YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) +``` + +Ou alternativamente: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) +``` + +Literais SQL podem conter parâmetros: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > ? AND year < ?', $min, $max), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) +``` + +Graças a isso, podemos criar combinações interessantes: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('?or', [ + 'active' => true, + 'role' => $role, + ]), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') +``` + + +Obtenção de dados +================= + + +Atalhos para consultas SELECT +----------------------------- + +Para simplificar a recuperação de dados, `Connection` oferece vários atalhos que combinam a chamada `query()` com a subsequente `fetch*()`. Esses métodos aceitam os mesmos parâmetros que `query()`, ou seja, a consulta SQL e parâmetros opcionais. Uma descrição completa dos métodos `fetch*()` pode ser encontrada [abaixo |#fetch]. + +| `fetch($sql, ...$params): ?Row` | Executa a consulta e retorna a primeira linha como um objeto `Row` +| `fetchAll($sql, ...$params): array` | Executa a consulta e retorna todas as linhas como um array de objetos `Row` +| `fetchPairs($sql, ...$params): array` | Executa a consulta e retorna um array associativo, onde a primeira coluna representa a chave e a segunda o valor +| `fetchField($sql, ...$params): mixed` | Executa a consulta e retorna o valor do primeiro campo da primeira linha +| `fetchList($sql, ...$params): ?array` | Executa a consulta e retorna a primeira linha como um array indexado + +Exemplo: + +```php +// fetchField() - retorna o valor da primeira célula +$count = $database->query('SELECT COUNT(*) FROM articles') + ->fetchField(); +``` + + +`foreach` - iteração sobre linhas +--------------------------------- + +Após a execução da consulta, é retornado um objeto [ResultSet|api:Nette\Database\ResultSet], que permite percorrer os resultados de várias maneiras. A maneira mais fácil de executar uma consulta e obter linhas é iterando em um loop `foreach`. Este método é o mais eficiente em termos de memória, pois retorna os dados gradualmente e não os armazena na memória de uma vez. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; + // ... +} +``` + +.[note] +`ResultSet` só pode ser iterado uma vez. Se precisar iterar repetidamente, você deve primeiro carregar os dados em um array, por exemplo, usando o método `fetchAll()`. + + +fetch(): ?Row .[method] +----------------------- + +Retorna a linha como um objeto `Row`. Se não houver mais linhas, retorna `null`. Move o ponteiro interno para a próxima linha. + +```php +$result = $database->query('SELECT * FROM users'); +$row = $result->fetch(); // carrega a primeira linha +if ($row) { + echo $row->name; +} +``` + + +fetchAll(): array .[method] +--------------------------- + +Retorna todas as linhas restantes do `ResultSet` como um array de objetos `Row`. + +```php +$result = $database->query('SELECT * FROM users'); +$rows = $result->fetchAll(); // carrega todas as linhas +foreach ($rows as $row) { + echo $row->name; +} +``` + + +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Retorna os resultados como um array associativo. O primeiro argumento especifica o nome da coluna a ser usada como chave no array, o segundo argumento especifica o nome da coluna a ser usada como valor: + +```php +$result = $database->query('SELECT id, name FROM users'); +$names = $result->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] +``` + +Se especificarmos apenas o primeiro parâmetro, o valor será a linha inteira, ou seja, o objeto `Row`: + +```php +$rows = $result->fetchPairs('id'); +// [1 => Row(id: 1, name: 'John'), 2 => Row(id: 2, name: 'Jane'), ...] +``` + +Em caso de chaves duplicadas, o valor da última linha é usado. Ao usar `null` como chave, o array será indexado numericamente a partir de zero (então não ocorrem colisões): + +```php +$names = $result->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] +``` + + +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Alternativamente, você pode fornecer um callback como parâmetro, que retornará para cada linha ou o próprio valor, ou um par chave-valor. + +```php +$result = $database->query('SELECT * FROM users'); +$items = $result->fetchPairs(fn($row) => "$row->id - $row->name"); +// ['1 - John', '2 - Jane', ...] + +// O callback também pode retornar um array com um par chave & valor: +$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]); +// ['John' => 46, 'Jane' => 21, ...] +``` + + +fetchField(): mixed .[method] +----------------------------- + +Retorna o valor do primeiro campo da linha atual. Se não houver mais linhas, retorna `null`. Move o ponteiro interno para a próxima linha. + +```php +$result = $database->query('SELECT name FROM users'); +$name = $result->fetchField(); // carrega o nome da primeira linha +``` + + +fetchList(): ?array .[method] +----------------------------- + +Retorna a linha como um array indexado. Se não houver mais linhas, retorna `null`. Move o ponteiro interno para a próxima linha. + +```php +$result = $database->query('SELECT name, email FROM users'); +$row = $result->fetchList(); // ['John', 'john@example.com'] +``` + + +getRowCount(): ?int .[method] +----------------------------- + +Retorna o número de linhas afetadas pela última consulta `UPDATE` ou `DELETE`. Para `SELECT`, é o número de linhas retornadas, mas isso pode não ser conhecido - nesse caso, o método retorna `null`. + + +getColumnCount(): ?int .[method] +-------------------------------- + +Retorna o número de colunas no `ResultSet`. + + +Informações sobre consultas +=========================== + +Para fins de depuração, podemos obter informações sobre a última consulta executada: + +```php +echo $database->getLastQueryString(); // imprime a consulta SQL + +$result = $database->query('SELECT * FROM articles'); +echo $result->getQueryString(); // imprime a consulta SQL +echo $result->getTime(); // imprime o tempo de execução em segundos +``` + +Para exibir o resultado como uma tabela HTML, pode-se usar: + +```php +$result = $database->query('SELECT * FROM articles'); +$result->dump(); +``` + +ResultSet oferece informações sobre os tipos das colunas: + +```php +$result = $database->query('SELECT * FROM articles'); +$types = $result->getColumnTypes(); + +foreach ($types as $column => $type) { + echo "$column é do tipo $type->type"; // por exemplo, 'id é do tipo int' +} +``` + + +Registro de consultas +--------------------- + +Podemos implementar nosso próprio registro de consultas. O evento `onQuery` é um array de callbacks que são chamados após cada consulta executada: + +```php +$database->onQuery[] = function ($database, $result) use ($logger) { + $logger->info('Query: ' . $result->getQueryString()); + $logger->info('Time: ' . $result->getTime()); + + if ($result->getRowCount() > 1000) { + $logger->warning('Large result set: ' . $result->getRowCount() . ' rows'); + } +}; +``` diff --git a/database/pt/transactions.texy b/database/pt/transactions.texy new file mode 100644 index 0000000000..eaa93de03e --- /dev/null +++ b/database/pt/transactions.texy @@ -0,0 +1,43 @@ +Transações +********** + +.[perex] +As transações garantem que todas as operações dentro de uma transação sejam executadas ou nenhuma delas seja executada. Elas são úteis para garantir a consistência dos dados em operações mais complexas. + +A maneira mais simples de usar transações é assim: + +```php +$database->beginTransaction(); +try { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); + $database->commit(); +} catch (\Exception $e) { + $database->rollBack(); + throw $e; +} +``` + +Você pode escrever a mesma coisa de forma muito mais elegante usando o método `transaction()`. Ele recebe um callback como parâmetro, que executa dentro da transação. Se o callback for executado sem exceção, a transação é automaticamente confirmada. Se ocorrer uma exceção, a transação é cancelada (rollback) e a exceção é propagada. + +```php +$database->transaction(function ($database) use ($id) { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); +}); +``` + +O método `transaction()` também pode retornar valores: + +```php +$count = $database->transaction(function ($database) { + $result = $database->query('UPDATE users SET active = ?', true); + return $result->getRowCount(); // retorna o número de linhas atualizadas +}); +``` diff --git a/database/ro/@home.texy b/database/ro/@home.texy index b3299b6084..e6e3ddb3ab 100644 --- a/database/ro/@home.texy +++ b/database/ro/@home.texy @@ -1,20 +1,21 @@ -Servere acceptate -================= +Baze de date suportate +====================== -Sunt acceptate aceste servere de baze de date: +Nette suportă următoarele baze de date: -|* Server de baze de date |* Nume DSN |* Suport de bază |* Suport pentru Explorer -| MySQL (>= 5.1) | mysql | YES | YES | YES -| PostgreSQL (>= 9.0) | pgsql | YES | YES | YES -Sqlite 3 (>= 3.8) | sqlite | YES | YES | YES -Oracle | oci | oci | YES | - -MS SQL (PDO_SQLSRV) | sqlsrv | YES | YES | YES -MS SQL (PDO_DBLIB) | mssql | YES | YES | - -ODBC | odbc | odbc | YES | - +|* Server bază de date |* Nume DSN |* Suport în Core |* Suport în Explorer +| MySQL (>= 5.1) | mysql | DA | DA +| PostgreSQL (>= 9.0) | pgsql | DA | DA +| Sqlite 3 (>= 3.8) | sqlite | DA | DA +| Oracle | oci | DA | - +| MS SQL (PDO_SQLSRV) | sqlsrv | DA | DA +| MS SQL (PDO_DBLIB) | mssql | DA | - +| ODBC | odbc | DA | - -{{title: Nette Database}} -{{description: Nette Database simplifică în mod semnificativ recuperarea datelor din baza de date fără a scrie interogări SQL. Folosește interogări eficiente și nu transmite date inutile.}} + +{{maintitle: Nette Database - awesome database layer for PHP}} +{{description: Nette Database simplifică semnificativ recuperarea datelor din baza de date fără a fi nevoie să scrieți interogări SQL. Execută interogări eficiente și nu transferă date inutile.}} diff --git a/database/ro/@left-menu.texy b/database/ro/@left-menu.texy index 2ca017bccc..7e37400986 100644 --- a/database/ro/@left-menu.texy +++ b/database/ro/@left-menu.texy @@ -1,5 +1,12 @@ -Baza de date -************ -- [Nucleu |Core] -- [Explorator |Explorer] -- [Configurație |Configuration] +Nette Database +************** +- [Introducere |guide] +- [Abordare SQL |sql way] +- [Explorer |Explorer] +- [Tranzacții |transactions] +- [Excepții |exceptions] +- [Reflecție |reflection] +- [Mapare |mapping] +- [Configurație |configuration] +- [Riscuri de securitate |security] +- [Actualizare |en:upgrading] diff --git a/database/ro/@meta.texy b/database/ro/@meta.texy new file mode 100644 index 0000000000..9c744b37d6 --- /dev/null +++ b/database/ro/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentație Nette}} diff --git a/database/ro/configuration.texy b/database/ro/configuration.texy index 7f35ca66b1..c1948e2cb1 100644 --- a/database/ro/configuration.texy +++ b/database/ro/configuration.texy @@ -2,60 +2,66 @@ Configurarea bazei de date ************************** .[perex] -Prezentare generală a opțiunilor de configurare pentru baza de date Nette. +Prezentare generală a opțiunilor de configurare pentru Nette Database. -Dacă nu utilizați întregul cadru, ci doar această bibliotecă, citiți [cum se încarcă configurația |bootstrap:]. +Dacă nu utilizați întregul framework, ci doar această bibliotecă, citiți [cum să încărcați configurația |bootstrap:]. -Conexiune unică .[#toc-single-connection] ------------------------------------------ +O singură conexiune +------------------- -Configurați o singură conexiune la baza de date: +Configurarea unei singure conexiuni la baza de date: ```neon database: - # DSN, numai cheia obligatorie + # DSN, singura cheie obligatorie dsn: "sqlite:%appDir%/Model/demo.db" user: ... password: ... ``` -Se creează servicii de tip `Nette\Database\Connection` și `Nette\Database\Explorer` pentru stratul [Database Explorer |explorer]. Conexiunea la baza de date este de obicei transmisă prin [autocablare |dependency-injection:autowiring], dacă acest lucru nu este posibil, utilizați numele serviciilor `@database.default.connection` resp. `@database.default.explorer`. +Creează serviciile `Nette\Database\Connection` și `Nette\Database\Explorer`, pe care de obicei le transmitem prin [autowiring |dependency-injection:autowiring], eventual prin referință la [numele lor |#Servicii DI]. Alte setări: ```neon database: - # afișează panoul bazei de date în Tracy Bar? - debugger: ... # (bool) valoarea implicită este true + # afișează panoul database în Tracy Bar? + debugger: ... # (bool) implicit este true - # afișează interogarea EXPLAIN în Tracy Bar? - explain: ... # (bool) implicit la true + # afișează EXPLAIN pentru interogări în Tracy Bar? + explain: ... # (bool) implicit este true - # pentru a activa cablarea automată pentru această conexiune? - autowired: ... # (bool) implicit la true pentru prima conexiune + # permite autowiring pentru această conexiune? + autowired: ... # (bool) implicit este true la prima conexiune - # convenții pentru tabele: descoperit, static sau nume de clasă - conventions: discovered # (string) valoarea implicită este "discovered" (descoperit) + # convenții pentru tabele: discovered, static sau numele clasei + conventions: discovered # (string) implicit este 'discovered' options: - # să se conecteze la baza de date numai atunci când este necesar? - lazy: ... # (bool) valoarea implicită este false + # conectare la baza de date doar când este necesar? + lazy: ... # (bool) implicit este false - # Clasa driverului de bază de date PHP + # clasa PHP a driverului bazei de date driverClass: # (string) - # numai MySQL: stabilește sql_mode + # doar MySQL: setează sql_mode sqlmode: # (string) - # numai MySQL: stabilește SET NAMES - charset: # (șir de caractere) implicit la "utf8mb4" ("utf8" înainte de v5.5.3) + # doar MySQL: setează SET NAMES + charset: # (string) implicit este 'utf8mb4' - # numai Oracle și SQLite: formatul datei - formatDateTime: # (șir de caractere) implicit la "U". + # doar MySQL: convertește TINYINT(1) la bool + convertBoolean: # (bool) implicit este false + + # returnează coloanele cu dată ca obiecte imutabile (de la versiunea 3.2.1) + newDateTime: # (bool) implicit este false + + # doar Oracle și SQLite: format pentru stocarea datei + formatDateTime: # (string) implicit este 'U' ``` -Cheia `options` poate conține și alte opțiuni care pot fi găsite în [documentația driverului PDO |https://www.php.net/manual/en/pdo.drivers.php], cum ar fi: +În cheia `options` se pot specifica și alte opțiuni, pe care le găsiți în [documentația driverelor PDO |https://www.php.net/manual/en/pdo.drivers.php], cum ar fi: ```neon database: @@ -64,10 +70,10 @@ database: ``` -Conexiuni multiple .[#toc-multiple-connections] ------------------------------------------------ +Mai multe conexiuni +------------------- -În configurație putem defini mai multe conexiuni la baza de date, împărțindu-le în secțiuni denumite: +În configurație putem defini și mai multe conexiuni la baza de date împărțindu-le în secțiuni numite: ```neon database: @@ -80,9 +86,23 @@ database: dsn: 'sqlite::memory:' ``` -Fiecare conexiune definită creează servicii care includ numele secțiunii în numele lor, adică `@database.main.connection` & `@database.main.explorer` și mai departe `@database.another.connection` & `@database.another.explorer`. +Autowiring-ul este activat doar pentru serviciile din prima secțiune. Acest lucru poate fi schimbat folosind `autowired: false` sau `autowired: true`. + + +Servicii DI +----------- + +Aceste servicii sunt adăugate în containerul DI, unde `###` reprezintă numele conexiunii: + +| Nume | Tip | Descriere +|---------------------------------------------------------- +| `database.###.connection` | [api:Nette\Database\Connection] | conexiune la baza de date +| `database.###.explorer` | [api:Nette\Database\Explorer] | [Database Explorer |explorer] + + +Dacă definim doar o singură conexiune, numele serviciilor vor fi `database.default.connection` și `database.default.explorer`. Dacă definim mai multe conexiuni ca în exemplul de mai sus, numele vor corespunde secțiunilor, adică `database.main.connection`, `database.main.explorer` și apoi `database.another.connection` și `database.another.explorer`. -Cablarea automată este activată numai pentru serviciile din prima secțiune. Acest lucru poate fi modificat cu `autowired: false` sau `autowired: true`. Serviciile care nu sunt conectate automat sunt transmise după nume: +Serviciile ne-autowired le transmitem explicit prin referință la numele lor: ```neon services: diff --git a/database/ro/core.texy b/database/ro/core.texy deleted file mode 100644 index 4314a3fe2d..0000000000 --- a/database/ro/core.texy +++ /dev/null @@ -1,350 +0,0 @@ -Baza de date de bază -******************** - -.[perex] -Nette Database Core este un strat de abstractizare a bazei de date și oferă funcționalitatea de bază. - - -Instalare .[#toc-installation] -============================== - -Descărcați și instalați pachetul folosind [Composer |best-practices:composer]: - -```shell -composer require nette/database -``` - - -Conectare și configurare .[#toc-connection-and-configuration] -============================================================= - -Pentru a vă conecta la baza de date, creați pur și simplu o instanță a clasei [api:Nette\Database\Connection]: - -```php -$database = new Nette\Database\Connection($dsn, $user, $password); -``` - -Parametrul `$dsn` (numele sursei de date) este [același cu cel utilizat de PDO |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], de exemplu `host=127.0.0.1;dbname=test`. În caz de eșec, se aruncă `Nette\Database\ConnectionException`. - -Cu toate acestea, o modalitate mai sofisticată oferă [configurarea aplicației |configuration]. Vom adăuga o secțiune `database` și aceasta creează obiectele necesare și un panou cu baza de date în bara [Tracy |tracy:]. - -```neon -database: - dsn: 'mysql:host=127.0.0.1;dbname=test' - user: root - password: password -``` - -Obiectul de conexiune pe care îl [primim ca serviciu de la un container DI |dependency-injection:passing-dependencies], de exemplu: - -```php -class Model -{ - // treceți Nette\Database\Explorer pentru a lucra cu stratul Database Explorer - public function __construct( - private Nette\Database\Connection $database, - ) { - } -} -``` - -Pentru mai multe informații, consultați [configurarea bazei de date |configuration]. - - -Interogări .[#toc-queries] -========================== - -Pentru a interoga baza de date, utilizați metoda `query()` care returnează [ResultSet |api:Nette\Database\ResultSet]. - -```php -$result = $database->query('SELECT * FROM users'); - -foreach ($result as $row) { - echo $row->id; - echo $row->name; -} - -echo $result->getRowCount(); // returnează numărul de rânduri dacă este cunoscut -``` - -.[note] -Pe `ResultSet` este posibilă iterarea doar o singură dată, dacă trebuie să iterăm de mai multe ori, este necesar să convertim rezultatul în matrice prin metoda `fetchAll()`. - -Puteți adăuga cu ușurință parametri la interogare, rețineți semnul de întrebare: - -```php -$database->query('SELECT * FROM users WHERE name = ?', $name); - -$database->query('SELECT * FROM users WHERE name = ? AND active = ?', $name, $active); - -$database->query('SELECT * FROM users WHERE id IN (?)', $ids); // $ids este o matrice -``` -<div class=warning> - -ATENȚIE, nu concatenați niciodată șiruri de caractere pentru a evita [vulnerabilitatea de injecție SQL |https://en.wikipedia.org/wiki/SQL_injection]! -/-- -$db->query('SELECT * FROM users WHERE name = ' . $name); // WRONG!!! -\-- -</div> - -În caz de eșec, `query()` aruncă fie `Nette\Database\DriverException`, fie unul dintre descendenții săi: - -- [ConstraintViolationException |api:Nette\Database\ConstraintViolationException] - încălcarea oricărei constrângeri. -- [ForeignKeyConstraintViolationException |api:Nette\Database\ForeignKeyConstraintViolationException] - cheie străină invalidă -- [NotNullConstraintViolationException |api:Nette\Database\NotNullConstraintViolationException] - încălcare a condiției NOT NULL -- [UniqueConstraintViolationException |api:Nette\Database\UniqueConstraintViolationException] - conflict cu un indice unic - -În plus față de `query()`, există și alte metode utile: - -```php -// returnează matricea asociativă id => nume -$pairs = $database->fetchPairs('SELECT id, name FROM users'); - -// returnează toate rândurile sub formă de matrice -$rows = $database->fetchAll('SELECT * FROM users'); - -// returnează un singur rând -$row = $database->fetch('SELECT * FROM users WHERE id = ?', $id); - -// returnează un singur câmp -$name = $database->fetchField('SELECT name FROM users WHERE id = ?', $id); -``` - -În caz de eșec, toate aceste metode aruncă `Nette\Database\DriverException.` - - -Inserare, actualizare și ștergere .[#toc-insert-update-delete] -============================================================== - -Parametrul pe care îl introducem în interogarea SQL poate fi, de asemenea, matricea (caz în care este posibil să se omită declarația wildcard `?`), which may be useful for the `INSERT`: - -```php -$database->query('INSERT INTO users ?', [ // aici poate fi omis semnul întrebării - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) - -$id = $database->getInsertId(); // returnează creșterea automată a rândului inserat - -$id = $database->getInsertId($sequence); // sau valoarea secvenței -``` - -Inserare multiplă: - -```php -$database->query('INSERT INTO users', [ - [ - 'name' => 'Jim', - 'year' => 1978, - ], [ - 'name' => 'Jack', - 'year' => 1987, - ], -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) -``` - -De asemenea, putem trece fișiere, obiecte DateTime sau [enumerări |https://www.php.net/enumerations]: - -```php -$database->query('INSERT INTO users', [ - 'name' => $name, - 'created' => new DateTime, // sau $database::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // inserează conținutul fișierului - 'status' => State::New, // enum State -]); -``` - -Actualizarea rândurilor: - -```php -$result = $database->query('UPDATE users SET', [ - 'name' => $name, - 'year' => $year, -], 'WHERE id = ?', $id); -// UPDATE users SET `name` = 'Jim', `year` = 1978 WHERE id = 123 - -echo $result->getRowCount(); // returnează numărul de rânduri afectate -``` - -Pentru UPDATE, putem folosi operatorii `+=` și `-=`: - -```php -$database->query('UPDATE users SET', [ - 'age+=' => 1, // notă += -], 'WHERE id = ?', $id); -// UPDATE users SET `age` = `age` + 1 -``` - -Ștergere: - -```php -$result = $database->query('DELETE FROM users WHERE id = ?', $id); -echo $result->getRowCount(); // returnează numărul de rânduri afectate -``` - - -Interogări avansate .[#toc-advanced-queries] -============================================ - -Inserare sau actualizare, dacă există deja: - -```php -$database->query('INSERT INTO users', [ - 'id' => $id, - 'name' => $name, - 'year' => $year, -], 'ON DUPLICATE KEY UPDATE', [ - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) -// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 -``` - -Rețineți că Nette Database recunoaște contextul SQL în care este introdus parametrul de matrice și construiește codul SQL în consecință. Astfel, din primul array generează `(id, name, year) VALUES (123, 'Jim', 1978)`, în timp ce al doilea se convertește în `name = 'Jim', year = 1978`. - -De asemenea, putem descrie sortarea folosind array, în care cheile sunt nume de coloane, iar valorile sunt booleane care determină dacă se sortează în ordine crescătoare: - -```php -$database->query('SELECT id FROM author ORDER BY', [ - 'id' => true, // ascendent - 'name' => false, // descrescător -]); -// SELECT id FROM author ORDER BY `id`, `name` DESC -``` - -În cazul în care detectarea nu a funcționat, puteți specifica forma ansamblului cu ajutorul unui wildcard `?` urmat de un indiciu. Aceste indicii sunt acceptate: - -| ?values | (key1, key2, ...) VALUES (value1, value2, ...) -| ?set | key1 = valoare1, key2 = valoare2, ... -| ?and | key1 = valoare1 AND key2 = valoare2 ... -| ?or | key1 = valoare1 OR key2 = valoare2 ... -| ?order | key1 ASC, key2 DESC - -Clauza WHERE utilizează operatorul `?and`, astfel încât condițiile sunt legate prin `AND`: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND `year` = 1978 -``` - -Care poate fi ușor de schimbat în `OR` prin utilizarea caracterului wildcard `?or`: - -```php -$result = $database->query('SELECT * FROM users WHERE ?or', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' OR `year` = 1978 -``` - -Putem folosi operatori în condiții: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name <>' => $name, - 'year >' => $year, -]); -// SELECT * FROM users WHERE `name` <> 'Jim' AND `year` > 1978 -``` - -Și, de asemenea, enumerări: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => ['Jim', 'Jack'], - 'role NOT IN' => ['admin', 'owner'], // enumerare + operator NOT IN -]); -// SELECT * FROM users WHERE -// `name` IN ('Jim', 'Jack') AND `role` NOT IN ('admin', 'owner') -``` - -De asemenea, putem include o bucată de cod SQL personalizat folosind așa-numitul literal SQL: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year >' => $database::literal('YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) -``` - -Alternativ: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) -``` - -Literalul SQL poate avea, de asemenea, parametrii săi: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > ? AND year < ?', $min, $max), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) -``` - -Datorită cărora putem crea combinații interesante: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('?or', [ - 'active' => true, - 'role' => $role, - ]), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') -``` - - -Denumire variabilă .[#toc-variable-name] -======================================== - -Există un wildcard `?name` pe care îl utilizați dacă numele tabelului sau al coloanei este o variabilă. (Atenție, nu permiteți utilizatorului să manipuleze conținutul unei astfel de variabile): - -```php -$table = 'blog.users'; -$column = 'name'; -$database->query('SELECT * FROM ?name WHERE ?name = ?', $table, $column, $name); -// SELECT * FROM `blog`.`users` WHERE `name` = 'Jim' -``` - - -Tranzacții .[#toc-transactions] -=============================== - -Există trei metode de tratare a tranzacțiilor: - -```php -$database->beginTransaction(); - -$database->commit(); - -$database->rollback(); -``` - -O modalitate elegantă este oferită de metoda `transaction()`. Se trece callback-ul care este executat în cadrul tranzacției. Dacă în timpul execuției se aruncă o excepție, tranzacția este abandonată, iar dacă totul merge bine, tranzacția este validată. - -```php -$id = $database->transaction(function ($database) { - $database->query('DELETE FROM ...'); - $database->query('INSERT INTO ...'); - // ... - return $database->getInsertId(); -}); -``` - -După cum puteți vedea, metoda `transaction()` returnează valoarea de returnare a callback-ului. - -Tranzacția() poate fi, de asemenea, imbricata, ceea ce simplifică implementarea de depozite independente. diff --git a/database/ro/exceptions.texy b/database/ro/exceptions.texy new file mode 100644 index 0000000000..7a2084e0d6 --- /dev/null +++ b/database/ro/exceptions.texy @@ -0,0 +1,34 @@ +Excepții +******** + +Nette Database utilizează o ierarhie de excepții. Clasa de bază este `Nette\Database\DriverException`, care moștenește din `PDOException` și oferă posibilități extinse pentru lucrul cu erorile bazei de date: + +- Metoda `getDriverCode()` returnează codul de eroare de la driverul bazei de date +- Metoda `getSqlState()` returnează codul SQLSTATE +- Metodele `getQueryString()` și `getParameters()` permit obținerea interogării originale și a parametrilor săi + +Din `DriverException` moștenesc următoarele excepții specializate: + +- `ConnectionException` - semnalează eșecul conexiunii la serverul bazei de date +- `ConstraintViolationException` - clasa de bază pentru încălcarea constrângerilor bazei de date, din care moștenesc: + - `ForeignKeyConstraintViolationException` - încălcarea cheii străine + - `NotNullConstraintViolationException` - încălcarea constrângerii NOT NULL + - `UniqueConstraintViolationException` - încălcarea unicității valorii + + +Exemplu de capturare a excepției `UniqueConstraintViolationException`, care apare atunci când încercăm să inserăm un utilizator cu un email care există deja în baza de date (presupunând că coloana email are un index unic). + +```php +try { + $database->query('INSERT INTO users', [ + 'email' => 'john@example.com', + 'name' => 'John Doe', + 'password' => $hashedPassword, + ]); +} catch (Nette\Database\UniqueConstraintViolationException $e) { + echo 'Utilizatorul cu acest email există deja.'; + +} catch (Nette\Database\DriverException $e) { + echo 'A apărut o eroare la înregistrare: ' . $e->getMessage(); +} +``` diff --git a/database/ro/explorer.texy b/database/ro/explorer.texy index ab5ffa2031..f7cb938248 100644 --- a/database/ro/explorer.texy +++ b/database/ro/explorer.texy @@ -1,550 +1,912 @@ -Exploratorul de baze de date -**************************** +Database Explorer +***************** <div class=perex> -Nette Database Explorer simplifică în mod semnificativ recuperarea datelor din baza de date fără a scrie interogări SQL. +Explorer oferă o modalitate intuitivă și eficientă de a lucra cu baza de date. Se ocupă automat de legăturile dintre tabele și de optimizarea interogărilor, astfel încât să vă puteți concentra pe aplicația dvs. Funcționează imediat fără configurare. Dacă aveți nevoie de control total asupra interogărilor SQL, puteți utiliza [abordarea SQL |sql-way]. -- utilizează interogări eficiente -- nu se transmit date în mod inutil -- dispune de o sintaxă elegantă +- Lucrul cu datele este natural și ușor de înțeles +- Generează interogări SQL optimizate, care încarcă doar datele necesare +- Permite accesul facil la datele conexe fără a fi nevoie să scrieți interogări JOIN +- Funcționează imediat fără nicio configurare sau generare de entități </div> -Pentru a utiliza Database Explorer, începeți cu un tabel - apelați `table()` pe un obiect [api:Nette\Database\Explorer]. Cel mai simplu mod de a obține o instanță de obiect contextual este [descris aici |core#Connection and Configuration] sau, pentru cazul în care Nette Database Explorer este utilizat ca instrument de sine stătător, acesta poate fi [creat manual |#Creating Explorer Manually]. + +Cu Explorer începeți prin apelarea metodei `table()` a obiectului [api:Nette\Database\Explorer] (detalii despre conectare găsiți în capitolul [Conectare și configurare |guide#Conectare și configurare]): ```php -$books = $explorer->table('book'); // numele tabelului db este 'book' +$books = $explorer->table('book'); // 'book' este numele tabelei ``` -Apelul returnează o instanță a obiectului [Selection |api:Nette\Database\Table\Selection], care poate fi iterată pentru a prelua toate cărțile. Fiecare element (un rând) este reprezentat de o instanță de [ActiveRow |api:Nette\Database\Table\ActiveRow], cu date alocate proprietăților sale: +Metoda returnează obiectul [Selection |api:Nette\Database\Table\Selection], care reprezintă o interogare SQL. Pe acest obiect putem înlănțui alte metode pentru filtrarea și sortarea rezultatelor. Interogarea se construiește și se execută abia în momentul în care începem să solicităm date. De exemplu, prin parcurgerea cu ciclul `foreach`. Fiecare rând este reprezentat de obiectul [ActiveRow |api:Nette\Database\Table\ActiveRow]: ```php foreach ($books as $book) { - echo $book->title; - echo $book->author_id; + echo $book->title; // afișarea coloanei 'title' + echo $book->author_id; // afișarea coloanei 'author_id' } ``` -Obținerea unui singur rând specific se face prin metoda `get()`, care returnează direct o instanță ActiveRow. +Explorer facilitează în mod fundamental lucrul cu [legăturile dintre tabele |#Relații între tabele]. Următorul exemplu arată cât de ușor putem afișa date din tabele legate (cărți și autorii lor). Observați că nu trebuie să scriem nicio interogare JOIN, Nette le creează pentru noi: ```php -$book = $explorer->table('book')->get(2); // returnează cartea cu id 2 -echo $book->title; -echo $book->author_id; +$books = $explorer->table('book'); + +foreach ($books as $book) { + echo 'Carte: ' . $book->title; + echo 'Autor: ' . $book->author->name; // creează JOIN pe tabela 'author' +} ``` -Să aruncăm o privire la un caz de utilizare obișnuită. Aveți nevoie să obțineți cărți și autorii acestora. Este o relație comună 1:N. Soluția frecvent utilizată este de a prelua datele utilizând o singură interogare SQL cu îmbinări de tabele. A doua posibilitate este de a prelua datele separat, de a executa o interogare pentru a obține cărți și apoi de a obține un autor pentru fiecare carte printr-o altă interogare (de exemplu, în ciclul foreach). Acest lucru ar putea fi ușor de optimizat pentru a rula doar două interogări, una pentru cărți și alta pentru autorii necesari - și exact așa procedează Nette Database Explorer. +Nette Database Explorer optimizează interogările pentru a fi cât mai eficiente. Exemplul de mai sus execută doar două interogări SELECT, indiferent dacă procesăm 10 sau 10 000 de cărți. -În exemplele de mai jos, vom lucra cu schema bazei de date din figură. Există legături OneHasMany (1:N) (autorul cărții `author_id` și posibilul traducător `translator_id`, care poate fi `null`) și legături ManyHasMany (M:N) între carte și etichetele acesteia. +În plus, Explorer urmărește ce coloane sunt utilizate în cod și încarcă din baza de date doar acelea, economisind astfel performanță suplimentară. Acest comportament este complet automat și adaptiv. Dacă modificați ulterior codul și începeți să utilizați alte coloane, Explorer ajustează automat interogările. Nu trebuie să setați nimic, nici să vă gândiți ce coloane veți avea nevoie - lăsați asta pe seama Nette. -[Un exemplu, inclusiv o schemă, se găsește pe GitHub |https://github.com/nette-examples/books]. -[* db-schema-1-.webp *] *** Structura bazei de date utilizată în exemplele .<> +Filtrare și sortare +=================== -Următorul cod enumeră numele autorului pentru fiecare carte și toate etichetele acesteia. Vom [discuta imediat |#Working with relationships] cum funcționează acest lucru la nivel intern. +Clasa `Selection` oferă metode pentru filtrarea și sortarea selecției de date. -```php -$books = $explorer->table('book'); +.[language-php] +| `where($condition, ...$params)` | Adaugă condiția WHERE. Mai multe condiții sunt legate cu operatorul AND +| `whereOr(array $conditions)` | Adaugă un grup de condiții WHERE legate cu operatorul OR +| `wherePrimary($value)` | Adaugă condiția WHERE după cheia primară +| `order($columns, ...$params)` | Setează sortarea ORDER BY +| `select($columns, ...$params)` | Specifică coloanele care trebuie încărcate +| `limit($limit, $offset = null)` | Limitează numărul de rânduri (LIMIT) și opțional setează OFFSET +| `page($page, $itemsPerPage, &$total = null)` | Setează paginarea +| `group($columns, ...$params)` | Grupează rândurile (GROUP BY) +| `having($condition, ...$params)` | Adaugă condiția HAVING pentru filtrarea rândurilor grupate -foreach ($books as $book) { - echo 'title: ' . $book->title; - echo 'written by: ' . $book->author->name; // $book->author este un rând din tabelul 'author' +Metodele pot fi înlănțuite (așa-numitul [fluent interface |nette:introduction-to-object-oriented-programming#Interfețe fluente]): `$table->where(...)->order(...)->limit(...)`. - echo 'tags: '; - foreach ($book->related('book_tag') as $bookTag) { - echo $bookTag->tag->name . ', '; // $bookTag->tag este un rând din tabelul 'tag' - } -} -``` +În aceste metode puteți utiliza și notația specială pentru accesarea [datelor din tabelele conexe |#Interogarea prin tabele asociate]. -Veți fi mulțumiți de cât de eficient funcționează stratul de bază de date. Exemplul de mai sus face un număr constant de cereri care arată astfel: -```sql -SELECT * FROM `book` -SELECT * FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT * FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) -``` +Escapare și identificatori +-------------------------- -Dacă folosiți [memoria cache |caching:] (activată în mod implicit), nicio coloană nu va fi interogată în mod inutil. După prima interogare, memoria cache va stoca numele coloanelor utilizate, iar Nette Database Explorer va rula interogări numai cu coloanele necesare: +Metodele escapează automat parametrii și încadrează identificatorii (numele tabelelor și coloanelor) în ghilimele, prevenind astfel SQL injection. Pentru funcționarea corectă este necesar să respectați câteva reguli: -```sql -SELECT `id`, `title`, `author_id` FROM `book` -SELECT `id`, `name` FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT `book_id`, `tag_id` FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT `id`, `name` FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) +- Cuvintele cheie, numele funcțiilor, procedurilor etc. scrieți-le cu **majuscule**. +- Numele coloanelor și tabelelor scrieți-le cu **litere mici**. +- Șirurile de caractere introduceți-le întotdeauna prin **parametri**. + +```php +where('name = ' . $name); // VULNERABILITATE CRITICĂ: SQL injection +where('name LIKE "%search%"'); // GREȘIT: complică încadrarea automată în ghilimele +where('name LIKE ?', '%search%'); // CORECT: valoare introdusă prin parametru + +where('name like ?', $name); // GREȘIT: generează: `name` `like` ? +where('name LIKE ?', $name); // CORECT: generează: `name` LIKE ? +where('LOWER(name) = ?', $value);// CORECT: LOWER(`name`) = ? ``` -Selecții .[#toc-selections] -=========================== +where(string|array $condition, ...$parameters): static .[method] +---------------------------------------------------------------- -Vedeți posibilitățile de filtrare și restricționare a rândurilor [api:Nette\Database\Table\Selection]: +Filtrează rezultatele folosind condiții WHERE. Punctul său forte este lucrul inteligent cu diferite tipuri de valori și alegerea automată a operatorilor SQL. -.[language-php] -| `$table->where($where[, $param[, ...]])` | Setați WHERE utilizând AND ca un liant dacă sunt furnizate două sau mai multe condiții -| `$table->whereOr($where)` | Set WHERE care utilizează OR ca liant dacă sunt furnizate două sau mai multe condiții. -| `$table->order($columns)` | Setați ORDER BY, poate fi o expresie `('column DESC, id DESC')` -| `$table->select($columns)` | Setați coloanele recuperate, poate fi o expresie `('col, MD5(col) AS hash')` -| `$table->limit($limit[, $offset])` | Setați LIMIT și OFFSET -| `$table->page($page, $itemsPerPage[, &$lastPage])` | Activează paginarea -| `$table->group($columns)` | Setează GROUP BY -| `$table->having($having)` | Setează HAVING +Utilizare de bază: -Se poate utiliza o interfață fluidă, de exemplu `$table->where(...)->order(...)->limit(...)`. Mai multe condiții `where` sau `whereOr` sunt conectate cu ajutorul operatorului `AND`. +```php +$table->where('id', $value); // WHERE `id` = 123 +$table->where('id > ?', $value); // WHERE `id` > 123 +$table->where('id = ? OR name = ?', $id, $name); // WHERE `id` = 1 OR `name` = 'Jon Snow' +``` +Datorită detectării automate a operatorilor potriviți, nu trebuie să ne ocupăm de diverse cazuri speciale. Nette le rezolvă pentru noi: -unde() .[#toc-where] --------------------- +```php +$table->where('id', 1); // WHERE `id` = 1 +$table->where('id', null); // WHERE `id` IS NULL +$table->where('id', [1, 2, 3]); // WHERE `id` IN (1, 2, 3) +// se poate utiliza și semnul de întrebare substituent fără operator: +$table->where('id ?', 1); // WHERE `id` = 1 +``` -Nette Database Explorer poate adăuga automat operatorii necesari pentru valorile transmise: +Metoda procesează corect și condițiile negative și array-urile goale: -.[language-php] -| `$table->where('field', $value)` | field = $value -| `$table->where('field', null)` | field IS NULL -| `$table->where('field > ?', $val)` | field > $val -| `$table->where('field', [1, 2])` | field IN (1, 2) -| `$table->where('id = ? OR name = ?', 1, $name)` | id = 1 OR name = 'Jon Snow' -| `$table->where('field', $explorer->table($tableName))` | field IN (SELECT $primary FROM $tableName) -| `$table->where('field', $explorer->table($tableName)->select('col'))` | field IN (SELECT col FROM $tableName) +```php +$table->where('id', []); // WHERE `id` IS NULL AND FALSE -- nu găsește nimic +$table->where('id NOT', []); // WHERE `id` IS NULL OR TRUE -- găsește tot +$table->where('NOT (id ?)', []); // WHERE NOT (`id` IS NULL AND FALSE) -- găsește tot +// $table->where('NOT id ?', $ids); Atenție - această sintaxă nu este suportată +``` -Puteți furniza spații libere chiar și fără operatorul de coloană. Aceste apeluri sunt identice. +Ca parametru putem transmite și rezultatul dintr-o altă tabelă - se va crea o subinterogare: ```php -$table->where('id = ? OR id = ?', 1, 2); -$table->where('id ? OR id ?', 1, 2); +// WHERE `id` IN (SELECT `id` FROM `tableName`) +$table->where('id', $explorer->table($tableName)); + +// WHERE `id` IN (SELECT `col` FROM `tableName`) +$table->where('id', $explorer->table($tableName)->select('col')); ``` -Această caracteristică permite generarea operatorului corect pe baza valorii: +Condițiile le putem transmite și ca array, ale cărui elemente se vor uni cu AND: ```php -$table->where('id ?', 2); // id = 2 -$table->where('id ?', null); // id IS NULL -$table->where('id', $ids); // id IN (...) +// WHERE (`price_final` < `price_original`) AND (`stock_count` > `min_stock`) +$table->where([ + 'price_final < price_original', + 'stock_count > min_stock', +]); ``` -Selecția gestionează corect și condițiile negative, funcționează și pentru array-uri goale: +În array putem folosi perechi cheie => valoare și Nette alege din nou automat operatorii corecți: ```php -$table->where('id', []); // id IS NULL AND FALSE -$table->where('id NOT', []); // id IS NULL OR TRUE -$table->where('NOT (id ?)', $ids); // NOT (id IS NULL AND FALSE) +// WHERE (`status` = 'active') AND (`id` IN (1, 2, 3)) +$table->where([ + 'status' => 'active', + 'id' => [1, 2, 3], +]); +``` -// acest lucru va genera o excepție, această sintaxă nu este acceptată. -$table->where('NOT id ?', $ids); +În array putem combina expresii SQL cu semne de întrebare substituente și mai mulți parametri. Acest lucru este potrivit pentru condiții complexe cu operatori definiți precis: + +```php +// WHERE (`age` > 18) AND (ROUND(`score`, 2) > 75.5) +$table->where([ + 'age > ?' => 18, + 'ROUND(score, ?) > ?' => [2, 75.5], // doi parametri îi transmitem ca array +]); ``` +Apelurile multiple ale `where()` leagă automat condițiile cu AND. -whereOr() .[#toc-whereor] -------------------------- -Exemplu de utilizare fără parametri: +whereOr(array $parameters): static .[method] +-------------------------------------------- + +Similar cu `where()`, adaugă condiții, dar cu diferența că le leagă cu OR: ```php -// WHERE (user_id IS NULL) OR (SUM(`field1`) > SUM(`field2`)) +// WHERE (`status` = 'active') OR (`deleted` = 1) $table->whereOr([ - 'user_id IS NULL', - 'SUM(field1) > SUM(field2)', + 'status' => 'active', + 'deleted' => true, ]); ``` -Se utilizează parametrii. Dacă nu specificați un operator, Nette Database Explorer îl va adăuga automat pe cel corespunzător: +Și aici putem folosi expresii mai complexe: ```php -// WHERE (`field1` IS NULL) OR (`field2` IN (3, 5)) OR (`amount` > 11) +// WHERE (`price` > 1000) OR (`price_with_tax` > 1500) $table->whereOr([ - 'field1' => null, - 'field2' => [3, 5], - 'amount >' => 11, + 'price > ?' => 1000, + 'price_with_tax > ?' => 1500, ]); ``` -Cheia poate conține o expresie care să conțină semne de întrebare wildcard și apoi să treacă parametrii în valoare: + +wherePrimary(mixed $key): static .[method] +------------------------------------------ + +Adaugă condiția pentru cheia primară a tabelei: ```php -// WHERE (`id` > 12) OR (ROUND(`id`, 5) = 3) -$table->whereOr([ - 'id > ?' => 12, - 'ROUND(id, ?) = ?' => [5, 3], -]); +// WHERE `id` = 123 +$table->wherePrimary(123); + +// WHERE `id` IN (1, 2, 3) +$table->wherePrimary([1, 2, 3]); ``` +Dacă tabela are o cheie primară compozită (de ex. `foo_id`, `bar_id`), o transmitem ca array: + +```php +// WHERE `foo_id` = 1 AND `bar_id` = 5 +$table->wherePrimary(['foo_id' => 1, 'bar_id' => 5])->fetch(); + +// WHERE (`foo_id`, `bar_id`) IN ((1, 5), (2, 3)) +$table->wherePrimary([ + ['foo_id' => 1, 'bar_id' => 5], + ['foo_id' => 2, 'bar_id' => 3], +])->fetchAll(); +``` -order() .[#toc-order] ---------------------- -Exemple de utilizare: +order(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- + +Determină ordinea în care vor fi returnate rândurile. Putem sorta după una sau mai multe coloane, în ordine descrescătoare sau crescătoare, sau după o expresie proprie: ```php -$table->order('field1'); // ORDER BY `field1` -$table->order('field1 DESC, field2'); // ORDER BY `field1` DESC, `field2` -$table->order('field = ? DESC', 123); // ORDER BY `field` = 123 DESC +$table->order('created'); // ORDER BY `created` +$table->order('created DESC'); // ORDER BY `created` DESC +$table->order('priority DESC, created'); // ORDER BY `priority` DESC, `created` +$table->order('status = ? DESC', 'active'); // ORDER BY `status` = 'active' DESC ``` -select() .[#toc-select] ------------------------ +select(string $columns, ...$parameters): static .[method] +--------------------------------------------------------- -Exemple de utilizare: +Specifică coloanele care trebuie returnate din baza de date. În mod implicit, Nette Database Explorer returnează doar acele coloane care sunt utilizate efectiv în cod. Metoda `select()` o folosim deci în cazurile în care avem nevoie să returnăm expresii specifice: ```php -$table->select('field1'); // SELECT `field1` -$table->select('col, UPPER(col) AS abc'); // SELECT `col`, UPPER(`col`) AS abc -$table->select('SUBSTR(title, ?)', 3); // SELECT SUBSTR(`title`, 3) +// SELECT *, DATE_FORMAT(`created_at`, "%d.%m.%Y") AS `formatted_date` +$table->select('*, DATE_FORMAT(created_at, ?) AS formatted_date', '%d.%m.%Y'); ``` +Aliasurile definite cu `AS` sunt apoi disponibile ca proprietăți ale obiectului ActiveRow: -limit() .[#toc-limit] ---------------------- +```php +foreach ($table as $row) { + echo $row->formatted_date; // acces la alias +} +``` + + +limit(?int $limit, ?int $offset = null): static .[method] +--------------------------------------------------------- -Exemple de utilizare: +Limitează numărul de rânduri returnate (LIMIT) și opțional permite setarea unui offset: ```php -$table->limit(1); // LIMIT 1 -$table->limit(1, 10); // LIMIT 1 OFFSET 10 +$table->limit(10); // LIMIT 10 (returnează primele 10 rânduri) +$table->limit(10, 20); // LIMIT 10 OFFSET 20 ``` +Pentru paginare este mai potrivită utilizarea metodei `page()`. -page() .[#toc-page] -------------------- -O modalitate alternativă de a seta limita și decalajul: +page(int $page, int $itemsPerPage, &$numOfPages = null): static .[method] +------------------------------------------------------------------------- + +Facilitează paginarea rezultatelor. Acceptă numărul paginii (numărat de la 1) și numărul de elemente pe pagină. Opțional, se poate transmite o referință la o variabilă în care se va stoca numărul total de pagini: ```php -$page = 5; -$itemsPerPage = 10; -$table->page($page, $itemsPerPage); // LIMIT 10 OFFSET 40 +$numOfPages = null; +$table->page(page: 3, itemsPerPage: 10, numOfPages: $numOfPages); +echo "Total pagini: $numOfPages"; ``` -Obținerea ultimului număr de pagină, trecut în variabila `$lastPage`: + +group(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- + +Grupează rândurile după coloanele specificate (GROUP BY). Se utilizează de obicei în combinație cu funcții de agregare: ```php -$table->page($page, $itemsPerPage, $lastPage); +// Calculează numărul de produse din fiecare categorie +$table->select('category_id, COUNT(*) AS count') + ->group('category_id'); ``` -group() .[#toc-group] ---------------------- +having(string $having, ...$parameters): static .[method] +-------------------------------------------------------- -Exemple de utilizare: +Setează condiția pentru filtrarea rândurilor grupate (HAVING). Poate fi utilizată în combinație cu metoda `group()` și funcții de agregare: ```php -$table->group('field1'); // GROUP BY `field1` -$table->group('field1, field2'); // GROUP BY `field1`, `field2` +// Găsește categoriile care au mai mult de 100 de produse +$table->select('category_id, COUNT(*) AS count') + ->group('category_id') + ->having('count > ?', 100); ``` -having() .[#toc-having] ------------------------ +Citirea datelor +=============== + +Pentru citirea datelor din baza de date avem la dispoziție câteva metode utile: + +.[language-php] +| `foreach ($table as $key => $row)` | Iterează peste toate rândurile, `$key` este valoarea cheii primare, `$row` este obiectul ActiveRow +| `$row = $table->get($key)` | Returnează un rând după cheia primară +| `$row = $table->fetch()` | Returnează rândul curent și mută pointerul la următorul +| `$array = $table->fetchPairs()` | Creează un array asociativ din rezultate +| `$array = $table->fetchAll()` | Returnează toate rândurile ca array +| `count($table)` | Returnează numărul de rânduri din obiectul Selection + +Obiectul [ActiveRow |api:Nette\Database\Table\ActiveRow] este destinat doar citirii. Acest lucru înseamnă că nu se pot modifica valorile proprietăților sale. Această limitare asigură consistența datelor și previne efectele secundare neașteptate. Datele sunt încărcate din baza de date și orice modificare ar trebui efectuată explicit și controlat. + + +`foreach` - iterare peste toate rândurile +----------------------------------------- -Exemple de utilizare: +Cel mai simplu mod de a executa o interogare și de a obține rândurile este iterarea într-un ciclu `foreach`. Lansează automat interogarea SQL. ```php -$table->having('COUNT(items) >', 100); // HAVING COUNT(`items`) > 100 +$books = $explorer->table('book'); +foreach ($books as $key => $book) { + // $key este valoarea cheii primare, $book este ActiveRow + echo "$book->title ({$book->author->name})"; +} ``` -Filtrarea după o altă valoare de tabel .[#toc-joining-key] ----------------------------------------------------------- +get($key): ?ActiveRow .[method] +------------------------------- + +Execută interogarea SQL și returnează rândul după cheia primară, sau `null`, dacă nu există. + +```php +$book = $explorer->table('book')->get(123); // returnează ActiveRow cu ID 123 sau null +if ($book) { + echo $book->title; +} +``` -Destul de des aveți nevoie să filtrați rezultatele în funcție de o condiție care implică un alt tabel din baza de date. Aceste tipuri de condiții necesită îmbinarea tabelelor. Cu toate acestea, nu mai este nevoie să le scrieți. -Să spunem că trebuie să obțineți toate cărțile al căror nume de autor este "Jon". Tot ce trebuie să scrieți este cheia de îmbinare a relației și numele coloanei din tabelul îmbinat. Cheia de îmbinare este derivată din coloana care se referă la tabelul pe care doriți să îl îmbinați. În exemplul nostru (a se vedea schema db), aceasta este coloana `author_id`, și este suficient să se utilizeze doar prima parte a acesteia - `author` (sufixul `_id` poate fi omis). `name` este o coloană din tabelul `author` pe care dorim să o utilizăm. O condiție pentru traducătorul de cărți (care este legată de coloana `translator_id` ) poate fi creată la fel de ușor. +fetch(): ?ActiveRow .[method] +----------------------------- + +Returnează rândul și mută pointerul intern la următorul. Dacă nu mai există alte rânduri, returnează `null`. ```php $books = $explorer->table('book'); -$books->where('author.name LIKE ?', '%Jon%'); -$books->where('translator.name', 'David Grudl'); +while ($book = $books->fetch()) { + $this->processBook($book); +} ``` -Logica cheilor de îmbinare este determinată de implementarea [Convențiilor |api:Nette\Database\Conventions]. Vă încurajăm să utilizați [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions], care analizează cheile străine și vă permite să lucrați cu ușurință cu aceste relații. -Relația dintre carte și autorul acesteia este 1:N. Relația inversă este, de asemenea, posibilă. Noi o numim **backjoin**. Aruncați o privire la un alt exemplu. Dorim să obținem toți autorii care au scris mai mult de 3 cărți. Pentru a inversa îmbinarea, folosim instrucțiunea `:` (colon). Colon means that the joined relationship means hasMany (and it's quite logical too, as two dots are more than one dot). Unfortunately, the Selection class isn't smart enough, so we have to help with the aggregation and provide a `GROUP BY`, iar condiția trebuie să fie scrisă sub forma unei instrucțiuni `HAVING`. +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Returnează rezultatele ca array asociativ. Primul argument specifică numele coloanei care se va utiliza ca cheie în array, al doilea argument specifică numele coloanei care se va utiliza ca valoare: ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book.id) > 3'); +$authors = $explorer->table('author')->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] ``` -Poate ați observat că expresia de alăturare se referă la carte, dar nu este clar dacă ne alăturăm prin `author_id` sau `translator_id`. În exemplul de mai sus, Selection se alătură prin coloana `author_id` deoarece a fost găsită o potrivire cu tabelul sursă - tabelul `author`. În cazul în care nu ar exista o astfel de potrivire și ar exista mai multe posibilități, Nette ar lansa [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. +Dacă specificăm doar primul parametru, valoarea va fi întregul rând, adică obiectul `ActiveRow`: -Pentru a realiza o îmbinare prin intermediul coloanei `translator_id`, furnizați un parametru opțional în cadrul expresiei de îmbinare. +```php +$authors = $explorer->table('author')->fetchPairs('id'); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` + +În cazul cheilor duplicate, se va utiliza valoarea din ultimul rând. La utilizarea `null` ca cheie, array-ul va fi indexat numeric de la zero (atunci nu apar coliziuni): ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book(translator).id) > 3'); +$authors = $explorer->table('author')->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] ``` -Să ne uităm la câteva expresii de îmbinare mai dificile. -Am dori să găsim toți autorii care au scris ceva despre PHP. Toate cărțile au etichete, deci ar trebui să selectăm acei autori care au scris o carte cu eticheta PHP. +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Alternativ, puteți specifica ca parametru un callback, care va returna pentru fiecare rând fie valoarea însăși, fie perechea cheie-valoare. ```php -$authors = $explorer->table('author'); -$authors->where(':book:book_tags.tag.name', 'PHP') - ->group('author.id') - ->having('COUNT(:book:book_tags.tag.id) > 0'); +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => "$row->title ({$row->author->name})"); +// ['Prima carte (Jan Novák)', ...] + +// Callback-ul poate returna și un array cu perechea cheie & valoare: +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => [$row->title, $row->author->name]); +// ['Prima carte' => 'Jan Novák', ...] ``` -Interogări agregate .[#toc-aggregate-queries] ---------------------------------------------- +fetchAll(): array .[method] +--------------------------- -| `$table->count('*')` | Obțineți numărul de rânduri -| `$table->count("DISTINCT $column")` | Obține numărul de valori distincte -| `$table->min($column)` | Obține valoarea minimă -| `$table->max($column)` | Obține valoarea maximă -| `$table->sum($column)` | Obține suma tuturor valorilor -| `$table->aggregation("GROUP_CONCAT($column)")` | Rulați orice funcție de agregare +Returnează toate rândurile ca array asociativ de obiecte `ActiveRow`, unde cheile sunt valorile cheilor primare. -.[caution] -Metoda `count()` fără niciun parametru specificat selectează toate înregistrările și returnează dimensiunea tabloului, ceea ce este foarte ineficient. De exemplu, dacă trebuie să calculați numărul de rânduri pentru paginare, specificați întotdeauna primul argument. +```php +$allBooks = $explorer->table('book')->fetchAll(); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` + + +count(): int .[method] +---------------------- + +Metoda `count()` fără parametru returnează numărul de rânduri din obiectul `Selection`: + +```php +$table->where('category', 1); +$count = $table->count(); +$count = count($table); // alternativă +``` + +Atenție, `count()` cu parametru execută funcția de agregare COUNT în baza de date. + + +ActiveRow::toArray(): array .[method] +------------------------------------- + +Convertește obiectul `ActiveRow` într-un array asociativ, unde cheile sunt numele coloanelor și valorile sunt datele corespunzătoare. + +```php +$book = $explorer->table('book')->get(1); +$bookArray = $book->toArray(); +// $bookArray va fi ['id' => 1, 'title' => '...', 'author_id' => ..., ...] +``` + + +Agregace +======== + +Clasa `Selection` oferă metode pentru executarea ușoară a funcțiilor de agregare (COUNT, SUM, MIN, MAX, AVG etc.). + +.[language-php] +| `count($expr)` | Numără numărul de rânduri +| `min($expr)` | Returnează valoarea minimă din coloană +| `max($expr)` | Returnează valoarea maximă din coloană +| `sum($expr)` | Returnează suma valorilor din coloană +| `aggregation($function)` | Permite executarea oricărei funcții de agregare. De ex. `AVG()`, `GROUP_CONCAT()` + + +count(string $expr): int .[method] +---------------------------------- + +Execută interogarea SQL cu funcția COUNT și returnează rezultatul. Metoda se utilizează pentru a afla câte rânduri corespund unei anumite condiții: + +```php +$count = $table->count('*'); // SELECT COUNT(*) FROM `table` +$count = $table->count('DISTINCT column'); // SELECT COUNT(DISTINCT `column`) FROM `table` +``` + +Atenție, [#count()] fără parametru returnează doar numărul de rânduri din obiectul `Selection`. + + +min(string $expr) și max(string $expr) .[method] +------------------------------------------------ + +Metodele `min()` și `max()` returnează valoarea minimă și maximă din coloana sau expresia specificată: + +```php +// SELECT MAX(`price`) FROM `products` WHERE `active` = 1 +$maxPrice = $products->where('active', true) + ->max('price'); +``` + + +sum(string $expr) .[method] +--------------------------- +Returnează suma valorilor din coloana sau expresia specificată: -Evadare și citare .[#toc-escaping-quoting] -========================================== +```php +// SELECT SUM(`price` * `items_in_stock`) FROM `products` WHERE `active` = 1 +$totalPrice = $products->where('active', true) + ->sum('price * items_in_stock'); +``` -Database Explorer este inteligent și evită parametrii și identificatorii de ghilimele pentru dumneavoastră. Totuși, trebuie respectate aceste reguli de bază: -- cuvintele cheie, funcțiile, procedurile trebuie să fie scrise cu majuscule -- coloanele și tabelele trebuie să fie scrise cu minuscule -- treceți variabilele ca parametri, nu concatenate +aggregation(string $function, ?string $groupFunction = null) .[method] +---------------------------------------------------------------------- + +Permite executarea oricărei funcții de agregare. ```php -->where('name like ?', 'John'); // WRONG! generează: `name` `like` ? -->where('name LIKE ?', 'John'); // CORECT +// prețul mediu al produselor din categorie +$avgPrice = $products->where('category_id', 1) + ->aggregation('AVG(price)'); -->where('KEY = ?', $value); // WRONG! KEY este un cuvânt cheie -->where('key = ?', $value); // CORECT. generează: `key` = ? +// unește etichetele produsului într-un singur șir +$tags = $products->where('id', 1) + ->aggregation('GROUP_CONCAT(tag.name) AS tags') + ->fetch() + ->tags; +``` -->where('name = ' . $name); // GREȘIT! injecție sql! -->where('name = ?', $name); // CORECT +Dacă avem nevoie să agregăm rezultate care deja provin dintr-o funcție de agregare și grupare (de ex. `SUM(valoare)` peste rândurile grupate), ca al doilea argument specificăm funcția de agregare care trebuie aplicată acestor rezultate intermediare: -->select('DATE_FORMAT(created, "%d.%m.%Y")'); // WRONG! treceți variabile ca parametri, nu concatenați -->select('DATE_FORMAT(created, ?)', '%d.%m.%Y'); // CORECT +```php +// Calculează prețul total al produselor din stoc pentru fiecare categorie și apoi adună aceste prețuri. +$totalPrice = $products->select('category_id, SUM(price * stock) AS category_total') + ->group('category_id') + ->aggregation('SUM(category_total)', 'SUM'); ``` -.[warning] -Utilizarea greșită poate produce găuri de securitate +În acest exemplu, mai întâi calculăm prețul total al produselor din fiecare categorie (`SUM(price * stock) AS category_total`) și grupăm rezultatele după `category_id`. Apoi folosim `aggregation('SUM(category_total)', 'SUM')` pentru a aduna aceste sume intermediare `category_total`. Al doilea argument `'SUM'` spune că funcția SUM trebuie aplicată rezultatelor intermediare. + + +Insert, Update & Delete +======================= + +Nette Database Explorer simplifică inserarea, actualizarea și ștergerea datelor. Toate metodele menționate aruncă excepția `Nette\Database\DriverException` în caz de eroare. -Preluarea datelor .[#toc-fetching-data] -======================================= +Selection::insert(iterable $data) .[method] +------------------------------------------- -| `foreach ($table as $id => $row)` | Iterați peste toate rândurile din rezultat -| `$row = $table->get($id)` | Obține un singur rând cu ID $id din tabel -| `$row = $table->fetch()` | Obține următorul rând din rezultat -| `$array = $table->fetchPairs($key, $value)` | Preluarea tuturor valorilor în matricea asociativă -| `$array = $table->fetchPairs($key)` | Preluarea tuturor rândurilor în matricea asociativă -| `count($table)` | Obține numărul de rânduri din setul de rezultate +Inserează înregistrări noi în tabelă. +**Inserarea unei singure înregistrări:** -Inserare, actualizare și ștergere .[#toc-insert-update-delete] -============================================================== +Transmitem noua înregistrare ca array asociativ sau obiect iterabil (de exemplu, ArrayHash utilizat în [formulare |forms:]), unde cheile corespund numelor coloanelor din tabelă. -Metoda `insert()` acceptă o matrice de obiecte Traversable (de exemplu, [ArrayHash |utils:arrays#ArrayHash], care returnează [formulare |forms:]): +Dacă tabela are o cheie primară definită, metoda returnează un obiect `ActiveRow`, care este reîncărcat din baza de date pentru a reflecta eventualele modificări efectuate la nivelul bazei de date (triggere, valori implicite ale coloanelor, calcule ale coloanelor auto-increment). Astfel se asigură consistența datelor și obiectul conține întotdeauna datele actuale din baza de date. Dacă nu are o cheie primară unică, returnează datele transmise sub formă de array. ```php $row = $explorer->table('users')->insert([ - 'name' => $name, - 'year' => $year, + 'name' => 'John Doe', + 'email' => 'john.doe@example.com', ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) +// $row este o instanță ActiveRow și conține datele complete ale rândului inserat, +// inclusiv ID-ul generat automat și eventualele modificări efectuate de triggere +echo $row->id; // Afișează ID-ul utilizatorului nou inserat +echo $row->created_at; // Afișează timpul creării, dacă este setat de un trigger ``` -În cazul în care cheia primară este definită în tabel, se returnează un obiect ActiveRow care conține rândul inserat. +**Inserarea mai multor înregistrări deodată:** -Inserare multiplă: +Metoda `insert()` permite inserarea mai multor înregistrări printr-o singură interogare SQL. În acest caz, returnează numărul de rânduri inserate. ```php -$explorer->table('users')->insert([ +$insertedRows = $explorer->table('users')->insert([ + [ + 'name' => 'John', + 'year' => 1994, + ], [ - 'name' => 'Jim', - 'year' => 1978, - ], [ 'name' => 'Jack', - 'year' => 1987, + 'year' => 1995, ], ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) +// INSERT INTO `users` (`name`, `year`) VALUES ('John', 1994), ('Jack', 1995) +// $insertedRows va fi 2 ``` -Fișiere sau obiecte DateTime pot fi transmise ca parametri: +Ca parametru se poate transmite și un obiect `Selection` cu selecția de date. + +```php +$newUsers = $explorer->table('potential_users') + ->where('approved', 1) + ->select('name, email'); + +$insertedRows = $explorer->table('users')->insert($newUsers); +``` + +**Inserarea valorilor speciale:** + +Ca valori putem transmite și fișiere, obiecte DateTime sau literali SQL: ```php $explorer->table('users')->insert([ - 'name' => $name, - 'created' => new DateTime, // sau $explorer::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // inserează fișierul + 'name' => 'John', + 'created_at' => new DateTime, // convertește la formatul bazei de date + 'avatar' => fopen('image.jpg', 'rb'), // inserează conținutul binar al fișierului + 'uuid' => $explorer::literal('UUID()'), // apelează funcția UUID() ]); ``` -Actualizare (returnează numărul de rânduri afectate): + +Selection::update(iterable $data): int .[method] +------------------------------------------------ + +Actualizează rândurile din tabelă conform filtrului specificat. Returnează numărul de rânduri efectiv modificate. + +Coloanele modificate le transmitem ca array asociativ sau obiect iterabil (de exemplu, ArrayHash utilizat în [formulare |forms:]), unde cheile corespund numelor coloanelor din tabelă: ```php -$count = $explorer->table('users') - ->where('id', 10) // trebuie să fie apelat înainte de update() +$affected = $explorer->table('users') + ->where('id', 10) ->update([ - 'name' => 'Ned Stark' + 'name' => 'John Smith', + 'year' => 1994, ]); -// UPDATE `users` SET `name`='Ned Stark' WHERE (`id` = 10) +// UPDATE `users` SET `name` = 'John Smith', `year` = 1994 WHERE `id` = 10 ``` -Pentru actualizare se pot folosi operatorii `+=` a `-=`: +Pentru modificarea valorilor numerice putem folosi operatorii `+=` și `-=`: ```php $explorer->table('users') + ->where('id', 10) ->update([ - 'age+=' => 1, // vezi += + 'points+=' => 1, // crește valoarea coloanei 'points' cu 1 + 'coins-=' => 1, // scade valoarea coloanei 'coins' cu 1 ]); -// UPDATE users SET `age` = `age` + 1 +// UPDATE `users` SET `points` = `points` + 1, `coins` = `coins` - 1 WHERE `id` = 10 ``` -Ștergere (returnează numărul de rânduri șterse): + +Selection::delete(): int .[method] +---------------------------------- + +Șterge rândurile din tabelă conform filtrului specificat. Returnează numărul de rânduri șterse. ```php $count = $explorer->table('users') ->where('id', 10) ->delete(); -// DELETE FROM `users` WHERE (`id` = 10) +// DELETE FROM `users` WHERE `id` = 10 ``` +.[caution] +La apelarea `update()` și `delete()`, nu uitați să specificați rândurile care trebuie modificate/șterse folosind `where()`. Dacă nu utilizați `where()`, operația se va efectua pe întreaga tabelă! -Lucrul cu relații .[#toc-working-with-relationships] -==================================================== +ActiveRow::update(iterable $data): bool .[method] +------------------------------------------------- -Are o singură relație .[#toc-has-one-relation] ----------------------------------------------- -Are o singură relație este un caz obișnuit de utilizare. Cartea *are un* autor. Cartea *are un* traducător. Obținerea rândului de relații se face în principal prin metoda `ref()`. Aceasta acceptă două argumente: numele tabelului țintă și coloana de legătură sursă. A se vedea exemplul: +Actualizează datele din rândul bazei de date reprezentat de obiectul `ActiveRow`. Ca parametru acceptă un iterabil cu datele care trebuie actualizate (cheile sunt numele coloanelor). Pentru modificarea valorilor numerice putem folosi operatorii `+=` și `-=`: + +După efectuarea actualizării, `ActiveRow` se reîncarcă automat din baza de date pentru a reflecta eventualele modificări efectuate la nivelul bazei de date (de ex. triggere). Metoda returnează true doar dacă a avut loc o modificare efectivă a datelor. ```php -$book = $explorer->table('book')->get(1); -$book->ref('author', 'author_id'); +$article = $explorer->table('article')->get(1); +$article->update([ + 'views += 1', // creștem numărul de vizualizări +]); +echo $article->views; // Afișează numărul curent de vizualizări ``` -În exemplul de mai sus, preluăm intrarea autorului asociat din tabelul `author`, cheia primară a autorului este căutată prin coloana `book.author_id`. Metoda Ref() returnează o instanță ActiveRow sau este nulă dacă nu există o intrare corespunzătoare. Rândul returnat este o instanță de ActiveRow, astfel încât putem lucra cu el în același mod ca și cu intrarea de carte. +Această metodă actualizează doar un singur rând specific din baza de date. Pentru actualizarea în masă a mai multor rânduri, utilizați metoda [#Selection::update()]. + + +ActiveRow::delete() .[method] +----------------------------- + +Șterge rândul din baza de date, care este reprezentat de obiectul `ActiveRow`. ```php -$author = $book->ref('author', 'author_id'); -$author->name; -$author->born; +$book = $explorer->table('book')->get(1); +$book->delete(); // Șterge cartea cu ID 1 +``` + +Această metodă șterge doar un singur rând specific din baza de date. Pentru ștergerea în masă a mai multor rânduri, utilizați metoda [#Selection::delete()]. + + +Relații între tabele +==================== + +În bazele de date relaționale, datele sunt împărțite în mai multe tabele și interconectate prin chei străine. Nette Database Explorer aduce o modalitate revoluționară de a lucra cu aceste legături - fără a scrie interogări JOIN și fără a fi nevoie să configurați sau să generați ceva. + +Pentru a ilustra lucrul cu legăturile, vom folosi exemplul bazei de date de cărți ([îl găsiți pe GitHub |https://github.com/nette-examples/books]). În baza de date avem tabelele: + +- `author` - scriitori și traducători (coloane `id`, `name`, `web`, `born`) +- `book` - cărți (coloane `id`, `author_id`, `translator_id`, `title`, `sequel_id`) +- `tag` - etichete (coloane `id`, `name`) +- `book_tag` - tabelă de legătură între cărți și etichete (coloane `book_id`, `tag_id`) + +[* db-schema-1-.webp *] *** Structura bazei de date folosită în exemple .<> + +În exemplul nostru de bază de date de cărți găsim mai multe tipuri de relații (deși modelul este simplificat față de realitate): + +- One-to-many 1:N – fiecare carte **are un** autor, autorul poate scrie **mai multe** cărți +- Zero-to-many 0:N – cartea **poate avea** un traducător, traducătorul poate traduce **mai multe** cărți +- Zero-to-one 0:1 – cartea **poate avea** o continuare +- Many-to-many M:N – cartea **poate avea mai multe** etichete și o etichetă poate fi atribuită **mai multor** cărți + +În aceste relații există întotdeauna o tabelă părinte și una copil. De exemplu, în relația dintre autor și carte, tabela `author` este părinte și `book` este copil - ne putem imagina că o carte "aparține" întotdeauna unui autor. Acest lucru se reflectă și în structura bazei de date: tabela copil `book` conține cheia străină `author_id`, care face referire la tabela părinte `author`. -// sau direct -$book->ref('author', 'author_id')->name; -$book->ref('author', 'author_id')->born; +Dacă avem nevoie să afișăm cărțile inclusiv numele autorilor lor, avem două opțiuni. Fie obținem datele printr-o singură interogare SQL folosind JOIN: + +```sql +SELECT book.*, author.name FROM book LEFT JOIN author ON book.author_id = author.id +``` + +Fie încărcăm datele în doi pași - mai întâi cărțile și apoi autorii lor - și apoi le asamblăm în PHP: + +```sql +SELECT * FROM book; +SELECT * FROM author WHERE id IN (1, 2, 3); -- id-urile autorilor cărților obținute ``` -Cartea are, de asemenea, un traducător, astfel încât obținerea numelui traducătorului este destul de ușoară. +A doua abordare este de fapt mai eficientă, deși poate fi surprinzător. Datele sunt încărcate o singură dată și pot fi utilizate mai bine în cache. Exact în acest mod lucrează Nette Database Explorer - rezolvă totul sub capotă și vă oferă o API elegantă: + ```php -$book->ref('author', 'translator_id')->name +$books = $explorer->table('book'); +foreach ($books as $book) { + echo 'titlu: ' . $book->title; + echo 'scris de: ' . $book->author->name; // $book->author este înregistrarea din tabela 'author' + echo 'tradus de: ' . $book->translator?->name; +} ``` -Toate acestea sunt bune, dar sunt oarecum greoaie, nu credeți? Database Explorer conține deja definițiile cheilor străine, așa că de ce să nu le folosim automat? Haideți să facem asta! -Dacă apelăm o proprietate, care nu există, ActiveRow încearcă să rezolve numele proprietății apelante ca fiind o relație "are o". Obținerea acestei proprietăți este identică cu apelarea metodei ref() cu un singur argument. Vom numi singurul argument **key**. Cheia va fi rezolvată în funcție de o anumită relație de cheie străină. Cheia transmisă este comparată cu coloanele rândului și, dacă se potrivește, cheia externă definită pe coloana corespunzătoare este utilizată pentru a obține date din tabelul țintă aferent. A se vedea exemplul: +Accesul la tabela părinte +------------------------- + +Accesul la tabela părinte este direct. Este vorba despre relații precum *cartea are un autor* sau *cartea poate avea un traducător*. Obținem înregistrarea asociată prin proprietatea obiectului ActiveRow - numele său corespunde numelui coloanei cu cheia străină fără `_id`: ```php -$book->author->name; -// la fel ca -$book->ref('author')->name; +$book = $explorer->table('book')->get(1); +echo $book->author->name; // găsește autorul după coloana author_id +echo $book->translator?->name; // găsește traducătorul după translator_id ``` -Instanța ActiveRow nu are o coloană "author". Toate coloanele de cărți sunt căutate pentru a găsi o potrivire cu *key*. În acest caz, potrivirea înseamnă că numele coloanei trebuie să conțină cheia. Astfel, în exemplul de mai sus, coloana `author_id` conține șirul de caractere "author" și, prin urmare, se potrivește cu cheia "author". Dacă doriți să obțineți traducătorul cărții, puteți utiliza, de exemplu, "translator" ca cheie, deoarece cheia "translator" se va potrivi cu coloana `translator_id`. Puteți afla mai multe despre logica de potrivire a cheilor în capitolul [Expresii de îmbinare |#joining-key]. +Când accesăm proprietatea `$book->author`, Explorer caută în tabela `book` o coloană al cărei nume conține șirul `author` (adică `author_id`). După valoarea din această coloană, încarcă înregistrarea corespunzătoare din tabela `author` și o returnează ca `ActiveRow`. Similar funcționează și `$book->translator`, care utilizează coloana `translator_id`. Deoarece coloana `translator_id` poate conține `null`, folosim în cod operatorul `?->`. + +O cale alternativă o oferă metoda `ref()`, care acceptă doi argumente, numele tabelei țintă și numele coloanei de legătură, și returnează o instanță `ActiveRow` sau `null`: ```php -echo $book->title . ': '; -echo $book->author->name; -if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; -} +echo $book->ref('author', 'author_id')->name; // legătura cu autorul +echo $book->ref('author', 'translator_id')->name; // legătura cu traducătorul ``` -Dacă doriți să obțineți mai multe cărți, trebuie să utilizați aceeași abordare. Nette Database Explorer va prelua deodată autorii și traducătorii pentru toate cărțile preluate. +Metoda `ref()` este utilă dacă nu se poate utiliza accesul prin proprietate, deoarece tabela conține o coloană cu același nume (adică `author`). În celelalte cazuri, se recomandă utilizarea accesului prin proprietate, care este mai lizibil. + +Explorer optimizează automat interogările bazei de date. Când parcurgem cărțile într-un ciclu și accesăm înregistrările lor asociate (autori, traducători), Explorer nu generează o interogare pentru fiecare carte în parte. În schimb, execută doar un singur SELECT pentru fiecare tip de legătură, reducând semnificativ sarcina bazei de date. De exemplu: ```php $books = $explorer->table('book'); foreach ($books as $book) { echo $book->title . ': '; echo $book->author->name; - if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; - } + echo $book->translator?->name; } ``` -Codul va rula doar aceste 3 interogări: +Acest cod apelează doar aceste trei interogări fulgerătoare în baza de date: + ```sql SELECT * FROM `book`; -SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- ids of fetched books from author_id column -SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- ids of fetched books from translator_id column +SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- id din coloana author_id a cărților selectate +SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- id din coloana translator_id a cărților selectate ``` +.[note] +Logica de căutare a coloanei de legătură este dată de implementarea [Conventions |api:Nette\Database\Conventions]. Recomandăm utilizarea [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions], care analizează cheile străine și permite lucrul simplu cu relațiile existente între tabele. -Are multe relații .[#toc-has-many-relation] -------------------------------------------- -Relația "are mai mulți" este doar o relație inversă a relației "are unul". Autorul *are* a scris *mai multe* cărți. Autorul *a* tradus *mai multe* cărți. După cum puteți vedea, acest tip de relație este puțin mai dificil, deoarece relația este "nominală" ("scris", "tradus"). Instanța ActiveRow are metoda `related()`, care va returna o matrice de intrări legate. Intrările sunt, de asemenea, instanțe ActiveRow. A se vedea exemplul de mai jos: +Accesul la tabela copil +----------------------- + +Accesul la tabela copil funcționează în direcția opusă. Acum întrebăm *ce cărți a scris acest autor* sau *a tradus acest traducător*. Pentru acest tip de interogare folosim metoda `related()`, care returnează `Selection` cu înregistrările asociate. Să vedem un exemplu: ```php -$author = $explorer->table('author')->get(11); -echo $author->name . ' has written:'; +$author = $explorer->table('author')->get(1); +// Afișează toate cărțile autorului foreach ($author->related('book.author_id') as $book) { - echo $book->title; + echo "A scris: $book->title"; } -echo 'and translated:'; +// Afișează toate cărțile pe care autorul le-a tradus foreach ($author->related('book.translator_id') as $book) { - echo $book->title; + echo "A tradus: $book->title"; } ``` -Metoda `related()` Metoda acceptă descrierea completă a îmbinării transmisă ca două argumente sau ca un singur argument unit prin punct. Primul argument este tabelul țintă, iar al doilea este coloana țintă. +Metoda `related()` acceptă descrierea legăturii ca un singur argument cu notație cu punct sau ca doi argumente separate: ```php -$author->related('book.translator_id'); -// la fel ca -$author->related('book', 'translator_id'); +$author->related('book.translator_id'); // un argument +$author->related('book', 'translator_id'); // doi argumente ``` -Puteți utiliza euristica Nette Database Explorer bazată pe chei străine și să furnizați doar argumentul **key**. Cheia va fi comparată cu toate cheile străine care indică spre tabelul curent (`author` table). Dacă există o potrivire, [Nette |api:Nette\InvalidArgumentException] Database Explorer va utiliza această cheie externă, în caz contrar va arunca [Nette\InvalidArgumentException |api:Nette\InvalidArgumentException] sau [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. Puteți afla mai multe despre logica de potrivire a cheilor în capitolul [Expresii de îmbinare |#joining-key]. +Explorer poate detecta automat coloana de legătură corectă pe baza numelui tabelei părinte. În acest caz, se leagă prin coloana `book.author_id`, deoarece numele tabelei sursă este `author`: -Desigur, puteți apela metodele aferente pentru toți autorii recuperați, Nette Database Explorer va recupera din nou cărțile corespunzătoare deodată. +```php +$author->related('book'); // utilizează book.author_id +``` + +Dacă ar exista mai multe legături posibile, Explorer ar arunca excepția [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. + +Metoda `related()` o putem folosi, desigur, și la parcurgerea mai multor înregistrări într-un ciclu și Explorer optimizează automat interogările și în acest caz: ```php $authors = $explorer->table('author'); foreach ($authors as $author) { - echo $author->name . ' has written:'; + echo $author->name . ' a scris:'; foreach ($author->related('book') as $book) { - $book->title; + echo $book->title; } } ``` -Exemplul de mai sus va rula doar două interogări: +Acest cod generează doar două interogări SQL fulgerătoare: ```sql SELECT * FROM `author`; -SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- ids of fetched authors +SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- id-urile autorilor selectați ``` -Crearea manuală a exploratorului .[#toc-creating-explorer-manually] -=================================================================== +Legătura Many-to-many +--------------------- -O conexiune la baza de date poate fi creată folosind configurația aplicației. În astfel de cazuri, se creează un serviciu `Nette\Database\Explorer` care poate fi trecut ca dependență cu ajutorul containerului DI. +Pentru legătura many-to-many (M:N) este necesară existența unei tabele de legătură (în cazul nostru `book_tag`), care conține două coloane cu chei străine (`book_id`, `tag_id`). Fiecare dintre aceste coloane face referire la cheia primară a uneia dintre tabelele legate. Pentru a obține datele asociate, mai întâi obținem înregistrările din tabela de legătură folosind `related('book_tag')` și apoi continuăm către datele țintă: -Cu toate acestea, în cazul în care Nette Database Explorer este utilizat ca instrument independent, trebuie creată manual o instanță a obiectului `Nette\Database\Explorer`. +```php +$book = $explorer->table('book')->get(1); +// afișează numele etichetelor atribuite cărții +foreach ($book->related('book_tag') as $bookTag) { + echo $bookTag->tag->name; // afișează numele etichetei prin tabela de legătură +} + +$tag = $explorer->table('tag')->get(1); +// sau invers: afișează numele cărților etichetate cu această etichetă +foreach ($tag->related('book_tag') as $bookTag) { + echo $bookTag->book->title; // afișează numele cărții +} +``` + +Explorer optimizează din nou interogările SQL într-o formă eficientă: + +```sql +SELECT * FROM `book`; +SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 2, ...)); -- id-urile cărților selectate +SELECT * FROM `tag` WHERE (`tag`.`id` IN (1, 2, ...)); -- id-urile etichetelor găsite în book_tag +``` + + +Interogarea prin tabele asociate +-------------------------------- + +În metodele `where()`, `select()`, `order()` și `group()` putem folosi notații speciale pentru accesarea coloanelor din alte tabele. Explorer creează automat JOIN-urile necesare. + +**Notația cu punct** (`tabela_parinte.coloana`) se utilizează pentru relația 1:N din perspectiva tabelei copil: ```php -// $storage implements Nette\Caching\Storage: -$storage = new Nette\Caching\Storages\FileStorage($tempDir); -$connection = new Nette\Database\Connection($dsn, $user, $password); -$structure = new Nette\Database\Structure($connection, $storage); -$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); -$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +$books = $explorer->table('book'); + +// Găsește cărțile al căror autor are numele începând cu 'Jon' +$books->where('author.name LIKE ?', 'Jon%'); + +// Sortează cărțile după numele autorului descrescător +$books->order('author.name DESC'); + +// Afișează titlul cărții și numele autorului +$books->select('book.title, author.name'); +``` + +**Notația cu două puncte** (`:tabela_copil.coloana`) se utilizează pentru relația 1:N din perspectiva tabelei părinte: + +```php +$authors = $explorer->table('author'); + +// Găsește autorii care au scris o carte cu 'PHP' în titlu +$authors->where(':book.title LIKE ?', '%PHP%'); + +// Calculează numărul de cărți pentru fiecare autor +$authors->select('*, COUNT(:book.id) AS book_count') + ->group('author.id'); +``` + +În exemplul de mai sus cu notația cu două puncte (`:book.title`), nu este specificată coloana cu cheia străină. Explorer detectează automat coloana corectă pe baza numelui tabelei părinte. În acest caz, se leagă prin coloana `book.author_id`, deoarece numele tabelei sursă este `author`. Dacă ar exista mai multe legături posibile, Explorer ar arunca excepția [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. + +Coloana de legătură poate fi specificată explicit în paranteză: + +```php +// Găsește autorii care au tradus o carte cu 'PHP' în titlu +$authors->where(':book(translator_id).title LIKE ?', '%PHP%'); +``` + +Notațiile pot fi înlănțuite pentru accesul prin mai multe tabele: + +```php +// Găsește autorii cărților etichetate cu 'PHP' +$authors->where(':book:book_tag.tag.name', 'PHP') + ->group('author.id'); ``` + + +Extinderea condițiilor pentru JOIN +---------------------------------- + +Metoda `joinWhere()` extinde condițiile care se specifică la legarea tabelelor în SQL după cuvântul cheie `ON`. + +Să presupunem că dorim să găsim cărțile traduse de un anumit traducător: + +```php +// Găsește cărțile traduse de traducătorul numit 'David' +$books = $explorer->table('book') + ->joinWhere('translator', 'translator.name', 'David'); +// LEFT JOIN author translator ON book.translator_id = translator.id AND (translator.name = 'David') +``` + +În condiția `joinWhere()` putem folosi aceleași construcții ca în metoda `where()` - operatori, semne de întrebare substituente, array-uri de valori sau expresii SQL. + +Pentru interogări mai complexe cu mai multe JOIN-uri, putem defini aliasuri pentru tabele: + +```php +$tags = $explorer->table('tag') + ->joinWhere(':book_tag.book.author', 'book_author.born < ?', 1950) + ->alias(':book_tag.book.author', 'book_author'); +// LEFT JOIN `book_tag` ON `tag`.`id` = `book_tag`.`tag_id` +// LEFT JOIN `book` ON `book_tag`.`book_id` = `book`.`id` +// LEFT JOIN `author` `book_author` ON `book`.`author_id` = `book_author`.`id` +// AND (`book_author`.`born` < 1950) +``` + +Observați că, în timp ce metoda `where()` adaugă condiții în clauza `WHERE`, metoda `joinWhere()` extinde condițiile în clauza `ON` la legarea tabelelor. diff --git a/database/ro/guide.texy b/database/ro/guide.texy new file mode 100644 index 0000000000..f315aff423 --- /dev/null +++ b/database/ro/guide.texy @@ -0,0 +1,216 @@ +Nette Database +************** + +.[perex] +Nette Database este un strat de baze de date puternic și elegant pentru PHP, cu accent pe simplitate și funcții inteligente. Oferă două moduri de a lucra cu baza de date - [Explorer |explorer] pentru dezvoltarea rapidă a aplicațiilor sau [abordarea SQL |sql-way] pentru lucrul direct cu interogări. + +<div class="grid gap-3"> +<div> + + +[Abordarea SQL |sql-way] +======================== +- Interogări parametrizate sigure +- Control precis asupra formei interogărilor SQL +- Când scrieți interogări complexe cu funcții avansate +- Optimizați performanța folosind funcții SQL specifice + +</div> + +<div> + + +[Explorer |explorer] +==================== +- Dezvoltați rapid fără a scrie SQL +- Lucru intuitiv cu relațiile dintre tabele +- Apreciați optimizarea automată a interogărilor +- Potrivit pentru lucrul rapid și confortabil cu baza de date + +</div> + +</div> + + +Instalare +========= + +Descărcați și instalați biblioteca folosind [Composer|best-practices:composer]: + +```shell +composer require nette/database +``` + + +Baze de date suportate +====================== + +Nette Database suportă următoarele baze de date: + +|* Server de baze de date |* Nume DSN |* Suport în Explorer +|---------------------|-------------|----------------------- +| MySQL (>= 5.1) | mysql | DA +| PostgreSQL (>= 9.0) | pgsql | DA +| Sqlite 3 (>= 3.8) | sqlite | DA +| Oracle | oci | - +| MS SQL (PDO_SQLSRV) | sqlsrv | DA +| MS SQL (PDO_DBLIB) | mssql | - +| ODBC | odbc | - + + +Două abordări ale bazei de date +=============================== + +Nette Database vă oferă o alegere: puteți fie să scrieți interogări SQL direct (abordarea SQL), fie să le lăsați generate automat (Explorer). Să vedem cum ambele abordări rezolvă aceleași sarcini: + +[Abordarea SQL|sql-way] - Interogări SQL + +```php +// inserarea unei înregistrări +$database->query('INSERT INTO books', [ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// obținerea înregistrărilor: autorii cărților +$result = $database->query(' + SELECT authors.*, COUNT(books.id) AS books_count + FROM authors + LEFT JOIN books ON authors.id = books.author_id + WHERE authors.active = 1 + GROUP BY authors.id +'); + +// listare (nu este optimă, generează N interogări suplimentare) +foreach ($result as $author) { + $books = $database->query(' + SELECT * FROM books + WHERE author_id = ? + ORDER BY published_at DESC + ', $author->id); + + echo "Autorul $author->name a scris $author->books_count cărți:\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +[Abordarea Explorer|explorer] - generare automată SQL + +```php +// inserarea unei înregistrări +$database->table('books')->insert([ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// obținerea înregistrărilor: autorii cărților +$authors = $database->table('authors') + ->where('active', 1); + +// listare (generează automat doar 2 interogări optimizate) +foreach ($authors as $author) { + $books = $author->related('books') + ->order('published_at DESC'); + + echo "Autorul $author->name a scris {$books->count()} cărți:\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +Abordarea Explorer generează și optimizează interogările SQL automat. În exemplul dat, abordarea SQL generează N+1 interogări (una pentru autori și apoi una pentru cărțile fiecărui autor), în timp ce Explorer optimizează automat interogările și execută doar două - una pentru autori și una pentru toate cărțile lor. + +Ambele abordări pot fi combinate liber în aplicație după cum este necesar. + + +Conectare și configurare +======================== + +Pentru a vă conecta la baza de date, trebuie doar să creați o instanță a clasei [api:Nette\Database\Connection]: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password); +``` + +Parametrul `$dsn` (data source name) este același [ca cel utilizat de PDO |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], de ex. `mysql:host=127.0.0.1;dbname=test`. În caz de eșec, aruncă o excepție `Nette\Database\ConnectionException`. + +Cu toate acestea, o modalitate mai convenabilă este oferită de [configurația aplicației |configuration], unde trebuie doar să adăugați secțiunea `database` și se vor crea obiectele necesare, precum și panoul bazei de date în bara [Tracy |tracy:]. + +```neon +database: + dsn: 'mysql:host=127.0.0.1;dbname=test' + user: root + password: password +``` + +Apoi, [obținem obiectul conexiunii ca serviciu din containerul DI |dependency-injection:passing-dependencies], de exemplu: + +```php +class Model +{ + public function __construct( + // sau Nette\Database\Explorer + private Nette\Database\Connection $database, + ) { + } +} +``` + +Mai multe informații despre [configurarea bazei de date |configuration]. + + +Crearea manuală a Explorer +-------------------------- + +Dacă nu utilizați containerul Nette DI, puteți crea manual o instanță `Nette\Database\Explorer`: + +```php +// conectare la baza de date +$connection = new Nette\Database\Connection('mysql:host=127.0.0.1;dbname=mydatabase', 'user', 'password'); +// stocare pentru cache, implementează Nette\Caching\Storage, de ex.: +$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp/dir'); +// se ocupă de reflexia structurii bazei de date +$structure = new Nette\Database\Structure($connection, $storage); +// definește reguli pentru maparea numelor de tabele, coloane și chei străine +$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); +$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +``` + + +Gestionarea conexiunii +====================== + +La crearea obiectului `Connection`, conexiunea se stabilește automat. Dacă doriți să amânați conexiunea, utilizați modul lazy - îl activați în [configurație |configuration] setând `lazy: true`, sau astfel: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password, ['lazy' => true]); +``` + +Pentru gestionarea conexiunii, utilizați metodele `connect()`, `disconnect()` și `reconnect()`. +- `connect()` creează conexiunea dacă nu există deja, putând arunca o excepție `Nette\Database\ConnectionException`. +- `disconnect()` deconectează conexiunea curentă la baza de date. +- `reconnect()` efectuează deconectarea și apoi reconectarea la baza de date. Această metodă poate arunca, de asemenea, o excepție `Nette\Database\ConnectionException`. + +În plus, puteți monitoriza evenimentele legate de conexiune folosind evenimentul `onConnect`, care este un array de callback-uri care sunt apelate după stabilirea conexiunii cu baza de date. + +```php +// se execută după conectarea la baza de date +$database->onConnect[] = function($database) { + echo "Conectat la baza de date"; +}; +``` + + +Bara de depanare Tracy +====================== + +Dacă utilizați [Tracy |tracy:], panoul Database se activează automat în bara de depanare, afișând toate interogările executate, parametrii lor, timpul de execuție și locul din cod unde au fost apelate. + +[* db-panel.webp *] diff --git a/database/ro/mapping.texy b/database/ro/mapping.texy new file mode 100644 index 0000000000..e64374fb06 --- /dev/null +++ b/database/ro/mapping.texy @@ -0,0 +1,55 @@ +Conversia tipurilor +******************* + +.[perex] +Nette Database convertește automat valorile returnate din baza de date în tipurile PHP corespunzătoare. + + +Data și ora +----------- + +Datele de timp sunt convertite în obiecte `Nette\Utils\DateTime`. Dacă doriți ca datele de timp să fie convertite în obiecte imuabile `Nette\Database\DateTime`, setați opțiunea `newDateTime` la true în [configurație|configuration]. + +```php +$row = $database->fetch('SELECT created_at FROM articles'); +echo $row->created_at instanceof DateTime; // true +echo $row->created_at->format('j. n. Y'); +``` + +În cazul MySQL, convertește tipul de date `TIME` în obiecte `DateInterval`. + + +Valori booleene +--------------- + +Valorile booleene sunt convertite automat în `true` sau `false`. Pentru MySQL, se convertește `TINYINT(1)` dacă setăm `convertBoolean: true` în [configurație |configuration]. + +```php +$row = $database->fetch('SELECT is_published FROM articles'); +echo gettype($row->is_published); // 'boolean' +``` + + +Valori numerice +--------------- + +Valorile numerice sunt convertite în `int` sau `float` în funcție de tipul coloanei din baza de date: + +```php +$row = $database->fetch('SELECT id, price FROM products'); +echo gettype($row->id); // integer +echo gettype($row->price); // float +``` + + +Normalizare personalizată +------------------------- + +Folosind metoda `setRowNormalizer(?callable $normalizer)`, puteți seta o funcție personalizată pentru transformarea rândurilor din baza de date. Acest lucru este util, de exemplu, pentru conversia automată a tipurilor de date. + +```php +$database->setRowNormalizer(function(array $row, ResultSet $resultSet): array { + // aici are loc conversia tipurilor + return $row; +}); +``` diff --git a/database/ro/reflection.texy b/database/ro/reflection.texy new file mode 100644 index 0000000000..33121e9002 --- /dev/null +++ b/database/ro/reflection.texy @@ -0,0 +1,125 @@ +Reflexia structurii +******************* + +.{data-version:3.2.1} +Nette Database oferă instrumente pentru introspecția structurii bazei de date folosind clasa [api:Nette\Database\Structure]. Aceasta permite obținerea de informații despre tabele, coloane, indecși și chei străine. Puteți utiliza reflexia pentru a genera scheme, a crea aplicații flexibile care lucrează cu baza de date sau instrumente generale pentru baze de date. + +Obținem obiectul structurii din instanța conexiunii la baza de date: + +```php +$reflection = $database->getReflection(); +``` + + +Obținerea tabelelor +------------------- + +Metoda `getTables()` returnează un array cu informații despre toate tabelele: + +```php +// Listarea numelor tuturor tabelelor +foreach ($structure->getTables() as $table) { + echo $table['name'] . "\n"; +} +``` + +Sunt disponibile încă două metode: + +```php +// Verificarea existenței tabelului +if ($reflection->hasTable('users')) { + echo "Tabelul users există"; +} + +// Returnează obiectul tabelului; dacă nu există, aruncă o excepție +$table = $reflection->getTable('users'); +``` + + +Informații despre tabel +----------------------- + +Tabelul este reprezentat de obiectul [Table|api:Nette\Database\Reflection\Table], care oferă următoarele proprietăți readonly: + +- `$name: string` – numele tabelului +- `$view: bool` – dacă este o vizualizare +- `$fullName: ?string` – numele complet al tabelului, inclusiv schema (dacă există) +- `$columns: array<string, Column>` – array asociativ al coloanelor tabelului +- `$indexes: Index[]` – array de indecși ai tabelului +- `$primaryKey: ?Index` – cheia primară a tabelului sau null +- `$foreignKeys: ForeignKey[]` – array de chei străine ale tabelului + + +Coloane +------- + +Proprietatea `columns` a tabelului oferă un array asociativ de coloane, unde cheia este numele coloanei și valoarea este o instanță [Column|api:Nette\Database\Reflection\Column] cu aceste proprietăți: + +- `$name: string` – numele coloanei +- `$table: ?Table` – referință la tabelul coloanei +- `$nativeType: string` – tipul de date nativ al bazei de date +- `$size: ?int` – dimensiunea/lungimea tipului +- `$nullable: bool` – dacă coloana poate conține NULL +- `$default: mixed` – valoarea implicită a coloanei +- `$autoIncrement: bool` – dacă coloana este auto-increment +- `$primary: bool` – dacă face parte din cheia primară +- `$vendor: array` – metadate suplimentare specifice sistemului de baze de date respectiv + +```php +foreach ($table->columns as $name => $column) { + echo "Coloană: $name\n"; + echo "Tip: {$column->nativeType}\n"; + echo "Nullable: " . ($column->nullable ? 'Da' : 'Nu') . "\n"; +} +``` + + +Indecși +------- + +Proprietatea `indexes` a tabelului oferă un array de indecși, unde fiecare index este o instanță [Index|api:Nette\Database\Reflection\Index] cu aceste proprietăți: + +- `$columns: Column[]` – array de coloane care formează indexul +- `$unique: bool` – dacă indexul este unic +- `$primary: bool` – dacă este cheia primară +- `$name: ?string` – numele indexului + +Cheia primară a tabelului poate fi obținută folosind proprietatea `primaryKey`, care returnează fie un obiect `Index`, fie `null` în cazul în care tabelul nu are cheie primară. + +```php +// Listarea indecșilor +foreach ($table->indexes as $index) { + $columns = implode(', ', array_map(fn($col) => $col->name, $index->columns)); + echo "Index" . ($index->name ? " {$index->name}" : '') . ":\n"; + echo " Coloane: $columns\n"; + echo " Unic: " . ($index->unique ? 'Da' : 'Nu') . "\n"; +} + +// Listarea cheii primare +if ($primaryKey = $table->primaryKey) { + $columns = implode(', ', array_map(fn($col) => $col->name, $primaryKey->columns)); + echo "Cheie primară: $columns\n"; +} +``` + + +Chei străine +------------ + +Proprietatea `foreignKeys` a tabelului oferă un array de chei străine, unde fiecare cheie străină este o instanță [ForeignKey|api:Nette\Database\Reflection\ForeignKey] cu aceste proprietăți: + +- `$foreignTable: Table` – tabelul referit +- `$localColumns: Column[]` – array de coloane locale +- `$foreignColumns: Column[]` – array de coloane referite +- `$name: ?string` – numele cheii străine + +```php +// Listarea cheilor străine +foreach ($table->foreignKeys as $fk) { + $localCols = implode(', ', array_map(fn($col) => $col->name, $fk->localColumns)); + $foreignCols = implode(', ', array_map(fn($col) => $col->name, $fk->foreignColumns)); + + echo "FK" . ($fk->name ? " {$fk->name}" : '') . ":\n"; + echo " $localCols -> {$fk->foreignTable->name}($foreignCols)\n"; +} +``` diff --git a/database/ro/security.texy b/database/ro/security.texy new file mode 100644 index 0000000000..fc616b8c1c --- /dev/null +++ b/database/ro/security.texy @@ -0,0 +1,185 @@ +Riscuri de securitate +********************* + +<div class=perex> + +Baza de date conține adesea date sensibile și permite efectuarea de operațiuni periculoase. Pentru a lucra în siguranță cu Nette Database, este esențial să: + +- Înțelegeți diferența dintre API-ul sigur și cel nesigur +- Utilizați interogări parametrizate +- Validați corect datele de intrare + +</div> + + +Ce este SQL Injection? +====================== + +SQL injection este cel mai grav risc de securitate atunci când lucrați cu o bază de date. Apare atunci când intrarea nesanitizată de la utilizator devine parte a unei interogări SQL. Atacatorul poate introduce propriile comenzi SQL și astfel: +- Obține acces neautorizat la date +- Modifică sau șterge datele din baza de date +- Ocolește autentificarea + +```php +// ❌ COD PERICULOS - vulnerabil la SQL injection +$database->query("SELECT * FROM users WHERE name = '$_GET[name]'"); + +// Atacatorul poate introduce, de exemplu, valoarea: ' OR '1'='1 +// Interogarea rezultată va fi: SELECT * FROM users WHERE name = '' OR '1'='1' +// Ceea ce returnează toți utilizatorii +``` + +Același lucru este valabil și pentru Database Explorer: + +```php +// ❌ COD PERICULOS - vulnerabil la SQL injection +$table->where('name = ' . $_GET['name']); +$table->where("name = '$_GET[name]'"); +``` + + +Interogări parametrizate +======================== + +Apărarea de bază împotriva SQL injection sunt interogările parametrizate. Nette Database oferă mai multe moduri de a le utiliza. + +Cel mai simplu mod este utilizarea **semnelor de întrebare placeholder**: + +```php +// ✅ Interogare parametrizată sigură +$database->query('SELECT * FROM users WHERE name = ?', $name); + +// ✅ Condiție sigură în Explorer +$table->where('name = ?', $name); +``` + +Acest lucru este valabil pentru toate celelalte metode din [Database Explorer |explorer], care permit inserarea de expresii cu semne de întrebare placeholder și parametri. + +Pentru comenzile INSERT, UPDATE sau clauza WHERE, putem transmite valorile într-un array: + +```php +// ✅ INSERT sigur +$database->query('INSERT INTO users', [ + 'name' => $name, + 'email' => $email, +]); + +// ✅ INSERT sigur în Explorer +$table->insert([ + 'name' => $name, + 'email' => $email, +]); +``` + + +Validarea valorilor parametrilor +================================ + +Interogările parametrizate sunt piatra de temelie a lucrului sigur cu baza de date. Cu toate acestea, valorile pe care le introducem în ele trebuie să treacă prin mai multe niveluri de control: + + +Controlul tipului +----------------- + +**Cel mai important este să se asigure tipul corect de date al parametrilor** - aceasta este o condiție necesară pentru utilizarea sigură a Nette Database. Baza de date presupune că toate datele de intrare au tipul de date corect corespunzător coloanei respective. + +De exemplu, dacă `$name` din exemplele anterioare ar fi în mod neașteptat un array în loc de un șir, Nette Database ar încerca să insereze toate elementele sale în interogarea SQL, ceea ce ar duce la o eroare. Prin urmare, **nu utilizați niciodată** date nevalidate din `$_GET`, `$_POST` sau `$_COOKIE` direct în interogările bazei de date. + + +Controlul formatului +-------------------- + +La al doilea nivel, verificăm formatul datelor - de exemplu, dacă șirurile sunt în codificare UTF-8 și lungimea lor corespunde definiției coloanei, sau dacă valorile numerice se încadrează în intervalul permis pentru tipul de date al coloanei respective. + +La acest nivel de validare, ne putem baza parțial și pe baza de date însăși - multe baze de date vor refuza datele nevalide. Cu toate acestea, comportamentul poate varia, unele pot scurta în tăcere șirurile lungi sau pot trunchia numerele în afara intervalului. + + +Controlul domeniului +-------------------- + +Al treilea nivel constă în controale logice specifice aplicației dvs. De exemplu, verificarea faptului că valorile din casetele de selecție corespund opțiunilor oferite, că numerele se încadrează în intervalul așteptat (de exemplu, vârsta 0-150 de ani) sau că dependențele reciproce dintre valori au sens. + + +Metode de validare recomandate +------------------------------ + +- Utilizați [Formulare Nette |forms:], care asigură automat validarea corectă a tuturor intrărilor +- Utilizați [Presentere |application:presenters] și specificați tipurile de date pentru parametrii din metodele `action*()` și `render*()` +- Sau implementați propriul strat de validare folosind instrumente PHP standard precum `filter_var()` + + +Lucrul sigur cu coloanele +========================= + +În secțiunea anterioară, am arătat cum să validăm corect valorile parametrilor. Cu toate acestea, atunci când folosim array-uri în interogările SQL, trebuie să acordăm aceeași atenție și cheilor lor. + +```php +// ❌ COD PERICULOS - cheile din array nu sunt tratate +$database->query('INSERT INTO users', $_POST); +``` + +Pentru comenzile INSERT și UPDATE, aceasta este o eroare de securitate fundamentală - atacatorul poate introduce sau modifica orice coloană în baza de date. Ar putea, de exemplu, să seteze `is_admin = 1` sau să introducă date arbitrare în coloane sensibile (așa-numita Mass Assignment Vulnerability). + +În condițiile WHERE, este și mai periculos, deoarece pot conține operatori: + +```php +// ❌ COD PERICULOS - cheile din array nu sunt tratate +$_POST['salary >'] = 100000; +$database->query('SELECT * FROM users WHERE', $_POST); +// execută interogarea WHERE (`salary` > 100000) +``` + +Atacatorul poate folosi această abordare pentru a descoperi sistematic salariile angajaților. De exemplu, începe cu o interogare pentru salarii peste 100.000, apoi sub 50.000 și, prin restrângerea treptată a intervalului, poate descoperi salariile aproximative ale tuturor angajaților. Acest tip de atac se numește SQL enumeration. + +Metodele `where()` și `whereOr()` sunt și [mult mai flexibile |explorer#where] și suportă expresii SQL în chei și valori, inclusiv operatori și funcții. Acest lucru îi oferă atacatorului posibilitatea de a efectua SQL injection: + +```php +// ❌ COD PERICULOS - atacatorul poate introduce propriul SQL +$_POST = ['0) UNION SELECT name, salary FROM users WHERE (1']; +$table->where($_POST); +// execută interogarea WHERE (0) UNION SELECT name, salary FROM users WHERE (1) +``` + +Acest atac încheie condiția originală folosind `0)`, adaugă propriul `SELECT` folosind `UNION` pentru a obține date sensibile din tabelul `users` și închide interogarea sintactic corectă folosind `WHERE (1)`. + + +Lista albă a coloanelor +----------------------- + +Pentru a lucra în siguranță cu numele coloanelor, avem nevoie de un mecanism care să asigure că utilizatorul poate lucra doar cu coloanele permise și nu poate adăuga propriile coloane. Am putea încerca să detectăm și să blocăm numele de coloane periculoase (lista neagră), dar această abordare nu este fiabilă - atacatorul poate găsi întotdeauna o nouă modalitate de a scrie un nume de coloană periculos pe care nu l-am prevăzut. + +Prin urmare, este mult mai sigur să inversăm logica și să definim o listă explicită de coloane permise (lista albă): + +```php +// Coloane pe care utilizatorul le poate modifica +$allowedColumns = ['name', 'email', 'active']; + +// Eliminăm toate coloanele nepermise din intrare +$filteredData = array_intersect_key($userData, array_flip($allowedColumns)); + +// ✅ Acum putem folosi în siguranță în interogări, cum ar fi: +$database->query('INSERT INTO users', $filteredData); +$table->update($filteredData); +$table->where($filteredData); +``` + + +Identificatori dinamici +======================= + +Pentru numele dinamice de tabele și coloane, utilizați substituentul `?name`. Acesta asigură escaparea corectă a identificatorilor conform sintaxei bazei de date respective (de exemplu, folosind ghilimele inverse în MySQL): + +```php +// ✅ Utilizare sigură a identificatorilor de încredere +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name', $column, $table); +// Rezultat în MySQL: SELECT `name` FROM `users` +``` + +Important: utilizați simbolul `?name` numai pentru valori de încredere definite în codul aplicației. Pentru valorile de la utilizator, utilizați din nou [lista albă |#Lista albă a coloanelor]. Altfel, vă expuneți riscurilor de securitate: + +```php +// ❌ PERICULOS - nu utilizați niciodată intrarea de la utilizator +$database->query('SELECT ?name FROM users', $_GET['column']); +``` diff --git a/database/ro/sql-way.texy b/database/ro/sql-way.texy new file mode 100644 index 0000000000..3276671a8d --- /dev/null +++ b/database/ro/sql-way.texy @@ -0,0 +1,513 @@ +Abordarea SQL +************* + +.[perex] +Nette Database oferă două abordări: puteți scrie interogări SQL singur (abordarea SQL) sau le puteți lăsa generate automat (vezi [Explorer |explorer]). Abordarea SQL vă oferă control complet asupra interogărilor și, în același timp, asigură construirea lor în siguranță. + +.[note] +Detalii despre conectarea și configurarea bazei de date găsiți în capitolul [Conectare și configurare |guide#Conectare și configurare]. + + +Interogare de bază +================== + +Pentru interogarea bazei de date se folosește metoda `query()`. Aceasta returnează un obiect [ResultSet |api:Nette\Database\ResultSet], care reprezintă rezultatul interogării. În caz de eșec, metoda [aruncă o excepție |exceptions]. Putem parcurge rezultatul interogării folosind bucla `foreach` sau putem folosi una dintre [funcțiile auxiliare |#Obținerea datelor]. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; +} +``` + +Pentru inserarea sigură a valorilor în interogările SQL, folosim interogări parametrizate. Nette Database le face extrem de simple - trebuie doar să adăugați o virgulă și valoarea după interogarea SQL: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Pentru mai mulți parametri, aveți două opțiuni de scriere. Fie puteți "intercala" interogarea SQL cu parametri: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age); +``` + +Fie scrieți mai întâi întreaga interogare SQL și apoi adăugați toți parametrii: + +```php +$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age); +``` + + +Protecție împotriva SQL injection +================================= + +De ce este important să folosim interogări parametrizate? Deoarece vă protejează împotriva atacului numit SQL injection, în care un atacator ar putea introduce propriile comenzi SQL și astfel să obțină sau să deterioreze datele din baza de date. + +.[warning] +**Nu introduceți niciodată variabile direct în interogarea SQL!** Folosiți întotdeauna interogări parametrizate, care vă protejează împotriva SQL injection. + +```php +// ❌ COD PERICULOS - vulnerabil la SQL injection +$database->query("SELECT * FROM users WHERE name = '$name'"); + +// ✅ Interogare parametrizată sigură +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Familiarizați-vă cu [posibilele riscuri de securitate |security]. + + +Tehnici de interogare +===================== + + +Condiții WHERE +-------------- + +Condițiile WHERE pot fi scrise ca un array asociativ, unde cheile sunt numele coloanelor și valorile sunt datele pentru comparație. Nette Database selectează automat operatorul SQL cel mai potrivit în funcție de tipul valorii. + +```php +$database->query('SELECT * FROM users WHERE', [ + 'name' => 'John', + 'active' => true, +]); +// WHERE `name` = 'John' AND `active` = 1 +``` + +În cheie, puteți specifica explicit și operatorul pentru comparație: + +```php +$database->query('SELECT * FROM users WHERE', [ + 'age >' => 25, // folosește operatorul > + 'name LIKE' => '%John%', // folosește operatorul LIKE + 'email NOT LIKE' => '%example.com%', // folosește operatorul NOT LIKE +]); +// WHERE `age` > 25 AND `name` LIKE '%John%' AND `email` NOT LIKE '%example.com%' +``` + +Nette tratează automat cazurile speciale precum valorile `null` sau array-urile. + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name' => 'Laptop', // folosește operatorul = + 'category_id' => [1, 2, 3], // folosește IN + 'description' => null, // folosește IS NULL +]); +// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL +``` + +Pentru condiții negative, utilizați operatorul `NOT`: + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name NOT' => 'Laptop', // folosește operatorul <> + 'category_id NOT' => [1, 2, 3], // folosește NOT IN + 'description NOT' => null, // folosește IS NOT NULL + 'id' => [], // se omite +]); +// WHERE `name` <> 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL +``` + +Pentru combinarea condițiilor se folosește operatorul `AND`. Acest lucru poate fi schimbat folosind [substituentul ?or |#Indicații pentru construirea SQL]. + + +Reguli ORDER BY +--------------- + +Sortarea `ORDER BY` poate fi scrisă folosind un array. În chei specificăm coloanele, iar valoarea va fi un boolean care determină dacă se sortează ascendent: + +```php +$database->query('SELECT id FROM author ORDER BY', [ + 'id' => true, // ascendent + 'name' => false, // descendent +]); +// SELECT id FROM author ORDER BY `id`, `name` DESC +``` + + +Inserarea datelor (INSERT) +-------------------------- + +Pentru inserarea înregistrărilor se folosește comanda SQL `INSERT`. + +```php +$values = [ + 'name' => 'John Doe', + 'email' => 'john@example.com', +]; +$database->query('INSERT INTO users ?', $values); +$userId = $database->getInsertId(); +``` + +Metoda `getInsertId()` returnează ID-ul ultimului rând inserat. Pentru unele baze de date (de ex. PostgreSQL), este necesar să specificați ca parametru numele secvenței din care trebuie generat ID-ul folosind `$database->getInsertId($sequenceId)`. + +Ca parametri putem transmite și [#valori speciale] precum fișiere, obiecte DateTime sau tipuri enum. + +Inserarea mai multor înregistrări simultan: + +```php +$database->query('INSERT INTO users ?', [ + ['name' => 'User 1', 'email' => 'user1@mail.com'], + ['name' => 'User 2', 'email' => 'user2@mail.com'], +]); +``` + +INSERT-ul multiplu este mult mai rapid, deoarece se execută o singură interogare la baza de date, în loc de multe interogări individuale. + +**Avertisment de securitate:** Nu utilizați niciodată date nevalidate ca `$values`. Familiarizați-vă cu [posibilele riscuri |security#Lucrul sigur cu coloanele]. + + +Actualizarea datelor (UPDATE) +----------------------------- + +Pentru actualizarea înregistrărilor se folosește comanda SQL `UPDATE`. + +```php +// Actualizarea unei singure înregistrări +$values = [ + 'name' => 'John Smith', +]; +$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1); +``` + +Numărul de rânduri afectate este returnat de `$result->getRowCount()`. + +Pentru UPDATE putem folosi operatorii `+=` și `-=`: + +```php +$database->query('UPDATE users SET ? WHERE id = ?', [ + 'login_count+=' => 1, // incrementarea login_count +], 1); +``` + +Exemplu de inserare sau modificare a unei înregistrări, dacă aceasta există deja. Folosim tehnica `ON DUPLICATE KEY UPDATE`: + +```php +$values = [ + 'name' => $name, + 'year' => $year, +]; +$database->query('INSERT INTO users ? ON DUPLICATE KEY UPDATE ?', + $values + ['id' => $id], + $values, +); +// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) +// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 +``` + +Observați că Nette Database recunoaște în ce context al comenzii SQL este inserat parametrul cu array-ul și, în funcție de aceasta, construiește codul SQL din el. Astfel, din primul array a construit `(id, name, year) VALUES (123, 'Jim', 1978)`, în timp ce al doilea l-a convertit în forma `name = 'Jim', year = 1978`. Detaliem acest aspect în secțiunea [#Indicații pentru construirea SQL]. + + +Ștergerea datelor (DELETE) +-------------------------- + +Pentru ștergerea înregistrărilor se folosește comanda SQL `DELETE`. Exemplu cu obținerea numărului de rânduri șterse: + +```php +$count = $database->query('DELETE FROM users WHERE id = ?', 1) + ->getRowCount(); +``` + + +Indicații pentru construirea SQL +-------------------------------- + +O indicație este un substituent special în interogarea SQL care specifică modul în care valoarea parametrului trebuie rescrisă într-o expresie SQL: + +| Indicație | Descriere | Se utilizează automat +|-----------|-------------------------------------------------|----------------------------- +| `?name` | se utilizează pentru inserarea numelui tabelului sau coloanei | - +| `?values` | generează `(cheie, ...) VALUES (valoare, ...)` | `INSERT ... ?`, `REPLACE ... ?` +| `?set` | generează atribuirea `cheie = valoare, ...` | `SET ?`, `KEY UPDATE ?` +| `?and` | combină condițiile din array cu operatorul `AND` | `WHERE ?`, `HAVING ?` +| `?or` | combină condițiile din array cu operatorul `OR` | - +| `?order` | generează clauza `ORDER BY` | `ORDER BY ?`, `GROUP BY ?` + +Pentru inserarea dinamică a numelor de tabele și coloane în interogare se folosește substituentul `?name`. Nette Database se ocupă de tratarea corectă a identificatorilor conform convențiilor bazei de date respective (de ex. încadrarea în ghilimele inverse în MySQL). + +```php +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table); +// SELECT `name` FROM `users` WHERE id = 1 (în MySQL) +``` + +**Avertisment:** utilizați simbolul `?name` numai pentru numele de tabele și coloane din intrări validate, altfel vă expuneți unui [risc de securitate |security#Identificatori dinamici]. + +Celelalte indicații de obicei nu trebuie specificate, deoarece Nette folosește o autodetecție inteligentă la construirea interogării SQL (vezi a treia coloană a tabelului). Dar le puteți utiliza, de exemplu, într-o situație în care doriți să combinați condițiile folosind `OR` în loc de `AND`: + +```php +$database->query('SELECT * FROM users WHERE ?or', [ + 'name' => 'John', + 'email' => 'john@example.com', +]); +// SELECT * FROM users WHERE `name` = 'John' OR `email` = 'john@example.com' +``` + + +Valori speciale +--------------- + +Pe lângă tipurile scalare obișnuite (string, int, bool), puteți transmite ca parametri și valori speciale: + +- fișiere: `fopen('image.gif', 'r')` inserează conținutul binar al fișierului +- data și ora: obiectele `DateTime` sunt convertite în formatul bazei de date +- tipuri enum: instanțele `enum` sunt convertite în valoarea lor +- literali SQL: creați folosind `Connection::literal('NOW()')` sunt inserați direct în interogare + +```php +$database->query('INSERT INTO articles ?', [ + 'title' => 'My Article', + 'published_at' => new DateTime, + 'content' => fopen('image.png', 'r'), + 'state' => Status::Draft, +]); +``` + +Pentru bazele de date care nu au suport nativ pentru tipul de date `datetime` (precum SQLite și Oracle), `DateTime` este convertit în valoarea specificată în [configurația bazei de date |configuration] prin elementul `formatDateTime` (valoarea implicită este `U` - timestamp unix). + + +Literali SQL +------------ + +În unele cazuri, trebuie să specificați direct cod SQL ca valoare, care însă nu trebuie interpretat ca șir și escapat. Pentru aceasta se folosesc obiectele clasei `Nette\Database\SqlLiteral`. Acestea sunt create de metoda `Connection::literal()`. + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + 'year >' => $database::literal('YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) +``` + +Sau alternativ: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) +``` + +Literalii SQL pot conține parametri: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > ? AND year < ?', $min, $max), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) +``` + +Datorită cărora putem crea combinații interesante: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('?or', [ + 'active' => true, + 'role' => $role, + ]), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') +``` + + +Obținerea datelor +================= + + +Scurtături pentru interogări SELECT +----------------------------------- + +Pentru a simplifica încărcarea datelor, `Connection` oferă câteva scurtături care combină apelul `query()` cu următorul `fetch*()`. Aceste metode acceptă aceiași parametri ca `query()`, adică interogarea SQL și parametrii opționali. O descriere completă a metodelor `fetch*()` găsiți [mai jos |#fetch]. + +| `fetch($sql, ...$params): ?Row` | Execută interogarea și returnează primul rând ca obiect `Row` +| `fetchAll($sql, ...$params): array` | Execută interogarea și returnează toate rândurile ca array de obiecte `Row` +| `fetchPairs($sql, ...$params): array` | Execută interogarea și returnează un array asociativ, unde prima coloană reprezintă cheia și a doua valoarea +| `fetchField($sql, ...$params): mixed` | Execută interogarea și returnează valoarea primului câmp din primul rând +| `fetchList($sql, ...$params): ?array` | Execută interogarea și returnează primul rând ca array indexat + +Exemplu: + +```php +// fetchField() - returnează valoarea primei celule +$count = $database->query('SELECT COUNT(*) FROM articles') + ->fetchField(); +``` + + +`foreach` - iterarea prin rânduri +--------------------------------- + +După executarea interogării, se returnează obiectul [ResultSet|api:Nette\Database\ResultSet], care permite parcurgerea rezultatelor în mai multe moduri. Cel mai simplu mod de a executa o interogare și de a obține rânduri este prin iterarea într-o buclă `foreach`. Această metodă este cea mai eficientă din punct de vedere al memoriei, deoarece returnează datele treptat și nu le stochează pe toate în memorie simultan. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; + // ... +} +``` + +.[note] +`ResultSet` poate fi iterat o singură dată. Dacă aveți nevoie să iterați în mod repetat, trebuie mai întâi să încărcați datele într-un array, de exemplu folosind metoda `fetchAll()`. + + +fetch(): ?Row .[method] +----------------------- + +Returnează un rând ca obiect `Row`. Dacă nu mai există alte rânduri, returnează `null`. Mută pointerul intern la următorul rând. + +```php +$result = $database->query('SELECT * FROM users'); +$row = $result->fetch(); // încarcă primul rând +if ($row) { + echo $row->name; +} +``` + + +fetchAll(): array .[method] +--------------------------- + +Returnează toate rândurile rămase din `ResultSet` ca un array de obiecte `Row`. + +```php +$result = $database->query('SELECT * FROM users'); +$rows = $result->fetchAll(); // încarcă toate rândurile +foreach ($rows as $row) { + echo $row->name; +} +``` + + +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Returnează rezultatele ca un array asociativ. Primul argument specifică numele coloanei care va fi folosită ca cheie în array, al doilea argument specifică numele coloanei care va fi folosită ca valoare: + +```php +$result = $database->query('SELECT id, name FROM users'); +$names = $result->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] +``` + +Dacă specificăm doar primul parametru, valoarea va fi întregul rând, adică obiectul `Row`: + +```php +$rows = $result->fetchPairs('id'); +// [1 => Row(id: 1, name: 'John'), 2 => Row(id: 2, name: 'Jane'), ...] +``` + +În cazul cheilor duplicate, se va folosi valoarea din ultimul rând. La utilizarea `null` ca cheie, array-ul va fi indexat numeric începând de la zero (atunci nu apar coliziuni): + +```php +$names = $result->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] +``` + + +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Alternativ, puteți specifica ca parametru un callback care va returna pentru fiecare rând fie valoarea însăși, fie o pereche cheie-valoare. + +```php +$result = $database->query('SELECT * FROM users'); +$items = $result->fetchPairs(fn($row) => "$row->id - $row->name"); +// ['1 - John', '2 - Jane', ...] + +// Callback-ul poate returna și un array cu perechea cheie & valoare: +$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]); +// ['John' => 46, 'Jane' => 21, ...] +``` + + +fetchField(): mixed .[method] +----------------------------- + +Returnează valoarea primului câmp din rândul curent. Dacă nu mai există alte rânduri, returnează `null`. Mută pointerul intern la următorul rând. + +```php +$result = $database->query('SELECT name FROM users'); +$name = $result->fetchField(); // încarcă numele din primul rând +``` + + +fetchList(): ?array .[method] +----------------------------- + +Returnează un rând ca array indexat. Dacă nu mai există alte rânduri, returnează `null`. Mută pointerul intern la următorul rând. + +```php +$result = $database->query('SELECT name, email FROM users'); +$row = $result->fetchList(); // ['John', 'john@example.com'] +``` + + +getRowCount(): ?int .[method] +----------------------------- + +Returnează numărul de rânduri afectate de ultima interogare `UPDATE` sau `DELETE`. Pentru `SELECT`, este numărul de rânduri returnate, dar acesta poate să nu fie cunoscut - în acest caz, metoda returnează `null`. + + +getColumnCount(): ?int .[method] +-------------------------------- + +Returnează numărul de coloane din `ResultSet`. + + +Informații despre interogări +============================ + +În scopuri de depanare, putem obține informații despre ultima interogare executată: + +```php +echo $database->getLastQueryString(); // afișează interogarea SQL + +$result = $database->query('SELECT * FROM articles'); +echo $result->getQueryString(); // afișează interogarea SQL +echo $result->getTime(); // afișează timpul de execuție în secunde +``` + +Pentru a afișa rezultatul ca tabel HTML, se poate folosi: + +```php +$result = $database->query('SELECT * FROM articles'); +$result->dump(); +``` + +ResultSet oferă informații despre tipurile coloanelor: + +```php +$result = $database->query('SELECT * FROM articles'); +$types = $result->getColumnTypes(); + +foreach ($types as $column => $type) { + echo "$column este de tip $type->type"; // de ex. 'id este de tip int' +} +``` + + +Logarea interogărilor +--------------------- + +Putem implementa propria logare a interogărilor. Evenimentul `onQuery` este un array de callback-uri care sunt apelate după fiecare interogare executată: + +```php +$database->onQuery[] = function ($database, $result) use ($logger) { + $logger->info('Query: ' . $result->getQueryString()); + $logger->info('Time: ' . $result->getTime()); + + if ($result->getRowCount() > 1000) { + $logger->warning('Large result set: ' . $result->getRowCount() . ' rows'); + } +}; +``` diff --git a/database/ro/transactions.texy b/database/ro/transactions.texy new file mode 100644 index 0000000000..86fa0b3bf8 --- /dev/null +++ b/database/ro/transactions.texy @@ -0,0 +1,43 @@ +Tranzacții +********** + +.[perex] +Tranzacțiile garantează că fie toate operațiunile din cadrul tranzacției sunt efectuate, fie niciuna nu este efectuată. Acestea sunt utile pentru a asigura consistența datelor în cazul operațiunilor mai complexe. + +Cel mai simplu mod de a utiliza tranzacțiile arată astfel: + +```php +$database->beginTransaction(); +try { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); + $database->commit(); +} catch (\Exception $e) { + $database->rollBack(); + throw $e; +} +``` + +Puteți scrie același lucru mult mai elegant folosind metoda `transaction()`. Aceasta acceptă un callback ca parametru, pe care îl execută în cadrul tranzacției. Dacă callback-ul se execută fără excepții, tranzacția este confirmată automat. Dacă apare o excepție, tranzacția este anulată (rollback), iar excepția este propagată mai departe. + +```php +$database->transaction(function ($database) use ($id) { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); +}); +``` + +Metoda `transaction()` poate returna și valori: + +```php +$count = $database->transaction(function ($database) { + $result = $database->query('UPDATE users SET active = ?', true); + return $result->getRowCount(); // returnează numărul de rânduri actualizate +}); +``` diff --git a/database/ru/@home.texy b/database/ru/@home.texy index 4606686643..0927782214 100644 --- a/database/ru/@home.texy +++ b/database/ru/@home.texy @@ -1,20 +1,21 @@ -Поддерживаемые серверы -====================== +Поддерживаемые базы данных +========================== -Поддерживаются следующие серверы баз данных: +Nette поддерживает следующие базы данных: -|* Сервер |* Имя DSN |* Поддержка Core |* Поддержка Explorer -| MySQL (>= 5.1) | mysql | ДА | ДА -| PostgreSQL (>= 9.0) | pgsql | ДА | ДА -| Sqlite 3 (>= 3.8) | sqlite | ДА | ДА -| Oracle | oci | ДА | - -| MS SQL (PDO_SQLSRV) | sqlsrv | ДА | ДА -| MS SQL (PDO_DBLIB) | mssql | ДА | - -| ODBC | odbc | ДА | - +|* Сервер базы данных |* Имя DSN |* Поддержка в Core |* Поддержка в Explorer +| MySQL (>= 5.1) | mysql | ДА | ДА +| PostgreSQL (>= 9.0) | pgsql | ДА | ДА +| Sqlite 3 (>= 3.8) | sqlite | ДА | ДА +| Oracle | oci | ДА | - +| MS SQL (PDO_SQLSRV) | sqlsrv | ДА | ДА +| MS SQL (PDO_DBLIB) | mssql | ДА | - +| ODBC | odbc | ДА | - -{{title: Nette Database}} -{{description: Класс Nette Database значительно упрощает получение данных из базы данных без написания SQL-запросов. Он использует эффективные запросы и не передает ненужные данные.}} + +{{maintitle: Nette Database - awesome database layer for PHP}} +{{description: Nette Database существенно упрощает получение данных из базы данных без необходимости писать SQL-запросы. Он выполняет эффективные запросы и не передает лишние данные.}} diff --git a/database/ru/@left-menu.texy b/database/ru/@left-menu.texy index ea31046b92..91864ddc35 100644 --- a/database/ru/@left-menu.texy +++ b/database/ru/@left-menu.texy @@ -1,5 +1,12 @@ -База данных -*********** -- [Core] -- [Explorer] -- [Настройка|configuration] +Nette Database +************** +- [Введение |guide] +- [SQL-подход |sql way] +- [Explorer |Explorer] +- [Транзакции |transactions] +- [Исключения |exceptions] +- [Рефлексия |reflection] +- [Маппинг |mapping] +- [Конфигурация |configuration] +- [Риски безопасности |security] +- [Обновление |en:upgrading] diff --git a/database/ru/@meta.texy b/database/ru/@meta.texy new file mode 100644 index 0000000000..7f329adfce --- /dev/null +++ b/database/ru/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документация Nette}} diff --git a/database/ru/configuration.texy b/database/ru/configuration.texy index ca4733e77b..58dd6654f1 100644 --- a/database/ru/configuration.texy +++ b/database/ru/configuration.texy @@ -1,16 +1,16 @@ -Настройка базы данных -********************* +Конфигурация базы данных +************************ .[perex] -Обзор вариантов конфигурации для базы данных Nette. +Обзор опций конфигурации для Nette Database. -Если вы используете не весь фреймворк, а только эту библиотеку, прочитайте [Как загрузить файл конфигурации|bootstrap:]. +Если вы не используете весь фреймворк, а только эту библиотеку, прочитайте, [как загрузить конфигурацию|bootstrap:]. -Одно подключение .[#toc-single-connection] ------------------------------------------- +Одно соединение +--------------- -Настройте одно подключение к базе данных: +Конфигурация одного соединения с базой данных: ```neon database: @@ -20,54 +20,60 @@ database: password: ... ``` -Он создает службы типа `Nette\Database\Connection`, а также `Nette\Database\Explorer` для уровня [Database Explorer|explorer]. Подключение к базе данных обычно передается автоподключением, если это невозможно, используйте имена сервисов `@database.default.connection` и `@database.default.context`. +Создает сервисы `Nette\Database\Connection` и `Nette\Database\Explorer`, которые обычно передаются с помощью [autowiring |dependency-injection:autowiring], либо по ссылке на [их имя |#Сервисы DI]. Другие настройки: ```neon database: - # отображает панель Database в Tracy Bar - debugger: ... # (bool) по умолчанию true + # отображать панель базы данных в Tracy Bar? + debugger: ... # (bool) по умолчанию true - # отображает запрос EXPLAIN в Tracy Bar - explain: ... # (bool) по умолчанию true + # отображать EXPLAIN запросов в Tracy Bar? + explain: ... # (bool) по умолчанию true - # включить автосвязывание для этого соединения - autowired: ... # (bool) по умолчанию true для первого соединения + # разрешить autowiring для этого соединения? + autowired: ... # (bool) по умолчанию true у первого соединения - # условные обозначения таблицы: discovered, static, или имя класса + # конвенции таблиц: discovered, static или имя класса conventions: discovered # (string) по умолчанию 'discovered' options: - # подключаться к базе данных только при необходимости? + # подключаться к базе данных только когда это необходимо? lazy: ... # (bool) по умолчанию false - # Класс драйвера базы данных PHP + # PHP класс драйвера базы данных driverClass: # (string) - # только для MySQL: устанавливает sql_mode + # только MySQL: устанавливает sql_mode sqlmode: # (string) - # только для MySQL: устанавливает SET NAMES - charset: # (string) по умолчанию 'utf8mb4' ('utf8' до v5.5.3) + # только MySQL: устанавливает SET NAMES + charset: # (string) по умолчанию 'utf8mb4' - # только для Oracle и SQLite: формат даті + # только MySQL: преобразует TINYINT(1) в bool + convertBoolean: # (bool) по умолчанию false + + # возвращает столбцы с датой как immutable объекты (с версии 3.2.1) + newDateTime: # (bool) по умолчанию false + + # только Oracle и SQLite: формат для сохранения даты formatDateTime: # (string) по умолчанию 'U' ``` -Ключ `options` может содержать другие опции, которые можно найти в [документации по драйверу PDO |https://www.php.net/manual/en/pdo.drivers.php], например: +В ключе `options` можно указывать другие опции, которые вы найдете в [документации драйверов PDO |https://www.php.net/manual/en/pdo.drivers.php], например: ```neon -база данных: +database: options: PDO::MYSQL_ATTR_COMPRESS: true ``` -Множественные подключения .[#toc-multiple-connections] ------------------------------------------------------- +Несколько соединений +-------------------- -В конфигурации мы можем определить больше соединений с базой данных, разделив их на именованные секции: +В конфигурации мы можем определить и несколько соединений с базой данных, разделив их на именованные секции: ```neon database: @@ -80,9 +86,23 @@ database: dsn: 'sqlite::memory:' ``` -Каждое определенное соединение создает сервисы, включающие имя секции в свое имя, т. е. `@database.main.connection` & `@database.main.context` и далее `@database.another.connection` & `@database.another.context`. +Autowiring включен только для сервисов из первой секции. Это можно изменить с помощью `autowired: false` или `autowired: true`. + + +Сервисы DI +---------- + +Эти сервисы добавляются в DI-контейнер, где `###` представляет имя соединения: + +| Название | Тип | Описание +|---------------------------------------------------------- +| `database.###.connection` | [api:Nette\Database\Connection] | соединение с базой данных +| `database.###.explorer` | [api:Nette\Database\Explorer] | [Database Explorer |explorer] + + +Если мы определяем только одно соединение, названия сервисов будут `database.default.connection` и `database.default.explorer`. Если мы определяем несколько соединений, как в примере выше, названия будут соответствовать секциям, т.е. `database.main.connection`, `database.main.explorer` и далее `database.another.connection` и `database.another.explorer`. -Автосвязывание включено только для сервисов из первой секции. Это можно изменить с помощью `autowired: false` или `autowired: true`. Неавтоматизированные сервисы передаются по имени: +Неавтовайренные сервисы передаем явно по ссылке на их имя: ```neon services: diff --git a/database/ru/core.texy b/database/ru/core.texy deleted file mode 100644 index 50d46cab53..0000000000 --- a/database/ru/core.texy +++ /dev/null @@ -1,350 +0,0 @@ -Database Core -************* - -.[perex] -Nette Database Core является уровнем абстракции базы данных и обеспечивает основную функциональность. - - -Установка .[#toc-installation] -============================== - -Загрузите и установите пакет с помощью [Composer|best-practices:composer]: - -```shell -composer require nette/database -``` - - -Подключение и настройка .[#toc-connection-and-configuration] -============================================================ - -Чтобы подключиться к базе данных, просто создайте экземпляр класса [api:Nette\Database\Connection]: - -```php -$database = new Nette\Database\Connection($dsn, $user, $password); -``` - -Параметр `$dsn` (имя источника данных) — [такой же, как используется в PDO |https://www.php.net/manual/ru/pdo.construct.php#refsect1-pdo.construct-parameters], например `host=127.0.0.1;dbname=test`. В случае неудачи выбрасывается исключение `Nette\Database\ConnectionException`. - -Однако, более сложный способ предлагает [конфигурация приложения |configuration]. Мы добавим раздел `database`, и он создаст необходимые объекты и панель `Database` в панели отладки [Tracy |tracy:]. - -```neon -database: - dsn: 'mysql:host=127.0.0.1;dbname=test' - user: root - password: password -``` - -Объект соединения, который мы [получаем как сервис от DI-контейнера |dependency-injection:passing-dependencies], например: - -```php -class Model -{ - // передаем Nette\Database\Explorer для работы с уровнем Database Explorer - public function __construct( - private Nette\Database\Connection $database, - ) { - } -} -``` - -Для получения дополнительной информации смотрите [конфигурацию базы данных|configuration]. - - -Запросы .[#toc-queries] -======================= - -Для запроса к базе данных используйте метод `query()`, который возвращает [ResultSet |api:Nette\Database\ResultSet]. - -```php -$result = $database->query('SELECT * FROM users'); - -foreach ($result as $row) { - echo $row->id; - echo $row->name; -} - -echo $result->getRowCount(); // возвращает количество строк, если оно известно -``` - -.[note] -Над `ResultSet` можно выполнить итерацию только один раз, если нам нужно выполнить итерацию несколько раз, необходимо преобразовать результат в массив с помощью метода `fetchAll()`. - -Вы можете легко добавить параметры в запрос, обратите внимание на знак вопроса: - -```php -$database->query('SELECT * FROM users WHERE name = ?', $name); - -$database->query('SELECT * FROM users WHERE name = ? AND active = ?', $name, $active); - -$database->query('SELECT * FROM users WHERE id IN (?)', $ids); // $ids - массив -``` - -<div class=warning> -ВНИМАНИЕ, никогда не объединяйте строки во избежание [уязвимости через SQL-инъекции |https://ru.wikipedia.org/wiki/%D0%92%D0%BD%D0%B5%D0%B4%D1%80%D0%B5%D0%BD%D0%B8%D0%B5_SQL-%D0%BA%D0%BE%D0%B4%D0%B0]! -/-- -$db->query('SELECT * FROM users WHERE name = ' . $name); // НЕПРАВИЛЬНО!!! -\-- -</div> - -В случае неудачи `query()` выбрасывает либо исключение `Nette\Database\DriverException`, либо одно из его дочерних исключений: - -- [ConstraintViolationException |api:Nette\Database\ConstraintViolationException] - нарушение любого из условий -- [ForeignKeyConstraintViolationException |api:Nette\Database\ForeignKeyConstraintViolationException] - недопустимый внешний ключ -- [NotNullConstraintViolationException |api:Nette\Database\NotNullConstraintViolationException] - нарушение условия NOT NULL -- [UniqueConstraintViolationException |api:Nette\Database\UniqueConstraintViolationException] - конфликт уникального индекса - -Помимо `query()`, существуют и другие полезные методы: - -```php -// возвращает ассоциативный массив id => name -$pairs = $database->fetchPairs('SELECT id, name FROM users'); - -// возвращает все строки в виде массива -$rows = $database->fetchAll('SELECT * FROM users'); - -// возвращает одну строку -$row = $database->fetch('SELECT * FROM users WHERE id = ?', $id); - -// возвращает одно поле -$name = $database->fetchField('SELECT name FROM users WHERE id = ?', $id); -``` - -В случае неудачи все эти методы выбрасывают исключение `Nette\Database\DriverException`. - - -Вставка, обновление и удаление .[#toc-insert-update-delete] -=========================================================== - -Параметр, который мы вставляем в SQL-запрос, также может быть массивом (в этом случае можно пропустить подстановочный знак `?`), что может быть полезно для оператора `INSERT`: - -```php -$database->query('INSERT INTO users ?', [ // здесь может быть опущен знак вопроса - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) - -$id = $database->getInsertId(); // возвращает автоинкремент вставленной строки - -$id = $database->getInsertId($sequence); // или значение последовательности -``` - -Вставка нескольких значений: - -```php -$database->query('INSERT INTO users', [ - [ - 'name' => 'Jim', - 'year' => 1978, - ], [ - 'name' => 'Jack', - 'year' => 1987, - ], -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) -``` - -Мы также можем передавать файлы, объекты DateTime или [перечисления |https://www.php.net/enumerations]: - -```php -$database->query('INSERT INTO users', [ - 'name' => $name, - 'created' => new DateTime, // или $database::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // вставляет содержимое файла - 'status' => State::New, // enum State -]); -``` - -Обновление строк: - -```php -$result = $database->query('UPDATE users SET', [ - 'name' => $name, - 'year' => $year, -], 'WHERE id = ?', $id); -// UPDATE users SET `name` = 'Jim', `year` = 1978 WHERE id = 123 - -echo $result->getRowCount(); // возвращает количество затронутых строк -``` - -Для UPDATE мы можем использовать операторы `+=` и `-=`: - -```php -$database->query('UPDATE users SET', [ - 'age+=' => 1, // note += -], 'WHERE id = ?', $id); -// UPDATE users SET `age` = `age` + 1 -``` - -Удаление: - -```php -$result = $database->query('DELETE FROM users WHERE id = ?', $id); -echo $result->getRowCount(); // возвращает количество затронутых строк -``` - - -Продвинутые запросы .[#toc-advanced-queries] -============================================ - -Вставка или обновление, если запись уже существует: - -```php -$database->query('INSERT INTO users', [ - 'id' => $id, - 'name' => $name, - 'year' => $year, -], 'ON DUPLICATE KEY UPDATE', [ - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) -// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 -``` - -Обратите внимание, что Nette Database распознает SQL-контекст, в который вставлен параметр массива, и строит SQL-код соответствующим образом. Так, из первого массива он генерирует `(id, name, year) VALUES (123, 'Jim', 1978)`, а второй преобразует в `name = 'Jim', year = 1978`. - -Мы также можем описать сортировку с помощью массива, в котором ключами являются имена столбцов, а значениями — значения типа boolean, определяющие, следует ли сортировать в порядке возрастания: - -```php -$database->query('SELECT id FROM author ORDER BY', [ - 'id' => true, // ascending - 'name' => false, // descending -]); -// SELECT id FROM author ORDER BY `id`, `name` DESC -``` - -Если обнаружение не сработало, вы можете указать форму сборки с помощью подстановочного знака `?`, за которым следует подсказка. Поддерживаются следующие подсказки: - -| ?values | (key1, key2, ...) VALUES (value1, value2, ...) -| ?set | key1 = value1, key2 = value2, ... -| ?and | key1 = value1 AND key2 = value2 ... -| ?or | key1 = value1 OR key2 = value2 ... -| ?order | key1 ASC, key2 DESC - -В предложении WHERE используется оператор `?and`, поэтому условия связаны `AND`: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND `year` = 1978 -``` - -Который можно легко изменить на `OR`, используя подстановочный знак `?or`: - -```php -$result = $database->query('SELECT * FROM users WHERE ?or', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' OR `year` = 1978 -``` - -Мы можем использовать операторы в условиях: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name <>' => $name, - 'year >' => $year, -]); -// SELECT * FROM users WHERE `name` <> 'Jim' AND `year` > 1978 -``` - -А также перечисления: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => ['Jim', 'Jack'], - 'role NOT IN' => ['admin', 'owner'], // перечисление + оператор NOT IN -]); -// SELECT * FROM users WHERE -// `name` IN ('Jim', 'Jack') AND `role` NOT IN ('admin', 'owner') -``` - -Мы также можем включить часть пользовательского SQL-кода, используя так называемый SQL-литерал: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year >' => $database::literal('YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) -``` - -В качестве альтернативы: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) -``` - -SQL-литерал также может иметь свои параметры: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > ? AND year < ?', $min, $max), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) -``` - -Благодаря этому мы можем создавать интересные комбинации: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('?or', [ - 'active' => true, - 'role' => $role, - ]), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') -``` - - -Имя переменной .[#toc-variable-name] -==================================== - -Существует подстановочный знак `?name', который используется, если имя таблицы или столбца является переменной. (Осторожно, не позволяйте пользователю манипулировать содержимым такой переменной): - -```php -$table = 'blog.users'; -$column = 'name'; -$database->query('SELECT * FROM ?name WHERE ?name = ?', $table, $column, $name); -// SELECT * FROM `blog`.`users` WHERE `name` = 'Jim' -``` - - -Транзакции .[#toc-transactions] -=============================== - -Существует три метода работы с транзакциями: - -```php -$database->beginTransaction(); - -$database->commit(); - -$database->rollback(); -``` - -Элегантный способ предлагает метод `transaction()`. Вы передаете обратный вызов, который выполняется в транзакции. Если во время выполнения возникает исключение, транзакция сбрасывается, если всё идет хорошо, транзакция фиксируется. - -```php -$id = $database->transaction(function ($database) { - $database->query('DELETE FROM ...'); - $database->query('INSERT INTO ...'); - // ... - return $database->getInsertId(); -}); -``` - -Как видите, метод `transaction()` возвращает возвращаемое значение обратного вызова. - -Транзакция() также может быть вложенной, что упрощает реализацию независимых хранилищ. diff --git a/database/ru/exceptions.texy b/database/ru/exceptions.texy new file mode 100644 index 0000000000..58fdc27104 --- /dev/null +++ b/database/ru/exceptions.texy @@ -0,0 +1,34 @@ +Исключения +********** + +Nette Database использует иерархию исключений. Базовым классом является `Nette\Database\DriverException`, который наследует от `PDOException` и предоставляет расширенные возможности для работы с ошибками базы данных: + +- Метод `getDriverCode()` возвращает код ошибки от драйвера базы данных +- Метод `getSqlState()` возвращает код SQLSTATE +- Методы `getQueryString()` и `getParameters()` позволяют получить исходный запрос и его параметры + +От `DriverException` наследуют следующие специализированные исключения: + +- `ConnectionException` - сигнализирует о сбое подключения к серверу базы данных +- `ConstraintViolationException` - базовый класс для нарушений ограничений базы данных, от которого наследуют: + - `ForeignKeyConstraintViolationException` - нарушение внешнего ключа + - `NotNullConstraintViolationException` - нарушение ограничения NOT NULL + - `UniqueConstraintViolationException` - нарушение уникальности значения + + +Пример перехвата исключения `UniqueConstraintViolationException`, которое возникает, когда мы пытаемся вставить пользователя с email, который уже существует в базе данных (при условии, что столбец email имеет уникальный индекс). + +```php +try { + $database->query('INSERT INTO users', [ + 'email' => 'john@example.com', + 'name' => 'John Doe', + 'password' => $hashedPassword, + ]); +} catch (Nette\Database\UniqueConstraintViolationException $e) { + echo 'Пользователь с этим email уже существует.'; + +} catch (Nette\Database\DriverException $e) { + echo 'Произошла ошибка при регистрации: ' . $e->getMessage(); +} +``` diff --git a/database/ru/explorer.texy b/database/ru/explorer.texy index a3f91ad8f7..1f82982676 100644 --- a/database/ru/explorer.texy +++ b/database/ru/explorer.texy @@ -3,548 +3,910 @@ Database Explorer <div class=perex> -Nette Database Explorer значительно упрощает получение данных из базы данных без написания SQL-запросов. +Explorer предлагает интуитивно понятный и эффективный способ работы с базой данных. Он автоматически заботится о связях между таблицами и оптимизации запросов, так что вы можете сосредоточиться на своем приложении. Работает сразу без настройки. Если вам нужен полный контроль над SQL-запросами, вы можете использовать [SQL-подход |SQL way]. -- использует эффективные запросы -- данные не передаются без необходимости -- отличается элегантным синтаксисом +- Работа с данными естественна и легко понятна +- Генерирует оптимизированные SQL-запросы, которые загружают только необходимые данные +- Обеспечивает легкий доступ к связанным данным без необходимости писать JOIN-запросы +- Работает мгновенно без какой-либо конфигурации или генерации сущностей </div> -Чтобы использовать Database Explorer, начните с таблицы — вызовите `table()` на объекте [api:Nette\Database\Explorer]. Проще всего получить экземпляр контекстного объекта [описано здесь |core#Connection-and-Configuration], или, в случае, когда Nette Database Explorer используется как отдельный инструмент, его можно [создать вручную|#Creating-Explorer-Manually]. + +С Explorer вы начинаете с вызова метода `table()` объекта [api:Nette\Database\Explorer] (подробности о подключении см. в главе [Подключение и конфигурация |guide#Подключение и конфигурация]): ```php -$books = $explorer->table('book'); // имя таблицы в бд — 'book' +$books = $explorer->table('book'); // 'book' - имя таблицы ``` -Вызов возвращает экземпляр объекта [Selection |api:Nette\Database\Table\Selection], который можно итерировать для получения всех книг. Каждый элемент (строка) представлен экземпляром [ActiveRow |api:Nette\Database\Table\ActiveRow] с данными, отображенными в его свойствах: +Метод возвращает объект [Selection |api:Nette\Database\Table\Selection], который представляет SQL-запрос. К этому объекту можно добавлять другие методы для фильтрации и сортировки результатов. Запрос составляется и выполняется только в тот момент, когда мы начинаем запрашивать данные. Например, при прохождении циклом `foreach`. Каждая строка представлена объектом [ActiveRow |api:Nette\Database\Table\ActiveRow]: ```php foreach ($books as $book) { - echo $book->title; - echo $book->author_id; + echo $book->title; // вывод столбца 'title' + echo $book->author_id; // вывод столбца 'author_id' } ``` -Получение только одного конкретного ряда осуществляется методом `get()`, который непосредственно возвращает экземпляр ActiveRow. +Explorer существенно упрощает работу со [связями между таблицами |#Связи между таблицами]. Следующий пример показывает, как легко можно вывести данные из связанных таблиц (книги и их авторы). Обратите внимание, что нам не нужно писать никаких JOIN-запросов, Nette создаст их за нас: ```php -$book = $explorer->table('book')->get(2); // возвращает книгу с идентификатором 2 -echo $book->title; -echo $book->author_id; +$books = $explorer->table('book'); + +foreach ($books as $book) { + echo 'Книга: ' . $book->title; + echo 'Автор: ' . $book->author->name; // создаст JOIN к таблице 'author' +} ``` -Давайте рассмотрим распространенный случай использования. Вам нужно получить книги и их авторов. Это обычное отношение 1:N. Часто используемым решением является получение данных с помощью одного SQL-запроса с объединением таблиц. Вторая возможность — получить данные отдельно, выполнить один запрос для получения книг, а затем получить автора для каждой книги другим запросом (например, в цикле foreach). Это можно легко оптимизировать для выполнения только двух запросов, один для книг, а другой для нужных авторов — и именно так это делает Nette Database Explorer. +Nette Database Explorer оптимизирует запросы, чтобы они были максимально эффективными. Вышеуказанный пример выполнит только два SELECT-запроса, независимо от того, обрабатываем ли мы 10 или 10 000 книг. -В приведенных ниже примерах мы будем работать со схемой базы данных, показанной на рисунке. Имеются связи OneHasMany (1:N) (автор книги `author_id` и возможный переводчик `translator_id`, который может быть `null`) и связи ManyHasMany (M:N) между книгой и её тегами. +Кроме того, Explorer отслеживает, какие столбцы используются в коде, и загружает из базы данных только их, тем самым экономя дополнительную производительность. Это поведение полностью автоматическое и адаптивное. Если вы позже измените код и начнете использовать другие столбцы, Explorer автоматически изменит запросы. Вам не нужно ничего настраивать или думать о том, какие столбцы вам понадобятся - оставьте это Nette. -[Пример, включая схему, можно найти на GitHub |https://github.com/nette-examples/books]. -[* db-schema-1-.webp *] *** Структура базы данных, используемая в примерах .<> +Фильтрация и сортировка +======================= -Следующий код перечисляет имя автора для каждой книги и все её теги. Мы [обсудим ниже |#Working-with-Relationships], как это работает внутри. +Класс `Selection` предоставляет методы для фильтрации и сортировки выборки данных. -```php -$books = $explorer->table('book'); +.[language-php] +| `where($condition, ...$params)` | Добавляет условие WHERE. Несколько условий соединяются оператором AND +| `whereOr(array $conditions)` | Добавляет группу условий WHERE, соединенных оператором OR +| `wherePrimary($value)` | Добавляет условие WHERE по первичному ключу +| `order($columns, ...$params)` | Устанавливает сортировку ORDER BY +| `select($columns, ...$params)` | Указывает столбцы, которые должны быть загружены +| `limit($limit, $offset = null)` | Ограничивает количество строк (LIMIT) и опционально устанавливает OFFSET +| `page($page, $itemsPerPage, &$total = null)` | Устанавливает пагинацию +| `group($columns, ...$params)` | Группирует строки (GROUP BY) +| `having($condition, ...$params)` | Добавляет условие HAVING для фильтрации сгруппированных строк -foreach ($books as $book) { - echo 'title: ' . $book->title; - echo 'written by: ' . $book->author->name; // $book->author — строка из таблицы 'author' +Методы можно вызывать цепочкой (так называемый [fluent interface |nette:introduction-to-object-oriented-programming#Текучие интерфейсы Fluent Interfaces]): `$table->where(...)->order(...)->limit(...)`. - echo 'tags: '; - foreach ($book->related('book_tag') as $bookTag) { - echo $bookTag->tag->name . ', '; // $bookTag->tag — строка из таблицы 'tag' - } -} -``` +В этих методах вы также можете использовать специальную нотацию для доступа к [данным из связанных таблиц |#Запросы через связанные таблицы]. -Вы будете довольны тем, насколько эффективно работает слой базы данных. Приведенный выше пример делает постоянное количество запросов, которые выглядят следующим образом: -```sql -SELECT * FROM `book` -SELECT * FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT * FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) -``` +Экранирование и идентификаторы +------------------------------ -Если вы используете [кэш |caching:] (по умолчанию включено), никакие столбцы не будут запрашиваться без необходимости. После первого запроса в кэше будут сохранены имена использованных столбцов, и Nette Database Explorer будет выполнять запросы только с нужными столбцами: +Методы автоматически экранируют параметры и заключают идентификаторы (имена таблиц и столбцов) в кавычки, тем самым предотвращая SQL-инъекции. Для правильной работы необходимо соблюдать несколько правил: -```sql -SELECT `id`, `title`, `author_id` FROM `book` -SELECT `id`, `name` FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT `book_id`, `tag_id` FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT `id`, `name` FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) +- Ключевые слова, имена функций, процедур и т.д. пишите **заглавными буквами**. +- Имена столбцов и таблиц пишите **строчными буквами**. +- Строки всегда передавайте через **параметры**. + +```php +where('name = ' . $name); // КРИТИЧЕСКАЯ УЯЗВИМОСТЬ: SQL-инъекция +where('name LIKE "%search%"'); // НЕПРАВИЛЬНО: усложняет автоматическое заключение в кавычки +where('name LIKE ?', '%search%'); // ПРАВИЛЬНО: значение передано через параметр + +where('name like ?', $name); // НЕПРАВИЛЬНО: сгенерирует: `name` `like` ? +where('name LIKE ?', $name); // ПРАВИЛЬНО: сгенерирует: `name` LIKE ? +where('LOWER(name) = ?', $value);// ПРАВИЛЬНО: LOWER(`name`) = ? ``` -Выборки .[#toc-selections] -========================== +where(string|array $condition, ...$parameters): static .[method] +---------------------------------------------------------------- -Смотрите возможности фильтрации и ограничения строк [api:Nette\Database\Table\Selection]: +Фильтрует результаты с помощью условий WHERE. Ее сильной стороной является интеллектуальная работа с различными типами значений и автоматический выбор SQL-операторов. -.[language-php] -| `$table->where($where[, $param[, ...]])` | Устанавливаем WHERE, используя AND как клей, если заданы два или более условий -| `$table->whereOr($where)` | Устанавливаем WHERE, используя OR в качестве связки, если заданы два или более условий -| `$table->order($columns)` | Устанавливаем ORDER BY, например, с помощью выражения `('column DESC, id DESC')`. -| `$table->select($columns)` | Устанавливаем извлеченные столбцы, например, с помощью выражения `('col, MD5(col) AS hash')`. -| `$table->limit($limit[, $offset])` | Устанавливаем LIMIT и OFFSET -| `$table->page($page, $itemsPerPage[, &$lastPage])` | Включаем пагинацию -| `$table->group($columns)` | Устанавливаем GROUP BY -| `$table->having($having)` | Устанавливаем HAVING +Основное использование: -Можно использовать текучий интерфейс (Fluent), например `$table->where(...)->order(...)->limit(...)`. Несколько условий `where` или `whereOr` соединяются с помощью оператора `AND`. +```php +$table->where('id', $value); // WHERE `id` = 123 +$table->where('id > ?', $value); // WHERE `id` > 123 +$table->where('id = ? OR name = ?', $id, $name); // WHERE `id` = 1 OR `name` = 'Jon Snow' +``` +Благодаря автоматическому определению подходящих операторов нам не нужно решать различные специальные случаи. Nette решит их за нас: -where() -------- +```php +$table->where('id', 1); // WHERE `id` = 1 +$table->where('id', null); // WHERE `id` IS NULL +$table->where('id', [1, 2, 3]); // WHERE `id` IN (1, 2, 3) +// можно использовать и заполнитель в виде вопросительного знака без оператора: +$table->where('id ?', 1); // WHERE `id` = 1 +``` -Nette Database Explorer может автоматически добавлять необходимые операторы для переданных значений: +Метод правильно обрабатывает и отрицательные условия, и пустые массивы: -.[language-php] -| `$table->where('field', $value)` | field = $value -| `$table->where('field', null)` | field IS NULL -| `$table->where('field > ?', $val)` | field > $val -| `$table->where('field', [1, 2])` | field IN (1, 2) -| `$table->where('id = ? OR name = ?', 1, $name)` | id = 1 OR name = 'Jon Snow' -| `$table->where('field', $explorer->table($tableName))` | field IN (SELECT $primary FROM $tableName) -| `$table->where('field', $explorer->table($tableName)->select('col'))` | field IN (SELECT col FROM $tableName) +```php +$table->where('id', []); // WHERE `id` IS NULL AND FALSE -- ничего не найдет +$table->where('id NOT', []); // WHERE `id` IS NULL OR TRUE -- найдет все +$table->where('NOT (id ?)', []); // WHERE NOT (`id` IS NULL AND FALSE) -- найдет все +// $table->where('NOT id ?', $ids); Внимание - этот синтаксис не поддерживается +``` -Вы можете указать заполнитель даже без оператора column. Эти вызовы одинаковы. +В качестве параметра можно передать также результат из другой таблицы - создастся подзапрос: ```php -$table->where('id = ? OR id = ?', 1, 2); -$table->where('id ? OR id ?', 1, 2); +// WHERE `id` IN (SELECT `id` FROM `tableName`) +$table->where('id', $explorer->table($tableName)); + +// WHERE `id` IN (SELECT `col` FROM `tableName`) +$table->where('id', $explorer->table($tableName)->select('col')); ``` -Эта функция позволяет генерировать правильный оператор на основе значения: +Условия можно передать также в виде массива, элементы которого соединяются с помощью AND: ```php -$table->where('id ?', 2); // id = 2 -$table->where('id ?', null); // id IS NULL -$table->where('id', $ids); // id IN (...) +// WHERE (`price_final` < `price_original`) AND (`stock_count` > `min_stock`) +$table->where([ + 'price_final < price_original', + 'stock_count > min_stock', +]); ``` -Selection корректно обрабатывает и отрицательные условия, работает и для пустых массивов: +В массиве можно использовать пары ключ => значение, и Nette снова автоматически выберет правильные операторы: ```php -$table->where('id', []); // id IS NULL AND FALSE -$table->where('id NOT', []); // id IS NULL OR TRUE -$table->where('NOT (id ?)', $ids); // NOT (id IS NULL AND FALSE) +// WHERE (`status` = 'active') AND (`id` IN (1, 2, 3)) +$table->where([ + 'status' => 'active', + 'id' => [1, 2, 3], +]); +``` + +В массиве можно комбинировать SQL-выражения с заполнителями в виде вопросительных знаков и несколькими параметрами. Это подходит для сложных условий с точно определенными операторами: -// это приведет к исключению, данный синтаксис не поддерживается -$table->where('NOT id ?', $ids); +```php +// WHERE (`age` > 18) AND (ROUND(`score`, 2) > 75.5) +$table->where([ + 'age > ?' => 18, + 'ROUND(score, ?) > ?' => [2, 75.5], // два параметра передаем как массив +]); ``` +Множественные вызовы `where()` автоматически соединяют условия с помощью AND. -whereOr() ---------- -Пример использования без параметров: +whereOr(array $parameters): static .[method] +-------------------------------------------- + +Подобно `where()` добавляет условия, но с тем отличием, что соединяет их с помощью OR: ```php -// WHERE (user_id IS NULL) OR (SUM(`field1`) > SUM(`field2`)) +// WHERE (`status` = 'active') OR (`deleted` = 1) $table->whereOr([ - 'user_id IS NULL', - 'SUM(field1) > SUM(field2)', + 'status' => 'active', + 'deleted' => true, ]); ``` -Мы используем параметры. Если вы не укажете оператор, Nette Database Explorer автоматически добавит соответствующий оператор: +Здесь также можно использовать более сложные выражения: ```php -// WHERE (`field1` IS NULL) OR (`field2` IN (3, 5)) OR (`amount` > 11) +// WHERE (`price` > 1000) OR (`price_with_tax` > 1500) $table->whereOr([ - 'field1' => null, - 'field2' => [3, 5], - 'amount >' => 11, + 'price > ?' => 1000, + 'price_with_tax > ?' => 1500, ]); ``` -Ключ может содержать выражение, содержащее подстановочные вопросительные знаки, а затем передавать параметры в значении: + +wherePrimary(mixed $key): static .[method] +------------------------------------------ + +Добавляет условие для первичного ключа таблицы: ```php -// WHERE (`id` > 12) OR (ROUND(`id`, 5) = 3) -$table->whereOr([ - 'id > ?' => 12, - 'ROUND(id, ?) = ?' => [5, 3], -]); +// WHERE `id` = 123 +$table->wherePrimary(123); + +// WHERE `id` IN (1, 2, 3) +$table->wherePrimary([1, 2, 3]); +``` + +Если таблица имеет составной первичный ключ (например, `foo_id`, `bar_id`), передаем его как массив: + +```php +// WHERE `foo_id` = 1 AND `bar_id` = 5 +$table->wherePrimary(['foo_id' => 1, 'bar_id' => 5])->fetch(); + +// WHERE (`foo_id`, `bar_id`) IN ((1, 5), (2, 3)) +$table->wherePrimary([ + ['foo_id' => 1, 'bar_id' => 5], + ['foo_id' => 2, 'bar_id' => 3], +])->fetchAll(); ``` -order() -------- +order(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- -Примеры использования: +Определяет порядок, в котором будут возвращены строки. Можно сортировать по одному или нескольким столбцам, в убывающем или возрастающем порядке, или по собственному выражению: ```php -$table->order('field1'); // ORDER BY `field1` -$table->order('field1 DESC, field2'); // ORDER BY `field1` DESC, `field2` -$table->order('field = ? DESC', 123); // ORDER BY `field` = 123 DESC +$table->order('created'); // ORDER BY `created` +$table->order('created DESC'); // ORDER BY `created` DESC +$table->order('priority DESC, created'); // ORDER BY `priority` DESC, `created` +$table->order('status = ? DESC', 'active'); // ORDER BY `status` = 'active' DESC ``` -select() --------- +select(string $columns, ...$parameters): static .[method] +--------------------------------------------------------- -Примеры использования: +Указывает столбцы, которые должны быть возвращены из базы данных. По умолчанию Nette Database Explorer возвращает только те столбцы, которые реально используются в коде. Метод `select()` мы используем в случаях, когда нужно вернуть специфические выражения: ```php -$table->select('field1'); // SELECT `field1` -$table->select('col, UPPER(col) AS abc'); // SELECT `col`, UPPER(`col`) AS abc -$table->select('SUBSTR(title, ?)', 3); // SELECT SUBSTR(`title`, 3) +// SELECT *, DATE_FORMAT(`created_at`, "%d.%m.%Y") AS `formatted_date` +$table->select('*, DATE_FORMAT(created_at, ?) AS formatted_date', '%d.%m.%Y'); ``` +Псевдонимы, определенные с помощью `AS`, затем доступны как свойства объекта ActiveRow: + +```php +foreach ($table as $row) { + echo $row->formatted_date; // доступ к псевдониму +} +``` -limit() -------- -Примеры использования: +limit(?int $limit, ?int $offset = null): static .[method] +--------------------------------------------------------- + +Ограничивает количество возвращаемых строк (LIMIT) и опционально позволяет установить смещение: ```php -$table->limit(1); // LIMIT 1 -$table->limit(1, 10); // LIMIT 1 OFFSET 10 +$table->limit(10); // LIMIT 10 (вернет первые 10 строк) +$table->limit(10, 20); // LIMIT 10 OFFSET 20 ``` +Для пагинации удобнее использовать метод `page()`. + -page() ------- +page(int $page, int $itemsPerPage, &$numOfPages = null): static .[method] +------------------------------------------------------------------------- -Альтернативный способ установки предела (limit) и смещения (offset): +Упрощает пагинацию результатов. Принимает номер страницы (считая от 1) и количество элементов на страницу. Опционально можно передать ссылку на переменную, в которую будет сохранено общее количество страниц: ```php -$page = 5; -$itemsPerPage = 10; -$table->page($page, $itemsPerPage); // LIMIT 10 OFFSET 40 +$numOfPages = null; +$table->page(page: 3, itemsPerPage: 10, $numOfPages); +echo "Всего страниц: $numOfPages"; ``` -Получение номера последней страницы, переданного в переменную `$lastPage`: + +group(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- + +Группирует строки по указанным столбцам (GROUP BY). Обычно используется в сочетании с агрегатными функциями: ```php -$table->page($page, $itemsPerPage, $lastPage); +// Подсчитывает количество продуктов в каждой категории +$table->select('category_id, COUNT(*) AS count') + ->group('category_id'); ``` -group() -------- +having(string $having, ...$parameters): static .[method] +-------------------------------------------------------- -Примеры использования: +Устанавливает условие для фильтрации сгруппированных строк (HAVING). Можно использовать в сочетании с методом `group()` и агрегатными функциями: ```php -$table->group('field1'); // GROUP BY `field1` -$table->group('field1, field2'); // GROUP BY `field1`, `field2` +// Находит категории, в которых более 100 продуктов +$table->select('category_id, COUNT(*) AS count') + ->group('category_id') + ->having('count > ?', 100); ``` -having() --------- +Чтение данных +============= + +Для чтения данных из базы данных у нас есть несколько полезных методов: + +.[language-php] +| `foreach ($table as $key => $row)` | Итерирует по всем строкам, `$key` - значение первичного ключа, `$row` - объект ActiveRow +| `$row = $table->get($key)` | Возвращает одну строку по первичному ключу +| `$row = $table->fetch()` | Возвращает текущую строку и перемещает указатель на следующую +| `$array = $table->fetchPairs()` | Создает ассоциативный массив из результатов +| `$array = $table->fetchAll()` | Возвращает все строки как массив +| `count($table)` | Возвращает количество строк в объекте Selection + +Объект [ActiveRow |api:Nette\Database\Table\ActiveRow] предназначен только для чтения. Это означает, что нельзя изменять значения его свойств. Это ограничение обеспечивает консистентность данных и предотвращает неожиданные побочные эффекты. Данные загружаются из базы данных, и любое изменение должно быть выполнено явно и контролируемо. + + +`foreach` - итерация по всем строкам +------------------------------------ -Примеры использования: +Самый простой способ выполнить запрос и получить строки — это итерация в цикле `foreach`. Автоматически запускает SQL-запрос. ```php -$table->having('COUNT(items) >', 100); // HAVING COUNT(`items`) > 100 +$books = $explorer->table('book'); +foreach ($books as $key => $book) { + // $key - значение первичного ключа, $book - ActiveRow + echo "$book->title ({$book->author->name})"; +} ``` -Фильтрация по другому значению таблицы .[#toc-joining-key] ----------------------------------------------------------- +get($key): ?ActiveRow .[method] +------------------------------- + +Выполняет SQL-запрос и возвращает строку по первичному ключу, или `null`, если она не существует. + +```php +$book = $explorer->table('book')->get(123); // вернет ActiveRow с ID 123 или null +if ($book) { + echo $book->title; +} +``` -Довольно часто требуется отфильтровать результаты по какому-либо условию, которое включает другую таблицу базы данных. Для таких условий требуются табличные соединения. Однако вам больше не нужно их писать. -Допустим, вам нужно получить все книги, имя автора которых 'Jon'. Всё, что вам нужно написать, это соединяющий ключ отношения и имя столбца в объединенной таблице. Ключ объединения берется из столбца, который ссылается на таблицу, к которой вы хотите присоединиться. В нашем примере (см. схему db) это столбец `author_id`, и достаточно использовать только его первую часть — `author` (суффикс `_id` можно опустить). `name` — это столбец в таблице `author`, который мы хотим использовать. Условие для переводчика книги (которое связано с колонкой `translator_id`) может быть создано так же просто. +fetch(): ?ActiveRow .[method] +----------------------------- + +Возвращает строку и перемещает внутренний указатель на следующую. Если больше нет строк, возвращает `null`. ```php $books = $explorer->table('book'); -$books->where('author.name LIKE ?', '%Jon%'); -$books->where('translator.name', 'David Grudl'); +while ($book = $books->fetch()) { + $this->processBook($book); +} ``` -Логика соединительных ключей определяется реализацией [Conventions |api:Nette\Database\Conventions]. Мы рекомендуем использовать [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions], который анализирует ваши внешние ключи и позволяет легко работать с этими отношениями. -Отношения между книгой и её автором — 1:N. Обратные отношения также возможны. Мы называем это **обратным соединением**. Взгляните на другой пример. Мы хотим получить всех авторов, которые написали более 3 книг. Чтобы сделать соединение обратным, мы используем `:` (двоеточие). Двоеточие означает, что объединенное отношение имеет значение hasMany (и это вполне логично, так как две точки больше, чем одна). К сожалению, класс Selection недостаточно умен, поэтому мы должны помочь с агрегацией и предоставить оператор `GROUP BY`, также условие должно быть записано в виде оператора `HAVING`. +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Возвращает результаты как ассоциативный массив. Первый аргумент указывает имя столбца, который будет использоваться как ключ в массиве, второй аргумент указывает имя столбца, который будет использоваться как значение: ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book.id) > 3'); +$authors = $explorer->table('author')->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] ``` -Вы, наверное, заметили, что выражение объединения относится к книге, но неясно, объединяем ли мы через `author_id` или `translator_id`. В приведенном выше примере Selection соединяется через столбец `author_id`, потому что найдено совпадение с исходной таблицей — таблицей `author`. Если бы такого совпадения не было, и было бы больше возможностей, Nette выбросил бы [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. +Если указать только первый параметр, значением будет вся строка, то есть объект `ActiveRow`: + +```php +$authors = $explorer->table('author')->fetchPairs('id'); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` -Чтобы выполнить объединение через колонку `translator_id`, предоставьте необязательный параметр в выражении объединения. +В случае дублирующихся ключей используется значение из последней строки. При использовании `null` в качестве ключа массив будет индексирован численно с нуля (тогда коллизий не происходит): ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book(translator).id) > 3'); +$authors = $explorer->table('author')->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] ``` -Давайте рассмотрим более сложное выражение присоединения. -Мы хотим найти всех авторов, которые написали что-то о PHP. У всех книг есть теги, поэтому мы должны выбрать тех авторов, которые написали любую книгу с тегом PHP. +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Альтернативно, вы можете указать в качестве параметра callback, который для каждой строки будет возвращать либо само значение, либо пару ключ-значение. ```php -$authors = $explorer->table('author'); -$authors->where(':book:book_tags.tag.name', 'PHP') - ->group('author.id') - ->having('COUNT(:book:book_tags.tag.id) > 0'); +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => "$row->title ({$row->author->name})"); +// ['Первая книга (Ян Новак)', ...] + +// Callback может также возвращать массив с парой ключ & значение: +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => [$row->title, $row->author->name]); +// ['Первая книга' => 'Ян Новак', ...] ``` -Агрегированные запросы .[#toc-aggregate-queries] ------------------------------------------------- +fetchAll(): array .[method] +--------------------------- -| `$table->count('*')` | Получаем количество строк -| `$table->count("DISTINCT $column")` | Получаем количество отдельных значений -| `$table->min($column)` | Получаем минимальное значение -| `$table->max($column)` | Получаем максимальное значение -| `$table->sum($column)` | Получаем сумму всех значений -| `$table->aggregation("GROUP_CONCAT($column)")` | Запускаем любую функцию агрегации +Возвращает все строки как ассоциативный массив объектов `ActiveRow`, где ключами являются значения первичных ключей. -.[caution] -Метод `count()` без указания параметров выбирает все записи и возвращает размер массива, что очень неэффективно. Например, если вам нужно подсчитать количество строк для пейджинга, всегда указывайте первый аргумент. +```php +$allBooks = $explorer->table('book')->fetchAll(); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` + + +count(): int .[method] +---------------------- + +Метод `count()` без параметра возвращает количество строк в объекте `Selection`: + +```php +$table->where('category', 1); +$count = $table->count(); +$count = count($table); // альтернатива +``` +Внимание, `count()` с параметром выполняет агрегатную функцию COUNT в базе данных, см. ниже. -Экранирование и кавычки .[#toc-escaping-quoting] -================================================ -Database Explorer умен и избавится от параметров и идентификаторов кавычек за вас. Тем не менее, необходимо соблюдать следующие основные правила: +ActiveRow::toArray(): array .[method] +------------------------------------- -- ключевые слова, функции, процедуры должны быть в верхнем регистре -- столбцы и таблицы должны быть в нижнем регистре -- передавайте переменные в качестве параметров, не объединяйте их +Преобразует объект `ActiveRow` в ассоциативный массив, где ключами являются имена столбцов, а значениями — соответствующие данные. ```php -->where('name like ?', 'John'); // НЕПРАВИЛЬНО! Генерирует: `name` `like` ? -->where('name LIKE ?', 'John'); // ПРАВИЛЬНО +$book = $explorer->table('book')->get(1); +$bookArray = $book->toArray(); +// $bookArray будет ['id' => 1, 'title' => '...', 'author_id' => ..., ...] +``` + + +Агрегация +========= + +Класс `Selection` предоставляет методы для легкого выполнения агрегатных функций (COUNT, SUM, MIN, MAX, AVG и т.д.). + +.[language-php] +| `count($expr)` | Подсчитывает количество строк +| `min($expr)` | Возвращает минимальное значение в столбце +| `max($expr)` | Возвращает максимальное значение в столбце +| `sum($expr)` | Возвращает сумму значений в столбце +| `aggregation($function)` | Позволяет выполнить любую агрегатную функцию. Напр. `AVG()`, `GROUP_CONCAT()` + -->where('KEY = ?', $value); // НЕПРАВИЛЬНО! КЛЮЧ - это ключевое слово -->where('key = ?', $value); // ПРАВИЛЬНО. Генерирует: `key` = ? +count(string $expr): int .[method] +---------------------------------- -->where('name = ' . $name); // Неправильно! sql-инъекция! -->where('name = ?', $name); // ПРАВИЛЬНО +Выполняет SQL-запрос с функцией COUNT и возвращает результат. Метод используется для определения, сколько строк соответствует определенному условию: -->select('DATE_FORMAT(created, "%d.%m.%Y")'); // НЕПРАВИЛЬНО! Передавайте переменные как параметры, не конкатенируйте -->select('DATE_FORMAT(created, ?)', '%d.%m.%Y'); // ПРАВИЛЬНО +```php +$count = $table->count('*'); // SELECT COUNT(*) FROM `table` +$count = $table->count('DISTINCT column'); // SELECT COUNT(DISTINCT `column`) FROM `table` ``` -.[warning] -Неправильное использование может привести к образованию дыр в безопасности +Внимание, [#count()] без параметра только возвращает количество строк в объекте `Selection`. + +min(string $expr) и max(string $expr) .[method] +----------------------------------------------- -Получение данных .[#toc-fetching-data] -====================================== +Методы `min()` и `max()` возвращают минимальное и максимальное значение в указанном столбце или выражении: -| `foreach ($table as $id => $row)` | Итерация по всем строкам результата -| `$row = $table->get($id)` | Получаем одну строку с идентификатором $id из таблицы -| `$row = $table->fetch()` | Получаем следующую строку из результата -| `$array = $table->fetchPairs($key, $value)` | Выборка всех значений в виде ассоциативного массива -| `$array = $table->fetchPairs($key)` | Выборка всех строк в виде ассоциативного массива -| `count($table)` | Получаем количество строк в результирующем наборе +```php +// SELECT MAX(`price`) FROM `products` WHERE `active` = 1 +$maxPrice = $products->where('active', true) + ->max('price'); +``` -Вставка, обновление и удаление .[#toc-insert-update-delete] -=========================================================== +sum(string $expr) .[method] +--------------------------- -Метод `insert()` принимает массив объектов Traversable (например, [ArrayHash |utils:arrays#ArrayHash], который возвращает [forms|forms:]): +Возвращает сумму значений в указанном столбце или выражении: + +```php +// SELECT SUM(`price` * `items_in_stock`) FROM `products` WHERE `active` = 1 +$totalPrice = $products->where('active', true) + ->sum('price * items_in_stock'); +``` + + +aggregation(string $function, ?string $groupFunction = null) .[method] +---------------------------------------------------------------------- + +Позволяет выполнить любую агрегатную функцию. + +```php +// средняя цена продуктов в категории +$avgPrice = $products->where('category_id', 1) + ->aggregation('AVG(price)'); + +// соединяет теги продукта в одну строку +$tags = $products->where('id', 1) + ->aggregation('GROUP_CONCAT(tag.name) AS tags') + ->fetch() + ->tags; +``` + +Если нам нужно агрегировать результаты, которые уже сами по себе получены из какой-либо агрегатной функции и группировки (например, `SUM(значение)` по сгруппированным строкам), в качестве второго аргумента указываем агрегатную функцию, которая должна быть применена к этим промежуточным результатам: + +```php +// Вычисляет общую цену продуктов на складе для отдельных категорий, а затем суммирует эти цены. +$totalPrice = $products->select('category_id, SUM(price * stock) AS category_total') + ->group('category_id') + ->aggregation('SUM(category_total)', 'SUM'); +``` + +В этом примере мы сначала вычисляем общую цену продуктов в каждой категории (`SUM(price * stock) AS category_total`) и группируем результаты по `category_id`. Затем используем `aggregation('SUM(category_total)', 'SUM')` для суммирования этих промежуточных сумм `category_total`. Второй аргумент `'SUM'` говорит, что к промежуточным результатам должна быть применена функция SUM. + + +Insert, Update & Delete +======================= + +Nette Database Explorer упрощает вставку, обновление и удаление данных. Все указанные методы в случае ошибки выбрасывают исключение `Nette\Database\DriverException`. + + +Selection::insert(iterable $data) .[method] +------------------------------------------- + +Вставляет новые записи в таблицу. + +**Вставка одной записи:** + +Новую запись передаем как ассоциативный массив или итерируемый объект (например, ArrayHash, используемый в [формах |forms:]), где ключи соответствуют именам столбцов в таблице. + +Если в таблице определен первичный ключ, метод возвращает объект `ActiveRow`, который перезагружается из базы данных, чтобы учесть возможные изменения, выполненные на уровне базы данных (триггеры, значения по умолчанию столбцов, вычисления автоинкрементных столбцов). Этим обеспечивается консистентность данных, и объект всегда содержит актуальные данные из базы данных. Если однозначного первичного ключа нет, возвращает переданные данные в виде массива. ```php $row = $explorer->table('users')->insert([ - 'name' => $name, - 'year' => $year, + 'name' => 'John Doe', + 'email' => 'john.doe@example.com', ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) +// $row - экземпляр ActiveRow и содержит полные данные вставленной строки, +// включая автоматически сгенерированный ID и возможные изменения, выполненные триггерами +echo $row->id; // Выведет ID нового вставленного пользователя +echo $row->created_at; // Выведет время создания, если оно установлено триггером ``` -Если для таблицы определен первичный ключ, возвращается объект ActiveRow, содержащий вставленную строку. +**Вставка нескольких записей одновременно:** -Вставка нескольких значений: +Метод `insert()` позволяет вставить несколько записей с помощью одного SQL-запроса. В этом случае возвращает количество вставленных строк. ```php -$explorer->table('users')->insert([ +$insertedRows = $explorer->table('users')->insert([ + [ + 'name' => 'John', + 'year' => 1994, + ], [ - 'name' => 'Jim', - 'year' => 1978, - ], [ 'name' => 'Jack', - 'year' => 1987, + 'year' => 1995, ], ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) +// INSERT INTO `users` (`name`, `year`) VALUES ('John', 1994), ('Jack', 1995) +// $insertedRows будет 2 ``` -В качестве параметров можно передавать файлы или объекты DateTime: +В качестве параметра можно также передать объект `Selection` с выборкой данных. + +```php +$newUsers = $explorer->table('potential_users') + ->where('approved', 1) + ->select('name, email'); + +$insertedRows = $explorer->table('users')->insert($newUsers); +``` + +**Вставка специальных значений:** + +В качестве значений можно передавать и файлы, объекты DateTime или SQL-литералы: ```php $explorer->table('users')->insert([ - 'name' => $name, - 'created' => new DateTime, // или $explorer::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // вставляет файл + 'name' => 'John', + 'created_at' => new DateTime, // преобразует в формат базы данных + 'avatar' => fopen('image.jpg', 'rb'), // вставит бинарное содержимое файла + 'uuid' => $explorer::literal('UUID()'), // вызовет функцию UUID() ]); ``` -Обновление (возвращает количество затронутых строк): + +Selection::update(iterable $data): int .[method] +------------------------------------------------ + +Обновляет строки в таблице согласно указанному фильтру. Возвращает количество действительно измененных строк. + +Изменяемые столбцы передаем как ассоциативный массив или итерируемый объект (например, ArrayHash, используемый в [формах |forms:]), где ключи соответствуют именам столбцов в таблице: ```php -$count = $explorer->table('users') - ->where('id', 10) // должен вызываться до update() +$affected = $explorer->table('users') + ->where('id', 10) ->update([ - 'name' => 'Ned Stark' + 'name' => 'John Smith', + 'year' => 1994, ]); -// UPDATE `users` SET `name`='Ned Stark' WHERE (`id` = 10) +// UPDATE `users` SET `name` = 'John Smith', `year` = 1994 WHERE `id` = 10 ``` -Для обновления мы можем использовать операторы `+=` и `-=`: +Для изменения числовых значений можно использовать операторы `+=` и `-=`: ```php $explorer->table('users') + ->where('id', 10) ->update([ - 'age+=' => 1, // see += + 'points+=' => 1, // увеличит значение столбца 'points' на 1 + 'coins-=' => 1, // уменьшит значение столбца 'coins' на 1 ]); -// UPDATE users SET `age` = `age` + 1 +// UPDATE `users` SET `points` = `points` + 1, `coins` = `coins` - 1 WHERE `id` = 10 ``` -Удаление (возвращает количество удаленных строк): + +Selection::delete(): int .[method] +---------------------------------- + +Удаляет строки из таблицы согласно указанному фильтру. Возвращает количество удаленных строк. ```php $count = $explorer->table('users') ->where('id', 10) ->delete(); -// DELETE FROM `users` WHERE (`id` = 10) +// DELETE FROM `users` WHERE `id` = 10 ``` +.[caution] +При вызове `update()` и `delete()` не забудьте с помощью `where()` указать строки, которые нужно изменить/удалить. Если `where()` не использовать, операция будет выполнена над всей таблицей! -Работа с отношениями .[#toc-working-with-relationships] -======================================================= +ActiveRow::update(iterable $data): bool .[method] +------------------------------------------------- -Один к одному ("has one") .[#toc-has-one-relation] --------------------------------------------------- -Отношение «Один к одному» — распространенный случай использования. У книги *есть один* автор. Книга *имеет одного* переводчика. Получение связанной строки в основном осуществляется методом `ref()`. Он принимает два аргумента: имя целевой таблицы и столбец исходного соединения. См. пример: +Обновляет данные в строке базы данных, представленной объектом `ActiveRow`. В качестве параметра принимает итерируемый объект с данными, которые нужно обновить (ключи — имена столбцов). Для изменения числовых значений можно использовать операторы `+=` и `-=`: + +После выполнения обновления `ActiveRow` автоматически перезагружается из базы данных, чтобы учесть возможные изменения, выполненные на уровне базы данных (например, триггеры). Метод возвращает true только если произошло действительное изменение данных. ```php -$book = $explorer->table('book')->get(1); -$book->ref('author', 'author_id'); +$article = $explorer->table('article')->get(1); +$article->update([ + 'views += 1', // увеличим количество просмотров +]); +echo $article->views; // Выведет текущее количество просмотров ``` -В приведенном выше примере мы извлекаем связанную запись об авторе из таблицы `author`, поиск первичного ключа автора осуществляется по столбцу `book.author_id`. Метод Ref() возвращает экземпляр ActiveRow или null, если нет подходящей записи. Возвращенная строка является экземпляром ActiveRow, поэтому мы можем работать с ней так же, как и с записью книги. +Этот метод обновляет только одну конкретную строку в базе данных. Для массового обновления нескольких строк используйте метод [#Selection::update()]. + + +ActiveRow::delete() .[method] +----------------------------- + +Удаляет строку из базы данных, которая представлена объектом `ActiveRow`. ```php -$author = $book->ref('author', 'author_id'); -$author->name; -$author->born; +$book = $explorer->table('book')->get(1); +$book->delete(); // Удалит книгу с ID 1 +``` + +Этот метод удаляет только одну конкретную строку в базе данных. Для массового удаления нескольких строк используйте метод [#Selection::delete()]. + + +Связи между таблицами +===================== + +В реляционных базах данных данные разделены на несколько таблиц и взаимосвязаны с помощью внешних ключей. Nette Database Explorer предлагает революционный способ работы с этими связями - без написания JOIN-запросов и необходимости что-либо конфигурировать или генерировать. + +Для иллюстрации работы со связями используем пример базы данных книг ([найдете его на GitHub |https://github.com/nette-examples/books]). В базе данных у нас есть таблицы: + +- `author` - писатели и переводчики (столбцы `id`, `name`, `web`, `born`) +- `book` - книги (столбцы `id`, `author_id`, `translator_id`, `title`, `sequel_id`) +- `tag` - теги (столбцы `id`, `name`) +- `book_tag` - связующая таблица между книгами и тегами (столбцы `book_id`, `tag_id`) + +[* db-schema-1-.webp *] *** Структура базы данных .<> + +В нашем примере базы данных книг мы находим несколько типов отношений (хотя модель упрощена по сравнению с реальностью): + +- Один-ко-многим 1:N – каждая книга **имеет одного** автора, автор может написать **несколько** книг +- Ноль-ко-многим 0:N – книга **может иметь** переводчика, переводчик может перевести **несколько** книг +- Ноль-к-одному 0:1 – книга **может иметь** продолжение +- Многие-ко-многим M:N – книга **может иметь несколько** тегов, и тег может быть присвоен **нескольким** книгам + +В этих отношениях всегда существует родительская и дочерняя таблица. Например, в отношении между автором и книгой таблица `author` является родительской, а `book` — дочерней - можно представить это так, что книга всегда "принадлежит" какому-то автору. Это проявляется и в структуре базы данных: дочерняя таблица `book` содержит внешний ключ `author_id`, который ссылается на родительскую таблицу `author`. -// или напрямую -$book->ref('author', 'author_id')->name; -$book->ref('author', 'author_id')->born; +Если нам нужно вывести книги, включая имена их авторов, у нас есть два варианта. Либо получить данные одним SQL-запросом с помощью JOIN: + +```sql +SELECT book.*, author.name FROM book LEFT JOIN author ON book.author_id = author.id ``` -У книги также один переводчик, поэтому узнать имя переводчика довольно просто. +Либо загрузить данные в два этапа - сначала книги, а затем их авторов - и потом собрать их в PHP: + +```sql +SELECT * FROM book; +SELECT * FROM author WHERE id IN (1, 2, 3); -- id авторов полученных книг +``` + +Второй подход на самом деле более эффективен, хотя это может показаться удивительным. Данные загружаются только один раз и могут быть лучше использованы в кеше. Именно таким образом работает Nette Database Explorer - все решает под капотом и предлагает вам элегантный API: + ```php -$book->ref('author', 'translator_id')->name +$books = $explorer->table('book'); +foreach ($books as $book) { + echo 'название: ' . $book->title; + echo 'написано: ' . $book->author->name; // $book->author - запись из таблицы 'author' + echo 'переведено: ' . $book->translator?->name; +} ``` -Всё это хорошо, но несколько громоздко, не находите? Database Explorer уже содержит определения внешних ключей, так почему бы не использовать их автоматически? Давайте сделаем это! -Если мы вызываем свойство, которого не существует, ActiveRow пытается разрешить имя вызывающего свойства как отношение 'has one'. Получение этого свойства аналогично вызову метода ref() только с одним аргументом. Мы будем называть единственный аргумент **key**. Ключ будет разрешен в конкретное отношение внешнего ключа. Переданный ключ сопоставляется со столбцами строки, и если он совпадает, то внешний ключ, определенный в сопоставленном столбце, используется для получения данных из связанной целевой таблицы. См. пример: +Доступ к родительской таблице +----------------------------- + +Доступ к родительской таблице прост. Речь идет об отношениях типа *книга имеет автора* или *книга может иметь переводчика*. Связанную запись получаем через свойство объекта ActiveRow - его имя соответствует имени столбца с внешним ключом без суффикса `_id`: ```php -$book->author->name; -// то же самое -$book->ref('author')->name; +$book = $explorer->table('book')->get(1); +echo $book->author->name; // найдет автора по столбцу author_id +echo $book->translator?->name; // найдет переводчика по translator_id ``` -Экземпляр ActiveRow не имеет колонки автора. Все столбцы книги ищутся на предмет совпадения с *key*. Совпадение в данном случае означает, что имя столбца должно содержать ключ. Так, в приведенном примере столбец `author_id` содержит строку 'author' и поэтому сопоставляется с ключом 'author'. Если вы хотите получить переводчика книги, то в качестве ключа можно использовать, например, 'translator', так как ключ 'translator' будет соответствовать столбцу `translator_id`. Подробнее о логике подбора ключей вы можете прочитать в главе [Joining expressions |#joining-key]. +Когда мы обращаемся к свойству `$book->author`, Explorer ищет в таблице `book` столбец, имя которого соответствует `author` (т.е. `author_id`). По значению в этом столбце он загружает соответствующую запись из таблицы `author` и возвращает ее как `ActiveRow`. Аналогично работает и `$book->translator`, который использует столбец `translator_id`. Поскольку столбец `translator_id` может содержать `null`, мы используем в коде nullsafe оператор `?->`. + +Альтернативный путь предлагает метод `ref()`, который принимает два аргумента: имя целевой таблицы и имя связующего столбца, и возвращает экземпляр `ActiveRow` или `null`: ```php -echo $book->title . ': '; -echo $book->author->name; -if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; -} +echo $book->ref('author', 'author_id')->name; // связь с автором +echo $book->ref('author', 'translator_id')->name; // связь с переводчиком ``` -Если вы хотите получить несколько книг, используйте тот же подход. Nette Database Explorer найдет авторов и переводчиков сразу для всех найденных книг. +Метод `ref()` удобен, если нельзя использовать доступ через свойство, потому что таблица содержит столбец с таким же именем (т.е. `author`). В остальных случаях рекомендуется использовать доступ через свойство, который более читабелен. + +Explorer автоматически оптимизирует запросы к базе данных. Когда мы проходим по книгам в цикле и обращаемся к их связанным записям (авторам, переводчикам), Explorer не генерирует запрос для каждой книги отдельно. Вместо этого он выполняет только один SELECT для каждого типа связи, тем самым значительно снижая нагрузку на базу данных. Например: ```php $books = $explorer->table('book'); foreach ($books as $book) { echo $book->title . ': '; echo $book->author->name; - if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; - } + echo $book->translator?->name; } ``` -Код будет выполнять только эти 3 запроса: +Этот код вызовет только эти три молниеносных запроса к базе данных: + ```sql SELECT * FROM `book`; -SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- ids of fetched books from author_id column -SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- ids of fetched books from translator_id column +SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- id из столбца author_id выбранных книг +SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- id из столбца translator_id выбранных книг ``` +.[note] +Логика поиска связующего столбца задана реализацией [Conventions |api:Nette\Database\Conventions]. Рекомендуем использовать [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions], которые анализируют внешние ключи и позволяют легко работать с существующими отношениями между таблицами. + -Один ко многим ("has many") .[#toc-has-many-relation] ------------------------------------------------------ +Доступ к дочерней таблице +------------------------- -Отношение «один ко многим» — это просто обратное отношение «один к одному». Автор *написал* *много* книг. Автор *перевел* *много* книг. Как видите, этот тип отношения немного сложнее, потому что отношение является «именованным» ("написал", "перевел"). У экземпляра ActiveRow есть метод `related()`, который возвращает массив связанных записей. Записи также являются экземплярами ActiveRow. См. пример ниже: +Доступ к дочерней таблице работает в обратном направлении. Теперь мы спрашиваем, *какие книги написал этот автор* или *перевел этот переводчик*. Для этого типа запроса мы используем метод `related()`, который возвращает `Selection` со связанными записями. Посмотрим на пример: ```php -$author = $explorer->table('author')->get(11); -echo $author->name . ' написал:'; +$author = $explorer->table('author')->get(1); +// Выведет все книги автора foreach ($author->related('book.author_id') as $book) { - echo $book->title; + echo "Написал: $book->title"; } -echo 'и перевёл:'; +// Выведет все книги, которые автор перевел foreach ($author->related('book.translator_id') as $book) { - echo $book->title; + echo "Перевел: $book->title"; } ``` -Метод `related()` принимает полное описание соединения, передаваемое как два аргумента или как один аргумент, соединённый точкой. Первый аргумент — целевая таблица, второй — целевой столбец. +Метод `related()` принимает описание соединения как один аргумент с точечной нотацией или как два отдельных аргумента: ```php -$author->related('book.translator_id'); -// то же самое -$author->related('book', 'translator_id'); +$author->related('book.translator_id'); // один аргумент +$author->related('book', 'translator_id'); // два аргумента ``` -Вы можете использовать эвристику Nette Database Explorer, основанную на внешних ключах, и указать только аргумент **key**. Ключ будет сопоставлен со всеми внешними ключами, указывающими на текущую таблицу (таблица `author`). Если есть совпадение, Nette Database Explorer будет использовать этот внешний ключ, в противном случае он выбросит [Nette\InvalidArgumentException|api:Nette\InvalidArgumentException] или [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. Подробнее о логике подбора ключей вы можете прочитать в главе [Joining expressions |#joining-key]. +Explorer может автоматически определить правильный связующий столбец на основе имени родительской таблицы. В данном случае соединение происходит через столбец `book.author_id`, поскольку имя исходной таблицы — `author`: -Конечно, вы можете вызвать связанные методы для всех найденных авторов, и Nette Database Explorer снова получит соответствующие книги сразу. +```php +$author->related('book'); // использует book.author_id +``` + +Если бы существовало несколько возможных соединений, Explorer выбросил бы исключение [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. + +Метод `related()` можно, конечно, использовать и при прохождении нескольких записей в цикле, и Explorer и в этом случае автоматически оптимизирует запросы: ```php $authors = $explorer->table('author'); foreach ($authors as $author) { echo $author->name . ' написал:'; foreach ($author->related('book') as $book) { - $book->title; + echo $book->title; } } ``` -В приведенном выше примере будет выполнено только два запроса: +Этот код сгенерирует только два молниеносных SQL-запроса: ```sql SELECT * FROM `author`; -SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- идентификаторы найденных авторов +SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- id выбранных авторов +``` + + +Связь Many-to-many +------------------ + +Для связи многие-ко-многим (M:N) необходимо наличие связующей таблицы (в нашем случае `book_tag`), которая содержит два столбца с внешними ключами (`book_id`, `tag_id`). Каждый из этих столбцов ссылается на первичный ключ одной из связываемых таблиц. Для получения связанных данных сначала получаем записи из связующей таблицы с помощью `related('book_tag')`, а затем переходим к целевым данным: + +```php +$book = $explorer->table('book')->get(1); +// выведет названия тегов, присвоенных книге +foreach ($book->related('book_tag') as $bookTag) { + echo $bookTag->tag->name; // выведет название тега через связующую таблицу +} + +$tag = $explorer->table('tag')->get(1); +// или наоборот: выведет названия книг, отмеченных этим тегом +foreach ($tag->related('book_tag') as $bookTag) { + echo $bookTag->book->title; // выведет название книги +} +``` + +Explorer снова оптимизирует SQL-запросы до эффективной формы: + +```sql +SELECT * FROM `book`; +SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 2, ...)); -- id выбранных книг +SELECT * FROM `tag` WHERE (`tag`.`id` IN (1, 2, ...)); -- id тегов, найденных в book_tag +``` + + +Запросы через связанные таблицы +------------------------------- + +В методах `where()`, `select()`, `order()` и `group()` мы можем использовать специальные нотации для доступа к столбцам из других таблиц. Explorer автоматически создаст необходимые JOIN'ы. + +**Точечная нотация** (`родительская_таблица.столбец`) используется для отношения 1:N с точки зрения дочерней таблицы: + +```php +$books = $explorer->table('book'); + +// Находит книги, автор которых имеет имя, начинающееся на 'Jon' +$books->where('author.name LIKE ?', 'Jon%'); + +// Сортирует книги по имени автора по убыванию +$books->order('author.name DESC'); + +// Выводит название книги и имя автора +$books->select('book.title, author.name'); ``` +**Двоеточная нотация** (`:дочерняя_таблица.столбец`) используется для отношения 1:N с точки зрения родительской таблицы: -Создание Explorer вручную .[#toc-creating-explorer-manually] -============================================================ +```php +$authors = $explorer->table('author'); + +// Находит авторов, которые написали книгу с 'PHP' в названии +$authors->where(':book.title LIKE ?', '%PHP%'); + +// Подсчитывает количество книг для каждого автора +$authors->select('*, COUNT(:book.id) AS book_count') + ->group('author.id'); +``` -Соединение с базой данных может быть создано с помощью конфигурации приложения. В таких случаях создается служба `Nette\Database\Explorer`, которая может быть передана в качестве зависимости с помощью DI-контейнера. +В вышеприведенном примере с двоеточной нотацией (`:book.title`) не указан столбец с внешним ключом. Explorer автоматически определяет правильный столбец на основе имени родительской таблицы. В данном случае соединение происходит через столбец `book.author_id`, поскольку имя исходной таблицы — `author`. Если бы существовало несколько возможных соединений, Explorer выбросил бы исключение [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. -Однако, если Nette Database Explorer используется как самостоятельный инструмент, экземпляр объекта `Nette\Database\Explorer` должен быть создан вручную. +Связующий столбец можно явно указать в скобках: ```php -// $storage implements Nette\Caching\Storage: -$storage = new Nette\Caching\Storages\FileStorage($tempDir); -$connection = new Nette\Database\Connection($dsn, $user, $password); -$structure = new Nette\Database\Structure($connection, $storage); -$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); -$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +// Находит авторов, которые перевели книгу с 'PHP' в названии +$authors->where(':book(translator_id).title LIKE ?', '%PHP%'); ``` + +Нотации можно объединять в цепочку для доступа через несколько таблиц: + +```php +// Находит авторов книг, отмеченных тегом 'PHP' +$authors->where(':book:book_tag.tag.name', 'PHP') + ->group('author.id'); +``` + + +Расширение условий для JOIN +--------------------------- + +Метод `joinWhere()` расширяет условия, которые указываются при соединении таблиц в SQL за ключевым словом `ON`. + +Допустим, мы хотим найти книги, переведенные конкретным переводчиком: + +```php +// Находит книги, переведенные переводчиком по имени 'David' +$books = $explorer->table('book') + ->joinWhere('translator', 'translator.name', 'David'); +// LEFT JOIN author translator ON book.translator_id = translator.id AND (translator.name = 'David') +``` + +В условии `joinWhere()` мы можем использовать те же конструкции, что и в методе `where()` - операторы, заполнители в виде вопросительных знаков, массивы значений или SQL-выражения. + +Для более сложных запросов с несколькими JOIN'ами мы можем определить псевдонимы таблиц: + +```php +$tags = $explorer->table('tag') + ->joinWhere(':book_tag.book.author', 'book_author.born < ?', 1950) + ->alias(':book_tag.book.author', 'book_author'); +// LEFT JOIN `book_tag` ON `tag`.`id` = `book_tag`.`tag_id` +// LEFT JOIN `book` ON `book_tag`.`book_id` = `book`.`id` +// LEFT JOIN `author` `book_author` ON `book`.`author_id` = `book_author`.`id` +// AND (`book_author`.`born` < 1950) +``` + +Обратите внимание, что в то время как метод `where()` добавляет условия в клаузулу `WHERE`, метод `joinWhere()` расширяет условия в клаузуле `ON` при соединении таблиц. diff --git a/database/ru/guide.texy b/database/ru/guide.texy new file mode 100644 index 0000000000..2e644aa3d5 --- /dev/null +++ b/database/ru/guide.texy @@ -0,0 +1,216 @@ +Nette Database +************** + +.[perex] +Nette Database — это мощный и элегантный слой базы данных для PHP, ориентированный на простоту и интеллектуальные функции. Он предлагает два способа работы с базой данных — [Explorer |Explorer] для быстрой разработки приложений или [SQL-подход |SQL way] для прямой работы с запросами. + +<div class="grid gap-3"> +<div> + + +[SQL-подход |SQL way] +===================== +- Безопасные параметризованные запросы +- Точный контроль над формой SQL-запросов +- Когда вы пишете сложные запросы с расширенными функциями +- Оптимизация производительности с помощью специфических функций SQL + +</div> + +<div> + + +[Explorer |Explorer] +==================== +- Быстрая разработка без написания SQL +- Интуитивно понятная работа с отношениями между таблицами +- Вы оцените автоматическую оптимизацию запросов +- Подходит для быстрой и удобной работы с базой данных + +</div> + +</div> + + +Установка +========= + +Вы можете скачать и установить библиотеку с помощью инструмента [Composer|best-practices:composer]: + +```shell +composer require nette/database +``` + + +Поддерживаемые базы данных +========================== + +Nette Database поддерживает следующие базы данных: + +|* Сервер базы данных |* Имя DSN |* Поддержка в Explorer +|---------------------|-------------|----------------------- +| MySQL (>= 5.1) | mysql | ДА +| PostgreSQL (>= 9.0) | pgsql | ДА +| Sqlite 3 (>= 3.8) | sqlite | ДА +| Oracle | oci | - +| MS SQL (PDO_SQLSRV) | sqlsrv | ДА +| MS SQL (PDO_DBLIB) | mssql | - +| ODBC | odbc | - + + +Два подхода к базе данных +========================= + +Nette Database дает вам выбор: вы можете либо писать SQL-запросы напрямую (SQL-подход), либо позволить генерировать их автоматически (Explorer). Давайте посмотрим, как оба подхода решают одни и те же задачи: + +[SQL-подход|sql way] - SQL-запросы + +```php +// вставка записи +$database->query('INSERT INTO books', [ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// получение записей: авторы книг +$result = $database->query(' + SELECT authors.*, COUNT(books.id) AS books_count + FROM authors + LEFT JOIN books ON authors.id = books.author_id + WHERE authors.active = 1 + GROUP BY authors.id +'); + +// вывод (не оптимально, генерирует N+1 запросов) +foreach ($result as $author) { + $books = $database->query(' + SELECT * FROM books + WHERE author_id = ? + ORDER BY published_at DESC + ', $author->id); + + echo "Автор $author->name написал $author->books_count книг:\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +[Подход Explorer|explorer] - автоматическая генерация SQL + +```php +// вставка записи +$database->table('books')->insert([ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// получение записей: авторы книг +$authors = $database->table('authors') + ->where('active', 1); + +// вывод (автоматически генерирует только 2 оптимизированных запроса) +foreach ($authors as $author) { + $books = $author->related('books') + ->order('published_at DESC'); + + echo "Автор $author->name написал {$books->count()} книг:\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +Подход Explorer автоматически генерирует и оптимизирует SQL-запросы. В приведенном примере SQL-подход генерирует N+1 запросов (один для авторов, а затем по одному для книг каждого автора), в то время как Explorer автоматически оптимизирует запросы и выполняет только два — один для авторов и один для всех их книг. + +Оба подхода можно свободно комбинировать в приложении по мере необходимости. + + +Подключение и конфигурация +========================== + +Для подключения к базе данных достаточно создать экземпляр класса [api:Nette\Database\Connection]: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password); +``` + +Параметр `$dsn` (data source name) такой же, [как используется в PDO |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], например, `host=127.0.0.1;dbname=test`. В случае сбоя будет выброшено исключение `Nette\Database\ConnectionException`. + +Однако более удобный способ предлагает [конфигурация приложения |configuration], куда достаточно добавить секцию `database`, и будут созданы необходимые объекты, а также панель базы данных в баре [Tracy |tracy:] . + +```neon +database: + dsn: 'mysql:host=127.0.0.1;dbname=test' + user: root + password: password +``` + +Затем объект соединения [можно получить как сервис из DI-контейнера |dependency-injection:passing-dependencies], например: + +```php +class Model +{ + public function __construct( + // или Nette\Database\Explorer + private Nette\Database\Connection $database, + ) { + } +} +``` + +Больше информации о [конфигурации базы данных|configuration]. + + +Ручное создание Explorer +------------------------ + +Если вы не используете DI-контейнер Nette, вы можете создать экземпляр `Nette\Database\Explorer` вручную: + +```php +// подключение к базе данных +$connection = new Nette\Database\Connection('mysql:host=127.0.0.1;dbname=mydatabase', 'user', 'password'); +// хранилище для кеша, реализует Nette\Caching\Storage, например: +$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp/dir'); +// отвечает за рефлексию структуры базы данных +$structure = new Nette\Database\Structure($connection, $storage); +// определяет правила для отображения имен таблиц, столбцов и внешних ключей +$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); +$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +``` + + +Управление соединением +====================== + +При создании объекта `Connection` подключение происходит автоматически. Если вы хотите отложить подключение, используйте ленивый режим — его можно включить в [конфигурации|configuration], установив `lazy: true`, или следующим образом: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password, ['lazy' => true]); +``` + +Для управления соединением используйте методы `connect()`, `disconnect()` и `reconnect()`. +- `connect()` создает соединение, если оно еще не существует, и может вызвать исключение `Nette\Database\ConnectionException`. +- `disconnect()` отключает текущее соединение с базой данных. +- `reconnect()` выполняет отключение и последующее повторное подключение к базе данных. Этот метод также может вызвать исключение `Nette\Database\ConnectionException`. + +Кроме того, вы можете отслеживать события, связанные с подключением, с помощью события `onConnect` — это массив обратных вызовов, которые вызываются после установления соединения с базой данных. + +```php +// выполняется после подключения к базе данных +$database->onConnect[] = function($database) { + echo "Подключено к базе данных"; +}; +``` + + +Tracy Debug Bar +=============== + +Если вы используете [Tracy |tracy:], панель Database в Debug Bar активируется автоматически. Она отображает все выполненные запросы, их параметры, время выполнения и место в коде, где они были вызваны. + +[* db-panel.webp *] diff --git a/database/ru/mapping.texy b/database/ru/mapping.texy new file mode 100644 index 0000000000..5a8b3c76bf --- /dev/null +++ b/database/ru/mapping.texy @@ -0,0 +1,55 @@ +Преобразование типов +******************** + +.[perex] +Nette Database автоматически преобразует значения, возвращаемые из базы данных, в соответствующие типы PHP. + + +Дата и время +------------ + +Временные данные преобразуются в объекты `Nette\Utils\DateTime`. Если вы хотите, чтобы временные данные преобразовывались в неизменяемые объекты `Nette\Database\DateTime`, установите в [конфигурации|configuration] опцию `newDateTime` в `true`. + +```php +$row = $database->fetch('SELECT created_at FROM articles'); +echo $row->created_at instanceof DateTime; // true +echo $row->created_at->format('j. n. Y'); +``` + +В случае MySQL тип данных `TIME` преобразуется в объекты `DateInterval`. + + +Логические значения +------------------- + +Логические значения автоматически преобразуются в `true` или `false`. В MySQL преобразуется `TINYINT(1)`, если мы установим в [конфигурации|configuration] `convertBoolean: true`. + +```php +$row = $database->fetch('SELECT is_published FROM articles'); +echo gettype($row->is_published); // 'boolean' +``` + + +Числовые значения +----------------- + +Числовые значения преобразуются в `int` или `float` в зависимости от типа столбца в базе данных: + +```php +$row = $database->fetch('SELECT id, price FROM products'); +echo gettype($row->id); // integer +echo gettype($row->price); // float +``` + + +Пользовательская нормализация +----------------------------- + +С помощью метода `setRowNormalizer(?callable $normalizer)` вы можете установить собственную функцию для преобразования строк из базы данных. Это полезно, например, для автоматического преобразования типов данных. + +```php +$database->setRowNormalizer(function(array $row, ResultSet $resultSet): array { + // здесь происходит преобразование типов + return $row; +}); +``` diff --git a/database/ru/reflection.texy b/database/ru/reflection.texy new file mode 100644 index 0000000000..6a13f25280 --- /dev/null +++ b/database/ru/reflection.texy @@ -0,0 +1,125 @@ +Рефлексия структуры +******************* + +.{data-version:3.2.1} +Nette Database предоставляет инструменты для интроспекции структуры базы данных с помощью класса [api:Nette\Database\Reflection]. Он позволяет получать информацию о таблицах, столбцах, индексах и внешних ключах. Рефлексию можно использовать для генерации схем, создания гибких приложений, работающих с базой данных, или общих инструментов для работы с базами данных. + +Объект рефлексии можно получить из экземпляра подключения к базе данных: + +```php +$reflection = $database->getReflection(); +``` + + +Получение таблиц +---------------- + +Свойство только для чтения `$reflection->tables` содержит ассоциативный массив всех таблиц в базе данных: + +```php +// Вывод имен всех таблиц +foreach ($reflection->tables as $name => $table) { + echo $name . "\n"; +} +``` + +Доступны еще два метода: + +```php +// Проверка существования таблицы +if ($reflection->hasTable('users')) { + echo "Таблица users существует"; +} + +// Возвращает объект таблицы; если не существует, выбрасывает исключение +$table = $reflection->getTable('users'); +``` + + +Информация о таблице +-------------------- + +Таблица представлена объектом [Table|api:Nette\Database\Reflection\Table], который предоставляет следующие свойства только для чтения: + +- `$name: string` – имя таблицы +- `$view: bool` – является ли представлением +- `$fullName: ?string` – полное имя таблицы, включая схему (БД) (если существует) +- `$columns: array<string, Column>` – ассоциативный массив столбцов таблицы +- `$indexes: Index[]` – массив индексов таблицы +- `$primaryKey: ?Index` – первичный ключ таблицы или null +- `$foreignKeys: ForeignKey[]` – массив внешних ключей таблицы + + +Столбцы +------- + +Свойство `columns` таблицы предоставляет ассоциативный массив столбцов, где ключом является имя столбца, а значением — экземпляр [Column|api:Nette\Database\Reflection\Column] со следующими свойствами: + +- `$name: string` – имя столбца +- `$table: ?Table` – ссылка на таблицу столбца +- `$nativeType: string` – нативный тип данных базы данных +- `$size: ?int` – размер/длина типа +- `$nullable: bool` – может ли столбец содержать NULL +- `$default: mixed` – значение по умолчанию столбца +- `$autoIncrement: bool` – является ли столбец автоинкрементным +- `$primary: bool` – является ли частью первичного ключа +- `$vendor: array` – дополнительные метаданные, специфичные для данной системы баз данных + +```php +foreach ($table->columns as $name => $column) { + echo "Столбец: $name\n"; + echo "Тип: {$column->nativeType}\n"; + echo "Nullable: " . ($column->nullable ? 'Да' : 'Нет') . "\n"; +} +``` + + +Индексы +------- + +Свойство `indexes` таблицы предоставляет массив индексов, где каждый индекс является экземпляром [Index|api:Nette\Database\Reflection\Index] со следующими свойствами: + +- `$columns: Column[]` – массив столбцов, составляющих индекс +- `$unique: bool` – является ли индекс уникальным +- `$primary: bool` – является ли первичным ключом +- `$name: ?string` – имя индекса + +Первичный ключ таблицы можно получить с помощью свойства `primaryKey`, которое возвращает либо объект `Index`, либо `null` в случае, если таблица не имеет первичного ключа. + +```php +// Вывод индексов +foreach ($table->indexes as $index) { + $columns = implode(', ', array_map(fn($col) => $col->name, $index->columns)); + echo "Индекс" . ($index->name ? " {$index->name}" : '') . ":\n"; + echo " Столбцы: $columns\n"; + echo " Unique: " . ($index->unique ? 'Да' : 'Нет') . "\n"; +} + +// Вывод первичного ключа +if ($primaryKey = $table->primaryKey) { + $columns = implode(', ', array_map(fn($col) => $col->name, $primaryKey->columns)); + echo "Первичный ключ: $columns\n"; +} +``` + + +Внешние ключи +------------- + +Свойство `foreignKeys` таблицы предоставляет массив внешних ключей, где каждый внешний ключ является экземпляром [ForeignKey|api:Nette\Database\Reflection\ForeignKey] со следующими свойствами: + +- `$foreignTable: Table` – таблица, на которую ссылается ключ +- `$localColumns: Column[]` – массив локальных столбцов +- `$foreignColumns: Column[]` – массив столбцов, на которые ссылается ключ +- `$name: ?string` – имя внешнего ключа + +```php +// Вывод внешних ключей +foreach ($table->foreignKeys as $fk) { + $localCols = implode(', ', array_map(fn($col) => $col->name, $fk->localColumns)); + $foreignCols = implode(', ', array_map(fn($col) => $col->name, $fk->foreignColumns)); + + echo "FK" . ($fk->name ? " {$fk->name}" : '') . ":\n"; + echo " $localCols -> {$fk->foreignTable->name}($foreignCols)\n"; +} +``` diff --git a/database/ru/security.texy b/database/ru/security.texy new file mode 100644 index 0000000000..3178bed6e0 --- /dev/null +++ b/database/ru/security.texy @@ -0,0 +1,185 @@ +Риски безопасности +****************** + +<div class=perex> + +База данных часто содержит конфиденциальные данные и позволяет выполнять опасные операции. Для безопасной работы с Nette Database ключевыми являются: + +- Понимание разницы между безопасным и небезопасным API +- Использование параметризованных запросов +- Правильная валидация входных данных + +</div> + + +Что такое SQL Injection? +======================== + +SQL Injection — это самый серьезный риск безопасности при работе с базой данных. Он возникает, когда необработанные входные данные от пользователя становятся частью SQL-запроса. Злоумышленник может внедрить собственные SQL-команды и тем самым: +- Получить несанкционированный доступ к данным +- Изменить или удалить данные в базе данных +- Обойти аутентификацию + +```php +// ❌ НЕБЕЗОПАСНЫЙ КОД - уязвимый для SQL-инъекций +$database->query("SELECT * FROM users WHERE name = '$_GET[name]'"); + +// Злоумышленник может ввести, например, значение: ' OR '1'='1 +// Результирующий запрос будет: SELECT * FROM users WHERE name = '' OR '1'='1' +// Что вернет всех пользователей +``` + +То же самое относится и к Database Explorer: + +```php +// ❌ НЕБЕЗОПАСНЫЙ КОД - уязвимый для SQL-инъекций +$table->where('name = ' . $_GET['name']); +$table->where("name = '$_GET[name]'"); +``` + + +Параметризованные запросы +========================= + +Основной защитой от SQL Injection являются параметризованные запросы. Nette Database предлагает несколько способов их использования. + +Самый простой способ — использовать **заполнители в виде вопросительных знаков**: + +```php +// ✅ Безопасный параметризованный запрос +$database->query('SELECT * FROM users WHERE name = ?', $name); + +// ✅ Безопасное условие в Explorer +$table->where('name = ?', $name); +``` + +Это относится ко всем другим методам в [Database Explorer|explorer], которые позволяют вставлять выражения с заполнителями в виде вопросительных знаков и параметрами. + +Для команд INSERT, UPDATE или условия WHERE мы можем передавать значения в массиве: + +```php +// ✅ Безопасная вставка INSERT +$database->query('INSERT INTO users', [ + 'name' => $name, + 'email' => $email, +]); + +// ✅ Безопасная вставка INSERT в Explorer +$table->insert([ + 'name' => $name, + 'email' => $email, +]); +``` + + +Валидация значений параметров +============================= + +Параметризованные запросы являются основой безопасной работы с базой данных. Однако значения, которые мы в них вставляем, должны проходить несколько уровней проверок: + + +Проверка типов +-------------- + +**Самое важное — обеспечить правильный тип данных параметров** — это необходимое условие для безопасного использования Nette Database. База данных предполагает, что все входные данные имеют правильный тип данных, соответствующий данному столбцу. + +Например, если бы `$name` в предыдущих примерах неожиданно оказался массивом вместо строки, Nette Database попыталась бы вставить все его элементы в SQL-запрос, что привело бы к ошибке. Поэтому **никогда не используйте** невалидированные данные из `$_GET`, `$_POST` или `$_COOKIE` непосредственно в запросах к базе данных. + + +Проверка формата +---------------- + +На втором уровне мы проверяем формат данных — например, находятся ли строки в кодировке UTF-8 и соответствует ли их длина определению столбца, или находятся ли числовые значения в допустимом диапазоне для данного типа данных столбца. + +На этом уровне валидации мы можем частично положиться и на саму базу данных — многие базы данных отклонят невалидные данные. Однако поведение может отличаться, некоторые могут молча обрезать длинные строки или усекать числа вне диапазона. + + +Проверка домена +--------------- + +Третий уровень представляют логические проверки, специфичные для вашего приложения. Например, проверка того, соответствуют ли значения из выпадающих списков предлагаемым вариантам, находятся ли числа в ожидаемом диапазоне (например, возраст 0–150 лет) или имеют ли смысл взаимные зависимости между значениями. + + +Рекомендуемые способы валидации +------------------------------- + +- Используйте [Nette Forms|forms:], которые автоматически обеспечивают правильную валидацию всех входных данных +- Используйте [Presenters|application:] и указывайте типы данных для параметров в методах `action*()` и `render*()` +- Или реализуйте собственный слой валидации с помощью стандартных инструментов PHP, таких как `filter_var()` + + +Безопасная работа со столбцами +============================== + +В предыдущем разделе мы показали, как правильно валидировать значения параметров. Однако при использовании массивов в SQL-запросах необходимо уделять такое же внимание и их ключам. + +```php +// ❌ НЕБЕЗОПАСНЫЙ КОД - ключи в массиве не обработаны +$database->query('INSERT INTO users', $_POST); +``` + +В командах INSERT и UPDATE это серьезная ошибка безопасности — злоумышленник может вставить или изменить любой столбец в базе данных. Он мог бы, например, установить `is_admin = 1` или вставить произвольные данные в конфиденциальные столбцы (так называемая Уязвимость массового присваивания). + +В условиях WHERE это еще опаснее, так как они могут содержать операторы: + +```php +// ❌ НЕБЕЗОПАСНЫЙ КОД - ключи в массиве не обработаны +$_POST['salary >'] = 100000; +$database->query('SELECT * FROM users WHERE', $_POST); +// выполняет запрос WHERE (`salary` > 100000) +``` + +Злоумышленник может использовать этот подход для систематического выяснения зарплат сотрудников. Например, он начнет с запроса зарплат выше 100 000, затем ниже 50 000 и, постепенно сужая диапазон, сможет раскрыть приблизительные зарплаты всех сотрудников. Этот тип атаки называется Перечисление SQL. + +Методы `where()` и `whereOr()` [гораздо более гибки |explorer#where] и поддерживают в ключах и значениях SQL-выражения, включая операторы и функции. Это дает злоумышленнику возможность выполнить SQL-инъекцию: + +```php +// ❌ НЕБЕЗОПАСНЫЙ КОД - злоумышленник может внедрить собственный SQL +$_POST = ['0) UNION SELECT name, salary FROM users WHERE (1']; +$table->where($_POST); +// выполняет запрос WHERE (0) UNION SELECT name, salary FROM users WHERE (1) +``` + +Эта атака завершает исходное условие с помощью `0)`, присоединяет собственный `SELECT` с помощью `UNION` для получения конфиденциальных данных из таблицы `users` и закрывает синтаксически правильный запрос с помощью `WHERE (1)`. + + +Белый список столбцов +--------------------- + +Для безопасной работы с именами столбцов нам нужен механизм, который гарантирует, что пользователь может работать только с разрешенными столбцами и не может добавить свои собственные. Мы могли бы попытаться обнаружить и заблокировать опасные имена столбцов (черный список), но этот подход ненадежен — злоумышленник всегда может придумать новый способ записи опасного имени столбца, который мы не предусмотрели. + +Поэтому гораздо безопаснее обратить логику и определить явный список разрешенных столбцов (белый список): + +```php +// Столбцы, которые пользователь может редактировать +$allowedColumns = ['name', 'email', 'active']; + +// Удаляем все недопустимые столбцы из входных данных +$filteredData = array_intersect_key($userData, array_flip($allowedColumns)); + +// ✅ Теперь можно безопасно использовать в запросах, например: +$database->query('INSERT INTO users', $filteredData); +$table->update($filteredData); +$table->where($filteredData); +``` + + +Динамические идентификаторы +=========================== + +Для динамических имен таблиц и столбцов используйте заполнитель `?name`. Он обеспечит правильное экранирование идентификаторов в соответствии с синтаксисом данной базы данных (например, с помощью обратных кавычек в MySQL): + +```php +// ✅ Безопасное использование доверенных идентификаторов +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name', $column, $table); +// Результат в MySQL: SELECT `name` FROM `users` +``` + +Важно: символ `?name` используйте только для доверенных значений, определенных в коде приложения. Для значений от пользователя снова используйте [белый список |#Белый список столбцов]. В противном случае вы подвергаетесь рискам безопасности: + +```php +// ❌ НЕБЕЗОПАСНО - никогда не используйте ввод пользователя +$database->query('SELECT ?name FROM users', $_GET['column']); +``` diff --git a/database/ru/sql-way.texy b/database/ru/sql-way.texy new file mode 100644 index 0000000000..4da96b74e6 --- /dev/null +++ b/database/ru/sql-way.texy @@ -0,0 +1,513 @@ +SQL-подход +********** + +.[perex] +Nette Database предлагает два пути: вы можете писать SQL-запросы сами (SQL-подход) или позволить генерировать их автоматически (см. [Explorer |explorer]). SQL-подход дает вам полный контроль над запросами и при этом обеспечивает их безопасное построение. + +.[note] +Подробности о подключении и конфигурации базы данных можно найти в главе [Подключение и конфигурация |guide#Подключение и конфигурация]. + + +Базовые запросы +=============== + +Для выполнения запросов к базе данных используется метод `query()`. Он возвращает объект [ResultSet |api:Nette\Database\ResultSet], который представляет результат запроса. В случае сбоя метод [выбрасывает исключение |exceptions]. Результат запроса можно перебирать с помощью цикла `foreach` или использовать одну из [вспомогательных функций |#Получение данных]. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; +} +``` + +Для безопасной вставки значений в SQL-запросы мы используем параметризованные запросы. Nette Database делает их максимально простыми — достаточно добавить запятую и значение после SQL-запроса: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +При наличии нескольких параметров у вас есть два варианта записи. Вы можете либо «перемежать» SQL-запрос параметрами: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age); +``` + +Либо сначала написать весь SQL-запрос, а затем добавить все параметры: + +```php +$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age); +``` + + +Защита от SQL Injection +======================= + +Почему важно использовать параметризованные запросы? Потому что они защищают вас от атаки под названием SQL Injection, при которой злоумышленник мог бы подставить собственные SQL-команды и тем самым получить или повредить данные в базе данных. + +.[warning] +**Никогда не вставляйте переменные непосредственно в SQL-запрос!** Всегда используйте параметризованные запросы, которые защитят вас от SQL Injection. + +```php +// ❌ ОПАСНЫЙ КОД - уязвимый для SQL-инъекций +$database->query("SELECT * FROM users WHERE name = '$name'"); + +// ✅ Безопасный параметризованный запрос +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Ознакомьтесь с [возможными рисками безопасности |security]. + + +Техники запросов +================ + + +Условия WHERE +------------- + +Условия WHERE можно записать как ассоциативный массив, где ключи — это имена столбцов, а значения — данные для сравнения. Nette Database автоматически выберет наиболее подходящий SQL-оператор в зависимости от типа значения. + +```php +$database->query('SELECT * FROM users WHERE', [ + 'name' => 'John', + 'active' => true, +]); +// WHERE `name` = 'John' AND `active` = 1 +``` + +В ключе также можно явно указать оператор для сравнения: + +```php +$database->query('SELECT * FROM users WHERE', [ + 'age >' => 25, // использует оператор > + 'name LIKE' => '%John%', // использует оператор LIKE + 'email NOT LIKE' => '%example.com%', // использует оператор NOT LIKE +]); +// WHERE `age` > 25 AND `name` LIKE '%John%' AND `email` NOT LIKE '%example.com%' +``` + +Nette автоматически обрабатывает особые случаи, такие как значения `null` или массивы. + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name' => 'Laptop', // использует оператор = + 'category_id' => [1, 2, 3], // использует IN + 'description' => null, // использует IS NULL +]); +// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL +``` + +Для отрицательных условий используйте оператор `NOT`: + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name NOT' => 'Laptop', // использует оператор <> + 'category_id NOT' => [1, 2, 3], // использует NOT IN + 'description NOT' => null, // использует IS NOT NULL + 'id' => [], // пропускается +]); +// WHERE `name` <> 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL +``` + +Для объединения условий используется оператор `AND`. Это можно изменить с помощью [плейсхолдера ?or |#Подсказки для построения SQL]. + + +Правила ORDER BY +---------------- + +Сортировку `ORDER BY` можно записать с помощью массива. В ключах указываем столбцы, а значением будет boolean, определяющий, сортировать ли по возрастанию: + +```php +$database->query('SELECT id FROM author ORDER BY', [ + 'id' => true, // по возрастанию + 'name' => false, // по убыванию +]); +// SELECT id FROM author ORDER BY `id`, `name` DESC +``` + + +Вставка данных (INSERT) +----------------------- + +Для вставки записей используется SQL-команда `INSERT`. + +```php +$values = [ + 'name' => 'John Doe', + 'email' => 'john@example.com', +]; +$database->query('INSERT INTO users ?', $values); +$userId = $database->getInsertId(); +``` + +Метод `getInsertId()` возвращает ID последней вставленной строки. В некоторых базах данных (например, PostgreSQL) необходимо в качестве параметра указать имя последовательности, из которой должен генерироваться ID, с помощью `$database->getInsertId($sequenceId)`. + +В качестве параметров можно передавать и [#специальные значения] такие как файлы, объекты DateTime или перечисляемые типы. + +Вставка нескольких записей одновременно: + +```php +$database->query('INSERT INTO users ?', [ + ['name' => 'User 1', 'email' => 'user1@mail.com'], + ['name' => 'User 2', 'email' => 'user2@mail.com'], +]); +``` + +Множественная вставка INSERT намного быстрее, так как выполняется один запрос к базе данных вместо множества отдельных. + +**Предупреждение о безопасности:** Никогда не используйте в качестве `$values` невалидированные данные. Ознакомьтесь с [возможными рисками |security#Безопасная работа со столбцами]. + + +Обновление данных (UPDATE) +-------------------------- + +Для обновления записей используется SQL-команда `UPDATE`. + +```php +// Обновление одной записи +$values = [ + 'name' => 'John Smith', +]; +$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1); +``` + +Количество затронутых строк возвращает `$result->getRowCount()`. + +Для UPDATE можно использовать операторы `+=` и `-=`: + +```php +$database->query('UPDATE users SET ? WHERE id = ?', [ + 'login_count+=' => 1, // инкремент login_count +], 1); +``` + +Пример вставки или обновления записи, если она уже существует. Используем технику `ON DUPLICATE KEY UPDATE`: + +```php +$values = [ + 'name' => $name, + 'year' => $year, +]; +$database->query('INSERT INTO users ? ON DUPLICATE KEY UPDATE ?', + $values + ['id' => $id], + $values, +); +// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) +// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 +``` + +Обратите внимание, что Nette Database распознает, в каком контексте SQL-команды вставляется параметр с массивом, и в соответствии с этим составляет из него SQL-код. Так, из первого массива он составил `(id, name, year) VALUES (123, 'Jim', 1978)`, в то время как второй преобразовал в вид `name = 'Jim', year = 1978`. Подробнее об этом мы поговорим в разделе [#Подсказки для построения SQL]. + + +Удаление данных (DELETE) +------------------------ + +Для удаления записей используется SQL-команда `DELETE`. Пример с получением количества удаленных строк: + +```php +$count = $database->query('DELETE FROM users WHERE id = ?', 1) + ->getRowCount(); +``` + + +Подсказки для построения SQL +---------------------------- + +Подсказка — это специальный плейсхолдер в SQL-запросе, который указывает, как значение параметра должно быть преобразовано в SQL-выражение: + +| Подсказка | Описание | Используется автоматически +|-----------|-------------------------------------------------|----------------------------- +| `?name` | используется для вставки имени таблицы или столбца | - +| `?values` | генерирует `(key, ...) VALUES (value, ...)` | `INSERT ... ?`, `REPLACE ... ?` +| `?set` | генерирует присваивание `key = value, ...` | `SET ?`, `KEY UPDATE ?` +| `?and` | объединяет условия в массиве оператором `AND` | `WHERE ?`, `HAVING ?` +| `?or` | объединяет условия в массиве оператором `OR` | - +| `?order` | генерирует условие `ORDER BY` | `ORDER BY ?`, `GROUP BY ?` + +Для динамической вставки имен таблиц и столбцов в запрос используется плейсхолдер `?name`. Nette Database позаботится о правильной обработке идентификаторов в соответствии с конвенциями данной базы данных (например, заключение в обратные кавычки в MySQL). + +```php +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table); +// SELECT `name` FROM `users` WHERE id = 1 (в MySQL) +``` + +**Предупреждение:** символ `?name` используйте только для имен таблиц и столбцов из валидированных входных данных, иначе вы подвергаетесь [риску безопасности |security#Динамические идентификаторы]. + +Остальные подсказки обычно указывать не нужно, так как Nette использует умное автоопределение при составлении SQL-запроса (см. третий столбец таблицы). Но вы можете использовать его, например, в ситуации, когда хотите объединить условия с помощью `OR` вместо `AND`: + +```php +$database->query('SELECT * FROM users WHERE ?or', [ + 'name' => 'John', + 'email' => 'john@example.com', +]); +// SELECT * FROM users WHERE `name` = 'John' OR `email` = 'john@example.com' +``` + + +Специальные значения +-------------------- + +Кроме обычных скалярных типов (string, int, bool), в качестве параметров можно передавать и специальные значения: + +- файлы: `fopen('image.gif', 'r')` вставляет бинарное содержимое файла +- дата и время: объекты `DateTime` преобразуются в формат базы данных +- перечисляемые типы: экземпляры `enum` преобразуются в их значение +- SQL-литералы: созданные с помощью `Connection::literal('NOW()')` вставляются непосредственно в запрос + +```php +$database->query('INSERT INTO articles ?', [ + 'title' => 'My Article', + 'published_at' => new DateTime, + 'content' => fopen('image.png', 'r'), + 'state' => Status::Draft, +]); +``` + +В базах данных, которые не имеют нативной поддержки типа данных `datetime` (например, SQLite и Oracle), `DateTime` преобразуется в значение, указанное в [конфигурации базы данных |configuration] в элементе `formatDateTime` (значение по умолчанию — `U` - unix timestamp). + + +SQL-литералы +------------ + +В некоторых случаях необходимо указать в качестве значения непосредственно SQL-код, который, однако, не должен восприниматься как строка и экранироваться. Для этого служат объекты класса `Nette\Database\SqlLiteral`. Их создает метод `Connection::literal()`. + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + 'year >' => $database::literal('YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) +``` + +Или альтернативно: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) +``` + +SQL-литералы могут содержать параметры: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > ? AND year < ?', $min, $max), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) +``` + +Благодаря чему мы можем создавать интересные комбинации: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('?or', [ + 'active' => true, + 'role' => $role, + ]), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') +``` + + +Получение данных +================ + + +Сокращения для запросов SELECT +------------------------------ + +Для упрощения извлечения данных `Connection` предлагает несколько сокращений, которые объединяют вызов `query()` с последующим `fetch*()`. Эти методы принимают те же параметры, что и `query()`, то есть SQL-запрос и необязательные параметры. Полное описание методов `fetch*()` вы найдете [ниже |#fetch]. + +| `fetch($sql, ...$params): ?Row` | Выполняет запрос и возвращает первую строку как объект `Row` +| `fetchAll($sql, ...$params): array` | Выполняет запрос и возвращает все строки как массив объектов `Row` +| `fetchPairs($sql, ...$params): array` | Выполняет запрос и возвращает ассоциативный массив, где первый столбец представляет ключ, а второй — значение +| `fetchField($sql, ...$params): mixed` | Выполняет запрос и возвращает значение первого поля из первой строки +| `fetchList($sql, ...$params): ?array` | Выполняет запрос и возвращает первую строку как индексированный массив + +Пример: + +```php +// fetchField() - возвращает значение первой ячейки +$count = $database->query('SELECT COUNT(*) FROM articles') + ->fetchField(); +``` + + +`foreach` - итерация по строкам +------------------------------- + +После выполнения запроса возвращается объект [ResultSet |api:Nette\Database\ResultSet], который позволяет перебирать результаты несколькими способами. Самый простой способ выполнить запрос и получить строки — это итерация в цикле `foreach`. Этот способ наиболее экономичен по памяти, так как возвращает данные постепенно и не сохраняет их все сразу в памяти. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; + // ... +} +``` + +.[note] +`ResultSet` можно итерировать только один раз. Если вам нужно итерировать повторно, сначала необходимо загрузить данные в массив, например, с помощью метода `fetchAll()`. + + +fetch(): ?Row .[method] +----------------------- + +Возвращает строку как объект `Row`. Если больше нет строк, возвращает `null`. Перемещает внутренний указатель на следующую строку. + +```php +$result = $database->query('SELECT * FROM users'); +$row = $result->fetch(); // считывает первую строку +if ($row) { + echo $row->name; +} +``` + + +fetchAll(): array .[method] +--------------------------- + +Возвращает все оставшиеся строки из `ResultSet` как массив объектов `Row`. + +```php +$result = $database->query('SELECT * FROM users'); +$rows = $result->fetchAll(); // считывает все строки +foreach ($rows as $row) { + echo $row->name; +} +``` + + +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Возвращает результаты как ассоциативный массив. Первый аргумент указывает имя столбца, который будет использоваться в качестве ключа в массиве, второй аргумент указывает имя столбца, который будет использоваться в качестве значения: + +```php +$result = $database->query('SELECT id, name FROM users'); +$names = $result->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] +``` + +Если указать только первый параметр, значением будет вся строка, то есть объект `Row`: + +```php +$rows = $result->fetchPairs('id'); +// [1 => Row(id: 1, name: 'John'), 2 => Row(id: 2, name: 'Jane'), ...] +``` + +В случае дублирующихся ключей используется значение из последней строки. При использовании `null` в качестве ключа массив будет индексирован численно с нуля (тогда коллизий не происходит): + +```php +$names = $result->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] +``` + + +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Альтернативно, в качестве параметра можно указать callback, который будет для каждой строки возвращать либо само значение, либо пару ключ-значение. + +```php +$result = $database->query('SELECT * FROM users'); +$items = $result->fetchPairs(fn($row) => "$row->id - $row->name"); +// ['1 - John', '2 - Jane', ...] + +// Callback также может возвращать массив с парой ключ & значение: +$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]); +// ['John' => 46, 'Jane' => 21, ...] +``` + + +fetchField(): mixed .[method] +----------------------------- + +Возвращает значение первого поля из текущей строки. Если больше нет строк, возвращает `null`. Перемещает внутренний указатель на следующую строку. + +```php +$result = $database->query('SELECT name FROM users'); +$name = $result->fetchField(); // считывает имя из первой строки +``` + + +fetchList(): ?array .[method] +----------------------------- + +Возвращает строку как индексированный массив. Если больше нет строк, возвращает `null`. Перемещает внутренний указатель на следующую строку. + +```php +$result = $database->query('SELECT name, email FROM users'); +$row = $result->fetchList(); // ['John', 'john@example.com'] +``` + + +getRowCount(): ?int .[method] +----------------------------- + +Возвращает количество затронутых строк последним запросом `UPDATE` или `DELETE`. Для `SELECT` это количество возвращенных строк, но оно может быть неизвестно — в таком случае метод вернет `null`. + + +getColumnCount(): ?int .[method] +-------------------------------- + +Возвращает количество столбцов в `ResultSet`. + + +Информация о запросах +===================== + +Для отладочных целей можно получить информацию о последнем выполненном запросе: + +```php +echo $database->getLastQueryString(); // выводит SQL-запрос + +$result = $database->query('SELECT * FROM articles'); +echo $result->getQueryString(); // выводит SQL-запрос +echo $result->getTime(); // выводит время выполнения в секундах +``` + +Для отображения результата в виде HTML-таблицы можно использовать: + +```php +$result = $database->query('SELECT * FROM articles'); +$result->dump(); +``` + +ResultSet предлагает информацию о типах столбцов: + +```php +$result = $database->query('SELECT * FROM articles'); +$types = $result->getColumnTypes(); + +foreach ($types as $column => $type) { + echo "$column имеет тип $type->type"; // например, 'id имеет тип int' +} +``` + + +Логирование запросов +-------------------- + +Мы можем реализовать собственное логирование запросов. Событие `onQuery` — это массив callback-функций, которые вызываются после каждого выполненного запроса: + +```php +$database->onQuery[] = function ($database, $result) use ($logger) { + $logger->info('Запрос: ' . $result->getQueryString()); + $logger->info('Время: ' . $result->getTime()); + + if ($result->getRowCount() > 1000) { + $logger->warning('Большой набор результатов: ' . $result->getRowCount() . ' строк'); + } +}; +``` diff --git a/database/ru/transactions.texy b/database/ru/transactions.texy new file mode 100644 index 0000000000..67eacd4dff --- /dev/null +++ b/database/ru/transactions.texy @@ -0,0 +1,43 @@ +Транзакции +********** + +.[perex] +Транзакции гарантируют, что либо все операции в рамках транзакции будут выполнены, либо ни одна из них не будет выполнена. Они полезны для обеспечения согласованности данных при более сложных операциях. + +Самый простой способ использования транзакций выглядит следующим образом: + +```php +$database->beginTransaction(); +try { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); + $database->commit(); +} catch (\Exception $e) { + $database->rollBack(); + throw $e; +} +``` + +Гораздо элегантнее то же самое можно записать с помощью метода `transaction()`. В качестве параметра он принимает колбэк, который выполняется в транзакции. Если колбэк выполняется без исключения, транзакция автоматически подтверждается. Если возникает исключение, транзакция отменяется (rollback), а исключение распространяется дальше. + +```php +$database->transaction(function ($database) use ($id) { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); +}); +``` + +Метод `transaction()` также может возвращать значения: + +```php +$count = $database->transaction(function ($database) { + $result = $database->query('UPDATE users SET active = ?', true); + return $result->getRowCount(); // возвращает количество обновленных строк +}); +``` diff --git a/database/sl/@home.texy b/database/sl/@home.texy index ed55e6412a..5c0ffe069e 100644 --- a/database/sl/@home.texy +++ b/database/sl/@home.texy @@ -1,20 +1,21 @@ -Podprti strežniki -================= +Podprte podatkovne baze +======================= -Podprti so ti strežniki zbirke podatkov: +Nette podpira naslednje podatkovne baze: -|* Strežnik zbirke podatkov |* Ime DSN |* Podpora jedra |* Podpora raziskovalca -| MySQL (>= 5.1) | mysql | DA | DA -| PostgreSQL (>= 9.0) | pgsql | DA | DA -| Sqlite 3 (>= 3.8) | sqlite | DA | DA | DA -| Oracle | oci | DA | - -| MS SQL (PDO_SQLSRV) | sqlsrv | DA | DA -| MS SQL (PDO_DBLIB) | mssql | DA | - -| ODBC | odbc | DA | - +|* Strežnik podatkovne baze |* Ime DSN |* Podpora v Core |* Podpora v Explorer +| MySQL (>= 5.1) | mysql | DA | DA +| PostgreSQL (>= 9.0) | pgsql | DA | DA +| Sqlite 3 (>= 3.8) | sqlite | DA | DA +| Oracle | oci | DA | - +| MS SQL (PDO_SQLSRV) | sqlsrv | DA | DA +| MS SQL (PDO_DBLIB) | mssql | DA | - +| ODBC | odbc | DA | - -{{title: Nette Database}} -{{description: Nette Database bistveno poenostavi pridobivanje podatkov iz zbirke podatkov brez pisanja poizvedb SQL. Uporablja učinkovite poizvedbe in ne posreduje nepotrebnih podatkov.}} + +{{maintitle: Nette Database - awesome database layer for PHP}} +{{description: Nette Database bistveno poenostavlja pridobivanje podatkov iz podatkovne baze brez potrebe po pisanju SQL poizvedb. Postavlja učinkovite poizvedbe in ne prenaša nepotrebnih podatkov.}} diff --git a/database/sl/@left-menu.texy b/database/sl/@left-menu.texy index fc84f30bde..280bbea034 100644 --- a/database/sl/@left-menu.texy +++ b/database/sl/@left-menu.texy @@ -1,5 +1,12 @@ -Podatkovna zbirka -***************** -- [Jedro |Core] -- [Raziskovalec |Explorer] -- [Konfiguracija |Configuration] +Nette Database +************** +- [Uvod |guide] +- [SQL pristop |sql way] +- [Explorer |Explorer] +- [Transakcije |transactions] +- [Izjeme |exceptions] +- [Refleksija |reflection] +- [Preslikava |mapping] +- [Konfiguracija |configuration] +- [Varnostna tveganja |security] +- [Nadgradnja |en:upgrading] diff --git a/database/sl/@meta.texy b/database/sl/@meta.texy new file mode 100644 index 0000000000..724324bee5 --- /dev/null +++ b/database/sl/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokumentacija}} diff --git a/database/sl/configuration.texy b/database/sl/configuration.texy index ed177eb5ef..1fd404c6d1 100644 --- a/database/sl/configuration.texy +++ b/database/sl/configuration.texy @@ -1,61 +1,67 @@ -Konfiguriranje podatkovne zbirke -******************************** +Konfiguracija podatkovne baze +***************************** .[perex] -Pregled možnosti konfiguracije za podatkovno zbirko Nette. +Pregled konfiguracijskih možnosti za Nette Database. -Če ne uporabljate celotnega ogrodja, temveč samo to knjižnico, preberite, [kako naložiti konfiguracijo |bootstrap:]. +Če ne uporabljate celotnega ogrodja, ampak samo to knjižnico, preberite, [kako naložiti konfiguracijo|bootstrap:]. -Posamezna povezava .[#toc-single-connection] --------------------------------------------- +Ena povezava +------------ -Konfiguracija ene povezave s podatkovno bazo: +Konfiguracija ene podatkovne povezave: ```neon database: - # DSN, samo obvezni ključ + # DSN, edini obvezni ključ dsn: "sqlite:%appDir%/Model/demo.db" user: ... password: ... ``` -Ustvari storitve tipa `Nette\Database\Connection` in `Nette\Database\Explorer` za sloj [Database Explorer |explorer]. Povezava s podatkovno zbirko se običajno posreduje s [samodejnim vodenjem |dependency-injection:autowiring], če to ni mogoče, uporabite imena storitev `@database.default.connection` oz. `@database.default.explorer`. +Ustvari storitvi `Nette\Database\Connection` in `Nette\Database\Explorer`, ki si jih običajno posredujemo z [autowiringom |dependency-injection:autowiring], ali pa s sklicem na [njihovo ime |#Storitve DI]. Druge nastavitve: ```neon database: - # prikazuje ploščo podatkovne baze v vrstici Tracy Bar? + # prikazati ploščo podatkovne baze v Tracy Bar? debugger: ... # (bool) privzeto je true - # prikaže poizvedbo EXPLAIN v Tracy Bar? + # prikazati EXPLAIN poizvedb v Tracy Bar? explain: ... # (bool) privzeto je true - # omogoči samodejno napeljavo za to povezavo? - autowired: ... # (bool) privzeto true za prvo povezavo + # dovoliti autowiring za to povezavo? + autowired: ... # (bool) privzeto je true pri prvi povezavi - # konvencije za tabele: odkrito, statično ali ime razreda + # konvencije tabel: discovered, static ali ime razreda conventions: discovered # (string) privzeto je 'discovered' options: - # da se poveže s podatkovno bazo samo, ko je to potrebno? - lazy: ... # (bool) privzeto false + # povezati se s podatkovno bazo šele, ko je potrebno? + lazy: ... # (bool) privzeto je false - # Razred gonilnika podatkovne zbirke PHP - driverClass: # (niz) + # PHP razred gonilnika podatkovne baze + driverClass: # (string) # samo MySQL: nastavi sql_mode - sqlmode: # (niz) + sqlmode: # (string) # samo MySQL: nastavi SET NAMES - charset: # (niz) privzeta vrednost je 'utf8mb4' ('utf8' pred v5.5.3) + charset: # (string) privzeto je 'utf8mb4' - # samo Oracle in SQLite: format datuma - formatDateTime: # (niz) privzeta vrednost je 'U' + # samo MySQL: pretvori TINYINT(1) v bool + convertBoolean: # (bool) privzeto je false + + # vrača stolpce z datumom kot nespremenljive objekte (od različice 3.2.1) + newDateTime: # (bool) privzeto je false + + # samo Oracle in SQLite: format za shranjevanje datuma + formatDateTime: # (string) privzeto je 'U' ``` -Ključ `options` lahko vsebuje druge možnosti, ki jih najdete v [dokumentaciji gonilnika PDO |https://www.php.net/manual/en/pdo.drivers.php], na primer: +V ključu `options` lahko navajate druge možnosti, ki jih najdete v [dokumentaciji gonilnikov PDO |https://www.php.net/manual/en/pdo.drivers.php], kot na primer: ```neon database: @@ -64,10 +70,10 @@ database: ``` -Več povezav .[#toc-multiple-connections] ----------------------------------------- +Več povezav +----------- -V konfiguraciji lahko opredelimo več povezav s podatkovno bazo, tako da jih razdelimo v poimenovane razdelke: +V konfiguraciji lahko definiramo tudi več podatkovnih povezav z razdelitvijo na poimenovane sekcije: ```neon database: @@ -80,9 +86,23 @@ database: dsn: 'sqlite::memory:' ``` -Vsaka opredeljena povezava ustvari storitve, ki v svojem imenu vključujejo ime odseka, tj. `@database.main.connection` in `@database.main.explorer` ter nadalje `@database.another.connection` in `@database.another.explorer`. +Autowiring je vklopljen samo pri storitvah iz prve sekcije. To lahko spremenite s pomočjo `autowired: false` ali `autowired: true`. + + +Storitve DI +----------- + +Te storitve se dodajo v DI vsebnik, kjer `###` predstavlja ime povezave: + +| Ime | Tip | Opis +|---------------------------------------------------------- +| `database.###.connection` | [api:Nette\Database\Connection] | povezava s podatkovno bazo +| `database.###.explorer` | [api:Nette\Database\Explorer] | [Database Explorer |explorer] + + +Če definiramo samo eno povezavo, bosta imeni storitev `database.default.connection` in `database.default.explorer`. Če definiramo več povezav kot v zgornjem primeru, bodo imena ustrezala sekcijam, tj. `database.main.connection`, `database.main.explorer` in naprej `database.another.connection` ter `database.another.explorer`. -Samodejno povezovanje je omogočeno samo za storitve iz prvega razdelka. To lahko spremenite s `autowired: false` ali `autowired: true`. Storitve, ki niso samodejno ožičene, se posredujejo po imenu: +Ne-autowirane storitve posredujemo eksplicitno s sklicem na njihovo ime: ```neon services: diff --git a/database/sl/core.texy b/database/sl/core.texy deleted file mode 100644 index 1f65ebf455..0000000000 --- a/database/sl/core.texy +++ /dev/null @@ -1,350 +0,0 @@ -Jedro zbirke podatkov -********************* - -.[perex] -Nette Database Core je abstraktni sloj podatkovne zbirke in zagotavlja osnovne funkcionalnosti. - - -Namestitev .[#toc-installation] -=============================== - -Prenesite in namestite paket s [programom Composer |best-practices:composer]: - -```shell -composer require nette/database -``` - - -Priključitev in konfiguracija .[#toc-connection-and-configuration] -================================================================== - -Če se želite povezati s podatkovno zbirko, preprosto ustvarite primerek razreda [api:Nette\Database\Connection]: - -```php -$database = new Nette\Database\Connection($dsn, $user, $password); -``` - -Parameter `$dsn` (ime vira podatkov) je [enak tistemu, ki ga uporablja PDO |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], npr. `host=127.0.0.1;dbname=test`. V primeru neuspeha se vrže `Nette\Database\ConnectionException`. - -Vendar pa bolj prefinjen način ponuja [konfiguracija aplikacije |configuration]. Dodamo razdelek `database` in ta ustvari zahtevane predmete in ploščo s podatkovno bazo v vrstici [Tracy |tracy:]. - -```neon -database: - dsn: 'mysql:host=127.0.0.1;dbname=test' - user: root - password: password -``` - -Objekt povezave [prejmemo kot storitev iz vsebnika DI |dependency-injection:passing-dependencies], na primer: - -```php -class Model -{ - // predajte Nette\Database\Explorer za delo s slojem Raziskovalec podatkovne baze - public function __construct( - private Nette\Database\Connection $database, - ) { - } -} -``` - -Za več informacij glejte [Konfiguracija podatkovne zbirke |configuration]. - - -Poizvedbe .[#toc-queries] -========================= - -Za poizvedovanje po zbirki podatkov uporabite metodo `query()`, ki vrne [nabor rezultatov (ResultSet) |api:Nette\Database\ResultSet]. - -```php -$result = $database->query('SELECT * FROM users'); - -foreach ($result as $row) { - echo $row->id; - echo $row->name; -} - -echo $result->getRowCount(); // vrne število vrstic, če je znano -``` - -.[note] -Nad `ResultSet` je mogoče iterirati samo enkrat, če pa moramo iterirati večkrat, je treba rezultat pretvoriti v polje prek metode `fetchAll()`. - -Poizvedbi lahko preprosto dodate parametre, pri čemer upoštevajte vprašalni znak: - -```php -$database->query('SELECT * FROM users WHERE name = ?', $name); - -$database->query('SELECT * FROM users WHERE name = ? AND active = ?', $name, $active); - -$database->query('SELECT * FROM users WHERE id IN (?)', $ids); // $ids je polje -``` -<div class=warning> - -Opozorilo, nikoli ne združujte nizov, da se izognete [ranljivosti vbrizgavanja SQL |https://en.wikipedia.org/wiki/SQL_injection]! -/-- -$db->query('SELECT * FROM users WHERE name = ' . $name); // WRONG!!! -\-- -</div> - -V primeru neuspeha `query()` vrže `Nette\Database\DriverException` ali enega od njegovih potomcev: - -- [ConstraintViolationException |api:Nette\Database\ConstraintViolationException] - kršitev katere koli omejitve -- [ForeignKeyConstraintViolationException |api:Nette\Database\ForeignKeyConstraintViolationException] - neveljaven tuj ključ -- [NotNullConstraintViolationException |api:Nette\Database\NotNullConstraintViolationException] - kršitev pogoja NOT NULL -- [UniqueConstraintViolationException |api:Nette\Database\UniqueConstraintViolationException] - konflikt edinstvenega indeksa - -Poleg `query()` so na voljo še druge uporabne metode: - -```php -// vrne asociativno polje id => ime -$pairs = $database->fetchPairs('SELECT id, name FROM users'); - -// vrne vse vrstice kot polje -$rows = $database->fetchAll('SELECT * FROM users'); - -// vrne posamezno vrstico -$row = $database->fetch('SELECT * FROM users WHERE id = ?', $id); - -// vrne posamezno polje -$name = $database->fetchField('SELECT name FROM users WHERE id = ?', $id); -``` - -V primeru neuspeha vse te metode zavržejo `Nette\Database\DriverException.` - - -Vstavljanje, posodabljanje in brisanje .[#toc-insert-update-delete] -=================================================================== - -Parameter, ki ga vstavimo v poizvedbo SQL, je lahko tudi polje (v tem primeru je mogoče preskočiti nadomestno izjavo `?`), which may be useful for the `INSERT`: - -```php -$database->query('INSERT INTO users ?', [ // tukaj se lahko izpusti vprašalno znamenje - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) - -$id = $database->getInsertId(); // vrne samodejno povečanje vstavljene vrstice - -$id = $database->getInsertId($sequence); // ali vrednost zaporedja -``` - -Večkratno vstavljanje: - -```php -$database->query('INSERT INTO users', [ - [ - 'name' => 'Jim', - 'year' => 1978, - ], [ - 'name' => 'Jack', - 'year' => 1987, - ], -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) -``` - -Vstavljamo lahko tudi datoteke, objekte DateTime ali [naštevanja |https://www.php.net/enumerations]: - -```php -$database->query('INSERT INTO users', [ - 'name' => $name, - 'created' => new DateTime, // ali $databaza::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // vstavi vsebino datoteke - 'status' => State::New, // enum State -]); -``` - -Posodabljanje vrstic: - -```php -$result = $database->query('UPDATE users SET', [ - 'name' => $name, - 'year' => $year, -], 'WHERE id = ?', $id); -// UPDATE users SET `name` = 'Jim', `year` = 1978 WHERE id = 123 - -echo $result->getRowCount(); // vrne število prizadetih vrstic -``` - -Za UPDATE lahko uporabimo operatorja `+=` in `-=`: - -```php -$database->query('UPDATE users SET', [ - 'age+=' => 1, // Opomba += -], 'WHERE id = ?', $id); -// UPDATE users SET `age` = `age` + 1 -``` - -Brisanje: - -```php -$result = $database->query('DELETE FROM users WHERE id = ?', $id); -echo $result->getRowCount(); // vrne število prizadetih vrstic -``` - - -Napredne poizvedbe .[#toc-advanced-queries] -=========================================== - -Vstavljanje ali posodabljanje, če že obstaja: - -```php -$database->query('INSERT INTO users', [ - 'id' => $id, - 'name' => $name, - 'year' => $year, -], 'ON DUPLICATE KEY UPDATE', [ - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) -// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 -``` - -Upoštevajte, da podatkovna baza Nette prepozna kontekst SQL, v katerem je vstavljen parameter polja, in v skladu s tem sestavi kodo SQL. Tako iz prvega polja ustvari `(id, name, year) VALUES (123, 'Jim', 1978)`, medtem ko drugo pretvori v `name = 'Jim', year = 1978`. - -Sortiranje lahko opišemo tudi z uporabo polja, pri čemer so ključi imena stolpcev, vrednosti pa logične vrednosti, ki določajo, ali naj se sortira v naraščajočem vrstnem redu: - -```php -$database->query('SELECT id FROM author ORDER BY', [ - 'id' => true, // naraščajoče - 'name' => false, // padajoče -]); -// SELECT id FROM author ORDER BY `id`, `name` DESC -``` - -Če zaznavanje ni delovalo, lahko določite obliko sklopa z nadomestnim znakom `?`, ki mu sledi namig. Ti namigi so podprti: - -| (key1, key2, ...) VALUES (value1, value2, ...) -| ?set | key1 = value1, key2 = value2, ... -| ?and | key1 = value1 AND key2 = value2 ... -| ?or | key1 = value1 OR key2 = value2 ... -| ?order | key1 ASC, key2 DESC - -V stavku WHERE je uporabljen operator `?and`, zato so pogoji povezani s `AND`: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND `year` = 1978 -``` - -To lahko z uporabo nadomestnega znaka `?or` preprosto spremenite v `OR`: - -```php -$result = $database->query('SELECT * FROM users WHERE ?or', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' OR `year` = 1978 -``` - -V pogojih lahko uporabljamo operatorje: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name <>' => $name, - 'year >' => $year, -]); -// SELECT * FROM users WHERE `name` <> 'Jim' AND `year` > 1978 -``` - -in tudi naštevanja: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => ['Jim', 'Jack'], - 'role NOT IN' => ['admin', 'owner'], // naštevanje + operator NOT IN -]); -// SELECT * FROM users WHERE -// `name` IN ('Jim', 'Jack') AND `role` NOT IN ('admin', 'owner') -``` - -Vključimo lahko tudi del kode SQL po meri z uporabo tako imenovanega literala SQL: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year >' => $database::literal('YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) -``` - -Druga možnost: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) -``` - -SQL literal ima lahko tudi svoje parametre: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > ? AND year < ?', $min, $max), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) -``` - -Zaradi tega lahko ustvarimo zanimive kombinacije: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('?or', [ - 'active' => true, - 'role' => $role, - ]), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') -``` - - -Ime spremenljivke .[#toc-variable-name] -======================================= - -Obstaja nadomestni znak `?name`, ki ga uporabite, če je ime tabele ali stolpca spremenljivka. (Pazite, da uporabniku ne dovolite, da bi manipuliral z vsebino take spremenljivke): - -```php -$table = 'blog.users'; -$column = 'name'; -$database->query('SELECT * FROM ?name WHERE ?name = ?', $table, $column, $name); -// SELECT * FROM `blog`.`users` WHERE `name` = 'Jim' -``` - - -Transakcije .[#toc-transactions] -================================ - -Obstajajo trije načini obravnavanja transakcij: - -```php -$database->beginTransaction(); - -$database->commit(); - -$database->rollback(); -``` - -Eleganten način ponuja metoda `transaction()`. Predate povratni klic, ki se izvede v transakciji. Če se med izvajanjem vrže izjema, se transakcija opusti, če je vse v redu, se transakcija izvede. - -```php -$id = $database->transaction(function ($database) { - $database->query('DELETE FROM ...'); - $database->query('INSERT INTO ...'); - // ... - return $database->getInsertId(); -}); -``` - -Kot lahko vidite, metoda `transaction()` vrne povratno vrednost povratnega klica. - -Metoda transaction() je lahko tudi vgnezdena, kar poenostavi izvajanje neodvisnih skladišč. diff --git a/database/sl/exceptions.texy b/database/sl/exceptions.texy new file mode 100644 index 0000000000..07d3bd8871 --- /dev/null +++ b/database/sl/exceptions.texy @@ -0,0 +1,34 @@ +Izjeme +****** + +Nette Database uporablja hierarhijo izjem. Osnovni razred je `Nette\Database\DriverException`, ki deduje iz `PDOException` in nudi razširjene možnosti za delo z napakami podatkovne baze: + +- Metoda `getDriverCode()` vrača kodo napake od gonilnika podatkovne baze +- Metoda `getSqlState()` vrača kodo SQLSTATE +- Metodi `getQueryString()` in `getParameters()` omogočata pridobitev prvotne poizvedbe in njenih parametrov + +Iz `DriverException` dedujejo naslednje specializirane izjeme: + +- `ConnectionException` - signalizira neuspeh povezave s podatkovnim strežnikom +- `ConstraintViolationException` - osnovni razred za kršitve podatkovnih omejitev, iz katerega dedujejo: + - `ForeignKeyConstraintViolationException` - kršitev tujega ključa + - `NotNullConstraintViolationException` - kršitev omejitve NOT NULL + - `UniqueConstraintViolationException` - kršitev edinstvenosti vrednosti + + +Primer lovljenja izjeme `UniqueConstraintViolationException`, ki nastane, ko poskušamo vstaviti uporabnika z e-pošto, ki že obstaja v podatkovni bazi (ob predpostavki, da ima stolpec email edinstven indeks). + +```php +try { + $database->query('INSERT INTO users', [ + 'email' => 'john@example.com', + 'name' => 'John Doe', + 'password' => $hashedPassword, + ]); +} catch (Nette\Database\UniqueConstraintViolationException $e) { + echo 'Uporabnik s tem e-naslovom že obstaja.'; + +} catch (Nette\Database\DriverException $e) { + echo 'Pri registraciji je prišlo do napake: ' . $e->getMessage(); +} +``` diff --git a/database/sl/explorer.texy b/database/sl/explorer.texy index 0763f4711c..1be13e409f 100644 --- a/database/sl/explorer.texy +++ b/database/sl/explorer.texy @@ -1,550 +1,912 @@ -Raziskovalec zbirke podatkov -**************************** +Database Explorer +***************** <div class=perex> -Nette Database Explorer bistveno poenostavi pridobivanje podatkov iz podatkovne zbirke brez pisanja poizvedb SQL. +Explorer ponuja intuitiven in učinkovit način dela s podatkovno bazo. Samodejno skrbi za relacije med tabelami in optimizacijo poizvedb, tako da se lahko osredotočite na svojo aplikacijo. Deluje takoj brez nastavljanja. Če potrebujete popoln nadzor nad SQL poizvedbami, lahko uporabite [SQL pristop |SQL way]. -- uporablja učinkovite poizvedbe -- podatki se ne prenašajo po nepotrebnem -- ima elegantno sintakso +- Delo s podatki je naravno in enostavno razumljivo +- Generira optimizirane SQL poizvedbe, ki nalagajo samo potrebne podatke +- Omogoča enostaven dostop do povezanih podatkov brez potrebe po pisanju JOIN poizvedb +- Deluje takoj brez kakršnekoli konfiguracije ali generiranja entitet </div> -Če želite uporabiti Raziskovalca podatkovne zbirke, začnite s tabelo - na objektu [api:Nette\Database\Explorer] pokličite `table()`. Najlažji način za pridobitev instance objekta konteksta je [opisan tukaj |core#Connection and Configuration], za primer, ko Nette Database Explorer uporabljate kot samostojno orodje, pa ga lahko [ustvarite ročno |#Creating Explorer Manually]. + +Z Explorerjem začnete s klicem metode `table()` objekta [api:Nette\Database\Explorer] (podrobnosti o povezavi najdete v poglavju [Povezava in konfiguracija |guide#Povezava in konfiguracija]): ```php -$books = $explorer->table('book'); // ime tabele db je 'book' +$books = $explorer->table('book'); // 'book' je ime tabele ``` -Klic vrne primerek objekta [Izbor |api:Nette\Database\Table\Selection], nad katerim lahko iterirate, da pridobite vse knjige. Vsak element (vrstica) je predstavljen z instanco [ActiveRow |api:Nette\Database\Table\ActiveRow] s podatki, ki so preslikani na njegove lastnosti: +Metoda vrača objekt [Selection |api:Nette\Database\Table\Selection], ki predstavlja SQL poizvedbo. Na ta objekt lahko navezujemo nadaljnje metode za filtriranje in razvrščanje rezultatov. Poizvedba se sestavi in zažene šele v trenutku, ko začnemo zahtevati podatke. Na primer s prehajanjem skozi zanko `foreach`. Vsaka vrstica je predstavljena z objektom [ActiveRow |api:Nette\Database\Table\ActiveRow]: ```php foreach ($books as $book) { - echo $book->title; - echo $book->author_id; + echo $book->title; // izpis stolpca 'title' + echo $book->author_id; // izpis stolpca 'author_id' } ``` -Za pridobitev samo ene določene vrstice se uporabi metoda `get()`, ki neposredno vrne primerek ActiveRow. +Explorer bistveno olajša delo s [povezavami med tabelami |#Povezave med tabelami]. Naslednji primer prikazuje, kako enostavno lahko izpišemo podatke iz povezanih tabel (knjige in njihovi avtorji). Opazite, da nam ni treba pisati nobenih JOIN poizvedb, Nette jih ustvari za nas: ```php -$book = $explorer->table('book')->get(2); // vrne knjigo z id 2 -echo $book->title; -echo $book->author_id; +$books = $explorer->table('book'); + +foreach ($books as $book) { + echo 'Knjiga: ' . $book->title; + echo 'Avtor: ' . $book->author->name; // ustvari JOIN na tabelo 'author' +} ``` -Oglejmo si pogost primer uporabe. Pridobiti morate knjige in njihove avtorje. To je običajno razmerje 1:N. Pogosto uporabljena rešitev je pridobivanje podatkov z eno poizvedbo SQL z združevanjem tabel. Druga možnost je, da podatke pridobite ločeno, zaženete eno poizvedbo za pridobitev knjig in nato z drugo poizvedbo (npr. v ciklu foreach) pridobite avtorja za vsako knjigo. To bi lahko zlahka optimizirali tako, da bi izvedli le dve poizvedbi, eno za knjige in drugo za potrebne avtorje - in točno tako to počne Nette Database Explorer. +Nette Database Explorer optimizira poizvedbe, da so čim bolj učinkovite. Zgornji primer izvede samo dve SELECT poizvedbi, ne glede na to, ali obdelujemo 10 ali 10.000 knjig. -V spodnjih primerih bomo delali s shemo podatkovne zbirke na sliki. Obstajajo povezave OneHasMany (1:N) (avtor knjige `author_id` in morebitni prevajalec `translator_id`, ki je lahko `null`) in ManyHasMany (M:N) med knjigo in njenimi oznakami. +Poleg tega Explorer spremlja, kateri stolpci se v kodi uporabljajo, in nalaga iz podatkovne baze samo te, s čimer prihrani dodatno zmogljivost. To obnašanje je popolnoma samodejno in prilagodljivo. Če kasneje prilagodite kodo in začnete uporabljati druge stolpce, Explorer samodejno prilagodi poizvedbe. Ničesar vam ni treba nastavljati, niti razmišljati o tem, katere stolpce boste potrebovali - prepustite to Nette. -[Primer, vključno s shemo, je na voljo na GitHubu |https://github.com/nette-examples/books]. -[* db-schema-1-.webp *] *** Struktura zbirke podatkov, uporabljena v primerih .<> +Filtriranje in razvrščanje +========================== -V naslednji kodi je za vsako knjigo navedeno ime avtorja in vse njegove oznake. [O tem, |#Working with relationships] kako to deluje interno, bomo [razpravljali |#Working with relationships] v naslednjem trenutku. +Razred `Selection` ponuja metode za filtriranje in razvrščanje izbora podatkov. -```php -$books = $explorer->table('book'); +.[language-php] +| `where($condition, ...$params)` | Doda pogoj WHERE. Več pogojev je povezanih z operatorjem AND +| `whereOr(array $conditions)` | Doda skupino pogojev WHERE, povezanih z operatorjem OR +| `wherePrimary($value)` | Doda pogoj WHERE po primarnem ključu +| `order($columns, ...$params)` | Nastavi razvrščanje ORDER BY +| `select($columns, ...$params)` | Določi stolpce, ki naj se naložijo +| `limit($limit, $offset = null)` | Omeji število vrstic (LIMIT) in po želji nastavi OFFSET +| `page($page, $itemsPerPage, &$total = null)` | Nastavi stranskanje +| `group($columns, ...$params)` | Združi vrstice (GROUP BY) +| `having($condition, ...$params)` | Doda pogoj HAVING za filtriranje združenih vrstic -foreach ($books as $book) { - echo 'title: ' . $book->title; - echo 'written by: ' . $book->author->name; // $book->author je vrstica iz tabele 'avtor' +Metode lahko verižimo (t.i. [fluent interface |nette:introduction-to-object-oriented-programming#Tekoči vmesniki]): `$table->where(...)->order(...)->limit(...)`. - echo 'tags: '; - foreach ($book->related('book_tag') as $bookTag) { - echo $bookTag->tag->name . ', '; // $bookTag->tag je vrstica iz tabele 'tag' - } -} -``` +V teh metodah lahko uporabljate tudi posebno notacijo za dostop do [podatkov iz povezanih tabel |#Poizvedovanje prek povezanih tabel]. -Zadovoljni boste, kako učinkovito deluje plast podatkovne zbirke. Zgornji primer izvaja stalno število zahtevkov, ki so videti takole: -```sql -SELECT * FROM `book` -SELECT * FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT * FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) -``` +Ubežanje znakov in identifikatorji +---------------------------------- -Če uporabljate [predpomnilnik |caching:] (privzeto vklopljen), ne boste po nepotrebnem poizvedovali po nobenem stolpcu. Po prvi poizvedbi bo predpomnilnik shranil uporabljena imena stolpcev in Nette Database Explorer bo izvajal poizvedbe samo s potrebnimi stolpci: +Metode samodejno ubežijo parametre in navajajo identifikatorje (imena tabel in stolpcev), s čimer preprečujejo SQL injection. Za pravilno delovanje je treba upoštevati nekaj pravil: -```sql -SELECT `id`, `title`, `author_id` FROM `book` -SELECT `id`, `name` FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT `book_id`, `tag_id` FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT `id`, `name` FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) +- Ključne besede, imena funkcij, procedur ipd. pišite **z velikimi črkami**. +- Imena stolpcev in tabel pišite **z malimi črkami**. +- Nize vedno vstavljajte prek **parametrov**. + +```php +where('name = ' . $name); // KRITIČNA RANLJIVOST: SQL injection +where('name LIKE "%search%"'); // NAPAKA: otežuje samodejno navajanje +where('name LIKE ?', '%search%'); // PRAVILNO: vrednost vstavljena prek parametra + +where('name like ?', $name); // NAPAKA: generira: `name` `like` ? +where('name LIKE ?', $name); // PRAVILNO: generira: `name` LIKE ? +where('LOWER(name) = ?', $value);// PRAVILNO: LOWER(`name`) = ? ``` -Izbori .[#toc-selections] -========================= +where(string|array $condition, ...$parameters): static .[method] +---------------------------------------------------------------- -Oglejte si možnosti za filtriranje in omejevanje vrstic [api:Nette\Database\Table\Selection]: +Filtrira rezultate s pomočjo pogojev WHERE. Njena močna stran je inteligentno delo z različnimi tipi vrednosti in samodejna izbira SQL operatorjev. -.[language-php] -| `$table->where($where[, $param[, ...]])` | Nastavite WHERE z uporabo AND kot lepilo, če sta podana dva ali več pogojev -| `$table->whereOr($where)` | Nastavitev WHERE z uporabo OR kot veziva, če sta podana dva ali več pogojev -| `$table->order($columns)` | Nastavitev ORDER BY, lahko je izraz `('column DESC, id DESC')` -| `$table->select($columns)` | Nastavitev pridobljenih stolpcev, lahko izraz `('col, MD5(col) AS hash')` -| `$table->limit($limit[, $offset])` | Nastavite LIMIT in OFFSET -| `$table->page($page, $itemsPerPage[, &$lastPage])` | Omogoča paginiranje -| `$table->group($columns)` | Nastavitev GROUP BY -| `$table->having($having)` | Nastavitev HAVING +Osnovna uporaba: -Uporabi se lahko vmesnik Fluent, na primer `$table->where(...)->order(...)->limit(...)`. Več pogojev `where` ali `whereOr` je povezanih z operatorjem `AND`. +```php +$table->where('id', $value); // WHERE `id` = 123 +$table->where('id > ?', $value); // WHERE `id` > 123 +$table->where('id = ? OR name = ?', $id, $name); // WHERE `id` = 1 OR `name` = 'Jon Snow' +``` +Zahvaljujoč samodejnemu zaznavanju ustreznih operatorjev nam ni treba reševati različnih posebnih primerov. Nette jih reši za nas: -kjer() .[#toc-where] --------------------- +```php +$table->where('id', 1); // WHERE `id` = 1 +$table->where('id', null); // WHERE `id` IS NULL +$table->where('id', [1, 2, 3]); // WHERE `id` IN (1, 2, 3) +// lahko se uporabi tudi nadomestni vprašaj brez operatorja: +$table->where('id ?', 1); // WHERE `id` = 1 +``` -Nette Database Explorer lahko samodejno doda potrebne operatorje za posredovane vrednosti: +Metoda pravilno obdela tudi negativne pogoje in prazno polje: -.[language-php] -| `$table->where('field', $value)` | polje = $vrednost -| `$table->where('field', null)` | polje JE NULL -| `$table->where('field > ?', $val)` | polje > $val -| `$table->where('field', [1, 2])` | field IN (1, 2) -| `$table->where('id = ? OR name = ?', 1, $name)` | id = 1 ALI ime = 'Jon Snow' -| `$table->where('field', $explorer->table($tableName))` | field IN (SELECT $primary FROM $tableName) -| `$table->where('field', $explorer->table($tableName)->select('col'))` | polje IN (SELECT col FROM $tableName) +```php +$table->where('id', []); // WHERE `id` IS NULL AND FALSE -- ničesar ne najde +$table->where('id NOT', []); // WHERE `id` IS NULL OR TRUE -- najde vse +$table->where('NOT (id ?)', []); // WHERE NOT (`id` IS NULL AND FALSE) -- najde vse +// $table->where('NOT id ?', $ids); Pozor - ta sintaksa ni podprta +``` -Namestno oznako lahko zagotovite tudi brez operatorja stolpca. Ti klici so enaki. +Kot parameter lahko posredujemo tudi rezultat iz druge tabele - ustvari se podpoizvedba: ```php -$table->where('id = ? OR id = ?', 1, 2); -$table->where('id ? OR id ?', 1, 2); +// WHERE `id` IN (SELECT `id` FROM `tableName`) +$table->where('id', $explorer->table($tableName)); + +// WHERE `id` IN (SELECT `col` FROM `tableName`) +$table->where('id', $explorer->table($tableName)->select('col')); ``` -Ta funkcija omogoča ustvarjanje pravilnega operatorja na podlagi vrednosti: +Pogoje lahko posredujemo tudi kot polje, katerega elementi se združijo s pomočjo AND: ```php -$table->where('id ?', 2); // id = 2 -$table->where('id ?', null); // id IS NULL -$table->where('id', $ids); // id IN (...) +// WHERE (`price_final` < `price_original`) AND (`stock_count` > `min_stock`) +$table->where([ + 'price_final < price_original', + 'stock_count > min_stock', +]); ``` -Izbira pravilno obravnava tudi negativne pogoje, deluje tudi pri praznih poljih: +V polju lahko uporabimo pare ključ => vrednost in Nette spet samodejno izbere pravilne operatorje: ```php -$table->where('id', []); // id IS NULL AND FALSE -$table->where('id NOT', []); // id IS NULL OR TRUE -$table->where('NOT (id ?)', $ids); // NOT (id IS NULL AND FALSE) +// WHERE (`status` = 'active') AND (`id` IN (1, 2, 3)) +$table->where([ + 'status' => 'active', + 'id' => [1, 2, 3], +]); +``` + +V polju lahko kombiniramo SQL izraze z nadomestnimi vprašaji in več parametri. To je primerno za kompleksne pogoje z natančno določenimi operatorji: -// to vrže izjemo, ker ta sintaksa ni podprta -$table->where('NOT id ?', $ids); +```php +// WHERE (`age` > 18) AND (ROUND(`score`, 2) > 75.5) +$table->where([ + 'age > ?' => 18, + 'ROUND(score, ?) > ?' => [2, 75.5], // dva parametra posredujemo kot polje +]); ``` +Večkratni klic `where()` pogoje samodejno združuje s pomočjo AND. -whereOr() .[#toc-whereor] -------------------------- -Primer uporabe brez parametrov: +whereOr(array $parameters): static .[method] +-------------------------------------------- + +Podobno kot `where()` dodaja pogoje, vendar s to razliko, da jih združuje s pomočjo OR: ```php -// WHERE (user_id IS NULL) OR (SUM(`field1`) > SUM(`field2`)) +// WHERE (`status` = 'active') OR (`deleted` = 1) $table->whereOr([ - 'user_id IS NULL', - 'SUM(field1) > SUM(field2)', + 'status' => 'active', + 'deleted' => true, ]); ``` -Uporabljamo parametre. Če ne določite operaterja, bo Nette Database Explorer samodejno dodal ustreznega: +Tudi tukaj lahko uporabimo kompleksnejše izraze: ```php -// WHERE (`field1` IS NULL) OR (`field2` IN (3, 5)) OR (`amount` > 11) +// WHERE (`price` > 1000) OR (`price_with_tax` > 1500) $table->whereOr([ - 'field1' => null, - 'field2' => [3, 5], - 'amount >' => 11, + 'price > ?' => 1000, + 'price_with_tax > ?' => 1500, ]); ``` -Ključ lahko vsebuje izraz, ki vsebuje nadomestne vprašalnike, nato pa v vrednosti posredujemo parametre: + +wherePrimary(mixed $key): static .[method] +------------------------------------------ + +Doda pogoj za primarni ključ tabele: ```php -// WHERE (`id` > 12) OR (ROUND(`id`, 5) = 3) -$table->whereOr([ - 'id > ?' => 12, - 'ROUND(id, ?) = ?' => [5, 3], -]); +// WHERE `id` = 123 +$table->wherePrimary(123); + +// WHERE `id` IN (1, 2, 3) +$table->wherePrimary([1, 2, 3]); +``` + +Če ima tabela sestavljen primarni ključ (npr. `foo_id`, `bar_id`), ga posredujemo kot polje: + +```php +// WHERE `foo_id` = 1 AND `bar_id` = 5 +$table->wherePrimary(['foo_id' => 1, 'bar_id' => 5])->fetch(); + +// WHERE (`foo_id`, `bar_id`) IN ((1, 5), (2, 3)) +$table->wherePrimary([ + ['foo_id' => 1, 'bar_id' => 5], + ['foo_id' => 2, 'bar_id' => 3], +])->fetchAll(); ``` -Naročilo() .[#toc-order] ------------------------- +order(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- -Primeri uporabe: +Določa vrstni red, v katerem bodo vrnjene vrstice. Lahko razvrščamo po enem ali več stolpcih, v padajočem ali naraščajočem vrstnem redu, ali po lastnem izrazu: ```php -$table->order('field1'); // ORDER BY `field1` -$table->order('field1 DESC, field2'); // ORDER BY `field1` DESC, `field2` -$table->order('field = ? DESC', 123); // ORDER BY `field` = 123 DESC +$table->order('created'); // ORDER BY `created` +$table->order('created DESC'); // ORDER BY `created` DESC +$table->order('priority DESC, created'); // ORDER BY `priority` DESC, `created` +$table->order('status = ? DESC', 'active'); // ORDER BY `status` = 'active' DESC ``` -select() .[#toc-select] ------------------------ +select(string $columns, ...$parameters): static .[method] +--------------------------------------------------------- -Primeri uporabe: +Določa stolpce, ki naj se vrnejo iz podatkovne baze. V privzetem stanju Nette Database Explorer vrača samo tiste stolpce, ki se dejansko uporabijo v kodi. Metodo `select()` tako uporabljamo v primerih, ko moramo vrniti specifične izraze: ```php -$table->select('field1'); // SELECT `polje1` -$table->select('col, UPPER(col) AS abc'); // SELECT `col`, UPPER(`col`) AS abc -$table->select('SUBSTR(title, ?)', 3); // SELECT SUBSTR(`title`, 3) +// SELECT *, DATE_FORMAT(`created_at`, "%d.%m.%Y") AS `formatted_date` +$table->select('*, DATE_FORMAT(created_at, ?) AS formatted_date', '%d.%m.%Y'); ``` +Aliasi, definirani s pomočjo `AS`, so nato dostopni kot lastnosti objekta ActiveRow: + +```php +foreach ($table as $row) { + echo $row->formatted_date; // dostop do aliasa +} +``` -limit() .[#toc-limit] ---------------------- -Primeri uporabe: +limit(?int $limit, ?int $offset = null): static .[method] +--------------------------------------------------------- + +Omejuje število vrnjenih vrstic (LIMIT) in po želji omogoča nastavitev odmika (offset): ```php -$table->limit(1); // LIMIT 1 -$table->limit(1, 10); // LIMIT 1 OFFSET 10 +$table->limit(10); // LIMIT 10 (vrne prvih 10 vrstic) +$table->limit(10, 20); // LIMIT 10 OFFSET 20 ``` +Za stranskanje je primernejša uporaba metode `page()`. + -stran() .[#toc-page] --------------------- +page(int $page, int $itemsPerPage, &$numOfPages = null): static .[method] +------------------------------------------------------------------------- -Alternativni način za nastavitev meje in odmika: +Olajša stranskanje rezultatov. Sprejme številko strani (šteto od 1) in število postavk na stran. Po želji lahko posredujemo referenco na spremenljivko, v katero se shrani skupno število strani: ```php -$page = 5; -$itemsPerPage = 10; -$table->page($page, $itemsPerPage); // LIMIT 10 OFFSET 40 +$numOfPages = null; +$table->page(page: 3, itemsPerPage: 10, $numOfPages); +echo "Skupaj strani: $numOfPages"; ``` -Pridobitev številke zadnje strani, ki je posredovana v spremenljivko `$lastPage`: + +group(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- + +Združuje vrstice po navedenih stolpcih (GROUP BY). Uporablja se običajno v povezavi z agregatnimi funkcijami: ```php -$table->page($page, $itemsPerPage, $lastPage); +// Prešteje število izdelkov v vsaki kategoriji +$table->select('category_id, COUNT(*) AS count') + ->group('category_id'); ``` -skupina() .[#toc-group] ------------------------ +having(string $having, ...$parameters): static .[method] +-------------------------------------------------------- -Primeri uporabe: +Nastavi pogoj za filtriranje združenih vrstic (HAVING). Lahko se uporablja v povezavi z metodo `group()` in agregatnimi funkcijami: ```php -$table->group('field1'); // GROUP BY `field1` -$table->group('field1, field2'); // GROUP BY `field1`, `field2` +// Najde kategorije, ki imajo več kot 100 izdelkov +$table->select('category_id, COUNT(*) AS count') + ->group('category_id') + ->having('count > ?', 100); ``` -having() .[#toc-having] ------------------------ +Branje podatkov +=============== + +Za branje podatkov iz podatkovne baze imamo na voljo več uporabnih metod: + +.[language-php] +| `foreach ($table as $key => $row)` | Iterira čez vse vrstice, `$key` je vrednost primarnega ključa, `$row` je objekt ActiveRow +| `$row = $table->get($key)` | Vrne eno vrstico po primarnem ključu +| `$row = $table->fetch()` | Vrne trenutno vrstico in premakne kazalec na naslednjo +| `$array = $table->fetchPairs()` | Ustvari asociativno polje iz rezultatov +| `$array = $table->fetchAll()` | Vrne vse vrstice kot polje +| `count($table)` | Vrne število vrstic v objektu Selection -Primeri uporabe: +Objekt [ActiveRow |api:Nette\Database\Table\ActiveRow] je namenjen samo za branje. To pomeni, da ni mogoče spreminjati vrednosti njegovih lastnosti. Ta omejitev zagotavlja doslednost podatkov in preprečuje nepričakovane stranske učinke. Podatki se nalagajo iz podatkovne baze in vsaka sprememba bi morala biti izvedena eksplicitno in nadzorovano. + + +`foreach` - iteracija čez vse vrstice +------------------------------------- + +Najlažji način za izvedbo poizvedbe in pridobitev vrstic je iteriranje v zanki `foreach`. Samodejno zažene SQL poizvedbo. ```php -$table->having('COUNT(items) >', 100); // HAVING COUNT(`items`) > 100 +$books = $explorer->table('book'); +foreach ($books as $key => $book) { + // $key je vrednost primarnega ključa, $book je ActiveRow + echo "$book->title ({$book->author->name})"; +} ``` -Filtriranje po vrednosti druge tabele .[#toc-joining-key] ---------------------------------------------------------- +get($key): ?ActiveRow .[method] +------------------------------- -Pogosto morate rezultate filtrirati glede na pogoj, ki vključuje drugo tabelo podatkovne zbirke. Te vrste pogojev zahtevajo združitev tabel. Vendar vam jih ni treba več pisati. +Izvede SQL poizvedbo in vrne vrstico po primarnem ključu, ali `null`, če ne obstaja. -Recimo, da morate dobiti vse knjige, katerih avtor je "Jon". Vse, kar morate napisati, je ključ združevanja relacije in ime stolpca v združeni tabeli. Ključ združevanja izhaja iz stolpca, ki se nanaša na tabelo, ki jo želite združiti. V našem primeru (glej shemo db) je to stolpec `author_id`, zato je dovolj, da uporabimo samo njegov prvi del - `author` (končnico `_id` lahko izpustimo). `name` je stolpec v tabeli `author`, ki bi ga radi uporabili. Pogoj za knjižni prevajalnik (ki ga povezuje stolpec `translator_id` ) lahko ustvarimo prav tako preprosto. +```php +$book = $explorer->table('book')->get(123); // vrne ActiveRow z ID 123 ali null +if ($book) { + echo $book->title; +} +``` + + +fetch(): ?ActiveRow .[method] +----------------------------- + +Vrne vrstico in premakne notranji kazalec na naslednjo. Če ni več vrstic, vrne `null`. ```php $books = $explorer->table('book'); -$books->where('author.name LIKE ?', '%Jon%'); -$books->where('translator.name', 'David Grudl'); +while ($book = $books->fetch()) { + $this->processBook($book); +} ``` -Logiko povezovalnega ključa poganja izvajanje funkcije [Conventions |api:Nette\Database\Conventions]. Priporočamo uporabo programa [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions], ki analizira vaše tuje ključe in vam omogoča enostavno delo s temi povezavami. -Razmerje med knjigo in njenim avtorjem je 1:N. Možno je tudi obratno razmerje. Imenujemo ga **povratno povezovanje**. Oglejte si še en primer. Želimo pridobiti vse avtorje, ki so napisali več kot 3 knjige. Za obratno združevanje uporabimo izjavo `:` (colon). Colon means that the joined relationship means hasMany (and it's quite logical too, as two dots are more than one dot). Unfortunately, the Selection class isn't smart enough, so we have to help with the aggregation and provide a `GROUP BY`, tudi pogoj mora biti zapisan v obliki izjave `HAVING`. +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Vrne rezultate kot asociativno polje. Prvi argument določa ime stolpca, ki se uporabi kot ključ v polju, drugi argument določa ime stolpca, ki se uporabi kot vrednost: ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book.id) > 3'); +$authors = $explorer->table('author')->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] ``` -Morda ste opazili, da se izraz za združevanje nanaša na knjigo, vendar ni jasno, ali se združujemo prek `author_id` ali `translator_id`. V zgornjem primeru se Selection združuje prek stolpca `author_id`, ker je bilo najdeno ujemanje z izvorno tabelo - tabelo `author`. Če takšnega ujemanja ne bi bilo in bi bilo več možnosti, bi Nette vrgel [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. +Če navedemo samo prvi parameter, bo vrednost celotna vrstica, torej objekt `ActiveRow`: + +```php +$authors = $explorer->table('author')->fetchPairs('id'); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` -Če želite združitev opraviti prek stolpca `translator_id`, v izrazu za združevanje navedite izbirni parameter. +V primeru podvojenih ključev se uporabi vrednost iz zadnje vrstice. Pri uporabi `null` kot ključa bo polje indeksirano numerično od nič (takrat do kolizij ne pride): ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book(translator).id) > 3'); +$authors = $explorer->table('author')->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] ``` -Oglejmo si nekaj zahtevnejših izrazov za združevanje. -Radi bi našli vse avtorje, ki so napisali kaj o PHP. Vse knjige imajo oznake, zato bi morali izbrati tiste avtorje, ki so napisali katero koli knjigo z oznako PHP. +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Alternativno lahko kot parameter navedete povratni klic (callback), ki bo za vsako vrstico vračal bodisi samo vrednost, bodisi par ključ-vrednost. ```php -$authors = $explorer->table('author'); -$authors->where(':book:book_tags.tag.name', 'PHP') - ->group('author.id') - ->having('COUNT(:book:book_tags.tag.id) > 0'); +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => "$row->title ({$row->author->name})"); +// ['Prva knjiga (Jan Novak)', ...] + +// Callback lahko vrne tudi polje s parom ključ & vrednost: +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => [$row->title, $row->author->name]); +// ['Prva knjiga' => 'Jan Novak', ...] ``` -Zbirne poizvedbe .[#toc-aggregate-queries] ------------------------------------------- +fetchAll(): array .[method] +--------------------------- -| `$table->count('*')` | Pridobi število vrstic -| `$table->count("DISTINCT $column")` | Pridobi število različnih vrednosti -| `$table->min($column)` | Pridobi najmanjšo vrednost -| `$table->max($column)` | Pridobi največjo vrednost -| `$table->sum($column)` | Pridobi vsoto vseh vrednosti -| `$table->aggregation("GROUP_CONCAT($column)")` | Izvedba katere koli funkcije združevanja +Vrne vse vrstice kot asociativno polje objektov `ActiveRow`, kjer so ključi vrednosti primarnih ključev. -.[caution] -Metoda `count()` brez določenih parametrov izbere vse zapise in vrne velikost polja, kar je zelo neučinkovito. Če morate na primer izračunati število vrstic za listanje, vedno navedite prvi argument. +```php +$allBooks = $explorer->table('book')->fetchAll(); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` + + +count(): int .[method] +---------------------- + +Metoda `count()` brez parametra vrne število vrstic v objektu `Selection`: + +```php +$table->where('category', 1); +$count = $table->count(); +$count = count($table); // alternativa +``` + +Pozor, `count()` s parametrom izvaja agregatno funkcijo COUNT v podatkovni bazi, glej spodaj. + + +ActiveRow::toArray(): array .[method] +------------------------------------- + +Pretvori objekt `ActiveRow` v asociativno polje, kjer so ključi imena stolpcev in vrednosti ustrezni podatki. + +```php +$book = $explorer->table('book')->get(1); +$bookArray = $book->toArray(); +// $bookArray bo ['id' => 1, 'title' => '...', 'author_id' => ..., ...] +``` + + +Agregacija +========== + +Razred `Selection` ponuja metode za enostavno izvajanje agregatnih funkcij (COUNT, SUM, MIN, MAX, AVG itd.). + +.[language-php] +| `count($expr)` | Prešteje število vrstic +| `min($expr)` | Vrne minimalno vrednost v stolpcu +| `max($expr)` | Vrne maksimalno vrednost v stolpcu +| `sum($expr)` | Vrne vsoto vrednosti v stolpcu +| `aggregation($function)` | Omogoča izvedbo poljubne agregatne funkcije. Npr. `AVG()`, `GROUP_CONCAT()` + + +count(string $expr): int .[method] +---------------------------------- + +Izvede SQL poizvedbo s funkcijo COUNT in vrne rezultat. Metoda se uporablja za ugotavljanje, koliko vrstic ustreza določenemu pogoju: + +```php +$count = $table->count('*'); // SELECT COUNT(*) FROM `table` +$count = $table->count('DISTINCT column'); // SELECT COUNT(DISTINCT `column`) FROM `table` +``` + +Pozor, [#count()] brez parametra samo vrača število vrstic v objektu `Selection`. + + +min(string $expr) a max(string $expr) .[method] +----------------------------------------------- + +Metodi `min()` in `max()` vračata minimalno in maksimalno vrednost v določenem stolpcu ali izrazu: + +```php +// SELECT MAX(`price`) FROM `products` WHERE `active` = 1 +$maxPrice = $products->where('active', true) + ->max('price'); +``` -Pobeg in citiranje .[#toc-escaping-quoting] -=========================================== +sum(string $expr) .[method] +--------------------------- -Raziskovalec podatkovnih zbirk je pameten in za vas izloči parametre in identifikatorje narekovajev. Kljub temu je treba upoštevati ta osnovna pravila: +Vrne vsoto vrednosti v določenem stolpcu ali izrazu: -- ključne besede, funkcije, postopki morajo biti zapisani z velikimi črkami -- stolpci in tabele morajo biti pisani z malimi črkami -- spremenljivke se posredujejo kot parametri, ne smejo se združevati +```php +// SELECT SUM(`price` * `items_in_stock`) FROM `products` WHERE `active` = 1 +$totalPrice = $products->where('active', true) + ->sum('price * items_in_stock'); +``` + + +aggregation(string $function, ?string $groupFunction = null) .[method] +---------------------------------------------------------------------- + +Omogoča izvedbo poljubne agregatne funkcije. ```php -->where('name like ?', 'John'); // Napačno! generira: `name` `like` ? -->where('name LIKE ?', 'John'); // PRAVILNO +// povprečna cena izdelkov v kategoriji +$avgPrice = $products->where('category_id', 1) + ->aggregation('AVG(price)'); -->where('KEY = ?', $value); // NAPAČNO! KEY je ključna beseda -->where('key = ?', $value); // PRAVILNO. generira: `key` = ? +// združi oznake izdelka v en niz +$tags = $products->where('id', 1) + ->aggregation('GROUP_CONCAT(tag.name) AS tags') + ->fetch() + ->tags; +``` -->where('name = ' . $name); // NAPAKA! vbrizgavanje sql! -->where('name = ?', $name); // PRAVILNO +Če moramo agregirati rezultate, ki so že sami po sebi nastali iz neke agregatne funkcije in združevanja (npr. `SUM(vrednost)` čez združene vrstice), kot drugi argument navedemo agregatno funkcijo, ki naj se uporabi na teh vmesnih rezultatih: -->select('DATE_FORMAT(created, "%d.%m.%Y")'); // NAPAKA! spremenljivke se posredujejo kot parametri, ne združujejo se -->select('DATE_FORMAT(created, ?)', '%d.%m.%Y'); // KORISTNO +```php +// Izračuna skupno ceno izdelkov na zalogi za posamezne kategorije in nato sešteje te cene skupaj. +$totalPrice = $products->select('category_id, SUM(price * stock) AS category_total') + ->group('category_id') + ->aggregation('SUM(category_total)', 'SUM'); ``` -.[warning] -nepravilna uporaba lahko povzroči varnostne luknje +V tem primeru najprej izračunamo skupno ceno izdelkov v vsaki kategoriji (`SUM(price * stock) AS category_total`) in združimo rezultate po `category_id`. Nato uporabimo `aggregation('SUM(category_total)', 'SUM')` za seštevanje teh vmesnih vsot `category_total`. Drugi argument `'SUM'` pove, da naj se na vmesne rezultate uporabi funkcija SUM. + +Insert, Update & Delete +======================= -Pridobivanje podatkov .[#toc-fetching-data] -=========================================== +Nette Database Explorer poenostavlja vstavljanje, posodabljanje in brisanje podatkov. Vse navedene metode v primeru napake vržejo izjemo `Nette\Database\DriverException`. -| `foreach ($table as $id => $row)` | Iterirajte po vseh vrsticah v rezultatu -| `$row = $table->get($id)` | Pridobi posamezno vrstico z ID $id iz tabele -| `$row = $table->fetch()` | Pridobi naslednjo vrstico iz rezultata -| `$array = $table->fetchPairs($key, $value)` | Prevzem vseh vrednosti v asociativno polje -| `$array = $table->fetchPairs($key)` | Prevzem vseh vrstic v asociativno polje -| `count($table)` | Pridobi število vrstic v nizu rezultatov +Selection::insert(iterable $data) .[method] +------------------------------------------- -Vstavljanje, posodabljanje in brisanje .[#toc-insert-update-delete] -=================================================================== +Vstavi nove zapise v tabelo. -Metoda `insert()` sprejme polje objektov Traversable (na primer [ArrayHash |utils:arrays#ArrayHash], ki vrne [obrazce |forms:]): +**Vstavljanje enega zapisa:** + +Nov zapis posredujemo kot asociativno polje ali iterable objekt (na primer ArrayHash, ki se uporablja v [obrazcih |forms:]), kjer ključi ustrezajo imenom stolpcev v tabeli. + +Če ima tabela definiran primarni ključ, metoda vrne objekt `ActiveRow`, ki se ponovno naloži iz podatkovne baze, da se upoštevajo morebitne spremembe, izvedene na ravni podatkovne baze (sprožilci, privzete vrednosti stolpcev, izračuni auto-increment stolpcev). S tem je zagotovljena doslednost podatkov in objekt vedno vsebuje aktualne podatke iz podatkovne baze. Če enoličnega primarnega ključa nima, vrne posredovane podatke v obliki polja. ```php $row = $explorer->table('users')->insert([ - 'name' => $name, - 'year' => $year, + 'name' => 'John Doe', + 'email' => 'john.doe@example.com', ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) +// $row je instanca ActiveRow in vsebuje celotne podatke vstavljene vrstice, +// vključno s samodejno generiranim ID-jem in morebitnimi spremembami, izvedenimi s sprožilci (triggerji) +echo $row->id; // Izpiše ID novo vstavljenega uporabnika +echo $row->created_at; // Izpiše čas ustvarjanja, če je nastavljen s sprožilcem ``` -Če je v tabeli določen primarni ključ, se vrne objekt ActiveRow, ki vsebuje vstavljeno vrstico. +**Vstavljanje več zapisov hkrati:** -Večkratno vstavljanje: +Metoda `insert()` omogoča vstavljanje več zapisov z eno samo SQL poizvedbo. V tem primeru vrne število vstavljenih vrstic. ```php -$explorer->table('users')->insert([ +$insertedRows = $explorer->table('users')->insert([ + [ + 'name' => 'John', + 'year' => 1994, + ], [ - 'name' => 'Jim', - 'year' => 1978, - ], [ 'name' => 'Jack', - 'year' => 1987, + 'year' => 1995, ], ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) +// INSERT INTO `users` (`name`, `year`) VALUES ('John', 1994), ('Jack', 1995) +// $insertedRows bo 2 +``` + +Kot parameter lahko posredujemo tudi objekt `Selection` z izborom podatkov. + +```php +$newUsers = $explorer->table('potential_users') + ->where('approved', 1) + ->select('name, email'); + +$insertedRows = $explorer->table('users')->insert($newUsers); ``` -Datoteke ali objekti DateTime se lahko posredujejo kot parametri: +**Vstavljanje posebnih vrednosti:** + +Kot vrednosti lahko posredujemo tudi datoteke, objekte DateTime ali SQL literale: ```php $explorer->table('users')->insert([ - 'name' => $name, - 'created' => new DateTime, // ali $explorer::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // vstavi datoteko + 'name' => 'John', + 'created_at' => new DateTime, // pretvori v format podatkovne baze + 'avatar' => fopen('image.jpg', 'rb'), // vstavi binarno vsebino datoteke + 'uuid' => $explorer::literal('UUID()'), // pokliče funkcijo UUID() ]); ``` -Posodabljanje (vrne število prizadetih vrstic): + +Selection::update(iterable $data): int .[method] +------------------------------------------------ + +Posodobi vrstice v tabeli po navedenem filtru. Vrne število dejansko spremenjenih vrstic. + +Spremenjene stolpce posredujemo kot asociativno polje ali iterable objekt (na primer ArrayHash, ki se uporablja v [obrazcih |forms:]), kjer ključi ustrezajo imenom stolpcev v tabeli: ```php -$count = $explorer->table('users') - ->where('id', 10) // je treba poklicati pred funkcijo update() +$affected = $explorer->table('users') + ->where('id', 10) ->update([ - 'name' => 'Ned Stark' + 'name' => 'John Smith', + 'year' => 1994, ]); -// UPDATE `users` SET `name`='Ned Stark' WHERE (`id` = 10) +// UPDATE `users` SET `name` = 'John Smith', `year` = 1994 WHERE `id` = 10 ``` -Za posodabljanje lahko uporabimo operatorje `+=` a `-=`: +Za spremembo številskih vrednosti lahko uporabimo operatorja `+=` in `-=`: ```php $explorer->table('users') + ->where('id', 10) ->update([ - 'age+=' => 1, // glej += + 'points+=' => 1, // poveča vrednost stolpca 'points' za 1 + 'coins-=' => 1, // zmanjša vrednost stolpca 'coins' za 1 ]); -// UPDATE users SET `age` = `age` + 1 +// UPDATE `users` SET `points` = `points` + 1, `coins` = `coins` - 1 WHERE `id` = 10 ``` -Brisanje (vrne število izbrisanih vrstic): + +Selection::delete(): int .[method] +---------------------------------- + +Briše vrstice iz tabele po navedenem filtru. Vrne število izbrisanih vrstic. ```php $count = $explorer->table('users') ->where('id', 10) ->delete(); -// DELETE FROM `users` WHERE (`id` = 10) +// DELETE FROM `users` WHERE `id` = 10 ``` +.[caution] +Pri klicu `update()` in `delete()` ne pozabite s pomočjo `where()` določiti vrstic, ki naj se uredijo/izbrišejo. Če `where()` ne uporabite, se operacija izvede na celotni tabeli! + -Delo z razmerji .[#toc-working-with-relationships] -================================================== +ActiveRow::update(iterable $data): bool .[method] +------------------------------------------------- +Posodobi podatke v podatkovni vrstici, ki jo predstavlja objekt `ActiveRow`. Kot parameter sprejme iterable s podatki, ki naj se posodobijo (ključi so imena stolpcev). Za spremembo številskih vrednosti lahko uporabimo operatorja `+=` in `-=`: -Ima eno razmerje .[#toc-has-one-relation] ------------------------------------------ -Ima eno razmerje je pogost primer uporabe. Knjiga ima enega avtorja. Knjiga ima enega* prevajalca. Pridobivanje povezanih vrstic se večinoma izvaja z metodo `ref()`. Sprejme dva argumenta: ime ciljne tabele in izvorni povezovalni stolpec. Oglejte si primer: +Po izvedbi posodobitve se `ActiveRow` samodejno ponovno naloži iz podatkovne baze, da se upoštevajo morebitne spremembe, izvedene na ravni podatkovne baze (npr. sprožilci). Metoda vrne true samo, če je prišlo do dejanske spremembe podatkov. ```php -$book = $explorer->table('book')->get(1); -$book->ref('author', 'author_id'); +$article = $explorer->table('article')->get(1); +$article->update([ + 'views += 1', // povečamo število prikazov +]); +echo $article->views; // Izpiše trenutno število prikazov ``` -V zgornjem primeru smo iz tabele `author` pridobili povezan vnos avtorja, primarni ključ avtorja pa smo poiskali po stolpcu `book.author_id`. Metoda Ref() vrne primerek ActiveRow ali nič, če ni ustreznega vnosa. Vrnjena vrstica je primerek ActiveRow, zato lahko z njo delamo enako kot z vnosom knjige. +Ta metoda posodobi samo eno določeno vrstico v podatkovni bazi. Za množično posodabljanje več vrstic uporabite metodo [#Selection::update()]. + + +ActiveRow::delete() .[method] +----------------------------- + +Izbriše vrstico iz podatkovne baze, ki jo predstavlja objekt `ActiveRow`. ```php -$author = $book->ref('author', 'author_id'); -$author->name; -$author->born; +$book = $explorer->table('book')->get(1); +$book->delete(); // Izbriše knjigo z ID 1 +``` + +Ta metoda briše samo eno določeno vrstico v podatkovni bazi. Za množično brisanje več vrstic uporabite metodo [#Selection::delete()]. + + +Povezave med tabelami +===================== + +V relacijskih podatkovnih bazah so podatki razdeljeni na več tabel in medsebojno povezani s pomočjo tujih ključev. Nette Database Explorer prinaša revolucionaren način dela s temi povezavami - brez pisanja JOIN poizvedb in potrebe po kakršnikoli konfiguraciji ali generiranju. + +Za ilustracijo dela s povezavami bomo uporabili primer podatkovne baze knjig ([najdete ga na GitHubu |https://github.com/nette-examples/books]). V podatkovni bazi imamo tabele: + +- `author` - pisatelji in prevajalci (stolpci `id`, `name`, `web`, `born`) +- `book` - knjige (stolpci `id`, `author_id`, `translator_id`, `title`, `sequel_id`) +- `tag` - oznake (stolpci `id`, `name`) +- `book_tag` - povezovalna tabela med knjigami in oznakami (stolpci `book_id`, `tag_id`) + +[* db-schema-1-.webp *] *** Struktura podatkovne baze .<> + +V našem primeru podatkovne baze knjig najdemo več tipov odnosov (čeprav je model poenostavljen v primerjavi z realnostjo): + +- Ena-proti-mnogo 1:N – vsaka knjiga **ima enega** avtorja, avtor lahko napiše **več** knjig +- Nič-proti-mnogo 0:N – knjiga **lahko ima** prevajalca, prevajalec lahko prevede **več** knjig +- Nič-proti-ena 0:1 – knjiga **lahko ima** naslednji del +- Mnogo-proti-mnogo M:N – knjiga **lahko ima več** oznak in oznaka je lahko dodeljena **več** knjigam -// ali neposredno -$book->ref('author', 'author_id')->name; -$book->ref('author', 'author_id')->born; +V teh odnosih vedno obstaja nadrejena in podrejena tabela. Na primer v odnosu med avtorjem in knjigo je tabela `author` nadrejena in `book` podrejena - lahko si predstavljamo, da knjiga vedno »pripada« nekemu avtorju. To se odraža tudi v strukturi podatkovne baze: podrejena tabela `book` vsebuje tuji ključ `author_id`, ki se nanaša na nadrejeno tabelo `author`. + +Če moramo izpisati knjige vključno z imeni njihovih avtorjev, imamo dve možnosti. Ali podatke pridobimo z eno samo SQL poizvedbo s pomočjo JOIN: + +```sql +SELECT book.*, author.name FROM book LEFT JOIN author ON book.author_id = author.id +``` + +Ali pa podatke naložimo v dveh korakih - najprej knjige in nato njihove avtorje - in jih nato v PHP sestavimo: + +```sql +SELECT * FROM book; +SELECT * FROM author WHERE id IN (1, 2, 3); -- id-ji avtorjev pridobljenih knjig ``` -Knjiga ima tudi en prevajalnik, zato je pridobivanje imena prevajalnika precej enostavno. +Drugi pristop je dejansko učinkovitejši, čeprav se to morda zdi presenetljivo. Podatki so naloženi samo enkrat in jih je mogoče bolje izkoristiti v predpomnilniku. Prav na ta način deluje Nette Database Explorer - vse rešuje pod površjem in vam ponuja eleganten API: + ```php -$book->ref('author', 'translator_id')->name +$books = $explorer->table('book'); +foreach ($books as $book) { + echo 'title: ' . $book->title; + echo 'written by: ' . $book->author->name; // $book->author je zapis iz tabele 'author' + echo 'translated by: ' . $book->translator?->name; +} ``` -Vse to je v redu, vendar je nekoliko okorno, se vam ne zdi? Raziskovalec zbirke podatkov že vsebuje definicije tujih ključev, zakaj jih torej ne bi uporabljali samodejno? Naredimo to! -Če kličemo lastnost, ki ne obstaja, ActiveRow poskuša ime kličoče lastnosti razrešiti kot relacijo 'ima eno'. Pridobitev te lastnosti je enaka klicu metode ref() s samo enim argumentom. Edini argument bomo imenovali **ključ**. Ključ bo razrešen na določeno relacijo tujega ključa. Predani ključ se primerja s stolpci vrstice, in če se ujema, se za pridobivanje podatkov iz povezane ciljne tabele uporabi tuji ključ, ki je opredeljen v ujemajočem se stolpcu. Oglejte si primer: +Dostop do nadrejene tabele +-------------------------- + +Dostop do nadrejene tabele je neposreden. Gre za odnose kot *knjiga ima avtorja* ali *knjiga lahko ima prevajalca*. Povezan zapis pridobimo prek lastnosti objekta ActiveRow - njeno ime ustreza imenu stolpca s tujim ključem brez `_id`: ```php -$book->author->name; -// enako kot -$book->ref('author')->name; +$book = $explorer->table('book')->get(1); +echo $book->author->name; // najde avtorja po stolpcu author_id +echo $book->translator?->name; // najde prevajalca po stolpcu translator_id ``` -Primer: Primerek ActiveRow nima stolpca avtor. V vseh stolpcih knjige se išče ujemanje s *ključ*. Ujemanje v tem primeru pomeni, da mora ime stolpca vsebovati ključ. V zgornjem primeru torej stolpec `author_id` vsebuje niz "avtor" in se zato ujema s ključem "avtor". Če želite pridobiti prevajalca knjige, lahko kot ključ uporabite npr. 'translator', saj se bo ključ 'translator' ujemal s stolpcem `translator_id`. Več o logiki ujemanja ključev najdete v poglavju [Združevanje izrazov |#joining-key]. +Ko dostopamo do lastnosti `$book->author`, Explorer v tabeli `book` išče stolpec, katerega ime vsebuje niz `author` (torej `author_id`). Po vrednosti v tem stolpcu naloži ustrezen zapis iz tabele `author` in ga vrne kot `ActiveRow`. Podobno deluje tudi `$book->translator`, ki uporabi stolpec `translator_id`. Ker stolpec `translator_id` lahko vsebuje `null`, v kodi uporabimo operator `?->`. + +Alternativno pot ponuja metoda `ref()`, ki sprejme dva argumenta, ime ciljne tabele in ime povezovalnega stolpca, ter vrne instanco `ActiveRow` ali `null`: ```php -echo $book->title . ': '; -echo $book->author->name; -if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; -} +echo $book->ref('author', 'author_id')->name; // povezava na avtorja +echo $book->ref('author', 'translator_id')->name; // povezava na prevajalca ``` -Če želite pridobiti več knjig, morate uporabiti enak pristop. Nette Database Explorer bo poiskal avtorje in prevajalce za vse poiskane knjige naenkrat. +Metoda `ref()` je koristna, če ni mogoče uporabiti dostopa prek lastnosti, ker tabela vsebuje stolpec z istim imenom (tj. `author`). V ostalih primerih je priporočljivo uporabljati dostop prek lastnosti, ki je bolj berljiv. + +Explorer samodejno optimizira podatkovne poizvedbe. Ko prehajamo skozi knjige v zanki in dostopamo do njihovih povezanih zapisov (avtorjev, prevajalcev), Explorer ne generira poizvedbe za vsako knjigo posebej. Namesto tega izvede samo en SELECT za vsak tip povezave, s čimer bistveno zmanjša obremenitev podatkovne baze. Na primer: ```php $books = $explorer->table('book'); foreach ($books as $book) { echo $book->title . ': '; echo $book->author->name; - if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; - } + echo $book->translator?->name; } ``` -Koda bo izvedla samo te tri poizvedbe: +Ta koda pokliče samo te tri bliskovite poizvedbe v podatkovno bazo: + ```sql SELECT * FROM `book`; -SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- ids of fetched books from author_id column -SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- ids of fetched books from translator_id column +SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- id iz stolpca author_id izbranih knjig +SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- id iz stolpca translator_id izbranih knjig ``` +.[note] +Logika iskanja povezovalnega stolpca je določena z implementacijo [Conventions |api:Nette\Database\Conventions]. Priporočamo uporabo [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions], ki analizira tuje ključe in omogoča enostavno delo z obstoječimi relacijami med tabelami. -Ima veliko relacij .[#toc-has-many-relation] --------------------------------------------- -Razmerje "ima veliko" je samo obrnjeno razmerje "ima eno". Avtor je napisal veliko knjig. Avtor *je* prevedel *mnogo* knjig. Kot lahko vidite, je ta vrsta relacije nekoliko težja, saj je relacija 'poimenovana' ('napisal', 'prevedel'). Primer ActiveRow ima metodo `related()`, ki vrne polje povezanih vnosov. Vnosi so prav tako primerki ActiveRow. Oglejte si primer spodaj: +Dostop do podrejene tabele +-------------------------- + +Dostop do podrejene tabele deluje v obratni smeri. Zdaj se sprašujemo *katere knjige je napisal ta avtor* ali *prevedel ta prevajalec*. Za ta tip poizvedbe uporabljamo metodo `related()`, ki vrne `Selection` s povezanimi zapisi. Poglejmo si primer: ```php -$author = $explorer->table('author')->get(11); -echo $author->name . ' has written:'; +$author = $explorer->table('author')->get(1); +// Izpiše vse knjige avtorja foreach ($author->related('book.author_id') as $book) { - echo $book->title; + echo "Napisal: $book->title"; } -echo 'and translated:'; +// Izpiše vse knjige, ki jih je avtor prevedel foreach ($author->related('book.translator_id') as $book) { - echo $book->title; + echo "Prevedel: $book->title"; } ``` -Metoda `related()` Metoda sprejme celoten opis združevanja, posredovan kot dva argumenta ali kot en argument, združen s piko. Prvi argument je ciljna tabela, drugi pa ciljni stolpec. +Metoda `related()` sprejme opis povezave kot en argument s pikčasto notacijo ali kot dva ločena argumenta: ```php -$author->related('book.translator_id'); -// enako kot -$author->related('book', 'translator_id'); +$author->related('book.translator_id'); // en argument +$author->related('book', 'translator_id'); // dva argumenta ``` -Uporabite lahko hevristiko Nette Database Explorerja, ki temelji na tujih ključih, in navedete samo argument **ključ**. Ključ bo primerjan z vsemi tujimi ključi, ki kažejo na trenutno tabelo (`author` tabela). Če se ujemajo, bo Nette Database Explorer uporabil ta tuji ključ, v nasprotnem primeru bo vrgel [Nette\InvalidArgumentException |api:Nette\InvalidArgumentException] ali [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. Več o logiki ujemanja ključev najdete v poglavju [Združevanje izrazov |#joining-key]. +Explorer zna samodejno zaznati pravilen povezovalni stolpec na podlagi imena nadrejene tabele. V tem primeru se povezuje prek stolpca `book.author_id`, ker je ime izvorne tabele `author`: -Seveda lahko pokličete sorodne metode za vse pridobljene avtorje, Nette Database Explorer pa bo ponovno pridobil ustrezne knjige naenkrat. +```php +$author->related('book'); // uporabi book.author_id +``` + +Če bi obstajalo več možnih povezav, Explorer vrže izjemo [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. + +Metodo `related()` lahko seveda uporabimo tudi pri prehajanju skozi več zapisov v zanki in Explorer tudi v tem primeru samodejno optimizira poizvedbe: ```php $authors = $explorer->table('author'); foreach ($authors as $author) { - echo $author->name . ' has written:'; + echo $author->name . ' napisal:'; foreach ($author->related('book') as $book) { - $book->title; + echo $book->title; } } ``` -Zgornji primer bo izvedel samo dve poizvedbi: +Ta koda generira samo dve bliskoviti SQL poizvedbi: ```sql SELECT * FROM `author`; -SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- ids of fetched authors +SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- id izbranih avtorjev +``` + + +Povezava Mnogo-proti-mnogo +-------------------------- + +Za povezavo mnogo-proti-mnogo (M:N) je potrebna obstoj povezovalne tabele (v našem primeru `book_tag`), ki vsebuje dva stolpca s tujima ključema (`book_id`, `tag_id`). Vsak od teh stolpcev se nanaša na primarni ključ ene od povezanih tabel. Za pridobitev povezanih podatkov najprej pridobimo zapise iz povezovalne tabele s pomočjo `related('book_tag')` in nato nadaljujemo k ciljnim podatkom: + +```php +$book = $explorer->table('book')->get(1); +// izpiše imena oznak, dodeljenih knjigi +foreach ($book->related('book_tag') as $bookTag) { + echo $bookTag->tag->name; // izpiše ime oznake prek povezovalne tabele +} + +$tag = $explorer->table('tag')->get(1); +// ali obratno: izpiše imena knjig, označenih s to oznako +foreach ($tag->related('book_tag') as $bookTag) { + echo $bookTag->book->title; // izpiše ime knjige +} ``` +Explorer spet optimizira SQL poizvedbe v učinkovito obliko: + +```sql +SELECT * FROM `book`; +SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 2, ...)); -- id izbranih knjig +SELECT * FROM `tag` WHERE (`tag`.`id` IN (1, 2, ...)); -- id oznak, najdenih v book_tag +``` -Ročno ustvarjanje raziskovalca .[#toc-creating-explorer-manually] -================================================================= -Povezavo s podatkovno zbirko lahko ustvarite s konfiguracijo aplikacije. V takih primerih se ustvari storitev `Nette\Database\Explorer`, ki jo je mogoče posredovati kot odvisnost z uporabo vsebnika DI. +Poizvedovanje prek povezanih tabel +---------------------------------- -Če pa se Nette Database Explorer uporablja kot samostojno orodje, je treba primerek objekta `Nette\Database\Explorer` ustvariti ročno. +V metodah `where()`, `select()`, `order()` in `group()` lahko uporabljamo posebne notacije za dostop do stolpcev iz drugih tabel. Explorer samodejno ustvari potrebne JOINe. + +**Pikčasta notacija** (`nadrejena_tabela.stolpec`) se uporablja za odnos 1:N z vidika podrejene tabele: ```php -// $storage implements Nette\Caching\Storage: -$storage = new Nette\Caching\Storages\FileStorage($tempDir); -$connection = new Nette\Database\Connection($dsn, $user, $password); -$structure = new Nette\Database\Structure($connection, $storage); -$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); -$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +$books = $explorer->table('book'); + +// Najde knjige, katerih avtor ima ime, ki se začne na 'Jon' +$books->where('author.name LIKE ?', 'Jon%'); + +// Razvrsti knjige po imenu avtorja padajoče +$books->order('author.name DESC'); + +// Izpiše naslov knjige in ime avtorja +$books->select('book.title, author.name'); ``` + +**Dvopična notacija** (`:podrejena_tabela.stolpec`) se uporablja za odnos 1:N z vidika nadrejene tabele: + +```php +$authors = $explorer->table('author'); + +// Najde avtorje, ki so napisali knjigo s 'PHP' v naslovu +$authors->where(':book.title LIKE ?', '%PHP%'); + +// Prešteje število knjig za vsakega avtorja +$authors->select('*, COUNT(:book.id) AS book_count') + ->group('author.id'); +``` + +V zgornjem primeru z dvopično notacijo (`:book.title`) ni določen stolpec s tujim ključem. Explorer samodejno zazna pravilen stolpec na podlagi imena nadrejene tabele. V tem primeru se povezuje prek stolpca `book.author_id`, ker je ime izvorne tabele `author`. Če bi obstajalo več možnih povezav, Explorer vrže izjemo [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. + +Povezovalni stolpec lahko eksplicitno navedemo v oklepaju: + +```php +// Najde avtorje, ki so prevedli knjigo s 'PHP' v naslovu +$authors->where(':book(translator_id).title LIKE ?', '%PHP%'); +``` + +Notacije lahko verižimo za dostop prek več tabel: + +```php +// Najde avtorje knjig, označenih z oznako 'PHP' +$authors->where(':book:book_tag.tag.name', 'PHP') + ->group('author.id'); +``` + + +Razširitev pogojev za JOIN +-------------------------- + +Metoda `joinWhere()` razširja pogoje, ki se navajajo pri povezovanju tabel v SQL za ključno besedo `ON`. + +Recimo, da želimo najti knjige, prevedene s strani določenega prevajalca: + +```php +// Najde knjige, prevedene s strani prevajalca z imenom 'David' +$books = $explorer->table('book') + ->joinWhere('translator', 'translator.name', 'David'); +// LEFT JOIN author translator ON book.translator_id = translator.id AND (translator.name = 'David') +``` + +V pogoju `joinWhere()` lahko uporabljamo enake konstrukcije kot v metodi `where()` - operatorje, nadomestne vprašaje, polja vrednosti ali SQL izraze. + +Za kompleksnejše poizvedbe z več JOINi lahko definiramo aliase tabel: + +```php +$tags = $explorer->table('tag') + ->joinWhere(':book_tag.book.author', 'book_author.born < ?', 1950) + ->alias(':book_tag.book.author', 'book_author'); +// LEFT JOIN `book_tag` ON `tag`.`id` = `book_tag`.`tag_id` +// LEFT JOIN `book` ON `book_tag`.`book_id` = `book`.`id` +// LEFT JOIN `author` `book_author` ON `book`.`author_id` = `book_author`.`id` +// AND (`book_author`.`born` < 1950) +``` + +Opazite, da medtem ko metoda `where()` dodaja pogoje v klavzulo `WHERE`, metoda `joinWhere()` razširja pogoje v klavzuli `ON` pri povezovanju tabel. diff --git a/database/sl/guide.texy b/database/sl/guide.texy new file mode 100644 index 0000000000..4ef0012d4d --- /dev/null +++ b/database/sl/guide.texy @@ -0,0 +1,216 @@ +Nette Database +************** + +.[perex] +Nette Database je zmogljiva in elegantna podatkovna plast za PHP s poudarkom na preprostosti in pametnih funkcijah. Ponuja dva načina dela z bazo podatkov - [Explorer] za hiter razvoj aplikacij ali [SQL pristop |SQL way] za neposredno delo s poizvedbami. + +<div class="grid gap-3"> +<div> + + +[SQL pristop |SQL way] +====================== +- Varne parametrizirane poizvedbe +- Natančen nadzor nad obliko SQL poizvedb +- Ko pišete kompleksne poizvedbe z naprednimi funkcijami +- Optimizirate zmogljivost s specifičnimi SQL funkcijami + +</div> + +<div> + + +[Explorer] +========== +- Hitro razvijate brez pisanja SQL +- Intuitivno delo z relacijami med tabelami +- Cenili boste samodejno optimizacijo poizvedb +- Primerno za hitro in udobno delo z bazo podatkov + +</div> + +</div> + + +Namestitev +========== + +Knjižnico prenesete in namestite z orodjem [Composer|best-practices:composer]: + +```shell +composer require nette/database +``` + + +Podprte podatkovne baze +======================= + +Nette Database podpira naslednje podatkovne baze: + +|* Podatkovni strežnik |* Ime DSN |* Podpora v Explorerju +|---------------------|-------------|----------------------- +| MySQL (>= 5.1) | mysql | DA +| PostgreSQL (>= 9.0) | pgsql | DA +| Sqlite 3 (>= 3.8) | sqlite | DA +| Oracle | oci | - +| MS SQL (PDO_SQLSRV) | sqlsrv | DA +| MS SQL (PDO_DBLIB) | mssql | - +| ODBC | odbc | - + + +Dva pristopa k bazi podatkov +============================ + +Nette Database vam daje izbiro: lahko pišete SQL poizvedbe neposredno (SQL pristop) ali pa jih pustite samodejno generirati (Explorer). Poglejmo, kako oba pristopa rešujeta enake naloge: + +[SQL pristop|sql way] - SQL poizvedbe + +```php +// vstavljanje zapisa +$database->query('INSERT INTO books', [ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// pridobivanje zapisov: avtorji knjig +$result = $database->query(' + SELECT authors.*, COUNT(books.id) AS books_count + FROM authors + LEFT JOIN books ON authors.id = books.author_id + WHERE authors.active = 1 + GROUP BY authors.id +'); + +// izpis (ni optimalno, generira N dodatnih poizvedb) +foreach ($result as $author) { + $books = $database->query(' + SELECT * FROM books + WHERE author_id = ? + ORDER BY published_at DESC + ', $author->id); + + echo "Avtor $author->name je napisal $author->books_count knjig:\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +[Pristop Explorer|explorer] - samodejno generiranje SQL + +```php +// vstavljanje zapisa +$database->table('books')->insert([ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// pridobivanje zapisov: avtorji knjig +$authors = $database->table('authors') + ->where('active', 1); + +// izpis (samodejno generira samo 2 optimizirani poizvedbi) +foreach ($authors as $author) { + $books = $author->related('books') + ->order('published_at DESC'); + + echo "Avtor $author->name je napisal {$books->count()} knjig:\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +Pristop Explorer samodejno generira in optimizira SQL poizvedbe. V navedenem primeru SQL pristop generira N+1 poizvedb (eno za avtorje in nato eno za knjige vsakega avtorja), medtem ko Explorer samodejno optimizira poizvedbe in izvede samo dve - eno za avtorje in eno za vse njihove knjige. + +Oba pristopa lahko v aplikaciji poljubno kombinirate po potrebi. + + +Povezava in konfiguracija +========================= + +Za povezavo z bazo podatkov zadostuje ustvariti instanco razreda [api:Nette\Database\Connection]: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password); +``` + +Parameter `$dsn` (data source name) je enak, [kot ga uporablja PDO |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], npr. `host=127.0.0.1;dbname=test`. V primeru napake vrže izjemo `Nette\Database\ConnectionException`. + +Vendar pa spretnejši način ponuja [konfiguracija aplikacije |configuration], kamor zadostuje dodati sekcijo `database` in ustvarijo se potrebni objekti ter tudi podatkovna plošča v [Tracy |tracy:] baru. + +```neon +database: + dsn: 'mysql:host=127.0.0.1;dbname=test' + user: root + password: password +``` + +Nato objekt povezave [pridobimo kot storitev iz DI vsebnika |dependency-injection:passing-dependencies], npr.: + +```php +class Model +{ + public function __construct( + // ali Nette\Database\Explorer + private Nette\Database\Connection $database, + ) { + } +} +``` + +Več informacij o [konfiguraciji baze podatkov|configuration]. + + +Ročno ustvarjanje Explorerja +---------------------------- + +Če ne uporabljate Nette DI vsebnika, lahko instanco `Nette\Database\Explorer` ustvarite ročno: + +```php +// povezava z bazo podatkov +$connection = new Nette\Database\Connection('mysql:host=127.0.0.1;dbname=mydatabase', 'user', 'password'); +// shramba za predpomnilnik, implementira Nette\Caching\Storage, npr.: +$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp/dir'); +// skrbi za refleksijo strukture baze podatkov +$structure = new Nette\Database\Structure($connection, $storage); +// definira pravila za preslikavo imen tabel, stolpcev in tujih ključev +$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); +$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +``` + + +Upravljanje povezave +==================== + +Pri ustvarjanju objekta `Connection` se samodejno vzpostavi povezava. Če želite povezavo odložiti, uporabite lazy način - tega vklopite v [konfiguraciji|configuration] z nastavitvijo `lazy` ali takole: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password, ['lazy' => true]); +``` + +Za upravljanje povezave uporabite metode `connect()`, `disconnect()` in `reconnect()`. +- `connect()` ustvari povezavo, če še ne obstaja, pri čemer lahko vrže izjemo `Nette\Database\ConnectionException`. +- `disconnect()` prekine trenutno povezavo z bazo podatkov. +- `reconnect()` izvede prekinitev in nato ponovno vzpostavitev povezave z bazo podatkov. Ta metoda lahko prav tako vrže izjemo `Nette\Database\ConnectionException`. + +Poleg tega lahko spremljate dogodke, povezane s povezavo, z uporabo dogodka `onConnect`, ki je polje povratnih klicev (callback), ki se pokličejo po vzpostavitvi povezave z bazo podatkov. + +```php +// izvede se po povezavi z bazo podatkov +$database->onConnect[] = function($database) { + echo "Povezano z bazo podatkov"; +}; +``` + + +Tracy Debug Bar +=============== + +Če uporabljate [Tracy |tracy:], se samodejno aktivira plošča Database v Debug baru, ki prikazuje vse izvedene poizvedbe, njihove parametre, čas izvedbe in mesto v kodi, kjer so bile poklicane. + +[* db-panel.webp *] diff --git a/database/sl/mapping.texy b/database/sl/mapping.texy new file mode 100644 index 0000000000..132f47367e --- /dev/null +++ b/database/sl/mapping.texy @@ -0,0 +1,55 @@ +Pretvorba tipov +*************** + +.[perex] +Nette Database samodejno pretvarja vrednosti, vrnjene iz baze podatkov, v ustrezne PHP tipe. + + +Datum in čas +------------ + +Časovni podatki se pretvorijo v objekte `Nette\Utils\DateTime`. Če želite, da se časovni podatki pretvorijo v nespremenljive objekte `Nette\Database\DateTime`, nastavite v [konfiguraciji|configuration] možnost `newDateTime` na true. + +```php +$row = $database->fetch('SELECT created_at FROM articles'); +echo $row->created_at instanceof DateTime; // true +echo $row->created_at->format('j. n. Y'); +``` + +V primeru MySQL pretvarja podatkovni tip `TIME` v objekte `DateInterval`. + + +Booleove vrednosti +------------------ + +Booleove vrednosti se samodejno pretvorijo v `true` ali `false`. Pri MySQL se pretvarja `TINYINT(1)`, če nastavimo v [konfiguraciji|configuration] `convertBoolean`. + +```php +$row = $database->fetch('SELECT is_published FROM articles'); +echo gettype($row->is_published); // 'boolean' +``` + + +Številske vrednosti +------------------- + +Številske vrednosti se pretvorijo v `int` ali `float` glede na tip stolpca v bazi podatkov: + +```php +$row = $database->fetch('SELECT id, price FROM products'); +echo gettype($row->id); // integer +echo gettype($row->price); // float +``` + + +Lastna normalizacija +-------------------- + +Z metodo `setRowNormalizer(?callable $normalizer)` lahko nastavite lastno funkcijo za transformacijo vrstic iz baze podatkov. To je koristno na primer za samodejno pretvorbo podatkovnih tipov. + +```php +$database->setRowNormalizer(function(array $row, ResultSet $resultSet): array { + // tukaj poteka pretvorba tipov + return $row; +}); +``` diff --git a/database/sl/reflection.texy b/database/sl/reflection.texy new file mode 100644 index 0000000000..a5d4cb846a --- /dev/null +++ b/database/sl/reflection.texy @@ -0,0 +1,125 @@ +Refleksija strukture +******************** + +.{data-version:3.2.1} +Nette Database ponuja orodja za introspekcijo strukture baze podatkov z uporabo razreda [api:Nette\Database\Reflection]. Ta omogoča pridobivanje informacij o tabelah, stolpcih, indeksih in tujih ključih. Refleksijo lahko uporabite za generiranje shem, ustvarjanje fleksibilnih aplikacij, ki delajo z bazo podatkov, ali splošnih orodij za baze podatkov. + +Objekt refleksije pridobimo iz instance povezave z bazo podatkov: + +```php +$reflection = $database->getReflection(); +``` + + +Pridobivanje tabel +------------------ + +Readonly lastnost `$reflection->tables` vsebuje asociativno polje vseh tabel v bazi podatkov: + +```php +// Izpis imen vseh tabel +foreach ($reflection->tables as $name => $table) { + echo $name . "\n"; +} +``` + +Na voljo sta še dve metodi: + +```php +// Preverjanje obstoja tabele +if ($reflection->hasTable('users')) { + echo "Tabela users obstaja"; +} + +// Vrne objekt tabele; če ne obstaja, vrže izjemo +$table = $reflection->getTable('users'); +``` + + +Informacije o tabeli +-------------------- + +Tabela je predstavljena z objektom [Table|api:Nette\Database\Reflection\Table], ki ponuja naslednje readonly lastnosti: + +- `$name: string` – ime tabele +- `$view: bool` – ali gre za pogled (view) +- `$fullName: ?string` – polno ime tabele, vključno s shemo (če obstaja) +- `$columns: array<string, Column>` – asociativno polje stolpcev tabele +- `$indexes: Index[]` – polje indeksov tabele +- `$primaryKey: ?Index` – primarni ključ tabele ali null +- `$foreignKeys: ForeignKey[]` – polje tujih ključev tabele + + +Stolpci +------- + +Lastnost `columns` tabele ponuja asociativno polje stolpcev, kjer je ključ ime stolpca in vrednost instanca [Column|api:Nette\Database\Reflection\Column] s temi lastnostmi: + +- `$name: string` – ime stolpca +- `$table: ?Table` – referenca na tabelo stolpca +- `$nativeType: string` – nativni podatkovni tip baze podatkov +- `$size: ?int` – velikost/dolžina tipa +- `$nullable: bool` – ali lahko stolpec vsebuje NULL +- `$default: mixed` – privzeta vrednost stolpca +- `$autoIncrement: bool` – ali je stolpec auto-increment +- `$primary: bool` – ali je del primarnega ključa +- `$vendor: array` – dodatni metapodatki, specifični za dani sistem baze podatkov + +```php +foreach ($table->columns as $name => $column) { + echo "Stolpec: $name\n"; + echo "Tip: {$column->nativeType}\n"; + echo "Nullable: " . ($column->nullable ? 'Da' : 'Ne') . "\n"; +} +``` + + +Indeksi +------- + +Lastnost `indexes` tabele ponuja polje indeksov, kjer je vsak indeks instanca [Index|api:Nette\Database\Reflection\Index] s temi lastnostmi: + +- `$columns: Column[]` – polje stolpcev, ki tvorijo indeks +- `$unique: bool` – ali je indeks unikaten +- `$primary: bool` – ali gre za primarni ključ +- `$name: ?string` – ime indeksa + +Primarni ključ tabele lahko pridobimo z lastnostjo `primaryKey`, ki vrne bodisi objekt `Index` ali `null` v primeru, da tabela nima primarnega ključa. + +```php +// Izpis indeksov +foreach ($table->indexes as $index) { + $columns = implode(', ', array_map(fn($col) => $col->name, $index->columns)); + echo "Indeks" . ($index->name ? " {$index->name}" : '') . ":\n"; + echo " Stolpci: $columns\n"; + echo " Unique: " . ($index->unique ? 'Da' : 'Ne') . "\n"; +} + +// Izpis primarnega ključa +if ($primaryKey = $table->primaryKey) { + $columns = implode(', ', array_map(fn($col) => $col->name, $primaryKey->columns)); + echo "Primarni ključ: $columns\n"; +} +``` + + +Tuji ključi +----------- + +Lastnost `foreignKeys` tabele ponuja polje tujih ključev, kjer je vsak tuji ključ instanca [ForeignKey|api:Nette\Database\Reflection\ForeignKey] s temi lastnostmi: + +- `$foreignTable: Table` – referencirana tabela +- `$localColumns: Column[]` – polje lokalnih stolpcev +- `$foreignColumns: Column[]` – polje referenciranih stolpcev +- `$name: ?string` – ime tujega ključa + +```php +// Izpis tujih ključev +foreach ($table->foreignKeys as $fk) { + $localCols = implode(', ', array_map(fn($col) => $col->name, $fk->localColumns)); + $foreignCols = implode(', ', array_map(fn($col) => $col->name, $fk->foreignColumns)); + + echo "FK" . ($fk->name ? " {$fk->name}" : '') . ":\n"; + echo " $localCols -> {$fk->foreignTable->name}($foreignCols)\n"; +} +``` diff --git a/database/sl/security.texy b/database/sl/security.texy new file mode 100644 index 0000000000..9dda20fdc4 --- /dev/null +++ b/database/sl/security.texy @@ -0,0 +1,185 @@ +Varnostna tveganja +****************** + +<div class=perex> + +Baza podatkov pogosto vsebuje občutljive podatke in omogoča izvajanje nevarnih operacij. Za varno delo z Nette Database je ključno: + +- Razumeti razliko med varnim in nevarnim API-jem +- Uporabljati parametrizirane poizvedbe +- Pravilno validirati vhodne podatke + +</div> + + +Kaj je SQL Injection? +===================== + +SQL injection je najresnejše varnostno tveganje pri delu z bazo podatkov. Nastane, ko neobdelan vnos uporabnika postane del SQL poizvedbe. Napadalec lahko vstavi lastne SQL ukaze in s tem: +- Pridobi nepooblaščen dostop do podatkov +- Spremeni ali izbriše podatke v bazi podatkov +- Obide avtentikacijo + +```php +// ❌ NEVARNA KODA - ranljiva za SQL injection +$database->query("SELECT * FROM users WHERE name = '$_GET[name]'"); + +// Napadalec lahko vnese na primer vrednost: ' OR '1'='1 +// Rezultatna poizvedba bo potem: SELECT * FROM users WHERE name = '' OR '1'='1' +// Kar vrne vse uporabnike +``` + +Enako velja tudi za Database Explorer: + +```php +// ❌ NEVARNA KODA - ranljiva za SQL injection +$table->where('name = ' . $_GET['name']); +$table->where("name = '$_GET[name]'"); +``` + + +Parametrizirane poizvedbe +========================= + +Osnovna obramba pred SQL injection so parametrizirane poizvedbe. Nette Database ponuja več načinov njihove uporabe. + +Najenostavnejši način je uporaba **nadomestnih vprašajev**: + +```php +// ✅ Varna parametrizirana poizvedba +$database->query('SELECT * FROM users WHERE name = ?', $name); + +// ✅ Varen pogoj v Explorerju +$table->where('name = ?', $name); +``` + +To velja za vse druge metode v [Database Explorer|explorer], ki omogočajo vstavljanje izrazov z nadomestnimi vprašaji in parametri. + +Za ukaze INSERT, UPDATE ali klavzulo WHERE lahko vrednosti posredujemo v polju: + +```php +// ✅ Varen INSERT +$database->query('INSERT INTO users', [ + 'name' => $name, + 'email' => $email, +]); + +// ✅ Varen INSERT v Explorerju +$table->insert([ + 'name' => $name, + 'email' => $email, +]); +``` + + +Validacija vrednosti parametrov +=============================== + +Parametrizirane poizvedbe so osnovni gradnik varnega dela z bazo podatkov. Vendar pa morajo vrednosti, ki jih vstavljamo vanje, preiti več ravni preverjanj: + + +Tipska kontrola +--------------- + +**Najpomembnejše je zagotoviti pravilen podatkovni tip parametrov** - to je nujen pogoj za varno uporabo Nette Database. Baza podatkov predpostavlja, da imajo vsi vhodni podatki pravilen podatkovni tip, ki ustreza danemu stolpcu. + +Na primer, če bi bil `$name` v prejšnjih primerih nepričakovano polje namesto niza, bi Nette Database poskusila vstaviti vse njegove elemente v SQL poizvedbo, kar bi povzročilo napako. Zato **nikoli ne uporabljajte** nevalidiranih podatkov iz `$_GET`, `$_POST` ali `$_COOKIE` neposredno v poizvedbah baze podatkov. + + +Formatna kontrola +----------------- + +Na drugi ravni preverjamo format podatkov - na primer, ali so nizi v UTF-8 kodiranju in njihova dolžina ustreza definiciji stolpca, ali pa so številske vrednosti v dovoljenem obsegu za dani podatkovni tip stolpca. + +Pri tej ravni validacije se lahko delno zanesemo tudi na samo bazo podatkov - mnoge baze podatkov zavrnejo nevalidne podatke. Vendar pa se obnašanje lahko razlikuje, nekatere lahko dolge nize tiho skrajšajo ali števila izven obsega obrežejo. + + +Domenska kontrola +----------------- + +Tretjo raven predstavljajo logične kontrole, specifične za vašo aplikacijo. Na primer preverjanje, da vrednosti iz izbirnih polj ustrezajo ponujenim možnostim, da so števila v pričakovanem obsegu (npr. starost 0-150 let) ali da medsebojne odvisnosti med vrednostmi imajo smisel. + + +Priporočeni načini validacije +----------------------------- + +- Uporabljajte [Nette Obrazce|forms:], ki samodejno zagotovijo pravilno validacijo vseh vnosov +- Uporabljajte [Presenterje|application:] in navedite pri parametrih v `action*()` in `render*()` metodah podatkovne tipe +- Ali implementirajte lastno validacijsko plast z uporabo standardnih PHP orodij, kot je `filter_var()` + + +Varno delo s stolpci +==================== + +V prejšnjem odseku smo si ogledali, kako pravilno validirati vrednosti parametrov. Pri uporabi polj v SQL poizvedbah pa moramo enako pozornost posvetiti tudi njihovim ključem. + +```php +// ❌ NEVARNA KODA - niso obdelani ključi v polju +$database->query('INSERT INTO users', $_POST); +``` + +Pri ukazih INSERT in UPDATE je to temeljna varnostna napaka - napadalec lahko v bazo podatkov vstavi ali spremeni kateri koli stolpec. Lahko bi si na primer nastavil `is_admin = 1` ali vstavil poljubne podatke v občutljive stolpce (t.i. Mass Assignment Vulnerability). + +V pogojih WHERE je to še nevarnejše, saj lahko vsebujejo operatorje: + +```php +// ❌ NEVARNA KODA - niso obdelani ključi v polju +$_POST['salary >'] = 100000; +$database->query('SELECT * FROM users WHERE', $_POST); +// izvede poizvedbo WHERE (`salary` > 100000) +``` + +Napadalec lahko ta pristop izkoristi za sistematično ugotavljanje plač zaposlenih. Začne na primer s poizvedbo o plačah nad 100.000, nato pod 50.000 in s postopnim zoževanjem obsega lahko odkrije približne plače vseh zaposlenih. Ta tip napada se imenuje SQL enumeration. + +Metodi `where()` in `whereOr()` sta še [veliko bolj fleksibilni |explorer#where] in podpirata v ključih in vrednostih SQL izraze, vključno z operatorji in funkcijami. To daje napadalcu možnost izvedbe SQL injection: + +```php +// ❌ NEVARNA KODA - napadalec lahko vstavi lasten SQL +$_POST = ['0) UNION SELECT name, salary FROM users WHERE (1']; +$table->where($_POST); +// izvede poizvedbo WHERE (0) UNION SELECT name, salary FROM users WHERE (1) +``` + +Ta napad zaključi prvotni pogoj z `0)`, priključi lasten `SELECT` z `UNION`, da pridobi občutljive podatke iz tabele `users`, in zaključi sintaktično pravilno poizvedbo z `WHERE (1)`. + + +Beli seznam stolpcev +-------------------- + +Za varno delo z imeni stolpcev potrebujemo mehanizem, ki zagotavlja, da lahko uporabnik dela samo z dovoljenimi stolpci in ne more dodati lastnih. Lahko bi poskusili zaznati in blokirati nevarna imena stolpcev (črni seznam), vendar je ta pristop nezanesljiv - napadalec lahko vedno najde nov način, kako zapisati nevarno ime stolpca, ki ga nismo predvideli. + +Zato je veliko varneje obrniti logiko in definirati ekspliciten seznam dovoljenih stolpcev (beli seznam): + +```php +// Stolpci, ki jih lahko uporabnik ureja +$allowedColumns = ['name', 'email', 'active']; + +// Odstranimo vse nedovoljene stolpce iz vnosa +$filteredData = array_intersect_key($userData, array_flip($allowedColumns)); // array_flip for performance + +// ✅ Zdaj lahko varno uporabimo v poizvedbah, kot na primer: +$database->query('INSERT INTO users', $filteredData); +$table->update($filteredData); +$table->where($filteredData); +``` + + +Dinamični identifikatorji +========================= + +Za dinamična imena tabel in stolpcev uporabite nadomestni znak `?name`. Ta zagotavlja pravilno ubežanje identifikatorjev glede na sintakso dane baze podatkov (npr. z uporabo povratnih narekovajev v MySQL): + +```php +// ✅ Varna uporaba zaupanja vrednih identifikatorjev +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name', $column, $table); +// Rezultat v MySQL: SELECT `name` FROM `users` +``` + +Pomembno: simbol `?name` uporabljajte samo za zaupanja vredne vrednosti, definirane v kodi aplikacije. Za vrednosti od uporabnika ponovno uporabite [beli seznam |#Beli seznam stolpcev]. Sicer se izpostavljate varnostnim tveganjem: + +```php +// ❌ NEVARNO - nikoli ne uporabljajte vnosa od uporabnika +$database->query('SELECT ?name FROM users', $_GET['column']); +``` diff --git a/database/sl/sql-way.texy b/database/sl/sql-way.texy new file mode 100644 index 0000000000..e8857452ba --- /dev/null +++ b/database/sl/sql-way.texy @@ -0,0 +1,513 @@ +SQL pristop +*********** + +.[perex] +Nette Database ponuja dve poti: lahko pišete SQL poizvedbe sami (SQL pristop) ali pa jih pustite samodejno generirati (glej [Explorer |explorer]). SQL pristop vam daje popoln nadzor nad poizvedbami in hkrati zagotavlja njihovo varno sestavljanje. + +.[note] +Podrobnosti o povezavi in konfiguraciji podatkovne baze najdete v poglavju [Povezava in konfiguracija |guide#Povezava in konfiguracija]. + + +Osnovno poizvedovanje +===================== + +Za poizvedovanje v podatkovni bazi služi metoda `query()`. Ta vrne objekt [ResultSet |api:Nette\Database\ResultSet], ki predstavlja rezultat poizvedbe. V primeru napake metoda [vrže izjemo|exceptions]. Rezultat poizvedbe lahko prehajamo z zanko `foreach` ali uporabimo katero od [pomožnih funkcij |#Pridobivanje podatkov]. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; +} +``` + +Za varno vstavljanje vrednosti v SQL poizvedbe uporabljamo parametrizirane poizvedbe. Nette Database jih naredi maksimalno preproste - zadostuje, da za SQL poizvedbo dodamo vejico in vrednost: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Pri več parametrih imate dve možnosti zapisa. Lahko SQL poizvedbo "prepletate" s parametri: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age); +``` + +Ali pa najprej napišete celotno SQL poizvedbo in nato priključite vse parametre: + +```php +$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age); +``` + + +Zaščita pred SQL injection +========================== + +Zakaj je pomembno uporabljati parametrizirane poizvedbe? Ker vas ščitijo pred napadom, imenovanim SQL injection, pri katerem bi napadalec lahko podtaknil lastne SQL ukaze in s tem pridobil ali poškodoval podatke v podatkovni bazi. + +.[warning] +**Nikoli ne vstavljajte spremenljivk neposredno v SQL poizvedbo!** Vedno uporabljajte parametrizirane poizvedbe, ki vas ščitijo pred SQL injection. + +```php +// ❌ NEVARNA KODA - ranljiva za SQL injection +$database->query("SELECT * FROM users WHERE name = '$name'"); + +// ✅ Varna parametrizirana poizvedba +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Seznanite se z [možnimi varnostnimi tveganji |security]. + + +Tehnike poizvedovanja +===================== + + +Pogoji WHERE +------------ + +Pogoje WHERE lahko zapišete kot asociativno polje, kjer so ključi imena stolpcev in vrednosti podatki za primerjavo. Nette Database samodejno izbere najprimernejši SQL operator glede na tip vrednosti. + +```php +$database->query('SELECT * FROM users WHERE', [ + 'name' => 'John', + 'active' => true, +]); +// WHERE `name` = 'John' AND `active` = 1 +``` + +V ključu lahko tudi eksplicitno določite operator za primerjavo: + +```php +$database->query('SELECT * FROM users WHERE', [ + 'age >' => 25, // uporabi operator > + 'name LIKE' => '%John%', // uporabi operator LIKE + 'email NOT LIKE' => '%example.com%', // uporabi operator NOT LIKE +]); +// WHERE `age` > 25 AND `name` LIKE '%John%' AND `email` NOT LIKE '%example.com%' +``` + +Nette samodejno obravnava posebne primere, kot so `null` vrednosti ali polja. + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name' => 'Laptop', // uporabi operator = + 'category_id' => [1, 2, 3], // uporabi IN + 'description' => null, // uporabi IS NULL +]); +// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL +``` + +Za negativne pogoje uporabite operator `NOT`: + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name NOT' => 'Laptop', // uporabi operator <> + 'category_id NOT' => [1, 2, 3], // uporabi NOT IN + 'description NOT' => null, // uporabi IS NOT NULL + 'id' => [], // izpusti se +]); +// WHERE `name` <> 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL +``` + +Za združevanje pogojev se uporablja operator `AND`. To lahko spremenite z uporabo [nadomestnega znaka ?or |#Namigi za sestavljanje SQL]. + + +Pravila ORDER BY +---------------- + +Razvrščanje `ORDER BY` lahko zapišemo z uporabo polja. V ključih navedemo stolpce, vrednost pa bo boolean, ki določa, ali razvrščati naraščajoče: + +```php +$database->query('SELECT id FROM author ORDER BY', [ + 'id' => true, // naraščajoče + 'name' => false, // padajoče +]); +// SELECT id FROM author ORDER BY `id`, `name` DESC +``` + + +Vstavljanje podatkov (INSERT) +----------------------------- + +Za vstavljanje zapisov se uporablja SQL ukaz `INSERT`. + +```php +$values = [ + 'name' => 'John Doe', + 'email' => 'john@example.com', +]; +$database->query('INSERT INTO users ?', $values); +$userId = $database->getInsertId(); +``` + +Metoda `getInsertId()` vrne ID zadnje vstavljene vrstice. Pri nekaterih podatkovnih bazah (npr. PostgreSQL) je treba kot parameter določiti ime sekvence, iz katere naj se ID generira z uporabo `$database->getInsertId($sequenceId)`. + +Kot parametre lahko posredujemo tudi [#Posebne vrednosti] kot so datoteke, objekti DateTime ali naštevni tipi. + +Vstavljanje več zapisov hkrati: + +```php +$database->query('INSERT INTO users ?', [ + ['name' => 'User 1', 'email' => 'user1@mail.com'], + ['name' => 'User 2', 'email' => 'user2@mail.com'], +]); +``` + +Večkratni INSERT je veliko hitrejši, ker se izvede ena sama poizvedba podatkovne baze namesto mnogih posameznih. + +**Varnostno opozorilo:** Nikoli ne uporabljajte kot `$values` nevalidiranih podatkov. Seznanite se z [možnimi tveganji |security#Varno delo s stolpci]. + + +Posodabljanje podatkov (UPDATE) +------------------------------- + +Za posodabljanje zapisov se uporablja SQL ukaz `UPDATE`. + +```php +// Posodobitev enega zapisa +$values = [ + 'name' => 'John Smith', +]; +$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1); +``` + +Število prizadetih vrstic vrne `$result->getRowCount()`. + +Za UPDATE lahko uporabimo operatorja `+=` in `-=`: + +```php +$database->query('UPDATE users SET ? WHERE id = ?', [ + 'login_count+=' => 1, // inkrementacija login_count +], 1); +``` + +Primer vstavljanja ali urejanja zapisa, če že obstaja. Uporabimo tehniko `ON DUPLICATE KEY UPDATE`: + +```php +$values = [ + 'name' => $name, + 'year' => $year, +]; +$database->query('INSERT INTO users ? ON DUPLICATE KEY UPDATE ?', + $values + ['id' => $id], + $values, +); +// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) +// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 +``` + +Opazite, da Nette Database prepozna, v kakšnem kontekstu SQL ukaza vstavljamo parameter s poljem in glede na to iz njega sestavi SQL kodo. Tako je iz prvega polja sestavil `(id, name, year) VALUES (123, 'Jim', 1978)`, medtem ko je drugega pretvoril v obliko `name = 'Jim', year = 1978`. Podrobneje se temu posvečamo v delu [#Namigi za sestavljanje SQL]. + + +Brisanje podatkov (DELETE) +-------------------------- + +Za brisanje zapisov se uporablja SQL ukaz `DELETE`. Primer s pridobivanjem števila izbrisanih vrstic: + +```php +$count = $database->query('DELETE FROM users WHERE id = ?', 1) + ->getRowCount(); +``` + + +Namigi za sestavljanje SQL +-------------------------- + +Namig je poseben nadomestni znak v SQL poizvedbi, ki pove, kako naj se vrednost parametra prepiše v SQL izraz: + +| Namig | Opis | Samodejno se uporabi +|-----------|-------------------------------------------------|----------------------------- +| `?name` | uporabi za vstavljanje imena tabele ali stolpca | - +| `?values` | generira `(key, ...) VALUES (value, ...)` | `INSERT ... ?`, `REPLACE ... ?` +| `?set` | generira prirejanje `key = value, ...` | `SET ?`, `KEY UPDATE ?` +| `?and` | združi pogoje v polju z operatorjem `AND` | `WHERE ?`, `HAVING ?` +| `?or` | združi pogoje v polju z operatorjem `OR` | - +| `?order` | generira klavzulo `ORDER BY` | `ORDER BY ?`, `GROUP BY ?` + +Za dinamično vstavljanje imen tabel in stolpcev v poizvedbo služi nadomestni znak `?name`. Nette Database poskrbi za pravilno obdelavo identifikatorjev glede na konvencije dane podatkovne baze (npr. zapiranje v povratne narekovaje v MySQL). + +```php +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table); +// SELECT `name` FROM `users` WHERE id = 1 (v MySQL) +``` + +**Opozorilo:** simbol `?name` uporabljajte samo za imena tabel in stolpcev iz validiranih vnosov, sicer se izpostavljate [varnostnemu tveganju |security#Dinamični identifikatorji]. + +Drugih namigov običajno ni treba navajati, saj Nette pri sestavljanju SQL poizvedbe uporablja pametno samodejno zaznavanje (glej tretji stolpec tabele). Lahko pa ga uporabite na primer v situaciji, ko želite združiti pogoje z `OR` namesto `AND`: + +```php +$database->query('SELECT * FROM users WHERE ?or', [ + 'name' => 'John', + 'email' => 'john@example.com', +]); +// SELECT * FROM users WHERE `name` = 'John' OR `email` = 'john@example.com' +``` + + +Posebne vrednosti +----------------- + +Poleg običajnih skalarnih tipov (string, int, bool) lahko kot parametre posredujete tudi posebne vrednosti: + +- datoteke: `fopen('image.gif', 'r')` vstavi binarno vsebino datoteke +- datum in čas: objekti `DateTime` se pretvorijo v format podatkovne baze +- naštevni tipi: instance `enum` se pretvorijo v njihovo vrednost +- SQL literali: ustvarjeni z `Connection::literal('NOW()')` se vstavijo neposredno v poizvedbo + +```php +$database->query('INSERT INTO articles ?', [ + 'title' => 'My Article', + 'published_at' => new DateTime, + 'content' => fopen('image.png', 'r'), + 'state' => Status::Draft, +]); +``` + +Pri podatkovnih bazah, ki nimajo nativne podpore za podatkovni tip `datetime` (kot SQLite in Oracle), se `DateTime` pretvori v vrednost, določeno v [konfiguraciji podatkovne baze|configuration] z vnosom `formatDateTime` (privzeta vrednost je `U` - unix timestamp). + + +SQL literali +------------ + +V nekaterih primerih morate kot vrednost navesti neposredno SQL kodo, ki pa se ne sme razumeti kot niz in ubežati. Za to služijo objekti razreda `Nette\Database\SqlLiteral`. Ustvarja jih metoda `Connection::literal()`. + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + 'year >' => $database::literal('YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) +``` + +Ali alternativno: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) +``` + +SQL literali lahko vsebujejo parametre: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > ? AND year < ?', $min, $max), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) +``` + +Zaradi česar lahko ustvarjamo zanimive kombinacije: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('?or', [ + 'active' => true, + 'role' => $role, + ]), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') +``` + + +Pridobivanje podatkov +===================== + + +Bližnjice za SELECT poizvedbe +----------------------------- + +Za poenostavitev nalaganja podatkov `Connection` ponuja več bližnjic, ki kombinirajo klic `query()` z naslednjim `fetch*()`. Te metode sprejemajo enake parametre kot `query()`, torej SQL poizvedbo in neobvezne parametre. Popoln opis metod `fetch*()` najdete [spodaj |#fetch]. + +| `fetch($sql, ...$params): ?Row` | Izvede poizvedbo in vrne prvo vrstico kot objekt `Row` +| `fetchAll($sql, ...$params): array` | Izvede poizvedbo in vrne vse vrstice kot polje objektov `Row` +| `fetchPairs($sql, ...$params): array` | Izvede poizvedbo in vrne asociativno polje, kjer prvi stolpec predstavlja ključ in drugi vrednost +| `fetchField($sql, ...$params): mixed` | Izvede poizvedbo in vrne vrednost prvega polja iz prve vrstice +| `fetchList($sql, ...$params): ?array` | Izvede poizvedbo in vrne prvo vrstico kot indeksirano polje + +Primer: + +```php +// fetchField() - vrne vrednost prve celice +$count = $database->query('SELECT COUNT(*) FROM articles') + ->fetchField(); +``` + + +`foreach` - iteracija čez vrstice +--------------------------------- + +Po izvedbi poizvedbe se vrne objekt [ResultSet|api:Nette\Database\ResultSet], ki omogoča prehajanje rezultatov na več načinov. Najlažji način za izvedbo poizvedbe in pridobitev vrstic je iteracija v zanki `foreach`. Ta način je pomnilniško najbolj varčen, saj vrača podatke postopoma in jih ne shranjuje vseh hkrati v pomnilnik. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; + // ... +} +``` + +.[note] +`ResultSet` je mogoče iterirati samo enkrat. Če potrebujete iterirati večkrat, morate najprej naložiti podatke v polje, na primer z metodo `fetchAll()`. + + +fetch(): ?Row .[method] +----------------------- + +Vrne vrstico kot objekt `Row`. Če ni več vrstic, vrne `null`. Premakne notranji kazalec na naslednjo vrstico. + +```php +$result = $database->query('SELECT * FROM users'); +$row = $result->fetch(); // naloži prvo vrstico +if ($row) { + echo $row->name; +} +``` + + +fetchAll(): array .[method] +--------------------------- + +Vrne vse preostale vrstice iz `ResultSet` kot polje objektov `Row`. + +```php +$result = $database->query('SELECT * FROM users'); +$rows = $result->fetchAll(); // naloži vse vrstice +foreach ($rows as $row) { + echo $row->name; +} +``` + + +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Vrne rezultate kot asociativno polje. Prvi argument določa ime stolpca, ki se uporabi kot ključ v polju, drugi argument določa ime stolpca, ki se uporabi kot vrednost: + +```php +$result = $database->query('SELECT id, name FROM users'); +$names = $result->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] +``` + +Če navedemo samo prvi parameter, bo vrednost celotna vrstica, torej objekt `Row`: + +```php +$rows = $result->fetchPairs('id'); +// [1 => Row(id: 1, name: 'John'), 2 => Row(id: 2, name: 'Jane'), ...] +``` + +V primeru podvojenih ključev se uporabi vrednost iz zadnje vrstice. Pri uporabi `null` kot ključa bo polje indeksirano numerično od nič (potem do kolizij ne pride): + +```php +$names = $result->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] +``` + + +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Alternativno lahko kot parameter navedete povratni klic (callback), ki bo za vsako vrstico vrnil bodisi samo vrednost ali par ključ-vrednost. + +```php +$result = $database->query('SELECT * FROM users'); +$items = $result->fetchPairs(fn($row) => "$row->id - $row->name"); +// ['1 - John', '2 - Jane', ...] + +// Callback lahko vrne tudi polje s parom ključ & vrednost: +$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]); +// ['John' => 46, 'Jane' => 21, ...] +``` + + +fetchField(): mixed .[method] +----------------------------- + +Vrne vrednost prvega polja iz trenutne vrstice. Če ni več vrstic, vrne `null`. Premakne notranji kazalec na naslednjo vrstico. + +```php +$result = $database->query('SELECT name FROM users'); +$name = $result->fetchField(); // naloži ime iz prve vrstice +``` + + +fetchList(): ?array .[method] +----------------------------- + +Vrne vrstico kot indeksirano polje. Če ni več vrstic, vrne `null`. Premakne notranji kazalec na naslednjo vrstico. + +```php +$result = $database->query('SELECT name, email FROM users'); +$row = $result->fetchList(); // ['John', 'john@example.com'] +``` + + +getRowCount(): ?int .[method] +----------------------------- + +Vrne število prizadetih vrstic zadnje poizvedbe `UPDATE` ali `DELETE`. Za `SELECT` je to število vrnjenih vrstic, vendar to morda ni znano - v takem primeru metoda vrne `null`. + + +getColumnCount(): ?int .[method] +-------------------------------- + +Vrne število stolpcev v `ResultSet`. + + +Informacije o poizvedbah +======================== + +Za namene razhroščevanja lahko pridobimo informacije o zadnji izvedeni poizvedbi: + +```php +echo $database->getLastQueryString(); // izpiše SQL poizvedbo + +$result = $database->query('SELECT * FROM articles'); +echo $result->getQueryString(); // izpiše SQL poizvedbo +echo $result->getTime(); // izpiše čas izvedbe v sekundah +``` + +Za prikaz rezultata kot HTML tabele lahko uporabimo: + +```php +$result = $database->query('SELECT * FROM articles'); +$result->dump(); +``` + +ResultSet ponuja informacije o tipih stolpcev: + +```php +$result = $database->query('SELECT * FROM articles'); +$types = $result->getColumnTypes(); + +foreach ($types as $column => $type) { + echo "$column je tipa $type->type"; // npr. 'id je tipa int' +} +``` + + +Dnevniško beleženje poizvedb +---------------------------- + +Lahko implementiramo lastno dnevniško beleženje poizvedb. Dogodek `onQuery` je polje povratnih klicev (callback), ki se pokličejo po vsaki izvedeni poizvedbi: + +```php +$database->onQuery[] = function ($database, $result) use ($logger) { + $logger->info('Poizvedba: ' . $result->getQueryString()); + $logger->info('Čas: ' . $result->getTime()); + + if ($result->getRowCount() > 1000) { + $logger->warning('Velik nabor rezultatov: ' . $result->getRowCount() . ' vrstic'); + } +}; +``` diff --git a/database/sl/transactions.texy b/database/sl/transactions.texy new file mode 100644 index 0000000000..4183ae8e2c --- /dev/null +++ b/database/sl/transactions.texy @@ -0,0 +1,43 @@ +Transakcije +*********** + +.[perex] +Transakcije zagotavljajo, da se bodisi izvedejo vse operacije znotraj transakcije ali pa se ne izvede nobena. Uporabne so za zagotavljanje skladnosti podatkov pri bolj zapletenih operacijah. + +Najenostavnejši način uporabe transakcij je videti takole: + +```php +$database->beginTransaction(); +try { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); + $database->commit(); +} catch (\Exception $e) { + $database->rollBack(); + throw $e; +} +``` + +Veliko bolj elegantno lahko isto zapišete z metodo `transaction()`. Kot parameter sprejme povratni klic, ki ga izvede v transakciji. Če povratni klic poteka brez izjeme, se transakcija samodejno potrdi. Če pride do izjeme, se transakcija prekliče (rollback) in izjema se širi naprej. + +```php +$database->transaction(function ($database) use ($id) { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); +}); +``` + +Metoda `transaction()` lahko tudi vrača vrednosti: + +```php +$count = $database->transaction(function ($database) { + $result = $database->query('UPDATE users SET active = ?', true); + return $result->getRowCount(); // vrne število posodobljenih vrstic +}); +``` diff --git a/database/tr/@home.texy b/database/tr/@home.texy index 307ab45651..7700df2b84 100644 --- a/database/tr/@home.texy +++ b/database/tr/@home.texy @@ -1,20 +1,21 @@ -Desteklenen Sunucular -===================== +Desteklenen Veritabanları +========================= -Bu veritabanı sunucuları desteklenmektedir: +Nette aşağıdaki veritabanlarını destekler: -|* Veritabanı sunucusu |* DSN adı |* Çekirdek desteği |* Explorer desteği -| MySQL (>= 5.1) | mysql | EVET | EVET -| PostgreSQL (>= 9.0) | pgsql | EVET | EVET -| Sqlite 3 (>= 3.8) | sqlite | EVET | EVET -| Oracle | oci | EVET | - -| MS SQL (PDO_SQLSRV) | sqlsrv | EVET | EVET -| MS SQL (PDO_DBLIB) | mssql | EVET | - -| ODBC | odbc | EVET | - +|* Veritabanı sunucusu |* DSN adı |* Core'da Destek |* Explorer'da Destek +| MySQL (>= 5.1) | mysql | EVET | EVET +| PostgreSQL (>= 9.0) | pgsql | EVET | EVET +| Sqlite 3 (>= 3.8) | sqlite | EVET | EVET +| Oracle | oci | EVET | - +| MS SQL (PDO_SQLSRV) | sqlsrv | EVET | EVET +| MS SQL (PDO_DBLIB) | mssql | EVET | - +| ODBC | odbc | EVET | - -{{title: Nette Database}} -{{description: Nette Veritabanı, SQL sorguları yazmaya gerek kalmadan veritabanından veri alınmasını kolaylaştırır. Verimli sorgular sorar ve gereksiz veri aktarımı yapmaz.}} + +{{maintitle: Nette Database - awesome database layer for PHP}} +{{description: Nette Database, SQL sorguları yazmaya gerek kalmadan veritabanından veri almayı önemli ölçüde basitleştirir. Etkili sorgular yapar ve gereksiz verileri aktarmaz.}} diff --git a/database/tr/@left-menu.texy b/database/tr/@left-menu.texy index 06543079e9..5a4cd8355f 100644 --- a/database/tr/@left-menu.texy +++ b/database/tr/@left-menu.texy @@ -1,5 +1,12 @@ -Veritabanı -********** -- [Çekirdek |Core] -- [Kaşif |Explorer] -- [Konfigürasyon |Configuration] +Nette Database +************** +- [Giriş |guide] +- [SQL Yaklaşımı |sql way] +- [Explorer |Explorer] +- [İşlemler |transactions] +- [İstisnalar |exceptions] +- [Yansıma |reflection] +- [Eşleme |mapping] +- [Yapılandırma |configuration] +- [Güvenlik Riskleri |security] +- [Yükseltme |en:upgrading] diff --git a/database/tr/@meta.texy b/database/tr/@meta.texy new file mode 100644 index 0000000000..8dfe82f311 --- /dev/null +++ b/database/tr/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokümantasyonu}} diff --git a/database/tr/configuration.texy b/database/tr/configuration.texy index 2f1ba49b62..eb6b6d98b6 100644 --- a/database/tr/configuration.texy +++ b/database/tr/configuration.texy @@ -1,61 +1,67 @@ -Veritabanını Yapılandırma +Veritabanı yapılandırması ************************* .[perex] -Nette Veritabanı için yapılandırma seçeneklerine genel bakış. +Nette Database için yapılandırma seçeneklerine genel bakış. -Tüm çerçeveyi değil, yalnızca bu kütüphaneyi kullanıyorsanız, [yapılandırmayı nasıl yükleyeceğinizi |bootstrap:] okuyun. +Tüm framework'ü değil de yalnızca bu kütüphaneyi kullanıyorsanız, [yapılandırmanın nasıl yükleneceğini|bootstrap:] okuyun. -Tek Bağlantı .[#toc-single-connection] --------------------------------------- +Tek bağlantı +------------ -Tek bir veritabanı bağlantısı yapılandırın: +Tek bir veritabanı bağlantısının yapılandırılması: ```neon database: - # DSN, yalnızca zorunlu anahtar + # DSN, tek zorunlu anahtar dsn: "sqlite:%appDir%/Model/demo.db" user: ... password: ... ``` -[Veritabanı Gezgini |explorer] katmanı için `Nette\Database\Connection` ve ayrıca `Nette\Database\Explorer` türünde hizmetler oluşturur. Veritabanı bağlantısı genellikle [otomatik kablolama |dependency-injection:autowiring] ile aktarılır, bu mümkün değilse `@database.default.connection` ve `@database.default.explorer` servis adlarını kullanın. +Genellikle [otomatik kablolama |dependency-injection:autowiring] ile ilettiğimiz `Nette\Database\Connection` ve `Nette\Database\Explorer` servislerini oluşturur veya [adlarına |#DI Servisleri] bir referansla. Diğer ayarlar: ```neon database: - # Tracy Bar'da veritabanı panelini gösterir mi? - debugger: ... # (bool) varsayılan değer true + # Tracy Bar'da veritabanı panelini göster? + debugger: ... # (bool) varsayılan true'dur - # Tracy Bar'da EXPLAIN sorgusunu gösterir? - explain: ... # (bool) varsayılan olarak true + # Tracy Bar'da sorguların EXPLAIN'ini göster? + explain: ... # (bool) varsayılan true'dur - # bu bağlantı için otomatik kablolamayı etkinleştirmek için? - autowired: ... # (bool) ilk bağlantı için varsayılan değer true + # Bu bağlantı için otomatik kablolamaya izin ver? + autowired: ... # (bool) ilk bağlantı için varsayılan true'dur - # tablo kuralları: keşfedilmiş, statik veya sınıf adı - conventions: discovered # (string) varsayılan olarak 'discovered' + # tablo kuralları: discovered, static veya sınıf adı + conventions: discovered # (string) varsayılan 'discovered' options: - # veritabanına yalnızca gerektiğinde bağlanmak için? - lazy: ... # (bool) varsayılan değer false + # veritabanına yalnızca gerektiğinde bağlan? + lazy: ... # (bool) varsayılan false'dur - # PHP veritabanı sürücü sınıfı + # Veritabanı sürücüsü PHP sınıfı driverClass: # (string) - # sadece MySQL: sql_mode'u ayarlar + # yalnızca MySQL: sql_mode ayarlar sqlmode: # (string) - # sadece MySQL: SET NAMES setleri - charset: # (string) varsayılan olarak 'utf8mb4' ('utf8' v5.5.3'ten önce) + # yalnızca MySQL: SET NAMES ayarlar + charset: # (string) varsayılan 'utf8mb4' - # sadece Oracle ve SQLite: tarih biçimi - formatDateTime: # (string) varsayılan değer 'U' + # yalnızca MySQL: TINYINT(1)'i bool'a dönüştürür + convertBoolean: # (bool) varsayılan false'dur + + # tarih içeren sütunları değişmez nesneler olarak döndürür (sürüm 3.2.1'den itibaren) + newDateTime: # (bool) varsayılan false'dur + + # yalnızca Oracle ve SQLite: tarih kaydetme formatı + formatDateTime: # (string) varsayılan 'U' ``` -`options` anahtarı, [PDO sürücü belgelerinde |https://www.php.net/manual/en/pdo.drivers.php] bulunabilecek diğer seçenekleri içerebilir, örneğin: +`options` anahtarında, [PDO sürücü belgelerinde |https://www.php.net/manual/en/pdo.drivers.php] bulabileceğiniz diğer seçenekleri belirtebilirsiniz, örneğin: ```neon database: @@ -64,10 +70,10 @@ database: ``` -Çoklu Bağlantılar .[#toc-multiple-connections] ----------------------------------------------- +Çoklu bağlantılar +----------------- -Yapılandırmada, adlandırılmış bölümlere ayırarak daha fazla veritabanı bağlantısı tanımlayabiliriz: +Yapılandırmada, adlandırılmış bölümlere ayırarak birden fazla veritabanı bağlantısı da tanımlayabiliriz: ```neon database: @@ -80,9 +86,23 @@ database: dsn: 'sqlite::memory:' ``` -Tanımlanan her bağlantı, adlarında bölüm adını içeren hizmetler oluşturur, yani `@database.main.connection` & `@database.main.explorer` ve ayrıca `@database.another.connection` & `@database.another.explorer`. +Otomatik kablolama yalnızca ilk bölümdeki servisler için etkindir. Bu, `autowired: false` veya `autowired: true` kullanılarak değiştirilebilir. + + +DI Servisleri +------------- + +Bu servisler DI konteynerine eklenir, burada `###` bağlantı adını temsil eder: + +| Ad | Tür | Açıklama +|---------------------------------------------------------- +| `database.###.connection` | [api:Nette\Database\Connection] | veritabanı bağlantısı +| `database.###.explorer` | [api:Nette\Database\Explorer] | [Database Explorer |explorer] + + +Yalnızca bir bağlantı tanımlarsak, servis adları `database.default.connection` ve `database.default.explorer` olacaktır. Yukarıdaki örnekte olduğu gibi birden fazla bağlantı tanımlarsak, adlar bölümlere karşılık gelir, yani `database.main.connection`, `database.main.explorer` ve ayrıca `database.another.connection` ve `database.another.explorer`. -Otomatik bağlantı yalnızca ilk bölümdeki hizmetler için etkinleştirilir. Bu, `autowired: false` veya `autowired: true` ile değiştirilebilir. Otomatik bağlanmayan hizmetler isimle iletilir: +Otomatik olarak kablolanmayan servisleri adlarına açık bir referansla iletiriz: ```neon services: diff --git a/database/tr/core.texy b/database/tr/core.texy deleted file mode 100644 index 5532d29906..0000000000 --- a/database/tr/core.texy +++ /dev/null @@ -1,350 +0,0 @@ -Veritabanı Çekirdeği -******************** - -.[perex] -Nette Database Core, veritabanı soyutlama katmanıdır ve temel işlevsellik sağlar. - - -Kurulum .[#toc-installation] -============================ - -[Composer'ı |best-practices:composer] kullanarak paketi indirin ve yükleyin: - -```shell -composer require nette/database -``` - - -Bağlantı ve Yapılandırma .[#toc-connection-and-configuration] -============================================================= - -Veritabanına bağlanmak için [api:Nette\Database\Connection] sınıfının bir örneğini oluşturmanız yeterlidir: - -```php -$database = new Nette\Database\Connection($dsn, $user, $password); -``` - -`$dsn` (veri kaynağı adı) parametresi [PDO tarafından kullanılanla aynıdır |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], örneğin `host=127.0.0.1;dbname=test`. Başarısızlık durumunda `Nette\Database\ConnectionException` atar. - -Ancak, daha sofistike bir yol [uygulama yapılandırması |configuration] sunar. Bir `database` bölümü ekleyeceğiz ve [Tracy |tracy:] çubuğunda gerekli nesneleri ve bir veritabanı paneli oluşturacak. - -```neon -database: - dsn: 'mysql:host=127.0.0.1;dbname=test' - user: root - password: password -``` - -Örneğin [bir DI konteynerinden servis olarak aldığımız |dependency-injection:passing-dependencies] bağlantı nesnesi: - -```php -class Model -{ - // Veritabanı Gezgini katmanı ile çalışmak için Nette\Database\Explorer'ı geçirin - public function __construct( - private Nette\Database\Connection $database, - ) { - } -} -``` - -Daha fazla bilgi için, bkz. [veritabanı yapılandırması |configuration]. - - -Sorgular .[#toc-queries] -======================== - -Veritabanını sorgulamak için [ResultSet |api:Nette\Database\ResultSet] döndüren `query()` yöntemini kullanın. - -```php -$result = $database->query('SELECT * FROM users'); - -foreach ($result as $row) { - echo $row->id; - echo $row->name; -} - -echo $result->getRowCount(); // biliniyorsa satır sayısını döndürür -``` - -.[note] -`ResultSet` üzerinden sadece bir kez yineleme yapmak mümkündür, birden fazla kez yineleme yapmamız gerekirse `fetchAll()` metodu ile sonucu diziye dönüştürmek gerekir. - -Sorguya kolayca parametre ekleyebilirsiniz, soru işaretine dikkat edin: - -```php -$database->query('SELECT * FROM users WHERE name = ?', $name); - -$database->query('SELECT * FROM users WHERE name = ? AND active = ?', $name, $active); - -$database->query('SELECT * FROM users WHERE id IN (?)', $ids); // $ids bir dizidir -``` -<div class=warning> - -UYARI, [SQL enjeksiyonu açığından |https://en.wikipedia.org/wiki/SQL_injection] kaçınmak için asla dizeleri birleştirmeyin! -/-- -$db->query('SELECT * FROM users WHERE name = ' . $name); // WRONG!!! -\-- -</div> - -Başarısızlık durumunda `query()`, `Nette\Database\DriverException` ya da onun soyundan gelenlerden birini fırlatır: - -- [ConstraintViolationException |api:Nette\Database\ConstraintViolationException] - herhangi bir kısıtlamanın ihlali -- [ForeignKeyConstraintViolationException |api:Nette\Database\ForeignKeyConstraintViolationException] - geçersiz yabancı anahtar -- [NotNullConstraintViolationException |api:Nette\Database\NotNullConstraintViolationException] - NOT NULL koşulunun ihlali -- [UniqueConstraintViolationException |api:Nette\Database\UniqueConstraintViolationException] - benzersiz dizin çakışması - -`query()` adresine ek olarak, başka faydalı yöntemler de vardır: - -```php -// id => name ilişkisel dizisini döndürür -$pairs = $database->fetchPairs('SELECT id, name FROM users'); - -// tüm satırları dizi olarak döndürür -$rows = $database->fetchAll('SELECT * FROM users'); - -// tek satır döndürür -$row = $database->fetch('SELECT * FROM users WHERE id = ?', $id); - -// tek alan döndür -$name = $database->fetchField('SELECT name FROM users WHERE id = ?', $id); -``` - -Başarısızlık durumunda, tüm bu yöntemler `Nette\Database\DriverException.` - - -Ekle, Güncelle ve Sil .[#toc-insert-update-delete] -================================================== - -SQL sorgusuna eklediğimiz parametre dizi de olabilir (bu durumda joker karakter `?`), which may be useful for the `INSERT` ifadesini atlamak mümkündür: - -```php -$database->query('INSERT INTO users ?', [ // burada soru işareti atlanabilir - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) - -$id = $database->getInsertId(); // eklenen satırın otomatik artışını döndürür - -id = $database->getInsertId($sequence); // veya sıra değeri -``` - -Çoklu ekleme: - -```php -$database->query('INSERT INTO users', [ - [ - 'name' => 'Jim', - 'year' => 1978, - ], [ - 'name' => 'Jack', - 'year' => 1987, - ], -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) -``` - -Ayrıca dosyaları, DateTime nesnelerini veya [numaralandırmaları |https://www.php.net/enumerations] da aktarabiliriz: - -```php -$database->query('INSERT INTO users', [ - 'name' => $name, - 'created' => new DateTime, // or $database::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // inserts file contents - 'status' => State::New, // enum State -]); -``` - -Satırlar güncelleniyor: - -```php -$result = $database->query('UPDATE users SET', [ - 'name' => $name, - 'year' => $year, -], 'WHERE id = ?', $id); -// UPDATE users SET `name` = 'Jim', `year` = 1978 WHERE id = 123 - -echo $result->getRowCount(); // etkilenen satır sayısını döndürür -``` - -UPDATE için `+=` ve `-=` operatörlerini kullanabiliriz: - -```php -$database->query('UPDATE users SET', [ - 'age+=' => 1, // note += -], 'WHERE id = ?', $id); -// UPDATE users SET `age` = `age` + 1 -``` - -Siliniyor: - -```php -$result = $database->query('DELETE FROM users WHERE id = ?', $id); -echo $result->getRowCount(); // etkilenen satır sayısını döndürür -``` - - -Gelişmiş Sorgular .[#toc-advanced-queries] -========================================== - -Zaten mevcutsa ekleyin veya güncelleyin: - -```php -$database->query('INSERT INTO users', [ - 'id' => $id, - 'name' => $name, - 'year' => $year, -], 'ON DUPLICATE KEY UPDATE', [ - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) -// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 -``` - -Nette Database'in dizi parametresinin eklendiği SQL bağlamını tanıdığını ve SQL kodunu buna göre oluşturduğunu unutmayın. Böylece, ilk diziden `(id, name, year) VALUES (123, 'Jim', 1978)` üretirken, ikincisini `name = 'Jim', year = 1978`'a dönüştürür. - -Sıralamayı dizi kullanarak da tanımlayabiliriz, anahtarlar sütun adlarıdır ve değerler artan sırada sıralanıp sıralanmayacağını belirleyen boolean'dır: - -```php -$database->query('SELECT id FROM author ORDER BY', [ - 'id' => true, // artan - 'name' => false, // azalan -]); -// SELECT id FROM author ORDER BY `id`, `name` DESC -``` - -Algılama işe yaramadıysa, derlemenin biçimini bir joker karakter `?` ve ardından bir ipucu ile belirtebilirsiniz. Bu ipuçları desteklenir: - -| ?values | (key1, key2, ...) VALUES (value1, value2, ...) -| ?set | anahtar1 = değer1, anahtar2 = değer2, ... -| ?ve | anahtar1 = değer1 VE anahtar2 = değer2 ... -| ?or | key1 = value1 OR key2 = value2 ... -| ?order | key1 ASC, key2 DESC - -WHERE cümlesi `?and` operatörünü kullanır, böylece koşullar `AND` ile birbirine bağlanır: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND `year` = 1978 -``` - -Bu, `?or` joker karakteri kullanılarak kolayca `OR` olarak değiştirilebilir: - -```php -$result = $database->query('SELECT * FROM users WHERE ?or', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' OR `year` = 1978 -``` - -Operatörleri koşullar içinde kullanabiliriz: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name <>' => $name, - 'year >' => $year, -]); -// SELECT * FROM users WHERE `name` <> 'Jim' AND `year` > 1978 -``` - -Ve ayrıca numaralandırmalar: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => ['Jim', 'Jack'], - 'role NOT IN' => ['admin', 'owner'], // enumeration + operator NOT IN -]); -// SELECT * FROM users WHERE -// `name` IN ('Jim', 'Jack') AND `role` NOT IN ('admin', 'owner') -``` - -Ayrıca SQL literal olarak adlandırılan özel bir SQL kodu parçası da ekleyebiliriz: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year >' => $database::literal('YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) -``` - -Alternatif olarak: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) -``` - -SQL literalinin parametreleri de olabilir: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > ? AND year < ?', $min, $max), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) -``` - -Bu sayede ilginç kombinasyonlar yaratabiliriz: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('?or', [ - 'active' => true, - 'role' => $role, - ]), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') -``` - - -Değişken Adı .[#toc-variable-name] -================================== - -Tablo adı veya sütun adı bir değişken ise kullanabileceğiniz bir `?name` joker karakteri vardır. (Dikkat edin, kullanıcının böyle bir değişkenin içeriğini değiştirmesine izin vermeyin): - -```php -$table = 'blog.users'; -$column = 'name'; -$database->query('SELECT * FROM ?name WHERE ?name = ?', $table, $column, $name); -// SELECT * FROM `blog`.`users` WHERE `name` = 'Jim' -``` - - -İşlemler .[#toc-transactions] -============================= - -İşlemlerle ilgilenmek için üç yöntem vardır: - -```php -$database->beginTransaction(); - -$database->commit(); - -$database->rollback(); -``` - -`transaction()` yöntemi zarif bir yol sunar. Transaction içinde çalıştırılan geri çağırmayı iletirsiniz. Yürütme sırasında bir istisna atılırsa, işlem düşürülür, her şey yolunda giderse işlem işlenir. - -```php -$id = $database->transaction(function ($database) { - $database->query('DELETE FROM ...'); - $database->query('INSERT INTO ...'); - // ... - return $database->getInsertId(); -}); -``` - -Gördüğünüz gibi, `transaction()` yöntemi geri aramanın dönüş değerini döndürür. - -transaction() da iç içe geçebilir, bu da bağımsız depoların uygulanmasını basitleştirir. diff --git a/database/tr/exceptions.texy b/database/tr/exceptions.texy new file mode 100644 index 0000000000..ec399a0309 --- /dev/null +++ b/database/tr/exceptions.texy @@ -0,0 +1,34 @@ +İstisnalar +********** + +Nette Database bir istisna hiyerarşisi kullanır. Temel sınıf `Nette\Database\DriverException`'dır, bu sınıf `PDOException`'dan miras alır ve veritabanı hatalarıyla çalışmak için genişletilmiş yetenekler sağlar: + +- `getDriverCode()` metodu, veritabanı sürücüsünden hata kodunu döndürür +- `getSqlState()` metodu, SQLSTATE kodunu döndürür +- `getQueryString()` ve `getParameters()` metotları, orijinal sorguyu ve parametrelerini almanızı sağlar + +`DriverException`'dan aşağıdaki özel istisnalar miras alır: + +- `ConnectionException` - veritabanı sunucusuna bağlantı hatasını belirtir +- `ConstraintViolationException` - veritabanı kısıtlamalarının ihlali için temel sınıf, bundan miras alanlar: + - `ForeignKeyConstraintViolationException` - yabancı anahtar ihlali + - `NotNullConstraintViolationException` - NOT NULL kısıtlaması ihlali + - `UniqueConstraintViolationException` - değer benzersizliği ihlali + + +Veritabanında zaten var olan bir e-postaya sahip bir kullanıcı eklemeye çalıştığımızda oluşan `UniqueConstraintViolationException` istisnasını yakalama örneği (e-posta sütununun benzersiz bir dizine sahip olduğu varsayılarak). + +```php +try { + $database->query('INSERT INTO users', [ + 'email' => 'john@example.com', + 'name' => 'John Doe', + 'password' => $hashedPassword, + ]); +} catch (Nette\Database\UniqueConstraintViolationException $e) { + echo 'Bu e-posta adresine sahip bir kullanıcı zaten var.'; + +} catch (Nette\Database\DriverException $e) { + echo 'Kayıt sırasında bir hata oluştu: ' . $e->getMessage(); +} +``` diff --git a/database/tr/explorer.texy b/database/tr/explorer.texy index cda960249c..6fb3f323fa 100644 --- a/database/tr/explorer.texy +++ b/database/tr/explorer.texy @@ -1,550 +1,912 @@ -Veritabanı Gezgini -****************** +Database Explorer +***************** <div class=perex> -Nette Database Explorer, SQL sorguları yazmadan veritabanından veri almayı önemli ölçüde kolaylaştırır. +Explorer, veritabanıyla çalışmak için sezgisel ve etkili bir yol sunar. Tablolar arasındaki ilişkileri ve sorgu optimizasyonunu otomatik olarak halleder, böylece uygulamanıza odaklanabilirsiniz. Ayarlama yapmadan hemen çalışır. SQL sorguları üzerinde tam kontrol sahibi olmanız gerekiyorsa, [SQL yaklaşımını |SQL way] kullanabilirsiniz. -- verimli sorgular kullanır -- hiçbir veri gereksiz yere iletilmez -- zarif sözdizimine sahiptir +- Verilerle çalışmak doğal ve anlaşılması kolaydır +- Yalnızca gerekli verileri yükleyen optimize edilmiş SQL sorguları oluşturur +- JOIN sorguları yazmaya gerek kalmadan ilgili verilere kolay erişim sağlar +- Herhangi bir yapılandırma veya varlık oluşturma olmadan anında çalışır </div> -Veritabanı Gezgini'ni kullanmak için bir tablo ile başlayın - [api:Nette\Database\Explorer] nesnesi üzerinde `table()` adresini çağırın. Bir bağlam nesnesi örneği elde etmenin en kolay yolu [burada açıklanmıştır |core#Connection and Configuration] veya Nette Database Explorer'ın bağımsız bir araç olarak kullanılması durumunda, [manuel |#Creating Explorer Manually] olarak [oluşturulabilir |#Creating Explorer Manually]. + +Explorer ile [api:Nette\Database\Explorer] nesnesinin `table()` metodunu çağırarak başlarsınız (bağlantı ayrıntıları [Bağlantı ve yapılandırma |guide#Bağlantı ve Yapılandırma] bölümünde bulunabilir): ```php -$books = $explorer->table('book'); // db tablo adı 'book' +$books = $explorer->table('book'); // 'book' tablo adıdır ``` -Çağrı, tüm kitapları almak için üzerinde yinelenebilen bir [Selection |api:Nette\Database\Table\Selection] nesnesi örneği döndürür. Her öğe (bir satır), özelliklerine eşlenen verilerle birlikte bir [ActiveRow |api:Nette\Database\Table\ActiveRow] örneği ile temsil edilir: +Metot, bir SQL sorgusunu temsil eden bir [Selection |api:Nette\Database\Table\Selection] nesnesi döndürür. Sonuçları filtrelemek ve sıralamak için bu nesneye ek metotlar zincirleyebiliriz. Sorgu, veri talep etmeye başladığımızda oluşturulur ve yürütülür. Örneğin, bir `foreach` döngüsüyle. Her satır bir [ActiveRow |api:Nette\Database\Table\ActiveRow] nesnesiyle temsil edilir: ```php foreach ($books as $book) { - echo $book->title; - echo $book->author_id; + echo $book->title; // 'title' sütununu yazdır + echo $book->author_id; // 'author_id' sütununu yazdır } ``` -Sadece belirli bir satırı almak, doğrudan bir ActiveRow örneği döndüren `get()` yöntemiyle yapılır. +Explorer, [tablolar arasındaki ilişkilerle |#Tablolar arasındaki ilişkiler] çalışmayı önemli ölçüde kolaylaştırır. Aşağıdaki örnek, ilişkili tablolardan (kitaplar ve yazarları) verilerin ne kadar kolay listelenebileceğini gösterir. Herhangi bir JOIN sorgusu yazmamıza gerek olmadığına dikkat edin, Nette bunları bizim için oluşturur: ```php -$book = $explorer->table('book')->get(2); // kimliği 2 olan kitabı döndürür -echo $book->title; -echo $book->author_id; +$books = $explorer->table('book'); + +foreach ($books as $book) { + echo 'Kitap: ' . $book->title; + echo 'Yazar: ' . $book->author->name; // 'author' tablosuna JOIN oluşturur +} ``` -Yaygın kullanım durumuna bir göz atalım. Kitapları ve yazarlarını getirmeniz gerekiyor. Bu yaygın bir 1:N ilişkisidir. Sık kullanılan çözüm, tablo birleştirmeleri ile tek bir SQL sorgusu kullanarak veri getirmektir. İkinci olasılık, verileri ayrı ayrı almak, kitapları almak için bir sorgu çalıştırmak ve ardından başka bir sorgu ile her kitap için bir yazar almaktır (örneğin foreach döngünüzde). Bu, biri kitaplar için diğeri de gerekli yazarlar için olmak üzere yalnızca iki sorgu çalıştıracak şekilde kolayca optimize edilebilir - ve Nette Database Explorer'ın yaptığı da tam olarak budur. +Nette Database Explorer, sorguları mümkün olduğunca verimli olacak şekilde optimize eder. Yukarıdaki örnek, 10 veya 10.000 kitap işliyor olsak da yalnızca iki SELECT sorgusu gerçekleştirir. -Aşağıdaki örneklerde, şekildeki veritabanı şeması ile çalışacağız. Kitap ve etiketleri arasında OneHasMany (1:N) bağlantıları (kitabın yazarı `author_id` ve olası çevirmeni `translator_id`, `null` olabilir) ve ManyHasMany (M:N) bağlantıları vardır. +Ek olarak, Explorer kodda hangi sütunların kullanıldığını izler ve veritabanından yalnızca bunları yükleyerek daha fazla performans tasarrufu sağlar. Bu davranış tamamen otomatik ve uyarlanabilirdir. Daha sonra kodu değiştirir ve ek sütunlar kullanmaya başlarsanız, Explorer sorguları otomatik olarak ayarlar. Hiçbir şey ayarlamanıza veya hangi sütunlara ihtiyacınız olacağını düşünmenize gerek yok - bırakın Nette halletsin. -[Şema da dahil olmak üzere bir örnek GitHub'da bulunabilir |https://github.com/nette-examples/books]. -[* db-schema-1-.webp *] *** Örneklerde kullanılan veritabanı yapısı .<> +Filtreleme ve sıralama +====================== -Aşağıdaki kod her kitap için yazarın adını ve tüm etiketlerini listeler. Bunun dahili olarak nasıl çalıştığını birazdan [tartışacağız |#Working with relationships]. +`Selection` sınıfı, veri seçimini filtrelemek ve sıralamak için metotlar sağlar. -```php -$books = $explorer->table('book'); +.[language-php] +| `where($condition, ...$params)` | WHERE koşulu ekler. Birden çok koşul AND operatörü ile birleştirilir +| `whereOr(array $conditions)` | OR operatörü ile birleştirilmiş bir WHERE koşulları grubu ekler +| `wherePrimary($value)` | Birincil anahtara göre WHERE koşulu ekler +| `order($columns, ...$params)` | ORDER BY sıralamasını ayarlar +| `select($columns, ...$params)` | Yüklenecek sütunları belirtir +| `limit($limit, $offset = null)` | Satır sayısını sınırlar (LIMIT) ve isteğe bağlı olarak OFFSET ayarlar +| `page($page, $itemsPerPage, &$total = null)` | Sayfalamayı ayarlar +| `group($columns, ...$params)` | Satırları gruplar (GROUP BY) +| `having($condition, ...$params)` | Gruplanmış satırları filtrelemek için HAVING koşulu ekler -foreach ($books as $book) { - echo 'başlık: ' . $kitap->başlık; - echo 'tarafından yazıldı: ' . $book->author->name; // $book->author is row from table 'author' +Metotlar zincirlenebilir (sözde [akıcı arayüz |nette:introduction-to-object-oriented-programming#Akıcı Arayüzler Fluent Interfaces]): `$table->where(...)->order(...)->limit(...)`. - echo 'etiketler: '; - foreach ($book->related('book_tag') as $bookTag) { - echo $bookTag->tag->name . ', '; // $bookTag->tag 'tag' tablosundan bir satırdır - } -} -``` +Bu metotlarda, [ilişkili tablolardaki verilere |#İlişkili tablolar üzerinden sorgulama] erişmek için özel bir gösterim de kullanabilirsiniz. -Veritabanı katmanının ne kadar verimli çalıştığını görünce memnun olacaksınız. Yukarıdaki örnek, aşağıdaki gibi görünen sabit sayıda istek yapar: -```sql -SELECT * FROM `book` -SELECT * FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT * FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) -``` +Kaçış ve tanımlayıcılar +----------------------- -[Önbellek |caching:] kullanırsanız (varsayılan olarak açık), hiçbir sütun gereksiz yere sorgulanmayacaktır. İlk sorgudan sonra, önbellek kullanılan sütun adlarını depolayacak ve Nette Database Explorer yalnızca gerekli sütunlarla sorguları çalıştıracaktır: +Metotlar parametreleri otomatik olarak kaçar ve tanımlayıcıları (tablo ve sütun adları) tırnak içine alır, böylece SQL injection'u önler. Doğru çalışması için birkaç kurala uymak gerekir: -```sql -SELECT `id`, `title`, `author_id` FROM `book` -SELECT `id`, `name` FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT `book_id`, `tag_id` FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT `id`, `name` FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) +- Anahtar kelimeleri, fonksiyon adlarını, prosedürleri vb. **büyük harflerle** yazın. +- Sütun ve tablo adlarını **küçük harflerle** yazın. +- Karakter dizilerini her zaman **parametreler** aracılığıyla ekleyin. + +```php +where('name = ' . $name); // KRİTİK GÜVENLİK AÇIĞI: SQL injection +where('name LIKE "%search%"'); // YANLIŞ: otomatik tırnak içine almayı zorlaştırır +where('name LIKE ?', '%search%'); // DOĞRU: parametre aracılığıyla eklenen değer + +where('name like ?', $name); // YANLIŞ: `name` `like` ? oluşturur +where('name LIKE ?', $name); // DOĞRU: `name` LIKE ? oluşturur +where('LOWER(name) = ?', $value);// DOĞRU: LOWER(`name`) = ? oluşturur ``` -Seçmeler .[#toc-selections] -=========================== +where(string|array $condition, ...$parameters): static .[method] +---------------------------------------------------------------- -Satırların nasıl filtreleneceği ve kısıtlanacağı ile ilgili olasılıklara bakın [api:Nette\Database\Table\Selection]: +Sonuçları WHERE koşullarıyla filtreler. Güçlü yanı, farklı değer tipleriyle akıllıca çalışması ve SQL operatörlerini otomatik olarak seçmesidir. -.[language-php] -| `$table->where($where[, $param[, ...]])` | İki veya daha fazla koşul sağlandığında AND öğesini yapıştırıcı olarak kullanarak WHERE öğesini ayarlayın -| `$table->whereOr($where)` | İki veya daha fazla koşul sağlandığında VEYA'yı yapıştırıcı olarak kullanarak WHERE'i ayarlayın -| `$table->order($columns)` | ORDER BY ayarla, ifade olabilir `('column DESC, id DESC')` -| `$table->select($columns)` | Alınan sütunları ayarlayın, ifade olabilir `('col, MD5(col) AS hash')` -| `$table->limit($limit[, $offset])` | LIMIT ve OFFSET'i Ayarla -| `$table->page($page, $itemsPerPage[, &$lastPage])` | Sayfalamayı etkinleştirir -| `$table->group($columns)` | GROUP BY ayarla -| `$table->having($having)` | Set HAVING +Temel kullanım: -Fluent arayüzü kullanılabilir, örneğin `$table->where(...)->order(...)->limit(...)`. Birden fazla `where` veya `whereOr` koşulu `AND` operatörü ile bağlanır. +```php +$table->where('id', $value); // WHERE `id` = 123 +$table->where('id > ?', $value); // WHERE `id` > 123 +$table->where('id = ? OR name = ?', $id, $name); // WHERE `id` = 1 OR `name` = 'Jon Snow' +``` +Uygun operatörlerin otomatik olarak algılanması sayesinde, farklı özel durumlarla uğraşmamıza gerek kalmaz. Nette bunları bizim için halleder: -nerede() .[#toc-where] ----------------------- +```php +$table->where('id', 1); // WHERE `id` = 1 +$table->where('id', null); // WHERE `id` IS NULL +$table->where('id', [1, 2, 3]); // WHERE `id` IN (1, 2, 3) +// operatör olmadan yer tutucu soru işareti de kullanılabilir: +$table->where('id ?', 1); // WHERE `id` = 1 +``` -Nette Database Explorer, geçirilen değerler için gerekli operatörleri otomatik olarak ekleyebilir: +Metot, negatif koşulları ve boş dizileri de doğru şekilde işler: -.[language-php] -| `$table->where('field', $value)` | alan = $değer -| `$table->where('field', null)` | alan NULL -| `$table->where('field > ?', $val)` | alan > $val -| `$table->where('field', [1, 2])` | alan IN (1, 2) -| `$table->where('id = ? OR name = ?', 1, $name)` | id = 1 OR name = 'Jon Snow' -| `$table->where('field', $explorer->table($tableName))` | field IN (SELECT $primary FROM $tableName) -| `$table->where('field', $explorer->table($tableName)->select('col'))` | field IN (SELECT col FROM $tableName) +```php +$table->where('id', []); // WHERE `id` IS NULL AND FALSE -- hiçbir şey bulmaz +$table->where('id NOT', []); // WHERE `id` IS NULL OR TRUE -- her şeyi bulur +$table->where('NOT (id ?)', []); // WHERE NOT (`id` IS NULL AND FALSE) -- her şeyi bulur +// $table->where('NOT id ?', $ids); Dikkat - bu sözdizimi desteklenmiyor +``` -Sütun operatörü olmadan da yer tutucu sağlayabilirsiniz. Bu çağrılar aynıdır. +Parametre olarak başka bir tablodan sonuç da iletebiliriz - bir alt sorgu oluşturulur: ```php -$table->where('id = ? OR id = ?', 1, 2); -$table->where('id ? OR id ?', 1, 2); +// WHERE `id` IN (SELECT `id` FROM `tableName`) +$table->where('id', $explorer->table($tableName)); + +// WHERE `id` IN (SELECT `col` FROM `tableName`) +$table->where('id', $explorer->table($tableName)->select('col')); ``` -Bu özellik, değere bağlı olarak doğru operatörün oluşturulmasını sağlar: +Koşulları, öğeleri AND ile birleştirilecek bir dizi olarak da iletebiliriz: ```php -$table->where('id ?', 2); // id = 2 -$table->where('id ?', null); // id IS NULL -$table->where('id', $ids); // id IN (...) +// WHERE (`price_final` < `price_original`) AND (`stock_count` > `min_stock`) +$table->where([ + 'price_final < price_original', + 'stock_count > min_stock', +]); ``` -Seçim, negatif koşulları da doğru bir şekilde işler, boş diziler için de çalışır: +Dizide anahtar => değer çiftleri kullanabiliriz ve Nette yine doğru operatörleri otomatik olarak seçer: ```php -$table->where('id', []); // id IS NULL AND FALSE -$table->where('id NOT', []); // id IS NULL OR TRUE -$table->where('NOT (id ?)', $ids); // NOT (id IS NULL AND FALSE) +// WHERE (`status` = 'active') AND (`id` IN (1, 2, 3)) +$table->where([ + 'status' => 'active', + 'id' => [1, 2, 3], +]); +``` -// this will throws an exception, this syntax is not supported -$table->where('NOT id ?', $ids); +Dizide, yer tutucu soru işaretleri ve birden çok parametre içeren SQL ifadelerini birleştirebiliriz. Bu, tam olarak tanımlanmış operatörlere sahip karmaşık koşullar için uygundur: + +```php +// WHERE (`age` > 18) AND (ROUND(`score`, 2) > 75.5) +$table->where([ + 'age > ?' => 18, + 'ROUND(score, ?) > ?' => [2, 75.5], // iki parametreyi dizi olarak iletiriz +]); ``` +Birden çok `where()` çağrısı, koşulları otomatik olarak AND ile birleştirir. + -whereOr() .[#toc-whereor] -------------------------- +whereOr(array $parameters): static .[method] +-------------------------------------------- -Parametresiz kullanım örneği: +`where()` gibi koşullar ekler, ancak farkı bunları OR kullanarak birleştirmesidir: ```php -// WHERE (user_id IS NULL) OR (SUM(`field1`) > SUM(`field2`)) +// WHERE (`status` = 'active') OR (`deleted` = 1) $table->whereOr([ - 'user_id IS NULL', - 'SUM(field1) > SUM(field2)', + 'status' => 'active', + 'deleted' => true, ]); ``` -Parametreleri kullanırız. Bir operatör belirtmezseniz, Nette Database Explorer uygun olanı otomatik olarak ekleyecektir: +Burada da daha karmaşık ifadeler kullanabiliriz: ```php -// WHERE (`field1` IS NULL) OR (`field2` IN (3, 5)) OR (`amount` > 11) +// WHERE (`price` > 1000) OR (`price_with_tax` > 1500) $table->whereOr([ - 'field1' => null, - 'field2' => [3, 5], - 'amount >' => 11, + 'price > ?' => 1000, + 'price_with_tax > ?' => 1500, ]); ``` -Anahtar, joker soru işaretleri içeren bir ifade içerebilir ve ardından değerde parametreleri iletebilir: + +wherePrimary(mixed $key): static .[method] +------------------------------------------ + +Tablonun birincil anahtarı için bir koşul ekler: ```php -// WHERE (`id` > 12) OR (ROUND(`id`, 5) = 3) -$table->whereOr([ - 'id > ?' => 12, - 'ROUND(id, ?) = ?' => [5, 3], -]); +// WHERE `id` = 123 +$table->wherePrimary(123); + +// WHERE `id` IN (1, 2, 3) +$table->wherePrimary([1, 2, 3]); +``` + +Tablonun bileşik bir birincil anahtarı varsa (örneğin `foo_id`, `bar_id`), bunu bir dizi olarak iletiriz: + +```php +// WHERE `foo_id` = 1 AND `bar_id` = 5 +$table->wherePrimary(['foo_id' => 1, 'bar_id' => 5])->fetch(); + +// WHERE (`foo_id`, `bar_id`) IN ((1, 5), (2, 3)) +$table->wherePrimary([ + ['foo_id' => 1, 'bar_id' => 5], + ['foo_id' => 2, 'bar_id' => 3], +])->fetchAll(); ``` -order() .[#toc-order] ---------------------- +order(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- -Kullanım örnekleri: +Satırların döndürüleceği sırayı belirler. Bir veya daha fazla sütuna göre, azalan veya artan sırada veya özel bir ifadeye göre sıralayabiliriz: ```php -$table->order('field1'); // ORDER BY `field1` -$table->order('field1 DESC, field2'); // ORDER BY `field1` DESC, `field2` -$table->order('field = ? DESC', 123); // ORDER BY `field` = 123 DESC +$table->order('created'); // ORDER BY `created` +$table->order('created DESC'); // ORDER BY `created` DESC +$table->order('priority DESC, created'); // ORDER BY `priority` DESC, `created` +$table->order('status = ? DESC', 'active'); // ORDER BY `status` = 'active' DESC ``` -select() .[#toc-select] ------------------------ +select(string $columns, ...$parameters): static .[method] +--------------------------------------------------------- -Kullanım örnekleri: +Veritabanından döndürülecek sütunları belirtir. Varsayılan olarak, Nette Database Explorer yalnızca kodda gerçekten kullanılan sütunları döndürür. Bu nedenle `select()` metodunu, belirli ifadeleri döndürmemiz gereken durumlarda kullanırız: ```php -$table->select('field1'); // SELECT `field1` -$table->select('col, UPPER(col) AS abc'); // SELECT `col`, UPPER(`col`) AS abc -$table->select('SUBSTR(title, ?)', 3); // SELECT SUBSTR(`title`, 3) +// SELECT *, DATE_FORMAT(`created_at`, "%d.%m.%Y") AS `formatted_date` +$table->select('*, DATE_FORMAT(created_at, ?) AS formatted_date', '%d.%m.%Y'); ``` +`AS` ile tanımlanan takma adlar daha sonra ActiveRow nesnesinin özellikleri olarak kullanılabilir: + +```php +foreach ($table as $row) { + echo $row->formatted_date; // takma ada erişim +} +``` -limit() .[#toc-limit] ---------------------- -Kullanım örnekleri: +limit(?int $limit, ?int $offset = null): static .[method] +--------------------------------------------------------- + +Döndürülen satır sayısını sınırlar (LIMIT) ve isteğe bağlı olarak bir ofset ayarlamaya izin verir: ```php -$table->limit(1); // LIMIT 1 -$table->limit(1, 10); // LIMIT 1 OFFSET 10 +$table->limit(10); // LIMIT 10 (ilk 10 satırı döndürür) +$table->limit(10, 20); // LIMIT 10 OFFSET 20 ``` +Sayfalama için `page()` metodunu kullanmak daha uygundur. + -page() .[#toc-page] -------------------- +page(int $page, int $itemsPerPage, &$numOfPages = null): static .[method] +------------------------------------------------------------------------- -Limit ve ofseti ayarlamak için alternatif bir yol: +Sonuçların sayfalanmasını kolaylaştırır. Sayfa numarasını (1'den başlayarak sayılır) ve sayfa başına öğe sayısını kabul eder. İsteğe bağlı olarak, toplam sayfa sayısının saklanacağı bir değişkene referans iletilebilir: ```php -$page = 5; -$itemsPerPage = 10; -$table->page($page, $itemsPerPage); // LIMIT 10 OFFSET 40 +$numOfPages = null; +$table->page(page: 3, itemsPerPage: 10, $numOfPages); +echo "Toplam sayfa sayısı: $numOfPages"; ``` -`$lastPage` değişkenine aktarılan son sayfa numarasını alır: + +group(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- + +Satırları belirtilen sütunlara göre gruplar (GROUP BY). Genellikle toplama fonksiyonlarıyla birlikte kullanılır: ```php -$table->page($page, $itemsPerPage, $lastPage); +// Her kategorideki ürün sayısını sayar +$table->select('category_id, COUNT(*) AS count') + ->group('category_id'); ``` -grup() .[#toc-group] --------------------- +having(string $having, ...$parameters): static .[method] +-------------------------------------------------------- -Kullanım örnekleri: +Gruplanmış satırları filtrelemek için bir koşul ayarlar (HAVING). `group()` metodu ve toplama fonksiyonlarıyla birlikte kullanılabilir: ```php -$table->group('field1'); // GROUP BY `field1` -$table->group('field1, field2'); // GROUP BY `field1`, `field2` +// 100'den fazla ürünü olan kategorileri bulur +$table->select('category_id, COUNT(*) AS count') + ->group('category_id') + ->having('count > ?', 100); ``` -sahip olmak() .[#toc-having] ----------------------------- +Veri okuma +========== + +Veritabanından veri okumak için birkaç kullanışlı metodumuz var: + +.[language-php] +| `foreach ($table as $key => $row)` | Tüm satırlar üzerinde yinelenir, `$key` birincil anahtar değeridir, `$row` bir ActiveRow nesnesidir +| `$row = $table->get($key)` | Birincil anahtara göre tek bir satır döndürür +| `$row = $table->fetch()` | Geçerli satırı döndürür ve işaretçiyi bir sonrakine taşır +| `$array = $table->fetchPairs()` | Sonuçlardan ilişkisel bir dizi oluşturur +| `$array = $table->fetchAll()` | Tüm satırları dizi olarak döndürür +| `count($table)` | Selection nesnesindeki satır sayısını döndürür -Kullanım örnekleri: +[ActiveRow |api:Nette\Database\Table\ActiveRow] nesnesi yalnızca okuma amaçlıdır. Bu, özelliklerinin değerlerini değiştiremeyeceğiniz anlamına gelir. Bu kısıtlama, veri tutarlılığını sağlar ve beklenmedik yan etkileri önler. Veriler veritabanından yüklenir ve herhangi bir değişiklik açıkça ve kontrollü bir şekilde yapılmalıdır. + + +`foreach` - tüm satırlar üzerinde yineleme +------------------------------------------ + +Bir sorguyu yürütmenin ve satırları almanın en kolay yolu, bir `foreach` döngüsünde yinelemektir. SQL sorgusunu otomatik olarak çalıştırır. ```php -$table->having('COUNT(items) >', 100); // HAVING COUNT(`items`) > 100 +$books = $explorer->table('book'); +foreach ($books as $key => $book) { + // $key birincil anahtar değeridir, $book ActiveRow'dur + echo "$book->title ({$book->author->name})"; +} ``` -Başka Bir Tablo Değerine Göre Filtreleme .[#toc-joining-key] ------------------------------------------------------------- +get($key): ?ActiveRow .[method] +------------------------------- -Çoğu zaman, sonuçları başka bir veritabanı tablosunu içeren bazı koşullara göre filtrelemeniz gerekir. Bu tür koşullar tablo birleştirme gerektirir. Ancak, artık bunları yazmanıza gerek yoktur. +SQL sorgusunu yürütür ve birincil anahtara göre satırı veya mevcut değilse `null` döndürür. -Diyelim ki yazarının adı 'Jon' olan tüm kitapları almanız gerekiyor. Yazmanız gereken tek şey ilişkinin birleştirme anahtarı ve birleştirilen tablodaki sütun adıdır. Birleştirme anahtarı, birleştirmek istediğiniz tabloyu ifade eden sütundan türetilir. Örneğimizde (db şemasına bakın) bu `author_id` sütunudur ve sadece ilk kısmını kullanmak yeterlidir - `author` ( `_id` son eki atlanabilir). `name`, kullanmak istediğimiz `author` tablosundaki bir sütundur. Kitap çevirmeni için bir koşul ( `translator_id` sütunu ile bağlantılı olan) aynı kolaylıkla oluşturulabilir. +```php +$book = $explorer->table('book')->get(123); // ID 123 olan ActiveRow'u veya null döndürür +if ($book) { + echo $book->title; +} +``` + + +fetch(): ?ActiveRow .[method] +----------------------------- + +Bir satır döndürür ve dahili işaretçiyi bir sonrakine taşır. Başka satır yoksa `null` döndürür. ```php $books = $explorer->table('book'); -$books->where('author.name LIKE ?', '%Jon%'); -$books->where('translator.name', 'David Grudl'); +while ($book = $books->fetch()) { + $this->processBook($book); +} ``` -Birleştirme anahtarı mantığı, [Conventions |api:Nette\Database\Conventions] uygulaması tarafından yönlendirilir. Yabancı anahtarlarınızı analiz eden ve bu ilişkilerle kolayca çalışmanıza olanak tanıyan [DiscoveredConventions'ı |api:Nette\Database\Conventions\DiscoveredConventions] kullanmanızı öneririz. -Kitap ve yazarı arasındaki ilişki 1:N'dir. Ters ilişki de mümkündür. Biz buna **backjoin** diyoruz. Başka bir örneğe bakalım. 3'ten fazla kitap yazmış olan tüm yazarları getirmek istiyoruz. Birleştirmeyi tersine çevirmek için `:` (colon). Colon means that the joined relationship means hasMany (and it's quite logical too, as two dots are more than one dot). Unfortunately, the Selection class isn't smart enough, so we have to help with the aggregation and provide a `GROUP BY` deyimini kullanırız, ayrıca koşul `HAVING` deyimi şeklinde yazılmalıdır. +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Sonuçları ilişkisel bir dizi olarak döndürür. İlk argüman, dizide anahtar olarak kullanılacak sütun adını belirtir, ikinci argüman değer olarak kullanılacak sütun adını belirtir: ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book.id) > 3'); +$authors = $explorer->table('author')->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] ``` -Birleştirme ifadesinin kitaba atıfta bulunduğunu fark etmiş olabilirsiniz, ancak `author_id` üzerinden mi yoksa `translator_id` üzerinden mi birleştirdiğimiz net değildir. Yukarıdaki örnekte, Selection `author_id` sütunu üzerinden birleştirir çünkü kaynak tablo ile bir eşleşme bulunmuştur - `author` tablosu. Böyle bir eşleşme olmasaydı ve daha fazla olasılık olsaydı, Nette [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException] atardı. +Yalnızca ilk parametreyi belirtirsek, değer tüm satır, yani `ActiveRow` nesnesi olacaktır: -`translator_id` sütunu aracılığıyla bir birleştirme yapmak için, birleştirme ifadesinde isteğe bağlı bir parametre sağlayın. +```php +$authors = $explorer->table('author')->fetchPairs('id'); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` + +Yinelenen anahtarlar durumunda, son satırdan gelen değer kullanılır. Anahtar olarak `null` kullanıldığında, dizi sıfırdan başlayarak sayısal olarak dizine eklenir (o zaman çakışma olmaz): ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book(translator).id) > 3'); +$authors = $explorer->table('author')->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] ``` -Şimdi biraz daha zor birleştirme ifadelerine bir göz atalım. -PHP hakkında bir şeyler yazmış olan tüm yazarları bulmak istiyoruz. Tüm kitapların etiketleri vardır, bu nedenle PHP etiketi ile herhangi bir kitap yazmış olan yazarları seçmeliyiz. +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Alternatif olarak, parametre olarak her satır için ya değerin kendisini ya da bir anahtar-değer çiftini döndürecek bir geri arama (callback) belirtebilirsiniz. ```php -$authors = $explorer->table('author'); -$authors->where(':book:book_tags.tag.name', 'PHP') - ->group('author.id') - ->having('COUNT(:book:book_tags.tag.id) > 0'); +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => "$row->title ({$row->author->name})"); +// ['İlk kitap (Jan Novák)', ...] + +// Geri arama ayrıca bir anahtar & değer çifti içeren bir dizi de döndürebilir: +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => [$row->title, $row->author->name]); +// ['İlk kitap' => 'Jan Novák', ...] ``` -Toplu Sorgular .[#toc-aggregate-queries] ----------------------------------------- +fetchAll(): array .[method] +--------------------------- -| `$table->count('*')` | Satır sayısını al -| `$table->count("DISTINCT $column")` | Farklı değerlerin sayısını al -| `$table->min($column)` | Minimum değeri al -| `$table->max($column)` | Maksimum değeri al -| `$table->sum($column)` | Tüm değerlerin toplamını alın -| `$table->aggregation("GROUP_CONCAT($column)")` | Herhangi bir toplama işlevini çalıştırın +Tüm satırları, anahtarların birincil anahtar değerleri olduğu `ActiveRow` nesnelerinin ilişkisel bir dizisi olarak döndürür. -.[caution] -Herhangi bir parametre belirtilmeyen `count()` yöntemi tüm kayıtları seçer ve dizi boyutunu döndürür, bu da çok verimsizdir. Örneğin, sayfalama için satır sayısını hesaplamanız gerekiyorsa, her zaman ilk bağımsız değişkeni belirtin. +```php +$allBooks = $explorer->table('book')->fetchAll(); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` -Kaçış ve Alıntı .[#toc-escaping-quoting] -======================================== +count(): int .[method] +---------------------- + +Parametresiz `count()` metodu, `Selection` nesnesindeki satır sayısını döndürür: + +```php +$table->where('category', 1); +$count = $table->count(); +$count = count($table); // alternatif +``` -Veritabanı Gezgini akıllıdır ve parametreleri ve tırnak tanımlayıcılarını sizin için kaçar. Yine de bu temel kurallara uyulması gerekir: +Dikkat, parametreli `count()` veritabanında COUNT toplama fonksiyonunu gerçekleştirir, aşağıya bakın. -- anahtar kelimeler, fonksiyonlar, prosedürler büyük harfle yazılmalıdır -- sütunlar ve tablolar küçük harfle yazılmalıdır -- değişkenleri parametre olarak geçirin, birleştirme yapmayın + +ActiveRow::toArray(): array .[method] +------------------------------------- + +`ActiveRow` nesnesini, anahtarların sütun adları ve değerlerin karşılık gelen veriler olduğu ilişkisel bir diziye dönüştürür. ```php -->where('name like ?', 'John'); // WRONG! generates: `isim` `gibi` ? -->where('name LIKE ?', 'John'); // DOĞRU +$book = $explorer->table('book')->get(1); +$bookArray = $book->toArray(); +// $bookArray ['id' => 1, 'title' => '...', 'author_id' => ..., ...] olacaktır +``` -->where('KEY = ?', $value); // YANLIŞ! KEY bir anahtar kelimedir -->where('key = ?', $value); // DOĞRU. üretir: anahtar` = ? -->where('name = ' . $name); // YANLIŞ! sql enjeksiyonu! -->where('name = ?', $name); // DOĞRU +Toplama +======= + +`Selection` sınıfı, toplama fonksiyonlarını (COUNT, SUM, MIN, MAX, AVG vb.) kolayca gerçekleştirmek için metotlar sağlar. + +.[language-php] +| `count($expr)` | Satır sayısını sayar +| `min($expr)` | Sütundaki minimum değeri döndürür +| `max($expr)` | Sütundaki maksimum değeri döndürür +| `sum($expr)` | Sütundaki değerlerin toplamını döndürür +| `aggregation($function)` | Herhangi bir toplama fonksiyonunun gerçekleştirilmesini sağlar. Örn. `AVG()`, `GROUP_CONCAT()` + -->select('DATE_FORMAT(created, "%d.%m.%Y")'); // YANLIŞ! değişkenleri parametre olarak geçirin, birleştirmeyin -->select('DATE_FORMAT(created, ?)', '%d.%m.%Y'); // DOĞRU +count(string $expr): int .[method] +---------------------------------- + +COUNT fonksiyonuyla bir SQL sorgusu gerçekleştirir ve sonucu döndürür. Metot, belirli bir koşula kaç satırın karşılık geldiğini bulmak için kullanılır: + +```php +$count = $table->count('*'); // SELECT COUNT(*) FROM `table` +$count = $table->count('DISTINCT column'); // SELECT COUNT(DISTINCT `column`) FROM `table` ``` -.[warning] -Yanlış kullanım güvenlik açıkları oluşturabilir +Dikkat, [#count()] parametresiz yalnızca `Selection` nesnesindeki satır sayısını döndürür. -Veri Getirme .[#toc-fetching-data] -================================== +min(string $expr) ve max(string $expr) .[method] +------------------------------------------------ -| `foreach ($table as $id => $row)` | Sonuçtaki tüm satırlar üzerinde yinele -| `$row = $table->get($id)` | Tablodan $id kimliğine sahip tek bir satır al -| `$row = $table->fetch()` | Sonuçtan bir sonraki satırı al -| `$array = $table->fetchPairs($key, $value)` | Tüm değerleri ilişkisel diziye getir -| `$array = $table->fetchPairs($key)` | Tüm satırları ilişkisel diziye getir -| `count($table)` | Sonuç kümesindeki satır sayısını al +`min()` ve `max()` metotları, belirtilen sütun veya ifadedeki minimum ve maksimum değeri döndürür: + +```php +// SELECT MAX(`price`) FROM `products` WHERE `active` = 1 +$maxPrice = $products->where('active', true) + ->max('price'); +``` -Ekle, Güncelle ve Sil .[#toc-insert-update-delete] -================================================== +sum(string $expr) .[method] +--------------------------- -`insert()` yöntemi Traversable nesneleri dizisini kabul eder (örneğin [formları |forms:] döndüren [ArrayHash |utils:arrays#ArrayHash] ): +Belirtilen sütun veya ifadedeki değerlerin toplamını döndürür: + +```php +// SELECT SUM(`price` * `items_in_stock`) FROM `products` WHERE `active` = 1 +$totalPrice = $products->where('active', true) + ->sum('price * items_in_stock'); +``` + + +aggregation(string $function, ?string $groupFunction = null) .[method] +---------------------------------------------------------------------- + +Herhangi bir toplama fonksiyonunun gerçekleştirilmesini sağlar. + +```php +// kategorideki ürünlerin ortalama fiyatı +$avgPrice = $products->where('category_id', 1) + ->aggregation('AVG(price)'); + +// ürün etiketlerini tek bir karakter dizisinde birleştirir +$tags = $products->where('id', 1) + ->aggregation('GROUP_CONCAT(tag.name) AS tags') + ->fetch() + ->tags; +``` + +Zaten bir toplama fonksiyonu ve gruplamadan (örneğin, gruplanmış satırlar üzerinde `SUM(değer)`) kaynaklanan sonuçları toplamamız gerekiyorsa, ikinci argüman olarak bu ara sonuçlara uygulanacak toplama fonksiyonunu belirtiriz: + +```php +// Her kategori için stoktaki ürünlerin toplam fiyatını hesaplar ve ardından bu fiyatları toplar. +$totalPrice = $products->select('category_id, SUM(price * stock) AS category_total') + ->group('category_id') + ->aggregation('SUM(category_total)', 'SUM'); +``` + +Bu örnekte, önce her kategorideki ürünlerin toplam fiyatını hesaplarız (`SUM(price * stock) AS category_total`) ve sonuçları `category_id`'ye göre gruplarız. Ardından, bu ara toplamları `category_total` toplamak için `aggregation('SUM(category_total)', 'SUM')` kullanırız. İkinci argüman `'SUM'`, ara sonuçlara SUM fonksiyonunun uygulanması gerektiğini söyler. + + +Ekleme, Güncelleme ve Silme +=========================== + +Nette Database Explorer, veri eklemeyi, güncellemeyi ve silmeyi basitleştirir. Belirtilen tüm metotlar, bir hata durumunda `Nette\Database\DriverException` istisnası fırlatır. + + +Selection::insert(iterable $data) .[method] +------------------------------------------- + +Tabloya yeni kayıtlar ekler. + +**Tek bir kayıt ekleme:** + +Yeni kaydı, anahtarların tablodaki sütun adlarına karşılık geldiği ilişkisel bir dizi veya yinelenebilir bir nesne (örneğin, [formlarda |forms:] kullanılan ArrayHash) olarak iletiriz. + +Tablonun tanımlanmış bir birincil anahtarı varsa, metot veritabanı düzeyinde yapılan olası değişiklikleri (tetikleyiciler, varsayılan sütun değerleri, otomatik artan sütun hesaplamaları) yansıtmak için veritabanından yeniden yüklenen bir `ActiveRow` nesnesi döndürür. Bu, veri tutarlılığını sağlar ve nesne her zaman veritabanındaki güncel verileri içerir. Benzersiz bir birincil anahtarı yoksa, iletilen verileri dizi biçiminde döndürür. ```php $row = $explorer->table('users')->insert([ - 'name' => $name, - 'year' => $year, + 'name' => 'John Doe', + 'email' => 'john.doe@example.com', ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) +// $row, ActiveRow örneğidir ve eklenen satırın tam verilerini içerir, +// otomatik olarak oluşturulan ID ve tetikleyiciler tarafından yapılan olası değişiklikler dahil +echo $row->id; // Yeni eklenen kullanıcının ID'sini yazdırır +echo $row->created_at; // Tetikleyici tarafından ayarlandıysa oluşturma zamanını yazdırır ``` -Tabloda birincil anahtar tanımlanmışsa, eklenen satırı içeren bir ActiveRow nesnesi döndürülür. +**Aynı anda birden çok kayıt ekleme:** -Çoklu ekleme: +`insert()` metodu, tek bir SQL sorgusu kullanarak birden çok kayıt eklemeye izin verir. Bu durumda, eklenen satır sayısını döndürür. ```php -$explorer->table('users')->insert([ +$insertedRows = $explorer->table('users')->insert([ + [ + 'name' => 'John', + 'year' => 1994, + ], [ - 'name' => 'Jim', - 'year' => 1978, - ], [ 'name' => 'Jack', - 'year' => 1987, + 'year' => 1995, ], ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) +// INSERT INTO `users` (`name`, `year`) VALUES ('John', 1994), ('Jack', 1995) +// $insertedRows 2 olacaktır +``` + +Parametre olarak, veri seçimi içeren bir `Selection` nesnesi de iletilebilir. + +```php +$newUsers = $explorer->table('potential_users') + ->where('approved', 1) + ->select('name, email'); + +$insertedRows = $explorer->table('users')->insert($newUsers); ``` -Dosyalar veya DateTime nesneleri parametre olarak geçirilebilir: +**Özel değerler ekleme:** + +Değer olarak dosyaları, DateTime nesnelerini veya SQL değişmezlerini de iletebiliriz: ```php $explorer->table('users')->insert([ - 'name' => $name, - 'created' => new DateTime, // or $explorer::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // inserts the file + 'name' => 'John', + 'created_at' => new DateTime, // veritabanı formatına dönüştürür + 'avatar' => fopen('image.jpg', 'rb'), // dosyanın ikili içeriğini ekler + 'uuid' => $explorer::literal('UUID()'), // UUID() fonksiyonunu çağırır ]); ``` -Güncelleme (etkilenen satırların sayısını döndürür): + +Selection::update(iterable $data): int .[method] +------------------------------------------------ + +Belirtilen filtreye göre tablodaki satırları günceller. Gerçekte değiştirilen satır sayısını döndürür. + +Değiştirilecek sütunları, anahtarların tablodaki sütun adlarına karşılık geldiği ilişkisel bir dizi veya yinelenebilir bir nesne (örneğin, [formlarda |forms:] kullanılan ArrayHash) olarak iletiriz: ```php -$count = $explorer->table('users') - ->where('id', 10) // update() işlevinden önce çağrılmalıdır +$affected = $explorer->table('users') + ->where('id', 10) ->update([ - 'name' => 'Ned Stark' + 'name' => 'John Smith', + 'year' => 1994, ]); -// UPDATE `users` SET `name`='Ned Stark' WHERE (`id` = 10) +// UPDATE `users` SET `name` = 'John Smith', `year` = 1994 WHERE `id` = 10 ``` -Güncelleme için `+=` a `-=` operatörlerini kullanabiliriz: +Sayısal değerleri değiştirmek için `+=` ve `-=` operatörlerini kullanabiliriz: ```php $explorer->table('users') + ->where('id', 10) ->update([ - 'age+=' => 1, // see += + 'points+=' => 1, // 'points' sütununun değerini 1 artırır + 'coins-=' => 1, // 'coins' sütununun değerini 1 azaltır ]); -// UPDATE users SET `age` = `age` + 1 +// UPDATE `users` SET `points` = `points` + 1, `coins` = `coins` - 1 WHERE `id` = 10 ``` -Silme (silinen satırların sayısını döndürür): + +Selection::delete(): int .[method] +---------------------------------- + +Belirtilen filtreye göre tablodan satırları siler. Silinen satır sayısını döndürür. ```php $count = $explorer->table('users') ->where('id', 10) ->delete(); -// DELETE FROM `users` WHERE (`id` = 10) +// DELETE FROM `users` WHERE `id` = 10 ``` +.[caution] +`update()` ve `delete()` çağırırken, `where()` kullanarak değiştirilecek/silinecek satırları belirtmeyi unutmayın. `where()` kullanmazsanız, işlem tüm tablo üzerinde gerçekleştirilir! + -İlişkilerle Çalışmak .[#toc-working-with-relationships] -======================================================= +ActiveRow::update(iterable $data): bool .[method] +------------------------------------------------- +`ActiveRow` nesnesi tarafından temsil edilen veritabanı satırındaki verileri günceller. Parametre olarak, güncellenecek verileri içeren yinelenebilir bir değer alır (anahtarlar sütun adlarıdır). Sayısal değerleri değiştirmek için `+=` ve `-=` operatörlerini kullanabiliriz: -Bir İlişkisi Var .[#toc-has-one-relation] ------------------------------------------ -Bir ilişkiye sahip olmak yaygın bir kullanım durumudur. Kitabın *bir* yazarı vardır. Kitabın *bir* çevirmeni vardır. İlgili satırı almak esas olarak `ref()` yöntemi ile yapılır. İki bağımsız değişken kabul eder: hedef tablo adı ve kaynak birleştirme sütunu. Örneğe bakınız: +Güncelleme yapıldıktan sonra, `ActiveRow` veritabanı düzeyinde yapılan olası değişiklikleri (örneğin tetikleyiciler) yansıtmak için otomatik olarak veritabanından yeniden yüklenir. Metot, yalnızca verilerde gerçek bir değişiklik yapıldıysa true döndürür. ```php -$book = $explorer->table('book')->get(1); -$book->ref('author', 'author_id'); +$article = $explorer->table('article')->get(1); +$article->update([ + 'views += 1', // görüntülenme sayısını artırırız +]); +echo $article->views; // Geçerli görüntülenme sayısını yazdırır ``` -Yukarıdaki örnekte, `author` tablosundan ilgili yazar girdisini getiriyoruz, yazar birincil anahtarı `book.author_id` sütunu tarafından aranır. Ref() metodu ActiveRow örneğini döndürür veya uygun bir girdi yoksa null döndürür. Dönen satır bir ActiveRow örneğidir, bu nedenle kitap girişiyle aynı şekilde çalışabiliriz. +Bu metot, veritabanındaki yalnızca belirli bir satırı günceller. Birden çok satırı toplu olarak güncellemek için [#Selection::update()] metodunu kullanın. + + +ActiveRow::delete() .[method] +----------------------------- + +`ActiveRow` nesnesi tarafından temsil edilen satırı veritabanından siler. ```php -$author = $book->ref('author', 'author_id'); -$author->name; -$author->born; +$book = $explorer->table('book')->get(1); +$book->delete(); // ID 1 olan kitabı siler +``` + +Bu metot, veritabanındaki yalnızca belirli bir satırı siler. Birden çok satırı toplu olarak silmek için [#Selection::delete()] metodunu kullanın. + + +Tablolar arasındaki ilişkiler +============================= + +İlişkisel veritabanlarında, veriler birden çok tabloya bölünür ve yabancı anahtarlar kullanılarak birbirine bağlanır. Nette Database Explorer, bu ilişkilerle çalışmak için devrim niteliğinde bir yol sunar - JOIN sorguları yazmadan ve herhangi bir şeyi yapılandırmaya veya oluşturmaya gerek kalmadan. + +İlişkilerle çalışmayı göstermek için bir kitap veritabanı örneği kullanacağız ([GitHub'da bulabilirsiniz |https://github.com/nette-examples/books]). Veritabanında şu tablolarımız var: + +- `author` - yazarlar ve çevirmenler (sütunlar `id`, `name`, `web`, `born`) +- `book` - kitaplar (sütunlar `id`, `author_id`, `translator_id`, `title`, `sequel_id`) +- `tag` - etiketler (sütunlar `id`, `name`) +- `book_tag` - kitaplar ve etiketler arasındaki ilişki tablosu (sütunlar `book_id`, `tag_id`) + +[* db-schema-1-.webp *] *** Veritabanı yapısı .<> + +Kitap veritabanı örneğimizde, birkaç ilişki tipi buluruz (model gerçekliğe göre basitleştirilmiş olsa da): + +- Bire çok 1:N – her kitabın **bir** yazarı vardır, bir yazar **birkaç** kitap yazabilir +- Sıfıra çok 0:N – kitabın bir çevirmeni **olabilir**, bir çevirmen **birkaç** kitap çevirebilir +- Sıfıra bir 0:1 – kitabın bir devamı **olabilir** +- Çoka çok M:N – kitabın **birkaç** etiketi olabilir ve bir etiket **birkaç** kitaba atanabilir -// veya doğrudan -$book->ref('author', 'author_id')->name; -$book->ref('author', 'author_id')->born; +Bu ilişkilerde her zaman bir üst ve bir alt tablo bulunur. Örneğin, yazar ve kitap arasındaki ilişkide, `author` tablosu üst, `book` tablosu alttır - bir kitabın her zaman bir yazara "ait olduğunu" düşünebiliriz. Bu, veritabanı yapısında da kendini gösterir: alt `book` tablosu, üst `author` tablosuna başvuran `author_id` yabancı anahtarını içerir. + +Yazarlarının adları da dahil olmak üzere kitapları listelememiz gerekiyorsa, iki seçeneğimiz vardır. Ya verileri JOIN kullanarak tek bir SQL sorgusuyla alırız: + +```sql +SELECT book.*, author.name FROM book LEFT JOIN author ON book.author_id = author.id +``` + +Ya da verileri iki adımda yükleriz - önce kitapları, sonra yazarlarını - ve sonra bunları PHP'de birleştiririz: + +```sql +SELECT * FROM book; +SELECT * FROM author WHERE id IN (1, 2, 3); -- alınan kitapların yazar kimlikleri ``` -Kitapta ayrıca bir çevirmen var, bu nedenle çevirmen adını almak oldukça kolay. +İkinci yaklaşım, şaşırtıcı olsa da aslında daha verimlidir. Veriler yalnızca bir kez yüklenir ve önbellekte daha iyi kullanılabilir. Nette Database Explorer tam olarak bu şekilde çalışır - her şeyi perde arkasında halleder ve size zarif bir API sunar: + ```php -$book->ref('author', 'translator_id')->name +$books = $explorer->table('book'); +foreach ($books as $book) { + echo 'başlık: ' . $book->title; + echo 'yazan: ' . $book->author->name; // $book->author, 'author' tablosundan bir kayıttır + echo 'çeviren: ' . $book->translator?->name; +} ``` -Tüm bunlar iyi, ancak biraz zahmetli, öyle değil mi? Veritabanı Gezgini zaten yabancı anahtar tanımlarını içeriyor, o halde neden bunları otomatik olarak kullanmayalım? Hadi bunu yapalım! -Eğer var olmayan bir özelliği çağırırsak, ActiveRow çağırılan özellik adını 'has one' ilişkisi olarak çözümlemeye çalışır. Bu özelliği almak, ref() metodunu sadece bir argümanla çağırmakla aynıdır. Tek argümanı **key** olarak adlandıracağız. Anahtar, belirli bir yabancı anahtar ilişkisine çözümlenecektir. İletilen anahtar satır sütunlarıyla eşleştirilir ve eşleşirse, eşleşen sütunda tanımlanan yabancı anahtar ilgili hedef tablodan veri almak için kullanılır. Örneğe bakınız: +Üst tabloya erişim +------------------ + +Üst tabloya erişim basittir. Bunlar *kitabın bir yazarı var* veya *kitabın bir çevirmeni olabilir* gibi ilişkilerdir. İlgili kaydı ActiveRow nesnesinin özelliği aracılığıyla alırız - adı, `id` olmadan yabancı anahtar sütununun adına karşılık gelir: ```php -$book->author->name; -// same as -$book->ref('author')->name; +$book = $explorer->table('book')->get(1); +echo $book->author->name; // author_id sütununa göre yazarı bulur +echo $book->translator?->name; // translator_id'ye göre çevirmeni bulur ``` -ActiveRow örneğinin yazar sütunu yoktur. Tüm kitap sütunları *key* ile eşleşme için aranır. Bu durumda eşleştirme, sütun adının anahtarı içermesi gerektiği anlamına gelir. Yukarıdaki örnekte, `author_id` sütunu 'author' dizesini içerir ve bu nedenle 'author' anahtarıyla eşleştirilir. Kitap çevirmenini almak istiyorsanız, anahtar olarak örneğin 'translator' kullanabilirsiniz, çünkü 'translator' anahtarı `translator_id` sütunuyla eşleşecektir. Anahtar eşleştirme mantığı hakkında daha fazla bilgiyi [Joining expressions |#joining-key] bölümünde bulabilirsiniz. +`$book->author` özelliğine eriştiğimizde, Explorer `book` tablosunda adı `author` karakter dizisini içeren bir sütun arar (yani `author_id`). Bu sütundaki değere göre, `author` tablosundan karşılık gelen kaydı yükler ve `ActiveRow` olarak döndürür. Benzer şekilde, `translator_id` sütununu kullanan `$book->translator` da çalışır. `translator_id` sütunu `null` içerebileceğinden, kodda `?->` operatörünü kullanırız. + +Alternatif bir yol, iki argüman alan `ref()` metodudur: hedef tablo adı ve birleştirme sütunu adı ve bir `ActiveRow` örneği veya `null` döndürür: ```php -echo $book->title . ': '; -echo $book->author->name; -if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; -} +echo $book->ref('author', 'author_id')->name; // yazara bağlantı +echo $book->ref('author', 'translator_id')->name; // çevirmene bağlantı ``` -Birden fazla kitap getirmek istiyorsanız, aynı yaklaşımı kullanmalısınız. Nette Database Explorer, getirilen tüm kitaplar için yazarları ve çevirmenleri bir kerede getirecektir. +`ref()` metodu, tablo aynı ada sahip bir sütun içerdiği için (yani `author`) özellik üzerinden erişim kullanılamadığında kullanışlıdır. Diğer durumlarda, daha okunabilir olan özellik üzerinden erişim kullanılması önerilir. + +Explorer, veritabanı sorgularını otomatik olarak optimize eder. Bir döngüde kitapları dolaşırken ve ilgili kayıtlarına (yazarlar, çevirmenler) erişirken, Explorer her kitap için ayrı bir sorgu oluşturmaz. Bunun yerine, her ilişki tipi için yalnızca bir SELECT gerçekleştirir, bu da veritabanı yükünü önemli ölçüde azaltır. Örneğin: ```php $books = $explorer->table('book'); foreach ($books as $book) { echo $book->title . ': '; echo $book->author->name; - if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; - } + echo $book->translator?->name; } ``` -Kod yalnızca bu 3 sorguyu çalıştıracaktır: +Bu kod, veritabanına yalnızca şu üç yıldırım hızında sorguyu çağırır: + ```sql SELECT * FROM `book`; -SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- ids of fetched books from author_id column -SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- ids of fetched books from translator_id column +SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- seçilen kitapların author_id sütunundan kimlikler +SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- seçilen kitapların translator_id sütunundan kimlikler ``` +.[note] +Birleştirme sütununu bulma mantığı, [Conventions |api:Nette\Database\Conventions] uygulaması tarafından belirlenir. Yabancı anahtarları analiz eden ve mevcut tablo ilişkileriyle kolayca çalışmanıza olanak tanıyan [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions] kullanmanızı öneririz. -Birçok İlişkisi Var .[#toc-has-many-relation] ---------------------------------------------- -'Has many' ilişkisi 'has one' ilişkisinin tersine çevrilmiş halidir. Yazar *çok* kitap yazmıştır. Yazar *çok* kitap çevirmiştir. Gördüğünüz gibi, bu tür bir ilişki biraz daha zordur çünkü ilişki 'adlandırılmıştır' ('yazılmıştır', 'çevrilmiştir'). ActiveRow örneği, ilgili girdilerin dizisini döndürecek olan `related()` yöntemine sahiptir. Girişler de ActiveRow örnekleridir. Aşağıdaki örneğe bakınız: +Alt tabloya erişim +------------------ + +Alt tabloya erişim ters yönde çalışır. Şimdi *bu yazar hangi kitapları yazdı* veya *bu çevirmen hangi kitapları çevirdi* diye soruyoruz. Bu tür bir sorgu için, ilgili kayıtlarla bir `Selection` döndüren `related()` metodunu kullanırız. Bir örneğe bakalım: ```php -$author = $explorer->table('author')->get(11); -echo $author->name . ' has written:'; +$author = $explorer->table('author')->get(1); +// Yazarın tüm kitaplarını yazdırır foreach ($author->related('book.author_id') as $book) { - echo $book->title; + echo "Yazdı: $book->title"; } -echo 'and translated:'; +// Yazarın çevirdiği tüm kitapları yazdırır foreach ($author->related('book.translator_id') as $book) { - echo $book->title; + echo "Çevirdi: $book->title"; } ``` -Yöntem `related()` yöntemi, iki bağımsız değişken olarak veya nokta ile birleştirilmiş bir bağımsız değişken olarak aktarılan tam birleştirme açıklamasını kabul eder. İlk bağımsız değişken hedef tablo, ikincisi ise hedef sütundur. +`related()` metodu, birleştirmeyi nokta gösterimiyle tek bir argüman olarak veya iki ayrı argüman olarak kabul eder: ```php -$author->related('book.translator_id'); -// ile aynı -$author->related('book', 'translator_id'); +$author->related('book.translator_id'); // tek argüman +$author->related('book', 'translator_id'); // iki argüman ``` -Yabancı anahtarlara dayalı Nette Database Explorer buluşsal yöntemlerini kullanabilir ve yalnızca **key** bağımsız değişkenini sağlayabilirsiniz. Anahtar, geçerli tabloya işaret eden tüm yabancı anahtarlarla eşleştirilecektir (`author` tablo). Bir eşleşme varsa, Nette Database Explorer bu yabancı anahtarı kullanacaktır, aksi takdirde [Nette\InvalidArgumentException |api:Nette\InvalidArgumentException] veya [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException] atacaktır. Anahtar eşleştirme mantığı hakkında daha fazla bilgiyi [Joining expressions |#joining-key] bölümünde bulabilirsiniz. +Explorer, üst tablonun adına göre doğru birleştirme sütununu otomatik olarak algılayabilir. Bu durumda, kaynak tablonun adı `author` olduğu için `book.author_id` sütunu üzerinden birleştirme yapılır: -Elbette, getirilen tüm yazarlar için ilgili yöntemleri çağırabilirsiniz, Nette Database Explorer uygun kitapları bir kerede tekrar getirecektir. +```php +$author->related('book'); // book.author_id kullanır +``` + +Birden fazla olası bağlantı varsa, Explorer bir [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException] istisnası fırlatır. + +`related()` metodunu elbette bir döngüde birden çok kaydı dolaşırken de kullanabiliriz ve Explorer bu durumda da sorguları otomatik olarak optimize eder: ```php $authors = $explorer->table('author'); foreach ($authors as $author) { - echo $author->name . ' has written:'; + echo $author->name . ' yazdı:'; foreach ($author->related('book') as $book) { - $book->title; + echo $book->title; } } ``` -Yukarıdaki örnek yalnızca iki sorgu çalıştıracaktır: +Bu kod yalnızca iki yıldırım hızında SQL sorgusu oluşturur: ```sql SELECT * FROM `author`; -SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- ids of fetched authors +SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- seçilen yazarların kimlikleri +``` + + +Çoka çok ilişki +--------------- + +Çoka çok (M:N) ilişkisi için bir ilişki tablosunun (bizim durumumuzda `book_tag`) varlığı gereklidir, bu tablo iki yabancı anahtar sütunu (`book_id`, `tag_id`) içerir. Bu sütunların her biri, birleştirilen tablolardan birinin birincil anahtarına başvurur. İlgili verileri almak için önce `related('book_tag')` kullanarak ilişki tablosundan kayıtları alırız ve ardından hedef verilere devam ederiz: + +```php +$book = $explorer->table('book')->get(1); +// kitaba atanan etiketlerin adlarını yazdırır +foreach ($book->related('book_tag') as $bookTag) { + echo $bookTag->tag->name; // ilişki tablosu üzerinden etiketin adını yazdırır +} + +$tag = $explorer->table('tag')->get(1); +// veya tersi: bu etiketle işaretlenmiş kitapların adlarını yazdırır +foreach ($tag->related('book_tag') as $bookTag) { + echo $bookTag->book->title; // kitabın adını yazdırır +} ``` +Explorer yine SQL sorgularını verimli bir forma optimize eder: + +```sql +SELECT * FROM `book`; +SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 2, ...)); -- seçilen kitapların kimlikleri +SELECT * FROM `tag` WHERE (`tag`.`id` IN (1, 2, ...)); -- book_tag'de bulunan etiketlerin kimlikleri +``` -Explorer'ı Manuel Olarak Oluşturma .[#toc-creating-explorer-manually] -===================================================================== -Uygulama yapılandırması kullanılarak bir veritabanı bağlantısı oluşturulabilir. Bu gibi durumlarda bir `Nette\Database\Explorer` hizmeti oluşturulur ve DI konteyneri kullanılarak bir bağımlılık olarak aktarılabilir. +İlişkili tablolar üzerinden sorgulama +------------------------------------- -Ancak, Nette Database Explorer bağımsız bir araç olarak kullanılıyorsa, `Nette\Database\Explorer` nesnesinin bir örneğinin manuel olarak oluşturulması gerekir. +`where()`, `select()`, `order()` ve `group()` metotlarında, diğer tablolardaki sütunlara erişmek için özel gösterimler kullanabiliriz. Explorer gerekli JOIN'leri otomatik olarak oluşturur. + +**Nokta gösterimi** (`üst_tablo.sütun`), alt tablo açısından 1:N ilişkisi için kullanılır: ```php -// $storage Nette\Caching\Storage uygular: -$storage = new Nette\Caching\Storages\FileStorage($tempDir); -$connection = new Nette\Database\Connection($dsn, $user, $password); -$structure = new Nette\Database\Structure($connection, $storage); -$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); -$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +$books = $explorer->table('book'); + +// Yazarı 'Jon' ile başlayan kitapları bulur +$books->where('author.name LIKE ?', 'Jon%'); + +// Kitapları yazar adına göre azalan sırada sıralar +$books->order('author.name DESC'); + +// Kitap adını ve yazar adını yazdırır +$books->select('book.title, author.name'); ``` + +**İki nokta üst üste gösterimi** (`:alt_tablo.sütun`), üst tablo açısından 1:N ilişkisi için kullanılır: + +```php +$authors = $explorer->table('author'); + +// Başlığında 'PHP' geçen bir kitap yazan yazarları bulur +$authors->where(':book.title LIKE ?', '%PHP%'); + +// Her yazar için kitap sayısını sayar +$authors->select('*, COUNT(:book.id) AS book_count') + ->group('author.id'); +``` + +Yukarıdaki örnekte iki nokta üst üste gösterimi (`:book.title`) ile yabancı anahtar sütunu belirtilmemiştir. Explorer, üst tablonun adına göre doğru sütunu otomatik olarak algılar. Bu durumda, kaynak tablonun adı `author` olduğu için `book.author_id` sütunu üzerinden birleştirme yapılır. Birden fazla olası bağlantı varsa, Explorer bir [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException] istisnası fırlatır. + +Birleştirme sütunu parantez içinde açıkça belirtilebilir: + +```php +// Başlığında 'PHP' geçen bir kitabı çeviren yazarları bulur +$authors->where(':book(translator_id).title LIKE ?', '%PHP%'); +``` + +Gösterimler, birden çok tablo üzerinden erişim için zincirlenebilir: + +```php +// 'PHP' etiketiyle işaretlenmiş kitapların yazarlarını bulur +$authors->where(':book:book_tag.tag.name', 'PHP') + ->group('author.id'); +``` + + +JOIN koşullarını genişletme +--------------------------- + +`joinWhere()` metodu, SQL'de tabloları birleştirirken `ON` anahtar kelimesinden sonra belirtilen koşulları genişletir. + +Belirli bir çevirmen tarafından çevrilen kitapları bulmak istediğimizi varsayalım: + +```php +// 'David' adlı çevirmen tarafından çevrilen kitapları bulur +$books = $explorer->table('book') + ->joinWhere('translator', 'translator.name', 'David'); +// LEFT JOIN author translator ON book.translator_id = translator.id AND (translator.name = 'David') +``` + +`joinWhere()` koşulunda, `where()` metodunda olduğu gibi aynı yapıları kullanabiliriz - operatörler, yer tutucu soru işaretleri, değer dizileri veya SQL ifadeleri. + +Daha karmaşık, birden çok JOIN içeren sorgular için tablo takma adları tanımlayabiliriz: + +```php +$tags = $explorer->table('tag') + ->joinWhere(':book_tag.book.author', 'book_author.born < ?', 1950) + ->alias(':book_tag.book.author', 'book_author'); +// LEFT JOIN `book_tag` ON `tag`.`id` = `book_tag`.`tag_id` +// LEFT JOIN `book` ON `book_tag`.`book_id` = `book`.`id` +// LEFT JOIN `author` `book_author` ON `book`.`author_id` = `book_author`.`id` +// AND (`book_author`.`born` < 1950) +``` + +`where()` metodunun koşulları `WHERE` yan tümcesine eklerken, `joinWhere()` metodunun tabloları birleştirirken `ON` yan tümcesindeki koşulları genişlettiğine dikkat edin. diff --git a/database/tr/guide.texy b/database/tr/guide.texy new file mode 100644 index 0000000000..cf0dd5e69e --- /dev/null +++ b/database/tr/guide.texy @@ -0,0 +1,216 @@ +Nette Database +************** + +.[perex] +Nette Database, basitliğe ve akıllı özelliklere odaklanan, PHP için güçlü ve zarif bir veritabanı katmanıdır. Veritabanıyla çalışmak için iki yol sunar - hızlı uygulama geliştirme için [Explorer |Explorer] veya sorgularla doğrudan çalışmak için [SQL yaklaşımı |SQL way]. + +<div class="grid gap-3"> +<div> + + +[SQL yaklaşımı |SQL way] +======================== +- Güvenli parametreli sorgular +- SQL sorgularının şekli üzerinde hassas kontrol +- Gelişmiş özelliklere sahip karmaşık sorgular yazdığınızda +- Belirli SQL fonksiyonlarını kullanarak performansı optimize ettiğinizde + +</div> + +<div> + + +[Explorer |Explorer] +==================== +- SQL yazmadan hızlı geliştirme yaparsınız +- Tablolar arasındaki ilişkilerle sezgisel çalışma +- Otomatik sorgu optimizasyonunu takdir edersiniz +- Veritabanıyla hızlı ve rahat çalışmak için uygundur + +</div> + +</div> + + +Kurulum / Yükleme +================= + +Kütüphaneyi [Composer |best-practices:composer] aracıyla indirip kurabilirsiniz: + +```shell +composer require nette/database +``` + + +Desteklenen veritabanları +========================= + +Nette Database aşağıdaki veritabanlarını destekler: + +|* Veritabanı sunucusu |* DSN adı |* Explorer desteği +|---------------------|-------------|----------------------- +| MySQL (>= 5.1) | mysql | EVET +| PostgreSQL (>= 9.0) | pgsql | EVET +| Sqlite 3 (>= 3.8) | sqlite | EVET +| Oracle | oci | - +| MS SQL (PDO_SQLSRV) | sqlsrv | EVET +| MS SQL (PDO_DBLIB) | mssql | - +| ODBC | odbc | - + + +Veritabanına iki yaklaşım +========================= + +Nette Database size bir seçenek sunar: SQL sorgularını doğrudan yazabilir (SQL yaklaşımı) veya otomatik olarak oluşturulmalarını sağlayabilirsiniz (Explorer). Her iki yaklaşımın da aynı görevleri nasıl çözdüğüne bakalım: + +[SQL yaklaşımı |sql way] - SQL sorguları + +```php +// kayıt ekleme +$database->query('INSERT INTO books', [ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// kayıtları alma: kitap yazarları +$result = $database->query(' + SELECT authors.*, COUNT(books.id) AS books_count + FROM authors + LEFT JOIN books ON authors.id = books.author_id + WHERE authors.active = 1 + GROUP BY authors.id +'); + +// çıktı (optimal değil, N tane daha sorgu üretir) +foreach ($result as $author) { + $books = $database->query(' + SELECT * FROM books + WHERE author_id = ? + ORDER BY published_at DESC + ', $author->id); + + echo "Yazar $author->name, $author->books_count kitap yazdı:\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +[Explorer yaklaşımı |explorer] - otomatik SQL oluşturma + +```php +// kayıt ekleme +$database->table('books')->insert([ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// kayıtları alma: kitap yazarları +$authors = $database->table('authors') + ->where('active', 1); + +// çıktı (otomatik olarak sadece 2 optimize edilmiş sorgu üretir) +foreach ($authors as $author) { + $books = $author->related('books') + ->order('published_at DESC'); + + echo "Yazar $author->name, {$books->count()} kitap yazdı:\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +Explorer yaklaşımı SQL sorgularını otomatik olarak oluşturur ve optimize eder. Yukarıdaki örnekte, SQL yaklaşımı N+1 sorgu üretir (biri yazarlar için ve sonra her yazarın kitapları için bir tane), Explorer ise sorguları otomatik olarak optimize eder ve yalnızca iki tane yürütür - biri yazarlar için ve biri tüm kitapları için. + +Her iki yaklaşım da uygulamada ihtiyaca göre serbestçe birleştirilebilir. + + +Bağlantı ve Yapılandırma +======================== + +Veritabanına bağlanmak için [api:Nette\Database\Connection] sınıfının bir örneğini oluşturmanız yeterlidir: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password); +``` + +`$dsn` (veri kaynağı adı) parametresi, [PDO'nun kullandığı |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters] ile aynıdır, örn. `host=127.0.0.1;dbname=test`. Başarısızlık durumunda `Nette\Database\ConnectionException` istisnası fırlatır. + +Ancak, [uygulama yapılandırması |configuration] daha kullanışlı bir yol sunar; buraya sadece `database` bölümünü eklemeniz yeterlidir ve gerekli nesneler oluşturulur ve ayrıca [Tracy |tracy:] çubuğunda veritabanı paneli de oluşturulur. + +```neon +database: + dsn: 'mysql:host=127.0.0.1;dbname=test' + user: root + password: password +``` + +Daha sonra bağlantı nesnesini [DI konteynerinden bir servis olarak alırız |dependency-injection:passing-dependencies], örn.: + +```php +class Model +{ + public function __construct( + // veya Nette\Database\Explorer + private Nette\Database\Connection $database, + ) { + } +} +``` + +[Veritabanı yapılandırması |configuration] hakkında daha fazla bilgi. + + +Manuel Explorer Oluşturma +------------------------- + +Nette DI konteynerini kullanmıyorsanız, `Nette\Database\Explorer` örneğini manuel olarak oluşturabilirsiniz: + +```php +// veritabanına bağlanma +$connection = new Nette\Database\Connection('mysql:host=127.0.0.1;dbname=mydatabase', 'user', 'password'); +// önbellek için depolama, Nette\Caching\Storage uygular, örn.: +$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp/dir'); +// veritabanı yapısının yansımasıyla ilgilenir +$structure = new Nette\Database\Structure($connection, $storage); +// tablo, sütun ve yabancı anahtar adlarının eşleştirilmesi için kuralları tanımlar +$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); +$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +``` + + +Bağlantı Yönetimi +================= + +`Connection` nesnesi oluşturulduğunda bağlantı otomatik olarak kurulur. Bağlantıyı ertelemek isterseniz, lazy modunu kullanın - bunu [yapılandırmada |configuration] `lazy` olarak ayarlayarak veya şu şekilde etkinleştirin: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password, ['lazy' => true]); +``` + +Bağlantıyı yönetmek için `connect()`, `disconnect()` ve `reconnect()` metotlarını kullanın. +- `connect()` henüz mevcut değilse bir bağlantı oluşturur ve `Nette\Database\ConnectionException` istisnası fırlatabilir. +- `disconnect()` veritabanına olan mevcut bağlantıyı keser. +- `reconnect()` bağlantıyı keser ve ardından veritabanına yeniden bağlanır. Bu metot da `Nette\Database\ConnectionException` istisnası fırlatabilir. + +Ayrıca, veritabanıyla bağlantı kurulduktan sonra çağrılacak geri arama (callback) dizisi olan `onConnect` olayını kullanarak bağlantıyla ilişkili olayları izleyebilirsiniz. + +```php +// veritabanına bağlandıktan sonra çalışır +$database->onConnect[] = function($database) { + echo "Veritabanına bağlandı"; +}; +``` + + +Tracy Debug Bar +=============== + +[Tracy |tracy:] kullanıyorsanız, Debug çubuğunda otomatik olarak bir Veritabanı paneli etkinleştirilir; bu panel, yürütülen tüm sorguları, parametrelerini, yürütme sürelerini ve kodda çağrıldıkları yeri gösterir. + +[* db-panel.webp *] diff --git a/database/tr/mapping.texy b/database/tr/mapping.texy new file mode 100644 index 0000000000..5ec4aef48e --- /dev/null +++ b/database/tr/mapping.texy @@ -0,0 +1,55 @@ +Tip Dönüşümü +************ + +.[perex] +Nette Database, veritabanından döndürülen değerleri otomatik olarak karşılık gelen PHP tiplerine dönüştürür. + + +Tarih ve Saat +------------- + +Zaman verileri `Nette\Utils\DateTime` nesnelerine dönüştürülür. Zaman verilerinin değişmez (immutable) `Nette\Database\DateTime` nesnelerine dönüştürülmesini istiyorsanız, [yapılandırmada |configuration] `newDateTime` seçeneğini true olarak ayarlayın. + +```php +$row = $database->fetch('SELECT created_at FROM articles'); +echo $row->created_at instanceof DateTime; // true +echo $row->created_at->format('j. n. Y'); +``` + +MySQL durumunda, `TIME` veri tipi `DateInterval` nesnelerine dönüştürülür. + + +Boolean Değerler +---------------- + +Boolean değerler otomatik olarak `true` veya `false` değerlerine dönüştürülür. MySQL için, [yapılandırmada |configuration] `convertBoolean` ayarını yaparsak `TINYINT(1)` dönüştürülür. + +```php +$row = $database->fetch('SELECT is_published FROM articles'); +echo gettype($row->is_published); // 'boolean' +``` + + +Sayısal Değerler +---------------- + +Sayısal değerler, veritabanındaki sütun tipine göre `int` veya `float` değerlerine dönüştürülür: + +```php +$row = $database->fetch('SELECT id, price FROM products'); +echo gettype($row->id); // integer +echo gettype($row->price); // float +``` + + +Özel Normalleştirme +------------------- + +`setRowNormalizer(?callable $normalizer)` metodunu kullanarak veritabanından gelen satırları dönüştürmek için kendi fonksiyonunuzu ayarlayabilirsiniz. Bu, örneğin veri tiplerinin otomatik dönüşümü için kullanışlıdır. + +```php +$database->setRowNormalizer(function(array $row, ResultSet $resultSet): array { + // burada tip dönüşümü gerçekleşir + return $row; +}); +``` diff --git a/database/tr/reflection.texy b/database/tr/reflection.texy new file mode 100644 index 0000000000..a17ac24d83 --- /dev/null +++ b/database/tr/reflection.texy @@ -0,0 +1,125 @@ +Yapı Yansıması +************** + +.{data-version:3.2.1} +Nette Database, [api:Nette\Database\Reflection] sınıfını kullanarak veritabanı yapısının iç gözlemi için araçlar sağlar. Bu, tablolar, sütunlar, indeksler ve yabancı anahtarlar hakkında bilgi almanızı sağlar. Yansımayı şemalar oluşturmak, veritabanıyla çalışan esnek uygulamalar oluşturmak veya genel veritabanı araçları oluşturmak için kullanabilirsiniz. + +Yansıma nesnesini veritabanı bağlantı örneğinden alırız: + +```php +$reflection = $database->getReflection(); +``` + + +Tabloları Alma +-------------- + +Salt okunur `$reflection->tables` özelliği, veritabanındaki tüm tabloların ilişkisel bir dizisini içerir: + +```php +// Tüm tablo adlarının çıktısı +foreach ($reflection->tables as $name => $table) { + echo $name . "\n"; +} +``` + +Ayrıca iki metot daha mevcuttur: + +```php +// Tablonun varlığını kontrol etme +if ($reflection->hasTable('users')) { + echo "users tablosu mevcut"; +} + +// Tablo nesnesini döndürür; mevcut değilse istisna fırlatır +$table = $reflection->getTable('users'); +``` + + +Tablo Bilgileri +--------------- + +Tablo, aşağıdaki salt okunur özellikleri sağlayan bir [Table |api:Nette\Database\Reflection\Table] nesnesiyle temsil edilir: + +- `$name: string` – tablo adı +- `$view: bool` – bir görünüm olup olmadığı +- `$fullName: ?string` – şema dahil tam tablo adı (varsa) +- `$columns: array<string, Column>` – tablo sütunlarının ilişkisel dizisi +- `$indexes: Index[]` – tablo indeksleri dizisi +- `$primaryKey: ?Index` – tablonun birincil anahtarı veya null +- `$foreignKeys: ForeignKey[]` – tablonun yabancı anahtarları dizisi + + +Sütunlar +-------- + +Tablonun `columns` özelliği, anahtarın sütun adı ve değerin aşağıdaki özelliklere sahip bir [Column |api:Nette\Database\Reflection\Column] örneği olduğu ilişkisel bir sütun dizisi sağlar: + +- `$name: string` – sütun adı +- `$table: ?Table` – sütunun tablosuna referans +- `$nativeType: string` – yerel veritabanı tipi +- `$size: ?int` – tipin boyutu/uzunluğu +- `$nullable: bool` – sütunun NULL içerip içeremeyeceği +- `$default: mixed` – sütunun varsayılan değeri +- `$autoIncrement: bool` – sütunun otomatik artan olup olmadığı +- `$primary: bool` – birincil anahtarın bir parçası olup olmadığı +- `$vendor: array` – belirli veritabanı sistemine özgü ek meta veri + +```php +foreach ($table->columns as $name => $column) { + echo "Sütun: $name\n"; + echo "Tip: {$column->nativeType}\n"; + echo "Null olabilir: " . ($column->nullable ? 'Evet' : 'Hayır') . "\n"; +} +``` + + +İndeksler +--------- + +Tablonun `indexes` özelliği, her indeksin aşağıdaki özelliklere sahip bir [Index |api:Nette\Database\Reflection\Index] örneği olduğu bir indeks dizisi sağlar: + +- `$columns: Column[]` – indeksi oluşturan sütunlar dizisi +- `$unique: bool` – indeksin benzersiz olup olmadığı +- `$primary: bool` – birincil anahtar olup olmadığı +- `$name: ?string` – indeks adı + +Tablonun birincil anahtarı, ya bir `Index` nesnesi ya da tablonun birincil anahtarı olmaması durumunda `null` döndüren `primaryKey` özelliği kullanılarak elde edilebilir. + +```php +// İndekslerin çıktısı +foreach ($table->indexes as $index) { + $columns = implode(', ', array_map(fn($col) => $col->name, $index->columns)); + echo "İndeks" . ($index->name ? " {$index->name}" : '') . ":\n"; + echo " Sütunlar: $columns\n"; + echo " Benzersiz: " . ($index->unique ? 'Evet' : 'Hayır') . "\n"; +} + +// Birincil anahtarın çıktısı +if ($primaryKey = $table->primaryKey) { + $columns = implode(', ', array_map(fn($col) => $col->name, $primaryKey->columns)); + echo "Birincil anahtar: $columns\n"; +} +``` + + +Yabancı Anahtarlar +------------------ + +Tablonun `foreignKeys` özelliği, her yabancı anahtarın aşağıdaki özelliklere sahip bir [ForeignKey |api:Nette\Database\Reflection\ForeignKey] örneği olduğu bir yabancı anahtar dizisi sağlar: + +- `$foreignTable: Table` – başvurulan tablo +- `$localColumns: Column[]` – yerel sütunlar dizisi +- `$foreignColumns: Column[]` – başvurulan sütunlar dizisi +- `$name: ?string` – yabancı anahtar adı + +```php +// Yabancı anahtarların çıktısı +foreach ($table->foreignKeys as $fk) { + $localCols = implode(', ', array_map(fn($col) => $col->name, $fk->localColumns)); + $foreignCols = implode(', ', array_map(fn($col) => $col->name, $fk->foreignColumns)); + + echo "FK" . ($fk->name ? " {$fk->name}" : '') . ":\n"; + echo " $localCols -> {$fk->foreignTable->name}($foreignCols)\n"; +} +``` diff --git a/database/tr/security.texy b/database/tr/security.texy new file mode 100644 index 0000000000..3db61769e9 --- /dev/null +++ b/database/tr/security.texy @@ -0,0 +1,185 @@ +Güvenlik Riskleri +***************** + +<div class=perex> + +Veritabanı genellikle hassas veriler içerir ve tehlikeli işlemler yapılmasına izin verir. Nette Database ile güvenli çalışmak için şunlar önemlidir: + +- Güvenli ve tehlikeli API arasındaki farkı anlamak +- Parametreli sorgular kullanmak +- Giriş verilerini doğru şekilde doğrulamak + +</div> + + +SQL Injection Nedir? +==================== + +SQL injection, veritabanıyla çalışırken en ciddi güvenlik riskidir. Kullanıcıdan gelen işlenmemiş girdinin SQL sorgusunun bir parçası haline gelmesiyle ortaya çıkar. Saldırgan kendi SQL deyimlerini ekleyebilir ve böylece: +- Verilere yetkisiz erişim sağlayabilir +- Veritabanındaki verileri değiştirebilir veya silebilir +- Kimlik doğrulamasını atlatabilir + +```php +// ❌ TEHLİKELİ KOD - SQL injection'a karşı savunmasız +$database->query("SELECT * FROM users WHERE name = '$_GET[name]'"); + +// Saldırgan örneğin şu değeri girebilir: ' OR '1'='1 +// Sonuç sorgusu şöyle olur: SELECT * FROM users WHERE name = '' OR '1'='1' +// Bu da tüm kullanıcıları döndürür +``` + +Aynı durum Database Explorer için de geçerlidir: + +```php +// ❌ TEHLİKELİ KOD - SQL injection'a karşı savunmasız +$table->where('name = ' . $_GET['name']); +$table->where("name = '$_GET[name]'"); +``` + + +Parametreli Sorgular +==================== + +SQL injection'a karşı temel savunma parametreli sorgulardır. Nette Database bunların kullanımı için birkaç yol sunar. + +En basit yol **yer tutucu soru işaretlerini** kullanmaktır: + +```php +// ✅ Güvenli parametreli sorgu +$database->query('SELECT * FROM users WHERE name = ?', $name); + +// ✅ Explorer'da güvenli koşul +$table->where('name = ?', $name); +``` + +Bu, yer tutucu soru işaretleri ve parametrelerle ifadeler eklemeye izin veren [Database Explorer |explorer]'daki diğer tüm metotlar için geçerlidir. + +INSERT, UPDATE deyimleri veya WHERE yan tümcesi için değerleri bir dizi içinde iletebiliriz: + +```php +// ✅ Güvenli INSERT +$database->query('INSERT INTO users', [ + 'name' => $name, + 'email' => $email, +]); + +// ✅ Explorer'da güvenli INSERT +$table->insert([ + 'name' => $name, + 'email' => $email, +]); +``` + + +Parametre Değerlerinin Doğrulanması +=================================== + +Parametreli sorgular, veritabanıyla güvenli çalışmanın temel yapı taşıdır. Ancak, bunlara eklediğimiz değerlerin birkaç kontrol seviyesinden geçmesi gerekir: + + +Tip Kontrolü +------------ + +**En önemlisi, parametrelerin doğru veri tipini sağlamaktır** - bu, Nette Database'in güvenli kullanımı için gerekli bir koşuldur. Veritabanı, tüm giriş verilerinin ilgili sütuna karşılık gelen doğru veri tipine sahip olduğunu varsayar. + +Örneğin, önceki örneklerdeki `$name` beklenmedik bir şekilde bir karakter dizisi yerine bir dizi olsaydı, Nette Database tüm öğelerini SQL sorgusuna eklemeye çalışır ve bu da hataya yol açardı. Bu nedenle, **asla** `$_GET`, `$_POST` veya `$_COOKIE`'den gelen doğrulanmamış verileri doğrudan veritabanı sorgularında kullanmayın. + + +Format Kontrolü +--------------- + +İkinci seviyede, verinin formatını kontrol ederiz - örneğin, karakter dizilerinin UTF-8 kodlamasında olup olmadığını ve uzunluklarının sütun tanımına uygun olup olmadığını veya sayısal değerlerin ilgili sütun veri tipi için izin verilen aralıkta olup olmadığını kontrol ederiz. + +Bu doğrulama seviyesinde, kısmen veritabanının kendisine de güvenebiliriz - birçok veritabanı geçersiz verileri reddeder. Ancak, davranış farklılık gösterebilir, bazıları uzun karakter dizilerini sessizce kısaltabilir veya aralık dışındaki sayıları kırpabilir. + + +Alan Kontrolü +------------- + +Üçüncü seviye, uygulamanıza özgü mantıksal kontrolleri temsil eder. Örneğin, seçim kutularından gelen değerlerin sunulan seçeneklere karşılık geldiğini, sayıların beklenen aralıkta olduğunu (örn. yaş 0-150 yıl) veya değerler arasındaki karşılıklı bağımlılıkların anlamlı olduğunu doğrulamak. + + +Önerilen Doğrulama Yöntemleri +----------------------------- + +- Tüm girdilerin doğru doğrulamasını otomatik olarak sağlayan [Nette Formları |forms:] kullanın +- [Presenter'ları |application:] kullanın ve `action*()` ve `render*()` metotlarındaki parametreler için veri tiplerini belirtin +- Veya `filter_var()` gibi standart PHP araçlarını kullanarak kendi doğrulama katmanınızı uygulayın + + +Sütunlarla Güvenli Çalışma +========================== + +Önceki bölümde, parametre değerlerini nasıl doğru bir şekilde doğrulayacağımızı gösterdik. Ancak, SQL sorgularında dizileri kullanırken, anahtarlarına da aynı özeni göstermeliyiz. + +```php +// ❌ TEHLİKELİ KOD - dizideki anahtarlar işlenmemiş +$database->query('INSERT INTO users', $_POST); +``` + +INSERT ve UPDATE deyimlerinde bu kritik bir güvenlik açığıdır - saldırgan veritabanına herhangi bir sütunu ekleyebilir veya değiştirebilir. Örneğin, `is_admin = 1` ayarlayabilir veya hassas sütunlara rastgele veriler ekleyebilir (Mass Assignment Vulnerability olarak adlandırılır). + +WHERE koşullarında bu daha da tehlikelidir, çünkü operatörler içerebilirler: + +```php +// ❌ TEHLİKELİ KOD - dizideki anahtarlar işlenmemiş +$_POST['salary >'] = 100000; +$database->query('SELECT * FROM users WHERE', $_POST); +// WHERE (`salary` > 100000) sorgusunu yürütür +``` + +Saldırgan bu yaklaşımı çalışanların maaşlarını sistematik olarak keşfetmek için kullanabilir. Örneğin, 100.000'in üzerindeki maaşlar için bir sorguyla başlar, sonra 50.000'in altındakilerle ve aralığı kademeli olarak daraltarak tüm çalışanların yaklaşık maaşlarını ortaya çıkarabilir. Bu tür saldırıya SQL enumeration denir. + +`where()` ve `whereOr()` metotları [çok daha esnektir |explorer#where] ve anahtarlarda ve değerlerde operatörler ve fonksiyonlar dahil olmak üzere SQL ifadelerini destekler. Bu, saldırgana SQL injection yapma imkanı verir: + +```php +// ❌ TEHLİKELİ KOD - saldırgan kendi SQL'ini ekleyebilir +$_POST = ['0) UNION SELECT name, salary FROM users WHERE (1']; +$table->where($_POST); +// WHERE (0) UNION SELECT name, salary FROM users WHERE (1) sorgusunu yürütür +``` + +Bu saldırı, orijinal koşulu `0)` ile sonlandırır, `users` tablosundan hassas verileri almak için `UNION` kullanarak kendi `SELECT`'ini ekler ve `WHERE (1)` kullanarak sözdizimsel olarak doğru bir sorgu kapatır. + + +Sütun Beyaz Listesi +------------------- + +Sütun adlarıyla güvenli çalışmak için, kullanıcının yalnızca izin verilen sütunlarla çalışabilmesini ve kendi sütunlarını ekleyememesini sağlayan bir mekanizmaya ihtiyacımız var. Tehlikeli sütun adlarını tespit etmeye ve engellemeye çalışabiliriz (kara liste), ancak bu yaklaşım güvenilmezdir - saldırgan her zaman tehlikeli bir sütun adını öngörmediğimiz yeni bir şekilde yazabilir. + +Bu nedenle, mantığı tersine çevirmek ve izin verilen sütunların açık bir listesini tanımlamak (beyaz liste) çok daha güvenlidir: + +```php +// Kullanıcının düzenleyebileceği sütunlar +$allowedColumns = ['name', 'email', 'active']; + +// Girdiden tüm izin verilmeyen sütunları kaldırırız +$filteredData = array_intersect_key($userData, array_flip($allowedColumns)); + +// ✅ Şimdi sorgularda güvenle kullanabiliriz, örneğin: +$database->query('INSERT INTO users', $filteredData); +$table->update($filteredData); +$table->where($filteredData); +``` + + +Dinamik Tanımlayıcılar +====================== + +Dinamik tablo ve sütun adları için `?name` yer tutucu sembolünü kullanın. Bu, tanımlayıcıların ilgili veritabanının sözdizimine göre (örn. MySQL'de geri tırnaklar kullanarak) doğru bir şekilde kaçışını sağlar: + +```php +// ✅ Güvenilir tanımlayıcıların güvenli kullanımı +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name', $column, $table); +// MySQL'deki sonuç: SELECT `name` FROM `users` +``` + +Önemli: `?name` sembolünü yalnızca uygulama kodunda tanımlanan güvenilir değerler için kullanın. Kullanıcıdan gelen değerler için tekrar [beyaz listeyi |#Sütun Beyaz Listesi] kullanın. Aksi takdirde güvenlik risklerine maruz kalırsınız: + +```php +// ❌ TEHLİKELİ - asla kullanıcı girdisini kullanmayın +$database->query('SELECT ?name FROM users', $_GET['column']); +``` diff --git a/database/tr/sql-way.texy b/database/tr/sql-way.texy new file mode 100644 index 0000000000..83e4f51e1e --- /dev/null +++ b/database/tr/sql-way.texy @@ -0,0 +1,513 @@ +SQL Yaklaşımı +************* + +.[perex] +Nette Database iki yol sunar: SQL sorgularını kendiniz yazabilir (SQL yaklaşımı) veya otomatik olarak oluşturulmalarını sağlayabilirsiniz (bkz. [Explorer |explorer]). SQL yaklaşımı size sorgular üzerinde tam kontrol sağlarken, güvenli bir şekilde oluşturulmalarını da garanti eder. + +.[note] +Veritabanı bağlantısı ve yapılandırmasıyla ilgili ayrıntıları [Bağlantı ve Yapılandırma |guide#Bağlantı ve Yapılandırma] bölümünde bulabilirsiniz. + + +Temel Sorgulama +=============== + +Veritabanına sorgu yapmak için `query()` metodu kullanılır. Bu metot, sorgu sonucunu temsil eden bir [ResultSet |api:Nette\Database\ResultSet] nesnesi döndürür. Başarısızlık durumunda metot [istisna fırlatır |exceptions]. Sorgu sonucunu `foreach` döngüsüyle gezebilir veya [yardımcı fonksiyonlardan |#Veri Alma] birini kullanabiliriz. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; +} +``` + +SQL sorgularına güvenli bir şekilde değer eklemek için parametreli sorgular kullanırız. Nette Database bunları son derece basit hale getirir - SQL sorgusundan sonra virgül ve değeri eklemeniz yeterlidir: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Birden fazla parametre olduğunda iki yazım seçeneğiniz vardır. SQL sorgusunu parametrelerle "serpiştirebilirsiniz": + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age); +``` + +Veya önce tüm SQL sorgusunu yazıp sonra tüm parametreleri ekleyebilirsiniz: + +```php +$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age); +``` + + +SQL Injection'dan Korunma +========================= + +Parametreli sorguları kullanmak neden önemlidir? Çünkü sizi, saldırganın kendi SQL deyimlerini ekleyerek veritabanındaki verilere erişebileceği veya zarar verebileceği SQL injection adlı saldırıdan korurlar. + +.[warning] +**Asla değişkenleri doğrudan SQL sorgusuna eklemeyin!** Sizi SQL injection'dan koruyan parametreli sorguları her zaman kullanın. + +```php +// ❌ TEHLİKELİ KOD - SQL injection'a karşı savunmasız +$database->query("SELECT * FROM users WHERE name = '$name'"); + +// ✅ Güvenli parametreli sorgu +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +[Olası güvenlik riskleri |security] hakkında bilgi edinin. + + +Sorgulama Teknikleri +==================== + + +WHERE Koşulları +--------------- + +WHERE koşullarını, anahtarların sütun adları ve değerlerin karşılaştırma için veriler olduğu ilişkisel bir dizi olarak yazabilirsiniz. Nette Database, değerin tipine göre en uygun SQL operatörünü otomatik olarak seçer. + +```php +$database->query('SELECT * FROM users WHERE', [ + 'name' => 'John', + 'active' => true, +]); +// WHERE `name` = 'John' AND `active` = 1 +``` + +Anahtarda karşılaştırma için operatörü açıkça belirtebilirsiniz: + +```php +$database->query('SELECT * FROM users WHERE', [ + 'age >' => 25, // > operatörünü kullanır + 'name LIKE' => '%John%', // LIKE operatörünü kullanır + 'email NOT LIKE' => '%example.com%', // NOT LIKE operatörünü kullanır +]); +// WHERE `age` > 25 AND `name` LIKE '%John%' AND `email` NOT LIKE '%example.com%' +``` + +Nette, `null` değerleri veya diziler gibi özel durumları otomatik olarak işler. + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name' => 'Laptop', // = operatörünü kullanır + 'category_id' => [1, 2, 3], // IN kullanır + 'description' => null, // IS NULL kullanır +]); +// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL +``` + +Negatif koşullar için `NOT` operatörünü kullanın: + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name NOT' => 'Laptop', // <> operatörünü kullanır + 'category_id NOT' => [1, 2, 3], // NOT IN kullanır + 'description NOT' => null, // IS NOT NULL kullanır + 'id' => [], // atlanır +]); +// WHERE `name` <> 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL +``` + +Koşulları birleştirmek için `AND` operatörü kullanılır. Bu, [?or yer tutucu sembolü |#SQL Oluşturma İpuçları] kullanılarak değiştirilebilir. + + +ORDER BY Kuralları +------------------ + +`ORDER BY` sıralaması bir dizi kullanılarak yazılabilir. Anahtarlarda sütunları belirtiriz ve değer, artan sırada sıralanıp sıralanmayacağını belirleyen bir boolean olur: + +```php +$database->query('SELECT id FROM author ORDER BY', [ + 'id' => true, // artan sırada + 'name' => false, // azalan sırada +]); +// SELECT id FROM author ORDER BY `id`, `name` DESC +``` + + +Veri Ekleme (INSERT) +-------------------- + +Kayıt eklemek için `INSERT` SQL deyimi kullanılır. + +```php +$values = [ + 'name' => 'John Doe', + 'email' => 'john@example.com', +]; +$database->query('INSERT INTO users ?', $values); +$userId = $database->getInsertId(); +``` + +`getInsertId()` metodu, son eklenen satırın ID'sini döndürür. Bazı veritabanlarında (örn. PostgreSQL), ID'nin oluşturulacağı sıra adını `$database->getInsertId($sequenceId)` kullanarak parametre olarak belirtmek gerekir. + +Parametre olarak dosyalar, DateTime nesneleri veya enum tipleri gibi [#özel değerler] de iletebiliriz. + +Aynı anda birden fazla kayıt ekleme: + +```php +$database->query('INSERT INTO users ?', [ + ['name' => 'User 1', 'email' => 'user1@mail.com'], + ['name' => 'User 2', 'email' => 'user2@mail.com'], +]); +``` + +Çoklu INSERT çok daha hızlıdır, çünkü birçok tekil sorgu yerine tek bir veritabanı sorgusu yürütülür. + +**Güvenlik uyarısı:** Asla `$values` olarak doğrulanmamış verileri kullanmayın. [Olası riskler |security#Sütunlarla Güvenli Çalışma] hakkında bilgi edinin. + + +Veri Güncelleme (UPDATE) +------------------------ + +Kayıtları güncellemek için `UPDATE` SQL deyimi kullanılır. + +```php +// Tek bir kaydı güncelleme +$values = [ + 'name' => 'John Smith', +]; +$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1); +``` + +Etkilenen satır sayısı `$result->getRowCount()` tarafından döndürülür. + +UPDATE için `+=` ve `-=` operatörlerini kullanabiliriz: + +```php +$database->query('UPDATE users SET ? WHERE id = ?', [ + 'login_count+=' => 1, // login_count'u artır +], 1); +``` + +Zaten varsa bir kaydı ekleme veya düzenleme örneği. `ON DUPLICATE KEY UPDATE` tekniğini kullanıyoruz: + +```php +$values = [ + 'name' => $name, + 'year' => $year, +]; +$database->query('INSERT INTO users ? ON DUPLICATE KEY UPDATE ?', + $values + ['id' => $id], + $values, +); +// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) +// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 +``` + +Nette Database'in, parametreyi dizi ile SQL deyiminin hangi bağlamına eklediğini tanıdığına ve buna göre SQL kodunu oluşturduğuna dikkat edin. Yani ilk diziden `(id, name, year) VALUES (123, 'Jim', 1978)` oluştururken, ikincisini `name = 'Jim', year = 1978` şekline dönüştürdü. Buna [#SQL Oluşturma İpuçları] bölümünde daha ayrıntılı olarak değiniyoruz. + + +Veri Silme (DELETE) +------------------- + +Kayıtları silmek için `DELETE` SQL deyimi kullanılır. Silinen satır sayısını alma örneği: + +```php +$count = $database->query('DELETE FROM users WHERE id = ?', 1) + ->getRowCount(); +``` + + +SQL Oluşturma İpuçları +---------------------- + +İpucu, parametre değerinin SQL ifadesine nasıl çevrileceğini belirten SQL sorgusundaki özel bir yer tutucu semboldür: + +| İpucu | Açıklama | Otomatik olarak kullanılır +|-----------|-------------------------------------------------|----------------------------- +| `?name` | tablo veya sütun adı eklemek için kullanılır | - +| `?values` | `(key, ...) VALUES (value, ...)` üretir | `INSERT ... ?`, `REPLACE ... ?` +| `?set` | `key = value, ...` atamasını üretir | `SET ?`, `KEY UPDATE ?` +| `?and` | dizideki koşulları `AND` operatörüyle birleştirir | `WHERE ?`, `HAVING ?` +| `?or` | dizideki koşulları `OR` operatörüyle birleştirir | - +| `?order` | `ORDER BY` yan tümcesini üretir | `ORDER BY ?`, `GROUP BY ?` + +Tablo ve sütun adlarını sorguya dinamik olarak eklemek için `?name` yer tutucu sembolü kullanılır. Nette Database, tanımlayıcıların ilgili veritabanının kurallarına göre (örn. MySQL'de geri tırnak içine alma) doğru bir şekilde işlenmesini sağlar. + +```php +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table); +// SELECT `name` FROM `users` WHERE id = 1 (MySQL'de) +``` + +**Uyarı:** `?name` sembolünü yalnızca doğrulanmış girdilerden gelen tablo ve sütun adları için kullanın, aksi takdirde [güvenlik riskine |security#Dinamik Tanımlayıcılar] maruz kalırsınız. + +Diğer ipuçlarını genellikle belirtmeye gerek yoktur, çünkü Nette SQL sorgusunu oluştururken akıllı otomatik algılama kullanır (tablonun üçüncü sütununa bakın). Ancak, örneğin koşulları `AND` yerine `OR` ile birleştirmek istediğiniz bir durumda kullanabilirsiniz: + +```php +$database->query('SELECT * FROM users WHERE ?or', [ + 'name' => 'John', + 'email' => 'john@example.com', +]); +// SELECT * FROM users WHERE `name` = 'John' OR `email` = 'john@example.com' +``` + + +Özel Değerler +------------- + +Normal skaler tiplerin (string, int, bool) yanı sıra, parametre olarak özel değerler de iletebilirsiniz: + +- dosyalar: `fopen('image.gif', 'r')` dosyanın ikili içeriğini ekler +- tarih ve saat: `DateTime` nesneleri veritabanı formatına dönüştürülür +- enum tipleri: `enum` örnekleri değerlerine dönüştürülür +- SQL literalleri: `Connection::literal('NOW()')` ile oluşturulanlar doğrudan sorguya eklenir + +```php +$database->query('INSERT INTO articles ?', [ + 'title' => 'My Article', + 'published_at' => new DateTime, + 'content' => fopen('image.png', 'r'), + 'state' => Status::Draft, +]); +``` + +`datetime` veri tipi için yerel desteği olmayan veritabanlarında (SQLite ve Oracle gibi), `DateTime` [veritabanı yapılandırmasındaki |configuration] `formatDateTime` öğesiyle belirtilen değere dönüştürülür (varsayılan değer `U` - unix zaman damgasıdır). + + +SQL Literalleri +--------------- + +Bazı durumlarda, değer olarak doğrudan SQL kodu belirtmeniz gerekir, ancak bu kodun bir karakter dizisi olarak anlaşılmaması ve kaçış yapılmaması gerekir. Bunun için `Nette\Database\SqlLiteral` sınıfının nesneleri kullanılır. Bunları `Connection::literal()` metodu oluşturur. + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + 'year >' => $database::literal('YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) +``` + +Veya alternatif olarak: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) +``` + +SQL literalleri parametreler içerebilir: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > ? AND year < ?', $min, $max), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) +``` + +Bu sayede ilginç kombinasyonlar oluşturabiliriz: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('?or', [ + 'active' => true, + 'role' => $role, + ]), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') +``` + + +Veri Alma +========= + + +SELECT Sorguları için Kısayollar +-------------------------------- + +Veri alımını basitleştirmek için `Connection`, `query()` çağrısını ardından `fetch*()` ile birleştiren birkaç kısayol sunar. Bu metotlar, `query()` ile aynı parametreleri kabul eder, yani SQL sorgusu ve isteğe bağlı parametreler. `fetch*()` metotlarının tam açıklaması [aşağıda |#fetch] bulunabilir. + +| `fetch($sql, ...$params): ?Row` | Sorguyu yürütür ve ilk satırı `Row` nesnesi olarak döndürür +| `fetchAll($sql, ...$params): array` | Sorguyu yürütür ve tüm satırları `Row` nesneleri dizisi olarak döndürür +| `fetchPairs($sql, ...$params): array` | Sorguyu yürütür ve ilk sütunun anahtar, ikinci sütunun değer olduğu ilişkisel bir dizi döndürür +| `fetchField($sql, ...$params): mixed` | Sorguyu yürütür ve ilk satırdaki ilk hücrenin değerini döndürür +| `fetchList($sql, ...$params): ?array` | Sorguyu yürütür ve ilk satırı indeksli bir dizi olarak döndürür + +Örnek: + +```php +// fetchField() - ilk hücrenin değerini döndürür +$count = $database->query('SELECT COUNT(*) FROM articles') + ->fetchField(); +``` + + +`foreach` - Satırlar Üzerinde Yineleme +-------------------------------------- + +Sorgu yürütüldükten sonra, sonuçları birkaç şekilde gezmenizi sağlayan bir [ResultSet |api:Nette\Database\ResultSet] nesnesi döndürülür. Bir sorguyu yürütmenin ve satırları almanın en kolay yolu `foreach` döngüsünde yinelemektir. Bu yöntem bellek açısından en verimli olanıdır, çünkü verileri kademeli olarak döndürür ve hepsini aynı anda bellekte saklamaz. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; + // ... +} +``` + +.[note] +`ResultSet` yalnızca bir kez yinelenebilir. Tekrar tekrar yinelemeniz gerekiyorsa, önce verileri bir diziye yüklemeniz gerekir, örneğin `fetchAll()` metodunu kullanarak. + + +fetch(): ?Row .[method] +----------------------- + +Satırı `Row` nesnesi olarak döndürür. Başka satır yoksa `null` döndürür. Dahili göstericiyi bir sonraki satıra taşır. + +```php +$result = $database->query('SELECT * FROM users'); +$row = $result->fetch(); // ilk satırı yükler +if ($row) { + echo $row->name; +} +``` + + +fetchAll(): array .[method] +--------------------------- + +`ResultSet`'ten kalan tüm satırları `Row` nesneleri dizisi olarak döndürür. + +```php +$result = $database->query('SELECT * FROM users'); +$rows = $result->fetchAll(); // tüm satırları yükler +foreach ($rows as $row) { + echo $row->name; +} +``` + + +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Sonuçları ilişkisel bir dizi olarak döndürür. İlk argüman, dizide anahtar olarak kullanılacak sütun adını belirtir, ikinci argüman değer olarak kullanılacak sütun adını belirtir: + +```php +$result = $database->query('SELECT id, name FROM users'); +$names = $result->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] +``` + +Yalnızca ilk parametreyi belirtirsek, değer tüm satır, yani `Row` nesnesi olacaktır: + +```php +$rows = $result->fetchPairs('id'); +// [1 => Row(id: 1, name: 'John'), 2 => Row(id: 2, name: 'Jane'), ...] +``` + +Yinelenen anahtarlar durumunda, son satırdaki değer kullanılır. Anahtar olarak `null` kullanıldığında, dizi sıfırdan başlayarak sayısal olarak indekslenir (o zaman çakışma olmaz): + +```php +$names = $result->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] +``` + + +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Alternatif olarak, parametre olarak her satır için ya değeri ya da anahtar-değer çiftini döndürecek bir geri arama (callback) belirtebilirsiniz. + +```php +$result = $database->query('SELECT * FROM users'); +$items = $result->fetchPairs(fn($row) => "$row->id - $row->name"); +// ['1 - John', '2 - Jane', ...] + +// Geri arama ayrıca anahtar & değer çifti içeren bir dizi döndürebilir: +$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]); +// ['John' => 46, 'Jane' => 21, ...] +``` + + +fetchField(): mixed .[method] +----------------------------- + +Geçerli satırdaki ilk hücrenin değerini döndürür. Başka satır yoksa `null` döndürür. Dahili göstericiyi bir sonraki satıra taşır. + +```php +$result = $database->query('SELECT name FROM users'); +$name = $result->fetchField(); // ilk satırdan adı yükler +``` + + +fetchList(): ?array .[method] +----------------------------- + +Satırı indeksli bir dizi olarak döndürür. Başka satır yoksa `null` döndürür. Dahili göstericiyi bir sonraki satıra taşır. + +```php +$result = $database->query('SELECT name, email FROM users'); +$row = $result->fetchList(); // ['John', 'john@example.com'] +``` + + +getRowCount(): ?int .[method] +----------------------------- + +Son `UPDATE` veya `DELETE` sorgusundan etkilenen satır sayısını döndürür. `SELECT` için bu, döndürülen satır sayısıdır, ancak bu bilinmeyebilir - bu durumda metot `null` döndürür. + + +getColumnCount(): ?int .[method] +-------------------------------- + +`ResultSet`'teki sütun sayısını döndürür. + + +Sorgu Bilgileri +=============== + +Hata ayıklama amacıyla, son yürütülen sorgu hakkında bilgi alabiliriz: + +```php +echo $database->getLastQueryString(); // SQL sorgusunu yazdırır + +$result = $database->query('SELECT * FROM articles'); +echo $result->getQueryString(); // SQL sorgusunu yazdırır +echo $result->getTime(); // yürütme süresini saniye cinsinden yazdırır +``` + +Sonucu HTML tablosu olarak görüntülemek için şunu kullanabilirsiniz: + +```php +$result = $database->query('SELECT * FROM articles'); +$result->dump(); +``` + +ResultSet, sütun tipleri hakkında bilgi sunar: + +```php +$result = $database->query('SELECT * FROM articles'); +$types = $result->getColumnTypes(); + +foreach ($types as $column => $type) { + echo "$column tipi $type->type"; // örn. 'id tipi int' +} +``` + + +Sorgu Günlüklemesi +------------------ + +Kendi sorgu günlüklememizi uygulayabiliriz. `onQuery` olayı, her yürütülen sorgudan sonra çağrılacak geri arama (callback) dizisidir: + +```php +$database->onQuery[] = function ($database, $result) use ($logger) { + $logger->info('Sorgu: ' . $result->getQueryString()); + $logger->info('Süre: ' . $result->getTime()); + + if ($result->getRowCount() > 1000) { + $logger->warning('Büyük sonuç kümesi: ' . $result->getRowCount() . ' satır'); + } +}; +``` diff --git a/database/tr/transactions.texy b/database/tr/transactions.texy new file mode 100644 index 0000000000..296291f458 --- /dev/null +++ b/database/tr/transactions.texy @@ -0,0 +1,43 @@ +İşlemler (Transactions) +*********************** + +.[perex] +İşlemler (Transactions), işlem içindeki tüm operasyonların ya gerçekleştirilmesini ya da hiçbirinin gerçekleştirilmemesini garanti eder. Daha karmaşık operasyonlarda veri tutarlılığını sağlamak için kullanışlıdırlar. + +İşlemleri kullanmanın en basit yolu şöyledir: + +```php +$database->beginTransaction(); +try { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); + $database->commit(); +} catch (\Exception $e) { + $database->rollBack(); + throw $e; +} +``` + +Aynı şeyi `transaction()` metodunu kullanarak çok daha zarif bir şekilde yazabilirsiniz. Parametre olarak, işlem içinde yürüteceği bir geri arama (callback) kabul eder. Geri arama bir istisna olmadan çalışırsa, işlem otomatik olarak onaylanır (commit). Bir istisna oluşursa, işlem iptal edilir (rollback) ve istisna daha da yayılır. + +```php +$database->transaction(function ($database) use ($id) { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); +}); +``` + +`transaction()` metodu ayrıca değerler de döndürebilir: + +```php +$count = $database->transaction(function ($database) { + $result = $database->query('UPDATE users SET active = ?', true); + return $result->getRowCount(); // güncellenen satır sayısını döndürür +}); +``` diff --git a/database/uk/@home.texy b/database/uk/@home.texy index 3f62d1da11..9cd3b93223 100644 --- a/database/uk/@home.texy +++ b/database/uk/@home.texy @@ -1,20 +1,21 @@ -Підтримувані сервери -==================== +Підтримувані бази даних +======================= -Підтримуються такі сервери баз даних: +Nette підтримує наступні бази даних: -|* Сервер |* Ім'я DSN |* Підтримка Core |* Підтримка Explorer -| MySQL (>= 5.1) | mysql | ТАК | ТАК -| PostgreSQL (>= 9.0) | pgsql | ТАК | ТАК -| Sqlite 3 (>= 3.8) | sqlite | ТАК | ТАК -| Oracle | oci | ТАК | - -| MS SQL (PDO_SQLSRV) | sqlsrv | ТАК | ТАК -| MS SQL (PDO_DBLIB) | mssql | ТАК | - -| ODBC | odbc | ТАК | - +|* Сервер бази даних |* Ім'я DSN |* Підтримка в Core |* Підтримка в Explorer +| MySQL (>= 5.1) | mysql | ТАК | ТАК +| PostgreSQL (>= 9.0) | pgsql | ТАК | ТАК +| Sqlite 3 (>= 3.8) | sqlite | ТАК | ТАК +| Oracle | oci | ТАК | - +| MS SQL (PDO_SQLSRV) | sqlsrv | ТАК | ТАК +| MS SQL (PDO_DBLIB) | mssql | ТАК | - +| ODBC | odbc | ТАК | - -{{title: Nette Database}} -{{description: Nette Database спрощує отримання даних з бази даних без необхідності писати SQL-запити. Він ставить ефективні запити і не передає зайвих даних.}} + +{{maintitle: Nette Database - awesome database layer for PHP}} +{{description: Nette Database суттєво спрощує отримання даних з бази даних без необхідності писати SQL-запити. Вона виконує ефективні запити та не передає зайвих даних.}} diff --git a/database/uk/@left-menu.texy b/database/uk/@left-menu.texy index 21b388c6a2..f8a09afcd7 100644 --- a/database/uk/@left-menu.texy +++ b/database/uk/@left-menu.texy @@ -1,5 +1,12 @@ -База даних -********** -- [Core |Core] +Nette Database +************** +- [Вступ |guide] +- [SQL-доступ |sql way] - [Explorer |Explorer] -- [Налаштування |configuration] +- [Транзакції |transactions] +- [Винятки |exceptions] +- [Рефлексія |reflection] +- [Мапування |mapping] +- [Конфігурація |configuration] +- [Ризики безпеки |security] +- [Оновлення |en:upgrading] diff --git a/database/uk/@meta.texy b/database/uk/@meta.texy new file mode 100644 index 0000000000..96e2d9752a --- /dev/null +++ b/database/uk/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документація Nette}} diff --git a/database/uk/configuration.texy b/database/uk/configuration.texy index b6d11c7ac9..60db622a55 100644 --- a/database/uk/configuration.texy +++ b/database/uk/configuration.texy @@ -1,16 +1,16 @@ -Налаштування бази даних +Конфігурація бази даних *********************** .[perex] -Огляд варіантів конфігурації для бази даних Nette. +Огляд параметрів конфігурації для Nette Database. -Якщо ви використовуєте не весь фреймворк, а тільки цю бібліотеку, прочитайте [Як завантажити файл конфігурації |bootstrap:]. +Якщо ви не використовуєте весь фреймворк, а лише цю бібліотеку, прочитайте, [як завантажити конфігурацію|bootstrap:]. -Одне підключення .[#toc-single-connection] ------------------------------------------- +Одне з'єднання +-------------- -Налаштуйте одне підключення до бази даних: +Конфігурація одного з'єднання з базою даних: ```neon database: @@ -20,54 +20,60 @@ database: password: ... ``` -Він створює служби типу `Nette\Database\Connection`, а також `Nette\Database\Explorer` для рівня [Database Explorer |explorer]. Підключення до бази даних зазвичай передається автопідключенням, якщо це неможливо, використовуйте імена сервісів `@database.default.connection` і `@database.default.context`. +Створює сервіси `Nette\Database\Connection` та `Nette\Database\Explorer`, які зазвичай передаються за допомогою [autowiring |dependency-injection:autowiring], або посиланням на [їхню назву |#Сервіси DI]. Інші налаштування: ```neon database: - # відображає панель Database у Tracy Bar - debugger: ... # (bool) за замовчуванням true + # відображати панель бази даних у Tracy Bar? + debugger: ... # (bool) за замовчуванням true - # відображає запит EXPLAIN у Tracy Bar - explain: ... # (bool) за замовчуванням true + # відображати EXPLAIN запитів у Tracy Bar? + explain: ... # (bool) за замовчуванням true - # увімкнути автозв'язування для цього з'єднання + # дозволити autowiring для цього з'єднання? autowired: ... # (bool) за замовчуванням true для першого з'єднання - # умовні позначення таблиці: discovered, static, або ім'я класу - conventions: discovered # (string) за замовчуванням 'discovered' + # конвенції таблиць: discovered, static або ім'я класу + conventions: discovered # (string) за замовчуванням 'discovered' options: - # підключатися до бази даних тільки за потреби? + # підключатися до бази даних лише коли це необхідно? lazy: ... # (bool) за замовчуванням false - # Клас драйвера бази даних PHP + # PHP клас драйвера бази даних driverClass: # (string) - # тільки для MySQL: встановлює sql_mode + # лише MySQL: встановлює sql_mode sqlmode: # (string) - # тільки для MySQL: встановлює SET NAMES - charset: # (string) за замовчуванням 'utf8mb4' ('utf8' до v5.5.3) + # лише MySQL: встановлює SET NAMES + charset: # (string) за замовчуванням 'utf8mb4' - # тільки для Oracle і SQLite: формат даті + # лише MySQL: перетворює TINYINT(1) на bool + convertBoolean: # (bool) за замовчуванням false + + # повертає стовпці з датою як immutable об'єкти (з версії 3.2.1) + newDateTime: # (bool) за замовчуванням false + + # лише Oracle та SQLite: формат для зберігання дати formatDateTime: # (string) за замовчуванням 'U' ``` -Ключ `options` може містити інші опції, які можна знайти в [документації щодо драйвера PDO |https://www.php.net/manual/en/pdo.drivers.php], наприклад: +У ключі `options` можна вказувати інші параметри, які ви знайдете в [документації драйверів PDO |https://www.php.net/manual/en/pdo.drivers.php], наприклад: ```neon -база данных: +database: options: PDO::MYSQL_ATTR_COMPRESS: true ``` -Множинні підключення .[#toc-multiple-connections] -------------------------------------------------- +Кілька з'єднань +--------------- -У конфігурації ми можемо визначити більше з'єднань із базою даних, розділивши їх на іменовані секції: +У конфігурації ми можемо визначити і кілька з'єднань з базою даних, розділивши їх на іменовані секції: ```neon database: @@ -80,9 +86,23 @@ database: dsn: 'sqlite::memory:' ``` -Кожне визначене з'єднання створює сервіси, що включають ім'я секції у своє ім'я, тобто `@database.main.connection` & `@database.main.context` і далі `@database.another.connection` & `@database.another.context`. +Autowiring увімкнено лише для сервісів з першої секції. Це можна змінити за допомогою `autowired: false` або `autowired: true`. + + +Сервіси DI +---------- + +Ці сервіси додаються до DI-контейнера, де `###` представляє назву з'єднання: + +| Назва | Тип | Опис +|---------------------------------------------------------- +| `database.###.connection` | [api:Nette\Database\Connection] | з'єднання з базою даних +| `database.###.explorer` | [api:Nette\Database\Explorer] | [Database Explorer |explorer] + + +Якщо ми визначаємо лише одне з'єднання, назви сервісів будуть `database.default.connection` та `database.default.explorer`. Якщо ми визначаємо кілька з'єднань, як у прикладі вище, назви будуть відповідати секціям, тобто `database.main.connection`, `database.main.explorer`, а також `database.another.connection` та `database.another.explorer`. -Автозв'язування ввімкнене тільки для сервісів із першої секції. Це можна змінити за допомогою `autowired: false` або `autowired: true`. Неавтоматизовані сервіси передаються за іменем: +Сервіси без autowiring передаються явно за посиланням на їхню назву: ```neon services: diff --git a/database/uk/core.texy b/database/uk/core.texy deleted file mode 100644 index fb2022e9f7..0000000000 --- a/database/uk/core.texy +++ /dev/null @@ -1,350 +0,0 @@ -Ядро бази даних -*************** - -.[perex] -Nette Database Core є рівнем абстракції бази даних і забезпечує основну функціональність. - - -Встановлення .[#toc-installation] -================================= - -Завантажте та встановіть пакет за допомогою [Composer |best-practices:composer]: - -```shell -composer require nette/database -``` - - -Підключення та налаштування .[#toc-connection-and-configuration] -================================================================ - -Щоб підключитися до бази даних, просто створіть екземпляр класу [api:Nette\Database\Connection]: - -```php -$database = new Nette\Database\Connection($dsn, $user, $password); -``` - -Параметр `$dsn` (ім'я джерела даних) - [такий самий, як використовується в PDO |https://www.php.net/manual/ru/pdo.construct.php#refsect1-pdo.construct-parameters], наприклад `host=127.0.0.1;dbname=test`. У разі невдачі викидається виняток `Nette\Database\ConnectionException`. - -Однак, більш складний спосіб пропонує [конфігурація програми |configuration]. Ми додамо розділ `database`, і він створить необхідні об'єкти та панель `Database` в панелі налагодження [Tracy |tracy:]. - -```neon -database: - dsn: 'mysql:host=127.0.0.1;dbname=test' - user: root - password: password -``` - -Об'єкт з'єднання, який ми [отримуємо як сервіс від DI-контейнера |dependency-injection:passing-dependencies], наприклад: - -```php -class Model -{ - // передаємо Nette\Database\Explorer для роботи з рівнем Database Explorer - public function __construct( - private Nette\Database\Connection $database, - ) { - } -} -``` - -Для отримання додаткової інформації дивіться [конфігурацію бази даних |configuration]. - - -Запити .[#toc-queries] -====================== - -Для запиту до бази даних використовуйте метод `query()`, який повертає [ResultSet |api:Nette\Database\ResultSet]. - -```php -$result = $database->query('SELECT * FROM users'); - -foreach ($result as $row) { - echo $row->id; - echo $row->name; -} - -echo $result->getRowCount(); // повертає кількість рядків, якщо вона відома -``` - -.[note] -Над `ResultSet` можна виконати ітерацію тільки один раз, якщо нам потрібно виконати ітерацію кілька разів, необхідно перетворити результат у масив за допомогою методу `fetchAll()`. - -Ви можете легко додати параметри в запит, зверніть увагу на знак питання: - -```php -$database->query('SELECT * FROM users WHERE name = ?', $name); - -$database->query('SELECT * FROM users WHERE name = ? AND active = ?', $name, $active); - -$database->query('SELECT * FROM users WHERE id IN (?)', $ids); // $ids - масив -``` -<div class=warning> - -УВАГА, ніколи не об'єднуйте рядки, щоб уникнути [уразливості через SQL-ін'єкції |https://ru.wikipedia.org/wiki/%D0%92%D0%BD%D0%B5%D0%B4%D1%80%D0%B5%D0%BD%D0%B8%D0%B5_SQL-%D0%BA%D0%BE%D0%B4%D0%B0]! -/-- -$db->query('SELECT * FROM users WHERE name = ' . $name); // НЕПРАВИЛЬНО!!! -\-- -</div> - -У разі невдачі `query()` викидає або виняток `Nette\Database\DriverException`, або одне з його дочірніх винятків: - -- [ConstraintViolationException |api:Nette\Database\ConstraintViolationException] - порушення будь-якої з умов -- [ForeignKeyConstraintViolationException |api:Nette\Database\ForeignKeyConstraintViolationException] - неприпустимий зовнішній ключ -- [NotNullConstraintViolationException |api:Nette\Database\NotNullConstraintViolationException] - порушення умови NOT NULL -- [UniqueConstraintViolationException |api:Nette\Database\UniqueConstraintViolationException] - конфлікт унікального індексу - -Крім `query()`, існують і інші корисні методи: - -```php -// повертає асоціативний масив id => name -$pairs = $database->fetchPairs('SELECT id, name FROM users'); - -// повертає всі рядки у вигляді масиву -$rows = $database->fetchAll('SELECT * FROM users'); - -// повертає один рядок -$row = $database->fetch('SELECT * FROM users WHERE id = ?', $id); - -// повертає одне поле -$name = $database->fetchField('SELECT name FROM users WHERE id = ?', $id); -``` - -У разі невдачі всі ці методи викидають виняток `Nette\Database\DriverException`. - - -Вставка, оновлення та видалення .[#toc-insert-update-delete] -============================================================ - -Параметр, який ми вставляємо в SQL-запит, також може бути масивом (у цьому випадку можна пропустити знак підстановки `?`), что может быть полезно для оператора `INSERT`: - -```php -$database->query('INSERT INTO users ?', [ // тут може бути опущений знак питання - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) - -$id = $database->getInsertId(); // повертає автоінкремент вставленого рядка - -$id = $database->getInsertId($sequence); // або значення послідовності -``` - -Вставка декількох значень: - -```php -$database->query('INSERT INTO users', [ - [ - 'name' => 'Jim', - 'year' => 1978, - ], [ - 'name' => 'Jack', - 'year' => 1987, - ], -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) -``` - -Ми також можемо передавати файли, об'єкти DateTime або [перерахування |https://www.php.net/enumerations]: - -```php -$database->query('INSERT INTO users', [ - 'name' => $name, - 'created' => new DateTime, // або $database::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // вставляє вміст файлу - 'status' => State::New, // enum State -]); -``` - -Оновлення рядків: - -```php -$result = $database->query('UPDATE users SET', [ - 'name' => $name, - 'year' => $year, -], 'WHERE id = ?', $id); -// UPDATE users SET `name` = 'Jim', `year` = 1978 WHERE id = 123 - -echo $result->getRowCount(); // повертає кількість порушених рядків -``` - -Для UPDATE ми можемо використовувати оператори `+=` і `-=`: - -```php -$database->query('UPDATE users SET', [ - 'age+=' => 1, // note += -], 'WHERE id = ?', $id); -// UPDATE users SET `age` = `age` + 1 -``` - -Видалення: - -```php -$result = $database->query('DELETE FROM users WHERE id = ?', $id); -echo $result->getRowCount(); // повертає кількість порушених рядків -``` - - -Просунуті запити .[#toc-advanced-queries] -========================================= - -Вставка або оновлення, якщо запис уже існує: - -```php -$database->query('INSERT INTO users', [ - 'id' => $id, - 'name' => $name, - 'year' => $year, -], 'ON DUPLICATE KEY UPDATE', [ - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) -// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 -``` - -Зверніть увагу, що Nette Database розпізнає SQL-контекст, у який вставлено параметр масиву, і будує SQL-код відповідним чином. Так, з першого масиву він генерує `(id, name, year) VALUES (123, 'Jim', 1978)`, а другий перетворює на `name = 'Jim', year = 1978`. - -Ми також можемо описати сортування за допомогою масиву, в якому ключами є імена стовпців, а значеннями - значення типу boolean, що визначають, чи слід сортувати в порядку зростання: - -```php -$database->query('SELECT id FROM author ORDER BY', [ - 'id' => true, // за зростанням - 'name' => false, // за спаданням -]); -// SELECT id FROM author ORDER BY `id`, `name` DESC -``` - -Якщо виявлення не спрацювало, ви можете вказати форму збірки за допомогою знака підстановки `?`, за яким слідує підказка. Підтримуються такі підказки: - -| ?values | (key1, key2, ...) VALUES (value1, value2, ...) -| ?set | key1 = value1, key2 = value2, ... -| ?and | key1 = value1 AND key2 = value2 ... -| ?or | key1 = value1 АБО key2 = value2 ... -| ?order | key1 ASC, key2 DESC - -У реченні WHERE використовується оператор `?and`, тому умови пов'язані `AND`: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND `year` = 1978 -``` - -Який можна легко змінити на `OR`, використовуючи знак підстановки `?or`: - -```php -$result = $database->query('SELECT * FROM users WHERE ?or', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' OR `year` = 1978 -``` - -Ми можемо використовувати оператори в умовах: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name <>' => $name, - 'year >' => $year, -]); -// SELECT * FROM users WHERE `name` <> 'Jim' AND `year` > 1978 -``` - -А також перерахування: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => ['Jim', 'Jack'], - 'role NOT IN' => ['admin', 'owner'], // перерахування + оператор NOT IN -]); -// SELECT * FROM users WHERE -// `name` IN ('Jim', 'Jack') AND `role` NOT IN ('admin', 'owner') -``` - -Ми також можемо включити частину користувацького SQL-коду, використовуючи так званий SQL-літерал: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year >' => $database::literal('YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) -``` - -Як альтернативу: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) -``` - -SQL-літерал також може мати свої параметри: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > ? AND year < ?', $min, $max), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) -``` - -Завдяки цьому ми можемо створювати цікаві комбінації: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('?or', [ - 'active' => true, - 'role' => $role, - ]), -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') -``` - - -Ім'я змінної .[#toc-variable-name] -================================== - -Існує знак підстановки `?name', який використовується, якщо ім'я таблиці або стовпця є змінною. (Обережно, не дозволяйте користувачеві маніпулювати вмістом такої змінної): - -```php -$table = 'blog.users'; -$column = 'name'; -$database->query('SELECT * FROM ?name WHERE ?name = ?', $table, $column, $name); -// SELECT * FROM `blog`.`users` WHERE `name` = 'Jim' -``` - - -Транзакції .[#toc-transactions] -=============================== - -Існує три методи роботи з транзакціями: - -```php -$database->beginTransaction(); - -$database->commit(); - -$database->rollback(); -``` - -Елегантний спосіб пропонує метод `transaction()`. Ви передаєте зворотний виклик, який виконується в транзакції. Якщо під час виконання виникає виняток, транзакція скидається, якщо все йде добре, транзакція фіксується. - -```php -$id = $database->transaction(function ($database) { - $database->query('DELETE FROM ...'); - $database->query('INSERT INTO ...'); - // ... - return $database->getInsertId(); -}); -``` - -Як бачите, метод `transaction()` повертає значення зворотного виклику, що повертається. - -Транзакція() також може бути вкладеною, що спрощує реалізацію незалежних сховищ. diff --git a/database/uk/exceptions.texy b/database/uk/exceptions.texy new file mode 100644 index 0000000000..3d1642573e --- /dev/null +++ b/database/uk/exceptions.texy @@ -0,0 +1,34 @@ +Винятки +******* + +Nette Database використовує ієрархію винятків. Базовим класом є `Nette\Database\DriverException`, який успадковує від `PDOException` і надає розширені можливості для роботи з помилками бази даних: + +- Метод `getDriverCode()` повертає код помилки від драйвера бази даних +- Метод `getSqlState()` повертає код SQLSTATE +- Методи `getQueryString()` та `getParameters()` дозволяють отримати початковий запит та його параметри + +Від `DriverException` успадковуються наступні спеціалізовані винятки: + +- `ConnectionException` - сигналізує про збій підключення до сервера бази даних +- `ConstraintViolationException` - базовий клас для порушення обмежень бази даних, від якого успадковуються: + - `ForeignKeyConstraintViolationException` - порушення зовнішнього ключа + - `NotNullConstraintViolationException` - порушення обмеження NOT NULL + - `UniqueConstraintViolationException` - порушення унікальності значення + + +Приклад перехоплення винятку `UniqueConstraintViolationException`, який виникає, коли ми намагаємося вставити користувача з email, який вже існує в базі даних (за умови, що стовпець email має унікальний індекс). + +```php +try { + $database->query('INSERT INTO users', [ + 'email' => 'john@example.com', + 'name' => 'John Doe', + 'password' => $hashedPassword, + ]); +} catch (Nette\Database\UniqueConstraintViolationException $e) { + echo 'Користувач з цим email вже існує.'; + +} catch (Nette\Database\DriverException $e) { + echo 'Сталася помилка під час реєстрації: ' . $e->getMessage(); +} +``` diff --git a/database/uk/explorer.texy b/database/uk/explorer.texy index 8c04ec8f5d..164032ccda 100644 --- a/database/uk/explorer.texy +++ b/database/uk/explorer.texy @@ -1,550 +1,912 @@ -Провідник баз даних -******************* +Database Explorer +***************** <div class=perex> -Nette Database Explorer значно спрощує отримання даних з бази даних без написання SQL-запитів. +Explorer пропонує інтуїтивно зрозумілий та ефективний спосіб роботи з базою даних. Він автоматично дбає про зв'язки між таблицями та оптимізацію запитів, тож ви можете зосередитися на своєму додатку. Працює одразу без налаштувань. Якщо вам потрібен повний контроль над SQL-запитами, ви можете скористатися [SQL-підходом |SQL way]. -- використовує ефективні запити -- дані не передаються без необхідності -- вирізняється елегантним синтаксисом +- Робота з даними є природною та легкою для розуміння +- Генерує оптимізовані SQL-запити, які завантажують лише необхідні дані +- Дозволяє легко отримати доступ до пов'язаних даних без необхідності писати JOIN-запити +- Працює одразу без будь-якої конфігурації чи генерації сутностей </div> -Щоб використовувати Database Explorer, почніть з таблиці - викличте `table()` на об'єкті [api:Nette\Database\Explorer]. Найпростіше отримати екземпляр контекстного об'єкта [описано тут |core#Connection-and-Configuration], або, у випадку, коли Nette Database Explorer використовується як окремий інструмент, його можна [створити вручну |#Creating-Explorer-Manually]. + +З Explorer ви починаєте, викликаючи метод `table()` об'єкта [api:Nette\Database\Explorer] (деталі підключення див. у розділі [Підключення та конфігурація |guide#Підключення та конфігурація]): ```php -$books = $explorer->table('book'); // ім'я таблиці в бд - 'book' +$books = $explorer->table('book'); // 'book' - назва таблиці ``` -Виклик повертає екземпляр об'єкта [Selection |api:Nette\Database\Table\Selection], який можна ітерувати для отримання всіх книг. Кожен елемент (рядок) представлений екземпляром [ActiveRow |api:Nette\Database\Table\ActiveRow] з даними, відображеними в його властивостях: +Метод повертає об'єкт [Selection |api:Nette\Database\Table\Selection], який представляє SQL-запит. До цього об'єкта можна додавати інші методи для фільтрації та сортування результатів. Запит складається та виконується лише тоді, коли ми починаємо запитувати дані. Наприклад, проходячи циклом `foreach`. Кожен рядок представлений об'єктом [ActiveRow |api:Nette\Database\Table\ActiveRow]: ```php foreach ($books as $book) { - echo $book->title; - echo $book->author_id; + echo $book->title; // виведення стовпця 'title' + echo $book->author_id; // виведення стовпця 'author_id' } ``` -Отримання тільки одного конкретного ряду здійснюється методом `get()`, який безпосередньо повертає екземпляр ActiveRow. +Explorer суттєво спрощує роботу зі [зв'язками між таблицями |#Зв язки між таблицями]. Наступний приклад показує, як легко ми можемо вивести дані з пов'язаних таблиць (книги та їхні автори). Зверніть увагу, що нам не потрібно писати жодних JOIN-запитів, Nette створить їх за нас: ```php -$book = $explorer->table('book')->get(2); // повертає книгу з ідентифікатором 2 -echo $book->title; -echo $book->author_id; +$books = $explorer->table('book'); + +foreach ($books as $book) { + echo 'Книга: ' . $book->title; + echo 'Автор: ' . $book->author->name; // створить JOIN на таблицю 'author' +} ``` -Давайте розглянемо поширений випадок використання. Вам потрібно отримати книги та їхніх авторів. Це звичайне відношення 1:N. Часто використовуваним рішенням є отримання даних за допомогою одного SQL-запиту з об'єднанням таблиць. Друга можливість - отримати дані окремо, виконати один запит для отримання книг, а потім отримати автора для кожної книги іншим запитом (наприклад, у циклі foreach). Це можна легко оптимізувати для виконання лише двох запитів, один для книг, а інший для потрібних авторів - і саме так це робить Nette Database Explorer. +Nette Database Explorer оптимізує запити, щоб вони були максимально ефективними. Вищезгаданий приклад виконає лише два SELECT-запити, незалежно від того, чи обробляємо ми 10 чи 10 000 книг. -У наведених нижче прикладах ми працюватимемо зі схемою бази даних, показаною на малюнку. Є зв'язки OneHasMany (1:N) (автор книжки `author_id` і можливий перекладач `translator_id`, який може бути `null`) і зв'язки ManyHasMany (M:N) між книжкою та її тегами. +Крім того, Explorer відстежує, які стовпці використовуються в коді, і завантажує з бази даних лише їх, тим самим заощаджуючи додаткову продуктивність. Ця поведінка повністю автоматична та адаптивна. Якщо ви пізніше зміните код і почнете використовувати інші стовпці, Explorer автоматично змінить запити. Вам не потрібно нічого налаштовувати або думати про те, які стовпці вам знадобляться - залиште це Nette. -[Приклад, включно зі схемою, можна знайти на GitHub |https://github.com/nette-examples/books]. -[* db-schema-1-.webp *] *** Структура бази даних, що використовується в прикладах .<> +Фільтрація та сортування +======================== -Наступний код перераховує ім'я автора для кожної книги та всі її теги. Ми [обговоримо нижче |#Working-with-Relationships], як це працює всередині. +Клас `Selection` надає методи для фільтрації та сортування вибірки даних. -```php -$books = $explorer->table('book'); +.[language-php] +| `where($condition, ...$params)` | Додає умову WHERE. Кілька умов об'єднуються оператором AND +| `whereOr(array $conditions)` | Додає групу умов WHERE, об'єднаних оператором OR +| `wherePrimary($value)` | Додає умову WHERE за первинним ключем +| `order($columns, ...$params)` | Встановлює сортування ORDER BY +| `select($columns, ...$params)` | Вказує стовпці, які потрібно завантажити +| `limit($limit, $offset = null)` | Обмежує кількість рядків (LIMIT) та опціонально встановлює OFFSET +| `page($page, $itemsPerPage, &$total = null)` | Встановлює пагінацію +| `group($columns, ...$params)` | Групує рядки (GROUP BY) +| `having($condition, ...$params)` | Додає умову HAVING для фільтрації згрупованих рядків -foreach ($books as $book) { - echo 'title: ' . $book->title; - echo 'written by: ' . $book->author->name; // $book->author - рядок із таблиці 'author' +Методи можна ланцюжком (так званий [fluent interface |nette:introduction-to-object-oriented-programming#Fluent Interfaces]): `$table->where(...)->order(...)->limit(...)`. - echo 'tags: '; - foreach ($book->related('book_tag') as $bookTag) { - echo $bookTag->tag->name . ', '; // $bookTag->tag - рядок із таблиці 'tag' - } -} -``` +У цих методах ви також можете використовувати спеціальну нотацію для доступу до [даних з пов'язаних таблиць |#Запити через пов язані таблиці]. -Ви будете задоволені тим, наскільки ефективно працює шар бази даних. Наведений вище приклад робить постійну кількість запитів, які виглядають наступним чином: -```sql -SELECT * FROM `book` -SELECT * FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT * FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) -``` +Екранування та ідентифікатори +----------------------------- -Якщо ви використовуєте [кеш |caching:] (за замовчуванням увімкнено), жодні стовпці не будуть запитуватися без потреби. Після першого запиту в кеші будуть збережені імена використаних стовпців, і Nette Database Explorer буде виконувати запити тільки з потрібними стовпцями: +Методи автоматично екранують параметри та беруть у лапки ідентифікатори (назви таблиць та стовпців), тим самим запобігаючи SQL injection. Для правильної роботи необхідно дотримуватися кількох правил: -```sql -SELECT `id`, `title`, `author_id` FROM `book` -SELECT `id`, `name` FROM `author` WHERE (`author`.`id` IN (11, 12)) -SELECT `book_id`, `tag_id` FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) -SELECT `id`, `name` FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23)) +- Ключові слова, назви функцій, процедур тощо пишіть **великими літерами**. +- Назви стовпців та таблиць пишіть **малими літерами**. +- Рядки завжди підставляйте через **параметри**. + +```php +where('name = ' . $name); // КРИТИЧНА ВРАЗЛИВІСТЬ: SQL injection +where('name LIKE "%search%"'); // ПОГАНО: ускладнює автоматичне взяття в лапки +where('name LIKE ?', '%search%'); // ПРАВИЛЬНО: значення підставлене через параметр + +where('name like ?', $name); // ПОГАНО: згенерує: `name` `like` ? +where('name LIKE ?', $name); // ПРАВИЛЬНО: згенерує: `name` LIKE ? +where('LOWER(name) = ?', $value);// ПРАВИЛЬНО: LOWER(`name`) = ? ``` -Вибірки .[#toc-selections] -========================== +where(string|array $condition, ...$parameters): static .[method] +---------------------------------------------------------------- -Дивіться можливості фільтрації та обмеження рядків [api:Nette\Database\Table\Selection]: +Фільтрує результати за допомогою умов WHERE. Її сильною стороною є інтелектуальна робота з різними типами значень та автоматичний вибір SQL-операторів. -.[language-php] -| `$table->where($where[, $param[, ...]])` | Встановлюємо WHERE, використовуючи AND як клей, якщо задано дві або більше умов -| `$table->whereOr($where)` | Встановлюємо WHERE, використовуючи OR як зв'язку, якщо задано дві або більше умов -| `$table->order($columns)` | Встановлюємо ORDER BY, наприклад, за допомогою виразу `('column DESC, id DESC')`. -| `$table->select($columns)` | Встановлюємо витягнуті стовпці, наприклад, за допомогою виразу `('col, MD5(col) AS hash')`. -| `$table->limit($limit[, $offset])` | Встановлюємо LIMIT та OFFSET -| `$table->page($page, $itemsPerPage[, &$lastPage])` | Вмикаємо пагінацію -| `$table->group($columns)` | Встановлюємо GROUP BY -| `$table->having($having)` | Встановлюємо HAVING +Базове використання: -Можна використовувати поточний інтерфейс (Fluent), наприклад `$table->where(...)->order(...)->limit(...)`. Кілька умов `where` або `whereOr` з'єднуються за допомогою оператора `AND`. +```php +$table->where('id', $value); // WHERE `id` = 123 +$table->where('id > ?', $value); // WHERE `id` > 123 +$table->where('id = ? OR name = ?', $id, $name); // WHERE `id` = 1 OR `name` = 'Jon Snow' +``` +Завдяки автоматичному визначенню відповідних операторів нам не потрібно розбиратися з різними спеціальними випадками. Nette вирішить їх за нас: -where() .[#toc-where] ---------------------- +```php +$table->where('id', 1); // WHERE `id` = 1 +$table->where('id', null); // WHERE `id` IS NULL +$table->where('id', [1, 2, 3]); // WHERE `id` IN (1, 2, 3) +// можна використовувати і знак питання без оператора: +$table->where('id ?', 1); // WHERE `id` = 1 +``` -Nette Database Explorer може автоматично додавати необхідні оператори для переданих значень: +Метод правильно обробляє також заперечні умови та порожні масиви: -.[language-php] -| `$table->where('field', $value)` | field = $value -| `$table->where('field', null)` | field IS NULL -| `$table->where('field > ?', $val)` | field > $val -| `$table->where('field', [1, 2])` | field IN (1, 2) -| `$table->where('id = ? OR name = ?', 1, $name)` | id = 1 OR name = 'Jon Snow' -| `$table->where('field', $explorer->table($tableName))` | field IN (SELECT $primary FROM $tableName) -| `$table->where('field', $explorer->table($tableName)->select('col'))` | field IN (SELECT col FROM $tableName) +```php +$table->where('id', []); // WHERE `id` IS NULL AND FALSE -- нічого не знайде +$table->where('id NOT', []); // WHERE `id` IS NULL OR TRUE -- знайде все +$table->where('NOT (id ?)', []); // WHERE NOT (`id` IS NULL AND FALSE) -- знайде все +// $table->where('NOT id ?', $ids); Увага - ця синтаксична конструкція не підтримується +``` -Ви можете вказати заповнювач навіть без оператора column. Ці виклики однакові. +Як параметр ми можемо передати також результат з іншої таблиці - створиться підзапит: ```php -$table->where('id = ? OR id = ?', 1, 2); -$table->where('id ? OR id ?', 1, 2); +// WHERE `id` IN (SELECT `id` FROM `tableName`) +$table->where('id', $explorer->table($tableName)); + +// WHERE `id` IN (SELECT `col` FROM `tableName`) +$table->where('id', $explorer->table($tableName)->select('col')); ``` -Ця функція дозволяє генерувати правильний оператор на основі значення: +Умови ми можемо передати також як масив, елементи якого об'єднаються за допомогою AND: ```php -$table->where('id ?', 2); // id = 2 -$table->where('id ?', null); // id IS NULL -$table->where('id', $ids); // id IN (...) +// WHERE (`price_final` < `price_original`) AND (`stock_count` > `min_stock`) +$table->where([ + 'price_final < price_original', + 'stock_count > min_stock', +]); ``` -Selection коректно обробляє і негативні умови, працює і для порожніх масивів: +У масиві ми можемо використовувати пари ключ => значення, і Nette знову автоматично вибере правильні оператори: ```php -$table->where('id', []); // id IS NULL AND FALSE -$table->where('id NOT', []); // id IS NULL OR TRUE -$table->where('NOT (id ?)', $ids); // NOT (id IS NULL AND FALSE) +// WHERE (`status` = 'active') AND (`id` IN (1, 2, 3)) +$table->where([ + 'status' => 'active', + 'id' => [1, 2, 3], +]); +``` -// це призведе до виключення, цей синтаксис не підтримується -$table->where('NOT id ?', $ids); +У масиві ми можемо комбінувати SQL-вирази зі знаками питання та кількома параметрами. Це зручно для складних умов з точно визначеними операторами: + +```php +// WHERE (`age` > 18) AND (ROUND(`score`, 2) > 75.5) +$table->where([ + 'age > ?' => 18, + 'ROUND(score, ?) > ?' => [2, 75.5], // два параметри передаємо як масив +]); ``` +Багаторазовий виклик `where()` автоматично об'єднує умови за допомогою AND. + -whereOr() .[#toc-whereor] -------------------------- +whereOr(array $parameters): static .[method] +-------------------------------------------- -Приклад використання без параметрів: +Подібно до `where()`, додає умови, але з тією різницею, що об'єднує їх за допомогою OR: ```php -// WHERE (user_id IS NULL) OR (SUM(`field1`) > SUM(`field2`)) +// WHERE (`status` = 'active') OR (`deleted` = 1) $table->whereOr([ - 'user_id IS NULL', - 'SUM(field1) > SUM(field2)', + 'status' => 'active', + 'deleted' => true, ]); ``` -Ми використовуємо параметри. Якщо ви не вкажете оператор, Nette Database Explorer автоматично додасть відповідний оператор: +Тут також можна використовувати складніші вирази: ```php -// WHERE (`field1` IS NULL) OR (`field2` IN (3, 5)) OR (`amount` > 11) +// WHERE (`price` > 1000) OR (`price_with_tax` > 1500) $table->whereOr([ - 'field1' => null, - 'field2' => [3, 5], - 'amount >' => 11, + 'price > ?' => 1000, + 'price_with_tax > ?' => 1500, ]); ``` -Ключ може містити вираз, що містить підстановні знаки питання, а потім передавати параметри в значенні: + +wherePrimary(mixed $key): static .[method] +------------------------------------------ + +Додає умову для первинного ключа таблиці: ```php -// WHERE (`id` > 12) OR (ROUND(`id`, 5) = 3) -$table->whereOr([ - 'id > ?' => 12, - 'ROUND(id, ?) = ?' => [5, 3], -]); +// WHERE `id` = 123 +$table->wherePrimary(123); + +// WHERE `id` IN (1, 2, 3) +$table->wherePrimary([1, 2, 3]); +``` + +Якщо таблиця має складений первинний ключ (наприклад, `foo_id`, `bar_id`), передаємо його як масив: + +```php +// WHERE `foo_id` = 1 AND `bar_id` = 5 +$table->wherePrimary(['foo_id' => 1, 'bar_id' => 5])->fetch(); + +// WHERE (`foo_id`, `bar_id`) IN ((1, 5), (2, 3)) +$table->wherePrimary([ + ['foo_id' => 1, 'bar_id' => 5], + ['foo_id' => 2, 'bar_id' => 3], +])->fetchAll(); ``` -order() .[#toc-order] ---------------------- +order(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- -Приклади використання: +Визначає порядок, у якому будуть повернені рядки. Можна сортувати за одним або кількома стовпцями, у спадному чи зростаючому порядку, або за власним виразом: ```php -$table->order('field1'); // ORDER BY `field1` -$table->order('field1 DESC, field2'); // ORDER BY `field1` DESC, `field2` -$table->order('field = ? DESC', 123); // ORDER BY `field` = 123 DESC +$table->order('created'); // ORDER BY `created` +$table->order('created DESC'); // ORDER BY `created` DESC +$table->order('priority DESC, created'); // ORDER BY `priority` DESC, `created` +$table->order('status = ? DESC', 'active'); // ORDER BY `status` = 'active' DESC ``` -select() .[#toc-select] ------------------------ +select(string $columns, ...$parameters): static .[method] +--------------------------------------------------------- + +Вказує стовпці, які потрібно повернути з бази даних. За замовчуванням Nette Database Explorer повертає лише ті стовпці, які реально використовуються в коді. Метод `select()` ми використовуємо у випадках, коли потрібно повернути специфічні вирази: + +```php +// SELECT *, DATE_FORMAT(`created_at`, "%d.%m.%Y") AS `formatted_date` +$table->select('*, DATE_FORMAT(created_at, ?) AS formatted_date', '%d.%m.%Y'); +``` -Приклади використання: +Аліаси, визначені за допомогою `AS`, потім доступні як властивості об'єкта ActiveRow: ```php -$table->select('field1'); // SELECT `field1` -$table->select('col, UPPER(col) AS abc'); // SELECT `col`, UPPER(`col`) AS abc -$table->select('SUBSTR(title, ?)', 3); // SELECT SUBSTR(`title`, 3) +foreach ($table as $row) { + echo $row->formatted_date; // доступ до аліасу +} ``` -limit() .[#toc-limit] ---------------------- +limit(?int $limit, ?int $offset = null): static .[method] +--------------------------------------------------------- -Приклади використання: +Обмежує кількість повернутих рядків (LIMIT) та опціонально дозволяє встановити зсув (offset): ```php -$table->limit(1); // LIMIT 1 -$table->limit(1, 10); // LIMIT 1 OFFSET 10 +$table->limit(10); // LIMIT 10 (поверне перші 10 рядків) +$table->limit(10, 20); // LIMIT 10 OFFSET 20 ``` +Для пагінації краще використовувати метод `page()`. -page() .[#toc-page] -------------------- -Альтернативний спосіб встановлення межі (limit) і зміщення (offset): +page(int $page, int $itemsPerPage, &$numOfPages = null): static .[method] +------------------------------------------------------------------------- + +Спрощує пагінацію результатів. Приймає номер сторінки (рахується з 1) та кількість елементів на сторінку. Опціонально можна передати посилання на змінну, в яку буде збережено загальну кількість сторінок: ```php -$page = 5; -$itemsPerPage = 10; -$table->page($page, $itemsPerPage); // LIMIT 10 OFFSET 40 +$numOfPages = null; +$table->page(page: 3, itemsPerPage: 10, $numOfPages); +echo "Всього сторінок: $numOfPages"; ``` -Отримання номера останньої сторінки, переданого у змінну `$lastPage`: + +group(string $columns, ...$parameters): static .[method] +-------------------------------------------------------- + +Групує рядки за вказаними стовпцями (GROUP BY). Зазвичай використовується у поєднанні з агрегатними функціями: ```php -$table->page($page, $itemsPerPage, $lastPage); +// Рахує кількість продуктів у кожній категорії +$table->select('category_id, COUNT(*) AS count') + ->group('category_id'); ``` -group() .[#toc-group] ---------------------- +having(string $having, ...$parameters): static .[method] +-------------------------------------------------------- -Приклади використання: +Встановлює умову для фільтрації згрупованих рядків (HAVING). Можна використовувати у поєднанні з методом `group()` та агрегатними функціями: ```php -$table->group('field1'); // GROUP BY `field1` -$table->group('field1, field2'); // GROUP BY `field1`, `field2` +// Знаходить категорії, які мають більше 100 продуктів +$table->select('category_id, COUNT(*) AS count') + ->group('category_id') + ->having('count > ?', 100); ``` -having() .[#toc-having] ------------------------ +Читання даних +============= + +Для читання даних з бази даних у нас є кілька корисних методів: + +.[language-php] +| `foreach ($table as $key => $row)` | Ітерує по всіх рядках, `$key` - значення первинного ключа, `$row` - об'єкт ActiveRow +| `$row = $table->get($key)` | Повертає один рядок за первинним ключем +| `$row = $table->fetch()` | Повертає поточний рядок і переміщує вказівник на наступний +| `$array = $table->fetchPairs()` | Створює асоціативний масив з результатів +| `$array = $table->fetchAll()` | Повертає всі рядки як масив +| `count($table)` | Повертає кількість рядків в об'єкті Selection + +Об'єкт [ActiveRow |api:Nette\Database\Table\ActiveRow] призначений лише для читання. Це означає, що не можна змінювати значення його властивостей. Це обмеження забезпечує консистенцію даних та запобігає неочікуваним побічним ефектам. Дані завантажуються з бази даних, і будь-яка зміна повинна бути виконана явно та контрольовано. -Приклади використання: + +`foreach` - ітерація по всіх рядках +----------------------------------- + +Найпростіший спосіб виконати запит і отримати рядки – це ітерація в циклі `foreach`. Автоматично запускає SQL-запит. ```php -$table->having('COUNT(items) >', 100); // HAVING COUNT(`items`) > 100 +$books = $explorer->table('book'); +foreach ($books as $key => $book) { + // $key - значення первинного ключа, $book - ActiveRow + echo "$book->title ({$book->author->name})"; +} ``` -Фільтрація за іншим значенням таблиці .[#toc-joining-key] ---------------------------------------------------------- +get($key): ?ActiveRow .[method] +------------------------------- -Досить часто потрібно відфільтрувати результати за будь-якою умовою, яка включає іншу таблицю бази даних. Для таких умов потрібні табличні з'єднання. Однак вам більше не потрібно їх писати. +Виконує SQL-запит і повертає рядок за первинним ключем, або `null`, якщо він не існує. -Припустимо, вам потрібно отримати всі книги, ім'я автора яких 'Jon'. Усе, що вам потрібно написати, це з'єднувальний ключ відношення та ім'я стовпця в об'єднаній таблиці. Ключ об'єднання береться зі стовпця, який посилається на таблицю, до якої ви хочете приєднатися. У нашому прикладі (див. схему db) це стовпчик `author_id`, і достатньо використовувати тільки його першу частину - `author` (суфікс `_id` можна опустити). `name` - це стовпець у таблиці `author`, який ми хочемо використовувати. Умова для перекладача книги (яка пов'язана з колонкою `translator_id`) може бути створена так само просто. +```php +$book = $explorer->table('book')->get(123); // поверне ActiveRow з ID 123 або null +if ($book) { + echo $book->title; +} +``` + + +fetch(): ?ActiveRow .[method] +----------------------------- + +Повертає рядок і переміщує внутрішній вказівник на наступний. Якщо більше немає рядків, повертає `null`. ```php $books = $explorer->table('book'); -$books->where('author.name LIKE ?', '%Jon%'); -$books->where('translator.name', 'David Grudl'); +while ($book = $books->fetch()) { + $this->processBook($book); +} ``` -Логіка сполучних ключів визначається реалізацією [Conventions |api:Nette\Database\Conventions]. Ми рекомендуємо використовувати [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions], який аналізує ваші зовнішні ключі та дозволяє легко працювати з цими відносинами. -Відносини між книгою та її автором - 1:N. Зворотні відносини також можливі. Ми називаємо це **зворотним з'єднанням**. Погляньте на інший приклад. Ми хочемо отримати всіх авторів, які написали понад 3 книги. Щоб зробити з'єднання зворотним, ми використовуємо `:` (двоеточие). Двоеточие означает, что объединенное отношение имеет значение hasMany (и это вполне логично, так как две точки больше, чем одна). К сожалению, класс Selection недостаточно умен, поэтому мы должны помочь с агрегацией и предоставить оператор `GROUP BY`, також умова має бути записана у вигляді оператора `HAVING`. +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Повертає результати як асоціативний масив. Перший аргумент визначає назву стовпця, який буде використовуватися як ключ у масиві, другий аргумент визначає назву стовпця, який буде використовуватися як значення: ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book.id) > 3'); +$authors = $explorer->table('author')->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] ``` -Ви, напевно, помітили, що вираз з'єднання відноситься до книги, але незрозуміло, чи об'єднуємо ми через `author_id` або `translator_id`. У наведеному вище прикладі Selection з'єднується через стовпчик `author_id`, тому що знайдено збіг з вихідною таблицею - таблицею `author`. Якби такого збігу не було, і було б більше можливостей, Nette викинув би [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. +Якщо вказати лише перший параметр, значенням буде весь рядок, тобто об'єкт `ActiveRow`: -Щоб виконати об'єднання через колонку `translator_id`, надайте необов'язковий параметр у виразі об'єднання. +```php +$authors = $explorer->table('author')->fetchPairs('id'); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` + +У випадку дублювання ключів використовується значення з останнього рядка. При використанні `null` як ключа, масив буде індексований чисельно з нуля (тоді колізій не виникає): ```php -$authors = $explorer->table('author'); -$authors->group('author.id') - ->having('COUNT(:book(translator).id) > 3'); +$authors = $explorer->table('author')->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] ``` -Давайте розглянемо складніший вираз приєднання. -Ми хочемо знайти всіх авторів, які написали щось про PHP. Усі книжки мають теги, тому ми повинні вибрати тих авторів, які написали будь-яку книжку з тегом PHP. +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Альтернативно, ви можете вказати як параметр callback, який для кожного рядка повертатиме або саме значення, або пару ключ-значення. ```php -$authors = $explorer->table('author'); -$authors->where(':book:book_tags.tag.name', 'PHP') - ->group('author.id') - ->having('COUNT(:book:book_tags.tag.id) > 0'); +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => "$row->title ({$row->author->name})"); +// ['Перша книга (Ян Новак)', ...] + +// Callback може також повертати масив з парою ключ & значення: +$titles = $explorer->table('book') + ->fetchPairs(fn($row) => [$row->title, $row->author->name]); +// ['Перша книга' => 'Ян Новак', ...] ``` -Агреговані запити .[#toc-aggregate-queries] -------------------------------------------- +fetchAll(): array .[method] +--------------------------- -| `$table->count('*')` | Отримуємо кількість рядків -| `$table->count("DISTINCT $column")` | Отримуємо кількість окремих значень -| `$table->min($column)` | Отримуємо мінімальне значення -| `$table->max($column)` | Отримуємо максимальне значення -| `$table->sum($column)` | Отримуємо суму всіх значень -| `$table->aggregation("GROUP_CONCAT($column)")` | Запускаємо будь-яку функцію агрегації +Повертає всі рядки як асоціативний масив об'єктів `ActiveRow`, де ключами є значення первинних ключів. + +```php +$allBooks = $explorer->table('book')->fetchAll(); +// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...] +``` + + +count(): int .[method] +---------------------- + +Метод `count()` без параметра повертає кількість рядків в об'єкті `Selection`: + +```php +$table->where('category', 1); +$count = $table->count(); +$count = count($table); // альтернатива +``` + +Увага, `count()` з параметром виконує агрегатну функцію COUNT у базі даних, див. нижче. + + +ActiveRow::toArray(): array .[method] +------------------------------------- + +Перетворює об'єкт `ActiveRow` на асоціативний масив, де ключами є назви стовпців, а значеннями – відповідні дані. + +```php +$book = $explorer->table('book')->get(1); +$bookArray = $book->toArray(); +// $bookArray буде ['id' => 1, 'title' => '...', 'author_id' => ..., ...] +``` + + +Агрегація +========= + +Клас `Selection` надає методи для легкого виконання агрегатних функцій (COUNT, SUM, MIN, MAX, AVG тощо). + +.[language-php] +| `count($expr)` | Рахує кількість рядків +| `min($expr)` | Повертає мінімальне значення у стовпці +| `max($expr)` | Повертає максимальне значення у стовпці +| `sum($expr)` | Повертає суму значень у стовпці +| `aggregation($function)` | Дозволяє виконати будь-яку агрегатну функцію. Напр. `AVG()`, `GROUP_CONCAT()` + + +count(string $expr): int .[method] +---------------------------------- + +Виконує SQL-запит з функцією COUNT і повертає результат. Метод використовується для визначення, скільки рядків відповідає певній умові: + +```php +$count = $table->count('*'); // SELECT COUNT(*) FROM `table` +$count = $table->count('DISTINCT column'); // SELECT COUNT(DISTINCT `column`) FROM `table` +``` + +Увага, [#count()] без параметра лише повертає кількість рядків в об'єкті `Selection`. + + +min(string $expr) a max(string $expr) .[method] +----------------------------------------------- + +Методи `min()` та `max()` повертають мінімальне та максимальне значення у вказаному стовпці або виразі: + +```php +// SELECT MAX(`price`) FROM `products` WHERE `active` = 1 +$maxPrice = $products->where('active', true) + ->max('price'); +``` -.[caution] -Метод `count()` без зазначення параметрів вибирає всі записи і повертає розмір масиву, що дуже неефективно. Наприклад, якщо вам потрібно підрахувати кількість рядків для пейджингу, завжди вказуйте перший аргумент. +sum(string $expr) .[method] +--------------------------- -Екранування та лапки .[#toc-escaping-quoting] -============================================= +Повертає суму значень у вказаному стовпці або виразі: -Database Explorer розумний і позбудеться параметрів та ідентифікаторів лапок за вас. Проте необхідно дотримуватися таких основних правил: +```php +// SELECT SUM(`price` * `items_in_stock`) FROM `products` WHERE `active` = 1 +$totalPrice = $products->where('active', true) + ->sum('price * items_in_stock'); +``` + + +aggregation(string $function, ?string $groupFunction = null) .[method] +---------------------------------------------------------------------- -- ключові слова, функції, процедури мають бути у верхньому регістрі -- стовпці й таблиці мають бути в нижньому регістрі -- передавайте змінні як параметри, не об'єднуйте їх +Дозволяє виконати будь-яку агрегатну функцію. ```php -->where('name like ?', 'John'); // НЕПРАВИЛЬНО! Генерує: `name` `like` ? -->where('name LIKE ?', 'John'); // ПРАВИЛЬНО +// середня ціна продуктів у категорії +$avgPrice = $products->where('category_id', 1) + ->aggregation('AVG(price)'); -->where('KEY = ?', $value); // НЕПРАВИЛЬНО! КЛЮЧ - це ключове слово -->where('key = ?', $value); // ПРАВИЛЬНО. Генерує: `key` = ? +// об'єднує теги продукту в один рядок +$tags = $products->where('id', 1) + ->aggregation('GROUP_CONCAT(tag.name) AS tags') + ->fetch() + ->tags; +``` -->where('name = ' . $name); // Неправильно! sql-ін'єкція! -->where('name = ?', $name); // ПРАВИЛЬНО +Якщо нам потрібно агрегувати результати, які вже самі по собі виникли з якоїсь агрегатної функції та групування (наприклад, `SUM(значення)` за згрупованими рядками), як другий аргумент вкажемо агрегатну функцію, яка має бути застосована до цих проміжних результатів: -->select('DATE_FORMAT(created, "%d.%m.%Y")'); // НЕПРАВИЛЬНО! Передавайте змінні як параметри, не конкатеніруйте -->select('DATE_FORMAT(created, ?)', '%d.%m.%Y'); // ПРАВИЛЬНО +```php +// Розраховує загальну вартість продуктів на складі для окремих категорій, а потім підсумовує ці ціни разом. +$totalPrice = $products->select('category_id, SUM(price * stock) AS category_total') + ->group('category_id') + ->aggregation('SUM(category_total)', 'SUM'); ``` -.[warning] -Неправильне використання може призвести до утворення дірок у безпеці +У цьому прикладі ми спочатку розраховуємо загальну вартість продуктів у кожній категорії (`SUM(price * stock) AS category_total`) та групуємо результати за `category_id`. Потім використовуємо `aggregation('SUM(category_total)', 'SUM')` для підсумовування цих проміжних сум `category_total`. Другий аргумент `'SUM'` вказує, що до проміжних результатів має бути застосована функція SUM. -Отримання даних .[#toc-fetching-data] -===================================== +Insert, Update & Delete +======================= -| `foreach ($table as $id => $row)` | Ітерація по всіх рядках результату -| `$row = $table->get($id)` | Отримуємо один рядок з ідентифікатором $id з таблиці -| `$row = $table->fetch()` | Отримуємо наступний рядок із результату -| `$array = $table->fetchPairs($key, $value)` | Вибірка всіх значень у вигляді асоціативного масиву -| `$array = $table->fetchPairs($key)` | Вибірка всіх рядків у вигляді асоціативного масиву -| `count($table)` | Отримуємо кількість рядків у результуючому наборі +Nette Database Explorer спрощує вставку, оновлення та видалення даних. Усі наведені методи у випадку помилки викидають виняток `Nette\Database\DriverException`. -Вставка, оновлення та видалення .[#toc-insert-update-delete] -============================================================ +Selection::insert(iterable $data) .[method] +------------------------------------------- + +Вставляє нові записи до таблиці. -Метод `insert()` приймає масив об'єктів Traversable (наприклад, [ArrayHash |utils:arrays#ArrayHash], який повертає [forms |forms:]): +**Вставка одного запису:** + +Новий запис передаємо як асоціативний масив або iterable об'єкт (наприклад, ArrayHash, що використовується у [формах |forms:]), де ключі відповідають назвам стовпців у таблиці. + +Якщо таблиця має визначений первинний ключ, метод повертає об'єкт `ActiveRow`, який перезавантажується з бази даних, щоб врахувати можливі зміни, внесені на рівні бази даних (тригери, значення за замовчуванням стовпців, обчислення auto-increment стовпців). Це забезпечує консистенцію даних, і об'єкт завжди містить актуальні дані з бази даних. Якщо однозначного первинного ключа немає, повертає передані дані у вигляді масиву. ```php $row = $explorer->table('users')->insert([ - 'name' => $name, - 'year' => $year, + 'name' => 'John Doe', + 'email' => 'john.doe@example.com', ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) +// $row є екземпляром ActiveRow і містить повні дані вставленого рядка, +// включно з автоматично згенерованим ID та можливими змінами, внесеними тригерами +echo $row->id; // Виведе ID новоствореного користувача +echo $row->created_at; // Виведе час створення, якщо встановлено тригером ``` -Якщо для таблиці визначено первинний ключ, повертається об'єкт ActiveRow, що містить вставлений рядок. +**Вставка кількох записів одночасно:** -Вставлення кількох значень: +Метод `insert()` дозволяє вставити кілька записів за допомогою одного SQL-запиту. У цьому випадку повертає кількість вставлених рядків. ```php -$explorer->table('users')->insert([ +$insertedRows = $explorer->table('users')->insert([ + [ + 'name' => 'John', + 'year' => 1994, + ], [ - 'name' => 'Jim', - 'year' => 1978, - ], [ 'name' => 'Jack', - 'year' => 1987, + 'year' => 1995, ], ]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) +// INSERT INTO `users` (`name`, `year`) VALUES ('John', 1994), ('Jack', 1995) +// $insertedRows буде 2 +``` + +Як параметр можна також передати об'єкт `Selection` з вибіркою даних. + +```php +$newUsers = $explorer->table('potential_users') + ->where('approved', 1) + ->select('name, email'); + +$insertedRows = $explorer->table('users')->insert($newUsers); ``` -Як параметри можна передавати файли або об'єкти DateTime: +**Вставка спеціальних значень:** + +Як значення ми можемо передавати також файли, об'єкти DateTime або SQL-літерали: ```php $explorer->table('users')->insert([ - 'name' => $name, - 'created' => new DateTime, // или $explorer::literal('NOW()') - 'avatar' => fopen('image.gif', 'r'), // вставляет файл + 'name' => 'John', + 'created_at' => new DateTime, // перетворює на формат бази даних + 'avatar' => fopen('image.jpg', 'rb'), // вставляє бінарний вміст файлу + 'uuid' => $explorer::literal('UUID()'), // викликає функцію UUID() ]); ``` -Оновлення (повертає кількість порушених рядків): + +Selection::update(iterable $data): int .[method] +------------------------------------------------ + +Оновлює рядки в таблиці відповідно до вказаного фільтра. Повертає кількість фактично змінених рядків. + +Змінювані стовпці передаємо як асоціативний масив або iterable об'єкт (наприклад, ArrayHash, що використовується у [формах |forms:]), де ключі відповідають назвам стовпців у таблиці: ```php -$count = $explorer->table('users') - ->where('id', 10) // должен вызываться до update() +$affected = $explorer->table('users') + ->where('id', 10) ->update([ - 'name' => 'Ned Stark' + 'name' => 'John Smith', + 'year' => 1994, ]); -// UPDATE `users` SET `name`='Ned Stark' WHERE (`id` = 10) +// UPDATE `users` SET `name` = 'John Smith', `year` = 1994 WHERE `id` = 10 ``` -Для оновлення ми можемо використовувати оператори `+=` і `-=`: +Для зміни числових значень можна використовувати оператори `+=` та `-=`: ```php $explorer->table('users') + ->where('id', 10) ->update([ - 'age+=' => 1, // see += + 'points+=' => 1, // збільшить значення стовпця 'points' на 1 + 'coins-=' => 1, // зменшить значення стовпця 'coins' на 1 ]); -// UPDATE users SET `age` = `age` + 1 +// UPDATE `users` SET `points` = `points` + 1, `coins` = `coins` - 1 WHERE `id` = 10 ``` -Видалення (повертає кількість видалених рядків): + +Selection::delete(): int .[method] +---------------------------------- + +Видаляє рядки з таблиці відповідно до вказаного фільтра. Повертає кількість видалених рядків. ```php $count = $explorer->table('users') ->where('id', 10) ->delete(); -// DELETE FROM `users` WHERE (`id` = 10) +// DELETE FROM `users` WHERE `id` = 10 ``` +.[caution] +При виклику `update()` та `delete()` не забудьте за допомогою `where()` вказати рядки, які потрібно змінити/видалити. Якщо `where()` не використовувати, операція буде виконана над усією таблицею! + -Робота з відносинами .[#toc-working-with-relationships] -======================================================= +ActiveRow::update(iterable $data): bool .[method] +------------------------------------------------- +Оновлює дані в рядку бази даних, представленому об'єктом `ActiveRow`. Як параметр приймає iterable з даними, які потрібно оновити (ключі - назви стовпців). Для зміни числових значень можна використовувати оператори `+=` та `-=`: -Один до одного ("has one") .[#toc-has-one-relation] ---------------------------------------------------- -Відношення "Один до одного" - поширений випадок використання. Книга *має одного* автора. Книга *має одного* перекладача. Отримання зв'язаного рядка в основному здійснюється методом `ref()`. Він приймає два аргументи: ім'я цільової таблиці та стовпець вихідного з'єднання. Див. приклад: +Після виконання оновлення `ActiveRow` автоматично перезавантажується з бази даних, щоб врахувати можливі зміни, внесені на рівні бази даних (наприклад, тригери). Метод повертає true лише якщо відбулася фактична зміна даних. ```php -$book = $explorer->table('book')->get(1); -$book->ref('author', 'author_id'); +$article = $explorer->table('article')->get(1); +$article->update([ + 'views += 1', // збільшимо кількість переглядів +]); +echo $article->views; // Виведе поточну кількість переглядів ``` -У наведеному вище прикладі ми витягуємо пов'язаний запис про автора з таблиці `author`, пошук первинного ключа автора здійснюється за стовпцем `book.author_id`. Метод Ref() повертає екземпляр ActiveRow або null, якщо немає відповідного запису. Повернутий рядок є екземпляром ActiveRow, тому ми можемо працювати з ним так само, як і з записом книги. +Цей метод оновлює лише один конкретний рядок у базі даних. Для масового оновлення кількох рядків використовуйте метод [#Selection::update()]. + + +ActiveRow::delete() .[method] +----------------------------- + +Видаляє рядок з бази даних, який представлений об'єктом `ActiveRow`. ```php -$author = $book->ref('author', 'author_id'); -$author->name; -$author->born; +$book = $explorer->table('book')->get(1); +$book->delete(); // Видалить книгу з ID 1 +``` + +Цей метод видаляє лише один конкретний рядок у базі даних. Для масового видалення кількох рядків використовуйте метод [#Selection::delete()]. + + +Зв'язки між таблицями +===================== + +У реляційних базах даних дані розділені на кілька таблиць і взаємопов'язані за допомогою зовнішніх ключів. Nette Database Explorer пропонує революційний спосіб роботи з цими зв'язками - без написання JOIN-запитів та необхідності щось конфігурувати чи генерувати. + +Для ілюстрації роботи зі зв'язками використаємо приклад бази даних книг ([знайдете його на GitHub |https://github.com/nette-examples/books]). У базі даних маємо таблиці: + +- `author` - письменники та перекладачі (стовпці `id`, `name`, `web`, `born`) +- `book` - книги (стовпці `id`, `author_id`, `translator_id`, `title`, `sequel_id`) +- `tag` - теги (стовпці `id`, `name`) +- `book_tag` - таблиця зв'язку між книгами та тегами (стовпці `book_id`, `tag_id`) + +[* db-schema-1-.webp *] *** Структура бази даних, що використовується в прикладах *** + +У нашому прикладі бази даних книг знайдемо кілька типів зв'язків (хоча модель спрощена порівняно з реальністю): + +- One-to-many 1:N – кожна книга **має одного** автора, автор може написати **кілька** книг +- Zero-to-many 0:N – книга **може мати** перекладача, перекладач може перекласти **кілька** книг +- Zero-to-one 0:1 – книга **може мати** наступну частину +- Many-to-many M:N – книга **може мати кілька** тегів, а тег може бути присвоєний **кільком** книгам -// або напряму -$book->ref('author', 'author_id')->name; -$book->ref('author', 'author_id')->born; +У цих зв'язках завжди існує батьківська та дочірня таблиця. Наприклад, у зв'язку між автором та книгою таблиця `author` є батьківською, а `book` - дочірньою. Ми можемо уявити це так, що книга завжди "належить" якомусь автору. Це проявляється і в структурі бази даних: дочірня таблиця `book` містить зовнішній ключ `author_id`, який посилається на батьківську таблицю `author`. + +Якщо нам потрібно вивести книги разом з іменами їхніх авторів, у нас є два варіанти. Або отримати дані одним SQL-запитом за допомогою JOIN: + +```sql +SELECT book.*, author.name FROM book LEFT JOIN author ON book.author_id = author.id +``` + +Або завантажити дані у два кроки - спочатку книги, а потім їхніх авторів - і потім зібрати їх у PHP: + +```sql +SELECT * FROM book; +SELECT * FROM author WHERE id IN (1, 2, 3); -- id авторів отриманих книг ``` -Книга також має одного перекладача, тому дізнатися ім'я перекладача досить просто. +Другий підхід насправді ефективніший, хоча це може здатися дивним. Дані завантажуються лише один раз і можуть бути краще використані в кеші. Саме таким чином працює Nette Database Explorer - все вирішує під капотом і пропонує вам елегантний API: + ```php -$book->ref('author', 'translator_id')->name +$books = $explorer->table('book'); +foreach ($books as $book) { + echo 'title: ' . $book->title; + echo 'written by: ' . $book->author->name; // $book->author - це запис з таблиці 'author' + echo 'translated by: ' . $book->translator?->name; +} ``` -Усе це добре, але дещо громіздко, чи не так? Database Explorer уже містить визначення зовнішніх ключів, то чому б не використовувати їх автоматично? Давайте зробимо це! -Якщо ми викликаємо властивість, якої не існує, ActiveRow намагається дозволити ім'я властивості, що викликає, як відношення 'has one'. Отримання цієї властивості аналогічно виклику методу ref() тільки з одним аргументом. Ми будемо називати єдиний аргумент **key**. Ключ буде дозволено в конкретне відношення зовнішнього ключа. Переданий ключ зіставляється зі стовпчиками рядка, і якщо він збігається, то зовнішній ключ, визначений у зіставленому стовпчику, використовується для отримання даних із пов'язаної цільової таблиці. Див. приклад: +Доступ до батьківської таблиці +------------------------------ + +Доступ до батьківської таблиці є прямолінійним. Йдеться про зв'язки типу *книга має автора* або *книга може мати перекладача*. Пов'язаний запис отримуємо через властивість об'єкта ActiveRow - її назва відповідає назві стовпця із зовнішнім ключем без `id`: ```php -$book->author->name; -// те ж саме -$book->ref('author')->name; +$book = $explorer->table('book')->get(1); +echo $book->author->name; // знайде автора за стовпцем author_id +echo $book->translator?->name; // знайде перекладача за translator_id ``` -Примірник ActiveRow не має колонки автора. Усі стовпці книги шукаються на предмет збігу з *key*. Збіг у цьому випадку означає, що ім'я стовпця має містити ключ. Так, у наведеному прикладі стовпець `author_id` містить рядок 'author' і тому зіставляється з ключем 'author'. Якщо ви хочете отримати перекладача книги, то як ключ можна використовувати, наприклад, 'translator', оскільки ключ 'translator' відповідатиме стовпчику `translator_id`. Докладніше про логіку підбору ключів ви можете прочитати в розділі [Joining expressions |#joining-key]. +Коли ми звертаємося до властивості `$book->author`, Explorer у таблиці `book` шукає стовпець, назва якого містить рядок `author` (тобто `author_id`). За значенням у цьому стовпці він завантажує відповідний запис з таблиці `author` і повертає його як `ActiveRow`. Подібно працює і `$book->translator`, який використовує стовпець `translator_id`. Оскільки стовпець `translator_id` може містити `null`, ми використовуємо в коді оператор `?->`. + +Альтернативний шлях пропонує метод `ref()`, який приймає два аргументи: назву цільової таблиці та назву сполучного стовпця, і повертає екземпляр `ActiveRow` або `null`: ```php -echo $book->title . ': '; -echo $book->author->name; -if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; -} +echo $book->ref('author', 'author_id')->name; // зв'язок з автором +echo $book->ref('author', 'translator_id')->name; // зв'язок з перекладачем ``` -Якщо ви хочете отримати кілька книг, використовуйте той самий підхід. Nette Database Explorer знайде авторів і перекладачів одразу для всіх знайдених книг. +Метод `ref()` корисний, якщо не можна використати доступ через властивість, оскільки таблиця містить стовпець з такою ж назвою (тобто `author`). В інших випадках рекомендується використовувати доступ через властивість, який є більш читабельним. + +Explorer автоматично оптимізує запити до бази даних. Коли ми проходимо книги в циклі та звертаємося до їхніх пов'язаних записів (авторів, перекладачів), Explorer не генерує запит для кожної книги окремо. Замість цього він виконує лише один SELECT для кожного типу зв'язку, тим самим значно знижуючи навантаження на базу даних. Наприклад: ```php $books = $explorer->table('book'); foreach ($books as $book) { echo $book->title . ': '; echo $book->author->name; - if ($book->translator) { - echo ' (translated by ' . $book->translator->name . ')'; - } + echo $book->translator?->name; } ``` -Код виконуватиме лише ці 3 запити: +Цей код викличе лише ці три блискавичні запити до бази даних: + ```sql SELECT * FROM `book`; -SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- ids of fetched books from author_id column -SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- ids of fetched books from translator_id column +SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- id зі стовпця author_id вибраних книг +SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- id зі стовпця translator_id вибраних книг ``` +.[note] +Логіка пошуку сполучного стовпця визначається реалізацією [Conventions |api:Nette\Database\Conventions]. Рекомендуємо використовувати [DiscoveredConventions |api:Nette\Database\Conventions\DiscoveredConventions], які аналізують зовнішні ключі та дозволяють легко працювати з існуючими зв'язками між таблицями. -Один до багатьох ("has many") .[#toc-has-many-relation] -------------------------------------------------------- -Відношення "один до багатьох" - це просто зворотне відношення "один до одного". Автор *написав* *багато* книг. Автор *переклав* *багато* книг. Як бачите, цей тип відношення трохи складніший, тому що відношення є "іменованим" ("написав", "переклав"). У екземпляра ActiveRow є метод `related()`, який повертає масив пов'язаних записів. Записи також є екземплярами ActiveRow. Див. приклад нижче: +Доступ до дочірньої таблиці +--------------------------- + +Доступ до дочірньої таблиці працює у зворотному напрямку. Тепер ми запитуємо *які книги написав цей автор* або *переклав цей перекладач*. Для цього типу запиту ми використовуємо метод `related()`, який повертає `Selection` з пов'язаними записами. Розглянемо приклад: ```php -$author = $explorer->table('author')->get(11); -echo $author->name . ' написал:'; +$author = $explorer->table('author')->get(1); +// Виведе всі книги автора foreach ($author->related('book.author_id') as $book) { - echo $book->title; + echo "Написав: $book->title"; } -echo 'и перевёл:'; +// Виведе всі книги, які автор переклав foreach ($author->related('book.translator_id') as $book) { - echo $book->title; + echo "Переклав: $book->title"; } ``` -Метод `related()` приймає повний опис з'єднання, що передається як два аргументи або як один аргумент, з'єднаний крапкою. Перший аргумент - цільова таблиця, другий - цільовий стовпець. +Метод `related()` приймає опис з'єднання як один аргумент з точковою нотацією або як два окремі аргументи: ```php -$author->related('book.translator_id'); -// те саме -$author->related('book', 'translator_id'); +$author->related('book.translator_id'); // один аргумент +$author->related('book', 'translator_id'); // два аргументи ``` -Ви можете використовувати евристику Nette Database Explorer, засновану на зовнішніх ключах, і вказати тільки аргумент **key**. Ключ буде зіставлено з усіма зовнішніми ключами, що вказують на поточну таблицю (таблиця `author`). Якщо є збіг, Nette Database Explorer буде використовувати цей зовнішній ключ, в іншому випадку він викине [Nette\InvalidArgumentException |api:Nette\InvalidArgumentException] або [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. Детальніше про логіку підбору ключів ви можете прочитати в розділі [Joining expressions |#joining-key]. +Explorer може автоматично визначити правильний сполучний стовпець на основі назви батьківської таблиці. У цьому випадку з'єднання відбувається через стовпець `book.author_id`, оскільки назва вихідної таблиці - `author`: -Звичайно, ви можете викликати пов'язані методи для всіх знайдених авторів, і Nette Database Explorer знову отримає відповідні книги одразу. +```php +$author->related('book'); // використовує book.author_id +``` + +Якщо існує кілька можливих з'єднань, Explorer викине виняток [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. + +Метод `related()` можна, звичайно, використовувати і при проходженні кількох записів у циклі, і Explorer і в цьому випадку автоматично оптимізує запити: ```php $authors = $explorer->table('author'); foreach ($authors as $author) { - echo $author->name . ' написал:'; + echo $author->name . ' написав:'; foreach ($author->related('book') as $book) { - $book->title; + echo $book->title; } } ``` -У наведеному вище прикладі буде виконано лише два запити: +Цей код згенерує лише два блискавичні SQL-запити: ```sql SELECT * FROM `author`; -SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- идентификаторы найденных авторов +SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- id вибраних авторів +``` + + +Зв'язок Many-to-many +-------------------- + +Для зв'язку many-to-many (M:N) необхідна наявність таблиці зв'язку (у нашому випадку `book_tag`), яка містить два стовпці із зовнішніми ключами (`book_id`, `tag_id`). Кожен з цих стовпців посилається на первинний ключ однієї з пов'язуваних таблиць. Для отримання пов'язаних даних спочатку отримуємо записи з таблиці зв'язку за допомогою `related('book_tag')`, а далі переходимо до цільових даних: + +```php +$book = $explorer->table('book')->get(1); +// виведе назви тегів, присвоєних книзі +foreach ($book->related('book_tag') as $bookTag) { + echo $bookTag->tag->name; // виведе назву тегу через таблицю зв'язку +} + +$tag = $explorer->table('tag')->get(1); +// або навпаки: виведе назви книг, позначених цим тегом +foreach ($tag->related('book_tag') as $bookTag) { + echo $bookTag->book->title; // виведе назву книги +} ``` +Explorer знову оптимізує SQL-запити до ефективної форми: + +```sql +SELECT * FROM `book`; +SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 2, ...)); -- id вибраних книг +SELECT * FROM `tag` WHERE (`tag`.`id` IN (1, 2, ...)); -- id тегів, знайдених у book_tag +``` -Створення Explorer вручну .[#toc-creating-explorer-manually] -============================================================ -З'єднання з базою даних може бути створено за допомогою конфігурації програми. У таких випадках створюється служба `Nette\Database\Explorer`, яка може бути передана як залежність за допомогою DI-контейнера. +Запити через пов'язані таблиці +------------------------------ -Однак, якщо Nette Database Explorer використовується як самостійний інструмент, екземпляр об'єкта `Nette\Database\Explorer` повинен бути створений вручну. +У методах `where()`, `select()`, `order()` та `group()` ми можемо використовувати спеціальні нотації для доступу до стовпців з інших таблиць. Explorer автоматично створить необхідні JOIN-и. + +**Точкова нотація** (`батьківська_таблиця.стовпець`) використовується для зв'язку 1:N з точки зору дочірньої таблиці: ```php -// $storage реалізує Nette\Caching\Storage: -$storage = new Nette\Caching\Storages\FileStorage($tempDir); -$connection = new Nette\Database\Connection($dsn, $user, $password); -$structure = new Nette\Database\Structure($connection, $storage); -$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); -$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +$books = $explorer->table('book'); + +// Знаходить книги, автор яких має ім'я, що починається на 'Jon' +$books->where('author.name LIKE ?', 'Jon%'); + +// Сортує книги за іменем автора за спаданням +$books->order('author.name DESC'); + +// Виводить назву книги та ім'я автора +$books->select('book.title, author.name'); ``` + +**Двокрапкова нотація** (`:дочірня_таблиця.стовпець`) використовується для зв'язку 1:N з точки зору батьківської таблиці: + +```php +$authors = $explorer->table('author'); + +// Знаходить авторів, які написали книгу з 'PHP' у назві +$authors->where(':book.title LIKE ?', '%PHP%'); + +// Рахує кількість книг для кожного автора +$authors->select('*, COUNT(:book.id) AS book_count') + ->group('author.id'); +``` + +У вищезгаданому прикладі з двокрапковою нотацією (`:book.title`) не вказано стовпець із зовнішнім ключем. Explorer автоматично визначає правильний стовпець на основі назви батьківської таблиці. У цьому випадку з'єднання відбувається через стовпець `book.author_id`, оскільки назва вихідної таблиці - `author`. Якщо існує кілька можливих з'єднань, Explorer викине виняток [AmbiguousReferenceKeyException |api:Nette\Database\Conventions\AmbiguousReferenceKeyException]. + +Сполучний стовпець можна явно вказати в дужках: + +```php +// Знаходить авторів, які переклали книгу з 'PHP' у назві +$authors->where(':book(translator_id).title LIKE ?', '%PHP%'); +``` + +Нотації можна ланцюжком для доступу через кілька таблиць: + +```php +// Знаходить авторів книг, позначених тегом 'PHP' +$authors->where(':book:book_tag.tag.name', 'PHP') + ->group('author.id'); +``` + + +Розширення умов для JOIN +------------------------ + +Метод `joinWhere()` розширює умови, які вказуються при з'єднанні таблиць у SQL за ключовим словом `ON`. + +Припустимо, ми хочемо знайти книги, перекладені конкретним перекладачем: + +```php +// Знаходить книги, перекладені перекладачем на ім'я 'David' +$books = $explorer->table('book') + ->joinWhere('translator', 'translator.name', 'David'); +// LEFT JOIN author translator ON book.translator_id = translator.id AND (translator.name = 'David') +``` + +В умові `joinWhere()` ми можемо використовувати ті ж конструкції, що й у методі `where()` - оператори, знаки питання, масиви значень або SQL-вирази. + +Для складніших запитів з кількома JOIN-ами ми можемо визначити аліаси таблиць: + +```php +$tags = $explorer->table('tag') + ->joinWhere(':book_tag.book.author', 'book_author.born < ?', 1950) + ->alias(':book_tag.book.author', 'book_author'); +// LEFT JOIN `book_tag` ON `tag`.`id` = `book_tag`.`tag_id` +// LEFT JOIN `book` ON `book_tag`.`book_id` = `book`.`id` +// LEFT JOIN `author` `book_author` ON `book`.`author_id` = `book_author`.`id` +// AND (`book_author`.`born` < 1950) +``` + +Зверніть увагу, що тоді як метод `where()` додає умови до клаузули `WHERE`, метод `joinWhere()` розширює умови в клаузулі `ON` при з'єднанні таблиць. diff --git a/database/uk/guide.texy b/database/uk/guide.texy new file mode 100644 index 0000000000..e192adf548 --- /dev/null +++ b/database/uk/guide.texy @@ -0,0 +1,216 @@ +Nette Database +************** + +.[perex] +Nette Database — це потужний та елегантний шар бази даних для PHP з акцентом на простоту та розумні функції. Він пропонує два способи роботи з базою даних — [Explorer |Explorer] для швидкої розробки додатків або [SQL підхід |SQL way] для прямої роботи з запитами. + +<div class="grid gap-3"> +<div> + + +[SQL підхід |SQL way] +===================== +- Безпечні параметризовані запити +- Точний контроль над формою SQL-запитів +- Коли ви пишете складні запити з розширеними функціями +- Оптимізуєте продуктивність за допомогою специфічних функцій SQL + +</div> + +<div> + + +[Explorer |Explorer] +==================== +- Розробляєте швидко, не пишучи SQL +- Інтуїтивна робота з відношеннями між таблицями +- Оціните автоматичну оптимізацію запитів +- Підходить для швидкої та зручної роботи з базою даних + +</div> + +</div> + + +Встановлення +============ + +Завантажте та встановіть бібліотеку за допомогою інструмента [Composer|best-practices:composer]: + +```shell +composer require nette/database +``` + + +Підтримувані бази даних +======================= + +Nette Database підтримує наступні бази даних: + +|* Сервер бази даних |* Ім'я DSN |* Підтримка в Explorer +|---------------------|-------------|----------------------- +| MySQL (>= 5.1) | mysql | ТАК +| PostgreSQL (>= 9.0) | pgsql | ТАК +| Sqlite 3 (>= 3.8) | sqlite | ТАК +| Oracle | oci | - +| MS SQL (PDO_SQLSRV) | sqlsrv | ТАК +| MS SQL (PDO_DBLIB) | mssql | - +| ODBC | odbc | - + + +Два підходи до бази даних +========================= + +Nette Database надає вам вибір: ви можете або писати SQL-запити безпосередньо (SQL підхід), або дозволити генерувати їх автоматично (Explorer). Давайте подивимося, як обидва підходи вирішують однакові завдання: + +[SQL підхід|sql way] - SQL-запити + +```php +// вставка запису +$database->query('INSERT INTO books', [ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// отримання записів: автори книг +$result = $database->query(' + SELECT authors.*, COUNT(books.id) AS books_count + FROM authors + LEFT JOIN books ON authors.id = books.author_id + WHERE authors.active = 1 + GROUP BY authors.id +'); + +// виведення (не оптимально, генерує N додаткових запитів) +foreach ($result as $author) { + $books = $database->query(' + SELECT * FROM books + WHERE author_id = ? + ORDER BY published_at DESC + ', $author->id); + + echo "Автор $author->name написав $author->books_count книг:\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +[Explorer підхід|explorer] - автоматичне генерування SQL + +```php +// вставка запису +$database->table('books')->insert([ + 'author_id' => $authorId, + 'title' => $bookData->title, + 'published_at' => new DateTime, +]); + +// отримання записів: автори книг +$authors = $database->table('authors') + ->where('active', 1); + +// виведення (автоматично генерує лише 2 оптимізовані запити) +foreach ($authors as $author) { + $books = $author->related('books') + ->order('published_at DESC'); + + echo "Автор $author->name написав {$books->count()} книг:\n"; + + foreach ($books as $book) { + echo "- $book->title\n"; + } +} +``` + +Підхід Explorer генерує та оптимізує SQL-запити автоматично. У наведеному прикладі SQL підхід генерує N+1 запитів (один для авторів, а потім один для книг кожного автора), тоді як Explorer автоматично оптимізує запити та виконує лише два - один для авторів та один для всіх їхніх книг. + +Обидва підходи можна вільно комбінувати в додатку за потреби. + + +Підключення та конфігурація +=========================== + +Для підключення до бази даних достатньо створити екземпляр класу [api:Nette\Database\Connection]: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password); +``` + +Параметр `$dsn` (data source name) такий самий, [який використовує PDO |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], наприклад `mysql:host=127.0.0.1;dbname=test`. У разі збою викидається виняток `Nette\Database\ConnectionException`. + +Однак, зручніший спосіб пропонує [конфігурація програми |configuration], куди достатньо додати секцію `database`, і будуть створені необхідні об'єкти, а також панель бази даних у [Tracy |tracy:] барі. + +```neon +database: + dsn: 'mysql:host=127.0.0.1;dbname=test' + user: root + password: password +``` + +Потім об'єкт з'єднання [отримаємо як сервіс з DI-контейнера |dependency-injection:passing-dependencies], наприклад: + +```php +class Model +{ + public function __construct( + // або Nette\Database\Explorer + private Nette\Database\Connection $database, + ) { + } +} +``` + +Більше інформації про [конфігурацію бази даних|configuration]. + + +Ручне створення Explorer +------------------------ + +Якщо ви не використовуєте Nette DI-контейнер, ви можете створити екземпляр `Nette\Database\Explorer` вручну: + +```php +// підключення до бази даних +$connection = new Nette\Database\Connection('mysql:host=127.0.0.1;dbname=mydatabase', 'user', 'password'); +// сховище для кешу, реалізує Nette\Caching\Storage, наприклад: +$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp/dir'); +// відповідає за рефлексію структури бази даних +$structure = new Nette\Database\Structure($connection, $storage); +// визначає правила для відображення назв таблиць, стовпців та зовнішніх ключів +$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure); +$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage); +``` + + +Управління підключенням +======================= + +При створенні об'єкта `Connection` підключення відбувається автоматично. Якщо ви хочете відкласти підключення, використовуйте режим lazy - його можна увімкнути в [конфігурації|configuration], встановивши `lazy: true`, або так: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password, ['lazy' => true]); +``` + +Для управління підключенням використовуйте методи `connect()`, `disconnect()` та `reconnect()`. +- `connect()` створює підключення, якщо його ще немає, при цьому може викликати виняток `Nette\Database\ConnectionException`. +- `disconnect()` відключає поточне підключення до бази даних. +- `reconnect()` виконує відключення та подальше повторне підключення до бази даних. Цей метод також може викликати виняток `Nette\Database\ConnectionException`. + +Крім того, ви можете відстежувати події, пов'язані з підключенням, за допомогою події `onConnect`, яка є масивом колбеків, що викликаються після встановлення з'єднання з базою даних. + +```php +// виконується після підключення до бази даних +$database->onConnect[] = function($database) { + echo "Підключено до бази даних"; +}; +``` + + +Tracy Debug Bar +=============== + +Якщо ви використовуєте [Tracy |tracy:], автоматично активується панель Database в Debug барі, яка відображає всі виконані запити, їхні параметри, час виконання та місце в коді, де вони були викликані. + +[* db-panel.webp *] diff --git a/database/uk/mapping.texy b/database/uk/mapping.texy new file mode 100644 index 0000000000..c8bcebb200 --- /dev/null +++ b/database/uk/mapping.texy @@ -0,0 +1,55 @@ +Перетворення типів +****************** + +.[perex] +Nette Database автоматично перетворює значення, повернуті з бази даних, на відповідні типи PHP. + + +Дата та час +----------- + +Часові дані перетворюються на об'єкти `Nette\Utils\DateTime`. Якщо ви хочете, щоб часові дані перетворювалися на незмінні об'єкти `Nette\Database\DateTime`, встановіть у [конфігурації|configuration] опцію `newDateTime: true`. + +```php +$row = $database->fetch('SELECT created_at FROM articles'); +echo $row->created_at instanceof DateTime; // true +echo $row->created_at->format('j. n. Y'); +``` + +У випадку MySQL перетворює тип даних `TIME` на об'єкти `DateInterval`. + + +Булеві значення +--------------- + +Булеві значення автоматично перетворюються на `true` або `false`. У MySQL перетворюється `TINYINT(1)`, якщо ми встановимо в [конфігурації|configuration] `convertBoolean: true`. + +```php +$row = $database->fetch('SELECT is_published FROM articles'); +echo gettype($row->is_published); // 'boolean' +``` + + +Числові значення +---------------- + +Числові значення перетворюються на `int` або `float` відповідно до типу стовпця в базі даних: + +```php +$row = $database->fetch('SELECT id, price FROM products'); +echo gettype($row->id); // integer +echo gettype($row->price); // float +``` + + +Власна нормалізація +------------------- + +За допомогою методу `setRowNormalizer(?callable $normalizer)` ви можете встановити власну функцію для трансформації рядків з бази даних. Це корисно, наприклад, для автоматичного перетворення типів даних. + +```php +$database->setRowNormalizer(function(array $row, ResultSet $resultSet): array { + // тут відбувається перетворення типів + return $row; +}); +``` diff --git a/database/uk/reflection.texy b/database/uk/reflection.texy new file mode 100644 index 0000000000..703664abb8 --- /dev/null +++ b/database/uk/reflection.texy @@ -0,0 +1,125 @@ +Рефлексія структури +******************* + +.{data-version:3.2.1} +Nette Database надає інструменти для інтроспекції структури бази даних за допомогою класу [api:Nette\Database\Reflection]. Вона дозволяє отримувати інформацію про таблиці, стовпці, індекси та зовнішні ключі. Рефлексію можна використовувати для генерації схем, створення гнучких додатків, що працюють з базою даних, або загальних інструментів для роботи з базами даних. + +Об'єкт рефлексії отримуємо з екземпляра підключення до бази даних: + +```php +$reflection = $database->getReflection(); +``` + + +Отримання таблиць +----------------- + +Readonly властивість `$reflection->tables` містить асоціативний масив усіх таблиць у базі даних: + +```php +// Виведення назв усіх таблиць +foreach ($reflection->tables as $name => $table) { + echo $name . "\n"; +} +``` + +Доступні ще два методи: + +```php +// Перевірка існування таблиці +if ($reflection->hasTable('users')) { + echo "Таблиця users існує"; +} + +// Повертає об'єкт таблиці; якщо не існує, викидає виняток +$table = $reflection->getTable('users'); +``` + + +Інформація про таблицю +---------------------- + +Таблиця представлена об'єктом [Table|api:Nette\Database\Reflection\Table], який надає наступні readonly властивості: + +- `$name: string` – назва таблиці +- `$view: bool` – чи є це представленням (view) +- `$fullName: ?string` – повна назва таблиці, включаючи схему (якщо існує) +- `$columns: array<string, Column>` – асоціативний масив стовпців таблиці +- `$indexes: Index[]` – масив індексів таблиці +- `$primaryKey: ?Index` – первинний ключ таблиці або null +- `$foreignKeys: ForeignKey[]` – масив зовнішніх ключів таблиці + + +Стовпці +------- + +Властивість `columns` таблиці надає асоціативний масив стовпців, де ключем є назва стовпця, а значенням - екземпляр [Column|api:Nette\Database\Reflection\Column] з такими властивостями: + +- `$name: string` – назва стовпця +- `$table: ?Table` – посилання на таблицю стовпця +- `$nativeType: string` – нативний тип даних бази даних +- `$size: ?int` – розмір/довжина типу +- `$nullable: bool` – чи може стовпець містити NULL +- `$default: mixed` – значення за замовчуванням стовпця +- `$autoIncrement: bool` – чи є стовпець автоінкрементним +- `$primary: bool` – чи є частиною первинного ключа +- `$vendor: array` – додаткові метадані, специфічні для даної системи бази даних + +```php +foreach ($table->columns as $name => $column) { + echo "Стовпець: $name\n"; + echo "Тип: {$column->nativeType}\n"; + echo "Nullable: " . ($column->nullable ? 'Так' : 'Ні') . "\n"; +} +``` + + +Індекси +------- + +Властивість `indexes` таблиці надає масив індексів, де кожен індекс є екземпляром [Index|api:Nette\Database\Reflection\Index] з такими властивостями: + +- `$columns: Column[]` – масив стовпців, що утворюють індекс +- `$unique: bool` – чи є індекс унікальним +- `$primary: bool` – чи є це первинним ключем +- `$name: ?string` – назва індексу + +Первинний ключ таблиці можна отримати за допомогою властивості `primaryKey`, яка повертає або об'єкт `Index`, або `null` у випадку, якщо таблиця не має первинного ключа. + +```php +// Виведення індексів +foreach ($table->indexes as $index) { + $columns = implode(', ', array_map(fn($col) => $col->name, $index->columns)); + echo "Індекс" . ($index->name ? " {$index->name}" : '') . ":\n"; + echo " Стовпці: $columns\n"; + echo " Unique: " . ($index->unique ? 'Так' : 'Ні') . "\n"; +} + +// Виведення первинного ключа +if ($primaryKey = $table->primaryKey) { + $columns = implode(', ', array_map(fn($col) => $col->name, $primaryKey->columns)); + echo "Первинний ключ: $columns\n"; +} +``` + + +Зовнішні ключі +-------------- + +Властивість `foreignKeys` таблиці надає масив зовнішніх ключів, де кожен зовнішній ключ є екземпляром [ForeignKey|api:Nette\Database\Reflection\ForeignKey] з такими властивостями: + +- `$foreignTable: Table` – таблиця, на яку посилається ключ +- `$localColumns: Column[]` – масив локальних стовпців +- `$foreignColumns: Column[]` – масив стовпців, на які посилається ключ +- `$name: ?string` – назва зовнішнього ключа + +```php +// Виведення зовнішніх ключів +foreach ($table->foreignKeys as $fk) { + $localCols = implode(', ', array_map(fn($col) => $col->name, $fk->localColumns)); + $foreignCols = implode(', ', array_map(fn($col) => $col->name, $fk->foreignColumns)); + + echo "FK" . ($fk->name ? " {$fk->name}" : '') . ":\n"; + echo " $localCols -> {$fk->foreignTable->name}($foreignCols)\n"; +} +``` diff --git a/database/uk/security.texy b/database/uk/security.texy new file mode 100644 index 0000000000..cc04ec9783 --- /dev/null +++ b/database/uk/security.texy @@ -0,0 +1,185 @@ +Ризики безпеки +************** + +<div class=perex> + +База даних часто містить конфіденційні дані та дозволяє виконувати небезпечні операції. Для безпечної роботи з Nette Database ключовим є: + +- Розуміти різницю між безпечним та небезпечним API +- Використовувати параметризовані запити +- Правильно валідувати вхідні дані + +</div> + + +Що таке SQL Injection? +====================== + +SQL injection є найсерйознішим ризиком безпеки при роботі з базою даних. Він виникає, коли необроблені вхідні дані від користувача стають частиною SQL-запиту. Зловмисник може вставити власні SQL-команди і таким чином: +- Отримати несанкціонований доступ до даних +- Змінити або видалити дані в базі даних +- Обійти автентифікацію + +```php +// ❌ НЕБЕЗПЕЧНИЙ КОД - вразливий до SQL-ін'єкції +$database->query("SELECT * FROM users WHERE name = '$_GET[name]'"); + +// Зловмисник може ввести, наприклад, значення: ' OR '1'='1 +// Кінцевий запит буде: SELECT * FROM users WHERE name = '' OR '1'='1' +// Що поверне всіх користувачів +``` + +Те саме стосується і Database Explorer: + +```php +// ❌ НЕБЕЗПЕЧНИЙ КОД - вразливий до SQL-ін'єкції +$table->where('name = ' . $_GET['name']); +$table->where("name = '$_GET[name]'"); +``` + + +Параметризовані запити +====================== + +Основний захист від SQL injection - це параметризовані запити. Nette Database пропонує кілька способів їх використання. + +Найпростіший спосіб - використання **заповнювачів-знаків питання**: + +```php +// ✅ Безпечний параметризований запит +$database->query('SELECT * FROM users WHERE name = ?', $name); + +// ✅ Безпечна умова в Explorer +$table->where('name = ?', $name); +``` + +Це стосується всіх інших методів у [Database Explorer|explorer], які дозволяють вставляти вирази з заповнювачами-знаками питання та параметрами. + +Для команд INSERT, UPDATE або умови WHERE ми можемо передати значення в масиві: + +```php +// ✅ Безпечний INSERT +$database->query('INSERT INTO users', [ + 'name' => $name, + 'email' => $email, +]); + +// ✅ Безпечний INSERT в Explorer +$table->insert([ + 'name' => $name, + 'email' => $email, +]); +``` + + +Валідація значень параметрів +============================ + +Параметризовані запити є основним будівельним блоком безпечної роботи з базою даних. Однак значення, які ми в них вставляємо, повинні пройти кілька рівнів перевірок: + + +Перевірка типу +-------------- + +**Найважливіше - забезпечити правильний тип даних параметрів** - це необхідна умова для безпечного використання Nette Database. База даних передбачає, що всі вхідні дані мають правильний тип даних, що відповідає даному стовпцю. + +Наприклад, якби `$name` у попередніх прикладах було несподівано масивом замість рядка, Nette Database спробувала б вставити всі його елементи в SQL-запит, що призвело б до помилки. Тому **ніколи не використовуйте** невалідовані дані з `$_GET`, `$_POST` або `$_COOKIE` безпосередньо в запитах до бази даних. + + +Перевірка формату +----------------- + +На другому рівні ми перевіряємо формат даних - наприклад, чи є рядки в кодуванні UTF-8 та чи їхня довжина відповідає визначенню стовпця, або чи є числові значення в дозволеному діапазоні для даного типу даних стовпця. + +На цьому рівні валідації ми можемо частково покладатися і на саму базу даних - багато баз даних відхилять невалідовані дані. Однак поведінка може відрізнятися, деякі можуть тихо скоротити довгі рядки або обрізати числа поза діапазоном. + + +Доменна перевірка +----------------- + +Третій рівень представляють логічні перевірки, специфічні для вашого додатка. Наприклад, перевірка, що значення з select box відповідають запропонованим варіантам, що числа знаходяться в очікуваному діапазоні (наприклад, вік 0-150 років) або що взаємні залежності між значеннями мають сенс. + + +Рекомендовані способи валідації +------------------------------- + +- Використовуйте [Nette Forms|forms:], які автоматично забезпечать правильну валідацію всіх вхідних даних +- Використовуйте [Presenters|application:] та вказуйте у параметрів в `action*()` та `render*()` методах типи даних +- Або реалізуйте власний шар валідації за допомогою стандартних інструментів PHP, таких як `filter_var()` + + +Безпечна робота зі стовпцями +============================ + +У попередньому розділі ми показали, як правильно валідувати значення параметрів. Однак при використанні масивів у SQL-запитах ми повинні приділяти таку ж увагу і їхнім ключам. + +```php +// ❌ НЕБЕЗПЕЧНИЙ КОД - не оброблені ключі в масиві +$database->query('INSERT INTO users', $_POST); +``` + +У командах INSERT та UPDATE це є критичною помилкою безпеки - зловмисник може вставити або змінити будь-який стовпець у базі даних. Він міг би, наприклад, встановити `is_admin = 1` або вставити будь-які дані в конфіденційні стовпці (так звана Mass Assignment Vulnerability). + +В умовах WHERE це ще небезпечніше, оскільки вони можуть містити оператори: + +```php +// ❌ НЕБЕЗПЕЧНИЙ КОД - не оброблені ключі в масиві +$_POST['salary >'] = 100000; +$database->query('SELECT * FROM users WHERE', $_POST); +// виконає запит WHERE (`salary` > 100000) +``` + +Зловмисник може використати цей підхід для систематичного з'ясування зарплат співробітників. Наприклад, почне із запиту на зарплати понад 100 000, потім менше 50 000 і поступовим звуженням діапазону може виявити приблизні зарплати всіх співробітників. Цей тип атаки називається SQL enumeration. + +Методи `where()` та `whereOr()` є ще [набагато гнучкішими |explorer#where] і підтримують у ключах та значеннях SQL-вирази, включаючи оператори та функції. Це дає зловмиснику можливість здійснити SQL-ін'єкцію: + +```php +// ❌ НЕБЕЗПЕЧНИЙ КОД - зловмисник може вставити власний SQL +$_POST = ['0) UNION SELECT name, salary FROM users WHERE (1']; +$table->where($_POST); +// виконає запит WHERE (0) UNION SELECT name, salary FROM users WHERE (1) +``` + +Ця атака завершує початкову умову за допомогою `0)`, приєднує власний `SELECT` за допомогою `UNION` для отримання конфіденційних даних з таблиці `users` та закриває синтаксично правильний запит за допомогою `WHERE (1)`. + + +Білий список стовпців +--------------------- + +Для безпечної роботи з назвами стовпців нам потрібен механізм, який забезпечить, що користувач може працювати лише з дозволеними стовпцями і не може додати власні. Ми могли б спробувати виявляти та блокувати небезпечні назви стовпців (чорний список), але цей підхід ненадійний - зловмисник завжди може придумати новий спосіб записати небезпечну назву стовпця, який ми не передбачили. + +Тому набагато безпечніше змінити логіку і визначити явний список дозволених стовпців (білий список): + +```php +// Стовпці, які користувач може редагувати +$allowedColumns = ['name', 'email', 'active']; + +// Видалимо всі недозволені стовпці з вхідних даних +$filteredData = array_intersect_key($userData, array_flip($allowedColumns)); + +// ✅ Тепер можна безпечно використовувати в запитах, наприклад: +$database->query('INSERT INTO users', $filteredData); +$table->update($filteredData); +$table->where($filteredData); +``` + + +Динамічні ідентифікатори +======================== + +Для динамічних назв таблиць та стовпців використовуйте заповнювач `?name`. Він забезпечить правильне екранування ідентифікаторів відповідно до синтаксису даної бази даних (наприклад, за допомогою зворотних апострофів у MySQL): + +```php +// ✅ Безпечне використання довірених ідентифікаторів +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name', $column, $table); +// Результат у MySQL: SELECT `name` FROM `users` +``` + +Важливо: символ `?name` використовуйте лише для довірених значень, визначених у коді програми. Для значень від користувача використовуйте знову [білий список |#Білий список стовпців]. Інакше ви наражаєтеся на ризики безпеки: + +```php +// ❌ НЕБЕЗПЕЧНО - ніколи не використовуйте вхідні дані від користувача +$database->query('SELECT ?name FROM users', $_GET['column']); +``` diff --git a/database/uk/sql-way.texy b/database/uk/sql-way.texy new file mode 100644 index 0000000000..3565ac16c4 --- /dev/null +++ b/database/uk/sql-way.texy @@ -0,0 +1,513 @@ +SQL підхід +********** + +.[perex] +Nette Database пропонує два шляхи: ви можете писати SQL-запити самостійно (SQL підхід), або дозволити генерувати їх автоматично (див. [Explorer |explorer]). SQL підхід дає вам повний контроль над запитами і при цьому забезпечує їх безпечне формування. + +.[note] +Деталі щодо підключення та конфігурації бази даних знайдете в розділі [Підключення та конфігурація |guide#Підключення та конфігурація]. + + +Базові запити +============= + +Для запитів до бази даних служить метод `query()`. Він повертає об'єкт [ResultSet |api:Nette\Database\ResultSet], який представляє результат запиту. У разі невдачі метод [викине виняток|exceptions]. Результат запиту можна перебирати за допомогою циклу `foreach`, або використати одну з [допоміжних функцій |#Отримання даних]. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; +} +``` + +Для безпечного вставлення значень у SQL-запити використовуємо параметризовані запити. Nette Database робить їх максимально простими - достатньо після SQL-запиту додати кому та значення: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +При використанні кількох параметрів у вас є два варіанти запису. Ви можете "розбавляти" SQL-запит параметрами: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age); +``` + +Або написати спочатку весь SQL-запит, а потім додати всі параметри: + +```php +$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age); +``` + + +Захист від SQL injection +======================== + +Чому важливо використовувати параметризовані запити? Тому що вони захищають вас від атаки під назвою SQL injection, під час якої зловмисник міг би підсунути власні SQL-команди і таким чином отримати або пошкодити дані в базі даних. + +.[warning] +**Ніколи не вставляйте змінні безпосередньо в SQL-запит!** Завжди використовуйте параметризовані запити, які захистять вас від SQL injection. + +```php +// ❌ НЕБЕЗПЕЧНИЙ КОД - вразливий до SQL injection +$database->query("SELECT * FROM users WHERE name = '$name'"); + +// ✅ Безпечний параметризований запит +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Ознайомтеся з [можливими ризиками безпеки |security]. + + +Техніки запитів +=============== + + +Умови WHERE +----------- + +Умови WHERE можна записати як асоціативний масив, де ключі - це назви стовпців, а значення - дані для порівняння. Nette Database автоматично вибере найбільш відповідний SQL-оператор залежно від типу значення. + +```php +$database->query('SELECT * FROM users WHERE', [ + 'name' => 'John', + 'active' => true, +]); +// WHERE `name` = 'John' AND `active` = 1 +``` + +У ключі можна також явно вказати оператор для порівняння: + +```php +$database->query('SELECT * FROM users WHERE', [ + 'age >' => 25, // використовує оператор > + 'name LIKE' => '%John%', // використовує оператор LIKE + 'email NOT LIKE' => '%example.com%', // використовує оператор NOT LIKE +]); +// WHERE `age` > 25 AND `name` LIKE '%John%' AND `email` NOT LIKE '%example.com%' +``` + +Nette автоматично обробляє спеціальні випадки, такі як значення `null` або масиви. + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name' => 'Laptop', // використовує оператор = + 'category_id' => [1, 2, 3], // використовує IN + 'description' => null, // використовує IS NULL +]); +// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL +``` + +Для негативних умов використовуйте оператор `NOT`: + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name NOT' => 'Laptop', // використовує оператор <> + 'category_id NOT' => [1, 2, 3], // використовує NOT IN + 'description NOT' => null, // використовує IS NOT NULL + 'id' => [], // пропускається +]); +// WHERE `name` <> 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL +``` + +Для об'єднання умов використовується оператор `AND`. Це можна змінити за допомогою [заповнювача ?or |#Підказки для побудови SQL]. + + +Правила ORDER BY +---------------- + +Сортування `ORDER BY` можна записати за допомогою масиву. У ключах вказуємо стовпці, а значенням буде boolean, що визначає, чи сортувати за зростанням: + +```php +$database->query('SELECT id FROM author ORDER BY', [ + 'id' => true, // за зростанням + 'name' => false, // за спаданням +]); +// SELECT id FROM author ORDER BY `id`, `name` DESC +``` + + +Вставка даних (INSERT) +---------------------- + +Для вставки записів використовується SQL-команда `INSERT`. + +```php +$values = [ + 'name' => 'John Doe', + 'email' => 'john@example.com', +]; +$database->query('INSERT INTO users ?', $values); +$userId = $database->getInsertId(); +``` + +Метод `getInsertId()` повертає ID останнього вставленого рядка. У деяких базах даних (наприклад, PostgreSQL) необхідно як параметр вказати назву послідовності, з якої має генеруватися ID, за допомогою `$database->getInsertId($sequenceId)`. + +Як параметри можна передавати і [#Спеціальні значення], такі як файли, об'єкти DateTime або перелічувані типи. + +Вставка кількох записів одночасно: + +```php +$database->query('INSERT INTO users ?', [ + ['name' => 'User 1', 'email' => 'user1@mail.com'], + ['name' => 'User 2', 'email' => 'user2@mail.com'], +]); +``` + +Багаторазовий INSERT набагато швидший, оскільки виконується єдиний запит до бази даних замість багатьох окремих. + +**Попередження щодо безпеки:** Ніколи не використовуйте як `$values` невалідовані дані. Ознайомтеся з [можливими ризиками |security#Безпечна робота зі стовпцями]. + + +Оновлення даних (UPDATE) +------------------------ + +Для оновлення записів використовується SQL-команда `UPDATE`. + +```php +// Оновлення одного запису +$values = [ + 'name' => 'John Smith', +]; +$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1); +``` + +Кількість зачеплених рядків поверне `$result->getRowCount()`. + +Для UPDATE можна використовувати оператори `+=` та `-=`: + +```php +$database->query('UPDATE users SET ? WHERE id = ?', [ + 'login_count+=' => 1, // інкрементація login_count +], 1); +``` + +Приклад вставки або оновлення запису, якщо він вже існує. Використаємо техніку `ON DUPLICATE KEY UPDATE`: + +```php +$values = [ + 'name' => $name, + 'year' => $year, +]; +$database->query('INSERT INTO users ? ON DUPLICATE KEY UPDATE ?', + $values + ['id' => $id], + $values, +); +// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) +// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 +``` + +Зверніть увагу, що Nette Database розпізнає, в якому контексті SQL-команди вставляється параметр з масивом, і відповідно до цього складає з нього SQL-код. Так, з першого масиву він склав `(id, name, year) VALUES (123, 'Jim', 1978)`, тоді як другий перетворив на вигляд `name = 'Jim', year = 1978`. Детальніше про це йдеться в розділі [#Підказки для побудови SQL]. + + +Видалення даних (DELETE) +------------------------ + +Для видалення записів використовується SQL-команда `DELETE`. Приклад з отриманням кількості видалених рядків: + +```php +$count = $database->query('DELETE FROM users WHERE id = ?', 1) + ->getRowCount(); +``` + + +Підказки для побудови SQL +------------------------- + +Підказка - це спеціальний заповнювач у SQL-запиті, який вказує, як значення параметра має бути перетворено на SQL-вираз: + +| Підказка | Опис | Автоматично використовується +|-----------|-------------------------------------------------|----------------------------- +| `?name` | використовується для вставки назви таблиці або стовпця | - +| `?values` | генерує `(key, ...) VALUES (value, ...)` | `INSERT ... ?`, `REPLACE ... ?` +| `?set` | генерує присвоєння `key = value, ...` | `SET ?`, `KEY UPDATE ?` +| `?and` | об'єднує умови в масиві оператором `AND` | `WHERE ?`, `HAVING ?` +| `?or` | об'єднує умови в масиві оператором `OR` | - +| `?order` | генерує умову `ORDER BY` | `ORDER BY ?`, `GROUP BY ?` + +Для динамічного вставлення назв таблиць та стовпців у запит служить заповнювач `?name`. Nette Database подбає про правильну обробку ідентифікаторів відповідно до конвенцій даної бази даних (наприклад, взяття у зворотні лапки в MySQL). + +```php +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table); +// SELECT `name` FROM `users` WHERE id = 1 (у MySQL) +``` + +**Попередження:** символ `?name` використовуйте лише для назв таблиць та стовпців з валідованих вхідних даних, інакше ви наражаєтеся на [ризик безпеки |security#Динамічні ідентифікатори]. + +Інші підказки зазвичай не потрібно вказувати, оскільки Nette використовує розумну автодетекцію при складанні SQL-запиту (див. третій стовпець таблиці). Але ви можете її використати, наприклад, у ситуації, коли хочете об'єднати умови за допомогою `OR` замість `AND`: + +```php +$database->query('SELECT * FROM users WHERE ?or', [ + 'name' => 'John', + 'email' => 'john@example.com', +]); +// SELECT * FROM users WHERE `name` = 'John' OR `email` = 'john@example.com' +``` + + +Спеціальні значення +------------------- + +Крім звичайних скалярних типів (string, int, bool), ви можете передавати як параметри і спеціальні значення: + +- файли: `fopen('image.gif', 'r')` вставить бінарний вміст файлу +- дата та час: об'єкти `DateTime` перетворяться на формат бази даних +- перелічувані типи: екземпляри `enum` перетворяться на їхнє значення +- SQL літерали: створені за допомогою `Connection::literal('NOW()')` вставляться безпосередньо в запит + +```php +$database->query('INSERT INTO articles ?', [ + 'title' => 'My Article', + 'published_at' => new DateTime, + 'content' => fopen('image.png', 'r'), + 'state' => Status::Draft, +]); +``` + +У базах даних, які не мають нативної підтримки для типу даних `datetime` (як SQLite та Oracle), `DateTime` перетворюється на значення, визначене в [конфігурації бази даних|configuration] елементом `formatDateTime` (значення за замовчуванням - `U` - unix timestamp). + + +SQL літерали +------------ + +У деяких випадках потрібно вказати як значення безпосередньо SQL-код, який, однак, не повинен розглядатися як рядок і екрануватися. Для цього служать об'єкти класу `Nette\Database\SqlLiteral`. Їх створює метод `Connection::literal()`. + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + 'year >' => $database::literal('YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) +``` + +Або альтернативно: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) +``` + +SQL літерали можуть містити параметри: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > ? AND year < ?', $min, $max), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) +``` + +Завдяки чому можна створювати цікаві комбінації: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('?or', [ + 'active' => true, + 'role' => $role, + ]), +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') +``` + + +Отримання даних +=============== + + +Скорочення для SELECT-запитів +----------------------------- + +Для спрощення завантаження даних `Connection` пропонує кілька скорочень, які комбінують виклик `query()` з наступним `fetch*()`. Ці методи приймають ті самі параметри, що й `query()`, тобто SQL-запит та необов'язкові параметри. Повний опис методів `fetch*()` знайдете [нижче |#fetch]. + +| `fetch($sql, ...$params): ?Row` | Виконує запит і повертає перший рядок як об'єкт `Row` +| `fetchAll($sql, ...$params): array` | Виконує запит і повертає всі рядки як масив об'єктів `Row` +| `fetchPairs($sql, ...$params): array` | Виконує запит і повертає асоціативний масив, де перший стовпець представляє ключ, а другий - значення +| `fetchField($sql, ...$params): mixed` | Виконує запит і повертає значення першого поля з першого рядка +| `fetchList($sql, ...$params): ?array` | Виконує запит і повертає перший рядок як індексований масив + +Приклад: + +```php +// fetchField() - повертає значення першої комірки +$count = $database->query('SELECT COUNT(*) FROM articles') + ->fetchField(); +``` + + +`foreach` - ітерація по рядках +------------------------------ + +Після виконання запиту повертається об'єкт [ResultSet|api:Nette\Database\ResultSet], який дозволяє перебирати результати кількома способами. Найпростіший спосіб виконати запит і отримати рядки - це ітерація в циклі `foreach`. Цей спосіб є найбільш економним з точки зору пам'яті, оскільки повертає дані поступово і не зберігає їх усі в пам'яті одночасно. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; + // ... +} +``` + +.[note] +`ResultSet` можна ітерувати лише один раз. Якщо вам потрібно ітерувати повторно, ви повинні спочатку завантажити дані в масив, наприклад, за допомогою методу `fetchAll()`. + + +fetch(): ?Row .[method] +----------------------- + +Повертає рядок як об'єкт `Row`. Якщо більше немає рядків, повертає `null`. Пересуває внутрішній вказівник на наступний рядок. + +```php +$result = $database->query('SELECT * FROM users'); +$row = $result->fetch(); // читає перший рядок +if ($row) { + echo $row->name; +} +``` + + +fetchAll(): array .[method] +--------------------------- + +Повертає всі рядки, що залишилися, з `ResultSet` як масив об'єктів `Row`. + +```php +$result = $database->query('SELECT * FROM users'); +$rows = $result->fetchAll(); // читає всі рядки +foreach ($rows as $row) { + echo $row->name; +} +``` + + +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Повертає результати як асоціативний масив. Перший аргумент визначає назву стовпця, який буде використаний як ключ у масиві, другий аргумент визначає назву стовпця, який буде використаний як значення: + +```php +$result = $database->query('SELECT id, name FROM users'); +$names = $result->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] +``` + +Якщо вказати лише перший параметр, значенням буде весь рядок, тобто об'єкт `Row`: + +```php +$rows = $result->fetchPairs('id'); +// [1 => Row(id: 1, name: 'John'), 2 => Row(id: 2, name: 'Jane'), ...] +``` + +У разі дублювання ключів використовується значення з останнього рядка. При використанні `null` як ключа масив буде індексовано нумерично з нуля (тоді колізій не виникає): + +```php +$names = $result->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] +``` + + +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Альтернативно, ви можете вказати як параметр callback, який для кожного рядка повертатиме або саме значення, або пару ключ-значення. + +```php +$result = $database->query('SELECT * FROM users'); +$items = $result->fetchPairs(fn($row) => "$row->id - $row->name"); +// ['1 - John', '2 - Jane', ...] + +// Callback також може повертати масив із парою ключ & значення: +$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]); +// ['John' => 46, 'Jane' => 21, ...] +``` + + +fetchField(): mixed .[method] +----------------------------- + +Повертає значення першого поля з поточного рядка. Якщо більше немає рядків, повертає `null`. Пересуває внутрішній вказівник на наступний рядок. + +```php +$result = $database->query('SELECT name FROM users'); +$name = $result->fetchField(); // читає ім'я з першого рядка +``` + + +fetchList(): ?array .[method] +----------------------------- + +Повертає рядок як індексований масив. Якщо більше немає рядків, повертає `null`. Пересуває внутрішній вказівник на наступний рядок. + +```php +$result = $database->query('SELECT name, email FROM users'); +$row = $result->fetchList(); // ['John', 'john@example.com'] +``` + + +getRowCount(): ?int .[method] +----------------------------- + +Повертає кількість зачеплених рядків останнім запитом `UPDATE` або `DELETE`. Для `SELECT` це кількість повернутих рядків, але вона може бути невідомою - у такому випадку метод поверне `null`. + + +getColumnCount(): ?int .[method] +-------------------------------- + +Повертає кількість стовпців у `ResultSet`. + + +Інформація про запити +===================== + +Для цілей налагодження ми можемо отримати інформацію про останній виконаний запит: + +```php +echo $database->getLastQueryString(); // виводить SQL-запит + +$result = $database->query('SELECT * FROM articles'); +echo $result->getQueryString(); // виводить SQL-запит +echo $result->getTime(); // виводить час виконання в секундах +``` + +Для відображення результату у вигляді HTML-таблиці можна використати: + +```php +$result = $database->query('SELECT * FROM articles'); +$result->dump(); +``` + +ResultSet пропонує інформацію про типи стовпців: + +```php +$result = $database->query('SELECT * FROM articles'); +$types = $result->getColumnTypes(); + +foreach ($types as $column => $type) { + echo "$column має тип $type->type"; // напр. 'id має тип int' +} +``` + + +Логування запитів +----------------- + +Ми можемо реалізувати власне логування запитів. Подія `onQuery` - це масив callback'ів, які викликаються після кожного виконаного запиту: + +```php +$database->onQuery[] = function ($database, $result) use ($logger) { + $logger->info('Запит: ' . $result->getQueryString()); + $logger->info('Час: ' . $result->getTime()); + + if ($result->getRowCount() > 1000) { + $logger->warning('Великий набір результатів: ' . $result->getRowCount() . ' рядків'); + } +}; +``` diff --git a/database/uk/transactions.texy b/database/uk/transactions.texy new file mode 100644 index 0000000000..57053362e7 --- /dev/null +++ b/database/uk/transactions.texy @@ -0,0 +1,43 @@ +Транзакції +********** + +.[perex] +Транзакції гарантують, що або всі операції в рамках транзакції будуть виконані, або жодна з них. Вони корисні для забезпечення узгодженості даних під час складних операцій. + +Найпростіший спосіб використання транзакцій виглядає так: + +```php +$database->beginTransaction(); +try { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); + $database->commit(); +} catch (\Exception $e) { + $database->rollBack(); + throw $e; +} +``` + +Набагато елегантніше те саме можна записати за допомогою методу `transaction()`. Він приймає як параметр callback, який виконується в транзакції. Якщо callback завершується без винятку, транзакція автоматично підтверджується. Якщо виникає виняток, транзакція скасовується (rollback), а виняток поширюється далі. + +```php +$database->transaction(function ($database) use ($id) { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); +}); +``` + +Метод `transaction()` також може повертати значення: + +```php +$count = $database->transaction(function ($database) { + $result = $database->query('UPDATE users SET active = ?', true); + return $result->getRowCount(); // повертає кількість оновлених рядків +}); +``` diff --git a/dependency-injection/bg/@home.texy b/dependency-injection/bg/@home.texy index 5db2ff419d..6e1130be6c 100644 --- a/dependency-injection/bg/@home.texy +++ b/dependency-injection/bg/@home.texy @@ -1,24 +1,21 @@ -Инжектиране на зависимости -************************** +Nette DI +******** .[perex] -Dependency Injection е шаблон за проектиране, който ще промени из основи начина, по който гледате на кода и разработката. Той открива пътя към свят на чисто проектирани и устойчиви приложения. +Dependency Injection е дизайн патърн, който коренно ще промени вашия поглед върху кода и разработката. Ще ви отвори пътя към света на чисто проектирани и устойчиви приложения. -- [Какво е вкарване на зависимости? |introduction] -- [Глобално състояние и единични елементи |global-state] +- [Какво е Dependency Injection? |introduction] +- [Глобално състояние и сингълтони |global-state] - [Предаване на зависимости |passing-dependencies] - [Какво е DI контейнер? |container] -- [Често задавани въпроси |faq] - +- [Често задавани въпроси|faq] -Nette DI --------- Пакетът `nette/di` предоставя изключително усъвършенстван компилиран DI контейнер за PHP. -- [Контейнер Nette DI |nette-container] +- [Nette DI Container |nette-container] - [Конфигурация |configuration] -- [Дефиниции на услуги |services] -- [Автоматично свързване |autowiring] +- [Дефиниране на сървиси |services] +- [Autowiring |autowiring] - [Генерирани фабрики |factory] -- [Създаване на разширения за Nette DI |extensions] +- [Създаване на разширения за Nette DI|extensions] diff --git a/dependency-injection/bg/@left-menu.texy b/dependency-injection/bg/@left-menu.texy index 4d8328ab57..77e92a85f8 100644 --- a/dependency-injection/bg/@left-menu.texy +++ b/dependency-injection/bg/@left-menu.texy @@ -1,17 +1,17 @@ -Изпълнение на зависимостта -************************** -- [Какво е прилагане на зависимостта? |introduction] -- [Глобално състояние и единични елементи |global-state] -- [Прехвърляне на зависимостта |passing-dependencies] -- [Какво е контейнер DI? |container] -- [Често задавани въпроси |faq] +Dependency Injection +******************** +- [Какво е DI? |introduction] +- [Глобално състояние и сингълтони |global-state] +- [Предаване на зависимости |passing-dependencies] +- [Какво е DI контейнер? |container] +- [Често задавани въпроси|faq] Nette DI -------- -- [Контейнер Nette DI |nette-container] -- [Настройване |configuration] -- [Определения на услугите |services] -- [Какво е автоматично обвързване |autowiring] +- [Nette DI Container |nette-container] +- [Конфигурация |configuration] +- [Дефиниране на сървиси |services] +- [Autowiring |autowiring] - [Генерирани фабрики |factory] -- [Създаване на разширения за Nette DI |extensions] +- [Създаване на разширения за Nette DI|extensions] diff --git a/dependency-injection/bg/@meta.texy b/dependency-injection/bg/@meta.texy new file mode 100644 index 0000000000..57804a1127 --- /dev/null +++ b/dependency-injection/bg/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документация на Nette}} diff --git a/dependency-injection/bg/autowiring.texy b/dependency-injection/bg/autowiring.texy index 3b826597cd..c5ce14c62f 100644 --- a/dependency-injection/bg/autowiring.texy +++ b/dependency-injection/bg/autowiring.texy @@ -1,24 +1,24 @@ -Автокомуникация -*************** +Autowiring +********** .[perex] -Autowiring или autobinding е чудесна функция, която може автоматично да предава услуги на конструктора и други методи, така че да не е необходимо да ги пишем изобщо. Това ще ви спести много време. +Autowiring е страхотна функция, която може автоматично да предава необходимите сървиси към конструктора и други методи, така че изобщо не е необходимо да ги пишем. Ще ви спести много време. -Това ни позволява да пропуснем по-голямата част от аргументите при писането на дефиниции на услуги. Вместо: +Благодарение на това можем да пропуснем по-голямата част от аргументите при писане на дефиниции на сървиси. Вместо: ```neon services: articles: Model\ArticleRepository(@database, @cache.storage) ``` -Просто напишете: +Достатъчно е да напишете: ```neon services: articles: Model\ArticleRepository ``` -Автоматичното свързване се контролира от типовете, така че класът `ArticleRepository` трябва да бъде дефиниран по следния начин: +Autowiring се ръководи от типовете, така че за да работи, класът `ArticleRepository` трябва да бъде дефиниран приблизително така: ```php namespace Model; @@ -30,22 +30,22 @@ class ArticleRepository } ``` -За да се използва автоматично обвързване, контейнерът трябва да има **само една услуга** за всеки тип. Ако те са повече, автоматичното свързване няма да знае коя от тях да предаде и ще направи изключение: +За да може да се използва autowiring, за всеки тип трябва да има **точно един сървис** в контейнера. Ако има повече, autowiring няма да знае кой от тях да предаде и ще хвърли изключение: ```neon services: mainDb: PDO(%dsn%, %user%, %password%) tempDb: PDO('sqlite::memory:') - articles: Model\ArticleRepository # ВЫБРАСЫВАЕТСЯ ИСКЛЮЧЕНИЕ, mainDb и tempDb совпадают + articles: Model\ArticleRepository # ХВЪРЛЯ ИЗКЛЮЧЕНИЕ, отговарят и mainDb, и tempDb ``` -Решението може да бъде или да се заобиколи автоматичната комуникация, или изрично да се посочи името на услугата (т.е. `articles: Model\ArticleRepository(@mainDb)`). Въпреки това е по-удобно да [деактивирате |#Disabled-Autowiring] автоматичното свързване на една услуга или да [предпочетете |#Preferred-Autowiring] конкретна услуга. +Решението би било или да се заобиколи autowiring и изрично да се посочи името на сървиса (т.е. `articles: Model\ArticleRepository(@mainDb)`). По-удобно обаче е autowiring-ът на един от сървисите да се [изключи |#Изключване на autowiring] или първият сървис да се [предпочете |#Предпочитание за autowiring]. -Деактивирано автоматично свързване .[#toc-disabled-autowiring] --------------------------------------------------------------- +Изключване на autowiring +------------------------ -Можете да деактивирате автоматичното откриване на зависимостите на услугите, като използвате параметъра `autowired: no`: +Можем да изключим autowiring-а на сървис с помощта на опцията `autowired: no`: ```neon services: @@ -53,28 +53,27 @@ services: tempDb: create: PDO('sqlite::memory:') - autowired: false # премахва tempDb от автоматичното обвързване + autowired: false # сървисът tempDb е изключен от autowiring - articles: Model\ArticleRepository # предава mainDb на конструктора + articles: Model\ArticleRepository # следователно предава mainDb на конструктора ``` -Услугата `articles` не хвърля изключение, че има две свързани услуги от тип `PDO` (т.е. `mainDb` и `tempDb`), които могат да бъдат предадени на конструктора, тъй като той вижда само услугата `mainDb`. +Сървисът `articles` няма да хвърли изключение, че съществуват два подходящи сървиса от тип `PDO` (т.е. `mainDb` и `tempDb`), които могат да бъдат предадени на конструктора, защото вижда само сървиса `mainDb`. .[note] -Конфигурацията за автоматично свързване работи по различен начин в Nette, отколкото в Symfony, където опцията `autowire: false` казва, че автоматичното свързване не трябва да се използва за аргументите на конструктора на услугата. -В Nette винаги се използва автоматично свързване, независимо дали става въпрос за аргументи на конструктора или за друг метод. Опцията `autowired: false` указва, че екземплярът на услугата не трябва да се предава никъде с помощта на автоматично свързване. +Конфигурацията на autowiring в Nette работи различно от тази в Symfony, където опцията `autowire: false` указва, че autowiring не трябва да се използва за аргументите на конструктора на дадения сървис. В Nette autowiring се използва винаги, независимо дали за аргументите на конструктора, или за които и да било други методи. Опцията `autowired: false` указва, че инстанцията на дадения сървис не трябва да бъде предавана никъде чрез autowiring. -Предпочитаното автоматично окабеляване .[#toc-preferred-autowiring] -------------------------------------------------------------------- +Предпочитание за autowiring +--------------------------- -Ако разполагаме с няколко услуги от един и същи вид и една от тях има опция `autowired`, тази услуга става предпочитана: +Ако имаме няколко сървиса от един и същи тип и за един от тях посочим опцията `autowired`, този сървис става предпочитан: ```neon services: mainDb: create: PDO(%dsn%, %user%, %password%) - autowired: PDO # прави го предпочитан + autowired: PDO # става предпочитан tempDb: create: PDO('sqlite::memory:') @@ -82,13 +81,13 @@ services: articles: Model\ArticleRepository ``` -Услугата `articles` няма да хвърли изключение, ако има две съвпадащи услуги `PDO` (т.е. `mainDb` и `tempDb`), а ще използва предпочитаната услуга, т.е. `mainDb`. +Сървисът `articles` няма да хвърли изключение, че съществуват два подходящи сървиса от тип `PDO` (т.е. `mainDb` и `tempDb`), а ще използва предпочитания сървис, т.е. `mainDb`. -Събиране на услуги .[#toc-collection-of-services] -------------------------------------------------- +Масив от сървиси +---------------- -Автоматичното свързване може също така да предава масив от услуги от определен тип. Тъй като PHP не може да обозначава типа на елементите на масива, в допълнение към типа `array` трябва да се добави phpDoc коментар с типа на елемента, например `ClassName[]`: +Autowiring може да предава и масиви от сървиси от определен тип. Тъй като в PHP не може нативно да се запише типът на елементите на масива, е необходимо освен типа `array` да се добави и phpDoc коментар с типа на елемента във формата `ClassName[]`: ```php namespace Model; @@ -103,16 +102,15 @@ class ShipManager } ``` -След това контейнерът DI автоматично ще предаде масив от услуги, отговарящи на зададения тип. Това ще доведе до пропускане на услуги с деактивирано автоматично свързване. +След това DI контейнерът автоматично предава масив от сървиси, съответстващи на дадения тип. Пропуска сървисите, които имат изключен autowiring. -Ако не можете да контролирате формата за коментари на phpDoc, можете да подадете масив от услуги директно в конфигурацията, като използвате [`typed()` |services#Special-Functions]. +Типът в коментара може да бъде също във формата `array<int, Class>` или `list<Class>`. Ако не можете да повлияете на формата на phpDoc коментара, можете да предадете масива от сървиси директно в конфигурацията с помощта на [`typed()` |services#Специални функции]. -Скаларни аргументи .[#toc-scalar-arguments] -------------------------------------------- +Скаларни аргументи +------------------ -Автоматичното свързване може да предава само обекти и масиви от обекти. Скаларните аргументи (напр. низове, числа, булеви стойности) [се записват в конфигурацията |services#Arguments]. -Алтернативата е да се създаде [обект за настройки, |best-practices:passing-settings-to-presenters] който капсулира скаларна стойност (или няколко стойности) като обект, който след това може да бъде предаден отново с помощта на автоматично свързване. +Autowiring може да инжектира само обекти и масиви от обекти. Скаларните аргументи (напр. низове, числа, булеви стойности) [се записват в конфигурацията |services#Аргументи]. Алтернатива е да се създаде [settings-обект |best-practices:passing-settings-to-presenters], който капсулира скаларната стойност (или няколко стойности) под формата на обект, и той след това може отново да се предава чрез autowiring. ```php class MySettings @@ -125,24 +123,24 @@ class MySettings } ``` -Създавате услуга, като я добавяте в конфигурацията: +Създавате сървис от него, като го добавите към конфигурацията: ```neon services: - - MySettings('любое значение') + - MySettings('any value') ``` -След това всички класове ще го заявят чрез автоматично свързване. +След това всички класове го изискват чрез autowiring. -Стесняване на автоматичното окабеляване .[#toc-narrowing-of-autowiring] ------------------------------------------------------------------------ +Стесняване на autowiring +------------------------ -За отделни услуги автоматичното свързване може да бъде ограничено до конкретни класове или интерфейси. +За отделни сървиси autowiring може да бъде стеснен само до определени класове или интерфейси. -Обикновено автоматичното свързване предава функция на всеки параметър на метода, на чийто тип съответства функцията. Стесняването означава, че посочваме условията, на които трябва да отговарят типовете, посочени за параметрите на метода, за да може функцията да им бъде предадена. +Обикновено autowiring предава сървиса на всеки параметър на метод, чийто тип съответства на сървиса. Стесняването означава, че задаваме условия, на които трябва да отговарят типовете, посочени в параметрите на методите, за да им бъде предаден сървисът. -Нека разгледаме един пример: +Ще го покажем с пример: ```php class ParentClass @@ -164,42 +162,42 @@ class ChildDependent } ``` -Ако ги регистрираме всички като услуги, автоматичното свързване няма да е възможно: +Ако ги регистрираме всички като сървиси, autowiring ще се провали: ```neon services: parent: ParentClass child: ChildClass - parentDep: ParentDependent # ИЗКЛЮЧЕНИЕ, родител и дете са едно и също - childDep: ChildDependent # предава услугата 'child' на конструктора + parentDep: ParentDependent # ХВЪРЛЯ ИЗКЛЮЧЕНИЕ, отговарят сървисите parent и child + childDep: ChildDependent # autowiring предава сървиса child на конструктора ``` -Услугата `parentDep` хвърля изключение `Multiple services of type ParentClass found: parent, child`, тъй като и `parent`, и `child` са поставени в нейния конструктор и автоматичното свързване не може да реши коя от тях да избере. +Сървисът `parentDep` ще хвърли изключение `Multiple services of type ParentClass found: parent, child`, тъй като и двата сървиса `parent` и `child` отговарят на конструктора му, и autowiring не може да реши кой от тях да избере. -Така за услугата `child` можем да стесним нейното автоматично свързване до `ChildClass`: +Затова можем да стесним autowiring-а на сървиса `child` до тип `ChildClass`: ```neon services: parent: ParentClass child: create: ChildClass - autowired: ChildClass # алтернатива: 'autowired: self' + autowired: ChildClass # може да се напише и 'autowired: self' - parentDep: ParentDependent # ИЗКЛЮЧЕНИЕ, 'child' не може да бъде автоматично свързан - childDep: ChildDependent # предава услугата 'child' на конструктора + parentDep: ParentDependent # autowiring предава сървиса parent на конструктора + childDep: ChildDependent # autowiring предава сървиса child на конструктора ``` -Услугата `parentDep` сега се предава на конструктора на услугата `parentDep`, тъй като сега тя е единственият подходящ обект. Услугата `child` вече не е автоматично обвързана. Да, функцията `child` все още е от тип `ParentClass`, но условието за стесняване, зададено за типа на параметъра, вече не важи, т.е. вече не е вярно, че `ParentClass` *е надтип* на `ChildClass`. +Сега на конструктора на сървиса `parentDep` се предава сървисът `parent`, защото сега той е единственият подходящ обект. Autowiring вече не предава сървиса `child` там. Да, сървисът `child` все още е от тип `ParentClass`, но стесняващото условие, зададено за типа на параметъра, вече не е валидно, т.е. не е вярно, че `ParentClass` *е надтип на* `ChildClass`. -В случая на `child`, `autowired: ChildClass` може да се запише като `autowired: self`, тъй като `self` означава текущия тип услуга. +При сървиса `child` би било възможно `autowired: ChildClass` да се запише и като `autowired: self`, тъй като `self` е заместващо означение за класа на текущия сървис. -Ключът `autowired` може да включва няколко класа и интерфейса като масив: +В ключа `autowired` е възможно да се посочат и няколко класа или интерфейса като масив: ```neon autowired: [BarClass, FooInterface] ``` -Нека се опитаме да добавим интерфейси към примера: +Нека допълним примера и с интерфейси: ```php interface FooInterface @@ -239,13 +237,13 @@ class ChildDependent } ``` -Ако не ограничим услугата до `child`, тя ще съвпадне с конструкторите на всички класове `FooDependent`, `BarDependent`, `ParentDependent` и `ChildDependent`, а автоматичното свързване ще я предаде там. +Ако не ограничим сървиса `child` по никакъв начин, той ще пасне на конструкторите на всички класове `FooDependent`, `BarDependent`, `ParentDependent` и `ChildDependent` и autowiring ще го предаде там. -Ако обаче стесним автосвързването до `ChildClass` с `autowired: ChildClass` (или `self`), автосвързването ще го предаде само на конструктора `ChildDependent`, тъй като той изисква аргумент от тип `ChildClass`, а `ChildClass` *е тип* `ChildClass`. Нито един друг тип, посочен за другите параметри, не е заместител на `ChildClass`, поради което услугата не успява. +Но ако стесним неговия autowiring до `ChildClass` с помощта на `autowired: ChildClass` (или `self`), autowiring ще го предаде само на конструктора на `ChildDependent`, тъй като той изисква аргумент от тип `ChildClass` и е вярно, че `ChildClass` *е от тип* `ChildClass`. Никой друг тип, посочен в другите параметри, не е надтип на `ChildClass`, така че сървисът не се предава. -Ако го ограничим до `ParentClass` с `autowired: ParentClass`, тогава автосвързването ще го предаде отново на конструктора `ChildDependent` (защото изискваният тип `ChildClass` е супермножество на `ParentClass`) и на конструктора `ParentDependent`, защото изискваният тип `ParentClass` също съвпада. +Ако го ограничим до `ParentClass` с помощта на `autowired: ParentClass`, autowiring отново ще го предаде на конструктора на `ChildDependent` (тъй като изискваният `ChildClass` е надтип на `ParentClass`), а също и на конструктора на `ParentDependent`, тъй като изискваният тип `ParentClass` също е подходящ. -Ако го ограничим до `FooInterface`, той все още ще се свързва с `ParentDependent` (необходимият тип `ParentClass` е надтип на `FooInterface`) и `ChildDependent`, но освен това и с конструктора `FooDependent`, но не и с `BarDependent`, тъй като `BarInterface` не е надтип на `FooInterface`. +Ако го ограничим до `FooInterface`, той все още ще бъде автоматично инжектиран в `ParentDependent` (изискваният `ParentClass` е надтип на `FooInterface`) и `ChildDependent`, но освен това и в конструктора на `FooDependent`, но не и в `BarDependent`, тъй като `BarInterface` не е надтип на `FooInterface`. ```neon services: @@ -253,8 +251,8 @@ services: create: ChildClass autowired: FooInterface - fooDep: FooDependent # предава подчинената услуга на конструктора - barDep: BarDependent # предава подчинената услуга на конструктора - parentDep: ParentDependent # Предава подчинената услуга на конструктора - childDep: ChildDependent # предава услугата на детето на конструктора + fooDep: FooDependent # autowiring предава child на конструктора + barDep: BarDependent # ХВЪРЛЯ ИЗКЛЮЧЕНИЕ, нито един сървис не отговаря + parentDep: ParentDependent # autowiring предава child на конструктора + childDep: ChildDependent # autowiring предава child на конструктора ``` diff --git a/dependency-injection/bg/configuration.texy b/dependency-injection/bg/configuration.texy index d90b41c6b4..3f6bf580ca 100644 --- a/dependency-injection/bg/configuration.texy +++ b/dependency-injection/bg/configuration.texy @@ -1,32 +1,33 @@ -Създаване на контейнер DI -************************* +Конфигурация на DI контейнера +***************************** .[perex] -Преглед на опциите за конфигуриране на контейнера Nette DI. +Преглед на опциите за конфигурация на Nette DI контейнера. Конфигурационен файл ==================== -Контейнерът Nette DI се управлява лесно с помощта на конфигурационни файлове. Обикновено те са записани във [формат NEON |neon:format]. Препоръчваме ви да използвате [редактори, които поддържат |best-practices:editors-and-tools#IDE-Editor] този формат, когато редактирате такива файлове. +Nette DI контейнерът се управлява лесно с помощта на конфигурационни файлове. Те обикновено се записват във [формат NEON|neon:format]. За редактиране препоръчваме [редактори с поддръжка |best-practices:editors-and-tools#IDE редактор] на този формат. <pre> "decorator .[prism-token prism-atrule]":[#Decorator]: "Декоратор .[prism-token prism-comment]"<br> -"di .[prism-token prism-atrule]":[#DI]: "DI Container .[prism-token prism-comment]"<br> -"extensions .[prism-token prism-atrule]":[#Extensions]: "Инсталиране на допълнителни разширения на DI .[prism-token prism-comment]"<br> -"includes .[prism-token prism-atrule]":[#Including files]: "Включва файлове .[prism-token prism-comment]"<br> -"parameters .[prism-token prism-atrule]":[#Parameters]: "Параметри .[prism-token prism-comment]"<br> -"search .[prism-token prism-atrule]":[#Search]: "Автоматична регистрация на услуги .[prism-token prism-comment]"<br> -"services .[prism-token prism-atrule]":[services]: "Услуги .[prism-token prism-comment]" +"di .[prism-token prism-atrule]":[#DI]: "DI контейнер .[prism-token prism-comment]"<br> +"extensions .[prism-token prism-atrule]":[#Разширения]: "Инсталиране на други DI разширения .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[#Включване на файлове]: "Включване на файлове .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[#Параметри]: "Параметри .[prism-token prism-comment]"<br> +"search .[prism-token prism-atrule]":[#Search]: "Автоматично регистриране на сървиси .[prism-token prism-comment]"<br> +"services .[prism-token prism-atrule]":[services]: "Сървиси .[prism-token prism-comment]" </pre> -Записване на низ, съдържащ символа `%`, вы должны экранировать его удвоением до `%%`. .[note] +.[note] +Ако искате да напишете низ, съдържащ знака `%`, трябва да го екранирате, като го удвоите на `%%`. -Параметри .[#toc-parameters] -============================ +Параметри +========= -Можете да зададете параметри, които след това да бъдат използвани като част от дефиницията на услугата. Това може да ви помогне да отделите стойностите, които искате да променяте по-редовно: +В конфигурацията можете да дефинирате параметри, които след това могат да се използват като част от дефинициите на сървисите. Това може да направи конфигурацията по-ясна или да обедини и изолира стойности, които ще се променят. ```neon parameters: @@ -35,9 +36,9 @@ parameters: password: secret ``` -Във всеки конфигурационен файл можете да се позовавате на параметъра `foo` чрез `%foo%` на друго място. Те могат да се използват и в рамките на редове като `'%wwwDir%/images'`. +Към параметъра `dsn` се обръщаме навсякъде в конфигурацията, като напишем `%dsn%`. Параметрите могат да се използват и в низове като `'%wwwDir%/images'`. -Не е задължително параметрите да бъдат само низове, те могат да бъдат и стойности на масиви: +Параметрите не трябва да бъдат само низове или числа, те могат да съдържат и масиви: ```neon parameters: @@ -48,32 +49,32 @@ parameters: languages: [cs, en, de] ``` -Можете да се обърнете към отделен ключ по следния начин: `%mailer.user%`. +Към конкретен ключ се обръщаме като `%mailer.user%`. -Ако трябва да получите стойността на някой параметър в кода си, например в клас, предайте го на този клас. Например, в конструктора. Няма обект на глобалната конфигурация, който да може да изисква стойности на параметрите. Това противоречи на принципа на прилагане на зависимостите. +Ако трябва да разберете стойността на който и да е параметър във вашия код, например в клас, предайте го на този клас. Например в конструктора. Няма глобален обект, представляващ конфигурацията, към който класовете да се обръщат за стойности на параметри. Това би било нарушение на принципа на dependency injection. -Услуги .[#toc-services] -======================= +Сървиси +======= -Вижте [отделна глава |services]. +Вижте [отделна глава|services]. -Декоратор .[#toc-decorator] -=========================== +Decorator +========= -Как да редактирам масово всички услуги от определен тип? Трябва ли да извикате определен метод за всички презентатори, наследени от определен общ предшественик? Оттук идва декораторът: +Как да модифицирате масово всички сървиси от определен тип? Например, да извикате определен метод за всички презентери, които наследяват от конкретен общ предшественик? За това служи декораторът. ```neon decorator: - # За всички услуги, които са екземпляри на този клас или интерфейс - App\Presenters\BasePresenter: + # за всички сървиси, които са инстанции на този клас или интерфейс + App\Presentation\BasePresenter: setup: - - setProjectId(10) # извикване на този метод - - $absoluteUrls = true # и задаване на променлива + - setProjectId(10) # извикайте този метод + - $absoluteUrls = true # и задайте променливата ``` -Декораторът може да се използва и за задаване на [тагове |services#Tags] или за активиране на [режима на вграждане |services#Inject-Mode]. +Decorator може да се използва и за задаване на [тагове |services#Тагове] или за активиране на режим [inject |services#Режим Inject]. ```neon decorator: @@ -86,67 +87,79 @@ decorator: DI === -Технически настройки за контейнера DI: +Технически настройки на DI контейнера. ```neon di: - # показване на DIC в Tracy debugger? + # показва ли се DIC в Tracy Bar? debugger: ... # (bool) по подразбиране е true - # типове параметри, които не е необходимо да се монтират автоматично + # типове параметри, които никога да не се autowire-ват excluded: ... # (string[]) - # клас, от който е наследен контейнерът DI - parentClass: ... # (string) по подразбиране Nette\DI\Container + # разрешава ли се lazy създаване на сървиси? + lazy: ... # (bool) по подразбиране е false + + # клас, от който наследява DI контейнерът + parentClass: ... # (string) по подразбиране е Nette\DI\Container ``` -Експорт на метаданни .[#toc-metadata-export] --------------------------------------------- +Lazy сървиси .{data-version:3.2.4} +---------------------------------- + +Настройката `lazy: true` активира lazy (отложено) създаване на сървиси. Това означава, че сървисите не се създават реално в момента, в който ги поискаме от DI контейнера, а едва в момента на първото им използване. Това може да ускори стартирането на приложението и да намали изискванията за памет, тъй като се създават само тези сървиси, които са действително необходими в дадена заявка. + +За конкретен сървис lazy създаването може да бъде [променено |services#Lazy сървиси]. + +.[note] +Lazy обектите могат да се използват само за потребителски класове, а не за вътрешни PHP класове. Изисква PHP 8.4 или по-нова версия. + + +Експортиране на метаданни +------------------------- -Контейнерният клас DI съдържа и много метаданни. Можете да го намалите, като намалите експортирането на метаданни. +Класът на DI контейнера съдържа и много метаданни. Можете да го намалите, като редуцирате експорта на метаданни. ```neon di: export: - # параметри за износ - parameters: false # (bool) по подразбиране е true + # експортиране на параметри? + parameters: false # (bool) по подразбиране е true - # експортни тагове - tags: # (string[]|bool) по подразбиране е all + # експортиране на тагове и кои? + tags: # (string[]|bool) по подразбиране са всички - event.subscriber - # експортиране на данни за автоматично свързване - types: # (string[]|bool) по подразбиране е all + # експортиране на данни за autowiring и кои? + types: # (string[]|bool) по подразбиране са всички - Nette\Database\Connection - Symfony\Component\Console\Application ``` -Ако не използвате масива `$container->parameters`, можете да деактивирате експортирането на параметри. Като алтернатива можете да експортирате само тези тагове, чрез които получавате услуги, като използвате метода `$container->findByTag(...)`. -Ако изобщо не извиквате този метод, можете да деактивирате напълно експортирането на тагове, като посочите `false`. +Ако не използвате масива `$container->getParameters()`, можете да изключите експорта на параметри. Освен това можете да експортирате само тези тагове, чрез които получавате сървиси с метода `$container->findByTag(...)`. Ако изобщо не извиквате метода, можете напълно да изключите експорта на тагове с `false`. -Можете значително да намалите метаданните за автоматичното свързване, като посочите класовете, които използвате, като параметър в метода `$container->getByType()`. -Отново, ако изобщо не извиквате този метод (или само в [bootstrap |application:bootstrap], за да получите `Nette\Application\Application`), можете да деактивирате износа напълно, като посочите стойността `false`. +Можете значително да намалите метаданните за [autowiring |autowiring] , като посочите класовете, които използвате като параметър на метода `$container->getByType()`. И отново, ако изобщо не извиквате метода (или само в [bootstrap|application:bootstrapping], за да получите `Nette\Application\Application`), можете напълно да изключите експорта с `false`. -Удължения .[#toc-extensions] -============================ +Разширения +========== -Регистрация на други разширения на DI. Така например добавяме разширението DI `Dibi\Bridges\Nette\DibiExtension22` под името `dibi`: +Регистриране на други DI разширения. По този начин добавяме например DI разширението `Dibi\Bridges\Nette\DibiExtension22` под името `dibi` ```neon extensions: dibi: Dibi\Bridges\Nette\DibiExtension22 ``` -След това го създаваме в раздела, наречен също `dibi`: +След това го конфигурираме в секцията `dibi`: ```neon dibi: host: localhost ``` -Можете също така да добавите клас за разширение с параметри: +Като разширение може да се добави и клас, който има параметри: ```neon extensions: @@ -154,10 +167,10 @@ extensions: ``` -Включване на файлове .[#toc-including-files] -============================================ +Включване на файлове +==================== -Допълнителни конфигурационни файлове могат да бъдат вмъкнати в разделите `includes`: +Можем да включим други конфигурационни файлове в секцията `includes`: ```neon includes: @@ -179,32 +192,27 @@ return [ ]; ``` -Ако в конфигурационните файлове се появят елементи със същите ключове, те ще бъдат [презаписани или обединени в |#Merging] случай на масиви. Следващият включен файл е с по-висок приоритет от предишния. Файл, в който е посочен разделът `includes`, има по-висок приоритет от включените в него файлове. +Ако в конфигурационните файлове се появят елементи с еднакви ключове, те ще бъдат презаписани или, в случай на [масиви, слети |#Сливане]. Файлът, включен по-късно, има по-висок приоритет от предишния. Файлът, в който е посочена секцията `includes`, има по-висок приоритет от файловете, включени в него. -Търсене за .[#toc-search] -========================= +Search +====== -Автоматичното добавяне на услуги към контейнера DI го прави изключително удобен за работа. Nette автоматично добавя водещи към контейнера, но можете лесно да добавите и други класове. +Автоматичното добавяне на сървиси към DI контейнера прави работата изключително приятна. Nette автоматично добавя презентери към контейнера, но можете лесно да добавяте и всякакви други класове. -Просто посочете в кои директории (и поддиректории) трябва да се търсят класовете: +Достатъчно е да посочите в кои директории (и поддиректории) да търси класове: ```neon -търсене: - # избирате собствени имена на раздели - myForms: - in: %appDir%/Forms - - model: - in: %appDir%/Model +search: + - in: %appDir%/Forms + - in: %appDir%/Model ``` -Обикновено обаче не искаме да добавяме всички класове и интерфейси, затова можем да ги филтрираме: +Обикновено обаче не искаме да добавяме абсолютно всички класове и интерфейси, така че можем да ги филтрираме: ```neon -търсене: - myForms: - in: %appDir%/Forms +search: + - in: %appDir%/Forms # филтриране по име на файл (string|string[]) files: @@ -215,42 +223,43 @@ return [ - *Factory ``` -Или можем да изберем класове, които наследяват или имплементират поне един от следните класове: +Или можем да изберем класове, които наследяват или имплементират поне един от изброените класове: ```neon search: - myForms: + - in: %appDir% extends: - App\*Form implements: - App\*FormInterface ``` -Можете също така да дефинирате отрицателни правила, т.е. маски за имена на класове или предци, и ако те отговарят на изискванията, услугата няма да бъде добавена към контейнера DI: +Могат да се дефинират и изключващи правила, т.е. маски на имена на класове или наследствени предци, които, ако съвпадат, сървисът няма да бъде добавен към DI контейнера: ```neon search: - myForms: + - in: %appDir% exclude: + files: ... classes: ... extends: ... implements: ... ``` -Могат да се дефинират етикети за допълнителни услуги: +На всички сървиси могат да се зададат тагове: ```neon search: - myForms: + - in: %appDir% tags: ... ``` -Асоциация .[#toc-merging] -========================= +Сливане +======= -Ако елементи с еднакви ключове се появят в няколко конфигурационни файла, те ще бъдат презаписани или обединени в случай на масиви. По-късно включеният файл е с по-висок приоритет. +Ако в няколко конфигурационни файла се появят елементи с еднакви ключове, те ще бъдат презаписани или, в случай на масиви, слети. Файлът, включен по-късно, има по-висок приоритет от предишния. <table class=table> <tr> @@ -283,7 +292,7 @@ items: </tr> </table> -За да предотвратите присъединяването на определен масив, използвайте възклицателен знак непосредствено след името на масива: +При масивите сливането може да бъде предотвратено чрез добавяне на удивителен знак след името на ключа: <table class=table> <tr> @@ -313,3 +322,5 @@ items: </td> </tr> </table> + +{{maintitle: Конфигурация на Dependency Injection}} diff --git a/dependency-injection/bg/container.texy b/dependency-injection/bg/container.texy index 7397aa3090..dc3ae58a36 100644 --- a/dependency-injection/bg/container.texy +++ b/dependency-injection/bg/container.texy @@ -1,16 +1,16 @@ -Какво е контейнер DI? +Какво е DI контейнер? ********************* .[perex] -Контейнер за изпълнение на зависимости (DIC) е клас, който може да инстанцира и конфигурира обекти. +Dependency injection контейнерът (DIC) е клас, който може да инстанцира и конфигурира обекти. -Това може да ви изненада, но в много случаи не се нуждаете от контейнер за инжектиране на зависимости, за да се възползвате от предимствата на инжектирането на зависимости (накратко DI). В края на краищата дори в [предишната глава |introduction] показахме конкретни примери за DI и не беше необходим контейнер. +Може да ви изненада, но в много случаи не се нуждаете от dependency injection контейнер, за да се възползвате от предимствата на dependency injection (накратко DI). В края на краищата, дори в [уводната глава|introduction] показахме DI с конкретни примери и не беше необходим контейнер. -Ако обаче трябва да управлявате много различни обекти с много зависимости, контейнер за инжектиране на зависимости би бил наистина полезен. Такъв може да е случаят с уеб приложения, изградени на базата на рамка. +Въпреки това, ако трябва да управлявате голям брой различни обекти с много зависимости, dependency injection контейнерът ще бъде наистина полезен. Такъв е случаят например с уеб приложения, изградени върху framework. -В предишната глава се запознахме с класовете `Article` и `UserController`. И двете имат някои зависимости, а именно базата данни и фабриката `ArticleFactory`. И за тези класове сега ще създадем контейнер. Разбира се, няма смисъл да се използва контейнер за такъв прост пример. Но ние ще създадем такъв, за да покажем как изглежда и работи. +В предишната глава представихме класовете `Article` и `UserController`. И двата имат някои зависимости, а именно база данни и фабриката `ArticleFactory`. И сега ще създадем контейнер за тези класове. Разбира се, за толкова прост пример няма смисъл да имаме контейнер. Но ще го създадем, за да покажем как изглежда и работи. -Ето един прост контейнер с твърд код за горния пример: +Ето един прост hardcoded контейнер за дадения пример: ```php class Container @@ -32,16 +32,16 @@ class Container } ``` -Използването му ще изглежда по следния начин +Използването би изглеждало така: ```php $container = new Container; $controller = $container->createUserController(); ``` -Просто заявяваме обект от контейнера и не е необходимо да знаем как да го създадем или какви са неговите зависимости - контейнерът знае всичко това. Зависимостите се въвеждат автоматично от контейнера. В това е неговата сила. +Просто питаме контейнера за обект и вече не е нужно да знаем нищо за това как да го създадем или какви са неговите зависимости; контейнерът знае всичко това. Зависимостите се инжектират автоматично от контейнера. В това е неговата сила. -Досега всичко в контейнера беше кодирано с твърд код. Затова ще направим следващата стъпка и ще добавим параметри, за да направим контейнера наистина полезен: +Засега контейнерът има всички данни, записани hardcoded. Така че ще направим следващата стъпка и ще добавим параметри, за да направим контейнера наистина полезен: ```php class Container @@ -70,9 +70,9 @@ $container = new Container([ ]); ``` -Внимателните читатели може би са забелязали проблем. Всеки път, когато получавам обект `UserController`, се създава и нова инстанция на `ArticleFactory` и базата данни. Със сигурност не искаме това. +Наблюдателните читатели може би са забелязали определен проблем. Всеки път, когато получа обект `UserController`, се създава и нова инстанция на `ArticleFactory` и базата данни. Определено не искаме това. -Затова добавяме метод `getService()`, който ще връща едни и същи екземпляри отново и отново: +Затова ще добавим метод `getService()`, който винаги ще връща едни и същи инстанции: ```php class Container @@ -87,7 +87,7 @@ class Container public function getService(string $name): object { if (!isset($this->services[$name])) { - // getService('Database') предизвиква createDatabase() + // getService('Database') ще извика createDatabase() $method = 'create' . $name; $this->services[$name] = $this->$method(); } @@ -98,9 +98,9 @@ class Container } ``` -Първото извикване на например `$container->getService('database')` ще създаде обект от базата данни, който ще съхрани в масива `$services` и ще върне директно при следващото извикване. +При първото извикване, например `$container->getService('Database')`, той ще накара `createDatabase()` да създаде обект на базата данни, ще го съхрани в масива `$services` и ще го върне директно при следващото извикване. -Променяме и останалата част от контейнера, за да използва `getService()': +Ще модифицираме и останалата част от контейнера, за да използва `getService()`: ```php class Container @@ -119,9 +119,9 @@ class Container } ``` -Между другото, терминът "услуга" се отнася за всеки обект, управляван от контейнера. Оттук идва и името на метода `getService()`. +Между другото, терминът сървис се отнася до всеки обект, управляван от контейнера. Оттук и името на метода `getService()`. -Имаме напълно функционален контейнер за DI! И можем да го използваме. +Готово. Имаме напълно функционален DI контейнер! И можем да го използваме: ```php $container = new Container([ @@ -134,6 +134,9 @@ $controller = $container->getService('UserController'); $database = $container->getService('Database'); ``` -Както виждате, не е трудно да се напише DIC. Забележително е, че самите обекти не знаят, че контейнерът ги създава. По този начин можете да създавате всякакви обекти в PHP, без да засягате изходния код. +Както виждате, написването на DIC не е сложно. Струва си да се отбележи, че самите обекти не знаят, че се създават от някакъв контейнер. Следователно е възможно да се създаде по този начин всеки PHP обект, без да се променя неговият изходен код. -Ръчното създаване и поддържане на контейнерен клас може бързо да се превърне в кошмар. Затова в следващата глава ще разгледаме [контейнера Nette DI |nette-container], който може да се генерира и актуализира почти автоматично. +Ръчното създаване и поддръжка на клас контейнер може бързо да се превърне в кошмар. Затова в следващата глава ще говорим за [Nette DI Container|nette-container], който може да се генерира и актуализира почти сам. + + +{{maintitle: Какво е dependency injection контейнер?}} diff --git a/dependency-injection/bg/extensions.texy b/dependency-injection/bg/extensions.texy index ce13c90c1b..d38bb6e373 100644 --- a/dependency-injection/bg/extensions.texy +++ b/dependency-injection/bg/extensions.texy @@ -2,19 +2,19 @@ *********************************** .[perex] -Създаването на контейнер DI в допълнение към конфигурационните файлове засяга и така наречените *разширения*. Активираме ги в конфигурационния файл в раздела `extensions`. +Генерирането на DI контейнера, освен от конфигурационните файлове, се влияе и от така наречените *разширения*. Активираме ги в конфигурационния файл в секцията `extensions`. -По този начин добавяме разширението, представено от класа `BlogExtension` с име `blog`: +По този начин добавяме разширение, представено от класа `BlogExtension`, под името `blog`: ```neon extensions: blog: BlogExtension ``` -Всяко разширение на компилатора наследява от [api:Nette\DI\CompilerExtension] и може да реализира следните методи, които се извикват при компилирането на DI: +Всяко разширение на компилатора наследява от [api:Nette\DI\CompilerExtension] и може да имплементира следните методи, които се извикват последователно по време на изграждането на DI контейнера: 1. getConfigSchema() -2. зареждане на конфигурацията (loadConfiguration()) +2. loadConfiguration() 3. beforeCompile() 4. afterCompile() @@ -22,18 +22,18 @@ extensions: getConfigSchema() .[method] =========================== -Този метод се извиква първи. Той определя схемата, използвана за проверка на параметрите на конфигурацията. +Този метод се извиква пръв. Той дефинира схема за валидиране на конфигурационните параметри. -Разширенията се конфигурират в раздел, чието име е същото като името, под което е добавено разширението, например `blog`. +Конфигурираме разширението в секция, чието име е същото като това, под което е добавено разширението, т.е. `blog`: ```neon -# е същото име като разширението +# същото име като разширението blog: postsPerPage: 10 - comments: false + allowComments: false ``` -Ще дефинираме схема, описваща всички параметри на конфигурацията, включително техните типове, приети стойности и евентуално стойности по подразбиране: +Създаваме схема, описваща всички опции за конфигурация, включително техните типове, разрешени стойности и евентуално стойности по подразбиране: ```php use Nette\Schema\Expect; @@ -50,9 +50,9 @@ class BlogExtension extends Nette\DI\CompilerExtension } ``` -За информация вижте [Схема |schema:]. Възможно е също така да определите кои опции могат да бъдат [динамични |application:bootstrap#Dynamic-Parameters] с помощта на `dynamic()', например `Expect::int()->dynamic()`. +Документацията можете да намерите на страницата [Schema |schema:]. Освен това можете да посочите кои опции могат да бъдат [динамични |application:bootstrapping#Динамични параметри] с помощта на `dynamic()`, напр. `Expect::int()->dynamic()`. -Достъпът до конфигурацията се осъществява чрез `$this->config`, който е обектът `stdClass`: +Достъпваме конфигурацията чрез променливата `$this->config`, която е обект `stdClass`: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -71,7 +71,7 @@ class BlogExtension extends Nette\DI\CompilerExtension loadConfiguration() .[method] ============================= -Този метод се използва за добавяне на услуги към контейнера. Това се прави с помощта на [api:Nette\DI\ContainerBuilder]: +Използва се за добавяне на сървиси към контейнера. За това служи [api:Nette\DI\ContainerBuilder]: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -86,19 +86,19 @@ class BlogExtension extends Nette\DI\CompilerExtension } ``` -Уговорката е услугите, добавени от дадено разширение, да се префиксират с неговото име, така че да не възникват конфликти на имена. Това се прави с помощта на `prefix()', так что если расширение называется 'blog', то служба будет называться `blog.articles`. +Конвенцията е сървисите, добавени от разширение, да се префиксират с неговото име, за да се избегнат конфликти на имена. Това прави методът `prefix()`, така че ако разширението се нарича `blog`, сървисът ще носи името `blog.articles`. -Ако трябва да преименуваме дадена услуга, можем да създадем псевдоним с оригиналното ѝ име, за да запазим обратната съвместимост. Nette прави същото за `routing.router`, който се предлага и под по-ранното име `router`. +Ако трябва да преименуваме сървис, можем да създадем псевдоним с оригиналното име, за да запазим обратната съвместимост. Nette прави нещо подобно, например със сървиса `routing.router`, който е достъпен и под предишното име `router`. ```php $builder->addAlias('router', 'routing.router'); ``` -Извличане на услуги от файла .[#toc-retrieve-services-from-a-file] ------------------------------------------------------------------- +Зареждане на сървиси от файл +---------------------------- -Можем да създаваме услуги с помощта на API на ContainerBuilder, но можем да ги добавяме и чрез познатия конфигурационен файл NEON и неговия раздел `services`. Префиксът `@extension` представлява текущото разширение. +Не е необходимо да създаваме сървиси само с помощта на API на класа ContainerBuilder, но и с познатия синтаксис, използван в конфигурационния файл NEON в секцията services. Префиксът `@extension` представлява текущото разширение. ```neon services: @@ -112,7 +112,7 @@ services: create: MyBlog\Components\ArticlesList(@extension.articles) ``` -Ще добавяме услуги по този начин: +Зареждаме сървисите: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -121,7 +121,7 @@ class BlogExtension extends Nette\DI\CompilerExtension { $builder = $this->getContainerBuilder(); - //зареждане на конфигурационен файл за разширението + // зареждане на конфигурационния файл за разширението $this->compiler->loadDefinitionsFromConfig( $this->loadFromFile(__DIR__ . '/blog.neon')['services'], ); @@ -133,7 +133,7 @@ class BlogExtension extends Nette\DI\CompilerExtension beforeCompile() .[method] ========================= -Методът се извиква, когато контейнерът съдържа всички услуги, добавени от отделните разширения в методите на `loadConfiguration`, както и конфигурационните файлове на потребителите. На този етап от изграждането можем да променяме дефинициите на услугите или да добавяме връзки между тях. Можем да използваме метода `findByTag()`, за да търсим услуги по тагове , или метода `findByType()`, за да търсим по клас или интерфейс. +Методът се извиква, когато контейнерът съдържа всички сървиси, добавени от отделните разширения в методите `loadConfiguration`, както и от потребителските конфигурационни файлове. Следователно на този етап от изграждането можем да модифицираме дефинициите на сървисите или да добавим връзки между тях. За търсене на сървиси в контейнера по тагове може да се използва методът `findByTag()`, а по клас или интерфейс - методът `findByType()`. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -153,7 +153,7 @@ class BlogExtension extends Nette\DI\CompilerExtension afterCompile() .[method] ======================== -В този момент контейнерният клас вече е генериран като обект [ClassType |php-generator:#Classes], съдържа всички методи, които услугата създава, и е готов да бъде кеширан като PHP файл. В този момент можем да редактираме кода на класа. +На този етап класът на контейнера вече е генериран под формата на обект [ClassType |php-generator:#Класове], съдържа всички методи, които създават сървиси, и е готов за запис в кеша. Все още можем да модифицираме получения код на класа на този етап. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -167,24 +167,24 @@ class BlogExtension extends Nette\DI\CompilerExtension ``` -$инициализация .[wiki-method] -============================= +$initialization .[method] +========================= -Конфигураторът се извиква от кода за инициализация след [създаването на контейнер |application:bootstrap#index-php], който се създава чрез запис в обекта `$this->initialization` с помощта на [метода addBody() |php-generator:#method-and-function-body]. +Класът Configurator, след [създаване на контейнера |application:bootstrapping#index.php], извиква инициализационен код, който се създава чрез запис в обекта `$this->initialization` с помощта на [метода addBody() |php-generator:#Тела на методи и функции]. -Ще покажем пример за това как да стартирате сесия или услуги, които имат таг `run`, като използвате кода за инициализация: +Ще покажем пример как да стартирате сесия или да стартирате сървиси, които имат таг `run`, с помощта на инициализационен код: ```php class BlogExtension extends Nette\DI\CompilerExtension { public function loadConfiguration() { - // автоматично стартиране на сесия + // автоматично стартиране на сесията if ($this->config->session->autoStart) { $this->initialization->addBody('$this->getService("session")->start()'); } - // услугите с таг 'run' трябва да бъдат създадени след инстанцирането на контейнера + // сървисите с таг run трябва да бъдат създадени след инстанциране на контейнера $builder = $this->getContainerBuilder(); foreach ($builder->findByTag('run') as $name => $foo) { $this->initialization->addBody('$this->getService(?);', [$name]); diff --git a/dependency-injection/bg/factory.texy b/dependency-injection/bg/factory.texy index d08862320c..eec7b3abf4 100644 --- a/dependency-injection/bg/factory.texy +++ b/dependency-injection/bg/factory.texy @@ -2,11 +2,11 @@ ****************** .[perex] -Nette DI може автоматично да генерира фабричен код въз основа на интерфейса, като ви спестява писането на код. +Nette DI може автоматично да генерира код на фабрики въз основа на интерфейси, което ви спестява писане на код. -Фабриката е клас, който създава и конфигурира обекти. Следователно той предава на тях и техните зависимости. Моля, не бъркайте с шаблона за проектиране *фабричен метод*, който описва специфичен начин за използване на фабрики и не е свързан с тази тема. +Фабриката е клас, който произвежда и конфигурира обекти. Следователно тя им предава и техните зависимости. Моля, не бъркайте с дизайн патърна *factory method*, който описва специфичен начин за използване на фабрики и не е свързан с тази тема. -Показахме как изглежда една такава фабрика в [уводната глава |introduction#factory]: +Как изглежда такава фабрика, показахме в [уводната глава |introduction#Фабрика]: ```php class ArticleFactory @@ -23,7 +23,7 @@ class ArticleFactory } ``` -Всичко, което трябва да направите, е да създадете интерфейс и Nette DI ще генерира неговата реализация. Интерфейсът трябва да има точно един метод с име `create` и да декларира тип на връщане: +Nette DI може автоматично да генерира код на фабрики. Всичко, което трябва да направите, е да създадете интерфейс и Nette DI ще генерира имплементацията. Интерфейсът трябва да има точно един метод с име `create` и да декларира тип на връщане: ```php interface ArticleFactory @@ -32,7 +32,7 @@ interface ArticleFactory } ``` -И така, фабриката `ArticleFactory` има метод `create`, който създава обекти `Article`. Един клас `Article` може да изглежда по следния начин, например +Така фабриката `ArticleFactory` има метод `create`, който създава обекти `Article`. Класът `Article` може да изглежда например така: ```php class Article @@ -44,16 +44,16 @@ class Article } ``` -Добавете фабриката към конфигурационния файл: +Добавяме фабриката към конфигурационния файл: ```neon services: - ArticleFactory ``` -Nette DI ще създаде подходяща фабрична реализация. +Nette DI ще генерира съответната имплементация на фабриката. -По този начин в кода, използващ фабриката, правим запитване към обекта по интерфейс, а Nette DI използва генерираната имплементация: +В кода, който използва фабриката, изискваме обект по интерфейс и Nette DI ще използва генерираната имплементация: ```php class UserController @@ -65,17 +65,17 @@ class UserController public function foo() { - // нека фабриката създаде обект + // оставяме фабриката да създаде обект $article = $this->articleFactory->create(); } } ``` -Параметризирана фабрика .[#toc-parameterized-factory] -===================================================== +Параметризирана фабрика +======================= -Фабричният метод `create` може да приема параметри, които след това предава на конструктора. Например нека добавим идентификатор на автор на статия към класа `Article`: +Фабричният метод `create` може да приема параметри, които след това предава на конструктора. Нека добавим например ID на автора на статията към класа `Article`: ```php class Article @@ -88,7 +88,7 @@ class Article } ``` -Ще добавим и параметър към фабриката: +Добавяме параметъра и към фабриката: ```php interface ArticleFactory @@ -97,13 +97,13 @@ interface ArticleFactory } ``` -Тъй като параметърът в конструктора и параметърът във фабриката имат едно и също име, Nette DI ще ги предаде автоматично. +Тъй като параметърът в конструктора и параметърът във фабриката имат едно и също име, Nette DI ги предава напълно автоматично. -Разширено определение .[#toc-advanced-definition] -================================================= +Разширена дефиниция +=================== -Дефиницията може да бъде записана и в многоредова форма с помощта на клавиша `implement`: +Дефиницията може да бъде записана и в многоредов вид, като се използва ключът `implement`: ```neon services: @@ -111,9 +111,9 @@ services: implement: ArticleFactory ``` -Когато е написан по този удължен начин, е възможно да се предоставят допълнителни аргументи за конструктора в ключа `arguments` и допълнителна конфигурация с `setup`, точно както при нормалните услуги. +При писане по този по-дълъг начин е възможно да се посочат допълнителни аргументи за конструктора в ключа `arguments` и допълнителна конфигурация с помощта на `setup`, точно както при обикновените сървиси. -Пример: Ако методът `create()` не приема параметъра `$authorId`, бихме могли да посочим фиксирана стойност в конфигурацията, която ще бъде предадена на конструктора `Article`: +Пример: ако методът `create()` не приемаше параметъра `$authorId`, бихме могли да посочим фиксирана стойност в конфигурацията, която да бъде предадена на конструктора на `Article`: ```neon services: @@ -123,7 +123,7 @@ services: authorId: 123 ``` -Или обратното, ако `create()` приеме параметъра `$authorId`, но той не е част от конструктора и е предаден на метода `Article::setAuthorId()`, ще го посочим в раздела `setup`: +Или обратно, ако `create()` приемаше параметъра `$authorId`, но той не беше част от конструктора и се предаваше чрез метода `Article::setAuthorId()`, щяхме да се обърнем към него в секцията `setup`: ```neon services: @@ -134,15 +134,14 @@ services: ``` -Accessor .[#toc-accessor] -========================= +Accessor +======== -В допълнение към фабриките Nette може да генерира и така наречените аксесори. Това са обекти с метода `get()`, който връща конкретна услуга от DI-контейнера. Многократните извиквания на `get()` все още връщат една и съща инстанция. +Освен фабрики, Nette може да генерира и т.нар. аксесори. Това са обекти с метод `get()`, който връща определен сървис от DI контейнера. Повторното извикване на `get()` винаги връща същата инстанция. -Този аксесоар осигурява лениво зареждане на зависимостите. Да предположим, че имаме клас, който записва грешки в специална база данни. Ако този клас предаде на конструктора връзка към база данни като зависимост, тя винаги ще трябва да бъде създадена, въпреки че на практика много рядко ще възникне грешка и затова връзката обикновено ще остане неизползвана. -Вместо това класът предава метод за достъп и само когато той бъде извикан `get()`, се създава обект на базата данни: +Аксесорите осигуряват lazy-loading за зависимостите. Да приемем, че имаме клас, който записва грешки в специална база данни. Ако този клас получаваше връзката с базата данни като зависимост чрез конструктора, връзката винаги трябваше да се създава, въпреки че на практика грешка се появява само рядко и следователно връзката в повечето случаи би останала неизползвана. Вместо това класът получава аксесор и едва когато се извика неговият `get()`, се създава обектът на базата данни: -Как да създадем аксесоар? Просто напишете интерфейс и Nette DI ще генерира реализация. Интерфейсът трябва да има точно един метод с име `get` и да декларира тип на връщане: +Как да създадем аксесор? Просто напишете интерфейс и Nette DI ще генерира имплементацията. Интерфейсът трябва да има точно един метод с име `get` и да декларира тип на връщане: ```php interface PDOAccessor @@ -151,7 +150,7 @@ interface PDOAccessor } ``` -Ще добавим аксесоара към конфигурационния файл, който съдържа и дефиниция на услугата, която той ще връща: +Добавяме аксесора към конфигурационния файл, където е и дефиницията на сървиса, който той ще връща: ```neon services: @@ -159,63 +158,69 @@ services: - PDO(%dsn%, %user%, %password%) ``` -Тъй като методът за достъп връща услугата `PDO`, а в конфигурацията има само една такава услуга, той ще я върне. Ако има повече услуги от този тип, определяме върнатата услуга с име, например `- PDOAccessor(@db1)`. +Тъй като аксесорът връща сървис от тип `PDO` и в конфигурацията има само един такъв сървис, той ще върне точно него. Ако имаше повече сървиси от този тип, щяхме да посочим връщания сървис по име, напр. `- PDOAccessor(@db1)`. -Множество фабрики/оценители .[#toc-multifactory-accessor] -========================================================= -Досега нашите фабрики и аксесори винаги можеха да произвеждат или връщат само един обект. Въпреки това е много лесно да се създадат множество фабрики в комбинация с аксесори. Интерфейсът на такъв клас ще съдържа произволен брой методи, наречени `create<name>()` и `get<name>()`, напр: +Множествена фабрика/аксесор +=========================== +Досега нашите фабрики и аксесори винаги са можели да произвеждат или връщат само един обект. Въпреки това е много лесно да се създадат и множествени фабрики, комбинирани с аксесори. Интерфейсът на такъв клас ще съдържа произволен брой методи с имена `create<name>()` и `get<name>()`, напр.: ```php interface MultiFactory { function createArticle(): Article; - function createFoo(): Model\Foo; function getDb(): PDO; } ``` -Така че вместо да предадем няколко генерирани фабрики и аксесори, ще предадем друга сложна фабрика, която може да прави повече. +Така че, вместо да предаваме няколко генерирани фабрики и аксесори, предаваме една по-сложна фабрика, която може да прави повече неща. -Алтернативно, вместо няколко метода, можем да използваме параметрите `create()` и `get()`: +Алтернативно, вместо няколко метода, може да се използва `get()` с параметър: ```php interface MultiFactoryAlt { - function create($name); function get($name): PDO; } ``` -Тогава `MultiFactory::createArticle()` прави същото като `MultiFactoryAlt::create('article')`. Алтернативният запис обаче има недостатъка, че не е ясно кои стойности поддържа `$name` и не е логически възможно да се разграничат различните стойности на връщане за различните `$name` в интерфейса. +Тогава `MultiFactory::getArticle()` прави същото като `MultiFactoryAlt::get('article')`. Въпреки това, алтернативният запис има недостатъка, че не е ясно кои стойности на `$name` се поддържат и логично не е възможно да се разграничат различни върнати стойности за различни `$name` в интерфейса. -Определение за списък .[#toc-definition-with-a-list] ----------------------------------------------------- -И как да дефинираме множество фабрики в конфигурацията? Ще създадем три услуги за създаване/извличане и след това самата фабрика: +Дефиниция чрез списък +--------------------- +По този начин може да се дефинира множествена фабрика в конфигурацията: .{data-version:3.2.0} + +```neon +services: + - MultiFactory( + article: Article # дефинира createArticle() + db: PDO(%dsn%, %user%, %password%) # дефинира getDb() + ) +``` + +Или можем да се обърнем към съществуващи сървиси в дефиницията на фабриката чрез референция: ```neon services: article: Article - - Model\Foo - PDO(%dsn%, %user%, %password%) - MultiFactory( - article: @article # createArticle() - foo: @Model\Foo # createFoo() - db: @\PDO # getDb() + article: @article # дефинира createArticle() + db: @\PDO # дефинира getDb() ) ``` -Дефиниции с помощта на етикети .[#toc-definition-with-tags] ------------------------------------------------------------ +Дефиниция с помощта на тагове +----------------------------- -Вторият вариант е да използвате [тагове за |services#Tags] дефиниции: +Втората възможност е да се използват [тагове |services#Тагове] за дефиницията: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter - App\Model\DatabaseAccessor( - db1: @database.db1.context + db1: @database.db1.explorer ) ``` diff --git a/dependency-injection/bg/faq.texy b/dependency-injection/bg/faq.texy index 392c71c995..9c77b0168d 100644 --- a/dependency-injection/bg/faq.texy +++ b/dependency-injection/bg/faq.texy @@ -2,97 +2,91 @@ ********************************** -DI ли е другото име на IoC? .[#toc-is-di-another-name-for-ioc] --------------------------------------------------------------- +DI ли е друго име за IoC? +------------------------- -*Инверсия на контрола* (IoC) е принцип, насочен към начина, по който се изпълнява кодът - дали вашият код инициира външен код или вашият код е интегриран във външен код, който след това го извиква. -IoC е широка концепция, която включва [събития |nette:glossary#Events], така наречения [холивудски принцип |application:components#Hollywood style] и други аспекти. -Фабриките, които са част от [Правило № 3: Нека фабриката да се справи с него |introduction#Rule #3: Let the Factory Handle It], и представляват инверсия за оператора `new`, също са компоненти на тази концепция. +*Inversion of Control* (IoC) е принцип, фокусиран върху начина, по който се изпълнява кодът - дали вашият код изпълнява чужд код, или вашият код е интегриран в чужд код, който след това го извиква. IoC е широк термин, обхващащ [събития |nette:glossary#Събития events], така наречения [Холивудски принцип |application:components#Hollywood style] и други аспекти. Част от тази концепция са и фабриките, за които се говори в [Правило № 3: оставете го на фабриката |introduction#Правило 3: Остави го на фабриката], и които представляват инверсия за оператора `new`. -*Dependency Injection* (DI) е за това как един обект знае за друг обект, т.е. за зависимостта. Това е шаблон за проектиране, който изисква изрично предаване на зависимости между обектите. +*Dependency Injection* (DI) се фокусира върху начина, по който един обект научава за друг обект, т.е. за неговите зависимости. Това е дизайн патърн, който изисква изрично предаване на зависимости между обектите. -Следователно може да се каже, че DI е специфична форма на IoC. Не всички форми на IoC обаче са подходящи от гледна точка на чистотата на кода. Например сред антимоделите включваме всички техники, които работят с [глобално състояние |global state] или така наречения [Service Locator |#What is a Service Locator]. +Следователно може да се каже, че DI е специфична форма на IoC. Въпреки това, не всички форми на IoC са подходящи от гледна точка на чистотата на кода. Например, анти-патърните включват техники, които работят с [глобално състояние |global-state] или така наречения [Service Locator |#Какво е Service Locator]. -Какво представлява Service Locator? .[#toc-what-is-a-service-locator] ---------------------------------------------------------------------- +Какво е Service Locator? +------------------------ -Service Locator е алтернатива на Dependency Injection. Той работи чрез създаване на централно хранилище, в което се регистрират всички налични услуги или зависимости. Когато даден обект се нуждае от зависимост, той я заявява от Service Locator. +Това е алтернатива на Dependency Injection. Работи, като създава централно хранилище, където са регистрирани всички налични сървиси или зависимости. Когато обект се нуждае от зависимост, той я иска от Service Locator. -Въпреки това, в сравнение с Dependency Injection, той губи прозрачност: зависимостите не се предават директно на обектите и следователно не са лесно разпознаваеми, което изисква разглеждане на кода, за да се открият и разберат всички връзки. Тестването също така е по-сложно, тъй като не можем просто да предаваме подигравателни обекти на тестваните обекти, а трябва да преминем през Service Locator. Освен това Service Locator нарушава дизайна на кода, тъй като отделните обекти трябва да знаят за неговото съществуване, което се различава от Dependency Injection, при която обектите не знаят за контейнера DI. +В сравнение с Dependency Injection обаче, той губи прозрачност: зависимостите не се предават директно на обектите и не са толкова лесно идентифицируеми, което изисква преглед на кода, за да се разкрият и разберат всички връзки. Тестването също е по-сложно, тъй като не можем просто да предаваме mock обекти на тестваните обекти, а трябва да го правим чрез Service Locator. Освен това, Service Locator нарушава дизайна на кода, тъй като отделните обекти трябва да знаят за неговото съществуване, което е различно от Dependency Injection, където обектите нямат представа за DI контейнера. -Кога е по-добре да не се използва DI? .[#toc-when-is-it-better-not-to-use-di] ------------------------------------------------------------------------------ +Кога е по-добре да не се използва DI? +------------------------------------- -Не са известни трудности, свързани с използването на шаблона за проектиране Dependency Injection. Напротив, получаването на зависимости от глобално достъпни места води до [редица усложнения, |global-state] както и използването на Service Locator. -Затова е препоръчително винаги да използвате DI. Това не е догматичен подход, но просто не е намерена по-добра алтернатива. +Не са известни трудности, свързани с използването на дизайн патърна Dependency Injection. Напротив, получаването на зависимости от глобално достъпни места води до [цяла поредица от усложнения |global-state], както и използването на Service Locator. Затова е препоръчително винаги да се използва DI. Това не е догматичен подход, а просто не е намерена по-добра алтернатива. -Въпреки това има някои ситуации, в които не предаваме обекти един на друг и ги получаваме от глобалното пространство. Например при отстраняване на грешки в кода, когато е необходимо да се изведе стойност на променлива в определен момент от програмата, да се измери продължителността на определена част от програмата или да се регистрира съобщение. -В такива случаи, когато става въпрос за временни действия, които по-късно ще бъдат премахнати от кода, е закономерно да се използва глобално достъпен дъмпер, хронометър или логер. В крайна сметка тези инструменти не принадлежат към дизайна на кода. +Въпреки това съществуват определени ситуации, в които не предаваме обекти, а ги получаваме от глобалното пространство. Например, при дебъгване на код, когато трябва да изведете стойността на променлива в определена точка от програмата, да измерите продължителността на определена част от програмата или да запишете съобщение. В такива случаи, когато става въпрос за временни действия, които по-късно ще бъдат премахнати от кода, е легитимно да се използва глобално достъпен dumper, хронометър или logger. Тези инструменти всъщност не принадлежат към дизайна на кода. -Има ли използването на DI своите недостатъци? .[#toc-does-using-di-have-its-drawbacks] --------------------------------------------------------------------------------------- +Има ли използването на DI своите недостатъци? +--------------------------------------------- -Използването на Dependency Injection има ли някакви недостатъци, като например по-голяма сложност при писането на код или по-лоша производителност? Какво губим, когато започнем да пишем код в съответствие с DI? +Носи ли използването на Dependency Injection някакви недостатъци, като например повишена сложност при писане на код или влошена производителност? Какво губим, когато започнем да пишем код в съответствие с DI? -DI не оказва влияние върху производителността на приложението или изискванията за памет. Производителността на DI контейнера може да играе роля, но в случая на [Nette DI | nette-container] контейнерът е компилиран в чист PHP, така че натоварването му по време на изпълнение на приложението е по същество нулево. +DI не влияе на производителността или изискванията за памет на приложението. Производителността на DI Container-а може да играе известна роля, но в случая на [Nette DI |nette-container], контейнерът се компилира в чист PHP, така че неговата режия по време на изпълнение на приложението е практически нулева. -При писане на код е необходимо да се създадат конструктори, които приемат зависимости. В миналото това можеше да отнеме много време, но благодарение на съвременните IDE и [промотирането на свойствата на конструкторите |https://blog.nette.org/bg/php-8-0-p-len-pregled-na-novostite#toc-constructor-property-promotion] сега е въпрос на няколко секунди. Конструкторите могат да се генерират лесно с помощта на Nette DI и приставка за PhpStorm само с няколко кликвания. -От друга страна, не е необходимо да се пишат синглетони и статични точки за достъп. +При писане на код често е необходимо да се създават конструктори, приемащи зависимости. Преди това можеше да бъде досадно, но благодарение на модерните IDE и [constructor property promotion |https://blog.nette.org/bg/php-8-0-complete-overview-of-news#toc-constructor-property-promotion], сега това е въпрос на няколко секунди. Фабриките могат лесно да се генерират с помощта на Nette DI и плъгин за PhpStorm с едно кликване на мишката. От друга страна, отпада необходимостта от писане на сингълтъни и статични точки за достъп. -Може да се заключи, че едно правилно проектирано приложение, използващо DI, не е нито по-кратко, нито по-дълго в сравнение с приложение, използващо синглетони. Частите от кода, работещи със зависимости, просто се извличат от отделните класове и се преместват на нови места, т.е. в контейнера и фабриките на DI. +Може да се каже, че правилно проектирано приложение, използващо DI, не е нито по-кратко, нито по-дълго в сравнение с приложение, използващо сингълтъни. Частите от кода, работещи със зависимости, са просто извадени от отделните класове и преместени на нови места, т.е. в DI контейнера и фабриките. -Как да пренапишем наследено приложение към DI? .[#toc-how-to-rewrite-a-legacy-application-to-di] ------------------------------------------------------------------------------------------------- +Как да пренапишем legacy приложение към DI? +------------------------------------------- -Мигрирането от наследено приложение към Dependency Injection може да бъде труден процес, особено за големи и сложни приложения. Важно е към този процес да се подходи систематично. +Преходът от legacy приложение към Dependency Injection може да бъде предизвикателен процес, особено при големи и сложни приложения. Важно е да се подходи към този процес систематично. -- При преминаването към Dependency Injection е важно всички членове на екипа да разбират принципите и практиките, които се използват. -- Първо, направете анализ на съществуващото приложение, за да идентифицирате ключовите компоненти и техните зависимости. Създайте план за това кои части ще бъдат префактурирани и в какъв ред. -- Реализирайте контейнер за DI или, още по-добре, използвайте съществуваща библиотека като Nette DI. -- Постепенно преработете всяка част от приложението, за да използвате впръскване на зависимости. Това може да включва модифициране на конструктори или методи, за да приемат зависимости като параметри. -- Променете местата в кода, където се създават обекти на зависимости, така че вместо това зависимостите да се инжектират от контейнера. Това може да включва използването на фабрики. +- При преминаване към Dependency Injection е важно всички членове на екипа да разбират принципите и процедурите, които се използват. +- Първо, направете анализ на съществуващото приложение и идентифицирайте ключовите компоненти и техните зависимости. Създайте план кои части ще бъдат рефакторирани и в какъв ред. +- Имплементирайте DI контейнер или още по-добре, използвайте съществуваща библиотека, например Nette DI. +- Постепенно рефакторирайте отделните части на приложението, за да използват Dependency Injection. Това може да включва промени в конструкторите или методите, така че да приемат зависимости като параметри. +- Модифицирайте местата в кода, където се създават обекти със зависимости, така че вместо това зависимостите да се инжектират от контейнера. Това може да включва използването на фабрики. -Не забравяйте, че преминаването към инжектиране на зависимости е инвестиция в качеството на кода и дългосрочната устойчивост на приложението. Въпреки че може да е предизвикателство да се направят тези промени, резултатът трябва да бъде по-чист, по-модулен и лесно тестваем код, който е готов за бъдещи разширения и поддръжка. +Помнете, че преходът към Dependency Injection е инвестиция в качеството на кода и дългосрочната поддръжка на приложението. Въпреки че може да е предизвикателство да се направят тези промени, резултатът трябва да бъде по-чист, по-модулен и лесно тестваем код, който е готов за бъдещи разширения и поддръжка. -Защо композицията е за предпочитане пред наследяването? .[#toc-why-composition-is-preferred-over-inheritance] -------------------------------------------------------------------------------------------------------------- -За предпочитане е да се използва композиция, а не наследяване, тъй като тя служи за целите на повторната употреба на кода, без да е необходимо да се притеснявате за ефекта от промяната. По този начин се осигурява по-свободно свързване, при което не е необходимо да се притесняваме, че промяната на някакъв код ще доведе до промяна на друг зависим код. Типичен пример за това е ситуацията, наречена [ад на конструкторите |passing-dependencies#Constructor hell]. +Защо се предпочита композиция пред наследяването? +------------------------------------------------- +По-подходящо е да се използва [композиция |nette:introduction-to-object-oriented-programming#Композиция] вместо [наследяване |nette:introduction-to-object-oriented-programming#Наследяване], тъй като тя служи за повторно използване на код, без да се налага да се притесняваме за последствията от промените. Следователно тя осигурява по-слаба връзка, при която не трябва да се притесняваме, че промяната на някой код ще доведе до необходимост от промяна на друг зависим код. Типичен пример е ситуацията, наречена [constructor hell |passing-dependencies#Адът на конструктора]. -Може ли Nette DI Container да се използва извън Nette? .[#toc-can-nette-di-container-be-used-outside-of-nette] --------------------------------------------------------------------------------------------------------------- +Може ли да се използва Nette DI Container извън Nette? +------------------------------------------------------ -Абсолютно. Nette DI Container е част от Nette, но е проектирана като самостоятелна библиотека, която може да се използва независимо от други части на рамката. Просто я инсталирайте с помощта на Composer, създайте конфигурационен файл, определящ вашите услуги, и след това използвайте няколко реда PHP код, за да създадете DI контейнера. -И веднага можете да започнете да се възползвате от предимствата на Dependency Injection във вашите проекти. +Определено. Nette DI Container е част от Nette, но е проектиран като самостоятелна библиотека, която може да се използва независимо от другите части на framework-а. Просто го инсталирайте с помощта на Composer, създайте конфигурационен файл с дефиницията на вашите сървиси и след това използвайте няколко реда PHP код, за да създадете DI контейнера. И веднага можете да започнете да се възползвате от предимствата на Dependency Injection във вашите проекти. -Главата [Nette DI Container |nette-container] описва как изглежда конкретен случай на употреба, включително кода. +Как изглежда конкретното използване, включително кодове, е описано в главата [Nette DI Container |nette-container]. -Защо конфигурацията е във файлове NEON? .[#toc-why-is-the-configuration-in-neon-files] --------------------------------------------------------------------------------------- +Защо е конфигурацията в NEON файлове? +------------------------------------- -NEON е прост и лесен за четене език за конфигуриране, разработен в Nette, за настройка на приложения, услуги и техните зависимости. В сравнение с JSON или YAML той предлага много по-интуитивни и гъвкави възможности за тази цел. В NEON можете по естествен начин да описвате връзки, които не биха били възможни да се напишат в Symfony & YAML или изобщо, или само чрез сложно описание. +NEON е прост и лесен за четене конфигурационен език, разработен в рамките на Nette за настройка на приложения, сървиси и техните зависимости. В сравнение с JSON или YAML, той предлага много по-интуитивни и гъвкави опции за тази цел. В NEON могат естествено да се опишат връзки, които в Symfony & YAMLu би било невъзможно да се запишат изобщо или само чрез сложно описание. -Обработката на NEON файловете забавя ли приложението? .[#toc-does-parsing-neon-files-slow-down-the-application] ---------------------------------------------------------------------------------------------------------------- +Не забавя ли приложението парсването на NEON файлове? +----------------------------------------------------- -Въпреки че файловете NEON се анализират много бързо, този аспект няма особено значение. Причината е, че парсирането на файловете се извършва само веднъж при първото стартиране на приложението. След това кодът на DI контейнера се генерира, съхранява се на диска и се изпълнява за всяка следваща заявка, без да е необходимо допълнително разбор. +Въпреки че NEON файловете се парсват много бързо, на този аспект изобщо няма значение. Причината е, че парсването на файловете се извършва само веднъж при първото стартиране на приложението. След това се генерира кодът на DI контейнера, записва се на диска и се изпълнява при всяка следваща заявка, без да е необходимо допълнително парсване. -Това е начинът, по който се работи в производствена среда. По време на разработката NEON файловете се анализират всеки път, когато съдържанието им се промени, което гарантира, че разработчикът винаги разполага с актуален DI контейнер. Както беше споменато по-рано, действителното парсване е въпрос на един миг. +Така работи в продукционна среда. По време на разработка NEON файловете се парсват всеки път, когато съдържанието им се промени, така че разработчикът винаги да има актуален DI контейнер. Самото парсване е, както беше споменато, въпрос на момент. -Как да получа достъп до параметрите от конфигурационния файл в моя клас? .[#toc-how-do-i-access-the-parameters-from-the-configuration-file-in-my-class] -------------------------------------------------------------------------------------------------------------------------------------------------------- +Как да получа достъп до параметрите в конфигурационния файл от моя клас? +------------------------------------------------------------------------ -Имайте предвид [Правило № 1: Позволете да ви бъде предадено |introduction#Rule #1: Let It Be Passed to You]. Ако даден клас изисква информация от конфигурационен файл, не е необходимо да измисляме как да получим достъп до тази информация; вместо това просто я изискваме - например чрез конструктора на класа. И извършваме предаването в конфигурационния файл. +Нека си припомним [Правило № 1: нека ти го предадат |introduction#Правило 1: Нека ви го предадат]. Ако класът изисква информация от конфигурационния файл, не е нужно да мислим как да стигнем до тази информация, вместо това просто я искаме - например чрез конструктора на класа. И осъществяваме предаването в конфигурационния файл. -В този пример `%myParameter%` е заместител на стойността на параметъра `myParameter`, който ще бъде предаден на конструктора `MyClass`: +В този пример `%myParameter%` е placeholder за стойността на параметъра `myParameter`, която се предава на конструктора на класа `MyClass`: ```php # config.neon @@ -103,10 +97,10 @@ services: - MyClass(%myParameter%) ``` -Ако искате да предадете няколко параметъра или да използвате автоматично свързване, е полезно да [обвиете параметрите в обект |best-practices:passing-settings-to-presenters]. +Ако искате да предавате повече параметри или да използвате autowiring, е препоръчително [да опаковате параметрите в обект |best-practices:passing-settings-to-presenters]. -Поддържа ли Nette интерфейса PSR-11 Container? .[#toc-does-nette-support-psr-11-container-interface] ----------------------------------------------------------------------------------------------------- +Поддържа ли Nette PSR-11: Container interface? +---------------------------------------------- -Nette DI Container не поддържа PSR-11 директно. Въпреки това, ако се нуждаете от оперативна съвместимост между Nette DI Container и библиотеки или рамки, които очакват PSR-11 Container Interface, можете да създадете [прост адаптер |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f], който да служи като мост между Nette DI Container и PSR-11. +Nette DI Container не поддържа директно PSR-11. Въпреки това, ако се нуждаете от оперативна съвместимост между Nette DI Container-а и библиотеки или framework-ове, които очакват PSR-11 Container Interface, можете да създадете [прост адаптер |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f], който ще служи като мост между Nette DI Container-а и PSR-11. diff --git a/dependency-injection/bg/global-state.texy b/dependency-injection/bg/global-state.texy index bbb5bbc5ba..b4bf3be4e8 100644 --- a/dependency-injection/bg/global-state.texy +++ b/dependency-injection/bg/global-state.texy @@ -1,67 +1,63 @@ -Глобално състояние и единични числа -*********************************** +Глобално състояние и сингълтъни +******************************* .[perex] -Предупреждение: Следните конструкции са симптоми на лошо проектиран код: +Предупреждение: Следните конструкции са признак на лошо проектиран код: - `Foo::getInstance()` - `DB::insert(...)` - `Article::setDb($db)` - `ClassName::$var` или `static::$var` -Срещате ли някои от тези конструкции в кода си? Ако е така, имате възможност да го подобрите. Може би си мислите, че това са общи конструкции, които често се срещат в примерни решения на различни библиотеки и фреймуърки. Ако случаят е такъв, дизайнът на техния код е погрешен. +Срещат ли се някои от тези конструкции във вашия код? Тогава имате възможност да го подобрите. Може би си мислите, че това са обичайни конструкции, които виждате дори в примерни решения на различни библиотеки и framework-ове. Ако е така, тогава дизайнът на техния код не е добър. -Тук не става дума за някаква академична чистота. Всички тези конструкции имат една обща черта: използват глобално състояние. А това оказва разрушително въздействие върху качеството на кода. Класовете са измамни по отношение на своите зависимости. Кодът става непредсказуем. Това обърква разработчиците и намалява тяхната ефективност. +Сега определено не говорим за някаква академична чистота. Всички тези конструкции имат едно общо нещо: те използват глобално състояние. А то има разрушителен ефект върху качеството на кода. Класовете лъжат за своите зависимости. Кодът става непредсказуем. Обърква програмистите и намалява тяхната ефективност. -В тази глава ще обясним защо това е така и как да избегнем глобалното състояние. +В тази глава ще обясним защо е така и как да избегнем глобалното състояние. -Глобално свързване .[#toc-global-interlinking] ----------------------------------------------- +Глобална свързаност +------------------- -В идеалния свят един обект трябва да комуникира само с обекти, които са [му предадени директно |passing-dependencies]. Ако създам два обекта `A` и `B` и никога не предам референция между тях, тогава нито `A`, нито `B` могат да получат достъп до състоянието на другия или да го променят. Това е много желателно свойство на кода. То прилича на това да имате батерия и електрическа крушка; крушката няма да светне, докато не я свържете към батерията с проводник. +В идеалния свят обектът трябва да може да комуникира само с обекти, които са му били [директно предадени |passing-dependencies]. Ако създам два обекта `A` и `B` и никога не предам референция между тях, тогава нито `A`, нито `B` могат да достигнат до другия обект или да променят неговото състояние. Това е много желана характеристика на кода. Подобно е на това да имате батерия и крушка; крушката няма да свети, докато не я свържете с батерията с проводник. -Това обаче не е валидно за глобалните (статични) променливи или единичните променливи. Обектът `A` може да получи *безжичен* достъп до обекта `C` и да го модифицира без предаване на референция, като извика `C::changeSomething()`. Ако обектът `B` също се свързва с глобалната променлива `C`, тогава `A` и `B` могат да си влияят взаимно чрез `C`. +Но това не важи за глобални (статични) променливи или сингълтъни. Обект `A` може *безжично* да достигне до обект `C` и да го модифицира без никакво предаване на референция, като извика `C::changeSomething()`. Ако обект `B` също се възползва от глобалния `C`, тогава `A` и `B` могат да си влияят взаимно чрез `C`. -Използването на глобални променливи въвежда нова форма на *безжично* свързване, която не е видима отвън. Тя създава димна завеса, която усложнява разбирането и използването на кода. За да разберат наистина зависимостите, разработчиците трябва да прочетат всеки ред от изходния код, а не само да се запознаят с интерфейсите на класовете. Освен това това заплитане е напълно ненужно. Глобалното състояние се използва, защото е лесно достъпно отвсякъде и позволява например запис в база данни чрез глобален (статичен) метод `DB::insert()`. Въпреки това, както ще видим, ползата от него е минимална, а усложненията, които въвежда, са сериозни. +Използването на глобални променливи въвежда нова форма на *безжична* свързаност в системата, която не се вижда отвън. Създава димна завеса, усложняваща разбирането и използването на кода. За да разберат наистина зависимостите, разработчиците трябва да прочетат всеки ред от изходния код. Вместо просто да се запознаят с интерфейсите на класовете. Освен това става дума за напълно ненужна свързаност. Глобалното състояние се използва, защото е лесно достъпно отвсякъде и позволява например запис в базата данни чрез глобален (статичен) метод `DB::insert()`. Но както ще покажем, предимството, което носи, е незначително, докато усложненията, които причинява, са фатални. .[note] -По отношение на поведението няма разлика между глобална и статична променлива. Те са еднакво вредни. +От гледна точка на поведението няма разлика между глобална и статична променлива. Те са еднакво вредни. -Призрачно действие на разстояние (Spooky Action at a Distance) .[#toc-the-spooky-action-at-a-distance] ------------------------------------------------------------------------------------------------------- +Призрачно действие от разстояние +-------------------------------- -"Призрачно действие на разстояние" - така Алберт Айнщайн нарича едно явление в квантовата физика, което през 1935 г. му докарало ужас. -Става дума за квантовото заплитане, чиято особеност е, че когато измервате информация за една частица, веднага въздействате върху друга частица, дори ако те са на милиони светлинни години една от друга. -което привидно нарушава фундаменталния закон на Вселената, според който нищо не може да се движи по-бързо от светлината. +"Призрачно действие от разстояние" - така Алберт Айнщайн нарича през 1935 г. явление в квантовата физика, което го кара да настръхне. +Става дума за квантово заплитане, чиято особеност е, че когато измерите информация за една частица, веднага повлиявате на другата частица, дори ако те са на милиони светлинни години една от друга. Което привидно нарушава основния закон на Вселената, че нищо не може да се разпространява по-бързо от светлината. -В света на софтуера можем да наречем "spooky action at a distance" ситуация, при която стартираме процес, който смятаме за изолиран (защото не сме му предали никакви референции), но в отдалечени места на системата се случват неочаквани взаимодействия и промени в състоянието, за които не сме казали на обекта. Това може да се случи само чрез глобалното състояние. +В света на софтуера можем да наречем "призрачно действие от разстояние" ситуация, при която стартираме някакъв процес, за който смятаме, че е изолиран (защото не сме му предали никакви референции), но на отдалечени места в системата възникват неочаквани взаимодействия и промени в състоянието, за които не сме подозирали. Това може да се случи само чрез глобално състояние. -Представете си, че се присъедините към екип за разработване на проект, който има голяма, зряла база от кодове. Новият ви ръководител ви моли да реализирате нова функция и като добър разработчик започвате с написването на тест. Но тъй като сте нов в проекта, правите много проучвателни тестове от типа "какво ще стане, ако извикам този метод". И се опитвате да напишете следния тест: +Представете си, че се присъединявате към екип от разработчици на проект, който има голяма, зряла кодова база. Новият ви ръководител ви моли да имплементирате нова функция и вие, като добър разработчик, започвате с писане на тест. Но тъй като сте нов в проекта, правите много проучвателни тестове от типа "какво ще се случи, ако извикам този метод". И опитвате да напишете следния тест: ```php function testCreditCardCharge() { - $cc = new CreditCard('1234567890123456', 5, 2028); // номера на картата ви. + $cc = new CreditCard('1234567890123456', 5, 2028); // номер на вашата карта $cc->charge(100); } ``` -Изпълнявате кода, може би няколко пъти, и след известно време забелязвате на телефона си известия от банката, че всеки път, когато го изпълнявате, от кредитната ви карта са били изтеглени 100 долара 🤦‍♂️ +Изпълнявате кода, може би няколко пъти, и след известно време забелязвате известия от банката на мобилния си телефон, че при всяко стартиране са били изтеглени 100 долара от вашата кредитна карта 🤦‍♂️ -Как, за Бога, тестът би могъл да предизвика действително таксуване? Не е лесно да се оперира с кредитна карта. Трябва да взаимодействате с уеб услуга на трета страна, трябва да знаете URL адреса на тази уеб услуга, трябва да влезете в системата и т.н. -Нито една от тези информации не е включена в теста. Още по-лошо, дори не знаете къде присъства тази информация и следователно как да издекламирате външните зависимости, така че всяко изпълнение да не води до повторно таксуване на 100 USD. А като нов разработчик откъде трябваше да знаете, че това, което ще направите, ще доведе до обедняването ви със 100 долара? +Как, за бога, тестът може да е причинил реално теглене на пари? Работата с кредитна карта не е лесна. Трябва да комуникирате с уеб услуга на трета страна, трябва да знаете URL адреса на тази уеб услуга, трябва да влезете и т.н. Нито една от тази информация не се съдържа в теста. Още по-лошо, дори не знаете къде се намира тази информация и следователно как да mock-нете външните зависимости, така че всяко стартиране да не води до повторно теглене на 100 долара. И как вие, като нов разработчик, трябваше да знаете, че това, което се каните да направите, ще доведе до това да сте с 100 долара по-беден? Това е призрачно действие от разстояние! -Не ви остава нищо друго, освен да се ровите в много изходен код, да питате по-възрастни и по-опитни колеги, докато разберете как работят връзките в проекта. -Това се дължи на факта, че когато разглеждате интерфейса на класа `CreditCard`, не можете да определите глобалното състояние, което трябва да бъде инициализирано. Дори разглеждането на изходния код на класа няма да ви подскаже кой метод за инициализация да извикате. В най-добрия случай можете да намерите глобалната променлива, до която се осъществява достъп, и да се опитате да предположите как да я инициализирате от нея. +Не ви остава нищо друго, освен дълго да ровите в много изходни кодове, да питате по-стари и по-опитни колеги, докато разберете как работят връзките в проекта. Това се дължи на факта, че при разглеждане на интерфейса на класа `CreditCard` не може да се установи глобалното състояние, което трябва да се инициализира. Дори поглед към изходния код на класа няма да ви каже кой инициализационен метод трябва да извикате. В най-добрия случай можете да намерите глобална променлива, до която се осъществява достъп, и от нея да се опитате да отгатнете как да я инициализирате. -Класовете в такъв проект са патологични лъжци. Платежната карта се преструва, че можете просто да я инстанцирате и да извикате метода `charge()`. Тя обаче тайно взаимодейства с друг клас, `PaymentGateway`. Дори интерфейсът му казва, че може да бъде инициализиран самостоятелно, но в действителност той тегли идентификационни данни от някакъв конфигурационен файл и така нататък. -За разработчиците, които са написали този код, е ясно, че `CreditCard` се нуждае от `PaymentGateway`. Те са написали кода по този начин. Но за всеки, който е нов в проекта, това е пълна загадка и пречи на обучението. +Класовете в такъв проект са патологични лъжци. Кредитната карта се преструва, че е достатъчно да я инстанцирате и да извикате метода `charge()`. Но тайно тя си сътрудничи с друг клас `PaymentGateway`, който представлява платежен портал. Неговият интерфейс също казва, че може да се инициализира самостоятелно, но всъщност извлича идентификационни данни от някакъв конфигурационен файл и т.н. За разработчиците, които са написали този код, е ясно, че `CreditCard` се нуждае от `PaymentGateway`. Те са написали кода по този начин. Но за всеки нов в проекта това е пълна загадка и пречи на ученето. -Как да поправим ситуацията? Лесно. **Дайте възможност на API да декларира зависимостите.** +Как да поправим ситуацията? Лесно. **Нека API декларира зависимостите.** ```php function testCreditCardCharge() @@ -72,36 +68,35 @@ function testCreditCardCharge() } ``` -Забележете как връзките в кода изведнъж стават очевидни. Като декларирате, че методът `charge()` се нуждае от `PaymentGateway`, не е нужно да питате никого как кодът е взаимозависим. Знаете, че трябва да създадете негова инстанция, а когато се опитате да го направите, се сблъсквате с факта, че трябва да предоставите параметри за достъп. Без тях кодът дори не би могъл да се изпълни. +Забележете как изведнъж взаимовръзките в кода стават очевидни. Тъй като методът `charge()` декларира, че се нуждае от `PaymentGateway`, не е нужно да питате никого как е свързан кодът. Знаете, че трябва да създадете негова инстанция и когато се опитате да го направите, ще откриете, че трябва да предоставите параметри за достъп. Без тях кодът дори не би могъл да се изпълни. -И най-важното, сега можете да издекламирате шлюза за плащане, така че да не ви таксуват по 100 долара всеки път, когато стартирате тест. +И най-важното, сега можете да mock-нете платежния портал, така че няма да ви бъдат таксувани 100 долара всеки път, когато стартирате теста. -Глобалното състояние кара обектите ви да могат тайно да получават достъп до неща, които не са декларирани в техните API, и в резултат на това прави API-тата ви патологични лъжци. +Глобалното състояние кара вашите обекти да имат таен достъп до неща, които не са декларирани в техните API, и в резултат на това превръща вашите API в патологични лъжци. -Може и да не сте го мислили по този начин преди, но винаги когато използвате глобално състояние, създавате тайни безжични канали за комуникация. Страховитото отдалечено действие принуждава разработчиците да четат всеки ред код, за да разберат потенциалните взаимодействия, намалява производителността на разработчиците и обърква новите членове на екипа. -Ако вие сте този, който е създал кода, знаете истинските зависимости, но всеки, който идва след вас, е безпомощен. +Може би не сте мислили за това по този начин преди, но всеки път, когато използвате глобално състояние, създавате тайни безжични комуникационни канали. Призрачното действие от разстояние принуждава разработчиците да четат всеки ред код, за да разберат потенциалните взаимодействия, намалява производителността на разработчиците и обърква новите членове на екипа. Ако вие сте този, който е създал кода, познавате истинските зависимости, но всеки, който дойде след вас, е безпомощен. -Не пишете код, който използва глобално състояние, а предпочитайте да предавате зависимости. Това е инжектиране на зависимости. +Не пишете код, който използва глобално състояние, предпочитайте предаването на зависимости. Тоест dependency injection. -Крехкостта на глобалната държава .[#toc-brittleness-of-the-global-state] ------------------------------------------------------------------------- +Крехкост на глобалното състояние +-------------------------------- -В код, който използва глобално състояние и единични елементи, никога не е сигурно кога и от кого е променено това състояние. Този риск е налице още при инициализацията. Следващият код трябва да създаде връзка с база данни и да инициализира шлюза за плащания, но продължава да хвърля изключение и намирането на причината е изключително трудоемко: +В код, който използва глобално състояние и сингълтъни, никога не е сигурно кога и кой е променил това състояние. Този риск се появява още при инициализацията. Следният код трябва да създаде връзка с база данни и да инициализира платежен портал, но постоянно хвърля изключение и намирането на причината е изключително досадно: ```php PaymentGateway::init(); DB::init('mysql:', 'user', 'password'); ``` -Трябва да прегледате подробно кода, за да откриете, че обектът `PaymentGateway` осъществява безжичен достъп до други обекти, някои от които изискват връзка с база данни. По този начин трябва да инициализирате базата данни, преди `PaymentGateway`. Димната завеса на глобалното състояние обаче скрива това от вас. Колко време бихте спестили, ако API на всеки клас не лъже и не декларира своите зависимости? +Трябва подробно да прегледате кода, за да установите, че обектът `PaymentGateway` осъществява безжичен достъп до други обекти, някои от които изискват връзка с база данни. Следователно е необходимо да се инициализира базата данни преди `PaymentGateway`. Въпреки това, димната завеса на глобалното състояние скрива това от вас. Колко време бихте спестили, ако API-тата на отделните класове не лъжеха и декларираха своите зависимости? ```php $db = new DB('mysql:', 'user', 'password'); $gateway = new PaymentGateway($db, ...); ``` -Подобен проблем възниква, когато използвате глобален достъп до връзка с база данни: +Подобен проблем възниква и при използване на глобален достъп до връзката с базата данни: ```php use Illuminate\Support\Facades\DB; @@ -115,9 +110,9 @@ class Article } ``` -Когато се извиква методът `save()`, не е сигурно дали вече е създадена връзка към базата данни и кой е отговорен за създаването ѝ. Например, ако искаме да променим връзката с базата данни в движение, може би за целите на тестването, вероятно ще трябва да създадем допълнителни методи като `DB::reconnect(...)` или `DB::reconnectForTest()`. +При извикване на метода `save()` не е сигурно дали вече е създадена връзка с базата данни и кой носи отговорност за нейното създаване. Ако искаме например да променяме връзката с базата данни по време на изпълнение, например за тестове, вероятно ще трябва да създадем допълнителни методи като `DB::reconnect(...)` или `DB::reconnectForTest()`. -Разгледайте един пример: +Да разгледаме пример: ```php $article = new Article; @@ -127,9 +122,9 @@ Foo::doSomething(); $article->save(); ``` -Откъде можем да сме сигурни, че тестовата база данни наистина се използва при извикването на `$article->save()`? Какво ще стане, ако методът `Foo::doSomething()` промени глобалната връзка към базата данни? За да разберем това, ще трябва да разгледаме изходния код на класа `Foo` и вероятно на много други класове. Този подход обаче би дал само краткосрочен отговор, тъй като ситуацията може да се промени в бъдеще. +Къде имаме сигурност, че при извикване на `$article->save()` наистина се използва тестовата база данни? Ами ако методът `Foo::doSomething()` е променил глобалната връзка с базата данни? За да разберем, ще трябва да проучим изходния код на класа `Foo` и вероятно на много други класове. Този подход обаче би донесъл само краткосрочен отговор, тъй като ситуацията може да се промени в бъдеще. -Какво ще стане, ако преместим връзката с базата данни в статична променлива вътре в класа `Article`? +Ами ако преместим връзката с базата данни в статична променлива вътре в класа `Article`? ```php class Article @@ -148,11 +143,11 @@ class Article } ``` -Това изобщо не променя нищо. Проблемът е глобално състояние и няма значение в кой клас се крие. В този случай, както и в предишния, нямаме представа в коя база данни се записва, когато се извика методът `$article->save()`. Всеки, който се намира в далечния край на приложението, може да промени базата данни по всяко време, като използва `Article::setDb()`. Под нашите ръце. +С това нищо не се промени. Проблемът е глобалното състояние и няма никакво значение в кой клас се крие. В този случай, както и в предишния, при извикване на метода `$article->save()` нямаме никаква представа в коя база данни ще се запише. Всеки от другия край на приложението може по всяко време да промени базата данни с помощта на `Article::setDb()`. Под носа ни. Глобалното състояние прави нашето приложение **изключително крехко**. -Има обаче прост начин да се справим с този проблем. Просто накарайте API да декларира зависимости, за да се осигури правилна функционалност. +Съществува обаче прост начин за справяне с този проблем. Достатъчно е да оставим API да декларира зависимостите, което ще гарантира правилната функционалност. ```php class Article @@ -174,15 +169,15 @@ Foo::doSomething(); $article->save(); ``` -Този подход елиминира притеснението от скрити и неочаквани промени във връзките с бази данни. Сега сме сигурни къде се съхранява статията и никакви модификации на кода вътре в друг несвързан клас вече не могат да променят ситуацията. Кодът вече не е крехък, а стабилен. +Благодарение на този подход отпада притеснението за скрити и неочаквани промени във връзката с базата данни. Сега имаме сигурност къде се съхранява статията и никакви промени в кода в друг несвързан клас вече не могат да променят ситуацията. Кодът вече не е крехък, а стабилен. -Не пишете код, който използва глобално състояние, а предпочитайте да предавате зависимости. По този начин се въвежда инжектиране на зависимости. +Не пишете код, който използва глобално състояние, предпочитайте предаването на зависимости. Тоест dependency injection. -Singleton .[#toc-singleton] ---------------------------- +Singleton +--------- -Singleton е шаблон за проектиране, който по [дефиниция от |https://en.wikipedia.org/wiki/Singleton_pattern] известната публикация на Gang of Four ограничава класа до един екземпляр и предлага глобален достъп до него. Реализацията на този шаблон обикновено прилича на следния код: +Singleton е дизайн патърн, който според "дефиницията":https://en.wikipedia.org/wiki/Singleton_pattern от известната публикация на Gang of Four ограничава класа до една единствена инстанция и предлага глобален достъп до нея. Имплементацията на този патърн обикновено прилича на следния код: ```php class Singleton @@ -195,38 +190,35 @@ class Singleton return self::$instance; } - // и други методи, които изпълняват функциите на класа. + // и други методи, изпълняващи функциите на дадения клас } ``` -За съжаление сингълтонът въвежда глобално състояние в приложението. А както показахме по-горе, глобалното състояние е нежелателно. Ето защо сингълтонът се счита за антимодел. +За съжаление, сингълтънът въвежда глобално състояние в приложението. И както показахме по-горе, глобалното състояние е нежелателно. Затова сингълтънът се счита за антипатърн. -Не използвайте сингълтони в кода си и ги заменете с други механизми. Наистина не се нуждаете от синглетони. Ако обаче трябва да гарантирате съществуването на една единствена инстанция на даден клас за цялото приложение, оставете това на [DI контейнера |container]. -По този начин създайте синглетон на приложението или услуга. Това ще попречи на класа да осигури собствената си уникалност (т.е. той няма да има `getInstance()` метод и статична променлива) и ще изпълнява само своите функции. По този начин той ще спре да нарушава принципа на единичната отговорност. +Не използвайте сингълтъни във вашия код и ги заменете с други механизми. Наистина не се нуждаете от сингълтъни. Въпреки това, ако трябва да гарантирате съществуването на една единствена инстанция на клас за цялото приложение, оставете това на [DI контейнера |container]. По този начин създайте апликационен сингълтън, т.е. сървис. Така класът ще спре да се занимава с осигуряването на собствената си уникалност (т.е. няма да има метод `getInstance()` и статична променлива) и ще изпълнява само своите функции. Така ще спре да нарушава принципа на единствената отговорност. -Глобално състояние срещу тестове .[#toc-global-state-versus-tests] ------------------------------------------------------------------- +Глобално състояние срещу тестове +-------------------------------- -Когато пишем тестове, приемаме, че всеки тест е изолирана единица и че в него не влиза никакво външно състояние. И никое състояние не напуска тестовете. Когато тестът завърши, всяко състояние, свързано с него, трябва да бъде премахнато автоматично от garbage collector. Това прави тестовете изолирани. Следователно можем да изпълняваме тестовете в произволен ред. +При писане на тестове предполагаме, че всеки тест е изолирана единица и че в него не влиза никакво външно състояние. И никакво състояние не напуска тестовете. След приключване на теста цялото свързано с теста състояние трябва да бъде автоматично премахнато от garbage collector-а. Благодарение на това тестовете са изолирани. Затова можем да изпълняваме тестовете в произволен ред. -Ако обаче са налице глобални състояния/синглетони, всички тези хубави предположения се развалят. Едно състояние може да влиза и излиза от тест. Изведнъж редът на тестовете може да има значение. +Ако обаче са налице глобални състояния/сингълтъни, всички тези приятни предположения се разпадат. Състоянието може да влиза и излиза от теста. Изведнъж редът на тестовете може да има значение. -За да могат изобщо да тестват единични състояния, разработчиците често трябва да смекчат техните свойства, може би като позволят даден екземпляр да бъде заменен с друг. Такива решения в най-добрия случай са хакове, които създават код, който е труден за поддържане и разбиране. Всеки тест или метод `tearDown()`, който засяга някое глобално състояние, трябва да отмени тези промени. +За да можем изобщо да тестваме сингълтъни, разработчиците често трябва да разхлабят техните свойства, например като позволят инстанцията да бъде заменена с друга. Такива решения в най-добрия случай са хак, който създава трудно поддържаем и разбираем код. Всеки тест или метод `tearDown()`, който повлияе на някакво глобално състояние, трябва да върне тези промени обратно. -Глобалното състояние е най-голямото главоболие при тестването на единици! +Глобалното състояние е най-голямото главоболие при unit тестването! -Как да поправим ситуацията? Лесно. Не пишете код, който използва singletons, а предпочитайте да предавате зависимости. Това е инжектиране на зависимости. +Как да поправим ситуацията? Лесно. Не пишете код, който използва сингълтъни, предпочитайте предаването на зависимости. Тоест dependency injection. -Глобални константи .[#toc-global-constants] -------------------------------------------- +Глобални константи +------------------ -Глобалното състояние не се ограничава само до използването на единични и статични променливи, но може да се прилага и за глобални константи. +Глобалното състояние не се ограничава само до използването на сингълтъни и статични променливи, но може да се отнася и до глобални константи. -Константи, чиято стойност не ни предоставя никаква нова (`M_PI`) или полезна (`PREG_BACKTRACK_LIMIT_ERROR`) информация, са очевидно ОК. -Обратно, константи, които служат като начин за *безжично* предаване на информация вътре в кода, не са нищо повече от скрита зависимост. Като `LOG_FILE` в следния пример. -Използването на константата `FILE_APPEND` е напълно правилно. +Константи, чиято стойност не ни носи никаква нова (`M_PI`) или полезна (`PREG_BACKTRACK_LIMIT_ERROR`) информация, са недвусмислено в ред. Напротив, константи, които служат като начин за *безжично* предаване на информация вътре в кода, не са нищо друго освен скрита зависимост. Като например `LOG_FILE` в следващия пример. Използването на константата `FILE_APPEND` е напълно коректно. ```php const LOG_FILE = '...'; @@ -242,7 +234,7 @@ class Foo } ``` -В този случай трябва да декларираме параметъра в конструктора на класа `Foo`, за да го направим част от API: +В този случай трябва да декларираме параметър в конструктора на класа `Foo`, за да стане част от API: ```php class Foo @@ -261,44 +253,42 @@ class Foo } ``` -Сега можем да подаваме информация за пътя до файла за регистриране и лесно да го променяме при необходимост, което улеснява тестването и поддръжката на кода. +Сега можем да предадем информация за пътя до лог файла и лесно да го променяме при нужда, което улеснява тестването и поддръжката на кода. -Глобални функции и статични методи .[#toc-global-functions-and-static-methods] ------------------------------------------------------------------------------- +Глобални функции и статични методи +---------------------------------- -Искаме да подчертаем, че използването на статични методи и глобални функции само по себе си не е проблематично. Обяснихме неуместността на използването на `DB::insert()` и други подобни методи, но винаги е ставало въпрос за глобално състояние, съхранявано в статична променлива. Методът `DB::insert()` изисква съществуването на статична променлива, защото съхранява връзката с базата данни. Без тази променлива би било невъзможно да се реализира методът. +Искаме да подчертаем, че самото използване на статични методи и глобални функции не е проблематично. Обяснихме защо използването на `DB::insert()` и подобни методи е неподходящо, но винаги ставаше дума само за глобално състояние, което се съхранява в някаква статична променлива. Методът `DB::insert()` изисква съществуването на статична променлива, тъй като в нея се съхранява връзката с базата данни. Без тази променлива би било невъзможно да се имплементира методът. -Използването на детерминистични статични методи и функции, като например `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` и много други, е напълно съвместимо с инжектирането на зависимости. Тези функции винаги връщат едни и същи резултати от едни и същи входни параметри и следователно са предвидими. Те не използват никакво глобално състояние. +Използването на детерминистични статични методи и функции, като например `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` и много други, е в пълно съответствие с dependency injection. Тези функции винаги връщат едни и същи резултати за едни и същи входни параметри и следователно са предвидими. Те не използват никакво глобално състояние. -В PHP обаче има функции, които не са детерминирани. Сред тях е например функцията `htmlspecialchars()`. Нейният трети параметър, `$encoding`, ако не е посочен, по подразбиране е стойността на конфигурационната опция `ini_get('default_charset')`. Ето защо се препоръчва винаги да посочвате този параметър, за да избегнете евентуално непредсказуемо поведение на функцията. Nette последователно прави това. +Съществуват обаче и функции в PHP, които не са детерминистични. Към тях принадлежи например функцията `htmlspecialchars()`. Нейният трети параметър `$encoding`, ако не е посочен, по подразбиране има стойността на конфигурационната опция `ini_get('default_charset')`. Затова се препоръчва винаги да се посочва този параметър, за да се избегне евентуално непредсказуемо поведение на функцията. Nette го прави последователно. -Някои функции, като например `strtolower()`, `strtoupper()` и други подобни, имаха недетерминирано поведение в близкото минало и зависеха от настройката `setlocale()`. Това предизвикваше много усложнения, най-често при работа с турския език. -Това е така, защото турският език прави разлика между големи и малки букви `I` с и без точка. Така `strtolower('I')` връщаше символа `ı`, а `strtoupper('i')` - символа `İ`, което водеше до това, че приложенията предизвикваха редица мистериозни грешки. -Този проблем обаче беше отстранен във версия 8.2 на PHP и функциите вече не зависят от локалите. +Някои функции, като например `strtolower()`, `strtoupper()` и подобни, в близкото минало се държаха недетерминистично и зависеха от настройката `setlocale()`. Това причиняваше много усложнения, най-често при работа с турски език. Той различава малки и големи букви `I` с точка и без точка. Така че `strtolower('I')` връщаше знака `ı`, а `strtoupper('i')` - знака `İ`, което водеше до това, че приложенията започваха да причиняват редица мистериозни грешки. Този проблем обаче беше отстранен в PHP версия 8.2 и функциите вече не зависят от locale. -Това е хубав пример за това как глобалното състояние е измъчвало хиляди разработчици по целия свят. Решението беше да го заменим с инжектиране на зависимости. +Това е хубав пример как глобалното състояние е измъчвало хиляди разработчици по целия свят. Решението беше да се замени с dependency injection. -Кога е възможно да се използва глобално състояние? .[#toc-when-is-it-possible-to-use-global-state] --------------------------------------------------------------------------------------------------- +Кога е възможно да се използва глобално състояние? +-------------------------------------------------- -Има някои специфични ситуации, в които е възможно да се използва глобално състояние. Например, когато отстранявате грешки в кода и трябва да изхвърлите стойността на променлива или да измерите продължителността на определена част от програмата. В такива случаи, които се отнасят до временни действия, които по-късно ще бъдат премахнати от кода, е закономерно да се използва глобално достъпен дъмпер или хронометър. Тези инструменти не са част от дизайна на кода. +Съществуват определени специфични ситуации, в които е възможно да се използва глобално състояние. Например, при дебъгване на код, когато трябва да изведете стойността на променлива или да измерите продължителността на определена част от програмата. В такива случаи, които се отнасят до временни актове, които по-късно ще бъдат премахнати от кода, е възможно легитимно да се използва глобално достъпен dumper или хронометър. Тези инструменти всъщност не са част от дизайна на кода. -Друг пример са функциите за работа с регулярни изрази `preg_*`, които вътрешно съхраняват компилирани регулярни изрази в статичен кеш в паметта. Когато извиквате един и същ регулярен израз няколко пъти в различни части на кода, той се компилира само веднъж. Кешът спестява производителност, а освен това е напълно невидим за потребителя, така че такава употреба може да се счита за легитимна. +Друг пример са функциите за работа с регулярни изрази `preg_*`, които вътрешно съхраняват компилирани регулярни изрази в статичен кеш в паметта. Така че, когато извиквате един и същ регулярен израз многократно на различни места в кода, той се компилира само веднъж. Кешът спестява производителност и в същото време е напълно невидим за потребителя, затова такова използване може да се счита за легитимно. -Обобщение .[#toc-summary] -------------------------- +Обобщение +--------- -Показахме защо има смисъл +Обсъдихме защо има смисъл: -1) Премахнете всички статични променливи от кода -2) Декларирайте зависимостите -3) И използвайте инжектиране на зависимости +1) Да премахнете всички статични променливи от кода +2) Да декларирате зависимости +3) И да използвате dependency injection -Когато обмисляте дизайна на кода, имайте предвид, че всеки `static $foo` представлява проблем. За да може кодът ви да бъде среда, уважаваща DI, е необходимо напълно да изкорените глобалното състояние и да го замените с инжектиране на зависимости. +Когато обмисляте дизайна на кода, имайте предвид, че всяко `static $foo` представлява проблем. За да бъде вашият код среда, уважаваща DI, е необходимо напълно да изкорените глобалното състояние и да го замените с dependency injection. -По време на този процес може да откриете, че трябва да разделите даден клас, защото той има повече от една отговорност. Не се притеснявайте за това; стремете се към принципа на една отговорност. +По време на този процес може да откриете, че е необходимо да разделите класа, защото той има повече от една отговорност. Не се страхувайте от това; стремете се към принципа на единствената отговорност. -*Искам да благодаря на Мишко Хевери, чиито статии като [Flaw: Brittle Global State & Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/] са в основата на тази глава.* +*Бих искал да благодаря на Miško Hevery, чиито статии, като [Flaw: Brittle Global State & Singletons |https://web.archive.org/web/20230321084133/http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/], са в основата на тази глава.* diff --git a/dependency-injection/bg/introduction.texy b/dependency-injection/bg/introduction.texy index b4c40679f1..902247b1dd 100644 --- a/dependency-injection/bg/introduction.texy +++ b/dependency-injection/bg/introduction.texy @@ -1,58 +1,58 @@ -Какво е "прилагане на зависимостта"? -************************************ +Какво е Dependency Injection? +***************************** .[perex] -Тази глава ще ви запознае с основните практики за програмиране, които трябва да спазвате при писането на всяко приложение. Това са основите, необходими за писането на чист, разбираем и поддържан код. +Тази глава ще ви запознае с основните програмни практики, които трябва да следвате при писането на всички приложения. Това са основите, необходими за писане на чист, разбираем и поддържаем код. -Ако научите и следвате тези правила, Nette ще бъде до вас на всяка крачка. Тя ще се справи с рутинните задачи вместо вас и ще ви осигури максимален комфорт, така че да можете да се съсредоточите върху самата логика. +Ако усвоите тези правила и ги спазвате, Nette ще ви помага на всяка стъпка. Той ще се справя с рутинните задачи вместо вас и ще ви осигури максимален комфорт, за да можете да се съсредоточите върху самата логика. -Принципите, които ще покажем тук, са съвсем прости. Не е нужно да се притеснявате за нищо. +Принципите, които ще покажем тук, са доста прости. Няма нужда да се притеснявате за нищо. -Помните ли първата си програма? .[#toc-remember-your-first-program] -------------------------------------------------------------------- +Спомняте ли си първата си програма? +----------------------------------- -Не знаем на какъв език сте го написали, но ако е PHP, може да изглежда по следния начин: +Не знаем на какъв език сте я написали, но ако беше PHP, вероятно щеше да изглежда така: ```php -function addition(float $a, float $b): float +function soucet(float $a, float $b): float { return $a + $b; } -echo addition(23, 1); // извежда 24 +echo soucet(23, 1); // извежда 24 ``` -Няколко тривиални реда код, но в тях са скрити толкова много ключови концепции. Че има променливи. Че кодът е разделен на по-малки единици, които са функции, например. Че им подаваме входни аргументи и те връщат резултати. Липсват само условия и цикли. +Няколко тривиални реда код, но в тях се крият толкова много ключови концепции. Че съществуват променливи. Че кодът се разделя на по-малки единици, като например функции. Че им предаваме входни аргументи и те връщат резултати. Липсват само условия и цикли. -Фактът, че функцията приема входни данни и връща резултат, е напълно разбираемо понятие, което се използва и в други области, например в математиката. +Това, че предаваме входни данни на функция и тя връща резултат, е напълно разбираема концепция, която се използва и в други области, като например математиката. -Една функция има сигнатура, която се състои от нейното име, списък с параметри и техните типове и накрая типа на връщаната стойност. Като потребители ние се интересуваме от сигнатурата и обикновено не е необходимо да знаем нищо за вътрешната реализация. +Функцията има своя сигнатура, която се състои от нейното име, списък с параметри и техните типове, и накрая тип на връщаната стойност. Като потребители ни интересува сигнатурата, обикновено не е необходимо да знаем нищо за вътрешната имплементация. -Сега си представете, че сигнатурата на функцията изглежда по следния начин: +Сега си представете, че сигнатурата на функцията изглеждаше така: ```php -function addition(float $x): float +function soucet(float $x): float ``` -Добавка с един параметър? Това е странно... Какво ще кажете за това? +Сума с един параметър? Това е странно… А какво ще кажете за това? ```php -function addition(): float +function soucet(): float ``` -Това е наистина странно, нали? Как се използва функцията? +Това вече е наистина много странно, нали? Как се използва функцията? ```php -echo addition(); // какво се показва тук? +echo soucet(); // какво ли ще изведе? ``` -Ако погледнем такъв код, ще се объркаме. Не само начинаещ програмист не би го разбрал, но дори и опитен програмист не би разбрал такъв код. +При вида на такъв код бихме били объркани. Не само начинаещ не би го разбрал, такъв код не разбира и опитен програмист. -Чудите ли се как всъщност би изглеждала една такава функция вътре? Откъде би получила сумарните стойности? Вероятно *по някакъв начин* би ги получила сама, може би по следния начин: +Чудите ли се как би изглеждала такава функция отвътре? Откъде ще вземе събираемите? Вероятно би си ги набавила *по някакъв начин* сама, например така: ```php -function addition(): float +function soucet(): float { $a = Input::get('a'); $b = Input::get('b'); @@ -60,71 +60,71 @@ function addition(): float } ``` -Оказва се, че в тялото на функцията има скрити връзки към други функции (или статични методи) и за да разберем откъде всъщност идват добавките, трябва да се разровим допълнително. +В тялото на функцията открихме скрити връзки към други глобални функции или статични методи. За да разберем откъде всъщност идват събираемите, трябва да търсим по-нататък. -Не по този начин! .[#toc-not-this-way] --------------------------------------- +Не така! +-------- -Дизайнът, който току-що показахме, е същността на много отрицателни характеристики: +Дизайнът, който току-що показахме, е есенцията на много негативни черти: -- сигнатурата на функцията се преструва, че не се нуждае от сумарните стойности, което ни обърква -- нямаме представа как да накараме функцията да изчислява с две други числа -- трябваше да разгледаме кода, за да разберем откъде идват сумарните стойности +- сигнатурата на функцията се преструваше, че не се нуждае от събираеми, което ни объркваше +- изобщо не знаем как да накараме функцията да събере други две числа +- трябваше да погледнем в кода, за да разберем откъде взема събираемите - открихме скрити зависимости -- пълното разбиране изисква да се разгледат и тези зависимости +- за пълно разбиране е необходимо да се проучат и тези зависимости -А дали изобщо задачата на функцията за добавяне е да набавя входове? Разбира се, че не е. Нейната отговорност е само да добавя. +И изобщо задача ли е на функцията за събиране да си набавя входове? Разбира се, че не е. Нейната отговорност е само самото събиране. -Не искаме да срещаме такъв код и със сигурност не искаме да го пишем. Лекарството е просто: върнете се към основите и просто използвайте параметри: +С такъв код не искаме да се сблъскваме и определено не искаме да го пишем. Поправката е проста: да се върнем към основите и просто да използваме параметри: ```php -function addition(float $a, float $b): float +function soucet(float $a, float $b): float { return $a + $b; } ``` -Правило № 1: Нека да ти бъде предадено .[#toc-rule-1-let-it-be-passed-to-you] ------------------------------------------------------------------------------ +Правило № 1: Нека ви го предадат +-------------------------------- -Най-важното правило е: **всички данни, от които се нуждаят функциите или класовете, трябва да им бъдат предадени**. +Най-важното правило е: **всички данни, от които функциите или класовете се нуждаят, трябва да им бъдат предадени**. -Вместо да измисляте скрити начини за самостоятелен достъп до данните, просто им предайте параметрите. Така ще спестите време, което бихте прекарали в измисляне на скрити пътища, които със сигурност няма да подобрят кода ви. +Вместо да измисляте скрити начини, чрез които те биха могли да стигнат до тях сами, просто предайте параметрите. Ще спестите време, необходимо за измисляне на скрити пътища, които определено няма да подобрят вашия код. -Ако винаги и навсякъде спазвате това правило, сте на път към код без скрити зависимости. Към код, който е разбираем не само за автора, но и за всеки, който го прочете след това. Където всичко се разбира от сигнатурите на функциите и класовете и няма нужда да търсите скрити тайни в имплементацията. +Ако спазвате това правило винаги и навсякъде, сте на път към код без скрити зависимости. Към код, който е разбираем не само за автора, но и за всеки, който ще го чете след него. Където всичко е разбираемо от сигнатурите на функциите и класовете и не е необходимо да се търсят скрити тайни в имплементацията. -Тази техника професионално се нарича **инжектиране на зависимости**. А тези данни се наричат **зависимости**. Това е просто обикновено предаване на параметри, нищо повече. +Тази техника се нарича професионално **dependency injection**. А тези данни се наричат **зависимости.** Всъщност това е просто предаване на параметри, нищо повече. .[note] -Моля, не бъркайте инжектирането на зависимости, което е шаблон за проектиране, с "контейнер за инжектиране на зависимости", който е инструмент, нещо диаметрално различно. Контейнерите ще разгледаме по-късно. +Моля, не бъркайте dependency injection, което е дизайнерски патърн, с „dependency injection container“, което пък е инструмент, т.е. нещо диаметрално различно. Ще се занимаваме с контейнерите по-късно. -От функции към класове .[#toc-from-functions-to-classes] --------------------------------------------------------- +От функции към класове +---------------------- -И как са свързани класовете? Класът е по-сложна единица от простата функция, но правило № 1 важи изцяло и тук. Просто има [повече начини за предаване на аргументи |passing-dependencies]. Например, доста подобно на случая с функцията: +А как това е свързано с класовете? Класът е по-сложна единица от проста функция, но правило № 1 важи изцяло и тук. Само че съществуват [повече начини за предаване на аргументи|passing-dependencies]. Например, доста подобно на случая с функция: ```php -class Math +class Matematika { - public function addition(float $a, float $b): float + public function soucet(float $a, float $b): float { return $a + $b; } } -$math = new Math; -echo $math->addition(23, 1); // 24 +$math = new Matematika; +echo $math->soucet(23, 1); // 24 ``` Или чрез други методи, или директно чрез конструктора: ```php -class Addition +class Soucet { public function __construct( private float $a, @@ -132,26 +132,26 @@ class Addition ) { } - public function calculate(): float + public function spocti(): float { return $this->a + $this->b; } } -$addition = new Addition(23, 1); -echo $addition->calculate(); // 24 +$soucet = new Soucet(23, 1); +echo $soucet->spocti(); // 24 ``` -И двата примера са в пълно съответствие с принципа за инжектиране на зависимости. +И двата примера са напълно в съответствие с dependency injection. -Примери от реалния живот .[#toc-real-life-examples] ---------------------------------------------------- +Реални примери +-------------- -В реалния свят няма да пишете класове за събиране на числа. Нека преминем към практически примери. +В реалния свят няма да пишете класове за събиране на числа. Нека преминем към примери от практиката. -Нека имаме клас `Article`, който представлява публикация в блог: +Нека имаме клас `Article`, представляващ статия в блог: ```php class Article @@ -162,12 +162,12 @@ class Article public function save(): void { - // запазване на статията в базата данни + // запазваме статията в базата данни } } ``` -и употребата му ще бъде следната: +и употребата ще бъде следната: ```php $article = new Article; @@ -176,9 +176,9 @@ $article->content = 'Every year millions of people in ...'; $article->save(); ``` -Методът `save()` ще запази статията в таблица в базата данни. Изпълнението му с помощта на [Nette Database |database:] ще бъде направо лесна работа, ако не беше една засечка: откъде `Article` получава връзката с базата данни, т.е. обект от клас `Nette\Database\Connection`? +Методът `save()` запазва статията в таблица в базата данни. Имплементирането му с помощта на [Nette Database |database:] ще бъде лесно, ако не беше една спънка: откъде `Article` да вземе връзка към базата данни, т.е. обект от клас `Nette\Database\Connection`? -Изглежда, че имаме много възможности. Той може да я вземе от статична променлива някъде. Или да наследи от клас, който предоставя връзка към база данни. Или да се възползва от [един-единствен клас |global-state#Singleton]. Или да използваме така наречените фасади, които се използват в Laravel: +Изглежда, че имаме много възможности. Може да я вземе отнякъде от статична променлива. Или да наследи от клас, който осигурява връзка с базата данни. Или да използва т.нар. [singleton |global-state#Singleton]. Или т.нар. фасади, които се използват в Laravel: ```php use Illuminate\Support\Facades\DB; @@ -199,17 +199,17 @@ class Article } ``` -Чудесно, решихме проблема. +Страхотно, решихме проблема. -Или сме го направили? +Или не? -Нека да си припомним [правило №1: Нека ви бъде предадено |#rule #1: Let It Be Passed to You]: всички зависимости, от които се нуждае класът, трябва да му бъдат предадени. Защото ако нарушим правилото, сме поели по пътя към мръсен код, пълен със скрити зависимости, неразбираемост, а резултатът ще бъде приложение, което ще бъде болезнено за поддържане и разработване. +Да си припомним [#Правило № 1: Нека ви го предадат |#Правило 1: Нека ви го предадат]: всички зависимости, от които класът се нуждае, трябва да му бъдат предадени. Защото ако нарушим правилото, сме поели по пътя към мръсен код, пълен със скрити зависимости, неразбираемост, и резултатът ще бъде приложение, което ще бъде болезнено за поддръжка и разработка. -Потребителят на класа `Article` няма представа къде методът `save()` съхранява статията. В таблица от базата данни? В коя - в производствената или в тестовата? И как може да бъде променена? +Потребителят на класа `Article` не знае къде методът `save()` запазва статията. В таблица в базата данни? В коя, продукционната или тестовата? И как може да се промени това? -Потребителят трябва да погледне как е реализиран методът `save()` и да открие използването на метода `DB::insert()`. И така, той трябва да търси по-нататък, за да открие как този метод получава връзка с база данни. А скритите зависимости могат да образуват доста дълга верига. +Потребителят трябва да погледне как е имплементиран методът `save()` и намира използването на метода `DB::insert()`. Така че трябва да търси по-нататък как този метод си набавя връзка към базата данни. А скритите зависимости могат да образуват доста дълга верига. -В чистия и добре проектиран код никога няма скрити зависимости, фасади на Laravel или статични променливи. В чистия и добре проектиран код се предават аргументи: +В чист и добре проектиран код никога не се срещат скрити зависимости, фасади в стил Laravel или статични променливи. В чист и добре проектиран код се предават аргументи: ```php class Article @@ -224,7 +224,7 @@ class Article } ``` -Още по-практичен подход, както ще видим по-късно, е чрез конструктора: +Още по-практично, както ще видим по-нататък, ще бъде чрез конструктора: ```php class Article @@ -245,11 +245,11 @@ class Article ``` .[note] -Ако сте опитен програмист, може би ще си помислите, че `Article` изобщо не трябва да има метод `save()`; той трябва да представлява чисто информационен компонент, а за съхранението трябва да се грижи отделно хранилище. Това е логично. Но това би ни изкарало далеч извън обхвата на темата, която е инжектиране на зависимости, и усилията да предоставим прости примери. +Ако сте опитен програмист, може би си мислите, че `Article` изобщо не трябва да има метод `save()`, трябва да представлява чисто компонент за данни и за запазването трябва да се грижи отделно репозитори. Това има смисъл. Но така бихме се отклонили твърде много от темата, която е dependency injection, и от стремежа да даваме прости примери. -Ако пишете клас, който изисква например база данни за работата си, не измисляйте откъде да я вземете, а нека тя бъде предадена. Или като параметър на конструктора, или като друг метод. Признайте зависимостите. Допускайте ги в API на класа си. Така ще получите разбираем и предвидим код. +Ако пишете клас, който изисква за дейността си например база данни, не измисляйте откъде да я вземете, а поискайте да ви бъде предадена. Например като параметър на конструктора или друг метод. Признайте зависимостите. Признайте ги в API на вашия клас. Ще получите разбираем и предвидим код. -А какво да кажем за този клас, който регистрира съобщения за грешки: +А какво ще кажете за този клас, който логва съобщения за грешки: ```php class Logger @@ -262,21 +262,21 @@ class Logger } ``` -Какво мислите, спазихме ли [правило № 1: Нека да ти бъде предадено |#rule #1: Let It Be Passed to You]? +Какво мислите, спазихме ли [#Правило № 1: Нека ви го предадат |#Правило 1: Нека ви го предадат]? Не спазихме. -Ключовата информация, т.е. директорията с лог файла, се *получава* от самия клас от константата. +Ключовата информация, т.е. директорията с файла с лога, класът *си набавя сам* от константа. -Вижте примера за използване: +Погледнете примера за употреба: ```php $logger = new Logger; -$logger->log('The temperature is 23 °C'); -$logger->log('The temperature is 10 °C'); +$logger->log('Температурата е 23 °C'); +$logger->log('Температурата е 10 °C'); ``` -Можете ли да отговорите на въпроса къде се записват съобщенията, без да познавате имплементацията? Бихте ли предположили, че съществуването на константата `LOG_DIR` е необходимо за нейното функциониране? А бихте ли могли да създадете втора инстанция, която да записва на друго място? Със сигурност не. +Без да познавате имплементацията, бихте ли могли да отговорите на въпроса къде се записват съобщенията? Би ли ви хрумнало, че за функционирането е необходимо съществуването на константата `LOG_DIR`? И бихте ли могли да създадете втора инстанция, която да записва другаде? Със сигурност не. Нека поправим класа: @@ -295,24 +295,24 @@ class Logger } ``` -Сега класът е много по-разбираем, конфигурируем и следователно по-полезен. +Класът сега е много по-разбираем, конфигурируем и следователно по-полезен. ```php -$logger = new Logger('/path/to/log.txt'); -$logger->log('The temperature is 15 °C'); +$logger = new Logger('/път/към/лог.txt'); +$logger->log('Температурата е 15 °C'); ``` -Но на мен не ми пука! .[#toc-but-i-don-t-care] ----------------------------------------------- +Но това не ме интересува! +------------------------- -*"Когато създавам обект Article и извиквам save(), не искам да се занимавам с базата данни, а само да го запазя в тази, която съм задал в конфигурацията."* +*„Когато създам обект Article и извикам save(), не искам да се занимавам с базата данни, просто искам да се запази в тази, която съм настроил в конфигурацията.“* -*"Когато използвам Logger, искам просто съобщението да бъде записано и не искам да се занимавам с това къде. Нека се използват глобалните настройки."* +*„Когато използвам Logger, просто искам съобщението да се запише и не искам да се занимавам къде. Нека се използва глобалната настройка.“* -Това са валидни забележки. +Това са правилни забележки. -Като пример, нека разгледаме клас, който изпраща бюлетини и записва как е преминал: +Като пример ще покажем клас, който разпраща бюлетини и логва как е минало: ```php class NewsletterDistributor @@ -322,27 +322,27 @@ class NewsletterDistributor $logger = new Logger(/* ... */); try { $this->sendEmails(); - $logger->log('Emails have been sent out'); + $logger->log('Имейлите бяха изпратени'); } catch (Exception $e) { - $logger->log('An error occurred during the sending'); + $logger->log('Възникна грешка при изпращането'); throw $e; } } } ``` -Подобреният `Logger`, който вече не използва константата `LOG_DIR`, изисква посочване на пътя до файла в конструктора. Как да се реши този проблем? Класът `NewsletterDistributor` не се интересува от това къде са записани съобщенията; той просто иска да ги запише. +Подобреният `Logger`, който вече не използва константата `LOG_DIR`, изисква в конструктора да се посочи пътят към файла. Как да решим това? Класът `NewsletterDistributor` изобщо не се интересува къде се записват съобщенията, иска само да ги запише. -Решението отново е в [правило № 1: Нека ви бъде предадено |#rule #1: Let It Be Passed to You]: предайте всички данни, от които класът се нуждае. +Решението е отново [#Правило № 1: Нека ви го предадат |#Правило 1: Нека ви го предадат]: всички данни, от които класът се нуждае, му предаваме. -Значи ли това, че през конструктора предаваме пътя до дневника, който след това използваме при създаването на обекта `Logger`? +Значи това означава, че ще си предадем пътя към лога чрез конструктора, който след това ще използваме при създаването на обекта `Logger`? ```php class NewsletterDistributor { public function __construct( - private string $file, // ⛔ НЕ ПО ТОЗИ НАЧИН! + private string $file, // ⛔ ТАКА НЕ! ) { } @@ -351,7 +351,7 @@ class NewsletterDistributor $logger = new Logger($this->file); ``` -Не, не по този начин! Пътят не принадлежи към данните, от които се нуждае класът `NewsletterDistributor`; всъщност от него се нуждае класът `Logger`. Виждате ли разликата? Класът `NewsletterDistributor` се нуждае от самия логер. Така че това е, което ще предадем: +Така не! Пътят всъщност **не принадлежи** към данните, от които класът `NewsletterDistributor` се нуждае; от тях се нуждае `Logger`. Усещате ли разликата? Класът `NewsletterDistributor` се нуждае от логъра като такъв. Така че ще си го предадем: ```php class NewsletterDistributor @@ -365,35 +365,33 @@ class NewsletterDistributor { try { $this->sendEmails(); - $this->logger->log('Emails have been sent out'); + $this->logger->log('Имейлите бяха изпратени'); } catch (Exception $e) { - $this->logger->log('An error occurred during the sending'); + $this->logger->log('Възникна грешка при изпращането'); throw $e; } } } ``` -Сега от сигнатурите на класа `NewsletterDistributor` става ясно, че регистрирането на данни също е част от неговата функционалност. А задачата да се смени логерът с друг, може би за тестване, е напълно тривиална. -Освен това, ако конструкторът на класа `Logger` се промени, това няма да се отрази на нашия клас. +Сега от сигнатурите на класа `NewsletterDistributor` е ясно, че част от неговата функционалност е и логването. А задачата да се смени логърът с друг, например за тестване, е напълно тривиална. Освен това, ако конструкторът на класа `Logger` се промени, това няма да има никакво влияние върху нашия клас. -Правило № 2: Вземете това, което е ваше .[#toc-rule-2-take-what-s-yours] ------------------------------------------------------------------------- +Правило № 2: Вземи това, което е твое +------------------------------------- -Не се подвеждайте и не си позволявайте да минавате покрай зависимостите на вашите зависимости. Просто предавайте собствените си зависимости. +Не се заблуждавайте и не си предавайте зависимостите на вашите зависимости. Предавайте си само вашите собствени зависимости. -Благодарение на това кодът, използващ други обекти, ще бъде напълно независим от промените в техните конструктори. Неговият API ще бъде по-истински. И най-вече ще бъде тривиално да замените тези зависимости с други. +Благодарение на това кодът, използващ други обекти, ще бъде напълно независим от промените в техните конструктори. Неговото API ще бъде по-вярно. И най-важното, ще бъде тривиално тези зависимости да се заменят с други. -Нов член на семейството .[#toc-new-family-member] -------------------------------------------------- +Нов член на семейството +----------------------- -Екипът на разработчиците реши да създаде втори регистратор, който да записва в базата данни. Затова създаваме клас `DatabaseLogger`. И така, имаме два класа, `Logger` и `DatabaseLogger`, единият записва във файл, а другият в база данни ... не ви ли се струва странно наименованието? -Няма ли да е по-добре да преименуваме `Logger` на `FileLogger`? Определено да. +В екипа за разработка беше взето решение да се създаде втори логър, който записва в база данни. Затова създаваме клас `DatabaseLogger`. Така имаме два класа, `Logger` и `DatabaseLogger`, единият записва във файл, другият в база данни … не ви ли се струва нещо странно в това именуване? Не би ли било по-добре да преименуваме `Logger` на `FileLogger`? Със сигурност да. -Но нека да го направим умно. Създаваме интерфейс под оригиналното име: +Но ще го направим умно. Под оригиналното име ще създадем интерфейс: ```php interface Logger @@ -402,7 +400,7 @@ interface Logger } ``` -... което ще бъде изпълнено и от двата регистратора: +… който и двата логъра ще имплементират: ```php class FileLogger implements Logger @@ -412,17 +410,17 @@ class DatabaseLogger implements Logger // ... ``` -Поради това няма да е необходимо да променяте нищо в останалата част от кода, където се използва логерът. Например конструкторът на класа `NewsletterDistributor` все още ще се задоволява с изискването на `Logger` като параметър. И от нас ще зависи коя инстанция ще подадем. +И благодарение на това няма да е необходимо да се променя нищо в останалата част от кода, където се използва логърът. Например конструкторът на класа `NewsletterDistributor` ще продължи да бъде доволен, че като параметър изисква `Logger`. И ще зависи само от нас коя инстанция ще му предадем. -**Поради това никога не добавяме суфикс `Interface` или префикс `I` към имената на интерфейсите.** В противен случай нямаше да е възможно да се разработи толкова хубав код. +**Затова никога не даваме на имената на интерфейсите суфикс `Interface` или префикс `I`.** В противен случай не би било възможно кодът да се развива толкова добре. -Хюстън, имаме проблем .[#toc-houston-we-have-a-problem] -------------------------------------------------------- +Хюстън, имаме проблем +--------------------- -Въпреки че можем да се справим с една единствена инстанция на логера, независимо дали е базиран на файл или на база данни, в цялото приложение и просто да го предаваме навсякъде, където нещо се регистрира, при класа `Article` е съвсем различно. Създаваме негови екземпляри, когато е необходимо, дори няколко пъти. Как да се справим със зависимостта от базата данни в неговия конструктор? +Докато в цялото приложение можем да се справим с една единствена инстанция на логъра, било то файлов или базиран на данни, и просто го предаваме навсякъде, където нещо се логва, съвсем различно е положението с класа `Article`. Неговите инстанции създаваме според нуждите, дори многократно. Как да се справим със зависимостта от базата данни в неговия конструктор? -Пример за това може да бъде контролер, който трябва да запише статия в базата данни след изпращане на формуляр: +Като пример може да послужи контролер, който след изпращане на формуляр трябва да запази статия в базата данни: ```php class EditController extends Controller @@ -437,30 +435,30 @@ class EditController extends Controller } ``` -Възможното решение е очевидно: предайте обекта на базата данни на конструктора `EditController` и използвайте `$article = new Article($this->db)`. +Възможното решение се натрапва само: ще си предадем обекта на базата данни чрез конструктора в `EditController` и ще използваме `$article = new Article($this->db)`. -Точно както в предишния случай с `Logger` и пътя до файла, това не е правилният подход. Базата данни не е зависима от `EditController`, а от `Article`. Предаването на базата данни противоречи на [правило № 2: вземи това, което е твое |#rule #2: take what's yours]. Ако конструкторът на класа `Article` се промени (добави се нов параметър), ще трябва да промените кода навсякъде, където се създават екземпляри. Ufff. +Точно както в предишния случай с `Logger` и пътя към файла, това не е правилният подход. Базата данни не е зависимост на `EditController`, а на `Article`. Предаването на базата данни следователно противоречи на [Правило № 2: Вземи това, което е твое |#Правило 2: Вземи това което е твое]. Когато конструкторът на класа `Article` се промени (добави се нов параметър), ще бъде необходимо да се коригира и кодът на всички места, където се създават инстанции. Уф. -Хюстън, какво предлагате? +Хюстън, какво предлагаш? -Правило № 3: Оставете фабриката да се справи с това .[#toc-rule-3-let-the-factory-handle-it] --------------------------------------------------------------------------------------------- +Правило № 3: Остави го на фабриката +----------------------------------- -Чрез премахването на скритите зависимости и предаването на всички зависимости като аргументи получихме по-конфигурируеми и гъвкави класове. И затова се нуждаем от нещо друго, което да създава и конфигурира тези по-гъвкави класове за нас. Ще го наречем фабрики. +Като премахнахме скритите зависимости и предаваме всички зависимости като аргументи, получихме по-конфигурируеми и гъвкави класове. И следователно се нуждаем от още нещо, което да ни създаде и конфигурира тези по-гъвкави класове. Ще го наречем фабрики. -Правилото е: ако даден клас има зависимости, оставете създаването на техните инстанции на фабриката. +Правилото гласи: ако класът има зависимости, оставете създаването на техните инстанции на фабрика. -Фабриките са по-интелигентен заместител на оператора `new` в света на инжектирането на зависимости. +Фабриките са по-умната замяна на оператора `new` в света на dependency injection. .[note] -Моля, не бъркайте с шаблона за проектиране *factory method*, който описва специфичен начин за използване на фабрики и не е свързан с тази тема. +Моля, не бъркайте с дизайнерския патърн *factory method*, който описва специфичен начин за използване на фабрики и не е свързан с тази тема. -Фабрика .[#toc-factory] ------------------------ +Фабрика +------- -Фабриката е метод или клас, който създава и конфигурира обекти. Ще назовем класа, произвеждащ `Article`, като `ArticleFactory`, и той може да изглежда по следния начин: +Фабриката е метод или клас, който произвежда и конфигурира обекти. Класът, произвеждащ `Article`, ще наречем `ArticleFactory` и би могъл да изглежда например така: ```php class ArticleFactory @@ -477,7 +475,7 @@ class ArticleFactory } ``` -Използването му в контролера ще бъде следното: +Нейното използване в контролера ще бъде следното: ```php class EditController extends Controller @@ -489,7 +487,7 @@ class EditController extends Controller public function formSubmitted($data) { - // нека фабриката създаде обект + // оставяме фабриката да създаде обекта $article = $this->articleFactory->create(); $article->title = $data->title; $article->content = $data->content; @@ -498,11 +496,11 @@ class EditController extends Controller } ``` -В този момент, ако сигнатурата на конструктора на класа `Article` се промени, единствената част от кода, която трябва да реагира, е самият `ArticleFactory`. Всички останали кодове, работещи с обекти `Article`, като например `EditController`, няма да бъдат засегнати. +Ако в този момент се промени сигнатурата на конструктора на класа `Article`, единствената част от кода, която трябва да реагира на това, е самата фабрика `ArticleFactory`. Целият останал код, който работи с обекти `Article`, като например `EditController`, няма да бъде засегнат по никакъв начин. -Може би се чудите дали всъщност сме подобрили нещата. Обемът на кода се е увеличил и всичко започва да изглежда подозрително сложно. +Може би сега си удряте челото, дали изобщо сме си помогнали. Количеството код нарасна и всичко започва да изглежда подозрително сложно. -Не се притеснявайте, скоро ще стигнем до контейнера Nette DI. А той има няколко трика в ръкава си, които значително ще опростят изграждането на приложения, използващи инжектиране на зависимости. Например, вместо класа `ArticleFactory` ще трябва да [напишете |factory] само [прост интерфейс |factory]: +Не се притеснявайте, скоро ще стигнем до Nette DI контейнера. А той има редица асове в ръкава, с които изграждането на приложения, използващи dependency injection, се опростява неимоверно. Така например, вместо клас `ArticleFactory`, ще е достатъчно [напишете само интерфейс |factory]: ```php interface ArticleFactory @@ -511,18 +509,18 @@ interface ArticleFactory } ``` -Но ние изпреварваме себе си; моля, бъдете търпеливи :-) +Но това е изпреварване, изчакайте още малко :-) -Резюме .[#toc-summary] ----------------------- +Резюме +------ -В началото на тази глава обещахме да ви покажем процес за проектиране на чист код. Всичко, което е необходимо, е класовете да: +В началото на тази глава обещахме, че ще покажем процедура за проектиране на чист код. Достатъчно е на класовете -- [да предават зависимостите, от които се нуждаят |#Rule #1: Let It Be Passed to You] -- [обратно, да не предават това, което не им е пряко необходимо |#Rule #2: Take What's Yours] -- [и че обектите със зависимости е най-добре да се създават във фабрики |#Rule #3: Let the Factory Handle it] +1) [предавайте зависимостите, от които се нуждаят |#Правило 1: Нека ви го предадат] +2) [и обратно, не предавайте това, от което не се нуждаят пряко |#Правило 2: Вземи това което е твое] +3) [и че обектите със зависимости се създават най-добре във фабрики |#Правило 3: Остави го на фабриката] -На пръв поглед тези три правила може да не изглеждат с далечни последици, но те водят до коренно различна перспектива за проектирането на кода. Струва ли си? Разработчиците, които са изоставили старите навици и са започнали последователно да използват инжектиране на зависимости, смятат тази стъпка за решаващ момент в професионалния си живот. Тя е отворила за тях света на ясните и поддържани приложения. +Може да не изглежда така на пръв поглед, но тези три правила имат далечни последици. Водят до радикално различен поглед върху дизайна на кода. Струва ли си? Програмистите, които са изоставили старите навици и са започнали последователно да използват dependency injection, смятат тази стъпка за ключов момент в професионалния си живот. Открил се е пред тях свят на прегледни и поддържаеми приложения. -Но какво става, ако в кода не се използва последователно инжектиране на зависимости? Какво става, ако той разчита на статични методи или единични методи? Това води ли до някакви проблеми? [Да, създава, и то много съществени |global-state]. +Ами ако кодът не използва последователно dependency injection? Ами ако е изграден върху статични методи или сингълтони? Носи ли това някакви проблеми? [Носи и то много съществени |global-state]. diff --git a/dependency-injection/bg/nette-container.texy b/dependency-injection/bg/nette-container.texy index b497926354..801555f8d8 100644 --- a/dependency-injection/bg/nette-container.texy +++ b/dependency-injection/bg/nette-container.texy @@ -1,10 +1,10 @@ -Контейнер Nette DI +Nette DI контейнер ****************** .[perex] -Контейнерът Nette DI е една от най-интересните части на рамката. Той може да генерира компилирани контейнери DI, които са изключително бързи и изненадващо лесни за създаване. +Nette DI е една от най-интересните библиотеки на Nette. Тя може да генерира и автоматично да актуализира компилирани DI контейнери, които са изключително бързи и невероятно лесни за конфигуриране. -Nette DI е библиотека, която предоставя инструменти за генериране, както и за автоматично обновяване на контейнерни класове. Инструктираме го (обикновено) с помощта на конфигурационни файлове. Контейнерът, който създадохме ръчно в [предишния раздел |container], ще бъде записан в конфигурационен формат [NEON |neon:format], както следва: +Формата на сървисите, които DI контейнерът трябва да създава, обикновено дефинираме с помощта на конфигурационни файлове във [формат NEON|neon:format]. Контейнерът, който ръчно създадохме в [предишната глава|container], би се записал така: ```neon parameters: @@ -19,16 +19,15 @@ services: - UserController ``` -Записът е много кратък. +Записът е наистина кратък. -Всички зависимости, декларирани в конструкторите `ArticleFactory` и `UserController`, се дефинират автоматично и се предават на Nette DI благодарение на така нареченото [автоматично свързване |autowiring]. По този начин можете да се съсредоточите върху развитието. -So even if the parameters change, you don't need to change anything in the configuration. Nette will automatically regenerate the container. You can concentrate there purely on application development. +Всички зависимости, декларирани в конструкторите на класовете `ArticleFactory` и `UserController`, Nette DI само открива и предава благодарение на т.нар. [autowiring|autowiring], затова в конфигурационния файл не е необходимо да се посочва нищо. Така че дори ако параметрите се променят, не е необходимо да променяте нищо в конфигурацията. Nette контейнерът автоматично ще се прегенерира. Вие можете да се съсредоточите изцяло върху разработката на приложението. -Ако искате да предадете зависимостите чрез setter, можем да добавим раздел [setup |services#Setup] към дефиницията на услугата. +Ако искаме да предаваме зависимости чрез сетъри, използваме за това секцията [setup |services#Setup]. -Всъщност Nette DI ще генерира PHP кода за контейнера. Така че е изключително бърз, програмистът знае какво точно прави и дори може да стъпи върху него. При по-големите приложения контейнерът може да се състои от десетки хиляди редове и вероятно вече не е възможно да се поддържа нещо подобно ръчно. +Nette DI генерира директно PHP код на контейнера. Резултатът е файл `.php`, който можете да отворите и изучавате. Благодарение на това виждате точно как работи контейнерът. Можете също да го дебъгвате в IDE и да го проследявате стъпка по стъпка. И най-важното: генерираният PHP е изключително бърз. -Nette DI може също така да генерира [фабричен |factory] код въз основа на интерфейса. Така че вместо клас `ArticleFactory` ще трябва да създадете само интерфейс: +Nette DI може също да генерира код на [фабрики|factory] въз основа на предоставен интерфейс. Затова вместо клас `ArticleFactory` ще ни е достатъчно да създадем в приложението само интерфейс: ```php interface ArticleFactory @@ -37,46 +36,45 @@ interface ArticleFactory } ``` -Можете да намерите пълен пример [в GitHub |https://github.com/nette-examples/di-example-doc]. +Целият пример можете да намерите [в GitHub|https://github.com/nette-examples/di-example-doc]. -Използване без рамка .[#toc-standalone-use] -------------------------------------------- +Самостоятелна употреба +---------------------- -Използването на Nette DI в приложение е много лесно. Първо, ще го инсталираме с помощта на Composer (защото изтеглянето на zip файла вече е остаряло): +Внедряването на библиотеката Nette DI в приложение е много лесно. Първо я инсталираме с Composer (защото изтеглянето на zip файлове е тааака остаряло): ```shell composer require nette/di ``` -Ще запазим горната конфигурация във файла `config.neon` и ще създадем контейнер, използвайки класа `Nette\DI\ContainerLoader`: +Следващият код създава инстанция на DI контейнер според конфигурацията, съхранена във файла `config.neon`: ```php $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp'); -$class = $loader->load(function($compiler) { +$class = $loader->load(function ($compiler) { $compiler->loadConfig(__DIR__ . '/config.neon'); }); $container = new $class; ``` -Контейнерът се създава само веднъж, кодът му се записва в кеша (директория `__DIR__ . '/temp'`) и се чете оттам само при бъдещи заявки. +Контейнерът се генерира само веднъж, неговият код се записва в кеша (директория `__DIR__ . '/temp'`) и при следващи заявки се зарежда само оттам. -Методите `getService()` или `getByType()` се използват за създаване и извличане на услуги. По този начин създаваме обекта `UserController`: +За създаване и получаване на сървиси служат методите `getService()` или `getByType()`. Така създаваме обект `UserController`: ```php -$database = $container->getByType(UserController::class); -$database->query('...'); +$controller = $container->getByType(UserController::class); +$controller->someMethod(); ``` -По време на разработката е полезно да активирате режима за автоматично обновяване, при който контейнерът се обновява автоматично при промяна на някой клас или конфигурационен файл. Просто посочете `true` като втори аргумент в конструктора `ContainerLoader`. +По време на разработка е полезно да се активира режимът на автоматично опресняване, при който контейнерът автоматично се прегенерира, ако настъпи промяна в някой клас или конфигурационен файл. Достатъчно е в конструктора на `ContainerLoader` да се посочи като втори аргумент `true`. ```php $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp', true); ``` -Използване с Nette Framework .[#toc-using-it-with-the-nette-framework] ----------------------------------------------------------------------- +Използване с Nette Framework +---------------------------- -Както показахме, използването на Nette DI не е ограничено до приложения, написани в Nette Framework, а можете да го внедрите навсякъде само с 3 реда код. -Ако разработвате приложения в Nette, конфигурирането и създаването на контейнера се извършва с помощта на класа [Bootstrap |application:bootstrap#toc-di-container-configuration]. +Както показахме, използването на Nette DI не е ограничено до приложения, написани в Nette Framework, можете да го внедрите навсякъде само с 3 реда код. Ако обаче разработвате приложения в Nette Framework, конфигурацията и създаването на контейнера се управляват от [Bootstrap |application:bootstrapping#Конфигурация на DI контейнера]. diff --git a/dependency-injection/bg/passing-dependencies.texy b/dependency-injection/bg/passing-dependencies.texy index 079f493c12..0652e8cd2f 100644 --- a/dependency-injection/bg/passing-dependencies.texy +++ b/dependency-injection/bg/passing-dependencies.texy @@ -1,24 +1,24 @@ -Прехвърляне на зависимостта -*************************** +Предаване на зависимости +************************ <div class=perex> -Аргументите или "зависимостите" в терминологията на DI могат да се предават на класовете по следните основни начини: +Аргументите, или в терминологията на DI „зависимости“, могат да се предават на класове по следните основни начини: * предаване чрез конструктор -* предаване по метод (нарича се setter) -* чрез задаване на свойство -* чрез метод, анотация или атрибут *inject +* предаване чрез метод (т.нар. сетър) +* задаване на променлива +* чрез метод, анотация или атрибут *inject* </div> -Сега ще илюстрираме различните варианти с конкретни примери. +Сега ще покажем отделните варианти с конкретни примери. -Внедряване чрез конструктор .[#toc-constructor-injection] -========================================================= +Предаване чрез конструктор +========================== -Зависимостите се предават като аргументи на конструктора при създаването на обекта: +Зависимостите се предават в момента на създаване на обекта като аргументи на конструктора: ```php class MyClass @@ -34,9 +34,9 @@ class MyClass $obj = new MyClass($cache); ``` -Тази форма е полезна за задължителни зависимости, които са абсолютно необходими за функционирането на класа, тъй като без тях не може да се създаде инстанция. +Тази форма е подходяща за задължителни зависимости, от които класът непременно се нуждае за своята функция, тъй като без тях инстанцията няма да може да бъде създадена. -От версия 8.0 на PHP можем да използваме по-кратка форма на запис ([constructor property promotion |https://blog.nette.org/bg/php-8-0-p-len-pregled-na-novostite#toc-constructor-property-promotion]), която е функционално еквивалентна: +От PHP 8.0 можем да използваме по-кратка форма на запис ([constructor property promotion |https://blog.nette.org/bg/php-8-0-complete-overview-of-news#toc-constructor-property-promotion]), която е функционално еквивалентна: ```php // PHP 8.0 @@ -49,7 +49,7 @@ class MyClass } ``` -От PHP 8.1 насам дадено свойство може да бъде маркирано с флага `readonly`, който декларира, че съдържанието на свойството няма да бъде променяно: +От PHP 8.1 променливата може да бъде маркирана с флага `readonly`, който декларира, че съдържанието на променливата няма да се променя повече: ```php // PHP 8.1 @@ -62,13 +62,13 @@ class MyClass } ``` -Контейнерът DI предава зависимостите на конструктора автоматично, като използва [автоматично свързване |autowiring]. Аргументите, които не могат да бъдат предадени по този начин (напр. низове, числа, булеви стойности), [се записват в конфигурацията |services#Arguments]. +DI контейнерът предава зависимостите на конструктора автоматично чрез [autowiring |autowiring]. Аргументите, които не могат да бъдат предадени по този начин (напр. низове, числа, булеви стойности), [записваме в конфигурацията |services#Аргументи]. -Адът на конструкторите .[#toc-constructor-hell] ------------------------------------------------ +Адът на конструктора +-------------------- -Терминът *ад на конструкторите* се отнася до ситуация, при която наследник наследява родителски клас, чийто конструктор изисква зависимости, и наследникът също изисква зависимости. То също трябва да поеме и предаде зависимостите на родителя: +Терминът *constructor hell* описва ситуация, когато наследник наследява от родителски клас, чийто конструктор изисква зависимости, и същевременно наследникът изисква зависимости. При това трябва да приеме и предаде и родителските: ```php abstract class BaseClass @@ -94,11 +94,11 @@ final class MyClass extends BaseClass } ``` -Проблемът възниква, когато искаме да променим конструктора на класа `BaseClass`, например когато се добави нова зависимост. Тогава трябва да променим и всички конструктори на децата. Което превръща подобна модификация в ад. +Проблемът възниква в момента, когато искаме да променим конструктора на класа `BaseClass`, например когато се добави нова зависимост. Тогава е необходимо да се коригират и всички конструктори на наследниците. Което превръща такава корекция в ад. -Как да предотвратим това? Решението е да се даде **приоритет на композицията пред наследяването**. +Как да предотвратим това? Решението е **да се дава предимство на [композиция пред наследяване |faq#Защо се предпочита композиция пред наследяването]**. -Така че нека да проектираме кода по различен начин. Ще избягваме абстрактните класове `Base*`. Вместо `MyClass` да получава някаква функционалност, наследявайки я от `BaseClass`, тя ще има тази функционалност, предадена като зависимост: +Тоест, ще проектираме кода по друг начин. Ще избягваме [абстрактни |nette:introduction-to-object-oriented-programming#Абстрактни класове] `Base*` класове. Вместо `MyClass` да получава определена функционалност чрез наследяване от `BaseClass`, тази функционалност ще му бъде предадена като зависимост: ```php final class SomeFunctionality @@ -125,10 +125,10 @@ final class MyClass ``` -Зависимости чрез задаващи елементи .[#toc-setter-injection] -=========================================================== +Предаване чрез сетър +==================== -Зависимостите се предават чрез извикване на метод, който ги съхранява в частни свойства. Обичайната конвенция за именуване на тези методи е от вида `set*()`, поради което те се наричат сетъри, но, разбира се, могат да се наричат и по друг начин. +Зависимостите се предават чрез извикване на метод, който ги съхранява в частна променлива. Обичайната конвенция за именуване на тези методи е формата `set*()`, затова се наричат сетъри, но разбира се, могат да се наричат и по друг начин. ```php class MyClass @@ -145,9 +145,9 @@ $obj = new MyClass; $obj->setCache($cache); ``` -Този метод е полезен за незадължителни зависимости, които не са необходими за функционирането на класа, тъй като не е гарантирано, че обектът действително ще ги получи (т.е. че потребителят ще извика метода). +Този начин е подходящ за незадължителни зависимости, които не са необходими за функцията на класа, тъй като не е гарантирано, че обектът действително ще получи зависимостта (т.е. че потребителят ще извика метода). -В същото време този метод позволява многократно извикване на setter за промяна на зависимостта. Ако това не е желателно, добавете проверка към метода или, от версия PHP 8.1, маркирайте свойството `$cache` с флага `readonly`. +Същевременно този начин позволява сетърът да се извиква многократно и така зависимостта да се променя. Ако това не е желателно, добавяме проверка в метода, или от PHP 8.1 маркираме свойството `$cache` с флага `readonly`. ```php class MyClass @@ -156,29 +156,28 @@ class MyClass public function setCache(Cache $cache): void { - if ($this->cache) { - throw new RuntimeException('The dependency has already been set'); + if (isset($this->cache)) { + throw new RuntimeException('Зависимостта вече е зададена'); } $this->cache = $cache; } } ``` -Извикването на setter се дефинира в конфигурацията на контейнера DI в [раздела за настройка |services#Setup]. И тук автоматичното предаване на зависимостите се използва чрез autowiring: +Извикването на сетъра дефинираме в конфигурацията на DI контейнера в [ключа setup |services#Setup]. И тук се използва автоматично предаване на зависимости чрез autowiring: ```neon services: - - - create: MyClass + - create: MyClass setup: - setCache ``` -Изпълнение чрез свойства .[#toc-property-injection] -=================================================== +Чрез задаване на променлива +=========================== -Зависимостите се предават директно на свойството: +Зависимостите се предават чрез записване директно в член-променлива: ```php class MyClass @@ -190,28 +189,27 @@ $obj = new MyClass; $obj->cache = $cache; ``` -Този метод се счита за неприемлив, тъй като свойството трябва да бъде декларирано като `public`. Следователно нямаме контрол върху това дали предадената зависимост действително има зададения тип (това беше вярно преди PHP 7.4) и губим възможността да реагираме на новоназначената зависимост със собствен код, например за да предотвратим последващи промени. В същото време свойството става част от публичния интерфейс на класа, което може да е нежелателно. +Този начин се счита за неподходящ, тъй като член-променливата трябва да бъде декларирана като `public`. И следователно нямаме контрол над това, че предадената зависимост ще бъде действително от дадения тип (важеше преди PHP 7.4) и губим възможността да реагираме на новоприсвоената зависимост със собствен код, например да предотвратим последваща промяна. Същевременно променливата става част от публичния интерфейс на класа, което може да не е желателно. -Настройката на променливата се дефинира в конфигурацията на контейнера DI в [раздел настройка |services#Setup]: +Задаването на променливата дефинираме в конфигурацията на DI контейнера в [секцията setup |services#Setup]: ```neon services: - - - create: MyClass + - create: MyClass setup: - $cache = @\Cache ``` -Инжектиране .[#toc-inject] -========================== +Inject +====== -Докато предишните три метода са общовалидни във всички обектно-ориентирани езици, инжектирането чрез метод, анотация или атрибут *inject* е специфично за презентаторите на Nette. Те са разгледани в [отделна глава |best-practices:inject-method-attribute]. +Докато предходните три начина важат общо за всички обектно-ориентирани езици, инжектирането чрез метод, анотация или атрибут *inject* е специфично само за презентерите в Nette. За тях се разказва в [отделна глава |best-practices:inject-method-attribute]. -Кой път да избера? .[#toc-which-way-to-choose] -============================================== +Кой метод да изберем? +===================== -- Конструкторът е подходящ за задължителни зависимости, от които класът се нуждае, за да функционира. -- сетърът, от друга страна, е подходящ за незадължителни зависимости или за зависимости, които могат да бъдат променяни. -- публичните променливи не се препоръчват +- конструкторът е подходящ за задължителни зависимости, от които класът непременно се нуждае за своята функция +- сетърът, напротив, е подходящ за незадължителни зависимости или зависимости, които може да се наложи да се променят по-нататък +- публичните променливи не са подходящи diff --git a/dependency-injection/bg/services.texy b/dependency-injection/bg/services.texy index 44bb7797e9..3328859a8f 100644 --- a/dependency-injection/bg/services.texy +++ b/dependency-injection/bg/services.texy @@ -1,33 +1,33 @@ -Определения на услуги +Дефиниране на сървиси ********************* .[perex] -Конфигурацията е мястото, където поставяме дефинициите на потребителските услуги. Това е направено в раздела `services`. +Конфигурацията е мястото, където учим DI контейнера как да изгражда отделните сървиси и как да ги свързва с други зависимости. Nette предоставя много прегледен и елегантен начин да се постигне това. -Например, така създаваме услуга с име `database`, която ще бъде инстанция на класа `PDO`: +Секцията `services` в конфигурационния файл във формат NEON е мястото, където дефинираме собствени сървиси и техните конфигурации. Нека разгледаме прост пример за дефиниция на сървис, наречен `database`, който представлява инстанция на класа `PDO`: ```neon services: database: PDO('sqlite::memory:') ``` -Наименованията на услугите се използват, за да можем да [се обръщаме към |#Referencing-Services] тях. Ако услугата не се споменава, не е необходимо да ѝ давате име. Затова просто използваме двоеточие вместо име: +Посочената конфигурация ще доведе до следния фабричен метод в [DI контейнера|container]: -```neon -services: - - PDO('sqlite::memory:') # анонимный сервис +```php +public function createServiceDatabase(): PDO +{ + return new PDO('sqlite::memory:'); +} ``` -Запис на един ред може да бъде разделен на няколко реда, за да се позволи добавянето на допълнителни клавиши, напр. за [настройка |#setup]. Псевдонимът на ключа `create:` е `factory:`. +Имената на сървисите ни позволяват да се позоваваме на тях в други части на конфигурационния файл, във формат `@имеНаСървис`. Ако не е необходимо сървисът да се именува, можем просто да използваме само тире: ```neon services: - database: - create: PDO('sqlite::memory:') - setup: ... + - PDO('sqlite::memory:') ``` -След това извличаме услугата от контейнера DI, като използваме метода `getService()` по име или още по-добре - метода `getByType()` по тип: +За да получим сървис от DI контейнера, можем да използваме метода `getService()` с името на сървиса като параметър, или метода `getByType()` с типа на сървиса: ```php $database = $container->getService('database'); @@ -35,26 +35,28 @@ $database = $container->getByType(PDO::class); ``` -Създаване на услуга .[#toc-creating-a-service] -============================================== +Създаване на сървис +=================== -В повечето случаи създаваме услуга, като просто създаваме инстанция на класа: +Обикновено създаваме сървис просто като създадем инстанция на определен клас. Например: ```neon services: database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -Което ще създаде фабричен метод в [DI-контейнера |container]: +Ако е необходимо да разширим конфигурацията с допълнителни ключове, дефиницията може да се разпише на няколко реда: -```php -public function createServiceDatabase(): PDO -{ - return new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'secret'); -} +```neon +services: + database: + create: PDO('sqlite::memory:') + setup: ... ``` -Алтернативно, ключът `arguments` може да се използва за предаване на [аргументи |#Arguments]: +Ключът `create` има псевдоним `factory`, и двата варианта са често срещани в практиката. Въпреки това препоръчваме да използвате `create`. + +Аргументите на конструктора или създаващия метод могат алтернативно да бъдат записани в ключа `arguments`: ```neon services: @@ -63,294 +65,303 @@ services: arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret] ``` -Статичен метод също може да създаде услуга: +Сървисите не е задължително да се създават само чрез просто създаване на инстанция на клас, те могат да бъдат и резултат от извикване на статични методи или методи на други сървиси: ```neon services: - database: My\Database::create(root, secret) + database: DatabaseFactory::create() + router: @routerFactory::create() ``` -Това е еквивалентно на код на PHP: +Обърнете внимание, че за простота вместо `->` се използва `::`, вижте [#изразителни средства]. Ще се генерират тези фабрични методи: ```php public function createServiceDatabase(): PDO { - return My\Database::create('root', 'secret'); + return DatabaseFactory::create(); +} + +public function createServiceRouter(): RouteList +{ + return $this->getService('routerFactory')->create(); } ``` -Предполага се, че статичният метод `My\Database::create()` има определена връщана стойност, която контейнерът DI трябва да знае. Ако няма такъв, записваме типа в конфигурацията: +DI контейнерът трябва да знае типа на създадения сървис. Ако създаваме сървис чрез метод, който няма указан тип на връщаната стойност, трябва изрично да посочим този тип в конфигурацията: ```neon services: database: - create: My\Database::create(root, secret) + create: DatabaseFactory::create() type: PDO ``` -Nette DI ви предоставя изключително мощни инструменти за изразяване, които ви позволяват да пишете почти всичко. Например за [препращане към |#Referencing-Services] друга услуга и извикване на нейния метод. За улеснение се използва `::` вместо `->`. + +Аргументи +========= + +Предаваме аргументи на конструктора и методите по начин, много подобен на самия PHP: ```neon services: - routerFactory: App\Router\Factory - router: @routerFactory::create() + database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -Това е еквивалентно на код на PHP: - -```php -public function createServiceRouterFactory(): App\Router\Factory -{ - return new App\Router\Factory; -} +За по-добра четимост можем да разпишем аргументите на отделни редове. В такъв случай използването на запетаи е по избор: -public function createServiceRouter(): Router -{ - return $this->getService('routerFactory')->create(); -} +```neon +services: + database: PDO( + 'mysql:host=127.0.0.1;dbname=test' + root + secret + ) ``` -Извикванията на методи могат да бъдат верижно свързани, както в PHP: +Можете също да именувате аргументите и тогава не е нужно да се притеснявате за техния ред: ```neon services: - foo: FooFactory::build()::get() + database: PDO( + username: root + password: secret + dsn: 'mysql:host=127.0.0.1;dbname=test' + ) ``` -Това е еквивалентно на код на PHP: +Ако искате да пропуснете някои аргументи и да използвате тяхната стойност по подразбиране или да вмъкнете сървис чрез [autowiring|autowiring], използвайте долна черта: -```php -public function createServiceFoo() -{ - return FooFactory::build()->get(); -} +```neon +services: + foo: Foo(_, %appDir%) ``` +Като аргументи могат да се предават сървиси, да се използват параметри и много повече, вижте [#изразителни средства]. + -Аргументи .[#toc-arguments] -=========================== +Setup +===== -Именуваните параметри могат да се използват и за предаване на аргументи: +В секцията `setup` дефинираме методите, които трябва да се извикат при създаването на сървиса. ```neon services: - database: PDO( - 'mysql:host=127.0.0.1;dbname=test' # позиционен - username: root # named - password: secret # named - ) + database: + create: PDO(%dsn%, %user%, %password%) + setup: + - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) ``` -Използването на запетаи не е задължително, когато аргументите се разделят на няколко реда. +Това в PHP би изглеждало така: -Разбира се, можем да използваме и [други услуги |#Referencing-Services] или [параметри |configuration#Parameters] като аргументи: +```php +public function createServiceDatabase(): PDO +{ + $service = new PDO('...', '...', '...'); + $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + return $service; +} +``` + +Освен извикване на методи, може също да се предават стойности на свойства. Поддържа се и добавяне на елемент към масив, което трябва да се запише в кавички, за да не колидира със синтаксиса на NEON: ```neon services: - - Foo(@anotherService, %appDir%) + foo: + create: Foo + setup: + - $value = 123 + - '$onClick[]' = [@bar, clickHandler] ``` -Съответства на кода на PHP: +Което в PHP кода би изглеждало по следния начин: ```php -public function createService01(): Foo +public function createServiceFoo(): Foo { - return new Foo($this->getService('anotherService'), '...'); + $service = new Foo; + $service->value = 123; + $service->onClick[] = [$this->getService('bar'), 'clickHandler']; + return $service; } ``` -Ако първият аргумент е [автоматично монтиран |autowiring] и искате да посочите втория аргумент, пропуснете първия, като използвате `_`, например `Foo(_, %appDir%)`. Или, още по-добре, предайте само втория аргумент като именуван параметър, например: `Foo(path: %appDir%)`. - -Nette DI и форматът NEON ви дават изключително мощно средство за изразяване, което ви позволява да напишете почти всичко. По този начин аргументът може да бъде новосъздаден обект, можете да извикате статични методи, методи от други услуги или дори глобални функции, като използвате специална нотация: +В setup обаче могат да се извикват и статични методи или методи на други сървиси. Ако е необходимо да предадете като аргумент текущия сървис, посочете го като `@self`: ```neon services: - analyser: My\Analyser( - FilesystemIterator(%appDir%) # създаване на обект - DateTime::createFromFormat('Y-m-d') # извикване на статичен метод - @anotherService # предаване на друга услуга - @http.request::getRemoteAddress() # извикване на друг метод на услугата - ::getenv(NetteMode) # извикване на глобална функция - ) + foo: + create: Foo + setup: + - My\Helpers::initializeFoo(@self) + - @anotherService::setFoo(@self) ``` -Съответства на кода на PHP: +Обърнете внимание, че за простота вместо `->` се използва `::`, вижте [#изразителни средства]. Ще се генерира такъв фабричен метод: ```php -public function createServiceAnalyser(): My\Analyser +public function createServiceFoo(): Foo { - return new My\Analyser( - new FilesystemIterator('...'), - DateTime::createFromFormat('Y-m-d'), - $this->getService('anotherService'), - $this->getService('http.request')->getRemoteAddress(), - getenv('NetteMode') - ); + $service = new Foo; + My\Helpers::initializeFoo($service); + $this->getService('anotherService')->setFoo($service); + return $service; } ``` -Специални функции .[#toc-special-functions] -------------------------------------------- - -Можете също така да използвате специални функции в аргументите, за да изравнявате или отричате стойности: +Изразителни средства +==================== -- `not(%arg%)` отрицание -- `bool(%arg%)` преобразуване на bool без загуби -- `int(%arg%)` преобразуване на int без загуби -- `float(%arg%)` преобразуване на плаващо състояние без загуби -- `string(%arg%)` преобразуване на низове без загуби +Nette DI ни дава изключително богати изразителни средства, с помощта на които можем да запишем почти всичко. В конфигурационните файлове така можем да използваме [параметри |configuration#Параметри]: ```neon -services: - - Foo( - id: int(::getenv('ProjectId')) - productionMode: not(%debugMode%) - ) -``` - -Презаписването без загуби се различава от нормалното презаписване в PHP, например с помощта на `(int)`, по това, че хвърля изключение за нецифрови стойности. +# параметър +%wwwDir% -Като аргументи могат да бъдат подадени няколко услуги. Масив от всички услуги от определен тип (т.е. клас или интерфейс) може да бъде създаден чрез функцията `typed()`. Функцията ще пропусне услуги, при които автоматичното свързване е забранено, и могат да бъдат зададени няколко типа, разделени със запетая. +# стойност на параметър под ключ +%mailer.user% -```neon -services: - - BarsDependent( typed(Bar) ) +# параметър вътре в низ +'%wwwDir%/images' ``` -Можете също така да предадете масив от услуги автоматично, като използвате [автоматично свързване |autowiring#Collection-of-Services]. - -Чрез функцията `tagged()` се създава масив от всички услуги с определен [етикет |#Tags]. Можете да посочите няколко тага, разделени със запетая. +Освен това да създаваме обекти, да извикваме методи и функции: ```neon -services: - - LoggersDependent( tagged(logger) ) -``` +# създаване на обект +DateTime() +# извикване на статичен метод +Collator::create(%locale%) -Връзки към услуги .[#toc-referencing-services] -============================================== +# извикване на PHP функция +::getenv(DB_USER) +``` -Връзките към отделните услуги се използват със символа `@` и имени, например `@database`: +Да се позоваваме на сървиси или по тяхното име, или чрез типа: ```neon -services: - - create: Foo(@database) - setup: - - setCacheStorage(@cache.storage) +# сървис по име +@database + +# сървис по тип +@Nette\Database\Connection ``` -Съответства на кода на PHP: +Да използваме first-class callable синтаксис: .{data-version:3.2.0} -```php -public function createService01(): Foo -{ - $service = new Foo($this->getService('database')); - $service->setCacheStorage($this->getService('cache.storage')); - return $service; -} +```neon +# създаване на callback, аналог на [@user, logout] +@user::logout(...) ``` -Дори към анонимни услуги може да се направи препратка чрез обратно извикване, като вместо името им се посочи техният тип (клас или интерфейс). Това обаче обикновено не е необходимо поради [автоматичното препращане |autowiring]. +Да използваме константи: ```neon -services: - - create: Foo(@Nette\Database\Connection) # или @\PDO - setup: - - setCacheStorage(@cache.storage) +# константа на клас +FilesystemIterator::SKIP_DOTS + +# глобална константа се получава с PHP функцията constant() +::constant(PHP_VERSION) ``` +Извикванията на методи могат да се верижат точно както в PHP. Само за простота вместо `->` се използва `::`: -Настройка .[#toc-setup] -======================= +```neon +DateTime()::format('Y-m-d') +# PHP: (new DateTime())->format('Y-m-d') -В раздела `setup` са изброени методите, които ще бъдат извикани при създаването на услугата: +@http.request::getUrl()::getHost() +# PHP: $this->getService('http.request')->getUrl()->getHost() +``` + +Тези изрази можете да използвате навсякъде, при [създаване на сървиси |#Създаване на сървис], в [#аргументи], в секцията [#setup] или [параметри |configuration#Параметри]: ```neon +parameters: + ipAddress: @http.request::getRemoteAddress() + services: database: - create: PDO(%dsn%, %user%, %password%) + create: DatabaseFactory::create( @anotherService::getDsn() ) setup: - - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) + - initialize( ::getenv('DB_USER') ) ``` -Съответства на кода на PHP: -```php -public function createServiceDatabase(): PDO -{ - $service = new PDO('...', '...', '...'); - $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - return $service; -} -``` +Специални функции +----------------- -Могат да се задават и свойства. Добавянето на елемент в масив също се поддържа и трябва да се запише в кавички, за да не противоречи на синтаксиса на NEON: +В конфигурационните файлове можете да използвате тези специални функции: +- `not()` отрицание на стойност +- `bool()`, `int()`, `float()`, `string()` преобразуване без загуба към дадения тип +- `typed()` създава масив от всички сървиси от указания тип +- `tagged()` създава масив от всички сървиси с дадения таг ```neon services: - foo: - create: Foo - setup: - - $value = 123 - - '$onClick[]' = [@bar, clickHandler] + - Foo( + id: int(::getenv('ProjectId')) + productionMode: not(%debugMode%) + ) ``` -Съответства на кода на PHP: +В сравнение с класическото преобразуване в PHP, като например `(int)`, преобразуването без загуба ще хвърли изключение за нечислови стойности. -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - $service->value = 123; - $service->onClick[] = [$this->getService('bar'), 'clickHandler']; - return $service; -} +Функцията `typed()` създава масив от всички сървиси от дадения тип (клас или интерфейс). Пропуска сървисите, които имат изключен autowiring. Могат да се посочат и повече типове, разделени със запетая. + +```neon +services: + - BarsDependent( typed(Bar) ) ``` -В конфигурацията обаче могат да се извикват и статични методи или методи на други услуги. Ние им предаваме действителната услуга като `@self`: +Масив от сървиси от определен тип можете да предавате като аргумент и автоматично чрез [autowiring |autowiring#Масив от сървиси]. +Функцията `tagged()` пък създава масив от всички сървиси с определен таг. И тук можете да специфицирате повече тагове, разделени със запетая. ```neon services: - foo: - create: Foo - setup: - - My\Helpers::initializeFoo(@self) - - @anotherService::setFoo(@self) + - LoggersDependent( tagged(logger) ) ``` -Съответства на кода на PHP: -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - My\Helpers::initializeFoo($service); - $this->getService('anotherService')->setFoo($service); - return $service; -} +Autowiring +========== + +Ключът `autowired` позволява да се повлияе на поведението на autowiring за конкретен сървис. За детайли вижте [глава за autowiring|autowiring]. + +```neon +services: + foo: + create: Foo + autowired: false # сървисът foo е изключен от autowiring ``` -Автоматично обвързване .[#toc-autowiring] -========================================= +Lazy сървиси .{data-version:3.2.4} +================================== -Ключът `autowired` може да се използва за изключване на услуга от автоматично свързване или за повлияване на нейното поведение. За повече информация вижте глава [Автоматично обвързване |autowiring]. +Lazy loading е техника, която отлага създаването на сървис до момента, в който той действително е необходим. В глобалната конфигурация може да се [активиране на lazy създаване |configuration#Lazy сървиси] за всички сървиси наведнъж. За отделни сървиси след това можете да презапишете това поведение: ```neon services: foo: create: Foo - autowired: false # foo е премахнат от автовръзката + lazy: false ``` +Когато сървисът е дефиниран като lazy, при неговото изискване от DI контейнера получаваме специален прокси обект. Той изглежда и се държи точно като реалния сървис, но реалната инициализация (извикване на конструктора и setup) се извършва едва при първото извикване на някой от неговите методи или свойства. -Етикети .[#toc-tags] -==================== +.[note] +Lazy loading може да се използва само за потребителски класове, а не за вътрешни PHP класове. Изисква PHP 8.4 или по-нова версия. + + +Тагове +====== -Информацията за потребителя може да бъде добавена към отделните услуги под формата на тагове: +Таговете служат за добавяне на допълнителна информация към сървисите. На сървис можете да добавите един или повече тагове: ```neon services: @@ -360,7 +371,7 @@ services: - cached ``` -Етикетите също могат да бъдат значими: +Таговете могат също да носят стойности: ```neon services: @@ -370,26 +381,26 @@ services: logger: monolog.logger.event ``` -Масив от услуги с дефинирани тагове може да бъде предаден като аргумент с помощта на функцията `tagged()`. Могат да бъдат зададени и няколко тага, разделени със запетая. +За да получите всички сървиси с определени тагове, можете да използвате функцията `tagged()`: ```neon services: - LoggersDependent( tagged(logger) ) ``` -Имената на услугите могат да бъдат получени от контейнера DI чрез метода `findByTag()`: +В DI контейнера можете да получите имената на всички сървиси с определен таг чрез метода `findByTag()`: ```php $names = $container->findByTag('logger'); -// $names е масив, съдържащ името на услугата и стойността на тага -// например ['foo' => 'monolog.logger.event', ...] +// $names е масив, съдържащ името на сървиса и стойността на тага +// напр. ['foo' => 'monolog.logger.event', ...] ``` -Режим на изпълнение .[#toc-inject-mode] -======================================= +Режим Inject +============ -Флагът `inject: true` се използва за активиране на прехвърлянето на зависимости чрез публични променливи с помощта на анотацията [inject |best-practices:inject-method-attribute#Inject Attributes] и методите [inject*() |best-practices:inject-method-attribute#inject Methods]. +С помощта на флага `inject: true` се активира предаването на зависимости чрез публични променливи с анотация [inject |best-practices:inject-method-attribute#Атрибути Inject] и методи [inject*() |best-practices:inject-method-attribute#Методи inject]. ```neon services: @@ -398,13 +409,13 @@ services: inject: true ``` -По подразбиране `inject` е активиран само за предварително засяване. +По подразбиране `inject` е активирано само за презентери. -Промяна на услугите .[#toc-modification-of-services] -==================================================== +Модификация на сървиси +====================== -В контейнера DI има редица услуги, които са добавени от вграденото или [вашето разширение |#di-extension]. Дефинициите на тези услуги могат да се променят в конфигурацията. Например за услугата `application.application`, която по подразбиране е обектът `Nette\Application\Application`, можем да променим класа: +DI контейнерът съдържа много сървиси, които са били добавени чрез вградено или [потребителско разширение|extensions]. Можете да променяте дефинициите на тези сървиси директно в конфигурацията. Например, можете да промените класа на сървиса `application.application`, който стандартно е `Nette\Application\Application`, на друг: ```neon services: @@ -413,9 +424,9 @@ services: alteration: true ``` -Флагът `alteration` е информативен и показва, че просто модифицираме съществуваща услуга. +Флагът `alteration` е информативен и казва, че само модифицираме съществуващ сървис. -Можем да добавим и `setup`: +Можем също да допълним setup: ```neon services: @@ -426,7 +437,7 @@ services: - '$onStartup[]' = [@resource, init] ``` -При пренаписване на услугата можем да изтрием първоначалните аргументи, елементи на `setup` или тагове, за които е `reset`: +При презаписване на сървис можем да искаме да премахнем оригиналните аргументи, елементи от setup или тагове, за което служи `reset`: ```neon services: @@ -439,7 +450,7 @@ services: - tags ``` -Услуга, добавена от разширение, може също да бъде премахната от контейнера: +Ако искате да премахнете сървис, добавен от разширение, можете да го направите така: ```neon services: diff --git a/dependency-injection/cs/@home.texy b/dependency-injection/cs/@home.texy index 15748c0a9d..4386fac50d 100644 --- a/dependency-injection/cs/@home.texy +++ b/dependency-injection/cs/@home.texy @@ -1,5 +1,5 @@ -Dependency Injection -******************** +Nette DI +******** .[perex] Dependency Injection je návrhový vzor, který zásadně změní váš pohled na kód a vývoj. Otevře vám cestu do světa čistě navržených a udržitelných aplikací. @@ -11,9 +11,6 @@ Dependency Injection je návrhový vzor, který zásadně změní váš pohled n - [Často kladené otázky|faq] -Nette DI --------- - Balíček `nette/di` poskytuje nesmírně pokročilý kompilovaný DI kontejner pro PHP. - [Nette DI Container |nette-container] diff --git a/dependency-injection/cs/@meta.texy b/dependency-injection/cs/@meta.texy new file mode 100644 index 0000000000..462d9add80 --- /dev/null +++ b/dependency-injection/cs/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokumentace}} diff --git a/dependency-injection/cs/autowiring.texy b/dependency-injection/cs/autowiring.texy index 3b2b4f8e0a..a03edb3d11 100644 --- a/dependency-injection/cs/autowiring.texy +++ b/dependency-injection/cs/autowiring.texy @@ -39,7 +39,7 @@ services: articles: Model\ArticleRepository # VYHODÍ VÝJIMKU, vyhovuje mainDb i tempDb ``` -Řešením by bylo buď autowiring obejít a explicitně uvést název služby (tj `articles: Model\ArticleRepository(@mainDb)`). Šikovnější ale je autowirování jedné ze služeb [vypnout|#Vypnutí autowiringu], nebo první službu [upřednostnit|#Preference autowiringu]. +Řešením by bylo buď autowiring obejít a explicitně uvést název služby (tj `articles: Model\ArticleRepository(@mainDb)`). Šikovnější ale je autowirování jedné ze služeb [vypnout |#Vypnutí autowiringu], nebo první službu [upřednostnit |#Preference autowiringu]. Vypnutí autowiringu @@ -61,8 +61,7 @@ services: Služba `articles` nevyhodí výjimku, že existují dvě vyhovující služby typu `PDO` (tj. `mainDb` a `tempDb`), které lze do konstruktoru předat, protože vidí jen službu `mainDb`. .[note] -Konfigurace autowiringu v Nette funguje odlišně než v Symfony, kde volba `autowire: false` říká, že se nemá autowiring používat pro argumenty konstruktoru dané služby. -V Nette se autowiring používá vždy, ať už pro argumenty konstruktoru, nebo kterékoliv jiné metody. Volba `autowired: false` říká, že instance dané služba nemá být pomocí autowiringu nikam předávána. +Konfigurace autowiringu v Nette funguje odlišně než v Symfony, kde volba `autowire: false` říká, že se nemá autowiring používat pro argumenty konstruktoru dané služby. V Nette se autowiring používá vždy, ať už pro argumenty konstruktoru, nebo kterékoliv jiné metody. Volba `autowired: false` říká, že instance dané služba nemá být pomocí autowiringu nikam předávána. Preference autowiringu @@ -105,14 +104,13 @@ class ShipManager DI kontejner pak automaticky předá pole služeb odpovídajících danému typu. Vynechá služby, které mají vypnutý autowiring. -Pokud nemůžete ovlivnit podobu phpDoc komentáře, můžete předat pole služeb přímo v konfiguraci pomocí [`typed()`|services#Speciální funkce]. +Typ v komentáři může být také ve tvaru `array<int, Class>` nebo `list<Class>`. Pokud nemůžete ovlivnit podobu phpDoc komentáře, můžete předat pole služeb přímo v konfiguraci pomocí [`typed()` |services#Speciální funkce]. Skalární argumenty ------------------ -Autowiring umí dosazovat pouze objekty a pole objektů. Skalární argumenty (např. řetězce, čísla, booleany) [zapíšeme v konfiguraci |services#Argumenty]. -Alternativnou je vytvořit [settings-objekt |best-practices:passing-settings-to-presenters], který skalární hodnotu (nebo více hodnot) zapouzdří do podoby objektu, a ten pak lze opět předávat pomocí autowiringu. +Autowiring umí dosazovat pouze objekty a pole objektů. Skalární argumenty (např. řetězce, čísla, booleany) [zapíšeme v konfiguraci |services#Argumenty]. Alternativnou je vytvořit [settings-objekt |best-practices:passing-settings-to-presenters], který skalární hodnotu (nebo více hodnot) zapouzdří do podoby objektu, a ten pak lze opět předávat pomocí autowiringu. ```php class MySettings diff --git a/dependency-injection/cs/configuration.texy b/dependency-injection/cs/configuration.texy index a42f461c3c..d89e0ab829 100644 --- a/dependency-injection/cs/configuration.texy +++ b/dependency-injection/cs/configuration.texy @@ -8,7 +8,7 @@ Přehled konfiguračních voleb pro Nette DI kontejner. Konfigurační soubor =================== -Nette DI kontejner se snadno ovládá pomocí konfiguračních souborů. Ty se obvykle zapisují ve [formátu NEON|neon:format]. K editaci doporučujeme [editory s podporou|best-practices:editors-and-tools#ide-editor] tohoto formátu. +Nette DI kontejner se snadno ovládá pomocí konfiguračních souborů. Ty se obvykle zapisují ve [formátu NEON|neon:format]. K editaci doporučujeme [editory s podporou |best-practices:editors-and-tools#IDE editor] tohoto formátu. <pre> "decorator .[prism-token prism-atrule]":[#decorator]: "Dekorátor .[prism-token prism-comment]"<br> @@ -20,7 +20,8 @@ Nette DI kontejner se snadno ovládá pomocí konfiguračních souborů. Ty se o "services .[prism-token prism-atrule]":[services]: "Služby .[prism-token prism-comment]" </pre> -Chcete-li zapsat řetězec obsahující znak `%`, musíte jej escapovat zdvojením na `%%`. .[note] +.[note] +Chcete-li zapsat řetězec obsahující znak `%`, musíte jej escapovat zdvojením na `%%`. Parametry @@ -67,13 +68,13 @@ Jak upravit hromadně všechny služby určitého typu? Třeba zavolat určitou ```neon decorator: # u všech služeb, co jsou instancí této třídy nebo rozhraní - App\Presenters\BasePresenter: + App\Presentation\BasePresenter: setup: - setProjectId(10) # zavolej tuto metodu - $absoluteUrls = true # a nastav proměnnou ``` -Decorator se dá používat také pro nastavení [tagů|services#Tagy] nebo zapnutí režimu [inject|services#Režim Inject]. +Decorator se dá používat také pro nastavení [tagů |services#Tagy] nebo zapnutí režimu [inject |services#Režim Inject]. ```neon decorator: @@ -96,11 +97,25 @@ di: # typy parametrů, které nikdy neautowirovat excluded: ... # (string[]) + # povolit lazy vytváření služeb? + lazy: ... # (bool) výchozí je false + # třída, od které dědí DI kontejner parentClass: ... # (string) výchozí je Nette\DI\Container ``` +Lazy služby .{data-version:3.2.4} +--------------------------------- + +Nastavení `lazy: true` aktivuje lazy (odložené) vytváření služeb. To znamená, že služby nejsou skutečně vytvořeny v okamžiku, kdy si je vyžádáme z DI kontejneru, ale až ve chvíli jejich prvního použití. Což může zrychlit start aplikace a snížit paměťové nároky, protože se vytváří jen ty služby, které jsou v daném requestu skutečně potřeba. + +U konkrétní služby lze lazy vytváření [změnit |services#Lazy služby]. + +.[note] +Lazy objekty lze použít pouze pro uživatelské třídy, nikoliv pro interní PHP třídy. Vyžaduje PHP 8.4 nebo novější. + + Export metadat -------------- @@ -122,11 +137,9 @@ di: - Symfony\Component\Console\Application ``` -Pokud nevyužíváte pole `$container->parameters`, můžete vypnout export parametrů. Dále můžete exportovat jen ty tagy, přes které získáváte služby metodou `$container->findByTag(...)`. -Pokud metodu nevoláte vůbec, můžete zcela vypnout export tagů pomocí `false`. +Pokud nevyužíváte pole `$container->getParameters()`, můžete vypnout export parametrů. Dále můžete exportovat jen ty tagy, přes které získáváte služby metodou `$container->findByTag(...)`. Pokud metodu nevoláte vůbec, můžete zcela vypnout export tagů pomocí `false`. -Výrazně můžete zredukovat metadata pro [autowiring] tím, že uvedete třídy, které používáte jako parametr metody `$container->getByType()`. -A opět, pokud metodu nevoláte vůbec (resp. jen v [bootstrapu|application:bootstrap] pro získání `Nette\Application\Application`), můžete export úplně vypnout pomocí `false`. +Výrazně můžete zredukovat metadata pro [autowiring] tím, že uvedete třídy, které používáte jako parametr metody `$container->getByType()`. A opět, pokud metodu nevoláte vůbec (resp. jen v [bootstrapu|application:bootstrapping] pro získání `Nette\Application\Application`), můžete export úplně vypnout pomocí `false`. Rozšíření @@ -191,20 +204,15 @@ Stačí uvést, ve kterých adresářích (a podadresářích) má třídy hleda ```neon search: - # názvy sekcí si volíte sami - formuláře: - in: %appDir%/Forms - - model: - in: %appDir%/Model + - in: %appDir%/Forms + - in: %appDir%/Model ``` Obvykle ovšem nechceme přidávat úplně všechny třídy a rozhraní, proto je můžeme filtrovat: ```neon search: - formuláře: - in: %appDir%/Forms + - in: %appDir%/Forms # filtrování podle názvu souboru (string|string[]) files: @@ -220,7 +228,7 @@ Nebo můžeme vybírat třídy, které dědí či implementují alespoň jednu z ```neon search: - formuláře: + - in: %appDir% extends: - App\*Form implements: @@ -231,8 +239,9 @@ Lze definovat i vylučující pravidla, tj. masky názvu třídy nebo dědičné ```neon search: - formuláře: + - in: %appDir% exclude: + files: ... classes: ... extends: ... implements: ... @@ -242,7 +251,7 @@ Všem službám lze nastavit tagy: ```neon search: - formuláře: + - in: %appDir% tags: ... ``` diff --git a/dependency-injection/cs/container.texy b/dependency-injection/cs/container.texy index 3583a27dad..0f861f22c6 100644 --- a/dependency-injection/cs/container.texy +++ b/dependency-injection/cs/container.texy @@ -41,7 +41,7 @@ $controller = $container->createUserController(); Kontejneru se pouze zeptáme na objekt a již nemusíme vědět nic o tom, jak jej vytvořit a jaké má závislosti; to všechno ví kontejner. Závislosti jsou kontejnerem injektovány automaticky. V tom je jeho síla. -Kontejner má zatím zapsané všechny údaje navrdo. Uděláme tedy další krok a přidáme parametry, aby byl kontejner skutečně užitečný: +Kontejner má zatím zapsané všechny údaje natvrdo. Uděláme tedy další krok a přidáme parametry, aby byl kontejner skutečně užitečný: ```php class Container diff --git a/dependency-injection/cs/extensions.texy b/dependency-injection/cs/extensions.texy index 873663753d..1263e7deff 100644 --- a/dependency-injection/cs/extensions.texy +++ b/dependency-injection/cs/extensions.texy @@ -50,7 +50,7 @@ class BlogExtension extends Nette\DI\CompilerExtension } ``` -Dokumentaci najdete na stránce [Schema |schema:]. Navíc lze určit, které volby mohou být [dynamické|application:bootstrap#Dynamické parametry] pomocí `dynamic()`, např. `Expect::int()->dynamic()`. +Dokumentaci najdete na stránce [Schema |schema:]. Navíc lze určit, které volby mohou být [dynamické |application:bootstrapping#Dynamické parametry] pomocí `dynamic()`, např. `Expect::int()->dynamic()`. Ke konfiguraci se dostaneme přes proměnnou `$this->config`, což je objekt `stdClass`: @@ -153,7 +153,7 @@ class BlogExtension extends Nette\DI\CompilerExtension afterCompile() .[method] ======================== -V této fázi už je třída kontejneru vygenerována v podobě objektu [ClassType |php-generator:#tridy], obsahuje všechny metody, které vytváří služby, a je připravena na zápis do cache. Výsledný kód třídy můžeme v této chvíli ještě upravit. +V této fázi už je třída kontejneru vygenerována v podobě objektu [ClassType |php-generator:#Třídy], obsahuje všechny metody, které vytváří služby, a je připravena na zápis do cache. Výsledný kód třídy můžeme v této chvíli ještě upravit. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -167,10 +167,10 @@ class BlogExtension extends Nette\DI\CompilerExtension ``` -$initialization .[wiki-method] -============================== +$initialization .[method] +========================= -Třída Configurator po [vytvoření kontejneru |application:bootstrap#index.php] volá inicializační kód, který se vytváří zápisem do objektu `$this->initialization` pomocí [metody addBody() |php-generator:#tela-metod-a-funkci]. +Třída Configurator po [vytvoření kontejneru |application:bootstrapping#index.php] volá inicializační kód, který se vytváří zápisem do objektu `$this->initialization` pomocí [metody addBody() |php-generator:#Těla metod a funkcí]. Ukážeme si příklad, jak třeba inicializačním kódem nastartovat session nebo spustit služby, které mají tag `run`: diff --git a/dependency-injection/cs/factory.texy b/dependency-injection/cs/factory.texy index 1398077e88..a90573d587 100644 --- a/dependency-injection/cs/factory.texy +++ b/dependency-injection/cs/factory.texy @@ -6,7 +6,7 @@ Nette DI umí automaticky generovat kód továren na základě rozhraní, což v Továrna je třída, která vyrábí a konfiguruje objekty. Předává jim tedy i jejich závislosti. Nezaměňujte prosím s návrhovým vzorem *factory method*, který popisuje specifický způsob využití továren a s tímto tématem nesouvisí. -Jak taková továrna vypadá jsme si ukázali v [úvodní kapitole|introduction#továrna]: +Jak taková továrna vypadá jsme si ukázali v [úvodní kapitole |introduction#Továrna]: ```php class ArticleFactory @@ -139,8 +139,7 @@ Accessor Nette umí krom továren generovat i tzv. accessory. Jde o objekty s metodou `get()`, která vrací určitou službu z DI kontejneru. Opakované volání `get()` vrací stále tutéž instanci. -Accessor poskytují závislostem lazy-loading. Mějme třídu, která zapisuje chyby do speciální databáze. Když by si tato třída nechávala připojení k databázi předávat jako závislost konstruktorem, muselo by se připojení vždycky vytvořit, ačkoliv v praxi se chyba objeví jen výjimečně a tedy povětšinou by zůstalo spojení nevyužité. -Místo toho si tak třída předá accessor a teprve když se zavolá jeho `get()`, dojde k vytvoření objektu databáze: +Accessor poskytují závislostem lazy-loading. Mějme třídu, která zapisuje chyby do speciální databáze. Když by si tato třída nechávala připojení k databázi předávat jako závislost konstruktorem, muselo by se připojení vždycky vytvořit, ačkoliv v praxi se chyba objeví jen výjimečně a tedy povětšinou by zůstalo spojení nevyužité. Místo toho si tak třída předá accessor a teprve když se zavolá jeho `get()`, dojde k vytvoření objektu databáze: Jak accessor vytvořit? Stačí napsat rozhraní a Nette DI vygeneruje implementaci. Rozhraní musí mít přesně jednu metodu s názvem `get` a deklarovat návratový typ: @@ -170,39 +169,45 @@ Naše továrny a accessory uměly zatím vždy vyrábět nebo vracet jen jeden o interface MultiFactory { function createArticle(): Article; - function createFoo(): Model\Foo; function getDb(): PDO; } ``` -Takže místo toho, abych si předávali několik generovaných továren a accessorů, předáme jednu komplexnější továrnu, která toho umí víc. +Takže místo toho, abychom si předávali několik generovaných továren a accessorů, předáme jednu komplexnější továrnu, která toho umí víc. -Alternativně lze místo několika metod použít `create()` a `get()` s parameterem: +Alternativně lze místo několika metod použít `get()` s parameterem: ```php interface MultiFactoryAlt { - function create($name); function get($name): PDO; } ``` -Pak platí, že `MultiFactory::createArticle()` dělá totéž jako `MultiFactoryAlt::create('article')`. Nicméně alternativní zápis má tu nevýhodu, že není zřejmé, jaké hodnoty `$name` jsou podporované a logicky také nelze v rozhraní odlišit různé návratové hodnoty pro různé `$name`. +Pak platí, že `MultiFactory::getArticle()` dělá totéž jako `MultiFactoryAlt::get('article')`. Nicméně alternativní zápis má tu nevýhodu, že není zřejmé, jaké hodnoty `$name` jsou podporované a logicky také nelze v rozhraní odlišit různé návratové hodnoty pro různé `$name`. Definice seznamem ----------------- -A jak definovat vícenásobnou továrnu v konfiguraci? Vytvoříme tři služby, které bude vytvářet/vracet, a potom samotnou továrnu: +Tímto způsobem lze definovat vícenásobnou továrnu v konfiguraci: .{data-version:3.2.0} + +```neon +services: + - MultiFactory( + article: Article # definuje createArticle() + db: PDO(%dsn%, %user%, %password%) # definuje getDb() + ) +``` + +Nebo se můžeme v definici továrny odkázat na existující služby pomocí reference: ```neon services: article: Article - - Model\Foo - PDO(%dsn%, %user%, %password%) - MultiFactory( - article: @article # createArticle() - foo: @Model\Foo # createFoo() - db: @\PDO # getDb() + article: @article # definuje createArticle() + db: @\PDO # definuje getDb() ) ``` @@ -210,11 +215,11 @@ services: Definice pomocí tagů -------------------- -Druhou možností je využít k definici [tagy|services#Tagy]: +Druhou možností je využít k definici [tagy |services#Tagy]: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter - App\Model\DatabaseAccessor( db1: @database.db1.explorer ) diff --git a/dependency-injection/cs/faq.texy b/dependency-injection/cs/faq.texy index 31f26903b0..e9842c0bba 100644 --- a/dependency-injection/cs/faq.texy +++ b/dependency-injection/cs/faq.texy @@ -5,9 +5,7 @@ Je DI jiným názvem pro IoC? --------------------------- -*Inversion of Control* (IoC) je princip zaměřený na způsob, jakým je kód spouštěn - zda váš kód spouští cizí nebo je váš kód integrován do cizího, který jej následně volá. -IoC je široký pojem zahrnující [události|nette:glossary#Události], takzvaný [Hollywoodský princip |application:components#Hollywood style] a další aspekty. -Součástí tohoto konceptu jsou i továrny, o kterých hovoří [Pravidlo č. 3: nech to na továrně |introduction#Pravidlo č. 3: nech to na továrně], a které představují inverzi pro operátor `new`. +*Inversion of Control* (IoC) je princip zaměřený na způsob, jakým je kód spouštěn - zda váš kód spouští cizí nebo je váš kód integrován do cizího, který jej následně volá. IoC je široký pojem zahrnující [události |nette:glossary#události], takzvaný [Hollywoodský princip |application:components#Hollywood style] a další aspekty. Součástí tohoto konceptu jsou i továrny, o kterých hovoří [Pravidlo č. 3: nech to na továrně |introduction#Pravidlo č. 3: nech to na továrně], a které představují inverzi pro operátor `new`. *Dependency Injection* (DI) se zaměřuje na způsob, jakým se jeden objekt dozví o jiném objektu, tedy o jeho závislosti. Jde o návrhový vzor, který požaduje explicitní předávání závislostí mezi objekty. @@ -25,11 +23,9 @@ Oproti Dependency Injection však ztrácí na transparentnosti: závislosti nejs Kdy je lepší DI nepoužít? ------------------------- -Nejsou známy žádné obtíže spojené s použitím návrhového vzoru Dependency Injection. Naopak získávání závislostí z globálně dostupných míst vede k [celé řadě komplikací |global-state], stejně tak používání Service Locatoru. -Proto je vhodné využívat DI vždy. To není dogmatický přístup, ale jednoduše nebyla nalezena lepší alternativa. +Nejsou známy žádné obtíže spojené s použitím návrhového vzoru Dependency Injection. Naopak získávání závislostí z globálně dostupných míst vede k [celé řadě komplikací |global-state], stejně tak používání Service Locatoru. Proto je vhodné využívat DI vždy. To není dogmatický přístup, ale jednoduše nebyla nalezena lepší alternativa. -Přesto existují určité situace, kdy si objeky nepředáváme a získáme je z globálního prostoru. Například při ladění kódu, kdy potřebujete v konkrétním bodě programu vypsat hodnotu proměnné, změřit dobu trvání určité části programu nebo zaznamenat zprávu. -V takových případech, kdy jde o dočasné úkony, které budou později z kódu odstraněny, je legitimní využít globálně dostupný dumper, stopky nebo logger. Tyto nástroje totiž nepatří k návrhu kódu. +Přesto existují určité situace, kdy si objeky nepředáváme a získáme je z globálního prostoru. Například při ladění kódu, kdy potřebujete v konkrétním bodě programu vypsat hodnotu proměnné, změřit dobu trvání určité části programu nebo zaznamenat zprávu. V takových případech, kdy jde o dočasné úkony, které budou později z kódu odstraněny, je legitimní využít globálně dostupný dumper, stopky nebo logger. Tyto nástroje totiž nepatří k návrhu kódu. Má používání DI své stinné stránky? @@ -39,8 +35,7 @@ Obnáší použití Dependency Injection nějaké nevýhody, jako například zv DI nemá na výkon nebo paměťové nároky aplikace vliv. Určitou roli může hrát výkon DI Containeru, avšak v případě [Nette DI |nette-container] je kontejner kompilován do čistého PHP, takže jeho režie při běhu aplikace je v podstatě nulová. -Při psaní kódu bývá nutné vytvářet konstruktory přijímající závislosti. Dříve to mohlo být zdlouhavé, avšak díky moderním IDE a [constructor property promotion |https://blog.nette.org/cs/php-8-0-kompletni-prehled-novinek#toc-constructor-property-promotion] je to nyní otázkou několika sekund. Továrny lze snadno generovat pomocí Nette DI a pluginu pro PhpStorm kliknutím myší. -Na druhou stranu odpadá potřeba psát singletony a statické přístupové body. +Při psaní kódu bývá nutné vytvářet konstruktory přijímající závislosti. Dříve to mohlo být zdlouhavé, avšak díky moderním IDE a [constructor property promotion |https://blog.nette.org/cs/php-8-0-kompletni-prehled-novinek#toc-constructor-property-promotion] je to nyní otázkou několika sekund. Továrny lze snadno generovat pomocí Nette DI a pluginu pro PhpStorm kliknutím myší. Na druhou stranu odpadá potřeba psát singletony a statické přístupové body. Lze konstatovat, že správně navržená aplikace využívající DI není v porovnání s aplikací využívající singletony ani kratší ani delší. Části kódu pracující se závislostmi jsou pouze vyňaty z jednotlivých tříd a přesunuty na nová místa, tedy do DI kontejneru a továren. @@ -61,14 +56,13 @@ Pamatujte, že přechod na Dependency Injection je investice do kvality kódu a Proč se upřednostňuje kompozice před dědičností? ------------------------------------------------ -Je vhodnější používat kompozici místo dědičnosti, protože slouží k opětovnému použití kódu, aniž bychom se museli starat o důsledky změn. Poskytuje tedy volnější vazbu, kdy nemusíme mít obavy, že změna nějakého kódu způsobí potřebu změny jiného závislého kódu. Typickým příkladem je situace označovaná jako [constructor hell |passing-dependencies#Constructor hell]. +Je vhodnější používat [kompozici |nette:introduction-to-object-oriented-programming#Kompozice] místo [dědičnosti |nette:introduction-to-object-oriented-programming#Dědičnost], protože slouží k opětovnému použití kódu, aniž bychom se museli starat o důsledky změn. Poskytuje tedy volnější vazbu, kdy nemusíme mít obavy, že změna nějakého kódu způsobí potřebu změny jiného závislého kódu. Typickým příkladem je situace označovaná jako [constructor hell |passing-dependencies#Constructor hell]. Lze použít Nette DI Container mimo Nette? ----------------------------------------- -Rozhodně. Nette DI Container je součástí Nette, ale je navržen jako samostatná knihovna, která může být použita nezávisle na ostatních částech frameworku. Stačí ji nainstalovat pomocí Composeru, vytvořit konfigurační soubor s definicí vašich služeb a poté pomocí několika řádků PHP kódu vytvořit DI kontejner. -A ihned můžte začít využívat výhody Dependency Injection ve svých projektech. +Rozhodně. Nette DI Container je součástí Nette, ale je navržen jako samostatná knihovna, která může být použita nezávisle na ostatních částech frameworku. Stačí ji nainstalovat pomocí Composeru, vytvořit konfigurační soubor s definicí vašich služeb a poté pomocí několika řádků PHP kódu vytvořit DI kontejner. A ihned můžte začít využívat výhody Dependency Injection ve svých projektech. Jak vypadá konkrétní použití včetně kódů popisuje kapitola [Nette DI Container |nette-container]. diff --git a/dependency-injection/cs/global-state.texy b/dependency-injection/cs/global-state.texy index 41c15a1b8c..d152d69973 100644 --- a/dependency-injection/cs/global-state.texy +++ b/dependency-injection/cs/global-state.texy @@ -33,8 +33,7 @@ Strašidelné působení na dálku ----------------------------- "Strašidelné působení na dálku" - tak slavně nazval roku 1935 Albert Einstein jev v kvantové fyzice, který mu naháněl husí kůži. -Jedná se o kvantové propojení, jehož zvláštností je, že když změříte informaci o jedné částici, okamžitě tím ovlivníte částici druhou, i když jsou od sebe vzdáleny miliony světelných let. -Což zdánlivě porušuje základní zákon vesmíru, že nic se nemůže šířit rychleji než světlo. +Jedná se o kvantové propojení, jehož zvláštností je, že když změříte informaci o jedné částici, okamžitě tím ovlivníte částici druhou, i když jsou od sebe vzdáleny miliony světelných let. Což zdánlivě porušuje základní zákon vesmíru, že nic se nemůže šířit rychleji než světlo. V softwarovém světě můžeme "strašidelným působení na dálku" nazvat situaci, kdy spustíme nějaký proces, o kterém se domníváme, že je izolovaný (protože jsme mu nepředali žádné reference), ale ve vzdálených místech systému dojde k neočekávaným interakcím a změnám stavu, o kterých jsme neměli tušení. K tomu může dojít pouze prostřednictvím globálního stavu. @@ -50,16 +49,13 @@ function testCreditCardCharge() Spustíte kód, třeba několikrát, a po nějaké době si všimnete na mobilu notifikací z banky, že při každém spuštění se strhlo 100 dolarů z vaší platební karty 🤦‍♂️ -Jak proboha mohl test způsobit skutečné stržení peněz? Operovat s platební kartou není snadné. Musíte komunikovat s webovou službou třetí strany, musíte znát URL této webové služby, musíte se přihlásit a tak dále. -Žádná z těchto informací není v testu obsažena. Ba co hůř, ani nevíte, kde jsou tyto informace přítomny, a tedy ani jak mockovat externí závislosti, aby každé spuštění nevedlo k tomu, že se znovu strhne 100 dolarů. A jak jste měl jako nový vývojář vědět, že to, co se chystáte udělat, povede k tomu, že budete o 100 dolarů chudší? +Jak proboha mohl test způsobit skutečné stržení peněz? Operovat s platební kartou není snadné. Musíte komunikovat s webovou službou třetí strany, musíte znát URL této webové služby, musíte se přihlásit a tak dále. Žádná z těchto informací není v testu obsažena. Ba co hůř, ani nevíte, kde jsou tyto informace přítomny, a tedy ani jak mockovat externí závislosti, aby každé spuštění nevedlo k tomu, že se znovu strhne 100 dolarů. A jak jste měl jako nový vývojář vědět, že to, co se chystáte udělat, povede k tomu, že budete o 100 dolarů chudší? To je strašidelné působení na dálku! -Nezbývá vám, než se dlouze hrabat ve spoustě zdrojových kódů, ptát se starších a zkušenějších kolegů, než pochopíte, jak vazby v projektu fungují. -To je způsobeno tím, že při pohledu na rozhraní třídy `CreditCard` nelze zjistit globální stav, který je třeba inicializovat. Dokonce ani pohled do zdrojového kódu třídy vám neprozradí, kterou inicializační metodu máte zavolat. V nejlepším případě můžete najít globální proměnnou, ke které se přistupuje, a z ní se pokusit odhadnout, jak ji inicializovat. +Nezbývá vám, než se dlouze hrabat ve spoustě zdrojových kódů, ptát se starších a zkušenějších kolegů, než pochopíte, jak vazby v projektu fungují. To je způsobeno tím, že při pohledu na rozhraní třídy `CreditCard` nelze zjistit globální stav, který je třeba inicializovat. Dokonce ani pohled do zdrojového kódu třídy vám neprozradí, kterou inicializační metodu máte zavolat. V nejlepším případě můžete najít globální proměnnou, ke které se přistupuje, a z ní se pokusit odhadnout, jak ji inicializovat. -Třídy v takovém projektu jsou patologickými lháři. Platební karta předstírá, že ji stačí instancovat a zavolat metodu `charge()`. Ve skrytu však spolupracuje s jinou třídou `PaymentGateway`, která představuje platební bránu. I její rozhraní říká, že ji lze inicializovat samostatně, ale ve skutečnosti si vytáhne credentials z nějakého konfiguračního souboru a tak dále. -Vývojářům, kteří tento kód napsali, je jasné, že `CreditCard` potřebuje `PaymentGateway`. Napsali kód tímto způsobem. Ale pro každého, kdo je v projektu nový, je to naprostá záhada a brání to učení. +Třídy v takovém projektu jsou patologickými lháři. Platební karta předstírá, že ji stačí instancovat a zavolat metodu `charge()`. Ve skrytu však spolupracuje s jinou třídou `PaymentGateway`, která představuje platební bránu. I její rozhraní říká, že ji lze inicializovat samostatně, ale ve skutečnosti si vytáhne credentials z nějakého konfiguračního souboru a tak dále. Vývojářům, kteří tento kód napsali, je jasné, že `CreditCard` potřebuje `PaymentGateway`. Napsali kód tímto způsobem. Ale pro každého, kdo je v projektu nový, je to naprostá záhada a brání to učení. Jak situaci opravit? Snadno. **Nechte API deklarovat závislosti.** @@ -78,8 +74,7 @@ A hlavně nyní můžete platební bránu mockovat, takže se vám při každém Globální stav způsobuje, že se vaše objekty mohou tajně dostat k věcem, které nejsou deklarovány v jejich API, a v důsledku toho dělají z vašich API patologické lháře. -Možná jste o tom dříve takto nepřemýšleli, ale kdykoli používáte globální stav, vytváříte tajné bezdrátové komunikační kanály. Strašidelná akce na dálku nutí vývojáře číst každý řádek kódu, aby pochopili potenciální interakce, snižuje produktivitu vývojářů a mate nové členy týmu. -Pokud jste vy ten, kdo kód vytvořil, znáte skutečné závislosti, ale každý, kdo přijde po vás, je bezradný. +Možná jste o tom dříve takto nepřemýšleli, ale kdykoli používáte globální stav, vytváříte tajné bezdrátové komunikační kanály. Strašidelná akce na dálku nutí vývojáře číst každý řádek kódu, aby pochopili potenciální interakce, snižuje produktivitu vývojářů a mate nové členy týmu. Pokud jste vy ten, kdo kód vytvořil, znáte skutečné závislosti, ale každý, kdo přijde po vás, je bezradný. Nepište kód, který využívá globální stav, dejte přednost předávání závislostí. Tedy dependency injection. @@ -201,8 +196,7 @@ class Singleton Bohužel, singleton zavádí do aplikace globální stav. A jak jsme si ukázali výše, globální stav je nežádoucí. Proto je singleton považován za antipattern. -Nepoužívejte ve svém kódu singletony a nahraďte je jinými mechanismy. Singletony opravdu nepotřebujete. Pokud však potřebujete zaručit existenci jediné instance třídy pro celou aplikaci, nechte to na [DI kontejneru |container]. -Vytvořte tak aplikační singleton, neboli službu. Tím se třída přestane věnovat zajištění své vlastní jedinečnosti (tj. nebude mít metodu `getInstance()` a statickou proměnnou) a bude plnit pouze své funkce. Tak přestane porušovat princip jediné odpovědnosti. +Nepoužívejte ve svém kódu singletony a nahraďte je jinými mechanismy. Singletony opravdu nepotřebujete. Pokud však potřebujete zaručit existenci jediné instance třídy pro celou aplikaci, nechte to na [DI kontejneru |container]. Vytvořte tak aplikační singleton, neboli službu. Tím se třída přestane věnovat zajištění své vlastní jedinečnosti (tj. nebude mít metodu `getInstance()` a statickou proměnnou) a bude plnit pouze své funkce. Tak přestane porušovat princip jediné odpovědnosti. Globální stav versus testy @@ -224,9 +218,7 @@ Globální konstanty Globální stav se neomezuje pouze na používání singletonů a statických proměnných, ale může se týkat také globálních konstant. -Konstanty, jejichž hodnota nám nepřináší žádnou novou (`M_PI`) nebo užitečnou (`PREG_BACKTRACK_LIMIT_ERROR`) informaci, jsou jednoznačně v pořádku. -Naopak konstanty, které slouží jako způsob, jak *bezdrátově* předat informaci dovnitř kódu, nejsou ničím jiným než skrytou závislostí. Jako třeba `LOG_FILE` v následujícím příkladu. -Použití konstanty `FILE_APPEND` je zcela korektní. +Konstanty, jejichž hodnota nám nepřináší žádnou novou (`M_PI`) nebo užitečnou (`PREG_BACKTRACK_LIMIT_ERROR`) informaci, jsou jednoznačně v pořádku. Naopak konstanty, které slouží jako způsob, jak *bezdrátově* předat informaci dovnitř kódu, nejsou ničím jiným než skrytou závislostí. Jako třeba `LOG_FILE` v následujícím příkladu. Použití konstanty `FILE_APPEND` je zcela korektní. ```php const LOG_FILE = '...'; @@ -273,9 +265,7 @@ Používání deterministických statických metod a funkcí, jako například ` Existují ovšem i funkce v PHP, které nejsou deterministické. K nim patří například funkce `htmlspecialchars()`. Její třetí parametr `$encoding`, pokud není uveden, jako výchozí hodnotu má hodnotu konfigurační volby `ini_get('default_charset')`. Proto se doporučuje tento parametr vždy uvádět a předejít tak případnému nepředvídatelnému chování funkce. Nette to důsledně dělá. -Některé funkce, jako například `strtolower()`, `strtoupper()` a podobné, se v nedávné minulosti nedeterministicky chovaly a byly závislé na nastavení `setlocale()`. To způsobovalo mnoho komplikací, nejčastěji při práci s tureckým jazykem. -Ten totiž rozlišuje malé i velké písmeno `I` s tečkou i bez tečky. Takže `strtolower('I')` vracelo znak `ı` a `strtoupper('i')` znak `İ`, což vedlo k tomu, že aplikace začaly způsobovat řadu záhadných chyb. -Tento problém byl však odstraněn v PHP verze 8.2 a funkce již nejsou závislé na locale. +Některé funkce, jako například `strtolower()`, `strtoupper()` a podobné, se v nedávné minulosti nedeterministicky chovaly a byly závislé na nastavení `setlocale()`. To způsobovalo mnoho komplikací, nejčastěji při práci s tureckým jazykem. Ten totiž rozlišuje malé i velké písmeno `I` s tečkou i bez tečky. Takže `strtolower('I')` vracelo znak `ı` a `strtoupper('i')` znak `İ`, což vedlo k tomu, že aplikace začaly způsobovat řadu záhadných chyb. Tento problém byl však odstraněn v PHP verze 8.2 a funkce již nejsou závislé na locale. Jde o pěkný příklad, jak globální stav potrápil tisíce vývojářů na celém světě. Řešením bylo nahradit jej za dependency injection. @@ -301,4 +291,4 @@ Když promýšlíte návrh kódu, myslete na to, že každé `static $foo` před Během tohoto procesu možná zjistíte, že je třeba třídu rozdělit, protože má více než jednu odpovědnost. Nebojte se toho; usilujte o princip jedné odpovědnosti. -*Rád bych poděkoval Miškovi Heverymu, jehož články, jako je [Flaw: Brittle Global State & Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/], jsou základem této kapitoly.* +*Rád bych poděkoval Miškovi Heverymu, jehož články, jako je [Flaw: Brittle Global State & Singletons |https://web.archive.org/web/20230321084133/http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/], jsou základem této kapitoly.* diff --git a/dependency-injection/cs/introduction.texy b/dependency-injection/cs/introduction.texy index 00109aa908..3b55474f2b 100644 --- a/dependency-injection/cs/introduction.texy +++ b/dependency-injection/cs/introduction.texy @@ -375,8 +375,7 @@ class NewsletterDistributor } ``` -Nyní je ze signatur třídy `NewsletterDistributor` jasné, že součástí její funkčnosti je i logování. A úkol vyměnit logger za jiný, třeba kvůli testování, je zcela triviální. -Navíc pokud by se konstruktor třídy `Logger` změnil, nebude to mít na naši třídu žádný vliv. +Nyní je ze signatur třídy `NewsletterDistributor` jasné, že součástí její funkčnosti je i logování. A úkol vyměnit logger za jiný, třeba kvůli testování, je zcela triviální. Navíc pokud by se konstruktor třídy `Logger` změnil, nebude to mít na naši třídu žádný vliv. Pravidlo č. 2: ber, co tvé jest @@ -390,8 +389,7 @@ Díky tomu bude kód využívající jiné objekty zcela nezávislý na změnác Nový člen rodiny ---------------- -Ve vývojářském týmu padlo rozhodnutí vytvořit druhý logger, který zapisuje do databáze. Vytvoříme tedy třídu `DatabaseLogger`. Takže máme dvě třídy, `Logger` a `DatabaseLogger`, jedna zapisuje do souboru, druhá do databáze … nezdá se vám na tom pojmenování něco divného? -Nebylo by lepší přejmenovat `Logger` na `FileLogger`? Určitě ano. +Ve vývojářském týmu padlo rozhodnutí vytvořit druhý logger, který zapisuje do databáze. Vytvoříme tedy třídu `DatabaseLogger`. Takže máme dvě třídy, `Logger` a `DatabaseLogger`, jedna zapisuje do souboru, druhá do databáze … nezdá se vám na tom pojmenování něco divného? Nebylo by lepší přejmenovat `Logger` na `FileLogger`? Určitě ano. Ale uděláme to chytře. Pod původním názvem vytvoříme rozhraní: @@ -439,7 +437,7 @@ class EditController extends Controller Možné řešení se přímo nabízí: necháme si objekt databáze předat konstruktorem do `EditController` a použijeme `$article = new Article($this->db)`. -Stejně jako v předchozím případě s `Logger` a cestou k souboru, tohle není správný postup. Databáze není závislost `EditController`, ale `Article`. Předávat si databázi tedy jde proti [pravidlu č. 2: ber, co tvé jest |#Pravidlo č. 2: ber, co tvé jest]. Když se změní konstruktor třídy `Article` (přibude nový parametr), bude nutné upravit také kód na všech místech, kde se vytváří instance. Ufff. +Stejně jako v předchozím případě s `Logger` a cestou k souboru, tohle není správný postup. Databáze není závislost `EditController`, ale `Article`. Předávat si databázi tedy jde proti [pravidlu č. 2: ber, co tvé jest |#Pravidlo č. 2: ber co tvé jest]. Když se změní konstruktor třídy `Article` (přibude nový parametr), bude nutné upravit také kód na všech místech, kde se vytváří instance. Ufff. Houstone, co navrhuješ? @@ -520,7 +518,7 @@ Shrnutí Na začátku této kapitoly jsme slibovali, že si ukážeme postup, jak navrhovat čistý kód. Stačí třídám 1) [předávat závislosti, které potřebují |#Pravidlo č. 1: nech si to předat] -2) [a naopak nepředávat, co přímo nepotřebují |#Pravidlo č. 2: ber, co tvé jest] +2) [a naopak nepředávat, co přímo nepotřebují |#Pravidlo č. 2: ber co tvé jest] 3) [a že objekty se závislostmi se nejlépe vyrábí v továrnách |#Pravidlo č. 3: nech to na továrně] Nemusí se to tak na první pohled zdát, ale tyhle tři pravidla mají dalekosáhlé důsledky. Vedou k radikálně jinému pohledu na návrh kódu. Stojí to za to? Programátoři, kteří zahodili staré zvyky a začali důsledně používat dependency injection, považují tento krok za zásadní moment v profesním životě. Otevřel se jim svět přehledných a udržitelných aplikací. diff --git a/dependency-injection/cs/nette-container.texy b/dependency-injection/cs/nette-container.texy index e48aa745d0..d9931af978 100644 --- a/dependency-injection/cs/nette-container.texy +++ b/dependency-injection/cs/nette-container.texy @@ -21,10 +21,9 @@ services: Zápis je opravdu stručný. -Všechny závislosti deklarované v konstruktorech tříd `ArticleFactory` a `UserController` si Nette DI samo zjistí a předá díky tzv. [autowiringu|autowiring], v konfiguračním souboru proto není potřeba nic uvádět. -Takže i když dojde ke změně parametrů, nemusíte v konfiguraci nic měnit. Nette kontejner automaticky přegeneruje. Vy se tam můžete soustředit čistě na vývoj aplikace. +Všechny závislosti deklarované v konstruktorech tříd `ArticleFactory` a `UserController` si Nette DI samo zjistí a předá díky tzv. [autowiringu|autowiring], v konfiguračním souboru proto není potřeba nic uvádět. Takže i když dojde ke změně parametrů, nemusíte v konfiguraci nic měnit. Nette kontejner automaticky přegeneruje. Vy se tam můžete soustředit čistě na vývoj aplikace. -Pokud chceme závislosti předávat pomocí setterů, použijeme k tomu sekci [setup|services#setup]. +Pokud chceme závislosti předávat pomocí setterů, použijeme k tomu sekci [setup |services#Setup]. Nette DI vygeneruje přímo PHP kód kontejneru. Výsledkem je tedy soubor `.php`, který si můžete otevřít a studovat. Díky tomu přesně vidíte, jak kontejner funguje. Můžete jej také debuggovat v IDE a krokovat. A hlavně: vygenerované PHP je extrémně rychlé. @@ -64,8 +63,8 @@ Kontejner se vygeneruje jen jednou, jeho kód se zapíše do cache (adresář `_ Pro vytvoření a získání služeb slouží metody `getService()` nebo `getByType()`. Takto vytvoříme objekt `UserController`: ```php -$database = $container->getByType(UserController::class); -$database->query('...'); +$controller = $container->getByType(UserController::class); +$controller->someMethod(); ``` Během vývoje je užitečné aktivovat auto-refresh mód, kdy se kontejner automaticky přegeneruje, pokud dojde ke změně jakékoliv třídy nebo konfiguračního souboru. Stačí v konstruktoru `ContainerLoader` uvést jako druhý argument `true`. @@ -78,5 +77,4 @@ $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp', true); Použití s frameworkem Nette --------------------------- -Jak jsme si ukázali, použití Nette DI není limitované na aplikace psané v Nette Frameworku, můžete jej pomocí pouhých 3 řádků kódu nasadit kdekoliv. -Pokud však vyvíjíte aplikace v Nette Framework, konfiguraci a vytvoření kontejneru má na starosti [Bootstrap|application:bootstrap#toc-konfigurace-di-kontejneru]. +Jak jsme si ukázali, použití Nette DI není limitované na aplikace psané v Nette Frameworku, můžete jej pomocí pouhých 3 řádků kódu nasadit kdekoliv. Pokud však vyvíjíte aplikace v Nette Framework, konfiguraci a vytvoření kontejneru má na starosti [Bootstrap |application:bootstrapping#Konfigurace DI kontejneru]. diff --git a/dependency-injection/cs/passing-dependencies.texy b/dependency-injection/cs/passing-dependencies.texy index 433f999eb0..f5c20e3084 100644 --- a/dependency-injection/cs/passing-dependencies.texy +++ b/dependency-injection/cs/passing-dependencies.texy @@ -96,9 +96,9 @@ final class MyClass extends BaseClass Problém nastane v okamžiku, kdy budeme chtít změnit kontruktor třídy `BaseClass`, třeba když přibude nová závislost. Pak je totiž nutné upravit také všechny konstruktory potomků. Což z takové úpravy dělá peklo. -Jak tomu předcházet? Řešením je **dávat přednost kompozici před dědičností.** +Jak tomu předcházet? Řešením je **dávat přednost [kompozici před dědičností |faq#Proč se upřednostňuje kompozice před dědičností]**. -Tedy navrhneme kód jinak. Budeme se vyhýbat abstraktním `Base*` třídám. Místo toho, aby `MyClass` získávala určitou funkčnost tím, že dědí od `BaseClass`, si tuto funkčnost nechá předat jako závislost: +Tedy navrhneme kód jinak. Budeme se vyhýbat [abstraktním |nette:introduction-to-object-oriented-programming#Abstraktní třídy] `Base*` třídám. Místo toho, aby `MyClass` získávala určitou funkčnost tím, že dědí od `BaseClass`, si tuto funkčnost nechá předat jako závislost: ```php final class SomeFunctionality @@ -156,7 +156,7 @@ class MyClass public function setCache(Cache $cache): void { - if ($this->cache) { + if (isset($this->cache)) { throw new RuntimeException('The dependency has already been set'); } $this->cache = $cache; @@ -168,8 +168,7 @@ Volání setteru definujeme v konfiguraci DI kontejneru v [klíči setup |servic ```neon services: - - - create: MyClass + - create: MyClass setup: - setCache ``` @@ -196,8 +195,7 @@ Nastavení proměnné definujeme v konfiraci DI kontejneru v [sekci setup |servi ```neon services: - - - create: MyClass + - create: MyClass setup: - $cache = @\Cache ``` diff --git a/dependency-injection/cs/services.texy b/dependency-injection/cs/services.texy index 52e3a357ea..8a86b230e9 100644 --- a/dependency-injection/cs/services.texy +++ b/dependency-injection/cs/services.texy @@ -2,32 +2,32 @@ Definování služeb ***************** .[perex] -Konfigurace je místem, kam umísťujeme definice vlastních služeb. Slouží k tomu sekce `services`. +Konfigurace je místem, kde učíme DI kontejner, jak má sestavovat jednotlivé služby a jak je propojovat s dalšími závislostmi. Nette poskytuje velice přehledný a elegantní způsob, jak toho dosáhnout. -Například takto vytvoříme službu pojmenovanou `database`, což bude instance třídy `PDO`: +Sekce `services` v konfiguračním souboru formátu NEON je místem, kde definujeme vlastní služby a jejich konfigurace. Podívejme se na jednoduchý příklad definice služby pojmenované `database`, která reprezentuje instanci třídy `PDO`: ```neon services: database: PDO('sqlite::memory:') ``` -Pojmenování služeb slouží k tomu, abychom se na ně mohli [odkazovat|#Odkazování na služby]. Pokud na službu není odkazováno, není ji potřeba pojmenovávat. Místo názvu tak použijeme jen odrážku: +Uvedená konfigurace vyústí v následující tovární metodu v [DI kontejneru|container]: -```neon -services: - - PDO('sqlite::memory:') # anonymní služba +```php +public function createServiceDatabase(): PDO +{ + return new PDO('sqlite::memory:'); +} ``` -Jednořádkový zápis lze rozepsat do více řádků a tak umožnit přidání dalších klíčů, jako je například [#setup]. Aliasem pro klíč `create:` je `factory:`. +Názvy služeb nám umožňují odkazovat se na ně v dalších částech konfiguračního souboru, a to ve formátu `@nazevSluzby`. Pokud není potřeba službu pojmenovávat, můžeme jednoduše použít pouze odrážku: ```neon services: - database: - create: PDO('sqlite::memory:') - setup: ... + - PDO('sqlite::memory:') ``` -Službu poté získáme z DI kontejneru metodou `getService()` podle názvu, nebo ještě lépe metodou `getByType()` podle typu: +Pro získání služby z DI kontejneru můžeme využít metodu `getService()` s názvem služby jako parametrem, nebo metodu `getByType()` s typem služby: ```php $database = $container->getService('database'); @@ -38,23 +38,25 @@ $database = $container->getByType(PDO::class); Vytvoření služby ================ -Nejčastěji službu vytváříme prostým vytvořením instance určité třídy: +Většinou vytváříme službu jednoduše tím, že vytvoříme instanci určité třídy. Například: ```neon services: database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -Což vygeneruje tovární metodu v [DI kontejneru|container]: +Pokud potřebujeme konfiguraci rozšířit o další klíče, lze definici rozepsat do více řádků: -```php -public function createServiceDatabase(): PDO -{ - return new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'secret'); -} +```neon +services: + database: + create: PDO('sqlite::memory:') + setup: ... ``` -Pro předání [argumentů|#Argumenty] lze alternativně použít i klíč `arguments`: +Klíč `create` má alias `factory`, obě varianty jsou v praxi běžné. Nicméně doporučujeme používat `create`. + +Argumenty konstruktoru nebo vytvářecí metody mohou být alternativně zapsány v klíči `arguments`: ```neon services: @@ -63,281 +65,272 @@ services: arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret] ``` -Službu může vytvořit také statická metoda: +Služby nemusí být vytvářeny jen prostým vytvořením instance třídy, mohou být také výsledkem volání statických metod nebo metod jiných služeb: ```neon services: - database: My\Database::create(root, secret) + database: DatabaseFactory::create() + router: @routerFactory::create() ``` -Odpovídá PHP kódu: +Všimněte si, že pro jednoduchost se místo `->` používá `::`, viz [#výrazové prostředky]. Vygenerují se tyto tovární metody: ```php public function createServiceDatabase(): PDO { - return My\Database::create('root', 'secret'); + return DatabaseFactory::create(); +} + +public function createServiceRouter(): RouteList +{ + return $this->getService('routerFactory')->create(); } ``` -Předpokládá se, statická metoda `My\Database::create()` má definovanou návratovou hodnotu, kterou DI kontejner potřebuje znát. Pokud ji nemá, zapíšeme typ do konfigurace: +DI kontejner potřebuje znát typ vytvořené služby. Pokud vytváříme službu pomocí metody, která nemá specifikovaný návratový typ, musíme tento typ explicitně uvést v konfiguraci: ```neon services: database: - create: My\Database::create(root, secret) + create: DatabaseFactory::create() type: PDO ``` -Nette DI nám dává mimořádně silné výrazové prostředky, pomocí kterých můžete zapsat téměř cokoliv. Například se [odkázat|#Odkazování na služby] na jinou službu a zavolat její metodu. Pro jednoduchost se místo `->` používá `::` + +Argumenty +========= + +Do konstruktoru a metod předáváme argumenty způsobem velmi podobným jako v samotném PHP: ```neon services: - routerFactory: App\Router\Factory - router: @routerFactory::create() + database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -Odpovídá PHP kódu: - -```php -public function createServiceRouterFactory(): App\Router\Factory -{ - return new App\Router\Factory; -} +Pro lepší čitelnost můžeme argumenty rozepsat do samostatných řádků. V takovém případě je používání čárek volitelné: -public function createServiceRouter(): Router -{ - return $this->getService('routerFactory')->create(); -} +```neon +services: + database: PDO( + 'mysql:host=127.0.0.1;dbname=test' + root + secret + ) ``` -Volání metod lze řetězit za sebe stejně jako v PHP: +Argumenty můžete také pojmenovat a nemusíte se pak starat o jejich pořadí: ```neon services: - foo: FooFactory::build()::get() + database: PDO( + username: root + password: secret + dsn: 'mysql:host=127.0.0.1;dbname=test' + ) ``` -Odpovídá PHP kódu: +Pokud chcete některé argumenty vynechat a použít jejich výchozí hodnotu nebo dosadit službu pomocí [autowiringu|autowiring], použijte podtržítko: -```php -public function createServiceFoo() -{ - return FooFactory::build()->get(); -} +```neon +services: + foo: Foo(_, %appDir%) ``` +Jako argumenty lze předávat služby, používat parametry a mnohem více, viz [#výrazové prostředky]. -Argumenty -========= -Pro předání argumentů lze používat i pojmenované parametry: +Setup +===== + +V sekci `setup` definujeme metody, které se mají volat při vytváření služby. ```neon services: - database: PDO( - 'mysql:host=127.0.0.1;dbname=test' # poziční - username: root # pojmenovaný - password: secret # pojmenovaný - ) + database: + create: PDO(%dsn%, %user%, %password%) + setup: + - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) ``` -Při rozepsání argumentů do více řádků je používání čárek volitelné. +To by v PHP vypadalo takto: -Jako argumenty můžeme samozřejmě použít i [jiné služby|#Odkazování na služby] nebo [parametry|configuration#parametry]: +```php +public function createServiceDatabase(): PDO +{ + $service = new PDO('...', '...', '...'); + $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + return $service; +} +``` + +Kromě volání metod lze také předávat hodnoty do properties. Podporováno je i přidání prvku do pole, které je potřeba zapsat v uvozovkách, aby nekolidovalo se syntaxí NEON: ```neon services: - - Foo(@anotherService, %appDir%) + foo: + create: Foo + setup: + - $value = 123 + - '$onClick[]' = [@bar, clickHandler] ``` -Odpovídá PHP kódu: +Což by v PHP kódu vypadalo následovně: ```php -public function createService01(): Foo +public function createServiceFoo(): Foo { - return new Foo($this->getService('anotherService'), '...'); + $service = new Foo; + $service->value = 123; + $service->onClick[] = $this->getService('bar')->clickHandler(...); + return $service; } ``` -Pokud se má první argument [autowirovat|autowiring] a chceme přitom uvést argument druhý, vynecháme první znakem `_`, tedy např. `Foo(_, %appDir%)`. Nebo ještě lépe předáme jen druhý argument jako pojmenovaný parametr, např. `Foo(path: %appDir%)`. - -Nette DI a formát NEON nám dává mimořádně silné výrazové prostředky, pomocí kterých můžete zapsat téměř cokoliv. Argumentem tak může být nově vytvořený objekt, lze volat statické metody, metody jiných služeb, nebo pomocí speciálního zápisu i globální funkce: +V setupu lze však volat i statické metody nebo metody jiných služeb. Pokud potřebujete předat jako argument aktuální službu, uveďte ji jako `@self`: ```neon services: - analyser: My\Analyser( - FilesystemIterator(%appDir%) # vytvoření objektu - DateTime::createFromFormat('Y-m-d') # volání statické metody - @anotherService # předání jiné služby - @http.request::getRemoteAddress() # volání metody jiné služby - ::getenv(NetteMode) # volání globální funkce - ) + foo: + create: Foo + setup: + - My\Helpers::initializeFoo(@self) + - @anotherService::setFoo(@self) ``` -Odpovídá PHP kódu: +Všimněte si, že pro jednoduchost se místo `->` používá `::`, viz [#výrazové prostředky]. Vygeneruje se taková tovární metoda: ```php -public function createServiceAnalyser(): My\Analyser +public function createServiceFoo(): Foo { - return new My\Analyser( - new FilesystemIterator('...'), - DateTime::createFromFormat('Y-m-d'), - $this->getService('anotherService'), - $this->getService('http.request')->getRemoteAddress(), - getenv('NetteMode') - ); + $service = new Foo; + My\Helpers::initializeFoo($service); + $this->getService('anotherService')->setFoo($service); + return $service; } ``` -Speciální funkce ----------------- - -V argumentech lze také používat speciální funkce pro přetypování nebo negaci hodnot: +Výrazové prostředky +=================== -- `not(%arg%)` negace -- `bool(%arg%)` bezeztrátové přetypování na bool -- `int(%arg%)` bezeztrátové přetypování na int -- `float(%arg%)` bezeztrátové přetypování na float -- `string(%arg%)` bezeztrátové přetypování na string +Nette DI nám dává mimořádně bohaté výrazové prostředky, pomocí kterých můžeme zapsat téměř cokoliv. V konfiguračních souborech tak můžeme využívat [parametry |configuration#Parametry]: ```neon -services: - - Foo( - id: int(::getenv('ProjectId')) - productionMode: not(%debugMode%) - ) -``` - -Bezztrátové přetypování se od běžného přetypování v PHP např. pomocí `(int)` liší v tom, že pro nečíselné hodnoty vyhodí výjimku. +# parametr +%wwwDir% -Jako argument lze předávat i více služeb. Pole všech služeb určitého typu (tj. třídy nebo rozhraní) vytvoří funkce `typed()`. Funkce vynechá služby, které mají vypnutý autowiring a lze uvést i více typů oddělených čárkou. +# hodnota parametru pod klíčem +%mailer.user% -```neon -services: - - BarsDependent( typed(Bar) ) +# parametr uvnitř řetězce +'%wwwDir%/images' ``` -Předávat pole služeb můžete i automaticky pomocí [autowiringu|autowiring#Pole služeb]. - -Pole všech služeb s určitým [tagem|#tagy] vytvoří funkce `tagged()`. Lze uvést i více tagů oddělených čárkou. +Dále vytvářet objekty, volat metody a funkce: ```neon -services: - - LoggersDependent( tagged(logger) ) -``` +# vytvoření objektu +DateTime() +# volání statické metody +Collator::create(%locale%) -Odkazování na služby -==================== +# volání PHP funkce +::getenv(DB_USER) +``` -Na jednotlivé služby se odkazuje pomocí zavináče a názvu služby, takže například `@database`: +Odkazovat se na služby buď jejich jménem nebo pomocí typu: ```neon -services: - - create: Foo(@database) - setup: - - setCacheStorage(@cache.storage) +# služba dle názvu +@database + +# služba dle typu +@Nette\Database\Connection ``` -Odpovídá PHP kódu: +Používat first-class callable syntax: .{data-version:3.2.0} -```php -public function createService01(): Foo -{ - $service = new Foo($this->getService('database')); - $service->setCacheStorage($this->getService('cache.storage')); - return $service; -} +```neon +# vytvoření callbacku, obdoba [@user, logout] +@user::logout(...) ``` -I na anonymní služby se lze odkazovat přes zavináč, jen místo názvu uvedeme jejich typ (třídu nebo rozhraní). Tohle ovšem obvykle není potřeba dělat díky [autowiringu|autowiring]. +Používat konstanty: ```neon -services: - - create: Foo(@Nette\Database\Connection) # nebo třeba @\PDO - setup: - - setCacheStorage(@cache.storage) +# konstanta třídy +FilesystemIterator::SKIP_DOTS + +# globální konstantu získáme PHP funkcí constant() +::constant(PHP_VERSION) ``` +Volání metod lze řetězit stejně jako v PHP. Jen pro jednoduchost se místo `->` používá `::`: -Setup -===== +```neon +DateTime()::format('Y-m-d') +# PHP: (new DateTime())->format('Y-m-d') + +@http.request::getUrl()::getHost() +# PHP: $this->getService('http.request')->getUrl()->getHost() +``` -V sekci setup uvádíme metody, které se mají zavolat při vytváření služby: +Tyto výrazy můžete používat kdekoliv, při [vytváření služeb |#Vytvoření služby], v [argumentech |#Argumenty], v sekci [#setup] nebo [parametrech |configuration#Parametry]: ```neon +parameters: + ipAddress: @http.request::getRemoteAddress() + services: database: - create: PDO(%dsn%, %user%, %password%) + create: DatabaseFactory::create( @anotherService::getDsn() ) setup: - - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) + - initialize( ::getenv('DB_USER') ) ``` -Odpovídá PHP kódu: -```php -public function createServiceDatabase(): PDO -{ - $service = new PDO('...', '...', '...'); - $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - return $service; -} -``` +Speciální funkce +---------------- -Lze také nastavovat hodnoty proměnných. Podporováno je i přidání prvku do pole, které je potřeba zapsat v uvozovkách, aby nekolidovalo se syntaxí NEON: +V konfiguračních souborech můžete používa tyto speciální funkce: +- `not()` negace hodnoty +- `bool()`, `int()`, `float()`, `string()` bezeztrátové přetypování na daný typ +- `typed()` vytvoří pole všech služeb specifikovaného typu +- `tagged()` vytvoření pole všech služeb s daným tagem ```neon services: - foo: - create: Foo - setup: - - $value = 123 - - '$onClick[]' = [@bar, clickHandler] -``` - -Odpovídá PHP kódu: - -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - $service->value = 123; - $service->onClick[] = [$this->getService('bar'), 'clickHandler']; - return $service; -} + - Foo( + id: int(::getenv('ProjectId')) + productionMode: not(%debugMode%) + ) ``` -V setupu lze však volat i statické metody nebo metod jiných služeb. Aktuální službu jim předáme jako `@self`: +Oproti klasickému přetypování v PHP, jako je např. `(int)`, bezeztrátové přetypování vyhodí výjimku pro nečíselné hodnoty. +Funkce `typed()` vytvoří pole všech služeb daného typu (třída nebo rozhraní). Vynechá služby, které mají vypnutý autowiring. Lze uvést i více typů oddělených čárkou. ```neon services: - foo: - create: Foo - setup: - - My\Helpers::initializeFoo(@self) - - @anotherService::setFoo(@self) + - BarsDependent( typed(Bar) ) ``` -Odpovídá PHP kódu: +Pole služeb určitého typu můžete předávat jako argument také automaticky pomocí [autowiringu |autowiring#Pole služeb]. -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - My\Helpers::initializeFoo($service); - $this->getService('anotherService')->setFoo($service); - return $service; -} +Funkce `tagged()` pak vytváří pole všech služeb s určitým tagem. I zde můžete specifikovat více tagů oddělených čárkou. + +```neon +services: + - LoggersDependent( tagged(logger) ) ``` Autowiring ========== -Pomocí klíče autowired lze službu vyřadit z autowiringu nebo jeho chování ovlivnit. Více v [kapitole o autowiringu|autowiring]. +Klíč `autowired` umožňuje ovlivnit chování autowiringu pro konkrétní službu. Pro detaily viz [kapitola o autowiringu|autowiring]. ```neon services: @@ -347,10 +340,28 @@ services: ``` +Lazy služby .{data-version:3.2.4} +================================= + +Lazy loading je technika, která odkládá vytvoření služby až do chvíle, kdy je skutečně potřeba. V globální konfiguraci lze [povolit lazy vytváření |configuration#Lazy služby] pro všechny služby najednou. Pro jednotlivé služby pak můžete toto chování přepsat: + +```neon +services: + foo: + create: Foo + lazy: false +``` + +Když je služba definovaná jako lazy, při jejím vyžádání z DI kontejneru dostaneme speciální zástupný objekt. Ten vypadá a chová se stejně jako skutečná služba, ale skutečná inicializace (volání konstruktoru a setupu) proběhne až při prvním volání jakékoliv její metody nebo property. + +.[note] +Lazy loading lze použít pouze pro uživatelské třídy, nikoliv pro interní PHP třídy. Vyžaduje PHP 8.4 nebo novější. + + Tagy ==== -Jednotlivým službám lze přidávat uživatelské informace v podobě tzv. tagů: +Tagy slouží k přidání doplňujících informací k službám. Službě můžete přidat jeden nebo více tagů: ```neon services: @@ -360,7 +371,7 @@ services: - cached ``` -Tagy mohou mít i hodnotu: +Tagy mohou také nést hodnoty: ```neon services: @@ -370,14 +381,14 @@ services: logger: monolog.logger.event ``` -Pole služeb s určitými tagy lze předat jako argument pomocí funkce `tagged()`. Lze uvést i více tagů oddělených čárkou. +Abyste získali všechny služby s určitými tagy, můžete použít funkci `tagged()`: ```neon services: - LoggersDependent( tagged(logger) ) ``` -Názvy služeb lze získat z DI kontejneru metodou `findByTag()`: +V DI kontejneru můžete získat názvy všech služeb s určitým tagem pomocí metody `findByTag()`: ```php $names = $container->findByTag('logger'); @@ -389,7 +400,7 @@ $names = $container->findByTag('logger'); Režim Inject ============ -Pomocí příznaku `inject: true` se aktivuje předávání závislostí přes veřejné proměnné s anotací [inject |best-practices:inject-method-attribute#Atributy Inject] a metody [inject*() |best-practices:inject-method-attribute#metody inject]. +Pomocí příznaku `inject: true` se aktivuje předávání závislostí přes veřejné proměnné s anotací [inject |best-practices:inject-method-attribute#Atributy Inject] a metody [inject*() |best-practices:inject-method-attribute#Metody inject]. ```neon services: @@ -398,13 +409,13 @@ services: inject: true ``` -V základním nastavení je `inject` aktivováno pouze pro presentery. +Ve výchozím nastavení je `inject` aktivováno pouze pro presentery. Modifikace služeb ================= -V DI kontejneru je řada služeb, které přidaly vestavěné nebo [vaše rozšíření|#rozšíření]. Definice těchto služeb lze v konfiguraci pozměnit. Třeba u služby `application.application`, což je standardně objekt `Nette\Application\Application`, můžeme změnit třídu: +DI kontejner obsahuje mnoho služeb, které byly přidány prostřednictvím vestavěného nebo [uživatelského rozšíření|extensions]. Můžete upravit definice těchto služeb přímo v konfiguraci. Například můžete změnit třídu služby `application.application`, což je standardně `Nette\Application\Application`, na jinou: ```neon services: @@ -439,7 +450,7 @@ services: - tags ``` -Službu přidanou rozšířením lze také z kontejneru odstranit: +Pokud chcete odstranit službu přidanou rozšířením, můžete to udělat takto: ```neon services: diff --git a/dependency-injection/de/@home.texy b/dependency-injection/de/@home.texy index da4652cddc..2f23b47fd6 100644 --- a/dependency-injection/de/@home.texy +++ b/dependency-injection/de/@home.texy @@ -1,24 +1,21 @@ -Injektion von Abhängigkeiten -**************************** +Nette DI +******** .[perex] -Dependency Injection ist ein Entwurfsmuster, das die Art und Weise, wie Sie Code und Entwicklung betrachten, grundlegend verändern wird. Es öffnet den Weg in eine Welt der sauber gestalteten und nachhaltigen Anwendungen. +Dependency Injection ist ein Entwurfsmuster, das Ihre Sichtweise auf Code und Entwicklung grundlegend verändern wird. Es öffnet Ihnen den Weg in die Welt sauber gestalteter und wartbarer Anwendungen. - [Was ist Dependency Injection? |introduction] -- [Globaler Zustand & Singletons |global-state] -- [Übergabe von Abhängigkeiten |passing-dependencies] +- [Globaler Zustand und Singletons |global-state] +- [Übergeben von Abhängigkeiten |passing-dependencies] - [Was ist ein DI-Container? |container] -- [Häufig gestellte Fragen |faq] - +- [Häufig gestellte Fragen|faq] -Nette DI --------- -Das Paket `nette/di` bietet einen extrem fortschrittlichen kompilierten DI-Container für PHP. +Das Paket `nette/di` bietet einen äußerst fortschrittlichen kompilierten DI-Container für PHP. -- [Nette DI-Container |nette-container] +- [Nette DI Container |nette-container] - [Konfiguration |configuration] -- [Dienst-Definitionen |services] +- [Definieren von Diensten |services] - [Autowiring |autowiring] -- [Generierte Fabriken |factory] -- [Erstellen von Erweiterungen für Nette DI |extensions] +- [Generierte Factories |factory] +- [Erstellen von Erweiterungen für Nette DI|extensions] diff --git a/dependency-injection/de/@left-menu.texy b/dependency-injection/de/@left-menu.texy index 404d36326d..40f65f1e59 100644 --- a/dependency-injection/de/@left-menu.texy +++ b/dependency-injection/de/@left-menu.texy @@ -1,17 +1,17 @@ -Injektion von Abhängigkeiten -**************************** +Dependency Injection +******************** - [Was ist DI? |introduction] -- [Globaler Zustand & Singletons |global-state] -- [Übergabe von Abhängigkeiten |passing-dependencies] +- [Globaler Zustand und Singletons |global-state] +- [Abhängigkeiten übergeben |passing-dependencies] - [Was ist ein DI-Container? |container] -- [Häufig gestellte Fragen |faq] +- [Häufig gestellte Fragen|faq] Nette DI -------- -- [Nette DI-Container |nette-container] +- [Nette DI Container |nette-container] - [Konfiguration |configuration] -- [Dienst-Definitionen |services] +- [Dienste definieren |services] - [Autowiring |autowiring] -- [Generierte Fabriken |factory] -- [Erstellen von Erweiterungen für Nette DI |extensions] +- [Generierte Factories |factory] +- [Erweiterungen für Nette DI erstellen |extensions] diff --git a/dependency-injection/de/@meta.texy b/dependency-injection/de/@meta.texy new file mode 100644 index 0000000000..b3b806b2ca --- /dev/null +++ b/dependency-injection/de/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokumentation}} diff --git a/dependency-injection/de/autowiring.texy b/dependency-injection/de/autowiring.texy index b5af4bf042..354c6e4e51 100644 --- a/dependency-injection/de/autowiring.texy +++ b/dependency-injection/de/autowiring.texy @@ -1,24 +1,24 @@ -Fahrzeugverkabelung -******************* +Autowiring +********** .[perex] -Autowiring ist eine großartige Funktion, mit der automatisch Dienste an den Konstruktor und andere Methoden übergeben werden können, so dass wir sie gar nicht erst schreiben müssen. Das spart Ihnen eine Menge Zeit. +Autowiring ist eine großartige Funktion, die automatisch die benötigten Dienste an den Konstruktor und andere Methoden übergeben kann, sodass wir sie überhaupt nicht schreiben müssen. Es spart Ihnen viel Zeit. -So können wir beim Schreiben von Dienstdefinitionen die meisten Argumente weglassen. Anstelle von: +Dadurch können wir die meisten Argumente beim Schreiben von Dienstdefinitionen weglassen. Anstelle von: ```neon services: articles: Model\ArticleRepository(@database, @cache.storage) ``` -Schreiben Sie einfach: +Reicht es aus zu schreiben: ```neon services: articles: Model\ArticleRepository ``` -Autowiring wird durch Typen gesteuert, daher muss die Klasse `ArticleRepository` wie folgt definiert werden: +Autowiring orientiert sich an Typen, daher muss die Klasse `ArticleRepository` ungefähr so definiert sein, damit es funktioniert: ```php namespace Model; @@ -30,22 +30,22 @@ class ArticleRepository } ``` -Um Autowiring nutzen zu können, muss es **nur einen Dienst** für jeden Typ im Container geben. Gäbe es mehr, wüsste Autowiring nicht, welchen es übergeben soll und würde eine Ausnahme auslösen: +Um Autowiring verwenden zu können, muss für jeden Typ im Container **genau ein Dienst** vorhanden sein. Gäbe es mehr, wüsste Autowiring nicht, welchen er übergeben soll, und würde eine Ausnahme auslösen: ```neon services: mainDb: PDO(%dsn%, %user%, %password%) tempDb: PDO('sqlite::memory:') - articles: Model\ArticleRepository # THROWS EXCEPTION, sowohl mainDb als auch tempDb passen + articles: Model\ArticleRepository # WIRFT EINE AUSNAHME, sowohl mainDb als auch tempDb passen ``` -Die Lösung wäre, entweder Autowiring zu umgehen und den Dienstnamen explizit anzugeben (z. B. `articles: Model\ArticleRepository(@mainDb)`). Es ist jedoch bequemer, das Autowiring eines Dienstes [zu deaktivieren |#Disabled autowiring], oder den ersten Dienst [vorzuziehen |#Preferred Autowiring]. +Die Lösung wäre entweder, Autowiring zu umgehen und den Dienstnamen explizit anzugeben (d.h. `articles: Model\ArticleRepository(@mainDb)`). Geschickter ist es jedoch, das Autowiring für einen der Dienste [zu deaktivieren |#Deaktivieren des Autowirings] oder den ersten Dienst [zu bevorzugen |#Bevorzugung beim Autowiring]. -Deaktiviertes Autowiring .[#toc-disabled-autowiring] ----------------------------------------------------- +Deaktivieren des Autowirings +---------------------------- -Sie können die automatische Verdrahtung von Diensten mit der Option `autowired: no` deaktivieren: +Wir können das Autowiring eines Dienstes mit der Option `autowired: no` deaktivieren: ```neon services: @@ -53,28 +53,27 @@ services: tempDb: create: PDO('sqlite::memory:') - autowired: false # Entfernt tempDb von autowiring + autowired: false # Der Dienst tempDb wird vom Autowiring ausgeschlossen - articles: Model\ArticleRepository # übergibt daher mainDb an den Konstruktor + articles: Model\ArticleRepository # übergibt daher mainDb an den Konstruktor ``` -Der Dienst `articles` löst nicht die Ausnahme aus, dass es zwei passende Dienste des Typs `PDO` (d.h. `mainDb` und `tempDb`) gibt, die an den Konstruktor übergeben werden können, da er nur den Dienst `mainDb` sieht. +Der Dienst `articles` löst keine Ausnahme aus, dass zwei passende Dienste vom Typ `PDO` (d.h. `mainDb` und `tempDb`) existieren, die an den Konstruktor übergeben werden können, da er nur den Dienst `mainDb` sieht. .[note] -Das Konfigurieren von Autowiring in Nette funktioniert anders als in Symfony, wo die Option `autowire: false` besagt, dass Autowiring nicht für Service-Konstruktor-Argumente verwendet werden soll. -In Nette wird Autowiring immer verwendet, ob für Argumente des Konstruktors oder einer anderen Methode. Die Option `autowired: false` besagt, dass die Service-Instanz nirgendwo mit Autowiring übergeben werden soll. +Die Konfiguration des Autowirings in Nette funktioniert anders als in Symfony, wo die Option `autowire: false` besagt, dass Autowiring nicht für die Konstruktorargumente des betreffenden Dienstes verwendet werden soll. In Nette wird Autowiring immer verwendet, sei es für Konstruktorargumente oder für andere Methoden. Die Option `autowired: false` besagt, dass die Instanz des betreffenden Dienstes nirgendwo per Autowiring übergeben werden soll. -Bevorzugtes Autowiring .[#toc-preferred-autowiring] ---------------------------------------------------- +Bevorzugung beim Autowiring +--------------------------- -Wenn es mehrere Dienste desselben Typs gibt und einer von ihnen die Option `autowired` hat, wird dieser Dienst bevorzugt: +Wenn wir mehrere Dienste desselben Typs haben und bei einem davon die Option `autowired` angeben, wird dieser Dienst bevorzugt: ```neon services: mainDb: create: PDO(%dsn%, %user%, %password%) - autowired: PDO # macht es bevorzugt + autowired: PDO # wird bevorzugt tempDb: create: PDO('sqlite::memory:') @@ -82,13 +81,13 @@ services: articles: Model\ArticleRepository ``` -Der Dienst `articles` macht nicht die Ausnahme, dass es zwei übereinstimmende Dienste `PDO` gibt (d. h. `mainDb` und `tempDb`), sondern verwendet den bevorzugten Dienst, d. h. `mainDb`. +Der Dienst `articles` löst keine Ausnahme aus, dass zwei passende Dienste vom Typ `PDO` (d.h. `mainDb` und `tempDb`) existieren, sondern verwendet den bevorzugten Dienst, also `mainDb`. -Sammlung von Diensten .[#toc-collection-of-services] ----------------------------------------------------- +Array von Diensten +------------------ -Autowiring kann auch ein Array von Diensten eines bestimmten Typs übergeben. Da PHP den Typ von Array-Elementen nicht nativ notieren kann, muss zusätzlich zum Typ `array` ein phpDoc-Kommentar mit dem Elementtyp wie `ClassName[]` hinzugefügt werden: +Autowiring kann auch Arrays von Diensten eines bestimmten Typs übergeben. Da in PHP der Typ der Array-Elemente nicht nativ angegeben werden kann, muss zusätzlich zum Typ `array` ein phpDoc-Kommentar mit dem Elementtyp im Format `ClassName[]` hinzugefügt werden: ```php namespace Model; @@ -103,46 +102,45 @@ class ShipManager } ``` -Der DI-Container übergibt dann automatisch ein Array von Diensten, die dem angegebenen Typ entsprechen. Er wird Dienste auslassen, bei denen die automatische Verdrahtung ausgeschaltet ist. +Der DI-Container übergibt dann automatisch ein Array von Diensten, die dem angegebenen Typ entsprechen. Dienste, deren Autowiring deaktiviert ist, werden ausgelassen. -Wenn Sie die Form des phpDoc-Kommentars nicht kontrollieren können, können Sie ein Array von Diensten direkt in der Konfiguration übergeben, indem Sie [`typed()` |services#Special Functions]. +Der Typ im Kommentar kann auch im Format `array<int, Class>` oder `list<Class>` vorliegen. Wenn Sie die Form des phpDoc-Kommentars nicht beeinflussen können, können Sie das Array von Diensten direkt in der Konfiguration mithilfe von [`typed()` |services#Spezielle Funktionen] übergeben. -Skalar-Argumente .[#toc-scalar-arguments] ------------------------------------------ +Skalare Argumente +----------------- -Autowiring kann nur Objekte und Arrays von Objekten übergeben. Skalare Argumente (z.B. Strings, Zahlen, Booleans) [werden in die Konfiguration geschrieben |services#Arguments]. -Eine Alternative ist die Erstellung eines [Einstellungsobjekts |best-practices:passing-settings-to-presenters], das einen skalaren Wert (oder mehrere Werte) als Objekt kapselt, das dann wiederum mit Autowiring übergeben werden kann. +Autowiring kann nur Objekte und Arrays von Objekten einfügen. Skalare Argumente (z. B. Zeichenketten, Zahlen, Booleans) [schreiben wir in der Konfiguration |services#Argumente]. Eine Alternative ist die Erstellung eines [Einstellungsobjekts |best-practices:passing-settings-to-presenters], das den skalaren Wert (oder mehrere Werte) in ein Objekt kapselt, welches dann wieder per Autowiring übergeben werden kann. ```php class MySettings { public function __construct( - // readonly kann seit PHP 8.1 verwendet werden + // readonly kann ab PHP 8.1 verwendet werden public readonly bool $value, ) {} } ``` -Sie erstellen einen Dienst, indem Sie ihn zur Konfiguration hinzufügen: +Sie erstellen daraus einen Dienst, indem Sie ihn zur Konfiguration hinzufügen: ```neon services: - MySettings('any value') ``` -Alle Klassen werden ihn dann über Autowiring anfordern. +Alle Klassen fordern ihn dann per Autowiring an. -Eingrenzung des Autowiring .[#toc-narrowing-of-autowiring] ----------------------------------------------------------- +Einschränken des Autowirings +---------------------------- -Für einzelne Dienste kann das Autowiring auf bestimmte Klassen oder Schnittstellen eingegrenzt werden. +Für einzelne Dienste kann das Autowiring auf bestimmte Klassen oder Schnittstellen eingeschränkt werden. -Normalerweise übergibt das Autowiring den Dienst an jeden Methodenparameter, dessen Typ der Dienst entspricht. Eingrenzen bedeutet, dass wir Bedingungen angeben, die die für die Methodenparameter angegebenen Typen erfüllen müssen, damit der Dienst an sie übergeben wird. +Normalerweise übergibt Autowiring einen Dienst an jeden Methodenparameter, dessen Typ dem Dienst entspricht. Die Einschränkung bedeutet, dass wir Bedingungen festlegen, denen die bei den Methodenparametern angegebenen Typen entsprechen müssen, damit der Dienst an sie übergeben wird. -Nehmen wir ein Beispiel: +Zeigen wir dies an einem Beispiel: ```php class ParentClass @@ -164,42 +162,42 @@ class ChildDependent } ``` -Wenn wir sie alle als Dienste registrieren würden, würde die automatische Verdrahtung fehlschlagen: +Wenn wir sie alle als Dienste registrieren würden, würde Autowiring fehlschlagen: ```neon services: parent: ParentClass child: ChildClass - parentDep: ParentDependent # THROWS EXCEPTION, sowohl parent als auch child stimmen überein - childDep: ChildDependent # übergibt den Dienst 'child' an den Konstruktor + parentDep: ParentDependent # WIRFT EINE AUSNAHME, sowohl parent als auch child passen + childDep: ChildDependent # Autowiring übergibt den Dienst child an den Konstruktor ``` -Der Dienst `parentDep` löst die Ausnahme `Multiple services of type ParentClass found: parent, child` aus, weil sowohl `parent` als auch `child` in seinen Konstruktor passen und Autowiring nicht entscheiden kann, welcher davon ausgewählt werden soll. +Der Dienst `parentDep` löst die Ausnahme `Multiple services of type ParentClass found: parent, child` aus, da beide Dienste `parent` und `child` in seinen Konstruktor passen und Autowiring nicht entscheiden kann, welchen es wählen soll. -Für den Dienst `child` können wir daher das Autowiring auf `ChildClass` eingrenzen: +Für den Dienst `child` können wir daher sein Autowiring auf den Typ `ChildClass` einschränken: ```neon services: parent: ParentClass child: create: ChildClass - autowired: ChildClass # alternativ: 'autowired: self' + autowired: ChildClass # kann auch 'autowired: self' geschrieben werden - parentDep: ParentDependent # THROWS EXCEPTION, das 'Kind' kann nicht autowired sein - childDep: ChildDependent # übergibt den Dienst 'child' an den Konstruktor + parentDep: ParentDependent # Autowiring übergibt den Dienst parent an den Konstruktor + childDep: ChildDependent # Autowiring übergibt den Dienst child an den Konstruktor ``` -Der Dienst `parentDep` wird nun an den Dienstkonstruktor `parentDep` übergeben, da er nun das einzige passende Objekt ist. Der Dienst `child` wird nicht mehr per Autowiring übergeben. Ja, der Dienst `child` ist immer noch vom Typ `ParentClass`, aber die für den Parametertyp angegebene Einschränkungsbedingung gilt nicht mehr, d. h. es ist nicht mehr wahr, dass `ParentClass` *ein Supertyp* von `ChildClass` ist. +Nun wird der Dienst `parent` an den Konstruktor von `parentDep` übergeben, da er jetzt das einzige passende Objekt ist. Der Dienst `child` wird dort vom Autowiring nicht mehr übergeben. Ja, der Dienst `child` ist immer noch vom Typ `ParentClass`, aber die einschränkende Bedingung für den Parametertyp gilt nicht mehr, d.h. es gilt nicht, dass `ParentClass` *ein Supertyp* von `ChildClass` ist. -Im Fall von `child` könnte `autowired: ChildClass` als `autowired: self` geschrieben werden, da `self` den aktuellen Diensttyp bezeichnet. +Für den Dienst `child` könnte `autowired: ChildClass` auch als `autowired: self` geschrieben werden, da `self` ein Platzhalter für die Klasse des aktuellen Dienstes ist. -Der Schlüssel `autowired` kann mehrere Klassen und Schnittstellen als Array enthalten: +Im Schlüssel `autowired` können auch mehrere Klassen oder Schnittstellen als Array angegeben werden: ```neon autowired: [BarClass, FooInterface] ``` -Versuchen wir, dem Beispiel Schnittstellen hinzuzufügen: +Ergänzen wir das Beispiel noch um Schnittstellen: ```php interface FooInterface @@ -239,13 +237,13 @@ class ChildDependent } ``` -Wenn wir den Dienst `child` nicht einschränken, wird er in die Konstruktoren aller Klassen `FooDependent`, `BarDependent`, `ParentDependent` und `ChildDependent` passen und die automatische Verdrahtung wird ihn dort übergeben. +Wenn wir den Dienst `child` nicht einschränken, passt er in die Konstruktoren aller Klassen `FooDependent`, `BarDependent`, `ParentDependent` und `ChildDependent`, und Autowiring übergibt ihn dorthin. -Wenn wir jedoch das Autowiring mit `autowired: ChildClass` (oder `self`) auf `ChildClass` einschränken, wird er nur an den `ChildDependent` -Konstruktor übergeben, da dieser ein Argument vom Typ `ChildClass` benötigt und `ChildClass` *vom Typ* `ChildClass` ist. Kein anderer Typ, der für die anderen Parameter angegeben ist, ist eine Obermenge von `ChildClass`, so dass der Dienst nicht übergeben wird. +Wenn wir sein Autowiring jedoch auf `ChildClass` mit `autowired: ChildClass` (oder `self`) einschränken, übergibt Autowiring ihn nur an den Konstruktor von `ChildDependent`, da dieser ein Argument vom Typ `ChildClass` erfordert und `ChildClass` *vom Typ* `ChildClass` ist. Kein anderer bei den weiteren Parametern angegebener Typ ist ein Supertyp von `ChildClass`, daher wird der Dienst nicht übergeben. -Wenn wir ihn mit `autowired: ParentClass` auf `ParentClass` beschränken, wird er von Autowiring wieder an den `ChildDependent` -Konstruktor (da der erforderliche Typ `ChildClass` eine Obermenge von `ParentClass` ist) und auch an den `ParentDependent` -Konstruktor weitergegeben, da der erforderliche Typ von `ParentClass` ebenfalls passt. +Wenn wir ihn auf `ParentClass` mit `autowired: ParentClass` beschränken, übergibt Autowiring ihn erneut an den Konstruktor von `ChildDependent` (da das erforderliche `ChildClass` ein Supertyp von `ParentClass` ist) und neu auch an den Konstruktor von `ParentDependent`, da der erforderliche Typ `ParentClass` ebenfalls passend ist. -Wenn wir es auf `FooInterface` einschränken, wird es immer noch an `ParentDependent` (der erforderliche Typ `ParentClass` ist ein Supertyp von `FooInterface`) und `ChildDependent` weitergegeben, aber zusätzlich an den `FooDependent` Konstruktor, aber nicht an `BarDependent`, da `BarInterface` kein Supertyp von `FooInterface` ist. +Wenn wir ihn auf `FooInterface` beschränken, wird er immer noch in `ParentDependent` (erforderliches `ParentClass` ist Supertyp von `FooInterface`) und `ChildDependent` autowired, aber zusätzlich auch in den Konstruktor von `FooDependent`, jedoch nicht in `BarDependent`, da `BarInterface` kein Supertyp von `FooInterface` ist. ```neon services: @@ -253,8 +251,8 @@ services: create: ChildClass autowired: FooInterface - fooDep: FooDependent # übergibt den untergeordneten Dienst an den Konstruktor - barDep: BarDependent # THROWS EXCEPTION, kein Dienst würde übergeben - parentDep: ParentDependent # übergibt den untergeordneten Dienst an den Konstruktor - childDep: ChildDependen # übergibt den untergeordneten Dienst an den Konstrukteur + fooDep: FooDependent # Autowiring übergibt child an den Konstruktor + barDep: BarDependent # WIRFT EINE AUSNAHME, kein Dienst passt + parentDep: ParentDependent # Autowiring übergibt child an den Konstruktor + childDep: ChildDependent # Autowiring übergibt child an den Konstruktor ``` diff --git a/dependency-injection/de/configuration.texy b/dependency-injection/de/configuration.texy index 897d0a2df3..27e564e3b5 100644 --- a/dependency-injection/de/configuration.texy +++ b/dependency-injection/de/configuration.texy @@ -1,32 +1,33 @@ -DI-Container konfigurieren -************************** +Konfiguration des DI-Containers +******************************* .[perex] -Überblick über die Konfigurationsmöglichkeiten für den Nette-DI-Container. +Übersicht über die Konfigurationsoptionen für den Nette DI Container. Konfigurationsdatei =================== -Der Nette-DI-Container lässt sich leicht über Konfigurationsdateien steuern. Sie sind normalerweise im [NEON-Format |neon:format] geschrieben. Wir empfehlen, für die Bearbeitung [Editoren |best-practices:editors-and-tools#ide-editor] zu verwenden [, die |best-practices:editors-and-tools#ide-editor] dieses Format [unterstützen |best-practices:editors-and-tools#ide-editor]. +Der Nette DI Container lässt sich leicht über Konfigurationsdateien steuern. Diese werden normalerweise im [NEON-Format|neon:format] geschrieben. Zur Bearbeitung empfehlen wir [Editoren mit Unterstützung |best-practices:editors-and-tools#IDE-Editor] für dieses Format. <pre> -"decorator .[prism-token prism-atrule]":[#Decorator]: "Dekorateur .[prism-token prism-comment]"<br> +"decorator .[prism-token prism-atrule]":[#Decorator]: "Decorator .[prism-token prism-comment]"<br> "di .[prism-token prism-atrule]":[#DI]: "DI-Container .[prism-token prism-comment]"<br> -"extensions .[prism-token prism-atrule]":[#Extensions]: "Zusätzliche DI-Erweiterungen installieren .[prism-token prism-comment]"<br> -"includes .[prism-token prism-atrule]":[#Including files]: "Einschließlich Dateien .[prism-token prism-comment]"<br> -"parameters .[prism-token prism-atrule]":[#Parameters]: "Parameter .[prism-token prism-comment]"<br> -"search .[prism-token prism-atrule]":[#Search]: "Automatische Registrierung von Diensten .[prism-token prism-comment]"<br> -"services .[prism-token prism-atrule]":[services]: "Dienstleistungen .[prism-token prism-comment]" +"extensions .[prism-token prism-atrule]":[#Erweiterungen]: "Installation weiterer DI-Erweiterungen .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[#Dateien einbinden]: "Einbinden von Dateien .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[#Parameter]: "Parameter .[prism-token prism-comment]"<br> +"search .[prism-token prism-atrule]":[#Suche]: "Automatische Registrierung von Diensten .[prism-token prism-comment]"<br> +"services .[prism-token prism-atrule]":[services]: "Dienste .[prism-token prism-comment]" </pre> -So schreiben Sie eine Zeichenfolge, die das Zeichen `%`, you must escape it by doubling it to `%%` enthält. .[note] +.[note] +Um eine Zeichenkette zu schreiben, die das Zeichen `%` enthält, müssen Sie es durch Verdoppelung auf `%%` escapen. -Parameter .[#toc-parameters] -============================ +Parameter +========= -Sie können Parameter definieren, die dann als Teil von Dienstdefinitionen verwendet werden können. Auf diese Weise können Sie Werte, die Sie häufiger ändern möchten, besser herausfiltern. +In der Konfiguration können Sie Parameter definieren, die dann als Teil der Dienstdefinitionen verwendet werden können. Dadurch können Sie die Konfiguration übersichtlicher gestalten oder Werte vereinheitlichen und ausgliedern, die sich ändern werden. ```neon parameters: @@ -35,9 +36,9 @@ parameters: password: secret ``` -Sie können auf den Parameter `foo` über `%foo%` an anderer Stelle in jeder Konfigurationsdatei verweisen. Sie können auch innerhalb von Zeichenketten wie `'%wwwDir%/images'` verwendet werden. +Auf den Parameter `dsn` verweisen wir überall in der Konfiguration mit der Schreibweise `%dsn%`. Parameter können auch innerhalb von Zeichenketten wie `'%wwwDir%/images'` verwendet werden. -Parameter müssen nicht nur Strings sein, sie können auch Array-Werte sein: +Parameter müssen nicht nur Zeichenketten oder Zahlen sein, sie können auch Arrays enthalten: ```neon parameters: @@ -48,32 +49,32 @@ parameters: languages: [cs, en, de] ``` -Sie können sich auf einzelne Schlüssel als `%mailer.user%` beziehen. +Auf einen bestimmten Schlüssel verweisen wir als `%mailer.user%`. -Wenn Sie den Wert eines Parameters in Ihrem Code benötigen, z. B. in Ihrer Klasse, dann übergeben Sie ihn an diese Klasse. Zum Beispiel im Konstruktor. Es gibt kein globales Konfigurationsobjekt, das Klassen nach Parameterwerten abfragen können. Dies würde gegen das Prinzip der Dependency Injection verstoßen. +Wenn Sie in Ihrem Code, beispielsweise in einer Klasse, den Wert eines Parameters ermitteln müssen, übergeben Sie ihn an diese Klasse. Zum Beispiel im Konstruktor. Es gibt kein globales Objekt, das die Konfiguration repräsentiert, bei dem Klassen Parameterwerte abfragen könnten. Das würde gegen das Prinzip der Dependency Injection verstoßen. -Dienste .[#toc-services] -======================== +Dienste +======= -Siehe [separates Kapitel |services]. +Siehe [separates Kapitel|services]. -Dekorateur .[#toc-decorator] -============================ +Decorator +========= -Wie kann man alle Dienste eines bestimmten Typs als Ganzes bearbeiten? Müssen Sie eine bestimmte Methode für alle Präsentatoren aufrufen, die von einem bestimmten gemeinsamen Vorfahren erben? Genau dafür gibt es den Decorator. +Wie kann man alle Dienste eines bestimmten Typs massenhaft ändern? Zum Beispiel eine bestimmte Methode bei allen Presentern aufrufen, die von einem bestimmten gemeinsamen Vorfahren erben? Dafür gibt es den Decorator. ```neon decorator: # für alle Dienste, die Instanzen dieser Klasse oder Schnittstelle sind - App\Presenters\BasePresenter: + App\Presentation\BasePresenter: setup: - - setProjectId(10) # diese Methode aufrufen - - $absoluteUrls = true # und setze die Variable + - setProjectId(10) # rufe diese Methode auf + - $absoluteUrls = true # und setze die Variable ``` -Decorator kann auch verwendet werden, um [Tags |services#Tags] zu setzen oder den [Inject-Modus |services#Inject Mode] zu aktivieren. +Der Decorator kann auch verwendet werden, um [Tags |services#Tags] zu setzen oder den [inject |services#Inject-Modus]-Modus zu aktivieren. ```neon decorator: @@ -90,63 +91,75 @@ Technische Einstellungen des DI-Containers. ```neon di: - # zeigt DIC in Tracy Bar? - debugger: ... # (bool) standardmäßig true + # DIC in der Tracy Bar anzeigen? + debugger: ... # (bool) Standard ist true - # Parametertypen, die Sie niemals automatisch verdrahten + # Parametertypen, die niemals autowired werden sollen excluded: ... # (string[]) - # die Klasse, von der der DI-Container erbt - parentClass: ... # (string) ist standardmäßig Nette\DI\Container + # lazy Erstellung von Diensten erlauben? + lazy: ... # (bool) Standard ist false + + # Klasse, von der der DI-Container erbt + parentClass: ... # (string) Standard ist Nette\DI\Container ``` -Metadaten-Export .[#toc-metadata-export] ----------------------------------------- +Lazy Dienste .{data-version:3.2.4} +---------------------------------- + +Die Einstellung `lazy: true` aktiviert die lazy (verzögerte) Erstellung von Diensten. Das bedeutet, dass Dienste nicht tatsächlich erstellt werden, wenn wir sie vom DI-Container anfordern, sondern erst im Moment ihrer ersten Verwendung. Dies kann den Start der Anwendung beschleunigen und den Speicherbedarf reduzieren, da nur die Dienste erstellt werden, die im jeweiligen Request tatsächlich benötigt werden. + +Für einen bestimmten Dienst kann die lazy Erstellung [geändert werden |services#Lazy Dienste]. + +.[note] +Lazy Objekte können nur für benutzerdefinierte Klassen verwendet werden, nicht für interne PHP-Klassen. Erfordert PHP 8.4 oder neuer. + -Die DI-Containerklasse enthält auch eine Menge Metadaten. Sie können diese reduzieren, indem Sie den Metadatenexport verringern. +Export von Metadaten +-------------------- + +Die DI-Container-Klasse enthält auch viele Metadaten. Sie können sie verkleinern, indem Sie den Export von Metadaten reduzieren. ```neon di: export: - # Parameter zu exportieren? - parameters: false # (bool) ist standardmäßig auf true eingestellt + # Parameter exportieren? + parameters: false # (bool) Standard ist true # Tags exportieren und welche? - tags: # (string[]|bool) der Standard ist all + tags: # (string[]|bool) Standard sind alle - event.subscriber - # exportiere Daten für Autowiring und welche? - types: # (string[]|bool) die Voreinstellung ist all + # Daten für Autowiring exportieren und welche? + types: # (string[]|bool) Standard sind alle - Nette\Database\Connection - Symfony\Component\Console\Application ``` -Wenn Sie das Array `$container->parameters` nicht verwenden, können Sie den Export der Parameter deaktivieren. Außerdem können Sie nur die Tags exportieren, über die Sie mit der Methode `$container->findByTag(...)` Dienste erhalten. -Wenn Sie die Methode gar nicht aufrufen, können Sie den Tag-Export mit `false` vollständig deaktivieren. +Wenn Sie das Array `$container->getParameters()` nicht verwenden, können Sie den Parameter-Export deaktivieren. Weiterhin können Sie nur die Tags exportieren, über die Sie Dienste mit der Methode `$container->findByTag(...)` abrufen. Wenn Sie die Methode überhaupt nicht aufrufen, können Sie den Tag-Export mit `false` vollständig deaktivieren. -Sie können die Metadaten für die [automatische Verdrahtung |autowiring] erheblich reduzieren, indem Sie die von Ihnen verwendeten Klassen als Parameter für die Methode `$container->getByType()` angeben. -Und wenn Sie die Methode gar nicht aufrufen (oder nur in [application:bootstrap], um `Nette\Application\Application` zu erhalten), können Sie den Export mit `false` vollständig deaktivieren. +Sie können die Metadaten für [Autowiring|autowiring] erheblich reduzieren, indem Sie die Klassen angeben, die Sie als Parameter der Methode `$container->getByType()` verwenden. Und wiederum, wenn Sie die Methode überhaupt nicht aufrufen (bzw. nur im [Bootstrap|application:bootstrapping], um `Nette\Application\Application` zu erhalten), können Sie den Export mit `false` vollständig deaktivieren. -Erweiterungen .[#toc-extensions] -================================ +Erweiterungen +============= -Registrierung von anderen DI-Erweiterungen. Auf diese Weise fügen wir z.B. die DI-Erweiterung `Dibi\Bridges\Nette\DibiExtension22` unter dem Namen `dibi` hinzu: +Registrierung weiterer DI-Erweiterungen. Auf diese Weise fügen wir z. B. die DI-Erweiterung `Dibi\Bridges\Nette\DibiExtension22` unter dem Namen `dibi` hinzu ```neon extensions: dibi: Dibi\Bridges\Nette\DibiExtension22 ``` -Dann konfigurieren wir sie in ihrem Abschnitt, der auch `dibi` heißt: +Anschließend konfigurieren wir sie im Abschnitt `dibi`: ```neon dibi: host: localhost ``` -Sie können auch eine Erweiterungsklasse mit Parametern hinzufügen: +Als Erweiterung kann auch eine Klasse hinzugefügt werden, die Parameter hat: ```neon extensions: @@ -154,10 +167,10 @@ extensions: ``` -Dateien einschließen .[#toc-including-files] -============================================ +Dateien einbinden +================= -Zusätzliche Konfigurationsdateien können im Abschnitt `includes` eingefügt werden: +Weitere Konfigurationsdateien können wir im Abschnitt `includes` einfügen: ```neon includes: @@ -166,7 +179,7 @@ includes: - presenters.neon ``` -Der Name `parameters.php` ist kein Tippfehler, die Konfiguration kann auch in eine PHP-Datei geschrieben werden, die sie als Array zurückgibt: +Der Name `parameters.php` ist kein Tippfehler, die Konfiguration kann auch in einer PHP-Datei geschrieben werden, die sie als Array zurückgibt: ```php <?php @@ -179,78 +192,74 @@ return [ ]; ``` -Wenn Elemente mit denselben Schlüsseln in Konfigurationsdateien vorkommen, werden sie [überschrieben oder |#Merging] im Falle von Arrays [zusammengeführt |#Merging]. Die später eingefügte Datei hat eine höhere Priorität als die vorherige. Die Datei, in der der Abschnitt `includes` aufgeführt ist, hat eine höhere Priorität als die darin enthaltenen Dateien. +Wenn in Konfigurationsdateien Elemente mit denselben Schlüsseln erscheinen, werden sie überschrieben oder im Falle von [Arrays zusammengeführt |#Zusammenführen]. Eine später eingebundene Datei hat eine höhere Priorität als die vorherige. Die Datei, in der der Abschnitt `includes` aufgeführt ist, hat eine höhere Priorität als die darin eingebundenen Dateien. -Suche .[#toc-search] -==================== +Suche +===== -Das automatische Hinzufügen von Diensten zum DI-Container macht die Arbeit sehr angenehm. Nette fügt automatisch Presenter in den Container ein, aber Sie können auch ganz einfach andere Klassen hinzufügen. +Das automatische Hinzufügen von Diensten zum DI-Container macht die Arbeit äußerst angenehm. Nette fügt Presenter automatisch zum Container hinzu, aber es können auch problemlos beliebige andere Klassen hinzugefügt werden. -Geben Sie einfach an, in welchen Verzeichnissen (und Unterverzeichnissen) nach den Klassen gesucht werden soll: +Es genügt anzugeben, in welchen Verzeichnissen (und Unterverzeichnissen) nach Klassen gesucht werden soll: ```neon -Suche: - # Sie wählen die Namen der Abschnitte selbst - myForms: - in: %appDir%/Forms - - model: - in: %appDir%/Model +search: + - in: %appDir%/Forms + - in: %appDir%/Model ``` -Normalerweise wollen wir jedoch nicht alle Klassen und Schnittstellen hinzufügen, so dass wir sie filtern können: +Normalerweise möchten wir jedoch nicht alle Klassen und Schnittstellen hinzufügen, daher können wir sie filtern: ```neon search: - myForms: - in: %appDir%/Forms + - in: %appDir%/Forms - # Filtern nach Dateinamen (string|string[]) + # Filtern nach Dateiname (string|string[]) files: - *Factory.php - # Filtern nach Klassennamen (string|string[]) + # Filtern nach Klassenname (string|string[]) classes: - - *Fabrik + - *Factory ``` -Oder wir können Klassen auswählen, die mindestens eine der folgenden Klassen erben oder implementieren: +Oder wir können Klassen auswählen, die mindestens eine der angegebenen Klassen erben oder implementieren: ```neon search: - myForms: + - in: %appDir% extends: - App\*Form implements: - App\*FormInterface ``` -Sie können auch negative Regeln definieren, z. B. Klassennamensmasken oder Vorfahren, und wenn sie diese erfüllen, wird der Dienst nicht zum DI-Container hinzugefügt: +Es können auch Ausschlussregeln definiert werden, d. h. Masken für Klassennamen oder erbende Vorfahren, bei deren Übereinstimmung der Dienst nicht zum DI-Container hinzugefügt wird: ```neon search: - myForms: + - in: %appDir% exclude: + files: ... classes: ... extends: ... implements: ... ``` -Für hinzugefügte Dienste können Tags gesetzt werden: +Für alle Dienste können Tags gesetzt werden: ```neon search: - myForms: + - in: %appDir% tags: ... ``` -Zusammenführung .[#toc-merging] -=============================== +Zusammenführen +============== -Wenn Elemente mit denselben Schlüsseln in mehreren Konfigurationsdateien vorkommen, werden sie überschrieben oder im Falle von Arrays zusammengeführt. Die später hinzugefügte Datei hat eine höhere Priorität. +Wenn in mehreren Konfigurationsdateien Elemente mit denselben Schlüsseln erscheinen, werden sie überschrieben oder im Falle von Arrays zusammengeführt. Eine später eingebundene Datei hat eine höhere Priorität als die vorherige. <table class=table> <tr> @@ -283,12 +292,12 @@ items: </tr> </table> -Um das Zusammenführen eines bestimmten Arrays zu verhindern, verwenden Sie ein Ausrufezeichen direkt nach dem Namen des Arrays: +Bei Arrays kann das Zusammenführen durch Angabe eines Ausrufezeichens nach dem Schlüsselnamen verhindert werden: <table class=table> <tr> <th width=33%>config1.neon</th> - <th width=33%>Konfig2.neon</th> + <th width=33%>config2.neon</th> <th>Ergebnis</th> </tr> <tr> @@ -313,3 +322,5 @@ items: </td> </tr> </table> + +{{maintitle: Konfiguration der Dependency Injection}} diff --git a/dependency-injection/de/container.texy b/dependency-injection/de/container.texy index 6befd33d9c..ac12397478 100644 --- a/dependency-injection/de/container.texy +++ b/dependency-injection/de/container.texy @@ -1,16 +1,16 @@ -Was ist ein DI-Container? +Was ist ein DI Container? ************************* .[perex] -Dependency Injection Container (DIC) ist eine Klasse, die Objekte instanziieren und konfigurieren kann. +Ein Dependency Injection Container (DIC) ist eine Klasse, die Objekte instanziieren und konfigurieren kann. -Es mag Sie überraschen, aber in vielen Fällen brauchen Sie keinen Dependency Injection Container, um die Vorteile von Dependency Injection (kurz DI) zu nutzen. Schließlich haben wir bereits im [vorigen Kapitel |introduction] konkrete Beispiele für DI gezeigt, für die kein Container erforderlich war. +Es mag Sie überraschen, aber in vielen Fällen benötigen Sie keinen Dependency Injection Container, um die Vorteile der Dependency Injection (kurz DI) zu nutzen. Schon im [Einführungskapitel |introduction] haben wir DI anhand konkreter Beispiele gezeigt, und es wurde kein Container benötigt. -Wenn Sie jedoch eine große Anzahl verschiedener Objekte mit vielen Abhängigkeiten verwalten müssen, ist ein Dependency Injection Container sehr nützlich. Das ist vielleicht der Fall bei Webanwendungen, die auf einem Framework aufbauen. +Wenn Sie jedoch eine große Anzahl verschiedener Objekte mit vielen Abhängigkeiten verwalten müssen, wird ein Dependency Injection Container wirklich nützlich sein. Dies ist beispielsweise bei Webanwendungen der Fall, die auf einem Framework basieren. -Im vorherigen Kapitel haben wir die Klassen `Article` und `UserController` vorgestellt. Beide haben einige Abhängigkeiten, nämlich Datenbank und Factory `ArticleFactory`. Und für diese Klassen werden wir nun einen Container erstellen. Natürlich ist es für ein so einfaches Beispiel nicht sinnvoll, einen Container zu haben. Aber wir werden einen erstellen, um zu zeigen, wie er aussieht und funktioniert. +Im vorherigen Kapitel haben wir die Klassen `Article` und `UserController` vorgestellt. Beide haben einige Abhängigkeiten, nämlich die Datenbank und die Fabrik `ArticleFactory`. Und für diese Klassen werden wir nun einen Container erstellen. Natürlich ist es für ein so einfaches Beispiel nicht sinnvoll, einen Container zu haben. Aber wir werden ihn erstellen, um zu zeigen, wie er aussieht und funktioniert. -Hier ist ein einfacher, hart kodierter Container für das obige Beispiel: +Hier ist ein einfacher hardcodierter Container für das gegebene Beispiel: ```php class Container @@ -32,19 +32,19 @@ class Container } ``` -Die Verwendung würde wie folgt aussehen: +Die Verwendung würde folgendermaßen aussehen: ```php $container = new Container; $controller = $container->createUserController(); ``` -Wir fragen den Container einfach nach dem Objekt und müssen nicht mehr wissen, wie es erstellt wird oder welche Abhängigkeiten es hat; der Container weiß das alles. Der Container weiß das alles. Die Abhängigkeiten werden vom Container automatisch injiziert. Das ist seine Stärke. +Wir fragen den Container nur nach dem Objekt und müssen nichts mehr darüber wissen, wie es erstellt wird oder welche Abhängigkeiten es hat; das alles weiß der Container. Die Abhängigkeiten werden vom Container automatisch injiziert. Darin liegt seine Stärke. -Bis jetzt hat der Container alles hart kodiert. Wir gehen also den nächsten Schritt und fügen Parameter hinzu, um den Container wirklich nützlich zu machen: +Bisher hat der Container alle Daten fest codiert. Wir werden also den nächsten Schritt machen und Parameter hinzufügen, damit der Container wirklich nützlich wird: ```php -Klasse Container +class Container { public function __construct( private array $parameters, @@ -54,7 +54,7 @@ Klasse Container public function createDatabase(): Nette\Database\Connection { return new Nette\Database\Connection( - $this->Parameter['db.dsn'], + $this->parameters['db.dsn'], $this->parameters['db.user'], $this->parameters['db.password'], ); @@ -70,12 +70,12 @@ $container = new Container([ ]); ``` -Aufmerksame Leser haben vielleicht ein Problem bemerkt. Jedes Mal, wenn ich ein Objekt `UserController` erhalte, wird eine neue Instanz `ArticleFactory` und eine neue Datenbank erstellt. Das wollen wir definitiv nicht. +Aufmerksame Leser haben vielleicht ein Problem bemerkt. Jedes Mal, wenn ich ein `UserController`-Objekt erhalte, werden auch eine neue Instanz von `ArticleFactory` und der Datenbank erstellt. Das wollen wir definitiv nicht. -Also fügen wir eine Methode `getService()` hinzu, die immer wieder die gleichen Instanzen zurückgibt: +Wir fügen daher eine Methode `getService()` hinzu, die immer dieselben Instanzen zurückgibt: ```php -Klasse Container +class Container { private array $services = []; @@ -87,7 +87,7 @@ Klasse Container public function getService(string $name): object { if (!isset($this->services[$name])) { - // getService('Datenbank') ruft createDatabase() auf + // getService('Database') ruft createDatabase() auf $method = 'create' . $name; $this->services[$name] = $this->$method(); } @@ -98,12 +98,12 @@ Klasse Container } ``` -Beim ersten Aufruf von z.B. `$container->getService('Database')` wird `createDatabase()` ein Datenbankobjekt erstellen, das im Array `$services` gespeichert und beim nächsten Aufruf direkt zurückgegeben wird. +Beim ersten Aufruf von z. B. `$container->getService('Database')` lässt sie von `createDatabase()` das Datenbankobjekt erstellen, speichert es im Array `$services` und gibt es beim nächsten Aufruf direkt zurück. -Wir ändern auch den Rest des Containers, um `getService()` zu verwenden: +Wir passen auch den Rest des Containers an, um `getService()` zu verwenden: ```php -Klasse Container +class Container { // ... @@ -119,9 +119,9 @@ Klasse Container } ``` -Der Begriff Dienst bezieht sich übrigens auf jedes vom Container verwaltete Objekt. Daher auch der Name der Methode `getService()`. +Übrigens wird der Begriff Dienst (Service) für jedes Objekt verwendet, das vom Container verwaltet wird. Daher auch der Name der Methode `getService()`. -Das war's. Wir haben einen voll funktionsfähigen DI-Container! Und wir können ihn benutzen: +Fertig. Wir haben einen voll funktionsfähigen DI-Container! Und wir können ihn verwenden: ```php $container = new Container([ @@ -134,6 +134,9 @@ $controller = $container->getService('UserController'); $database = $container->getService('Database'); ``` -Wie Sie sehen können, ist es nicht schwer, ein DIC zu schreiben. Bemerkenswert ist, dass die Objekte selbst nicht wissen, dass sie von einem Container erstellt werden. Es ist also möglich, jedes beliebige Objekt in PHP auf diese Weise zu erstellen, ohne den Quellcode zu verändern. +Wie Sie sehen, ist es nicht kompliziert, einen DIC zu schreiben. Es sei daran erinnert, dass die Objekte selbst nicht wissen, dass sie von einem Container erstellt werden. Daher ist es möglich, jedes Objekt in PHP auf diese Weise zu erstellen, ohne seinen Quellcode zu ändern. + +Das manuelle Erstellen und Warten einer Containerklasse kann schnell zu einem Albtraum werden. Im nächsten Kapitel werden wir daher über den [Nette DI Container|nette-container] sprechen, der sich fast von selbst generieren und aktualisieren kann. + -Die manuelle Erstellung und Pflege einer Containerklasse kann schnell zu einem Alptraum werden. Deshalb werden wir im nächsten Kapitel über [Nette DI Container |nette-container] sprechen, die sich fast automatisch erzeugen und aktualisieren können. +{{maintitle: Was ist ein Dependency Injection Container?}} diff --git a/dependency-injection/de/extensions.texy b/dependency-injection/de/extensions.texy index 9761299aa9..62184d5d59 100644 --- a/dependency-injection/de/extensions.texy +++ b/dependency-injection/de/extensions.texy @@ -1,20 +1,20 @@ -Erweiterungen für Nette DI erstellen -************************************ +Erstellen von Erweiterungen für Nette DI +**************************************** .[perex] -Das Erzeugen eines DI-Containers betrifft neben den Konfigurationsdateien auch die sogenannten *Extensions*. Diese aktivieren wir in der Konfigurationsdatei im Abschnitt `extensions`. +Die Generierung des DI-Containers wird neben den Konfigurationsdateien auch durch sogenannte *Erweiterungen* beeinflusst. Wir aktivieren sie in der Konfigurationsdatei im Abschnitt `extensions`. -Auf diese Weise fügen wir die Erweiterung hinzu, die durch die Klasse `BlogExtension` mit dem Namen `blog` repräsentiert wird: +So fügen wir eine Erweiterung hinzu, die durch die Klasse `BlogExtension` repräsentiert wird, unter dem Namen `blog`: ```neon extensions: blog: BlogExtension ``` -Jede Compiler-Erweiterung erbt von [api:Nette\DI\CompilerExtension] und kann folgende Methoden implementieren, die während der DI-Kompilierung aufgerufen werden: +Jede Compiler-Erweiterung erbt von [api:Nette\DI\CompilerExtension] und kann die folgenden Methoden implementieren, die während der Erstellung des DI-Containers nacheinander aufgerufen werden: 1. getConfigSchema() -2. loadKonfiguration() +2. loadConfiguration() 3. beforeCompile() 4. afterCompile() @@ -22,18 +22,18 @@ Jede Compiler-Erweiterung erbt von [api:Nette\DI\CompilerExtension] und kann fol getConfigSchema() .[method] =========================== -Diese Methode wird zuerst aufgerufen. Sie definiert das Schema, das zur Validierung der Konfigurationsparameter verwendet wird. +Diese Methode wird zuerst aufgerufen. Sie definiert das Schema zur Validierung der Konfigurationsparameter. -Erweiterungen werden in einem Abschnitt konfiguriert, der denselben Namen trägt wie der Abschnitt, unter dem die Erweiterung hinzugefügt wurde, z. B. `blog`. +Wir konfigurieren die Erweiterung im Abschnitt, dessen Name mit dem Namen übereinstimmt, unter dem die Erweiterung hinzugefügt wurde, also `blog`: ```neon -# gleicher Name wie meine Nebenstelle +# gleicher Name wie die Extension blog: postsPerPage: 10 - comments: false + allowComments: false ``` -Wir werden ein Schema definieren, das alle Konfigurationsoptionen beschreibt, einschließlich ihrer Typen, akzeptierten Werte und möglicherweise Standardwerte: +Wir erstellen ein Schema, das alle Konfigurationsoptionen beschreibt, einschließlich ihrer Typen, erlaubten Werte und gegebenenfalls auch Standardwerte: ```php use Nette\Schema\Expect; @@ -50,9 +50,9 @@ class BlogExtension extends Nette\DI\CompilerExtension } ``` -Siehe das [Schema |schema:] für die Dokumentation. Zusätzlich können Sie mit `dynamic()` angeben, welche Optionen [dynamisch |application:bootstrap#Dynamic Parameters] sein können, zum Beispiel `Expect::int()->dynamic()`. +Die Dokumentation finden Sie auf der Seite [Schema |schema:]. Zusätzlich kann festgelegt werden, welche Optionen [dynamisch |application:bootstrapping#Dynamische Parameter] sein können, mittels `dynamic()`, z.B. `Expect::int()->dynamic()`. -Wir greifen auf die Konfiguration über `$this->config` zu, das ein Objekt `stdClass` ist: +Auf die Konfiguration greifen wir über die Variable `$this->config` zu, die ein `stdClass`-Objekt ist: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -71,7 +71,7 @@ class BlogExtension extends Nette\DI\CompilerExtension loadConfiguration() .[method] ============================= -Diese Methode wird verwendet, um dem Container Dienste hinzuzufügen. Dies geschieht durch [api:Nette\DI\ContainerBuilder]: +Wird verwendet, um Dienste zum Container hinzuzufügen. Dazu dient [api:Nette\DI\ContainerBuilder]: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -86,19 +86,19 @@ class BlogExtension extends Nette\DI\CompilerExtension } ``` -Die Konvention ist, den von einer Erweiterung hinzugefügten Diensten ihren Namen voranzustellen, damit keine Namenskonflikte entstehen. Dies geschieht durch `prefix()`. Wenn also die Erweiterung "blog" heißt, wird der Dienst `blog.articles` genannt. +Die Konvention ist, Dienste, die durch eine Erweiterung hinzugefügt werden, mit deren Namen zu präfixieren, um Namenskonflikte zu vermeiden. Dies macht die Methode `prefix()`, sodass, wenn die Erweiterung `blog` heißt, der Dienst den Namen `blog.articles` trägt. -Wenn wir einen Dienst umbenennen müssen, können wir einen Alias mit seinem ursprünglichen Namen erstellen, um die Abwärtskompatibilität zu wahren. Ähnlich verfährt Nette z. B. mit `routing.router`, das auch unter dem früheren Namen `router` verfügbar ist. +Wenn wir einen Dienst umbenennen müssen, können wir aus Gründen der Abwärtskompatibilität einen Alias mit dem ursprünglichen Namen erstellen. Ähnlich macht es Nette z. B. beim Dienst `routing.router`, der auch unter dem früheren Namen `router` verfügbar ist. ```php $builder->addAlias('router', 'routing.router'); ``` -Abrufen von Diensten aus einer Datei .[#toc-retrieve-services-from-a-file] --------------------------------------------------------------------------- +Laden von Diensten aus einer Datei +---------------------------------- -Wir können Dienste mit Hilfe der ContainerBuilder-API erstellen, aber auch über die bekannte NEON-Konfigurationsdatei und ihren Abschnitt `services` hinzufügen. Das Präfix `@extension` steht für die aktuelle Erweiterung. +Dienste müssen nicht nur über die API der ContainerBuilder-Klasse erstellt werden, sondern auch mit der bekannten Schreibweise, die in der NEON-Konfigurationsdatei im Abschnitt `services` verwendet wird. Das Präfix `@extension` repräsentiert die aktuelle Extension. ```neon services: @@ -112,7 +112,7 @@ services: create: MyBlog\Components\ArticlesList(@extension.articles) ``` -Wir werden auf diese Weise Dienste hinzufügen: +Wir laden die Dienste: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -133,7 +133,7 @@ class BlogExtension extends Nette\DI\CompilerExtension beforeCompile() .[method] ========================= -Die Methode wird aufgerufen, wenn der Container alle Dienste enthält, die von den einzelnen Erweiterungen in den Methoden von `loadConfiguration` hinzugefügt wurden, sowie die Konfigurationsdateien der Benutzer. In dieser Phase der Zusammenstellung können wir dann Dienstdefinitionen ändern oder Verknüpfungen zwischen ihnen hinzufügen. Sie können die Methode `findByTag()` verwenden, um Dienste nach Tags zu suchen, oder die Methode `findByType()`, um nach Klassen oder Schnittstellen zu suchen. +Die Methode wird aufgerufen, wenn der Container alle Dienste enthält, die von den einzelnen Erweiterungen in den `loadConfiguration`-Methoden sowie durch die Benutzer-Konfigurationsdateien hinzugefügt wurden. In dieser Phase der Erstellung können wir also die Dienstdefinitionen ändern oder Abhängigkeiten zwischen ihnen hinzufügen. Zum Suchen von Diensten im Container nach Tags kann die Methode `findByTag()` verwendet werden, nach Klasse oder Schnittstelle die Methode `findByType()`. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -153,7 +153,7 @@ class BlogExtension extends Nette\DI\CompilerExtension afterCompile() .[method] ======================== -In dieser Phase ist die Containerklasse bereits als [ClassType-Objekt |php-generator:#classes] generiert, sie enthält alle Methoden, die der Dienst erstellt, und ist bereit für die Zwischenspeicherung als PHP-Datei. Wir können den resultierenden Klassencode zu diesem Zeitpunkt noch bearbeiten. +In dieser Phase ist die Containerklasse bereits in Form eines [ClassType |php-generator:#Klassen]-Objekts generiert, enthält alle Methoden, die Dienste erstellen, und ist bereit, in den Cache geschrieben zu werden. Der resultierende Klassencode kann zu diesem Zeitpunkt noch geändert werden. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -167,24 +167,24 @@ class BlogExtension extends Nette\DI\CompilerExtension ``` -$Initialisierung .[wiki-method] -=============================== +$initialization .[method] +========================= -Der Configurator ruft den Initialisierungscode nach der [Erstellung des Containers |application:bootstrap#index.php] auf, der durch Schreiben in ein Objekt `$this->initialization` mit der [Methode addBody() |php-generator:#method-and-function-body] erzeugt wird. +Die Configurator-Klasse ruft nach der [Erstellung des Containers |application:bootstrapping#index.php] Initialisierungscode auf, der durch Schreiben in das `$this->initialization`-Objekt mittels der [Methode addBody() |php-generator:#Körper von Methoden und Funktionen] erstellt wird. -Wir werden ein Beispiel zeigen, wie man eine Session oder Dienste mit dem Tag `run` unter Verwendung des Initialisierungscodes startet: +Wir zeigen ein Beispiel, wie man z. B. mit Initialisierungscode die Session startet oder Dienste startet, die das Tag `run` haben: ```php class BlogExtension extends Nette\DI\CompilerExtension { public function loadConfiguration() { - // automatischer Start der Session + // automatisches Starten der Session if ($this->config->session->autoStart) { $this->initialization->addBody('$this->getService("session")->start()'); } - // Dienste mit dem Tag "run" müssen nach der Instanziierung des Containers erstellt werden + // Dienste mit dem Tag run müssen nach der Instanziierung des Containers erstellt werden $builder = $this->getContainerBuilder(); foreach ($builder->findByTag('run') as $name => $foo) { $this->initialization->addBody('$this->getService(?);', [$name]); diff --git a/dependency-injection/de/factory.texy b/dependency-injection/de/factory.texy index e0381a449d..9d7eafb558 100644 --- a/dependency-injection/de/factory.texy +++ b/dependency-injection/de/factory.texy @@ -2,11 +2,11 @@ Generierte Fabriken ******************* .[perex] -Nette DI kann automatisch Fabrikcode basierend auf der Schnittstelle generieren, was Ihnen das Schreiben von Code erspart. +Nette DI kann automatisch Code für Fabriken basierend auf Schnittstellen generieren, was Ihnen das Schreiben von Code erspart. -Eine Fabrik ist eine Klasse, die Objekte erstellt und konfiguriert. Sie gibt daher auch deren Abhängigkeiten an sie weiter. Bitte nicht mit dem Entwurfsmuster *Fabrikmethode* verwechseln, das eine spezielle Art der Verwendung von Fabriken beschreibt und nicht mit diesem Thema zusammenhängt. +Eine Fabrik ist eine Klasse, die Objekte herstellt und konfiguriert. Sie übergibt ihnen also auch ihre Abhängigkeiten. Bitte verwechseln Sie dies nicht mit dem Entwurfsmuster *Factory Method*, das eine spezifische Art der Verwendung von Fabriken beschreibt und mit diesem Thema nichts zu tun hat. -Wir haben im [Einführungskapitel |introduction#factory] gezeigt, wie eine solche Fabrik aussieht: +Wie eine solche Fabrik aussieht, haben wir im [Einführungskapitel |introduction#Fabrik] gezeigt: ```php class ArticleFactory @@ -23,7 +23,7 @@ class ArticleFactory } ``` -Nette DI kann automatisch Factory-Code generieren. Alles, was Sie tun müssen, ist, eine Schnittstelle zu erstellen, und Nette DI wird eine Implementierung generieren. Die Schnittstelle muss genau eine Methode namens `create` haben und einen Rückgabetyp deklarieren: +Nette DI kann den Code von Fabriken automatisch generieren. Alles, was Sie tun müssen, ist, eine Schnittstelle zu erstellen, und Nette DI generiert die Implementierung. Die Schnittstelle muss genau eine Methode namens `create` haben und einen Rückgabetyp deklarieren: ```php interface ArticleFactory @@ -32,7 +32,7 @@ interface ArticleFactory } ``` -Die Fabrik `ArticleFactory` hat also eine Methode `create`, die Objekte `Article` erzeugt. Die Klasse `Article` könnte z.B. wie folgt aussehen: +Die Fabrik `ArticleFactory` hat also eine Methode `create`, die `Article`-Objekte erstellt. Die Klasse `Article` könnte beispielsweise so aussehen: ```php class Article @@ -44,16 +44,16 @@ class Article } ``` -Fügen Sie die Fabrik in die Konfigurationsdatei ein: +Wir fügen die Fabrik zur Konfigurationsdatei hinzu: ```neon services: - ArticleFactory ``` -Nette DI wird die entsprechende Fabrikimplementierung erzeugen. +Nette DI generiert die entsprechende Implementierung der Fabrik. -Im Code, der die Fabrik verwendet, fordern wir das Objekt also über die Schnittstelle an, und Nette DI verwendet die generierte Implementierung: +Im Code, der die Fabrik verwendet, fordern wir das Objekt über die Schnittstelle an, und Nette DI verwendet die generierte Implementierung: ```php class UserController @@ -65,17 +65,17 @@ class UserController public function foo() { - // Die Fabrik soll ein Objekt erstellen + // lassen wir die Fabrik das Objekt erstellen $article = $this->articleFactory->create(); } } ``` -Parametrisierte Fabrik .[#toc-parameterized-factory] -==================================================== +Parametrisierte Fabrik +====================== -Die Factory-Methode `create` kann Parameter akzeptieren, die sie dann an den Konstruktor weitergibt. Fügen wir zum Beispiel der Klasse `Article` eine Artikelautor-ID hinzu: +Die Fabrikmethode `create` kann Parameter annehmen, die sie dann an den Konstruktor weitergibt. Ergänzen wir beispielsweise die Klasse `Article` um die ID des Artikelautors: ```php class Article @@ -88,7 +88,7 @@ class Article } ``` -Wir fügen auch den Parameter zur Fabrik hinzu: +Wir fügen den Parameter auch zur Fabrik hinzu: ```php interface ArticleFactory @@ -97,13 +97,13 @@ interface ArticleFactory } ``` -Da der Parameter im Konstruktor und der Parameter in der Fabrik den gleichen Namen haben, werden sie von Nette DI automatisch übergeben. +Da der Parameter im Konstruktor und der Parameter in der Fabrik denselben Namen haben, übergibt Nette DI sie vollautomatisch. -Erweiterte Definition .[#toc-advanced-definition] -================================================= +Erweiterte Definition +===================== -Die Definition kann auch in mehrzeiliger Form mit der Taste `implement` geschrieben werden: +Die Definition kann auch in mehrzeiliger Form unter Verwendung des Schlüssels `implement` geschrieben werden: ```neon services: @@ -111,9 +111,9 @@ services: implement: ArticleFactory ``` -Beim Schreiben in dieser längeren Form ist es möglich, zusätzliche Argumente für den Konstruktor im Schlüssel `arguments` und zusätzliche Konfigurationen mit `setup` anzugeben, genau wie bei normalen Diensten. +Bei dieser längeren Schreibweise können zusätzliche Argumente für den Konstruktor im Schlüssel `arguments` und zusätzliche Konfigurationen mittels `setup` angegeben werden, genau wie bei regulären Diensten. -Beispiel: Wenn die Methode `create()` den Parameter `$authorId` nicht akzeptiert, könnte man in der Konfiguration einen festen Wert angeben, der an den Konstruktor `Article` übergeben wird: +Beispiel: Wenn die Methode `create()` den Parameter `$authorId` nicht akzeptieren würde, könnten wir einen festen Wert in der Konfiguration angeben, der an den Konstruktor von `Article` übergeben würde: ```neon services: @@ -123,7 +123,7 @@ services: authorId: 123 ``` -Oder umgekehrt, wenn `create()` den Parameter `$authorId` akzeptiert, dieser aber nicht Teil des Konstruktors ist, sondern von der Methode `Article::setAuthorId()` übergeben wird, würden wir in Abschnitt `setup` auf ihn verweisen: +Oder umgekehrt, wenn `create()` den Parameter `$authorId` akzeptieren würde, aber er nicht Teil des Konstruktors wäre und über die Methode `Article::setAuthorId()` übergeben würde, würden wir im Abschnitt `setup` darauf verweisen: ```neon services: @@ -134,15 +134,14 @@ services: ``` -Accessor .[#toc-accessor] -========================= +Accessor +======== -Neben Fabriken kann Nette auch so genannte Accessoren erzeugen. Ein Accessor ist ein Objekt mit der Methode `get()`, die einen bestimmten Dienst aus dem DI-Container zurückgibt. Mehrere `get()` Aufrufe geben immer dieselbe Instanz zurück. +Nette kann neben Fabriken auch sogenannte Accessoren generieren. Dies sind Objekte mit einer `get()`-Methode, die einen bestimmten Dienst aus dem DI-Container zurückgibt. Wiederholte Aufrufe von `get()` geben immer dieselbe Instanz zurück. -Accessoren bringen Lazy-Loading in Abhängigkeiten. Nehmen wir an, eine Klasse protokolliert Fehler in einer speziellen Datenbank. Wenn die Datenbankverbindung als Abhängigkeit in ihrem Konstruktor übergeben würde, müsste die Verbindung immer erstellt werden, obwohl sie nur selten verwendet würde, wenn ein Fehler auftritt, so dass die Verbindung meist ungenutzt bliebe. -Stattdessen kann die Klasse einen Accessor übergeben, und wenn ihre Methode `get()` aufgerufen wird, wird erst dann das Datenbankobjekt erstellt: +Accessoren ermöglichen Lazy-Loading für Abhängigkeiten. Nehmen wir an, wir haben eine Klasse, die Fehler in eine spezielle Datenbank schreibt. Wenn diese Klasse die Datenbankverbindung als Abhängigkeit im Konstruktor übergeben bekäme, müsste die Verbindung immer erstellt werden, obwohl in der Praxis ein Fehler nur selten auftritt und die Verbindung daher meist ungenutzt bliebe. Stattdessen übergibt sich die Klasse einen Accessor, und erst wenn dessen `get()` aufgerufen wird, wird das Datenbankobjekt erstellt: -Wie erstellt man einen Accessor? Schreiben Sie nur eine Schnittstelle und Nette DI wird die Implementierung generieren. Die Schnittstelle muss genau eine Methode namens `get` haben und den Rückgabetyp deklarieren: +Wie erstellt man einen Accessor? Schreiben Sie einfach eine Schnittstelle, und Nette DI generiert die Implementierung. Die Schnittstelle muss genau eine Methode namens `get` haben und einen Rückgabetyp deklarieren: ```php interface PDOAccessor @@ -151,7 +150,7 @@ interface PDOAccessor } ``` -Fügen Sie den Accessor in die Konfigurationsdatei ein, zusammen mit der Definition des Dienstes, den der Accessor zurückgibt: +Wir fügen den Accessor zur Konfigurationsdatei hinzu, wo auch die Definition des Dienstes steht, den er zurückgeben wird: ```neon services: @@ -159,62 +158,68 @@ services: - PDO(%dsn%, %user%, %password%) ``` -Der Accessor gibt einen Dienst des Typs `PDO` zurück, und da es nur einen solchen Dienst in der Konfiguration gibt, gibt der Accessor ihn zurück. Bei mehreren konfigurierten Diensten dieses Typs können Sie angeben, welcher Dienst zurückgegeben werden soll, indem Sie seinen Namen verwenden, zum Beispiel `- PDOAccessor(@db1)`. +Da der Accessor einen Dienst vom Typ `PDO` zurückgibt und in der Konfiguration nur ein solcher Dienst vorhanden ist, wird er genau diesen zurückgeben. Wenn es mehrere Dienste dieses Typs gäbe, würden wir den zurückgegebenen Dienst anhand seines Namens bestimmen, z. B. `- PDOAccessor(@db1)`. -Multifactory/Accessor .[#toc-multifactory-accessor] -=================================================== -Bisher konnten die Fabriken und Accessoren nur ein einziges Objekt erstellen oder zurückgeben. Es kann auch eine Multifactory in Kombination mit einem Accessor erstellt werden. Die Schnittstelle einer solchen Multifactory-Klasse kann aus mehreren Methoden bestehen, die `create<name>()` und `get<name>()`bestehen, zum Beispiel: +Mehrfachfabrik/-accessor +======================== +Unsere Fabriken und Accessoren konnten bisher immer nur ein Objekt herstellen oder zurückgeben. Es ist jedoch sehr einfach, auch Mehrfachfabriken in Kombination mit Accessoren zu erstellen. Die Schnittstelle einer solchen Klasse enthält eine beliebige Anzahl von Methoden mit den Namen `create<name>()` und `get<name>()`, z. B.: ```php interface MultiFactory { function createArticle(): Article; - function createFoo(): Model\Foo; function getDb(): PDO; } ``` -Anstatt mehrere generierte Fabriken und Accessoren zu übergeben, können Sie nur eine komplexe Multifabrik übergeben. +Anstatt also mehrere generierte Fabriken und Accessoren zu übergeben, übergeben wir eine komplexere Fabrik, die mehr kann. -Alternativ können Sie auch `create()` und `get()` mit einem Parameter anstelle mehrerer Methoden verwenden: +Alternativ kann anstelle mehrerer Methoden `get()` mit einem Parameter verwendet werden: ```php interface MultiFactoryAlt { - function create($name); function get($name): PDO; } ``` -In diesem Fall bewirkt `MultiFactory::createArticle()` das Gleiche wie `MultiFactoryAlt::create('article')`. Die alternative Syntax hat jedoch einige Nachteile. Es ist nicht klar, welche `$name` Werte unterstützt werden und der Rückgabetyp kann nicht in der Schnittstelle angegeben werden, wenn mehrere verschiedene `$name` Werte verwendet werden. +Dann gilt, dass `MultiFactory::getArticle()` dasselbe tut wie `MultiFactoryAlt::get('article')`. Die alternative Schreibweise hat jedoch den Nachteil, dass nicht ersichtlich ist, welche Werte für `$name` unterstützt werden, und logischerweise können in der Schnittstelle auch keine unterschiedlichen Rückgabewerte für verschiedene `$name` unterschieden werden. -Definition mit einer Liste .[#toc-definition-with-a-list] ---------------------------------------------------------- -Wie definieren Sie eine Multifabrik in Ihrer Konfiguration? Lassen Sie uns drei Dienste erstellen, die von der Multifabrik zurückgegeben werden, und die Multifabrik selbst: +Definition durch Liste +---------------------- +Auf diese Weise kann eine Mehrfachfabrik in der Konfiguration definiert werden: .{data-version:3.2.0} + +```neon +services: + - MultiFactory( + article: Article # definiert createArticle() + db: PDO(%dsn%, %user%, %password%) # definiert getDb() + ) +``` + +Oder wir können uns in der Fabrikdefinition mittels Referenz auf bestehende Dienste beziehen: ```neon services: article: Article - - Model\Foo - PDO(%dsn%, %user%, %password%) - MultiFactory( - article: @article # createArticle() - foo: @Model\Foo # createFoo() - db: @\PDO # getDb() + article: @article # definiert createArticle() + db: @\PDO # definiert getDb() ) ``` -Definition mit Tags .[#toc-definition-with-tags] ------------------------------------------------- +Definition mittels Tags +----------------------- -Eine weitere Möglichkeit, eine Multifabrik zu definieren, ist die Verwendung von [Tags |services#Tags]: +Die zweite Möglichkeit ist, zur Definition [Tags |services#Tags] zu verwenden: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter - App\Model\DatabaseAccessor( db1: @database.db1.explorer ) diff --git a/dependency-injection/de/faq.texy b/dependency-injection/de/faq.texy index 0a4279f61e..cc2f8fd98d 100644 --- a/dependency-injection/de/faq.texy +++ b/dependency-injection/de/faq.texy @@ -2,97 +2,91 @@ Häufig gestellte Fragen zu DI (FAQ) *********************************** -Ist DI ein anderer Name für IoC? .[#toc-is-di-another-name-for-ioc] -------------------------------------------------------------------- +Ist DI ein anderer Name für IoC? +-------------------------------- -Die *Inversion of Control* (IoC) ist ein Prinzip, das sich auf die Art und Weise konzentriert, wie Code ausgeführt wird - ob Ihr Code externen Code initiiert oder Ihr Code in externen Code integriert ist, der ihn dann aufruft. -IoC ist ein umfassendes Konzept, das [Ereignisse |nette:glossary#Events], das so genannte [Hollywood-Prinzip |application:components#Hollywood style] und andere Aspekte einschließt. -Fabriken, die Teil von [Regel Nr. 3: Let the Factory Handle It |introduction#Rule #3: Let the Factory Handle It] sind und eine Umkehrung des `new` -Operators darstellen, sind ebenfalls Bestandteil dieses Konzepts. +*Inversion of Control* (IoC) ist ein Prinzip, das sich darauf konzentriert, wie Code ausgeführt wird – ob Ihr Code fremden Code ausführt oder ob Ihr Code in fremden Code integriert ist, der ihn anschließend aufruft. IoC ist ein weit gefasster Begriff, der [Ereignisse |nette:glossary#Events Ereignisse], das sogenannte [Hollywood-Prinzip |application:components#Hollywood Style] und andere Aspekte umfasst. Teil dieses Konzepts sind auch Fabriken, über die [Regel Nr. 3: Überlasse es der Fabrik |introduction#Regel Nr. 3: Überlasse es der Fabrik] spricht und die eine Inversion für den `new`-Operator darstellen. -Bei der *Dependency Injection* (DI) geht es darum, wie ein Objekt über ein anderes Objekt Bescheid weiß, d.h. um die Abhängigkeit. Es handelt sich um ein Entwurfsmuster, das die explizite Weitergabe von Abhängigkeiten zwischen Objekten erfordert. +*Dependency Injection* (DI) konzentriert sich darauf, wie ein Objekt von einem anderen Objekt, also von seinen Abhängigkeiten, erfährt. Es handelt sich um ein Entwurfsmuster, das die explizite Übergabe von Abhängigkeiten zwischen Objekten erfordert. -Somit kann DI als eine spezielle Form von IoC bezeichnet werden. Allerdings sind nicht alle Formen von IoC im Hinblick auf die Reinheit des Codes geeignet. Zu den Anti-Patterns gehören zum Beispiel alle Techniken, die mit einem [globalen Zustand |global state] oder dem sogenannten [Service Locator |#What is a Service Locator] arbeiten. +Man kann also sagen, dass DI eine spezifische Form von IoC ist. Allerdings sind nicht alle Formen von IoC im Hinblick auf die Code-Reinheit geeignet. Zu den Anti-Patterns gehören beispielsweise Techniken, die mit [globalem Zustand |global-state] arbeiten oder der sogenannte [Service Locator |#Was ist ein Service Locator]. -Was ist ein Service Locator? .[#toc-what-is-a-service-locator] --------------------------------------------------------------- +Was ist ein Service Locator? +---------------------------- -Ein Service Locator ist eine Alternative zur Dependency Injection. Dabei wird ein zentraler Speicher angelegt, in dem alle verfügbaren Dienste oder Abhängigkeiten registriert werden. Wenn ein Objekt eine Abhängigkeit benötigt, fordert es diese beim Service Locator an. +Es handelt sich um eine Alternative zur Dependency Injection. Er funktioniert so, dass ein zentraler Speicher erstellt wird, in dem alle verfügbaren Dienste oder Abhängigkeiten registriert sind. Wenn ein Objekt eine Abhängigkeit benötigt, fordert es diese vom Service Locator an. -Im Vergleich zu Dependency Injection verliert es jedoch an Transparenz: Abhängigkeiten werden nicht direkt an Objekte weitergegeben und sind daher nicht leicht zu erkennen, was eine Untersuchung des Codes erfordert, um alle Verbindungen aufzudecken und zu verstehen. Auch das Testen ist komplizierter, da wir nicht einfach Mock-Objekte an die getesteten Objekte übergeben können, sondern den Weg über den Service Locator gehen müssen. Außerdem stört der Service Locator das Design des Codes, da die einzelnen Objekte von seiner Existenz wissen müssen, was sich von der Dependency Injection unterscheidet, bei der die Objekte keine Kenntnis vom DI-Container haben. +Im Vergleich zur Dependency Injection geht jedoch die Transparenz verloren: Abhängigkeiten werden den Objekten nicht direkt übergeben und sind daher nicht leicht zu identifizieren, was eine Untersuchung des Codes erfordert, um alle Verknüpfungen aufzudecken und zu verstehen. Das Testen ist ebenfalls komplizierter, da wir Mock-Objekte nicht einfach an die zu testenden Objekte übergeben können, sondern über den Service Locator gehen müssen. Darüber hinaus stört der Service Locator das Code-Design, da einzelne Objekte von seiner Existenz wissen müssen, was sich von der Dependency Injection unterscheidet, bei der Objekte keine Kenntnis vom DI-Container haben. -Wann ist es besser, DI nicht zu verwenden? .[#toc-when-is-it-better-not-to-use-di] ----------------------------------------------------------------------------------- +Wann ist es besser, DI nicht zu verwenden? +------------------------------------------ -Es sind keine Schwierigkeiten bekannt, die mit der Verwendung des Dependency Injection Design Pattern verbunden sind. Im Gegenteil, die Beschaffung von Abhängigkeiten von global zugänglichen Stellen führt zu einer [Reihe von Komplikationen |global-state], ebenso wie die Verwendung eines Service Locators. -Daher ist es ratsam, immer DI zu verwenden. Dies ist kein dogmatischer Ansatz, aber es wurde einfach keine bessere Alternative gefunden. +Es sind keine Schwierigkeiten im Zusammenhang mit der Verwendung des Dependency Injection-Entwurfsmusters bekannt. Im Gegenteil, das Abrufen von Abhängigkeiten von global verfügbaren Orten führt zu [einer ganzen Reihe von Komplikationen |global-state], ebenso wie die Verwendung des Service Locators. Daher ist es ratsam, DI immer zu verwenden. Dies ist kein dogmatischer Ansatz, sondern es wurde einfach keine bessere Alternative gefunden. -Es gibt jedoch bestimmte Situationen, in denen wir Objekte nicht aneinander übergeben und sie aus dem globalen Raum beziehen. Zum Beispiel beim Debuggen von Code, wenn man einen Variablenwert an einem bestimmten Punkt im Programm ausgeben, die Dauer eines bestimmten Teils des Programms messen oder eine Meldung protokollieren muss. -In solchen Fällen, in denen es sich um temporäre Aktionen handelt, die später aus dem Code entfernt werden, ist es legitim, einen global zugänglichen Dumper, eine Stoppuhr oder einen Logger zu verwenden. Diese Werkzeuge gehören schließlich nicht zum Design des Codes. +Dennoch gibt es bestimmte Situationen, in denen wir Objekte nicht übergeben und sie aus dem globalen Raum beziehen. Zum Beispiel beim Debuggen von Code, wenn Sie an einem bestimmten Punkt im Programm den Wert einer Variablen ausgeben, die Dauer eines bestimmten Programmteils messen oder eine Nachricht protokollieren müssen. In solchen Fällen, in denen es sich um temporäre Aufgaben handelt, die später aus dem Code entfernt werden, ist es legitim, einen global verfügbaren Dumper, eine Stoppuhr oder einen Logger zu verwenden. Diese Werkzeuge gehören nämlich nicht zum Code-Design. -Hat die Verwendung von DI auch Nachteile? .[#toc-does-using-di-have-its-drawbacks] ----------------------------------------------------------------------------------- +Hat die Verwendung von DI Nachteile? +------------------------------------ -Bringt die Verwendung von Dependency Injection irgendwelche Nachteile mit sich, wie z. B. eine höhere Komplexität beim Schreiben von Code oder eine schlechtere Leistung? Was verlieren wir, wenn wir anfangen, Code in Übereinstimmung mit DI zu schreiben? +Bringt die Verwendung von Dependency Injection Nachteile mit sich, wie z. B. erhöhten Schreibaufwand oder Leistungseinbußen? Was verlieren wir, wenn wir anfangen, Code gemäß DI zu schreiben? -DI hat keinen Einfluss auf die Anwendungsleistung oder den Speicherbedarf. Die Leistung des DI-Containers kann eine Rolle spielen, aber im Fall von [Nette DI | nette-container] wird der Container in reines PHP kompiliert, so dass sein Overhead während der Laufzeit der Anwendung im Wesentlichen gleich Null ist. +DI hat keinen Einfluss auf die Leistung oder den Speicherbedarf der Anwendung. Die Leistung des DI-Containers kann eine gewisse Rolle spielen, aber im Falle des [Nette DI |nette-container] wird der Container zu reinem PHP kompiliert, sodass sein Overhead während der Laufzeit der Anwendung praktisch null ist. -Beim Schreiben von Code ist es notwendig, Konstruktoren zu erstellen, die Abhängigkeiten akzeptieren. In der Vergangenheit konnte dies zeitaufwändig sein, aber dank moderner IDEs und der [Förderung von Konstruktoreigenschaften |https://blog.nette.org/de/php-8-0-vollstaendiger-ueberblick-ueber-die-neuigkeiten#toc-constructor-property-promotion] ist dies nun eine Sache von wenigen Sekunden. Factories lassen sich mit Nette DI und einem PhpStorm-Plugin mit wenigen Klicks erzeugen. -Andererseits besteht keine Notwendigkeit, Singletons und statische Zugriffspunkte zu schreiben. +Beim Schreiben von Code ist es oft notwendig, Konstruktoren zu erstellen, die Abhängigkeiten akzeptieren. Früher konnte dies mühsam sein, aber dank moderner IDEs und [constructor property promotion |https://blog.nette.org/de/php-8-0-complete-overview-of-news#toc-constructor-property-promotion] ist dies jetzt eine Frage von Sekunden. Fabriken können mit Nette DI und einem Plugin für PhpStorm einfach per Mausklick generiert werden. Andererseits entfällt die Notwendigkeit, Singletons und statische Zugriffspunkte zu schreiben. -Daraus lässt sich schließen, dass eine richtig konzipierte Anwendung mit DI im Vergleich zu einer Anwendung mit Singletons weder kürzer noch länger ist. Teile des Codes, die mit Abhängigkeiten arbeiten, werden einfach aus den einzelnen Klassen extrahiert und an neue Stellen verschoben, d.h. in den DI-Container und die Fabriken. +Man kann feststellen, dass eine korrekt entworfene Anwendung, die DI verwendet, im Vergleich zu einer Anwendung, die Singletons verwendet, weder kürzer noch länger ist. Teile des Codes, die mit Abhängigkeiten arbeiten, werden lediglich aus den einzelnen Klassen extrahiert und an neue Orte verschoben, d. h. in den DI-Container und die Fabriken. -Wie schreibt man eine Legacy-Anwendung auf DI um? .[#toc-how-to-rewrite-a-legacy-application-to-di] ---------------------------------------------------------------------------------------------------- +Wie schreibt man eine Legacy-Anwendung auf DI um? +------------------------------------------------- -Die Umstellung einer Legacy-Anwendung auf Dependency Injection kann ein schwieriger Prozess sein, insbesondere bei großen und komplexen Anwendungen. Es ist wichtig, diesen Prozess systematisch anzugehen. +Der Übergang von einer Legacy-Anwendung zur Dependency Injection kann ein anspruchsvoller Prozess sein, insbesondere bei großen und komplexen Anwendungen. Es ist wichtig, diesen Prozess systematisch anzugehen. -- Bei der Umstellung auf Dependency Injection ist es wichtig, dass alle Teammitglieder die Prinzipien und Praktiken verstehen, die verwendet werden. -- Führen Sie zunächst eine Analyse der bestehenden Anwendung durch, um die wichtigsten Komponenten und ihre Abhängigkeiten zu ermitteln. Erstellen Sie einen Plan, welche Teile in welcher Reihenfolge refaktorisiert werden sollen. -- Implementieren Sie einen DI-Container oder, noch besser, verwenden Sie eine vorhandene Bibliothek wie Nette DI. -- Schrittweise Umstrukturierung jedes Teils der Anwendung, um Dependency Injection zu verwenden. Dies kann die Änderung von Konstruktoren oder Methoden beinhalten, um Abhängigkeiten als Parameter zu akzeptieren. -- Ändern Sie die Stellen im Code, an denen Abhängigkeitsobjekte erstellt werden, so dass die Abhängigkeiten stattdessen vom Container injiziert werden. Dies kann die Verwendung von Fabriken beinhalten. +- Beim Übergang zur Dependency Injection ist es wichtig, dass alle Teammitglieder die verwendeten Prinzipien und Verfahren verstehen. +- Führen Sie zunächst eine Analyse der bestehenden Anwendung durch und identifizieren Sie die Schlüsselkomponenten und ihre Abhängigkeiten. Erstellen Sie einen Plan, welche Teile refaktorisiert werden und in welcher Reihenfolge. +- Implementieren Sie einen DI-Container oder verwenden Sie besser eine vorhandene Bibliothek, z. B. Nette DI. +- Refaktorisieren Sie nach und nach einzelne Teile der Anwendung, um Dependency Injection zu verwenden. Dies kann Anpassungen von Konstruktoren oder Methoden beinhalten, sodass sie Abhängigkeiten als Parameter akzeptieren. +- Passen Sie die Stellen im Code an, an denen Objekte mit Abhängigkeiten erstellt werden, sodass stattdessen die Abhängigkeiten vom Container injiziert werden. Dies kann die Verwendung von Fabriken beinhalten. -Denken Sie daran, dass der Wechsel zu Dependency Injection eine Investition in die Codequalität und die langfristige Nachhaltigkeit der Anwendung ist. Auch wenn es eine Herausforderung sein mag, diese Änderungen vorzunehmen, sollte das Ergebnis ein sauberer, modularer und leicht testbarer Code sein, der für zukünftige Erweiterungen und Wartung bereit ist. +Denken Sie daran, dass der Übergang zur Dependency Injection eine Investition in die Codequalität und die langfristige Wartbarkeit der Anwendung ist. Auch wenn es anspruchsvoll sein kann, diese Änderungen durchzuführen, sollte das Ergebnis ein saubererer, modularerer und leicht testbarer Code sein, der für zukünftige Erweiterungen und Wartung bereit ist. -Warum ist Komposition der Vererbung vorzuziehen? .[#toc-why-composition-is-preferred-over-inheritance] ------------------------------------------------------------------------------------------------------- -Die Komposition ist der Vererbung vorzuziehen, da sie die Wiederverwendbarkeit des Codes ermöglicht, ohne dass man sich um die Auswirkungen von Änderungen sorgen muss. Dadurch wird eine lockerere Kopplung erreicht, bei der wir uns keine Sorgen machen müssen, dass die Änderung eines Codes dazu führt, dass ein anderer abhängiger Code geändert werden muss. Ein typisches Beispiel ist die Situation, die als [Konstruktorhölle |passing-dependencies#Constructor hell] bezeichnet wird. +Warum wird Komposition der Vererbung vorgezogen? +------------------------------------------------ +Es ist ratsamer, [Komposition |nette:introduction-to-object-oriented-programming#Komposition] anstelle von [Vererbung |nette:introduction-to-object-oriented-programming#Vererbung] zu verwenden, da sie zur Wiederverwendung von Code dient, ohne sich um die Folgen von Änderungen kümmern zu müssen. Sie bietet also eine lockerere Kopplung, bei der wir keine Bedenken haben müssen, dass die Änderung eines Codes die Notwendigkeit zur Änderung eines anderen abhängigen Codes verursacht. Ein typisches Beispiel ist die Situation, die als [constructor hell |passing-dependencies#Constructor Hell] bezeichnet wird. -Kann Nette DI Container auch außerhalb von Nette verwendet werden? .[#toc-can-nette-di-container-be-used-outside-of-nette] --------------------------------------------------------------------------------------------------------------------------- +Kann Nette DI Container außerhalb von Nette verwendet werden? +------------------------------------------------------------- -Auf jeden Fall. Der Nette DI Container ist Teil von Nette, aber er ist als eigenständige Bibliothek konzipiert, die unabhängig von anderen Teilen des Frameworks verwendet werden kann. Installieren Sie ihn einfach mit Composer, erstellen Sie eine Konfigurationsdatei, die Ihre Dienste definiert, und verwenden Sie dann ein paar Zeilen PHP-Code, um den DI-Container zu erstellen. -Und schon können Sie damit beginnen, die Vorteile der Dependency Injection in Ihren Projekten zu nutzen. +Auf jeden Fall. Der Nette DI Container ist Teil von Nette, aber er ist als eigenständige Bibliothek konzipiert, die unabhängig von den anderen Teilen des Frameworks verwendet werden kann. Installieren Sie ihn einfach mit Composer, erstellen Sie eine Konfigurationsdatei mit der Definition Ihrer Dienste und erstellen Sie dann mit wenigen Zeilen PHP-Code den DI-Container. Und schon können Sie die Vorteile der Dependency Injection in Ihren Projekten nutzen. -Das Kapitel [Nette DI Container |nette-container] beschreibt, wie ein spezifischer Anwendungsfall aussieht, einschließlich des Codes. +Wie die konkrete Verwendung einschließlich des Codes aussieht, beschreibt das Kapitel [Nette DI Container |nette-container]. -Warum ist die Konfiguration in NEON-Dateien? .[#toc-why-is-the-configuration-in-neon-files] -------------------------------------------------------------------------------------------- +Warum ist die Konfiguration in NEON-Dateien? +-------------------------------------------- -NEON ist eine einfache und leicht lesbare Konfigurationssprache, die innerhalb von Nette entwickelt wurde, um Anwendungen, Dienste und deren Abhängigkeiten einzurichten. Im Vergleich zu JSON oder YAML bietet sie für diesen Zweck wesentlich intuitivere und flexiblere Möglichkeiten. In NEON lassen sich auf natürliche Weise Bindungen beschreiben, die in Symfony & YAML entweder gar nicht oder nur durch eine komplexe Beschreibung möglich wären. +NEON ist eine einfache und leicht lesbare Konfigurationssprache, die im Rahmen von Nette für die Konfiguration von Anwendungen, Diensten und deren Abhängigkeiten entwickelt wurde. Im Vergleich zu JSON oder YAML bietet sie für diesen Zweck wesentlich intuitivere und flexiblere Möglichkeiten. In NEON lassen sich Verknüpfungen natürlich beschreiben, die in Symfony & YAML entweder gar nicht oder nur durch eine komplizierte Umschreibung möglich wären. -Verlangsamt das Parsen von NEON-Dateien die Anwendung? .[#toc-does-parsing-neon-files-slow-down-the-application] ----------------------------------------------------------------------------------------------------------------- +Verlangsamt das Parsen von NEON-Dateien die Anwendung nicht? +------------------------------------------------------------ -Obwohl NEON-Dateien sehr schnell geparst werden, ist dieser Aspekt nicht wirklich von Bedeutung. Der Grund dafür ist, dass das Parsen von Dateien nur einmal beim ersten Start der Anwendung erfolgt. Danach wird der DI-Container-Code generiert, auf der Festplatte gespeichert und bei jeder nachfolgenden Anforderung ausgeführt, ohne dass ein weiteres Parsen erforderlich ist. +Obwohl NEON-Dateien sehr schnell geparst werden, spielt dieser Aspekt überhaupt keine Rolle. Der Grund dafür ist, dass das Parsen der Dateien nur einmal beim ersten Start der Anwendung erfolgt. Danach wird der Code des DI-Containers generiert, auf der Festplatte gespeichert und bei jeder weiteren Anfrage ausgeführt, ohne dass weiteres Parsen erforderlich ist. -So funktioniert es auch in einer Produktionsumgebung. Während der Entwicklung werden die NEON-Dateien jedes Mal geparst, wenn sich ihr Inhalt ändert, so dass der Entwickler immer über einen aktuellen DI-Container verfügt. Wie bereits erwähnt, ist das eigentliche Parsen eine Sache von einem Augenblick. +So funktioniert es in der Produktionsumgebung. Während der Entwicklung werden NEON-Dateien jedes Mal geparst, wenn sich ihr Inhalt ändert, damit der Entwickler immer über einen aktuellen DI-Container verfügt. Das eigentliche Parsen ist, wie gesagt, eine Frage von Augenblicken. -Wie greife ich in meiner Klasse auf die Parameter aus der Konfigurationsdatei zu? .[#toc-how-do-i-access-the-parameters-from-the-configuration-file-in-my-class] ----------------------------------------------------------------------------------------------------------------------------------------------------------------- +Wie greife ich von meiner Klasse auf Parameter in der Konfigurationsdatei zu? +----------------------------------------------------------------------------- -Denken Sie an [Regel Nr. 1: Lass es dir übergeben |introduction#Rule #1: Let It Be Passed to You]. Wenn eine Klasse Informationen aus einer Konfigurationsdatei benötigt, brauchen wir nicht herauszufinden, wie wir auf diese Informationen zugreifen können; stattdessen fragen wir sie einfach ab - zum Beispiel über den Klassenkonstruktor. Und wir führen die Übergabe in der Konfigurationsdatei durch. +Denken wir an [Regel Nr. 1: Lass es dir übergeben |introduction#Regel Nr. 1: Lass es dir übergeben]. Wenn eine Klasse Informationen aus der Konfigurationsdatei benötigt, müssen wir nicht darüber nachdenken, wie wir an diese Informationen gelangen, sondern wir fordern sie einfach an – zum Beispiel über den Konstruktor der Klasse. Und die Übergabe erfolgt in der Konfigurationsdatei. -In diesem Beispiel ist `%myParameter%` ein Platzhalter für den Wert des Parameters `myParameter`, der an den Konstruktor `MyClass` übergeben wird: +In diesem Beispiel ist `%myParameter%` ein Platzhalter für den Wert des Parameters `myParameter`, der an den Konstruktor der Klasse `MyClass` übergeben wird: ```php # config.neon @@ -103,10 +97,10 @@ services: - MyClass(%myParameter%) ``` -Wenn Sie mehrere Parameter übergeben oder Autowiring verwenden wollen, ist es sinnvoll, [die Parameter in ein Objekt zu verpacken |best-practices:passing-settings-to-presenters]. +Um mehrere Parameter zu übergeben oder Autowiring zu nutzen, ist es ratsam, [die Parameter in ein Objekt zu verpacken |best-practices:passing-settings-to-presenters]. -Unterstützt Nette die PSR-11 Container-Schnittstelle? .[#toc-does-nette-support-psr-11-container-interface] ------------------------------------------------------------------------------------------------------------ +Unterstützt Nette PSR-11: Container interface? +---------------------------------------------- Nette DI Container unterstützt PSR-11 nicht direkt. Wenn Sie jedoch Interoperabilität zwischen dem Nette DI Container und Bibliotheken oder Frameworks benötigen, die das PSR-11 Container Interface erwarten, können Sie einen [einfachen Adapter |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f] erstellen, der als Brücke zwischen dem Nette DI Container und PSR-11 dient. diff --git a/dependency-injection/de/global-state.texy b/dependency-injection/de/global-state.texy index 4df2cdc0ec..888cd7de63 100644 --- a/dependency-injection/de/global-state.texy +++ b/dependency-injection/de/global-state.texy @@ -2,43 +2,42 @@ Globaler Zustand und Singletons ******************************* .[perex] -Warnung: Die folgenden Konstrukte sind Symptome für schlecht entworfenen Code: +Warnung: Die folgenden Konstrukte sind Anzeichen für schlecht entworfenen Code: - `Foo::getInstance()` - `DB::insert(...)` - `Article::setDb($db)` - `ClassName::$var` oder `static::$var` -Finden Sie eines dieser Konstrukte in Ihrem Code? Wenn ja, dann haben Sie die Möglichkeit, ihn zu verbessern. Sie könnten denken, dass es sich um gängige Konstrukte handelt, die oft in Beispiellösungen verschiedener Bibliotheken und Frameworks zu sehen sind. Wenn das der Fall ist, ist ihr Code-Design mangelhaft. +Treten einige dieser Konstrukte in Ihrem Code auf? Dann haben Sie die Möglichkeit, ihn zu verbessern. Vielleicht denken Sie, dass dies übliche Konstrukte sind, die Sie vielleicht sogar in Beispiel-Lösungen verschiedener Bibliotheken und Frameworks sehen. Wenn dies der Fall ist, dann ist das Design ihres Codes nicht gut. -Wir sprechen hier nicht von einer akademischen Reinheit. Alle diese Konstrukte haben eines gemeinsam: Sie verwenden einen globalen Zustand. Und das hat zerstörerische Auswirkungen auf die Codequalität. Die Klassen täuschen über ihre Abhängigkeiten hinweg. Der Code wird unberechenbar. Das verwirrt die Entwickler und mindert ihre Effizienz. +Wir sprechen hier definitiv nicht von irgendeiner akademischen Reinheit. Alle diese Konstrukte haben eines gemeinsam: Sie verwenden globalen Zustand. Und dieser hat einen zerstörerischen Einfluss auf die Codequalität. Klassen lügen über ihre Abhängigkeiten. Code wird unvorhersehbar. Er verwirrt Programmierer und reduziert ihre Effizienz. -In diesem Kapitel wird erklärt, warum dies der Fall ist und wie man globale Zustände vermeiden kann. +In diesem Kapitel erklären wir, warum das so ist und wie man globalen Zustand vermeidet. -Globale Verkettung .[#toc-global-interlinking] ----------------------------------------------- +Globale Kopplung +---------------- -In einer idealen Welt sollte ein Objekt nur mit Objekten kommunizieren, die [direkt an es übergeben |passing-dependencies] wurden. Wenn ich zwei Objekte `A` und `B` erstelle und nie einen Verweis zwischen ihnen übergebe, dann können weder `A` noch `B` auf den Zustand des anderen zugreifen oder ihn ändern. Dies ist eine höchst wünschenswerte Eigenschaft von Code. Es ist so, als hätte man eine Batterie und eine Glühbirne; die Glühbirne leuchtet erst, wenn man sie über ein Kabel mit der Batterie verbindet. +In einer idealen Welt sollte ein Objekt nur mit Objekten kommunizieren können, die ihm [direkt übergeben |passing-dependencies] wurden. Wenn ich zwei Objekte `A` und `B` erstelle und niemals eine Referenz zwischen ihnen übergebe, dann können weder `A` noch `B` auf das andere Objekt zugreifen oder seinen Zustand ändern. Das ist eine sehr wünschenswerte Eigenschaft von Code. Es ist ähnlich wie bei einer Batterie und einer Glühbirne; die Glühbirne leuchtet nicht, solange Sie sie nicht mit einem Draht mit der Batterie verbinden. -Dies gilt jedoch nicht für globale (statische) Variablen oder Singletons. Das Objekt `A` könnte *drahtlos* auf das Objekt `C` zugreifen und es ohne jegliche Referenzübergabe ändern, indem es `C::changeSomething()` aufruft. Wenn das Objekt `B` auch auf das globale `C` zugreift, dann können sich `A` und `B` über `C` gegenseitig beeinflussen. +Das gilt jedoch nicht für globale (statische) Variablen oder Singletons. Objekt `A` könnte *drahtlos* auf Objekt `C` zugreifen und es modifizieren, ohne dass eine Referenz übergeben wird, indem es `C::changeSomething()` aufruft. Wenn Objekt `B` ebenfalls auf das globale `C` zugreift, dann können sich `A` und `B` gegenseitig über `C` beeinflussen. -Die Verwendung globaler Variablen führt eine neue Form der *drahtlosen* Kopplung ein, die von außen nicht sichtbar ist. Sie schafft einen Nebelschleier, der das Verständnis und die Verwendung des Codes erschwert. Um die Abhängigkeiten wirklich zu verstehen, müssen Entwickler jede Zeile des Quellcodes lesen, anstatt sich nur mit den Klassenschnittstellen vertraut zu machen. Außerdem ist diese Verstrickung völlig unnötig. Globale Zustände werden verwendet, weil sie von überall her leicht zugänglich sind und beispielsweise das Schreiben in eine Datenbank über eine globale (statische) Methode `DB::insert()` ermöglichen. Wie wir jedoch sehen werden, ist der Nutzen, den er bietet, minimal, während die Komplikationen, die er mit sich bringt, schwerwiegend sind. +Die Verwendung globaler Variablen führt eine neue Form der *drahtlosen* Kopplung in das System ein, die von außen nicht sichtbar ist. Sie erzeugt eine Nebelwand, die das Verständnis und die Verwendung des Codes erschwert. Um die Abhängigkeiten wirklich zu verstehen, müssen Entwickler jede Zeile des Quellcodes lesen, anstatt sich nur mit der Schnittstelle der Klassen vertraut zu machen. Es handelt sich zudem um eine völlig unnötige Kopplung. Globaler Zustand wird verwendet, weil er von überall leicht zugänglich ist und es beispielsweise ermöglicht, über eine globale (statische) Methode `DB::insert()` in die Datenbank zu schreiben. Aber wie wir zeigen werden, ist der Vorteil, den dies bringt, gering, während die dadurch verursachten Komplikationen fatal sind. .[note] -Was das Verhalten angeht, gibt es keinen Unterschied zwischen einer globalen und einer statischen Variable. Sie sind gleichermaßen schädlich. +Aus Verhaltenssicht gibt es keinen Unterschied zwischen einer globalen und einer statischen Variablen. Sie sind gleichermaßen schädlich. -Die spukhafte Wirkung in der Ferne .[#toc-the-spooky-action-at-a-distance] --------------------------------------------------------------------------- +Spukhafte Fernwirkung +--------------------- -"Spukhafte Fernwirkung" - so nannte Albert Einstein 1935 ein Phänomen der Quantenphysik, das ihm eine Gänsehaut bereitete. -Es handelt sich dabei um die Quantenverschränkung, deren Besonderheit darin besteht, dass die Messung von Informationen über ein Teilchen sofort Auswirkungen auf ein anderes Teilchen hat, selbst wenn sie Millionen von Lichtjahren voneinander entfernt sind. -Dies verstößt scheinbar gegen das grundlegende Gesetz des Universums, dass sich nichts schneller als das Licht bewegen kann. +"Spukhafte Fernwirkung" – so nannte Albert Einstein 1935 berühmt ein Phänomen in der Quantenphysik, das ihm Gänsehaut bereitete. +Es handelt sich um die Quantenverschränkung, deren Besonderheit darin besteht, dass, wenn man Informationen über ein Teilchen misst, man sofort das andere Teilchen beeinflusst, auch wenn sie Millionen von Lichtjahren voneinander entfernt sind. Dies scheint das Grundgesetz des Universums zu verletzen, dass sich nichts schneller als Licht ausbreiten kann. -In der Software-Welt können wir von einer "spukhaften Fernwirkung" sprechen, wenn wir einen Prozess laufen lassen, von dem wir glauben, dass er isoliert ist (weil wir ihm keine Referenzen übergeben haben), aber unerwartete Wechselwirkungen und Zustandsänderungen an entfernten Stellen des Systems auftreten, von denen wir dem Objekt nichts gesagt haben. Dies kann nur über den globalen Zustand geschehen. +In der Softwarewelt können wir "spukhafte Fernwirkung" eine Situation nennen, in der wir einen Prozess starten, von dem wir annehmen, dass er isoliert ist (weil wir ihm keine Referenzen übergeben haben), aber an entfernten Stellen im System unerwartete Interaktionen und Zustandsänderungen auftreten, von denen wir keine Ahnung hatten. Dies kann nur durch globalen Zustand geschehen. -Stellen Sie sich vor, Sie treten in ein Projektentwicklungsteam ein, das über eine große, ausgereifte Codebasis verfügt. Ihr neuer Leiter bittet Sie, eine neue Funktion zu implementieren, und wie ein guter Entwickler beginnen Sie damit, einen Test zu schreiben. Da Sie aber neu im Projekt sind, machen Sie viele Tests vom Typ "was passiert, wenn ich diese Methode aufrufe". Und Sie versuchen, den folgenden Test zu schreiben: +Stellen Sie sich vor, Sie treten einem Entwicklerteam eines Projekts bei, das über eine umfangreiche, ausgereifte Codebasis verfügt. Ihr neuer Vorgesetzter bittet Sie, eine neue Funktion zu implementieren, und Sie beginnen als guter Entwickler mit dem Schreiben eines Tests. Da Sie jedoch neu im Projekt sind, führen Sie viele explorative Tests durch, wie z. B. "Was passiert, wenn ich diese Methode aufrufe?". Und Sie versuchen, den folgenden Test zu schreiben: ```php function testCreditCardCharge() @@ -48,20 +47,17 @@ function testCreditCardCharge() } ``` -Sie führen den Code aus, vielleicht mehrere Male, und nach einer Weile bemerken Sie auf Ihrem Telefon Benachrichtigungen von der Bank, dass jedes Mal, wenn Sie ihn ausführen, Ihre Kreditkarte mit 100 Dollar belastet wurde 🤦‍♂️ +Sie führen den Code aus, vielleicht mehrmals, und nach einer Weile bemerken Sie Benachrichtigungen von Ihrer Bank auf Ihrem Handy, dass bei jedem Ausführen 100 Dollar von Ihrer Kreditkarte abgebucht wurden 🤦‍♂️ -Wie um alles in der Welt konnte der Test eine tatsächliche Belastung verursachen? Es ist nicht einfach, mit einer Kreditkarte zu arbeiten. Sie müssen mit einem Webdienst eines Drittanbieters interagieren, Sie müssen die URL dieses Webdienstes kennen, Sie müssen sich anmelden und so weiter. -Keine dieser Informationen ist in dem Test enthalten. Noch schlimmer ist, dass Sie nicht einmal wissen, wo diese Informationen vorhanden sind und wie Sie externe Abhängigkeiten simulieren können, damit nicht bei jedem Durchlauf erneut 100 Dollar fällig werden. Und woher sollten Sie als neuer Entwickler wissen, dass das, was Sie gerade tun wollten, Sie um 100 Dollar ärmer machen würde? +Wie um alles in der Welt konnte der Test dazu führen, dass tatsächlich Geld abgebucht wird? Die Handhabung einer Kreditkarte ist nicht einfach. Sie müssen mit einem Webdienst eines Drittanbieters kommunizieren, Sie müssen die URL dieses Webdienstes kennen, Sie müssen sich anmelden und so weiter. Keine dieser Informationen ist im Test enthalten. Schlimmer noch, Sie wissen nicht einmal, wo diese Informationen vorhanden sind, und daher auch nicht, wie Sie externe Abhängigkeiten mocken können, damit nicht bei jeder Ausführung erneut 100 Dollar abgebucht werden. Und wie hätten Sie als neuer Entwickler wissen sollen, dass das, was Sie tun wollten, dazu führen würde, dass Sie um 100 Dollar ärmer sind? -Das ist eine gespenstische Aktion aus der Ferne! +Das ist spukhafte Fernwirkung! -Es bleibt Ihnen nichts anderes übrig, als sich durch eine Menge Quellcode zu wühlen und ältere und erfahrenere Kollegen zu fragen, bis Sie verstehen, wie die Zusammenhänge im Projekt funktionieren. -Das liegt daran, dass man bei einem Blick auf die Schnittstelle der Klasse `CreditCard` den globalen Zustand, der initialisiert werden muss, nicht feststellen kann. Selbst ein Blick in den Quellcode der Klasse verrät Ihnen nicht, welche Initialisierungsmethode Sie aufrufen müssen. Bestenfalls können Sie die globale Variable finden, auf die zugegriffen wird, und versuchen, daraus zu erraten, wie sie zu initialisieren ist. +Es bleibt Ihnen nichts anderes übrig, als sich lange durch eine Menge Quellcode zu wühlen und ältere und erfahrenere Kollegen zu fragen, bis Sie verstehen, wie die Abhängigkeiten im Projekt funktionieren. Dies liegt daran, dass beim Betrachten der Schnittstelle der Klasse `CreditCard` der globale Zustand, der initialisiert werden muss, nicht erkannt werden kann. Selbst ein Blick in den Quellcode der Klasse verrät Ihnen nicht, welche Initialisierungsmethode Sie aufrufen müssen. Im besten Fall finden Sie eine globale Variable, auf die zugegriffen wird, und können daraus versuchen abzuleiten, wie sie initialisiert wird. -Die Klassen in einem solchen Projekt sind pathologische Lügner. Die Zahlungskarte gibt vor, dass man sie einfach instanziieren und die Methode `charge()` aufrufen kann. Insgeheim interagiert sie jedoch mit einer anderen Klasse, `PaymentGateway`. Sogar ihre Schnittstelle sagt, dass sie unabhängig initialisiert werden kann, aber in Wirklichkeit bezieht sie Anmeldedaten aus einer Konfigurationsdatei usw. -Den Entwicklern, die diesen Code geschrieben haben, ist klar, dass `CreditCard` `PaymentGateway` benötigt. Sie haben den Code auf diese Weise geschrieben. Aber für jeden, der neu in das Projekt einsteigt, ist dies ein völliges Rätsel und behindert das Lernen. +Klassen in einem solchen Projekt sind pathologische Lügner. Die Kreditkarte tut so, als ob es ausreicht, sie zu instanziieren und die Methode `charge()` aufzurufen. Im Verborgenen arbeitet sie jedoch mit einer anderen Klasse `PaymentGateway` zusammen, die das Zahlungsgateway darstellt. Auch deren Schnittstelle besagt, dass sie separat initialisiert werden kann, aber tatsächlich holt sie sich Anmeldeinformationen aus einer Konfigurationsdatei und so weiter. Den Entwicklern, die diesen Code geschrieben haben, ist klar, dass `CreditCard` `PaymentGateway` benötigt. Sie haben den Code auf diese Weise geschrieben. Aber für jeden, der neu im Projekt ist, ist es ein absolutes Rätsel und behindert das Lernen. -Wie kann man das Problem lösen? Ganz einfach. **Lassen Sie die API Abhängigkeiten deklarieren. +Wie kann man die Situation beheben? Einfach. **Lassen Sie die API Abhängigkeiten deklarieren.** ```php function testCreditCardCharge() @@ -72,36 +68,35 @@ function testCreditCardCharge() } ``` -Beachten Sie, dass die Beziehungen innerhalb des Codes plötzlich offensichtlich sind. Indem Sie erklären, dass die Methode `charge()` `PaymentGateway` benötigt, müssen Sie niemanden fragen, wie der Code voneinander abhängig ist. Sie wissen, dass Sie eine Instanz der Methode erstellen müssen, und wenn Sie dies versuchen, stoßen Sie auf die Tatsache, dass Sie Zugriffsparameter bereitstellen müssen. Ohne sie würde der Code nicht einmal laufen. +Beachten Sie, wie die Abhängigkeiten innerhalb des Codes plötzlich offensichtlich sind. Dadurch, dass die Methode `charge()` deklariert, dass sie `PaymentGateway` benötigt, müssen Sie niemanden fragen, wie der Code verknüpft ist. Sie wissen, dass Sie eine Instanz davon erstellen müssen, und wenn Sie dies versuchen, stoßen Sie darauf, dass Sie Zugriffsparameter angeben müssen. Ohne sie ließe sich der Code nicht einmal ausführen. -Und das Wichtigste ist, dass Sie jetzt das Zahlungs-Gateway simulieren können, damit Sie nicht jedes Mal, wenn Sie einen Test durchführen, 100 Dollar bezahlen müssen. +Und vor allem können Sie jetzt das Zahlungsgateway mocken, sodass Ihnen nicht bei jedem Testlauf 100 Dollar berechnet werden. -Der globale Status bewirkt, dass Ihre Objekte heimlich auf Dinge zugreifen können, die nicht in ihren APIs deklariert sind, und macht Ihre APIs damit zu pathologischen Lügnern. +Globaler Zustand führt dazu, dass Ihre Objekte heimlich auf Dinge zugreifen können, die nicht in ihrer API deklariert sind, und macht Ihre APIs dadurch zu pathologischen Lügnern. -Sie haben vielleicht noch nie darüber nachgedacht, aber immer wenn Sie einen globalen Zustand verwenden, schaffen Sie geheime drahtlose Kommunikationskanäle. Unheimliche Remote-Aktionen zwingen Entwickler dazu, jede Codezeile zu lesen, um mögliche Interaktionen zu verstehen, verringern die Produktivität der Entwickler und verwirren neue Teammitglieder. -Wenn Sie derjenige sind, der den Code erstellt hat, kennen Sie die tatsächlichen Abhängigkeiten, aber jeder, der nach Ihnen kommt, ist ahnungslos. +Vielleicht haben Sie bisher nicht so darüber nachgedacht, aber wann immer Sie globalen Zustand verwenden, erstellen Sie geheime drahtlose Kommunikationskanäle. Spukhafte Fernwirkung zwingt Entwickler, jede Codezeile zu lesen, um potenzielle Interaktionen zu verstehen, reduziert die Produktivität der Entwickler und verwirrt neue Teammitglieder. Wenn Sie derjenige sind, der den Code erstellt hat, kennen Sie die tatsächlichen Abhängigkeiten, aber jeder, der nach Ihnen kommt, ist ratlos. -Schreiben Sie keinen Code, der globale Zustände verwendet, sondern übergeben Sie lieber Abhängigkeiten. Das heißt, Dependency Injection. +Schreiben Sie keinen Code, der globalen Zustand verwendet, bevorzugen Sie die Übergabe von Abhängigkeiten. Also Dependency Injection. -Die Zerbrechlichkeit des globalen Staates .[#toc-brittleness-of-the-global-state] ---------------------------------------------------------------------------------- +Zerbrechlichkeit des globalen Zustands +-------------------------------------- -Bei Code, der globale Zustände und Singletons verwendet, ist es nie sicher, wann und von wem dieser Zustand geändert wurde. Dieses Risiko ist bereits bei der Initialisierung gegeben. Der folgende Code soll eine Datenbankverbindung erstellen und das Zahlungs-Gateway initialisieren, aber er löst immer wieder eine Ausnahme aus, und die Suche nach der Ursache ist extrem mühsam: +In Code, der globalen Zustand und Singletons verwendet, ist nie sicher, wann und wer diesen Zustand geändert hat. Dieses Risiko tritt bereits bei der Initialisierung auf. Der folgende Code soll eine Datenbankverbindung herstellen und das Zahlungsgateway initialisieren, wirft jedoch ständig eine Ausnahme, und die Suche nach der Ursache ist extrem langwierig: ```php PaymentGateway::init(); DB::init('mysql:', 'user', 'password'); ``` -Sie müssen den Code im Detail durchgehen, um herauszufinden, dass das Objekt `PaymentGateway` drahtlos auf andere Objekte zugreift, von denen einige eine Datenbankverbindung benötigen. Sie müssen also die Datenbank vor `PaymentGateway` initialisieren. Der Nebel des globalen Zustands verbirgt dies jedoch vor Ihnen. Wie viel Zeit würden Sie sparen, wenn die API der einzelnen Klassen nicht lügen und ihre Abhängigkeiten deklarieren würde? +Sie müssen den Code detailliert durchgehen, um festzustellen, dass das `PaymentGateway`-Objekt drahtlos auf andere Objekte zugreift, von denen einige eine Datenbankverbindung erfordern. Daher muss die Datenbank vor `PaymentGateway` initialisiert werden. Die Nebelwand des globalen Zustands verbirgt dies jedoch vor Ihnen. Wie viel Zeit hätten Sie gespart, wenn die API der einzelnen Klassen nicht gelogen und ihre Abhängigkeiten deklariert hätte? ```php $db = new DB('mysql:', 'user', 'password'); $gateway = new PaymentGateway($db, ...); ``` -Ein ähnliches Problem ergibt sich bei der Verwendung des globalen Zugriffs auf eine Datenbankverbindung: +Ein ähnliches Problem tritt auch bei der Verwendung des globalen Zugriffs auf die Datenbankverbindung auf: ```php use Illuminate\Support\Facades\DB; @@ -115,7 +110,7 @@ class Article } ``` -Beim Aufruf der Methode `save()` ist nicht sicher, ob bereits eine Datenbankverbindung erstellt wurde und wer für deren Erstellung verantwortlich ist. Wenn wir zum Beispiel die Datenbankverbindung spontan ändern wollten, vielleicht zu Testzwecken, müssten wir wahrscheinlich zusätzliche Methoden wie `DB::reconnect(...)` oder `DB::reconnectForTest()` erstellen. +Beim Aufruf der Methode `save()` ist nicht sicher, ob die Datenbankverbindung bereits hergestellt wurde und wer für ihre Erstellung verantwortlich ist. Wenn wir beispielsweise die Datenbankverbindung zur Laufzeit ändern möchten, etwa für Tests, müssten wir wahrscheinlich weitere Methoden wie `DB::reconnect(...)` oder `DB::reconnectForTest()` erstellen. Betrachten wir ein Beispiel: @@ -127,9 +122,9 @@ Foo::doSomething(); $article->save(); ``` -Wo können wir sicher sein, dass beim Aufruf von `$article->save()` wirklich die Testdatenbank verwendet wird? Was wäre, wenn die Methode `Foo::doSomething()` die globale Datenbankverbindung ändern würde? Um das herauszufinden, müssten wir den Quellcode der Klasse `Foo` und wahrscheinlich vieler anderer Klassen untersuchen. Dieser Ansatz würde jedoch nur eine kurzfristige Antwort liefern, da sich die Situation in der Zukunft ändern kann. +Woher haben wir die Gewissheit, dass beim Aufruf von `$article->save()` tatsächlich die Testdatenbank verwendet wird? Was wäre, wenn die Methode `Foo::doSomething()` die globale Datenbankverbindung geändert hätte? Um dies herauszufinden, müssten wir den Quellcode der Klasse `Foo` und wahrscheinlich auch vieler anderer Klassen untersuchen. Dieser Ansatz würde jedoch nur eine kurzfristige Antwort liefern, da sich die Situation in Zukunft ändern kann. -Was wäre, wenn wir die Datenbankverbindung in eine statische Variable innerhalb der Klasse `Article` verschieben? +Und was wäre, wenn wir die Datenbankverbindung in eine statische Variable innerhalb der Klasse `Article` verschieben? ```php class Article @@ -148,11 +143,11 @@ class Article } ``` -Das ändert überhaupt nichts. Das Problem ist ein globaler Zustand und es spielt keine Rolle, in welcher Klasse es sich versteckt. In diesem Fall, wie auch im vorherigen, haben wir keine Ahnung, in welche Datenbank geschrieben wird, wenn die Methode `$article->save()` aufgerufen wird. Jeder am entfernten Ende der Anwendung könnte die Datenbank jederzeit mit `Article::setDb()` ändern. Unter unseren Händen. +Dadurch hat sich überhaupt nichts geändert. Das Problem ist der globale Zustand, und es ist völlig egal, in welcher Klasse er sich versteckt. In diesem Fall haben wir, genau wie im vorherigen, beim Aufruf der Methode `$article->save()` keinen Hinweis darauf, in welche Datenbank geschrieben wird. Irgendjemand am anderen Ende der Anwendung könnte die Datenbank jederzeit mit `Article::setDb()` ändern. Unter unseren Händen. -Der globale Zustand macht unsere Anwendung **extrem anfällig**. +Globaler Zustand macht unsere Anwendung **extrem zerbrechlich**. -Es gibt jedoch eine einfache Möglichkeit, mit diesem Problem umzugehen. Lassen Sie die API einfach Abhängigkeiten deklarieren, um die ordnungsgemäße Funktionalität zu gewährleisten. +Es gibt jedoch eine einfache Möglichkeit, dieses Problem zu lösen. Lassen Sie einfach die API Abhängigkeiten deklarieren, um die korrekte Funktionalität sicherzustellen. ```php class Article @@ -174,15 +169,15 @@ Foo::doSomething(); $article->save(); ``` -Auf diese Weise wird die Sorge vor versteckten und unerwarteten Änderungen an Datenbankverbindungen beseitigt. Jetzt wissen wir genau, wo der Artikel gespeichert ist, und keine Code-Änderungen in einer anderen, nicht verwandten Klasse können die Situation mehr verändern. Der Code ist nicht mehr anfällig, sondern stabil. +Dank dieses Ansatzes entfällt die Sorge über versteckte und unerwartete Änderungen der Datenbankverbindung. Jetzt haben wir die Gewissheit, wohin der Artikel gespeichert wird, und keine Codeänderungen innerhalb einer anderen, nicht zusammenhängenden Klasse können die Situation mehr ändern. Der Code ist nicht mehr zerbrechlich, sondern stabil. -Schreiben Sie keinen Code, der globale Zustände verwendet, sondern übergeben Sie lieber Abhängigkeiten. Daher Dependency Injection. +Schreiben Sie keinen Code, der globalen Zustand verwendet, bevorzugen Sie die Übergabe von Abhängigkeiten. Also Dependency Injection. -Singleton .[#toc-singleton] ---------------------------- +Singleton +--------- -Singleton ist ein Entwurfsmuster, das gemäß der [Definition |https://en.wikipedia.org/wiki/Singleton_pattern] aus der berühmten Gang of Four-Publikation eine Klasse auf eine einzige Instanz beschränkt und globalen Zugriff auf diese bietet. Die Implementierung dieses Musters ähnelt normalerweise dem folgenden Code: +Singleton ist ein Entwurfsmuster, das laut der "Definition":https://en.wikipedia.org/wiki/Singleton_pattern aus der bekannten Publikation der Gang of Four eine Klasse auf eine einzige Instanz beschränkt und einen globalen Zugriff darauf bietet. Die Implementierung dieses Musters ähnelt normalerweise dem folgenden Code: ```php class Singleton @@ -195,38 +190,35 @@ class Singleton return self::$instance; } - // und andere Methoden, die die Funktionen der Klasse ausführen + // und weitere Methoden, die die Funktionen der gegebenen Klasse erfüllen } ``` -Leider führt das Singleton einen globalen Zustand in die Anwendung ein. Und wie wir oben gezeigt haben, ist ein globaler Zustand unerwünscht. Deshalb wird das Singleton als Antipattern betrachtet. +Leider führt das Singleton globalen Zustand in die Anwendung ein. Und wie wir oben gezeigt haben, ist globaler Zustand unerwünscht. Daher wird das Singleton als Anti-Pattern betrachtet. -Verwenden Sie keine Singletons in Ihrem Code und ersetzen Sie sie durch andere Mechanismen. Sie brauchen Singletons wirklich nicht. Wenn Sie jedoch die Existenz einer einzigen Instanz einer Klasse für die gesamte Anwendung garantieren müssen, überlassen Sie dies dem [DI-Container |container]. -Erstellen Sie also ein Anwendungssingleton oder einen Dienst. Dadurch wird die Klasse nicht mehr für ihre eigene Einzigartigkeit sorgen (d. h. sie wird keine `getInstance()` -Methode und keine statische Variable haben) und nur ihre Funktionen ausführen. Damit wird das Prinzip der einzigen Verantwortung nicht mehr verletzt. +Verwenden Sie keine Singletons in Ihrem Code und ersetzen Sie sie durch andere Mechanismen. Sie benötigen Singletons wirklich nicht. Wenn Sie jedoch sicherstellen müssen, dass nur eine einzige Instanz einer Klasse für die gesamte Anwendung existiert, überlassen Sie dies dem [DI-Container |container]. Erstellen Sie so einen Anwendungs-Singleton, also einen Dienst. Dadurch kümmert sich die Klasse nicht mehr um die Sicherstellung ihrer eigenen Einzigartigkeit (d. h. sie hat keine `getInstance()`-Methode und keine statische Variable) und erfüllt nur noch ihre Funktionen. So hört sie auf, das Prinzip der einzigen Verantwortung zu verletzen. -Globaler Zustand vs. Tests .[#toc-global-state-versus-tests] ------------------------------------------------------------- +Globaler Zustand versus Tests +----------------------------- -Beim Schreiben von Tests gehen wir davon aus, dass jeder Test eine isolierte Einheit ist und dass kein externer Zustand in ihn eintritt. Und kein Zustand verlässt die Tests. Wenn ein Test abgeschlossen ist, sollte jeder mit dem Test verbundene Zustand automatisch vom Garbage Collector entfernt werden. Dadurch werden die Tests isoliert. Daher können wir die Tests in beliebiger Reihenfolge ausführen. +Beim Schreiben von Tests gehen wir davon aus, dass jeder Test eine isolierte Einheit ist und kein externer Zustand in ihn eintritt. Und kein Zustand verlässt die Tests. Nach Abschluss eines Tests sollte der gesamte zugehörige Zustand automatisch vom Garbage Collector entfernt werden. Dadurch sind die Tests isoliert. Daher können wir Tests in beliebiger Reihenfolge ausführen. -Wenn jedoch globale Zustände/Singletons vorhanden sind, sind alle diese schönen Annahmen hinfällig. Ein Zustand kann einen Test betreten und verlassen. Plötzlich kann die Reihenfolge der Tests eine Rolle spielen. +Wenn jedoch globale Zustände/Singletons vorhanden sind, zerfallen all diese angenehmen Annahmen. Zustand kann in den Test eintreten und ihn verlassen. Plötzlich kann die Reihenfolge der Tests eine Rolle spielen. -Um Singletons überhaupt testen zu können, müssen Entwickler oft ihre Eigenschaften lockern, indem sie beispielsweise zulassen, dass eine Instanz durch eine andere ersetzt wird. Solche Lösungen sind bestenfalls Hacks, die schwer zu wartenden und schwer zu verstehenden Code produzieren. Jeder Test oder jede Methode `tearDown()`, die einen globalen Zustand beeinflusst, muss diese Änderungen rückgängig machen. +Um Singletons überhaupt testen zu können, müssen Entwickler oft ihre Eigenschaften lockern, etwa indem sie erlauben, die Instanz durch eine andere zu ersetzen. Solche Lösungen sind bestenfalls Hacks, die schwer wartbaren und verständlichen Code erzeugen. Jeder Test oder jede `tearDown()`-Methode, die einen globalen Zustand beeinflusst, muss diese Änderungen rückgängig machen. -Der globale Zustand ist das größte Problem bei Unit-Tests! +Globaler Zustand ist der größte Kopfschmerz beim Unit-Testing! -Wie kann man das Problem lösen? Ganz einfach. Schreiben Sie keinen Code, der Singletons verwendet, sondern ziehen Sie es vor, Abhängigkeiten zu übergeben. Das heißt, dependency injection. +Wie kann man die Situation beheben? Einfach. Schreiben Sie keinen Code, der Singletons verwendet, bevorzugen Sie die Übergabe von Abhängigkeiten. Also Dependency Injection. -Globale Konstanten .[#toc-global-constants] -------------------------------------------- +Globale Konstanten +------------------ -Der globale Status ist nicht auf die Verwendung von Singletons und statischen Variablen beschränkt, sondern kann auch für globale Konstanten gelten. +Globaler Zustand beschränkt sich nicht nur auf die Verwendung von Singletons und statischen Variablen, sondern kann auch globale Konstanten betreffen. -Konstanten, deren Wert uns keine neuen (`M_PI`) oder nützlichen (`PREG_BACKTRACK_LIMIT_ERROR`) Informationen liefert, sind eindeutig in Ordnung. -Umgekehrt sind Konstanten, die dazu dienen, Informationen innerhalb des Codes *drahtlos* weiterzugeben, nichts anderes als eine versteckte Abhängigkeit. Wie `LOG_FILE` im folgenden Beispiel. -Die Verwendung der Konstante `FILE_APPEND` ist völlig korrekt. +Konstanten, deren Wert uns keine neue (`M_PI`) oder nützliche (`PREG_BACKTRACK_LIMIT_ERROR`) Information bringt, sind eindeutig in Ordnung. Im Gegensatz dazu sind Konstanten, die als Mittel dienen, Informationen *drahtlos* in den Code zu übergeben, nichts anderes als versteckte Abhängigkeiten. Wie z. B. `LOG_FILE` im folgenden Beispiel. Die Verwendung der Konstante `FILE_APPEND` ist völlig korrekt. ```php const LOG_FILE = '...'; @@ -242,7 +234,7 @@ class Foo } ``` -In diesem Fall sollten wir den Parameter im Konstruktor der Klasse `Foo` deklarieren, um ihn zum Bestandteil der API zu machen: +In diesem Fall sollten wir einen Parameter im Konstruktor der Klasse `Foo` deklarieren, damit er Teil der API wird: ```php class Foo @@ -261,44 +253,42 @@ class Foo } ``` -Jetzt können wir Informationen über den Pfad zur Protokollierungsdatei übergeben und ihn bei Bedarf leicht ändern, was das Testen und Warten des Codes erleichtert. +Jetzt können wir die Information über den Pfad zur Log-Datei übergeben und sie bei Bedarf leicht ändern, was das Testen und die Wartung des Codes erleichtert. -Globale Funktionen und statische Methoden .[#toc-global-functions-and-static-methods] -------------------------------------------------------------------------------------- +Globale Funktionen und statische Methoden +----------------------------------------- -Wir möchten betonen, dass die Verwendung von statischen Methoden und globalen Funktionen an sich nicht problematisch ist. Wir haben die Unangemessenheit der Verwendung von `DB::insert()` und ähnlichen Methoden erläutert, aber es ging immer um den globalen Zustand, der in einer statischen Variablen gespeichert wird. Die Methode `DB::insert()` erfordert das Vorhandensein einer statischen Variablen, weil sie die Datenbankverbindung speichert. Ohne diese Variable wäre es unmöglich, die Methode zu implementieren. +Wir möchten betonen, dass die Verwendung statischer Methoden und globaler Funktionen an sich nicht problematisch ist. Wir haben erklärt, warum die Verwendung von `DB::insert()` und ähnlichen Methoden ungeeignet ist, aber es ging immer nur um den globalen Zustand, der in einer statischen Variablen gespeichert ist. Die Methode `DB::insert()` erfordert die Existenz einer statischen Variablen, da darin die Datenbankverbindung gespeichert ist. Ohne diese Variable wäre es unmöglich, die Methode zu implementieren. -Die Verwendung von deterministischen statischen Methoden und Funktionen, wie `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` und viele andere, ist mit der Dependency Injection vollkommen vereinbar. Diese Funktionen liefern immer die gleichen Ergebnisse für die gleichen Eingabeparameter und sind daher vorhersehbar. Sie verwenden keinen globalen Zustand. +Die Verwendung deterministischer statischer Methoden und Funktionen wie `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` und vielen anderen steht in vollem Einklang mit der Dependency Injection. Diese Funktionen geben bei gleichen Eingabeparametern immer die gleichen Ergebnisse zurück und sind daher vorhersagbar. Sie verwenden keinen globalen Zustand. -Allerdings gibt es in PHP Funktionen, die nicht deterministisch sind. Dazu gehört z.B. die Funktion `htmlspecialchars()`. Ihr dritter Parameter, `$encoding`, wird, wenn er nicht angegeben wird, standardmäßig mit dem Wert der Konfigurationsoption `ini_get('default_charset')` belegt. Es wird daher empfohlen, diesen Parameter immer anzugeben, um ein unvorhersehbares Verhalten der Funktion zu vermeiden. Nette tut dies konsequent. +Es gibt jedoch auch Funktionen in PHP, die nicht deterministisch sind. Dazu gehört beispielsweise die Funktion `htmlspecialchars()`. Ihr dritter Parameter `$encoding`, falls nicht angegeben, hat als Standardwert den Wert der Konfigurationsoption `ini_get('default_charset')`. Daher wird empfohlen, diesen Parameter immer anzugeben, um mögliches unvorhersehbares Verhalten der Funktion zu vermeiden. Nette tut dies konsequent. -Einige Funktionen, wie `strtolower()`, `strtoupper()` und ähnliche, haben sich in der jüngsten Vergangenheit nicht deterministisch verhalten und waren von der Einstellung `setlocale()` abhängig. Dies führte zu zahlreichen Komplikationen, vor allem bei der Arbeit mit der türkischen Sprache. -Das liegt daran, dass die türkische Sprache zwischen Groß- und Kleinschreibung `I` mit und ohne Punkt unterscheidet. So gab `strtolower('I')` das Zeichen `ı` und `strtoupper('i')` das Zeichen `İ` zurück, was dazu führte, dass Anwendungen eine Reihe von mysteriösen Fehlern verursachten. -Dieses Problem wurde jedoch in der PHP-Version 8.2 behoben, und die Funktionen sind nun nicht mehr vom Gebietsschema abhängig. +Einige Funktionen wie `strtolower()`, `strtoupper()` und ähnliche verhielten sich in der jüngeren Vergangenheit nicht deterministisch und waren von der Einstellung `setlocale()` abhängig. Dies verursachte viele Komplikationen, am häufigsten bei der Arbeit mit der türkischen Sprache. Diese unterscheidet nämlich sowohl Klein- als auch Großbuchstaben `I` mit und ohne Punkt. So gab `strtolower('I')` den Buchstaben `ı` zurück und `strtoupper('i')` den Buchstaben `İ`, was dazu führte, dass Anwendungen eine Reihe rätselhafter Fehler verursachten. Dieses Problem wurde jedoch in PHP Version 8.2 behoben, und die Funktionen sind nicht mehr von der Locale abhängig. -Dies ist ein schönes Beispiel dafür, wie der globale Zustand Tausende von Entwicklern auf der ganzen Welt geplagt hat. Die Lösung bestand darin, ihn durch Dependency Injection zu ersetzen. +Dies ist ein schönes Beispiel dafür, wie globaler Zustand Tausende von Entwicklern weltweit geplagt hat. Die Lösung bestand darin, ihn durch Dependency Injection zu ersetzen. -Wann ist es möglich, einen globalen Status zu verwenden? .[#toc-when-is-it-possible-to-use-global-state] --------------------------------------------------------------------------------------------------------- +Wann ist die Verwendung von globalem Zustand möglich? +----------------------------------------------------- -Es gibt bestimmte Situationen, in denen es möglich ist, globale Zustände zu verwenden. Zum Beispiel beim Debuggen von Code, wenn Sie den Wert einer Variablen ausgeben oder die Dauer eines bestimmten Programmteils messen müssen. In solchen Fällen, die temporäre Aktionen betreffen, die später aus dem Code entfernt werden, ist es legitim, einen global verfügbaren Dumper oder eine Stoppuhr zu verwenden. Diese Werkzeuge sind nicht Teil des Codeentwurfs. +Es gibt bestimmte spezifische Situationen, in denen die Verwendung von globalem Zustand möglich ist. Zum Beispiel beim Debuggen von Code, wenn Sie den Wert einer Variablen ausgeben oder die Dauer eines bestimmten Programmteils messen müssen. In solchen Fällen, die sich auf temporäre Aktionen beziehen, die später aus dem Code entfernt werden, ist es legitim, einen global verfügbaren Dumper oder eine Stoppuhr zu verwenden. Diese Werkzeuge sind nämlich nicht Teil des Code-Designs. -Ein weiteres Beispiel sind die Funktionen für die Arbeit mit regulären Ausdrücken `preg_*`, die intern kompilierte reguläre Ausdrücke in einem statischen Cache im Speicher ablegen. Wenn Sie denselben regulären Ausdruck mehrmals in verschiedenen Teilen des Codes aufrufen, wird er nur einmal kompiliert. Der Cache spart Leistung und ist außerdem für den Benutzer völlig unsichtbar, so dass eine solche Verwendung als legitim angesehen werden kann. +Ein weiteres Beispiel sind Funktionen zur Arbeit mit regulären Ausdrücken `preg_*`, die intern kompilierte reguläre Ausdrücke in einem statischen Cache im Speicher ablegen. Wenn Sie also denselben regulären Ausdruck mehrmals an verschiedenen Stellen im Code aufrufen, wird er nur einmal kompiliert. Der Cache spart Leistung und ist gleichzeitig für den Benutzer völlig unsichtbar, daher kann eine solche Verwendung als legitim angesehen werden. -Zusammenfassung .[#toc-summary] -------------------------------- +Zusammenfassung +--------------- -Wir haben gezeigt, warum es Sinn macht +Wir haben besprochen, warum es sinnvoll ist: -1) Entfernen Sie alle statischen Variablen aus dem Code -2) Deklarieren Sie Abhängigkeiten -3) Und verwenden Sie Dependency Injection +1) Alle statischen Variablen aus dem Code zu entfernen +2) Abhängigkeiten zu deklarieren +3) Und Dependency Injection zu verwenden -Wenn Sie über den Entwurf von Code nachdenken, sollten Sie bedenken, dass jedes `static $foo` ein Problem darstellt. Damit Ihr Code eine DI-konforme Umgebung wird, ist es unerlässlich, den globalen Zustand vollständig zu beseitigen und durch Dependency Injection zu ersetzen. +Wenn Sie über das Code-Design nachdenken, denken Sie daran, dass jedes `static $foo` ein Problem darstellt. Damit Ihr Code eine Umgebung ist, die DI respektiert, ist es unerlässlich, den globalen Zustand vollständig zu beseitigen und ihn durch Dependency Injection zu ersetzen. -Während dieses Prozesses kann es vorkommen, dass Sie eine Klasse aufteilen müssen, weil sie mehr als eine Verantwortung hat. Machen Sie sich keine Gedanken darüber; streben Sie das Prinzip der einen Verantwortung an. +Während dieses Prozesses stellen Sie möglicherweise fest, dass eine Klasse aufgeteilt werden muss, da sie mehr als eine Verantwortung hat. Scheuen Sie sich nicht davor; streben Sie das Prinzip der einzigen Verantwortung an. -*Ich möchte Miško Hevery danken, dessen Artikel wie [Flaw: Brittle Global State & Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/] die Grundlage für dieses Kapitel bilden.* +*Ich möchte Miško Hevery danken, dessen Artikel wie [Flaw: Brittle Global State & Singletons |https://web.archive.org/web/20230321084133/http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/] die Grundlage für dieses Kapitel bilden.* diff --git a/dependency-injection/de/introduction.texy b/dependency-injection/de/introduction.texy index 74bb6c1d35..e1dece6d1b 100644 --- a/dependency-injection/de/introduction.texy +++ b/dependency-injection/de/introduction.texy @@ -2,57 +2,57 @@ Was ist Dependency Injection? ***************************** .[perex] -In diesem Kapitel werden Sie mit den grundlegenden Programmierpraktiken vertraut gemacht, die Sie beim Schreiben jeder Anwendung befolgen sollten. Dies sind die Grundlagen, die zum Schreiben von sauberem, verständlichem und wartbarem Code erforderlich sind. +Dieses Kapitel führt Sie in die grundlegenden Programmierpraktiken ein, die Sie beim Schreiben aller Anwendungen befolgen sollten. Dies sind die Grundlagen für das Schreiben von sauberem, verständlichem und wartbarem Code. -Wenn Sie diese Regeln lernen und befolgen, wird Nette Ihnen bei jedem Schritt zur Seite stehen. Es wird Routineaufgaben für Sie erledigen und Ihnen maximalen Komfort bieten, so dass Sie sich auf die eigentliche Logik konzentrieren können. +Wenn Sie sich diese Regeln aneignen und befolgen, wird Nette Sie bei jedem Schritt unterstützen. Es wird Routineaufgaben für Sie erledigen und Ihnen maximalen Komfort bieten, damit Sie sich auf die eigentliche Logik konzentrieren können. -Die Prinzipien, die wir hier zeigen, sind ganz einfach. Sie brauchen sich um nichts zu kümmern. +Die Prinzipien, die wir hier vorstellen werden, sind dabei recht einfach. Sie müssen sich keine Sorgen machen. -Erinnern Sie sich an Ihr erstes Programm? .[#toc-remember-your-first-program] ------------------------------------------------------------------------------ +Erinnern Sie sich an Ihr erstes Programm? +----------------------------------------- -Wir wissen nicht, in welcher Sprache Sie es geschrieben haben, aber wenn es PHP war, könnte es etwa so aussehen: +Wir wissen nicht, in welcher Sprache Sie es geschrieben haben, aber wenn es PHP gewesen wäre, hätte es wahrscheinlich so ausgesehen: ```php -function summe(float $a, float $b): float +function soucet(float $a, float $b): float { return $a + $b; } -echo summe(23, 1); // gibt 24 aus +echo soucet(23, 1); // gibt 24 aus ``` -Ein paar triviale Codezeilen, aber so viele wichtige Konzepte, die darin versteckt sind. Dass es Variablen gibt. Dass der Code in kleinere Einheiten unterteilt ist, die zum Beispiel Funktionen sind. Dass wir ihnen Eingabeargumente übergeben und sie Ergebnisse zurückgeben. Alles, was fehlt, sind Bedingungen und Schleifen. +Ein paar triviale Codezeilen, aber sie enthalten so viele Schlüsselkonzepte. Dass es Variablen gibt. Dass Code in kleinere Einheiten unterteilt wird, wie zum Beispiel Funktionen. Dass wir ihnen Eingabeargumente übergeben und sie Ergebnisse zurückgeben. Es fehlen nur noch Bedingungen und Schleifen. -Die Tatsache, dass eine Funktion Eingabedaten entgegennimmt und ein Ergebnis zurückliefert, ist ein durchaus verständliches Konzept, das auch in anderen Bereichen, z. B. der Mathematik, verwendet wird. +Dass wir einer Funktion Eingabedaten übergeben und sie ein Ergebnis zurückgibt, ist ein perfekt verständliches Konzept, das auch in anderen Bereichen verwendet wird, wie zum Beispiel in der Mathematik. -Eine Funktion hat ihre Signatur, die aus ihrem Namen, einer Liste von Parametern und deren Typen und schließlich dem Typ des Rückgabewerts besteht. Als Benutzer sind wir an der Signatur interessiert und müssen normalerweise nichts über die interne Implementierung wissen. +Eine Funktion hat ihre Signatur, die aus ihrem Namen, einer Übersicht der Parameter und ihrer Typen und schließlich dem Typ des Rückgabewerts besteht. Als Benutzer interessiert uns die Signatur, über die interne Implementierung müssen wir normalerweise nichts wissen. -Stellen Sie sich nun vor, die Funktionssignatur sähe wie folgt aus: +Stellen Sie sich nun vor, die Funktionssignatur sähe so aus: ```php -function summe(float $x): float +function soucet(float $x): float ``` -Ein Zusatz mit einem Parameter? Das ist seltsam... Und was ist damit? +Eine Summe mit einem Parameter? Das ist seltsam… Und was ist hiermit? ```php -function summe(): float +function soucet(): float ``` -Das ist doch wirklich seltsam, oder? Wie wird die Funktion verwendet? +Das ist jetzt wirklich sehr seltsam, oder? Wie wird die Funktion wohl verwendet? ```php -echo summe(); // was wird gedruckt? +echo soucet(); // was gibt das wohl aus? ``` -Wenn wir uns einen solchen Code ansehen, wären wir verwirrt. Nicht nur ein Anfänger würde ihn nicht verstehen, sondern auch ein erfahrener Programmierer würde einen solchen Code nicht verstehen. +Beim Anblick eines solchen Codes wären wir verwirrt. Nicht nur ein Anfänger würde ihn nicht verstehen, auch ein erfahrener Programmierer versteht solchen Code nicht. -Fragen Sie sich, wie eine solche Funktion eigentlich aussehen würde? Woher würde sie die Summanden bekommen? Sie würde sie wahrscheinlich *irgendwie* selbst beschaffen, vielleicht so: +Überlegen Sie, wie eine solche Funktion intern aussehen würde? Woher nimmt sie die Summanden? Wahrscheinlich würde sie sie sich *irgendwie* selbst beschaffen, vielleicht so: ```php -function summe(): float +function soucet(): float { $a = Input::get('a'); $b = Input::get('b'); @@ -60,71 +60,71 @@ function summe(): float } ``` -Es stellt sich heraus, dass es versteckte Bindungen zu anderen Funktionen (oder statischen Methoden) im Körper der Funktion gibt, und um herauszufinden, woher die Summanden tatsächlich kommen, müssen wir weiter graben. +Im Funktionskörper haben wir versteckte Abhängigkeiten zu anderen globalen Funktionen oder statischen Methoden entdeckt. Um herauszufinden, woher die Summanden tatsächlich stammen, müssen wir weiter suchen. -Nicht hier entlang! .[#toc-not-this-way] ----------------------------------------- +So nicht! +--------- -Das eben gezeigte Design ist die Essenz vieler negativer Merkmale: +Der Entwurf, den wir gerade vorgestellt haben, ist die Essenz vieler negativer Eigenschaften: -- die Funktionssignatur gibt vor, dass sie die Summanden nicht braucht, was uns verwirrt -- wir haben keine Ahnung, wie wir die Funktion mit zwei anderen Zahlen rechnen lassen können -- wir mussten uns den Code ansehen, um herauszufinden, woher die Summanden kamen -- wir haben versteckte Abhängigkeiten gefunden -- ein vollständiges Verständnis erfordert auch die Untersuchung dieser Abhängigkeiten +- Die Funktionssignatur tat so, als ob sie keine Summanden bräuchte, was uns verwirrte +- Wir wissen überhaupt nicht, wie wir die Funktion dazu bringen können, zwei andere Zahlen zu addieren +- Wir mussten uns den Code ansehen, um herauszufinden, woher sie die Summanden nimmt +- Wir haben versteckte Abhängigkeiten entdeckt +- Für ein vollständiges Verständnis müssen auch diese Abhängigkeiten untersucht werden -Und ist es überhaupt die Aufgabe der Additionsfunktion, Eingaben zu beschaffen? Nein, natürlich nicht. Ihre Aufgabe ist es nur, zu addieren. +Und ist es überhaupt die Aufgabe einer Additionsfunktion, sich Eingaben zu beschaffen? Natürlich nicht. Ihre Verantwortung liegt ausschließlich in der Addition selbst. -Solchen Code wollen wir nicht sehen, und wir wollen ihn schon gar nicht schreiben. Die Abhilfe ist einfach: Zurück zu den Grundlagen und einfach Parameter verwenden: +Solchen Code wollen wir nicht sehen und schon gar nicht schreiben. Die Korrektur ist dabei einfach: Zurück zu den Grundlagen und einfach Parameter verwenden: ```php -function summe(float $a, float $b): float +function soucet(float $a, float $b): float { return $a + $b; } ``` -Regel Nr. 1: Lass es dir übergeben .[#toc-rule-1-let-it-be-passed-to-you] -------------------------------------------------------------------------- +Regel Nr. 1: Lass es dir übergeben +---------------------------------- -Die wichtigste Regel lautet: **alle Daten, die Funktionen oder Klassen benötigen, müssen an sie übergeben werden**. +Die wichtigste Regel lautet: **Alle Daten, die Funktionen oder Klassen benötigen, müssen ihnen übergeben werden**. -Anstatt versteckte Wege für den Zugriff auf die Daten selbst zu erfinden, übergeben Sie einfach die Parameter. So sparen Sie Zeit, die Sie sonst für das Erfinden versteckter Pfade aufwenden müssten, die Ihren Code sicherlich nicht verbessern würden. +Anstatt versteckte Wege zu erfinden, über die sie irgendwie selbst an die Daten gelangen könnten, übergeben Sie einfach die Parameter. Sie sparen sich die Zeit, die für das Ausdenken versteckter Pfade benötigt wird, die Ihren Code definitiv nicht verbessern werden. -Wenn Sie diese Regel immer und überall befolgen, sind Sie auf dem Weg zu einem Code ohne versteckte Abhängigkeiten. Zu einem Code, der nicht nur für den Autor, sondern auch für jeden, der ihn später liest, verständlich ist. Wo alles aus den Signaturen von Funktionen und Klassen verständlich ist und man nicht nach versteckten Geheimnissen in der Implementierung suchen muss. +Wenn Sie diese Regel immer und überall befolgen, sind Sie auf dem Weg zu Code ohne versteckte Abhängigkeiten. Zu Code, der nicht nur für den Autor verständlich ist, sondern auch für jeden, der ihn nach ihm liest. Wo alles aus den Signaturen von Funktionen und Klassen verständlich ist und man nicht nach versteckten Geheimnissen in der Implementierung suchen muss. -Diese Technik wird in der Fachsprache **dependency injection** genannt. Und diese Daten werden **Abhängigkeiten** genannt. Es ist nur eine gewöhnliche Parameterübergabe, nichts weiter. +Diese Technik wird fachmännisch als **Dependency Injection** bezeichnet. Und diese Daten werden **Abhängigkeiten** genannt. Dabei handelt es sich um die einfache Übergabe von Parametern, nichts weiter. .[note] -Verwechseln Sie bitte nicht Dependency Injection, die ein Entwurfsmuster ist, mit einem "Dependency Injection Container", der ein Werkzeug ist, etwas diametral anderes. Wir werden uns später mit Containern beschäftigen. +Bitte verwechseln Sie Dependency Injection, ein Entwurfsmuster, nicht mit einem „Dependency Injection Container“, einem Werkzeug, also etwas völlig anderem. Container werden wir später behandeln. -Von Funktionen zu Klassen .[#toc-from-functions-to-classes] ------------------------------------------------------------ +Von Funktionen zu Klassen +------------------------- -Und wie hängen die Klassen zusammen? Eine Klasse ist eine komplexere Einheit als eine einfache Funktion, aber auch hier gilt Regel Nr. 1 uneingeschränkt. Es gibt einfach [mehr Möglichkeiten, Argumente |passing-dependencies] zu übergeben. Zum Beispiel, ganz ähnlich wie im Fall einer Funktion: +Und wie hängt das mit Klassen zusammen? Eine Klasse ist eine komplexere Einheit als eine einfache Funktion, aber Regel Nr. 1 gilt hier uneingeschränkt. Es gibt nur [mehr Möglichkeiten, Argumente zu übergeben |passing-dependencies]. Zum Beispiel ganz ähnlich wie im Fall einer Funktion: ```php -class Mathematik +class Matematika { - public function summe(float $a, float $b): float + public function soucet(float $a, float $b): float { return $a + $b; } } -$math = new Mathematik; -echo $math->summe(23, 1); // 24 +$math = new Matematika; +echo $math->soucet(23, 1); // 24 ``` -Oder durch andere Methoden, oder direkt durch den Konstruktor: +Oder über andere Methoden oder direkt über den Konstruktor: ```php -class Summe +class Soucet { public function __construct( private float $a, @@ -132,26 +132,26 @@ class Summe ) { } - public function calculate(): float + public function spocti(): float { return $this->a + $this->b; } } -$summe = new Summe(23, 1); -echo $summe->calculate(); // 24 +$soucet = new Soucet(23, 1); +echo $soucet->spocti(); // 24 ``` Beide Beispiele stehen vollständig im Einklang mit Dependency Injection. -Beispiele aus der Praxis .[#toc-real-life-examples] ---------------------------------------------------- +Reale Beispiele +--------------- -In der realen Welt werden Sie keine Klassen für die Addition von Zahlen schreiben. Kommen wir nun zu den praktischen Beispielen. +In der realen Welt werden Sie keine Klassen zum Addieren von Zahlen schreiben. Gehen wir zu Beispielen aus der Praxis über. -Nehmen wir eine Klasse `Article`, die einen Blogbeitrag darstellt: +Nehmen wir eine Klasse `Article`, die einen Blogartikel repräsentiert: ```php class Article @@ -162,7 +162,7 @@ class Article public function save(): void { - // speichert den Artikel in der Datenbank + // wir speichern den Artikel in der Datenbank } } ``` @@ -176,9 +176,9 @@ $article->content = 'Every year millions of people in ...'; $article->save(); ``` -Die Methode `save()` speichert den Artikel in einer Datenbanktabelle. Die Implementierung mit [Nette Database |database:] ist ein Kinderspiel, wenn es nicht ein Problem gäbe: Woher bekommt `Article` die Datenbankverbindung, d.h. ein Objekt der Klasse `Nette\Database\Connection`? +Die Methode `save()` speichert den Artikel in einer Datenbanktabelle. Die Implementierung mit [Nette Database |database:] wäre ein Kinderspiel, gäbe es nicht einen Haken: Woher nimmt `Article` die Datenbankverbindung, d.h. das Objekt der Klasse `Nette\Database\Connection`? -Es scheint, dass wir viele Möglichkeiten haben. Es kann die Verbindung von einer statischen Variable irgendwoher nehmen. Oder von einer Klasse erben, die eine Datenbankverbindung bereitstellt. Oder die Vorteile eines [Singletons |global-state#Singleton] nutzen. Oder sogenannte Fassaden verwenden, die in Laravel verwendet werden: +Es scheint, wir haben viele Möglichkeiten. Sie könnte sie irgendwoher aus einer statischen Variablen nehmen. Oder von einer Klasse erben, die die Datenbankverbindung bereitstellt. Oder das sogenannte [Singleton |global-state#Singleton] verwenden. Oder sogenannte Facades, wie sie in Laravel verwendet werden: ```php use Illuminate\Support\Facades\DB; @@ -199,17 +199,17 @@ class Article } ``` -Toll, wir haben das Problem gelöst. +Großartig, wir haben das Problem gelöst. -Oder haben wir das? +Oder nicht? -Erinnern wir uns an [Regel Nr. 1: "Let It Be Passed to You |#rule #1: Let It Be Passed to You]": Alle Abhängigkeiten, die die Klasse benötigt, müssen an sie weitergegeben werden. Denn wenn wir diese Regel brechen, haben wir uns auf einen Weg zu schmutzigem Code voller versteckter Abhängigkeiten und Unverständlichkeit begeben, und das Ergebnis wird eine Anwendung sein, die mühsam zu warten und zu entwickeln sein wird. +Erinnern wir uns an [#Regel Nr. 1: Lass es dir übergeben]: Alle Abhängigkeiten, die eine Klasse benötigt, müssen ihr übergeben werden. Denn wenn wir die Regel verletzen, haben wir den Weg zu schmutzigem Code voller versteckter Abhängigkeiten und Unverständlichkeit eingeschlagen, und das Ergebnis wird eine Anwendung sein, deren Wartung und Entwicklung mühsam sein wird. -Der Benutzer der Klasse `Article` hat keine Ahnung, wo die Methode `save()` den Artikel speichert. In einer Datenbanktabelle? In welcher, der Produktions- oder der Testtabelle? Und wie kann sie geändert werden? +Der Benutzer der Klasse `Article` weiß nicht, wohin die Methode `save()` den Artikel speichert. In eine Datenbanktabelle? In welche, die Produktiv- oder die Testdatenbank? Und wie kann man das ändern? -Der Benutzer muss sich ansehen, wie die Methode `save()` implementiert ist, und findet die Verwendung der Methode `DB::insert()`. Er muss also weiter suchen, um herauszufinden, wie diese Methode eine Datenbankverbindung herstellt. Und versteckte Abhängigkeiten können eine ziemlich lange Kette bilden. +Der Benutzer muss sich ansehen, wie die Methode `save()` implementiert ist, und findet die Verwendung der Methode `DB::insert()`. Also muss er weiter suchen, wie diese Methode die Datenbankverbindung beschafft. Und versteckte Abhängigkeiten können eine ziemlich lange Kette bilden. -In sauberem und gut durchdachtem Code gibt es niemals versteckte Abhängigkeiten, Laravel-Fassaden oder statische Variablen. In sauberem und gut durchdachtem Code werden Argumente übergeben: +In sauberem und gut entworfenem Code gibt es niemals versteckte Abhängigkeiten, Laravel-Facades oder statische Variablen. In sauberem und gut entworfenem Code werden Argumente übergeben: ```php class Article @@ -224,7 +224,7 @@ class Article } ``` -Ein noch praktischerer Ansatz ist, wie wir später sehen werden, die Verwendung des Konstruktors: +Noch praktischer, wie wir später sehen werden, ist es über den Konstruktor: ```php class Article @@ -245,11 +245,11 @@ class Article ``` .[note] -Wenn Sie ein erfahrener Programmierer sind, denken Sie vielleicht, dass `Article` überhaupt keine Methode `save()` haben sollte; es sollte eine reine Datenkomponente darstellen, und ein separates Repository sollte sich um das Speichern kümmern. Das macht Sinn. Aber das würde weit über den Rahmen dieses Themas hinausgehen, das sich mit der Injektion von Abhängigkeiten befasst, und den Versuch, einfache Beispiele zu liefern. +Wenn Sie ein erfahrener Programmierer sind, denken Sie vielleicht, dass `Article` überhaupt keine `save()`-Methode haben sollte, sondern eine reine Datenkomponente sein sollte und die Speicherung von einem separaten Repository übernommen werden sollte. Das macht Sinn. Aber damit würden wir weit über das Thema Dependency Injection hinausgehen und das Bemühen, einfache Beispiele zu geben, sprengen. -Wenn Sie eine Klasse schreiben, die zum Beispiel eine Datenbank für ihren Betrieb benötigt, erfinden Sie nicht, woher Sie diese bekommen, sondern lassen Sie sie übergeben. Entweder als Parameter des Konstruktors oder einer anderen Methode. Geben Sie Abhängigkeiten zu. Geben Sie sie in der API Ihrer Klasse an. Sie werden verständlichen und vorhersehbaren Code erhalten. +Wenn Sie eine Klasse schreiben, die für ihre Tätigkeit z. B. eine Datenbank benötigt, überlegen Sie nicht, woher Sie sie bekommen, sondern lassen Sie sie sich übergeben. Zum Beispiel als Parameter des Konstruktors oder einer anderen Methode. Geben Sie Abhängigkeiten zu. Geben Sie sie in der API Ihrer Klasse zu. Sie erhalten verständlichen und vorhersagbaren Code. -Und was ist mit dieser Klasse, die Fehlermeldungen protokolliert? +Und was ist mit dieser Klasse, die Fehlermeldungen protokolliert: ```php class Logger @@ -262,21 +262,21 @@ class Logger } ``` -Was meinen Sie, haben wir die [Regel Nr. 1: Lass es dir übergeben |#rule #1: Let It Be Passed to You]: Es wird an Sie weitergegeben? +Was meinen Sie, haben wir [#Regel Nr. 1: Lass es dir übergeben] eingehalten? -Wir haben es nicht getan. +Nein, haben wir nicht. -Die Schlüsselinformation, d.h. das Verzeichnis mit der Protokolldatei, wird von der Klasse selbst aus der Konstante *erhalten*. +Die Schlüsselinformation, nämlich das Verzeichnis mit der Logdatei, beschafft sich die Klasse *selbst* aus einer Konstante. -Sehen Sie sich das Beispiel für die Verwendung an: +Sehen Sie sich das Anwendungsbeispiel an: ```php $logger = new Logger; -$logger->log('The temperature is 23 °C'); -$logger->log('The temperature is 10 °C'); +$logger->log('Temperatur ist 23 °C'); +$logger->log('Temperatur ist 10 °C'); ``` -Können Sie, ohne die Implementierung zu kennen, die Frage beantworten, wo die Nachrichten geschrieben werden? Würden Sie vermuten, dass das Vorhandensein der Konstante `LOG_DIR` für das Funktionieren des Programms notwendig ist? Und könnten Sie eine zweite Instanz erstellen, die an einen anderen Ort schreibt? Sicherlich nicht. +Ohne Kenntnis der Implementierung, könnten Sie die Frage beantworten, wohin die Nachrichten geschrieben werden? Wäre Ihnen eingefallen, dass für die Funktion die Existenz der Konstante `LOG_DIR` erforderlich ist? Und könnten Sie eine zweite Instanz erstellen, die woanders hinschreibt? Sicher nicht. Lassen Sie uns die Klasse korrigieren: @@ -295,24 +295,24 @@ class Logger } ``` -Die Klasse ist jetzt viel verständlicher, konfigurierbar und daher nützlicher. +Die Klasse ist jetzt viel verständlicher, konfigurierbarer und daher nützlicher. ```php -$logger = new Logger('/path/to/log.txt'); -$logger->log('The temperature is 15 °C'); +$logger = new Logger('/pfad/zum/log.txt'); +$logger->log('Temperatur ist 15 °C'); ``` -Aber das ist mir egal! .[#toc-but-i-don-t-care] ------------------------------------------------ +Aber das interessiert mich nicht! +--------------------------------- -*"Wenn ich ein Artikel-Objekt erstelle und save() aufrufe, möchte ich mich nicht mit der Datenbank befassen; ich möchte nur, dass es in der Datenbank gespeichert wird, die ich in der Konfiguration eingestellt habe."* +*„Wenn ich ein Article-Objekt erstelle und save() aufrufe, will ich mich nicht um die Datenbank kümmern, ich will einfach, dass es in die Datenbank gespeichert wird, die ich in der Konfiguration eingestellt habe.“* -*"Wenn ich Logger verwende, möchte ich nur, dass die Nachricht geschrieben wird, und ich möchte mich nicht darum kümmern, wo. Es sollen die globalen Einstellungen verwendet werden."* +*„Wenn ich Logger verwende, will ich einfach, dass die Nachricht geschrieben wird, und ich will mich nicht darum kümmern, wohin. Es soll die globale Einstellung verwendet werden.“* -Dies sind berechtigte Einwände. +Das sind berechtigte Einwände. -Betrachten wir als Beispiel eine Klasse, die Newsletter versendet und protokolliert, wie es gelaufen ist: +Als Beispiel zeigen wir eine Klasse, die Newsletter versendet und protokolliert, wie es gelaufen ist: ```php class NewsletterDistributor @@ -322,27 +322,27 @@ class NewsletterDistributor $logger = new Logger(/* ... */); try { $this->sendEmails(); - $logger->log('Emails have been sent out'); + $logger->log('E-Mails wurden versendet'); } catch (Exception $e) { - $logger->log('An error occurred during the sending'); + $logger->log('Fehler beim Versenden aufgetreten'); throw $e; } } } ``` -Bei der verbesserten Version `Logger`, die nicht mehr die Konstante `LOG_DIR` verwendet, muss der Dateipfad im Konstruktor angegeben werden. Wie lässt sich das Problem lösen? Der Klasse `NewsletterDistributor` ist es egal, wohin die Nachrichten geschrieben werden; sie will sie einfach nur schreiben. +Der verbesserte `Logger`, der die Konstante `LOG_DIR` nicht mehr verwendet, erfordert im Konstruktor die Angabe des Dateipfads. Wie löst man das? Die Klasse `NewsletterDistributor` interessiert sich überhaupt nicht dafür, wohin die Nachrichten geschrieben werden, sie will sie nur schreiben. -Die Lösung ist wieder [Regel Nr. 1: Lass sie dir übergeben |#rule #1: Let It Be Passed to You]: Übergeben Sie alle Daten, die die Klasse benötigt. +Die Lösung ist wieder [#Regel Nr. 1: Lass es dir übergeben]: Alle Daten, die die Klasse benötigt, übergeben wir ihr. -Heißt das also, dass wir den Pfad zum Protokoll über den Konstruktor übergeben, den wir dann bei der Erstellung des `Logger` Objekts verwenden? +Bedeutet das also, dass wir uns den Pfad zum Log über den Konstruktor übergeben, den wir dann beim Erstellen des `Logger`-Objekts verwenden? ```php class NewsletterDistributor { public function __construct( - private string $file, // ⛔ NICHT AUF DIESE WEISE! + private string $file, // ⛔ SO NICHT! ) { } @@ -351,7 +351,7 @@ class NewsletterDistributor $logger = new Logger($this->file); ``` -Nein, nicht auf diese Weise! Der Pfad gehört nicht zu den Daten, die die Klasse `NewsletterDistributor` braucht, sondern die Klasse `Logger` braucht ihn. Verstehen Sie den Unterschied? Die Klasse `NewsletterDistributor` braucht den Logger selbst. Das ist es also, was wir übergeben: +So nicht! Der Pfad gehört nämlich **nicht** zu den Daten, die die Klasse `NewsletterDistributor` benötigt; diese benötigt der `Logger`. Erkennen Sie den Unterschied? Die Klasse `NewsletterDistributor` benötigt den Logger als solchen. Also übergeben wir uns diesen: ```php class NewsletterDistributor @@ -365,35 +365,33 @@ class NewsletterDistributor { try { $this->sendEmails(); - $this->logger->log('Emails have been sent out'); + $this->logger->log('E-Mails wurden versendet'); } catch (Exception $e) { - $this->logger->log('An error occurred during the sending'); + $this->logger->log('Fehler beim Versenden aufgetreten'); throw $e; } } } ``` -Nun geht aus den Signaturen der Klasse `NewsletterDistributor` hervor, dass auch die Protokollierung zu ihrer Funktionalität gehört. Und die Aufgabe, den Logger gegen einen anderen auszutauschen, etwa zu Testzwecken, ist völlig trivial. -Wenn sich außerdem der Konstruktor der Klasse `Logger` ändert, hat dies keine Auswirkungen auf unsere Klasse. +Nun ist aus den Signaturen der Klasse `NewsletterDistributor` klar, dass auch Logging Teil ihrer Funktionalität ist. Und die Aufgabe, den Logger gegen einen anderen auszutauschen, z. B. zum Testen, ist völlig trivial. Außerdem: Wenn sich der Konstruktor der Klasse `Logger` ändern würde, hätte dies keinerlei Auswirkungen auf unsere Klasse. -Regel Nr. 2: Nimm, was dir gehört .[#toc-rule-2-take-what-s-yours] ------------------------------------------------------------------- +Regel Nr. 2: Nimm, was deins ist +-------------------------------- -Lassen Sie sich nicht in die Irre führen und lassen Sie sich nicht die Abhängigkeiten von Ihren Abhängigen geben. Übergeben Sie nur Ihre eigenen Abhängigkeiten. +Lassen Sie sich nicht täuschen und lassen Sie sich nicht die Abhängigkeiten Ihrer Abhängigkeiten übergeben. Lassen Sie sich nur Ihre eigenen Abhängigkeiten übergeben. -Dadurch wird der Code, der andere Objekte verwendet, völlig unabhängig von Änderungen in deren Konstruktoren. Seine API wird wahrheitsgetreuer sein. Und vor allem wird es trivial sein, diese Abhängigkeiten durch andere zu ersetzen. +Dank dessen wird der Code, der andere Objekte verwendet, völlig unabhängig von Änderungen an deren Konstruktoren. Seine API wird wahrheitsgetreuer sein. Und vor allem wird es trivial sein, diese Abhängigkeiten gegen andere auszutauschen. -Neues Familienmitglied .[#toc-new-family-member] ------------------------------------------------- +Neues Familienmitglied +---------------------- -Das Entwicklungsteam beschloss, einen zweiten Logger zu erstellen, der in die Datenbank schreibt. Also erstellen wir eine `DatabaseLogger` Klasse. Wir haben also zwei Klassen, `Logger` und `DatabaseLogger`, eine schreibt in eine Datei, die andere in eine Datenbank ... kommt Ihnen die Namensgebung nicht seltsam vor? -Wäre es nicht besser, `Logger` in `FileLogger` umzubenennen? Eindeutig ja. +Im Entwicklungsteam wurde beschlossen, einen zweiten Logger zu erstellen, der in die Datenbank schreibt. Wir erstellen also die Klasse `DatabaseLogger`. Wir haben also zwei Klassen, `Logger` und `DatabaseLogger`, eine schreibt in eine Datei, die andere in die Datenbank … scheint Ihnen an dieser Benennung nicht etwas seltsam? Wäre es nicht besser, `Logger` in `FileLogger` umzubenennen? Sicherlich ja. -Aber lassen Sie uns das auf intelligente Weise tun. Wir erstellen eine Schnittstelle unter dem ursprünglichen Namen: +Aber wir machen es clever. Unter dem ursprünglichen Namen erstellen wir eine Schnittstelle: ```php interface Logger @@ -402,7 +400,7 @@ interface Logger } ``` -... die beide Logger implementieren werden: +… die beide Logger implementieren werden: ```php class FileLogger implements Logger @@ -412,17 +410,17 @@ class DatabaseLogger implements Logger // ... ``` -Daher muss im restlichen Code, in dem der Logger verwendet wird, nichts geändert werden. Zum Beispiel wird der Konstruktor der Klasse `NewsletterDistributor` immer noch damit zufrieden sein, `Logger` als Parameter zu benötigen. Und es bleibt uns überlassen, welche Instanz wir übergeben. +Und dank dessen muss im Rest des Codes, wo der Logger verwendet wird, nichts geändert werden. Zum Beispiel wird der Konstruktor der Klasse `NewsletterDistributor` weiterhin damit zufrieden sein, dass er als Parameter `Logger` benötigt. Und es liegt nur an uns, welche Instanz wir ihm übergeben. -**Deshalb fügen wir den Schnittstellennamen niemals das Suffix `Interface` oder das Präfix `I` hinzu.** Sonst wäre es nicht möglich, den Code so schön zu entwickeln. +**Deshalb geben wir Schnittstellennamen niemals das Suffix `Interface` oder das Präfix `I`.** Sonst wäre es nicht möglich, den Code so schön weiterzuentwickeln. -Houston, wir haben ein Problem .[#toc-houston-we-have-a-problem] ----------------------------------------------------------------- +Houston, wir haben ein Problem +------------------------------ -Während wir mit einer einzigen Instanz des Loggers, egal ob datei- oder datenbankbasiert, in der gesamten Anwendung auskommen und sie einfach überall dort übergeben können, wo etwas protokolliert wird, verhält es sich bei der Klasse `Article` ganz anders. Wir erzeugen ihre Instanzen je nach Bedarf, sogar mehrfach. Wie geht man mit der Datenbankabhängigkeit in ihrem Konstruktor um? +Während wir in der gesamten Anwendung mit einer einzigen Instanz des Loggers auskommen können, sei es datei- oder datenbankbasiert, und ihn einfach überall dorthin übergeben, wo etwas protokolliert wird, ist es bei der Klasse `Article` ganz anders. Ihre Instanzen erstellen wir nach Bedarf, gerne auch mehrmals. Wie gehen wir mit der Abhängigkeit zur Datenbank in ihrem Konstruktor um? -Ein Beispiel kann ein Controller sein, der nach dem Absenden eines Formulars einen Artikel in der Datenbank speichern soll: +Als Beispiel kann ein Controller dienen, der nach dem Absenden eines Formulars einen Artikel in der Datenbank speichern soll: ```php class EditController extends Controller @@ -437,30 +435,30 @@ class EditController extends Controller } ``` -Eine mögliche Lösung liegt auf der Hand: Übergeben Sie das Datenbankobjekt an den `EditController` Konstruktor und verwenden Sie `$article = new Article($this->db)`. +Eine mögliche Lösung bietet sich direkt an: Wir lassen uns das Datenbankobjekt über den Konstruktor in `EditController` übergeben und verwenden `$article = new Article($this->db)`. -Genau wie im vorherigen Fall mit `Logger` und dem Dateipfad ist dies nicht der richtige Ansatz. Die Datenbank ist keine Abhängigkeit von `EditController`, sondern von `Article`. Die Übergabe der Datenbank verstößt gegen [Regel #2: Nimm, was dir gehört |#rule #2: take what's yours]. Wenn sich der Konstruktor der Klasse `Article` ändert (ein neuer Parameter wird hinzugefügt), müssen Sie den Code überall dort ändern, wo Instanzen erzeugt werden. Ufff. +Genau wie im vorherigen Fall mit `Logger` und dem Dateipfad ist dies nicht der richtige Ansatz. Die Datenbank ist keine Abhängigkeit von `EditController`, sondern von `Article`. Sich die Datenbank übergeben zu lassen, verstößt also gegen [#Regel Nr. 2: Nimm, was deins ist]. Wenn sich der Konstruktor der Klasse `Article` ändert (ein neuer Parameter kommt hinzu), muss auch der Code an allen Stellen angepasst werden, an denen Instanzen erstellt werden. Uff. Houston, was schlagen Sie vor? -Regel Nr. 3: Überlassen Sie die Abwicklung der Fabrik .[#toc-rule-3-let-the-factory-handle-it] ----------------------------------------------------------------------------------------------- +Regel Nr. 3: Überlasse es der Fabrik +------------------------------------ -Durch die Beseitigung versteckter Abhängigkeiten und die Übergabe aller Abhängigkeiten als Argumente haben wir mehr konfigurierbare und flexible Klassen erhalten. Und deshalb brauchen wir etwas anderes, um diese flexibleren Klassen für uns zu erstellen und zu konfigurieren. Wir werden es Fabriken nennen. +Dadurch, dass wir versteckte Abhängigkeiten beseitigt und alle Abhängigkeiten als Argumente übergeben haben, haben wir konfigurierbarere und flexiblere Klassen erhalten. Und daher brauchen wir noch etwas anderes, das uns diese flexibleren Klassen erstellt und konfiguriert. Wir nennen es Fabriken. -Die Faustregel lautet: Wenn eine Klasse Abhängigkeiten hat, überlassen Sie die Erstellung ihrer Instanzen der Fabrik. +Die Regel lautet: Wenn eine Klasse Abhängigkeiten hat, überlasse die Erstellung ihrer Instanzen einer Fabrik. -Fabriken sind ein intelligenter Ersatz für den `new` Operator in der Welt der Dependency Injection. +Fabriken sind der clevere Ersatz für den `new`-Operator in der Welt der Dependency Injection. .[note] -Nicht zu verwechseln mit dem Entwurfsmuster *Fabrikmethode*, das eine spezielle Art der Verwendung von Fabriken beschreibt und nichts mit diesem Thema zu tun hat. +Bitte verwechseln Sie dies nicht mit dem Entwurfsmuster *Factory Method*, das eine spezifische Art der Verwendung von Fabriken beschreibt und mit diesem Thema nichts zu tun hat. -Fabrik .[#toc-factory] ----------------------- +Fabrik +------ -Eine Fabrik ist eine Methode oder Klasse, die Objekte erstellt und konfiguriert. Wir werden die Klasse, die `Article` erzeugt, `ArticleFactory` nennen, und sie könnte wie folgt aussehen: +Eine Fabrik ist eine Methode oder Klasse, die Objekte herstellt und konfiguriert. Die Klasse, die `Article` herstellt, nennen wir `ArticleFactory` und sie könnte beispielsweise so aussehen: ```php class ArticleFactory @@ -477,7 +475,7 @@ class ArticleFactory } ``` -Die Verwendung im Controller sieht folgendermaßen aus: +Ihre Verwendung im Controller wird wie folgt sein: ```php class EditController extends Controller @@ -489,7 +487,7 @@ class EditController extends Controller public function formSubmitted($data) { - // die Fabrik ein Objekt erstellen lassen + // lassen wir die Fabrik das Objekt erstellen $article = $this->articleFactory->create(); $article->title = $data->title; $article->content = $data->content; @@ -498,11 +496,11 @@ class EditController extends Controller } ``` -Wenn sich nun die Signatur des Konstruktors der Klasse `Article` ändert, ist der einzige Teil des Codes, der darauf reagieren muss, der `ArticleFactory` selbst. Alle anderen Codes, die mit `Article` Objekten arbeiten, wie z.B. `EditController`, sind davon nicht betroffen. +Wenn sich zu diesem Zeitpunkt die Signatur des Konstruktors der Klasse `Article` ändert, ist der einzige Teil des Codes, der darauf reagieren muss, die Fabrik `ArticleFactory` selbst. Aller anderer Code, der mit `Article`-Objekten arbeitet, wie zum Beispiel `EditController`, bleibt davon unberührt. -Sie werden sich vielleicht fragen, ob wir die Dinge tatsächlich besser gemacht haben. Die Menge des Codes hat zugenommen, und das Ganze sieht verdächtig kompliziert aus. +Vielleicht klopfen Sie sich jetzt an die Stirn, ob wir uns überhaupt geholfen haben. Die Menge an Code ist gewachsen und das Ganze beginnt verdächtig kompliziert auszusehen. -Keine Sorge, bald werden wir zum Nette-DI-Container kommen. Und der hat einige Tricks in petto, die das Erstellen von Anwendungen mit Dependency Injection erheblich vereinfachen werden. Zum Beispiel müssen Sie anstelle der Klasse `ArticleFactory` nur [eine einfache Schnittstelle schreiben |factory]: +Keine Sorge, wir kommen gleich zum Nette DI Container. Und der hat eine Reihe von Assen im Ärmel, die den Bau von Anwendungen, die Dependency Injection verwenden, enorm vereinfachen. So wird beispielsweise anstelle der Klasse `ArticleFactory` nur [das Schreiben einer reinen Schnittstelle |factory] ausreichen: ```php interface ArticleFactory @@ -511,18 +509,18 @@ interface ArticleFactory } ``` -Aber wir greifen uns selbst vor; bitte haben Sie noch etwas Geduld :-) +Aber das greifen wir vor, bleiben Sie noch dran :-) -Zusammenfassung .[#toc-summary] -------------------------------- +Zusammenfassung +--------------- -Zu Beginn dieses Kapitels haben wir versprochen, Ihnen einen Prozess zur Entwicklung von sauberem Code zu zeigen. Alles, was es braucht, ist, dass die Klassen: +Am Anfang dieses Kapitels haben wir versprochen, Ihnen einen Ansatz zu zeigen, wie man sauberen Code entwirft. Es genügt, den Klassen -- [die Abhängigkeiten zu übergeben, die sie benötigen |#Rule #1: Let It Be Passed to You] -- [umgekehrt nicht übergeben, was sie nicht direkt brauchen |#Rule #2: Take What's Yours] -- [und dass Objekte mit Abhängigkeiten am besten in Fabriken erstellt werden |#Rule #3: Let the Factory Handle it] +1) [die Abhängigkeiten zu übergeben, die sie benötigen |#Regel Nr. 1: Lass es dir übergeben] +2) [und umgekehrt nicht das zu übergeben, was sie nicht direkt benötigen |#Regel Nr. 2: Nimm was deins ist] +3) [und dass Objekte mit Abhängigkeiten am besten in Fabriken hergestellt werden |#Regel Nr. 3: Überlasse es der Fabrik] -Auf den ersten Blick scheinen diese drei Regeln keine weitreichenden Konsequenzen zu haben, aber sie führen zu einer radikal anderen Sichtweise des Codeentwurfs. Ist es das wert? Entwickler, die alte Gewohnheiten aufgegeben und mit der konsequenten Nutzung von Dependency Injection begonnen haben, betrachten diesen Schritt als einen entscheidenden Moment in ihrem Berufsleben. Er hat ihnen die Welt der klaren und wartbaren Anwendungen eröffnet. +Es mag auf den ersten Blick nicht so erscheinen, aber diese drei Regeln haben weitreichende Konsequenzen. Sie führen zu einer radikal anderen Sichtweise auf das Code-Design. Lohnt es sich? Programmierer, die alte Gewohnheiten aufgegeben haben und konsequent Dependency Injection verwenden, betrachten diesen Schritt als einen entscheidenden Moment in ihrer beruflichen Laufbahn. Es öffnete sich ihnen die Welt übersichtlicher und wartbarer Anwendungen. -Was aber, wenn der Code nicht konsequent Dependency Injection verwendet? Was ist, wenn er sich auf statische Methoden oder Singletons stützt? Verursacht das Probleme? [Ja, das tut es, und zwar ganz grundlegende |global-state]. +Was aber, wenn der Code Dependency Injection nicht konsequent verwendet? Was, wenn er auf statischen Methoden oder Singletons basiert? Bringt das irgendwelche Probleme mit sich? [Ja, und zwar sehr grundlegende |global-state]. diff --git a/dependency-injection/de/nette-container.texy b/dependency-injection/de/nette-container.texy index adc497aafc..872434616a 100644 --- a/dependency-injection/de/nette-container.texy +++ b/dependency-injection/de/nette-container.texy @@ -2,9 +2,9 @@ Nette DI Container ****************** .[perex] -Nette DI ist eine der interessantesten Nette-Bibliotheken. Sie kann kompilierte DI-Container erzeugen und automatisch aktualisieren, die extrem schnell und erstaunlich einfach zu konfigurieren sind. +Nette DI ist eine der interessantesten Bibliotheken von Nette. Sie kann kompilierte DI-Container generieren und automatisch aktualisieren, die extrem schnell und erstaunlich einfach zu konfigurieren sind. -Die von einem DI-Container zu erstellenden Dienste werden in der Regel über Konfigurationsdateien im [NEON-Format |neon:format] definiert. Der Container, den wir im [vorigen Abschnitt |container] manuell erstellt haben, würde wie folgt geschrieben werden: +Die Form der Dienste, die der DI-Container erstellen soll, definieren wir normalerweise mithilfe von Konfigurationsdateien im [NEON-Format|neon:format]. Der Container, den wir im [vorherigen Kapitel|container] manuell erstellt haben, würde so geschrieben werden: ```neon parameters: @@ -19,16 +19,15 @@ services: - UserController ``` -Die Notation ist wirklich kurz. +Die Notation ist sehr prägnant. -Alle Abhängigkeiten, die in den Konstruktoren der Klassen `ArticleFactory` und `UserController` deklariert sind, werden von Nette DI selbst gefunden und weitergegeben, dank des so genannten [Autowiring |autowiring], so dass nichts in der Konfigurationsdatei angegeben werden muss. -Selbst wenn sich also die Parameter ändern, müssen Sie nichts in der Konfiguration ändern. Nette wird den Container automatisch neu generieren. Sie können sich also ganz auf die Anwendungsentwicklung konzentrieren. +Alle in den Konstruktoren der Klassen `ArticleFactory` und `UserController` deklarierten Abhängigkeiten findet Nette DI selbst heraus und übergibt sie dank des sogenannten [Autowiring|autowiring]. Daher muss in der Konfigurationsdatei nichts angegeben werden. Selbst wenn sich die Parameter ändern, müssen Sie in der Konfiguration nichts ändern. Der Nette-Container wird automatisch neu generiert. Sie können sich dort ganz auf die Entwicklung der Anwendung konzentrieren. -Wenn Sie Abhängigkeiten mit Hilfe von Settern übergeben wollen, verwenden Sie dazu den [Setup-Abschnitt |services#setup]. +Wenn wir Abhängigkeiten über Setter übergeben möchten, verwenden wir dazu den Abschnitt [setup |services#Setup]. -Nette DI generiert direkt den PHP-Code für den Container. Das Ergebnis ist also eine `.php` Datei, die Sie öffnen und studieren können. So können Sie genau sehen, wie der Container funktioniert. Sie können ihn auch in der IDE debuggen und Schritt für Schritt durchgehen. Und das Wichtigste: das generierte PHP ist extrem schnell. +Nette DI generiert direkt PHP-Code für den Container. Das Ergebnis ist also eine `.php`-Datei, die Sie öffnen und studieren können. Dadurch sehen Sie genau, wie der Container funktioniert. Sie können ihn auch in der IDE debuggen und schrittweise durchgehen. Und vor allem: Das generierte PHP ist extrem schnell. -Nette DI kann auch [Factory-Code |factory] auf der Grundlage der bereitgestellten Schnittstelle generieren. Anstelle der Klasse `ArticleFactory` müssen wir also nur eine Schnittstelle in der Anwendung erstellen: +Nette DI kann auch Code für [Fabriken|factory] basierend auf einer bereitgestellten Schnittstelle generieren. Anstelle der Klasse `ArticleFactory` reicht es uns daher aus, in der Anwendung nur eine Schnittstelle zu erstellen: ```php interface ArticleFactory @@ -37,19 +36,19 @@ interface ArticleFactory } ``` -Das vollständige Beispiel finden Sie [auf GitHub |https://github.com/nette-examples/di-example-doc]. +Das vollständige Beispiel finden Sie [auf GitHub|https://github.com/nette-examples/di-example-doc]. -Eigenständige Verwendung .[#toc-standalone-use] ------------------------------------------------ +Eigenständige Verwendung +------------------------ -Die Verwendung der Nette DI-Bibliothek in einer Anwendung ist sehr einfach. Zuerst installieren wir sie mit Composer (weil das Herunterladen von Zip-Dateien so veraltet ist): +Der Einsatz der Nette DI-Bibliothek in einer Anwendung ist sehr einfach. Zuerst installieren wir sie mit Composer (denn das Herunterladen von ZIP-Dateien ist sooo veraltet): ```shell composer require nette/di ``` -Der folgende Code erzeugt eine Instanz des DI-Containers entsprechend der in der Datei `config.neon` gespeicherten Konfiguration: +Der folgende Code erstellt eine Instanz des DI-Containers gemäß der Konfiguration, die in der Datei `config.neon` gespeichert ist: ```php $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp'); @@ -59,24 +58,23 @@ $class = $loader->load(function ($compiler) { $container = new $class; ``` -Der Container wird nur einmal erzeugt, sein Code wird in den Cache (das Verzeichnis `__DIR__ . '/temp'` ) geschrieben und bei nachfolgenden Anfragen nur von dort gelesen. +Der Container wird nur einmal generiert, sein Code wird im Cache (Verzeichnis `__DIR__ . '/temp'`) gespeichert und bei weiteren Anfragen nur noch von dort geladen. -Die Methoden `getService()` oder `getByType()` werden verwendet, um Dienste zu erstellen und abzurufen. So erstellen wir das Objekt `UserController`: +Zum Erstellen und Abrufen von Diensten dienen die Methoden `getService()` oder `getByType()`. So erstellen wir das `UserController`-Objekt: ```php -$database = $container->getByType(UserController::class); -$database->query('...'); +$controller = $container->getByType(UserController::class); +$controller->someMethod(); ``` -Während der Entwicklung ist es nützlich, den Auto-Refresh-Modus zu aktivieren, bei dem der Container automatisch neu generiert wird, wenn eine Klasse oder eine Konfigurationsdatei geändert wird. Geben Sie einfach `true` als zweites Argument im `ContainerLoader` Konstruktor an. +Während der Entwicklung ist es nützlich, den Auto-Refresh-Modus zu aktivieren, bei dem der Container automatisch neu generiert wird, wenn sich eine Klasse oder Konfigurationsdatei ändert. Geben Sie einfach im Konstruktor von `ContainerLoader` als zweites Argument `true` an. ```php $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp', true); ``` -Verwendung mit dem Nette Framework .[#toc-using-it-with-the-nette-framework] ----------------------------------------------------------------------------- +Verwendung mit dem Nette Framework +---------------------------------- -Wie wir gezeigt haben, ist die Verwendung von Nette DI nicht auf Anwendungen beschränkt, die im Nette Framework geschrieben wurden. Sie können es überall mit nur 3 Zeilen Code einsetzen. -Wenn Sie jedoch Anwendungen im Nette Framework entwickeln, wird die Konfiguration und Erstellung des Containers von [Bootstrap |application:bootstrap#toc-di-container-configuration] übernommen. +Wie wir gezeigt haben, ist die Verwendung von Nette DI nicht auf Anwendungen beschränkt, die im Nette Framework geschrieben wurden; Sie können es mit nur 3 Zeilen Code überall einsetzen. Wenn Sie jedoch Anwendungen im Nette Framework entwickeln, ist der [Bootstrap |application:bootstrapping#Konfiguration des DI-Containers] für die Konfiguration und Erstellung des Containers verantwortlich. diff --git a/dependency-injection/de/passing-dependencies.texy b/dependency-injection/de/passing-dependencies.texy index c5111a38be..cc5a8ed7ee 100644 --- a/dependency-injection/de/passing-dependencies.texy +++ b/dependency-injection/de/passing-dependencies.texy @@ -1,24 +1,24 @@ -Übergabe von Abhängigkeiten -*************************** +Übergeben von Abhängigkeiten +**************************** <div class=perex> -Argumente, oder "Abhängigkeiten" in der DI-Terminologie, können auf die folgenden Hauptwege an Klassen übergeben werden: +Argumente, oder in der DI-Terminologie „Abhängigkeiten“, können auf folgende Hauptarten an Klassen übergeben werden: * Übergabe per Konstruktor -* Übergabe durch eine Methode (Setter genannt) -* durch das Setzen einer Eigenschaft -* durch Methode, Annotation oder Attribut *inject* +* Übergabe per Methode (sog. Setter) +* Zuweisung zu einer Variablen +* Methode, Annotation oder Attribut *inject* </div> -Wir werden nun die verschiedenen Varianten mit konkreten Beispielen illustrieren. +Nun werden wir die einzelnen Varianten anhand konkreter Beispiele erläutern. -Konstruktor-Injektion .[#toc-constructor-injection] -=================================================== +Übergabe per Konstruktor +======================== -Abhängigkeiten werden als Argumente an den Konstruktor übergeben, wenn das Objekt erstellt wird: +Abhängigkeiten werden im Moment der Objekterstellung als Argumente des Konstruktors übergeben: ```php class MyClass @@ -34,9 +34,9 @@ class MyClass $obj = new MyClass($cache); ``` -Diese Form ist nützlich für obligatorische Abhängigkeiten, die die Klasse unbedingt benötigt, um zu funktionieren, da ohne sie die Instanz nicht erstellt werden kann. +Diese Form eignet sich für obligatorische Abhängigkeiten, die die Klasse unbedingt für ihre Funktion benötigt, da ohne sie keine Instanz erstellt werden kann. -Seit PHP 8.0 können wir eine kürzere Form der Notation verwenden ([constructor property promotion |https://blog.nette.org/de/php-8-0-vollstaendiger-ueberblick-ueber-die-neuigkeiten#toc-constructor-property-promotion]), die funktional gleichwertig ist: +Seit PHP 8.0 können wir eine kürzere Schreibweise verwenden ([constructor property promotion |https://blog.nette.org/de/php-8-0-complete-overview-of-news#toc-constructor-property-promotion]), die funktional äquivalent ist: ```php // PHP 8.0 @@ -49,7 +49,7 @@ class MyClass } ``` -Seit PHP 8.1 kann eine Eigenschaft mit einem Flag `readonly` markiert werden, das besagt, dass sich der Inhalt der Eigenschaft nicht ändern wird: +Seit PHP 8.1 kann die Variable mit dem Flag `readonly` markiert werden, was deklariert, dass sich der Inhalt der Variablen nicht mehr ändern wird: ```php // PHP 8.1 @@ -62,13 +62,13 @@ class MyClass } ``` -Der DI-Container übergibt Abhängigkeiten automatisch an den Konstruktor mittels [Autowiring |autowiring]. Argumente, die nicht auf diese Weise übergeben werden können (z.B. Strings, Zahlen, Booleans), [werden in die Konfiguration geschrieben |services#Arguments]. +Der DI-Container übergibt Abhängigkeiten automatisch an den Konstruktor mittels [Autowiring |autowiring]. Argumente, die auf diese Weise nicht übergeben werden können (z. B. Zeichenketten, Zahlen, Booleans), [schreiben wir in die Konfiguration |services#Argumente]. -Konstrukteur-Hölle .[#toc-constructor-hell] -------------------------------------------- +Constructor Hell +---------------- -Der Begriff *Konstruktorhölle* bezieht sich auf eine Situation, in der ein Kind von einer Elternklasse erbt, deren Konstruktor Abhängigkeiten benötigt, und das Kind benötigt ebenfalls Abhängigkeiten. Es muss auch die Abhängigkeiten der Elternklasse übernehmen und weitergeben: +Der Begriff *Constructor Hell* bezeichnet eine Situation, in der eine Kindklasse von einer Elternklasse erbt, deren Konstruktor Abhängigkeiten erfordert, und gleichzeitig die Kindklasse Abhängigkeiten erfordert. Dabei muss sie auch die elterlichen übernehmen und übergeben: ```php abstract class BaseClass @@ -94,11 +94,11 @@ final class MyClass extends BaseClass } ``` -Das Problem tritt auf, wenn wir den Konstruktor der Klasse `BaseClass` ändern wollen, zum Beispiel wenn eine neue Abhängigkeit hinzugefügt wird. Dann müssen wir auch alle Konstruktoren der Kinder ändern. Das macht eine solche Änderung zur Hölle. +Das Problem tritt auf, wenn wir den Konstruktor der Klasse `BaseClass` ändern wollen, zum Beispiel wenn eine neue Abhängigkeit hinzukommt. Dann müssen nämlich auch alle Konstruktoren der Kindklassen angepasst werden. Was eine solche Anpassung zur Hölle macht. -Wie lässt sich das verhindern? Die Lösung besteht darin, **Komposition gegenüber Vererbung** zu bevorzugen. +Wie kann man dem vorbeugen? Die Lösung ist, **[Komposition der Vererbung vorzuziehen |faq#Warum wird Komposition der Vererbung vorgezogen]**. -Lassen Sie uns also den Code anders gestalten. Wir werden abstrakte `Base*` Klassen vermeiden. Anstatt dass `MyClass` eine bestimmte Funktionalität durch Vererbung von `BaseClass` erhält, wird diese Funktionalität als Abhängigkeit übergeben: +Also entwerfen wir den Code anders. Wir werden [abstrakte |nette:introduction-to-object-oriented-programming#Abstrakte Klassen] `Base*` Klassen vermeiden. Anstatt dass `MyClass` bestimmte Funktionalität durch Erben von `BaseClass` erhält, lässt sie sich diese Funktionalität als Abhängigkeit übergeben: ```php final class SomeFunctionality @@ -125,10 +125,10 @@ final class MyClass ``` -Setter-Injektion .[#toc-setter-injection] -========================================= +Übergabe per Setter +=================== -Abhängigkeiten werden durch den Aufruf einer Methode übergeben, die sie in einer privaten Eigenschaft speichert. Die übliche Namenskonvention für diese Methoden ist die Form `set*()`, weshalb sie auch Setter genannt werden, aber natürlich können sie auch anders heißen. +Abhängigkeiten werden durch Aufruf einer Methode übergeben, die sie in einer privaten Variablen speichert. Die übliche Namenskonvention für diese Methoden ist die Form `set*()`, daher werden sie Setter genannt, aber sie können natürlich auch anders heißen. ```php class MyClass @@ -145,9 +145,9 @@ $obj = new MyClass; $obj->setCache($cache); ``` -Diese Methode ist nützlich für optionale Abhängigkeiten, die für die Funktion der Klasse nicht notwendig sind, da nicht garantiert ist, dass das Objekt sie tatsächlich erhält (d. h. dass der Benutzer die Methode aufruft). +Diese Methode eignet sich für optionale Abhängigkeiten, die für die Funktion der Klasse nicht notwendig sind, da nicht garantiert ist, dass das Objekt die Abhängigkeit tatsächlich erhält (d. h. dass der Benutzer die Methode aufruft). -Gleichzeitig ermöglicht diese Methode, dass der Setter wiederholt aufgerufen werden kann, um die Abhängigkeit zu ändern. Wenn dies nicht erwünscht ist, fügen Sie der Methode ein Häkchen hinzu, oder markieren Sie ab PHP 8.1 die Eigenschaft `$cache` mit dem Flag `readonly`. +Gleichzeitig erlaubt diese Methode, den Setter wiederholt aufzurufen und die Abhängigkeit so zu ändern. Wenn dies nicht erwünscht ist, fügen wir der Methode eine Prüfung hinzu oder markieren ab PHP 8.1 die Eigenschaft `$cache` mit dem Flag `readonly`. ```php class MyClass @@ -156,7 +156,7 @@ class MyClass public function setCache(Cache $cache): void { - if ($this->cache) { + if (isset($this->cache)) { throw new RuntimeException('The dependency has already been set'); } $this->cache = $cache; @@ -164,21 +164,20 @@ class MyClass } ``` -Der Setter-Aufruf wird in der DI-Container-Konfiguration im [Abschnitt setup |services#Setup] definiert. Auch hier wird die automatische Übergabe von Abhängigkeiten durch Autowiring genutzt: +Der Aufruf des Setters wird in der Konfiguration des DI-Containers im [Schlüssel setup |services#Setup] definiert. Auch hier wird die automatische Übergabe von Abhängigkeiten mittels Autowiring genutzt: ```neon services: - - - create: MyClass + - create: MyClass setup: - setCache ``` -Property Injection .[#toc-property-injection] -============================================= +Zuweisung zu einer Variablen +============================ -Abhängigkeiten werden direkt an die Eigenschaft übergeben: +Abhängigkeiten werden durch Schreiben direkt in eine Mitgliedsvariable übergeben: ```php class MyClass @@ -190,28 +189,27 @@ $obj = new MyClass; $obj->cache = $cache; ``` -Diese Methode wird als ungeeignet angesehen, da die Eigenschaft als `public` deklariert werden muss. Daher haben wir keine Kontrolle darüber, ob die übergebene Abhängigkeit tatsächlich vom angegebenen Typ ist (dies war vor PHP 7.4 der Fall), und wir verlieren die Möglichkeit, auf die neu zugewiesene Abhängigkeit mit unserem eigenen Code zu reagieren, um zum Beispiel nachträgliche Änderungen zu verhindern. Gleichzeitig wird die Eigenschaft Teil der öffentlichen Schnittstelle der Klasse, was möglicherweise nicht wünschenswert ist. +Diese Methode wird als ungeeignet angesehen, da die Mitgliedsvariable als `public` deklariert werden muss. Dadurch haben wir keine Kontrolle darüber, dass die übergebene Abhängigkeit tatsächlich vom angegebenen Typ ist (galt vor PHP 7.4), und wir verlieren die Möglichkeit, auf die neu zugewiesene Abhängigkeit mit eigenem Code zu reagieren, beispielsweise um eine nachfolgende Änderung zu verhindern. Gleichzeitig wird die Variable Teil der öffentlichen Schnittstelle der Klasse, was möglicherweise nicht erwünscht ist. -Die Einstellung der Variablen wird in der Konfiguration des DI-Containers im [Abschnitt setup |services#Setup] festgelegt: +Die Zuweisung der Variablen wird in der Konfiguration des DI-Containers im [Abschnitt setup |services#Setup] definiert: ```neon services: - - - create: MyClass + - create: MyClass setup: - $cache = @\Cache ``` -Einspritzen .[#toc-inject] -========================== +Inject +====== -Während die drei vorangegangenen Methoden allgemein in allen objektorientierten Sprachen gültig sind, ist das Injizieren per Methode, Annotation oder *inject*-Attribut spezifisch für Nette-Präsentatoren. Sie werden in einem [separaten Kapitel |best-practices:inject-method-attribute] behandelt. +Während die vorherigen drei Methoden allgemein in allen objektorientierten Sprachen gelten, ist das Injizieren per Methode, Annotation oder Attribut *inject* spezifisch für Presenter in Nette. Ein [separates Kapitel |best-practices:inject-method-attribute] behandelt sie. -Welcher Weg soll gewählt werden? .[#toc-which-way-to-choose] -============================================================ +Welche Methode wählen? +====================== -- Der Konstruktor eignet sich für obligatorische Abhängigkeiten, die die Klasse zum Funktionieren benötigt. -- der Setter hingegen eignet sich für optionale oder veränderbare Abhängigkeiten -- öffentliche Variablen werden nicht empfohlen +- Der Konstruktor eignet sich für obligatorische Abhängigkeiten, die die Klasse unbedingt für ihre Funktion benötigt. +- Der Setter eignet sich hingegen für optionale Abhängigkeiten oder Abhängigkeiten, die man weiter ändern können möchte. +- Öffentliche Variablen sind nicht geeignet. diff --git a/dependency-injection/de/services.texy b/dependency-injection/de/services.texy index f4659b138d..7c80005bfb 100644 --- a/dependency-injection/de/services.texy +++ b/dependency-injection/de/services.texy @@ -1,33 +1,33 @@ -Dienst-Definitionen -******************* +Definition von Diensten +*********************** .[perex] -In der Konfiguration werden die Definitionen der benutzerdefinierten Dienste abgelegt. Dies geschieht im Abschnitt `services`. +Die Konfiguration ist der Ort, an dem wir dem DI-Container beibringen, wie er einzelne Dienste erstellen und sie mit anderen Abhängigkeiten verbinden soll. Nette bietet eine sehr übersichtliche und elegante Möglichkeit, dies zu erreichen. -So erstellen wir zum Beispiel einen Dienst namens `database`, der eine Instanz der Klasse `PDO` sein wird: +Der Abschnitt `services` in der Konfigurationsdatei im NEON-Format ist der Ort, an dem wir eigene Dienste und ihre Konfigurationen definieren. Sehen wir uns ein einfaches Beispiel für die Definition eines Dienstes namens `database` an, der eine Instanz der Klasse `PDO` repräsentiert: ```neon services: database: PDO('sqlite::memory:') ``` -Die Benennung von Diensten dient dazu, dass wir auf sie [verweisen |#Referencing Services] können. Wenn ein Dienst nicht referenziert wird, ist es nicht notwendig, ihn zu benennen. Wir verwenden also einfach einen Aufzählungspunkt anstelle eines Namens: +Die angegebene Konfiguration führt zu folgender Factory-Methode im [DI-Container|container]: -```neon -services: - - PDO('sqlite::memory:') # anonymer Dienst +```php +public function createServiceDatabase(): PDO +{ + return new PDO('sqlite::memory:'); +} ``` -Ein einzeiliger Eintrag kann in mehrere Zeilen aufgeteilt werden, um weitere Schlüssel hinzuzufügen, wie z. B. [setup |#setup]. Der Alias für die Taste `create:` lautet `factory:`. +Dienstnamen ermöglichen es uns, uns in anderen Teilen der Konfigurationsdatei im Format `@dienstName` darauf zu beziehen. Wenn es nicht notwendig ist, den Dienst zu benennen, können wir einfach einen Bindestrich verwenden: ```neon services: - database: - create: PDO('sqlite::memory:') - setup: ... + - PDO('sqlite::memory:') ``` -Anschließend rufen wir den Dienst aus dem DI-Container mit der Methode `getService()` nach dem Namen oder besser noch mit der Methode `getByType()` nach dem Typ ab: +Um einen Dienst aus dem DI-Container zu erhalten, können wir die Methode `getService()` mit dem Dienstnamen als Parameter oder die Methode `getByType()` mit dem Diensttyp verwenden: ```php $database = $container->getService('database'); @@ -35,26 +35,28 @@ $database = $container->getByType(PDO::class); ``` -Erstellen eines Dienstes .[#toc-creating-a-service] -=================================================== +Erstellung eines Dienstes +========================= -Meistens erstellen wir einen Dienst, indem wir einfach eine Instanz einer Klasse erstellen: +Meistens erstellen wir einen Dienst einfach durch Instanziierung einer bestimmten Klasse. Zum Beispiel: ```neon services: database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -Dadurch wird eine Fabrikmethode im [DI-Container |container] erzeugt: +Wenn wir die Konfiguration um weitere Schlüssel erweitern müssen, kann die Definition auf mehrere Zeilen aufgeteilt werden: -```php -public function createServiceDatabase(): PDO -{ - return new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'secret'); -} +```neon +services: + database: + create: PDO('sqlite::memory:') + setup: ... ``` -Alternativ kann auch ein Schlüssel `arguments` verwendet werden, um [Argumente |#Arguments] zu übergeben: +Der Schlüssel `create` hat den Alias `factory`, beide Varianten sind in der Praxis üblich. Wir empfehlen jedoch die Verwendung von `create`. + +Die Argumente des Konstruktors oder der Erstellungsmethode können alternativ im Schlüssel `arguments` angegeben werden: ```neon services: @@ -63,294 +65,303 @@ services: arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret] ``` -Eine statische Methode kann auch einen Dienst erstellen: +Dienste müssen nicht nur durch einfache Instanziierung einer Klasse erstellt werden, sie können auch das Ergebnis des Aufrufs statischer Methoden oder Methoden anderer Dienste sein: ```neon services: - database: My\Database::create(root, secret) + database: DatabaseFactory::create() + router: @routerFactory::create() ``` -Entspricht dem PHP-Code: +Beachten Sie, dass zur Vereinfachung anstelle von `->` das Zeichen `::` verwendet wird, siehe [#Ausdrucksmittel]. Es werden diese Factory-Methoden generiert: ```php public function createServiceDatabase(): PDO { - return My\Database::create('root', 'secret'); + return DatabaseFactory::create(); +} + +public function createServiceRouter(): RouteList +{ + return $this->getService('routerFactory')->create(); } ``` -Bei einer statischen Methode `My\Database::create()` wird davon ausgegangen, dass sie einen definierten Rückgabewert hat, den der DI-Container kennen muss. Ist dies nicht der Fall, schreiben wir den Typ in die Konfiguration: +Der DI-Container muss den Typ des erstellten Dienstes kennen. Wenn wir einen Dienst mit einer Methode erstellen, die keinen spezifizierten Rückgabetyp hat, müssen wir diesen Typ explizit in der Konfiguration angeben: ```neon services: database: - create: My\Database::create(root, secret) + create: DatabaseFactory::create() type: PDO ``` -Nette DI gibt Ihnen extrem leistungsfähige Ausdrucksmöglichkeiten, um fast alles zu schreiben. Zum Beispiel, um auf einen anderen Dienst zu [verweisen |#Referencing Services] und seine Methode aufzurufen. Der Einfachheit halber wird `::` anstelle von `->` verwendet. + +Argumente +========= + +Wir übergeben Argumente an den Konstruktor und Methoden auf eine Weise, die dem Vorgehen in PHP selbst sehr ähnlich ist: ```neon services: - routerFactory: App\Router\Factory - router: @routerFactory::create() + database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -Entspricht dem PHP-Code: - -```php -public function createServiceRouterFactory(): App\Router\Factory -{ - return new App\Router\Factory; -} +Zur besseren Lesbarkeit können wir die Argumente auf separate Zeilen aufteilen. In diesem Fall ist die Verwendung von Kommas optional: -public function createServiceRouter(): Router -{ - return $this->getService('routerFactory')->create(); -} +```neon +services: + database: PDO( + 'mysql:host=127.0.0.1;dbname=test' + root + secret + ) ``` -Methodenaufrufe können wie in PHP aneinandergereiht werden: +Sie können Argumente auch benennen und müssen sich dann nicht um ihre Reihenfolge kümmern: ```neon services: - foo: FooFactory::build()::get() + database: PDO( + username: root + password: secret + dsn: 'mysql:host=127.0.0.1;dbname=test' + ) ``` -Entspricht dem PHP-Code: +Wenn Sie einige Argumente auslassen und ihren Standardwert verwenden oder einen Dienst mittels [Autowiring|autowiring] einsetzen möchten, verwenden Sie einen Unterstrich `_`: -```php -public function createServiceFoo() -{ - return FooFactory::build()->get(); -} +```neon +services: + foo: Foo(_, %appDir%) ``` +Als Argumente können Dienste übergeben, Parameter verwendet und vieles mehr getan werden, siehe [#Ausdrucksmittel]. + -Argumente .[#toc-arguments] -=========================== +Setup +===== -Benannte Parameter können auch zur Übergabe von Argumenten verwendet werden: +Im Abschnitt `setup` definieren wir Methoden, die beim Erstellen des Dienstes aufgerufen werden sollen. ```neon services: - database: PDO( - 'mysql:host=127.0.0.1;dbname=test' # positional - username: root # named - password: secret # named - ) + database: + create: PDO(%dsn%, %user%, %password%) + setup: + - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) ``` -Die Verwendung von Kommas ist optional, wenn Argumente in mehrere Zeilen aufgeteilt werden. +Das würde in PHP so aussehen: -Natürlich können wir auch [andere Dienste |#Referencing Services] oder [Parameter |configuration#parameters] als Argumente verwenden: +```php +public function createServiceDatabase(): PDO +{ + $service = new PDO('...', '...', '...'); + $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + return $service; +} +``` + +Neben dem Aufruf von Methoden können auch Werte an Eigenschaften übergeben werden. Das Hinzufügen eines Elements zu einem Array wird ebenfalls unterstützt, was in Anführungszeichen geschrieben werden muss, um nicht mit der NEON-Syntax zu kollidieren: ```neon services: - - Foo(@anotherService, %appDir%) + foo: + create: Foo + setup: + - $value = 123 + - '$onClick[]' = [@bar, clickHandler] ``` -Entspricht dem PHP-Code: +Was im PHP-Code folgendermaßen aussehen würde: ```php -public function createService01(): Foo +public function createServiceFoo(): Foo { - return new Foo($this->getService('anotherService'), '...'); + $service = new Foo; + $service->value = 123; + $service->onClick[] = [$this->getService('bar'), 'clickHandler']; + return $service; } ``` -Wenn das erste Argument [autowired |autowiring] ist und Sie das zweite angeben wollen, lassen Sie das erste mit `_` character, for example `Foo(_, %appDir%)` weg. Oder noch besser, übergeben Sie nur das zweite Argument als benannten Parameter, z. B. `Foo(path: %appDir%)`. - -Nette DI und das NEON-Format geben Ihnen extrem leistungsfähige Ausdrucksmöglichkeiten, um fast alles zu schreiben. So kann ein Argument ein neu erstelltes Objekt sein, Sie können statische Methoden, Methoden anderer Dienste oder sogar globale Funktionen unter Verwendung einer speziellen Notation aufrufen: +Im Setup können jedoch auch statische Methoden oder Methoden anderer Dienste aufgerufen werden. Wenn Sie den aktuellen Dienst als Argument übergeben müssen, geben Sie ihn als `@self` an: ```neon services: - analyser: My\Analyser( - FilesystemIterator(%appDir%) # Objekt erstellen - DateTime::createFromFormat('Y-m-d') # statische Methode aufrufen - @anotherService # Übergabe eines anderen Dienstes - @http.request::getRemoteAddress() # Aufruf einer anderen Dienstmethode - ::getenv(NetteMode) # Aufruf einer globalen Funktion - ) + foo: + create: Foo + setup: + - My\Helpers::initializeFoo(@self) + - @anotherService::setFoo(@self) ``` -Entspricht dem PHP-Code: +Beachten Sie, dass zur Vereinfachung anstelle von `->` das Zeichen `::` verwendet wird, siehe [#Ausdrucksmittel]. Es wird eine solche Factory-Methode generiert: ```php -public function createServiceAnalyser(): My\Analyser +public function createServiceFoo(): Foo { - return new My\Analyser( - new FilesystemIterator('...'), - DateTime::createFromFormat('Y-m-d'), - $this->getService('anotherService'), - $this->getService('http.request')->getRemoteAddress(), - getenv('NetteMode') - ); + $service = new Foo; + My\Helpers::initializeFoo($service); + $this->getService('anotherService')->setFoo($service); + return $service; } ``` -Spezielle Funktionen .[#toc-special-functions] ----------------------------------------------- +Ausdrucksmittel +=============== -Sie können auch spezielle Funktionen in Argumenten verwenden, um Werte zu casten oder zu negieren: - -- `not(%arg%)` negation -- `bool(%arg%)` verlustfreie Umwandlung in bool -- `int(%arg%)` verlustfreie Umwandlung in int -- `float(%arg%)` verlustfreie Umwandlung in float -- `string(%arg%)` lossless cast to string +Nette DI bietet uns außergewöhnlich reichhaltige Ausdrucksmittel, mit denen wir fast alles schreiben können. In Konfigurationsdateien können wir daher [Parameter |configuration#Parameter] verwenden: ```neon -services: - - Foo( - id: int(::getenv('ProjectId')) - productionMode: not(%debugMode%) - ) -``` - -Verlustfreies Rewriting unterscheidet sich von normalem PHP Rewriting, z.B. mit `(int)`, dadurch, dass es eine Ausnahme für nicht-numerische Werte auslöst. +# Parameter +%wwwDir% -Es können mehrere Dienste als Argumente übergeben werden. Ein Array mit allen Diensten eines bestimmten Typs (d.h. Klasse oder Schnittstelle) wird von der Funktion `typed()` erstellt. Die Funktion lässt Dienste aus, bei denen die automatische Verdrahtung deaktiviert ist, und es können mehrere Typen durch ein Komma getrennt angegeben werden. +# Wert des Parameters unter dem Schlüssel +%mailer.user% -```neon -services: - - BarsDependent( typed(Bar) ) +# Parameter innerhalb einer Zeichenkette +'%wwwDir%/images' ``` -Sie können ein Array von Diensten auch automatisch mit [Autowiring |autowiring#Collection of Services] übergeben. - -Ein Array mit allen Diensten mit einem bestimmten [Tag |#tags] wird mit der Funktion `tagged()` erstellt. Es können mehrere Tags durch ein Komma getrennt angegeben werden. +Weiterhin Objekte erstellen, Methoden und Funktionen aufrufen: ```neon -services: - - LoggersDependent( tagged(logger) ) -``` +# Objekt erstellen +DateTime() +# statische Methode aufrufen +Collator::create(%locale%) -Referenzierungsdienste .[#toc-referencing-services] -=================================================== +# PHP-Funktion aufrufen +::getenv(DB_USER) +``` -Die einzelnen Dienste werden mit dem Zeichen `@` and name, so for example `@database` referenziert: +Auf Dienste entweder nach ihrem Namen oder nach Typ verweisen: ```neon -services: - - create: Foo(@database) - setup: - - setCacheStorage(@cache.storage) +# Dienst nach Namen +@database + +# Dienst nach Typ +@Nette\Database\Connection ``` -Entspricht dem PHP-Code: +Verwenden Sie die First-Class-Callable-Syntax: .{data-version:3.2.0} -```php -public function createService01(): Foo -{ - $service = new Foo($this->getService('database')); - $service->setCacheStorage($this->getService('cache.storage')); - return $service; -} +```neon +# Callback erstellen, Äquivalent zu [@user, logout] +@user::logout(...) ``` -Auch anonyme Dienste können über einen Callback referenziert werden, man muss nur ihren Typ (Klasse oder Schnittstelle) anstelle ihres Namens angeben. Dies ist jedoch aufgrund der [automatischen Verdrahtung |autowiring] normalerweise nicht erforderlich. +Konstanten verwenden: ```neon -services: - - create: Foo(@Nette\Database\Connection) # oder @\PDO - setup: - - setCacheStorage(@cache.storage) +# Klassenkonstante +FilesystemIterator::SKIP_DOTS + +# globale Konstante erhalten wir mit der PHP-Funktion constant() +::constant(PHP_VERSION) ``` +Methodenaufrufe können wie in PHP verkettet werden. Nur zur Vereinfachung wird anstelle von `->` das Zeichen `::` verwendet: -Einrichtung .[#toc-setup] -========================= +```neon +DateTime()::format('Y-m-d') +# PHP: (new DateTime())->format('Y-m-d') -Im Abschnitt "Setup" werden die Methoden aufgeführt, die bei der Erstellung des Dienstes aufgerufen werden müssen: +@http.request::getUrl()::getHost() +# PHP: $this->getService('http.request')->getUrl()->getHost() +``` + +Diese Ausdrücke können Sie überall verwenden, beim [Erstellen von Diensten |#Erstellung eines Dienstes], in [Argumenten |#Argumente], im Abschnitt [#setup] oder bei [Parametern |configuration#Parameter]: ```neon +parameters: + ipAddress: @http.request::getRemoteAddress() + services: database: - create: PDO(%dsn%, %user%, %password%) + create: DatabaseFactory::create( @anotherService::getDsn() ) setup: - - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) + - initialize( ::getenv('DB_USER') ) ``` -Entspricht dem PHP-Code: -```php -public function createServiceDatabase(): PDO -{ - $service = new PDO('...', '...', '...'); - $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - return $service; -} -``` +Spezielle Funktionen +-------------------- -Properites können auch gesetzt werden. Das Hinzufügen eines Elements zu einem Array wird ebenfalls unterstützt und sollte in Anführungszeichen geschrieben werden, um nicht mit der NEON-Syntax in Konflikt zu geraten: +In Konfigurationsdateien können Sie diese speziellen Funktionen verwenden: +- `not()` Negation eines Wertes +- `bool()`, `int()`, `float()`, `string()` verlustfreie Typumwandlung in den angegebenen Typ +- `typed()` erstellt ein Array aller Dienste des angegebenen Typs +- `tagged()` erstellt ein Array aller Dienste mit dem angegebenen Tag ```neon services: - foo: - create: Foo - setup: - - $value = 123 - - '$onClick[]' = [@bar, clickHandler] + - Foo( + id: int(::getenv('ProjectId')) + productionMode: not(%debugMode%) + ) ``` -Entspricht dem PHP-Code: +Im Gegensatz zur klassischen Typumwandlung in PHP, wie z. B. `(int)`, wirft die verlustfreie Typumwandlung eine Ausnahme für nicht-numerische Werte. -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - $service->value = 123; - $service->onClick[] = [$this->getService('bar'), 'clickHandler']; - return $service; -} +Die Funktion `typed()` erstellt ein Array aller Dienste des angegebenen Typs (Klasse oder Schnittstelle). Sie lässt Dienste aus, deren Autowiring deaktiviert ist. Es können auch mehrere Typen, durch Komma getrennt, angegeben werden. + +```neon +services: + - BarsDependent( typed(Bar) ) ``` -Es können aber auch statische Methoden oder Methoden anderer Dienste im Setup aufgerufen werden. Wir übergeben ihnen den eigentlichen Dienst als `@self`: +Arrays von Diensten eines bestimmten Typs können auch automatisch mittels [Autowiring |autowiring#Array von Diensten] als Argument übergeben werden. +Die Funktion `tagged()` erstellt dann ein Array aller Dienste mit einem bestimmten Tag. Auch hier können Sie mehrere Tags durch Komma getrennt angeben. ```neon services: - foo: - create: Foo - setup: - - My\Helpers::initializeFoo(@self) - - @anotherService::setFoo(@self) + - LoggersDependent( tagged(logger) ) ``` -Entspricht dem PHP-Code: -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - My\Helpers::initializeFoo($service); - $this->getService('anotherService')->setFoo($service); - return $service; -} +Autowiring +========== + +Der Schlüssel `autowired` ermöglicht es, das Verhalten des Autowirings für einen bestimmten Dienst zu beeinflussen. Für Details siehe [Kapitel über Autowiring|autowiring]. + +```neon +services: + foo: + create: Foo + autowired: false # der Dienst foo wird vom Autowiring ausgeschlossen ``` -Autowiring .[#toc-autowiring] -============================= +Lazy Dienste .{data-version:3.2.4} +================================== -Der Autowiring-Schlüssel kann verwendet werden, um einen Dienst vom Autowiring auszuschließen oder um sein Verhalten zu beeinflussen. Weitere Informationen finden Sie im [Kapitel über Autowiring |autowiring]. +Lazy Loading ist eine Technik, die die Erstellung eines Dienstes bis zu dem Zeitpunkt aufschiebt, an dem er tatsächlich benötigt wird. In der globalen Konfiguration kann die [lazy Erstellung |configuration#Lazy Dienste] für alle Dienste gleichzeitig aktiviert werden. Für einzelne Dienste können Sie dieses Verhalten dann überschreiben: ```neon services: foo: create: Foo - autowired: false # foo wird aus der automatischen Verdrahtung entfernt + lazy: false ``` +Wenn ein Dienst als lazy definiert ist, erhalten wir bei seiner Anforderung aus dem DI-Container ein spezielles Platzhalterobjekt. Dieses sieht aus und verhält sich genauso wie der tatsächliche Dienst, aber die tatsächliche Initialisierung (Aufruf des Konstruktors und des Setups) erfolgt erst beim ersten Zugriff auf eine seiner Methoden oder Eigenschaften. + +.[note] +Lazy Loading kann nur für benutzerdefinierte Klassen verwendet werden, nicht für interne PHP-Klassen. Erfordert PHP 8.4 oder neuer. -Tags .[#toc-tags] -================= -Benutzerinformationen können den einzelnen Diensten in Form von Tags hinzugefügt werden: +Tags +==== + +Tags dienen dazu, Diensten zusätzliche Informationen hinzuzufügen. Sie können einem Dienst einen oder mehrere Tags hinzufügen: ```neon services: @@ -360,7 +371,7 @@ services: - cached ``` -Tags können auch einen Wert haben: +Tags können auch Werte tragen: ```neon services: @@ -370,26 +381,26 @@ services: logger: monolog.logger.event ``` -Ein Array von Diensten mit bestimmten Tags kann mit der Funktion `tagged()` als Argument übergeben werden. Es können auch mehrere Tags durch ein Komma getrennt angegeben werden. +Um alle Dienste mit bestimmten Tags zu erhalten, können Sie die Funktion `tagged()` verwenden: ```neon services: - LoggersDependent( tagged(logger) ) ``` -Dienstnamen können mit der Methode `findByTag()` aus dem DI-Container bezogen werden: +Im DI-Container können Sie die Namen aller Dienste mit einem bestimmten Tag mithilfe der Methode `findByTag()` abrufen: ```php $names = $container->findByTag('logger'); // $names ist ein Array, das den Dienstnamen und den Tag-Wert enthält -// d.h. ['foo' => 'monolog.logger.event', ...] +// z. B. ['foo' => 'monolog.logger.event', ...] ``` -Injektionsmodus .[#toc-inject-mode] -=================================== +Inject-Modus +============ -Das Flag `inject: true` wird verwendet, um die Übergabe von Abhängigkeiten über öffentliche Variablen mit der [inject-Annotation |best-practices:inject-method-attribute#Inject Attributes] und den [inject*() |best-practices:inject-method-attribute#inject Methods] -Methoden zu aktivieren. +Mit dem Flag `inject: true` wird die Übergabe von Abhängigkeiten über öffentliche Variablen mit der Annotation [inject |best-practices:inject-method-attribute#Inject -Attribute] und Methoden [inject*() |best-practices:inject-method-attribute#inject -Methoden] aktiviert. ```neon services: @@ -398,13 +409,13 @@ services: inject: true ``` -Standardmäßig ist `inject` nur für Präsentatoren aktiviert. +Standardmäßig ist `inject` nur für Presenter aktiviert. -Modifikation der Dienste .[#toc-modification-of-services] -========================================================= +Modifikation von Diensten +========================= -Es gibt eine Reihe von Diensten im DI-Container, die durch eine eingebaute oder [Ihre Erweiterung |#di-extensions] hinzugefügt wurden. Die Definitionen dieser Dienste können in der Konfiguration geändert werden. Zum Beispiel kann für den Dienst `application.application`, der standardmäßig ein Objekt `Nette\Application\Application` ist, die Klasse geändert werden: +Der DI-Container enthält viele Dienste, die über eingebaute oder [benutzerdefinierte Erweiterungen|extensions] hinzugefügt wurden. Sie können die Definitionen dieser Dienste direkt in der Konfiguration ändern. Beispielsweise können Sie die Klasse des Dienstes `application.application`, die standardmäßig `Nette\Application\Application` ist, in eine andere ändern: ```neon services: @@ -413,9 +424,9 @@ services: alteration: true ``` -Das Kennzeichen `alteration` ist informativ und besagt, dass wir nur einen bestehenden Dienst ändern. +Das Flag `alteration` ist informativ und besagt, dass wir nur einen bestehenden Dienst modifizieren. -Wir können auch eine Einrichtung hinzufügen: +Wir können auch das Setup ergänzen: ```neon services: @@ -426,7 +437,7 @@ services: - '$onStartup[]' = [@resource, init] ``` -Wenn wir einen Dienst neu schreiben, möchten wir vielleicht die ursprünglichen Argumente, Setup-Elemente oder Tags entfernen. Dafür ist `reset` gedacht: +Beim Überschreiben eines Dienstes möchten wir möglicherweise die ursprünglichen Argumente, Setup-Einträge oder Tags entfernen, wozu `reset` dient: ```neon services: @@ -439,7 +450,7 @@ services: - tags ``` -Ein durch eine Erweiterung hinzugefügter Dienst kann auch aus dem Container entfernt werden: +Wenn Sie einen durch eine Erweiterung hinzugefügten Dienst entfernen möchten, können Sie dies wie folgt tun: ```neon services: diff --git a/dependency-injection/el/@home.texy b/dependency-injection/el/@home.texy index e93142a244..8c439b2f8a 100644 --- a/dependency-injection/el/@home.texy +++ b/dependency-injection/el/@home.texy @@ -1,24 +1,21 @@ -Εγχώνευση εξάρτησης -******************* +Nette DI +******** .[perex] -Το Dependency Injection είναι ένα πρότυπο σχεδίασης που θα αλλάξει ριζικά τον τρόπο με τον οποίο βλέπετε τον κώδικα και την ανάπτυξη. Ανοίγει το δρόμο για έναν κόσμο καθαρά σχεδιασμένων και βιώσιμων εφαρμογών. +Το Dependency Injection είναι ένα πρότυπο σχεδίασης που θα αλλάξει ριζικά την οπτική σας για τον κώδικα και την ανάπτυξη. Θα σας ανοίξει τον δρόμο στον κόσμο των καθαρά σχεδιασμένων και βιώσιμων εφαρμογών. - [Τι είναι το Dependency Injection; |introduction] -- [Παγκόσμια κατάσταση & Singletons |global-state] +- [Καθολική κατάσταση και singletons |global-state] - [Πέρασμα εξαρτήσεων |passing-dependencies] -- [Τι είναι το DI Container; |container] -- [Συχνές ερωτήσεις |faq] - +- [Τι είναι ο DI container; |container] +- [Συχνές Ερωτήσεις|faq] -Nette DI --------- -Το πακέτο `nette/di` παρέχει ένα εξαιρετικά προηγμένο μεταγλωττισμένο δοχείο DI για την PHP. +Το πακέτο `nette/di` παρέχει έναν εξαιρετικά προηγμένο μεταγλωττισμένο DI container για PHP. -- [Δοχείο Nette DI |nette-container] +- [Nette DI Container |nette-container] - [Διαμόρφωση |configuration] -- [Ορισμοί υπηρεσιών |services] -- [Αυτόματη σύνδεση |autowiring] -- [Παραγόμενα εργοστάσια |factory] -- [Δημιουργία επεκτάσεων για το Nette DI |extensions] +- [Ορισμός υπηρεσιών |services] +- [Autowiring |autowiring] +- [Δημιουργημένα factories |factory] +- [Δημιουργία επεκτάσεων για το Nette DI|extensions] diff --git a/dependency-injection/el/@left-menu.texy b/dependency-injection/el/@left-menu.texy index 25ec08ae37..4cfa98f34c 100644 --- a/dependency-injection/el/@left-menu.texy +++ b/dependency-injection/el/@left-menu.texy @@ -1,17 +1,17 @@ -Εγχώνευση εξάρτησης -******************* -- [Τι είναι η DI; |introduction] -- [Παγκόσμια κατάσταση & Singletons |global-state] +Dependency Injection +******************** +- [Τι είναι το DI; |introduction] +- [Καθολική κατάσταση και singletons |global-state] - [Πέρασμα εξαρτήσεων |passing-dependencies] -- [Τι είναι το DI Container; |container] -- [Συχνές ερωτήσεις |faq] +- [Τι είναι ο DI container; |container] +- [Συχνές Ερωτήσεις|faq] Nette DI -------- - [Nette DI Container |nette-container] - [Διαμόρφωση |configuration] -- [Ορισμοί υπηρεσιών |services] -- [Αυτόματη καλωδίωση |autowiring] -- [Παραγόμενα εργοστάσια |factory] -- [Δημιουργία επεκτάσεων για το Nette DI |extensions] +- [Ορισμός υπηρεσιών |services] +- [Autowiring |autowiring] +- [Δημιουργημένα factories |factory] +- [Δημιουργία επεκτάσεων για το Nette DI|extensions] diff --git a/dependency-injection/el/@meta.texy b/dependency-injection/el/@meta.texy new file mode 100644 index 0000000000..88e29852c7 --- /dev/null +++ b/dependency-injection/el/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Τεκμηρίωση}} diff --git a/dependency-injection/el/autowiring.texy b/dependency-injection/el/autowiring.texy index eec1365d62..024d4079d1 100644 --- a/dependency-injection/el/autowiring.texy +++ b/dependency-injection/el/autowiring.texy @@ -1,24 +1,24 @@ -Αυτόματη καλωδίωση -****************** +Autowiring +********** .[perex] -Το Autowiring είναι ένα σπουδαίο χαρακτηριστικό που μπορεί να περάσει αυτόματα υπηρεσίες στον κατασκευαστή και σε άλλες μεθόδους, έτσι ώστε να μην χρειάζεται να τις γράψουμε καθόλου. Σας εξοικονομεί πολύ χρόνο. +Το Autowiring είναι ένα εξαιρετικό χαρακτηριστικό που μπορεί να περάσει αυτόματα τις απαιτούμενες υπηρεσίες στον κατασκευαστή και σε άλλες μεθόδους, οπότε δεν χρειάζεται να τις γράψουμε καθόλου. Σας εξοικονομεί πολύ χρόνο. -Αυτό μας επιτρέπει να παραλείψουμε τη συντριπτική πλειοψηφία των επιχειρημάτων κατά τη συγγραφή των ορισμών υπηρεσιών. Αντί για: +Χάρη σε αυτό, μπορούμε να παραλείψουμε τη συντριπτική πλειοψηφία των ορισμάτων κατά τη σύνταξη ορισμών υπηρεσιών. Αντί για: ```neon services: articles: Model\ArticleRepository(@database, @cache.storage) ``` -Απλά γράψτε: +Αρκεί να γράψουμε: ```neon services: articles: Model\ArticleRepository ``` -Έτσι, η κλάση `ArticleRepository` πρέπει να οριστεί ως εξής: +Το Autowiring καθοδηγείται από τους τύπους, οπότε για να λειτουργήσει, η κλάση `ArticleRepository` πρέπει να οριστεί κάπως έτσι: ```php namespace Model; @@ -30,22 +30,22 @@ class ArticleRepository } ``` -Για να χρησιμοποιηθεί η αυτόματη καλωδίωση, πρέπει να υπάρχει **μόνο μια υπηρεσία** για κάθε τύπο στο δοχείο. Αν υπήρχαν περισσότερες, το autowiring δεν θα ήξερε ποια να περάσει και θα πετούσε μια εξαίρεση: +Για να είναι δυνατή η χρήση του autowiring, πρέπει να υπάρχει **ακριβώς μία υπηρεσία** για κάθε τύπο στο container. Αν υπήρχαν περισσότερες, το autowiring δεν θα ήξερε ποια να περάσει και θα προκαλούσε εξαίρεση: ```neon services: mainDb: PDO(%dsn%, %user%, %password%) tempDb: PDO('sqlite::memory:') - articles: Model\ArticleRepository # THROWS EXCEPTION, τόσο η mainDb όσο και η tempDb ταιριάζουν + articles: Model\ArticleRepository # ΠΡΟΚΑΛΕΙ ΕΞΑΙΡΕΣΗ, ταιριάζουν και η mainDb και η tempDb ``` -Η λύση θα ήταν είτε να παρακάμψετε το autowiring και να δηλώσετε ρητά το όνομα της υπηρεσίας (π.χ. `articles: Model\ArticleRepository(@mainDb)`). Ωστόσο, είναι πιο βολικό να [απενεργοποιήσετε |#Disabled autowiring] την αυτόματη σύνδεση μιας υπηρεσίας ή να [προτιμήσετε |#Preferred Autowiring] την πρώτη υπηρεσία. +Η λύση θα ήταν είτε να παρακάμψουμε το autowiring και να δηλώσουμε ρητά το όνομα της υπηρεσίας (δηλ. `articles: Model\ArticleRepository(@mainDb)`). Πιο έξυπνο όμως είναι να [απενεργοποιήσουμε |#Απενεργοποίηση του autowiring] το autowiring για μία από τις υπηρεσίες, ή να [δώσουμε προτεραιότητα |#Προτίμηση autowiring] στην πρώτη υπηρεσία. -Απενεργοποιημένη αυτόματη σύνδεση .[#toc-disabled-autowiring] -------------------------------------------------------------- +Απενεργοποίηση του autowiring +----------------------------- -Μπορείτε να απενεργοποιήσετε την αυτόματη καλωδίωση υπηρεσιών χρησιμοποιώντας την επιλογή `autowired: no`: +Μπορούμε να απενεργοποιήσουμε το autowiring μιας υπηρεσίας χρησιμοποιώντας την επιλογή `autowired: no`: ```neon services: @@ -53,28 +53,27 @@ services: tempDb: create: PDO('sqlite::memory:') - autowired: false # αφαιρεί το tempDb από την αυτόματη σύνδεση + autowired: false # η υπηρεσία tempDb εξαιρείται από το autowiring - articles: Model\ArticleRepository # επομένως περνάει την mainDb στον κατασκευαστή + articles: Model\ArticleRepository # επομένως περνάει τη mainDb στον κατασκευαστή ``` -Η υπηρεσία `articles` δεν πετάει την εξαίρεση ότι υπάρχουν δύο αντίστοιχες υπηρεσίες τύπου `PDO` (δηλ. `mainDb` και `tempDb`) που μπορούν να περάσουν στον κατασκευαστή, επειδή βλέπει μόνο την υπηρεσία `mainDb`. +Η υπηρεσία `articles` δεν προκαλεί εξαίρεση ότι υπάρχουν δύο κατάλληλες υπηρεσίες τύπου `PDO` (δηλ. `mainDb` και `tempDb`) που μπορούν να περάσουν στον κατασκευαστή, επειδή βλέπει μόνο την υπηρεσία `mainDb`. .[note] -Η διαμόρφωση της αυτόματης καλωδίωσης στο Nette λειτουργεί διαφορετικά από ό,τι στο Symfony, όπου η επιλογή `autowire: false` λέει ότι η αυτόματη καλωδίωση δεν πρέπει να χρησιμοποιείται για τα ορίσματα του κατασκευαστή υπηρεσιών. -Στη Nette, η αυτόματη σύνδεση χρησιμοποιείται πάντα, είτε για τα ορίσματα του κατασκευαστή είτε για οποιαδήποτε άλλη μέθοδο. Η επιλογή `autowired: false` λέει ότι το instance της υπηρεσίας δεν πρέπει να μεταβιβάζεται πουθενά με χρήση autowiring. +Η διαμόρφωση του autowiring στο Nette λειτουργεί διαφορετικά από ό,τι στο Symfony, όπου η επιλογή `autowire: false` λέει ότι το autowiring δεν πρέπει να χρησιμοποιείται για τα ορίσματα του κατασκευαστή της συγκεκριμένης υπηρεσίας. Στο Nette, το autowiring χρησιμοποιείται πάντα, είτε για τα ορίσματα του κατασκευαστή, είτε για οποιαδήποτε άλλη μέθοδο. Η επιλογή `autowired: false` λέει ότι η παρουσία της συγκεκριμένης υπηρεσίας δεν πρέπει να περνιέται πουθενά μέσω autowiring. -Προτιμώμενη αυτόματη σύνδεση .[#toc-preferred-autowiring] ---------------------------------------------------------- +Προτίμηση autowiring +-------------------- -Εάν έχουμε περισσότερες υπηρεσίες του ίδιου τύπου και μία από αυτές έχει την επιλογή `autowired`, αυτή η υπηρεσία γίνεται η προτιμώμενη: +Εάν έχουμε πολλές υπηρεσίες του ίδιου τύπου και σε μία από αυτές δηλώσουμε την επιλογή `autowired`, αυτή η υπηρεσία γίνεται η προτιμώμενη: ```neon services: mainDb: create: PDO(%dsn%, %user%, %password%) - autowired: PDO # το καθιστά προτιμώμενο + autowired: PDO # γίνεται η προτιμώμενη tempDb: create: PDO('sqlite::memory:') @@ -82,13 +81,13 @@ services: articles: Model\ArticleRepository ``` -Η υπηρεσία `articles` δεν απορρίπτει την εξαίρεση ότι υπάρχουν δύο αντίστοιχες υπηρεσίες `PDO` (δηλ. `mainDb` και `tempDb`), αλλά χρησιμοποιεί την προτιμώμενη υπηρεσία, δηλ. `mainDb`. +Η υπηρεσία `articles` δεν προκαλεί εξαίρεση ότι υπάρχουν δύο κατάλληλες υπηρεσίες τύπου `PDO` (δηλ. `mainDb` και `tempDb`), αλλά χρησιμοποιεί την προτιμώμενη υπηρεσία, δηλαδή τη `mainDb`. -Συλλογή υπηρεσιών .[#toc-collection-of-services] ------------------------------------------------- +Πίνακας υπηρεσιών +----------------- -Η αυτόματη καλωδίωση μπορεί επίσης να περάσει μια συστοιχία υπηρεσιών ενός συγκεκριμένου τύπου. Δεδομένου ότι η PHP δεν μπορεί να σημειώσει εγγενώς τον τύπο των στοιχείων του πίνακα, εκτός από τον τύπο `array`, πρέπει να προστεθεί ένα σχόλιο phpDoc με τον τύπο του στοιχείου όπως το `ClassName[]`: +Το Autowiring μπορεί επίσης να περάσει πίνακες υπηρεσιών ενός συγκεκριμένου τύπου. Επειδή στην PHP δεν είναι δυνατό να γραφτεί εγγενώς ο τύπος των στοιχείων του πίνακα, είναι απαραίτητο, εκτός από τον τύπο `array`, να συμπληρωθεί και ένα phpDoc σχόλιο με τον τύπο του στοιχείου στη μορφή `ClassName[]`: ```php namespace Model; @@ -103,46 +102,45 @@ class ShipManager } ``` -Ο περιέκτης DI περνάει τότε αυτόματα έναν πίνακα υπηρεσιών που ταιριάζουν στον συγκεκριμένο τύπο. Θα παραλείψει τις υπηρεσίες που έχουν απενεργοποιήσει την αυτόματη καλωδίωση. +Το DI container στη συνέχεια περνά αυτόματα έναν πίνακα υπηρεσιών που αντιστοιχούν στον συγκεκριμένο τύπο. Παραλείπει τις υπηρεσίες που έχουν απενεργοποιημένο το autowiring. -Αν δεν μπορείτε να ελέγξετε τη μορφή του σχολίου phpDoc, μπορείτε να περάσετε έναν πίνακα υπηρεσιών απευθείας στη διαμόρφωση χρησιμοποιώντας την εντολή [`typed()` |services#Special Functions]. +Ο τύπος στο σχόλιο μπορεί επίσης να είναι στη μορφή `array<int, Class>` ή `list<Class>`. Εάν δεν μπορείτε να επηρεάσετε τη μορφή του phpDoc σχολίου, μπορείτε να περάσετε τον πίνακα υπηρεσιών απευθείας στη διαμόρφωση χρησιμοποιώντας το [`typed()` |services#Ειδικές συναρτήσεις]. -Scalar Arguments .[#toc-scalar-arguments] ------------------------------------------ +Σκαλωτά ορίσματα +---------------- -Η αυτόματη σύνδεση μπορεί να περάσει μόνο αντικείμενα και πίνακες αντικειμένων. Τα κλιμακωτά ορίσματα (π.χ. συμβολοσειρές, αριθμοί, booleans) [γράφονται στη διαμόρφωση |services#Arguments]. -Μια εναλλακτική λύση είναι η δημιουργία ενός [αντικειμένου settings-object |best-practices:passing-settings-to-presenters] που ενθυλακώνει μια κλιμακωτή τιμή (ή πολλαπλές τιμές) ως αντικείμενο, το οποίο μπορεί στη συνέχεια να μεταβιβαστεί και πάλι με τη χρήση autowiring. +Το Autowiring μπορεί να αντικαταστήσει μόνο αντικείμενα και πίνακες αντικειμένων. Τα σκαλωτά ορίσματα (π.χ. συμβολοσειρές, αριθμοί, booleans) [τα γράφουμε στη διαμόρφωση |services#Ορίσματα]. Μια εναλλακτική λύση είναι να δημιουργήσετε ένα [settings-object |best-practices:passing-settings-to-presenters], το οποίο ενσωματώνει την σκαλωτή τιμή (ή περισσότερες τιμές) σε μορφή αντικειμένου, το οποίο στη συνέχεια μπορεί να περάσει ξανά μέσω autowiring. ```php class MySettings { public function __construct( - // readonly μπορεί να χρησιμοποιηθεί από την PHP 8.1 + // το readonly είναι δυνατό να χρησιμοποιηθεί από την PHP 8.1 public readonly bool $value, ) {} } ``` -Δημιουργείτε μια υπηρεσία προσθέτοντας την στη διαμόρφωση: +Δημιουργείτε μια υπηρεσία από αυτό προσθέτοντάς το στη διαμόρφωση: ```neon services: - MySettings('any value') ``` -Όλες οι τάξεις θα την ζητούν στη συνέχεια μέσω αυτόματης σύνδεσης. +Όλες οι κλάσεις στη συνέχεια το ζητούν μέσω autowiring. -Περιορισμός της αυτόματης καλωδίωσης .[#toc-narrowing-of-autowiring] --------------------------------------------------------------------- +Περιορισμός του autowiring +-------------------------- -Για μεμονωμένες υπηρεσίες, η αυτόματη καλωδίωση μπορεί να περιοριστεί σε συγκεκριμένες κλάσεις ή διεπαφές. +Για μεμονωμένες υπηρεσίες, το autowiring μπορεί να περιοριστεί μόνο σε συγκεκριμένες κλάσεις ή interfaces. -Κανονικά, η αυτόματη καλωδίωση περνάει την υπηρεσία σε κάθε παράμετρο μεθόδου της οποίας ο τύπος αντιστοιχεί στην υπηρεσία. Στένωση σημαίνει ότι καθορίζουμε τις συνθήκες που πρέπει να ικανοποιούν οι τύποι που ορίζονται για τις παραμέτρους της μεθόδου για να περάσει η υπηρεσία σε αυτές. +Κανονικά, το autowiring περνά την υπηρεσία σε κάθε παράμετρο μεθόδου, ο τύπος της οποίας αντιστοιχεί στην υπηρεσία. Ο περιορισμός σημαίνει ότι θέτουμε συνθήκες που πρέπει να πληρούν οι τύποι που αναφέρονται στις παραμέτρους των μεθόδων, ώστε η υπηρεσία να τους περάσει. -Ας πάρουμε ένα παράδειγμα: +Ας το δείξουμε με ένα παράδειγμα: ```php class ParentClass @@ -164,42 +162,42 @@ class ChildDependent } ``` -Αν τα καταχωρίζαμε όλα ως υπηρεσίες, η αυτόματη καλωδίωση θα αποτύγχανε: +Αν τις καταχωρούσαμε όλες ως υπηρεσίες, το autowiring θα αποτύγχανε: ```neon services: parent: ParentClass child: ChildClass - parentDep: ParentDependent # THROWS EXCEPTION, και οι δύο γονείς και τα παιδιά ταιριάζουν - childDep: ChildDependent # μεταβιβάζει την υπηρεσία 'child' στον κατασκευαστή + parentDep: ParentDependent # ΠΡΟΚΑΛΕΙ ΕΞΑΙΡΕΣΗ, ταιριάζουν οι υπηρεσίες parent και child + childDep: ChildDependent # το autowiring περνά την υπηρεσία child στον κατασκευαστή ``` -Η υπηρεσία `parentDep` πετάει την εξαίρεση `Multiple services of type ParentClass found: parent, child` επειδή τόσο η `parent` όσο και η `child` χωράνε στον κατασκευαστή της και η αυτόματη σύνδεση δεν μπορεί να αποφασίσει ποια θα επιλέξει. +Η υπηρεσία `parentDep` προκαλεί εξαίρεση `Multiple services of type ParentClass found: parent, child`, επειδή στον κατασκευαστή της ταιριάζουν και οι δύο υπηρεσίες `parent` και `child`, και το autowiring δεν μπορεί να αποφασίσει ποια να επιλέξει. -Για την υπηρεσία `child`, μπορούμε επομένως να περιορίσουμε την αυτόματη σύνδεσή της στο `ChildClass`: +Για την υπηρεσία `child`, μπορούμε επομένως να περιορίσουμε το autowiring της στον τύπο `ChildClass`: ```neon services: parent: ParentClass child: create: ChildClass - autowired: ChildClass # εναλλακτική λύση: 'autowired: self' + autowired: ChildClass # μπορεί να γραφτεί και 'autowired: self' - parentDep: ParentDependent # THROWS EXCEPTION, το 'παιδί' δεν μπορεί να είναι autowired - childDep: ChildDependent # μεταβιβάζει την υπηρεσία 'child' στον κατασκευαστή + parentDep: ParentDependent # το autowiring περνά την υπηρεσία parent στον κατασκευαστή + childDep: ChildDependent # το autowiring περνά την υπηρεσία child στον κατασκευαστή ``` -Η υπηρεσία `parentDep` περνάει τώρα στον κατασκευαστή της υπηρεσίας `parentDep`, αφού είναι πλέον το μόνο αντικείμενο που ταιριάζει. Η υπηρεσία `child` δεν περνάει πλέον με αυτόματη σύνδεση. Ναι, η υπηρεσία `child` εξακολουθεί να είναι τύπου `ParentClass`, αλλά η συνθήκη περιορισμού που δίνεται για τον τύπο της παραμέτρου δεν ισχύει πλέον, δηλαδή δεν ισχύει πλέον ότι το `ParentClass` *είναι υπερτύπος* του `ChildClass`. +Τώρα, στον κατασκευαστή της υπηρεσίας `parentDep` περνιέται η υπηρεσία `parent`, επειδή τώρα είναι το μόνο κατάλληλο αντικείμενο. Το autowiring δεν περνά πλέον την υπηρεσία `child` εκεί. Ναι, η υπηρεσία `child` εξακολουθεί να είναι τύπου `ParentClass`, αλλά η περιοριστική συνθήκη που δόθηκε για τον τύπο της παραμέτρου δεν ισχύει πλέον, δηλ. δεν ισχύει ότι το `ParentClass` *είναι υπερτύπος* του `ChildClass`. -Στην περίπτωση του `child`, το `autowired: ChildClass` θα μπορούσε να γραφτεί ως `autowired: self` καθώς το `self` σημαίνει τρέχων τύπος υπηρεσίας. +Για την υπηρεσία `child`, το `autowired: ChildClass` θα μπορούσε επίσης να γραφτεί ως `autowired: self`, καθώς το `self` είναι ένα placeholder για την κλάση της τρέχουσας υπηρεσίας. -Το κλειδί `autowired` μπορεί να περιλαμβάνει διάφορες κλάσεις και διεπαφές ως πίνακα: +Στο κλειδί `autowired`, είναι δυνατόν να αναφερθούν και πολλές κλάσεις ή interfaces ως πίνακας: ```neon autowired: [BarClass, FooInterface] ``` -Ας προσπαθήσουμε να προσθέσουμε διεπαφές στο παράδειγμα: +Ας δοκιμάσουμε να συμπληρώσουμε το παράδειγμα και με interfaces: ```php interface FooInterface @@ -239,13 +237,13 @@ class ChildDependent } ``` -Όταν δεν περιορίζουμε την υπηρεσία `child`, θα χωρέσει στους κατασκευαστές όλων των κλάσεων `FooDependent`, `BarDependent`, `ParentDependent` και `ChildDependent` και η αυτόματη σύνδεση θα την περάσει εκεί. +Όταν η υπηρεσία `child` δεν περιορίζεται καθόλου, θα ταιριάζει στους κατασκευαστές όλων των κλάσεων `FooDependent`, `BarDependent`, `ParentDependent` και `ChildDependent` και το autowiring θα την περάσει εκεί. -Ωστόσο, αν περιορίσουμε την αυτόματη σύνδεσή της στο `ChildClass` χρησιμοποιώντας το `autowired: ChildClass` (ή το `self`), η αυτόματη σύνδεση την περνάει μόνο στον κατασκευαστή `ChildDependent`, επειδή απαιτεί ένα όρισμα τύπου `ChildClass` και το `ChildClass` *είναι τύπου* `ChildClass`. Κανένας άλλος τύπος που καθορίζεται για τις άλλες παραμέτρους δεν είναι υπερσύνολο του `ChildClass`, οπότε η υπηρεσία δεν περνάει. +Αν όμως περιορίσουμε το autowiring της σε `ChildClass` χρησιμοποιώντας `autowired: ChildClass` (ή `self`), το autowiring θα την περάσει μόνο στον κατασκευαστή του `ChildDependent`, επειδή απαιτεί όρισμα τύπου `ChildClass` και ισχύει ότι το `ChildClass` *είναι τύπου* `ChildClass`. Κανένας άλλος τύπος που αναφέρεται στις άλλες παραμέτρους δεν είναι υπερτύπος του `ChildClass`, οπότε η υπηρεσία δεν περνιέται. -Αν την περιορίσουμε στο `ParentClass` χρησιμοποιώντας το `autowired: ParentClass`, η αυτόματη σύνδεση θα την περάσει ξανά στον κατασκευαστή `ChildDependent` (αφού ο απαιτούμενος τύπος `ChildClass` είναι υπερσύνολο του `ParentClass`) και στον κατασκευαστή `ParentDependent` επίσης, αφού ο απαιτούμενος τύπος του `ParentClass` είναι επίσης αντίστοιχος. +Αν το περιορίσουμε σε `ParentClass` χρησιμοποιώντας `autowired: ParentClass`, το autowiring θα την περάσει ξανά στον κατασκευαστή του `ChildDependent` (επειδή το απαιτούμενο `ChildClass` είναι υπερτύπος του `ParentClass`) και τώρα και στον κατασκευαστή του `ParentDependent`, επειδή ο απαιτούμενος τύπος `ParentClass` είναι επίσης κατάλληλος. -Αν το περιορίσουμε στο `FooInterface`, θα συνεχίσει να μεταβιβάζεται αυτόνομα στο `ParentDependent` (ο απαιτούμενος τύπος `ParentClass` είναι υπερτύπος του `FooInterface`) και στο `ChildDependent`, αλλά επιπλέον στον κατασκευαστή `FooDependent`, αλλά όχι στο `BarDependent`, αφού το `BarInterface` δεν είναι υπερτύπος του `FooInterface`. +Αν το περιορίσουμε σε `FooInterface`, θα εξακολουθεί να γίνεται autowired στο `ParentDependent` (το απαιτούμενο `ParentClass` είναι υπερτύπος του `FooInterface`) και στο `ChildDependent`, αλλά επιπλέον και στον κατασκευαστή του `FooDependent`, όχι όμως στο `BarDependent`, επειδή το `BarInterface` δεν είναι υπερτύπος του `FooInterface`. ```neon services: @@ -253,8 +251,8 @@ services: create: ChildClass autowired: FooInterface - fooDep: FooDependent # μεταβιβάζει το παιδί υπηρεσίας στον κατασκευαστή - barDep: BarDependent # THROWS EXCEPTION, καμία υπηρεσία δεν θα περνούσε - parentDep: ParentDependent # περνάει το παιδί της υπηρεσίας στον κατασκευαστή - childDep: ChildDependent # μεταβιβάζει το παιδί της υπηρεσίας στον κατασκευαστή + fooDep: FooDependent # το autowiring περνά το child στον κατασκευαστή + barDep: BarDependent # ΠΡΟΚΑΛΕΙ ΕΞΑΙΡΕΣΗ, καμία υπηρεσία δεν ταιριάζει + parentDep: ParentDependent # το autowiring περνά το child στον κατασκευαστή + childDep: ChildDependent # το autowiring περνά το child στον κατασκευαστή ``` diff --git a/dependency-injection/el/configuration.texy b/dependency-injection/el/configuration.texy index c56264af03..03e6ecb64d 100644 --- a/dependency-injection/el/configuration.texy +++ b/dependency-injection/el/configuration.texy @@ -2,31 +2,32 @@ *************************** .[perex] -Επισκόπηση των επιλογών διαμόρφωσης για το δοχείο DI της Nette. +Επισκόπηση των επιλογών διαμόρφωσης για το Nette DI Container. -Αρχείο διαμόρφωσης .[#toc-configuration-file] -============================================= +Αρχείο διαμόρφωσης +================== -Το δοχείο Nette DI είναι εύκολο να ελεγχθεί με τη χρήση αρχείων διαμόρφωσης. Συνήθως είναι γραμμένα σε [μορφή NEON |neon:format]. Συνιστούμε να χρησιμοποιείτε για την επεξεργασία [επεξεργαστές με υποστήριξη |best-practices:editors-and-tools#ide-editor] αυτής της μορφής. +Το Nette DI Container ελέγχεται εύκολα μέσω αρχείων διαμόρφωσης. Αυτά συνήθως γράφονται σε [μορφή NEON |neon:format]. Για την επεξεργασία, συνιστούμε [editors με υποστήριξη |best-practices:editors-and-tools#IDE editor] αυτής της μορφής. <pre> -"decorator .[prism-token prism-atrule]":[#Decorator]: "Decorator .[prism-token prism-comment]"<br> -"di .[prism-token prism-atrule]":[#DI]: "DI Container .[prism-token prism-comment]"<br> -"extensions .[prism-token prism-atrule]":[#Extensions]: "Εγκατάσταση πρόσθετων επεκτάσεων DI .[prism-token prism-comment]"<br> -"includes .[prism-token prism-atrule]":[#Including files]: "Συμπεριλαμβανομένων αρχείων .[prism-token prism-comment]"<br> -"parameters .[prism-token prism-atrule]":[#Parameters]: "Παράμετροι .[prism-token prism-comment]"<br> -"search .[prism-token prism-atrule]":[#Search]: "Αυτόματη καταχώρηση υπηρεσιών .[prism-token prism-comment]"<br> +"decorator .[prism-token prism-atrule]":[#decorator]: "Decorator .[prism-token prism-comment]"<br> +"di .[prism-token prism-atrule]":[#DI]: "DI container .[prism-token prism-comment]"<br> +"extensions .[prism-token prism-atrule]":[#Επεκτάσεις]: "Εγκατάσταση πρόσθετων επεκτάσεων DI .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[#Εισαγωγή αρχείων]: "Εισαγωγή αρχείων .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[#Παράμετροι]: "Παράμετροι .[prism-token prism-comment]"<br> +"search .[prism-token prism-atrule]":[#Αναζήτηση]: "Αυτόματη καταχώρηση υπηρεσιών .[prism-token prism-comment]"<br> "services .[prism-token prism-atrule]":[services]: "Υπηρεσίες .[prism-token prism-comment]" </pre> -Για να γράψετε μια συμβολοσειρά που περιέχει τον χαρακτήρα `%`, you must escape it by doubling it to `%%`. .[note] +.[note] +Για να γράψετε μια συμβολοσειρά που περιέχει τον χαρακτήρα `%`, πρέπει να τον διαφύγετε διπλασιάζοντάς τον σε `%%`. -Παράμετροι .[#toc-parameters] -============================= +Παράμετροι +========== -Μπορείτε να ορίσετε παραμέτρους που μπορούν στη συνέχεια να χρησιμοποιηθούν ως μέρος των ορισμών υπηρεσιών. Αυτό μπορεί να σας βοηθήσει να διαχωρίσετε τις τιμές που θα θέλετε να αλλάζετε πιο τακτικά. +Στη διαμόρφωση, μπορείτε να ορίσετε παραμέτρους που μπορούν στη συνέχεια να χρησιμοποιηθούν ως μέρος των ορισμών υπηρεσιών. Με αυτόν τον τρόπο, μπορείτε να κάνετε τη διαμόρφωση πιο σαφή ή να ενοποιήσετε και να απομονώσετε τιμές που θα αλλάξουν. ```neon parameters: @@ -35,9 +36,9 @@ parameters: password: secret ``` -Μπορείτε να ανατρέξετε στην παράμετρο `foo` μέσω της διεύθυνσης `%foo%` σε οποιοδήποτε άλλο σημείο του αρχείου ρυθμίσεων. Μπορούν επίσης να χρησιμοποιηθούν μέσα σε συμβολοσειρές όπως το `'%wwwDir%/images'`. +Αναφερόμαστε στην παράμετρο `dsn` οπουδήποτε στη διαμόρφωση γράφοντας `%dsn%`. Οι παράμετροι μπορούν να χρησιμοποιηθούν και μέσα σε συμβολοσειρές όπως `'%wwwDir%/images'`. -Οι παράμετροι δεν χρειάζεται να είναι μόνο συμβολοσειρές, μπορούν επίσης να είναι τιμές πίνακα: +Οι παράμετροι δεν χρειάζεται να είναι μόνο συμβολοσειρές ή αριθμοί, μπορούν επίσης να περιέχουν πίνακες: ```neon parameters: @@ -48,32 +49,32 @@ parameters: languages: [cs, en, de] ``` -Μπορείτε να αναφέρεστε σε ένα μόνο κλειδί ως `%mailer.user%`. +Αναφερόμαστε σε ένα συγκεκριμένο κλειδί ως `%mailer.user%`. -Εάν πρέπει να λάβετε την τιμή οποιασδήποτε παραμέτρου στον κώδικά σας, για παράδειγμα στην κλάση σας, τότε περάστε την σε αυτή την κλάση. Για παράδειγμα, στον κατασκευαστή. Δεν υπάρχει κανένα αντικείμενο παγκόσμιας διαμόρφωσης το οποίο να μπορεί η κλάση να ζητήσει τις τιμές των παραμέτρων. Αυτό θα ήταν ενάντια στην αρχή της έγχυσης εξάρτησης. +Εάν χρειάζεστε στον κώδικά σας, για παράδειγμα σε μια κλάση, να μάθετε την τιμή οποιασδήποτε παραμέτρου, τότε περάστε την σε αυτήν την κλάση. Για παράδειγμα, στον κατασκευαστή. Δεν υπάρχει κανένα καθολικό αντικείμενο που να αντιπροσωπεύει τη διαμόρφωση, το οποίο οι κλάσεις θα ρωτούσαν για τις τιμές των παραμέτρων. Αυτό θα παραβίαζε την αρχή του dependency injection. -Υπηρεσίες .[#toc-services] -========================== +Υπηρεσίες +========= -Βλέπε [ξεχωριστό κεφάλαιο |services]. +Βλ. [ξεχωριστό κεφάλαιο |services]. -Διακοσμητής .[#toc-decorator] -============================= +Decorator +========= -Πώς μπορώ να επεξεργαστώ μαζικά όλες τις υπηρεσίες ενός συγκεκριμένου τύπου; Πρέπει να καλέσετε μια συγκεκριμένη μέθοδο για όλους τους παρουσιαστές που κληρονομούν από έναν συγκεκριμένο κοινό πρόγονο; Από εκεί προέρχεται ο διακοσμητής. +Πώς να τροποποιήσετε μαζικά όλες τις υπηρεσίες ενός συγκεκριμένου τύπου; Για παράδειγμα, να καλέσετε μια συγκεκριμένη μέθοδο σε όλους τους presenters που κληρονομούν από έναν συγκεκριμένο κοινό πρόγονο? Γι' αυτό υπάρχει ο decorator. ```neon decorator: - # για όλες τις υπηρεσίες που είναι στιγμιότυπα αυτής της κλάσης ή διεπαφής - App\Presenters\BasePresenter: + # για όλες τις υπηρεσίες που είναι παρουσίες αυτής της κλάσης ή interface + App\Presentation\BasePresenter: setup: - setProjectId(10) # καλέστε αυτή τη μέθοδο - $absoluteUrls = true # και ορίστε τη μεταβλητή ``` -Ο διακοσμητής μπορεί επίσης να χρησιμοποιηθεί για να ορίσετε [ετικέτες |services#Tags] ή να ενεργοποιήσετε [τη λειτουργία inject |services#Inject Mode]. +Ο decorator μπορεί επίσης να χρησιμοποιηθεί για τον ορισμό [tags |services#Tags] ή την ενεργοποίηση της λειτουργίας [inject |services#Λειτουργία Inject]. ```neon decorator: @@ -86,67 +87,79 @@ decorator: DI === -Τεχνικές ρυθμίσεις του δοχείου DI. +Τεχνικές ρυθμίσεις του DI container. ```neon di: - # δείχνει DIC στο Tracy Bar; - debugger: ... # (bool) προεπιλογή true + # εμφάνιση του DIC στο Tracy Bar; + debugger: ... # (bool) η προεπιλογή είναι true - # τύποι παραμέτρων που δεν συνδέετε ποτέ αυτόματα + # τύποι παραμέτρων που δεν γίνονται ποτέ autowired excluded: ... # (string[]) - # η κλάση από την οποία κληρονομεί ο περιέκτης DI - parentClass: ... # (string) προεπιλογή Nette\DI\Container + # επιτρέπεται η lazy δημιουργία υπηρεσιών; + lazy: ... # (bool) η προεπιλογή είναι false + + # κλάση από την οποία κληρονομεί το DI container + parentClass: ... # (string) η προεπιλογή είναι Nette\DI\Container ``` -Εξαγωγή μεταδεδομένων .[#toc-metadata-export] ---------------------------------------------- +Lazy υπηρεσίες .{data-version:3.2.4} +------------------------------------ + +Η ρύθμιση `lazy: true` ενεργοποιεί τη lazy (καθυστερημένη) δημιουργία υπηρεσιών. Αυτό σημαίνει ότι οι υπηρεσίες δεν δημιουργούνται πραγματικά τη στιγμή που τις ζητάμε από το DI container, αλλά τη στιγμή της πρώτης τους χρήσης. Αυτό μπορεί να επιταχύνει την εκκίνηση της εφαρμογής και να μειώσει τις απαιτήσεις μνήμης, καθώς δημιουργούνται μόνο οι υπηρεσίες που είναι πραγματικά απαραίτητες στο συγκεκριμένο request. + +Για μια συγκεκριμένη υπηρεσία, η lazy δημιουργία μπορεί να [αλλάξει |services#Lazy υπηρεσίες]. + +.[note] +Τα lazy αντικείμενα μπορούν να χρησιμοποιηθούν μόνο για κλάσεις χρήστη, όχι για εσωτερικές κλάσεις PHP. Απαιτεί PHP 8.4 ή νεότερη έκδοση. + -Η κλάση DI container περιέχει επίσης πολλά μεταδεδομένα. Μπορείτε να τα μειώσετε μειώνοντας την εξαγωγή μεταδεδομένων. +Εξαγωγή μεταδεδομένων +--------------------- + +Η κλάση του DI container περιέχει επίσης πολλά μεταδεδομένα. Μπορείτε να τη μειώσετε περιορίζοντας την εξαγωγή μεταδεδομένων. ```neon di: export: - # για την εξαγωγή παραμέτρων; - parameters: false # (bool) προεπιλογή true + # εξαγωγή παραμέτρων; + parameters: false # (bool) η προεπιλογή είναι true - # να εξάγει ετικέτες και ποιες; - tags: # (string[]|bool) η προεπιλογή είναι όλες + # εξαγωγή tags και ποια; + tags: # (string[]|bool) η προεπιλογή είναι όλα - event.subscriber - # export data for autowiring and which? + # εξαγωγή δεδομένων για autowiring και ποια; types: # (string[]|bool) η προεπιλογή είναι όλα - Nette\Database\Connection - Symfony\Component\Console\Application ``` -Εάν δεν χρησιμοποιείτε τον πίνακα `$container->parameters`, μπορείτε να απενεργοποιήσετε την εξαγωγή παραμέτρων. Επιπλέον, μπορείτε να εξάγετε μόνο τις ετικέτες μέσω των οποίων λαμβάνετε υπηρεσίες χρησιμοποιώντας τη μέθοδο `$container->findByTag(...)`. -Αν δεν καλείτε καθόλου τη μέθοδο, μπορείτε να απενεργοποιήσετε εντελώς την εξαγωγή ετικετών με τη μέθοδο `false`. +Εάν δεν χρησιμοποιείτε τον πίνακα `$container->getParameters()`, μπορείτε να απενεργοποιήσετε την εξαγωγή παραμέτρων. Επιπλέον, μπορείτε να εξάγετε μόνο τα tags μέσω των οποίων λαμβάνετε υπηρεσίες με τη μέθοδο `$container->findByTag(...)`. Εάν δεν καλείτε καθόλου τη μέθοδο, μπορείτε να απενεργοποιήσετε εντελώς την εξαγωγή tags χρησιμοποιώντας `false`. -Μπορείτε να μειώσετε σημαντικά τα μεταδεδομένα για την [αυτόματη καλωδίωση |autowiring], καθορίζοντας τις κλάσεις που χρησιμοποιείτε ως παράμετρο στη μέθοδο `$container->getByType()`. -Και πάλι, αν δεν καλέσετε καθόλου τη μέθοδο (ή μόνο στο [application:bootstrap] για να λάβετε το `Nette\Application\Application`), μπορείτε να απενεργοποιήσετε εντελώς την εξαγωγή με το `false`. +Μπορείτε να μειώσετε σημαντικά τα μεταδεδομένα για [autowiring |autowiring] αναφέροντας τις κλάσεις που χρησιμοποιείτε ως παράμετρο της μεθόδου `$container->getByType()`. Και πάλι, εάν δεν καλείτε καθόλου τη μέθοδο (ή μόνο στο [bootstrap |application:bootstrapping] για να λάβετε το `Nette\Application\Application`), μπορείτε να απενεργοποιήσετε εντελώς την εξαγωγή χρησιμοποιώντας `false`. -Επεκτάσεις .[#toc-extensions] -============================= +Επεκτάσεις +========== -Καταχώρηση άλλων επεκτάσεων DI. Με αυτόν τον τρόπο προσθέτουμε, για παράδειγμα, την επέκταση DI `Dibi\Bridges\Nette\DibiExtension22` με το όνομα `dibi`: +Καταχώρηση πρόσθετων επεκτάσεων DI. Με αυτόν τον τρόπο προσθέτουμε, για παράδειγμα, την επέκταση DI `Dibi\Bridges\Nette\DibiExtension22` με το όνομα `dibi` ```neon extensions: dibi: Dibi\Bridges\Nette\DibiExtension22 ``` -Στη συνέχεια, τη διαμορφώνουμε στο τμήμα της που ονομάζεται επίσης `dibi`: +Στη συνέχεια, τη διαμορφώνουμε στην ενότητα `dibi`: ```neon dibi: host: localhost ``` -Μπορείτε επίσης να προσθέσετε μια κλάση επέκτασης με παραμέτρους: +Ως επέκταση μπορεί να προστεθεί και μια κλάση που έχει παραμέτρους: ```neon extensions: @@ -154,10 +167,10 @@ extensions: ``` -Συμπεριλαμβανομένων αρχείων .[#toc-including-files] -=================================================== +Εισαγωγή αρχείων +================ -Πρόσθετα αρχεία ρυθμίσεων μπορούν να εισαχθούν στην ενότητα `includes`: +Μπορούμε να εισάγουμε άλλα αρχεία διαμόρφωσης στην ενότητα `includes`: ```neon includes: @@ -166,7 +179,7 @@ includes: - presenters.neon ``` - `parameters.php` Η διαμόρφωση μπορεί επίσης να γραφτεί σε ένα αρχείο PHP, το οποίο την επιστρέφει ως πίνακα: +Το όνομα `parameters.php` δεν είναι τυπογραφικό λάθος, η διαμόρφωση μπορεί επίσης να γραφτεί σε ένα αρχείο PHP, το οποίο την επιστρέφει ως πίνακα: ```php <?php @@ -179,32 +192,27 @@ return [ ]; ``` -Εάν στοιχεία με τα ίδια κλειδιά εμφανίζονται μέσα σε αρχεία ρυθμίσεων, θα [αντικατασταθούν ή θα συγχωνευθούν |#Merging] στην περίπτωση των πινάκων. Το αρχείο που περιλαμβάνεται αργότερα έχει υψηλότερη προτεραιότητα από το προηγούμενο. Το αρχείο στο οποίο παρατίθεται το τμήμα `includes` έχει υψηλότερη προτεραιότητα από τα αρχεία που περιλαμβάνονται σε αυτό. +Εάν εμφανιστούν στοιχεία με τα ίδια κλειδιά σε αρχεία διαμόρφωσης, θα αντικατασταθούν ή, στην περίπτωση [πινάκων, θα συγχωνευθούν |#Συγχώνευση]. Το αρχείο που εισάγεται αργότερα έχει υψηλότερη προτεραιότητα από το προηγούμενο. Το αρχείο στο οποίο αναφέρεται η ενότητα `includes` έχει υψηλότερη προτεραιότητα από τα αρχεία που εισάγονται σε αυτό. -Αναζήτηση .[#toc-search] -======================== +Αναζήτηση +========= -Η αυτόματη προσθήκη υπηρεσιών στο δοχείο DI κάνει την εργασία εξαιρετικά ευχάριστη. Η Nette προσθέτει αυτόματα παρουσιαστές στο δοχείο, αλλά μπορείτε εύκολα να προσθέσετε οποιεσδήποτε άλλες κλάσεις. +Η αυτόματη προσθήκη υπηρεσιών στο DI container διευκολύνει εξαιρετικά την εργασία. Το Nette προσθέτει αυτόματα presenters στο container, αλλά μπορεί εύκολα να προσθέσει και οποιεσδήποτε άλλες κλάσεις. -Απλά καθορίστε σε ποιους καταλόγους (και υποκαταλόγους) θα πρέπει να αναζητηθούν οι κλάσεις: +Αρκεί να αναφέρετε σε ποιους καταλόγους (και υποκαταλόγους) πρέπει να αναζητήσει κλάσεις: ```neon search: - # επιλέγετε μόνοι σας τα ονόματα των τμημάτων - myForms: - in: %appDir%/Forms - - model: - in: %appDir%/Model + - in: %appDir%/Forms + - in: %appDir%/Model ``` -Συνήθως, όμως, δεν θέλουμε να προσθέσουμε όλες τις κλάσεις και τις διεπαφές, οπότε μπορούμε να τις φιλτράρουμε: +Συνήθως, όμως, δεν θέλουμε να προσθέσουμε απολύτως όλες τις κλάσεις και τα interfaces, γι' αυτό μπορούμε να τα φιλτράρουμε: ```neon search: - myForms: - in: %appDir%/Forms + - in: %appDir%/Forms # φιλτράρισμα με βάση το όνομα αρχείου (string|string[]) files: @@ -215,42 +223,43 @@ search: - *Factory ``` -Ή μπορούμε να επιλέξουμε κλάσεις που κληρονομούν ή υλοποιούν τουλάχιστον μία από τις ακόλουθες κλάσεις: +Ή μπορούμε να επιλέξουμε κλάσεις που κληρονομούν ή υλοποιούν τουλάχιστον μία από τις αναφερόμενες κλάσεις: ```neon search: - myForms: + - in: %appDir% extends: - App\*Form implements: - App\*FormInterface ``` -Μπορείτε επίσης να ορίσετε αρνητικούς κανόνες, δηλαδή μάσκες ονομάτων κλάσεων ή προγόνων και αν συμμορφώνονται, η υπηρεσία δεν θα προστεθεί στο δοχείο DI: +Μπορούν επίσης να οριστούν κανόνες εξαίρεσης, δηλ. μάσκες ονόματος κλάσης ή κληρονομικοί πρόγονοι, που εάν ταιριάζουν, η υπηρεσία δεν προστίθεται στο DI container: ```neon search: - myForms: + - in: %appDir% exclude: + files: ... classes: ... extends: ... implements: ... ``` -Μπορούν να οριστούν ετικέτες για τις υπηρεσίες που προστίθενται: +Σε όλες τις υπηρεσίες μπορούν να οριστούν tags: ```neon search: - myForms: + - in: %appDir% tags: ... ``` -Συγχώνευση .[#toc-merging] -========================== +Συγχώνευση +========== -Εάν στοιχεία με τα ίδια κλειδιά εμφανίζονται σε περισσότερα αρχεία ρυθμίσεων, θα αντικατασταθούν ή θα συγχωνευθούν στην περίπτωση των πινάκων. Το αρχείο που περιλαμβάνεται αργότερα έχει μεγαλύτερη προτεραιότητα. +Εάν εμφανιστούν στοιχεία με τα ίδια κλειδιά σε περισσότερα αρχεία διαμόρφωσης, θα αντικατασταθούν ή, στην περίπτωση πινάκων, θα συγχωνευθούν. Το αρχείο που εισάγεται αργότερα έχει υψηλότερη προτεραιότητα από το προηγούμενο. <table class=table> <tr> @@ -283,11 +292,11 @@ items: </tr> </table> -Για να αποτρέψετε τη συγχώνευση ενός συγκεκριμένου πίνακα χρησιμοποιήστε θαυμαστικό αμέσως μετά το όνομα του πίνακα: +Στους πίνακες, η συγχώνευση μπορεί να αποτραπεί αναφέροντας ένα θαυμαστικό μετά το όνομα του κλειδιού: <table class=table> <tr> - <th width=33%>neon</th> + <th width=33%>config1.neon</th> <th width=33%>config2.neon</th> <th>αποτέλεσμα</th> </tr> @@ -314,5 +323,4 @@ items: </tr> </table> - -{{maintitle: }} +{{maintitle: Διαμόρφωση Dependency Injection}} diff --git a/dependency-injection/el/container.texy b/dependency-injection/el/container.texy index 81d545b17e..9b371ad0da 100644 --- a/dependency-injection/el/container.texy +++ b/dependency-injection/el/container.texy @@ -2,15 +2,15 @@ ************************* .[perex] -Το Dependency injection container (DIC) είναι μια κλάση που μπορεί να ενσαρκώνει και να διαμορφώνει αντικείμενα. +Ένα dependency injection container (DIC) είναι μια κλάση που μπορεί να δημιουργήσει και να διαμορφώσει αντικείμενα. -Μπορεί να σας εκπλήξει, αλλά σε πολλές περιπτώσεις δεν χρειάζεστε ένα dependency injection container για να εκμεταλλευτείτε το dependency injection (DI για συντομία). Εξάλλου, ακόμη και σε [προηγούμενο |introduction] κεφάλαιο δείξαμε συγκεκριμένα παραδείγματα DI και δεν χρειαζόταν κανένας περιέκτης. +Μπορεί να σας εκπλήξει, αλλά σε πολλές περιπτώσεις δεν χρειάζεστε ένα dependency injection container για να επωφεληθείτε από το dependency injection (συντομογραφία DI). Άλλωστε, ακόμη και στο [εισαγωγικό κεφάλαιο |introduction] δείξαμε το DI με συγκεκριμένα παραδείγματα και δεν χρειαζόταν κανένα container. -Ωστόσο, αν πρέπει να διαχειριστείτε έναν μεγάλο αριθμό διαφορετικών αντικειμένων με πολλές εξαρτήσεις, ένας περιέκτης dependency injection θα είναι πραγματικά χρήσιμος. Κάτι που ίσως ισχύει για εφαρμογές ιστού που είναι χτισμένες σε ένα πλαίσιο. +Ωστόσο, εάν χρειάζεται να διαχειριστείτε μεγάλο αριθμό διαφορετικών αντικειμένων με πολλές εξαρτήσεις, ένα dependency injection container θα είναι πραγματικά χρήσιμο. Αυτό ισχύει, για παράδειγμα, για τις web εφαρμογές που βασίζονται σε ένα framework. -Στο προηγούμενο κεφάλαιο, παρουσιάσαμε τις κλάσεις `Article` και `UserController`. Και οι δύο έχουν κάποιες εξαρτήσεις, δηλαδή τη βάση δεδομένων και το εργοστάσιο `ArticleFactory`. Και για αυτές τις κλάσεις, θα δημιουργήσουμε τώρα έναν περιέκτη. Φυσικά, για ένα τόσο απλό παράδειγμα, δεν έχει νόημα να έχουμε ένα δοχείο. Αλλά θα δημιουργήσουμε έναν για να δείξουμε πώς φαίνεται και πώς λειτουργεί. +Στο προηγούμενο κεφάλαιο, παρουσιάσαμε τις κλάσεις `Article` και `UserController`. Και οι δύο έχουν κάποιες εξαρτήσεις, δηλαδή τη βάση δεδομένων και το factory `ArticleFactory`. Και για αυτές τις κλάσεις θα δημιουργήσουμε τώρα ένα container. Φυσικά, για ένα τόσο απλό παράδειγμα, δεν έχει νόημα να έχουμε ένα container. Αλλά θα το δημιουργήσουμε για να δείξουμε πώς μοιάζει και πώς λειτουργεί. -Εδώ είναι ένας απλός σκληρά κωδικοποιημένος περιέκτης για το παραπάνω παράδειγμα: +Εδώ είναι ένα απλό hardcoded container για το αναφερόμενο παράδειγμα: ```php class Container @@ -39,9 +39,9 @@ $container = new Container; $controller = $container->createUserController(); ``` -Το δοχείο τα γνωρίζει όλα αυτά. Οι εξαρτήσεις εγχέονται αυτόματα από τον περιέκτη. Αυτή είναι η δύναμή του. +Απλώς ρωτάμε το container για το αντικείμενο και δεν χρειάζεται πλέον να γνωρίζουμε τίποτα για το πώς να το δημιουργήσουμε και ποιες είναι οι εξαρτήσεις του. όλα αυτά τα γνωρίζει το container. Οι εξαρτήσεις εισάγονται αυτόματα από το container. Σε αυτό έγκειται η δύναμή του. -Μέχρι στιγμής, ο περιέκτης έχει τα πάντα κωδικοποιημένα. Οπότε κάνουμε το επόμενο βήμα και προσθέτουμε παραμέτρους για να κάνουμε τον περιέκτη πραγματικά χρήσιμο: +Το container έχει προς το παρόν όλα τα δεδομένα γραμμένα απευθείας στον κώδικα. Θα κάνουμε λοιπόν το επόμενο βήμα και θα προσθέσουμε παραμέτρους, ώστε το container να είναι πραγματικά χρήσιμο: ```php class Container @@ -70,9 +70,9 @@ $container = new Container([ ]); ``` -Οι έξυπνοι αναγνώστες μπορεί να έχουν παρατηρήσει ένα πρόβλημα. Κάθε φορά που παίρνω ένα αντικείμενο `UserController`, δημιουργείται επίσης μια νέα περίπτωση `ArticleFactory` και μια νέα βάση δεδομένων. Σίγουρα δεν το θέλουμε αυτό. +Οι προσεκτικοί αναγνώστες μπορεί να έχουν παρατηρήσει ένα συγκεκριμένο πρόβλημα. Κάθε φορά που λαμβάνω ένα αντικείμενο `UserController`, δημιουργείται επίσης μια νέα παρουσία του `ArticleFactory` και της βάσης δεδομένων. Αυτό σίγουρα δεν το θέλουμε. -Έτσι προσθέτουμε μια μέθοδο `getService()` που θα επιστρέφει τα ίδια instances ξανά και ξανά: +Θα προσθέσουμε λοιπόν μια μέθοδο `getService()`, η οποία θα επιστρέφει πάντα τις ίδιες παρουσίες: ```php class Container @@ -87,7 +87,7 @@ class Container public function getService(string $name): object { if (!isset($this->services[$name])) { - // getService('Database') καλεί createDatabase() + // το getService('Database') θα καλέσει το createDatabase() $method = 'create' . $name; $this->services[$name] = $this->$method(); } @@ -98,9 +98,9 @@ class Container } ``` -Η πρώτη κλήση π.χ. της `$container->getService('Database')` θα έχει ως αποτέλεσμα η `createDatabase()` να δημιουργήσει ένα αντικείμενο βάσης δεδομένων, το οποίο θα αποθηκεύσει στον πίνακα `$services` και θα το επιστρέψει άμεσα στην επόμενη κλήση. +Κατά την πρώτη κλήση, π.χ. `$container->getService('Database')`, θα ζητήσει από το `createDatabase()` να δημιουργήσει το αντικείμενο της βάσης δεδομένων, το οποίο θα αποθηκεύσει στον πίνακα `$services` και κατά την επόμενη κλήση θα το επιστρέψει απευθείας. -Τροποποιούμε επίσης το υπόλοιπο δοχείο ώστε να χρησιμοποιεί το `getService()`: +Θα τροποποιήσουμε και το υπόλοιπο container, ώστε να χρησιμοποιεί το `getService()`: ```php class Container @@ -119,9 +119,9 @@ class Container } ``` -Παρεμπιπτόντως, ο όρος service αναφέρεται σε κάθε αντικείμενο που διαχειρίζεται ο περιέκτης. Εξ ου και το όνομα της μεθόδου `getService()`. +Παρεμπιπτόντως, ο όρος υπηρεσία (service) αναφέρεται σε οποιοδήποτε αντικείμενο διαχειρίζεται το container. Γι' αυτό και το όνομα της μεθόδου `getService()`. -Έγινε. Έχουμε ένα πλήρως λειτουργικό DI container! Και μπορούμε να το χρησιμοποιήσουμε: +Έτοιμο. Έχουμε ένα πλήρως λειτουργικό DI container! Και μπορούμε να το χρησιμοποιήσουμε: ```php $container = new Container([ @@ -134,9 +134,9 @@ $controller = $container->getService('UserController'); $database = $container->getService('Database'); ``` -Όπως μπορείτε να δείτε, δεν είναι δύσκολο να γράψετε ένα DIC. Είναι αξιοσημείωτο ότι τα ίδια τα αντικείμενα δεν γνωρίζουν ότι ένα δοχείο τα δημιουργεί. Έτσι, είναι δυνατόν να δημιουργηθεί οποιοδήποτε αντικείμενο στην PHP με αυτόν τον τρόπο χωρίς να επηρεαστεί ο πηγαίος κώδικάς τους. +Όπως βλέπετε, η σύνταξη ενός DIC δεν είναι κάτι περίπλοκο. Αξίζει να θυμηθούμε ότι τα ίδια τα αντικείμενα δεν γνωρίζουν ότι τα δημιουργεί κάποιο container. Έτσι, είναι δυνατόν να δημιουργηθεί με αυτόν τον τρόπο οποιοδήποτε αντικείμενο στην PHP χωρίς παρέμβαση στον πηγαίο κώδικά του. -Η χειροκίνητη δημιουργία και συντήρηση μιας κλάσης εμπορευματοκιβωτίου μπορεί να γίνει ένας εφιάλτης μάλλον γρήγορα. Επομένως, στο επόμενο κεφάλαιο θα μιλήσουμε για [το Nette DI Container |nette-container], το οποίο μπορεί να δημιουργεί και να ενημερώνεται σχεδόν αυτόματα. +Η χειροκίνητη δημιουργία και συντήρηση της κλάσης του container μπορεί γρήγορα να γίνει εφιάλτης. Στο επόμενο κεφάλαιο, θα μιλήσουμε λοιπόν για το [Nette DI Container |nette-container], το οποίο μπορεί να δημιουργείται και να ενημερώνεται σχεδόν από μόνο του. -{{maintitle: Τι είναι το Dependency Injection Container; }} +{{maintitle: Τι είναι το dependency injection container;}} diff --git a/dependency-injection/el/extensions.texy b/dependency-injection/el/extensions.texy index 969759ea52..3f37bb6398 100644 --- a/dependency-injection/el/extensions.texy +++ b/dependency-injection/el/extensions.texy @@ -2,7 +2,7 @@ ************************************* .[perex] -Η δημιουργία ενός δοχείου DI εκτός από τα αρχεία ρυθμίσεων επηρεάζει και τα λεγόμενα *επεκτάσεις*. Τις ενεργοποιούμε στο αρχείο διαμόρφωσης στην ενότητα `extensions`. +Η δημιουργία του DI container, εκτός από τα αρχεία διαμόρφωσης, επηρεάζεται και από τις λεγόμενες *επεκτάσεις*. Τις ενεργοποιούμε στο αρχείο διαμόρφωσης στην ενότητα `extensions`. Έτσι προσθέτουμε την επέκταση που αντιπροσωπεύεται από την κλάση `BlogExtension` με το όνομα `blog`: @@ -11,7 +11,7 @@ extensions: blog: BlogExtension ``` -Κάθε επέκταση μεταγλωττιστή κληρονομεί από την [api:Nette\DI\CompilerExtension] και μπορεί να υλοποιήσει τις ακόλουθες μεθόδους που καλούνται κατά τη διάρκεια της μεταγλώττισης του DI: +Κάθε επέκταση του compiler κληρονομεί από το [api:Nette\DI\CompilerExtension] και μπορεί να υλοποιήσει τις ακόλουθες μεθόδους, οι οποίες καλούνται διαδοχικά κατά τη συναρμολόγηση του DI container: 1. getConfigSchema() 2. loadConfiguration() @@ -22,18 +22,18 @@ extensions: getConfigSchema() .[method] =========================== -Αυτή η μέθοδος καλείται πρώτη. Ορίζει το σχήμα που χρησιμοποιείται για την επικύρωση των παραμέτρων διαμόρφωσης. +Αυτή η μέθοδος καλείται πρώτη. Ορίζει το schema για την επικύρωση των παραμέτρων διαμόρφωσης. -Οι επεκτάσεις διαμορφώνονται σε ένα τμήμα του οποίου το όνομα είναι το ίδιο με αυτό στο οποίο προστέθηκε η επέκταση, π.χ. `blog`. +Διαμορφώνουμε την επέκταση στην ενότητα της οποίας το όνομα είναι το ίδιο με αυτό με το οποίο προστέθηκε η επέκταση, δηλαδή `blog`: ```neon -# ίδιο όνομα με την επέκτασή μου +# ίδιο όνομα με την επέκταση blog: postsPerPage: 10 - comments: false + allowComments: false ``` -Θα ορίσουμε ένα σχήμα που θα περιγράφει όλες τις επιλογές διαμόρφωσης, συμπεριλαμβανομένων των τύπων τους, των αποδεκτών τιμών και ενδεχομένως των προεπιλεγμένων τιμών: +Δημιουργούμε ένα schema που περιγράφει όλες τις επιλογές διαμόρφωσης, συμπεριλαμβανομένων των τύπων τους, των επιτρεπόμενων τιμών και, ενδεχομένως, των προεπιλεγμένων τιμών τους: ```php use Nette\Schema\Expect; @@ -50,9 +50,9 @@ class BlogExtension extends Nette\DI\CompilerExtension } ``` -Ανατρέξτε στο [Σχήμα |schema:] για τεκμηρίωση. Επιπλέον, μπορείτε να καθορίσετε ποιες επιλογές μπορούν να είναι [δυναμικές |application:bootstrap#Dynamic Parameters] χρησιμοποιώντας `dynamic()`, για παράδειγμα `Expect::int()->dynamic()`. +Θα βρείτε την τεκμηρίωση στη σελίδα [Schema |schema:]. Επιπλέον, μπορείτε να καθορίσετε ποιες επιλογές μπορούν να είναι [δυναμικές |application:bootstrapping#Δυναμικές Παράμετροι] χρησιμοποιώντας το `dynamic()`, π.χ. `Expect::int()->dynamic()`. -Έχουμε πρόσβαση στη διαμόρφωση μέσω του `$this->config`, το οποίο είναι ένα αντικείμενο `stdClass`: +Έχουμε πρόσβαση στη διαμόρφωση μέσω της μεταβλητής `$this->config`, η οποία είναι ένα αντικείμενο `stdClass`: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -71,7 +71,7 @@ class BlogExtension extends Nette\DI\CompilerExtension loadConfiguration() .[method] ============================= -Αυτή η μέθοδος χρησιμοποιείται για την προσθήκη υπηρεσιών στο δοχείο. Αυτό γίνεται με τη μέθοδο [api:Nette\DI\ContainerBuilder]: +Χρησιμοποιείται για την προσθήκη υπηρεσιών στο container. Γι' αυτό χρησιμοποιείται το [api:Nette\DI\ContainerBuilder]: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -80,25 +80,25 @@ class BlogExtension extends Nette\DI\CompilerExtension { $builder = $this->getContainerBuilder(); $builder->addDefinition($this->prefix('articles')) - ->setFactory(App\Model\HomepageArticles::class, ['@connection']) // ή setCreator() + ->setFactory(App\Model\HomepageArticles::class, ['@connection']) // or setCreator() ->addSetup('setLogger', ['@logger']); } } ``` -Η σύμβαση είναι να προτάσσονται οι υπηρεσίες που προστίθενται από μια επέκταση με το όνομά της, ώστε να μην προκύπτουν συγκρούσεις ονομάτων. Αυτό γίνεται με το `prefix()`, οπότε αν η επέκταση ονομάζεται "blog", η υπηρεσία θα ονομάζεται `blog.articles`. +Η σύμβαση είναι να προτάσσεται στις υπηρεσίες που προστίθενται από την επέκταση το όνομά της, ώστε να μην προκύπτουν συγκρούσεις ονομάτων. Αυτό το κάνει η μέθοδος `prefix()`, οπότε αν η επέκταση ονομάζεται `blog`, η υπηρεσία θα ονομάζεται `blog.articles`. -Αν χρειαστεί να μετονομάσουμε μια υπηρεσία, μπορούμε να δημιουργήσουμε ένα ψευδώνυμο με το αρχικό της όνομα για να διατηρήσουμε την προς τα πίσω συμβατότητα. Ομοίως αυτό κάνει η Nette για παράδειγμα για το `routing.router`, το οποίο είναι επίσης διαθέσιμο με το προηγούμενο όνομα `router`. +Εάν χρειαστεί να μετονομάσουμε μια υπηρεσία, μπορούμε, για λόγους διατήρησης της συμβατότητας προς τα πίσω, να δημιουργήσουμε ένα ψευδώνυμο (alias) με το αρχικό όνομα. Παρόμοια το κάνει το Nette, π.χ. για την υπηρεσία `routing.router`, η οποία είναι διαθέσιμη και με το προηγούμενο όνομα `router`. ```php $builder->addAlias('router', 'routing.router'); ``` -Ανάκτηση υπηρεσιών από ένα αρχείο .[#toc-retrieve-services-from-a-file] ------------------------------------------------------------------------ +Φόρτωση υπηρεσιών από αρχείο +---------------------------- -Μπορούμε να δημιουργήσουμε υπηρεσίες χρησιμοποιώντας το API του ContainerBuilder, αλλά μπορούμε επίσης να τις προσθέσουμε μέσω του γνωστού αρχείου ρυθμίσεων NEON και της ενότητας `services`. Το πρόθεμα `@extension` αντιπροσωπεύει την τρέχουσα επέκταση. +Δεν χρειάζεται να δημιουργούμε υπηρεσίες μόνο μέσω του API της κλάσης ContainerBuilder, αλλά και με τη γνωστή σύνταξη που χρησιμοποιείται στο αρχείο διαμόρφωσης NEON στην ενότητα services. Το πρόθεμα `@extension` αντιπροσωπεύει την τρέχουσα επέκταση. ```neon services: @@ -112,7 +112,7 @@ services: create: MyBlog\Components\ArticlesList(@extension.articles) ``` -Θα προσθέσουμε υπηρεσίες με αυτόν τον τρόπο: +Φορτώνουμε τις υπηρεσίες: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -121,7 +121,7 @@ class BlogExtension extends Nette\DI\CompilerExtension { $builder = $this->getContainerBuilder(); - // φορτώνει το αρχείο ρυθμίσεων για την επέκταση + // φόρτωση του αρχείου διαμόρφωσης για την επέκταση $this->compiler->loadDefinitionsFromConfig( $this->loadFromFile(__DIR__ . '/blog.neon')['services'], ); @@ -133,7 +133,7 @@ class BlogExtension extends Nette\DI\CompilerExtension beforeCompile() .[method] ========================= -Η μέθοδος καλείται όταν ο περιέκτης περιέχει όλες τις υπηρεσίες που έχουν προστεθεί από τις επιμέρους επεκτάσεις στις μεθόδους `loadConfiguration` καθώς και τα αρχεία ρυθμίσεων του χρήστη. Σε αυτή τη φάση της συναρμολόγησης, μπορούμε στη συνέχεια να τροποποιήσουμε τους ορισμούς των υπηρεσιών ή να προσθέσουμε συνδέσμους μεταξύ τους. Μπορείτε να χρησιμοποιήσετε τη μέθοδο `findByTag()` για την αναζήτηση υπηρεσιών με βάση τις ετικέτες ή τη μέθοδο `findByType()` για την αναζήτηση με βάση την κλάση ή τη διεπαφή. +Η μέθοδος καλείται τη στιγμή που το container περιέχει όλες τις υπηρεσίες που προστέθηκαν από τις μεμονωμένες επεκτάσεις στις μεθόδους `loadConfiguration` καθώς και από τα αρχεία διαμόρφωσης χρήστη. Σε αυτή τη φάση της συναρμολόγησης, μπορούμε λοιπόν να τροποποιήσουμε τους ορισμούς των υπηρεσιών ή να συμπληρώσουμε τις συνδέσεις μεταξύ τους. Για την αναζήτηση υπηρεσιών στο container με βάση τα tags, μπορεί να χρησιμοποιηθεί η μέθοδος `findByTag()`, ενώ με βάση την κλάση ή το interface, η μέθοδος `findByType()`. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -153,7 +153,7 @@ class BlogExtension extends Nette\DI\CompilerExtension afterCompile() .[method] ======================== -Σε αυτή τη φάση, η κλάση εμπορευματοκιβωτίου έχει ήδη δημιουργηθεί ως αντικείμενο [ClassType |php-generator:#classes], περιέχει όλες τις μεθόδους που δημιουργεί η υπηρεσία και είναι έτοιμη για προσωρινή αποθήκευση ως αρχείο PHP. Μπορούμε ακόμα να επεξεργαστούμε τον κώδικα της κλάσης που προκύπτει σε αυτό το σημείο. +Σε αυτή τη φάση, η κλάση του container έχει ήδη δημιουργηθεί με τη μορφή αντικειμένου [ClassType |php-generator:#Κλάσεις], περιέχει όλες τις μεθόδους που δημιουργούν τις υπηρεσίες και είναι έτοιμη για εγγραφή στην cache. Τον τελικό κώδικα της κλάσης μπορούμε ακόμα να τον τροποποιήσουμε σε αυτή τη στιγμή. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -167,24 +167,24 @@ class BlogExtension extends Nette\DI\CompilerExtension ``` -$initialization .[wiki-method] -============================== +$initialization .[method] +========================= -Ο διαμορφωτής καλεί τον κώδικα αρχικοποίησης μετά τη [δημιουργία του περιέκτη |application:bootstrap#index.php], ο οποίος δημιουργείται με εγγραφή σε ένα αντικείμενο `$this->initialization` χρησιμοποιώντας τη [μέθοδο addBody() |php-generator:#method-and-function-body]. +Η κλάση Configurator, μετά τη [δημιουργία του container |application:bootstrapping#index.php], καλεί τον κώδικα αρχικοποίησης, ο οποίος δημιουργείται με εγγραφή στο αντικείμενο `$this->initialization` χρησιμοποιώντας τη [μέθοδο addBody() |php-generator:#Σώματα μεθόδων και συναρτήσεων]. -Θα δείξουμε ένα παράδειγμα για το πώς να ξεκινήσετε μια συνεδρία ή να ξεκινήσετε υπηρεσίες που έχουν την ετικέτα `run` χρησιμοποιώντας κώδικα αρχικοποίησης: +Ας δείξουμε ένα παράδειγμα για το πώς, για παράδειγμα, με τον κώδικα αρχικοποίησης να ξεκινήσουμε τη session ή να εκκινήσουμε υπηρεσίες που έχουν το tag `run`: ```php class BlogExtension extends Nette\DI\CompilerExtension { public function loadConfiguration() { - // αυτόματη εκκίνηση συνεδρίας + // αυτόματη εκκίνηση της session if ($this->config->session->autoStart) { $this->initialization->addBody('$this->getService("session")->start()'); } - // οι υπηρεσίες με ετικέτα 'run' πρέπει να δημιουργούνται μετά την ενσάρκωση του δοχείου + // οι υπηρεσίες με tag run πρέπει να δημιουργηθούν μετά την παρουσίαση του container $builder = $this->getContainerBuilder(); foreach ($builder->findByTag('run') as $name => $foo) { $this->initialization->addBody('$this->getService(?);', [$name]); diff --git a/dependency-injection/el/factory.texy b/dependency-injection/el/factory.texy index cb608707a4..54f04fde20 100644 --- a/dependency-injection/el/factory.texy +++ b/dependency-injection/el/factory.texy @@ -1,12 +1,12 @@ -Παραγόμενα εργοστάσια -********************* +Δημιουργημένα Factories +*********************** .[perex] -Το Nette DI μπορεί να παράγει αυτόματα κώδικα εργοστασίων με βάση τη διεπαφή, γεγονός που σας απαλλάσσει από τη συγγραφή κώδικα. +Το Nette DI μπορεί να δημιουργήσει αυτόματα κώδικα factory βάσει interfaces, εξοικονομώντας σας τη συγγραφή κώδικα. -Ένα εργοστάσιο είναι μια κλάση που δημιουργεί και διαμορφώνει αντικείμενα. Επομένως, τους μεταβιβάζει και τις εξαρτήσεις τους. Μην συγχέετε με το πρότυπο σχεδίασης *factory method*, το οποίο περιγράφει έναν συγκεκριμένο τρόπο χρήσης των εργοστασίων και δεν σχετίζεται με αυτό το θέμα. +Ένα factory είναι μια κλάση που παράγει και διαμορφώνει αντικείμενα. Τους περνάει δηλαδή και τις εξαρτήσεις τους. Μην το συγχέετε με το σχεδιαστικό πρότυπο *factory method*, το οποίο περιγράφει έναν συγκεκριμένο τρόπο χρήσης των factories και δεν σχετίζεται με αυτό το θέμα. -Έχουμε δείξει πώς μοιάζει ένα τέτοιο εργοστάσιο στο [εισαγωγικό |introduction#factory] κεφάλαιο: +Πώς μοιάζει ένα τέτοιο factory, το δείξαμε στο [εισαγωγικό κεφάλαιο |introduction#Factory]: ```php class ArticleFactory @@ -23,7 +23,7 @@ class ArticleFactory } ``` -Το Nette DI μπορεί να παράγει αυτόματα κώδικα εργοστασίου. Το μόνο που έχετε να κάνετε είναι να δημιουργήσετε μια διεπαφή και το Nette DI θα δημιουργήσει μια υλοποίηση. Η διεπαφή πρέπει να έχει ακριβώς μία μέθοδο με το όνομα `create` και να δηλώνει έναν τύπο επιστροφής: +Το Nette DI μπορεί να δημιουργήσει αυτόματα τον κώδικα των factories. Το μόνο που έχετε να κάνετε είναι να δημιουργήσετε ένα interface και το Nette DI θα δημιουργήσει την υλοποίηση. Το interface πρέπει να έχει ακριβώς μία μέθοδο με το όνομα `create` και να δηλώνει τον τύπο επιστροφής: ```php interface ArticleFactory @@ -32,7 +32,7 @@ interface ArticleFactory } ``` -Έτσι, το εργοστάσιο `ArticleFactory` έχει μια μέθοδο `create` που δημιουργεί αντικείμενα `Article`. Η κλάση `Article` μπορεί να μοιάζει, για παράδειγμα, με την ακόλουθη: +Δηλαδή, το factory `ArticleFactory` έχει μια μέθοδο `create`, η οποία δημιουργεί αντικείμενα `Article`. Η κλάση `Article` μπορεί να μοιάζει κάπως έτσι: ```php class Article @@ -44,16 +44,16 @@ class Article } ``` -Προσθέστε το εργοστάσιο στο αρχείο ρυθμίσεων: +Προσθέτουμε το factory στο αρχείο διαμόρφωσης: ```neon services: - ArticleFactory ``` -Το Nette DI θα δημιουργήσει την αντίστοιχη υλοποίηση του εργοστασίου. +Το Nette DI θα δημιουργήσει την αντίστοιχη υλοποίηση του factory. -Έτσι, στον κώδικα που χρησιμοποιεί το εργοστάσιο, ζητάμε το αντικείμενο μέσω διεπαφής και το Nette DI χρησιμοποιεί την υλοποίηση που παράγεται: +Στον κώδικα που χρησιμοποιεί το factory, ζητάμε λοιπόν το αντικείμενο σύμφωνα με το interface και το Nette DI θα χρησιμοποιήσει τη δημιουργημένη υλοποίηση: ```php class UserController @@ -65,17 +65,17 @@ class UserController public function foo() { - // αφήστε το εργοστάσιο να δημιουργήσει ένα αντικείμενο + // αφήνουμε το factory να δημιουργήσει το αντικείμενο $article = $this->articleFactory->create(); } } ``` -Παραμετροποιημένο εργοστάσιο .[#toc-parameterized-factory] -========================================================== +Παραμετροποιημένο factory +========================= -Η μέθοδος του εργοστασίου `create` μπορεί να δέχεται παραμέτρους τις οποίες στη συνέχεια μεταβιβάζει στον κατασκευαστή. Για παράδειγμα, ας προσθέσουμε ένα αναγνωριστικό συγγραφέα άρθρου στην κλάση `Article`: +Η μέθοδος του factory `create` μπορεί να δέχεται παραμέτρους, τις οποίες στη συνέχεια περνά στον κατασκευαστή. Ας συμπληρώσουμε, για παράδειγμα, την κλάση `Article` με το ID του συγγραφέα του άρθρου: ```php class Article @@ -88,7 +88,7 @@ class Article } ``` -Θα προσθέσουμε επίσης την παράμετρο στο εργοστάσιο: +Προσθέτουμε την παράμετρο και στο factory: ```php interface ArticleFactory @@ -97,13 +97,13 @@ interface ArticleFactory } ``` -Επειδή η παράμετρος στον κατασκευαστή και η παράμετρος στο εργοστάσιο έχουν το ίδιο όνομα, η Nette DI θα τις περάσει αυτόματα. +Χάρη στο γεγονός ότι η παράμετρος στον κατασκευαστή και η παράμετρος στο factory ονομάζονται το ίδιο, το Nette DI τις περνά εντελώς αυτόματα. -Προηγμένος ορισμός .[#toc-advanced-definition] -============================================== +Προηγμένος ορισμός +================== -Ο ορισμός μπορεί επίσης να γραφτεί σε μορφή πολλαπλών γραμμών χρησιμοποιώντας το πλήκτρο `implement`: +Ο ορισμός μπορεί να γραφτεί και σε πολυγραμμική μορφή χρησιμοποιώντας το κλειδί `implement`: ```neon services: @@ -111,9 +111,9 @@ services: implement: ArticleFactory ``` -Όταν γράφετε με αυτόν τον μακρύτερο τρόπο, είναι δυνατόν να παρέχετε πρόσθετα ορίσματα για τον κατασκευαστή στο κλειδί `arguments` και πρόσθετες ρυθμίσεις χρησιμοποιώντας το `setup`, όπως ακριβώς και για τις κανονικές υπηρεσίες. +Κατά τη σύνταξη με αυτόν τον μακρύτερο τρόπο, είναι δυνατόν να αναφερθούν επιπλέον ορίσματα για τον κατασκευαστή στο κλειδί `arguments` και συμπληρωματική διαμόρφωση μέσω του `setup`, όπως και στις κανονικές υπηρεσίες. -Παράδειγμα: αν η μέθοδος `create()` δεν δεχόταν την παράμετρο `$authorId`, θα μπορούσαμε να καθορίσουμε μια σταθερή τιμή στη διαμόρφωση που θα περνούσε στον κατασκευαστή `Article`: +Παράδειγμα: εάν η μέθοδος `create()` δεν δεχόταν την παράμετρο `$authorId`, θα μπορούσαμε να δηλώσουμε μια σταθερή τιμή στη διαμόρφωση, η οποία θα περνούσε στον κατασκευαστή του `Article`: ```neon services: @@ -123,7 +123,7 @@ services: authorId: 123 ``` -Ή, αντίθετα, αν η `create()` δεχόταν την παράμετρο `$authorId` αλλά δεν αποτελούσε μέρος του κατασκευαστή και περνούσε από τη μέθοδο `Article::setAuthorId()`, θα αναφερόμασταν σε αυτήν στην ενότητα `setup`: +Ή αντίστροφα, εάν η `create()` δεχόταν την παράμετρο `$authorId`, αλλά δεν ήταν μέρος του κατασκευαστή και περνούσε μέσω της μεθόδου `Article::setAuthorId()`, θα αναφερόμασταν σε αυτήν στην ενότητα `setup`: ```neon services: @@ -134,15 +134,14 @@ services: ``` -Accessor .[#toc-accessor] -========================= +Accessor +======== -Εκτός από τα εργοστάσια, η Nette μπορεί επίσης να δημιουργήσει τους λεγόμενους accessors. Ο accessor είναι ένα αντικείμενο με τη μέθοδο `get()` που επιστρέφει μια συγκεκριμένη υπηρεσία από το δοχείο DI. Πολλαπλές κλήσεις `get()` θα επιστρέφουν πάντα το ίδιο παράδειγμα. +Το Nette μπορεί, εκτός από factories, να δημιουργεί και τα λεγόμενα accessors. Πρόκειται για αντικείμενα με μια μέθοδο `get()`, η οποία επιστρέφει μια συγκεκριμένη υπηρεσία από το DI container. Η επανειλημμένη κλήση του `get()` επιστρέφει πάντα την ίδια παρουσία. -Οι accessors φέρνουν lazy-loading στις εξαρτήσεις. Ας έχουμε μια κλάση που καταγράφει σφάλματα σε μια ειδική βάση δεδομένων. Αν η σύνδεση της βάσης δεδομένων περνούσε ως εξάρτηση στον κατασκευαστή της, η σύνδεση θα έπρεπε να δημιουργείται πάντα, αν και θα χρησιμοποιούνταν μόνο σπάνια όταν εμφανίζεται ένα σφάλμα, οπότε η σύνδεση θα έμενε ως επί το πλείστον αχρησιμοποίητη. -Αντ' αυτού, η κλάση μπορεί να περάσει έναν accessor και όταν καλείται η μέθοδος `get()`, μόνο τότε δημιουργείται το αντικείμενο της βάσης δεδομένων: +Οι accessors παρέχουν lazy-loading στις εξαρτήσεις. Ας υποθέσουμε ότι έχουμε μια κλάση που καταγράφει σφάλματα σε μια ειδική βάση δεδομένων. Εάν αυτή η κλάση λάμβανε τη σύνδεση με τη βάση δεδομένων ως εξάρτηση μέσω του κατασκευαστή, η σύνδεση θα έπρεπε πάντα να δημιουργείται, παρόλο που στην πράξη ένα σφάλμα εμφανίζεται μόνο σπάνια και, επομένως, τις περισσότερες φορές η σύνδεση θα παρέμενε αχρησιμοποίητη. Αντί γι' αυτό, η κλάση περνά έναν accessor και μόνο όταν κληθεί το `get()` του, δημιουργείται το αντικείμενο της βάσης δεδομένων: -Πώς να δημιουργήσετε έναν προσπελάτη; Γράψτε μόνο μια διεπαφή και η Nette DI θα δημιουργήσει την υλοποίηση. Η διασύνδεση πρέπει να έχει ακριβώς μία μέθοδο που ονομάζεται `get` και πρέπει να δηλώνει τον τύπο επιστροφής: +Πώς να δημιουργήσετε έναν accessor; Αρκεί να γράψετε ένα interface και το Nette DI θα δημιουργήσει την υλοποίηση. Το interface πρέπει να έχει ακριβώς μία μέθοδο με το όνομα `get` και να δηλώνει τον τύπο επιστροφής: ```php interface PDOAccessor @@ -151,7 +150,7 @@ interface PDOAccessor } ``` -Προσθέστε την προσπέλαση στο αρχείο ρυθμίσεων μαζί με τον ορισμό της υπηρεσίας που θα επιστρέψει η προσπέλαση: +Προσθέτουμε τον accessor στο αρχείο διαμόρφωσης, όπου ορίζεται επίσης η υπηρεσία που θα επιστρέφει: ```neon services: @@ -159,62 +158,68 @@ services: - PDO(%dsn%, %user%, %password%) ``` -Ο accessor επιστρέφει μια υπηρεσία τύπου `PDO` και επειδή υπάρχει μόνο μια τέτοια υπηρεσία στη διαμόρφωση, ο accessor θα την επιστρέψει. Με πολλαπλές διαμορφωμένες υπηρεσίες αυτού του τύπου μπορείτε να καθορίσετε ποια θα επιστραφεί χρησιμοποιώντας το όνομά της, για παράδειγμα `- PDOAccessor(@db1)`. +Επειδή ο accessor επιστρέφει μια υπηρεσία τύπου `PDO` και στη διαμόρφωση υπάρχει μόνο μία τέτοια υπηρεσία, θα επιστρέφει ακριβώς αυτήν. Εάν υπήρχαν περισσότερες υπηρεσίες αυτού του τύπου, θα καθορίζαμε την επιστρεφόμενη υπηρεσία χρησιμοποιώντας το όνομα, π.χ. `- PDOAccessor(@db1)`. -Multifactory/Accessor .[#toc-multifactory-accessor] -=================================================== -Μέχρι στιγμής, τα εργοστάσια και οι accessors μπορούσαν να δημιουργήσουν ή να επιστρέψουν μόνο ένα αντικείμενο. Μπορεί επίσης να δημιουργηθεί ένα multifactory σε συνδυασμό με έναν accessor. Η διεπαφή μιας τέτοιας κλάσης multifactory μπορεί να αποτελείται από πολλαπλές μεθόδους που ονομάζονται `create<name>()` και `get<name>()`, για παράδειγμα: +Πολλαπλό factory/accessor +========================= +Τα factories και οι accessors μας μπορούσαν μέχρι τώρα πάντα να παράγουν ή να επιστρέφουν μόνο ένα αντικείμενο. Ωστόσο, είναι πολύ εύκολο να δημιουργηθούν και πολλαπλά factories συνδυασμένα με accessors. Το interface μιας τέτοιας κλάσης θα περιέχει οποιονδήποτε αριθμό μεθόδων με ονόματα `create<name>()` και `get<name>()`, π.χ.: ```php interface MultiFactory { function createArticle(): Article; - function createFoo(): Model\Foo; function getDb(): PDO; } ``` -Αντί να περνάτε πολλαπλά παραγόμενα εργοστάσια και accessors, μπορείτε να περνάτε μόνο ένα σύνθετο multifactory. +Έτσι, αντί να περνάμε πολλά δημιουργημένα factories και accessors, περνάμε ένα πιο σύνθετο factory που μπορεί να κάνει περισσότερα. -Εναλλακτικά, μπορείτε να χρησιμοποιήσετε τις διευθύνσεις `create()` και `get()` με μια παράμετρο αντί για πολλαπλές μεθόδους: +Εναλλακτικά, αντί για πολλές μεθόδους, μπορούμε να χρησιμοποιήσουμε το `get()` με παράμετρο: ```php interface MultiFactoryAlt { - function create($name); function get($name): PDO; } ``` -Στην περίπτωση αυτή, η `MultiFactory::createArticle()` κάνει το ίδιο πράγμα με την `MultiFactoryAlt::create('article')`. Ωστόσο, η εναλλακτική σύνταξη έχει μερικά μειονεκτήματα. Δεν είναι σαφές ποιες τιμές `$name` υποστηρίζονται και ο τύπος επιστροφής δεν μπορεί να καθοριστεί στη διεπαφή όταν χρησιμοποιούνται πολλαπλές διαφορετικές τιμές `$name`. +Τότε ισχύει ότι το `MultiFactory::getArticle()` κάνει το ίδιο πράγμα με το `MultiFactoryAlt::get('article')`. Ωστόσο, η εναλλακτική σύνταξη έχει το μειονέκτημα ότι δεν είναι σαφές ποιες τιμές `$name` υποστηρίζονται και λογικά δεν είναι δυνατό στο interface να διακριθούν διαφορετικές τιμές επιστροφής για διαφορετικά `$name`. + + +Ορισμός με λίστα +---------------- +Με αυτόν τον τρόπο μπορεί να οριστεί ένα πολλαπλό factory στη διαμόρφωση: .{data-version:3.2.0} +```neon +services: + - MultiFactory( + article: Article # ορίζει το createArticle() + db: PDO(%dsn%, %user%, %password%) # ορίζει το getDb() + ) +``` -Ορισμός με λίστα .[#toc-definition-with-a-list] ------------------------------------------------ -Πώς να ορίσετε ένα multifactory στη διαμόρφωσή σας; Ας δημιουργήσουμε τρεις υπηρεσίες που θα επιστρέφονται από το multifactory, καθώς και το ίδιο το multifactory: +Ή μπορούμε στον ορισμό του factory να αναφερθούμε σε υπάρχουσες υπηρεσίες μέσω αναφοράς: ```neon services: article: Article - - Model\Foo - PDO(%dsn%, %user%, %password%) - MultiFactory( - article: @article # createArticle() - foo: @Model\Foo # createFoo() - db: @\PDO # getDb() + article: @article # ορίζει το createArticle() + db: @\PDO # ορίζει το getDb() ) ``` -Ορισμός με ετικέτες .[#toc-definition-with-tags] ------------------------------------------------- +Ορισμός με tags +--------------- -Μια άλλη επιλογή για τον ορισμό ενός multifactory είναι η χρήση [ετικετών |services#Tags]: +Η δεύτερη επιλογή είναι να χρησιμοποιήσουμε για τον ορισμό [tags |services#Tags]: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter - App\Model\DatabaseAccessor( db1: @database.db1.explorer ) diff --git a/dependency-injection/el/faq.texy b/dependency-injection/el/faq.texy index 0ee71b6c73..ba6c075467 100644 --- a/dependency-injection/el/faq.texy +++ b/dependency-injection/el/faq.texy @@ -1,98 +1,92 @@ -Συχνές ερωτήσεις σχετικά με το DI (FAQ) -*************************************** +Συχνές ερωτήσεις για το DI (FAQ) +******************************** -Είναι το DI ένα άλλο όνομα για το IoC; .[#toc-is-di-another-name-for-ioc] -------------------------------------------------------------------------- +Είναι το DI άλλο όνομα για το IoC; +---------------------------------- -Η *Inversion of Control* (IoC) είναι μια αρχή που επικεντρώνεται στον τρόπο εκτέλεσης του κώδικα - αν ο κώδικάς σας εκκινεί εξωτερικό κώδικα ή αν ο κώδικάς σας ενσωματώνεται σε εξωτερικό κώδικα, ο οποίος στη συνέχεια τον καλεί. -Η IoC είναι μια ευρεία έννοια που περιλαμβάνει [γεγονότα |nette:glossary#Events], τη λεγόμενη [αρχή του Χόλιγουντ |application:components#Hollywood style] και άλλες πτυχές. -Τα εργοστάσια, τα οποία αποτελούν μέρος του [κανόνα #3: Let the Factory Handle It |introduction#Rule #3: Let the Factory Handle It], και αντιπροσωπεύουν την αντιστροφή για τον τελεστή `new`, είναι επίσης συστατικά αυτής της έννοιας. +Το *Inversion of Control* (IoC) είναι μια αρχή που εστιάζει στον τρόπο εκτέλεσης του κώδικα - εάν ο κώδικάς σας εκτελεί ξένο κώδικα ή εάν ο κώδικάς σας ενσωματώνεται σε ξένο κώδικα, ο οποίος στη συνέχεια τον καλεί. Το IoC είναι ένας ευρύς όρος που περιλαμβάνει [γεγονότα |nette:glossary#Events], το λεγόμενο [Hollywood principle |application:components#Hollywood Style] και άλλες πτυχές. Μέρος αυτής της έννοιας είναι και τα factories, για τα οποία μιλά ο [Κανόνας #3: άφησέ το στο factory |introduction#Κανόνας αρ. 3: άφησέ το στο factory], και τα οποία αντιπροσωπεύουν μια αντιστροφή για τον τελεστή `new`. -Το *Dependency Injection* (DI) αφορά το πώς ένα αντικείμενο γνωρίζει για ένα άλλο αντικείμενο, δηλαδή την εξάρτηση. Πρόκειται για ένα πρότυπο σχεδίασης που απαιτεί ρητή μεταβίβαση εξαρτήσεων μεταξύ αντικειμένων. +Το *Dependency Injection* (DI) εστιάζει στον τρόπο με τον οποίο ένα αντικείμενο μαθαίνει για ένα άλλο αντικείμενο, δηλαδή για τις εξαρτήσεις του. Πρόκειται για ένα σχεδιαστικό πρότυπο που απαιτεί τη ρητή μεταβίβαση εξαρτήσεων μεταξύ αντικειμένων. -Έτσι, το DI μπορεί να ειπωθεί ότι είναι μια ειδική μορφή του IoC. Ωστόσο, δεν είναι όλες οι μορφές IoC κατάλληλες όσον αφορά την καθαρότητα του κώδικα. Για παράδειγμα, στα αντι-πρότυπα περιλαμβάνουμε όλες τις τεχνικές που λειτουργούν με την [παγκόσμια κατάσταση |global state] ή το λεγόμενο [Service Locator |#What is a Service Locator]. +Μπορούμε λοιπόν να πούμε ότι το DI είναι μια συγκεκριμένη μορφή IoC. Ωστόσο, δεν είναι όλες οι μορφές IoC κατάλληλες από την άποψη της καθαρότητας του κώδικα. Για παράδειγμα, μεταξύ των αντι-προτύπων (antipatterns) περιλαμβάνονται τεχνικές που λειτουργούν με [καθολική κατάσταση |global-state] ή το λεγόμενο [Service Locator |#Τι είναι το Service Locator]. -Τι είναι ο εντοπιστής υπηρεσιών; .[#toc-what-is-a-service-locator] ------------------------------------------------------------------- +Τι είναι το Service Locator; +---------------------------- -Ο εντοπιστής υπηρεσιών είναι μια εναλλακτική λύση για την ένθεση εξαρτήσεων (Dependency Injection). Λειτουργεί με τη δημιουργία ενός κεντρικού αποθηκευτικού χώρου όπου καταχωρούνται όλες οι διαθέσιμες υπηρεσίες ή εξαρτήσεις. Όταν ένα αντικείμενο χρειάζεται μια εξάρτηση, τη ζητάει από το Service Locator. +Πρόκειται για μια εναλλακτική λύση στο Dependency Injection. Λειτουργεί δημιουργώντας ένα κεντρικό αποθετήριο όπου καταχωρούνται όλες οι διαθέσιμες υπηρεσίες ή εξαρτήσεις. Όταν ένα αντικείμενο χρειάζεται μια εξάρτηση, τη ζητά από το Service Locator. -Ωστόσο, σε σύγκριση με το Dependency Injection, χάνει σε διαφάνεια: οι εξαρτήσεις δεν μεταβιβάζονται απευθείας σε αντικείμενα και επομένως δεν είναι εύκολα αναγνωρίσιμες, γεγονός που απαιτεί την εξέταση του κώδικα για να αποκαλυφθούν και να κατανοηθούν όλες οι συνδέσεις. Η δοκιμή είναι επίσης πιο περίπλοκη, καθώς δεν μπορούμε απλά να περάσουμε αντικείμενα προσομοίωσης στα δοκιμαζόμενα αντικείμενα, αλλά πρέπει να περάσουμε μέσω του Service Locator. Επιπλέον, ο Service Locator διαταράσσει τη σχεδίαση του κώδικα, καθώς τα μεμονωμένα αντικείμενα πρέπει να γνωρίζουν την ύπαρξή του, κάτι που διαφέρει από το Dependency Injection, όπου τα αντικείμενα δεν έχουν καμία γνώση του DI container. +Σε σύγκριση με το Dependency Injection, ωστόσο, χάνει σε διαφάνεια: οι εξαρτήσεις δεν περνούν απευθείας στα αντικείμενα και δεν είναι τόσο εύκολα αναγνωρίσιμες, πράγμα που απαιτεί την εξέταση του κώδικα για να αποκαλυφθούν και να κατανοηθούν όλες οι συνδέσεις. Ο έλεγχος (testing) είναι επίσης πιο περίπλοκος, επειδή δεν μπορούμε απλώς να περάσουμε mock αντικείμενα στα υπό έλεγχο αντικείμενα, αλλά πρέπει να το κάνουμε μέσω του Service Locator. Επιπλέον, το Service Locator διαταράσσει τον σχεδιασμό του κώδικα, καθώς τα μεμονωμένα αντικείμενα πρέπει να γνωρίζουν την ύπαρξή του, πράγμα που διαφέρει από το Dependency Injection, όπου τα αντικείμενα δεν έχουν επίγνωση του DI container. -Πότε είναι καλύτερο να μην χρησιμοποιείται το DI; .[#toc-when-is-it-better-not-to-use-di] ------------------------------------------------------------------------------------------ +Πότε είναι καλύτερο να μην χρησιμοποιηθεί το DI; +------------------------------------------------ -Δεν υπάρχουν γνωστές δυσκολίες που σχετίζονται με τη χρήση του προτύπου σχεδίασης Dependency Injection. Αντιθέτως, η απόκτηση εξαρτήσεων από παγκοσμίως προσβάσιμες θέσεις οδηγεί σε [αρκετές επιπλοκές |global-state], όπως και η χρήση ενός Service Locator. -Επομένως, είναι σκόπιμο να χρησιμοποιείτε πάντα το DI. Αυτή δεν είναι μια δογματική προσέγγιση, αλλά απλά δεν έχει βρεθεί καλύτερη εναλλακτική λύση. +Δεν είναι γνωστές δυσκολίες που να σχετίζονται με τη χρήση του σχεδιαστικού προτύπου Dependency Injection. Αντίθετα, η λήψη εξαρτήσεων από καθολικά διαθέσιμα σημεία οδηγεί σε [μια ολόκληρη σειρά επιπλοκών |global-state], όπως και η χρήση του Service Locator. Επομένως, είναι σκόπιμο να χρησιμοποιείται πάντα το DI. Αυτό δεν είναι μια δογματική προσέγγιση, αλλά απλώς δεν έχει βρεθεί καλύτερη εναλλακτική λύση. -Ωστόσο, υπάρχουν ορισμένες καταστάσεις όπου δεν περνάμε αντικείμενα μεταξύ μας και τα λαμβάνουμε από τον παγκόσμιο χώρο. Για παράδειγμα, όταν κάνουμε αποσφαλμάτωση κώδικα και χρειάζεται να απορρίψουμε την τιμή μιας μεταβλητής σε ένα συγκεκριμένο σημείο του προγράμματος, να μετρήσουμε τη διάρκεια ενός συγκεκριμένου τμήματος του προγράμματος ή να καταγράψουμε ένα μήνυμα. -Σε τέτοιες περιπτώσεις, όπου πρόκειται για προσωρινές ενέργειες που θα αφαιρεθούν αργότερα από τον κώδικα, είναι θεμιτό να χρησιμοποιείται ένας dumper, ένα χρονόμετρο ή ένας καταγραφέας με παγκόσμια πρόσβαση. Αυτά τα εργαλεία, άλλωστε, δεν ανήκουν στον σχεδιασμό του κώδικα. +Παρ' όλα αυτά, υπάρχουν ορισμένες καταστάσεις όπου δεν περνάμε τα αντικείμενα και τα λαμβάνουμε από τον καθολικό χώρο. Για παράδειγμα, κατά τον εντοπισμό σφαλμάτων στον κώδικα, όταν χρειάζεται να εκτυπώσετε την τιμή μιας μεταβλητής σε ένα συγκεκριμένο σημείο του προγράμματος, να μετρήσετε τη διάρκεια ενός συγκεκριμένου τμήματος του προγράμματος ή να καταγράψετε ένα μήνυμα. Σε τέτοιες περιπτώσεις, όπου πρόκειται για προσωρινές ενέργειες που θα αφαιρεθούν αργότερα από τον κώδικα, είναι θεμιτό να χρησιμοποιηθεί ένας καθολικά διαθέσιμος dumper, χρονόμετρο ή logger. Αυτά τα εργαλεία, δηλαδή, δεν ανήκουν στον σχεδιασμό του κώδικα. -Η χρήση DI έχει τα μειονεκτήματά της; .[#toc-does-using-di-have-its-drawbacks] ------------------------------------------------------------------------------- +Έχει η χρήση του DI τα μειονεκτήματά της; +----------------------------------------- -Η χρήση του Dependency Injection συνεπάγεται μειονεκτήματα, όπως αυξημένη πολυπλοκότητα στη συγγραφή κώδικα ή χειρότερες επιδόσεις; Τι χάνουμε όταν αρχίζουμε να γράφουμε κώδικα σύμφωνα με το DI; +Συνεπάγεται η χρήση του Dependency Injection κάποια μειονεκτήματα, όπως για παράδειγμα αυξημένη δυσκολία στη συγγραφή κώδικα ή χειρότερη απόδοση; Τι χάνουμε όταν αρχίζουμε να γράφουμε κώδικα σύμφωνα με το DI; -Η DI δεν έχει καμία επίπτωση στην απόδοση της εφαρμογής ή στις απαιτήσεις μνήμης. Η απόδοση του DI Container μπορεί να παίζει κάποιο ρόλο, αλλά στην περίπτωση του [Nette DI | nette-container], το container μεταγλωττίζεται σε καθαρή PHP, οπότε η επιβάρυνσή του κατά την εκτέλεση της εφαρμογής είναι ουσιαστικά μηδενική. +Το DI δεν επηρεάζει την απόδοση ή τις απαιτήσεις μνήμης της εφαρμογής. Ορισμένο ρόλο μπορεί να παίξει η απόδοση του DI Container, ωστόσο στην περίπτωση του [Nette DI |nette-container], το container μεταγλωττίζεται σε καθαρή PHP, οπότε η επιβάρυνσή του κατά την εκτέλεση της εφαρμογής είναι ουσιαστικά μηδενική. -Κατά τη συγγραφή κώδικα, είναι απαραίτητο να δημιουργηθούν κατασκευαστές που δέχονται εξαρτήσεις. Στο παρελθόν, αυτό μπορούσε να είναι χρονοβόρο, αλλά χάρη στα σύγχρονα IDE και την [προώθηση ιδιοτήτων των κατασκευαστών |https://blog.nette.org/el/php-8-0-pleres-episkopese-ton-neon#toc-constructor-property-promotion], είναι πλέον θέμα λίγων δευτερολέπτων. Οι κατασκευαστές μπορούν εύκολα να δημιουργηθούν χρησιμοποιώντας το Nette DI και ένα πρόσθετο PhpStorm με λίγα μόνο κλικ. -Από την άλλη πλευρά, δεν χρειάζεται να γράψετε singletons και στατικά σημεία πρόσβασης. +Κατά τη συγγραφή κώδικα, συχνά είναι απαραίτητο να δημιουργηθούν κατασκευαστές που δέχονται εξαρτήσεις. Παλαιότερα αυτό μπορούσε να είναι χρονοβόρο, ωστόσο χάρη στα σύγχρονα IDE και το [constructor property promotion |https://blog.nette.org/el/php-8-0-complete-overview-of-news#toc-constructor-property-promotion], είναι πλέον θέμα δευτερολέπτων. Τα factories μπορούν εύκολα να δημιουργηθούν με το Nette DI και το plugin για το PhpStorm με ένα κλικ του ποντικιού. Από την άλλη πλευρά, εξαλείφεται η ανάγκη συγγραφής singletons και στατικών σημείων πρόσβασης. -Μπορούμε να συμπεράνουμε ότι μια σωστά σχεδιασμένη εφαρμογή που χρησιμοποιεί DI δεν είναι ούτε μικρότερη ούτε μεγαλύτερη σε σύγκριση με μια εφαρμογή που χρησιμοποιεί singletons. Τμήματα του κώδικα που εργάζονται με εξαρτήσεις απλώς εξάγονται από μεμονωμένες κλάσεις και μεταφέρονται σε νέες θέσεις, δηλαδή στον περιέκτη DI και στα εργοστάσια. +Μπορούμε να συμπεράνουμε ότι μια σωστά σχεδιασμένη εφαρμογή που χρησιμοποιεί DI δεν είναι ούτε συντομότερη ούτε μακρύτερη σε σύγκριση με μια εφαρμογή που χρησιμοποιεί singletons. Τα τμήματα του κώδικα που εργάζονται με εξαρτήσεις απλώς αφαιρούνται από τις μεμονωμένες κλάσεις και μεταφέρονται σε νέα σημεία, δηλαδή στο DI container και στα factories. -Πώς να ξαναγράψετε μια παλαιά εφαρμογή σε DI; .[#toc-how-to-rewrite-a-legacy-application-to-di] ------------------------------------------------------------------------------------------------ +Πώς να ξαναγράψετε μια legacy εφαρμογή σε DI; +--------------------------------------------- -Η μετάβαση από μια παλαιά εφαρμογή σε Dependency Injection μπορεί να είναι μια δύσκολη διαδικασία, ειδικά για μεγάλες και πολύπλοκες εφαρμογές. Είναι σημαντικό να προσεγγίσετε αυτή τη διαδικασία συστηματικά. +Η μετάβαση από μια legacy εφαρμογή στο Dependency Injection μπορεί να είναι μια απαιτητική διαδικασία, ειδικά σε μεγάλες και πολύπλοκες εφαρμογές. Είναι σημαντικό να προσεγγίσετε αυτή τη διαδικασία συστηματικά. -- Κατά τη μετάβαση σε Dependency Injection, είναι σημαντικό όλα τα μέλη της ομάδας να κατανοήσουν τις αρχές και τις πρακτικές που χρησιμοποιούνται. -- Αρχικά, πραγματοποιήστε μια ανάλυση της υπάρχουσας εφαρμογής για να εντοπίσετε τα βασικά στοιχεία και τις εξαρτήσεις τους. Δημιουργήστε ένα σχέδιο για το ποια μέρη θα αναδιαμορφωθούν και με ποια σειρά. -- Υλοποιήστε έναν περιέκτη DI ή, ακόμα καλύτερα, χρησιμοποιήστε μια υπάρχουσα βιβλιοθήκη όπως η Nette DI. -- Αναδιαμορφώστε σταδιακά κάθε τμήμα της εφαρμογής ώστε να χρησιμοποιεί Dependency Injection. Αυτό μπορεί να περιλαμβάνει την τροποποίηση κατασκευαστών ή μεθόδων ώστε να δέχονται εξαρτήσεις ως παραμέτρους. -- Τροποποιήστε τα σημεία του κώδικα όπου δημιουργούνται αντικείμενα εξάρτησης έτσι ώστε οι εξαρτήσεις να εγχέονται από τον περιέκτη. Αυτό μπορεί να περιλαμβάνει τη χρήση εργοστασίων. +- Κατά τη μετάβαση στο Dependency Injection, είναι σημαντικό όλα τα μέλη της ομάδας να κατανοούν τις αρχές και τις διαδικασίες που χρησιμοποιούνται. +- Πρώτα, πραγματοποιήστε μια ανάλυση της υπάρχουσας εφαρμογής και εντοπίστε τα βασικά στοιχεία και τις εξαρτήσεις τους. Δημιουργήστε ένα σχέδιο για το ποια τμήματα θα αναδιαρθρωθούν και με ποια σειρά. +- Υλοποιήστε ένα DI container ή, ακόμα καλύτερα, χρησιμοποιήστε μια υπάρχουσα βιβλιοθήκη, για παράδειγμα το Nette DI. +- Σταδιακά αναδιαρθρώστε τα μεμονωμένα τμήματα της εφαρμογής ώστε να χρησιμοποιούν το Dependency Injection. Αυτό μπορεί να περιλαμβάνει τροποποιήσεις των κατασκευαστών ή των μεθόδων ώστε να δέχονται εξαρτήσεις ως παραμέτρους. +- Τροποποιήστε τα σημεία στον κώδικα όπου δημιουργούνται αντικείμενα με εξαρτήσεις, ώστε αντί γι' αυτό οι εξαρτήσεις να εισάγονται από το container. Αυτό μπορεί να περιλαμβάνει τη χρήση factories. -Να θυμάστε ότι η μετάβαση σε Dependency Injection είναι μια επένδυση στην ποιότητα του κώδικα και στη μακροπρόθεσμη βιωσιμότητα της εφαρμογής. Αν και μπορεί να είναι πρόκληση να γίνουν αυτές οι αλλαγές, το αποτέλεσμα θα πρέπει να είναι καθαρότερος, πιο αρθρωτός και εύκολα ελέγξιμος κώδικας που είναι έτοιμος για μελλοντικές επεκτάσεις και συντήρηση. +Θυμηθείτε ότι η μετάβαση στο Dependency Injection είναι μια επένδυση στην ποιότητα του κώδικα και τη μακροπρόθεσμη συντηρησιμότητα της εφαρμογής. Αν και μπορεί να είναι δύσκολο να πραγματοποιηθούν αυτές οι αλλαγές, το αποτέλεσμα θα πρέπει να είναι ένας καθαρότερος, πιο αρθρωτός και εύκολα ελεγχόμενος κώδικας, ο οποίος είναι έτοιμος για μελλοντική επέκταση και συντήρηση. -Γιατί η σύνθεση προτιμάται από την κληρονομικότητα; .[#toc-why-composition-is-preferred-over-inheritance] ---------------------------------------------------------------------------------------------------------- -Είναι προτιμότερο να χρησιμοποιείται η σύνθεση παρά η κληρονομικότητα, καθώς εξυπηρετεί τον σκοπό της δυνατότητας επαναχρησιμοποίησης του κώδικα χωρίς να χρειάζεται να ανησυχείτε για την επίδραση της αλλαγής. Έτσι, παρέχει πιο χαλαρή σύζευξη όπου δεν χρειάζεται να ανησυχούμε για το αν η αλλαγή κάποιου κώδικα προκαλεί την αλλαγή κάποιου άλλου εξαρτημένου κώδικα που απαιτεί αλλαγή. Ένα τυπικό παράδειγμα είναι η κατάσταση που προσδιορίζεται ως [κόλαση των κατασκευαστών |passing-dependencies#Constructor hell]. +Γιατί προτιμάται η σύνθεση (composition) έναντι της κληρονομικότητας; +--------------------------------------------------------------------- +Είναι προτιμότερο να χρησιμοποιείται η [σύνθεση |nette:introduction-to-object-oriented-programming#Σύνθεση] αντί της [κληρονομικότητας |nette:introduction-to-object-oriented-programming#Κληρονομικότητα], επειδή χρησιμεύει στην επαναχρησιμοποίηση του κώδικα, χωρίς να χρειάζεται να ανησυχούμε για τις συνέπειες των αλλαγών. Παρέχει δηλαδή μια πιο χαλαρή σύνδεση, όπου δεν χρειάζεται να φοβόμαστε ότι η αλλαγή κάποιου κώδικα θα προκαλέσει την ανάγκη αλλαγής άλλου εξαρτώμενου κώδικα. Τυπικό παράδειγμα είναι η κατάσταση που ονομάζεται [constructor hell |passing-dependencies#Constructor hell]. -Μπορεί το Nette DI Container να χρησιμοποιηθεί εκτός της Nette; .[#toc-can-nette-di-container-be-used-outside-of-nette] ------------------------------------------------------------------------------------------------------------------------ +Μπορεί να χρησιμοποιηθεί το Nette DI Container εκτός του Nette; +--------------------------------------------------------------- -Απολύτως. Το Nette DI Container είναι μέρος του Nette, αλλά έχει σχεδιαστεί ως αυτόνομη βιβλιοθήκη που μπορεί να χρησιμοποιηθεί ανεξάρτητα από άλλα μέρη του πλαισίου. Απλά εγκαταστήστε το χρησιμοποιώντας το Composer, δημιουργήστε ένα αρχείο ρυθμίσεων που ορίζει τις υπηρεσίες σας και στη συνέχεια χρησιμοποιήστε μερικές γραμμές κώδικα PHP για να δημιουργήσετε το DI container. -Και μπορείτε αμέσως να αρχίσετε να εκμεταλλεύεστε το Dependency Injection στα έργα σας. +Σίγουρα. Το Nette DI Container είναι μέρος του Nette, αλλά έχει σχεδιαστεί ως μια αυτόνομη βιβλιοθήκη που μπορεί να χρησιμοποιηθεί ανεξάρτητα από τα υπόλοιπα μέρη του framework. Αρκεί να την εγκαταστήσετε μέσω του Composer, να δημιουργήσετε ένα αρχείο διαμόρφωσης με τον ορισμό των υπηρεσιών σας και στη συνέχεια, με λίγες γραμμές κώδικα PHP, να δημιουργήσετε το DI container. Και αμέσως μπορείτε να αρχίσετε να επωφελείστε από το Dependency Injection στα έργα σας. -Το κεφάλαιο [Nette DI Container |nette-container] περιγράφει πώς μοιάζει μια συγκεκριμένη περίπτωση χρήσης, συμπεριλαμβανομένου του κώδικα. +Πώς μοιάζει η συγκεκριμένη χρήση, συμπεριλαμβανομένων των κωδίκων, περιγράφεται στο κεφάλαιο [Nette DI Container |nette-container]. -Γιατί η διαμόρφωση βρίσκεται σε αρχεία NEON; .[#toc-why-is-the-configuration-in-neon-files] -------------------------------------------------------------------------------------------- +Γιατί η διαμόρφωση είναι σε αρχεία NEON; +---------------------------------------- -Το NEON είναι μια απλή και ευανάγνωστη γλώσσα ρυθμίσεων που αναπτύχθηκε στο πλαίσιο της Nette για τη ρύθμιση εφαρμογών, υπηρεσιών και των εξαρτήσεών τους. Σε σύγκριση με το JSON ή το YAML, προσφέρει πολύ πιο διαισθητικές και ευέλικτες επιλογές για το σκοπό αυτό. Στη NEON, μπορείτε φυσικά να περιγράψετε δεσμεύσεις που δεν θα ήταν δυνατόν να γραφτούν σε Symfony & YAML είτε καθόλου είτε μόνο μέσω μιας πολύπλοκης περιγραφής. +Το NEON είναι μια απλή και ευανάγνωστη γλώσσα διαμόρφωσης, η οποία αναπτύχθηκε στο πλαίσιο του Nette για τη ρύθμιση εφαρμογών, υπηρεσιών και των εξαρτήσεών τους. Σε σύγκριση με το JSON ή το YAML, προσφέρει για τον σκοπό αυτό πολύ πιο διαισθητικές και ευέλικτες δυνατότητες. Στο NEON μπορούν να περιγραφούν φυσικά συνδέσεις, οι οποίες στο Symfony & YAMLu δεν θα ήταν δυνατόν να γραφτούν είτε καθόλου, είτε μόνο μέσω πολύπλοκης περιγραφής. -Επιβραδύνει η ανάλυση των αρχείων NEON την εφαρμογή; .[#toc-does-parsing-neon-files-slow-down-the-application] --------------------------------------------------------------------------------------------------------------- +Δεν επιβραδύνει την εφαρμογή η ανάλυση (parsing) των αρχείων NEON; +------------------------------------------------------------------ -Παρόλο που τα αρχεία NEON αναλύονται πολύ γρήγορα, αυτή η πτυχή δεν έχει πραγματικά σημασία. Ο λόγος είναι ότι η ανάλυση των αρχείων γίνεται μόνο μία φορά κατά την πρώτη εκκίνηση της εφαρμογής. Μετά από αυτό, ο κώδικας δοχείου DI δημιουργείται, αποθηκεύεται στο δίσκο και εκτελείται για κάθε επόμενη αίτηση χωρίς να χρειάζεται περαιτέρω ανάλυση. +Παρόλο που τα αρχεία NEON αναλύονται πολύ γρήγορα, αυτή η πτυχή δεν έχει καμία σημασία. Ο λόγος είναι ότι η ανάλυση των αρχείων πραγματοποιείται μόνο μία φορά κατά την πρώτη εκκίνηση της εφαρμογής. Στη συνέχεια, δημιουργείται ο κώδικας του DI container, αποθηκεύεται στον δίσκο και εκτελείται σε κάθε επόμενο αίτημα, χωρίς να είναι απαραίτητη η περαιτέρω ανάλυση. -Έτσι λειτουργεί σε ένα περιβάλλον παραγωγής. Κατά τη διάρκεια της ανάπτυξης, τα αρχεία NEON αναλύονται κάθε φορά που αλλάζει το περιεχόμενό τους, διασφαλίζοντας ότι ο προγραμματιστής έχει πάντα ένα ενημερωμένο DI container. Όπως αναφέρθηκε προηγουμένως, η πραγματική ανάλυση είναι θέμα μιας στιγμής. +Έτσι λειτουργεί στο περιβάλλον παραγωγής. Κατά την ανάπτυξη, τα αρχεία NEON αναλύονται κάθε φορά που αλλάζει το περιεχόμενό τους, ώστε ο προγραμματιστής να έχει πάντα τον τρέχοντα DI container. Η ίδια η ανάλυση είναι, όπως ειπώθηκε, θέμα στιγμής. -Πώς μπορώ να προσπελάσω τις παραμέτρους από το αρχείο διαμόρφωσης στην κλάση μου; .[#toc-how-do-i-access-the-parameters-from-the-configuration-file-in-my-class] ----------------------------------------------------------------------------------------------------------------------------------------------------------------- +Πώς μπορώ να αποκτήσω πρόσβαση στις παραμέτρους στο αρχείο διαμόρφωσης από την κλάση μου; +----------------------------------------------------------------------------------------- -Λάβετε υπόψη σας τον [Κανόνας #1: Αφήστε να περάσει σε σας |introduction#Rule #1: Let It Be Passed to You]. Εάν μια κλάση απαιτεί πληροφορίες από ένα αρχείο ρυθμίσεων, δεν χρειάζεται να βρούμε πώς να αποκτήσουμε πρόσβαση σε αυτές τις πληροφορίες- αντίθετα, απλά τις ζητάμε - για παράδειγμα, μέσω του κατασκευαστή της κλάσης. Και εκτελούμε την πάσα στο αρχείο διαμόρφωσης. +Ας θυμηθούμε τον [Κανόνα #1: άφησέ το να σου περαστεί |introduction#Κανόνας αρ. 1: αφήστε το να σας παραδοθεί]. Εάν η κλάση απαιτεί πληροφορίες από το αρχείο διαμόρφωσης, δεν χρειάζεται να σκεφτούμε πώς να φτάσουμε σε αυτές τις πληροφορίες, αντίθετα απλώς τις ζητάμε - για παράδειγμα, μέσω του κατασκευαστή της κλάσης. Και πραγματοποιούμε τη μεταβίβαση στο αρχείο διαμόρφωσης. -Σε αυτό το παράδειγμα, το `%myParameter%` είναι ένας χώρος τοποθέτησης για την τιμή της παραμέτρου `myParameter`, η οποία θα μεταβιβαστεί στον κατασκευαστή `MyClass`: +Σε αυτό το παράδειγμα, το `%myParameter%` είναι ένα placeholder για την τιμή της παραμέτρου `myParameter`, η οποία περνά στον κατασκευαστή της κλάσης `MyClass`: ```php # config.neon @@ -103,10 +97,10 @@ services: - MyClass(%myParameter%) ``` -Εάν θέλετε να περάσετε πολλαπλές παραμέτρους ή να χρησιμοποιήσετε αυτόματη σύνδεση, είναι χρήσιμο να [τυλίξετε τις παραμέτρους σε ένα αντικείμενο |best-practices:passing-settings-to-presenters]. +Για να περάσετε πολλαπλές παραμέτρους ή να χρησιμοποιήσετε autowiring, είναι σκόπιμο να [ενσωματώσετε τις παραμέτρους σε ένα αντικείμενο |best-practices:passing-settings-to-presenters]. -Υποστηρίζει η Nette τη διεπαφή PSR-11 Container; .[#toc-does-nette-support-psr-11-container-interface] ------------------------------------------------------------------------------------------------------- +Υποστηρίζει το Nette το PSR-11: Container interface; +---------------------------------------------------- -Το Nette DI Container δεν υποστηρίζει άμεσα το PSR-11. Ωστόσο, αν χρειάζεστε διαλειτουργικότητα μεταξύ του Nette DI Container και βιβλιοθηκών ή πλαισίων που αναμένουν τη διεπαφή PSR-11 Container, μπορείτε να δημιουργήσετε έναν [απλό προσαρμογέα |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f] που θα χρησιμεύσει ως γέφυρα μεταξύ του Nette DI Container και του PSR-11. +Το Nette DI Container δεν υποστηρίζει απευθείας το PSR-11. Ωστόσο, εάν χρειάζεστε διαλειτουργικότητα μεταξύ του Nette DI Container και βιβλιοθηκών ή frameworks που αναμένουν το PSR-11 Container Interface, μπορείτε να δημιουργήσετε έναν [απλό προσαρμογέα |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f], ο οποίος θα χρησιμεύσει ως γέφυρα μεταξύ του Nette DI Container και του PSR-11. diff --git a/dependency-injection/el/global-state.texy b/dependency-injection/el/global-state.texy index 621b419f46..5207b38638 100644 --- a/dependency-injection/el/global-state.texy +++ b/dependency-injection/el/global-state.texy @@ -1,67 +1,63 @@ -Παγκόσμια κατάσταση και singletons -********************************** +Καθολική κατάσταση και singletons +********************************* .[perex] -Προειδοποίηση: Οι ακόλουθες δομές είναι συμπτώματα κακοσχεδιασμένου κώδικα: +Προειδοποίηση: Οι ακόλουθες κατασκευές είναι σημάδι κακώς σχεδιασμένου κώδικα: - `Foo::getInstance()` - `DB::insert(...)` - `Article::setDb($db)` - `ClassName::$var` ή `static::$var` -Συναντάτε κάποια από αυτές τις δομές στον κώδικά σας; Αν ναι, έχετε την ευκαιρία να τον βελτιώσετε. Μπορεί να νομίζετε ότι πρόκειται για κοινές κατασκευές, που συχνά συναντάτε σε παραδείγματα λύσεων διαφόρων βιβλιοθηκών και πλαισίων. Αν αυτό ισχύει, ο σχεδιασμός του κώδικά τους είναι ελαττωματικός. +Εμφανίζονται κάποιες από αυτές τις κατασκευές στον κώδικά σας; Τότε έχετε την ευκαιρία να τον βελτιώσετε. Ίσως σκέφτεστε ότι πρόκειται για συνήθεις κατασκευές, τις οποίες βλέπετε ίσως και σε παραδείγματα λύσεων διαφόρων βιβλιοθηκών και frameworks. Αν ισχύει αυτό, τότε ο σχεδιασμός του κώδικά τους δεν είναι καλός. -Εδώ δεν μιλάμε για κάποια ακαδημαϊκή καθαρότητα. Όλες αυτές οι δομές έχουν ένα κοινό: χρησιμοποιούν την παγκόσμια κατάσταση. Και αυτό έχει καταστροφικό αντίκτυπο στην ποιότητα του κώδικα. Οι κλάσεις είναι παραπλανητικές σχετικά με τις εξαρτήσεις τους. Ο κώδικας γίνεται απρόβλεπτος. Δημιουργεί σύγχυση στους προγραμματιστές και μειώνει την αποδοτικότητά τους. +Τώρα σίγουρα δεν μιλάμε για κάποια ακαδημαϊκή καθαρότητα. Όλες αυτές οι κατασκευές έχουν ένα κοινό: χρησιμοποιούν καθολική κατάσταση. Και αυτή έχει καταστροφική επίδραση στην ποιότητα του κώδικα. Οι κλάσεις λένε ψέματα για τις εξαρτήσεις τους. Ο κώδικας γίνεται απρόβλεπτος. Μπερδεύει τους προγραμματιστές και μειώνει την αποδοτικότητά τους. -Σε αυτό το κεφάλαιο, θα εξηγήσουμε γιατί συμβαίνει αυτό και πώς να αποφύγετε την παγκόσμια κατάσταση. +Σε αυτό το κεφάλαιο θα εξηγήσουμε γιατί συμβαίνει αυτό και πώς να αποφύγετε την καθολική κατάσταση. -Παγκόσμια διασύνδεση .[#toc-global-interlinking] ------------------------------------------------- +Καθολική σύζευξη +---------------- -Σε έναν ιδανικό κόσμο, ένα αντικείμενο θα πρέπει να επικοινωνεί μόνο με αντικείμενα που [του έχουν δοθεί απευθείας |passing-dependencies]. Αν δημιουργήσω δύο αντικείμενα `A` και `B` και δεν περάσω ποτέ μια αναφορά μεταξύ τους, τότε ούτε το `A` ούτε το `B` μπορούν να έχουν πρόσβαση ή να τροποποιήσουν την κατάσταση του άλλου. Αυτή είναι μια ιδιαίτερα επιθυμητή ιδιότητα του κώδικα. Είναι σαν να έχουμε μια μπαταρία και μια λάμπα- η λάμπα δεν θα ανάψει μέχρι να τη συνδέσετε με ένα καλώδιο στην μπαταρία. +Σε έναν ιδανικό κόσμο, ένα αντικείμενο θα έπρεπε να μπορεί να επικοινωνεί μόνο με αντικείμενα που του έχουν [περαστεί απευθείας |passing-dependencies]. Εάν δημιουργήσω δύο αντικείμενα `A` και `B` και ποτέ δεν περάσω αναφορά μεταξύ τους, τότε ούτε το `A`, ούτε το `B`, μπορούν να φτάσουν στο άλλο αντικείμενο ή να αλλάξουν την κατάστασή του. Αυτό είναι ένα πολύ επιθυμητό χαρακτηριστικό του κώδικα. Είναι παρόμοιο με το να έχετε μια μπαταρία και μια λάμπα. η λάμπα δεν θα ανάψει αν δεν τη συνδέσετε με την μπαταρία με ένα καλώδιο. -Ωστόσο, αυτό δεν ισχύει για παγκόσμιες (στατικές) μεταβλητές ή singletons. Το αντικείμενο `A` θα μπορούσε *χωρίς καλώδιο* να προσπελάσει το αντικείμενο `C` και να το τροποποιήσει χωρίς καμία μεταβίβαση αναφοράς, καλώντας το `C::changeSomething()`. Εάν το αντικείμενο `B` έχει επίσης πρόσβαση στο παγκόσμιο `C`, τότε τα `A` και `B` μπορούν να αλληλοεπηρεάζονται μέσω του `C`. +Αυτό όμως δεν ισχύει για τις καθολικές (στατικές) μεταβλητές ή τα singletons. Το αντικείμενο `A` θα μπορούσε *ασύρματα* να φτάσει στο αντικείμενο `C` και να το τροποποιήσει χωρίς καμία μεταβίβαση αναφοράς, καλώντας το `C::changeSomething()`. Εάν το αντικείμενο `B` αρπάξει επίσης το καθολικό `C`, τότε τα `A` και `B` μπορούν να αλληλεπιδράσουν μέσω του `C`. -Η χρήση παγκόσμιων μεταβλητών εισάγει μια νέα μορφή *ασύρματης* σύζευξης που δεν είναι εξωτερικά ορατή. Δημιουργεί ένα προπέτασμα καπνού που περιπλέκει την κατανόηση και τη χρήση του κώδικα. Για να κατανοήσουν πραγματικά τις εξαρτήσεις, οι προγραμματιστές πρέπει να διαβάσουν κάθε γραμμή του πηγαίου κώδικα, αντί να εξοικειωθούν μόνο με τις διεπαφές των κλάσεων. Επιπλέον, αυτή η εμπλοκή είναι εντελώς περιττή. Η παγκόσμια κατάσταση χρησιμοποιείται επειδή είναι εύκολα προσβάσιμη από οπουδήποτε και επιτρέπει, για παράδειγμα, την εγγραφή σε μια βάση δεδομένων μέσω μιας παγκόσμιας (στατικής) μεθόδου `DB::insert()`. Ωστόσο, όπως θα δούμε, το όφελος που προσφέρει είναι ελάχιστο, ενώ οι επιπλοκές που εισάγει είναι σοβαρές. +Η χρήση καθολικών μεταβλητών εισάγει στο σύστημα μια νέα μορφή *ασύρματης* σύζευξης, η οποία δεν είναι ορατή από έξω. Δημιουργεί ένα παραπέτασμα καπνού που περιπλέκει την κατανόηση και τη χρήση του κώδικα. Για να κατανοήσουν πραγματικά οι προγραμματιστές τις εξαρτήσεις, πρέπει να διαβάσουν κάθε γραμμή του πηγαίου κώδικα. Αντί απλώς να εξοικειωθούν με τα interfaces των κλάσεων. Επιπλέον, πρόκειται για μια εντελώς περιττή σύζευξη. Η καθολική κατάσταση χρησιμοποιείται επειδή είναι εύκολα προσβάσιμη από οπουδήποτε και επιτρέπει, για παράδειγμα, την εγγραφή στη βάση δεδομένων μέσω της καθολικής (στατικής) μεθόδου `DB::insert()`. Αλλά όπως θα δείξουμε, το πλεονέκτημα που προσφέρει είναι ασήμαντο, ενώ αντίθετα οι επιπλοκές που προκαλεί είναι μοιραίες. .[note] -Όσον αφορά τη συμπεριφορά, δεν υπάρχει καμία διαφορά μεταξύ μιας παγκόσμιας και μιας στατικής μεταβλητής. Είναι εξίσου επιβλαβείς. +Από την άποψη της συμπεριφοράς, δεν υπάρχει διαφορά μεταξύ καθολικής και στατικής μεταβλητής. Είναι εξίσου επιβλαβείς. -Η τρομακτική δράση από απόσταση .[#toc-the-spooky-action-at-a-distance] ------------------------------------------------------------------------ +Απόκοσμη δράση από απόσταση +--------------------------- -"Φαινομενική δράση από απόσταση" - έτσι ονόμασε ο Άλμπερτ Αϊνστάιν ένα φαινόμενο της κβαντικής φυσικής που τον ανατρίχιασε το 1935. -Πρόκειται για την κβαντική διεμπλοκή, η ιδιαιτερότητα της οποίας είναι ότι όταν μετράτε πληροφορίες για ένα σωματίδιο, επηρεάζετε αμέσως ένα άλλο σωματίδιο, ακόμη και αν αυτά απέχουν εκατομμύρια έτη φωτός. -γεγονός που φαινομενικά παραβιάζει τον θεμελιώδη νόμο του σύμπαντος ότι τίποτα δεν μπορεί να ταξιδέψει γρηγορότερα από το φως. +"Απόκοσμη δράση από απόσταση" (Spooky action at a distance) - έτσι ονόμασε περίφημα το 1935 ο Άλμπερτ Αϊνστάιν ένα φαινόμενο στην κβαντική φυσική που του προκαλούσε ανατριχίλα. +Πρόκειται για την κβαντική διεμπλοκή, της οποίας η ιδιαιτερότητα είναι ότι όταν μετράτε πληροφορίες για ένα σωματίδιο, επηρεάζετε αμέσως το άλλο σωματίδιο, ακόμα κι αν απέχουν εκατομμύρια έτη φωτός. Αυτό φαινομενικά παραβιάζει τον θεμελιώδη νόμο του σύμπαντος ότι τίποτα δεν μπορεί να ταξιδέψει γρηγορότερα από το φως. -Στον κόσμο του λογισμικού, μπορούμε να ονομάσουμε "spooky action at a distance" μια κατάσταση κατά την οποία εκτελούμε μια διαδικασία που νομίζουμε ότι είναι απομονωμένη (επειδή δεν της έχουμε περάσει καμία αναφορά), αλλά απροσδόκητες αλληλεπιδράσεις και αλλαγές κατάστασης συμβαίνουν σε απομακρυσμένες θέσεις του συστήματος για τις οποίες δεν ενημερώσαμε το αντικείμενο. Αυτό μπορεί να συμβεί μόνο μέσω της παγκόσμιας κατάστασης. +Στον κόσμο του λογισμικού, μπορούμε να ονομάσουμε "απόκοσμη δράση από απόσταση" μια κατάσταση όπου εκκινούμε μια διαδικασία, την οποία θεωρούμε απομονωμένη (επειδή δεν της περάσαμε καμία αναφορά), αλλά σε απομακρυσμένα σημεία του συστήματος συμβαίνουν απροσδόκητες αλληλεπιδράσεις και αλλαγές κατάστασης, για τις οποίες δεν είχαμε ιδέα. Αυτό μπορεί να συμβεί μόνο μέσω της καθολικής κατάστασης. -Φανταστείτε να ενταχθείτε σε μια ομάδα ανάπτυξης έργου που έχει μια μεγάλη, ώριμη βάση κώδικα. Ο νέος σας επικεφαλής σας ζητά να υλοποιήσετε ένα νέο χαρακτηριστικό και, σαν καλός προγραμματιστής, ξεκινάτε γράφοντας μια δοκιμή. Αλλά επειδή είστε νέος στο έργο, κάνετε πολλές διερευνητικές δοκιμές τύπου "τι συμβαίνει αν καλέσω αυτή τη μέθοδο". Και προσπαθείτε να γράψετε την ακόλουθη δοκιμή: +Φανταστείτε ότι εντάσσεστε σε μια ομάδα προγραμματιστών ενός έργου που έχει μια εκτεταμένη, ώριμη βάση κώδικα. Ο νέος σας προϊστάμενος σας ζητά να υλοποιήσετε μια νέα λειτουργία και εσείς, ως σωστός προγραμματιστής, ξεκινάτε γράφοντας ένα τεστ. Επειδή όμως είστε νέοι στο έργο, κάνετε πολλά διερευνητικά τεστ του τύπου "τι θα συμβεί αν καλέσω αυτή τη μέθοδο". Και δοκιμάζετε να γράψετε το ακόλουθο τεστ: ```php function testCreditCardCharge() { - $cc = new CreditCard('1234567890123456', 5, 2028); // τον αριθμό της κάρτας σας + $cc = new CreditCard('1234567890123456', 5, 2028); // ο αριθμός της κάρτας σας $cc->charge(100); } ``` -Εκτελείτε τον κώδικα, ίσως αρκετές φορές, και μετά από λίγο παρατηρείτε ειδοποιήσεις στο τηλέφωνό σας από την τράπεζα ότι κάθε φορά που τον εκτελείτε, χρεώνονται 100 δολάρια στην πιστωτική σας κάρτα 🤦‍♂️ +Εκτελείτε τον κώδικα, ίσως αρκετές φορές, και μετά από λίγο παρατηρείτε ειδοποιήσεις στο κινητό σας από την τράπεζα ότι κάθε φορά που εκτελείται, χρεώνονται 100 δολάρια από την πιστωτική σας κάρτα 🤦‍♂️ -Πώς στο καλό θα μπορούσε το τεστ να προκαλέσει πραγματική χρέωση; Δεν είναι εύκολο να λειτουργήσει με πιστωτική κάρτα. Πρέπει να αλληλεπιδράσετε με μια διαδικτυακή υπηρεσία τρίτου μέρους, πρέπει να γνωρίζετε τη διεύθυνση URL αυτής της διαδικτυακής υπηρεσίας, πρέπει να συνδεθείτε και ούτω καθεξής. -Καμία από αυτές τις πληροφορίες δεν περιλαμβάνεται στη δοκιμή. Ακόμα χειρότερα, δεν γνωρίζετε καν πού υπάρχουν αυτές οι πληροφορίες και, επομένως, πώς να παριστάνετε τις εξωτερικές εξαρτήσεις, ώστε κάθε εκτέλεση να μην οδηγεί σε νέα χρέωση 100 δολαρίων. Και ως νέος προγραμματιστής, πώς υποτίθεται ότι θα γνωρίζατε ότι αυτό που θα κάνατε θα σας οδηγούσε στο να γίνετε κατά 100 δολάρια φτωχότεροι; +Πώς στο καλό μπόρεσε το τεστ να προκαλέσει πραγματική χρέωση χρημάτων; Η λειτουργία με πιστωτική κάρτα δεν είναι εύκολη. Πρέπει να επικοινωνήσετε με μια web υπηρεσία τρίτου μέρους, πρέπει να γνωρίζετε τη διεύθυνση URL αυτής της web υπηρεσίας, πρέπει να συνδεθείτε και ούτω καθεξής. Καμία από αυτές τις πληροφορίες δεν περιέχεται στο τεστ. Ακόμα χειρότερα, ούτε καν γνωρίζετε πού βρίσκονται αυτές οι πληροφορίες, και επομένως ούτε πώς να κάνετε mock τις εξωτερικές εξαρτήσεις, ώστε κάθε εκτέλεση να μην οδηγεί ξανά σε χρέωση 100 δολαρίων. Και πώς έπρεπε να γνωρίζετε, ως νέος προγραμματιστής, ότι αυτό που ετοιμαζόσασταν να κάνετε θα οδηγούσε στο να γίνετε 100 δολάρια φτωχότεροι; -Αυτή είναι μια τρομακτική δράση από απόσταση! +Αυτή είναι η απόκοσμη δράση από απόσταση! -Δεν έχετε άλλη επιλογή από το να ψάξετε πολύ πηγαίο κώδικα, ρωτώντας παλαιότερους και πιο έμπειρους συναδέλφους, μέχρι να καταλάβετε πώς λειτουργούν οι συνδέσεις στο έργο. -Αυτό οφείλεται στο γεγονός ότι, όταν εξετάζετε τη διεπαφή της κλάσης `CreditCard`, δεν μπορείτε να προσδιορίσετε την παγκόσμια κατάσταση που πρέπει να αρχικοποιηθεί. Ακόμα και αν κοιτάξετε τον πηγαίο κώδικα της κλάσης δεν θα σας πει ποια μέθοδος αρχικοποίησης πρέπει να καλέσετε. Στην καλύτερη περίπτωση, μπορείτε να βρείτε την παγκόσμια μεταβλητή στην οποία γίνεται πρόσβαση και να προσπαθήσετε να μαντέψετε πώς να την αρχικοποιήσετε από αυτήν. +Δεν σας μένει παρά να ψάξετε για πολλή ώρα σε πολλούς πηγαίους κώδικες, να ρωτήσετε παλαιότερους και πιο έμπειρους συναδέλφους, μέχρι να καταλάβετε πώς λειτουργούν οι συνδέσεις στο έργο. Αυτό οφείλεται στο ότι, κοιτάζοντας το interface της κλάσης `CreditCard`, δεν μπορείτε να προσδιορίσετε την καθολική κατάσταση που πρέπει να αρχικοποιηθεί. Ακόμη και η ματιά στον πηγαίο κώδικα της κλάσης δεν σας αποκαλύπτει ποια μέθοδο αρχικοποίησης πρέπει να καλέσετε. Στην καλύτερη περίπτωση, μπορείτε να βρείτε μια καθολική μεταβλητή στην οποία γίνεται πρόσβαση και από αυτήν να προσπαθήσετε να μαντέψετε πώς να την αρχικοποιήσετε. -Οι κλάσεις σε ένα τέτοιο έργο είναι παθολογικοί ψεύτες. Η κάρτα πληρωμών προσποιείται ότι μπορείτε απλώς να την ενσαρκώσετε και να καλέσετε τη μέθοδο `charge()`. Ωστόσο, κρυφά αλληλεπιδρά με μια άλλη κλάση, την `PaymentGateway`. Ακόμη και η διεπαφή της λέει ότι μπορεί να αρχικοποιηθεί ανεξάρτητα, αλλά στην πραγματικότητα αντλεί διαπιστευτήρια από κάποιο αρχείο ρυθμίσεων κ.ο.κ. -Είναι σαφές στους προγραμματιστές που έγραψαν αυτόν τον κώδικα ότι το `CreditCard` χρειάζεται το `PaymentGateway`. Έγραψαν τον κώδικα με αυτόν τον τρόπο. Αλλά για οποιονδήποτε νέο στο έργο, αυτό είναι ένα πλήρες μυστήριο και εμποδίζει την εκμάθηση. +Οι κλάσεις σε ένα τέτοιο έργο είναι παθολογικοί ψεύτες. Η πιστωτική κάρτα προσποιείται ότι αρκεί να την παρουσιάσετε και να καλέσετε τη μέθοδο `charge()`. Κρυφά, όμως, συνεργάζεται με μια άλλη κλάση `PaymentGateway`, η οποία αντιπροσωπεύει την πύλη πληρωμών. Ακόμη και το interface της λέει ότι μπορεί να αρχικοποιηθεί ξεχωριστά, αλλά στην πραγματικότητα αντλεί διαπιστευτήρια από κάποιο αρχείο διαμόρφωσης και ούτω καθεξής. Για τους προγραμματιστές που έγραψαν αυτόν τον κώδικα, είναι σαφές ότι η `CreditCard` χρειάζεται την `PaymentGateway`. Έγραψαν τον κώδικα με αυτόν τον τρόπο. Αλλά για οποιονδήποτε είναι νέος στο έργο, είναι ένα απόλυτο μυστήριο και εμποδίζει τη μάθηση. -Πώς να διορθώσετε την κατάσταση; Εύκολα. **Αφήστε το API να δηλώσει εξαρτήσεις.** +Πώς να διορθώσετε την κατάσταση; Εύκολα. **Αφήστε το API να δηλώσει τις εξαρτήσεις.** ```php function testCreditCardCharge() @@ -72,36 +68,35 @@ function testCreditCardCharge() } ``` -Παρατηρήστε πώς οι σχέσεις μέσα στον κώδικα είναι ξαφνικά προφανείς. Δηλώνοντας ότι η μέθοδος `charge()` χρειάζεται τη διεύθυνση `PaymentGateway`, δεν χρειάζεται να ρωτήσετε κανέναν πώς ο κώδικας είναι αλληλοεξαρτώμενος. Ξέρετε ότι πρέπει να δημιουργήσετε μια παρουσία της, και όταν προσπαθείτε να το κάνετε αυτό, πέφτετε πάνω στο γεγονός ότι πρέπει να παρέχετε παραμέτρους πρόσβασης. Χωρίς αυτές, ο κώδικας δεν θα μπορούσε καν να εκτελεστεί. +Παρατηρήστε πώς οι συνδέσεις μέσα στον κώδικα γίνονται ξαφνικά προφανείς. Με το γεγονός ότι η μέθοδος `charge()` δηλώνει ότι χρειάζεται την `PaymentGateway`, δεν χρειάζεται να ρωτήσετε κανέναν πώς συνδέεται ο κώδικας. Γνωρίζετε ότι πρέπει να δημιουργήσετε την παρουσία της, και όταν προσπαθήσετε να το κάνετε, θα διαπιστώσετε ότι πρέπει να δώσετε παραμέτρους πρόσβασης. Χωρίς αυτές, ο κώδικας δεν θα μπορούσε καν να εκτελεστεί. -Και το πιο σημαντικό, μπορείτε τώρα να μιμηθείτε την πύλη πληρωμών, ώστε να μην χρεώνεστε 100 δολάρια κάθε φορά που εκτελείτε μια δοκιμή. +Και κυρίως, τώρα μπορείτε να κάνετε mock την πύλη πληρωμών, ώστε να μην χρεώνεστε 100 δολάρια κάθε φορά που εκτελείτε το τεστ. -Η παγκόσμια κατάσταση προκαλεί στα αντικείμενά σας τη δυνατότητα να έχουν κρυφή πρόσβαση σε πράγματα που δεν έχουν δηλωθεί στα API τους, και ως αποτέλεσμα καθιστά τα API σας παθολογικά ψεύτικα. +Η καθολική κατάσταση κάνει τα αντικείμενά σας να μπορούν κρυφά να έχουν πρόσβαση σε πράγματα που δεν δηλώνονται στα API τους, και ως αποτέλεσμα, μετατρέπει τα API σας σε παθολογικούς ψεύτες. -Μπορεί να μην το είχατε σκεφτεί με αυτόν τον τρόπο πριν, αλλά κάθε φορά που χρησιμοποιείτε global state, δημιουργείτε μυστικά ασύρματα κανάλια επικοινωνίας. Η ανατριχιαστική απομακρυσμένη δράση αναγκάζει τους προγραμματιστές να διαβάσουν κάθε γραμμή κώδικα για να κατανοήσουν τις πιθανές αλληλεπιδράσεις, μειώνει την παραγωγικότητα των προγραμματιστών και μπερδεύει τα νέα μέλη της ομάδας. -Αν είστε αυτός που δημιούργησε τον κώδικα, γνωρίζετε τις πραγματικές εξαρτήσεις, αλλά όποιος έρχεται μετά από εσάς είναι άσχετος. +Ίσως να μην το είχατε σκεφτεί έτσι προηγουμένως, αλλά κάθε φορά που χρησιμοποιείτε καθολική κατάσταση, δημιουργείτε μυστικούς ασύρματους διαύλους επικοινωνίας. Η απόκοσμη δράση από απόσταση αναγκάζει τους προγραμματιστές να διαβάζουν κάθε γραμμή κώδικα για να κατανοήσουν τις πιθανές αλληλεπιδράσεις, μειώνει την παραγωγικότητα των προγραμματιστών και μπερδεύει τα νέα μέλη της ομάδας. Εάν είστε εσείς αυτός που δημιούργησε τον κώδικα, γνωρίζετε τις πραγματικές εξαρτήσεις, αλλά οποιοσδήποτε έρθει μετά από εσάς είναι αβοήθητος. -Μη γράφετε κώδικα που χρησιμοποιεί παγκόσμια κατάσταση, προτιμήστε να μεταβιβάζετε εξαρτήσεις. Δηλαδή, την έγχυση εξαρτήσεων (dependency injection). +Μην γράφετε κώδικα που χρησιμοποιεί καθολική κατάσταση, προτιμήστε τη μεταβίβαση εξαρτήσεων. Δηλαδή, dependency injection. -Η ευθραυστότητα του παγκόσμιου κράτους .[#toc-brittleness-of-the-global-state] ------------------------------------------------------------------------------- +Ευθραυστότητα της καθολικής κατάστασης +-------------------------------------- -Σε κώδικα που χρησιμοποιεί παγκόσμια κατάσταση και singletons, δεν είναι ποτέ βέβαιο πότε και από ποιον έχει αλλάξει αυτή η κατάσταση. Αυτός ο κίνδυνος υπάρχει ήδη κατά την αρχικοποίηση. Ο παρακάτω κώδικας υποτίθεται ότι δημιουργεί μια σύνδεση με βάση δεδομένων και αρχικοποιεί την πύλη πληρωμών, αλλά συνεχίζει να πετάει μια εξαίρεση και η εύρεση της αιτίας είναι εξαιρετικά κουραστική: +Στον κώδικα που χρησιμοποιεί καθολική κατάσταση και singletons, δεν είναι ποτέ σίγουρο πότε και ποιος άλλαξε αυτή την κατάσταση. Αυτός ο κίνδυνος εμφανίζεται ήδη κατά την αρχικοποίηση. Ο ακόλουθος κώδικας υποτίθεται ότι δημιουργεί μια σύνδεση βάσης δεδομένων και αρχικοποιεί την πύλη πληρωμών, ωστόσο προκαλεί συνεχώς εξαίρεση και η εύρεση της αιτίας είναι εξαιρετικά χρονοβόρα: ```php PaymentGateway::init(); DB::init('mysql:', 'user', 'password'); ``` -Πρέπει να ψάξετε λεπτομερώς τον κώδικα για να διαπιστώσετε ότι το αντικείμενο `PaymentGateway` αποκτά ασύρματη πρόσβαση σε άλλα αντικείμενα, ορισμένα από τα οποία απαιτούν σύνδεση με βάση δεδομένων. Έτσι, πρέπει να αρχικοποιήσετε τη βάση δεδομένων πριν από το `PaymentGateway`. Ωστόσο, το προπέτασμα καπνού της παγκόσμιας κατάστασης το κρύβει αυτό από εσάς. Πόσο χρόνο θα γλιτώνατε αν το API κάθε κλάσης δεν έλεγε ψέματα και δεν δήλωνε τις εξαρτήσεις του; +Πρέπει να εξετάσετε λεπτομερώς τον κώδικα για να διαπιστώσετε ότι το αντικείμενο `PaymentGateway` έχει ασύρματη πρόσβαση σε άλλα αντικείμενα, ορισμένα από τα οποία απαιτούν σύνδεση βάσης δεδομένων. Δηλαδή, είναι απαραίτητο να αρχικοποιήσετε τη βάση δεδομένων πριν από την `PaymentGateway`. Ωστόσο, το παραπέτασμα καπνού της καθολικής κατάστασης το κρύβει αυτό από εσάς. Πόσο χρόνο θα είχατε εξοικονομήσει εάν τα API των μεμονωμένων κλάσεων δεν εξαπατούσαν και δήλωναν τις εξαρτήσεις τους; ```php $db = new DB('mysql:', 'user', 'password'); $gateway = new PaymentGateway($db, ...); ``` -Ένα παρόμοιο πρόβλημα προκύπτει όταν χρησιμοποιείτε παγκόσμια πρόσβαση σε μια σύνδεση βάσης δεδομένων: +Ένα παρόμοιο πρόβλημα εμφανίζεται και κατά τη χρήση καθολικής πρόσβασης στη σύνδεση της βάσης δεδομένων: ```php use Illuminate\Support\Facades\DB; @@ -115,9 +110,9 @@ class Article } ``` -Κατά την κλήση της μεθόδου `save()`, δεν είναι βέβαιο αν έχει ήδη δημιουργηθεί μια σύνδεση βάσης δεδομένων και ποιος είναι υπεύθυνος για τη δημιουργία της. Για παράδειγμα, αν θέλαμε να αλλάξουμε τη σύνδεση της βάσης δεδομένων εν κινήσει, ίσως για λόγους δοκιμών, θα έπρεπε πιθανώς να δημιουργήσουμε πρόσθετες μεθόδους όπως οι `DB::reconnect(...)` ή `DB::reconnectForTest()`. +Κατά την κλήση της μεθόδου `save()`, δεν είναι βέβαιο εάν έχει ήδη δημιουργηθεί η σύνδεση με τη βάση δεδομένων και ποιος φέρει την ευθύνη για τη δημιουργία της. Εάν θέλουμε, για παράδειγμα, να αλλάξουμε τη σύνδεση της βάσης δεδομένων κατά την εκτέλεση, ίσως για λόγους δοκιμών, θα έπρεπε πιθανότατα να δημιουργήσουμε επιπλέον μεθόδους όπως `DB::reconnect(...)` ή `DB::reconnectForTest()`. -Σκεφτείτε ένα παράδειγμα: +Ας εξετάσουμε ένα παράδειγμα: ```php $article = new Article; @@ -127,9 +122,9 @@ Foo::doSomething(); $article->save(); ``` -Πού μπορούμε να είμαστε σίγουροι ότι η βάση δεδομένων δοκιμής χρησιμοποιείται πραγματικά όταν καλούμε το `$article->save()`; Τι γίνεται αν η μέθοδος `Foo::doSomething()` αλλάξει την παγκόσμια σύνδεση της βάσης δεδομένων; Για να το μάθουμε, θα πρέπει να εξετάσουμε τον πηγαίο κώδικα της κλάσης `Foo` και πιθανώς πολλών άλλων κλάσεων. Ωστόσο, αυτή η προσέγγιση θα έδινε μόνο μια βραχυπρόθεσμη απάντηση, καθώς η κατάσταση μπορεί να αλλάξει στο μέλλον. +Πού έχουμε τη βεβαιότητα ότι κατά την κλήση του `$article->save()` χρησιμοποιείται όντως η δοκιμαστική βάση δεδομένων; Τι γίνεται αν η μέθοδος `Foo::doSomething()` άλλαξε την καθολική σύνδεση της βάσης δεδομένων; Για να το διαπιστώσουμε, θα έπρεπε να εξετάσουμε τον πηγαίο κώδικα της κλάσης `Foo` και πιθανώς και πολλών άλλων κλάσεων. Αυτή η προσέγγιση, ωστόσο, θα έδινε μόνο μια βραχυπρόθεσμη απάντηση, καθώς η κατάσταση μπορεί να αλλάξει στο μέλλον. -Τι θα γινόταν αν μετακινούσαμε τη σύνδεση με τη βάση δεδομένων σε μια στατική μεταβλητή μέσα στην κλάση `Article`; +Και τι γίνεται αν μεταφέρουμε τη σύνδεση με τη βάση δεδομένων σε μια στατική μεταβλητή μέσα στην κλάση `Article`; ```php class Article @@ -148,11 +143,11 @@ class Article } ``` -Αυτό δεν αλλάζει τίποτα απολύτως. Το πρόβλημα είναι μια παγκόσμια κατάσταση και δεν έχει σημασία σε ποια κλάση κρύβεται. Σε αυτή την περίπτωση, όπως και στην προηγούμενη, δεν έχουμε καμία ένδειξη για το σε ποια βάση δεδομένων γράφεται όταν καλείται η μέθοδος `$article->save()`. Οποιοσδήποτε στο μακρινό άκρο της εφαρμογής θα μπορούσε να αλλάξει τη βάση δεδομένων ανά πάσα στιγμή χρησιμοποιώντας τη μέθοδο `Article::setDb()`. Κάτω από τα χέρια μας. +Αυτό δεν άλλαξε απολύτως τίποτα. Το πρόβλημα είναι η καθολική κατάσταση και είναι εντελώς αδιάφορο σε ποια κλάση κρύβεται. Σε αυτή την περίπτωση, όπως και στην προηγούμενη, δεν έχουμε καμία ένδειξη κατά την κλήση της μεθόδου `$article->save()` για το σε ποια βάση δεδομένων θα γίνει η εγγραφή. Οποιοσδήποτε στο άλλο άκρο της εφαρμογής θα μπορούσε ανά πάσα στιγμή να αλλάξει τη βάση δεδομένων χρησιμοποιώντας το `Article::setDb()`. Κάτω από τα χέρια μας. -Η παγκόσμια κατάσταση καθιστά την εφαρμογή μας **εξαιρετικά εύθραυστη**. +Η καθολική κατάσταση καθιστά την εφαρμογή μας **εξαιρετικά εύθραυστη**. -Ωστόσο, υπάρχει ένας απλός τρόπος να αντιμετωπίσουμε αυτό το πρόβλημα. Απλά βάλτε το API να δηλώσει εξαρτήσεις για να διασφαλιστεί η σωστή λειτουργικότητα. +Υπάρχει όμως ένας απλός τρόπος για να αντιμετωπίσουμε αυτό το πρόβλημα. Αρκεί να αφήσουμε το API να δηλώσει τις εξαρτήσεις, εξασφαλίζοντας έτσι τη σωστή λειτουργικότητα. ```php class Article @@ -174,15 +169,15 @@ Foo::doSomething(); $article->save(); ``` -Αυτή η προσέγγιση εξαλείφει την ανησυχία για κρυφές και απροσδόκητες αλλαγές στις συνδέσεις βάσης δεδομένων. Τώρα είμαστε σίγουροι για το πού αποθηκεύεται το άρθρο και καμία τροποποίηση κώδικα μέσα σε μια άλλη άσχετη κλάση δεν μπορεί πλέον να αλλάξει την κατάσταση. Ο κώδικας δεν είναι πλέον εύθραυστος, αλλά σταθερός. +Χάρη σε αυτή την προσέγγιση, εξαλείφεται η ανησυχία για κρυφές και απροσδόκητες αλλαγές στη σύνδεση της βάσης δεδομένων. Τώρα έχουμε τη βεβαιότητα για το πού αποθηκεύεται το άρθρο και καμία τροποποίηση του κώδικα μέσα σε μια άλλη άσχετη κλάση δεν μπορεί πλέον να αλλάξει την κατάσταση. Ο κώδικας δεν είναι πλέον εύθραυστος, αλλά σταθερός. -Μη γράφετε κώδικα που χρησιμοποιεί παγκόσμια κατάσταση, προτιμήστε να περνάτε εξαρτήσεις. Έτσι, η έγχυση εξαρτήσεων (dependency injection). +Μην γράφετε κώδικα που χρησιμοποιεί καθολική κατάσταση, προτιμήστε τη μεταβίβαση εξαρτήσεων. Δηλαδή, dependency injection. -Singleton .[#toc-singleton] ---------------------------- +Singleton +--------- -Το Singleton είναι ένα μοτίβο σχεδίασης που, σύμφωνα με τον [ορισμό |https://en.wikipedia.org/wiki/Singleton_pattern] από τη διάσημη δημοσίευση της Gang of Four, περιορίζει μια κλάση σε μια μοναδική περίπτωση και προσφέρει παγκόσμια πρόσβαση σε αυτήν. Η υλοποίηση αυτού του προτύπου μοιάζει συνήθως με τον ακόλουθο κώδικα: +Το Singleton είναι ένα σχεδιαστικό πρότυπο που, σύμφωνα με τον "ορισμό":https://en.wikipedia.org/wiki/Singleton_pattern από τη γνωστή δημοσίευση Gang of Four, περιορίζει μια κλάση σε μία μόνο παρουσία και προσφέρει καθολική πρόσβαση σε αυτήν. Η υλοποίηση αυτού του προτύπου συνήθως μοιάζει με τον ακόλουθο κώδικα: ```php class Singleton @@ -195,38 +190,35 @@ class Singleton return self::$instance; } - // και άλλες μεθόδους που εκτελούν τις λειτουργίες της κλάσης + // και άλλες μέθοδοι που εκτελούν τις λειτουργίες της συγκεκριμένης κλάσης } ``` -Δυστυχώς, το singleton εισάγει παγκόσμια κατάσταση στην εφαρμογή. Και όπως δείξαμε παραπάνω, η παγκόσμια κατάσταση είναι ανεπιθύμητη. Αυτός είναι ο λόγος για τον οποίο το singleton θεωρείται αντιπρότυπο. +Δυστυχώς, το singleton εισάγει καθολική κατάσταση στην εφαρμογή. Και όπως δείξαμε παραπάνω, η καθολική κατάσταση είναι ανεπιθύμητη. Επομένως, το singleton θεωρείται αντι-πρότυπο (antipattern). -Μην χρησιμοποιείτε singletons στον κώδικά σας και αντικαταστήστε τα με άλλους μηχανισμούς. Πραγματικά δεν χρειάζεστε singletons. Ωστόσο, αν πρέπει να εγγυηθείτε την ύπαρξη μιας και μόνο περίπτωσης μιας κλάσης για ολόκληρη την εφαρμογή, αφήστε το στο [DI |container] container. -Έτσι, δημιουργήστε ένα singleton της εφαρμογής ή μια υπηρεσία. Αυτό θα σταματήσει την κλάση από το να παρέχει τη δική της μοναδικότητα (δηλαδή, δεν θα έχει μια μέθοδο `getInstance()` και μια στατική μεταβλητή) και θα εκτελεί μόνο τις λειτουργίες της. Έτσι, θα σταματήσει να παραβιάζει την αρχή της ενιαίας ευθύνης. +Μην χρησιμοποιείτε singletons στον κώδικά σας και αντικαταστήστε τα με άλλους μηχανισμούς. Τα singletons πραγματικά δεν τα χρειάζεστε. Εάν, ωστόσο, χρειάζεται να εγγυηθείτε την ύπαρξη μιας μόνο παρουσίας της κλάσης για ολόκληρη την εφαρμογή, αφήστε το στον [DI container |container]. Δημιουργήστε έτσι ένα application singleton, δηλαδή μια υπηρεσία. Με αυτόν τον τρόπο, η κλάση παύει να ασχολείται με τη διασφάλιση της μοναδικότητάς της (δηλ. δεν θα έχει μέθοδο `getInstance()` και στατική μεταβλητή) και θα εκτελεί μόνο τις λειτουργίες της. Έτσι, παύει να παραβιάζει την αρχή της μοναδικής ευθύνης (single responsibility principle). -Παγκόσμια κατάσταση έναντι δοκιμών .[#toc-global-state-versus-tests] --------------------------------------------------------------------- +Καθολική κατάσταση έναντι δοκιμών +--------------------------------- -Όταν γράφουμε δοκιμές, υποθέτουμε ότι κάθε δοκιμή είναι μια απομονωμένη μονάδα και ότι δεν εισέρχεται σε αυτήν καμία εξωτερική κατάσταση. Και καμία κατάσταση δεν φεύγει από τις δοκιμές. Όταν μια δοκιμή ολοκληρώνεται, κάθε κατάσταση που σχετίζεται με τη δοκιμή θα πρέπει να αφαιρείται αυτόματα από τον garbage collector. Αυτό καθιστά τις δοκιμές απομονωμένες. Επομένως, μπορούμε να εκτελέσουμε τις δοκιμές με οποιαδήποτε σειρά. +Κατά τη συγγραφή δοκιμών, υποθέτουμε ότι κάθε δοκιμή είναι μια απομονωμένη μονάδα και ότι καμία εξωτερική κατάσταση δεν εισέρχεται σε αυτήν. Και καμία κατάσταση δεν εξέρχεται από τις δοκιμές. Μετά την ολοκλήρωση της δοκιμής, όλη η σχετική κατάσταση με τη δοκιμή θα πρέπει να αφαιρεθεί αυτόματα από τον garbage collector. Χάρη σε αυτό, οι δοκιμές είναι απομονωμένες. Επομένως, μπορούμε να εκτελέσουμε τις δοκιμές με οποιαδήποτε σειρά. -Ωστόσο, αν υπάρχουν καθολικές καταστάσεις/συνθήκες, όλες αυτές οι ωραίες υποθέσεις καταρρέουν. Μια κατάσταση μπορεί να εισέλθει και να εξέλθει από μια δοκιμή. Ξαφνικά, η σειρά των δοκιμών μπορεί να έχει σημασία. +Εάν, ωστόσο, υπάρχουν καθολικές καταστάσεις/singletons, όλες αυτές οι ευχάριστες υποθέσεις καταρρέουν. Η κατάσταση μπορεί να εισέλθει και να εξέλθει από τη δοκιμή. Ξαφνικά, η σειρά των δοκιμών μπορεί να έχει σημασία. -Για να δοκιμάσουν καθόλου singletons, οι προγραμματιστές πρέπει συχνά να χαλαρώσουν τις ιδιότητές τους, ίσως επιτρέποντας την αντικατάσταση μιας περίπτωσης από μια άλλη. Τέτοιες λύσεις είναι, στην καλύτερη περίπτωση, χάκερς που παράγουν κώδικα που είναι δύσκολο να συντηρηθεί και να κατανοηθεί. Κάθε δοκιμή ή μέθοδος `tearDown()` που επηρεάζει οποιαδήποτε παγκόσμια κατάσταση πρέπει να αναιρεί αυτές τις αλλαγές. +Για να μπορέσουμε καν να δοκιμάσουμε τα singletons, οι προγραμματιστές συχνά πρέπει να χαλαρώσουν τις ιδιότητές τους, για παράδειγμα επιτρέποντας την αντικατάσταση της παρουσίας με μια άλλη. Τέτοιες λύσεις είναι στην καλύτερη περίπτωση ένα hack, που δημιουργεί κώδικα δύσκολο στη συντήρηση και την κατανόηση. Κάθε δοκιμή ή μέθοδος `tearDown()`, που επηρεάζει οποιαδήποτε καθολική κατάσταση, πρέπει να αναιρέσει αυτές τις αλλαγές. -Η παγκόσμια κατάσταση είναι ο μεγαλύτερος πονοκέφαλος στον έλεγχο μονάδων! +Η καθολική κατάσταση είναι ο μεγαλύτερος πονοκέφαλος στις δοκιμές μονάδας (unit testing)! -Πώς να διορθώσετε την κατάσταση; Εύκολα. Μη γράφετε κώδικα που χρησιμοποιεί singletons, προτιμήστε να περνάτε εξαρτήσεις. Δηλαδή, με την έγχυση εξαρτήσεων (dependency injection). +Πώς να διορθώσετε την κατάσταση; Εύκολα. Μην γράφετε κώδικα που χρησιμοποιεί singletons, προτιμήστε τη μεταβίβαση εξαρτήσεων. Δηλαδή, dependency injection. -Παγκόσμιες σταθερές .[#toc-global-constants] --------------------------------------------- +Καθολικές σταθερές +------------------ -Η παγκόσμια κατάσταση δεν περιορίζεται στη χρήση των singletons και των στατικών μεταβλητών, αλλά μπορεί να εφαρμοστεί και στις παγκόσμιες σταθερές. +Η καθολική κατάσταση δεν περιορίζεται μόνο στη χρήση singletons και στατικών μεταβλητών, αλλά μπορεί να αφορά και τις καθολικές σταθερές. -Οι σταθερές των οποίων η τιμή δεν μας παρέχει καμία νέα (`M_PI`) ή χρήσιμη (`PREG_BACKTRACK_LIMIT_ERROR`) πληροφορία είναι σαφώς ΟΚ. -Αντίθετα, οι σταθερές που χρησιμεύουν ως ένας τρόπος για την *ασύρματη* μετάδοση πληροφοριών μέσα στον κώδικα δεν είναι τίποτα περισσότερο από μια κρυφή εξάρτηση. Όπως το `LOG_FILE` στο ακόλουθο παράδειγμα. -Η χρήση της σταθεράς `FILE_APPEND` είναι απολύτως σωστή. +Οι σταθερές, η τιμή των οποίων δεν μας προσφέρει καμία νέα (`M_PI`) ή χρήσιμη (`PREG_BACKTRACK_LIMIT_ERROR`) πληροφορία, είναι σαφώς εντάξει. Αντίθετα, οι σταθερές που χρησιμεύουν ως τρόπος για να περάσουμε *ασύρματα* πληροφορίες μέσα στον κώδικα, δεν είναι τίποτα άλλο από κρυφές εξαρτήσεις. Όπως για παράδειγμα το `LOG_FILE` στο ακόλουθο παράδειγμα. Η χρήση της σταθεράς `FILE_APPEND` είναι απολύτως σωστή. ```php const LOG_FILE = '...'; @@ -242,7 +234,7 @@ class Foo } ``` -Σε αυτή την περίπτωση, θα πρέπει να δηλώσουμε την παράμετρο στον κατασκευαστή της κλάσης `Foo` για να γίνει μέρος του API: +Σε αυτή την περίπτωση, θα έπρεπε να δηλώσουμε μια παράμετρο στον κατασκευαστή της κλάσης `Foo`, ώστε να γίνει μέρος του API: ```php class Foo @@ -261,44 +253,42 @@ class Foo } ``` -Τώρα μπορούμε να περνάμε πληροφορίες σχετικά με τη διαδρομή του αρχείου καταγραφής και να την αλλάζουμε εύκολα ανάλογα με τις ανάγκες, διευκολύνοντας έτσι τον έλεγχο και τη συντήρηση του κώδικα. +Τώρα μπορούμε να περάσουμε την πληροφορία για τη διαδρομή του αρχείου καταγραφής και να την αλλάξουμε εύκολα ανάλογα με τις ανάγκες, πράγμα που διευκολύνει τις δοκιμές και τη συντήρηση του κώδικα. -Παγκόσμιες συναρτήσεις και στατικές μέθοδοι .[#toc-global-functions-and-static-methods] ---------------------------------------------------------------------------------------- +Καθολικές συναρτήσεις και στατικές μέθοδοι +------------------------------------------ -Θέλουμε να τονίσουμε ότι η χρήση στατικών μεθόδων και παγκόσμιων συναρτήσεων δεν είναι από μόνη της προβληματική. Έχουμε εξηγήσει την ακαταλληλότητα της χρήσης του `DB::insert()` και παρόμοιων μεθόδων, αλλά πρόκειται πάντα για την παγκόσμια κατάσταση που αποθηκεύεται σε μια στατική μεταβλητή. Η μέθοδος `DB::insert()` απαιτεί την ύπαρξη μιας στατικής μεταβλητής επειδή αποθηκεύει τη σύνδεση με τη βάση δεδομένων. Χωρίς αυτή τη μεταβλητή, θα ήταν αδύνατη η υλοποίηση της μεθόδου. +Θέλουμε να τονίσουμε ότι η ίδια η χρήση στατικών μεθόδων και καθολικών συναρτήσεων δεν είναι προβληματική. Εξηγήσαμε σε τι συνίσταται η ακαταλληλότητα της χρήσης του `DB::insert()` και παρόμοιων μεθόδων, αλλά πάντα αφορούσε μόνο την καθολική κατάσταση, η οποία είναι αποθηκευμένη σε κάποια στατική μεταβλητή. Η μέθοδος `DB::insert()` απαιτεί την ύπαρξη στατικής μεταβλητής, επειδή σε αυτήν είναι αποθηκευμένη η σύνδεση με τη βάση δεδομένων. Χωρίς αυτή τη μεταβλητή, θα ήταν αδύνατο να υλοποιηθεί η μέθοδος. -Η χρήση ντετερμινιστικών στατικών μεθόδων και συναρτήσεων, όπως οι `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` και πολλές άλλες, είναι απόλυτα συνεπής με την έγχυση εξάρτησης. Αυτές οι συναρτήσεις επιστρέφουν πάντα τα ίδια αποτελέσματα από τις ίδιες παραμέτρους εισόδου και επομένως είναι προβλέψιμες. Δεν χρησιμοποιούν καμία παγκόσμια κατάσταση. +Η χρήση ντετερμινιστικών στατικών μεθόδων και συναρτήσεων, όπως `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` και πολλών άλλων, είναι απολύτως σύμφωνη με το dependency injection. Αυτές οι συναρτήσεις επιστρέφουν πάντα τα ίδια αποτελέσματα για τις ίδιες παραμέτρους εισόδου και είναι επομένως προβλέψιμες. Δεν χρησιμοποιούν καμία καθολική κατάσταση. -Ωστόσο, υπάρχουν συναρτήσεις στην PHP που δεν είναι ντετερμινιστικές. Σε αυτές περιλαμβάνεται, για παράδειγμα, η συνάρτηση `htmlspecialchars()`. Η τρίτη της παράμετρος, `$encoding`, αν δεν καθοριστεί, έχει ως προεπιλογή την τιμή της επιλογής διαμόρφωσης `ini_get('default_charset')`. Επομένως, συνιστάται να προσδιορίζετε πάντα αυτή την παράμετρο για να αποφύγετε πιθανή απρόβλεπτη συμπεριφορά της συνάρτησης. Η Nette το πράττει αυτό με συνέπεια. +Υπάρχουν, ωστόσο, και συναρτήσεις στην PHP που δεν είναι ντετερμινιστικές. Σε αυτές ανήκει, για παράδειγμα, η συνάρτηση `htmlspecialchars()`. Η τρίτη της παράμετρος `$encoding`, εάν δεν αναφέρεται, έχει ως προεπιλεγμένη τιμή την τιμή της επιλογής διαμόρφωσης `ini_get('default_charset')`. Επομένως, συνιστάται να αναφέρεται πάντα αυτή η παράμετρος και να αποφεύγεται έτσι η πιθανή απρόβλεπτη συμπεριφορά της συνάρτησης. Το Nette το κάνει αυτό με συνέπεια. -Ορισμένες συναρτήσεις, όπως η `strtolower()`, η `strtoupper()`, και οι παρόμοιες, είχαν μη ντετερμινιστική συμπεριφορά στο πρόσφατο παρελθόν και εξαρτιόνταν από τη ρύθμιση `setlocale()`. Αυτό προκάλεσε πολλές επιπλοκές, τις περισσότερες φορές όταν εργάζονταν με την τουρκική γλώσσα. -Αυτό οφείλεται στο γεγονός ότι η τουρκική γλώσσα κάνει διάκριση μεταξύ κεφαλαίων και πεζών `I` με και χωρίς τελεία. Έτσι το `strtolower('I')` επέστρεφε τον χαρακτήρα `ı` και το `strtoupper('i')` επέστρεφε τον χαρακτήρα `İ`, γεγονός που οδηγούσε τις εφαρμογές να προκαλούν μια σειρά από μυστηριώδη σφάλματα. -Ωστόσο, το πρόβλημα αυτό διορθώθηκε στην έκδοση 8.2 της PHP και οι λειτουργίες δεν εξαρτώνται πλέον από την τοπική γλώσσα. +Ορισμένες συναρτήσεις, όπως `strtolower()`, `strtoupper()` και παρόμοιες, στο πρόσφατο παρελθόν συμπεριφέρονταν μη ντετερμινιστικά και εξαρτώνταν από τη ρύθμιση `setlocale()`. Αυτό προκαλούσε πολλές επιπλοκές, συχνότερα κατά την εργασία με την τουρκική γλώσσα. Αυτή, δηλαδή, διακρίνει το πεζό και το κεφαλαίο γράμμα `I` με και χωρίς τελεία. Έτσι, το `strtolower('I')` επέστρεφε τον χαρακτήρα `ı` και το `strtoupper('i')` τον χαρακτήρα `İ`, πράγμα που οδηγούσε στο να αρχίσουν οι εφαρμογές να προκαλούν μια σειρά από μυστηριώδη σφάλματα. Αυτό το πρόβλημα, ωστόσο, διορθώθηκε στην έκδοση PHP 8.2 και οι συναρτήσεις δεν εξαρτώνται πλέον από το locale. -Αυτό είναι ένα ωραίο παράδειγμα του πώς η παγκόσμια κατάσταση έχει ταλαιπωρήσει χιλιάδες προγραμματιστές σε όλο τον κόσμο. Η λύση ήταν η αντικατάστασή του με την έγχυση εξάρτησης. +Πρόκειται για ένα ωραίο παράδειγμα του πώς η καθολική κατάσταση ταλαιπώρησε χιλιάδες προγραμματιστές σε όλο τον κόσμο. Η λύση ήταν η αντικατάστασή της με dependency injection. -Πότε είναι δυνατή η χρήση του Global State; .[#toc-when-is-it-possible-to-use-global-state] -------------------------------------------------------------------------------------------- +Πότε είναι δυνατόν να χρησιμοποιηθεί η καθολική κατάσταση? +---------------------------------------------------------- -Υπάρχουν ορισμένες συγκεκριμένες καταστάσεις στις οποίες είναι δυνατή η χρήση καθολικής κατάστασης. Για παράδειγμα, όταν κάνετε αποσφαλμάτωση κώδικα και πρέπει να απορρίψετε την τιμή μιας μεταβλητής ή να μετρήσετε τη διάρκεια ενός συγκεκριμένου τμήματος του προγράμματος. Σε τέτοιες περιπτώσεις, οι οποίες αφορούν προσωρινές ενέργειες που θα αφαιρεθούν αργότερα από τον κώδικα, είναι θεμιτό να χρησιμοποιήσετε έναν σφαιρικά διαθέσιμο ντάμπερ ή ένα χρονόμετρο. Τα εργαλεία αυτά δεν αποτελούν μέρος του σχεδιασμού του κώδικα. +Υπάρχουν ορισμένες συγκεκριμένες καταστάσεις όπου είναι δυνατόν να χρησιμοποιηθεί η καθολική κατάσταση. Για παράδειγμα, κατά τον εντοπισμό σφαλμάτων στον κώδικα, όταν χρειάζεται να εκτυπώσετε την τιμή μιας μεταβλητής ή να μετρήσετε τη διάρκεια ενός συγκεκριμένου τμήματος του προγράμματος. Σε τέτοιες περιπτώσεις, που αφορούν προσωρινές ενέργειες οι οποίες θα αφαιρεθούν αργότερα από τον κώδικα, είναι δυνατόν να χρησιμοποιηθεί θεμιτά ένας καθολικά διαθέσιμος dumper ή χρονόμετρο. Αυτά τα εργαλεία, δηλαδή, δεν αποτελούν μέρος του σχεδιασμού του κώδικα. -Ένα άλλο παράδειγμα είναι οι συναρτήσεις για την εργασία με κανονικές εκφράσεις `preg_*`, οι οποίες αποθηκεύουν εσωτερικά τις μεταγλωττισμένες κανονικές εκφράσεις σε μια στατική κρυφή μνήμη στη μνήμη. Όταν καλείτε την ίδια κανονική έκφραση πολλές φορές σε διαφορετικά μέρη του κώδικα, αυτή μεταγλωττίζεται μόνο μία φορά. Η κρυφή μνήμη εξοικονομεί απόδοση και είναι επίσης εντελώς αόρατη στον χρήστη, οπότε μια τέτοια χρήση μπορεί να θεωρηθεί νόμιμη. +Ένα άλλο παράδειγμα είναι οι συναρτήσεις για την εργασία με κανονικές εκφράσεις `preg_*`, οι οποίες εσωτερικά αποθηκεύουν τις μεταγλωττισμένες κανονικές εκφράσεις σε μια στατική cache στη μνήμη. Όταν λοιπόν καλείτε την ίδια κανονική έκφραση πολλές φορές σε διαφορετικά σημεία του κώδικα, μεταγλωττίζεται μόνο μία φορά. Η cache εξοικονομεί απόδοση και ταυτόχρονα είναι για τον χρήστη εντελώς αόρατη, επομένως μια τέτοια χρήση μπορεί να θεωρηθεί θεμιτή. -Περίληψη .[#toc-summary] ------------------------- +Σύνοψη +------ -Δείξαμε γιατί έχει νόημα +Συζητήσαμε γιατί έχει νόημα: -1) Αφαιρέστε όλες τις στατικές μεταβλητές από τον κώδικα -2) Δηλώστε εξαρτήσεις -3) Και χρησιμοποιήστε την έγχυση εξαρτήσεων +1) Να αφαιρέσετε όλες τις στατικές μεταβλητές από τον κώδικα +2) Να δηλώσετε τις εξαρτήσεις +3) Και να χρησιμοποιείτε dependency injection -Όταν μελετάτε το σχεδιασμό κώδικα, να έχετε κατά νου ότι κάθε `static $foo` αντιπροσωπεύει ένα πρόβλημα. Προκειμένου ο κώδικάς σας να είναι ένα περιβάλλον που σέβεται το DI, είναι απαραίτητο να εξαλείψετε εντελώς την παγκόσμια κατάσταση και να την αντικαταστήσετε με έγχυση εξαρτήσεων. +Όταν σκέφτεστε τον σχεδιασμό του κώδικα, σκεφτείτε ότι κάθε `static $foo` αποτελεί πρόβλημα. Για να είναι ο κώδικάς σας ένα περιβάλλον που σέβεται το DI, είναι απαραίτητο να εξαλείψετε εντελώς την καθολική κατάσταση και να την αντικαταστήσετε με dependency injection. -Κατά τη διάρκεια αυτής της διαδικασίας, μπορεί να διαπιστώσετε ότι πρέπει να χωρίσετε μια κλάση επειδή έχει περισσότερες από μία αρμοδιότητες. Μην ανησυχείτε γι' αυτό- επιδιώξτε την αρχή της μίας ευθύνης. +Κατά τη διάρκεια αυτής της διαδικασίας, ίσως διαπιστώσετε ότι είναι απαραίτητο να χωρίσετε την κλάση, επειδή έχει περισσότερες από μία ευθύνες. Μην το φοβάστε. επιδιώξτε την αρχή της μοναδικής ευθύνης. -*Θα ήθελα να ευχαριστήσω τον Miško Hevery, του οποίου άρθρα όπως το [Flaw: Brittle Global State & Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/] αποτελούν τη βάση αυτού του κεφαλαίου.* +*Θα ήθελα να ευχαριστήσω τον Miško Hevery, του οποίου τα άρθρα, όπως το [Flaw: Brittle Global State & Singletons |https://web.archive.org/web/20230321084133/http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/], αποτελούν τη βάση αυτού του κεφαλαίου.* diff --git a/dependency-injection/el/introduction.texy b/dependency-injection/el/introduction.texy index f64ff0169a..1711cd3f9b 100644 --- a/dependency-injection/el/introduction.texy +++ b/dependency-injection/el/introduction.texy @@ -2,57 +2,57 @@ ********************************* .[perex] -Αυτό το κεφάλαιο θα σας εισαγάγει στις βασικές πρακτικές προγραμματισμού που πρέπει να ακολουθείτε κατά τη συγγραφή οποιασδήποτε εφαρμογής. Αυτές είναι οι βασικές αρχές που απαιτούνται για τη συγγραφή καθαρού, κατανοητού και συντηρήσιμου κώδικα. +Αυτό το κεφάλαιο θα σας εισαγάγει στις βασικές πρακτικές προγραμματισμού που πρέπει να ακολουθείτε κατά τη συγγραφή όλων των εφαρμογών. Αυτά είναι τα θεμέλια που απαιτούνται για τη συγγραφή καθαρού, κατανοητού και συντηρήσιμου κώδικα. -Αν μάθετε και ακολουθήσετε αυτούς τους κανόνες, η Nette θα είναι δίπλα σας σε κάθε σας βήμα. Θα χειρίζεται εργασίες ρουτίνας για εσάς και θα σας παρέχει τη μέγιστη δυνατή άνεση, ώστε να μπορείτε να επικεντρωθείτε στην ίδια τη λογική. +Εάν υιοθετήσετε αυτούς τους κανόνες και τους ακολουθήσετε, το Nette θα σας βοηθήσει σε κάθε βήμα. Θα χειριστεί τις εργασίες ρουτίνας για εσάς και θα σας προσφέρει μέγιστη άνεση, ώστε να μπορείτε να επικεντρωθείτε στην ίδια τη λογική. Οι αρχές που θα παρουσιάσουμε εδώ είναι αρκετά απλές. Δεν χρειάζεται να ανησυχείτε για τίποτα. -Θυμάστε το πρώτο σας πρόγραμμα; .[#toc-remember-your-first-program] -------------------------------------------------------------------- +Θυμάστε το πρώτο σας πρόγραμμα; +------------------------------- -Δεν ξέρουμε σε ποια γλώσσα το γράψατε, αλλά αν ήταν PHP, μπορεί να έμοιαζε κάπως έτσι: +Δεν ξέρουμε σε ποια γλώσσα το γράψατε, αλλά αν ήταν PHP, πιθανότατα θα έμοιαζε κάπως έτσι: ```php -function addition(float $a, float $b): float +function soucet(float $a, float $b): float { return $a + $b; } -echo addition(23, 1); // εκτυπώσεις 24 +echo soucet(23, 1); // εκτυπώνει 24 ``` -Λίγες ασήμαντες γραμμές κώδικα, αλλά τόσες πολλές βασικές έννοιες κρυμμένες σε αυτές. Ότι υπάρχουν μεταβλητές. Ότι ο κώδικας αναλύεται σε μικρότερες μονάδες, οι οποίες είναι συναρτήσεις, για παράδειγμα. Ότι τους δίνουμε ορίσματα εισόδου και επιστρέφουν αποτελέσματα. Το μόνο που λείπει είναι οι συνθήκες και οι βρόχοι. +Λίγες ασήμαντες γραμμές κώδικα, αλλά περιέχουν τόσες πολλές βασικές έννοιες. Ότι υπάρχουν μεταβλητές. Ότι ο κώδικας χωρίζεται σε μικρότερες μονάδες, όπως συναρτήσεις. Ότι τους περνάμε ορίσματα εισόδου και επιστρέφουν αποτελέσματα. Λείπουν μόνο οι συνθήκες και οι βρόχοι. -Το γεγονός ότι μια συνάρτηση λαμβάνει δεδομένα εισόδου και επιστρέφει ένα αποτέλεσμα είναι μια απολύτως κατανοητή έννοια, η οποία χρησιμοποιείται και σε άλλους τομείς, όπως τα μαθηματικά. +Το γεγονός ότι περνάμε δεδομένα εισόδου σε μια συνάρτηση και αυτή επιστρέφει ένα αποτέλεσμα είναι μια απολύτως κατανοητή έννοια που χρησιμοποιείται και σε άλλους τομείς, όπως τα μαθηματικά. -Μια συνάρτηση έχει την υπογραφή της, η οποία αποτελείται από το όνομά της, έναν κατάλογο παραμέτρων και τους τύπους τους και, τέλος, τον τύπο της τιμής επιστροφής. Ως χρήστες, μας ενδιαφέρει η υπογραφή και συνήθως δεν χρειάζεται να γνωρίζουμε τίποτα για την εσωτερική υλοποίηση. +Μια συνάρτηση έχει την υπογραφή της, η οποία αποτελείται από το όνομά της, μια λίστα παραμέτρων και τους τύπους τους, και τέλος τον τύπο της τιμής επιστροφής. Ως χρήστες, μας ενδιαφέρει η υπογραφή· συνήθως δεν χρειάζεται να γνωρίζουμε τίποτα για την εσωτερική υλοποίηση. -Φανταστείτε τώρα ότι η υπογραφή της συνάρτησης έμοιαζε ως εξής: +Τώρα φανταστείτε η υπογραφή της συνάρτησης να έμοιαζε κάπως έτσι: ```php -function addition(float $x): float +function soucet(float $x): float ``` -Μια προσθήκη με μία παράμετρο; Αυτό είναι παράξενο... Τι λέτε γι' αυτό; +Άθροισμα με μία παράμετρο; Αυτό είναι περίεργο… Και τι θα λέγατε για αυτό; ```php -function addition(): float +function soucet(): float ``` -Τώρα αυτό είναι πραγματικά περίεργο, σωστά; Πώς χρησιμοποιείται η συνάρτηση; +Αυτό είναι πραγματικά πολύ περίεργο, έτσι δεν είναι; Πώς χρησιμοποιείται η συνάρτηση; ```php -echo addition(); // τι εκτυπώνει; +echo soucet(); // τι θα εκτυπώσει άραγε; ``` -Κοιτάζοντας έναν τέτοιο κώδικα, θα ήμασταν μπερδεμένοι. Όχι μόνο ένας αρχάριος δεν θα τον καταλάβαινε, αλλά ακόμη και ένας έμπειρος προγραμματιστής δεν θα καταλάβαινε έναν τέτοιο κώδικα. +Κοιτάζοντας έναν τέτοιο κώδικα, θα ήμασταν μπερδεμένοι. Όχι μόνο ένας αρχάριος δεν θα τον καταλάβαινε, αλλά ούτε και ένας έμπειρος προγραμματιστής δεν καταλαβαίνει τέτοιο κώδικα. -Αναρωτιέστε πώς θα έμοιαζε στην πραγματικότητα μια τέτοια συνάρτηση στο εσωτερικό της; Από πού θα έπαιρνε τα αθροίσματα; Πιθανότατα θα τις έπαιρνε *με κάποιο τρόπο* από μόνη της, ίσως κάπως έτσι: +Αναρωτιέστε πώς θα έμοιαζε μια τέτοια συνάρτηση εσωτερικά; Από πού θα έπαιρνε τους προσθετέους; Προφανώς, θα τους έβρισκε *με κάποιο τρόπο* μόνη της, ίσως κάπως έτσι: ```php -function addition(): float +function soucet(): float { $a = Input::get('a'); $b = Input::get('b'); @@ -60,71 +60,71 @@ function addition(): float } ``` -Αποδεικνύεται ότι υπάρχουν κρυφές συνδέσεις με άλλες συναρτήσεις (ή στατικές μεθόδους) στο σώμα της συνάρτησης, και για να μάθουμε από πού προέρχονται στην πραγματικότητα οι προσθετέοι, πρέπει να ψάξουμε περισσότερο. +Στο σώμα της συνάρτησης, ανακαλύψαμε κρυφές εξαρτήσεις από άλλες καθολικές συναρτήσεις ή στατικές μεθόδους. Για να μάθουμε από πού προέρχονται πραγματικά οι προσθετέοι, πρέπει να ψάξουμε περαιτέρω. -Όχι με αυτόν τον τρόπο! .[#toc-not-this-way] --------------------------------------------- +Όχι από εδώ! +------------ Ο σχεδιασμός που μόλις δείξαμε είναι η ουσία πολλών αρνητικών χαρακτηριστικών: -- Η υπογραφή της συνάρτησης προσποιούνταν ότι δεν χρειαζόταν τα αθροίσματα, γεγονός που μας μπέρδευε. -- δεν έχουμε ιδέα πώς να κάνουμε τη συνάρτηση να υπολογίζει με δύο άλλους αριθμούς -- έπρεπε να κοιτάξουμε τον κώδικα για να βρούμε από πού προέρχονταν τα αθροίσματα -- Βρήκαμε κρυφές εξαρτήσεις. -- η πλήρης κατανόηση απαιτεί την εξέταση και αυτών των εξαρτήσεων +- η υπογραφή της συνάρτησης προσποιούνταν ότι δεν χρειαζόταν προσθετέους, πράγμα που μας μπέρδεψε +- δεν ξέρουμε καθόλου πώς να κάνουμε τη συνάρτηση να προσθέσει δύο άλλους αριθμούς +- έπρεπε να κοιτάξουμε τον κώδικα για να δούμε από πού έπαιρνε τους προσθετέους +- ανακαλύψαμε κρυφές εξαρτήσεις +- για πλήρη κατανόηση, είναι απαραίτητο να εξετάσουμε και αυτές τις εξαρτήσεις -Και μήπως είναι καν δουλειά της συνάρτησης πρόσθεσης να προμηθεύεται εισόδους; Φυσικά και δεν είναι. Η ευθύνη της είναι μόνο να προσθέτει. +Και είναι καθόλου έργο της συνάρτησης πρόσθεσης να αποκτά εισόδους; Φυσικά και όχι. Η ευθύνη της είναι μόνο η ίδια η πρόσθεση. -Δεν θέλουμε να συναντήσουμε τέτοιο κώδικα και σίγουρα δεν θέλουμε να τον γράψουμε. Η λύση είναι απλή: επιστρέψτε στα βασικά και χρησιμοποιήστε απλά παραμέτρους: +Δεν θέλουμε να συναντήσουμε τέτοιο κώδικα, και σίγουρα δεν θέλουμε να τον γράψουμε. Η διόρθωση είναι απλή: επιστροφή στα βασικά και απλή χρήση παραμέτρων: ```php -function addition(float $a, float $b): float +function soucet(float $a, float $b): float { return $a + $b; } ``` -Κανόνας #1: Αφήστε να περάσει σε σας .[#toc-rule-1-let-it-be-passed-to-you] ---------------------------------------------------------------------------- +Κανόνας αρ. 1: αφήστε το να σας παραδοθεί +----------------------------------------- -Ο πιο σημαντικός κανόνας είναι: **Όλα τα δεδομένα που χρειάζονται οι συναρτήσεις ή οι κλάσεις πρέπει να τους μεταβιβάζονται**. +Ο πιο σημαντικός κανόνας είναι: **όλα τα δεδομένα που χρειάζονται οι συναρτήσεις ή οι κλάσεις πρέπει να τους παραδίδονται**. -Αντί να εφευρίσκονται κρυφοί τρόποι για να έχουν πρόσβαση στα δεδομένα, απλώς μεταβιβάστε τις παραμέτρους. Θα εξοικονομήσετε χρόνο που θα ξοδευόταν στην εφεύρεση κρυφών μονοπατιών που σίγουρα δεν θα βελτιώσουν τον κώδικά σας. +Αντί να επινοείτε κρυφούς τρόπους με τους οποίους θα μπορούσαν να τα αποκτήσουν μόνοι τους, απλά περάστε τις παραμέτρους. Θα εξοικονομήσετε χρόνο που απαιτείται για την επινόηση κρυφών μονοπατιών, τα οποία σίγουρα δεν θα βελτιώσουν τον κώδικά σας. -Αν ακολουθείτε πάντα και παντού αυτόν τον κανόνα, βρίσκεστε στο δρόμο για κώδικα χωρίς κρυφές εξαρτήσεις. Σε κώδικα που είναι κατανοητός όχι μόνο για τον συγγραφέα αλλά και για οποιονδήποτε τον διαβάσει στη συνέχεια. Όπου τα πάντα είναι κατανοητά από τις υπογραφές των συναρτήσεων και των κλάσεων και δεν χρειάζεται να ψάχνετε για κρυμμένα μυστικά στην υλοποίηση. +Αν ακολουθείτε πάντα και παντού αυτόν τον κανόνα, βρίσκεστε στο δρόμο για κώδικα χωρίς κρυφές εξαρτήσεις. Για κώδικα που είναι κατανοητός όχι μόνο στον συγγραφέα, αλλά και σε οποιονδήποτε τον διαβάσει μετά από αυτόν. Όπου όλα είναι κατανοητά από τις υπογραφές των συναρτήσεων και των κλάσεων και δεν χρειάζεται να ψάχνετε για κρυμμένα μυστικά στην υλοποίηση. -Αυτή η τεχνική ονομάζεται επαγγελματικά **έγχυση εξάρτησης**. Και αυτά τα δεδομένα ονομάζονται **εξαρτήσεις**. Είναι απλά μια συνηθισμένη μεταβίβαση παραμέτρων, τίποτα περισσότερο. +Αυτή η τεχνική ονομάζεται τεχνικά **dependency injection**. Και αυτά τα δεδομένα ονομάζονται **εξαρτήσεις (dependencies).** Στην πραγματικότητα, είναι απλή παράδοση παραμέτρων, τίποτα περισσότερο. .[note] -Παρακαλώ μην συγχέετε την έγχυση εξαρτήσεων, η οποία είναι ένα πρότυπο σχεδίασης, με ένα "dependency injection container", το οποίο είναι ένα εργαλείο, κάτι διαμετρικά διαφορετικό. Θα ασχοληθούμε με τους περιέκτες αργότερα. +Παρακαλώ μην συγχέετε το dependency injection, το οποίο είναι ένα πρότυπο σχεδίασης, με το "dependency injection container", το οποίο είναι ένα εργαλείο, δηλαδή κάτι διαμετρικά αντίθετο. Θα ασχοληθούμε με τα containers αργότερα. -Από τις συναρτήσεις στις κλάσεις .[#toc-from-functions-to-classes] ------------------------------------------------------------------- +Από συναρτήσεις σε κλάσεις +-------------------------- -Και πώς συνδέονται οι τάξεις; Μια κλάση είναι μια πιο σύνθετη μονάδα από μια απλή συνάρτηση, αλλά ο κανόνας #1 ισχύει πλήρως και εδώ. Απλά υπάρχουν [περισσότεροι τρόποι για να περάσετε ορίσματα |passing-dependencies]. Για παράδειγμα, αρκετά παρόμοια με την περίπτωση μιας συνάρτησης: +Και πώς σχετίζονται οι κλάσεις με αυτό; Μια κλάση είναι μια πιο σύνθετη οντότητα από μια απλή συνάρτηση, ωστόσο ο κανόνας αρ. 1 ισχύει πλήρως και εδώ. Απλώς υπάρχουν [περισσότερες επιλογές για την παράδοση ορισμάτων |passing-dependencies]. Για παράδειγμα, αρκετά παρόμοια με την περίπτωση μιας συνάρτησης: ```php -class Math +class Matematika { - public function addition(float $a, float $b): float + public function soucet(float $a, float $b): float { return $a + $b; } } -$math = new Math; -echo $math->addition(23, 1); // 24 +$math = new Matematika; +echo $math->soucet(23, 1); // 24 ``` -Ή μέσω άλλων μεθόδων ή απευθείας μέσω του κατασκευαστή: +Ή χρησιμοποιώντας άλλες μεθόδους, ή απευθείας τον κατασκευαστή: ```php -class Addition +class Soucet { public function __construct( private float $a, @@ -132,26 +132,26 @@ class Addition ) { } - public function calculate(): float + public function spocti(): float { return $this->a + $this->b; } } -$addition = new Addition(23, 1); -echo $addition->calculate(); // 24 +$soucet = new Soucet(23, 1); +echo $soucet->spocti(); // 24 ``` -Και τα δύο παραδείγματα συμμορφώνονται πλήρως με την έγχυση εξάρτησης. +Και τα δύο παραδείγματα είναι πλήρως σύμφωνα με το dependency injection. -Παραδείγματα πραγματικής ζωής .[#toc-real-life-examples] --------------------------------------------------------- +Πραγματικά παραδείγματα +----------------------- -Στον πραγματικό κόσμο, δεν θα γράφετε μαθήματα για την πρόσθεση αριθμών. Ας προχωρήσουμε σε πρακτικά παραδείγματα. +Στον πραγματικό κόσμο, δεν θα γράφετε κλάσεις για την πρόσθεση αριθμών. Ας προχωρήσουμε σε παραδείγματα από την πράξη. -Ας έχουμε μια κλάση `Article` που αναπαριστά μια δημοσίευση σε ιστολόγιο: +Έστω μια κλάση `Article` που αντιπροσωπεύει ένα άρθρο σε ένα blog: ```php class Article @@ -162,7 +162,7 @@ class Article public function save(): void { - // αποθήκευση του άρθρου στη βάση δεδομένων + // αποθηκεύουμε το άρθρο στη βάση δεδομένων } } ``` @@ -176,9 +176,9 @@ $article->content = 'Every year millions of people in ...'; $article->save(); ``` -Η μέθοδος `save()` θα αποθηκεύσει το άρθρο σε έναν πίνακα της βάσης δεδομένων. Η υλοποίησή της με τη χρήση [της Nette Database |database:] θα είναι πανεύκολη, αν δεν υπήρχε ένα πρόβλημα: από πού παίρνει η `Article` τη σύνδεση με τη βάση δεδομένων, δηλαδή ένα αντικείμενο της κλάσης `Nette\Database\Connection`; +Η μέθοδος `save()` αποθηκεύει το άρθρο σε έναν πίνακα βάσης δεδομένων. Η υλοποίησή της με τη βοήθεια του [Nette Database |database:] θα ήταν παιχνιδάκι, αν δεν υπήρχε ένα εμπόδιο: πού παίρνει η `Article` τη σύνδεση με τη βάση δεδομένων, δηλαδή το αντικείμενο της κλάσης `Nette\Database\Connection`; -Φαίνεται ότι έχουμε πολλές επιλογές. Μπορεί να την πάρει από μια στατική μεταβλητή κάπου. Ή να κληρονομήσει από μια κλάση που παρέχει μια σύνδεση με τη βάση δεδομένων. Ή να επωφεληθεί από ένα [singleton |global-state#Singleton]. Ή να χρησιμοποιήσει τα λεγόμενα facades, τα οποία χρησιμοποιούνται στο Laravel: +Φαίνεται ότι έχουμε πολλές επιλογές. Μπορεί να την πάρει από κάπου από μια στατική μεταβλητή. Ή να κληρονομήσει από μια κλάση που εξασφαλίζει τη σύνδεση με τη βάση δεδομένων. Ή να χρησιμοποιήσει το λεγόμενο [singleton |global-state#Singleton]. Ή τις λεγόμενες facades, που χρησιμοποιούνται στο Laravel: ```php use Illuminate\Support\Facades\DB; @@ -199,17 +199,17 @@ class Article } ``` -Υπέροχα, έχουμε λύσει το πρόβλημα. +Υπέροχα, λύσαμε το πρόβλημα. Ή μήπως όχι; -Ας θυμηθούμε τον [κανόνα #1: Let It Be Passed to |#rule #1: Let It Be Passed to You] You: όλες οι εξαρτήσεις που χρειάζεται η κλάση πρέπει να περάσουν σε αυτήν. Διότι αν παραβιάσουμε τον κανόνα, έχουμε ξεκινήσει μια πορεία προς βρώμικο κώδικα γεμάτο κρυφές εξαρτήσεις, ακατανόητο και το αποτέλεσμα θα είναι μια εφαρμογή που θα είναι οδυνηρή στη συντήρηση και την ανάπτυξη. +Ας θυμηθούμε τον [##Κανόνας αρ. 1: αφήστε το να σας παραδοθεί]: όλες οι εξαρτήσεις που χρειάζεται η κλάση πρέπει να της παραδίδονται. Επειδή αν παραβιάσουμε τον κανόνα, έχουμε πάρει τον δρόμο για βρώμικο κώδικα γεμάτο κρυφές εξαρτήσεις, ασάφεια, και το αποτέλεσμα θα είναι μια εφαρμογή που θα είναι επώδυνο να συντηρηθεί και να αναπτυχθεί. -Ο χρήστης της κλάσης `Article` δεν έχει ιδέα πού αποθηκεύει το άρθρο η μέθοδος `save()`. Σε έναν πίνακα της βάσης δεδομένων; Σε ποιον, στην παραγωγή ή στις δοκιμές; Και πώς μπορεί να αλλάξει; +Ο χρήστης της κλάσης `Article` δεν έχει ιδέα πού αποθηκεύει η μέθοδος `save()` το άρθρο. Σε έναν πίνακα βάσης δεδομένων; Σε ποιον, τον παραγωγικό ή τον δοκιμαστικό; Και πώς μπορεί να αλλάξει αυτό; -Ο χρήστης πρέπει να κοιτάξει πώς υλοποιείται η μέθοδος `save()` και να βρει τη χρήση της μεθόδου `DB::insert()`. Έτσι, πρέπει να ψάξει περαιτέρω για να βρει πώς αυτή η μέθοδος αποκτά μια σύνδεση με τη βάση δεδομένων. Και οι κρυφές εξαρτήσεις μπορεί να σχηματίσουν μια αρκετά μεγάλη αλυσίδα. +Ο χρήστης πρέπει να δει πώς υλοποιείται η μέθοδος `save()` και βρίσκει τη χρήση της μεθόδου `DB::insert()`. Άρα πρέπει να ψάξει περαιτέρω, πώς αυτή η μέθοδος αποκτά τη σύνδεση με τη βάση δεδομένων. Και οι κρυφές εξαρτήσεις μπορούν να σχηματίσουν μια αρκετά μεγάλη αλυσίδα. -Σε καθαρό και καλά σχεδιασμένο κώδικα, δεν υπάρχουν ποτέ κρυφές εξαρτήσεις, προσόψεις του Laravel ή στατικές μεταβλητές. Στον καθαρό και καλά σχεδιασμένο κώδικα, τα ορίσματα μεταβιβάζονται: +Σε καθαρό και καλά σχεδιασμένο κώδικα, δεν υπάρχουν ποτέ κρυφές εξαρτήσεις, facades του Laravel ή στατικές μεταβλητές. Σε καθαρό και καλά σχεδιασμένο κώδικα, παραδίδονται ορίσματα: ```php class Article @@ -224,7 +224,7 @@ class Article } ``` -Μια ακόμα πιο πρακτική προσέγγιση, όπως θα δούμε αργότερα, θα είναι μέσω του κατασκευαστή: +Ακόμα πιο πρακτικό, όπως θα δούμε παρακάτω, θα είναι με τον κατασκευαστή: ```php class Article @@ -245,11 +245,11 @@ class Article ``` .[note] -Αν είστε έμπειρος προγραμματιστής, μπορεί να σκεφτείτε ότι το `Article` δεν θα έπρεπε να έχει καθόλου τη μέθοδο `save()` - θα έπρεπε να αντιπροσωπεύει ένα καθαρά στοιχείο δεδομένων και ένα ξεχωριστό αποθετήριο θα έπρεπε να φροντίζει για την αποθήκευση. Αυτό είναι λογικό. Αλλά αυτό θα μας πήγαινε πολύ πέρα από το αντικείμενο του θέματος, που είναι η έγχυση εξαρτήσεων, και την προσπάθεια να δώσουμε απλά παραδείγματα. +Αν είστε έμπειρος προγραμματιστής, ίσως σκέφτεστε ότι η `Article` δεν θα έπρεπε καθόλου να έχει τη μέθοδο `save()`, θα έπρεπε να αντιπροσωπεύει ένα καθαρά δεδομενικό component και η αποθήκευση θα έπρεπε να γίνεται από ένα ξεχωριστό repository. Αυτό έχει νόημα. Αλλά αυτό θα μας πήγαινε πολύ πέρα από το θέμα, το οποίο είναι το dependency injection, και την προσπάθεια να δώσουμε απλά παραδείγματα. -Αν γράψετε μια κλάση που απαιτεί, για παράδειγμα, μια βάση δεδομένων για τη λειτουργία της, μην επινοήσετε από πού θα την πάρετε, αλλά να την έχετε περάσει. Είτε ως παράμετρο του κατασκευαστή είτε ως κάποια άλλη μέθοδο. Παραδεχτείτε εξαρτήσεις. Παραδεχτείτε τις στο API της κλάσης σας. Θα έχετε κατανοητό και προβλέψιμο κώδικα. +Αν γράφετε μια κλάση που απαιτεί, για παράδειγμα, μια βάση δεδομένων για τη λειτουργία της, μην επινοείτε από πού να την πάρετε, αλλά αφήστε την να σας παραδοθεί. Ίσως ως παράμετρος του κατασκευαστή ή άλλης μεθόδου. Αναγνωρίστε τις εξαρτήσεις. Αναγνωρίστε τις στο API της κλάσης σας. Θα αποκτήσετε κατανοητό και προβλέψιμο κώδικα. -Και τι γίνεται με αυτή την κλάση, η οποία καταγράφει μηνύματα σφάλματος: +Και τι θα λέγατε για αυτήν την κλάση, η οποία καταγράφει μηνύματα σφάλματος: ```php class Logger @@ -262,21 +262,21 @@ class Logger } ``` -Τι νομίζετε, ακολουθήσαμε τον [κανόνα #1: Αφήστε να περάσει σε σας |#rule #1: Let It Be Passed to You]; +Τι πιστεύετε, τηρήσαμε τον [##Κανόνας αρ. 1: αφήστε το να σας παραδοθεί]? -Δεν το κάναμε. +Δεν τον τηρήσαμε. -Η βασική πληροφορία, δηλαδή ο κατάλογος με το αρχείο καταγραφής, *αποκτάται* από την ίδια την κλάση από τη σταθερά. +Η κλάση *αποκτά μόνη της* την κρίσιμη πληροφορία, δηλαδή τον κατάλογο με το αρχείο καταγραφής, από μια σταθερά. -Κοιτάξτε το παράδειγμα χρήσης: +Δείτε το παράδειγμα χρήσης: ```php $logger = new Logger; -$logger->log('The temperature is 23 °C'); -$logger->log('The temperature is 10 °C'); +$logger->log('Η θερμοκρασία είναι 23 °C'); +$logger->log('Η θερμοκρασία είναι 10 °C'); ``` -Χωρίς να γνωρίζετε την υλοποίηση, μπορείτε να απαντήσετε στο ερώτημα πού γράφονται τα μηνύματα; Θα μπορούσατε να υποθέσετε ότι η ύπαρξη της σταθεράς `LOG_DIR` είναι απαραίτητη για τη λειτουργία του; Και θα μπορούσατε να δημιουργήσετε μια δεύτερη περίπτωση που θα έγραφε σε διαφορετική θέση; Σίγουρα όχι. +Χωρίς γνώση της υλοποίησης, θα μπορούσατε να απαντήσετε στην ερώτηση πού γράφονται τα μηνύματα; Θα σκεφτόσασταν ότι για τη λειτουργία απαιτείται η ύπαρξη της σταθεράς `LOG_DIR`; Και θα μπορούσατε να δημιουργήσετε μια δεύτερη παρουσία που θα γράφει αλλού; Σίγουρα όχι. Ας διορθώσουμε την κλάση: @@ -295,24 +295,24 @@ class Logger } ``` -Η κλάση είναι τώρα πολύ πιο κατανοητή, παραμετροποιήσιμη και επομένως πιο χρήσιμη. +Η κλάση είναι τώρα πολύ πιο κατανοητή, διαμορφώσιμη και επομένως πιο χρήσιμη. ```php $logger = new Logger('/path/to/log.txt'); -$logger->log('The temperature is 15 °C'); +$logger->log('Η θερμοκρασία είναι 15 °C'); ``` -Αλλά δεν με νοιάζει! .[#toc-but-i-don-t-care] ---------------------------------------------- +Αλλά αυτό δεν με ενδιαφέρει! +---------------------------- -*"Όταν δημιουργώ ένα αντικείμενο Article και καλώ την save(), δεν θέλω να ασχοληθώ με τη βάση δεδομένων- θέλω απλώς να αποθηκευτεί σε αυτήν που έχω ορίσει στη ρύθμιση παραμέτρων."* +*«Όταν δημιουργώ ένα αντικείμενο Article και καλώ την save(), δεν θέλω να ασχολούμαι με τη βάση δεδομένων, απλά θέλω να αποθηκευτεί σε αυτήν που έχω ορίσει στη διαμόρφωση.»* -*"Όταν χρησιμοποιώ το Logger, θέλω απλώς να γράφεται το μήνυμα και δεν θέλω να ασχοληθώ με το πού. Αφήστε να χρησιμοποιηθούν οι παγκόσμιες ρυθμίσεις."* +*«Όταν χρησιμοποιώ το Logger, απλά θέλω το μήνυμα να καταγραφεί, και δεν θέλω να ασχολούμαι με το πού. Ας χρησιμοποιηθεί η καθολική ρύθμιση.»* -Αυτά είναι βάσιμα σημεία. +Αυτές είναι σωστές παρατηρήσεις. -Ως παράδειγμα, ας δούμε μια κλάση που στέλνει ενημερωτικά δελτία και καταγράφει πώς πήγε: +Ως παράδειγμα, θα δείξουμε μια κλάση που στέλνει newsletters, η οποία καταγράφει πώς πήγε: ```php class NewsletterDistributor @@ -322,27 +322,27 @@ class NewsletterDistributor $logger = new Logger(/* ... */); try { $this->sendEmails(); - $logger->log('Emails have been sent out'); + $logger->log('Τα emails στάλθηκαν'); } catch (Exception $e) { - $logger->log('An error occurred during the sending'); + $logger->log('Παρουσιάστηκε σφάλμα κατά την αποστολή'); throw $e; } } } ``` -Το βελτιωμένο `Logger`, το οποίο δεν χρησιμοποιεί πλέον τη σταθερά `LOG_DIR`, απαιτεί τον προσδιορισμό της διαδρομής του αρχείου στον κατασκευαστή. Πώς να το λύσετε αυτό; Η κλάση `NewsletterDistributor` δεν ενδιαφέρεται για το πού γράφονται τα μηνύματα- θέλει απλώς να τα γράψει. +Ο βελτιωμένος `Logger`, ο οποίος δεν χρησιμοποιεί πλέον τη σταθερά `LOG_DIR`, απαιτεί τη διαδρομή προς το αρχείο στον κατασκευαστή. Πώς να το λύσουμε αυτό; Η κλάση `NewsletterDistributor` δεν ενδιαφέρεται καθόλου για το πού γράφονται τα μηνύματα, θέλει απλώς να τα γράψει. -Η λύση είναι και πάλι ο [κανόνας #1: Let It Be Passed to You |#rule #1: Let It Be Passed to You]: περάστε όλα τα δεδομένα που χρειάζεται η κλάση. +Η λύση είναι και πάλι ο [##Κανόνας αρ. 1: αφήστε το να σας παραδοθεί]: παραδίδουμε όλα τα δεδομένα που χρειάζεται η κλάση. -Αυτό λοιπόν σημαίνει ότι περνάμε τη διαδρομή προς το αρχείο καταγραφής μέσω του κατασκευαστή, την οποία στη συνέχεια χρησιμοποιούμε κατά τη δημιουργία του αντικειμένου `Logger`; +Άρα αυτό σημαίνει ότι παραδίδουμε τη διαδρομή προς το αρχείο καταγραφής μέσω του κατασκευαστή, την οποία στη συνέχεια χρησιμοποιούμε κατά τη δημιουργία του αντικειμένου `Logger`; ```php class NewsletterDistributor { public function __construct( - private string $file, // ⛔ ΌΧΙ ΜΕ ΑΥΤΌΝ ΤΟΝ ΤΡΌΠΟ! + private string $file, // ⛔ ΟΧΙ ΕΤΣΙ! ) { } @@ -351,7 +351,7 @@ class NewsletterDistributor $logger = new Logger($this->file); ``` -Όχι, όχι έτσι! Το μονοπάτι δεν ανήκει στα δεδομένα που χρειάζεται η κλάση `NewsletterDistributor` - στην πραγματικότητα, το `Logger` το χρειάζεται. Βλέπετε τη διαφορά; Η κλάση `NewsletterDistributor` χρειάζεται τον ίδιο τον καταγραφέα. Έτσι, αυτό είναι που θα περάσουμε: +Όχι έτσι! Η διαδρομή **δεν ανήκει** στα δεδομένα που χρειάζεται η κλάση `NewsletterDistributor`· αυτά τα χρειάζεται ο `Logger`. Αντιλαμβάνεστε τη διαφορά; Η κλάση `NewsletterDistributor` χρειάζεται τον logger ως τέτοιο. Άρα αυτόν θα παραδώσουμε: ```php class NewsletterDistributor @@ -365,35 +365,33 @@ class NewsletterDistributor { try { $this->sendEmails(); - $this->logger->log('Emails have been sent out'); + $this->logger->log('Τα emails στάλθηκαν'); } catch (Exception $e) { - $this->logger->log('An error occurred during the sending'); + $this->logger->log('Παρουσιάστηκε σφάλμα κατά την αποστολή'); throw $e; } } } ``` -Τώρα είναι σαφές από τις υπογραφές της κλάσης `NewsletterDistributor` ότι η καταγραφή αποτελεί επίσης μέρος της λειτουργικότητάς της. Και το έργο της ανταλλαγής του καταγραφέα με έναν άλλο, ίσως για δοκιμές, είναι εντελώς ασήμαντο. -Επιπλέον, αν αλλάξει ο κατασκευαστής της κλάσης `Logger`, αυτό δεν θα επηρεάσει την κλάση μας. +Τώρα είναι σαφές από τις υπογραφές της κλάσης `NewsletterDistributor` ότι η καταγραφή αποτελεί μέρος της λειτουργικότητάς της. Και η εργασία της αντικατάστασης του logger με έναν άλλο, για παράδειγμα για δοκιμές, είναι εντελώς ασήμαντη. Επιπλέον, αν ο κατασκευαστής της κλάσης `Logger` άλλαζε, αυτό δεν θα είχε καμία επίδραση στην κλάση μας. -Κανόνας #2: Πάρτε ό,τι σας ανήκει .[#toc-rule-2-take-what-s-yours] ------------------------------------------------------------------- +Κανόνας αρ. 2: πάρε ό,τι είναι δικό σου +--------------------------------------- -Μην παρασύρεστε και μην αφήνετε τον εαυτό σας να περάσει τις εξαρτήσεις των εξαρτημένων σας. Περάστε μόνο τις δικές σας εξαρτήσεις. +Μην μπερδεύεστε και μην αφήνετε να σας παραδίδουν τις εξαρτήσεις των εξαρτήσεών σας. Αφήστε να σας παραδίδουν μόνο τις δικές σας εξαρτήσεις. -Χάρη σε αυτό, ο κώδικας που χρησιμοποιεί άλλα αντικείμενα θα είναι εντελώς ανεξάρτητος από τις αλλαγές στους κατασκευαστές τους. Το API του θα είναι πιο ειλικρινές. Και πάνω απ' όλα, θα είναι τετριμμένο να αντικαταστήσετε αυτές τις εξαρτήσεις με άλλες. +Χάρη σε αυτό, ο κώδικας που χρησιμοποιεί άλλα αντικείμενα θα είναι εντελώς ανεξάρτητος από τις αλλαγές στους κατασκευαστές τους. Το API του θα είναι πιο αληθινό. Και κυρίως, θα είναι ασήμαντο να αντικαταστήσετε αυτές τις εξαρτήσεις με άλλες. -Νέο μέλος της οικογένειας .[#toc-new-family-member] ---------------------------------------------------- +Νέο μέλος της οικογένειας +------------------------- -Η ομάδα ανάπτυξης αποφάσισε να δημιουργήσει έναν δεύτερο καταγραφέα που γράφει στη βάση δεδομένων. Έτσι δημιουργούμε μια κλάση `DatabaseLogger`. Έτσι έχουμε δύο κλάσεις, `Logger` και `DatabaseLogger`, η μία γράφει σε ένα αρχείο, η άλλη σε μια βάση δεδομένων ... δεν σας φαίνεται περίεργη η ονομασία; -Δεν θα ήταν καλύτερα να μετονομάσετε την `Logger` σε `FileLogger`; Σίγουρα ναι. +Στην ομάδα ανάπτυξης, αποφασίστηκε να δημιουργηθεί ένας δεύτερος logger, ο οποίος γράφει στη βάση δεδομένων. Έτσι, δημιουργούμε την κλάση `DatabaseLogger`. Έχουμε λοιπόν δύο κλάσεις, `Logger` και `DatabaseLogger`, η μία γράφει σε αρχείο, η άλλη στη βάση δεδομένων... δεν σας φαίνεται κάτι περίεργο στην ονομασία; Δεν θα ήταν καλύτερα να μετονομάσουμε τον `Logger` σε `FileLogger`; Σίγουρα ναι. -Αλλά ας το κάνουμε έξυπνα. Δημιουργούμε μια διεπαφή με το αρχικό όνομα: +Αλλά θα το κάνουμε έξυπνα. Κάτω από το αρχικό όνομα, θα δημιουργήσουμε ένα interface: ```php interface Logger @@ -402,7 +400,7 @@ interface Logger } ``` -... το οποίο θα εφαρμόσουν και οι δύο καταγραφείς: +… το οποίο θα υλοποιούν και οι δύο loggers: ```php class FileLogger implements Logger @@ -412,17 +410,17 @@ class DatabaseLogger implements Logger // ... ``` -Και εξαιτίας αυτού, δεν θα χρειαστεί να αλλάξετε τίποτα στον υπόλοιπο κώδικα όπου χρησιμοποιείται ο καταγραφέας. Για παράδειγμα, ο κατασκευαστής της κλάσης `NewsletterDistributor` θα εξακολουθεί να αρκείται στην απαίτηση του `Logger` ως παραμέτρου. Και θα εξαρτάται από εμάς ποια περίπτωση θα περάσουμε. +Και χάρη σε αυτό, δεν θα χρειαστεί να αλλάξουμε τίποτα στον υπόλοιπο κώδικα όπου χρησιμοποιείται ο logger. Για παράδειγμα, ο κατασκευαστής της κλάσης `NewsletterDistributor` θα είναι ακόμα ικανοποιημένος με το ότι απαιτεί `Logger` ως παράμετρο. Και θα εξαρτάται από εμάς ποια παρουσία θα του παραδώσουμε. -**Αυτός είναι ο λόγος για τον οποίο δεν προσθέτουμε ποτέ το επίθημα `Interface` ή το πρόθεμα `I` στα ονόματα των διεπαφών.** Διαφορετικά, δεν θα ήταν δυνατή η ανάπτυξη του κώδικα τόσο όμορφα. +**Γι' αυτό ποτέ δεν δίνουμε στα ονόματα των interfaces την κατάληξη `Interface` ή το πρόθεμα `I`.** Διαφορετικά, δεν θα ήταν δυνατόν να αναπτύξουμε τον κώδικα τόσο όμορφα. -Χιούστον, έχουμε ένα πρόβλημα .[#toc-houston-we-have-a-problem] ---------------------------------------------------------------- +Χιούστον, έχουμε πρόβλημα +------------------------- -Ενώ μπορούμε να τα βγάλουμε πέρα με μια μοναδική περίπτωση του καταγραφέα, είτε βασίζεται σε αρχείο είτε σε βάση δεδομένων, σε ολόκληρη την εφαρμογή και απλά να την περνάμε οπουδήποτε καταγράφεται κάτι, τα πράγματα είναι εντελώς διαφορετικά για την κλάση `Article`. Δημιουργούμε τις περιπτώσεις της όπως χρειάζεται, ακόμη και πολλές φορές. Πώς θα αντιμετωπίσουμε την εξάρτηση από τη βάση δεδομένων στον κατασκευαστή της; +Ενώ σε ολόκληρη την εφαρμογή μπορούμε να αρκεστούμε σε μία μόνο παρουσία του logger, είτε αρχείου είτε βάσης δεδομένων, και απλά να τον παραδίδουμε παντού όπου κάτι καταγράφεται, η κατάσταση είναι εντελώς διαφορετική στην περίπτωση της κλάσης `Article`. Οι παρουσίες της δημιουργούνται ανάλογα με τις ανάγκες, ακόμα και πολλές φορές. Πώς να αντιμετωπίσουμε την εξάρτηση από τη βάση δεδομένων στον κατασκευαστή της; -Ένα παράδειγμα μπορεί να είναι ένας ελεγκτής που θα πρέπει να αποθηκεύσει ένα άρθρο στη βάση δεδομένων μετά την υποβολή μιας φόρμας: +Ως παράδειγμα μπορεί να χρησιμεύσει ένας controller, ο οποίος μετά την υποβολή μιας φόρμας πρέπει να αποθηκεύσει το άρθρο στη βάση δεδομένων: ```php class EditController extends Controller @@ -437,30 +435,30 @@ class EditController extends Controller } ``` -Μια πιθανή λύση είναι προφανής: περάστε το αντικείμενο της βάσης δεδομένων στον κατασκευαστή `EditController` και χρησιμοποιήστε το `$article = new Article($this->db)`. +Μια πιθανή λύση προσφέρεται άμεσα: αφήνουμε το αντικείμενο της βάσης δεδομένων να παραδοθεί μέσω του κατασκευαστή στον `EditController` και χρησιμοποιούμε `$article = new Article($this->db)`. -Όπως και στην προηγούμενη περίπτωση με το `Logger` και τη διαδρομή του αρχείου, αυτή δεν είναι η σωστή προσέγγιση. Η βάση δεδομένων δεν αποτελεί εξάρτηση του `EditController`, αλλά του `Article`. Η μεταβίβαση της βάσης δεδομένων αντιβαίνει στον [κανόνα #2: πάρτε ό,τι σας ανήκει |#rule #2: take what's yours]. Αν ο κατασκευαστής της κλάσης `Article` αλλάξει (προστίθεται μια νέα παράμετρος), θα πρέπει να τροποποιήσετε τον κώδικα όπου δημιουργούνται περιπτώσεις. Ufff. +Όπως και στην προηγούμενη περίπτωση με τον `Logger` και τη διαδρομή προς το αρχείο, αυτή δεν είναι η σωστή προσέγγιση. Η βάση δεδομένων δεν είναι εξάρτηση του `EditController`, αλλά του `Article`. Η παράδοση της βάσης δεδομένων λοιπόν αντιβαίνει στον [Κανόνα αρ. 2: πάρε ό,τι είναι δικό σου |#Κανόνας αρ. 2: πάρε ό τι είναι δικό σου]. Όταν αλλάξει ο κατασκευαστής της κλάσης `Article` (προστεθεί μια νέα παράμετρος), θα είναι απαραίτητο να τροποποιηθεί ο κώδικας σε όλα τα σημεία όπου δημιουργούνται παρουσίες. Ουφ. Χιούστον, τι προτείνεις; -Κανόνας #3: Αφήστε το εργοστάσιο να το χειριστεί .[#toc-rule-3-let-the-factory-handle-it] ------------------------------------------------------------------------------------------ +Κανόνας αρ. 3: άφησέ το στο factory +----------------------------------- -Εξαλείφοντας τις κρυφές εξαρτήσεις και περνώντας όλες τις εξαρτήσεις ως ορίσματα, έχουμε αποκτήσει πιο παραμετροποιήσιμες και ευέλικτες κλάσεις. Και επομένως, χρειαζόμαστε κάτι άλλο για να δημιουργήσουμε και να διαμορφώσουμε αυτές τις πιο ευέλικτες κλάσεις για εμάς. Θα το ονομάσουμε εργοστάσια. +Καταργώντας τις κρυφές εξαρτήσεις και παραδίδοντας όλες τις εξαρτήσεις ως ορίσματα, αποκτήσαμε πιο διαμορφώσιμες και ευέλικτες κλάσεις. Και επομένως χρειαζόμαστε κάτι ακόμα, το οποίο θα δημιουργήσει και θα διαμορφώσει αυτές τις πιο ευέλικτες κλάσεις για εμάς. Θα το ονομάσουμε factories. -Ο γενικός κανόνας είναι: αν μια κλάση έχει εξαρτήσεις, αφήστε τη δημιουργία των περιπτώσεων τους στο εργοστάσιο. +Ο κανόνας λέει: αν μια κλάση έχει εξαρτήσεις, άφησε τη δημιουργία των παρουσιών της σε ένα factory. -Τα εργοστάσια είναι μια πιο έξυπνη αντικατάσταση του τελεστή `new` στον κόσμο της έγχυσης εξαρτήσεων. +Τα factories είναι μια πιο έξυπνη αντικατάσταση του τελεστή `new` στον κόσμο του dependency injection. .[note] -Μην συγχέετε με το πρότυπο σχεδίασης *factory method*, το οποίο περιγράφει έναν συγκεκριμένο τρόπο χρήσης των εργοστασίων και δεν σχετίζεται με αυτό το θέμα. +Παρακαλώ μην συγχέετε με το πρότυπο σχεδίασης *factory method*, το οποίο περιγράφει έναν συγκεκριμένο τρόπο χρήσης των factories και δεν σχετίζεται με αυτό το θέμα. -Εργοστάσιο .[#toc-factory] --------------------------- +Factory +------- -Ένα εργοστάσιο είναι μια μέθοδος ή μια κλάση που δημιουργεί και ρυθμίζει αντικείμενα. Θα ονομάσουμε την κλάση που παράγει το `Article` ως `ArticleFactory`, και θα μπορούσε να μοιάζει ως εξής: +Ένα factory είναι μια μέθοδος ή μια κλάση που παράγει και διαμορφώνει αντικείμενα. Την κλάση που παράγει `Article` θα την ονομάσουμε `ArticleFactory` και θα μπορούσε να μοιάζει κάπως έτσι: ```php class ArticleFactory @@ -477,7 +475,7 @@ class ArticleFactory } ``` -Η χρήση του στον ελεγκτή θα έχει ως εξής: +Η χρήση της στον controller θα είναι η εξής: ```php class EditController extends Controller @@ -489,7 +487,7 @@ class EditController extends Controller public function formSubmitted($data) { - // αφήστε το εργοστάσιο να δημιουργήσει ένα αντικείμενο + // αφήνουμε το factory να δημιουργήσει το αντικείμενο $article = $this->articleFactory->create(); $article->title = $data->title; $article->content = $data->content; @@ -498,11 +496,11 @@ class EditController extends Controller } ``` -Σε αυτό το σημείο, αν αλλάξει η υπογραφή του κατασκευαστή της κλάσης `Article`, το μόνο μέρος του κώδικα που χρειάζεται να αντιδράσει είναι το ίδιο το `ArticleFactory`. Όλος ο υπόλοιπος κώδικας που εργάζεται με αντικείμενα `Article`, όπως το `EditController`, δεν θα επηρεαστεί. +Αν αυτή τη στιγμή αλλάξει η υπογραφή του κατασκευαστή της κλάσης `Article`, το μόνο μέρος του κώδικα που πρέπει να αντιδράσει σε αυτό είναι το ίδιο το factory `ArticleFactory`. Όλος ο υπόλοιπος κώδικας που λειτουργεί με αντικείμενα `Article`, όπως για παράδειγμα ο `EditController`, δεν θα επηρεαστεί καθόλου. -Μπορεί να αναρωτιέστε αν έχουμε κάνει τα πράγματα πραγματικά καλύτερα. Η ποσότητα του κώδικα έχει αυξηθεί και όλα αρχίζουν να φαίνονται ύποπτα περίπλοκα. +Ίσως τώρα χτυπάτε το κεφάλι σας, αν βοηθήσαμε καθόλου. Η ποσότητα του κώδικα αυξήθηκε και όλο αυτό αρχίζει να φαίνεται ύποπτα περίπλοκο. -Μην ανησυχείτε, σύντομα θα φτάσουμε στο δοχείο Nette DI. Και έχει αρκετά κόλπα στο μανίκι του, τα οποία θα απλοποιήσουν σημαντικά τη δημιουργία εφαρμογών που χρησιμοποιούν dependency injection. Για παράδειγμα, αντί για την κλάση `ArticleFactory`, θα χρειαστεί να [γράψετε |factory] μόνο [μια απλή διεπαφή |factory]: +Μην ανησυχείτε, σε λίγο θα φτάσουμε στο Nette DI container. Και αυτός έχει πολλούς άσους στο μανίκι του, οι οποίοι θα απλοποιήσουν εξαιρετικά την κατασκευή εφαρμογών που χρησιμοποιούν dependency injection. Έτσι, για παράδειγμα, αντί για την κλάση `ArticleFactory`, θα αρκεί να [γράψουμε απλώς ένα interface |factory]: ```php interface ArticleFactory @@ -511,18 +509,18 @@ interface ArticleFactory } ``` -Αλλά βρισκόμαστε μπροστά από τους εαυτούς μας- σας παρακαλούμε να είστε υπομονετικοί :-) +Αλλά προτρέχουμε, περιμένετε λίγο ακόμα :-) -Περίληψη .[#toc-summary] ------------------------- +Σύνοψη +------ -Στην αρχή αυτού του κεφαλαίου, υποσχεθήκαμε να σας δείξουμε μια διαδικασία για τη σχεδίαση καθαρού κώδικα. Το μόνο που χρειάζεται είναι οι κλάσεις να: +Στην αρχή αυτού του κεφαλαίου, υποσχεθήκαμε ότι θα δείξουμε μια διαδικασία για τον σχεδιασμό καθαρού κώδικα. Αρκεί στις κλάσεις -- [να περνούν τις εξαρτήσεις που χρειάζονται |#Rule #1: Let It Be Passed to You] -- [αντίστροφα, να μην περνούν ό,τι δεν χρειάζονται άμεσα |#Rule #2: Take What's Yours] -- [και ότι τα αντικείμενα με εξαρτήσεις είναι καλύτερο να δημιουργούνται σε εργοστάσια |#Rule #3: Let the Factory Handle it] +1) [να παραδίδονται οι εξαρτήσεις που χρειάζονται |#Κανόνας αρ. 1: αφήστε το να σας παραδοθεί] +2) [και αντίστροφα, να μην παραδίδονται ό,τι δεν χρειάζονται άμεσα |#Κανόνας αρ. 2: πάρε ό τι είναι δικό σου] +3) [και ότι τα αντικείμενα με εξαρτήσεις κατασκευάζονται καλύτερα σε factories |#Κανόνας αρ. 3: άφησέ το στο factory] -Με μια πρώτη ματιά, αυτοί οι τρεις κανόνες μπορεί να μην φαίνεται να έχουν εκτεταμένες συνέπειες, αλλά οδηγούν σε μια ριζικά διαφορετική οπτική για τη σχεδίαση κώδικα. Αξίζει τον κόπο; Οι προγραμματιστές που εγκατέλειψαν τις παλιές συνήθειες και άρχισαν να χρησιμοποιούν με συνέπεια την έγχυση εξαρτήσεων θεωρούν αυτό το βήμα μια κρίσιμη στιγμή στην επαγγελματική τους ζωή. Τους άνοιξε τον κόσμο των σαφών και συντηρήσιμων εφαρμογών. +Μπορεί να μην φαίνεται έτσι με την πρώτη ματιά, αλλά αυτοί οι τρεις κανόνες έχουν εκτεταμένες συνέπειες. Οδηγούν σε μια ριζικά διαφορετική άποψη για τον σχεδιασμό του κώδικα. Αξίζει τον κόπο; Οι προγραμματιστές που εγκατέλειψαν τις παλιές συνήθειες και άρχισαν να χρησιμοποιούν με συνέπεια το dependency injection θεωρούν αυτό το βήμα ως μια κρίσιμη στιγμή στην επαγγελματική τους ζωή. Τους άνοιξε τον κόσμο των σαφών και συντηρήσιμων εφαρμογών. -Τι γίνεται όμως αν ο κώδικας δεν χρησιμοποιεί με συνέπεια την έγχυση εξαρτήσεων; Τι γίνεται αν βασίζεται σε στατικές μεθόδους ή singletons; Προκαλεί αυτό προβλήματα; [Ναι, δημιουργεί, και μάλιστα πολύ θεμελιώδη |global-state]. +Τι γίνεται όμως αν ο κώδικας δεν χρησιμοποιεί με συνέπεια το dependency injection; Τι γίνεται αν βασίζεται σε στατικές μεθόδους ή singletons; Προκαλεί αυτό προβλήματα; [Προκαλεί, και μάλιστα πολύ σοβαρά |global-state]. diff --git a/dependency-injection/el/nette-container.texy b/dependency-injection/el/nette-container.texy index 93e6fdff8f..7489be893e 100644 --- a/dependency-injection/el/nette-container.texy +++ b/dependency-injection/el/nette-container.texy @@ -1,10 +1,10 @@ -Δοχείο Nette DI -*************** +Nette DI Container +****************** .[perex] -Η Nette DI είναι μία από τις πιο ενδιαφέρουσες βιβλιοθήκες της Nette. Μπορεί να παράγει και να ενημερώνει αυτόματα μεταγλωττισμένα δοχεία DI που είναι εξαιρετικά γρήγορα και εκπληκτικά εύκολα στη διαμόρφωση. +Το Nette DI είναι μία από τις πιο ενδιαφέρουσες βιβλιοθήκες του Nette. Μπορεί να δημιουργεί και να ενημερώνει αυτόματα μεταγλωττισμένα DI containers, τα οποία είναι εξαιρετικά γρήγορα και εκπληκτικά εύκολα στη διαμόρφωση. -Οι υπηρεσίες που θα δημιουργηθούν από ένα DI container ορίζονται συνήθως με τη χρήση αρχείων διαμόρφωσης σε [μορφή NEON |neon:format]. Ο περιέκτης που δημιουργήσαμε χειροκίνητα στην [προηγούμενη |container] ενότητα θα γραφόταν ως εξής: +Τη μορφή των υπηρεσιών που πρόκειται να δημιουργήσει το DI container την ορίζουμε συνήθως χρησιμοποιώντας αρχεία διαμόρφωσης σε [μορφή NEON|neon:format]. Το container που δημιουργήσαμε χειροκίνητα στο [προηγούμενο κεφάλαιο|container], θα γραφόταν ως εξής: ```neon parameters: @@ -19,16 +19,15 @@ services: - UserController ``` -Ο συμβολισμός είναι πραγματικά σύντομος. +Η σύνταξη είναι πραγματικά συνοπτική. -Όλες οι εξαρτήσεις που δηλώνονται στους κατασκευαστές των κλάσεων `ArticleFactory` και `UserController` βρίσκονται και μεταβιβάζονται από το ίδιο το Nette DI χάρη στη λεγόμενη [αυτόματη σύνδεση |autowiring], οπότε δεν χρειάζεται να καθορίσετε τίποτα στο αρχείο ρυθμίσεων. -Έτσι, ακόμη και αν αλλάξουν οι παράμετροι, δεν χρειάζεται να αλλάξετε τίποτα στη διαμόρφωση. Η Nette θα αναδημιουργήσει αυτόματα το δοχείο. Εκεί μπορείτε να επικεντρωθείτε καθαρά στην ανάπτυξη εφαρμογών. +Όλες οι εξαρτήσεις που δηλώνονται στους κατασκευαστές των κλάσεων `ArticleFactory` και `UserController`, το Nette DI τις βρίσκει και τις παραδίδει αυτόματα χάρη στο λεγόμενο [autowiring|autowiring], επομένως δεν χρειάζεται να δηλωθεί τίποτα στο αρχείο διαμόρφωσης. Έτσι, ακόμα κι αν αλλάξουν οι παράμετροι, δεν χρειάζεται να αλλάξετε τίποτα στη διαμόρφωση. Το Nette container θα αναδημιουργηθεί αυτόματα. Μπορείτε να επικεντρωθείτε αποκλειστικά στην ανάπτυξη της εφαρμογής. -Αν θέλετε να περάσετε εξαρτήσεις χρησιμοποιώντας setters, χρησιμοποιήστε την ενότητα [setup |services#setup] για να το κάνετε. +Αν θέλουμε να παραδώσουμε εξαρτήσεις χρησιμοποιώντας setters, χρησιμοποιούμε την ενότητα [setup |services#Setup] για αυτό. -Το Nette DI θα δημιουργήσει απευθείας τον κώδικα PHP για το δοχείο. Το αποτέλεσμα είναι έτσι ένα αρχείο `.php` το οποίο μπορείτε να ανοίξετε και να μελετήσετε. Αυτό σας επιτρέπει να δείτε ακριβώς πώς λειτουργεί το εμπορευματοκιβώτιο. Μπορείτε επίσης να κάνετε αποσφαλμάτωση στο IDE και να το εξετάσετε βήμα προς βήμα. Και το πιο σημαντικό: η παραγόμενη PHP είναι εξαιρετικά γρήγορη. +Το Nette DI παράγει απευθείας τον κώδικα PHP του container. Το αποτέλεσμα είναι λοιπόν ένα αρχείο `.php`, το οποίο μπορείτε να ανοίξετε και να μελετήσετε. Χάρη σε αυτό, βλέπετε ακριβώς πώς λειτουργεί το container. Μπορείτε επίσης να το κάνετε debug στο IDE και να το εκτελέσετε βήμα-βήμα. Και κυρίως: ο παραγόμενος κώδικας PHP είναι εξαιρετικά γρήγορος. -Το Nette DI μπορεί επίσης να παράγει [εργοστασιακό |factory] κώδικα με βάση την παρεχόμενη διεπαφή. Επομένως, αντί για την κλάση `ArticleFactory`, χρειάζεται μόνο να δημιουργήσουμε μια διεπαφή στην εφαρμογή: +Το Nette DI μπορεί επίσης να παράγει κώδικα για [factories|factory] βάσει ενός παρεχόμενου interface. Επομένως, αντί για την κλάση `ArticleFactory`, θα αρκεί να δημιουργήσουμε μόνο ένα interface στην εφαρμογή: ```php interface ArticleFactory @@ -37,19 +36,19 @@ interface ArticleFactory } ``` -Μπορείτε να βρείτε το πλήρες παράδειγμα [στο GitHub |https://github.com/nette-examples/di-example-doc]. +Ολόκληρο το παράδειγμα μπορείτε να το βρείτε [στο GitHub|https://github.com/nette-examples/di-example-doc]. -Αυτόνομη χρήση .[#toc-standalone-use] -------------------------------------- +Αυτόνομη χρήση +-------------- -Η χρήση της βιβλιοθήκης Nette DI σε μια εφαρμογή είναι πολύ εύκολη. Πρώτα την εγκαθιστούμε με το Composer (επειδή το κατέβασμα αρχείων zip είναι τόσο ξεπερασμένο): +Η ενσωμάτωση της βιβλιοθήκης Nette DI σε μια εφαρμογή είναι πολύ εύκολη. Πρώτα την εγκαθιστούμε με το Composer (επειδή η λήψη zip είναι τόοοσο παλιομοδίτικη): ```shell composer require nette/di ``` -Ο παρακάτω κώδικας δημιουργεί μια περίπτωση του δοχείου DI σύμφωνα με τις ρυθμίσεις που είναι αποθηκευμένες στο αρχείο `config.neon`: +Ο παρακάτω κώδικας δημιουργεί μια παρουσία του DI container σύμφωνα με τη διαμόρφωση που είναι αποθηκευμένη στο αρχείο `config.neon`: ```php $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp'); @@ -59,24 +58,23 @@ $class = $loader->load(function ($compiler) { $container = new $class; ``` -Ο περιέκτης δημιουργείται μόνο μία φορά, ο κώδικάς του εγγράφεται στην κρυφή μνήμη (στον κατάλογο `__DIR__ . '/temp'` ) και σε επόμενες αιτήσεις διαβάζεται μόνο από εκεί. +Το container δημιουργείται μόνο μία φορά, ο κώδικας του γράφεται στην cache (κατάλογος `__DIR__ . '/temp'`) και στα επόμενα αιτήματα απλώς φορτώνεται από εκεί. -Οι μέθοδοι `getService()` ή `getByType()` χρησιμοποιούνται για τη δημιουργία και την ανάκτηση υπηρεσιών. Με αυτόν τον τρόπο δημιουργούμε το αντικείμενο `UserController`: +Για τη δημιουργία και λήψη υπηρεσιών χρησιμοποιούνται οι μέθοδοι `getService()` ή `getByType()`. Έτσι δημιουργούμε το αντικείμενο `UserController`: ```php -$database = $container->getByType(UserController::class); -$database->query('...'); +$controller = $container->getByType(UserController::class); +$controller->someMethod(); ``` -Κατά τη διάρκεια της ανάπτυξης, είναι χρήσιμο να ενεργοποιήσετε τη λειτουργία αυτόματης ανανέωσης, όπου ο περιέκτης αναγεννιέται αυτόματα αν αλλάξει κάποια κλάση ή αρχείο ρυθμίσεων. Απλά δώστε το `true` ως δεύτερο όρισμα στον κατασκευαστή `ContainerLoader`. +Κατά την ανάπτυξη, είναι χρήσιμο να ενεργοποιήσετε τη λειτουργία auto-refresh, όπου το container αναδημιουργείται αυτόματα εάν αλλάξει οποιαδήποτε κλάση ή αρχείο διαμόρφωσης. Αρκεί να δώσετε `true` ως δεύτερο όρισμα στον κατασκευαστή `ContainerLoader`. ```php $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp', true); ``` -Χρήση με το Nette Framework .[#toc-using-it-with-the-nette-framework] ---------------------------------------------------------------------- +Χρήση με το Nette Framework +--------------------------- -Όπως δείξαμε, η χρήση του Nette DI δεν περιορίζεται σε εφαρμογές που έχουν γραφτεί στο Nette Framework, μπορείτε να το αναπτύξετε οπουδήποτε με μόλις 3 γραμμές κώδικα. -Ωστόσο, αν αναπτύσσετε εφαρμογές στο Nette Framework, η διαμόρφωση και η δημιουργία του δοχείου αναλαμβάνεται από το [Bootstrap |application:bootstrap#toc-di-container-configuration]. +Όπως δείξαμε, η χρήση του Nette DI δεν περιορίζεται σε εφαρμογές γραμμένες στο Nette Framework, μπορείτε να το ενσωματώσετε οπουδήποτε με μόλις 3 γραμμές κώδικα. Ωστόσο, εάν αναπτύσσετε εφαρμογές στο Nette Framework, τη διαμόρφωση και τη δημιουργία του container την αναλαμβάνει το [Bootstrap |application:bootstrapping#Διαμόρφωση του DI Container]. diff --git a/dependency-injection/el/passing-dependencies.texy b/dependency-injection/el/passing-dependencies.texy index 01698c8e6a..9d5c8b4701 100644 --- a/dependency-injection/el/passing-dependencies.texy +++ b/dependency-injection/el/passing-dependencies.texy @@ -1,24 +1,24 @@ -Πέρασμα εξαρτήσεων -****************** +Παράδοση εξαρτήσεων +******************* <div class=perex> -Τα επιχειρήματα, ή "εξαρτήσεις" στην ορολογία DI, μπορούν να μεταβιβαστούν σε κλάσεις με τους ακόλουθους κύριους τρόπους: +Τα ορίσματα, ή στην ορολογία του DI "εξαρτήσεις", μπορούν να παραδοθούν στις κλάσεις με τους ακόλουθους κύριους τρόπους: -* περνώντας από τον κατασκευαστή -* πέρασμα μέσω μεθόδου (που ονομάζεται setter) -* με τον καθορισμό μιας ιδιότητας -* μέσω μεθόδου, σημείωσης ή χαρακτηριστικού *inject* +* παράδοση μέσω κατασκευαστή +* παράδοση μέσω μεθόδου (του λεγόμενου setter) +* ρύθμιση μεταβλητής +* με μέθοδο, annotation ή attribute *inject* </div> -Θα παρουσιάσουμε τώρα τις διάφορες παραλλαγές με συγκεκριμένα παραδείγματα. +Τώρα θα δείξουμε τις διάφορες παραλλαγές με συγκεκριμένα παραδείγματα. -Έγχυση κατασκευαστή .[#toc-constructor-injection] -================================================= +Παράδοση μέσω κατασκευαστή +========================== -Οι εξαρτήσεις περνούν ως ορίσματα στον κατασκευαστή κατά τη δημιουργία του αντικειμένου: +Οι εξαρτήσεις παραδίδονται τη στιγμή της δημιουργίας του αντικειμένου ως ορίσματα του κατασκευαστή: ```php class MyClass @@ -34,9 +34,9 @@ class MyClass $obj = new MyClass($cache); ``` -Αυτή η μορφή είναι χρήσιμη για τις υποχρεωτικές εξαρτήσεις που η κλάση χρειάζεται οπωσδήποτε για να λειτουργήσει, καθώς χωρίς αυτές δεν μπορεί να δημιουργηθεί η περίπτωση. +Αυτή η μορφή είναι κατάλληλη για υποχρεωτικές εξαρτήσεις που η κλάση χρειάζεται απαραίτητα για τη λειτουργία της, καθώς χωρίς αυτές δεν θα είναι δυνατή η δημιουργία της παρουσίας. -Από την PHP 8.0, μπορούμε να χρησιμοποιήσουμε μια συντομότερη μορφή συμβολισμού που είναι λειτουργικά ισοδύναμη ([constructor property promotion |https://blog.nette.org/el/php-8-0-pleres-episkopese-ton-neon#toc-constructor-property-promotion]): +Από την PHP 8.0, μπορούμε να χρησιμοποιήσουμε μια συντομότερη μορφή σύνταξης ([constructor property promotion |https://blog.nette.org/el/php-8-0-complete-overview-of-news#toc-constructor-property-promotion]), η οποία είναι λειτουργικά ισοδύναμη: ```php // PHP 8.0 @@ -49,7 +49,7 @@ class MyClass } ``` -Από την PHP 8.1, μια ιδιότητα μπορεί να επισημανθεί με μια σημαία `readonly` που δηλώνει ότι τα περιεχόμενα της ιδιότητας δεν θα αλλάξουν: +Από την PHP 8.1, η μεταβλητή μπορεί να επισημανθεί με τη σημαία `readonly`, η οποία δηλώνει ότι το περιεχόμενο της μεταβλητής δεν θα αλλάξει πλέον: ```php // PHP 8.1 @@ -62,13 +62,13 @@ class MyClass } ``` -Το DI container περνάει τις εξαρτήσεις στον κατασκευαστή αυτόματα χρησιμοποιώντας [την αυτόματη σύνδεση (autowiring |autowiring]). Τα επιχειρήματα που δεν μπορούν να περάσουν με αυτόν τον τρόπο (π.χ. συμβολοσειρές, αριθμοί, booleans) [γράφονται στη διαμόρφωση |services#Arguments]. +Το DI container παραδίδει αυτόματα τις εξαρτήσεις στον κατασκευαστή χρησιμοποιώντας [autowiring |autowiring]. Τα ορίσματα που δεν μπορούν να παραδοθούν με αυτόν τον τρόπο (π.χ. strings, αριθμοί, booleans) τα [γράφουμε στη διαμόρφωση |services#Ορίσματα]. -Κόλαση του κατασκευαστή .[#toc-constructor-hell] ------------------------------------------------- +Constructor hell +---------------- -Ο όρος *κόλαση κατασκευαστών* αναφέρεται σε μια κατάσταση όπου ένα παιδί κληρονομεί από μια γονική κλάση της οποίας ο κατασκευαστής απαιτεί εξαρτήσεις και το παιδί απαιτεί επίσης εξαρτήσεις. Πρέπει επίσης να αναλάβει και να μεταβιβάσει τις εξαρτήσεις του γονέα: +Ο όρος *constructor hell* περιγράφει την κατάσταση όπου ένας απόγονος κληρονομεί από μια γονική κλάση, της οποίας ο κατασκευαστής απαιτεί εξαρτήσεις, και ταυτόχρονα ο απόγονος απαιτεί εξαρτήσεις. Ταυτόχρονα, πρέπει να αναλάβει και να παραδώσει και τις γονικές: ```php abstract class BaseClass @@ -94,11 +94,11 @@ final class MyClass extends BaseClass } ``` -Το πρόβλημα εμφανίζεται όταν θέλουμε να αλλάξουμε τον κατασκευαστή της κλάσης `BaseClass`, για παράδειγμα όταν προστίθεται μια νέα εξάρτηση. Τότε πρέπει να τροποποιήσουμε και όλους τους κατασκευαστές των παιδιών. Το οποίο κάνει μια τέτοια τροποποίηση κόλαση. +Το πρόβλημα προκύπτει τη στιγμή που θα θέλαμε να αλλάξουμε τον κατασκευαστή της κλάσης `BaseClass`, για παράδειγμα, όταν προστεθεί μια νέα εξάρτηση. Τότε είναι απαραίτητο να τροποποιηθούν και όλοι οι κατασκευαστές των απογόνων. Κάτι που καθιστά μια τέτοια τροποποίηση κόλαση. -Πώς μπορεί να αποφευχθεί αυτό; Η λύση είναι η **προτεραιότητα της σύνθεσης έναντι της κληρονομικότητας**. +Πώς να το αποτρέψουμε αυτό; Η λύση είναι **να προτιμάμε τη [σύνθεση έναντι κληρονομικότητας |faq#Γιατί προτιμάται η σύνθεση composition έναντι της κληρονομικότητας]**. -Ας σχεδιάσουμε λοιπόν τον κώδικα με διαφορετικό τρόπο. Θα αποφύγουμε τις αφηρημένες κλάσεις `Base*`. Αντί το `MyClass` να παίρνει κάποια λειτουργικότητα κληρονομώντας από το `BaseClass`, θα έχει αυτή τη λειτουργικότητα περασμένη ως εξάρτηση: +Δηλαδή, θα σχεδιάσουμε τον κώδικα διαφορετικά. Θα αποφεύγουμε τις [αφηρημένες |nette:introduction-to-object-oriented-programming#Αφηρημένες κλάσεις] `Base*` κλάσεις. Αντί η `MyClass` να αποκτά μια συγκεκριμένη λειτουργικότητα κληρονομώντας από την `BaseClass`, θα της παραδοθεί αυτή η λειτουργικότητα ως εξάρτηση: ```php final class SomeFunctionality @@ -125,10 +125,10 @@ final class MyClass ``` -Έγχυση ρυθμιστή .[#toc-setter-injection] -======================================== +Παράδοση μέσω setter +==================== -Οι εξαρτήσεις μεταφέρονται με την κλήση μιας μεθόδου που τις αποθηκεύει σε μια ιδιωτική ιδιότητα. Η συνήθης σύμβαση ονοματοδοσίας για αυτές τις μεθόδους είναι της μορφής `set*()`, γι' αυτό και ονομάζονται setters, αλλά φυσικά μπορούν να ονομαστούν και αλλιώς. +Οι εξαρτήσεις παραδίδονται καλώντας μια μέθοδο, η οποία τις αποθηκεύει σε μια ιδιωτική μεταβλητή. Η συνήθης σύμβαση ονομασίας αυτών των μεθόδων είναι η μορφή `set*()`, γι' αυτό ονομάζονται setters, αλλά μπορούν φυσικά να ονομάζονται και οτιδήποτε άλλο. ```php class MyClass @@ -145,9 +145,9 @@ $obj = new MyClass; $obj->setCache($cache); ``` -Η μέθοδος αυτή είναι χρήσιμη για προαιρετικές εξαρτήσεις που δεν είναι απαραίτητες για τη λειτουργία της κλάσης, καθώς δεν είναι εγγυημένο ότι το αντικείμενο θα τις λάβει πραγματικά (δηλαδή ότι ο χρήστης θα καλέσει τη μέθοδο). +Αυτός ο τρόπος είναι κατάλληλος για προαιρετικές εξαρτήσεις που δεν είναι απαραίτητες για τη λειτουργία της κλάσης, καθώς δεν εγγυάται ότι το αντικείμενο θα λάβει πραγματικά την εξάρτηση (δηλαδή ότι ο χρήστης θα καλέσει τη μέθοδο). -Ταυτόχρονα, η μέθοδος αυτή επιτρέπει την επανειλημμένη κλήση του setter για την αλλαγή της εξάρτησης. Αν αυτό δεν είναι επιθυμητό, προσθέστε έναν έλεγχο στη μέθοδο ή, από την PHP 8.1, σημειώστε την ιδιότητα `$cache` με τη σημαία `readonly`. +Ταυτόχρονα, αυτός ο τρόπος επιτρέπει την επανειλημμένη κλήση του setter και την αλλαγή της εξάρτησης. Εάν αυτό δεν είναι επιθυμητό, προσθέτουμε έναν έλεγχο στη μέθοδο, ή από την PHP 8.1 επισημαίνουμε την property `$cache` με τη σημαία `readonly`. ```php class MyClass @@ -156,7 +156,7 @@ class MyClass public function setCache(Cache $cache): void { - if ($this->cache) { + if (isset($this->cache)) { throw new RuntimeException('The dependency has already been set'); } $this->cache = $cache; @@ -164,21 +164,20 @@ class MyClass } ``` -Η κλήση του setter ορίζεται στη διαμόρφωση του δοχείου DI στην [ενότητα setup |services#Setup]. Επίσης, εδώ χρησιμοποιείται η αυτόματη μεταβίβαση εξαρτήσεων από την αυτόματη σύνδεση: +Η κλήση του setter ορίζεται στη διαμόρφωση του DI container στο [κλειδί setup |services#Setup]. Και εδώ χρησιμοποιείται η αυτόματη παράδοση εξαρτήσεων μέσω autowiring: ```neon services: - - - create: MyClass + - create: MyClass setup: - setCache ``` -Property Injection .[#toc-property-injection] -============================================= +Ρύθμιση μεταβλητής +================== -Οι εξαρτήσεις περνούν απευθείας στην ιδιότητα: +Οι εξαρτήσεις παραδίδονται γράφοντας απευθείας στη μεταβλητή μέλους: ```php class MyClass @@ -190,28 +189,27 @@ $obj = new MyClass; $obj->cache = $cache; ``` - `public`Ως εκ τούτου, δεν έχουμε κανέναν έλεγχο για το αν η μεταβιβαζόμενη εξάρτηση θα είναι πράγματι του καθορισμένου τύπου (αυτό ίσχυε πριν από την PHP 7.4) και χάνουμε τη δυνατότητα να αντιδράσουμε στη νέα ανατεθείσα εξάρτηση με το δικό μας κώδικα, για παράδειγμα για να αποτρέψουμε μεταγενέστερες αλλαγές. Ταυτόχρονα, η ιδιότητα γίνεται μέρος της δημόσιας διεπαφής της κλάσης, κάτι που μπορεί να μην είναι επιθυμητό. +Αυτός ο τρόπος θεωρείται ακατάλληλος, επειδή η μεταβλητή μέλους πρέπει να δηλωθεί ως `public`. Και επομένως δεν έχουμε έλεγχο ότι η παραδοθείσα εξάρτηση θα είναι πράγματι του συγκεκριμένου τύπου (ίσχυε πριν την PHP 7.4) και χάνουμε τη δυνατότητα να αντιδράσουμε στη νέα εκχωρημένη εξάρτηση με δικό μας κώδικα, για παράδειγμα, να αποτρέψουμε την επακόλουθη αλλαγή. Ταυτόχρονα, η μεταβλητή γίνεται μέρος του δημόσιου interface της κλάσης, κάτι που μπορεί να μην είναι επιθυμητό. -Η ρύθμιση της μεταβλητής ορίζεται στη διαμόρφωση του δοχείου DI στην [ενότητα setup |services#Setup]: +Η ρύθμιση της μεταβλητής ορίζεται στη διαμόρφωση του DI container στην [ενότητα setup |services#Setup]: ```neon services: - - - create: MyClass + - create: MyClass setup: - $cache = @\Cache ``` -Ένεση .[#toc-inject] -==================== +Inject +====== -Ενώ οι τρεις προηγούμενες μέθοδοι ισχύουν γενικά σε όλες τις αντικειμενοστραφείς γλώσσες, η έγχυση μέσω μεθόδου, σχολίου ή χαρακτηριστικού *inject* είναι ειδική για τους παρουσιαστές Nette. Συζητούνται σε [ξεχωριστό |best-practices:inject-method-attribute] κεφάλαιο. +Ενώ οι τρεις προηγούμενοι τρόποι ισχύουν γενικά σε όλες τις αντικειμενοστραφείς γλώσσες, η έγχυση με μέθοδο, annotation ή attribute *inject* είναι ειδική αποκλειστικά για τους presenters στο Nette. Αυτά συζητούνται σε [ξεχωριστό κεφάλαιο |best-practices:inject-method-attribute]. -Ποιον τρόπο να επιλέξω; .[#toc-which-way-to-choose] -=================================================== +Ποιον τρόπο να επιλέξω; +======================= -- ο κατασκευαστής είναι κατάλληλος για τις υποχρεωτικές εξαρτήσεις που χρειάζεται η κλάση για να λειτουργήσει -- ο setter, από την άλλη πλευρά, είναι κατάλληλος για προαιρετικές εξαρτήσεις ή εξαρτήσεις που μπορούν να αλλάξουν -- οι δημόσιες μεταβλητές δεν συνιστώνται +- ο κατασκευαστής είναι κατάλληλος για υποχρεωτικές εξαρτήσεις που η κλάση χρειάζεται απαραίτητα για τη λειτουργία της +- ο setter είναι αντίθετα κατάλληλος για προαιρετικές εξαρτήσεις, ή εξαρτήσεις που μπορεί να χρειαστεί να αλλάξουν περαιτέρω +- οι δημόσιες μεταβλητές δεν είναι κατάλληλες diff --git a/dependency-injection/el/services.texy b/dependency-injection/el/services.texy index f264fd21f6..c611508ddd 100644 --- a/dependency-injection/el/services.texy +++ b/dependency-injection/el/services.texy @@ -1,33 +1,33 @@ -Ορισμοί υπηρεσιών +Ορισμός υπηρεσιών ***************** .[perex] -Η διαμόρφωση είναι το σημείο όπου τοποθετούμε τους ορισμούς των προσαρμοσμένων υπηρεσιών. Αυτό γίνεται στην ενότητα `services`. +Η διαμόρφωση είναι το μέρος όπου διδάσκουμε στο DI container πώς να συναρμολογεί τις επιμέρους υπηρεσίες και πώς να τις συνδέει με άλλες εξαρτήσεις. Το Nette παρέχει έναν πολύ σαφή και κομψό τρόπο για να το πετύχουμε αυτό. -Για παράδειγμα, έτσι δημιουργούμε μια υπηρεσία με το όνομα `database`, η οποία θα είναι μια περίπτωση της κλάσης `PDO`: +Η ενότητα `services` στο αρχείο διαμόρφωσης μορφής NEON είναι το μέρος όπου ορίζουμε τις δικές μας υπηρεσίες και τις διαμορφώσεις τους. Ας δούμε ένα απλό παράδειγμα ορισμού μιας υπηρεσίας με όνομα `database`, η οποία αντιπροσωπεύει μια παρουσία της κλάσης `PDO`: ```neon services: database: PDO('sqlite::memory:') ``` -Η ονοματοδοσία των υπηρεσιών χρησιμοποιείται για να μας επιτρέπει να τις [αναφέρουμε |#Referencing Services]. Εάν μια υπηρεσία δεν αναφέρεται, δεν υπάρχει λόγος να την ονομάσουμε. Έτσι, αντί για όνομα χρησιμοποιούμε απλώς ένα σημείο αναφοράς: +Η παραπάνω διαμόρφωση θα οδηγήσει στην ακόλουθη μέθοδο factory στο [DI container|container]: -```neon -services: - - PDO('sqlite::memory:') # ανώνυμη υπηρεσία +```php +public function createServiceDatabase(): PDO +{ + return new PDO('sqlite::memory:'); +} ``` -Μια καταχώρηση μιας γραμμής μπορεί να σπάσει σε πολλές γραμμές για να επιτραπεί η προσθήκη πρόσθετων κλειδιών, όπως το [setup |#setup]. Το ψευδώνυμο για το κλειδί `create:` είναι `factory:`. +Τα ονόματα των υπηρεσιών μας επιτρέπουν να αναφερόμαστε σε αυτές σε άλλα μέρη του αρχείου διαμόρφωσης, με τη μορφή `@ονομαΥπηρεσιας`. Εάν δεν χρειάζεται να ονομάσουμε την υπηρεσία, μπορούμε απλά να χρησιμοποιήσουμε μόνο μια παύλα: ```neon services: - database: - create: PDO('sqlite::memory:') - setup: ... + - PDO('sqlite::memory:') ``` -Στη συνέχεια, ανακτούμε την υπηρεσία από το δοχείο DI χρησιμοποιώντας τη μέθοδο `getService()` με βάση το όνομα ή, ακόμα καλύτερα, τη μέθοδο `getByType()` με βάση τον τύπο: +Για να λάβουμε μια υπηρεσία από το DI container, μπορούμε να χρησιμοποιήσουμε τη μέθοδο `getService()` με το όνομα της υπηρεσίας ως παράμετρο, ή τη μέθοδο `getByType()` με τον τύπο της υπηρεσίας: ```php $database = $container->getService('database'); @@ -35,26 +35,28 @@ $database = $container->getByType(PDO::class); ``` -Δημιουργία υπηρεσίας .[#toc-creating-a-service] -=============================================== +Δημιουργία υπηρεσίας +==================== -Τις περισσότερες φορές, δημιουργούμε μια υπηρεσία με την απλή δημιουργία μιας περίπτωσης μιας κλάσης: +Συνήθως δημιουργούμε μια υπηρεσία απλά δημιουργώντας μια παρουσία μιας συγκεκριμένης κλάσης. Για παράδειγμα: ```neon services: database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -Η οποία θα δημιουργήσει μια μέθοδο εργοστασίου στο [DI |container] container: +Εάν χρειάζεται να επεκτείνουμε τη διαμόρφωση με επιπλέον κλειδιά, μπορούμε να αναπτύξουμε τον ορισμό σε πολλές γραμμές: -```php -public function createServiceDatabase(): PDO -{ - return new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'secret'); -} +```neon +services: + database: + create: PDO('sqlite::memory:') + setup: ... ``` -Εναλλακτικά, ένα κλειδί `arguments` μπορεί να χρησιμοποιηθεί για να περάσει [ορίσματα |#Arguments]: +Το κλειδί `create` έχει ένα alias `factory`, και οι δύο παραλλαγές είναι συνηθισμένες στην πράξη. Ωστόσο, συνιστούμε τη χρήση του `create`. + +Τα ορίσματα του κατασκευαστή ή της μεθόδου δημιουργίας μπορούν εναλλακτικά να γραφτούν στο κλειδί `arguments`: ```neon services: @@ -63,294 +65,303 @@ services: arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret] ``` -Μια στατική μέθοδος μπορεί επίσης να δημιουργήσει μια υπηρεσία: +Οι υπηρεσίες δεν χρειάζεται να δημιουργούνται μόνο με την απλή δημιουργία μιας παρουσίας κλάσης, μπορούν επίσης να είναι το αποτέλεσμα της κλήσης στατικών μεθόδων ή μεθόδων άλλων υπηρεσιών: ```neon services: - database: My\Database::create(root, secret) + database: DatabaseFactory::create() + router: @routerFactory::create() ``` -Αντιστοιχεί στον κώδικα PHP: +Σημειώστε ότι για λόγους απλότητας, αντί για `->` χρησιμοποιείται `::`, δείτε [#Εκφραστικά μέσα]. Θα δημιουργηθούν αυτές οι μέθοδοι factory: ```php public function createServiceDatabase(): PDO { - return My\Database::create('root', 'secret'); + return DatabaseFactory::create(); +} + +public function createServiceRouter(): RouteList +{ + return $this->getService('routerFactory')->create(); } ``` -Μια στατική μέθοδος `My\Database::create()` θεωρείται ότι έχει καθορισμένη τιμή επιστροφής που πρέπει να γνωρίζει ο περιέκτης DI. Εάν δεν την έχει, γράφουμε τον τύπο στη διαμόρφωση: +Το DI container πρέπει να γνωρίζει τον τύπο της δημιουργημένης υπηρεσίας. Εάν δημιουργούμε μια υπηρεσία χρησιμοποιώντας μια μέθοδο που δεν έχει καθορισμένο τύπο επιστροφής, πρέπει να δηλώσουμε ρητά αυτόν τον τύπο στη διαμόρφωση: ```neon services: database: - create: My\Database::create(root, secret) + create: DatabaseFactory::create() type: PDO ``` -Η Nette DI σας δίνει εξαιρετικά ισχυρές δυνατότητες έκφρασης για να γράψετε σχεδόν οτιδήποτε. Για παράδειγμα, για να [αναφερθούμε |#Referencing Services] σε μια άλλη υπηρεσία και να καλέσουμε τη μέθοδό της. Για λόγους απλότητας, χρησιμοποιείται το `::` αντί του `->`. + +Ορίσματα +======== + +Στον κατασκευαστή και τις μεθόδους παραδίδουμε ορίσματα με τρόπο πολύ παρόμοιο με την ίδια την PHP: ```neon services: - routerFactory: App\Router\Factory - router: @routerFactory::create() + database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -Αντιστοιχεί σε κώδικα PHP: +Για καλύτερη αναγνωσιμότητα, μπορούμε να αναπτύξουμε τα ορίσματα σε ξεχωριστές γραμμές. Σε αυτή την περίπτωση, η χρήση κομμάτων είναι προαιρετική: -```php -public function createServiceRouterFactory(): App\Router\Factory -{ - return new App\Router\Factory; -} - -public function createServiceRouter(): Router -{ - return $this->getService('routerFactory')->create(); -} +```neon +services: + database: PDO( + 'mysql:host=127.0.0.1;dbname=test' + root + secret + ) ``` -Οι κλήσεις μεθόδων μπορούν να συνδεθούν αλυσιδωτά όπως στην PHP: +Μπορείτε επίσης να ονομάσετε τα ορίσματα και δεν χρειάζεται να ανησυχείτε για τη σειρά τους: ```neon services: - foo: FooFactory::build()::get() + database: PDO( + username: root + password: secret + dsn: 'mysql:host=127.0.0.1;dbname=test' + ) ``` -Αντιστοιχεί στον κώδικα PHP: +Εάν θέλετε να παραλείψετε ορισμένα ορίσματα και να χρησιμοποιήσετε την προεπιλεγμένη τους τιμή ή να εισαγάγετε μια υπηρεσία χρησιμοποιώντας [autowiring|autowiring], χρησιμοποιήστε την κάτω παύλα: -```php -public function createServiceFoo() -{ - return FooFactory::build()->get(); -} +```neon +services: + foo: Foo(_, %appDir%) ``` +Ως ορίσματα μπορούν να παραδοθούν υπηρεσίες, να χρησιμοποιηθούν παράμετροι και πολλά άλλα, δείτε [#Εκφραστικά μέσα]. -Επιχειρήματα .[#toc-arguments] -============================== -Οι ονομαστικές παράμετροι μπορούν επίσης να χρησιμοποιηθούν για τη μετάδοση επιχειρημάτων: +Setup +===== + +Στην ενότητα `setup` ορίζουμε τις μεθόδους που πρέπει να κληθούν κατά τη δημιουργία της υπηρεσίας. ```neon services: - database: PDO( - 'mysql:host=127.0.0.1;dbname=test' # θέση - username: root # όνομα - password: secret # named - ) + database: + create: PDO(%dsn%, %user%, %password%) + setup: + - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) ``` -Η χρήση των κομμάτων είναι προαιρετική όταν τα ορίσματα χωρίζονται σε πολλές γραμμές. +Αυτό θα έμοιαζε έτσι στην PHP: -Φυσικά, μπορούμε επίσης να χρησιμοποιήσουμε [άλλες υπηρεσίες |#Referencing Services] ή [παραμέτρους |configuration#parameters] ως ορίσματα: +```php +public function createServiceDatabase(): PDO +{ + $service = new PDO('...', '...', '...'); + $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + return $service; +} +``` + +Εκτός από την κλήση μεθόδων, μπορούν επίσης να παραδοθούν τιμές σε properties. Υποστηρίζεται επίσης η προσθήκη ενός στοιχείου σε έναν πίνακα, το οποίο πρέπει να γραφτεί σε εισαγωγικά για να μην συγκρούεται με τη σύνταξη NEON: ```neon services: - - Foo(@anotherService, %appDir%) + foo: + create: Foo + setup: + - $value = 123 + - '$onClick[]' = [@bar, clickHandler] ``` -Αντιστοιχεί στον κώδικα PHP: +Αυτό θα έμοιαζε ως εξής στον κώδικα PHP: ```php -public function createService01(): Foo +public function createServiceFoo(): Foo { - return new Foo($this->getService('anotherService'), '...'); + $service = new Foo; + $service->value = 123; + $service->onClick[] = [$this->getService('bar'), 'clickHandler']; + return $service; } ``` - `_` character, for example `Foo(_, %appDir%)`Ή ακόμα καλύτερα, περάστε μόνο το δεύτερο όρισμα ως ονομαστική παράμετρο, π.χ. `Foo(path: %appDir%)`. - -Το Nette DI και η μορφή NEON σας δίνουν εξαιρετικά ισχυρές εκφραστικές δυνατότητες για να γράψετε σχεδόν οτιδήποτε. Έτσι, ένα όρισμα μπορεί να είναι ένα νεοδημιουργημένο αντικείμενο, μπορείτε να καλέσετε στατικές μεθόδους, μεθόδους άλλων υπηρεσιών ή ακόμη και παγκόσμιες συναρτήσεις χρησιμοποιώντας ειδικό συμβολισμό: +Στο setup, ωστόσο, μπορούν να κληθούν και στατικές μέθοδοι ή μέθοδοι άλλων υπηρεσιών. Εάν χρειάζεται να παραδώσετε την τρέχουσα υπηρεσία ως όρισμα, δηλώστε την ως `@self`: ```neon services: - analyser: My\Analyser( - FilesystemIterator(%appDir%) # δημιουργία αντικειμένου - DateTime::createFromFormat('Y-m-d') # κλήση στατικής μεθόδου - @anotherService # περνώντας μια άλλη υπηρεσία - @http.request::getRemoteAddress() # κλήση άλλης μεθόδου υπηρεσίας - ::getenv(NetteMode) # κλήση παγκόσμιας συνάρτησης - ) + foo: + create: Foo + setup: + - My\Helpers::initializeFoo(@self) + - @anotherService::setFoo(@self) ``` -Αντιστοιχεί στον κώδικα PHP: +Σημειώστε ότι για λόγους απλότητας, αντί για `->` χρησιμοποιείται `::`, δείτε [#Εκφραστικά μέσα]. Θα δημιουργηθεί μια τέτοια μέθοδος factory: ```php -public function createServiceAnalyser(): My\Analyser +public function createServiceFoo(): Foo { - return new My\Analyser( - new FilesystemIterator('...'), - DateTime::createFromFormat('Y-m-d'), - $this->getService('anotherService'), - $this->getService('http.request')->getRemoteAddress(), - getenv('NetteMode') - ); + $service = new Foo; + My\Helpers::initializeFoo($service); + $this->getService('anotherService')->setFoo($service); + return $service; } ``` -PHP: Ειδικές λειτουργίες .[#toc-special-functions] --------------------------------------------------- - -Μπορείτε επίσης να χρησιμοποιήσετε ειδικές συναρτήσεις σε ορίσματα για να ρίξετε ή να αναιρέσετε τιμές: +Εκφραστικά μέσα +=============== -- `not(%arg%)` negation -- `bool(%arg%)` cast χωρίς απώλειες σε bool -- `int(%arg%)` cast χωρίς απώλειες σε int -- `float(%arg%)` lossless cast to float -- `string(%arg%)` lossless cast to string +Το Nette DI μας δίνει εξαιρετικά πλούσια εκφραστικά μέσα, με τα οποία μπορούμε να γράψουμε σχεδόν οτιδήποτε. Στα αρχεία διαμόρφωσης μπορούμε έτσι να χρησιμοποιούμε [παραμέτρους |configuration#Παράμετροι]: ```neon -services: - - Foo( - id: int(::getenv('ProjectId')) - productionMode: not(%debugMode%) - ) -``` +# παράμετρος +%wwwDir% -Η αναδιαγραφή χωρίς απώλειες διαφέρει από την κανονική αναδιαγραφή της PHP, π.χ. με τη χρήση του `(int)`, στο ότι πετάει μια εξαίρεση για μη αριθμητικές τιμές. +# τιμή παραμέτρου κάτω από κλειδί +%mailer.user% -Πολλαπλές υπηρεσίες μπορούν να περάσουν ως ορίσματα. Ένας πίνακας όλων των υπηρεσιών ενός συγκεκριμένου τύπου (δηλ. κλάσης ή διεπαφής) δημιουργείται από τη συνάρτηση `typed()`. Η συνάρτηση θα παραλείψει τις υπηρεσίες που έχουν απενεργοποιήσει την αυτόματη καλωδίωση και μπορούν να καθοριστούν πολλαπλοί τύποι που χωρίζονται με κόμμα. - -```neon -services: - - BarsDependent( typed(Bar) ) +# παράμετρος μέσα σε string +'%wwwDir%/images' ``` -Μπορείτε επίσης να περάσετε έναν πίνακα υπηρεσιών αυτόματα χρησιμοποιώντας [την αυτόματη καλωδίωση |autowiring#Collection of Services]. - -Ένας πίνακας όλων των υπηρεσιών με μια συγκεκριμένη [ετικέτα |#tags] δημιουργείται από τη συνάρτηση `tagged()`. Μπορούν να καθοριστούν πολλαπλές ετικέτες που χωρίζονται με κόμμα. +Επίσης, να δημιουργούμε αντικείμενα, να καλούμε μεθόδους και συναρτήσεις: ```neon -services: - - LoggersDependent( tagged(logger) ) -``` +# δημιουργία αντικειμένου +DateTime() +# κλήση στατικής μεθόδου +Collator::create(%locale%) -Αναφορά σε υπηρεσίες .[#toc-referencing-services] -================================================= +# κλήση συνάρτησης PHP +::getenv(DB_USER) +``` -Η αναφορά σε μεμονωμένες υπηρεσίες γίνεται με τη χρήση των χαρακτήρων `@` and name, so for example `@database`: +Να αναφερόμαστε σε υπηρεσίες είτε με το όνομά τους είτε με τον τύπο τους: ```neon -services: - - create: Foo(@database) - setup: - - setCacheStorage(@cache.storage) +# υπηρεσία βάσει ονόματος +@database + +# υπηρεσία βάσει τύπου +@Nette\Database\Connection ``` -Αντιστοιχεί στον κώδικα PHP: +Να χρησιμοποιούμε first-class callable syntax: .{data-version:3.2.0} -```php -public function createService01(): Foo -{ - $service = new Foo($this->getService('database')); - $service->setCacheStorage($this->getService('cache.storage')); - return $service; -} +```neon +# δημιουργία callback, αντίστοιχο του [@user, logout] +@user::logout(...) ``` -Ακόμα και ανώνυμες υπηρεσίες μπορούν να αναφερθούν χρησιμοποιώντας μια επανάκληση, απλά καθορίστε τον τύπο τους (κλάση ή διεπαφή) αντί για το όνομά τους. Ωστόσο, αυτό συνήθως δεν είναι απαραίτητο λόγω της [αυτόματης καλωδίωσης |autowiring]. +Να χρησιμοποιούμε σταθερές: ```neon -services: - - create: Foo(@Nette\Database\Connection) # ή @\PDO - setup: - - setCacheStorage(@cache.storage) +# σταθερά κλάσης +FilesystemIterator::SKIP_DOTS + +# καθολική σταθερά λαμβάνεται με τη συνάρτηση PHP constant() +::constant(PHP_VERSION) ``` +Οι κλήσεις μεθόδων μπορούν να αλυσιδωθούν όπως στην PHP. Απλώς για λόγους απλότητας, αντί για `->` χρησιμοποιείται `::`: -Ρύθμιση .[#toc-setup] -===================== +```neon +DateTime()::format('Y-m-d') +# PHP: (new DateTime())->format('Y-m-d') -Στην ενότητα setup παραθέτουμε τις μεθόδους που θα κληθούν κατά τη δημιουργία της υπηρεσίας: +@http.request::getUrl()::getHost() +# PHP: $this->getService('http.request')->getUrl()->getHost() +``` + +Αυτές τις εκφράσεις μπορείτε να τις χρησιμοποιείτε οπουδήποτε, κατά τη [δημιουργία υπηρεσιών |#Δημιουργία υπηρεσίας], στα [#ορίσματα], στην ενότητα [#setup] ή στις [παραμέτρους |configuration#Παράμετροι]: ```neon +parameters: + ipAddress: @http.request::getRemoteAddress() + services: database: - create: PDO(%dsn%, %user%, %password%) + create: DatabaseFactory::create( @anotherService::getDsn() ) setup: - - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) + - initialize( ::getenv('DB_USER') ) ``` -Αντιστοιχεί στον κώδικα PHP: -```php -public function createServiceDatabase(): PDO -{ - $service = new PDO('...', '...', '...'); - $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - return $service; -} -``` +Ειδικές συναρτήσεις +------------------- -Properites μπορεί επίσης να οριστεί. Η προσθήκη ενός στοιχείου σε έναν πίνακα υποστηρίζεται επίσης, και θα πρέπει να γράφεται σε εισαγωγικά για να μην έρχεται σε σύγκρουση με τη σύνταξη του NEON: +Στα αρχεία διαμόρφωσης μπορείτε να χρησιμοποιείτε αυτές τις ειδικές συναρτήσεις: +- `not()` άρνηση της τιμής +- `bool()`, `int()`, `float()`, `string()` μετατροπή τύπου χωρίς απώλειες στον καθορισμένο τύπο +- `typed()` δημιουργεί έναν πίνακα όλων των υπηρεσιών του καθορισμένου τύπου +- `tagged()` δημιουργεί έναν πίνακα όλων των υπηρεσιών με το δεδομένο tag ```neon services: - foo: - create: Foo - setup: - - $value = 123 - - '$onClick[]' = [@bar, clickHandler] + - Foo( + id: int(::getenv('ProjectId')) + productionMode: not(%debugMode%) + ) ``` -Αντιστοιχεί στον κώδικα PHP: +Σε αντίθεση με την κλασική μετατροπή τύπου στην PHP, όπως π.χ. `(int)`, η μετατροπή τύπου χωρίς απώλειες θα προκαλέσει εξαίρεση για μη αριθμητικές τιμές. -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - $service->value = 123; - $service->onClick[] = [$this->getService('bar'), 'clickHandler']; - return $service; -} +Η συνάρτηση `typed()` δημιουργεί έναν πίνακα όλων των υπηρεσιών του δεδομένου τύπου (κλάση ή interface). Παραλείπει τις υπηρεσίες που έχουν απενεργοποιημένο το autowiring. Μπορούν να δηλωθούν και περισσότεροι τύποι διαχωρισμένοι με κόμμα. + +```neon +services: + - BarsDependent( typed(Bar) ) ``` -Ωστόσο, οι στατικές μέθοδοι ή οι μέθοδοι άλλων υπηρεσιών μπορούν επίσης να κληθούν στη ρύθμιση. Τους περνάμε την πραγματική υπηρεσία ως `@self`: +Μπορείτε επίσης να παραδώσετε αυτόματα έναν πίνακα υπηρεσιών ενός συγκεκριμένου τύπου ως όρισμα χρησιμοποιώντας [autowiring |autowiring#Πίνακας υπηρεσιών]. +Η συνάρτηση `tagged()` στη συνέχεια δημιουργεί έναν πίνακα όλων των υπηρεσιών με ένα συγκεκριμένο tag. Και εδώ μπορείτε να καθορίσετε περισσότερα tags διαχωρισμένα με κόμμα. ```neon services: - foo: - create: Foo - setup: - - My\Helpers::initializeFoo(@self) - - @anotherService::setFoo(@self) + - LoggersDependent( tagged(logger) ) ``` -Αντιστοιχεί στον κώδικα PHP: -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - My\Helpers::initializeFoo($service); - $this->getService('anotherService')->setFoo($service); - return $service; -} +Autowiring +========== + +Το κλειδί `autowired` επιτρέπει την επίδραση στη συμπεριφορά του autowiring για μια συγκεκριμένη υπηρεσία. Για λεπτομέρειες, δείτε το [κεφάλαιο για το autowiring|autowiring]. + +```neon +services: + foo: + create: Foo + autowired: false # η υπηρεσία foo εξαιρείται από το autowiring ``` -Autowiring .[#toc-autowiring] -============================= +Lazy υπηρεσίες .{data-version:3.2.4} +==================================== -Το κλειδί autowired μπορεί να χρησιμοποιηθεί για να αποκλείσει μια υπηρεσία από την αυτόματη σύνδεση ή για να επηρεάσει τη συμπεριφορά της. Για περισσότερες πληροφορίες, ανατρέξτε στο [κεφάλαιο σχετικά με την αυτόματη σύνδεση |autowiring]. +Το Lazy loading είναι μια τεχνική που αναβάλλει τη δημιουργία μιας υπηρεσίας μέχρι τη στιγμή που πραγματικά χρειάζεται. Στην καθολική διαμόρφωση, μπορείτε να [ενεργοποιήσετε την τεμπέλικη δημιουργία |configuration#Lazy υπηρεσίες] για όλες τις υπηρεσίες ταυτόχρονα. Για μεμονωμένες υπηρεσίες, μπορείτε στη συνέχεια να παρακάμψετε αυτή τη συμπεριφορά: ```neon services: foo: create: Foo - autowired: false # Το foo αφαιρείται από την αυτόματη καλωδίωση + lazy: false ``` +Όταν μια υπηρεσία ορίζεται ως lazy, κατά την αίτησή της από το DI container, λαμβάνουμε ένα ειδικό αντικείμενο υποκατάστατο. Αυτό φαίνεται και συμπεριφέρεται το ίδιο με την πραγματική υπηρεσία, αλλά η πραγματική αρχικοποίηση (κλήση του κατασκευαστή και του setup) πραγματοποιείται μόνο κατά την πρώτη κλήση οποιασδήποτε μεθόδου ή property της. + +.[note] +Το Lazy loading μπορεί να χρησιμοποιηθεί μόνο για κλάσεις χρήστη, όχι για εσωτερικές κλάσεις PHP. Απαιτεί PHP 8.4 ή νεότερη έκδοση. -Ετικέτες .[#toc-tags] -===================== -Οι πληροφορίες χρήστη μπορούν να προστεθούν σε μεμονωμένες υπηρεσίες με τη μορφή ετικετών: +Tags +==== + +Τα tags χρησιμοποιούνται για την προσθήκη συμπληρωματικών πληροφοριών στις υπηρεσίες. Μπορείτε να προσθέσετε ένα ή περισσότερα tags σε μια υπηρεσία: ```neon services: @@ -360,7 +371,7 @@ services: - cached ``` -Οι ετικέτες μπορούν επίσης να έχουν μια τιμή: +Τα tags μπορούν επίσης να φέρουν τιμές: ```neon services: @@ -370,26 +381,26 @@ services: logger: monolog.logger.event ``` -Ένας πίνακας υπηρεσιών με συγκεκριμένες ετικέτες μπορεί να περάσει ως όρισμα χρησιμοποιώντας τη συνάρτηση `tagged()`. Μπορούν επίσης να καθοριστούν πολλαπλές ετικέτες που χωρίζονται με κόμμα. +Για να λάβετε όλες τις υπηρεσίες με συγκεκριμένα tags, μπορείτε να χρησιμοποιήσετε τη συνάρτηση `tagged()`: ```neon services: - LoggersDependent( tagged(logger) ) ``` -Τα ονόματα των υπηρεσιών μπορούν να ληφθούν από το δοχείο DI χρησιμοποιώντας τη μέθοδο `findByTag()`: +Στο DI container, μπορείτε να λάβετε τα ονόματα όλων των υπηρεσιών με ένα συγκεκριμένο tag χρησιμοποιώντας τη μέθοδο `findByTag()`: ```php $names = $container->findByTag('logger'); -// $names είναι ένας πίνακας που περιέχει το όνομα της υπηρεσίας και την τιμή της ετικέτας +// Το $names είναι ένας πίνακας που περιέχει το όνομα της υπηρεσίας και την τιμή του tag // π.χ. ['foo' => 'monolog.logger.event', ...] ``` -Inject Mode .[#toc-inject-mode] -=============================== +Λειτουργία Inject +================= -Η σημαία `inject: true` χρησιμοποιείται για την ενεργοποίηση του περάσματος εξαρτήσεων μέσω δημόσιων μεταβλητών με τον σχολιασμό [inject |best-practices:inject-method-attribute#Inject Attributes] και τις μεθόδους [inject*() |best-practices:inject-method-attribute#inject Methods]. +Με τη χρήση της σημαίας `inject: true` ενεργοποιείται η παράδοση εξαρτήσεων μέσω δημόσιων μεταβλητών με την annotation [inject |best-practices:inject-method-attribute#Attributes Inject] και μεθόδων [inject*() |best-practices:inject-method-attribute#Μέθοδοι inject]. ```neon services: @@ -398,13 +409,13 @@ services: inject: true ``` -Από προεπιλογή, το `inject` είναι ενεργοποιημένο μόνο για τους παρουσιαστές. +Στην προεπιλεγμένη ρύθμιση, το `inject` ενεργοποιείται μόνο για τους presenters. -Τροποποίηση των υπηρεσιών .[#toc-modification-of-services] -========================================================== +Τροποποίηση υπηρεσιών +===================== -Υπάρχει ένας αριθμός υπηρεσιών στο δοχείο DI που έχουν προστεθεί από ενσωματωμένο ή από [την επέκτασή σας |#di-extensions]. Οι ορισμοί αυτών των υπηρεσιών μπορούν να τροποποιηθούν στη διαμόρφωση. Για παράδειγμα, για την υπηρεσία `application.application`, η οποία είναι εξ ορισμού ένα αντικείμενο `Nette\Application\Application`, μπορούμε να αλλάξουμε την κλάση: +Το DI container περιέχει πολλές υπηρεσίες που έχουν προστεθεί μέσω ενσωματωμένης ή [επέκτασης χρήστη|extensions]. Μπορείτε να τροποποιήσετε τους ορισμούς αυτών των υπηρεσιών απευθείας στη διαμόρφωση. Για παράδειγμα, μπορείτε να αλλάξετε την κλάση της υπηρεσίας `application.application`, η οποία είναι συνήθως `Nette\Application\Application`, σε άλλη: ```neon services: @@ -413,9 +424,9 @@ services: alteration: true ``` -Η σημαία `alteration` είναι πληροφοριακή και δηλώνει ότι απλώς τροποποιούμε μια υπάρχουσα υπηρεσία. +Η σημαία `alteration` είναι πληροφοριακή και λέει ότι απλώς τροποποιούμε μια υπάρχουσα υπηρεσία. -Μπορούμε επίσης να προσθέσουμε μια ρύθμιση: +Μπορούμε επίσης να συμπληρώσουμε το setup: ```neon services: @@ -426,7 +437,7 @@ services: - '$onStartup[]' = [@resource, init] ``` -Γι' αυτό υπάρχει το `reset`: +Κατά την αντικατάσταση μιας υπηρεσίας, μπορεί να θέλουμε να αφαιρέσουμε τα αρχικά ορίσματα, στοιχεία setup ή tags, για τα οποία χρησιμοποιείται το `reset`: ```neon services: @@ -439,7 +450,7 @@ services: - tags ``` -Μια υπηρεσία που προστίθεται με επέκταση μπορεί επίσης να αφαιρεθεί από το δοχείο: +Εάν θέλετε να αφαιρέσετε μια υπηρεσία που προστέθηκε από επέκταση, μπορείτε να το κάνετε ως εξής: ```neon services: diff --git a/dependency-injection/en/@home.texy b/dependency-injection/en/@home.texy index 8ea471e277..fb7805396b 100644 --- a/dependency-injection/en/@home.texy +++ b/dependency-injection/en/@home.texy @@ -1,5 +1,5 @@ -Dependency Injection -******************** +Nette DI +******** .[perex] Dependency Injection is a design pattern that will fundamentally change the way you look at code and development. It opens the way to a world of cleanly designed and sustainable applications. @@ -7,13 +7,10 @@ Dependency Injection is a design pattern that will fundamentally change the way - [What is Dependency Injection? |introduction] - [Global State & Singletons |global-state] - [Passing Dependencies |passing-dependencies] -- [What is DI Container? |container] +- [What is a DI Container? |container] - [Frequently Asked Questions |faq] -Nette DI --------- - The `nette/di` package provides an extremely advanced compiled DI container for PHP. - [Nette DI Container |nette-container] diff --git a/dependency-injection/en/@left-menu.texy b/dependency-injection/en/@left-menu.texy index 3ddd017bd2..db31b078c0 100644 --- a/dependency-injection/en/@left-menu.texy +++ b/dependency-injection/en/@left-menu.texy @@ -3,7 +3,7 @@ Dependency Injection - [What is DI? |introduction] - [Global State & Singletons |global-state] - [Passing Dependencies |passing-dependencies] -- [What is DI Container? |container] +- [What is a DI Container? |container] - [Frequently Asked Questions |faq] diff --git a/dependency-injection/en/@meta.texy b/dependency-injection/en/@meta.texy new file mode 100644 index 0000000000..42471908b0 --- /dev/null +++ b/dependency-injection/en/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Documentation}} diff --git a/dependency-injection/en/autowiring.texy b/dependency-injection/en/autowiring.texy index 92c827f3de..e8a4114cd0 100644 --- a/dependency-injection/en/autowiring.texy +++ b/dependency-injection/en/autowiring.texy @@ -2,9 +2,9 @@ Autowiring ********** .[perex] -Autowiring is a great feature that can automatically pass services to the constructor and other methods, so we do not need to write them at all. It saves you a lot of time. +Autowiring is a great feature that automatically passes required services to the constructor and other methods, so we don't have to specify them explicitly. It saves you a lot of time. -This allows us to skip the vast majority of arguments when writing service definitions. Instead of: +Thanks to this, we can omit the vast majority of arguments when writing service definitions. Instead of: ```neon services: @@ -18,7 +18,7 @@ services: articles: Model\ArticleRepository ``` -Autowiring is driven by types, so `ArticleRepository` class must be defined as follows: +Autowiring is guided by types, so for it to work, the `ArticleRepository` class must be defined roughly as follows: ```php namespace Model; @@ -30,22 +30,22 @@ class ArticleRepository } ``` -To use autowiring, there must be **just one service** for each type in the container. If there were more, autowiring would not know which one to pass and throw away an exception: +To be able to use autowiring, there must be **exactly one service** of each type in the container. If there were more, autowiring wouldn't know which one to pass and would throw an exception: ```neon services: mainDb: PDO(%dsn%, %user%, %password%) tempDb: PDO('sqlite::memory:') - articles: Model\ArticleRepository # THROWS EXCEPTION, both mainDb and tempDb matches + articles: Model\ArticleRepository # THROWS EXCEPTION, both mainDb and tempDb match ``` -The solution would be to either bypass autowiring and explicitly state the service name (i.e. `articles: Model\ArticleRepository(@mainDb)`). However, it is more convenient to [disable|#Disabled autowiring] autowiring of one services, or the first service [prefer|#Preferred Autowiring]. +The solution is to either bypass autowiring and explicitly specify the service name (e.g., `articles: Model\ArticleRepository(@mainDb)`). However, a more convenient approach is to either [disable |#Disabling Autowiring] autowiring for one of the services, or to [prefer |#Autowiring Preference] one service over the others. -Disabled Autowiring -------------------- +Disabling Autowiring +-------------------- -You can disable service autowiring by using the `autowired: no` option: +We can disable autowiring for a service using the `autowired: false` option: ```neon services: @@ -53,28 +53,27 @@ services: tempDb: create: PDO('sqlite::memory:') - autowired: false # removes tempDb from autowiring + autowired: false # service tempDb is excluded from autowiring - articles: Model\ArticleRepository # therefore passes mainDb to constructor + articles: Model\ArticleRepository # therefore passes mainDb to the constructor ``` -The `articles` service does not throw the exception that there are two matching services of type `PDO` (i.e. `mainDb` and `tempDb`) that can be passed to the constructor, because it only sees the `mainDb` service. +The `articles` service will not throw an exception about two matching `PDO` services (`mainDb` and `tempDb`) being available for the constructor, because it only considers the `mainDb` service. .[note] -Configuring autowiring in Nette works differently than in Symfony, where the `autowire: false` option says that autowiring should not be used for service constructor arguments. -In Nette, autowiring is always used, whether for arguments of the constructor or any other method. The `autowired: false` option says that the service instance should not be passed anywhere using autowiring. +Autowiring configuration in Nette differs from Symfony. In Symfony, `autowire: false` means autowiring shouldn't be used for the service's constructor arguments. In Nette, autowiring applies to constructor arguments and any other methods invoked via the container (like setter injection). The `autowired: false` option prevents the container from automatically passing this service instance as a dependency to other services. -Preferred Autowiring --------------------- +Autowiring Preference +--------------------- -If we have more services of the same type and one of them has the `autowired` option, this service becomes the preferred one: +If we have multiple services of the same type and specify the `autowired` option for one of them, this service becomes the preferred one: ```neon services: mainDb: create: PDO(%dsn%, %user%, %password%) - autowired: PDO # makes it preferred + autowired: PDO # becomes preferred tempDb: create: PDO('sqlite::memory:') @@ -82,13 +81,13 @@ services: articles: Model\ArticleRepository ``` -The `articles` service does not throw the exception that there are two matching `PDO` services (i.e. `mainDb` and `tempDb`), but uses the preferred service, i.e. `mainDb`. +The `articles` service will not throw an exception about multiple matching `PDO` services (`mainDb` and `tempDb`), but will use the preferred one, which is `mainDb`. Collection of Services ---------------------- -Autowiring can also pass an array of services of a particular type. Since PHP cannot natively notate the type of array items, in addition to the `array` type, a phpDoc comment with the item type like `ClassName[]` must be added: +Autowiring can also pass arrays of services of a specific type. Since PHP doesn't natively support specifying array item types in type hints, you must supplement the `array` type hint with a phpDoc comment specifying the item type, like `ClassName[]`: ```php namespace Model; @@ -103,16 +102,15 @@ class ShipManager } ``` -The DI container then automatically passes an array of services matching the given type. It will omit services that have autowiring turned off. +The DI container then automatically passes an array of services corresponding to the given type. It omits services that have autowiring disabled. -If you can't control the form of the phpDoc comment, you can pass an array of services directly in the configuration using [`typed()`|services#Special Functions]. +The type in the comment can also be of the form `array<int, Class>` or `list<Class>`. If you can't control the form of the phpDoc comment, you can pass an array of services directly in the configuration using [`typed()` |services#Special Functions]. Scalar Arguments ---------------- -Autowiring can only pass objects and arrays of objects. Scalar arguments (e.g. strings, numbers, booleans) [write in configuration |services#Arguments]. -An alternative is to create a [settings-object |best-practices:passing-settings-to-presenters] that encapsulates a scalar value (or multiple values) as an object, which can then be passed again using autowiring. +Autowiring only works for objects and arrays of objects. Scalar arguments (e.g., strings, numbers, booleans) must be [specified in the configuration |services#Arguments]. An alternative is to create a [settings object|best-practices:passing-settings-to-presenters] that encapsulates the scalar value (or multiple values). This object can then be passed using autowiring. ```php class MySettings @@ -125,22 +123,22 @@ class MySettings } ``` -You create a service by adding it to the configuration: +You register it as a service by adding it to the configuration: ```neon services: - MySettings('any value') ``` -All classes will then request it via autowiring. +Other classes can then request it via autowiring. -Narrowing of Autowiring ------------------------ +Narrowing Autowiring +-------------------- -For individual services, autowiring can be narrowed to specific classes or interfaces. +For individual services, autowiring can be narrowed down to specific classes or interfaces. -Normally, autowiring passes the service to each method parameter whose type the service corresponds to. Narrowing means that we specify conditions that the types specified for the method parameters must satisfy for the service to be passed to them. +Normally, autowiring passes a service to every method parameter whose type the service matches. Narrowing means we establish conditions that the types specified for the method parameters must meet for the service to be passed to them. Let's take an example: @@ -170,30 +168,30 @@ If we registered them all as services, autowiring would fail: services: parent: ParentClass child: ChildClass - parentDep: ParentDependent # THROWS EXCEPTION, both parent and child matches - childDep: ChildDependent # passes the service 'child' to the constructor + parentDep: ParentDependent # THROWS EXCEPTION, both parent and child services match + childDep: ChildDependent # autowiring passes the child service to the constructor ``` -The `parentDep` service throws the exception `Multiple services of type ParentClass found: parent, child` because both `parent` and `child` fit into its constructor and autowiring can not make a decision on which one to choose. +The `parentDep` service throws the exception `Multiple services of type ParentClass found: parent, child`, because both the `parent` and `child` services fit into its constructor, and autowiring cannot decide which one to choose. -For service `child`, we can therefore narrow down its autowiring to `ChildClass`: +For the `child` service, we can therefore narrow its autowiring to the type `ChildClass`: ```neon services: parent: ParentClass child: create: ChildClass - autowired: ChildClass # alternative: 'autowired: self' + autowired: ChildClass # can also be written as 'autowired: self' - parentDep: ParentDependent # THROWS EXCEPTION, the 'child' can not be autowired - childDep: ChildDependent # passes the service 'child' to the constructor + parentDep: ParentDependent # autowiring passes the parent service to the constructor + childDep: ChildDependent # autowiring passes the child service to the constructor ``` -The `parentDep` service is now passed to the `parentDep` service constructor, since it is now the only matching object. The `child` service is no longer passed in by autowiring. Yes, the `child` service is still of type `ParentClass`, but the narrowing condition given for the parameter type no longer applies, i.e. it is no longer true that `ParentClass` *is a supertype* of `ChildClass`. +Now, the `parent` service is passed to the `parentDep` service's constructor, as it is now the only matching object. The `child` service is no longer passed there by autowiring. Yes, the `child` service is still of type `ParentClass`, but the narrowing condition `autowired: ChildClass` means it will only be passed to parameters explicitly typed as `ChildClass` (or its subtypes). Since `ParentDependent` requires `ParentClass`, the `child` service is no longer considered a candidate for autowiring there. -In the case of `child`, `autowired: ChildClass` could be written as `autowired: self` as the `self` means current service type. +For the `child` service, `autowired: ChildClass` could also be written as `autowired: self`, since `self` is a placeholder for the current service's class. -The `autowired` key can include several classes and interfaces as array: +In the `autowired` key, it is also possible to specify multiple classes or interfaces as an array: ```neon autowired: [BarClass, FooInterface] @@ -239,13 +237,13 @@ class ChildDependent } ``` -When we do not limit the `child` service, it will fit into the constructors of all `FooDependent`, `BarDependent`, `ParentDependent` and `ChildDependent` classes and autowiring will pass it there. +If we don't restrict the `child` service in any way, it will fit into the constructors of all `FooDependent`, `BarDependent`, `ParentDependent`, and `ChildDependent` classes, and autowiring will pass it there. -However, if we narrow its autowiring to `ChildClass` using `autowired: ChildClass` (or `self`), autowiring it only passes it to the `ChildDependent` constructor, because it requires an argument of type `ChildClass` and `ChildClass` *is of type* `ChildClass`. No other type specified for the other parameters is a superset of `ChildClass`, so the service is not passed. +However, if we narrow its autowiring to `ChildClass` using `autowired: ChildClass` (or `self`), autowiring will only pass it to the `ChildDependent` constructor, because it requires an argument of type `ChildClass` and it holds that `ChildClass` *is of type* `ChildClass`. No other type specified for the other parameters is a supertype of `ChildClass`, so the service is not passed. -If we restrict it to `ParentClass` using `autowired: ParentClass`, autowiring will pass it again to the `ChildDependent` constructor (since the required type `ChildClass` is a superset of `ParentClass`) and to the `ParentDependent` constructor too, since the required type of `ParentClass` is also matching. +If we restrict it to `ParentClass` using `autowired: ParentClass`, autowiring will again pass it to the `ChildDependent` constructor (because the required `ChildClass` is a supertype of `ParentClass`) and now also to the `ParentDependent` constructor, because the required type `ParentClass` is also suitable. -If we restrict it to `FooInterface`, it will still autowire to `ParentDependent` (the required type `ParentClass` is a supertype of `FooInterface`) and `ChildDependent`, but additionally to the `FooDependent` constructor, but not to `BarDependent`, since `BarInterface` is not a supertype of `FooInterface`. +If we restrict it to `FooInterface`, it will still be autowired into `ParentDependent` (the required `ParentClass` is a supertype of `FooInterface`) and `ChildDependent`, but additionally also into the `FooDependent` constructor, but not into `BarDependent`, because `BarInterface` is not a supertype of `FooInterface`. ```neon services: @@ -253,8 +251,8 @@ services: create: ChildClass autowired: FooInterface - fooDep: FooDependent # passes the service child to the constructor - barDep: BarDependent # THROWS EXCEPTION, no service would pass - parentDep: ParentDependent # passes the service child to the constructor - childDep: ChildDependent # passes the service child to the constructor + fooDep: FooDependent # autowiring passes the child service to the constructor + barDep: BarDependent # THROWS EXCEPTION, no service matches + parentDep: ParentDependent # autowiring passes the child service to the constructor + childDep: ChildDependent # autowiring passes the child service to the constructor ``` diff --git a/dependency-injection/en/configuration.texy b/dependency-injection/en/configuration.texy index a43f9a653c..7f42e4bc97 100644 --- a/dependency-injection/en/configuration.texy +++ b/dependency-injection/en/configuration.texy @@ -8,7 +8,7 @@ Overview of configuration options for the Nette DI container. Configuration File ================== -Nette DI container is easy to control using configuration files. They are usually written in [NEON format|neon:format]. We recommend to use [editors with support|best-practices:editors-and-tools#ide-editor] for this format for editing. +The Nette DI container is easily controlled using configuration files. These are usually written in the [NEON format|neon:format]. We recommend using [editors with support |best-practices:editors-and-tools#IDE Editor] for this format. <pre> "decorator .[prism-token prism-atrule]":[#Decorator]: "Decorator .[prism-token prism-comment]"<br> @@ -20,13 +20,14 @@ Nette DI container is easy to control using configuration files. They are usuall "services .[prism-token prism-atrule]":[services]: "Services .[prism-token prism-comment]" </pre> -To write a string containing the character `%`, you must escape it by doubling it to `%%`. .[note] +.[note] +To write a string containing the character `%`, you must escape it by doubling it to `%%`. Parameters ========== -You can define parameters that can then be used as part of service definitions. This can help to separate out values that you will want to change more regularly. +In the configuration, you can define parameters that can then be used as part of service definitions. This allows you to clarify the configuration or to centralize values that might change. ```neon parameters: @@ -35,9 +36,9 @@ parameters: password: secret ``` -You can refer to `foo` parameter via `%foo%` elsewhere in any config file. They can also be used inside strings like `'%wwwDir%/images'`. +We refer to the `dsn` parameter anywhere in the configuration using the notation `%dsn%`. Parameters can also be used inside strings like `'%wwwDir%/images'`. -Parameters do not need to be just strings, they can also be array values: +Parameters do not have to be just strings or numbers, they can also contain arrays: ```neon parameters: @@ -48,9 +49,9 @@ parameters: languages: [cs, en, de] ``` -You can refer to single key as `%mailer.user%`. +We refer to a specific key as `%mailer.user%`. -If you need to get the value of any parameter in your code, for example in your class, then pass it to this class. For example, in the constructor. There is no global configuration object which can classes query for parameter values. This would be against to the principle of dependency injection. +If your code (e.g., a class) needs the value of a parameter, pass it to the class. For example, in the constructor. There is no global configuration object that classes can query for parameter values. That would be a violation of the dependency injection principle. Services @@ -62,18 +63,18 @@ See [separate chapter|services]. Decorator ========= -How to bulk edit all services of a certain type? Need to call a certain method for all presenters inheriting from a particular common ancestor? That's where the decorator comes from. +How can you modify multiple services of a certain type at once? For example, how to call a specific method on all presenters that inherit from a particular base class? That's what the decorator is for. ```neon decorator: # for all services that are instances of this class or interface - App\Presenters\BasePresenter: + App\Presentation\BasePresenter: setup: - setProjectId(10) # call this method - $absoluteUrls = true # and set the variable ``` -Decorator can also be used to set [tags|services#Tags] or turn on [inject mode|services#Inject Mode]. +Decorators can also be used to set [tags |services#Tags] or enable [inject mode |services#Inject Mode]. ```neon decorator: @@ -90,26 +91,40 @@ Technical settings of the DI container. ```neon di: - # shows DIC in Tracy Bar? + # show DIC in Tracy Bar? debugger: ... # (bool) defaults to true # parameter types that you never autowire excluded: ... # (string[]) + # enable lazy service creation? + lazy: ... # (bool) default is false + # the class from which the DI container inherits parentClass: ... # (string) defaults to Nette\DI\Container ``` +Lazy Services .{data-version:3.2.4} +----------------------------------- + +Setting `lazy: true` activates lazy (deferred) creation of services. This means that services are not actually created at the moment they are requested from the DI container, but only at the time of their first use. This can speed up application startup and reduce memory usage, as only the services actually needed for a given request are created. + +For a specific service, lazy creation can be [adjusted |services#Lazy Services]. + +.[note] +Lazy objects can only be used for user-defined classes, not for internal PHP classes. Requires PHP 8.4 or newer. + + Metadata Export --------------- -The DI container class also contains a lot of metadata. You can reduce it by reducing the metadata export. +The DI container class also contains a lot of metadata. You can reduce its size by reducing the metadata export. ```neon di: export: - # to export parameters? + # export parameters? parameters: false # (bool) defaults to true # export tags and which ones? @@ -122,31 +137,29 @@ di: - Symfony\Component\Console\Application ``` -If you don't use the `$container->parameters` array, you can disable parameter export. Furthermore, you can export only those tags through which you get services using the `$container->findByTag(...)` method. -If you don't call the method at all, you can completely disable tag export with `false`. +If you don't use `$container->getParameters()`, you can disable parameter export. Furthermore, you can export only the tags that you actually use to retrieve services via `$container->findByTag(...)`. If you don't call this method at all, you can disable tag export completely using `false`. -You can significantly reduce the metadata for [autowiring] by specifying the classes you use as a parameter to the `$container->getByType()` method. -And again, if you don't call the method at all (or only in [application:bootstrap] to get `Nette\Application\Application`), you can disable the export entirely with `false`. +You can significantly reduce metadata for [autowiring|autowiring] by listing only the classes you actually request using `$container->getByType()`. Again, if you don't call this method (or only call it in the [bootstrap|application:bootstrapping] file, e.g., to get `Nette\Application\Application`), you can disable type export completely using `false`. Extensions ========== -Registration of other DI extensions. In this way we add, for example, DI extension `Dibi\Bridges\Nette\DibiExtension22` under the name `dibi`: +Registration of additional DI extensions. This is how you add, for example, the DI extension `Dibi\Bridges\Nette\DibiExtension22` under the name `dibi`: ```neon extensions: dibi: Dibi\Bridges\Nette\DibiExtension22 ``` -Then we configure it in it's section called also `dibi`: +You then configure it in the `dibi` section: ```neon dibi: host: localhost ``` -You can also add a extension class with parameters: +You can also add a class with parameters as an extension: ```neon extensions: @@ -157,7 +170,7 @@ extensions: Including Files =============== -Additional configuration files can be inserted in the `includes` section: +Additional configuration files can be included in the `includes` section: ```neon includes: @@ -166,7 +179,7 @@ includes: - presenters.neon ``` -The name `parameters.php` is not a typo, the configuration can also be written in a PHP file, which returns it as an array: +The name `parameters.php` is not a typo; the configuration can also be written in a PHP file that returns it as an array: ```php <?php @@ -179,32 +192,27 @@ return [ ]; ``` -If items with the same keys appear within configuration files, they will be [overwritten or merged |#Merging] in the case of arrays. Later included file has a higher priority than the previous one. The file in which the `includes` section is listed has a higher priority than the files included in it. +If items with the same keys appear in multiple configuration files, they will be overwritten, or [merged |#Merging] in the case of arrays. A file included later has higher priority than the previous one. The file in which the `includes` section is listed has a higher priority than the files included in it. Search ====== -The automatic adding of services to the DI container makes work extremely pleasant. Nette automatically adds presenters to the container, but you can easily add any other classes. +Automatic registration of services in the DI container significantly simplifies development. Nette automatically adds presenters to the container, but you can easily add any other classes as well. -Just specify in which directories (and subdirectories) the classes should be search for: +Just specify in which directories (and subdirectories) the classes should be searched for: ```neon search: - # you choose the section names yourself - myForms: - in: %appDir%/Forms - - model: - in: %appDir%/Model + - in: %appDir%/Forms + - in: %appDir%/Model ``` -Usually, however, we don't want to add all the classes and interfaces, so we can filter them: +Usually, however, we don't want to add absolutely all classes and interfaces, so we can filter them: ```neon search: - myForms: - in: %appDir%/Forms + - in: %appDir%/Forms # filtering by file name (string|string[]) files: @@ -215,34 +223,35 @@ search: - *Factory ``` -Or we can select classes that inherit or implement at least one of the following classes: +Or we can select classes that inherit or implement at least one of the listed classes: ```neon search: - myForms: + - in: %appDir% extends: - App\*Form implements: - App\*FormInterface ``` -You can also define negative rules, ie class name masks or ancestors and if they comply, the service will not be added to the DI container: +You can also define exclusion rules using class name masks or ancestors. If a class matches an exclusion rule, it won't be added to the DI container: ```neon search: - myForms: + - in: %appDir% exclude: + files: ... classes: ... extends: ... implements: ... ``` -Tags can be set for added services: +Tags can be assigned to all automatically registered services: ```neon search: - myForms: + - in: %appDir% tags: ... ``` @@ -250,7 +259,7 @@ search: Merging ======= -If items with the same keys appear in more configuration files, they will be overwritten or merged in the case of arrays. The later included file has a higher priority. +If elements with the same keys appear in multiple configuration files, they will be overwritten, or merged in the case of arrays. The later included file has higher priority than the previous one. <table class=table> <tr> @@ -283,7 +292,7 @@ items: </tr> </table> -To prevent merging of a certain array use exclamation mark right after the name of the array: +For arrays, merging can be prevented by adding an exclamation mark after the key name: <table class=table> <tr> @@ -314,5 +323,4 @@ items: </tr> </table> - {{maintitle: Dependency Injection Configuration}} diff --git a/dependency-injection/en/container.texy b/dependency-injection/en/container.texy index 2f5e18d979..fc8dd7bbec 100644 --- a/dependency-injection/en/container.texy +++ b/dependency-injection/en/container.texy @@ -2,13 +2,13 @@ What Is DI Container? ********************* .[perex] -Dependency injection container (DIC) is a class that can instantiate and configure objects. +A Dependency Injection Container (DIC or DI container) is an object responsible for instantiating and configuring other objects (called services). -It may surprise you, but in many cases you don't need a dependency injection container to take advantage of dependency injection (DI for short). After all, even in [introductory chapter|introduction] we showed specific examples of DI and no container was needed. +It might surprise you, but in many cases, you don't need a dependency injection container to leverage the benefits of dependency injection (DI for short). After all, even in the [introductory chapter|introduction], we showed specific examples of DI, and no container was necessary. -However, if you need to manage a large number of different objects with many dependencies, a dependency injection container will be really useful. Which is perhaps the case for web applications built on a framework. +However, when managing a large number of objects with complex dependencies, a DI container becomes very useful. This is often the case for web applications built on a framework. -In the previous chapter, we introduced the classes `Article` and `UserController`. Both of them have some dependencies, namely database and factory `ArticleFactory`. And for these classes, we will now create a container. Of course, for such a simple example, it doesn't make sense to have a container. But we'll create one to show how it looks and works. +In the previous chapter, we introduced the classes `Article` and `UserController`. Both have dependencies, namely the database and the factory `ArticleFactory`. And for these classes, we will now create a container. Of course, creating a container for such a simple example is overkill. But we'll create one to show how it looks and works. Here is a simple hardcoded container for the above example: @@ -39,9 +39,9 @@ $container = new Container; $controller = $container->createUserController(); ``` -We just ask the container for the object and no longer need to know anything about how to create it or what its dependencies are; the container knows all that. The dependencies are injected automatically by the container. That's its power. +We simply request the object from the container, without needing to know how to create it or what its dependencies are; the container handles all of that. Dependencies are automatically injected by the container. That's its strength. -So far, the container has everything hardcoded. So we take the next step and add parameters to make the container really useful: +Currently, the container has all the information hardcoded. So, let's take the next step and add parameters to make the container truly useful: ```php class Container @@ -70,9 +70,9 @@ $container = new Container([ ]); ``` -Astute readers may have noticed a problem. Every time I get an object `UserController`, a new instance `ArticleFactory` and database is also created. We definitely don't want that. +Sharp-eyed readers might notice a problem. Every time we retrieve a `UserController` object, new instances of `ArticleFactory` and the database connection are also created. We definitely don't want that. -So we add a method `getService()` that will return the same instances over and over again: +Therefore, we'll add a `getService()` method that will always return the same instances: ```php class Container @@ -87,7 +87,7 @@ class Container public function getService(string $name): object { if (!isset($this->services[$name])) { - // getService('Database') calls createDatabase() + // getService('Database') will call createDatabase() $method = 'create' . $name; $this->services[$name] = $this->$method(); } @@ -98,7 +98,7 @@ class Container } ``` -The first call to e.g. `$container->getService('Database')` will have `createDatabase()` create a database object, which it will store in the array `$services` and return it directly on the next call. +On the first call, for example `$container->getService('Database')`, it calls `createDatabase()` to create the database object, stores it in the `$services` array, and returns it. On subsequent calls, it returns the already stored instance directly. We also modify the rest of the container to use `getService()`: @@ -134,9 +134,9 @@ $controller = $container->getService('UserController'); $database = $container->getService('Database'); ``` -As you can see, it's not difficult to write a DIC. It's notable that the objects themselves don't know that a container is creating them. Thus, it is possible to create any object in PHP this way without affecting their source code. +As you can see, writing a DIC isn't difficult. It's worth noting that the objects themselves are unaware that a container is creating them. Consequently, it's possible to create any PHP object this way without modifying its source code. -Manually creating and maintaining a container class can become a nightmare rather quickly. Therefore, in the next chapter we will talk about [Nette DI Container|nette-container], which can generate and update itself almost automatically. +Manually creating and maintaining the container class can quickly become a nightmare. Therefore, in the next chapter, we will discuss the [Nette DI Container|nette-container], which can generate and update itself almost automatically. {{maintitle: What is Dependency Injection Container?}} diff --git a/dependency-injection/en/extensions.texy b/dependency-injection/en/extensions.texy index 4e016ce8b7..6a5ccdd87d 100644 --- a/dependency-injection/en/extensions.texy +++ b/dependency-injection/en/extensions.texy @@ -2,16 +2,16 @@ Creating Extensions for Nette DI ******************************** .[perex] -Generating an DI container in addition to configuration files also affect the so-called *extensions*. We activate them in the configuration file in the `extensions` section. +Besides configuration files, the generation of the DI container is also influenced by *extensions*. We activate them in the configuration file in the `extensions` section. -This is how we add the extension represented by class `BlogExtension` with name `blog`: +This is how you add an extension, represented by the `BlogExtension` class, under the name `blog`: ```neon extensions: blog: BlogExtension ``` -Each compiler extension inherits from [api:Nette\DI\CompilerExtension] and can implement following methods that are called during DI compilation: +Each compiler extension inherits from [api:Nette\DI\CompilerExtension] and can implement the following methods, which are called sequentially during the DI container compilation process: 1. getConfigSchema() 2. loadConfiguration() @@ -22,18 +22,18 @@ Each compiler extension inherits from [api:Nette\DI\CompilerExtension] and can i getConfigSchema() .[method] =========================== -This method is called first. It defines schema used to validate configuration parameters. +This method is called first. It defines the schema for validating configuration parameters. -Extensions are configured in a section whose name is the same as the one under which the extension was added, eg `blog`. +You configure the extension in a section named after the extension, in this case `blog`: ```neon -# same name as my extension +# same name as the extension blog: postsPerPage: 10 - comments: false + allowComments: false ``` -We will define a schema describing all configuration options, including their types, accepted values and possibly default values: +We create a schema describing all configuration options, including their types, allowed values, and optional default values: ```php use Nette\Schema\Expect; @@ -50,9 +50,9 @@ class BlogExtension extends Nette\DI\CompilerExtension } ``` -See the [Schema |schema:] for documentation. Additionally, you can specify which options can be [dynamic |application:bootstrap#Dynamic Parameters] using `dynamic()`, for example `Expect::int()->dynamic()`. +Refer to the [Schema |schema:] page for documentation. Additionally, you can specify which options can be [dynamic |application:bootstrapping#Dynamic Parameters] using `dynamic()`, for example `Expect::int()->dynamic()`. -We access configuration through the `$this->config`, which is an object `stdClass`: +We access the configuration via the `$this->config` variable, which is an `stdClass` object: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -71,7 +71,7 @@ class BlogExtension extends Nette\DI\CompilerExtension loadConfiguration() .[method] ============================= -This method is used to add services to the container. This is done by [api:Nette\DI\ContainerBuilder]: +This method is used to add services to the container. The [api:Nette\DI\ContainerBuilder] is used for this: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -86,19 +86,19 @@ class BlogExtension extends Nette\DI\CompilerExtension } ``` -The convention is to prefix the services added by an extension with its name so that no name conflicts arise. This is done by `prefix()`, so if the extension is called 'blog', the service will be called `blog.articles`. +The convention is to prefix services added by the extension with its name to avoid name conflicts. The `prefix()` method does this, so if the extension is named `blog`, the service will be named `blog.articles`. -If we need to rename a service, we can create an alias with its original name to maintain backward compatibility. Similarly this is what Nette does for eg `routing.router`, which is also available under the earlier name `router`. +If we need to rename a service, we can create an alias with the original name for backward compatibility. Nette does this similarly, e.g., for the `routing.router` service, which is also available under the former name `router`. ```php $builder->addAlias('router', 'routing.router'); ``` -Retrieve Services from a File ------------------------------ +Loading Services from File +-------------------------- -We can create services using the ContainerBuilder API, but also we can add them via the familiar NEON configuration file and its `services` section. The prefix `@extension` represents the current extension. +Services can be defined not only using the ContainerBuilder API but also using the familiar NEON syntax within the extension's configuration file or a separate NEON file. The prefix `@extension` represents the current extension. ```neon services: @@ -112,7 +112,7 @@ services: create: MyBlog\Components\ArticlesList(@extension.articles) ``` -We will add services this way: +Load the services using `Compiler::loadDefinitionsFromConfig()`: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -133,7 +133,7 @@ class BlogExtension extends Nette\DI\CompilerExtension beforeCompile() .[method] ========================= -The method is called when the container contains all the services added by individual extensions in `loadConfiguration` methods as well as user configuration files. At this phase of assembling, we can then modify service definitions or add links between them. You can use the `findByTag()` method to search for services by tags, or `findByType()` method to search by class or interface. +This method is called once the container builder holds all service definitions loaded from extensions (`loadConfiguration` methods) and user configuration files. At this stage, you can modify existing service definitions or add relationships between them (e.g., using method calls). You can find services using `findByTag()` or `findByType()`. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -153,7 +153,7 @@ class BlogExtension extends Nette\DI\CompilerExtension afterCompile() .[method] ======================== -At this phase, the container class is already generated as a [ClassType |php-generator:#classes] object, it contains all the methods that the service creates, and is ready for caching as PHP file. We can still edit the resulting class code at this point. +At this stage, the container class has been generated as a [ClassType |php-generator:#Classes] object. It includes all the factory methods for creating services and is ready to be written to the cache file. You can still modify the generated class code at this point. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -167,12 +167,12 @@ class BlogExtension extends Nette\DI\CompilerExtension ``` -$initialization .[wiki-method] -============================== +$initialization .[method] +========================= -The Configurator calls the initialization code after [container creation |application:bootstrap#index.php], which is created by writing to an object `$this->initialization` using [method addBody() |php-generator:#method-and-function-body]. +The `Configurator` executes initialization code after the [container is created |application:bootstrapping#index.php]. This code is built by adding statements to the `$this->initialization` object using its [addBody() method |php-generator:#Method and Function Bodies]. -We will show an example of how to start a session or start services that have the `run` tag using initialization code: +Here's an example showing how to start a session or instantiate services tagged with `run` using initialization code: ```php class BlogExtension extends Nette\DI\CompilerExtension diff --git a/dependency-injection/en/factory.texy b/dependency-injection/en/factory.texy index daf99d3f3f..64ac6435b7 100644 --- a/dependency-injection/en/factory.texy +++ b/dependency-injection/en/factory.texy @@ -2,11 +2,11 @@ Generated Factories ******************* .[perex] -Nette DI can automatically generate factory code based on the interface, which saves you from writing code. +Nette DI can automatically generate factory code based on interfaces, saving you from writing code. -A factory is a class that creates and configures objects. It therefore passes their dependencies to them as well. Please do not confuse with the *factory method* design pattern, which describes a specific way of using factories and is not related to this topic. +A factory is a class that is responsible for creating objects and passing their dependencies. Please do not confuse this with the *factory method* design pattern, which describes a specific way of using factories and is unrelated to this topic. -We have shown what such a factory looks like in the [introductory chapter |introduction#factory]: +We have shown what such a factory looks like in the [introductory chapter |introduction#Factory]: ```php class ArticleFactory @@ -23,7 +23,7 @@ class ArticleFactory } ``` -Nette DI can generate factory code automatically. All you have to do is create an interface and Nette DI will generate an implementation. The interface must have exactly one method named `create` and declare a return type: +Nette DI can automatically generate factory code. All you need to do is create an interface, and Nette DI will generate the implementation. The interface must have exactly one method named `create` and declare a return type: ```php interface ArticleFactory @@ -32,7 +32,7 @@ interface ArticleFactory } ``` -So the factory `ArticleFactory` has a method `create` that creates objects `Article`. Class `Article` might look like the following, for example: +So, the factory `ArticleFactory` has a method `create` that creates `Article` objects. The `Article` class might look like this, for example: ```php class Article @@ -53,7 +53,7 @@ services: Nette DI will generate the corresponding factory implementation. -Thus, in the code that uses the factory, we request the object by interface and Nette DI uses the generated implementation: +In the code that uses the factory, request the object by its interface, and Nette DI will provide the generated implementation: ```php class UserController @@ -75,7 +75,7 @@ class UserController Parameterized Factory ===================== -The factory method `create` can accept parameters which it then passes to the constructor. For example, let's add an article author ID to the class `Article`: +The factory method `create` can accept parameters, which it then passes to the constructor. For example, let's add the article author's ID to the `Article` class: ```php class Article @@ -97,13 +97,13 @@ interface ArticleFactory } ``` -Because the parameter in the constructor and the parameter in the factory have the same name, Nette DI will pass them automatically. +Since the parameter name in the constructor (`$authorId`) matches the parameter name in the factory method, Nette DI automatically passes it. Advanced Definition =================== -The definition can also be written in multi-line form using the key `implement`: +The definition can also be written in multi-line form using the `implement` key: ```neon services: @@ -111,9 +111,9 @@ services: implement: ArticleFactory ``` -When writing in this longer way, it is possible to provide additional arguments for the constructor in the key `arguments` and additional configuration using `setup`, just as for normal services. +Using this longer format allows specifying additional arguments for the constructor via the `arguments` key and further configuration using `setup`, similar to regular service definitions. -Example: if the method `create()` did not accept the parameter `$authorId`, we could specify a fixed value in the configuration that would be passed to the constructor `Article`: +Example: If the `create()` method didn't accept the `$authorId` parameter, we could provide a fixed value in the configuration to be passed to the `Article` constructor: ```neon services: @@ -123,7 +123,7 @@ services: authorId: 123 ``` -Or, conversely, if `create()` did accept the parameter `$authorId` but it was not part of the constructor and was passed by method `Article::setAuthorId()`, we would refer to it in section `setup`: +Conversely, if `create()` accepted `$authorId`, but it wasn't part of the constructor and was instead passed via a method like `Article::setAuthorId()`, we would reference the parameter in the `setup` section: ```neon services: @@ -137,12 +137,11 @@ services: Accessor ======== -Besides factories, Nette can also generate so called accessors. Accessor is an object with `get()` method returning a particular service from the DI container. Multiple `get()` calls will always return the same instance. +Besides factories, Nette can also generate so-called accessors. These are objects with a `get()` method that returns a specific service from the DI container. Repeated calls to `get()` always return the same instance. -Accessors bring lazy-loading to dependencies. Let's have a class logging errors to a special database. If the database connection would be passed as a dependency in its constructor, the connection would need to be always created although it would be used only rarely when an error appears so the connection would stay mostly unused. -Instead, the class can pass an accessor and when its `get()` method is called, only then the database object is created: +Accessors provide lazy-loading for dependencies. Consider a class that logs errors to a dedicated database. If this class received the database connection via constructor dependency injection, the connection would always be established, even if errors occur rarely and the connection remains unused most of the time. Instead, the class can receive an accessor. The database object (connection) is only created when the accessor's `get()` method is called for the first time: -How to create an accessor? Write an interface only and Nette DI will generate the implementation. The interface must have exactly one method called `get` and must declare the return type: +How to create an accessor? Just write an interface, and Nette DI will generate the implementation. The interface must have exactly one method named `get` and declare the return type: ```php interface PDOAccessor @@ -151,7 +150,7 @@ interface PDOAccessor } ``` -Add the accessor to the configuration file together with the definition of the service the accessor will return: +Add the accessor to the configuration file, along with the definition of the service it should return: ```neon services: @@ -159,50 +158,56 @@ services: - PDO(%dsn%, %user%, %password%) ``` -The accessor returns a service of type `PDO` and because there's only one such service in the configuration, the accessor will return it. With multiple configured services of that type you can specify which one should be returned using its name, for example `- PDOAccessor(@db1)`. +Because the accessor returns a `PDO` service, and there's only one such service defined in the configuration, the accessor will return that specific service. If multiple services of that type exist, specify which one the accessor should return by name, e.g., `- PDOAccessor(@db1)`. Multifactory/Accessor ===================== -So far, the factories and accessors could only create or return just one object. A multifactory combined with an accessor can be created as well. The interface of such multifactory class can consist of multiple methods called `create<name>()` and `get<name>()`, for example: +So far, our factories and accessors could only create or return a single type of object. However, you can easily create multifactories, which combine features of factories and accessors. The interface for such a component can contain multiple methods named `create<Name>()` and `get<Name>()`, for example: ```php interface MultiFactory { function createArticle(): Article; - function createFoo(): Model\Foo; function getDb(): PDO; } ``` -Instead of passing multiple generated factories and accessors, you can pass just one complex multifactory. +So, instead of injecting multiple individual factories and accessors, you can inject a single, more comprehensive component. -Alternatively, you can use `create()` and `get()` with a parameter instead of multiple methods: +Alternatively, instead of multiple methods, `get()` with a parameter can be used: ```php interface MultiFactoryAlt { - function create($name); function get($name): PDO; } ``` -In this case, `MultiFactory::createArticle()` does the same thing as `MultiFactoryAlt::create('article')`. However, the alternative syntax has a few disadvantages. It's not clear which `$name` values are supported and the return type cannot be specified in the interface when using multiple different `$name` values. +Then, `MultiFactory::getDb()` does the same thing as `MultiFactoryAlt::get('db')`. However, this alternative notation has the disadvantage that the supported values for `$name` are not explicitly clear from the interface signature. Additionally, you cannot define different return types for different `$name` values within the interface. Definition with a List ---------------------- -Hos to define a multifactory in your configuration? Let's create three services which will be returned by the multifactory, and the multifactory itself: +You can define a multifactory in the configuration using a list: .{data-version:3.2.0} + +```neon +services: + - MultiFactory( + article: Article # defines createArticle() + db: PDO(%dsn%, %user%, %password%) # defines getDb() + ) +``` + +Alternatively, you can refer to existing services in the multifactory definition using references: ```neon services: article: Article - - Model\Foo - PDO(%dsn%, %user%, %password%) - MultiFactory( - article: @article # createArticle() - foo: @Model\Foo # createFoo() - db: @\PDO # getDb() + article: @article # defines createArticle() + db: @\PDO # defines getDb() ) ``` @@ -210,11 +215,11 @@ services: Definition with Tags -------------------- -Another option how to define a multifactory is to use [tags|services#Tags]: +Another option how to define a multifactory is to use [tags |services#Tags]: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter - App\Model\DatabaseAccessor( db1: @database.db1.explorer ) diff --git a/dependency-injection/en/faq.texy b/dependency-injection/en/faq.texy index ecf0ee0df0..b7f65321ba 100644 --- a/dependency-injection/en/faq.texy +++ b/dependency-injection/en/faq.texy @@ -5,44 +5,39 @@ DI Frequently Asked Questions (FAQ) Is DI another name for IoC? --------------------------- -The *Inversion of Control* (IoC) is a principle focused on the way code is executed - whether your code initiates external code or your code is integrated into external code, which then calls it. -IoC is a broad concept that includes [events |nette:glossary#Events], the so-called [Hollywood Principle |application:components#Hollywood style], and other aspects. -Factories, which are part of [Rule #3: Let the Factory Handle It |introduction#Rule #3: Let the Factory Handle It], and represent inversion for the `new` operator, are also components of this concept. +*Inversion of Control* (IoC) is a principle describing the flow of control in a program: is your code calling external code, or is external code (like a framework) calling your code? IoC is a broad concept that includes [events |nette:glossary#Events], the so-called [Hollywood Principle |application:components#Hollywood Style], and other aspects. This concept also includes factories, discussed in [Rule #3: Let the Factory Handle It |introduction#Rule #3: Let the Factory Handle It], which represent an inversion of the `new` operator. -The *Dependency Injection* (DI) is about how one object knows about another object, i.e., dependency. It is a design pattern that requires explicit passing of dependencies between objects. +*Dependency Injection* (DI) focuses on how objects obtain their dependencies (i.e., other objects they need to work with). It's a design pattern advocating for passing dependencies explicitly to objects rather than having objects create or find them. -Thus, DI can be said to be a specific form of IoC. However, not all forms of IoC are suitable in terms of code purity. For example, among anti-patterns, we include all techniques that work with [global state] or the so-called [Service Locator |#What is a Service Locator]. +Therefore, DI can be considered a specific form of IoC. However, not all forms of IoC promote clean code. For example, anti-patterns include techniques relying on [global state|global-state] or the [Service Locator |#What is a Service Locator] pattern. What is a Service Locator? -------------------------- -A Service Locator is an alternative to Dependency Injection. It works by creating a central storage where all available services or dependencies are registered. When an object needs a dependency, it requests it from the Service Locator. +It's an alternative approach to Dependency Injection. It involves a central object (the locator) where all available services (dependencies) are registered. When an object needs a dependency, it requests it from the Service Locator. -However, compared to Dependency Injection, it loses transparency: dependencies are not directly passed to objects and are therefore not easily identifiable, which requires examining the code to uncover and understand all connections. Testing is also more complicated, as we cannot simply pass mock objects to the tested objects, but have to go through the Service Locator. Furthermore, the Service Locator disrupts the design of the code, as individual objects must be aware of its existence, which differs from Dependency Injection, where objects have no knowledge of the DI container. +However, compared to DI, it lacks transparency. Dependencies are hidden within the object's code (calls to the locator) rather than being explicit in its API (constructor or methods), requiring code inspection to understand the connections. Testing is also more complex, as you can't simply pass mock dependencies when instantiating an object; you often need to manipulate the Service Locator itself. Furthermore, the Service Locator introduces an unnecessary dependency: objects become coupled to the locator, unlike with DI, where objects ideally remain unaware of the container. When is it better not to use DI? -------------------------------- -There are no known difficulties associated with using the Dependency Injection design pattern. On the contrary, obtaining dependencies from globally accessible locations leads to [a number of complications |global-state], as does using a Service Locator. -Therefore, it is advisable to always use DI. This is not a dogmatic approach, but simply no better alternative has been found. +There are no known significant drawbacks to using the Dependency Injection design pattern correctly. On the contrary, obtaining dependencies from globally accessible locations (like static properties or singletons) leads to [numerous complications|global-state], as does using a Service Locator. Therefore, using DI is generally always advisable. This isn't dogma; it's simply that no better alternative for managing dependencies in a clean way has been widely adopted. -However, there are certain situations where we do not pass objects to each other and obtain them from the global space. For example, when debugging code and needing to dump a variable value at a specific point in the program, measure the duration of a certain part of the program, or log a message. -In such cases, where it is about temporary actions that will be later removed from the code, it is legitimate to use a globally accessible dumper, stopwatch, or logger. These tools, after all, do not belong to the design of the code. +However, there are specific, limited situations where accessing objects globally might be acceptable. For example, during debugging, when you need to dump a variable's value, measure execution time, or log a message at a specific point. In these cases, involving temporary actions that will later be removed from the code, using a globally accessible dumper, timer, or logger can be legitimate. These tools are not part of the application's core design. Does using DI have its drawbacks? --------------------------------- -Does using Dependency Injection involve any disadvantages, such as increased code writing complexity or worse performance? What do we lose when we start writing code in accordance with DI? +Does using Dependency Injection introduce disadvantages, like increased coding effort or reduced performance? What do we lose when we start writing code in accordance with DI? -DI has no impact on application performance or memory requirements. The performance of the DI Container may play a role, but in the case of [Nette DI | nette-container], the container is compiled into pure PHP, so its overhead during application runtime is essentially zero. +DI itself has negligible impact on runtime performance or memory usage. The performance of the DI container can be a factor, but [Nette DI |nette-container] compiles the container into plain PHP code, resulting in virtually zero overhead during application execution. -When writing code, it is necessary to create constructors that accept dependencies. In the past, this could be time-consuming, but thanks to modern IDEs and [constructor property promotion |https://blog.nette.org/en/php-8-0-complete-overview-of-news#toc-constructor-property-promotion], it is now a matter of a few seconds. Factories can be easily generated using Nette DI and a PhpStorm plugin with just a few clicks. -On the other hand, there is no need to write singletons and static access points. +When writing code following DI principles, you often need to create constructors that accept dependencies. While this might have seemed tedious in the past, modern IDEs and features like PHP 8's [constructor property promotion |https://blog.nette.org/en/php-8-0-complete-overview-of-news#toc-constructor-property-promotion] make it very quick. Factories can often be generated automatically by Nette DI, further reducing boilerplate. On the other hand, you eliminate the need to write singletons and static accessors. -It can be concluded that a properly designed application using DI is neither shorter nor longer compared to an application using singletons. Parts of the code working with dependencies are simply extracted from individual classes and moved to new locations, i.e., the DI container and factories. +Overall, a well-designed application using DI is typically neither significantly shorter nor longer than one relying on singletons or global access. The code related to dependency creation and wiring is simply moved from individual classes to dedicated locations: the DI container configuration and factories. How to rewrite a legacy application to DI? @@ -51,46 +46,45 @@ How to rewrite a legacy application to DI? Migrating from a legacy application to Dependency Injection can be a challenging process, especially for large and complex applications. It is important to approach this process systematically. - When moving to Dependency Injection, it is important that all team members understand the principles and practices being used. -- First, perform an analysis of the existing application to identify key components and their dependencies. Create a plan for which parts will be refactored and in what order. +- First, analyze the existing application to identify key components and their dependencies. Create a plan for which parts will be refactored and in what order. - Implement a DI container or, better yet, use an existing library such as Nette DI. -- Gradually refactor each part of the application to use Dependency Injection. This may involve modifying constructors or methods to accept dependencies as parameters. -- Modify places in the code where dependency objects are created so that dependencies are instead injected by the container. This may include the use of factories. +- Gradually refactor parts of the application to use Dependency Injection. This may involve modifying constructors or methods to accept dependencies as parameters. +- Update the code where objects are instantiated to retrieve them from the container or use factories provided by the container. This may include the use of factories. -Remember that moving to Dependency Injection is an investment in code quality and the long-term sustainability of the application. While it may be challenging to make these changes, the result should be cleaner, more modular, and easily testable code that is ready for future extensions and maintenance. +Remember that transitioning to Dependency Injection is an investment in code quality and long-term application maintainability. While it may be challenging to make these changes, the result should be cleaner, more modular, and easily testable code that is ready for future extensions and maintenance. Why composition is preferred over inheritance? ---------------------------------------------- -It is preferable to use composition rather then inheritance as it serves the purpose of code reuse-ability without having the need to worry about the trickle down effect of change. Thus it provides more loosely coupling where we do not have to worry about changing some code causing some other dependent code requiring change. A typical example is the situation identified as [constructor hell |passing-dependencies#Constructor hell]. +Using [composition |nette:introduction-to-object-oriented-programming#Composition] is generally preferred over [inheritance |nette:introduction-to-object-oriented-programming#Inheritance] for code reuse because it leads to looser coupling. With composition, you are less likely to face issues where changing a base class breaks dependent subclasses. A typical example is a situation referred to as [constructor hell |passing-dependencies#Constructor Hell]. Can Nette DI Container be used outside of Nette? ------------------------------------------------ -Absolutely. The Nette DI Container is part of Nette, but is designed as a standalone library that can be used independently of other parts of the framework. Just install it using Composer, create a configuration file defining your services, and then use a few lines of PHP code to create the DI container. -And you can immediately start taking advantage of Dependency Injection in your projects. +Absolutely. The Nette DI Container is part of Nette, but is designed as a standalone library that can be used independently of other parts of the framework. Just install it using Composer, create a configuration file defining your services, and then use a few lines of PHP code to create the DI container. And you can immediately start taking advantage of Dependency Injection in your projects. -The [Nette DI Container |nette-container] chapter describes what a specific use case looks like, including the code. +The chapter on [Nette DI Container |nette-container] describes a specific use case with code examples. Why is the configuration in NEON files? --------------------------------------- -NEON is a simple and easily readable configuration language developed within Nette for setting up applications, services, and their dependencies. Compared to JSON or YAML, it offers much more intuitive and flexible options for this purpose. In NEON, you can naturally describe bindings that would not be possible to write in Symfony & YAML either at all or only through a complex description. +NEON is a simple and easily readable configuration language developed within Nette for setting up applications, services, and their dependencies. Compared to JSON or YAML, it offers much more intuitive and flexible options for this purpose. In NEON, you can naturally describe service definitions and relationships that would be difficult or impossible to express as clearly in JSON or YAML. Does parsing NEON files slow down the application? -------------------------------------------------- -Although NEON files are parsed very quickly, this aspect doesn't really matter. The reason is that parsing files occurs only once during the first launch of the application. After that, the DI container code is generated, stored on the disk, and executed for each subsequent request without the need for further parsing. +Although NEON files parse very quickly, their parsing speed is largely irrelevant in production. This is because configuration files are parsed only once when the application first runs (or when they change). After parsing, the DI container code is generated, cached (stored on disk), and this compiled PHP code is executed on every subsequent request, eliminating the need for further parsing. -This is how it works in a production environment. During development, NEON files are parsed every time their content changes, ensuring that the developer always has an up-to-date DI container. As mentioned earlier, the actual parsing is a matter of an instant. +This is how it works in a production environment. During development, NEON files are parsed every time their content changes, ensuring that the developer always has an up-to-date DI container. As mentioned, the parsing itself is very fast. How do I access the parameters from the configuration file in my class? ----------------------------------------------------------------------- -Keep in mind [Rule #1: Let It Be Passed to You |introduction#Rule #1: Let It Be Passed to You]. If a class requires information from a configuration file, we don't need to figure out how to access that information; instead, we simply ask for it - for example, through the class constructor. And we perform the passing in the configuration file. +Keep in mind [Rule #1: Let It Be Passed to You |introduction#Rule #1: Let It Be Passed to You]. If a class needs information from the configuration file, don't try to figure out how the class can *fetch* it. Instead, simply request it, for example, via the class constructor. Then, provide this value in the configuration file. In this example, `%myParameter%` is a placeholder for the value of the `myParameter` parameter, which will be passed to the `MyClass` constructor: diff --git a/dependency-injection/en/global-state.texy b/dependency-injection/en/global-state.texy index f8f47d4b7e..f626392831 100644 --- a/dependency-injection/en/global-state.texy +++ b/dependency-injection/en/global-state.texy @@ -9,9 +9,9 @@ Warning: The following constructs are symptoms of poorly designed code: - `Article::setDb($db)` - `ClassName::$var` or `static::$var` -Do you encounter any of these constructs in your code? If so, you have an opportunity to improve it. You might think these are common constructs, often seen in sample solutions of various libraries and frameworks. If that's the case, their code design is flawed. +Do any of these constructs appear in your code? If so, you have an opportunity for improvement. You might think these are common constructs, perhaps seen in example solutions from various libraries and frameworks. If so, their code design is flawed. -We're not talking about some academic purity here. All these constructs have one thing in common: they utilize global state. And this has a destructive impact on code quality. Classes are deceptive about their dependencies. The code becomes unpredictable. It confuses developers and reduces their efficiency. +We're not talking about some academic purity here. All these constructs share one common trait: they utilize global state. And global state has a detrimental impact on code quality. Classes become deceptive about their dependencies. The code becomes unpredictable. It confuses developers and reduces their efficiency. In this chapter, we'll explain why this is the case and how to avoid global state. @@ -23,7 +23,7 @@ In an ideal world, an object should only communicate with objects that were [dir However, this doesn't hold true for global (static) variables or singletons. Object `A` could *wirelessly* access object `C` and modify it without any reference passing, by calling `C::changeSomething()`. If object `B` also taps into the global `C`, then `A` and `B` can influence each other through `C`. -Using global variables introduces a new form of *wireless* coupling that's not externally visible. It creates a smokescreen that complicates understanding and using the code. To truly understand the dependencies, developers have to read every line of the source code, rather than just familiarizing themselves with class interfaces. Moreover, this entanglement is entirely unnecessary. Global state is used because it's easily accessible from anywhere and allows, for instance, writing to a database through a global (static) method `DB::insert()`. However, as we'll see, the benefit it offers is minimal, while the complications it introduces are severe. +Using global variables introduces a new form of *wireless* coupling, invisible from the outside. It creates a smokescreen, making the code harder to understand and use. To truly grasp the dependencies, developers must read every line of source code, instead of just relying on the class interfaces. Furthermore, this coupling is entirely unnecessary. Global state is used because it's easily accessible from anywhere and allows, for instance, writing to a database through a global (static) method `DB::insert()`. However, as we will demonstrate, the perceived convenience is minimal compared to the severe complications it introduces. .[note] In terms of behavior, there is no difference between a global and a static variable. They are equally harmful. @@ -33,12 +33,11 @@ The Spooky Action at a Distance ------------------------------- "Spooky action at a distance" - that's what Albert Einstein famously called a phenomenon in quantum physics that gave him the creeps in 1935. -It is quantum entanglement, the peculiarity of which is that when you measure information about one particle, you immediately affect another particle, even if they are millions of light years apart. -which seemingly violates the fundamental law of the universe that nothing can travel faster than light. +It refers to quantum entanglement, where measuring a property of one particle instantaneously affects another entangled particle, regardless of the distance separating them, even millions of light-years. which seemingly violates the fundamental law of the universe that nothing can travel faster than light. -In the software world, we can call a "spooky action at a distance" a situation where we run a process that we think is isolated (because we haven't passed it any references), but unexpected interactions and state changes happen in distant locations of the system which we did not tell the object about. This can only happen through the global state. +In the software world, 'spooky action at a distance' describes a situation where we execute a process believed to be isolated (since no dependencies were explicitly passed), yet unexpected interactions and state changes occur in distant parts of the system, unbeknownst to us. This can only occur through global state. -Imagine joining a project development team that has a large, mature code base. Your new lead asks you to implement a new feature and, like a good developer, you start by writing a test. But because you're new to the project, you do a lot of exploratory "what happens if I call this method" type tests. And you try to write the following test: +Imagine joining a development team on a project with a large, mature codebase. Your new lead asks you to implement a new feature and, like a good developer, you start by writing a test. But because you're new to the project, you do a lot of exploratory 'what happens if I call this method' type tests. And you try to write the following test: ```php function testCreditCardCharge() @@ -48,18 +47,15 @@ function testCreditCardCharge() } ``` -You run the code, maybe several times, and after a while you notice notifications on your phone from the bank that each time you run it, $100 was charged to your credit card 🤦‍♂️ +You run the code, perhaps several times, and after a while, you notice bank notifications on your phone: $100 has been charged to your credit card with each execution! 🤦‍♂️ -How on earth could the test cause an actual charge? It's not easy to operate with credit card. You have to interact with a third party web service, you have to know the URL of that web service, you have to log in, and so on. -None of this information is included in the test. Even worse, you don't even know where this information is present, and therefore how to mock external dependencies so that each run doesn't result in $100 being charged again. And as a new developer, how were you supposed to know that what you were about to do would lead to you being $100 poorer? +How on earth could the test cause an actual charge? Operating with a credit card isn't simple. You need to interact with a third-party web service, know its URL, authenticate, and so forth. None of this information is included in the test. Worse still, you don't know where this information resides, making it impossible to mock the external dependencies to prevent the $100 charge on each test run. And as a new developer, how were you supposed to know that what you were about to do would lead to you being $100 poorer? That's a spooky action at a distance! -You have no choice but to dig through a lot of source code, asking older and more experienced colleagues, until you understand how the connections in the project work. -This is due to the fact that when looking at the interface of the `CreditCard` class, you cannot determine the global state that needs to be initialized. Even looking at the source code of the class won't tell you which initialization method to call. At best, you can find the global variable being accessed and try to guess how to initialize it from that. +You're forced to sift through extensive source code and consult senior colleagues to understand the project's interconnections. This difficulty arises because the `CreditCard` class interface doesn't reveal the necessary global state initialization. Even examining the class's source code might not reveal which initialization method to call. At best, you might find the global variable being accessed and attempt to deduce how to initialize it. -The classes in such a project are pathological liars. The payment card pretends that you can just instantiate it and call the `charge()` method. However, it secretly interacts with another class, `PaymentGateway`. Even its interface says it can be initialized independently, but in reality it pulls credentials from some configuration file and so on. -It is clear to the developers who wrote this code that `CreditCard` needs `PaymentGateway`. They wrote the code this way. But for anyone new to the project, this is a complete mystery and hinders learning. +The classes in such a project are pathological liars. The `CreditCard` class pretends it can be simply instantiated and its `charge()` method called. However, it secretly interacts with another class, `PaymentGateway`, representing the payment gateway. Even the `PaymentGateway` interface might suggest independent initialization, but in reality, it might pull credentials from a configuration file, and so on. The original developers understand that `CreditCard` requires `PaymentGateway`. They wrote the code this way. But for newcomers, it's a complete mystery that hinders their ability to learn and contribute effectively. How to fix the situation? Easy. **Let the API declare dependencies.** @@ -72,29 +68,28 @@ function testCreditCardCharge() } ``` -Notice how the relationships within the code are suddenly obvious. By declaring that the `charge()` method needs `PaymentGateway`, you don't have to ask anyone how the code is interdependent. You know you have to create an instance of it, and when you try to do so, you run into the fact that you have to supply access parameters. Without them, the code wouldn't even run. +Notice how the interdependencies within the code become immediately apparent. Because the `charge()` method declares its need for a `PaymentGateway`, you no longer need to guess or ask about this dependency. You know you need to create an instance, and in doing so, you'll discover the required access parameters. Without them, the code wouldn't even run. And most importantly, you can now mock the payment gateway so you won't be charged $100 every time you run a test. -The global state causes your objects to be able to secretly access things that aren't declared in their APIs, and as a result makes your APIs pathological liars. +Global state allows objects to secretly access dependencies not declared in their APIs, effectively turning your APIs into pathological liars. -You may not have thought of it this way before, but whenever you use global state, you're creating secret wireless communication channels. Creepy remote action forces developers to read every line of code to understand potential interactions, reduces developer productivity, and confuses new team members. -If you're the one who created the code, you know the real dependencies, but anyone who comes after you is clueless. +You may not have thought of it this way before, but whenever you use global state, you're creating secret wireless communication channels. This spooky action at a distance forces developers to read every line of code to understand potential interactions, reducing productivity and confusing new team members. If you're the one who created the code, you know the real dependencies, but anyone who comes after you is clueless. -Don't write code that uses global state, prefer to pass dependencies. That is, dependency injection. +Avoid writing code that relies on global state; prefer passing dependencies explicitly. Embrace dependency injection. -Brittleness of the Global State -------------------------------- +Fragility of Global State +------------------------- -In code that uses global state and singletons, it is never certain when and by whom that state has changed. This risk is already present at initialization. The following code is supposed to create a database connection and initialize the payment gateway, but it keeps throwing an exception and finding the cause is extremely tedious: +In code utilizing global state and singletons, you can never be certain when or by whom the state was modified. This risk manifests even during initialization. The following code intends to create a database connection and initialize a payment gateway, but it repeatedly throws exceptions, and debugging the cause is extremely tedious: ```php PaymentGateway::init(); DB::init('mysql:', 'user', 'password'); ``` -You have to go through the code in detail to find that the `PaymentGateway` object accesses other objects wirelessly, some of which require a database connection. Thus, you must initialize the database before `PaymentGateway`. However, the smokescreen of global state hides this from you. How much time would you save if the API of each class did not lie and declare its dependencies? +You must meticulously trace the code to discover that the `PaymentGateway` object wirelessly accesses other objects, some requiring a database connection. Therefore, the database must be initialized before `PaymentGateway`. However, the smokescreen of global state hides this from you. How much time would be saved if the APIs of these classes were honest and declared their dependencies? ```php $db = new DB('mysql:', 'user', 'password'); @@ -115,7 +110,7 @@ class Article } ``` -When calling the `save()` method, it is not certain whether a database connection has already been created and who is responsible for creating it. For example, if we wanted to change the database connection on the fly, perhaps for testing purposes, we would probably have to create additional methods such as `DB::reconnect(...)` or `DB::reconnectForTest()`. +When calling the `save()` method, it's uncertain whether a database connection has been established or who is responsible for establishing it. If we need to change the database connection dynamically (e.g., for testing), we might resort to adding methods like `DB::reconnect(...)` or `DB::reconnectForTest()`. Consider an example: @@ -127,7 +122,7 @@ Foo::doSomething(); $article->save(); ``` -Where can we be sure that the test database is really being used when calling `$article->save()`? What if the `Foo::doSomething()` method changed the global database connection? To find out, we would have to examine the source code of the `Foo` class and probably many other classes. However, this approach would provide only a short-term answer, as the situation may change in the future. +How can we be sure that the test database is actually used when `$article->save()` is called? What if the `Foo::doSomething()` method changed the global database connection? To determine this, we'd need to inspect the source code of `Foo` and potentially many other classes. This investigation would only provide a temporary answer, as the situation could change later. What if we move the database connection to a static variable inside the `Article` class? @@ -148,7 +143,7 @@ class Article } ``` -This doesn't change anything at all. The problem is a global state and it doesn't matter which class it hides in. In this case, as in the previous one, we have no clue as to what database is being written to when the `$article->save()` method is called. Anyone on the distant end of the application could change the database at any time using `Article::setDb()`. Under our hands. +This doesn't change anything at all. The problem is the global state itself, regardless of which class it's hidden within. In this scenario, just like the previous one, when `$article->save()` is called, we have no certainty about which database the data will be written to. Anyone, anywhere in the application, could have changed the database at any time using `Article::setDb()`. Without our knowledge. The global state makes our application **extremely fragile**. @@ -174,9 +169,9 @@ Foo::doSomething(); $article->save(); ``` -This approach eliminates the worry of hidden and unexpected changes to database connections. Now we are sure where the article is stored and no code modifications inside another unrelated class can change the situation anymore. The code is no longer fragile, but stable. +This approach eliminates concerns about hidden or unexpected changes to the database connection. We now have certainty about where the article is being saved, and modifications within unrelated classes can no longer affect it. The code is no longer fragile, but stable. -Don't write code that uses global state, prefer to pass dependencies. Thus, dependency injection. +Avoid writing code that relies on global state; prefer passing dependencies explicitly. Embrace dependency injection. Singleton @@ -201,22 +196,21 @@ class Singleton Unfortunately, the singleton introduces global state into the application. And as we have shown above, global state is undesirable. That's why the singleton is considered an antipattern. -Don't use singletons in your code and replace them with other mechanisms. You really don't need singletons. However, if you need to guarantee the existence of a single instance of a class for the entire application, leave it to the [DI container |container]. -Thus, create an application singleton, or service. This will stop the class from providing its own uniqueness (i.e., it won't have a `getInstance()` method and a static variable) and will only perform its functions. Thus, it will stop violating the single responsibility principle. +Don't use singletons in your code and replace them with other mechanisms. You really don't need singletons. However, if you need to ensure only one instance of a class exists throughout the application, delegate this responsibility to the [DI container |container]. This creates an application-scoped singleton, commonly referred to as a service. The class itself is then freed from managing its uniqueness (i.e., it won't have a `getInstance()` method or a static instance property) and can focus solely on its responsibilities. Thus, it will stop violating the single responsibility principle. Global State Versus Tests ------------------------- -When writing tests, we assume that each test is an isolated unit and that no external state enters it. And no state leaves the tests. When a test completes, any state associated with the test should be removed automatically by the garbage collector. This makes the tests isolated. Therefore, we can run the tests in any order. +When writing tests, we ideally assume each test is an isolated unit, with no external state entering or leaving it. After a test completes, any state associated with it should be automatically cleaned up by the garbage collector. This makes the tests isolated. Therefore, we can run the tests in any order. -However, if global states/singletons are present, all these nice assumptions break down. A state can enter and exit a test. Suddenly, the order of the tests may matter. +However, when global states or singletons are present, these beneficial assumptions crumble. State can leak into and out of tests. Suddenly, the order of the tests may matter. -To test singletons at all, developers often have to relax their properties, perhaps by allowing an instance to be replaced by another. Such solutions are, at best, hacks that produce code that is difficult to maintain and understand. Any test or method `tearDown()` that affects any global state must undo those changes. +To even test code involving singletons, developers often have to compromise their integrity, for example, by allowing the singleton instance to be replaced. Such solutions are, at best, hacks that lead to code that is difficult to maintain and understand. Any test (or its `tearDown()` method) that modifies global state must meticulously revert those changes. Global state is the biggest headache in unit testing! -How to fix the situation? Easy. Don't write code that uses singletons, prefer to pass dependencies. That is, dependency injection. +How to fix this? Simple. Avoid writing code that uses singletons; prefer passing dependencies explicitly. Embrace dependency injection. Global Constants @@ -224,9 +218,7 @@ Global Constants Global state is not limited to the use of singletons and static variables, but can also apply to global constants. -Constants whose value does not provide us with any new (`M_PI`) or useful (`PREG_BACKTRACK_LIMIT_ERROR`) information are clearly OK. -Conversely, constants that serve as a way to *wirelessly* pass information inside the code are nothing more than a hidden dependency. Like `LOG_FILE` in the following example. -Using the `FILE_APPEND` constant is perfectly correct. +Constants whose values represent universal truths (`M_PI`) or provide self-contained information (`PREG_BACKTRACK_LIMIT_ERROR`) are generally acceptable. Conversely, constants used as a way to *wirelessly* inject information into code are effectively hidden dependencies. Like `LOG_FILE` in the following example. Using the `FILE_APPEND` constant is perfectly correct. ```php const LOG_FILE = '...'; @@ -242,7 +234,7 @@ class Foo } ``` -In this case, we should declare the parameter in the constructor of the `Foo` class to make it part of the API: +Instead, we should declare the log file path as a parameter in the `Foo` class's constructor, making it an explicit part of its API: ```php class Foo @@ -261,44 +253,42 @@ class Foo } ``` -Now we can pass information about the path to the logging file and easily change it as needed, making it easier to test and maintain the code. +Now, we explicitly pass the path to the log file. We can easily change it as needed, which simplifies testing and code maintenance. Global Functions and Static Methods ----------------------------------- -We want to emphasize that the use of static methods and global functions is not in itself problematic. We have explained the inappropriateness of using `DB::insert()` and similar methods, but it has always been a matter of global state stored in a static variable. The `DB::insert()` method requires the existence of a static variable because it stores the database connection. Without this variable, it would be impossible to implement the method. +We want to emphasize that using static methods and global functions is not inherently problematic. We explained the issues with methods like `DB::insert()`, but the core problem was always the underlying global state, typically stored in a static variable. The `DB::insert()` method relies on a static variable to hold the database connection. Without this variable, it would be impossible to implement the method. -The use of deterministic static methods and functions, such as `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` and many others, is perfectly consistent with dependency injection. These functions always return the same results from the same input parameters and are therefore predictable. They do not use any global state. +Using deterministic static methods and functions like `DateTimeImmutable::createFromFormat()`, `Closure::fromCallable()`, `strlen()`, and many others is perfectly compatible with dependency injection. These functions are predictable because they always return the same result for the same input parameters. They do not use any global state. -However, there are functions in PHP that are not deterministic. These include, for example, the `htmlspecialchars()` function. Its third parameter, `$encoding`, if not specified, defaults to the value of the configuration option `ini_get('default_charset')`. Therefore, it is recommended to always specify this parameter to avoid possible unpredictable behavior of the function. Nette consistently does this. +However, there are functions in PHP that are not deterministic. These include, for example, the `htmlspecialchars()` function. Its third parameter, `$encoding`, if omitted, defaults to the value of the `default_charset` configuration option (`ini_get('default_charset')`). Therefore, it's recommended to always specify this parameter to prevent potential unpredictable behavior. Nette consistently does this. -Some functions, such as `strtolower()`, `strtoupper()`, and the like, have had non-deterministic behavior in the recent past and have depended on the `setlocale()` setting. This caused many complications, most often when working with the Turkish language. -This is because the Turkish language distinguishes between upper and lower case `I` with and without a dot. So `strtolower('I')` returned the `ı` character and `strtoupper('i')` returned the `İ` character , which led to applications causing a number of mysterious errors. -However, this problem was fixed in PHP version 8.2 and the functions are no longer locale dependent. +Some functions, like `strtolower()` and `strtoupper()`, exhibited non-deterministic behavior in the recent past, depending on the locale setting (`setlocale()`). This caused many complications, most often when working with the Turkish language. This is because Turkish distinguishes between dotted and dotless 'I' in both lowercase and uppercase. Consequently, `strtolower('I')` returned `ı` (dotless lowercase i), and `strtoupper('i')` returned `İ` (dotted uppercase I), leading to numerous mysterious application errors. However, this problem was fixed in PHP version 8.2 and the functions are no longer locale dependent. -This is a nice example of how global state has plagued thousands of developers around the world. The solution was to replace it with dependency injection. +This serves as a good example of how global state (locale setting) troubled thousands of developers worldwide. The eventual solution involved making the functions locale-independent, effectively removing the hidden dependency. When Is It Possible to Use Global State? ---------------------------------------- -There are certain specific situations where it is possible to use global state. For example, when debugging code and you need to dump the value of a variable or measure the duration of a specific part of the program. In such cases, which concern temporary actions that will be later removed from the code, it is legitimate to use a globally available dumper or stopwatch. These tools are not part of the code design. +There are specific, limited situations where using global state might be acceptable. For example, during debugging, when you need to dump a variable's value or measure the execution time of a specific code segment. In these cases, involving temporary actions that will later be removed from the code, using a globally accessible dumper or timer can be legitimate. These tools are not part of the application's core design. -Another example is the functions for working with regular expressions `preg_*`, which internally store compiled regular expressions in a static cache in memory. When you call the same regular expression multiple times in different parts of the code, it is compiled only once. The cache saves performance and is also completely invisible to the user, so such usage can be considered legitimate. +Another example involves PHP's regular expression functions (`preg_*`), which internally cache compiled regular expressions in static memory. When you call functions with the same regular expression multiple times across your code, the expression is compiled only once. This caching improves performance and is entirely invisible to the user, making this use of internal static state generally acceptable. Summary ------- -We've shown why it makes sense +We have discussed why it makes sense to: -1) Remove all static variables from the code -2) Declare dependencies -3) And use dependency injection +1) Eliminate all mutable static properties (global state) from your code +2) Explicitly declare dependencies +3) And utilize dependency injection -When contemplating code design, keep in mind that each `static $foo` represents a problem. In order for your code to be a DI-respecting environment, it is essential to completely eradicate global state and replace it with dependency injection. +When designing your code, remember that every mutable `static $foo` is a potential source of problems. To create a DI-friendly environment, it's crucial to completely eliminate global state and replace it with dependency injection. -During this process, you may find that you need to split a class because it has more than one responsibility. Don't worry about it; strive for the principle of one responsibility. +During this process, you might discover the need to split classes that have multiple responsibilities. Don't hesitate to do so; strive for the Single Responsibility Principle. -*I would like to thank Miško Hevery, whose articles such as [Flaw: Brittle Global State & Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/] form the basis of this chapter.* +*I would like to thank Miško Hevery, whose articles such as [Flaw: Brittle Global State & Singletons |https://web.archive.org/web/20230321084133/http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/] form the basis of this chapter.* diff --git a/dependency-injection/en/introduction.texy b/dependency-injection/en/introduction.texy index 3179e5fdd2..ae1db18bac 100644 --- a/dependency-injection/en/introduction.texy +++ b/dependency-injection/en/introduction.texy @@ -2,17 +2,17 @@ What is Dependency Injection? ***************************** .[perex] -This chapter will introduce you to the basic programming practices that you should follow when writing any application. These are the fundamentals needed for writing clean, understandable, and maintainable code. +This chapter introduces the basic programming practices you should follow when writing any application. These are the fundamentals necessary for writing clean, understandable, and maintainable code. -If you learn and follow these rules, Nette will be there for you every step of the way. It will handle routine tasks for you and provide maximum comfort, so you can focus on the logic itself. +If you adopt and follow these rules, Nette will support you at every step. It will handle routine tasks for you and provide maximum convenience, allowing you to focus on the logic itself. -The principles we will show here are quite simple. You don't have to worry about anything. +The principles we will demonstrate here are quite simple. There's nothing to fear. Remember Your First Program? ---------------------------- -We don't know what language you wrote it in, but if it was PHP, it might have looked something like this: +We don't know what language you wrote it in, but if it were PHP, it probably looked something like this: ```php function addition(float $a, float $b): float @@ -23,33 +23,33 @@ function addition(float $a, float $b): float echo addition(23, 1); // prints 24 ``` -A few trivial lines of code, but so many key concepts hidden in them. That there are variables. That code is broken down into smaller units, which are functions, for example. That we pass them input arguments and they return results. All that's missing are conditions and loops. +A few trivial lines of code, yet they hide so many key concepts. That variables exist. That code is divided into smaller units, such as functions. That we pass input arguments to them and they return results. Only conditions and loops are missing. -The fact that a function takes input data and returns a result is a perfectly understandable concept, which is also used in other fields, such as mathematics. +The fact that we pass input data to a function and it returns a result is a perfectly understandable concept, used in other fields as well, like mathematics. -A function has its signature, which consists of its name, a list of parameters and their types, and finally the type of the return value. As users, we are interested in the signature, and we usually don't need to know anything about the internal implementation. +A function has its signature, consisting of its name, a list of parameters and their types, and finally, the return value type. As users, we are interested in the signature; we usually don't need to know anything about the internal implementation. -Now imagine that the function signature looked like this: +Now imagine the function signature looked like this: ```php function addition(float $x): float ``` -An addition with one parameter? That's strange... What about this? +Summation with one parameter? That's strange… How about this? ```php function addition(): float ``` -Now that's really weird, right? How is the function used? +That's really strange now, isn't it? How is the function used? ```php -echo addition(); // what does it prints? +echo addition(); // what will it print? ``` -Looking at such code, we would be confused. Not only would a beginner not understand it, but even an experienced programmer would not understand such code. +Looking at such code, we would be confused. Not only would a beginner not understand it, but even a skilled programmer wouldn't understand such code. -Are you wondering what such a function would actually look like inside? Where would it get the summands? It would probably *somehow* get them by itself, perhaps like this: +Are you wondering what such a function would actually look like inside? Where would it get the numbers to add? It would probably procure them *somehow* itself, perhaps like this: ```php function addition(): float @@ -60,24 +60,24 @@ function addition(): float } ``` -It turns out that there are hidden bindings to other functions (or static methods) in the body of the function, and to find out where the addends actually come from, we have to dig further. +In the function body, we discovered hidden dependencies on other global functions or static methods. To find out where the numbers actually come from, we need to investigate further. Not This Way! ------------- -The design we just showed is the essence of many negative features: +The design we just showed is the essence of many negative characteristics: -- the function signature pretended that it didn't need the summands, which confused us -- we have no idea how to make the function calculate with two other numbers -- we had to look at the code to find out where the summands came from -- we found hidden dependencies -- a full understanding requires examining these dependencies as well +- The function signature pretended it didn't need the numbers to add, which confused us. +- We have no idea how to make the function sum two different numbers. +- We had to look into the code to find out where it gets the numbers. +- We discovered hidden dependencies. +- Fully understanding requires examining these dependencies as well. -And is it even the job of the addition function to procure inputs? Of course it isn't. Its responsibility is only to add. +And is it even the task of the summation function to obtain the inputs? Of course not. Its responsibility is only the summation itself. -We don't want to encounter such code, and we certainly don't want to write it. The remedy is simple: go back to basics and just use parameters: +We don't want to encounter such code, and we certainly don't want to write it. The fix is simple: return to the basics and simply use parameters: ```php @@ -91,40 +91,40 @@ function addition(float $a, float $b): float Rule #1: Let It Be Passed to You -------------------------------- -The most important rule is: **all data that functions or classes need must be passed to them**. +The most important rule is: **all the data that functions or classes require must be provided to them**. -Instead of inventing hidden ways for them to access the data themselves, simply pass the parameters. You will save time that would be spent inventing hidden paths that certainly won't improve your code. +Instead of inventing hidden ways for them to obtain the data, simply provide the parameters. You will save time spent inventing hidden pathways that definitely won't improve your code. -If you always and everywhere follow this rule, you are on your way to code without hidden dependencies. To code that is understandable not only to the author but also to anyone who reads it afterward. Where everything is understandable from the signatures of functions and classes, and there is no need to search for hidden secrets in the implementation. +If you always follow this rule everywhere, you are on the path towards code without hidden dependencies. Towards code that is understandable not only to the author but also to anyone who reads it later. Where everything is understandable from the signatures of functions and classes, and there's no need to search for hidden details in the implementation. -This technique is professionally called **dependency injection**. And those data are called **dependencies**. It's just ordinary parameter passing, nothing more. +This technique is professionally called **Dependency Injection**. And the data are called **dependencies.** It's just plain parameter passing, nothing more. .[note] -Please do not confuse dependency injection, which is a design pattern, with a "dependency injection container", which is a tool, something diametrically different. We will deal with containers later. +Please do not confuse Dependency Injection, which is a design pattern, with a "Dependency Injection container," which is a tool, something fundamentally different. We will discuss containers later. From Functions to Classes ------------------------- -And how are classes related? A class is a more complex unit than a simple function, but rule #1 applies entirely here as well. There are just [more ways to pass arguments |passing-dependencies]. For example, quite similar to the case of a function: +And how does this apply to classes? A class is a more complex entity than a simple function, but Rule #1 applies fully here as well. There are just [more ways to pass arguments |passing-dependencies]. For example, quite similarly to the function case: ```php class Math { - public function addition(float $a, float $b): float + public function sum(float $a, float $b): float { return $a + $b; } } $math = new Math; -echo $math->addition(23, 1); // 24 +echo $math->sum(23, 1); // 24 ``` -Or through other methods, or directly through the constructor: +Or using other methods, or directly the constructor: ```php -class Addition +class Sum { public function __construct( private float $a, @@ -139,19 +139,19 @@ class Addition } -$addition = new Addition(23, 1); -echo $addition->calculate(); // 24 +$sum = new Sum(23, 1); +echo $sum->calculate(); // 24 ``` -Both examples are completely in compliance with dependency injection. +Both examples are fully compliant with Dependency Injection. Real-Life Examples ------------------ -In the real world, you won't be writing classes for adding numbers. Let's move on to practical examples. +In the real world, you won't be writing classes for summing numbers. Let's move on to practical examples. -Let's have a `Article` class representing a blog post: +Let's have an `Article` class representing a blog article: ```php class Article @@ -176,9 +176,9 @@ $article->content = 'Every year millions of people in ...'; $article->save(); ``` -The `save()` method will save the article to a database table. Implementing it using [Nette Database |database:] will be a piece of cake, were it not for one hitch: where does `Article` get the database connection, i.e., an object of class `Nette\Database\Connection`? +The `save()` method will save the article to a database table. Implementing it using [Nette Database |database:] would be straightforward, if not for one catch: where does `Article` get the database connection, i.e., an object of the `Nette\Database\Connection` class? -It seems we have plenty of options. It can take it from a static variable somewhere. Or inherit from a class that provides a database connection. Or take advantage of a [singleton |global-state#Singleton]. Or use so-called facades, which are used in Laravel: +It seems we have many options. It could take it from a static variable. Or by inheriting from a class that provides the database connection. Or use a [singleton |global-state#Singleton]. Or so-called facades, as used in Laravel: ```php use Illuminate\Support\Facades\DB; @@ -199,17 +199,17 @@ class Article } ``` -Great, we've solved the problem. +Great, we solved the problem. -Or have we? +Or did we? -Let's recall [#rule #1: Let It Be Passed to You]: all the dependencies the class needs must be passed to it. Because if we break the rule, we have embarked on a path to dirty code full of hidden dependencies, incomprehensibility, and the result will be an application that will be painful to maintain and develop. +Let's recall [#Rule #1: Let It Be Passed to You]: all dependencies that the class needs must be passed to it. Because if we break the rule, we've embarked on the path to messy code full of hidden dependencies, lack of clarity, and the result will be an application that is a challenge to maintain and develop. -The user of the `Article` class has no idea where the `save()` method stores the article. In a database table? Which one, production or testing? And how can it be changed? +The user of the `Article` class has no idea where the `save()` method stores the article. In a database table? Which one, the production or test database? And how can it be changed? -The user has to look at how the `save()` method is implemented, and finds the use of the `DB::insert()` method. So, he has to search further to find out how this method obtains a database connection. And hidden dependencies can form quite a long chain. +The user has to look at how the `save()` method is implemented and finds the use of the `DB::insert()` method. So, they must investigate further how this method obtains the database connection. And hidden dependencies can form a rather long chain. -In clean and well-designed code, there are never any hidden dependencies, Laravel facades, or static variables. In clean and well-designed code, arguments are passed: +In clean and well-designed code, there are never hidden dependencies, Laravel facades, or static variables. In clean and well-designed code, arguments are provided: ```php class Article @@ -224,7 +224,7 @@ class Article } ``` -An even more practical approach, as we will see later, will be through the constructor: +Even more practical, as we will see later, is using the constructor: ```php class Article @@ -245,9 +245,9 @@ class Article ``` .[note] -If you are an experienced programmer, you might think that `Article` should not have a `save()` method at all; it should represent a purely data component, and a separate repository should take care of saving. That makes sense. But that would take us far beyond the scope of the topic, which is dependency injection, and the effort to provide simple examples. +If you are an experienced programmer, you might think that `Article` shouldn't have a `save()` method at all; it should represent purely a data structure, and a separate repository should handle the saving. That makes sense. But that would take us well beyond the scope of the topic, which is Dependency Injection, and the goal of providing simple examples. -If you write a class that requires, for example, a database for its operation, don't invent where to get it from, but have it passed. Either as a parameter of the constructor or another method. Admit dependencies. Admit them in the API of your class. You will get understandable and predictable code. +If you write a class that requires, for example, a database for its operation, don't invent where to get it from, but have it passed to you. Perhaps as a parameter of the constructor or another method. Acknowledge dependencies. Acknowledge them in your class's API. You will get understandable and predictable code. And what about this class, which logs error messages: @@ -262,21 +262,21 @@ class Logger } ``` -What do you think, did we follow [#rule #1: Let It Be Passed to You]? +What do you think, did we follow [#Rule #1: Let It Be Passed to You]? -We didn't. +We did not. -The key information, i.e., the directory with the log file, is *obtained* by the class itself from the constant. +The key piece of information, the directory containing the log file, is *obtained by the class itself* from a constant. -Look at the example of usage: +Look at the usage example: ```php $logger = new Logger; -$logger->log('The temperature is 23 °C'); -$logger->log('The temperature is 10 °C'); +$logger->log('Temperature is 23 °C'); +$logger->log('Temperature is 10 °C'); ``` -Without knowing the implementation, could you answer the question of where the messages are written? Would you guess that the existence of the `LOG_DIR` constant is necessary for its functioning? And could you create a second instance that would write to a different location? Certainly not. +Without knowing the implementation, could you answer where the messages are being written? Would it occur to you that the existence of the `LOG_DIR` constant is required for its operation? And could you create a second instance that would write elsewhere? Certainly not. Let's fix the class: @@ -295,24 +295,24 @@ class Logger } ``` -The class is now much more understandable, configurable, and therefore more useful. +The class is now much more understandable, configurable, and thus more useful. ```php $logger = new Logger('/path/to/log.txt'); -$logger->log('The temperature is 15 °C'); +$logger->log('Temperature is 15 °C'); ``` But I Don’t Care! ----------------- -*"When I create an Article object and call save(), I don't want to deal with the database; I just want it to be saved in the one I have set in the configuration."* +*"When I create an Article object and call save(), I don't want to deal with the database; I just want it to be saved in the one I have configured."* *"When I use Logger, I just want the message to be written, and I don't want to deal with where. Let the global settings be used."* These are valid points. -As an example, let's look at a class that sends newsletters and logs how it went: +As an example, let's show a class that distributes newsletters and logs the outcome: ```php class NewsletterDistributor @@ -325,24 +325,24 @@ class NewsletterDistributor $logger->log('Emails have been sent out'); } catch (Exception $e) { - $logger->log('An error occurred during the sending'); + $logger->log('An error occurred during sending'); throw $e; } } } ``` -The improved `Logger`, which no longer uses the `LOG_DIR` constant, requires specifying the file path in the constructor. How to solve this? The `NewsletterDistributor` class doesn't care where the messages are written; it just wants to write them. +The improved `Logger`, which no longer uses the `LOG_DIR` constant, requires the file path in the constructor. How can this be resolved? The `NewsletterDistributor` class is not concerned with where messages are written; it simply wants to log them. -The solution is again [#rule #1: Let It Be Passed to You]: pass all the data that the class needs. +The solution is again [#Rule #1: Let It Be Passed to You]: we pass all the data the class needs. -So does that mean we pass the path to the log through the constructor, which we then use when creating the `Logger` object? +So, does this mean we pass the log path through the constructor, which we then use when creating the `Logger` object? ```php class NewsletterDistributor { public function __construct( - private string $file, // ⛔ NOT THIS WAY! + private string $file, // ⛔ NOT LIKE THIS! ) { } @@ -351,7 +351,7 @@ class NewsletterDistributor $logger = new Logger($this->file); ``` -No, not like this! The path doesn't belong among the data that the `NewsletterDistributor` class needs; in fact, the `Logger` needs it. Do you see the difference? The `NewsletterDistributor` class needs the logger itself. So that's what we'll pass: +Not like this! Because the path is **not** data that the `NewsletterDistributor` class requires; the `Logger` requires it. Do you perceive the difference? The `NewsletterDistributor` class needs the logger itself. So, we will pass the logger itself: ```php class NewsletterDistributor @@ -368,32 +368,30 @@ class NewsletterDistributor $this->logger->log('Emails have been sent out'); } catch (Exception $e) { - $this->logger->log('An error occurred during the sending'); + $this->logger->log('An error occurred during sending'); throw $e; } } } ``` -Now it is clear from the signatures of the `NewsletterDistributor` class that logging is also part of its functionality. And the task of exchanging the logger for another, perhaps for testing, is completely trivial. -Moreover, if the constructor of the `Logger` class changes, it will not affect our class. +Now it's clear from the signature of the `NewsletterDistributor` class that logging is part of its function. And the task of substituting the logger with another, perhaps for testing, is entirely straightforward. Moreover, if the `Logger` class constructor were to change, it will have no impact on our class. Rule #2: Take What's Yours -------------------------- -Don't be misled and don't let yourself pass the dependencies of your dependencies. Just pass your own dependencies. +Don't be confused and don't accept the dependencies of your dependencies. Accept only your own dependencies. -Thanks to this, the code using other objects will be completely independent of changes in their constructors. Its API will be more truthful. And above all, it will be trivial to replace these dependencies with others. +Thanks to this, code that uses other objects will be completely independent of changes to their constructors. Its API will be more accurate. And most importantly, it will be straightforward to substitute these dependencies with others. New Family Member ----------------- -The development team decided to create a second logger that writes to the database. So we create a `DatabaseLogger` class. So we have two classes, `Logger` and `DatabaseLogger`, one writes to a file, the other to a database ... doesn't the naming seem strange to you? -Wouldn't it be better to rename `Logger` to `FileLogger`? Definitely yes. +The development team has decided to create a second logger, one that writes to the database. So, we create a `DatabaseLogger` class. Now we have two classes, `Logger` and `DatabaseLogger`; one writes to a file, the other to the database... doesn't the naming seem somewhat odd? Wouldn't it be better to rename `Logger` to `FileLogger`? Certainly. -But let's do it smartly. We create an interface under the original name: +But let's do it cleverly. We create an interface using the original name: ```php interface Logger @@ -402,7 +400,7 @@ interface Logger } ``` -... which both loggers will implement: +… which both loggers will implement: ```php class FileLogger implements Logger @@ -412,17 +410,17 @@ class DatabaseLogger implements Logger // ... ``` -And because of this, there will be no need to change anything in the rest of the code where the logger is used. For example, the constructor of the `NewsletterDistributor` class will still be satisfied with requiring `Logger` as a parameter. And it will be up to us which instance we pass. +And thanks to this, there will be no need to modify anything in the rest of the code where the logger is utilized. For example, the `NewsletterDistributor` class constructor will still be content requiring `Logger` as a parameter. And it will be up to us which instance we provide to it. -**That's why we never add the `Interface` suffix or `I` prefix to interface names.** Otherwise, it would not be possible to develop the code so nicely. +**That's why we never add the `Interface` suffix or the `I` prefix to interface names.** Otherwise, it wouldn't be possible to extend the code so elegantly. Houston, We Have a Problem -------------------------- -While we can get by with a single instance of the logger, whether file-based or database-based, throughout the entire application and simply pass it wherever something is logged, it's quite different for the `Article` class. We create its instances as needed, even multiple times. How to deal with the database dependency in its constructor? +While in the entire application we can get by with a single instance of the logger, whether file-based or database-based, and simply pass it wherever logging occurs, the situation is quite different with the `Article` class. We create its instances as required, even multiple times. How do we handle the database dependency in its constructor? -An example can be a controller that should save an article to the database after submitting a form: +An example could be a controller that should save an article to the database after submitting a form: ```php class EditController extends Controller @@ -437,30 +435,30 @@ class EditController extends Controller } ``` -A possible solution is obvious: pass the database object to the `EditController` constructor and use `$article = new Article($this->db)`. +A potential solution seems obvious: let's have the database object passed via the constructor into `EditController` and use `$article = new Article($this->db)`. -Just as in the previous case with `Logger` and the file path, this is not the right approach. The database is not a dependency of the `EditController`, but of the `Article`. Passing the database goes against [#rule #2: take what's yours]. If the `Article` class constructor changes (a new parameter is added), you will need to modify the code wherever instances are created. Ufff. +Just as in the previous case involving `Logger` and the file path, this is not the correct approach. The database is not a dependency of `EditController`, but rather of `Article`. Passing the database thus violates [#Rule #2: Take What's Yours |#Rule #2: Take What's Yours]. If the `Article` class constructor changes (a new parameter is added), you will need to modify the code in all places where instances are created. Oof. -Houston, what do you suggest? +Houston, what's your suggestion? Rule #3: Let the Factory Handle It ---------------------------------- -By eliminating hidden dependencies and passing all dependencies as arguments, we have gained more configurable and flexible classes. And therefore, we need something else to create and configure those more flexible classes for us. We will call it factories. +By eliminating hidden dependencies and passing all dependencies as arguments, we have gained more configurable and flexible classes. Therefore, we need something additional to create and configure these more flexible classes for us. We'll call these factories. -The rule of thumb is: if a class has dependencies, leave the creation of their instances to the factory. +The rule is: If a class has dependencies, delegate the creation of its instances to a factory. -Factories are a smarter replacement for the `new` operator in the world of dependency injection. +Factories are a smarter alternative to the `new` operator in the world of Dependency Injection. .[note] -Please do not confuse with the *factory method* design pattern, which describes a specific way of using factories and is not related to this topic. +Please do not confuse this with the *factory method* design pattern, which describes a specific way of utilizing factories and is unrelated to this topic. Factory ------- -A factory is a method or class that creates and configures objects. We will name the class producing `Article` as `ArticleFactory`, and it could look like this: +A factory is a method or class that creates and configures objects. We will call the class that produces `Article` `ArticleFactory`, and it might look like this: ```php class ArticleFactory @@ -489,7 +487,7 @@ class EditController extends Controller public function formSubmitted($data) { - // let the factory create an object + // let the factory create the object $article = $this->articleFactory->create(); $article->title = $data->title; $article->content = $data->content; @@ -498,11 +496,11 @@ class EditController extends Controller } ``` -At this point, if the signature of the `Article` class constructor changes, the only part of the code that needs to react is the `ArticleFactory` itself. All other code working with `Article` objects, such as the `EditController`, will not be affected. +At this point, if the signature of the `Article` class constructor changes, the only part of the code that needs to respond is the `ArticleFactory` itself. All other code that interacts with `Article` objects, such as `EditController`, will remain unaffected. -You might be wondering if we have actually made things better. The amount of code has increased, and it all starts to look suspiciously complicated. +You might be scratching your head now, wondering if we've actually improved the situation. The amount of code has grown, and the whole thing is starting to look suspiciously complex. -Don't worry, soon we will get to the Nette DI container. And it has several tricks up its sleeve, which will greatly simplify building applications using dependency injection. For example, instead of the `ArticleFactory` class, you will only need to [write a simple interface |factory]: +Don't worry, we'll get to the Nette DI container soon. And it has several tricks up its sleeve that will greatly simplify the construction of applications using Dependency Injection. For example, instead of the `ArticleFactory` class, it will suffice to [write just an interface |factory]: ```php interface ArticleFactory @@ -511,18 +509,18 @@ interface ArticleFactory } ``` -But we're getting ahead of ourselves; please be patient :-) +But we are getting ahead of ourselves, stay tuned :-) Summary ------- -At the beginning of this chapter, we promised to show you a process for designing clean code. All it takes is for classes to: +At the beginning of this chapter, we promised to show a process for designing clean code. Simply ensure that classes: -- [pass the dependencies they need |#Rule #1: Let It Be Passed to You] -- [conversely, not pass what they don't directly need |#Rule #2: Take What's Yours] -- [and that objects with dependencies are best created in factories |#Rule #3: Let the Factory Handle it] +1) [are passed the dependencies they need |#Rule #1: Let It Be Passed to You] +2) [and conversely, are not passed what they don't directly need |#Rule #2: Take What's Yours] +3) [and that objects with dependencies are best created in factories |#Rule #3: Let the Factory Handle It] -At first glance, these three rules may not seem to have far-reaching consequences, but they lead to a radically different perspective on code design. Is it worth it? Developers who have abandoned old habits and started consistently using dependency injection consider this step a crucial moment in their professional lives. It has opened the world of clear and maintainable applications for them. +It might not seem apparent at first glance, but these three rules have far-reaching consequences. They lead to a radically different perspective on code design. Is it worthwhile? Programmers who have abandoned old habits and started consistently using Dependency Injection consider this step a defining moment in their professional careers. It opened up a world of clear and maintainable applications for them. -But what if the code does not consistently use dependency injection? What if it relies on static methods or singletons? Does that cause any problems? [Yes, it does, and very fundamental ones |global-state]. +But what if the code doesn't consistently use Dependency Injection? What if it's built upon static methods or singletons? Does this lead to problems? [Yes, it does, and very significant ones |global-state]. diff --git a/dependency-injection/en/nette-container.texy b/dependency-injection/en/nette-container.texy index 89331d5a46..f651345142 100644 --- a/dependency-injection/en/nette-container.texy +++ b/dependency-injection/en/nette-container.texy @@ -2,9 +2,9 @@ Nette DI Container ****************** .[perex] -Nette DI is one of the most interesting Nette libraries. It can generate and automatically update compiled DI containers that are extremely fast and amazingly easy to configure. +Nette DI is one of Nette's most interesting libraries. It can generate and automatically update compiled DI containers that are extremely fast and remarkably easy to configure. -The services to be created by a DI container are usually defined using configuration files in [NEON format|neon:format]. The container we manually created in [previous section|container] would be written as follows: +The form of services that the DI container should create is usually defined using configuration files in [NEON format|neon:format]. The container we manually created in the [previous chapter|container] would be written like this: ```neon parameters: @@ -19,16 +19,15 @@ services: - UserController ``` -The notation is really brief. +The syntax is very concise. -All dependencies declared in the constructors of the `ArticleFactory` and `UserController` classes are found and passed by Nette DI itself thanks to the so-called [autowiring], so there is no need to specify anything in the configuration file. -So even if the parameters change, you don't need to change anything in the configuration. Nette will automatically regenerate the container. You can concentrate there purely on application development. +All dependencies declared in the constructors of the `ArticleFactory` and `UserController` classes are discovered and passed automatically by Nette DI thanks to so-called [autowiring|autowiring], so there's no need to specify anything in the configuration file. Thus, even if parameters change, you don't need to change anything in the configuration. Nette automatically regenerates the container. You can focus purely on application development. -If you want to pass dependencies using setters, use the [setup|services#setup] section to do so. +If we want to pass dependencies using setters, we use the [setup |services#Setup] section for this. -Nette DI will directly generate the PHP code for the container. The result is thus a `.php` file that you can open and study. This allows you to see exactly how the container works. You can also debug it in the IDE and step through it. And most importantly: the generated PHP is extremely fast. +Nette DI generates PHP code for the container directly. The result is thus a `.php` file that you can open and examine. This allows you to see exactly how the container functions. You can also debug it in your IDE and step through its execution. And most importantly: the generated PHP code is extremely fast. -Nette DI can also generate [factory] code based on the supplied interface. Therefore, instead of the `ArticleFactory` class, we only need to create an interface in the application: +Nette DI can also generate [factory|factory] code based on a provided interface. Therefore, instead of the `ArticleFactory` class, we only need to create an interface in the application: ```php interface ArticleFactory @@ -43,7 +42,7 @@ You can find the full example [on GitHub|https://github.com/nette-examples/di-ex Standalone Use -------------- -Utilization the Nette DI library in an application is very easy. First we install it with Composer (because downloading zip files is so outdated): +Integrating the Nette DI library into an application is very easy. First, we install it using Composer (because downloading zip files is so outdated): ```shell composer require nette/di @@ -59,24 +58,23 @@ $class = $loader->load(function ($compiler) { $container = new $class; ``` -The container is generated only once, its code is written to the cache (the `__DIR__ . '/temp'` directory) and on subsequent requests it is only read from there. +The container is generated only once, its code is written to the cache (the `__DIR__ . '/temp'` directory), and on subsequent requests, it is only loaded from there. The `getService()` or `getByType()` methods are used to create and retrieve services. This is how we create the `UserController` object: ```php -$database = $container->getByType(UserController::class); -$database->query('...'); +$controller = $container->getByType(UserController::class); +$controller->someMethod(); ``` -During development, it is useful to enable auto-refresh mode, where the container is automatically regenerated if any class or configuration file is changed. Just provide `true` as the second argument in the `ContainerLoader` constructor. +During development, it's useful to activate auto-refresh mode, where the container automatically regenerates if any class or configuration file is modified. Just provide `true` as the second argument in the `ContainerLoader` constructor. ```php $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp', true); ``` -Using It with the Nette Framework ---------------------------------- +Using with Nette Framework +-------------------------- -As we have shown, the use of Nette DI is not limited to applications written in the Nette Framework, you can deploy it anywhere with just 3 lines of code. -However, if you are developing applications in the Nette Framework, the configuration and creation of the container is handled by [Bootstrap|application:bootstrap#toc-di-container-configuration]. +As we have shown, the use of Nette DI is not restricted to applications built with Nette Framework; you can integrate it anywhere with just three lines of code. However, if you are developing applications using the Nette Framework, the configuration and creation of the container are handled by [Bootstrap |application:bootstrapping#DI Container Configuration]. diff --git a/dependency-injection/en/passing-dependencies.texy b/dependency-injection/en/passing-dependencies.texy index 43912611d4..a35fda44c5 100644 --- a/dependency-injection/en/passing-dependencies.texy +++ b/dependency-injection/en/passing-dependencies.texy @@ -3,22 +3,22 @@ Passing Dependencies <div class=perex> -Arguments, or "dependencies" in DI terminology, can be passed to classes in the following main ways: +Arguments, or 'dependencies' in DI terminology, can be passed to classes in the following main ways: -* passing by constructor -* passing by method (called a setter) -* by setting a property -* by method, annotation or attribute *inject* +* Constructor injection +* Method injection (so-called setter injection) +* Property injection +* Using the `inject` method, annotation, or attribute </div> -We will now illustrate the different variants with concrete examples. +Let's demonstrate each variant with specific examples. Constructor Injection ===================== -Dependencies are passed as arguments to the constructor when the object is created: +Dependencies are provided as constructor arguments at the time the object is instantiated: ```php class MyClass @@ -34,9 +34,9 @@ class MyClass $obj = new MyClass($cache); ``` -This form is useful for mandatory dependencies that the class absolutely needs to function, as without them the instance cannot be created. +This approach is suitable for mandatory dependencies that the class absolutely requires for its operation, because without them, the instance cannot be created. -Since PHP 8.0, we can use a shorter form of notation ([constructor property promotion |https://blog.nette.org/en/php-8-0-complete-overview-of-news#toc-constructor-property-promotion]) that is functionally equivalent: +Since PHP 8.0, we can use a shorter notation ([constructor property promotion |https://blog.nette.org/en/php-8-0-complete-overview-of-news#toc-constructor-property-promotion]), which is functionally equivalent: ```php // PHP 8.0 @@ -49,7 +49,7 @@ class MyClass } ``` -As of PHP 8.1, a property can be marked with a flag `readonly` that declares that the contents of the property will not change: +Since PHP 8.1, a property can be marked with the `readonly` flag, which declares that the property's value will not change after initialization: ```php // PHP 8.1 @@ -62,13 +62,13 @@ class MyClass } ``` -DI container passes dependencies to the constructor automatically using [autowiring]. Arguments that cannot be passed in this way (e.g. strings, numbers, booleans) [write in configuration |services#Arguments]. +The DI container passes dependencies to the constructor automatically using [autowiring |autowiring]. Arguments that cannot be provided this way (e.g., strings, numbers, booleans) [are specified in the configuration |services#Arguments]. Constructor Hell ---------------- -The term *constructor hell* refers to a situation where a child inherits from a parent class whose constructor requires dependencies, and the child requires dependencies too. It must also take over and pass on the parent's dependencies: +The term *constructor hell* describes a situation where a child class inherits from a parent class whose constructor requires dependencies, and the child class also requires dependencies. It must then accept and pass on the parent's dependencies as well: ```php abstract class BaseClass @@ -94,11 +94,11 @@ final class MyClass extends BaseClass } ``` -The problem occurs when we want to change the constructor of the `BaseClass` class, for example when a new dependency is added. Then we have to modify all the constructors of the children as well. Which makes such a modification hell. +The problem arises when we want to change the constructor of the `BaseClass`, for example, when a new dependency is added. Then, it becomes necessary to modify all the constructors of the child classes as well. Which turns such a modification into hell. -How to prevent this? The solution is to **prioritize composition over inheritance**. +How can this be prevented? The solution is to **prefer [composition over inheritance |faq#Why composition is preferred over inheritance]**. -So let's design the code differently. We'll avoid abstract `Base*` classes. Instead of `MyClass` getting some functionality by inheriting from `BaseClass`, it will have that functionality passed as a dependency: +So, we design the code differently. We will avoid [abstract |nette:introduction-to-object-oriented-programming#Abstract Classes] `Base*` classes. Instead of `MyClass` acquiring certain functionality by inheriting from `BaseClass`, it will have this functionality passed as a dependency: ```php final class SomeFunctionality @@ -128,7 +128,7 @@ final class MyClass Setter Injection ================ -Dependencies are passed by calling a method that stores them in a private properties. The usual naming convention for these methods is of the form `set*()`, which is why they are called setters, but of course they can be called anything else. +Dependencies are provided by calling a method that stores them in a private property. The common naming convention for these methods is the `set*()` pattern, hence they are called setters, but they can, of course, be named differently. ```php class MyClass @@ -145,9 +145,9 @@ $obj = new MyClass; $obj->setCache($cache); ``` -This method is useful for optional dependencies that are not necessary for the class function, since it is not guaranteed that the object will actually receive them (i.e., that the user will call the method). +This approach is suitable for optional dependencies that are not essential for the class's operation, as it's not guaranteed that the object will actually receive the dependency (i.e., that the caller will invoke the method). -At the same time, this method allows the setter to be called repeatedly to change the dependency. If this is not desirable, add a check to the method, or as of PHP 8.1, mark the property `$cache` with the `readonly` flag. +At the same time, this method allows the setter to be called repeatedly to change the dependency. If this is undesirable, add a check within the method, or since PHP 8.1, mark the `$cache` property with the `readonly` flag. ```php class MyClass @@ -164,12 +164,11 @@ class MyClass } ``` -The setter call is defined in the DI container configuration in [section setup |services#Setup]. Also here the automatic passing of dependencies is used by autowiring: +The setter call is defined in the DI container configuration in the [setup key |services#Setup]. Here too, automatic dependency provision via autowiring is used: ```neon services: - - - create: MyClass + - create: MyClass setup: - setCache ``` @@ -178,7 +177,7 @@ services: Property Injection ================== -Dependencies are passed directly to the property: +Dependencies are provided by writing directly to a member property: ```php class MyClass @@ -190,14 +189,13 @@ $obj = new MyClass; $obj->cache = $cache; ``` -This method is considered inappropriate because the property must be declared as `public`. Hence, we have no control over whether the passed dependency will actually be of the specified type (this was true before PHP 7.4) and we lose the ability to react to the newly assigned dependency with our own code, for example to prevent subsequent changes. At the same time, the property becomes part of the public interface of the class, which may not be desirable. +This method is considered inappropriate because the member property must be declared as `public`. Consequently, we lose control over ensuring the passed dependency is actually of the required type (this was particularly true before PHP 7.4 type hinting for properties), and we lose the ability to react to a newly assigned dependency with custom logic, for example, to prevent subsequent modification. At the same time, the property becomes part of the class's public API, which might not be intended. -The setting of the variable is defined in the DI container configuration in [section setup |services#Setup]: +Property assignment is defined in the DI container configuration in the [setup section |services#Setup]: ```neon services: - - - create: MyClass + - create: MyClass setup: - $cache = @\Cache ``` @@ -206,12 +204,12 @@ services: Inject ====== -While the previous three methods are generally valid in all object-oriented languages, injecting by method, annotation or *inject* attribute is specific to Nette presenters. They are discussed in [a separate chapter |best-practices:inject-method-attribute]. +While the previous three approaches apply generally in all object-oriented languages, injection via method, annotation, or the `inject` attribute is specific to Nette presenters. They are discussed in a [separate chapter |best-practices:inject-method-attribute]. -Which Way to Choose? -==================== +Which Method to Choose? +======================= -- constructor is suitable for mandatory dependencies that the class needs to function -- the setter, on the other hand, is suitable for optional dependencies, or dependencies that can be changed -- public variables are not recommended +- The constructor is suitable for mandatory dependencies that the class absolutely requires for its operation. +- The setter, conversely, is suitable for optional dependencies, or dependencies that might need to be changed later. +- Public properties are generally not recommended. diff --git a/dependency-injection/en/services.texy b/dependency-injection/en/services.texy index b0ba6e9eaa..d724a229cf 100644 --- a/dependency-injection/en/services.texy +++ b/dependency-injection/en/services.texy @@ -2,32 +2,32 @@ Service Definitions ******************* .[perex] -Configuration is where we place the definitions of custom services. This is done in section `services`. +Configuration is where we instruct the DI container how to create individual services and how to connect them with their dependencies. Nette offers a very clear and elegant way to achieve this. -For example, this is how we create a service named `database`, which will be an instance of class `PDO`: +The `services` section in the NEON configuration file is where we define our own services and their configurations. Let's look at a simple example defining a service named `database`, which represents an instance of the `PDO` class: ```neon services: database: PDO('sqlite::memory:') ``` -The naming of services is used to allow us to [reference|#Referencing Services] them. If a service is not referenced, there is no need to name it. So we just use a bullet point instead of a name: +The configuration above results in the following factory method in the [DI container|container]: -```neon -services: - - PDO('sqlite::memory:') # anonymous service +```php +public function createServiceDatabase(): PDO +{ + return new PDO('sqlite::memory:'); +} ``` -A one-line entry can be broken up into multiple lines to allow additional keys to be added, such as [#setup]. The alias for the `create:` key is `factory:`. +Service names enable referencing them in other parts of the configuration file, using the `@serviceName` format. If there's no need to assign a name to the service, we can simply use a bullet point (`-`): ```neon services: - database: - create: PDO('sqlite::memory:') - setup: ... + - PDO('sqlite::memory:') ``` -We then retrieve the service from the DI container using the method `getService()` by name, or better yet, the method `getByType()` by type: +To retrieve a service from the DI container, we can use the `getService()` method with the service name as a parameter, or the `getByType()` method with the service type: ```php $database = $container->getService('database'); @@ -35,26 +35,28 @@ $database = $container->getByType(PDO::class); ``` -Creating a Service -================== +Service Creation +================ -Most often, we create a service by simply creating an instance of a class: +Usually, we create a service simply by instantiating a specific class. For example: ```neon services: database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -Which will generate a factory method in [DI container|container]: +If we need to expand the configuration with additional keys, the definition can be split across multiple lines: -```php -public function createServiceDatabase(): PDO -{ - return new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'secret'); -} +```neon +services: + database: + create: PDO('sqlite::memory:') + setup: ... ``` -Alternatively, a key `arguments` can be used to pass [arguments|#Arguments]: +The `create` key has an alias `factory`; both variants are commonly used. However, we recommend using `create`. + +Arguments for the constructor or the factory method can alternatively be specified using the `arguments` key: ```neon services: @@ -63,294 +65,303 @@ services: arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret] ``` -A static method can also create a service: +Services don't necessarily have to be created by simple class instantiation; they can also be the result of invoking static methods or methods of other services: ```neon services: - database: My\Database::create(root, secret) + database: DatabaseFactory::create() + router: @routerFactory::create() ``` -Corresponds to PHP code: +Note that for simplicity, `::` is used instead of `->`, see [#Expression Language]. These factory methods will be generated: ```php public function createServiceDatabase(): PDO { - return My\Database::create('root', 'secret'); + return DatabaseFactory::create(); +} + +public function createServiceRouter(): RouteList +{ + return $this->getService('routerFactory')->create(); } ``` -A static method `My\Database::create()` is assumed to have a defined return value that the DI container needs to know. If it does not have it, we write the type to the configuration: +The DI container needs to know the type of the service being created. If we create a service using a method that lacks a specified return type, we must explicitly declare this type in the configuration: ```neon services: database: - create: My\Database::create(root, secret) + create: DatabaseFactory::create() type: PDO ``` -Nette DI gives you extremely powerful expression facilities to write almost anything. For example, to [refer|#Referencing Services] to another service and call its method. For simplicity, `::` is used instead of `->`. + +Arguments +========= + +We pass arguments to constructors and methods in a way very similar to how it's done in PHP itself: ```neon services: - routerFactory: App\Router\Factory - router: @routerFactory::create() + database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -Corresponds to PHP code: - -```php -public function createServiceRouterFactory(): App\Router\Factory -{ - return new App\Router\Factory; -} +For better readability, we can list arguments on separate lines. In this case, using commas becomes optional: -public function createServiceRouter(): Router -{ - return $this->getService('routerFactory')->create(); -} +```neon +services: + database: PDO( + 'mysql:host=127.0.0.1;dbname=test' + root + secret + ) ``` -Method calls can be chained together as in PHP: +You can also name the arguments, eliminating the need to worry about their order: ```neon services: - foo: FooFactory::build()::get() + database: PDO( + username: root + password: secret + dsn: 'mysql:host=127.0.0.1;dbname=test' + ) ``` -Corresponds to PHP code: +If you want to omit certain arguments and use their default values or have a service injected via [autowiring|autowiring], use an underscore (`_`): -```php -public function createServiceFoo() -{ - return FooFactory::build()->get(); -} +```neon +services: + foo: Foo(_, %appDir%) ``` +Arguments can include services, parameters, and much more, see [#Expression Language]. -Arguments -========= -Named parameters can also be used to pass arguments: +Setup +===== + +In the `setup` section, we define methods that should be invoked upon service creation. ```neon services: - database: PDO( - 'mysql:host=127.0.0.1;dbname=test' # positional - username: root # named - password: secret # named - ) + database: + create: PDO(%dsn%, %user%, %password%) + setup: + - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) ``` -The use of commas is optional when breaking arguments into multiple lines. +This would look like this in PHP: + +```php +public function createServiceDatabase(): PDO +{ + $service = new PDO('...', '...', '...'); + $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + return $service; +} +``` -Of course, we can also use [other services|#Referencing Services] or [parameters|configuration#parameters] as arguments: +In addition to method calls, values can also be assigned to properties. Adding elements to arrays is also supported, which requires enclosing the array access in quotes to avoid conflicts with NEON syntax: ```neon services: - - Foo(@anotherService, %appDir%) + foo: + create: Foo + setup: + - $value = 123 + - '$onClick[]' = [@bar, clickHandler] ``` -Corresponds to PHP code: +Which would look like the following in PHP code: ```php -public function createService01(): Foo +public function createServiceFoo(): Foo { - return new Foo($this->getService('anotherService'), '...'); + $service = new Foo; + $service->value = 123; + $service->onClick[] = $this->getService('bar')->clickHandler(...); + return $service; } ``` -If the first argument is [autowired|autowiring] and you want to specify the second one, omit the first one with the `_` character, for example `Foo(_, %appDir%)`. Or better yet, pass only the second argument as a named parameter, e.g. `Foo(path: %appDir%)`. - -Nette DI and the NEON format give you extremely powerful expressive facilities to write almost anything. Thus an argument can be a newly created object, you can call static methods, methods of other services, or even global functions using special notation: +However, in the setup, you can also invoke static methods or methods of other services. If you need to pass the current service itself as an argument, refer to it using `@self`: ```neon services: - analyser: My\Analyser( - FilesystemIterator(%appDir%) # create object - DateTime::createFromFormat('Y-m-d') # call static method - @anotherService # passing another service - @http.request::getRemoteAddress() # calling another service method - ::getenv(NetteMode) # call a global function - ) + foo: + create: Foo + setup: + - My\Helpers::initializeFoo(@self) + - @anotherService::setFoo(@self) ``` -Corresponds to PHP code: +Note that for simplicity, `::` is used instead of `->`, see [#Expression Language]. Such a factory method will be generated: ```php -public function createServiceAnalyser(): My\Analyser +public function createServiceFoo(): Foo { - return new My\Analyser( - new FilesystemIterator('...'), - DateTime::createFromFormat('Y-m-d'), - $this->getService('anotherService'), - $this->getService('http.request')->getRemoteAddress(), - getenv('NetteMode') - ); + $service = new Foo; + My\Helpers::initializeFoo($service); + $this->getService('anotherService')->setFoo($service); + return $service; } ``` -Special Functions ------------------ - -You can also use special functions in arguments to cast or negate values: +Expression Language +=================== -- `not(%arg%)` negation -- `bool(%arg%)` lossless cast to bool -- `int(%arg%)` lossless cast to int -- `float(%arg%)` lossless cast to float -- `string(%arg%)` lossless cast to string +Nette DI provides an exceptionally rich expression language, through which we can define almost anything. In configuration files, we can thus use [parameters |configuration#Parameters]: ```neon -services: - - Foo( - id: int(::getenv('ProjectId')) - productionMode: not(%debugMode%) - ) -``` - -Lossless rewriting differs from normal PHP rewriting, e.g. using `(int)`, in that it throws an exception for non-numeric values. +# parameter +%wwwDir% -Multiple services can be passed as arguments. An array of all services of a particular type (i.e., class or interface) is created by function `typed()`. The function will omit services that have autowiring disabled, and multiple types separated by a comma can be specified. +# value of a parameter under a key +%mailer.user% -```neon -services: - - BarsDependent( typed(Bar) ) +# parameter inside a string +'%wwwDir%/images' ``` -You can also pass an array of services automatically using [autowiring|autowiring#Collection of Services]. - -An array of all services with a certain [tag|#tags] is created by function `tagged()`. Multiple tags separated by a comma can be specified. +Furthermore, create objects, call methods and functions: ```neon -services: - - LoggersDependent( tagged(logger) ) -``` +# create object +DateTime() +# call static method +Collator::create(%locale%) -Referencing Services -==================== +# call PHP function +::getenv(DB_USER) +``` -Individual services are referenced using character `@` and name, so for example `@database`: +Refer to services either by their name or by type: ```neon -services: - - create: Foo(@database) - setup: - - setCacheStorage(@cache.storage) +# service by name +@database + +# service by type +@Nette\Database\Connection ``` -Corresponds to PHP code: +Use first-class callable syntax: .{data-version:3.2.0} -```php -public function createService01(): Foo -{ - $service = new Foo($this->getService('database')); - $service->setCacheStorage($this->getService('cache.storage')); - return $service; -} +```neon +# create callback, equivalent to [@user, logout] +@user::logout(...) ``` -Even anonymous services can be referenced using a callback, just specify their type (class or interface) instead of their name. However, this is usually not necessary due to [autowiring]. +Use constants: ```neon -services: - - create: Foo(@Nette\Database\Connection) # or @\PDO - setup: - - setCacheStorage(@cache.storage) +# class constant +FilesystemIterator::SKIP_DOTS + +# get global constant using PHP function constant() +::constant(\PHP_VERSION) ``` +Method calls can be chained just like in PHP. For simplicity, `::` is used instead of `->`: -Setup -===== +```neon +DateTime()::format('Y-m-d') +# PHP: (new DateTime())->format('Y-m-d') -In the setup section we list the methods to be called when creating the service: +@http.request::getUrl()::getHost() +# PHP: $this->getService('http.request')->getUrl()->getHost() +``` + +You can use these expressions anywhere, when [creating services |#Service Creation], in [#arguments], in the [#setup] section, or in [parameters |configuration#Parameters]: ```neon +parameters: + ipAddress: @http.request::getRemoteAddress() + services: database: - create: PDO(%dsn%, %user%, %password%) + create: DatabaseFactory::create( @anotherService::getDsn() ) setup: - - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) + - initialize( ::getenv('DB_USER') ) ``` -Corresponds to PHP code: -```php -public function createServiceDatabase(): PDO -{ - $service = new PDO('...', '...', '...'); - $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - return $service; -} -``` +Special Functions +----------------- -Properites can also be set. Adding an element to an array is also supported, and should be written in quotes so as not to conflict with NEON syntax: +In configuration files, you can use the following special functions: +- `not()` negates a value +- `bool()`, `int()`, `float()`, `string()` lossless casting to the specified type +- `typed()` creates an array of all services of the specified type +- `tagged()` creates an array of all services with the given tag ```neon services: - foo: - create: Foo - setup: - - $value = 123 - - '$onClick[]' = [@bar, clickHandler] + - Foo( + id: int(::getenv('ProjectId')) + productionMode: not(%debugMode%) + ) ``` -Corresponds to PHP code: +Unlike standard PHP casting, such as `(int)`, lossless casting throws an exception for non-numeric values. -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - $service->value = 123; - $service->onClick[] = [$this->getService('bar'), 'clickHandler']; - return $service; -} +The `typed()` function creates an array of all services of the specified type (class or interface). It excludes services that have autowiring disabled. Multiple types can also be specified, separated by commas. + +```neon +services: + - BarsDependent( typed(Bar) ) ``` -However, static methods or methods of other services can also be called in the setup. We pass the actual service to them as `@self`: +An array of services of a certain type can also be passed as an argument automatically using [autowiring |autowiring#Collection of Services]. +The `tagged()` function then creates an array of all services with a specific tag. Here too, you can specify multiple tags separated by commas. ```neon services: - foo: - create: Foo - setup: - - My\Helpers::initializeFoo(@self) - - @anotherService::setFoo(@self) + - LoggersDependent( tagged(logger) ) ``` -Corresponds to PHP code: -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - My\Helpers::initializeFoo($service); - $this->getService('anotherService')->setFoo($service); - return $service; -} +Autowiring +========== + +The `autowired` key allows you to influence the autowiring behavior for a specific service. For details, see the [chapter on autowiring|autowiring]. + +```neon +services: + foo: + create: Foo + autowired: false # the foo service is excluded from autowiring ``` -Autowiring -========== +Lazy Services .{data-version:3.2.4} +=================================== -The autowired key can be used to exclude a service from autowiring or to influence its behavior. See [chapter on autowiring|autowiring] for more information. +Lazy loading is a technique that defers the creation of a service until it is actually needed. In the global configuration, you can [enable lazy creation |configuration#Lazy Services] for all services at once. For individual services, you can then override this behavior: ```neon services: foo: create: Foo - autowired: false # foo is removed from autowiring + lazy: false ``` +When a service is defined as lazy, upon requesting it from the DI container, we receive a special proxy object. This proxy looks and behaves identically to the actual service, but the actual initialization (constructor invocation and setup calls) occurs only upon the first access to any of its methods or properties. + +.[note] +Lazy loading can only be used for user-defined classes, not for internal PHP classes. It requires PHP 8.4 or newer. + Tags ==== -User information can be added to individual services in the form of tags: +Tags serve to add supplementary information to services. You can assign one or more tags to a service: ```neon services: @@ -360,7 +371,7 @@ services: - cached ``` -Tags can also have a value: +Tags can also hold values: ```neon services: @@ -370,26 +381,26 @@ services: logger: monolog.logger.event ``` -An array of services with certain tags can be passed as an argument using the function `tagged()`. Multiple tags separated by a comma can also be specified. +To retrieve all services associated with specific tags, you can use the `tagged()` function: ```neon services: - LoggersDependent( tagged(logger) ) ``` -Service names can be obtained from the DI container using the method `findByTag()`: +Within the DI container, you can retrieve the names of all services with a specific tag using the `findByTag()` method: ```php $names = $container->findByTag('logger'); -// $names is an array containing the service name and tag value -// i.e. ['foo' => 'monolog.logger.event', ...] +// $names is an array containing service names as keys and tag values as values +// e.g., ['foo' => 'monolog.logger.event', ...] ``` Inject Mode =========== -The `inject: true` flag is used to activate the passing of dependencies via public variables with the [inject |best-practices:inject-method-attribute#Inject Attributes] annotation and the [inject*() |best-practices:inject-method-attribute#inject Methods] methods. +Using the `inject: true` flag enables dependency injection via public properties annotated with [inject |best-practices:inject-method-attribute#Inject Attributes] and [inject*() |best-practices:inject-method-attribute#inject Methods] methods. ```neon services: @@ -398,13 +409,13 @@ services: inject: true ``` -By default, `inject` is only activated for presenters. +By default, `inject` mode is enabled only for presenters. -Modification of Services -======================== +Service Modifications +===================== -There are a number of services in the DI container that have been added by built-in or [your extension|#di-extensions]. The definitions of these services can be modified in the configuration. For example, for service `application.application`, which is by default an object `Nette\Application\Application`, we can change the class: +The DI container holds numerous services added via built-in or [user extensions|extensions]. You can modify the definitions of these existing services directly in the configuration. For example, you can change the class for the `application.application` service, which defaults to `Nette\Application\Application`, to a different one: ```neon services: @@ -413,9 +424,9 @@ services: alteration: true ``` -The `alteration` flag is informative and says that we are just modifying an existing service. +The `alteration` flag is informative, indicating that we are merely modifying an existing service. -We can also add a setup: +We can also supplement the setup: ```neon services: @@ -426,7 +437,7 @@ services: - '$onStartup[]' = [@resource, init] ``` -When rewriting a service, we may want to remove the original arguments, setup items or tags, which is what `reset` is for: +When modifying a service, we might want to remove original arguments, setup items, or tags, using the `reset` key: ```neon services: @@ -439,7 +450,7 @@ services: - tags ``` -A service added by extension can also be removed from the container: +If you want to remove a service added by an extension, you can do so as follows: ```neon services: diff --git a/dependency-injection/es/@home.texy b/dependency-injection/es/@home.texy index 5a2577eb9d..ced4c8f5ab 100644 --- a/dependency-injection/es/@home.texy +++ b/dependency-injection/es/@home.texy @@ -1,24 +1,21 @@ -Inyección de dependencia -************************ +Nette DI +******** .[perex] -La inyección de dependencias es un patrón de diseño que cambiará radicalmente tu forma de ver el código y el desarrollo. Abre el camino a un mundo de aplicaciones sostenibles y de diseño limpio. +La Inyección de Dependencias es un patrón de diseño que cambiará fundamentalmente su perspectiva sobre el código y el desarrollo. Le abrirá las puertas a un mundo de aplicaciones limpiamente diseñadas y mantenibles. -- [¿Qué es la inyección de dependencia? |introduction] -- [Estado Global y Singletons |global-state] -- [Pasar Dependencias |passing-dependencies] -- [¿Qué es un Contenedor DI? |container] +- [¿Qué es la Inyección de Dependencias? |introduction] +- [Estado global y singletons |global-state] +- [Paso de dependencias |passing-dependencies] +- [¿Qué es un contenedor DI? |container] - [Preguntas frecuentes|faq] -Nette DI --------- - El paquete `nette/di` proporciona un contenedor DI compilado extremadamente avanzado para PHP. -- [Contenedor |nette-container]Nette DI +- [Contenedor DI de Nette |nette-container] - [Configuración |configuration] -- [Definiciones de Servicio |services] -- [Autocableado |autowiring] -- [Fábricas Generadas |factory] -- [Creación de extensiones para |extensions]Nette DI +- [Definición de servicios |services] +- [Autowiring |autowiring] +- [Fábricas generadas |factory] +- [Creación de extensiones para Nette DI|extensions] diff --git a/dependency-injection/es/@left-menu.texy b/dependency-injection/es/@left-menu.texy index 16a88609ad..5b3b787b97 100644 --- a/dependency-injection/es/@left-menu.texy +++ b/dependency-injection/es/@left-menu.texy @@ -1,17 +1,17 @@ -Inyección de dependencia -************************ -- [Qué es el DI? |introduction] -- [Estado Global y Singletons |global-state] -- [Pasar dependencias |passing-dependencies] -- [Qué es el Contenedor DI? |container] +Inyección de Dependencias +************************* +- [¿Qué es DI? |introduction] +- [Estado global y singletons |global-state] +- [Paso de dependencias |passing-dependencies] +- [¿Qué es un contenedor DI? |container] - [Preguntas frecuentes|faq] Nette DI -------- -- [Contenedor Nette DI |nette-container] +- [Contenedor DI de Nette |nette-container] - [Configuración |configuration] - [Definición de servicios |services] -- [Autowiring (Autocableado) |autowiring] +- [Autowiring |autowiring] - [Fábricas generadas |factory] - [Creación de extensiones para Nette DI|extensions] diff --git a/dependency-injection/es/@meta.texy b/dependency-injection/es/@meta.texy new file mode 100644 index 0000000000..1670b124ad --- /dev/null +++ b/dependency-injection/es/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Documentación}} diff --git a/dependency-injection/es/autowiring.texy b/dependency-injection/es/autowiring.texy index 7a459f767c..fab3b4da8c 100644 --- a/dependency-injection/es/autowiring.texy +++ b/dependency-injection/es/autowiring.texy @@ -1,24 +1,24 @@ -Autowiring (Autocableado) -************************* +Autowiring +********** .[perex] -El Autowiring es una gran característica que puede pasar automáticamente servicios al constructor y otros métodos, por lo que no necesitamos escribirlos en absoluto. Te ahorra mucho tiempo. +Autowiring es una gran característica que puede pasar automáticamente los servicios requeridos al constructor y otros métodos, por lo que no tenemos que escribirlos en absoluto. Le ahorrará mucho tiempo. -Esto nos permite saltarnos la gran mayoría de argumentos a la hora de redactar las definiciones de los servicios. En lugar de: +Gracias a esto, podemos omitir la gran mayoría de los argumentos al escribir definiciones de servicios. En lugar de: ```neon services: articles: Model\ArticleRepository(@database, @cache.storage) ``` -Sólo escribe: +Simplemente escriba: ```neon services: articles: Model\ArticleRepository ``` -El autowiring se rige por tipos, por lo que la clase `ArticleRepository` debe definirse de la siguiente manera: +Autowiring se guía por tipos, por lo que para que funcione, la clase `ArticleRepository` debe definirse de la siguiente manera: ```php namespace Model; @@ -30,22 +30,22 @@ class ArticleRepository } ``` -Para utilizar el autowiring, debe haber **sólo un servicio** para cada tipo en el contenedor. Si hubiera más, el autowiring no sabría cuál pasar y lanzaría una excepción: +Para poder usar autowiring, debe haber **exactamente un servicio** para cada tipo en el contenedor. Si hubiera más, autowiring no sabría cuál pasar y lanzaría una excepción: ```neon services: mainDb: PDO(%dsn%, %user%, %password%) tempDb: PDO('sqlite::memory:') - articles: Model\ArticleRepository # THROWS EXCEPTION, ambas mainDb y tempDb coinciden + articles: Model\ArticleRepository # LANZARÁ EXCEPCIÓN, coinciden mainDb y tempDb ``` -La solución sería evitar el autowiring e indicar explícitamente el nombre del servicio (es decir, `articles: Model\ArticleRepository(@mainDb)`). Sin embargo, es más conveniente [desactivar|#Disabled autowiring] el autowiring de uno de los servicios, o el primer servicio [preferido|#Preferred Autowiring]. +La solución sería omitir autowiring y especificar explícitamente el nombre del servicio (es decir, `articles: Model\ArticleRepository(@mainDb)`). Pero es más inteligente [desactivar |#Desactivación de autowiring] el autowiring de uno de los servicios, o [dar preferencia |#Preferencia de autowiring] al primer servicio. -Desactivar Autowiring .[#toc-disabled-autowiring] -------------------------------------------------- +Desactivación de autowiring +--------------------------- -Puedes desactivar el autowiring del servicio utilizando la opción `autowired: no`: +Podemos desactivar el autowiring de un servicio usando la opción `autowired: no`: ```neon services: @@ -53,28 +53,27 @@ services: tempDb: create: PDO('sqlite::memory:') - autowired: false # elimina tempDb del autocableado + autowired: false # el servicio tempDb está excluido de autowiring - articles: Model\ArticleRepository # por lo tanto pasa mainDb al constructor + articles: Model\ArticleRepository # por lo tanto, pasa mainDb al constructor ``` -El servicio `articles` no lanza la excepción de que hay dos servicios coincidentes de tipo `PDO` (es decir, `mainDb` y `tempDb`) que se pueden pasar al constructor, porque sólo ve el servicio `mainDb`. +El servicio `articles` no lanzará una excepción porque existen dos servicios compatibles de tipo `PDO` (es decir, `mainDb` y `tempDb`) que se pueden pasar al constructor, ya que solo ve el servicio `mainDb`. -.[nota] -Configurar el autowiring en Nette funciona diferente que en Symfony, donde la opción `autowire: false` dice que el autowiring no debe ser usado para los argumentos del constructor del servicio. -En Nette, el autowiring se utiliza siempre, tanto para los argumentos del constructor como para cualquier otro método. La opción `autowired: false` dice que la instancia del servicio no debe ser pasada a ninguna parte usando autowiring. +.[note] +La configuración de autowiring en Nette funciona de manera diferente que en Symfony, donde la opción `autowire: false` indica que no se debe usar autowiring para los argumentos del constructor del servicio dado. En Nette, autowiring siempre se usa, ya sea para los argumentos del constructor o cualquier otro método. La opción `autowired: false` indica que la instancia del servicio dado no debe pasarse a ningún lugar mediante autowiring. -Preferir Autowiring .[#toc-preferred-autowiring] ------------------------------------------------- +Preferencia de autowiring +------------------------- -Si tenemos más servicios del mismo tipo y uno de ellos tiene la opción `autowired`, este servicio se convierte en el preferido: +Si tenemos varios servicios del mismo tipo y especificamos la opción `autowired` para uno de ellos, este servicio se convierte en el preferido: ```neon services: mainDb: create: PDO(%dsn%, %user%, %password%) - autowired: PDO # lo hace preferible + autowired: PDO # se convierte en el preferido tempDb: create: PDO('sqlite::memory:') @@ -82,13 +81,13 @@ services: articles: Model\ArticleRepository ``` -El servicio `articles` no lanza la excepción de que hay dos servicios `PDO` coincidentes (es decir, `mainDb` y `tempDb`), sino que utiliza el servicio preferido, es decir, `mainDb`. +El servicio `articles` no lanzará una excepción porque existen dos servicios compatibles de tipo `PDO` (es decir, `mainDb` y `tempDb`), sino que utilizará el servicio preferido, es decir, `mainDb`. -Colección de Servicios .[#toc-collection-of-services] ------------------------------------------------------ +Array de servicios +------------------ -El autowiring también puede pasar un array de servicios de un tipo particular. Dado que PHP no puede anotar de forma nativa el tipo de elementos de array, además del tipo `array`, se debe añadir un comentario phpDoc con el tipo de elemento como `ClassName[]`: +Autowiring también puede pasar arrays de servicios de un tipo específico. Dado que en PHP no se puede escribir nativamente el tipo de los elementos del array, es necesario, además del tipo `array`, agregar un comentario phpDoc con el tipo del elemento en el formato `ClassName[]`: ```php namespace Model; @@ -103,46 +102,45 @@ class ShipManager } ``` -El contenedor DI pasa automáticamente una matriz de servicios que coinciden con el tipo dado. Omitirá los servicios que tengan desactivado el autowiring. +El contenedor DI luego pasa automáticamente un array de servicios que coinciden con el tipo dado. Omite los servicios que tienen autowiring desactivado. -Si no puede controlar la forma del comentario phpDoc, puede pasar un array de servicios directamente en la configuración usando [`typed()`|services#Special Functions]. +El tipo en el comentario también puede tener el formato `array<int, Class>` o `list<Class>`. Si no puede influir en la forma del comentario phpDoc, puede pasar el array de servicios directamente en la configuración usando [`typed()` |services#Funciones especiales]. -Argumentos escalares .[#toc-scalar-arguments] ---------------------------------------------- +Argumentos escalares +-------------------- -El autowiring sólo puede pasar objetos y matrices de objetos. Argumentos escalares (por ejemplo, cadenas, números, booleanos) [escribir en configuración |services#Arguments]. -Una alternativa es crear un *setting-object* que encapsula un valor escalar (o múltiples valores) como un objeto, que luego puede ser pasado de nuevo utilizando autowiring. +Autowiring solo puede inyectar objetos y arrays de objetos. Los argumentos escalares (por ejemplo, cadenas, números, booleanos) [los escribimos en la configuración |services#Argumentos]. Una alternativa es crear un [objeto de configuración |best-practices:passing-settings-to-presenters], que encapsula el valor escalar (o múltiples valores) en forma de objeto, y este luego se puede pasar nuevamente mediante autowiring. ```php class MySettings { public function __construct( - // readonly puede ser usado desde PHP 8.1 + // readonly se puede usar desde PHP 8.1 public readonly bool $value, ) {} } ``` -Un servicio se crea añadiéndolo a la configuración: +Lo convierte en un servicio agregándolo a la configuración: ```neon services: - MySettings('any value') ``` -Todas las clases lo solicitarán mediante el autowiring +Todas las clases lo solicitarán luego mediante autowiring. -Estrechamiento del Autowiring .[#toc-narrowing-of-autowiring] -------------------------------------------------------------- +Restricción de autowiring +------------------------- -Para servicios individuales, el autowiring puede limitarse a clases o interfaces específicas. +Para servicios individuales, autowiring se puede restringir solo a ciertas clases o interfaces. -Normalmente, el autowiring pasa el servicio a cada parámetro de método a cuyo tipo corresponde el servicio. Estrechar significa que especificamos las condiciones que deben cumplir los tipos especificados para los parámetros del método para que se les pase el servicio. +Normalmente, autowiring pasa el servicio a cada parámetro del método cuyo tipo coincide con el servicio. La restricción significa que establecemos condiciones que deben cumplir los tipos especificados en los parámetros del método para que se les pase el servicio. -Veamos un ejemplo: +Lo mostraremos con un ejemplo: ```php class ParentClass @@ -164,42 +162,42 @@ class ChildDependent } ``` -Si los registráramos todos como servicios, el autowiring fallaría: +Si los registráramos todos como servicios, autowiring fallaría: ```neon services: parent: ParentClass child: ChildClass - parentDep: ParentDependent # THROWS EXCEPTION, coincidencia de padres e hijos - childDep: ChildDependent # pasa el servicio 'child' al constructor + parentDep: ParentDependent # LANZARÁ EXCEPCIÓN, coinciden los servicios parent y child + childDep: ChildDependent # autowiring pasa el servicio child al constructor ``` -El servicio `parentDep` lanza la excepción `Multiple services of type ParentClass found: parent, child` porque tanto `parent` como `child` caben en su constructor y el autowiring no puede tomar una decisión sobre cuál elegir. +El servicio `parentDep` lanzará una excepción `Multiple services of type ParentClass found: parent, child`, porque ambos servicios `parent` y `child` encajan en su constructor, y autowiring no puede decidir cuál elegir. -Para el servicio `child`, podemos por tanto limitar su autowiring a `ChildClass`: +Por lo tanto, para el servicio `child`, podemos restringir su autowiring al tipo `ChildClass`: ```neon services: parent: ParentClass child: create: ChildClass - autowired: ChildClass # alternative: 'autowired: self' + autowired: ChildClass # también se puede escribir 'autowired: self' - parentDep: ParentDependent # THROWS EXCEPTION, the 'child' can not be autowired - childDep: ChildDependent # pasa el servicio 'child' al constructor + parentDep: ParentDependent # autowiring pasa el servicio parent al constructor + childDep: ChildDependent # autowiring pasa el servicio child al constructor ``` -El servicio `parentDep` ahora se pasa al constructor del servicio `parentDep`, ya que ahora es el único objeto coincidente. El servicio `child` ya no se pasa por autowiring. Sí, el servicio `child` sigue siendo de tipo `ParentClass`, pero la condición de restricción dada para el tipo de parámetro ya no se aplica, es decir, ya no es cierto que `ParentClass` *es un supertipo* de `ChildClass`. +Ahora, el servicio `parent` se pasa al constructor del servicio `parentDep`, porque ahora es el único objeto compatible. Autowiring ya no pasa el servicio `child` allí. Sí, el servicio `child` sigue siendo de tipo `ParentClass`, pero la condición de restricción dada para el tipo de parámetro ya no se cumple, es decir, no es cierto que `ParentClass` *es un supertipo de* `ChildClass`. -En el caso de `child`, `autowired: ChildClass` podría escribirse como `autowired: self` ya que `self` significa tipo de servicio actual. +Para el servicio `child`, `autowired: ChildClass` también podría escribirse como `autowired: self`, ya que `self` es un marcador de posición para la clase del servicio actual. -La clave `autowired` puede incluir varias clases e interfaces como array: +En la clave `autowired`, también es posible especificar varias clases o interfaces como un array: ```neon autowired: [BarClass, FooInterface] ``` -Intentemos añadir interfaces al ejemplo: +Intentemos complementar el ejemplo con interfaces: ```php interface FooInterface @@ -239,13 +237,13 @@ class ChildDependent } ``` -Cuando no limitamos el servicio `child`, entrará en los constructores de todas las clases `FooDependent`, `BarDependent`, `ParentDependent` y `ChildDependent` y el autowiring lo pasará allí. +Si no restringimos el servicio `child` de ninguna manera, encajará en los constructores de todas las clases `FooDependent`, `BarDependent`, `ParentDependent` y `ChildDependent`, y autowiring lo pasará allí. -Sin embargo, si limitamos su autowiring a `ChildClass` utilizando `autowired: ChildClass` (o `self`), el autowiring sólo lo pasa al constructor `ChildDependent`, porque requiere un argumento de tipo `ChildClass` y `ChildClass` *es de tipo* `ChildClass`. Ningún otro tipo especificado para los otros parámetros es un superconjunto de `ChildClass`, por lo que el servicio no se pasa. +Pero si restringimos su autowiring a `ChildClass` usando `autowired: ChildClass` (o `self`), autowiring solo lo pasará al constructor de `ChildDependent`, porque requiere un argumento de tipo `ChildClass` y es cierto que `ChildClass` *es de tipo* `ChildClass`. Ningún otro tipo especificado en los otros parámetros es un supertipo de `ChildClass`, por lo que el servicio no se pasa. -Si lo restringimos a `ParentClass` usando `autowired: ParentClass`, el autowiring lo pasará de nuevo al constructor `ChildDependent` (ya que el tipo requerido `ChildClass` es un superconjunto de `ParentClass`) y al constructor `ParentDependent` también, ya que el tipo requerido de `ParentClass` también es coincidente. +Si lo restringimos a `ParentClass` usando `autowired: ParentClass`, autowiring lo pasará nuevamente al constructor de `ChildDependent` (porque el `ChildClass` requerido es un supertipo de `ParentClass`) y ahora también al constructor de `ParentDependent`, porque el tipo requerido `ParentClass` también es compatible. -Si lo restringimos a `FooInterface`, seguirá autoconectándose a `ParentDependent` (el tipo requerido `ParentClass` es un supertipo de `FooInterface`) y `ChildDependent`, pero además al constructor `FooDependent`, pero no a `BarDependent`, ya que `BarInterface` no es un supertipo de `FooInterface`. +Si lo restringimos a `FooInterface`, seguirá siendo autowired en `ParentDependent` (el `ParentClass` requerido es un supertipo de `FooInterface`) y `ChildDependent`, pero además también en el constructor de `FooDependent`, pero no en `BarDependent`, porque `BarInterface` no es un supertipo de `FooInterface`. ```neon services: @@ -253,8 +251,8 @@ services: create: ChildClass autowired: FooInterface - fooDep: FooDependent # pasa el servicio hijo al constructor - barDep: BarDependent # THROWS EXCEPTION, ningún servicio pasaría - parentDep: ParentDependent # pasa el servicio hijo al constructor - childDep: ChildDependent # pasa el servicio hijo al constructor + fooDep: FooDependent # autowiring pasa child al constructor + barDep: BarDependent # LANZARÁ EXCEPCIÓN, ningún servicio coincide + parentDep: ParentDependent # autowiring pasa child al constructor + childDep: ChildDependent # autowiring pasa child al constructor ``` diff --git a/dependency-injection/es/configuration.texy b/dependency-injection/es/configuration.texy index 5dcfe64475..8d3c0151ae 100644 --- a/dependency-injection/es/configuration.texy +++ b/dependency-injection/es/configuration.texy @@ -2,31 +2,32 @@ Configuración del Contenedor DI ******************************* .[perex] -Visión general de las opciones de configuración del contenedor Nette DI. +Resumen de las opciones de configuración para el contenedor Nette DI. -Fichero de configuración +Archivo de configuración ======================== -El contenedor Nette DI es fácil de controlar usando ficheros de configuración. Normalmente están escritos en [formato NEON|neon:format]. Recomendamos usar [editores con soporte|best-practices:editors-and-tools#ide-editor] para este formato de edición. +El contenedor Nette DI se controla fácilmente mediante archivos de configuración. Normalmente se escriben en [formato NEON |neon:format]. Para la edición, recomendamos [editores con soporte |best-practices:editors-and-tools#Editor IDE] para este formato. <pre> -"decorator .[prism-token prism-atrule]":[#Decorator]: "Decorator .[prism-token prism-comment]"<br> -"di .[prism-token prism-atrule]":[#DI]: "DI Container .[prism-token prism-comment]"<br> -"extensions .[prism-token prism-atrule]":[#Extensions]: "Instalar extensiones DI adicionales .[prism-token prism-comment]"<br> -"includes .[prism-token prism-atrule]":[#Including files]: "Incluir archivos .[prism-token prism-comment]"<br> -"parameters .[prism-token prism-atrule]":[#Parameters]: "Parámetros .[prism-token prism-comment]"<br> -"search .[prism-token prism-atrule]":[#Búsqueda]: "Registro automático de servicios .[prism-token prism-comment]"<br> -"services .[prism-token prism-atrule]":[services]: "Servicios .[prism-token prism-comment]"<br> +"decorator .[prism-token prism-atrule]":[#decorator]: "Decorador .[prism-token prism-comment]"<br> +"di .[prism-token prism-atrule]":[#DI]: "Contenedor DI .[prism-token prism-comment]"<br> +"extensions .[prism-token prism-atrule]":[#Extensiones]: "Instalación de extensiones DI adicionales .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[#Inclusión de archivos]: "Inclusión de archivos .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[#parámetros]: "Parámetros .[prism-token prism-comment]"<br> +"search .[prism-token prism-atrule]":[#Search]: "Registro automático de servicios .[prism-token prism-comment]"<br> +"services .[prism-token prism-atrule]":[services]: "Servicios .[prism-token prism-comment]" </pre> -Si utilizas una cadena que empieza por `@` o tiene `%` en cualquier parte, tienes que escaparla añadiendo otra `@` o `%`. .[nota] +.[note] +Si desea escribir una cadena que contenga el carácter `%`, debe escaparlo duplicándolo a `%%`. -Parámetros .[#toc-parameters] -============================= +Parámetros +========== -Puedes definir parámetros que luego se pueden utilizar como parte de las definiciones de servicio. Esto puede ayudar a separar valores que querrás cambiar más regularmente. +En la configuración, puede definir parámetros que luego se pueden usar como parte de las definiciones de servicios. Esto puede aclarar la configuración o unificar y separar valores que cambiarán. ```neon parameters: @@ -35,9 +36,9 @@ parameters: password: secret ``` -Puede hacer referencia al parámetro `foo` mediante `%foo%` en cualquier otro lugar de cualquier archivo de configuración. También se pueden utilizar dentro de cadenas como `'%wwwDir%/images'`. +Nos referimos al parámetro `dsn` en cualquier parte de la configuración escribiendo `%dsn%`. Los parámetros también se pueden usar dentro de cadenas como `'%wwwDir%/images'`. -Los parámetros no necesitan ser sólo cadenas, también pueden ser valores de array: +Los parámetros no tienen que ser solo cadenas o números, también pueden contener arrays: ```neon parameters: @@ -48,32 +49,32 @@ parameters: languages: [cs, en, de] ``` -Puedes referirte a una sola clave como `%mailer.user%`. +Nos referimos a una clave específica como `%mailer.user%`. -Si necesitas obtener el valor de algún parámetro en tu código, por ejemplo en tu clase, entonces pásalo a esta clase. Por ejemplo, en el constructor. No existe un objeto de configuración global que pueda consultar a las clases los valores de los parámetros. Esto iría en contra del principio de inyección de dependencias. +Si necesita averiguar el valor de cualquier parámetro en su código, por ejemplo, en una clase, páselo a esa clase. Por ejemplo, en el constructor. No existe un objeto global que represente la configuración al que las clases consultarían los valores de los parámetros. Eso violaría el principio de inyección de dependencias. -Servicios .[#toc-services] -========================== +Servicios +========= -Ver [capítulo aparte|services]. +Ver [capítulo separado |services]. -Decorador .[#toc-decorator] -=========================== +Decorator +========= -¿Cómo editar masivamente todos los servicios de un determinado tipo? ¿Necesitas llamar a un determinado método para todos los presentadores que heredan de un determinado ancestro común? De ahí surge el decorador. +¿Cómo modificar masivamente todos los servicios de un tipo determinado? Por ejemplo, ¿llamar a un método específico en todos los presenters que heredan de un ancestro común específico? Para eso está el decorator. ```neon decorator: # para todos los servicios que son instancias de esta clase o interfaz - App\Presenters\BasePresenter: + App\Presentation\BasePresenter: setup: - - setProjectId(10) # llamar a este método - - $absoluteUrls = true # y establecer la variable + - setProjectId(10) # llama a este método + - $absoluteUrls = true # y establece la variable ``` -Decorator también se puede utilizar para establecer [tags|services#Tags] o activar [inject mode|services#Inject Mode]. +El decorator también se puede usar para configurar [tags |services#Tags] o activar el modo [inject |services#Modo Inject]. ```neon decorator: @@ -90,63 +91,75 @@ Configuración técnica del contenedor DI. ```neon di: - # shows DIC in Tracy Bar? - debugger: ... # (bool) defaults to true + # ¿mostrar DIC en Tracy Bar? + debugger: ... # (bool) predeterminado es true - # tipos de parámetros que nunca se autocablean (autowire) + # tipos de parámetros que nunca autowirear excluded: ... # (string[]) - # la clase de la que hereda el contenedor DI - parentClass: ... # (string) defaults to Nette\DI\Container + # ¿permitir la creación lazy de servicios? + lazy: ... # (bool) predeterminado es false + + # clase de la que hereda el contenedor DI + parentClass: ... # (string) predeterminado es Nette\DI\Container ``` -Exportación de metadatos .[#toc-metadata-export] ------------------------------------------------- +Servicios lazy .{data-version:3.2.4} +------------------------------------ + +La configuración `lazy: true` activa la creación lazy (diferida) de servicios. Esto significa que los servicios no se crean realmente en el momento en que los solicitamos del contenedor DI, sino en el momento de su primer uso. Esto puede acelerar el inicio de la aplicación y reducir los requisitos de memoria, ya que solo se crean los servicios que realmente se necesitan en la solicitud dada. + +Para un servicio específico, la creación lazy se puede [cambiar |services#Servicios Lazy]. + +.[note] +Los objetos lazy solo se pueden usar para clases de usuario, no para clases internas de PHP. Requiere PHP 8.4 o posterior. -La clase contenedor DI también contiene muchos metadatos. Puedes reducirla reduciendo la exportación de metadatos. + +Exportación de metadatos +------------------------ + +La clase del contenedor DI también contiene muchos metadatos. Puede reducir su tamaño reduciendo la exportación de metadatos. ```neon di: export: # ¿exportar parámetros? - parameters: false # (bool) por defecto true + parameters: false # (bool) predeterminado es true - # ¿etiquetas de exportación y cuáles? - tags: # (string[]|bool) por defecto todos + # ¿exportar tags y cuáles? + tags: # (string[]|bool) predeterminados son todos - event.subscriber - # exportar datos para autowiring y ¿cuáles? - types: # (string[]|bool) por defecto todos + # ¿exportar datos para autowiring y cuáles? + types: # (string[]|bool) predeterminados son todos - Nette\Database\Connection - Symfony\Component\Console\Application ``` -Si no utilizas el array `$container->parameters`, puedes desactivar la exportación de parámetros. Además, puede exportar sólo aquellas etiquetas a través de las cuales obtiene servicios utilizando el método `$container->findByTag(...)`. -Si no llama al método en absoluto, puede desactivar completamente la exportación de etiquetas con `false`. +Si no utiliza el array `$container->getParameters()`, puede desactivar la exportación de parámetros. Además, puede exportar solo los tags a través de los cuales obtiene servicios con el método `$container->findByTag(...)`. Si no llama al método en absoluto, puede desactivar completamente la exportación de tags usando `false`. -Puede reducir significativamente los metadatos para [autowiring] especificando las clases que utiliza como parámetro del método `$container->getByType()`. -Y de nuevo, si no llama al método en absoluto (o sólo en [application:bootstrap] para obtener `Nette\Application\Application`), puede desactivar la exportación completamente con `false`. +Puede reducir significativamente los metadatos para [autowiring |autowiring] especificando las clases que usa como parámetro del método `$container->getByType()`. Y nuevamente, si no llama al método en absoluto (o solo en el [bootstrap |application:bootstrapping] para obtener `Nette\Application\Application`), puede desactivar la exportación por completo usando `false`. -Extensiones .[#toc-extensions] -============================== +Extensiones +=========== -Registro de otras extensiones DI. De esta forma añadimos, por ejemplo, la extensión DI `Dibi\Bridges\Nette\DibiExtension22` bajo el nombre `dibi`: +Registro de extensiones DI adicionales. De esta manera, agregamos, por ejemplo, la extensión DI `Dibi\Bridges\Nette\DibiExtension22` bajo el nombre `dibi` ```neon extensions: dibi: Dibi\Bridges\Nette\DibiExtension22 ``` -Luego lo configuramos en su sección llamada también `dibi`: +Posteriormente, la configuramos en la sección `dibi`: ```neon dibi: host: localhost ``` -También puedes añadir una clase de extensión con parámetros: +También se puede agregar como extensión una clase que tiene parámetros: ```neon extensions: @@ -154,10 +167,10 @@ extensions: ``` -Inclusión de archivos .[#toc-including-files] -============================================= +Inclusión de archivos +===================== -Se pueden insertar ficheros de configuración adicionales en la sección `includes`: +Podemos incluir otros archivos de configuración en la sección `includes`: ```neon includes: @@ -166,7 +179,7 @@ includes: - presenters.neon ``` -El nombre `parameters.php` no es una errata, la configuración también se puede escribir en un fichero PHP, que la devuelve como un array: +El nombre `parameters.php` no es un error tipográfico, la configuración también se puede escribir en un archivo PHP, que la devuelve como un array: ```php <?php @@ -179,32 +192,27 @@ return [ ]; ``` -Si elementos con las mismas claves aparecen dentro de ficheros de configuración, serán [sobreescritos o fusionados |#Merging] en el caso de arrays. El fichero incluido posteriormente tiene mayor prioridad que el anterior. El fichero en el que aparece la sección `includes` tiene una prioridad mayor que los ficheros incluidos en él. +Si aparecen elementos con las mismas claves en los archivos de configuración, se sobrescribirán o, en el caso de [arrays, se fusionarán |#Fusión]. El archivo incluido posteriormente tiene mayor prioridad que el anterior. El archivo en el que se especifica la sección `includes` tiene mayor prioridad que los archivos incluidos en él. -Búsqueda .[#toc-search] -======================= +Search +====== -La adición automática de servicios al contenedor DI hace el trabajo extremadamente agradable. Nette añade automáticamente presentadores al contenedor, pero usted puede añadir fácilmente cualquier otra clase. +La adición automática de servicios al contenedor DI hace que el trabajo sea extremadamente agradable. Nette agrega automáticamente presenters al contenedor, pero también se pueden agregar fácilmente cualquier otra clase. -Basta con especificar en qué directorios (y subdirectorios) deben buscarse las clases: +Simplemente especifique en qué directorios (y subdirectorios) debe buscar clases: ```neon search: - # usted mismo elige los nombres de las secciones - myForms: - in: %appDir%/Forms - - model: - in: %appDir%/Model + - in: %appDir%/Forms + - in: %appDir%/Model ``` -Normalmente, sin embargo, no queremos añadir todas las clases e interfaces, así que podemos filtrarlas: +Sin embargo, generalmente no queremos agregar absolutamente todas las clases e interfaces, por lo que podemos filtrarlas: ```neon search: - myForms: - in: %appDir%/Forms + - in: %appDir%/Forms # filtrado por nombre de archivo (string|string[]) files: @@ -215,48 +223,49 @@ search: - *Factory ``` -O podemos seleccionar clases que hereden o implementen al menos una de las siguientes clases: +O podemos seleccionar clases que heredan o implementan al menos una de las clases especificadas: ```neon search: - myForms: + - in: %appDir% extends: - App\*Form implements: - App\*FormInterface ``` -También se pueden definir reglas negativas, es decir, máscaras de nombres de clases o ancestros y si las cumplen, el servicio no se añadirá al contenedor DI: +También se pueden definir reglas de exclusión, es decir, máscaras de nombre de clase o ancestros hereditarios, que si coinciden, el servicio no se agrega al contenedor DI: ```neon search: - myForms: + - in: %appDir% exclude: + files: ... classes: ... extends: ... implements: ... ``` -Se pueden establecer etiquetas para los servicios añadidos: +Se pueden establecer tags para todos los servicios: ```neon search: - myForms: + - in: %appDir% tags: ... ``` -Fusión .[#toc-merging] -====================== +Fusión +====== -Si aparecen elementos con las mismas claves en más archivos de configuración, se sobrescribirán o fusionarán en el caso de las matrices. El fichero incluido más tarde tiene mayor prioridad. +Si aparecen elementos con las mismas claves en varios archivos de configuración, se sobrescribirán o, en el caso de arrays, se fusionarán. El archivo incluido posteriormente tiene mayor prioridad que el anterior. <table class=table> <tr> <th width=33%>config1.neon</th> <th width=33%>config2.neon</th> - <th>result</th> + <th>resultado</th> </tr> <tr> <td> @@ -283,13 +292,13 @@ items: </tr> </table> -Para evitar la fusión de un determinado array utiliza el signo de exclamación justo después del nombre del array: +Para los arrays, se puede evitar la fusión agregando un signo de exclamación después del nombre de la clave: <table class=table> <tr> <th width=33%>config1.neon</th> <th width=33%>config2.neon</th> - <th>result</th> + <th>resultado</th> </tr> <tr> <td> @@ -313,3 +322,5 @@ items: </td> </tr> </table> + +{{maintitle: Configuración de Inyección de Dependencias}} diff --git a/dependency-injection/es/container.texy b/dependency-injection/es/container.texy index 41de87595c..75f9e13142 100644 --- a/dependency-injection/es/container.texy +++ b/dependency-injection/es/container.texy @@ -4,13 +4,13 @@ .[perex] Un contenedor de inyección de dependencias (DIC) es una clase que puede instanciar y configurar objetos. -Puede sorprenderte, pero en muchos casos no necesitas un contenedor de inyección de dependencias para aprovechar las ventajas de la inyección de dependencias (DI para abreviar). Después de todo, incluso en [capítulo anterior|introduction] mostramos ejemplos específicos de DI y no se necesitaba ningún contenedor. +Puede que le sorprenda, pero en muchos casos no necesita un contenedor de inyección de dependencias para aprovechar los beneficios de la inyección de dependencias (DI para abreviar). Después de todo, incluso en el [capítulo introductorio |introduction], mostramos DI con ejemplos concretos y no se necesitó ningún contenedor. -Sin embargo, si necesitas gestionar un gran número de objetos diferentes con muchas dependencias, un contenedor de inyección de dependencias será realmente útil. Que es quizás el caso de las aplicaciones web construidas sobre un framework. +Sin embargo, si necesita administrar una gran cantidad de objetos diferentes con muchas dependencias, un contenedor de inyección de dependencias será realmente útil. Este es el caso, por ejemplo, de las aplicaciones web construidas sobre un framework. -En el capítulo anterior, introdujimos las clases `Article` y `UserController`. Ambas tienen algunas dependencias, a saber, la base de datos y la fábrica `ArticleFactory`. Y para estas clases, ahora vamos a crear un contenedor. Por supuesto, para un ejemplo tan simple, no tiene sentido tener un contenedor. Pero vamos a crear uno para mostrar cómo se ve y funciona. +En el capítulo anterior, presentamos las clases `Article` y `UserController`. Ambas tienen algunas dependencias, a saber, la base de datos y la fábrica `ArticleFactory`. Y ahora crearemos un contenedor para estas clases. Por supuesto, para un ejemplo tan simple, no tiene sentido tener un contenedor. Pero lo crearemos para mostrar cómo se ve y funciona. -He aquí un simple contenedor hardcodeado para el ejemplo anterior: +Aquí hay un contenedor simple codificado para el ejemplo dado: ```php class Container @@ -32,16 +32,16 @@ class Container } ``` -El uso sería así +El uso se vería así: ```php $container = new Container; $controller = $container->createUserController(); ``` -Simplemente pedimos el objeto al contenedor y ya no necesitamos saber nada sobre cómo crearlo o cuáles son sus dependencias; el contenedor sabe todo eso. Las dependencias son inyectadas automáticamente por el contenedor. Ese es su poder. +Simplemente le pedimos al contenedor el objeto y ya no necesitamos saber nada sobre cómo crearlo y cuáles son sus dependencias; el contenedor sabe todo eso. Las dependencias son inyectadas automáticamente por el contenedor. Ahí radica su poder. -Hasta ahora, el contenedor tiene todo codificado. Así que damos el siguiente paso y añadimos parámetros para hacer el contenedor realmente útil: +Hasta ahora, el contenedor tiene todos los datos codificados. Así que daremos el siguiente paso y agregaremos parámetros para que el contenedor sea realmente útil: ```php class Container @@ -70,9 +70,9 @@ $container = new Container([ ]); ``` -Los lectores avispados habrán notado un problema. Cada vez que obtengo un objeto `UserController`, también se crea una nueva instancia `ArticleFactory` y base de datos. Definitivamente no queremos eso. +Los lectores atentos pueden haber notado un cierto problema. Cada vez que obtengo un objeto `UserController`, también se crea una nueva instancia de `ArticleFactory` y la base de datos. Definitivamente no queremos eso. -Así que añadimos un método `getService()` que devolverá las mismas instancias una y otra vez: +Por lo tanto, agregaremos un método `getService()` que devolverá las mismas instancias siempre: ```php class Container @@ -87,7 +87,7 @@ class Container public function getService(string $name): object { if (!isset($this->services[$name])) { - // getService('Database') calls createDatabase() + // getService('Database') llamará a createDatabase() $method = 'create' . $name; $this->services[$name] = $this->$method(); } @@ -98,9 +98,9 @@ class Container } ``` -La primera llamada a, por ejemplo, `$container->getService('Database')` hará que `createDatabase()` cree un objeto base de datos, que almacenará en el array `$services` y lo devolverá directamente en la siguiente llamada. +En la primera llamada, por ejemplo, `$container->getService('Database')`, hará que `createDatabase()` cree el objeto de la base de datos, lo almacenará en el array `$services` y lo devolverá directamente en la próxima llamada. -También modificamos el resto del contenedor para que utilice `getService()`: +También modificaremos el resto del contenedor para usar `getService()`: ```php class Container @@ -119,9 +119,9 @@ class Container } ``` -Por cierto, el término servicio se refiere a cualquier objeto gestionado por el contenedor. De ahí el nombre del método `getService()`. +Por cierto, el término servicio se refiere a cualquier objeto administrado por el contenedor. Por eso el nombre del método `getService()`. -Listo. ¡Tenemos un contenedor DI completamente funcional! Y ya podemos usarlo: +Hecho. ¡Tenemos un contenedor DI completamente funcional! Y podemos usarlo: ```php $container = new Container([ @@ -134,6 +134,9 @@ $controller = $container->getService('UserController'); $database = $container->getService('Database'); ``` -Como puedes ver, no es difícil escribir un DIC. Es destacable que los propios objetos no saben que un contenedor los está creando. Por lo tanto, es posible crear cualquier objeto en PHP de esta manera sin afectar su código fuente. +Como puede ver, escribir un DIC no es nada complicado. Vale la pena recordar que los propios objetos no saben que algún contenedor los está creando. Por lo tanto, es posible crear cualquier objeto en PHP de esta manera sin interferir con su código fuente. -Crear y mantener manualmente una clase contenedora puede convertirse rápidamente en una pesadilla. Por ello, en el próximo capítulo hablaremos de [Nette DI Container|nette-container], que puede generarse y actualizarse casi automáticamente. +Crear y mantener manualmente una clase de contenedor puede convertirse rápidamente en una pesadilla. Por lo tanto, en el próximo capítulo, hablaremos sobre el [Contenedor Nette DI |nette-container], que puede generarse y actualizarse casi por sí mismo. + + +{{maintitle: ¿Qué es un contenedor de inyección de dependencias?}} diff --git a/dependency-injection/es/extensions.texy b/dependency-injection/es/extensions.texy index 4d1deedefd..17faf12c21 100644 --- a/dependency-injection/es/extensions.texy +++ b/dependency-injection/es/extensions.texy @@ -2,16 +2,16 @@ Creación de extensiones para Nette DI ************************************* .[perex] -Al generar un contenedor DI además de los ficheros de configuración también afectan las llamadas *extensiones*. Las activamos en el fichero de configuración en la sección `extensions`. +La generación del contenedor DI, además de los archivos de configuración, también está influenciada por las llamadas *extensiones*. Las activamos en el archivo de configuración en la sección `extensions`. -Así es como añadimos la extensión representada por la clase `BlogExtension` con nombre `blog`: +Así es como agregamos una extensión representada por la clase `BlogExtension` bajo el nombre `blog`: ```neon extensions: blog: BlogExtension ``` -Cada extensión de compilador hereda de [api:Nette\DI\CompilerExtension] y puede implementar los siguientes métodos que son llamados durante la compilación DI: +Cada extensión del compilador hereda de [api:Nette\DI\CompilerExtension] y puede implementar los siguientes métodos, que se llaman secuencialmente durante la construcción del contenedor DI: 1. getConfigSchema() 2. loadConfiguration() @@ -22,18 +22,18 @@ Cada extensión de compilador hereda de [api:Nette\DI\CompilerExtension] y puede getConfigSchema() .[method] =========================== -Este método se ejecuta en primer lugar. Define el esquema utilizado para validar los parámetros de configuración. +Este método se llama primero. Define el esquema para validar los parámetros de configuración. -Las extensiones se configuran en una sección cuyo nombre es el mismo bajo el que se añadió la extensión, por ejemplo `blog`. +Configuramos la extensión en la sección cuyo nombre es el mismo que aquel bajo el cual se agregó la extensión, es decir, `blog`: ```neon -# same name as my extension +# mismo nombre que la extensión blog: postsPerPage: 10 - comments: false + allowComments: false ``` -Definiremos un esquema describiendo todas las opciones de configuración, incluyendo sus tipos, valores aceptados y posiblemente valores por defecto: +Creamos un esquema que describe todas las opciones de configuración, incluidos sus tipos, valores permitidos y, opcionalmente, valores predeterminados: ```php use Nette\Schema\Expect; @@ -50,16 +50,16 @@ class BlogExtension extends Nette\DI\CompilerExtension } ``` -Consulte la documentación [Schema |schema:]. Además, puedes especificar qué opciones pueden ser [dynamic |application:bootstrap#Dynamic Parameters] usando `dynamic()`, por ejemplo `Expect::int()->dynamic()`. +Encontrará la documentación en la página [Schema |schema:]. Además, se puede especificar qué opciones pueden ser [dinámicas |application:bootstrapping#Parámetros dinámicos] usando `dynamic()`, por ejemplo, `Expect::int()->dynamic()`. -Accedemos a la configuración a través de `$this->config`, que es un objeto `stdClass`: +Accedemos a la configuración a través de la variable `$this->config`, que es un objeto `stdClass`: ```php class BlogExtension extends Nette\DI\CompilerExtension { public function loadConfiguration() { - $num = $this->config->postPerPage; + $num = $this->config->postsPerPage; if ($this->config->allowComments) { // ... } @@ -71,7 +71,7 @@ class BlogExtension extends Nette\DI\CompilerExtension loadConfiguration() .[method] ============================= -Este método se utiliza para añadir servicios al contenedor. Esto se hace mediante [api:Nette\DI\ContainerBuilder]: +Se utiliza para agregar servicios al contenedor. Para esto se utiliza [api:Nette\DI\ContainerBuilder]: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -80,25 +80,25 @@ class BlogExtension extends Nette\DI\CompilerExtension { $builder = $this->getContainerBuilder(); $builder->addDefinition($this->prefix('articles')) - ->setFactory(App\Model\HomepageArticles::class, ['@connection']) // or setCreator() + ->setFactory(App\Model\HomepageArticles::class, ['@connection']) // o setCreator() ->addSetup('setLogger', ['@logger']); } } ``` -La convención es prefijar los servicios añadidos por una extensión con su nombre para que no surjan conflictos de nombres. Esto se hace mediante `prefix()`, de forma que si la extensión se llama `blog`, el servicio se llamará `blog.articles`. +La convención es prefijar los servicios agregados por la extensión con su nombre para evitar conflictos de nombres. Esto lo hace el método `prefix()`, por lo que si la extensión se llama `blog`, el servicio se llamará `blog.articles`. -Si necesitamos renombrar un servicio, podemos crear un alias con su nombre original para mantener la compatibilidad hacia atrás. De forma similar, esto es lo que hace Nette para, por ejemplo, `routing.router`, que también está disponible con el nombre anterior `router`. +Si necesitamos renombrar un servicio, podemos crear un alias con el nombre original para mantener la compatibilidad hacia atrás. Nette hace algo similar, por ejemplo, con el servicio `routing.router`, que también está disponible bajo el nombre anterior `router`. ```php $builder->addAlias('router', 'routing.router'); ``` -Recuperar servicios de un archivo .[#toc-retrieve-services-from-a-file] ------------------------------------------------------------------------ +Carga de servicios desde un archivo +----------------------------------- -Podemos crear servicios utilizando la API de ContainerBuilder, pero también podemos añadirlos a través del conocido archivo de configuración de NEON y su sección `services`. El prefijo `@extension` representa la extensión actual. +No tenemos que crear servicios solo usando la API de la clase ContainerBuilder, sino también con la notación familiar utilizada en el archivo de configuración NEON en la sección de servicios. El prefijo `@extension` representa la extensión actual. ```neon services: @@ -112,7 +112,7 @@ services: create: MyBlog\Components\ArticlesList(@extension.articles) ``` -Añadiremos los servicios de esta forma +Cargamos los servicios: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -121,7 +121,7 @@ class BlogExtension extends Nette\DI\CompilerExtension { $builder = $this->getContainerBuilder(); - // load the configuration file for the extension + // cargar el archivo de configuración para la extensión $this->compiler->loadDefinitionsFromConfig( $this->loadFromFile(__DIR__ . '/blog.neon')['services'], ); @@ -133,7 +133,7 @@ class BlogExtension extends Nette\DI\CompilerExtension beforeCompile() .[method] ========================= -El método es llamado cuando el contenedor contiene todos los servicios añadidos por las extensiones individuales en los métodos `loadConfiguration` así como los ficheros de configuración del usuario. En esta fase de ensamblaje, podemos modificar las definiciones de los servicios o añadir enlaces entre ellos. Puedes utilizar el método `findByTag()` para buscar servicios por etiquetas, o el método `findByType()` para buscar por clase o interfaz. +El método se llama cuando el contenedor contiene todos los servicios agregados por las extensiones individuales en los métodos `loadConfiguration` y también por los archivos de configuración del usuario. En esta etapa de construcción, podemos modificar las definiciones de servicios o agregar enlaces entre ellos. Para buscar servicios en el contenedor por tags, se puede usar el método `findByTag()`, y por clase o interfaz, el método `findByType()`. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -153,7 +153,7 @@ class BlogExtension extends Nette\DI\CompilerExtension afterCompile() .[method] ======================== -En esta fase, la clase contenedora ya está generada como un objeto [ClassType |php-generator:#classes], contiene todos los métodos que crea el servicio, y está lista para ser cacheada como fichero PHP. Todavía podemos editar el código de la clase resultante en este punto. +En esta etapa, la clase del contenedor ya está generada en forma de objeto [ClassType |php-generator:#Clases], contiene todos los métodos que crean servicios y está lista para ser escrita en la caché. Todavía podemos modificar el código de la clase resultante en este momento. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -167,24 +167,24 @@ class BlogExtension extends Nette\DI\CompilerExtension ``` -$initialization .[wiki-method] -============================== +$initialization .[method] +========================= -El Configurador llama al código de inicialización después de [container creation |application:bootstrap#index.php], que se crea escribiendo en un objeto `$this->initialization` usando [method addBody() |php-generator:#method-and-function-body]. +La clase Configurator, después de [crear el contenedor |application:bootstrapping#index.php], llama al código de inicialización, que se crea escribiendo en el objeto `$this->initialization` usando el [método addBody() |php-generator:#Cuerpos de métodos y funciones]. -Vamos a mostrar un ejemplo de cómo iniciar una sesión o iniciar servicios que tienen la etiqueta `run` usando código de inicialización: +Mostraremos un ejemplo de cómo iniciar la sesión con código de inicialización o ejecutar servicios que tienen el tag `run`: ```php class BlogExtension extends Nette\DI\CompilerExtension { public function loadConfiguration() { - // inicio automático de sesión + // inicio automático de la sesión if ($this->config->session->autoStart) { $this->initialization->addBody('$this->getService("session")->start()'); } - // los servicios con la etiqueta "run" deben crearse después de instanciar el contenedor + // los servicios con el tag run deben crearse después de instanciar el contenedor $builder = $this->getContainerBuilder(); foreach ($builder->findByTag('run') as $name => $foo) { $this->initialization->addBody('$this->getService(?);', [$name]); diff --git a/dependency-injection/es/factory.texy b/dependency-injection/es/factory.texy index 29891ce0a5..79d16143b1 100644 --- a/dependency-injection/es/factory.texy +++ b/dependency-injection/es/factory.texy @@ -2,11 +2,11 @@ Fábricas generadas ****************** .[perex] -Nette DI puede generar automáticamente código de fábrica basado en la interfaz, lo que te ahorra escribir código. +Nette DI puede generar automáticamente código de fábrica basado en interfaces, lo que le ahorra escribir código. -Una fábrica es una clase que crea y configura objetos. Por lo tanto, también les pasa sus dependencias. Por favor, no confundir con el patrón de diseño *método de fábrica*, que describe una forma específica de utilizar las fábricas y no está relacionado con este tema. +Una fábrica es una clase que produce y configura objetos. Por lo tanto, también les pasa sus dependencias. Por favor, no lo confunda con el patrón de diseño *factory method*, que describe una forma específica de usar fábricas y no está relacionado con este tema. -Hemos mostrado cómo es una fábrica de este tipo en el [capítulo introductorio |introduction#factory]: +Mostramos cómo se ve una fábrica así en el [capítulo introductorio |introduction#Fábrica]: ```php class ArticleFactory @@ -23,7 +23,7 @@ class ArticleFactory } ``` -Nette DI puede generar código de fábrica automáticamente. Todo lo que tiene que hacer es crear una interfaz y Nette DI generará una implementación. La interfaz debe tener exactamente un método llamado `create` y declarar un tipo de retorno: +Nette DI puede generar automáticamente el código de las fábricas. Todo lo que tiene que hacer es crear una interfaz y Nette DI generará la implementación. La interfaz debe tener exactamente un método llamado `create` y declarar un tipo de retorno: ```php interface ArticleFactory @@ -32,7 +32,7 @@ interface ArticleFactory } ``` -Así que la fábrica `ArticleFactory` tiene un método `create` que crea objetos `Article`. La clase `Article` puede tener, por ejemplo, el siguiente aspecto: +Es decir, la fábrica `ArticleFactory` tiene un método `create` que crea objetos `Article`. La clase `Article` podría verse así: ```php class Article @@ -44,16 +44,16 @@ class Article } ``` -Añade la factoría al fichero de configuración: +Agregamos la fábrica al archivo de configuración: ```neon services: - ArticleFactory ``` -Nette DI generará la implementación de la fábrica correspondiente. +Nette DI generará la implementación correspondiente de la fábrica. -Así, en el código que utiliza la fábrica, solicitamos el objeto por interfaz y Nette DI utiliza la implementación generada: +En el código que usa la fábrica, solicitamos el objeto según la interfaz y Nette DI usará la implementación generada: ```php class UserController @@ -65,17 +65,17 @@ class UserController public function foo() { - // let the factory create an object + // dejamos que la fábrica cree el objeto $article = $this->articleFactory->create(); } } ``` -Fábrica parametrizada .[#toc-parameterized-factory] -=================================================== +Fábrica parametrizada +===================== -El método de fábrica `create` puede aceptar parámetros que luego pasa al constructor. Por ejemplo, vamos a añadir un ID de autor de artículo a la clase `Article`: +El método de fábrica `create` puede aceptar parámetros, que luego pasa al constructor. Agreguemos, por ejemplo, a la clase `Article` el ID del autor del artículo: ```php class Article @@ -88,7 +88,7 @@ class Article } ``` -También añadiremos el parámetro a la fábrica: +También agregamos el parámetro a la fábrica: ```php interface ArticleFactory @@ -97,13 +97,13 @@ interface ArticleFactory } ``` -Como el parámetro en el constructor y el parámetro en la fábrica tienen el mismo nombre, Nette DI los pasará automáticamente. +Gracias a que el parámetro en el constructor y el parámetro en la fábrica tienen el mismo nombre, Nette DI los pasa de forma completamente automática. -Definición avanzada .[#toc-advanced-definition] -=============================================== +Definición avanzada +=================== -La definición también se puede escribir en forma multilínea utilizando la clave `implement`: +La definición también se puede escribir en forma multilínea usando la clave `implement`: ```neon services: @@ -111,9 +111,9 @@ services: implement: ArticleFactory ``` -Cuando se escribe de esta forma más larga, es posible proporcionar argumentos adicionales para el constructor en la clave `arguments` y configuración adicional usando `setup`, igual que para los servicios normales. +Al escribir de esta manera más larga, es posible especificar argumentos adicionales para el constructor en la clave `arguments` y configuración adicional usando `setup`, al igual que con los servicios normales. -Ejemplo: si el método `create()` no aceptara el parámetro `$authorId`, podríamos especificar un valor fijo en la configuración que se pasaría al constructor `Article`: +Ejemplo: si el método `create()` no aceptara el parámetro `$authorId`, podríamos especificar un valor fijo en la configuración, que se pasaría al constructor de `Article`: ```neon services: @@ -123,7 +123,7 @@ services: authorId: 123 ``` -O, por el contrario, si `create()` aceptara el parámetro `$authorId` pero no formara parte del constructor y fuera pasado por el método `Article::setAuthorId()`, haríamos referencia a él en la sección `setup`: +O viceversa, si `create()` aceptara el parámetro `$authorId`, pero no fuera parte del constructor y se pasara mediante el método `Article::setAuthorId()`, nos referiríamos a él en la sección `setup`: ```neon services: @@ -137,12 +137,11 @@ services: Accessor ======== -Además de las fábricas, Nette también puede generar los llamados accessors. Un accessor es un objeto con el método `get()` que devuelve un servicio concreto del contenedor DI. Múltiples llamadas a `get()` siempre devolverán la misma instancia. +Además de las fábricas, Nette también puede generar los llamados accessors. Son objetos con un método `get()` que devuelve un servicio específico del contenedor DI. Las llamadas repetidas a `get()` devuelven siempre la misma instancia. -Accessors trae lazy-loading a las dependencias. Tengamos una clase que registra errores en una base de datos especial. Si la conexión a la base de datos se pasara como una dependencia en su constructor, la conexión tendría que crearse siempre, aunque sólo se utilizaría en raras ocasiones cuando apareciera un error, por lo que la conexión no se utilizaría. -En su lugar, la clase puede pasar un accessor y cuando se llame a su método `get()`, sólo entonces se creará el objeto de la base de datos: +Los accessors proporcionan carga diferida (lazy-loading) a las dependencias. Supongamos que tenemos una clase que escribe errores en una base de datos especial. Si esta clase recibiera la conexión a la base de datos como dependencia a través del constructor, la conexión siempre tendría que crearse, aunque en la práctica un error ocurre solo excepcionalmente y, por lo tanto, la conexión permanecería mayormente sin usar. En lugar de eso, la clase recibe un accessor y solo cuando se llama a su `get()`, se crea el objeto de la base de datos: -¿Cómo crear un accessor? Escriba sólo una interfaz y Nette DI generará la implementación. La interfaz debe tener exactamente un método llamado `get` y debe declarar el tipo de retorno: +¿Cómo crear un accessor? Simplemente escriba una interfaz y Nette DI generará la implementación. La interfaz debe tener exactamente un método llamado `get` y declarar un tipo de retorno: ```php interface PDOAccessor @@ -151,7 +150,7 @@ interface PDOAccessor } ``` -Añade el accessor al fichero de configuración junto con la definición del servicio que devolverá el accessor: +Agregamos el accessor al archivo de configuración, donde también está la definición del servicio que devolverá: ```neon services: @@ -159,62 +158,68 @@ services: - PDO(%dsn%, %user%, %password%) ``` -El accesor devuelve un servicio de tipo `PDO` y como sólo hay un servicio de este tipo en la configuración, el accesor lo devolverá. Con múltiples servicios configurados de ese tipo puedes especificar cuál debe ser devuelto usando su nombre, por ejemplo `- PDOAccessor(@db1)`. +Dado que el accessor devuelve un servicio de tipo `PDO` y en la configuración hay un único servicio de este tipo, devolverá precisamente ese. Si hubiera más servicios del tipo dado, especificaríamos el servicio devuelto usando el nombre, por ejemplo, `- PDOAccessor(@db1)`. -Multifactory/Accessor -===================== -Hasta ahora, los factories (fábricas) y los accessors sólo podían crear o devolver un único objeto. También se puede crear un multifactory combinado con un accessor. La interfaz de esta clase multifactory puede consistir en múltiples métodos llamados `create<name>()` y `get<name>()`, por ejemplo: +Fábrica/Accessor múltiple +========================= +Hasta ahora, nuestras fábricas y accessors siempre podían producir o devolver solo un objeto. Pero también es muy fácil crear fábricas múltiples combinadas con accessors. La interfaz de tal clase contendrá cualquier número de métodos con los nombres `create<name>()` y `get<name>()`, por ejemplo: ```php interface MultiFactory { function createArticle(): Article; - function createFoo(): Model\Foo; function getDb(): PDO; } ``` -En lugar de pasar múltiples fábricas y accessors generados, puedes pasar un único multifactory complejo. +Así que en lugar de pasar varias fábricas y accessors generados, pasamos una fábrica más compleja que puede hacer más cosas. -Alternativamente, puedes usar `create()` y `get()` con un parámetro en lugar de múltiples métodos: +Alternativamente, en lugar de varios métodos, se puede usar `get()` con un parámetro: ```php interface MultiFactoryAlt { - function create($name); function get($name): PDO; } ``` -En este caso, `MultiFactory::createArticle()` hace lo mismo que `MultiFactoryAlt::create('article')`. Sin embargo, la sintaxis alternativa tiene algunas desventajas. No está claro qué valores de `$name` están soportados y el tipo de retorno no puede especificarse en la interfaz cuando se utilizan múltiples valores diferentes de `$name`. +Entonces se cumple que `MultiFactory::getArticle()` hace lo mismo que `MultiFactoryAlt::get('article')`. Sin embargo, la notación alternativa tiene la desventaja de que no está claro qué valores de `$name` son compatibles y, lógicamente, tampoco es posible distinguir diferentes valores de retorno para diferentes `$name` en la interfaz. + +Definición por lista +-------------------- +De esta manera se puede definir una fábrica múltiple en la configuración: .{data-version:3.2.0} + +```neon +services: + - MultiFactory( + article: Article # define createArticle() + db: PDO(%dsn%, %user%, %password%) # define getDb() + ) +``` -Definición con una lista .[#toc-definition-with-a-list] -------------------------------------------------------- -¿Cómo definir un multifactory en tu configuración? Vamos a crear tres servicios que serán devueltos por el multifactory, y el propio multifactory: +O podemos referirnos a servicios existentes en la definición de la fábrica usando una referencia: ```neon services: article: Article - - Model\Foo - PDO(%dsn%, %user%, %password%) - MultiFactory( - article: @article # createArticle() - foo: @Model\Foo # createFoo() - db: @\PDO # getDb() + article: @article # define createArticle() + db: @\PDO # define getDb() ) ``` -Definición con etiquetas .[#toc-definition-with-tags] ------------------------------------------------------ +Definición mediante tags +------------------------ -Otra opción para definir un multifactory es utilizar [tags|services#Tags]: +La segunda opción es usar [tags |services#Tags] para la definición: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter - App\Model\DatabaseAccessor( db1: @database.db1.explorer ) diff --git a/dependency-injection/es/faq.texy b/dependency-injection/es/faq.texy index ae21869cfa..fda627b6c1 100644 --- a/dependency-injection/es/faq.texy +++ b/dependency-injection/es/faq.texy @@ -2,97 +2,91 @@ Preguntas frecuentes sobre DI (FAQ) *********************************** -¿Es DI otro nombre para IoC? .[#toc-is-di-another-name-for-ioc] ---------------------------------------------------------------- +¿Es DI otro nombre para IoC? +---------------------------- -La *Inversion of Control* (IoC) es un principio centrado en la forma en que se ejecuta el código: si tu código inicia código externo o si tu código se integra en código externo, que luego lo llama. -IoC es un concepto amplio que incluye [eventos |nette:glossary#Events], el llamado [Principio de Hollywood |application:components#Hollywood style] y otros aspectos. -Las fábricas, que forman parte de [la Regla #3: Deja que la Fábrica se encargue |introduction#Rule #3: Let the Factory Handle It], y representan la inversión para el operador `new`, también son componentes de este concepto. +*Inversion of Control* (IoC) es un principio centrado en la forma en que se ejecuta el código: si su código ejecuta código ajeno o si su código se integra en código ajeno, que luego lo llama. IoC es un término amplio que incluye [eventos |nette:glossary#Eventos], el llamado [Principio de Hollywood |application:components#Estilo Hollywood] y otros aspectos. Parte de este concepto también son las fábricas, de las que habla la [Regla n.º 3: déjalo en manos de la fábrica |introduction#Regla nº 3: déjalo en manos de la fábrica], y que representan una inversión para el operador `new`. -La *Dependency Injection* (DI) trata sobre cómo un objeto sabe de otro objeto, es decir, la dependencia. Es un patrón de diseño que requiere el paso explícito de dependencias entre objetos. +*Dependency Injection* (DI) se centra en la forma en que un objeto conoce a otro objeto, es decir, sus dependencias. Es un patrón de diseño que requiere el paso explícito de dependencias entre objetos. -Por lo tanto, se puede decir que DI es una forma específica de IoC. Sin embargo, no todas las formas de IoC son adecuadas en términos de pureza del código. Por ejemplo, entre los anti-patrones, incluimos todas las técnicas que trabajan con [estado global |global state] o el llamado [Service Locator |#What is a Service Locator]. +Por lo tanto, se puede decir que DI es una forma específica de IoC. Sin embargo, no todas las formas de IoC son adecuadas desde el punto de vista de la limpieza del código. Por ejemplo, entre los antipatrones se encuentran técnicas que trabajan con [estado global |global-state] o el llamado [Service Locator |#Qué es Service Locator]. -¿Qué es un Service Locator? .[#toc-what-is-a-service-locator] -------------------------------------------------------------- +¿Qué es Service Locator? +------------------------ -Un Localizador de Servicios es una alternativa a la Inyección de Dependencias. Funciona creando un almacén central donde se registran todos los servicios o dependencias disponibles. Cuando un objeto necesita una dependencia, la solicita al Localizador de Servicios. +Es una alternativa a la Inyección de Dependencias. Funciona creando un repositorio central donde se registran todos los servicios o dependencias disponibles. Cuando un objeto necesita una dependencia, la solicita al Service Locator. -Sin embargo, en comparación con la Inyección de Dependencias, pierde transparencia: las dependencias no se pasan directamente a los objetos y, por tanto, no son fácilmente identificables, lo que requiere examinar el código para descubrir y comprender todas las conexiones. Las pruebas también son más complicadas, ya que no podemos simplemente pasar objetos simulados a los objetos probados, sino que tenemos que pasar por el Localizador de Servicios. Además, el Localizador de Servicios altera el diseño del código, ya que los objetos individuales deben ser conscientes de su existencia, lo que difiere de la Inyección de Dependencias, donde los objetos no tienen conocimiento del contenedor DI. +Sin embargo, en comparación con la Inyección de Dependencias, pierde transparencia: las dependencias no se pasan directamente a los objetos y no son tan fáciles de identificar, lo que requiere examinar el código para revelar y comprender todas las conexiones. Las pruebas también son más complicadas porque no podemos simplemente pasar objetos simulados (mock) a los objetos probados, sino que debemos hacerlo a través del Service Locator. Además, el Service Locator interrumpe el diseño del código, ya que los objetos individuales deben conocer su existencia, lo que difiere de la Inyección de Dependencias, donde los objetos no tienen conocimiento del contenedor DI. -¿Cuándo es mejor no utilizar DI? .[#toc-when-is-it-better-not-to-use-di] ------------------------------------------------------------------------- +¿Cuándo es mejor no usar DI? +---------------------------- -No se conocen dificultades asociadas al uso del patrón de diseño Inyección de Dependencias. Por el contrario, la obtención de dependencias desde ubicaciones globalmente accesibles conlleva una [serie de complicaciones |global-state], al igual que el uso de un Localizador de Servicios. -Por lo tanto, es aconsejable utilizar siempre DI. No se trata de un enfoque dogmático, sino que simplemente no se ha encontrado una alternativa mejor. +No se conocen dificultades asociadas con el uso del patrón de diseño de Inyección de Dependencias. Por el contrario, obtener dependencias de lugares globalmente disponibles conduce a [toda una serie de complicaciones |global-state], al igual que el uso de Service Locator. Por lo tanto, es aconsejable usar DI siempre. Esto no es un enfoque dogmático, sino simplemente que no se ha encontrado una alternativa mejor. -Sin embargo, hay ciertas situaciones en las que no nos pasamos objetos y los obtenemos del espacio global. Por ejemplo, al depurar código y necesitar volcar el valor de una variable en un punto concreto del programa, medir la duración de cierta parte del programa o registrar un mensaje. -En estos casos, en los que se trata de acciones temporales que más tarde se eliminarán del código, es legítimo utilizar un dumper, cronómetro o logger accesible globalmente. Al fin y al cabo, estas herramientas no pertenecen al diseño del código. +Sin embargo, existen ciertas situaciones en las que no pasamos objetos y los obtenemos del espacio global. Por ejemplo, al depurar código, cuando necesita imprimir el valor de una variable en un punto específico del programa, medir la duración de una parte específica del programa o registrar un mensaje. En tales casos, cuando se trata de tareas temporales que luego se eliminarán del código, es legítimo utilizar un dumper, cronómetro o logger globalmente disponible. Estas herramientas no pertenecen al diseño del código. -¿Tiene sus inconvenientes el uso de DI? .[#toc-does-using-di-have-its-drawbacks] --------------------------------------------------------------------------------- +¿Tiene el uso de DI sus desventajas? +------------------------------------ -¿El uso de la inyección de dependencias implica alguna desventaja, como una mayor complejidad en la escritura de código o un peor rendimiento? ¿Qué perdemos cuando empezamos a escribir código de acuerdo con DI? +¿Implica el uso de la Inyección de Dependencias alguna desventaja, como una mayor dificultad para escribir código o un peor rendimiento? ¿Qué perdemos cuando empezamos a escribir código de acuerdo con DI? -DI no tiene ningún impacto en el rendimiento de la aplicación ni en los requisitos de memoria. El rendimiento del contenedor DI puede influir, pero en el caso de [Nette DI | nette-container], el contenedor se compila en PHP puro, por lo que su sobrecarga durante el tiempo de ejecución de la aplicación es esencialmente nula. +DI no tiene impacto en el rendimiento ni en los requisitos de memoria de la aplicación. El rendimiento del Contenedor DI puede jugar un cierto papel, sin embargo, en el caso de [Nette DI |nette-container], el contenedor se compila en PHP puro, por lo que su sobrecarga durante la ejecución de la aplicación es esencialmente nula. -Al escribir código, es necesario crear constructores que acepten dependencias. En el pasado, esto podía llevar mucho tiempo, pero gracias a los IDEs modernos y a la [promoción de propiedades de los |https://blog.nette.org/es/php-8-0-vision-completa-de-las-novedades#toc-constructor-property-promotion] constructores, ahora es cuestión de unos segundos. Las fábricas se pueden generar fácilmente utilizando Nette DI y un plugin de PhpStorm con sólo unos clics. -Por otra parte, no hay necesidad de escribir singletons y puntos de acceso estáticos. +Al escribir código, suele ser necesario crear constructores que acepten dependencias. Antes esto podía ser tedioso, pero gracias a los IDE modernos y la [promoción de propiedades del constructor |https://blog.nette.org/es/php-8-0-resumen-completo-de-novedades#toc-promocion-de-propiedades-del-constructor], ahora es cuestión de segundos. Las fábricas se pueden generar fácilmente usando Nette DI y el plugin para PhpStorm con un clic del ratón. Por otro lado, desaparece la necesidad de escribir singletons y puntos de acceso estáticos. -Se puede concluir que una aplicación correctamente diseñada usando DI no es ni más corta ni más larga comparada con una aplicación usando singletons. Las partes del código que trabajan con dependencias simplemente se extraen de las clases individuales y se trasladan a nuevas ubicaciones, es decir, el contenedor DI y las fábricas. +Se puede afirmar que una aplicación correctamente diseñada que utiliza DI no es ni más corta ni más larga en comparación con una aplicación que utiliza singletons. Las partes del código que trabajan con dependencias simplemente se extraen de las clases individuales y se mueven a nuevos lugares, es decir, al contenedor DI y a las fábricas. -¿Cómo reescribir una aplicación heredada a DI? .[#toc-how-to-rewrite-a-legacy-application-to-di] ------------------------------------------------------------------------------------------------- +¿Cómo reescribir una aplicación legacy a DI? +-------------------------------------------- -La migración de una aplicación heredada a Inyección de Dependencias puede ser un proceso difícil, especialmente para aplicaciones grandes y complejas. Es importante abordar este proceso de forma sistemática. +La transición de una aplicación legacy a la Inyección de Dependencias puede ser un proceso desafiante, especialmente para aplicaciones grandes y complejas. Es importante abordar este proceso sistemáticamente. -- Al pasar a la inyección de dependencias, es importante que todos los miembros del equipo comprendan los principios y prácticas que se están utilizando. -- En primer lugar, realice un análisis de la aplicación existente para identificar los componentes clave y sus dependencias. Cree un plan sobre qué partes se refactorizarán y en qué orden. -- Implemente un contenedor DI o, mejor aún, utilice una biblioteca existente como Nette DI. -- Refactorizar gradualmente cada parte de la aplicación para utilizar la inyección de dependencias. Esto puede implicar modificar constructores o métodos para que acepten dependencias como parámetros. -- Modificar los lugares del código donde se crean los objetos de dependencia para que las dependencias sean inyectadas por el contenedor. Esto puede incluir el uso de fábricas. +- Al pasar a la Inyección de Dependencias, es importante que todos los miembros del equipo comprendan los principios y procedimientos que se utilizan. +- Primero, realice un análisis de la aplicación existente e identifique los componentes clave y sus dependencias. Cree un plan sobre qué partes se refactorizarán y en qué orden. +- Implemente un contenedor DI o, mejor aún, use una biblioteca existente, como Nette DI. +- Refactorice gradualmente partes individuales de la aplicación para usar la Inyección de Dependencias. Esto puede incluir modificar constructores o métodos para que acepten dependencias como parámetros. +- Modifique los lugares en el código donde se crean objetos con dependencias para que, en su lugar, las dependencias sean inyectadas por el contenedor. Esto puede incluir el uso de fábricas. -Recuerde que pasar a la inyección de dependencias es una inversión en la calidad del código y la sostenibilidad a largo plazo de la aplicación. Aunque puede ser difícil hacer estos cambios, el resultado debería ser un código más limpio, más modular y fácil de probar que esté listo para futuras extensiones y mantenimiento. +Recuerde que la transición a la Inyección de Dependencias es una inversión en la calidad del código y la mantenibilidad a largo plazo de la aplicación. Aunque puede ser desafiante realizar estos cambios, el resultado debería ser un código más limpio, modular y fácilmente comprobable, listo para futuras expansiones y mantenimiento. -¿Por qué se prefiere la composición a la herencia? .[#toc-why-composition-is-preferred-over-inheritance] --------------------------------------------------------------------------------------------------------- -Es preferible utilizar la composición en lugar de la herencia, ya que sirve al propósito de la reutilización del código sin tener que preocuparse por el efecto de goteo del cambio. Por lo tanto, proporciona un acoplamiento más laxo en el que no tenemos que preocuparnos de que el cambio de algún código provoque que otro código dependiente requiera un cambio. Un ejemplo típico es la situación identificada como [el infierno de los constructores |passing-dependencies#Constructor hell]. +¿Por qué se prefiere la composición sobre la herencia? +------------------------------------------------------ +Es preferible usar la [composición |nette:introduction-to-object-oriented-programming#Composición] en lugar de la [herencia |nette:introduction-to-object-oriented-programming#Herencia], porque sirve para reutilizar código sin tener que preocuparnos por las consecuencias de los cambios. Proporciona, por tanto, un acoplamiento más flexible, donde no tenemos que temer que un cambio en algún código provoque la necesidad de cambiar otro código dependiente. Un ejemplo típico es la situación conocida como [constructor hell |passing-dependencies#Constructor hell]. -¿Se puede utilizar Nette DI Container fuera de Nette? .[#toc-can-nette-di-container-be-used-outside-of-nette] -------------------------------------------------------------------------------------------------------------- +¿Se puede usar Nette DI Container fuera de Nette? +------------------------------------------------- -Por supuesto. El Nette DI Container es parte de Nette, pero está diseñado como una librería independiente que puede ser usada independientemente de otras partes del framework. Simplemente instálelo usando Composer, cree un archivo de configuración definiendo sus servicios, y luego use unas pocas líneas de código PHP para crear el contenedor DI. -E inmediatamente puedes empezar a aprovechar las ventajas de la Inyección de Dependencias en tus proyectos. +Definitivamente. Nette DI Container es parte de Nette, pero está diseñado como una biblioteca independiente que se puede usar independientemente de otras partes del framework. Simplemente instálelo usando Composer, cree un archivo de configuración con la definición de sus servicios y luego, usando unas pocas líneas de código PHP, cree el contenedor DI. Y puede comenzar a aprovechar de inmediato las ventajas de la Inyección de Dependencias en sus proyectos. -El capítulo [Nette DI Container |nette-container] describe cómo es un caso de uso específico, incluyendo el código. +Cómo se ve el uso concreto, incluidos los códigos, se describe en el capítulo [Nette DI Container |nette-container]. -¿Por qué la configuración está en archivos NEON? .[#toc-why-is-the-configuration-in-neon-files] ------------------------------------------------------------------------------------------------ +¿Por qué la configuración está en archivos NEON? +------------------------------------------------ -NEON es un lenguaje de configuración sencillo y fácilmente legible desarrollado dentro de Nette para configurar aplicaciones, servicios y sus dependencias. Comparado con JSON o YAML, ofrece opciones mucho más intuitivas y flexibles para este propósito. En NEON, puedes describir de forma natural enlaces que no sería posible escribir en Symfony y YAML en absoluto o sólo a través de una descripción compleja. +NEON es un lenguaje de configuración simple y fácil de leer que se desarrolló dentro de Nette para configurar aplicaciones, servicios y sus dependencias. En comparación con JSON o YAML, ofrece opciones mucho más intuitivas y flexibles para este propósito. En NEON, se pueden describir naturalmente las relaciones que en Symfony & YAML serían imposibles de escribir o solo a través de una descripción compleja. -¿El análisis de los archivos NEON ralentiza la aplicación? .[#toc-does-parsing-neon-files-slow-down-the-application] --------------------------------------------------------------------------------------------------------------------- +¿No ralentiza la aplicación el análisis de archivos NEON? +--------------------------------------------------------- -Aunque los archivos NEON se analizan muy rápidamente, este aspecto no importa realmente. La razón es que el análisis sintáctico de los archivos se produce sólo una vez durante el primer lanzamiento de la aplicación. Después de eso, el código contenedor DI se genera, se almacena en el disco, y se ejecuta para cada solicitud posterior sin necesidad de más análisis. +Aunque los archivos NEON se analizan muy rápidamente, este aspecto no importa en absoluto. La razón es que el análisis de archivos solo ocurre una vez cuando la aplicación se inicia por primera vez. Luego, se genera el código del contenedor DI, se guarda en el disco y se ejecuta en cada solicitud posterior, sin necesidad de realizar más análisis. -Así es como funciona en un entorno de producción. Durante el desarrollo, los archivos NEON se analizan cada vez que cambia su contenido, lo que garantiza que el desarrollador siempre disponga de un contenedor DI actualizado. Como se mencionó anteriormente, el análisis sintáctico real es cuestión de un instante. +Así es como funciona en un entorno de producción. Durante el desarrollo, los archivos NEON se analizan cada vez que cambia su contenido, para que el desarrollador siempre tenga un contenedor DI actualizado. El análisis en sí, como se dijo, es cuestión de un momento. -¿Cómo puedo acceder a los parámetros del archivo de configuración en mi clase? .[#toc-how-do-i-access-the-parameters-from-the-configuration-file-in-my-class] -------------------------------------------------------------------------------------------------------------------------------------------------------------- +¿Cómo accedo desde mi clase a los parámetros en el archivo de configuración? +---------------------------------------------------------------------------- -Ten en cuenta la [Regla nº 1: Déjalo que te lo pasen |introduction#Rule #1: Let It Be Passed to You]. Si una clase requiere información de un fichero de configuración, no necesitamos averiguar cómo acceder a esa información; en su lugar, simplemente la pedimos - por ejemplo, a través del constructor de la clase. Y realizamos el paso en el archivo de configuración. +Tengamos en cuenta la [Regla n.º 1: haz que te lo pasen |introduction#Regla nº 1: deja que te lo pasen]. Si una clase requiere información del archivo de configuración, no tenemos que pensar en cómo acceder a esa información, sino que simplemente la solicitamos, por ejemplo, a través del constructor de la clase. Y realizamos el paso en el archivo de configuración. -En este ejemplo, `%myParameter%` es un marcador de posición para el valor del parámetro `myParameter`, que se pasará al constructor `MyClass`: +En este ejemplo, `%myParameter%` es un marcador de posición para el valor del parámetro `myParameter`, que se pasa al constructor de la clase `MyClass`: ```php # config.neon @@ -103,10 +97,10 @@ services: - MyClass(%myParameter%) ``` -Si quieres pasar múltiples parámetros o usar autocableado, es útil [envolver los parámetros en un objeto |best-practices:passing-settings-to-presenters]. +Si desea pasar múltiples parámetros o usar autowiring, es aconsejable [envolver los parámetros en un objeto |best-practices:passing-settings-to-presenters]. -¿Admite Nette la interfaz PSR-11 Container? .[#toc-does-nette-support-psr-11-container-interface] -------------------------------------------------------------------------------------------------- +¿Soporta Nette PSR-11: Container interface? +------------------------------------------- -Nette DI Container no soporta PSR-11 directamente. Sin embargo, si necesita interoperabilidad entre el Nette DI Container y las librerías o frameworks que esperan el PSR-11 Container Interface, puede crear un [adaptador simple |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f] que sirva de puente entre el Nette DI Container y el PSR-11. +Nette DI Container no soporta PSR-11 directamente. Sin embargo, si necesita interoperabilidad entre Nette DI Container y bibliotecas o frameworks que esperan la Interfaz de Contenedor PSR-11, puede crear un [adaptador simple |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f] que sirva como puente entre Nette DI Container y PSR-11. diff --git a/dependency-injection/es/global-state.texy b/dependency-injection/es/global-state.texy index 6a5d698af1..be82e804ba 100644 --- a/dependency-injection/es/global-state.texy +++ b/dependency-injection/es/global-state.texy @@ -1,67 +1,63 @@ -Estado global y Singletons +Estado global y singletons ************************** .[perex] -Advertencia: Las siguientes construcciones son síntomas de un código mal diseñado: +Advertencia: Las siguientes construcciones son un signo de código mal diseñado: - `Foo::getInstance()` - `DB::insert(...)` - `Article::setDb($db)` - `ClassName::$var` o `static::$var` -¿Encuentra alguna de estas construcciones en su código? Si es así, tienes la oportunidad de mejorarlo. Podría pensar que se trata de construcciones comunes, vistas a menudo en soluciones de ejemplo de diversas bibliotecas y frameworks. Si es así, el diseño de su código es defectuoso. +¿Aparece alguna de estas construcciones en su código? Entonces tiene la oportunidad de mejorarlo. Quizás piense que son construcciones comunes que ve incluso en soluciones de ejemplo de varias bibliotecas y frameworks. Si es así, entonces el diseño de su código no es bueno. -No estamos hablando de una pureza académica. Todas estas construcciones tienen una cosa en común: utilizan estado global. Y esto tiene un impacto destructivo en la calidad del código. Las clases engañan sobre sus dependencias. El código se vuelve impredecible. Confunde a los desarrolladores y reduce su eficiencia. +Ahora definitivamente no estamos hablando de algún tipo de pureza académica. Todas estas construcciones tienen una cosa en común: utilizan estado global. Y eso tiene un impacto destructivo en la calidad del código. Las clases mienten sobre sus dependencias. El código se vuelve impredecible. Confunde a los programadores y reduce su eficiencia. -En este capítulo explicaremos por qué ocurre esto y cómo evitar el estado global. +En este capítulo explicaremos por qué es así y cómo evitar el estado global. -Interconexión global .[#toc-global-interlinking] ------------------------------------------------- +Acoplamiento global +------------------- -En un mundo ideal, un objeto sólo debería comunicarse con objetos que [le hayan sido pasados directamente |passing-dependencies]. Si creo dos objetos `A` y `B` y nunca paso una referencia entre ellos, entonces ni `A` ni `B` pueden acceder o modificar el estado del otro. Esta es una propiedad muy deseable del código. Es como tener una pila y una bombilla; la bombilla no se enciende hasta que la conectas a la pila con un cable. +En un mundo ideal, un objeto debería poder comunicarse solo con los objetos que le han sido [pasados directamente |passing-dependencies]. Si creo dos objetos `A` y `B` y nunca paso una referencia entre ellos, entonces ni `A` ni `B` pueden acceder al otro objeto o cambiar su estado. Esta es una propiedad muy deseable del código. Es similar a tener una batería y una bombilla; la bombilla no se encenderá hasta que la conecte a la batería con un cable. -Sin embargo, esto no es cierto para las variables globales (estáticas) o singletons. El objeto `A` podría acceder *inalámbricamente* al objeto `C` y modificarlo sin ningún paso de referencia, llamando a `C::changeSomething()`. Si el objeto `B` también accede al global `C`, entonces `A` y `B` pueden influirse mutuamente a través de `C`. +Pero esto no se aplica a las variables globales (estáticas) o singletons. El objeto `A` podría acceder *inalámbricamente* al objeto `C` y modificarlo sin pasar ninguna referencia, llamando a `C::changeSomething()`. Si el objeto `B` también toma el `C` global, entonces `A` y `B` pueden influenciarse mutuamente a través de `C`. -El uso de variables globales introduce una nueva forma de acoplamiento *inalámbrico* que no es visible externamente. Crea una cortina de humo que complica la comprensión y el uso del código. Para comprender realmente las dependencias, los desarrolladores tienen que leer cada línea del código fuente, en lugar de limitarse a familiarizarse con las interfaces de las clases. Además, este enredo es totalmente innecesario. El estado global se utiliza porque es fácilmente accesible desde cualquier lugar y permite, por ejemplo, escribir en una base de datos a través de un método global (estático) `DB::insert()`. Sin embargo, como veremos, el beneficio que ofrece es mínimo, mientras que las complicaciones que introduce son graves. +El uso de variables globales introduce en el sistema una nueva forma de acoplamiento *inalámbrico* que no es visible desde el exterior. Crea una cortina de humo que complica la comprensión y el uso del código. Para que los desarrolladores comprendan realmente las dependencias, deben leer cada línea del código fuente. En lugar de simplemente familiarizarse con la interfaz de las clases. Además, es un acoplamiento completamente innecesario. El estado global se usa porque es fácilmente accesible desde cualquier lugar y permite, por ejemplo, escribir en la base de datos a través del método global (estático) `DB::insert()`. Pero como mostraremos, la ventaja que esto aporta es insignificante, mientras que las complicaciones que causa son fatales. .[note] -En términos de comportamiento, no hay diferencia entre una variable global y una estática. Son igualmente perjudiciales. +Desde el punto de vista del comportamiento, no hay diferencia entre una variable global y una estática. Son igualmente dañinas. -La espeluznante acción a distancia .[#toc-the-spooky-action-at-a-distance] --------------------------------------------------------------------------- +Acción fantasmal a distancia +---------------------------- -"Espeluznante acción a distancia": así llamó Albert Einstein en 1935 a un fenómeno de la física cuántica que le puso los pelos de punta. -Se trata del entrelazamiento cuántico, cuya peculiaridad es que cuando se mide información sobre una partícula, afecta inmediatamente a otra, aunque estén a millones de años luz de distancia. -Lo que aparentemente viola la ley fundamental del universo de que nada puede viajar más rápido que la luz. +"Acción fantasmal a distancia" - así llamó famosamente Albert Einstein en 1935 a un fenómeno de la física cuántica que le ponía la piel de gallina. +Se trata del entrelazamiento cuántico, cuya peculiaridad es que cuando mides información sobre una partícula, influyes instantáneamente en la otra partícula, incluso si están separadas por millones de años luz. Lo cual aparentemente viola la ley fundamental del universo de que nada puede propagarse más rápido que la luz. -En el mundo del software, podemos llamar "espeluznante acción a distancia" a una situación en la que ejecutamos un proceso que creemos aislado (porque no le hemos pasado ninguna referencia), pero se producen interacciones inesperadas y cambios de estado en lugares distantes del sistema de los que no hemos informado al objeto. Esto sólo puede ocurrir a través del estado global. +En el mundo del software, podemos llamar "acción fantasmal a distancia" a una situación en la que iniciamos un proceso que creemos que está aislado (porque no le pasamos ninguna referencia), pero en lugares remotos del sistema ocurren interacciones y cambios de estado inesperados de los que no teníamos ni idea. Esto solo puede ocurrir a través del estado global. -Imagina que te unes a un equipo de desarrollo de un proyecto que tiene una base de código grande y madura. Tu nuevo jefe te pide que implementes una nueva función y, como buen desarrollador, empiezas escribiendo una prueba. Pero como eres nuevo en el proyecto, haces muchas pruebas exploratorias del tipo "qué pasa si llamo a este método". Y tratas de escribir la siguiente prueba: +Imagine que se une a un equipo de desarrolladores de un proyecto que tiene una base de código extensa y madura. Su nuevo jefe le pide que implemente una nueva función y usted, como buen desarrollador, comienza escribiendo una prueba. Pero como es nuevo en el proyecto, realiza muchas pruebas exploratorias del tipo "¿qué pasa si llamo a este método?". E intenta escribir la siguiente prueba: ```php function testCreditCardCharge() { - $cc = new CreditCard('1234567890123456', 5, 2028); // su número de tarjeta + $cc = new CreditCard('1234567890123456', 5, 2028); // número de su tarjeta $cc->charge(100); } ``` -Ejecutas el código, tal vez varias veces, y después de un tiempo notas notificaciones en tu teléfono del banco que cada vez que lo ejecutas, $100 fueron cargados a tu tarjeta de crédito 🤦‍♂️ +Ejecuta el código, quizás varias veces, y después de un tiempo nota notificaciones en su móvil del banco de que cada vez que se ejecuta, se cargan 100 dólares a su tarjeta de crédito 🤦‍♂️ -¿Cómo diablos pudo la prueba causar un cargo real? No es fácil operar con tarjeta de crédito. Tienes que interactuar con un servicio web de terceros, tienes que conocer la URL de ese servicio web, tienes que iniciar sesión, etc. -Ninguna de estas informaciones se incluye en la prueba. Peor aún, ni siquiera sabes dónde está presente esta información y, por lo tanto, cómo simular las dependencias externas para que cada ejecución no suponga un nuevo cargo de 100 dólares. Y como nuevo desarrollador, ¿cómo ibas a saber que lo que estabas a punto de hacer te llevaría a ser 100 dólares más pobre? +¿Cómo diablos pudo la prueba causar un cargo real de dinero? Operar con una tarjeta de crédito no es fácil. Debe comunicarse con un servicio web de terceros, debe conocer la URL de este servicio web, debe iniciar sesión, etc. Ninguna de esta información está contenida en la prueba. Peor aún, ni siquiera sabe dónde está presente esta información y, por lo tanto, tampoco cómo simular (mock) las dependencias externas para que cada ejecución no resulte en que se carguen nuevamente 100 dólares. ¿Y cómo se suponía que usted, como nuevo desarrollador, supiera que lo que estaba a punto de hacer le haría 100 dólares más pobre? -¡Eso es una acción espeluznante a distancia! +¡Eso es acción fantasmal a distancia! -No te queda más remedio que escarbar en un montón de código fuente, preguntando a colegas más veteranos y experimentados, hasta que entiendes cómo funcionan las conexiones en el proyecto. -Esto se debe al hecho de que al mirar la interfaz de la clase `CreditCard`, no puedes determinar el estado global que necesita ser inicializado. Incluso mirando el código fuente de la clase no le dirá qué método de inicialización para llamar. Como mucho, puedes encontrar la variable global a la que se accede e intentar adivinar cómo inicializarla a partir de ahí. +No le queda más remedio que rebuscar durante mucho tiempo en un montón de código fuente, preguntar a colegas más antiguos y experimentados, antes de comprender cómo funcionan las conexiones en el proyecto. Esto se debe a que al mirar la interfaz de la clase `CreditCard`, no se puede determinar el estado global que debe inicializarse. Incluso mirar el código fuente de la clase no le dice qué método de inicialización debe llamar. En el mejor de los casos, puede encontrar una variable global a la que se accede y, a partir de ella, intentar adivinar cómo inicializarla. -Las clases de un proyecto así son mentirosas patológicas. La tarjeta de pago finge que puedes simplemente instanciarla y llamar al método `charge()`. Sin embargo, secretamente interactúa con otra clase, `PaymentGateway`. Incluso su interfaz dice que se puede inicializar de forma independiente, pero en realidad extrae credenciales de algún archivo de configuración y demás. -Está claro para los desarrolladores que escribieron este código que `CreditCard` necesita a `PaymentGateway`. Ellos escribieron el código de esta manera. Pero para cualquiera que sea nuevo en el proyecto, esto es un completo misterio y dificulta el aprendizaje. +Las clases en tal proyecto son mentirosas patológicas. La tarjeta de crédito finge que basta con instanciarla y llamar al método `charge()`. En secreto, sin embargo, colabora con otra clase `PaymentGateway`, que representa la pasarela de pago. Incluso su interfaz dice que se puede inicializar por separado, pero en realidad extrae credenciales de algún archivo de configuración, etc. Para los desarrolladores que escribieron este código, está claro que `CreditCard` necesita `PaymentGateway`. Escribieron el código de esta manera. Pero para cualquiera que sea nuevo en el proyecto, es un completo misterio y dificulta el aprendizaje. -¿Cómo arreglar la situación? Fácil. **Deja que la API declare las dependencias.** +¿Cómo arreglar la situación? Fácilmente. **Deje que la API declare las dependencias.** ```php function testCreditCardCharge() @@ -72,36 +68,35 @@ function testCreditCardCharge() } ``` -Observa cómo las relaciones dentro del código son repentinamente obvias. Al declarar que el método `charge()` necesita `PaymentGateway`, no tienes que preguntar a nadie cómo el código es interdependiente. Sabes que tienes que crear una instancia del mismo, y cuando intentas hacerlo, te encuentras con el hecho de que tienes que suministrar parámetros de acceso. Sin ellos, el código ni siquiera se ejecutaría. +Observe cómo de repente las interconexiones dentro del código son obvias. Al declarar el método `charge()` que necesita `PaymentGateway`, no tiene que preguntar a nadie cómo está interconectado el código. Sabe que debe crear su instancia, y cuando intenta hacerlo, se da cuenta de que debe proporcionar los parámetros de acceso. Sin ellos, el código ni siquiera se ejecutaría. -Y lo más importante, ahora puedes simular la pasarela de pago para que no te cobren 100 dólares cada vez que ejecutes una prueba. +Y lo más importante, ahora puede simular (mock) la pasarela de pago, para que no se le cobren 100 dólares cada vez que ejecute la prueba. -El estado global hace que tus objetos puedan acceder secretamente a cosas que no están declaradas en sus APIs, y como resultado hace que tus APIs sean mentirosas patológicas. +El estado global hace que sus objetos puedan acceder en secreto a cosas que no están declaradas en su API y, como resultado, convierten sus API en mentirosos patológicos. -Puede que no lo hayas pensado así antes, pero siempre que usas estado global, estás creando canales secretos de comunicación inalámbrica. La espeluznante acción remota obliga a los desarrolladores a leer cada línea de código para entender las posibles interacciones, reduce la productividad de los desarrolladores y confunde a los nuevos miembros del equipo. -Si eres tú quien ha creado el código, conoces las dependencias reales, pero cualquiera que venga después no tiene ni idea. +Quizás no lo había pensado así antes, pero cada vez que usa estado global, está creando canales de comunicación inalámbricos secretos. La acción fantasmal a distancia obliga a los desarrolladores a leer cada línea de código para comprender las interacciones potenciales, reduce la productividad de los desarrolladores y confunde a los nuevos miembros del equipo. Si usted es quien creó el código, conoce las dependencias reales, pero cualquiera que venga después de usted está perdido. -No escribas código que utilice estado global, prefiere pasar dependencias. Es decir, inyección de dependencias. +No escriba código que utilice estado global, dé preferencia al paso de dependencias. Es decir, inyección de dependencias. -La fragilidad del Estado mundial .[#toc-brittleness-of-the-global-state] ------------------------------------------------------------------------- +Fragilidad del estado global +---------------------------- -En código que utiliza estado global y singletons, nunca se sabe con certeza cuándo y por quién ha cambiado ese estado. Este riesgo ya está presente en la inicialización. El siguiente código se supone que debe crear una conexión a la base de datos e inicializar la pasarela de pago, pero sigue lanzando una excepción y encontrar la causa es extremadamente tedioso: +En el código que utiliza estado global y singletons, nunca se sabe cuándo y quién cambió este estado. Este riesgo aparece ya durante la inicialización. El siguiente código debe crear una conexión a la base de datos e inicializar la pasarela de pago, pero constantemente lanza una excepción y encontrar la causa es extremadamente tedioso: ```php PaymentGateway::init(); DB::init('mysql:', 'user', 'password'); ``` -Hay que revisar el código en detalle para descubrir que el objeto `PaymentGateway` accede a otros objetos de forma inalámbrica, algunos de los cuales requieren una conexión a la base de datos. Así, debe inicializar la base de datos antes de `PaymentGateway`. Sin embargo, la cortina de humo del estado global te lo oculta. ¿Cuánto tiempo ahorrarías si la API de cada clase no mintiera y declarara sus dependencias? +Debe examinar detenidamente el código para descubrir que el objeto `PaymentGateway` accede de forma inalámbrica a otros objetos, algunos de los cuales requieren una conexión a la base de datos. Por lo tanto, es necesario inicializar la base de datos antes que `PaymentGateway`. Sin embargo, la cortina de humo del estado global le oculta esto. ¿Cuánto tiempo habría ahorrado si la API de las clases individuales no engañara y declarara sus dependencias? ```php $db = new DB('mysql:', 'user', 'password'); $gateway = new PaymentGateway($db, ...); ``` -Un problema similar surge cuando se utiliza el acceso global a una conexión de base de datos: +Un problema similar surge también al usar acceso global a la conexión de la base de datos: ```php use Illuminate\Support\Facades\DB; @@ -115,9 +110,9 @@ class Article } ``` -Cuando se llama al método `save()`, no se sabe con certeza si ya se ha creado una conexión a la base de datos y quién es el responsable de crearla. Por ejemplo, si quisiéramos cambiar la conexión a la base de datos sobre la marcha, quizás con fines de prueba, probablemente tendríamos que crear métodos adicionales como `DB::reconnect(...)` o `DB::reconnectForTest()`. +Al llamar al método `save()`, no está claro si ya se ha creado la conexión a la base de datos y quién es responsable de su creación. Si quisiéramos, por ejemplo, cambiar la conexión a la base de datos sobre la marcha, por ejemplo, para pruebas, probablemente tendríamos que crear métodos adicionales como `DB::reconnect(...)` o `DB::reconnectForTest()`. -Veamos un ejemplo: +Consideremos un ejemplo: ```php $article = new Article; @@ -127,9 +122,9 @@ Foo::doSomething(); $article->save(); ``` -¿Dónde podemos estar seguros de que se está utilizando realmente la base de datos de prueba cuando se llama a `$article->save()`? ¿Qué pasaría si el método `Foo::doSomething()` cambiara la conexión global a la base de datos? Para averiguarlo, tendríamos que examinar el código fuente de la clase `Foo` y probablemente de muchas otras clases. Sin embargo, este enfoque sólo proporcionaría una respuesta a corto plazo, ya que la situación podría cambiar en el futuro. +¿Dónde tenemos la certeza de que al llamar a `$article->save()` se está utilizando realmente la base de datos de prueba? ¿Qué pasa si el método `Foo::doSomething()` cambió la conexión global a la base de datos? Para averiguarlo, tendríamos que examinar el código fuente de la clase `Foo` y probablemente de muchas otras clases. Sin embargo, este enfoque solo proporcionaría una respuesta a corto plazo, ya que la situación puede cambiar en el futuro. -¿Y si trasladamos la conexión a la base de datos a una variable estática dentro de la clase `Article`? +¿Y si movemos la conexión a la base de datos a una variable estática dentro de la clase `Article`? ```php class Article @@ -148,11 +143,11 @@ class Article } ``` -Esto no cambia nada en absoluto. El problema es un estado global y no importa en qué clase se esconda. En este caso, como en el anterior, no tenemos ni idea de en qué base de datos se está escribiendo cuando se llama al método `$article->save()`. Cualquiera en el extremo distante de la aplicación podría cambiar la base de datos en cualquier momento usando `Article::setDb()`. Bajo nuestras manos. +Esto no cambia nada en absoluto. El problema es el estado global y es completamente irrelevante en qué clase se esconde. En este caso, al igual que en el anterior, al llamar al método `$article->save()` no tenemos ninguna pista sobre en qué base de datos se escribirá. Cualquiera en el otro extremo de la aplicación podría haber cambiado la base de datos en cualquier momento usando `Article::setDb()`. Bajo nuestras narices. El estado global hace que nuestra aplicación sea **extremadamente frágil**. -Sin embargo, hay una forma sencilla de lidiar con este problema. Basta con hacer que la API declare dependencias para garantizar una funcionalidad adecuada. +Sin embargo, existe una forma sencilla de abordar este problema. Simplemente deje que la API declare las dependencias, lo que garantizará la funcionalidad correcta. ```php class Article @@ -174,15 +169,15 @@ Foo::doSomething(); $article->save(); ``` -Este enfoque elimina la preocupación de cambios ocultos e inesperados en las conexiones a la base de datos. Ahora estamos seguros de dónde se almacena el artículo y ninguna modificación de código dentro de otra clase no relacionada puede cambiar la situación nunca más. El código ya no es frágil, sino estable. +Gracias a este enfoque, desaparece la preocupación por cambios ocultos e inesperados en la conexión a la base de datos. Ahora tenemos la certeza de dónde se guarda el artículo y ninguna modificación del código dentro de otra clase no relacionada puede cambiar la situación. El código ya no es frágil, sino estable. -No escribas código que use estado global, prefiere pasar dependencias. Por lo tanto, la inyección de dependencia. +No escriba código que utilice estado global, dé preferencia al paso de dependencias. Es decir, inyección de dependencias. -Singleton .[#toc-singleton] ---------------------------- +Singleton +--------- -Singleton es un patrón de diseño que, por [definición |https://en.wikipedia.org/wiki/Singleton_pattern] de la famosa publicación Gang of Four, restringe una clase a una única instancia y ofrece acceso global a la misma. La implementación de este patrón suele parecerse al siguiente código: +Singleton es un patrón de diseño que, según la "definición":https://en.wikipedia.org/wiki/Singleton_pattern de la conocida publicación Gang of Four, restringe una clase a una única instancia y ofrece acceso global a ella. La implementación de este patrón generalmente se asemeja al siguiente código: ```php class Singleton @@ -195,38 +190,35 @@ class Singleton return self::$instance; } - // y otros métodos que realizan las funciones de la clase + // y otros métodos que cumplen las funciones de la clase dada } ``` -Desafortunadamente, el singleton introduce estado global en la aplicación. Y como hemos demostrado anteriormente, el estado global no es deseable. Por eso el singleton se considera un antipatrón. +Desafortunadamente, singleton introduce estado global en la aplicación. Y como hemos mostrado anteriormente, el estado global es indeseable. Por lo tanto, singleton se considera un antipatrón. -No utilices singletons en tu código y sustitúyelos por otros mecanismos. Realmente no necesitas singletons. Sin embargo, si necesitas garantizar la existencia de una única instancia de una clase para toda la aplicación, déjalo en manos del [contenedor DI |container]. -Por lo tanto, cree un singleton de aplicación, o servicio. Esto evitará que la clase proporcione su propia unicidad (es decir, no tendrá un método `getInstance()` y una variable estática) y sólo realizará sus funciones. Así, dejará de violar el principio de responsabilidad única. +No use singletons en su código y reemplácelos con otros mecanismos. Realmente no necesita singletons. Sin embargo, si necesita garantizar la existencia de una única instancia de una clase para toda la aplicación, déjelo en manos del [contenedor DI |container]. Cree así un singleton de aplicación, o servicio. De esta manera, la clase dejará de ocuparse de garantizar su propia unicidad (es decir, no tendrá el método `getInstance()` ni la variable estática) y cumplirá solo sus funciones. Así dejará de violar el principio de responsabilidad única. -Estado global frente a pruebas .[#toc-global-state-versus-tests] ----------------------------------------------------------------- +Estado global versus pruebas +---------------------------- -Cuando escribimos pruebas, asumimos que cada prueba es una unidad aislada y que ningún estado externo entra en ella. Y ningún estado sale de las pruebas. Cuando una prueba se completa, cualquier estado asociado con la prueba debe ser eliminado automáticamente por el recolector de basura. Esto hace que las pruebas estén aisladas. Por lo tanto, podemos ejecutar las pruebas en cualquier orden. +Al escribir pruebas, asumimos que cada prueba es una unidad aislada y que ningún estado externo entra en ella. Y ningún estado sale de las pruebas. Después de completar una prueba, todo el estado relacionado con la prueba debería ser eliminado automáticamente por el recolector de basura. Gracias a esto, las pruebas están aisladas. Por lo tanto, podemos ejecutar las pruebas en cualquier orden. -Sin embargo, si hay estados/singletons globales, todas estas suposiciones se vienen abajo. Un estado puede entrar y salir de una prueba. De repente, el orden de las pruebas puede ser importante. +Sin embargo, si hay estados globales/singletons presentes, todas estas agradables suposiciones se desmoronan. El estado puede entrar y salir de la prueba. De repente, el orden de las pruebas puede importar. -Para probar los singletons, los desarrolladores a menudo tienen que relajar sus propiedades, tal vez permitiendo que una instancia sea sustituida por otra. Estas soluciones son, en el mejor de los casos, trucos que producen un código difícil de mantener y comprender. Cualquier prueba o método `tearDown()` que afecte a cualquier estado global debe deshacer esos cambios. +Para poder probar los singletons, los desarrolladores a menudo tienen que relajar sus propiedades, por ejemplo, permitiendo que la instancia sea reemplazada por otra. Tales soluciones son, en el mejor de los casos, un hack que crea código difícil de mantener y comprender. Cada prueba o método `tearDown()` que afecte a cualquier estado global debe revertir estos cambios. -El estado global es el mayor dolor de cabeza en las pruebas unitarias. +¡El estado global es el mayor dolor de cabeza en las pruebas unitarias! -¿Cómo arreglar la situación? Fácil. No escribas código que utilice singletons, prefiere pasar dependencias. Es decir, inyección de dependencias. +¿Cómo arreglar la situación? Fácilmente. No escriba código que utilice singletons, dé preferencia al paso de dependencias. Es decir, inyección de dependencias. -Constantes globales .[#toc-global-constants] --------------------------------------------- +Constantes globales +------------------- -El estado global no se limita al uso de singletons y variables estáticas, sino que también puede aplicarse a las constantes globales. +El estado global no se limita solo al uso de singletons y variables estáticas, sino que también puede referirse a constantes globales. -Las constantes cuyo valor no nos proporciona ninguna información nueva (`M_PI`) o útil (`PREG_BACKTRACK_LIMIT_ERROR`) están claramente bien. -Por el contrario, las constantes que sirven para pasar información de forma *inalámbrica* dentro del código no son más que una dependencia oculta. Como `LOG_FILE` en el siguiente ejemplo. -Utilizar la constante `FILE_APPEND` es perfectamente correcto. +Las constantes cuyo valor no nos aporta ninguna información nueva (`M_PI`) o útil (`PREG_BACKTRACK_LIMIT_ERROR`) están claramente bien. Por el contrario, las constantes que sirven como una forma de pasar información *inalámbricamente* al código no son más que una dependencia oculta. Como `LOG_FILE` en el siguiente ejemplo. El uso de la constante `FILE_APPEND` es completamente correcto. ```php const LOG_FILE = '...'; @@ -242,7 +234,7 @@ class Foo } ``` -En este caso, debemos declarar el parámetro en el constructor de la clase `Foo` para que forme parte de la API: +En este caso, deberíamos declarar un parámetro en el constructor de la clase `Foo` para que se convierta en parte de la API: ```php class Foo @@ -261,44 +253,42 @@ class Foo } ``` -Ahora podemos pasar información sobre la ruta al archivo de registro y cambiarla fácilmente según sea necesario, lo que facilita las pruebas y el mantenimiento del código. +Ahora podemos pasar la información sobre la ruta al archivo de registro y cambiarla fácilmente según sea necesario, lo que facilita las pruebas y el mantenimiento del código. -Funciones globales y métodos estáticos .[#toc-global-functions-and-static-methods] ----------------------------------------------------------------------------------- +Funciones globales y métodos estáticos +-------------------------------------- -Queremos enfatizar que el uso de métodos estáticos y funciones globales no es problemático en sí mismo. Hemos explicado lo inapropiado de usar `DB::insert()` y métodos similares, pero siempre ha sido una cuestión de estado global almacenado en una variable estática. El método `DB::insert()` requiere la existencia de una variable estática porque almacena la conexión a la base de datos. Sin esta variable, sería imposible implementar el método. +Queremos enfatizar que el uso de métodos estáticos y funciones globales en sí mismo no es problemático. Explicamos por qué el uso de `DB::insert()` y métodos similares es inapropiado, pero siempre se trató solo de una cuestión de estado global, que se almacena en alguna variable estática. El método `DB::insert()` requiere la existencia de una variable estática porque la conexión a la base de datos se almacena en ella. Sin esta variable, sería imposible implementar el método. -El uso de métodos y funciones estáticas deterministas, como `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` y muchos otros, es perfectamente coherente con la inyección de dependencias. Estas funciones siempre devuelven los mismos resultados a partir de los mismos parámetros de entrada y, por lo tanto, son predecibles. No utilizan ningún estado global. +El uso de métodos estáticos y funciones deterministas, como `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` y muchas otras, está en perfecta consonancia con la inyección de dependencias. Estas funciones siempre devuelven los mismos resultados para los mismos parámetros de entrada y, por lo tanto, son predecibles. No utilizan ningún estado global. -Sin embargo, hay funciones en PHP que no son deterministas. Estas incluyen, por ejemplo, la función `htmlspecialchars()`. Su tercer parámetro, `$encoding`, si no se especifica, toma por defecto el valor de la opción de configuración `ini_get('default_charset')`. Por lo tanto, se recomienda especificar siempre este parámetro para evitar un posible comportamiento impredecible de la función. Nette lo hace sistemáticamente. +Sin embargo, también existen funciones en PHP que no son deterministas. Entre ellas se encuentra, por ejemplo, la función `htmlspecialchars()`. Su tercer parámetro `$encoding`, si no se especifica, tiene como valor predeterminado el valor de la opción de configuración `ini_get('default_charset')`. Por lo tanto, se recomienda especificar siempre este parámetro y evitar así un posible comportamiento impredecible de la función. Nette lo hace consistentemente. -Algunas funciones, como `strtolower()`, `strtoupper()`, y similares, han tenido un comportamiento no determinista en el pasado reciente y han dependido del parámetro `setlocale()`. Esto causaba muchas complicaciones, sobre todo cuando se trabajaba con el idioma turco. -Esto se debe a que el idioma turco distingue entre mayúsculas y minúsculas `I` con y sin punto. Así que `strtolower('I')` devolvía el carácter `ı` y `strtoupper('i')` devolvía el carácter `İ`, lo que provocaba en las aplicaciones una serie de misteriosos errores. -Sin embargo, este problema se solucionó en la versión 8.2 de PHP y las funciones ya no dependen de la configuración regional. +Algunas funciones, como `strtolower()`, `strtoupper()` y similares, en el pasado reciente se comportaron de forma no determinista y dependían de la configuración de `setlocale()`. Esto causó muchas complicaciones, más comúnmente al trabajar con el idioma turco. Este distingue entre las letras `I` mayúscula y minúscula con y sin punto. Así que `strtolower('I')` devolvía el carácter `ı` y `strtoupper('i')` el carácter `İ`, lo que provocó que las aplicaciones comenzaran a causar una serie de errores misteriosos. Sin embargo, este problema se solucionó en la versión 8.2 de PHP y las funciones ya no dependen de la configuración regional (locale). -Este es un buen ejemplo de cómo el estado global ha plagado a miles de desarrolladores en todo el mundo. La solución fue reemplazarlo con inyección de dependencia. +Este es un buen ejemplo de cómo el estado global atormentó a miles de desarrolladores en todo el mundo. La solución fue reemplazarlo por inyección de dependencias. -¿Cuándo es posible utilizar el Estado Global? .[#toc-when-is-it-possible-to-use-global-state] ---------------------------------------------------------------------------------------------- +¿Cuándo es posible usar estado global? +-------------------------------------- -Hay ciertas situaciones específicas en las que es posible utilizar el estado global. Por ejemplo, cuando se depura código y se necesita volcar el valor de una variable o medir la duración de una parte concreta del programa. En estos casos, que se refieren a acciones temporales que más tarde se eliminarán del código, es legítimo utilizar un dumper o un cronómetro disponibles globalmente. Estas herramientas no forman parte del diseño del código. +Existen ciertas situaciones específicas en las que es posible utilizar el estado global. Por ejemplo, al depurar código, cuando necesita imprimir el valor de una variable o medir la duración de una parte específica del programa. En tales casos, que se refieren a acciones temporales que luego se eliminarán del código, es legítimo utilizar un dumper o cronómetro globalmente disponible. Estas herramientas no forman parte del diseño del código. -Otro ejemplo son las funciones para trabajar con expresiones regulares `preg_*`, que almacenan internamente expresiones regulares compiladas en una caché estática en memoria. Cuando se llama a la misma expresión regular varias veces en distintas partes del código, sólo se compila una vez. La caché ahorra rendimiento y además es completamente invisible para el usuario, por lo que este uso puede considerarse legítimo. +Otro ejemplo son las funciones para trabajar con expresiones regulares `preg_*`, que almacenan internamente las expresiones regulares compiladas en una caché estática en memoria. Por lo tanto, cuando llama a la misma expresión regular varias veces en diferentes lugares del código, solo se compila una vez. La caché ahorra rendimiento y, al mismo tiempo, es completamente invisible para el usuario, por lo que dicho uso puede considerarse legítimo. -Resumen .[#toc-summary] ------------------------ +Resumen +------- -Hemos demostrado por qué tiene sentido +Hemos discutido por qué tiene sentido: 1) Eliminar todas las variables estáticas del código 2) Declarar dependencias -3) Y utilizar la inyección de dependencias +3) Y usar inyección de dependencias -Cuando contemples el diseño del código, ten en cuenta que cada `static $foo` representa un problema. Para que tu código sea un entorno respetuoso con DI, es esencial erradicar por completo el estado global y sustituirlo por la inyección de dependencias. +Al pensar en el diseño del código, tenga en cuenta que cada `static $foo` representa un problema. Para que su código sea un entorno que respete DI, es esencial erradicar por completo el estado global y reemplazarlo mediante inyección de dependencias. -Durante este proceso, puede que descubras que necesitas dividir una clase porque tiene más de una responsabilidad. No te preocupes por ello; esfuérzate por el principio de una sola responsabilidad. +Durante este proceso, puede descubrir que es necesario dividir la clase porque tiene más de una responsabilidad. No tenga miedo de eso; esfuércese por el principio de responsabilidad única. -*Me gustaría dar las gracias a Miško Hevery, cuyos artículos como [Flaw: Brittle Global State & Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/] forman la base de este capítulo.* +*Me gustaría agradecer a Miško Hevery, cuyos artículos, como [Flaw: Brittle Global State & Singletons |https://web.archive.org/web/20230321084133/http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/], son la base de este capítulo.* diff --git a/dependency-injection/es/introduction.texy b/dependency-injection/es/introduction.texy index 4531425f04..8655556c51 100644 --- a/dependency-injection/es/introduction.texy +++ b/dependency-injection/es/introduction.texy @@ -1,58 +1,58 @@ -¿Qué es la inyección de dependencias? +¿Qué es la Inyección de Dependencias? ************************************* .[perex] -Este capítulo te introduce a las prácticas básicas de programación que debes seguir al escribir cualquier aplicación. Estos son los fundamentos necesarios para escribir código limpio, comprensible y mantenible. +Este capítulo le introducirá en las prácticas básicas de programación que debe seguir al escribir todas las aplicaciones. Estos son los fundamentos necesarios para escribir código limpio, comprensible y mantenible. -Si aprendes y sigues estas reglas, el Nette estará ahí para ti en cada paso del camino. Se encargará de las tareas rutinarias por ti y te hará sentir lo más cómodo posible para que puedas centrarte en la propia lógica. +Si adopta estas reglas y las sigue, Nette le ayudará en cada paso del camino. Se encargará de las tareas rutinarias por usted y le proporcionará la máxima comodidad, para que pueda centrarse en la lógica en sí. -Los principios que mostraremos aquí son bastante simples. No tienes nada de qué preocuparte. +Los principios que mostraremos aquí son bastante simples. No tiene que preocuparse por nada. -¿Recuerdas tu primer programa? .[#toc-remember-your-first-program] ------------------------------------------------------------------- +¿Recuerda su primer programa? +----------------------------- -No tenemos idea en qué lenguaje lo escribiste, pero si fuera PHP, probablemente se vería algo como esto: +No sabemos en qué lenguaje lo escribió, pero si fue PHP, probablemente se parecía a esto: ```php -function suma(float $a, float $b): float +function soucet(float $a, float $b): float { return $a + $b; } -echo suma(23, 1); // imprime 24 +echo soucet(23, 1); // imprime 24 ``` -Unas pocas líneas de código triviales, pero en las que se esconden muchos conceptos clave. Vemos que hay variables. Que el código se descompone en unidades más pequeñas, que son funciones, por ejemplo. Que les pasamos argumentos de entrada y devuelven resultados. Lo único que falta son condiciones y bucles. +Unas pocas líneas triviales de código, pero contienen muchos conceptos clave. Que existen variables. Que el código se divide en unidades más pequeñas, como funciones. Que les pasamos argumentos de entrada y devuelven resultados. Solo faltan las condiciones y los bucles. -El hecho de que pasemos argumentos de entrada a una función y ésta devuelva un resultado es un concepto perfectamente comprensible que se utiliza en otros campos, como las matemáticas. +El hecho de que pasemos datos de entrada a una función y esta devuelva un resultado es un concepto perfectamente comprensible que se utiliza en otros campos, como las matemáticas. -Una función tiene una firma, que consiste en su nombre, una lista de parámetros y sus tipos y, por último, el tipo de valor de retorno. Como usuarios, lo que nos interesa es la firma; normalmente no necesitamos saber nada sobre la implementación interna. +Una función tiene su firma, que consiste en su nombre, una lista de parámetros y sus tipos, y finalmente el tipo de valor de retorno. Como usuarios, nos interesa la firma, normalmente no necesitamos saber nada sobre la implementación interna. -Ahora imagina que la firma de una función tiene este aspecto +Ahora imagine que la firma de la función fuera así: ```php -function suma(float $x): float +function soucet(float $x): float ``` -¿Una suma con un solo parámetro? Eso es raro... ¿Qué tal esto? +¿Una suma con un solo parámetro? Eso es extraño... ¿Y qué tal así? ```php -function suma(): float +function soucet(): float ``` -Eso sí que es raro, ¿no? ¿Cómo crees que se usa la función? +Esto ya es realmente muy extraño, ¿verdad? ¿Cómo se usaría la función? ```php -echo suma(); // ¿Qué imprime? +echo soucet(); // ¿qué imprimirá? ``` -Mirando este código, estamos confundidos. No sólo un principiante no lo entendería, incluso un programador experto no entendería tal código. +Al ver tal código, estaríamos confundidos. No solo un principiante no lo entendería, sino que incluso un programador experimentado no comprendería este código. -¿Nos preguntamos cómo sería una función así por dentro? ¿De dónde sacaría los sumandos? Probablemente los obtendría *de alguna manera* por sí misma, así: +¿Se pregunta cómo sería realmente tal función por dentro? ¿De dónde obtendría los sumandos? Aparentemente, los obtendría *de alguna manera* por sí misma, tal vez así: ```php -function suma(): float +function soucet(): float { $a = Input::get('a'); $b = Input::get('b'); @@ -60,71 +60,71 @@ function suma(): float } ``` -Resulta que hay enlaces ocultos a otras funciones (o métodos estáticos) en el cuerpo de la función, y para averiguar de dónde vienen realmente las sumas, tenemos que indagar más. +En el cuerpo de la función, descubrimos dependencias ocultas a otras funciones globales o métodos estáticos. Para averiguar de dónde provienen realmente los sumandos, debemos investigar más a fondo. -Así no! .[#toc-not-this-way] ----------------------------- +¡Por aquí no! +------------- El diseño que acabamos de mostrar es la esencia de muchas características negativas: -- la firma de la función pretendía que no necesitaba sumandos, lo que nos confundió. -- no tenemos ni idea de cómo hacer que la función calcule con otros dos números. -- tuvimos que mirar en el código para ver de dónde toma los sumandos. -- descubrimos ligaduras ocultas. -- para entenderlo completamente, necesitamos explorar también estas ligaduras. +- la firma de la función pretendía no necesitar sumandos, lo que nos confundió +- no sabemos en absoluto cómo hacer que la función sume otros dos números +- tuvimos que mirar el código para averiguar de dónde obtenía los sumandos +- descubrimos dependencias ocultas +- para una comprensión completa, también es necesario examinar estas dependencias -¿Y acaso la función de suma se encarga de obtener entradas? Por supuesto que no. Su única responsabilidad es sumar. +¿Y es siquiera tarea de la función de suma obtener las entradas? Por supuesto que no. Su responsabilidad es únicamente la suma en sí. -No queremos encontrarnos con un código así, y desde luego no queremos escribirlo. El remedio es simple: volver a lo básico y usar sólo parámetros: +No queremos encontrarnos con tal código, y definitivamente no queremos escribirlo. La solución es simple: volver a lo básico y simplemente usar parámetros: ```php -function suma(float $a, float $b): float +function soucet(float $a, float $b): float { return $a + $b; } ``` -Regla nº 1: Déjalo que te lo pasen .[#toc-rule-1-let-it-be-passed-to-you] -------------------------------------------------------------------------- +Regla nº 1: deja que te lo pasen +-------------------------------- -La regla más importante es: **todos los datos que necesiten las funciones o clases deben pasárseles**. +La regla más importante es: **todos los datos que las funciones o clases necesitan deben serles pasados**. -En lugar de inventar mecanismos ocultos para que de alguna manera lleguen a ellos por sí mismos, simplemente pásales los parámetros. Ahorrarás el tiempo que lleva inventar mecanismos ocultos, que definitivamente no mejorarán tu código. +En lugar de inventar formas ocultas para que puedan obtenerlos por sí mismos, simplemente pase los parámetros. Ahorrará tiempo necesario para inventar caminos ocultos, que definitivamente no mejorarán su código. -Si sigues esta regla siempre y en todas partes, estarás en camino hacia un código sin enlaces ocultos. Hacia un código que sea comprensible no sólo para el autor, sino también para cualquiera que lo lea después. Donde todo es comprensible desde las firmas de funciones y clases y no hay necesidad de buscar secretos ocultos en la implementación. +Si sigue esta regla siempre y en todas partes, estará en camino hacia un código sin dependencias ocultas. Hacia un código que sea comprensible no solo para el autor, sino también para cualquiera que lo lea después. Donde todo es comprensible a partir de las firmas de funciones y clases y no es necesario buscar secretos ocultos en la implementación. -Esta técnica se denomina de forma experta **inyección de dependencias**. Y los datos se llaman **dependencias.** Pero es un simple paso de parámetros, nada más. +Esta técnica se llama técnicamente **inyección de dependencias**. Y esos datos se llaman **dependencias.** Sin embargo, es simplemente pasar parámetros, nada más. .[note] -Por favor, no confundas la inyección de dependencias, que es un patrón de diseño, con un "contenedor de inyección de dependencias", que es una herramienta, algo diametralmente distinto. Nos ocuparemos de los contenedores más adelante. +Por favor, no confunda la inyección de dependencias, que es un patrón de diseño, con el "contenedor de inyección de dependencias", que es una herramienta, es decir, algo diametralmente diferente. Hablaremos de los contenedores más adelante. -De las funciones a las clases .[#toc-from-functions-to-classes] ---------------------------------------------------------------- +De funciones a clases +--------------------- -¿Y cómo se relacionan las clases con esto? Una clase es una entidad más compleja que una simple función, pero la regla #1 se aplica aquí también. Simplemente hay [más formas de pasar argumentos |passing-dependencies]. Por ejemplo, bastante similar al caso de una función: +¿Y cómo se relacionan las clases con esto? Una clase es una unidad más compleja que una simple función, sin embargo, la regla nº 1 se aplica aquí sin excepción. Solo que hay [más formas de pasar argumentos |passing-dependencies]. Por ejemplo, de manera bastante similar al caso de una función: ```php -class Matematicas +class Matematika { - public function suma(float $a, float $b): float + public function soucet(float $a, float $b): float { return $a + $b; } } -$math = new Matematicas; -echo $math->suma(23, 1); // 24 +$math = new Matematika; +echo $math->soucet(23, 1); // 24 ``` -O usando otros métodos, o el constructor directamente: +O mediante otros métodos, o directamente el constructor: ```php -class Suma +class Soucet { public function __construct( private float $a, @@ -132,24 +132,24 @@ class Suma ) { } - public function calculate(): float + public function spocti(): float { return $this->a + $this->b; } } -$suma = new Suma(23, 1); -echo $suma->calculate(); // 24 +$soucet = new Soucet(23, 1); +echo $soucet->spocti(); // 24 ``` -Ambos ejemplos se ajustan completamente a la inyección de dependencias. +Ambos ejemplos están completamente en línea con la inyección de dependencias. -Ejemplos de la vida real .[#toc-real-life-examples] ---------------------------------------------------- +Ejemplos reales +--------------- -En el mundo real, no escribirás clases para sumar números. Pasemos a ejemplos de la vida real. +En el mundo real, no escribirá clases para sumar números. Pasemos a ejemplos prácticos. Tengamos una clase `Article` que represente un artículo de blog: @@ -162,23 +162,23 @@ class Article public function save(): void { - // guardar el artículo en la base de datos + // guardamos el artículo en la base de datos } } ``` -y el uso será el siguiente +y el uso será el siguiente: ```php $article = new Article; -$article->title = '10 cosas que debe saber sobre la pérdida de peso'; -$article->content = 'Cada año, millones de personas en ...'; +$article->title = '10 Things You Need to Know About Losing Weight'; +$article->content = 'Every year millions of people in ...'; $article->save(); ``` -El método `save()` almacenará el artículo en una tabla de la base de datos. Implementarlo usando [Nette Database |database:] sería pan comido, si no fuera por una pega: ¿de dónde saca `Article` la conexión a la base de datos, es decir, el objeto de clase `Nette\Database\Connection`? +El método `save()` guarda el artículo en una tabla de la base de datos. Implementarlo usando [Nette Database |database:] sería pan comido, si no fuera por un pequeño inconveniente: ¿de dónde obtiene `Article` la conexión a la base de datos, es decir, el objeto de la clase `Nette\Database\Connection`? -Parece que tenemos muchas opciones. Puede tomarla de alguna variable estática. O heredar de la clase que proporcionará la conexión a la base de datos. O aprovechar un llamado singleton. O las llamadas facades que se usan en Laravel: +Parece que tenemos muchas opciones. Puede tomarla de alguna variable estática. O heredar de una clase que proporcione la conexión a la base de datos. O usar el llamado [singleton |global-state#Singleton]. O las llamadas facades, que se usan en Laravel: ```php use Illuminate\Support\Facades\DB; @@ -203,13 +203,13 @@ Genial, hemos resuelto el problema. ¿O no? -Recordemos [la regla nº 1: déjalo que te lo pasen |#rule #1: Let It Be Passed to You]: todas las dependencias que necesite la clase deben pasársele. Porque si no lo hacemos, y rompemos la regla, habremos empezado el camino hacia un código sucio lleno de bindings ocultos, incomprensibilidad, y el resultado será una aplicación que será un dolor de mantener y desarrollar. +Recordemos la [##Regla nº 1: deja que te lo pasen]: todas las dependencias que la clase necesita deben serle pasadas. Porque si rompemos la regla, hemos tomado el camino hacia un código sucio lleno de dependencias ocultas, incomprensibilidad, y el resultado será una aplicación que será doloroso mantener y desarrollar. -El usuario de la clase `Article` no tiene ni idea de dónde almacena el método `save()` el artículo. ¿En una tabla de la base de datos? ¿En cuál, en producción o en desarrollo? ¿Y cómo se puede cambiar esto? +El usuario de la clase `Article` no tiene idea de dónde guarda el artículo el método `save()`. ¿En una tabla de base de datos? ¿En cuál, la de producción o la de prueba? ¿Y cómo se puede cambiar eso? -El usuario tiene que mirar cómo está implementado el método `save()` para encontrar el uso del método `DB::insert()`. Así que tiene que buscar más para averiguar cómo este método procura una conexión a la base de datos. Y los enlaces ocultos pueden formar una cadena bastante larga. +El usuario debe mirar cómo está implementado el método `save()` y encuentra el uso del método `DB::insert()`. Así que debe investigar más a fondo cómo este método obtiene la conexión a la base de datos. Y las dependencias ocultas pueden formar una cadena bastante larga. -Los enlaces ocultos, las facades de Laravel o las variables estáticas nunca están presentes en un código limpio y bien diseñado. En código limpio y bien diseñado, los argumentos se pasan: +En un código limpio y bien diseñado, nunca hay dependencias ocultas, facades de Laravel o variables estáticas. En un código limpio y bien diseñado, se pasan argumentos: ```php class Article @@ -224,7 +224,7 @@ class Article } ``` -Aún más práctico, como veremos a continuación, es utilizar un constructor: +Aún más práctico, como veremos más adelante, será mediante el constructor: ```php class Article @@ -245,11 +245,11 @@ class Article ``` .[note] -Si usted es un programador experimentado, podría pensar que `Article` no debería tener un método `save()` en absoluto; debería representar un componente puramente de datos, y un repositorio separado debería encargarse de guardar. Eso tiene sentido. Pero eso nos llevaría mucho más allá del alcance del tema, que es la inyección de dependencias, y del esfuerzo por proporcionar ejemplos sencillos. +Si es un programador experimentado, quizás piense que `Article` no debería tener un método `save()` en absoluto, debería representar un componente puramente de datos y un repositorio separado debería encargarse del almacenamiento. Eso tiene sentido. Pero eso nos llevaría mucho más allá del alcance del tema, que es la inyección de dependencias, y el esfuerzo por dar ejemplos simples. -Si vas a escribir una clase que requiere una base de datos para funcionar, por ejemplo, no te imagines de dónde obtenerla, sino que te la pasen. Quizá como parámetro de un constructor u otro método. Declara las dependencias. Exponlas en la API de tu clase. Conseguirás un código comprensible y predecible. +Si escribe una clase que requiere, por ejemplo, una base de datos para su funcionamiento, no piense de dónde obtenerla, sino deje que se la pasen. Tal vez como parámetro del constructor u otro método. Admita las dependencias. Admítalas en la API de su clase. Obtendrá un código comprensible y predecible. -Qué tal esta clase que registra mensajes de error: +¿Y qué tal esta clase, que registra mensajes de error?: ```php class Logger @@ -262,13 +262,13 @@ class Logger } ``` -¿Qué le parece, hemos seguido [la regla nº 1: déjalo que te lo pasen|#rule #1: Let It Be Passed to You]? +¿Qué piensa, hemos seguido la [##Regla nº 1: deja que te lo pasen]? No lo hemos hecho. -La información clave, el directorio del archivo de registro, es *obtenida* por la clase a partir de la constante. +La información clave, es decir, el directorio con el archivo de registro, la clase la *obtiene por sí misma* de una constante. -Vea el ejemplo de uso: +Mire el ejemplo de uso: ```php $logger = new Logger; @@ -276,9 +276,9 @@ $logger->log('La temperatura es 23 °C'); $logger->log('La temperatura es 10 °C'); ``` -Sin conocer la implementación, ¿podrías responder a la pregunta de dónde se escriben los mensajes? ¿Te parecería necesaria la existencia de la constante LOG_DIR para que funcione? ¿Y sería capaz de crear una segunda instancia que escribiera en una ubicación diferente? Desde luego que no. +Sin conocer la implementación, ¿podría responder a la pregunta de dónde se escriben los mensajes? ¿Se le ocurriría que para funcionar es necesaria la existencia de la constante `LOG_DIR`? ¿Y podría crear una segunda instancia que escriba en otro lugar? Seguramente no. -Arreglemos la clase: +Corrijamos la clase: ```php class Logger @@ -295,24 +295,24 @@ class Logger } ``` -La clase es ahora mucho más clara, configurable y por tanto más útil. +La clase ahora es mucho más comprensible, configurable y, por lo tanto, más útil. ```php -$logger = new Logger('/path/to/log.txt'); -$logger->log('The temperature is 15 °C'); +$logger = new Logger('/ruta/al/log.txt'); +$logger->log('La temperatura es 15 °C'); ``` -Pero no me importa! .[#toc-but-i-don-t-care] --------------------------------------------- +¡Pero eso no me interesa! +------------------------- -*"Cuando creo un objeto Article y llamo a save(), no quiero tratar con la base de datos, sólo quiero que se guarde en la que he establecido en la configuración."* +*„Cuando creo un objeto Article y llamo a save(), no quiero ocuparme de la base de datos, simplemente quiero que se guarde en la que tengo configurada.“* -*"Cuando uso Logger, sólo quiero que se escriba el mensaje, y no quiero ocuparme de dónde. Que se use la configuración global."* +*„Cuando uso Logger, simplemente quiero que el mensaje se escriba, y no quiero preocuparme por dónde. Que se use la configuración global.“* -Estos comentarios son correctos. +Estos son comentarios válidos. -Como ejemplo, tomemos una clase que envía boletines y registra cómo ha ido: +Como ejemplo, mostraremos una clase que envía boletines informativos y registra cómo fue: ```php class NewsletterDistributor @@ -322,21 +322,21 @@ class NewsletterDistributor $logger = new Logger(/* ... */); try { $this->sendEmails(); - $logger->log('Se han enviado correos electrónicos'); + $logger->log('Los correos electrónicos fueron enviados'); } catch (Exception $e) { - $logger->log('Se ha producido un error durante el envío'); + $logger->log('Ocurrió un error al enviar'); throw $e; } } } ``` -La mejorada `Logger`, que ya no utiliza la constante `LOG_DIR`, requiere una ruta de archivo en el constructor. ¿Cómo resolver esto? A la clase `NewsletterDistributor` no le importa dónde se escriben los mensajes, sólo quiere escribirlos. +El `Logger` mejorado, que ya no usa la constante `LOG_DIR`, requiere que se especifique la ruta del archivo en el constructor. ¿Cómo resolver esto? A la clase `NewsletterDistributor` no le importa en absoluto dónde se escriben los mensajes, solo quiere escribirlos. -La solución es de nuevo [la regla nº 1: déjalo que te lo pasen |#rule #1: Let It Be Passed to You]: pásale todos los datos que la clase necesite. +La solución es nuevamente la [##Regla nº 1: deja que te lo pasen]: todos los datos que la clase necesita, se los pasamos. -Así que pasamos la ruta al log al constructor, que luego usamos para crear el objeto `Logger`? +Entonces, ¿eso significa que pasamos la ruta del registro a través del constructor, que luego usamos al crear el objeto `Logger`? ```php class NewsletterDistributor @@ -351,7 +351,7 @@ class NewsletterDistributor $logger = new Logger($this->file); ``` -Pues no. Porque la ruta **no** pertenece a los datos que la clase `NewsletterDistributor` necesita; necesita `Logger`. La clase necesita el propio logger. Y eso es lo que vamos a pasar: +¡Así no! La ruta **no pertenece** a los datos que la clase `NewsletterDistributor` necesita; esos los necesita `Logger`. ¿Percibe la diferencia? La clase `NewsletterDistributor` necesita el logger como tal. Así que eso es lo que pasaremos: ```php class NewsletterDistributor @@ -365,35 +365,33 @@ class NewsletterDistributor { try { $this->sendEmails(); - $this->logger->log('Se han enviado correos electrónicos'); + $this->logger->log('Los correos electrónicos fueron enviados'); } catch (Exception $e) { - $this->logger->log('Se ha producido un error durante el envío'); + $this->logger->log('Ocurrió un error al enviar'); throw $e; } } } ``` -Ahora está claro por las firmas de la clase `NewsletterDistributor` que el registro es parte de su funcionalidad. Y la tarea de reemplazar el logger por otro, quizás con fines de prueba, es bastante trivial. -Además, si se cambia el constructor de la clase `Logger`, no tendrá ningún efecto en nuestra clase. +Ahora está claro a partir de las firmas de la clase `NewsletterDistributor` que el registro es parte de su funcionalidad. Y la tarea de cambiar el logger por otro, por ejemplo, para pruebas, es completamente trivial. Además, si el constructor de la clase `Logger` cambiara, no tendría ningún efecto en nuestra clase. -Regla nº 2: Toma lo que es tuyo .[#toc-rule-2-take-what-s-yours] ----------------------------------------------------------------- +Regla nº 2: toma lo que es tuyo +------------------------------- -No te dejes engañar y no te dejes pasar las dependencias de tus dependencias. Sólo pasa tus propias dependencias. +No se deje engañar y no deje que le pasen las dependencias de sus dependencias. Deje que le pasen solo sus dependencias. -Esto hará que el código que utilice otros objetos sea completamente independiente de los cambios en sus constructores. Su API será más verdadera. Y lo más importante, será trivial cambiar esas dependencias por otras. +Gracias a esto, el código que utiliza otros objetos será completamente independiente de los cambios en sus constructores. Su API será más veraz. Y, sobre todo, será trivial reemplazar estas dependencias por otras. -Nuevo miembro de la familia .[#toc-new-family-member] ------------------------------------------------------ +Nuevo miembro de la familia +--------------------------- -El equipo de desarrollo decidió crear un segundo registrador que escribe en la base de datos. Así que creamos una clase `DatabaseLogger`. Así que tenemos dos clases, `Logger` y `DatabaseLogger`, una escribe en un archivo, la otra en una base de datos ... ¿no te parece extraña la nomenclatura? -¿No sería mejor renombrar `Logger` a `FileLogger`? Desde luego que sí. +En el equipo de desarrollo, se decidió crear un segundo logger que escriba en la base de datos. Por lo tanto, crearemos la clase `DatabaseLogger`. Así que tenemos dos clases, `Logger` y `DatabaseLogger`, una escribe en un archivo, la otra en la base de datos... ¿no le parece algo extraño en el nombre? ¿No sería mejor renombrar `Logger` a `FileLogger`? Definitivamente sí. -Pero hagámoslo de forma inteligente. Creamos una interfaz con el nombre original: +Pero lo haremos inteligentemente. Crearemos una interfaz con el nombre original: ```php interface Logger @@ -402,7 +400,7 @@ interface Logger } ``` -... que ambos registradores implementarán: +... que ambos loggers implementarán: ```php class FileLogger implements Logger @@ -412,17 +410,17 @@ class DatabaseLogger implements Logger // ... ``` -Y debido a esto, no habrá necesidad de cambiar nada en el resto del código donde se utilice el logger. Por ejemplo, el constructor de la clase `NewsletterDistributor` seguirá conformándose con requerir `Logger` como parámetro. Y dependerá de nosotros qué instancia le pasemos. +Y gracias a esto, no será necesario cambiar nada en el resto del código donde se utiliza el logger. Por ejemplo, el constructor de la clase `NewsletterDistributor` seguirá estando satisfecho con requerir `Logger` como parámetro. Y dependerá de nosotros qué instancia le pasemos. -**Por eso nunca añadimos el sufijo `Interface` o el prefijo `I` a los nombres de las interfaces.** De lo contrario, no sería posible desarrollar el código de forma tan agradable. +**Por eso nunca damos a los nombres de las interfaces el sufijo `Interface` o el prefijo `I`.** De lo contrario, no sería posible desarrollar el código de esta manera tan agradable. -Houston, tenemos un problema .[#toc-houston-we-have-a-problem] --------------------------------------------------------------- +Houston, tenemos un problema +---------------------------- -Mientras que podemos arreglárnoslas con una única instancia del registrador, ya sea basado en archivos o en bases de datos, a lo largo de toda la aplicación y simplemente pasarlo allí donde se registre algo, es bastante diferente para la clase `Article`. Creamos sus instancias según sea necesario, incluso varias veces. ¿Cómo tratar la dependencia de la base de datos en su constructor? +Mientras que en toda la aplicación podemos arreglárnoslas con una única instancia de logger, ya sea de archivo o de base de datos, y simplemente pasarla a donde sea que algo se registre, la situación es bastante diferente en el caso de la clase `Article`. Creamos sus instancias según sea necesario, incluso varias veces. ¿Cómo lidiar con la dependencia de la base de datos en su constructor? -Un ejemplo puede ser un controlador que debe guardar un artículo en la base de datos después de enviar un formulario: +Como ejemplo puede servir un controlador que, después de enviar un formulario, debe guardar el artículo en la base de datos: ```php class EditController extends Controller @@ -437,30 +435,30 @@ class EditController extends Controller } ``` -Una posible solución es obvia: pasar el objeto de base de datos al constructor `EditController` y utilizar `$article = new Article($this->db)`. +Una posible solución se presenta directamente: dejamos que el objeto de la base de datos se pase mediante el constructor a `EditController` y usamos `$article = new Article($this->db)`. -Al igual que en el caso anterior con `Logger` y la ruta del archivo, este no es el enfoque correcto. La base de datos no es una dependencia de `EditController`, sino de `Article`. Pasar la base de datos va en contra de [la regla #2: toma lo que es tuyo |#rule #2: take what's yours]. Si el constructor de la clase `Article` cambia (se añade un nuevo parámetro), tendrás que modificar el código allí donde se creen instancias. Ufff. +Al igual que en el caso anterior con `Logger` y la ruta al archivo, este no es el procedimiento correcto. La base de datos no es una dependencia de `EditController`, sino de `Article`. Pasar la base de datos va en contra de la [##Regla nº 2: toma lo que es tuyo]. Cuando cambie el constructor de la clase `Article` (se agregue un nuevo parámetro), será necesario modificar también el código en todos los lugares donde se creen instancias. Ufff. Houston, ¿qué sugieres? -Regla nº 3: Deje que se encargue la fábrica .[#toc-rule-3-let-the-factory-handle-it] ------------------------------------------------------------------------------------- +Regla nº 3: déjalo en manos de la fábrica +----------------------------------------- -Al eliminar las dependencias ocultas y pasar todas las dependencias como argumentos, hemos conseguido clases más configurables y flexibles. Y por lo tanto, necesitamos algo más para crear y configurar esas clases más flexibles para nosotros. Lo llamaremos fábricas. +Al eliminar las dependencias ocultas y pasar todas las dependencias como argumentos, hemos obtenido clases más configurables y flexibles. Y, por lo tanto, necesitamos algo más que cree y configure esas clases más flexibles para nosotros. Lo llamaremos fábricas. -La regla general es: si una clase tiene dependencias, deja la creación de sus instancias a la fábrica. +La regla es: si una clase tiene dependencias, deja la creación de sus instancias en manos de una fábrica. -Las fábricas son un sustituto más inteligente del operador `new` en el mundo de la inyección de dependencias. +Las fábricas son un reemplazo más inteligente del operador `new` en el mundo de la inyección de dependencias. .[note] -Por favor, no confundir con el patrón de diseño *método de fábrica*, que describe una forma específica de utilizar las fábricas y no está relacionado con este tema. +Por favor, no confunda con el patrón de diseño *factory method*, que describe una forma específica de usar fábricas y no está relacionado con este tema. -Fábrica .[#toc-factory] ------------------------ +Fábrica +------- -Una fábrica es un método o clase que produce y configura objetos. Llamamos `Article` a la clase productora `ArticleFactory` y podría tener este aspecto: +Una fábrica es un método o clase que produce y configura objetos. La clase que produce `Article` la llamaremos `ArticleFactory` y podría verse así, por ejemplo: ```php class ArticleFactory @@ -477,7 +475,7 @@ class ArticleFactory } ``` -Su uso en el controlador sería el siguiente: +Su uso en el controlador será el siguiente: ```php class EditController extends Controller @@ -489,7 +487,7 @@ class EditController extends Controller public function formSubmitted($data) { - // dejar que la fábrica cree un objeto + // dejamos que la fábrica cree el objeto $article = $this->articleFactory->create(); $article->title = $data->title; $article->content = $data->content; @@ -498,11 +496,11 @@ class EditController extends Controller } ``` -En este punto, cuando la firma del constructor de la clase `Article` cambia, la única parte del código que necesita responder es la propia fábrica `ArticleFactory`. Cualquier otro código que trabaje con objetos `Article`, como `EditController`, no se verá afectado. +Si en este momento cambia la firma del constructor de la clase `Article`, la única parte del código que debe reaccionar es la propia fábrica `ArticleFactory`. Todo el código adicional que trabaja con objetos `Article`, como `EditController`, no se verá afectado de ninguna manera. -Puede que ahora mismo te estés dando golpecitos en la frente preguntándote si nos hemos ayudado a nosotros mismos en algo. La cantidad de código ha crecido y todo empieza a parecer sospechosamente complicado. +Quizás ahora se esté golpeando la frente, preguntándose si realmente hemos mejorado algo. La cantidad de código ha aumentado y todo comienza a parecer sospechosamente complicado. -No te preocupes, pronto llegaremos al contenedor Nette DI. Y tiene una serie de ases en la manga que harán que construir aplicaciones usando inyección de dependencias sea extremadamente sencillo. Por ejemplo, en lugar de la clase `ArticleFactory`, bastará con [escribir una simple interfaz |factory]: +No se preocupe, pronto llegaremos al contenedor DI de Nette. Y este tiene varios ases bajo la manga que simplificarán enormemente la construcción de aplicaciones que utilizan inyección de dependencias. Por ejemplo, en lugar de la clase `ArticleFactory`, será suficiente [escribir solo una interfaz |factory]: ```php interface ArticleFactory @@ -511,18 +509,18 @@ interface ArticleFactory } ``` -Pero nos estamos adelantando, espera :-) +Pero nos estamos adelantando, espere un poco más :-) -Resumen .[#toc-summary] ------------------------ +Resumen +------- -Al principio de este capítulo, prometimos mostrarte una forma de diseñar código limpio. Basta con dar a las clases +Al comienzo de este capítulo, prometimos mostrar un método para diseñar código limpio. Basta con que las clases -- [pasen las dependencias que necesitan|#Rule #1: Let It Be Passed to You] -- [a la inversa, no pasen lo que no necesitan directamente |#Rule #2: Take What's Yours] -- [y que los objetos con dependencias se creen mejor en fábricas |#Rule #3: Let the Factory Handle it] +1) [reciban las dependencias que necesitan |#Regla nº 1: deja que te lo pasen] +2) [y, por el contrario, no reciban lo que no necesitan directamente |#Regla nº 2: toma lo que es tuyo] +3) [y que los objetos con dependencias se fabriquen mejor en fábricas |#Regla nº 3: déjalo en manos de la fábrica] -A primera vista, puede que estas tres reglas no parezcan tener consecuencias de gran alcance, pero conducen a una perspectiva radicalmente distinta del diseño de código. ¿Merece la pena? Los desarrolladores que han abandonado viejos hábitos y han empezado a utilizar sistemáticamente la inyección de dependencias consideran este paso un momento crucial en su vida profesional. Les ha abierto el mundo de las aplicaciones claras y mantenibles. +Puede que no lo parezca a primera vista, pero estas tres reglas tienen consecuencias de gran alcance. Conducen a una visión radicalmente diferente del diseño de código. ¿Vale la pena? Los programadores que abandonaron viejos hábitos y comenzaron a usar consistentemente la inyección de dependencias consideran este paso un momento crucial en sus vidas profesionales. Se les abrió un mundo de aplicaciones claras y mantenibles. -Pero, ¿qué ocurre si el código no utiliza sistemáticamente la inyección de dependencias? ¿Y si se basa en métodos estáticos o singletons? ¿Causa problemas? [Sí, los hay, y muy fundamentales |global-state]. +Pero, ¿qué pasa si el código no utiliza consistentemente la inyección de dependencias? ¿Qué pasa si se basa en métodos estáticos o singletons? ¿Trae algún problema? [Sí, y muy fundamentales |global-state]. diff --git a/dependency-injection/es/nette-container.texy b/dependency-injection/es/nette-container.texy index 02d1993284..3839e5515b 100644 --- a/dependency-injection/es/nette-container.texy +++ b/dependency-injection/es/nette-container.texy @@ -1,10 +1,10 @@ -Contenedor Nette DI -******************* +Contenedor DI de Nette +********************** .[perex] -Nette DI es una de las librerías más interesantes de Nette. Puede generar y actualizar automáticamente contenedores DI compilados que son extremadamente rápidos y sorprendentemente fáciles de configurar. +Nette DI es una de las librerías más interesantes de Nette. Puede generar y actualizar automáticamente contenedores DI compilados, que son extremadamente rápidos y sorprendentemente fáciles de configurar. -Los servicios a crear por un contenedor DI suelen definirse mediante ficheros de configuración en [formato NEON|neon:format]. El contenedor que creamos manualmente en [sección anterior|container] se escribiría de la siguiente manera: +La forma de los servicios que debe crear el contenedor DI se define generalmente mediante archivos de configuración en [formato NEON|neon:format]. El contenedor que creamos manualmente en el [capítulo anterior|container] se escribiría así: ```neon parameters: @@ -19,16 +19,15 @@ services: - UserController ``` -La notación es realmente breve. +La notación es realmente concisa. -Todas las dependencias declaradas en los constructores de las clases `ArticleFactory` y `UserController` son encontradas y pasadas por el propio DI de Nette gracias al llamado [autowiring], por lo que no es necesario especificar nada en el fichero de configuración. -Así que incluso si los parámetros cambian, no es necesario cambiar nada en la configuración. Nette regenerará automáticamente el contenedor. Usted puede concentrarse allí puramente en el desarrollo de la aplicación. +Todas las dependencias declaradas en los constructores de las clases `ArticleFactory` y `UserController` son detectadas y pasadas automáticamente por Nette DI gracias al llamado [autowiring|autowiring], por lo que no es necesario especificar nada en el archivo de configuración. Así que incluso si los parámetros cambian, no necesita cambiar nada en la configuración. Nette regenerará automáticamente el contenedor. Puede concentrarse puramente en el desarrollo de la aplicación. -Si quiere pasar dependencias usando setters, use la sección [setup|services#setup] para hacerlo. +Si queremos pasar dependencias mediante setters, usamos la sección [setup |services#Setup] para ello. -Nette DI generará directamente el código PHP para el contenedor. El resultado es un archivo `.php` que puede abrir y estudiar. Esto le permite ver exactamente cómo funciona el contenedor. También puede depurarlo en el IDE y recorrerlo paso a paso. Y lo más importante: el PHP generado es extremadamente rápido. +Nette DI genera directamente el código PHP del contenedor. El resultado es, por tanto, un archivo `.php` que puede abrir y estudiar. Gracias a esto, puede ver exactamente cómo funciona el contenedor. También puede depurarlo en su IDE y recorrerlo paso a paso. Y lo más importante: el PHP generado es extremadamente rápido. -Nette DI también puede generar código [de fábrica|factory] basado en la interfaz suministrada. Por lo tanto, en lugar de la clase `ArticleFactory`, sólo necesitamos crear una interfaz en la aplicación: +Nette DI también puede generar código de [fábricas|factory] basándose en la interfaz proporcionada. Por lo tanto, en lugar de la clase `ArticleFactory`, solo necesitaremos crear una interfaz en la aplicación: ```php interface ArticleFactory @@ -37,19 +36,19 @@ interface ArticleFactory } ``` -Puedes encontrar el ejemplo completo [en GitHub|https://github.com/nette-examples/di-example-doc]. +Puede encontrar el ejemplo completo [en GitHub|https://github.com/nette-examples/di-example-doc]. -Uso autónomo .[#toc-standalone-use] ------------------------------------ +Uso independiente +----------------- -Utilizar la librería Nette DI en una aplicación es muy sencillo. Primero la instalamos con Composer (porque descargar archivos zip está muy pasado de moda): +Implementar la librería Nette DI en una aplicación es muy fácil. Primero, la instalamos con Composer (porque descargar zips es taaan anticuado): ```shell composer require nette/di ``` -El siguiente código crea una instancia del contenedor DI según la configuración almacenada en el fichero `config.neon`: +El siguiente código crea una instancia del contenedor DI según la configuración almacenada en el archivo `config.neon`: ```php $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp'); @@ -59,23 +58,23 @@ $class = $loader->load(function ($compiler) { $container = new $class; ``` -El contenedor se genera sólo una vez, su código se escribe en la caché (el directorio `__DIR__ . '/temp'`) y en las siguientes peticiones sólo se lee de ahí. +El contenedor se genera solo una vez, su código se escribe en la caché (directorio `__DIR__ . '/temp'`) y en las siguientes peticiones simplemente se carga desde allí. -Los métodos `getService()` o `getByType()` se utilizan para crear y recuperar servicios. Así es como creamos el objeto `UserController`: +Para crear y obtener servicios, se utilizan los métodos `getService()` o `getByType()`. Así creamos el objeto `UserController`: ```php -$database = $container->getByType(UserController::class); -$database->query('...'); +$controller = $container->getByType(UserController::class); +$controller->someMethod(); ``` -Durante el desarrollo, es útil habilitar el modo auto-refresh, en el que el contenedor se regenera automáticamente si se cambia alguna clase o fichero de configuración. Simplemente proporciona `true` como segundo argumento en el constructor `ContainerLoader`. +Durante el desarrollo, es útil activar el modo de auto-refresco, donde el contenedor se regenera automáticamente si se cambia alguna clase o archivo de configuración. Simplemente proporcione `true` como segundo argumento en el constructor de `ContainerLoader`. ```php $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp', true); ``` -Usándolo con Nette Framework .[#toc-using-it-with-the-nette-framework] ----------------------------------------------------------------------- +Uso con el framework Nette +-------------------------- -Como hemos mostrado, el uso de Nette DI no está limitado a aplicaciones escritas en Nette Framework, puedes desplegarlo en cualquier lugar con sólo 3 líneas de código. Sin embargo, si estás desarrollando aplicaciones en Nette Framework, la configuración y creación del contenedor es manejada por [Bootstrap|application:bootstrap#toc-di-container-configuration]. +Como hemos mostrado, el uso de Nette DI no está limitado a aplicaciones escritas en Nette Framework, puede implementarlo en cualquier lugar con solo 3 líneas de código. Sin embargo, si desarrolla aplicaciones en Nette Framework, la configuración y creación del contenedor está a cargo de [Bootstrap |application:bootstrapping#Configuración del contenedor DI]. diff --git a/dependency-injection/es/passing-dependencies.texy b/dependency-injection/es/passing-dependencies.texy index 05588ddfdc..3640c239aa 100644 --- a/dependency-injection/es/passing-dependencies.texy +++ b/dependency-injection/es/passing-dependencies.texy @@ -1,24 +1,24 @@ -Pasar dependencias -****************** +Paso de dependencias +******************** <div class=perex> -Los argumentos, o "dependencias" en la terminología DI, se pueden pasar a las clases de las siguientes formas principales: +Los argumentos, o en la terminología de DI "dependencias", se pueden pasar a las clases de las siguientes maneras principales: -* pasando por constructor -* pasando por método (llamado setter) -* estableciendo una propiedad -* por método, anotación o atributo *inject*. +* paso por constructor +* paso por método (llamado setter) +* asignación a variable +* mediante método, anotación o atributo *inject* </div> -A continuación ilustraremos las distintas variantes con ejemplos concretos. +Ahora mostraremos las variantes individuales con ejemplos concretos. -Inyección de constructor .[#toc-constructor-injection] -====================================================== +Paso por constructor +==================== -Las dependencias se pasan como argumentos al constructor cuando se crea el objeto: +Las dependencias se pasan en el momento de la creación del objeto como argumentos del constructor: ```php class MyClass @@ -34,9 +34,9 @@ class MyClass $obj = new MyClass($cache); ``` -Esta forma es útil para dependencias obligatorias que la clase necesita absolutamente para funcionar, ya que sin ellas la instancia no puede ser creada. +Esta forma es adecuada para dependencias obligatorias que la clase necesita indispensablemente para su función, ya que sin ellas no se podrá crear la instancia. -Desde PHP 8.0, podemos usar una forma más corta de notación que es funcionalmente equivalente ([constructor property promotion |https://blog.nette.org/es/php-8-0-vision-completa-de-las-novedades#toc-constructor-property-promotion]): +Desde PHP 8.0, podemos usar una forma de escritura más corta ([constructor property promotion |https://blog.nette.org/es/php-8-0-complete-overview-of-news#toc-constructor-property-promotion]), que es funcionalmente equivalente: ```php // PHP 8.0 @@ -49,7 +49,7 @@ class MyClass } ``` -A partir de PHP 8.1, una propiedad puede ser marcada con una bandera `readonly` que declara que el contenido de la propiedad no cambiará: +Desde PHP 8.1, se puede marcar la variable con el flag `readonly`, que declara que el contenido de la variable ya no cambiará: ```php // PHP 8.1 @@ -62,13 +62,13 @@ class MyClass } ``` -El contenedor DI pasa dependencias al constructor automáticamente usando [autowiring]. Argumentos que no se pueden pasar de esta forma (por ejemplo cadenas, números, booleanos) [escribir en configuración |services#Arguments]. +El contenedor DI pasa las dependencias al constructor automáticamente mediante [autowiring |autowiring]. Los argumentos que no se pueden pasar de esta manera (por ejemplo, cadenas, números, booleanos) [se escriben en la configuración |services#Argumentos]. -Infierno constructor .[#toc-constructor-hell] ---------------------------------------------- +Constructor hell +---------------- -El término *infierno de constructores* se refiere a una situación en la que un hijo hereda de una clase padre cuyo constructor requiere dependencias, y el hijo también requiere dependencias. También debe asumir y pasar las dependencias del padre: +El término *constructor hell* se refiere a la situación en la que un descendiente hereda de una clase padre cuyo constructor requiere dependencias, y al mismo tiempo el descendiente requiere dependencias. Además, debe recibir y pasar también las del padre: ```php abstract class BaseClass @@ -94,11 +94,11 @@ final class MyClass extends BaseClass } ``` -El problema surge cuando queremos cambiar el constructor de la clase `BaseClass`, por ejemplo cuando se añade una nueva dependencia. Entonces tenemos que modificar también todos los constructores de los hijos. Lo que convierte tal modificación en un infierno. +El problema surge en el momento en que queremos cambiar el constructor de la clase `BaseClass`, por ejemplo, cuando se agrega una nueva dependencia. Entonces es necesario modificar también todos los constructores de los descendientes. Lo que convierte tal modificación en un infierno. -¿Cómo evitarlo? La solución es **priorizar la composición sobre la herencia**. +¿Cómo prevenir esto? La solución es **dar preferencia a la [composición sobre la herencia |faq#Por qué se prefiere la composición sobre la herencia]**. -Así que diseñemos el código de otra manera. Evitaremos las clases abstractas `Base*`. En lugar de que `MyClass` obtenga alguna funcionalidad heredando de `BaseClass`, tendrá esa funcionalidad pasada como una dependencia: +Es decir, diseñaremos el código de manera diferente. Evitaremos las clases [abstractas |nette:introduction-to-object-oriented-programming#Clases abstractas] `Base*`. En lugar de que `MyClass` obtenga cierta funcionalidad heredando de `BaseClass`, dejará que esta funcionalidad se le pase como dependencia: ```php final class SomeFunctionality @@ -125,10 +125,10 @@ final class MyClass ``` -Inyección de Setter .[#toc-setter-injection] -============================================ +Paso por setter +=============== -Las dependencias se pasan llamando a un método que las almacena en una propiedad privada. La convención de nomenclatura habitual para estos métodos es de la forma `set*()`, que es por lo que se llaman setters, pero por supuesto pueden llamarse de cualquier otra forma. +Las dependencias se pasan llamando a un método que las almacena en una variable privada. La convención habitual para nombrar estos métodos es la forma `set*()`, por eso se les llama setters, pero por supuesto pueden llamarse de cualquier otra manera. ```php class MyClass @@ -145,9 +145,9 @@ $obj = new MyClass; $obj->setCache($cache); ``` -Este método es útil para dependencias opcionales que no son necesarias para la función de la clase, ya que no se garantiza que el objeto las reciba realmente (es decir, que el usuario llame al método). +Este método es adecuado para dependencias opcionales que no son necesarias para la función de la clase, ya que no se garantiza que el objeto reciba realmente la dependencia (es decir, que el usuario llame al método). -Al mismo tiempo, este método permite llamar al setter repetidamente para cambiar la dependencia. Si esto no es deseable, añada una comprobación al método, o a partir de PHP 8.1, marque la propiedad `$cache` con la bandera `readonly`. +Al mismo tiempo, este método permite llamar al setter repetidamente y así cambiar la dependencia. Si esto no es deseable, agregamos una verificación al método, o desde PHP 8.1 marcamos la propiedad `$cache` con el flag `readonly`. ```php class MyClass @@ -156,29 +156,28 @@ class MyClass public function setCache(Cache $cache): void { - if ($this->cache) { - throw new RuntimeException('La dependencia ya se ha establecido'); + if (isset($this->cache)) { + throw new RuntimeException('La dependencia ya ha sido establecida'); } $this->cache = $cache; } } ``` -La llamada al setter se define en la configuración del contenedor DI en [sección setup |services#Setup]. También aquí se utiliza el paso automático de dependencias mediante autowiring: +La llamada al setter se define en la configuración del contenedor DI en la [clave setup |services#Setup]. Aquí también se utiliza el paso automático de dependencias mediante autowiring: ```neon services: - - - create: MyClass + - create: MyClass setup: - setCache ``` -Inyección de propiedades .[#toc-property-injection] -=================================================== +Asignación a variable +===================== -Las dependencias se pasan directamente a la propiedad: +Las dependencias se pasan escribiendo directamente en la variable miembro: ```php class MyClass @@ -190,28 +189,27 @@ $obj = new MyClass; $obj->cache = $cache; ``` -Este método se considera inapropiado porque la propiedad debe declararse como `public`. Por lo tanto, no tenemos control sobre si la dependencia pasada será realmente del tipo especificado (esto era cierto antes de PHP 7.4) y perdemos la capacidad de reaccionar a la dependencia recién asignada con nuestro propio código, por ejemplo para evitar cambios posteriores. Al mismo tiempo, la propiedad pasa a formar parte de la interfaz pública de la clase, lo que puede no ser deseable. +Este método se considera inadecuado porque la variable miembro debe declararse como `public`. Y, por lo tanto, no tenemos control sobre si la dependencia pasada será realmente del tipo dado (válido antes de PHP 7.4) y perdemos la posibilidad de reaccionar a la dependencia recién asignada con nuestro propio código, por ejemplo, para evitar cambios posteriores. Al mismo tiempo, la variable se convierte en parte de la interfaz pública de la clase, lo que puede no ser deseable. -La configuración de la variable se define en la configuración del contenedor DI en [sección setup |services#Setup]: +La asignación de la variable se define en la configuración del contenedor DI en la [sección setup |services#Setup]: ```neon services: - - - create: MyClass + - create: MyClass setup: - $cache = @\Cache ``` -Inyectar .[#toc-inject] -======================= +Inject +====== -Mientras que los tres métodos anteriores son generalmente válidos en todos los lenguajes orientados a objetos, la inyección por método, anotación o atributo *inject* es específica de los presentadores Nette. Se tratan en [un capítulo aparte |best-practices:inject-method-attribute]. +Mientras que los tres métodos anteriores son válidos en general en todos los lenguajes orientados a objetos, la inyección mediante método, anotación o atributo *inject* es específica puramente para los presenters en Nette. Se tratan en un [capítulo separado |best-practices:inject-method-attribute]. -¿Qué camino elegir? .[#toc-which-way-to-choose] -=============================================== +¿Qué método elegir? +=================== -- el constructor es adecuado para dependencias obligatorias que la clase necesita para funcionar. -- el setter, por otro lado, es adecuado para dependencias opcionales, o dependencias que se pueden cambiar. -- las variables públicas no son recomendables. +- el constructor es adecuado para dependencias obligatorias que la clase necesita indispensablemente para su función +- el setter, por el contrario, es adecuado para dependencias opcionales, o dependencias que se puedan cambiar más adelante +- las variables públicas no son adecuadas diff --git a/dependency-injection/es/services.texy b/dependency-injection/es/services.texy index 2f6af40b80..efa7c55b79 100644 --- a/dependency-injection/es/services.texy +++ b/dependency-injection/es/services.texy @@ -1,33 +1,33 @@ -Definiciones de servicios -************************* +Definición de servicios +*********************** .[perex] -Configuración es donde colocamos las definiciones de los servicios personalizados. Esto se hace en la sección `services`. +La configuración es el lugar donde enseñamos al contenedor DI cómo debe construir los servicios individuales y cómo conectarlos con otras dependencias. Nette proporciona una forma muy clara y elegante de lograrlo. -Por ejemplo, así es como creamos un servicio llamado `database`, que será una instancia de la clase `PDO`: +La sección `services` en el archivo de configuración de formato NEON es el lugar donde definimos nuestros propios servicios y sus configuraciones. Veamos un ejemplo simple de definición de un servicio llamado `database`, que representa una instancia de la clase `PDO`: ```neon services: database: PDO('sqlite::memory:') ``` -El nombre de los servicios se utiliza para permitirnos [referenciarlos|#Referencing Services]. Si un servicio no es referenciado, no hay necesidad de nombrarlo. Así que simplemente usamos una viñeta en lugar de un nombre: +La configuración anterior resultará en el siguiente método de fábrica en el [contenedor DI|container]: -```neon -services: - - PDO('sqlite::memory:') # servicio anónimo +```php +public function createServiceDatabase(): PDO +{ + return new PDO('sqlite::memory:'); +} ``` -Una entrada de una línea puede dividirse en varias líneas para permitir añadir claves adicionales, como [#setup]. El alias de la clave `create:` es `factory:`. +Los nombres de los servicios nos permiten referirnos a ellos en otras partes del archivo de configuración, en el formato `@nombreDelServicio`. Si no es necesario nombrar el servicio, podemos simplemente usar una viñeta: ```neon services: - database: - create: PDO('sqlite::memory:') - setup: ... + - PDO('sqlite::memory:') ``` -A continuación recuperamos el servicio del contenedor DI usando el método `getService()` por nombre, o mejor aún, el método `getByType()` por tipo: +Para obtener un servicio del contenedor DI, podemos usar el método `getService()` con el nombre del servicio como parámetro, o el método `getByType()` con el tipo de servicio: ```php $database = $container->getService('database'); @@ -35,26 +35,28 @@ $database = $container->getByType(PDO::class); ``` -Creación de un servicio .[#toc-creating-a-service] -================================================== +Creación de servicio +==================== -La mayoría de las veces, creamos un servicio simplemente creando una instancia de una clase: +Normalmente, creamos un servicio simplemente creando una instancia de una clase determinada. Por ejemplo: ```neon services: database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -Lo que generará un método de fábrica en [DI container|container]: +Si necesitamos ampliar la configuración con claves adicionales, la definición se puede desglosar en varias líneas: -```php -public function createServiceDatabase(): PDO -{ - return new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'secret'); -} +```neon +services: + database: + create: PDO('sqlite::memory:') + setup: ... ``` -Alternativamente, se puede usar una clave `arguments` para pasar [arguments|#Arguments]: +La clave `create` tiene el alias `factory`, ambas variantes son comunes en la práctica. Sin embargo, recomendamos usar `create`. + +Los argumentos del constructor o del método de creación pueden escribirse alternativamente en la clave `arguments`: ```neon services: @@ -63,294 +65,303 @@ services: arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret] ``` -Un método estático también puede crear un servicio: +Los servicios no tienen que crearse solo mediante la simple creación de una instancia de clase, también pueden ser el resultado de llamar a métodos estáticos o métodos de otros servicios: ```neon services: - database: My\Database::create(root, secret) + database: DatabaseFactory::create() + router: @routerFactory::create() ``` -Corresponde al código PHP: +Observe que, por simplicidad, se usa `::` en lugar de `->`, consulte [#expresiones]. Se generarán estos métodos de fábrica: ```php public function createServiceDatabase(): PDO { - return My\Database::create('root', 'secret'); + return DatabaseFactory::create(); +} + +public function createServiceRouter(): RouteList +{ + return $this->getService('routerFactory')->create(); } ``` -Se supone que un método estático `My\Database::create()` tiene un valor de retorno definido que el contenedor DI necesita conocer. Si no lo tiene, escribimos el tipo en la configuración: +El contenedor DI necesita conocer el tipo del servicio creado. Si creamos un servicio mediante un método que no tiene especificado un tipo de retorno, debemos indicar explícitamente este tipo en la configuración: ```neon services: database: - create: My\Database::create(root, secret) + create: DatabaseFactory::create() type: PDO ``` -Nette DI te da facilidades de expresión extremadamente potentes para escribir casi cualquier cosa. Por ejemplo, para [refer|#Referencing services] a otro servicio y llamar a su método. Para simplificar, se utiliza `::` en lugar de `->`. + +Argumentos +========== + +Pasamos argumentos al constructor y a los métodos de una manera muy similar a como se hace en PHP mismo: ```neon services: - routerFactory: App\Router\Factory - router: @routerFactory::create() + database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -Corresponde al código PHP: - -```php -public function createServiceRouterFactory(): App\Router\Factory -{ - return new App\Router\Factory; -} +Para una mejor legibilidad, podemos desglosar los argumentos en líneas separadas. En tal caso, el uso de comas es opcional: -public function createServiceRouter(): Router -{ - return $this->getService('routerFactory')->create(); -} +```neon +services: + database: PDO( + 'mysql:host=127.0.0.1;dbname=test' + root + secret + ) ``` -Las llamadas a métodos se pueden encadenar como en PHP: +También puede nombrar los argumentos y no tener que preocuparse por su orden: ```neon services: - foo: FooFactory::build()::get() + database: PDO( + username: root + password: secret + dsn: 'mysql:host=127.0.0.1;dbname=test' + ) ``` -Corresponde a código PHP: +Si desea omitir algunos argumentos y usar su valor predeterminado o inyectar un servicio mediante [autowiring|autowiring], use un guion bajo: -```php -public function createServiceFoo() -{ - return FooFactory::build()->get(); -} +```neon +services: + foo: Foo(_, %appDir%) ``` +Como argumentos se pueden pasar servicios, usar parámetros y mucho más, consulte [#expresiones]. + -Argumentos .[#toc-arguments] -============================ +Setup +===== -Los parámetros con nombre también pueden usarse para pasar argumentos: +En la sección `setup`, definimos los métodos que se deben llamar al crear el servicio. ```neon services: - database: PDO( - 'mysql:host=127.0.0.1;dbname=test' # positional - username: root # named - password: secret # named - ) + database: + create: PDO(%dsn%, %user%, %password%) + setup: + - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) ``` -El uso de comas es opcional cuando se dividen los argumentos en varias líneas. +Esto se vería así en PHP: -Por supuesto, también podemos utilizar [otros servicios|#Referencing Services] o [parámetros|configuration#parameters] como argumentos: +```php +public function createServiceDatabase(): PDO +{ + $service = new PDO('...', '...', '...'); + $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + return $service; +} +``` + +Además de llamar a métodos, también se pueden pasar valores a las propiedades. También se admite agregar un elemento a un array, lo cual debe escribirse entre comillas para no entrar en conflicto con la sintaxis de NEON: ```neon services: - - Foo(@anotherService, %appDir%) + foo: + create: Foo + setup: + - $value = 123 + - '$onClick[]' = [@bar, clickHandler] ``` -Corresponde a código PHP: +Lo que se vería así en el código PHP: ```php -public function createService01(): Foo +public function createServiceFoo(): Foo { - return new Foo($this->getService('anotherService'), '...'); + $service = new Foo; + $service->value = 123; + $service->onClick[] = [$this->getService('bar'), 'clickHandler']; + return $service; } ``` -Si el primer argumento es [autowired|autowiring] y quieres especificar el segundo, omite el primero con el caracter `_`, por ejemplo `Foo(_, %appDir%)`. O mejor aún, pase sólo el segundo argumento como un parámetro con nombre, por ejemplo `Foo(path: %appDir%)`. - -Nette DI y el formato NEON le ofrecen facilidades expresivas extremadamente potentes para escribir casi cualquier cosa. Así, un argumento puede ser un objeto recién creado, puede llamar a métodos estáticos, métodos de otros servicios, o incluso funciones globales utilizando una notación especial: +En setup, sin embargo, también se pueden llamar métodos estáticos o métodos de otros servicios. Si necesita pasar el servicio actual como argumento, indíquelo como `@self`: ```neon services: - analyser: My\Analyser( - FilesystemIterator(%appDir%) # crear objeto - DateTime::createFromFormat('Y-m-d') # llamar método estático - @anotherService # pasar otro servicio - @http.request::getRemoteAddress() # llamar a otro método de servicio - ::getenv(NetteMode) # llamar a una función global - ) + foo: + create: Foo + setup: + - My\Helpers::initializeFoo(@self) + - @anotherService::setFoo(@self) ``` -Corresponde al código PHP: +Observe que, por simplicidad, se usa `::` en lugar de `->`, consulte [#expresiones]. Se generará tal método de fábrica: ```php -public function createServiceAnalyser(): My\Analyser +public function createServiceFoo(): Foo { - return new My\Analyser( - new FilesystemIterator('...'), - DateTime::createFromFormat('Y-m-d'), - $this->getService('anotherService'), - $this->getService('http.request')->getRemoteAddress(), - getenv('NetteMode') - ); + $service = new Foo; + My\Helpers::initializeFoo($service); + $this->getService('anotherService')->setFoo($service); + return $service; } ``` -Funciones especiales .[#toc-special-functions] ----------------------------------------------- - -También puede utilizar funciones especiales en los argumentos para convertir o negar valores: +Expresiones +=========== -- `not(%arg%)` negation -- `bool(%arg%)` lossless cast to bool -- `int(%arg%)` lossless cast to int -- `float(%arg%)` lossless cast to float -- `string(%arg%)` lossless cast to string +Nette DI nos proporciona capacidades expresivas extraordinariamente ricas, con las que podemos escribir casi cualquier cosa. En los archivos de configuración, podemos usar [parámetros |configuration#Parámetros]: ```neon -services: - - Foo( - id: int(::getenv('ProjectId')) - productionMode: not(%debugMode%) - ) -``` - -La reescritura sin pérdidas difiere de la reescritura PHP normal, por ejemplo usando `(int)`, en que lanza una excepción para valores no numéricos. +# parámetro +%wwwDir% -Se pueden pasar múltiples servicios como argumentos. La función `typed()` crea un array de todos los servicios de un tipo particular (clase o interfaz). La función omitirá los servicios que tengan desactivado el autowiring, y se pueden especificar múltiples tipos separados por una coma. +# valor del parámetro bajo la clave +%mailer.user% -```neon -services: - - BarsDependent( typed(Bar) ) +# parámetro dentro de una cadena +'%wwwDir%/images' ``` -También puede pasar un array de servicios automáticamente usando [autowiring|autowiring#Collection of Services]. - -La función `tagged()` crea una matriz de todos los servicios con una determinada [tag|#tags]. Se pueden especificar múltiples etiquetas separadas por una coma. +Además, crear objetos, llamar a métodos y funciones: ```neon -services: - - LoggersDependent( tagged(logger) ) -``` +# creación de objeto +DateTime() +# llamada a método estático +Collator::create(%locale%) -Referencia a servicios .[#toc-referencing-services] -=================================================== +# llamada a función PHP +::getenv(DB_USER) +``` -Los servicios individuales se referencian utilizando el carácter `@` y el nombre, así por ejemplo `@database`: +Referirse a servicios ya sea por su nombre o por tipo: ```neon -services: - - create: Foo(@database) - setup: - - setCacheStorage(@cache.storage) +# servicio por nombre +@database + +# servicio por tipo +@Nette\Database\Connection ``` -Corresponde a código PHP: +Usar la sintaxis callable de primera clase: .{data-version:3.2.0} -```php -public function createService01(): Foo -{ - $service = new Foo($this->getService('database')); - $service->setCacheStorage($this->getService('cache.storage')); - return $service; -} +```neon +# creación de callback, análogo a [@user, logout] +@user::logout(...) ``` -Incluso los servicios anónimos pueden ser referenciados usando una llamada de retorno, simplemente especificando su tipo (clase o interfaz) en lugar de su nombre. Sin embargo, esto no suele ser necesario debido a [autowiring]. +Usar constantes: ```neon -services: - - create: Foo(@Nette\Database\Connection) # or @\PDO - setup: - - setCacheStorage(@cache.storage) +# constante de clase +FilesystemIterator::SKIP_DOTS + +# constante global se obtiene con la función PHP constant() +::constant(PHP_VERSION) ``` +Las llamadas a métodos se pueden encadenar igual que en PHP. Solo que, por simplicidad, se usa `::` en lugar de `->`: -Configuración .[#toc-setup] -=========================== +```neon +DateTime()::format('Y-m-d') +# PHP: (new DateTime())->format('Y-m-d') -En la sección setup listamos los métodos a llamar al crear el servicio: +@http.request::getUrl()::getHost() +# PHP: $this->getService('http.request')->getUrl()->getHost() +``` + +Puede usar estas expresiones en cualquier lugar, al [crear servicios |#Creación de servicio], en [#argumentos], en la sección [#setup] o en [parámetros |configuration#Parámetros]: ```neon +parameters: + ipAddress: @http.request::getRemoteAddress() + services: database: - create: PDO(%dsn%, %user%, %password%) + create: DatabaseFactory::create( @anotherService::getDsn() ) setup: - - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) + - initialize( ::getenv('DB_USER') ) ``` -Corresponde al código PHP: -```php -public function createServiceDatabase(): PDO -{ - $service = new PDO('...', '...', '...'); - $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - return $service; -} -``` +Funciones especiales +-------------------- -También se pueden establecer propiedades. También se puede añadir un elemento a un array, y debe escribirse entre comillas para no entrar en conflicto con la sintaxis de NEON: +En los archivos de configuración puede usar estas funciones especiales: +- `not()` negación del valor +- `bool()`, `int()`, `float()`, `string()` conversión sin pérdidas al tipo dado +- `typed()` crea un array de todos los servicios del tipo especificado +- `tagged()` crea un array de todos los servicios con el tag dado ```neon services: - foo: - create: Foo - setup: - - $value = 123 - - '$onClick[]' = [@bar, clickHandler] + - Foo( + id: int(::getenv('ProjectId')) + productionMode: not(%debugMode%) + ) ``` -Corresponde a código PHP: +A diferencia de la conversión de tipos clásica en PHP, como `(int)`, la conversión sin pérdidas lanzará una excepción para valores no numéricos. -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - $service->value = 123; - $service->onClick[] = [$this->getService('bar'), 'clickHandler']; - return $service; -} +La función `typed()` crea un array de todos los servicios del tipo dado (clase o interfaz). Omite los servicios que tienen el autowiring desactivado. Se pueden especificar múltiples tipos separados por comas. + +```neon +services: + - BarsDependent( typed(Bar) ) ``` -Sin embargo, los métodos estáticos o de otros servicios también pueden ser llamados en la configuración. Les pasamos el servicio real como `@self`: +También puede pasar un array de servicios de un tipo determinado como argumento automáticamente mediante [autowiring |autowiring#Array de servicios]. +La función `tagged()` crea un array de todos los servicios con un tag determinado. Aquí también puede especificar múltiples tags separados por comas. ```neon services: - foo: - create: Foo - setup: - - My\Helpers::initializeFoo(@self) - - @anotherService::setFoo(@self) + - LoggersDependent( tagged(logger) ) ``` -Corresponde al código PHP: -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - My\Helpers::initializeFoo($service); - $this->getService('anotherService')->setFoo($service); - return $service; -} +Autowiring +========== + +La clave `autowired` permite influir en el comportamiento del autowiring para un servicio específico. Para más detalles, consulte el [capítulo sobre autowiring|autowiring]. + +```neon +services: + foo: + create: Foo + autowired: false # el servicio foo se excluye del autowiring ``` -Autowiring (autocableado) .[#toc-autowiring] -============================================ +Servicios Lazy .{data-version:3.2.4} +==================================== -La clave de autocableado se puede utilizar para excluir un servicio del autocableado o para influir en su comportamiento. Ver [chapter on autowiring|autowiring] para más información. +La carga diferida (lazy loading) es una técnica que pospone la creación de un servicio hasta el momento en que realmente se necesita. En la configuración global, se puede [habilitar la creación lazy |configuration#Servicios lazy] para todos los servicios a la vez. Para servicios individuales, puede sobrescribir este comportamiento: ```neon services: foo: create: Foo - autowired: false # foo is removed from autowiring + lazy: false ``` +Cuando un servicio se define como lazy, al solicitarlo desde el contenedor DI, obtenemos un objeto proxy especial. Este se ve y se comporta igual que el servicio real, pero la inicialización real (llamada al constructor y setup) ocurre solo en la primera llamada a cualquiera de sus métodos o propiedades. + +.[note] +La carga diferida solo se puede usar para clases de usuario, nikoliv pro interní PHP třídy. Vyžaduje PHP 8.4 nebo novější.no para clases internas de PHP. Requiere PHP 8.4 o posterior. + -Etiquetas .[#toc-tags] -====================== +Tags +==== -Se puede añadir información de usuario a servicios individuales en forma de etiquetas: +Los tags sirven para agregar información adicional a los servicios. Puede agregar uno o más tags a un servicio: ```neon services: @@ -360,7 +371,7 @@ services: - cached ``` -Las etiquetas también pueden tener un valor: +Los tags también pueden llevar valores: ```neon services: @@ -370,26 +381,26 @@ services: logger: monolog.logger.event ``` -Con la función `tagged()` se puede pasar como argumento un array de servicios con determinadas etiquetas. También se pueden especificar varias etiquetas separadas por una coma. +Para obtener todos los servicios con ciertos tags, puede usar la función `tagged()`: ```neon services: - LoggersDependent( tagged(logger) ) ``` -Los nombres de los servicios pueden obtenerse del contenedor DI utilizando el método `findByTag()`: +En el contenedor DI, puede obtener los nombres de todos los servicios con un tag determinado usando el método `findByTag()`: ```php $names = $container->findByTag('logger'); -// $names es una matriz que contiene el nombre del servicio y el valor de la etiqueta -// i.e. ['foo' => 'monolog.logger.event', ...] +// $names es un array que contiene el nombre del servicio y el valor del tag +// por ej. ['foo' => 'monolog.logger.event', ...] ``` -Modo de inyección .[#toc-inject-mode] -===================================== +Modo Inject +=========== -El indicador `inject: true` se utiliza para activar el paso de dependencias a través de variables públicas con la anotación [inject |best-practices:inject-method-attribute#Inject Attributes] y los métodos [inject*() |best-practices:inject-method-attribute#inject Methods]. +Mediante el flag `inject: true` se activa el paso de dependencias a través de variables públicas con la anotación [inject |best-practices:inject-method-attribute#Atributos Inject] y los métodos [inject*() |best-practices:inject-method-attribute#Métodos inject]. ```neon services: @@ -398,13 +409,13 @@ services: inject: true ``` -Por defecto, `inject` sólo está activado para los presentadores. +Por defecto, `inject` está activado solo para los presenters. -Modificación de servicios .[#toc-modification-of-services] -========================================================== +Modificación de servicios +========================= -Hay una serie de servicios en el contenedor DI que han sido añadidos por built-in o [su extensión|#di-extensions]. Las definiciones de estos servicios se pueden modificar en la configuración. Por ejemplo, para el servicio `application.application`, que por defecto es un objeto `Nette\Application\Application`, podemos cambiar la clase: +El contenedor DI contiene muchos servicios que fueron agregados mediante extensiones incorporadas o [de usuario|extensions]. Puede modificar las definiciones de estos servicios directamente en la configuración. Por ejemplo, puede cambiar la clase del servicio `application.application`, que es estándarmente `Nette\Application\Application`, por otra: ```neon services: @@ -413,9 +424,9 @@ services: alteration: true ``` -La bandera `alteration` es informativa e indica que estamos modificando un servicio existente. +El flag `alteration` es informativo e indica que solo estamos modificando un servicio existente. -También podemos añadir una configuración: +También podemos complementar el setup: ```neon services: @@ -426,7 +437,7 @@ services: - '$onStartup[]' = [@resource, init] ``` -Al reescribir un servicio, es posible que queramos eliminar los argumentos, elementos de configuración o etiquetas originales, que es para lo que sirve `reset`: +Al sobrescribir un servicio, podemos querer eliminar los argumentos originales, elementos de setup o tags, para lo cual se usa `reset`: ```neon services: @@ -439,7 +450,7 @@ services: - tags ``` -Un servicio añadido por extensión también se puede eliminar del contenedor: +Si desea eliminar un servicio agregado por una extensión, puede hacerlo así: ```neon services: diff --git a/dependency-injection/fr/@home.texy b/dependency-injection/fr/@home.texy index a5a7bfffec..0d8bf439da 100644 --- a/dependency-injection/fr/@home.texy +++ b/dependency-injection/fr/@home.texy @@ -1,24 +1,21 @@ -Injection de dépendances -************************ +Nette DI +******** .[perex] -L'injection de dépendances est un modèle de conception qui va fondamentalement changer votre façon de voir le code et le développement. Il ouvre la voie à un monde d'applications propres et durables. +L'Injection de Dépendances est un patron de conception qui changera fondamentalement votre façon de voir le code et le développement. Il vous ouvrira la voie vers un monde d'applications conçues proprement et maintenables. -- [Qu'est-ce que l'injection de dépendances ? |introduction] +- [Qu'est-ce que l'Injection de Dépendances ? |introduction] - [État global et singletons |global-state] - [Passage des dépendances |passing-dependencies] - [Qu'est-ce qu'un conteneur DI ? |container] -- [Questions Fréquemment Posées |faq] - +- [Foire aux questions|faq] -Nette DI --------- -Le paquetage `nette/di` fournit un conteneur DI compilé extrêmement avancé pour PHP. +Le paquet `nette/di` fournit un conteneur DI compilé extrêmement avancé pour PHP. - [Conteneur Nette DI |nette-container] - [Configuration |configuration] -- [Définitions des services |services] -- [Autocâblage |autowiring] -- [Usines générées |factory] -- [Création d'extensions pour Nette DI |extensions] +- [Définition des services |services] +- [Autowiring |autowiring] +- [Factories générées |factory] +- [Création d'extensions pour Nette DI|extensions] diff --git a/dependency-injection/fr/@left-menu.texy b/dependency-injection/fr/@left-menu.texy index a140c8dbc4..5562cd8ba9 100644 --- a/dependency-injection/fr/@left-menu.texy +++ b/dependency-injection/fr/@left-menu.texy @@ -1,17 +1,17 @@ Injection de dépendances ************************ - [Qu'est-ce que DI ? |introduction] -- [Etat global et singletons |global-state] -- [Passer les dépendances |passing-dependencies] -- [Qu'est-ce que DI Container ? |container] -- [Questions fréquemment posées |faq] +- [État global et singletons |global-state] +- [Passage des dépendances |passing-dependencies] +- [Qu'est-ce qu'un conteneur DI ? |container] +- [Foire aux questions|faq] Nette DI -------- -- [Nette DI Container |nette-container] +- [Conteneur Nette DI |nette-container] - [Configuration |configuration] -- [Définitions des services |services] -- [Autocâblage |autowiring] -- [Usines générées |factory] -- [Création d'extensions pour Nette DI |extensions] +- [Définition des services |services] +- [Autowiring |autowiring] +- [Factories générées |factory] +- [Création d'extensions pour Nette DI|extensions] diff --git a/dependency-injection/fr/@meta.texy b/dependency-injection/fr/@meta.texy new file mode 100644 index 0000000000..72ae4b8db8 --- /dev/null +++ b/dependency-injection/fr/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentation Nette}} diff --git a/dependency-injection/fr/autowiring.texy b/dependency-injection/fr/autowiring.texy index 1ab809a378..bdb671496a 100644 --- a/dependency-injection/fr/autowiring.texy +++ b/dependency-injection/fr/autowiring.texy @@ -1,24 +1,24 @@ -Câblage automatique -******************* +Autowiring +********** .[perex] -Le câblage automatique est une excellente fonctionnalité qui permet de transmettre automatiquement des services au constructeur et à d'autres méthodes, de sorte que nous n'avons pas besoin de les écrire du tout. Cela permet de gagner beaucoup de temps. +L'autowiring est une fonctionnalité formidable qui peut automatiquement passer les services requis au constructeur et à d'autres méthodes, de sorte que nous n'avons pas du tout besoin de les écrire. Cela vous fait gagner beaucoup de temps. -Cela nous permet de sauter la grande majorité des arguments lors de l'écriture des définitions de services. Au lieu de : +Grâce à cela, nous pouvons omettre la grande majorité des arguments lors de l'écriture des définitions de service. Au lieu de : ```neon services: articles: Model\ArticleRepository(@database, @cache.storage) ``` -écrivez simplement : +Il suffit d'écrire : ```neon services: articles: Model\ArticleRepository ``` -Le câblage automatique étant guidé par les types, la classe `ArticleRepository` doit être définie comme suit : +L'autowiring est basé sur les types, donc pour qu'il fonctionne, la classe `ArticleRepository` doit être définie à peu près comme ceci : ```php namespace Model; @@ -30,22 +30,22 @@ class ArticleRepository } ``` -Pour utiliser le câblage automatique, il doit y avoir **un seul service** pour chaque type dans le conteneur. S'il y en avait plus, l'autowiring ne saurait pas lequel passer et lancerait une exception : +Pour pouvoir utiliser l'autowiring, il doit y avoir **exactement un service** pour chaque type dans le conteneur. S'il y en avait plus, l'autowiring ne saurait pas lequel passer et lèverait une exception : ```neon services: mainDb: PDO(%dsn%, %user%, %password%) tempDb: PDO('sqlite::memory:') - articles: Model\ArticleRepository # THROWS EXCEPTION, mainDb and tempDb matches + articles: Model\ArticleRepository # LÈVE UNE EXCEPTION, mainDb et tempDb correspondent ``` -La solution serait soit de contourner l'autowiring et d'indiquer explicitement le nom du service (par exemple `articles: Model\ArticleRepository(@mainDb)`). Cependant, il est plus pratique de [désactiver le |#Disabled autowiring] câblage automatique d'un seul service, ou du premier service [préféré |#Preferred Autowiring]. +La solution serait soit de contourner l'autowiring et de spécifier explicitement le nom du service (c'est-à-dire `articles: Model\ArticleRepository(@mainDb)`). Mais il est plus judicieux de [désactiver |#Désactivation de l autowiring] l'autowiring pour l'un des services, ou de [préférer |#Préférence d autowiring] le premier service. -Câblage automatique désactivé .[#toc-disabled-autowiring] ---------------------------------------------------------- +Désactivation de l'autowiring +----------------------------- -Vous pouvez désactiver le câblage automatique des services en utilisant l'option `autowired: no`: +Nous pouvons désactiver l'autowiring d'un service à l'aide de l'option `autowired: no` : ```neon services: @@ -53,28 +53,27 @@ services: tempDb: create: PDO('sqlite::memory:') - autowired: false # supprime l'autowiring de tempDb + autowired: false # le service tempDb est exclu de l'autowiring - articles: Model\ArticleRepository # passe donc mainDb au constructeur + articles: Model\ArticleRepository # donc il passe mainDb au constructeur ``` -Le service `articles` ne lève pas l'exception selon laquelle il existe deux services correspondants de type `PDO` (c'est-à-dire `mainDb` et `tempDb`) qui peuvent être transmis au constructeur, car il ne voit que le service `mainDb`. +Le service `articles` ne lèvera pas d'exception indiquant qu'il existe deux services correspondants de type `PDO` (c'est-à-dire `mainDb` et `tempDb`) qui peuvent être passés au constructeur, car il ne voit que le service `mainDb`. .[note] -La configuration du câblage automatique dans Nette fonctionne différemment de celle de Symfony, où l'option `autowire: false` indique que le câblage automatique ne doit pas être utilisé pour les arguments des constructeurs de services. -Dans Nette, le câblage automatique est toujours utilisé, que ce soit pour les arguments du constructeur ou de toute autre méthode. L'option `autowired: false` indique que l'instance du service ne doit être transmise nulle part en utilisant le câblage automatique. +La configuration de l'autowiring dans Nette fonctionne différemment de Symfony, où l'option `autowire: false` indique que l'autowiring ne doit pas être utilisé pour les arguments du constructeur du service donné. Dans Nette, l'autowiring est toujours utilisé, que ce soit pour les arguments du constructeur ou pour toute autre méthode. L'option `autowired: false` indique que l'instance du service donné ne doit être passée nulle part via l'autowiring. -Câblage automatique préféré .[#toc-preferred-autowiring] --------------------------------------------------------- +Préférence d'autowiring +----------------------- -Si nous avons plusieurs services du même type et que l'un d'entre eux possède l'option `autowired`, ce service devient le service préféré : +Si nous avons plusieurs services du même type et que nous spécifions l'option `autowired` pour l'un d'entre eux, ce service devient le préféré : ```neon services: mainDb: create: PDO(%dsn%, %user%, %password%) - autowired: PDO # le fait préférer + autowired: PDO # devient préféré tempDb: create: PDO('sqlite::memory:') @@ -82,13 +81,13 @@ services: articles: Model\ArticleRepository ``` -Le service `articles` ne lève pas l'exception selon laquelle il existe deux services `PDO` correspondants (c'est-à-dire `mainDb` et `tempDb`), mais utilise le service préféré, c'est-à-dire `mainDb`. +Le service `articles` ne lèvera pas d'exception indiquant qu'il existe deux services correspondants de type `PDO` (c'est-à-dire `mainDb` et `tempDb`), mais utilisera le service préféré, c'est-à-dire `mainDb`. -Collection de services .[#toc-collection-of-services] ------------------------------------------------------ +Tableau de services +------------------- -L'autowiring peut aussi passer un tableau de services d'un type particulier. Comme PHP ne peut pas noter nativement le type des éléments d'un tableau, en plus du type `array`, un commentaire phpDoc avec le type d'élément comme `ClassName[]` doit être ajouté : +L'autowiring peut également passer des tableaux de services d'un certain type. Comme il n'est pas possible d'écrire nativement le type des éléments d'un tableau en PHP, il faut, en plus du type `array`, ajouter un commentaire phpDoc avec le type de l'élément sous la forme `ClassName[]` : ```php namespace Model; @@ -103,16 +102,15 @@ class ShipManager } ``` -Le conteneur DI passe alors automatiquement un tableau de services correspondant au type donné. Il omettra les services dont le câblage automatique est désactivé. +Le conteneur DI passera alors automatiquement un tableau de services correspondant au type donné. Il omettra les services dont l'autowiring est désactivé. -Si vous ne pouvez pas contrôler la forme du commentaire phpDoc, vous pouvez passer un tableau de services directement dans la configuration en utilisant la commande [`typed()` |services#Special Functions]. +Le type dans le commentaire peut également être sous la forme `array<int, Class>` ou `list<Class>`. Si vous ne pouvez pas influencer la forme du commentaire phpDoc, vous pouvez passer le tableau de services directement dans la configuration à l'aide de [`typed()` |services#Fonctions spéciales]. -Arguments scalaires .[#toc-scalar-arguments] --------------------------------------------- +Arguments scalaires +------------------- -Le câblage automatique ne peut transmettre que des objets et des tableaux d'objets. Les arguments scalaires (par exemple, les chaînes de caractères, les nombres, les booléens) [écrivent dans la configuration |services#Arguments]. -Une alternative est de créer un [settings-object |best-practices:passing-settings-to-presenters] qui encapsule une valeur scalaire (ou plusieurs valeurs) comme un objet, qui peut ensuite être passé à nouveau en utilisant le câblage automatique. +L'autowiring ne peut injecter que des objets et des tableaux d'objets. Les arguments scalaires (par exemple, chaînes, nombres, booléens) sont [écrits dans la configuration |services#Arguments]. Une alternative est de créer un [objet de paramètres |best-practices:passing-settings-to-presenters], qui encapsule la valeur scalaire (ou plusieurs valeurs) sous forme d'objet, lequel peut ensuite être à nouveau passé via l'autowiring. ```php class MySettings @@ -125,24 +123,24 @@ class MySettings } ``` -Vous créez un service en l'ajoutant à la configuration : +Vous en faites un service en l'ajoutant à la configuration : ```neon services: - MySettings('any value') ``` -Toutes les classes le demanderont alors via le câblage automatique. +Toutes les classes le demanderont ensuite via l'autowiring. -Restriction du câblage automatique .[#toc-narrowing-of-autowiring] ------------------------------------------------------------------- +Réduction de l'autowiring +------------------------- -Pour les services individuels, le câblage automatique peut être limité à des classes ou des interfaces spécifiques. +Pour les services individuels, l'autowiring peut être limité à certaines classes ou interfaces. -Normalement, le câblage automatique transmet le service à chaque paramètre de méthode dont le type correspond au service. Le rétrécissement signifie que nous spécifions les conditions que les types spécifiés pour les paramètres de méthode doivent satisfaire pour que le service leur soit transmis. +Normalement, l'autowiring passe le service à chaque paramètre de méthode dont le type correspond au service. La réduction signifie que nous définissons des conditions auxquelles les types spécifiés pour les paramètres de méthode doivent satisfaire pour que le service leur soit passé. -Prenons un exemple : +Illustrons cela par un exemple : ```php class ParentClass @@ -164,42 +162,42 @@ class ChildDependent } ``` -Si nous les enregistrions tous en tant que services, le câblage automatique échouerait : +Si nous les enregistrions tous comme services, l'autowiring échouerait : ```neon services: parent: ParentClass child: ChildClass - parentDep: ParentDependent # THROWS EXCEPTION, le parent et l'enfant correspondent tous les deux - childDep: ChildDependent # passe le service 'child' au constructeur + parentDep: ParentDependent # LÈVE UNE EXCEPTION, les services parent et child correspondent + childDep: ChildDependent # l'autowiring passe le service child au constructeur ``` -Le service `parentDep` lève l'exception `Multiple services of type ParentClass found: parent, child` parce que `parent` et `child` entrent tous deux dans son constructeur et que l'autowiring ne peut pas décider lequel choisir. +Le service `parentDep` lèvera une exception `Multiple services of type ParentClass found: parent, child`, car les deux services `parent` et `child` correspondent à son constructeur, et l'autowiring ne peut pas décider lequel choisir. -Pour le service `child`, nous pouvons donc réduire son autowiring à `ChildClass`: +Pour le service `child`, nous pouvons donc réduire son autowiring au type `ChildClass` : ```neon services: parent: ParentClass child: create: ChildClass - autowired: ChildClass # alternative: 'autowired: self' + autowired: ChildClass # peut aussi s'écrire 'autowired: self' - parentDep: ParentDependent # THROWS EXCEPTION, le 'child' ne peut pas être autowired - childDep: ChildDependent # passe le service 'child' au constructeur + parentDep: ParentDependent # l'autowiring passe le service parent au constructeur + childDep: ChildDependent # l'autowiring passe le service child au constructeur ``` -Le service `parentDep` est maintenant passé au constructeur du service `parentDep`, puisqu'il est maintenant le seul objet correspondant. Le service `child` n'est plus passé par autowiring. Oui, le service `child` est toujours de type `ParentClass`, mais la condition de restriction donnée pour le type de paramètre ne s'applique plus, c'est-à-dire qu'il n'est plus vrai que `ParentClass` *est un supertype* de `ChildClass`. +Maintenant, le service `parent` est passé au constructeur du service `parentDep`, car c'est maintenant le seul objet correspondant. L'autowiring ne passera plus le service `child` là-bas. Oui, le service `child` est toujours de type `ParentClass`, mais la condition de réduction donnée pour le type de paramètre n'est plus remplie, c'est-à-dire qu'il n'est pas vrai que `ParentClass` *est un supertype de* `ChildClass`. -Dans le cas de `child`, `autowired: ChildClass` pourrait être écrit comme `autowired: self` car `self` signifie le type de service actuel. +Pour le service `child`, `autowired: ChildClass` pourrait également être écrit comme `autowired: self`, car `self` est un alias pour la classe du service actuel. -La clé `autowired` peut inclure plusieurs classes et interfaces comme tableau : +Dans la clé `autowired`, il est également possible de spécifier plusieurs classes ou interfaces sous forme de tableau : ```neon autowired: [BarClass, FooInterface] ``` -Essayons d'ajouter des interfaces à l'exemple : +Essayons de compléter l'exemple avec des interfaces : ```php interface FooInterface @@ -239,13 +237,13 @@ class ChildDependent } ``` -Si nous ne limitons pas le service `child`, il s'insérera dans les constructeurs de toutes les classes `FooDependent`, `BarDependent`, `ParentDependent` et `ChildDependent` et le câblage automatique l'y fera passer. +Si nous ne limitons pas le service `child` de quelque manière que ce soit, il correspondra aux constructeurs de toutes les classes `FooDependent`, `BarDependent`, `ParentDependent` et `ChildDependent` et l'autowiring l'y passera. -Cependant, si nous limitons son autowiring à `ChildClass` en utilisant `autowired: ChildClass` (ou `self`), l'autowiring ne le transmet qu'au constructeur `ChildDependent`, car il requiert un argument de type `ChildClass` et `ChildClass` *est de type* `ChildClass`. Aucun autre type spécifié pour les autres paramètres n'est un superset de `ChildClass`, donc le service n'est pas transmis. +Cependant, si nous limitons son autowiring à `ChildClass` en utilisant `autowired: ChildClass` (ou `self`), l'autowiring ne le passera qu'au constructeur de `ChildDependent`, car il nécessite un argument de type `ChildClass` et il est vrai que `ChildClass` *est de type* `ChildClass`. Aucun autre type spécifié pour les autres paramètres n'est un supertype de `ChildClass`, donc le service ne sera pas passé. -Si nous le limitons à `ParentClass` en utilisant `autowired: ParentClass`, le câblage automatique le transmettra à nouveau au constructeur `ChildDependent` (puisque le type requis `ChildClass` est un superset de `ParentClass`) et au constructeur `ParentDependent` également, puisque le type requis de `ParentClass` correspond également. +Si nous le limitons à `ParentClass` en utilisant `autowired: ParentClass`, il sera à nouveau passé au constructeur de `ChildDependent` (car le `ChildClass` requis est un supertype de `ParentClass`) et nouvellement aussi au constructeur de `ParentDependent`, car le type `ParentClass` requis est également satisfaisant. -Si nous le limitons à `FooInterface`, il se connectera toujours automatiquement à `ParentDependent` (le type requis `ParentClass` est un supertype de `FooInterface`) et `ChildDependent`, mais aussi au constructeur `FooDependent`, mais pas à `BarDependent`, puisque `BarInterface` n'est pas un supertype de `FooInterface`. +Si nous le limitons à `FooInterface`, il sera toujours autowiré dans `ParentDependent` (le `ParentClass` requis est un supertype de `FooInterface`) et `ChildDependent`, mais en plus aussi dans le constructeur de `FooDependent`, mais pas dans `BarDependent`, car `BarInterface` n'est pas un supertype de `FooInterface`. ```neon services: @@ -253,8 +251,8 @@ services: create: ChildClass autowired: FooInterface - fooDep: FooDependent # passe le service enfant au constructeur - barDep: BarDependent # THROWS EXCEPTION, aucun service ne serait passé - parentDep: ParentDependent # passe le service enfant au constructeur - childDep: ChildDependent # passe le service enfant au constructeur + fooDep: FooDependent # l'autowiring passe le service child au constructeur + barDep: BarDependent # LÈVE UNE EXCEPTION, aucun service ne correspond + parentDep: ParentDependent # l'autowiring passe le service child au constructeur + childDep: ChildDependent # l'autowiring passe le service child au constructeur ``` diff --git a/dependency-injection/fr/configuration.texy b/dependency-injection/fr/configuration.texy index 0a423fb41b..a5167ac052 100644 --- a/dependency-injection/fr/configuration.texy +++ b/dependency-injection/fr/configuration.texy @@ -2,31 +2,32 @@ Configuration du conteneur DI ***************************** .[perex] -Aperçu des options de configuration du conteneur DI de Nette. +Aperçu des options de configuration pour le conteneur Nette DI. Fichier de configuration ======================== -Le conteneur Nette DI est facile à contrôler à l'aide de fichiers de configuration. Ceux-ci sont généralement écrits au [format NEON |neon:format]. Nous recommandons d'utiliser des [éditeurs prenant en charge |best-practices:editors-and-tools#ide-editor] ce format pour les éditer. +Le conteneur Nette DI est facilement contrôlé à l'aide de fichiers de configuration. Ceux-ci sont généralement écrits au [format NEON |neon:format]. Pour l'édition, nous recommandons des [éditeurs avec support |best-practices:editors-and-tools#Éditeur IDE] pour ce format. <pre> "decorator .[prism-token prism-atrule]":[#Decorator]: "Décorateur .[prism-token prism-comment]"<br> "di .[prism-token prism-atrule]":[#DI]: "Conteneur DI .[prism-token prism-comment]"<br> -"extensions .[prism-token prism-atrule]":[#Extensions]: "Installer des extensions DI supplémentaires .[prism-token prism-comment]"<br> -"includes .[prism-token prism-atrule]":[#Including files]: "Incluant les fichiers .[prism-token prism-comment]"<br> -"parameters .[prism-token prism-atrule]":[#Parameters]: "Paramètres .[prism-token prism-comment]"<br> +"extensions .[prism-token prism-atrule]":[#Extensions]: "Installation d'extensions DI supplémentaires .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[#Inclusion de fichiers]: "Inclusion de fichiers .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[#Paramètres]: "Paramètres .[prism-token prism-comment]"<br> "search .[prism-token prism-atrule]":[#Search]: "Enregistrement automatique des services .[prism-token prism-comment]"<br> "services .[prism-token prism-atrule]":[services]: "Services .[prism-token prism-comment]" </pre> -Pour écrire une chaîne contenant le caractère `%`, you must escape it by doubling it to `%%`. .[note] +.[note] +Pour écrire une chaîne contenant le caractère `%`, vous devez l'échapper en le doublant en `%%`. -Paramètres .[#toc-parameters] -============================= +Paramètres +========== -Vous pouvez définir des paramètres qui peuvent ensuite être utilisés dans le cadre de définitions de services. Cela peut permettre de séparer les valeurs que vous souhaitez modifier plus régulièrement. +Dans la configuration, vous pouvez définir des paramètres qui peuvent ensuite être utilisés dans le cadre des définitions de service. Cela peut clarifier la configuration ou unifier et isoler les valeurs qui changeront. ```neon parameters: @@ -35,9 +36,9 @@ parameters: password: secret ``` -Vous pouvez faire référence au paramètre `foo` via `%foo%` ailleurs dans n'importe quel fichier de configuration. Ils peuvent également être utilisés à l'intérieur de chaînes de caractères comme `'%wwwDir%/images'`. +Nous nous référons au paramètre `dsn` n'importe où dans la configuration en écrivant `%dsn%`. Les paramètres peuvent également être utilisés à l'intérieur de chaînes comme `'%wwwDir%/images'`. -Les paramètres ne doivent pas nécessairement être des chaînes de caractères, ils peuvent également être des tableaux de valeurs : +Les paramètres ne doivent pas nécessairement être des chaînes ou des nombres, ils peuvent également contenir des tableaux : ```neon parameters: @@ -48,32 +49,32 @@ parameters: languages: [cs, en, de] ``` -Vous pouvez vous référer à une seule clé comme `%mailer.user%`. +Nous nous référons à une clé spécifique comme `%mailer.user%`. -Si vous avez besoin d'obtenir la valeur d'un paramètre dans votre code, par exemple dans votre classe, alors passez-le à cette classe. Par exemple, dans le constructeur. Il n'y a pas d'objet de configuration global que les classes peuvent interroger pour connaître les valeurs des paramètres. Cela irait à l'encontre du principe d'injection de dépendances. +Si vous avez besoin de connaître la valeur d'un paramètre dans votre code, par exemple dans une classe, passez-le à cette classe. Par exemple, dans le constructeur. Il n'existe pas d'objet global représentant la configuration que les classes interrogeraient pour les valeurs des paramètres. Cela violerait le principe d'injection de dépendances. -Services .[#toc-services] -========================= +Services +======== Voir le [chapitre séparé |services]. -Décorateur .[#toc-decorator] -============================ +Decorator +========= -Comment modifier en masse tous les services d'un certain type ? Vous avez besoin d'appeler une certaine méthode pour tous les présentateurs héritant d'un ancêtre commun particulier ? C'est là qu'intervient le décorateur. +Comment modifier en masse tous les services d'un certain type ? Par exemple, appeler une certaine méthode sur tous les presenters qui héritent d'un ancêtre commun spécifique ? C'est là qu'intervient le décorateur. ```neon decorator: # pour tous les services qui sont des instances de cette classe ou interface - App\Presenters\BasePresenter: + App\Presentation\BasePresenter: setup: - - setProjectId(10) # appelle cette méthode - - $absoluteUrls = true # et définir la variable + - setProjectId(10) # appelle cette méthode + - $absoluteUrls = true # et définit la variable ``` -Le décorateur peut également être utilisé pour définir des [balises |services#Tags] ou activer le [mode injection |services#Inject Mode]. +Le décorateur peut également être utilisé pour définir des [tags |services#Tags] ou activer le mode [inject |services#Mode Inject]. ```neon decorator: @@ -90,63 +91,75 @@ Paramètres techniques du conteneur DI. ```neon di: - # montre le DIC dans la barre de Tracy ? - debugger: ... # (bool) par défaut à true + # afficher le DIC dans la barre Tracy ? + debugger: ... # (bool) par défaut est true - # types de paramètres que l'on ne peut jamais câbler automatiquement + # types de paramètres à ne jamais autowirer excluded: ... # (string[]) - # la classe dont hérite le conteneur DI - parentClass: ... # (string) a pour valeur par défaut Nette\DI\Container + # autoriser la création paresseuse de services ? + lazy: ... # (bool) par défaut est false + + # classe dont hérite le conteneur DI + parentClass: ... # (string) par défaut est Nette\DI\Container ``` -Exportation de métadonnées .[#toc-metadata-export] --------------------------------------------------- +Services paresseux .{data-version:3.2.4} +---------------------------------------- + +Le paramètre `lazy: true` active la création paresseuse (différée) des services. Cela signifie que les services ne sont pas réellement créés au moment où nous les demandons au conteneur DI, mais seulement au moment de leur première utilisation. Cela peut accélérer le démarrage de l'application et réduire l'empreinte mémoire, car seuls les services réellement nécessaires dans la requête donnée sont créés. + +Pour un service spécifique, la création paresseuse peut être [modifiée |services#Services Lazy]. + +.[note] +Les objets paresseux ne peuvent être utilisés que pour les classes utilisateur, pas pour les classes PHP internes. Nécessite PHP 8.4 ou plus récent. + + +Exportation des métadonnées +--------------------------- -La classe conteneur DI contient également beaucoup de métadonnées. Vous pouvez les réduire en réduisant l'exportation de métadonnées. +La classe du conteneur DI contient également beaucoup de métadonnées. Vous pouvez la réduire en réduisant l'exportation des métadonnées. ```neon di: export: - # pour exporter les paramètres ? - parameters: false # (bool) par défaut à true + # exporter les paramètres ? + parameters: false # (bool) par défaut est true - # exporter les balises et lesquelles ? - tags: # (string[]|bool) la valeur par défaut est all + # exporter les tags et lesquels ? + tags: # (string[]|bool) par défaut tous - event.subscriber - # exporter les données pour le câblage automatique et lesquelles ? - types: # (string[]|bool) la valeur par défaut est all + # exporter les données pour l'autowiring et lesquelles ? + types: # (string[]|bool) par défaut toutes - Nette\Database\Connection - Symfony\Component\Console\Application ``` -Si vous n'utilisez pas le tableau `$container->parameters`, vous pouvez désactiver l'exportation des paramètres. En outre, vous pouvez exporter uniquement les balises par lesquelles vous obtenez des services en utilisant la méthode `$container->findByTag(...)`. -Si vous n'appelez pas du tout la méthode, vous pouvez désactiver complètement l'exportation des balises avec `false`. +Si vous n'utilisez pas le tableau `$container->getParameters()`, vous pouvez désactiver l'exportation des paramètres. De plus, vous pouvez exporter uniquement les tags via lesquels vous obtenez des services avec la méthode `$container->findByTag(...)`. Si vous n'appelez pas du tout la méthode, vous pouvez désactiver complètement l'exportation des tags en utilisant `false`. -Vous pouvez réduire considérablement les métadonnées pour le [câblage automatique |autowiring] en spécifiant les classes que vous utilisez comme paramètre de la méthode `$container->getByType()`. -Et encore une fois, si vous n'appelez pas du tout la méthode (ou seulement dans [application:bootstrap] pour obtenir `Nette\Application\Application`), vous pouvez désactiver entièrement l'exportation avec `false`. +Vous pouvez réduire considérablement les métadonnées pour l'[autowiring |autowiring] en listant les classes que vous utilisez comme paramètre de la méthode `$container->getByType()`. Et encore une fois, si vous n'appelez pas du tout la méthode (ou seulement dans le [bootstrap |application:bootstrapping] pour obtenir `Nette\Application\Application`), vous pouvez désactiver complètement l'exportation en utilisant `false`. -Extensions .[#toc-extensions] -============================= +Extensions +========== -Enregistrement d'autres extensions DI. De cette façon, nous ajoutons, par exemple, l'extension DI `Dibi\Bridges\Nette\DibiExtension22` sous le nom `dibi`: +Enregistrement d'extensions DI supplémentaires. De cette manière, nous ajoutons par exemple l'extension DI `Dibi\Bridges\Nette\DibiExtension22` sous le nom `dibi` ```neon extensions: dibi: Dibi\Bridges\Nette\DibiExtension22 ``` -Puis nous la configurons dans sa section appelée également `dibi`: +Ensuite, nous la configurons dans la section `dibi` : ```neon dibi: host: localhost ``` -Vous pouvez également ajouter une classe d'extension avec des paramètres : +Une classe avec des paramètres peut également être ajoutée comme extension : ```neon extensions: @@ -154,10 +167,10 @@ extensions: ``` -Inclure des fichiers .[#toc-including-files] -============================================ +Inclusion de fichiers +===================== -Des fichiers de configuration supplémentaires peuvent être insérés dans la section `includes`: +Nous pouvons inclure d'autres fichiers de configuration dans la section `includes` : ```neon includes: @@ -166,7 +179,7 @@ includes: - presenters.neon ``` -Le nom `parameters.php` n'est pas une faute de frappe, la configuration peut également être écrite dans un fichier PHP, qui la renvoie sous forme de tableau : +Le nom `parameters.php` n'est pas une faute de frappe, la configuration peut également être écrite dans un fichier PHP qui la renvoie sous forme de tableau : ```php <?php @@ -179,34 +192,29 @@ return [ ]; ``` -Si des éléments avec les mêmes clés apparaissent dans les fichiers de configuration, ils seront [écrasés ou fusionnés |#Merging] dans le cas des tableaux. Le fichier inclus le plus tard a une priorité plus élevée que le précédent. Le fichier dans lequel figure la section `includes` a une priorité plus élevée que les fichiers qui y sont inclus. +Si des éléments avec les mêmes clés apparaissent dans les fichiers de configuration, ils seront écrasés ou, dans le cas de [tableaux fusionnés |#Fusion]. Le fichier inclus plus tard a une priorité plus élevée que le précédent. Le fichier dans lequel la section `includes` est listée a une priorité plus élevée que les fichiers qu'il inclut. -Recherche .[#toc-search] -======================== +Search +====== -L'ajout automatique de services au conteneur DI rend le travail extrêmement agréable. Nette ajoute automatiquement les présentateurs au conteneur, mais vous pouvez facilement ajouter toute autre classe. +L'ajout automatique de services au conteneur DI rend le travail extrêmement agréable. Nette ajoute automatiquement les presenters au conteneur, mais il est facile d'ajouter également d'autres classes. -Il suffit de préciser dans quels répertoires (et sous-répertoires) les classes doivent être recherchées : +Il suffit d'indiquer dans quels répertoires (et sous-répertoires) les classes doivent être recherchées : ```neon -recherche: - # vous choisissez vous-même les noms des sections - mesFormulaires: - in: %appDir%/Forms - - modèle: - in: %appDir%/Model +search: + - in: %appDir%/Forms + - in: %appDir%/Model ``` -En général, cependant, nous ne voulons pas ajouter toutes les classes et interfaces, nous pouvons donc les filtrer : +Cependant, nous ne voulons généralement pas ajouter absolument toutes les classes et interfaces, nous pouvons donc les filtrer : ```neon -recherche: - mesFormulaires: - in: %appDir%/Forms +search: + - in: %appDir%/Forms - # filtrer par nom de fichier (string|string[]) + # filtrage par nom de fichier (string|string[]) files: - *Factory.php @@ -215,42 +223,43 @@ recherche: - *Factory ``` -Ou nous pouvons sélectionner les classes qui héritent ou implémentent au moins une des classes suivantes : +Ou nous pouvons sélectionner des classes qui héritent ou implémentent au moins une des classes listées : ```neon search: - myForms: + - in: %appDir% extends: - App\*Form implements: - App\*FormInterface ``` -Vous pouvez également définir des règles négatives, c'est-à-dire des masques de noms de classes ou d'ancêtres et s'ils sont conformes, le service ne sera pas ajouté au conteneur DI : +Il est également possible de définir des règles d'exclusion, c'est-à-dire des masques de nom de classe ou des ancêtres héréditaires, qui, s'ils correspondent, empêchent l'ajout du service au conteneur DI : ```neon search: - myForms: + - in: %appDir% exclude: + files: ... classes: ... extends: ... implements: ... ``` -Des étiquettes peuvent être définies pour les services ajoutés : +Des tags peuvent être définis pour tous les services : ```neon search: - myForms: + - in: %appDir% tags: ... ``` -Fusionner .[#toc-merging] -========================= +Fusion +====== -Si des éléments ayant les mêmes clés apparaissent dans plusieurs fichiers de configuration, ils seront écrasés ou fusionnés dans le cas de tableaux. Le fichier inclus le plus tard a une priorité plus élevée. +Si des éléments avec les mêmes clés apparaissent dans plusieurs fichiers de configuration, ils seront écrasés ou, dans le cas de tableaux, fusionnés. Le fichier inclus plus tard a une priorité plus élevée que le précédent. <table class=table> <tr> @@ -283,7 +292,7 @@ items: </tr> </table> -Pour empêcher la fusion d'un certain tableau, utilisez le point d'exclamation juste après le nom du tableau : +Pour les tableaux, la fusion peut être empêchée en ajoutant un point d'exclamation après le nom de la clé : <table class=table> <tr> @@ -313,3 +322,5 @@ items: </td> </tr> </table> + +{{maintitle: Configuration de l'injection de dépendances}} diff --git a/dependency-injection/fr/container.texy b/dependency-injection/fr/container.texy index ff44e749b2..fe447ab679 100644 --- a/dependency-injection/fr/container.texy +++ b/dependency-injection/fr/container.texy @@ -2,15 +2,15 @@ Qu'est-ce qu'un conteneur DI ? ****************************** .[perex] -Le conteneur d'injection de dépendances (DIC) est une classe qui peut instancier et configurer des objets. +Un conteneur d'injection de dépendances (DIC) est une classe qui peut instancier et configurer des objets. -Cela peut vous surprendre, mais dans de nombreux cas, vous n'avez pas besoin d'un conteneur d'injection de dépendances pour profiter de l'injection de dépendances (DI en abrégé). Après tout, même dans le [chapitre précédent |introduction], nous avons montré des exemples spécifiques de DI et aucun conteneur n'était nécessaire. +Cela peut vous surprendre, mais dans de nombreux cas, vous n'avez pas besoin d'un conteneur d'injection de dépendances pour profiter des avantages de l'injection de dépendances (DI en abrégé). Après tout, même dans le [chapitre d'introduction |introduction], nous avons montré la DI avec des exemples concrets, et aucun conteneur n'était nécessaire. -Cependant, si vous devez gérer un grand nombre d'objets différents avec de nombreuses dépendances, un conteneur d'injection de dépendances sera vraiment utile. Ce qui est peut-être le cas pour les applications web construites sur un framework. +Cependant, si vous devez gérer un grand nombre d'objets différents avec de nombreuses dépendances, un conteneur d'injection de dépendances sera vraiment utile. C'est le cas, par exemple, des applications web construites sur un framework. -Dans le chapitre précédent, nous avons présenté les classes `Article` et `UserController`. Toutes deux ont quelques dépendances, à savoir la base de données et la fabrique `ArticleFactory`. Et pour ces classes, nous allons maintenant créer un conteneur. Bien sûr, pour un exemple aussi simple, cela n'a pas de sens d'avoir un conteneur. Mais nous allons en créer un pour montrer comment il se présente et fonctionne. +Dans le chapitre précédent, nous avons présenté les classes `Article` et `UserController`. Les deux ont des dépendances, à savoir la base de données et la factory `ArticleFactory`. Et pour ces classes, nous allons maintenant créer un conteneur. Bien sûr, pour un exemple aussi simple, il n'est pas logique d'avoir un conteneur. Mais nous allons le créer pour montrer à quoi il ressemble et comment il fonctionne. -Voici un conteneur simple codé en dur pour l'exemple ci-dessus : +Voici un conteneur simple codé en dur pour l'exemple donné : ```php class Container @@ -39,24 +39,24 @@ $container = new Container; $controller = $container->createUserController(); ``` -Nous demandons simplement l'objet au conteneur et n'avons plus besoin de savoir comment le créer ou quelles sont ses dépendances ; le conteneur sait tout cela. Les dépendances sont injectées automatiquement par le conteneur. C'est là toute sa puissance. +Nous demandons simplement l'objet au conteneur et nous n'avons plus besoin de savoir comment le créer ni quelles sont ses dépendances ; le conteneur sait tout cela. Les dépendances sont injectées automatiquement par le conteneur. C'est là sa force. -Jusqu'à présent, le conteneur a tout codé en dur. Nous passons donc à l'étape suivante et ajoutons des paramètres pour rendre le conteneur vraiment utile : +Pour l'instant, le conteneur a toutes les données codées en dur. Faisons donc un pas de plus et ajoutons des paramètres pour rendre le conteneur vraiment utile : ```php -classe Container +class Container { - fonction publique __construct( - tableau privé $paramètres, + public function __construct( + private array $parameters, ) { } - fonction publique createDatabase(): Connexion à la base de données Nette\Database\ + public function createDatabase(): Nette\Database\Connection { return new Nette\Database\Connection( - $this->paramètres['db.dsn'], - $this->paramètres['db.user'], - $this->paramètres['db.password'], + $this->parameters['db.dsn'], + $this->parameters['db.user'], + $this->parameters['db.password'], ); } @@ -70,24 +70,24 @@ $container = new Container([ ]); ``` -Les lecteurs avisés ont peut-être remarqué un problème. Chaque fois que j'obtiens un objet `UserController`, une nouvelle instance `ArticleFactory` et une base de données sont également créées. Nous ne voulons absolument pas de cela. +Les lecteurs attentifs ont peut-être remarqué un certain problème. Chaque fois que j'obtiens un objet `UserController`, une nouvelle instance de `ArticleFactory` et de la base de données est également créée. Ce n'est certainement pas ce que nous voulons. -Nous ajoutons donc une méthode `getService()` qui renverra les mêmes instances encore et encore : +Ajoutons donc une méthode `getService()` qui renverra toujours les mêmes instances : ```php -classe Container +class Container { - tableau privé $services = []; + private array $services = []; - fonction publique __construct( - tableau privé $paramètres, + public function __construct( + private array $parameters, ) { } public function getService(string $name): object { - si (!isset($this->services[$name])) { - // getService('Database') appelle createDatabase() + if (!isset($this->services[$name])) { + // getService('Database') appellera createDatabase() $method = 'create' . $name; $this->services[$name] = $this->$method(); } @@ -98,16 +98,16 @@ classe Container } ``` -Le premier appel à `$container->getService('Database')`, par exemple, fera en sorte que `createDatabase()` crée un objet de base de données, qu'il stockera dans le tableau `$services` et le renverra directement lors de l'appel suivant. +Lors du premier appel, par exemple `$container->getService('Database')`, il demandera à `createDatabase()` de créer l'objet de base de données, le stockera dans le tableau `$services` et le renverra directement lors du prochain appel. -Nous modifions également le reste du conteneur pour utiliser `getService()`: +Modifions également le reste du conteneur pour utiliser `getService()` : ```php -classe Container +class Container { // ... - Fonction publique createArticleFactory(): ArticleFactory + public function createArticleFactory(): ArticleFactory { return new ArticleFactory($this->getService('Database')); } @@ -119,9 +119,9 @@ classe Container } ``` -Au passage, le terme service désigne tout objet géré par le conteneur. D'où le nom de la méthode `getService()`. +Au fait, le terme service désigne tout objet géré par le conteneur. D'où le nom de la méthode `getService()`. -C'est fait. Nous avons un conteneur DI entièrement fonctionnel ! Et nous pouvons l'utiliser : +Terminé. Nous avons un conteneur DI entièrement fonctionnel ! Et nous pouvons l'utiliser : ```php $container = new Container([ @@ -134,6 +134,9 @@ $controller = $container->getService('UserController'); $database = $container->getService('Database'); ``` -Comme vous pouvez le voir, il n'est pas difficile d'écrire un DIC. Il est à noter que les objets eux-mêmes ne savent pas qu'un conteneur est en train de les créer. Ainsi, il est possible de créer n'importe quel objet en PHP de cette façon sans affecter leur code source. +Comme vous pouvez le voir, écrire un DIC n'est pas compliqué. Il convient de rappeler que les objets eux-mêmes ne savent pas qu'ils sont créés par un conteneur. Par conséquent, il est possible de créer ainsi n'importe quel objet en PHP sans interférer avec son code source. + +La création et la maintenance manuelles de la classe du conteneur peuvent rapidement devenir un cauchemar. Dans le chapitre suivant, nous parlerons donc du [Conteneur Nette DI |nette-container], qui peut se générer et se mettre à jour presque tout seul. + -Créer et maintenir manuellement une classe conteneur peut devenir un cauchemar assez rapidement. C'est pourquoi, dans le prochain chapitre, nous parlerons de [Nette DI Container |nette-container], qui peut se générer et se mettre à jour presque automatiquement. +{{maintitle: Qu'est-ce qu'un conteneur d'injection de dépendances ?}} diff --git a/dependency-injection/fr/extensions.texy b/dependency-injection/fr/extensions.texy index e54b2acff0..8feef95ca4 100644 --- a/dependency-injection/fr/extensions.texy +++ b/dependency-injection/fr/extensions.texy @@ -2,16 +2,16 @@ Création d'extensions pour Nette DI *********************************** .[perex] -La génération d'un conteneur DI, en plus des fichiers de configuration, affecte également ce que l'on appelle les *extensions*. Nous les activons dans le fichier de configuration dans la section `extensions`. +La génération du conteneur DI, en plus des fichiers de configuration, est également influencée par ce qu'on appelle des *extensions*. Nous les activons dans le fichier de configuration dans la section `extensions`. -C'est ainsi que nous ajoutons l'extension représentée par la classe `BlogExtension` avec le nom `blog`: +De cette manière, nous ajoutons l'extension représentée par la classe `BlogExtension` sous le nom `blog` : ```neon extensions: blog: BlogExtension ``` -Chaque extension de compilateur hérite de [api:Nette\DI\CompilerExtension] et peut implémenter les méthodes suivantes qui sont appelées pendant la compilation DI : +Chaque extension du compilateur hérite de [api:Nette\DI\CompilerExtension] et peut implémenter les méthodes suivantes, qui sont appelées séquentiellement lors de la construction du conteneur DI : 1. getConfigSchema() 2. loadConfiguration() @@ -22,18 +22,18 @@ Chaque extension de compilateur hérite de [api:Nette\DI\CompilerExtension] et p getConfigSchema() .[method] =========================== -Cette méthode est appelée en premier. Elle définit le schéma utilisé pour valider les paramètres de configuration. +Cette méthode est appelée en premier. Elle définit le schéma pour la validation des paramètres de configuration. -Les extensions sont configurées dans une section dont le nom est le même que celui sous lequel l'extension a été ajoutée, par exemple `blog`. +Nous configurons l'extension dans la section dont le nom est le même que celui sous lequel l'extension a été ajoutée, c'est-à-dire `blog` : ```neon -# même nom que mon extension +# même nom que l'extension blog: postsPerPage: 10 - comments: false + allowComments: false ``` -Nous allons définir un schéma décrivant toutes les options de configuration, y compris leurs types, les valeurs acceptées et éventuellement les valeurs par défaut : +Nous créons un schéma décrivant toutes les options de configuration, y compris leurs types, les valeurs autorisées et éventuellement les valeurs par défaut : ```php use Nette\Schema\Expect; @@ -50,9 +50,9 @@ class BlogExtension extends Nette\DI\CompilerExtension } ``` -Voir le [schéma |schema:] pour la documentation. En outre, vous pouvez spécifier quelles options peuvent être [dynamiques |application:bootstrap#Dynamic Parameters] en utilisant `dynamic()`, par exemple `Expect::int()->dynamic()`. +La documentation se trouve sur la page [Schéma |schema:]. De plus, il est possible de spécifier quelles options peuvent être [dynamiques |application:bootstrapping#Paramètres Dynamiques] à l'aide de `dynamic()`, par ex. `Expect::int()->dynamic()`. -On accède à la configuration par le biais de `$this->config`, qui est un objet `stdClass`: +Nous accédons à la configuration via la variable `$this->config`, qui est un objet `stdClass` : ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -71,7 +71,7 @@ class BlogExtension extends Nette\DI\CompilerExtension loadConfiguration() .[method] ============================= -Cette méthode est utilisée pour ajouter des services au conteneur. Ceci est fait par [api:Nette\DI\ContainerBuilder]: +Utilisé pour ajouter des services au conteneur. Pour cela, on utilise [api:Nette\DI\ContainerBuilder] : ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -86,19 +86,19 @@ class BlogExtension extends Nette\DI\CompilerExtension } ``` -La convention consiste à préfixer les services ajoutés par une extension avec son nom afin d'éviter tout conflit de noms. C'est ce que fait `prefix()`, donc si l'extension s'appelle "blog", le service s'appellera `blog.articles`. +La convention est de préfixer les services ajoutés par l'extension avec son nom pour éviter les conflits de noms. C'est ce que fait la méthode `prefix()`, donc si l'extension s'appelle `blog`, le service portera le nom `blog.articles`. -Si nous devons renommer un service, nous pouvons créer un alias avec son nom d'origine pour maintenir la compatibilité ascendante. C'est ce que Nette fait par exemple pour `routing.router`, qui est également disponible sous l'ancien nom `router`. +Si nous devons renommer un service, nous pouvons créer un alias avec le nom d'origine pour maintenir la compatibilité ascendante. Nette fait de même, par exemple, pour le service `routing.router`, qui est également disponible sous son ancien nom `router`. ```php $builder->addAlias('router', 'routing.router'); ``` -Récupérer les services d'un fichier .[#toc-retrieve-services-from-a-file] -------------------------------------------------------------------------- +Chargement des services depuis un fichier +----------------------------------------- -Nous pouvons créer des services à l'aide de l'API ContainerBuilder, mais nous pouvons également les ajouter via le fichier de configuration NEON bien connu et sa section `services`. Le préfixe `@extension` représente l'extension actuelle. +Nous n'avons pas besoin de créer des services uniquement à l'aide de l'API de la classe ContainerBuilder, mais aussi avec la notation familière utilisée dans le fichier de configuration NEON dans la section services. Le préfixe `@extension` représente l'extension actuelle. ```neon services: @@ -112,7 +112,7 @@ services: create: MyBlog\Components\ArticlesList(@extension.articles) ``` -Nous allons ajouter des services de cette manière : +Nous chargeons les services : ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -121,7 +121,7 @@ class BlogExtension extends Nette\DI\CompilerExtension { $builder = $this->getContainerBuilder(); - // charge le fichier de configuration de l'extension + // chargement du fichier de configuration pour l'extension $this->compiler->loadDefinitionsFromConfig( $this->loadFromFile(__DIR__ . '/blog.neon')['services'], ); @@ -133,7 +133,7 @@ class BlogExtension extends Nette\DI\CompilerExtension beforeCompile() .[method] ========================= -Cette méthode est appelée lorsque le conteneur contient tous les services ajoutés par les extensions individuelles dans les méthodes `loadConfiguration` ainsi que les fichiers de configuration de l'utilisateur. À cette phase d'assemblage, nous pouvons alors modifier les définitions des services ou ajouter des liens entre eux. Vous pouvez utiliser la méthode `findByTag()` pour rechercher des services par balises, ou la méthode `findByType()` pour rechercher par classe ou interface. +La méthode est appelée lorsque le conteneur contient tous les services ajoutés par les extensions individuelles dans les méthodes `loadConfiguration` ainsi que par les fichiers de configuration utilisateur. À ce stade de la construction, nous pouvons donc modifier les définitions de service ou ajouter des liens entre elles. Pour rechercher des services dans le conteneur par tags, on peut utiliser la méthode `findByTag()`, et par classe ou interface, la méthode `findByType()`. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -153,7 +153,7 @@ class BlogExtension extends Nette\DI\CompilerExtension afterCompile() .[method] ======================== -À ce stade, la classe conteneur est déjà générée en tant qu'objet [ClassType |php-generator:#classes], elle contient toutes les méthodes créées par le service et est prête à être mise en cache sous forme de fichier PHP. Nous pouvons encore modifier le code de la classe résultante à ce stade. +À ce stade, la classe du conteneur est déjà générée sous forme d'objet [ClassType |php-generator:#Classes], contient toutes les méthodes qui créent les services et est prête à être écrite dans le cache. Nous pouvons encore modifier le code résultant de la classe à ce moment. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -167,12 +167,12 @@ class BlogExtension extends Nette\DI\CompilerExtension ``` -$initialisation .[wiki-method] -============================== +$initialization .[method] +========================= -Le configurateur appelle le code d'initialisation après la [création du conteneur |application:bootstrap#index.php], qui est créé en écrivant dans un objet `$this->initialization` à l'aide de la [méthode addBody() |php-generator:#method-and-function-body]. +Après la [création du conteneur |application:bootstrapping#index.php], la classe Configurator appelle le code d'initialisation, qui est créé en écrivant dans l'objet `$this->initialization` à l'aide de la [méthode addBody() |php-generator:#Corps de méthodes et de fonctions]. -Nous allons montrer un exemple de la façon de démarrer une session ou de lancer des services qui ont la balise `run` en utilisant le code d'initialisation : +Montrons un exemple de comment démarrer la session ou lancer des services qui ont le tag `run` avec le code d'initialisation : ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -184,7 +184,7 @@ class BlogExtension extends Nette\DI\CompilerExtension $this->initialization->addBody('$this->getService("session")->start()'); } - // les services avec l'étiquette "run" doivent être créés après l'instanciation du conteneur. + // les services avec le tag run doivent être créés après l'instanciation du conteneur $builder = $this->getContainerBuilder(); foreach ($builder->findByTag('run') as $name => $foo) { $this->initialization->addBody('$this->getService(?);', [$name]); diff --git a/dependency-injection/fr/factory.texy b/dependency-injection/fr/factory.texy index a57c5fa1da..b8a0c3f249 100644 --- a/dependency-injection/fr/factory.texy +++ b/dependency-injection/fr/factory.texy @@ -1,12 +1,12 @@ -Usines générées -*************** +Factories générées +****************** .[perex] -Nette DI peut générer automatiquement du code d'usine basé sur l'interface, ce qui vous évite d'écrire du code. +Nette DI peut générer automatiquement le code des factories basé sur des interfaces, ce qui vous évite d'écrire du code. -Une fabrique est une classe qui crée et configure des objets. Elle leur transmet donc également leurs dépendances. Ne pas confondre avec le modèle de conception *méthode usine*, qui décrit une manière spécifique d'utiliser les usines et n'est pas lié à ce sujet. +Une factory est une classe qui produit et configure des objets. Elle leur transmet donc également leurs dépendances. Ne confondez pas, s'il vous plaît, avec le patron de conception *factory method*, qui décrit une manière spécifique d'utiliser les factories et n'est pas lié à ce sujet. -Nous avons montré à quoi ressemble une telle usine dans le [chapitre d'introduction |introduction#factory]: +Nous avons montré à quoi ressemble une telle factory dans le [chapitre d'introduction |introduction#Factory] : ```php class ArticleFactory @@ -23,7 +23,7 @@ class ArticleFactory } ``` -Nette DI peut générer du code de fabrique automatiquement. Tout ce que vous avez à faire est de créer une interface et Nette DI va générer une implémentation. L'interface doit avoir exactement une méthode nommée `create` et déclarer un type de retour : +Nette DI peut générer automatiquement le code des factories. Tout ce que vous avez à faire est de créer une interface et Nette DI générera l'implémentation. L'interface doit avoir exactement une méthode nommée `create` et déclarer un type de retour : ```php interface ArticleFactory @@ -32,7 +32,7 @@ interface ArticleFactory } ``` -Ainsi, la fabrique `ArticleFactory` a une méthode `create` qui crée des objets `Article`. La classe `Article` pourrait ressembler à ce qui suit, par exemple : +Ainsi, la factory `ArticleFactory` a une méthode `create` qui crée des objets `Article`. La classe `Article` peut ressembler à ceci : ```php class Article @@ -44,16 +44,16 @@ class Article } ``` -Ajoutez la fabrique au fichier de configuration : +Nous ajoutons la factory au fichier de configuration : ```neon services: - ArticleFactory ``` -Nette DI va générer l'implémentation de la fabrique correspondante. +Nette DI générera l'implémentation correspondante de la factory. -Ainsi, dans le code qui utilise la fabrique, nous demandons l'objet par interface et Nette DI utilise l'implémentation générée : +Dans le code qui utilise la factory, nous demandons donc l'objet par l'interface et Nette DI utilisera l'implémentation générée : ```php class UserController @@ -65,17 +65,17 @@ class UserController public function foo() { - // laissez la fabrique créer un objet + // laissons la factory créer l'objet $article = $this->articleFactory->create(); } } ``` -Usine paramétrée .[#toc-parameterized-factory] -============================================== +Factory paramétrée +================== -La méthode factory `create` peut accepter des paramètres qu'elle transmet ensuite au constructeur. Par exemple, ajoutons un ID d'auteur d'article à la classe `Article`: +La méthode de factory `create` peut accepter des paramètres, qu'elle transmet ensuite au constructeur. Ajoutons par exemple à la classe `Article` l'ID de l'auteur de l'article : ```php class Article @@ -88,7 +88,7 @@ class Article } ``` -Nous allons également ajouter le paramètre à la fabrique : +Nous ajoutons également le paramètre à la factory : ```php interface ArticleFactory @@ -97,13 +97,13 @@ interface ArticleFactory } ``` -Comme le paramètre dans le constructeur et le paramètre dans la fabrique ont le même nom, Nette DI les passera automatiquement. +Grâce au fait que le paramètre dans le constructeur et le paramètre dans la factory portent le même nom, Nette DI les transmet de manière entièrement automatique. -Définition avancée .[#toc-advanced-definition] -============================================== +Définition avancée +================== -La définition peut également être écrite sous forme de lignes multiples à l'aide de la touche `implement`: +La définition peut également être écrite sous forme multiligne en utilisant la clé `implement` : ```neon services: @@ -111,9 +111,9 @@ services: implement: ArticleFactory ``` -Lorsqu'on écrit de cette manière plus longue, il est possible de fournir des arguments supplémentaires pour le constructeur dans la clé `arguments` et une configuration supplémentaire en utilisant `setup`, comme pour les services normaux. +Lors de l'écriture de cette manière plus longue, il est possible de spécifier des arguments supplémentaires pour le constructeur dans la clé `arguments` et une configuration supplémentaire à l'aide de `setup`, tout comme pour les services normaux. -Exemple : si la méthode `create()` n'accepte pas le paramètre `$authorId`, nous pouvons spécifier une valeur fixe dans la configuration qui sera transmise au constructeur `Article`: +Exemple : si la méthode `create()` n'acceptait pas le paramètre `$authorId`, nous pourrions spécifier une valeur fixe dans la configuration, qui serait transmise au constructeur de `Article` : ```neon services: @@ -123,7 +123,7 @@ services: authorId: 123 ``` -Ou, à l'inverse, si `create()` acceptait le paramètre `$authorId` mais qu'il ne faisait pas partie du constructeur et était transmis par la méthode `Article::setAuthorId()`, nous y ferions référence dans la section `setup`: +Ou inversement, si `create()` acceptait le paramètre `$authorId`, mais qu'il ne faisait pas partie du constructeur et était transmis par la méthode `Article::setAuthorId()`, nous nous y référerions dans la section `setup` : ```neon services: @@ -134,15 +134,14 @@ services: ``` -Accesseur .[#toc-accessor] -========================== +Accessor +======== -Outre les fabriques, Nette peut également générer ce que l'on appelle des accesseurs. L'accesseur est un objet avec une méthode `get()` qui retourne un service particulier du conteneur DI. Les appels multiples à `get()` renverront toujours la même instance. +En plus des factories, Nette peut également générer ce qu'on appelle des accessors. Ce sont des objets avec une méthode `get()` qui renvoie un certain service du conteneur DI. Les appels répétés à `get()` renvoient toujours la même instance. -Les accesseurs apportent un chargement paresseux aux dépendances. Prenons l'exemple d'une classe qui enregistre les erreurs dans une base de données spéciale. Si la connexion à la base de données était transmise en tant que dépendance dans son constructeur, la connexion devrait toujours être créée, bien qu'elle ne soit utilisée que rarement lorsqu'une erreur apparaît, de sorte qu'elle resterait le plus souvent inutilisée. -Au lieu de cela, la classe peut passer un accesseur et lorsque sa méthode `get()` est appelée, c'est seulement à ce moment-là que l'objet base de données est créé : +Les accessors fournissent un chargement paresseux (lazy-loading) pour les dépendances. Supposons une classe qui écrit des erreurs dans une base de données spéciale. Si cette classe se faisait passer la connexion à la base de données comme dépendance par le constructeur, la connexion devrait toujours être créée, même si en pratique une erreur n'apparaît qu'exceptionnellement et donc la plupart du temps la connexion resterait inutilisée. Au lieu de cela, la classe se fait passer un accessor et ce n'est que lorsque son `get()` est appelé que l'objet de base de données est créé : -Comment créer un accesseur ? Ecrivez seulement une interface et Nette DI générera l'implémentation. L'interface doit avoir exactement une méthode appelée `get` et doit déclarer le type de retour : +Comment créer un accessor ? Il suffit d'écrire une interface et Nette DI générera l'implémentation. L'interface doit avoir exactement une méthode nommée `get` et déclarer un type de retour : ```php interface PDOAccessor @@ -151,7 +150,7 @@ interface PDOAccessor } ``` -Ajoutez l'accesseur au fichier de configuration avec la définition du service que l'accesseur retournera : +Nous ajoutons l'accessor au fichier de configuration, où se trouve également la définition du service qu'il renverra : ```neon services: @@ -159,62 +158,68 @@ services: - PDO(%dsn%, %user%, %password%) ``` -L'accesseur retourne un service de type `PDO` et comme il n'y a qu'un seul service de ce type dans la configuration, l'accesseur le retournera. Avec plusieurs services configurés de ce type, vous pouvez spécifier celui qui doit être retourné en utilisant son nom, par exemple `- PDOAccessor(@db1)`. +Comme l'accessor renvoie un service de type `PDO` et qu'il n'y a qu'un seul service de ce type dans la configuration, il renverra précisément celui-ci. S'il y avait plusieurs services de ce type, nous spécifierions le service renvoyé à l'aide de son nom, par ex. `- PDOAccessor(@db1)`. -Multifactory/Accesseur .[#toc-multifactory-accessor] -==================================================== -Jusqu'à présent, les fabriques et les accesseurs ne pouvaient créer ou renvoyer qu'un seul objet. Il est également possible de créer une classe multifactory combinée à un accesseur. L'interface d'une telle classe multifactory peut être constituée de plusieurs méthodes appelées `create<name>()` et `get<name>()`par exemple : +Factory/Accessor multiple +========================= +Nos factories et accessors ne pouvaient jusqu'à présent produire ou renvoyer qu'un seul objet. Mais il est très facile de créer également des factories multiples combinées avec des accessors. L'interface d'une telle classe contiendra un nombre quelconque de méthodes nommées `create<name>()` et `get<name>()`, par ex. : ```php interface MultiFactory { function createArticle(): Article; - function createFoo(): Model\Foo; function getDb(): PDO; } ``` -Au lieu de passer plusieurs fabriques et accesseurs générés, vous pouvez passer une seule multifactory complexe. +Ainsi, au lieu de nous passer plusieurs factories et accessors générés, nous passons une seule factory plus complexe qui en fait plus. -Vous pouvez également utiliser `create()` et `get()` avec un paramètre au lieu de plusieurs méthodes : +Alternativement, au lieu de plusieurs méthodes, on peut utiliser `get()` avec un paramètre : ```php interface MultiFactoryAlt { - function create($name); function get($name): PDO; } ``` -Dans ce cas, `MultiFactory::createArticle()` fait la même chose que `MultiFactoryAlt::create('article')`. Toutefois, cette syntaxe alternative présente quelques inconvénients. On ne sait pas clairement quelles valeurs de `$name` sont prises en charge et le type de retour ne peut pas être spécifié dans l'interface lorsqu'on utilise plusieurs valeurs différentes de `$name`. +Alors, `MultiFactory::getArticle()` fait la même chose que `MultiFactoryAlt::get('article')`. Cependant, la notation alternative a l'inconvénient qu'il n'est pas clair quelles valeurs de `$name` sont prises en charge et logiquement, il n'est pas non plus possible de distinguer différentes valeurs de retour pour différents `$name` dans l'interface. -Définition avec une liste .[#toc-definition-with-a-list] --------------------------------------------------------- -Comment définir un multifactory dans votre configuration ? Créons trois services qui seront retournés par le multifactory, et le multifactory lui-même : +Définition par liste +-------------------- +De cette manière, on peut définir une factory multiple dans la configuration : .{data-version:3.2.0} + +```neon +services: + - MultiFactory( + article: Article # définit createArticle() + db: PDO(%dsn%, %user%, %password%) # définit getDb() + ) +``` + +Ou nous pouvons nous référer à des services existants dans la définition de la factory à l'aide d'une référence : ```neon services: article: Article - - Model\Foo - PDO(%dsn%, %user%, %password%) - MultiFactory( - article: @article # createArticle() - foo: @Model\Foo # createFoo() - db: @\PDO # getDb() + article: @article # définit createArticle() + db: @\PDO # définit getDb() ) ``` -Définition avec balises .[#toc-definition-with-tags] ----------------------------------------------------- +Définition par tags +------------------- -Une autre option pour définir un multifactory est d'utiliser des [balises |services#Tags]: +La deuxième option consiste à utiliser des [tags |services#Tags] pour la définition : ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter - App\Model\DatabaseAccessor( db1: @database.db1.explorer ) diff --git a/dependency-injection/fr/faq.texy b/dependency-injection/fr/faq.texy index 6a16f99f8a..c65c685eab 100644 --- a/dependency-injection/fr/faq.texy +++ b/dependency-injection/fr/faq.texy @@ -1,98 +1,92 @@ -Questions fréquemment posées sur DI (FAQ) -***************************************** +Foire aux questions sur la DI (FAQ) +*********************************** -DI est-il un autre nom pour IoC ? .[#toc-is-di-another-name-for-ioc] --------------------------------------------------------------------- +DI est-il un autre nom pour IoC ? +--------------------------------- -*Inversion of Control* (IoC) est un principe axé sur la manière dont le code est exécuté - que votre code initie un code externe ou que votre code soit intégré dans un code externe, qui l'appelle ensuite. -L'inversion de contrôle est un concept large qui inclut les [événements |nette:glossary#Events], le [principe |application:components#Hollywood style] dit [d'Hollywood |application:components#Hollywood style] et d'autres aspects. -Les usines, qui font partie de la [règle n° 3 : laisser l'usine s'en charger |introduction#Rule #3: Let the Factory Handle It], et représentent l'inversion de l'opérateur `new`, sont également des composantes de ce concept. +L'*Inversion de Contrôle* (IoC) est un principe axé sur la manière dont le code est exécuté - si votre code exécute du code étranger ou si votre code est intégré dans du code étranger qui l'appelle ensuite. IoC est un terme large englobant les [événements |nette:glossary#Événements events], ce qu'on appelle le [principe d'Hollywood |application:components#Style Hollywood] et d'autres aspects. Les factories, dont parle la [Règle n°3 : laissez faire la factory |introduction#Règle n 3 : laissez faire la factory], font également partie de ce concept et représentent une inversion pour l'opérateur `new`. -*Dependency Injection* (DI) concerne la manière dont un objet connaît un autre objet, c'est-à-dire la dépendance. Il s'agit d'un modèle de conception qui exige le passage explicite des dépendances entre les objets. +L'*Injection de Dépendances* (DI) se concentre sur la manière dont un objet prend connaissance d'un autre objet, c'est-à-dire de ses dépendances. C'est un patron de conception qui exige le passage explicite des dépendances entre les objets. -On peut donc dire que l'injection de dépendances est une forme spécifique d'IoC. Cependant, toutes les formes de contrôle interne ne conviennent pas en termes de pureté du code. Par exemple, parmi les anti-modèles, nous incluons toutes les techniques qui travaillent avec l'[état global |global state] ou ce que l'on appelle le [Service Locator |#What is a Service Locator]. +On peut donc dire que la DI est une forme spécifique d'IoC. Cependant, toutes les formes d'IoC ne sont pas appropriées du point de vue de la propreté du code. Par exemple, parmi les anti-patrons figurent les techniques qui travaillent avec l'[état global |global-state] ou ce qu'on appelle le [Service Locator |#Qu est-ce que le Service Locator]. -Qu'est-ce qu'un Service Locator ? .[#toc-what-is-a-service-locator] -------------------------------------------------------------------- +Qu'est-ce que le Service Locator ? +---------------------------------- -Un localisateur de services est une alternative à l'injection de dépendances. Il fonctionne en créant un stockage central où tous les services ou dépendances disponibles sont enregistrés. Lorsqu'un objet a besoin d'une dépendance, il la demande au localisateur de services. +C'est une alternative à l'Injection de Dépendances. Il fonctionne en créant un dépôt central où tous les services ou dépendances disponibles sont enregistrés. Lorsqu'un objet a besoin d'une dépendance, il la demande au Service Locator. -Cependant, par rapport à l'injection de dépendances, elle perd en transparence : les dépendances ne sont pas directement transmises aux objets et ne sont donc pas facilement identifiables, ce qui nécessite d'examiner le code pour découvrir et comprendre toutes les connexions. Les tests sont également plus compliqués, car nous ne pouvons pas simplement passer des objets fictifs aux objets testés, mais nous devons passer par le Service Locator. En outre, le Service Locator perturbe la conception du code, car les objets individuels doivent être conscients de son existence, ce qui diffère de l'injection de dépendances, où les objets n'ont aucune connaissance du conteneur DI. +Cependant, par rapport à l'Injection de Dépendances, il perd en transparence : les dépendances ne sont pas passées directement aux objets et ne sont donc pas facilement identifiables, ce qui nécessite d'examiner le code pour découvrir et comprendre toutes les liaisons. Les tests sont également plus complexes, car nous ne pouvons pas simplement passer des objets mock aux objets testés, mais nous devons passer par le Service Locator. De plus, le Service Locator perturbe la conception du code, car les objets individuels doivent connaître son existence, ce qui diffère de l'Injection de Dépendances, où les objets n'ont pas connaissance du conteneur DI. -Quand est-il préférable de ne pas utiliser l'injection de dépendance ? .[#toc-when-is-it-better-not-to-use-di] --------------------------------------------------------------------------------------------------------------- +Quand est-il préférable de ne pas utiliser la DI ? +-------------------------------------------------- -Il n'y a pas de difficultés connues liées à l'utilisation du modèle de conception de l'injection de dépendances. Au contraire, l'obtention de dépendances à partir d'emplacements accessibles globalement entraîne un [certain nombre de complications |global-state], tout comme l'utilisation d'un localisateur de services. -Il est donc conseillé de toujours utiliser l'injection de dépendances. Il ne s'agit pas d'une approche dogmatique, mais simplement de l'absence d'une meilleure alternative. +Aucune difficulté connue n'est associée à l'utilisation du patron de conception Injection de Dépendances. Au contraire, l'obtention de dépendances à partir d'emplacements globalement disponibles entraîne [toute une série de complications |global-state], tout comme l'utilisation du Service Locator. Il est donc conseillé d'utiliser toujours la DI. Ce n'est pas une approche dogmatique, mais simplement aucune meilleure alternative n'a été trouvée. -Cependant, dans certaines situations, il n'est pas nécessaire de se transmettre des objets et de les obtenir dans l'espace global. Par exemple, lors du débogage d'un code, il est nécessaire de vidanger la valeur d'une variable à un moment précis du programme, de mesurer la durée d'une certaine partie du programme ou d'enregistrer un message. -Dans de tels cas, lorsqu'il s'agit d'actions temporaires qui seront ultérieurement supprimées du code, il est légitime d'utiliser un dumper, un chronomètre ou un logger accessible globalement. Ces outils, après tout, ne font pas partie de la conception du code. +Néanmoins, il existe certaines situations où nous ne passons pas d'objets et les obtenons depuis l'espace global. Par exemple, lors du débogage de code, lorsque vous devez afficher la valeur d'une variable à un point spécifique du programme, mesurer la durée d'une certaine partie du programme ou enregistrer un message. Dans de tels cas, lorsqu'il s'agit d'actions temporaires qui seront ultérieurement supprimées du code, il est légitime d'utiliser un dumper, un chronomètre ou un logger globalement disponible. Ces outils ne font en effet pas partie de la conception du code. -L'utilisation de l'ID présente-t-elle des inconvénients ? .[#toc-does-using-di-have-its-drawbacks] --------------------------------------------------------------------------------------------------- +L'utilisation de la DI a-t-elle des inconvénients ? +--------------------------------------------------- -L'utilisation de l'injection de dépendances présente-t-elle des inconvénients, tels qu'une complexité accrue de l'écriture du code ou une baisse des performances ? Que perdons-nous lorsque nous commençons à écrire du code conformément à l'injection de dépendances ? +L'utilisation de l'Injection de Dépendances entraîne-t-elle des inconvénients, tels qu'une complexité accrue de l'écriture du code ou une dégradation des performances ? Que perdons-nous lorsque nous commençons à écrire du code conformément à la DI ? -L'injection de dépendances n'a aucun impact sur les performances de l'application ou sur les besoins en mémoire. La performance du conteneur DI peut jouer un rôle, mais dans le cas de [Nette DI | nette-container], le conteneur est compilé en PHP pur, de sorte que sa surcharge pendant l'exécution de l'application est pratiquement nulle. +La DI n'a pas d'impact sur les performances ou l'utilisation de la mémoire de l'application. Les performances du conteneur DI peuvent jouer un certain rôle, mais dans le cas de [Nette DI |nette-container], le conteneur est compilé en PHP pur, de sorte que sa surcharge lors de l'exécution de l'application est pratiquement nulle. -Lors de l'écriture du code, il est nécessaire de créer des constructeurs qui acceptent les dépendances. Dans le passé, cela pouvait prendre du temps, mais grâce aux IDE modernes et à la [promotion des propriétés des constructeurs |https://blog.nette.org/fr/php-8-0-apercu-complet-des-nouveautes#toc-constructor-property-promotion], c'est maintenant une question de quelques secondes. Les usines peuvent être facilement générées à l'aide de Nette DI et d'un plugin PhpStorm en quelques clics. -D'autre part, il n'est pas nécessaire d'écrire des singletons et des points d'accès statiques. +Lors de l'écriture du code, il est parfois nécessaire de créer des constructeurs acceptant des dépendances. Auparavant, cela pouvait être fastidieux, mais grâce aux IDE modernes et à la [promotion des propriétés du constructeur |https://blog.nette.org/fr/php-8-0-complete-overview-of-news#toc-constructor-property-promotion], c'est maintenant une question de quelques secondes. Les factories peuvent être facilement générées à l'aide de Nette DI et du plugin pour PhpStorm en un clic de souris. D'un autre côté, il n'est plus nécessaire d'écrire des singletons et des points d'accès statiques. -On peut conclure qu'une application bien conçue utilisant DI n'est ni plus courte ni plus longue qu'une application utilisant des singletons. Les parties du code travaillant avec les dépendances sont simplement extraites des classes individuelles et déplacées vers de nouveaux emplacements, c'est-à-dire le conteneur DI et les usines. +On peut affirmer qu'une application correctement conçue utilisant la DI n'est ni plus courte ni plus longue qu'une application utilisant des singletons. Les parties du code travaillant avec des dépendances sont simplement extraites des classes individuelles et déplacées vers de nouveaux emplacements, c'est-à-dire dans le conteneur DI et les factories. -Comment réécrire une application existante en DI ? .[#toc-how-to-rewrite-a-legacy-application-to-di] ----------------------------------------------------------------------------------------------------- +Comment réécrire une application legacy en DI ? +----------------------------------------------- -La migration d'une application existante vers l'injection de dépendances peut être un processus difficile, en particulier pour les applications complexes et de grande taille. Il est important d'aborder ce processus de manière systématique. +La transition d'une application legacy vers l'Injection de Dépendances peut être un processus exigeant, en particulier pour les applications volumineuses et complexes. Il est important d'aborder ce processus de manière systématique. -- Lors du passage à l'injection de dépendances, il est important que tous les membres de l'équipe comprennent les principes et les pratiques utilisés. -- Tout d'abord, effectuez une analyse de l'application existante afin d'identifier les composants clés et leurs dépendances. Créez un plan pour savoir quelles parties seront remaniées et dans quel ordre. -- Implémenter un conteneur DI ou, mieux encore, utiliser une bibliothèque existante telle que Nette DI. -- Refondre progressivement chaque partie de l'application pour utiliser l'injection de dépendances. Cela peut impliquer de modifier les constructeurs ou les méthodes pour qu'ils acceptent les dépendances en tant que paramètres. -- Modifier les endroits du code où les objets de dépendance sont créés afin que les dépendances soient injectées par le conteneur. Cela peut inclure l'utilisation d'usines. +- Lors de la transition vers l'Injection de Dépendances, il est important que tous les membres de l'équipe comprennent les principes et les procédures utilisés. +- Commencez par analyser l'application existante et identifier les composants clés et leurs dépendances. Créez un plan indiquant quelles parties seront refactorisées et dans quel ordre. +- Implémentez un conteneur DI ou, mieux encore, utilisez une bibliothèque existante, telle que Nette DI. +- Refactorisez progressivement les différentes parties de l'application pour utiliser l'Injection de Dépendances. Cela peut inclure la modification des constructeurs ou des méthodes pour accepter les dépendances comme paramètres. +- Modifiez les endroits du code où les objets avec des dépendances sont créés, afin que les dépendances soient injectées par le conteneur à la place. Cela peut inclure l'utilisation de factories. -N'oubliez pas que le passage à l'injection de dépendances est un investissement dans la qualité du code et la viabilité à long terme de l'application. Bien qu'il puisse être difficile d'effectuer ces changements, le résultat devrait être un code plus propre, plus modulaire et plus facilement testable, prêt pour les extensions et la maintenance futures. +N'oubliez pas que la transition vers l'Injection de Dépendances est un investissement dans la qualité du code et la maintenabilité à long terme de l'application. Bien qu'il puisse être difficile d'apporter ces changements, le résultat devrait être un code plus propre, plus modulaire et facilement testable, prêt pour les extensions et la maintenance futures. -Pourquoi la composition est-elle préférable à l'héritage ? .[#toc-why-composition-is-preferred-over-inheritance] ----------------------------------------------------------------------------------------------------------------- -Il est préférable d'utiliser la composition plutôt que l'héritage car elle permet de réutiliser le code sans avoir à s'inquiéter de l'effet de ruissellement des changements. Elle permet donc un couplage plus lâche, sans avoir à se soucier du fait que la modification d'un code entraîne la modification d'un autre code dépendant. Un exemple typique est la situation identifiée comme l'[enfer des constructeurs |passing-dependencies#Constructor hell]. +Pourquoi la composition est-elle préférée à l'héritage ? +-------------------------------------------------------- +Il est préférable d'utiliser la [composition |nette:introduction-to-object-oriented-programming#Composition] plutôt que l'[héritage |nette:introduction-to-object-oriented-programming#Héritage], car elle sert à réutiliser le code sans avoir à se soucier des conséquences des changements. Elle offre donc un couplage plus lâche, où nous n'avons pas à craindre que la modification d'un code n'entraîne la nécessité de modifier un autre code dépendant. Un exemple typique est la situation appelée [enfer du constructeur |passing-dependencies#Constructor hell]. -Nette DI Container peut-il être utilisé en dehors de Nette ? .[#toc-can-nette-di-container-be-used-outside-of-nette] --------------------------------------------------------------------------------------------------------------------- +Peut-on utiliser le conteneur Nette DI en dehors de Nette ? +----------------------------------------------------------- -Absolument. Le Nette DI Container fait partie de Nette, mais il est conçu comme une bibliothèque autonome qui peut être utilisée indépendamment des autres parties du framework. Il suffit de l'installer à l'aide de Composer, de créer un fichier de configuration définissant vos services, puis d'utiliser quelques lignes de code PHP pour créer le conteneur DI. -Et vous pouvez immédiatement commencer à tirer parti de l'injection de dépendances dans vos projets. +Absolument. Le conteneur Nette DI fait partie de Nette, mais il est conçu comme une bibliothèque autonome qui peut être utilisée indépendamment des autres parties du framework. Il suffit de l'installer à l'aide de Composer, de créer un fichier de configuration avec la définition de vos services, puis d'utiliser quelques lignes de code PHP pour créer le conteneur DI. Et vous pouvez immédiatement commencer à profiter des avantages de l'Injection de Dépendances dans vos projets. -Le chapitre sur le [conteneur DI de Nette |nette-container] décrit un cas d'utilisation spécifique, y compris le code. +L'utilisation concrète, y compris les codes, est décrite dans le chapitre [Conteneur Nette DI |nette-container]. -Pourquoi la configuration se fait-elle dans des fichiers NEON ? .[#toc-why-is-the-configuration-in-neon-files] --------------------------------------------------------------------------------------------------------------- +Pourquoi la configuration est-elle dans des fichiers NEON ? +----------------------------------------------------------- -NEON est un langage de configuration simple et facilement lisible développé au sein de Nette pour configurer les applications, les services et leurs dépendances. Comparé à JSON ou YAML, il offre des options beaucoup plus intuitives et flexibles à cet effet. En NEON, vous pouvez naturellement décrire des bindings qu'il ne serait pas possible d'écrire en Symfony & YAML, soit du tout, soit seulement au travers d'une description complexe. +NEON est un langage de configuration simple et facile à lire, développé dans le cadre de Nette pour configurer les applications, les services et leurs dépendances. Par rapport à JSON ou YAML, il offre des possibilités beaucoup plus intuitives et flexibles à cet effet. En NEON, on peut décrire naturellement des liaisons qui seraient impossibles à écrire en Symfony & YAML, ou seulement au moyen d'une description complexe. -L'analyse des fichiers NEON ralentit-elle l'application ? .[#toc-does-parsing-neon-files-slow-down-the-application] -------------------------------------------------------------------------------------------------------------------- +L'analyse des fichiers NEON ralentit-elle l'application ? +--------------------------------------------------------- -Bien que les fichiers NEON soient analysés très rapidement, cet aspect n'a pas vraiment d'importance. En effet, l'analyse des fichiers n'a lieu qu'une seule fois lors du premier lancement de l'application. Ensuite, le code du conteneur DI est généré, stocké sur le disque et exécuté pour chaque demande ultérieure sans qu'il soit nécessaire de procéder à une nouvelle analyse. +Bien que les fichiers NEON soient analysés très rapidement, cet aspect n'a aucune importance. La raison en est que l'analyse des fichiers n'a lieu qu'une seule fois lors du premier lancement de l'application. Ensuite, le code du conteneur DI est généré, enregistré sur le disque et exécuté à chaque requête ultérieure, sans qu'il soit nécessaire d'effectuer d'autres analyses. -C'est ainsi que cela fonctionne dans un environnement de production. Pendant le développement, les fichiers NEON sont analysés chaque fois que leur contenu est modifié, ce qui garantit que le développeur dispose toujours d'un conteneur DI à jour. Comme nous l'avons déjà mentionné, l'analyse proprement dite se fait en un instant. +C'est ainsi que cela fonctionne dans un environnement de production. Pendant le développement, les fichiers NEON sont analysés chaque fois que leur contenu est modifié, afin que le développeur dispose toujours d'un conteneur DI à jour. L'analyse elle-même est, comme mentionné, une question d'instant. -Comment puis-je accéder aux paramètres du fichier de configuration dans ma classe ? .[#toc-how-do-i-access-the-parameters-from-the-configuration-file-in-my-class] ------------------------------------------------------------------------------------------------------------------------------------------------------------------- +Comment accéder aux paramètres du fichier de configuration depuis ma classe ? +----------------------------------------------------------------------------- -Gardez à l'esprit la [Règle n° 1 : Laissez-le vous être transmis |introduction#Rule #1: Let It Be Passed to You]. Si une classe nécessite des informations provenant d'un fichier de configuration, il n'est pas nécessaire de déterminer comment accéder à ces informations ; au lieu de cela, nous les demandons simplement - par exemple, par l'intermédiaire du constructeur de la classe. Et nous effectuons le transfert dans le fichier de configuration. +Gardons à l'esprit la [Règle n°1 : faites-vous le passer |introduction#Règle n 1 : faites-vous passer les choses]. Si une classe nécessite des informations du fichier de configuration, nous n'avons pas besoin de réfléchir à la manière d'accéder à ces informations, nous les demandons simplement - par exemple, via le constructeur de la classe. Et nous effectuons le passage dans le fichier de configuration. -Dans cet exemple, `%myParameter%` est un espace réservé pour la valeur du paramètre `myParameter`, qui sera transmis au constructeur `MyClass`: +Dans cet exemple, `%myParameter%` est un placeholder pour la valeur du paramètre `myParameter`, qui est passée au constructeur de la classe `MyClass` : ```php # config.neon @@ -103,10 +97,10 @@ services: - MyClass(%myParameter%) ``` -Si vous souhaitez passer plusieurs paramètres ou utiliser le câblage automatique, il est utile d'[envelopper les paramètres dans un objet |best-practices:passing-settings-to-presenters]. +Si vous souhaitez passer plusieurs paramètres ou utiliser l'autowiring, il est conseillé d'[encapsuler les paramètres dans un objet |best-practices:passing-settings-to-presenters]. -Nette prend-il en charge l'interface PSR-11 Container ? .[#toc-does-nette-support-psr-11-container-interface] -------------------------------------------------------------------------------------------------------------- +Nette supporte-t-il PSR-11 : Container interface ? +-------------------------------------------------- -Nette DI Container ne supporte pas directement PSR-11. Cependant, si vous avez besoin d'interopérabilité entre le Nette DI Container et des bibliothèques ou des frameworks qui attendent l'interface de conteneur PSR-11, vous pouvez créer un [simple adaptateur |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f] pour servir de pont entre le Nette DI Container et PSR-11. +Le conteneur Nette DI ne prend pas en charge PSR-11 directement. Cependant, si vous avez besoin d'interopérabilité entre le conteneur Nette DI et des bibliothèques ou des frameworks qui attendent une interface de conteneur PSR-11, vous pouvez créer un [simple adaptateur |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f] qui servira de pont entre le conteneur Nette DI et PSR-11. diff --git a/dependency-injection/fr/global-state.texy b/dependency-injection/fr/global-state.texy index e55a85076d..dc60e290a1 100644 --- a/dependency-injection/fr/global-state.texy +++ b/dependency-injection/fr/global-state.texy @@ -2,43 +2,42 @@ ************************* .[perex] -Avertissement : Les constructions suivantes sont des symptômes d'un code mal conçu : +Avertissement : Les constructions suivantes sont le signe d'un code mal conçu : - `Foo::getInstance()` - `DB::insert(...)` - `Article::setDb($db)` - `ClassName::$var` ou `static::$var` -Rencontrez-vous l'une de ces constructions dans votre code ? Si c'est le cas, vous avez la possibilité de l'améliorer. Vous pouvez penser qu'il s'agit de constructions courantes, souvent observées dans des exemples de solutions de diverses bibliothèques et frameworks. Si c'est le cas, la conception de leur code est défectueuse. +Certaines de ces constructions apparaissent-elles dans votre code ? Alors vous avez l'occasion de l'améliorer. Vous pensez peut-être qu'il s'agit de constructions courantes que vous voyez même dans les exemples de solutions de diverses bibliothèques et frameworks. Si c'est le cas, alors la conception de leur code n'est pas bonne. -Nous ne parlons pas ici de pureté académique. Toutes ces constructions ont une chose en commun : elles utilisent un état global. Et cela a un impact destructeur sur la qualité du code. Les classes sont trompeuses quant à leurs dépendances. Le code devient imprévisible. Cela perturbe les développeurs et réduit leur efficacité. +Nous ne parlons certainement pas ici d'une sorte de pureté académique. Toutes ces constructions ont une chose en commun : elles utilisent l'état global. Et celui-ci a un impact destructeur sur la qualité du code. Les classes mentent sur leurs dépendances. Le code devient imprévisible. Il embrouille les programmeurs et réduit leur efficacité. -Dans ce chapitre, nous expliquerons pourquoi c'est le cas et comment éviter l'état global. +Dans ce chapitre, nous expliquerons pourquoi il en est ainsi et comment éviter l'état global. -Interconnexion globale .[#toc-global-interlinking] --------------------------------------------------- +Couplage global +--------------- -Dans un monde idéal, un objet ne devrait communiquer qu'avec les objets qui lui ont été [directement transmis |passing-dependencies]. Si je crée deux objets `A` et `B` et que je ne leur passe jamais de référence, ni `A` ni `B` ne peuvent accéder à l'état de l'autre ou le modifier. Il s'agit d'une propriété hautement souhaitable du code. C'est un peu comme si vous aviez une batterie et une ampoule ; l'ampoule ne s'allumera pas tant que vous ne l'aurez pas reliée à la batterie par un fil. +Dans un monde idéal, un objet ne devrait pouvoir communiquer qu'avec les objets qui lui ont été [directement passés |passing-dependencies]. Si je crée deux objets `A` et `B` et que je ne passe jamais de référence entre eux, alors ni `A` ni `B` ne peuvent accéder à l'autre objet ou modifier son état. C'est une propriété très souhaitable du code. C'est similaire à avoir une batterie et une ampoule ; l'ampoule ne s'allumera pas tant que vous ne la connecterez pas à la batterie avec un fil. -Cependant, cela n'est pas vrai pour les variables globales (statiques) ou les singletons. L'objet `A` pourrait accéder sans fil à l'objet `C` et le modifier sans aucun passage de référence, en appelant `C::changeSomething()`. Si l'objet `B` accède également à la variable globale `C`, alors `A` et `B` peuvent s'influencer mutuellement par l'intermédiaire de `C`. +Mais cela ne s'applique pas aux variables globales (statiques) ou aux singletons. L'objet `A` pourrait accéder *sans fil* à l'objet `C` et le modifier sans aucun passage de référence, en appelant `C::changeSomething()`. Si l'objet `B` s'empare également du `C` global, alors `A` et `B` peuvent s'influencer mutuellement via `C`. -L'utilisation de variables globales introduit une nouvelle forme de couplage "sans fil" qui n'est pas visible de l'extérieur. Cela crée un écran de fumée qui complique la compréhension et l'utilisation du code. Pour vraiment comprendre les dépendances, les développeurs doivent lire chaque ligne du code source, au lieu de se contenter de se familiariser avec les interfaces des classes. De plus, cet enchevêtrement n'est absolument pas nécessaire. L'état global est utilisé parce qu'il est facilement accessible de partout et permet, par exemple, d'écrire dans une base de données par le biais d'une méthode globale (statique) `DB::insert()`. Cependant, comme nous le verrons, l'avantage qu'il offre est minime, tandis que les complications qu'il introduit sont graves. +L'utilisation de variables globales introduit dans le système une nouvelle forme de couplage *sans fil*, qui n'est pas visible de l'extérieur. Elle crée un écran de fumée compliquant la compréhension et l'utilisation du code. Pour que les développeurs comprennent réellement les dépendances, ils doivent lire chaque ligne du code source. Au lieu de simplement se familiariser avec l'interface des classes. De plus, il s'agit d'un couplage totalement inutile. L'état global est utilisé parce qu'il est facilement accessible de n'importe où et permet, par exemple, d'écrire dans la base de données via la méthode globale (statique) `DB::insert()`. Mais comme nous le montrerons, l'avantage que cela apporte est minime, tandis que les complications qu'il provoque sont fatales. .[note] -En termes de comportement, il n'y a pas de différence entre une variable globale et une variable statique. Elles sont tout aussi nuisibles l'une que l'autre. +Du point de vue du comportement, il n'y a pas de différence entre une variable globale et une variable statique. Elles sont tout aussi nuisibles. -L'action sinistre à distance .[#toc-the-spooky-action-at-a-distance] --------------------------------------------------------------------- +Action fantôme à distance +------------------------- -"L'action sinistre à distance" : c'est ainsi qu'Albert Einstein a appelé un phénomène de la physique quantique qui lui a donné la chair de poule en 1935. -Il s'agit de l'intrication quantique, dont la particularité est que lorsque vous mesurez une information sur une particule, vous affectez immédiatement une autre particule, même si elles sont distantes de millions d'années-lumière. -Ce qui semble violer la loi fondamentale de l'univers selon laquelle rien ne peut voyager plus vite que la lumière. +"Action fantôme à distance" - c'est ainsi qu'Albert Einstein a fameusement nommé en 1935 un phénomène de la physique quantique qui lui donnait la chair de poule. +Il s'agit de l'intrication quantique, dont la particularité est que lorsque vous mesurez une information sur une particule, vous influencez instantanément l'autre particule, même si elles sont séparées par des millions d'années-lumière. Ce qui semble violer la loi fondamentale de l'univers selon laquelle rien ne peut se propager plus vite que la lumière. -Dans le monde des logiciels, nous pouvons parler d'une "action étrange à distance", une situation dans laquelle nous exécutons un processus que nous pensons isolé (parce que nous ne lui avons transmis aucune référence), mais des interactions inattendues et des changements d'état se produisent dans des endroits éloignés du système dont nous n'avons pas parlé à l'objet. Cela ne peut se produire qu'à travers l'état global. +Dans le monde logiciel, nous pouvons appeler "action fantôme à distance" une situation où nous lançons un processus que nous croyons isolé (parce que nous ne lui avons passé aucune référence), mais où des interactions et des changements d'état inattendus se produisent dans des endroits éloignés du système, dont nous n'avions aucune idée. Cela ne peut se produire que par le biais de l'état global. -Imaginez que vous rejoignez une équipe de développement de projet qui dispose d'une base de code importante et mature. Votre nouveau chef vous demande d'implémenter une nouvelle fonctionnalité et, comme tout bon développeur, vous commencez par écrire un test. Mais comme vous êtes nouveau dans le projet, vous faites beaucoup de tests exploratoires du type "que se passe-t-il si j'appelle cette méthode". Et vous essayez d'écrire le test suivant : +Imaginez que vous rejoigniez une équipe de développeurs sur un projet doté d'une base de code vaste et mature. Votre nouveau responsable vous demande d'implémenter une nouvelle fonctionnalité et, en tant que bon développeur, vous commencez par écrire un test. Mais comme vous êtes nouveau dans le projet, vous effectuez de nombreux tests exploratoires du type "que se passe-t-il si j'appelle cette méthode". Et vous essayez d'écrire le test suivant : ```php function testCreditCardCharge() @@ -48,20 +47,17 @@ function testCreditCardCharge() } ``` -Vous exécutez le code, peut-être plusieurs fois, et après un certain temps, vous remarquez sur votre téléphone des notifications de la banque indiquant qu'à chaque fois que vous l'exécutez, 100 $ ont été débités sur votre carte de crédit 🤦‍♂️. +Vous exécutez le code, peut-être plusieurs fois, et après un certain temps, vous remarquez des notifications de votre banque sur votre mobile indiquant qu'à chaque exécution, 100 dollars ont été débités de votre carte de crédit 🤦‍♂️ -Comment diable le test a-t-il pu provoquer un débit réel ? Il n'est pas facile d'opérer avec une carte de crédit. Vous devez interagir avec un service web tiers, vous devez connaître l'URL de ce service web, vous devez vous connecter, et ainsi de suite. -Aucune de ces informations n'est incluse dans le test. Pire encore, vous ne savez même pas où ces informations sont présentes, et donc comment simuler les dépendances externes pour que chaque exécution n'entraîne pas une nouvelle facturation de 100 dollars. Et en tant que nouveau développeur, comment étiez-vous censé savoir que ce que vous étiez sur le point de faire vous ferait perdre 100 dollars ? +Comment diable le test a-t-il pu provoquer un débit réel d'argent ? Opérer avec une carte de crédit n'est pas facile. Vous devez communiquer avec un service web tiers, vous devez connaître l'URL de ce service web, vous devez vous connecter, etc. Aucune de ces informations n'est contenue dans le test. Pire encore, vous ne savez même pas où ces informations sont présentes, et donc vous ne savez pas non plus comment mocker les dépendances externes pour que chaque exécution n'entraîne pas à nouveau le débit de 100 dollars. Et comment, en tant que nouveau développeur, auriez-vous pu savoir que ce que vous alliez faire vous rendrait 100 dollars plus pauvre ? -C'est une action effrayante à distance ! +C'est l'action fantôme à distance ! -Vous n'avez pas d'autre choix que de fouiller dans une grande quantité de code source, en demandant à des collègues plus anciens et plus expérimentés, jusqu'à ce que vous compreniez comment fonctionnent les connexions dans le projet. -Cela est dû au fait qu'en regardant l'interface de la classe `CreditCard`, vous ne pouvez pas déterminer l'état global qui doit être initialisé. Même en regardant le code source de la classe, vous ne pourrez pas savoir quelle méthode d'initialisation appeler. Au mieux, vous pouvez trouver la variable globale à laquelle on accède et essayer de deviner comment l'initialiser à partir de là. +Il ne vous reste plus qu'à fouiller longuement dans de nombreux codes sources, à interroger des collègues plus âgés et plus expérimentés, avant de comprendre comment fonctionnent les liens dans le projet. Cela est dû au fait qu'en regardant l'interface de la classe `CreditCard`, on ne peut pas déterminer l'état global qu'il faut initialiser. Même un coup d'œil au code source de la classe ne vous dira pas quelle méthode d'initialisation appeler. Au mieux, vous pouvez trouver une variable globale à laquelle on accède et essayer d'en déduire comment l'initialiser. -Les classes d'un tel projet sont des menteurs pathologiques. La carte de paiement prétend que vous pouvez simplement l'instancier et appeler la méthode `charge()`. Cependant, elle interagit secrètement avec une autre classe, `PaymentGateway`. Même son interface indique qu'elle peut être initialisée de manière indépendante, mais en réalité, elle tire les informations d'identification d'un fichier de configuration et ainsi de suite. -Il est clair pour les développeurs qui ont écrit ce code que `CreditCard` a besoin de `PaymentGateway`. Ils ont écrit le code de cette façon. Mais pour toute personne nouvelle dans le projet, c'est un mystère complet et cela entrave l'apprentissage. +Les classes d'un tel projet sont des menteurs pathologiques. La carte de crédit prétend qu'il suffit de l'instancier et d'appeler la méthode `charge()`. En secret, elle coopère avec une autre classe `PaymentGateway`, qui représente la passerelle de paiement. Son interface dit également qu'elle peut être initialisée séparément, mais en réalité, elle extrait les informations d'identification d'un fichier de configuration, etc. Pour les développeurs qui ont écrit ce code, il est clair que `CreditCard` a besoin de `PaymentGateway`. Ils ont écrit le code de cette manière. Mais pour quiconque est nouveau dans le projet, c'est un mystère complet et cela entrave l'apprentissage. -Comment remédier à cette situation ? Facile. **Laissez l'API déclarer les dépendances.** +Comment corriger la situation ? Facilement. **Laissez l'API déclarer les dépendances.** ```php function testCreditCardCharge() @@ -72,36 +68,35 @@ function testCreditCardCharge() } ``` -Remarquez comment les relations au sein du code deviennent soudainement évidentes. En déclarant que la méthode `charge()` a besoin de `PaymentGateway`, vous n'avez pas besoin de demander à qui que ce soit comment le code est interdépendant. Vous savez que vous devez créer une instance de cette méthode, et lorsque vous essayez de le faire, vous vous heurtez au fait que vous devez fournir des paramètres d'accès. Sans eux, le code ne fonctionnerait même pas. +Remarquez comment les interconnexions à l'intérieur du code deviennent soudainement évidentes. Le fait que la méthode `charge()` déclare avoir besoin de `PaymentGateway` signifie que vous n'avez pas besoin de demander à qui que ce soit comment le code est interconnecté. Vous savez que vous devez créer son instance, et lorsque vous essayez de le faire, vous réalisez que vous devez fournir les paramètres d'accès. Sans eux, le code ne pourrait même pas s'exécuter. -Et surtout, vous pouvez maintenant simuler la passerelle de paiement afin de ne pas être facturé 100 $ à chaque fois que vous exécutez un test. +Et surtout, vous pouvez maintenant mocker la passerelle de paiement, de sorte que 100 dollars ne vous seront pas facturés à chaque exécution du test. -L'état global permet à vos objets d'accéder secrètement à des éléments qui ne sont pas déclarés dans leurs API et, par conséquent, fait de vos API des menteurs pathologiques. +L'état global fait que vos objets peuvent accéder secrètement à des choses qui ne sont pas déclarées dans leur API et, par conséquent, font de vos API des menteurs pathologiques. -Vous n'y avez peut-être jamais pensé de cette façon, mais chaque fois que vous utilisez l'état global, vous créez des canaux de communication sans fil secrets. Les actions à distance effrayantes obligent les développeurs à lire chaque ligne de code pour comprendre les interactions potentielles, réduisent la productivité des développeurs et désorientent les nouveaux membres de l'équipe. -Si vous êtes celui qui a créé le code, vous connaissez les véritables dépendances, mais tous ceux qui viennent après vous ne savent rien. +Vous n'y avez peut-être pas pensé de cette façon auparavant, mais chaque fois que vous utilisez l'état global, vous créez des canaux de communication secrets sans fil. L'action fantôme à distance oblige les développeurs à lire chaque ligne de code pour comprendre les interactions potentielles, réduit la productivité des développeurs et embrouille les nouveaux membres de l'équipe. Si c'est vous qui avez créé le code, vous connaissez les dépendances réelles, mais quiconque viendra après vous sera désemparé. -N'écrivez pas de code qui utilise l'état global, préférez passer les dépendances. C'est l'injection de dépendances. +N'écrivez pas de code qui utilise l'état global, préférez le passage de dépendances. C'est-à-dire l'injection de dépendances. -La fragilité de l'État mondial .[#toc-brittleness-of-the-global-state] ----------------------------------------------------------------------- +Fragilité de l'état global +-------------------------- -Dans le code qui utilise un état global et des singletons, on ne sait jamais avec certitude quand et par qui cet état a été modifié. Ce risque est déjà présent à l'initialisation. Le code suivant est censé créer une connexion à une base de données et initialiser la passerelle de paiement, mais il continue à lancer une exception et il est extrêmement fastidieux d'en trouver la cause : +Dans le code qui utilise l'état global et les singletons, on n'est jamais sûr de quand et qui a modifié cet état. Ce risque apparaît dès l'initialisation. Le code suivant est censé créer une connexion à la base de données et initialiser la passerelle de paiement, mais il lève constamment une exception et trouver la cause est extrêmement long : ```php PaymentGateway::init(); DB::init('mysql:', 'user', 'password'); ``` -Vous devez parcourir le code en détail pour découvrir que l'objet `PaymentGateway` accède à d'autres objets sans fil, dont certains nécessitent une connexion à une base de données. Ainsi, vous devez initialiser la base de données avant `PaymentGateway`. Cependant, l'écran de fumée de l'état global vous cache cela. Combien de temps gagneriez-vous si l'API de chaque classe ne mentait pas et ne déclarait pas ses dépendances ? +Vous devez parcourir le code en détail pour découvrir que l'objet `PaymentGateway` accède sans fil à d'autres objets, dont certains nécessitent une connexion à la base de données. Il est donc nécessaire d'initialiser la base de données avant `PaymentGateway`. Cependant, l'écran de fumée de l'état global vous le cache. Combien de temps auriez-vous gagné si les API des classes individuelles ne mentaient pas et déclaraient leurs dépendances ? ```php $db = new DB('mysql:', 'user', 'password'); $gateway = new PaymentGateway($db, ...); ``` -Un problème similaire se pose lors de l'utilisation de l'accès global à une connexion de base de données : +Un problème similaire se pose lors de l'utilisation d'un accès global à la connexion à la base de données : ```php use Illuminate\Support\Facades\DB; @@ -115,9 +110,9 @@ class Article } ``` -Lorsque l'on appelle la méthode `save()`, on ne sait pas si une connexion à la base de données a déjà été créée et qui est responsable de sa création. Par exemple, si nous voulions modifier la connexion à la base de données à la volée, peut-être à des fins de test, nous devrions probablement créer des méthodes supplémentaires telles que `DB::reconnect(...)` ou `DB::reconnectForTest()`. +Lors de l'appel de la méthode `save()`, on n'est pas certain que la connexion à la base de données a déjà été créée et qui est responsable de sa création. Si nous voulons, par exemple, modifier la connexion à la base de données à la volée, par exemple pour des tests, nous devrions probablement créer d'autres méthodes comme `DB::reconnect(...)` ou `DB::reconnectForTest()`. -Prenons un exemple : +Considérons un exemple : ```php $article = new Article; @@ -127,9 +122,9 @@ Foo::doSomething(); $article->save(); ``` -Comment pouvons-nous être sûrs que la base de données de test est réellement utilisée lors de l'appel à `$article->save()`? Et si la méthode `Foo::doSomething()` modifiait la connexion globale à la base de données ? Pour le savoir, nous devrions examiner le code source de la classe `Foo` et probablement de nombreuses autres classes. Toutefois, cette approche ne fournirait qu'une réponse à court terme, car la situation pourrait changer à l'avenir. +Où avons-nous la certitude que lors de l'appel de `$article->save()`, la base de données de test est réellement utilisée ? Et si la méthode `Foo::doSomething()` avait modifié la connexion globale à la base de données ? Pour le savoir, nous devrions examiner le code source de la classe `Foo` et probablement de nombreuses autres classes. Cette approche ne fournirait cependant qu'une réponse à court terme, car la situation pourrait changer à l'avenir. -Et si nous déplacions la connexion à la base de données vers une variable statique à l'intérieur de la classe `Article`? +Et si nous déplacions la connexion à la base de données dans une variable statique à l'intérieur de la classe `Article` ? ```php class Article @@ -148,11 +143,11 @@ class Article } ``` -Cela ne change rien du tout. Le problème est un état global et la classe dans laquelle il se cache n'a pas d'importance. Dans ce cas, comme dans le précédent, nous n'avons aucune idée de la base de données qui est écrite lorsque la méthode `$article->save()` est appelée. N'importe qui à l'extrémité distante de l'application pourrait changer la base de données à tout moment en utilisant `Article::setDb()`. Sous nos yeux. +Cela ne change absolument rien. Le problème est l'état global et peu importe dans quelle classe il se cache. Dans ce cas, comme dans le précédent, nous n'avons aucune idée, lors de l'appel de la méthode `$article->save()`, dans quelle base de données l'écriture aura lieu. N'importe qui à l'autre bout de l'application aurait pu modifier la base de données à tout moment en utilisant `Article::setDb()`. Sous notre nez. L'état global rend notre application **extrêmement fragile**. -Cependant, il existe un moyen simple de résoudre ce problème. Il suffit de faire en sorte que l'API déclare des dépendances pour garantir une bonne fonctionnalité. +Il existe cependant un moyen simple de traiter ce problème. Il suffit de laisser l'API déclarer les dépendances, ce qui garantit le bon fonctionnement. ```php class Article @@ -174,15 +169,15 @@ Foo::doSomething(); $article->save(); ``` -Cette approche élimine le souci de modifications cachées et inattendues des connexions à la base de données. Maintenant, nous sommes sûrs de l'endroit où l'article est stocké et aucune modification du code dans une autre classe sans rapport ne peut plus changer la situation. Le code n'est plus fragile, mais stable. +Grâce à cette approche, la crainte de modifications cachées et inattendues de la connexion à la base de données disparaît. Nous avons maintenant la certitude de l'endroit où l'article est enregistré et aucune modification du code à l'intérieur d'une autre classe non liée ne peut plus changer la situation. Le code n'est plus fragile, mais stable. -N'écrivez pas de code qui utilise l'état global, préférez le passage des dépendances. Ainsi, l'injection de dépendances. +N'écrivez pas de code qui utilise l'état global, préférez le passage de dépendances. C'est-à-dire l'injection de dépendances. -Singleton .[#toc-singleton] ---------------------------- +Singleton +--------- -Le singleton est un modèle de conception qui, selon la [définition de |https://en.wikipedia.org/wiki/Singleton_pattern] la célèbre publication Gang of Four, limite une classe à une seule instance et lui offre un accès global. L'implémentation de ce patron ressemble généralement au code suivant : +Le singleton est un patron de conception qui, selon la "définition":https://en.wikipedia.org/wiki/Singleton_pattern de la célèbre publication du Gang of Four, limite une classe à une seule instance et offre un accès global à celle-ci. L'implémentation de ce patron ressemble généralement au code suivant : ```php class Singleton @@ -195,38 +190,35 @@ class Singleton return self::$instance; } - // et d'autres méthodes qui exécutent les fonctions de la classe + // et d'autres méthodes remplissant les fonctions de la classe donnée } ``` -Malheureusement, le singleton introduit un état global dans l'application. Et comme nous l'avons montré ci-dessus, l'état global n'est pas souhaitable. C'est pourquoi le singleton est considéré comme un anti-modèle. +Malheureusement, le singleton introduit un état global dans l'application. Et comme nous l'avons montré ci-dessus, l'état global est indésirable. C'est pourquoi le singleton est considéré comme un anti-patron. -N'utilisez pas les singletons dans votre code et remplacez-les par d'autres mécanismes. Vous n'avez vraiment pas besoin de singletons. Toutefois, si vous devez garantir l'existence d'une seule instance d'une classe pour l'ensemble de l'application, confiez cette tâche au [conteneur DI |container]. -Ainsi, créez un singleton d'application, ou service. Cela empêchera la classe de fournir sa propre unicité (c'est-à-dire qu'elle n'aura pas de méthode `getInstance()` et de variable statique) et n'exécutera que ses fonctions. Ainsi, elle cessera de violer le principe de responsabilité unique. +N'utilisez pas de singletons dans votre code et remplacez-les par d'autres mécanismes. Vous n'avez vraiment pas besoin de singletons. Cependant, si vous devez garantir l'existence d'une seule instance d'une classe pour toute l'application, laissez cela au [conteneur DI |container]. Créez ainsi un singleton d'application, c'est-à-dire un service. De cette façon, la classe cessera de s'occuper d'assurer sa propre unicité (c'est-à-dire qu'elle n'aura pas de méthode `getInstance()` ni de variable statique) et ne remplira que ses fonctions. Elle cessera ainsi de violer le principe de responsabilité unique. -État global versus tests .[#toc-global-state-versus-tests] ----------------------------------------------------------- +État global versus tests +------------------------ -Lorsque nous écrivons des tests, nous supposons que chaque test est une unité isolée et qu'aucun état externe n'y entre. Et aucun état ne quitte les tests. Lorsqu'un test se termine, tout état associé au test devrait être supprimé automatiquement par le ramasse-miettes. Cela rend les tests isolés. Par conséquent, nous pouvons exécuter les tests dans n'importe quel ordre. +Lors de l'écriture de tests, nous supposons que chaque test est une unité isolée et qu'aucun état externe n'y entre. Et aucun état ne quitte les tests. Une fois le test terminé, tout l'état lié au test devrait être automatiquement supprimé par le garbage collector. Grâce à cela, les tests sont isolés. Nous pouvons donc exécuter les tests dans n'importe quel ordre. -Cependant, si des états/singletons globaux sont présents, toutes ces belles hypothèses s'effondrent. Un état peut entrer et sortir d'un test. Soudainement, l'ordre des tests peut avoir de l'importance. +Cependant, s'il y a des états globaux/singletons, toutes ces hypothèses agréables s'effondrent. L'état peut entrer et sortir du test. Soudain, l'ordre des tests peut avoir de l'importance. -Pour tester les singletons, les développeurs doivent souvent assouplir leurs propriétés, peut-être en permettant à une instance d'être remplacée par une autre. De telles solutions sont, au mieux, des bidouillages qui produisent un code difficile à maintenir et à comprendre. Tout test ou méthode `tearDown()` qui affecte un état global doit annuler ces changements. +Pour pouvoir tester les singletons, les développeurs doivent souvent assouplir leurs propriétés, par exemple en permettant de remplacer l'instance par une autre. De telles solutions sont au mieux des hacks qui créent un code difficile à maintenir et à comprendre. Chaque test ou méthode `tearDown()` qui affecte un état global doit annuler ces changements. -L'état global est le plus gros casse-tête des tests unitaires ! +L'état global est le plus grand casse-tête des tests unitaires ! -Comment remédier à cette situation ? Facile. N'écrivez pas de code qui utilise des singletons, préférez passer des dépendances. C'est-à-dire l'injection de dépendances. +Comment corriger la situation ? Facilement. N'écrivez pas de code qui utilise des singletons, préférez le passage de dépendances. C'est-à-dire l'injection de dépendances. -Constantes globales .[#toc-global-constants] --------------------------------------------- +Constantes globales +------------------- -L'état global ne se limite pas à l'utilisation des singletons et des variables statiques, mais peut également s'appliquer aux constantes globales. +L'état global ne se limite pas à l'utilisation de singletons et de variables statiques, mais peut également concerner les constantes globales. -Les constantes dont la valeur ne nous fournit aucune information nouvelle (`M_PI`) ou utile (`PREG_BACKTRACK_LIMIT_ERROR`) sont clairement OK. -À l'inverse, les constantes qui servent à faire passer des informations *sans fil* à l'intérieur du code ne sont rien d'autre qu'une dépendance cachée. Comme `LOG_FILE` dans l'exemple suivant. -L'utilisation de la constante `FILE_APPEND` est parfaitement correcte. +Les constantes dont la valeur ne nous apporte aucune information nouvelle (`M_PI`) ou utile (`PREG_BACKTRACK_LIMIT_ERROR`) sont clairement acceptables. En revanche, les constantes qui servent de moyen de passer *sans fil* des informations à l'intérieur du code ne sont rien d'autre qu'une dépendance cachée. Comme `LOG_FILE` dans l'exemple suivant. L'utilisation de la constante `FILE_APPEND` est tout à fait correcte. ```php const LOG_FILE = '...'; @@ -242,7 +234,7 @@ class Foo } ``` -Dans ce cas, nous devons déclarer le paramètre dans le constructeur de la classe `Foo` pour qu'il fasse partie de l'API : +Dans ce cas, nous devrions déclarer un paramètre dans le constructeur de la classe `Foo` pour qu'il fasse partie de l'API : ```php class Foo @@ -261,44 +253,42 @@ class Foo } ``` -Nous pouvons maintenant transmettre des informations sur le chemin d'accès au fichier de journalisation et le modifier facilement si nécessaire, ce qui facilite les tests et la maintenance du code. +Maintenant, nous pouvons passer l'information sur le chemin du fichier journal et la modifier facilement selon les besoins, ce qui facilite les tests et la maintenance du code. -Fonctions globales et méthodes statiques .[#toc-global-functions-and-static-methods] ------------------------------------------------------------------------------------- +Fonctions globales et méthodes statiques +---------------------------------------- -Nous tenons à souligner que l'utilisation de méthodes statiques et de fonctions globales n'est pas problématique en soi. Nous avons expliqué le caractère inapproprié de l'utilisation de `DB::insert()` et de méthodes similaires, mais il s'agissait toujours d'un état global stocké dans une variable statique. La méthode `DB::insert()` nécessite l'existence d'une variable statique car elle stocke la connexion à la base de données. Sans cette variable, il serait impossible de mettre en œuvre la méthode. +Nous tenons à souligner que l'utilisation de méthodes statiques et de fonctions globales n'est pas problématique en soi. Nous avons expliqué pourquoi l'utilisation de `DB::insert()` et de méthodes similaires est inappropriée, mais il s'agissait toujours uniquement d'une question d'état global stocké dans une variable statique. La méthode `DB::insert()` nécessite l'existence d'une variable statique car la connexion à la base de données y est stockée. Sans cette variable, il serait impossible d'implémenter la méthode. -L'utilisation de méthodes et de fonctions statiques déterministes, telles que `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` et bien d'autres, est parfaitement compatible avec l'injection de dépendances. Ces fonctions renvoient toujours les mêmes résultats à partir des mêmes paramètres d'entrée et sont donc prévisibles. Elles n'utilisent pas d'état global. +L'utilisation de méthodes statiques et de fonctions déterministes, telles que `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` et bien d'autres, est parfaitement conforme à l'injection de dépendances. Ces fonctions renvoient toujours les mêmes résultats pour les mêmes paramètres d'entrée et sont donc prévisibles. Elles n'utilisent aucun état global. -Cependant, il existe des fonctions en PHP qui ne sont pas déterministes. C'est le cas, par exemple, de la fonction `htmlspecialchars()`. Son troisième paramètre, `$encoding`, s'il n'est pas spécifié, prend par défaut la valeur de l'option de configuration `ini_get('default_charset')`. Il est donc recommandé de toujours spécifier ce paramètre pour éviter un éventuel comportement imprévisible de la fonction. C'est ce que fait systématiquement Nette. +Il existe cependant des fonctions en PHP qui ne sont pas déterministes. Parmi elles, par exemple, la fonction `htmlspecialchars()`. Son troisième paramètre `$encoding`, s'il n'est pas spécifié, prend par défaut la valeur de l'option de configuration `ini_get('default_charset')`. C'est pourquoi il est recommandé de toujours spécifier ce paramètre et d'éviter ainsi un comportement éventuellement imprévisible de la fonction. Nette le fait systématiquement. -Certaines fonctions, telles que `strtolower()`, `strtoupper()`, et autres, ont eu un comportement non déterministe dans un passé récent et ont dépendu du paramètre `setlocale()`. Cela a causé de nombreuses complications, le plus souvent en travaillant avec la langue turque. -En effet, la langue turque fait la distinction entre les majuscules et les minuscules `I` avec et sans point. Ainsi, `strtolower('I')` renvoyait le caractère `ı` et `strtoupper('i')` renvoyait le caractère `İ`, ce qui conduisait les applications à provoquer un certain nombre d'erreurs mystérieuses. -Toutefois, ce problème a été corrigé dans la version 8.2 de PHP et les fonctions ne dépendent plus de la locale. +Certaines fonctions, telles que `strtolower()`, `strtoupper()` et similaires, se comportaient de manière non déterministe dans un passé récent et dépendaient du paramètre `setlocale()`. Cela causait de nombreuses complications, le plus souvent lors du travail avec la langue turque. Celle-ci distingue en effet les lettres `I` majuscules et minuscules avec et sans point. Ainsi, `strtolower('I')` renvoyait le caractère `ı` et `strtoupper('i')` le caractère `İ`, ce qui entraînait l'apparition de nombreuses erreurs mystérieuses dans les applications. Ce problème a cependant été corrigé dans la version PHP 8.2 et les fonctions ne dépendent plus de la locale. -C'est un bel exemple de la manière dont l'état global a tourmenté des milliers de développeurs dans le monde. La solution a été de le remplacer par l'injection de dépendances. +C'est un bel exemple de la façon dont l'état global a tourmenté des milliers de développeurs dans le monde entier. La solution a été de le remplacer par l'injection de dépendances. -Quand est-il possible d'utiliser l'état global ? .[#toc-when-is-it-possible-to-use-global-state] ------------------------------------------------------------------------------------------------- +Quand est-il possible d'utiliser l'état global ? +------------------------------------------------ -Dans certaines situations spécifiques, il est possible d'utiliser l'état global. Par exemple, lorsque vous déboguez un code et que vous avez besoin d'extraire la valeur d'une variable ou de mesurer la durée d'une partie spécifique du programme. Dans de tels cas, qui concernent des actions temporaires qui seront ultérieurement supprimées du code, il est légitime d'utiliser un dumper ou un chronomètre disponible globalement. Ces outils ne font pas partie de la conception du code. +Il existe certaines situations spécifiques où il est possible d'utiliser l'état global. Par exemple, lors du débogage de code, lorsque vous devez afficher la valeur d'une variable ou mesurer la durée d'une certaine partie du programme. Dans de tels cas, qui concernent des actions temporaires qui seront ultérieurement supprimées du code, il est légitime d'utiliser un dumper ou un chronomètre globalement disponible. Ces outils ne font en effet pas partie de la conception du code. -Un autre exemple est celui des fonctions permettant de travailler avec des expressions régulières `preg_*`, qui stockent en interne les expressions régulières compilées dans un cache statique en mémoire. Lorsque vous appelez la même expression régulière plusieurs fois dans différentes parties du code, elle n'est compilée qu'une seule fois. Le cache permet de gagner en performance et est totalement invisible pour l'utilisateur, de sorte qu'une telle utilisation peut être considérée comme légitime. +Un autre exemple sont les fonctions pour travailler avec les expressions régulières `preg_*`, qui stockent en interne les expressions régulières compilées dans un cache statique en mémoire. Ainsi, lorsque vous appelez la même expression régulière plusieurs fois à différents endroits du code, elle n'est compilée qu'une seule fois. Le cache économise les performances et est en même temps totalement invisible pour l'utilisateur, c'est pourquoi une telle utilisation peut être considérée comme légitime. -Résumé .[#toc-summary] ----------------------- +Résumé +------ -Nous avons montré pourquoi il est logique +Nous avons discuté des raisons pour lesquelles il est judicieux de : 1) Supprimer toutes les variables statiques du code 2) Déclarer les dépendances 3) Et utiliser l'injection de dépendances -Lorsque vous envisagez la conception d'un code, gardez à l'esprit que chaque `static $foo` représente un problème. Pour que votre code soit un environnement respectueux de l'injection de dépendances, il est essentiel d'éradiquer complètement l'état global et de le remplacer par l'injection de dépendances. +Lorsque vous réfléchissez à la conception du code, gardez à l'esprit que chaque `static $foo` représente un problème. Pour que votre code soit un environnement respectant la DI, il est essentiel d'éradiquer complètement l'état global et de le remplacer par l'injection de dépendances. -Au cours de ce processus, vous constaterez peut-être que vous devez diviser une classe parce qu'elle a plus d'une responsabilité. Ne vous en préoccupez pas ; efforcez-vous de respecter le principe de la responsabilité unique. +Au cours de ce processus, vous découvrirez peut-être qu'il est nécessaire de diviser une classe car elle a plus d'une responsabilité. N'ayez pas peur de cela ; visez le principe de responsabilité unique. -*Je tiens à remercier Miško Hevery, dont les articles tels que [Flaw : Brittle Global State & Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/] constituent la base de ce chapitre*. +*Je tiens à remercier Miško Hevery, dont les articles, tels que [Flaw: Brittle Global State & Singletons |https://web.archive.org/web/20230321084133/http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/], sont à la base de ce chapitre.* diff --git a/dependency-injection/fr/introduction.texy b/dependency-injection/fr/introduction.texy index 03adbd78c3..543c963588 100644 --- a/dependency-injection/fr/introduction.texy +++ b/dependency-injection/fr/introduction.texy @@ -1,58 +1,58 @@ -Qu'est-ce que l'injection de dépendances ? +Qu'est-ce que l'Injection de Dépendances ? ****************************************** .[perex] -Ce chapitre vous présente les pratiques de programmation de base que vous devez suivre lorsque vous écrivez une application. Il s'agit des principes fondamentaux nécessaires à l'écriture d'un code propre, compréhensible et facile à maintenir. +Ce chapitre vous présente les pratiques de programmation de base que vous devriez suivre lors de l'écriture de toutes vos applications. Ce sont les fondations nécessaires pour écrire du code propre, compréhensible et maintenable. -Si vous apprenez et suivez ces règles, Nette sera là pour vous à chaque étape. Il s'occupera des tâches routinières à votre place et vous offrira un maximum de confort, afin que vous puissiez vous concentrer sur la logique elle-même. +Si vous adoptez ces règles et les suivez, Nette vous soutiendra à chaque étape. Il s'occupera des tâches routinières pour vous et vous offrira un maximum de confort, afin que vous puissiez vous concentrer sur la logique elle-même. -Les principes que nous allons exposer ici sont très simples. Vous n'avez pas à vous soucier de quoi que ce soit. +Les principes que nous allons montrer ici sont assez simples. Vous n'avez rien à craindre. -Vous vous souvenez de votre premier programme ? .[#toc-remember-your-first-program] ------------------------------------------------------------------------------------ +Vous souvenez-vous de votre premier programme ? +----------------------------------------------- -Nous ne savons pas dans quel langage vous l'avez écrit, mais s'il s'agit de PHP, il aurait pu ressembler à ceci : +Nous ne savons pas dans quel langage vous l'avez écrit, mais si c'était en PHP, il aurait probablement ressemblé à quelque chose comme ceci : ```php -function addition(float $a, float $b): float +function soucet(float $a, float $b): float { return $a + $b; } -echo addition(23, 1); // imprime 24 +echo soucet(23, 1); // affiche 24 ``` -Quelques lignes de code triviales, mais tellement de concepts clés cachés en elles. Qu'il y a des variables. Que le code est décomposé en unités plus petites, qui sont des fonctions, par exemple. Qu'on leur passe des arguments d'entrée et qu'elles renvoient des résultats. Il ne manque que les conditions et les boucles. +Quelques lignes de code triviales, mais elles cachent tellement de concepts clés. Qu'il existe des variables. Que le code est divisé en unités plus petites, telles que des fonctions. Que nous leur passons des arguments d'entrée et qu'elles retournent des résultats. Il ne manque que les conditions et les boucles. -Le fait qu'une fonction prenne des données en entrée et renvoie un résultat est un concept parfaitement compréhensible, qui est également utilisé dans d'autres domaines, tels que les mathématiques. +Le fait qu'une fonction reçoive des données d'entrée et retourne un résultat est un concept parfaitement compréhensible, utilisé également dans d'autres domaines, comme les mathématiques. -Une fonction a sa signature, qui se compose de son nom, d'une liste de paramètres et de leurs types, et enfin du type de la valeur de retour. En tant qu'utilisateurs, nous sommes intéressés par la signature, et nous n'avons généralement pas besoin de connaître l'implémentation interne. +Une fonction a sa signature, qui comprend son nom, une liste de paramètres et leurs types, et enfin le type de la valeur de retour. En tant qu'utilisateur, nous nous intéressons à la signature ; nous n'avons généralement pas besoin de connaître l'implémentation interne. -Imaginons maintenant que la signature de la fonction ressemble à ceci : +Maintenant, imaginez que la signature de la fonction ressemble à ceci : ```php -function addition(float $x): float +function soucet(float $x): float ``` -Une addition avec un seul paramètre ? C'est étrange... Qu'en est-il de ceci ? +Une somme avec un seul paramètre ? C'est étrange... Et comme ça ? ```php -function addition(): float +function soucet(): float ``` -C'est vraiment bizarre, non ? Comment la fonction est-elle utilisée ? +C'est vraiment très étrange, n'est-ce pas ? Comment la fonction est-elle utilisée ? ```php -echo addition(); // qu'est-ce que ça imprime ? +echo soucet(); // qu'est-ce que cela va afficher ? ``` -En regardant un tel code, nous serions confus. Non seulement un débutant ne le comprendrait pas, mais même un programmeur expérimenté ne le comprendrait pas. +En regardant un tel code, nous serions confus. Non seulement un débutant ne le comprendrait pas, mais même un programmeur expérimenté ne comprendrait pas ce code. -Vous demandez-vous à quoi ressemblerait une telle fonction à l'intérieur ? Où obtiendrait-elle les sommets ? Elle les obtiendrait probablement d'elle-même, d'une manière ou d'une autre, par exemple de la manière suivante : +Vous demandez-vous à quoi ressemblerait réellement une telle fonction à l'intérieur ? Où obtiendrait-elle les opérandes ? Elle les obtiendrait probablement *d'une manière ou d'une autre* elle-même, peut-être comme ceci : ```php -function addition(): float +function soucet(): float { $a = Input::get('a'); $b = Input::get('b'); @@ -60,71 +60,71 @@ function addition(): float } ``` -Il s'avère qu'il y a des liens cachés vers d'autres fonctions (ou méthodes statiques) dans le corps de la fonction, et pour trouver d'où viennent réellement les addends, nous devons creuser davantage. +Dans le corps de la fonction, nous avons découvert des liens cachés vers d'autres fonctions globales ou méthodes statiques. Pour savoir d'où proviennent réellement les opérandes, nous devons chercher plus loin. -Pas par là ! .[#toc-not-this-way] ---------------------------------- +Pas par ici ! +------------- -La conception que nous venons de montrer est l'essence même de nombreuses caractéristiques négatives : +La conception que nous venons de montrer est l'essence de nombreuses caractéristiques négatives : -- la signature de la fonction prétend qu'elle n'a pas besoin des sommets, ce qui nous rend perplexes -- nous n'avons aucune idée de la manière dont la fonction pourrait être calculée avec deux autres nombres -- nous avons dû consulter le code pour savoir d'où venaient les sommations -- nous avons découvert des dépendances cachées -- pour bien comprendre, il faut aussi examiner ces dépendances +- La signature de la fonction prétendait qu'elle n'avait pas besoin d'opérandes, ce qui nous a induits en erreur. +- Nous ne savons pas du tout comment faire pour que la fonction additionne deux autres nombres. +- Nous avons dû regarder dans le code pour savoir d'où elle tirait les opérandes. +- Nous avons découvert des liens cachés. +- Pour une compréhension complète, il faut également examiner ces liens. -Et est-ce même le rôle de la fonction d'addition de se procurer des entrées ? Bien sûr que non. Sa responsabilité est uniquement d'ajouter. +Et est-ce vraiment la tâche d'une fonction d'addition d'obtenir des entrées ? Bien sûr que non. Sa responsabilité est uniquement l'addition elle-même. -Nous ne voulons pas rencontrer un tel code, et nous ne voulons certainement pas l'écrire. Le remède est simple : revenez à l'essentiel et utilisez simplement des paramètres : +Nous ne voulons pas rencontrer un tel code, et nous ne voulons certainement pas l'écrire. La solution est simple : revenir aux bases et simplement utiliser des paramètres : ```php -function addition(float $a, float $b): float +function soucet(float $a, float $b): float { return $a + $b; } ``` -Règle n° 1 : Laissez-le vous être transmis .[#toc-rule-1-let-it-be-passed-to-you] ---------------------------------------------------------------------------------- +Règle n°1 : faites-vous passer les choses +----------------------------------------- -La règle la plus importante est la suivante : **toutes les données dont les fonctions ou les classes ont besoin doivent leur être transmises**. +La règle la plus importante est : **toutes les données dont une fonction ou une classe a besoin doivent lui être passées**. -Au lieu d'inventer des moyens cachés pour qu'ils accèdent eux-mêmes aux données, passez simplement les paramètres. Vous gagnerez du temps que vous auriez passé à inventer des chemins cachés qui n'amélioreront certainement pas votre code. +Au lieu d'inventer des moyens cachés pour qu'elles puissent y accéder elles-mêmes, passez simplement les paramètres. Vous économiserez du temps nécessaire à l'invention de chemins cachés, qui n'amélioreront certainement pas votre code. -Si vous suivez toujours et partout cette règle, vous êtes sur la voie d'un code sans dépendances cachées. Un code compréhensible non seulement pour l'auteur mais aussi pour tous ceux qui le liront par la suite. Où tout est compréhensible à partir des signatures des fonctions et des classes, et où il n'est pas nécessaire de chercher des secrets cachés dans l'implémentation. +Si vous suivez toujours et partout cette règle, vous êtes sur la voie d'un code sans liens cachés. D'un code compréhensible non seulement par l'auteur, mais aussi par quiconque le lira après lui. Où tout est compréhensible à partir des signatures des fonctions et des classes, et il n'est pas nécessaire de chercher des secrets cachés dans l'implémentation. -Cette technique est appelée professionnellement **injection de dépendance**. Et ces données sont appelées **dépendances**. Il s'agit d'un simple passage de paramètres, rien de plus. +Cette technique est appelée techniquement **Injection de Dépendances**. Et ces données sont appelées **dépendances.** En fait, c'est simplement du passage de paramètres, rien de plus. .[note] -Ne confondez pas l'injection de dépendances, qui est un modèle de conception, avec un "conteneur d'injection de dépendances", qui est un outil, quelque chose de diamétralement différent. Nous traiterons des conteneurs plus tard. +Ne confondez pas l'injection de dépendances, qui est un patron de conception, avec le "conteneur d'injection de dépendances", qui est un outil, c'est-à-dire quelque chose de diamétralement différent. Nous aborderons les conteneurs plus tard. -Des fonctions aux classes .[#toc-from-functions-to-classes] ------------------------------------------------------------ +Des fonctions aux classes +------------------------- -Et quel est le lien entre les classes ? Une classe est une unité plus complexe qu'une simple fonction, mais la règle n° 1 s'applique entièrement ici aussi. Il y a simplement [plus de façons de passer des arguments |passing-dependencies]. Par exemple, comme dans le cas d'une fonction : +Et quel est le rapport avec les classes ? Une classe est une entité plus complexe qu'une simple fonction, mais la règle n°1 s'applique également ici sans exception. Il existe simplement [plus d'options pour passer les arguments|passing-dependencies]. Par exemple, de manière assez similaire au cas d'une fonction : ```php -class Math +class Matematika { - public function addition(float $a, float $b): float + public function soucet(float $a, float $b): float { return $a + $b; } } -$math = new Math; -echo $math->addition(23, 1); // 24 +$math = new Matematika; +echo $math->soucet(23, 1); // 24 ``` -Ou par d'autres méthodes, ou directement par le constructeur : +Ou en utilisant d'autres méthodes, ou directement le constructeur : ```php -class Addition +class Soucet { public function __construct( private float $a, @@ -132,24 +132,24 @@ class Addition ) { } - public function calculate(): float + public function spocti(): float { return $this->a + $this->b; } } -$addition = new Addition(23, 1); -echo $addition->calculate(); // 24 +$soucet = new Soucet(23, 1); +echo $soucet->spocti(); // 24 ``` -Ces deux exemples sont tout à fait conformes à l'injection de dépendances. +Les deux exemples sont entièrement conformes à l'injection de dépendances. -Exemples concrets .[#toc-real-life-examples] --------------------------------------------- +Exemples réels +-------------- -Dans le monde réel, vous n'écrirez pas de cours sur l'addition de nombres. Passons maintenant aux exemples pratiques. +Dans le monde réel, vous n'écrirez pas de classes pour additionner des nombres. Passons à des exemples pratiques. Prenons une classe `Article` représentant un article de blog : @@ -162,7 +162,7 @@ class Article public function save(): void { - // sauvegarder l'article dans la base de données + // enregistre l'article dans la base de données } } ``` @@ -171,14 +171,14 @@ et l'utilisation sera la suivante : ```php $article = new Article; -$article->title = '10 Things You Need to Know About Losing Weight'; -$article->content = 'Every year millions of people in ...'; +$article->title = '10 choses que vous devez savoir sur la perte de poids'; +$article->content = 'Chaque année, des millions de personnes dans ...'; $article->save(); ``` -La méthode `save()` enregistre l'article dans une table de la base de données. L'implémentation de cette méthode à l'aide de [Nette Database |database:] serait un jeu d'enfant, si ce n'était d'un problème : où `Article` obtient-il la connexion à la base de données, c'est-à-dire un objet de la classe `Nette\Database\Connection`? +La méthode `save()` enregistre l'article dans une table de base de données. L'implémenter avec [Nette Database |database:] serait un jeu d'enfant, s'il n'y avait pas un hic : où `Article` obtient-il la connexion à la base de données, c'est-à-dire l'objet de la classe `Nette\Database\Connection` ? -Il semble que nous ayons beaucoup d'options. Il peut l'obtenir à partir d'une variable statique quelque part. Ou hériter d'une classe qui fournit une connexion à la base de données. Ou tirer parti d'un [singleton |global-state#Singleton]. Ou utiliser ce que l'on appelle les façades, qui sont utilisées dans Laravel : +Il semble que nous ayons beaucoup d'options. Il peut la prendre quelque part dans une variable statique. Ou hériter d'une classe qui assure la connexion à la base de données. Ou utiliser un [singleton |global-state#Singleton]. Ou les façades, qui sont utilisées dans Laravel : ```php use Illuminate\Support\Facades\DB; @@ -199,17 +199,17 @@ class Article } ``` -Super, nous avons résolu le problème. +Génial, nous avons résolu le problème. -Ou l'avons-nous fait ? +Ou pas ? -Rappelons la [règle n°1 : Let It Be Passed to You |#rule #1: Let It Be Passed to You]: toutes les dépendances dont la classe a besoin doivent lui être transmises. Car si nous enfreignons cette règle, nous nous engageons sur la voie d'un code sale, plein de dépendances cachées, incompréhensible, et le résultat sera une application pénible à maintenir et à développer. +Rappelons la [##Règle n°1 : faites-vous passer les choses] : toutes les dépendances dont la classe a besoin doivent lui être passées. Car si nous enfreignons la règle, nous nous engageons sur la voie d'un code sale plein de liens cachés, d'incompréhensibilité, et le résultat sera une application qu'il sera pénible de maintenir et de développer. -L'utilisateur de la classe `Article` n'a aucune idée de l'endroit où la méthode `save()` stocke l'article. Dans une table de la base de données ? Laquelle, celle de production ou celle de test ? Et comment la modifier ? +L'utilisateur de la classe `Article` ne sait pas où la méthode `save()` enregistre l'article. Dans une table de base de données ? Laquelle, la production ou le test ? Et comment peut-on changer cela ? -L'utilisateur doit regarder comment la méthode `save()` est implémentée et trouve l'utilisation de la méthode `DB::insert()`. Il doit donc poursuivre ses recherches pour découvrir comment cette méthode obtient une connexion à la base de données. Les dépendances cachées peuvent former une longue chaîne. +L'utilisateur doit regarder comment la méthode `save()` est implémentée et trouve l'utilisation de la méthode `DB::insert()`. Il doit donc chercher plus loin comment cette méthode obtient la connexion à la base de données. Et les liens cachés peuvent former une chaîne assez longue. -Dans un code propre et bien conçu, il n'y a jamais de dépendances cachées, de façades Laravel ou de variables statiques. Dans un code propre et bien conçu, les arguments sont transmis : +Dans un code propre et bien conçu, il n'y a jamais de liens cachés, de façades Laravel ou de variables statiques. Dans un code propre et bien conçu, on passe des arguments : ```php class Article @@ -224,7 +224,7 @@ class Article } ``` -Une approche encore plus pratique, comme nous le verrons plus loin, consistera à utiliser le constructeur : +Ce sera encore plus pratique, comme nous le verrons plus loin, avec le constructeur : ```php class Article @@ -245,11 +245,11 @@ class Article ``` .[note] -Si vous êtes un programmeur expérimenté, vous pouvez penser que `Article` ne devrait pas avoir de méthode `save()` du tout ; il devrait représenter un composant purement de données, et un référentiel séparé devrait s'occuper de la sauvegarde. C'est logique. Mais cela nous mènerait bien au-delà du sujet, qui est l'injection de dépendances, et de l'effort pour fournir des exemples simples. +Si vous êtes un programmeur expérimenté, vous pensez peut-être que `Article` ne devrait pas du tout avoir de méthode `save()`, qu'il devrait représenter purement un composant de données et que l'enregistrement devrait être géré par un dépôt séparé. C'est logique. Mais cela nous éloignerait beaucoup du sujet, qui est l'injection de dépendances, et de l'effort de fournir des exemples simples. -Si vous écrivez une classe qui a besoin, par exemple, d'une base de données pour fonctionner, n'inventez pas où aller la chercher, mais faites-la passer. Soit en tant que paramètre du constructeur, soit en tant que paramètre d'une autre méthode. Admettez les dépendances. Admettez-les dans l'API de votre classe. Vous obtiendrez un code compréhensible et prévisible. +Si vous écrivez une classe qui nécessite, par exemple, une base de données pour fonctionner, n'inventez pas d'où l'obtenir, mais faites-la vous passer. Par exemple, comme paramètre du constructeur ou d'une autre méthode. Admettez les dépendances. Admettez-les dans l'API de votre classe. Vous obtiendrez un code compréhensible et prévisible. -Et que dire de cette classe qui enregistre les messages d'erreur : +Et que dire de cette classe, qui enregistre les messages d'erreur : ```php class Logger @@ -262,23 +262,23 @@ class Logger } ``` -Qu'en pensez-vous, avons-nous respecté la [règle n°1 : laissez-le vous être transmis |#rule #1: Let It Be Passed to You]? +Que pensez-vous, avons-nous respecté la [##Règle n°1 : faites-vous passer les choses] ? -On ne l'a pas fait. +Nous ne l'avons pas respectée. -L'information clé, c'est-à-dire le répertoire contenant le fichier journal, est *obtenue* par la classe elle-même à partir de la constante. +L'information clé, c'est-à-dire le répertoire avec le fichier journal, la classe *l'obtient elle-même* à partir d'une constante. Regardez l'exemple d'utilisation : ```php $logger = new Logger; -$logger->log('The temperature is 23 °C'); -$logger->log('The temperature is 10 °C'); +$logger->log('La température est de 23 °C'); +$logger->log('La température est de 10 °C'); ``` -Sans connaître la mise en œuvre, pourriez-vous répondre à la question de savoir où les messages sont écrits ? Devinez-vous que l'existence de la constante `LOG_DIR` est nécessaire à son fonctionnement ? Et pourriez-vous créer une deuxième instance qui écrirait à un autre endroit ? Certainement pas. +Sans connaître l'implémentation, pourriez-vous répondre à la question de savoir où les messages sont écrits ? Auriez-vous pensé que pour fonctionner, l'existence de la constante `LOG_DIR` est nécessaire ? Et pourriez-vous créer une deuxième instance qui écrirait ailleurs ? Certainement pas. -Réparons la classe : +Corrigeons la classe : ```php class Logger @@ -295,24 +295,24 @@ class Logger } ``` -La classe est désormais beaucoup plus compréhensible, configurable et donc plus utile. +La classe est maintenant beaucoup plus compréhensible, configurable et donc plus utile. ```php -$logger = new Logger('/path/to/log.txt'); -$logger->log('The temperature is 15 °C'); +$logger = new Logger('/chemin/vers/log.txt'); +$logger->log('La température est de 15 °C'); ``` -Mais je m'en fiche ! .[#toc-but-i-don-t-care] ---------------------------------------------- +Mais ça ne m'intéresse pas ! +---------------------------- -*"Lorsque je crée un objet Article et que j'appelle save(), je ne veux pas m'occuper de la base de données ; je veux juste qu'il soit enregistré dans celle que j'ai définie dans la configuration."* +*« Quand je crée un objet Article et que j'appelle save(), je ne veux pas m'occuper de la base de données, je veux juste qu'il soit enregistré dans celle que j'ai configurée. »* -*"Lorsque j'utilise Logger, je veux juste que le message soit écrit, et je ne veux pas m'occuper de l'endroit. Laissez les paramètres globaux être utilisés."* +*« Quand j'utilise Logger, je veux juste que le message soit écrit, et je ne veux pas me soucier de l'endroit. Qu'il utilise la configuration globale. »* -Ces points sont valables. +Ce sont des remarques pertinentes. -Prenons l'exemple d'une classe qui envoie des lettres d'information et enregistre leur déroulement : +Comme exemple, montrons une classe qui envoie des newsletters et enregistre le résultat : ```php class NewsletterDistributor @@ -322,27 +322,27 @@ class NewsletterDistributor $logger = new Logger(/* ... */); try { $this->sendEmails(); - $logger->log('Emails have been sent out'); + $logger->log('Les e-mails ont été envoyés'); } catch (Exception $e) { - $logger->log('An error occurred during the sending'); + $logger->log('Une erreur est survenue lors de l\'envoi'); throw $e; } } } ``` -La version améliorée de `Logger`, qui n'utilise plus la constante `LOG_DIR`, nécessite de spécifier le chemin d'accès au fichier dans le constructeur. Comment résoudre ce problème ? La classe `NewsletterDistributor` ne se préoccupe pas de l'endroit où les messages sont écrits ; elle veut simplement les écrire. +Le `Logger` amélioré, qui n'utilise plus la constante `LOG_DIR`, nécessite le chemin du fichier dans le constructeur. Comment résoudre cela ? La classe `NewsletterDistributor` ne se soucie pas du tout de l'endroit où les messages sont écrits, elle veut juste les écrire. -La solution est encore une fois la [règle n° 1 : "Let It Be Passed to You |#rule #1: Let It Be Passed to You]" : transmettez toutes les données dont la classe a besoin. +La solution est à nouveau la [##Règle n°1 : faites-vous passer les choses] : toutes les données dont la classe a besoin, nous les lui passons. -Cela signifie-t-il que nous transmettons le chemin d'accès au journal par l'intermédiaire du constructeur, que nous utilisons ensuite lors de la création de l'objet `Logger`? +Cela signifie donc que nous passons le chemin du journal via le constructeur, que nous utilisons ensuite lors de la création de l'objet `Logger` ? ```php class NewsletterDistributor { public function __construct( - private string $file, // ⛔ PAS DE CETTE FAÇON ! + private string $file, // ⛔ PAS COMME ÇA ! ) { } @@ -351,7 +351,7 @@ class NewsletterDistributor $logger = new Logger($this->file); ``` -Non, pas comme ça ! Le chemin d'accès ne fait pas partie des données dont la classe `NewsletterDistributor` a besoin ; en fait, c'est la classe `Logger` qui en a besoin. Voyez-vous la différence ? La classe `NewsletterDistributor` a besoin du logger lui-même. C'est donc ce que nous allons passer : +Pas comme ça ! Le chemin **n'appartient pas** aux données dont la classe `NewsletterDistributor` a besoin ; ce sont les besoins de `Logger`. Voyez-vous la différence ? La classe `NewsletterDistributor` a besoin du logger en tant que tel. Donc, nous le passons : ```php class NewsletterDistributor @@ -365,35 +365,33 @@ class NewsletterDistributor { try { $this->sendEmails(); - $this->logger->log('Emails have been sent out'); + $this->logger->log('Les e-mails ont été envoyés'); } catch (Exception $e) { - $this->logger->log('An error occurred during the sending'); + $this->logger->log('Une erreur est survenue lors de l\'envoi'); throw $e; } } } ``` -Les signatures de la classe `NewsletterDistributor` montrent clairement que la journalisation fait également partie de ses fonctionnalités. Et la tâche consistant à remplacer le logger par un autre, par exemple pour des tests, est tout à fait triviale. -De plus, si le constructeur de la classe `Logger` change, cela n'affectera pas notre classe. +Maintenant, il est clair d'après les signatures de la classe `NewsletterDistributor` que le logging fait partie de sa fonctionnalité. Et la tâche de remplacer le logger par un autre, par exemple pour les tests, est tout à fait triviale. De plus, si le constructeur de la classe `Logger` changeait, cela n'aurait aucune incidence sur notre classe. -Règle n° 2 : Prendre ce qui vous appartient .[#toc-rule-2-take-what-s-yours] ----------------------------------------------------------------------------- +Règle n°2 : prenez ce qui vous appartient +----------------------------------------- -Ne vous laissez pas induire en erreur et ne vous laissez pas passer les dépendances de vos dépendances. Contentez-vous de passer vos propres dépendances. +Ne vous laissez pas tromper et ne vous faites pas passer les dépendances de vos dépendances. Faites-vous passer uniquement vos propres dépendances. -Grâce à cela, le code utilisant d'autres objets sera complètement indépendant des changements dans leurs constructeurs. Son API sera plus véridique. Et surtout, il sera trivial de remplacer ces dépendances par d'autres. +Grâce à cela, le code utilisant d'autres objets sera totalement indépendant des changements dans leurs constructeurs. Son API sera plus véridique. Et surtout, il sera trivial de remplacer ces dépendances par d'autres. -Nouveau membre de la famille .[#toc-new-family-member] ------------------------------------------------------- +Nouveau membre de la famille +---------------------------- -L'équipe de développement a décidé de créer un deuxième enregistreur qui écrit dans la base de données. Nous avons donc créé une classe `DatabaseLogger`. Nous avons donc deux classes, `Logger` et `DatabaseLogger`, l'une écrit dans un fichier, l'autre dans une base de données ... le nommage ne vous semble pas étrange ? -Ne serait-il pas préférable de renommer `Logger` en `FileLogger`? Tout à fait. +L'équipe de développement a décidé de créer un deuxième logger, qui écrit dans la base de données. Nous créons donc une classe `DatabaseLogger`. Nous avons donc deux classes, `Logger` et `DatabaseLogger`, l'une écrit dans un fichier, l'autre dans la base de données... ne trouvez-vous pas quelque chose d'étrange dans ce nommage ? Ne serait-il pas préférable de renommer `Logger` en `FileLogger` ? Certainement oui. -Mais faisons-le intelligemment. Nous créons une interface sous le nom original : +Mais nous allons le faire intelligemment. Sous le nom d'origine, nous créons une interface : ```php interface Logger @@ -402,7 +400,7 @@ interface Logger } ``` -... que les deux bûcherons mettront en œuvre : +… que les deux loggers implémenteront : ```php class FileLogger implements Logger @@ -412,17 +410,17 @@ class DatabaseLogger implements Logger // ... ``` -De ce fait, il ne sera pas nécessaire de changer quoi que ce soit dans le reste du code où le logger est utilisé. Par exemple, le constructeur de la classe `NewsletterDistributor` se contentera toujours d'exiger `Logger` comme paramètre. Et il nous appartiendra de choisir l'instance que nous lui transmettrons. +Et grâce à cela, il ne sera pas nécessaire de changer quoi que ce soit dans le reste du code où le logger est utilisé. Par exemple, le constructeur de la classe `NewsletterDistributor` sera toujours satisfait d'exiger `Logger` comme paramètre. Et ce sera à nous de décider quelle instance lui passer. -**C'est pourquoi nous n'ajoutons jamais le suffixe `Interface` ou le préfixe `I` aux noms d'interface,** sans quoi il ne serait pas possible de développer le code de manière aussi agréable. +**C'est pourquoi nous ne donnons jamais aux noms d'interfaces le suffixe `Interface` ou le préfixe `I`.** Sinon, il ne serait pas possible de développer le code aussi joliment. -Houston, nous avons un problème .[#toc-houston-we-have-a-problem] ------------------------------------------------------------------ +Houston, nous avons un problème +------------------------------- -Alors que nous pouvons nous contenter d'une seule instance de l'enregistreur, qu'il soit basé sur un fichier ou une base de données, dans l'ensemble de l'application et qu'il suffit de le passer à chaque fois que quelque chose est enregistré, il en va tout autrement pour la classe `Article`. Nous créons ses instances en fonction des besoins, même plusieurs fois. Comment gérer la dépendance de la base de données dans son constructeur ? +Alors que dans toute l'application, nous pouvons nous contenter d'une seule instance de logger, qu'il soit fichier ou base de données, et simplement la passer partout où quelque chose est loggué, la situation est tout à fait différente dans le cas de la classe `Article`. En effet, nous créons ses instances selon les besoins, parfois plusieurs fois. Comment gérer la dépendance à la base de données dans son constructeur ? -Un exemple peut être un contrôleur qui doit enregistrer un article dans la base de données après avoir soumis un formulaire : +Comme exemple, prenons un contrôleur qui, après soumission d'un formulaire, doit enregistrer l'article dans la base de données : ```php class EditController extends Controller @@ -437,30 +435,30 @@ class EditController extends Controller } ``` -Une solution possible est évidente : passer l'objet base de données au constructeur `EditController` et utiliser `$article = new Article($this->db)`. +Une solution possible s'offre directement : nous nous faisons passer l'objet de la base de données via le constructeur dans `EditController` et utilisons `$article = new Article($this->db)`. -Tout comme dans le cas précédent avec `Logger` et le chemin d'accès au fichier, ce n'est pas la bonne approche. La base de données n'est pas une dépendance de `EditController`, mais de `Article`. Passer la base de données va à l'encontre de la [règle #2 : prenez ce qui vous appartient |#rule #2: take what's yours]. Si le constructeur de la classe `Article` change (un nouveau paramètre est ajouté), vous devrez modifier le code partout où des instances sont créées. Ufff. +Comme dans le cas précédent avec `Logger` et le chemin du fichier, ce n'est pas la bonne approche. La base de données n'est pas une dépendance de `EditController`, mais de `Article`. Passer la base de données va donc à l'encontre de la [##Règle n°2 : prenez ce qui vous appartient]. Si le constructeur de la classe `Article` change (un nouveau paramètre est ajouté), il faudra également modifier le code à tous les endroits où une instance est créée. Ouf. Houston, que suggérez-vous ? -Règle n° 3 : Laissez l'usine s'en occuper .[#toc-rule-3-let-the-factory-handle-it] ----------------------------------------------------------------------------------- +Règle n°3 : laissez faire la factory +------------------------------------ -En éliminant les dépendances cachées et en passant toutes les dépendances en tant qu'arguments, nous avons obtenu des classes plus configurables et plus flexibles. Par conséquent, nous avons besoin de quelque chose d'autre pour créer et configurer ces classes plus flexibles pour nous. Nous l'appellerons "usine". +En supprimant les liens cachés et en passant toutes les dépendances comme arguments, nous avons obtenu des classes plus configurables et flexibles. Et par conséquent, nous avons besoin de quelque chose d'autre pour créer et configurer ces classes plus flexibles. Nous appellerons cela des factories. -La règle de base est la suivante : si une classe a des dépendances, laissez la création de leurs instances à la fabrique. +La règle est la suivante : si une classe a des dépendances, laissez la création de ses instances à une factory. -Les usines sont un remplacement plus intelligent de l'opérateur `new` dans le monde de l'injection de dépendances. +Les factories sont un remplacement plus intelligent de l'opérateur `new` dans le monde de l'injection de dépendances. .[note] -Ne pas confondre avec le modèle de conception *factory method*, qui décrit une manière spécifique d'utiliser les usines et n'est pas lié à ce sujet. +Ne confondez pas avec le patron de conception *factory method*, qui décrit une manière spécifique d'utiliser les factories et n'est pas lié à ce sujet. -Usine .[#toc-factory] ---------------------- +Factory +------- -Une fabrique est une méthode ou une classe qui crée et configure des objets. Nous nommerons la classe produisant `Article` `ArticleFactory` , et elle pourrait ressembler à ceci : +Une factory est une méthode ou une classe qui produit et configure des objets. La classe produisant `Article` s'appellera `ArticleFactory` et pourrait ressembler à ceci : ```php class ArticleFactory @@ -489,7 +487,7 @@ class EditController extends Controller public function formSubmitted($data) { - // laisser l'usine créer un objet + // laissons la factory créer l'objet $article = $this->articleFactory->create(); $article->title = $data->title; $article->content = $data->content; @@ -498,11 +496,11 @@ class EditController extends Controller } ``` -À ce stade, si la signature du constructeur de la classe `Article` change, la seule partie du code qui doit réagir est le `ArticleFactory` lui-même. Tout autre code travaillant avec des objets `Article`, comme `EditController`, ne sera pas affecté. +Si la signature du constructeur de la classe `Article` change à ce moment, la seule partie du code qui doit y réagir est la factory `ArticleFactory` elle-même. Tout autre code qui travaille avec les objets `Article`, comme `EditController`, ne sera en aucun cas affecté. -Vous vous demandez peut-être si nous avons vraiment amélioré les choses. La quantité de code a augmenté et tout cela commence à sembler étrangement compliqué. +Peut-être vous tapez-vous sur le front maintenant, vous demandant si nous avons vraiment amélioré les choses. La quantité de code a augmenté et tout cela commence à paraître suspectement compliqué. -Ne vous inquiétez pas, nous allons bientôt aborder le conteneur Nette DI. Il a plus d'un tour dans son sac, ce qui simplifiera grandement la construction d'applications utilisant l'injection de dépendances. Par exemple, au lieu de la classe `ArticleFactory`, vous n'aurez qu'à [écrire une simple interface |factory]: +Ne vous inquiétez pas, nous arriverons bientôt au conteneur Nette DI. Et il a plusieurs atouts dans sa manche qui simplifieront énormément la construction d'applications utilisant l'injection de dépendances. Par exemple, au lieu de la classe `ArticleFactory`, il suffira [d'écrire une simple interface |factory] : ```php interface ArticleFactory @@ -511,18 +509,18 @@ interface ArticleFactory } ``` -Mais nous prenons de l'avance ; soyez patients :-) +Mais nous anticipons, attendez encore un peu :-) -Résumé .[#toc-summary] ----------------------- +Résumé +------ -Au début de ce chapitre, nous avons promis de vous montrer un processus de conception de code propre. Tout ce qu'il faut, c'est que les classes.. : +Au début de ce chapitre, nous avons promis de montrer une méthode pour concevoir du code propre. Il suffit aux classes de -- [passer les dépendances dont elles ont besoin |#Rule #1: Let It Be Passed to You] -- [à l'inverse, ne pas transmettre ce dont elles n'ont pas directement besoin |#Rule #2: Take What's Yours] -- [et que les objets avec des dépendances soient mieux créés dans des usines |#Rule #3: Let the Factory Handle it] +1) [se faire passer les dépendances dont elles ont besoin |#Règle n 1 : faites-vous passer les choses] +2) [et inversement, ne pas se faire passer ce dont elles n'ont pas directement besoin |#Règle n 2 : prenez ce qui vous appartient] +3) [et que les objets avec dépendances sont mieux fabriqués dans des factories |#Règle n 3 : laissez faire la factory] -À première vue, ces trois règles ne semblent pas avoir de conséquences importantes, mais elles conduisent à une perspective radicalement différente de la conception du code. Le jeu en vaut-il la chandelle ? Les développeurs qui ont abandonné leurs vieilles habitudes et commencé à utiliser systématiquement l'injection de dépendances considèrent cette étape comme un moment crucial de leur vie professionnelle. Elle leur a ouvert le monde des applications claires et faciles à maintenir. +Cela peut ne pas sembler évident au premier abord, mais ces trois règles ont des conséquences considérables. Elles conduisent à une vision radicalement différente de la conception du code. Est-ce que ça en vaut la peine ? Les programmeurs qui ont abandonné leurs anciennes habitudes et ont commencé à utiliser systématiquement l'injection de dépendances considèrent cette étape comme un moment crucial de leur vie professionnelle. Un monde d'applications claires et maintenables s'est ouvert à eux. -Mais que se passe-t-il si le code n'utilise pas systématiquement l'injection de dépendances ? Que se passe-t-il s'il s'appuie sur des méthodes statiques ou des singletons ? Cela pose-t-il des problèmes ? [Oui, cela pose des problèmes, et des problèmes fondamentaux |global-state]. +Mais que se passe-t-il si le code n'utilise pas systématiquement l'injection de dépendances ? Que se passe-t-il s'il est basé sur des méthodes statiques ou des singletons ? Cela pose-t-il des problèmes ? [Oui, et de très importants |global-state]. diff --git a/dependency-injection/fr/nette-container.texy b/dependency-injection/fr/nette-container.texy index 8f8db20f7d..37afd6f97a 100644 --- a/dependency-injection/fr/nette-container.texy +++ b/dependency-injection/fr/nette-container.texy @@ -2,9 +2,9 @@ Conteneur Nette DI ****************** .[perex] -Nette DI est l'une des bibliothèques Nette les plus intéressantes. Elle peut générer et mettre à jour automatiquement des conteneurs DI compilés qui sont extrêmement rapides et étonnamment faciles à configurer. +Nette DI est l'une des bibliothèques les plus intéressantes de Nette. Elle peut générer et mettre à jour automatiquement des conteneurs DI compilés, qui sont extrêmement rapides et incroyablement faciles à configurer. -Les services à créer par un conteneur DI sont généralement définis à l'aide de fichiers de configuration au [format NEON |neon:format]. Le conteneur que nous avons créé manuellement dans la [section précédente |container] serait écrit comme suit : +La forme des services que le conteneur DI doit créer est généralement définie à l'aide de fichiers de configuration au [format NEON|neon:format]. Le conteneur que nous avons créé manuellement dans le [chapitre précédent|container] s'écrirait ainsi : ```neon parameters: @@ -19,16 +19,15 @@ services: - UserController ``` -La notation est vraiment brève. +La notation est vraiment concise. -Toutes les dépendances déclarées dans les constructeurs des classes `ArticleFactory` et `UserController` sont trouvées et transmises par Nette DI elle-même grâce à ce que l'on appelle le [câblage automatique |autowiring], il n'est donc pas nécessaire de spécifier quoi que ce soit dans le fichier de configuration. -Ainsi, même si les paramètres changent, vous n'avez pas besoin de modifier quoi que ce soit dans la configuration. Nette régénérera automatiquement le conteneur. Vous pouvez alors vous concentrer uniquement sur le développement de l'application. +Toutes les dépendances déclarées dans les constructeurs des classes `ArticleFactory` et `UserController` sont découvertes et passées automatiquement par Nette DI grâce à ce qu'on appelle l'[autowiring|autowiring], il n'est donc pas nécessaire de spécifier quoi que ce soit dans le fichier de configuration. Ainsi, même si les paramètres changent, vous n'avez rien à modifier dans la configuration. Le conteneur Nette se régénère automatiquement. Vous pouvez ainsi vous concentrer uniquement sur le développement de l'application. -Si vous voulez passer des dépendances en utilisant des setters, utilisez la section [setup |services#setup] pour le faire. +Si nous voulons passer les dépendances via des setters, nous utilisons la section [setup |services#Setup]. -Nette DI va directement générer le code PHP pour le conteneur. Le résultat est donc un fichier `.php` que vous pouvez ouvrir et étudier. Cela vous permet de voir exactement comment le conteneur fonctionne. Vous pouvez également le déboguer dans l'IDE et le parcourir. Et le plus important : le PHP généré est extrêmement rapide. +Nette DI génère directement le code PHP du conteneur. Le résultat est donc un fichier `.php` que vous pouvez ouvrir et étudier. Grâce à cela, vous voyez exactement comment fonctionne le conteneur. Vous pouvez également le déboguer dans votre IDE et le parcourir pas à pas. Et surtout : le PHP généré est extrêmement rapide. -Nette DI peut également générer du code d'[usine |factory] basé sur l'interface fournie. Par conséquent, au lieu de la classe `ArticleFactory`, il nous suffit de créer une interface dans l'application : +Nette DI peut également générer du code pour les [factories|factory] sur la base d'une interface fournie. Par conséquent, au lieu de la classe `ArticleFactory`, il nous suffira de créer uniquement une interface dans l'application : ```php interface ArticleFactory @@ -37,19 +36,19 @@ interface ArticleFactory } ``` -Vous pouvez trouver l'exemple complet [sur GitHub |https://github.com/nette-examples/di-example-doc]. +Vous trouverez l'exemple complet [sur GitHub|https://github.com/nette-examples/di-example-doc]. -Utilisation en mode autonome .[#toc-standalone-use] ---------------------------------------------------- +Utilisation autonome +-------------------- -L'utilisation de la bibliothèque Nette DI dans une application est très simple. Tout d'abord, nous l'installons avec Composer (parce que le téléchargement de fichiers zip est tellement dépassé) : +Déployer la bibliothèque Nette DI dans une application est très facile. D'abord, nous l'installons avec Composer (car télécharger des zips est tellement dépassé) : ```shell composer require nette/di ``` -Le code suivant crée une instance du conteneur DI selon la configuration stockée dans le fichier `config.neon`: +Le code suivant crée une instance du conteneur DI selon la configuration stockée dans le fichier `config.neon` : ```php $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp'); @@ -59,24 +58,23 @@ $class = $loader->load(function ($compiler) { $container = new $class; ``` -Le conteneur n'est généré qu'une seule fois, son code est écrit dans le cache (le répertoire `__DIR__ . '/temp'` ) et lors des requêtes suivantes, il n'est lu qu'à partir de celui-ci. +Le conteneur n'est généré qu'une seule fois, son code est écrit dans le cache (répertoire `__DIR__ . '/temp'`) et lors des requêtes suivantes, il est simplement chargé à partir de là. -Les méthodes `getService()` ou `getByType()` sont utilisées pour créer et récupérer des services. C'est ainsi que l'on crée l'objet `UserController`: +Pour créer et obtenir des services, on utilise les méthodes `getService()` ou `getByType()`. C'est ainsi que nous créons l'objet `UserController` : ```php -$database = $container->getByType(UserController::class); -$database->query('...'); +$controller = $container->getByType(UserController::class); +$controller->someMethod(); ``` -Pendant le développement, il est utile d'activer le mode auto-refresh, où le conteneur est automatiquement régénéré si une classe ou un fichier de configuration est modifié. Il suffit de fournir `true` comme deuxième argument dans le constructeur de `ContainerLoader`. +Pendant le développement, il est utile d'activer le mode de rafraîchissement automatique, où le conteneur se régénère automatiquement si une classe ou un fichier de configuration est modifié. Il suffit d'indiquer `true` comme deuxième argument dans le constructeur de `ContainerLoader`. ```php $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp', true); ``` -Utilisation avec le cadre Nette .[#toc-using-it-with-the-nette-framework] -------------------------------------------------------------------------- +Utilisation avec le framework Nette +----------------------------------- -Comme nous l'avons montré, l'utilisation de Nette DI n'est pas limitée aux applications écrites dans le Nette Framework, vous pouvez le déployer n'importe où avec seulement 3 lignes de code. -Cependant, si vous développez des applications dans le Nette Framework, la configuration et la création du conteneur sont gérées par [Bootstrap |application:bootstrap#toc-di-container-configuration]. +Comme nous l'avons montré, l'utilisation de Nette DI n'est pas limitée aux applications écrites avec Nette Framework, vous pouvez le déployer n'importe où avec seulement 3 lignes de code. Cependant, si vous développez des applications avec Nette Framework, la configuration et la création du conteneur sont gérées par [Bootstrap |application:bootstrapping#Configuration du Conteneur DI]. diff --git a/dependency-injection/fr/passing-dependencies.texy b/dependency-injection/fr/passing-dependencies.texy index 67f2ea3138..fe05ae4015 100644 --- a/dependency-injection/fr/passing-dependencies.texy +++ b/dependency-injection/fr/passing-dependencies.texy @@ -3,22 +3,22 @@ Passage des dépendances <div class=perex> -Les arguments, ou "dépendances" dans la terminologie DI, peuvent être transmis aux classes de la manière suivante : +Les arguments, ou dans la terminologie DI "dépendances", peuvent être passés aux classes de ces manières principales : * passage par constructeur -* en passant par une méthode (appelée setter) -* en définissant une propriété -* par méthode, annotation ou attribut *injecter*. +* passage par méthode (appelée setter) +* assignation à une variable +* méthode, annotation ou attribut *inject* </div> -Nous allons maintenant illustrer les différentes variantes par des exemples concrets. +Nous allons maintenant montrer les différentes variantes avec des exemples concrets. -Injection de constructeur .[#toc-constructor-injection] -======================================================= +Passage par constructeur +======================== -Les dépendances sont transmises comme arguments au constructeur lors de la création de l'objet : +Les dépendances sont passées au moment de la création de l'objet comme arguments du constructeur : ```php class MyClass @@ -34,9 +34,9 @@ class MyClass $obj = new MyClass($cache); ``` -Cette forme est utile pour les dépendances obligatoires dont la classe a absolument besoin pour fonctionner, car sans elles, l'instance ne peut être créée. +Cette forme convient aux dépendances obligatoires dont la classe a absolument besoin pour fonctionner, car sans elles, l'instance ne pourra pas être créée. -Depuis PHP 8.0, nous pouvons utiliser une forme plus courte de notation qui est fonctionnellement équivalente ([constructor property promotion |https://blog.nette.org/fr/php-8-0-apercu-complet-des-nouveautes#toc-constructor-property-promotion]) : +Depuis PHP 8.0, nous pouvons utiliser une forme d'écriture plus courte ([constructor property promotion |https://blog.nette.org/fr/php-8-0-complete-overview-of-news#toc-constructor-property-promotion]), qui est fonctionnellement équivalente : ```php // PHP 8.0 @@ -49,7 +49,7 @@ class MyClass } ``` -Depuis PHP 8.1, une propriété peut être marquée avec un drapeau `readonly` qui déclare que le contenu de la propriété ne changera pas : +Depuis PHP 8.1, la variable peut être marquée avec l'indicateur `readonly`, qui déclare que le contenu de la variable ne changera plus : ```php // PHP 8.1 @@ -62,13 +62,13 @@ class MyClass } ``` -Le conteneur DI passe les dépendances au constructeur automatiquement en utilisant le [câblage automatique |autowiring]. Les arguments qui ne peuvent pas être passés de cette façon (par exemple, les chaînes de caractères, les nombres, les booléens) [s'écrivent dans la configuration |services#Arguments]. +Le conteneur DI passe automatiquement les dépendances au constructeur via l'[autowiring |autowiring]. Les arguments qui ne peuvent pas être passés de cette manière (par exemple, les chaînes de caractères, les nombres, les booléens) sont [écrits dans la configuration |services#Arguments]. -L'enfer des constructeurs .[#toc-constructor-hell] --------------------------------------------------- +Constructor hell +---------------- -Le terme *constructor hell* fait référence à une situation où un enfant hérite d'une classe parentale dont le constructeur nécessite des dépendances, et où l'enfant nécessite également des dépendances. Il doit également reprendre et transmettre les dépendances du parent : +Le terme *constructor hell* désigne la situation où un enfant hérite d'une classe parente dont le constructeur requiert des dépendances, et en même temps, l'enfant requiert également des dépendances. Il doit alors reprendre et passer également celles du parent : ```php abstract class BaseClass @@ -94,11 +94,11 @@ final class MyClass extends BaseClass } ``` -Le problème se pose lorsque nous voulons modifier le constructeur de la classe `BaseClass`, par exemple lorsqu'une nouvelle dépendance est ajoutée. Il faut alors modifier tous les constructeurs des enfants. Ce qui rend une telle modification infernale. +Le problème survient lorsque nous voulons changer le constructeur de la classe `BaseClass`, par exemple lorsqu'une nouvelle dépendance est ajoutée. Il est alors nécessaire de modifier également tous les constructeurs des enfants. Ce qui transforme une telle modification en enfer. -Comment éviter cela ? La solution est de **prioriser la composition sur l'héritage**. +Comment éviter cela ? La solution est de **préférer la [composition plutôt que l'héritage |faq#Pourquoi la composition est-elle préférée à l héritage]**. -Concevons donc le code différemment. Nous éviterons les classes abstraites `Base*`. Au lieu que `MyClass` obtienne une fonctionnalité en héritant de `BaseClass`, cette fonctionnalité lui sera transmise en tant que dépendance : +Nous concevrons donc le code différemment. Nous éviterons les classes [abstraites |nette:introduction-to-object-oriented-programming#Classes abstraites] `Base*`. Au lieu que `MyClass` obtienne une certaine fonctionnalité en héritant de `BaseClass`, elle se fera passer cette fonctionnalité comme une dépendance : ```php final class SomeFunctionality @@ -125,10 +125,10 @@ final class MyClass ``` -Injection de setter .[#toc-setter-injection] -============================================ +Passage par setter +================== -Les dépendances sont transmises en appelant une méthode qui les stocke dans une propriété privée. La convention de dénomination habituelle pour ces méthodes est de la forme `set*()`, c'est pourquoi elles sont appelées "setters", mais elles peuvent bien sûr être appelées autrement. +Les dépendances sont passées en appelant une méthode qui les stocke dans une variable privée. La convention habituelle pour nommer ces méthodes est la forme `set*()`, c'est pourquoi on les appelle setters, mais elles peuvent bien sûr s'appeler autrement. ```php class MyClass @@ -145,9 +145,9 @@ $obj = new MyClass; $obj->setCache($cache); ``` -Cette méthode est utile pour les dépendances facultatives qui ne sont pas nécessaires au fonctionnement de la classe, car il n'est pas garanti que l'objet les recevra effectivement (c'est-à-dire que l'utilisateur appellera la méthode). +Cette méthode convient aux dépendances facultatives qui ne sont pas nécessaires au fonctionnement de la classe, car il n'est pas garanti que l'objet reçoive réellement la dépendance (c'est-à-dire que l'utilisateur appelle la méthode). -En même temps, cette méthode permet d'appeler le setter à plusieurs reprises pour modifier la dépendance. Si cela n'est pas souhaitable, ajoutez une vérification à la méthode, ou à partir de PHP 8.1, marquez la propriété `$cache` avec le drapeau `readonly`. +En même temps, cette méthode permet d'appeler le setter de manière répétée et de changer ainsi la dépendance. Si cela n'est pas souhaité, nous ajoutons un contrôle dans la méthode, ou à partir de PHP 8.1, nous marquons la propriété `$cache` avec l'indicateur `readonly`. ```php class MyClass @@ -156,7 +156,7 @@ class MyClass public function setCache(Cache $cache): void { - if ($this->cache) { + if (isset($this->cache)) { throw new RuntimeException('The dependency has already been set'); } $this->cache = $cache; @@ -164,21 +164,20 @@ class MyClass } ``` -L'appel au setter est défini dans la configuration du conteneur DI dans la [section setup |services#Setup]. Ici aussi, le passage automatique des dépendances est utilisé par autowiring : +L'appel du setter est défini dans la configuration du conteneur DI dans la [clé setup |services#Setup]. Ici aussi, le passage automatique des dépendances via l'autowiring est utilisé : ```neon services: - - - create: MyClass + - create: MyClass setup: - setCache ``` -Injection de propriétés .[#toc-property-injection] -================================================== +Assignation à une variable +========================== -Les dépendances sont transmises directement à la propriété : +Les dépendances sont passées en écrivant directement dans la variable membre : ```php class MyClass @@ -190,28 +189,27 @@ $obj = new MyClass; $obj->cache = $cache; ``` -Cette méthode est considérée comme inappropriée car la propriété doit être déclarée comme `public`. Par conséquent, nous n'avons aucun contrôle sur le fait que la dépendance passée sera effectivement du type spécifié (c'était vrai avant PHP 7.4) et nous perdons la possibilité de réagir à la dépendance nouvellement assignée avec notre propre code, par exemple pour empêcher des changements ultérieurs. En même temps, la propriété devient une partie de l'interface publique de la classe, ce qui n'est pas forcément souhaitable. +Cette méthode est considérée comme inappropriée car la variable membre doit être déclarée comme `public`. Par conséquent, nous n'avons aucun contrôle sur le fait que la dépendance passée sera réellement du type donné (valable avant PHP 7.4) et nous perdons la possibilité de réagir à la dépendance nouvellement assignée avec notre propre code, par exemple pour empêcher un changement ultérieur. En même temps, la variable devient partie intégrante de l'interface publique de la classe, ce qui peut ne pas être souhaitable. -Le paramétrage de la variable est défini dans la configuration du conteneur DI dans la [section setup |services#Setup]: +L'assignation de la variable est définie dans la configuration du conteneur DI dans la [section setup |services#Setup] : ```neon services: - - - create: MyClass + - create: MyClass setup: - $cache = @\Cache ``` -Injecter .[#toc-inject] -======================= +Inject +====== -Alors que les trois méthodes précédentes sont généralement valables dans tous les langages orientés objet, l'injection par méthode, annotation ou attribut *inject* est spécifique aux présentateurs Nette. Elles font l'objet d'un [chapitre distinct |best-practices:inject-method-attribute]. +Alors que les trois méthodes précédentes sont généralement valables dans tous les langages orientés objet, l'injection par méthode, annotation ou attribut *inject* est spécifique uniquement aux presenters dans Nette. Un [chapitre distinct |best-practices:inject-method-attribute] leur est consacré. -Quelle voie choisir ? .[#toc-which-way-to-choose] -================================================= +Quelle méthode choisir ? +======================== -- le constructeur convient aux dépendances obligatoires dont la classe a besoin pour fonctionner -- le setter, quant à lui, convient aux dépendances optionnelles, ou aux dépendances qui peuvent être modifiées. -- les variables publiques ne sont pas recommandées +- Le constructeur convient aux dépendances obligatoires dont la classe a absolument besoin pour fonctionner. +- Le setter convient au contraire aux dépendances facultatives, ou aux dépendances qu'il est possible de modifier ultérieurement. +- Les variables publiques ne sont pas appropriées. diff --git a/dependency-injection/fr/services.texy b/dependency-injection/fr/services.texy index 95f152a65b..a85d365709 100644 --- a/dependency-injection/fr/services.texy +++ b/dependency-injection/fr/services.texy @@ -1,33 +1,33 @@ -Définitions des services -************************ +Définition des services +*********************** .[perex] -La configuration est l'endroit où nous plaçons les définitions des services personnalisés. Ceci est fait dans la section `services`. +La configuration est l'endroit où nous apprenons au conteneur DI comment assembler les différents services et comment les connecter à d'autres dépendances. Nette fournit une manière très claire et élégante d'y parvenir. -Par exemple, voici comment nous créons un service nommé `database`, qui sera une instance de la classe `PDO`: +La section `services` dans le fichier de configuration au format NEON est l'endroit où nous définissons nos propres services et leurs configurations. Voyons un exemple simple de définition d'un service nommé `database`, qui représente une instance de la classe `PDO` : ```neon services: database: PDO('sqlite::memory:') ``` -Le nommage des services est utilisé pour nous permettre de les [référencer |#Referencing Services]. Si un service n'est pas référencé, il n'est pas nécessaire de le nommer. Nous utilisons donc simplement une puce au lieu d'un nom : +La configuration indiquée aboutira à la méthode factory suivante dans le [conteneur DI|container] : -```neon -services: - - PDO('sqlite::memory:') # service anonyme +```php +public function createServiceDatabase(): PDO +{ + return new PDO('sqlite::memory:'); +} ``` -Une entrée d'une ligne peut être décomposée en plusieurs lignes pour permettre l'ajout de clés supplémentaires, telles que [setup |#setup]. L'alias de la clé `create:` est `factory:`. +Les noms des services nous permettent d'y faire référence dans d'autres parties du fichier de configuration, sous la forme `@nomDuService`. S'il n'est pas nécessaire de nommer le service, nous pouvons simplement utiliser un tiret : ```neon services: - database: - create: PDO('sqlite::memory:') - setup: ... + - PDO('sqlite::memory:') ``` -Nous récupérons ensuite le service dans le conteneur DI en utilisant la méthode `getService()` par nom, ou mieux encore, la méthode `getByType()` par type : +Pour obtenir un service du conteneur DI, nous pouvons utiliser la méthode `getService()` avec le nom du service comme paramètre, ou la méthode `getByType()` avec le type du service : ```php $database = $container->getService('database'); @@ -35,26 +35,28 @@ $database = $container->getByType(PDO::class); ``` -Création d'un service .[#toc-creating-a-service] -================================================ +Création de service +=================== -Le plus souvent, nous créons un service en créant simplement une instance d'une classe : +La plupart du temps, nous créons un service simplement en créant une instance d'une certaine classe. Par exemple : ```neon services: database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -Ce qui va générer une méthode de fabrique dans le [conteneur DI |container]: +Si nous devons étendre la configuration avec d'autres clés, la définition peut être répartie sur plusieurs lignes : -```php -public function createServiceDatabase(): PDO -{ - return new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'secret'); -} +```neon +services: + database: + create: PDO('sqlite::memory:') + setup: ... ``` -Alternativement, une clé `arguments` peut être utilisée pour passer des [arguments |#Arguments]: +La clé `create` a un alias `factory`, les deux variantes sont courantes en pratique. Cependant, nous recommandons d'utiliser `create`. + +Les arguments du constructeur ou de la méthode de création peuvent alternativement être écrits dans la clé `arguments` : ```neon services: @@ -63,294 +65,303 @@ services: arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret] ``` -Une méthode statique peut également créer un service : +Les services ne doivent pas nécessairement être créés par simple instanciation d'une classe, ils peuvent aussi être le résultat d'appels de méthodes statiques ou de méthodes d'autres services : ```neon services: - database: My\Database::create(root, secret) + database: DatabaseFactory::create() + router: @routerFactory::create() ``` -Correspond au code PHP : +Notez que pour la simplicité, `::` est utilisé à la place de `->`, voir [##expressions]. Ces méthodes factory seront générées : ```php public function createServiceDatabase(): PDO { - return My\Database::create('root', 'secret'); + return DatabaseFactory::create(); +} + +public function createServiceRouter(): RouteList +{ + return $this->getService('routerFactory')->create(); } ``` -Une méthode statique `My\Database::create()` est supposée avoir une valeur de retour définie que le conteneur DI doit connaître. S'il ne l'a pas, nous écrivons le type dans la configuration : +Le conteneur DI a besoin de connaître le type du service créé. Si nous créons un service à l'aide d'une méthode qui n'a pas de type de retour spécifié, nous devons explicitement indiquer ce type dans la configuration : ```neon services: database: - create: My\Database::create(root, secret) + create: DatabaseFactory::create() type: PDO ``` -Nette DI vous donne des facilités d'expression extrêmement puissantes pour écrire presque n'importe quoi. Par exemple, pour [faire référence |#Referencing Services] à un autre service et appeler sa méthode. Pour simplifier, on utilise `::` au lieu de `->`. + +Arguments +========= + +Nous passons les arguments au constructeur et aux méthodes d'une manière très similaire à PHP lui-même : ```neon services: - routerFactory: App\Router\Factory - router: @routerFactory::create() + database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -Correspond au code PHP : - -```php -public function createServiceRouterFactory(): App\Router\Factory -{ - return new App\Router\Factory; -} +Pour une meilleure lisibilité, nous pouvons répartir les arguments sur des lignes séparées. Dans ce cas, l'utilisation de virgules est facultative : -public function createServiceRouter(): Router -{ - return $this->getService('routerFactory')->create(); -} +```neon +services: + database: PDO( + 'mysql:host=127.0.0.1;dbname=test' + root + secret + ) ``` -Les appels de méthode peuvent être enchaînés comme en PHP : +Vous pouvez également nommer les arguments et ne pas vous soucier de leur ordre : ```neon services: - foo: FooFactory::build()::get() + database: PDO( + username: root + password: secret + dsn: 'mysql:host=127.0.0.1;dbname=test' + ) ``` -Correspond au code PHP : +Si vous souhaitez omettre certains arguments et utiliser leur valeur par défaut ou injecter un service via l'[autowiring|autowiring], utilisez le trait de soulignement : -```php -public function createServiceFoo() -{ - return FooFactory::build()->get(); -} +```neon +services: + foo: Foo(_, %appDir%) ``` +Comme arguments, on peut passer des services, utiliser des paramètres et bien plus encore, voir [##expressions]. + -Arguments .[#toc-arguments] -=========================== +Setup +===== -Les paramètres nommés peuvent également être utilisés pour transmettre des arguments : +Dans la section `setup`, nous définissons les méthodes qui doivent être appelées lors de la création du service. ```neon services: - database: PDO( - mysql:host=127.0.0.1;dbname=test' # positionnel - username: root # nommé - password: secret # nommé - ) + database: + create: PDO(%dsn%, %user%, %password%) + setup: + - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) ``` -L'utilisation de virgules est facultative lorsque les arguments sont répartis sur plusieurs lignes. +Cela ressemblerait à ceci en PHP : -Bien entendu, nous pouvons également utiliser [d'autres services |#Referencing Services] ou [paramètres |configuration#parameters] comme arguments : +```php +public function createServiceDatabase(): PDO +{ + $service = new PDO('...', '...', '...'); + $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + return $service; +} +``` + +En plus des appels de méthodes, il est également possible de passer des valeurs aux propriétés. L'ajout d'un élément à un tableau est également pris en charge, ce qui doit être écrit entre guillemets pour ne pas entrer en conflit avec la syntaxe NEON : ```neon services: - - Foo(@anotherService, %appDir%) + foo: + create: Foo + setup: + - $value = 123 + - '$onClick[]' = [@bar, clickHandler] ``` -Correspond au code PHP : +Ce qui ressemblerait au code PHP suivant : ```php -public function createService01(): Foo +public function createServiceFoo(): Foo { - return new Foo($this->getService('anotherService'), '...'); + $service = new Foo; + $service->value = 123; + $service->onClick[] = [$this->getService('bar'), 'clickHandler']; + return $service; } ``` -Si le premier argument est [autodirigé |autowiring] et que vous souhaitez spécifier le second, omettez le premier avec `_` character, for example `Foo(_, %appDir%)`. Ou mieux encore, passez uniquement le second argument comme paramètre nommé, par exemple `Foo(path: %appDir%)`. - -Nette DI et le format NEON vous offrent des possibilités d'expression extrêmement puissantes pour écrire presque tout. Ainsi, un argument peut être un objet nouvellement créé, vous pouvez appeler des méthodes statiques, des méthodes d'autres services, ou même des fonctions globales en utilisant une notation spéciale : +Dans le setup, on peut cependant aussi appeler des méthodes statiques ou des méthodes d'autres services. Si vous avez besoin de passer le service actuel comme argument, indiquez-le comme `@self` : ```neon services: - analyser: My\Analyser( - FilesystemIterator(%appDir%) # créer un objet - DateTime::createFromFormat('Y-m-d') # appelle la méthode statique - @anotherService # passage d'un autre service - @http.request::getRemoteAddress() # appel d'une autre méthode de service - ::getenv(NetteMode) # appel d'une fonction globale - ) + foo: + create: Foo + setup: + - My\Helpers::initializeFoo(@self) + - @anotherService::setFoo(@self) ``` -Correspond au code PHP : +Notez que pour la simplicité, `::` est utilisé à la place de `->`, voir [##expressions]. Une telle méthode factory sera générée : ```php -public function createServiceAnalyser(): My\Analyser +public function createServiceFoo(): Foo { - return new My\Analyser( - new FilesystemIterator('...'), - DateTime::createFromFormat('Y-m-d'), - $this->getService('anotherService'), - $this->getService('http.request')->getRemoteAddress(), - getenv('NetteMode') - ); + $service = new Foo; + My\Helpers::initializeFoo($service); + $this->getService('anotherService')->setFoo($service); + return $service; } ``` -Fonctions spéciales .[#toc-special-functions] ---------------------------------------------- - -Vous pouvez également utiliser des fonctions spéciales dans les arguments pour exprimer ou nier des valeurs : +Expressions +=========== -- `not(%arg%)` négation -- `bool(%arg%)` Conversion sans perte en bool -- `int(%arg%)` Conversion sans perte en int -- `float(%arg%)` conversion sans perte en float -- `string(%arg%)` Moulage sans perte vers string +Nette DI nous offre des moyens d'expression extraordinairement riches, grâce auxquels nous pouvons écrire presque n'importe quoi. Dans les fichiers de configuration, nous pouvons ainsi utiliser des [paramètres |configuration#Paramètres] : ```neon -services: - - Foo( - id: int(::getenv('ProjectId')) - productionMode: not(%debugMode%) - ) -``` - -La réécriture sans perte diffère de la réécriture PHP normale, par exemple en utilisant `(int)`, car elle lève une exception pour les valeurs non numériques. +# paramètre +%wwwDir% -Plusieurs services peuvent être passés en argument. Un tableau de tous les services d'un type particulier (c'est-à-dire une classe ou une interface) est créé par la fonction `typed()`. La fonction omettra les services dont le câblage automatique est désactivé, et plusieurs types séparés par une virgule peuvent être spécifiés. +# valeur du paramètre sous la clé +%mailer.user% -```neon -services: - - BarsDependent( typed(Bar) ) +# paramètre à l'intérieur d'une chaîne +'%wwwDir%/images' ``` -Vous pouvez également passer un tableau de services automatiquement en utilisant le [câblage automatique |autowiring#Collection of Services]. - -Un tableau de tous les services avec une certaine [étiquette |#tags] est créé par la fonction `tagged()`. Plusieurs balises séparées par une virgule peuvent être spécifiées. +De plus, créer des objets, appeler des méthodes et des fonctions : ```neon -services: - - LoggersDependent( tagged(logger) ) -``` +# création d'objet +DateTime() +# appel de méthode statique +Collator::create(%locale%) -Services de référencement .[#toc-referencing-services] -====================================================== +# appel de fonction PHP +::getenv(DB_USER) +``` -Les services individuels sont référencés à l'aide du caractère `@` and name, so for example `@database`: +Se référer aux services soit par leur nom, soit par leur type : ```neon -services: - - create: Foo(@database) - setup: - - setCacheStorage(@cache.storage) +# service par nom +@database + +# service par type +@Nette\Database\Connection ``` -Correspond au code PHP : +Utiliser la syntaxe first-class callable : .{data-version:3.2.0} -```php -public function createService01(): Foo -{ - $service = new Foo($this->getService('database')); - $service->setCacheStorage($this->getService('cache.storage')); - return $service; -} +```neon +# création d'un callback, équivalent à [@user, logout] +@user::logout(...) ``` -Même les services anonymes peuvent être référencés à l'aide d'un callback, il suffit de spécifier leur type (classe ou interface) au lieu de leur nom. Cependant, cela n'est généralement pas nécessaire en raison du [câblage automatique |autowiring]. +Utiliser des constantes : ```neon -services: - - create: Foo(@Nette\Database\Connection) # ou @\PDO - setup: - - setCacheStorage(@cache.storage) +# constante de classe +FilesystemIterator::SKIP_DOTS + +# constante globale obtenue avec la fonction PHP constant() +::constant(PHP_VERSION) ``` +Les appels de méthodes peuvent être chaînés comme en PHP. Seulement pour la simplicité, `::` est utilisé à la place de `->` : -Configuration .[#toc-setup] -=========================== +```neon +DateTime()::format('Y-m-d') +# PHP: (new DateTime())->format('Y-m-d') -Dans la section "setup", nous listons les méthodes à appeler lors de la création du service : +@http.request::getUrl()::getHost() +# PHP: $this->getService('http.request')->getUrl()->getHost() +``` + +Vous pouvez utiliser ces expressions n'importe où, lors de la [création de services |#Création de service], dans les [#arguments], dans la section [#setup] ou les [paramètres |configuration#Paramètres] : ```neon +parameters: + ipAddress: @http.request::getRemoteAddress() + services: database: - create: PDO(%dsn%, %user%, %password%) + create: DatabaseFactory::create( @anotherService::getDsn() ) setup: - - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) + - initialize( ::getenv('DB_USER') ) ``` -Correspond au code PHP : -```php -public function createServiceDatabase(): PDO -{ - $service = new PDO('...', '...', '...'); - $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - return $service; -} -``` +Fonctions spéciales +------------------- -Les propriétés peuvent également être définies. L'ajout d'un élément à un tableau est également supporté, et doit être écrit entre guillemets pour ne pas entrer en conflit avec la syntaxe NEON : +Dans les fichiers de configuration, vous pouvez utiliser ces fonctions spéciales : +- `not()` négation de la valeur +- `bool()`, `int()`, `float()`, `string()` conversion sans perte vers le type donné +- `typed()` crée un tableau de tous les services du type spécifié +- `tagged()` crée un tableau de tous les services avec le tag donné ```neon services: - foo: - create: Foo - setup: - - $value = 123 - - '$onClick[]' = [@bar, clickHandler] + - Foo( + id: int(::getenv('ProjectId')) + productionMode: not(%debugMode%) + ) ``` -Correspond au code PHP : +Contrairement à la conversion de type classique en PHP, comme par exemple `(int)`, la conversion sans perte lèvera une exception pour les valeurs non numériques. -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - $service->value = 123; - $service->onClick[] = [$this->getService('bar'), 'clickHandler']; - return $service; -} +La fonction `typed()` crée un tableau de tous les services du type donné (classe ou interface). Elle omet les services dont l'autowiring est désactivé. Il est possible d'indiquer plusieurs types séparés par une virgule. + +```neon +services: + - BarsDependent( typed(Bar) ) ``` -Cependant, les méthodes statiques ou les méthodes d'autres services peuvent également être appelées dans la configuration. Nous leur passons le service réel en tant que `@self`: +Vous pouvez également passer un tableau de services d'un certain type comme argument automatiquement via l'[autowiring |autowiring#Tableau de services]. +La fonction `tagged()` crée ensuite un tableau de tous les services avec un certain tag. Ici aussi, vous pouvez spécifier plusieurs tags séparés par une virgule. ```neon services: - foo: - create: Foo - setup: - - My\Helpers::initializeFoo(@self) - - @anotherService::setFoo(@self) + - LoggersDependent( tagged(logger) ) ``` -Correspond au code PHP : -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - My\Helpers::initializeFoo($service); - $this->getService('anotherService')->setFoo($service); - return $service; -} +Autowiring +========== + +La clé `autowired` permet d'influencer le comportement de l'autowiring pour un service spécifique. Pour plus de détails, voir le [chapitre sur l'autowiring|autowiring]. + +```neon +services: + foo: + create: Foo + autowired: false # le service foo est exclu de l'autowiring ``` -Câblage automatique .[#toc-autowiring] -====================================== +Services Lazy .{data-version:3.2.4} +=================================== -La clé autowired peut être utilisée pour exclure un service de l'autowiring ou pour influencer son comportement. Voir le [chapitre sur le câblage automatique |autowiring] pour plus d'informations. +Le chargement paresseux (lazy loading) est une technique qui reporte la création d'un service jusqu'au moment où il est réellement nécessaire. Dans la configuration globale, il est possible d'[activer la création lazy |configuration#Services paresseux] pour tous les services en même temps. Pour des services individuels, vous pouvez ensuite surcharger ce comportement : ```neon services: foo: create: Foo - autowired: false # foo est retiré de l'autowiring + lazy: false ``` +Lorsqu'un service est défini comme lazy, lors de sa demande depuis le conteneur DI, nous recevons un objet placeholder spécial. Celui-ci ressemble et se comporte comme le service réel, mais l'initialisation réelle (appel du constructeur et du setup) n'a lieu qu'au premier appel de l'une de ses méthodes ou propriétés. + +.[note] +Le chargement paresseux ne peut être utilisé que pour les classes utilisateur, pas pour les classes internes de PHP. Nécessite PHP 8.4 ou plus récent. + -Tags .[#toc-tags] -================= +Tags +==== -Des informations sur les utilisateurs peuvent être ajoutées aux services individuels sous la forme de balises : +Les tags servent à ajouter des informations supplémentaires aux services. Vous pouvez ajouter un ou plusieurs tags à un service : ```neon services: @@ -360,7 +371,7 @@ services: - cached ``` -Les étiquettes peuvent également avoir une valeur : +Les tags peuvent également porter des valeurs : ```neon services: @@ -370,26 +381,26 @@ services: logger: monolog.logger.event ``` -Un tableau de services avec certaines balises peut être passé comme argument en utilisant la fonction `tagged()`. Plusieurs balises séparées par une virgule peuvent également être spécifiées. +Pour obtenir tous les services avec certains tags, vous pouvez utiliser la fonction `tagged()` : ```neon services: - LoggersDependent( tagged(logger) ) ``` -Les noms des services peuvent être obtenus du conteneur DI à l'aide de la méthode `findByTag()`: +Dans le conteneur DI, vous pouvez obtenir les noms de tous les services avec un certain tag en utilisant la méthode `findByTag()` : ```php $names = $container->findByTag('logger'); // $names est un tableau contenant le nom du service et la valeur du tag -// c'est-à-dire ['foo' => 'monolog.logger.event', ...] +// par ex. ['foo' => 'monolog.logger.event', ...] ``` -Mode d'injection .[#toc-inject-mode] -==================================== +Mode Inject +=========== -L'indicateur `inject: true` est utilisé pour activer le passage des dépendances via des variables publiques avec l'annotation [inject |best-practices:inject-method-attribute#Inject Attributes] et les méthodes [inject*() |best-practices:inject-method-attribute#inject Methods]. +À l'aide de l'indicateur `inject: true`, le passage des dépendances via les variables publiques avec l'annotation [inject |best-practices:inject-method-attribute#Attributs Inject] et les méthodes [inject*() |best-practices:inject-method-attribute#Méthodes inject] est activé. ```neon services: @@ -398,13 +409,13 @@ services: inject: true ``` -Par défaut, `inject` n'est activé que pour les présentateurs. +Par défaut, `inject` est activé uniquement pour les presenters. -Modification des services .[#toc-modification-of-services] -========================================================== +Modification des services +========================= -Il existe un certain nombre de services dans le conteneur DI qui ont été ajoutés par l'intégration ou [votre extension |#di-extensions]. Les définitions de ces services peuvent être modifiées dans la configuration. Par exemple, pour le service `application.application`, qui est par défaut un objet `Nette\Application\Application`, nous pouvons changer la classe : +Le conteneur DI contient de nombreux services qui ont été ajoutés via une extension intégrée ou [utilisateur|extensions]. Vous pouvez modifier les définitions de ces services directement dans la configuration. Par exemple, vous pouvez changer la classe du service `application.application`, qui est par défaut `Nette\Application\Application`, en une autre : ```neon services: @@ -413,9 +424,9 @@ services: alteration: true ``` -Le drapeau `alteration` est informatif et indique que nous ne faisons que modifier un service existant. +L'indicateur `alteration` est informatif et indique que nous modifions simplement un service existant. -Nous pouvons également ajouter un service : +Nous pouvons également compléter le setup : ```neon services: @@ -426,7 +437,7 @@ services: - '$onStartup[]' = [@resource, init] ``` -Lorsque nous réécrivons un service, nous pouvons vouloir supprimer les arguments, les éléments de configuration ou les balises d'origine, ce à quoi sert `reset`: +Lors de la réécriture d'un service, nous pouvons vouloir supprimer les arguments d'origine, les éléments de setup ou les tags, ce à quoi sert `reset` : ```neon services: @@ -439,7 +450,7 @@ services: - tags ``` -Un service ajouté par extension peut également être retiré du conteneur : +Si vous souhaitez supprimer un service ajouté par une extension, vous pouvez le faire comme ceci : ```neon services: diff --git a/dependency-injection/hu/@home.texy b/dependency-injection/hu/@home.texy index d53d1b944a..efbfe2814b 100644 --- a/dependency-injection/hu/@home.texy +++ b/dependency-injection/hu/@home.texy @@ -1,24 +1,21 @@ -Függőségi injekció -****************** +Nette DI +******** .[perex] -A Dependency Injection egy olyan tervezési minta, amely alapvetően megváltoztatja a kód és a fejlesztés szemléletét. Megnyitja az utat a tisztán megtervezett és fenntartható alkalmazások világa felé. +A Dependency Injection egy tervezési minta, amely alapvetően megváltoztatja a kódra és a fejlesztésre vonatkozó nézeteit. Megnyitja az utat a tisztán megtervezett és fenntartható alkalmazások világába. - [Mi az a Dependency Injection? |introduction] - [Globális állapot és singletonok |global-state] - [Függőségek átadása |passing-dependencies] -- [Mi az a DI Container? |container] -- [Gyakran ismételt kérdések |faq] - +- [Mi az a DI konténer? |container] +- [Gyakran Ismételt Kérdések|faq] -Nette DI --------- -A `nette/di` csomag egy rendkívül fejlett kompilált DI konténert biztosít a PHP számára. +A `nette/di` csomag egy rendkívül fejlett, fordított DI konténert biztosít PHP-hoz. -- [Nette DI konténer |nette-container] +- [Nette DI Konténer |nette-container] - [Konfiguráció |configuration] -- [Szolgáltatás definíciók |services] +- [Szolgáltatások definiálása |services] - [Autowiring |autowiring] -- [Generált gyárak |factory] -- [Bővítmények létrehozása a Nette DI számára |extensions] +- [Generált factory-k |factory] +- [Bővítmények készítése Nette DI-hez|extensions] diff --git a/dependency-injection/hu/@left-menu.texy b/dependency-injection/hu/@left-menu.texy index 4247a766ec..c82c0c7af5 100644 --- a/dependency-injection/hu/@left-menu.texy +++ b/dependency-injection/hu/@left-menu.texy @@ -1,17 +1,17 @@ -Függőségi injekció -****************** +Dependency Injection +******************** - [Mi az a DI? |introduction] - [Globális állapot és singletonok |global-state] - [Függőségek átadása |passing-dependencies] -- [Mi az a DI Container? |container] -- [Gyakran ismételt kérdések |faq] +- [Mi az a DI konténer? |container] +- [Gyakran Ismételt Kérdések|faq] Nette DI -------- -- [Nette DI konténer |nette-container] +- [Nette DI Konténer |nette-container] - [Konfiguráció |configuration] -- [Szolgáltatás definíciók |services] +- [Szolgáltatások definiálása |services] - [Autowiring |autowiring] -- [Generált gyárak |factory] -- [Bővítmények létrehozása a Nette DI számára |extensions] +- [Generált factory-k |factory] +- [Bővítmények készítése Nette DI-hez|extensions] diff --git a/dependency-injection/hu/@meta.texy b/dependency-injection/hu/@meta.texy new file mode 100644 index 0000000000..c172d1cda5 --- /dev/null +++ b/dependency-injection/hu/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette dokumentáció}} diff --git a/dependency-injection/hu/autowiring.texy b/dependency-injection/hu/autowiring.texy index fd1c0b7e10..fff308f812 100644 --- a/dependency-injection/hu/autowiring.texy +++ b/dependency-injection/hu/autowiring.texy @@ -2,23 +2,23 @@ Autowiring ********** .[perex] -Az autowiring egy nagyszerű funkció, amely automatikusan átadhatja a szolgáltatásokat a konstruktornak és más metódusoknak, így egyáltalán nem kell megírnunk őket. Ezzel rengeteg időt spórolhatunk meg. +Az Autowiring egy nagyszerű funkció, amely automatikusan átadja a szükséges szolgáltatásokat a konstruktornak és más metódusoknak, így egyáltalán nem kell őket megírnunk. Rengeteg időt takarít meg Önnek. -Ez lehetővé teszi, hogy a szolgáltatásdefiníciók írásakor az argumentumok túlnyomó többségét kihagyjuk. Ahelyett: +Ennek köszönhetően a szolgáltatásdefiníciók írásakor a legtöbb argumentumot elhagyhatjuk. Helyette: ```neon services: articles: Model\ArticleRepository(@database, @cache.storage) ``` -Just write: +Elég ennyit írni: ```neon services: articles: Model\ArticleRepository ``` -Az autowiringet a típusok vezérlik, ezért a `ArticleRepository` osztályt a következőképpen kell definiálni: +Az autowiring típusok alapján működik, tehát ahhoz, hogy működjön, az `ArticleRepository` osztályt valahogy így kell definiálni: ```php namespace Model; @@ -30,22 +30,22 @@ class ArticleRepository } ``` -Az autowiring használatához **csak egy szolgáltatásnak** kell lennie minden egyes típushoz a konténerben. Ha több lenne, az autowiring nem tudná, hogy melyiket kell átadni, és kivételt dobna: +Az autowiring használatához minden típushoz **pontosan egy szolgáltatásnak** kell lennie a konténerben. Ha több lenne belőlük, az autowiring nem tudná, melyiket adja át, és kivételt dobna: ```neon services: mainDb: PDO(%dsn%, %user%, %password%) tempDb: PDO('sqlite::memory:') - articles: Model\ArticleRepository # THROWS EXCEPTION, mind a mainDb, mind a tempDb egyezik. + articles: Model\ArticleRepository # KIVÉTELT DOB, a mainDb és a tempDb is megfelel ``` -A megoldás az autowiring megkerülése és a szolgáltatás nevének explicit megadása lenne (pl. `articles: Model\ArticleRepository(@mainDb)`). Kényelmesebb azonban az autowiring [letiltása |#Disabled autowiring] egy szolgáltatásnál, vagy [inkább |#Preferred Autowiring] az első szolgáltatásnál. +A megoldás az lenne, ha vagy megkerülnénk az autowiringot, és explicit módon megadnánk a szolgáltatás nevét (azaz `articles: Model\ArticleRepository(@mainDb)`). De ügyesebb az egyik szolgáltatás autowiringját [kikapcsolni |#Autowiring kikapcsolása], vagy az első szolgáltatást [előnyben részesíteni |#Autowiring preferencia]. -Letiltott autowiring .[#toc-disabled-autowiring] ------------------------------------------------- +Autowiring kikapcsolása +----------------------- -A `autowired: no` opcióval letilthatja a szolgáltatás automatikus bekötését: +Egy szolgáltatás autowiringját kikapcsolhatjuk az `autowired: no` opcióval: ```neon services: @@ -53,28 +53,27 @@ services: tempDb: create: PDO('sqlite::memory:') - autowired: false # eltávolítja a tempDb-t az autowiringből. + autowired: false # a tempDb szolgáltatás ki van zárva az autowiringból - articles: # ezért átadja a mainDb-t a konstruktornak. + articles: Model\ArticleRepository # tehát a konstruktorba a mainDb-t adja át ``` -A `articles` szolgáltatás nem dobja ki azt a kivételt, hogy a konstruktornak átadható két megfelelő `PDO` típusú szolgáltatás (azaz `mainDb` és `tempDb`), mert csak a `mainDb` szolgáltatást látja. +Az `articles` szolgáltatás nem dob kivételt, hogy két megfelelő `PDO` típusú szolgáltatás létezik (azaz `mainDb` és `tempDb`), amelyeket át lehet adni a konstruktorba, mert csak a `mainDb` szolgáltatást látja. .[note] -Az autowiring beállítása a Nette-ben másképp működik, mint a Symfony-ban, ahol a `autowire: false` opció azt mondja ki, hogy az autowiring nem használható a szolgáltatáskonstruktor argumentumaihoz. -A Nette-ben az autowiring mindig használatos, akár a konstruktor, akár bármely más metódus argumentumaira. A `autowired: false` opció azt mondja ki, hogy a szolgáltatáspéldányt nem szabad sehol autowiring használatával átadni. +Az autowiring konfigurációja a Nette-ben másképp működik, mint a Symfony-ban, ahol az `autowire: false` opció azt mondja, hogy ne használja az autowiringot az adott szolgáltatás konstruktorának argumentumaihoz. A Nette-ben az autowiring mindig használatos, akár a konstruktor argumentumaihoz, akár bármely más metódushoz. Az `autowired: false` opció azt mondja, hogy az adott szolgáltatás példányát ne adják át sehova autowiring segítségével. -Előnyben részesített autowiring .[#toc-preferred-autowiring] ------------------------------------------------------------- +Autowiring preferencia +---------------------- -Ha több azonos típusú szolgáltatásunk van, és az egyikben van a `autowired` opció, akkor ez a szolgáltatás lesz az előnyben részesített: +Ha több azonos típusú szolgáltatásunk van, és az egyiknél megadjuk az `autowired` opciót, ez a szolgáltatás preferálttá válik: ```neon services: mainDb: create: PDO(%dsn%, %user%, %password%) - autowired: PDO # előnyben részesíti + autowired: PDO # preferálttá válik tempDb: create: PDO('sqlite::memory:') @@ -82,13 +81,13 @@ services: articles: Model\ArticleRepository ``` -A `articles` szolgáltatás nem dobja ki a kivételt, hogy két egyező `PDO` szolgáltatás van (azaz `mainDb` és `tempDb`), hanem az előnyben részesített szolgáltatást, azaz a `mainDb` szolgáltatást használja. +Az `articles` szolgáltatás nem dob kivételt, hogy két megfelelő `PDO` típusú szolgáltatás létezik (azaz `mainDb` és `tempDb`), hanem a preferált szolgáltatást használja, tehát a `mainDb`-t. -Szolgáltatások gyűjteménye .[#toc-collection-of-services] ---------------------------------------------------------- +Szolgáltatások tömbje +--------------------- -Az autowiring egy adott típusú szolgáltatások tömbjét is átadhatja. Mivel a PHP nem tudja natívan jelölni a tömbelemek típusát, a `array` típus mellett egy phpDoc kommentárt is hozzá kell adni az elem típusával, mint például a `ClassName[]`: +Az autowiring képes átadni egy adott típusú szolgáltatások tömbjét is. Mivel PHP-ban natívan nem lehet megadni a tömb elemeinek típusát, a `array` típus mellett egy phpDoc kommentet is hozzá kell adni az elem típusával `ClassName[]` formában: ```php namespace Model; @@ -103,46 +102,45 @@ class ShipManager } ``` -A DI konténer ekkor automatikusan átadja a megadott típusnak megfelelő szolgáltatások tömbjét. Kihagyja azokat a szolgáltatásokat, amelyeknél az automatikus kapcsolás ki van kapcsolva. +A DI konténer ezután automatikusan átadja az adott típusnak megfelelő szolgáltatások tömbjét. Kihagyja azokat a szolgáltatásokat, amelyeknek ki van kapcsolva az autowiringja. -Ha nem tudja ellenőrizni a phpDoc megjegyzés formáját, akkor közvetlenül a konfigurációban is átadhatja a szolgáltatások tömbjét a következővel [`typed()` |services#Special Functions]. +A kommentben szereplő típus lehet `array<int, Class>` vagy `list<Class>` formájú is. Ha nem tudja befolyásolni a phpDoc komment formáját, átadhatja a szolgáltatások tömbjét közvetlenül a konfigurációban a [`typed()` |services#Speciális függvények] segítségével. -Skaláris argumentumok .[#toc-scalar-arguments] ----------------------------------------------- +Skalár argumentumok +------------------- -Az autowiring csak objektumokat és objektumok tömbjeit adhatja át. A skalár argumentumok (pl. karakterláncok, számok, boolék) [a konfigurációban írhatók |services#Arguments]. -Alternatív megoldás egy olyan [settings-objektum |best-practices:passing-settings-to-presenters] létrehozása, amely objektumként egy skalár értéket (vagy több értéket) foglal magába, amelyet aztán újra át lehet adni az autowiring segítségével. +Az autowiring csak objektumokat és objektumok tömbjeit tudja beilleszteni. A skalár argumentumokat (pl. stringek, számok, logikai értékek) [a konfigurációban írjuk le |services#Argumentumok]. Alternatíva egy [settings-objektum |best-practices:passing-settings-to-presenters] létrehozása, amely a skalár értéket (vagy több értéket) objektum formájába csomagolja, és ezt aztán újra át lehet adni autowiring segítségével. ```php class MySettings { public function __construct( - // readonly a PHP 8.1 óta használható. + // a readonly PHP 8.1-től használható public readonly bool $value, ) {} } ``` -Egy szolgáltatást úgy hozunk létre, hogy hozzáadjuk a konfigurációhoz: +Szolgáltatást hozhat létre belőle a konfigurációhoz való hozzáadással: ```neon services: - MySettings('any value') ``` -Ezt követően minden osztály automatikus bekötés útján fogja kérni. +Ezután minden osztály autowiring segítségével kérheti azt. -Az autowiring szűkítése .[#toc-narrowing-of-autowiring] -------------------------------------------------------- +Autowiring szűkítése +-------------------- -Az egyes szolgáltatások esetében az autowiring szűkíthető bizonyos osztályokra vagy interfészekre. +Az egyes szolgáltatások autowiringját le lehet szűkíteni csak bizonyos osztályokra vagy interfészekre. -Normális esetben az autowiring minden olyan metódusparaméterhez átadja a szolgáltatást, amelynek típusának a szolgáltatás megfelel. A szűkítés azt jelenti, hogy megadjuk azokat a feltételeket, amelyeknek a metódusparaméterekhez megadott típusoknak meg kell felelniük ahhoz, hogy a szolgáltatás átadásra kerüljön hozzájuk. +Normális esetben az autowiring átadja a szolgáltatást minden olyan metódusparaméternek, amelynek típusa megfelel a szolgáltatásnak. A szűkítés azt jelenti, hogy feltételeket szabunk, amelyeknek a metódusparamétereknél megadott típusoknak meg kell felelniük ahhoz, hogy a szolgáltatást átadják nekik. -Vegyünk egy példát: +Nézzünk egy példát: ```php class ParentClass @@ -164,42 +162,42 @@ class ChildDependent } ``` -Ha az összeset szolgáltatásként regisztrálnánk, az autowiring nem sikerülne: +Ha mindegyiket szolgáltatásként regisztrálnánk, az autowiring meghiúsulna: ```neon services: parent: ParentClass child: ChildClass - parentDep: # THROWS EXCEPTION, mind a szülő, mind a gyermek egyezik - childDep: ChildDependent # átadja a 'child' szolgáltatást a konstruktornak. + parentDep: ParentDependent # KIVÉTELT DOB, a parent és a child szolgáltatás is megfelel + childDep: ChildDependent # az autowiring a child szolgáltatást adja át a konstruktorba ``` -A `parentDep` szolgáltatás a `Multiple services of type ParentClass found: parent, child` kivételt dobja, mert a `parent` és a `child` is belefér a konstruktorába, és az autowiring nem tudja eldönteni, hogy melyiket válassza. +A `parentDep` szolgáltatás `Multiple services of type ParentClass found: parent, child` kivételt dob, mert a konstruktorába mind a `parent`, mind a `child` szolgáltatás illeszkedik, és az autowiring nem tudja eldönteni, melyiket válassza. -A `child` szolgáltatás esetében ezért az autowiringet a `ChildClass` szolgáltatásra tudjuk leszűkíteni: +Ezért a `child` szolgáltatásnál leszűkíthetjük az autowiringját a `ChildClass` típusra: ```neon services: parent: ParentClass child: create: ChildClass - autowired: ChildClass # alternatíva: 'autowired: self' + autowired: ChildClass # 'autowired: self'-et is lehet írni - parentDep: ParentDependent # THROWS EXCEPTION, a 'child' nem lehet autowired - childDep: ChildDependent # átadja a 'child' szolgáltatást a konstruktornak. + parentDep: ParentDependent # az autowiring a parent szolgáltatást adja át a konstruktorba + childDep: ChildDependent # az autowiring a child szolgáltatást adja át a konstruktorba ``` -A `parentDep` szolgáltatást most átadjuk a `parentDep` szolgáltatás konstruktorának, mivel ez az egyetlen megfelelő objektum. A `child` szolgáltatást már nem adjuk át autowiringgel. Igen, a `child` szolgáltatás még mindig a `ParentClass` típusú, de a paramétertípusra megadott szűkítő feltétel már nem érvényes, azaz már nem igaz, hogy a `ParentClass` *a `ChildClass` szupertípusa*. +Most a `parentDep` szolgáltatás konstruktorába a `parent` szolgáltatás kerül átadásra, mert most ez az egyetlen megfelelő objektum. A `child` szolgáltatást az autowiring már nem adja át oda. Igen, a `child` szolgáltatás továbbra is `ParentClass` típusú, de már nem teljesül a paraméter típusára vonatkozó szűkítő feltétel, azaz nem igaz, hogy a `ParentClass` *felülírja* a `ChildClass`-t. -A `child` esetében a `autowired: ChildClass` -t írhatnánk `autowired: self` -nek, mivel a `self` az aktuális szolgáltatás típusát jelenti. +A `child` szolgáltatásnál az `autowired: ChildClass`-t `autowired: self`-ként is lehetne írni, mivel a `self` az aktuális szolgáltatás osztályának helyettesítő jelölése. -A `autowired` kulcs több osztályt és interfészt is tartalmazhat tömbként: +Az `autowired` kulcsban több osztályt vagy interfészt is meg lehet adni tömbként: ```neon autowired: [BarClass, FooInterface] ``` -Próbáljuk meg hozzáadni interfészeket a példához: +Próbáljuk meg a példát kiegészíteni egy interfésszel: ```php interface FooInterface @@ -239,13 +237,13 @@ class ChildDependent } ``` -Ha nem korlátozzuk a `child` szolgáltatást, akkor az összes `FooDependent`, `BarDependent`, `ParentDependent` és `ChildDependent` osztály konstruktorába befér, és az autowiring átadja azt. +Ha a `child` szolgáltatást semmilyen módon nem korlátozzuk, akkor illeszkedni fog az összes `FooDependent`, `BarDependent`, `ParentDependent` és `ChildDependent` osztály konstruktorába, és az autowiring oda fogja átadni. -Ha azonban az autowiringjét a `ChildClass` -ra szűkítjük a `autowired: ChildClass` (vagy a `self`) segítségével, az autowiring csak a `ChildDependent` konstruktorába passzolja, mert az `ChildClass` típusú argumentumot igényel, és a `ChildClass` *típusú* `ChildClass`. A többi paraméterhez megadott típus nem a `ChildClass` szuperhalmaza, így a szolgáltatás nem kerül átadásra. +Ha azonban az autowiringját leszűkítjük a `ChildClass`-ra az `autowired: ChildClass` (vagy `self`) segítségével, az autowiring csak a `ChildDependent` konstruktorába adja át, mert az `ChildClass` típusú argumentumot igényel, és igaz, hogy a `ChildClass` *típusa* `ChildClass`. A többi paraméternél megadott további típusok egyike sem felülírja a `ChildClass`-t, így a szolgáltatás nem kerül átadásra. -Ha a `ParentClass` -ra korlátozzuk a `autowired: ParentClass` segítségével, akkor az autowiring ismét átadja a `ChildDependent` konstruktornak (mivel a szükséges típus `ChildClass` a `ParentClass` szuperszettje ) és a `ParentDependent` konstruktornak is, mivel a szükséges típus `ParentClass` szintén megfelel. +Ha a `ParentClass`-ra korlátozzuk az `autowired: ParentClass` segítségével, az autowiring ismét átadja a `ChildDependent` konstruktorába (mert a szükséges `ChildClass` felülírja a `ParentClass`-t), és újonnan a `ParentDependent` konstruktorába is, mert a szükséges `ParentClass` típus szintén megfelelő. -Ha a `FooInterface`-ra korlátozzuk, akkor is autowire-elni fog a `ParentDependent` (a szükséges típus `ParentClass` a `FooInterface` szupertípusa ) és a `ChildDependent`, de ezen kívül a `FooDependent` konstruktornak is, de a `BarDependent`-nak nem, mivel a `BarInterface` nem szupertípusa a `FooInterface`-nak. +Ha a `FooInterface`-re korlátozzuk, akkor továbbra is autowire-olva lesz a `ParentDependent`-be (a szükséges `ParentClass` felülírja a `FooInterface`-t) és a `ChildDependent`-be, de ráadásul a `FooDependent` konstruktorába is, viszont nem a `BarDependent`-be, mert a `BarInterface` nem felülírja a `FooInterface`-t. ```neon services: @@ -253,8 +251,8 @@ services: create: ChildClass autowired: FooInterface - fooDep: FooDependent # átadja a szolgáltatás gyermekét a konstruktornak - barDep: BarDependent # THROWS EXCEPTION, nincs szolgáltatás átadása. - parentDep: ParentDependent # átadja a szolgáltatás gyermekét a konstruktornak. - childDep: ChildDependent # átadja a szolgáltatás gyermekét a konstruktornak. + fooDep: FooDependent # az autowiring a child-ot adja át a konstruktorba + barDep: BarDependent # KIVÉTELT DOB, egyetlen szolgáltatás sem felel meg + parentDep: ParentDependent # az autowiring a child-ot adja át a konstruktorba + childDep: ChildDependent # az autowiring a child-ot adja át a konstruktorba ``` diff --git a/dependency-injection/hu/configuration.texy b/dependency-injection/hu/configuration.texy index 1563ee5bf8..a4f889fa85 100644 --- a/dependency-injection/hu/configuration.texy +++ b/dependency-injection/hu/configuration.texy @@ -1,32 +1,33 @@ -DI konténer konfigurálása -************************* +DI konténer konfigurációja +************************** .[perex] -A Nette DI konténer konfigurációs lehetőségeinek áttekintése. +A Nette DI konténer konfigurációs opcióinak áttekintése. Konfigurációs fájl ================== -A Nette DI konténer könnyen vezérelhető a konfigurációs fájlok segítségével. Ezeket általában [NEON formátumban |neon:format] írják. A szerkesztéshez [olyan szerkesztőket |best-practices:editors-and-tools#ide-editor] ajánlunk használni [, amelyek támogatják |best-practices:editors-and-tools#ide-editor] ezt a formátumot. +A Nette DI konténer könnyen vezérelhető konfigurációs fájlok segítségével. Ezek általában [NEON formátumban|neon:format] íródnak. A szerkesztéshez [támogatással rendelkező szerkesztőket |best-practices:editors-and-tools#IDE szerkesztő] ajánlunk ehhez a formátumhoz. <pre> -"decorator .[prism-token prism-atrule]":[#Decorator]: "Díszítő: .[prism-token prism-comment]"<br> -"di .[prism-token prism-atrule]":[#DI]: "DI Container .[prism-token prism-comment]"<br> -"extensions .[prism-token prism-atrule]":[#Extensions]: "További DI-bővítmények telepítése .[prism-token prism-comment]"<br> -"includes .[prism-token prism-atrule]":[#Including files]: "Beleértve a fájlokat .[prism-token prism-comment]"<br> -"parameters .[prism-token prism-atrule]":[#Parameters]: "Paraméterek .[prism-token prism-comment]"<br> -"search .[prism-token prism-atrule]":[#Search]: "Automatikus szolgáltatás regisztráció .[prism-token prism-comment]"<br> +"decorator .[prism-token prism-atrule]":[#Decorator]: "Dekorátor .[prism-token prism-comment]"<br> +"di .[prism-token prism-atrule]":[#DI]: "DI konténer .[prism-token prism-comment]"<br> +"extensions .[prism-token prism-atrule]":[#Kiterjesztések]: "További DI kiterjesztések telepítése .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[#Fájlok beillesztése]: "Fájlok beillesztése .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[#Paraméterek]: "Paraméterek .[prism-token prism-comment]"<br> +"search .[prism-token prism-atrule]":[#Search]: "Szolgáltatások automatikus regisztrálása .[prism-token prism-comment]"<br> "services .[prism-token prism-atrule]":[services]: "Szolgáltatások .[prism-token prism-comment]" </pre> -A `%`, you must escape it by doubling it to `%%` karaktert tartalmazó karakterlánc írása. .[note] +.[note] +Ha `%` karaktert tartalmazó stringet szeretne írni, duplázással kell escapelni `%%`-ra. -Paraméterek .[#toc-parameters] -============================== +Paraméterek +=========== -Meghatározhat olyan paramétereket, amelyeket aztán a szolgáltatásdefiníciók részeként használhat. Ez segíthet elkülöníteni azokat az értékeket, amelyeket rendszeresebben szeretne módosítani. +A konfigurációban definiálhat paramétereket, amelyeket aztán a szolgáltatásdefiníciók részeként használhat. Ezzel áttekinthetőbbé teheti a konfigurációt, vagy egységesítheti és kiemelheti azokat az értékeket, amelyek változni fognak. ```neon parameters: @@ -35,9 +36,9 @@ parameters: password: secret ``` -A `foo` paraméterre a `%foo%` címen keresztül bármely konfigurációs fájlban hivatkozhat. Használhatók a stringeken belül is, mint például a `'%wwwDir%/images'`. +A `dsn` paraméterre bárhol a konfigurációban `%dsn%` írással hivatkozhatunk. A paramétereket stringeken belül is használhatjuk, mint például `'%wwwDir%/images'`. -A paramétereknek nem csak karakterláncoknak kell lenniük, lehetnek tömbértékek is: +A paraméterek nem csak stringek vagy számok lehetnek, tartalmazhatnak tömböket is: ```neon parameters: @@ -48,32 +49,32 @@ parameters: languages: [cs, en, de] ``` -Egyetlen kulcsra hivatkozhat a `%mailer.user%`. +Egy konkrét kulcsra `%mailer.user%`-ként hivatkozhatunk. -Ha a kódodban, például az osztályodban szükséged van bármely paraméter értékére, akkor add át azt ennek az osztálynak. Például a konstruktorban. Nincs olyan globális konfigurációs objektum, amelyet az osztályok lekérdezhetnének a paraméterértékekért. Ez ellenkezne a függőségi injektálás elvével. +Ha a kódjában, például egy osztályban, meg kell tudnia bármely paraméter értékét, adja át azt ennek az osztálynak. Például a konstruktorban. Nincs globális objektum, amely a konfigurációt képviselné, és amelytől az osztályok lekérdeznék a paraméterértékeket. Ez megsértené a dependency injection elvét. -Szolgáltatások .[#toc-services] -=============================== +Szolgáltatások +============== -Lásd a [külön fejezetet |services]. +Lásd a [külön fejezetben|services]. -Díszítő .[#toc-decorator] -========================= +Decorator +========= -Hogyan lehet egy bizonyos típusú összes szolgáltatást tömegesen szerkeszteni? Meg kell hívni egy bizonyos metódust egy adott közös őstől öröklődő összes bemutatóhoz? Erre szolgál a dekorátor. +Hogyan lehet tömegesen módosítani egy adott típusú összes szolgáltatást? Például meghívni egy bizonyos metódust minden olyan presenter esetén, amely egy konkrét közös őstől öröklődik? Erre való a decorator. ```neon decorator: - # minden olyan szolgáltatáshoz, amely ennek az osztálynak vagy interfésznek a példánya. - App\Presenters\BasePresenter: + # minden olyan szolgáltatásnál, amely ennek az osztálynak vagy interfésznek a példánya + App\Presentation\BasePresenter: setup: - - # hívja meg ezt a metódust - - $absoluteUrls = true # és állítsuk be a változót + - setProjectId(10) # hívd meg ezt a metódust + - $absoluteUrls = true # és állítsd be a változót ``` -A dekorátor használható [címkék |services#Tags] beállítására vagy az [injektálási mód |services#Inject Mode] bekapcsolására is. +A decorator használható [tagekkel |services#Tagek] beállítására vagy az [inject |services#Inject mód] mód bekapcsolására is. ```neon decorator: @@ -90,63 +91,75 @@ A DI konténer technikai beállításai. ```neon di: - # mutatja a DIC-et a Tracy Barban? - debugger: ... # (bool) alapértelmezés szerint true + # megjeleníteni a DIC-t a Tracy Bar-ban? + debugger: ... # (bool) alapértelmezett true - # olyan paramétertípusok, amelyeket soha nem kapcsolsz be automatikusan + # soha nem autowire-olandó paramétertípusok excluded: ... # (string[]) - # az osztály, amelytől a DI konténer örököl. - parentClass: ... # (string) alapértelmezett értéke Nette\DI\Container + # engedélyezni a szolgáltatások lazy létrehozását? + lazy: ... # (bool) alapértelmezett false + + # osztály, amelytől a DI konténer öröklődik + parentClass: ... # (string) alapértelmezett Nette\DI\Container ``` -Metaadatok exportálása .[#toc-metadata-export] ----------------------------------------------- +Lazy szolgáltatások .{data-version:3.2.4} +----------------------------------------- + +A `lazy: true` beállítás aktiválja a szolgáltatások lazy (késleltetett) létrehozását. Ez azt jelenti, hogy a szolgáltatások nem jönnek létre ténylegesen abban a pillanatban, amikor lekérjük őket a DI konténerből, hanem csak az első használatuk pillanatában. Ez gyorsíthatja az alkalmazás indítását és csökkentheti a memóriaterhelést, mivel csak azok a szolgáltatások jönnek létre, amelyekre az adott kérésben valóban szükség van. + +Egy konkrét szolgáltatásnál a lazy létrehozást [módosítani |services#Lazy szolgáltatások] lehet. + +.[note] +A lazy objektumok csak felhasználói osztályokhoz használhatók, nem belső PHP osztályokhoz. PHP 8.4 vagy újabb verziót igényel. + -A DI konténer osztály is sok metaadatot tartalmaz. Ezt csökkentheti a metaadatok exportjának csökkentésével. +Metaadatok exportálása +---------------------- + +A DI konténer osztálya sok metaadatot is tartalmaz. Csökkentheti a méretét azáltal, hogy redukálja a metaadatok exportálását. ```neon di: export: - # paraméterek exportálása? - parameters: false # (bool) alapértelmezett értéke true + # exportálni a paramétereket? + parameters: false # (bool) alapértelmezett true - # exportálni a címkéket és melyeket? - tags: # (string[]|bool) az alapértelmezett az all + # exportálni a tageket és melyeket? + tags: # (string[]|bool) alapértelmezés szerint mindet - event.subscriber - # exportálja az autowiring adatait és melyiket? - types: # (string[]|bool) az alapértelmezett az all + # exportálni az autowiring adatokat és melyeket? + types: # (string[]|bool) alapértelmezés szerint mindet - Nette\Database\Connection - Symfony\Component\Console\Application ``` -Ha nem használja a `$container->parameters` tömböt, kikapcsolhatja a paraméterek exportálását. Továbbá csak azokat a címkéket exportálhatja, amelyeken keresztül a `$container->findByTag(...)` módszerrel szolgáltatásokat kap. -Ha egyáltalán nem hívja meg a módszert, akkor a `false` segítségével teljesen letilthatja a címkék exportálását. +Ha nem használja a `$container->getParameters()` tömböt, kikapcsolhatja a paraméterek exportálását. Továbbá exportálhatja csak azokat a tageket, amelyeken keresztül szolgáltatásokat szerez a `$container->findByTag(...)` metódussal. Ha egyáltalán nem hívja meg a metódust, teljesen kikapcsolhatja a tagek exportálását `false`-szal. -Jelentősen csökkentheti az [autowiring |autowiring] metaadatait, ha a `$container->getByType()` metódus paramétereként megadja a használt osztályokat. -És ismét, ha egyáltalán nem hívja meg a metódust (vagy csak az [application:bootstrap-ben |application:bootstrap] a `Nette\Application\Application`), akkor a `false` segítségével teljesen letilthatja az exportot. +Jelentősen redukálhatja az [autowiring |autowiring] metaadatait azáltal, hogy megadja azokat az osztályokat, amelyeket a `$container->getByType()` metódus paramétereként használ. És ismét, ha egyáltalán nem hívja meg a metódust (illetve csak a [bootstrapban|application:bootstrapping] a `Nette\Application\Application` megszerzéséhez), teljesen kikapcsolhatja az exportálást `false`-szal. -Bővítések .[#toc-extensions] -============================ +Kiterjesztések +============== -Más DI-bővítmények regisztrálása. Így például a `Dibi\Bridges\Nette\DibiExtension22` DI-bővítményt a `dibi` név alatt adjuk hozzá a név alatt: +További DI kiterjesztések regisztrálása. Ezzel a módszerrel hozzáadjuk például a `Dibi\Bridges\Nette\DibiExtension22` DI kiterjesztést `dibi` néven. ```neon extensions: dibi: Dibi\Bridges\Nette\DibiExtension22 ``` -Ezután konfiguráljuk a szintén `dibi` nevű szekciójában: +Ezután a `dibi` szekcióban konfiguráljuk: ```neon dibi: host: localhost ``` -Hozzáadhatunk egy bővítmény osztályt is paraméterekkel: +Kiterjesztésként hozzá lehet adni egy osztályt is, amelynek paraméterei vannak: ```neon extensions: @@ -154,10 +167,10 @@ extensions: ``` -Beleértve a fájlokat .[#toc-including-files] -============================================ +Fájlok beillesztése +=================== -További konfigurációs fájlokat lehet beilleszteni a `includes` szakaszba: +További konfigurációs fájlokat illeszthetünk be az `includes` szekcióban: ```neon includes: @@ -166,7 +179,7 @@ includes: - presenters.neon ``` -A `parameters.php` név nem elírás, a konfiguráció egy PHP-fájlba is beírható, amely azt tömbként adja vissza: +A `parameters.php` név nem elírás, a konfiguráció PHP fájlban is leírható, amely tömbként adja vissza: ```php <?php @@ -179,32 +192,27 @@ return [ ]; ``` -Ha a konfigurációs fájlokban azonos kulcsú elemek jelennek meg, akkor azok [felülíródnak, vagy |#Merging] tömbök esetén [összevonásra |#Merging] kerülnek. A később bevont fájlnak magasabb prioritása van, mint az előzőnek. Az a fájl, amelyben a `includes` szakasz szerepel, magasabb prioritással rendelkezik, mint a benne foglalt fájlok. +Ha a konfigurációs fájlokban azonos kulcsokkal rendelkező elemek jelennek meg, felülíródnak, vagy [tömbök esetén egyesítve |#Összefésülés] lesznek. A később beillesztett fájl magasabb prioritású, mint az előző. Az a fájl, amelyben az `includes` szekció szerepel, magasabb prioritású, mint a benne beillesztett fájlok. -Keresés .[#toc-search] -====================== +Search +====== -A szolgáltatások automatikus hozzáadása a DI konténerhez rendkívül kellemes munkát tesz lehetővé. A Nette automatikusan hozzáadja az előadókat a konténerhez, de bármilyen más osztályokat is könnyen hozzáadhatunk. +A szolgáltatások automatikus hozzáadása a DI konténerhez rendkívül megkönnyíti a munkát. A Nette automatikusan hozzáadja a presentereket a konténerhez, de könnyen hozzáadhat bármilyen más osztályt is. -Csak adjuk meg, hogy mely könyvtárakban (és alkönyvtárakban) kell keresni az osztályokat: +Csak meg kell adni, mely könyvtárakban (és alkönyvtárakban) keresse az osztályokat: ```neon search: - # te magad választod ki a szekciók nevét - myForms: - in: %appDir%/Forms - - model: - in: %appDir%/Model + - in: %appDir%/Forms + - in: %appDir%/Model ``` -Általában azonban nem akarjuk az összes osztályt és interfészt felvenni, így szűrhetjük őket: +Általában azonban nem akarjuk hozzáadni az összes osztályt és interfészt, ezért szűrhetjük őket: ```neon search: - myForms: - in: %appDir%/Forms + - in: %appDir%/Forms # szűrés fájlnév alapján (string|string[]) files: @@ -215,48 +223,49 @@ search: - *Factory ``` -Vagy kiválaszthatjuk azokat az osztályokat, amelyek a következő osztályok közül legalább egyet örökölnek vagy implementálnak: +Vagy kiválaszthatunk olyan osztályokat, amelyek legalább egyet örökölnek vagy implementálnak a megadott osztályok közül: ```neon search: - myForms: + - in: %appDir% extends: - App\*Form implements: - App\*FormInterface ``` -Meghatározhatunk negatív szabályokat is, azaz osztálynév maszkokat vagy ősöket, és ha ezek megfelelnek, a szolgáltatás nem kerül hozzá a DI konténerhez: +Definiálhatunk kizáró szabályokat is, azaz osztálynév maszkokat vagy örökölt ősöket, amelyek ha megfelelnek, a szolgáltatás nem kerül hozzáadásra a DI konténerhez: ```neon search: - myForms: + - in: %appDir% exclude: + files: ... classes: ... extends: ... implements: ... ``` -A hozzáadott szolgáltatásokhoz címkéket lehet beállítani: +Minden szolgáltatáshoz be lehet állítani tageket: ```neon search: - myForms: + - in: %appDir% tags: ... ``` -Összevonás .[#toc-merging] -========================== +Összefésülés +============ -Ha azonos kulcsú elemek több konfigurációs fájlban is megjelennek, akkor azok felülíródnak, vagy tömbök esetén egyesülnek. A később felvett fájlnak nagyobb prioritása van. +Ha több konfigurációs fájlban azonos kulcsokkal rendelkező elemek jelennek meg, felülíródnak, vagy tömbök esetén összefésülődnek. A később beillesztett fájl magasabb prioritású, mint az előző. <table class=table> <tr> <th width=33%>config1.neon</th> <th width=33%>config2.neon</th> - <th>result</th> + <th>eredmény</th> </tr> <tr> <td> @@ -283,11 +292,11 @@ items: </tr> </table> -Egy adott tömb összevonásának megakadályozásához használjon felkiáltójelet a tömb neve után: +Tömbök esetén megakadályozható az összefésülés egy felkiáltójel hozzáadásával a kulcs neve után: <table class=table> <tr> - <th width=33%>neon</th> + <th width=33%>config1.neon</th> <th width=33%>config2.neon</th> <th>eredmény</th> </tr> @@ -313,3 +322,5 @@ items: </td> </tr> </table> + +{{maintitle: Dependency Injection Konfiguráció}} diff --git a/dependency-injection/hu/container.texy b/dependency-injection/hu/container.texy index 8ac6651543..d20eb30da4 100644 --- a/dependency-injection/hu/container.texy +++ b/dependency-injection/hu/container.texy @@ -2,15 +2,15 @@ Mi az a DI konténer? ******************** .[perex] -A függőségi injektálási konténer (DIC) egy olyan osztály, amely képes objektumok példányosítására és konfigurálására. +A Dependency injection konténer (DIC) egy olyan osztály, amely képes objektumokat példányosítani és konfigurálni. -Meglepő lehet, de sok esetben nincs szükség függőségi injektáló konténerre ahhoz, hogy kihasználjuk a függőségi injektálás (röviden DI) előnyeit. Végül is, még az [előző fejezetben |introduction] is mutattunk konkrét példákat a DI-re, és nem volt szükség konténerre. +Talán meglepő, de sok esetben nincs szüksége dependency injection konténerre ahhoz, hogy kihasználja a dependency injection (röviden DI) előnyeit. Hiszen már a [bevezető fejezetben|introduction] is konkrét példákon keresztül mutattuk be a DI-t, és nem volt szükség semmilyen konténerre. -Ha azonban nagyszámú különböző objektumot kell kezelnünk sok függőséggel, akkor egy függőségi injektálási konténer igazán hasznos lesz. Ami talán a keretrendszerre épülő webes alkalmazások esetében áll fenn. +Ha azonban nagyszámú, sok függőséggel rendelkező különböző objektumot kell kezelnie, a dependency injection konténer valóban hasznos lesz. Ez például a keretrendszerre épülő webalkalmazások esetében igaz. -Az előző fejezetben bemutattuk a `Article` és a `UserController` osztályokat. Mindkettőnek van néhány függősége, nevezetesen az adatbázis és a gyár `ArticleFactory`. Ezekhez az osztályokhoz pedig most létrehozunk egy konténert. Természetesen egy ilyen egyszerű példánál nincs értelme konténernek. De létrehozunk egyet, hogy megmutassuk, hogyan néz ki és hogyan működik. +Az előző fejezetben bemutattuk az `Article` és `UserController` osztályokat. Mindkettőnek vannak bizonyos függőségei, nevezetesen az adatbázis és az `ArticleFactory` factory. És ezekhez az osztályokhoz most létrehozunk egy konténert. Természetesen egy ilyen egyszerű példához nincs értelme konténert használni. De létrehozzuk, hogy megmutassuk, hogyan néz ki és működik. -Íme egy egyszerű, keményen kódolt konténer a fenti példához: +Itt van egy egyszerű hardcoded konténer a megadott példához: ```php class Container @@ -39,9 +39,9 @@ $container = new Container; $controller = $container->createUserController(); ``` -Nem kell többé tudnunk semmit arról, hogyan hozzuk létre, vagy mik a függőségei; a konténer mindezt tudja. A függőségeket a konténer automatikusan injektálja. Ez az ereje. +Csak megkérdezzük a konténert az objektumról, és már nem kell tudnunk semmit arról, hogyan kell létrehozni, és milyen függőségei vannak; mindezt a konténer tudja. A függőségeket a konténer automatikusan injektálja. Ebben rejlik az ereje. -Eddig a konténer mindent keményen kódolt. Ezért megtesszük a következő lépést, és paramétereket adunk hozzá, hogy a konténer valóban hasznos legyen: +A konténernek eddig minden adata fixen be van írva. Tegyünk tehát egy újabb lépést, és adjunk hozzá paramétereket, hogy a konténer valóban hasznos legyen: ```php class Container @@ -70,9 +70,9 @@ $container = new Container([ ]); ``` -Az éles eszű olvasók talán észrevettek egy problémát. Minden alkalommal, amikor kapok egy objektumot `UserController`, egy új példányt `ArticleFactory` és az adatbázis is létrejön. Ezt semmiképpen sem akarjuk. +Az éles szemű olvasók talán észrevettek egy problémát. Minden alkalommal, amikor lekérünk egy `UserController` objektumot, új `ArticleFactory` példány és adatbázis is létrejön. Ezt biztosan nem akarjuk. -Ezért hozzáadunk egy `getService()` metódust, amely újra és újra ugyanazokat a példányokat adja vissza: +Ezért hozzáadunk egy `getService()` metódust, amely mindig ugyanazokat a példányokat adja vissza: ```php class Container @@ -87,7 +87,7 @@ class Container public function getService(string $name): object { if (!isset($this->services[$name])) { - // getService('Database') hívja a createDatabase() + // a getService('Database') a createDatabase()-t fogja hívni $method = 'create' . $name; $this->services[$name] = $this->$method(); } @@ -98,9 +98,9 @@ class Container } ``` -A `$container->getService('Database')` első hívására a `createDatabase()` létrehoz egy adatbázis-objektumot, amelyet a `$services` tömbben tárol, és a következő híváskor közvetlenül visszaadja. +Az első híváskor, pl. `$container->getService('Database')`, a `createDatabase()` metódussal létrehozza az adatbázis objektumot, amelyet a `$services` tömbbe ment, és a következő híváskor egyenesen visszaadja. -A konténer többi részét is úgy módosítjuk, hogy a `getService()`-t használja: +Módosítjuk a konténer többi részét is, hogy a `getService()`-t használja: ```php class Container @@ -119,9 +119,9 @@ class Container } ``` -Egyébként a szolgáltatás kifejezés a konténer által kezelt bármely objektumra vonatkozik. Ezért a metódus neve `getService()`. +Mellesleg, a szolgáltatás kifejezés bármely, a konténer által kezelt objektumot jelöl. Ezért is a metódus neve `getService()`. -Kész. Van egy teljesen működőképes DI konténerünk! És használhatjuk is: +Kész. Van egy teljesen működőképes DI konténerünk! És használhatjuk: ```php $container = new Container([ @@ -134,6 +134,9 @@ $controller = $container->getService('UserController'); $database = $container->getService('Database'); ``` -Amint láthatjuk, nem nehéz DIC-et írni. Figyelemre méltó, hogy maguk az objektumok nem tudják, hogy egy konténer hozza létre őket. Így bármilyen objektumot létrehozhatunk így a PHP-ben anélkül, hogy a forráskódjukat befolyásolnánk. +Ahogy láthatja, egy DIC megírása nem bonyolult dolog. Érdemes megjegyezni, hogy maguk az objektumok nem tudják, hogy valamilyen konténer hozza őket létre. Így bármilyen PHP objektumot létre lehet hozni anélkül, hogy a forráskódjába bele kellene nyúlni. -Egy konténer osztály manuális létrehozása és karbantartása elég gyorsan rémálommá válhat. Ezért a következő fejezetben a [Nette DI Containerről |nette-container] lesz szó, amely szinte automatikusan képes létrehozni és frissíteni magát. +A konténer osztály manuális létrehozása és karbantartása meglehetősen gyorsan rémálommá válhat. Ezért a következő fejezetben a [Nette DI Container-ről|nette-container] beszélünk, amely szinte önmagát tudja generálni és frissíteni. + + +{{maintitle: Mi az a dependency injection konténer?}} diff --git a/dependency-injection/hu/extensions.texy b/dependency-injection/hu/extensions.texy index 1064295fba..b29a120db5 100644 --- a/dependency-injection/hu/extensions.texy +++ b/dependency-injection/hu/extensions.texy @@ -1,17 +1,17 @@ -Bővítmények létrehozása a Nette DI számára -****************************************** +Kiterjesztések készítése a Nette DI-hez +*************************************** .[perex] -A DI konténer generálása a konfigurációs fájlok mellett az úgynevezett *bővítményekre* is hatással van. Ezeket a konfigurációs fájlban a `extensions` szakaszban aktiváljuk. +A DI konténer generálását a konfigurációs fájlokon kívül az úgynevezett *kiterjesztések* is befolyásolják. Ezeket a konfigurációs fájl `extensions` szekciójában aktiváljuk. -Így adjuk hozzá a `BlogExtension` osztály által képviselt kiterjesztést a `blog` névvel: +Így adjuk hozzá a `BlogExtension` osztály által reprezentált kiterjesztést `blog` néven: ```neon extensions: blog: BlogExtension ``` -Minden fordítóbővítmény a [api:Nette\DI\CompilerExtension] osztályból örököl, és a következő metódusokat tudja megvalósítani, amelyeket a DI-fordítás során hívunk meg: +Minden compiler kiterjesztés a [api:Nette\DI\CompilerExtension]-ből öröklődik, és implementálhatja a következő metódusokat, amelyeket a DI konténer összeállítása során sorban hívnak meg: 1. getConfigSchema() 2. loadConfiguration() @@ -22,18 +22,18 @@ Minden fordítóbővítmény a [api:Nette\DI\CompilerExtension] osztályból ör getConfigSchema() .[method] =========================== -Ezt a módszert hívjuk meg először. Meghatározza a konfigurációs paraméterek érvényesítésére használt sémát. +Ez a metódus hívódik meg először. Definiálja a sémát a konfigurációs paraméterek validálásához. -A kiterjesztések konfigurálása egy olyan szakaszban történik, amelynek neve megegyezik azzal, amelyik alatt a kiterjesztést hozzáadták, pl. `blog`. +A kiterjesztést abban a szekcióban konfiguráljuk, amelynek neve megegyezik azzal, amely alatt a kiterjesztést hozzáadták, tehát `blog`: ```neon -# ugyanaz a név, mint a kiterjesztésem +# ugyanaz a név, mint a kiterjesztésé blog: postsPerPage: 10 - comments: false + allowComments: false ``` -Meghatározunk egy sémát, amely leírja az összes konfigurációs opciót, beleértve azok típusait, elfogadott értékeit és esetlegesen alapértelmezett értékeit: +Létrehozunk egy sémát, amely leírja az összes konfigurációs opciót, beleértve azok típusait, megengedett értékeit és esetleg alapértelmezett értékeit is: ```php use Nette\Schema\Expect; @@ -50,9 +50,9 @@ class BlogExtension extends Nette\DI\CompilerExtension } ``` -Lásd a dokumentációt a [sémában |schema:]. Ezen kívül a `dynamic()` segítségével megadhatjuk, hogy mely opciók lehetnek [dinamikusak |application:bootstrap#Dynamic Parameters], például a `Expect::int()->dynamic()` segítségével. +A dokumentációt a [Schema |schema:] oldalon találja. Ezenkívül meg lehet határozni, mely opciók lehetnek [dinamikusak |application:bootstrapping#Dinamikus paraméterek] a `dynamic()` segítségével, pl. `Expect::int()->dynamic()`. -A konfigurációhoz a `$this->config` segítségével férünk hozzá, amely a `stdClass` objektum: +A konfigurációhoz a `$this->config` változón keresztül férünk hozzá, amely egy `stdClass` objektum: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -71,7 +71,7 @@ class BlogExtension extends Nette\DI\CompilerExtension loadConfiguration() .[method] ============================= -Ezt a metódust arra használjuk, hogy szolgáltatásokat adjunk hozzá a konténerhez. Ez a [api:Nette\DI\ContainerBuilder] segítségével történik: +Szolgáltatások hozzáadására szolgál a konténerhez. Erre a [api:Nette\DI\ContainerBuilder] szolgál: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -86,19 +86,19 @@ class BlogExtension extends Nette\DI\CompilerExtension } ``` -A konvenció az, hogy a kiterjesztés által hozzáadott szolgáltatásokat a saját nevükkel előtagozzák, hogy ne alakuljanak ki névkonfliktusok. Ezt a `prefix()` teszi, így ha a kiterjesztés neve "blog", akkor a szolgáltatás neve `blog.articles` lesz. +A konvenció az, hogy a kiterjesztés által hozzáadott szolgáltatásokat annak nevével prefixeljük, hogy ne keletkezzenek névütközések. Ezt a `prefix()` metódus teszi, tehát ha a kiterjesztés neve `blog`, a szolgáltatás neve `blog.articles` lesz. -Ha át kell neveznünk egy szolgáltatást, akkor a visszafelé kompatibilitás fenntartása érdekében létrehozhatunk egy aliast az eredeti névvel. Hasonlóképpen ezt teszi a Nette pl. a `routing.router`, amely a korábbi `router` néven is elérhető. +Ha át kell neveznünk egy szolgáltatást, a visszamenőleges kompatibilitás megőrzése érdekében létrehozhatunk egy aliast az eredeti névvel. Hasonlóan teszi ezt a Nette például a `routing.router` szolgáltatásnál, amely a korábbi `router` néven is elérhető. ```php $builder->addAlias('router', 'routing.router'); ``` -Szolgáltatások lekérdezése egy fájlból .[#toc-retrieve-services-from-a-file] ----------------------------------------------------------------------------- +Szolgáltatások betöltése fájlból +-------------------------------- -Szolgáltatásokat a ContainerBuilder API segítségével hozhatunk létre, de a már ismert NEON konfigurációs fájlon és annak `services` szakaszán keresztül is hozzáadhatunk szolgáltatásokat. A `@extension` előtag az aktuális kiterjesztést jelöli. +A szolgáltatásokat nem csak a ContainerBuilder osztály API-ján keresztül hozhatjuk létre, hanem a konfigurációs fájlban a services szekcióban használt ismert írásmóddal is. Az `@extension` prefix az aktuális kiterjesztést jelenti. ```neon services: @@ -112,7 +112,7 @@ services: create: MyBlog\Components\ArticlesList(@extension.articles) ``` -Mi így fogunk szolgáltatásokat hozzáadni: +A szolgáltatásokat betöltjük: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -133,7 +133,7 @@ class BlogExtension extends Nette\DI\CompilerExtension beforeCompile() .[method] ========================= -A metódus akkor hívódik meg, amikor a konténer tartalmazza a `loadConfiguration` metódusban az egyes bővítmények által hozzáadott összes szolgáltatást, valamint a felhasználói konfigurációs fájlokat. Az összerakás ezen fázisában aztán módosíthatjuk a szolgáltatásdefiníciókat, vagy hozzáadhatunk linkeket közöttük. A `findByTag()` metódus segítségével címkék alapján kereshetünk szolgáltatásokat, a `findByType()` metódus segítségével pedig osztály vagy interfész alapján. +A metódus akkor hívódik meg, amikor a konténer tartalmazza az összes, az egyes kiterjesztések által a `loadConfiguration` metódusokban hozzáadott szolgáltatást, valamint a felhasználói konfigurációs fájlokból származókat is. Az összeállítás ezen szakaszában tehát módosíthatjuk a szolgáltatásdefiníciókat, vagy kiegészíthetjük a köztük lévő kapcsolatokat. A szolgáltatások konténerben való kereséséhez tagek alapján a `findByTag()` metódust, osztály vagy interfész alapján pedig a `findByType()` metódust használhatjuk. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -153,7 +153,7 @@ class BlogExtension extends Nette\DI\CompilerExtension afterCompile() .[method] ======================== -Ebben a fázisban a konténer osztály már [ClassType |php-generator:#classes] objektumként generálódik, tartalmazza az összes metódust, amelyet a szolgáltatás létrehoz, és PHP fájlként készen áll a gyorsítótárazásra. A keletkező osztály kódját ezen a ponton még szerkeszthetjük. +Ebben a fázisban a konténer osztálya már [ClassType |php-generator:#Osztályok] objektum formájában van generálva, tartalmazza az összes metódust, amely szolgáltatásokat hoz létre, és készen áll a cache-be írásra. Az eredményül kapott osztálykódot ebben a pillanatban még módosíthatjuk. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -167,24 +167,24 @@ class BlogExtension extends Nette\DI\CompilerExtension ``` -$inicializálás .[wiki-method] -============================= +$initialization .[method] +========================= -A konfigurátor a [konténer létrehozása |application:bootstrap#index.php] után hívja meg az inicializálási kódot, amely a `$this->initialization` objektumba való írással jön létre az [addBody() metódus |php-generator:#method-and-function-body] segítségével. +A Configurator osztály a [konténer létrehozása |application:bootstrapping#index.php] után meghívja az inicializációs kódot, amely a `$this->initialization` objektumba való írással jön létre a [addBody() metódusával |php-generator:#Metódus és függvény törzsek] segítségével. -Mutatunk egy példát arra, hogyan indíthatunk el egy munkamenetet vagy indíthatunk el szolgáltatásokat, amelyek a `run` címkével rendelkeznek az inicializálási kód segítségével: +Mutatunk egy példát, hogyan indíthatjuk el például a sessiont inicializációs kóddal, vagy futtathatunk olyan szolgáltatásokat, amelyeknek `run` tagjük van: ```php class BlogExtension extends Nette\DI\CompilerExtension { public function loadConfiguration() { - // automatikus munkamenet indítás + // session automatikus indítása if ($this->config->session->autoStart) { $this->initialization->addBody('$this->getService("session")->start()'); } - // a 'run' címkével ellátott szolgáltatásokat a konténer példányosítása után kell létrehozni. + // a run taggel rendelkező szolgáltatásokat a konténer példányosítása után kell létrehozni $builder = $this->getContainerBuilder(); foreach ($builder->findByTag('run') as $name => $foo) { $this->initialization->addBody('$this->getService(?);', [$name]); diff --git a/dependency-injection/hu/factory.texy b/dependency-injection/hu/factory.texy index 1929e3e8ec..07f077420e 100644 --- a/dependency-injection/hu/factory.texy +++ b/dependency-injection/hu/factory.texy @@ -1,12 +1,12 @@ -Generált gyárak -*************** +Generált factory-k +****************** .[perex] -A Nette DI képes automatikusan gyárkódot generálni a felület alapján, ami megkíméli Önt a kód írásától. +A Nette DI képes automatikusan generálni factory kódot interfészek alapján, ami megkíméli Önt a kódírástól. -A gyár egy olyan osztály, amely objektumokat hoz létre és konfigurál. Ezért a függőségeiket is átadja nekik. Kérjük, ne keverjük össze a *factory method* tervezési mintával, amely a gyárak használatának egy speciális módját írja le, és nem kapcsolódik ehhez a témához. +A factory egy olyan osztály, amely objektumokat gyárt és konfigurál. Tehát átadja nekik a függőségeiket is. Kérjük, ne keverje össze a *factory method* tervezési mintával, amely a factory-k specifikus felhasználási módját írja le, és nem kapcsolódik ehhez a témához. -A [bevezető fejezetben |introduction#factory] megmutattuk, hogyan néz ki egy ilyen gyár: +Hogy néz ki egy ilyen factory, azt a [bevezető fejezetben |introduction#Factory] mutattuk be: ```php class ArticleFactory @@ -23,7 +23,7 @@ class ArticleFactory } ``` -A Nette DI képes automatikusan gyárkódot generálni. Csak egy interfészt kell létrehozni, és a Nette DI generál egy implementációt. Az interfésznek pontosan egy `create` nevű metódussal kell rendelkeznie, és deklarálnia kell egy visszatérési típust: +A Nette DI képes automatikusan generálni a factory kódot. Mindössze annyit kell tennie, hogy létrehoz egy interfészt, és a Nette DI legenerálja az implementációt. Az interfésznek pontosan egy `create` nevű metódussal kell rendelkeznie, és deklarálnia kell a visszatérési típust: ```php interface ArticleFactory @@ -32,7 +32,7 @@ interface ArticleFactory } ``` -Tehát a `ArticleFactory` gyárnak van egy `create` metódusa, amely létrehozza a `Article` objektumokat. A `Article` osztály például a következőképpen nézhet ki: +Tehát az `ArticleFactory` factorynak van egy `create` metódusa, amely `Article` objektumokat hoz létre. Az `Article` osztály például így nézhet ki: ```php class Article @@ -44,16 +44,16 @@ class Article } ``` -Adja hozzá a gyárat a konfigurációs fájlhoz: +A factoryt hozzáadjuk a konfigurációs fájlhoz: ```neon services: - ArticleFactory ``` -A Nette DI létrehozza a megfelelő gyár implementációját. +A Nette DI legenerálja a factory megfelelő implementációját. -Így a gyárat használó kódban az objektumot interfész szerint kérjük, és a Nette DI a generált implementációt használja: +A kódban, amely a factoryt használja, így kérünk egy objektumot az interfész alapján, és a Nette DI a generált implementációt használja: ```php class UserController @@ -65,17 +65,17 @@ class UserController public function foo() { - // hagyjuk, hogy a gyár létrehozzon egy objektumot + // hagyjuk, hogy a factory létrehozza az objektumot $article = $this->articleFactory->create(); } } ``` -Paraméteres gyár .[#toc-parameterized-factory] -============================================== +Paraméterezett factory +====================== -A `create` gyári metódus paramétereket fogadhat el, amelyeket aztán átad a konstruktornak. Adjunk például egy cikk szerzői azonosítót a `Article` osztályhoz: +A `create` factory metódus elfogadhat paramétereket, amelyeket aztán átad a konstruktornak. Egészítsük ki például az `Article` osztályt a cikk szerzőjének ID-jával: ```php class Article @@ -88,7 +88,7 @@ class Article } ``` -A paramétert is hozzáadjuk a gyárhoz: +A paramétert hozzáadjuk a factoryhoz is: ```php interface ArticleFactory @@ -97,13 +97,13 @@ interface ArticleFactory } ``` -Mivel a konstruktorban lévő paraméter és a gyárban lévő paraméter neve megegyezik, a Nette DI automatikusan átadja őket. +Annak köszönhetően, hogy a konstruktorban és a factoryban lévő paraméter neve ugyanaz, a Nette DI teljesen automatikusan átadja őket. -Speciális definíció .[#toc-advanced-definition] -=============================================== +Haladó definíció +================ -A definíciót többsoros formában is meg lehet írni a `implement` billentyűvel: +A definíciót többsoros formában is le lehet írni az `implement` kulcs használatával: ```neon services: @@ -111,9 +111,9 @@ services: implement: ArticleFactory ``` -Ha ilyen hosszabb formában írjuk, a `arguments` kulcsban további argumentumokat adhatunk meg a konstruktor számára, és a `setup` segítségével további konfigurációkat adhatunk meg, ugyanúgy, mint a normál szolgáltatások esetében. +Ezzel a hosszabb írásmóddal további argumentumokat lehet megadni a konstruktorhoz az `arguments` kulcsban, és kiegészítő konfigurációt a `setup` segítségével, ugyanúgy, mint a [normál szolgáltatásoknál|services]. -Példa: ha a `create()` metódus nem fogadná el a `$authorId` paramétert, akkor a konfigurációban megadhatnánk egy fix értéket, amelyet a `Article` konstruktornak adnánk át: +Példa: ha a `create()` metódus nem fogadná el a `$authorId` paramétert, megadhatnánk egy fix értéket a konfigurációban, amelyet átadnánk az `Article` konstruktorának: ```neon services: @@ -123,7 +123,7 @@ services: authorId: 123 ``` -Vagy fordítva, ha a `create()` elfogadja a `$authorId` paramétert, de az nem része a konstruktornak, hanem a `Article::setAuthorId()` módszer adja át, akkor a `setup` szakaszban hivatkoznánk rá: +Vagy fordítva, ha a `create()` elfogadná a `$authorId` paramétert, de az nem lenne része a konstruktornak, és a `Article::setAuthorId()` metódussal adnánk át, akkor a `setup` szekcióban hivatkoznánk rá: ```neon services: @@ -134,15 +134,14 @@ services: ``` -Accessor .[#toc-accessor] -========================= +Accessor +======== -A gyárak mellett a Nette képes úgynevezett accessorokat is generálni. Az accessor egy olyan objektum, amely a `get()` metódussal rendelkezik, és egy adott szolgáltatást ad vissza a DI konténerből. Több `get()` hívás mindig ugyanazt a példányt adja vissza. +A Nette a factory-k mellett ún. accessorokat is tud generálni. Ezek olyan objektumok, amelyeknek van egy `get()` metódusa, amely egy bizonyos szolgáltatást ad vissza a DI konténerből. A `get()` ismételt hívása mindig ugyanazt a példányt adja vissza. -Az accessorok a lazy-loadingot hozzák a függőségekbe. Legyen egy osztályunk, amely a hibákat egy speciális adatbázisba naplózza. Ha az adatbázis-kapcsolatot függőségként adnánk át a konstruktorában, a kapcsolatot mindig létre kellene hozni, bár csak ritkán, hiba esetén használnánk, így a kapcsolat többnyire kihasználatlan maradna. -Ehelyett az osztály átadhat egy accessort, és amikor annak `get()` metódusa meghívásra kerül, csak ekkor jön létre az adatbázis-objektum: +Az accessorok lazy-loadingot biztosítanak a függőségeknek. Tegyük fel, hogy van egy osztályunk, amely hibákat ír egy speciális adatbázisba. Ha ez az osztály konstruktorfüggőségként kapná meg az adatbázis-kapcsolatot, a kapcsolatot mindig létre kellene hozni, bár a gyakorlatban hiba csak kivételesen fordul elő, és így a kapcsolat legtöbbször kihasználatlan maradna. Ehelyett az osztály átad egy accessort, és csak akkor jön létre az adatbázis objektum, amikor annak `get()` metódusát meghívják: -Hogyan hozzunk létre egy accessort? Írjunk csak egy interfészt, és a Nette DI legenerálja az implementációt. Az interfésznek pontosan egy `get` nevű metódussal kell rendelkeznie, és deklarálnia kell a visszatérési típust: +Hogyan hozzunk létre accessort? Csak írjunk egy interfészt, és a Nette DI legenerálja az implementációt. Az interfésznek pontosan egy `get` nevű metódussal kell rendelkeznie, és deklarálnia kell a visszatérési típust: ```php interface PDOAccessor @@ -151,7 +150,7 @@ interface PDOAccessor } ``` -Adja hozzá az elérőt a konfigurációs fájlhoz annak a szolgáltatásnak a definíciójával együtt, amelyet az elérő visszaad: +Az accessort hozzáadjuk a konfigurációs fájlhoz, ahol a szolgáltatás definíciója is található, amelyet vissza fog adni: ```neon services: @@ -159,62 +158,68 @@ services: - PDO(%dsn%, %user%, %password%) ``` -Az accessor egy `PDO` típusú szolgáltatást ad vissza, és mivel csak egy ilyen szolgáltatás van a konfigurációban, az accessor azt fogja visszaadni. Több ilyen típusú konfigurált szolgáltatás esetén a névvel megadhatja, hogy melyik legyen a visszaadandó szolgáltatás, például `- PDOAccessor(@db1)`. +Mivel az accessor `PDO` típusú szolgáltatást ad vissza, és a konfigurációban csak egy ilyen szolgáltatás van, pontosan azt fogja visszaadni. Ha több ilyen típusú szolgáltatás lenne, a visszaadott szolgáltatást név szerint határoznánk meg, pl. `- PDOAccessor(@db1)`. -Multifactory/Accessor .[#toc-multifactory-accessor] -=================================================== -Eddig a factories és accessorok csak egy objektumot tudtak létrehozni vagy visszaadni. Létrehozható egy multifactory egy accessorral kombinálva is. Az ilyen multifactory osztály interfésze több metódusból állhat, amelyek neve `create<name>()` és `get<name>()`például: +Többszörös factory/accessor +=========================== +Eddig a factory-ink és accessoraink mindig csak egy objektumot tudtak gyártani vagy visszaadni. De nagyon könnyen létrehozhatunk többszörös factory-kat is accessorokkal kombinálva. Egy ilyen osztály interfésze tetszőleges számú `create<name>()` és `get<name>()` nevű metódust tartalmazhat, pl.: ```php interface MultiFactory { function createArticle(): Article; - function createFoo(): Model\Foo; function getDb(): PDO; } ``` -Ahelyett, hogy több generált gyárat és accessort adna át, csak egy összetett multifactory-t adhat át. +Tehát ahelyett, hogy több generált factoryt és accessort adnánk át, egy komplexebb factoryt adunk át, amely többet tud. -Alternatívaként több metódus helyett használhatja a `create()` és a `get()` egy paramétert: +Alternatívaként több metódus helyett használhatjuk a `get()`-et paraméterrel: ```php interface MultiFactoryAlt { - function create($name); function get($name): PDO; } ``` -Ebben az esetben a `MultiFactory::createArticle()` ugyanazt teszi, mint a `MultiFactoryAlt::create('article')`. Az alternatív szintaxisnak azonban van néhány hátránya. Nem egyértelmű, hogy mely `$name` értékek támogatottak, és több különböző `$name` érték használata esetén a visszatérési típus nem adható meg az interfészben. +Ekkor igaz, hogy a `MultiFactory::getArticle()` ugyanazt csinálja, mint a `MultiFactoryAlt::get('article')`. Az alternatív írásmódnak azonban az a hátránya, hogy nem egyértelmű, milyen `$name` értékek támogatottak, és logikailag nem lehet megkülönböztetni a különböző visszatérési értékeket a különböző `$name`-ekhez az interfészben. -Definíció listával .[#toc-definition-with-a-list] -------------------------------------------------- -Hos definiálni egy multifactory-t a konfigurációban? Hozzunk létre három szolgáltatást, amelyeket a multifactory fog visszaadni, és magát a multifactory-t: +Definíció listával +------------------ +Ezzel a módszerrel definiálhatunk többszörös factoryt a konfigurációban: .{data-version:3.2.0} + +```neon +services: + - MultiFactory( + article: Article # definiálja a createArticle()-t + db: PDO(%dsn%, %user%, %password%) # definiálja a getDb()-t + ) +``` + +Vagy a factory definíciójában hivatkozhatunk létező szolgáltatásokra referenciával: ```neon services: article: Article - - Model\Foo - PDO(%dsn%, %user%, %password%) - MultiFactory( - article: @article # createArticle() - foo: @Model\Foo # createFoo() - db: @\PDO # getDb() + article: @article # definiálja a createArticle()-t + db: @\PDO # definiálja a getDb()-t ) ``` -Meghatározás címkékkel .[#toc-definition-with-tags] ---------------------------------------------------- +Definíció tagekkel +------------------ -Egy másik lehetőség a multifactory definiálására a [címkék |services#Tags] használata: +A második lehetőség a [tageket |services#Tagek] használni a definícióhoz: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter - App\Model\DatabaseAccessor( db1: @database.db1.explorer ) diff --git a/dependency-injection/hu/faq.texy b/dependency-injection/hu/faq.texy index 7bb9fe4437..3ac34906d6 100644 --- a/dependency-injection/hu/faq.texy +++ b/dependency-injection/hu/faq.texy @@ -1,98 +1,92 @@ -Gyakran ismételt kérdések a DI-ről (FAQ) -**************************************** +Gyakran Ismételt Kérdések a DI-ről (GYIK) +***************************************** -A DI az IoC másik neve? .[#toc-is-di-another-name-for-ioc] ----------------------------------------------------------- +A DI egy másik név az IoC-re? +----------------------------- -Az *Inversion of Control* (IoC) egy olyan elv, amely a kód végrehajtásának módjára összpontosít - arra, hogy a kódod külső kódot kezdeményez, vagy a kódod integrálódik külső kódba, amely aztán meghívja azt. -Az IoC egy tág fogalom, amely magában foglalja az [eseményeket |nette:glossary#Events], az úgynevezett [hollywoodi elvet |application:components#Hollywood style] és más szempontokat. -A gyárak, amelyek a [3. szabály: Let the Factory Handle It |introduction#Rule #3: Let the Factory Handle It] részét képezik, és a `new` operátor inverzióját jelentik, szintén e koncepció összetevői. +Az *Inversion of Control* (IoC) egy elv, amely arra összpontosít, hogyan fut a kód - hogy a kódja futtat-e egy idegen kódot, vagy a kódja integrálva van egy idegen kódba, amely aztán meghívja. Az IoC egy tág fogalom, amely magában foglalja az [eseményeket |nette:glossary#Eventek események], az úgynevezett [Hollywood-elvet |application:components#Hollywood style] és más szempontokat is. Ennek a koncepciónak a része a factory is, amelyről a [3. szabály: hagyd a factory-ra |introduction#3. szabály: Hagyd a factory-ra] szól, és amely az `new` operátor inverzióját jelenti. -A *Dependency Injection* (DI) arról szól, hogy egy objektum hogyan tud egy másik objektumról, azaz függőségről. Ez egy olyan tervezési minta, amely megköveteli a függőségek explicit átadását az objektumok között. +A *Dependency Injection* (DI) arra összpontosít, hogyan tud meg egy objektum egy másik objektumról, azaz annak függőségeiről. Ez egy tervezési minta, amely megköveteli a függőségek explicit átadását az objektumok között. -Így a DI az IoC egy sajátos formájának mondható. Az IoC nem minden formája alkalmas azonban a kódtisztaság szempontjából. Például az anti-minták közé soroljuk az összes olyan technikát, amely [globális állapottal |global state] vagy az úgynevezett [Service Locatorral |#What is a Service Locator] dolgozik. +Tehát mondhatjuk, hogy a DI az IoC egy specifikus formája. Azonban nem minden IoC forma megfelelő a kód tisztasága szempontjából. Például az antipattern-ek közé tartoznak azok a technikák, amelyek [globális állapottal |global-state] dolgoznak, vagy az úgynevezett [Service Locator |#Mi az a Service Locator]. -Mi az a Service Locator? .[#toc-what-is-a-service-locator] ----------------------------------------------------------- +Mi az a Service Locator? +------------------------ -A Service Locator a Dependency Injection alternatívája. Úgy működik, hogy létrehoz egy központi tárolót, ahol az összes elérhető szolgáltatás vagy függőség regisztrálva van. Amikor egy objektumnak szüksége van egy függőségre, akkor azt a Service Locatorból kéri. +Ez egy alternatíva a Dependency Injection-re. Úgy működik, hogy létrehoz egy központi tárolót, ahol minden elérhető szolgáltatás vagy függőség regisztrálva van. Amikor egy objektumnak szüksége van egy függőségre, a Service Locatortól kéri azt. -A Dependency Injectionhoz képest azonban veszít az átláthatóságból: a függőségek nem kerülnek közvetlenül az objektumokhoz, ezért nem könnyen azonosíthatók, ami a kód vizsgálatát igényli az összes kapcsolat feltárásához és megértéséhez. A tesztelés is bonyolultabb, mivel nem adhatunk át egyszerűen mock objektumokat a tesztelt objektumoknak, hanem a Service Locatoron keresztül kell haladnunk. Továbbá a Service Locator megzavarja a kód tervezését, mivel az egyes objektumoknak tisztában kell lenniük a létezésével, ami eltér a Dependency Injectiontől, ahol az objektumok nem tudnak a DI konténerről. +A Dependency Injection-nel szemben azonban elveszíti az átláthatóságot: a függőségek nem közvetlenül kerülnek átadásra az objektumoknak, és így nem könnyen azonosíthatók, ami megköveteli a kód átvizsgálását, hogy minden kapcsolatot feltárjunk és megértsünk. A tesztelés is bonyolultabb, mert nem tudunk egyszerűen mock objektumokat átadni a tesztelt objektumoknak, hanem a Service Locatoron keresztül kell ezt megtennünk. Ráadásul a Service Locator megzavarja a kód tervezését, mivel az egyes objektumoknak tudniuk kell a létezéséről, ami eltér a Dependency Injection-től, ahol az objektumoknak nincs tudomásuk a DI konténerről. -Mikor jobb, ha nem használjuk a DI-t? .[#toc-when-is-it-better-not-to-use-di] ------------------------------------------------------------------------------ +Mikor jobb nem használni a DI-t? +-------------------------------- -A Dependency Injection tervezési minta használatával kapcsolatban nincsenek ismert nehézségek. Ellenkezőleg, a függőségek globálisan elérhető helyekről való megszerzése [számos bonyodalomhoz |global-state] vezet, akárcsak a Service Locator használata. -Ezért tanácsos mindig a DI használata. Ez nem dogmatikus megközelítés, de egyszerűen nem találtunk jobb alternatívát. +Nincsenek ismert nehézségek a Dependency Injection tervezési minta használatával kapcsolatban. Ellenkezőleg, a függőségek globálisan elérhető helyekről való beszerzése [számos komplikációhoz |global-state] vezet, ahogy a Service Locator használata is. Ezért célszerű mindig DI-t használni. Ez nem dogmatikus megközelítés, egyszerűen nem találtak jobb alternatívát. -Vannak azonban olyan helyzetek, amikor nem adunk át objektumokat egymásnak, és a globális térből szerezzük meg őket. Például, amikor kódot hibakeresünk, és a program egy adott pontján ki kell dobnunk egy változó értékét, meg kell mérnünk a program egy bizonyos részének időtartamát, vagy naplóznunk kell egy üzenetet. -Ilyen esetekben, amikor olyan ideiglenes műveletekről van szó, amelyek később eltávolításra kerülnek a kódból, jogos egy globálisan elérhető dumper, stopperóra vagy logger használata. Ezek az eszközök végül is nem tartoznak a kód tervezéséhez. +Ennek ellenére léteznek bizonyos helyzetek, amikor nem adunk át objektumokat, és a globális térből szerezzük be őket. Például a kód debuggolásakor, amikor egy adott ponton ki kell íratni egy változó értékét, meg kell mérni egy programrész futási idejét, vagy naplózni kell egy üzenetet. Ilyen esetekben, amikor ideiglenes műveletekről van szó, amelyeket később eltávolítanak a kódból, legitim egy globálisan elérhető dumper, stopperóra vagy logger használata. Ezek az eszközök ugyanis nem tartoznak a kód tervezéséhez. -Vannak-e hátrányai a DI használatának? .[#toc-does-using-di-have-its-drawbacks] -------------------------------------------------------------------------------- +Vannak árnyoldalai a DI használatának? +-------------------------------------- -A Dependency Injection használata jár-e hátrányokkal, például a kódírás bonyolultabbá válásával vagy rosszabb teljesítménnyel? Mit veszítünk, ha a DI-vel összhangban kezdünk kódot írni? +Jár-e a Dependency Injection használata valamilyen hátránnyal, például megnövekedett kódírási igénybevétellel vagy rosszabb teljesítménnyel? Mit veszítünk, ha elkezdünk DI-kompatibilis kódot írni? -A DI nincs hatással az alkalmazás teljesítményére vagy memóriaigényére. A DI konténer teljesítménye szerepet játszhat, de a [Nette DI | nette-container] esetében a konténer tiszta PHP-be van fordítva, így az alkalmazás futási ideje alatt a többletköltsége lényegében nulla. +A DI nincs hatással az alkalmazás teljesítményére vagy memóriaigényére. A DI Container teljesítménye játszhat némi szerepet, azonban a [Nette DI |nette-container] esetében a konténer tiszta PHP-ba van fordítva, így a futásidejű overhead lényegében nulla. -A kód írásakor olyan konstruktorokat kell létrehozni, amelyek elfogadják a függőségeket. Régebben ez időigényes lehetett, de a modern IDE-knek és a [konstruktorok tulajdonságainak promóciójának |https://blog.nette.org/hu/php-8-0-teljes-attekintes-az-ujdonsagokrol#toc-constructor-property-promotion] köszönhetően ez ma már néhány másodperc kérdése. A Nette DI és egy PhpStorm plugin segítségével néhány kattintással könnyedén létrehozhatók a konstruktorok. -Másrészt nincs szükség singletonok és statikus hozzáférési pontok írására. +A kódírás során szükség lehet konstruktorok létrehozására, amelyek függőségeket fogadnak el. Korábban ez időigényes lehetett, de a modern IDE-knek és a [constructor property promotion |https://blog.nette.org/hu/php-8-0-complete-overview-of-news#toc-constructor-property-promotion]-nek köszönhetően ez most másodpercek kérdése. A factory-kat könnyen lehet generálni a Nette DI és a PhpStorm plugin segítségével egy egérkattintással. Másrészt nincs szükség singletonok és statikus hozzáférési pontok írására. -Megállapítható, hogy egy megfelelően megtervezett, DI-t használó alkalmazás nem rövidebb és nem hosszabb a singletonokat használó alkalmazáshoz képest. A függőségekkel dolgozó kódrészek egyszerűen kivonásra kerülnek az egyes osztályokból, és új helyekre, azaz a DI konténerbe és a gyárakba kerülnek. +Megállapítható, hogy egy helyesen megtervezett, DI-t használó alkalmazás sem rövidebb, sem hosszabb nem lesz egy singletonokat használó alkalmazáshoz képest. A függőségekkel dolgozó kódrészek csupán ki vannak emelve az egyes osztályokból, és új helyekre kerülnek, azaz a DI konténerbe és a factory-kba. -Hogyan írjunk át egy régi alkalmazást DI-re? .[#toc-how-to-rewrite-a-legacy-application-to-di] ----------------------------------------------------------------------------------------------- +Hogyan írjunk át egy legacy alkalmazást DI-re? +---------------------------------------------- -A régebbi alkalmazásról a Dependency Injectionra való áttérés kihívást jelentő folyamat lehet, különösen a nagy és összetett alkalmazások esetében. Fontos, hogy szisztematikusan közelítsük meg ezt a folyamatot. +Egy legacy alkalmazás átállítása Dependency Injection-re kihívást jelentő folyamat lehet, különösen nagy és komplex alkalmazások esetén. Fontos, hogy ezt a folyamatot szisztematikusan közelítsük meg. -- A Dependency Injectionra való áttérés során fontos, hogy a csapat minden tagja megértse az alkalmazott elveket és gyakorlatokat. -- Először is végezze el a meglévő alkalmazás elemzését a kulcsfontosságú összetevők és függőségük azonosítása érdekében. Készítsen tervet arra vonatkozóan, hogy mely részeket és milyen sorrendben fogja refaktorálni. -- Implementáljon egy DI-konténert, vagy még jobb, ha egy meglévő könyvtárat használ, például a Nette DI-t. -- Fokozatosan alakítsa át az alkalmazás minden egyes részét a Dependency Injection használatára. Ez magában foglalhatja a konstruktorok vagy metódusok módosítását, hogy paraméterként elfogadják a függőségeket. -- Módosítsa a kód azon helyeit, ahol függőségi objektumok jönnek létre, hogy a függőségeket a konténer injektálja. Ez magában foglalhatja a gyárak használatát. +- A Dependency Injection-re való áttéréskor fontos, hogy a csapat minden tagja megértse az alkalmazott elveket és eljárásokat. +- Először végezzen elemzést a meglévő alkalmazásról, és azonosítsa a kulcsfontosságú komponenseket és azok függőségeit. Készítsen tervet arról, mely részeket kell refaktorálni és milyen sorrendben. +- Implementáljon egy DI konténert, vagy még jobb, ha egy létező könyvtárat használ, például a Nette DI-t. +- Fokozatosan refaktorálja az alkalmazás egyes részeit, hogy Dependency Injection-t használjanak. Ez magában foglalhatja a konstruktorok vagy metódusok módosítását úgy, hogy paraméterként fogadják el a függőségeket. +- Módosítsa azokat a kódrészeket, ahol függőségekkel rendelkező objektumok jönnek létre, hogy ehelyett a függőségeket a konténer injektálja. Ez magában foglalhatja a factory-k használatát. -Ne feledje, hogy a függőségi injektálásra való áttérés a kód minőségébe és az alkalmazás hosszú távú fenntarthatóságába való befektetés. Bár kihívást jelenthet ezeknek a változtatásoknak a végrehajtása, az eredménynek tisztább, modulárisabb és könnyen tesztelhető kódnak kell lennie, amely készen áll a jövőbeli bővítésekre és karbantartásra. +Ne feledje, hogy a Dependency Injection-re való áttérés befektetés a kód minőségébe és az alkalmazás hosszú távú fenntarthatóságába. Bár kihívást jelenthet ezeknek a változtatásoknak a végrehajtása, az eredmény egy tisztább, modulárisabb és könnyen tesztelhető kód kell, hogy legyen, amely készen áll a jövőbeli bővítésre és karbantartásra. -Miért előnyösebb a kompozíció az örökléssel szemben? .[#toc-why-composition-is-preferred-over-inheritance] ----------------------------------------------------------------------------------------------------------- -Az öröklés helyett előnyösebb a kompozíciót használni, mivel ez a kód újrafelhasználhatóságát szolgálja anélkül, hogy aggódnunk kellene a változtatások átcsapó hatása miatt. Így lazább csatolást biztosít, ahol nem kell aggódnunk amiatt, hogy egy kód megváltoztatása más függő kódok megváltoztatását eredményezi. Tipikus példa erre a [konstruktorpokolként |passing-dependencies#Constructor hell] azonosított helyzet. +Miért részesítjük előnyben a kompozíciót az öröklődéssel szemben? +----------------------------------------------------------------- +Célszerűbb a [kompozíciót |nette:introduction-to-object-oriented-programming#Kompozíció] használni az [öröklődés |nette:introduction-to-object-oriented-programming#Öröklődés] helyett, mert a kód újrafelhasználására szolgál anélkül, hogy aggódnunk kellene a változtatások következményei miatt. Tehát lazább kötést biztosít, ahol nem kell attól tartanunk, hogy egy kód módosítása szükségessé teszi egy másik függő kód módosítását. Tipikus példa erre a [constructor hell |passing-dependencies#Constructor hell] néven ismert helyzet. -Használható-e a Nette DI Container a Nette rendszeren kívül is? .[#toc-can-nette-di-container-be-used-outside-of-nette] ------------------------------------------------------------------------------------------------------------------------ +Használható a Nette DI Container a Nette-n kívül? +------------------------------------------------- -Természetesen. A Nette DI Container része a Nette-nek, de önálló könyvtárként van kialakítva, amely a keretrendszer más részeitől függetlenül használható. Csak telepítse a Composer segítségével, hozzon létre egy konfigurációs fájlt, amely meghatározza a szolgáltatásait, majd néhány sor PHP kóddal hozza létre a DI konténert. -És máris elkezdheti kihasználni a Dependency Injection előnyeit a projektjeiben. +Határozottan. A Nette DI Container a Nette része, de önálló könyvtárként lett tervezve, amely a keretrendszer többi részétől függetlenül használható. Csak telepíteni kell a Composer segítségével, létre kell hozni egy konfigurációs fájlt a szolgáltatások definíciójával, majd néhány sor PHP kóddal létre kell hozni a DI konténert. És azonnal elkezdheti kihasználni a Dependency Injection előnyeit a projektjeiben. -A [Nette DI Container |nette-container] fejezetben leírja, hogyan néz ki egy konkrét felhasználási eset, a kóddal együtt. +A konkrét használatot, beleértve a kódokat is, a [Nette DI Container |nette-container] fejezet írja le. -Miért van a konfiguráció NEON fájlokban? .[#toc-why-is-the-configuration-in-neon-files] ---------------------------------------------------------------------------------------- +Miért van a konfiguráció NEON fájlokban? +---------------------------------------- -A NEON egy egyszerű és könnyen olvasható konfigurációs nyelv, amelyet a Nette-en belül fejlesztettek ki az alkalmazások, szolgáltatások és függőségeik beállítására. A JSON-hoz vagy a YAML-hez képest sokkal intuitívabb és rugalmasabb lehetőségeket kínál erre a célra. A NEON-ban természetesen leírhatók olyan kötöttségek, amelyeket Symfony & YAML-ben egyáltalán nem vagy csak bonyolult leírással lehetne megírni. +A NEON egy egyszerű és könnyen olvasható konfigurációs nyelv, amelyet a Nette keretében fejlesztettek ki alkalmazások, szolgáltatások és azok függőségeinek beállítására. A JSON-nal vagy YAML-lel összehasonlítva sokkal intuitívabb és rugalmasabb lehetőségeket kínál erre a célra. A NEON-ban természetesen leírhatók olyan kapcsolatok, amelyeket a Symfony & YAML-ben vagy egyáltalán nem lehetne leírni, vagy csak bonyolult leírással. -Lassítja a NEON fájlok elemzése az alkalmazást? .[#toc-does-parsing-neon-files-slow-down-the-application] ---------------------------------------------------------------------------------------------------------- +Nem lassítja le az alkalmazást a NEON fájlok feldolgozása? +---------------------------------------------------------- -Bár a NEON fájlok elemzése nagyon gyorsan történik, ez a szempont nem igazán számít. Ennek oka az, hogy a fájlok elemzése csak egyszer történik meg az alkalmazás első indítása során. Ezt követően a DI-konténer kódja generálódik, a lemezen tárolódik, és minden további kérésnél további elemzés nélkül végrehajtódik. +Bár a NEON fájlok nagyon gyorsan feldolgozódnak, ez a szempont egyáltalán nem számít. Az ok az, hogy a fájlok feldolgozása csak egyszer történik meg az alkalmazás első indításakor. Ezután legenerálódik a DI konténer kódja, elmentődik a lemezre, és minden további kérésnél elindul anélkül, hogy további feldolgozásra lenne szükség. -Ez így működik a termelési környezetben. A fejlesztés során a NEON-fájlok minden alkalommal elemzése megtörténik, amikor tartalmuk megváltozik, így biztosítva, hogy a fejlesztő mindig naprakész DI-konténerrel rendelkezzen. Mint korábban említettük, a tényleges elemzés egy pillanat alatt megtörténik. +Ez így működik a produkciós környezetben. A fejlesztés során a NEON fájlok minden alkalommal feldolgozódnak, amikor a tartalmuk megváltozik, hogy a fejlesztő mindig naprakész DI konténerrel rendelkezzen. Maga a feldolgozás, ahogy említettük, pillanatok kérdése. -Hogyan férhetek hozzá a konfigurációs fájl paramétereihez az osztályomban? .[#toc-how-do-i-access-the-parameters-from-the-configuration-file-in-my-class] ---------------------------------------------------------------------------------------------------------------------------------------------------------- +Hogyan férek hozzá az osztályomból a konfigurációs fájl paramétereihez? +----------------------------------------------------------------------- -Tartsd szem előtt az [1. szabályt: Hagyd, hogy átadják n |introduction#Rule #1: Let It Be Passed to You]eked. Ha egy osztálynak szüksége van egy konfigurációs fájlból származó információra, nem kell kitalálnunk, hogyan érhetjük el az információt, hanem egyszerűen kérdezzük meg - például az osztály konstruktorán keresztül. Az átadást pedig a konfigurációs fájlban végezzük el. +Tartsuk szem előtt az [1. szabályt: kérd el, hogy átadják |introduction#1. szabály: Kérd el]. Ha egy osztálynak információra van szüksége a konfigurációs fájlból, nem kell azon gondolkodnunk, hogyan jussunk hozzá ehhez az információhoz, ehelyett egyszerűen kérjük el - például az osztály konstruktorán keresztül. Az átadást pedig a konfigurációs fájlban valósítjuk meg. -Ebben a példában a `%myParameter%` a `myParameter` paraméter értékének helyőrzője, amelyet a `MyClass` konstruktornak adunk át: +Ebben a példában a `%myParameter%` a `myParameter` paraméter értékének helyettesítője, amelyet átadunk a `MyClass` osztály konstruktorának: ```php # config.neon @@ -103,10 +97,10 @@ services: - MyClass(%myParameter%) ``` -Ha több paramétert akar átadni, vagy automatikus kapcsolást szeretne használni, hasznos, ha [a paramétereket egy objektumba csomagolja |best-practices:passing-settings-to-presenters]. +Ha több paramétert szeretne átadni, vagy autowiringot szeretne használni, célszerű [a paramétereket objektumba csomagolni |best-practices:passing-settings-to-presenters]. -Támogatja a Nette a PSR-11 konténer interfészt? .[#toc-does-nette-support-psr-11-container-interface] ------------------------------------------------------------------------------------------------------ +Támogatja a Nette a PSR-11: Container interface-t? +-------------------------------------------------- -A Nette DI Container nem támogatja közvetlenül a PSR-11-et. Ha azonban interoperabilitásra van szüksége a Nette DI Container és a PSR-11 Container interfészt elváró könyvtárak vagy keretrendszerek között, létrehozhat egy [egyszerű adaptert |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f], amely hídként szolgál a Nette DI Container és a PSR-11 között. +A Nette DI Container nem támogatja közvetlenül a PSR-11-et. Azonban, ha interoperabilitásra van szüksége a Nette DI Container és olyan könyvtárak vagy keretrendszerek között, amelyek PSR-11 Container Interface-t várnak, létrehozhat egy [egyszerű adaptert |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f], amely hídként szolgál a Nette DI Container és a PSR-11 között. diff --git a/dependency-injection/hu/global-state.texy b/dependency-injection/hu/global-state.texy index 3ef1f4916f..1cef481b30 100644 --- a/dependency-injection/hu/global-state.texy +++ b/dependency-injection/hu/global-state.texy @@ -2,66 +2,62 @@ Globális állapot és singletonok ******************************* .[perex] -Figyelmeztetés: A következő konstrukciók a rosszul megtervezett kód tünetei: +Figyelmeztetés: A következő konstrukciók rosszul megtervezett kód jelei: - `Foo::getInstance()` - `DB::insert(...)` - `Article::setDb($db)` - `ClassName::$var` vagy `static::$var` -Találkozik ilyen konstrukciókkal a kódjában? Ha igen, akkor lehetősége van javítani rajta. Azt gondolhatod, hogy ezek gyakori konstrukciók, amelyeket gyakran láthatsz különböző könyvtárak és keretrendszerek mintamegoldásaiban. Ha ez a helyzet, akkor a kódtervezésük hibás. +Előfordulnak ezek a konstrukciók a kódjában? Akkor itt a lehetőség a javításra. Talán azt gondolja, hogy ezek általános konstrukciók, amelyeket akár különböző könyvtárak és keretrendszerek példamegoldásaiban is lát. Ha ez így van, akkor a kódjuk tervezése nem jó. -Itt most nem valami akadémiai tisztaságról beszélünk. Mindezekben a konstrukciókban egy dolog közös: globális állapotot használnak. Ez pedig romboló hatással van a kód minőségére. Az osztályok megtévesztőek a függőségeiket illetően. A kód kiszámíthatatlanná válik. Ez összezavarja a fejlesztőket és csökkenti a hatékonyságukat. +Most biztosan nem valamilyen akadémiai tisztaságról beszélünk. Minden ilyen konstrukciónak egy közös vonása van: globális állapotot használnak. És ennek romboló hatása van a kód minőségére. Az osztályok hazudnak a függőségeikről. A kód kiszámíthatatlanná válik. Megzavarja a programozókat és csökkenti hatékonyságukat. -Ebben a fejezetben elmagyarázzuk, miért van ez így, és hogyan kerülhetjük el a globális állapotot. +Ebben a fejezetben elmagyarázzuk, miért van ez így, és hogyan kerüljük el a globális állapotot. -Globális összekapcsolás .[#toc-global-interlinking] ---------------------------------------------------- +Globális összekapcsolás +----------------------- -Egy ideális világban egy objektum csak olyan objektumokkal kommunikálhat, amelyeket [közvetlenül átadtak neki |passing-dependencies]. Ha létrehozok két objektumot `A` és `B`, és soha nem adok át közöttük hivatkozást, akkor sem a `A`, sem a `B` nem férhet hozzá a másik állapotához, illetve nem módosíthatja azt. Ez egy nagyon kívánatos tulajdonsága a kódnak. Olyan, mintha lenne egy elem és egy izzó; az izzó nem fog világítani, amíg nem csatlakoztatjuk az elemhez egy vezetékkel. +Egy ideális világban egy objektumnak csak azokkal az objektumokkal kellene tudnia kommunikálni, amelyeket [közvetlenül átadva |passing-dependencies] kapott. Ha létrehozok két `A` és `B` objektumot, és soha nem adok át referenciát közöttük, akkor sem `A`, sem `B` nem férhet hozzá a másik objektumhoz, vagy nem változtathatja meg annak állapotát. Ez a kód egy nagyon kívánatos tulajdonsága. Hasonló ahhoz, mint amikor van egy elem és egy izzó; az izzó nem fog világítani, amíg nem köti össze az elemmel egy dróttal. -Ez azonban nem igaz a globális (statikus) változókra vagy a szingletonokra. A `A` objektum *vezeték nélkül* hozzáférhet a `C` objektumhoz, és módosíthatja azt mindenféle referenciaátadás nélkül, a `C::changeSomething()` meghívásával. Ha a `B` objektum a globális `C` objektumot is megcsapolja, akkor a `A` és a `B` objektumok a `C` objektumon keresztül befolyásolhatják egymást. +Ez azonban nem igaz a globális (statikus) változókra vagy singletonokra. Az `A` objektum *vezeték nélkül* hozzáférhetne a `C` objektumhoz, és módosíthatná azt anélkül, hogy bármilyen referenciát átadna, azáltal, hogy meghívja a `C::changeSomething()`-t. Ha a `B` objektum is megragadja a globális `C`-t, akkor `A` és `B` kölcsönösen befolyásolhatják egymást a `C`-n keresztül. -A globális változók használata a *vezeték nélküli* csatolás egy új, kívülről nem látható formáját vezeti be. Ez egy füstfüggönyt hoz létre, amely megnehezíti a kód megértését és használatát. A függőségek valódi megértéséhez a fejlesztőknek a forráskód minden sorát el kell olvasniuk, ahelyett, hogy csak az osztályok interfészeivel ismerkednének. Ráadásul ez az összefonódás teljesen felesleges. A globális állapotot azért használjuk, mert bárhonnan könnyen elérhető, és lehetővé teszi például az adatbázisba való írást egy globális (statikus) metóduson keresztül `DB::insert()`. Azonban, mint látni fogjuk, az általa nyújtott előny minimális, míg a bevezetett bonyodalmak súlyosak. +A globális változók használata a *vezeték nélküli* összekapcsolás új formáját vezeti be a rendszerbe, amely kívülről nem látható. Füstfüggönyt hoz létre, amely bonyolítja a kód megértését és használatát. Ahhoz, hogy a fejlesztők valóban megértsék a függőségeket, el kell olvasniuk a forráskód minden sorát. Ahelyett, hogy egyszerűen megismerkednének az osztályok interfészével. Ráadásul ez egy teljesen felesleges összekapcsolás. A globális állapotot azért használják, mert könnyen hozzáférhető bárhonnan, és lehetővé teszi például az adatbázisba írást a globális (statikus) `DB::insert()` metóduson keresztül. De ahogy megmutatjuk, az ebből származó előny elenyésző, míg a okozott komplikációk végzetesek. .[note] -A viselkedés szempontjából nincs különbség egy globális és egy statikus változó között. Egyformán károsak. +Viselkedés szempontjából nincs különbség a globális és a statikus változó között. Ugyanolyan károsak. -A kísérteties cselekvés távolról .[#toc-the-spooky-action-at-a-distance] ------------------------------------------------------------------------- +Kísérteties távolhatás +---------------------- -"Kísérteties hatás a távolban" - így nevezte Albert Einstein 1935-ben a kvantumfizika egyik jelenségét, amelytől kirázta a hideg. -Ez a kvantum összefonódás, amelynek sajátossága, hogy amikor egy részecskéről információt mérünk, azonnal hatással vagyunk egy másik részecskére, még akkor is, ha azok több millió fényévre vannak egymástól. -ami látszólag sérti a világegyetem alapvető törvényét, miszerint semmi sem haladhat gyorsabban a fénynél. +"Kísérteties távolhatás" - így nevezte el híresen 1935-ben Albert Einstein a kvantumfizika egy jelenségét, amelytől libabőrös lett. +Ez egy kvantum-összefonódás, amelynek különlegessége, hogy ha megmérjük az információt az egyik részecskéről, azonnal befolyásoljuk a másik részecskét is, még akkor is, ha millió fényév távolságra vannak egymástól. Ami látszólag megsérti az univerzum alapvető törvényét, hogy semmi sem terjedhet gyorsabban a fénynél. -A szoftverek világában "spooky action at a distance"-nek nevezhetjük azt a helyzetet, amikor lefuttatunk egy folyamatot, amelyről azt gondoljuk, hogy elszigetelt (mert nem adtunk át neki semmilyen hivatkozást), de váratlan kölcsönhatások és állapotváltozások történnek a rendszer távoli pontjain, amelyekről nem szóltunk az objektumnak. Ez csak a globális állapoton keresztül történhet. +A szoftver világában "kísérteties távolhatásnak" nevezhetjük azt a helyzetet, amikor elindítunk egy folyamatot, amelyről azt gondoljuk, hogy izolált (mert nem adtunk át neki semmilyen referenciát), de a rendszer távoli pontjain váratlan interakciók és állapotváltozások következnek be, amelyekről nem volt tudomásunk. Ez csak globális állapoton keresztül történhet meg. -Képzeljük el, hogy csatlakozunk egy olyan projektfejlesztő csapathoz, amely nagy, kiforrott kódbázissal rendelkezik. Az új vezetőd megkér egy új funkció megvalósítására, és jó fejlesztőhöz méltóan egy teszt megírásával kezded. De mivel új vagy a projektben, sok feltáró "mi történik, ha meghívom ezt a metódust" típusú tesztet csinálsz. És megpróbálod megírni a következő tesztet: +Képzelje el, hogy csatlakozik egy projekt fejlesztői csapatához, amelynek kiterjedt, kiforrott kódbázisa van. Az új vezetője megkéri Önt egy új funkció implementálására, és Ön, mint jó fejlesztő, a teszt írásával kezdi. Mivel azonban új a projektben, sok feltáró tesztet végez, mint például "mi történik, ha meghívom ezt a metódust". És megpróbálja megírni a következő tesztet: ```php function testCreditCardCharge() { - $cc = new CreditCard('1234567890123456', 5, 2028); // az Ön kártyaszámát + $cc = new CreditCard('1234567890123456', 5, 2028); // az Ön kártyaszáma $cc->charge(100); } ``` -Egy idő után észreveszed, hogy a bank értesítést küld a telefonodon, hogy minden egyes futtatáskor 100 dollárral terhelték meg a hitelkártyádat. 🤦‍♂️ +Futtatja a kódot, talán többször is, és egy idő után észreveszi a mobilján a banki értesítéseket, hogy minden futtatáskor 100 dollárt vontak le a bankkártyájáról 🤦‍♂️ -Hogy a fenébe okozhatott a teszt tényleges terhelést? Nem könnyű a hitelkártyával operálni. Egy harmadik fél webes szolgáltatásával kell kapcsolatba lépnie, ismernie kell a webes szolgáltatás URL-jét, be kell jelentkeznie, és így tovább. -Ezek közül az információk közül egyik sem szerepel a tesztben. Még rosszabb, hogy azt sem tudod, hol vannak ezek az információk, és ezért nem tudod, hogyan kell a külső függőségeket leutánozni, hogy minden egyes futtatásnál ne kelljen újra 100 dollárt fizetni. És új fejlesztőként honnan kellett volna tudnod, hogy amit most fogsz csinálni, az 100 dollárral szegényebbé tesz téged? +Hogy a fenébe okozhatta a teszt a valódi pénzlevonást? A bankkártyával való művelet nem egyszerű. Kommunikálnia kell egy harmadik fél webszolgáltatásával, ismernie kell ennek a webszolgáltatásnak az URL-jét, be kell jelentkeznie és így tovább. Ezek közül az információk közül egyik sem szerepel a tesztben. Sőt, még azt sem tudja, hol vannak ezek az információk, és így azt sem, hogyan mockolja az externális függőségeket, hogy minden futtatás ne vezessen újabb 100 dollár levonásához. És honnan kellett volna tudnia új fejlesztőként, hogy amit tenni készül, az 100 dollárral szegényebbé teszi? -Ez egy kísérteties akció a távolból! +Ez a kísérteties távolhatás! -Nincs más választásod, mint rengeteg forráskódban turkálni, megkérdezni idősebb és tapasztaltabb kollégákat, amíg meg nem érted, hogyan működnek az összefüggések a projektben. -Ennek oka, hogy a `CreditCard` osztály interfészét megnézve nem tudod meghatározni az inicializálandó globális állapotot. Még az osztály forráskódjának megnézése sem árulja el, hogy melyik inicializálási metódust kell meghívni. A legjobb esetben megkereshetjük a globális változót, amelyhez hozzáférünk, és ebből próbálhatjuk kitalálni, hogyan kell inicializálni. +Nem marad más hátra, mint hosszan turkálni a rengeteg forráskódban, kérdezgetni az idősebb és tapasztaltabb kollégákat, amíg meg nem érti, hogyan működnek a kapcsolatok a projektben. Ez azért van, mert a `CreditCard` osztály interfészének megtekintésekor nem lehet megállapítani a globális állapotot, amelyet inicializálni kell. Még az osztály forráskódjának megtekintése sem árulja el, melyik inicializációs metódust kell meghívnia. Legjobb esetben találhat egy globális változót, amelyhez hozzáférnek, és abból megpróbálhatja kitalálni, hogyan inicializálja. -Egy ilyen projektben az osztályok beteges hazudozók. A fizetési kártya úgy tesz, mintha egyszerűen csak instanciáznád, és meghívnád a `charge()` metódust. Titokban azonban kölcsönhatásba lép egy másik osztállyal, a `PaymentGateway`. Még az interfésze is azt mondja, hogy önállóan inicializálható, de a valóságban valamilyen konfigurációs fájlból húzza a hitelesítő adatokat, és így tovább. -A kódot író fejlesztők számára egyértelmű, hogy a `CreditCard` -nak szüksége van a `PaymentGateway`. Így írták meg a kódot. De bárki számára, aki új a projektben, ez teljes rejtély, és akadályozza a tanulást. +Az ilyen projekt osztályai patologikus hazudozók. A bankkártya úgy tesz, mintha elég lenne példányosítani és meghívni a `charge()` metódust. Titokban azonban együttműködik egy másik `PaymentGateway` osztállyal, amely a fizetési kaput képviseli. Annak interfésze is azt mondja, hogy önállóan inicializálható, de valójában kihúzza a hitelesítő adatokat valamilyen konfigurációs fájlból és így tovább. A fejlesztőknek, akik ezt a kódot írták, világos, hogy a `CreditCard`-nak szüksége van a `PaymentGateway`-re. Így írták a kódot. De bárki számára, aki új a projektben, ez teljes rejtély, és akadályozza a tanulást. -Hogyan lehet kijavítani a helyzetet? Egyszerűen. **Hagyjuk, hogy az API deklarálja a függőségeket.** +Hogyan javítsuk a helyzetet? Könnyen. **Hagyja, hogy az API deklarálja a függőségeket.** ```php function testCreditCardCharge() @@ -72,36 +68,35 @@ function testCreditCardCharge() } ``` -Figyeljük meg, hogy a kódon belüli kapcsolatok hirtelen nyilvánvalóvá válnak. Azzal, hogy deklaráljuk, hogy a `charge()` metódusnak szüksége van a `PaymentGateway` címre, senkitől sem kell megkérdeznünk, hogy a kód hogyan függ egymástól. Tudod, hogy egy példányt kell létrehoznod belőle, és amikor megpróbálod ezt megtenni, belefutsz abba, hogy hozzáférési paramétereket kell megadnod. Ezek nélkül a kód nem is futna. +Figyelje meg, hogyan válnak hirtelen nyilvánvalóvá a kódon belüli kapcsolatok. Azzal, hogy a `charge()` metódus deklarálja, hogy szüksége van a `PaymentGateway`-re, nem kell senkitől megkérdeznie, hogyan van összekapcsolva a kód. Tudja, hogy létre kell hoznia annak példányát, és amikor megpróbálja, rájön, hogy meg kell adnia a hozzáférési paramétereket. Nélkülük a kód el sem indulna. -És ami a legfontosabb, most már le tudja mockolni a fizetési átjárót, hogy ne kelljen 100 dollárt fizetnie minden egyes teszt futtatásakor. +És ami a legfontosabb, most már mockolhatja a fizetési kaput, így nem vonnak le 100 dollárt minden tesztfuttatáskor. -A globális állapot miatt az objektumaid titokban hozzáférhetnek olyan dolgokhoz, amelyek nincsenek deklarálva az API-jukban, és ennek eredményeképpen az API-id kóros hazudozókká válnak. +A globális állapot miatt az objektumai titokban hozzáférhetnek olyan dolgokhoz, amelyek nincsenek deklarálva az API-jukban, és ennek következtében az API-jai patologikus hazudozókká válnak. -Lehet, hogy eddig nem gondoltál rá így, de valahányszor globális állapotot használsz, titkos vezeték nélküli kommunikációs csatornákat hozol létre. A hátborzongató távoli működés arra kényszeríti a fejlesztőket, hogy minden egyes kódsort elolvassanak a lehetséges interakciók megértéséhez, csökkenti a fejlesztők termelékenységét, és összezavarja az új csapattagokat. -Ha te vagy az, aki a kódot létrehozta, akkor ismered a valódi függőségeket, de bárki, aki utánad jön, tanácstalan. +Talán korábban nem gondolt rá így, de minden alkalommal, amikor globális állapotot használ, titkos vezeték nélküli kommunikációs csatornákat hoz létre. A kísérteties távolhatás arra kényszeríti a fejlesztőket, hogy minden kódsort elolvassanak a potenciális interakciók megértéséhez, csökkenti a fejlesztők termelékenységét és megzavarja az új csapattagokat. Ha Ön hozta létre a kódot, ismeri a valódi függőségeket, de bárki, aki Ön után jön, tanácstalan. -Ne írjon olyan kódot, amely globális állapotot használ, inkább adja át a függőségeket. Vagyis a függőségi injektálás. +Ne írjon olyan kódot, amely globális állapotot használ, részesítse előnyben a függőségek átadását. Tehát a dependency injection-t. -A globális állam törékenysége .[#toc-brittleness-of-the-global-state] ---------------------------------------------------------------------- +Globális állapot törékenysége +----------------------------- -A globális állapotot és singletonokat használó kódban sosem lehetünk biztosak abban, hogy az állapotot mikor és ki változtatta meg. Ez a kockázat már az inicializáláskor fennáll. A következő kódnak egy adatbázis-kapcsolatot kellene létrehoznia és inicializálnia a fizetési átjárót, de folyamatosan kivételt dob, és az okának megtalálása rendkívül fárasztó: +A globális állapotot és singletonokat használó kódban soha nem biztos, hogy mikor és ki változtatta meg ezt az állapotot. Ez a kockázat már az inicializáláskor megjelenik. A következő kódnak adatbázis-kapcsolatot kellene létrehoznia és inicializálnia a fizetési kaput, azonban folyamatosan kivételt dob, és az ok keresése rendkívül hosszadalmas: ```php PaymentGateway::init(); DB::init('mysql:', 'user', 'password'); ``` -Részletesen át kell nézni a kódot, hogy kiderüljön, hogy a `PaymentGateway` objektum vezeték nélkül más objektumokhoz is hozzáfér, amelyek közül néhányhoz adatbázis-kapcsolat szükséges. Így a `PaymentGateway` előtt inicializálni kell az adatbázist. A globális állapot füstfüggönye azonban ezt elrejti Ön elől. Mennyi időt spórolna meg, ha az egyes osztályok API-ja nem hazudna és nem jelentené be függőségeit? +Részletesen át kell néznie a kódot, hogy rájöjjön, a `PaymentGateway` objektum vezeték nélkül hozzáfér más objektumokhoz, amelyek közül néhány adatbázis-kapcsolatot igényel. Tehát az adatbázist korábban kell inicializálni, mint a `PaymentGateway`-t. Azonban a globális állapot füstfüggönye ezt elrejti Ön elől. Mennyi időt takaríthatna meg, ha az egyes osztályok API-ja nem hazudna, és deklarálná a függőségeit? ```php $db = new DB('mysql:', 'user', 'password'); $gateway = new PaymentGateway($db, ...); ``` -Hasonló probléma merül fel, amikor globális hozzáférést használunk egy adatbázis-kapcsolathoz: +Hasonló probléma merül fel az adatbázis-kapcsolat globális elérésének használatakor is: ```php use Illuminate\Support\Facades\DB; @@ -115,7 +110,7 @@ class Article } ``` -A `save()` metódus meghívásakor nem biztos, hogy az adatbázis-kapcsolat már létrejött-e, és ki a felelős a létrehozásáért. Ha például menet közben szeretnénk megváltoztatni az adatbázis-kapcsolatot, esetleg tesztelési céllal, akkor valószínűleg további metódusokat kellene létrehoznunk, például a `DB::reconnect(...)` vagy a `DB::reconnectForTest()` metódusokat. +A `save()` metódus hívásakor nem biztos, hogy már létrejött-e az adatbázis-kapcsolat, és ki felelős annak létrehozásáért. Ha például futás közben szeretnénk megváltoztatni az adatbázis-kapcsolatot, például tesztek miatt, valószínűleg további metódusokat kellene létrehoznunk, mint például `DB::reconnect(...)` vagy `DB::reconnectForTest()`. Vegyünk egy példát: @@ -127,9 +122,9 @@ Foo::doSomething(); $article->save(); ``` -Hol lehetünk biztosak abban, hogy a `$article->save()` meghívásakor valóban a tesztadatbázist használjuk ? Mi van, ha a `Foo::doSomething()` módszer megváltoztatta a globális adatbázis-kapcsolatot? Ahhoz, hogy ezt megtudjuk, meg kellene vizsgálnunk a `Foo` osztály és valószínűleg sok más osztály forráskódját. Ez a megközelítés azonban csak rövid távú választ adna, mivel a jövőben változhat a helyzet. +Hol van a biztosíték arra, hogy a `$article->save()` hívásakor valóban a tesztadatbázist használjuk? Mi van, ha a `Foo::doSomething()` metódus megváltoztatta a globális adatbázis-kapcsolatot? Ennek kiderítéséhez meg kellene vizsgálnunk a `Foo` osztály forráskódját, és valószínűleg sok más osztályét is. Ez a megközelítés azonban csak rövid távú választ adna, mivel a helyzet a jövőben megváltozhat. -Mi lenne, ha az adatbázis-kapcsolatot egy statikus változóba helyeznénk át a `Article` osztályon belül? +És mi van, ha az adatbázis-kapcsolatot egy statikus változóba helyezzük az `Article` osztályon belül? ```php class Article @@ -148,11 +143,11 @@ class Article } ``` -Ez egyáltalán nem változtat semmit. A probléma egy globális állapot, és nem számít, hogy melyik osztályban rejtőzik. Ebben az esetben, ahogy az előző esetben is, fogalmunk sincs arról, hogy a `$article->save()` metódus meghívásakor milyen adatbázisba íródik. Bárki az alkalmazás távoli végén bármikor megváltoztathatja az adatbázist a `Article::setDb()` segítségével. A mi kezünk alatt. +Ezzel egyáltalán semmi sem változott. A probléma a globális állapot, és teljesen mindegy, melyik osztályban rejtőzik. Ebben az esetben, akárcsak az előzőben, a `$article->save()` metódus hívásakor nincs semmilyen támpontunk arra vonatkozóan, hogy melyik adatbázisba íródik. Bárki az alkalmazás másik végén bármikor megváltoztathatta az adatbázist az `Article::setDb()` segítségével. A kezünk alatt. -A globális állapot miatt az alkalmazásunk **rendkívül törékennyé** válik. +A globális állapot **rendkívül törékennyé** teszi az alkalmazásunkat. -Van azonban egy egyszerű módja ennek a problémának a kezelésére. Csak az API-nak kell deklarálnia a függőségeket a megfelelő funkcionalitás biztosítása érdekében. +Van azonban egy egyszerű módja ennek a problémának a kezelésére. Csak hagyni kell, hogy az API deklarálja a függőségeket, ami biztosítja a helyes működést. ```php class Article @@ -174,15 +169,15 @@ Foo::doSomething(); $article->save(); ``` -Ez a megközelítés kiküszöböli az adatbázis-kapcsolatok rejtett és váratlan változásai miatti aggodalmat. Most már biztosak vagyunk abban, hogy a cikket hol tároljuk, és semmilyen kódmódosítás egy másik, nem kapcsolódó osztályon belül nem változtathatja meg többé a helyzetet. A kód többé nem törékeny, hanem stabil. +Ennek a megközelítésnek köszönhetően megszűnik az aggodalom a rejtett és váratlan adatbázis-kapcsolat változások miatt. Most már biztosak lehetünk benne, hova mentődik a cikk, és semmilyen kódmódosítás egy másik, nem kapcsolódó osztályon belül már nem változtathat a helyzeten. A kód már nem törékeny, hanem stabil. -Ne írjunk olyan kódot, amely globális állapotot használ, inkább adjuk át a függőségeket. Így a függőségi injektálás. +Ne írjon olyan kódot, amely globális állapotot használ, részesítse előnyben a függőségek átadását. Tehát a dependency injection-t. -Singleton .[#toc-singleton] ---------------------------- +Singleton +--------- -A singleton egy olyan tervezési minta, amely a híres Gang of Four kiadvány [definíciója |https://en.wikipedia.org/wiki/Singleton_pattern] szerint egy osztályt egyetlen példányra korlátoz, és globális hozzáférést biztosít hozzá. Ennek a mintának a megvalósítása általában a következő kódhoz hasonlít: +A Singleton egy tervezési minta, amely a híres Gang of Four kiadvány "definíciója":https://en.wikipedia.org/wiki/Singleton_pattern szerint egy osztályt egyetlen példányra korlátoz, és globális hozzáférést kínál hozzá. Ennek a mintának az implementációja általában a következő kódhoz hasonlít: ```php class Singleton @@ -195,38 +190,35 @@ class Singleton return self::$instance; } - // és más metódusok, amelyek az osztály funkcióit hajtják végre. + // és további metódusok, amelyek az adott osztály funkcióit töltik be } ``` -Sajnos a singleton globális állapotot vezet be az alkalmazásba. És mint fentebb megmutattuk, a globális állapot nem kívánatos. Ezért tekinthető a singleton antipatternnek. +Sajnos a singleton globális állapotot vezet be az alkalmazásba. És ahogy fentebb megmutattuk, a globális állapot nemkívánatos. Ezért a singletont antipattern-nek tekintik. -Ne használjon singletont a kódjában, és helyettesítse más mechanizmusokkal. Tényleg nincs szükséged szingletonokra. Ha azonban garantálnod kell egy osztály egyetlen példányának létezését az egész alkalmazás számára, akkor hagyd ezt a [DI konténerre |container]. -Így hozzon létre egy alkalmazás szingletont, vagy szolgáltatást. Ezáltal az osztály nem fogja biztosítani a saját egyediségét (azaz nem lesz `getInstance()` metódusa és statikus változója), és csak a funkcióit fogja végrehajtani. Így megszűnik az egyetlen felelősség elvének megsértése. +Ne használjon singletonokat a kódjában, és helyettesítse őket más mechanizmusokkal. Valóban nincs szüksége singletonokra. Ha azonban garantálnia kell egy osztály egyetlen példányának létezését az egész alkalmazás számára, bízza azt a [DI konténerre |container]. Hozzon létre így egy alkalmazás szintű singletont, azaz egy szolgáltatást. Ezzel az osztály megszűnik foglalkozni saját egyediségének biztosításával (azaz nem lesz `getInstance()` metódusa és statikus változója), és csak a funkcióit fogja ellátni. Így megszűnik megsérteni az egyetlen felelősség elvét. -Globális állapot a tesztek ellenében .[#toc-global-state-versus-tests] ----------------------------------------------------------------------- +Globális állapot versus tesztek +------------------------------- -A tesztek írása során feltételezzük, hogy minden teszt egy izolált egység, és nem kerül bele külső állapot. És semmilyen állapot nem hagyja el a teszteket. Amikor egy teszt befejeződik, a teszthez kapcsolódó állapotot a szemétgyűjtőnek automatikusan el kell távolítania. Ez teszi a teszteket izolálttá. Ezért a teszteket tetszőleges sorrendben futtathatjuk. +Tesztek írásakor feltételezzük, hogy minden teszt egy izolált egység, és hogy semmilyen külső állapot nem lép be. És semmilyen állapot nem hagyja el a teszteket. A teszt befejezése után minden, a teszthez kapcsolódó állapotot automatikusan el kell távolítania a garbage collectornak. Ennek köszönhetően a tesztek izoláltak. Ezért futtathatjuk a teszteket tetszőleges sorrendben. -Ha azonban globális állapotok/singletonok vannak jelen, akkor mindezek a szép feltételezések összeomlanak. Egy állapot beléphet és kiléphet egy tesztből. Hirtelen a tesztek sorrendje számíthat. +Ha azonban globális állapotok/singletonok vannak jelen, mindezek a kellemes feltételezések összeomlanak. Az állapot beléphet a tesztbe és kiléphet belőle. Hirtelen számíthat a tesztek sorrendje. -Ahhoz, hogy a szingletonokat egyáltalán tesztelni lehessen, a fejlesztőknek gyakran lazítaniuk kell a tulajdonságaikon, például úgy, hogy megengedik, hogy egy példányt egy másikra cseréljenek. Az ilyen megoldások a legjobb esetben is hackek, amelyek nehezen karbantartható és nehezen érthető kódot eredményeznek. Minden olyan tesztnek vagy metódusnak `tearDown()`, amely bármilyen globális állapotot érint, vissza kell vonnia ezeket a változásokat. +Ahhoz, hogy egyáltalán tesztelni tudjuk a singletonokat, a fejlesztők gyakran kénytelenek lazítani a tulajdonságaikat, például azáltal, hogy megengedik a példány cseréjét egy másikkal. Az ilyen megoldások legjobb esetben is hackek, amelyek nehezen karbantartható és érthető kódot hoznak létre. Minden tesztnek vagy `tearDown()` metódusnak, amely bármilyen globális állapotot befolyásol, vissza kell állítania ezeket a változtatásokat. -A globális állapot a legnagyobb fejfájás az egységtesztelésben! +A globális állapot a legnagyobb fejfájás az unit tesztelés során! -Hogyan lehet megoldani a helyzetet? Egyszerűen. Ne írj olyan kódot, amely singletonokat használ, inkább add át a függőségeket. Vagyis függőségi injektálással. +Hogyan javítsuk a helyzetet? Könnyen. Ne írjon olyan kódot, amely singletonokat használ, részesítse előnyben a függőségek átadását. Tehát a dependency injection-t. -Globális konstansok .[#toc-global-constants] --------------------------------------------- +Globális konstansok +------------------- -A globális állapot nem korlátozódik a szingletonok és statikus változók használatára, hanem a globális konstansokra is vonatkozhat. +A globális állapot nem korlátozódik csak a singletonok és statikus változók használatára, hanem globális konstansokra is vonatkozhat. -Azok a konstansok, amelyek értéke nem szolgáltat számunkra új (`M_PI`) vagy hasznos (`PREG_BACKTRACK_LIMIT_ERROR`) információt, egyértelműen rendben vannak. -Ezzel szemben azok a konstansok, amelyek arra szolgálnak, hogy *vezeték nélkül* információt adjunk át a kódon belül, nem többek, mint rejtett függőség. Mint a `LOG_FILE` a következő példában. -A `FILE_APPEND` konstans használata teljesen helyes. +Azok a konstansok, amelyek értéke nem hoz számunkra semmilyen új (`M_PI`) vagy hasznos (`PREG_BACKTRACK_LIMIT_ERROR`) információt, egyértelműen rendben vannak. Ellenben azok a konstansok, amelyek arra szolgálnak, hogy *vezeték nélkül* információt adjanak át a kódba, nem mások, mint rejtett függőségek. Mint például a `LOG_FILE` a következő példában. A `FILE_APPEND` konstans használata teljesen korrekt. ```php const LOG_FILE = '...'; @@ -242,7 +234,7 @@ class Foo } ``` -Ebben az esetben a paramétert a `Foo` osztály konstruktorában kell deklarálnunk, hogy az API részévé váljon: +Ebben az esetben deklarálnunk kellene egy paramétert a `Foo` osztály konstruktorában, hogy az API részévé váljon: ```php class Foo @@ -261,44 +253,42 @@ class Foo } ``` -Most már átadhatjuk a naplófájl elérési útvonalára vonatkozó információt, és szükség esetén könnyen módosíthatjuk azt, így könnyebben tesztelhetjük és karbantarthatjuk a kódot. +Most már átadhatjuk az információt a naplófájl elérési útjáról, és szükség szerint könnyen megváltoztathatjuk, ami megkönnyíti a kód tesztelését és karbantartását. -Globális függvények és statikus metódusok .[#toc-global-functions-and-static-methods] -------------------------------------------------------------------------------------- +Globális függvények és statikus metódusok +----------------------------------------- -Szeretnénk hangsúlyozni, hogy a statikus metódusok és globális függvények használata önmagában nem problémás. A `DB::insert()` és hasonló módszerek használatának helytelenségét már elmagyaráztuk, de mindig is a statikus változóban tárolt globális állapotról volt szó. A `DB::insert()` metódus megköveteli egy statikus változó meglétét, mivel az adatbázis-kapcsolatot tárolja. E változó nélkül lehetetlen lenne a módszer végrehajtása. +Szeretnénk hangsúlyozni, hogy maguk a statikus metódusok és globális függvények használata nem problematikus. Elmagyaráztuk, miért nem megfelelő a `DB::insert()` és hasonló metódusok használata, de ez mindig csak a globális állapot kérdése volt, amely valamilyen statikus változóban van tárolva. A `DB::insert()` metódus megköveteli egy statikus változó létezését, mert abban van tárolva az adatbázis-kapcsolat. E változó nélkül lehetetlen lenne a metódust implementálni. -A determinisztikus statikus módszerek és függvények, mint például a `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` és sok más, használata tökéletesen összhangban van a függőségi injektálással. Ezek a függvények mindig ugyanazokat az eredményeket adják vissza ugyanazokból a bemeneti paraméterekből, ezért kiszámíthatóak. Nem használnak semmilyen globális állapotot. +Determinisztikus statikus metódusok és függvények használata, mint például a `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` és sok más, teljes mértékben összhangban van a dependency injection-nel. Ezek a függvények mindig ugyanazokat az eredményeket adják vissza ugyanazokból a bemeneti paraméterekből, és ezért előrejelezhetők. Nem használnak semmilyen globális állapotot. -Vannak azonban a PHP-ben olyan függvények, amelyek nem determinisztikusak. Ezek közé tartozik például a `htmlspecialchars()` függvény. Harmadik paramétere, a `$encoding`, ha nincs megadva, alapértelmezés szerint a `ini_get('default_charset')` konfigurációs opció értékét veszi fel. Ezért ajánlott mindig megadni ezt a paramétert, hogy elkerüljük a függvény esetleges kiszámíthatatlan viselkedését. A Nette következetesen ezt teszi. +Léteznek azonban olyan függvények is PHP-ban, amelyek nem determinisztikusak. Ezek közé tartozik például a `htmlspecialchars()` függvény. Annak harmadik paramétere, a `$encoding`, ha nincs megadva, alapértelmezett értékként a `ini_get('default_charset')` konfigurációs opció értékét veszi fel. Ezért ajánlott ezt a paramétert mindig megadni, hogy elkerüljük a függvény esetleges kiszámíthatatlan viselkedését. A Nette ezt következetesen megteszi. -Egyes függvények, mint például a `strtolower()`, `strtoupper()` és hasonlók, a közelmúltban nem determinisztikus viselkedést mutattak, és a `setlocale()` beállításától függtek. Ez sok bonyodalmat okozott, leggyakrabban a török nyelvvel való munka során. -Ennek oka, hogy a török nyelv különbséget tesz a kis- és nagybetűs `I` között ponttal és pont nélkül. Így a `strtolower('I')` a `ı` karaktert, a `strtoupper('i')` pedig a `İ` karaktert adta vissza , ami az alkalmazásokban számos rejtélyes hibát okozott. -Ezt a problémát azonban a PHP 8.2-es verziójában kijavították, és a függvények többé nem függnek a nyelvjárástól. +Néhány függvény, mint például a `strtolower()`, `strtoupper()` és hasonlók, a közelmúltban nem determinisztikusan viselkedtek, és a `setlocale()` beállítástól függtek. Ez sok komplikációt okozott, leggyakrabban a török nyelvvel való munka során. Az ugyanis megkülönbözteti a kis- és nagybetűs `I`-t ponttal és pont nélkül is. Így a `strtolower('I')` az `ı` karaktert adta vissza, a `strtoupper('i')` pedig az `İ` karaktert, ami ahhoz vezetett, hogy az alkalmazások számos rejtélyes hibát kezdtek okozni. Ezt a problémát azonban a PHP 8.2-es verziójában orvosolták, és a függvények már nem függnek a locale-tól. -Ez egy szép példa arra, hogy a globális állapot fejlesztők ezreit sújtotta világszerte. A megoldás az volt, hogy függőségi injektálással helyettesítették. +Ez egy szép példa arra, hogyan okozott fejfájást a globális állapot több ezer fejlesztőnek világszerte. A megoldás az volt, hogy dependency injection-nel helyettesítették. -Mikor lehetséges a globális állapot használata? .[#toc-when-is-it-possible-to-use-global-state] ------------------------------------------------------------------------------------------------ +Mikor lehet globális állapotot használni? +----------------------------------------- -Vannak bizonyos speciális helyzetek, amikor lehetséges a globális állapot használata. Például kód hibakereséskor, amikor ki kell dobni egy változó értékét, vagy meg kell mérni a program egy adott részének időtartamát. Ilyen esetekben, amelyek olyan ideiglenes műveletekre vonatkoznak, amelyeket később eltávolítunk a kódból, jogos egy globálisan elérhető dumper vagy stopperóra használata. Ezek az eszközök nem részei a kódtervezésnek. +Léteznek bizonyos specifikus helyzetek, amikor lehet globális állapotot használni. Például a kód debuggolásakor, amikor ki kell íratni egy változó értékét, vagy meg kell mérni egy programrész futási idejét. Ilyen esetekben, amelyek ideiglenes műveletekre vonatkoznak, amelyeket később eltávolítanak a kódból, legitim egy globálisan elérhető dumper vagy stopperóra használata. Ezek az eszközök ugyanis nem részei a kód tervezésének. -Egy másik példa a `preg_*`, a reguláris kifejezésekkel való munkavégzésre szolgáló függvények, amelyek a lefordított reguláris kifejezéseket belsőleg egy statikus gyorsítótárban tárolják a memóriában. Ha ugyanazt a reguláris kifejezést a kód különböző részeiben többször hívja meg, akkor csak egyszer fordítja le. A gyorsítótár teljesítményt takarít meg, és a felhasználó számára is teljesen láthatatlan, így az ilyen használat jogosnak tekinthető. +Egy másik példa a reguláris kifejezésekkel dolgozó `preg_*` függvények, amelyek belsőleg statikus cache-ben tárolják a lefordított reguláris kifejezéseket a memóriában. Tehát ha ugyanazt a reguláris kifejezést többször hívja meg a kód különböző pontjain, csak egyszer fordítódik le. A cache teljesítményt takarít meg, és ugyanakkor a felhasználó számára teljesen láthatatlan, ezért az ilyen használat legitimnek tekinthető. -Összefoglaló .[#toc-summary] ----------------------------- +Összegzés +--------- -Megmutattuk, miért van értelme +Megbeszéltük, miért van értelme: -1) Távolítsunk el minden statikus változót a kódból. -2) Deklaráljuk a függőségeket -3) És használjuk a függőségi injektálást +1) Eltávolítani minden statikus változót a kódból +2) Deklarálni a függőségeket +3) És használni a dependency injection-t -Amikor a kódtervezésről gondolkodik, tartsa szem előtt, hogy minden egyes `static $foo` egy problémát jelent. Ahhoz, hogy a kódod DI-tisztelő környezet legyen, elengedhetetlen a globális állapot teljes kiirtása és függőségi injektálással való helyettesítése. +Amikor a kód tervezésén gondolkodik, gondoljon arra, hogy minden `static $foo` problémát jelent. Ahhoz, hogy a kódja DI-t tiszteletben tartó környezet legyen, elengedhetetlen a globális állapot teljes kiirtása és dependency injection-nel való helyettesítése. -E folyamat során előfordulhat, hogy egy osztályt fel kell osztanod, mert egynél több felelőssége van. Ne aggódjon emiatt; törekedjen az egy felelősség elvére. +E folyamat során talán rájön, hogy egy osztályt fel kell osztani, mert több felelőssége van. Ne féljen ettől; törekedjen az egyetlen felelősség elvére. -*Köszönöm Miško Hevery-nek, akinek olyan cikkei, mint a [Flaw: Brittle Global State & Singletons (Hiba: Törékeny globális állapot és szingletonok |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/] ) képezik e fejezet alapját.* +*Szeretnék köszönetet mondani Miško Hevery-nek, akinek cikkei, mint például a [Flaw: Brittle Global State & Singletons |https://web.archive.org/web/20230321084133/http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/], képezik ennek a fejezetnek az alapját.* diff --git a/dependency-injection/hu/introduction.texy b/dependency-injection/hu/introduction.texy index f46cc6195d..8cd8b4fe69 100644 --- a/dependency-injection/hu/introduction.texy +++ b/dependency-injection/hu/introduction.texy @@ -2,57 +2,57 @@ Mi az a Dependency Injection? ***************************** .[perex] -Ez a fejezet megismerteti Önt azokkal az alapvető programozási gyakorlatokkal, amelyeket bármilyen alkalmazás írása során követnie kell. Ezek az alapok szükségesek a tiszta, érthető és karbantartható kód írásához. +Ez a fejezet bemutatja azokat az alapvető programozási gyakorlatokat, amelyeket minden alkalmazás írásakor követnie kell. Ezek az alapok szükségesek a tiszta, érthető és karbantartható kód írásához. -Ha megtanulod és betartod ezeket a szabályokat, a Nette minden lépésnél a segítségedre lesz. El fogja végezni helyetted a rutinfeladatokat, és maximális kényelmet biztosít, így magára a logikára koncentrálhatsz. +Ha elsajátítja és követi ezeket a szabályokat, a Nette minden lépésben segíteni fog Önnek. Kezelni fogja a rutinfeladatokat, és maximális kényelmet biztosít Önnek, hogy a tényleges logikára koncentrálhasson. -Az elvek, amelyeket itt bemutatunk, meglehetősen egyszerűek. Nem kell aggódnia semmi miatt. +Az itt bemutatott elvek meglehetősen egyszerűek. Nincs mitől félnie. -Emlékszel az első programodra? .[#toc-remember-your-first-program] ------------------------------------------------------------------- +Emlékszel az első programodra? +------------------------------ -Nem tudjuk, hogy milyen nyelven írta, de ha PHP volt, akkor valahogy így nézhetett ki: +Nem tudjuk, milyen nyelven írta, de ha PHP lett volna, valószínűleg így nézett volna ki: ```php -function osszeg(float $a, float $b): float +function soucet(float $a, float $b): float { return $a + $b; } -echo osszeg(23, 1); // 24-et ír ki. +echo soucet(23, 1); // kiírja a 24-et ``` -Néhány triviális kódsor, de nagyon sok kulcsfogalom rejtőzik benne. Hogy vannak változók. Hogy a kódot kisebb egységekre bontják, amelyek például függvények. Hogy bemeneti argumentumokat adunk át nekik, és eredményt adnak vissza. Csak a feltételek és a ciklusok hiányoznak. +Néhány triviális kódsor, de annyi kulcsfontosságú koncepciót rejtenek magukban. Hogy vannak változók. Hogy a kód kisebb egységekre van osztva, mint például a függvények. Hogy bemeneti argumentumokat adunk át nekik, és eredményeket adnak vissza. Már csak a feltételek és a ciklusok hiányoznak. -Az a tény, hogy egy függvény bemeneti adatokat fogad el, és egy eredményt ad vissza, tökéletesen érthető fogalom, amelyet más területeken, például a matematikában is használnak. +Az, hogy bemeneti adatokat adunk át egy függvénynek, és az eredményt ad vissza, egy tökéletesen érthető koncepció, amelyet más területeken is használnak, például a matematikában. -Egy függvénynek van egy szignatúrája, amely a nevéből, a paraméterek listájából és azok típusából, végül pedig a visszatérési érték típusából áll. Felhasználóként minket a szignatúra érdekel, és általában nem kell tudnunk semmit a belső megvalósításról. +Egy függvénynek van szignatúrája, amely a nevéből, a paraméterek és típusaik listájából, valamint végül a visszatérési érték típusából áll. Felhasználóként minket a szignatúra érdekel, a belső megvalósításról általában nem kell tudnunk semmit. -Most képzeljük el, hogy a függvény aláírás így néz ki: +Most képzelje el, hogy a függvény szignatúrája így néz ki: ```php -function osszeg(float $x): float +function soucet(float $x): float ``` -Egyetlen paraméteres kiegészítés? Ez furcsa... Mi a helyzet ezzel? +Összeadás egy paraméterrel? Ez furcsa… És mi van ezzel? ```php -function osszeg(): float +function soucet(): float ``` -Ez tényleg furcsa, nem? Hogyan használják a funkciót? +Ez már tényleg nagyon furcsa, nem? Hogyan használják a függvényt? ```php -echo osszeg(); // mit ír ki? +echo soucet(); // vajon mit ír ki? ``` -Ha ilyen kódot néznénk, összezavarodnánk. Nemcsak egy kezdő nem értené, de még egy tapasztalt programozó sem értené az ilyen kódot. +Egy ilyen kódot látva összezavarodnánk. Nemcsak egy kezdő nem értené, de egy tapasztalt programozó sem. -Kíváncsi vagy, hogy egy ilyen függvény valójában hogyan nézne ki belülről? Honnan szerezné meg az összegzőket? Valószínűleg *valahogyan* magától szerezné meg őket, talán így: +Gondolkodik azon, hogyan nézne ki egy ilyen függvény belülről? Honnan veszi az összeadandókat? Valószínűleg *valahogy* maga szerezné be őket, például így: ```php -function osszeg(): float +function soucet(): float { $a = Input::get('a'); $b = Input::get('b'); @@ -60,71 +60,71 @@ function osszeg(): float } ``` -Kiderült, hogy a függvény testében rejtett kötések vannak más függvényekhez (vagy statikus metódusokhoz), és ahhoz, hogy megtudjuk, honnan származnak az összeadók, tovább kell ásnunk. +A függvény törzsében rejtett függőségeket fedeztünk fel más globális függvényekre vagy statikus metódusokra. Ahhoz, hogy megtudjuk, honnan származnak valójában az összeadandók, tovább kell kutatnunk. -Ne erre! .[#toc-not-this-way] ------------------------------ +Nem erre! +--------- -Az imént bemutatott dizájn sok negatív tulajdonság lényege: +Az imént bemutatott tervezés számos negatív tulajdonság esszenciája: -- A függvény aláírás úgy tett, mintha nem lenne szüksége az összegzőkre, ami összezavart minket. -- fogalmunk sincs, hogyan lehetne a függvényt két másik számmal kiszámítani. -- meg kellett néznünk a kódot, hogy rájöjjünk, honnan származnak az összegzők -- rejtett függőségeket találtunk -- a teljes megértéshez ezeket a függőségeket is meg kell vizsgálni +- A függvény szignatúrája úgy tett, mintha nem lenne szüksége összeadandókra, ami félrevezetett minket. +- Fogalmunk sincs, hogyan vegyük rá a függvényt, hogy két másik számot adjon össze. +- Bele kellett néznünk a kódba, hogy megtudjuk, honnan veszi az összeadandókat. +- Rejtett függőségeket fedeztünk fel. +- A teljes megértéshez ezeket a függőségeket is meg kell vizsgálni. -És egyáltalán az összeadási függvény feladata a bemenetek beszerzése? Természetesen nem az. Az ő feladata csak az összeadás. +És egyáltalán az összeadó függvény feladata a bemenetek beszerzése? Természetesen nem. Az ő felelőssége csak maga az összeadás. -Ilyen kóddal nem akarunk találkozni, és biztosan nem akarjuk megírni. A megoldás egyszerű: térjünk vissza az alapokhoz, és használjunk csak paramétereket: +Ilyen kóddal nem akarunk találkozni, és határozottan nem akarunk ilyet írni. A javítás egyszerű: térjünk vissza az alapokhoz, és egyszerűen használjunk paramétereket: ```php -function osszeg(float $a, float $b): float +function soucet(float $a, float $b): float { return $a + $b; } ``` -1. szabály: Hadd adassák át neked .[#toc-rule-1-let-it-be-passed-to-you] ------------------------------------------------------------------------- +1. szabály: Kérd el +------------------- -A legfontosabb szabály: **minden adatot, amelyre a függvényeknek vagy osztályoknak szükségük van, át kell adni nekik**. +A legfontosabb szabály: **minden adatot, amire egy függvénynek vagy osztálynak szüksége van, át kell adni neki**. -Ahelyett, hogy rejtett módokat találnának ki, hogy maguk férjenek hozzá az adatokhoz, egyszerűen adja át a paramétereket. Ezzel időt takarít meg, amelyet rejtett utak kitalálásával töltene, amelyek biztosan nem javítanának a kódján. +Ahelyett, hogy rejtett módokat találnál ki, amelyekkel maguk is hozzáférhetnének, egyszerűen add át a paramétereket. Időt takarítasz meg a rejtett utak kitalálásával, amelyek biztosan nem javítják a kódodat. -Ha mindig és mindenhol követi ezt a szabályt, akkor máris úton van a rejtett függőségek nélküli kód felé. Olyan kódhoz, amely nemcsak a szerző, hanem mindenki számára érthető, aki utólag elolvassa. Ahol minden érthető a függvények és osztályok szignatúrájából, és nem kell rejtett titkokat keresni az implementációban. +Ha ezt a szabályt mindig és mindenhol betartod, úton vagy a rejtett függőségek nélküli kód felé. Egy olyan kód felé, amely nemcsak a szerző számára érthető, hanem bárki számára is, aki utána olvassa. Ahol minden érthető a függvények és osztályok szignatúráiból, és nem kell rejtett titkok után kutatni a megvalósításban. -Ezt a technikát szaknyelven **függőségi injektálásnak** nevezik. Ezeket az adatokat pedig **függőségeknek** nevezik. Ez csak közönséges paraméterátadás, semmi több. +Ezt a technikát szakmailag **dependency injection**-nek (függőséginjektálás) nevezik. És ezeket az adatokat **függőségeknek** (dependencies). Valójában ez csak egyszerű paraméterátadás, semmi több. .[note] -Kérlek, ne keverd össze a függőségi injektálást, ami egy tervezési minta, a "függőségi injektálási konténerrel", ami egy eszköz, valami szögesen más. A konténerekkel később fogunk foglalkozni. +Kérjük, ne keverje össze a dependency injection-t, ami egy tervezési minta, a „dependency injection container”-rel, ami egy eszköz, tehát valami gyökeresen más. A konténerekkel később foglalkozunk. -A függvényektől az osztályokig .[#toc-from-functions-to-classes] ----------------------------------------------------------------- +Függvényektől az osztályokig +---------------------------- -És hogyan kapcsolódnak az osztályok? Egy osztály összetettebb egység, mint egy egyszerű függvény, de az 1. szabály itt is teljes mértékben érvényes. Csak [több módja van az argumentumok átadásának |passing-dependencies]. Például egészen hasonlóan, mint egy függvény esetében: +És hogyan kapcsolódik ez az osztályokhoz? Az osztály egy összetettebb egység, mint egy egyszerű függvény, de az 1. szabály itt is maradéktalanul érvényes. Csak [több lehetőség van az argumentumok átadására|passing-dependencies]. Például egészen hasonlóan, mint egy függvénynél: ```php class Matematika { - public function osszeg(float $a, float $b): float + public function soucet(float $a, float $b): float { return $a + $b; } } $math = new Matematika; -echo $math->osszeg(23, 1); // 24 +echo $math->soucet(23, 1); // 24 ``` -Vagy más metódusokon keresztül, vagy közvetlenül a konstruktoron keresztül: +Vagy más metódusokkal, vagy közvetlenül a konstruktorral: ```php -class Osszeg +class Soucet { public function __construct( private float $a, @@ -132,24 +132,24 @@ class Osszeg ) { } - public function calculate(): float + public function spocti(): float { return $this->a + $this->b; } } -$osszeg = new Osszeg(23, 1); -echo $osszeg->calculate(); // 24 +$soucet = new Soucet(23, 1); +echo $soucet->spocti(); // 24 ``` -Mindkét példa teljesen megfelel a függőségi injektálásnak. +Mindkét példa teljes mértékben összhangban van a dependency injection elvével. -Valós életbeli példák .[#toc-real-life-examples] ------------------------------------------------- +Valós példák +------------ -A való világban nem fogsz számok összeadására szolgáló osztályokat írni. Térjünk át a gyakorlati példákra. +A való világban nem fogsz osztályokat írni számok összeadására. Térjünk át a gyakorlati példákra. Legyen egy `Article` osztályunk, amely egy blogbejegyzést reprezentál: @@ -162,7 +162,7 @@ class Article public function save(): void { - // a cikk elmentése az adatbázisba + // elmentjük a cikket az adatbázisba } } ``` @@ -171,14 +171,14 @@ class Article ```php $article = new Article; -$article->title = '10 Things You Need to Know About Losing Weight'; -$article->content = 'Every year millions of people in ...'; +$article->title = '10 dolog, amit tudnod kell a fogyásról'; +$article->content = 'Minden évben emberek milliói ...'; $article->save(); ``` -A `save()` módszer elmenti a cikket egy adatbázis-táblába. Ennek megvalósítása a [Nette Database |database:] segítségével gyerekjáték lesz, ha nem lenne egy bökkenő: honnan szerzi meg a `Article` az adatbázis-kapcsolatot, azaz egy `Nette\Database\Connection` osztályú objektumot ? +A `save()` metódus elmenti a cikket egy adatbázis táblába. A [Nette Database |database:] segítségével megvalósítani gyerekjáték lenne, ha nem lenne egy bökkenő: honnan veszi az `Article` az adatbázis-kapcsolatot, azaz a `Nette\Database\Connection` osztály objektumát? -Úgy tűnik, rengeteg lehetőségünk van. Valahonnan egy statikus változóból is veheti. Vagy egy olyan osztályból örökli, amely adatbázis-kapcsolatot biztosít. Vagy kihasználja egy [singleton |global-state#Singleton] előnyeit. Vagy használhatunk úgynevezett facades-t, amit a Laravelben használnak: +Úgy tűnik, sok lehetőségünk van. Veheti valahonnan egy statikus változóból. Vagy örökölhet egy olyan osztálytól, amely biztosítja az adatbázis-kapcsolatot. Vagy használhatja az úgynevezett [singleton |global-state#Singleton] mintát. Vagy az úgynevezett facades-okat, amelyeket a Laravelben használnak: ```php use Illuminate\Support\Facades\DB; @@ -201,15 +201,15 @@ class Article Nagyszerű, megoldottuk a problémát. -Vagy mégis? +Vagy mégsem? -Emlékezzünk vissza az [1. szabályra: Legyen átadva |#rule #1: Let It Be Passed to You]: minden függőséget, amire az osztálynak szüksége van, át kell adni neki. Mert ha megszegjük ezt a szabályt, akkor elindultunk a rejtett függőségekkel teli, piszkos kódhoz vezető úton, amely tele van rejtett függőségekkel, érthetetlenséggel, és az eredmény egy olyan alkalmazás lesz, amelyet fájdalmas lesz karbantartani és fejleszteni. +Idézzük fel az [##1. szabály: Kérd el]: minden függőséget, amire az osztálynak szüksége van, át kell adni neki. Mert ha megszegjük a szabályt, a piszkos kód útjára léptünk, tele rejtett függőségekkel, érthetetlenséggel, és az eredmény egy olyan alkalmazás lesz, amelyet fájdalmas lesz karbantartani és fejleszteni. -A `Article` osztály felhasználójának fogalma sincs arról, hogy a `save()` metódus hol tárolja a cikket. Egy adatbázis táblában? Melyikben, a termelési vagy a tesztelésben? És hogyan lehet ezt megváltoztatni? +Az `Article` osztály felhasználója nem tudja, hova menti a `save()` metódus a cikket. Adatbázis táblába? Melyikbe, az élesbe vagy a tesztbe? És hogyan lehet ezt megváltoztatni? -A felhasználónak meg kell néznie, hogyan van implementálva a `save()` metódus, és meg kell találnia a `DB::insert()` metódus használatát. Tehát tovább kell keresnie, hogy megtudja, hogyan szerez ez a módszer adatbázis-kapcsolatot. A rejtett függőségek pedig elég hosszú láncot alkothatnak. +A felhasználónak meg kell néznie, hogyan van implementálva a `save()` metódus, és megtalálja a `DB::insert()` metódus használatát. Tehát tovább kell kutatnia, hogyan szerzi be ez a metódus az adatbázis-kapcsolatot. És a rejtett függőségek elég hosszú láncot alkothatnak. -A tiszta és jól megtervezett kódban soha nincsenek rejtett függőségek, Laravel-facadek vagy statikus változók. A tiszta és jól megtervezett kódban az argumentumok átadásra kerülnek: +A tiszta és jól megtervezett kódban soha nincsenek rejtett függőségek, Laravel facade-ok vagy statikus változók. A tiszta és jól megtervezett kódban argumentumokat adnak át: ```php class Article @@ -224,7 +224,7 @@ class Article } ``` -Egy még praktikusabb megközelítés, mint később látni fogjuk, a konstruktoron keresztül történik: +Még praktikusabb lesz, ahogy később látni fogjuk, a konstruktorral: ```php class Article @@ -245,11 +245,11 @@ class Article ``` .[note] -Ha tapasztalt programozó vagy, azt gondolhatod, hogy a `Article` egyáltalán nem kellene, hogy rendelkezzen `save()` metódussal; tisztán adatkomponenst kellene képviselnie, és egy külön tárolónak kellene gondoskodnia a mentésről. Ennek van is értelme. De ez messze túlmutatna a téma keretein, ami a függőségi injektálás, és az egyszerű példák bemutatására tett erőfeszítésen. +Ha tapasztalt programozó vagy, talán azt gondolod, hogy az `Article`-nek egyáltalán nem kellene `save()` metódussal rendelkeznie, tisztán adatkomponensnek kellene lennie, és a mentésről egy különálló repositorynak kellene gondoskodnia. Ennek van értelme. De ezzel messze túllépnénk a témán, ami a dependency injection, és az egyszerű példák bemutatására tett erőfeszítésen. -Ha olyan osztályt írsz, aminek a működéséhez például adatbázisra van szüksége, ne találd ki, honnan szerzed be, hanem legyen átadva. Vagy a konstruktor paramétereként, vagy egy másik metódus paramétereként. Ismerd el a függőségeket. Ismerd el őket az osztályod API-jában. Érthető és kiszámítható kódot fogsz kapni. +Ha olyan osztályt írsz, amelynek a működéséhez például adatbázisra van szüksége, ne azon gondolkodj, honnan szerezd be, hanem kérd el. Például a konstruktor vagy egy másik metódus paramétereként. Ismerd el a függőségeket. Ismerd el őket az osztályod API-jában. Érthető és kiszámítható kódot kapsz. -És mi a helyzet ezzel az osztállyal, amely hibaüzeneteket naplóz: +És mi van ezzel az osztállyal, amely hibaüzeneteket naplóz: ```php class Logger @@ -262,23 +262,23 @@ class Logger } ``` -Mit gondolsz, betartottuk az [1. számú szabályt: hadd adassák át neked |#rule #1: Let It Be Passed to You]? +Mit gondolsz, betartottuk az [##1. szabály: Kérd el]? -Nem tettük meg. +Nem tartottuk be. -A kulcsinformációt, azaz a naplófájlt tartalmazó könyvtárat maga az osztály *kapja* a konstansból. +A kulcsinformációt, azaz a naplófájlt tartalmazó könyvtárat, az osztály *maga szerzi be* egy konstansból. Nézd meg a használati példát: ```php $logger = new Logger; -$logger->log('The temperature is 23 °C'); -$logger->log('The temperature is 10 °C'); +$logger->log('A hőmérséklet 23 °C'); +$logger->log('A hőmérséklet 10 °C'); ``` -A megvalósítás ismerete nélkül tudna válaszolni arra a kérdésre, hogy hová íródnak az üzenetek? Gondolnád, hogy a `LOG_DIR` állandó létezése szükséges a működéséhez? És tudnál-e létrehozni egy második példányt, amely más helyre írna? Biztosan nem. +Az implementáció ismerete nélkül tudnál válaszolni arra a kérdésre, hogy hova íródnak az üzenetek? Eszedbe jutna, hogy a működéshez szükség van a `LOG_DIR` konstans létezésére? És tudnál létrehozni egy második példányt, amely máshova ír? Biztosan nem. -Javítsuk meg az osztályt: +Javítsuk ki az osztályt: ```php class Logger @@ -295,24 +295,24 @@ class Logger } ``` -Az osztály most már sokkal érthetőbb, konfigurálhatóbb és ezáltal hasznosabb. +Az osztály most sokkal érthetőbb, konfigurálhatóbb és ezáltal hasznosabb. ```php -$logger = new Logger('/path/to/log.txt'); -$logger->log('The temperature is 15 °C'); +$logger = new Logger('/útvonal/a/naplóhoz.txt'); +$logger->log('A hőmérséklet 15 °C'); ``` -De nem érdekel! .[#toc-but-i-don-t-care] ----------------------------------------- +De ez engem nem érdekel! +------------------------ -*"Amikor létrehozok egy cikkobjektumot, és meghívom a save() parancsot, nem akarok foglalkozni az adatbázissal; csak azt akarom, hogy abba az adatbázisba kerüljön elmentésre, amelyet a konfigurációban beállítottam."* +*"Amikor létrehozok egy Article objektumot és meghívom a save()-t, nem akarok az adatbázissal foglalkozni, egyszerűen azt akarom, hogy abba mentse el, amit a konfigurációban beállítottam."* -*"Amikor a Logger-t használom, csak azt akarom, hogy az üzenet kiírásra kerüljön, és nem akarok foglalkozni azzal, hogy hova. Legyen a globális beállítások használata."* +*"Amikor a Logger-t használom, egyszerűen azt akarom, hogy az üzenet íródjon ki, és nem akarom megoldani, hogy hova. Használja a globális beállítást."* -Ezek jogos észrevételek. +Ezek helyes észrevételek. -Példaként nézzünk meg egy olyan osztályt, amely hírleveleket küld és naplózza, hogyan ment: +Példaként egy hírleveleket küldő osztályt mutatunk be, amely naplózza, hogyan sikerült: ```php class NewsletterDistributor @@ -322,21 +322,21 @@ class NewsletterDistributor $logger = new Logger(/* ... */); try { $this->sendEmails(); - $logger->log('Emails have been sent out'); + $logger->log('Az e-mailek elküldve'); } catch (Exception $e) { - $logger->log('An error occurred during the sending'); + $logger->log('Hiba történt a küldés során'); throw $e; } } } ``` -A továbbfejlesztett `Logger`, amely már nem használja a `LOG_DIR` konstanst, megköveteli a fájl elérési útvonalának megadását a konstruktorban. Hogyan lehet ezt megoldani? A `NewsletterDistributor` osztályt nem érdekli, hogy az üzenetek hova íródnak, csak írni akarja őket. +A továbbfejlesztett `Logger`, amely már nem használja a `LOG_DIR` konstansot, a konstruktorban megköveteli a fájl elérési útjának megadását. Hogyan oldjuk ezt meg? A `NewsletterDistributor` osztályt egyáltalán nem érdekli, hova íródnak az üzenetek, csak ki akarja írni őket. -A megoldás ismét az [1. szabály: Hagyd, hogy átadják n |#rule #1: Let It Be Passed to You]eked: adj át minden adatot, amire az osztálynak szüksége van. +A megoldás ismét az [##1. szabály: Kérd el]: minden adatot, amire az osztálynak szüksége van, átadunk neki. -Ez tehát azt jelenti, hogy a konstruktoron keresztül átadjuk a napló elérési útvonalát, amit aztán a `Logger` objektum létrehozásakor használunk? +Tehát ez azt jelenti, hogy a konstruktoron keresztül átadjuk a napló elérési útját, amelyet aztán a `Logger` objektum létrehozásakor használunk? ```php class NewsletterDistributor @@ -351,7 +351,7 @@ class NewsletterDistributor $logger = new Logger($this->file); ``` -Nem, nem így! Az útvonal nem tartozik a `NewsletterDistributor` osztály számára szükséges adatok közé, sőt, a `Logger` osztálynak van rá szüksége. Látod a különbséget? A `NewsletterDistributor` osztálynak magára a naplózóra van szüksége. Tehát ezt fogjuk átadni: +Nem így! Az elérési út ugyanis **nem tartozik** azok közé az adatok közé, amelyekre a `NewsletterDistributor` osztálynak szüksége van; azokra ugyanis a `Logger`-nek van szüksége. Érzed a különbséget? A `NewsletterDistributor` osztálynak magára a loggerre van szüksége. Tehát azt adjuk át: ```php class NewsletterDistributor @@ -365,35 +365,33 @@ class NewsletterDistributor { try { $this->sendEmails(); - $this->logger->log('Emails have been sent out'); + $this->logger->log('Az e-mailek elküldve'); } catch (Exception $e) { - $this->logger->log('An error occurred during the sending'); + $this->logger->log('Hiba történt a küldés során'); throw $e; } } } ``` -A `NewsletterDistributor` osztály aláírásaiból egyértelmű, hogy a naplózás is része a funkcióinak. És a logger cseréje egy másikra, esetleg tesztelés céljából, teljesen triviális feladat. -Ráadásul, ha a `Logger` osztály konstruktora megváltozik, az nem befolyásolja a mi osztályunkat. +Most már a `NewsletterDistributor` osztály szignatúráiból világos, hogy a funkcionalitásának része a naplózás is. És a logger cseréjének feladata egy másikra, például tesztelés céljából, teljesen triviális. Ráadásul, ha a `Logger` osztály konstruktora megváltozna, az nem lenne hatással az osztályunkra. -2. szabály: Vedd el, ami a tiéd .[#toc-rule-2-take-what-s-yours] ----------------------------------------------------------------- +2. szabály: Vedd el, ami a tiéd +------------------------------- -Ne hagyd magad félrevezetni, és ne engedd át magad a függőségeid függőségein. Csak a saját függőségeidet add át. +Ne hagyd magad megtéveszteni, és ne kérd a függőségeid függőségeinek átadását. Csak a saját függőségeidet kérd el. -Ennek köszönhetően a más objektumokat használó kód teljesen független lesz a konstruktoraikban bekövetkező változásoktól. Az API-ja sokkal igazabb lesz. És mindenekelőtt triviális lesz ezeket a függőségeket másokkal helyettesíteni. +Ennek köszönhetően a más objektumokat használó kód teljesen független lesz a konstruktoraik változásaitól. Az API-ja igazabb lesz. És főleg triviális lesz ezeket a függőségeket másokra cserélni. -Új családtag .[#toc-new-family-member] --------------------------------------- +Új családtag +------------ -A fejlesztőcsapat úgy döntött, hogy létrehoz egy második loggert, amely az adatbázisba ír. Ezért létrehozunk egy `DatabaseLogger` osztályt. Tehát két osztályunk van, `Logger` és `DatabaseLogger`, az egyik egy fájlba ír, a másik az adatbázisba ... nem tűnik furcsának az elnevezés? -Nem lenne jobb átnevezni a `Logger` -t `FileLogger`-re ? Határozottan igen. +A fejlesztői csapat úgy döntött, hogy létrehoz egy második loggert, amely adatbázisba ír. Tehát létrehozunk egy `DatabaseLogger` osztályt. Így van két osztályunk, a `Logger` és a `DatabaseLogger`, az egyik fájlba ír, a másik adatbázisba… nem tűnik valami furcsának az elnevezés? Nem lenne jobb átnevezni a `Logger`-t `FileLogger`-re? Biztosan igen. -De tegyük ezt okosan. Létrehozunk egy interfészt az eredeti név alatt: +De okosan csináljuk. Az eredeti név alatt létrehozunk egy interfészt: ```php interface Logger @@ -402,7 +400,7 @@ interface Logger } ``` -... amelyet mindkét logger végrehajt: +… amelyet mindkét logger implementálni fog: ```php class FileLogger implements Logger @@ -412,17 +410,17 @@ class DatabaseLogger implements Logger // ... ``` -És emiatt nem kell semmit sem változtatni a kód többi részén, ahol a naplózót használjuk. Például a `NewsletterDistributor` osztály konstruktora továbbra is megelégszik azzal, hogy paraméterként a `Logger` címet kéri. Az pedig csak rajtunk múlik majd, hogy melyik példányt adjuk át. +Ennek köszönhetően nem kell semmit sem változtatni a kód többi részében, ahol a loggert használják. Például a `NewsletterDistributor` osztály konstruktora továbbra is elégedett lesz azzal, hogy paraméterként `Logger`-t igényel. És csak rajtunk múlik, melyik példányt adjuk át neki. -**Ez az oka annak, hogy soha nem adjuk hozzá a `Interface` utótagot vagy a `I` előtagot az interfésznevekhez.** Máskülönben nem lehetne ilyen szépen fejleszteni a kódot. +**Ezért soha nem adunk az interfészek nevéhez `Interface` utótagot vagy `I` előtagot.** Különben nem lehetne a kódot ilyen szépen fejleszteni. -Houston, van egy problémánk .[#toc-houston-we-have-a-problem] -------------------------------------------------------------- +Houston, van egy problémánk +--------------------------- -Míg a logger egyetlen példányával - legyen az fájl- vagy adatbázis-alapú - az egész alkalmazásban boldogulhatunk, és egyszerűen átadhatjuk azt mindenhol, ahol valamit naplózni kell, addig a `Article` osztály esetében ez teljesen másképp van. Annak példányait szükség szerint hozzuk létre, akár többször is. Hogyan kezeljük az adatbázis-függőséget a konstruktorában? +Míg az egész alkalmazásban megelégedhetünk egyetlen logger példánnyal, legyen az fájl- vagy adatbázis-alapú, és egyszerűen átadjuk mindenhol, ahol valami naplózásra kerül, egészen más a helyzet az `Article` osztály esetében. Ennek példányait ugyanis szükség szerint hozzuk létre, akár többször is. Hogyan kezeljük az adatbázis-függőséget a konstruktorában? -Egy példa lehet egy olyan vezérlő, amelynek egy űrlap elküldése után egy cikket kell elmentenie az adatbázisba: +Példaként szolgálhat egy kontroller, amelynek egy űrlap elküldése után el kell mentenie a cikket az adatbázisba: ```php class EditController extends Controller @@ -437,30 +435,30 @@ class EditController extends Controller } ``` -Egy lehetséges megoldás kézenfekvő: adjuk át az adatbázis-objektumot a `EditController` konstruktornak, és használjuk a `$article = new Article($this->db)` címet. +Egy lehetséges megoldás közvetlenül adódik: átadjuk az adatbázis objektumot a konstruktoron keresztül az `EditController`-nek, és használjuk a `$article = new Article($this->db)` kódot. -Csakúgy, mint az előző esetben a `Logger` és a fájl elérési útvonalával, ez nem a helyes megközelítés. Az adatbázis nem a `EditController`, hanem a `Article` függvénye. Az adatbázis átadása ellentétes a [2. szabállyal: vedd el, ami a tiéd |#rule #2: take what's yours]. Ha a `Article` osztály konstruktora megváltozik (új paramétert adunk hozzá), akkor a kódot módosítani kell, ahol a példányok létrehozására sor kerül. Ufff. +Ahogy az előző esetben a `Logger`-rel és a fájl elérési útjával, ez sem a helyes megközelítés. Az adatbázis nem az `EditController` függősége, hanem az `Article`-é. Az adatbázis átadása tehát ellentétes a [#2. szabály: Vedd el, ami a tiéd] szabállyal. Ha az `Article` osztály konstruktora megváltozik (új paraméter kerül hozzáadásra), akkor a kódot is módosítani kell mindenhol, ahol példányt hoznak létre. Pfff. Houston, mit javasolsz? -3. szabály: Hagyd, hogy a gyár kezelje a dolgot .[#toc-rule-3-let-the-factory-handle-it] ----------------------------------------------------------------------------------------- +3. szabály: Hagyd a factory-ra +------------------------------ -A rejtett függőségek kiküszöbölésével és az összes függőség argumentumként való átadásával sokkal konfigurálhatóbb és rugalmasabb osztályokat kaptunk. És ezért szükségünk van valami másra, ami ezeket a rugalmasabb osztályokat létrehozza és konfigurálja helyettünk. Ezt nevezzük gyáraknak. +Azzal, hogy megszüntettük a rejtett függőségeket, és minden függőséget argumentumként adunk át, konfigurálhatóbb és rugalmasabb osztályokat kaptunk. És ezért szükségünk van még valamire, ami létrehozza és konfigurálja nekünk ezeket a rugalmasabb osztályokat. Ezt factory-nak (gyárnak) fogjuk nevezni. -Az ökölszabály a következő: ha egy osztály függőségekkel rendelkezik, a példányaik létrehozását bízzuk a gyárra. +A szabály így szól: ha egy osztálynak függőségei vannak, hagyd a példányok létrehozását a factory-ra. -A gyárak a `new` operátor okosabb helyettesítői a függőségi injektálás világában. +A factory-k az `new` operátor okosabb helyettesítői a dependency injection világában. .[note] -Kérjük, ne keverjük össze a *factory method* tervezési mintával, amely a gyárak használatának egy speciális módját írja le, és nem kapcsolódik ehhez a témához. +Kérjük, ne keverje össze a *factory method* tervezési mintával, amely a factory-k specifikus felhasználási módját írja le, és nem kapcsolódik ehhez a témához. -Gyár .[#toc-factory] --------------------- +Factory +------- -A gyár egy olyan metódus vagy osztály, amely objektumokat hoz létre és konfigurál. A `Article` -t előállító osztályt `ArticleFactory`-nek fogjuk nevezni, és így nézhet ki: +A factory egy metódus vagy osztály, amely objektumokat gyárt és konfigurál. Az `Article`-t gyártó osztályt `ArticleFactory`-nak nevezzük, és például így nézhet ki: ```php class ArticleFactory @@ -477,7 +475,7 @@ class ArticleFactory } ``` -A vezérlőben való használata a következő: +Használata a kontrollerben a következő lesz: ```php class EditController extends Controller @@ -489,7 +487,7 @@ class EditController extends Controller public function formSubmitted($data) { - // hagyja, hogy a gyár létrehozzon egy objektumot + // hagyjuk, hogy a factory hozza létre az objektumot $article = $this->articleFactory->create(); $article->title = $data->title; $article->content = $data->content; @@ -498,11 +496,11 @@ class EditController extends Controller } ``` -Ezen a ponton, ha a `Article` osztály konstruktorának aláírása megváltozik, a kód egyetlen része, amelyre reagálnia kell, maga a `ArticleFactory`. A `Article` objektumokkal dolgozó minden más kódot, például a `EditController`, ez nem érinti. +Ha ebben a pillanatban megváltozik az `Article` osztály konstruktorának szignatúrája, az egyetlen kódrészlet, amelynek reagálnia kell rá, maga a `ArticleFactory`. Minden más kód, amely `Article` objektumokkal dolgozik, mint például az `EditController`, ettől érintetlen marad. -Talán elgondolkodik azon, hogy valóban jobbá tettük-e a dolgokat. A kód mennyisége megnőtt, és az egész kezd gyanúsan bonyolultnak tűnni. +Talán most a homlokodra csapsz, hogy egyáltalán segítettünk-e magunkon. A kód mennyisége megnőtt, és az egész kezd gyanúsan bonyolultnak tűnni. -Ne aggódjon, hamarosan eljutunk a Nette DI konténerhez. És ez számos trükköt tartogat a tarsolyában, ami nagyban leegyszerűsíti a függőségi injektálást használó alkalmazások építését. Például a `ArticleFactory` osztály helyett csak [egy egyszerű interfészt |factory] kell [írni |factory]: +Ne aggódj, hamarosan eljutunk a Nette DI konténerhez. És annak számos aduásza van a tarsolyában, amelyek rendkívül leegyszerűsítik a dependency injectiont használó alkalmazások építését. Például az `ArticleFactory` osztály helyett elég lesz [csak egy interfészt írni |factory]: ```php interface ArticleFactory @@ -511,18 +509,18 @@ interface ArticleFactory } ``` -De most már elébe megyünk a dolgoknak, kérlek, legyetek türelemmel :-) +De ezzel előreszaladunk, még tarts ki :-) -Összefoglaló .[#toc-summary] ----------------------------- +Összegzés +--------- -A fejezet elején azt ígértük, hogy bemutatjuk a tiszta kód tervezésének folyamatát. Mindössze annyit kell tennünk, hogy az osztályok: +Ennek a fejezetnek az elején azt ígértük, hogy bemutatunk egy módszert a tiszta kód tervezésére. Elég az osztályoknak -- [átadják a szükséges függőségeket |#Rule #1: Let It Be Passed to You] -- [fordítva, ne adják át azt, amire nincs közvetlen szükségük |#Rule #2: Take What's Yours] -- [és hogy a függőségekkel rendelkező objektumokat a legjobb gyárakban létrehozni |#Rule #3: Let the Factory Handle it] +1) [átadni a szükséges függőségeket |#1. szabály: Kérd el] +2) [és fordítva, nem átadni azt, amire közvetlenül nincs szükségük |#2. szabály: Vedd el ami a tiéd] +3) [és hogy a függőségekkel rendelkező objektumokat a legjobban factory-kban lehet létrehozni |#3. szabály: Hagyd a factory-ra] -Első pillantásra úgy tűnhet, hogy ennek a három szabálynak nincsenek messzemenő következményei, de a kódtervezés gyökeresen más szemléletéhez vezetnek. Megéri ez? Azok a fejlesztők, akik felhagytak a régi szokásokkal, és következetesen használni kezdték a függőségi injektálást, ezt a lépést szakmai életük döntő pillanatának tekintik. Megnyitotta előttük a letisztult és karbantartható alkalmazások világát. +Első pillantásra talán nem tűnik úgy, de ennek a három szabálynak messzemenő következményei vannak. Radikálisan más nézőponthoz vezetnek a kódtervezésben. Megéri? Azok a programozók, akik elhagyták régi szokásaikat és következetesen elkezdték használni a dependency injectiont, ezt a lépést szakmai életük kulcsfontosságú pillanatának tartják. Megnyílt előttük az áttekinthető és karbantartható alkalmazások világa. -De mi van akkor, ha a kód nem használja következetesen a függőségi injektálást? Mi van, ha statikus metódusokra vagy singletonokra támaszkodik? Okoz ez problémát? [Igen, és nagyon alapvető problémákat okoz |global-state]. +De mi van, ha a kód nem használja következetesen a dependency injectiont? Mi van, ha statikus metódusokra vagy singletonokra épül? Ez okoz valamilyen problémát? [Igen, és nagyon alapvetőeket |global-state]. diff --git a/dependency-injection/hu/nette-container.texy b/dependency-injection/hu/nette-container.texy index 24f183dfc6..ffcccc429a 100644 --- a/dependency-injection/hu/nette-container.texy +++ b/dependency-injection/hu/nette-container.texy @@ -1,10 +1,10 @@ -Nette DI konténer -***************** +Nette DI Container +****************** .[perex] -A Nette DI az egyik legérdekesebb Nette könyvtár. Képes lefordított DI konténereket generálni és automatikusan frissíteni, amelyek rendkívül gyorsak és elképesztően könnyen konfigurálhatók. +A Nette DI a Nette egyik legérdekesebb könyvtára. Képes generálni és automatikusan frissíteni a lefordított DI konténereket, amelyek rendkívül gyorsak és elképesztően könnyen konfigurálhatók. -A DI konténer által létrehozandó szolgáltatásokat általában [NEON formátumú |neon:format] konfigurációs fájlok segítségével határozzuk meg. Az [előző |container] szakaszban kézzel létrehozott konténer a következőképpen lenne leírva: +A DI konténer által létrehozandó szolgáltatások formáját általában konfigurációs fájlokban definiáljuk [NEON formátumban|neon:format]. A konténer, amelyet manuálisan hoztunk létre az [előző fejezetben|container], így íródna le: ```neon parameters: @@ -19,16 +19,15 @@ services: - UserController ``` -A jelölés valóban rövid. +A leírás valóban tömör. -A `ArticleFactory` és `UserController` osztályok konstruktoraiban deklarált összes függőséget maga a Nette DI találja meg és adja át az úgynevezett [autowiringnek |autowiring] köszönhetően, így a konfigurációs fájlban nem kell semmit sem megadni. -Tehát még ha a paraméterek változnak is, akkor sem kell semmit sem változtatni a konfigurációban. A Nette automatikusan újratermeli a konténert. Ott tisztán az alkalmazásfejlesztésre koncentrálhat. +Az `ArticleFactory` és `UserController` osztályok konstruktoraiban deklarált összes függőséget a Nette DI maga kideríti és átadja az úgynevezett [autowiring|autowiring] segítségével, ezért a konfigurációs fájlban semmit sem kell megadni. Tehát még ha a paraméterek megváltoznak is, a konfigurációban semmit sem kell módosítani. A Nette konténer automatikusan újragenerálódik. Ön így tisztán az alkalmazás fejlesztésére koncentrálhat. -Ha függőségeket akarsz átadni setterek segítségével, akkor használd a [setup |services#setup] szekciót erre a célra. +Ha a függőségeket setterek segítségével szeretnénk átadni, használjuk a [setup |services#Setup] szekciót. -A Nette DI közvetlenül generálja a PHP kódot a konténerhez. Az eredmény így egy `.php` fájl, amelyet megnyithat és tanulmányozhat. Így pontosan láthatja, hogyan működik a konténer. Az IDE-ben is debugolhatod és végiglépkedhetsz rajta. És ami a legfontosabb: a generált PHP rendkívül gyors. +A Nette DI közvetlenül PHP kódot generál a konténerhez. Az eredmény tehát egy `.php` fájl, amelyet megnyithat és tanulmányozhat. Ennek köszönhetően pontosan láthatja, hogyan működik a konténer. Debuggolhatja is az IDE-ben és lépésenként végigkövetheti. És ami a legfontosabb: a generált PHP rendkívül gyors. -A Nette DI képes [gyári |factory] kódot is generálni a mellékelt interfész alapján. Ezért a `ArticleFactory` osztály helyett csak egy interfészt kell létrehoznunk az alkalmazásban: +A Nette DI képes [factory|factory] kódot is generálni egy megadott interfész alapján. Ezért az `ArticleFactory` osztály helyett elég lesz csak egy interfészt létrehozni az alkalmazásban: ```php interface ArticleFactory @@ -37,19 +36,19 @@ interface ArticleFactory } ``` -A teljes példa megtalálható a [GitHubon |https://github.com/nette-examples/di-example-doc]. +A teljes példát megtalálja [GitHubon|https://github.com/nette-examples/di-example-doc]. -Önálló használat .[#toc-standalone-use] ---------------------------------------- +Önálló használat +---------------- -A Nette DI könyvtár felhasználása egy alkalmazásban nagyon egyszerű. Először is telepítsük a Composer segítségével (mert a zip fájlok letöltése annyira elavult): +A Nette DI könyvtár bevezetése egy alkalmazásba nagyon egyszerű. Először telepítjük a Composerrel (mert a zip fájlok letöltése annyira elavult): ```shell composer require nette/di ``` -A következő kód létrehozza a DI konténer egy példányát a `config.neon` fájlban tárolt konfigurációnak megfelelően: +A következő kód létrehoz egy DI konténer példányt a `config.neon` fájlban tárolt konfiguráció alapján: ```php $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp'); @@ -59,24 +58,23 @@ $class = $loader->load(function ($compiler) { $container = new $class; ``` -A konténer csak egyszer jön létre, kódja a gyorsítótárba (a `__DIR__ . '/temp'` könyvtárba) íródik, és a későbbi kéréseknél csak onnan olvassa be. +A konténer csak egyszer generálódik le, a kódja a cache-be íródik (a `__DIR__ . '/temp'` könyvtárba), és a további kéréseknél már csak innen töltődik be. -A `getService()` vagy a `getByType()` metódusok a szolgáltatások létrehozására és lekérdezésére szolgálnak. Így hozzuk létre a `UserController` objektumot: +A szolgáltatások létrehozására és lekérésére a `getService()` vagy a `getByType()` metódusok szolgálnak. Így hozunk létre egy `UserController` objektumot: ```php -$database = $container->getByType(UserController::class); -$database->query('...'); +$controller = $container->getByType(UserController::class); +$controller->someMethod(); ``` -A fejlesztés során hasznos engedélyezni az automatikus frissítési módot, amikor a konténer automatikusan újratermelődik, ha bármelyik osztály vagy konfigurációs fájl megváltozik. Csak adjuk meg a `true` címet második argumentumként a `ContainerLoader` konstruktorban. +Fejlesztés közben hasznos aktiválni az auto-refresh módot, amelyben a konténer automatikusan újragenerálódik, ha bármelyik osztály vagy konfigurációs fájl megváltozik. Ehhez elég a `ContainerLoader` konstruktorában második argumentumként `true`-t megadni. ```php $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp', true); ``` -Használat a Nette keretrendszerrel .[#toc-using-it-with-the-nette-framework] ----------------------------------------------------------------------------- +Használat a Nette keretrendszerrel +---------------------------------- -Amint megmutattuk, a Nette DI használata nem korlátozódik a Nette Frameworkben írt alkalmazásokra, bárhol bevethető mindössze 3 sornyi kóddal. -Ha azonban a Nette Frameworkben fejlesztünk alkalmazásokat, a konténer konfigurálását és létrehozását a [Bootstrap |application:bootstrap#toc-di-container-configuration] kezeli. +Ahogy bemutattuk, a Nette DI használata nem korlátozódik a Nette Frameworkben írt alkalmazásokra, mindössze 3 sor kóddal bárhol bevethető. Ha azonban alkalmazásokat fejleszt a Nette Frameworkben, a konténer konfigurálását és létrehozását a [Bootstrap |application:bootstrapping#DI konténer konfigurálása] végzi. diff --git a/dependency-injection/hu/passing-dependencies.texy b/dependency-injection/hu/passing-dependencies.texy index a426543d32..6d33c4c5f1 100644 --- a/dependency-injection/hu/passing-dependencies.texy +++ b/dependency-injection/hu/passing-dependencies.texy @@ -3,22 +3,22 @@ Függőségek átadása <div class=perex> -Az argumentumok, vagy DI terminológiában "függőségek", a következő főbb módokon adhatók át az osztályoknak: +Az argumentumokat, vagy a DI terminológiájában „függőségeket”, a következő fő módokon lehet átadni az osztályoknak: -* konstruktor általi átadás -* átadás metóduson keresztül (úgynevezett setter) -* egy tulajdonság beállításával -* módszerrel, annotációval vagy attribútummal *injektálással*. +* konstruktoron keresztüli átadás +* metóduson (úgynevezett setteren) keresztüli átadás +* property beállításával +* *inject* metódussal, annotációval vagy attribútummal </div> -Most konkrét példákkal illusztráljuk a különböző változatokat. +Most az egyes változatokat konkrét példákon mutatjuk be. -Konstruktor-befecskendezés .[#toc-constructor-injection] -======================================================== +Konstruktoron keresztüli átadás +=============================== -A függőségek az objektum létrehozásakor argumentumként kerülnek átadásra a konstruktornak: +A függőségek az objektum létrehozásának pillanatában kerülnek átadásra a konstruktor argumentumaiként: ```php class MyClass @@ -34,9 +34,9 @@ class MyClass $obj = new MyClass($cache); ``` -Ez a forma olyan kötelező függőségek esetében hasznos, amelyekre az osztálynak feltétlenül szüksége van a működéshez, mivel nélkülük a példány nem hozható létre. +Ez a forma alkalmas a kötelező függőségekre, amelyekre az osztálynak feltétlenül szüksége van a működéséhez, mivel nélkülük nem lehet példányt létrehozni. -A PHP 8.0 óta használhatunk egy rövidebb jelölési formát, amely funkcionálisan egyenértékű ([constructor property promotion |https://blog.nette.org/hu/php-8-0-teljes-attekintes-az-ujdonsagokrol#toc-constructor-property-promotion]): +PHP 8.0 óta használhatunk rövidebb írásmódot ([constructor property promotion |https://blog.nette.org/hu/php-8-0-complete-overview-of-news#toc-constructor-property-promotion]), amely funkcionálisan ekvivalens: ```php // PHP 8.0 @@ -49,7 +49,7 @@ class MyClass } ``` -A PHP 8.1 óta egy tulajdonságot megjelölhetünk egy `readonly` jelzővel, amely kijelenti, hogy a tulajdonság tartalma nem fog változni: +PHP 8.1 óta a property-t `readonly` jelzővel lehet ellátni, amely deklarálja, hogy a property tartalma már nem fog megváltozni: ```php // PHP 8.1 @@ -62,13 +62,13 @@ class MyClass } ``` -A DI konténer automatikusan átadja a függőségeket a konstruktornak az [autowiring |autowiring] segítségével. Az ilyen módon nem átadható argumentumok (pl. stringek, számok, booleans) [a konfigurátorban íródnak |services#Arguments]. +A DI konténer automatikusan átadja a függőségeket a konstruktornak az [autowiring |autowiring] segítségével. Azokat az argumentumokat, amelyeket így nem lehet átadni (pl. stringek, számok, booleanek), [a konfigurációban írjuk le |services#Argumentumok]. -Constructor Hell .[#toc-constructor-hell] ------------------------------------------ +Constructor hell +---------------- -A *construktor hell* kifejezés arra a helyzetre utal, amikor egy gyermek egy olyan szülő osztálytól örököl, amelynek konstruktora függőségeket igényel, és a gyermek is függőségeket igényel. A szülő függőségeit is át kell vennie és tovább kell adnia: +A *constructor hell* kifejezés azt a helyzetet jelöli, amikor egy leszármazott egy szülő osztálytól örököl, amelynek konstruktora függőségeket igényel, és ugyanakkor a leszármazott is függőségeket igényel. Eközben át kell vennie és át kell adnia a szülő függőségeit is: ```php abstract class BaseClass @@ -94,11 +94,11 @@ final class MyClass extends BaseClass } ``` -A probléma akkor jelentkezik, amikor a `BaseClass` osztály konstruktorát meg akarjuk változtatni, például egy új függőség hozzáadásakor. Ekkor a gyerekek összes konstruktorát is módosítani kell. Ami pokollá teszi az ilyen módosítást. +A probléma akkor merül fel, amikor meg akarjuk változtatni a `BaseClass` osztály konstruktorát, például ha új függőség kerül hozzáadásra. Ekkor ugyanis módosítani kell az összes leszármazott konstruktorát is. Ami egy ilyen módosítást pokollá tesz. -Hogyan lehet ezt megelőzni? A megoldás az, hogy **prioritást adunk a kompozíciónak az örökléssel szemben**. +Hogyan előzzük ezt meg? A megoldás az, hogy **előnyben részesítjük a [kompozíciót az öröklődéssel szemben |faq#Miért részesítjük előnyben a kompozíciót az öröklődéssel szemben]**. -Tehát tervezzük meg a kódot másképp. Kerüljük az absztrakt `Base*` osztályokat. Ahelyett, hogy a `MyClass` a `BaseClass` örökölése révén kapna bizonyos funkciókat, ahelyett, hogy a függőségként kapná meg ezeket a funkciókat: +Tehát másképp tervezzük meg a kódot. Kerülni fogjuk az [absztrakt |nette:introduction-to-object-oriented-programming#Absztrakt osztályok] `Base*` osztályokat. Ahelyett, hogy a `MyClass` bizonyos funkcionalitást úgy szerezne meg, hogy a `BaseClass`-tól örököl, ezt a funkcionalitást függőségként kapja meg: ```php final class SomeFunctionality @@ -125,10 +125,10 @@ final class MyClass ``` -Setter injektálás .[#toc-setter-injection] -========================================== +Setteren keresztüli átadás +========================== -A függőségek átadása egy olyan metódus meghívásával történik, amely egy privát tulajdonságban tárolja őket. Ezeknek a metódusoknak a szokásos elnevezési konvenciója a `set*()`, ezért hívják őket settereknek, de természetesen hívhatók máshogy is. +A függőségek egy metódus hívásával kerülnek átadásra, amely egy privát property-be menti őket. Ezeknek a metódusoknak a szokásos elnevezési konvenciója a `set*()` forma, ezért settereknek nevezik őket, de természetesen bármilyen más néven is nevezhetők. ```php class MyClass @@ -145,9 +145,9 @@ $obj = new MyClass; $obj->setCache($cache); ``` -Ez a metódus olyan opcionális függőségek esetében hasznos, amelyek nem szükségesek az osztály működéséhez, mivel nem garantált, hogy az objektum valóban megkapja őket (azaz a felhasználó meghívja a metódust). +Ez a módszer alkalmas a nem kötelező függőségekre, amelyek nem szükségesek az osztály működéséhez, mivel nincs garantálva, hogy az objektum ténylegesen megkapja a függőséget (azaz hogy a felhasználó meghívja a metódust). -Ugyanakkor ez a metódus lehetővé teszi a setter ismételt meghívását a függőség megváltoztatására. Ha ez nem kívánatos, adjunk hozzá egy ellenőrzést a metódushoz, vagy a PHP 8.1-től kezdve jelöljük meg a `$cache` tulajdonságot a `readonly` flaggel. +Ugyanakkor ez a módszer lehetővé teszi a setter ismételt meghívását és a függőség megváltoztatását. Ha ez nem kívánatos, adjunk hozzá egy ellenőrzést a metódushoz, vagy PHP 8.1 óta jelöljük a `$cache` property-t `readonly` jelzővel. ```php class MyClass @@ -156,7 +156,7 @@ class MyClass public function setCache(Cache $cache): void { - if ($this->cache) { + if (isset($this->cache)) { throw new RuntimeException('The dependency has already been set'); } $this->cache = $cache; @@ -164,21 +164,20 @@ class MyClass } ``` -A setter hívás a DI konténer konfigurációjában a [setup szakaszban |services#Setup] van definiálva. Itt is a függőségek automatikus átadását használja az autowiring: +A setter hívását a DI konténer konfigurációjában a [setup kulcsban |services#Setup] definiáljuk. Itt is automatikus függőségátadás történik az autowiring segítségével: ```neon services: - - - create: MyClass + - create: MyClass setup: - setCache ``` -Property Injection .[#toc-property-injection] -============================================= +Property beállításával +====================== -A függőségek közvetlenül a tulajdonsághoz kerülnek átadásra: +A függőségek közvetlenül a tagváltozóba (property-be) írással kerülnek átadásra: ```php class MyClass @@ -190,28 +189,27 @@ $obj = new MyClass; $obj->cache = $cache; ``` -Ez a módszer nem tekinthető megfelelőnek, mivel a tulajdonságot a `public` címen kell deklarálni. Így nincs befolyásunk arra, hogy az átadott függőség valóban a megadott típusú lesz-e (ez a PHP 7.4 előtt volt igaz), és elveszítjük a lehetőséget, hogy saját kódunkkal reagáljunk az újonnan hozzárendelt függőségre, például a későbbi változások megakadályozására. Ugyanakkor a tulajdonság az osztály nyilvános interfészének részévé válik, ami nem feltétlenül kívánatos. +Ez a módszer nem megfelelőnek tekinthető, mivel a property-t `public`-ként kell deklarálni. Így nincs ellenőrzésünk afölött, hogy az átadott függőség valóban a megadott típusú-e (ez a PHP 7.4 előtt volt érvényes), és elveszítjük a lehetőséget, hogy saját kóddal reagáljunk az újonnan hozzárendelt függőségre, például megakadályozzuk a későbbi módosítást. Ugyanakkor a property az osztály nyilvános interfészének részévé válik, ami nem feltétlenül kívánatos. -A változó beállítását a DI konténer konfigurációjában, a [setup szakaszban |services#Setup] határozzuk meg: +A property beállítását a DI konténer konfigurációjában a [setup szekcióban |services#Setup] definiáljuk: ```neon services: - - - create: MyClass + - create: MyClass setup: - $cache = @\Cache ``` -Injektálás .[#toc-inject] -========================= +Inject +====== -Míg az előző három módszer általában minden objektumorientált nyelvben érvényes, a metódus, annotáció vagy *inject* attribútum általi injektálás a Nette prezenterekre jellemző. Ezeket [külön fejezetben |best-practices:inject-method-attribute] tárgyaljuk. +Míg az előző három módszer általánosan érvényes minden objektumorientált nyelvben, a metódussal, annotációval vagy *inject* attribútummal történő injektálás kizárólag a Nette presenterjeire jellemző. Ezekről egy [külön fejezet |best-practices:inject-method-attribute] szól. -Melyik utat válasszuk? .[#toc-which-way-to-choose] -================================================== +Melyik módszert válasszuk? +========================== -- A konstruktor alkalmas a kötelező függőségekre, amelyekre az osztálynak szüksége van a működéshez. -- a setter ezzel szemben az opcionális, vagy megváltoztatható függőségekhez alkalmas. -- a nyilvános változók nem ajánlottak +- A konstruktor alkalmas a kötelező függőségekre, amelyekre az osztálynak feltétlenül szüksége van a működéséhez. +- A setter viszont alkalmas a nem kötelező függőségekre, vagy olyan függőségekre, amelyeket lehetőség szerint tovább lehet módosítani. +- A public property-k nem megfelelőek. diff --git a/dependency-injection/hu/services.texy b/dependency-injection/hu/services.texy index d1630dc19a..d1891fed65 100644 --- a/dependency-injection/hu/services.texy +++ b/dependency-injection/hu/services.texy @@ -1,33 +1,33 @@ -Szolgáltatás meghatározások -*************************** +Szolgáltatások definiálása +************************** .[perex] -A konfigurációban helyezzük el az egyéni szolgáltatások definícióit. Ez a `services` szakaszban történik. +A konfiguráció az a hely, ahol megtanítjuk a DI konténernek, hogyan állítsa össze az egyes szolgáltatásokat, és hogyan kapcsolja össze őket más függőségekkel. A Nette nagyon áttekinthető és elegáns módot kínál ennek elérésére. -Például így hozunk létre egy `database` nevű szolgáltatást, amely a `PDO` osztály példánya lesz: +A `services` szekció a NEON formátumú konfigurációs fájlban az a hely, ahol saját szolgáltatásainkat és azok konfigurációját definiáljuk. Nézzünk egy egyszerű példát egy `database` nevű szolgáltatás definíciójára, amely egy `PDO` osztály példányát reprezentálja: ```neon services: database: PDO('sqlite::memory:') ``` -A szolgáltatások elnevezése arra szolgál, hogy [hivatkozni |#Referencing Services] tudjunk rájuk. Ha egy szolgáltatásra nem hivatkozunk, akkor nincs szükség nevet adni neki. Ezért a név helyett csak egy felsoroláspontot használunk: +A megadott konfiguráció a következő factory metódust eredményezi a [DI konténerben|container]: -```neon -services: - - PDO('sqlite::memory:') # anonim szolgáltatás +```php +public function createServiceDatabase(): PDO +{ + return new PDO('sqlite::memory:'); +} ``` -Az egysoros bejegyzés több sorra bontható, hogy további kulcsokat lehessen hozzáadni, például [setup |#setup]. A `create:` billentyű álneve a `factory:`. +A szolgáltatásnevek lehetővé teszik, hogy a konfigurációs fájl más részeiben hivatkozzunk rájuk, `@szolgaltatasNev` formátumban. Ha nincs szükség a szolgáltatás elnevezésére, egyszerűen használhatunk csak egy kötőjelet: ```neon services: - database: - create: PDO('sqlite::memory:') - setup: ... + - PDO('sqlite::memory:') ``` -Ezután a szolgáltatást a DI konténerből a `getService()` metódus segítségével hívjuk le név szerint, vagy még jobb esetben a `getByType()` metódus segítségével típus szerint: +A szolgáltatás lekéréséhez a DI konténerből használhatjuk a `getService()` metódust a szolgáltatás nevével paraméterként, vagy a `getByType()` metódust a szolgáltatás típusával: ```php $database = $container->getService('database'); @@ -35,26 +35,28 @@ $database = $container->getByType(PDO::class); ``` -Szolgáltatás létrehozása .[#toc-creating-a-service] -=================================================== +Szolgáltatás létrehozása +======================== -Leggyakrabban úgy hozunk létre egy szolgáltatást, hogy egyszerűen létrehozzuk egy osztály példányát: +Legtöbbször egyszerűen úgy hozunk létre egy szolgáltatást, hogy létrehozunk egy példányt egy adott osztályból. Például: ```neon services: database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -Ami egy gyári metódust generál a [DI konténerben |container]: +Ha a konfigurációt további kulcsokkal kell bővítenünk, a definíciót több sorba is szétírhatjuk: -```php -public function createServiceDatabase(): PDO -{ - return new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'secret'); -} +```neon +services: + database: + create: PDO('sqlite::memory:') + setup: ... ``` -Alternatívaként egy kulcsot `arguments` lehet használni [az argumentumok |#Arguments] átadására: +A `create` kulcsnak van egy `factory` aliasa, mindkét változat gyakori a gyakorlatban. Azonban javasoljuk a `create` használatát. + +A konstruktor vagy a létrehozó metódus argumentumai alternatívaként az `arguments` kulcsban is megadhatók: ```neon services: @@ -63,294 +65,303 @@ services: arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret] ``` -Egy statikus módszer is létrehozhat egy szolgáltatást: +A szolgáltatásokat nemcsak egyszerű osztálypéldányosítással lehet létrehozni, hanem statikus metódusok vagy más szolgáltatások metódusainak hívásának eredményeként is: ```neon services: - database: My\Database::create(root, secret) + database: DatabaseFactory::create() + router: @routerFactory::create() ``` -Megfelel a PHP-kódnak: +Vegyük észre, hogy az egyszerűség kedvéért `->` helyett `::` használatos, lásd [#kifejező eszközök]. Ezek a factory metódusok generálódnak: ```php public function createServiceDatabase(): PDO { - return My\Database::create('root', 'secret'); + return DatabaseFactory::create(); +} + +public function createServiceRouter(): RouteList +{ + return $this->getService('routerFactory')->create(); } ``` -Egy statikus metódus `My\Database::create()` feltételezhetően rendelkezik egy meghatározott visszatérési értékkel, amelyet a DI konténernek ismernie kell. Ha nem rendelkezik vele, akkor a típusát a konfigurációba írjuk: +A DI konténernek ismernie kell a létrehozott szolgáltatás típusát. Ha egy olyan metódussal hozunk létre szolgáltatást, amelynek nincs megadva visszatérési típusa, akkor ezt a típust explicit módon meg kell adnunk a konfigurációban: ```neon services: database: - create: My\Database::create(root, secret) + create: DatabaseFactory::create() type: PDO ``` -A Nette DI rendkívül hatékony kifejezési lehetőségeket biztosít, amelyekkel szinte bármit leírhatunk. Például egy másik szolgáltatásra való [hivatkozás |#Referencing Services] és annak metódusának meghívása. Az egyszerűség kedvéért a `->` helyett a `::` -t használjuk. + +Argumentumok +============ + +A konstruktoroknak és metódusoknak argumentumokat adunk át, nagyon hasonlóan magához a PHP-hez: ```neon services: - routerFactory: App\Router\Factory - router: @routerFactory::create() + database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -Megfelel a PHP-kódnak: +A jobb olvashatóság érdekében az argumentumokat külön sorokba írhatjuk. Ebben az esetben a vesszők használata opcionális: -```php -public function createServiceRouterFactory(): App\Router\Factory -{ - return new App\Router\Factory; -} - -public function createServiceRouter(): Router -{ - return $this->getService('routerFactory')->create(); -} +```neon +services: + database: PDO( + 'mysql:host=127.0.0.1;dbname=test' + root + secret + ) ``` -A metódushívások a PHP-hez hasonlóan láncolhatók egymáshoz: +Az argumentumokat el is nevezheti, és akkor nem kell törődnie a sorrendjükkel: ```neon services: - foo: FooFactory::build()::get() + database: PDO( + username: root + password: secret + dsn: 'mysql:host=127.0.0.1;dbname=test' + ) ``` -Megfelel a PHP kódnak: +Ha ki szeretne hagyni néhány argumentumot, és azok alapértelmezett értékét szeretné használni, vagy egy szolgáltatást szeretne beilleszteni az [autowiring|autowiring] segítségével, használjon aláhúzást: -```php -public function createServiceFoo() -{ - return FooFactory::build()->get(); -} +```neon +services: + foo: Foo(_, %appDir%) ``` +Argumentumként átadhatók szolgáltatások, használhatók paraméterek és még sok más, lásd [#kifejező eszközök]. -Érvek: .[#toc-arguments] -======================== -A megnevezett paraméterek argumentumok átadására is használhatók: +Setup +===== + +A `setup` szekcióban definiáljuk azokat a metódusokat, amelyeket a szolgáltatás létrehozásakor kell meghívni. ```neon services: - database: PDO( - 'mysql:host=127.0.0.1;dbname=test' # pozicionális - username: root # named - password: secret # named - ) + database: + create: PDO(%dsn%, %user%, %password%) + setup: + - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) ``` -A vesszők használata opcionális, ha az argumentumokat több sorra bontjuk. +Ez PHP-ban így nézne ki: + +```php +public function createServiceDatabase(): PDO +{ + $service = new PDO('...', '...', '...'); + $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + return $service; +} +``` -Természetesen [más szolgáltatásokat |#Referencing Services] vagy [paramétereket |configuration#parameters] is használhatunk argumentumként: +A metódushívásokon kívül értékeket is átadhatunk a property-knek. Támogatott az elem hozzáadása egy tömbhöz is, amelyet idézőjelek közé kell írni, hogy ne ütközzön a NEON szintaxisával: ```neon services: - - Foo(@anotherService, %appDir%) + foo: + create: Foo + setup: + - $value = 123 + - '$onClick[]' = [@bar, clickHandler] ``` -Megfelel a PHP-kódnak: +Ami a PHP kódban a következőképpen nézne ki: ```php -public function createService01(): Foo +public function createServiceFoo(): Foo { - return new Foo($this->getService('anotherService'), '...'); + $service = new Foo; + $service->value = 123; + $service->onClick[] = [$this->getService('bar'), 'clickHandler']; + return $service; } ``` -`_` character, for example `Foo(_, %appDir%)`Vagy még jobb, ha csak a második argumentumot adjuk át megnevezett paraméterként, pl. `Foo(path: %appDir%)`. - -A Nette DI és a NEON formátum rendkívül erőteljes kifejezési lehetőségeket biztosít szinte bármi megírásához. Így egy argumentum lehet egy újonnan létrehozott objektum, hívhat statikus metódusokat, más szolgáltatások metódusait, vagy akár globális függvényeket is, speciális jelölés használatával: +A setupban azonban hívhatunk statikus metódusokat vagy más szolgáltatások metódusait is. Ha az aktuális szolgáltatást argumentumként kell átadni, adja meg `@self`-ként: ```neon services: - analyser: My\Analyser( - FilesystemIterator(%appDir%) # create object - DateTime::createFromFormat('Y-m-d') # statikus metódus hívása - @anotherService # egy másik szolgáltatás átadása - @http.request::getRemoteAddress() # egy másik szolgáltatás metódusának hívása - ::getenv(NetteMode) # globális függvény hívása - ) + foo: + create: Foo + setup: + - My\Helpers::initializeFoo(@self) + - @anotherService::setFoo(@self) ``` -Megfelel a PHP kódnak: +Vegyük észre, hogy az egyszerűség kedvéért `->` helyett `::` használatos, lásd [#kifejező eszközök]. Ilyen factory metódus generálódik: ```php -public function createServiceAnalyser(): My\Analyser +public function createServiceFoo(): Foo { - return new My\Analyser( - new FilesystemIterator('...'), - DateTime::createFromFormat('Y-m-d'), - $this->getService('anotherService'), - $this->getService('http.request')->getRemoteAddress(), - getenv('NetteMode') - ); + $service = new Foo; + My\Helpers::initializeFoo($service); + $this->getService('anotherService')->setFoo($service); + return $service; } ``` -Speciális funkciók .[#toc-special-functions] --------------------------------------------- - -Speciális függvényeket is használhat az argumentumokban értékek kiértékelésére vagy negálására: +Kifejező eszközök +================= -- `not(%arg%)` negáció -- `bool(%arg%)` lossless cast to bool -- `int(%arg%)` veszteségmentes átváltás int-re -- `float(%arg%)` lossless cast to float -- `string(%arg%)` lossless cast to string +A Nette DI rendkívül gazdag kifejező eszközöket ad nekünk, amelyekkel szinte bármit leírhatunk. A konfigurációs fájlokban így használhatunk [paramétereket |configuration#Paraméterek]: ```neon -services: - - Foo( - id: int(::getenv('ProjectId')) - productionMode: not(%debugMode%) - ) -``` +# paraméter +%wwwDir% -A veszteségmentes átírás abban különbözik a normál PHP átírástól, pl. a `(int)` használatával, hogy a nem numerikus értékek esetén kivételt dob. +# paraméter értéke kulcs alatt +%mailer.user% -Több szolgáltatás is átadható argumentumként. Egy adott típusú (azaz osztály vagy interfész) összes szolgáltatásának tömbjét a `typed()` függvény hozza létre. A függvény kihagyja azokat a szolgáltatásokat, amelyeknél az automatikus kapcsolás ki van kapcsolva, és több típus is megadható vesszővel elválasztva. - -```neon -services: - - BarsDependent( typed(Bar) ) +# paraméter egy stringen belül +'%wwwDir%/images' ``` -A szolgáltatások tömbjét automatikusan is átadhatja az [automatikus bekötés |autowiring#Collection of Services] segítségével. - -A `tagged()` függvény létrehozza az összes, egy adott [címkével |#tags] rendelkező szolgáltatás tömbjét. Több, vesszővel elválasztott címke is megadható. +Továbbá objektumokat hozhatunk létre, metódusokat és függvényeket hívhatunk: ```neon -services: - - LoggersDependent( tagged(logger) ) -``` +# objektum létrehozása +DateTime() +# statikus metódus hívása +Collator::create(%locale%) -Szolgáltatásokra való hivatkozás .[#toc-referencing-services] -============================================================= +# PHP függvény hívása +::getenv(DB_USER) +``` -Az egyes szolgáltatásokra a `@` and name, so for example `@database` karakterek segítségével lehet hivatkozni: +Hivatkozhatunk szolgáltatásokra akár a nevükkel, akár a típusukkal: ```neon -services: - - create: Foo(@database) - setup: - - setCacheStorage(@cache.storage) +# szolgáltatás név szerint +@database + +# szolgáltatás típus szerint +@Nette\Database\Connection ``` -Megfelel a PHP kódnak: +Használhatunk first-class callable szintaxist: .{data-version:3.2.0} -```php -public function createService01(): Foo -{ - $service = new Foo($this->getService('database')); - $service->setCacheStorage($this->getService('cache.storage')); - return $service; -} +```neon +# callback létrehozása, hasonlóan a [@user, logout]-hoz +@user::logout(...) ``` -A névtelen szolgáltatásokra is lehet hivatkozni visszahívással, csak a típusukat (osztály vagy interfész) kell megadni a nevük helyett. Erre azonban az [autowiring |autowiring] miatt általában nincs szükség. +Használhatunk konstansokat: ```neon -services: - - create: Foo(@Nette\Database\Connection) # or @\PDO - setup: - - setCacheStorage(@cache.storage) +# osztály konstans +FilesystemIterator::SKIP_DOTS + +# globális konstansot a constant() PHP függvénnyel kapunk +::constant(PHP_VERSION) ``` +A metódushívásokat ugyanúgy lehet láncolni, mint PHP-ban. Csak az egyszerűség kedvéért `->` helyett `::` használatos: -Beállítás .[#toc-setup] -======================= +```neon +DateTime()::format('Y-m-d') +# PHP: (new DateTime())->format('Y-m-d') + +@http.request::getUrl()::getHost() +# PHP: $this->getService('http.request')->getUrl()->getHost() +``` -A setup szakaszban felsoroljuk a szolgáltatás létrehozásakor meghívandó metódusokat: +Ezeket a kifejezéseket bárhol használhatja, a [szolgáltatások létrehozásakor |#Szolgáltatás létrehozása], az [argumentumokban |#Argumentumok], a [#setup] szekcióban vagy a [paraméterekben |configuration#Paraméterek]: ```neon +parameters: + ipAddress: @http.request::getRemoteAddress() + services: database: - create: PDO(%dsn%, %user%, %password%) + create: DatabaseFactory::create( @anotherService::getDsn() ) setup: - - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) + - initialize( ::getenv('DB_USER') ) ``` -Megfelel a PHP-kódnak: -```php -public function createServiceDatabase(): PDO -{ - $service = new PDO('...', '...', '...'); - $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - return $service; -} -``` +Speciális függvények +-------------------- -Properites is beállítható. Egy elem hozzáadása egy tömbhöz szintén támogatott, és idézőjelek közé kell írni, hogy ne ütközzön a NEON szintaxissal: +A konfigurációs fájlokban használhatja ezeket a speciális függvényeket: +- `not()` érték negálása +- `bool()`, `int()`, `float()`, `string()` veszteségmentes típuskonverzió a megadott típusra +- `typed()` létrehozza a megadott típusú összes szolgáltatás tömbjét +- `tagged()` létrehozza a megadott taggel rendelkező összes szolgáltatás tömbjét ```neon services: - foo: - create: Foo - setup: - - $value = 123 - - '$onClick[]' = [@bar, clickHandler] + - Foo( + id: int(::getenv('ProjectId')) + productionMode: not(%debugMode%) + ) ``` -Megfelel a PHP-kódnak: +A klasszikus PHP típuskonverzióval ellentétben, mint pl. az `(int)`, a veszteségmentes típuskonverzió kivételt dob nem numerikus értékek esetén. -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - $service->value = 123; - $service->onClick[] = [$this->getService('bar'), 'clickHandler']; - return $service; -} +A `typed()` függvény létrehozza a megadott típusú (osztály vagy interfész) összes szolgáltatás tömbjét. Kihagyja azokat a szolgáltatásokat, amelyeknek ki van kapcsolva az autowiringja. Több típust is meg lehet adni vesszővel elválasztva. + +```neon +services: + - BarsDependent( typed(Bar) ) ``` -A beállításban azonban statikus metódusok vagy más szolgáltatások metódusai is meghívhatók. Az aktuális szolgáltatást a `@self` címen adjuk át nekik: +Egy adott típusú szolgáltatások tömbjét argumentumként is átadhatja automatikusan az [autowiring |autowiring#Szolgáltatások tömbje] segítségével. +A `tagged()` függvény pedig létrehozza az összes, adott taggel rendelkező szolgáltatás tömbjét. Itt is megadhat több taget vesszővel elválasztva. ```neon services: - foo: - create: Foo - setup: - - My\Helpers::initializeFoo(@self) - - @anotherService::setFoo(@self) + - LoggersDependent( tagged(logger) ) ``` -Megfelel a PHP kódnak: -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - My\Helpers::initializeFoo($service); - $this->getService('anotherService')->setFoo($service); - return $service; -} +Autowiring +========== + +Az `autowired` kulcs lehetővé teszi az autowiring viselkedésének befolyásolását egy adott szolgáltatásra. Részletekért lásd az [autowiringról szóló fejezetet|autowiring]. + +```neon +services: + foo: + create: Foo + autowired: false # a foo szolgáltatás ki van zárva az autowiringból ``` -Autowiring .[#toc-autowiring] -============================= +Lazy szolgáltatások .{data-version:3.2.4} +========================================= -Az autowired kulccsal kizárható egy szolgáltatás az autowiringből, vagy befolyásolható a viselkedése. További információért lásd [az autowiring fejezetet |autowiring]. +A lazy loading egy technika, amely elhalasztja a szolgáltatás létrehozását egészen addig a pillanatig, amíg valóban szükség van rá. A globális konfigurációban [engedélyezhető a lazy létrehozás |configuration#Lazy szolgáltatások] minden szolgáltatásra egyszerre. Az egyes szolgáltatások esetében ezt a viselkedést felülbírálhatja: ```neon services: foo: create: Foo - autowired: false # foo-t eltávolítjuk az autowiringből + lazy: false ``` +Ha egy szolgáltatás lazy-ként van definiálva, annak a DI konténerből való lekérésekor egy speciális helyettesítő objektumot kapunk. Ez ugyanúgy néz ki és viselkedik, mint a valódi szolgáltatás, de a tényleges inicializálás (konstruktor és setup hívása) csak bármely metódusának vagy property-jének első hívásakor történik meg. + +.[note] +A lazy loading csak felhasználói osztályokra használható, belső PHP osztályokra nem. PHP 8.4 vagy újabb verziót igényel. + -Címkék .[#toc-tags] -=================== +Tagek +===== -A felhasználói információk címkék formájában adhatók hozzá az egyes szolgáltatásokhoz: +A tagek további információk hozzáadására szolgálnak a szolgáltatásokhoz. Egy szolgáltatáshoz egy vagy több taget adhat hozzá: ```neon services: @@ -360,7 +371,7 @@ services: - cached ``` -A címkéknek lehet értékük is: +A tagek értékeket is hordozhatnak: ```neon services: @@ -370,26 +381,26 @@ services: logger: monolog.logger.event ``` -A `tagged()` függvény segítségével bizonyos címkékkel rendelkező szolgáltatások tömbje adható át argumentumként. Több, vesszővel elválasztott címke is megadható. +Ahhoz, hogy megkapja az összes, adott tagekkel rendelkező szolgáltatást, használhatja a `tagged()` függvényt: ```neon services: - LoggersDependent( tagged(logger) ) ``` -A szolgáltatások nevei a DI konténerből a `findByTag()` módszerrel szerezhetők be: +A DI konténerben lekérheti az összes, adott taggel rendelkező szolgáltatás nevét a `findByTag()` metódussal: ```php $names = $container->findByTag('logger'); -// $names egy tömb, amely a szolgáltatás nevét és a tag értékét tartalmazza. +// $names egy tömb, amely tartalmazza a szolgáltatás nevét és a tag értékét // pl. ['foo' => 'monolog.logger.event', ...] ``` -Inject Mode .[#toc-inject-mode] -=============================== +Inject mód +========== -A `inject: true` flag a függőségek nyilvános változókon keresztüli átadásának aktiválására szolgál az [inject |best-practices:inject-method-attribute#Inject Attributes] annotációval és az [inject*() |best-practices:inject-method-attribute#inject Methods] metódusokkal. +Az `inject: true` jelzővel aktiválódik a függőségek átadása a public property-ken keresztül [inject |best-practices:inject-method-attribute#Inject attribútumok] annotációval és az [inject*() |best-practices:inject-method-attribute#inject metódusok] metódusokkal. ```neon services: @@ -398,13 +409,13 @@ services: inject: true ``` -Alapértelmezés szerint a `inject` csak az előadók esetében van aktiválva. +Alapértelmezés szerint az `inject` csak a presenterekre van aktiválva. -A szolgáltatások módosítása .[#toc-modification-of-services] -============================================================ +Szolgáltatások módosítása +========================= -A DI konténerben számos olyan szolgáltatás van, amelyet beépített vagy [a bővítménye |#di-extensions] adott hozzá. Ezeknek a szolgáltatásoknak a definíciói a konfigurációban módosíthatók. Például a `application.application` szolgáltatás esetében, amely alapértelmezés szerint egy `Nette\Application\Application` objektum, megváltoztathatjuk az osztályt: +A DI konténer számos szolgáltatást tartalmaz, amelyeket beépített vagy [felhasználói kiterjesztés|extensions] révén adtak hozzá. Módosíthatja ezeknek a szolgáltatásoknak a definícióit közvetlenül a konfigurációban. Például megváltoztathatja az `application.application` szolgáltatás osztályát, amely alapértelmezés szerint `Nette\Application\Application`, egy másikra: ```neon services: @@ -413,9 +424,9 @@ services: alteration: true ``` -A `alteration` jelző tájékoztató jellegű, és azt mondja, hogy csak egy meglévő szolgáltatást módosítunk. +Az `alteration` jelző informatív jellegű, és azt jelzi, hogy csak egy meglévő szolgáltatást módosítunk. -Hozzáadhatunk egy beállítást is: +Kiegészíthetjük a setupot is: ```neon services: @@ -426,7 +437,7 @@ services: - '$onStartup[]' = [@resource, init] ``` -Erre való a `reset`: +Egy szolgáltatás felülírásakor előfordulhat, hogy el akarjuk távolítani az eredeti argumentumokat, setup elemeket vagy tageket, erre szolgál a `reset`: ```neon services: @@ -439,7 +450,7 @@ services: - tags ``` -A kiterjesztéssel hozzáadott szolgáltatás is eltávolítható a konténerből: +Ha el szeretne távolítani egy kiterjesztés által hozzáadott szolgáltatást, azt így teheti meg: ```neon services: diff --git a/dependency-injection/it/@home.texy b/dependency-injection/it/@home.texy index 76ccee5828..28ffcf93fb 100644 --- a/dependency-injection/it/@home.texy +++ b/dependency-injection/it/@home.texy @@ -1,24 +1,21 @@ -Iniezione di dipendenza -*********************** +Nette DI +******** .[perex] -La Dependency Injection è un modello di progettazione che cambierà radicalmente il modo di vedere il codice e lo sviluppo. Apre la strada a un mondo di applicazioni pulite e sostenibili. +La Dependency Injection è un design pattern che cambierà radicalmente la tua prospettiva sul codice e sullo sviluppo. Ti aprirà le porte a un mondo di applicazioni progettate in modo pulito e sostenibile. -- [Che cos'è la Dependency Injection? |introduction] +- [Cos'è la Dependency Injection? |introduction] - [Stato globale e singleton |global-state] -- [Passare le dipendenze |passing-dependencies] -- [Cos'è il contenitore DI? |container] -- [Domande frequenti |faq] - +- [Passaggio delle dipendenze |passing-dependencies] +- [Cos'è un container DI? |container] +- [Domande frequenti|faq] -Nette DI --------- -Il pacchetto `nette/di` fornisce un contenitore DI compilato estremamente avanzato per PHP. +Il pacchetto `nette/di` fornisce un container DI compilato estremamente avanzato per PHP. -- [Contenitore Nette DI |nette-container] +- [Nette DI Container |nette-container] - [Configurazione |configuration] -- [Definizioni di servizio |services] -- [Autocablaggio |autowiring] -- [Fabbriche generate |factory] -- [Creazione di estensioni per Nette DI |extensions] +- [Definizione dei servizi |services] +- [Autowiring |autowiring] +- [Factory generate |factory] +- [Creazione di estensioni per Nette DI|extensions] diff --git a/dependency-injection/it/@left-menu.texy b/dependency-injection/it/@left-menu.texy index 31552b7412..5fb12cd34a 100644 --- a/dependency-injection/it/@left-menu.texy +++ b/dependency-injection/it/@left-menu.texy @@ -1,17 +1,17 @@ -Iniezione di dipendenza -*********************** -- [Che cos'è DI? |introduction] +Dependency Injection +******************** +- [Cos'è la DI? |introduction] - [Stato globale e singleton |global-state] -- [Passare le dipendenze |passing-dependencies] -- [Che cos'è il contenitore DI? |container] -- [Domande frequenti |faq] +- [Passaggio delle dipendenze |passing-dependencies] +- [Cos'è un container DI? |container] +- [Domande frequenti|faq] Nette DI -------- -- [Nette del contenitore DI |nette-container] +- [Nette DI Container |nette-container] - [Configurazione |configuration] -- [Definizioni di servizio |services] -- [Autocablaggio |autowiring] -- [Fabbriche generate |factory] -- [Creare estensioni per Nette DI |extensions] +- [Definizione dei servizi |services] +- [Autowiring |autowiring] +- [Factory generate |factory] +- [Creazione di estensioni per Nette DI|extensions] diff --git a/dependency-injection/it/@meta.texy b/dependency-injection/it/@meta.texy new file mode 100644 index 0000000000..4647d0c8a2 --- /dev/null +++ b/dependency-injection/it/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentazione Nette}} diff --git a/dependency-injection/it/autowiring.texy b/dependency-injection/it/autowiring.texy index 35b1a3f2d4..231e26bbc0 100644 --- a/dependency-injection/it/autowiring.texy +++ b/dependency-injection/it/autowiring.texy @@ -1,24 +1,24 @@ -Cablaggio auto -************** +Autowiring +********** .[perex] -L'autowiring è un'ottima funzione che permette di passare automaticamente i servizi al costruttore e ad altri metodi, senza doverli scrivere. Permette di risparmiare molto tempo. +L'Autowiring è una funzionalità fantastica che può passare automaticamente i servizi richiesti al costruttore e ad altri metodi, quindi non dobbiamo scriverli affatto. Ti fa risparmiare un sacco di tempo. -Questo ci permette di saltare la maggior parte degli argomenti quando scriviamo le definizioni dei servizi. Invece di: +Grazie a questo, possiamo omettere la stragrande maggioranza degli argomenti quando scriviamo le definizioni dei servizi. Invece di: ```neon services: articles: Model\ArticleRepository(@database, @cache.storage) ``` -Scrivete: +Basta scrivere: ```neon services: articles: Model\ArticleRepository ``` -Il cablaggio automatico è guidato dai tipi, quindi la classe `ArticleRepository` deve essere definita come segue: +L'Autowiring si basa sui tipi, quindi affinché funzioni, la classe `ArticleRepository` deve essere definita più o meno così: ```php namespace Model; @@ -30,22 +30,22 @@ class ArticleRepository } ``` -Per usare l'autowiring, deve esserci **un solo servizio** per ogni tipo nel contenitore. Se ce ne fossero di più, l'autowiring non saprebbe quale passare e lancerebbe un'eccezione: +Per poter utilizzare l'autowiring, deve esserci **esattamente un servizio** per ogni tipo nel container. Se ce ne fossero di più, l'autowiring non saprebbe quale passare e lancerebbe un'eccezione: ```neon services: mainDb: PDO(%dsn%, %user%, %password%) tempDb: PDO('sqlite::memory:') - articles: Model\ArticleRepository # LANCIA L'ECCEZIONE, sia mainDb che tempDb corrispondono + articles: Model\ArticleRepository # LANCIA ECCEZIONE, soddisfano sia mainDb che tempDb ``` -La soluzione sarebbe quella di bypassare l'autowiring e dichiarare esplicitamente il nome del servizio (per esempio `articles: Model\ArticleRepository(@mainDb)`). Tuttavia, è più conveniente [disabilitare l' |#Disabled autowiring] autowiring di un solo servizio, o del primo servizio [preferito |#Preferred Autowiring]. +La soluzione sarebbe bypassare l'autowiring e specificare esplicitamente il nome del servizio (cioè `articles: Model\ArticleRepository(@mainDb)`). Ma è più intelligente [disattivare |#Disattivazione dell autowiring] l'autowiring per uno dei servizi, o [dare la preferenza |#Preferenza dell autowiring] al primo servizio. -Cablaggio automatico disabilitato .[#toc-disabled-autowiring] -------------------------------------------------------------- +Disattivazione dell'autowiring +------------------------------ -È possibile disabilitare il cablaggio automatico del servizio utilizzando l'opzione `autowired: no`: +Possiamo disattivare l'autowiring di un servizio usando l'opzione `autowired: no`: ```neon services: @@ -53,28 +53,27 @@ services: tempDb: create: PDO('sqlite::memory:') - autowired: false # rimuove tempDb dall'autowiring + autowired: false # il servizio tempDb è escluso dall'autowiring - articles: Model\ArticleRepository # quindi passa mainDb al costruttore + articles: Model\ArticleRepository # quindi passa mainDb al costruttore ``` -Il servizio `articles` non lancia l'eccezione che ci sono due servizi corrispondenti di tipo `PDO` (cioè `mainDb` e `tempDb`) che possono essere passati al costruttore, perché vede solo il servizio `mainDb`. +Il servizio `articles` non lancerà un'eccezione perché esistono due servizi compatibili di tipo `PDO` (cioè `mainDb` e `tempDb`) che possono essere passati al costruttore, perché vede solo il servizio `mainDb`. .[note] -La configurazione dell'autowiring in Nette funziona in modo diverso rispetto a Symfony, dove l'opzione `autowire: false` dice che l'autowiring non deve essere usato per i parametri del costruttore di servizi. -In Nette, l'autowiring è sempre usato, sia per i parametri del costruttore che per qualsiasi altro metodo. L'opzione `autowired: false` dice che l'istanza del servizio non deve essere passata da nessuna parte usando l'autowiring. +La configurazione dell'autowiring in Nette funziona diversamente rispetto a Symfony, dove l'opzione `autowire: false` indica che l'autowiring non deve essere utilizzato per gli argomenti del costruttore del servizio specificato. In Nette, l'autowiring viene sempre utilizzato, sia per gli argomenti del costruttore che per qualsiasi altro metodo. L'opzione `autowired: false` indica che l'istanza del servizio specificato non deve essere passata da nessuna parte tramite autowiring. -Autowiring preferito .[#toc-preferred-autowiring] -------------------------------------------------- +Preferenza dell'autowiring +-------------------------- -Se abbiamo più servizi dello stesso tipo e uno di essi ha l'opzione `autowired`, questo servizio diventa quello preferito: +Se abbiamo più servizi dello stesso tipo e per uno di essi specifichiamo l'opzione `autowired`, questo servizio diventa preferito: ```neon services: mainDb: create: PDO(%dsn%, %user%, %password%) - autowired: PDO # lo rende preferibile + autowired: PDO # diventa preferito tempDb: create: PDO('sqlite::memory:') @@ -82,13 +81,13 @@ services: articles: Model\ArticleRepository ``` -Il servizio `articles` non lancia l'eccezione che ci sono due servizi `PDO` corrispondenti (cioè `mainDb` e `tempDb`), ma utilizza il servizio preferito, cioè `mainDb`. +Il servizio `articles` non lancerà un'eccezione perché esistono due servizi compatibili di tipo `PDO` (cioè `mainDb` e `tempDb`), ma utilizzerà il servizio preferito, ovvero `mainDb`. -Raccolta di servizi .[#toc-collection-of-services] --------------------------------------------------- +Array di servizi +---------------- -L'autowiring può anche passare un array di servizi di un tipo particolare. Poiché PHP non è in grado di annotare nativamente il tipo di elementi dell'array, oltre al tipo `array`, è necessario aggiungere un commento phpDoc con il tipo di elemento come `ClassName[]`: +L'Autowiring può anche passare array di servizi di un certo tipo. Poiché in PHP non è possibile scrivere nativamente il tipo degli elementi dell'array, è necessario aggiungere, oltre al tipo `array`, anche un commento phpDoc con il tipo dell'elemento nella forma `ClassName[]`: ```php namespace Model; @@ -103,16 +102,15 @@ class ShipManager } ``` -Il contenitore DI passa automaticamente un array di servizi che corrispondono al tipo dato. Ometterà i servizi che hanno il cablaggio automatico disattivato. +Il container DI passerà quindi automaticamente un array di servizi corrispondenti al tipo specificato. Ometterà i servizi che hanno l'autowiring disattivato. -Se non si può controllare la forma del commento di phpDoc, si può passare un array di servizi direttamente nella configurazione, usando il metodo [`typed()` |services#Special Functions]. +Il tipo nel commento può anche essere nella forma `array<int, Class>` o `list<Class>`. Se non puoi influenzare la forma del commento phpDoc, puoi passare l'array di servizi direttamente nella configurazione usando [`typed()` |services#Funzioni speciali]. -Argomenti scalari .[#toc-scalar-arguments] ------------------------------------------- +Argomenti scalari +----------------- -Il cablaggio automatico può passare solo oggetti e array di oggetti. Gli argomenti scalari (ad esempio stringhe, numeri, booleani) si [scrivono nella configurazione |services#Arguments]. -Un'alternativa è quella di creare un [oggetto settings |best-practices:passing-settings-to-presenters] che incapsuli un valore scalare (o più valori) come un oggetto, che può essere passato di nuovo con l'autowiring. +L'Autowiring può fornire solo oggetti e array di oggetti. Gli argomenti scalari (ad es. stringhe, numeri, booleani) [li scriviamo nella configurazione |services#Argomenti]. Un'alternativa è creare un [oggetto-impostazioni |best-practices:passing-settings-to-presenters], che incapsula il valore scalare (o più valori) in un oggetto, che può poi essere nuovamente passato tramite autowiring. ```php class MySettings @@ -125,24 +123,24 @@ class MySettings } ``` -Si crea un servizio aggiungendolo alla configurazione: +Lo trasformi in un servizio aggiungendolo alla configurazione: ```neon services: - MySettings('any value') ``` -Tutte le classi lo richiederanno quindi tramite il cablaggio automatico. +Tutte le classi lo richiederanno quindi tramite autowiring. -Restringimento del cablaggio automatico .[#toc-narrowing-of-autowiring] ------------------------------------------------------------------------ +Restrizione dell'autowiring +--------------------------- -Per i singoli servizi, il cablaggio automatico può essere ristretto a classi o interfacce specifiche. +Per singoli servizi, l'autowiring può essere ristretto a determinate classi o interfacce. -Normalmente, l'autowiring passa il servizio a ogni parametro del metodo al cui tipo il servizio corrisponde. Restringere significa specificare le condizioni che i tipi specificati per i parametri del metodo devono soddisfare affinché il servizio venga passato ad essi. +Normalmente, l'autowiring passa un servizio a ogni parametro di metodo il cui tipo corrisponde al servizio. La restrizione significa che stabiliamo condizioni che i tipi specificati nei parametri dei metodi devono soddisfare affinché il servizio venga loro passato. -Facciamo un esempio: +Lo mostreremo con un esempio: ```php class ParentClass @@ -164,42 +162,42 @@ class ChildDependent } ``` -Se li registrassimo tutti come servizi, il cablaggio automatico fallirebbe: +Se li registrassimo tutti come servizi, l'autowiring fallirebbe: ```neon services: parent: ParentClass child: ChildClass - parentDep: ParentDependent # LANCIA L'ECCEZIONE, sia il genitore che il figlio corrispondono - childDep: ChildDependent # passa il servizio 'child' al costruttore + parentDep: ParentDependent # LANCIA ECCEZIONE, soddisfano i servizi parent e child + childDep: ChildDependent # l'autowiring passa il servizio child al costruttore ``` -Il servizio `parentDep` lancia l'eccezione `Multiple services of type ParentClass found: parent, child` perché sia `parent` che `child` si inseriscono nel suo costruttore e l'autowiring non può decidere quale scegliere. +Il servizio `parentDep` lancerà l'eccezione `Multiple services of type ParentClass found: parent, child`, perché entrambi i servizi `parent` e `child` corrispondono al suo costruttore, e l'autowiring non può decidere quale scegliere. -Per il servizio `child`, possiamo quindi restringere il suo autowiring a `ChildClass`: +Per il servizio `child`, possiamo quindi restringere il suo autowiring al tipo `ChildClass`: ```neon services: parent: ParentClass child: create: ChildClass - autowired: ChildClass # alternative: 'autowired: self' + autowired: ChildClass # si può anche scrivere 'autowired: self' - parentDep: ParentDependent # Lancia un'eccezione, il 'figlio' non può essere autowired - childDep: ChildDependent # passa il servizio 'child' al costruttore + parentDep: ParentDependent # l'autowiring passa il servizio parent al costruttore + childDep: ChildDependent # l'autowiring passa il servizio child al costruttore ``` -Il servizio `parentDep` viene ora passato al costruttore del servizio `parentDep`, poiché ora è l'unico oggetto corrispondente. Il servizio `child` non viene più passato per autocablaggio. Sì, il servizio `child` è ancora di tipo `ParentClass`, ma la condizione di restringimento data per il tipo di parametro non si applica più, cioè non è più vero che `ParentClass` *è un supertipo* di `ChildClass`. +Ora, al costruttore del servizio `parentDep` viene passato il servizio `parent`, perché ora è l'unico oggetto compatibile. L'autowiring non passerà più il servizio `child` lì. Sì, il servizio `child` è ancora di tipo `ParentClass`, ma la condizione restrittiva data per il tipo del parametro non è più valida, cioè non è vero che `ParentClass` *è un supertipo di* `ChildClass`. -Nel caso di `child`, `autowired: ChildClass` potrebbe essere scritto come `autowired: self`, poiché `self` indica il tipo di servizio corrente. +Per il servizio `child`, `autowired: ChildClass` potrebbe anche essere scritto come `autowired: self`, poiché `self` è un segnaposto per la classe del servizio corrente. -La chiave `autowired` può includere diverse classi e interfacce come array: +Nella chiave `autowired` è possibile specificare anche più classi o interfacce come array: ```neon autowired: [BarClass, FooInterface] ``` -Proviamo ad aggiungere le interfacce all'esempio: +Proviamo a completare l'esempio con un'interfaccia: ```php interface FooInterface @@ -239,13 +237,13 @@ class ChildDependent } ``` -Se non limitiamo il servizio `child`, esso entrerà nei costruttori di tutte le classi `FooDependent`, `BarDependent`, `ParentDependent` e `ChildDependent` e l'autowiring lo passerà lì. +Se non limitiamo in alcun modo il servizio `child`, corrisponderà ai costruttori di tutte le classi `FooDependent`, `BarDependent`, `ParentDependent` e `ChildDependent` e l'autowiring lo passerà lì. -Tuttavia, se restringiamo il suo autowiring a `ChildClass` usando `autowired: ChildClass` (o `self`), l'autowiring lo passa solo al costruttore `ChildDependent`, perché richiede un argomento di tipo `ChildClass` e `ChildClass` *è di tipo* `ChildClass`. Nessun altro tipo specificato per gli altri parametri è un superset di `ChildClass`, quindi il servizio non viene passato. +Ma se restringiamo il suo autowiring a `ChildClass` usando `autowired: ChildClass` (o `self`), l'autowiring lo passerà solo al costruttore di `ChildDependent`, perché richiede un argomento di tipo `ChildClass` ed è vero che `ChildClass` *è di tipo* `ChildClass`. Nessun altro tipo specificato negli altri parametri è un supertipo di `ChildClass`, quindi il servizio non viene passato. -Se lo limitiamo a `ParentClass` usando `autowired: ParentClass`, l'autowiring lo passerà di nuovo al costruttore `ChildDependent` (poiché il tipo richiesto `ChildClass` è un sottoinsieme di `ParentClass`) e anche al costruttore `ParentDependent`, poiché anche il tipo richiesto di `ParentClass` corrisponde. +Se lo limitiamo a `ParentClass` usando `autowired: ParentClass`, l'autowiring lo passerà di nuovo al costruttore di `ChildDependent` (perché il `ChildClass` richiesto è un supertipo di `ParentClass`) e ora anche al costruttore di `ParentDependent`, perché anche il tipo `ParentClass` richiesto è compatibile. -Se lo limitiamo a `FooInterface`, si autocablerà ancora a `ParentDependent` (il tipo richiesto `ParentClass` è un supertipo di `FooInterface`) e a `ChildDependent`, ma anche al costruttore `FooDependent`, ma non a `BarDependent`, poiché `BarInterface` non è un supertipo di `FooInterface`. +Se lo limitiamo a `FooInterface`, sarà ancora autowired in `ParentDependent` (il `ParentClass` richiesto è un supertipo di `FooInterface`) e `ChildDependent`, ma inoltre anche nel costruttore di `FooDependent`, ma non in `BarDependent`, perché `BarInterface` non è un supertipo di `FooInterface`. ```neon services: @@ -253,8 +251,8 @@ services: create: ChildClass autowired: FooInterface - fooDep: FooDependent # passa il servizio figlio al costruttore - barDep: BarDependent # LANCIA L'ECCEZIONE, nessun servizio passerebbe - parentDep: ParentDependent # passa il servizio figlio al costruttore - childDep: ChildDependent # passa il servizio figlio al costruttore + fooDep: FooDependent # l'autowiring passa child al costruttore + barDep: BarDependent # LANCIA ECCEZIONE, nessun servizio corrisponde + parentDep: ParentDependent # l'autowiring passa child al costruttore + childDep: ChildDependent # l'autowiring passa child al costruttore ``` diff --git a/dependency-injection/it/configuration.texy b/dependency-injection/it/configuration.texy index 58a933c479..7155ff7ebf 100644 --- a/dependency-injection/it/configuration.texy +++ b/dependency-injection/it/configuration.texy @@ -1,32 +1,33 @@ -Configurazione del contenitore DI -********************************* +Configurazione del container DI +******************************* .[perex] -Panoramica delle opzioni di configurazione del contenitore Nette DI. +Panoramica delle opzioni di configurazione per il container Nette DI. File di configurazione ====================== -Il contenitore Nette DI può essere controllato facilmente tramite file di configurazione. Di solito sono scritti in [formato NEON |neon:format]. Per la modifica si consiglia di utilizzare [editor che supportino |best-practices:editors-and-tools#ide-editor] questo formato. +Il container Nette DI è facilmente controllabile tramite file di configurazione. Questi sono solitamente scritti nel [formato NEON|neon:format]. Per la modifica, consigliamo [editor con supporto |best-practices:editors-and-tools#Editor IDE] per questo formato. <pre> -"decorator .[prism-token prism-atrule]":[#Decorator]: "Decoratore .[prism-token prism-comment]"<br> -"di .[prism-token prism-atrule]":[#DI]: "Contenitore DI .[prism-token prism-comment]"<br> -"extensions .[prism-token prism-atrule]":[#Extensions]: "Installa estensioni DI aggiuntive .[prism-token prism-comment]"<br> -"includes .[prism-token prism-atrule]":[#Including files]: "File di inclusione .[prism-token prism-comment]"<br> -"parameters .[prism-token prism-atrule]":[#Parameters]: "Parametri .[prism-token prism-comment]"<br> +"decorator .[prism-token prism-atrule]":[#Decorator]: "Decorator .[prism-token prism-comment]"<br> +"di .[prism-token prism-atrule]":[#DI]: "Container DI .[prism-token prism-comment]"<br> +"extensions .[prism-token prism-atrule]":[#Estensioni]: "Installazione di estensioni DI aggiuntive .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[#Inclusione di file]: "Inclusione di file .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[#Parametri]: "Parametri .[prism-token prism-comment]"<br> "search .[prism-token prism-atrule]":[#Search]: "Registrazione automatica dei servizi .[prism-token prism-comment]"<br> "services .[prism-token prism-atrule]":[services]: "Servizi .[prism-token prism-comment]" </pre> -Per scrivere una stringa contenente il carattere `%`, you must escape it by doubling it to `%%`. .[note] +.[note] +Per scrivere una stringa contenente il carattere `%`, è necessario escaparlo raddoppiandolo in `%%`. -Parametri .[#toc-parameters] -============================ +Parametri +========= -È possibile definire dei parametri che possono essere utilizzati come parte delle definizioni dei servizi. Questo può aiutare a separare i valori che si desidera modificare più regolarmente. +Nella configurazione puoi definire parametri che possono poi essere utilizzati come parte delle definizioni dei servizi. In questo modo puoi rendere la configurazione più chiara o unificare ed estrarre valori che cambieranno. ```neon parameters: @@ -35,9 +36,9 @@ parameters: password: secret ``` -È possibile fare riferimento al parametro `foo` tramite `%foo%` in qualsiasi altro punto del file di configurazione. Possono anche essere usati all'interno di stringhe come `'%wwwDir%/images'`. +Ci riferiamo al parametro `dsn` ovunque nella configurazione scrivendo `%dsn%`. I parametri possono essere utilizzati anche all'interno di stringhe come `'%wwwDir%/images'`. -Non è necessario che i parametri siano solo stringhe, possono anche essere valori di array: +I parametri non devono essere solo stringhe o numeri, possono anche contenere array: ```neon parameters: @@ -48,32 +49,32 @@ parameters: languages: [cs, en, de] ``` -Si può fare riferimento a una singola chiave come `%mailer.user%`. +Ci riferiamo a una chiave specifica come `%mailer.user%`. -Se è necessario ottenere il valore di un parametro nel codice, ad esempio nella classe, passarlo a questa classe. Per esempio, nel costruttore. Non esiste un oggetto di configurazione globale che possa essere interrogato dalle classi per ottenere i valori dei parametri. Ciò sarebbe contrario al principio dell'iniezione di dipendenza. +Se hai bisogno nel tuo codice, ad esempio in una classe, di conoscere il valore di un qualsiasi parametro, passalo a questa classe. Ad esempio nel costruttore. Non esiste un oggetto globale che rappresenti la configurazione, a cui le classi chiedono i valori dei parametri. Ciò violerebbe il principio di dependency injection. -Servizi .[#toc-services] -======================== +Servizi +======= -Si veda il [capitolo separato |services]. +Vedi [capitolo separato|services]. -Decoratore .[#toc-decorator] -============================ +Decorator +========= -Come modificare in blocco tutti i servizi di un certo tipo? È necessario chiamare un certo metodo per tutti i presentatori che ereditano da un particolare antenato comune? Ecco dove nasce il decoratore. +Come modificare in blocco tutti i servizi di un certo tipo? Ad esempio, chiamare un certo metodo su tutti i presenter che ereditano da un antenato comune specifico? A questo serve il decorator. ```neon decorator: # per tutti i servizi che sono istanze di questa classe o interfaccia - App\Presenters\BasePresenter: + App\Presentation\BasePresenter: setup: - - setProjectId(10) # chiama questo metodo - - $absoluteUrls = true # e imposta la variabile + - setProjectId(10) # chiama questo metodo + - $absoluteUrls = true # e imposta la variabile ``` -Il decoratore può essere usato anche per impostare [i tag |services#Tags] o attivare la [modalità inject |services#Inject Mode]. +Il decorator può essere utilizzato anche per impostare [tag |services#Tag] o attivare la modalità [inject |services#Modalità Inject]. ```neon decorator: @@ -86,67 +87,79 @@ decorator: DI === -Impostazioni tecniche del contenitore DI. +Impostazioni tecniche del container DI. ```neon di: - # mostra il DIC nella barra Tracy? - debugger: ... # (bool) predefinito a true + # visualizzare DIC nella Tracy Bar? + debugger: ... # (bool) predefinito è true - # tipi di parametri che non devono mai essere autocablati + # tipi di parametri da non autowirare mai excluded: ... # (string[]) - # la classe da cui eredita il contenitore DI - parentClass: ... # (string) predefinita a Nette\DI\Container + # consentire la creazione lazy dei servizi? + lazy: ... # (bool) predefinito è false + + # classe da cui eredita il container DI + parentClass: ... # (string) predefinito è Nette\DI\Container ``` -Esportazione di metadati .[#toc-metadata-export] ------------------------------------------------- +Servizi lazy .{data-version:3.2.4} +---------------------------------- + +L'impostazione `lazy: true` attiva la creazione lazy (differita) dei servizi. Ciò significa che i servizi non vengono effettivamente creati nel momento in cui li richiediamo dal container DI, ma solo al momento del loro primo utilizzo. Ciò può accelerare l'avvio dell'applicazione e ridurre l'utilizzo della memoria, poiché vengono creati solo i servizi effettivamente necessari nella richiesta corrente. + +Per un servizio specifico, la creazione lazy può essere [modificata |services#Servizi Lazy]. + +.[note] +Gli oggetti lazy possono essere utilizzati solo per classi utente, non per classi PHP interne. Richiede PHP 8.4 o successivo. + -Anche la classe contenitore DI contiene molti metadati. È possibile ridurli riducendo l'esportazione dei metadati. +Esportazione metadati +--------------------- + +La classe del container DI contiene anche molti metadati. Puoi ridurne le dimensioni riducendo l'esportazione dei metadati. ```neon di: export: - # per esportare i parametri? - parameters: false # (bool) predefinito a true + # esportare i parametri? + parameters: false # (bool) predefinito è true # esportare i tag e quali? - tags: # (string[]|bool) il valore predefinito è tutti + tags: # (string[]|bool) predefiniti sono tutti - event.subscriber - # esporta i dati per il cablaggio automatico e quali? - types: # (string[]|bool) il valore predefinito è all + # esportare i dati per l'autowiring e quali? + types: # (string[]|bool) predefiniti sono tutti - Nette\Database\Connection - Symfony\Component\Console\Application ``` -Se non si usa l'array `$container->parameters`, si può disabilitare l'esportazione dei parametri. Inoltre, si possono esportare solo i tag attraverso i quali si ottengono servizi con il metodo `$container->findByTag(...)`. -Se non si chiama affatto il metodo, si può disabilitare completamente l'esportazione dei tag con `false`. +Se non utilizzi l'array `$container->getParameters()`, puoi disattivare l'esportazione dei parametri. Inoltre, puoi esportare solo i tag tramite i quali ottieni i servizi con il metodo `$container->findByTag(...)`. Se non chiami affatto il metodo, puoi disattivare completamente l'esportazione dei tag usando `false`. -È possibile ridurre in modo significativo i metadati per il [cablaggio automatico |autowiring], specificando le classi utilizzate come parametro del metodo `$container->getByType()`. -E ancora, se non si chiama affatto il metodo (o solo in [bootstrap |application:bootstrap] per ottenere `Nette\Application\Application`), si può disabilitare completamente l'esportazione con `false`. +Puoi ridurre significativamente i metadati per [l'autowiring |autowiring] specificando le classi che usi come parametro del metodo `$container->getByType()`. E ancora, se non chiami affatto il metodo (o solo nel [bootstrap|application:bootstrapping] per ottenere `Nette\Application\Application`), puoi disattivare completamente l'esportazione usando `false`. -Estensioni .[#toc-extensions] -============================= +Estensioni +========== -Registrazione di altre estensioni DI. In questo modo si aggiunge, ad esempio, l'estensione DI `Dibi\Bridges\Nette\DibiExtension22` con il nome `dibi`: +Registrazione di estensioni DI aggiuntive. In questo modo aggiungiamo ad esempio l'estensione DI `Dibi\Bridges\Nette\DibiExtension22` con il nome `dibi` ```neon extensions: dibi: Dibi\Bridges\Nette\DibiExtension22 ``` -Poi la configuriamo nella sua sezione chiamata anche `dibi`: +Successivamente, la configuriamo nella sezione `dibi`: ```neon dibi: host: localhost ``` -Si può anche aggiungere una classe di estensione con parametri: +Come estensione si può aggiungere anche una classe che ha parametri: ```neon extensions: @@ -154,10 +167,10 @@ extensions: ``` -Includere i file .[#toc-including-files] -======================================== +Inclusione di file +================== -È possibile inserire file di configurazione aggiuntivi nella sezione `includes`: +Possiamo includere altri file di configurazione nella sezione `includes`: ```neon includes: @@ -166,7 +179,7 @@ includes: - presenters.neon ``` -Il nome `parameters.php` non è un refuso, la configurazione può essere scritta anche in un file PHP, che la restituisce come array: +Il nome `parameters.php` non è un errore di battitura, la configurazione può essere scritta anche in un file PHP, che la restituisce come array: ```php <?php @@ -179,78 +192,74 @@ return [ ]; ``` -Se all'interno dei file di configurazione compaiono elementi con le stesse chiavi, questi verranno [sovrascritti o uniti |#Merging] nel caso di array. Il file incluso successivamente ha una priorità maggiore rispetto al precedente. Il file in cui è elencata la sezione `includes` ha una priorità maggiore rispetto ai file inclusi in esso. +Se nei file di configurazione compaiono elementi con le stesse chiavi, verranno sovrascritti o, nel caso di [array, uniti |#Unione]. Il file incluso successivamente ha una priorità maggiore rispetto al precedente. Il file in cui è specificata la sezione `includes` ha una priorità maggiore rispetto ai file inclusi al suo interno. -Ricerca .[#toc-search] -====================== +Search +====== -L'aggiunta automatica di servizi al contenitore DI rende il lavoro estremamente piacevole. Nette aggiunge automaticamente i presentatori al contenitore, ma è possibile aggiungere facilmente qualsiasi altra classe. +L'aggiunta automatica di servizi al container DI rende il lavoro estremamente piacevole. Nette aggiunge automaticamente i presenter al container, ma è possibile aggiungere facilmente anche qualsiasi altra classe. -Basta specificare in quali directory (e sottodirectory) devono essere cercate le classi: +Basta specificare in quali directory (e sottodirectory) cercare le classi: ```neon search: - # scegliete voi stessi i nomi delle sezioni - myForms: - in: %appDir%/Forms - - model: - in: %appDir%/Model + - in: %appDir%/Forms + - in: %appDir%/Model ``` -Di solito, però, non vogliamo aggiungere tutte le classi e le interfacce, quindi possiamo filtrarle: +Di solito, però, non vogliamo aggiungere assolutamente tutte le classi e le interfacce, quindi possiamo filtrarle: ```neon search: - myForms: - in: %appDir%/Forms + - in: %appDir%/Forms - # filtrare per nome di file (string|string[]) + # filtraggio per nome file (string|string[]) files: - *Factory.php - # filtrare per nome di classe (string|string[]) + # filtraggio per nome classe (string|string[]) classes: - *Factory ``` -Oppure possiamo selezionare le classi che ereditano o implementano almeno una delle seguenti classi: +Oppure possiamo selezionare classi che ereditano o implementano almeno una delle classi specificate: ```neon search: - myForms: + - in: %appDir% extends: - App\*Form implements: - App\*FormInterface ``` -Si possono anche definire regole negative, cioè maschere di nomi di classi o antenati e, se sono conformi, il servizio non sarà aggiunto al contenitore DI: +È possibile definire anche regole di esclusione, cioè maschere di nomi di classi o antenati ereditari, che se soddisfatte, il servizio non viene aggiunto al container DI: ```neon search: - myForms: + - in: %appDir% exclude: + files: ... classes: ... extends: ... implements: ... ``` -I tag possono essere impostati per i servizi aggiunti: +A tutti i servizi possono essere assegnati tag: ```neon search: - myForms: + - in: %appDir% tags: ... ``` -Fusione .[#toc-merging] -======================= +Unione +====== -Se elementi con le stesse chiavi appaiono in più file di configurazione, questi verranno sovrascritti o uniti nel caso di array. Il file incluso più tardi ha una priorità maggiore. +Se in più file di configurazione compaiono elementi con le stesse chiavi, verranno sovrascritti o, nel caso di array, uniti. Il file incluso successivamente ha una priorità maggiore rispetto al precedente. <table class=table> <tr> @@ -283,7 +292,7 @@ items: </tr> </table> -Per impedire l'unione di un determinato array, utilizzare il punto esclamativo subito dopo il nome dell'array: +Per gli array, è possibile impedire l'unione specificando un punto esclamativo dopo il nome della chiave: <table class=table> <tr> @@ -313,3 +322,5 @@ items: </td> </tr> </table> + +{{maintitle: Configurazione Dependency Injection}} diff --git a/dependency-injection/it/container.texy b/dependency-injection/it/container.texy index 03c091f174..d5ab3d2efd 100644 --- a/dependency-injection/it/container.texy +++ b/dependency-injection/it/container.texy @@ -1,16 +1,16 @@ -Che cos'è il contenitore DI? -**************************** +Cos'è un container DI? +********************** .[perex] -Il dependency injection container (DIC) è una classe che può istanziare e configurare oggetti. +Un container dependency injection (DIC) è una classe che può istanziare e configurare oggetti. -Può sorprendere, ma in molti casi non è necessario un contenitore per l'iniezione di dipendenza (dependency injection container) per trarre vantaggio dall'iniezione di dipendenza (DI in breve). Dopo tutto, anche nel [capitolo precedente |introduction] abbiamo mostrato esempi specifici di DI e non è stato necessario alcun contenitore. +Potrebbe sorprenderti, ma in molti casi non hai bisogno di un container dependency injection per sfruttare i vantaggi della dependency injection (abbreviato DI). Dopotutto, anche nel [capitolo introduttivo|introduction] abbiamo mostrato DI con esempi concreti e non era necessario alcun container. -Tuttavia, se è necessario gestire un gran numero di oggetti diversi con molte dipendenze, un contenitore di dependency injection sarà davvero utile. Questo è forse il caso delle applicazioni web costruite su un framework. +Tuttavia, se devi gestire un gran numero di oggetti diversi con molte dipendenze, un container dependency injection sarà davvero utile. Questo è il caso, ad esempio, delle applicazioni web costruite su un framework. -Nel capitolo precedente, abbiamo introdotto le classi `Article` e `UserController`. Entrambe hanno alcune dipendenze, ovvero il database e il factory `ArticleFactory`. Per queste classi, ora creeremo un contenitore. Naturalmente, per un esempio così semplice, non ha senso avere un contenitore. Ma ne creeremo uno per mostrare l'aspetto e il funzionamento. +Nel capitolo precedente, abbiamo introdotto le classi `Article` e `UserController`. Entrambe hanno alcune dipendenze, ovvero il database e la factory `ArticleFactory`. E per queste classi creeremo ora un container. Ovviamente, per un esempio così semplice, non ha senso avere un container. Ma lo creeremo per mostrare come appare e funziona. -Ecco un semplice contenitore codificato per l'esempio precedente: +Ecco un semplice container hardcoded per l'esempio fornito: ```php class Container @@ -32,16 +32,16 @@ class Container } ``` -L'uso sarebbe simile a questo: +L'utilizzo sarebbe il seguente: ```php $container = new Container; $controller = $container->createUserController(); ``` -Basta chiedere al contenitore l'oggetto e non è più necessario sapere come crearlo o quali sono le sue dipendenze; il contenitore sa tutto. Le dipendenze vengono iniettate automaticamente dal contenitore. Questo è il suo potere. +Chiediamo semplicemente l'oggetto al container e non dobbiamo più sapere nulla su come crearlo e quali dipendenze ha; il container sa tutto questo. Le dipendenze vengono iniettate automaticamente dal container. In questo sta la sua forza. -Finora, il contenitore ha tutto codificato. Quindi facciamo il passo successivo e aggiungiamo dei parametri per rendere il contenitore davvero utile: +Per ora, il container ha tutti i dati scritti in modo fisso. Faremo quindi il passo successivo e aggiungeremo parametri per rendere il container veramente utile: ```php class Container @@ -70,9 +70,9 @@ $container = new Container([ ]); ``` -I lettori più attenti avranno notato un problema. Ogni volta che ottengo un oggetto `UserController`, viene creata anche una nuova istanza `ArticleFactory` e un database. Questo non lo vogliamo assolutamente. +I lettori attenti potrebbero aver notato un certo problema. Ogni volta che ottengo un oggetto `UserController`, viene creata anche una nuova istanza di `ArticleFactory` e del database. Questo decisamente non lo vogliamo. -Aggiungiamo quindi un metodo `getService()` che restituirà sempre le stesse istanze: +Aggiungeremo quindi un metodo `getService()`, che restituirà sempre le stesse istanze: ```php class Container @@ -87,7 +87,7 @@ class Container public function getService(string $name): object { if (!isset($this->services[$name])) { - // getService('Database') chiama createDatabase() + // getService('Database') chiamerà createDatabase() $method = 'create' . $name; $this->services[$name] = $this->$method(); } @@ -98,9 +98,9 @@ class Container } ``` -La prima chiamata ad esempio a `$container->getService('Database')` farà sì che `createDatabase()` crei un oggetto database, che memorizzerà nell'array `$services` e lo restituirà direttamente alla chiamata successiva. +Alla prima chiamata, ad esempio `$container->getService('Database')`, farà creare l'oggetto database da `createDatabase()`, lo salverà nell'array `$services` e alla chiamata successiva lo restituirà direttamente. -Modifichiamo anche il resto del contenitore per usare `getService()`: +Modificheremo anche il resto del container per utilizzare `getService()`: ```php class Container @@ -119,9 +119,9 @@ class Container } ``` -A proposito, il termine servizio si riferisce a qualsiasi oggetto gestito dal contenitore. Da qui il nome del metodo `getService()`. +A proposito, il termine servizio si riferisce a qualsiasi oggetto gestito dal container. Ecco perché anche il nome del metodo `getService()`. -Fatto. Abbiamo un contenitore DI completamente funzionante! E possiamo usarlo: +Fatto. Abbiamo un container DI completamente funzionante! E possiamo usarlo: ```php $container = new Container([ @@ -134,6 +134,9 @@ $controller = $container->getService('UserController'); $database = $container->getService('Database'); ``` -Come si può vedere, non è difficile scrivere un DI. È da notare che gli oggetti stessi non sanno che un contenitore li sta creando. Pertanto, è possibile creare qualsiasi oggetto in PHP in questo modo, senza modificare il codice sorgente. +Come vedi, scrivere un DIC non è complicato. Vale la pena ricordare che gli oggetti stessi non sanno di essere creati da un container. Di conseguenza, è possibile creare in questo modo qualsiasi oggetto in PHP senza intervenire sul suo codice sorgente. -Creare e mantenere manualmente una classe contenitore può diventare un incubo piuttosto rapidamente. Pertanto, nel prossimo capitolo parleremo di [Nette DI Container |nette-container], che può generare e aggiornare se stesso in modo quasi automatico. +La creazione e la manutenzione manuale della classe del container possono diventare rapidamente un incubo. Nel prossimo capitolo, parleremo quindi del [Container Nette DI|nette-container], che può generarsi e aggiornarsi quasi da solo. + + +{{maintitle: Cos'è un container dependency injection?}} diff --git a/dependency-injection/it/extensions.texy b/dependency-injection/it/extensions.texy index 380674ded4..2c7da1fc47 100644 --- a/dependency-injection/it/extensions.texy +++ b/dependency-injection/it/extensions.texy @@ -2,38 +2,38 @@ Creazione di estensioni per Nette DI ************************************ .[perex] -La generazione di un contenitore DI, oltre ai file di configurazione, riguarda anche le cosiddette *estensioni*. Le attiviamo nel file di configurazione nella sezione `extensions`. +La generazione del container DI, oltre ai file di configurazione, è influenzata anche dalle cosiddette *estensioni*. Le attiviamo nel file di configurazione nella sezione `extensions`. -In questo modo si aggiunge l'estensione rappresentata dalla classe `BlogExtension` con il nome `blog`: +In questo modo aggiungiamo l'estensione rappresentata dalla classe `BlogExtension` con il nome `blog`: ```neon extensions: blog: BlogExtension ``` -Ogni estensione del compilatore eredita da [api:Nette\DI\CompilerExtension] e può implementare i seguenti metodi che vengono richiamati durante la compilazione di DI: +Ogni estensione del compilatore eredita da [api:Nette\DI\CompilerExtension] e può implementare i seguenti metodi, che vengono chiamati in sequenza durante la costruzione del container DI: 1. getConfigSchema() 2. loadConfiguration() -3. prima della compilazione() -4. dopo la compilazione() +3. beforeCompile() +4. afterCompile() getConfigSchema() .[method] =========================== -Questo metodo viene chiamato per primo. Definisce lo schema usato per convalidare i parametri di configurazione. +Questo metodo viene chiamato per primo. Definisce lo schema per la validazione dei parametri di configurazione. -Le estensioni sono configurate in una sezione il cui nome è lo stesso di quella in cui è stata aggiunta l'estensione, ad esempio `blog`. +Configuriamo l'estensione nella sezione il cui nome è lo stesso di quello con cui è stata aggiunta l'estensione, cioè `blog`: ```neon -# Lo stesso nome della mia estensione +# stesso nome dell'estensione blog: postsPerPage: 10 - comments: false + allowComments: false ``` -Verrà definito uno schema che descrive tutte le opzioni di configurazione, compresi i loro tipi, i valori accettati ed eventualmente i valori predefiniti: +Creiamo uno schema che descrive tutte le opzioni di configurazione, inclusi i loro tipi, valori consentiti ed eventualmente anche valori predefiniti: ```php use Nette\Schema\Expect; @@ -50,9 +50,9 @@ class BlogExtension extends Nette\DI\CompilerExtension } ``` -Vedere lo [Schema |schema:] per la documentazione. Inoltre, è possibile specificare quali opzioni possono essere [dinamiche |application:bootstrap#Dynamic Parameters] usando `dynamic()`, per esempio `Expect::int()->dynamic()`. +La documentazione si trova nella pagina [Schema |schema:]. Inoltre, è possibile specificare quali opzioni possono essere [dinamiche |application:bootstrapping#Parametri Dinamici] usando `dynamic()`, ad es. `Expect::int()->dynamic()`. -Si accede alla configurazione tramite `$this->config`, che è un oggetto `stdClass`: +Accediamo alla configurazione tramite la variabile `$this->config`, che è un oggetto `stdClass`: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -71,7 +71,7 @@ class BlogExtension extends Nette\DI\CompilerExtension loadConfiguration() .[method] ============================= -Questo metodo è usato per aggiungere servizi al contenitore. Questo viene fatto da [api:Nette\DI\ContainerBuilder]: +Utilizzato per aggiungere servizi al container. A questo serve [api:Nette\DI\ContainerBuilder]: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -80,25 +80,25 @@ class BlogExtension extends Nette\DI\CompilerExtension { $builder = $this->getContainerBuilder(); $builder->addDefinition($this->prefix('articles')) - ->setFactory(App\Model\HomepageArticles::class, ['@connection']) // o setCreator() + ->setFactory(App\Model\HomepageArticles::class, ['@connection']) // or setCreator() ->addSetup('setLogger', ['@logger']); } } ``` -La convenzione prevede che i servizi aggiunti da un'estensione abbiano un prefisso con il suo nome, in modo da evitare conflitti di nomi. Questo viene fatto da `prefix()`, quindi se l'estensione si chiama "blog", il servizio si chiamerà `blog.articles`. +La convenzione è di prefissare i servizi aggiunti dall'estensione con il suo nome, per evitare conflitti di nomi. Questo lo fa il metodo `prefix()`, quindi se l'estensione si chiama `blog`, il servizio si chiamerà `blog.articles`. -Se dobbiamo rinominare un servizio, possiamo creare un alias con il suo nome originale per mantenere la retrocompatibilità. Lo stesso fa Nette per esempio per `routing.router`, che è disponibile anche con il nome precedente `router`. +Se dobbiamo rinominare un servizio, possiamo creare un alias con il nome originale per mantenere la compatibilità all'indietro. Nette fa qualcosa di simile, ad esempio, con il servizio `routing.router`, che è disponibile anche con il nome precedente `router`. ```php $builder->addAlias('router', 'routing.router'); ``` -Recuperare i servizi da un file .[#toc-retrieve-services-from-a-file] ---------------------------------------------------------------------- +Caricamento dei servizi da file +------------------------------- -È possibile creare servizi utilizzando l'API ContainerBuilder, ma anche aggiungerli tramite il noto file di configurazione NEON e la sua sezione `services`. Il prefisso `@extension` rappresenta l'estensione corrente. +Non dobbiamo creare servizi solo tramite l'API della classe ContainerBuilder, ma anche con la nota sintassi utilizzata nel file di configurazione NEON nella sezione services. Il prefisso `@extension` rappresenta l'estensione corrente. ```neon services: @@ -112,7 +112,7 @@ services: create: MyBlog\Components\ArticlesList(@extension.articles) ``` -Aggiungeremo i servizi in questo modo: +Carichiamo i servizi: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -121,7 +121,7 @@ class BlogExtension extends Nette\DI\CompilerExtension { $builder = $this->getContainerBuilder(); - // carica il file di configurazione dell'estensione + // caricamento del file di configurazione per l'estensione $this->compiler->loadDefinitionsFromConfig( $this->loadFromFile(__DIR__ . '/blog.neon')['services'], ); @@ -133,7 +133,7 @@ class BlogExtension extends Nette\DI\CompilerExtension beforeCompile() .[method] ========================= -Il metodo viene richiamato quando il contenitore contiene tutti i servizi aggiunti dalle singole estensioni nei metodi `loadConfiguration` e i file di configurazione dell'utente. In questa fase di assemblaggio, si possono modificare le definizioni dei servizi o aggiungere collegamenti tra di essi. Si può usare il metodo `findByTag()` per cercare i servizi per tag, o il metodo `findByType()` per cercare per classe o interfaccia. +Il metodo viene chiamato nel momento in cui il container contiene tutti i servizi aggiunti dalle singole estensioni nei metodi `loadConfiguration` e anche dai file di configurazione utente. In questa fase di costruzione, possiamo quindi modificare le definizioni dei servizi o aggiungere legami tra di essi. Per cercare servizi nel container in base ai tag, si può utilizzare il metodo `findByTag()`, per classe o interfaccia invece il metodo `findByType()`. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -153,7 +153,7 @@ class BlogExtension extends Nette\DI\CompilerExtension afterCompile() .[method] ======================== -In questa fase, la classe contenitore è già generata come oggetto [ClassType |php-generator:#classes], contiene tutti i metodi che il servizio crea ed è pronta per la cache come file PHP. A questo punto, possiamo ancora modificare il codice della classe risultante. +In questa fase, la classe del container è già generata sotto forma di oggetto [ClassType |php-generator:#Classi], contiene tutti i metodi che creano i servizi ed è pronta per essere scritta nella cache. Possiamo ancora modificare il codice risultante della classe in questo momento. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -167,12 +167,12 @@ class BlogExtension extends Nette\DI\CompilerExtension ``` -$inizializzazione .[wiki-method] -================================ +$initialization .[method] +========================= -Il Configuratore chiama il codice di inizializzazione dopo la [creazione del contenitore |application:bootstrap#index.php], che viene creato scrivendo su un oggetto `$this->initialization` con il [metodo addBody() |php-generator:#method-and-function-body]. +La classe Configurator, dopo la [creazione del container |application:bootstrapping#index.php], chiama il codice di inizializzazione, che viene creato scrivendo nell'oggetto `$this->initialization` tramite il [metodo addBody() |php-generator:#Corpi di metodi e funzioni]. -Verrà mostrato un esempio di come avviare una sessione o servizi che hanno il tag `run` usando il codice di inizializzazione: +Mostriamo un esempio di come, ad esempio, avviare la sessione con il codice di inizializzazione o avviare servizi che hanno il tag `run`: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -184,7 +184,7 @@ class BlogExtension extends Nette\DI\CompilerExtension $this->initialization->addBody('$this->getService("session")->start()'); } - // i servizi con tag 'run' devono essere creati dopo l'istanziazione del contenitore + // i servizi con il tag run devono essere creati dopo l'istanza del container $builder = $this->getContainerBuilder(); foreach ($builder->findByTag('run') as $name => $foo) { $this->initialization->addBody('$this->getService(?);', [$name]); diff --git a/dependency-injection/it/factory.texy b/dependency-injection/it/factory.texy index d2645a41ed..6ee12f3e24 100644 --- a/dependency-injection/it/factory.texy +++ b/dependency-injection/it/factory.texy @@ -1,12 +1,12 @@ -Fabbriche generate -****************** +Factory generate +**************** .[perex] -Nette DI può generare automaticamente il codice di fabbrica basato sull'interfaccia, evitando così di scrivere codice. +Nette DI può generare automaticamente il codice delle factory basandosi su interfacce, risparmiandoti la scrittura di codice. -Un factory è una classe che crea e configura gli oggetti. Pertanto, passa anche le loro dipendenze. Non bisogna confondersi con il design pattern *metodo di fabbrica*, che descrive un modo specifico di usare i factory e non è correlato a questo argomento. +Una factory è una classe che produce e configura oggetti. Quindi passa loro anche le loro dipendenze. Si prega di non confondere con il design pattern *factory method*, che descrive un modo specifico di utilizzare le factory e non è correlato a questo argomento. -Abbiamo mostrato l'aspetto di una fabbrica di questo tipo nel [capitolo introduttivo |introduction#factory]: +Come appare una tale factory lo abbiamo mostrato nel [capitolo introduttivo |introduction#Factory]: ```php class ArticleFactory @@ -23,7 +23,7 @@ class ArticleFactory } ``` -Nette DI può generare automaticamente il codice del factory. Tutto ciò che si deve fare è creare un'interfaccia e Nette DI genererà un'implementazione. L'interfaccia deve avere esattamente un metodo chiamato `create` e dichiarare un tipo di ritorno: +Nette DI può generare automaticamente il codice delle factory. Tutto ciò che devi fare è creare un'interfaccia e Nette DI genererà l'implementazione. L'interfaccia deve avere esattamente un metodo chiamato `create` e dichiarare il tipo di ritorno: ```php interface ArticleFactory @@ -32,7 +32,7 @@ interface ArticleFactory } ``` -Quindi il factory `ArticleFactory` ha un metodo `create` che crea oggetti `Article`. La classe `Article` potrebbe avere il seguente aspetto, ad esempio: +Quindi la factory `ArticleFactory` ha un metodo `create`, che crea oggetti `Article`. La classe `Article` può apparire ad esempio così: ```php class Article @@ -44,16 +44,16 @@ class Article } ``` -Aggiungere il factory al file di configurazione: +Aggiungiamo la factory al file di configurazione: ```neon services: - ArticleFactory ``` -Nette DI genererà l'implementazione del factory corrispondente. +Nette DI genererà l'implementazione corrispondente della factory. -Pertanto, nel codice che utilizza il factory, si richiede l'oggetto per interfaccia e Nette DI utilizza l'implementazione generata: +Nel codice che utilizza la factory, richiediamo quindi l'oggetto tramite l'interfaccia e Nette DI utilizzerà l'implementazione generata: ```php class UserController @@ -65,17 +65,17 @@ class UserController public function foo() { - // lasciamo che il factory crei un oggetto + // facciamo creare l'oggetto alla factory $article = $this->articleFactory->create(); } } ``` -Fabbrica parametrizzata .[#toc-parameterized-factory] -===================================================== +Factory parametrizzata +====================== -Il metodo factory `create` può accettare parametri, che poi passa al costruttore. Per esempio, aggiungiamo l'ID dell'autore di un articolo alla classe `Article`: +Il metodo della factory `create` può accettare parametri, che poi passa al costruttore. Aggiungiamo ad esempio alla classe `Article` l'ID dell'autore dell'articolo: ```php class Article @@ -88,7 +88,7 @@ class Article } ``` -Aggiungeremo anche il parametro al factory: +Aggiungiamo il parametro anche alla factory: ```php interface ArticleFactory @@ -97,13 +97,13 @@ interface ArticleFactory } ``` -Poiché il parametro nel costruttore e quello nel factory hanno lo stesso nome, Nette DI li passerà automaticamente. +Grazie al fatto che il parametro nel costruttore e il parametro nella factory si chiamano allo stesso modo, Nette DI li passa in modo completamente automatico. -Definizione avanzata .[#toc-advanced-definition] -================================================ +Definizione avanzata +==================== -La definizione può essere scritta anche in forma multilinea utilizzando il tasto `implement`: +La definizione può essere scritta anche in forma multiriga utilizzando la chiave `implement`: ```neon services: @@ -111,9 +111,9 @@ services: implement: ArticleFactory ``` -Quando si scrive in questo modo più lungo, è possibile fornire argomenti aggiuntivi per il costruttore nella chiave `arguments` e configurazioni aggiuntive usando `setup`, proprio come per i servizi normali. +Scrivendo in questo modo più lungo, è possibile specificare argomenti aggiuntivi per il costruttore nella chiave `arguments` e configurazioni supplementari tramite `setup`, proprio come per i servizi normali. -Esempio: se il metodo `create()` non accettasse il parametro `$authorId`, si potrebbe specificare nella configurazione un valore fisso da passare al costruttore `Article`: +Esempio: se il metodo `create()` non accettasse il parametro `$authorId`, potremmo specificare un valore fisso nella configurazione, che verrebbe passato al costruttore di `Article`: ```neon services: @@ -123,7 +123,7 @@ services: authorId: 123 ``` -Oppure, al contrario, se `create()` accettasse il parametro `$authorId`, ma non facesse parte del costruttore e fosse passato dal metodo `Article::setAuthorId()`, faremmo riferimento ad esso nella sezione `setup`: +O al contrario, se `create()` accettasse il parametro `$authorId`, ma non fosse parte del costruttore e venisse passato tramite il metodo `Article::setAuthorId()`, ci riferiremmo ad esso nella sezione `setup`: ```neon services: @@ -134,15 +134,14 @@ services: ``` -Accessore .[#toc-accessor] -========================== +Accessor +======== -Oltre ai factory, Nette può anche generare i cosiddetti accessor. L'accessor è un oggetto con il metodo `get()` che restituisce un particolare servizio dal contenitore DI. Più chiamate a `get()` restituiranno sempre la stessa istanza. +Nette, oltre alle factory, può generare anche i cosiddetti accessor. Si tratta di oggetti con un metodo `get()`, che restituisce un determinato servizio dal container DI. Chiamate ripetute a `get()` restituiscono sempre la stessa istanza. -Gli accessor portano il lazy-loading alle dipendenze. Abbiamo una classe che registra gli errori in un database speciale. Se la connessione al database fosse passata come dipendenza nel suo costruttore, la connessione dovrebbe essere sempre creata, anche se verrebbe usata solo raramente, quando appare un errore, quindi la connessione rimarrebbe per lo più inutilizzata. -Invece, la classe può passare un accessor e quando viene chiamato il suo metodo `get()`, solo allora viene creato l'oggetto database: +Gli accessor forniscono il lazy-loading alle dipendenze. Supponiamo di avere una classe che scrive errori in un database speciale. Se questa classe ricevesse la connessione al database come dipendenza tramite il costruttore, la connessione dovrebbe sempre essere creata, anche se in pratica un errore si verifica solo eccezionalmente e quindi la maggior parte delle volte la connessione rimarrebbe inutilizzata. Invece, la classe riceve un accessor e solo quando viene chiamato il suo `get()`, viene creato l'oggetto del database: -Come creare un accessor? Scrivete solo un'interfaccia e Nette DI genererà l'implementazione. L'interfaccia deve avere esattamente un metodo chiamato `get` e deve dichiarare il tipo di ritorno: +Come creare un accessor? Basta scrivere un'interfaccia e Nette DI genererà l'implementazione. L'interfaccia deve avere esattamente un metodo chiamato `get` e dichiarare il tipo di ritorno: ```php interface PDOAccessor @@ -151,7 +150,7 @@ interface PDOAccessor } ``` -Aggiungete l'accessor al file di configurazione insieme alla definizione del servizio che l'accessor restituirà: +Aggiungiamo l'accessor al file di configurazione, dove è definita anche la definizione del servizio che restituirà: ```neon services: @@ -159,62 +158,68 @@ services: - PDO(%dsn%, %user%, %password%) ``` -L'accessor restituisce un servizio di tipo `PDO` e poiché esiste un solo servizio di questo tipo nella configurazione, l'accessor lo restituirà. Con più servizi configurati di quel tipo, si può specificare quale deve essere restituito usando il suo nome, per esempio `- PDOAccessor(@db1)`. +Poiché l'accessor restituisce un servizio di tipo `PDO` e nella configurazione c'è un solo servizio di questo tipo, restituirà proprio quello. Se ci fossero più servizi di quel tipo, specificheremmo il servizio restituito tramite il nome, ad es. `- PDOAccessor(@db1)`. -Multifactory/Accessor .[#toc-multifactory-accessor] -=================================================== -Finora, i factory e gli accessor potevano creare o restituire un solo oggetto. È possibile creare anche una multifactory combinata con un accessor. L'interfaccia di questa classe multifactory può essere composta da più metodi chiamati `create<name>()` e `get<name>()`ad esempio: +Factory/accessor multipli +========================= +Le nostre factory e accessor finora sapevano sempre produrre o restituire solo un oggetto. Ma è possibile creare molto facilmente anche factory multiple combinate con accessor. L'interfaccia di una tale classe conterrà un numero qualsiasi di metodi con nomi `create<name>()` e `get<name>()`, ad es.: ```php interface MultiFactory { function createArticle(): Article; - function createFoo(): Model\Foo; function getDb(): PDO; } ``` -Invece di passare più factory e accessor generati, si può passare un solo multifactory complesso. +Quindi, invece di passare diverse factory e accessor generati, passiamo una factory più complessa che sa fare di più. -In alternativa, si possono usare `create()` e `get()` con un parametro, invece di più metodi: +In alternativa, invece di più metodi, si può usare `get()` con un parametro: ```php interface MultiFactoryAlt { - function create($name); function get($name): PDO; } ``` -In questo caso, `MultiFactory::createArticle()` fa la stessa cosa di `MultiFactoryAlt::create('article')`. Tuttavia, la sintassi alternativa presenta alcuni svantaggi. Non è chiaro quali valori di `$name` siano supportati e il tipo di ritorno non può essere specificato nell'interfaccia quando si usano più valori diversi di `$name`. +Allora vale che `MultiFactory::getArticle()` fa la stessa cosa di `MultiFactoryAlt::get('article')`. Tuttavia, la scrittura alternativa ha lo svantaggio che non è chiaro quali valori di `$name` siano supportati e logicamente non è possibile distinguere nell'interfaccia diversi valori di ritorno per diversi `$name`. -Definizione con un elenco .[#toc-definition-with-a-list] --------------------------------------------------------- -Come definire una multifactory nella propria configurazione? Creiamo tre servizi che saranno restituiti dalla multifactory e la multifactory stessa: +Definizione tramite elenco +-------------------------- +In questo modo è possibile definire una factory multipla nella configurazione: .{data-version:3.2.0} + +```neon +services: + - MultiFactory( + article: Article # definisce createArticle() + db: PDO(%dsn%, %user%, %password%) # definisce getDb() + ) +``` + +Oppure possiamo riferirci a servizi esistenti nella definizione della factory tramite riferimento: ```neon services: article: Article - - Model\Foo - PDO(%dsn%, %user%, %password%) - MultiFactory( - article: @article # createArticle() - foo: @Model\Foo # createFoo() - db: @\PDO # getDb() + article: @article # definisce createArticle() + db: @\PDO # definisce getDb() ) ``` -Definizione con tag .[#toc-definition-with-tags] ------------------------------------------------- +Definizione tramite tag +----------------------- -Un'altra opzione per definire una multifactory è quella di usare i [tag |services#Tags]: +La seconda possibilità è utilizzare per la definizione i [tag |services#Tag]: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter - App\Model\DatabaseAccessor( db1: @database.db1.explorer ) diff --git a/dependency-injection/it/faq.texy b/dependency-injection/it/faq.texy index 4dfa2d5e1a..794c8dfb48 100644 --- a/dependency-injection/it/faq.texy +++ b/dependency-injection/it/faq.texy @@ -2,97 +2,91 @@ Domande frequenti su DI (FAQ) ***************************** -DI è un altro nome per IoC? .[#toc-is-di-another-name-for-ioc] --------------------------------------------------------------- +DI è un altro nome per IoC? +--------------------------- -*Inversion of Control* (IoC) è un principio che si concentra sul modo in cui il codice viene eseguito: sia che il codice inizi il codice esterno, sia che il codice sia integrato nel codice esterno, che poi lo chiama. -L'IoC è un concetto ampio che include gli [eventi |nette:glossary#Events], il cosiddetto [principio di Hollywood |application:components#Hollywood style] e altri aspetti. -Anche le fabbriche, che fanno parte della [regola #3: Let the Factory Handle It |introduction#Rule #3: Let the Factory Handle It], e che rappresentano l'inversione dell'operatore `new`, sono componenti di questo concetto. +*Inversion of Control* (IoC) è un principio focalizzato sul modo in cui il codice viene eseguito - se il tuo codice esegue codice altrui o se il tuo codice è integrato in codice altrui, che poi lo chiama. IoC è un termine ampio che include [eventi |nette:glossary#Eventi], il cosiddetto [Principio di Hollywood |application:components#Stile Hollywood] e altri aspetti. Parte di questo concetto sono anche le factory, di cui parla la [Regola n. 3: lascialo alla factory |introduction#Regola n. 3: lascia fare alla factory], e che rappresentano un'inversione per l'operatore `new`. -La *Dependency Injection* (DI) riguarda il modo in cui un oggetto conosce un altro oggetto, cioè la dipendenza. È un modello di progettazione che richiede il passaggio esplicito delle dipendenze tra gli oggetti. +*Dependency Injection* (DI) si concentra sul modo in cui un oggetto viene a conoscenza di un altro oggetto, cioè delle sue dipendenze. È un design pattern che richiede il passaggio esplicito delle dipendenze tra oggetti. -Pertanto, si può dire che DI sia una forma specifica di IoC. Tuttavia, non tutte le forme di IoC sono adatte in termini di purezza del codice. Per esempio, tra gli anti-pattern, includiamo tutte le tecniche che lavorano con lo [stato globale |global state] o il cosiddetto [Service Locator |#What is a Service Locator]. +Si può quindi dire che DI è una forma specifica di IoC. Tuttavia, non tutte le forme di IoC sono adatte dal punto di vista della pulizia del codice. Ad esempio, tra gli antipattern ci sono tecniche che lavorano con lo [stato globale |global-state] o il cosiddetto [Service Locator |#Cos è il Service Locator]. -Che cos'è un Service Locator? .[#toc-what-is-a-service-locator] ---------------------------------------------------------------- +Cos'è il Service Locator? +------------------------- -Un localizzatore di servizi è un'alternativa alla Dependency Injection. Funziona creando un archivio centrale in cui sono registrati tutti i servizi o le dipendenze disponibili. Quando un oggetto ha bisogno di una dipendenza, la richiede al Service Locator. +È un'alternativa alla Dependency Injection. Funziona creando un repository centrale dove sono registrati tutti i servizi o le dipendenze disponibili. Quando un oggetto ha bisogno di una dipendenza, la richiede al Service Locator. -Tuttavia, rispetto alla Dependency Injection, perde in trasparenza: le dipendenze non vengono passate direttamente agli oggetti e non sono quindi facilmente identificabili, il che richiede l'esame del codice per scoprire e comprendere tutte le connessioni. Anche i test sono più complicati, perché non si possono semplicemente passare gli oggetti mock agli oggetti testati, ma bisogna passare attraverso il Service Locator. Inoltre, il Service Locator sconvolge la progettazione del codice, poiché i singoli oggetti devono essere a conoscenza della sua esistenza, a differenza della Dependency Injection, in cui gli oggetti non sono a conoscenza del contenitore DI. +Rispetto alla Dependency Injection, tuttavia, perde in trasparenza: le dipendenze non vengono passate direttamente agli oggetti e non sono quindi facilmente identificabili, il che richiede l'esame del codice per rivelare e comprendere tutti i legami. Anche il testing è più complesso, perché non possiamo semplicemente passare oggetti mock agli oggetti testati, ma dobbiamo passare attraverso il Service Locator. Inoltre, il Service Locator infrange il design del codice, poiché i singoli oggetti devono essere a conoscenza della sua esistenza, il che differisce dalla Dependency Injection, dove gli oggetti non sono consapevoli del container DI. -Quando è meglio non usare DI? .[#toc-when-is-it-better-not-to-use-di] ---------------------------------------------------------------------- +Quando è meglio non usare DI? +----------------------------- -Non ci sono difficoltà note associate all'uso del design pattern Dependency Injection. Al contrario, ottenere le dipendenze da posizioni accessibili a livello globale comporta [una serie di complicazioni |global-state], così come l'uso di un Service Locator. -Pertanto, è consigliabile utilizzare sempre la DI. Non si tratta di un approccio dogmatico, ma semplicemente non è stata trovata un'alternativa migliore. +Non sono note difficoltà associate all'uso del design pattern Dependency Injection. Al contrario, ottenere dipendenze da luoghi globalmente accessibili porta a [tutta una serie di complicazioni |global-state], così come l'uso del Service Locator. Pertanto, è consigliabile utilizzare sempre DI. Questo non è un approccio dogmatico, ma semplicemente non è stata trovata un'alternativa migliore. -Tuttavia, ci sono alcune situazioni in cui non è possibile passare gli oggetti tra loro e ottenerli dallo spazio globale. Ad esempio, quando si esegue il debug del codice e si ha bisogno di scaricare il valore di una variabile in un punto specifico del programma, di misurare la durata di una certa parte del programma o di registrare un messaggio. -In questi casi, quando si tratta di azioni temporanee che verranno successivamente rimosse dal codice, è legittimo utilizzare un dumper, un cronometro o un logger accessibile a livello globale. Questi strumenti, dopo tutto, non appartengono alla progettazione del codice. +Tuttavia, esistono alcune situazioni in cui non passiamo oggetti e li otteniamo dallo spazio globale. Ad esempio, durante il debugging del codice, quando è necessario stampare il valore di una variabile in un punto specifico del programma, misurare la durata di una certa parte del programma o registrare un messaggio. In tali casi, quando si tratta di operazioni temporanee che verranno successivamente rimosse dal codice, è legittimo utilizzare un dumper, un cronometro o un logger globalmente accessibili. Questi strumenti, infatti, non appartengono al design del codice. -L'uso di DI ha degli svantaggi? .[#toc-does-using-di-have-its-drawbacks] ------------------------------------------------------------------------- +L'uso di DI ha i suoi lati negativi? +------------------------------------ -L'uso della Dependency Injection comporta qualche svantaggio, come una maggiore complessità di scrittura del codice o prestazioni peggiori? Che cosa perdiamo quando iniziamo a scrivere codice secondo la DI? +L'uso della Dependency Injection comporta degli svantaggi, come ad esempio una maggiore complessità nella scrittura del codice o prestazioni peggiori? Cosa perdiamo quando iniziamo a scrivere codice in conformità con DI? -DI non ha alcun impatto sulle prestazioni dell'applicazione o sui requisiti di memoria. Le prestazioni del contenitore DI possono avere un ruolo, ma nel caso di [Nette DI | nette-container], il contenitore è compilato in PHP puro, quindi il suo overhead durante l'esecuzione dell'applicazione è sostanzialmente nullo. +DI non ha alcun impatto sulle prestazioni o sui requisiti di memoria dell'applicazione. Le prestazioni del DI Container possono giocare un certo ruolo, tuttavia, nel caso di [Nette DI |nette-container], il container viene compilato in PHP puro, quindi il suo overhead durante l'esecuzione dell'applicazione è essenzialmente nullo. -Quando si scrive il codice, è necessario creare costruttori che accettino le dipendenze. In passato, questo poteva richiedere molto tempo, ma grazie ai moderni IDE e alla [promozione delle proprietà dei costruttori |https://blog.nette.org/it/php-8-0-panoramica-completa-delle-novita#toc-constructor-property-promotion], ora è una questione di pochi secondi. Le fabbriche possono essere facilmente generate utilizzando Nette DI e un plugin di PhpStorm con pochi clic. -D'altra parte, non è necessario scrivere singleton e punti di accesso statici. +Durante la scrittura del codice, è spesso necessario creare costruttori che accettano dipendenze. In passato questo poteva essere noioso, ma grazie agli IDE moderni e alla [constructor property promotion |https://blog.nette.org/it/php-8-0-complete-overview-of-news#toc-constructor-property-promotion], ora è questione di pochi secondi. Le factory possono essere facilmente generate usando Nette DI e il plugin per PhpStorm con un clic del mouse. D'altra parte, scompare la necessità di scrivere singleton e punti di accesso statici. -Si può concludere che un'applicazione progettata correttamente utilizzando DI non è né più corta né più lunga rispetto a un'applicazione che utilizza singleton. Le parti di codice che lavorano con le dipendenze vengono semplicemente estratte dalle singole classi e spostate in nuove posizioni, cioè nel contenitore DI e nelle fabbriche. +Si può affermare che un'applicazione progettata correttamente che utilizza DI non è né più corta né più lunga di un'applicazione che utilizza singleton. Le parti di codice che lavorano con le dipendenze vengono semplicemente estratte dalle singole classi e spostate in nuovi luoghi, cioè nel container DI e nelle factory. -Come riscrivere un'applicazione legacy in DI? .[#toc-how-to-rewrite-a-legacy-application-to-di] ------------------------------------------------------------------------------------------------ +Come riscrivere un'applicazione legacy in DI? +--------------------------------------------- -La migrazione da un'applicazione legacy alla Dependency Injection può essere un processo impegnativo, soprattutto per applicazioni grandi e complesse. È importante affrontare questo processo in modo sistematico. +La transizione da un'applicazione legacy alla Dependency Injection può essere un processo impegnativo, soprattutto per applicazioni grandi e complesse. È importante approcciare questo processo in modo sistematico. -- Quando si passa alla Dependency Injection, è importante che tutti i membri del team comprendano i principi e le pratiche da utilizzare. -- In primo luogo, è necessario eseguire un'analisi dell'applicazione esistente per identificare i componenti chiave e le loro dipendenze. Creare un piano per le parti da rifattorizzare e in quale ordine. -- Implementare un contenitore DI o, meglio ancora, utilizzare una libreria esistente come Nette DI. -- Rifattorizzare gradualmente ogni parte dell'applicazione per utilizzare la Dependency Injection. Ciò può comportare la modifica di costruttori o metodi per accettare le dipendenze come parametri. -- Modificare i punti del codice in cui vengono creati gli oggetti di dipendenza, in modo che le dipendenze siano invece iniettate dal contenitore. Questo può includere l'uso di fabbriche. +- Durante la transizione alla Dependency Injection, è importante che tutti i membri del team comprendano i principi e le procedure utilizzate. +- Innanzitutto, esegui un'analisi dell'applicazione esistente e identifica i componenti chiave e le loro dipendenze. Crea un piano su quali parti verranno refattorizzate e in quale ordine. +- Implementa un container DI o, ancora meglio, utilizza una libreria esistente, ad esempio Nette DI. +- Refattorizza gradualmente le singole parti dell'applicazione per utilizzare la Dependency Injection. Ciò può includere la modifica di costruttori o metodi in modo che accettino le dipendenze come parametri. +- Modifica i punti nel codice in cui vengono creati oggetti con dipendenze, in modo che invece le dipendenze vengano iniettate dal container. Ciò può includere l'uso di factory. -Ricordate che il passaggio alla Dependency Injection è un investimento nella qualità del codice e nella sostenibilità a lungo termine dell'applicazione. Sebbene possa essere impegnativo apportare queste modifiche, il risultato dovrebbe essere un codice più pulito, modulare e facilmente testabile, pronto per estensioni e manutenzioni future. +Ricorda che la transizione alla Dependency Injection è un investimento nella qualità del codice e nella manutenibilità a lungo termine dell'applicazione. Sebbene possa essere impegnativo apportare queste modifiche, il risultato dovrebbe essere un codice più pulito, modulare e facilmente testabile, pronto per future estensioni e manutenzione. -Perché la composizione è preferibile all'ereditarietà? .[#toc-why-composition-is-preferred-over-inheritance] ------------------------------------------------------------------------------------------------------------- -È preferibile usare la composizione piuttosto che l'ereditarietà, perché serve a riutilizzare il codice senza doversi preoccupare dell'effetto a cascata delle modifiche. In questo modo si ottiene un accoppiamento più lasco, in cui non ci si deve preoccupare che la modifica di un codice provochi la modifica di un altro codice dipendente. Un esempio tipico è la situazione definita [inferno dei costruttori |passing-dependencies#Constructor hell]. +Perché si preferisce la composizione all'ereditarietà? +------------------------------------------------------ +È preferibile utilizzare la [composizione |nette:introduction-to-object-oriented-programming#Composizione] invece dell'[ereditarietà |nette:introduction-to-object-oriented-programming#Ereditarietà], perché serve a riutilizzare il codice senza doversi preoccupare delle conseguenze delle modifiche. Fornisce quindi un legame più lasco, in cui non dobbiamo preoccuparci che la modifica di un codice causi la necessità di modificare un altro codice dipendente. Un esempio tipico è la situazione nota come [constructor hell |passing-dependencies#Constructor hell]. -Nette DI Container può essere utilizzato al di fuori di Nette? .[#toc-can-nette-di-container-be-used-outside-of-nette] ----------------------------------------------------------------------------------------------------------------------- +È possibile utilizzare Nette DI Container al di fuori di Nette? +--------------------------------------------------------------- -Assolutamente sì. Nette DI Container fa parte di Nette, ma è stato progettato come una libreria autonoma che può essere utilizzata indipendentemente da altre parti del framework. È sufficiente installarla con Composer, creare un file di configurazione che definisca i servizi e poi utilizzare poche righe di codice PHP per creare il contenitore DI. -In questo modo si può iniziare a sfruttare immediatamente la Dependency Injection nei propri progetti. +Assolutamente. Nette DI Container fa parte di Nette, ma è progettato come una libreria autonoma che può essere utilizzata indipendentemente dalle altre parti del framework. Basta installarla tramite Composer, creare un file di configurazione con la definizione dei tuoi servizi e quindi, con poche righe di codice PHP, creare il container DI. E puoi iniziare subito a sfruttare i vantaggi della Dependency Injection nei tuoi progetti. -Il capitolo [Nette DI Container |nette-container] descrive un caso d'uso specifico, compreso il codice. +Come appare l'uso concreto, compresi i codici, è descritto nel capitolo [Nette DI Container |nette-container]. -Perché la configurazione è nei file NEON? .[#toc-why-is-the-configuration-in-neon-files] ----------------------------------------------------------------------------------------- +Perché la configurazione è nei file NEON? +----------------------------------------- -NEON è un linguaggio di configurazione semplice e facilmente leggibile sviluppato all'interno di Nette per la configurazione di applicazioni, servizi e relative dipendenze. Rispetto a JSON o YAML, offre opzioni molto più intuitive e flessibili. In NEON, è possibile descrivere in modo naturale legami che non sarebbe possibile scrivere in Symfony e YAML o solo attraverso una descrizione complessa. +NEON è un linguaggio di configurazione semplice e facilmente leggibile, sviluppato nell'ambito di Nette per impostare applicazioni, servizi e le loro dipendenze. Rispetto a JSON o YAML, offre opzioni molto più intuitive e flessibili per questo scopo. In NEON è possibile descrivere naturalmente legami che in Symfony & YAML non sarebbe possibile scrivere affatto, o solo tramite una descrizione complessa. -L'analisi dei file NEON rallenta l'applicazione? .[#toc-does-parsing-neon-files-slow-down-the-application] ----------------------------------------------------------------------------------------------------------- +Il parsing dei file NEON non rallenta l'applicazione? +----------------------------------------------------- -Sebbene l'analisi dei file NEON sia molto rapida, questo aspetto non ha molta importanza. Il motivo è che l'analisi dei file avviene solo una volta durante il primo avvio dell'applicazione. In seguito, il codice del contenitore DI viene generato, memorizzato sul disco ed eseguito per ogni richiesta successiva, senza bisogno di ulteriori analisi. +Sebbene i file NEON vengano parsati molto rapidamente, questo aspetto non ha alcuna importanza. Il motivo è che il parsing dei file avviene solo una volta al primo avvio dell'applicazione. Successivamente, viene generato il codice del container DI, salvato su disco ed eseguito ad ogni richiesta successiva, senza la necessità di eseguire ulteriori parsing. -Questo è il modo in cui funziona in un ambiente di produzione. Durante lo sviluppo, i file NEON vengono analizzati ogni volta che il loro contenuto cambia, assicurando che lo sviluppatore abbia sempre un contenitore DI aggiornato. Come già detto, il parsing vero e proprio è questione di un istante. +Così funziona in ambiente di produzione. Durante lo sviluppo, i file NEON vengono parsati ogni volta che il loro contenuto viene modificato, in modo che lo sviluppatore abbia sempre un container DI aggiornato. Il parsing stesso è, come detto, questione di un attimo. -Come si accede ai parametri del file di configurazione nella propria classe? .[#toc-how-do-i-access-the-parameters-from-the-configuration-file-in-my-class] ------------------------------------------------------------------------------------------------------------------------------------------------------------ +Come accedo ai parametri nel file di configurazione dalla mia classe? +--------------------------------------------------------------------- -Tenete a mente la [regola n. 1: lasciate che vi venga passato |introduction#Rule #1: Let It Be Passed to You]. Se una classe richiede informazioni da un file di configurazione, non è necessario capire come accedere a tali informazioni, ma è sufficiente richiederle, ad esempio attraverso il costruttore della classe. Ed eseguiamo il passaggio nel file di configurazione. +Teniamo presente la [Regola n. 1: fattelo passare |introduction#Regola n. 1: fatti passare le dipendenze]. Se una classe richiede informazioni dal file di configurazione, non dobbiamo pensare a come ottenere quelle informazioni, ma semplicemente le chiediamo - ad esempio tramite il costruttore della classe. E il passaggio lo effettuiamo nel file di configurazione. -In questo esempio, `%myParameter%` è un segnaposto per il valore del parametro `myParameter`, che sarà passato al costruttore `MyClass`: +In questo esempio, `%myParameter%` è un segnaposto per il valore del parametro `myParameter`, che viene passato al costruttore della classe `MyClass`: ```php # config.neon @@ -103,10 +97,10 @@ services: - MyClass(%myParameter%) ``` -Se si desidera passare più parametri o utilizzare il cablaggio automatico, è utile [avvolgere i parametri in un oggetto |best-practices:passing-settings-to-presenters]. +Se vuoi passare più parametri o utilizzare l'autowiring, è consigliabile [impacchettare i parametri in un oggetto |best-practices:passing-settings-to-presenters]. -Nette supporta l'interfaccia PSR-11 Container? .[#toc-does-nette-support-psr-11-container-interface] ----------------------------------------------------------------------------------------------------- +Nette supporta PSR-11: Container interface? +------------------------------------------- -Nette DI Container non supporta direttamente PSR-11. Tuttavia, se è necessaria l'interoperabilità tra Nette DI Container e librerie o framework che si aspettano l'interfaccia PSR-11 Container, è possibile creare un [semplice adattatore |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f] che funga da ponte tra Nette DI Container e PSR-11. +Nette DI Container non supporta direttamente PSR-11. Tuttavia, se hai bisogno di interoperabilità tra Nette DI Container e librerie o framework che si aspettano PSR-11 Container Interface, puoi creare un [semplice adapter |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f], che fungerà da ponte tra Nette DI Container e PSR-11. diff --git a/dependency-injection/it/global-state.texy b/dependency-injection/it/global-state.texy index 11fc656413..49cda6dd0c 100644 --- a/dependency-injection/it/global-state.texy +++ b/dependency-injection/it/global-state.texy @@ -1,67 +1,63 @@ -Stato globale e singoletti -************************** +Stato globale e singleton +************************* .[perex] -Attenzione: I seguenti costrutti sono sintomi di codice mal progettato: +Avviso: I seguenti costrutti sono un segno di codice mal progettato: - `Foo::getInstance()` - `DB::insert(...)` - `Article::setDb($db)` - `ClassName::$var` o `static::$var` -Avete riscontrato alcuni di questi costrutti nel vostro codice? Se sì, avete l'opportunità di migliorarlo. Potreste pensare che si tratti di costrutti comuni, spesso visti nelle soluzioni di esempio di varie librerie e framework. Se è così, la progettazione del codice è sbagliata. +Alcuni di questi costrutti compaiono nel tuo codice? Allora hai l'opportunità di migliorarlo. Forse pensi che si tratti di costrutti comuni, che vedi anche in soluzioni di esempio di varie librerie e framework. Se è così, allora il design del loro codice non è buono. -Non stiamo parlando di una purezza accademica. Tutti questi costrutti hanno una cosa in comune: utilizzano lo stato globale. E questo ha un impatto distruttivo sulla qualità del codice. Le classi sono ingannevoli riguardo alle loro dipendenze. Il codice diventa imprevedibile. Confonde gli sviluppatori e riduce la loro efficienza. +Ora non stiamo certo parlando di una sorta di purezza accademica. Tutti questi costrutti hanno una cosa in comune: utilizzano lo stato globale. E questo ha un impatto distruttivo sulla qualità del codice. Le classi mentono sulle loro dipendenze. Il codice diventa imprevedibile. Confonde i programmatori e riduce la loro efficienza. In questo capitolo spiegheremo perché è così e come evitare lo stato globale. -Interconnessione globale .[#toc-global-interlinking] ----------------------------------------------------- +Accoppiamento globale +--------------------- -In un mondo ideale, un oggetto dovrebbe comunicare solo con gli oggetti che [gli |passing-dependencies] sono stati [passati direttamente |passing-dependencies]. Se creo due oggetti `A` e `B` e non passo mai un riferimento tra loro, né `A` né `B` possono accedere o modificare lo stato dell'altro. Questa è una proprietà molto desiderabile del codice. È come avere una batteria e una lampadina; la lampadina non si accende finché non la si collega alla batteria con un filo. +In un mondo ideale, un oggetto dovrebbe essere in grado di comunicare solo con oggetti che gli sono stati [passati direttamente |passing-dependencies]. Se creo due oggetti `A` e `B` e non passo mai un riferimento tra di loro, allora né `A` né `B` possono accedere all'altro oggetto o modificarne lo stato. Questa è una proprietà molto desiderabile del codice. È simile a quando hai una batteria e una lampadina; la lampadina non si accenderà finché non la colleghi alla batteria con un filo. -Tuttavia, questo non vale per le variabili globali (statiche) o per i singleton. L'oggetto `A` potrebbe accedere *senza fili* all'oggetto `C` e modificarlo senza alcun passaggio di riferimenti, chiamando `C::changeSomething()`. Se anche l'oggetto `B` accede al globale `C`, allora `A` e `B` possono influenzarsi a vicenda attraverso `C`. +Questo però non vale per le variabili globali (statiche) o i singleton. L'oggetto `A` potrebbe accedere *senza fili* all'oggetto `C` e modificarlo senza alcun passaggio di riferimento, chiamando `C::changeSomething()`. Se anche l'oggetto `B` si appropria del `C` globale, allora `A` e `B` possono influenzarsi a vicenda tramite `C`. -L'uso di variabili globali introduce una nuova forma di accoppiamento *wireless* non visibile all'esterno. Crea una cortina di fumo che complica la comprensione e l'uso del codice. Per capire veramente le dipendenze, gli sviluppatori devono leggere ogni riga del codice sorgente, invece di limitarsi a familiarizzare con le interfacce delle classi. Inoltre, questo intreccio è del tutto inutile. Lo stato globale viene utilizzato perché è facilmente accessibile da qualsiasi punto e consente, per esempio, di scrivere su un database attraverso un metodo globale (statico) `DB::insert()`. Tuttavia, come vedremo, il vantaggio che offre è minimo, mentre le complicazioni che introduce sono gravi. +L'uso di variabili globali introduce nel sistema una nuova forma di accoppiamento *senza fili*, che non è visibile dall'esterno. Crea una cortina fumogena che complica la comprensione e l'uso del codice. Affinché gli sviluppatori comprendano veramente le dipendenze, devono leggere ogni riga del codice sorgente. Invece di familiarizzare semplicemente con le interfacce delle classi. Si tratta inoltre di un accoppiamento del tutto inutile. Lo stato globale viene utilizzato perché è facilmente accessibile da qualsiasi luogo e consente, ad esempio, di scrivere nel database tramite il metodo globale (statico) `DB::insert()`. Ma come mostreremo, il vantaggio che ciò porta è minimo, mentre le complicazioni che causa sono fatali. .[note] -In termini di comportamento, non c'è differenza tra una variabile globale e una statica. Sono ugualmente dannose. +Dal punto di vista del comportamento, non c'è differenza tra una variabile globale e una statica. Sono ugualmente dannose. -L'azione spettrale a distanza .[#toc-the-spooky-action-at-a-distance] ---------------------------------------------------------------------- +Azione spettrale a distanza +--------------------------- -"Azione spettrale a distanza": così Albert Einstein definì nel 1935 un fenomeno della fisica quantistica che gli fece venire i brividi. -Si tratta dell'entanglement quantistico, la cui peculiarità è che quando si misura un'informazione su una particella, si influisce immediatamente su un'altra particella, anche se si trovano a milioni di anni luce di distanza. -Il che sembra violare la legge fondamentale dell'universo secondo cui nulla può viaggiare più veloce della luce. +"Azione spettrale a distanza" - così famosamente chiamò nel 1935 Albert Einstein un fenomeno della fisica quantistica che gli faceva venire la pelle d'oca. +Si tratta dell'entanglement quantistico, la cui particolarità è che quando misuri l'informazione su una particella, influenzi istantaneamente l'altra particella, anche se sono distanti milioni di anni luce. Ciò sembra violare la legge fondamentale dell'universo, secondo cui nulla può propagarsi più velocemente della luce. -Nel mondo del software, possiamo chiamare "azione spettrale a distanza" una situazione in cui eseguiamo un processo che pensiamo sia isolato (perché non gli abbiamo passato alcun riferimento), ma interazioni inaspettate e cambiamenti di stato avvengono in posizioni distanti del sistema di cui non abbiamo parlato all'oggetto. Questo può avvenire solo attraverso lo stato globale. +Nel mondo del software, possiamo chiamare "azione spettrale a distanza" una situazione in cui avviamo un processo che riteniamo isolato (perché non gli abbiamo passato alcun riferimento), ma in luoghi remoti del sistema si verificano interazioni e cambiamenti di stato imprevisti, di cui non avevamo idea. Ciò può accadere solo tramite lo stato globale. -Immaginate di entrare in un team di sviluppo di un progetto che ha una base di codice ampia e matura. Il vostro nuovo capo vi chiede di implementare una nuova funzionalità e, da bravi sviluppatori, iniziate scrivendo un test. Ma poiché siete nuovi nel progetto, fate molti test esplorativi del tipo "cosa succede se chiamo questo metodo". E provate a scrivere il seguente test: +Immagina di unirti a un team di sviluppatori di un progetto che ha una vasta e matura base di codice. Il tuo nuovo capo ti chiede di implementare una nuova funzionalità e tu, da bravo sviluppatore, inizi scrivendo un test. Ma poiché sei nuovo nel progetto, fai molti test esplorativi del tipo "cosa succede se chiamo questo metodo". E provi a scrivere il seguente test: ```php function testCreditCardCharge() { - $cc = new CreditCard('1234567890123456', 5, 2028); // il numero della carta + $cc = new CreditCard('1234567890123456', 5, 2028); // il numero della tua carta $cc->charge(100); } ``` -Eseguite il codice, magari più volte, e dopo un po' notate sul vostro telefono le notifiche della banca che ogni volta che lo eseguite, 100 dollari sono stati addebitati sulla vostra carta di credito 🤦‍♂️ +Esegui il codice, magari più volte, e dopo un po' noti notifiche dalla banca sul cellulare, che ad ogni esecuzione sono stati addebitati 100 dollari dalla tua carta di pagamento 🤦‍♂️ -Come può il test causare un addebito effettivo? Non è facile operare con la carta di credito. Bisogna interagire con un servizio web di terze parti, conoscere l'URL di tale servizio web, effettuare il login e così via. -Nessuna di queste informazioni è inclusa nel test. Ancora peggio, non si sa nemmeno dove siano presenti queste informazioni e quindi come prendere in giro le dipendenze esterne in modo che ogni esecuzione non comporti un nuovo addebito di 100 dollari. E come nuovo sviluppatore, come potevate sapere che quello che stavate per fare vi avrebbe fatto perdere 100 dollari? +Come diavolo ha fatto il test a causare un addebito reale di denaro? Operare con una carta di pagamento non è facile. Devi comunicare con un servizio web di terze parti, devi conoscere l'URL di questo servizio web, devi autenticarti e così via. Nessuna di queste informazioni è contenuta nel test. Peggio ancora, non sai nemmeno dove queste informazioni siano presenti, e quindi nemmeno come mockare le dipendenze esterne, in modo che ogni esecuzione non porti a un nuovo addebito di 100 dollari. E come avresti dovuto sapere, come nuovo sviluppatore, che quello che stavi per fare ti avrebbe reso più povero di 100 dollari? -È un'azione spettrale a distanza! +Questa è l'azione spettrale a distanza! -Non avete altra scelta se non quella di scavare nel codice sorgente, chiedendo ai colleghi più anziani e più esperti, fino a capire come funzionano le connessioni nel progetto. -Ciò è dovuto al fatto che, guardando l'interfaccia della classe `CreditCard`, non è possibile determinare lo stato globale che deve essere inizializzato. Anche guardando il codice sorgente della classe non si può sapere quale metodo di inizializzazione chiamare. Al massimo, si può trovare la variabile globale a cui si accede e cercare di indovinare come inizializzarla a partire da essa. +Non ti resta che scavare a lungo in un sacco di codice sorgente, chiedere ai colleghi più anziani ed esperti, prima di capire come funzionano i legami nel progetto. Ciò è dovuto al fatto che guardando l'interfaccia della classe `CreditCard` non è possibile determinare lo stato globale che deve essere inizializzato. Nemmeno uno sguardo al codice sorgente della classe ti dirà quale metodo di inizializzazione devi chiamare. Nel migliore dei casi, puoi trovare una variabile globale a cui si accede e da essa cercare di indovinare come inizializzarla. -Le classi in un progetto di questo tipo sono bugiarde patologiche. La carta di pagamento finge di poter essere semplicemente istanziata e di poter chiamare il metodo `charge()`. Tuttavia, interagisce segretamente con un'altra classe, `PaymentGateway`. Anche la sua interfaccia dice che può essere inizializzata in modo indipendente, ma in realtà preleva le credenziali da qualche file di configurazione e così via. -Per gli sviluppatori che hanno scritto questo codice è chiaro che `CreditCard` ha bisogno di `PaymentGateway`. Hanno scritto il codice in questo modo. Ma per chiunque sia nuovo al progetto, questo è un completo mistero e ostacola l'apprendimento. +Le classi in un tale progetto sono bugiarde patologiche. La carta di pagamento finge che basti istanziarla e chiamare il metodo `charge()`. Di nascosto, però, collabora con un'altra classe `PaymentGateway`, che rappresenta il gateway di pagamento. Anche la sua interfaccia dice che può essere inizializzata separatamente, ma in realtà estrae le credenziali da qualche file di configurazione e così via. Agli sviluppatori che hanno scritto questo codice è chiaro che `CreditCard` ha bisogno di `PaymentGateway`. Hanno scritto il codice in questo modo. Ma per chiunque sia nuovo nel progetto, è un mistero assoluto e ostacola l'apprendimento. -Come risolvere la situazione? Semplice. **Lasciare che l'API dichiari le dipendenze.** +Come risolvere la situazione? Facilmente. **Lascia che l'API dichiari le dipendenze.** ```php function testCreditCardCharge() @@ -72,36 +68,35 @@ function testCreditCardCharge() } ``` -Si noti come le relazioni all'interno del codice siano improvvisamente evidenti. Dichiarando che il metodo `charge()` ha bisogno di `PaymentGateway`, non è necessario chiedere a nessuno come il codice sia interdipendente. Si sa che bisogna crearne un'istanza e, quando si cerca di farlo, ci si imbatte nel fatto che bisogna fornire dei parametri di accesso. Senza di essi, il codice non potrebbe nemmeno funzionare. +Nota come improvvisamente le interconnessioni all'interno del codice diventano evidenti. Poiché il metodo `charge()` dichiara di aver bisogno di `PaymentGateway`, non devi chiedere a nessuno come è interconnesso il codice. Sai che devi crearne un'istanza e, quando ci provi, ti imbatti nel fatto che devi fornire i parametri di accesso. Senza di essi, il codice non potrebbe nemmeno essere eseguito. -E soprattutto, ora potete prendere in giro il gateway di pagamento, così non vi verranno addebitati 100 dollari ogni volta che eseguite un test. +E soprattutto, ora puoi mockare il gateway di pagamento, così non ti verranno addebitati 100 dollari ogni volta che esegui il test. -Lo stato globale fa sì che i vostri oggetti possano accedere segretamente a cose che non sono dichiarate nelle loro API e, di conseguenza, rende le vostre API dei bugiardi patologici. +Lo stato globale fa sì che i tuoi oggetti possano accedere segretamente a cose che non sono dichiarate nella loro API e, di conseguenza, rende le tue API bugiarde patologiche. -Forse non ci avete mai pensato prima, ma ogni volta che usate lo stato globale, state creando canali di comunicazione wireless segreti. Le inquietanti azioni remote costringono gli sviluppatori a leggere ogni riga di codice per capire le potenziali interazioni, riducono la produttività degli sviluppatori e confondono i nuovi membri del team. -Se siete voi a creare il codice, conoscete le vere dipendenze, ma chi viene dopo di voi non sa nulla. +Forse non ci avevi pensato in questo modo prima, ma ogni volta che usi lo stato globale, stai creando canali di comunicazione segreti senza fili. L'azione spettrale a distanza costringe gli sviluppatori a leggere ogni riga di codice per comprendere le potenziali interazioni, riduce la produttività degli sviluppatori e confonde i nuovi membri del team. Se sei tu quello che ha creato il codice, conosci le vere dipendenze, ma chiunque venga dopo di te è perso. -Non scrivete codice che utilizza lo stato globale, ma preferite passare le dipendenze. Ovvero, la dependency injection. +Non scrivere codice che utilizza lo stato globale, dai la preferenza al passaggio delle dipendenze. Cioè, dependency injection. -La fragilità dello Stato globale .[#toc-brittleness-of-the-global-state] ------------------------------------------------------------------------- +Fragilità dello stato globale +----------------------------- -Nel codice che utilizza lo stato globale e i singleton, non è mai certo quando e da chi lo stato è stato modificato. Questo rischio è già presente al momento dell'inizializzazione. Il codice seguente dovrebbe creare una connessione al database e inizializzare il gateway di pagamento, ma continua a lanciare un'eccezione e trovare la causa è estremamente noioso: +Nel codice che utilizza lo stato globale e i singleton, non è mai certo quando e chi ha modificato questo stato. Questo rischio si presenta già durante l'inizializzazione. Il seguente codice dovrebbe creare una connessione al database e inizializzare il gateway di pagamento, ma lancia costantemente un'eccezione e trovare la causa è estremamente lungo: ```php PaymentGateway::init(); DB::init('mysql:', 'user', 'password'); ``` -È necessario esaminare il codice in dettaglio per scoprire che l'oggetto `PaymentGateway` accede ad altri oggetti in modalità wireless, alcuni dei quali richiedono una connessione al database. Pertanto, è necessario inizializzare il database prima di `PaymentGateway`. Tuttavia, la cortina di fumo dello stato globale nasconde questo aspetto. Quanto tempo si risparmierebbe se l'API di ogni classe non mentisse e non dichiarasse le sue dipendenze? +Devi esaminare attentamente il codice per scoprire che l'oggetto `PaymentGateway` accede senza fili ad altri oggetti, alcuni dei quali richiedono una connessione al database. Quindi è necessario inizializzare il database prima di `PaymentGateway`. Tuttavia, la cortina fumogena dello stato globale ti nasconde questo. Quanto tempo avresti risparmiato se le API delle singole classi non avessero mentito e avessero dichiarato le loro dipendenze? ```php $db = new DB('mysql:', 'user', 'password'); $gateway = new PaymentGateway($db, ...); ``` -Un problema simile si presenta quando si utilizza l'accesso globale a una connessione di database: +Un problema simile si presenta anche quando si utilizza l'accesso globale alla connessione del database: ```php use Illuminate\Support\Facades\DB; @@ -115,7 +110,7 @@ class Article } ``` -Quando si chiama il metodo `save()`, non si sa se è già stata creata una connessione al database e chi è responsabile della sua creazione. Ad esempio, se si volesse cambiare al volo la connessione al database, magari a scopo di test, probabilmente si dovrebbero creare metodi aggiuntivi come `DB::reconnect(...)` o `DB::reconnectForTest()`. +Quando si chiama il metodo `save()`, non è certo se sia già stata creata una connessione al database e chi sia responsabile della sua creazione. Se volessimo, ad esempio, cambiare la connessione al database durante l'esecuzione, magari per i test, dovremmo probabilmente creare altri metodi come `DB::reconnect(...)` o `DB::reconnectForTest()`. Consideriamo un esempio: @@ -127,7 +122,7 @@ Foo::doSomething(); $article->save(); ``` -Come possiamo essere sicuri che il database di prova sia davvero utilizzato quando chiamiamo `$article->save()`? E se il metodo `Foo::doSomething()` cambiasse la connessione globale al database? Per scoprirlo, dovremmo esaminare il codice sorgente della classe `Foo` e probabilmente di molte altre classi. Tuttavia, questo approccio fornirebbe solo una risposta a breve termine, poiché la situazione potrebbe cambiare in futuro. +Dove abbiamo la certezza che quando si chiama `$article->save()` si stia effettivamente utilizzando il database di test? E se il metodo `Foo::doSomething()` avesse cambiato la connessione globale al database? Per scoprirlo, dovremmo esaminare il codice sorgente della classe `Foo` e probabilmente anche di molte altre classi. Questo approccio, tuttavia, fornirebbe solo una risposta a breve termine, poiché la situazione potrebbe cambiare in futuro. E se spostassimo la connessione al database in una variabile statica all'interno della classe `Article`? @@ -148,11 +143,11 @@ class Article } ``` -Questo non cambia assolutamente nulla. Il problema è uno stato globale e non importa in quale classe si nasconda. In questo caso, come in quello precedente, non abbiamo alcun indizio su quale database viene scritto quando viene chiamato il metodo `$article->save()`. Chiunque, all'estremità distante dell'applicazione, potrebbe cambiare il database in qualsiasi momento usando `Article::setDb()`. Sotto le nostre mani. +Questo non cambia assolutamente nulla. Il problema è lo stato globale ed è del tutto indifferente in quale classe si nasconda. In questo caso, come nel precedente, non abbiamo alcun indizio, quando chiamiamo il metodo `$article->save()`, su quale database verrà scritto. Chiunque all'altro capo dell'applicazione avrebbe potuto cambiare il database in qualsiasi momento usando `Article::setDb()`. Sotto il nostro naso. Lo stato globale rende la nostra applicazione **estremamente fragile**. -Tuttavia, esiste un modo semplice per affrontare questo problema. Basta che l'API dichiari le dipendenze per garantire la corretta funzionalità. +Esiste tuttavia un modo semplice per affrontare questo problema. Basta lasciare che l'API dichiari le dipendenze, garantendo così la corretta funzionalità. ```php class Article @@ -174,15 +169,15 @@ Foo::doSomething(); $article->save(); ``` -Questo approccio elimina la preoccupazione di modifiche nascoste e inaspettate alle connessioni al database. Ora siamo sicuri di dove è memorizzato l'articolo e nessuna modifica del codice all'interno di un'altra classe non correlata può più cambiare la situazione. Il codice non è più fragile, ma stabile. +Grazie a questo approccio, scompare la preoccupazione per modifiche nascoste e impreviste della connessione al database. Ora abbiamo la certezza di dove viene salvato l'articolo e nessuna modifica del codice all'interno di un'altra classe non correlata può più cambiare la situazione. Il codice non è più fragile, ma stabile. -Non scrivete codice che utilizza lo stato globale, ma preferite passare le dipendenze. Quindi, l'iniezione di dipendenza. +Non scrivere codice che utilizza lo stato globale, dai la preferenza al passaggio delle dipendenze. Cioè, dependency injection. -Singleton .[#toc-singleton] ---------------------------- +Singleton +--------- -Singleton è un pattern di progettazione che, secondo [la definizione |https://en.wikipedia.org/wiki/Singleton_pattern] della famosa pubblicazione Gang of Four, limita una classe a una singola istanza e offre un accesso globale a essa. L'implementazione di questo pattern di solito assomiglia al codice seguente: +Il Singleton è un design pattern che, secondo la "definizione":https://en.wikipedia.org/wiki/Singleton_pattern della nota pubblicazione Gang of Four, limita una classe a una singola istanza e offre un accesso globale ad essa. L'implementazione di questo pattern di solito assomiglia al seguente codice: ```php class Singleton @@ -195,38 +190,35 @@ class Singleton return self::$instance; } - // e altri metodi che svolgono le funzioni della classe + // e altri metodi che svolgono le funzioni della classe data } ``` -Purtroppo, il singleton introduce lo stato globale nell'applicazione. E come abbiamo mostrato in precedenza, lo stato globale è indesiderabile. Ecco perché il singleton è considerato un antipattern. +Purtroppo, il singleton introduce uno stato globale nell'applicazione. E come abbiamo mostrato sopra, lo stato globale è indesiderabile. Pertanto, il singleton è considerato un antipattern. -Non utilizzate i singleton nel vostro codice e sostituiteli con altri meccanismi. I singleton non sono necessari. Tuttavia, se è necessario garantire l'esistenza di una singola istanza di una classe per l'intera applicazione, bisogna lasciarla al [contenitore DI |container]. -Quindi, creare un singleton dell'applicazione, o un servizio. Questo impedirà alla classe di fornire la propria unicità (cioè, non avrà un metodo `getInstance()` e una variabile statica) ed eseguirà solo le sue funzioni. In questo modo, non violerà più il principio della responsabilità unica. +Non utilizzare singleton nel tuo codice e sostituiscili con altri meccanismi. I singleton non ti servono davvero. Tuttavia, se hai bisogno di garantire l'esistenza di una singola istanza di una classe per l'intera applicazione, lascialo fare al [container DI |container]. Crea così un singleton applicativo, ovvero un servizio. In questo modo, la classe smette di occuparsi di garantire la propria unicità (cioè non avrà il metodo `getInstance()` e una variabile statica) e svolgerà solo le sue funzioni. Così smetterà di violare il principio di singola responsabilità. -Stato globale e test .[#toc-global-state-versus-tests] ------------------------------------------------------- +Stato globale versus test +------------------------- -Quando si scrivono i test, si assume che ogni test sia un'unità isolata e che nessuno stato esterno vi entri. E nessuno stato lascia i test. Quando un test viene completato, qualsiasi stato associato al test dovrebbe essere rimosso automaticamente dal garbage collector. Questo rende i test isolati. Pertanto, possiamo eseguire i test in qualsiasi ordine. +Quando scriviamo test, presumiamo che ogni test sia un'unità isolata e che nessuno stato esterno vi entri. E nessuno stato lascia i test. Al termine del test, tutto lo stato correlato al test dovrebbe essere rimosso automaticamente dal garbage collector. Grazie a ciò, i test sono isolati. Pertanto, possiamo eseguire i test in qualsiasi ordine. -Tuttavia, se sono presenti stati/singleton globali, tutte queste belle assunzioni vengono meno. Uno stato può entrare e uscire da un test. Improvvisamente, l'ordine dei test può essere importante. +Tuttavia, se sono presenti stati globali/singleton, tutte queste piacevoli supposizioni crollano. Lo stato può entrare e uscire dal test. Improvvisamente, l'ordine dei test può avere importanza. -Per testare i singleton, gli sviluppatori devono spesso rilassare le loro proprietà, magari permettendo a un'istanza di essere sostituita da un'altra. Queste soluzioni sono, nella migliore delle ipotesi, dei trucchi che producono codice difficile da mantenere e da capire. Qualsiasi test o metodo `tearDown()` che influisca su uno stato globale deve annullare tali modifiche. +Per poter testare affatto i singleton, gli sviluppatori spesso devono allentare le loro proprietà, ad esempio permettendo di sostituire l'istanza con un'altra. Tali soluzioni sono nel migliore dei casi un hack, che crea codice difficile da mantenere e comprendere. Ogni test o metodo `tearDown()`, che influenzi qualsiasi stato globale, deve annullare queste modifiche. -Lo stato globale è il più grande grattacapo dei test unitari! +Lo stato globale è il più grande mal di testa nel unit testing! -Come risolvere la situazione? Semplice. Non scrivete codice che utilizza singleton, ma preferite passare le dipendenze. Ovvero, la dependency injection. +Come risolvere la situazione? Facilmente. Non scrivere codice che utilizza singleton, dai la preferenza al passaggio delle dipendenze. Cioè, dependency injection. -Costanti globali .[#toc-global-constants] ------------------------------------------ +Costanti globali +---------------- -Lo stato globale non è limitato all'uso di singleton e variabili statiche, ma può essere applicato anche alle costanti globali. +Lo stato globale non si limita solo all'uso di singleton e variabili statiche, ma può riguardare anche costanti globali. -Le costanti il cui valore non ci fornisce alcuna informazione nuova (`M_PI`) o utile (`PREG_BACKTRACK_LIMIT_ERROR`) sono chiaramente OK. -Al contrario, le costanti che servono a passare *senza fili* informazioni all'interno del codice non sono altro che una dipendenza nascosta. Come `LOG_FILE` nell'esempio seguente. -L'uso della costante `FILE_APPEND` è perfettamente corretto. +Le costanti il cui valore non ci porta alcuna nuova (`M_PI`) o utile (`PREG_BACKTRACK_LIMIT_ERROR`) informazione, sono chiaramente a posto. Al contrario, le costanti che servono come modo per passare informazioni *senza fili* all'interno del codice, non sono altro che una dipendenza nascosta. Come ad esempio `LOG_FILE` nell'esempio seguente. L'uso della costante `FILE_APPEND` è del tutto corretto. ```php const LOG_FILE = '...'; @@ -242,7 +234,7 @@ class Foo } ``` -In questo caso, dovremmo dichiarare il parametro nel costruttore della classe `Foo` per renderlo parte dell'API: +In questo caso, dovremmo dichiarare un parametro nel costruttore della classe `Foo`, affinché diventi parte dell'API: ```php class Foo @@ -261,44 +253,42 @@ class Foo } ``` -Ora possiamo passare le informazioni sul percorso del file di registrazione e modificarle facilmente in base alle necessità, rendendo più facili i test e la manutenzione del codice. +Ora possiamo passare l'informazione sul percorso del file per il logging e modificarla facilmente secondo necessità, il che facilita il testing e la manutenzione del codice. -Funzioni globali e metodi statici .[#toc-global-functions-and-static-methods] ------------------------------------------------------------------------------ +Funzioni globali e metodi statici +--------------------------------- -Vogliamo sottolineare che l'uso di metodi statici e funzioni globali non è di per sé problematico. Abbiamo spiegato l'inadeguatezza dell'uso di `DB::insert()` e di metodi simili, ma si è sempre trattato di uno stato globale memorizzato in una variabile statica. Il metodo `DB::insert()` richiede l'esistenza di una variabile statica perché memorizza la connessione al database. Senza questa variabile, sarebbe impossibile implementare il metodo. +Vogliamo sottolineare che l'uso stesso di metodi statici e funzioni globali non è problematico. Abbiamo spiegato perché l'uso di `DB::insert()` e metodi simili è inappropriato, ma si è sempre trattato solo di una questione di stato globale, che è memorizzato in qualche variabile statica. Il metodo `DB::insert()` richiede l'esistenza di una variabile statica, perché in essa è memorizzata la connessione al database. Senza questa variabile, sarebbe impossibile implementare il metodo. -L'uso di metodi e funzioni statiche deterministiche, come `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` e molti altri, è perfettamente coerente con la dependency injection. Queste funzioni restituiscono sempre gli stessi risultati dagli stessi parametri di ingresso e sono quindi prevedibili. Non utilizzano alcuno stato globale. +L'uso di metodi statici e funzioni deterministiche, come ad esempio `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` e molte altre, è in perfetta armonia con la dependency injection. Queste funzioni restituiscono sempre gli stessi risultati dagli stessi parametri di input e sono quindi prevedibili. Non utilizzano alcuno stato globale. -Tuttavia, esistono funzioni in PHP che non sono deterministiche. Tra queste c'è, per esempio, la funzione `htmlspecialchars()`. Il suo terzo parametro, `$encoding`, se non specificato, assume per default il valore dell'opzione di configurazione `ini_get('default_charset')`. Pertanto, si raccomanda di specificare sempre questo parametro per evitare un comportamento imprevedibile della funzione. Nette fa sempre così. +Esistono tuttavia anche funzioni in PHP che non sono deterministiche. Tra queste c'è ad esempio la funzione `htmlspecialchars()`. Il suo terzo parametro `$encoding`, se non specificato, ha come valore predefinito il valore dell'opzione di configurazione `ini_get('default_charset')`. Pertanto, si consiglia di specificare sempre questo parametro per evitare un eventuale comportamento imprevedibile della funzione. Nette lo fa costantemente. -Alcune funzioni, come `strtolower()`, `strtoupper()`, e simili, nel recente passato hanno avuto un comportamento non deterministico e sono dipese dall'impostazione `setlocale()`. Questo ha causato molte complicazioni, soprattutto quando si lavorava con la lingua turca. -Questo perché la lingua turca distingue tra maiuscole e minuscole `I` con e senza punto. Quindi `strtolower('I')` restituiva il carattere `ı` e `strtoupper('i')` restituiva il carattere `İ`, il che portava le applicazioni a causare una serie di errori misteriosi. -Tuttavia, questo problema è stato risolto nella versione 8.2 di PHP e le funzioni non dipendono più dal locale. +Alcune funzioni, come ad esempio `strtolower()`, `strtoupper()` e simili, in un passato recente si comportavano in modo non deterministico ed erano dipendenti dall'impostazione `setlocale()`. Ciò causava molte complicazioni, più spesso quando si lavorava con la lingua turca. Questa, infatti, distingue sia la lettera minuscola che maiuscola `I` con e senza punto. Quindi `strtolower('I')` restituiva il carattere `ı` e `strtoupper('i')` il carattere `İ`, il che portava le applicazioni a causare una serie di errori misteriosi. Questo problema è stato tuttavia risolto nella versione PHP 8.2 e le funzioni non dipendono più dalla locale. -Questo è un bell'esempio di come lo stato globale abbia afflitto migliaia di sviluppatori in tutto il mondo. La soluzione è stata quella di sostituirlo con la dependency injection. +È un bell'esempio di come lo stato globale abbia tormentato migliaia di sviluppatori in tutto il mondo. La soluzione è stata sostituirlo con la dependency injection. -Quando è possibile utilizzare lo Stato globale? .[#toc-when-is-it-possible-to-use-global-state] ------------------------------------------------------------------------------------------------ +Quando è possibile utilizzare lo stato globale? +----------------------------------------------- -Ci sono alcune situazioni specifiche in cui è possibile utilizzare lo stato globale. Ad esempio, quando si esegue il debug del codice ed è necessario scaricare il valore di una variabile o misurare la durata di una parte specifica del programma. In questi casi, che riguardano azioni temporanee che saranno successivamente rimosse dal codice, è legittimo utilizzare un dumper o un cronometro disponibile a livello globale. Questi strumenti non fanno parte della progettazione del codice. +Esistono alcune situazioni specifiche in cui è possibile utilizzare lo stato globale. Ad esempio, durante il debugging del codice, quando è necessario stampare il valore di una variabile o misurare la durata di una certa parte del programma. In tali casi, che riguardano azioni temporanee che verranno successivamente rimosse dal codice, è possibile utilizzare legittimamente un dumper o un cronometro globalmente accessibili. Questi strumenti, infatti, non fanno parte del design del codice. -Un altro esempio sono le funzioni per lavorare con le espressioni regolari `preg_*`, che memorizzano internamente le espressioni regolari compilate in una cache statica in memoria. Quando si richiama la stessa espressione regolare più volte in diverse parti del codice, essa viene compilata una sola volta. La cache consente di risparmiare prestazioni ed è completamente invisibile all'utente, per cui tale utilizzo può essere considerato legittimo. +Un altro esempio sono le funzioni per lavorare con le espressioni regolari `preg_*`, che internamente memorizzano le espressioni regolari compilate in una cache statica in memoria. Quindi, quando chiami la stessa espressione regolare più volte in punti diversi del codice, viene compilata solo una volta. La cache risparmia prestazioni ed è allo stesso tempo completamente invisibile per l'utente, quindi tale utilizzo può essere considerato legittimo. -Sintesi .[#toc-summary] ------------------------ +Riepilogo +--------- -Abbiamo mostrato perché ha senso +Abbiamo discusso perché ha senso: 1) Rimuovere tutte le variabili statiche dal codice 2) Dichiarare le dipendenze -3) E utilizzare l'iniezione delle dipendenze +3) E usare la dependency injection -Quando si progetta il codice, bisogna tenere presente che ogni `static $foo` rappresenta un problema. Affinché il codice sia un ambiente che rispetta la DI, è essenziale sradicare completamente lo stato globale e sostituirlo con la dependency injection. +Quando pensi al design del codice, tieni presente che ogni `static $foo` rappresenta un problema. Affinché il tuo codice sia un ambiente che rispetta DI, è indispensabile sradicare completamente lo stato globale e sostituirlo con la dependency injection. -Durante questo processo, potreste scoprire che è necessario dividere una classe perché ha più di una responsabilità. Non preoccupatevi di questo; cercate di mantenere il principio di una sola responsabilità. +Durante questo processo, potresti scoprire che è necessario dividere la classe, perché ha più di una responsabilità. Non averne paura; cerca di rispettare il principio di singola responsabilità. -*Vorrei ringraziare Miško Hevery, i cui articoli, come [Flaw: Brittle Global State & Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/], costituiscono la base di questo capitolo.* +*Vorrei ringraziare Miško Hevery, i cui articoli, come [Flaw: Brittle Global State & Singletons |https://web.archive.org/web/20230321084133/http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/], sono alla base di questo capitolo.* diff --git a/dependency-injection/it/introduction.texy b/dependency-injection/it/introduction.texy index e526eef1e8..b66a1e8e13 100644 --- a/dependency-injection/it/introduction.texy +++ b/dependency-injection/it/introduction.texy @@ -1,58 +1,58 @@ -Cos'è l'iniezione di dipendenza? -******************************** +Cos'è la Dependency Injection? +****************************** .[perex] -Questo capitolo introduce le pratiche di programmazione di base da seguire nella scrittura di qualsiasi applicazione. Si tratta dei fondamenti necessari per scrivere codice pulito, comprensibile e manutenibile. +Questo capitolo vi introdurrà alle pratiche di programmazione di base che dovreste seguire quando scrivete qualsiasi applicazione. Si tratta delle basi necessarie per scrivere codice pulito, comprensibile e manutenibile. -Se imparate e seguite queste regole, Nette vi assisterà in ogni momento. Gestirà per voi le attività di routine e vi offrirà il massimo comfort, in modo che possiate concentrarvi sulla logica. +Se adotterete queste regole e le seguirete, Nette vi supporterà in ogni passo. Si occuperà dei compiti di routine per voi e vi fornirà la massima comodità, così potrete concentrarvi sulla logica stessa. -I principi che illustreremo qui sono piuttosto semplici. Non dovete preoccuparvi di nulla. +I principi che vi mostreremo qui sono piuttosto semplici. Non c'è nulla di cui preoccuparsi. -Ricordate il vostro primo programma? .[#toc-remember-your-first-program] ------------------------------------------------------------------------- +Ricordi il tuo primo programma? +------------------------------- -Non sappiamo in quale linguaggio sia stato scritto, ma se si tratta di PHP, potrebbe avere un aspetto simile a questo: +Non sappiamo in quale linguaggio lo hai scritto, ma se fosse stato PHP, probabilmente sarebbe stato simile a questo: ```php -function addition(float $a, float $b): float +function soucet(float $a, float $b): float { return $a + $b; } -echo addition(23, 1); // francobollo 24 +echo soucet(23, 1); // stampa 24 ``` -Poche righe di codice banali, ma con tanti concetti chiave nascosti. Che ci sono variabili. Che il codice è suddiviso in unità più piccole, come ad esempio le funzioni. Che si passano loro degli argomenti in ingresso e che restituiscono dei risultati. Mancano solo le condizioni e i cicli. +Poche righe di codice banali, ma contengono così tanti concetti chiave. Che esistono le variabili. Che il codice è diviso in unità più piccole, come le funzioni. Che passiamo loro argomenti di input e restituiscono risultati. Mancano solo le condizioni e i cicli. -Il fatto che una funzione prenda dei dati in ingresso e restituisca un risultato è un concetto perfettamente comprensibile, utilizzato anche in altri campi, come la matematica. +Il fatto che passiamo dati di input a una funzione e questa restituisca un risultato è un concetto perfettamente comprensibile, utilizzato anche in altri campi, come la matematica. -Una funzione ha una firma, che consiste nel suo nome, in un elenco di parametri e dei loro tipi e, infine, nel tipo del valore di ritorno. Come utenti, siamo interessati alla firma e di solito non abbiamo bisogno di sapere nulla dell'implementazione interna. +Una funzione ha la sua firma, che consiste nel suo nome, un elenco di parametri e i loro tipi, e infine il tipo di valore di ritorno. Come utenti, ci interessa la firma; di solito non abbiamo bisogno di sapere nulla dell'implementazione interna. -Immaginiamo ora che la firma della funzione abbia questo aspetto: +Ora immagina che la firma della funzione fosse così: ```php -function addition(float $x): float +function soucet(float $x): float ``` -Un'aggiunta con un solo parametro? È strano... Che ne dite di questo? +Una somma con un solo parametro? Strano... E che ne dici di questo? ```php -function addition(): float +function soucet(): float ``` -È davvero strano, vero? Come viene utilizzata la funzione? +Questo è davvero molto strano, vero? Come si usa la funzione? ```php -echo addition(); // cosa stampa? +echo soucet(); // cosa stamperà? ``` -Guardando questo codice, saremmo confusi. Non solo un principiante non lo capirebbe, ma anche un programmatore esperto non lo capirebbe. +Guardando un codice del genere, saremmo confusi. Non solo un principiante non lo capirebbe, ma nemmeno un programmatore esperto capirebbe un codice del genere. -Vi state chiedendo che aspetto avrebbe una funzione del genere? Dove troverebbe i sommatori? Probabilmente li otterrebbe *in qualche modo* da sola, forse in questo modo: +Ti stai chiedendo come sarebbe effettivamente una funzione del genere all'interno? Dove prenderebbe gli addendi? Probabilmente se li procurerebbe *in qualche modo* da sola, forse così: ```php -function addition(): float +function soucet(): float { $a = Input::get('a'); $b = Input::get('b'); @@ -60,71 +60,71 @@ function addition(): float } ``` -Si scopre che nel corpo della funzione ci sono legami nascosti con altre funzioni (o metodi statici) e per scoprire da dove provengono effettivamente gli addendi, dobbiamo scavare ulteriormente. +Nel corpo della funzione, abbiamo scoperto dipendenze nascoste verso altre funzioni globali o metodi statici. Per scoprire da dove provengono effettivamente gli addendi, dobbiamo indagare ulteriormente. -Non in questo modo! .[#toc-not-this-way] ----------------------------------------- +Non da questa parte! +-------------------- -Il design appena mostrato è l'essenza di molte caratteristiche negative: +Il design che abbiamo appena mostrato è l'essenza di molte caratteristiche negative: -- la firma della funzione fingeva di non aver bisogno dei sommatori, il che ci confondeva -- non abbiamo idea di come far calcolare la funzione con altri due numeri -- abbiamo dovuto esaminare il codice per scoprire da dove provenissero i sommatori -- abbiamo trovato dipendenze nascoste -- una comprensione completa richiede l'esame anche di queste dipendenze +- la firma della funzione fingeva di non aver bisogno di addendi, il che ci confondeva +- non sappiamo affatto come far sommare alla funzione altri due numeri +- abbiamo dovuto guardare nel codice per scoprire dove prendeva gli addendi +- abbiamo scoperto dipendenze nascoste +- per una comprensione completa, è necessario esaminare anche queste dipendenze -E il compito della funzione di addizione è anche quello di procurarsi gli input? Ovviamente no. La sua responsabilità è solo quella di aggiungere. +Ed è compito della funzione di somma procurarsi gli input? Ovviamente no. La sua responsabilità è solo la somma stessa. -Non vogliamo incontrare codice di questo tipo e certamente non vogliamo scriverlo. Il rimedio è semplice: tornare alle origini e usare solo i parametri: +Non vogliamo incontrare codice del genere, e certamente non vogliamo scriverlo. La correzione è semplice: tornare alle basi e usare semplicemente i parametri: ```php -function addition(float $a, float $b): float +function soucet(float $a, float $b): float { return $a + $b; } ``` -Regola n. 1: Lascia che ti venga passato .[#toc-rule-1-let-it-be-passed-to-you] -------------------------------------------------------------------------------- +Regola n. 1: fatti passare le dipendenze +---------------------------------------- -La regola più importante è: **tutti i dati di cui hanno bisogno le funzioni o le classi devono essere passati a loro**. +La regola più importante è: **tutti i dati di cui le funzioni o le classi hanno bisogno devono essere passati loro**. -Invece di inventare modi nascosti per accedere ai dati, basta passare i parametri. Si risparmierà tempo che sarebbe stato speso per inventare percorsi nascosti che certamente non miglioreranno il codice. +Invece di inventare modi nascosti attraverso i quali potrebbero ottenerli da soli, passa semplicemente i parametri. Risparmierai tempo necessario per inventare percorsi nascosti, che sicuramente non miglioreranno il tuo codice. -Se si segue sempre e ovunque questa regola, si è sulla strada per un codice senza dipendenze nascoste. Un codice comprensibile non solo per l'autore, ma anche per chiunque lo legga in seguito. Dove tutto è comprensibile dalle firme delle funzioni e delle classi e non c'è bisogno di cercare segreti nascosti nell'implementazione. +Se seguirai sempre e ovunque questa regola, sarai sulla strada per un codice senza dipendenze nascoste. Verso un codice comprensibile non solo per l'autore, ma anche per chiunque lo leggerà dopo di lui. Dove tutto è comprensibile dalle firme delle funzioni e delle classi e non c'è bisogno di cercare segreti nascosti nell'implementazione. -Questa tecnica è chiamata professionalmente **dependency injection**. E questi dati sono chiamati **dipendenze**. È solo un normale passaggio di parametri, niente di più. +Questa tecnica è tecnicamente chiamata **dependency injection**. E questi dati sono chiamati **dipendenze.** In realtà, si tratta semplicemente di passare parametri, niente di più. .[note] -Non bisogna confondere l'iniezione di dipendenze, che è un modello di progettazione, con un "contenitore di iniezione di dipendenze", che è uno strumento, qualcosa di diametralmente diverso. Ci occuperemo dei contenitori più avanti. +Per favore, non confondete la dependency injection, che è un design pattern, con il "dependency injection container", che è invece uno strumento, cioè qualcosa di diametralmente diverso. Ci occuperemo dei container più avanti. -Dalle funzioni alle classi .[#toc-from-functions-to-classes] ------------------------------------------------------------- +Dalle funzioni alle classi +-------------------------- -E come sono collegate le classi? Una classe è un'unità più complessa di una semplice funzione, ma la regola n. 1 si applica interamente anche in questo caso. Ci sono solo [più modi per passare gli argomenti |passing-dependencies]. Per esempio, in modo del tutto simile al caso di una funzione: +E come si relaziona questo con le classi? Una classe è un'unità più complessa di una semplice funzione, ma la regola n. 1 si applica pienamente anche qui. Ci sono solo [più opzioni per passare gli argomenti |passing-dependencies]. Ad esempio, in modo abbastanza simile al caso di una funzione: ```php -class Math +class Matematika { - public function addition(float $a, float $b): float + public function soucet(float $a, float $b): float { return $a + $b; } } -$math = new Math; -echo $math->addition(23, 1); // 24 +$math = new Matematika; +echo $math->soucet(23, 1); // 24 ``` -Oppure attraverso altri metodi o direttamente attraverso il costruttore: +Oppure usando altri metodi, o direttamente il costruttore: ```php -class Addition +class Soucet { public function __construct( private float $a, @@ -132,26 +132,26 @@ class Addition ) { } - public function calculate(): float + public function spocti(): float { return $this->a + $this->b; } } -$addition = new Addition(23, 1); -echo $addition->calculate(); // 24 +$soucet = new Soucet(23, 1); +echo $soucet->spocti(); // 24 ``` -Entrambi gli esempi sono completamente conformi alla dependency injection. +Entrambi gli esempi sono pienamente conformi alla dependency injection. -Esempi reali .[#toc-real-life-examples] ---------------------------------------- +Esempi reali +------------ -Nel mondo reale, non scriverete classi per l'aggiunta di numeri. Passiamo agli esempi pratici. +Nel mondo reale, non scriverai classi per sommare numeri. Passiamo a esempi pratici. -Abbiamo una classe `Article` che rappresenta un post del blog: +Abbiamo una classe `Article` che rappresenta un articolo di blog: ```php class Article @@ -162,7 +162,7 @@ class Article public function save(): void { - // salvare l'articolo nel database + // salviamo l'articolo nel database } } ``` @@ -176,9 +176,9 @@ $article->content = 'Every year millions of people in ...'; $article->save(); ``` -Il metodo `save()` salva l'articolo in una tabella del database. Implementarlo utilizzando [Nette Database |database:] sarà un gioco da ragazzi, se non fosse per un problema: dove `Article` ottiene la connessione al database, cioè un oggetto della classe `Nette\Database\Connection`? +Il metodo `save()` salva l'articolo in una tabella del database. Implementarlo usando [Nette Database |database:] sarebbe un gioco da ragazzi, se non fosse per un intoppo: dove prende `Article` la connessione al database, cioè l'oggetto della classe `Nette\Database\Connection`? -Sembra che ci siano molte opzioni. Può prenderla da una variabile statica da qualche parte. Oppure ereditare da una classe che fornisce una connessione al database. Oppure sfruttare un [singleton |global-state#Singleton]. Oppure utilizzare le cosiddette facciate, utilizzate in Laravel: +Sembra che abbiamo molte opzioni. Può prenderla da qualche variabile statica. O ereditare da una classe che fornisce la connessione al database. O utilizzare il cosiddetto [singleton |global-state#Singleton]. O le cosiddette facades, che vengono utilizzate in Laravel: ```php use Illuminate\Support\Facades\DB; @@ -199,17 +199,17 @@ class Article } ``` -Bene, abbiamo risolto il problema. +Fantastico, abbiamo risolto il problema. -O forse sì? +O no? -Ricordiamo la [regola numero 1: Let It Be Passed to You |#rule #1: Let It Be Passed to You]: tutte le dipendenze di cui la classe ha bisogno devono essere passate ad essa. Perché se infrangiamo questa regola, abbiamo intrapreso la strada del codice sporco, pieno di dipendenze nascoste, incomprensibile e il risultato sarà un'applicazione dolorosa da mantenere e sviluppare. +Ricordiamo la [##Regola n. 1: fatti passare le dipendenze]: tutte le dipendenze di cui la classe ha bisogno devono essere passate ad essa. Perché se violiamo la regola, abbiamo intrapreso la strada verso un codice sporco pieno di dipendenze nascoste, incomprensibilità, e il risultato sarà un'applicazione che sarà doloroso mantenere e sviluppare. -L'utente della classe `Article` non ha idea di dove il metodo `save()` memorizzi l'articolo. In una tabella del database? Quale, quella di produzione o quella di test? E come può essere modificata? +L'utente della classe `Article` non ha idea di dove il metodo `save()` salvi l'articolo. In una tabella del database? In quale, quella di produzione o di test? E come si può cambiare? -L'utente deve guardare a come è implementato il metodo `save()` e trova l'uso del metodo `DB::insert()`. Quindi, deve cercare ulteriormente per scoprire come questo metodo ottiene una connessione al database. E le dipendenze nascoste possono formare una catena piuttosto lunga. +L'utente deve guardare come è implementato il metodo `save()` e trova l'uso del metodo `DB::insert()`. Quindi deve indagare ulteriormente su come questo metodo ottiene la connessione al database. E le dipendenze nascoste possono formare una catena piuttosto lunga. -In un codice pulito e ben progettato, non ci sono mai dipendenze nascoste, facciate di Laravel o variabili statiche. Nel codice pulito e ben progettato, gli argomenti vengono passati: +Nel codice pulito e ben progettato, non ci sono mai dipendenze nascoste, facades di Laravel o variabili statiche. Nel codice pulito e ben progettato, si passano argomenti: ```php class Article @@ -224,7 +224,7 @@ class Article } ``` -Un approccio ancora più pratico, come vedremo in seguito, sarà quello del costruttore: +Ancora più pratico, come vedremo più avanti, sarà tramite il costruttore: ```php class Article @@ -245,11 +245,11 @@ class Article ``` .[note] -Se siete programmatori esperti, potreste pensare che `Article` non dovrebbe avere un metodo `save()`; dovrebbe rappresentare un componente puramente di dati e un repository separato dovrebbe occuparsi del salvataggio. Questo ha senso. Ma questo ci porterebbe ben oltre lo scopo dell'argomento, che è l'iniezione di dipendenza, e lo sforzo di fornire semplici esempi. +Se sei un programmatore esperto, potresti pensare che `Article` non dovrebbe affatto avere un metodo `save()`, dovrebbe rappresentare puramente un componente dati e il salvataggio dovrebbe essere gestito da un repository separato. Questo ha senso. Ma ci porterebbe molto lontano dall'argomento, che è la dependency injection, e dallo sforzo di fornire esempi semplici. -Se scrivete una classe che richiede, per esempio, un database per il suo funzionamento, non inventate dove prenderlo, ma fatelo passare. Come parametro del costruttore o di un altro metodo. Ammettete le dipendenze. Ammettetele nell'API della vostra classe. Otterrete un codice comprensibile e prevedibile. +Se scrivi una classe che richiede, ad esempio, un database per funzionare, non inventare da dove ottenerlo, ma fattelo passare. Ad esempio, come parametro del costruttore o di un altro metodo. Riconosci le dipendenze. Riconoscile nell'API della tua classe. Otterrai un codice comprensibile e prevedibile. -E che dire di questa classe, che registra i messaggi di errore: +E che ne dici di questa classe, che registra i messaggi di errore: ```php class Logger @@ -262,21 +262,21 @@ class Logger } ``` -Cosa ne pensate, abbiamo rispettato la [regola n. 1: lascia che ti venga passato |#rule #1: Let It Be Passed to You]? +Cosa ne pensi, abbiamo rispettato la [##Regola n. 1: fatti passare le dipendenze]? -Non l'abbiamo fatto. +Non l'abbiamo rispettata. -L'informazione chiave, cioè la directory con il file di log, viene *ottenuta* dalla classe stessa dalla costante. +L'informazione chiave, cioè la directory con il file di log, la classe se la *procura da sola* da una costante. -Guardate l'esempio di utilizzo: +Guarda l'esempio di utilizzo: ```php $logger = new Logger; -$logger->log('The temperature is 23 °C'); -$logger->log('The temperature is 10 °C'); +$logger->log('La temperatura è 23 °C'); +$logger->log('La temperatura è 10 °C'); ``` -Senza conoscere l'implementazione, potreste rispondere alla domanda su dove vengono scritti i messaggi? Si potrebbe ipotizzare che l'esistenza della costante `LOG_DIR` sia necessaria per il suo funzionamento? E si potrebbe creare una seconda istanza che scriva in una posizione diversa? Certamente no. +Senza conoscere l'implementazione, saresti in grado di rispondere alla domanda su dove vengono scritti i messaggi? Ti verrebbe in mente che per funzionare è necessaria l'esistenza della costante `LOG_DIR`? E saresti in grado di creare una seconda istanza che scriva altrove? Certamente no. Correggiamo la classe: @@ -298,21 +298,21 @@ class Logger La classe è ora molto più comprensibile, configurabile e quindi più utile. ```php -$logger = new Logger('/path/to/log.txt'); -$logger->log('The temperature is 15 °C'); +$logger = new Logger('/percorso/al/log.txt'); +$logger->log('La temperatura è 15 °C'); ``` -Ma non mi interessa! .[#toc-but-i-don-t-care] ---------------------------------------------- +Ma questo non mi interessa! +--------------------------- -*"Quando creo un oggetto Articolo e chiamo save(), non voglio avere a che fare con il database; voglio solo che sia salvato in quello che ho impostato nella configurazione."* +*„Quando creo un oggetto Article e chiamo save(), non voglio occuparmi del database, voglio semplicemente che venga salvato in quello che ho impostato nella configurazione.“* -*"Quando uso Logger, voglio solo che il messaggio venga scritto e non voglio occuparmi di dove. Lasciate che vengano utilizzate le impostazioni globali."* +*„Quando uso Logger, voglio semplicemente che il messaggio venga scritto, e non voglio preoccuparmi di dove. Che venga utilizzata l'impostazione globale.“* -Questi sono punti validi. +Queste sono osservazioni corrette. -A titolo di esempio, analizziamo una classe che invia newsletter e registra come è andata: +Come esempio, mostreremo una classe che invia newsletter e registra come è andata: ```php class NewsletterDistributor @@ -322,27 +322,27 @@ class NewsletterDistributor $logger = new Logger(/* ... */); try { $this->sendEmails(); - $logger->log('Emails have been sent out'); + $logger->log('Le email sono state inviate'); } catch (Exception $e) { - $logger->log('An error occurred during the sending'); + $logger->log('Si è verificato un errore durante l\'invio'); throw $e; } } } ``` -La versione migliorata di `Logger`, che non utilizza più la costante `LOG_DIR`, richiede di specificare il percorso del file nel costruttore. Come risolvere questo problema? Alla classe `NewsletterDistributor` non interessa dove vengono scritti i messaggi, vuole solo scriverli. +Il `Logger` migliorato, che non utilizza più la costante `LOG_DIR`, richiede nel costruttore di specificare il percorso del file. Come risolvere questo problema? La classe `NewsletterDistributor` non si preoccupa affatto di dove vengono scritti i messaggi, vuole solo scriverli. -La soluzione è ancora una volta la [regola n. 1: Let It Be Passed to You |#rule #1: Let It Be Passed to You]: passare tutti i dati di cui la classe ha bisogno. +La soluzione è di nuovo la [##Regola n. 1: fatti passare le dipendenze]: tutti i dati di cui la classe ha bisogno, glieli passiamo. -Questo significa che passiamo il percorso del log attraverso il costruttore, che poi usiamo quando creiamo l'oggetto `Logger`? +Quindi significa che passiamo il percorso del log tramite il costruttore, che poi usiamo quando creiamo l'oggetto `Logger`? ```php class NewsletterDistributor { public function __construct( - private string $file, // NON IN QUESTO MODO! + private string $file, // ⛔ NON COSÌ! ) { } @@ -351,7 +351,7 @@ class NewsletterDistributor $logger = new Logger($this->file); ``` -No, non così! Il percorso non fa parte dei dati di cui ha bisogno la classe `NewsletterDistributor`; infatti, la classe `Logger` ne ha bisogno. Vedete la differenza? La classe `NewsletterDistributor` ha bisogno del logger stesso. Quindi è questo che passeremo: +Non così! Il percorso infatti **non appartiene** ai dati di cui la classe `NewsletterDistributor` ha bisogno; questi li necessita `Logger`. Percepisci la differenza? La classe `NewsletterDistributor` ha bisogno del logger come tale. Quindi glielo passiamo: ```php class NewsletterDistributor @@ -365,35 +365,33 @@ class NewsletterDistributor { try { $this->sendEmails(); - $this->logger->log('Emails have been sent out'); + $this->logger->log('Le email sono state inviate'); } catch (Exception $e) { - $this->logger->log('An error occurred during the sending'); + $this->logger->log('Si è verificato un errore durante l\'invio'); throw $e; } } } ``` -Ora è chiaro dalle firme della classe `NewsletterDistributor` che anche il logging fa parte delle sue funzionalità. E il compito di scambiare il logger con un altro, magari per i test, è del tutto banale. -Inoltre, se il costruttore della classe `Logger` cambia, questo non influisce sulla nostra classe. +Ora è chiaro dalle firme della classe `NewsletterDistributor` che parte della sua funzionalità è anche il logging. E il compito di sostituire il logger con un altro, ad esempio per i test, è del tutto banale. Inoltre, se il costruttore della classe `Logger` dovesse cambiare, ciò non avrebbe alcun impatto sulla nostra classe. -Regola n. 2: prendere ciò che è vostro .[#toc-rule-2-take-what-s-yours] ------------------------------------------------------------------------ +Regola n. 2: prendi ciò che è tuo +--------------------------------- -Non fatevi ingannare e non lasciate passare le dipendenze delle vostre dipendenze. Passate solo le vostre dipendenze. +Non lasciarti confondere e non farti passare le dipendenze delle tue dipendenze. Fatti passare solo le tue dipendenze. -In questo modo, il codice che utilizza altri oggetti sarà completamente indipendente dalle modifiche ai loro costruttori. La sua API sarà più veritiera. E soprattutto, sarà banale sostituire queste dipendenze con altre. +Grazie a ciò, il codice che utilizza altri oggetti sarà completamente indipendente dalle modifiche ai loro costruttori. La sua API sarà più veritiera. E soprattutto, sarà banale sostituire queste dipendenze con altre. -Un nuovo membro della famiglia .[#toc-new-family-member] --------------------------------------------------------- +Nuovo membro della famiglia +--------------------------- -Il team di sviluppo ha deciso di creare un secondo logger che scrive sul database. Abbiamo quindi creato una classe `DatabaseLogger`. Abbiamo quindi due classi, `Logger` e `DatabaseLogger`, una scrive su un file, l'altra su un database... non vi sembra strano come nome? -Non sarebbe meglio rinominare `Logger` in `FileLogger`? Decisamente sì. +Nel team di sviluppo è stata presa la decisione di creare un secondo logger, che scrive nel database. Creeremo quindi la classe `DatabaseLogger`. Quindi abbiamo due classi, `Logger` e `DatabaseLogger`, una scrive su file, l'altra nel database... non ti sembra ci sia qualcosa di strano in questa denominazione? Non sarebbe meglio rinominare `Logger` in `FileLogger`? Certamente sì. -Ma facciamolo in modo intelligente. Creiamo un'interfaccia con il nome originale: +Ma lo faremo in modo intelligente. Sotto il nome originale, creeremo un'interfaccia: ```php interface Logger @@ -412,17 +410,17 @@ class DatabaseLogger implements Logger // ... ``` -Per questo motivo, non sarà necessario modificare nulla nel resto del codice in cui viene utilizzato il logger. Per esempio, il costruttore della classe `NewsletterDistributor` si accontenterà di richiedere `Logger` come parametro. E dipenderà da noi quale istanza passare. +E grazie a ciò, non sarà necessario cambiare nulla nel resto del codice dove viene utilizzato il logger. Ad esempio, il costruttore della classe `NewsletterDistributor` sarà ancora soddisfatto del fatto che come parametro richiede `Logger`. E starà solo a noi decidere quale istanza passargli. -**Ecco perché non aggiungiamo mai il suffisso `Interface` o il prefisso `I` ai nomi delle interfacce.** Altrimenti, non sarebbe possibile sviluppare il codice in modo così gradevole. +**Per questo motivo non diamo mai ai nomi delle interfacce il suffisso `Interface` o il prefisso `I`.** Altrimenti non sarebbe possibile sviluppare il codice in modo così elegante. -Houston, abbiamo un problema .[#toc-houston-we-have-a-problem] --------------------------------------------------------------- +Houston, abbiamo un problema +---------------------------- -Mentre per l'intera applicazione si può utilizzare una singola istanza del logger, sia essa basata su file o su database, e passarla semplicemente ogni volta che si deve registrare qualcosa, per la classe `Article` è molto diverso. Creiamo le sue istanze secondo le necessità, anche più volte. Come gestire la dipendenza dal database nel suo costruttore? +Mentre in tutta l'applicazione possiamo accontentarci di una singola istanza del logger, sia esso basato su file o database, e semplicemente passarlo ovunque si registri qualcosa, la situazione è completamente diversa nel caso della classe `Article`. Le sue istanze, infatti, le creiamo secondo necessità, anche più volte. Come gestire la dipendenza dal database nel suo costruttore? -Un esempio può essere un controllore che deve salvare un articolo nel database dopo aver inviato un modulo: +Come esempio può servire un controller che, dopo l'invio di un form, deve salvare l'articolo nel database: ```php class EditController extends Controller @@ -437,30 +435,30 @@ class EditController extends Controller } ``` -Una possibile soluzione è ovvia: passare l'oggetto database al costruttore `EditController` e utilizzare `$article = new Article($this->db)`. +Una possibile soluzione si offre direttamente: ci facciamo passare l'oggetto database tramite il costruttore in `EditController` e usiamo `$article = new Article($this->db)`. -Come nel caso precedente con `Logger` e il percorso del file, questo non è l'approccio giusto. Il database non è una dipendenza di `EditController`, ma di `Article`. Passare il database va contro la [regola n. 2: prendi ciò che è tuo |#rule #2: take what's yours]. Se il costruttore della classe `Article` cambia (viene aggiunto un nuovo parametro), sarà necessario modificare il codice ovunque vengano create le istanze. Ufff. +Proprio come nel caso precedente con `Logger` e il percorso del file, questa non è la procedura corretta. Il database non è una dipendenza di `EditController`, ma di `Article`. Passare il database va quindi contro la [#Regola n. 2: prendi ciò che è tuo]. Se il costruttore della classe `Article` cambia (viene aggiunto un nuovo parametro), sarà necessario modificare anche il codice in tutti i punti in cui viene creata un'istanza. Ufff. Houston, cosa suggerisci? -Regola n. 3: lasciare che se ne occupi la fabbrica .[#toc-rule-3-let-the-factory-handle-it] -------------------------------------------------------------------------------------------- +Regola n. 3: lascia fare alla factory +------------------------------------- -Eliminando le dipendenze nascoste e passando tutte le dipendenze come argomenti, abbiamo ottenuto classi più configurabili e flessibili. Pertanto, abbiamo bisogno di qualcos'altro per creare e configurare queste classi più flessibili. Lo chiameremo "fabbriche". +Eliminando le dipendenze nascoste e passando tutte le dipendenze come argomenti, abbiamo ottenuto classi più configurabili e flessibili. E quindi abbiamo bisogno di qualcos'altro che crei e configuri per noi queste classi più flessibili. Lo chiameremo factory. -La regola generale è: se una classe ha delle dipendenze, lasciare la creazione delle sue istanze al factory. +La regola è: se una classe ha dipendenze, lascia la creazione delle sue istanze a una factory. -Le fabbriche sono un sostituto più intelligente dell'operatore `new` nel mondo della dependency injection. +Le factory sono un sostituto più intelligente dell'operatore `new` nel mondo della dependency injection. .[note] -Non bisogna confondersi con il design pattern *factory method*, che descrive un modo specifico di usare i factory e non è correlato a questo argomento. +Per favore, non confondete con il design pattern *factory method*, che descrive un modo specifico di utilizzare le factory e non è correlato a questo argomento. -Fabbrica .[#toc-factory] ------------------------- +Factory +------- -Un factory è un metodo o una classe che crea e configura oggetti. Chiameremo la classe che produce `Article` come `ArticleFactory`, e potrebbe avere questo aspetto: +Una factory è un metodo o una classe che produce e configura oggetti. La classe che produce `Article` la chiameremo `ArticleFactory` e potrebbe assomigliare ad esempio a questo: ```php class ArticleFactory @@ -477,7 +475,7 @@ class ArticleFactory } ``` -Il suo utilizzo nel controllore sarà il seguente: +Il suo utilizzo nel controller sarà il seguente: ```php class EditController extends Controller @@ -489,7 +487,7 @@ class EditController extends Controller public function formSubmitted($data) { - // lasciare che la fabbrica crei un oggetto + // lasciamo che la factory crei l'oggetto $article = $this->articleFactory->create(); $article->title = $data->title; $article->content = $data->content; @@ -498,11 +496,11 @@ class EditController extends Controller } ``` -A questo punto, se la firma del costruttore della classe `Article` cambia, l'unica parte del codice che deve reagire è la stessa `ArticleFactory`. Tutto il resto del codice che lavora con gli oggetti `Article`, come ad esempio `EditController`, non ne risentirà. +Se in questo momento la firma del costruttore della classe `Article` cambia, l'unica parte del codice che deve reagire è la factory stessa `ArticleFactory`. Tutto il resto del codice che lavora con gli oggetti `Article`, come ad esempio `EditController`, non ne sarà minimamente influenzato. -Ci si potrebbe chiedere se abbiamo effettivamente migliorato le cose. La quantità di codice è aumentata e tutto inizia a sembrare sospettosamente complicato. +Forse ora ti stai chiedendo se ci siamo davvero aiutati. La quantità di codice è aumentata e tutto inizia a sembrare sospettosamente complicato. -Non preoccupatevi, presto arriveremo al contenitore Nette DI. Questo contenitore ha diversi assi nella manica, che semplificheranno notevolmente la costruzione di applicazioni che utilizzano l'iniezione di dipendenze. Per esempio, invece della classe `ArticleFactory`, sarà sufficiente [scrivere una semplice interfaccia |factory]: +Non preoccuparti, tra poco arriveremo al Nette DI container. E quello ha molti assi nella manica che semplificheranno enormemente la costruzione di applicazioni che utilizzano la dependency injection. Ad esempio, invece della classe `ArticleFactory`, basterà [scrivere una semplice interfaccia |factory]: ```php interface ArticleFactory @@ -511,18 +509,18 @@ interface ArticleFactory } ``` -Ma stiamo correndo troppo; abbiate pazienza :-) +Ma stiamo anticipando, resisti ancora un po' :-) -Riassunto .[#toc-summary] -------------------------- +Riepilogo +--------- -All'inizio di questo capitolo abbiamo promesso di mostrare un processo per progettare codice pulito. Tutto ciò che serve è che le classi: +All'inizio di questo capitolo, abbiamo promesso di mostrarvi un metodo per progettare codice pulito. Basta alle classi -- [passino le dipendenze di cui hanno bisogno |#Rule #1: Let It Be Passed to You] -- [viceversa, non passino ciò di cui non hanno direttamente bisogno |#Rule #2: Take What's Yours] -- [e che gli oggetti con dipendenze siano meglio creati nei factory |#Rule #3: Let the Factory Handle it] +1) [passare le dipendenze di cui hanno bisogno |#Regola n. 1: fatti passare le dipendenze] +2) [e al contrario non passare ciò di cui non hanno direttamente bisogno |#Regola n. 2: prendi ciò che è tuo] +3) [e che gli oggetti con dipendenze si producono meglio nelle factory |#Regola n. 3: lascia fare alla factory] -A prima vista, queste tre regole non sembrano avere conseguenze di vasta portata, ma portano a una prospettiva radicalmente diversa sulla progettazione del codice. Ne vale la pena? Gli sviluppatori che hanno abbandonato le vecchie abitudini e hanno iniziato a usare coerentemente la dependency injection considerano questo passo un momento cruciale della loro vita professionale. Ha aperto loro il mondo delle applicazioni chiare e manutenibili. +Potrebbe non sembrare così a prima vista, ma queste tre regole hanno conseguenze di vasta portata. Portano a una visione radicalmente diversa della progettazione del codice. Ne vale la pena? I programmatori che hanno abbandonato le vecchie abitudini e hanno iniziato a usare costantemente la dependency injection considerano questo passo un momento fondamentale nella loro vita professionale. Si è aperto loro il mondo delle applicazioni chiare e manutenibili. -Ma cosa succede se il codice non utilizza costantemente l'iniezione di dipendenza? E se si basa su metodi statici o singleton? Questo causa qualche problema? [Sì, e sono molto importanti. |global-state] +Ma cosa succede se il codice non utilizza costantemente la dependency injection? Cosa succede se è basato su metodi statici o singleton? Porta a qualche problema? [Sì, e molto fondamentali |global-state]. diff --git a/dependency-injection/it/nette-container.texy b/dependency-injection/it/nette-container.texy index 7c615866b8..36bf88de99 100644 --- a/dependency-injection/it/nette-container.texy +++ b/dependency-injection/it/nette-container.texy @@ -1,10 +1,10 @@ -Contenitore Nette DI -******************** +Nette DI Container +****************** .[perex] -Nette DI è una delle librerie Nette più interessanti. È in grado di generare e aggiornare automaticamente contenitori DI compilati, estremamente veloci e incredibilmente facili da configurare. +Nette DI è una delle librerie più interessanti di Nette. Può generare e aggiornare automaticamente container DI compilati, che sono estremamente veloci e incredibilmente facili da configurare. -I servizi che devono essere creati da un contenitore DI sono solitamente definiti tramite file di configurazione in [formato NEON |neon:format]. Il contenitore che abbiamo creato manualmente nella [sezione precedente |container] sarebbe scritto come segue: +La forma dei servizi che il container DI deve creare viene solitamente definita tramite file di configurazione nel [formato NEON |neon:format]. Il container che abbiamo creato manualmente nel [capitolo precedente |container] sarebbe scritto così: ```neon parameters: @@ -19,16 +19,15 @@ services: - UserController ``` -La notazione è davvero breve. +La scrittura è davvero concisa. -Tutte le dipendenze dichiarate nei costruttori delle classi `ArticleFactory` e `UserController` vengono trovate e passate da Nette DI stesso grazie al cosiddetto [autowiring |autowiring], quindi non è necessario specificare nulla nel file di configurazione. -Quindi, anche se i parametri cambiano, non è necessario modificare nulla nella configurazione. Nette rigenererà automaticamente il contenitore. Potete concentrarvi esclusivamente sullo sviluppo dell'applicazione. +Tutte le dipendenze dichiarate nei costruttori delle classi `ArticleFactory` e `UserController` vengono rilevate e passate automaticamente da Nette DI grazie al cosiddetto [autowiring |autowiring], quindi non è necessario specificare nulla nel file di configurazione. Quindi, anche se i parametri cambiano, non è necessario modificare nulla nella configurazione. Il container Nette si rigenera automaticamente. Puoi concentrarti esclusivamente sullo sviluppo dell'applicazione. -Se si vogliono passare le dipendenze usando i setter, si può usare la sezione [setup |services#setup] per farlo. +Se vogliamo passare le dipendenze tramite setter, usiamo la sezione [setup |services#Setup]. -Nette DI genererà direttamente il codice PHP per il contenitore. Il risultato è un file `.php` che si può aprire e studiare. Questo permette di vedere esattamente come funziona il contenitore. È anche possibile eseguire il debug nell'IDE e procedere con il codice. E soprattutto: il PHP generato è estremamente veloce. +Nette DI genera direttamente il codice PHP del container. Il risultato è quindi un file `.php` che puoi aprire e studiare. Grazie a ciò, vedi esattamente come funziona il container. Puoi anche eseguirne il debug nell'IDE e fare lo step-by-step. E soprattutto: il PHP generato è estremamente veloce. -Nette DI può anche generare codice [factory |factory] basato sull'interfaccia fornita. Pertanto, invece della classe `ArticleFactory`, è sufficiente creare un'interfaccia nell'applicazione: +Nette DI può anche generare codice per le [factory |factory] basate sull'interfaccia fornita. Pertanto, invece della classe `ArticleFactory`, basterà creare solo un'interfaccia nell'applicazione: ```php interface ArticleFactory @@ -40,16 +39,16 @@ interface ArticleFactory L'esempio completo è disponibile [su GitHub |https://github.com/nette-examples/di-example-doc]. -Uso autonomo .[#toc-standalone-use] ------------------------------------ +Utilizzo indipendente +--------------------- -Utilizzare la libreria Nette DI in un'applicazione è molto semplice. Per prima cosa la installiamo con Composer (perché scaricare file zip è così obsoleto): +Implementare la libreria Nette DI in un'applicazione è molto semplice. Prima la installiamo con Composer (perché scaricare zip è coooosì obsoleto): ```shell composer require nette/di ``` -Il codice seguente crea un'istanza del contenitore DI in base alla configurazione memorizzata nel file `config.neon`: +Il seguente codice crea un'istanza del container DI secondo la configurazione salvata nel file `config.neon`: ```php $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp'); @@ -59,24 +58,23 @@ $class = $loader->load(function ($compiler) { $container = new $class; ``` -Il contenitore viene generato una sola volta, il suo codice viene scritto nella cache (la cartella `__DIR__ . '/temp'` ) e alle successive richieste viene letto solo da lì. +Il container viene generato solo una volta, il suo codice viene scritto nella cache (directory `__DIR__ . '/temp'`) e nelle richieste successive viene semplicemente caricato da lì. -I metodi `getService()` o `getByType()` sono usati per creare e recuperare i servizi. Ecco come si crea l'oggetto `UserController`: +Per creare e ottenere servizi si usano i metodi `getService()` o `getByType()`. In questo modo creiamo l'oggetto `UserController`: ```php -$database = $container->getByType(UserController::class); -$database->query('...'); +$controller = $container->getByType(UserController::class); +$controller->someMethod(); ``` -Durante lo sviluppo, è utile abilitare la modalità di aggiornamento automatico, in cui il contenitore viene rigenerato automaticamente se qualsiasi classe o file di configurazione viene modificato. Basta fornire `true` come secondo parametro del costruttore `ContainerLoader`. +Durante lo sviluppo, è utile attivare la modalità di auto-refresh, in cui il container si rigenera automaticamente se viene modificata una qualsiasi classe o file di configurazione. Basta specificare `true` come secondo argomento nel costruttore di `ContainerLoader`. ```php $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp', true); ``` -Utilizzo con il framework Nette .[#toc-using-it-with-the-nette-framework] -------------------------------------------------------------------------- +Utilizzo con Nette Framework +---------------------------- -Come abbiamo mostrato, l'uso di Nette DI non è limitato alle applicazioni scritte in Nette Framework: è possibile distribuirlo ovunque con sole 3 righe di codice. -Tuttavia, se si sviluppano applicazioni in Nette Framework, la configurazione e la creazione del contenitore sono gestite da [Bootstrap |application:bootstrap#toc-di-container-configuration]. +Come abbiamo mostrato, l'uso di Nette DI non è limitato alle applicazioni scritte in Nette Framework, puoi implementarlo ovunque con sole 3 righe di codice. Tuttavia, se sviluppi applicazioni in Nette Framework, la configurazione e la creazione del container sono gestite da [Bootstrap |application:bootstrapping#Configurazione del Container DI]. diff --git a/dependency-injection/it/passing-dependencies.texy b/dependency-injection/it/passing-dependencies.texy index bfd95ad60a..2c026635f3 100644 --- a/dependency-injection/it/passing-dependencies.texy +++ b/dependency-injection/it/passing-dependencies.texy @@ -1,24 +1,24 @@ -Passaggio di dipendenze -*********************** +Passaggio delle dipendenze +************************** <div class=perex> -Gli argomenti, o "dipendenze" nella terminologia DI, possono essere passati alle classi nei seguenti modi principali: +Gli argomenti, o nella terminologia DI "dipendenze", possono essere passati alle classi nei seguenti modi principali: -* passaggio per costruttore -* passando per un metodo (chiamato setter) -* impostando una proprietà -* con un metodo, un'annotazione o un attributo * con un'iniezione*. +* passaggio tramite costruttore +* passaggio tramite metodo (cosiddetto setter) +* impostazione di una variabile +* tramite metodo, annotazione o attributo *inject* </div> -Illustriamo ora le diverse varianti con esempi concreti. +Ora mostreremo le singole varianti con esempi concreti. -Iniezione del costruttore .[#toc-constructor-injection] -======================================================= +Passaggio tramite costruttore +============================= -Le dipendenze vengono passate come argomenti al costruttore quando l'oggetto viene creato: +Le dipendenze vengono passate al momento della creazione dell'oggetto come argomenti del costruttore: ```php class MyClass @@ -34,9 +34,9 @@ class MyClass $obj = new MyClass($cache); ``` -Questa forma è utile per le dipendenze obbligatorie di cui la classe ha assolutamente bisogno per funzionare, poiché senza di esse l'istanza non può essere creata. +Questa forma è adatta per le dipendenze obbligatorie di cui la classe ha necessariamente bisogno per funzionare, poiché senza di esse non sarà possibile creare l'istanza. -Da PHP 8.0, è possibile utilizzare una forma di notazione più breve ([constructor property promotion |https://blog.nette.org/it/php-8-0-panoramica-completa-delle-novita#toc-constructor-property-promotion]), funzionalmente equivalente: +Da PHP 8.0 possiamo usare una forma di scrittura più breve ([constructor property promotion |https://blog.nette.org/it/php-8-0-complete-overview-of-news#toc-constructor-property-promotion]), che è funzionalmente equivalente: ```php // PHP 8.0 @@ -49,7 +49,7 @@ class MyClass } ``` -A partire da PHP 8.1, una proprietà può essere contrassegnata da un flag `readonly` che dichiara che il contenuto della proprietà non cambierà: +Da PHP 8.1 è possibile contrassegnare la variabile con il flag `readonly`, che dichiara che il contenuto della variabile non cambierà più: ```php // PHP 8.1 @@ -62,13 +62,13 @@ class MyClass } ``` -Il contenitore DI passa le dipendenze al costruttore automaticamente, utilizzando l'[autowiring |autowiring]. Gli argomenti che non possono essere passati in questo modo (per esempio stringhe, numeri, booleani) vengono [scritti nella configurazione |services#Arguments]. +Il container DI passa automaticamente le dipendenze al costruttore tramite [autowiring |autowiring]. Gli argomenti che non possono essere passati in questo modo (es. stringhe, numeri, booleani) [li scriviamo nella configurazione |services#Argomenti]. -L'inferno dei costruttori .[#toc-constructor-hell] --------------------------------------------------- +Constructor hell +---------------- -Il termine *inferno dei costruttori* si riferisce a una situazione in cui un figlio eredita da una classe genitore il cui costruttore richiede delle dipendenze, e anche il figlio richiede delle dipendenze. Deve anche assumere e trasmettere le dipendenze del genitore: +Il termine *constructor hell* indica la situazione in cui un figlio eredita da una classe genitore il cui costruttore richiede dipendenze, e allo stesso tempo il figlio richiede dipendenze. Deve quindi ricevere e passare anche quelle del genitore: ```php abstract class BaseClass @@ -94,11 +94,11 @@ final class MyClass extends BaseClass } ``` -Il problema si presenta quando si vuole modificare il costruttore della classe `BaseClass`, ad esempio quando viene aggiunta una nuova dipendenza. Allora dobbiamo modificare anche tutti i costruttori dei figli. Il che rende tale modifica un inferno. +Il problema sorge nel momento in cui vogliamo modificare il costruttore della classe `BaseClass`, ad esempio quando viene aggiunta una nuova dipendenza. Allora è necessario modificare anche tutti i costruttori dei figli. Il che rende una tale modifica un inferno. -Come evitarlo? La soluzione è quella di **privilegiare la composizione rispetto all'ereditarietà**. +Come prevenirlo? La soluzione è **dare la preferenza alla [composizione rispetto all'ereditarietà |faq#Perché si preferisce la composizione all ereditarietà]**. -Quindi progettiamo il codice in modo diverso. Eviteremo le classi astratte di `Base*`. Invece di ottenere una funzionalità da `MyClass` ereditando da `BaseClass`, avrà quella funzionalità passata come dipendenza: +Quindi progetteremo il codice diversamente. Eviteremo le classi [astratte |nette:introduction-to-object-oriented-programming#Classi astratte] `Base*`. Invece che `MyClass` ottenga una certa funzionalità ereditando da `BaseClass`, si farà passare questa funzionalità come dipendenza: ```php final class SomeFunctionality @@ -125,10 +125,10 @@ final class MyClass ``` -Iniezione di setter .[#toc-setter-injection] -============================================ +Passaggio tramite setter +======================== -Le dipendenze vengono passate chiamando un metodo che le memorizza in una proprietà privata. La convenzione di denominazione usuale per questi metodi è la forma `set*()`, che è il motivo per cui sono chiamati setter, ma naturalmente possono essere chiamati in qualsiasi altro modo. +Le dipendenze vengono passate chiamando un metodo che le salva in una variabile privata. La convenzione usuale per nominare questi metodi è la forma `set*()`, per questo vengono chiamati setter, ma possono ovviamente chiamarsi in qualsiasi altro modo. ```php class MyClass @@ -145,9 +145,9 @@ $obj = new MyClass; $obj->setCache($cache); ``` -Questo metodo è utile per le dipendenze opzionali che non sono necessarie per il funzionamento della classe, poiché non è garantito che l'oggetto le riceva effettivamente (cioè che l'utente chiami il metodo). +Questo metodo è adatto per le dipendenze opzionali che non sono indispensabili per la funzione della classe, poiché non è garantito che l'oggetto riceva effettivamente la dipendenza (cioè che l'utente chiami il metodo). -Allo stesso tempo, questo metodo consente di richiamare ripetutamente il setter per modificare la dipendenza. Se ciò non è auspicabile, si può aggiungere un controllo al metodo o, a partire da PHP 8.1, contrassegnare la proprietà `$cache` con il flag `readonly`. +Allo stesso tempo, questo metodo consente di chiamare il setter ripetutamente e quindi modificare la dipendenza. Se ciò non è desiderato, aggiungiamo un controllo nel metodo, o da PHP 8.1 contrassegniamo la property `$cache` con il flag `readonly`. ```php class MyClass @@ -156,7 +156,7 @@ class MyClass public function setCache(Cache $cache): void { - if ($this->cache) { + if (isset($this->cache)) { throw new RuntimeException('The dependency has already been set'); } $this->cache = $cache; @@ -164,21 +164,20 @@ class MyClass } ``` -La chiamata al setter è definita nella configurazione del contenitore DI, nella [sezione setup |services#Setup]. Anche qui il passaggio automatico delle dipendenze è usato dall'autowiring: +La chiamata del setter viene definita nella configurazione del container DI nella [chiave setup |services#Setup]. Anche qui si utilizza il passaggio automatico delle dipendenze tramite autowiring: ```neon services: - - - create: MyClass + - create: MyClass setup: - setCache ``` -Iniezione di proprietà .[#toc-property-injection] -================================================= +Impostazione di una variabile +============================= -Le dipendenze vengono passate direttamente alla proprietà: +Le dipendenze vengono passate scrivendo direttamente nella variabile membro: ```php class MyClass @@ -190,28 +189,27 @@ $obj = new MyClass; $obj->cache = $cache; ``` -Questo metodo è considerato inappropriato, perché la proprietà deve essere dichiarata come `public`. Quindi, non si ha alcun controllo sul fatto che la dipendenza passata sia effettivamente del tipo specificato (questo era vero prima di PHP 7.4) e si perde la possibilità di reagire alla nuova dipendenza assegnata con il proprio codice, ad esempio per prevenire modifiche successive. Allo stesso tempo, la proprietà diventa parte dell'interfaccia pubblica della classe, il che potrebbe non essere auspicabile. +Questo metodo è considerato inappropriato, poiché la variabile membro deve essere dichiarata come `public`. E quindi non abbiamo controllo sul fatto che la dipendenza passata sia effettivamente del tipo specificato (valido prima di PHP 7.4) e perdiamo la possibilità di reagire alla dipendenza appena assegnata con codice personalizzato, ad esempio impedendo modifiche successive. Allo stesso tempo, la variabile diventa parte dell'interfaccia pubblica della classe, il che potrebbe non essere desiderabile. -L'impostazione della variabile è definita nella configurazione del contenitore DI, nella [sezione setup |services#Setup]: +L'impostazione della variabile viene definita nella configurazione del container DI nella [sezione setup |services#Setup]: ```neon services: - - - create: MyClass + - create: MyClass setup: - $cache = @\Cache ``` -Iniettare .[#toc-inject] -======================== +Inject +====== -Mentre i tre metodi precedenti sono generalmente validi in tutti i linguaggi orientati agli oggetti, l'iniezione tramite metodo, annotazione o attributo *inject* è specifica dei presentatori Nette. Sono trattati in [un capitolo a parte |best-practices:inject-method-attribute]. +Mentre i tre metodi precedenti valgono in generale in tutti i linguaggi orientati agli oggetti, l'iniezione tramite metodo, annotazione o attributo *inject* è specifica esclusivamente per i presenter in Nette. Ne parla un [capitolo separato |best-practices:inject-method-attribute]. -Quale strada scegliere? .[#toc-which-way-to-choose] -=================================================== +Quale metodo scegliere? +======================= -- il costruttore è adatto alle dipendenze obbligatorie di cui la classe ha bisogno per funzionare -- il setter, invece, è adatto per le dipendenze opzionali, o per le dipendenze che possono essere modificate -- Le variabili pubbliche non sono consigliate +- il costruttore è adatto per le dipendenze obbligatorie di cui la classe ha necessariamente bisogno per funzionare +- il setter è invece adatto per le dipendenze opzionali, o dipendenze che si desidera poter modificare ulteriormente +- le variabili pubbliche non sono adatte diff --git a/dependency-injection/it/services.texy b/dependency-injection/it/services.texy index d3b690fe82..a01436837d 100644 --- a/dependency-injection/it/services.texy +++ b/dependency-injection/it/services.texy @@ -1,33 +1,33 @@ -Definizioni del servizio -************************ +Definizione dei servizi +*********************** .[perex] -La configurazione è il punto in cui si inseriscono le definizioni dei servizi personalizzati. Questo viene fatto nella sezione `services`. +La configurazione è il luogo in cui insegniamo al container DI come costruire i singoli servizi e come collegarli ad altre dipendenze. Nette fornisce un modo molto chiaro ed elegante per raggiungere questo obiettivo. -Per esempio, ecco come si crea un servizio chiamato `database`, che sarà un'istanza della classe `PDO`: +La sezione `services` nel file di configurazione in formato NEON è il luogo in cui definiamo i nostri servizi personalizzati e le loro configurazioni. Vediamo un semplice esempio di definizione di un servizio chiamato `database`, che rappresenta un'istanza della classe `PDO`: ```neon services: database: PDO('sqlite::memory:') ``` -La denominazione dei servizi serve a consentirci di fare [riferimento a |#Referencing Services] essi. Se un servizio non è referenziato, non c'è bisogno di dargli un nome. Per questo motivo, al posto del nome si usa un punto elenco: +La configurazione specificata darà luogo al seguente metodo factory nel [container DI |container]: -```neon -services: - - PDO('sqlite::memory:') # servizio anonimo +```php +public function createServiceDatabase(): PDO +{ + return new PDO('sqlite::memory:'); +} ``` -Una voce di una riga può essere suddivisa in più righe per consentire l'aggiunta di altre chiavi, come [setup |#setup]. L'alias della chiave `create:` è `factory:`. +I nomi dei servizi ci consentono di fare riferimento ad essi in altre parti del file di configurazione, nel formato `@nomeServizio`. Se non è necessario nominare il servizio, possiamo semplicemente usare solo un trattino: ```neon services: - database: - create: PDO('sqlite::memory:') - setup: ... + - PDO('sqlite::memory:') ``` -Si recupera quindi il servizio dal contenitore DI usando il metodo `getService()` per nome o, meglio ancora, il metodo `getByType()` per tipo: +Per ottenere un servizio dal container DI, possiamo utilizzare il metodo `getService()` con il nome del servizio come parametro, o il metodo `getByType()` con il tipo del servizio: ```php $database = $container->getService('database'); @@ -35,26 +35,28 @@ $database = $container->getByType(PDO::class); ``` -Creare un servizio .[#toc-creating-a-service] -============================================= +Creazione del servizio +====================== -Il più delle volte, per creare un servizio basta creare un'istanza di una classe: +Di solito creiamo un servizio semplicemente creando un'istanza di una certa classe. Ad esempio: ```neon services: database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -Il che genererà un metodo factory nel [contenitore DI |container]: +Se abbiamo bisogno di estendere la configurazione con ulteriori chiavi, la definizione può essere suddivisa su più righe: -```php -public function createServiceDatabase(): PDO -{ - return new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'secret'); -} +```neon +services: + database: + create: PDO('sqlite::memory:') + setup: ... ``` -In alternativa, si può usare una chiave `arguments` per passare gli [argomenti |#Arguments]: +La chiave `create` ha un alias `factory`, entrambe le varianti sono comuni nella pratica. Tuttavia, raccomandiamo di usare `create`. + +Gli argomenti del costruttore o del metodo di creazione possono essere alternativamente scritti nella chiave `arguments`: ```neon services: @@ -63,294 +65,303 @@ services: arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret] ``` -Un metodo statico può anche creare un servizio: +I servizi non devono essere creati solo tramite la semplice creazione di un'istanza di classe, possono anche essere il risultato della chiamata di metodi statici o metodi di altri servizi: ```neon services: - database: My\Database::create(root, secret) + database: DatabaseFactory::create() + router: @routerFactory::create() ``` -Corrisponde al codice PHP: +Notate che per semplicità, invece di `->` si usa `::`, vedi [#Espressioni]. Verranno generati questi metodi factory: ```php public function createServiceDatabase(): PDO { - return My\Database::create('root', 'secret'); + return DatabaseFactory::create(); +} + +public function createServiceRouter(): RouteList +{ + return $this->getService('routerFactory')->create(); } ``` -Si presume che un metodo statico `My\Database::create()` abbia un valore di ritorno definito che il contenitore DI deve conoscere. Se non ce l'ha, si scrive il tipo nella configurazione: +Il container DI ha bisogno di conoscere il tipo del servizio creato. Se creiamo un servizio tramite un metodo che non ha un tipo di ritorno specificato, dobbiamo indicare esplicitamente questo tipo nella configurazione: ```neon services: database: - create: My\Database::create(root, secret) + create: DatabaseFactory::create() type: PDO ``` -Nette DI offre strumenti di espressione estremamente potenti per scrivere quasi tutto. Ad esempio, per fare [riferimento |#Referencing Services] a un altro servizio e chiamare il suo metodo. Per semplicità, si usa `::` invece di `->`. + +Argomenti +========= + +Passiamo gli argomenti al costruttore e ai metodi in modo molto simile a come avviene in PHP stesso: ```neon services: - routerFactory: App\Router\Factory - router: @routerFactory::create() + database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -Corrisponde al codice PHP: - -```php -public function createServiceRouterFactory(): App\Router\Factory -{ - return new App\Router\Factory; -} +Per una migliore leggibilità, possiamo suddividere gli argomenti su righe separate. In tal caso, l'uso delle virgole è opzionale: -public function createServiceRouter(): Router -{ - return $this->getService('routerFactory')->create(); -} +```neon +services: + database: PDO( + 'mysql:host=127.0.0.1;dbname=test' + root + secret + ) ``` -Le chiamate ai metodi possono essere concatenate come in PHP: +Puoi anche nominare gli argomenti e non dovrai preoccuparti del loro ordine: ```neon services: - foo: FooFactory::build()::get() + database: PDO( + username: root + password: secret + dsn: 'mysql:host=127.0.0.1;dbname=test' + ) ``` -Corrisponde al codice PHP: +Se vuoi omettere alcuni argomenti e usare il loro valore predefinito o inserire un servizio tramite [autowiring |autowiring], usa un trattino basso: -```php -public function createServiceFoo() -{ - return FooFactory::build()->get(); -} +```neon +services: + foo: Foo(_, %appDir%) ``` +Come argomenti si possono passare servizi, usare parametri e molto altro, vedi [#Espressioni]. + -Argomenti .[#toc-arguments] -=========================== +Setup +===== -I parametri denominati possono essere usati anche per passare argomenti: +Nella sezione `setup` definiamo i metodi che devono essere chiamati durante la creazione del servizio. ```neon services: - database: PDO( - 'mysql:host=127.0.0.1;dbname=test' # posizionale - username: root # nominato - password: secret # nominato - ) + database: + create: PDO(%dsn%, %user%, %password%) + setup: + - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) ``` -L'uso delle virgole è facoltativo quando si suddividono gli argomenti in più righe. +Questo in PHP sarebbe simile a: -Naturalmente, è possibile utilizzare [altri servizi |#Referencing Services] o [parametri |configuration#parameters] come argomenti: +```php +public function createServiceDatabase(): PDO +{ + $service = new PDO('...', '...', '...'); + $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + return $service; +} +``` + +Oltre alla chiamata di metodi, è possibile anche passare valori alle proprietà. È supportata anche l'aggiunta di un elemento a un array, che deve essere scritto tra virgolette per non entrare in conflitto con la sintassi NEON: ```neon services: - - Foo(@anotherService, %appDir%) + foo: + create: Foo + setup: + - $value = 123 + - '$onClick[]' = [@bar, clickHandler] ``` -Corrisponde al codice PHP: +Che nel codice PHP sarebbe simile a: ```php -public function createService01(): Foo +public function createServiceFoo(): Foo { - return new Foo($this->getService('anotherService'), '...'); + $service = new Foo; + $service->value = 123; + $service->onClick[] = [$this->getService('bar'), 'clickHandler']; + return $service; } ``` -Se il primo argomento è [autocablato |autowiring] e si vuole specificare il secondo, omettere il primo con `_` character, for example `Foo(_, %appDir%)`. O meglio ancora, passare solo il secondo argomento come parametro con nome, ad esempio `Foo(path: %appDir%)`. - -Nette DI e il formato NEON offrono strumenti espressivi estremamente potenti per scrivere praticamente qualsiasi cosa. Un argomento può essere un oggetto appena creato, si possono chiamare metodi statici, metodi di altri servizi o persino funzioni globali, utilizzando una notazione speciale: +Nel setup è tuttavia possibile chiamare anche metodi statici o metodi di altri servizi. Se hai bisogno di passare come argomento il servizio corrente, indicalo come `@self`: ```neon services: - analyser: My\Analyser( - FilesystemIterator(%appDir%) # creare l'oggetto - DateTime::createFromFormat('Y-m-d') # chiama un metodo statico - @anotherService # passare un altro servizio - @http.request::getRemoteAddress() # chiamare un altro metodo del servizio - ::getenv(NetteMode) # chiama una funzione globale - ) + foo: + create: Foo + setup: + - My\Helpers::initializeFoo(@self) + - @anotherService::setFoo(@self) ``` -Corrisponde al codice PHP: +Notate che per semplicità, invece di `->` si usa `::`, vedi [#Espressioni]. Verrà generato un tale metodo factory: ```php -public function createServiceAnalyser(): My\Analyser +public function createServiceFoo(): Foo { - return new My\Analyser( - new FilesystemIterator('...'), - DateTime::createFromFormat('Y-m-d'), - $this->getService('anotherService'), - $this->getService('http.request')->getRemoteAddress(), - getenv('NetteMode') - ); + $service = new Foo; + My\Helpers::initializeFoo($service); + $this->getService('anotherService')->setFoo($service); + return $service; } ``` -Funzioni speciali .[#toc-special-functions] -------------------------------------------- - -È inoltre possibile utilizzare funzioni speciali negli argomenti per eseguire il cast o la negazione dei valori: +Espressioni +=========== -- `not(%arg%)` negazione -- `bool(%arg%)` cast senza perdite a bool -- `int(%arg%)` cast senza perdite in int -- `float(%arg%)` cast senza perdite a float -- `string(%arg%)` cast senza perdite a stringa +Nette DI ci offre mezzi espressivi eccezionalmente ricchi, con i quali possiamo scrivere quasi qualsiasi cosa. Nei file di configurazione possiamo quindi utilizzare [parametri |configuration#Parametri]: ```neon -services: - - Foo( - id: int(::getenv('ProjectId')) - productionMode: not(%debugMode%) - ) -``` - -La riscrittura lossless differisce dalla normale riscrittura PHP, ad esempio utilizzando `(int)`, in quanto lancia un'eccezione per i valori non numerici. +# parametro +%wwwDir% -È possibile passare più servizi come argomenti. Un array di tutti i servizi di un particolare tipo (cioè, classe o interfaccia) viene creato dalla funzione `typed()`. La funzione ometterà i servizi che hanno il cablaggio automatico disabilitato e si possono specificare più tipi separati da una virgola. +# valore del parametro sotto la chiave +%mailer.user% -```neon -services: - - BarsDependent( typed(Bar) ) +# parametro all'interno di una stringa +'%wwwDir%/images' ``` -È anche possibile passare automaticamente un array di servizi utilizzando l'[autowiring |autowiring#Collection of Services]. - -Un array di tutti i servizi con un determinato [tag |#tags] viene creato dalla funzione `tagged()`. È possibile specificare più tag separati da una virgola. +Inoltre creare oggetti, chiamare metodi e funzioni: ```neon -services: - - LoggersDependent( tagged(logger) ) -``` +# creazione di un oggetto +DateTime() +# chiamata di un metodo statico +Collator::create(%locale%) -Servizi di riferimento .[#toc-referencing-services] -=================================================== +# chiamata di una funzione PHP +::getenv(DB_USER) +``` -I singoli servizi sono referenziati utilizzando il carattere `@` and name, so for example `@database`: +Fare riferimento ai servizi tramite il loro nome o tipo: ```neon -services: - - create: Foo(@database) - setup: - - setCacheStorage(@cache.storage) +# servizio per nome +@database + +# servizio per tipo +@Nette\Database\Connection ``` -Corrisponde al codice PHP: +Utilizzare la sintassi first-class callable: .{data-version:3.2.0} -```php -public function createService01(): Foo -{ - $service = new Foo($this->getService('database')); - $service->setCacheStorage($this->getService('cache.storage')); - return $service; -} +```neon +# creazione di un callback, analogo a [@user, logout] +@user::logout(...) ``` -Anche i servizi anonimi possono essere referenziati usando un callback, basta specificare il loro tipo (classe o interfaccia) invece del loro nome. Tuttavia, di solito non è necessario, a causa del [cablaggio automatico |autowiring]. +Utilizzare le costanti: ```neon -services: - - create: Foo(@Nette\Database\Connection) # or @\PDO - setup: - - setCacheStorage(@cache.storage) +# costante di classe +FilesystemIterator::SKIP_DOTS + +# costante globale ottenuta tramite la funzione PHP constant() +::constant(PHP_VERSION) ``` +Le chiamate ai metodi possono essere concatenate come in PHP. Solo per semplicità, invece di `->` si usa `::`: -Configurazione .[#toc-setup] -============================ +```neon +DateTime()::format('Y-m-d') +# PHP: (new DateTime())->format('Y-m-d') -Nella sezione di configurazione vengono elencati i metodi da richiamare durante la creazione del servizio: +@http.request::getUrl()::getHost() +# PHP: $this->getService('http.request')->getUrl()->getHost() +``` + +Queste espressioni possono essere utilizzate ovunque, durante la [creazione dei servizi |#Creazione del servizio], negli [#argomenti], nella sezione [#setup] o nei [parametri |configuration#Parametri]: ```neon +parameters: + ipAddress: @http.request::getRemoteAddress() + services: database: - create: PDO(%dsn%, %user%, %password%) + create: DatabaseFactory::create( @anotherService::getDsn() ) setup: - - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) + - initialize( ::getenv('DB_USER') ) ``` -Corrisponde al codice PHP: -```php -public function createServiceDatabase(): PDO -{ - $service = new PDO('...', '...', '...'); - $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - return $service; -} -``` +Funzioni speciali +----------------- -È possibile impostare anche le proprietà. È supportata anche l'aggiunta di un elemento a un array, che deve essere scritto tra virgolette per non entrare in conflitto con la sintassi NEON: +Nei file di configurazione è possibile utilizzare queste funzioni speciali: +- `not()` negazione del valore +- `bool()`, `int()`, `float()`, `string()` conversione senza perdita al tipo specificato +- `typed()` crea un array di tutti i servizi del tipo specificato +- `tagged()` crea un array di tutti i servizi con il tag specificato ```neon services: - foo: - create: Foo - setup: - - $value = 123 - - '$onClick[]' = [@bar, clickHandler] + - Foo( + id: int(::getenv('ProjectId')) + productionMode: not(%debugMode%) + ) ``` -Corrisponde al codice PHP: +Rispetto al classico type casting in PHP, come ad esempio `(int)`, la conversione senza perdita genera un'eccezione per valori non numerici. -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - $service->value = 123; - $service->onClick[] = [$this->getService('bar'), 'clickHandler']; - return $service; -} +La funzione `typed()` crea un array di tutti i servizi del tipo specificato (classe o interfaccia). Omette i servizi che hanno l'autowiring disabilitato. È possibile specificare anche più tipi separati da virgola. + +```neon +services: + - BarsDependent( typed(Bar) ) ``` -Tuttavia, anche i metodi statici o i metodi di altri servizi possono essere chiamati nel setup. Si passa il servizio effettivo a loro come `@self`: +È possibile passare l'array di servizi di un certo tipo come argomento anche automaticamente tramite [autowiring |autowiring#Array di servizi]. +La funzione `tagged()` crea quindi un array di tutti i servizi con un determinato tag. Anche qui è possibile specificare più tag separati da virgola. ```neon services: - foo: - create: Foo - setup: - - My\Helpers::initializeFoo(@self) - - @anotherService::setFoo(@self) + - LoggersDependent( tagged(logger) ) ``` -Corrisponde al codice PHP: -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - My\Helpers::initializeFoo($service); - $this->getService('anotherService')->setFoo($service); - return $service; -} +Autowiring +========== + +La chiave `autowired` consente di influenzare il comportamento dell'autowiring per un servizio specifico. Per i dettagli, vedi [capitolo sull'autowiring |autowiring]. + +```neon +services: + foo: + create: Foo + autowired: false # il servizio foo è escluso dall'autowiring ``` -Cablaggio automatico .[#toc-autowiring] -======================================= +Servizi Lazy .{data-version:3.2.4} +================================== -La chiave autowiring può essere usata per escludere un servizio dall'autowiring o per influenzarne il comportamento. Per ulteriori informazioni, vedere il [capitolo sull'autowiring |autowiring]. +Il lazy loading è una tecnica che posticipa la creazione di un servizio fino al momento in cui è effettivamente necessario. Nella configurazione globale è possibile [abilitare la creazione lazy |configuration#Servizi lazy] per tutti i servizi contemporaneamente. Per i singoli servizi è poi possibile sovrascrivere questo comportamento: ```neon services: foo: create: Foo - autowired: false # foo è rimosso dall'autowiring + lazy: false ``` +Quando un servizio è definito come lazy, al momento della sua richiesta dal container DI, otteniamo uno speciale oggetto placeholder. Questo sembra e si comporta come il servizio reale, ma l'inizializzazione effettiva (chiamata del costruttore e del setup) avviene solo alla prima chiamata di uno qualsiasi dei suoi metodi o proprietà. + +.[note] +Il lazy loading può essere utilizzato solo per classi utente, non per classi PHP interne. Richiede PHP 8.4 o versioni successive. + -Tag .[#toc-tags] -================ +Tag +=== -Le informazioni sugli utenti possono essere aggiunte ai singoli servizi sotto forma di tag: +I tag servono per aggiungere informazioni supplementari ai servizi. A un servizio è possibile aggiungere uno o più tag: ```neon services: @@ -360,7 +371,7 @@ services: - cached ``` -I tag possono anche avere un valore: +I tag possono anche contenere valori: ```neon services: @@ -370,26 +381,26 @@ services: logger: monolog.logger.event ``` -Un array di servizi con determinati tag può essere passato come argomento utilizzando la funzione `tagged()`. Si possono anche specificare più tag separati da una virgola. +Per ottenere tutti i servizi con determinati tag, puoi usare la funzione `tagged()`: ```neon services: - LoggersDependent( tagged(logger) ) ``` -I nomi dei servizi possono essere ottenuti dal contenitore DI con il metodo `findByTag()`: +Nel container DI è possibile ottenere i nomi di tutti i servizi con un determinato tag tramite il metodo `findByTag()`: ```php $names = $container->findByTag('logger'); // $names è un array contenente il nome del servizio e il valore del tag -// ad esempio ['foo' => 'monolog.logger.event', ...] +// es. ['foo' => 'monolog.logger.event', ...] ``` -Modalità Inject .[#toc-inject-mode] -=================================== +Modalità Inject +=============== -Il flag `inject: true` è usato per attivare il passaggio di dipendenze tramite variabili pubbliche con l'annotazione [inject |best-practices:inject-method-attribute#Inject Attributes] e i metodi [inject*() |best-practices:inject-method-attribute#inject Methods]. +Tramite il flag `inject: true` si attiva il passaggio delle dipendenze tramite variabili pubbliche con l'annotazione [inject |best-practices:inject-method-attribute#Attributi Inject] e i metodi [inject*() |best-practices:inject-method-attribute#Metodi inject]. ```neon services: @@ -398,13 +409,13 @@ services: inject: true ``` -Per impostazione predefinita, `inject` è attivato solo per i presentatori. +Nell'impostazione predefinita, `inject` è attivato solo per i presenter. -Modifica dei servizi .[#toc-modification-of-services] -===================================================== +Modifica dei servizi +==================== -Nel contenitore DI sono presenti diversi servizi aggiunti da un'[estensione |#di-extensions] incorporata o dall'[utente |#di-extensions]. Le definizioni di questi servizi possono essere modificate nella configurazione. Ad esempio, per il servizio `application.application`, che per impostazione predefinita è un oggetto `Nette\Application\Application`, si può cambiare la classe: +Il container DI contiene molti servizi che sono stati aggiunti tramite estensioni integrate o [estensioni utente |extensions]. È possibile modificare le definizioni di questi servizi direttamente nella configurazione. Ad esempio, è possibile modificare la classe del servizio `application.application`, che è standard `Nette\Application\Application`, in un'altra: ```neon services: @@ -415,7 +426,7 @@ services: Il flag `alteration` è informativo e indica che stiamo solo modificando un servizio esistente. -Possiamo anche aggiungere una configurazione: +Possiamo anche completare il setup: ```neon services: @@ -426,7 +437,7 @@ services: - '$onStartup[]' = [@resource, init] ``` -Quando si riscrive un servizio, si possono rimuovere gli argomenti originali, gli elementi di configurazione o i tag, a questo serve `reset`: +Durante la sovrascrittura di un servizio, potremmo voler rimuovere gli argomenti originali, le voci di setup o i tag, a tale scopo serve `reset`: ```neon services: @@ -439,7 +450,7 @@ services: - tags ``` -Un servizio aggiunto per estensione può anche essere rimosso dal contenitore: +Se si desidera rimuovere un servizio aggiunto da un'estensione, è possibile farlo in questo modo: ```neon services: diff --git a/dependency-injection/ja/@home.texy b/dependency-injection/ja/@home.texy index bf7357f0ab..88c0bf78b6 100644 --- a/dependency-injection/ja/@home.texy +++ b/dependency-injection/ja/@home.texy @@ -1,24 +1,21 @@ -依存性注入 -***** +Nette DI +******** .[perex] -依存性注入は、コードや開発に対する見方を根本的に変えるデザインパターンです。クリーンな設計で持続可能なアプリケーションの世界への道を開くものです。 +依存関係注入は、コードと開発に対する見方を根本的に変えるデザインパターンです。クリーンに設計され、保守可能なアプリケーションの世界への扉を開きます。 -- [Dependency Injectionとは? |introduction] +- [依存関係注入とは? |introduction] - [グローバルステートとシングルトン |global-state] -- [依存性の受け渡し |passing-dependencies] -- [DIコンテナとは? |container] -- [よくある質問 |faq] - +- [依存関係の受け渡し |passing-dependencies] +- [DI コンテナとは? |container] +- [よくある質問|faq] -Nette DI --------- -`nette/di` パッケージは、PHP のための非常に高度なコンパイル済み DI コンテナを提供します。 +`nette/di` パッケージは、PHP用の非常に高度なコンパイル済みDIコンテナを提供します。 -- [ネッテDIコンテナ |nette-container] -- [コンフィギュレーション |configuration] -- [サービス定義 |services] -- [オートワイヤリング |autowiring] -- [生成されたファクトリー |factory] -- [ネッテDIの拡張機能を作成する |extensions] +- [Nette DI コンテナ |nette-container] +- [設定 |configuration] +- [サービスの定義 |services] +- [Autowiring |autowiring] +- [生成されたファクトリ |factory] +- [Nette DI 拡張機能の作成|extensions] diff --git a/dependency-injection/ja/@left-menu.texy b/dependency-injection/ja/@left-menu.texy index 0f99c6dd14..9b82537edd 100644 --- a/dependency-injection/ja/@left-menu.texy +++ b/dependency-injection/ja/@left-menu.texy @@ -1,17 +1,17 @@ -依存性注入 -***** -- [DIとは? |introduction] +Dependency Injection +******************** +- [DI とは? |introduction] - [グローバルステートとシングルトン |global-state] - [依存関係の受け渡し |passing-dependencies] -- [DI コンテナとは?|container] -- [よくある質問 |faq] +- [DI コンテナとは? |container] +- [よくある質問|faq] Nette DI -------- -- [ネットDIコンテナ|nette-container] -- [構成 |configuration] -- [サービス定義 |services] -- [自動配線 |autowiring] -- [生成されたファクトリー |factory] -- [Nette DIの拡張機能を作成する |extensions] +- [Nette DI コンテナ |nette-container] +- [設定 |configuration] +- [サービスの定義 |services] +- [Autowiring |autowiring] +- [生成されたファクトリ |factory] +- [Nette DI 拡張機能の作成|extensions] diff --git a/dependency-injection/ja/@meta.texy b/dependency-injection/ja/@meta.texy new file mode 100644 index 0000000000..d3c41dc3d7 --- /dev/null +++ b/dependency-injection/ja/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette ドキュメンテーション}} diff --git a/dependency-injection/ja/autowiring.texy b/dependency-injection/ja/autowiring.texy index b968fa720e..2c16cc73d5 100644 --- a/dependency-injection/ja/autowiring.texy +++ b/dependency-injection/ja/autowiring.texy @@ -1,24 +1,24 @@ -オートワイヤリング -********* +Autowiring +********** .[perex] -Autowiringは、コンストラクタなどに自動的にサービスを渡すことができる優れた機能で、私たちはサービスを書く必要が全くありません。時間を大幅に節約できます。 +オートワイヤリングは、コンストラクタや他のメソッドに必要なサービスを自動的に渡すことができる素晴らしい機能であり、それらを書く必要がまったくありません。これにより、多くの時間を節約できます。 -これにより、サービス定義を書く際に、大半の引数を省略することができます。代わりに +これにより、サービス定義を書く際にほとんどの引数を省略できます。代わりに: ```neon services: articles: Model\ArticleRepository(@database, @cache.storage) ``` -と書くだけです。 +次のように書くだけで十分です: ```neon services: articles: Model\ArticleRepository ``` -自動配線は型によって駆動されるので、`ArticleRepository` クラスは以下のように定義する必要があります。 +オートワイヤリングは型に基づいて行われるため、機能するためには `ArticleRepository` クラスが次のように定義されている必要があります: ```php namespace Model; @@ -30,22 +30,22 @@ class ArticleRepository } ``` -autowiring を使用するには、コンテナ内の各タイプに対して **ただ1つのサービス** が必要です。それ以上あると、autowiring はどれを渡せばいいのかわからなくなり、例外を投げ出してしまいます。 +オートワイヤリングを使用できるようにするには、コンテナ内に各型に対して**ちょうど1つのサービス**が存在する必要があります。もし複数存在する場合、オートワイヤリングはどれを渡すべきか分からず、例外をスローします: ```neon services: mainDb: PDO(%dsn%, %user%, %password%) tempDb: PDO('sqlite::memory:') - articles: Model\ArticleRepository # THROWS EXCEPTION, both mainDb and tempDb matches + articles: Model\ArticleRepository # 例外をスローします。mainDb と tempDb の両方が適合します ``` -解決策としては、autowiring をバイパスしてサービス名を明示的に指定する方法があります (例:`articles: Model\ArticleRepository(@mainDb)`)。しかし、1つのサービス、または最初のサービスの自動配線を[無効に |#Disabled autowiring]する方が便利[です |#Preferred Autowiring]。 +解決策としては、オートワイヤリングを回避してサービス名を明示的に指定する(例:`articles: Model\ArticleRepository(@mainDb)`)か、より賢い方法として、サービスの1つのオートワイヤリングを[無効にする |#オートワイヤリングの無効化]か、最初のサービスを[優先する |#オートワイヤリングの優先順位]ことです。 -自動配線を無効にする .[#toc-disabled-autowiring] --------------------------------------- +オートワイヤリングの無効化 +------------- -`autowired: no` オプションを使用することで、サービスの自動配線を無効にすることができます。 +サービスのオートワイヤリングは、`autowired: no` オプションを使用して無効にできます: ```neon services: @@ -53,28 +53,27 @@ services: tempDb: create: PDO('sqlite::memory:') - autowired: false # removes tempDb from autowiring + autowired: false # tempDb サービスはオートワイヤリングから除外されます - articles: Model\ArticleRepository # therefore passes mainDb to constructor + articles: Model\ArticleRepository # したがって、mainDb をコンストラクタに渡します ``` -`articles` サービスは`mainDb` サービスしか見ていないので、 コンストラクタに渡すことのできる`PDO` タイプの一致するサービスが二つある (つまり`mainDb` と`tempDb`) という例外を投げません。 +`articles` サービスは、コンストラクタに渡すことができる `PDO` 型の適合するサービスが2つ(`mainDb` と `tempDb`)存在するという例外をスローしません。なぜなら、`mainDb` サービスしか見ていないからです。 .[note] -Nette で自動配線を設定すると、Symfony の`autowire: false` オプションで自動配線をサービスのコンストラクタ引数に使用しないように指定した場合とは異なる動作をします。 -Nette では、コンストラクタの引数であろうと他のメソッドであろうと、自動配線は常に使用されます。`autowired: false` オプションは、自動配線を使ってサービスのインスタンスをどこにも渡してはいけないというものです。 +Netteでのオートワイヤリングの設定はSymfonyとは異なります。Symfonyでは `autowire: false` オプションは、特定のサービスのコンストラクタ引数にオートワイヤリングを使用しないことを意味します。 Netteでは、オートワイヤリングは常に、コンストラクタ引数であろうと他のメソッドであろうと使用されます。`autowired: false` オプションは、特定のサービスのインスタンスがオートワイヤリングによってどこにも渡されるべきではないことを意味します。 -望ましい自動配線 .[#toc-preferred-autowiring] -------------------------------------- +オートワイヤリングの優先順位 +-------------- -同じタイプのサービスが複数あり、そのうちの1つが`autowired` のオプションを持っている場合、そのサービスが優先されます。 +同じ型のサービスが複数あり、そのうちの1つに `autowired` オプションを指定すると、そのサービスが優先されます: ```neon services: mainDb: create: PDO(%dsn%, %user%, %password%) - autowired: PDO # makes it preferred + autowired: PDO # 優先されます tempDb: create: PDO('sqlite::memory:') @@ -82,13 +81,13 @@ services: articles: Model\ArticleRepository ``` -`articles` サービスは、一致する`PDO` サービスが2つある (すなわち`mainDb` と`tempDb`)という例外を投げず、優先されるサービス、すなわち`mainDb` を使用します。 +`articles` サービスは、`PDO` 型の適合するサービスが2つ(`mainDb` と `tempDb`)存在するという例外をスローしませんが、優先されるサービス、つまり `mainDb` を使用します。 -サービスのコレクション .[#toc-collection-of-services] ------------------------------------------- +サービスの配列 +------- -Autowiring は、特定の型のサービスの配列を渡すこともできます。PHP は配列の項目の型をネイティブに表記することができないので、`array` の型に加えて、`ClassName[]` のように項目の型を指定した phpDoc コメントを追加する必要があります。 +オートワイヤリングは、特定の型のサービスの配列も渡すことができます。PHPでは配列の要素の型をネイティブに記述できないため、`array` 型に加えて、`ClassName[]` 形式の要素型を持つphpDocコメントを追加する必要があります: ```php namespace Model; @@ -103,46 +102,45 @@ class ShipManager } ``` -DI コンテナは、指定した型に対応するサービスの配列を自動的に渡します。DI コンテナは、指定した型に対応するサービスの配列を自動的に渡します。自動配線が無効になっているサービスは省略されます。 +DIコンテナは、指定された型に対応するサービスの配列を自動的に渡します。オートワイヤリングが無効になっているサービスは除外されます。 -phpDoc のコメントの形式を制御できない場合は、 サービスの配列を直接設定に渡すこともできます。 [`typed()` |services#Special Functions]. +コメント内の型は `array<int, Class>` または `list<Class>` の形式でもかまいません。phpDocコメントの形式を変更できない場合は、[`typed()` |services#特殊関数] を使用して設定で直接サービスの配列を渡すことができます。 -スカラー引数 .[#toc-scalar-arguments] -------------------------------- +スカラー引数 +------ -Autowiring は、オブジェクトとオブジェクトの配列のみを渡すことができます。スカラー引数(文字列、数値、ブール値など)は[設定に書きます |services#Arguments]。 -代替案としては、スカラー値(または複数の値)をオブジェクトとしてカプセル化した[settings-object |best-practices:en:passing-settings-to-presenters]を作成し、それを autowiring を使って再度渡すことができます。 +オートワイヤリングは、オブジェクトとオブジェクトの配列のみを注入できます。スカラー引数(例:文字列、数値、ブール値)は[設定で記述します |services#引数]。 代替案は、スカラー値(または複数の値)をオブジェクトの形式にカプセル化する[settings-objekt |best-practices:passing-settings-to-presenters]を作成することです。その後、このオブジェクトを再びオートワイヤリングで渡すことができます。 ```php class MySettings { public function __construct( - // readonly can be used since PHP 8.1 + // readonly は PHP 8.1 以降で使用可能です public readonly bool $value, ) {} } ``` -サービスを作成するには,コンフィギュレーションに追加します. +設定に追加することでサービスを作成します: ```neon services: - MySettings('any value') ``` -すべてのクラスは自動配線によってそれを要求します。 +その後、すべてのクラスがオートワイヤリングを使用してそれを要求します。 -オートワイヤリングの絞り込み .[#toc-narrowing-of-autowiring] ----------------------------------------------- +オートワイヤリングの絞り込み +-------------- -個々のサービスにおいて、オートワイヤリングは特定のクラスやインターフェースに絞り込むことができます。 +個々のサービスのオートワイヤリングを特定のクラスやインターフェースに絞り込むことができます。 -通常、自動配線では、サービスが対応する型を持つ各メソッドパラメータにサービスを 渡す。絞り込みとは、メソッドパラメータに指定された型がサービスを渡すために満たすべき条件を指定することである。 +通常、オートワイヤリングは、サービスの型が一致するメソッドの各パラメータにサービスを渡します。絞り込みとは、サービスが渡されるためにメソッドパラメータで指定された型が満たさなければならない条件を設定することを意味します。 -例を挙げてみよう。 +例を見てみましょう: ```php class ParentClass @@ -164,42 +162,42 @@ class ChildDependent } ``` -すべてサービスとして登録すると、自動配線は失敗してしまいます。 +これらすべてをサービスとして登録すると、オートワイヤリングは失敗します: ```neon services: parent: ParentClass child: ChildClass - parentDep: ParentDependent # THROWS EXCEPTION, both parent and child matches - childDep: ChildDependent # passes the service 'child' to the constructor + parentDep: ParentDependent # 例外をスローします。parent と child の両方のサービスが適合します + childDep: ChildDependent # オートワイヤリングは child サービスをコンストラクタに渡します ``` -`parentDep` サービスは例外`Multiple services of type ParentClass found: parent, child` を投げます。なぜなら`parent` と`child` の両方がそのコンストラクタに適合し、autowiring はどちらを選ぶべきかの判断を下すことができないからです。 +`parentDep` サービスは `Multiple services of type ParentClass found: parent, child` という例外をスローします。なぜなら、そのコンストラクタには `parent` と `child` の両方のサービスが適合し、オートワイヤリングはどちらを選択すべきか決定できないからです。 -したがって、サービス`child` の自動配線は`ChildClass` に絞られます。 +したがって、`child` サービスのオートワイヤリングを `ChildClass` 型に絞り込むことができます: ```neon services: parent: ParentClass child: create: ChildClass - autowired: ChildClass # alternative: 'autowired: self' + autowired: ChildClass # 'autowired: self' と書くこともできます - parentDep: ParentDependent # THROWS EXCEPTION, the 'child' can not be autowired - childDep: ChildDependent # passes the service 'child' to the constructor + parentDep: ParentDependent # オートワイヤリングは parent サービスをコンストラクタに渡します + childDep: ChildDependent # オートワイヤリングは child サービスをコンストラクタに渡します ``` -`parentDep` サービスは、現在唯一の一致するオブジェクトであるため、`parentDep` サービスのコンストラクタに渡されます。`child` サービスは自動配線で渡されなくなりました。はい、`child` サービスはまだ`ParentClass` 型ですが、パラメータ型に与えられた狭義の条件はもはや適用されません。つまり、`ParentClass` *is a supertype* of`ChildClass` はもはや真ではありません。 +これで、`parentDep` サービスのコンストラクタには `parent` サービスが渡されます。なぜなら、これが現在唯一適合するオブジェクトだからです。`child` サービスはもはやオートワイヤリングによって渡されません。はい、`child` サービスは依然として `ParentClass` 型ですが、パラメータの型に指定された絞り込み条件はもはや満たされません。つまり、`ParentClass` が `ChildClass` の*スーパータイプである*という条件は満たされません。 -`child` の場合,`self` は現在のサービスタイプを意味するので,`autowired: ChildClass` は`autowired: self` と書くことができる. +`child` サービスでは、`autowired: ChildClass` は `autowired: self` と書くこともできます。なぜなら `self` は現在のサービスクラスのプレースホルダーだからです。 -`autowired` のキーには、いくつかのクラスやインターフェイスを配列として含めることができます。 +`autowired` キーには、複数のクラスやインターフェースを配列として指定することもできます: ```neon autowired: [BarClass, FooInterface] ``` -例題にインターフェイスを追加してみましょう。 +例にインターフェースを追加してみましょう: ```php interface FooInterface @@ -239,13 +237,13 @@ class ChildDependent } ``` -`child` のサービスを制限しない場合、`FooDependent`,`BarDependent`,`ParentDependent`,`ChildDependent` のすべてのクラスのコンストラクタに収まり、 autowiring はそこにそれを渡します。 +`child` サービスを制限しない場合、`FooDependent`、`BarDependent`、`ParentDependent`、`ChildDependent` のすべてのクラスのコンストラクタに適合し、オートワイヤリングはそれを渡します。 -しかし、`autowired: ChildClass` (または`self`) を使って`ChildClass` に自動配線を絞ると、`ChildDependent` のコンストラクタにしか渡らなくなります。これは、`ChildClass` 型の引数が必要で、`ChildClass` *型*`ChildClass` であるためです。他のパラメータに指定された型は`ChildClass` のスーパーセットではないので、サービスは渡されません。 +しかし、そのオートワイヤリングを `autowired: ChildClass` (または `self`) を使用して `ChildClass` に絞り込むと、オートワイヤリングはそれを `ChildDependent` のコンストラクタにのみ渡します。なぜなら、要求される引数の型は `ChildClass` であり、`ChildClass` は `ChildClass` の*型である*という条件が満たされるからです。他のパラメータで指定された他の型は `ChildClass` のスーパータイプではないため、サービスは渡されません。 -`autowired: ParentClass` を使って`ParentClass` に制限すると、autowiring は`ChildDependent` コンストラクタに再び渡します (必要な型`ChildClass` は`ParentClass` のスーパーセットなので)。また、必要な型`ParentClass` も一致するので`ParentDependent` コンストラクタにも渡します。 +`autowired: ParentClass` を使用して `ParentClass` に制限すると、オートワイヤリングはそれを再び `ChildDependent` のコンストラクタに渡します(要求される `ChildClass` は `ParentClass` のスーパータイプであるため)。そして、新たに `ParentDependent` のコンストラクタにも渡します。なぜなら、要求される型 `ParentClass` も適合するからです。 -`FooInterface` に限定すると、`ParentDependent` (要求される型`ParentClass` は`FooInterface` の上位型) と`ChildDependent` にはまだ自動配線されますが、さらに`FooDependent` のコンストラクタには渡されますが、`BarInterface` は`FooInterface` の上位型ではないので、`BarDependent` には渡りません。 +`FooInterface` に制限すると、依然として `ParentDependent`(要求される `ParentClass` は `FooInterface` のスーパータイプ)と `ChildDependent` にオートワイヤリングされますが、さらに `FooDependent` のコンストラクタにも渡されます。しかし、`BarDependent` には渡されません。なぜなら `BarInterface` は `FooInterface` のスーパータイプではないからです。 ```neon services: @@ -253,8 +251,8 @@ services: create: ChildClass autowired: FooInterface - fooDep: FooDependent # passes the service child to the constructor - barDep: BarDependent # THROWS EXCEPTION, no service would pass - parentDep: ParentDependent # passes the service child to the constructor - childDep: ChildDependent # passes the service child to the constructor + fooDep: FooDependent # オートワイヤリングは child をコンストラクタに渡します + barDep: BarDependent # 例外をスローします。適合するサービスがありません + parentDep: ParentDependent # オートワイヤリングは child をコンストラクタに渡します + childDep: ChildDependent # オートワイヤリングは child をコンストラクタに渡します ``` diff --git a/dependency-injection/ja/configuration.texy b/dependency-injection/ja/configuration.texy index 6da88d5eec..b9510329cf 100644 --- a/dependency-injection/ja/configuration.texy +++ b/dependency-injection/ja/configuration.texy @@ -2,31 +2,32 @@ DIコンテナの設定 ********* .[perex] -Nette DIコンテナの設定オプションの概要を説明します。 +Nette DIコンテナの設定オプションの概要。 -設定ファイル .[#toc-configuration-file] -================================= +設定ファイル +====== -Nette DIコンテナは、設定ファイルを使って簡単に制御することができます。設定ファイルは通常、[NEON |neon:en:format]形式で記述されます。編集には、この形式に[対応 |best-practices:en:editors-and-tools#ide-editor]したエディタを使用することをお勧めします。 +Nette DIコンテナは、設定ファイルを使用して簡単に制御できます。これらは通常、[NEON形式|neon:format]で記述されます。編集には、この形式を[サポートするエディタ |best-practices:editors-and-tools#IDEエディタ]をお勧めします。 <pre> -"decorator .[prism-token prism-atrule]":[#Decorator]: "Decorator .[prism-token prism-comment]"<br> -"di .[prism-token prism-atrule]":[#DI]: "DI Container .[prism-token prism-comment]"<br> -"extensions .[prism-token prism-atrule]":[#Extensions]: "Install additional DI extensions .[prism-token prism-comment]"<br> -"includes .[prism-token prism-atrule]":[#Including files]: "Including files .[prism-token prism-comment]"<br> -"parameters .[prism-token prism-atrule]":[#Parameters]: "Parameters .[prism-token prism-comment]"<br> -"search .[prism-token prism-atrule]":[#Search]: "Automatic service registration .[prism-token prism-comment]"<br> -"services .[prism-token prism-atrule]":[services]: "Services .[prism-token prism-comment]" +"decorator .[prism-token prism-atrule]":[#decorator]: "デコレータ .[prism-token prism-comment]"<br> +"di .[prism-token prism-atrule]":[#DI]: "DIコンテナ .[prism-token prism-comment]"<br> +"extensions .[prism-token prism-atrule]":[#拡張機能]: "追加のDI拡張機能のインストール .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[#ファイルのインクルード]: "ファイルのインクルード .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[#パラメータ]: "パラメータ .[prism-token prism-comment]"<br> +"search .[prism-token prism-atrule]":[#Search]: "サービスの自動登録 .[prism-token prism-comment]"<br> +"services .[prism-token prism-atrule]":[services]: "サービス .[prism-token prism-comment]" </pre> -`%`, you must escape it by doubling it to `%%` という文字を含む文字列を書き込むには. .[note] +.[note] +`%` 文字を含む文字列を記述したい場合は、`%%` と二重にしてエスケープする必要があります。 -パラメータ .[#toc-parameters] -======================== +パラメータ +===== -パラメータを定義して、サービス定義の一部として使用することができます。これにより、より定期的に変更したい値を分離することができます。 +設定内でパラメータを定義し、それらをサービス定義の一部として使用できます。これにより、設定を明確にしたり、変更される値を統合して分離したりできます。 ```neon parameters: @@ -35,9 +36,9 @@ parameters: password: secret ``` -`foo` パラメータは、設定ファイル内の他の場所で`%foo%` を介して参照することができます。また、`'%wwwDir%/images'` のような文字列の中で使用することもできます。 +パラメータ `dsn` は、設定内のどこでも `%dsn%` と記述して参照できます。パラメータは `'%wwwDir%/images'` のような文字列内でも使用できます。 -パラメータは単なる文字列である必要はなく、配列の値でもかまいません。 +パラメータは文字列や数値だけでなく、配列を含むこともできます: ```neon parameters: @@ -48,32 +49,32 @@ parameters: languages: [cs, en, de] ``` -一つのキーを`%mailer.user%` と呼ぶことができます。 +特定のキーは `%mailer.user%` のように参照します。 -もし、コードの中で、例えばクラスの中で、何らかのパラメータの値を取得する必要がある場合は、このクラスに渡してください。例えば、コンストラクタの中で。パラメータ値を問い合わせることができるグローバル設定オブジェクトはありません。これは依存性注入の原則に反しています。 +コード内、例えばクラス内で、任意のパラメータの値を知る必要がある場合は、そのクラスに渡します。例えばコンストラクタで。パラメータの値をクラスが問い合わせるような、設定を表すグローバルオブジェクトは存在しません。それは依存関係注入の原則に反します。 -サービス .[#toc-services] -===================== +サービス +==== -[別章 |services]参照。 +[別の章を参照|services]。 -デコレーター .[#toc-decorator] -======================== +Decorator +========= -特定のタイプの全サービスを一括編集するには?特定の共通の祖先から継承したすべてのプレゼンターに対して、あるメソッドを呼び出す必要がありますか?そこで登場するのがデコレータです。 +特定の型のすべてのサービスを一括して変更するにはどうすればよいでしょうか?例えば、特定の共通の親クラスを継承するすべてのPresenterで特定のメソッドを呼び出すには?そのためにデコレータがあります。 ```neon decorator: - # for all services that are instances of this class or interface - App\Presenters\BasePresenter: + # このクラスまたはインターフェースのインスタンスであるすべてのサービスに対して + App\Presentation\BasePresenter: setup: - - setProjectId(10) # call this method - - $absoluteUrls = true # and set the variable + - setProjectId(10) # このメソッドを呼び出す + - $absoluteUrls = true # そして変数を設定する ``` -デコレータは、[タグを |services#Tags]設定したり、[インジェクトモードを |services#Inject Mode]オンにしたりするのにも使用できます。 +デコレータは、[タグ |services#タグ]の設定や[injectモード |services#Injectモード]の有効化にも使用できます。 ```neon decorator: @@ -90,63 +91,75 @@ DIコンテナの技術設定。 ```neon di: - # shows DIC in Tracy Bar? - debugger: ... # (bool) defaults to true + # Tracy Bar に DIC を表示しますか? + debugger: ... # (bool) デフォルトは true - # parameter types that you never autowire + # 決してオートワイヤリングしないパラメータの型 excluded: ... # (string[]) - # the class from which the DI container inherits - parentClass: ... # (string) defaults to Nette\DI\Container + # サービスの遅延生成を許可しますか? + lazy: ... # (bool) デフォルトは false + + # DIコンテナが継承するクラス + parentClass: ... # (string) デフォルトは Nette\DI\Container ``` -メタデータエクスポート .[#toc-metadata-export] ------------------------------------ +遅延サービス .{data-version:3.2.4} +---------------------------- + +`lazy: true` 設定は、サービスの遅延(遅延)生成を有効にします。これは、サービスがDIコンテナから要求された瞬間に実際に作成されるのではなく、初回使用時に作成されることを意味します。これにより、特定のリクエストで実際に必要なサービスのみが作成されるため、アプリケーションの起動が高速化され、メモリ要件が削減される可能性があります。 + +特定のサービスに対して、遅延生成は[変更できます |services#遅延サービス]。 + +.[note] +遅延オブジェクトは、ユーザー定義クラスにのみ使用でき、PHP内部クラスには使用できません。PHP 8.4以降が必要です。 + -DIコンテナクラスには、多くのメタデータも含まれています。メタデータのエクスポートを減らすことで、それを軽減することができます。 +メタデータのエクスポート +------------ + +DIコンテナクラスには多くのメタデータも含まれています。メタデータのエクスポートを削減することで、そのサイズを小さくすることができます。 ```neon di: export: - # to export parameters? - parameters: false # (bool) defaults to true + # パラメータをエクスポートしますか? + parameters: false # (bool) デフォルトは true - # export tags and which ones? - tags: # (string[]|bool) the default is all + # タグをエクスポートしますか?どのタグを? + tags: # (string[]|bool) デフォルトはすべて - event.subscriber - # export data for autowiring and which? - types: # (string[]|bool) the default is all + # オートワイヤリング用のデータをエクスポートしますか?どのデータを? + types: # (string[]|bool) デフォルトはすべて - Nette\Database\Connection - Symfony\Component\Console\Application ``` -`$container->parameters` 配列を使用しない場合は、パラメータのエクスポートを無効にすることができます。さらに、`$container->findByTag(...)` メソッドを使って、サービスを取得するタグだけをエクスポートすることができます。 -このメソッドを全く呼び出さない場合は、`false` を使ってタグのエクスポートを完全に無効にすることができます。 +`$container->getParameters()` 配列を使用しない場合は、パラメータのエクスポートを無効にできます。さらに、`$container->findByTag(...)` メソッドでサービスを取得するために使用するタグのみをエクスポートできます。このメソッドをまったく呼び出さない場合は、`false` を使用してタグのエクスポートを完全に無効にできます。 -`$container->getByType()` メソッドのパラメータとして使用するクラスを指定すれば、[自動配線の |autowiring]ためのメタデータを大幅に減らすことができます。 -そしてまた、もしあなたがこのメソッドを全く呼ばないのであれば(あるいは[bootstrapの |application:en:bootstrap]中だけで`Nette\Application\Application`)、`false` で完全にエクスポートを無効にすることができます。 +`$container->getByType()` メソッドのパラメータとして使用するクラスを指定することで、[オートワイヤリング |autowiring]用のメタデータを大幅に削減できます。そして再び、このメソッドをまったく呼び出さない場合(または[bootstrap|application:bootstrapping]で `Nette\Application\Application` を取得するためだけに使用する場合)、`false` を使用してエクスポートを完全に無効にできます。 -拡張機能 .[#toc-extensions] -======================= +拡張機能 +==== -他のDI拡張を登録する。このようにして、例えばDI拡張`Dibi\Bridges\Nette\DibiExtension22` を`dibi` という名前で追加します。 +追加のDI拡張機能を登録します。この方法で、例えば `Dibi\Bridges\Nette\DibiExtension22` DI拡張機能を `dibi` という名前で追加します。 ```neon extensions: dibi: Dibi\Bridges\Nette\DibiExtension22 ``` -そして、その中の`dibi` というセクションで設定します。 +その後、`dibi` セクションで設定します: ```neon dibi: host: localhost ``` -また、パラメータを持つ拡張クラスを追加することができます。 +パラメータを持つクラスを拡張機能として追加することもできます: ```neon extensions: @@ -154,10 +167,10 @@ extensions: ``` -ファイルのインクルード .[#toc-including-files] -=================================== +ファイルのインクルード +=========== -`includes` セクションに、追加の設定ファイルを挿入することができます。 +`includes` セクションで他の設定ファイルをインクルードできます: ```neon includes: @@ -166,7 +179,7 @@ includes: - presenters.neon ``` -`parameters.php` という名前はタイプミスではありません。設定は PHP ファイルに書くこともでき、その場合は配列として返されます。 +`parameters.php` という名前はタイプミスではありません。設定はPHPファイルで記述することもでき、そのファイルは配列として設定を返します: ```php <?php @@ -179,78 +192,74 @@ return [ ]; ``` -同じキーを持つ項目が設定ファイル内に現れた場合、それらは[上書きされるか、 |#Merging]配列の場合はされます。後からインクルードされたファイルは、前のファイルより優先順位が高くなります。`includes` セクションが記載されているファイルは、それに含まれるファイルよりも優先順位が高くなります。 +設定ファイル内で同じキーを持つ要素が現れた場合、それらは上書きされるか、[配列の場合はマージされます |#マージ]。後からインクルードされるファイルは、前のファイルよりも高い優先度を持ちます。`includes` セクションが記載されているファイルは、その中でインクルードされるファイルよりも高い優先度を持ちます。 -検索 .[#toc-search] -================= +Search +====== -DIコンテナにサービスを自動追加することで、非常に快適に作業ができます。Netteは自動的にPresenterをコンテナに追加しますが、それ以外のクラスも簡単に追加することができます。 +DIコンテナへのサービスの自動追加は、作業を非常に快適にします。NetteはPresenterを自動的にコンテナに追加しますが、他のクラスも簡単に追加できます。 -どのディレクトリ(およびサブディレクトリ)でクラスを検索するかを指定するだけです。 +クラスを検索するディレクトリ(およびサブディレクトリ)を指定するだけです: ```neon search: - # you choose the section names yourself - myForms: - in: %appDir%/Forms - - model: - in: %appDir%/Model + - in: %appDir%/Forms + - in: %appDir%/Model ``` -しかし、通常は、すべてのクラスとインターフェイスを追加したくないので、フィルタリングすることができます。 +通常、すべてのクラスとインターフェースを追加したいわけではないため、それらをフィルタリングできます: ```neon search: - myForms: - in: %appDir%/Forms + - in: %appDir%/Forms - # filtering by file name (string|string[]) + # ファイル名によるフィルタリング (string|string[]) files: - *Factory.php - # filtering by class name (string|string[]) + # クラス名によるフィルタリング (string|string[]) classes: - *Factory ``` -あるいは、以下のクラスのうち少なくともひとつを継承するか実装しているクラスを選択することもできます。 +または、指定されたクラスの少なくとも1つを継承または実装するクラスを選択することもできます: ```neon search: - myForms: + - in: %appDir% extends: - App\*Form implements: - App\*FormInterface ``` -また、クラス名のマスクや祖先など、否定的なルールを定義することもでき、それらに従った場合、そのサービスはDIコンテナに追加されなくなります。 +除外ルール、つまりクラス名のマスクや継承元の親クラスを定義することもできます。これらが一致する場合、サービスはDIコンテナに追加されません: ```neon search: - myForms: + - in: %appDir% exclude: + files: ... classes: ... extends: ... implements: ... ``` -追加されたサービスにはタグを設定することができます。 +すべてのサービスにタグを設定できます: ```neon search: - myForms: + - in: %appDir% tags: ... ``` -マージ .[#toc-merging] -=================== +マージ +=== -同じキーを持つ項目が複数の設定ファイルに存在する場合、それらは上書きされるか、配列の場合はマージされます。後で組み込まれたファイルの方が優先されます。 +複数の設定ファイルで同じキーを持つ要素が現れた場合、それらは上書きされるか、配列の場合はマージされます。後からインクルードされるファイルは、前のファイルよりも高い優先度を持ちます。 <table class=table> <tr> @@ -283,7 +292,7 @@ items: </tr> </table> -特定の配列のマージを防ぐには、配列名の直後にエクスクラメーションマークを付けます。 +配列の場合、キー名の後に感嘆符を付けることでマージを防ぐことができます: <table class=table> <tr> @@ -314,5 +323,4 @@ items: </tr> </table> - -{{maintitle:依存性注入の設定}} +{{maintitle: 依存性注入の設定}} diff --git a/dependency-injection/ja/container.texy b/dependency-injection/ja/container.texy index 6968e61551..97a2692df3 100644 --- a/dependency-injection/ja/container.texy +++ b/dependency-injection/ja/container.texy @@ -2,15 +2,15 @@ DIコンテナとは? ********* .[perex] -依存性注入コンテナ(DIC)とは、オブジェクトのインスタンス化と設定を行うことができるクラスです。 +依存性注入コンテナ(DIC)は、オブジェクトのインスタンス化と設定を行うクラスです。 -意外かもしれませんが、多くの場合、依存性注入(Dependency Injection、略してDI)を利用するために、依存性注入コンテナは必要ありません。なにしろ、[前の章でも |introduction]DIの具体例を示しましたが、コンテナは必要なかったのですから。 +驚かれるかもしれませんが、多くの場合、依存性注入(略してDI)の利点を活用するために依存性注入コンテナは必要ありません。[導入章|introduction]でも、DIの具体例を示しましたが、コンテナは必要ありませんでした。 -しかし、多数の異なるオブジェクトを多くの依存関係で管理する必要がある場合、依存性注入コンテナは本当に便利です。おそらく、フレームワーク上で構築されたWebアプリケーションの場合がそうでしょう。 +しかし、多くの依存関係を持つ大量の異なるオブジェクトを管理する必要がある場合、依存性注入コンテナは本当に便利です。これは、フレームワーク上に構築されたWebアプリケーションの場合などです。 -前の章では、`Article` と`UserController` というクラスを紹介しました。この2つのクラスは、データベースとファクトリ`ArticleFactory` という依存性を持っています。そして、これらのクラスのために、これからコンテナを作成します。もちろん、このような単純な例では、コンテナを作成する意味はありません。しかし、どのように見えるか、どのように動作するかを示すために、コンテナを作成します。 +前の章で、`Article` と `UserController` クラスを紹介しました。どちらもデータベースと `ArticleFactory` ファクトリという依存関係を持っています。そして、これらのクラスのためにコンテナを作成します。もちろん、このような簡単な例ではコンテナを持つ意味はありません。しかし、それがどのように見え、機能するかを示すために作成します。 -以下は、上記の例のためにハードコードされた簡単なコンテナです。 +以下は、上記の例のための簡単なハードコードされたコンテナです: ```php class Container @@ -32,16 +32,16 @@ class Container } ``` -使い方は次のようになります。 +使用法は次のようになります: ```php $container = new Container; $controller = $container->createUserController(); ``` -コンテナにオブジェクトを要求するだけで、オブジェクトの作成方法や依存関係については何も知る必要はない。コンテナがすべてを知っているのだ。依存関係はコンテナが自動的に注入してくれる。それがコンテナの力です。 +コンテナにオブジェクトを問い合わせるだけで、それをどのように作成するか、どのような依存関係を持っているかを知る必要はありません。コンテナがすべてを知っています。依存関係はコンテナによって自動的に注入されます。これがその強みです。 -ここまでは、コンテナがすべてをハードコードしています。そこで次のステップでは、コンテナを本当に便利なものにするためのパラメータを追加します。 +コンテナにはまだすべてのデータがハードコードされています。そこで、次のステップとしてパラメータを追加し、コンテナを本当に便利にします: ```php class Container @@ -70,9 +70,9 @@ $container = new Container([ ]); ``` -勘のいい読者なら、ある問題にお気づきかもしれません。オブジェクト`UserController` を取得するたびに、新しいインスタンス`ArticleFactory` とデータベースも作成されるのです。それは絶対に避けたい。 +鋭い読者は特定の問題に気付いたかもしれません。`UserController` オブジェクトを取得するたびに、新しい `ArticleFactory` インスタンスとデータベースも作成されます。これは絶対に望ましくありません。 -そこで、同じインスタンスを何度も何度も返すメソッド`getService()` を追加する。 +そこで、常に同じインスタンスを返す `getService()` メソッドを追加します: ```php class Container @@ -87,7 +87,7 @@ class Container public function getService(string $name): object { if (!isset($this->services[$name])) { - // getService('Database') calls createDatabase() + // getService('Database') は createDatabase() を呼び出します $method = 'create' . $name; $this->services[$name] = $this->$method(); } @@ -98,9 +98,9 @@ class Container } ``` -例えば`$container->getService('Database')` を最初に呼び出すと、`createDatabase()` がデータベースオブジェクトを生成し、それを配列`$services` に格納して、次の呼び出しでそれを直接返します。 +例えば `$container->getService('Database')` を初めて呼び出すと、`createDatabase()` にデータベースオブジェクトを作成させ、それを `$services` 配列に保存し、次回の呼び出しではそのまま返します。 -また、コンテナの残りの部分を変更して、`getService()` を使用するようにします。 +コンテナの残りの部分も `getService()` を使用するように修正します: ```php class Container @@ -119,9 +119,9 @@ class Container } ``` -ところで、サービスという言葉は、コンテナによって管理されるあらゆるオブジェクトを指します。そのため、メソッド名は`getService()` 。 +ちなみに、サービスという用語は、コンテナによって管理される任意のオブジェクトを指します。そのため、メソッド名も `getService()` です。 -完了です。これで、完全に機能するDIコンテナが完成しました。これでDIコンテナが完成です!早速使ってみましょう。 +完了です。完全に機能するDIコンテナができました!そして、それを使用できます: ```php $container = new Container([ @@ -134,9 +134,9 @@ $controller = $container->getService('UserController'); $database = $container->getService('Database'); ``` -見てわかるように、DICを書くのは難しいことではありません。注目すべきは、オブジェクト自身はコンテナがそれらを作成していることを知らないということです。したがって、この方法で PHP の任意のオブジェクトを、そのソースコードに影響を与えずに作成することが可能です。 +ご覧のとおり、DICを書くことは複雑ではありません。オブジェクト自体は、何らかのコンテナによって作成されていることを知らないという点を思い出す価値があります。その結果、PHPの任意のオブジェクトを、そのソースコードに介入することなくこのように作成することが可能です。 -手動でコンテナクラスを作成し、維持することは、むしろすぐに悪夢となります。そこで、次の章では、ほぼ自動的に生成・更新できる[Nette DI Container |nette-container] について説明します。 +コンテナクラスの手動での作成とメンテナンスは、かなり早く悪夢になる可能性があります。したがって、次の章では、ほぼ自動的に生成および更新できる[Nette DIコンテナ|nette-container]について話します。 -{{maintitle:Dependency Injection Containerとは?} +{{maintitle: 依存性注入コンテナとは?}} diff --git a/dependency-injection/ja/extensions.texy b/dependency-injection/ja/extensions.texy index beccfd35c0..c86b993487 100644 --- a/dependency-injection/ja/extensions.texy +++ b/dependency-injection/ja/extensions.texy @@ -1,17 +1,17 @@ -ネッテDIの拡張機能作成 -************ +Nette DIの拡張機能の作成 +**************** .[perex] -設定ファイルに加えてDIコンテナを生成することは、いわゆる*extensions*にも影響します。`extensions` セクションの設定ファイルでそれらを有効にします。 +DIコンテナの生成は、設定ファイルに加えて、いわゆる*拡張機能*によっても影響を受けます。これらは、設定ファイルの `extensions` セクションで有効化します。 -これは、クラス`BlogExtension` で表される拡張子を、名前`blog` で追加する方法です。 +このようにして、`BlogExtension` クラスによって表現される拡張機能を `blog` という名前で追加します: ```neon extensions: blog: BlogExtension ``` -各コンパイラ拡張は[api:Nette\DI\CompilerExtension] を継承し、DIコンパイル時に呼び出される以下のメソッドを実装することができます。 +各コンパイラ拡張機能は [api:Nette\DI\CompilerExtension] を継承し、DIコンテナのビルド中に順次呼び出される以下のメソッドを実装できます: 1. getConfigSchema() 2. loadConfiguration() @@ -22,18 +22,18 @@ extensions: getConfigSchema() .[method] =========================== -このメソッドが最初に呼び出されます。これは、設定パラメータの検証に使用するスキーマを定義します。 +このメソッドが最初に呼び出されます。設定パラメータの検証のためのスキーマを定義します。 -拡張機能は、拡張機能が追加されたセクションと同じ名前のセクションで設定されます。例:`blog`. +拡張機能は、拡張機能が追加された名前と同じ名前のセクション、つまり `blog` で設定します: ```neon -# same name as my extension +# extension と同じ名前 blog: postsPerPage: 10 - comments: false + allowComments: false ``` -すべての設定オプションについて、その型、受け入れられる値、そして場合によってはデフォルト値などを記述したスキーマを定義する予定です。 +すべての設定オプション、それらの型、許可された値、そして場合によってはデフォルト値を含むスキーマを作成します: ```php use Nette\Schema\Expect; @@ -50,9 +50,9 @@ class BlogExtension extends Nette\DI\CompilerExtension } ``` -ドキュメントは[スキーマを |schema:en]参照してください。さらに、どのオプションが[ダイナミックに |application:en:bootstrap#Dynamic Parameters]動作できるかは、`dynamic()` 、例えば`Expect::int()->dynamic()` を使って指定することができます。 +ドキュメントは [スキーマ |schema:] ページにあります。さらに、`dynamic()` を使用してどのオプションが[動的 |application:bootstrapping#動的パラメータ]であるかを指定できます。例:`Expect::int()->dynamic()`。 -設定には、`$this->config` 、オブジェクトである`stdClass` を通してアクセスします。 +設定には、`stdClass` オブジェクトである `$this->config` 変数を通じてアクセスします: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -71,7 +71,7 @@ class BlogExtension extends Nette\DI\CompilerExtension loadConfiguration() .[method] ============================= -このメソッドは、コンテナにサービスを追加するために使用されます。これは、[api:Nette\DI\ContainerBuilder] によって行われます。 +コンテナにサービスを追加するために使用されます。これには [api:Nette\DI\ContainerBuilder] を使用します: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -80,25 +80,25 @@ class BlogExtension extends Nette\DI\CompilerExtension { $builder = $this->getContainerBuilder(); $builder->addDefinition($this->prefix('articles')) - ->setFactory(App\Model\HomepageArticles::class, ['@connection']) // or setCreator() + ->setFactory(App\Model\HomepageArticles::class, ['@connection']) // または setCreator() ->addSetup('setLogger', ['@logger']); } } ``` -名前の衝突が起こらないように、拡張機能によって追加されるサービスにはその名前をプレフィックスとして付けるという慣例があります。これは`prefix()` によって行われます。したがって、拡張機能が 'blog' と呼ばれている場合、そのサービスは`blog.articles` と呼ばれることになります。 +慣習として、拡張機能によって追加されたサービスには、名前の衝突が発生しないように、その名前でプレフィックスを付けます。これは `prefix()` メソッドが行うため、拡張機能の名前が `blog` であれば、サービスは `blog.articles` という名前を持ちます。 -サービスの名前を変更する必要がある場合、後方互換性を維持するために、元の名前でエイリアスを作成することができます。同様に、これはNetteが例えば`routing.router` に対して行っていることで、`router` という以前の名前でも利用可能です。 +サービスの名前を変更する必要がある場合、後方互換性を維持するために、元の名前でエイリアスを作成できます。Netteは、例えば `routing.router` サービスで同様のことを行っています。これは以前の名前 `router` でも利用可能です。 ```php $builder->addAlias('router', 'routing.router'); ``` -ファイルからサービスを取得する .[#toc-retrieve-services-from-a-file] ------------------------------------------------------ +ファイルからのサービスのロード +--------------- -ContainerBuilder API を使ってサービスを作成することもできますが、おなじみの NEON 設定ファイルとその`services` セクションを使ってサービスを追加することもできます。`@extension` という接頭辞は、現在の拡張子を表します。 +サービスは、ContainerBuilderクラスのAPIを使用して作成するだけでなく、設定ファイルNEONのservicesセクションで使用されるよく知られた記法でも作成できます。プレフィックス `@extension` は現在の拡張機能を表します。 ```neon services: @@ -112,7 +112,7 @@ services: create: MyBlog\Components\ArticlesList(@extension.articles) ``` -この方法でサービスを追加していきます。 +サービスをロードします: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -121,7 +121,7 @@ class BlogExtension extends Nette\DI\CompilerExtension { $builder = $this->getContainerBuilder(); - // load the configuration file for the extension + // 拡張機能の設定ファイルをロード $this->compiler->loadDefinitionsFromConfig( $this->loadFromFile(__DIR__ . '/blog.neon')['services'], ); @@ -133,7 +133,7 @@ class BlogExtension extends Nette\DI\CompilerExtension beforeCompile() .[method] ========================= -このメソッドは、コンテナにユーザー設定ファイルだけでなく、`loadConfiguration` メソッドで個々の拡張機能によって追加されたすべてのサービスが含まれているときに呼び出されます。この組み立ての段階で、次にサービス定義を修正したり、サービス間のリンクを追加したりすることができます。`findByTag()` メソッドでタグによるサービスの検索、`findByType()` メソッドでクラスやインターフェイスによる検索が可能です。 +このメソッドは、コンテナが `loadConfiguration` メソッドで個々の拡張機能によって追加されたすべてのサービス、およびユーザー設定ファイルによって追加されたすべてのサービスを含む時点で呼び出されます。したがって、ビルドのこの段階で、サービス定義を修正したり、それらの間の関連を補完したりできます。コンテナ内のサービスをタグで検索するには `findByTag()` メソッドを、クラスまたはインターフェースで検索するには `findByType()` メソッドを利用できます。 ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -153,7 +153,7 @@ class BlogExtension extends Nette\DI\CompilerExtension afterCompile() .[method] ======================== -この段階では、コンテナクラスはすでに[ClassType |php-generator:#classes]オブジェクトとして生成されており、サービスが作成するすべてのメソッドを含み、PHP ファイルとしてキャッシュできる状態になっています。この時点でも、生成されたクラスコードを編集することができます。 +この段階では、コンテナクラスはすでに [ClassType |php-generator:#クラス] オブジェクトの形式で生成されており、サービスを作成するすべてのメソッドを含み、キャッシュへの書き込み準備ができています。この時点で、結果のクラスコードをさらに修正できます。 ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -167,24 +167,24 @@ class BlogExtension extends Nette\DI\CompilerExtension ``` -$initialization .[wiki-method] -============================== +$initialization .[method] +========================= -Configurator は、[コンテナ作成 |application:en:bootstrap#index.php]後に初期化コードを呼び出します。このコードは、[メソッド addBody() |php-generator:#method-and-function-body] を使用してオブジェクト`$this->initialization` に書き込むことで作成されます。 +Configuratorクラスは、[コンテナ作成後 |application:bootstrapping#index.php] に初期化コードを呼び出します。これは、[メソッド addBody() |php-generator:#メソッドと関数の本体] を使用して `$this->initialization` オブジェクトに書き込むことによって作成されます。 -ここでは、初期化コードを用いて、セッションの開始や、`run` タグを持つサービスの開始を行う例を示します。 +例えば、初期化コードでセッションを開始したり、`run` タグを持つサービスを開始したりする方法の例を示します: ```php class BlogExtension extends Nette\DI\CompilerExtension { public function loadConfiguration() { - // automatic session startup + // セッションの自動開始 if ($this->config->session->autoStart) { $this->initialization->addBody('$this->getService("session")->start()'); } - // services with tag 'run' must be created after the container is instantiated + // run タグを持つサービスはコンテナのインスタンス化後に作成される必要がある $builder = $this->getContainerBuilder(); foreach ($builder->findByTag('run') as $name => $foo) { $this->initialization->addBody('$this->getService(?);', [$name]); diff --git a/dependency-injection/ja/factory.texy b/dependency-injection/ja/factory.texy index 726203ee1b..f3416962b4 100644 --- a/dependency-injection/ja/factory.texy +++ b/dependency-injection/ja/factory.texy @@ -1,12 +1,12 @@ -生成されたファクトリー -*********** +生成されたファクトリ +********** .[perex] -Nette DIは、インターフェイスを元にファクトリーコードを自動生成することができるので、コードを書く手間が省けます。 +Nette DIはインターフェースに基づいてファクトリコードを自動生成でき、コード記述の手間を省きます。 -ファクトリーは、オブジェクトを作成し、設定するクラスです。したがって、その依存関係も同様に渡します。ファクトリーメソッド*デザインパターンと混同しないように注意してください。 +ファクトリは、オブジェクトを製造し設定するクラスです。したがって、それらの依存関係も渡します。デザインパターンの*ファクトリメソッド*と混同しないでください。これはファクトリの特定の利用方法を説明するものであり、このトピックとは関係ありません。 -そんな工場の姿を[序章で |introduction#factory]紹介しました: +そのようなファクトリがどのように見えるかは、[導入章 |introduction#ファクトリ]で示しました: ```php class ArticleFactory @@ -23,7 +23,7 @@ class ArticleFactory } ``` -Nette DIはファクトリーコードを自動生成することができます。インターフェイスを作成するだけで、Nette DIが実装を生成してくれます。インターフェースは、`create` という名前のメソッドを1つだけ持ち、戻り値の型を宣言する必要があります。 +Nette DIはファクトリコードを自動生成できます。あなたがする必要があるのはインターフェースを作成することだけで、Nette DIが実装を生成します。インターフェースは、`create` という名前のメソッドを正確に1つ持ち、戻り値の型を宣言する必要があります: ```php interface ArticleFactory @@ -32,7 +32,7 @@ interface ArticleFactory } ``` -つまり、ファクトリー`ArticleFactory` は、オブジェクトを作成するメソッド`create` を持っています`Article` 。クラス`Article` は、たとえば次のようなものになるでしょう。 +つまり、`ArticleFactory` ファクトリには、`Article` オブジェクトを作成する `create` メソッドがあります。`Article` クラスは、例えば次のようになります: ```php class Article @@ -44,16 +44,16 @@ class Article } ``` -設定ファイルにファクトリーを追加します。 +ファクトリを設定ファイルに追加します: ```neon services: - ArticleFactory ``` -Nette DIは、対応するファクトリーの実装を生成します。 +Nette DIは対応するファクトリの実装を生成します。 -このように、ファクトリーを利用するコードでは、インターフェースでオブジェクトを要求し、生成された実装をネットDIが利用します。 +ファクトリを使用するコード内で、インターフェースに基づいてオブジェクトを要求し、Nette DIは生成された実装を使用します: ```php class UserController @@ -65,17 +65,17 @@ class UserController public function foo() { - // let the factory create an object + // ファクトリにオブジェクトを作成させる $article = $this->articleFactory->create(); } } ``` -パラメタライズドファクトリー .[#toc-parameterized-factory] -============================================ +パラメータ化されたファクトリ +============== -ファクトリーメソッド`create` はパラメータを受け取ることができ、それをコンストラクタに渡します。例えば、記事の著者IDをクラス`Article` に追加してみましょう。 +ファクトリメソッド `create` はパラメータを受け取ることができ、その後それらをコンストラクタに渡します。例えば、`Article` クラスに記事の著者IDを追加しましょう: ```php class Article @@ -88,7 +88,7 @@ class Article } ``` -また、ファクトリーにパラメータを追加します。 +パラメータをファクトリにも追加します: ```php interface ArticleFactory @@ -97,13 +97,13 @@ interface ArticleFactory } ``` -コンストラクタのパラメータとファクトリーのパラメータは同じ名前なので、ネットDIは自動的にこれらを渡します。 +コンストラクタのパラメータとファクトリのパラメータが同じ名前であるという理由で、Nette DIはそれらを完全に自動的に渡します。 -高度な定義 .[#toc-advanced-definition] -================================= +高度な定義 +===== -定義は、キー`implement` を使って複数行で記述することも可能です。 +定義は、`implement` キーを使用して複数行形式で記述することもできます: ```neon services: @@ -111,9 +111,9 @@ services: implement: ArticleFactory ``` -この長い書き方では,通常のサービスと同様に,コンストラクタの引数を`arguments` で,設定を`setup` で追加することが可能です. +この長い形式で記述する場合、通常のサービスと同様に、`arguments` キーでコンストラクタ用の追加の引数を指定し、`setup` で追加の設定を行うことが可能です。 -例:メソッド`create()` がパラメータ`$authorId` を受け付けない場合,コンストラクタ`Article` に渡される固定値を設定に指定することができる. +例:`create()` メソッドが `$authorId` パラメータを受け取らない場合、設定内で固定値を指定でき、それが `Article` のコンストラクタに渡されます: ```neon services: @@ -123,7 +123,7 @@ services: authorId: 123 ``` -あるいは逆に、`create()` がパラメータ`$authorId` を受け付けたが、それがコンストラクタの一部ではなく、メソッド`Article::setAuthorId()` から渡された場合、セクション`setup` でそれを参照することになる。 +または逆に、`create()` が `$authorId` パラメータを受け取るが、コンストラクタの一部ではなく、`Article::setAuthorId()` メソッドによって渡される場合、`setup` セクションでそれを参照します: ```neon services: @@ -134,15 +134,14 @@ services: ``` -アクセッサー .[#toc-accessor] -======================= +アクセサ +==== -Netteではファクトリーの他に、いわゆるアクセサーを生成することができます。アクセサーとは、DIコンテナから特定のサービスを返す`get()` メソッドを持つオブジェクトです。`get()` を複数回呼び出すと、常に同じインスタンスが返されます。 +Netteは、ファクトリに加えて、いわゆるアクセサも生成できます。これらは、DIコンテナから特定のサービスを返す `get()` メソッドを持つオブジェクトです。`get()` を繰り返し呼び出すと、常に同じインスタンスが返されます。 -アクセッサは、依存関係にレイジーローディングをもたらします。例えば、エラーを特殊なデータベースに記録するクラスがあるとします。もしデータベース接続をコンストラクタの依存関係として渡した場合、 接続は常に作成する必要がありますが、エラーが発生したときにしか使われないので、 接続はほとんど使われないままです。 -その代わりに、このクラスはアクセサを渡すことができ、その`get()` メソッドが呼ばれたときだけ、データベースオブジェクトが作成されます。 +アクセサは依存関係に遅延ロードを提供します。特別なデータベースにエラーを書き込むクラスを考えてみましょう。このクラスがデータベース接続をコンストラクタの依存関係として渡させていた場合、実際にはエラーは例外的にしか発生せず、したがってほとんどの場合、接続は未使用のままになるでしょうが、接続は常に作成される必要があったでしょう。 その代わりに、クラスはアクセサを渡し、その `get()` が呼び出されたときに初めてデータベースオブジェクトが作成されます: -アクセサを作るには?インターフェイスを書くだけで、ネットDIが実装を生成してくれます。インターフェイスは、`get` というメソッドを1つだけ持ち、戻り値の型を宣言しなければなりません。 +アクセサを作成するには?インターフェースを書くだけで、Nette DIが実装を生成します。インターフェースは、`get` という名前のメソッドを正確に1つ持ち、戻り値の型を宣言する必要があります: ```php interface PDOAccessor @@ -151,7 +150,7 @@ interface PDOAccessor } ``` -アクセッサが返すサービスの定義と一緒に、設定ファイルにアクセッサを追加してください。 +アクセサを設定ファイルに追加します。そこには、それが返すサービス定義も含まれます: ```neon services: @@ -159,62 +158,68 @@ services: - PDO(%dsn%, %user%, %password%) ``` -このアクセサは`PDO` というタイプのサービスを返します。 このようなサービスは設定に1つしかないので、アクセサはそれを返します。このタイプのサービスが複数設定されている場合、どのサービスを返すかをその名前を使って指定できます。たとえば、`- PDOAccessor(@db1)` 。 +なぜなら、アクセサは `PDO` 型のサービスを返し、設定にはそのようなサービスが1つしかないため、まさにそれを返します。その型のサービスが複数ある場合、返されるサービスを名前を使用して指定します。例:`- PDOAccessor(@db1)`。 -マルチファクトリー/アクセサー .[#toc-multifactory-accessor] -============================================= -今までのファクトリーとアクセッサは、1つのオブジェクトを作成するか返すだけでした。アクセッサと組み合わせたマルチファクトリーも作成することができます。このようなマルチファクトリークラスのインターフェイスは `create<name>()`と `get<name>()`と呼ばれる複数のメソッドで構成されます。 +複数ファクトリ/アクセサ +============ +私たちのファクトリとアクセサは、これまでは常に1つのオブジェクトしか製造または返せませんでした。しかし、アクセサと組み合わせた複数ファクトリを非常に簡単に作成できます。そのようなクラスのインターフェースは、`create<name>()` および `get<name>()` という名前の任意の数のメソッドを含むでしょう。例: ```php interface MultiFactory { function createArticle(): Article; - function createFoo(): Model\Foo; function getDb(): PDO; } ``` -生成された複数のファクトリーとアクセッサを渡す代わりに、複雑なマルチファクトリーを1つだけ渡すことができます。 +したがって、いくつかの生成されたファクトリとアクセサを渡す代わりに、より多くのことができる1つのより複雑なファクトリを渡します。 -あるいは、複数のメソッドの代わりに、パラメータで`create()` と`get()` を使うこともできます。 +あるいは、いくつかのメソッドの代わりにパラメータ付きの `get()` を使用できます: ```php interface MultiFactoryAlt { - function create($name); function get($name): PDO; } ``` -この場合、`MultiFactory::createArticle()` は`MultiFactoryAlt::create('article')` と同じことをします。 しかし、この代替構文にはいくつかの欠点があります。どの`$name` の値がサポートされているかは不明ですし、複数の異なる`$name` の値を使用する場合、インターフェースで戻り値の型を指定することができません。 +その場合、`MultiFactory::getArticle()` は `MultiFactoryAlt::get('article')` と同じことをするということが成り立ちます。しかしながら、代替の記法には、どの `$name` の値がサポートされているかが明らかではないという欠点があり、論理的にもインターフェースで異なる `$name` に対して異なる戻り値を区別することはできません。 -リストを使った定義 .[#toc-definition-with-a-list] ----------------------------------------- -マルチファクトリーを構成で定義する方法は?マルチファクトリーから返される3つのサービスと、マルチファクトリーそのものを作成してみましょう。 +リストによる定義 +-------- +この方法で、設定内で複数ファクトリを定義できます: .{data-version:3.2.0} + +```neon +services: + - MultiFactory( + article: Article # createArticle() を定義します + db: PDO(%dsn%, %user%, %password%) # getDb() を定義します + ) +``` + +または、ファクトリの定義内で、参照を使用して既存のサービスを参照できます: ```neon services: article: Article - - Model\Foo - PDO(%dsn%, %user%, %password%) - MultiFactory( - article: @article # createArticle() - foo: @Model\Foo # createFoo() - db: @\PDO # getDb() + article: @article # createArticle() を定義します + db: @\PDO # getDb() を定義します ) ``` -タグ付き定義 .[#toc-definition-with-tags] ------------------------------------ +タグによる定義 +------- -マルチファクトリーを定義するもう一つの方法は、[タグを |services#Tags]使うことです。 +2番目の選択肢は、定義に[タグ |services#タグ]を利用することです: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter - App\Model\DatabaseAccessor( db1: @database.db1.explorer ) diff --git a/dependency-injection/ja/faq.texy b/dependency-injection/ja/faq.texy index f8656dc7a5..2e8beb4533 100644 --- a/dependency-injection/ja/faq.texy +++ b/dependency-injection/ja/faq.texy @@ -1,98 +1,92 @@ -DIに関するFAQ -********* +DIに関するよくある質問(FAQ) +***************** -DIはIoCの別名なのか? .[#toc-is-di-another-name-for-ioc] ------------------------------------------------- +DIはIoCの別名ですか? +------------- -IoC(Inversion of Control)とは、コードの実行方法に着目した原則で、自分のコードが外部コードを起動するのか、自分のコードが外部コードに統合され、その外部コードがコードを呼び出すのか、ということです。 -IoCは、[イベント |nette:en:glossary#Events]、いわゆる[ハリウッドの原則などを |application:en:components#Hollywood style]含む広い概念である。 -[ルール3「ファクトリーに |introduction#Rule #3: Let the Factory Handle It]任せる」の一部であり、`new` 演算子の反転を表すファクトリーもこのコンセプトの構成要素です。 +*Inversion of Control*(IoC)は、コードがどのように実行されるかに焦点を当てた原則です - あなたのコードが外部のコードを実行するのか、それともあなたのコードが外部のコードに統合され、その後呼び出されるのか。 IoCは、[イベント |nette:glossary#イベント]、いわゆる[ハリウッド原則 |application:components#ハリウッドスタイル]、およびその他の側面を含む広範な概念です。 この概念の一部には、[ルールNo.3:ファクトリに任せる |introduction#ルール3 ファクトリに任せる]で説明されているファクトリも含まれ、これらは`new`演算子の逆転を表します。 -依存性注入*(DI)とは、あるオブジェクトが他のオブジェクトについてどのように知っているか、つまり依存関係についてです。これは、オブジェクト間で依存関係を明示的に受け渡すことを要求するデザインパターンです。 +*Dependency Injection*(DI)は、あるオブジェクトが別のオブジェクト、つまりその依存関係についてどのように知るかに焦点を当てています。これは、オブジェクト間で依存関係を明示的に渡すことを要求する設計パターンです。 -したがって、DIはIoCの特定の形態であると言える。しかし、IoCのすべての形態がコードピュリティの点で適しているわけではありません。例えば、アンチパターンの中には、[グローバルステートを |global state]扱う技法や、いわゆる[サービスロケーターも |#What is a Service Locator]全て含まれます。 +したがって、DIはIoCの特定の形式であると言えます。ただし、すべての形式のIoCがコードの純粋性の観点から適切であるわけではありません。たとえば、アンチパターンの中には、[グローバル状態 |global-state]を操作する手法や、いわゆる[サービスロケータ |#サービスロケータとは何ですか]があります。 -サービスロケーターとは? .[#toc-what-is-a-service-locator] ----------------------------------------------- +サービスロケータとは何ですか? +--------------- -サービスロケーターは、依存性注入の代替となるものです。利用可能なサービスや依存関係がすべて登録されている中央ストレージを作成することで機能します。オブジェクトが依存関係を必要とするとき、サービスロケータに要求します。 +これはDependency Injectionの代替案です。利用可能なすべてのサービスまたは依存関係が登録される中央リポジトリを作成することで機能します。オブジェクトが依存関係を必要とするとき、Service Locatorにそれを要求します。 -依存関係はオブジェクトに直接渡されるわけではないので、簡単に識別することができず、すべての接続を明らかにして理解するためにコードを調査する必要があります。また、モックオブジェクトをテスト対象オブジェクトに単純に渡すことができず、Service Locatorを経由する必要があるため、テストがより複雑になります。さらに、Service Locatorは、個々のオブジェクトがその存在を認識する必要があるため、コードの設計を混乱させます。これは、オブジェクトがDIコンテナを知らないDependency Injectionとは異なります。 +ただし、Dependency Injectionと比較して、透明性が失われます:依存関係はオブジェクトに直接渡されず、簡単には識別できないため、すべての関連性を明らかにし理解するにはコードを調査する必要があります。テストもより複雑になります。なぜなら、テスト対象のオブジェクトにモックオブジェクトを単純に渡すのではなく、Service Locatorを介して行う必要があるからです。さらに、Service Locatorはコードの設計を損ないます。個々のオブジェクトはその存在を知る必要があるため、オブジェクトがDIコンテナを認識しないDependency Injectionとは異なります。 -DIを使わない方が良い場合とは? .[#toc-when-is-it-better-not-to-use-di] --------------------------------------------------------- +DIを使用しない方が良い場合はいつですか? +--------------------- -Dependency Injectionデザインパターンを使用することに関して、既知の困難はありません。逆に、グローバルにアクセス可能な場所から依存関係を取得することは、Service Locatorを使用する場合と同様に、[多くの複雑さを |global-state]もたらします。 -そのため、常にDIを使用することが望ましいとされています。これは独断的なアプローチではなく、単に他に良い代替案が見つかっていないだけなのです。 +設計パターンDependency Injectionの使用に関連する既知の困難はありません。逆に、グローバルに利用可能な場所から依存関係を取得することは、[多くの合併症 |global-state]につながり、Service Locatorの使用も同様です。 したがって、常にDIを使用することが推奨されます。これは独断的なアプローチではなく、単により良い代替案が見つからなかったためです。 -しかし、オブジェクトを相互に受け渡しせず、グローバル空間から取得する場面もある。たとえば、コードをデバッグするときに、プログラムの特定の時点で変数値をダンプしたり、プログラムのある部分の継続時間を測定したり、メッセージをログに記録したりする必要がある場合です。 -このような場合、後でコードから削除される一時的な動作に関するものであれば、グローバルにアクセス可能なダンパ、ストップウォッチ、ロガーを使用することは正当です。これらのツールは、結局のところ、コードの設計に属するものではありません。 +それでも、オブジェクトを渡さずにグローバル空間から取得する特定の状況があります。たとえば、コードのデバッグ中に、プログラムの特定のポイントで変数の値を出力したり、プログラムの特定の部分の期間を測定したり、メッセージを記録したりする必要がある場合です。 このような場合、後でコードから削除される一時的なタスクである場合、グローバルに利用可能なダンパー、ストップウォッチ、またはロガーを利用することは正当です。これらのツールはコードの設計には属しません。 -DIを使うと欠点があるのでしょうか? .[#toc-does-using-di-have-its-drawbacks] ------------------------------------------------------------ +DIの使用には欠点がありますか? +---------------- -Dependency Injectionを使うと、コードの書き方が複雑になったり、パフォーマンスが低下したりするなどのデメリットはあるのでしょうか?DIに従ってコードを書き始めると、何を失うのでしょうか? +Dependency Injectionの使用には、たとえばコード記述の複雑さの増加やパフォーマンスの低下などの欠点がありますか? DIに従ってコードを書き始めたときに何を失いますか? -DIは、アプリケーションのパフォーマンスやメモリ要件に影響を与えることはありません。DIコンテナの性能が影響する場合もありますが、[ネットDIの | nette-container]場合、コンテナは純粋なPHPにコンパイルされているため、アプリケーション実行時のオーバーヘッドは実質ゼロです。 +DIはアプリケーションのパフォーマンスやメモリ要件に影響を与えません。DIコンテナのパフォーマンスが役割を果たす可能性がありますが、[Nette DI |nette-container]の場合、コンテナは純粋なPHPにコンパイルされるため、アプリケーション実行時のオーバーヘッドは基本的にゼロです。 -コードを書くとき、依存関係を受け入れるコンストラクタを作成する必要があります。以前は、この作業に時間がかかることもありましたが、最新のIDEと[コンストラクタのプロパティプロモーションの |https://blog.nette.org/en/php-8-0-complete-overview-of-news#toc-constructor-property-promotion]おかげで、今では数秒の問題で済むようになりました。Nette DIとPhpStormプラグインを使えば、数クリックで簡単にファクトリーを生成することができる。 -一方、シングルトンや静的アクセスポイントを書く必要はありません。 +コードを記述する際には、依存関係を受け入れるコンストラクタを作成する必要がある場合があります。以前は時間がかかる可能性がありましたが、最新のIDEと[コンストラクタプロパティプロモーション |https://blog.nette.org/en/php-8-0-complete-overview-of-news#toc-constructor-property-promotion]のおかげで、現在は数秒の問題です。ファクトリは、Nette DIとPhpStorm用プラグインを使用してマウスをクリックするだけで簡単に生成できます。 一方、シングルトンや静的アクセスポイントを記述する必要はなくなります。 -DIを使用して適切に設計されたアプリケーションは、シングルトンを使用したアプリケーションと比較して、短くも長くもないという結論に達することができる。依存関係を扱うコードの一部は、単に個々のクラスから抽出され、新しい場所、すなわちDIコンテナやファクトリーに移動されます。 +DIを使用する適切に設計されたアプリケーションは、シングルトンを使用するアプリケーションと比較して、短くも長くもないと結論付けることができます。依存関係を扱うコードの部分は、個々のクラスから抽出され、新しい場所、つまりDIコンテナとファクトリに移動されるだけです。 -レガシーアプリケーションをDIに書き換えるには? .[#toc-how-to-rewrite-a-legacy-application-to-di] --------------------------------------------------------------------------- +レガシーアプリケーションをDIに書き換える方法は? +------------------------- -レガシーアプリケーションからDependency Injectionへの移行は、特に大規模で複雑なアプリケーションの場合、困難なプロセスになることがあります。このプロセスには、体系的にアプローチすることが重要です。 +レガシーアプリケーションからDependency Injectionへの移行は、特に大規模で複雑なアプリケーションの場合、困難なプロセスになる可能性があります。このプロセスに体系的にアプローチすることが重要です。 -- Dependency Injectionに移行する場合、チームメンバー全員が使用する原則と実践方法を理解することが重要である。 -- まず、既存のアプリケーションを分析し、主要なコンポーネントとその依存関係を特定します。どの部分をどのような順序でリファクタリングするか、計画を作成する。 -- DIコンテナを実装するか、Nette DIなどの既存のライブラリを使用する。 -- Dependency Injectionを使用するために、アプリケーションの各部を徐々にリファクタリングする。これは、コンストラクタやメソッドを修正して、依存関係をパラメータとして受け取るようにすることを含むかもしれません。 -- 依存性オブジェクトが作成されるコードの場所を変更し、コンテナによって依存性が注入されるようにする。これには、ファクトリーの使用が含まれる場合があります。 +- Dependency Injectionに移行する際には、チームのすべてのメンバーが使用される原則と手順を理解することが重要です。 +- まず、既存のアプリケーションの分析を実行し、主要なコンポーネントとその依存関係を特定します。どの部分をリファクタリングし、どの順序で行うかの計画を作成します。 +- DIコンテナを実装するか、さらに良いのは、Nette DIなどの既存のライブラリを使用することです。 +- Dependency Injectionを使用するように、アプリケーションの個々の部分を徐々にリファクタリングします。これには、依存関係をパラメータとして受け入れるようにコンストラクタまたはメソッドを変更することが含まれる場合があります。 +- 依存関係を持つオブジェクトが作成されるコード内の場所を変更して、代わりに依存関係がコンテナによって注入されるようにします。これにはファクトリの使用が含まれる場合があります。 -Dependency Injectionへの移行は、コードの品質とアプリケーションの長期的な持続可能性への投資であることを忘れないでください。これらの変更を行うのは難しいかもしれませんが、その結果、よりクリーンで、よりモジュール化され、将来の拡張やメンテナンスに対応できる、テストしやすいコードができるはずです。 +Dependency Injectionへの移行は、コードの品質とアプリケーションの長期的な保守性への投資であることを忘れないでください。これらの変更を行うのは困難な場合がありますが、結果は、将来の拡張と保守に対応できる、よりクリーンで、よりモジュール化され、テストしやすいコードになるはずです。 -なぜコンポジションが継承より好まれるのか? .[#toc-why-composition-is-preferred-over-inheritance] ---------------------------------------------------------------------------- -継承ではなく、コンポジションを使用することが望ましい。あるコードを変更すると、他の依存するコードも変更しなければならなくなるという心配がないため、より緩やかな結合が可能になります。典型的な例として、「[コンストラクタ地獄 |passing-dependencies#Constructor hell]」と呼ばれる状況があります。 +なぜ継承よりもコンポジションが優先されるのですか? +------------------------- +変更の影響を心配することなくコードを再利用するために、[継承 |nette:introduction-to-object-oriented-programming#コンポジション]の代わりに[コンポジション |nette:introduction-to-object-oriented-programming#継承]を使用する方が適切です。したがって、あるコードの変更が他の依存コードの変更を必要とすることを心配する必要がない、より緩やかな結合を提供します。典型的な例は、[コンストラクタ地獄 |passing-dependencies#コンストラクタ地獄]と呼ばれる状況です。 -ネッテDIコンテナは、ネッテ以外でも使えるのですか? .[#toc-can-nette-di-container-be-used-outside-of-nette] ----------------------------------------------------------------------------------- +Nette DIコンテナをNette以外で使用できますか? +----------------------------- -もちろんです。Nette DIコンテナはNetteの一部ですが、フレームワークの他の部分から独立して使用できるスタンドアロンライブラリとして設計されています。Composerを使ってインストールし、サービスを定義する設定ファイルを作成し、数行のPHPコードでDIコンテナを作成するだけです。 -そして、すぐにあなたのプロジェクトで依存性注入を活用し始めることができます。 +もちろんです。Nette DIコンテナはNetteの一部ですが、フレームワークの他の部分から独立して使用できるスタンドアロンライブラリとして設計されています。Composerを使用してインストールし、サービスを定義する設定ファイルを作成し、数行のPHPコードを使用してDIコンテナを作成するだけです。 そして、すぐにプロジェクトでDependency Injectionの利点を活用し始めることができます。 -[ネットDIコンテナ |nette-container]編では、具体的な使用例がどのようなものか、コードも含めて解説しています。 +コードを含む具体的な使用方法は、[Nette DIコンテナ |nette-container]の章で説明されています。 -なぜNEONファイルに設定があるのですか? .[#toc-why-is-the-configuration-in-neon-files] --------------------------------------------------------------------- +なぜ設定はNEONファイルにあるのですか? +--------------------- -NEONは、アプリケーション、サービス、およびそれらの依存関係を設定するために、Nette内で開発されたシンプルで読みやすい設定言語です。JSONやYAMLと比較して、より直感的で柔軟なオプションを提供することができます。NEONでは、SymfonyやYAMLではまったく、あるいは複雑な記述でしか書けないようなバインディングを自然に記述することができます。 +NEONは、アプリケーション、サービス、およびそれらの依存関係を設定するためにNette内で開発された、シンプルで読みやすい設定言語です。JSONやYAMLと比較して、この目的のためにはるかに直感的で柔軟なオプションを提供します。NEONでは、Symfony&YAMLではまったく記述できないか、複雑な記述によってのみ記述できる関連性を自然に記述できます。 -NEONファイルの解析はアプリケーションの速度を低下させますか? .[#toc-does-parsing-neon-files-slow-down-the-application] ------------------------------------------------------------------------------------------- +NEONファイルの解析はアプリケーションを遅くしませんか? +----------------------------- -NEONファイルは非常に高速に解析されますが、この点はあまり重要ではありません。なぜなら、ファイルの解析はアプリケーションの最初の起動時に1回だけ行われるからです。その後、DIコンテナコードが生成され、ディスクに保存され、その後のリクエストごとに実行されるため、さらなる解析は必要ありません。 +NEONファイルは非常に高速に解析されますが、この点はまったく重要ではありません。理由は、ファイルの解析はアプリケーションの初回実行時に一度だけ行われるためです。その後、DIコンテナのコードが生成され、ディスクに保存され、それ以降のリクエストごとに実行され、追加の解析を行う必要はありません。 -本番環境ではこのように動作します。開発中は、NEONファイルの内容が変わるたびに解析され、開発者は常に最新のDIコンテナを手に入れることができます。前述したように、実際のパースは一瞬で終わります。 +これは本番環境での動作方法です。開発中は、開発者が常に最新のDIコンテナを持つように、内容が変更されるたびにNEONファイルが解析されます。前述のように、解析自体は一瞬の問題です。 -クラス内で設定ファイルからパラメータにアクセスするには? .[#toc-how-do-i-access-the-parameters-from-the-configuration-file-in-my-class] ------------------------------------------------------------------------------------------------------------ +自分のクラスから設定ファイル内のパラメータにアクセスするにはどうすればよいですか? +----------------------------------------- -[ルールその1: 渡されるように |introduction#Rule #1: Let It Be Passed to You]する」を覚えておきましょう。クラスが設定ファイルからの情報を必要とする場合、その情報にアクセスする方法を考える必要はなく、代わりに、例えばクラスのコンストラクタを通して、単に情報を求めます。そして、設定ファイルの中で受け渡しを行います。 +[ルールNo.1:渡してもらう |introduction#ルール1 渡してもらう]を覚えておきましょう。クラスが設定ファイルからの情報を必要とする場合、その情報にアクセスする方法を考える必要はありません。代わりに、単純にそれを要求します - たとえば、クラスのコンストラクタを介して。そして、設定ファイルで受け渡しを行います。 -この例では、`%myParameter%` は`myParameter` パラメータの値のプレースホルダで、`MyClass` コンストラクタに渡される。 +この例では、`%myParameter%`はパラメータ`myParameter`の値のプレースホルダであり、クラス`MyClass`のコンストラクタに渡されます: ```php # config.neon @@ -103,10 +97,10 @@ services: - MyClass(%myParameter%) ``` -複数のパラメータを渡したり、自動配線を使用する場合は、[パラメータをオブジェクトで囲むと |best-practices:en:passing-settings-to-presenters]便利です。 +複数のパラメータを渡す場合やautowiringを利用したい場合は、[パラメータをオブジェクトにラップする |best-practices:passing-settings-to-presenters]ことをお勧めします。 -ネッテはPSR-11コンテナインターフェースに対応していますか? .[#toc-does-nette-support-psr-11-container-interface] --------------------------------------------------------------------------------------- +NetteはPSR-11: Container interfaceをサポートしていますか? +--------------------------------------------- -Nette DI Containerは、PSR-11を直接サポートしていません。しかし、Nette DI ContainerとPSR-11 Container Interfaceを期待するライブラリやフレームワークとの相互運用性が必要な場合は、Nette DI ContainerとPSR-11の橋渡しとなる[簡易アダプタを |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f]作成することができます。 +Nette DIコンテナはPSR-11を直接サポートしていません。ただし、Nette DIコンテナとPSR-11 Container Interfaceを期待するライブラリまたはフレームワークとの間で相互運用性が必要な場合は、Nette DIコンテナとPSR-11の間のブリッジとして機能する[単純なアダプタ |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f]を作成できます。 diff --git a/dependency-injection/ja/global-state.texy b/dependency-injection/ja/global-state.texy index 7622def46d..a4ff9c52ef 100644 --- a/dependency-injection/ja/global-state.texy +++ b/dependency-injection/ja/global-state.texy @@ -1,67 +1,63 @@ -グローバルステートとシングルトン -**************** +グローバル状態とシングルトン +************** .[perex] -警告以下の構文は、コードの設計が不十分な場合に見られる症状です: +警告:以下の構造は、設計の悪いコードの兆候です: --`Foo::getInstance()` --`DB::insert(...)` --`Article::setDb($db)` --`ClassName::$var` または`static::$var` +- `Foo::getInstance()` +- `DB::insert(...)` +- `Article::setDb($db)` +- `ClassName::$var` または `static::$var` -あなたのコードにこのような構文はありませんか?もしそうなら、改善するチャンスです。これらはよくある構成で、さまざまなライブラリやフレームワークのサンプル・ソリューションでよく見かけるものだと思うかもしれません。もしそうなら、そのコード設計には欠陥があります。 +これらの構造のいずれかがあなたのコードに存在しますか? それなら、それを改善する機会があります。これらは、さまざまなライブラリやフレームワークのサンプルソリューションでも見られる一般的な構造だと思うかもしれません。もしそうなら、それらのコードの設計は良くありません。 -ここでは学術的な純度の話をしているのではない。これらのコンストラクトに共通しているのは、グローバル・ステートを利用しているということだ。そしてこれは、コードの品質に破壊的な影響を与える。クラスは依存関係をごまかす。コードは予測不可能になる。開発者を混乱させ、効率を低下させる。 +ここでは、学術的な純粋さについて話しているのではありません。これらの構造はすべて、共通点が1つあります:グローバル状態を利用しています。そして、それはコードの品質に破壊的な影響を与えます。クラスはその依存関係について嘘をつきます。コードは予測不可能になります。プログラマーを混乱させ、効率を低下させます。 -この章では、なぜそうなるのか、そしてグローバル・ステートを回避する方法を説明します。 +この章では、なぜそうなるのか、そしてグローバル状態を回避する方法について説明します。 -グローバルインターリンキング .[#toc-global-interlinking] ------------------------------------------- +グローバル結合 +------- -理想的な世界では、オブジェクトは[直接渡さ |passing-dependencies]れたオブジェクトとしか通信できないはずだ。もし私が2つのオブジェクト`A` と`B` を作成し、その間に決して参照を渡さないとすると、`A` も`B` も、もう一方の状態にアクセスしたり変更したりすることはできない。これはコードにとって非常に望ましい性質である。これは、電池と電球があるようなもので、電池をワイヤーでつなぐまで電球は点灯しない。 +理想的な世界では、オブジェクトは[直接渡された |passing-dependencies]オブジェクトとのみ通信できるべきです。2つのオブジェクト `A` と `B` を作成し、それらの間で参照を渡さなければ、`A` も `B` も他のオブジェクトにアクセスしたり、その状態を変更したりすることはできません。これはコードの非常に望ましい特性です。バッテリーと電球を持っているようなものです。バッテリーとワイヤーで接続しない限り、電球は点灯しません。 -しかし、これはグローバル(静的)変数やシングルトンには当てはまらない。オブジェクト`A` は、`C::changeSomething()` を呼び出すことで、*ワイヤレスで*オブジェクト`C` にアクセスし、参照渡しをすることなくそれを変更することができる。オブジェクト`B` がグローバル変数`C` にもアクセスする場合、`A` と`B` は、`C` を介して互いに影響を与え合うことができます。 +しかし、これはグローバル(静的)変数やシングルトンには当てはまりません。オブジェクト `A` は、参照を渡さずに `C::changeSomething()` を呼び出すことで、*ワイヤレス*でオブジェクト `C` にアクセスして変更できます。オブジェクト `B` もグローバル `C` をつかむと、`A` と `B` は `C` を介して相互に影響を与えることができます。 -グローバル変数の使用は、外部からは見えない新しい形の*ワイヤレス*カップリングを導入する。これは、コードの理解と使用を複雑にする煙幕を作ります。依存関係を真に理解するためには、開発者はクラス・インターフェイスに精通するだけでなく、ソースコードのすべての行を読まなければなりません。しかも、このもつれ合いはまったく不要なものだ。グローバル・ステートが使われるのは、どこからでも簡単にアクセスでき、例えばグローバル(静的)メソッド`DB::insert()` を使ってデータベースに書き込むことができるからです。しかし、後述するように、グローバル・ステートがもたらす利点はごくわずかであり、その一方で、グローバル・ステートがもたらす複雑さは深刻です。 +グローバル変数の使用は、外部からは見えない新しい形式の*ワイヤレス*結合をシステムにもたらします。コードの理解と使用を複雑にする煙幕を作り出します。開発者が依存関係を真に理解するには、ソースコードのすべての行を読む必要があります。クラスのインターフェースに精通するだけではありません。さらに、これは完全に不要な結合です。グローバル状態は、どこからでも簡単にアクセスでき、たとえばグローバル(静的)メソッド `DB::insert()` を介してデータベースに書き込むことができるため使用されます。しかし、これから示すように、それがもたらす利点はごくわずかであり、逆に引き起こす合併症は致命的です。 .[note] -動作の面では、グローバル変数と静的変数の間に違いはありません。どちらも同じように有害です。 +動作の観点からは、グローバル変数と静的変数に違いはありません。どちらも同じように有害です。 -遠距離の不気味な作用 .[#toc-the-spooky-action-at-a-distance] --------------------------------------------------- +遠隔での不気味な作用 +---------- -1935年、アルベルト・アインシュタインは、量子物理学のある現象を「Spooky action at a distance」(距離による不気味な作用)と名付けた。 -量子もつれとは、ある粒子に関する情報を測定すると、たとえそれが何百万光年も離れていても、すぐに別の粒子に影響を与えるという特殊性のことである。 -これは、「光より速く移動するものはない」という宇宙の基本法則を一見破っているように見える。 +「遠隔での不気味な作用」 - 1935年にアルベルト・アインシュタインが、彼に鳥肌を立たせた量子物理学の現象をそう名付けました。 +これは量子もつれであり、その特徴は、一方の粒子に関する情報を測定すると、たとえそれらが何百万光年も離れていても、即座に他方の粒子に影響を与えることです。 これは、光よりも速く何も伝播できないという宇宙の基本法則に明らかに違反しているように見えます。 -ソフトウェアの世界では、(参照を渡していないので)孤立していると思われるプロセスを実行しても、オブジェクトに伝えていないシステムの遠い場所で予期せぬ相互作用や状態変化が起こる状況を「spooky action at a distance」と呼ぶことができます。これはグローバルな状態を通してのみ起こりうることです。 +ソフトウェアの世界では、「遠隔での不気味な作用」とは、分離されていると信じているプロセス(参照を渡さなかったため)を実行するが、システムの遠隔地で予期しない相互作用や状態の変化が発生し、それについて知らなかった状況を指すことができます。これはグローバル状態を介してのみ発生する可能性があります。 -大規模で成熟したコードベースを持つプロジェクト開発チームに参加することを想像してください。新しいリーダーから新機能の実装を依頼されたあなたは、優秀な開発者らしく、テストを書くことから始めます。しかし、あなたはプロジェクトに参加したばかりなので、「このメソッドを呼び出したらどうなるか」という探索的なテストをたくさん行います。そして、次のようなテストを書こうとします。 +広範で成熟したコードベースを持つプロジェクトの開発チームに参加したと想像してください。新しい上司が新しい機能の実装を依頼し、あなたは適切な開発者としてテストを書くことから始めます。しかし、プロジェクトに慣れていないため、「このメソッドを呼び出すとどうなるか」のような探索的なテストをたくさん行います。そして、次のテストを書いてみます: ```php function testCreditCardCharge() { - $cc = new CreditCard('1234567890123456', 5, 2028); // your card number + $cc = new CreditCard('1234567890123456', 5, 2028); // あなたのカード番号 $cc->charge(100); } ``` -あなたはコードを実行し、おそらく数回実行しました。しばらくして、銀行からあなたの携帯電話に、実行するたびに100ドルがあなたのクレジットカードに請求されたという通知に気づきます 🤦‍♂️ +コードを実行し、おそらく数回実行した後、しばらくして、実行するたびにクレジットカードから100ドルが引き落とされているという銀行からの通知が携帯電話に表示されることに気づきます 🤦‍♂️ -一体どうやってテストで実際の請求が発生するのでしょうか?クレジットカードで操作するのは簡単ではありません。サードパーティのウェブサービスとやりとりしなければならない、そのウェブサービスのURLを知っていなければならない、ログインしなければならない、などなど。 -これらの情報は、テストには一切含まれていません。さらに悪いことに、この情報がどこに存在するのか、したがって、実行のたびに100ドルが再び請求されることがないように、外部の依存関係をどのように模擬すればよいのかさえもわからないのです。そして新米開発者であるあなたは、これからやろうとしていることが100ドル貧乏になることにつながると、どうやって知ることになるのでしょうか? +一体どうしてテストが実際のお金の引き落としを引き起こしたのでしょうか? クレジットカードの操作は簡単ではありません。サードパーティのWebサービスと通信する必要があり、そのWebサービスのURLを知る必要があり、ログインする必要があり、などなど。 これらの情報はテストには含まれていません。さらに悪いことに、これらの情報がどこにあるのかさえわからないため、実行するたびに再び100ドルが引き落とされることがないように外部依存関係をモックする方法もわかりません。そして、新しい開発者として、これから行うことが100ドル貧しくなることにつながることをどうやって知ることができたのでしょうか? -遠目で見ると不気味な動作ですね!? +これが遠隔での不気味な作用です! -プロジェクト内の接続の仕組みが理解できるまで、先輩や経験者に聞きながら、たくさんのソースコードを掘り下げるしかないのです。 -これは、`CreditCard` クラスのインターフェイスを見ても、初期化が必要なグローバル状態を判断できないことに起因しています。クラスのソースコードを見ても、どの初期化メソッドを呼び出せばいいのかがわからないのです。せいぜい、アクセスされているグローバル変数を見つけ、そこから初期化方法を推測するくらいです。 +プロジェクト内の結合がどのように機能するかを理解するまで、多くのソースコードを長時間掘り下げ、年上で経験豊富な同僚に尋ねるしかありません。 これは、クラス `CreditCard` のインターフェースを見ても、初期化する必要があるグローバル状態を特定できないためです。クラスのソースコードを見ても、どの初期化メソッドを呼び出す必要があるかはわかりません。最良の場合、アクセスされるグローバル変数を見つけて、そこから初期化方法を推測しようとすることができます。 -このようなプロジェクトのクラスは病的な嘘つきである。ペイメントカードは、インスタンス化して`charge()` メソッドを呼び出すだけでよいように装っています。しかし、それは密かに別のクラス、`PaymentGateway` と相互作用している。そのインターフェースでさえ、独立して初期化できると言っているが、実際には、ある設定ファイルからクレデンシャルを引き出したりするのである。 -このコードを書いた開発者には、`CreditCard` が`PaymentGateway` を必要とすることは明らかです。彼らはこのようにコードを書きました。しかし、このプロジェクトに初めて参加する人にとっては、これは完全な謎であり、学習の妨げになります。 +このようなプロジェクトのクラスは病的な嘘つきです。クレジットカードは、インスタンス化して `charge()` メソッドを呼び出すだけで十分であるかのように装います。しかし、舞台裏では、支払いゲートウェイを表す別のクラス `PaymentGateway` と協力しています。そのインターフェースも、単独で初期化できると言っていますが、実際には、ある設定ファイルなどから資格情報を取得します。 このコードを書いた開発者には、`CreditCard` が `PaymentGateway` を必要とすることは明らかです。彼らはこの方法でコードを書きました。しかし、プロジェクトに新しい人にとっては、それは完全な謎であり、学習を妨げます。 -どうすればこの状況を解決できるのか?簡単です。**Let the API declare dependencies.**(APIに依存関係を宣言させる)。 +状況を修正するにはどうすればよいですか? 簡単です。**APIに依存関係を宣言させます。** ```php function testCreditCardCharge() @@ -72,36 +68,35 @@ function testCreditCardCharge() } ``` -コード内の関係が突然明らかになったことに注目してください。`charge()` メソッドが`PaymentGateway` を必要とすると宣言することで、このコードがどのように相互依存しているのか、誰かに尋ねる必要はありません。あなたは、このメソッドのインスタンスを作成しなければならないことを知っていて、それを実行しようとすると、アクセス・パラメータを提供しなければならないという事実にぶつかります。アクセス・パラメータがなければ、コードは実行すらできないのです。 +コード内の結合が突然どのように明らかになるかに注目してください。`charge()` メソッドが `PaymentGateway` を必要とすることを宣言することで、コードがどのように結合されているかを誰かに尋ねる必要はありません。そのインスタンスを作成する必要があることを知っており、そうしようとすると、アクセスパラメータを提供する必要があることに気づきます。それらがなければ、コードを実行することさえできません。 -そして最も重要なのは、決済ゲートウェイをモックにすることで、テストを実行するたびに100ドル請求されることがないようにしたことです。 +そして最も重要なことは、これで支払いゲートウェイをモックできるため、テストを実行するたびに100ドルが請求されることはありません。 -グローバルな状態は、オブジェクトがAPIで宣言されていないものに密かにアクセスできるようになり、結果としてAPIを病的な嘘つきにしてしまいます。 +グローバル状態により、オブジェクトはAPIで宣言されていないものに密かにアクセスできるようになり、結果としてAPIを病的な嘘つきにしてしまいます。 -あなたは今までこのように考えていなかったかもしれませんが、グローバルステートを使うときはいつも、秘密の無線通信チャンネルを作っているのです。不気味な遠隔操作によって、開発者は潜在的な相互作用を理解するためにコードのすべての行を読まなければならず、開発者の生産性を低下させ、新しいチームメンバーを混乱させる。 -あなたがコードを作成した人であれば、本当の依存関係を知っていますが、あなたの後に来る人は何も知りません。 +以前はこのように考えていなかったかもしれませんが、グローバル状態を使用するたびに、秘密のワイヤレス通信チャネルを作成しています。遠隔での不気味なアクションは、開発者に潜在的な相互作用を理解するためにコードのすべての行を読むことを強制し、開発者の生産性を低下させ、新しいチームメンバーを混乱させます。 あなたがコードを作成した人なら、実際の依存関係を知っていますが、あなたの後に来る人は誰でも途方に暮れます。 -グローバルな状態を使うようなコードを書かず、依存関係を渡すことを優先する。つまり、依存性注入です。 +グローバル状態を利用するコードを書かないでください。依存関係の受け渡しを優先してください。つまり、依存性注入です。 -グローバル国家の脆さ .[#toc-brittleness-of-the-global-state] --------------------------------------------------- +グローバル状態の脆弱性 +----------- -グローバルステートとシングルトンを使用するコードでは、そのステートがいつ、誰によって変更されたのか、決して確実ではありません。このリスクは、初期化時にすでに存在している。次のコードは、データベース接続を作成し、ペイメントゲートウェイを初期化することになっていますが、例外を投げ続け、その原因を見つけるのは非常に面倒です。 +グローバル状態とシングルトンを使用するコードでは、いつ誰がこの状態を変更したかが決して確実ではありません。このリスクは初期化時にすでに現れます。次のコードはデータベース接続を作成し、支払いゲートウェイを初期化することを目的としていますが、常に例外をスローし、原因を見つけるのは非常に時間がかかります: ```php PaymentGateway::init(); DB::init('mysql:', 'user', 'password'); ``` -`PaymentGateway` オブジェクトが他のオブジェクトに無線でアクセスし、その中にはデータベース接続を必要とするものがあることは、コードを詳しく見てみなければわかりません。したがって、`PaymentGateway` の前にデータベースを初期化する必要があります。しかし、グローバルステートという煙幕が、このことを隠しています。もし各クラスのAPIが嘘をつかず、依存関係を宣言していたら、どれだけの時間を節約できるでしょうか? +`PaymentGateway`オブジェクトが他のオブジェクトにワイヤレスでアクセスし、その一部がデータベース接続を必要とすることを理解するには、コードを詳細に調べる必要があります。したがって、`PaymentGateway`の前にデータベースを初期化する必要があります。しかし、グローバル状態の煙幕はこれをあなたから隠します。個々のクラスのAPIが欺瞞的でなく、依存関係を宣言していたら、どれだけの時間を節約できたでしょうか? ```php $db = new DB('mysql:', 'user', 'password'); $gateway = new PaymentGateway($db, ...); ``` -データベース接続にグローバルアクセスを使用する場合にも、同様の問題が発生します。 +同様の問題は、データベース接続へのグローバルアクセスを使用する場合にも発生します: ```php use Illuminate\Support\Facades\DB; @@ -115,9 +110,9 @@ class Article } ``` -`save()` メソッドを呼び出す際、データベース接続がすでに作成されているかどうか、また、誰がその作成に責任を持つのかが不明確である。たとえば、テスト目的でデータベース接続をその場で変更したい場合、`DB::reconnect(...)` や`DB::reconnectForTest()` などの追加のメソッドを作成する必要があるでしょう。 +`save()`メソッドを呼び出すとき、データベース接続がすでに作成されているかどうか、そして誰がその作成を担当しているかは定かではありません。たとえば、テストのために実行時にデータベース接続を変更したい場合は、おそらく`DB::reconnect(...)`や`DB::reconnectForTest()`などの追加のメソッドを作成する必要があるでしょう。 -一例を考えてみましょう。 +例を考えてみましょう: ```php $article = new Article; @@ -127,9 +122,9 @@ Foo::doSomething(); $article->save(); ``` -`$article->save()` を呼び出す際に、テストデータベースが本当に使用されていることをどこで確認できるのでしょうか?もし、`Foo::doSomething()` メソッドがグローバルデータベース接続を変更したとしたらどうでしょうか?それを知るためには、`Foo` クラスのソースコードと、おそらく他の多くのクラスのソースコードを調査する必要があります。しかし、この方法は短期的な答えしか得られません。なぜなら、将来的に状況が変わる可能性があるからです。 +`$article->save()`を呼び出すときに、テストデータベースが実際に使用されているという確信はどこにありますか? `Foo::doSomething()`メソッドがグローバルデータベース接続を変更した場合はどうなりますか? これを確認するには、クラス`Foo`のソースコード、そしておそらく他の多くのクラスを調べる必要があります。しかし、このアプローチは短期的な答えしか提供せず、状況は将来変わる可能性があります。 -データベース接続を`Article` クラス内の静的変数に移したらどうでしょう。 +そして、データベース接続をクラス`Article`内の静的変数に移動したらどうなりますか? ```php class Article @@ -148,11 +143,11 @@ class Article } ``` -これでは全く何も変わりません。問題はグローバルな状態であり、どのクラスに潜んでいるかは関係ないのです。この場合、前のものと同様に、`$article->save()` メソッドが呼ばれたときに、どのデータベースに書き込まれているのかについては、全くわかりません。アプリケーションの遠くの端にいる誰もが、`Article::setDb()` を使っていつでもデータベースを変更することができます。私たちの手の中で +これは何も変わりません。問題はグローバル状態であり、どのクラスに隠されているかはまったく関係ありません。この場合、前のケースと同様に、`$article->save()`メソッドを呼び出すときに、どのデータベースに書き込まれるかについての手がかりはありません。アプリケーションの反対側の誰かが、いつでも`Article::setDb()`を使用してデータベースを変更できた可能性があります。私たちの手の下で。 -グローバルな状態は、私たちのアプリケーションを**極めて壊れやすい**ものにしています。 +グローバル状態は、アプリケーションを**非常に脆弱**にします。 -しかし、この問題に対処する簡単な方法があります。APIに依存関係を宣言させるだけで、適切な機能を確保することができるのです。 +しかし、この問題に対処する簡単な方法があります。APIに依存関係を宣言させるだけで、正しい機能が保証されます。 ```php class Article @@ -174,15 +169,15 @@ Foo::doSomething(); $article->save(); ``` -このアプローチにより、データベース接続の隠れた予期せぬ変更の心配がなくなります。今、私たちは記事がどこに保存されているかを確信しており、別の無関係なクラス内のコードを修正しても、もう状況を変えることはできません。コードはもはや壊れやすくなく、安定しているのです。 +このアプローチのおかげで、データベース接続の隠れた予期しない変更を心配する必要はなくなります。これで、記事がどこに保存されるかが確実になり、他の無関係なクラス内のコードの変更が状況を変えることはできなくなります。コードはもはや脆弱ではなく、安定しています。 -グローバルな状態を使うようなコードは書かないで、依存関係を渡す方がいい。したがって、依存性注入。 +グローバル状態を利用するコードを書かないでください。依存関係の受け渡しを優先してください。つまり、依存性注入です。 -シングルトン .[#toc-singleton] ------------------------- +シングルトン +------ -シングルトンは、有名なGang of Fourの出版物からの[定義により |https://en.wikipedia.org/wiki/Singleton_pattern]、クラスを単一のインスタンスに制限し、それに対してグローバルなアクセスを提供するデザインパターンである。このパターンの実装は、通常、次のようなコードに似ています。 +シングルトンは、有名なGang of Fourの出版物からの[定義|https://en.wikipedia.org/wiki/Singleton_pattern] によると、クラスを単一のインスタンスに制限し、それにグローバルアクセスを提供する設計パターンです。このパターンの実装は通常、次のコードに似ています: ```php class Singleton @@ -195,38 +190,35 @@ class Singleton return self::$instance; } - // and other methods that perform the functions of the class + // クラスの機能を実行する他のメソッド } ``` -残念ながら、シングルトンはアプリケーションにグローバルな状態を導入することになります。そして、上で示したように、グローバルな状態は望ましくありません。これが、シングルトンがアンチパターンと言われる所以です。 +残念ながら、シングルトンはアプリケーションにグローバル状態を導入します。そして、上で示したように、グローバル状態は望ましくありません。したがって、シングルトンはアンチパターンと見なされます。 -コードにシングルトンを使わず、他のメカニズムに置き換えてください。シングルトンは本当に必要ない。しかし、アプリケーション全体に対して、あるクラスの単一のインスタンスの存在を保証する必要がある場合は、[DIコンテナに |container]任せます。 -したがって、アプリケーションシングルトン(サービス)を作成します。これにより、クラスは独自のユニークさを持たなくなり(つまり、`getInstance()` メソッドや静的変数を持たなくなり)、その機能のみを実行するようになります。したがって、単一責任の原則に違反することはなくなる。 +コードでシングルトンを使用せず、他のメカニズムに置き換えてください。シングルトンは本当に必要ありません。ただし、アプリケーション全体でクラスの単一インスタンスの存在を保証する必要がある場合は、[DIコンテナ |container]に任せてください。 これにより、アプリケーションシングルトン、つまりサービスが作成されます。これにより、クラスは自身の独自性を保証すること(つまり、`getInstance()`メソッドと静的変数を持たないこと)をやめ、その機能のみを実行します。したがって、単一責任の原則に違反しなくなります。 -グローバル・ステート・バーズ・テスト .[#toc-global-state-versus-tests] ----------------------------------------------------- +グローバル状態 対 テスト +------------- -テストを書くとき、各テストは孤立したユニットであり、外部の状態が入り込むことはないと仮定します。また、テストから離れる状態もない。テストが完了すると、テストに関連する状態は、ガベージコレクタによって自動的に削除されるはずです。これにより、テストは孤立したものになります。したがって、テストを任意の順序で実行することができます。 +テストを作成するとき、各テストは分離されたユニットであり、外部状態が入力されないことを前提としています。そして、テストから状態は出力されません。テストが完了すると、テストに関連するすべての状態はガベージコレクタによって自動的に削除されるはずです。これにより、テストは分離されます。したがって、テストは任意の順序で実行できます。 -しかし、グローバルな状態やシングルトンが存在する場合、これらの素敵な仮定はすべて崩れてしまいます。状態はテストに入り、テストから出ることができる。突然、テストの順番が問題になることがある。 +ただし、グローバル状態/シングルトンが存在する場合、これらの快適な前提はすべて崩壊します。状態はテストに入力および出力できます。突然、テストの順序が重要になる可能性があります。 -シングルトンをテストするために、開発者はしばしば、インスタンスを別のものに置き換えるなどして、その特性を緩和しなければなりません。このような解決策は、せいぜいハック程度で、維持と理解が困難なコードを生成します。グローバルな状態に影響を与えるテストやメソッド(`tearDown()` )は、それらの変更を元に戻さなければなりません。 +シングルトンをテストできるようにするために、開発者はしばしば、インスタンスを別のインスタンスに置き換えることを許可するなどして、そのプロパティを緩和する必要があります。このようなソリューションは、せいぜいハックであり、保守や理解が困難なコードを作成します。グローバル状態に影響を与える各テストまたは`tearDown()`メソッドは、これらの変更を元に戻す必要があります。 -グローバルステートは、ユニットテストにおける最大の頭痛の種です +グローバル状態は、ユニットテストにおける最大の頭痛の種です! -どうすればこの状況を解決できるのか?簡単です。シングルトンを使うようなコードを書かず、依存関係を渡すことを優先する。つまり、依存性注入です。 +状況を修正するにはどうすればよいですか? 簡単です。シングルトンを利用するコードを書かないでください。依存関係の受け渡しを優先してください。つまり、依存性注入です。 -グローバル定数 .[#toc-global-constants] --------------------------------- +グローバル定数 +------- -グローバルステートは、シングルトンや静的変数の使用に限らず、グローバル定数にも適用可能です。 +グローバル状態は、シングルトンや静的変数の使用に限定されず、グローバル定数にも関係する可能性があります。 -定数の値が、新しい情報(`M_PI` )や有用な情報(`PREG_BACKTRACK_LIMIT_ERROR` )を提供しない定数は、明らかにOKです。 -逆に、コード内部で情報を*ワイヤレス*で受け渡す方法として機能する定数は、隠れた依存関係以外の何物でもありません。次の例の`LOG_FILE` のようなものです。 -`FILE_APPEND` 定数を使用することは完全に正しいです。 +値が新しい(`M_PI`)または有用な(`PREG_BACKTRACK_LIMIT_ERROR`)情報をもたらさない定数は、明らかに問題ありません。 逆に、情報をコード内に*ワイヤレス*で渡す方法として機能する定数は、隠れた依存関係にすぎません。次の例の`LOG_FILE`のように。 定数`FILE_APPEND`の使用は完全に正しいです。 ```php const LOG_FILE = '...'; @@ -242,7 +234,7 @@ class Foo } ``` -この場合、`Foo` クラスのコンストラクタでパラメータを宣言し、API の一部とする必要があります。 +この場合、APIの一部となるように、クラス`Foo`のコンストラクタでパラメータを宣言する必要があります: ```php class Foo @@ -261,44 +253,42 @@ class Foo } ``` -これで、ロギングファイルのパスに関する情報を渡して、必要に応じて簡単に変更できるようになり、コードのテストやメンテナンスがしやすくなりました。 +これで、ロギング用のファイルパスに関する情報を渡し、必要に応じて簡単に変更できるため、コードのテストと保守が容易になります。 -グローバルファンクションとスタティックメソッド .[#toc-global-functions-and-static-methods] -------------------------------------------------------------------- +グローバル関数と静的メソッド +-------------- -静的メソッドやグローバル関数の使用自体が問題ではないことを強調したい。`DB::insert()` や同様のメソッドの使用が不適切であることを説明してきましたが、それは常に静的変数に格納されるグローバルな状態の問題でした。`DB::insert()` メソッドは、データベース接続を格納するため、静的変数の存在を必要とします。この変数がなければ、このメソッドを実装することは不可能です。 +静的メソッドとグローバル関数自体の使用が問題ではないことを強調したいと思います。`DB::insert()`や同様のメソッドの使用が不適切である理由を説明しましたが、それは常に、ある静的変数に格納されているグローバル状態の問題にすぎませんでした。`DB::insert()`メソッドは、データベース接続が格納されているため、静的変数の存在を必要とします。この変数がなければ、メソッドを実装することは不可能です。 -`DateTime::createFromFormat()`,`Closure::fromCallable`,`strlen()` などの決定論的な静的メソッドや関数の使用は、依存性注入と完全に一致します。これらの関数は、常に同じ入力パラメータから同じ結果を返すので、予測可能です。また、グローバルな状態を使用することもありません。 +`DateTime::createFromFormat()`、`Closure::fromCallable`、`strlen()`、その他多くの決定論的な静的メソッドと関数の使用は、依存性注入と完全に一致しています。これらの関数は、同じ入力パラメータから常に同じ結果を返し、したがって予測可能です。グローバル状態は使用しません。 -しかし、PHPには決定論的でない関数があります。例えば、`htmlspecialchars()` 関数がそうです。その第3パラメータである`$encoding` は、指定しない場合、デフォルトで設定オプション`ini_get('default_charset')` の値になります。したがって、関数の予測不可能な動作を避けるために、このパラメータを常に指定することが推奨されます。Netteでは一貫してこれを採用しています。 +ただし、PHPには決定論的でない関数もあります。これらには、たとえば関数`htmlspecialchars()`が含まれます。その3番目のパラメータ`$encoding`が指定されていない場合、デフォルト値は設定オプション`ini_get('default_charset')`の値になります。したがって、このパラメータを常に指定し、関数の予期しない動作の可能性を防ぐことをお勧めします。Netteはこれを一貫して行っています。 -`strtolower()`,`strtoupper()` などの一部の関数は、最近になって非決定的な動作をするようになり、`setlocale()` の設定に依存するようになりました。このため、多くの複雑な問題が発生し、その多くはトルコ語を扱うときに発生しました。 -というのも、トルコ語はドットのある大文字と小文字`I` を区別しているからです。そのため、`strtolower('I')` は`ı` の文字を返し、`strtoupper('i')` は`İ` の文字を返します。このため、アプリケーションは多くの謎のエラーを引き起こすことになりました。 -しかし、この問題はPHPバージョン8.2で修正され、関数はロケールに依存しなくなりました。 +`strtolower()`、`strtoupper()`などの一部の関数は、最近まで非決定論的に動作し、`setlocale()`の設定に依存していました。これは多くの合併症を引き起こし、最も一般的にはトルコ語を扱うときに発生しました。トルコ語では、ドット付きとドットなしの小文字と大文字の`I`を区別します。したがって、`strtolower('I')`は文字`ı`を返し、`strtoupper('i')`は文字`İ`を返し、これによりアプリケーションが一連の不可解なエラーを引き起こし始めました。 しかし、この問題はPHPバージョン8.2で修正され、関数はもはやロケールに依存しません。 -これは、グローバルステートが世界中の何千人もの開発者を悩ませてきたことを示すいい例です。その解決策は、依存性注入に置き換えることでした。 +これは、グローバル状態が世界中の何千人もの開発者をどのように悩ませたかの良い例です。解決策は、それを依存性注入に置き換えることでした。 -グローバルステートの使用はどのような場合に可能か? .[#toc-when-is-it-possible-to-use-global-state] -------------------------------------------------------------------------- +グローバル状態を使用できる場合はいつですか? +---------------------- -グローバルステートを使用することが可能な特定の状況があります。例えば、コードをデバッグする際に、変数の値をダンプしたり、プログラムの特定の部分の時間を測定したりする必要がある場合です。このような場合、後でコードから削除される一時的な動作に関するものであれば、グローバルに利用可能なダンパやストップウォッチを使用することが正当です。これらのツールは、コード設計の一部ではありません。 +グローバル状態を利用できる特定の状況があります。たとえば、コードのデバッグ中に、変数の値を出力したり、プログラムの特定の部分の期間を測定したりする必要がある場合です。このような場合、後でコードから削除される一時的なアクションに関する場合、グローバルに利用可能なダンパーまたはストップウォッチを正当に利用できます。これらのツールはコードの設計の一部ではありません。 -もう一つの例は、正規表現を扱うための関数`preg_*` で、コンパイルされた正規表現を内部的にメモリ上の静的キャッシュに保存します。コードの異なる部分で同じ正規表現を複数回呼び出しても、コンパイルされるのは1回だけです。キャッシュは性能を節約し、またユーザーには全く見えないので、このような使い方は正当なものだと考えることができます。 +別の例は、正規表現を扱う関数`preg_*`であり、コンパイルされた正規表現をメモリ内の静的キャッシュに内部的に格納します。したがって、コードの異なる場所で同じ正規表現を複数回呼び出すと、一度だけコンパイルされます。キャッシュはパフォーマンスを節約し、同時にユーザーには完全に表示されないため、このような使用は正当と見なすことができます。 -概要 .[#toc-summary] ------------------- +まとめ +--- -なぜそれが理にかなっているのかを示しました +私たちは、なぜ意味があるのか​​を議論しました: 1) コードからすべての静的変数を削除する 2) 依存関係を宣言する -3) そして依存性注入を使う +3) そして依存性注入を使用する -コード設計を考えるとき、`static $foo` のそれぞれが問題を表していることに留意してください。あなたのコードがDIを尊重する環境になるためには、グローバルステートを完全に根絶し、依存性注入に置き換えることが必要不可欠です。 +コードの設計を考えるとき、すべての`static $foo`が問題であることを念頭に置いてください。コードがDIを尊重する環境であるためには、グローバル状態を完全に根絶し、依存性注入に置き換えることが不可欠です。 -この過程で、クラスが複数の責任を持つため、クラスを分割する必要があることがわかるかもしれません。そのようなことは気にせず、1つの責任という原則を貫くようにしましょう。 +このプロセス中に、クラスが複数の責任を持っているため、分割する必要があることに気づくかもしれません。恐れないでください。単一責任の原則を目指してください。 -*本章は、Miško Hevery氏の「[Flaw: Brittle Global State & Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/]」等の論文に基づくものです。 +*この章の基礎となった[Flaw: Brittle Global State & Singletons |https://web.archive.org/web/20230321084133/http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/]などの記事を提供してくれたMiško Hevery氏に感謝します。* diff --git a/dependency-injection/ja/introduction.texy b/dependency-injection/ja/introduction.texy index fb13b55c6c..ce73fa0e23 100644 --- a/dependency-injection/ja/introduction.texy +++ b/dependency-injection/ja/introduction.texy @@ -1,58 +1,58 @@ -依存性注入(Dependency Injection)とは? -****************************** +依存性の注入とは? +********* .[perex] -この章では、アプリケーションを書くときに守るべき基本的なプログラミングの実践について紹介します。これらは、きれいで理解しやすく、保守しやすいコードを書くために必要な基礎知識です。 +この章では、すべてのアプリケーションを作成する際に従うべき基本的なプログラミング手法を紹介します。これらは、クリーンで理解しやすく、保守可能なコードを書くために必要な基礎です。 -このルールに従えば、ネッテはどんなときでもあなたの味方になってくれます。ロジックに集中できるよう、ルーティンワークをこなし、最高の快適さを提供します。 +これらのルールを習得し、遵守すれば、Netteはあらゆるステップであなたをサポートします。ルーチンタスクを処理し、最大限の快適さを提供するため、ロジック自体に集中できます。 -ここで紹介する原理は、とてもシンプルです。何も心配する必要はありません。 +ここで示す原則は、実際には非常にシンプルです。何も心配する必要はありません。 -初めてのプログラムを覚えていますか? .[#toc-remember-your-first-program] ------------------------------------------------------- +最初のプログラムを覚えていますか? +----------------- -どのような言語で書かれたかはわかりませんが、PHPであれば、次のような感じだったかもしれません: +どの言語で書いたかはわかりませんが、もしPHPだったら、おそらくこのようになっていたでしょう: ```php -function addition(float $a, float $b): float +function soucet(float $a, float $b): float { return $a + $b; } -echo addition(23, 1); // prints 24 +echo soucet(23, 1); // 24 を出力します ``` -ほんの数行の些細なコードですが、そこには多くの重要な概念が隠されています。変数があること。コードはより小さな単位に分解され、例えば関数となる。入力引数を渡すと、結果を返してくれる。足りないのは、条件とループだけだ。 +いくつかの簡単なコード行ですが、そこには非常に多くの重要な概念が隠されています。変数があること。コードがより小さな単位、例えば関数に分割されること。それらに入力引数を渡し、それらが結果を返すこと。そこには条件とループだけが欠けています。 -関数が入力データを受け取り、結果を返すというのは、数学など他の分野でも使われている、完全に理解できる概念である。 +関数に入力データを渡し、それが結果を返すというのは、数学のような他の分野でも使用される、完全に理解できる概念です。 -関数にはシグネチャがあり、その名前、パラメータのリストとその型、そして最後に戻り値の型から構成されています。ユーザーとしては、シグネチャに興味があり、通常、内部実装については何も知る必要はない。 +関数には、その名前、パラメータとその型のリスト、そして最後に返り値の型からなるシグネチャがあります。ユーザーとしては、シグネチャに興味があり、通常、内部実装について何も知る必要はありません。 -ここで、関数シグネチャが次のようなものだったと想像してみてください: +さて、関数のシグネチャがこのようになっていると想像してみてください: ```php -function addition(float $x): float +function soucet(float $x): float ``` -パラメータが1つの足し算?それはおかしい...。これならどうだろう? +1つのパラメータでの合計?それは奇妙です… では、これはどうでしょう? ```php -function addition(): float +function soucet(): float ``` -今のは本当に変ですよね?その機能はどのように使われているのでしょうか? +これは本当に非常に奇妙ですね?関数はどのように使用されるのでしょうか? ```php -echo addition(); // what does it prints? +echo soucet(); // 何が出力されるでしょうか? ``` -このようなコードを見ると、私たちは混乱してしまうでしょう。初心者が理解できないだけでなく、経験豊富なプログラマーでさえ、このようなコードは理解できないでしょう。 +このようなコードを見ると、私たちは混乱するでしょう。初心者だけでなく、熟練したプログラマーでさえ、そのようなコードを理解できません。 -このような関数が実際に内部でどのように見えるか気になりませんか?その関数はどこで和集合を得るのだろう?おそらく、次のような形で、自分自身で取得するのでしょう: +そのような関数が内部でどのように見えるか考えていますか?加算される数はどこから取得するのでしょうか?おそらく、*何らかの方法で*それらを自分で取得するでしょう、例えばこのように: ```php -function addition(): float +function soucet(): float { $a = Input::get('a'); $b = Input::get('b'); @@ -60,71 +60,71 @@ function addition(): float } ``` -その結果、関数本体には他の関数(または静的メソッド)へのバインディングが隠されていることが判明し、実際にアドエンドの由来を知るには、さらに掘り下げる必要がある。 +関数の本体で、他のグローバル関数や静的メソッドへの隠れた依存関係を発見しました。加算される数が実際にどこから来るのかを知るためには、さらに調査する必要があります。 -このままではダメだ! .[#toc-not-this-way] -------------------------------- +これはダメ! +------ -先ほどお見せしたデザインは、多くのマイナス点を解消するためのエッセンスです: +私たちが示した設計は、多くの否定的な特徴の本質です: -- 関数のシグネチャが和集合を必要としないように見せかけるので、混乱しました。 -- 他の2つの数値で計算する関数を作る方法がわからない -- 和算がどこから来たのか、コードを見なければわかりませんでしたが +- 関数のシグネチャは、加算される数を必要としないように見せかけ、私たちを混乱させました +- 他の2つの数を合計するように関数をどうやって強制するのか、まったくわかりません +- 加算される数をどこから取得するかを知るために、コードを見る必要がありました - 隠れた依存関係を発見しました -- 完全な理解には、これらの依存関係も調べる必要があります。 +- 完全に理解するためには、これらの依存関係も調査する必要があります -そして、インプットを調達するのも加算機能の仕事なのだろうか?もちろん、そんなことはありません。 足し算の仕事だけです。 +そして、入力データを取得することは、加算関数のタスクなのでしょうか?もちろん、そうではありません。その責任は、加算自体だけです。 -このようなコードには遭遇したくないし、書きたくもない。解決策は簡単です。基本に立ち返って、パラメータを使うだけです。 +このようなコードには出会いたくないし、絶対に書きたくありません。修正は簡単です:基本に戻り、単にパラメータを使用します: ```php -function addition(float $a, float $b): float +function soucet(float $a, float $b): float { return $a + $b; } ``` -ルールその1:渡されるようにする .[#toc-rule-1-let-it-be-passed-to-you] -------------------------------------------------------- +ルール1:渡してもらう +----------- -最も重要なルールは**関数やクラスが必要とするすべてのデータは、関数やクラスに渡さなければならない**ということです。 +最も重要なルールは次のとおりです:**関数やクラスが必要とするすべてのデータは、それらに渡されなければなりません**。 -データへのアクセス方法を工夫する代わりに、パラメータを渡すだけでよいのです。コードを改善することのない隠し通路を考案する時間を節約することができます。 +それらが何らかの方法で自分でアクセスできる隠れた方法を考案する代わりに、単にパラメータを渡してください。隠れたパスを考案するのに必要な時間を節約できます。それは間違いなくあなたのコードを改善しません。 -もしあなたがいつもどこでもこのルールに従うなら、あなたは隠れた依存関係のないコードへの道を歩んでいることになります。作者だけでなく、その後に読む人にも理解できるコードへ。関数やクラスのシグネチャからすべてが理解でき、実装の中に隠された秘密を探す必要がないところ。 +このルールを常にどこでも守れば、隠れた依存関係のないコードへの道を歩んでいます。作者だけでなく、後でそれを読むすべての人にとって理解しやすいコードへ。関数のシグネチャとクラスからすべてが理解でき、実装内の隠れた秘密を探す必要がないコードへ。 -この手法は専門的には**依存性注入**と呼ばれています。そして、それらのデータは**依存性**と呼ばれています。これは単なるパラメータの受け渡しであり、それ以上のものではありません。 +この技術は専門的には**依存性の注入** (dependency injection) と呼ばれます。そして、それらのデータは**依存関係** (dependencies) と呼ばれます。実際には、それは単なるパラメータ渡しであり、それ以上のものではありません。 .[note] -デザインパターンである依存性注入と、ツールである「依存性注入コンテナ」は正反対のものなので、混同しないようにしてください。コンテナについては後述します。 +デザインパターンである依存性の注入と、「依存性注入コンテナ」、つまり全く異なるツールを混同しないでください。コンテナについては後で説明します。 -関数からクラスへ .[#toc-from-functions-to-classes] ------------------------------------------- +関数からクラスへ +-------- -また、クラスはどのように関係しているのでしょうか?クラスは単純な関数よりも複雑な単位ですが、ここでもルールその1が完全に適用されます。[引数を |passing-dependencies]渡す方法が増えただけです。例えば、関数の場合とかなり似ています: +そして、クラスはこれとどのように関連していますか?クラスは単純な関数よりも複雑な全体ですが、ルール1はここでも完全に適用されます。ただし、[引数を渡すためのより多くのオプション|passing-dependencies]があります。例えば、関数の場合と非常によく似ています: ```php -class Math +class Matematika { - public function addition(float $a, float $b): float + public function soucet(float $a, float $b): float { return $a + $b; } } -$math = new Math; -echo $math->addition(23, 1); // 24 +$math = new Matematika; +echo $math->soucet(23, 1); // 24 ``` -あるいは他のメソッドを通じて、あるいはコンストラクターを通じて直接: +または、他のメソッドやコンストラクタを使用して: ```php -class Addition +class Soucet { public function __construct( private float $a, @@ -132,26 +132,26 @@ class Addition ) { } - public function calculate(): float + public function spocti(): float { return $this->a + $this->b; } } -$addition = new Addition(23, 1); -echo $addition->calculate(); // 24 +$soucet = new Soucet(23, 1); +echo $soucet->spocti(); // 24 ``` -どちらの例も、依存性注入に完全に準拠している。 +両方の例は、依存性の注入と完全に一致しています。 -実際の事例 .[#toc-real-life-examples] --------------------------------- +実際の例 +---- -実社会では、数字の足し算のクラスを書くことはないでしょう。では、実践的な例題に移りましょう。 +現実の世界では、数を合計するためのクラスを書くことはありません。実際の例に移りましょう。 -ブログの記事を表す`Article` クラスを用意しよう: +ブログ記事を表すクラス `Article` があるとします: ```php class Article @@ -162,12 +162,12 @@ class Article public function save(): void { - // save the article to the database + // 記事をデータベースに保存します } } ``` -となり、使い方は以下のようになります。 +そして、使用法は次のようになります: ```php $article = new Article; @@ -176,9 +176,9 @@ $article->content = 'Every year millions of people in ...'; $article->save(); ``` -`save()` メソッドは、記事をデータベースのテーブルに保存します。[Nette Databaseを使って |database:en]これを実装するのは簡単ですが、1つだけ難点があります。`Article` は、データベース接続、つまりクラス`Nette\Database\Connection` のオブジェクトをどこで取得するのか? +`save()` メソッドは記事をデータベーステーブルに保存します。[Nette Database |database:] を使用して実装するのは簡単ですが、1つの問題があります:`Article` はデータベース接続、つまり `Nette\Database\Connection` クラスのオブジェクトをどこから取得するのでしょうか? -選択肢はたくさんあるようです。どこかの静的変数から取得することができます。あるいは、データベース接続を提供するクラスから継承する。あるいは、[シングルトンを |global-state#Singleton]利用する。あるいは、Laravelで使われている、いわゆるファサードを利用する: +多くの選択肢があるようです。静的変数から取得できます。または、データベース接続を提供するクラスから継承することもできます。または、いわゆる [シングルトン |global-state#シングルトン] を使用することもできます。または、Laravelで使用されるいわゆるfacades: ```php use Illuminate\Support\Facades\DB; @@ -199,17 +199,17 @@ class Article } ``` -素晴らしい、問題を解決した。 +素晴らしい、問題を解決しました。 -あるいは、そうしてきたのだろうか。 +それとも? -[ルールその1「渡されるようにする |#rule #1: Let It Be Passed to You]」:クラスが必要とする依存関係はすべて渡されなければならない、ということを思い出してみましょう。なぜなら、このルールを破ると、隠れた依存関係でいっぱいの汚いコード、理解不能なコードへの道に乗り出したことになり、その結果、保守や開発に苦痛を伴うアプリケーションになってしまうからです。 +[#ルール1 渡してもらう] を思い出してください:クラスが必要とするすべての依存関係は、それに渡されなければなりません。なぜなら、ルールを破ると、隠れた依存関係、不可解さでいっぱいの汚いコードへの道を歩み始め、その結果、維持および開発が苦痛になるアプリケーションになるからです。 -`Article` クラスのユーザーは、`save()` メソッドが記事をどこに保存するのか分からない。データベースのテーブルの中?本番とテスト、どちらでしょうか?そして、それはどのように変更できるのでしょうか? +`Article` クラスのユーザーは、`save()` メソッドが記事をどこに保存するかを知りません。データベーステーブルに?どちらに、本番用またはテスト用?そして、それをどのように変更できますか? -ユーザーは、`save()` メソッドがどのように実装されているかを見て、`DB::insert()` メソッドの使用を見つけなければなりません。そこで、このメソッドがどのようにデータベース接続を取得するのか、さらに調べなければならない。そして、隠された依存関係は非常に長い鎖を形成することができます。 +ユーザーは `save()` メソッドがどのように実装されているかを確認し、`DB::insert()` メソッドの使用を見つける必要があります。したがって、このメソッドがデータベース接続をどのように取得するかをさらに調査する必要があります。そして、隠れた依存関係は非常に長い連鎖を形成する可能性があります。 -きれいに設計されたコードでは、隠れた依存関係、Laravelファサード、静的変数が存在することはありません。きれいでよくできたコードでは、引数は渡されます: +クリーンで適切に設計されたコードでは、隠れた依存関係、Laravelのfacades、または静的変数は決して存在しません。クリーンで適切に設計されたコードでは、引数が渡されます: ```php class Article @@ -224,7 +224,7 @@ class Article } ``` -後述するように、さらに実用的なアプローチは、コンストラクタを利用することになります: +さらに実用的には、後で見るように、コンストラクタを使用することです: ```php class Article @@ -245,11 +245,11 @@ class Article ``` .[note] -経験豊富なプログラマーであれば、`Article` は`save()` メソッドを持つべきでないと考えるかもしれません。純粋にデータコンポーネントを表し、保存は別のリポジトリが行うべきでしょう。それはそれで理にかなっている。しかし、それでは、依存性注入という今回のテーマの範囲をはるかに超えてしまいますし、簡単な例を提供する努力も必要です。 +経験豊富なプログラマーであれば、`Article` は `save()` メソッドを持つべきではなく、純粋なデータコンポーネントとして表現し、保存は別のリポジトリが担当すべきだと考えるかもしれません。それは理にかなっています。しかし、それでは依存性の注入というトピックや、簡単な例を示すという試みの範囲を大きく超えてしまいます。 -例えば、操作のためにデータベースを必要とするクラスを書く場合、そのデータベースをどこから取得するかは考えず、渡すようにします。コンストラクタのパラメータとして、あるいは別のメソッドとして。依存関係を認める。クラスのAPIで依存関係を認めてください。そうすれば、理解しやすく、予測可能なコードを得ることができます。 +例えばデータベースを必要とするクラスを作成する場合、それをどこから取得するかを考え出すのではなく、渡してもらうようにしてください。例えば、コンストラクタや他のメソッドのパラメータとして。依存関係を認めてください。クラスのAPIでそれらを認めてください。理解しやすく予測可能なコードが得られます。 -そして、エラーメッセージを記録するこのクラスはどうでしょう: +そして、エラーメッセージをログに記録するこのクラスはどうでしょうか: ```php class Logger @@ -262,23 +262,23 @@ class Logger } ``` -どうでしょう、[ルールその1「受け継がせる |#rule #1: Let It Be Passed to You]」は守れたでしょうか? +[#ルール1 渡してもらう] を守ったと思いますか? -していないんです。 +守っていません。 -重要な情報、すなわちログファイルのあるディレクトリは、クラス自身が定数から*取得*します。 +クラスは、重要な情報、つまりログファイルのあるディレクトリを、定数から*自分で取得*しています。 使用例を見てください: ```php $logger = new Logger; -$logger->log('The temperature is 23 °C'); -$logger->log('The temperature is 10 °C'); +$logger->log('温度は23℃です'); +$logger->log('温度は10℃です'); ``` -実装を知らなくても、どこにメッセージが書かれているかという質問に答えられるだろうか?`LOG_DIR` 定数の存在がその機能に必要であると推測できますか?そして、別の場所に書き込む2番目のインスタンスを作ることができるでしょうか?もちろん無理でしょう。 +実装を知らずに、メッセージがどこに書き込まれるかという質問に答えられますか?機能するためには定数 `LOG_DIR` の存在が必要だと考えましたか?そして、別の場所に書き込む2番目のインスタンスを作成できますか?絶対にできません。 -クラスを固定しよう。 +クラスを修正しましょう: ```php class Logger @@ -295,24 +295,24 @@ class Logger } ``` -このクラスは、より理解しやすく、設定可能で、したがって、より便利なものになりました。 +クラスは今、はるかに理解しやすく、構成可能で、したがってより便利です。 ```php $logger = new Logger('/path/to/log.txt'); -$logger->log('The temperature is 15 °C'); +$logger->log('温度は15℃です'); ``` -But I Don't Care! .[#toc-but-i-don-t-care] ------------------------------------------- +でも、それは気にしない! +------------ -*記事オブジェクトを作成してsave()を呼び出すと、データベースを扱うのではなく、設定で設定したものに保存されるだけでいいのです。 +*「Article オブジェクトを作成して save() を呼び出すとき、データベースについて考えたくない。設定で設定したデータベースに保存してほしいだけだ。」* -*Loggerを使うときは、メッセージを書いてほしいだけで、どこをどうするかは考えたくないんです。グローバル設定を使わせてください」*。 +*「Logger を使用するとき、メッセージが書き込まれるだけで、どこに書き込まれるかは気にしない。グローバル設定が使用されるようにしてほしい。」* -これらは有効な指摘です。 +これらは正しいコメントです。 -例として、ニュースレターを送信し、その様子をログに残すクラスを見てみましょう: +例として、ニュースレターを送信し、結果をログに記録するクラスを示します: ```php class NewsletterDistributor @@ -322,27 +322,27 @@ class NewsletterDistributor $logger = new Logger(/* ... */); try { $this->sendEmails(); - $logger->log('Emails have been sent out'); + $logger->log('メールは送信されました'); } catch (Exception $e) { - $logger->log('An error occurred during the sending'); + $logger->log('送信中にエラーが発生しました'); throw $e; } } } ``` -`LOG_DIR` 定数を使用しなくなった改良版`Logger` では、コンストラクタでファイルパスを指定する必要があります。これを解決するにはどうしたらいいのでしょうか?`NewsletterDistributor` クラスは、メッセージがどこに書き込まれるかは気にしません。 +改善された `Logger` は、もはや定数 `LOG_DIR` を使用せず、コンストラクタでファイルパスを指定する必要があります。これをどのように解決しますか?`NewsletterDistributor` クラスは、メッセージがどこに書き込まれるかにまったく関心がなく、単にそれらを書き込みたいだけです。 -解決策は、やはり[ルールその1「渡されるように |#rule #1: Let It Be Passed to You]する」:クラスが必要とするデータをすべて渡すことです。 +解決策は再び [#ルール1 渡してもらう] です:クラスが必要とするすべてのデータは、それに渡します。 -つまり、コンストラクタでログへのパスを渡し、`Logger` オブジェクトを作成する際にそれを使用するということでしょうか。 +したがって、ログファイルへのパスをコンストラクタ経由で渡し、それを `Logger` オブジェクトを作成するときに使用するという意味ですか? ```php class NewsletterDistributor { public function __construct( - private string $file, // ⛔ NOT THIS WAY! + private string $file, // ⛔ これはダメ! ) { } @@ -351,7 +351,7 @@ class NewsletterDistributor $logger = new Logger($this->file); ``` -いいえ、このようなことはありません!パスは、`NewsletterDistributor` クラスが必要とするデータの中には含まれていません。実際には、`Logger` が必要とします。この違いがお分かりになりますか?`NewsletterDistributor` クラスは、ロガーそのものを必要としているのです。だから、それを渡すのです: +これはダメ!パスは `NewsletterDistributor` クラスが必要とするデータに**属していません**。それらは `Logger` が必要とするものです。違いを理解していますか?`NewsletterDistributor` クラスはロガー自体を必要としています。したがって、それを渡します: ```php class NewsletterDistributor @@ -365,35 +365,33 @@ class NewsletterDistributor { try { $this->sendEmails(); - $this->logger->log('Emails have been sent out'); + $this->logger->log('メールは送信されました'); } catch (Exception $e) { - $this->logger->log('An error occurred during the sending'); + $this->logger->log('送信中にエラーが発生しました'); throw $e; } } } ``` -`NewsletterDistributor` クラスの署名から、ロギングもその機能の一部であることは明らかです。そして、ロガーを別のものと交換する作業(おそらくテストのため)は、完全に些細なことです。 -さらに、`Logger` クラスのコンストラクタが変更されても、私たちのクラスには影響しません。 +これで、`NewsletterDistributor` クラスのシグネチャから、ロギングがその機能の一部であることが明らかになりました。そして、テストなどのためにロガーを別のものに交換するタスクは完全に簡単です。 さらに、`Logger` クラスのコンストラクタが変更された場合、それは私たちのクラスにまったく影響を与えません。 -ルールその2:自分のものは自分で取る .[#toc-rule-2-take-what-s-yours] ---------------------------------------------------- +ルール2:自分のものだけを受け取る +----------------- -惑わされずに、自分の依存関係を通さないようにしましょう。自分の依存関係を通すだけです。 +混乱しないでください。依存関係の依存関係を渡さないでください。自分の依存関係だけを渡してください。 -このおかげで、他のオブジェクトを使用するコードは、そのコンストラクタの変更に完全に依存しなくなります。そのAPIは、より真実に近いものになります。そして何よりも、これらの依存関係を他のものに置き換えるのは簡単なことなのです。 +これにより、他のオブジェクトを使用するコードは、それらのコンストラクタの変更から完全に独立します。そのAPIはより真実になります。そして何よりも、これらの依存関係を他のものに交換することが簡単になります。 -新しい家族 .[#toc-new-family-member] -------------------------------- +新しい家族の一員 +-------- -開発チームは、データベースに書き込む2つ目のロガーを作ることにしました。そこで、`DatabaseLogger` クラスを作成しました。つまり、`Logger` と`DatabaseLogger` の2つのクラスがあり、1つはファイルに書き込み、もう1つはデータベースに書き込みます...このネーミングは奇妙だと思いませんか? -`Logger` を`FileLogger` に改名した方が良いのではないでしょうか?間違いなくそうです。 +開発チームは、データベースに書き込む2番目のロガーを作成することを決定しました。したがって、`DatabaseLogger` クラスを作成します。したがって、`Logger` と `DatabaseLogger` の2つのクラスがあり、1つはファイルに書き込み、もう1つはデータベースに書き込みます… このネーミングに何か奇妙な点はありませんか?`Logger` を `FileLogger` に名前変更する方が良いのではないでしょうか?間違いなくそうです。 -でも、スマートにやってしまいましょう。元の名前でインターフェイスを作るのです: +しかし、賢く行います。元の名前の下にインターフェースを作成します: ```php interface Logger @@ -402,7 +400,7 @@ interface Logger } ``` -... 両方のロガーが実装することになります: +…両方のロガーが実装します: ```php class FileLogger implements Logger @@ -412,17 +410,17 @@ class DatabaseLogger implements Logger // ... ``` -このため、ロガーが使用される他のコードでは、何も変更する必要がありません。例えば、`NewsletterDistributor` クラスのコンストラクタは、`Logger` をパラメータとして要求することで、満足することができます。そして、どのインスタンスを渡すかは、私たち次第です。 +そして、これにより、ロガーが使用される残りのコードで何も変更する必要がなくなります。たとえば、`NewsletterDistributor` クラスのコンストラクタは、パラメータとして `Logger` を必要とすることに依然として満足します。そして、どのインスタンスを渡すかは私たち次第です。 -**インターフェース名に`Interface` や`I` という接頭辞をつけないのはそのためです。** そうでなければ、こんなにきれいに開発することはできません。 +**したがって、インターフェース名に `Interface` サフィックスや `I` プレフィックスを付けないでください。** そうでなければ、このようにコードをきれいに展開することはできません。 -ヒューストン、問題が発生した .[#toc-houston-we-have-a-problem] ------------------------------------------------- +ヒューストン、問題が発生しました +---------------- -ファイルベースであれデータベースベースであれ、アプリケーション全体を通してロガーのインスタンスを1つ用意し、何かが記録される場所に渡すだけで、何とかなるものですが、`Article` クラスの場合は全く違います。必要に応じてインスタンスを作成し、複数回作成することもあります。コンストラクタでデータベースの依存関係をどのように扱うか? +アプリケーション全体で、ファイルベースまたはデータベースベースのロガーの単一のインスタンスで十分であり、何かがログに記録される場所に単純に渡すことができますが、`Article` クラスの場合はまったく異なります。そのインスタンスは必要に応じて作成され、複数回作成されることもあります。そのコンストラクタでのデータベースへの依存関係をどのように処理しますか? -例えば、フォームを送信した後、記事をデータベースに保存するコントローラが考えられます: +例として、フォームが送信された後に記事をデータベースに保存する必要があるコントローラーがあります: ```php class EditController extends Controller @@ -437,30 +435,30 @@ class EditController extends Controller } ``` -解決策としては、`EditController` のコンストラクタにデータベースオブジェクトを渡し、`$article = new Article($this->db)` を使用する方法が考えられます。 +可能な解決策は明らかです:データベースオブジェクトをコンストラクタ経由で `EditController` に渡し、`$article = new Article($this->db)` を使用します。 -先ほどの`Logger` とファイルパスの場合と同様に、これは正しいアプローチではありません。データベースは`EditController` の依存関係ではなく、`Article` の依存関係です。データベースを渡すと、[ルール2「自分のものを取る |#rule #2: take what's yours]」に反します。`Article` クラスのコンストラクタが変更された場合(新しいパラメータが追加された場合)、インスタンスが作成される場所のコードを修正する必要があります。ウフフ。 +`Logger` とファイルパスの前のケースと同様に、これは正しいアプローチではありません。データベースは `EditController` の依存関係ではなく、`Article` の依存関係です。したがって、データベースを渡すことは [#ルール2:自分のものだけを受け取る] に反します。`Article` クラスのコンストラクタが変更された場合(新しいパラメータが追加された場合)、インスタンスが作成されるすべての場所でコードを調整する必要もあります。うーん。 -ヒューストン、どうする? +ヒューストン、何を提案しますか? -ルールその3:工場に任せる .[#toc-rule-3-let-the-factory-handle-it] ------------------------------------------------------- +ルール3:ファクトリに任せる +-------------- -隠れた依存関係を排除し、すべての依存関係を引数として渡すことで、私たちはより設定可能で柔軟なクラスを得ることができました。そのため、より柔軟なクラスを作成し、構成してくれるものが必要です。それをファクトリーと呼ぶことにします。 +隠れた依存関係を排除し、すべての依存関係を引数として渡すことで、より構成可能で柔軟なクラスが得られました。したがって、より柔軟なクラスを作成および構成する何か他のものが必要です。それをファクトリと呼びます。 -経験則では、クラスに依存性がある場合、そのインスタンスの作成はファクトリーに任せます。 +ルールは次のとおりです:クラスに依存関係がある場合、そのインスタンスの作成をファクトリに任せます。 -ファクトリーは、依存性注入の世界では、`new` 演算子に代わるスマートな存在です。 +ファクトリは、依存性の注入の世界における `new` 演算子のより賢い代替品です。 .[note] -ファクトリーメソッド*デザインパターンと混同しないようにご注意ください。 +デザインパターンである*ファクトリメソッド*と混同しないでください。これはファクトリの特定の利用方法を説明するものであり、このトピックとは関係ありません。 -工場 .[#toc-factory] ------------------- +ファクトリ +----- -ファクトリーとは、オブジェクトを生成したり設定したりするメソッドやクラスのことです。ここでは、`Article` を生成するクラスを`ArticleFactory` と名付け、以下のような形にします: +ファクトリは、オブジェクトを作成および構成するメソッドまたはクラスです。`Article` を作成するクラスを `ArticleFactory` と呼び、たとえば次のようになります: ```php class ArticleFactory @@ -477,7 +475,7 @@ class ArticleFactory } ``` -コントローラーでの使い方は以下のようになります: +コントローラーでの使用法は次のようになります: ```php class EditController extends Controller @@ -489,7 +487,7 @@ class EditController extends Controller public function formSubmitted($data) { - // let the factory create an object + // ファクトリにオブジェクトを作成させます $article = $this->articleFactory->create(); $article->title = $data->title; $article->content = $data->content; @@ -498,11 +496,11 @@ class EditController extends Controller } ``` -この時点で、`Article` クラスのコンストラクタのシグネチャが変更された場合、対応する必要があるのは、`ArticleFactory` 自身だけです。`EditController` のような`Article` オブジェクトを扱う他のすべてのコードは影響を受けません。 +この時点で `Article` クラスのコンストラクタのシグネチャが変更された場合、それに対応する必要があるコードの部分は `ArticleFactory` ファクトリ自体だけです。`Article` オブジェクトを操作する他のすべてのコード、たとえば `EditController` は、まったく影響を受けません。 -本当に良くなったのだろうかと疑問に思われるかもしれません。コードの量が増え、すべてが怪しく複雑に見えるようになりました。 +今、あなたは私たちが本当に助けになったのか疑問に思って、額を叩いているかもしれません。コードの量が増え、全体が疑わしく複雑に見え始めています。 -心配しないでください、すぐにNette DIコンテナにたどり着きます。そして、このコンテナにはいくつかのトリックがあり、依存性注入を使ったアプリケーションの構築を大幅に簡略化することができます。例えば、`ArticleFactory` クラスの代わりに、[シンプルなインターフェイスを書くだけ |factory]でよいのです: +心配しないでください、すぐにNette DIコンテナに到達します。そして、それは依存性の注入を使用するアプリケーションの構築を非常に簡素化する多くのトリックを持っています。たとえば、`ArticleFactory` クラスの代わりに、[単なるインターフェースを書くだけで十分です |factory]: ```php interface ArticleFactory @@ -511,18 +509,18 @@ interface ArticleFactory } ``` -でも、先を急ぐので、どうかご容赦ください :-) +しかし、それは先走りです、まだ待ってください :-) -概要 .[#toc-summary] ------------------- +まとめ +--- -この章の冒頭で、きれいなコードを設計するためのプロセスを紹介することを約束しました。必要なのは、クラスが +この章の冒頭で、クリーンなコードを設計する方法を示すことを約束しました。クラスには単純に -[- 必要な依存関係を渡す |#Rule #1: Let It Be Passed to You] -[- 逆に言えば、直接的に必要でないものは通さない |#Rule #2: Take What's Yours] -[- また、依存関係のあるオブジェクトはファクトリーで作成するのがベスト |#Rule #3: Let the Factory Handle it]であること +1) [必要とする依存性を渡す |#ルール1 渡してもらう] +2) [逆に、直接必要としないものは渡さない |#ルール2 自分のものだけを受け取る] +3) [そして、依存性を持つオブジェクトはファクトリで作成するのが最適であること |#ルール3 ファクトリに任せる] -一見すると、この3つのルールは遠大な結果をもたらすようには見えないかもしれませんが、コード設計の視点を根本から変えることにつながるのです。その価値はあるのでしょうか?古い習慣を捨て、依存性注入を一貫して使い始めた開発者は、このステップが彼らの職業生活における決定的な瞬間であると考えます。依存性注入によって、明快で保守性の高いアプリケーションの世界が開かれたのです。 +一見そうは見えないかもしれませんが、これらの3つのルールには広範囲にわたる影響があります。それらはコード設計に対する根本的に異なる見方につながります。それは価値がありますか?古い習慣を捨て、一貫して依存性の注入を使用し始めたプログラマーは、このステップをプロとしての人生における決定的な瞬間と見なしています。明確で保守可能なアプリケーションの世界が彼らに開かれました。 -しかし、そのコードが一貫して依存性注入を使用していない場合はどうでしょうか?静的メソッドやシングルトンに依存していたらどうでしょう?それは何か問題を引き起こすのでしょうか?そうです、[非常に基本的な |global-state]問題です。 +しかし、コードが一貫して依存性の注入を使用していない場合はどうなりますか?静的メソッドまたはシングルトンに基づいて構築されている場合はどうなりますか?それは何らかの問題を引き起こしますか?[非常に重大な問題を引き起こします |global-state]。 diff --git a/dependency-injection/ja/nette-container.texy b/dependency-injection/ja/nette-container.texy index 19c2ba3178..6d0d2bd667 100644 --- a/dependency-injection/ja/nette-container.texy +++ b/dependency-injection/ja/nette-container.texy @@ -1,10 +1,10 @@ -ネッテDIコンテナ -********* +Nette DIコンテナ +************ .[perex] -Nette DI は、最も興味深い Nette ライブラリの 1 つです。コンパイル済みのDIコンテナを生成して自動更新することができ、非常に高速で驚くほど簡単に設定することができます。 +Nette DIは、Netteの最も興味深いライブラリの1つです。非常に高速で驚くほど簡単に構成できるコンパイル済みDIコンテナを生成および自動更新できます。 -DIコンテナで作成するサービスは、通常、[NEON |neon:en:format]形式の設定ファイルを使って定義します。[前節で |container]手動で作成したコンテナは、次のように記述することになります。 +DIコンテナが作成するサービスの形式は、通常、[NEONフォーマット|neon:format]の構成ファイルを使用して定義します。[前の章|container]で手動で作成したコンテナは、次のように記述されます: ```neon parameters: @@ -19,16 +19,15 @@ services: - UserController ``` -表記は実に簡潔である。 +記述は本当に簡潔です。 -`ArticleFactory` と`UserController` クラスのコンストラクタで宣言された依存関係は、いわゆる[自動配線によって |autowiring] Nette DI 自身が見つけて渡すので、設定ファイルに何も指定する必要がありません。 -そのため、パラメータが変更されても、設定を何も変更する必要はありません。Netteが自動的にコンテナを再生成します。そこで、純粋にアプリケーションの開発に集中することができます。 +`ArticleFactory` および `UserController` クラスのコンストラクタで宣言されたすべての依存関係は、いわゆる[オートワイヤリング|autowiring]のおかげでNette DIによって自動的に検出され、渡されるため、構成ファイルに何も指定する必要はありません。したがって、パラメータが変更された場合でも、構成で何も変更する必要はありません。Netteコンテナは自動的に再生成されます。アプリケーションの開発に集中できます。 -もし、セッターを使って依存関係を渡したい場合は、[セットアップ |services#setup]セクションを使用してください。 +セッターを使用して依存関係を渡したい場合は、[setup |services#Setup]セクションを使用します。 -Nette DI はコンテナ用の PHP コードを直接生成します。その結果、`.php` ファイルが生成され、それを開いて学習することができます。これにより、コンテナがどのように動作するかを正確に確認することができます。また、IDEでデバッグし、ステップ実行することも可能です。そして最も重要なことは、生成される PHP は非常に高速です。 +Nette DIは、コンテナのPHPコードを直接生成します。結果は`.php`ファイルであり、開いて調べることができます。これにより、コンテナがどのように機能するかを正確に確認できます。IDEでデバッグしてステップ実行することもできます。そして最も重要なこと:生成されたPHPは非常に高速です。 -Nette DIは、提供されたインターフェイスに基づいて[ファクトリーコードを |factory]生成することもできます。したがって、`ArticleFactory` クラスの代わりに、アプリケーションでインターフェイスを作成するだけでよいのです。 +Nette DIは、提供されたインターフェースに基づいて[ファクトリ|factory]のコードを生成することもできます。したがって、`ArticleFactory` クラスの代わりに、アプリケーションでインターフェースを作成するだけで十分です: ```php interface ArticleFactory @@ -37,19 +36,19 @@ interface ArticleFactory } ``` -完全なサンプルは[GitHubで |https://github.com/nette-examples/di-example-doc]見ることができます。 +完全な例は[GitHub上|https://github.com/nette-examples/di-example-doc]にあります。 -スタンドアローンでの使用 .[#toc-standalone-use] ------------------------------------ +スタンドアロンでの使用 +----------- -Nette DI ライブラリをアプリケーションで利用するのは非常に簡単です。まず、Composerでインストールします(ZIPファイルをダウンロードするのは時代遅れなので)。 +Nette DIライブラリをアプリケーションに導入するのは非常に簡単です。まず、Composerでインストールします(zipファイルのダウンロードは時代遅れなので): ```shell composer require nette/di ``` -次のコードは、`config.neon` ファイルに保存された設定に従って、DI コンテナのインスタンスを作成します。 +次のコードは、`config.neon`ファイルに保存された構成に従ってDIコンテナのインスタンスを作成します: ```php $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp'); @@ -59,24 +58,23 @@ $class = $loader->load(function ($compiler) { $container = new $class; ``` -コンテナは一度だけ生成され、そのコードはキャッシュ(`__DIR__ . '/temp'` ディレクトリ)に書き込まれ、その後のリクエストではそこから読み込まれるだけである。 +コンテナは一度だけ生成され、そのコードはキャッシュ(ディレクトリ`__DIR__ . '/temp'`)に書き込まれ、後続のリクエストではそこから読み込まれるだけです。 -サービスの作成と取得には、`getService()` または`getByType()` のメソッドを使用します。このようにして、`UserController` オブジェクトを作成します。 +サービスを作成および取得するには、サービス名をパラメータとして`getService()`メソッドを使用するか、サービスタイプをパラメータとして`getByType()`メソッドを使用します。このようにして`UserController`オブジェクトを作成します: ```php -$database = $container->getByType(UserController::class); -$database->query('...'); +$controller = $container->getByType(UserController::class); +$controller->someMethod(); ``` -開発時には、オートリフレッシュモードを有効にしておくと便利です。このモードでは、クラスや設定ファイルが変更されると、コンテナが自動的に再生成されます。`ContainerLoader` のコンストラクタの第2引数として`true` を指定するだけです。 +開発中は、自動更新モードを有効にすると便利です。これにより、クラスまたは構成ファイルが変更されると、コンテナが自動的に再生成されます。`ContainerLoader`のコンストラクタで2番目の引数として`true`を指定するだけです。 ```php $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp', true); ``` -Netteフレームワークで使用する .[#toc-using-it-with-the-nette-framework] ------------------------------------------------------------ +Netteフレームワークでの使用 +---------------- -これまで紹介したように、Nette DI の利用は Nette Framework で書かれたアプリケーションに限らず、たった3行のコードでどこにでも展開することができます。 -ただし、Nette Frameworkでアプリケーションを開発する場合、コンテナの設定や作成は[Bootstrapが |application:en:bootstrap#toc-di-container-configuration]担当します。 +示したように、Nette DIの使用はNette Frameworkで書かれたアプリケーションに限定されず、わずか3行のコードでどこにでも展開できます。 ただし、Nette Frameworkでアプリケーションを開発している場合、コンテナの構成と作成は[Bootstrap |application:bootstrapping#DIコンテナの設定]が担当します。 diff --git a/dependency-injection/ja/passing-dependencies.texy b/dependency-injection/ja/passing-dependencies.texy index 22d913aed6..0422ec61a6 100644 --- a/dependency-injection/ja/passing-dependencies.texy +++ b/dependency-injection/ja/passing-dependencies.texy @@ -1,24 +1,24 @@ -依存関係の受け渡し -********* +依存性の受け渡し +******** <div class=perex> -引数(DI用語では「依存関係」)は、主に次のような方法でクラスに渡すことができます。 +引数、またはDIの用語では「依存関係」は、次の主な方法でクラスに渡すことができます: -* コンストラクタによる引数渡し -* メソッド(セッターと呼ばれる)による引数渡し -* プロパティの設定によるもの -* メソッド、アノテーション、属性によるもの * インジェクションによるもの +* コンストラクタによる受け渡し +* メソッド(いわゆるセッター)による受け渡し +* 変数の設定による受け渡し +* *inject*メソッド、アノテーション、または属性による受け渡し </div> -ここでは、さまざまなバリエーションを具体的な例で説明します。 +次に、具体的な例で各バリアントを示します。 -コンストラクタ・インジェクション .[#toc-constructor-injection] -============================================== +コンストラクタによる受け渡し +============== -依存関係は、オブジェクトが作成されるときにコンストラクタに引数として渡されます。 +依存関係は、オブジェクトの作成時にコンストラクタの引数として渡されます: ```php class MyClass @@ -34,9 +34,9 @@ class MyClass $obj = new MyClass($cache); ``` -この形式は、クラスが機能するために絶対に必要な必須の依存関係を指定するのに便利です。なぜなら、その依存関係がなければインスタンスを作成することができないからです。 +この形式は、クラスが機能するために不可欠な必須の依存関係に適しています。なぜなら、それらなしではインスタンスを作成できないからです。 -PHP 8.0 以降、機能的に同等な短い形式の記法を使用することができます。 +PHP 8.0以降では、より短い形式の記述([コンストラクタプロパティプロモーション |https://blog.nette.org/en/php-8-0-complete-overview-of-news#toc-constructor-property-promotion])を使用できます。これは機能的に同等です: ```php // PHP 8.0 @@ -49,7 +49,7 @@ class MyClass } ``` -PHP 8.1 以降では、プロパティにフラグ`readonly` を指定して、 そのプロパティの内容を変更しないことを宣言することができます。 +PHP 8.1以降では、変数を`readonly`フラグでマークできます。これは、変数の内容が変更されないことを宣言します: ```php // PHP 8.1 @@ -62,13 +62,13 @@ class MyClass } ``` -DI コンテナは、[自動配線によって |autowiring]依存関係をコンストラクタに渡します。この方法で渡すことができない引数(文字列、数値、ブール値など)は、[設定に書きます |services#Arguments]。 +DIコンテナは、[オートワイヤリング |autowiring]を使用してコンストラクタに依存関係を自動的に渡します。このように渡すことができない引数(文字列、数値、ブール値など)は、[設定ファイルに記述します |services#引数]。 -コンストラクター地獄 .[#toc-constructor-hell] ------------------------------------ +コンストラクタ地獄 +--------- -コンストラクタ地獄とは、コンストラクタが依存性を要求する親クラスを子が継承し、その子も依存性を要求する状況を指す言葉である。また、親クラスの依存関係を引き継がなければなりません。 +*コンストラクタ地獄*という用語は、子が親クラスから継承し、その親クラスのコンストラクタが依存関係を必要とし、同時に子も依存関係を必要とする状況を指します。その際、親の依存関係も引き継いで渡す必要があります: ```php abstract class BaseClass @@ -85,7 +85,7 @@ final class MyClass extends BaseClass { private Database $db; - // ⛔ CONSTRUCTOR HELL + // ⛔ コンストラクタ地獄 public function __construct(Cache $cache, Database $db) { parent::__construct($cache); @@ -94,11 +94,11 @@ final class MyClass extends BaseClass } ``` -問題は、新しい依存関係が追加されたときなど、`BaseClass` クラスのコンストラクタを変更したいときに発生します。そうすると、子クラスのコンストラクタもすべて変更しなければならない。そのため、このような修正は地獄のようなものです。 +問題は、`BaseClass`クラスのコンストラクタを変更したいときに発生します。たとえば、新しい依存関係が追加された場合です。その場合、すべての子のコンストラクタも変更する必要があります。これにより、そのような変更は地獄になります。 -これを防ぐにはどうしたらいいのでしょうか?解決策は、**継承よりも構成を優先させる**ことです。 +これをどのように防ぐか?解決策は、**[継承よりもコンポジションを |faq#なぜ継承よりもコンポジションが優先されるのですか]優先すること**です。 -そこで、コードを違った形で設計してみましょう。抽象的な`Base*` クラスは避けることにします。`MyClass` が`BaseClass` を継承して機能を得る代わりに、その機能は依存関係として渡されるようにします。 +つまり、コードを異なる方法で設計します。[抽象 |nette:introduction-to-object-oriented-programming#抽象クラス] `Base*` クラスを避けます。`MyClass` が `BaseClass` から継承することによって特定の機能を取得する代わりに、この機能を依存関係として渡してもらいます: ```php final class SomeFunctionality @@ -125,10 +125,10 @@ final class MyClass ``` -セッター・インジェクション .[#toc-setter-injection] -====================================== +セッターによる受け渡し +=========== -依存関係は、プライベート・プロパティに格納するメソッドを呼び出すことで渡されます。これらのメソッドの通常の命名規則は、`set*()` という形式です。そのため、セッターと呼ばれていますが、もちろん、他の名前で呼ぶこともできます。 +依存関係は、それらをプライベート変数に保存するメソッドを呼び出すことによって渡されます。これらのメソッドの一般的な命名規則は`set*()`形式であるため、セッターと呼ばれますが、もちろん他の名前を付けることもできます。 ```php class MyClass @@ -145,9 +145,9 @@ $obj = new MyClass; $obj->setCache($cache); ``` -このメソッドは、オブジェクトが実際にそれを受け取る(つまり、ユーザーがメソッドを呼び出す)ことが保証されていないため、クラス機能に必要のないオプションの依存関係に対して便利です。 +この方法は、クラスの機能に必須ではないオプションの依存関係に適しています。なぜなら、オブジェクトが実際に依存関係を受け取る(つまり、ユーザーがメソッドを呼び出す)ことは保証されていないからです。 -同時に、このメソッドによって、依存関係を変更するためにセッターを繰り返し呼び出すことができます。これが望ましくない場合は、メソッドにチェックを加えるか、 PHP 8.1 以降は`readonly` フラグでプロパティ`$cache` をマークします。 +同時に、この方法はセッターを繰り返し呼び出して依存関係を変更することを可能にします。これが望ましくない場合は、メソッドにチェックを追加するか、PHP 8.1以降ではプロパティ`$cache`を`readonly`フラグでマークします。 ```php class MyClass @@ -156,7 +156,7 @@ class MyClass public function setCache(Cache $cache): void { - if ($this->cache) { + if (isset($this->cache)) { throw new RuntimeException('The dependency has already been set'); } $this->cache = $cache; @@ -164,21 +164,20 @@ class MyClass } ``` -setter の呼び出しは、DI コンテナの設定[セクション setup |services#Setup] で定義します。また、ここでも autowiring による依存関係の自動的な受け渡しが行われています。 +セッターの呼び出しは、DIコンテナの構成の[setupキー |services#Setup]で定義します。ここでも、オートワイヤリングによる依存関係の自動受け渡しが利用されます: ```neon services: - - - create: MyClass + - create: MyClass setup: - setCache ``` -プロパティ・インジェクション .[#toc-property-injection] -========================================= +変数の設定による受け渡し +============ -依存関係は直接プロパティに渡されます。 +依存関係は、メンバー変数に直接書き込むことによって渡されます: ```php class MyClass @@ -190,28 +189,27 @@ $obj = new MyClass; $obj->cache = $cache; ``` -なぜなら、プロパティは`public` として宣言されなければならないからです。 したがって、渡された依存関係が実際に指定された型になるかどうかを制御することはできませんし (これは PHP 7.4 以前も同様でした)、新しく割り当てられた依存関係に独自のコードで対応する能力、 たとえばその後の変更を防止する能力も失われています。同時に、このプロパティはクラスのパブリックインターフェイスの一部となり、 望ましくないかもしれません。 +この方法は、メンバー変数を`public`として宣言する必要があるため、不適切と見なされます。したがって、渡された依存関係が実際に指定された型であること(PHP 7.4以前に適用)を制御できず、新しく割り当てられた依存関係に独自のコードで応答する可能性(たとえば、後続の変更を防ぐ)を失います。同時に、変数はクラスのパブリックインターフェースの一部となり、これは望ましくない場合があります。 -変数の設定は、[setupセクションの |services#Setup]DIコンテナ構成で定義されます。 +変数の設定は、DIコンテナの構成の[setupセクション |services#Setup]で定義します: ```neon services: - - - create: MyClass + - create: MyClass setup: - $cache = @\Cache ``` -インジェクト .[#toc-inject] -===================== +Inject +====== -前の3つの方法は、一般的にすべてのオブジェクト指向言語で有効ですが、メソッド、アノテーション、*inject*属性による注入は、Netteプレゼンターに特有の方法です。これらは[別章で |best-practices:en:inject-method-attribute]説明します。 +前の3つの方法はすべてのオブジェクト指向言語で一般的に適用されますが、*inject*メソッド、アノテーション、または属性による注入は、NetteのPresenterに特有のものです。[別の章 |best-practices:inject-method-attribute]で説明されています。 -どの方法を選択するか? .[#toc-which-way-to-choose] -======================================= +どの方法を選択するか? +=========== -- コンストラクタは、クラスが機能するために必要な必須の依存関係に対して適しています。 -- 一方、セッターはオプションの依存関係、または変更可能な依存関係に適しています。 -- パブリック変数は推奨しません。 +- コンストラクタは、クラスが機能するために不可欠な必須の依存関係に適しています +- セッターは、オプションの依存関係、または後で変更できる可能性のある依存関係に適しています +- public変数は適していません diff --git a/dependency-injection/ja/services.texy b/dependency-injection/ja/services.texy index f4b2054bee..b8fc4824f9 100644 --- a/dependency-injection/ja/services.texy +++ b/dependency-injection/ja/services.texy @@ -1,33 +1,33 @@ -サービス定義 -****** +サービスの定義 +******* .[perex] -コンフィギュレーションは、カスタムサービスの定義を配置する場所です。これは、セクション`services` で行います。 +設定は、DIコンテナに個々のサービスをどのように組み立て、他の依存関係とどのように接続するかを教える場所です。Netteは、これを達成するための非常に明確でエレガントな方法を提供します。 -例えば、これは`database` という名前のサービスを作成する方法で、これはクラス`PDO` のインスタンスになります。 +NEON形式の設定ファイルの`services`セクションは、独自のサービスとその構成を定義する場所です。`PDO`クラスのインスタンスを表す`database`という名前のサービスを定義する簡単な例を見てみましょう: ```neon services: database: PDO('sqlite::memory:') ``` -サービスの名前付けは,サービスを[参照 |#Referencing Services]できるようにするために行います.もしサービスが参照されないのであれば、名前を付ける必要はありません。そこで、名前の代わりに箇条書きを使っているだけです。 +上記の構成は、[DIコンテナ|container]で次のファクトリメソッドになります: -```neon -services: - - PDO('sqlite::memory:') # anonymous service +```php +public function createServiceDatabase(): PDO +{ + return new PDO('sqlite::memory:'); +} ``` -1行のエントリーを複数行に分割して、[setupなどの |#setup]キーを追加できるようにすることも可能です。`create:` キーのエイリアスは、`factory:` です。 +サービス名を使用すると、設定ファイルの他の部分で`@サービス名`の形式で参照できます。サービスに名前を付ける必要がない場合は、単に箇条書きを使用できます: ```neon services: - database: - create: PDO('sqlite::memory:') - setup: ... + - PDO('sqlite::memory:') ``` -次に、DIコンテナからサービスを取り出すには、名前を指定して`getService()` というメソッドを使うか、種類を指定して`getByType()` というメソッドを使うとよいでしょう。 +DIコンテナからサービスを取得するには、サービス名をパラメータとして`getService()`メソッドを使用するか、サービスタイプをパラメータとして`getByType()`メソッドを使用できます: ```php $database = $container->getService('database'); @@ -35,26 +35,28 @@ $database = $container->getByType(PDO::class); ``` -サービスの作成 .[#toc-creating-a-service] -================================== +サービスの作成 +======= -多くの場合、サービスの作成は単にクラスのインスタンスを作成することで行います。 +ほとんどの場合、特定のクラスのインスタンスを作成するだけでサービスを作成します。例: ```neon services: database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -この場合、[DIコンテナ |container]内にファクトリーメソッドが生成されます。 +構成を他のキーで拡張する必要がある場合は、定義を複数行に分割できます: -```php -public function createServiceDatabase(): PDO -{ - return new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'secret'); -} +```neon +services: + database: + create: PDO('sqlite::memory:') + setup: ... ``` -また、[引数の |#Arguments]受け渡しにキー`arguments` を使うこともできます。 +`create`キーにはエイリアス`factory`があり、両方のバリアントが実際によく使用されます。ただし、`create`を使用することをお勧めします。 + +コンストラクタまたは作成メソッドの引数は、代わりに`arguments`キーに記述することもできます: ```neon services: @@ -63,294 +65,303 @@ services: arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret] ``` -静的メソッドは、サービスを作成することもできます。 +サービスは、クラスのインスタンスを単純に作成するだけでなく、静的メソッドや他のサービスのメソッドを呼び出すことによっても作成できます: ```neon services: - database: My\Database::create(root, secret) + database: DatabaseFactory::create() + router: @routerFactory::create() ``` -PHP のコードに対応します。 +単純化のために`->`の代わりに`::`が使用されていることに注意してください。[#表現手段]を参照してください。これらのファクトリメソッドが生成されます: ```php public function createServiceDatabase(): PDO { - return My\Database::create('root', 'secret'); + return DatabaseFactory::create(); +} + +public function createServiceRouter(): RouteList +{ + return $this->getService('routerFactory')->create(); } ``` -静的メソッド`My\Database::create()` は、DI コンテナが知る必要のある戻り値が定義されていることが前提です。もしそれがない場合は、その型を設定に書き込みます。 +DIコンテナは、作成されたサービスのタイプを知る必要があります。指定された戻り値の型を持たないメソッドを使用してサービスを作成する場合、このタイプを構成で明示的に指定する必要があります: ```neon services: database: - create: My\Database::create(root, secret) + create: DatabaseFactory::create() type: PDO ``` -Nette DIは、非常に強力な表現機能を備えており、ほとんど何でも書くことができます。例えば、他のサービスを[参照して |#Referencing Services]、そのメソッドを呼び出すことができます。簡単のために、`->` の代わりに`::` を使用します。 + +引数 +========= + +コンストラクタとメソッドに引数を渡す方法は、PHP自体と非常によく似ています: ```neon services: - routerFactory: App\Router\Factory - router: @routerFactory::create() + database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -PHPのコードに対応します。 - -```php -public function createServiceRouterFactory(): App\Router\Factory -{ - return new App\Router\Factory; -} +読みやすくするために、引数を個別の行に分割できます。この場合、カンマの使用はオプションです: -public function createServiceRouter(): Router -{ - return $this->getService('routerFactory')->create(); -} +```neon +services: + database: PDO( + 'mysql:host=127.0.0.1;dbname=test' + root + secret + ) ``` -PHPのようにメソッドコールを連鎖させることができます。 +引数に名前を付けることもでき、その順序を気にする必要はありません: ```neon services: - foo: FooFactory::build()::get() + database: PDO( + username: root + password: secret + dsn: 'mysql:host=127.0.0.1;dbname=test' + ) ``` -PHPのコードに対応しています。 +一部の引数を省略してデフォルト値を使用するか、[オートワイヤリング|autowiring]を使用してサービスを挿入する場合は、アンダースコアを使用します: -```php -public function createServiceFoo() -{ - return FooFactory::build()->get(); -} +```neon +services: + foo: Foo(_, %appDir%) ``` +引数としてサービスを渡したり、パラメータを使用したり、その他多くのことができます。[#表現手段]を参照してください。 + -引数 .[#toc-arguments] -==================== +Setup +===== -名前付きパラメータは引数の受け渡しに使うこともできます。 +`setup`セクションでは、サービスの作成時に呼び出すメソッドを定義します。 ```neon services: - database: PDO( - 'mysql:host=127.0.0.1;dbname=test' # positional - username: root # named - password: secret # named - ) + database: + create: PDO(%dsn%, %user%, %password%) + setup: + - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) ``` -引数を複数行に分ける場合、カンマの使用は任意である。 +これはPHPでは次のようになります: -もちろん、[他のサービスや |#Referencing Services] [パラメータを |configuration#parameters]引数として使用することもできます。 +```php +public function createServiceDatabase(): PDO +{ + $service = new PDO('...', '...', '...'); + $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + return $service; +} +``` + +メソッドの呼び出しに加えて、プロパティに値を渡すこともできます。配列への要素の追加もサポートされており、NEON構文と衝突しないように引用符で囲む必要があります: ```neon services: - - Foo(@anotherService, %appDir%) + foo: + create: Foo + setup: + - $value = 123 + - '$onClick[]' = [@bar, clickHandler] ``` -PHPのコードに対応します。 +これはPHPコードでは次のようになります: ```php -public function createService01(): Foo +public function createServiceFoo(): Foo { - return new Foo($this->getService('anotherService'), '...'); + $service = new Foo; + $service->value = 123; + $service->onClick[] = [$this->getService('bar'), 'clickHandler']; + return $service; } ``` -第一引数が[自動生成で |autowiring]、第二引数を指定したい場合は、第一引数を省略して`_` character, for example `Foo(_, %appDir%)`. あるいは、第二引数のみを名前付きパラメータとして渡すのがよいでしょう。例えば、`Foo(path: %appDir%)`. - -Nette DI と NEON フォーマットは、ほとんど何でも書くことができる非常に強力な表現力を持っています。そのため、引数には新しく作成したオブジェクトを指定したり、静的メソッドや他のサービスのメソッドを呼び出したり、特殊な記法を使ってグローバルな関数を呼び出すこともできます。 +ただし、setupでは静的メソッドや他のサービスのメソッドを呼び出すこともできます。現在のサービスを引数として渡す必要がある場合は、`@self`として指定します: ```neon services: - analyser: My\Analyser( - FilesystemIterator(%appDir%) # create object - DateTime::createFromFormat('Y-m-d') # call static method - @anotherService # passing another service - @http.request::getRemoteAddress() # calling another service method - ::getenv(NetteMode) # call a global function - ) + foo: + create: Foo + setup: + - My\Helpers::initializeFoo(@self) + - @anotherService::setFoo(@self) ``` -PHPのコードに対応します。 +単純化のために`->`の代わりに`::`が使用されていることに注意してください。[#表現手段]を参照してください。このようなファクトリメソッドが生成されます: ```php -public function createServiceAnalyser(): My\Analyser +public function createServiceFoo(): Foo { - return new My\Analyser( - new FilesystemIterator('...'), - DateTime::createFromFormat('Y-m-d'), - $this->getService('anotherService'), - $this->getService('http.request')->getRemoteAddress(), - getenv('NetteMode') - ); + $service = new Foo; + My\Helpers::initializeFoo($service); + $this->getService('anotherService')->setFoo($service); + return $service; } ``` -特殊機能 .[#toc-special-functions] ------------------------------- - -引数で特殊関数を使用し、値のキャストやネゲートを行うこともできます。 +表現手段 +==== --`not(%arg%)` 負論理 --`bool(%arg%)` 無損失な bool へのキャスト --`int(%arg%)` int へのロスレスキャスト --`float(%arg%)` float へのロスレスキャスト --`string(%arg%)` ロスレス キャストから文字列 +Nette DIは、ほとんど何でも記述できる非常に豊富な表現手段を提供します。したがって、構成ファイルで[パラメータ |configuration#パラメータ]を使用できます: ```neon -services: - - Foo( - id: int(::getenv('ProjectId')) - productionMode: not(%debugMode%) - ) -``` - -ロスレス書き換えは、通常の PHP の書き換え (`(int)` など) とは異なり、数値以外の値では例外がスローされます。 +# パラメータ +%wwwDir% -引数として、複数のサービスを渡すことができます。特定の型 (クラスやインターフェイス) のすべてのサービスの配列は、関数`typed()` で作成されます。この関数では,自動配線を無効にしているサービスは省略されます.また,複数のタイプをカンマで区切って指定することもできます. +# キーの下のパラメータ値 +%mailer.user% -```neon -services: - - BarsDependent( typed(Bar) ) +# 文字列内のパラメータ +'%wwwDir%/images' ``` -また,[autowiringを |autowiring#Collection of Services]使用して,自動的にサービスの配列を渡すこともできます. - -ある[タグを |#tags]持つすべてのサービスの配列を関数`tagged()` で作成します.タグはカンマで区切って複数指定することができます。 +さらに、オブジェクトを作成し、メソッドと関数を呼び出します: ```neon -services: - - LoggersDependent( tagged(logger) ) -``` +# オブジェクトの作成 +DateTime() +# 静的メソッドの呼び出し +Collator::create(%locale%) -参照先サービス .[#toc-referencing-services] -==================================== +# PHP関数の呼び出し +::getenv(DB_USER) +``` -個々のサービスの参照には、`@` and name, so for example `@database` を使用します。 +サービスを名前またはタイプで参照します: ```neon -services: - - create: Foo(@database) - setup: - - setCacheStorage(@cache.storage) +# 名前によるサービス +@database + +# タイプによるサービス +@Nette\Database\Connection ``` -PHPのコードに対応します。 +ファーストクラスの呼び出し可能構文を使用します: .{data-version:3.2.0} -```php -public function createService01(): Foo -{ - $service = new Foo($this->getService('database')); - $service->setCacheStorage($this->getService('cache.storage')); - return $service; -} +```neon +# コールバックの作成、[@user, logout]に類似 +@user::logout(...) ``` -匿名サービスでもコールバックを使って参照することができます。ただし、[自動配線の |autowiring]関係で通常は必要ありません。 +定数を使用します: ```neon -services: - - create: Foo(@Nette\Database\Connection) # or @\PDO - setup: - - setCacheStorage(@cache.storage) +# クラス定数 +FilesystemIterator::SKIP_DOTS + +# グローバル定数はPHP関数constant()で取得します +::constant(PHP_VERSION) ``` +メソッド呼び出しはPHPと同様に連鎖させることができます。ただし、単純化のために`->`の代わりに`::`が使用されます: -セットアップ .[#toc-setup] -==================== +```neon +DateTime()::format('Y-m-d') +# PHP: (new DateTime())->format('Y-m-d') -セットアップでは、サービスを作成する際に呼び出されるメソッドをリストアップします。 +@http.request::getUrl()::getHost() +# PHP: $this->getService('http.request')->getUrl()->getHost() +``` + +これらの式は、[#サービスの作成]、[#引数]、[#setup]セクション、または[パラメータ |configuration#パラメータ]でどこでも使用できます: ```neon +parameters: + ipAddress: @http.request::getRemoteAddress() + services: database: - create: PDO(%dsn%, %user%, %password%) + create: DatabaseFactory::create( @anotherService::getDsn() ) setup: - - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) + - initialize( ::getenv('DB_USER') ) ``` -PHPのコードに対応します。 -```php -public function createServiceDatabase(): PDO -{ - $service = new PDO('...', '...', '...'); - $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - return $service; -} -``` +特殊関数 +---- -プロパティを設定することも可能です。配列への要素の追加もサポートされており、NEON の構文と衝突しないように引用符で囲んで記述する必要があります。 +構成ファイルでは、次の特殊関数を使用できます: +- `not()` 値の否定 +- `bool()`, `int()`, `float()`, `string()` 指定された型へのロスレス型キャスト +- `typed()` 指定された型のすべてのサービスの配列を作成します +- `tagged()` 指定されたタグを持つすべてのサービスの配列を作成します ```neon services: - foo: - create: Foo - setup: - - $value = 123 - - '$onClick[]' = [@bar, clickHandler] + - Foo( + id: int(::getenv('ProjectId')) + productionMode: not(%debugMode%) + ) ``` -PHP のコードに対応します。 +PHPの従来の型キャスト(例:`(int)`)とは異なり、ロスレス型キャストは非数値に対して例外をスローします。 -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - $service->value = 123; - $service->onClick[] = [$this->getService('bar'), 'clickHandler']; - return $service; -} +`typed()`関数は、指定された型(クラスまたはインターフェース)のすべてのサービスの配列を作成します。オートワイヤリングが無効になっているサービスは除外されます。カンマで区切って複数の型を指定することもできます。 + +```neon +services: + - BarsDependent( typed(Bar) ) ``` -ただし、静的メソッドや他のサービスのメソッドもセットアップで呼び出すことができます。それらには、実際のサービスを`@self` として渡します。 +特定の型のサービスの配列は、[オートワイヤリング |autowiring#サービスの配列]を使用して自動的に引数として渡すこともできます。 +`tagged()`関数は、特定のタグを持つすべてのサービスの配列を作成します。ここでも、カンマで区切って複数のタグを指定できます。 ```neon services: - foo: - create: Foo - setup: - - My\Helpers::initializeFoo(@self) - - @anotherService::setFoo(@self) + - LoggersDependent( tagged(logger) ) ``` -PHPのコードに対応する。 -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - My\Helpers::initializeFoo($service); - $this->getService('anotherService')->setFoo($service); - return $service; -} +オートワイヤリング +========= + +`autowired`キーを使用すると、特定のサービスのオートワイヤリングの動作に影響を与えることができます。詳細については、[オートワイヤリングに関する章|autowiring]を参照してください。 + +```neon +services: + foo: + create: Foo + autowired: false # サービスfooはオートワイヤリングから除外されます ``` -オートワイヤリング .[#toc-autowiring] +遅延サービス .{data-version:3.2.4} ============================ -autowiredキーは、あるサービスを自動配線から除外したり、その動作に影響を与えたりするために使用されます。詳細については、「自動配線」の[章を |autowiring]参照してください。 +遅延読み込みは、サービスが実際に必要になるまでその作成を延期する技術です。グローバル設定では、すべてのサービスに対して[遅延作成を有効にする |configuration#遅延サービス]ことができます。個々のサービスについては、この動作を上書きできます: ```neon services: foo: create: Foo - autowired: false # foo is removed from autowiring + lazy: false ``` +サービスが遅延として定義されている場合、DIコンテナから要求されると、特別なプレースホルダーオブジェクトが返されます。これは実際のサービスと同じように見え、動作しますが、実際の初期化(コンストラクタとセットアップの呼び出し)は、そのメソッドまたはプロパティのいずれかが最初に呼び出されたときにのみ行われます。 + +.[note] +遅延読み込みは、ユーザー定義クラスにのみ使用でき、内部PHPクラスには使用できません。PHP 8.4以降が必要です。 + -タグ .[#toc-tags] -=============== +タグ +==== -個々のサービスには、タグという形でユーザー情報を付加することができます。 +タグは、サービスに追加情報を提供するために使用されます。サービスに1つ以上のタグを追加できます: ```neon services: @@ -360,7 +371,7 @@ services: - cached ``` -タグは値を持つこともできます。 +タグは値を持つこともできます: ```neon services: @@ -370,26 +381,26 @@ services: logger: monolog.logger.event ``` -特定のタグを持つサービスの配列は,関数`tagged()` を使って引数として渡すことができます.カンマで区切って複数のタグを指定することもできます. +特定のタグを持つすべてのサービスを取得するには、`tagged()`関数を使用できます: ```neon services: - LoggersDependent( tagged(logger) ) ``` -サービス名は,DIコンテナから`findByTag()` というメソッドで取得することができる. +DIコンテナでは、`findByTag()`メソッドを使用して特定のタグを持つすべてのサービスのリストを取得できます: ```php $names = $container->findByTag('logger'); -// $names is an array containing the service name and tag value -// i.e. ['foo' => 'monolog.logger.event', ...] +// $names はサービス名とタグ値を含む配列です +// 例:['foo' => 'monolog.logger.event', ...] ``` -インジェクトモード .[#toc-inject-mode] -============================= +Injectモード +========= -`inject: true` フラグは、[inject |best-practices:en:inject-method-attribute#Inject Attributes]アノテーションと[inject*() |best-practices:en:inject-method-attribute#inject Methods]メソッドによるパブリック変数経由の依存関係の受け渡しを有効にするために使用されます。 +`inject: true`フラグを使用すると、[inject |best-practices:inject-method-attribute#Inject 属性]アノテーションを持つパブリック変数と[inject*() |best-practices:inject-method-attribute#inject メソッド]メソッドを介した依存関係の受け渡しが有効になります。 ```neon services: @@ -398,13 +409,13 @@ services: inject: true ``` -デフォルトでは、`inject` はプレゼンターに対してのみ有効です。 +デフォルトでは、`inject`はPresenterに対してのみ有効化されます。 -サービスの変更 .[#toc-modification-of-services] -======================================== +サービスの変更 +======= -DIコンテナには、ビルトインまたは[拡張機能 |#di-extensions]によって追加されたサービスが多数存在します。これらのサービスの定義は、コンフィギュレーションで変更することができます。例えば、サービス`application.application` 、デフォルトではオブジェクト`Nette\Application\Application` 、クラスを変更することができます。 +DIコンテナには、組み込みまたは[ユーザー拡張機能|extensions]を介して追加された多くのサービスが含まれています。これらのサービスの定義は、構成で直接変更できます。たとえば、通常は`Nette\Application\Application`であるサービス`application.application`のクラスを別のものに変更できます: ```neon services: @@ -413,9 +424,9 @@ services: alteration: true ``` -`alteration` フラグは情報提供であり、既存のサービスを変更するだけであることを示します。 +`alteration`フラグは情報提供であり、既存のサービスを変更しているだけであることを示します。 -また、セットアップを追加することもできます。 +セットアップを追加することもできます: ```neon services: @@ -426,7 +437,7 @@ services: - '$onStartup[]' = [@resource, init] ``` -サービスを書き換える際、元の引数や設定項目、タグを削除したい場合があります。そのために、`reset` 。 +サービスを上書きするときに、元の引数、セットアップ項目、またはタグを削除したい場合は、`reset`を使用します: ```neon services: @@ -439,7 +450,7 @@ services: - tags ``` -拡張で追加されたサービスもコンテナから削除することができます. +拡張機能によって追加されたサービスを削除したい場合は、次のようにします: ```neon services: diff --git a/dependency-injection/pl/@home.texy b/dependency-injection/pl/@home.texy index 37148286cc..bb16bf32da 100644 --- a/dependency-injection/pl/@home.texy +++ b/dependency-injection/pl/@home.texy @@ -1,24 +1,21 @@ -Dependency Injection -******************** +Nette DI +******** .[perex] -Dependency Injection jest wzorcem projektowym, który fundamentalnie zmieni sposób w jaki patrzysz na kod i rozwój. Otwiera on drogę do świata czysto zaprojektowanych i trwałych aplikacji. +Dependency Injection to wzorzec projektowy, który zasadniczo zmieni Twój pogląd na kod i rozwój. Otworzy Ci drogę do świata czysto zaprojektowanych i łatwych w utrzymaniu aplikacji. -- [Co to jest Wstrzykiwanie Zależności? |introduction] +- [Co to jest Dependency Injection? |introduction] - [Stan globalny i singletony |global-state] -- [Przekazywanie zależności|passing-dependencies] +- [Przekazywanie zależności |passing-dependencies] - [Co to jest kontener DI? |container] -- [Najczęściej zadawane pytania |faq] - +- [Często zadawane pytania|faq] -Nette DI --------- -Pakiet `nette/di` dostarcza niezwykle zaawansowany, skompilowany kontener DI dla PHP. +Pakiet `nette/di` dostarcza niezwykle zaawansowany kompilowany kontener DI dla PHP. -- [[Nette DI Kontener |nette-container] +- [Kontener Nette DI |nette-container] - [Konfiguracja |configuration] -- [Definicje usług |services] +- [Definiowanie usług |services] - [Autowiring |autowiring] -- [Fabryki generowane |factory] -- [Tworzenie rozszerzeń dla Nette DI |extensions] +- [Generowane fabryki |factory] +- [Tworzenie rozszerzeń dla Nette DI|extensions] diff --git a/dependency-injection/pl/@left-menu.texy b/dependency-injection/pl/@left-menu.texy index 58f1a5f291..a4acad0599 100644 --- a/dependency-injection/pl/@left-menu.texy +++ b/dependency-injection/pl/@left-menu.texy @@ -1,17 +1,17 @@ -Wtrysk zależności -***************** +Dependency Injection +******************** - [Co to jest DI? |introduction] - [Stan globalny i singletony |global-state] - [Przekazywanie zależności |passing-dependencies] - [Co to jest kontener DI? |container] -- [Najczęściej zadawane pytania |faq] +- [Często zadawane pytania|faq] Nette DI -------- -- [Pojemnik Nette DI |nette-container] +- [Kontener Nette DI |nette-container] - [Konfiguracja |configuration] - [Definiowanie usług |services] -- [Okablowanie |autowiring] -- [Wytworzone fabryki |factory] -- [Tworzenie rozszerzeń dla Nette DI |extensions] +- [Autowiring |autowiring] +- [Generowane fabryki |factory] +- [Tworzenie rozszerzeń dla Nette DI|extensions] diff --git a/dependency-injection/pl/@meta.texy b/dependency-injection/pl/@meta.texy new file mode 100644 index 0000000000..61ac92d1af --- /dev/null +++ b/dependency-injection/pl/@meta.texy @@ -0,0 +1 @@ +{{sitename: Dokumentacja Nette}} diff --git a/dependency-injection/pl/autowiring.texy b/dependency-injection/pl/autowiring.texy index e9c7612788..9b3ba7e86c 100644 --- a/dependency-injection/pl/autowiring.texy +++ b/dependency-injection/pl/autowiring.texy @@ -2,9 +2,9 @@ Autowiring ********** .[perex] -Autowiring to świetna funkcja, która może automatycznie przekazywać wymagane usługi do konstruktora i innych metod, więc nie musimy ich w ogóle pisać. Dzięki temu można zaoszczędzić sporo czasu. +Autowiring to świetna funkcja, która potrafi automatycznie przekazywać do konstruktora i innych metod wymagane usługi, dzięki czemu nie musimy ich w ogóle pisać. Oszczędza to mnóstwo czasu. -Dzięki temu możemy pominąć zdecydowaną większość argumentów przy pisaniu definicji usług. Zamiast: +Dzięki temu możemy pominąć zdecydowaną większość argumentów podczas pisania definicji usług. Zamiast: ```neon services: @@ -18,7 +18,7 @@ services: articles: Model\ArticleRepository ``` -Autowiring jest oparty na typie, więc aby działał, klasa `ArticleRepository` musi być zdefiniowana w ten sposób: +Autowiring kieruje się typami, więc aby działał, klasa `ArticleRepository` musi być zdefiniowana mniej więcej tak: ```php namespace Model; @@ -30,22 +30,22 @@ class ArticleRepository } ``` -Aby użyć autowiring, w kontenerze musi być **tylko jedna usługa** dla każdego typu. Gdyby było ich więcej niż jeden, autowiring nie wiedziałby, który z nich przekazać i rzuciłby wyjątek: +Aby można było użyć autowiringu, dla każdego typu musi istnieć w kontenerze **dokładnie jedna usługa**. Jeśli byłoby ich więcej, autowiring nie wiedziałby, którą z nich przekazać i rzuciłby wyjątek: ```neon services: mainDb: PDO(%dsn%, %user%, %password%) tempDb: PDO('sqlite::memory:') - articles: Model\ArticleRepository # VYHODÍ VÝJIMKU, vyhovuje mainDb i tempDb. + articles: Model\ArticleRepository # RZUCI WYJĄTEK, pasuje zarówno mainDb, jak i tempDb ``` -Rozwiązaniem byłoby albo ominięcie autowiring i jawne podanie nazwy usługi (np. `articles: Model\ArticleRepository(@mainDb)`). Łatwiej jest jednak [wyłączyć |#Disabled-Autowiring] autowiring dla jednej z usług lub [nadać priorytet |#Preferred-Autowiring] pierwszej usłudze. +Rozwiązaniem byłoby albo obejście autowiringu i jawne podanie nazwy usługi (tj. `articles: Model\ArticleRepository(@mainDb)`). Lepszym rozwiązaniem jest jednak [wyłączenie |#Wyłączenie autowiringu] autowiringu dla jednej z usług lub [nadanie priorytetu |#Preferencja autowiringu] pierwszej usłudze. -Wyłączanie automatycznego okablowania .[#toc-disabled-autowiring] ------------------------------------------------------------------ +Wyłączenie autowiringu +---------------------- -Można wyłączyć funkcję autopowrotu dla usługi, wybierając adres `autowired: no`: +Autowiring usługi możemy wyłączyć za pomocą opcji `autowired: no`: ```neon services: @@ -53,28 +53,27 @@ services: tempDb: create: PDO('sqlite::memory:') - autowired: false # usługa tempDb jest wyłączona z autowiring + autowired: false # usługa tempDb jest wyłączona z autowiringu - articles: Model\ArticleRepository # dlatego przechodzi do konstruktora mainDb + articles: Model\ArticleRepository # w związku z tym przekazuje do konstruktora mainDb ``` -Serwis `articles` nie rzuca wyjątku, że istnieją dwa pasujące serwisy typu `PDO` (czyli `mainDb` i `tempDb`), które można przekazać do konstruktora, ponieważ widzi tylko serwis `mainDb`. +Usługa `articles` nie rzuci wyjątku, że istnieją dwie pasujące usługi typu `PDO` (tj. `mainDb` i `tempDb`), które można przekazać do konstruktora, ponieważ widzi tylko usługę `mainDb`. .[note] -Konfiguracja autowiring w Nette działa inaczej niż w Symfony, gdzie opcja `autowire: false` mówi, aby nie używać autowiring dla argumentów do konstruktora danego serwisu. -W Nette, autowiring jest zawsze używany, czy to dla argumentów konstruktora, czy jakiejkolwiek innej metody. Opcja `autowired: false` mówi, że instancja danej usługi nie powinna być nigdzie przekazywana za pomocą autowiring. +Konfiguracja autowiringu w Nette działa inaczej niż w Symfony, gdzie opcja `autowire: false` mówi, że nie należy używać autowiringu dla argumentów konstruktora danej usługi. W Nette autowiring jest używany zawsze, czy to dla argumentów konstruktora, czy jakiejkolwiek innej metody. Opcja `autowired: false` mówi, że instancja danej usługi nie powinna być nigdzie przekazywana za pomocą autowiringu. -Preferencje dotyczące automatycznego okablowania .[#toc-preferred-autowiring] ------------------------------------------------------------------------------ +Preferencja autowiringu +----------------------- -Jeśli masz wiele usług tego samego typu i określisz `autowired` dla jednej z nich, ta usługa staje się usługą preferowaną: +Jeśli mamy więcej usług tego samego typu i dla jednej z nich podamy opcję `autowired`, staje się ona usługą preferowaną: ```neon services: mainDb: create: PDO(%dsn%, %user%, %password%) - autowired: PDO # stává se preferovanou + autowired: PDO # staje się preferowaną tempDb: create: PDO('sqlite::memory:') @@ -82,13 +81,13 @@ services: articles: Model\ArticleRepository ``` -Usługa `articles` nie rzuci wyjątku, że istnieją dwie pasujące usługi typu `PDO` (tj. `mainDb` i `tempDb`), ale użyje preferowanej usługi, `mainDb`. +Usługa `articles` nie rzuci wyjątku, że istnieją dwie pasujące usługi typu `PDO` (tj. `mainDb` i `tempDb`), ale użyje usługi preferowanej, czyli `mainDb`. -Pole serwisowe .[#toc-collection-of-services] ---------------------------------------------- +Tablica usług +------------- -Autowiring może również przekazać pole serwisowe określonego typu. Ponieważ PHP nie potrafi natywnie zapisywać typu elementów tablicy, oprócz typu `array` należy dodać komentarz phpDoc z typem elementu `ClassName[]`: +Autowiring potrafi przekazywać również tablice usług określonego typu. Ponieważ w PHP nie można natywnie zapisać typu elementów tablicy, oprócz typu `array` należy dodać komentarz phpDoc z typem elementu w formacie `ClassName[]`: ```php namespace Model; @@ -103,46 +102,45 @@ class ShipManager } ``` -Następnie kontener DI automatycznie przekaże tablicę usług pasujących do tego typu. Pominie usługi, które mają wyłączone autowiring. +Kontener DI następnie automatycznie przekaże tablicę usług odpowiadających danemu typowi. Pominie usługi, które mają wyłączony autowiring. -Jeśli nie możesz kontrolować formy komentarza phpDoc, możesz przekazać pole usługi bezpośrednio w konfiguracji za pomocą [`typed()` |services#Special-Functions]. +Typ w komentarzu może być również w formacie `array<int, Class>` lub `list<Class>`. Jeśli nie możesz wpłynąć na postać komentarza phpDoc, możesz przekazać tablicę usług bezpośrednio w konfiguracji za pomocą [`typed()` |services#Funkcje specjalne]. -Argumenty skalarne .[#toc-scalar-arguments] -------------------------------------------- +Argumenty skalarne +------------------ -Autowiring może ustawiać tylko obiekty i tablice obiektów. Argumenty skalarne (np. ciągi znaków, liczby, booleans) [są zapisywane w konfiguracji |services#Arguments]. -Alternatywą jest stworzenie [settings-object |best-practices:passing-settings-to-presenters], który enkapsuluje wartość skalarną (lub wiele wartości) jako obiekt, który następnie można przekazać ponownie za pomocą autowiring. +Autowiring potrafi podstawiać tylko obiekty i tablice obiektów. Argumenty skalarne (np. ciągi znaków, liczby, wartości logiczne) [zapisujemy w konfiguracji |services#Argumenty]. Alternatywą jest utworzenie [obiektu ustawień |best-practices:passing-settings-to-presenters], który enkapsuluje wartość skalarną (lub więcej wartości) w postaci obiektu, a ten następnie można ponownie przekazywać za pomocą autowiringu. ```php class MySettings { public function __construct( - // readonly może być używany od PHP 8.1 + // readonly można używać od PHP 8.1 public readonly bool $value, ) {} } ``` -Tworzysz z niego usługę, dodając ją do konfiguracji: +Utworzysz z niego usługę, dodając ją do konfiguracji: ```neon services: - MySettings('any value') ``` -Wszystkie klasy będą wtedy żądać go poprzez autowiring. +Wszystkie klasy następnie zażądają jej za pomocą autowiringu. -Zawężenie autowiringów .[#toc-narrowing-of-autowiring] ------------------------------------------------------- +Zawężenie autowiringu +--------------------- -Możesz ograniczyć autowiring dla poszczególnych usług do określonych klas lub interfejsów. +Dla poszczególnych usług można zawęzić autowiring tylko do określonych klas lub interfejsów. -Normalnie autowiring przekazuje usługę do każdego parametru metody, której typowi odpowiada usługa. Zawężanie oznacza, że określamy warunki, jakie muszą spełniać typy określone dla parametrów metody, aby można było przekazać do nich usługę. +Normalnie autowiring przekazuje usługę do każdego parametru metody, którego typowi usługa odpowiada. Zawężenie oznacza, że ustalamy warunki, które muszą spełniać typy podane przy parametrach metod, aby usługa została im przekazana. -Pokażmy to na przykładzie: +Pokażemy to na przykładzie: ```php class ParentClass @@ -164,42 +162,42 @@ class ChildDependent } ``` -Gdybyśmy zarejestrowali je wszystkie jako usługi, autowiring by się nie powiódł: +Gdybyśmy wszystkie zarejestrowali jako usługi, autowiring by zawiódł: ```neon services: parent: ParentClass child: ChildClass - parentDep: ParentDependent # WYJĄTEK WYJĄTEK, zarówno usługi rodzica jak i dziecka są zgodne - childDep: ChildDependent # autowiring przekazuje usługę dziecka do konstruktora + parentDep: ParentDependent # RZUCI WYJĄTEK, pasują usługi parent i child + childDep: ChildDependent # autowiring przekaże do konstruktora usługę child ``` -Serwis `parentDep` rzuciłby wyjątek `Multiple services of type ParentClass found: parent, child`, ponieważ zarówno `parent`, jak i `child` mieszczą się w jego kontenerze, a autowiring nie może zdecydować, który z nich wybrać. +Usługa `parentDep` rzuci wyjątek `Multiple services of type ParentClass found: parent, child`, ponieważ do jej konstruktora pasują obie usługi `parent` i `child`, a autowiring nie może zdecydować, którą z nich wybrać. -Dla serwisu `child` możemy zatem zawęzić jego autowizerunki do typu `ChildClass`: +Dla usługi `child` możemy zatem zawęzić jej autowiring do typu `ChildClass`: ```neon services: parent: ParentClass child: create: ChildClass - autowired: ChildClass # można również napisać 'autowired: self' + autowired: ChildClass # można napisać również 'autowired: self' - parentDep: ParentDependent # autowiring przekazuje usługę nadrzędną do konstruktora - childDep: ChildDependent # autowiring przekazuje usługę dziecka do konstruktora + parentDep: ParentDependent # autowiring przekaże do konstruktora usługę parent + childDep: ChildDependent # autowiring przekaże do konstruktora usługę child ``` -Teraz serwis `parent` jest przekazywany do konstruktora serwisu `parentDep`, ponieważ jest to teraz jedyny pasujący obiekt. Usługa `child` nie będzie już tam autowired. Tak, usługa `child` jest nadal typu `ParentClass`, ale warunek zawężający podany dla parametru typu nie ma już zastosowania, tzn. nie ma już racji, że `ParentClass` *jest supertypem* `ChildClass`. +Teraz do konstruktora usługi `parentDep` zostanie przekazana usługa `parent`, ponieważ jest to teraz jedyny pasujący obiekt. Usługi `child` autowiring już tam nie przekaże. Tak, usługa `child` nadal jest typu `ParentClass`, ale nie jest już spełniony warunek zawężający podany dla typu parametru, tj. nie jest prawdą, że `ParentClass` *jest nadtypem* `ChildClass`. -W przypadku serwisu `child`, `autowired: ChildClass` może być również zapisany jako `autowired: self`, ponieważ `self` jest miejscem dla aktualnej klasy serwisu. +Dla usługi `child` można by `autowired: ChildClass` zapisać również jako `autowired: self`, ponieważ `self` jest zastępczym oznaczeniem dla klasy bieżącej usługi. -Możliwe jest również wypisanie wielu klas lub interfejsów jako tablic w kluczu `autowired`: +W kluczu `autowired` można podać również kilka klas lub interfejsów jako tablicę: ```neon autowired: [BarClass, FooInterface] ``` -Spróbujmy dodać do przykładu interfejs: +Spróbujmy uzupełnić przykład o interfejsy: ```php interface FooInterface @@ -239,13 +237,13 @@ class ChildDependent } ``` -Jeśli nie ograniczymy w żaden sposób serwisu `child`, to zmieści się on w konstruktorach wszystkich klas `FooDependent`, `BarDependent`, `ParentDependent` i `ChildDependent` i autowiring go tam przekaże. +Gdy usługi `child` w żaden sposób nie ograniczymy, będzie pasować do konstruktorów wszystkich klas `FooDependent`, `BarDependent`, `ParentDependent` i `ChildDependent`, a autowiring ją tam przekaże. -Jeśli jednak ograniczymy jego autowiring do `ChildClass` za pomocą `autowired: ChildClass` (lub `self`), autowiring przekaże go tylko do konstruktora `ChildDependent`, ponieważ wymaga on argumentu typu `ChildClass` i prawdą jest, że `ChildClass` *jest typu* `ChildClass`. Żaden inny typ określony dla pozostałych parametrów nie jest supersetem `ChildClass`, więc serwis nie zostanie przekazany. +Jeśli jednak jej autowiring zawęzimy do `ChildClass` za pomocą `autowired: ChildClass` (lub `self`), autowiring przekaże ją tylko do konstruktora `ChildDependent`, ponieważ wymaga on argumentu typu `ChildClass` i jest prawdą, że `ChildClass` *jest typu* `ChildClass`. Żaden inny typ podany przy pozostałych parametrach nie jest nadtypem `ChildClass`, więc usługa nie zostanie przekazana. -Jeśli ograniczymy go do `ParentClass` za pomocą `autowired: ParentClass`, autowiring przekaże go ponownie do konstruktora `ChildDependent` (ponieważ wymagany `ChildClass` jest supersetem `ParentClass`, a teraz do konstruktora `ParentDependent`, ponieważ wymagany typ `ParentClass` jest również dopasowany. +Jeśli ograniczymy ją do `ParentClass` za pomocą `autowired: ParentClass`, autowiring przekaże ją ponownie do konstruktora `ChildDependent` (ponieważ wymagany `ChildClass` jest nadtypem `ParentClass`), a nowo również do konstruktora `ParentDependent`, ponieważ wymagany typ `ParentClass` jest również pasujący. -Jeśli ograniczymy go do `FooInterface`, to nadal będzie autowire do `ParentDependent` (wymagany `ParentClass` jest nadtypem `FooInterface`) i `ChildDependent`, ale dodatkowo do konstruktora `FooDependent`, ale nie do `BarDependent`, ponieważ `BarInterface` nie jest nadtypem `FooInterface`. +Jeśli ograniczymy ją do `FooInterface`, nadal będzie autowirowana do `ParentDependent` (wymagany `ParentClass` jest nadtypem `FooInterface`) i `ChildDependent`, ale dodatkowo również do konstruktora `FooDependent`, jednak nie do `BarDependent`, ponieważ `BarInterface` nie jest nadtypem `FooInterface`. ```neon services: @@ -253,8 +251,8 @@ services: create: ChildClass autowired: FooInterface - fooDep: FooDependent # autowiring przechodzi do konstruktora dziecka - barDep: BarDependent # rzuca wyjątek, żadna usługa nie pasuje - parentDep: ParentDependent # autowiring przechodzi do konstruktora dziecka - childDep: ChildDependent # autowiring przechodzi do konstruktora dziecka + fooDep: FooDependent # autowiring przekaże do konstruktora child + barDep: BarDependent # RZUCI WYJĄTEK, żadna usługa nie pasuje + parentDep: ParentDependent # autowiring przekaże do konstruktora child + childDep: ChildDependent # autowiring przekaże do konstruktora child ``` diff --git a/dependency-injection/pl/configuration.texy b/dependency-injection/pl/configuration.texy index 111cbc9e1e..d56a300da7 100644 --- a/dependency-injection/pl/configuration.texy +++ b/dependency-injection/pl/configuration.texy @@ -2,31 +2,32 @@ Konfiguracja kontenera DI ************************* .[perex] -Przegląd opcji konfiguracyjnych kontenera Nette DI. +Przegląd opcji konfiguracyjnych dla kontenera Nette DI. Plik konfiguracyjny =================== -Kontener Nette DI jest łatwy w użyciu dzięki plikom konfiguracyjnym. Zazwyczaj są one zapisane w [formacie NEON |neon:format]. Do edycji zalecane są [edytory z obsługą |best-practices:editors-and-tools#IDE-Editor] tego formatu. +Kontener Nette DI łatwo się kontroluje za pomocą plików konfiguracyjnych. Zazwyczaj są one zapisywane w [formacie NEON|neon:format]. Do edycji polecamy [edytory z obsługą |best-practices:editors-and-tools#Edytor IDE] tego formatu. <pre> -"decorator .[prism-token prism-atrule]":[#Decorator]: "dekorator .[prism-token prism-comment]"<br> -"di .[prism-token prism-atrule]":[#DI]: "kontener DI .[prism-token prism-comment]"<br> -"extensions .[prism-token prism-atrule]":[#Extensions]: "zainstaluj inne rozszerzenia DI .[prism-token prism-comment]"<br> -"includes .[prism-token prism-atrule]":[#Including files]: "wstawianie plików .[prism-token prism-comment]"<br> -"parameters .[prism-token prism-atrule]":[#Parameters]: "parametry .[prism-token prism-comment]"<br> -"search .[prism-token prism-atrule]":[#Search]: "automatyczna rejestracja serwisu .[prism-token prism-comment]"<br> -"services .[prism-token prism-atrule]":[services]: "serwisy .[prism-token prism-comment]" +"decorator .[prism-token prism-atrule]":[#decorator]: "Dekorator .[prism-token prism-comment]"<br> +"di .[prism-token prism-atrule]":[#DI]: "Kontener DI .[prism-token prism-comment]"<br> +"extensions .[prism-token prism-atrule]":[#Rozszerzenia]: "Instalacja dodatkowych rozszerzeń DI .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[#Dołączanie plików]: "Dołączanie plików .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[#parametry]: "Parametry .[prism-token prism-comment]"<br> +"search .[prism-token prism-atrule]":[#Search]: "Automatyczna rejestracja usług .[prism-token prism-comment]"<br> +"services .[prism-token prism-atrule]":[services]: "Usługi .[prism-token prism-comment]" </pre> -Aby wprowadzić ciąg zawierający znak `%`, musíte jej escapovat zdvojením na `%%`. .[note] +.[note] +Aby zapisać ciąg znaków zawierający znak `%`, musisz go escapować podwajając na `%%`. -Parametry .[#toc-parameters] -============================ +Parametry +========= -W konfiguracji można zdefiniować parametry, które następnie mogą być wykorzystane jako część definicji usług. Może to uczynić konfigurację bardziej przejrzystą lub ujednolicić i wyizolować wartości, które będą zmieniane. +W konfiguracji możesz zdefiniować parametry, które można następnie użyć jako część definicji usług. Dzięki temu możesz uporządkować konfigurację lub ujednolicić i wyodrębnić wartości, które będą się zmieniać. ```neon parameters: @@ -35,9 +36,9 @@ parameters: password: secret ``` -Do parametru `dsn` można się odwołać w dowolnym miejscu konfiguracji, pisząc `%dsn%`. Parametry mogą być również używane wewnątrz łańcuchów, np. `'%wwwDir%/images'`. +Do parametru `dsn` odwołujemy się w dowolnym miejscu konfiguracji zapisem `%dsn%`. Parametry można używać również wewnątrz ciągów znaków, jak `'%wwwDir%/images'`. -Parametry nie muszą być ciągami znaków lub liczbami, mogą również zawierać pola: +Parametry nie muszą być tylko ciągami znaków lub liczbami, mogą również zawierać tablice: ```neon parameters: @@ -48,32 +49,32 @@ parameters: languages: [cs, en, de] ``` -Określony klucz określamy jako `%mailer.user%`. +Do konkretnego klucza odwołujemy się jako `%mailer.user%`. -Jeśli potrzebujesz uzyskać wartość dowolnego parametru w swoim kodzie, na przykład klasy, przekaż ją do tej klasy. Na przykład w konstruktorze. Nie ma globalnego obiektu reprezentującego konfigurację, którą klasy odpytywałyby o wartości parametrów. Byłoby to naruszenie zasady wtrysku zależności. +Jeśli potrzebujesz w swoim kodzie, na przykład w klasie, poznać wartość dowolnego parametru, przekaż go do tej klasy. Na przykład w konstruktorze. Nie istnieje żaden globalny obiekt reprezentujący konfigurację, którego klasy pytałyby o wartości parametrów. Byłoby to naruszeniem zasady wstrzykiwania zależności. Usługi ====== -Patrz [osobny rozdział |services]. +Zobacz [osobny rozdział|services]. -Dekorator .[#toc-decorator] -=========================== +Decorator +========= -Jak masowo edytować wszystkie usługi danego typu? Na przykład wywołaj określoną metodę na wszystkich prezenterach, które dziedziczą po określonym wspólnym przodku? Do tego właśnie służy dekorator. +Jak masowo modyfikować wszystkie usługi określonego typu? Na przykład wywołać określoną metodę u wszystkich prezenterów, które dziedziczą po konkretnym wspólnym przodku? Do tego służy dekorator. ```neon decorator: - # dla wszystkich usług, które są instancjami tej klasy lub interfejsu - App\Presenters\BasePresenter: + # dla wszystkich usług, które są instancją tej klasy lub interfejsu + App\Presentation\BasePresenter: setup: - - setProjectId(10) # wywołaj tę metodę - - $absoluteUrls = true # i ustawić zmienną + - setProjectId(10) # wywołaj tę metodę + - $absoluteUrls = true # i ustaw zmienną ``` -Dekorator może być również użyty do ustawienia [tagów |services#Tags] lub włączenia trybu [iniekcji |services#Inject-Mode]. +Dekorator można również używać do ustawiania [tagów |services#Tagi] lub włączania trybu [inject |services#Tryb Inject]. ```neon decorator: @@ -86,67 +87,79 @@ decorator: DI === -Ustawienia techniczne kontenera DI. +Techniczne ustawienia kontenera DI. ```neon di: - # pokazać DIC w Tracy Bar? - debugger: ... # (bool) domyślnie jest true + # wyświetlać DIC w Tracy Bar? + debugger: ... # (bool) domyślnie true - # typy parametrów, które nigdy nie są autowire + # typy parametrów, których nigdy nie autowirować excluded: ... # (string[]) + # zezwolić na lazy tworzenie usług? + lazy: ... # (bool) domyślnie false + # klasa, po której dziedziczy kontener DI - parentClass: ... # (string) default to Nette\DI\Container + parentClass: ... # (string) domyślnie Nette\DI\Container ``` -Eksport metadanych .[#toc-metadata-export] ------------------------------------------- +Usługi lazy .{data-version:3.2.4} +--------------------------------- + +Ustawienie `lazy: true` aktywuje lazy (odroczone) tworzenie usług. Oznacza to, że usługi nie są faktycznie tworzone w momencie, gdy żądamy ich z kontenera DI, ale dopiero w chwili ich pierwszego użycia. Może to przyspieszyć start aplikacji i zmniejszyć zużycie pamięci, ponieważ tworzone są tylko te usługi, które są faktycznie potrzebne w danym żądaniu. + +Dla konkretnej usługi można [zmienić |services#Usługi lazy] lazy tworzenie. + +.[note] +Obiekty lazy można używać tylko dla klas użytkownika, a nie dla wewnętrznych klas PHP. Wymaga PHP 8.4 lub nowszego. + -Klasa kontenera DI zawiera również wiele metadanych. Możesz go zmniejszyć, zmniejszając eksport metadanych. +Eksport metadanych +------------------ + +Klasa kontenera DI zawiera również wiele metadanych. Możesz ją zmniejszyć, redukując eksport metadanych. ```neon di: export: - # parametry eksportu? - parameters: false # (bool) domyślnie jest true + # eksportować parametry? + parameters: false # (bool) domyślnie true - # eksportować tagi i jakie? + # eksportować tagi i które? tags: # (string[]|bool) domyślnie wszystkie - event.subscriber - # eksportować dane autowiringowe i jakie? - types: # (string[]|bool) default to all + # eksportować dane dla autowiringu i które? + types: # (string[]|bool) domyślnie wszystkie - Nette\Database\Connection - Symfony\Component\Console\Application ``` -Jeśli nie używasz pola `$container->parameters`, możesz wyłączyć eksport parametrów. Można również wyeksportować tylko te tagi, za pośrednictwem których uzyskuje się usługi metodą `$container->findByTag(...)`. -Jeśli w ogóle nie wywołujesz metody, możesz całkowicie wyłączyć eksport tagów za pomocą `false`. +Jeśli nie używasz tablicy `$container->getParameters()`, możesz wyłączyć eksport parametrów. Ponadto możesz eksportować tylko te tagi, za pomocą których uzyskujesz usługi metodą `$container->findByTag(...)`. Jeśli metody nie wywołujesz w ogóle, możesz całkowicie wyłączyć eksport tagów za pomocą `false`. -Możesz znacznie zmniejszyć metadane dla [autowiring |autowiring], określając klasy, których używasz jako parametr metody `$container->getByType()`. -I znowu, jeśli nie wywołujesz metody w ogóle (lub tylko w [bootstrapie |application:bootstrap], aby uzyskać `Nette\Application\Application`), możesz całkowicie wyłączyć eksport za pomocą `false`. +Możesz znacznie zredukować metadane dla [autowiringu |autowiring] podając klasy, których używasz jako parametr metody `$container->getByType()`. I ponownie, jeśli metody nie wywołujesz w ogóle (lub tylko w [bootstrapie|application:bootstrapping] do uzyskania `Nette\Application\Application`), możesz eksport całkowicie wyłączyć za pomocą `false`. -Przedłużenie .[#toc-extensions] -=============================== +Rozszerzenia +============ -Zarejestruj więcej rozszerzeń DI. W ten sposób dodajemy np. rozszerzenie DI `Dibi\Bridges\Nette\DibiExtension22` pod nazwą `dibi` +Rejestracja dodatkowych rozszerzeń DI. W ten sposób dodajemy np. rozszerzenie DI `Dibi\Bridges\Nette\DibiExtension22` pod nazwą `dibi` ```neon extensions: dibi: Dibi\Bridges\Nette\DibiExtension22 ``` -Następnie konfigurujemy go w sekcji `dibi`: +Następnie konfigurujemy je w sekcji `dibi`: ```neon dibi: host: localhost ``` -Jako rozszerzenie możesz również dodać klasę, która ma parametry: +Jako rozszerzenie można dodać również klasę, która ma parametry: ```neon extensions: @@ -154,10 +167,10 @@ extensions: ``` -Wstawianie plików .[#toc-including-files] -========================================= +Dołączanie plików +================= -Dodatkowe pliki konfiguracyjne można przesłać w sekcji `includes`: +Kolejne pliki konfiguracyjne możemy dołączyć w sekcji `includes`: ```neon includes: @@ -166,7 +179,7 @@ includes: - presenters.neon ``` -Nazwa `parameters.php` nie jest literówką, konfiguracja może być również zapisana w pliku PHP, który zwraca ją jako tablicę: +Nazwa `parameters.php` nie jest literówką, konfiguracja może być zapisana również w pliku PHP, który zwróci ją jako tablicę: ```php <?php @@ -179,84 +192,80 @@ return [ ]; ``` -Jeśli w plikach konfiguracyjnych pojawią się elementy o takich samych kluczach, zostaną one nadpisane, lub [scalone |#Slučování] w przypadku [pól |#Slučování]. Plik wstawiony później ma wyższy priorytet niż poprzedni. Plik, w którym wymieniona jest sekcja `includes` ma wyższy priorytet niż pliki wstawione do niego. +Jeśli w plikach konfiguracyjnych pojawią się elementy o tych samych kluczach, zostaną nadpisane, lub w przypadku [tablic połączone |#Łączenie]. Później dołączany plik ma wyższy priorytet niż poprzedni. Plik, w którym znajduje się sekcja `includes`, ma wyższy priorytet niż w nim dołączane pliki. -Szukaj .[#toc-search] -===================== +Search +====== -Automatyczne dodawanie usług do kontenera DI czyni pracę niezwykle wygodną. Nette automatycznie dodaje prezenterów do kontenera, ale możesz łatwo dodać dowolne inne klasy. +Automatyczne dodawanie usług do kontenera DI niezwykle ułatwia pracę. Nette automatycznie dodaje do kontenera presentery, ale można łatwo dodawać również dowolne inne klasy. -Wystarczy określić, w których katalogach (i podkatalogach) szukać klas: +Wystarczy podać, w których katalogach (i podkatalogach) ma szukać klas: ```neon search: - # wybierasz nazwy sekcji - formy: - in: %appDir%/Forms - - model: - in: %appDir%/Model + - in: %appDir%/Forms + - in: %appDir%/Model ``` -Zazwyczaj nie chcemy dodawać wszystkich klas i interfejsów, więc możemy je filtrować: +Zazwyczaj jednak nie chcemy dodawać absolutnie wszystkich klas i interfejsów, dlatego możemy je filtrować: ```neon search: - Formy: - in: %appDir%/Forms + - in: %appDir%/Forms - # filter by filename (string|string[]) + # filtrowanie według nazwy pliku (string|string[]) files: - *Factory.php - # filter by class name (string|string[]) + # filtrowanie według nazwy klasy (string|string[]) classes: - *Factory ``` -Możemy też wybrać klasy, które dziedziczą lub implementują przynajmniej jedną z wymienionych klas: +Lub możemy wybierać klasy, które dziedziczą lub implementują co najmniej jedną z podanych klas: ```neon search: - formuláře: + - in: %appDir% extends: - App\*Form implements: - App\*FormInterface ``` -Można również zdefiniować reguły wykluczenia, czyli maski nazw klas lub dziedziczonych przodków, które jeśli będą pasować, usługa nie zostanie dodana do kontenera DI: +Można zdefiniować również reguły wykluczające, tj. maski nazwy klasy lub przodków dziedziczenia, które jeśli pasują, usługa nie zostanie dodana do kontenera DI: ```neon search: - formuláře: + - in: %appDir% exclude: + files: ... classes: ... extends: ... implements: ... ``` -Wszystkie usługi mogą być oznakowane: +Wszystkim usługom można ustawić tagi: ```neon search: - formuláře: + - in: %appDir% tags: ... ``` -Łączenie .[#toc-merging] -======================== +Łączenie +======== -Jeśli elementy o tych samych kluczach pojawią się w wielu plikach konfiguracyjnych, zostaną nadpisane lub, w przypadku tablic, scalone. Plik wstawiony później ma wyższy priorytet niż poprzedni. +Jeśli w wielu plikach konfiguracyjnych pojawią się elementy o tych samych kluczach, zostaną nadpisane, lub w przypadku tablic połączone. Później dołączany plik ma wyższy priorytet niż poprzedni. <table class=table> <tr> <th width=33%>config1.neon</th> <th width=33%>config2.neon</th> - <th>Wynik</th> + <th>wynik</th> </tr> <tr> <td> @@ -283,13 +292,13 @@ items: </tr> </table> -W przypadku pól można zapobiec scalaniu, umieszczając wykrzyknik po nazwie klucza: +W przypadku tablic można zapobiec łączeniu, dodając wykrzyknik za nazwą klucza: <table class=table> <tr> <th width=33%>config1.neon</th> <th width=33%>config2.neon</th> - <th>Wynik</th> + <th>wynik</th> </tr> <tr> <td> @@ -313,3 +322,5 @@ items: </td> </tr> </table> + +{{maintitle: Konfiguracja Dependency Injection}} diff --git a/dependency-injection/pl/container.texy b/dependency-injection/pl/container.texy index a1b13cd85a..37940a6e1b 100644 --- a/dependency-injection/pl/container.texy +++ b/dependency-injection/pl/container.texy @@ -2,15 +2,15 @@ Co to jest kontener DI? *********************** .[perex] -Dependency injection container (DIC) to klasa, która może instancjonować i konfigurować obiekty. +Kontener wstrzykiwania zależności (DIC) to klasa, która potrafi tworzyć instancje i konfigurować obiekty. -Może cię to zaskoczyć, ale w wielu przypadkach nie potrzebujesz kontenera wtrysku zależności, aby skorzystać z wtrysku zależności (w skrócie DI). Przecież nawet w [poprzednim rozdziale |introduction] pokazywaliśmy konkretne przykłady DI i żaden kontener nie był potrzebny. +Może Cię to zaskoczyć, ale w wielu przypadkach nie potrzebujesz kontenera wstrzykiwania zależności, aby móc korzystać z zalet wstrzykiwania zależności (krótko DI). Przecież nawet w [rozdziale wstępnym|introduction] pokazaliśmy DI na konkretnych przykładach i żaden kontener nie był potrzebny. -Jeśli jednak musisz zarządzać dużą liczbą różnych obiektów z wieloma zależnościami, kontener wtrysku zależności będzie naprawdę przydatny. Co ma miejsce np. w przypadku aplikacji internetowych zbudowanych na frameworku. +Jeśli jednak potrzebujesz zarządzać dużą liczbą różnych obiektów z wieloma zależnościami, kontener wstrzykiwania zależności będzie naprawdę przydatny. Co ma miejsce na przykład w przypadku aplikacji internetowych zbudowanych na frameworku. -W poprzednim rozdziale przedstawiliśmy klasy `Article` i `UserController`. Obie mają pewne zależności, a mianowicie bazę danych i fabrykę `ArticleFactory`. I dla tych klas stworzymy teraz kontener. Oczywiście dla tak prostego przykładu posiadanie kontenera nie ma sensu. Ale stworzymy jeden, aby pokazać jak to wygląda i działa. +W poprzednim rozdziale przedstawiliśmy klasy `Article` i `UserController`. Obie mają pewne zależności, a mianowicie bazę danych i fabrykę `ArticleFactory`. A dla tych klas utworzymy teraz kontener. Oczywiście dla tak prostego przykładu nie ma sensu mieć kontenera. Ale utworzymy go, aby pokazać, jak wygląda i działa. -Oto prosty kontener hardcoded dla tego przykładu: +Oto prosty, hardkodowany kontener dla podanego przykładu: ```php class Container @@ -32,16 +32,16 @@ class Container } ``` -Użycie wyglądałoby tak: +Użycie wyglądałoby następująco: ```php $container = new Container; $controller = $container->createUserController(); ``` -Po prostu pytamy kontener o obiekt i nie musimy nic wiedzieć o tym, jak go stworzyć i jakie są jego zależności; kontener wie to wszystko. Zależności są wstrzykiwane automatycznie przez kontener. To jest jego moc. +Kontenera pytamy tylko o obiekt i nie musimy już wiedzieć nic o tym, jak go utworzyć i jakie ma zależności; to wszystko wie kontener. Zależności są wstrzykiwane przez kontener automatycznie. W tym tkwi jego siła. -Do tej pory kontener zapisywał wszystkie dane na górze. Robimy więc kolejny krok i dodajemy parametry, aby kontener był naprawdę użyteczny: +Kontener ma na razie wszystkie dane zapisane na stałe. Zrobimy więc kolejny krok i dodamy parametry, aby kontener był rzeczywiście użyteczny: ```php class Container @@ -70,9 +70,9 @@ $container = new Container([ ]); ``` -Uważni czytelnicy mogli zauważyć pewien problem. Za każdym razem, gdy otrzymuję obiekt `UserController`, tworzona jest również nowa instancja `ArticleFactory` i baza danych. Z pewnością tego nie chcemy. +Bystrzy czytelnicy mogli zauważyć pewien problem. Za każdym razem, gdy pobieram obiekt `UserController`, tworzona jest również nowa instancja `ArticleFactory` i bazy danych. Tego zdecydowanie nie chcemy. -Dodamy więc metodę `getService()`, która będzie zwracała w kółko te same instancje: +Dodamy więc metodę `getService()`, która będzie zwracać zawsze te same instancje: ```php class Container @@ -87,7 +87,7 @@ class Container public function getService(string $name): object { if (!isset($this->services[$name])) { - // getService('Database') bude volat createDatabase() + // getService('Database') będzie wywoływać createDatabase() $method = 'create' . $name; $this->services[$name] = $this->$method(); } @@ -98,9 +98,9 @@ class Container } ``` -Przy pierwszym wywołaniu np. `$container->getService('Database')` zleci `createDatabase()` stworzenie obiektu bazy danych, który zapisze w tablicy `$services` i zwróci go bezpośrednio przy kolejnym wywołaniu. +Przy pierwszym wywołaniu np. `$container->getService('Database')` zleci `createDatabase()` utworzenie obiektu bazy danych, który zapisze w tablicy `$services`, a przy następnym wywołaniu od razu go zwróci. -Modyfikujemy też resztę kontenera, aby korzystał z `getService()`: +Zmodyfikujemy również resztę kontenera, aby używał `getService()`: ```php class Container @@ -119,9 +119,9 @@ class Container } ``` -Przy okazji, termin usługa odnosi się do dowolnego obiektu zarządzanego przez kontener. Stąd nazwa metody `getService()`. +Nawiasem mówiąc, terminem usługa określa się dowolny obiekt zarządzany przez kontener. Stąd też nazwa metody `getService()`. -Zrobione. Mamy w pełni funkcjonalny kontener DI! I możemy z niego korzystać: +Gotowe. Mamy w pełni funkcjonalny kontener DI! I możemy go użyć: ```php $container = new Container([ @@ -134,6 +134,9 @@ $controller = $container->getService('UserController'); $database = $container->getService('Database'); ``` -Jak widać, napisanie DIC nie jest trudne. Warto pamiętać, że same obiekty nie wiedzą, że tworzy je kontener. Można więc w ten sposób stworzyć dowolny obiekt w PHP bez ingerencji w jego kod źródłowy. +Jak widzisz, napisanie DIC nie jest niczym skomplikowanym. Warto przypomnieć, że same obiekty nie wiedzą, że tworzy je jakiś kontener. Dzięki temu można w ten sposób tworzyć dowolny obiekt w PHP bez ingerencji w jego kod źródłowy. -Ręczne tworzenie i utrzymywanie klasy kontenera może dość szybko stać się koszmarem. Dlatego w następnym rozdziale opowiemy o [kontenerze Nette DI Container |nette-container], który może generować i aktualizować się niemal samodzielnie. +Ręczne tworzenie i utrzymywanie klasy kontenera może dość szybko stać się koszmarem. Dlatego w następnym rozdziale opowiemy o [Kontenerze Nette DI|nette-container], który potrafi generować się i aktualizować niemal sam. + + +{{maintitle: Co to jest kontener wstrzykiwania zależności?}} diff --git a/dependency-injection/pl/extensions.texy b/dependency-injection/pl/extensions.texy index 4f1d5723c1..827dd74ec4 100644 --- a/dependency-injection/pl/extensions.texy +++ b/dependency-injection/pl/extensions.texy @@ -2,7 +2,7 @@ Tworzenie rozszerzeń dla Nette DI ********************************* .[perex] -Oprócz plików konfiguracyjnych, na generowanie kontenera DI mają wpływ również tzw. *extensions*. Są one aktywowane w pliku konfiguracyjnym w sekcji `extensions`. +Na generowanie kontenera DI oprócz plików konfiguracyjnych wpływają również tzw. *rozszerzenia*. Aktywujemy je w pliku konfiguracyjnym w sekcji `extensions`. W ten sposób dodajemy rozszerzenie reprezentowane przez klasę `BlogExtension` pod nazwą `blog`: @@ -11,7 +11,7 @@ extensions: blog: BlogExtension ``` -Każde rozszerzenie kompilatora dziedziczy po [api:Nette\DI\CompilerExtension] i może implementować następujące metody, które są wywoływane sekwencyjnie w miarę budowania kontenera DI: +Każde rozszerzenie kompilatora dziedziczy po [api:Nette\DI\CompilerExtension] i może implementować następujące metody, które są kolejno wywoływane podczas budowania kontenera DI: 1. getConfigSchema() 2. loadConfiguration() @@ -22,9 +22,9 @@ Każde rozszerzenie kompilatora dziedziczy po [api:Nette\DI\CompilerExtension] i getConfigSchema() .[method] =========================== -Ta metoda jest wywoływana jako pierwsza. Definiuje on schemat walidacji parametrów konfiguracyjnych. +Ta metoda jest wywoływana jako pierwsza. Definiuje schemat do walidacji parametrów konfiguracyjnych. -Rozszerzenie jest konfigurowane w sekcji, której nazwa jest taka sama jak ta, pod którą rozszerzenie zostało dodane, czyli `blog`: +Rozszerzenie konfigurujemy w sekcji, której nazwa jest taka sama jak ta, pod którą rozszerzenie zostało dodane, czyli `blog`: ```neon # ta sama nazwa co rozszerzenie @@ -33,7 +33,7 @@ blog: allowComments: false ``` -Tworzymy schemat opisujący wszystkie opcje konfiguracyjne wraz z ich typami, dozwolonymi wartościami oraz, jeśli to konieczne, wartościami domyślnymi: +Tworzymy schemat opisujący wszystkie opcje konfiguracyjne, w tym ich typy, dozwolone wartości i ewentualnie wartości domyślne: ```php use Nette\Schema\Expect; @@ -50,9 +50,9 @@ class BlogExtension extends Nette\DI\CompilerExtension } ``` -Zobacz stronę [Schemat |schema:], aby uzyskać dokumentację. Dodatkowo można określić, które opcje mogą być [dynamiczne |application:bootstrap#Dynamic-Parameters] za pomocą `dynamic()`, np. `Expect::int()->dynamic()`. +Dokumentację znajdziesz na stronie [Schema |schema:]. Dodatkowo można określić, które opcje mogą być [dynamiczne |application:bootstrapping#Parametry dynamiczne] za pomocą `dynamic()`, np. `Expect::int()->dynamic()`. -Dostęp do konfiguracji odbywa się poprzez zmienną `$this->config`, która jest obiektem `stdClass`: +Do konfiguracji dostajemy się przez zmienną `$this->config`, która jest obiektem `stdClass`: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -71,7 +71,7 @@ class BlogExtension extends Nette\DI\CompilerExtension loadConfiguration() .[method] ============================= -Służy do dodawania usług do kontenera. Aby to zrobić, należy użyć [api:Nette\DI\ContainerBuilder]: +Służy do dodawania usług do kontenera. Do tego służy [api:Nette\DI\ContainerBuilder]: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -80,25 +80,25 @@ class BlogExtension extends Nette\DI\CompilerExtension { $builder = $this->getContainerBuilder(); $builder->addDefinition($this->prefix('articles')) - ->setFactory(App\Model\HomepageArticles::class, ['@connection']) //lub setCreator() + ->setFactory(App\Model\HomepageArticles::class, ['@connection']) // lub setCreator() ->addSetup('setLogger', ['@logger']); } } ``` -Konwencją jest poprzedzanie usług dodawanych przez rozszerzenie jego nazwą, aby uniknąć konfliktów nazw. Odbywa się to za pomocą metody `prefix()`, więc jeśli rozszerzenie ma nazwę `blog`, usługa będzie miała nazwę `blog.articles`. +Konwencją jest prefiksowanie usług dodanych przez rozszerzenie jego nazwą, aby nie dochodziło do konfliktów nazw. Robi to metoda `prefix()`, więc jeśli rozszerzenie nazywa się `blog`, usługa będzie nosić nazwę `blog.articles`. -Jeśli musimy zmienić nazwę usługi, możemy stworzyć alias z oryginalną nazwą, aby zachować kompatybilność wsteczną. Nette robi podobnie na przykład w przypadku serwisu `routing.router`, który jest dostępny również pod dawną nazwą `router`. +Jeśli potrzebujemy zmienić nazwę usługi, możemy ze względu na zachowanie wstecznej kompatybilności utworzyć alias z pierwotną nazwą. Podobnie robi Nette np. w przypadku usługi `routing.router`, która jest dostępna również pod wcześniejszą nazwą `router`. ```php $builder->addAlias('router', 'routing.router'); ``` -Ładowanie usług z pliku .[#toc-retrieve-services-from-a-file] -------------------------------------------------------------- +Ładowanie usług z pliku +----------------------- -Usługi muszą być tworzone nie tylko przy użyciu API klasy ContainerBuilder, ale także przy użyciu znanej notacji stosowanej w pliku konfiguracyjnym NEON w sekcji services. Przedrostek `@extension` reprezentuje aktualne rozszerzenie. +Usługi możemy tworzyć nie tylko za pomocą API klasy ContainerBuilder, ale także znanym zapisem używanym w pliku konfiguracyjnym NEON w sekcji services. Prefiks `@extension` reprezentuje bieżące rozszerzenie. ```neon services: @@ -112,7 +112,7 @@ services: create: MyBlog\Components\ArticlesList(@extension.articles) ``` -Ładujemy usługi: +Usługi wczytamy: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -121,7 +121,7 @@ class BlogExtension extends Nette\DI\CompilerExtension { $builder = $this->getContainerBuilder(); - // načtení konfiguračního souboru pro rozšíření + // wczytanie pliku konfiguracyjnego dla rozszerzenia $this->compiler->loadDefinitionsFromConfig( $this->loadFromFile(__DIR__ . '/blog.neon')['services'], ); @@ -133,7 +133,7 @@ class BlogExtension extends Nette\DI\CompilerExtension beforeCompile() .[method] ========================= -Metoda jest wywoływana, gdy kontener zawiera wszystkie usługi dodane przez poszczególne rozszerzenia w metodach `loadConfiguration`, a także pliki konfiguracyjne użytkownika. Dzięki temu na tym etapie budowania możemy edytować definicje usług lub dodawać wiązania między nimi. Do wyszukiwania usług w kontenerze po znacznikach można użyć metody `findByTag()`, a po klasie lub interfejsie - metody `findByType()`. +Metoda jest wywoływana w momencie, gdy kontener zawiera wszystkie usługi dodane przez poszczególne rozszerzenia w metodach `loadConfiguration` oraz przez użytkownika w plikach konfiguracyjnych. Na tym etapie budowania możemy więc modyfikować definicje usług lub uzupełniać powiązania między nimi. Do wyszukiwania usług w kontenerze według tagów można użyć metody `findByTag()`, a według klasy lub interfejsu metody `findByType()`. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -153,7 +153,7 @@ class BlogExtension extends Nette\DI\CompilerExtension afterCompile() .[method] ======================== -Na tym etapie klasa kontenera jest już wygenerowana jako obiekt [ClassType |php-generator:#Classes], zawiera wszystkie metody tworzące usługi i jest gotowa do zapisania w pamięci podręcznej. W tym momencie możemy jeszcze zmodyfikować wynikowy kod klasy. +Na tym etapie klasa kontenera jest już wygenerowana w postaci obiektu [ClassType |php-generator:#Klasy], zawiera wszystkie metody tworzące usługi i jest gotowa do zapisu do cache. Wynikowy kod klasy możemy na tym etapie jeszcze zmodyfikować. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -167,24 +167,24 @@ class BlogExtension extends Nette\DI\CompilerExtension ``` -$inicjalizacja .[wiki-method][#toc-initialization] -================================================== +$initialization .[method] +========================= -Po [utworzeniu kontenera |application:bootstrap#index-php] klasa Configurator wywołuje kod inicjalizacyjny, który powstaje poprzez zapis do obiektu `$this->initialization` za pomocą [metody addBody() |php-generator:#Method-and-Function-Body]. +Klasa Configurator po [utworzeniu kontenera |application:bootstrapping#index.php] wywołuje kod inicjalizacyjny, który tworzy się zapisem do obiektu `$this->initialization` za pomocą [metody addBody() |php-generator:#Ciała metod i funkcji]. -Pokażemy przykład, jak np. kod inicjalizujący może uruchomić sesję lub uruchomić usługi, które mają znacznik `run`: +Pokażemy przykład, jak na przykład kodem inicjalizacyjnym uruchomić sesję lub uruchomić usługi, które mają tag `run`: ```php class BlogExtension extends Nette\DI\CompilerExtension { public function loadConfiguration() { - // automatyczny start sesji + // automatyczne uruchamianie sesji if ($this->config->session->autoStart) { $this->initialization->addBody('$this->getService("session")->start()'); } - // služby s tagem run musejí být vytvořeny po instancování kontejneru + // usługi z tagiem run muszą być utworzone po instancjonowaniu kontenera $builder = $this->getContainerBuilder(); foreach ($builder->findByTag('run') as $name => $foo) { $this->initialization->addBody('$this->getService(?);', [$name]); diff --git a/dependency-injection/pl/factory.texy b/dependency-injection/pl/factory.texy index 6a55c713ca..ae649ef771 100644 --- a/dependency-injection/pl/factory.texy +++ b/dependency-injection/pl/factory.texy @@ -1,12 +1,12 @@ -Wytworzone fabryki +Generowane fabryki ****************** .[perex] -Nette DI może automatycznie generować kod fabryczny na podstawie interfejsu, oszczędzając na pisaniu kodu. +Nette DI potrafi automatycznie generować kod fabryk na podstawie interfejsów, co oszczędza Ci pisania kodu. -Fabryka to klasa, która tworzy i konfiguruje obiekty. W związku z tym przekazuje im również ich zależności. Proszę nie mylić z wzorcem projektowym *factory method*, który opisuje specyficzny sposób używania fabryk i nie jest związany z tym tematem. +Fabryka to klasa, która tworzy i konfiguruje obiekty. Przekazuje im więc również ich zależności. Proszę nie mylić z wzorcem projektowym *factory method*, który opisuje specyficzny sposób wykorzystania fabryk i nie jest związany z tym tematem. -Jak wygląda taka fabryka, pokazaliśmy w [rozdziale wprowadzającym |introduction#factory]: +Jak wygląda taka fabryka, pokazaliśmy w [rozdziale wstępnym |introduction#Fabryka]: ```php class ArticleFactory @@ -23,7 +23,7 @@ class ArticleFactory } ``` -Nette DI może automatycznie wygenerować kod fabryczny. Wystarczy, że stworzysz interfejs, a Nette DI wygeneruje jego implementację. Interfejs musi mieć dokładnie jedną metodę o nazwie `create` i deklarować typ zwrotny: +Nette DI potrafi automatycznie generować kod fabryk. Wszystko, co musisz zrobić, to utworzyć interfejs, a Nette DI wygeneruje implementację. Interfejs musi mieć dokładnie jedną metodę o nazwie `create` i deklarować typ zwracany: ```php interface ArticleFactory @@ -32,7 +32,7 @@ interface ArticleFactory } ``` -Tak więc fabryka `ArticleFactory` ma metodę `create`, która tworzy obiekty `Article`. Klasa `Article` może wyglądać na przykład tak: +Czyli fabryka `ArticleFactory` ma metodę `create`, która tworzy obiekty `Article`. Klasa `Article` może wyglądać na przykład następująco: ```php class Article @@ -44,16 +44,16 @@ class Article } ``` -Dodaj fabrykę do pliku konfiguracyjnego: +Fabrykę dodajemy do pliku konfiguracyjnego: ```neon services: - ArticleFactory ``` -Nette DI wygeneruje odpowiednią implementację fabryczną. +Nette DI wygeneruje odpowiednią implementację fabryki. -W kodzie, który korzysta z fabryki, żądamy obiektu przez interfejs, a Nette DI używa wygenerowanej implementacji: +W kodzie, który używa fabryki, żądamy obiektu według interfejsu, a Nette DI użyje wygenerowanej implementacji: ```php class UserController @@ -65,17 +65,17 @@ class UserController public function foo() { - // pozwól fabryce stworzyć obiekt + // zlecamy fabryce utworzenie obiektu $article = $this->articleFactory->create(); } } ``` -Fabryka z parametrami .[#toc-parameterized-factory] -=================================================== +Fabryka sparametryzowana +======================== -Metoda fabryczna `create` może otrzymywać parametry, które następnie przekazuje do konstruktora. Na przykład uzupełnijmy klasę `Article` o ID autora artykułu: +Metoda fabryczna `create` może przyjmować parametry, które następnie przekaże do konstruktora. Uzupełnijmy na przykład klasę `Article` o ID autora artykułu: ```php class Article @@ -88,7 +88,7 @@ class Article } ``` -Dodamy również parametr do fabryki: +Parametr dodamy również do fabryki: ```php interface ArticleFactory @@ -97,13 +97,13 @@ interface ArticleFactory } ``` -Ponieważ parametr w konstruktorze i parametr w fabryce mają tę samą nazwę, Nette DI przekazuje je automatycznie. +Dzięki temu, że parametr w konstruktorze i parametr w fabryce nazywają się tak samo, Nette DI przekaże je całkowicie automatycznie. -Zaawansowana definicja .[#toc-advanced-definition] -================================================== +Definicja zaawansowana +====================== -Definicja może być również zapisana w formie wieloliniowej za pomocą klawisza `implement`: +Definicję można zapisać również w formie wieloliniowej za pomocą klucza `implement`: ```neon services: @@ -111,9 +111,9 @@ services: implement: ArticleFactory ``` -Pisząc w ten dłuższy sposób, można podać dodatkowe argumenty do konstruktora w kluczu `arguments` oraz dodatkową konfigurację za pomocą `setup`, tak jak w przypadku zwykłych serwisów. +Przy zapisie tym dłuższym sposobem można podać dodatkowe argumenty dla konstruktora w kluczu `arguments` oraz dodatkową konfigurację za pomocą `setup`, tak samo jak w przypadku zwykłych usług. -Przykład: gdyby metoda `create()` nie akceptowała parametru `$authorId`, moglibyśmy w konfiguracji określić stałą wartość, która zostałaby przekazana do konstruktora `Article`: +Przykład: gdyby metoda `create()` nie przyjmowała parametru `$authorId`, moglibyśmy podać stałą wartość w konfiguracji, która byłaby przekazywana do konstruktora `Article`: ```neon services: @@ -123,7 +123,7 @@ services: authorId: 123 ``` -Lub odwrotnie, gdyby `create()` zaakceptował parametr `$authorId`, ale nie był on częścią konstruktora i został przekazany przez metodę `Article::setAuthorId()`, odwołalibyśmy się do niego w sekcji `setup`: +Lub odwrotnie, gdyby `create()` przyjmowała parametr `$authorId`, ale nie byłby on częścią konstruktora i przekazywany byłby metodą `Article::setAuthorId()`, odwołalibyśmy się do niego w sekcji `setup`: ```neon services: @@ -134,15 +134,14 @@ services: ``` -Pomocnik .[#toc-accessor] -========================= +Accessor +======== -Nette oprócz fabryk może generować accessory. Są to obiekty z metodą `get()`, która zwraca określoną usługę z kontenera DI. Wielokrotne wywołanie `get()` wciąż zwraca tę samą instancję. +Nette potrafi oprócz fabryk generować również tzw. akcesory. Są to obiekty z metodą `get()`, która zwraca określoną usługę z kontenera DI. Powtarzane wywołanie `get()` zwraca zawsze tę samą instancję. -Accessory zapewniają leniwe ładowanie zależności. Rozważmy klasę, która zapisuje błędy do specjalnej bazy danych. Gdyby ta klasa miała połączenie z bazą danych przekazywane jako zależność przez konstruktor, połączenie zawsze musiałoby zostać nawiązane, choć w praktyce rzadko występowałby błąd, a więc przez większość czasu połączenie pozostawałoby nieużywane. -Więc zamiast tego klasa przekazuje accessor i tylko wtedy, gdy jego `get()` jest wywoływany, obiekt bazy danych zostaje utworzony: +Akcesory zapewniają lazy-loading zależności. Miejmy klasę, która zapisuje błędy do specjalnej bazy danych. Gdyby ta klasa otrzymywała połączenie z bazą danych jako zależność przez konstruktor, połączenie musiałoby być zawsze tworzone, chociaż w praktyce błąd pojawia się tylko wyjątkowo, a więc w większości przypadków połączenie pozostałoby niewykorzystane. Zamiast tego klasa przekaże sobie akcesor i dopiero gdy zostanie wywołana jego metoda `get()`, dojdzie do utworzenia obiektu bazy danych: -Jak stworzyć accessora? Wystarczy napisać interfejs, a Nette DI wygeneruje jego implementację. Interfejs musi mieć dokładnie jedną metodę o nazwie `get` i deklarować typ zwrotny: +Jak utworzyć akcesor? Wystarczy napisać interfejs, a Nette DI wygeneruje implementację. Interfejs musi mieć dokładnie jedną metodę o nazwie `get` i deklarować typ zwracany: ```php interface PDOAccessor @@ -151,7 +150,7 @@ interface PDOAccessor } ``` -Dodaj accessor do pliku konfiguracyjnego, który określa również usługę, którą zwróci: +Akcesor dodajemy do pliku konfiguracyjnego, gdzie znajduje się również definicja usługi, którą będzie zwracał: ```neon services: @@ -159,62 +158,68 @@ services: - PDO(%dsn%, %user%, %password%) ``` -Ponieważ accessor zwraca usługę typu `PDO` i w konfiguracji jest tylko jedna taka usługa, zwróci tę usługę. Jeśli istnieje więcej usług tego typu, określ usługę, która ma być zwrócona przez nazwę, np. `- PDOAccessor(@db1)`. +Ponieważ akcesor zwraca usługę typu `PDO`, a w konfiguracji jest jedyna taka usługa, będzie zwracał właśnie ją. Gdyby usług danego typu było więcej, określimy zwracaną usługę za pomocą nazwy, np. `- PDOAccessor(@db1)`. -Wiele fabryk/akcesoriów .[#toc-multifactory-accessor] -===================================================== -Dotychczas nasze fabryki i accessory były w stanie wyprodukować lub zwrócić tylko jeden obiekt. Jednak bardzo łatwo jest stworzyć wiele fabryk połączonych z accessorami. Interfejs takiej klasy będzie zawierał dowolną liczbę metod o nazwach `create<name>()` a `get<name>()`, np: +Wielokrotna fabryka/akcesor +=========================== +Nasze fabryki i akcesory potrafiły dotychczas zawsze tworzyć lub zwracać tylko jeden obiekt. Można jednak bardzo łatwo utworzyć również wielokrotne fabryki połączone z akcesorami. Interfejs takiej klasy będzie zawierał dowolną liczbę metod o nazwach `create<name>()` i `get<name>()`, np.: ```php interface MultiFactory { function createArticle(): Article; - function createFoo(): Model\Foo; function getDb(): PDO; } ``` -Więc zamiast przekazywać kilka wygenerowanych fabryk i accessorów, przekazujemy jedną bardziej złożoną fabrykę, która może zrobić więcej. +Więc zamiast przekazywać sobie kilka generowanych fabryk i akcesorów, przekażemy jedną bardziej złożoną fabrykę, która potrafi więcej. -Alternatywnie, zamiast korzystać z wielu metod, możemy użyć `create()` i `get()` z parametrem: +Alternatywnie można zamiast kilku metod użyć `get()` z parametrem: ```php interface MultiFactoryAlt { - function create($name); function get($name): PDO; } ``` -Następnie `MultiFactory::createArticle()` robi to samo, co `MultiFactoryAlt::create('article')`. Jednak alternatywna notacja ma tę wadę, że nie jest oczywiste, jakie wartości `$name` są obsługiwane, a logicznie nie jest również możliwe wyróżnienie różnych wartości zwrotnych dla różnych `$name` w interfejsie. +Wtedy obowiązuje, że `MultiFactory::getArticle()` robi to samo co `MultiFactoryAlt::get('article')`. Jednak alternatywny zapis ma tę wadę, że nie jest oczywiste, jakie wartości `$name` są obsługiwane i logicznie rzecz biorąc, nie można również w interfejsie rozróżnić różnych wartości zwracanych dla różnych `$name`. -Definicja według listy .[#toc-definition-with-a-list] ------------------------------------------------------ -I jak zdefiniować wiele fabryk w konfiguracji? Tworzymy trzy serwisy, które będą tworzyć/zwracać, a następnie samą fabrykę: +Definicja listą +--------------- +W ten sposób można zdefiniować wielokrotną fabrykę w konfiguracji: .{data-version:3.2.0} + +```neon +services: + - MultiFactory( + article: Article # definiuje createArticle() + db: PDO(%dsn%, %user%, %password%) # definiuje getDb() + ) +``` + +Lub możemy w definicji fabryki odwołać się do istniejących usług za pomocą referencji: ```neon services: article: Article - - Model\Foo - PDO(%dsn%, %user%, %password%) - MultiFactory( - article: @article # createArticle() - foo: @Model\Foo # createFoo() - db: @\PDO # getDb() + article: @article # definiuje createArticle() + db: @\PDO # definiuje getDb() ) ``` -Definicja według znaczników .[#toc-definition-with-tags] --------------------------------------------------------- +Definicja za pomocą tagów +------------------------- -Druga opcja to użycie [tagów |services#Tags] do definicji: +Drugą możliwością jest wykorzystanie do definicji [tagów |services#Tagi]: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter - App\Model\DatabaseAccessor( db1: @database.db1.explorer ) diff --git a/dependency-injection/pl/faq.texy b/dependency-injection/pl/faq.texy index 46b1bd8cc7..dd9bdfd93d 100644 --- a/dependency-injection/pl/faq.texy +++ b/dependency-injection/pl/faq.texy @@ -1,98 +1,92 @@ -Najczęściej zadawane pytania dotyczące DI (FAQ) -*********************************************** +Często zadawane pytania o DI (FAQ) +********************************** -Czy DI to kolejna nazwa dla IoC? .[#toc-is-di-another-name-for-ioc] -------------------------------------------------------------------- +Czy DI to inna nazwa dla IoC? +----------------------------- -*Inversion of Control* (IoC) to zasada skupiająca się na sposobie wykonywania kodu - czy twój kod inicjuje kod zewnętrzny, czy też twój kod jest zintegrowany z kodem zewnętrznym, który następnie go wywołuje. -IoC jest szeroką koncepcją, która obejmuje [zdarzenia |nette:glossary#Events], tzw. zasadę [Hollywood |application:components#Hollywood style] i inne aspekty. -Fabryki, które są częścią [zasady # 3: Let the Factory Handle It |introduction#Rule #3: Let the Factory Handle It], i reprezentują inwersję dla operatora `new`, są również składnikami tej koncepcji. +*Inversion of Control* (IoC) to zasada skupiająca się na sposobie, w jaki kod jest uruchamiany - czy Twój kod uruchamia obcy kod, czy Twój kod jest integrowany z obcym kodem, który go następnie wywołuje. IoC to szerokie pojęcie obejmujące [zdarzenia |nette:glossary#Eventy zdarzenia], tak zwaną [zasadę Hollywood |application:components#Styl Hollywood] i inne aspekty. Częścią tej koncepcji są również fabryki, o których mówi [Reguła nr 3: zostaw to fabryce |introduction#Zasada nr 3: zostaw to fabryce], które stanowią inwersję dla operatora `new`. -*Dependency Injection* (DI) dotyczy tego, jak jeden obiekt wie o innym obiekcie, czyli zależności. Jest to wzorzec projektowy, który wymaga jawnego przekazywania zależności między obiektami. +*Dependency Injection* (DI) skupia się na sposobie, w jaki jeden obiekt dowiaduje się o innym obiekcie, czyli o jego zależnościach. Jest to wzorzec projektowy, który wymaga jawnego przekazywania zależności między obiektami. -Można więc powiedzieć, że DI jest specyficzną formą IoC. Jednak nie wszystkie formy IoC są odpowiednie pod względem czystości kodu. Na przykład do anty-wzorców zaliczamy wszystkie techniki pracujące z [globalnym stanem |global state] lub tzw. [Service Locator |#What is a Service Locator]. +Można więc powiedzieć, że DI jest specyficzną formą IoC. Jednak nie wszystkie formy IoC są odpowiednie z punktu widzenia czystości kodu. Na przykład do antywzorców należą techniki, które pracują z [globalnym stanem |global-state] lub tak zwany [Service Locator |#Co to jest Service Locator]. -Czym jest Service Locator? .[#toc-what-is-a-service-locator] ------------------------------------------------------------- +Co to jest Service Locator? +--------------------------- -Service Locator jest alternatywą dla Dependency Injection. Działa on poprzez stworzenie centralnego magazynu, w którym zarejestrowane są wszystkie dostępne usługi lub zależności. Kiedy obiekt potrzebuje zależności, żąda jej z lokalizatora usług. +Jest to alternatywa dla Dependency Injection. Działa tak, że tworzy centralne repozytorium, w którym rejestrowane są wszystkie dostępne usługi lub zależności. Kiedy obiekt potrzebuje zależności, prosi o nią Service Locator. -Jednakże, w porównaniu z Dependency Injection, traci na przejrzystości: zależności nie są bezpośrednio przekazywane do obiektów i dlatego nie są łatwo identyfikowalne, co wymaga zbadania kodu, aby odkryć i zrozumieć wszystkie połączenia. Testowanie jest również bardziej skomplikowane, ponieważ nie możemy po prostu przekazać obiektów mock do testowanych obiektów, ale musimy przejść przez Service Locator. Ponadto Service Locator zakłóca projektowanie kodu, ponieważ poszczególne obiekty muszą być świadome jego istnienia, co różni się od Dependency Injection, gdzie obiekty nie mają wiedzy o kontenerze DI. +W porównaniu do Dependency Injection traci jednak na przejrzystości: zależności nie są przekazywane obiektom bezpośrednio i nie są tak łatwo identyfikowalne, co wymaga przeanalizowania kodu, aby wszystkie powiązania zostały odkryte i zrozumiane. Testowanie jest również bardziej skomplikowane, ponieważ nie możemy po prostu przekazywać obiektów mock do testowanych obiektów, ale musimy to robić przez Service Locator. Ponadto Service Locator narusza projekt kodu, ponieważ poszczególne obiekty muszą wiedzieć o jego istnieniu, co różni się od Dependency Injection, gdzie obiekty nie mają świadomości istnienia kontenera DI. -Kiedy lepiej nie używać DI? .[#toc-when-is-it-better-not-to-use-di] -------------------------------------------------------------------- +Kiedy lepiej nie używać DI? +--------------------------- -Nie są znane trudności związane z używaniem wzorca projektowego Dependency Injection. Wręcz przeciwnie, uzyskiwanie zależności z globalnie dostępnych lokalizacji prowadzi do [wielu komplikacji |global-state], podobnie jak używanie Service Locatora. -Dlatego zaleca się, aby zawsze używać DI. Nie jest to podejście dogmatyczne, ale po prostu nie znaleziono lepszej alternatywy. +Nie są znane żadne trudności związane z użyciem wzorca projektowego Dependency Injection. Wręcz przeciwnie, pobieranie zależności z globalnie dostępnych miejsc prowadzi do [całego szeregu komplikacji |global-state], podobnie jak używanie Service Locatora. Dlatego warto zawsze korzystać z DI. To nie jest podejście dogmatyczne, ale po prostu nie znaleziono lepszej alternatywy. -Istnieją jednak pewne sytuacje, w których nie przekazujemy sobie obiektów i nie uzyskujemy ich z przestrzeni globalnej. Na przykład podczas debugowania kodu i konieczności zrzucenia wartości zmiennej w określonym punkcie programu, zmierzenia czasu trwania pewnego fragmentu programu, czy też zalogowania komunikatu. -W takich przypadkach, gdy chodzi o działania tymczasowe, które później zostaną usunięte z kodu, uzasadnione jest użycie globalnie dostępnego dumpera, stopera czy loggera. Narzędzia te nie należą przecież do projektu kodu. +Mimo to istnieją pewne sytuacje, w których nie przekazujemy sobie obiektów i pobieramy je z przestrzeni globalnej. Na przykład podczas debugowania kodu, gdy potrzebujesz w konkretnym punkcie programu wypisać wartość zmiennej, zmierzyć czas trwania określonej części programu lub zapisać komunikat. W takich przypadkach, gdy chodzi o tymczasowe czynności, które zostaną później usunięte z kodu, uzasadnione jest wykorzystanie globalnie dostępnego dumpera, stopera lub loggera. Te narzędzia bowiem nie należą do projektu kodu. -Czy używanie DI ma swoje wady? .[#toc-does-using-di-have-its-drawbacks] ------------------------------------------------------------------------ +Czy używanie DI ma swoje wady? +------------------------------ -Czy używanie Dependency Injection wiąże się z jakimiś wadami, takimi jak zwiększenie złożoności pisania kodu lub gorsza wydajność? Co tracimy, gdy zaczynamy pisać kod zgodnie z DI? +Czy użycie Dependency Injection wiąże się z jakimiś wadami, takimi jak zwiększona pracochłonność pisania kodu lub pogorszona wydajność? Co tracimy, gdy zaczniemy pisać kod zgodnie z DI? -DI nie ma wpływu na wydajność aplikacji ani na wymagania dotyczące pamięci. Wydajność kontenera DI może odgrywać pewną rolę, ale w przypadku [Nette DI | nette-container], kontener jest skompilowany do czystego PHP, więc jego narzut w czasie działania aplikacji jest w zasadzie zerowy. +DI nie ma wpływu na wydajność ani zużycie pamięci aplikacji. Pewną rolę może odgrywać wydajność Kontenera DI, jednak w przypadku [Nette DI |nette-container] kontener jest kompilowany do czystego PHP, więc jego narzut podczas działania aplikacji jest w zasadzie zerowy. -Podczas pisania kodu konieczne jest tworzenie konstruktorów, które akceptują zależności. W przeszłości mogło to być czasochłonne, ale dzięki nowoczesnym IDE i [promowaniu właściwości |https://blog.nette.org/pl/php-8-0-kompletny-przeglad-nowosci#toc-constructor-property-promotion] konstruktorów jest to obecnie kwestia kilku sekund. Fabryki można łatwo wygenerować za pomocą Nette DI i wtyczki PhpStorm za pomocą kilku kliknięć. -Z drugiej strony nie ma potrzeby pisania singletonów i statycznych punktów dostępu. +Podczas pisania kodu często konieczne jest tworzenie konstruktorów przyjmujących zależności. Kiedyś mogło to być czasochłonne, jednak dzięki nowoczesnym IDE i [constructor property promotion |https://blog.nette.org/pl/php-8-0-complete-overview-of-news#toc-constructor-property-promotion] jest to teraz kwestia kilku sekund. Fabryki można łatwo generować za pomocą Nette DI i wtyczki do PhpStorm jednym kliknięciem myszy. Z drugiej strony odpada potrzeba pisania singletonów i statycznych punktów dostępu. -Można stwierdzić, że prawidłowo zaprojektowana aplikacja wykorzystująca DI nie jest ani krótsza, ani dłuższa w porównaniu z aplikacją wykorzystującą singletony. Części kodu pracujące z zależnościami są po prostu wyodrębnione z poszczególnych klas i przeniesione w nowe miejsca, czyli do kontenera DI i fabryk. +Można stwierdzić, że poprawnie zaprojektowana aplikacja wykorzystująca DI nie jest ani krótsza, ani dłuższa w porównaniu z aplikacją wykorzystującą singletony. Części kodu pracujące z zależnościami są jedynie wyjęte z poszczególnych klas i przeniesione do nowych miejsc, czyli do kontenera DI i fabryk. -Jak przepisać starszą aplikację na DI? .[#toc-how-to-rewrite-a-legacy-application-to-di] ----------------------------------------------------------------------------------------- +Jak przepisać aplikację legacy na DI? +------------------------------------- -Migracja ze starszej aplikacji do Dependency Injection może być trudnym procesem, szczególnie w przypadku dużych i złożonych aplikacji. Ważne jest, aby podejść do tego procesu systematycznie. +Przejście z aplikacji legacy na Dependency Injection może być wymagającym procesem, zwłaszcza w przypadku dużych i złożonych aplikacji. Ważne jest, aby podchodzić do tego procesu systematycznie. -- Podczas przechodzenia na Dependency Injection ważne jest, aby wszyscy członkowie zespołu rozumieli zasady i praktyki, które są używane. -- Najpierw przeprowadź analizę istniejącej aplikacji, aby zidentyfikować kluczowe komponenty i ich zależności. Stwórz plan, które części będą refaktoryzowane i w jakiej kolejności. -- Zaimplementuj kontener DI lub, jeszcze lepiej, użyj istniejącej biblioteki, takiej jak Nette DI. -- Stopniowo refaktoryzuj każdą część aplikacji, aby użyć Dependency Injection. Może to obejmować modyfikację konstruktorów lub metod, aby akceptowały zależności jako parametry. -- Zmodyfikuj miejsca w kodzie, gdzie tworzone są obiekty zależności, tak aby zależności były zamiast tego wstrzykiwane przez kontener. Może to obejmować użycie fabryk. +- Podczas przechodzenia na Dependency Injection ważne jest, aby wszyscy członkowie zespołu rozumieli zasady i procedury, które są stosowane. +- Najpierw przeprowadź analizę istniejącej aplikacji i zidentyfikuj kluczowe komponenty oraz ich zależności. Stwórz plan, które części będą refaktoryzowane i w jakiej kolejności. +- Zaimplementuj kontener DI lub jeszcze lepiej użyj istniejącej biblioteki, na przykład Nette DI. +- Stopniowo refaktoryzuj poszczególne części aplikacji, aby używały Dependency Injection. Może to obejmować modyfikacje konstruktorów lub metod tak, aby przyjmowały zależności jako parametry. +- Zmodyfikuj miejsca w kodzie, gdzie tworzone są obiekty z zależnościami, aby zamiast tego zależności były wstrzykiwane przez kontener. Może to obejmować użycie fabryk. -Pamiętaj, że przejście na Dependency Injection jest inwestycją w jakość kodu i długoterminową stabilność aplikacji. Chociaż wprowadzenie tych zmian może być wyzwaniem, rezultatem powinien być czystszy, bardziej modułowy i łatwo testowalny kod, który jest gotowy do przyszłych rozszerzeń i konserwacji. +Pamiętaj, że przejście na Dependency Injection to inwestycja w jakość kodu i długoterminową utrzymywalność aplikacji. Chociaż przeprowadzenie tych zmian może być trudne, wynikiem powinien być czystszy, bardziej modularny i łatwo testowalny kod, który jest gotowy na przyszłe rozszerzenia i konserwację. -Dlaczego kompozycja jest preferowana nad dziedziczeniem? .[#toc-why-composition-is-preferred-over-inheritance] --------------------------------------------------------------------------------------------------------------- -Lepiej jest używać kompozycji niż dziedziczenia, ponieważ służy to celowi ponownego użycia kodu bez potrzeby martwienia się o efekt podstępnej zmiany. W ten sposób zapewnia bardziej luźne sprzężenie, gdzie nie musimy się martwić o to, że zmiana jakiegoś kodu powoduje, że inny zależny kod wymaga zmiany. Typowym przykładem jest sytuacja określona jako [piekło konstruktorów |passing-dependencies#Constructor hell]. +Dlaczego preferuje się kompozycję nad dziedziczeniem? +----------------------------------------------------- +Lepiej jest używać [kompozycji |nette:introduction-to-object-oriented-programming#Kompozycja] zamiast [dziedziczenia |nette:introduction-to-object-oriented-programming#Dziedziczenie], ponieważ służy ona do ponownego wykorzystania kodu, nie martwiąc się o konsekwencje zmian. Zapewnia więc luźniejsze powiązanie, dzięki czemu nie musimy się obawiać, że zmiana jakiegoś kodu spowoduje potrzebę zmiany innego zależnego kodu. Typowym przykładem jest sytuacja określana jako [constructor hell |passing-dependencies#Constructor hell]. -Czy Nette DI Container może być używany poza Nette? .[#toc-can-nette-di-container-be-used-outside-of-nette] ------------------------------------------------------------------------------------------------------------ +Czy można użyć Nette DI Container poza Nette? +--------------------------------------------- -Absolutnie. Nette DI Container jest częścią Nette, ale został zaprojektowany jako samodzielna biblioteka, która może być używana niezależnie od innych części frameworka. Wystarczy zainstalować ją za pomocą Composera, stworzyć plik konfiguracyjny definiujący Twoje usługi, a następnie użyć kilku linii kodu PHP do stworzenia kontenera DI. -I możesz natychmiast zacząć wykorzystywać Dependency Injection w swoich projektach. +Zdecydowanie. Nette DI Container jest częścią Nette, ale został zaprojektowany jako samodzielna biblioteka, która może być używana niezależnie od pozostałych części frameworka. Wystarczy ją zainstalować za pomocą Composera, utworzyć plik konfiguracyjny z definicją Twoich usług, a następnie za pomocą kilku linii kodu PHP utworzyć kontener DI. I od razu możesz zacząć korzystać z zalet Dependency Injection w swoich projektach. -Rozdział [Nette DI Container |nette-container] opisuje, jak wygląda konkretny przypadek użycia, łącznie z kodem. +Jak wygląda konkretne użycie wraz z kodami opisuje rozdział [Nette DI Container |nette-container]. -Dlaczego konfiguracja znajduje się w plikach NEON? .[#toc-why-is-the-configuration-in-neon-files] -------------------------------------------------------------------------------------------------- +Dlaczego konfiguracja jest w plikach NEON? +------------------------------------------ -NEON to prosty i czytelny język konfiguracyjny opracowany w ramach Nette, służący do konfigurowania aplikacji, usług i ich zależności. W porównaniu do JSON czy YAML, oferuje znacznie bardziej intuicyjne i elastyczne opcje w tym zakresie. W NEONie możesz naturalnie opisać wiązania, które w Symfony & YAML nie byłyby możliwe do napisania w ogóle lub tylko poprzez skomplikowany opis. +NEON to prosty i łatwy do odczytania język konfiguracyjny, który został opracowany w ramach Nette do ustawiania aplikacji, usług i ich zależności. W porównaniu z JSONem lub YAMLem oferuje dla tego celu znacznie bardziej intuicyjne i elastyczne możliwości. W NEONie można naturalnie opisać powiązania, których w Symfony & YAMLu nie dałoby się zapisać albo w ogóle, albo tylko za pomocą skomplikowanego opisu. -Czy parsowanie plików NEON spowalnia działanie aplikacji? .[#toc-does-parsing-neon-files-slow-down-the-application] -------------------------------------------------------------------------------------------------------------------- +Czy parsowanie plików NEON nie spowalnia aplikacji? +--------------------------------------------------- -Chociaż pliki NEON są parsowane bardzo szybko, aspekt ten nie ma większego znaczenia. Powodem jest to, że parsowanie plików występuje tylko raz podczas pierwszego uruchomienia aplikacji. Następnie kod kontenera DI jest generowany, przechowywany na dysku i wykonywany dla każdego kolejnego żądania bez potrzeby dalszego parsowania. +Chociaż pliki NEON parsują się bardzo szybko, ten aspekt w ogóle nie ma znaczenia. Powodem jest to, że parsowanie plików odbywa się tylko raz przy pierwszym uruchomieniu aplikacji. Następnie generowany jest kod kontenera DI, zapisywany na dysku i uruchamiany przy każdym kolejnym żądaniu, bez konieczności przeprowadzania dalszego parsowania. -Tak właśnie działa to w środowisku produkcyjnym. Podczas tworzenia aplikacji pliki NEON są parsowane za każdym razem, gdy zmienia się ich zawartość, co zapewnia, że programista ma zawsze aktualny kontener DI. Jak wspomniano wcześniej, faktyczne parsowanie jest kwestią chwili. +Tak to działa w środowisku produkcyjnym. Podczas rozwoju pliki NEON są parsowane za każdym razem, gdy dojdzie do zmiany ich zawartości, aby programista miał zawsze aktualny kontener DI. Samo parsowanie jest, jak powiedziano, kwestią chwili. -Jak uzyskać dostęp do parametrów z pliku konfiguracyjnego w mojej klasie? .[#toc-how-do-i-access-the-parameters-from-the-configuration-file-in-my-class] --------------------------------------------------------------------------------------------------------------------------------------------------------- +Jak dostać się z mojej klasy do parametrów w pliku konfiguracyjnym? +------------------------------------------------------------------- -Należy pamiętać o [regule #1: Let It Be Passed to |introduction#Rule #1: Let It Be Passed to You] You. Jeśli klasa wymaga informacji z pliku konfiguracyjnego, nie musimy wymyślać jak uzyskać dostęp do tych informacji; zamiast tego, po prostu o nie prosimy - na przykład poprzez konstruktor klasy. A my wykonujemy przekazywanie informacji w pliku konfiguracyjnym. +Pamiętajmy o [Regule nr 1: niech Ci to przekażą |introduction#Zasada nr 1: niech ci to przekażą]. Jeśli klasa wymaga informacji z pliku konfiguracyjnego, nie musimy zastanawiać się, jak się do tych informacji dostać, zamiast tego po prostu o nie prosimy - na przykład za pomocą konstruktora klasy. A przekazanie realizujemy w pliku konfiguracyjnym. -W tym przykładzie, `%myParameter%` to placeholder dla wartości parametru `myParameter`, który zostanie przekazany do konstruktora `MyClass`: +W tym przykładzie `%myParameter%` jest symbolem zastępczym dla wartości parametru `myParameter`, który zostanie przekazany do konstruktora klasy `MyClass`: ```php # config.neon @@ -103,10 +97,10 @@ services: - MyClass(%myParameter%) ``` -Jeśli chcesz przekazać wiele parametrów lub użyć autowiring, warto [zawinąć parametry w obiekt |best-practices:passing-settings-to-presenters]. +Aby przekazywać więcej parametrów lub wykorzystać autowiring, warto [opakować parametry w obiekt |best-practices:passing-settings-to-presenters]. -Czy Nette obsługuje interfejs PSR-11 Container? .[#toc-does-nette-support-psr-11-container-interface] ------------------------------------------------------------------------------------------------------ +Czy Nette obsługuje PSR-11: Container interface? +------------------------------------------------ -Nette DI Container nie obsługuje bezpośrednio PSR-11. Jeśli jednak potrzebujesz interoperacyjności między Nette DI Container a bibliotekami lub frameworkami, które oczekują interfejsu PSR-11 Container, możesz stworzyć [prosty adapter |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f], który posłuży jako most między Nette DI Container a PSR-11. +Nette DI Container nie obsługuje bezpośrednio PSR-11. Jednakże, jeśli potrzebujesz interoperacyjności między Nette DI Containerem a bibliotekami lub frameworkami, które oczekują PSR-11 Container Interface, możesz utworzyć [prosty adapter |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f], który będzie służył jako most między Nette DI Containerem a PSR-11. diff --git a/dependency-injection/pl/global-state.texy b/dependency-injection/pl/global-state.texy index d2812e5143..8d9e41920d 100644 --- a/dependency-injection/pl/global-state.texy +++ b/dependency-injection/pl/global-state.texy @@ -2,66 +2,62 @@ Stan globalny i singletony ************************** .[perex] -Ostrzeżenie: Poniższe konstrukcje są objawami źle zaprojektowanego kodu: +Ostrzeżenie: Poniższe konstrukcje są oznaką źle zaprojektowanego kodu: - `Foo::getInstance()` - `DB::insert(...)` - `Article::setDb($db)` - `ClassName::$var` lub `static::$var` -Czy napotkałeś którąś z tych konstrukcji w swoim kodzie? Jeśli tak, to masz okazję je poprawić. Można by pomyśleć, że są to powszechne konstrukcje, często spotykane w przykładowych rozwiązaniach różnych bibliotek i frameworków. Jeśli tak jest, ich projekt kodu jest wadliwy. +Czy niektóre z tych konstrukcji występują w Twoim kodzie? W takim razie masz okazję do jego ulepszenia. Być może myślisz, że są to powszechne konstrukcje, które widzisz nawet w przykładowych rozwiązaniach różnych bibliotek i frameworków. Jeśli tak jest, to projekt ich kodu nie jest dobry. -Nie mówimy tutaj o jakiejś akademickiej czystości. Wszystkie te konstrukcje mają jedną wspólną cechę: wykorzystują stan globalny. Ma to destrukcyjny wpływ na jakość kodu. Klasy wprowadzają w błąd co do swoich zależności. Kod staje się nieprzewidywalny. Dezorientuje to programistów i zmniejsza ich wydajność. +Teraz zdecydowanie nie mówimy o jakiejś akademickiej czystości. Wszystkie te konstrukcje mają jedną wspólną cechę: wykorzystują stan globalny. A ten ma destrukcyjny wpływ na jakość kodu. Klasy kłamią o swoich zależnościach. Kod staje się nieprzewidywalny. Mylą programistów i obniżają ich efektywność. -W tym rozdziale wyjaśnimy, dlaczego tak się dzieje i jak uniknąć stanu globalnego. +W tym rozdziale wyjaśnimy, dlaczego tak jest i jak unikać stanu globalnego. -Globalne powiązania .[#toc-global-interlinking] ------------------------------------------------ +Globalne powiązanie +------------------- -W idealnym świecie obiekt powinien komunikować się tylko z obiektami, które zostały [mu bezpośrednio przekazane |passing-dependencies]. Jeśli utworzę dwa obiekty `A` i `B` i nigdy nie przekażę między nimi referencji, to ani `A`, ani `B` nie będą mogły uzyskać dostępu do stanu drugiego obiektu ani go zmodyfikować. Jest to bardzo pożądana właściwość kodu. Przypomina to posiadanie baterii i żarówki; żarówka nie zaświeci się, dopóki nie połączysz jej z baterią za pomocą przewodu. +W idealnym świecie obiekt powinien móc komunikować się tylko z obiektami, które zostały mu [bezpośrednio przekazane |passing-dependencies]. Jeśli utworzę dwa obiekty `A` i `B` i nigdy nie przekażę referencji między nimi, to ani `A`, ani `B` nie mogą dostać się do drugiego obiektu ani zmienić jego stanu. To jest bardzo pożądana właściwość kodu. Jest to podobne do sytuacji, gdy masz baterię i żarówkę; żarówka nie zaświeci, dopóki nie połączysz jej z baterią drutem. -Nie dotyczy to jednak zmiennych globalnych (statycznych) lub singletonów. Obiekt `A` może *bezprzewodowo* uzyskać dostęp do obiektu `C` i zmodyfikować go bez przekazywania referencji, poprzez wywołanie `C::changeSomething()`. Jeśli obiekt `B` ma również dostęp do globalnego `C`, to `A` i `B` mogą wpływać na siebie nawzajem poprzez `C`. +Ale to nie dotyczy globalnych (statycznych) zmiennych lub singletonów. Obiekt `A` mógłby *bezprzewodowo* dostać się do obiektu `C` i zmodyfikować go bez jakiegokolwiek przekazania referencji, wywołując `C::changeSomething()`. Jeśli obiekt `B` również chwyci globalne `C`, to `A` i `B` mogą wzajemnie na siebie wpływać za pośrednictwem `C`. -Używanie zmiennych globalnych wprowadza nową formę *bezprzewodowego* sprzężenia, które nie jest widoczne z zewnątrz. Tworzy to zasłonę dymną, która komplikuje zrozumienie i używanie kodu. Aby naprawdę zrozumieć zależności, programiści muszą przeczytać każdą linijkę kodu źródłowego, a nie tylko zapoznać się z interfejsami klas. Co więcej, to uwikłanie jest całkowicie niepotrzebne. Stan globalny jest używany, ponieważ jest łatwo dostępny z dowolnego miejsca i umożliwia, na przykład, zapis do bazy danych za pomocą globalnej (statycznej) metody `DB::insert()`. Jednak, jak zobaczymy, korzyści, jakie oferuje, są minimalne, podczas gdy komplikacje, które wprowadza, są poważne. +Użycie globalnych zmiennych wprowadza do systemu nową formę *bezprzewodowego* powiązania, która nie jest widoczna z zewnątrz. Tworzy zasłonę dymną utrudniającą zrozumienie i używanie kodu. Aby programiści rzeczywiście zrozumieli zależności, muszą przeczytać każdą linię kodu źródłowego. Zamiast jedynie zapoznać się z interfejsem klas. Jest to ponadto powiązanie całkowicie zbędne. Stan globalny jest używany dlatego, że jest łatwo dostępny z dowolnego miejsca i pozwala na przykład zapisać do bazy danych za pomocą globalnej (statycznej) metody `DB::insert()`. Ale jak pokażemy, korzyść, którą to przynosi, jest znikoma, natomiast komplikacje, które powoduje, są fatalne. .[note] -Pod względem zachowania nie ma różnicy między zmienną globalną a statyczną. Są one równie szkodliwe. +Z punktu widzenia zachowania nie ma różnicy między zmienną globalną a statyczną. Są równie szkodliwe. -Upiorne działanie na odległość .[#toc-the-spooky-action-at-a-distance] ----------------------------------------------------------------------- +Upiorne działanie na odległość +------------------------------ -"Upiorne działanie na odległość" - tak Albert Einstein nazwał słynne zjawisko w fizyce kwantowej, które w 1935 roku przyprawiło go o dreszcze. -Chodzi o splątanie kwantowe, którego osobliwość polega na tym, że gdy mierzymy informację o jednej cząstce, natychmiast wpływamy na inną cząstkę, nawet jeśli są one oddalone od siebie o miliony lat świetlnych. -Co pozornie narusza podstawowe prawo wszechświata, że nic nie może podróżować szybciej niż światło. +"Upiorne działanie na odległość" - tak słynnie nazwał w 1935 roku Albert Einstein zjawisko w fizyce kwantowej, które przyprawiało go o gęsią skórkę. +Chodzi o splątanie kwantowe, którego osobliwością jest to, że gdy zmierzysz informację o jednej cząstce, natychmiast wpływasz na drugą cząstkę, nawet jeśli są oddalone od siebie o miliony lat świetlnych. Co pozornie narusza podstawowe prawo wszechświata, że nic nie może poruszać się szybciej niż światło. -W świecie oprogramowania "upiornym działaniem na odległość" możemy nazwać sytuację, w której uruchamiamy proces, o którym myślimy, że jest izolowany (bo nie przekazaliśmy mu żadnych referencji), ale nieoczekiwane interakcje i zmiany stanu zachodzą w odległych miejscach systemu, o których nie powiedzieliśmy obiektowi. Może to nastąpić tylko poprzez stan globalny. +W świecie oprogramowania możemy nazwać "upiornym działaniem na odległość" sytuację, gdy uruchamiamy jakiś proces, o którym sądzimy, że jest izolowany (ponieważ nie przekazaliśmy mu żadnych referencji), ale w odległych miejscach systemu dochodzi do nieoczekiwanych interakcji i zmian stanu, o których nie mieliśmy pojęcia. Może do tego dojść tylko za pośrednictwem stanu globalnego. -Wyobraź sobie, że dołączasz do zespołu rozwijającego projekt, który ma dużą, dojrzałą bazę kodu. Twój nowy lider prosi cię o wdrożenie nowej funkcji i, jak dobry deweloper, zaczynasz od napisania testu. Ale ponieważ jesteś nowy w projekcie, robisz wiele testów eksploracyjnych "co się stanie, jeśli zadzwonię do tej metody" typu. I próbujesz napisać następujący test: +Wyobraź sobie, że dołączasz do zespołu programistów projektu, który ma obszerną, dojrzałą bazę kodu. Twój nowy przełożony prosi Cię o zaimplementowanie nowej funkcji, a Ty jako dobry programista zaczynasz od napisania testu. Ale ponieważ jesteś nowy w projekcie, robisz wiele testów eksploracyjnych typu "co się stanie, jeśli wywołam tę metodę". I próbujesz napisać następujący test: ```php function testCreditCardCharge() { - $cc = new CreditCard('1234567890123456', 5, 2028); // numer karty + $cc = new CreditCard('1234567890123456', 5, 2028); // numer Twojej karty $cc->charge(100); } ``` -Uruchamiasz kod, może kilka razy, i po jakimś czasie zauważasz na swoim telefonie powiadomienia z banku, że przy każdym uruchomieniu 100$ zostało pobrane z Twojej karty kredytowej 🤦‍♂️ +Uruchamiasz kod, może kilka razy, i po jakimś czasie zauważasz na telefonie powiadomienia z banku, że przy każdym uruchomieniu pobrano 100 dolarów z Twojej karty płatniczej 🤦‍♂️ -Jak u licha test mógł spowodować faktyczne obciążenie? Nie jest łatwo operować kartą kredytową. Musisz wejść w interakcję z usługą internetową strony trzeciej, musisz znać adres URL tej usługi internetowej, musisz się zalogować i tak dalej. -Żadna z tych informacji nie jest zawarta w teście. Co gorsza, nie wiesz nawet, gdzie te informacje są obecne, a zatem jak kpić z zewnętrznych zależności, aby każdy bieg nie powodował ponownego naliczania 100 USD. A jako nowy deweloper, jak miałeś wiedzieć, że to, co zamierzasz zrobić, doprowadzi do tego, że będziesz uboższy o 100 dolarów? +Jak, do diabła, test mógł spowodować rzeczywiste pobranie pieniędzy? Operowanie kartą płatniczą nie jest łatwe. Musisz komunikować się z usługą internetową strony trzeciej, musisz znać adres URL tej usługi, musisz się zalogować i tak dalej. Żadna z tych informacji nie jest zawarta w teście. Co gorsza, nawet nie wiesz, gdzie te informacje się znajdują, a więc ani jak mockować zewnętrzne zależności, aby każde uruchomienie nie prowadziło do ponownego pobrania 100 dolarów. I skąd jako nowy programista miałeś wiedzieć, że to, co zamierzasz zrobić, doprowadzi do tego, że będziesz o 100 dolarów biedniejszy? To jest upiorne działanie na odległość! -Nie masz wyboru, musisz przekopać się przez wiele kodu źródłowego, pytając starszych i bardziej doświadczonych kolegów, aż zrozumiesz, jak działają połączenia w projekcie. -Wynika to z faktu, że patrząc na interfejs klasy `CreditCard`, nie można określić stanu globalnego, który musi zostać zainicjalizowany. Nawet patrząc na kod źródłowy klasy nie dowiesz się, którą metodę inicjalizacji należy wywołać. W najlepszym przypadku możesz znaleźć zmienną globalną, do której uzyskuje się dostęp, i spróbować zgadnąć, jak ją zainicjalizować z tego. +Nie pozostaje Ci nic innego, jak długo grzebać w mnóstwie kodu źródłowego, pytać starszych i bardziej doświadczonych kolegów, zanim zrozumiesz, jak działają powiązania w projekcie. Jest to spowodowane tym, że patrząc na interfejs klasy `CreditCard`, nie można zidentyfikować stanu globalnego, który należy zainicjować. Nawet spojrzenie na kod źródłowy klasy nie powie Ci, którą metodę inicjalizacyjną masz wywołać. W najlepszym przypadku możesz znaleźć globalną zmienną, do której uzyskuje się dostęp, i na jej podstawie próbować odgadnąć, jak ją zainicjować. -Klasy w takim projekcie są patologicznymi kłamcami. Karta płatnicza udaje, że można ją po prostu zainicjować i wywołać metodę `charge()`. Jednak potajemnie współdziała z inną klasą, `PaymentGateway`. Nawet jej interfejs mówi, że może być inicjalizowana niezależnie, ale w rzeczywistości ściąga poświadczenia z jakiegoś pliku konfiguracyjnego i tak dalej. -Dla programistów, którzy napisali ten kod, jest jasne, że `CreditCard` potrzebuje `PaymentGateway`. Napisali kod w ten sposób. Ale dla każdego nowego w projekcie jest to kompletna tajemnica i utrudnia naukę. +Klasy w takim projekcie są patologicznymi kłamcami. Karta płatnicza udaje, że wystarczy ją utworzyć i wywołać metodę `charge()`. W ukryciu jednak współpracuje z inną klasą `PaymentGateway`, która reprezentuje bramkę płatniczą. Jej interfejs również mówi, że można ją zainicjować samodzielnie, ale w rzeczywistości pobiera dane uwierzytelniające z jakiegoś pliku konfiguracyjnego i tak dalej. Programistom, którzy napisali ten kod, jest jasne, że `CreditCard` potrzebuje `PaymentGateway`. Napisali kod w ten sposób. Ale dla każdego, kto jest nowy w projekcie, jest to kompletna zagadka i utrudnia naukę. -Jak naprawić tę sytuację? Łatwo. **Pozwól API zadeklarować zależności**. +Jak naprawić sytuację? Łatwo. **Niech API deklaruje zależności.** ```php function testCreditCardCharge() @@ -72,36 +68,35 @@ function testCreditCardCharge() } ``` -Zauważ, jak zależności wewnątrz kodu są nagle oczywiste. Deklarując, że metoda `charge()` potrzebuje `PaymentGateway`, nie musisz nikogo pytać, w jaki sposób kod jest współzależny. Wiesz, że musisz stworzyć jego instancję, a kiedy próbujesz to zrobić, natrafiasz na fakt, że musisz dostarczyć parametry dostępu. Bez nich kod nawet by się nie uruchomił. +Zauważ, jak nagle powiązania wewnątrz kodu stają się oczywiste. Dzięki temu, że metoda `charge()` deklaruje, że potrzebuje `PaymentGateway`, nie musisz nikogo pytać, jak kod jest powiązany. Wiesz, że musisz utworzyć jej instancję, a gdy spróbujesz to zrobić, natkniesz się na to, że musisz podać parametry dostępu. Bez nich kod nie dałby się nawet uruchomić. -I co najważniejsze, możesz teraz kpić z bramki płatności, więc nie zostaniesz obciążony 100 $ za każdym razem, gdy uruchomisz test. +A co najważniejsze, teraz możesz mockować bramkę płatniczą, dzięki czemu przy każdym uruchomieniu testu nie zostanie Ci naliczone 100 dolarów. -Stan globalny powoduje, że twoje obiekty mogą potajemnie uzyskać dostęp do rzeczy, które nie są zadeklarowane w ich interfejsach API, a w rezultacie czyni twoje interfejsy API patologicznymi kłamcami. +Stan globalny powoduje, że Twoje obiekty mogą potajemnie uzyskiwać dostęp do rzeczy, które nie są zadeklarowane w ich API, a w rezultacie czynią z Twoich API patologicznych kłamców. -Być może wcześniej nie myślałeś o tym w ten sposób, ale zawsze, gdy używasz stanu globalnego, tworzysz tajne kanały komunikacji bezprzewodowej. Przerażające zdalne działanie zmusza programistów do czytania każdej linijki kodu, aby zrozumieć potencjalne interakcje, zmniejsza produktywność programistów i dezorientuje nowych członków zespołu. -Jeśli jesteś tym, który stworzył kod, znasz prawdziwe zależności, ale każdy, kto przyjdzie po tobie, nie ma pojęcia. +Być może wcześniej o tym tak nie myślałeś, ale za każdym razem, gdy używasz stanu globalnego, tworzysz tajne bezprzewodowe kanały komunikacyjne. Upiorne działanie na odległość zmusza programistów do czytania każdej linii kodu, aby zrozumieć potencjalne interakcje, obniża produktywność programistów i myli nowych członków zespołu. Jeśli to Ty stworzyłeś kod, znasz rzeczywiste zależności, ale każdy, kto przyjdzie po Tobie, jest bezradny. -Nie pisz kodu, który używa globalnego stanu, wolą przekazać zależności. To jest zastrzyk zależności. +Nie pisz kodu, który wykorzystuje stan globalny, preferuj przekazywanie zależności. Czyli dependency injection. -Kruchość globalnego państwa .[#toc-brittleness-of-the-global-state] -------------------------------------------------------------------- +Kruchość stanu globalnego +------------------------- -W kodzie, który używa stanu globalnego i singletonów, nigdy nie ma pewności, kiedy i przez kogo ten stan został zmieniony. To ryzyko pojawia się już podczas inicjalizacji. Poniższy kod ma za zadanie utworzyć połączenie z bazą danych i zainicjalizować bramkę płatniczą, ale ciągle rzuca wyjątek, a znalezienie przyczyny jest niezwykle żmudne: +W kodzie, który używa stanu globalnego i singletonów, nigdy nie jest pewne, kiedy i kto ten stan zmienił. To ryzyko pojawia się już przy inicjalizacji. Poniższy kod ma utworzyć połączenie z bazą danych i zainicjować bramkę płatniczą, jednak ciągle rzuca wyjątek, a znalezienie przyczyny jest niezwykle czasochłonne: ```php PaymentGateway::init(); DB::init('mysql:', 'user', 'password'); ``` -Musisz szczegółowo przejrzeć kod, aby znaleźć, że obiekt `PaymentGateway` uzyskuje dostęp do innych obiektów bezprzewodowo, z których niektóre wymagają połączenia z bazą danych. Musisz więc zainicjalizować bazę danych przed `PaymentGateway`. Jednak zasłona dymna stanu globalnego ukrywa to przed tobą. Ile czasu zaoszczędziłbyś, gdyby API każdej klasy nie kłamało i nie deklarowało swoich zależności? +Musisz szczegółowo przeglądać kod, aby dowiedzieć się, że obiekt `PaymentGateway` uzyskuje bezprzewodowy dostęp do innych obiektów, z których niektóre wymagają połączenia z bazą danych. Czyli konieczne jest zainicjowanie bazy danych przed `PaymentGateway`. Jednak zasłona dymna stanu globalnego ukrywa to przed Tobą. Ile czasu byś zaoszczędził, gdyby API poszczególnych klas nie kłamało i deklarowało swoje zależności? ```php $db = new DB('mysql:', 'user', 'password'); $gateway = new PaymentGateway($db, ...); ``` -Podobny problem pojawia się podczas korzystania z globalnego dostępu do połączenia z bazą danych: +Podobny problem pojawia się również przy użyciu globalnego dostępu do połączenia z bazą danych: ```php use Illuminate\Support\Facades\DB; @@ -115,7 +110,7 @@ class Article } ``` -Podczas wywoływania metody `save()` nie ma pewności, czy połączenie z bazą danych zostało już utworzone i kto jest odpowiedzialny za jego utworzenie. Na przykład, gdybyśmy chcieli zmienić połączenie z bazą danych w locie, być może w celach testowych, prawdopodobnie musielibyśmy stworzyć dodatkowe metody, takie jak `DB::reconnect(...)` lub `DB::reconnectForTest()`. +Przy wywołaniu metody `save()` nie jest pewne, czy połączenie z bazą danych zostało już utworzone i kto jest odpowiedzialny za jego utworzenie. Jeśli chcemy na przykład zmieniać połączenie z bazą danych w trakcie działania, na przykład w celu testów, musielibyśmy najprawdopodobniej utworzyć dodatkowe metody, takie jak `DB::reconnect(...)` lub `DB::reconnectForTest()`. Rozważmy przykład: @@ -127,9 +122,9 @@ Foo::doSomething(); $article->save(); ``` -Skąd możemy mieć pewność, że testowa baza danych jest naprawdę używana podczas wywoływania metody `$article->save()`? Co by było, gdyby metoda `Foo::doSomething()` zmieniła globalne połączenie z bazą danych? Aby się tego dowiedzieć, musielibyśmy zbadać kod źródłowy klasy `Foo` i prawdopodobnie wielu innych klas. Jednak takie podejście dałoby tylko krótkotrwałą odpowiedź, ponieważ sytuacja może się zmienić w przyszłości. +Skąd mamy pewność, że przy wywołaniu `$article->save()` rzeczywiście używana jest testowa baza danych? Co jeśli metoda `Foo::doSomething()` zmieniła globalne połączenie z bazą danych? Aby to sprawdzić, musielibyśmy przeanalizować kod źródłowy klasy `Foo` i prawdopodobnie wielu innych klas. To podejście przyniosłoby jednak tylko krótkoterminową odpowiedź, ponieważ sytuacja może się w przyszłości zmienić. -Co by się stało, gdybyśmy przenieśli połączenie z bazą danych do zmiennej statycznej wewnątrz klasy `Article`? +A co jeśli przeniesiemy połączenie z bazą danych do zmiennej statycznej wewnątrz klasy `Article`? ```php class Article @@ -148,11 +143,11 @@ class Article } ``` -To w ogóle niczego nie zmienia. Problem jest stanem globalnym i nie ma znaczenia, w której klasie się ukrywa. W tym przypadku, podobnie jak w poprzednim, nie mamy pojęcia, do jakiej bazy danych jest zapisywana w momencie wywołania metody `$article->save()`. Ktokolwiek na odległym końcu aplikacji mógłby w każdej chwili zmienić bazę danych za pomocą `Article::setDb()`. Pod naszymi rękami. +To w ogóle nic nie zmieniło. Problemem jest stan globalny i jest zupełnie obojętne, w której klasie się ukrywa. W tym przypadku, podobnie jak w poprzednim, przy wywołaniu metody `$article->save()` nie mamy żadnej wskazówki co do tego, do jakiej bazy danych zostanie zapisany. Ktokolwiek na drugim końcu aplikacji mógł w dowolnym momencie za pomocą `Article::setDb()` zmienić bazę danych. Nam pod nosem. -Stan globalny sprawia, że nasza aplikacja jest **ekstremalnie krucha**. +Stan globalny czyni naszą aplikację **niezwykle kruchą**. -Istnieje jednak prosty sposób na poradzenie sobie z tym problemem. Wystarczy kazać API zadeklarować zależności, aby zapewnić odpowiednią funkcjonalność. +Istnieje jednak prosty sposób, aby poradzić sobie z tym problemem. Wystarczy pozwolić API deklarować zależności, co zapewni poprawną funkcjonalność. ```php class Article @@ -174,15 +169,15 @@ Foo::doSomething(); $article->save(); ``` -Takie podejście eliminuje obawy o ukryte i nieoczekiwane zmiany w połączeniach z bazą danych. Teraz mamy pewność, gdzie przechowywany jest artykuł i żadne modyfikacje kodu wewnątrz innej niepowiązanej klasy nie mogą już zmienić sytuacji. Kod nie jest już kruchy, ale stabilny. +Dzięki temu podejściu znika obawa o ukryte i nieoczekiwane zmiany połączenia z bazą danych. Teraz mamy pewność, gdzie artykuł jest zapisywany, a żadne modyfikacje kodu wewnątrz innej, niepowiązanej klasy nie mogą już zmienić sytuacji. Kod nie jest już kruchy, ale stabilny. -Nie pisz kodu, który korzysta z globalnego stanu, wolisz przekazać zależności. A więc dependency injection. +Nie pisz kodu, który wykorzystuje stan globalny, preferuj przekazywanie zależności. Czyli dependency injection. -Singleton .[#toc-singleton] ---------------------------- +Singleton +--------- -Singleton to wzorzec projektowy, który z [definicji |https://en.wikipedia.org/wiki/Singleton_pattern] ze słynnej publikacji Gang of Four ogranicza klasę do pojedynczej instancji i oferuje do niej globalny dostęp. Implementacja tego wzorca zazwyczaj przypomina następujący kod: +Singleton to wzorzec projektowy, który według [definicji|https://en.wikipedia.org/wiki/Singleton_pattern] ze znanej publikacji Gang of Four ogranicza klasę do jednej instancji i oferuje do niej globalny dostęp. Implementacja tego wzorca zwykle przypomina następujący kod: ```php class Singleton @@ -195,38 +190,35 @@ class Singleton return self::$instance; } - // oraz inne metody realizujące funkcje klasy + // i inne metody pełniące funkcje danej klasy } ``` -Niestety, singleton wprowadza do aplikacji stan globalny. A jak pokazaliśmy powyżej, stan globalny jest niepożądany. Dlatego właśnie singleton jest uważany za antypattern. +Niestety, singleton wprowadza do aplikacji stan globalny. A jak pokazaliśmy wyżej, stan globalny jest niepożądany. Dlatego singleton jest uważany za antywzorzec. -Nie używaj singletonów w swoim kodzie i zastąp je innymi mechanizmami. Naprawdę nie potrzebujesz singletonów. Jeśli jednak musisz zagwarantować istnienie pojedynczej instancji klasy dla całej aplikacji, zostaw to [kontenerowi DI |container]. -W ten sposób utwórz singleton aplikacji, czyli usługę. Dzięki temu klasa przestanie zapewniać własną unikalność (tzn. Nie będzie miała metody `getInstance()` i zmiennej statycznej) i będzie wykonywać tylko swoje funkcje. Tym samym przestanie naruszać zasadę pojedynczej odpowiedzialności. +Nie używaj w swoim kodzie singletonów i zastąp je innymi mechanizmami. Singletony naprawdę nie są potrzebne. Jeśli jednak potrzebujesz zagwarantować istnienie jednej instancji klasy dla całej aplikacji, pozostaw to [kontenerowi DI |container]. Stwórz w ten sposób singleton aplikacyjny, czyli usługę. Dzięki temu klasa przestanie zajmować się zapewnieniem swojej własnej unikalności (tj. nie będzie miała metody `getInstance()` i zmiennej statycznej) i będzie pełnić tylko swoje funkcje. W ten sposób przestanie naruszać zasadę pojedynczej odpowiedzialności. -Stan globalny a testy .[#toc-global-state-versus-tests] -------------------------------------------------------- +Stan globalny a testy +--------------------- -Pisząc testy, zakładamy, że każdy test jest izolowaną jednostką i że nie wchodzi do niego żaden zewnętrzny stan. I żaden stan nie opuszcza testów. Kiedy test się kończy, wszelkie stany związane z testem powinny być automatycznie usuwane przez garbage collector. To sprawia, że testy są odizolowane. Dlatego możemy uruchamiać testy w dowolnej kolejności. +Podczas pisania testów zakładamy, że każdy test jest izolowaną jednostką i że nie wchodzi do niego żaden zewnętrzny stan. I żaden stan nie opuszcza testów. Po zakończeniu testu cały powiązany z nim stan powinien zostać automatycznie usunięty przez garbage collector. Dzięki temu testy są izolowane. Dlatego możemy uruchamiać testy w dowolnej kolejności. -Jednakże, jeśli obecne są globalne stany/singletony, wszystkie te miłe założenia ulegają załamaniu. Stan może wejść i wyjść z testu. Nagle okazuje się, że kolejność wykonywania testów może mieć znaczenie. +Jeśli jednak obecne są stany globalne/singletony, wszystkie te przyjemne założenia się rozpadają. Stan może wchodzić do testu i wychodzić z niego. Nagle kolejność testów może mieć znaczenie. -Aby w ogóle testować singletony, programiści często muszą rozluźnić ich właściwości, być może pozwalając na zastąpienie jednej instancji inną. Takie rozwiązania są w najlepszym wypadku hackami, które produkują kod trudny do utrzymania i zrozumienia. Każdy test lub metoda `tearDown()`, która wpływa na jakikolwiek stan globalny, musi cofnąć te zmiany. +Aby w ogóle móc testować singletony, programiści często muszą rozluźnić ich właściwości, na przykład pozwalając na zastąpienie instancji inną. Takie rozwiązania są w najlepszym przypadku hackiem, który tworzy trudny do utrzymania i zrozumienia kod. Każdy test lub metoda `tearDown()`, która wpływa na jakikolwiek stan globalny, musi te zmiany cofnąć. -Globalny stan jest największym bólem głowy w testach jednostkowych! +Stan globalny to największy ból głowy przy testach jednostkowych! -Jak naprawić tę sytuację? Proste. Nie pisz kodu, który używa singletonów, wolisz przekazywać zależności. Czyli dependency injection. +Jak naprawić sytuację? Łatwo. Nie pisz kodu, który wykorzystuje singletony, preferuj przekazywanie zależności. Czyli dependency injection. -Stałe globalne .[#toc-global-constants] ---------------------------------------- +Stałe globalne +-------------- -Stan globalny nie jest ograniczony do używania singletonów i zmiennych statycznych, ale może również dotyczyć stałych globalnych. +Stan globalny nie ogranicza się tylko do używania singletonów i zmiennych statycznych, ale może dotyczyć również stałych globalnych. -Stałe, których wartość nie dostarcza nam żadnych nowych (`M_PI`) lub użytecznych (`PREG_BACKTRACK_LIMIT_ERROR`) informacji są oczywiście OK. -I odwrotnie, stałe, które służą jako sposób na *bezprzewodowe* przekazywanie informacji wewnątrz kodu, są niczym więcej niż ukrytą zależnością. Jak `LOG_FILE` w poniższym przykładzie. -Używanie stałej `FILE_APPEND` jest całkowicie poprawne. +Stałe, których wartość nie wnosi nam żadnej nowej (`M_PI`) lub użytecznej (`PREG_BACKTRACK_LIMIT_ERROR`) informacji, są jednoznacznie w porządku. Natomiast stałe, które służą jako sposób na *bezprzewodowe* przekazanie informacji do wnętrza kodu, są niczym innym jak ukrytą zależnością. Jak na przykład `LOG_FILE` w poniższym przykładzie. Użycie stałej `FILE_APPEND` jest całkowicie poprawne. ```php const LOG_FILE = '...'; @@ -242,7 +234,7 @@ class Foo } ``` -W tym przypadku powinniśmy zadeklarować parametr w konstruktorze klasy `Foo`, aby uczynić go częścią interfejsu API: +W tym przypadku powinniśmy zadeklarować parametr w konstruktorze klasy `Foo`, aby stał się częścią API: ```php class Foo @@ -261,44 +253,42 @@ class Foo } ``` -Teraz możemy przekazać informację o ścieżce do pliku logowania i łatwo ją zmienić w razie potrzeby, co ułatwi testowanie i utrzymanie kodu. +Teraz możemy przekazać informację o ścieżce do pliku logów i łatwo ją zmieniać w zależności od potrzeb, co ułatwia testowanie i konserwację kodu. -Funkcje globalne i metody statyczne .[#toc-global-functions-and-static-methods] -------------------------------------------------------------------------------- +Funkcje globalne i metody statyczne +----------------------------------- -Chcemy podkreślić, że używanie metod statycznych i funkcji globalnych samo w sobie nie jest problematyczne. Wyjaśniliśmy niewłaściwość używania `DB::insert()` i podobnych metod, ale zawsze chodziło o stan globalny przechowywany w zmiennej statycznej. Metoda `DB::insert()` wymaga istnienia zmiennej statycznej, ponieważ przechowuje ona połączenie z bazą danych. Bez tej zmiennej implementacja metody byłaby niemożliwa. +Chcemy podkreślić, że samo używanie metod statycznych i funkcji globalnych nie jest problematyczne. Wyjaśnialiśmy, na czym polega nieodpowiedniość użycia `DB::insert()` i podobnych metod, ale zawsze chodziło tylko o kwestię stanu globalnego, który jest przechowywany w jakiejś zmiennej statycznej. Metoda `DB::insert()` wymaga istnienia zmiennej statycznej, ponieważ w niej jest przechowywane połączenie z bazą danych. Bez tej zmiennej implementacja metody byłaby niemożliwa. -Stosowanie deterministycznych metod i funkcji statycznych, takich jak `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` i wielu innych, jest doskonale zgodne z wstrzykiwaniem zależności. Funkcje te zawsze zwracają te same wyniki z tych samych parametrów wejściowych i dlatego są przewidywalne. Nie używają one żadnego stanu globalnego. +Używanie deterministycznych metod statycznych i funkcji, takich jak `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` i wielu innych, jest w całkowitej zgodzie z dependency injection. Funkcje te zawsze zwracają te same wyniki dla tych samych parametrów wejściowych i są zatem przewidywalne. Nie używają żadnego stanu globalnego. -W PHP istnieją jednak funkcje, które nie są deterministyczne. Należy do nich na przykład funkcja `htmlspecialchars()`. Jej trzeci parametr, `$encoding`, jeśli nie zostanie określony, domyślnie przyjmuje wartość opcji konfiguracyjnej `ini_get('default_charset')`. Dlatego zaleca się zawsze podawać ten parametr, aby uniknąć ewentualnego nieprzewidywalnego zachowania funkcji. Nette konsekwentnie to robi. +Istnieją jednak również funkcje w PHP, które nie są deterministyczne. Należy do nich na przykład funkcja `htmlspecialchars()`. Jej trzeci parametr `$encoding`, jeśli nie jest podany, domyślnie przyjmuje wartość opcji konfiguracyjnej `ini_get('default_charset')`. Dlatego zaleca się zawsze podawać ten parametr, aby zapobiec ewentualnemu nieprzewidywalnemu zachowaniu funkcji. Nette konsekwentnie to robi. -Niektóre funkcje, takie jak `strtolower()`, `strtoupper()`, i tym podobne, miały w niedawnej przeszłości niedeterministyczne zachowanie i zależały od ustawienia `setlocale()`. Powodowało to wiele komplikacji, najczęściej podczas pracy z językiem tureckim. -Dzieje się tak dlatego, że język turecki rozróżnia duże i małe litery `I` z i bez kropki. Tak więc `strtolower('I')` zwracał znak `ı`, a `strtoupper('i')` zwracał znak `İ`, co prowadziło do aplikacji powodujących wiele tajemniczych błędów. -Jednak ten problem został naprawiony w PHP w wersji 8.2 i funkcje nie są już zależne od locale. +Niektóre funkcje, takie jak `strtolower()`, `strtoupper()` i podobne, w niedawnej przeszłości zachowywały się niedeterministycznie i były zależne od ustawienia `setlocale()`. Powodowało to wiele komplikacji, najczęściej przy pracy z językiem tureckim. Ten bowiem rozróżnia małą i dużą literę `I` z kropką i bez kropki. Tak więc `strtolower('I')` zwracało znak `ı`, a `strtoupper('i')` znak `İ`, co prowadziło do tego, że aplikacje zaczęły powodować szereg zagadkowych błędów. Ten problem został jednak usunięty w PHP w wersji 8.2 i funkcje nie są już zależne od locale. -Jest to ładny przykład tego, jak stan globalny nękał tysiące programistów na całym świecie. Rozwiązaniem było zastąpienie go zastrzykiem zależności. +Jest to piękny przykład, jak stan globalny napsuł krwi tysiącom programistów na całym świecie. Rozwiązaniem było zastąpienie go przez dependency injection. -Kiedy można użyć stanu globalnego? .[#toc-when-is-it-possible-to-use-global-state] ----------------------------------------------------------------------------------- +Kiedy można użyć stanu globalnego? +---------------------------------- -Istnieją pewne specyficzne sytuacje, w których możliwe jest użycie stanu globalnego. Na przykład, gdy debugujemy kod i musimy zrzucić wartość zmiennej lub zmierzyć czas trwania określonej części programu. W takich przypadkach, które dotyczą działań tymczasowych, które później zostaną usunięte z kodu, uzasadnione jest użycie dostępnego globalnie dumpera lub stopera. Narzędzia te nie są częścią projektu kodu. +Istnieją pewne specyficzne sytuacje, w których można wykorzystać stan globalny. Na przykład podczas debugowania kodu, gdy potrzebujesz wypisać wartość zmiennej lub zmierzyć czas trwania określonej części programu. W takich przypadkach, które dotyczą tymczasowych działań, które zostaną później usunięte z kodu, uzasadnione jest wykorzystanie globalnie dostępnego dumpera lub stopera. Te narzędzia bowiem nie są częścią projektu kodu. -Innym przykładem są funkcje do pracy z wyrażeniami regularnymi `preg_*`, które wewnętrznie przechowują skompilowane wyrażenia regularne w statycznej pamięci podręcznej w pamięci. Gdy wywołujesz to samo wyrażenie regularne wiele razy w różnych częściach kodu, jest ono kompilowane tylko raz. Cache oszczędza wydajność, a także jest całkowicie niewidoczny dla użytkownika, więc takie użycie można uznać za uzasadnione. +Innym przykładem są funkcje do pracy z wyrażeniami regularnymi `preg_*`, które wewnętrznie przechowują skompilowane wyrażenia regularne w statycznej pamięci podręcznej w pamięci. Kiedy więc wywołujesz to samo wyrażenie regularne wielokrotnie w różnych miejscach kodu, kompiluje się ono tylko raz. Pamięć podręczna oszczędza wydajność, a jednocześnie jest dla użytkownika całkowicie niewidoczna, dlatego takie wykorzystanie można uznać za uzasadnione. -Podsumowanie .[#toc-summary] ----------------------------- +Podsumowanie +------------ -Pokazaliśmy, dlaczego to ma sens +Omówiliśmy, dlaczego warto: -1) Usuń z kodu wszystkie zmienne statyczne -2) Zadeklarować zależności -3) I używać zastrzyku zależności +1) Usunąć wszystkie zmienne statyczne z kodu +2) Deklarować zależności +3) I używać dependency injection -Rozważając projekt kodu, pamiętaj, że każdy `static $foo` reprezentuje problem. Aby twój kod był środowiskiem respektującym DI, konieczne jest całkowite wyeliminowanie stanu globalnego i zastąpienie go zastrzykiem zależności. +Kiedy zastanawiasz się nad projektem kodu, pamiętaj, że każde `static $foo` stanowi problem. Aby Twój kod był środowiskiem szanującym DI, konieczne jest całkowite wyeliminowanie stanu globalnego i zastąpienie go za pomocą dependency injection. -Podczas tego procesu może się okazać, że musisz podzielić klasę, ponieważ ma ona więcej niż jedną odpowiedzialność. Nie przejmuj się tym; dąż do zasady jednej odpowiedzialności. +Podczas tego procesu być może odkryjesz, że trzeba podzielić klasę, ponieważ ma więcej niż jedną odpowiedzialność. Nie bój się tego; dąż do zasady pojedynczej odpowiedzialności. -*Chciałbym podziękować Miško Hevery'emu, którego artykuły takie jak [Flaw: Brittle Global State & Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/] stanowią podstawę tego rozdziału.* +*Chciałbym podziękować Miškovi Hevery'emu, którego artykuły, takie jak [Flaw: Brittle Global State & Singletons |https://web.archive.org/web/20230321084133/http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/], są podstawą tego rozdziału.* diff --git a/dependency-injection/pl/introduction.texy b/dependency-injection/pl/introduction.texy index 8431bfb782..6e0570520d 100644 --- a/dependency-injection/pl/introduction.texy +++ b/dependency-injection/pl/introduction.texy @@ -1,58 +1,58 @@ -Co to jest Dependency Injection? -******************************** +Co to jest Wstrzykiwanie Zależności? +************************************ .[perex] -Ten rozdział zapozna Cię z podstawowymi praktykami programistycznymi, których powinieneś przestrzegać podczas pisania dowolnej aplikacji. Są to podstawy potrzebne do pisania czystego, zrozumiałego i możliwego do utrzymania kodu. +Ten rozdział wprowadzi Cię w podstawowe praktyki programistyczne, których powinieneś przestrzegać podczas pisania wszystkich aplikacji. Są to podstawy niezbędne do pisania czystego, zrozumiałego i łatwego w utrzymaniu kodu. -Jeśli nauczysz się i będziesz przestrzegać tych zasad, Nette będzie Cię wspierać na każdym kroku. Zajmie się rutynowymi zadaniami za Ciebie i zapewni maksymalny komfort, dzięki czemu będziesz mógł skupić się na samej logice. +Jeśli przyswoisz sobie te zasady i będziesz ich przestrzegać, Nette będzie Cię wspierać na każdym kroku. Zajmie się za Ciebie rutynowymi zadaniami i zapewni maksymalną wygodę, abyś mógł skupić się na samej logice. -Zasady, które tu pokażemy, są dość proste. Nie musisz się o nic martwić. +Zasady, które tutaj przedstawimy, są przy tym całkiem proste. Nie musisz się niczego obawiać. -Pamiętasz swój pierwszy program? .[#toc-remember-your-first-program] --------------------------------------------------------------------- +Pamiętasz swój pierwszy program? +-------------------------------- -Nie wiemy, w jakim języku go napisałeś, ale jeśli był to PHP, mógł wyglądać coś takiego: +Nie wiemy, w jakim języku go napisałeś, ale gdyby to było PHP, prawdopodobnie wyglądałby jakoś tak: ```php -function suma(float $a, float $b): float +function soucet(float $a, float $b): float { return $a + $b; } -echo suma(23, 1); // wykazy 24 +echo soucet(23, 1); // wypisze 24 ``` -Kilka banalnych linii kodu, ale tak wiele kluczowych pojęć w nich ukrytych. To, że istnieją zmienne. Że kod jest rozbity na mniejsze jednostki, którymi są na przykład funkcje. Że przekazujemy im argumenty wejściowe, a one zwracają wyniki. Brakuje tylko warunków i pętli. +Kilka trywialnych linii kodu, a jednak kryje się w nich tyle kluczowych koncepcji. Że istnieją zmienne. Że kod dzieli się na mniejsze jednostki, jakimi są na przykład funkcje. Że przekazujemy im argumenty wejściowe, a one zwracają wyniki. Brakuje tam już tylko warunków i pętli. -Fakt, że funkcja przyjmuje dane wejściowe i zwraca wynik, jest całkowicie zrozumiałym pojęciem, które jest również wykorzystywane w innych dziedzinach, takich jak matematyka. +To, że funkcji przekazujemy dane wejściowe, a ona zwraca wynik, jest doskonale zrozumiałym konceptem, stosowanym również w innych dziedzinach, jak na przykład w matematyce. -Funkcja ma swoją sygnaturę, która składa się z jej nazwy, listy parametrów i ich typów, wreszcie typu wartości zwracanej. Jako użytkowników interesuje nas sygnatura, a o wewnętrznej implementacji zwykle nie musimy nic wiedzieć. +Funkcja ma swoją sygnaturę, którą tworzy jej nazwa, przegląd parametrów i ich typów, a na końcu typ zwracanej wartości. Jako użytkowników interesuje nas sygnatura, o wewnętrznej implementacji zazwyczaj nie potrzebujemy nic wiedzieć. -Teraz wyobraź sobie, że sygnatura funkcji wyglądała tak: +Teraz wyobraź sobie, że sygnatura funkcji wyglądałaby tak: ```php -function suma(float $x): float +function soucet(float $x): float ``` -Dodatek z jednym parametrem? To dziwne... A co z tym? +Suma z jednym parametrem? To dziwne… A co na przykład tak? ```php -function suma(): float +function soucet(): float ``` -Teraz to jest naprawdę dziwne, prawda? Jak ta funkcja jest używana? +To już jest naprawdę bardzo dziwne, prawda? Jak się takiej funkcji używa? ```php -echo suma(); // co wypisuje? +echo soucet(); // co to może wypisać? ``` -Patrząc na taki kod, bylibyśmy zdezorientowani. Nie tylko początkujący nie zrozumiałby go, ale nawet doświadczony programista nie zrozumiałby takiego kodu. +Patrząc na taki kod, bylibyśmy zdezorientowani. Nie tylko początkujący by go nie zrozumiał, takiego kodu nie rozumie nawet doświadczony programista. -Zastanawiasz się jak właściwie wyglądałaby taka funkcja w środku? Skąd brałaby sumy? Prawdopodobnie *w jakiś sposób* sama by je uzyskała, być może w taki sposób: +Zastanawiasz się, jak właściwie taka funkcja wyglądałaby w środku? Skąd wzięłaby składniki sumy? Prawdopodobnie *w jakiś sposób* załatwiłaby je sobie sama, na przykład tak: ```php -function suma(): float +function soucet(): float { $a = Input::get('a'); $b = Input::get('b'); @@ -60,71 +60,71 @@ function suma(): float } ``` -Okazuje się, że w ciele funkcji znajdują się ukryte wiązania do innych funkcji (lub metod statycznych), a żeby dowiedzieć się, skąd tak naprawdę pochodzą addytywy, musimy kopać dalej. +W ciele funkcji odkryliśmy ukryte powiązania z innymi globalnymi funkcjami lub metodami statycznymi. Aby dowiedzieć się, skąd naprawdę biorą się składniki sumy, musimy szukać dalej. -Nie tędy droga! .[#toc-not-this-way] ------------------------------------- +Tędy nie! +--------- -Projekt, który właśnie pokazaliśmy, jest esencją wielu negatywnych cech: +Projekt, który właśnie przedstawiliśmy, jest esencją wielu negatywnych cech: -- sygnatura funkcji udawała, że nie potrzebuje sumatorów, co nas dezorientowało -- nie mamy pojęcia, jak sprawić, żeby funkcja obliczała z dwoma innymi liczbami -- musieliśmy zajrzeć do kodu, żeby dowiedzieć się, skąd wzięły się sumy -- znaleźliśmy ukryte zależności -- pełne zrozumienie wymaga zbadania także tych zależności +- sygnatura funkcji udawała, że nie potrzebuje składników sumy, co nas myliło +- w ogóle nie wiemy, jak zmusić funkcję do zsumowania dwóch innych liczb +- musieliśmy zajrzeć do kodu, aby dowiedzieć się, skąd bierze składniki sumy +- odkryliśmy ukryte powiązania +- do pełnego zrozumienia trzeba zbadać również te powiązania -A czy zadaniem funkcji dodawania jest w ogóle pozyskiwanie wejść? Oczywiście, że nie. Jej zadaniem jest tylko dodawanie. +A czy w ogóle zadaniem funkcji sumującej jest pozyskiwanie danych wejściowych? Oczywiście, że nie. Jej odpowiedzialnością jest tylko samo sumowanie. -Nie chcemy spotkać takiego kodu, a już na pewno nie chcemy go pisać. Lekarstwo jest proste: wróć do podstaw i po prostu używaj parametrów: +Z takim kodem nie chcemy się spotykać i zdecydowanie nie chcemy go pisać. Naprawa jest przy tym prosta: wrócić do podstaw i po prostu użyć parametrów: ```php -function suma(float $a, float $b): float +function soucet(float $a, float $b): float { return $a + $b; } ``` -Zasada #1: Niech ci to zostanie przekazane .[#toc-rule-1-let-it-be-passed-to-you] ---------------------------------------------------------------------------------- +Zasada nr 1: niech ci to przekażą +--------------------------------- -Najważniejszą zasadą jest: **wszystkie dane, których potrzebują funkcje lub klasy, muszą być do nich przekazane**. +Najważniejsza zasada brzmi: **wszystkie dane, których funkcje lub klasy potrzebują, muszą być im przekazane**. -Zamiast wymyślać ukryte sposoby, aby mogli sami uzyskać dostęp do danych, po prostu przekaż parametry. Zaoszczędzisz czas, który zostałby poświęcony na wymyślanie ukrytych ścieżek, które z pewnością nie poprawią twojego kodu. +Zamiast wymyślać ukryte sposoby, za pomocą których mogłyby jakoś same do nich dotrzeć, po prostu przekaż parametry. Zaoszczędzisz czas potrzebny na wymyślanie ukrytych ścieżek, które zdecydowanie nie ulepszą twojego kodu. -Jeśli zawsze i wszędzie będziesz przestrzegał tej zasady, jesteś na dobrej drodze do kodu bez ukrytych zależności. Do kodu, który jest zrozumiały nie tylko dla autora, ale także dla każdego, kto go potem przeczyta. Gdzie wszystko jest zrozumiałe z sygnatur funkcji i klas, i nie ma potrzeby szukania ukrytych sekretów w implementacji. +Jeśli będziesz przestrzegać tej zasady zawsze i wszędzie, jesteś na drodze do kodu bez ukrytych powiązań. Do kodu, który jest zrozumiały nie tylko dla autora, ale i dla każdego, kto będzie go po nim czytał. Gdzie wszystko jest zrozumiałe z sygnatur funkcji i klas i nie trzeba szukać ukrytych tajemnic w implementacji. -Ta technika nazywa się fachowo **dependency injection**. A te dane nazywane są **zależnościami**. To tylko zwykłe przekazywanie parametrów, nic więcej. +Tej technice fachowo mówi się **wstrzykiwanie zależności** (dependency injection). A tym danym mówi się **zależności.** Przy czym jest to zwykłe przekazywanie parametrów, nic więcej. .[note] -Proszę nie mylić wtrysku zależności, który jest wzorcem projektowym, z "kontenerem wtrysku zależności", który jest narzędziem, czymś diametralnie różnym. Kontenerami zajmiemy się później. +Proszę nie mylić wstrzykiwania zależności, które jest wzorcem projektowym, z „kontenerem wstrzykiwania zależności” (dependency injection container), który jest z kolei narzędziem, czyli czymś diametralnie innym. Kontenerami DI zajmiemy się później. -Od funkcji do klas .[#toc-from-functions-to-classes] ----------------------------------------------------- +Od funkcji do klas +------------------ -A jak klasy są powiązane? Klasa jest bardziej złożoną jednostką niż prosta funkcja, ale zasada #1 ma tutaj również całkowite zastosowanie. Jest po prostu [więcej sposobów na przekazywanie argumentów |passing-dependencies]. Na przykład, dość podobny do przypadku funkcji: +A jak to się ma do klas? Klasa jest bardziej złożoną całością niż prosta funkcja, niemniej jednak zasada nr 1 obowiązuje bez wyjątku również tutaj. Istnieje tylko [więcej możliwości przekazania argumentów|passing-dependencies]. Na przykład całkiem podobnie jak w przypadku funkcji: ```php class Matematika { - public function suma(float $a, float $b): float + public function soucet(float $a, float $b): float { return $a + $b; } } $math = new Matematika; -echo $math->suma(23, 1); // 24 +echo $math->soucet(23, 1); // 24 ``` -Lub poprzez inne metody, lub bezpośrednio poprzez konstruktor: +Lub za pomocą innych metod, czy bezpośrednio konstruktora: ```php -class Suma +class Soucet { public function __construct( private float $a, @@ -139,19 +139,19 @@ class Suma } -$suma = new Suma(23, 1); -echo $suma->spocti(); // 24 +$soucet = new Soucet(23, 1); +echo $soucet->spocti(); // 24 ``` -Oba przykłady są całkowicie zgodne z zastrzykiem zależności. +Oba przykłady są całkowicie zgodne z wstrzykiwaniem zależności. -Przykłady z życia wzięte .[#toc-real-life-examples] ---------------------------------------------------- +Prawdziwe przykłady +------------------- -W prawdziwym świecie nie będziesz pisał klas do dodawania liczb. Przejdźmy więc do praktycznych przykładów. +W prawdziwym świecie nie będziesz pisać klas do sumowania liczb. Przejdźmy do przykładów z praktyki. -Miejmy klasę `Article` reprezentującą wpis na blogu: +Mamy klasę `Article` reprezentującą artykuł na blogu: ```php class Article @@ -162,12 +162,12 @@ class Article public function save(): void { - // zapisać artykuł do bazy danych + // zapiszemy artykuł do bazy danych } } ``` -, a sposób użycia będzie następujący: +a użycie będzie następujące: ```php $article = new Article; @@ -176,9 +176,9 @@ $article->content = 'Every year millions of people in ...'; $article->save(); ``` -Metoda `save()` zapisze artykuł do tabeli w bazie danych. Wdrożenie jej przy użyciu [Nette Database |database:] to bułka z masłem, gdyby nie jeden problem: skąd `Article` ma wziąć połączenie z bazą danych, czyli obiekt klasy `Nette\Database\Connection`? +Metoda `save()` zapisze artykuł do tabeli w bazie danych. Zaimplementowanie jej za pomocą [Nette Database |database:] byłoby dziecinnie proste, gdyby nie jeden haczyk: skąd `Article` ma wziąć połączenie z bazą danych, tj. obiekt klasy `Nette\Database\Connection`? -Wydaje się, że mamy wiele możliwości. Może wziąć je gdzieś ze zmiennej statycznej. Albo dziedziczyć po klasie, która zapewnia połączenie z bazą danych. Albo skorzystać z [singletonu |global-state#Singleton]. Albo wykorzystać tzw. fasady, które są stosowane w Laravelu: +Wydaje się, że mamy wiele możliwości. Może je wziąć skądś ze zmiennej statycznej. Lub dziedziczyć po klasie, która zapewni połączenie z bazą danych. Lub wykorzystać tzw. [singleton |global-state#Singleton]. Lub tzw. fasady (facades), które są używane w Laravelu: ```php use Illuminate\Support\Facades\DB; @@ -199,17 +199,17 @@ class Article } ``` -Świetnie, rozwiązaliśmy problem. +Świetnie, problem rozwiązany. -A może jednak? +Czy na pewno? -Przypomnijmy sobie [regułę #1: Let It Be Passed to You |#rule #1: Let It Be Passed to You]: wszystkie zależności, których potrzebuje klasa, muszą być do niej przekazane. Bo jeśli złamiemy tę regułę, to wkroczyliśmy na drogę do brudnego kodu pełnego ukrytych zależności, niezrozumiałości, a efektem będzie aplikacja, której utrzymanie i rozwój będą bolesne. +Przypomnijmy [#zasadę nr 1: niech ci to przekażą |#Zasada nr 1: niech ci to przekażą]: wszystkie zależności, których klasa potrzebuje, muszą być jej przekazane. Ponieważ jeśli naruszymy tę zasadę, wkroczyliśmy na drogę do brudnego kodu pełnego ukrytych powiązań, niezrozumiałości, a wynikiem będzie aplikacja, której utrzymanie i rozwój będą bolesne. -Użytkownik klasy `Article` nie ma pojęcia, gdzie metoda `save()` przechowuje artykuł. W tabeli bazy danych? W której, produkcyjnej czy testowej? I jak można ją zmienić? +Użytkownik klasy `Article` nie ma pojęcia, gdzie metoda `save()` zapisuje artykuł. Do tabeli w bazie danych? Do której, produkcyjnej czy testowej? I jak to można zmienić? -Użytkownik musi przyjrzeć się, jak zaimplementowana jest metoda `save()`, i znajduje zastosowanie metody `DB::insert()`. Musi więc szukać dalej, aby dowiedzieć się, jak ta metoda uzyskuje połączenie z bazą danych. A ukryte zależności mogą tworzyć dość długi łańcuch. +Użytkownik musi zajrzeć, jak zaimplementowana jest metoda `save()`, i znajduje użycie metody `DB::insert()`. Musi więc szukać dalej, jak ta metoda pozyskuje połączenie z bazą danych. A ukryte powiązania mogą tworzyć całkiem długi łańcuch. -W czystym i dobrze zaprojektowanym kodzie nigdy nie ma ukrytych zależności, fasad Laravel czy zmiennych statycznych. W czystym i dobrze zaprojektowanym kodzie przekazywane są argumenty: +W czystym i dobrze zaprojektowanym kodzie nigdy nie występują ukryte powiązania, fasady Laravela czy zmienne statyczne. W czystym i dobrze zaprojektowanym kodzie przekazuje się argumenty: ```php class Article @@ -224,7 +224,7 @@ class Article } ``` -Jeszcze bardziej praktyczne podejście, jak zobaczymy później, będzie poprzez konstruktor: +Jeszcze bardziej praktyczne, jak zobaczymy dalej, będzie to przez konstruktor: ```php class Article @@ -245,11 +245,11 @@ class Article ``` .[note] -Jeśli jesteś doświadczonym programistą, możesz pomyśleć, że `Article` nie powinien w ogóle mieć metody `save()`; powinien reprezentować składnik czysto danych, a oddzielne repozytorium powinno zająć się zapisywaniem. To ma sens. Ale to zabrałoby nas daleko poza zakres tego tematu, który jest zastrzykiem zależności, a także wysiłek, aby zapewnić proste przykłady. +Jeśli jesteś doświadczonym programistą, być może myślisz, że `Article` w ogóle nie powinien mieć metody `save()`, powinien reprezentować czysto komponent danych, a o zapisywaniu powinien dbać oddzielny repozytorium. To ma sens. Ale tym wyszlibyśmy daleko poza zakres tematu, którym jest wstrzykiwanie zależności, i starania o podawanie prostych przykładów. -Jeśli piszesz klasę, która do swojego działania wymaga np. bazy danych, to nie wymyślaj skąd ją wziąć, tylko zleć jej przekazanie. Albo jako parametr konstruktora, albo innej metody. Przyznaj się do zależności. Przyznaj się do nich w API swojej klasy. Otrzymasz zrozumiały i przewidywalny kod. +Jeśli będziesz pisać klasę wymagającą do swojego działania np. bazy danych, nie wymyślaj, skąd ją zdobyć, ale pozwól sobie ją przekazać. Na przykład jako parametr konstruktora lub innej metody. Przyznaj się do zależności. Przyznaj się do nich w API swojej klasy. Zyskasz zrozumiały i przewidywalny kod. -A co z tą klasą, która loguje komunikaty o błędach: +A co na przykład z tą klasą, która loguje komunikaty błędów: ```php class Logger @@ -262,23 +262,23 @@ class Logger } ``` -Jak myślicie, czy zastosowaliśmy się do [zasady nr 1: Niech ci to zostanie przekazane |#rule #1: Let It Be Passed to You]? +Co myślisz, czy przestrzegaliśmy [#zasady nr 1: niech ci to przekażą |#Zasada nr 1: niech ci to przekażą]? -Nie. +Nie przestrzegaliśmy. -Kluczowa informacja, czyli katalog z plikiem dziennika, jest *pozyskiwana* przez samą klasę ze stałej. +Kluczową informację, czyli katalog z plikiem logu, klasa *pozyskuje sama* ze stałej. Spójrz na przykład użycia: ```php $logger = new Logger; -$logger->log('The temperature is 23 °C'); -$logger->log('The temperature is 10 °C'); +$logger->log('Temperatura wynosi 23 °C'); +$logger->log('Temperatura wynosi 10 °C'); ``` -Czy nie znając implementacji, mógłbyś odpowiedzieć na pytanie, gdzie zapisywane są wiadomości? Czy domyśliłbyś się, że istnienie stałej `LOG_DIR` jest niezbędne do jej funkcjonowania? A czy mógłbyś stworzyć drugą instancję, która zapisywałaby w innym miejscu? Z pewnością nie. +Bez znajomości implementacji, czy potrafiłbyś odpowiedzieć na pytanie, gdzie zapisywane są komunikaty? Czy przyszłoby ci do głowy, że do działania potrzebna jest stała `LOG_DIR`? I czy potrafiłbyś utworzyć drugą instancję, która będzie zapisywać gdzie indziej? Na pewno nie. -Naprawmy więc klasę: +Poprawmy klasę: ```php class Logger @@ -298,21 +298,21 @@ class Logger Klasa jest teraz znacznie bardziej zrozumiała, konfigurowalna, a zatem bardziej użyteczna. ```php -$logger = new Logger('/path/to/log.txt'); -$logger->log('The temperature is 15 °C'); +$logger = new Logger('/sciezka/do/logu.txt'); +$logger->log('Temperatura wynosi 15 °C'); ``` -Ale nie obchodzi mnie to! .[#toc-but-i-don-t-care] --------------------------------------------------- +Ale to mnie nie obchodzi! +------------------------- -*"Kiedy tworzę obiekt Article i wywołuję save(), nie chcę mieć do czynienia z bazą danych; chcę tylko, aby został zapisany w tej, którą ustawiłem w konfiguracji."* +*„Kiedy tworzę obiekt Article i wywołuję save(), nie chcę zajmować się bazą danych, po prostu chcę, żeby zapisał się do tej, którą mam ustawioną w konfiguracji.”* -*"Kiedy używam Loggera, chcę tylko, aby wiadomość została zapisana i nie chcę zajmować się tym, gdzie. Pozwól, aby użyto ustawień globalnych."* +*„Kiedy używam Loggera, po prostu chcę, żeby komunikat został zapisany, i nie chcę zajmować się tym, gdzie. Niech użyje globalnych ustawień.”* -To są ważne punkty. +To są słuszne uwagi. -Jako przykład spójrzmy na klasę, która wysyła biuletyny i rejestruje, jak to się stało: +Jako przykład pokażemy klasę rozsyłającą newslettery, która zaloguje, jak poszło: ```php class NewsletterDistributor @@ -322,27 +322,27 @@ class NewsletterDistributor $logger = new Logger(/* ... */); try { $this->sendEmails(); - $logger->log('Emails have been sent out'); + $logger->log('E-maile zostały rozesłane'); } catch (Exception $e) { - $logger->log('An error occurred during the sending'); + $logger->log('Wystąpił błąd podczas rozsyłania'); throw $e; } } } ``` -Ulepszony `Logger`, który nie używa już stałej `LOG_DIR`, wymaga określenia ścieżki pliku w konstruktorze. Jak to rozwiązać? Klasa `NewsletterDistributor` nie dba o to, gdzie zapisywane są wiadomości; chce je po prostu zapisać. +Ulepszony `Logger`, który już nie używa stałej `LOG_DIR`, wymaga w konstruktorze podania ścieżki do pliku. Jak to rozwiązać? Klasę `NewsletterDistributor` w ogóle nie interesuje, gdzie zapisywane są komunikaty, chce je tylko zapisać. -Rozwiązaniem jest ponownie [zasada #1: Let It Be Passed to You |#rule #1: Let It Be Passed to You]: przekaż wszystkie dane, których potrzebuje klasa. +Rozwiązaniem jest ponownie [##zasada nr 1: niech ci to przekażą]: wszystkie dane, których klasa potrzebuje, przekazujemy jej. -Czy oznacza to więc, że przekazujemy ścieżkę do dziennika poprzez konstruktor, którego następnie używamy podczas tworzenia obiektu `Logger`? +Czy to oznacza, że przez konstruktor przekażemy ścieżkę do logu, którą następnie użyjemy przy tworzeniu obiektu `Logger`? ```php class NewsletterDistributor { public function __construct( - private string $file, // ⛔ NIE W TEN SPOSÓB! + private string $file, // ⛔ TAK NIE! ) { } @@ -351,7 +351,7 @@ class NewsletterDistributor $logger = new Logger($this->file); ``` -Nie, nie w ten sposób! Ścieżka nie należy do danych, których potrzebuje klasa `NewsletterDistributor`; w rzeczywistości potrzebuje jej `Logger`. Czy widzisz różnicę? Klasa `NewsletterDistributor` potrzebuje samego loggera. Więc to jest to, co przekażemy: +Tak nie! Ścieżka bowiem **nie należy** do danych, których potrzebuje klasa `NewsletterDistributor`; te bowiem potrzebuje `Logger`. Czy dostrzegasz różnicę? Klasa `NewsletterDistributor` potrzebuje loggera jako takiego. Więc to jego sobie przekażemy: ```php class NewsletterDistributor @@ -365,35 +365,33 @@ class NewsletterDistributor { try { $this->sendEmails(); - $this->logger->log('Emails have been sent out'); + $this->logger->log('E-maile zostały rozesłane'); } catch (Exception $e) { - $this->logger->log('An error occurred during the sending'); + $this->logger->log('Wystąpił błąd podczas rozsyłania'); throw $e; } } } ``` -Teraz z podpisów klasy `NewsletterDistributor` jasno wynika, że logowanie jest również częścią jej funkcjonalności. A zadanie zamiany loggera na inny, być może w celu przetestowania, jest zupełnie trywialne. -Co więcej, jeśli zmieni się konstruktor klasy `Logger`, nie będzie to miało wpływu na naszą klasę. +Teraz z sygnatur klasy `NewsletterDistributor` jest jasne, że częścią jej funkcjonalności jest również logowanie. A zadanie wymiany loggera na inny, na przykład w celu testowania, jest całkowicie trywialne. Co więcej, jeśli konstruktor klasy `Logger` się zmieni, nie będzie to miało żadnego wpływu na naszą klasę. -Zasada #2: Bierz co twoje .[#toc-rule-2-take-what-s-yours] ----------------------------------------------------------- +Zasada nr 2: bierz, co twoje +---------------------------- -Nie daj się zwieść i nie pozwól sobie na przechodzenie przez zależności swoich zależności. Przekazuj tylko swoje własne zależności. +Nie daj się zwieść i nie pozwól sobie przekazywać zależności swoich zależności. Pozwól sobie przekazywać tylko swoje zależności. -Dzięki temu kod korzystający z innych obiektów będzie całkowicie niezależny od zmian w ich konstruktorach. Jego API będzie bardziej zgodne z prawdą. A przede wszystkim trywialne będzie zastąpienie tych zależności innymi. +Dzięki temu kod wykorzystujący inne obiekty będzie całkowicie niezależny od zmian ich konstruktorów. Jego API będzie bardziej prawdziwe. A przede wszystkim będzie trywialne wymienić te zależności na inne. -Nowy członek rodziny .[#toc-new-family-member] ----------------------------------------------- +Nowy członek rodziny +-------------------- -Zespół programistów postanowił stworzyć drugi logger, który zapisuje do bazy danych. Więc tworzymy klasę `DatabaseLogger`. Mamy więc dwie klasy, `Logger` i `DatabaseLogger`, jedna zapisuje do pliku, druga do bazy danych ... czy to nazewnictwo nie wydaje Ci się dziwne? -Czy nie lepiej byłoby zmienić nazwę `Logger` na `FileLogger`? Zdecydowanie tak. +W zespole deweloperskim podjęto decyzję o stworzeniu drugiego loggera, który zapisuje do bazy danych. Stworzymy więc klasę `DatabaseLogger`. Mamy więc dwie klasy, `Logger` i `DatabaseLogger`, jedna zapisuje do pliku, druga do bazy danych… czy nie wydaje ci się, że w tej nazwie jest coś dziwnego? Czy nie byłoby lepiej zmienić nazwę `Logger` na `FileLogger`? Na pewno tak. -Ale zróbmy to sprytnie. Tworzymy interfejs pod oryginalną nazwą: +Ale zrobimy to sprytnie. Pod pierwotną nazwą stworzymy interfejs: ```php interface Logger @@ -402,7 +400,7 @@ interface Logger } ``` -...które oba rejestratory zaimplementują: +… który oba loggery będą implementować: ```php class FileLogger implements Logger @@ -412,17 +410,17 @@ class DatabaseLogger implements Logger // ... ``` -I z tego powodu nie będzie trzeba nic zmieniać w pozostałej części kodu, w której używany jest logger. Na przykład konstruktor klasy `NewsletterDistributor` nadal będzie zadowalał się wymaganiem `Logger` jako parametru. I to od nas będzie zależało, którą instancję przekażemy. +A dzięki temu nie będzie trzeba niczego zmieniać w reszcie kodu, gdzie używany jest logger. Na przykład konstruktor klasy `NewsletterDistributor` nadal będzie zadowolony z tego, że jako parametr wymaga `Logger`. A od nas będzie zależeć, którą instancję mu przekażemy. -**To właśnie dlatego nigdy nie dodajemy do nazw interfejsów przyrostka `Interface` ani przedrostka `I`** Inaczej nie dałoby się tak ładnie rozwinąć kodu. +**Dlatego nigdy nie dodajemy do nazw interfejsów przyrostka `Interface` ani przedrostka `I`.** W przeciwnym razie nie byłoby możliwe tak ładnie rozwijać kodu. -Houston, mamy problem .[#toc-houston-we-have-a-problem] -------------------------------------------------------- +Houston, mamy problem +--------------------- -O ile możemy sobie poradzić z pojedynczą instancją loggera, czy to plikowego, czy bazodanowego, w całej aplikacji i po prostu przekazać ją wszędzie tam, gdzie coś ma być rejestrowane, to w przypadku klasy `Article` jest zupełnie inaczej. Jej instancje tworzymy w miarę potrzeb, nawet wielokrotnie. Jak poradzić sobie z zależnością od bazy danych w jej konstruktorze? +Podczas gdy w całej aplikacji możemy sobie poradzić z jedną instancją loggera, czy to plikowego, czy bazodanowego, i po prostu przekazujemy go wszędzie tam, gdzie coś jest logowane, zupełnie inaczej jest w przypadku klasy `Article`. Jej instancje bowiem tworzymy w miarę potrzeb, nawet wielokrotnie. Jak poradzić sobie z powiązaniem z bazą danych w jej konstruktorze? -Przykładem może być kontroler, który po przesłaniu formularza powinien zapisać artykuł do bazy danych: +Jako przykład może posłużyć kontroler, który po wysłaniu formularza ma zapisać artykuł do bazy danych: ```php class EditController extends Controller @@ -437,30 +435,30 @@ class EditController extends Controller } ``` -Możliwe rozwiązanie jest oczywiste: przekazać obiekt bazy danych do konstruktora `EditController` i użyć `$article = new Article($this->db)`. +Możliwe rozwiązanie nasuwa się samo: przekażemy sobie obiekt bazy danych konstruktorem do `EditController` i użyjemy `$article = new Article($this->db)`. -Podobnie jak w poprzednim przypadku z `Logger` i ścieżką do pliku, nie jest to właściwe podejście. Baza danych nie jest zależna od `EditController`, ale od `Article`. Przekazanie bazy danych jest sprzeczne z [zasadą #2: bierz to, co twoje |#rule #2: take what's yours]. Jeśli konstruktor klasy `Article` ulegnie zmianie (zostanie dodany nowy parametr), będziesz musiał zmodyfikować kod wszędzie tam, gdzie tworzone są instancje. Ufff. +Tak jak w poprzednim przypadku z `Logger` i ścieżką do pliku, to nie jest właściwe postępowanie. Baza danych nie jest zależnością `EditController`, ale `Article`. Przekazywanie sobie bazy danych jest więc sprzeczne z [#zasadą nr 2: bierz, co twoje]. Kiedy zmieni się konstruktor klasy `Article` (pojawi się nowy parametr), konieczne będzie również zmodyfikowanie kodu we wszystkich miejscach, gdzie tworzone są instancje. Ufff. Houston, co proponujesz? -Zasada #3: Niech fabryka się tym zajmie .[#toc-rule-3-let-the-factory-handle-it] --------------------------------------------------------------------------------- +Zasada nr 3: zostaw to fabryce +------------------------------ -Eliminując ukryte zależności i przekazując wszystkie zależności jako argumenty, zyskaliśmy bardziej konfigurowalne i elastyczne klasy. I dlatego potrzebujemy czegoś innego, co stworzy i skonfiguruje dla nas te bardziej elastyczne klasy. Nazwiemy to fabrykami. +Dzięki temu, że zlikwidowaliśmy ukryte powiązania i wszystkie zależności przekazujemy jako argumenty, zyskaliśmy bardziej konfigurowalne i elastyczne klasy. A zatem potrzebujemy jeszcze czegoś innego, co nam te bardziej elastyczne klasy utworzy i skonfiguruje. Będziemy to nazywać fabrykami. -Zasadą jest: jeśli klasa ma zależności, pozostaw tworzenie ich instancji fabryce. +Zasada brzmi: jeśli klasa ma zależności, zostaw tworzenie ich instancji fabryce. -Fabryki są mądrzejszym zamiennikiem dla operatora `new` w świecie zastrzyku zależności. +Fabryki są sprytniejszym zamiennikiem operatora `new` w świecie wstrzykiwania zależności. .[note] -Proszę nie mylić z wzorcem projektowym *factory method*, który opisuje specyficzny sposób używania fabryk i nie jest związany z tym tematem. +Proszę nie mylić z wzorcem projektowym *factory method*, który opisuje specyficzny sposób wykorzystania fabryk i nie ma związku z tym tematem. -Fabryka .[#toc-factory] ------------------------ +Fabryka +------- -Fabryka to metoda lub klasa, która tworzy i konfiguruje obiekty. Klasę produkującą `Article` nazwiemy jako `ArticleFactory`, a mogłaby ona wyglądać tak: +Fabryka to metoda lub klasa, która produkuje i konfiguruje obiekty. Klasę produkującą `Article` nazwiemy `ArticleFactory` i mogłaby wyglądać na przykład tak: ```php class ArticleFactory @@ -477,7 +475,7 @@ class ArticleFactory } ``` -Jego wykorzystanie w sterowniku będzie wyglądało następująco: +Jej użycie w kontrolerze będzie następujące: ```php class EditController extends Controller @@ -489,7 +487,7 @@ class EditController extends Controller public function formSubmitted($data) { - // pozwól fabryce stworzyć obiekt + // pozwolimy fabryce utworzyć obiekt $article = $this->articleFactory->create(); $article->title = $data->title; $article->content = $data->content; @@ -498,11 +496,11 @@ class EditController extends Controller } ``` -W tym momencie, jeśli zmieni się podpis konstruktora klasy `Article`, jedyną częścią kodu, która musi zareagować, jest sam `ArticleFactory`. Wszystkie inne kody pracujące z obiektami `Article`, takie jak `EditController`, nie zostaną dotknięte. +Gdy w tym momencie zmieni się sygnatura konstruktora klasy `Article`, jedyną częścią kodu, która musi na to zareagować, jest sama fabryka `ArticleFactory`. Całego pozostałego kodu, który pracuje z obiektami `Article`, jak na przykład `EditController`, to w żaden sposób nie dotknie. -Możesz się zastanawiać, czy faktycznie poprawiliśmy sytuację. Ilość kodu wzrosła, a wszystko zaczyna wyglądać podejrzanie skomplikowanie. +Być może teraz pukasz się w czoło, czy w ogóle sobie pomogliśmy. Ilość kodu wzrosła i całość zaczyna wyglądać podejrzanie skomplikowanie. -Nie martw się, wkrótce dotrzemy do kontenera Nette DI. A ma on w rękawie kilka sztuczek, które znacznie ułatwią budowanie aplikacji wykorzystujących wstrzykiwanie zależności. Na przykład zamiast klasy `ArticleFactory` wystarczy [napisać prosty interfejs |factory]: +Nie martw się, za chwilę dojdziemy do kontenera Nette DI. A ten ma wiele asów w rękawie, którymi budowanie aplikacji wykorzystujących wstrzykiwanie zależności niezmiernie uprości. Na przykład zamiast klasy `ArticleFactory` wystarczy [napisać tylko interfejs |factory]: ```php interface ArticleFactory @@ -511,18 +509,18 @@ interface ArticleFactory } ``` -Ale wyprzedzamy się; prosimy o cierpliwość :-) +Ale wyprzedzamy fakty, jeszcze wytrzymaj :-) -Podsumowanie .[#toc-summary] ----------------------------- +Podsumowanie +------------ -Na początku tego rozdziału obiecaliśmy, że pokażemy Ci proces projektowania czystego kodu. Wystarczy, że klasy: +Na początku tego rozdziału obiecywaliśmy, że pokażemy sposób projektowania czystego kodu. Wystarczy klasom -- [przekazywały zależności, których potrzebują |#Rule #1: Let It Be Passed to You] -- [odwrotnie, nie przekazywały tego, czego bezpośrednio nie potrzebują |#Rule #2: Take What's Yours] -- [oraz że obiekty z zależnościami najlepiej tworzyć w fabrykach |#Rule #3: Let the Factory Handle it] +1) [przekazywać zależności, których potrzebują |#Zasada nr 1: niech ci to przekażą] +2) [i odwrotnie, nie przekazywać tego, czego bezpośrednio nie potrzebują |#Zasada nr 2: bierz co twoje] +3) [oraz że obiekty z zależnościami najlepiej tworzyć w fabrykach |#Zasada nr 3: zostaw to fabryce] -Na pierwszy rzut oka te trzy zasady mogą nie wydawać się mieć daleko idących konsekwencji, ale prowadzą do radykalnie innego spojrzenia na projektowanie kodu. Czy to się opłaca? Deweloperzy, którzy porzucili stare nawyki i zaczęli konsekwentnie używać dependency injection, uważają ten krok za kluczowy moment w swoim życiu zawodowym. Otworzył on przed nimi świat przejrzystych i możliwych do utrzymania aplikacji. +Na pierwszy rzut oka może się tak nie wydawać, ale te trzy zasady mają dalekosiężne konsekwencje. Prowadzą do radykalnie innego spojrzenia na projektowanie kodu. Czy warto? Programiści, którzy porzucili stare nawyki i zaczęli konsekwentnie używać wstrzykiwania zależności, uważają ten krok za kluczowy moment w życiu zawodowym. Otworzył się przed nimi świat przejrzystych i łatwych w utrzymaniu aplikacji. -Ale co jeśli kod nie korzysta konsekwentnie z dependency injection? Co jeśli opiera się na metodach statycznych lub singletonach? Czy to powoduje jakieś problemy? [Tak, powoduje, i to bardzo podstawowe |global-state]. +A co jeśli kod konsekwentnie nie używa wstrzykiwania zależności? Co jeśli jest zbudowany na metodach statycznych lub singletonach? Czy przynosi to jakieś problemy? [Przynosi i to bardzo zasadnicze |global-state]. diff --git a/dependency-injection/pl/nette-container.texy b/dependency-injection/pl/nette-container.texy index 2a50904bdd..4ddf9b1d98 100644 --- a/dependency-injection/pl/nette-container.texy +++ b/dependency-injection/pl/nette-container.texy @@ -1,10 +1,10 @@ -Pojemnik Nette DI +Kontener Nette DI ***************** .[perex] -Nette DI to jedna z najciekawszych bibliotek Nette. Może generować i automatycznie aktualizować skompilowane kontenery DI, które są niezwykle szybkie i niesamowicie łatwe do skonfigurowania. +Nette DI jest jedną z najciekawszych bibliotek Nette. Potrafi generować i automatycznie aktualizować skompilowane kontenery DI, które są ekstremalnie szybkie i niezwykle łatwe w konfiguracji. -Forma usług, które mają być tworzone przez kontener DI, jest zwykle definiowana za pomocą plików konfiguracyjnych w [formacie NEON |neon:format]. Kontener, który ręcznie stworzyliśmy w [poprzednim rozdziale |container], zostałby zapisany w następujący sposób: +Postać usług, które ma tworzyć kontener DI, definiujemy zazwyczaj za pomocą plików konfiguracyjnych w [formacie NEON|neon:format]. Kontener, który ręcznie utworzyliśmy w [poprzednim rozdziale|container], zapisałby się tak: ```neon parameters: @@ -19,16 +19,15 @@ services: - UserController ``` -Zapis jest naprawdę krótki. +Zapis jest naprawdę zwięzły. -Wszystkie zależności zadeklarowane w konstruktorach klas `ArticleFactory` i `UserController` są wykrywane i przekazywane przez samo Nette DI dzięki tzw. [autowiringowi |autowiring], więc nie trzeba niczego określać w pliku konfiguracyjnym. -Więc nawet jeśli parametry się zmienią, nie musisz zmieniać niczego w konfiguracji. Nette automatycznie zregeneruje kontener. Tam możesz skupić się wyłącznie na tworzeniu aplikacji. +Wszystkie zależności zadeklarowane w konstruktorach klas `ArticleFactory` i `UserController` Nette DI samo wykryje i przekaże dzięki tzw. [autowiringu|autowiring], dlatego w pliku konfiguracyjnym nie trzeba niczego podawać. Więc nawet jeśli dojdzie do zmiany parametrów, nie musisz niczego zmieniać w konfiguracji. Kontener Nette automatycznie się przgeneruje. Ty możesz skupić się wyłącznie na rozwoju aplikacji. -Jeśli chcemy przekazać zależności za pomocą setterów, używamy do tego sekcji [setup |services#Setup]. +Jeśli chcemy przekazywać zależności za pomocą setterów, użyjemy do tego sekcji [setup |services#Setup]. -Nette DI wygeneruje bezpośrednio kod PHP dla kontenera. Wynikiem jest więc plik `.php`, który możesz otworzyć i przestudiować. Dzięki temu możesz zobaczyć dokładnie jak działa kontener. Możesz także debugować go w IDE i przechodzić przez niego krok po kroku. I co najważniejsze: wygenerowany PHP jest niezwykle szybki. +Nette DI generuje bezpośrednio kod PHP kontenera. Wynikiem jest więc plik `.php`, który możesz otworzyć i studiować. Dzięki temu dokładnie widzisz, jak działa kontener. Możesz go również debugować w IDE i krokowo śledzić. A co najważniejsze: wygenerowany PHP jest ekstremalnie szybki. -Nette DI może również wygenerować kod [fabryczny |factory] w oparciu o dostarczony interfejs. Dlatego zamiast klasy `ArticleFactory` musimy stworzyć w aplikacji jedynie interfejs: +Nette DI potrafi również generować kod [fabryk|factory] na podstawie dostarczonego interfejsu. Dlatego zamiast klasy `ArticleFactory` wystarczy nam stworzyć w aplikacji tylko interfejs: ```php interface ArticleFactory @@ -37,13 +36,13 @@ interface ArticleFactory } ``` -Pełny przykład można znaleźć [na GitHubie |https://github.com/nette-examples/di-example-doc]. +Cały przykład znajdziesz [na GitHubie|https://github.com/nette-examples/di-example-doc]. -Użytkowanie samodzielne .[#toc-standalone-use] ----------------------------------------------- +Samodzielne użycie +------------------ -Wdrożenie biblioteki Nette DI do aplikacji jest bardzo proste. Najpierw instalujemy go za pomocą Composera (bo ściąganie plików zip jest takie przestarzałe): +Wdrożenie biblioteki Nette DI do aplikacji jest bardzo łatwe. Najpierw zainstalujemy ją Composerem (ponieważ pobieranie zipów jest taaak przestarzałe): ```shell composer require nette/di @@ -59,24 +58,23 @@ $class = $loader->load(function ($compiler) { $container = new $class; ``` -Kontener jest generowany tylko raz, jego kod jest zapisywany do cache (katalog `__DIR__ . '/temp'`) i przy kolejnych żądaniach jest tylko stamtąd odczytywany. +Kontener generuje się tylko raz, jego kod zapisuje się do cache (katalog `__DIR__ . '/temp'`) i przy kolejnych żądaniach jest już tylko stamtąd odczytywany. -Do tworzenia i pobierania serwisów służą metody `getService()` lub `getByType()` W ten sposób tworzymy obiekt `UserController`: +Do tworzenia i pobierania usług służą metody `getService()` lub `getByType()`. W ten sposób utworzymy obiekt `UserController`: ```php -$database = $container->getByType(UserController::class); -$database->query('...'); +$controller = $container->getByType(UserController::class); +$controller->someMethod(); ``` -Podczas rozwoju warto włączyć tryb autoodświeżania, w którym kontener jest automatycznie regenerowany w przypadku zmiany jakiejkolwiek klasy lub pliku konfiguracyjnego. Wystarczy podać `true` jako drugi argument w konstruktorze `ContainerLoader`. +Podczas rozwoju przydatne jest aktywowanie trybu auto-refresh, w którym kontener automatycznie się przgeneruje, jeśli dojdzie do zmiany jakiejkolwiek klasy lub pliku konfiguracyjnego. Wystarczy w konstruktorze `ContainerLoader` podać jako drugi argument `true`. ```php $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp', true); ``` -Zastosowanie z ramą Nette .[#toc-using-it-with-the-nette-framework] -------------------------------------------------------------------- +Użycie z frameworkiem Nette +--------------------------- -Jak pokazaliśmy, wykorzystanie Nette DI nie ogranicza się do aplikacji napisanych w Nette Framework, można je wdrożyć wszędzie, mając do dyspozycji zaledwie 3 linie kodu. -Jeśli jednak tworzysz aplikacje w Nette Framework, [Bootstrap |application:bootstrap#di-container-configuration] jest odpowiedzialny za konfigurację i tworzenie kontenera. +Jak pokazaliśmy, użycie Nette DI nie jest ograniczone do aplikacji pisanych w Nette Framework, możesz go za pomocą zaledwie 3 linii kodu wdrożyć gdziekolwiek. Jeśli jednak rozwijasz aplikacje w Nette Framework, konfigurację i tworzenie kontenera ma na starcie [Bootstrap |application:bootstrapping#Konfiguracja kontenera DI]. diff --git a/dependency-injection/pl/passing-dependencies.texy b/dependency-injection/pl/passing-dependencies.texy index d4e4656bd3..7e60c0010c 100644 --- a/dependency-injection/pl/passing-dependencies.texy +++ b/dependency-injection/pl/passing-dependencies.texy @@ -3,22 +3,22 @@ Przekazywanie zależności <div class=perex> -Argumenty, lub "zależności" w terminologii DI, mogą być przekazywane do klas na następujące główne sposoby: +Argumenty, lub w terminologii DI „zależności”, można przekazywać do klas na następujące główne sposoby: -* przekazywanie przez konstruktora -* przekazywanie przez metodę (zwane setterem) -* poprzez ustawienie zmiennej -* przez metodę, adnotację lub atrybut *inject*. +* przekazywanie przez konstruktor +* przekazywanie przez metodę (tzw. setter) +* ustawienie właściwości (zmiennej członkowskiej) +* metodą, adnotacją lub atrybutem *inject* </div> -Zilustrujemy teraz różne warianty na konkretnych przykładach. +Teraz pokażemy poszczególne warianty na konkretnych przykładach. -Przekazywanie przez konstruktora .[#toc-constructor-injection] -============================================================== +Przekazywanie przez konstruktor +=============================== -Zależności są przekazywane jako argumenty do konstruktora w czasie tworzenia obiektu: +Zależności są przekazywane w momencie tworzenia obiektu jako argumenty konstruktora: ```php class MyClass @@ -34,9 +34,9 @@ class MyClass $obj = new MyClass($cache); ``` -Ta forma jest odpowiednia dla obowiązkowych zależności, które klasa bezwzględnie potrzebuje do funkcjonowania, ponieważ bez nich nie można utworzyć instancji. +Ta forma jest odpowiednia dla obowiązkowych zależności, których klasa bezwzględnie potrzebuje do swojego działania, ponieważ bez nich nie da się utworzyć instancji. -Od PHP 8.0 możemy używać krótszej formy notacji ([constructor property promotion |https://blog.nette.org/pl/php-8-0-kompletny-przeglad-nowosci#toc-constructor-property-promotion]), która jest funkcjonalnie równoważna: +Od PHP 8.0 możemy użyć krótszej formy zapisu ([constructor property promotion |https://blog.nette.org/pl/php-8-0-complete-overview-of-news#toc-constructor-property-promotion]), która jest funkcjonalnie równoważna: ```php // PHP 8.0 @@ -49,7 +49,7 @@ class MyClass } ``` -Od PHP 8.1 zmienna może być oznaczona flagą `readonly`, która deklaruje, że zawartość zmiennej nie będzie się już zmieniać: +Od PHP 8.1 można właściwość oznaczyć flagą `readonly`, która deklaruje, że zawartość właściwości już się nie zmieni: ```php // PHP 8.1 @@ -62,13 +62,13 @@ class MyClass } ``` -Kontener DI przekazuje zależność do konstruktora automatycznie przez [autowiring |autowiring]. Argumenty, które nie mogą być przekazane [w |services#Arguments] ten sposób (np. ciągi znaków, liczby, booleans) [są zapisywane w konfiguracji |services#Arguments]. +Kontener DI przekaże konstruktorowi zależności automatycznie za pomocą [autowiringu |autowiring]. Argumenty, których w ten sposób przekazać nie można (np. stringi, liczby, booleany) [zapiszemy w konfiguracji |services#Argumenty]. -Piekło konstruktorów .[#toc-constructor-hell] ---------------------------------------------- +Constructor hell +---------------- -Termin *constructor hell* odnosi się do sytuacji, w której dziecko dziedziczy po klasie rodzica, której konstruktor wymaga zależności, a dziecko również wymaga zależności. Musi ono również przejąć i przekazać zależności rodzica: +Termin *constructor hell* (piekło konstruktorów) oznacza sytuację, gdy potomek dziedziczy po klasie rodzicielskiej, której konstruktor wymaga zależności, a jednocześnie potomek wymaga zależności. Przy tym musi przejąć i przekazać również te rodzicielskie: ```php abstract class BaseClass @@ -94,11 +94,11 @@ final class MyClass extends BaseClass } ``` -Problem pojawia się, gdy chcemy zmienić konstruktor klasy `BaseClass`, na przykład gdy dodamy nową zależność. Wtedy musimy zmodyfikować również wszystkie konstruktory dzieci. Co czyni taką modyfikację piekłem. +Problem pojawia się w momencie, gdy będziemy chcieli zmienić konstruktor klasy `BaseClass`, na przykład gdy pojawi się nowa zależność. Wtedy konieczne jest również zmodyfikowanie wszystkich konstruktorów potomków. Co z takiej modyfikacji czyni piekło. -Jak temu zapobiec? Rozwiązaniem jest **priorytet kompozycji nad dziedziczeniem**. +Jak temu zapobiegać? Rozwiązaniem jest **preferowanie [kompozycji nad dziedziczeniem |faq#Dlaczego preferuje się kompozycję nad dziedziczeniem]**. -Zaprojektujmy więc kod inaczej. Będziemy unikać klas abstrakcyjnych `Base*`. Zamiast `MyClass` uzyskać pewną funkcjonalność poprzez dziedziczenie z `BaseClass`, będzie ona miała tę funkcjonalność przekazaną jako zależność: +Czyli zaprojektujemy kod inaczej. Będziemy unikać [abstrakcyjnych |nette:introduction-to-object-oriented-programming#Klasy abstrakcyjne] klas `Base*`. Zamiast tego, aby `MyClass` uzyskiwała pewną funkcjonalność przez dziedziczenie po `BaseClass`, tę funkcjonalność przekażemy jej jako zależność: ```php final class SomeFunctionality @@ -125,10 +125,10 @@ final class MyClass ``` -Przekazywanie przez setera .[#toc-setter-injection] -=================================================== +Przekazywanie przez setter +========================== -Zależności są przekazywane przez wywołanie metody, która przechowuje je w prywatnej właściwości. Zwykła konwencja nazewnicza dla tych metod ma postać `set*()`, dlatego są one nazywane setterami, ale oczywiście mogą być nazywane inaczej. +Zależności są przekazywane przez wywołanie metody, która zapisuje je do prywatnej właściwości. Zwykłą konwencją nazewnictwa tych metod jest forma `set*()`, dlatego nazywa się je setterami, ale mogą oczywiście nazywać się jakkolwiek inaczej. ```php class MyClass @@ -145,9 +145,9 @@ $obj = new MyClass; $obj->setCache($cache); ``` -Ta metoda jest przydatna dla opcjonalnych zależności, które nie są konieczne dla funkcji klasy, ponieważ nie jest gwarantowane, że obiekt faktycznie otrzyma zależność (tj. Że użytkownik wywoła metodę). +Ten sposób jest odpowiedni dla nieobowiązkowych zależności, które nie są niezbędne do działania klasy, ponieważ nie ma gwarancji, że obiekt faktycznie otrzyma zależność (tj. że użytkownik wywoła metodę). -Jednocześnie metoda ta pozwala na wielokrotne wywoływanie setera w celu zmiany zależności. Jeśli nie jest to pożądane, dodaj do metody kontrolę lub od PHP 8.1 oznacz zmienną `$cache` flagą `readonly`. +Jednocześnie ten sposób pozwala na wielokrotne wywoływanie settera i tym samym zmianę zależności. Jeśli nie jest to pożądane, dodamy do metody kontrolę, lub od PHP 8.1 oznaczymy właściwość `$cache` flagą `readonly`. ```php class MyClass @@ -156,7 +156,7 @@ class MyClass public function setCache(Cache $cache): void { - if ($this->cache) { + if (isset($this->cache)) { throw new RuntimeException('The dependency has already been set'); } $this->cache = $cache; @@ -164,21 +164,20 @@ class MyClass } ``` -Wywołanie setera jest zdefiniowane w konfiguracji kontenera DI w [sekcji |services#Setup] setup. Również w tym przypadku wykorzystywane jest automatyczne przekazywanie zależności przez autowiring: +Wywołanie settera definiujemy w konfiguracji kontenera DI w [kluczu setup |services#Setup]. Również tutaj wykorzystuje się automatyczne przekazywanie zależności za pomocą autowiringu: ```neon services: - - - create: MyClass + - create: MyClass setup: - setCache ``` -Poprzez ustawienie zmiennej .[#toc-property-injection] -====================================================== +Ustawienie właściwości +====================== -Zależności są przekazywane przez zapis bezpośrednio do zmiennej członkowskiej: +Zależności są przekazywane przez zapisanie bezpośrednio do publicznej właściwości (zmiennej członkowskiej): ```php class MyClass @@ -190,28 +189,27 @@ $obj = new MyClass; $obj->cache = $cache; ``` -Metoda ta jest uważana za niewłaściwą, ponieważ zmienna członkowska musi być zadeklarowana jako `public`. Tym samym nie mamy kontroli nad tym, czy przekazana zależność jest rzeczywiście danego typu (tak było przed PHP 7.4) i tracimy możliwość reagowania na nowo przypisaną zależność własnym kodem, na przykład w celu zapobieżenia późniejszej zmianie. W tym samym czasie zmienna staje się częścią publicznego interfejsu klasy, co może nie być pożądane. +Ten sposób uważa się za niewłaściwy, ponieważ właściwość musi być zadeklarowana jako `public`. A zatem nie mamy kontroli nad tym, że przekazana zależność będzie faktycznie danego typu (obowiązywało przed PHP 7.4) i tracimy możliwość reagowania na nowo przypisaną zależność własnym kodem, na przykład zapobiegania późniejszej zmianie. Jednocześnie właściwość staje się częścią publicznego interfejsu klasy, co może nie być pożądane. -Ustawienia zmiennej definiujemy w konfiguracji kontenera DI w [sekcji setup |services#Setup]: +Ustawienie właściwości definiujemy w konfiguracji kontenera DI w [sekcji setup |services#Setup]: ```neon services: - - - create: MyClass + - create: MyClass setup: - $cache = @\Cache ``` -Inject .[#toc-inject] -===================== +Inject +====== -O ile poprzednie trzy metody obowiązują ogólnie we wszystkich językach obiektowych, o tyle wstrzykiwanie za pomocą metody, adnotacji lub atrybutu *inject* jest specyficzne dla prezenterów Nette. Zostały one omówione w [osobnym rozdziale |best-practices:inject-method-attribute]. +Podczas gdy poprzednie trzy sposoby obowiązują ogólnie we wszystkich językach zorientowanych obiektowo, wstrzykiwanie metodą, adnotacją lub atrybutem *inject* jest specyficzne wyłącznie dla prezenterów w Nette. Omawiają je [osobny rozdział |best-practices:inject-method-attribute]. -Jaką metodę wybrać? .[#toc-which-way-to-choose] -=============================================== +Który sposób wybrać? +==================== -- Konstruktor jest odpowiedni dla obowiązkowych zależności, które klasa potrzebuje do działania -- setter jest odpowiedni dla opcjonalnych zależności lub zależności, które mogą być zmienione -- zmienne publiczne nie są odpowiednie +- konstruktor jest odpowiedni dla obowiązkowych zależności, których klasa bezwzględnie potrzebuje do swojego działania +- setter jest natomiast odpowiedni dla nieobowiązkowych zależności, lub zależności, które można mieć możliwość dalej zmieniać +- publiczne właściwości nie są odpowiednie diff --git a/dependency-injection/pl/services.texy b/dependency-injection/pl/services.texy index d40aece53e..f1ecc1112c 100644 --- a/dependency-injection/pl/services.texy +++ b/dependency-injection/pl/services.texy @@ -2,32 +2,32 @@ Definiowanie usług ****************** .[perex] -Konfiguracja to miejsce, w którym umieszczamy nasze niestandardowe definicje usług. Służy do tego sekcja `services`. +Konfiguracja jest miejscem, w którym uczymy kontener DI, jak ma budować poszczególne usługi i jak je łączyć z innymi zależnościami. Nette dostarcza bardzo przejrzysty i elegancki sposób, jak tego dokonać. -Na przykład w ten sposób tworzymy serwis o nazwie `database`, który będzie instancją klasy `PDO`: +Sekcja `services` w pliku konfiguracyjnym formatu NEON jest miejscem, gdzie definiujemy własne usługi i ich konfiguracje. Spójrzmy na prosty przykład definicji usługi o nazwie `database`, która reprezentuje instancję klasy `PDO`: ```neon services: database: PDO('sqlite::memory:') ``` -Nazewnictwo usług służy do [odwoływania się |#Referencing-Services] do nich. Jeśli usługa nie jest przywoływana, nie musi być nazwana. Więc po prostu używamy wypunktowania zamiast nazwy: +Podana konfiguracja zaowocuje następującą metodą fabrykującą w [kontenerze DI|container]: -```neon -services: - - PDO('sqlite::memory:') # anonimowa usługa +```php +public function createServiceDatabase(): PDO +{ + return new PDO('sqlite::memory:'); +} ``` -Wpis jednowierszowy może być rozbity na wiele wierszy, aby umożliwić dodanie innych klawiszy, takich jak [konfiguracja |#Setup]. Alias dla klucza `create:` to `factory:`. +Nazwy usług pozwalają nam odwoływać się do nich w innych częściach pliku konfiguracyjnego, w formacie `@nazwaUslugi`. Jeśli nie ma potrzeby nazywania usługi, możemy po prostu użyć tylko myślnika: ```neon services: - database: - create: PDO('sqlite::memory:') - setup: ... + - PDO('sqlite::memory:') ``` -Usługa jest następnie pobierana z kontenera DI za pomocą metody `getService()` by name lub lepiej `getByType()` by type: +Aby uzyskać usługę z kontenera DI, możemy wykorzystać metodę `getService()` z nazwą usługi jako parametrem, lub metodę `getByType()` z typem usługi: ```php $database = $container->getService('database'); @@ -35,26 +35,28 @@ $database = $container->getByType(PDO::class); ``` -Tworzenie usługi .[#toc-creating-a-service] -=========================================== +Tworzenie usługi +================ -Najczęściej tworzymy usługę, tworząc po prostu instancję klasy: +Zazwyczaj tworzymy usługę po prostu tworząc instancję określonej klasy. Na przykład: ```neon services: database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -Co generuje metodę fabryczną w [kontenerze DI |container]: +Jeśli potrzebujemy rozszerzyć konfigurację o dodatkowe klucze, można definicję rozpisać na więcej linii: -```php -public function createServiceDatabase(): PDO -{ - return new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'secret'); -} +```neon +services: + database: + create: PDO('sqlite::memory:') + setup: ... ``` -Alternatywnie, klucz `arguments` może być użyty do przekazania [argumentów |#Arguments]: +Klucz `create` ma alias `factory`, obie warianty są w praktyce powszechne. Niemniej jednak zalecamy używanie `create`. + +Argumenty konstruktora lub metody tworzącej mogą być alternatywnie zapisane w kluczu `arguments`: ```neon services: @@ -63,294 +65,303 @@ services: arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret] ``` -Metoda statyczna może również tworzyć usługę: +Usługi nie muszą być tworzone tylko przez proste utworzenie instancji klasy, mogą być również wynikiem wywołania metod statycznych lub metod innych usług: ```neon services: - database: My\Database::create(root, secret) + database: DatabaseFactory::create() + router: @routerFactory::create() ``` -Odpowiada kodowi PHP: +Zauważ, że dla uproszczenia zamiast `->` używa się `::`, zobacz [#wyrażenia]. Wygenerują się te metody fabrykujące: ```php public function createServiceDatabase(): PDO { - return My\Database::create('root', 'secret'); + return DatabaseFactory::create(); +} + +public function createServiceRouter(): RouteList +{ + return $this->getService('routerFactory')->create(); } ``` -Zakłada się, że metoda statyczna `My\Database::create()` ma zdefiniowaną wartość zwrotną, którą kontener DI musi znać. Jeśli go nie ma, zapisz typ do konfiguracji: +Kontener DI potrzebuje znać typ utworzonej usługi. Jeśli tworzymy usługę za pomocą metody, która nie ma określonego typu zwracanego, musimy ten typ jawnie podać w konfiguracji: ```neon services: database: - create: My\Database::create(root, secret) + create: DatabaseFactory::create() type: PDO ``` -Nette DI daje nam niezwykle potężne środki wyrazu, które możesz wykorzystać do napisania niemal wszystkiego. Na przykład, aby [odwołać się do |#Referencing-Services] innej usługi i wywołać jej metodę. Dla uproszczenia, zamiast `->`, używamy `::` + +Argumenty +========= + +Do konstruktora i metod przekazujemy argumenty w sposób bardzo podobny jak w samym PHP: ```neon services: - routerFactory: App\Router\Factory - router: @routerFactory::create() + database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -Odpowiada kodowi PHP: - -```php -public function createServiceRouterFactory(): App\Router\Factory -{ - return new App\Router\Factory; -} +Dla lepszej czytelności możemy argumenty rozpisać na osobne linie. W takim przypadku używanie przecinków jest opcjonalne: -public function createServiceRouter(): Router -{ - return $this->getService('routerFactory')->create(); -} +```neon +services: + database: PDO( + 'mysql:host=127.0.0.1;dbname=test' + root + secret + ) ``` -Wywołania metod mogą być łączone łańcuchowo, tak jak w PHP: +Argumenty możesz również nazwać i nie musisz się wtedy martwić o ich kolejność: ```neon services: - foo: FooFactory::build()::get() + database: PDO( + username: root + password: secret + dsn: 'mysql:host=127.0.0.1;dbname=test' + ) ``` -Odpowiada kodowi PHP: +Jeśli chcesz niektóre argumenty pominąć i użyć ich wartości domyślnej lub podstawić usługę za pomocą [autowiringu|autowiring], użyj podkreślenia: -```php -public function createServiceFoo() -{ - return FooFactory::build()->get(); -} +```neon +services: + foo: Foo(_, %appDir%) ``` +Jako argumenty można przekazywać usługi, używać parametrów i wiele więcej, zobacz [#wyrażenia]. + -Argumenty .[#toc-arguments] -=========================== +Setup +===== -Parametry nazwane mogą być również używane do przekazywania argumentów: +W sekcji `setup` definiujemy metody, które mają być wywoływane podczas tworzenia usługi. ```neon services: - database: PDO( - 'mysql:host=127.0.0.1;dbname=test' # pozycja - username: root # nazwa - password: secret # nazwa - ) + database: + create: PDO(%dsn%, %user%, %password%) + setup: + - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) ``` -Przy dzieleniu argumentów na wiele linii, użycie przecinków jest opcjonalne. +To w PHP wyglądałoby tak: -Oczywiście jako argumenty mogą być użyte [inne usługi |#Referencing-Services] lub [parametry |configuration#parameters]: +```php +public function createServiceDatabase(): PDO +{ + $service = new PDO('...', '...', '...'); + $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + return $service; +} +``` + +Oprócz wywoływania metod można również przekazywać wartości do właściwości. Obsługiwane jest również dodanie elementu do tablicy, które należy zapisać w cudzysłowach, aby nie kolidowało ze składnią NEON: ```neon services: - - Foo(@anotherService, %appDir%) + foo: + create: Foo + setup: + - $value = 123 + - '$onClick[]' = [@bar, clickHandler] ``` -Odpowiada kodowi PHP: +Co w kodzie PHP wyglądałoby następująco: ```php -public function createService01(): Foo +public function createServiceFoo(): Foo { - return new Foo($this->getService('anotherService'), '...'); + $service = new Foo; + $service->value = 123; + $service->onClick[] = [$this->getService('bar'), 'clickHandler']; + return $service; } ``` -Jeśli pierwszy argument ma być [autowpisywany |autowiring], a chcemy dołączyć drugi argument, to pomijamy pierwszy argument za pomocą `_`, tedy např. `Foo(_, %appDir%)`. Albo jeszcze lepiej, po prostu przekaż drugi argument jako nazwany parametr, np. `Foo(path: %appDir%)`. - -Nette DI i format NEON dają nam niezwykle potężne zasoby ekspresyjne, które można wykorzystać do napisania niemal wszystkiego. Tak więc argumentem może być nowo utworzony obiekt, można wywoływać metody statyczne, metody innych serwisów, a nawet funkcje globalne przy użyciu specjalnej notacji: +W setupie można jednak wywoływać również metody statyczne lub metody innych usług. Jeśli potrzebujesz przekazać jako argument aktualną usługę, podaj ją jako `@self`: ```neon services: - analyser: My\Analyser( - FilesystemIterator(%appDir%) # vytvoření objektu - DateTime::createFromFormat('Y-m-d') # volání statické metody - @anotherService # předání jiné služby - @http.request::getRemoteAddress() # volání metody jiné služby - ::getenv(NetteMode) # volání globální funkce - ) + foo: + create: Foo + setup: + - My\Helpers::initializeFoo(@self) + - @anotherService::setFoo(@self) ``` -Odpowiada on kodowi PHP: +Zauważ, że dla uproszczenia zamiast `->` używa się `::`, zobacz [#wyrażenia]. Wygeneruje się taka metoda fabrykująca: ```php -public function createServiceAnalyser(): My\Analyser +public function createServiceFoo(): Foo { - return new My\Analyser( - new FilesystemIterator('...'), - DateTime::createFromFormat('Y-m-d'), - $this->getService('anotherService'), - $this->getService('http.request')->getRemoteAddress(), - getenv('NetteMode') - ); + $service = new Foo; + My\Helpers::initializeFoo($service); + $this->getService('anotherService')->setFoo($service); + return $service; } ``` -Funkcje specjalne .[#toc-special-functions] -------------------------------------------- +Wyrażenia +========= -Możesz również użyć specjalnych funkcji w argumentach, aby zastąpić lub zanegować wartości: - -- `not(%arg%)` negacja -- `bool(%arg%)` lossless override to bool -- `int(%arg%)` bezstratne przepisywanie na int -- `float(%arg%)` bezstratny reflow do float -- `string(%arg%)` bezstratne przepisywanie na ciąg znaków +Nette DI daje nam niezwykle bogate środki wyrazu, za pomocą których możemy zapisać prawie wszystko. W plikach konfiguracyjnych możemy więc wykorzystywać [parametry |configuration#Parametry]: ```neon -services: - - Foo( - id: int(::getenv('ProjectId')) - productionMode: not(%debugMode%) - ) -``` - -Bezstratne repartycjonowanie różni się od normalnego repartycjonowania PHP, np. przy użyciu `(int)`, tym, że rzuca wyjątek dla wartości nienumerycznych. +# parametr +%wwwDir% -Jako argumenty można przekazać wiele usług. Tablicę wszystkich usług danego typu (tj. klasy lub interfejsu) tworzy funkcja `typed()`. Funkcja pomija usługi, które mają wyłączone autowiring, można podać wiele typów oddzielonych przecinkami. +# wartość parametru pod kluczem +%mailer.user% -```neon -services: - - BarsDependent( typed(Bar) ) +# parametr wewnątrz stringa +'%wwwDir%/images' ``` -Możesz również przekazać tablicę usług automatycznie za pomocą [autowiring |autowiring#Collection-of-Services]. - -Funkcja `tagged()` tworzy tablicę wszystkich usług z określonym [tagiem |#Tags]. Można również podać wiele tagów oddzielonych przecinkiem. +Dalej tworzyć obiekty, wywoływać metody i funkcje: ```neon -services: - - LoggersDependent( tagged(logger) ) -``` +# tworzenie obiektu +DateTime() +# wywołanie metody statycznej +Collator::create(%locale%) -Usługi odsyłania .[#toc-referencing-services] -============================================= +# wywołanie funkcji PHP +::getenv(DB_USER) +``` -Do usług odwołujemy się za pomocą nazwy usługi i nazwy serwisu, więc na przykład `@database`: +Odwoływać się do usług albo ich nazwą, albo za pomocą typu: ```neon -services: - - create: Foo(@database) - setup: - - setCacheStorage(@cache.storage) +# usługa według nazwy +@database + +# usługa według typu +@Nette\Database\Connection ``` -Odpowiada kodowi PHP: +Używać składni first-class callable: .{data-version:3.2.0} -```php -public function createService01(): Foo -{ - $service = new Foo($this->getService('database')); - $service->setCacheStorage($this->getService('cache.storage')); - return $service; -} +```neon +# tworzenie callbacku, odpowiednik [@user, logout] +@user::logout(...) ``` -Nawet anonimowe usługi mogą być przywoływane poprzez alias, wystarczy podać typ (klasa lub interfejs) zamiast nazwy. Zwykle jednak nie jest to konieczne ze względu na [autowiring |autowiring]. +Używać stałych: ```neon -services: - - create: Foo(@Nette\Database\Connection) # nebo třeba @PDO. - setup: - - setCacheStorage(@cache.storage) +# stała klasy +FilesystemIterator::SKIP_DOTS + +# stałą globalną uzyskamy funkcją PHP constant() +::constant(PHP_VERSION) ``` +Wywołania metod można łączyć w łańcuchy tak samo jak w PHP. Tylko dla uproszczenia zamiast `->` używa się `::`: -Ustawienie .[#toc-setup] -======================== +```neon +DateTime()::format('Y-m-d') +# PHP: (new DateTime())->format('Y-m-d') -W sekcji setup znajduje się lista metod, które należy wywołać podczas tworzenia usługi: +@http.request::getUrl()::getHost() +# PHP: $this->getService('http.request')->getUrl()->getHost() +``` + +Te wyrażenia możesz używać wszędzie, przy [tworzeniu usług |#Tworzenie usługi], w [argumentach |#Argumenty], w sekcji [#setup] lub [parametrach |configuration#Parametry]: ```neon +parameters: + ipAddress: @http.request::getRemoteAddress() + services: database: - create: PDO(%dsn%, %user%, %password%) + create: DatabaseFactory::create( @anotherService::getDsn() ) setup: - - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) + - initialize( ::getenv('DB_USER') ) ``` -Odpowiada kodowi PHP: -```php -public function createServiceDatabase(): PDO -{ - $service = new PDO('...', '...', '...'); - $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - return $service; -} -``` +Funkcje specjalne +----------------- -Można również ustawić wartości zmienne. Dodawanie elementu do tablicy jest również obsługiwane, ale musi być napisane w cudzysłowie, aby uniknąć konfliktu ze składnią NEON: +W plikach konfiguracyjnych możesz używać tych specjalnych funkcji: +- `not()` negacja wartości +- `bool()`, `int()`, `float()`, `string()` bezstratne rzutowanie na dany typ +- `typed()` stworzy tablicę wszystkich usług określonego typu +- `tagged()` stworzenie tablicy wszystkich usług z danym tagiem ```neon services: - foo: - create: Foo - setup: - - $value = 123 - - '$onClick[]' = [@bar, clickHandler] + - Foo( + id: int(::getenv('ProjectId')) + productionMode: not(%debugMode%) + ) ``` -Odpowiada kodowi PHP: +W przeciwieństwie do klasycznego rzutowania w PHP, jak np. `(int)`, bezstratne rzutowanie rzuci wyjątek dla wartości nieliczbowych. -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - $service->value = 123; - $service->onClick[] = [$this->getService('bar'), 'clickHandler']; - return $service; -} +Funkcja `typed()` tworzy tablicę wszystkich usług danego typu (klasa lub interfejs). Pomija usługi, które mają wyłączony autowiring. Można podać również więcej typów oddzielonych przecinkiem. + +```neon +services: + - BarsDependent( typed(Bar) ) ``` -Jednak metody statyczne lub metody innych usług mogą być wywoływane w konfiguracji. Rzeczywista usługa jest przekazywana do nich jako `@self`: +Tablicę usług określonego typu możesz przekazywać jako argument również automatycznie za pomocą [autowiringu |autowiring#Tablica usług]. +Funkcja `tagged()` tworzy następnie tablicę wszystkich usług z określonym tagiem. Również tutaj możesz specyfikować więcej tagów oddzielonych przecinkiem. ```neon services: - foo: - create: Foo - setup: - - My\Helpers::initializeFoo(@self) - - @anotherService::setFoo(@self) + - LoggersDependent( tagged(logger) ) ``` -Odpowiada kodowi PHP: -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - My\Helpers::initializeFoo($service); - $this->getService('anotherService')->setFoo($service); - return $service; -} +Autowiring +========== + +Klucz `autowired` pozwala wpłynąć na zachowanie autowiringu dla konkretnej usługi. Szczegóły znajdziesz w [rozdziale o autowiringu|autowiring]. + +```neon +services: + foo: + create: Foo + autowired: false # usługa foo jest wyłączona z autowiringu ``` -Autowiring .[#toc-autowiring] -============================= +Usługi lazy .{data-version:3.2.4} +================================= -Możesz użyć klucza autowired, aby wykluczyć usługę z autowiring lub wpłynąć na jej zachowanie. Więcej informacji można znaleźć w [rozdziale dotyczącym autowiring |autowiring]. +Lazy loading (leniwe ładowanie) to technika, która odkłada tworzenie usługi aż do momentu, gdy jest ona faktycznie potrzebna. W globalnej konfiguracji można [włączyć leniwe tworzenie |configuration#Usługi lazy] dla wszystkich usług naraz. Dla poszczególnych usług można następnie to zachowanie nadpisać: ```neon services: foo: create: Foo - autowired: false # service foo jest wyłączony z autowiring + lazy: false ``` +Gdy usługa jest zdefiniowana jako lazy, przy jej żądaniu z kontenera DI otrzymujemy specjalny obiekt zastępczy. Wygląda on i zachowuje się tak samo jak rzeczywista usługa, ale rzeczywista inicjalizacja (wywołanie konstruktora i setupu) nastąpi dopiero przy pierwszym wywołaniu jakiejkolwiek jej metody lub właściwości. + +.[note] +Leniwe ładowanie można stosować tylko dla klas użytkownika, a nie dla wewnętrznych klas PHP. Wymaga PHP 8.4 lub nowszego. -Tagi .[#toc-tags] -================= -Informacje o użytkownikach mogą być dodawane do poszczególnych serwisów w postaci tagów: +Tagi +==== + +Tagi służą do dodawania dodatkowych informacji do usług. Usłudze możesz dodać jeden lub więcej tagów: ```neon services: @@ -360,7 +371,7 @@ services: - cached ``` -Tagi mogą mieć również wartość: +Tagi mogą również przenosić wartości: ```neon services: @@ -370,26 +381,26 @@ services: logger: monolog.logger.event ``` -Możesz przekazać tablicę usług z określonymi tagami jako argument za pomocą funkcji `tagged()`. Możesz określić wiele tagów oddzielonych przecinkami. +Aby uzyskać wszystkie usługi z określonymi tagami, możesz użyć funkcji `tagged()`: ```neon services: - LoggersDependent( tagged(logger) ) ``` -Nazwy usług można pobrać z kontenera DI za pomocą metody `findByTag()`: +W kontenerze DI możesz uzyskać nazwy wszystkich usług z określonym tagiem za pomocą metody `findByTag()`: ```php $names = $container->findByTag('logger'); -// $names jest tablicą zawierającą nazwę serwisu i wartość tagu +// $names to tablica zawierająca nazwę usługi i wartość tagu // np. ['foo' => 'monolog.logger.event', ...] ``` -Tryb wtrysku .[#toc-inject-mode] -================================ +Tryb Inject +=========== -Flaga `inject: true` służy do aktywowania przekazywania zależności poprzez zmienne publiczne z adnotacją [inject |best-practices:inject-method-attribute#Inject-Attribute] i metodami [inject*() |best-practices:inject-method-attribute#inject-Methods]. +Za pomocą flagi `inject: true` aktywuje się przekazywanie zależności przez publiczne właściwości z adnotacją [inject |best-practices:inject-method-attribute#Atrybuty Inject] i metody [inject*() |best-practices:inject-method-attribute#Metody inject]. ```neon services: @@ -398,13 +409,13 @@ services: inject: true ``` -Domyślnie strona `inject` jest aktywowana tylko dla prezenterów. +Domyślnie `inject` jest aktywowany tylko dla prezenterów. -Modyfikacja usług .[#toc-modification-of-services] -================================================== +Modyfikacja usług +================= -Istnieje wiele usług w kontenerze DI, które dodały wbudowane lub [twoje rozszerzenia |#rozšíření]. Definicje tych usług mogą być modyfikowane w konfiguracji. Na przykład dla serwisu `application.application`, który domyślnie jest obiektem `Nette\Application\Application`, możemy zmienić klasę: +Kontener DI zawiera wiele usług, które zostały dodane za pośrednictwem wbudowanego lub [użytkownika rozszerzenia|extensions]. Możesz modyfikować definicje tych usług bezpośrednio w konfiguracji. Na przykład możesz zmienić klasę usługi `application.application`, która standardowo jest `Nette\Application\Application`, na inną: ```neon services: @@ -413,9 +424,9 @@ services: alteration: true ``` -Flaga `alteration` ma charakter informacyjny i mówi, że właśnie modyfikujemy istniejącą usługę. +Flaga `alteration` jest informacyjna i mówi, że tylko modyfikujemy istniejącą usługę. -Możemy również dodać ustawienie: +Możemy również uzupełnić setup: ```neon services: @@ -426,7 +437,7 @@ services: - '$onStartup[]' = [@resource, init] ``` -Podczas przepisywania usługi możemy chcieć usunąć oryginalne argumenty, elementy ustawień lub tagi, do czego służy `reset`: +Podczas nadpisywania usługi możemy chcieć usunąć oryginalne argumenty, pozycje setup lub tagi, do czego służy `reset`: ```neon services: @@ -439,7 +450,7 @@ services: - tags ``` -Usługa dodana przez rozszerzenie może być również usunięta z kontenera: +Jeśli chcesz usunąć usługę dodaną przez rozszerzenie, możesz to zrobić tak: ```neon services: diff --git a/dependency-injection/pt/@home.texy b/dependency-injection/pt/@home.texy index 4389971f4a..513481f0e3 100644 --- a/dependency-injection/pt/@home.texy +++ b/dependency-injection/pt/@home.texy @@ -1,24 +1,21 @@ -Injeção de dependência -********************** +Nette DI +******** .[perex] -A injeção de dependência é um padrão de projeto que mudará fundamentalmente a maneira como você vê o código e o desenvolvimento. Ela abre o caminho para um mundo de aplicações sustentáveis e de design limpo. +A Injeção de Dependência é um padrão de projeto que mudará fundamentalmente sua perspectiva sobre código e desenvolvimento. Abrirá o caminho para o mundo de aplicações bem projetadas e sustentáveis. - [O que é Injeção de Dependência? |introduction] -- [Estado global e Singletons |global-state] -- [Dependências de passagem |passing-dependencies] -- [O que é DI Container? |container] -- [Perguntas mais freqüentes |faq] - +- [Estado global e singletons |global-state] +- [Passando dependências |passing-dependencies] +- [O que é um Contêiner DI? |container] +- [Perguntas frequentes|faq] -Nette DI --------- -O pacote `nette/di` fornece um recipiente DI compilado extremamente avançado para PHP. +O pacote `nette/di` fornece um contêiner de DI compilado extremamente avançado para PHP. -- [Container Nette DI |nette-container] +- [Contêiner Nette DI |nette-container] - [Configuração |configuration] -- [Definições de serviço |services] -- [Cablagem automática |autowiring] -- [Fábricas Geradas |factory] -- [Criação de extensões para Nette DI |extensions] +- [Definindo serviços |services] +- [Autowiring |autowiring] +- [Fábricas geradas |factory] +- [Criando extensões para Nette DI|extensions] diff --git a/dependency-injection/pt/@left-menu.texy b/dependency-injection/pt/@left-menu.texy index a202a79191..2b9273bd2a 100644 --- a/dependency-injection/pt/@left-menu.texy +++ b/dependency-injection/pt/@left-menu.texy @@ -1,17 +1,17 @@ -Injeção de dependência +Injeção de Dependência ********************** - [O que é DI? |introduction] -- [Estado global e Singletons |global-state] -- [Dependências de passagem |passing-dependencies] -- [O que é DI Container? |container] -- [Perguntas mais freqüentes |faq] +- [Estado global e singletons |global-state] +- [Passando dependências |passing-dependencies] +- [O que é um Contêiner DI? |container] +- [Perguntas frequentes|faq] Nette DI -------- -- [Container DI Nette |nette-container] +- [Contêiner Nette DI |nette-container] - [Configuração |configuration] -- [Definições de serviço |services] -- [Cablagem automática |autowiring] -- [Fábricas Geradas |factory] -- [Criação de extensões para Nette DI |extensions] +- [Definindo serviços |services] +- [Autowiring |autowiring] +- [Fábricas geradas |factory] +- [Criando extensões para Nette DI|extensions] diff --git a/dependency-injection/pt/@meta.texy b/dependency-injection/pt/@meta.texy new file mode 100644 index 0000000000..41a853b6aa --- /dev/null +++ b/dependency-injection/pt/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentação Nette}} diff --git a/dependency-injection/pt/autowiring.texy b/dependency-injection/pt/autowiring.texy index b970520160..bcf85fc932 100644 --- a/dependency-injection/pt/autowiring.texy +++ b/dependency-injection/pt/autowiring.texy @@ -1,10 +1,10 @@ -Cablagem automática -******************* +Autowiring +********** .[perex] -A fiação automática é uma grande característica que pode passar automaticamente os serviços para o construtor e outros métodos, portanto, não precisamos escrevê-los de forma alguma. Isso economiza muito tempo. +Autowiring é um ótimo recurso que pode passar automaticamente os serviços necessários para o construtor e outros métodos, para que não precisemos escrevê-los. Isso economiza muito tempo. -Isto nos permite saltar a grande maioria dos argumentos ao escrever definições de serviços. Ao invés de: +Graças a isso, podemos omitir a grande maioria dos argumentos ao escrever definições de serviço. Em vez de: ```neon services: @@ -18,7 +18,7 @@ services: articles: Model\ArticleRepository ``` -A fiação automática é feita por tipos, portanto `ArticleRepository` classe deve ser definida como segue: +O Autowiring é orientado por tipos, então para funcionar, a classe `ArticleRepository` deve ser definida aproximadamente assim: ```php namespace Model; @@ -30,22 +30,22 @@ class ArticleRepository } ``` -Para utilizar a fiação automática, deve haver ** apenas um serviço** para cada tipo no contêiner. Se houvesse mais, o cabeamento automático não saberia qual deles passar e jogar fora uma exceção: +Para poder usar o autowiring, deve haver **exatamente um serviço** para cada tipo no contêiner. Se houver mais, o autowiring não saberá qual passar e lançará uma exceção: ```neon services: mainDb: PDO(%dsn%, %user%, %password%) tempDb: PDO('sqlite::memory:') - articles: Model\ArticleRepository # EXCEPÇÃO, tanto a mainDb como a tempDb combinam + articles: Model\ArticleRepository # LANÇARÁ EXCEÇÃO, tanto mainDb quanto tempDb correspondem ``` -A solução seria contornar a fiação automática e declarar explicitamente o nome do serviço (ou seja, `articles: Model\ArticleRepository(@mainDb)`). Entretanto, é mais conveniente [desativar |#Disabled autowiring] a fiação automática de um serviço, ou o primeiro serviço [prefere |#Preferred Autowiring]. +A solução seria contornar o autowiring e especificar explicitamente o nome do serviço (ou seja, `articles: Model\ArticleRepository(@mainDb)`). Mas é mais inteligente [desativar |#Desativação do autowiring] o autowiring para um dos serviços, ou [dar preferência |#Preferência de autowiring] ao primeiro serviço. -Desligamento automático .[#toc-disabled-autowiring] ---------------------------------------------------- +Desativação do autowiring +------------------------- -Você pode desativar o serviço de cabeamento automático usando a opção `autowired: no`: +Podemos desativar o autowiring de um serviço usando a opção `autowired: no`: ```neon services: @@ -53,28 +53,27 @@ services: tempDb: create: PDO('sqlite::memory:') - autowired: false # remove a tempDb da autowiring + autowired: false # o serviço tempDb é excluído do autowiring - articles: Model\ArticleRepository # portanto passa o mainDb para o construtor + articles: Model\ArticleRepository # portanto, passa mainDb para o construtor ``` -O serviço `articles` não lança a exceção de que existem dois serviços correspondentes do tipo `PDO` (ou seja, `mainDb` e `tempDb`) que podem ser passados ao construtor, pois ele só vê o serviço `mainDb`. +O serviço `articles` não lançará uma exceção dizendo que existem dois serviços do tipo `PDO` correspondentes (ou seja, `mainDb` e `tempDb`) que podem ser passados para o construtor, porque ele vê apenas o serviço `mainDb`. .[note] -A configuração da fiação automática em Nette funciona de forma diferente da Symfony, onde a opção `autowire: false` diz que a fiação automática não deve ser usada para argumentos de construção de serviços. -Em Nette, a fiação automática é sempre usada, seja para argumentos do construtor ou para qualquer outro método. A opção `autowired: false` diz que a instância de serviço não deve ser aprovada em nenhum lugar usando a fiação automática. +A configuração do autowiring no Nette funciona de forma diferente do Symfony, onde a opção `autowire: false` diz que o autowiring não deve ser usado para os argumentos do construtor do serviço fornecido. No Nette, o autowiring é sempre usado, seja para argumentos do construtor ou para quaisquer outros métodos. A opção `autowired: false` diz que a instância do serviço fornecido não deve ser passada para lugar nenhum usando autowiring. -Fiação automática preferencial .[#toc-preferred-autowiring] ------------------------------------------------------------ +Preferência de autowiring +------------------------- -Se tivermos mais serviços do mesmo tipo e um deles tiver a opção `autowired`, este serviço se torna o preferido: +Se tivermos vários serviços do mesmo tipo e especificarmos a opção `autowired` para um deles, esse serviço se torna o preferido: ```neon services: mainDb: create: PDO(%dsn%, %user%, %password%) - autowired: PDO # faz com que seja preferível + autowired: PDO # torna-se preferido tempDb: create: PDO('sqlite::memory:') @@ -82,13 +81,13 @@ services: articles: Model\ArticleRepository ``` -O serviço `articles` não lança a exceção de que existem dois serviços correspondentes `PDO` (ou seja, `mainDb` e `tempDb`), mas utiliza o serviço preferido, ou seja, `mainDb`. +O serviço `articles` não lançará uma exceção dizendo que existem dois serviços do tipo `PDO` correspondentes (ou seja, `mainDb` e `tempDb`), mas usará o serviço preferido, ou seja, `mainDb`. -Coleção de serviços .[#toc-collection-of-services] --------------------------------------------------- +Array de serviços +----------------- -A fiação automática também pode passar uma série de serviços de um determinado tipo. Uma vez que o PHP não pode nativamente anotar o tipo de itens de array, além do tipo `array`, um comentário phpDoc com o tipo de item como `ClassName[]` deve ser adicionado: +O Autowiring também pode passar arrays de serviços de um determinado tipo. Como não é possível escrever nativamente o tipo dos itens do array em PHP, é necessário, além do tipo `array`, adicionar um comentário phpDoc com o tipo do item no formato `ClassName[]`: ```php namespace Model; @@ -103,46 +102,45 @@ class ShipManager } ``` -O recipiente DI passa então automaticamente uma série de serviços que correspondem ao tipo dado. Ele irá omitir serviços que tenham a fiação automática desligada. +O contêiner DI então passa automaticamente um array de serviços correspondentes ao tipo fornecido. Ele omite serviços que têm o autowiring desativado. -Se você não pode controlar a forma do comentário phpDoc, você pode passar uma série de serviços diretamente na configuração usando [`typed()` |services#Special Functions]. +O tipo no comentário também pode estar no formato `array<int, Class>` ou `list<Class>`. Se você não pode influenciar a forma do comentário phpDoc, pode passar o array de serviços diretamente na configuração usando [`typed()` |services#Funções especiais]. -Argumentos escalares .[#toc-scalar-arguments] ---------------------------------------------- +Argumentos escalares +-------------------- -A cablagem automática só pode passar objetos e matrizes de objetos. Argumentos escalares (por exemplo, cordas, números, booleanos) [escrevem em configuração |services#Arguments]. -Uma alternativa é criar um [objeto-objeto de configuração |best-practices:passing-settings-to-presenters] que encapsula um valor escalar (ou múltiplos valores) como um objeto, que pode então ser passado novamente usando a fiação automática. +O Autowiring só pode injetar objetos e arrays de objetos. Argumentos escalares (por exemplo, strings, números, booleanos) [são escritos na configuração |services#Argumentos]. Uma alternativa é criar um [objeto de configurações |best-practices:passing-settings-to-presenters], que encapsula o valor escalar (ou múltiplos valores) em um objeto, que pode então ser passado novamente usando autowiring. ```php class MySettings { public function __construct( - // somente leitura pode ser usado desde PHP 8.1 + // readonly pode ser usado a partir do PHP 8.1 public readonly bool $value, ) {} } ``` -Você cria um serviço ao adicioná-lo à configuração: +Você cria um serviço a partir dele adicionando-o à configuração: ```neon services: - MySettings('any value') ``` -Todas as classes o solicitarão então por meio de fiação automática. +Todas as classes então o solicitarão usando autowiring. -Estreitamento da fiação automática .[#toc-narrowing-of-autowiring] ------------------------------------------------------------------- +Restringindo o autowiring +------------------------- -Para serviços individuais, a fiação automática pode ser restringida a classes ou interfaces específicas. +Para serviços individuais, o autowiring pode ser restrito a certas classes ou interfaces. -Normalmente, a cablagem automática passa o serviço para cada parâmetro do método a cujo tipo o serviço corresponde. Estreitamento significa que especificamos as condições que os tipos especificados para os parâmetros do método devem satisfazer para que o serviço lhes seja passado. +Normalmente, o autowiring passa o serviço para cada parâmetro de método cujo tipo o serviço corresponde. Restringir significa que estabelecemos condições que os tipos especificados nos parâmetros do método devem satisfazer para que o serviço seja passado para eles. -Vejamos um exemplo: +Vamos ilustrar com um exemplo: ```php class ParentClass @@ -164,42 +162,42 @@ class ChildDependent } ``` -Se todos eles fossem registrados como serviços, a fiação automática falharia: +Se registrássemos todos eles como serviços, o autowiring falharia: ```neon services: parent: ParentClass child: ChildClass - parentDep: ParentDependent # THROWS EXCEPTION, both parent and child matches - childDep: ChildDependent # passa o serviço 'criança' para o construtor + parentDep: ParentDependent # LANÇARÁ EXCEÇÃO, os serviços parent e child correspondem + childDep: ChildDependent # autowiring passa o serviço child para o construtor ``` -O serviço `parentDep` lança a exceção `Multiple services of type ParentClass found: parent, child` porque tanto `parent` quanto `child` cabem em seu construtor e a fiação automática não pode tomar uma decisão sobre qual escolher. +O serviço `parentDep` lançará a exceção `Multiple services of type ParentClass found: parent, child`, porque ambos os serviços `parent` e `child` se encaixam em seu construtor, e o autowiring não pode decidir qual escolher. -Para o serviço `child`, podemos, portanto, reduzir sua fiação automática para `ChildClass`: +Para o serviço `child`, podemos, portanto, restringir seu autowiring ao tipo `ChildClass`: ```neon services: parent: ParentClass child: create: ChildClass - autowired: ChildClass # alternativa para a classe infantil: 'autowired: autowired' + autowired: ChildClass # também pode escrever 'autowired: self' - paiDep: ParentDependent # THROWS EXCEPTION, the 'child' can not be autowired - childDep: ChildDependent # passa o serviço 'criança' para o construtor + parentDep: ParentDependent # autowiring passa o serviço parent para o construtor + childDep: ChildDependent # autowiring passa o serviço child para o construtor ``` -O serviço `parentDep` é agora passado para o construtor de serviços `parentDep`, uma vez que agora é o único objeto correspondente. O serviço `child` não é mais passado por auto-cablagem. Sim, o serviço `child` ainda é do tipo `ParentClass`, mas a condição de estreitamento dada para o tipo de parâmetro não se aplica mais, ou seja, não é mais verdade que `ParentClass` * é um supertipo* de `ChildClass`. +Agora, o serviço `parent` é passado para o construtor do serviço `parentDep`, porque agora é o único objeto correspondente. O autowiring não passa mais o serviço `child` para lá. Sim, o serviço `child` ainda é do tipo `ParentClass`, mas a condição restritiva dada para o tipo do parâmetro não é mais válida, ou seja, não é verdade que `ParentClass` *é um supertipo de* `ChildClass`. -No caso do `child`, `autowired: ChildClass` poderia ser escrito como `autowired: self`, pois o `self` significa tipo de serviço atual. +Para o serviço `child`, `autowired: ChildClass` também poderia ser escrito como `autowired: self`, já que `self` é um placeholder para a classe do serviço atual. -A chave `autowired` pode incluir várias classes e interfaces como matriz: +Na chave `autowired`, também é possível especificar várias classes ou interfaces como um array: ```neon autowired: [BarClass, FooInterface] ``` -Vamos tentar acrescentar interfaces ao exemplo: +Vamos tentar complementar o exemplo com interfaces: ```php interface FooInterface @@ -239,13 +237,13 @@ class ChildDependent } ``` -Quando não limitarmos o serviço `child`, ele caberá nos construtores de todas as classes `FooDependent`, `BarDependent`, `ParentDependent` e `ChildDependent` e a fiação automática o passará por lá. +Se não restringirmos o serviço `child` de forma alguma, ele se encaixará nos construtores de todas as classes `FooDependent`, `BarDependent`, `ParentDependent` e `ChildDependent`, e o autowiring o passará para lá. -No entanto, se limitarmos sua fiação automática a `ChildClass` usando `autowired: ChildClass` (ou `self`), a fiação automática só passa para o construtor `ChildDependent`, pois requer um argumento do tipo `ChildClass` e `ChildClass` *é do tipo* `ChildClass`. Nenhum outro tipo especificado para os outros parâmetros é um superconjunto de `ChildClass`, portanto, o serviço não é passado. +No entanto, se restringirmos seu autowiring a `ChildClass` usando `autowired: ChildClass` (ou `self`), o autowiring o passará apenas para o construtor de `ChildDependent`, porque ele requer um argumento do tipo `ChildClass` e é verdade que `ChildClass` *é do tipo* `ChildClass`. Nenhum outro tipo especificado nos outros parâmetros é um supertipo de `ChildClass`, então o serviço não é passado. -Se restringirmos a `ParentClass` usando `autowired: ParentClass`, a fiação automática passará novamente para o construtor `ChildDependent` (já que o tipo requerido `ChildClass` é um superconjunto de `ParentClass`) e para o construtor `ParentDependent` também, já que o tipo requerido de `ParentClass` também é compatível. +Se o restringirmos a `ParentClass` usando `autowired: ParentClass`, ele será novamente passado para o construtor de `ChildDependent` (porque o `ChildClass` exigido é um supertipo de `ParentClass`) e, agora também para o construtor de `ParentDependent`, porque o tipo `ParentClass` exigido também é adequado. -Se o restringirmos a `FooInterface`, ele ainda fará a ligação automática para `ParentDependent` (o tipo exigido `ParentClass` é um supertipo de `FooInterface`) e `ChildDependent`, mas além do construtor `FooDependent`, mas não para `BarDependent`, já que `BarInterface` não é um supertipo de `FooInterface`. +Se o restringirmos a `FooInterface`, ele ainda será autowired para `ParentDependent` (o `ParentClass` exigido é um supertipo de `FooInterface`) e `ChildDependent`, mas adicionalmente também para o construtor de `FooDependent`, mas não para `BarDependent`, porque `BarInterface` não é um supertipo de `FooInterface`. ```neon services: @@ -253,8 +251,8 @@ services: create: ChildClass autowired: FooInterface - fooDep: FooDependent # passa a criança de serviço para o construtor - barDep: BarDependent # EXCEPÇÃO DE GARANTIAS, nenhum serviço passaria - parentDep: ParentDependent # passa a criança de serviço para o construtor - childDep: ChildDependent # passa a criança de serviço para o construtor + fooDep: FooDependent # autowiring passa child para o construtor + barDep: BarDependent # LANÇARÁ EXCEÇÃO, nenhum serviço corresponde + parentDep: ParentDependent # autowiring passa child para o construtor + childDep: ChildDependent # autowiring passa child para o construtor ``` diff --git a/dependency-injection/pt/configuration.texy b/dependency-injection/pt/configuration.texy index e81ca1b571..e22c943046 100644 --- a/dependency-injection/pt/configuration.texy +++ b/dependency-injection/pt/configuration.texy @@ -1,32 +1,33 @@ -Configuração do DI Container +Configuração do Contêiner DI **************************** .[perex] Visão geral das opções de configuração para o contêiner Nette DI. -Arquivo de configuração +Arquivo de Configuração ======================= -O recipiente Nette DI é fácil de controlar utilizando arquivos de configuração. Eles são geralmente escritos no [formato NEON |neon:format]. Recomendamos utilizar [editores com suporte |best-practices:editors-and-tools#ide-editor] para este formato para edição. +O contêiner Nette DI é facilmente controlado por meio de arquivos de configuração. Eles geralmente são escritos no [formato NEON|neon:format]. Para edição, recomendamos [editores com suporte |best-practices:editors-and-tools#Editor IDE] para este formato. <pre> -"decorator .[prism-token prism-atrule]":[#Decorator]: "Decorator .[prism-token prism-comment]"<br> -"di .[prism-token prism-atrule]":[#DI]: "DI Container .[prism-token prism-comment]"<br> -"extensions .[prism-token prism-atrule]":[#Extensions]: "Instalar extensões DI adicionais .[prism-token prism-comment]"<br> -"includes .[prism-token prism-atrule]":[#Including files]: "Incluindo arquivos .[prism-token prism-comment]"<br> -"parameters .[prism-token prism-atrule]":[#Parameters]: "Parâmetros .[prism-token prism-comment]"<br> -"search .[prism-token prism-atrule]":[#Search]: "Registro automático de serviço .[prism-token prism-comment]"<br> +"decorator .[prism-token prism-atrule]":[#decorator]: "Decorador .[prism-token prism-comment]"<br> +"di .[prism-token prism-atrule]":[#DI]: "Contêiner DI .[prism-token prism-comment]"<br> +"extensions .[prism-token prism-atrule]":[#Extensões]: "Instalação de extensões DI adicionais .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[#Inclusão de arquivos]: "Inclusão de arquivos .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[#Parâmetros]: "Parâmetros .[prism-token prism-comment]"<br> +"search .[prism-token prism-atrule]":[#Search]: "Registro automático de serviços .[prism-token prism-comment]"<br> "services .[prism-token prism-atrule]":[services]: "Serviços .[prism-token prism-comment]" </pre> -Para escrever um texto contendo o caracter `%`, you must escape it by doubling it to `%%`. .[note] +.[note] +Para escrever uma string contendo o caractere `%`, você deve escapá-lo duplicando-o para `%%`. -Parâmetros .[#toc-parameters] -============================= +Parâmetros +========== -Você pode definir parâmetros que podem então ser usados como parte das definições de serviço. Isto pode ajudar a separar valores que você desejará alterar mais regularmente. +Na configuração, você pode definir parâmetros que podem ser usados como parte das definições de serviço. Isso pode tornar a configuração mais clara ou unificar e extrair valores que serão alterados. ```neon parameters: @@ -35,9 +36,9 @@ parameters: password: secret ``` -Você pode consultar o parâmetro `foo` via `%foo%` em qualquer outro lugar em qualquer arquivo de configuração. Eles também podem ser usados dentro de cordas como `'%wwwDir%/images'`. +Referimo-nos ao parâmetro `dsn` em qualquer lugar na configuração escrevendo `%dsn%`. Os parâmetros também podem ser usados dentro de strings como `'%wwwDir%/images'`. -Os parâmetros não precisam ser apenas cordas, eles também podem ser valores de matriz: +Os parâmetros não precisam ser apenas strings ou números, eles também podem conter arrays: ```neon parameters: @@ -48,32 +49,32 @@ parameters: languages: [cs, en, de] ``` -Você pode consultar a chave única como `%mailer.user%`. +Referimo-nos a uma chave específica como `%mailer.user%`. -Se você precisar obter o valor de qualquer parâmetro em seu código, por exemplo, em sua classe, então passe-o para esta classe. Por exemplo, no construtor. Não há nenhum objeto de configuração global que as classes possam consultar para valores de parâmetros. Isto seria contrário ao princípio de injeção de dependência. +Se você precisar descobrir o valor de qualquer parâmetro em seu código, por exemplo, em uma classe, passe-o para essa classe. Por exemplo, no construtor. Não existe um objeto global representando a configuração que as classes consultariam para obter valores de parâmetros. Isso violaria o princípio da injeção de dependência. -Serviços .[#toc-services] -========================= +Serviços +======== -Ver [capítulo separado |services]. +Veja [capítulo separado|services]. -Decorador .[#toc-decorator] -=========================== +Decorator +========= -Como editar em massa todos os serviços de um determinado tipo? Precisa chamar um certo método para todos os apresentadores herdados de um determinado antepassado comum? É de lá que vem o decorador. +Como modificar em massa todos os serviços de um determinado tipo? Por exemplo, chamar um determinado método em todos os presenters que herdam de um ancestral comum específico? É para isso que serve o decorator. ```neon decorator: # para todos os serviços que são instâncias desta classe ou interface - App\Presenters\BasePresenter: + App\Presentation\BasePresenter: setup: - - setProjectId(10) # chamar este método - - $absoluteUrls = true # e definir a variável + - setProjectId(10) # chame este método + - $absoluteUrls = true # e defina a variável ``` -O decorador também pode ser usado para definir [etiquetas |services#Tags] ou ligar o [modo de injeção |services#Inject Mode]. +O decorator também pode ser usado para definir [tags |services#Tags] ou ativar o modo [inject |services#Modo Inject]. ```neon decorator: @@ -86,67 +87,79 @@ decorator: DI === -Configurações técnicas do recipiente DI. +Configurações técnicas do contêiner DI. ```neon di: - # mostra DIC em Tracy Bar? - debugger: ... # (bool) não é verdadeiro + # exibir DIC na Tracy Bar? + debugger: ... # (bool) padrão é true - # tipos de parâmetros que você nunca faz fiação automática + # tipos de parâmetros que nunca devem ser autowired excluded: ... # (string[]) - # a classe da qual o recipiente DI herda - parentClass: ... # (string) defaults to Nette\DI\Container + # permitir criação lazy de serviços? + lazy: ... # (bool) padrão é false + + # classe da qual o contêiner DI herda + parentClass: ... # (string) padrão é Nette\DI\Container ``` -Exportação de Metadados .[#toc-metadata-export] ------------------------------------------------ +Serviços Lazy .{data-version:3.2.4} +----------------------------------- + +A configuração `lazy: true` ativa a criação lazy (adiada) de serviços. Isso significa que os serviços não são realmente criados no momento em que os solicitamos do contêiner DI, mas apenas no momento de seu primeiro uso. Isso pode acelerar o início da aplicação e reduzir o consumo de memória, pois apenas os serviços que são realmente necessários na requisição atual são criados. + +Para um serviço específico, a criação lazy pode ser [alterada |services#Serviços Lazy]. + +.[note] +Objetos lazy só podem ser usados para classes de usuário, não para classes internas do PHP. Requer PHP 8.4 ou posterior. + -A classe de contêineres DI também contém um monte de metadados. Você pode reduzi-la reduzindo a exportação de metadados. +Exportação de metadados +----------------------- + +A classe do contêiner DI também contém muitos metadados. Você pode reduzi-la reduzindo a exportação de metadados. ```neon di: export: - # para parâmetros de exportação? - parameters: falso # (bool) padrão para verdadeiro + # exportar parâmetros? + parameters: false # (bool) padrão é true - # etiquetas de exportação e quais? - tags: # (string[]|bool) o padrão é tudo + # exportar tags e quais? + tags: # (string[]|bool) padrão são todas - event.subscriber - # dados de exportação para fiação automática e quais? - types: # (string[]|bool) o padrão é tudo + # exportar dados para autowiring e quais? + types: # (string[]|bool) padrão são todas - Nette\Database\Connection - Symfony\Component\Console\Application ``` -Se você não usar a matriz `$container->parameters`, você pode desativar a exportação de parâmetros. Além disso, você pode exportar somente as tags através das quais você obtém serviços usando o método `$container->findByTag(...)`. -Se você não chamar o método, você pode desabilitar completamente a exportação de tags com `false`. +Se você não usa o array `$container->getParameters()`, pode desativar a exportação de parâmetros. Além disso, você pode exportar apenas as tags pelas quais obtém serviços usando o método `$container->findByTag(...)`. Se você não chamar o método, pode desativar completamente a exportação de tags usando `false`. -Você pode reduzir significativamente os metadados para a [fiação automática |autowiring] especificando as classes que você usa como parâmetro para o método `$container->getByType()`. -E novamente, se você não chamar o método (ou apenas na [application:bootstrap] para obter `Nette\Application\Application`), você pode desativar a exportação inteiramente com `false`. +Você pode reduzir significativamente os metadados para [autowiring] especificando as classes que você usa como parâmetro do método `$container->getByType()`. E novamente, se você não chamar o método (respectivamente, apenas no [bootstrap|application:bootstrapping] para obter `Nette\Application\Application`), pode desativar completamente a exportação usando `false`. -Extensões .[#toc-extensions] -============================ +Extensões +========= -Registro de outras extensões de DI. Desta forma, acrescentamos, por exemplo, a extensão DI `Dibi\Bridges\Nette\DibiExtension22` sob o nome `dibi`: +Registro de extensões DI adicionais. Desta forma, adicionamos, por exemplo, a extensão DI `Dibi\Bridges\Nette\DibiExtension22` sob o nome `dibi` ```neon extensions: dibi: Dibi\Bridges\Nette\DibiExtension22 ``` -Em seguida, configuramos na sua seção também chamada `dibi`: +Posteriormente, a configuramos na seção `dibi`: ```neon dibi: host: localhost ``` -Você também pode adicionar uma classe de extensão com parâmetros: +Também é possível adicionar uma classe que tem parâmetros como extensão: ```neon extensions: @@ -154,10 +167,10 @@ extensions: ``` -Incluindo Arquivos .[#toc-including-files] -========================================== +Inclusão de arquivos +==================== -Arquivos de configuração adicionais podem ser inseridos na seção `includes`: +Podemos incluir outros arquivos de configuração na seção `includes`: ```neon includes: @@ -166,7 +179,7 @@ includes: - presenters.neon ``` -O nome `parameters.php` não é um erro de digitação, a configuração também pode ser escrita em um arquivo PHP, que a devolve como um array: +O nome `parameters.php` não é um erro de digitação, a configuração também pode ser escrita em um arquivo PHP, que a retorna como um array: ```php <?php @@ -179,32 +192,27 @@ return [ ]; ``` -Se itens com as mesmas chaves aparecerem dentro dos arquivos de configuração, eles serão [sobrescritos ou fundidos |#Merging] no caso de arrays. O arquivo incluído posteriormente tem uma prioridade mais alta do que o anterior. O arquivo no qual a seção `includes` é listada tem uma prioridade mais alta do que os arquivos incluídos nela. +Se elementos com as mesmas chaves aparecerem em vários arquivos de configuração, eles serão sobrescritos ou, no caso de [arrays, mesclados |#Mesclagem]. O arquivo incluído posteriormente tem prioridade maior que o anterior. O arquivo no qual a seção `includes` está listada tem prioridade maior que os arquivos incluídos nele. -Busca .[#toc-search] -==================== +Search +====== -A adição automática de serviços ao contêiner DI torna o trabalho extremamente agradável. A Nette adiciona automaticamente apresentadores ao contêiner, mas você pode adicionar facilmente qualquer outra classe. +A adição automática de serviços ao contêiner DI torna o trabalho extremamente agradável. Nette adiciona automaticamente presenters ao contêiner, mas também é fácil adicionar quaisquer outras classes. -Basta especificar em quais diretórios (e subdiretórios) as classes devem ser pesquisadas: +Basta especificar em quais diretórios (e subdiretórios) as classes devem ser procuradas: ```neon search: - # você mesmo escolhe os nomes das seções - myForms: - em: %appDir%/Forms - - model: - em: %appDir%/Modelo + - in: %appDir%/Forms + - in: %appDir%/Model ``` -Normalmente, porém, não queremos acrescentar todas as classes e interfaces, para que possamos filtrá-las: +No entanto, geralmente não queremos adicionar absolutamente todas as classes e interfaces, por isso podemos filtrá-las: ```neon search: - myForms: - in: %appDir%/Forms + - in: %appDir%/Forms # filtragem por nome de arquivo (string|string[]) files: @@ -215,42 +223,43 @@ search: - *Factory ``` -Ou podemos selecionar classes que herdam ou implementam pelo menos uma das seguintes classes: +Ou podemos selecionar classes que herdam ou implementam pelo menos uma das classes listadas: ```neon search: - myForms: + - in: %appDir% extends: - App\*Form implements: - App\*FormInterface ``` -Você também pode definir regras negativas, ou seja, máscaras de nome de classe ou antepassados e, se elas estiverem de acordo, o serviço não será adicionado ao recipiente DI: +Também é possível definir regras de exclusão, ou seja, máscaras de nome de classe ou ancestrais herdados, que, se corresponderem, o serviço não será adicionado ao contêiner DI: ```neon search: - myForms: + - in: %appDir% exclude: + files: ... classes: ... extends: ... implements: ... ``` -As etiquetas podem ser definidas para serviços adicionais: +Tags podem ser definidas para todos os serviços: ```neon search: - myForms: + - in: %appDir% tags: ... ``` -Fusão .[#toc-merging] -===================== +Mesclagem +========= -Se itens com as mesmas chaves aparecerem em mais arquivos de configuração, eles serão sobrescritos ou fundidos no caso de arrays. O arquivo incluído posteriormente tem uma prioridade mais alta. +Se elementos com as mesmas chaves aparecerem em vários arquivos de configuração, eles serão sobrescritos ou, no caso de arrays, mesclados. O arquivo incluído posteriormente tem prioridade maior que o anterior. <table class=table> <tr> @@ -283,7 +292,7 @@ items: </tr> </table> -Para evitar a fusão de uma determinada matriz, use o ponto de exclamação logo após o nome da matriz: +Para arrays, a mesclagem pode ser evitada adicionando um ponto de exclamação após o nome da chave: <table class=table> <tr> @@ -313,3 +322,5 @@ items: </td> </tr> </table> + +{{maintitle: Configuração de Injeção de Dependência}} diff --git a/dependency-injection/pt/container.texy b/dependency-injection/pt/container.texy index e114c4a31d..fff5f871d2 100644 --- a/dependency-injection/pt/container.texy +++ b/dependency-injection/pt/container.texy @@ -1,16 +1,16 @@ -O que é DI Container? -********************* +O que é um Contêiner DI? +************************ .[perex] -O recipiente de injeção de dependência (DIC) é uma classe que pode instanciar e configurar objetos. +Um contêiner de injeção de dependência (DIC) é uma classe que pode instanciar e configurar objetos. -Pode surpreendê-lo, mas em muitos casos você não precisa de um recipiente de injeção de dependência para tirar proveito da injeção de dependência (DI para abreviar). Afinal de contas, mesmo no [capítulo anterior |introduction] mostramos exemplos específicos de DI e nenhum recipiente era necessário. +Pode surpreendê-lo, mas em muitos casos, você não precisa de um contêiner de injeção de dependência para aproveitar os benefícios da injeção de dependência (DI para abreviar). Afinal, mesmo no [capítulo introdutório|introduction], mostramos DI com exemplos concretos e nenhum contêiner foi necessário. -Entretanto, se você precisar gerenciar um grande número de objetos diferentes com muitas dependências, um recipiente de injeção de dependência será realmente útil. O que talvez seja o caso de aplicações web construídas sobre uma estrutura. +No entanto, se você precisar gerenciar um grande número de objetos diferentes com muitas dependências, um contêiner de injeção de dependência será realmente útil. É o caso, por exemplo, de aplicações web construídas sobre um framework. -No capítulo anterior, introduzimos as classes `Article` e `UserController`. Ambas têm algumas dependências, a saber, banco de dados e fábrica `ArticleFactory`. E para estas classes, vamos agora criar um container. Claro que, para um exemplo tão simples, não faz sentido ter um contêiner. Mas vamos criar um para mostrar como ele parece e funciona. +No capítulo anterior, apresentamos as classes `Article` e `UserController`. Ambas têm algumas dependências, nomeadamente o banco de dados e a fábrica `ArticleFactory`. E para essas classes, agora criaremos um contêiner. Claro, para um exemplo tão simples, não faz sentido ter um contêiner. Mas vamos criá-lo para mostrar como ele se parece e funciona. -Aqui está um simples recipiente de código rígido para o exemplo acima: +Aqui está um contêiner simples hardcoded para o exemplo dado: ```php class Container @@ -32,16 +32,16 @@ class Container } ``` -O uso teria este aspecto: +O uso seria o seguinte: ```php $container = new Container; $controller = $container->createUserController(); ``` -Nós apenas perguntamos ao recipiente pelo objeto e não precisamos mais saber nada sobre como criá-lo ou quais são suas dependências; o recipiente sabe tudo isso. As dependências são injetadas automaticamente pelo contêiner. Esse é o seu poder. +Apenas pedimos ao contêiner o objeto e não precisamos mais saber nada sobre como criá-lo ou quais são suas dependências; o contêiner sabe tudo isso. As dependências são injetadas automaticamente pelo contêiner. Essa é a sua força. -Até agora, o contêiner tem tudo codificado de forma rígida. Portanto, damos o próximo passo e adicionamos parâmetros para tornar o contêiner realmente útil: +Por enquanto, o contêiner tem todos os dados codificados. Daremos o próximo passo e adicionaremos parâmetros para tornar o contêiner realmente útil: ```php class Container @@ -70,9 +70,9 @@ $container = new Container([ ]); ``` -Os leitores astutos podem ter notado um problema. Toda vez que eu recebo um objeto `UserController`, uma nova instância `ArticleFactory` e um banco de dados também é criado. Definitivamente, nós não queremos isso. +Leitores atentos podem ter notado um certo problema. Toda vez que obtenho um objeto `UserController`, uma nova instância de `ArticleFactory` e do banco de dados também é criada. Definitivamente não queremos isso. -Assim, acrescentamos um método `getService()` que retornará as mesmas instâncias repetidas vezes: +Portanto, adicionaremos um método `getService()` que sempre retornará as mesmas instâncias: ```php class Container @@ -87,7 +87,7 @@ class Container public function getService(string $name): object { if (!isset($this->services[$name])) { - // getService('Database') chamadas createDatabase() + // getService('Database') chamará createDatabase() $method = 'create' . $name; $this->services[$name] = $this->$method(); } @@ -98,9 +98,9 @@ class Container } ``` -A primeira chamada para, por exemplo, `$container->getService('Database')` terá `createDatabase()` criar um objeto de banco de dados, que será armazenado na matriz `$services` e devolvido diretamente na próxima chamada. +Na primeira chamada, por exemplo, `$container->getService('Database')`, ele fará com que `createDatabase()` crie o objeto do banco de dados, que ele armazena no array `$services`, e na próxima chamada, ele o retorna diretamente. -Também modificamos o restante do contêiner para usar `getService()`: +Também modificaremos o restante do contêiner para usar `getService()`: ```php class Container @@ -119,9 +119,9 @@ class Container } ``` -A propósito, o termo serviço se refere a qualquer objeto administrado pelo contêiner. Daí o nome do método `getService()`. +A propósito, o termo serviço refere-se a qualquer objeto gerenciado pelo contêiner. É por isso que o método se chama `getService()`. -Feito. Temos um container DI totalmente funcional! E podemos utilizá-lo: +Feito. Temos um contêiner DI totalmente funcional! E podemos usá-lo: ```php $container = new Container([ @@ -134,6 +134,9 @@ $controller = $container->getService('UserController'); $database = $container->getService('Database'); ``` -Como você pode ver, não é difícil escrever um DIC. É notável que os próprios objetos não sabem que um recipiente os está criando. Assim, é possível criar qualquer objeto em PHP desta forma sem afetar seu código fonte. +Como você pode ver, escrever um DIC não é complicado. Vale a pena notar que os próprios objetos não sabem que estão sendo criados por algum contêiner. Assim, é possível criar qualquer objeto em PHP dessa forma sem interferir em seu código-fonte. -A criação e manutenção manual de uma classe de contêineres pode se tornar um pesadelo bastante rápido. Portanto, no próximo capítulo falaremos sobre o [Nette DI Container |nette-container], que pode gerar e se atualizar quase que automaticamente. +Criar e manter manualmente a classe do contêiner pode se tornar rapidamente um pesadelo. Portanto, no próximo capítulo, falaremos sobre o [Nette DI Container|nette-container], que pode se gerar e atualizar quase sozinho. + + +{{maintitle: O que é um contêiner de injeção de dependência?}} diff --git a/dependency-injection/pt/extensions.texy b/dependency-injection/pt/extensions.texy index 6d38a69bf3..a0fa7b614e 100644 --- a/dependency-injection/pt/extensions.texy +++ b/dependency-injection/pt/extensions.texy @@ -2,38 +2,38 @@ Criação de extensões para Nette DI ********************************** .[perex] -A geração de um recipiente DI, além dos arquivos de configuração, também afeta os chamados *extensões*. Nós as ativamos no arquivo de configuração na seção `extensions`. +A geração do contêiner DI, além dos arquivos de configuração, também é influenciada pelas chamadas *extensões*. Nós as ativamos no arquivo de configuração na seção `extensions`. -É assim que adicionamos a extensão representada pela classe `BlogExtension` com o nome `blog`: +Desta forma, adicionamos a extensão representada pela classe `BlogExtension` sob o nome `blog`: ```neon extensions: blog: BlogExtension ``` -Cada extensão do compilador herda de [api:Nette\DI\CompilerExtension] e pode implementar os seguintes métodos que são chamados durante a compilação DI: +Cada extensão do compilador herda de [api:Nette\DI\CompilerExtension] e pode implementar os seguintes métodos, que são chamados sequencialmente durante a construção do contêiner DI: 1. getConfigSchema() -2. cargaConfiguração() -3. Antes da Compilação() -4. depois da Compilação() +2. loadConfiguration() +3. beforeCompile() +4. afterCompile() getConfigSchema() .[method] =========================== -Este método é chamado primeiro. Ele define o esquema utilizado para validar os parâmetros de configuração. +Este método é chamado primeiro. Ele define o schema para validação dos parâmetros de configuração. -As extensões são configuradas em uma seção cujo nome é o mesmo que aquele sob o qual a extensão foi adicionada, por exemplo `blog`. +Configuramos a extensão na seção cujo nome é o mesmo sob o qual a extensão foi adicionada, ou seja, `blog`: ```neon -# same name as my extension +# mesmo nome da extensão blog: postsPerPage: 10 - comments: false + allowComments: false ``` -Definiremos um esquema descrevendo todas as opções de configuração, incluindo seus tipos, valores aceitos e possivelmente valores padrão: +Criamos um schema descrevendo todas as opções de configuração, incluindo seus tipos, valores permitidos e, opcionalmente, valores padrão: ```php use Nette\Schema\Expect; @@ -50,16 +50,16 @@ class BlogExtension extends Nette\DI\CompilerExtension } ``` -Consulte o [Esquema |schema:] para documentação. Além disso, você pode especificar quais opções podem ser [dinâmicas |application:bootstrap#Dynamic Parameters] usando `dynamic()`, por exemplo `Expect::int()->dynamic()`. +A documentação pode ser encontrada na página [Schema |schema:]. Além disso, pode-se especificar quais opções podem ser [dinâmicas |application:bootstrapping#Parâmetros dinâmicos] usando `dynamic()`, např. `Expect::int()->dynamic()`. -Acessamos a configuração através do `$this->config`, que é um objeto `stdClass`: +Acessamos a configuração através da variável `$this->config`, que é um objeto `stdClass`: ```php class BlogExtension extends Nette\DI\CompilerExtension { public function loadConfiguration() { - $num = $this->config->postPerPage; + $num = $this->config->postsPerPage; if ($this->config->allowComments) { // ... } @@ -71,7 +71,7 @@ class BlogExtension extends Nette\DI\CompilerExtension loadConfiguration() .[method] ============================= -Este método é usado para adicionar serviços ao contêiner. Isto é feito por [api:Nette\DI\ContainerBuilder]: +Usado para adicionar serviços ao contêiner. Para isso, serve a [api:Nette\DI\ContainerBuilder]: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -80,25 +80,25 @@ class BlogExtension extends Nette\DI\CompilerExtension { $builder = $this->getContainerBuilder(); $builder->addDefinition($this->prefix('articles')) - ->setFactory(App\Model\HomepageArticles::class, ['@connection']) // or setCreator() + ->setFactory(App\Model\HomepageArticles::class, ['@connection']) // ou setCreator() ->addSetup('setLogger', ['@logger']); } } ``` -A convenção é para prefixar os serviços acrescentados por uma extensão com seu nome, para que não surjam conflitos de nome. Isto é feito por `prefix()`, portanto, se a extensão for chamada de 'blog', o serviço será chamado `blog.articles`. +A convenção é prefixar os serviços adicionados pela extensão com seu nome, para que não ocorram conflitos de nomes. O método `prefix()` faz isso, então se a extensão se chama `blog`, o serviço será nomeado `blog.articles`. -Se precisarmos renomear um serviço, podemos criar um pseudônimo com seu nome original para manter a retrocompatibilidade. Da mesma forma, é o que a Nette faz por exemplo `routing.router`, que também está disponível sob o nome anterior `router`. +Se precisarmos renomear um serviço, podemos, para manter a compatibilidade retroativa, criar um alias com o nome original. A Nette faz algo semelhante, por exemplo, com o serviço `routing.router`, que também está disponível sob o nome anterior `router`. ```php $builder->addAlias('router', 'routing.router'); ``` -Recuperar serviços de um arquivo .[#toc-retrieve-services-from-a-file] ----------------------------------------------------------------------- +Carregamento de serviços de um arquivo +-------------------------------------- -Podemos criar serviços utilizando o ContainerBuilder API, mas também podemos adicioná-los através do familiar arquivo de configuração NEON e sua seção `services`. O prefixo `@extension` representa a extensão atual. +Não precisamos criar serviços apenas usando a API da classe ContainerBuilder, mas também com a notação familiar usada no arquivo de configuração NEON na seção services. O prefixo `@extension` representa a extensão atual. ```neon services: @@ -112,7 +112,7 @@ services: create: MyBlog\Components\ArticlesList(@extension.articles) ``` -Desta forma, acrescentaremos serviços: +Carregamos os serviços: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -121,7 +121,7 @@ class BlogExtension extends Nette\DI\CompilerExtension { $builder = $this->getContainerBuilder(); - // carregar o arquivo de configuração para a extensão + // carregamento do arquivo de configuração para a extensão $this->compiler->loadDefinitionsFromConfig( $this->loadFromFile(__DIR__ . '/blog.neon')['services'], ); @@ -133,7 +133,7 @@ class BlogExtension extends Nette\DI\CompilerExtension beforeCompile() .[method] ========================= -O método é chamado quando o contêiner contém todos os serviços adicionados por extensões individuais em `loadConfiguration` métodos, bem como arquivos de configuração do usuário. Nesta fase de montagem, podemos então modificar as definições dos serviços ou adicionar links entre eles. Você pode usar o método `findByTag()` para procurar serviços por tags, ou o método `findByType()` para procurar por classe ou interface. +O método é chamado no momento em que o contêiner contém todos os serviços adicionados pelas extensões individuais nos métodos `loadConfiguration` e também pelos arquivos de configuração do usuário. Nesta fase de construção, podemos, portanto, modificar as definições de serviço ou adicionar ligações entre eles. Para pesquisar serviços no contêiner por tags, pode-se usar o método `findByTag()`, por classe ou interface, o método `findByType()`. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -153,7 +153,7 @@ class BlogExtension extends Nette\DI\CompilerExtension afterCompile() .[method] ======================== -Nesta fase, a classe container já é gerada como um objeto [ClassType |php-generator:#classes], ela contém todos os métodos que o serviço cria, e está pronta para o caching como arquivo PHP. Neste ponto, ainda podemos editar o código da classe resultante. +Nesta fase, a classe do contêiner já está gerada na forma de um objeto [ClassType |php-generator:#Classes], contém todos os métodos que criam serviços e está pronta para ser escrita no cache. O código resultante da classe ainda pode ser modificado neste momento. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -167,24 +167,24 @@ class BlogExtension extends Nette\DI\CompilerExtension ``` -$inicialização .[wiki-method] -============================= +$initialization .[method] +========================= -O Configurador chama o código de inicialização após a [criação do recipiente |application:bootstrap#index.php], que é criado escrevendo para um objeto `$this->initialization` usando [o método addBody() |php-generator:#method-and-function-body]. +A classe Configurator, após [criar o contêiner |application:bootstrapping#index.php], chama o código de inicialização, que é criado escrevendo no objeto `$this->initialization` usando o [método addBody() |php-generator:#Corpos de métodos e funções]. -Mostraremos um exemplo de como iniciar uma sessão ou iniciar serviços que tenham a tag `run` usando o código de inicialização: +Mostraremos um exemplo de como, por exemplo, iniciar a sessão com o código de inicialização ou iniciar serviços que têm a tag `run`: ```php class BlogExtension extends Nette\DI\CompilerExtension { public function loadConfiguration() { - // inicialização automática da sessão + // início automático da sessão if ($this->config->session->autoStart) { $this->initialization->addBody('$this->getService("session")->start()'); } - // serviços com etiqueta 'run' devem ser criados depois que o container for instanciado + // serviços com a tag run devem ser criados após a instanciação do contêiner $builder = $this->getContainerBuilder(); foreach ($builder->findByTag('run') as $name => $foo) { $this->initialization->addBody('$this->getService(?);', [$name]); diff --git a/dependency-injection/pt/factory.texy b/dependency-injection/pt/factory.texy index 4ded195f1e..7d64f484c7 100644 --- a/dependency-injection/pt/factory.texy +++ b/dependency-injection/pt/factory.texy @@ -2,11 +2,11 @@ Fábricas Geradas **************** .[perex] -Nette DI pode gerar automaticamente o código de fábrica com base na interface, o que poupa você de escrever o código. +A Nette DI pode gerar automaticamente código de fábricas com base em interfaces, o que economiza a escrita de código. -Uma fábrica é uma classe que cria e configura objetos. Portanto, ela passa suas dependências também para eles. Por favor, não confunda com o padrão de design *método de fábrica*, que descreve uma maneira específica de usar as fábricas e não está relacionado a este tópico. +Uma fábrica é uma classe que produz e configura objetos. Portanto, ela também passa suas dependências para eles. Por favor, não confunda com o padrão de projeto *factory method*, que descreve uma maneira específica de usar fábricas e não está relacionado a este tópico. -Mostramos como é uma fábrica desse tipo no [capítulo introdutório |introduction#factory]: +Mostramos como é uma fábrica no [capítulo introdutório |introduction#Fábrica]: ```php class ArticleFactory @@ -23,7 +23,7 @@ class ArticleFactory } ``` -A Nette DI pode gerar código de fábrica automaticamente. Tudo que você precisa fazer é criar uma interface e a Nette DI irá gerar uma implementação. A interface deve ter exatamente um método chamado `create` e declarar um tipo de retorno: +A Nette DI pode gerar automaticamente o código das fábricas. Tudo o que você precisa fazer é criar uma interface e a Nette DI gerará a implementação. A interface deve ter exatamente um método chamado `create` e declarar o tipo de retorno: ```php interface ArticleFactory @@ -32,7 +32,7 @@ interface ArticleFactory } ``` -Assim, a fábrica `ArticleFactory` tem um método `create` que cria objetos `Article`. A classe `Article` pode se parecer, por exemplo, com a seguinte: +Ou seja, a fábrica `ArticleFactory` tem um método `create` que cria objetos `Article`. A classe `Article` pode se parecer com o seguinte: ```php class Article @@ -44,16 +44,16 @@ class Article } ``` -Acrescentar a fábrica ao arquivo de configuração: +Adicionamos a fábrica ao arquivo de configuração: ```neon services: - ArticleFactory ``` -Nette DI irá gerar a implementação da fábrica correspondente. +A Nette DI gerará a implementação correspondente da fábrica. -Assim, no código que utiliza a fábrica, solicitamos o objeto por interface e a Nette DI utiliza a implementação gerada: +No código que usa a fábrica, solicitamos o objeto pela interface e a Nette DI usará a implementação gerada: ```php class UserController @@ -65,17 +65,17 @@ class UserController public function foo() { - // deixar a fábrica criar um objeto + // deixamos a fábrica criar o objeto $article = $this->articleFactory->create(); } } ``` -Fábrica parametrizada .[#toc-parameterized-factory] -=================================================== +Fábrica Parametrizada +===================== -O método de fábrica `create` pode aceitar parâmetros que depois passa para o construtor. Por exemplo, vamos adicionar uma identificação do autor do artigo à classe `Article`: +O método da fábrica `create` pode aceitar parâmetros, que são então passados para o construtor. Vamos adicionar, por exemplo, o ID do autor do artigo à classe `Article`: ```php class Article @@ -88,7 +88,7 @@ class Article } ``` -Acrescentaremos também o parâmetro à fábrica: +Também adicionamos o parâmetro à fábrica: ```php interface ArticleFactory @@ -97,13 +97,13 @@ interface ArticleFactory } ``` -Como o parâmetro no construtor e o parâmetro na fábrica têm o mesmo nome, a Nette DI os passará automaticamente. +Graças ao fato de que o parâmetro no construtor e o parâmetro na fábrica têm o mesmo nome, a Nette DI os passa de forma totalmente automática. -Definição avançada .[#toc-advanced-definition] -============================================== +Definição Avançada +================== -A definição também pode ser escrita em várias linhas usando a chave `implement`: +A definição também pode ser escrita em formato de múltiplas linhas usando a chave `implement`: ```neon services: @@ -111,9 +111,9 @@ services: implement: ArticleFactory ``` -Ao escrever desta forma mais longa, é possível fornecer argumentos adicionais para o construtor na chave `arguments` e configuração adicional usando `setup`, assim como para serviços normais. +Ao escrever desta forma mais longa, é possível especificar argumentos adicionais para o construtor na chave `arguments` e configuração adicional usando `setup`, assim como nos serviços comuns. -Exemplo: se o método `create()` não aceitasse o parâmetro `$authorId`, poderíamos especificar um valor fixo na configuração que seria passado para o construtor `Article`: +Exemplo: se o método `create()` não aceitasse o parâmetro `$authorId`, poderíamos especificar um valor fixo na configuração, que seria passado para o construtor de `Article`: ```neon services: @@ -123,7 +123,7 @@ services: authorId: 123 ``` -Ou, inversamente, se `create()` aceitasse o parâmetro `$authorId` mas não fizesse parte do construtor e fosse aprovado pelo método `Article::setAuthorId()`, referir-nos-íamos a ele na seção `setup`: +Ou, inversamente, se `create()` aceitasse o parâmetro `$authorId`, mas ele não fizesse parte do construtor e fosse passado pelo método `Article::setAuthorId()`, faríamos referência a ele na seção `setup`: ```neon services: @@ -134,15 +134,14 @@ services: ``` -Accessor .[#toc-accessor] -========================= +Accessor +======== -Além das fábricas, a Nette também pode gerar os chamados acessores. O acessor é um objeto com método `get()` devolvendo um determinado serviço a partir do contêiner DI. Várias chamadas `get()` retornarão sempre a mesma instância. +Além das fábricas, a Nette também pode gerar os chamados accessors. São objetos com um método `get()`, que retorna um determinado serviço do contêiner DI. Chamadas repetidas de `get()` retornam sempre a mesma instância. -Os acessores trazem a carga preguiçosa para as dependências. Vamos ter um registro de erros de classe em um banco de dados especial. Se a conexão de banco de dados fosse passada como uma dependência em seu construtor, a conexão precisaria ser sempre criada, embora só fosse usada raramente quando um erro aparecesse, de modo que a conexão permaneceria, na maioria das vezes, sem uso. -Em vez disso, a classe pode passar por um acessor e quando seu método `get()` é chamado, somente então o objeto banco de dados é criado: +Os accessors fornecem carregamento preguiçoso (lazy-loading) para dependências. Considere uma classe que registra erros em um banco de dados especial. Se essa classe recebesse a conexão com o banco de dados como dependência via construtor, a conexão sempre teria que ser criada, embora na prática um erro ocorra apenas excepcionalmente e, portanto, na maioria das vezes a conexão permaneceria inutilizada. Em vez disso, a classe recebe um accessor e somente quando seu `get()` é chamado, o objeto do banco de dados é criado: -Como criar um acessor? Escreva apenas uma interface e a Nette DI irá gerar a implementação. A interface deve ter exatamente um método chamado `get` e deve declarar o tipo de retorno: +Como criar um accessor? Basta escrever uma interface e a Nette DI gerará a implementação. A interface deve ter exatamente um método chamado `get` e declarar o tipo de retorno: ```php interface PDOAccessor @@ -151,7 +150,7 @@ interface PDOAccessor } ``` -Adicione o acessor ao arquivo de configuração junto com a definição do serviço que o acessor retornará: +Adicionamos o accessor ao arquivo de configuração, onde também está a definição do serviço que ele retornará: ```neon services: @@ -159,63 +158,69 @@ services: - PDO(%dsn%, %user%, %password%) ``` -O acessor devolve um serviço do tipo `PDO` e como só existe um serviço desse tipo na configuração, o acessor o devolverá. Com vários serviços desse tipo configurados, você pode especificar qual deles deve ser devolvido usando seu nome, por exemplo `- PDOAccessor(@db1)`. +Como o accessor retorna um serviço do tipo `PDO` e há apenas um serviço desse tipo na configuração, ele retornará exatamente esse. Se houvesse mais serviços do tipo fornecido, especificaríamos o serviço retornado usando o nome, por exemplo, `- PDOAccessor(@db1)`. -Multifábrica/Acessor .[#toc-multifactory-accessor] -================================================== -Até agora, as fábricas e os acessores só podiam criar ou devolver um único objeto. Uma multifábrica combinada com um acessório também pode ser criada. A interface de tal classe multifatorial pode consistir de múltiplos métodos chamados `create<name>()` e `get<name>()`por exemplo: +Fábrica/Accessor Múltiplo +========================= +Nossas fábricas e accessors até agora só podiam produzir ou retornar um objeto. No entanto, é muito fácil criar também fábricas múltiplas combinadas сom accessors. A interface de tal classe conterá qualquer número de métodos com os nomes `create<name>()` e `get<name>()`, por exemplo: ```php interface MultiFactory { function createArticle(): Article; - function createFoo(): Model\Foo; function getDb(): PDO; } ``` -Em vez de passar por múltiplas fábricas e acessores gerados, você pode passar apenas por uma complexa multifábrica. +Então, em vez de passar várias fábricas e accessors gerados, passamos uma fábrica mais complexa que pode fazer mais. -Alternativamente, você pode usar `create()` e `get()` com um parâmetro ao invés de múltiplos métodos: +Alternativamente, em vez de vários métodos, pode-se usar `get()` сom um parâmetro: ```php interface MultiFactoryAlt { - function create($name); function get($name): PDO; } ``` -Neste caso, `MultiFactory::createArticle()` faz a mesma coisa que `MultiFactoryAlt::create('article')`. No entanto, a sintaxe alternativa tem algumas desvantagens. Não está claro quais valores `$name` são suportados e o tipo de retorno não pode ser especificado na interface quando se utiliza vários valores diferentes do `$name`. +Então, vale que `MultiFactory::createArticle()` faz o mesmo que `MultiFactoryAlt::get('article')`. No entanto, a notação alternativa tem a desvantagem de não ficar claro quais valores de `$name` são suportados e, logicamente, também não é possível na interface distinguir diferentes valores de retorno para diferentes `$name`. + +Definição por lista +------------------- +Desta forma, é possível definir uma fábrica múltipla na configuração: .{data-version:3.2.0} + +```neon +services: + - MultiFactory( + article: Article # define createArticle() + db: PDO(%dsn%, %user%, %password%) # define getDb() + ) +``` -Definição com uma lista .[#toc-definition-with-a-list] ------------------------------------------------------- -Hos para definir uma multifábrica em sua configuração? Vamos criar três serviços que serão devolvidos pela multifábrica, e a própria multifábrica: +Ou podemos, na definição da fábrica, referir-nos a serviços existentes usando uma referência: ```neon services: article: Article - - Model\Foo - PDO(%dsn%, %user%, %password%) - MultiFactory( - article: @article # createArticle() - foo: @Model\Foo # createFoo() - db: @\PDO # getDb() + article: @article # define createArticle() + db: @\PDO # define getDb() ) ``` -Definição com tags .[#toc-definition-with-tags] ------------------------------------------------ +Definição usando tags +--------------------- -Outra opção para definir uma multifábrica é a utilização de [etiquetas |services#Tags]: +A segunda opção é usar [tags |services#Tags] para a definição: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter # Assumindo que isso é um serviço ou factory - App\Model\DatabaseAccessor( - db1: @database.db1.explorer + db1: @database.db1.explorer # Assumindo que existe um serviço com este nome ) ``` diff --git a/dependency-injection/pt/faq.texy b/dependency-injection/pt/faq.texy index 09f9f07758..d93dd8c94a 100644 --- a/dependency-injection/pt/faq.texy +++ b/dependency-injection/pt/faq.texy @@ -1,98 +1,92 @@ -Perguntas mais freqüentes sobre DI (FAQ) -**************************************** +Perguntas Frequentes sobre DI (FAQ) +*********************************** -DI é outro nome para IoC? .[#toc-is-di-another-name-for-ioc] ------------------------------------------------------------- +DI é outro nome para IoC? +------------------------- -A *Inversion of Control* (IoC) é um princípio focado na forma como o código é executado - se seu código inicia o código externo ou se seu código é integrado ao código externo, que então o chama. -IoC é um conceito amplo que inclui [eventos |nette:glossary#Events], o chamado [Princípio de Hollywood |application:components#Hollywood style], e outros aspectos. -Fábricas, que fazem parte da [Regra #3: Deixe a Fábrica tratar disso |introduction#Rule #3: Let the Factory Handle It], e representam a inversão para o operador `new`, também são componentes deste conceito. +*Inversion of Control* (IoC) é um princípio focado na maneira como o código é executado - se o seu código executa código de terceiros ou se o seu código é integrado a código de terceiros que o chama posteriormente. IoC é um termo amplo que inclui [eventos |nette:glossary#Eventos], o chamado [Princípio de Hollywood |application:components#Estilo Hollywood] e outros aspectos. Parte deste conceito também são as fábricas, sobre as quais fala a [Regra nº 3: deixe para a fábrica |introduction#Regra nº 3: deixe para a fábrica], e que representam uma inversão para o operador `new`. -A *Dependency Injection* (DI) é sobre como um objeto sabe sobre outro objeto, ou seja, dependência. É um padrão de desenho que requer a passagem explícita de dependências entre objetos. +*Dependency Injection* (DI) foca na maneira como um objeto aprende sobre outro objeto, ou seja, sobre suas dependências. É um padrão de projeto que exige a passagem explícita de dependências entre objetos. -Assim, pode-se dizer que a DI pode ser uma forma específica de IoC. No entanto, nem todas as formas de IoC são adequadas em termos de pureza de código. Por exemplo, entre os anti-padrões, incluímos todas as técnicas que funcionam com o [estado global |global state] ou o chamado [Service Locator |#What is a Service Locator]. +Pode-se dizer, portanto, que DI é uma forma específica de IoC. No entanto, nem todas as formas de IoC são adequadas do ponto de vista da pureza do código. Por exemplo, entre os antipadrões estão técnicas que trabalham com [estado global |global-state] ou o chamado [Service Locator |#O que é Service Locator]. -O que é um Localizador de Serviços? .[#toc-what-is-a-service-locator] ---------------------------------------------------------------------- +O que é Service Locator? +------------------------ -Um Localizador de Serviços é uma alternativa à Injeção de Dependência. Ele funciona criando um armazenamento central onde todos os serviços ou dependências disponíveis são registrados. Quando um objeto precisa de uma dependência, ele o solicita ao Service Locator. +É uma alternativa à Injeção de Dependência. Funciona criando um repositório central onde todos os serviços ou dependências disponíveis são registrados. Quando um objeto precisa de uma dependência, ele a solicita ao Service Locator. -Entretanto, em comparação com a Injeção de Dependência, ela perde transparência: as dependências não são passadas diretamente aos objetos e, portanto, não são facilmente identificáveis, o que exige o exame do código para descobrir e compreender todas as conexões. Os testes também são mais complicados, pois não podemos simplesmente passar objetos simulados para os objetos testados, mas temos que passar pelo Localizador de Serviços. Além disso, o Localizador de Serviços perturba o projeto do código, pois objetos individuais devem estar cientes de sua existência, o que difere da Injeção de Dependência, onde os objetos não têm conhecimento do recipiente DI. +No entanto, em comparação com a Injeção de Dependência, perde em transparência: as dependências não são passadas diretamente aos objetos e não são tão facilmente identificáveis, o que exige examinar o código para revelar e entender todas as ligações. O teste também é mais complicado, pois não podemos simplesmente passar objetos mock para os objetos testados, mas temos que passar pelo Service Locator. Além disso, o Service Locator perturba o design do código, pois objetos individuais precisam saber de sua existência, o que difere da Injeção de Dependência, onde os objetos não têm conhecimento do contêiner DI. -Quando é melhor não usar DI? .[#toc-when-is-it-better-not-to-use-di] --------------------------------------------------------------------- +Quando é melhor não usar DI? +---------------------------- -Não há dificuldades conhecidas associadas ao uso do padrão de projeto de injeção de dependência. Pelo contrário, a obtenção de dependências a partir de locais globalmente acessíveis leva a [uma série de complicações |global-state], assim como o uso de um Localizador de Serviços. -Portanto, é aconselhável usar sempre o DI. Esta não é uma abordagem dogmática, mas simplesmente não foi encontrada uma alternativa melhor. +Não são conhecidas dificuldades associadas ao uso do padrão de projeto Injeção de Dependência. Pelo contrário, obter dependências de locais globalmente disponíveis leva a [uma série de complicações |global-state], assim como o uso do Service Locator. Portanto, é aconselhável usar DI sempre. Isso não é uma abordagem dogmática, mas simplesmente não foi encontrada uma alternativa melhor. -Entretanto, há certas situações em que não passamos objetos um para o outro e os obtemos do espaço global. Por exemplo, ao depurar um código e precisar descarregar um valor variável em um ponto específico do programa, medir a duração de uma determinada parte do programa, ou registrar uma mensagem. -Nesses casos, quando se trata de ações temporárias que serão posteriormente removidas do código, é legítimo usar um dumper, cronômetro ou registrador globalmente acessível. Estas ferramentas, afinal, não pertencem ao projeto do código. +No entanto, existem certas situações em que não passamos objetos e os obtemos do espaço global. Por exemplo, ao depurar código, quando você precisa imprimir o valor de uma variável em um ponto específico do programa, medir a duração de uma determinada parte do programa ou registrar uma mensagem. Nesses casos, quando se trata de tarefas temporárias que serão posteriormente removidas do código, é legítimo usar um dumper, cronômetro ou logger globalmente disponível. Essas ferramentas não pertencem ao design do código. -O uso de DI tem seus inconvenientes? .[#toc-does-using-di-have-its-drawbacks] ------------------------------------------------------------------------------ +O uso de DI tem desvantagens? +----------------------------- -O uso da Injeção de Dependência envolve alguma desvantagem, como o aumento da complexidade de escrita de código ou pior desempenho? O que perdemos quando começamos a escrever código de acordo com a DI? +O uso da Injeção de Dependência traz alguma desvantagem, como aumento da complexidade na escrita do código ou piora no desempenho? O que perdemos quando começamos a escrever código de acordo com DI? -DI não tem impacto no desempenho da aplicação ou nos requisitos de memória. O desempenho do Recipiente DI pode desempenhar um papel, mas no caso da [Nette DI | nette-container], o recipiente é compilado em PHP puro, de modo que suas despesas gerais durante o tempo de execução da aplicação são essencialmente zero. +DI não tem impacto no desempenho ou nos requisitos de memória da aplicação. O desempenho do Contêiner DI pode desempenhar algum papel, mas no caso do [Nette DI |nette-container], o contêiner é compilado em PHP puro, então sua sobrecarga durante a execução da aplicação é essencialmente zero. -Ao escrever o código, é necessário criar construtoras que aceitem dependências. No passado, isto poderia ser demorado, mas graças às modernas IDEs e à [promoção da propriedade dos construtores |https://blog.nette.org/pt/php-8-0-visao-geral-completa-das-noticias#toc-constructor-property-promotion], agora é uma questão de poucos segundos. As fábricas podem ser facilmente geradas usando Nette DI e um plugin PhpStorm com apenas alguns cliques. -Por outro lado, não há necessidade de escrever singletons e pontos de acesso estáticos. +Ao escrever código, geralmente é necessário criar construtores que aceitam dependências. Antigamente, isso podia ser demorado, mas graças aos IDEs modernos e à [promoção de propriedades do construtor |https://blog.nette.org/pt/php-8-0-complete-overview-of-news#toc-constructor-property-promotion], agora é uma questão de segundos. As fábricas podem ser facilmente geradas usando Nette DI e o plugin para PhpStorm com um clique do mouse. Por outro lado, elimina-se a necessidade de escrever singletons e pontos de acesso estáticos. -Pode-se concluir que uma aplicação devidamente projetada usando DI não é nem mais curta nem mais longa em comparação com uma aplicação usando singletons. Partes do código que funcionam com dependências são simplesmente extraídas de classes individuais e movidas para novos locais, ou seja, o container DI e as fábricas. +Pode-se afirmar que uma aplicação corretamente projetada usando DI não é nem mais curta nem mais longa em comparação com uma aplicação usando singletons. As partes do código que trabalham com dependências são apenas extraídas das classes individuais e movidas para novos locais, ou seja, para o contêiner DI e fábricas. -Como reescrever um aplicativo legado para DI? .[#toc-how-to-rewrite-a-legacy-application-to-di] ------------------------------------------------------------------------------------------------ +Como reescrever uma aplicação legada para DI? +--------------------------------------------- -A migração de uma aplicação herdada para a Injeção de Dependência pode ser um processo desafiador, especialmente para aplicações grandes e complexas. É importante abordar este processo de forma sistemática. +A transição de uma aplicação legada para Injeção de Dependência pode ser um processo desafiador, especialmente para aplicações grandes e complexas. É importante abordar este processo sistematicamente. -- Ao passar para a Injeção de Dependência, é importante que todos os membros da equipe compreendam os princípios e práticas que estão sendo utilizados. -- Primeiro, realizar uma análise da aplicação existente para identificar os componentes-chave e suas dependências. Crie um plano para quais peças serão refatoradas e em que ordem. -- Implementar um recipiente DI ou, melhor ainda, utilizar uma biblioteca existente, como a Nette DI. -- Refatorar gradualmente cada parte da aplicação para usar a Injeção de Dependência. Isto pode envolver a modificação de construtores ou métodos para aceitar dependências como parâmetros. -- Modificar os lugares no código onde os objetos de dependência são criados de modo que as dependências sejam injetadas pelo contêiner. Isto pode incluir o uso de fábricas. +- Ao fazer a transição para Injeção de Dependência, é importante que todos os membros da equipe entendam os princípios e procedimentos que estão sendo usados. +- Primeiro, realize uma análise da aplicação existente e identifique os componentes chave e suas dependências. Crie um plano de quais partes serão refatoradas e em que ordem. +- Implemente um contêiner DI ou, melhor ainda, use uma biblioteca existente, como Nette DI. +- Refatore gradualmente partes individuais da aplicação para usar Injeção de Dependência. Isso pode incluir a modificação de construtores ou métodos para aceitar dependências como parâmetros. +- Modifique os locais no código onde objetos com dependências são criados para que, em vez disso, as dependências sejam injetadas pelo contêiner. Isso pode incluir o uso de fábricas. -Lembre-se de que passar para a Injeção de Dependência é um investimento na qualidade do código e na sustentabilidade a longo prazo da aplicação. Embora possa ser um desafio fazer estas mudanças, o resultado deve ser um código mais limpo, mais modular e facilmente testável que esteja pronto para futuras extensões e manutenção. +Lembre-se que a transição para Injeção de Dependência é um investimento na qualidade do código e na sustentabilidade a longo prazo da aplicação. Embora possa ser desafiador fazer essas mudanças, o resultado deve ser um código mais limpo, modular e facilmente testável, pronto para futuras extensões e manutenção. -Por que a composição é preferível à herança? .[#toc-why-composition-is-preferred-over-inheritance] --------------------------------------------------------------------------------------------------- -É preferível usar composição em vez de herança, pois serve ao propósito da reutilização do código sem ter a necessidade de se preocupar com o efeito de trickle down da mudança. Assim, ele proporciona um acoplamento mais frouxo onde não temos que nos preocupar em mudar algum código causando algum outro código dependente que necessite de mudança. Um exemplo típico é a situação identificada como [o inferno do construtor |passing-dependencies#Constructor hell]. +Por que a composição é preferida em relação à herança? +------------------------------------------------------ +É preferível usar [composição |nette:introduction-to-object-oriented-programming#Composição] em vez de [herança |nette:introduction-to-object-oriented-programming#Herança], porque ela serve para reutilizar código sem ter que nos preocupar com as consequências das mudanças. Ela fornece, portanto, um acoplamento mais fraco, onde não precisamos nos preocupar que a mudança em algum código cause a necessidade de mudar outro código dependente. Um exemplo típico é a situação conhecida como [inferno de construtores |passing-dependencies#Constructor hell]. -O Nette DI Container pode ser usado fora da Nette? .[#toc-can-nette-di-container-be-used-outside-of-nette] ----------------------------------------------------------------------------------------------------------- +É possível usar o Nette DI Container fora do Nette? +--------------------------------------------------- -Absolutamente. O Nette DI Container faz parte da Nette, mas foi projetado como uma biblioteca independente que pode ser usada independentemente de outras partes da estrutura. Basta instalá-lo usando o Composer, criar um arquivo de configuração definindo seus serviços, e depois usar algumas linhas de código PHP para criar o container DI. -E você pode começar imediatamente a aproveitar a Injeção de Dependência em seus projetos. +Com certeza. O Nette DI Container faz parte do Nette, mas foi projetado como uma biblioteca independente que pode ser usada independentemente de outras partes do framework. Basta instalá-lo usando o Composer, criar um arquivo de configuração com a definição de seus serviços e, em seguida, usar algumas linhas de código PHP para criar o contêiner DI. E você pode começar imediatamente a aproveitar os benefícios da Injeção de Dependência em seus projetos. -O capítulo [Nette DI Container |nette-container] descreve como é um caso de uso específico, incluindo o código. +O uso específico, incluindo códigos, é descrito no capítulo [Nette DI Container |nette-container]. -Por que a configuração está nos arquivos NEON? .[#toc-why-is-the-configuration-in-neon-files] ---------------------------------------------------------------------------------------------- +Por que a configuração está em arquivos NEON? +--------------------------------------------- -NEON é uma linguagem de configuração simples e de fácil leitura desenvolvida dentro da Nette para a criação de aplicações, serviços e suas dependências. Em comparação com JSON ou YAML, oferece opções muito mais intuitivas e flexíveis para este fim. Em NEON, você pode descrever naturalmente as ligações que não seriam possíveis de escrever em Symfony & YAML, ou apenas através de uma descrição complexa. +NEON é uma linguagem de configuração simples e fácil de ler, desenvolvida no Nette para configurar aplicações, serviços e suas dependências. Em comparação com JSON ou YAML, oferece opções muito mais intuitivas e flexíveis para este propósito. Em NEON, é possível descrever naturalmente ligações que em Symfony & YAMLu não seria possível escrever, ou apenas por meio de uma descrição complexa. -A análise dos arquivos NEON torna a aplicação mais lenta? .[#toc-does-parsing-neon-files-slow-down-the-application] -------------------------------------------------------------------------------------------------------------------- +A análise de arquivos NEON não torna a aplicação mais lenta? +------------------------------------------------------------ -Embora os arquivos NEON sejam analisados muito rapidamente, este aspecto não importa muito. A razão é que os arquivos de análise ocorrem apenas uma vez durante o primeiro lançamento do aplicativo. Depois disso, o código do recipiente DI é gerado, armazenado no disco e executado para cada pedido subseqüente sem a necessidade de análise posterior. +Embora os arquivos NEON sejam analisados muito rapidamente, este aspecto não importa. A razão é que a análise dos arquivos ocorre apenas uma vez na primeira execução da aplicação. Depois disso, o código do contêiner DI é gerado, salvo em disco e executado em cada requisição subsequente, sem a necessidade de realizar análises adicionais. -É assim que funciona em um ambiente de produção. Durante o desenvolvimento, os arquivos NEON são analisados cada vez que seu conteúdo muda, garantindo que o desenvolvedor tenha sempre um container DI atualizado. Como mencionado anteriormente, a análise propriamente dita é uma questão de um instante. +É assim que funciona em um ambiente de produção. Durante o desenvolvimento, os arquivos NEON são analisados toda vez que seu conteúdo é alterado, para que o desenvolvedor sempre tenha um contêiner DI atualizado. A análise em si é, como mencionado, uma questão de momento. -Como posso acessar os parâmetros do arquivo de configuração em minha classe? .[#toc-how-do-i-access-the-parameters-from-the-configuration-file-in-my-class] ------------------------------------------------------------------------------------------------------------------------------------------------------------ +Como acesso os parâmetros do arquivo de configuração a partir da minha classe? +------------------------------------------------------------------------------ -Tenha em mente a [Regra nº 1: Deixe que seja passada a você |introduction#Rule #1: Let It Be Passed to You]. Se uma classe requer informações de um arquivo de configuração, não precisamos descobrir como acessar essas informações; em vez disso, simplesmente pedimos por elas - por exemplo, através do construtor da classe. E realizamos a passagem no arquivo de configuração. +Lembre-se da [Regra nº 1: peça para receber |introduction#Regra nº 1: peça para ser passado]. Se uma classe requer informações do arquivo de configuração, não precisamos pensar em como obter essas informações, em vez disso, simplesmente as solicitamos - por exemplo, através do construtor da classe. E realizamos a passagem no arquivo de configuração. -Neste exemplo, `%myParameter%` é um espaço reservado para o valor do parâmetro `myParameter`, que será passado para o construtor de `MyClass`: +Neste exemplo, `%myParameter%` é um placeholder para o valor do parâmetro `myParameter`, que é passado para o construtor da classe `MyClass`: ```php # config.neon @@ -103,10 +97,10 @@ services: - MyClass(%myParameter%) ``` -Se você quiser passar vários parâmetros ou usar a fiação automática, é útil [embrulhar os parâmetros em um objeto |best-practices:passing-settings-to-presenters]. +Se você deseja passar vários parâmetros ou usar autowiring, é aconselhável [envolver os parâmetros em um objeto |best-practices:passing-settings-to-presenters]. -A Nette suporta a interface do Container PSR-11? .[#toc-does-nette-support-psr-11-container-interface] ------------------------------------------------------------------------------------------------------- +Nette suporta a interface PSR-11: Container? +-------------------------------------------- -A Nette DI Container não suporta diretamente o PSR-11. Entretanto, se você precisar de interoperabilidade entre o Container Nette DI e bibliotecas ou estruturas que esperam a interface do Container PSR-11, você pode criar um [adaptador simples |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f] para servir como ponte entre o Container Nette DI e o PSR-11. +O Nette DI Container não suporta PSR-11 diretamente. No entanto, se você precisar de interoperabilidade entre o Nette DI Container e bibliotecas ou frameworks que esperam a Interface de Contêiner PSR-11, você pode criar um [adaptador simples |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f] que servirá como uma ponte entre o Nette DI Container e o PSR-11. diff --git a/dependency-injection/pt/global-state.texy b/dependency-injection/pt/global-state.texy index f5a33c5ea0..65375c7661 100644 --- a/dependency-injection/pt/global-state.texy +++ b/dependency-injection/pt/global-state.texy @@ -1,67 +1,63 @@ -Estado global e Singletons +Estado Global e Singletons ************************** .[perex] -Advertência: As construções a seguir são sintomas de código mal projetado: +Aviso: As seguintes construções são um sinal de código mal projetado: - `Foo::getInstance()` - `DB::insert(...)` - `Article::setDb($db)` - `ClassName::$var` ou `static::$var` -Você encontra alguma dessas construções em seu código? Se sim, você tem a oportunidade de aprimorá-lo. Você pode pensar que essas construções são comuns, frequentemente vistas em soluções de exemplo de várias bibliotecas e estruturas. Se esse for o caso, o design do código é falho. +Alguma dessas construções ocorre em seu código? Então você tem a oportunidade de melhorá-lo. Você pode pensar que são construções comuns que você vê até mesmo em soluções de exemplo de várias bibliotecas e frameworks. Se for esse o caso, então o design do código deles não é bom. -Não estamos falando de pureza acadêmica. Todas essas construções têm uma coisa em comum: elas utilizam o estado global. E isso tem um impacto destrutivo na qualidade do código. As classes são enganosas quanto às suas dependências. O código se torna imprevisível. Isso confunde os desenvolvedores e reduz sua eficiência. +Agora, definitivamente não estamos falando de alguma pureza acadêmica. Todas essas construções têm uma coisa em comum: elas usam estado global. E isso tem um impacto destrutivo na qualidade do código. As classes mentem sobre suas dependências. O código se torna imprevisível. Confunde os programadores e reduz sua eficiência. Neste capítulo, explicaremos por que isso acontece e como evitar o estado global. -Interligação global .[#toc-global-interlinking] ------------------------------------------------ +Acoplamento Global +------------------ -Em um mundo ideal, um objeto só deve se comunicar com objetos que foram [passados diretamente a ele |passing-dependencies]. Se eu criar dois objetos `A` e `B` e nunca passar uma referência entre eles, então nem `A` nem `B` poderão acessar ou modificar o estado do outro. Essa é uma propriedade altamente desejável do código. É como ter uma bateria e uma lâmpada; a lâmpada não acenderá até que você a conecte à bateria com um fio. +Em um mundo ideal, um objeto só deveria ser capaz de se comunicar com objetos que lhe foram [passados diretamente |passing-dependencies]. Se eu criar dois objetos `A` e `B` e nunca passar uma referência entre eles, então nem `A` nem `B` podem acessar o outro objeto ou alterar seu estado. Esta é uma propriedade muito desejável do código. É semelhante a ter uma bateria e uma lâmpada; a lâmpada não acenderá até que você a conecte à bateria com um fio. -Entretanto, isso não se aplica a variáveis globais (estáticas) ou singletons. O objeto `A` poderia acessar *sem fio* o objeto `C` e modificá-lo sem nenhuma passagem de referência, chamando `C::changeSomething()`. Se o objeto `B` também acessar o objeto global `C`, então `A` e `B` poderão se influenciar mutuamente por meio de `C`. +Mas isso não se aplica a variáveis globais (estáticas) ou singletons. O objeto `A` poderia acessar *sem fio* o objeto `C` e modificá-lo sem qualquer passagem de referência, chamando `C::changeSomething()`. Se o objeto `B` também pegar o `C` global, então `A` e `B` podem se influenciar mutuamente através de `C`. -O uso de variáveis globais introduz uma nova forma de acoplamento *sem fio* que não é visível externamente. Isso cria uma cortina de fumaça que complica a compreensão e o uso do código. Para realmente entender as dependências, os desenvolvedores precisam ler cada linha do código-fonte, em vez de apenas se familiarizarem com as interfaces de classe. Além disso, esse emaranhado é totalmente desnecessário. O estado global é usado porque é facilmente acessível de qualquer lugar e permite, por exemplo, gravar em um banco de dados por meio de um método global (estático) `DB::insert()`. Entretanto, como veremos, o benefício que ele oferece é mínimo, enquanto as complicações que ele introduz são graves. +O uso de variáveis globais introduz no sistema uma nova forma de acoplamento *sem fio*, que não é visível de fora. Cria uma cortina de fumaça complicando a compreensão e o uso do código. Para que os desenvolvedores realmente entendam as dependências, eles precisam ler cada linha do código-fonte. Em vez de apenas se familiarizarem com as interfaces das classes. Além disso, é um acoplamento completamente desnecessário. O estado global é usado porque é facilmente acessível de qualquer lugar e permite, por exemplo, escrever no banco de dados através do método global (estático) `DB::insert()`. Mas, como mostraremos, a vantagem que isso traz é insignificante, enquanto as complicações que causa são fatais. .[note] -Em termos de comportamento, não há diferença entre uma variável global e uma variável estática. Elas são igualmente prejudiciais. +Do ponto de vista do comportamento, não há diferença entre uma variável global e estática. Elas são igualmente prejudiciais. -A ação assustadora à distância .[#toc-the-spooky-action-at-a-distance] ----------------------------------------------------------------------- +Ação fantasmagórica à distância +------------------------------- -"Ação assustadora à distância" - é o que Albert Einstein chamou famoso fenômeno na física quântica que lhe deu arrepios em 1935. -É um emaranhado quântico, cuja peculiaridade é que quando você mede informações sobre uma partícula, você afeta imediatamente outra partícula, mesmo que elas estejam a milhões de anos-luz de distância. -o que aparentemente viola a lei fundamental do universo de que nada pode viajar mais rápido do que a luz. +"Ação fantasmagórica à distância" - foi assim que Albert Einstein famosamente chamou, em 1935, um fenômeno na física quântica que lhe causava arrepios. +Trata-se do emaranhamento quântico, cuja peculiaridade é que, quando você mede a informação sobre uma partícula, influencia instantaneamente a outra partícula, mesmo que estejam a milhões de anos-luz de distância. Isso aparentemente viola a lei fundamental do universo de que nada pode se propagar mais rápido que a luz. -No mundo do software, podemos chamar uma "ação assustadora à distância" de uma situação em que executamos um processo que pensamos estar isolado (porque não passamos nenhuma referência), mas interações e mudanças de estado inesperadas acontecem em locais distantes do sistema, das quais não falamos ao objeto. Isto só pode acontecer através do estado global. +No mundo do software, podemos chamar de "ação fantasmagórica à distância" a situação em que iniciamos um processo que acreditamos ser isolado (porque não passamos nenhuma referência a ele), mas em locais remotos do sistema ocorrem interações inesperadas e mudanças de estado das quais não tínhamos conhecimento. Isso só pode acontecer através do estado global. -Imagine se juntar a uma equipe de desenvolvimento de projetos que tenha uma base de código grande e madura. Sua nova liderança lhe pede para implementar uma nova funcionalidade e, como um bom desenvolvedor, você começa escrevendo um teste. Mas como você é novo no projeto, você faz um monte de testes exploratórios do tipo "o que acontece se eu chamar este método". E você tenta escrever o seguinte teste: +Imagine que você se junta a uma equipe de desenvolvedores de um projeto que tem uma base de código extensa e madura. Seu novo líder pede que você implemente uma nova funcionalidade e você, como um bom desenvolvedor, começa escrevendo um teste. Mas como você é novo no projeto, faz muitos testes exploratórios do tipo "o que acontece se eu chamar este método". E tenta escrever o seguinte teste: ```php function testCreditCardCharge() { - $cc = new CreditCard('1234567890123456', 5, 2028); // o número de seu cartão + $cc = new CreditCard('1234567890123456', 5, 2028); // número do seu cartão $cc->charge(100); } ``` -Você executa o código, talvez várias vezes, e depois de um tempo você nota em seu telefone notificações do banco de que cada vez que você o executa, $100 foram cobrados em seu cartão de crédito 🤦♂️ +Você executa o código, talvez várias vezes, e depois de um tempo percebe notificações do banco no seu celular informando que a cada execução foram debitados 100 dólares do seu cartão de crédito 🤦‍♂️ -Como diabos o teste poderia causar uma carga real? Não é fácil de operar com cartão de crédito. Você tem que interagir com um serviço web de terceiros, você tem que conhecer o URL desse serviço web, você tem que fazer o login, e assim por diante. -Nenhuma destas informações está incluída no teste. Pior ainda, você nem sabe onde estas informações estão presentes e, portanto, como zombar das dependências externas para que cada execução não resulte na cobrança de US$ 100 novamente. E como um novo desenvolvedor, como você deveria saber que o que você estava prestes a fazer o levaria a ser $100 mais pobre? +Como diabos o teste pôde causar um débito real de dinheiro? Operar com um cartão de crédito não é fácil. Você precisa se comunicar com um serviço web de terceiros, precisa saber a URL desse serviço web, precisa fazer login e assim por diante. Nenhuma dessas informações está contida no teste. Pior ainda, você nem sabe onde essas informações estão presentes e, portanto, nem como mockar as dependências externas para que cada execução não leve a um novo débito de 100 dólares. E como você, como novo desenvolvedor, deveria saber que o que estava prestes a fazer resultaria em ficar 100 dólares mais pobre? -Isso é uma ação assustadora à distância! +Isso é ação fantasmagórica à distância! -Você não tem escolha a não ser cavar muito código fonte, perguntando aos colegas mais velhos e mais experientes, até entender como funcionam as conexões no projeto. -Isto se deve ao fato de que, ao olhar para a interface da classe `CreditCard`, você não pode determinar o estado global que precisa ser inicializado. Mesmo olhando para o código-fonte da classe, você não dirá qual método de inicialização deve ser chamado. Na melhor das hipóteses, você pode encontrar a variável global a ser acessada e tentar adivinhar como inicializá-la a partir disso. +Você não tem escolha a não ser vasculhar longamente um monte de código-fonte, perguntar aos colegas mais velhos e experientes, até entender como as ligações no projeto funcionam. Isso ocorre porque, ao olhar para a interface da classe `CreditCard`, não é possível identificar o estado global que precisa ser inicializado. Mesmo olhar para o código-fonte da classe não revela qual método de inicialização você deve chamar. Na melhor das hipóteses, você pode encontrar uma variável global que está sendo acessada e, a partir dela, tentar adivinhar como inicializá-la. -As aulas em tal projeto são mentirosos patológicos. O cartão de pagamento finge que você pode simplesmente instanciá-lo e ligar para o método `charge()`. No entanto, ele interage secretamente com outra classe, `PaymentGateway`. Mesmo sua interface diz que pode ser inicializada independentemente, mas na realidade, ela retira credenciais de algum arquivo de configuração e assim por diante. -É claro para os desenvolvedores que escreveram este código que `CreditCard` precisa `PaymentGateway`. Eles escreveram o código desta forma. Mas para qualquer novato no projeto, isto é um mistério completo e dificulta o aprendizado. +As classes em tal projeto são mentirosas patológicas. O cartão de crédito finge que basta instanciá-lo e chamar o método `charge()`. Secretamente, porém, ele colabora com outra classe `PaymentGateway`, que representa o gateway de pagamento. Sua interface também diz que pode ser inicializada separadamente, mas na realidade ela extrai credenciais de algum arquivo de configuração e assim por diante. Para os desenvolvedores que escreveram este código, está claro que `CreditCard` precisa de `PaymentGateway`. Eles escreveram o código desta forma. Mas para qualquer pessoa nova no projeto, é um mistério absoluto e impede o aprendizado. -Como consertar a situação? Fácil. **Deixe a API declarar as dependências.** +Como consertar a situação? Facilmente. **Deixe a API declarar as dependências.** ```php function testCreditCardCharge() @@ -72,36 +68,35 @@ function testCreditCardCharge() } ``` -Observe como as relações dentro do código são subitamente óbvias. Ao declarar que o método `charge()` precisa `PaymentGateway`, você não precisa perguntar a ninguém como o código é interdependente. Você sabe que tem que criar uma instância dele, e quando você tenta fazer isso, você se depara com o fato de que tem que fornecer parâmetros de acesso. Sem eles, o código não funcionaria. +Observe como as interconexões dentro do código se tornam repentinamente óbvias. Como o método `charge()` declara que precisa de `PaymentGateway`, você não precisa perguntar a ninguém como o código está interconectado. Você sabe que precisa criar sua instância e, ao tentar fazê-lo, descobrirá que precisa fornecer parâmetros de acesso. Sem eles, o código nem sequer seria executado. -E o mais importante, agora você pode zombar da porta de pagamento para que não lhe sejam cobrados 100 dólares cada vez que fizer um teste. +E, o mais importante, agora você pode mockar o gateway de pagamento, para não ser cobrado 100 dólares toda vez que executar o teste. -O estado global faz com que seus objetos possam acessar secretamente coisas que não estão declaradas em seus APIs e, como resultado, torna seus APIs mentirosos patológicos. +O estado global faz com que seus objetos possam acessar secretamente coisas que não são declaradas em sua API e, como resultado, tornam suas APIs mentirosas patológicas. -Você pode não ter pensado nisso antes, mas sempre que você usa o estado global, você está criando canais secretos de comunicação sem fio. A ação remota assustadora força os desenvolvedores a ler cada linha de código para entender as possíveis interações, reduz a produtividade dos desenvolvedores e confunde os novos membros da equipe. -Se foi você quem criou o código, você conhece as dependências reais, mas qualquer um que venha atrás de você não tem a menor idéia. +Talvez você não tenha pensado nisso antes, mas sempre que usa estado global, está criando canais de comunicação secretos sem fio. A ação fantasmagórica à distância força os desenvolvedores a ler cada linha de código para entender as interações potenciais, reduz a produtividade dos desenvolvedores e confunde os novos membros da equipe. Se você foi quem criou o código, conhece as dependências reais, mas qualquer pessoa que vier depois de você ficará perdida. -Não escreva código que use o estado global, prefira passar dependências. Ou seja, injeção de dependência. +Não escreva código que utilize estado global, prefira passar dependências. Ou seja, injeção de dependência. -Brittleness do Estado Global .[#toc-brittleness-of-the-global-state] --------------------------------------------------------------------- +Fragilidade do estado global +---------------------------- -Em código que usa estado global e singletons, nunca é certo quando e por quem esse estado mudou. Este risco já está presente na inicialização. O código a seguir deve criar uma conexão de banco de dados e inicializar o gateway de pagamento, mas ele continua lançando uma exceção e encontrar a causa é extremamente enfadonho: +No código que usa estado global e singletons, nunca é certo quando e quem alterou esse estado. Esse risco surge já na inicialização. O código a seguir deve criar uma conexão com o banco de dados e inicializar o gateway de pagamento, mas lança constantemente uma exceção e encontrar a causa é extremamente demorado: ```php PaymentGateway::init(); DB::init('mysql:', 'user', 'password'); ``` -Você tem que percorrer o código em detalhes para descobrir que o objeto `PaymentGateway` acessa outros objetos sem fio, alguns dos quais requerem uma conexão de banco de dados. Portanto, você deve inicializar o banco de dados antes de `PaymentGateway`. No entanto, a cortina de fumaça do estado global esconde isso de você. Quanto tempo você economizaria se a API de cada classe não mentisse e declarasse suas dependências? +Você precisa percorrer detalhadamente o código para descobrir que o objeto `PaymentGateway` acessa sem fio outros objetos, alguns dos quais requerem uma conexão com o banco de dados. Portanto, é necessário inicializar o banco de dados antes de `PaymentGateway`. No entanto, a cortina de fumaça do estado global esconde isso de você. Quanto tempo você economizaria se a API das classes individuais não mentisse e declarasse suas dependências? ```php $db = new DB('mysql:', 'user', 'password'); $gateway = new PaymentGateway($db, ...); ``` -Um problema semelhante surge quando se utiliza o acesso global a uma conexão de banco de dados: +Um problema semelhante surge também ao usar acesso global à conexão do banco de dados: ```php use Illuminate\Support\Facades\DB; @@ -115,9 +110,9 @@ class Article } ``` -Ao chamar o método `save()`, não é certo se já foi criada uma conexão de banco de dados e quem é o responsável por criá-la. Por exemplo, se quiséssemos mudar a conexão de banco de dados na hora, talvez para fins de teste, provavelmente teríamos que criar métodos adicionais, como `DB::reconnect(...)` ou `DB::reconnectForTest()`. +Ao chamar o método `save()`, não é certo se a conexão com o banco de dados já foi criada e quem é responsável por sua criação. Se quisermos, por exemplo, alterar a conexão com o banco de dados em tempo de execução, talvez para testes, provavelmente teríamos que criar outros métodos como `DB::reconnect(...)` ou `DB::reconnectForTest()`. -Considere um exemplo: +Considere o exemplo: ```php $article = new Article; @@ -127,9 +122,9 @@ Foo::doSomething(); $article->save(); ``` -Onde podemos ter certeza de que o banco de dados de testes está realmente sendo usado quando se liga para `$article->save()`? E se o método `Foo::doSomething()` mudou a conexão global do banco de dados? Para descobrir, teríamos que examinar o código fonte da classe `Foo` e provavelmente muitas outras classes. Entretanto, esta abordagem forneceria apenas uma resposta a curto prazo, pois a situação pode mudar no futuro. +Onde temos certeza de que ao chamar `$article->save()` o banco de dados de teste está realmente sendo usado? E se o método `Foo::doSomething()` alterou a conexão global do banco de dados? Para descobrir, teríamos que examinar o código-fonte da classe `Foo` e provavelmente de muitas outras classes. Essa abordagem, no entanto, traria apenas uma resposta de curto prazo, pois a situação pode mudar no futuro. -E se movermos a conexão de banco de dados para uma variável estática dentro da classe `Article`? +E se movermos a conexão com o banco de dados para uma variável estática dentro da classe `Article`? ```php class Article @@ -148,11 +143,11 @@ class Article } ``` -Isto não muda absolutamente nada. O problema é um estado global e não importa em qual classe ele se esconde. Neste caso, como no anterior, não temos nenhuma pista de qual banco de dados está sendo escrito quando o método `$article->save()` é chamado. Qualquer pessoa no extremo distante da aplicação poderia alterar o banco de dados a qualquer momento usando `Article::setDb()`. Sob nossas mãos. +Isso não mudou nada. O problema é o estado global e é completamente irrelevante em qual classe ele está escondido. Neste caso, assim como no anterior, ao chamar o método `$article->save()`, não temos nenhuma pista sobre em qual banco de dados ele será escrito. Qualquer pessoa do outro lado da aplicação poderia ter alterado o banco de dados a qualquer momento usando `Article::setDb()`. Sob nossos narizes. O estado global torna nossa aplicação **extremamente frágil**. -Entretanto, há uma maneira simples de lidar com este problema. Basta que o API declare as dependências para garantir a funcionalidade adequada. +No entanto, existe uma maneira simples de lidar com esse problema. Basta deixar a API declarar as dependências, garantindo assim a funcionalidade correta. ```php class Article @@ -174,15 +169,15 @@ Foo::doSomething(); $article->save(); ``` -Esta abordagem elimina a preocupação de mudanças ocultas e inesperadas nas conexões de banco de dados. Agora temos certeza de onde o artigo é armazenado e nenhuma modificação de código dentro de outra classe não relacionada pode mais alterar a situação. O código não é mais frágil, mas estável. +Graças a essa abordagem, elimina-se a preocupação com alterações ocultas e inesperadas na conexão do banco de dados. Agora temos certeza de onde o artigo está sendo salvo e nenhuma modificação no código dentro de outra classe não relacionada pode mais alterar a situação. O código não é mais frágil, mas estável. -Não escreva código que utilize o estado global, prefira passar dependências. Portanto, injeção de dependência. +Não escreva código que utilize estado global, prefira passar dependências. Ou seja, injeção de dependência. -Singleton .[#toc-singleton] ---------------------------- +Singleton +--------- -Singleton é um padrão de design que, por [definição |https://en.wikipedia.org/wiki/Singleton_pattern] da famosa publicação Gang of Four, restringe uma classe a uma única instância e oferece acesso global a ela. A implementação deste padrão geralmente se assemelha ao seguinte código: +Singleton é um padrão de projeto que, de acordo com a "definição":https://en.wikipedia.org/wiki/Singleton_pattern da conhecida publicação Gang of Four, restringe uma classe a uma única instância e oferece acesso global a ela. A implementação desse padrão geralmente se assemelha ao seguinte código: ```php class Singleton @@ -195,38 +190,35 @@ class Singleton return self::$instance; } - // e outros métodos que desempenham as funções da classe + // e outros métodos que cumprem as funções da classe dada } ``` -Infelizmente, o singleton introduz o estado global na aplicação. E como demonstramos acima, o estado global é indesejável. É por isso que o singleton é considerado um antipadrão. +Infelizmente, o singleton introduz estado global na aplicação. E como mostramos acima, o estado global é indesejável. Portanto, o singleton é considerado um antipadrão. -Não use singletons em seu código e substitua-os por outros mecanismos. Você realmente não precisa de singletons. Entretanto, se você precisar garantir a existência de uma única instância de uma classe para toda a aplicação, deixe-a para o [container DI |container]. -Assim, crie um singleton de aplicação, ou serviço. Isto impedirá a classe de fornecer sua própria singularidade (ou seja, ela não terá um método `getInstance()` e uma variável estática) e só desempenhará suas funções. Assim, deixará de violar o princípio da responsabilidade única. +Não use singletons em seu código e substitua-os por outros mecanismos. Você realmente não precisa de singletons. No entanto, se precisar garantir a existência de uma única instância de uma classe para toda a aplicação, deixe isso para o [contêiner DI |container]. Crie assim um singleton de aplicação, ou seja, um serviço. Com isso, a classe deixa de se preocupar em garantir sua própria unicidade (ou seja, não terá o método `getInstance()` e a variável estática) e cumprirá apenas suas funções. Assim, deixará de violar o princípio da responsabilidade única. -Testes de estado global versus testes .[#toc-global-state-versus-tests] ------------------------------------------------------------------------ +Estado global versus testes +--------------------------- -Ao escrever testes, assumimos que cada teste é uma unidade isolada e que nenhum estado externo entra nele. E que nenhum estado deixa os testes. Quando um teste é concluído, qualquer estado associado com o teste deve ser removido automaticamente pelo coletor de lixo. Isto faz com que os testes sejam isolados. Portanto, podemos executar os testes em qualquer ordem. +Ao escrever testes, assumimos que cada teste é uma unidade isolada e que nenhum estado externo entra nele. E nenhum estado sai dos testes. Após a conclusão do teste, todo o estado relacionado ao teste deve ser removido automaticamente pelo coletor de lixo. Graças a isso, os testes são isolados. Portanto, podemos executar os testes em qualquer ordem. -Entretanto, se estados/cingilões globais estiverem presentes, todas essas simpáticas suposições se desmoronam. Um estado pode entrar e sair de um teste. De repente, a ordem dos testes pode ser importante. +No entanto, se houver estados globais/singletons, todas essas suposições agradáveis desmoronam. O estado pode entrar e sair do teste. De repente, a ordem dos testes pode importar. -Para testar singletons, os desenvolvedores muitas vezes têm que relaxar suas propriedades, talvez permitindo que uma instância seja substituída por outra. Tais soluções são, na melhor das hipóteses, hacks que produzem códigos difíceis de manter e de entender. Qualquer teste ou método `tearDown()` que afete qualquer estado global deve desfazer essas mudanças. +Para poder testar singletons, os desenvolvedores muitas vezes precisam afrouxar suas propriedades, talvez permitindo que a instância seja substituída por outra. Tais soluções são, na melhor das hipóteses, um hack que cria código difícil de manter e entender. Cada teste ou método `tearDown()`, que afeta qualquer estado global, deve reverter essas alterações. -O estado global é a maior dor de cabeça em testes unitários! +O estado global é a maior dor de cabeça nos testes unitários! -Como consertar a situação? Fácil. Não escreva código que utilize singletons, prefira passar dependências. Ou seja, injeção de dependência. +Como consertar a situação? Facilmente. Não escreva código que utilize singletons, prefira passar dependências. Ou seja, injeção de dependência. -Constantes globais .[#toc-global-constants] -------------------------------------------- +Constantes Globais +------------------ -O estado global não se limita ao uso de singletons e variáveis estáticas, mas também pode se aplicar a constantes globais. +O estado global não se limita apenas ao uso de singletons e variáveis estáticas, mas também pode se referir a constantes globais. -Constantes cujo valor não nos fornece nenhuma informação nova (`M_PI`) ou útil (`PREG_BACKTRACK_LIMIT_ERROR`) são claramente OK. -Por outro lado, constantes que servem como uma forma de *sem fio* passar informações dentro do código nada mais são do que uma dependência oculta. Como `LOG_FILE` no exemplo a seguir. -O uso da constante `FILE_APPEND` é perfeitamente correto. +Constantes cujo valor não nos traz nenhuma informação nova (`M_PI`) ou útil (`PREG_BACKTRACK_LIMIT_ERROR`) são claramente aceitáveis. Por outro lado, constantes que servem como uma forma de passar informações *sem fio* para dentro do código não são nada mais do que uma dependência oculta. Como `LOG_FILE` no exemplo a seguir. O uso da constante `FILE_APPEND` é totalmente correto. ```php const LOG_FILE = '...'; @@ -242,7 +234,7 @@ class Foo } ``` -Neste caso, devemos declarar o parâmetro no construtor da classe `Foo` para torná-la parte da API: +Neste caso, deveríamos declarar um parâmetro no construtor da classe `Foo`, para que ele se torne parte da API: ```php class Foo @@ -261,44 +253,42 @@ class Foo } ``` -Agora podemos passar informações sobre o caminho para o arquivo de registro e facilmente alterá-lo conforme necessário, facilitando o teste e a manutenção do código. +Agora podemos passar a informação sobre o caminho do arquivo para log e alterá-la facilmente conforme necessário, o que facilita o teste e a manutenção do código. -Funções globais e métodos estáticos .[#toc-global-functions-and-static-methods] -------------------------------------------------------------------------------- +Funções Globais e Métodos Estáticos +----------------------------------- -Queremos enfatizar que o uso de métodos estáticos e funções globais não é, por si só, problemático. Explicamos a inadequação do uso de `DB::insert()` e métodos similares, mas sempre foi uma questão de estado global armazenada em uma variável estática. O método `DB::insert()` requer a existência de uma variável estática porque armazena a conexão de banco de dados. Sem esta variável, seria impossível implementar o método. +Queremos enfatizar que o uso de métodos estáticos e funções globais em si não é problemático. Explicamos por que o uso de `DB::insert()` e métodos semelhantes é inadequado, mas sempre foi apenas uma questão de estado global armazenado em alguma variável estática. O método `DB::insert()` requer a existência de uma variável estática porque a conexão com o banco de dados está armazenada nela. Sem essa variável, seria impossível implementar o método. -O uso de métodos e funções estáticas determinísticas, tais como `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` e muitas outras, é perfeitamente consistente com a injeção de dependência. Estas funções sempre retornam os mesmos resultados a partir dos mesmos parâmetros de entrada e são, portanto, previsíveis. Elas não utilizam nenhum estado global. +O uso de métodos estáticos e funções determinísticas, como `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` e muitas outras, está em total conformidade com a injeção de dependência. Essas funções sempre retornam os mesmos resultados para os mesmos parâmetros de entrada e são, portanto, previsíveis. Elas não usam nenhum estado global. -No entanto, há funções no PHP que não são determinísticas. Estas incluem, por exemplo, a função `htmlspecialchars()`. Seu terceiro parâmetro, `$encoding`, se não for especificado, é o valor padrão da opção de configuração `ini_get('default_charset')`. Portanto, recomenda-se sempre especificar este parâmetro para evitar um possível comportamento imprevisível da função. A Nette faz isto de forma consistente. +Existem, porém, também funções no PHP que não são determinísticas. Entre elas está, por exemplo, a função `htmlspecialchars()`. Seu terceiro parâmetro `$encoding`, se não for especificado, tem como valor padrão o valor da opção de configuração `ini_get('default_charset')`. Portanto, recomenda-se sempre especificar este parâmetro para evitar possíveis comportamentos imprevisíveis da função. A Nette faz isso consistentemente. -Algumas funções, tais como `strtolower()`, `strtoupper()`, e similares, tiveram um comportamento não determinístico no passado recente e dependeram da configuração `setlocale()`. Isto causou muitas complicações, na maioria das vezes quando se trabalha com o idioma turco. -Isto porque o idioma turco distingue entre maiúsculas e minúsculas `I` com e sem um ponto. Assim, `strtolower('I')` devolveu o caracter `ı` e `strtoupper('i')` devolveu o caracter `İ`, o que levou a aplicações que causaram uma série de erros misteriosos. -Entretanto, este problema foi corrigido na versão 8.2 do PHP e as funções não são mais dependentes do locale. +Algumas funções, como `strtolower()`, `strtoupper()` e semelhantes, comportaram-se de forma não determinística no passado recente e dependiam da configuração `setlocale()`. Isso causou muitas complicações, mais frequentemente ao trabalhar com a língua turca. Isso porque o turco distingue entre letras `I` maiúsculas e minúsculas com e sem ponto. Assim, `strtolower('I')` retornava o caractere `ı` e `strtoupper('i')` o caractere `İ`, o que levou as aplicações a causar uma série de erros misteriosos. No entanto, esse problema foi corrigido na versão 8.2 do PHP e as funções já não dependem do locale. -Este é um bom exemplo de como o estado global tem atormentado milhares de desenvolvedores em todo o mundo. A solução foi substituí-lo por uma injeção de dependência. +Este é um bom exemplo de como o estado global atormentou milhares de desenvolvedores em todo o mundo. A solução foi substituí-lo por injeção de dependência. -Quando é possível usar o Estado Global? .[#toc-when-is-it-possible-to-use-global-state] ---------------------------------------------------------------------------------------- +Quando é possível usar estado global? +------------------------------------- -Existem certas situações específicas em que é possível utilizar o estado global. Por exemplo, quando se depura o código e é necessário descarregar o valor de uma variável ou medir a duração de uma parte específica do programa. Em tais casos, que dizem respeito a ações temporárias que serão posteriormente removidas do código, é legítimo usar um dumper ou cronômetro disponível globalmente. Estas ferramentas não fazem parte do projeto do código. +Existem certas situações específicas em que é possível utilizar o estado global. Por exemplo, ao depurar código, quando você precisa imprimir o valor de uma variável ou medir a duração de uma determinada parte do programa. Nesses casos, que dizem respeito a ações temporárias que serão posteriormente removidas do código, é legítimo usar um dumper ou cronômetro globalmente disponível. Essas ferramentas não fazem parte do design do código. -Outro exemplo são as funções para trabalhar com expressões regulares `preg_*`, que armazenam internamente expressões regulares compiladas em um cache estático na memória. Quando você chama a mesma expressão regular várias vezes em diferentes partes do código, ela é compilada apenas uma vez. O cache economiza desempenho e também é completamente invisível para o usuário, de modo que tal uso pode ser considerado legítimo. +Outro exemplo são as funções para trabalhar com expressões regulares `preg_*`, que internamente armazenam expressões regulares compiladas em um cache estático na memória. Assim, quando você chama a mesma expressão regular várias vezes em diferentes partes do código, ela é compilada apenas uma vez. O cache economiza desempenho e, ao mesmo tempo, é completamente invisível para o usuário, portanto, tal uso pode ser considerado legítimo. -Sumário .[#toc-summary] ------------------------ +Resumo +------ -Mostramos porque faz sentido +Discutimos por que faz sentido: 1) Remover todas as variáveis estáticas do código -2) Declarar as dependências +2) Declarar dependências 3) E usar injeção de dependência -Ao contemplar o projeto do código, tenha em mente que cada `static $foo` representa um problema. Para que seu código seja um ambiente respeitador do DI, é essencial erradicar completamente o estado global e substituí-lo por injeção de dependência. +Ao pensar no design do código, lembre-se de que cada `static $foo` representa um problema. Para que seu código seja um ambiente que respeite DI, é essencial erradicar completamente o estado global e substituí-lo por injeção de dependência. -Durante este processo, você pode descobrir que precisa dividir uma classe porque ela tem mais de uma responsabilidade. Não se preocupe com isso; esforce-se pelo princípio de uma responsabilidade. +Durante esse processo, você pode descobrir que é necessário dividir a classe porque ela tem mais de uma responsabilidade. Não tenha medo disso; busque o princípio da responsabilidade única. -*Gostaria de agradecer a Miško Hevery, cujos artigos como [Flaw: Brittle Global State & Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/] formam a base deste capítulo.* +*Gostaria de agradecer a Miško Hevery, cujos artigos, como [Flaw: Brittle Global State & Singletons |https://web.archive.org/web/20230321084133/http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/], são a base deste capítulo.* diff --git a/dependency-injection/pt/introduction.texy b/dependency-injection/pt/introduction.texy index 102337536f..cfa0126a29 100644 --- a/dependency-injection/pt/introduction.texy +++ b/dependency-injection/pt/introduction.texy @@ -2,57 +2,57 @@ O que é Injeção de Dependência? ******************************* .[perex] -Este capítulo lhe apresentará as práticas básicas de programação que você deve seguir ao redigir qualquer solicitação. Estes são os fundamentos necessários para escrever um código limpo, compreensível e de fácil manutenção. +Este capítulo apresentará os procedimentos básicos de programação que você deve seguir ao escrever todas as aplicações. São os fundamentos necessários para escrever código limpo, compreensível e sustentável. -Se você aprender e seguir estas regras, Nette estará lá para você a cada passo do caminho. Ela cuidará das tarefas rotineiras para você e lhe proporcionará o máximo de conforto, para que você possa se concentrar na própria lógica. +Se você dominar e seguir estas regras, o Nette o apoiará em cada passo. Ele cuidará das tarefas rotineiras para você e fornecerá o máximo de conforto, para que você possa se concentrar na lógica em si. -Os princípios que vamos mostrar aqui são bastante simples. Você não tem que se preocupar com nada. +Os princípios que mostraremos aqui são bastante simples. Você não precisa se preocupar com nada. -Lembra-se de seu primeiro programa? .[#toc-remember-your-first-program] ------------------------------------------------------------------------ +Lembra do seu primeiro programa? +-------------------------------- -Não sabemos em que linguagem você a escreveu, mas se fosse PHP, poderia ter sido algo parecido com isto: +Não sabemos em que linguagem você o escreveu, mas se fosse PHP, provavelmente seria algo assim: ```php -function addition(float $a, float $b): float +function soucet(float $a, float $b): float { return $a + $b; } -echo addition(23, 1); // impressões 24 +echo soucet(23, 1); // imprime 24 ``` -Algumas linhas triviais de código, mas tantos conceitos-chave escondidos nelas. Que existem variáveis. Que esse código é dividido em unidades menores, que são funções, por exemplo. Que nós as passamos argumentos de entrada e elas retornam resultados. Tudo o que está faltando são condições e loops. +Algumas linhas triviais de código, mas nelas se escondem tantos conceitos-chave. Que existem variáveis. Que o código é dividido em unidades menores, como funções. Que passamos argumentos de entrada para elas e elas retornam resultados. Faltam apenas condições e loops. -O fato de uma função pegar dados de entrada e retornar um resultado é um conceito perfeitamente compreensível, que também é usado em outros campos, como a matemática. +O fato de passarmos dados de entrada para uma função e ela retornar um resultado é um conceito perfeitamente compreensível, usado também em outras áreas, como na matemática. -Uma função tem sua assinatura, que consiste em seu nome, uma lista de parâmetros e seus tipos, e finalmente o tipo do valor de retorno. Como usuários, estamos interessados na assinatura, e normalmente não precisamos saber nada sobre a implementação interna. +Uma função tem sua assinatura, que consiste em seu nome, uma lista de parâmetros e seus tipos, e finalmente o tipo do valor de retorno. Como usuários, estamos interessados na assinatura; geralmente não precisamos saber nada sobre a implementação interna. -Agora imagine que a assinatura da função tivesse este aspecto: +Agora imagine que a assinatura da função fosse assim: ```php -function addition(float $x): float +function soucet(float $x): float ``` -Uma adição com um parâmetro? Isso é estranho... E quanto a isto? +Soma com um parâmetro? Isso é estranho... E que tal assim? ```php -function addition(): float +function soucet(): float ``` -Isso é realmente estranho, certo? Como a função é utilizada? +Isso já é muito estranho, não é? Como a função seria usada? ```php -echo addition(); // o que imprime? +echo soucet(); // o que será que imprime? ``` -Olhando para tal código, ficaríamos confusos. Não só um iniciante não entenderia, mas até mesmo um programador experiente não entenderia tal código. +Ao olhar para tal código, ficaríamos confusos. Não apenas um iniciante não entenderia, mas nem mesmo um programador experiente entenderia tal código. -Você está se perguntando como seria realmente uma função desse tipo por dentro? Onde obteria as somas? Provavelmente, de alguma forma, ela os obteria sozinha, talvez assim: +Você está pensando como essa função seria por dentro? Onde ela obteria os operandos? Provavelmente, ela os obteria *de alguma forma* por conta própria, talvez assim: ```php -function addition(): float +function soucet(): float { $a = Input::get('a'); $b = Input::get('b'); @@ -60,71 +60,71 @@ function addition(): float } ``` -Acontece que existem ligações ocultas a outras funções (ou métodos estáticos) no corpo da função, e para descobrir de onde vêm os adendos de fato, temos que cavar mais. +No corpo da função, descobrimos ligações ocultas a outras funções globais ou métodos estáticos. Para descobrir de onde os operandos realmente vêm, precisamos investigar mais. -Não desta maneira! .[#toc-not-this-way] ---------------------------------------- +Não por aqui! +------------- -O projeto que acabamos de mostrar é a essência de muitas características negativas: +O design que acabamos de mostrar é a essência de muitas características negativas: -- a assinatura da função fingia que não precisava das somas, o que nos confundia -- não temos idéia de como fazer o cálculo da função com dois outros números -- tivemos que olhar para o código para descobrir de onde veio o summands -- encontramos dependências ocultas -- um entendimento completo requer o exame destas dependências também +- a assinatura da função fingia não precisar de operandos, o que nos confundiu +- não sabemos como fazer a função somar outros dois números +- tivemos que olhar o código para descobrir onde ela obtém os operandos +- descobrimos ligações ocultas +- para entender completamente, é necessário examinar também essas ligações -E é mesmo o trabalho da função de adição a aquisição de insumos? Claro que não é. Sua responsabilidade é apenas a de acrescentar. +E é tarefa da função de soma obter as entradas? Claro que não. Sua responsabilidade é apenas a soma em si. -Não queremos encontrar tal código, e certamente não queremos escrevê-lo. O remédio é simples: voltar ao básico e usar apenas parâmetros: +Não queremos encontrar tal código, e definitivamente não queremos escrevê-lo. A correção é simples: voltar ao básico e simplesmente usar parâmetros: ```php -function addition(float $a, float $b): float +function soucet(float $a, float $b): float { return $a + $b; } ``` -Regra nº 1: Deixe que seja passado para você .[#toc-rule-1-let-it-be-passed-to-you] ------------------------------------------------------------------------------------ +Regra nº 1: peça para ser passado +--------------------------------- -A regra mais importante é: **todos os dados que funcionam ou classes precisam ser passados a eles**. +A regra mais importante é: **todos os dados que uma função ou classe precisa devem ser passados para ela**. -Em vez de inventar formas ocultas de acesso aos dados em si, basta passar os parâmetros. Você economizará tempo que seria gasto inventando caminhos ocultos que certamente não irão melhorar seu código. +Em vez de inventar maneiras ocultas pelas quais eles poderiam obtê-los sozinhos, simplesmente passe os parâmetros. Você economizará o tempo necessário para inventar caminhos ocultos, que definitivamente não melhorarão seu código. -Se você sempre e em todos os lugares segue esta regra, está a caminho de codificar sem dependências ocultas. A um código que seja compreensível não só para o autor, mas também para qualquer pessoa que o leia depois. Onde tudo é compreensível a partir das assinaturas de funções e classes, e não há necessidade de buscar segredos ocultos na implementação. +Se você seguir esta regra sempre e em toda parte, estará no caminho para um código sem ligações ocultas. Para um código que é compreensível não apenas para o autor, mas também para qualquer pessoa que o leia depois dele. Onde tudo é compreensível a partir das assinaturas das funções e classes e não há necessidade de procurar segredos ocultos na implementação. -Esta técnica é denominada habilmente **injeção de dependência**. E os dados são chamados de **dependências**. Mas é um parâmetro simples de passagem, nada mais. +Essa técnica é tecnicamente chamada de **injeção de dependência**. E esses dados são chamados de **dependências.** Na verdade, é apenas a passagem comum de parâmetros, nada mais. .[note] -Por favor, não confunda a injeção por dependência, que é um padrão de projeto, com um "recipiente de injeção por dependência", que é uma ferramenta, algo diametralmente diferente. Trataremos dos recipientes mais tarde. +Por favor, não confunda injeção de dependência, que é um padrão de projeto, com "contêiner de injeção de dependência", que é uma ferramenta, ou seja, algo diametralmente diferente. Falaremos sobre contêineres mais tarde. -Das funções às aulas .[#toc-from-functions-to-classes] ------------------------------------------------------- +De funções para classes +----------------------- -E como as classes estão relacionadas? Uma classe é uma unidade mais complexa do que uma simples função, mas a regra nº 1 também se aplica inteiramente aqui. Há apenas [mais maneiras de passar argumentos |passing-dependencies]. Por exemplo, bem parecido com o caso de uma função: +E como as classes se relacionam com isso? Uma classe é uma unidade mais complexa do que uma função simples, mas a regra nº 1 se aplica integralmente aqui também. Apenas existem [mais opções para passar argumentos|passing-dependencies]. Por exemplo, de forma bastante semelhante ao caso de uma função: ```php -class Math +class Matematika { - public function addition(float $a, float $b): float + public function soucet(float $a, float $b): float { return $a + $b; } } -$math = new Math; -echo $math->addition(23, 1); // 24 +$math = new Matematika; +echo $math->soucet(23, 1); // 24 ``` -Ou através de outros métodos, ou diretamente através do construtor: +Ou usando outros métodos, ou diretamente o construtor: ```php -class Addition +class Soucet { public function __construct( private float $a, @@ -132,26 +132,26 @@ class Addition ) { } - public function calculate(): float + public function spocti(): float { return $this->a + $this->b; } } -$addition = new Addition(23, 1); -echo $addition->calculate(); // 24 +$soucet = new Soucet(23, 1); +echo $soucet->spocti(); // 24 ``` -Ambos os exemplos estão completamente de acordo com a injeção de dependência. +Ambos os exemplos estão totalmente de acordo com a injeção de dependência. -Exemplos da vida real .[#toc-real-life-examples] ------------------------------------------------- +Exemplos reais +-------------- -No mundo real, você não estará escrevendo aulas para adicionar números. Passemos a exemplos práticos. +No mundo real, você não escreverá classes para somar números. Vamos passar para exemplos práticos. -Vamos ter uma aula `Article` representando um post no blog: +Temos uma classe `Article` representando um artigo de blog: ```php class Article @@ -162,23 +162,23 @@ class Article public function save(): void { - // salvar o artigo no banco de dados + // salvamos o artigo no banco de dados } } ``` -e a utilização será a seguinte: +e o uso será o seguinte: ```php $article = new Article; -$article->title = '10 Things You Need to Know About Losing Weight'; -$article->content = 'Every year millions of people in ...'; +$article->title = '10 coisas que você precisa saber sobre perder peso'; +$article->content = 'Todo ano milhões de pessoas em ...'; $article->save(); ``` -O método `save()` salvará o artigo em uma tabela de banco de dados. A implementação usando o [Nette Database |database:] será canja, se não fosse por uma única questão: onde `Article` obtém a conexão com o banco de dados, ou seja, um objeto de classe `Nette\Database\Connection`? +O método `save()` salva o artigo em uma tabela do banco de dados. Implementá-lo usando [Nette Database |database:] seria moleza, se não fosse por um obstáculo: onde `Article` obtém a conexão com o banco de dados, ou seja, o objeto da classe `Nette\Database\Connection`? -Parece que temos muitas opções. Ele pode tirá-lo de uma variável estática em algum lugar. Ou herdá-la de uma classe que fornece uma conexão de banco de dados. Ou tirar vantagem de um [único botão |global-state#Singleton]. Ou usar as chamadas fachadas, que são usadas em Laravel: +Parece que temos muitas opções. Pode obtê-lo de algum lugar em uma variável estática. Ou herdar de uma classe que fornece a conexão com o banco de dados. Ou usar o chamado [singleton |global-state#Singleton]. Ou as chamadas facades, que são usadas no Laravel: ```php use Illuminate\Support\Facades\DB; @@ -199,17 +199,17 @@ class Article } ``` -Ótimo, nós resolvemos o problema. +Ótimo, resolvemos o problema. -Ou temos? +Ou não? -Vamos relembrar a [regra nº 1: Que seja passada a você |#rule #1: Let It Be Passed to You]: todas as dependências que a classe precisa devem ser passadas a ela. Porque se quebrarmos a regra, embarcamos num caminho de código sujo cheio de dependências ocultas, incompreensíveis, e o resultado será uma aplicação que será dolorosa de manter e desenvolver. +Lembre-se da [##Regra nº 1: peça para ser passado]: todas as dependências que a classe precisa devem ser passadas para ela. Porque se quebrarmos a regra, entramos no caminho do código sujo cheio de ligações ocultas, incompreensibilidade, e o resultado será uma aplicação que será dolorosa de manter e desenvolver. -O usuário da classe `Article` não tem idéia onde o método `save()` armazena o artigo. Em uma tabela de banco de dados? Qual delas, produção ou teste? E como pode ser mudado? +O usuário da classe `Article` não tem ideia de onde o método `save()` salva o artigo. Em uma tabela do banco de dados? Em qual, produção ou teste? E como isso pode ser alterado? -O usuário tem que olhar como o método `save()` é implementado, e encontra o uso do método `DB::insert()`. Portanto, ele tem que pesquisar mais para descobrir como este método obtém uma conexão de banco de dados. E as dependências ocultas podem formar uma cadeia bastante longa. +O usuário precisa olhar como o método `save()` é implementado e encontra o uso do método `DB::insert()`. Então, ele precisa investigar mais, como esse método obtém a conexão com o banco de dados. E as ligações ocultas podem formar uma cadeia bastante longa. -Em código limpo e bem projetado, nunca há dependências ocultas, fachadas de Laravel, ou variáveis estáticas. Em código limpo e bem desenhado, os argumentos são passados: +Em código limpo e bem projetado, nunca existem ligações ocultas, facades do Laravel ou variáveis estáticas. Em código limpo e bem projetado, os argumentos são passados: ```php class Article @@ -224,7 +224,7 @@ class Article } ``` -Uma abordagem ainda mais prática, como veremos mais adiante, será através do construtor: +Ainda mais prático, como veremos mais adiante, será pelo construtor: ```php class Article @@ -245,11 +245,11 @@ class Article ``` .[note] -Se você é um programador experiente, você pode pensar que `Article` não deveria ter um método `save()`; ele deveria representar um componente de dados puramente, e um repositório separado deveria se encarregar de salvar. Isso faz sentido. Mas isso nos levaria muito além do escopo do tópico, que é a injeção de dependência, e o esforço para fornecer exemplos simples. +Se você é um programador experiente, pode estar pensando que `Article` não deveria ter um método `save()`, deveria representar puramente um componente de dados e o armazenamento deveria ser responsabilidade de um repositório separado. Isso faz sentido. Mas isso nos levaria muito além do escopo do tópico, que é a injeção de dependência, e do esforço para fornecer exemplos simples. -Se você escreve uma classe que requer, por exemplo, um banco de dados para seu funcionamento, não invente de onde obtê-lo, mas faça com que ele passe. Seja como um parâmetro do construtor ou outro método. Admita dependências. Admita-as na API de sua classe. Você obterá um código compreensível e previsível. +Se você for escrever uma classe que requer, por exemplo, um banco de dados para sua operação, não invente de onde obtê-lo, mas peça para que seja passado. Talvez como um parâmetro do construtor ou de outro método. Admita as dependências. Admita-as na API da sua classe. Você obterá um código compreensível e previsível. -E quanto a esta classe, que registra mensagens de erro: +E que tal esta classe, que registra mensagens de erro: ```php class Logger @@ -262,23 +262,23 @@ class Logger } ``` -O que você acha, nós seguimos a [regra nº 1: Deixe que seja passado para você |#rule #1: Let It Be Passed to You]? +O que você acha, seguimos a [##Regra nº 1: peça para ser passado]? -Nós não o fizemos. +Não seguimos. -A informação chave, ou seja, o diretório com o arquivo de log, é *obtida* pela própria classe a partir da constante. +A informação chave, ou seja, o diretório com o arquivo de log, a classe *obtém por si mesma* a partir de uma constante. -Vejam o exemplo de uso: +Veja o exemplo de uso: ```php $logger = new Logger; -$logger->log('The temperature is 23 °C'); -$logger->log('The temperature is 10 °C'); +$logger->log('A temperatura é 23 °C'); +$logger->log('A temperatura é 10 °C'); ``` -Sem conhecer a implementação, você poderia responder à questão de onde as mensagens são escritas? Você adivinharia que a existência da constante `LOG_DIR` é necessária para seu funcionamento? E você poderia criar uma segunda instância que escrevesse para um local diferente? Certamente que não. +Sem conhecer a implementação, você conseguiria responder à pergunta de onde as mensagens são escritas? Você pensaria que para funcionar é necessária a existência da constante `LOG_DIR`? E você conseguiria criar uma segunda instância que escreveria em outro lugar? Certamente não. -Vamos consertar a classe: +Vamos corrigir a classe: ```php class Logger @@ -295,24 +295,24 @@ class Logger } ``` -A classe é agora muito mais compreensível, configurável e, portanto, mais útil. +A classe agora é muito mais compreensível, configurável e, portanto, mais útil. ```php -$logger = new Logger('/path/to/log.txt'); -$logger->log('The temperature is 15 °C'); +$logger = new Logger('/caminho/para/log.txt'); +$logger->log('A temperatura é 15 °C'); ``` -Mas eu não me importo! .[#toc-but-i-don-t-care] ------------------------------------------------ +Mas isso não me interessa! +-------------------------- -*"Quando eu crio um objeto de Artigo e chamo salvar(), eu não quero lidar com o banco de dados; eu só quero que ele seja salvo no que eu defini na configuração."* +*"Quando crio um objeto Article e chamo save(), não quero lidar com o banco de dados, só quero que ele seja salvo naquele que configurei."* -*"Quando uso o Logger, só quero que a mensagem seja escrita, e não quero lidar com onde. Deixe que as configurações globais sejam usadas."* +*"Quando uso o Logger, só quero que a mensagem seja escrita, e não quero me preocupar onde. Que use a configuração global."* -Estes são pontos válidos. +Essas são observações válidas. -Como exemplo, vejamos uma aula que envia boletins informativos e registros de como foi: +Como exemplo, mostraremos uma classe que envia newsletters e registra o resultado: ```php class NewsletterDistributor @@ -322,27 +322,27 @@ class NewsletterDistributor $logger = new Logger(/* ... */); try { $this->sendEmails(); - $logger->log('Emails have been sent out'); + $logger->log('E-mails foram enviados'); } catch (Exception $e) { - $logger->log('An error occurred during the sending'); + $logger->log('Ocorreu um erro ao enviar'); throw $e; } } } ``` -O melhorado `Logger`, que não usa mais a constante `LOG_DIR`, requer a especificação do caminho do arquivo no construtor. Como resolver isso? A classe `NewsletterDistributor` não se importa onde as mensagens são escritas; ela só quer escrevê-las. +O `Logger` aprimorado, que não usa mais a constante `LOG_DIR`, requer que o caminho do arquivo seja especificado no construtor. Como resolver isso? A classe `NewsletterDistributor` não se importa onde as mensagens são escritas, ela só quer escrevê-las. -A solução é novamente a [regra nº 1: Que seja passada a você |#rule #1: Let It Be Passed to You]: passe todos os dados que a classe precisa. +A solução é novamente a [##Regra nº 1: peça para ser passado]: todos os dados que a classe precisa, nós passamos para ela. -Então isso significa que passamos o caminho para o tronco através do construtor, que depois usamos ao criar o objeto `Logger`? +Então isso significa que passamos o caminho do log através do construtor, que então usamos ao criar o objeto `Logger`? ```php class NewsletterDistributor { public function __construct( - private string $file, // ⛔ NÃO DESTA FORMA! + private string $file, // ⛔ ASSIM NÃO! ) { } @@ -351,7 +351,7 @@ class NewsletterDistributor $logger = new Logger($this->file); ``` -Não, assim não! O caminho não faz parte dos dados que a classe `NewsletterDistributor` precisa; na verdade, o `Logger` precisa dele. Você vê a diferença? A classe `NewsletterDistributor` precisa do próprio madeireiro. Então, é isso que vamos passar: +Assim não! O caminho, de fato, **não pertence** aos dados que a classe `NewsletterDistributor` precisa; esses são necessários pelo `Logger`. Você percebe a diferença? A classe `NewsletterDistributor` precisa do logger como tal. Então, passamos ele: ```php class NewsletterDistributor @@ -365,35 +365,33 @@ class NewsletterDistributor { try { $this->sendEmails(); - $this->logger->log('Emails have been sent out'); + $this->logger->log('E-mails foram enviados'); } catch (Exception $e) { - $this->logger->log('An error occurred during the sending'); + $this->logger->log('Ocorreu um erro ao enviar'); throw $e; } } } ``` -Agora fica claro a partir das assinaturas da classe `NewsletterDistributor` que a extração de madeira também faz parte de sua funcionalidade. E a tarefa de trocar o madeireiro por outro, talvez para testes, é completamente trivial. -Além disso, se o construtor da classe `Logger` mudar, isso não afetará nossa classe. +Agora está claro pelas assinaturas da classe `NewsletterDistributor` que o log faz parte de sua funcionalidade. E a tarefa de trocar o logger por outro, talvez para testes, é completamente trivial. Além disso, se o construtor da classe `Logger` mudar, isso não terá nenhum efeito em nossa classe. -Regra # 2: Tome o que é seu .[#toc-rule-2-take-what-s-yours] ------------------------------------------------------------- +Regra nº 2: pegue o que é seu +----------------------------- -Não se deixe enganar e não se deixe passar pelas dependências de suas dependências. Basta passar suas próprias dependências. +Não se deixe enganar e não peça para passar as dependências de suas dependências. Peça para passar apenas suas dependências. -Graças a isso, o código que utiliza outros objetos será completamente independente das mudanças em seus construtores. Sua API será mais verdadeira. E acima de tudo, será trivial substituir estas dependências por outras. +Graças a isso, o código que utiliza outros objetos será completamente independente das mudanças em seus construtores. Sua API será mais verdadeira. E, principalmente, será trivial trocar essas dependências por outras. -Novo membro da família .[#toc-new-family-member] ------------------------------------------------- +Novo membro da família +---------------------- -A equipe de desenvolvimento decidiu criar um segundo logger que escreva para o banco de dados. Por isso, criamos uma classe `DatabaseLogger`. Então temos duas classes, `Logger` e `DatabaseLogger`, uma que escreve para um arquivo, a outra para um banco de dados ... o nome não lhe parece estranho? -Não seria melhor renomear `Logger` para `FileLogger`? Definitivamente sim. +Na equipe de desenvolvimento, foi decidido criar um segundo logger, que escreve no banco de dados. Criaremos então a classe `DatabaseLogger`. Então temos duas classes, `Logger` e `DatabaseLogger`, uma escreve em arquivo, a outra no banco de dados... não parece algo estranho nessa nomenclatura? Não seria melhor renomear `Logger` para `FileLogger`? Certamente sim. -Mas façamos isso de forma inteligente. Criamos uma interface com o nome original: +Mas faremos isso de forma inteligente. Sob o nome original, criaremos uma interface: ```php interface Logger @@ -402,7 +400,7 @@ interface Logger } ``` -... que ambos os madeireiros irão implementar: +... que ambos os loggers implementarão: ```php class FileLogger implements Logger @@ -412,17 +410,17 @@ class DatabaseLogger implements Logger // ... ``` -E por causa disso, não haverá necessidade de alterar nada no resto do código onde o madeireiro é utilizado. Por exemplo, o construtor da classe `NewsletterDistributor` ainda estará satisfeito com a exigência de `Logger` como parâmetro. E caberá a nós qual instância passaremos. +E graças a isso, não será necessário alterar nada no restante do código onde o logger é utilizado. Por exemplo, o construtor da classe `NewsletterDistributor` continuará satisfeito em exigir `Logger` como parâmetro. E caberá a nós qual instância passar para ele. -**É por isso que nunca adicionamos o sufixo `Interface` ou o prefixo `I` aos nomes das interfaces.** Caso contrário, não seria possível desenvolver o código tão bem. +**Por isso, nunca damos aos nomes das interfaces o sufixo `Interface` ou o prefixo `I`.** Caso contrário, não seria possível desenvolver o código de forma tão elegante. -Houston, temos um problema .[#toc-houston-we-have-a-problem] ------------------------------------------------------------- +Houston, temos um problema +-------------------------- -Embora possamos passar com uma única instância do registrador, seja baseada em arquivo ou em banco de dados, em toda a aplicação e simplesmente passá-la onde quer que algo esteja registrado, é bastante diferente para a classe `Article`. Criamos suas instâncias conforme a necessidade, mesmo várias vezes. Como lidar com a dependência do banco de dados em seu construtor? +Enquanto em toda a aplicação podemos nos contentar com uma única instância de logger, seja de arquivo ou de banco de dados, e simplesmente passá-la para todos os lugares onde algo é registrado, a situação é bem diferente no caso da classe `Article`. Suas instâncias são criadas conforme necessário, até mesmo várias vezes. Como lidar com a dependência do banco de dados em seu construtor? -Um exemplo pode ser um controlador que deve salvar um artigo no banco de dados depois de submeter um formulário: +Como exemplo, pode servir um controller que, após o envio de um formulário, deve salvar o artigo no banco de dados: ```php class EditController extends Controller @@ -437,30 +435,30 @@ class EditController extends Controller } ``` -Uma possível solução é óbvia: passar o objeto do banco de dados para o construtor `EditController` e usar `$article = new Article($this->db)`. +Uma solução possível se oferece diretamente: passamos o objeto do banco de dados pelo construtor para `EditController` e usamos `$article = new Article($this->db)`. -Assim como no caso anterior com `Logger` e o caminho do arquivo, esta não é a abordagem correta. O banco de dados não é uma dependência do `EditController`, mas do `Article`. Passar o banco de dados vai contra a [regra nº 2: pegue o que é seu |#rule #2: take what's yours]. Se o construtor da classe `Article` mudar (um novo parâmetro é adicionado), você precisará modificar o código onde quer que as instâncias sejam criadas. Ufff. +Assim como no caso anterior com `Logger` e o caminho do arquivo, este não é o procedimento correto. O banco de dados não é uma dependência de `EditController`, mas de `Article`. Passar o banco de dados, portanto, vai contra a [#regra nº 2: pegue o que é seu]. Quando o construtor da classe `Article` mudar (um novo parâmetro for adicionado), será necessário modificar também o código em todos os lugares onde instâncias são criadas. Ufa. Houston, o que você sugere? -Regra nº 3: Deixe a Fábrica tratar disso .[#toc-rule-3-let-the-factory-handle-it] ---------------------------------------------------------------------------------- +Regra nº 3: deixe para a fábrica +-------------------------------- -Ao eliminar dependências ocultas e passar todas as dependências como argumentos, ganhamos classes mais configuráveis e flexíveis. E, portanto, precisamos de algo mais para criar e configurar essas classes mais flexíveis para nós. Vamos chamá-la de fábricas. +Ao eliminar as ligações ocultas e passar todas as dependências como argumentos, obtivemos classes mais configuráveis e flexíveis. E, portanto, precisamos de algo mais, que crie e configure essas classes mais flexíveis para nós. Chamaremos isso de fábricas. -A regra básica é: se uma classe tem dependências, deixar a criação de suas instâncias para a fábrica. +A regra é: se uma classe tem dependências, deixe a criação de suas instâncias para a fábrica. -As fábricas são um substituto mais inteligente para o operador `new` no mundo da injeção de dependência. +As fábricas são substitutos mais inteligentes do operador `new` no mundo da injeção de dependência. .[note] -Por favor, não confunda com o padrão de projeto *método de fábrica*, que descreve uma maneira específica de usar as fábricas e não está relacionado a este tópico. +Por favor, não confunda com o padrão de projeto *factory method*, que descreve um uso específico de fábricas e não está relacionado a este tópico. -Fábrica .[#toc-factory] ------------------------ +Fábrica +------- -Uma fábrica é um método ou classe que cria e configura objetos. Vamos nomear a classe que produz `Article` como `ArticleFactory`, e pode parecer assim: +Uma fábrica é um método ou classe que produz e configura objetos. A classe que produz `Article` chamaremos de `ArticleFactory` e poderia parecer, por exemplo, assim: ```php class ArticleFactory @@ -477,7 +475,7 @@ class ArticleFactory } ``` -Sua utilização no controlador será a seguinte: +Seu uso no controller será o seguinte: ```php class EditController extends Controller @@ -489,7 +487,7 @@ class EditController extends Controller public function formSubmitted($data) { - // deixar a fábrica criar um objeto + // deixamos a fábrica criar o objeto $article = $this->articleFactory->create(); $article->title = $data->title; $article->content = $data->content; @@ -498,11 +496,11 @@ class EditController extends Controller } ``` -Neste ponto, se a assinatura do construtor da classe `Article` mudar, a única parte do código que precisa reagir é o próprio `ArticleFactory`. Todos os outros códigos que trabalham com objetos `Article`, como o `EditController`, não serão afetados. +Neste momento, se a assinatura do construtor da classe `Article` mudar, a única parte do código que precisa reagir é a própria fábrica `ArticleFactory`. Todo o restante do código que trabalha com objetos `Article`, como `EditController`, não será afetado de forma alguma. -Você pode estar se perguntando se nós realmente fizemos as coisas melhorarem. A quantidade de código aumentou, e tudo começa a parecer suspeitosamente complicado. +Talvez você esteja batendo na testa agora, se realmente nos ajudamos. A quantidade de código aumentou e tudo começa a parecer suspeitosamente complicado. -Não se preocupe, logo chegaremos ao recipiente Nette DI. E ele tem vários truques na manga, o que simplificará muito as aplicações de construção usando a injeção de dependência. Por exemplo, ao invés da classe `ArticleFactory`, você só precisará [escrever uma interface simples |factory]: +Não se preocupe, em breve chegaremos ao Contêiner de DI do Nette. E ele tem vários ases na manga que simplificarão imensamente a construção de aplicações usando injeção de dependência. Por exemplo, em vez da classe `ArticleFactory`, será suficiente [escrever apenas uma interface |factory]: ```php interface ArticleFactory @@ -511,18 +509,18 @@ interface ArticleFactory } ``` -Mas estamos nos adiantando; por favor, seja paciente :-) +Mas estamos nos adiantando, aguarde mais um pouco :-) -Sumário .[#toc-summary] ------------------------ +Resumo +------ -No início deste capítulo, prometemos mostrar-lhe um processo para projetar um código limpo. Tudo o que é preciso é que as aulas o façam: +No início deste capítulo, prometemos mostrar um procedimento para projetar código limpo. Basta para as classes -- [passar as dependências de que necessitam |#Rule #1: Let It Be Passed to You] -- [por outro lado, não passar o que eles não precisam diretamente |#Rule #2: Take What's Yours] -- [e que os objetos com dependências são melhor criados em fábricas |#Rule #3: Let the Factory Handle it] +1) [passar as dependências que precisam |#Regra nº 1: peça para ser passado |#pravidlo č. 1: nech si to předat] +2) [e, inversamente, não passar o que não precisam diretamente |#Regra nº 2: pegue o que é seu |#Pravidlo č. 2: ber, co tvé jest] +3) [e que objetos com dependências são melhor criados em fábricas |#Regra nº 3: deixe para a fábrica |#Pravidlo č. 3: nech to na továrně] -À primeira vista, estas três regras podem não parecer ter conseqüências de longo alcance, mas elas levam a uma perspectiva radicalmente diferente sobre o desenho de códigos. Será que vale a pena? Os desenvolvedores que abandonaram velhos hábitos e começaram a usar de forma consistente a injeção de dependência consideram esta etapa um momento crucial em suas vidas profissionais. Ela abriu o mundo de aplicações claras e de fácil manutenção para eles. +Pode não parecer à primeira vista, mas essas três regras têm consequências de longo alcance. Elas levam a uma visão radicalmente diferente do design de código. Vale a pena? Programadores que abandonaram velhos hábitos e começaram a usar consistentemente a injeção de dependência consideram este passo um momento crucial em suas vidas profissionais. Abriu-se para eles o mundo de aplicações claras e sustentáveis. -Mas e se o código não usar a injeção de dependência de forma consistente? E se ele se baseia em métodos estáticos ou singletons? Isso causa algum problema? [Sim, e muito fundamentais |global-state]. +Mas e se o código não usar consistentemente a injeção de dependência? E se for construído sobre métodos estáticos ou singletons? Isso traz algum problema? [Traz e muito fundamentais |global-state]. diff --git a/dependency-injection/pt/nette-container.texy b/dependency-injection/pt/nette-container.texy index 98e8eee77c..d7373bd24d 100644 --- a/dependency-injection/pt/nette-container.texy +++ b/dependency-injection/pt/nette-container.texy @@ -1,10 +1,10 @@ -Container Nette DI -****************** +Contêiner de DI do Nette +************************ .[perex] -Nette DI é uma das mais interessantes bibliotecas Nette. Ela pode gerar e atualizar automaticamente containers DI compilados que são extremamente rápidos e incrivelmente fáceis de configurar. +Nette DI é uma das bibliotecas mais interessantes do Nette. Ela pode gerar e atualizar automaticamente contêineres de DI compilados, que são extremamente rápidos e incrivelmente fáceis de configurar. -Os serviços a serem criados por um contêiner DI são geralmente definidos utilizando arquivos de configuração no [formato NEON |neon:format]. O container que criamos manualmente na [seção anterior |container] seria escrito da seguinte forma: +A forma dos serviços que o Contêiner de DI deve criar é geralmente definida usando arquivos de configuração no [formato NEON|neon:format]. O contêiner que criamos manualmente no [capítulo anterior|container] seria escrito assim: ```neon parameters: @@ -19,16 +19,15 @@ services: - UserController ``` -A notação é realmente breve. +A notação é realmente concisa. -Todas as dependências declaradas nos construtores das classes `ArticleFactory` e `UserController` são encontradas e passadas pela própria Nette DI graças à chamada [autoconexão |autowiring], portanto não há necessidade de especificar nada no arquivo de configuração. -Portanto, mesmo que os parâmetros mudem, não há necessidade de alterar nada na configuração. A Nette irá regenerar automaticamente o recipiente. Você pode se concentrar lá puramente no desenvolvimento de aplicações. +Todas as dependências declaradas nos construtores das classes `ArticleFactory` e `UserController` são descobertas e passadas automaticamente pelo Nette DI graças ao chamado [autowiring|autowiring], portanto, não é necessário especificar nada no arquivo de configuração. Assim, mesmo que os parâmetros mudem, você não precisa alterar nada na configuração. O contêiner Nette é regenerado automaticamente. Você pode se concentrar puramente no desenvolvimento da aplicação. -Se você quiser passar dependências usando setters, use a seção de [configuração |services#setup] para fazer isso. +Se quisermos passar dependências usando setters, usamos a seção [setup |services#Setup] para isso. -Nette DI irá gerar diretamente o código PHP para o recipiente. O resultado é, portanto, um arquivo `.php` que você pode abrir e estudar. Isto permite que você veja exatamente como o contêiner funciona. Você também pode depurá-lo na IDE e passar por ela. E o mais importante: o PHP gerado é extremamente rápido. +Nette DI gera diretamente o código PHP do contêiner. O resultado é, portanto, um arquivo `.php` que você pode abrir e estudar. Graças a isso, você vê exatamente como o contêiner funciona. Você também pode depurá-lo no IDE e percorrer passo a passo. E o mais importante: o PHP gerado é extremamente rápido. -A Nette DI também pode gerar código de [fábrica |factory] com base na interface fornecida. Portanto, ao invés da classe `ArticleFactory`, precisamos apenas criar uma interface na aplicação: +Nette DI também pode gerar código para [fábricas|factory] com base na interface fornecida. Portanto, em vez da classe `ArticleFactory`, basta criar apenas uma interface na aplicação: ```php interface ArticleFactory @@ -37,19 +36,19 @@ interface ArticleFactory } ``` -Você pode encontrar o exemplo completo [no GitHub |https://github.com/nette-examples/di-example-doc]. +Você pode encontrar o exemplo completo [no GitHub|https://github.com/nette-examples/di-example-doc]. -Uso autônomo .[#toc-standalone-use] ------------------------------------ +Uso independente +---------------- -A utilização da biblioteca Nette DI em uma aplicação é muito fácil. Primeiro a instalamos com o Composer (porque o download de arquivos zip está tão desatualizado): +Implantar a biblioteca Nette DI em uma aplicação é muito fácil. Primeiro, instalamos com o Composer (porque baixar zips é tããão ultrapassado): ```shell composer require nette/di ``` -O seguinte código cria uma instância do recipiente DI de acordo com a configuração armazenada no arquivo `config.neon`: +O código a seguir cria uma instância do Contêiner de DI de acordo com a configuração armazenada no arquivo `config.neon`: ```php $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp'); @@ -59,24 +58,23 @@ $class = $loader->load(function ($compiler) { $container = new $class; ``` -O container é gerado apenas uma vez, seu código é escrito no cache (o diretório `__DIR__ . '/temp'` ) e em pedidos subsequentes é lido apenas a partir daí. +O contêiner é gerado apenas uma vez, seu código é escrito no cache (diretório `__DIR__ . '/temp'`) e nas requisições subsequentes ele é apenas carregado de lá. -Os métodos `getService()` ou `getByType()` são usados para criar e recuperar serviços. É assim que criamos o objeto `UserController`: +Para criar e obter serviços, são usados os métodos `getService()` ou `getByType()`. Assim criamos o objeto `UserController`: ```php -$database = $container->getByType(UserController::class); -$database->query('...'); +$controller = $container->getByType(UserController::class); +$controller->someMethod(); ``` -Durante o desenvolvimento, é útil ativar o modo de atualização automática, onde o recipiente é automaticamente regenerado se qualquer classe ou arquivo de configuração for alterado. Basta fornecer `true` como segundo argumento no construtor do `ContainerLoader`. +Durante o desenvolvimento, é útil ativar o modo de atualização automática, onde o contêiner é automaticamente regenerado se qualquer classe ou arquivo de configuração for alterado. Basta especificar `true` como segundo argumento no construtor `ContainerLoader`. ```php -$loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp', true); +$loader = new ContainerLoader(__DIR__ . '/temp', autoRebuild: true); ``` -Usando-o com a estrutura Nette .[#toc-using-it-with-the-nette-framework] ------------------------------------------------------------------------- +Uso com o framework Nette +------------------------- -Como demonstramos, o uso de Nette DI não está limitado a aplicações escritas no Nette Framework, você pode implementá-lo em qualquer lugar com apenas 3 linhas de código. -Entretanto, se você estiver desenvolvendo aplicações no Nette Framework, a configuração e a criação do container é feita pelo [Bootstrap |application:bootstrap#toc-di-container-configuration]. +Como mostramos, o uso do Nette DI não se limita a aplicações escritas no Nette Framework, você pode implantá-lo em qualquer lugar com apenas 3 linhas de código. No entanto, se você desenvolve aplicações no Nette Framework, a configuração e criação do contêiner são de responsabilidade do [Bootstrap |application:bootstrapping#Configuração do contêiner de DI]. diff --git a/dependency-injection/pt/passing-dependencies.texy b/dependency-injection/pt/passing-dependencies.texy index a6b4434e8d..303ddb09c0 100644 --- a/dependency-injection/pt/passing-dependencies.texy +++ b/dependency-injection/pt/passing-dependencies.texy @@ -1,24 +1,24 @@ -Dependências de passagem -************************ +Passando Dependências +********************* <div class=perex> -Argumentos, ou "dependências" na terminologia DI, podem ser passados às aulas das seguintes maneiras principais: +Argumentos, ou na terminologia de DI "dependências", podem ser passados para classes das seguintes maneiras principais: -* passando por construtor -* passando por método (chamado setter) -* definindo uma propriedade -*por método, anotação ou atributo *injectar* +* passagem pelo construtor +* passagem por método (chamado setter) +* configuração de propriedade +* método, anotação ou atributo *inject* </div> -Vamos agora ilustrar as diferentes variantes com exemplos concretos. +Agora mostraremos cada variante com exemplos concretos. -Injeção do construtor .[#toc-constructor-injection] -=================================================== +Passagem pelo construtor +======================== -As dependências são passadas como argumentos para o construtor quando o objeto é criado: +As dependências são passadas no momento da criação do objeto como argumentos do construtor: ```php class MyClass @@ -34,9 +34,9 @@ class MyClass $obj = new MyClass($cache); ``` -Este formulário é útil para as dependências obrigatórias que a classe precisa absolutamente funcionar, pois sem elas a instância não pode ser criada. +Esta forma é adequada para dependências obrigatórias que a classe necessita essencialmente para sua função, pois sem elas a instância não poderá ser criada. -Desde o PHP 8.0, podemos usar uma forma mais curta de notação que é funcionalmente equivalente ([constructor property promotion |https://blog.nette.org/pt/php-8-0-visao-geral-completa-das-noticias#toc-constructor-property-promotion]): +A partir do PHP 8.0, podemos usar uma forma mais curta de notação ([constructor property promotion |https://blog.nette.org/pt/php-8-0-complete-overview-of-news#toc-constructor-property-promotion]), que é funcionalmente equivalente: ```php // PHP 8.0 @@ -49,7 +49,7 @@ class MyClass } ``` -A partir do PHP 8.1, uma propriedade pode ser marcada com uma bandeira `readonly` que declara que o conteúdo da propriedade não mudará: +A partir do PHP 8.1, a propriedade pode ser marcada com o sinalizador `readonly`, que declara que o conteúdo da propriedade não mudará mais: ```php // PHP 8.1 @@ -62,13 +62,13 @@ class MyClass } ``` -Recipiente DI passa automaticamente as dependências para o construtor usando [a fiação automática |autowiring]. Argumentos que não podem ser passados desta forma (por exemplo, strings, números, booleans) [escrevem na configuração |services#Arguments]. +O contêiner de DI passa as dependências para o construtor automaticamente usando [autowiring |autowiring]. Argumentos que não podem ser passados dessa forma (por exemplo, strings, números, booleanos) [escrevemos na configuração |services#Argumentos]. -Construtor Inferno .[#toc-constructor-hell] -------------------------------------------- +Constructor hell +---------------- -O termo *inferno construtor* refere-se a uma situação em que uma criança herda de uma classe de pais cujo construtor requer dependências, e a criança requer dependências também. Também deve assumir e transmitir as dependências dos pais: +O termo *constructor hell* descreve a situação em que um descendente herda de uma classe pai cujo construtor requer dependências, e ao mesmo tempo o descendente requer dependências. Ele também deve receber e passar as dependências do pai: ```php abstract class BaseClass @@ -94,11 +94,11 @@ final class MyClass extends BaseClass } ``` -O problema ocorre quando queremos mudar o construtor da classe `BaseClass`, por exemplo, quando uma nova dependência é acrescentada. Então temos que modificar todos os construtores das crianças também. O que faz de tal modificação um inferno. +O problema surge no momento em que queremos alterar o construtor da classe `BaseClass`, por exemplo, quando uma nova dependência é adicionada. Então, é necessário modificar também todos os construtores dos descendentes. O que torna tal modificação um inferno. -Como evitar isso? A solução é **priorizar a composição sobre a herança***. +Como evitar isso? A solução é **dar preferência à [composição em vez de herança |faq#Por que a composição é preferida em relação à herança]**. -Portanto, vamos projetar o código de forma diferente. Evitaremos as aulas abstratas `Base*`. Ao invés de `MyClass` obter alguma funcionalidade herdando de `BaseClass`, terá essa funcionalidade passada como uma dependência: +Ou seja, projetaremos o código de forma diferente. Evitaremos classes [abstratas |nette:introduction-to-object-oriented-programming#Classes Abstratas] `Base*`. Em vez de `MyClass` obter certas funcionalidades herdando de `BaseClass`, essa funcionalidade será passada como dependência: ```php final class SomeFunctionality @@ -125,10 +125,10 @@ final class MyClass ``` -Injeção de setter .[#toc-setter-injection] -========================================== +Passagem por setter +=================== -As dependências são passadas chamando um método que as armazena em uma propriedade privada. A convenção usual de nomes para estes métodos é a forma `set*()`, razão pela qual eles são chamados de setters, mas é claro que eles podem ser chamados de qualquer outra coisa. +As dependências são passadas chamando um método que as armazena em uma propriedade privada. A convenção usual de nomenclatura para esses métodos é a forma `set*()`, por isso são chamados de setters, mas podem, é claro, ter qualquer outro nome. ```php class MyClass @@ -145,9 +145,9 @@ $obj = new MyClass; $obj->setCache($cache); ``` -Este método é útil para dependências opcionais que não são necessárias para a função de classe, uma vez que não é garantido que o objeto realmente as receberá (ou seja, que o usuário chamará o método). +Este método é adequado para dependências opcionais que não são essenciais para a função da classe, pois não há garantia de que o objeto realmente receberá a dependência (ou seja, que o usuário chamará o método). -Ao mesmo tempo, este método permite que o setter seja chamado repetidamente para mudar a dependência. Se isto não for desejável, acrescente uma verificação ao método, ou a partir do PHP 8.1, marque a propriedade `$cache` com a bandeira `readonly`. +Ao mesmo tempo, este método permite chamar o setter repetidamente e, assim, alterar a dependência. Se isso não for desejado, adicionamos uma verificação ao método ou, a partir do PHP 8.1, marcamos a propriedade `$cache` com o sinalizador `readonly`. ```php class MyClass @@ -156,29 +156,28 @@ class MyClass public function setCache(Cache $cache): void { - if ($this->cache) { - throw new RuntimeException('The dependency has already been set'); + if (isset($this->cache)) { + throw new \RuntimeException('A dependência já foi definida'); } $this->cache = $cache; } } ``` -A chamada do setter é definida na configuração do recipiente DI na [configuração da seção |services#Setup]. Também aqui a passagem automática de dependências é usada por cabeamento automático: +A chamada do setter é definida na configuração do contêiner de DI na [chave setup |services#Setup]. Aqui também se utiliza a passagem automática de dependências por autowiring: ```neon services: - - - create: MyClass + - create: MyClass setup: - setCache ``` -Injeção de propriedade .[#toc-property-injection] -================================================= +Configuração de propriedade +=========================== -As dependências são passadas diretamente para a propriedade: +As dependências são passadas escrevendo diretamente na propriedade de membro: ```php class MyClass @@ -190,28 +189,27 @@ $obj = new MyClass; $obj->cache = $cache; ``` -Este método é considerado inadequado porque a propriedade deve ser declarada como `public`. Assim, não temos controle sobre se a dependência passada será realmente do tipo especificado (isto era verdade antes do PHP 7.4) e perdemos a capacidade de reagir à dependência recém-atribuída com nosso próprio código, por exemplo, para evitar mudanças subseqüentes. Ao mesmo tempo, a propriedade se torna parte da interface pública da classe, o que pode não ser desejável. +Este método é considerado inadequado porque a propriedade de membro deve ser declarada como `public`. E, portanto, não temos controle sobre se a dependência passada será realmente do tipo especificado (válido antes do PHP 7.4) e perdemos a capacidade de reagir à dependência recém-atribuída com código próprio, por exemplo, para impedir alterações subsequentes. Ao mesmo tempo, a propriedade se torna parte da interface pública da classe, o que pode não ser desejável. -A configuração da variável é definida na configuração do recipiente DI na [configuração da seção |services#Setup]: +A configuração da propriedade é definida na configuração do contêiner de DI na [seção setup |services#Setup]: ```neon services: - - - create: MyClass + - create: MyClass setup: - $cache = @\Cache ``` -Injetar .[#toc-inject] -====================== +Inject +====== -Enquanto os três métodos anteriores são geralmente válidos em todos os idiomas orientados a objetos, a injeção por método, a anotação ou o atributo *injet* é específico para os apresentadores Nette. Eles são discutidos em [um capítulo separado |best-practices:inject-method-attribute]. +Enquanto os três métodos anteriores se aplicam geralmente em todas as linguagens orientadas a objetos, a injeção por método, anotação ou atributo *inject* é específica puramente para presenters no Nette. Eles são discutidos em um [capítulo separado |best-practices:inject-method-attribute]. -Qual a maneira de escolher? .[#toc-which-way-to-choose] -======================================================= +Qual método escolher? +===================== -- construtor é adequado para as dependências obrigatórias que a classe precisa para funcionar -- o setter, por outro lado, é adequado para dependências opcionais, ou dependências que podem ser alteradas -- variáveis públicas não são recomendadas +- o construtor é adequado para dependências obrigatórias que a classe necessita essencialmente para sua função +- o setter, por outro lado, é adequado para dependências opcionais, ou dependências que podem ser alteradas posteriormente +- propriedades públicas não são adequadas diff --git a/dependency-injection/pt/services.texy b/dependency-injection/pt/services.texy index d8f55bada1..916ac4050d 100644 --- a/dependency-injection/pt/services.texy +++ b/dependency-injection/pt/services.texy @@ -1,33 +1,33 @@ -Definições de serviço -********************* +Definindo Serviços +****************** .[perex] -A configuração é onde colocamos as definições de serviços personalizados. Isto é feito na seção `services`. +A configuração é o local onde ensinamos ao contêiner de DI como construir serviços individuais e como conectá-los a outras dependências. O Nette fornece uma maneira muito clara e elegante de conseguir isso. -Por exemplo, é assim que criamos um serviço chamado `database`, que será uma instância da classe `PDO`: +A seção `services` no arquivo de configuração no formato NEON é onde definimos nossos próprios serviços e suas configurações. Vejamos um exemplo simples de definição de um serviço chamado `database`, que representa uma instância da classe `PDO`: ```neon services: database: PDO('sqlite::memory:') ``` -A nomenclatura dos serviços é utilizada para nos permitir [referenciá-los |#Referencing Services]. Se um serviço não é referenciado, não há necessidade de nomeá-lo. Portanto, usamos apenas um ponto em vez de um nome: +A configuração fornecida resultará no seguinte método de fábrica no [Contêiner de DI|container]: -```neon -services: - - PDO('sqlite::memory:') # serviço anônimo +```php +public function createServiceDatabase(): PDO +{ + return new PDO('sqlite::memory:'); +} ``` -Uma entrada de uma linha pode ser dividida em várias linhas para permitir que chaves adicionais sejam adicionadas, tais como [configuração |#setup]. O pseudônimo para a chave `create:` é `factory:`. +Os nomes dos serviços nos permitem referenciá-los em outras partes do arquivo de configuração, no formato `@nomeDoServico`. Se não for necessário nomear o serviço, podemos simplesmente usar um marcador: ```neon services: - database: - create: PDO('sqlite::memory:') - setup: ... + - PDO('sqlite::memory:') ``` -Recuperamos então o serviço do recipiente DI usando o método `getService()` pelo nome, ou melhor ainda, o método `getByType()` pelo tipo: +Para obter um serviço do contêiner de DI, podemos usar o método `getService()` com o nome do serviço como parâmetro, ou o método `getByType()` com o tipo do serviço: ```php $database = $container->getService('database'); @@ -35,26 +35,28 @@ $database = $container->getByType(PDO::class); ``` -Criando um serviço .[#toc-creating-a-service] -============================================= +Criação do serviço +================== -Na maioria das vezes, nós criamos um serviço simplesmente criando uma instância de uma classe: +Geralmente, criamos um serviço simplesmente criando uma instância de uma determinada classe. Por exemplo: ```neon services: database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -O que gerará um método de fábrica em [contêineres DI |container]: +Se precisarmos estender a configuração com outras chaves, a definição pode ser dividida em várias linhas: -```php -public function createServiceDatabase(): PDO -{ - return new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'secret'); -} +```neon +services: + database: + create: PDO('sqlite::memory:') + setup: ... ``` -Alternativamente, uma chave `arguments` pode ser usada para passar [argumentos |#Arguments]: +A chave `create` tem um alias `factory`, ambas as variantes são comuns na prática. No entanto, recomendamos usar `create`. + +Os argumentos do construtor ou do método de criação podem ser escritos alternativamente na chave `arguments`: ```neon services: @@ -63,294 +65,303 @@ services: arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret] ``` -Um método estático também pode criar um serviço: +Os serviços não precisam ser criados apenas pela simples criação de uma instância de classe, eles também podem ser o resultado da chamada de métodos estáticos ou métodos de outros serviços: ```neon services: - database: My\Database::create(root, secret) + database: DatabaseFactory::create() + router: @routerFactory::create() ``` -Corresponde ao código PHP: +Observe que, para simplificar, `::` é usado em vez de `->`, veja [#expressões]. Os seguintes métodos de fábrica serão gerados: ```php public function createServiceDatabase(): PDO { - return My\Database::create('root', 'secret'); + return DatabaseFactory::create(); +} + +public function createServiceRouter(): RouteList +{ + return $this->getService('routerFactory')->create(); } ``` -Presume-se que um método estático `My\Database::create()` tenha um valor de retorno definido que o recipiente DI precisa conhecer. Se não o tiver, escrevemos o tipo na configuração: +O contêiner de DI precisa saber o tipo do serviço criado. Se criarmos um serviço usando um método que não tem um tipo de retorno especificado, devemos especificar explicitamente esse tipo na configuração: ```neon services: database: - create: My\Database::create(root, secret) + create: DatabaseFactory::create() type: PDO ``` -A Nette DI lhe dá facilidades de expressão extremamente poderosas para escrever quase tudo. Por exemplo, para [se referir |#Referencing Services] a outro serviço e chamar seu método. Para simplificar, `::` é usado ao invés de `->`. + +Argumentos +========== + +Passamos argumentos para o construtor e métodos de maneira muito semelhante ao próprio PHP: ```neon services: - routerFactory: App\Router\Factory - router: @routerFactory::create() + database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -Corresponde ao código PHP: - -```php -public function createServiceRouterFactory(): App\Router\Factory -{ - return new App\Router\Factory; -} +Para melhor legibilidade, podemos dividir os argumentos em linhas separadas. Nesse caso, o uso de vírgulas é opcional: -public function createServiceRouter(): Router -{ - return $this->getService('routerFactory')->create(); -} +```neon +services: + database: PDO( + 'mysql:host=127.0.0.1;dbname=test' + root + secret + ) ``` -As chamadas de métodos podem ser encadeadas como em PHP: +Você também pode nomear os argumentos e não precisa se preocupar com a ordem deles: ```neon services: - foo: FooFactory::build()::get() + database: PDO( + username: root + password: secret + dsn: 'mysql:host=127.0.0.1;dbname=test' + ) ``` -Corresponde ao código PHP: +Se você quiser omitir alguns argumentos e usar seu valor padrão ou injetar um serviço usando [autowiring|autowiring], use um sublinhado: -```php -public function createServiceFoo() -{ - return FooFactory::build()->get(); -} +```neon +services: + foo: Foo(_, %appDir%) ``` +Como argumentos, é possível passar serviços, usar parâmetros e muito mais, veja [#expressões]. + -Argumentos .[#toc-arguments] -============================ +Setup +===== -Os parâmetros nomeados também podem ser usados para passar argumentos: +Na seção `setup`, definimos os métodos que devem ser chamados ao criar o serviço. ```neon services: - database: PDO( - 'mysql:host=127.0.0.1;dbname=test' # posicional - username: root # named - password: secret # named - ) + database: + create: PDO(%dsn%, %user%, %password%) + setup: + - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) ``` -O uso de vírgulas é opcional ao quebrar argumentos em várias linhas. +Isso seria assim em PHP: -Naturalmente, também podemos utilizar [outros serviços |#Referencing Services] ou [parâmetros |configuration#parameters] como argumentos: +```php +public function createServiceDatabase(): PDO +{ + $service = new PDO('...', '...', '...'); + $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + return $service; +} +``` + +Além de chamar métodos, também é possível passar valores para propriedades. A adição de um elemento a um array também é suportada, o que precisa ser escrito entre aspas para não colidir com a sintaxe NEON: ```neon services: - - Foo(@anotherService, %appDir%) + foo: + create: Foo + setup: + - $value = 123 + - '$onClick[]' = [@bar, clickHandler] ``` -Corresponde ao código PHP: +O que seria assim no código PHP: ```php -public function createService01(): Foo +public function createServiceFoo(): Foo { - return new Foo($this->getService('anotherService'), '...'); + $service = new Foo; + $service->value = 123; + $service->onClick[] = [$this->getService('bar'), 'clickHandler']; + return $service; } ``` -Se o primeiro argumento for [auto-cablado |autowiring] e você quiser especificar o segundo, omitir o primeiro com o `_` character, for example `Foo(_, %appDir%)`. Ou melhor ainda, passar apenas o segundo argumento como um parâmetro nomeado, por exemplo `Foo(path: %appDir%)`. - -Nette DI e o formato NEON lhe dão facilidades extremamente poderosas para escrever quase tudo. Assim, um argumento pode ser um objeto recém-criado, você pode chamar métodos estáticos, métodos de outros serviços, ou mesmo funções globais usando notação especial: +No setup, no entanto, também é possível chamar métodos estáticos ou métodos de outros serviços. Se você precisar passar o serviço atual como argumento, indique-o como `@self`: ```neon services: - analisador: Meu Analisador ( - FilesystemIterator(%appDir%) # criar objeto - DateTime::createFromFormat('Y-m-d') # chama método estático - @anotherService # passando outro serviço - @http.request::getRemoteAddress() # chamando outro método de serviço - ::getenv(NetteMode) # chamar uma função global - ) + foo: + create: Foo + setup: + - My\Helpers::initializeFoo(@self) + - @anotherService::setFoo(@self) ``` -Corresponde ao código PHP: +Observe que, para simplificar, `::` é usado em vez de `->`, veja [#expressões]. O seguinte método de fábrica será gerado: ```php -public function createServiceAnalyser(): My\Analyser +public function createServiceFoo(): Foo { - return new My\Analyser( - new FilesystemIterator('...'), - DateTime::createFromFormat('Y-m-d'), - $this->getService('anotherService'), - $this->getService('http.request')->getRemoteAddress(), - getenv('NetteMode') - ); + $service = new Foo; + My\Helpers::initializeFoo($service); + $this->getService('anotherService')->setFoo($service); + return $service; } ``` -Funções especiais .[#toc-special-functions] -------------------------------------------- - -Você também pode usar funções especiais em argumentos para lançar ou negar valores: +Expressões .{expressões} +======================== -- `not(%arg%)` negação -- `bool(%arg%)` elenco sem perdas a bool -- `int(%arg%)` elenco sem perdas para int -- `float(%arg%)` elenco sem perdas para flutuar -- `string(%arg%)` elenco sem perdas para cordas +Nette DI nos dá recursos de expressão extraordinariamente ricos, com os quais podemos escrever quase qualquer coisa. Nos arquivos de configuração, podemos usar [parâmetros |configuration#Parâmetros]: ```neon -services: - - Foo( - id: int(::getenv('ProjectId')) - productionMode: not(%debugMode%) - ) -``` - -A reescrita sem perdas difere da reescrita normal em PHP, por exemplo, usando `(int)`, na medida em que lança uma exceção para valores não-numéricos. +# parâmetro +%wwwDir% -Múltiplos serviços podem ser passados como argumentos. Um conjunto de todos os serviços de um determinado tipo (ou seja, classe ou interface) é criado pela função `typed()`. A função omitirá serviços que tenham a fiação automática desativada, e múltiplos tipos separados por uma vírgula podem ser especificados. +# valor do parâmetro sob a chave +%mailer.user% -```neon -services: - - BarsDependent( typed(Bar) ) +# parâmetro dentro de uma string +'%wwwDir%/images' ``` -Você também pode passar um conjunto de serviços automaticamente usando [a fiação automática |autowiring#Collection of Services]. - -Um conjunto de todos os serviços com uma certa [etiqueta |#tags] é criado pela função `tagged()`. Múltiplas tags separadas por uma vírgula podem ser especificadas. +Além disso, criar objetos, chamar métodos e funções: ```neon -services: - - LoggersDependent( tagged(logger) ) -``` +# criação de objeto +DateTime() +# chamada de método estático +Collator::create(%locale%) -Serviços de referência .[#toc-referencing-services] -=================================================== +# chamada de função PHP +::getenv(DB_USER) +``` -Os serviços individuais são referenciados usando o personagem `@` and name, so for example `@database`: +Referenciar serviços pelo nome ou pelo tipo: ```neon -services: - - create: Foo(@database) - setup: - - setCacheStorage(@cache.storage) +# serviço por nome +@database + +# serviço por tipo +@Nette\Database\Connection ``` -Corresponde ao código PHP: +Usar a sintaxe first-class callable: .{data-version:3.2.0} -```php -public function createService01(): Foo -{ - $service = new Foo($this->getService('database')); - $service->setCacheStorage($this->getService('cache.storage')); - return $service; -} +```neon +# criação de callback, análogo a [@user, logout] +@user::logout(...) ``` -Mesmo serviços anônimos podem ser referenciados usando uma chamada de retorno, basta especificar seu tipo (classe ou interface) ao invés de seu nome. No entanto, isto geralmente não é necessário devido ao [cabeamento automático |autowiring]. +Usar constantes: ```neon -services: - - create: Foo(@Nette\Database\Connection) # or @\PDO - setup: - - setCacheStorage(@cache.storage) +# constante de classe +FilesystemIterator::SKIP_DOTS + +# constante global obtida pela função PHP constant() +::constant(PHP_VERSION) ``` +As chamadas de método podem ser encadeadas como em PHP. Apenas para simplificar, `::` é usado em vez de `->`: -Configuração .[#toc-setup] -========================== +```neon +DateTime()::format('Y-m-d') +# PHP: (new DateTime())->format('Y-m-d') -Na seção de configuração, listamos os métodos a serem chamados ao criar o serviço: +@http.request::getUrl()::getHost() +# PHP: $this->getService('http.request')->getUrl()->getHost() +``` + +Você pode usar essas expressões em qualquer lugar, ao [criar serviços |#Criação do serviço], em [#argumentos], na seção [#Setup] ou em [parâmetros |configuration#Parâmetros]: ```neon +parameters: + ipAddress: @http.request::getRemoteAddress() + services: database: - create: PDO(%dsn%, %user%, %password%) + create: DatabaseFactory::create( @anotherService::getDsn() ) setup: - - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) + - initialize( ::getenv('DB_USER') ) ``` -Corresponde ao código PHP: -```php -public function createServiceDatabase(): PDO -{ - $service = new PDO('...', '...', '...'); - $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - return $service; -} -``` +Funções especiais +----------------- -Também é possível definir os locais adequados. A adição de um elemento a uma matriz também é apoiada, e deve ser escrita entre aspas para não entrar em conflito com a sintaxe NEON: +Nos arquivos de configuração, você pode usar estas funções especiais: +- `not()` negação do valor +- `bool()`, `int()`, `float()`, `string()` conversão sem perdas para o tipo especificado +- `typed()` cria um array de todos os serviços do tipo especificado +- `tagged()` cria um array de todos os serviços com a tag especificada ```neon services: - foo: - create: Foo - setup: - - $value = 123 - - '$onClick[]' = [@bar, clickHandler] + - Foo( + id: int(::getenv('ProjectId')) + productionMode: not(%debugMode%) + ) ``` -Corresponde ao código PHP: +Em comparação com a conversão de tipo clássica em PHP, como `(int)`, a conversão sem perdas lançará uma exceção para valores não numéricos. -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - $service->value = 123; - $service->onClick[] = [$this->getService('bar'), 'clickHandler']; - return $service; -} +A função `typed()` cria um array de todos os serviços de um determinado tipo (classe ou interface). Ela omite serviços que têm o autowiring desativado. É possível especificar vários tipos separados por vírgula. + +```neon +services: + - BarsDependent( typed(Bar) ) ``` -No entanto, métodos estáticos ou métodos de outros serviços também podem ser chamados na configuração. Passamos o serviço real a eles como `@self`: +Você também pode passar um array de serviços de um determinado tipo como argumento automaticamente usando [autowiring |autowiring#Array de serviços]. +A função `tagged()` então cria um array de todos os serviços com uma determinada tag. Aqui também você pode especificar várias tags separadas por vírgula. ```neon services: - foo: - create: Foo - setup: - - My\Helpers::initializeFoo(@self) - - @anotherService::setFoo(@self) + - LoggersDependent( tagged(logger) ) ``` -Corresponde ao código PHP: -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - My\Helpers::initializeFoo($service); - $this->getService('anotherService')->setFoo($service); - return $service; -} +Autowiring +========== + +A chave `autowired` permite influenciar o comportamento do autowiring para um serviço específico. Para detalhes, veja [o capítulo sobre autowiring|autowiring]. + +```neon +services: + foo: + create: Foo + autowired: false # o serviço foo é excluído do autowiring ``` -Cablagem automática .[#toc-autowiring] -====================================== +Serviços Lazy .{data-version:3.2.4} +=================================== -A chave autowired pode ser usada para excluir um serviço da autowired ou para influenciar seu comportamento. Consulte o [capítulo sobre a fiação automática |autowiring] para obter mais informações. +Lazy loading é uma técnica que adia a criação de um serviço até o momento em que ele é realmente necessário. Na configuração global, é possível [habilitar a criação lazy |configuration#Serviços Lazy] para todos os serviços de uma vez. Para serviços individuais, você pode então substituir esse comportamento: ```neon services: foo: create: Foo - autowired: false # foo é removido do autowiring + lazy: false ``` +Quando um serviço é definido como lazy, ao solicitá-lo do contêiner de DI, recebemos um objeto substituto especial. Ele parece e se comporta da mesma forma que o serviço real, mas a inicialização real (chamada do construtor e setup) ocorre apenas na primeira chamada de qualquer um de seus métodos ou propriedades. + +.[note] +O lazy loading pode ser usado apenas para classes de usuário, não para classes internas do PHP. Requer PHP 8.4 ou mais recente. + -Etiquetas .[#toc-tags] -====================== +Tags +==== -As informações do usuário podem ser adicionadas aos serviços individuais sob a forma de tags: +As tags servem para adicionar informações complementares aos serviços. Você pode adicionar uma ou mais tags a um serviço: ```neon services: @@ -360,7 +371,7 @@ services: - cached ``` -As etiquetas também podem ter um valor: +As tags também podem carregar valores: ```neon services: @@ -370,26 +381,26 @@ services: logger: monolog.logger.event ``` -Uma série de serviços com determinadas tags pode ser passada como argumento usando a função `tagged()`. Múltiplas tags separadas por uma vírgula também podem ser especificadas. +Para obter todos os serviços com certas tags, você pode usar a função `tagged()`: ```neon services: - LoggersDependent( tagged(logger) ) ``` -Os nomes dos serviços podem ser obtidos no recipiente DI usando o método `findByTag()`: +No contêiner de DI, você pode obter os nomes de todos os serviços com uma determinada tag usando o método `findByTag()`: ```php $names = $container->findByTag('logger'); -// $names é um array contendo o nome do serviço e o valor da etiqueta -// i.e. ['foo' => 'monolog.logger.event', ...] +// $names é um array contendo o nome do serviço e o valor da tag +// por exemplo, ['foo' => 'monolog.logger.event', ...] ``` -Modo injetado .[#toc-inject-mode] -================================= +Modo Inject +=========== -A bandeira `inject: true` é usada para ativar a passagem de dependências através de variáveis públicas com a anotação de [injeção |best-practices:inject-method-attribute#Inject Attributes] e os métodos de [injeção*() |best-practices:inject-method-attribute#inject Methods]. +Usando o sinalizador `inject: true`, a passagem de dependências é ativada através de propriedades públicas com a anotação [inject |best-practices:inject-method-attribute#Atributos Inject] e métodos [inject*() |best-practices:inject-method-attribute#Métodos inject]. ```neon services: @@ -398,13 +409,13 @@ services: inject: true ``` -Por padrão, `inject` é ativado somente para apresentadores. +Por padrão, `inject` é ativado apenas para presenters. -Modificação de serviços .[#toc-modification-of-services] -======================================================== +Modificação de serviços +======================= -Há uma série de serviços no contêiner DI que foram acrescentados por embutido ou por [sua extensão |#di-extensions]. As definições desses serviços podem ser modificadas na configuração. Por exemplo, para o serviço `application.application`, que é por padrão um objeto `Nette\Application\Application`, podemos alterar a classe: +O contêiner de DI contém muitos serviços que foram adicionados através de extensões embutidas ou [de usuário|extensions]. Você pode modificar as definições desses serviços diretamente na configuração. Por exemplo, você pode alterar a classe do serviço `application.application`, que por padrão é `Nette\Application\Application`, para outra: ```neon services: @@ -413,9 +424,9 @@ services: alteration: true ``` -A bandeira `alteration` é informativa e diz que estamos apenas modificando um serviço existente. +O sinalizador `alteration` é informativo e indica que estamos apenas modificando um serviço existente. -Também podemos acrescentar uma configuração: +Também podemos complementar o setup: ```neon services: @@ -426,7 +437,7 @@ services: - '$onStartup[]' = [@resource, init] ``` -Ao reescrever um serviço, podemos querer remover os argumentos originais, itens de configuração ou tags, que é para isso que serve `reset`: +Ao sobrescrever um serviço, podemos querer remover os argumentos originais, itens de setup ou tags, para o qual usamos `reset`: ```neon services: @@ -439,7 +450,7 @@ services: - tags ``` -Um serviço adicionado por extensão também pode ser removido do container: +Se você quiser remover um serviço adicionado por uma extensão, pode fazer assim: ```neon services: diff --git a/dependency-injection/ro/@home.texy b/dependency-injection/ro/@home.texy index 560356122d..4f32db93ab 100644 --- a/dependency-injection/ro/@home.texy +++ b/dependency-injection/ro/@home.texy @@ -1,24 +1,21 @@ -Injectarea dependenței -********************** +Nette DI +******** .[perex] -Injectarea dependenței este un model de proiectare care va schimba fundamental modul în care priviți codul și dezvoltarea. Acesta deschide calea către o lume a aplicațiilor cu design curat și sustenabil. +Dependency Injection este un pattern de design care vă va schimba fundamental perspectiva asupra codului și dezvoltării. Vă va deschide calea către lumea aplicațiilor proiectate curat și sustenabile. -- [Ce este injecția de dependență? |introduction] -- [Starea globală și singletonii |global-state] +- [Ce este Dependency Injection? |introduction] +- [Stare globală și singleton-uri |global-state] - [Transmiterea dependențelor |passing-dependencies] -- [Ce este DI Container? |container] -- [Întrebări frecvente |faq] - +- [Ce este un container DI? |container] +- [Întrebări frecvente|faq] -Nette DI --------- Pachetul `nette/di` oferă un container DI compilat extrem de avansat pentru PHP. -- [Containerul Nette DI |nette-container] +- [Nette DI Container |nette-container] - [Configurație |configuration] -- [Definiții de servicii |services] -- [Cablare automată |autowiring] +- [Definirea serviciilor |services] +- [Autowiring |autowiring] - [Fabrici generate |factory] -- [Crearea extensiilor pentru Nette DI |extensions] +- [Crearea extensiilor pentru Nette DI|extensions] diff --git a/dependency-injection/ro/@left-menu.texy b/dependency-injection/ro/@left-menu.texy index 6708d7c8d7..5ae7872994 100644 --- a/dependency-injection/ro/@left-menu.texy +++ b/dependency-injection/ro/@left-menu.texy @@ -1,17 +1,17 @@ -Injectarea dependenței -********************** +Dependency Injection +******************** - [Ce este DI? |introduction] -- [Stare globală și singletoni |global-state] +- [Stare globală și singleton-uri |global-state] - [Transmiterea dependențelor |passing-dependencies] -- [Ce este DI Container? |container] -- [Întrebări frecvente |faq] +- [Ce este un container DI? |container] +- [Întrebări frecvente|faq] Nette DI -------- -- [Containerul Nette DI |nette-container] +- [Nette DI Container |nette-container] - [Configurație |configuration] -- [Definiții ale serviciilor |services] -- [Cablare automată |autowiring] +- [Definirea serviciilor |services] +- [Autowiring |autowiring] - [Fabrici generate |factory] -- [Crearea extensiilor pentru Nette DI |extensions] +- [Crearea extensiilor pentru Nette DI|extensions] diff --git a/dependency-injection/ro/@meta.texy b/dependency-injection/ro/@meta.texy new file mode 100644 index 0000000000..9c744b37d6 --- /dev/null +++ b/dependency-injection/ro/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentație Nette}} diff --git a/dependency-injection/ro/autowiring.texy b/dependency-injection/ro/autowiring.texy index d9f7b90586..58ccaf9083 100644 --- a/dependency-injection/ro/autowiring.texy +++ b/dependency-injection/ro/autowiring.texy @@ -1,24 +1,24 @@ -Cablare automată -**************** +Autowiring +********** .[perex] -Autowiring este o caracteristică grozavă care poate trece automat servicii către constructor și alte metode, astfel încât nu trebuie să le scriem deloc. Aceasta vă economisește mult timp. +Autowiring este o caracteristică excelentă care poate transmite automat serviciile necesare către constructor și alte metode, astfel încât nu trebuie să le scriem deloc. Vă economisește mult timp. -Acest lucru ne permite să sărim peste marea majoritate a argumentelor atunci când scriem definițiile serviciilor. În loc de: +Datorită acestui fapt, putem omite marea majoritate a argumentelor atunci când scriem definiții de servicii. În loc de: ```neon services: articles: Model\ArticleRepository(@database, @cache.storage) ``` -Scrieți doar: +Este suficient să scrieți: ```neon services: articles: Model\ArticleRepository ``` -Cablarea automată este determinată de tipuri, astfel încât clasa `ArticleRepository` trebuie să fie definită după cum urmează: +Autowiring se ghidează după tipuri, așa că pentru a funcționa, clasa `ArticleRepository` trebuie definită aproximativ astfel: ```php namespace Model; @@ -30,22 +30,22 @@ class ArticleRepository } ``` -Pentru a utiliza autowiring, trebuie să existe **un singur serviciu** pentru fiecare tip din container. Dacă ar exista mai multe, autowiring nu ar ști pe care să îl treacă și ar arunca o excepție: +Pentru a putea utiliza autowiring, trebuie să existe **exact un serviciu** pentru fiecare tip în container. Dacă ar exista mai multe, autowiring nu ar ști pe care să îl transmită și ar arunca o excepție: ```neon services: mainDb: PDO(%dsn%, %user%, %password%) tempDb: PDO('sqlite::memory:') - articles: Model\ArticleRepository # THROWS EXCEPTION, atât mainDb cât și tempDb se potrivesc + articles: Model\ArticleRepository # ARUNCĂ EXCEPȚIE, se potrivesc atât mainDb cât și tempDb ``` -Soluția ar fi fie să se ocolească autowiring și să se precizeze explicit numele serviciului (de exemplu, `articles: Model\ArticleRepository(@mainDb)`). Cu toate acestea, este mai convenabil să se [dezactiveze |#Disabled autowiring] autowiring-ul pentru un singur serviciu, sau să se [prefere |#Preferred Autowiring] primul serviciu. +Soluția ar fi fie să ocoliți autowiring-ul și să specificați explicit numele serviciului (adică `articles: Model\ArticleRepository(@mainDb)`). Dar este mai convenabil să [dezactivați |#Dezactivarea autowiring-ului] autowiring-ul pentru unul dintre servicii sau să [prioritizați |#Preferința autowiring-ului] primul serviciu. -Autowiring dezactivat .[#toc-disabled-autowiring] -------------------------------------------------- +Dezactivarea autowiring-ului +---------------------------- -Puteți dezactiva cablarea automată a serviciilor utilizând opțiunea `autowired: no`: +Putem dezactiva autowiring-ul unui serviciu folosind opțiunea `autowired: no`: ```neon services: @@ -53,28 +53,27 @@ services: tempDb: create: PDO('sqlite::memory:') - autowired: false # elimină tempDb din autowiring + autowired: false # serviciul tempDb este exclus din autowiring - articles: Model\ArticleRepository # prin urmare, trece mainDb la constructor + articles: Model\ArticleRepository # prin urmare, transmite mainDb către constructor ``` -Serviciul `articles` nu aruncă excepția că există două servicii corespunzătoare de tip `PDO` (de exemplu, `mainDb` și `tempDb`) care pot fi transmise constructorului, deoarece acesta vede doar serviciul `mainDb`. +Serviciul `articles` nu aruncă o excepție că există două servicii potrivite de tip `PDO` (adică `mainDb` și `tempDb`) care pot fi transmise constructorului, deoarece vede doar serviciul `mainDb`. .[note] -Configurarea autowiring-ului în Nette funcționează diferit față de Symfony, unde opțiunea `autowire: false` spune că autowiring-ul nu trebuie folosit pentru argumentele constructorului serviciului. -În Nette, autowiring este întotdeauna folosit, fie pentru argumentele constructorului, fie pentru orice altă metodă. Opțiunea `autowired: false` spune că instanța serviciului nu trebuie să fie transmisă nicăieri folosind autowiring. +Configurarea autowiring-ului în Nette funcționează diferit față de Symfony, unde opțiunea `autowire: false` specifică faptul că autowiring-ul nu trebuie utilizat pentru argumentele constructorului serviciului respectiv. În Nette, autowiring-ul este întotdeauna utilizat, fie pentru argumentele constructorului, fie pentru orice altă metodă. Opțiunea `autowired: false` specifică faptul că instanța serviciului respectiv nu trebuie transmisă nicăieri prin autowiring. -Autowiring preferat .[#toc-preferred-autowiring] ------------------------------------------------- +Preferința autowiring-ului +-------------------------- -În cazul în care avem mai multe servicii de același tip și unul dintre ele are opțiunea `autowired`, acest serviciu devine cel preferat: +Dacă avem mai multe servicii de același tip și pentru unul dintre ele specificăm opțiunea `autowired`, acest serviciu devine preferat: ```neon services: mainDb: create: PDO(%dsn%, %user%, %password%) - autowired: PDO # îl face preferat + autowired: PDO # devine preferat tempDb: create: PDO('sqlite::memory:') @@ -82,13 +81,13 @@ services: articles: Model\ArticleRepository ``` -Serviciul `articles` nu aruncă excepția că există două servicii `PDO` corespunzătoare (adică `mainDb` și `tempDb`), ci utilizează serviciul preferat, adică `mainDb`. +Serviciul `articles` nu aruncă o excepție că există două servicii potrivite de tip `PDO` (adică `mainDb` și `tempDb`), ci folosește serviciul preferat, adică `mainDb`. -Colecția de servicii .[#toc-collection-of-services] ---------------------------------------------------- +Array de servicii +----------------- -Autowiring poate transmite, de asemenea, o serie de servicii de un anumit tip. Deoarece PHP nu poate nota în mod nativ tipul de elemente ale tabloului, pe lângă tipul `array`, trebuie adăugat un comentariu phpDoc cu tipul de element, cum ar fi `ClassName[]`: +Autowiring poate transmite și array-uri de servicii de un anumit tip. Deoarece în PHP nu se poate scrie nativ tipul elementelor unui array, este necesar, pe lângă tipul `array`, să se adauge și un comentariu phpDoc cu tipul elementului în formatul `ClassName[]`: ```php namespace Model; @@ -103,16 +102,15 @@ class ShipManager } ``` -Recipientul DI transmite apoi automat un array de servicii care corespund tipului dat. Acesta va omite serviciile care au cablarea automată dezactivată. +Containerul DI transmite apoi automat un array de servicii corespunzătoare tipului respectiv. Omită serviciile care au autowiring-ul dezactivat. -Dacă nu puteți controla forma comentariului phpDoc, puteți trece o matrice de servicii direct în configurație, utilizând [`typed()` |services#Special Functions]. +Tipul din comentariu poate fi și în formatul `array<int, Class>` sau `list<Class>`. Dacă nu puteți influența forma comentariului phpDoc, puteți transmite array-ul de servicii direct în configurație folosind [`typed()` |services#Funcții speciale]. -Argumente scalare .[#toc-scalar-arguments] ------------------------------------------- +Argumente scalare +----------------- -Autowiring poate transmite numai obiecte și array-uri de obiecte. Argumentele scalare (de exemplu, șiruri de caractere, numere, booleeni) se [scriu în configurare |services#Arguments]. -O alternativă este crearea unui [obiect de configurare |best-practices:passing-settings-to-presenters] care încapsulează o valoare scalară (sau mai multe valori) sub forma unui obiect, care poate fi apoi transmis din nou cu ajutorul autowiring-ului. +Autowiring poate injecta doar obiecte și array-uri de obiecte. Argumentele scalare (de ex. șiruri, numere, booleeni) [le scriem în configurație |services#Argumente]. O alternativă este crearea unui [obiect de setări |best-practices:passing-settings-to-presenters], care încapsulează valoarea scalară (sau mai multe valori) sub formă de obiect, care apoi poate fi transmis din nou prin autowiring. ```php class MySettings @@ -125,24 +123,24 @@ class MySettings } ``` -Creați un serviciu adăugându-l la configurație: +Creați un serviciu din acesta adăugându-l în configurație: ```neon services: - MySettings('any value') ``` -Toate clasele îl vor solicita apoi prin cablare automată. +Toate clasele îl vor solicita apoi prin autowiring. -Restrângerea cablării automate .[#toc-narrowing-of-autowiring] --------------------------------------------------------------- +Restrângerea autowiring-ului +---------------------------- -Pentru servicii individuale, autocablarea poate fi restrânsă la anumite clase sau interfețe. +Autowiring-ul serviciilor individuale poate fi restrâns la anumite clase sau interfețe. -În mod normal, autowiring-ul trece serviciul la fiecare parametru al metodei căruia îi corespunde tipul serviciului. Restrângerea înseamnă că se specifică condițiile pe care tipurile specificate pentru parametrii metodei trebuie să le îndeplinească pentru ca serviciul să le fie transmis. +În mod normal, autowiring-ul transmite serviciul către fiecare parametru al metodei al cărui tip corespunde serviciului. Restrângerea înseamnă că stabilim condiții pe care tipurile specificate la parametrii metodelor trebuie să le îndeplinească pentru ca serviciul să le fie transmis. -Să luăm un exemplu: +Să ilustrăm acest lucru cu un exemplu: ```php class ParentClass @@ -164,42 +162,42 @@ class ChildDependent } ``` -Dacă le-am înregistrat pe toate ca servicii, cablarea automată ar eșua: +Dacă le-am înregistra pe toate ca servicii, autowiring-ul ar eșua: ```neon services: parent: ParentClass child: ChildClass - parentDep: ParentDependent # EXCEPȚIE DE ÎNTÂRZIERE, atât părintele cât și copilul se potrivesc - childDep: ChildDependent # trece serviciul "child" în constructor + parentDep: ParentDependent # ARUNCĂ EXCEPȚIE, se potrivesc serviciile parent și child + childDep: ChildDependent # autowiring transmite serviciul child către constructor ``` -Serviciul `parentDep` aruncă excepția `Multiple services of type ParentClass found: parent, child`, deoarece atât `parent`, cât și `child` se potrivesc în constructorul său, iar autowiring nu poate lua o decizie cu privire la care să o aleagă. +Serviciul `parentDep` aruncă excepția `Multiple services of type ParentClass found: parent, child`, deoarece ambele servicii `parent` și `child` se potrivesc constructorului său, iar autowiring-ul nu poate decide pe care să îl aleagă. -Prin urmare, pentru serviciul `child`, putem restrânge cablarea automată la `ChildClass`: +Prin urmare, pentru serviciul `child`, putem restrânge autowiring-ul său la tipul `ChildClass`: ```neon services: parent: ParentClass child: create: ChildClass - autowired: ChildClass # alternative: 'autowired: self' + autowired: ChildClass # se poate scrie și 'autowired: self' - parentDep: ParentDependent # Se produce o EXCEPȚIE, 'child' nu poate fi autowired. - childDep: ChildDependent # transmite serviciul "child" la constructor + parentDep: ParentDependent # autowiring transmite serviciul parent către constructor + childDep: ChildDependent # autowiring transmite serviciul child către constructor ``` -Serviciul `parentDep` este transmis acum constructorului serviciului `parentDep`, deoarece este acum singurul obiect corespunzător. Serviciul `child` nu mai este transmis prin autocablare. Da, serviciul `child` este în continuare de tipul `ParentClass`, dar condiția de restrângere dată pentru tipul de parametru nu se mai aplică, adică nu mai este adevărat că `ParentClass` *este un supratip* al `ChildClass`. +Acum, serviciul `parent` este transmis constructorului serviciului `parentDep`, deoarece acum este singurul obiect potrivit. Autowiring-ul nu mai transmite serviciul `child` acolo. Da, serviciul `child` este încă de tip `ParentClass`, dar condiția de restrângere dată pentru tipul parametrului nu mai este valabilă, adică nu este adevărat că `ParentClass` *este un supratip* al `ChildClass`. -În cazul `child`, `autowired: ChildClass` ar putea fi scris ca `autowired: self`, deoarece `self` înseamnă tipul de serviciu curent. +Pentru serviciul `child`, `autowired: ChildClass` ar putea fi scris și ca `autowired: self`, deoarece `self` este un substituent pentru clasa serviciului curent. -Cheia `autowired` poate include mai multe clase și interfețe ca matrice: +În cheia `autowired` este posibil să se specifice și mai multe clase sau interfețe ca un array: ```neon autowired: [BarClass, FooInterface] ``` -Să încercăm să adăugăm interfețe la exemplu: +Să încercăm să completăm exemplul cu interfețe: ```php interface FooInterface @@ -239,13 +237,13 @@ class ChildDependent } ``` -Atunci când nu limităm serviciul `child`, acesta se va potrivi în constructorii tuturor claselor `FooDependent`, `BarDependent`, `ParentDependent` și `ChildDependent` și autowiring îl va trece acolo. +Dacă nu restricționăm în niciun fel serviciul `child`, acesta se va potrivi constructorilor tuturor claselor `FooDependent`, `BarDependent`, `ParentDependent` și `ChildDependent`, iar autowiring-ul îl va transmite acolo. -Cu toate acestea, dacă limităm autowiring-ul la `ChildClass` folosind `autowired: ChildClass` (sau `self`), autowiring-ul îl trece doar la constructorul `ChildDependent`, deoarece necesită un argument de tip `ChildClass` și `ChildClass` *este de tip* `ChildClass`. Niciun alt tip specificat pentru ceilalți parametri nu este un supraansamblu al `ChildClass`, astfel încât serviciul nu este transmis. +Dar dacă îi restrângem autowiring-ul la `ChildClass` folosind `autowired: ChildClass` (sau `self`), autowiring-ul îl va transmite doar constructorului `ChildDependent`, deoarece necesită un argument de tip `ChildClass` și este adevărat că `ChildClass` *este de tip* `ChildClass`. Niciun alt tip specificat la ceilalți parametri nu este un supratip al `ChildClass`, deci serviciul nu este transmis. -Dacă îl restricționăm la `ParentClass` folosind `autowired: ParentClass`, autowiring îl va trece din nou la constructorul `ChildDependent` (deoarece tipul necesar `ChildClass` este un supraansamblu al `ParentClass`) și la constructorul `ParentDependent` de asemenea, deoarece tipul necesar al `ParentClass` este de asemenea corespunzător. +Dacă îl restricționăm la `ParentClass` folosind `autowired: ParentClass`, autowiring-ul îl va transmite din nou constructorului `ChildDependent` (deoarece `ChildClass` necesar este un supratip al `ParentClass`) și, nou, și constructorului `ParentDependent`, deoarece tipul necesar `ParentClass` este, de asemenea, potrivit. -Dacă îl restricționăm la `FooInterface`, acesta va trece în continuare automat la `ParentDependent` (tipul necesar `ParentClass` este un supratip al `FooInterface`) și la `ChildDependent`, dar și la constructorul `FooDependent`, dar nu și la `BarDependent`, deoarece `BarInterface` nu este un supratip al `FooInterface`. +Dacă îl restricționăm la `FooInterface`, va fi în continuare autowired în `ParentDependent` (necesarul `ParentClass` este un supratip al `FooInterface`) și `ChildDependent`, dar în plus și în constructorul `FooDependent`, însă nu în `BarDependent`, deoarece `BarInterface` nu este un supratip al `FooInterface`. ```neon services: @@ -253,8 +251,8 @@ services: create: ChildClass autowired: FooInterface - fooDep: FooDependent # transmite copilul serviciului către constructor - barDep: BarDependent # EXCEPȚIE, niciun serviciu nu ar trece - parentDep: ParentDependent # trece serviciul copil la constructor - childDep: ChildDependent # trece serviciul copil la constructor + fooDep: FooDependent # autowiring transmite child către constructor + barDep: BarDependent # ARUNCĂ EXCEPȚIE, niciun serviciu nu se potrivește + parentDep: ParentDependent # autowiring transmite child către constructor + childDep: ChildDependent # autowiring transmite child către constructor ``` diff --git a/dependency-injection/ro/configuration.texy b/dependency-injection/ro/configuration.texy index 3c0a3e410c..c78aeade7a 100644 --- a/dependency-injection/ro/configuration.texy +++ b/dependency-injection/ro/configuration.texy @@ -5,28 +5,29 @@ Configurarea containerului DI Prezentare generală a opțiunilor de configurare pentru containerul Nette DI. -Fișier de configurare .[#toc-configuration-file] -================================================ +Fișier de configurare +===================== -Containerul Nette DI este ușor de controlat cu ajutorul fișierelor de configurare. Acestea sunt de obicei scrise în [format NEON |neon:format]. Pentru editare, vă recomandăm să folosiți [editoare cu suport |best-practices:editors-and-tools#ide-editor] pentru acest format. +Containerul Nette DI este ușor de controlat folosind fișiere de configurare. Acestea sunt de obicei scrise în [formatul NEON |neon:format]. Pentru editare, recomandăm [editoare cu suport |best-practices:editors-and-tools#Editor IDE] pentru acest format. <pre> -"decorator .[prism-token prism-atrule]":[#Decorator]: "Decorator .[prism-token prism-comment]"<br> -"di .[prism-token prism-atrule]":[#DI]: "DI Container .[prism-token prism-comment]"<br> -"extensions .[prism-token prism-atrule]":[#Extensions]: "Instalați extensii DI suplimentare .[prism-token prism-comment]"<br> -"includes .[prism-token prism-atrule]":[#Including files]: "Fișiere de includere .[prism-token prism-comment]"<br> -"parameters .[prism-token prism-atrule]":[#Parameters]: "Parametrii .[prism-token prism-comment]"<br> -"search .[prism-token prism-atrule]":[#Search]: "Înregistrare automată a serviciilor .[prism-token prism-comment]"<br> +"decorator .[prism-token prism-atrule]":[#decorator]: "Decorator .[prism-token prism-comment]"<br> +"di .[prism-token prism-atrule]":[#DI]: "Container DI .[prism-token prism-comment]"<br> +"extensions .[prism-token prism-atrule]":[#Extensii]: "Instalarea altor extensii DI .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[#Includerea fișierelor]: "Includerea fișierelor .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[#Parametri]: "Parametri .[prism-token prism-comment]"<br> +"search .[prism-token prism-atrule]":[#Search]: "Înregistrarea automată a serviciilor .[prism-token prism-comment]"<br> "services .[prism-token prism-atrule]":[services]: "Servicii .[prism-token prism-comment]" </pre> -Pentru a scrie un șir care conține caracterul `%`, you must escape it by doubling it to `%%`. .[note] +.[note] +Pentru a scrie un șir care conține caracterul `%`, trebuie să îl escapați dublându-l la `%%`. -Parametrii .[#toc-parameters] -============================= +Parametri +========= -Puteți defini parametrii care pot fi apoi utilizați ca parte a definițiilor serviciilor. Acest lucru poate ajuta la separarea valorilor pe care veți dori să le modificați mai regulat. +În configurație puteți defini parametri care pot fi apoi utilizați ca parte a definițiilor serviciilor. Astfel puteți clarifica configurația sau puteți unifica și extrage valorile care se vor modifica. ```neon parameters: @@ -35,9 +36,9 @@ parameters: password: secret ``` -Puteți face referire la parametrul `foo` prin intermediul `%foo%` în altă parte în orice fișier de configurare. De asemenea, pot fi utilizați în interiorul unor șiruri de caractere precum `'%wwwDir%/images'`. +Ne referim la parametrul `dsn` oriunde în configurație scriind `%dsn%`. Parametrii pot fi utilizați și în interiorul șirurilor precum `'%wwwDir%/images'`. -Parametrii nu trebuie să fie doar șiruri de caractere, ci și valori de tip array: +Parametrii nu trebuie să fie doar șiruri sau numere, pot conține și array-uri: ```neon parameters: @@ -48,32 +49,32 @@ parameters: languages: [cs, en, de] ``` -Vă puteți referi la o singură cheie ca `%mailer.user%`. +Ne referim la cheia specifică ca `%mailer.user%`. -Dacă aveți nevoie să obțineți valoarea unui parametru în codul dumneavoastră, de exemplu în clasa dumneavoastră, atunci treceți-l în această clasă. De exemplu, în constructor. Nu există un obiect de configurare globală care să poată fi interogat de clase pentru valorile parametrilor. Acest lucru ar fi contrar principiului de injectare a dependențelor. +Dacă aveți nevoie în codul dvs., de exemplu într-o clasă, să aflați valoarea oricărui parametru, transmiteți-l acelei clase. De exemplu, în constructor. Nu există niciun obiect global care să reprezinte configurația, pe care clasele să îl interogheze pentru valorile parametrilor. Acest lucru ar încălca principiul injecției de dependență. -Servicii .[#toc-services] -========================= +Servicii +======== -A se vedea [capitolul separat |services]. +Vezi [capitolul separat |services]. -Decorator .[#toc-decorator] -=========================== +Decorator +========= -Cum se pot edita în bloc toate serviciile de un anumit tip? Aveți nevoie să apelați o anumită metodă pentru toți prezentatorii care moștenesc de la un anumit strămoș comun? De aici vine decoratorul. +Cum să modificați în masă toate serviciile de un anumit tip? De exemplu, să apelați o anumită metodă la toți presenterii care moștenesc de la un anumit strămoș comun? Pentru asta există decoratorul. ```neon decorator: # pentru toate serviciile care sunt instanțe ale acestei clase sau interfețe - App\Presenters\BasePresenter: + App\Presentation\BasePresenter: setup: - setProjectId(10) # apelează această metodă - $absoluteUrls = true # și setează variabila ``` -Decoratorul poate fi utilizat și pentru a seta [etichete |services#Tags] sau pentru a activa [modul de injectare |services#Inject Mode]. +Decoratorul poate fi utilizat și pentru setarea [tag-urilor |services#Tag-uri] sau activarea modului [inject |services#Mod Inject]. ```neon decorator: @@ -90,63 +91,75 @@ Setări tehnice ale containerului DI. ```neon di: - # arată DIC în Tracy Bar? - debugger: ... # (bool) valoarea implicită este true + # afișează DIC în Tracy Bar? + debugger: ... # (bool) implicit este true - # tipuri de parametri pe care nu îi conectați niciodată automat + # tipuri de parametri care nu se autowirează niciodată excluded: ... # (string[]) - # clasa din care moștenește containerul DI - parentClass: ... # (string) valoarea implicită este Nette\DI\Container + # permite crearea lazy a serviciilor? + lazy: ... # (bool) implicit este false + + # clasa de la care moștenește containerul DI + parentClass: ... # (string) implicit este Nette\DI\Container ``` -Exportul de metadate .[#toc-metadata-export] --------------------------------------------- +Servicii lazy .{data-version:3.2.4} +----------------------------------- + +Setarea `lazy: true` activează crearea lazy (amânată) a serviciilor. Acest lucru înseamnă că serviciile nu sunt create efectiv în momentul în care le solicităm din containerul DI, ci abia în momentul primei lor utilizări. Acest lucru poate accelera pornirea aplicației și reduce cerințele de memorie, deoarece se creează doar serviciile care sunt efectiv necesare în request-ul respectiv. + +Pentru un serviciu specific, crearea lazy poate fi [modificată |services#Servicii lazy]. + +.[note] +Obiectele lazy pot fi utilizate doar pentru clasele utilizatorului, nu și pentru clasele interne PHP. Necesită PHP 8.4 sau o versiune mai recentă. -Clasa container DI conține, de asemenea, o mulțime de metadate. Puteți să le reduceți prin reducerea exportului de metadate. + +Export metadate +--------------- + +Clasa containerului DI conține și multe metadate. Puteți reduce dimensiunea acesteia prin reducerea exportului de metadate. ```neon di: export: - # pentru a exporta parametrii? - parameters: false # (bool) valoarea implicită este true + # exportă parametrii? + parameters: false # (bool) implicit este true - # să exporte etichete și care dintre ele? - tags: # (string[]|bool) implicit toate + # exportă tag-urile și care anume? + tags: # (string[]|bool) implicit sunt toate - event.subscriber - # exportă date pentru autocablare și care? - types: # (string[]|bool) implicit toate + # exportă datele pentru autowiring și care anume? + types: # (string[]|bool) implicit sunt toate - Nette\Database\Connection - Symfony\Component\Console\Application ``` -Dacă nu utilizați matricea `$container->parameters`, puteți dezactiva exportul de parametri. În plus, puteți exporta numai acele etichete prin care obțineți servicii utilizând metoda `$container->findByTag(...)`. -Dacă nu apelați deloc metoda, puteți dezactiva complet exportul de etichete cu `false`. +Dacă nu utilizați array-ul `$container->getParameters()`, puteți dezactiva exportul parametrilor. În plus, puteți exporta doar acele tag-uri prin care obțineți servicii folosind metoda `$container->findByTag(...)`. Dacă nu apelați deloc metoda, puteți dezactiva complet exportul tag-urilor folosind `false`. -Puteți reduce în mod semnificativ metadatele pentru [cablarea |autowiring] automată prin specificarea claselor pe care le utilizați ca parametru pentru metoda `$container->getByType()`. -Și, din nou, dacă nu apelați deloc metoda (sau doar în [application:bootstrap] pentru a obține `Nette\Application\Application`), puteți dezactiva complet exportul cu `false`. +Puteți reduce semnificativ metadatele pentru [autowiring |autowiring] specificând clasele pe care le utilizați ca parametru al metodei `$container->getByType()`. Și din nou, dacă nu apelați deloc metoda (respectiv doar în [bootstrap |application:bootstrapping] pentru a obține `Nette\Application\Application`), puteți dezactiva complet exportul folosind `false`. -Extensii .[#toc-extensions] -=========================== +Extensii +======== -Înregistrarea altor extensii DI. În acest fel, adăugăm, de exemplu, extensia DI `Dibi\Bridges\Nette\DibiExtension22` sub numele `dibi`: +Înregistrarea altor extensii DI. În acest fel adăugăm, de exemplu, extensia DI `Dibi\Bridges\Nette\DibiExtension22` sub numele `dibi` ```neon extensions: dibi: Dibi\Bridges\Nette\DibiExtension22 ``` -Apoi o configurăm în secțiunea sa numită tot `dibi`: +Ulterior, o configurăm în secțiunea `dibi`: ```neon dibi: host: localhost ``` -De asemenea, puteți adăuga o clasă de extensie cu parametri: +Ca extensie se poate adăuga și o clasă care are parametri: ```neon extensions: @@ -154,10 +167,10 @@ extensions: ``` -Includerea fișierelor .[#toc-including-files] -============================================= +Includerea fișierelor +===================== -Fișiere de configurare suplimentare pot fi inserate în secțiunea `includes`: +Putem include alte fișiere de configurare în secțiunea `includes`: ```neon includes: @@ -166,7 +179,7 @@ includes: - presenters.neon ``` -Numele `parameters.php` nu este o greșeală de tipar, configurația poate fi scrisă și într-un fișier PHP, care o returnează sub formă de matrice: +Numele `parameters.php` nu este o greșeală de tipar, configurația poate fi scrisă și într-un fișier PHP, care o returnează ca array: ```php <?php @@ -179,78 +192,74 @@ return [ ]; ``` -În cazul în care în fișierele de configurare apar elemente cu aceleași chei, acestea vor fi [suprascrise sau fuzionate |#Merging] în cazul array-urilor. Ultimul fișier inclus are o prioritate mai mare decât cel anterior. Fișierul în care este listată secțiunea `includes` are o prioritate mai mare decât fișierele incluse în el. +Dacă în fișierele de configurare apar elemente cu aceleași chei, acestea vor fi suprascrise sau, în cazul [array-urilor, combinate |#Combinare]. Fișierul inclus ulterior are prioritate mai mare decât cel anterior. Fișierul în care este specificată secțiunea `includes` are prioritate mai mare decât fișierele incluse în el. -Căutare .[#toc-search] -====================== +Search +====== -Adăugarea automată a serviciilor în containerul DI face munca extrem de plăcută. Nette adaugă automat prezentatorii la container, dar puteți adăuga cu ușurință orice alte clase. +Adăugarea automată a serviciilor în containerul DI face munca extrem de plăcută. Nette adaugă automat presenterii în container, dar se pot adăuga ușor și orice alte clase. -Trebuie doar să specificați în ce directoare (și subdirectoare) trebuie căutate clasele: +Este suficient să specificați în ce directoare (și subdirectoare) trebuie căutate clasele: ```neon search: - # alegeți singuri denumirile secțiunilor - myForms: - in: %appDir%/Forms - - model: - in: %appDir%/Model + - in: %appDir%/Forms + - in: %appDir%/Model ``` -De obicei, însă, nu dorim să adăugăm toate clasele și interfețele, așa că le putem filtra: +De obicei, însă, nu dorim să adăugăm absolut toate clasele și interfețele, așa că le putem filtra: ```neon search: - myForms: - in: %appDir%/Forms + - in: %appDir%/Forms - # filtrarea după numele fișierului (string|string[]) + # filtrare după numele fișierului (string|string[]) files: - *Factory.php - # filtrarea după numele clasei (string|string[]) + # filtrare după numele clasei (string|string[]) classes: - *Factory ``` -Sau putem selecta clasele care moștenesc sau implementează cel puțin una dintre următoarele clase: +Sau putem selecta clase care moștenesc sau implementează cel puțin una dintre clasele specificate: ```neon search: - myForms: + - in: %appDir% extends: - App\*Form implements: - App\*FormInterface ``` -De asemenea, se pot defini reguli negative, adică măști de nume de clasă sau strămoși, iar dacă acestea sunt conforme, serviciul nu va fi adăugat la containerul DI: +Se pot defini și reguli de excludere, adică măști pentru numele clasei sau strămoși ereditari, care, dacă se potrivesc, serviciul nu se adaugă în containerul DI: ```neon search: - myForms: + - in: %appDir% exclude: + files: ... classes: ... extends: ... implements: ... ``` -Se pot stabili etichete pentru serviciile adăugate: +Tuturor serviciilor li se pot seta tag-uri: ```neon search: - myForms: + - in: %appDir% tags: ... ``` -Fuziune .[#toc-merging] -======================= +Combinare +========= -În cazul în care elemente cu aceleași chei apar în mai multe fișiere de configurare, acestea vor fi suprascrise sau fuzionate, în cazul tablourilor. Ultimul fișier inclus are o prioritate mai mare. +Dacă în mai multe fișiere de configurare apar elemente cu aceleași chei, acestea vor fi suprascrise sau, în cazul array-urilor, combinate. Fișierul inclus ulterior are prioritate mai mare decât cel anterior. <table class=table> <tr> @@ -283,7 +292,7 @@ items: </tr> </table> -Pentru a împiedica fuzionarea unei anumite matrice, utilizați semnul exclamării imediat după numele matricei: +Pentru array-uri, se poate preveni combinarea specificând un semn de exclamare după numele cheii: <table class=table> <tr> @@ -314,5 +323,4 @@ items: </tr> </table> - -{{maintitle: Configurație de injecție a dependenței}} +{{maintitle: Configurarea Injecției de Dependență}} diff --git a/dependency-injection/ro/container.texy b/dependency-injection/ro/container.texy index fbac768d59..2a47b7c357 100644 --- a/dependency-injection/ro/container.texy +++ b/dependency-injection/ro/container.texy @@ -1,16 +1,16 @@ -Ce este DI Container? -********************* +Ce este un container DI? +************************ .[perex] -Containerul de injecție a dependențelor (DIC) este o clasă care poate instanția și configura obiecte. +Containerul de injecție de dependență (DIC) este o clasă care poate instanția și configura obiecte. -S-ar putea să vă surprindă, dar în multe cazuri nu aveți nevoie de un container de injecție a dependențelor pentru a profita de injecția dependențelor (abreviat DI). La urma urmei, chiar și în [capitolul anterior |introduction] am arătat exemple specifice de DI și nu a fost nevoie de niciun container. +Poate vă va surprinde, dar în multe cazuri nu aveți nevoie de un container de injecție de dependență pentru a beneficia de avantajele injecției de dependență (pe scurt DI). Până la urmă, chiar și în [capitolul introductiv |introduction] am arătat DI pe exemple concrete și nu a fost nevoie de niciun container. -Cu toate acestea, dacă trebuie să gestionați un număr mare de obiecte diferite cu multe dependențe, un container de injecție a dependențelor va fi cu adevărat util. Ceea ce este probabil cazul aplicațiilor web construite pe un framework. +Cu toate acestea, dacă trebuie să gestionați un număr mare de obiecte diferite cu multe dependențe, un container de injecție de dependență va fi cu adevărat util. Ceea ce este cazul aplicațiilor web construite pe un framework. -În capitolul anterior, am introdus clasele `Article` și `UserController`. Ambele au unele dependențe, și anume baza de date și fabrica `ArticleFactory`. Și pentru aceste clase, vom crea acum un container. Desigur, pentru un exemplu atât de simplu, nu are sens să avem un container. Dar vom crea unul pentru a arăta cum arată și cum funcționează. +În capitolul anterior, am prezentat clasele `Article` și `UserController`. Ambele au anumite dependențe, și anume baza de date și factory-ul `ArticleFactory`. Și pentru aceste clase vom crea acum un container. Desigur, pentru un exemplu atât de simplu nu are sens să avem un container. Dar îl vom crea pentru a arăta cum arată și cum funcționează. -Iată un container simplu, codat pentru exemplul de mai sus: +Iată un container simplu hardcodat pentru exemplul dat: ```php class Container @@ -39,9 +39,9 @@ $container = new Container; $controller = $container->createUserController(); ``` -Pur și simplu cerem containerului obiectul și nu mai trebuie să știm nimic despre cum să îl creăm sau care sunt dependențele sale; containerul știe toate acestea. Dependențele sunt injectate automat de către container. Aceasta este puterea sa. +Întrebăm doar containerul despre obiect și nu mai trebuie să știm nimic despre cum să îl creăm și ce dependențe are; containerul știe toate acestea. Dependențele sunt injectate automat de container. Aici stă puterea sa. -Până în prezent, containerul are totul codificat. Așadar, facem următorul pas și adăugăm parametri pentru a face containerul cu adevărat util: +Containerul are deocamdată toate datele scrise hardcodat. Vom face deci următorul pas și vom adăuga parametri pentru ca containerul să fie cu adevărat util: ```php class Container @@ -70,9 +70,9 @@ $container = new Container([ ]); ``` -Cititorii isteți ar fi putut observa o problemă. De fiecare dată când obțin un obiect `UserController`, se creează, de asemenea, o nouă instanță `ArticleFactory` și o bază de date. Cu siguranță nu ne dorim acest lucru. +Cititorii atenți ar fi putut observa o anumită problemă. De fiecare dată când obțin obiectul `UserController`, se creează și o nouă instanță `ArticleFactory` și a bazei de date. Cu siguranță nu dorim acest lucru. -Așa că adăugăm o metodă `getService()` care va returna aceleași instanțe la nesfârșit: +Vom adăuga deci metoda `getService()`, care va returna mereu aceleași instanțe: ```php class Container @@ -87,7 +87,7 @@ class Container public function getService(string $name): object { if (!isset($this->services[$name])) { - // getService('Database') apelează createDatabase() + // getService('Database') va apela createDatabase() $method = 'create' . $name; $this->services[$name] = $this->$method(); } @@ -98,9 +98,9 @@ class Container } ``` -La primul apel la, de exemplu, `$container->getService('Database')`, `createDatabase()` va crea un obiect de bază de date, pe care îl va stoca în matricea `$services` și îl va returna direct la următorul apel. +La primul apel, de ex. `$container->getService('Database')`, va lăsa `createDatabase()` să creeze obiectul bazei de date, pe care îl va stoca în array-ul `$services` și la următorul apel îl va returna direct. -De asemenea, modificăm restul containerului pentru a utiliza `getService()`: +Modificăm și restul containerului pentru a utiliza `getService()`: ```php class Container @@ -119,9 +119,9 @@ class Container } ``` -Apropo, termenul de serviciu se referă la orice obiect gestionat de container. De aici și numele metodei `getService()`. +Apropo, termenul serviciu se referă la orice obiect gestionat de container. De aceea și numele metodei `getService()`. -S-a făcut. Avem un container DI complet funcțional! Și îl putem folosi: +Gata. Avem un container DI complet funcțional! Și îl putem folosi: ```php $container = new Container([ @@ -134,9 +134,9 @@ $controller = $container->getService('UserController'); $database = $container->getService('Database'); ``` -După cum puteți vedea, nu este dificil să scrieți un DIC. Este de remarcat faptul că obiectele însele nu știu că sunt create de un container. Astfel, este posibil să se creeze orice obiect în PHP în acest mod, fără a afecta codul sursă al acestora. +După cum puteți vedea, scrierea unui DIC nu este nimic complicat. Merită menționat că obiectele în sine nu știu că sunt create de vreun container. Astfel, este posibil să se creeze în acest mod orice obiect în PHP fără a interveni în codul său sursă. -Crearea și întreținerea manuală a unei clase container poate deveni un coșmar destul de repede. Prin urmare, în capitolul următor vom vorbi despre [Nette DI Container |nette-container], care se poate genera și actualiza aproape automat. +Crearea și întreținerea manuală a clasei containerului poate deveni destul de repede un coșmar. De aceea, în capitolul următor vom vorbi despre [Containerul Nette DI |nette-container], care se poate genera și actualiza aproape singur. -{{maintitle: Ce este Containerul de Injecție a Dependenței?}} +{{maintitle: Ce este un container de injecție de dependență?}} diff --git a/dependency-injection/ro/extensions.texy b/dependency-injection/ro/extensions.texy index 39bab90248..016ab80c2d 100644 --- a/dependency-injection/ro/extensions.texy +++ b/dependency-injection/ro/extensions.texy @@ -2,16 +2,16 @@ Crearea extensiilor pentru Nette DI *********************************** .[perex] -Generarea unui container DI, pe lângă fișierele de configurare, afectează și așa-numitele *extensiuni*. Le activăm în fișierul de configurare din secțiunea `extensions`. +Generarea containerului DI, pe lângă fișierele de configurare, este influențată și de așa-numitele *extensii*. Le activăm în fișierul de configurare în secțiunea `extensions`. -În acest mod adăugăm extensia reprezentată de clasa `BlogExtension` cu numele `blog`: +Astfel adăugăm extensia reprezentată de clasa `BlogExtension` sub numele `blog`: ```neon extensions: blog: BlogExtension ``` -Fiecare extensie de compilare moștenește din [api:Nette\DI\CompilerExtension] și poate implementa următoarele metode care sunt apelate în timpul compilării DI: +Fiecare extensie a compilatorului moștenește de la [api:Nette\DI\CompilerExtension] și poate implementa următoarele metode, care sunt apelate succesiv în timpul construirii containerului DI: 1. getConfigSchema() 2. loadConfiguration() @@ -22,18 +22,18 @@ Fiecare extensie de compilare moștenește din [api:Nette\DI\CompilerExtension] getConfigSchema() .[method] =========================== -Această metodă este apelată mai întâi. Aceasta definește schema utilizată pentru validarea parametrilor de configurare. +Această metodă este apelată prima. Definește schema pentru validarea parametrilor de configurare. -Extensiile sunt configurate într-o secțiune al cărei nume este același cu cel al secțiunii în care a fost adăugată extensia, de exemplu `blog`. +Configurăm extensia în secțiunea al cărei nume este același cu cel sub care a fost adăugată extensia, adică `blog`: ```neon -# același nume ca și extensia mea +# același nume ca extensia blog: postsPerPage: 10 - comments: false + allowComments: false ``` -Vom defini o schemă care să descrie toate opțiunile de configurare, inclusiv tipurile, valorile acceptate și, eventual, valorile implicite ale acestora: +Creăm o schemă care descrie toate opțiunile de configurare, inclusiv tipurile lor, valorile permise și, eventual, valorile implicite: ```php use Nette\Schema\Expect; @@ -50,9 +50,9 @@ class BlogExtension extends Nette\DI\CompilerExtension } ``` -Consultați [Schema |schema:] pentru documentație. În plus, puteți specifica ce opțiuni pot fi [dinamice |application:bootstrap#Dynamic Parameters] folosind `dynamic()`, de exemplu `Expect::int()->dynamic()`. +Documentația o găsiți pe pagina [Schema |schema:]. În plus, se poate specifica ce opțiuni pot fi [dinamice |application:bootstrapping#Parametri dinamici] folosind `dynamic()`, de ex. `Expect::int()->dynamic()`. -Accesăm configurația prin intermediul `$this->config`, care este un obiect `stdClass`: +Accesăm configurația prin variabila `$this->config`, care este un obiect `stdClass`: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -71,7 +71,7 @@ class BlogExtension extends Nette\DI\CompilerExtension loadConfiguration() .[method] ============================= -Această metodă este utilizată pentru a adăuga servicii la container. Acest lucru se face prin [api:Nette\DI\ContainerBuilder]: +Se utilizează pentru adăugarea serviciilor în container. Pentru aceasta se folosește [api:Nette\DI\ContainerBuilder]: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -86,19 +86,19 @@ class BlogExtension extends Nette\DI\CompilerExtension } ``` -Convenția este de a prefixa serviciile adăugate de o extensie cu numele acesteia, astfel încât să nu apară conflicte de nume. Acest lucru se face prin `prefix()`, astfel încât, dacă extensia se numește "blog", serviciul se va numi `blog.articles`. +Convenția este de a prefixa serviciile adăugate de extensie cu numele său, pentru a evita conflictele de nume. Acest lucru îl face metoda `prefix()`, deci dacă extensia se numește `blog`, serviciul va purta numele `blog.articles`. -Dacă trebuie să redenumim un serviciu, putem crea un alias cu numele său original pentru a menține compatibilitatea cu trecutul. În mod similar, acest lucru este ceea ce face Nette pentru, de exemplu, `routing.router`, care este, de asemenea, disponibil sub numele anterior `router`. +Dacă trebuie să redenumim un serviciu, putem crea un alias cu numele original pentru a menține compatibilitatea retroactivă. Nette face acest lucru similar, de exemplu, pentru serviciul `routing.router`, care este disponibil și sub numele anterior `router`. ```php $builder->addAlias('router', 'routing.router'); ``` -Recuperarea serviciilor dintr-un fișier .[#toc-retrieve-services-from-a-file] ------------------------------------------------------------------------------ +Încărcarea serviciilor din fișier +--------------------------------- -Putem crea servicii utilizând API-ul ContainerBuilder, dar le putem adăuga și prin intermediul cunoscutului fișier de configurare NEON și al secțiunii sale `services`. Prefixul `@extension` reprezintă extensia curentă. +Serviciile nu trebuie create doar folosind API-ul clasei ContainerBuilder, ci și prin sintaxa cunoscută utilizată în fișierul de configurare NEON în secțiunea services. Prefixul `@extension` reprezintă extensia curentă. ```neon services: @@ -112,7 +112,7 @@ services: create: MyBlog\Components\ArticlesList(@extension.articles) ``` -Vom adăuga servicii în acest mod: +Încărcăm serviciile: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -121,7 +121,7 @@ class BlogExtension extends Nette\DI\CompilerExtension { $builder = $this->getContainerBuilder(); - // încarcă fișierul de configurare pentru extensie + // încărcarea fișierului de configurare pentru extensie $this->compiler->loadDefinitionsFromConfig( $this->loadFromFile(__DIR__ . '/blog.neon')['services'], ); @@ -133,7 +133,7 @@ class BlogExtension extends Nette\DI\CompilerExtension beforeCompile() .[method] ========================= -Metoda este apelată atunci când containerul conține toate serviciile adăugate de extensiile individuale în metodele `loadConfiguration`, precum și fișierele de configurare ale utilizatorului. În această fază a asamblării, putem apoi modifica definițiile serviciilor sau adăuga legături între ele. Puteți utiliza metoda `findByTag()` pentru a căuta servicii după etichete sau metoda `findByType()` pentru a căuta după clasă sau interfață. +Metoda este apelată în momentul în care containerul conține toate serviciile adăugate de extensiile individuale în metodele `loadConfiguration` și, de asemenea, de fișierele de configurare ale utilizatorului. În această fază a construirii, putem deci modifica definițiile serviciilor sau completa legăturile dintre ele. Pentru căutarea serviciilor în container după tag-uri se poate utiliza metoda `findByTag()`, iar după clasă sau interfață, metoda `findByType()`. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -153,7 +153,7 @@ class BlogExtension extends Nette\DI\CompilerExtension afterCompile() .[method] ======================== -În această fază, clasa container este deja generată ca obiect [ClassType |php-generator:#classes], conține toate metodele pe care serviciul le creează și este gata pentru a fi pusă în cache ca fișier PHP. În acest moment putem încă să modificăm codul clasei rezultate. +În această fază, clasa containerului este deja generată sub forma unui obiect [ClassType |php-generator:#Clase], conține toate metodele care creează servicii și este pregătită pentru scrierea în cache. Putem încă modifica codul rezultat al clasei în acest moment. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -167,12 +167,12 @@ class BlogExtension extends Nette\DI\CompilerExtension ``` -$initializare .[wiki-method] -============================ +$initialization .[method] +========================= -Configuratorul apelează codul de inițializare după [crearea containerului |application:bootstrap#index.php], care este creat prin scrierea într-un obiect `$this->initialization` folosind [metoda addBody() |php-generator:#method-and-function-body]. +Clasa Configurator, după [crearea containerului |application:bootstrapping#index.php], apelează codul de inițializare, care se creează prin scrierea în obiectul `$this->initialization` folosind [metoda addBody() |php-generator:#Corpuri de metode și funcții]. -Vom prezenta un exemplu de pornire a unei sesiuni sau de pornire a serviciilor care au eticheta `run` folosind codul de inițializare: +Vom arăta un exemplu despre cum, de exemplu, să pornim sesiunea cu codul de inițializare sau să rulăm servicii care au tag-ul `run`: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -184,7 +184,7 @@ class BlogExtension extends Nette\DI\CompilerExtension $this->initialization->addBody('$this->getService("session")->start()'); } - // serviciile cu tag-ul "run" trebuie create după instanțierea containerului. + // serviciile cu tag-ul run trebuie create după instanțierea containerului $builder = $this->getContainerBuilder(); foreach ($builder->findByTag('run') as $name => $foo) { $this->initialization->addBody('$this->getService(?);', [$name]); diff --git a/dependency-injection/ro/factory.texy b/dependency-injection/ro/factory.texy index 6fb2fa2fea..540c2320d7 100644 --- a/dependency-injection/ro/factory.texy +++ b/dependency-injection/ro/factory.texy @@ -1,12 +1,12 @@ -Fabrici generate -**************** +Factory-uri generate +******************** .[perex] -Nette DI poate genera automat codul fabricii pe baza interfeței, ceea ce vă scutește de scrierea de cod. +Nette DI poate genera automat codul factory-urilor pe baza interfețelor, ceea ce vă economisește scrierea codului. -O fabrică este o clasă care creează și configurează obiecte. Prin urmare, le transmite și dependențele acestora. Vă rugăm să nu faceți confuzie cu modelul de proiectare *factory method*, care descrie un mod specific de utilizare a fabricilor și nu are legătură cu acest subiect. +Un factory este o clasă care produce și configurează obiecte. Le transmite deci și dependențele lor. Vă rugăm să nu confundați cu pattern-ul de design *factory method*, care descrie un mod specific de utilizare a factory-urilor și nu are legătură cu acest subiect. -Am arătat cum arată o astfel de fabrică în [capitolul introductiv |introduction#factory]: +Cum arată un astfel de factory am arătat în [capitolul introductiv |introduction#Fabrica]: ```php class ArticleFactory @@ -23,7 +23,7 @@ class ArticleFactory } ``` -Nette DI poate genera automat cod de fabrică. Tot ce trebuie să faceți este să creați o interfață și Nette DI va genera o implementare. Interfața trebuie să aibă exact o metodă numită `create` și să declare un tip de returnare: +Nette DI poate genera automat codul factory-urilor. Tot ce trebuie să faceți este să creați o interfață și Nette DI va genera implementarea. Interfața trebuie să aibă exact o metodă numită `create` și să declare tipul returnat: ```php interface ArticleFactory @@ -32,7 +32,7 @@ interface ArticleFactory } ``` -Astfel, fabrica `ArticleFactory` are o metodă `create` care creează obiecte `Article`. Clasa `Article` ar putea arăta, de exemplu, după cum urmează: +Deci, factory-ul `ArticleFactory` are o metodă `create`, care creează obiecte `Article`. Clasa `Article` poate arăta, de exemplu, astfel: ```php class Article @@ -44,16 +44,16 @@ class Article } ``` -Adăugați fabrica în fișierul de configurare: +Adăugăm factory-ul în fișierul de configurare: ```neon services: - ArticleFactory ``` -Nette DI va genera implementarea fabricii corespunzătoare. +Nette DI va genera implementarea corespunzătoare a factory-ului. -Astfel, în codul care utilizează fabrica, solicităm obiectul prin interfață, iar Nette DI utilizează implementarea generată: +În codul care utilizează factory-ul, solicităm astfel obiectul conform interfeței și Nette DI va utiliza implementarea generată: ```php class UserController @@ -65,17 +65,17 @@ class UserController public function foo() { - // permiteți fabricii să creeze un obiect + // lăsăm factory-ul să creeze obiectul $article = $this->articleFactory->create(); } } ``` -Fabrica parametrizată .[#toc-parameterized-factory] -=================================================== +Factory parametrizat +==================== -Metoda factory `create` poate accepta parametri pe care îi transmite apoi constructorului. De exemplu, să adăugăm un ID de autor de articol la clasa `Article`: +Metoda factory `create` poate accepta parametri, pe care îi transmite apoi constructorului. Să completăm, de exemplu, clasa `Article` cu ID-ul autorului articolului: ```php class Article @@ -88,7 +88,7 @@ class Article } ``` -Vom adăuga, de asemenea, parametrul la fabrică: +Adăugăm parametrul și în factory: ```php interface ArticleFactory @@ -97,13 +97,13 @@ interface ArticleFactory } ``` -Deoarece parametrul din constructor și parametrul din fabrică au același nume, Nette DI le va trece automat. +Datorită faptului că parametrul din constructor și parametrul din factory se numesc la fel, Nette DI îi transmite complet automat. -Definiție avansată .[#toc-advanced-definition] -============================================== +Definiție avansată +================== -Definiția poate fi scrisă și sub formă de mai multe rânduri cu ajutorul tastei `implement`: +Definiția poate fi scrisă și într-o formă multi-linie folosind cheia `implement`: ```neon services: @@ -111,9 +111,9 @@ services: implement: ArticleFactory ``` -Atunci când se scrie în acest mod mai lung, este posibil să se furnizeze argumente suplimentare pentru constructor în cheia `arguments` și o configurație suplimentară folosind `setup`, la fel ca pentru serviciile normale. +La scrierea în această formă mai lungă, este posibil să se specifice argumente suplimentare pentru constructor în cheia `arguments` și configurație suplimentară folosind `setup`, la fel ca la serviciile obișnuite. -Exemplu: dacă metoda `create()` nu acceptă parametrul `$authorId`, am putea specifica o valoare fixă în configurație, care va fi transmisă constructorului `Article`: +Exemplu: dacă metoda `create()` nu ar accepta parametrul `$authorId`, am putea specifica o valoare fixă în configurație, care ar fi transmisă constructorului `Article`: ```neon services: @@ -123,7 +123,7 @@ services: authorId: 123 ``` -Sau, dimpotrivă, dacă `create()` accepta parametrul `$authorId`, dar acesta nu făcea parte din constructor și era transmis de metoda `Article::setAuthorId()`, ne-am putea referi la el în secțiunea `setup`: +Sau invers, dacă `create()` ar accepta parametrul `$authorId`, dar acesta nu ar face parte din constructor și s-ar transmite prin metoda `Article::setAuthorId()`, ne-am referi la el în secțiunea `setup`: ```neon services: @@ -134,15 +134,14 @@ services: ``` -Accesor .[#toc-accessor] -======================== +Accessor +======== -În afară de fabrici, Nette poate genera și așa numiții accesori. Accesorul este un obiect cu metoda `get()` care returnează un anumit serviciu de la containerul DI. Mai multe apeluri la `get()` vor returna întotdeauna aceeași instanță. +Nette poate genera, pe lângă factory-uri, și așa-numiții accesori. Aceștia sunt obiecte cu o metodă `get()`, care returnează un anumit serviciu din containerul DI. Apelarea repetată a `get()` returnează mereu aceeași instanță. -Accesorii permit încărcarea leneșă a dependențelor. Să avem o clasă care înregistrează erorile într-o bază de date specială. Dacă conexiunea la baza de date ar fi trecută ca dependență în constructorul său, conexiunea ar trebui să fie mereu creată, deși ar fi folosită doar rareori când apare o eroare, astfel încât conexiunea ar rămâne în mare parte neutilizată. -În schimb, clasa poate trece un accesor și, atunci când este apelată metoda `get()`, numai atunci este creat obiectul bazei de date: +Accesorii oferă lazy-loading pentru dependențe. Să presupunem că avem o clasă care scrie erori într-o bază de date specială. Dacă această clasă ar primi conexiunea la baza de date ca dependență prin constructor, conexiunea ar trebui creată întotdeauna, deși în practică eroarea apare doar excepțional și, prin urmare, în majoritatea cazurilor conexiunea ar rămâne neutilizată. În schimb, clasa primește un accesor și abia atunci când se apelează `get()`, se creează obiectul bazei de date: -Cum se creează un accesor? Scrieți doar o interfață, iar Nette DI va genera implementarea. Interfața trebuie să aibă exact o metodă numită `get` și trebuie să declare tipul de returnare: +Cum se creează un accesor? Este suficient să scrieți o interfață și Nette DI va genera implementarea. Interfața trebuie să aibă exact o metodă numită `get` și să declare tipul returnat: ```php interface PDOAccessor @@ -151,7 +150,7 @@ interface PDOAccessor } ``` -Adăugați accesorul în fișierul de configurare împreună cu definiția serviciului pe care accesorul îl va returna: +Adăugăm accesorul în fișierul de configurare, unde este definit și serviciul pe care îl va returna: ```neon services: @@ -159,62 +158,68 @@ services: - PDO(%dsn%, %user%, %password%) ``` -Accesorul returnează un serviciu de tip `PDO` și, deoarece există un singur astfel de serviciu în configurație, accesorul îl va returna. În cazul în care există mai multe servicii configurate de acest tip, puteți specifica care dintre ele trebuie returnat folosind numele său, de exemplu `- PDOAccessor(@db1)`. +Deoarece accesorul returnează un serviciu de tip `PDO` și în configurație există un singur astfel de serviciu, îl va returna tocmai pe acesta. Dacă ar exista mai multe servicii de tipul respectiv, specificăm serviciul returnat folosind numele, de ex. `- PDOAccessor(@db1)`. -Multifactory/Accesor .[#toc-multifactory-accessor] -================================================== -Până acum, fabricile și accesorii puteau crea sau returna doar un singur obiect. Se poate crea, de asemenea, un multifactory combinat cu un accesor. Interfața unei astfel de clase multifactory poate consta din mai multe metode numite `create<name>()` și `get<name>()`, de exemplu: +Factory/Accesor multiplu +======================== +Factory-urile și accesorii noștri au putut până acum să producă sau să returneze doar un singur obiect. Dar se pot crea foarte ușor și factory-uri multiple combinate cu accesori. Interfața unei astfel de clase va conține un număr arbitrar de metode cu numele `create<name>()` și `get<name>()`, de ex.: ```php interface MultiFactory { function createArticle(): Article; - function createFoo(): Model\Foo; function getDb(): PDO; } ``` -În loc să transmiteți mai multe fabrici și accesori generați, puteți transmite doar o singură multifactorie complexă. +Deci, în loc să transmitem mai multe factory-uri și accesori generați, transmitem un factory mai complex care poate face mai multe lucruri. -Alternativ, puteți utiliza `create()` și `get()` cu un parametru în loc de mai multe metode: +Alternativ, în loc de mai multe metode, se poate folosi `get()` cu un parametru: ```php interface MultiFactoryAlt { - function create($name); function get($name): PDO; } ``` -În acest caz, `MultiFactory::createArticle()` face același lucru ca și `MultiFactoryAlt::create('article')`. Cu toate acestea, sintaxa alternativă are câteva dezavantaje. Nu este clar ce valori `$name` sunt acceptate, iar tipul de returnare nu poate fi specificat în interfață atunci când se utilizează mai multe valori `$name` diferite. +Atunci este valabil că `MultiFactory::getArticle()` face același lucru ca `MultiFactoryAlt::get('article')`. Cu toate acestea, scrierea alternativă are dezavantajul că nu este evident ce valori `$name` sunt suportate și, logic, nici nu se pot distinge în interfață diferite valori returnate pentru diferite `$name`. + +Definiție prin listă +-------------------- +În acest mod se poate defini un factory multiplu în configurație: .{data-version:3.2.0} + +```neon +services: + - MultiFactory( + article: Article # definește createArticle() + db: PDO(%dsn%, %user%, %password%) # definește getDb() + ) +``` -Definiție cu o listă .[#toc-definition-with-a-list] ---------------------------------------------------- -Hos pentru a defini un multifactory în configurația dvs. Să creăm trei servicii care vor fi returnate de către multifactory și multifactory însuși: +Sau ne putem referi în definiția factory-ului la servicii existente folosind o referință: ```neon services: article: Article - - Model\Foo - PDO(%dsn%, %user%, %password%) - MultiFactory( - article: @article # createArticle() - foo: @Model\Foo # createFoo() - db: @\PDO # getDb() + article: @article # definește createArticle() + db: @\PDO # definește getDb() ) ``` -Definiție cu etichete .[#toc-definition-with-tags] --------------------------------------------------- +Definiție prin tag-uri +---------------------- -O altă opțiune de definire a unui multifactorial este utilizarea [etichetelor |services#Tags]: +A doua opțiune este utilizarea [tag-urilor |services#Tag-uri] pentru definire: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter - App\Model\DatabaseAccessor( db1: @database.db1.explorer ) diff --git a/dependency-injection/ro/faq.texy b/dependency-injection/ro/faq.texy index 9d09d2e6fe..241a7f3c64 100644 --- a/dependency-injection/ro/faq.texy +++ b/dependency-injection/ro/faq.texy @@ -2,97 +2,91 @@ *********************************** -Este DI un alt nume pentru IoC? .[#toc-is-di-another-name-for-ioc] ------------------------------------------------------------------- +Este DI un alt nume pentru IoC? +------------------------------- -*Inversion of Control* (IoC) este un principiu care se concentrează asupra modului în care este executat codul - dacă codul dumneavoastră inițiază codul extern sau dacă codul dumneavoastră este integrat în codul extern, care îl apelează. -IoC este un concept larg care include [evenimente |nette:glossary#Events], așa-numitul [principiu Hollywood |application:components#Hollywood style] și alte aspecte. -Fabricile, care fac parte din [Regula nr. 3: Lasă fabrica să se ocupe |introduction#Rule #3: Let the Factory Handle It], și reprezintă inversiunea pentru operatorul `new`, sunt, de asemenea, componente ale acestui concept. +*Inversion of Control* (IoC) este un principiu axat pe modul în care este executat codul - dacă codul dvs. rulează cod străin sau dacă codul dvs. este integrat în cod străin, care îl apelează ulterior. IoC este un termen larg care include [evenimente |nette:glossary#Evenimente], așa-numitul [Principiu Hollywood |application:components#Stilul Hollywood] și alte aspecte. Parte a acestui concept sunt și factory-urile, despre care vorbește [Regula nr. 3: lasă pe seama factory-ului |introduction#Regula nr. 3: Lasă pe seama fabricii], și care reprezintă o inversiune pentru operatorul `new`. -*Dependency Injection* (DI) se referă la modul în care un obiect știe despre un alt obiect, adică la dependență. Este un model de proiectare care necesită transmiterea explicită a dependențelor între obiecte. +*Dependency Injection* (DI) se concentrează pe modul în care un obiect află despre alt obiect, adică despre dependențele sale. Este un pattern de design care necesită transmiterea explicită a dependențelor între obiecte. -Astfel, se poate spune că DI este o formă specifică de IoC. Cu toate acestea, nu toate formele de IoC sunt potrivite din punct de vedere al purității codului. De exemplu, printre antimodele, includem toate tehnicile care lucrează cu [starea globală |global state] sau așa-numitul [Service Locator |#What is a Service Locator]. +Se poate deci spune că DI este o formă specifică de IoC. Cu toate acestea, nu toate formele de IoC sunt potrivite din punct de vedere al curățeniei codului. De exemplu, printre anti-pattern-uri se numără tehnicile care lucrează cu [starea globală |global-state] sau așa-numitul [Service Locator |#Ce este Service Locator]. -Ce este un Service Locator? .[#toc-what-is-a-service-locator] -------------------------------------------------------------- +Ce este Service Locator? +------------------------ -Un localizator de servicii este o alternativă la injecția de dependență. Funcționează prin crearea unui depozit central în care sunt înregistrate toate serviciile sau dependențele disponibile. Atunci când un obiect are nevoie de o dependență, o solicită de la Localizatorul de servicii. +Este o alternativă la Dependency Injection. Funcționează prin crearea unui depozit central unde sunt înregistrate toate serviciile sau dependențele disponibile. Când un obiect are nevoie de o dependență, o solicită de la Service Locator. -Cu toate acestea, în comparație cu injecția de dependență, pierde din transparență: dependențele nu sunt transmise direct obiectelor și, prin urmare, nu sunt ușor de identificat, ceea ce necesită examinarea codului pentru a descoperi și înțelege toate conexiunile. Testarea este, de asemenea, mai complicată, deoarece nu putem trece pur și simplu obiecte simulate către obiectele testate, ci trebuie să trecem prin intermediul Localizatorului de servicii. În plus, Service Locator perturbă proiectarea codului, deoarece obiectele individuale trebuie să fie conștiente de existența sa, ceea ce diferă de Dependency Injection, unde obiectele nu au cunoștință de containerul DI. +Cu toate acestea, în comparație cu Dependency Injection, pierde din transparență: dependențele nu sunt transmise direct obiectelor și nu sunt la fel de ușor de identificat, ceea ce necesită examinarea codului pentru a descoperi și înțelege toate legăturile. Testarea este, de asemenea, mai complicată, deoarece nu putem transmite pur și simplu obiecte mock obiectelor testate, ci trebuie să trecem prin Service Locator. În plus, Service Locator perturbă designul codului, deoarece obiectele individuale trebuie să știe despre existența sa, ceea ce diferă de Dependency Injection, unde obiectele nu au cunoștință despre containerul DI. -Când este mai bine să nu folosiți DI? .[#toc-when-is-it-better-not-to-use-di] ------------------------------------------------------------------------------ +Când este mai bine să nu folosim DI? +------------------------------------ -Nu există dificultăți cunoscute asociate cu utilizarea modelului de proiectare Dependency Injection. Dimpotrivă, obținerea dependențelor din locații accesibile la nivel global duce la [o serie de complicații |global-state], la fel ca și utilizarea unui Service Locator. -Prin urmare, este recomandabil să se utilizeze întotdeauna DI. Aceasta nu este o abordare dogmatică, ci pur și simplu nu s-a găsit o alternativă mai bună. +Nu sunt cunoscute dificultăți asociate cu utilizarea pattern-ului de design Dependency Injection. Dimpotrivă, obținerea dependențelor din locații disponibile global duce la [o întreagă serie de complicații |global-state], la fel ca și utilizarea Service Locator-ului. Prin urmare, este recomandat să se utilizeze DI întotdeauna. Aceasta nu este o abordare dogmatică, ci pur și simplu nu a fost găsită o alternativă mai bună. -Cu toate acestea, există anumite situații în care nu ne transmitem obiecte între noi și le obținem din spațiul global. De exemplu, atunci când se depanează codul și este nevoie să se descarce valoarea unei variabile într-un anumit punct al programului, să se măsoare durata unei anumite părți a programului sau să se înregistreze un mesaj. -În astfel de cazuri, în care este vorba despre acțiuni temporare care vor fi ulterior eliminate din cod, este legitim să se utilizeze un dumper, un cronometru sau un logger accesibil la nivel global. Aceste instrumente, la urma urmei, nu fac parte din proiectarea codului. +Cu toate acestea, există anumite situații în care nu transmitem obiecte și le obținem din spațiul global. De exemplu, la depanarea codului, când trebuie să afișați valoarea unei variabile într-un anumit punct al programului, să măsurați durata unei anumite părți a programului sau să înregistrați un mesaj. În astfel de cazuri, când este vorba de acțiuni temporare care vor fi ulterior eliminate din cod, este legitim să se utilizeze un dumper, un cronometru sau un logger disponibil global. Aceste instrumente nu fac parte din designul codului. -Utilizarea DI are dezavantajele sale? .[#toc-does-using-di-have-its-drawbacks] ------------------------------------------------------------------------------- +Are utilizarea DI dezavantaje? +------------------------------ -Utilizarea Injecției de dependență implică dezavantaje, cum ar fi creșterea complexității scrierii codului sau o performanță mai slabă? Ce pierdem atunci când începem să scriem cod în conformitate cu DI? +Implică utilizarea Dependency Injection vreun dezavantaj, cum ar fi o complexitate crescută a scrierii codului sau o performanță redusă? Ce pierdem când începem să scriem cod în conformitate cu DI? -DI nu are niciun impact asupra performanței aplicației sau a cerințelor de memorie. Performanța containerului DI poate juca un rol, dar în cazul [Nette DI | nette-container], containerul este compilat în PHP pur, astfel că sarcina sa în timpul rulării aplicației este practic zero. +DI nu are impact asupra performanței sau a cerințelor de memorie ale aplicației. Performanța containerului DI poate juca un anumit rol, însă în cazul [Nette DI |nette-container], containerul este compilat în PHP pur, astfel încât overhead-ul său în timpul rulării aplicației este practic nul. -La scrierea codului, este necesar să se creeze constructori care acceptă dependențe. În trecut, acest lucru putea consuma mult timp, dar datorită IDE-urilor moderne și [promovării proprietăților constructorilor |https://blog.nette.org/ro/php-8-0-prezentare-completa-a-noutatilor#toc-constructor-property-promotion], acum este o chestiune de câteva secunde. Constructorii pot fi generați cu ușurință folosind Nette DI și un plugin PhpStorm cu doar câteva clicuri. -Pe de altă parte, nu este nevoie să scrieți singletons și puncte de acces statice. +La scrierea codului, este necesar să se creeze constructori care acceptă dependențe. În trecut, acest lucru putea fi anevoios, însă datorită IDE-urilor moderne și [promovării proprietăților constructorului |https://blog.nette.org/ro/php-8-0-complete-overview-of-news#toc-constructor-property-promotion], acum este o chestiune de câteva secunde. Factory-urile pot fi generate ușor folosind Nette DI și plugin-ul pentru PhpStorm printr-un clic de mouse. Pe de altă parte, dispare necesitatea de a scrie singleton-uri și puncte de acces statice. -Se poate concluziona că o aplicație proiectată corespunzător care utilizează DI nu este nici mai scurtă, nici mai lungă în comparație cu o aplicație care utilizează singletons. Părțile de cod care lucrează cu dependențele sunt pur și simplu extrase din clasele individuale și mutate în locații noi, adică în containerul DI și în fabrici. +Se poate constata că o aplicație proiectată corect care utilizează DI nu este nici mai scurtă, nici mai lungă în comparație cu o aplicație care utilizează singleton-uri. Părțile de cod care lucrează cu dependențe sunt doar extrase din clasele individuale și mutate în locații noi, adică în containerul DI și în factory-uri. -Cum să rescrieți o aplicație moștenită în DI? .[#toc-how-to-rewrite-a-legacy-application-to-di] ------------------------------------------------------------------------------------------------ +Cum să rescrii o aplicație legacy la DI? +---------------------------------------- -Migrarea de la o aplicație moștenită la Injecția de dependență poate fi un proces dificil, în special pentru aplicațiile mari și complexe. Este important să abordați acest proces în mod sistematic. +Trecerea de la o aplicație legacy la Dependency Injection poate fi un proces solicitant, în special pentru aplicații mari și complexe. Este important să abordați acest proces sistematic. -- Atunci când se trece la Injectarea dependenței, este important ca toți membrii echipei să înțeleagă principiile și practicile utilizate. -- În primul rând, efectuați o analiză a aplicației existente pentru a identifica componentele cheie și dependențele acestora. Creați un plan pentru care părți vor fi refactorizate și în ce ordine. -- Implementați un container DI sau, și mai bine, utilizați o bibliotecă existentă, cum ar fi Nette DI. -- Refaceți treptat fiecare parte a aplicației pentru a utiliza Injecția de dependență. Acest lucru poate implica modificarea constructorilor sau a metodelor pentru a accepta dependențele ca parametri. -- Modificați locurile din cod în care sunt create obiecte de dependență, astfel încât dependențele să fie injectate de container. Acest lucru poate include utilizarea fabricilor. +- La trecerea la Dependency Injection, este important ca toți membrii echipei să înțeleagă principiile și procedurile utilizate. +- Mai întâi, efectuați o analiză a aplicației existente și identificați componentele cheie și dependențele lor. Creați un plan care să specifice ce părți vor fi refactorizate și în ce ordine. +- Implementați un container DI sau, și mai bine, utilizați o bibliotecă existentă, de exemplu Nette DI. +- Refactorizați treptat părțile individuale ale aplicației pentru a utiliza Dependency Injection. Acest lucru poate include modificarea constructorilor sau metodelor astfel încât să accepte dependențe ca parametri. +- Modificați locurile din cod unde se creează obiecte cu dependențe, astfel încât dependențele să fie injectate de container. Acest lucru poate include utilizarea factory-urilor. -Nu uitați că trecerea la Injectarea dependențelor este o investiție în calitatea codului și în durabilitatea pe termen lung a aplicației. Deși poate fi o provocare să faceți aceste schimbări, rezultatul ar trebui să fie un cod mai curat, mai modular și ușor de testat, pregătit pentru extinderi și întreținere viitoare. +Rețineți că trecerea la Dependency Injection este o investiție în calitatea codului și în mentenabilitatea pe termen lung a aplicației. Deși poate fi dificil să efectuați aceste modificări, rezultatul ar trebui să fie un cod mai curat, mai modular și mai ușor de testat, pregătit pentru extinderi și întreținere viitoare. -De ce este preferată compoziția în locul moștenirii? .[#toc-why-composition-is-preferred-over-inheritance] ----------------------------------------------------------------------------------------------------------- -Este preferabilă utilizarea compoziției în locul moștenirii, deoarece servește scopului de reutilizare a codului, fără a fi nevoie să vă faceți griji cu privire la efectul de schimbare. Astfel, se asigură o cuplare mai lejeră, în care nu trebuie să ne facem griji că modificarea unui cod ar putea duce la modificarea unui alt cod dependent. Un exemplu tipic este situația identificată drept [iadul constructorilor |passing-dependencies#Constructor hell]. +De ce se preferă compoziția în locul moștenirii? +------------------------------------------------ +Este mai potrivit să se utilizeze [compoziția |nette:introduction-to-object-oriented-programming#Compoziție] în locul [moștenirii |nette:introduction-to-object-oriented-programming#Moștenire], deoarece servește la reutilizarea codului fără a ne preocupa de consecințele modificărilor. Oferă deci o legătură mai slabă, în care nu trebuie să ne temem că modificarea unui cod va necesita modificarea altui cod dependent. Un exemplu tipic este situația denumită [constructor hell |passing-dependencies#Constructor hell]. -Poate fi utilizat Nette DI Container în afara Nette? .[#toc-can-nette-di-container-be-used-outside-of-nette] ------------------------------------------------------------------------------------------------------------- +Se poate utiliza Nette DI Container în afara Nette? +--------------------------------------------------- -Absolut. Nette DI Container face parte din Nette, dar este conceput ca o bibliotecă autonomă care poate fi utilizată independent de alte părți ale cadrului. Trebuie doar să o instalați folosind Composer, să creați un fișier de configurare care să definească serviciile dvs. și apoi să folosiți câteva linii de cod PHP pentru a crea containerul DI. -Și puteți începe imediat să profitați de Injecția de dependență în proiectele dumneavoastră. +Categoric. Nette DI Container face parte din Nette, dar este proiectat ca o bibliotecă independentă care poate fi utilizată independent de celelalte părți ale framework-ului. Este suficient să o instalați folosind Composer, să creați un fișier de configurare cu definiția serviciilor dvs. și apoi, folosind câteva linii de cod PHP, să creați containerul DI. Și puteți începe imediat să beneficiați de avantajele Dependency Injection în proiectele dvs. -Capitolul [Nette DI Container |nette-container] descrie cum arată un caz de utilizare specific, inclusiv codul. +Modul concret de utilizare, inclusiv codurile, este descris în capitolul [Containerul Nette DI |nette-container]. -De ce este configurația în fișiere NEON? .[#toc-why-is-the-configuration-in-neon-files] ---------------------------------------------------------------------------------------- +De ce este configurația în fișiere NEON? +---------------------------------------- -NEON este un limbaj de configurare simplu și ușor de citit, dezvoltat în cadrul Nette pentru configurarea aplicațiilor, a serviciilor și a dependențelor acestora. În comparație cu JSON sau YAML, acesta oferă opțiuni mult mai intuitive și mai flexibile în acest scop. În NEON, puteți descrie în mod natural legături care nu ar fi posibil de scris în Symfony & YAML, fie deloc, fie doar printr-o descriere complexă. +NEON este un limbaj de configurare simplu și ușor de citit, care a fost dezvoltat în cadrul Nette pentru setarea aplicațiilor, serviciilor și dependențelor lor. În comparație cu JSON sau YAML, oferă opțiuni mult mai intuitive și flexibile în acest scop. În NEON se pot descrie natural legături care în Symfony & YAMLu nu ar putea fi scrise fie deloc, fie doar printr-o descriere complicată. -Analiza fișierelor NEON încetinește aplicația? .[#toc-does-parsing-neon-files-slow-down-the-application] --------------------------------------------------------------------------------------------------------- +Nu încetinește aplicația parsarea fișierelor NEON? +-------------------------------------------------- -Deși fișierele NEON sunt analizate foarte rapid, acest aspect nu contează cu adevărat. Motivul este că analiza fișierelor are loc o singură dată în timpul primei lansări a aplicației. După aceea, codul containerului DI este generat, stocat pe disc și executat pentru fiecare solicitare ulterioară, fără a mai fi nevoie de o analiză suplimentară. +Deși fișierele NEON se parsează foarte rapid, acest aspect nu contează deloc. Motivul este că parsarea fișierelor are loc doar o singură dată la prima rulare a aplicației. Apoi se generează codul containerului DI, se salvează pe disc și se rulează la fiecare request ulterior, fără a fi necesară o altă parsare. -Acesta este modul în care funcționează într-un mediu de producție. În timpul dezvoltării, fișierele NEON sunt analizate de fiecare dată când conținutul lor se modifică, asigurându-se că dezvoltatorul are întotdeauna un container DI actualizat. După cum am menționat mai devreme, parsarea efectivă este o chestiune de o clipă. +Așa funcționează în mediul de producție. În timpul dezvoltării, fișierele NEON se parsează de fiecare dată când conținutul lor se modifică, pentru ca dezvoltatorul să aibă mereu containerul DI actualizat. Parsarea în sine este, așa cum s-a spus, o chestiune de moment. -Cum pot accesa parametrii din fișierul de configurare în clasa mea? .[#toc-how-do-i-access-the-parameters-from-the-configuration-file-in-my-class] --------------------------------------------------------------------------------------------------------------------------------------------------- +Cum accesez din clasa mea parametrii din fișierul de configurare? +----------------------------------------------------------------- -Rețineți [regula nr. 1: Lăsați să vă fie transmisă |introduction#Rule #1: Let It Be Passed to You]. Dacă o clasă necesită informații dintr-un fișier de configurare, nu trebuie să ne dăm seama cum să accesăm aceste informații; în schimb, pur și simplu le cerem - de exemplu, prin intermediul constructorului clasei. Și efectuăm transmiterea în fișierul de configurare. +Să ne amintim [Regula nr. 1: lasă-l să ți se transmită |introduction#Regula nr. 1: Primește ce ai nevoie]. Dacă o clasă necesită informații din fișierul de configurare, nu trebuie să ne gândim cum să ajungem la acele informații, ci pur și simplu le solicităm - de exemplu, prin constructorul clasei. Și realizăm transmiterea în fișierul de configurare. -În acest exemplu, `%myParameter%` este un spațiu rezervat pentru valoarea parametrului `myParameter`, care va fi transmis constructorului `MyClass`: +În acest exemplu, `%myParameter%` este un substituent pentru valoarea parametrului `myParameter`, care se transmite constructorului clasei `MyClass`: ```php # config.neon @@ -103,10 +97,10 @@ services: - MyClass(%myParameter%) ``` -Dacă doriți să transmiteți mai mulți parametri sau să utilizați autowiring, este util să includeți [parametrii într-un obiect |best-practices:passing-settings-to-presenters]. +Dacă doriți să transmiteți mai mulți parametri sau să utilizați autowiring, este recomandat [să împachetați parametrii într-un obiect |best-practices:passing-settings-to-presenters]. -Nette acceptă interfața PSR-11 Container? .[#toc-does-nette-support-psr-11-container-interface] ------------------------------------------------------------------------------------------------ +Suportă Nette PSR-11: Container interface? +------------------------------------------ -Nette DI Container nu acceptă direct PSR-11. Cu toate acestea, dacă aveți nevoie de interoperabilitate între containerul Nette DI și bibliotecile sau cadrele care așteaptă interfața PSR-11 Container, puteți crea un [adaptor simplu |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f] care să servească drept punte între containerul Nette DI și PSR-11. +Nette DI Container nu suportă PSR-11 direct. Cu toate acestea, dacă aveți nevoie de interoperabilitate între Nette DI Container și biblioteci sau framework-uri care așteaptă PSR-11 Container Interface, puteți crea un [adaptor simplu |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f], care va servi ca o punte între Nette DI Container și PSR-11. diff --git a/dependency-injection/ro/global-state.texy b/dependency-injection/ro/global-state.texy index 7a4d561745..7008c14d33 100644 --- a/dependency-injection/ro/global-state.texy +++ b/dependency-injection/ro/global-state.texy @@ -1,44 +1,43 @@ -Starea globală și singletoni -**************************** +Stare globală și singleton-uri +****************************** .[perex] -Avertisment: Următoarele construcții sunt simptome ale unui cod prost conceput: +Avertisment: Următoarele construcții sunt un semn al unui cod prost proiectat: - `Foo::getInstance()` - `DB::insert(...)` - `Article::setDb($db)` - `ClassName::$var` sau `static::$var` -Întâlnești vreunul dintre aceste construcții în codul tău? În caz afirmativ, aveți posibilitatea de a-l îmbunătăți. S-ar putea să vă gândiți că acestea sunt construcții obișnuite, văzute adesea în soluții de exemplu ale diferitelor biblioteci și cadre de lucru. Dacă este așa, designul codului lor este defectuos. +Apar unele dintre aceste construcții în codul dvs.? Atunci aveți ocazia să îl îmbunătățiți. Poate vă gândiți că sunt construcții obișnuite, pe care le vedeți poate chiar și în soluții demonstrative ale diverselor biblioteci și framework-uri. Dacă este așa, atunci designul codului lor nu este bun. -Nu vorbim aici despre o puritate academică. Toate aceste construcții au un lucru în comun: utilizează starea globală. Iar acest lucru are un impact distructiv asupra calității codului. Clasele sunt înșelătoare în ceea ce privește dependențele lor. Codul devine imprevizibil. Îi încurcă pe dezvoltatori și le reduce eficiența. +Acum nu vorbim deloc despre vreo puritate academică. Toate aceste construcții au un lucru în comun: utilizează starea globală. Și aceasta are un impact distructiv asupra calității codului. Clasele mint despre dependențele lor. Codul devine imprevizibil. Încurcă programatorii și le reduce eficiența. -În acest capitol, vom explica de ce se întâmplă acest lucru și cum să evităm starea globală. +În acest capitol vom explica de ce este așa și cum să evitați starea globală. -Interconectarea globală .[#toc-global-interlinking] ---------------------------------------------------- +Cuplare globală +--------------- -Într-o lume ideală, un obiect ar trebui să comunice numai cu obiectele care [i-au fost transmise direct |passing-dependencies]. Dacă creez două obiecte `A` și `B` și nu transmit niciodată o referință între ele, atunci nici `A` și nici `B` nu pot accesa sau modifica starea celuilalt. Aceasta este o proprietate foarte dorită a codului. Este ca și cum ai avea o baterie și un bec; becul nu se va aprinde până când nu îl conectezi la baterie cu un fir. +Într-o lume ideală, un obiect ar trebui să poată comunica doar cu obiectele care i-au fost [transmise direct |passing-dependencies]. Dacă creez două obiecte `A` și `B` și nu transmit niciodată o referință între ele, atunci nici `A`, nici `B`, nu pot ajunge la celălalt obiect sau să îi modifice starea. Aceasta este o proprietate foarte dorită a codului. Este similar cu situația în care aveți o baterie și un bec; becul nu va lumina până nu îl conectați la baterie cu un fir. -Cu toate acestea, acest lucru nu este valabil pentru variabilele globale (statice) sau singletone. Obiectul `A` poate accesa *fără fir* obiectul `C` și îl poate modifica fără a trece referințe, prin apelarea `C::changeSomething()`. În cazul în care obiectul `B` accesează și obiectul global `C`, atunci `A` și `B` se pot influența reciproc prin intermediul `C`. +Dar acest lucru nu este valabil pentru variabilele globale (statice) sau singleton-uri. Obiectul `A` ar putea ajunge *fără fir* la obiectul `C` și să îl modifice fără nicio transmitere de referință, prin apelarea `C::changeSomething()`. Dacă obiectul `B` se agață și el de `C` global, atunci `A` și `B` se pot influența reciproc prin intermediul `C`. -Utilizarea variabilelor globale introduce o nouă formă de cuplare *fără fir* care nu este vizibilă din exterior. Aceasta creează o perdea de fum care complică înțelegerea și utilizarea codului. Pentru a înțelege cu adevărat dependențele, dezvoltatorii trebuie să citească fiecare linie a codului sursă, în loc să se familiarizeze doar cu interfețele claselor. În plus, această încurcătură este complet inutilă. Starea globală este utilizată deoarece este ușor de accesat de oriunde și permite, de exemplu, scrierea într-o bază de date prin intermediul unei metode globale (statice) `DB::insert()`. Cu toate acestea, după cum vom vedea, beneficiul pe care îl oferă este minim, în timp ce complicațiile pe care le introduce sunt grave. +Utilizarea variabilelor globale introduce în sistem o nouă formă de cuplare *fără fir*, care nu este vizibilă din exterior. Creează o perdea de fum care complică înțelegerea și utilizarea codului. Pentru ca dezvoltatorii să înțeleagă cu adevărat dependențele, trebuie să citească fiecare linie de cod sursă. În loc să se familiarizeze pur și simplu cu interfața claselor. Mai mult, este o cuplare complet inutilă. Starea globală se folosește deoarece este ușor accesibilă de oriunde și permite, de exemplu, scrierea în baza de date prin metoda globală (statică) `DB::insert()`. Dar, așa cum vom arăta, avantajul pe care îl aduce este nesemnificativ, în timp ce complicațiile pe care le provoacă sunt fatale. .[note] -În ceea ce privește comportamentul, nu există nicio diferență între o variabilă globală și una statică. Ele sunt la fel de dăunătoare. +Din punct de vedere comportamental, nu există nicio diferență între o variabilă globală și una statică. Sunt la fel de dăunătoare. -Acțiunea înfricoșătoare la distanță .[#toc-the-spooky-action-at-a-distance] ---------------------------------------------------------------------------- +Acțiune înfricoșătoare la distanță +---------------------------------- -"Acțiunea ciudată la distanță" - așa a numit Albert Einstein un fenomen din fizica cuantică care i-a dat fiori în 1935. -Este vorba despre entanglarea cuantică, a cărei particularitate este că atunci când măsori informații despre o particulă, afectezi imediat o altă particulă, chiar dacă acestea se află la milioane de ani lumină distanță. -Ceea ce aparent încalcă legea fundamentală a universului conform căreia nimic nu poate călători mai repede decât lumina. +"Acțiune înfricoșătoare la distanță" - așa a numit celebrul Albert Einstein în 1935 un fenomen din fizica cuantică care îi dădea fiori. +Este vorba despre inseparabilitatea cuantică, a cărei particularitate este că atunci când măsori informația despre o particulă, influențezi instantaneu cealaltă particulă, chiar dacă sunt la milioane de ani-lumină distanță. Ceea ce pare să încalce legea fundamentală a universului, că nimic nu se poate propaga mai repede decât lumina. -În lumea software-ului, putem numi "acțiune fantomatică la distanță" o situație în care rulăm un proces pe care îl considerăm izolat (deoarece nu i-am transmis nicio referință), dar interacțiuni neașteptate și schimbări de stare au loc în locații îndepărtate ale sistemului, despre care nu am informat obiectul. Acest lucru se poate întâmpla numai prin intermediul stării globale. +În lumea software, putem numi "acțiune înfricoșătoare la distanță" situația în care pornim un proces despre care credem că este izolat (deoarece nu i-am transmis nicio referință), dar în locuri îndepărtate ale sistemului apar interacțiuni neașteptate și modificări de stare despre care nu aveam nicio idee. Acest lucru se poate întâmpla doar prin intermediul stării globale. -Imaginați-vă că vă alăturați unei echipe de dezvoltare a unui proiect care are o bază de cod mare și matură. Noul dvs. șef vă cere să implementați o nouă caracteristică și, ca un bun dezvoltator, începeți prin a scrie un test. Dar, pentru că sunteți nou în proiect, faceți o mulțime de teste exploratorii de tipul "ce se întâmplă dacă apelez această metodă". Și încercați să scrieți următorul test: +Imaginați-vă că vă alăturați unei echipe de dezvoltatori ai unui proiect care are o bază de cod extinsă și matură. Noul dvs. șef vă cere să implementați o nouă funcționalitate și, ca un dezvoltator bun, începeți prin scrierea unui test. Dar, fiind nou în proiect, faceți multe teste exploratorii de tipul "ce se întâmplă dacă apelez această metodă". Și încercați să scrieți următorul test: ```php function testCreditCardCharge() @@ -48,20 +47,17 @@ function testCreditCardCharge() } ``` -Rulați codul, poate de mai multe ori, și după un timp observați notificări pe telefon de la bancă care vă anunță că, de fiecare dată când îl executați, 100 de dolari au fost debitați de pe cardul dvs. de credit 🤦‍♂️. +Rulați codul, poate de mai multe ori, și după un timp observați pe mobil notificări de la bancă că la fiecare rulare s-au retras 100 de dolari de pe cardul dvs. de plată 🤦‍♂️ -Cum naiba a putut testul să provoace o încărcare reală? Nu este ușor de operat cu cardul de credit. Trebuie să interacționezi cu un serviciu web terț, trebuie să cunoști URL-ul acelui serviciu web, trebuie să te loghezi și așa mai departe. -Niciuna dintre aceste informații nu este inclusă în test. Chiar mai rău, nici măcar nu știți unde sunt prezente aceste informații și, prin urmare, nu știți cum să vă bateți joc de dependențele externe, astfel încât fiecare execuție să nu ducă la o nouă taxare de 100 de dolari. Și, în calitate de dezvoltator nou, de unde să știi că ceea ce urma să faci te va duce la o sărăcie de 100 de dolari? +Cum naiba a putut testul să provoace retragerea reală de bani? Operarea cu un card de plată nu este ușoară. Trebuie să comunicați cu un serviciu web terț, trebuie să cunoașteți URL-ul acestui serviciu web, trebuie să vă autentificați și așa mai departe. Nicio informație de acest gen nu este conținută în test. Mai rău, nici măcar nu știți unde sunt prezente aceste informații și, prin urmare, nici cum să mock-uiți dependențele externe, astfel încât fiecare rulare să nu ducă la retragerea din nou a 100 de dolari. Și cum trebuia să știți, ca dezvoltator nou, că ceea ce urmați să faceți va duce la sărăcirea cu 100 de dolari? -Aceasta este o acțiune înfricoșătoare la distanță! +Aceasta este acțiunea înfricoșătoare la distanță! -Nu ai altă soluție decât să scotocești prin mult cod sursă, întrebând colegi mai vechi și mai experimentați, până când înțelegi cum funcționează conexiunile din proiect. -Acest lucru se datorează faptului că, atunci când vă uitați la interfața clasei `CreditCard`, nu puteți determina starea globală care trebuie inițializată. Nici măcar dacă vă uitați la codul sursă al clasei nu vă va spune ce metodă de inițializare trebuie să apelați. În cel mai bun caz, puteți găsi variabila globală care este accesată și încercați să ghiciți cum să o inițializați pornind de la aceasta. +Nu vă rămâne decât să scormoniți îndelung în multe coduri sursă, să întrebați colegii mai vechi și mai experimentați, până când înțelegeți cum funcționează legăturile în proiect. Acest lucru este cauzat de faptul că, privind interfața clasei `CreditCard`, nu se poate identifica starea globală care trebuie inițializată. Nici măcar privirea în codul sursă al clasei nu vă dezvăluie ce metodă de inițializare trebuie să apelați. În cel mai bun caz, puteți găsi o variabilă globală la care se accesează și din ea să încercați să ghiciți cum să o inițializați. -Clasele dintr-un astfel de proiect sunt niște mincinoși patologici. Cardul de plată pretinde că puteți pur și simplu să îl instanți și să apelați metoda `charge()`. Cu toate acestea, ea interacționează în secret cu o altă clasă, `PaymentGateway`. Chiar și interfața sa spune că poate fi inițializată în mod independent, dar în realitate trage acreditările dintr-un fișier de configurare și așa mai departe. -Este clar pentru dezvoltatorii care au scris acest cod că `CreditCard` are nevoie de `PaymentGateway`. Aceștia au scris codul în acest fel. Dar pentru oricine este nou în proiect, acest lucru este un mister complet și împiedică învățarea. +Clasele dintr-un astfel de proiect sunt mincinoși patologici. Cardul de plată pretinde că este suficient să îl instanțiați și să apelați metoda `charge()`. În secret, însă, colaborează cu o altă clasă `PaymentGateway`, care reprezintă poarta de plată. Și interfața sa spune că poate fi inițializată separat, dar în realitate își extrage credențialele dintr-un fișier de configurare și așa mai departe. Dezvoltatorilor care au scris acest cod le este clar că `CreditCard` are nevoie de `PaymentGateway`. Au scris codul în acest fel. Dar pentru oricine este nou în proiect, este un mister total și împiedică învățarea. -Cum se poate remedia situația? Ușor. **Lasă API-ul să declare dependențele.** +Cum să reparați situația? Ușor. **Lăsați API-ul să declare dependențele.** ```php function testCreditCardCharge() @@ -72,36 +68,35 @@ function testCreditCardCharge() } ``` -Observați cum relațiile din cadrul codului sunt brusc evidente. Declarând că metoda `charge()` are nevoie de `PaymentGateway`, nu mai trebuie să întrebați pe nimeni cum este interdependent codul. Știți că trebuie să creați o instanță a acesteia, iar când încercați să faceți acest lucru, vă loviți de faptul că trebuie să furnizați parametri de acces. Fără aceștia, codul nici măcar nu ar funcționa. +Observați cum legăturile din interiorul codului devin brusc evidente. Prin faptul că metoda `charge()` declară că are nevoie de `PaymentGateway`, nu trebuie să întrebați pe nimeni cum este legat codul. Știți că trebuie să creați instanța sa și, când încercați să faceți acest lucru, veți descoperi că trebuie să furnizați parametrii de acces. Fără ei, codul nici măcar nu ar rula. -Și, cel mai important, acum puteți să vă bateți joc de gateway-ul de plată, astfel încât să nu fiți taxat cu 100 de dolari de fiecare dată când executați un test. +Și, cel mai important, acum puteți mock-ui poarta de plată, astfel încât să nu vi se taxeze 100 de dolari la fiecare rulare a testului. -Starea globală face ca obiectele dvs. să poată accesa în secret lucruri care nu sunt declarate în API-urile lor și, ca urmare, face ca API-urile dvs. să fie mincinoase patologice. +Starea globală face ca obiectele dvs. să poată accesa în secret lucruri care nu sunt declarate în API-ul lor și, în consecință, transformă API-urile dvs. în mincinoși patologici. -Poate că nu v-ați gândit la asta până acum, dar ori de câte ori folosiți starea globală, creați canale secrete de comunicare fără fir. Acțiunile înfiorătoare de la distanță îi obligă pe dezvoltatori să citească fiecare linie de cod pentru a înțelege interacțiunile potențiale, reduc productivitatea dezvoltatorilor și îi derutează pe noii membri ai echipei. -Dacă tu ești cel care a creat codul, cunoști dependențele reale, dar oricine vine după tine nu știe nimic. +Poate că nu v-ați gândit la asta înainte în acest fel, dar ori de câte ori utilizați starea globală, creați canale de comunicare secrete fără fir. Acțiunea înfricoșătoare la distanță îi obligă pe dezvoltatori să citească fiecare linie de cod pentru a înțelege interacțiunile potențiale, reduce productivitatea dezvoltatorilor și îi încurcă pe noii membri ai echipei. Dacă sunteți cel care a creat codul, cunoașteți dependențele reale, dar oricine vine după dvs. este neajutorat. -Nu scrieți cod care utilizează starea globală, preferați să treceți dependențele. Adică injectarea dependențelor. +Nu scrieți cod care utilizează starea globală, preferați transmiterea dependențelor. Adică injecția de dependență. -Bătălia statului global .[#toc-brittleness-of-the-global-state] ---------------------------------------------------------------- +Fragilitatea stării globale +--------------------------- -În codul care utilizează starea globală și singletonii, nu este niciodată sigur când și de către cine a fost schimbată acea stare. Acest risc este deja prezent la inițializare. Următorul cod ar trebui să creeze o conexiune la baza de date și să inițializeze gateway-ul de plată, dar continuă să arunce o excepție, iar găsirea cauzei este extrem de anevoioasă: +În codul care utilizează starea globală și singleton-uri, nu este niciodată sigur când și cine a modificat această stare. Acest risc apare deja la inițializare. Următorul cod ar trebui să creeze o conexiune la baza de date și să inițializeze poarta de plată, însă aruncă constant o excepție și găsirea cauzei este extrem de anevoioasă: ```php PaymentGateway::init(); DB::init('mysql:', 'user', 'password'); ``` -Trebuie să parcurgeți codul în detaliu pentru a descoperi că obiectul `PaymentGateway` accesează alte obiecte fără fir, dintre care unele necesită o conexiune la baza de date. Astfel, trebuie să inițializați baza de date înainte de `PaymentGateway`. Cu toate acestea, perdeaua de fum a statului global vă ascunde acest lucru. Cât timp ați economisi dacă API-ul fiecărei clase nu ar minți și nu și-ar declara dependențele? +Trebuie să parcurgeți codul în detaliu pentru a descoperi că obiectul `PaymentGateway` accesează fără fir alte obiecte, dintre care unele necesită o conexiune la baza de date. Prin urmare, este necesar să inițializați baza de date înainte de `PaymentGateway`. Cu toate acestea, perdeaua de fum a stării globale ascunde acest lucru de dvs. Cât timp ați economisi dacă API-urile claselor individuale nu ar minți și și-ar declara dependențele? ```php $db = new DB('mysql:', 'user', 'password'); $gateway = new PaymentGateway($db, ...); ``` -O problemă similară apare atunci când se utilizează accesul global la o conexiune la o bază de date: +O problemă similară apare și la utilizarea accesului global la conexiunea bazei de date: ```php use Illuminate\Support\Facades\DB; @@ -115,9 +110,9 @@ class Article } ``` -Atunci când se apelează metoda `save()`, nu se știe cu siguranță dacă a fost deja creată o conexiune la baza de date și cine este responsabil pentru crearea acesteia. De exemplu, dacă am dori să modificăm din mers conexiunea la baza de date, poate în scopuri de testare, probabil că ar trebui să creăm metode suplimentare, cum ar fi `DB::reconnect(...)` sau `DB::reconnectForTest()`. +La apelarea metodei `save()`, nu este sigur dacă a fost deja creată conexiunea la baza de date și cine poartă responsabilitatea pentru crearea sa. Dacă dorim, de exemplu, să schimbăm conexiunea la baza de date în timpul rulării, de exemplu pentru teste, ar trebui probabil să creăm alte metode precum `DB::reconnect(...)` sau `DB::reconnectForTest()`. -Luați în considerare un exemplu: +Să luăm în considerare un exemplu: ```php $article = new Article; @@ -127,9 +122,9 @@ Foo::doSomething(); $article->save(); ``` -De unde putem fi siguri că baza de date de testare este într-adevăr utilizată atunci când apelăm `$article->save()`? Ce se întâmplă dacă metoda `Foo::doSomething()` a schimbat conexiunea globală la baza de date? Pentru a afla, ar trebui să examinăm codul sursă al clasei `Foo` și, probabil, al multor alte clase. Cu toate acestea, această abordare ar oferi doar un răspuns pe termen scurt, deoarece situația se poate schimba în viitor. +Unde avem certitudinea că la apelarea `$article->save()` se utilizează într-adevăr baza de date de test? Ce se întâmplă dacă metoda `Foo::doSomething()` a schimbat conexiunea globală la baza de date? Pentru a afla, ar trebui să examinăm codul sursă al clasei `Foo` și probabil și al multor altor clase. Această abordare ar aduce însă doar un răspuns pe termen scurt, deoarece situația se poate schimba în viitor. -Ce se întâmplă dacă mutăm conexiunea la baza de date într-o variabilă statică în interiorul clasei `Article`? +Și ce se întâmplă dacă mutăm conexiunea la baza de date într-o variabilă statică în interiorul clasei `Article`? ```php class Article @@ -148,11 +143,11 @@ class Article } ``` -Acest lucru nu schimbă absolut nimic. Problema este o stare globală și nu contează în ce clasă se ascunde. În acest caz, ca și în cel precedent, nu avem niciun indiciu cu privire la baza de date în care se scrie atunci când este apelată metoda `$article->save()`. Oricine aflat la capătul îndepărtat al aplicației ar putea schimba baza de date în orice moment folosind `Article::setDb()`. În mâinile noastre. +Acest lucru nu a schimbat absolut nimic. Problema este starea globală și este complet irelevant în ce clasă se ascunde. În acest caz, la fel ca în cel precedent, nu avem niciun indiciu la apelarea metodei `$article->save()` despre în ce bază de date se va scrie. Oricine de la celălalt capăt al aplicației ar fi putut schimba oricând baza de date folosind `Article::setDb()`. Sub nasul nostru. -Starea globală face ca aplicația noastră să fie **extrem de fragilă**. +Starea globală face aplicația noastră **extrem de fragilă**. -Cu toate acestea, există o modalitate simplă de a rezolva această problemă. Este suficient ca API-ul să declare dependențele pentru a asigura o funcționalitate corespunzătoare. +Există însă o modalitate simplă de a aborda această problemă. Este suficient să lăsăm API-ul să declare dependențele, asigurându-se astfel funcționalitatea corectă. ```php class Article @@ -174,15 +169,15 @@ Foo::doSomething(); $article->save(); ``` -Această abordare elimină grija modificărilor ascunse și neașteptate ale conexiunilor la baza de date. Acum suntem siguri unde este stocat articolul și nicio modificare de cod în interiorul unei alte clase fără legătură nu mai poate schimba situația. Codul nu mai este fragil, ci stabil. +Datorită acestei abordări, dispare teama de modificări ascunse și neașteptate ale conexiunii la baza de date. Acum avem certitudinea unde se salvează articolul și nicio modificare a codului în interiorul altei clase nelegate nu mai poate schimba situația. Codul nu mai este fragil, ci stabil. -Nu scrieți cod care utilizează starea globală, preferați să treceți dependențele. Astfel, injecția de dependențe. +Nu scrieți cod care utilizează starea globală, preferați transmiterea dependențelor. Adică injecția de dependență. -Singleton .[#toc-singleton] ---------------------------- +Singleton +--------- -Singleton este un model de proiectare care, prin [definiția |https://en.wikipedia.org/wiki/Singleton_pattern] din celebra publicație Gang of Four, limitează o clasă la o singură instanță și oferă acces global la aceasta. Implementarea acestui model seamănă, de obicei, cu următorul cod: +Singleton este un pattern de design care, conform "definiției":https://en.wikipedia.org/wiki/Singleton_pattern din celebra publicație Gang of Four, limitează clasa la o singură instanță și oferă acces global la aceasta. Implementarea acestui pattern seamănă de obicei cu următorul cod: ```php class Singleton @@ -195,38 +190,35 @@ class Singleton return self::$instance; } - // și alte metode care îndeplinesc funcțiile clasei + // și alte metode care îndeplinesc funcțiile clasei respective } ``` -Din păcate, singletonul introduce o stare globală în aplicație. Și, după cum am arătat mai sus, starea globală nu este de dorit. De aceea, singletonul este considerat un antipattern. +Din păcate, singleton introduce starea globală în aplicație. Și, așa cum am arătat mai sus, starea globală este nedorită. Prin urmare, singleton este considerat un antipattern. -Nu folosiți singletonii în codul dvs. și înlocuiți-i cu alte mecanisme. Chiar nu aveți nevoie de singletons. Cu toate acestea, dacă trebuie să garantați existența unei singure instanțe a unei clase pentru întreaga aplicație, lăsați acest lucru în seama [containerului DI |container]. -Astfel, creați un singleton de aplicație, sau serviciu. Acest lucru va împiedica clasa să își asigure propria unicitate (adică nu va avea o metodă `getInstance()` și o variabilă statică) și își va îndeplini doar funcțiile. Astfel, nu va mai încălca principiul responsabilității unice. +Nu utilizați singleton-uri în codul dvs. și înlocuiți-le cu alte mecanisme. Chiar nu aveți nevoie de singleton-uri. Cu toate acestea, dacă trebuie să garantați existența unei singure instanțe a clasei pentru întreaga aplicație, lăsați acest lucru pe seama [containerului DI |container]. Creați astfel un singleton de aplicație, adică un serviciu. Astfel, clasa încetează să se mai ocupe de asigurarea propriei unicități (adică nu va avea metoda `getInstance()` și variabila statică) și va îndeplini doar funcțiile sale. Astfel, nu va mai încălca principiul responsabilității unice. -Starea globală față de teste .[#toc-global-state-versus-tests] --------------------------------------------------------------- +Stare globală versus teste +-------------------------- -Atunci când scriem teste, presupunem că fiecare test este o unitate izolată și că nicio stare externă nu intră în el. Și nicio stare nu părăsește testele. Atunci când un test se finalizează, orice stare asociată cu testul ar trebui să fie eliminată automat de către garbage collector. Acest lucru face ca testele să fie izolate. Prin urmare, putem rula testele în orice ordine. +La scrierea testelor, presupunem că fiecare test este o unitate izolată și că nicio stare externă nu intră în el. Și nicio stare nu părăsește testele. După finalizarea testului, toată starea asociată cu testul ar trebui eliminată automat de garbage collector. Datorită acestui fapt, testele sunt izolate. Prin urmare, putem rula testele în orice ordine. -Cu toate acestea, dacă sunt prezente stări globale/singletele globale, toate aceste presupuneri frumoase se prăbușesc. O stare poate intra și ieși dintr-un test. Dintr-o dată, ordinea testelor poate conta. +Cu toate acestea, dacă sunt prezente stări globale/singleton-uri, toate aceste presupuneri plăcute se destramă. Starea poate intra și ieși din test. Brusc, ordinea testelor poate conta. -Pentru a testa singletonii, dezvoltatorii trebuie adesea să relaxeze proprietățile acestora, poate permițând ca o instanță să fie înlocuită cu alta. Astfel de soluții sunt, în cel mai bun caz, hack-uri care produc un cod dificil de întreținut și de înțeles. Orice test sau metodă `tearDown()` care afectează orice stare globală trebuie să anuleze aceste modificări. +Pentru a putea testa singleton-urile, dezvoltatorii trebuie adesea să le relaxeze proprietățile, de exemplu, permițând înlocuirea instanței cu alta. Astfel de soluții sunt, în cel mai bun caz, hack-uri care creează cod dificil de întreținut și de înțeles. Fiecare test sau metodă `tearDown()` care afectează orice stare globală trebuie să anuleze aceste modificări. -Starea globală este cea mai mare bătaie de cap în testarea unitară! +Starea globală este cea mai mare durere de cap la testarea unitară! -Cum se poate remedia situația? Ușor. Nu scrieți cod care folosește singletoni, preferați să treceți dependențele. Adică injecția de dependență. +Cum să reparați situația? Ușor. Nu scrieți cod care utilizează singleton-uri, preferați transmiterea dependențelor. Adică injecția de dependență. -Constante globale .[#toc-global-constants] ------------------------------------------- +Constante globale +----------------- -Starea globală nu se limitează la utilizarea singletonilor și a variabilelor statice, ci se poate aplica și constantelor globale. +Starea globală nu se limitează doar la utilizarea singleton-urilor și a variabilelor statice, ci se poate referi și la constantele globale. -Constantele a căror valoare nu ne furnizează informații noi (`M_PI`) sau utile (`PREG_BACKTRACK_LIMIT_ERROR`) sunt în mod clar în regulă. -Dimpotrivă, constantele care servesc drept modalitate de a transmite *fără fir* informații în interiorul codului nu sunt altceva decât o dependență ascunsă. Cum ar fi `LOG_FILE` din exemplul următor. -Utilizarea constantei `FILE_APPEND` este perfect corectă. +Constantele a căror valoare nu ne aduce nicio informație nouă (`M_PI`) sau utilă (`PREG_BACKTRACK_LIMIT_ERROR`) sunt în mod clar în regulă. Dimpotrivă, constantele care servesc ca o modalitate de a transmite *fără fir* informații în interiorul codului nu sunt altceva decât o dependență ascunsă. Cum ar fi `LOG_FILE` în exemplul următor. Utilizarea constantei `FILE_APPEND` este complet corectă. ```php const LOG_FILE = '...'; @@ -242,7 +234,7 @@ class Foo } ``` -În acest caz, ar trebui să declarăm parametrul în constructorul clasei `Foo` pentru a-l face parte din API: +În acest caz, ar trebui să declarăm un parametru în constructorul clasei `Foo`, pentru ca acesta să devină parte a API-ului: ```php class Foo @@ -261,44 +253,42 @@ class Foo } ``` -Acum putem transmite informații despre calea către fișierul de logare și o putem modifica cu ușurință, după cum este necesar, facilitând testarea și întreținerea codului. +Acum putem transmite informația despre calea către fișierul de logare și o putem schimba ușor după nevoie, ceea ce facilitează testarea și întreținerea codului. -Funcții globale și metode statice .[#toc-global-functions-and-static-methods] ------------------------------------------------------------------------------ +Funcții globale și metode statice +--------------------------------- -Dorim să subliniem faptul că utilizarea metodelor statice și a funcțiilor globale nu este în sine problematică. Am explicat caracterul nepotrivit al utilizării `DB::insert()` și a metodelor similare, dar întotdeauna a fost vorba de starea globală stocată într-o variabilă statică. Metoda `DB::insert()` necesită existența unei variabile statice, deoarece stochează conexiunea la baza de date. Fără această variabilă, ar fi imposibil de implementat metoda. +Dorim să subliniem că utilizarea în sine a metodelor statice și a funcțiilor globale nu este problematică. Am explicat în ce constă inadecvarea utilizării `DB::insert()` și a metodelor similare, dar întotdeauna a fost vorba doar de o chestiune de stare globală, care este stocată într-o variabilă statică. Metoda `DB::insert()` necesită existența unei variabile statice, deoarece în ea este stocată conexiunea la baza de date. Fără această variabilă, ar fi imposibil să se implementeze metoda. -Utilizarea metodelor și funcțiilor statice deterministe, cum ar fi `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` și multe altele, este perfect coerentă cu injecția de dependență. Aceste funcții returnează întotdeauna aceleași rezultate la aceiași parametri de intrare și, prin urmare, sunt previzibile. Ele nu utilizează nicio stare globală. +Utilizarea metodelor statice și a funcțiilor deterministe, precum `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` și multe altele, este în perfectă concordanță cu injecția de dependență. Aceste funcții returnează întotdeauna aceleași rezultate pentru aceiași parametri de intrare și sunt deci previzibile. Nu utilizează nicio stare globală. -Cu toate acestea, există funcții în PHP care nu sunt deterministe. Printre acestea se numără, de exemplu, funcția `htmlspecialchars()`. Cel de-al treilea parametru al acesteia, `$encoding`, dacă nu este specificat, este valoarea implicită a opțiunii de configurare `ini_get('default_charset')`. Prin urmare, se recomandă să specificați întotdeauna acest parametru pentru a evita un eventual comportament imprevizibil al funcției. Nette face acest lucru în mod constant. +Există însă și funcții în PHP care nu sunt deterministe. Printre acestea se numără, de exemplu, funcția `htmlspecialchars()`. Al treilea său parametru `$encoding`, dacă nu este specificat, are ca valoare implicită valoarea opțiunii de configurare `ini_get('default_charset')`. De aceea se recomandă specificarea întotdeauna a acestui parametru și prevenirea astfel a unui eventual comportament imprevizibil al funcției. Nette face acest lucru în mod consecvent. -Unele funcții, cum ar fi `strtolower()`, `strtoupper()`, și altele similare, au avut un comportament nedeterminist în trecutul recent și au depins de setarea `setlocale()`. Acest lucru a cauzat multe complicații, cel mai adesea atunci când se lucra cu limba turcă. -Acest lucru se datorează faptului că limba turcă face distincție între majuscule și minuscule `I` cu și fără punct. Astfel, `strtolower('I')` returna caracterul `ı`, iar `strtoupper('i')` returna caracterul `İ`, ceea ce a dus la aplicații care provocau o serie de erori misterioase. -Cu toate acestea, această problemă a fost rezolvată în versiunea 8.2 a PHP, iar funcțiile nu mai depind de locale. +Unele funcții, precum `strtolower()`, `strtoupper()` și altele similare, s-au comportat nedeterminist în trecutul recent și au fost dependente de setarea `setlocale()`. Acest lucru a cauzat multe complicații, cel mai adesea la lucrul cu limba turcă. Aceasta distinge literele mici și mari `I` cu și fără punct. Astfel, `strtolower('I')` returna caracterul `ı` și `strtoupper('i')` caracterul `İ`, ceea ce a dus la faptul că aplicațiile au început să provoace o serie de erori misterioase. Această problemă a fost însă eliminată în PHP versiunea 8.2 și funcțiile nu mai sunt dependente de locale. -Acesta este un exemplu frumos al modului în care statul global a afectat mii de dezvoltatori din întreaga lume. Soluția a fost înlocuirea acesteia cu injecția de dependență. +Este un exemplu frumos despre cum starea globală a chinuit mii de dezvoltatori din întreaga lume. Soluția a fost înlocuirea sa cu injecția de dependență. -Când este posibil să se utilizeze statul global? .[#toc-when-is-it-possible-to-use-global-state] ------------------------------------------------------------------------------------------------- +Când este posibil să se utilizeze starea globală? +------------------------------------------------- -Există anumite situații specifice în care este posibil să se utilizeze starea globală. De exemplu, atunci când depanați codul și trebuie să descărcați valoarea unei variabile sau să măsurați durata unei anumite părți a programului. În astfel de cazuri, care se referă la acțiuni temporare care vor fi ulterior eliminate din cod, este legitim să se utilizeze un dumper sau un cronometru disponibil la nivel global. Aceste instrumente nu fac parte din proiectarea codului. +Există anumite situații specifice în care este posibil să se utilizeze starea globală. De exemplu, la depanarea codului, când trebuie să afișați valoarea unei variabile sau să măsurați durata unei anumite părți a programului. În astfel de cazuri, care se referă la acțiuni temporare ce vor fi ulterior eliminate din cod, este posibil să se utilizeze legitim un dumper sau un cronometru disponibil global. Aceste instrumente nu fac parte din designul codului. -Un alt exemplu este reprezentat de funcțiile de lucru cu expresii regulate `preg_*`, care stochează intern expresiile regulate compilate într-o memorie cache statică în memorie. Atunci când apelați aceeași expresie regulată de mai multe ori în diferite părți ale codului, aceasta este compilată o singură dată. Memoria cache economisește performanță și este, de asemenea, complet invizibilă pentru utilizator, astfel încât o astfel de utilizare poate fi considerată legitimă. +Un alt exemplu sunt funcțiile pentru lucrul cu expresii regulate `preg_*`, care stochează intern expresiile regulate compilate într-un cache static în memorie. Astfel, când apelați aceeași expresie regulată de mai multe ori în diferite locuri ale codului, aceasta se compilează o singură dată. Cache-ul economisește performanța și, în același timp, este complet invizibil pentru utilizator, prin urmare o astfel de utilizare poate fi considerată legitimă. -Rezumat .[#toc-summary] ------------------------ +Rezumat +------- -Am arătat de ce are sens +Am discutat de ce are sens: -1) Să eliminăm toate variabilele statice din cod -2) Declarați dependențele -3) Și folosiți injectarea dependențelor +1) Să eliminați toate variabilele statice din cod +2) Să declarați dependențele +3) Și să utilizați injecția de dependență -Atunci când vă gândiți la proiectarea codului, nu uitați că fiecare `static $foo` reprezintă o problemă. Pentru ca codul dumneavoastră să fie un mediu care respectă DI, este esențial să eradicați complet starea globală și să o înlocuiți cu injecția de dependență. +Când vă gândiți la designul codului, gândiți-vă că fiecare `static $foo` reprezintă o problemă. Pentru ca codul dvs. să fie un mediu care respectă DI, este necesar să eliminați complet starea globală și să o înlocuiți folosind injecția de dependență. -În timpul acestui proces, s-ar putea să descoperiți că trebuie să divizați o clasă deoarece aceasta are mai multe responsabilități. Nu vă faceți griji în această privință; străduiți-vă să respectați principiul unei singure responsabilități. +În timpul acestui proces, este posibil să descoperiți că este necesar să împărțiți clasa, deoarece are mai mult de o responsabilitate. Nu vă temeți de acest lucru; urmăriți principiul responsabilității unice. -*Doresc să îi mulțumesc lui Miško Hevery, ale cărui articole, precum [Flaw: Brittle Global State & Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/], constituie baza acestui capitol.* +*Aș dori să îi mulțumesc lui Miško Hevery, ale cărui articole, precum [Flaw: Brittle Global State & Singletons |https://web.archive.org/web/20230321084133/http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/], stau la baza acestui capitol.* diff --git a/dependency-injection/ro/introduction.texy b/dependency-injection/ro/introduction.texy index 6b5fb2ed25..1d0be17a88 100644 --- a/dependency-injection/ro/introduction.texy +++ b/dependency-injection/ro/introduction.texy @@ -1,58 +1,58 @@ -Ce este Injecția de dependență? -******************************* +Ce este Dependency Injection? +***************************** .[perex] -Acest capitol vă va prezenta practicile de programare de bază pe care trebuie să le urmați atunci când scrieți orice aplicație. Acestea sunt elementele fundamentale necesare pentru a scrie un cod curat, ușor de înțeles și de întreținut. +Acest capitol vă va introduce în practicile de programare de bază pe care ar trebui să le urmați atunci când scrieți toate aplicațiile. Acestea sunt elementele de bază necesare pentru a scrie cod curat, ușor de înțeles și de întreținut. -Dacă învățați și urmați aceste reguli, Nette vă va fi alături la fiecare pas. Se va ocupa de sarcinile de rutină în locul dumneavoastră și vă va oferi un confort maxim, astfel încât să vă puteți concentra asupra logicii propriu-zise. +Dacă adoptați aceste reguli și le urmați, Nette vă va sprijini la fiecare pas. Se va ocupa de sarcinile de rutină pentru dvs. și vă va oferi confort maxim, astfel încât să vă puteți concentra pe logica în sine. Principiile pe care le vom arăta aici sunt destul de simple. Nu trebuie să vă faceți griji pentru nimic. -Vă amintiți primul program? .[#toc-remember-your-first-program] ---------------------------------------------------------------- +Vă amintiți primul program? +--------------------------- -Nu știm în ce limbaj ați scris-o, dar dacă a fost PHP, ar fi putut arăta cam așa: +Nu știm în ce limbaj l-ați scris, dar dacă ar fi fost PHP, probabil ar fi arătat cam așa: ```php -function addition(float $a, float $b): float +function soucet(float $a, float $b): float { return $a + $b; } -echo addition(23, 1); // amprente 24 +echo soucet(23, 1); // afișează 24 ``` -Câteva linii de cod banale, dar atât de multe concepte cheie ascunse în ele. Că există variabile. Că codul este împărțit în unități mai mici, care sunt funcții, de exemplu. Că le trecem argumente de intrare și că ele returnează rezultate. Tot ceea ce lipsește sunt condițiile și buclele. +Câteva rânduri triviale de cod, dar conțin atât de multe concepte cheie. Că există variabile. Că codul este împărțit în unități mai mici, cum ar fi funcțiile. Că le transmitem argumente de intrare și ele returnează rezultate. Lipsesc doar condițiile și buclele. -Faptul că o funcție primește date de intrare și returnează un rezultat este un concept perfect inteligibil, care este utilizat și în alte domenii, cum ar fi matematica. +Faptul că transmitem date de intrare unei funcții și aceasta returnează un rezultat este un concept perfect de înțeles, care este utilizat și în alte domenii, cum ar fi matematica. -O funcție are o semnătură, care constă în numele său, o listă de parametri și tipurile acestora și, în final, tipul valorii de returnare. În calitate de utilizatori, suntem interesați de semnătură și, de obicei, nu trebuie să știm nimic despre implementarea internă. +O funcție are semnătura sa, care constă în numele său, o listă de parametri și tipurile acestora și, în final, tipul valorii returnate. Ca utilizatori, suntem interesați de semnătură, de obicei nu trebuie să știm nimic despre implementarea internă. -Acum imaginați-vă că semnătura funcției arată astfel: +Acum imaginați-vă că semnătura funcției ar arăta astfel: ```php -function addition(float $x): float +function soucet(float $x): float ``` -O adiție cu un singur parametru? Asta e ciudat... Ce zici de asta? +O sumă cu un singur parametru? Ciudat... Și ce ziceți de asta? ```php -function addition(): float +function soucet(): float ``` -Asta e foarte ciudat, nu? Cum este folosită funcția? +Asta e deja foarte ciudat, nu-i așa? Cum se folosește funcția? ```php -echo addition(); // ce imprimă? +echo soucet(); // ce va afișa oare? ``` -Privind un astfel de cod, am fi confuzi. Nu numai că un începător nu l-ar înțelege, dar nici măcar un programator experimentat nu ar înțelege un astfel de cod. +Privind un astfel de cod, am fi confuzi. Nu numai că un începător nu l-ar înțelege, dar nici un programator experimentat nu înțelege un astfel de cod. -Vă întrebați cum ar arăta de fapt o astfel de funcție în interior? De unde ar lua summenele? Probabil că le-ar obține *într-un fel* de la sine, poate în felul următor: +Vă întrebați cum ar arăta de fapt o astfel de funcție în interior? De unde ar lua termenii? Probabil că i-ar obține *într-un fel* singură, poate așa: ```php -function addition(): float +function soucet(): float { $a = Input::get('a'); $b = Input::get('b'); @@ -60,71 +60,71 @@ function addition(): float } ``` -Se pare că există legături ascunse cu alte funcții (sau metode statice) în corpul funcției, iar pentru a afla de unde provin de fapt adunările, trebuie să săpăm mai departe. +În corpul funcției am descoperit legături ascunse către alte funcții globale sau metode statice. Pentru a afla de unde provin de fapt termenii, trebuie să investigăm mai departe. -Nu pe aici! .[#toc-not-this-way] --------------------------------- +Nu pe aici! +----------- Designul pe care tocmai l-am arătat este esența multor caracteristici negative: -- semnătura funcției se pretindea că nu are nevoie de sumanți, ceea ce ne-a derutat. -- nu avem nicio idee cum să facem funcția să calculeze cu alte două numere -- a trebuit să ne uităm în cod pentru a afla de unde provin sumatorii -- am găsit dependențe ascunse -- pentru o înțelegere completă este necesar să examinăm și aceste dependențe +- semnătura funcției pretindea că nu are nevoie de termeni, ceea ce ne-a indus în eroare +- nu știm deloc cum să facem funcția să adune alte două numere +- a trebuit să ne uităm în cod pentru a afla de unde ia termenii +- am descoperit dependențe ascunse +- pentru o înțelegere completă, este necesar să examinăm și aceste dependențe -Și chiar este treaba funcției de adunare să procure intrări? Bineînțeles că nu este. Responsabilitatea sa este doar de a adăuga. +Și este oare sarcina funcției de adunare să obțină intrări? Desigur că nu. Responsabilitatea sa este doar adunarea în sine. -Nu dorim să întâlnim un astfel de cod și cu siguranță nu dorim să-l scriem. Remediul este simplu: reveniți la elementele de bază și folosiți doar parametri: +Nu vrem să întâlnim un astfel de cod și cu siguranță nu vrem să-l scriem. Remedierea este simplă: revenirea la elementele de bază și pur și simplu folosirea parametrilor: ```php -function addition(float $a, float $b): float +function soucet(float $a, float $b): float { return $a + $b; } ``` -Regula nr. 1: Lăsați să vi se transmită .[#toc-rule-1-let-it-be-passed-to-you] ------------------------------------------------------------------------------- +Regula nr. 1: Primește ce ai nevoie +----------------------------------- -Cea mai importantă regulă este: **toate datele de care au nevoie funcțiile sau clasele trebuie să le fie transmise**. +Cea mai importantă regulă este: **toate datele de care funcțiile sau clasele au nevoie trebuie să le fie transmise**. -În loc să inventați modalități ascunse de accesare a datelor de către ei înșiși, pur și simplu transmiteți parametrii. Veți economisi timp pe care l-ați petrece inventând căi ascunse care cu siguranță nu vă vor îmbunătăți codul. +În loc să inventați modalități ascunse prin care acestea ar putea ajunge cumva singure la ele, pur și simplu transmiteți parametrii. Veți economisi timp necesar pentru a inventa căi ascunse, care cu siguranță nu vă vor îmbunătăți codul. -Dacă urmați întotdeauna și peste tot această regulă, sunteți pe drumul către un cod fără dependențe ascunse. Spre un cod care este inteligibil nu numai pentru autor, ci și pentru oricine îl citește ulterior. În care totul este de înțeles din semnăturile funcțiilor și claselor și nu este nevoie să căutați secrete ascunse în implementare. +Dacă veți respecta această regulă întotdeauna și peste tot, sunteți pe drumul către un cod fără dependențe ascunse. Către un cod care este de înțeles nu numai pentru autor, ci și pentru oricine îl va citi după el. Unde totul este de înțeles din semnăturile funcțiilor și claselor și nu este nevoie să căutați secrete ascunse în implementare. -Această tehnică se numește în mod profesional **injecție de dependență**. Iar aceste date se numesc **dependențe**. Este doar o simplă trecere de parametri obișnuită, nimic mai mult. +Această tehnică se numește tehnic **dependency injection** (injectarea dependențelor). Iar acele date se numesc **dependențe.** De fapt, este vorba de transmiterea obișnuită a parametrilor, nimic mai mult. .[note] -Vă rugăm să nu confundați injectarea dependențelor, care este un model de proiectare, cu un "container de injectare a dependențelor", care este un instrument, ceva diametral diferit. Ne vom ocupa de containere mai târziu. +Vă rugăm să nu confundați dependency injection, care este un model de design (design pattern), cu „container DI”, care este un instrument, adică ceva diametral opus. Vom discuta despre containere mai târziu. -De la funcții la clase .[#toc-from-functions-to-classes] --------------------------------------------------------- +De la funcții la clase +---------------------- -Și cum sunt legate clasele? O clasă este o unitate mai complexă decât o simplă funcție, dar regula nr. 1 se aplică în întregime și aici. Există doar [mai multe moduri de a transmite argumente |passing-dependencies]. De exemplu, destul de asemănător cu cazul unei funcții: +Și cum se leagă clasele de asta? O clasă este o unitate mai complexă decât o funcție simplă, dar regula nr. 1 se aplică în totalitate și aici. Doar că există [mai multe opțiuni pentru a pasa argumente|passing-dependencies]. De exemplu, destul de similar cu cazul unei funcții: ```php -class Math +class Matematika { - public function addition(float $a, float $b): float + public function soucet(float $a, float $b): float { return $a + $b; } } -$math = new Math; -echo $math->addition(23, 1); // 24 +$math = new Matematika; +echo $math->soucet(23, 1); // 24 ``` -Sau prin alte metode, sau direct prin constructor: +Sau folosind alte metode, sau direct constructorul: ```php -class Addition +class Soucet { public function __construct( private float $a, @@ -132,26 +132,26 @@ class Addition ) { } - public function calculate(): float + public function spocti(): float { return $this->a + $this->b; } } -$addition = new Addition(23, 1); -echo $addition->calculate(); // 24 +$soucet = new Soucet(23, 1); +echo $soucet->spocti(); // 24 ``` -Ambele exemple sunt în deplină conformitate cu injecția de dependență. +Ambele exemple sunt pe deplin în concordanță cu dependency injection. -Exemple din viața reală .[#toc-real-life-examples] --------------------------------------------------- +Exemple reale +------------- -În lumea reală, nu veți scrie cursuri pentru adunarea numerelor. Să trecem la exemple practice. +În lumea reală, nu veți scrie clase pentru adunarea numerelor. Să trecem la exemple din practică. -Să avem o clasă `Article` care reprezintă o postare pe blog: +Să avem o clasă `Article` care reprezintă un articol de blog: ```php class Article @@ -162,12 +162,12 @@ class Article public function save(): void { - // salvați articolul în baza de date + // salvăm articolul în baza de date } } ``` -iar utilizarea va fi următoarea: +și utilizarea va fi următoarea: ```php $article = new Article; @@ -176,9 +176,9 @@ $article->content = 'Every year millions of people in ...'; $article->save(); ``` -Metoda `save()` va salva articolul într-un tabel din baza de date. Implementarea acesteia folosind [Nette Database |database:] va fi floare la ureche, dacă nu ar exista o singură problemă: de unde obține `Article` conexiunea la baza de date, adică un obiect din clasa `Nette\Database\Connection`? +Metoda `save()` salvează articolul într-un tabel din baza de date. Implementarea acesteia cu ajutorul [Nette Database |database:] ar fi o joacă de copil, dacă n-ar fi o mică problemă: de unde obține `Article` conexiunea la baza de date, adică obiectul clasei `Nette\Database\Connection`? -Se pare că avem o mulțime de opțiuni. Poate să o ia de la o variabilă statică undeva. Sau să moștenească dintr-o clasă care oferă o conexiune la baza de date. Sau să profite de un [singleton |global-state#Singleton]. Sau să folosească așa-numitele facade, care sunt folosite în Laravel: +Se pare că avem multe opțiuni. Poate să o ia de undeva dintr-o variabilă statică. Sau să moștenească de la o clasă care asigură conexiunea la baza de date. Sau să utilizeze așa-numitul [singleton |global-state#Singleton]. Sau așa-numitele facades, care sunt utilizate în Laravel: ```php use Illuminate\Support\Facades\DB; @@ -199,17 +199,17 @@ class Article } ``` -Minunat, am rezolvat problema. +Excelent, am rezolvat problema. -Sau am făcut-o? +Sau nu? -Să ne amintim [regula nr. 1: Să ți |#rule #1: Let It Be Passed to You] se transmită: toate dependențele de care clasa are nevoie trebuie să îi fie transmise. Pentru că, dacă încălcăm regula, am pornit pe drumul spre un cod murdar, plin de dependențe ascunse, incomprehensibil, iar rezultatul va fi o aplicație care va fi dureros de întreținut și dezvoltat. +Să ne amintim [##Regula nr. 1: Primește ce ai nevoie]: toate dependențele de care clasa are nevoie trebuie să-i fie transmise. Pentru că dacă încălcăm regula, am pornit pe calea către un cod murdar, plin de dependențe ascunse, neinteligibil, iar rezultatul va fi o aplicație pe care va fi dureros să o întreținem și să o dezvoltăm. -Utilizatorul clasei `Article` nu are nicio idee despre locul în care metoda `save()` stochează articolul. Într-un tabel din baza de date? În care, în cea de producție sau în cea de testare? Și cum poate fi modificat? +Utilizatorul clasei `Article` nu știe unde metoda `save()` salvează articolul. Într-un tabel din baza de date? În care, cel de producție sau cel de test? Și cum se poate schimba asta? -Utilizatorul trebuie să se uite la modul în care este implementată metoda `save()` și găsește utilizarea metodei `DB::insert()`. Deci, el trebuie să caute mai departe pentru a afla cum obține această metodă o conexiune la baza de date. Iar dependențele ascunse pot forma un lanț destul de lung. +Utilizatorul trebuie să se uite cum este implementată metoda `save()` și găsește utilizarea metodei `DB::insert()`. Așa că trebuie să investigheze mai departe cum își obține această metodă conexiunea la baza de date. Iar dependențele ascunse pot forma un lanț destul de lung. -În codul curat și bine conceput, nu există niciodată dependențe ascunse, fațade Laravel sau variabile statice. În codul curat și bine conceput, argumentele sunt transmise: +Într-un cod curat și bine proiectat nu există niciodată dependențe ascunse, facades Laravel sau variabile statice. Într-un cod curat și bine proiectat se transmit argumente: ```php class Article @@ -224,7 +224,7 @@ class Article } ``` -O abordare și mai practică, după cum vom vedea mai târziu, va fi prin intermediul constructorului: +Și mai practic, așa cum vom vedea mai departe, va fi prin constructor: ```php class Article @@ -245,11 +245,11 @@ class Article ``` .[note] -Dacă sunteți un programator experimentat, ați putea crede că `Article` nu ar trebui să aibă o metodă `save()`; ar trebui să reprezinte o componentă pur de date, iar un depozit separat ar trebui să se ocupe de salvare. Acest lucru are sens. Dar acest lucru ne-ar duce cu mult dincolo de scopul subiectului, care este injectarea dependențelor, și de efortul de a oferi exemple simple. +Dacă sunteți un programator experimentat, poate vă gândiți că `Article` nu ar trebui să aibă deloc metoda `save()`, ar trebui să reprezinte o componentă pură de date, iar de salvare ar trebui să se ocupe un repository separat. Asta are sens. Dar astfel am depăși cu mult subiectul dependency injection și efortul de a oferi exemple simple. -Dacă scrieți o clasă care are nevoie, de exemplu, de o bază de date pentru funcționarea sa, nu inventați de unde să o luați, ci să o aveți trecută. Fie ca parametru al constructorului, fie ca parametru al unei alte metode. Admiteți dependențele. Admiteți-le în API-ul clasei dumneavoastră. Veți obține un cod ușor de înțeles și previzibil. +Dacă scrieți o clasă care necesită, de exemplu, o bază de date pentru funcționarea sa, nu vă gândiți de unde să o obțineți, ci lăsați să vă fie transmisă. De exemplu, ca parametru al constructorului sau al altei metode. Recunoașteți dependențele. Recunoașteți-le în API-ul clasei dvs. Veți obține un cod inteligibil și previzibil. -Și cum rămâne cu această clasă, care înregistrează mesaje de eroare: +Și ce ziceți de această clasă, care loghează mesajele de eroare: ```php class Logger @@ -262,23 +262,23 @@ class Logger } ``` -Ce părere aveți, am respectat [regula #1: Lăsați să vi se transmită |#rule #1: Let It Be Passed to You]? +Ce credeți, am respectat [##Regula nr. 1: Primește ce ai nevoie]? Nu am respectat-o. -Informația cheie, și anume directorul cu fișierul jurnal, este *obținută* de clasa însăși din constantă. +Informația cheie, adică directorul cu fișierul de log, clasa *o obține singură* dintr-o constantă. -Priviți exemplul de utilizare: +Uitați-vă la exemplul de utilizare: ```php $logger = new Logger; -$logger->log('The temperature is 23 °C'); -$logger->log('The temperature is 10 °C'); +$logger->log('Temperatura este 23 °C'); +$logger->log('Temperatura este 10 °C'); ``` -Fără a cunoaște implementarea, ați putea răspunde la întrebarea unde sunt scrise mesajele? Ați putea ghici că existența constantei `LOG_DIR` este necesară pentru funcționarea sa? Și ați putea crea o a doua instanță care să scrie într-o altă locație? Cu siguranță că nu. +Fără a cunoaște implementarea, ați putea răspunde la întrebarea unde se scriu mesajele? V-ați fi gândit că pentru funcționare este necesară existența constantei `LOG_DIR`? Și ați putea crea o a doua instanță care să scrie în altă parte? Cu siguranță nu. -Haideți să reparăm clasa: +Să corectăm clasa: ```php class Logger @@ -295,24 +295,24 @@ class Logger } ``` -Clasa este acum mult mai ușor de înțeles, mai ușor de configurat și, prin urmare, mai utilă. +Clasa este acum mult mai inteligibilă, configurabilă și, prin urmare, mai utilă. ```php -$logger = new Logger('/path/to/log.txt'); -$logger->log('The temperature is 15 °C'); +$logger = new Logger('/cale/catre/log.txt'); +$logger->log('Temperatura este 15 °C'); ``` -Dar nu-mi pasă! .[#toc-but-i-don-t-care] ----------------------------------------- +Dar nu mă interesează! +---------------------- -*"Când creez un obiect articol și apelez la save(), nu vreau să am de-a face cu baza de date; vreau doar să fie salvat în cea pe care am stabilit-o în configurație."* +*„Când creez un obiect Article și apelez save(), nu vreau să mă ocup de baza de date, vreau doar să fie salvat în cea pe care o am setată în configurație.”* -*"Când folosesc Logger, vreau doar ca mesajul să fie scris și nu vreau să mă ocup de locul în care este scris. Să se folosească setările globale. "* +*„Când folosesc Logger, vreau doar ca mesajul să fie scris și nu vreau să mă ocup de unde. Să se folosească setarea globală.”* -Acestea sunt puncte valide. +Acestea sunt observații corecte. -Ca exemplu, să ne uităm la o clasă care trimite buletine informative și înregistrează cum a decurs: +Ca exemplu, vom arăta o clasă care distribuie newslettere și care loghează cum a decurs: ```php class NewsletterDistributor @@ -322,27 +322,27 @@ class NewsletterDistributor $logger = new Logger(/* ... */); try { $this->sendEmails(); - $logger->log('Emails have been sent out'); + $logger->log('E-mailurile au fost trimise'); } catch (Exception $e) { - $logger->log('An error occurred during the sending'); + $logger->log('A apărut o eroare la trimitere'); throw $e; } } } ``` -Varianta îmbunătățită `Logger`, care nu mai utilizează constanta `LOG_DIR`, necesită specificarea căii de acces la fișier în constructor. Cum se poate rezolva acest lucru? Clasei `NewsletterDistributor` nu-i pasă unde sunt scrise mesajele; ea vrea doar să le scrie. +`Logger`-ul îmbunătățit, care nu mai folosește constanta `LOG_DIR`, necesită specificarea căii către fișier în constructor. Cum rezolvăm asta? Clasa `NewsletterDistributor` nu este deloc interesată unde se scriu mesajele, vrea doar să le scrie. -Soluția este din nou [regula nr. 1: Lasă să ți se transmită |#rule #1: Let It Be Passed to You]: transmite toate datele de care clasa are nevoie. +Soluția este din nou [##Regula nr. 1: Primește ce ai nevoie]: toate datele de care clasa are nevoie, i le transmitem. -Asta înseamnă că trebuie să transmitem calea către jurnal prin constructor, pe care o folosim apoi la crearea obiectului `Logger`? +Deci asta înseamnă că transmitem calea către log prin constructor, pe care apoi o folosim la crearea obiectului `Logger`? ```php class NewsletterDistributor { public function __construct( - private string $file, // ⛔ NU ÎN ACEST FEL! + private string $file, // ⛔ NU AȘA! ) { } @@ -351,7 +351,7 @@ class NewsletterDistributor $logger = new Logger($this->file); ``` -Nu, nu așa! Calea nu face parte dintre datele de care are nevoie clasa `NewsletterDistributor`; de fapt, `Logger` are nevoie de ea. Vedeți care este diferența? Clasa `NewsletterDistributor` are nevoie de loggerul însuși. Așa că asta este ceea ce vom trece: +Nu așa! Calea **nu face parte** din datele de care are nevoie clasa `NewsletterDistributor`; de acestea are nevoie `Logger`. Percepeți diferența? Clasa `NewsletterDistributor` are nevoie de logger ca atare. Așa că îl vom transmite pe acesta: ```php class NewsletterDistributor @@ -365,35 +365,33 @@ class NewsletterDistributor { try { $this->sendEmails(); - $this->logger->log('Emails have been sent out'); + $this->logger->log('E-mailurile au fost trimise'); } catch (Exception $e) { - $this->logger->log('An error occurred during the sending'); + $this->logger->log('A apărut o eroare la trimitere'); throw $e; } } } ``` -Din semnăturile clasei `NewsletterDistributor` reiese clar că și jurnalizarea face parte din funcționalitatea sa. Iar sarcina de a schimba loggerul cu un altul, poate pentru testare, este complet trivială. -În plus, dacă se schimbă constructorul clasei `Logger`, acest lucru nu va afecta clasa noastră. +Acum, din semnăturile clasei `NewsletterDistributor` este clar că logarea face parte din funcționalitatea sa. Iar sarcina de a înlocui loggerul cu altul, de exemplu pentru testare, este complet trivială. Mai mult, dacă constructorul clasei `Logger` s-ar schimba, acest lucru nu ar avea niciun impact asupra clasei noastre. -Regula nr. 2: Luați ceea ce este al dumneavoastră .[#toc-rule-2-take-what-s-yours] ----------------------------------------------------------------------------------- +Regula nr. 2: Ia doar ce este al tău +------------------------------------ -Nu vă lăsați păcăliți și nu vă lăsați să treceți peste dependențele dependențelor voastre. Treceți-vă doar propriile dependențe. +Nu vă lăsați induși în eroare și nu vă lăsați să vi se transmită dependențele dependențelor voastre. Lăsați să vi se transmită doar dependențele voastre. -Datorită acestui lucru, codul care utilizează alte obiecte va fi complet independent de modificările din constructorii acestora. API-ul său va fi mai veridic. Și, mai presus de toate, va fi trivial să înlocuiți aceste dependențe cu altele. +Datorită acestui fapt, codul care utilizează alte obiecte va fi complet independent de modificările constructorilor acestora. API-ul său va fi mai veridic. Și, mai presus de toate, va fi trivial să înlocuiți aceste dependențe cu altele. -Un nou membru al familiei .[#toc-new-family-member] ---------------------------------------------------- +Un nou membru al familiei +------------------------- -Echipa de dezvoltare a decis să creeze un al doilea logger care să scrie în baza de date. Așa că am creat o clasă `DatabaseLogger`. Deci avem două clase, `Logger` și `DatabaseLogger`, una scrie într-un fișier, cealaltă într-o bază de date ... nu vi se pare ciudată denumirea? -Nu ar fi mai bine să redenumim `Logger` în `FileLogger`? Categoric da. +În echipa de dezvoltare s-a decis crearea unui al doilea logger, care scrie în baza de date. Vom crea deci clasa `DatabaseLogger`. Așadar, avem două clase, `Logger` și `DatabaseLogger`, una scrie într-un fișier, cealaltă în baza de date... nu vi se pare ceva ciudat la această denumire? Nu ar fi mai bine să redenumim `Logger` în `FileLogger`? Cu siguranță da. -Dar haideți să o facem în mod inteligent. Creăm o interfață sub numele original: +Dar o vom face inteligent. Sub numele original vom crea o interfață: ```php interface Logger @@ -402,7 +400,7 @@ interface Logger } ``` -... pe care le vor pune în aplicare ambele loguri: +… pe care ambii loggeri o vor implementa: ```php class FileLogger implements Logger @@ -412,17 +410,17 @@ class DatabaseLogger implements Logger // ... ``` -Și, din acest motiv, nu va fi nevoie să se schimbe nimic în restul codului în care este utilizat loggerul. De exemplu, constructorul clasei `NewsletterDistributor` se va mulțumi în continuare să solicite `Logger` ca parametru. Și va fi la latitudinea noastră ce instanță vom trece. +Și datorită acestui fapt, nu va fi nevoie să schimbăm nimic în restul codului unde se utilizează loggerul. De exemplu, constructorul clasei `NewsletterDistributor` va fi în continuare mulțumit că necesită `Logger` ca parametru. Și va depinde doar de noi ce instanță îi vom transmite. -**De aceea nu adăugăm niciodată sufixul `Interface` sau prefixul `I` la numele interfețelor.** Altfel, nu ar fi posibilă dezvoltarea atât de frumoasă a codului. +**De aceea nu adăugăm niciodată sufixul `Interface` sau prefixul `I` la numele interfețelor.** Altfel nu ar fi posibil să dezvoltăm codul atât de frumos. -Houston, avem o problemă .[#toc-houston-we-have-a-problem] ----------------------------------------------------------- +Houston, avem o problemă +------------------------ -În timp ce ne putem descurca cu o singură instanță a logger-ului, fie că este bazat pe fișier sau pe bază de date, în întreaga aplicație și pur și simplu să o trecem oriunde este înregistrat ceva, este cu totul altceva pentru clasa `Article`. Creăm instanțele sale după cum este necesar, chiar și de mai multe ori. Cum să tratăm dependența de baza de date în constructorul său? +În timp ce în întreaga aplicație ne putem descurca cu o singură instanță de logger, fie el de fișier sau de bază de date, și pur și simplu o transmitem oriunde se loghează ceva, situația este destul de diferită în cazul clasei `Article`. Instanțele sale le creăm după nevoie, chiar de mai multe ori. Cum să gestionăm dependența de baza de date în constructorul său? -Un exemplu poate fi un controler care ar trebui să salveze un articol în baza de date după trimiterea unui formular: +Ca exemplu poate servi un controller care, după trimiterea unui formular, trebuie să salveze articolul în baza de date: ```php class EditController extends Controller @@ -437,30 +435,30 @@ class EditController extends Controller } ``` -O posibilă soluție este evidentă: treceți obiectul bazei de date la constructorul `EditController` și utilizați `$article = new Article($this->db)`. +O posibilă soluție se oferă direct: lăsăm obiectul bazei de date să fie transmis prin constructor către `EditController` și folosim `$article = new Article($this->db)`. -La fel ca în cazul precedent cu `Logger` și calea de acces la fișier, aceasta nu este abordarea corectă. Baza de date nu este o dependență a `EditController`, ci a `Article`. Transmiterea bazei de date contravine [regulii nr. 2: ia ceea ce este al tău |#rule #2: take what's yours]. Dacă se modifică constructorul clasei `Article` (se adaugă un nou parametru), va trebui să modificați codul acolo unde sunt create instanțe. Ufff. +La fel ca în cazul anterior cu `Logger` și calea către fișier, aceasta nu este abordarea corectă. Baza de date nu este o dependență a `EditController`, ci a `Article`. Transmiterea bazei de date contravine deci [Regulii nr. 2: Ia doar ce este al tău |#Regula nr. 2: Ia doar ce este al tău]. Când se schimbă constructorul clasei `Article` (se adaugă un nou parametru), va fi necesar să se modifice și codul în toate locurile unde se creează instanțe. Ufff. -Houston, ce sugerezi? +Houston, ce propui? -Regula nr. 3: Lăsați fabrica să se ocupe de asta .[#toc-rule-3-let-the-factory-handle-it] ------------------------------------------------------------------------------------------ +Regula nr. 3: Lasă pe seama fabricii +------------------------------------ -Prin eliminarea dependențelor ascunse și prin transmiterea tuturor dependențelor ca argumente, am obținut clase mai configurabile și mai flexibile. Și, prin urmare, avem nevoie de altceva pentru a crea și configura aceste clase mai flexibile pentru noi. Îl vom numi fabrici. +Prin eliminarea dependențelor ascunse și transmiterea tuturor dependențelor ca argumente, am obținut clase mai configurabile și mai flexibile. Și, prin urmare, avem nevoie de ceva în plus, care să ne creeze și să ne configureze acele clase mai flexibile. Le vom numi fabrici. -Regula de bază este: dacă o clasă are dependențe, lăsați crearea instanțelor acestora în seama fabricii. +Regula este: dacă o clasă are dependențe, lăsați crearea instanțelor sale pe seama unei fabrici. -Fabricile sunt un înlocuitor mai inteligent pentru operatorul `new` în lumea injecției de dependență. +Fabricile sunt înlocuitori mai inteligenți ai operatorului `new` în lumea dependency injection. .[note] -Vă rugăm să nu faceți confuzie cu modelul de proiectare *factory method*, care descrie un mod specific de utilizare a fabricilor și nu are legătură cu acest subiect. +Vă rugăm să nu confundați cu modelul de design (design pattern) *factory method*, care descrie un mod specific de utilizare a fabricilor și nu are legătură cu acest subiect. -Fabrica .[#toc-factory] ------------------------ +Fabrica +------- -O fabrică este o metodă sau o clasă care creează și configurează obiecte. Vom numi clasa care produce `Article` ca `ArticleFactory`, iar aceasta ar putea arăta astfel: +O fabrică este o metodă sau o clasă care produce și configurează obiecte. Clasa care produce `Article` o vom numi `ArticleFactory` și ar putea arăta, de exemplu, astfel: ```php class ArticleFactory @@ -477,7 +475,7 @@ class ArticleFactory } ``` -Utilizarea sa în controler va fi următoarea: +Utilizarea sa în controller va fi următoarea: ```php class EditController extends Controller @@ -489,7 +487,7 @@ class EditController extends Controller public function formSubmitted($data) { - // permiteți fabricii să creeze un obiect + // lăsăm fabrica să creeze obiectul $article = $this->articleFactory->create(); $article->title = $data->title; $article->content = $data->content; @@ -498,11 +496,11 @@ class EditController extends Controller } ``` -În acest moment, dacă semnătura constructorului clasei `Article` se schimbă, singura parte a codului care trebuie să reacționeze este `ArticleFactory`. Toate celelalte coduri care lucrează cu obiectele `Article`, cum ar fi `EditController`, nu vor fi afectate. +Dacă în acest moment se schimbă semnătura constructorului clasei `Article`, singura parte a codului care trebuie să reacționeze este însăși fabrica `ArticleFactory`. Tot restul codului care lucrează cu obiecte `Article`, cum ar fi `EditController`, nu va fi afectat în niciun fel. -S-ar putea să vă întrebați dacă am îmbunătățit de fapt lucrurile. Cantitatea de cod a crescut și totul începe să pară suspect de complicat. +Poate vă bateți acum capul dacă ne-am ajutat cu ceva. Cantitatea de cod a crescut și totul începe să pară suspect de complicat. -Nu vă faceți griji, în curând vom ajunge la containerul Nette DI. Iar acesta are câteva trucuri în mânecă, care vor simplifica foarte mult construirea de aplicații folosind injecția de dependență. De exemplu, în loc de clasa `ArticleFactory`, va trebui să [scrieți |factory] doar [o interfață simplă |factory]: +Nu vă faceți griji, în curând vom ajunge la containerul Nette DI. Și acesta are o serie de ași în mânecă, care simplifică enorm construirea aplicațiilor care utilizează dependency injection. De exemplu, în loc de clasa `ArticleFactory`, va fi suficient să [scrie doar o interfață |factory]: ```php interface ArticleFactory @@ -511,18 +509,18 @@ interface ArticleFactory } ``` -Dar ne devansăm; vă rugăm să aveți răbdare :-) +Dar anticipăm, mai aveți puțină răbdare :-) -Rezumat .[#toc-summary] ------------------------ +Rezumat +------- -La începutul acestui capitol, am promis să vă arătăm un proces de proiectare a unui cod curat. Tot ce este nevoie este ca clasele să: +La începutul acestui capitol am promis că vom arăta o metodă de a proiecta cod curat. Este suficient ca claselor -- [să treacă dependențele de care au nevoie |#Rule #1: Let It Be Passed to You] -- [invers, să nu treacă ceea ce nu au nevoie în mod direct |#Rule #2: Take What's Yours] -- [și că obiectele cu dependențe sunt cel mai bine create în fabrici |#Rule #3: Let the Factory Handle it] +1) [să le transmitem dependențele de care au nevoie |#Regula nr. 1: Primește ce ai nevoie] +2) [și, dimpotrivă, să nu le transmitem ceea ce nu au nevoie direct |#Regula nr. 2: Ia doar ce este al tău] +3) [și că obiectele cu dependențe sunt cel mai bine create în fabrici |#Regula nr. 3: Lasă pe seama fabricii] -La prima vedere, aceste trei reguli pot părea să nu aibă consecințe profunde, dar ele conduc la o perspectivă radical diferită asupra proiectării codului. Merită? Dezvoltatorii care au renunțat la vechile obiceiuri și au început să folosească în mod consecvent injecția de dependență consideră acest pas un moment crucial în viața lor profesională. Le-a deschis lumea aplicațiilor clare și ușor de întreținut. +Poate nu pare așa la prima vedere, dar aceste trei reguli au consecințe de anvergură. Conduc la o perspectivă radical diferită asupra designului codului. Merită? Programatorii care au renunțat la vechile obiceiuri și au început să utilizeze consecvent dependency injection consideră acest pas un moment crucial în viața lor profesională. Li s-a deschis lumea aplicațiilor clare și ușor de întreținut. -Dar ce se întâmplă dacă codul nu folosește în mod consecvent injecția de dependență? Ce se întâmplă dacă se bazează pe metode statice sau singletoni? Cauzează asta probleme? [Da, da, și unele foarte fundamentale |global-state]. +Dar ce se întâmplă dacă codul nu utilizează consecvent dependency injection? Ce se întâmplă dacă este construit pe metode statice sau singleton-uri? Aduce asta probleme? [Aduce și foarte fundamentale |global-state]. diff --git a/dependency-injection/ro/nette-container.texy b/dependency-injection/ro/nette-container.texy index 298dff78c7..c076686fe6 100644 --- a/dependency-injection/ro/nette-container.texy +++ b/dependency-injection/ro/nette-container.texy @@ -1,10 +1,10 @@ -Container Nette DI +Nette DI Container ****************** .[perex] -Nette DI este una dintre cele mai interesante biblioteci Nette. Aceasta poate genera și actualiza automat containere DI compilate care sunt extrem de rapide și uimitor de ușor de configurat. +Nette DI este una dintre cele mai interesante biblioteci Nette. Poate genera și actualiza automat containere DI compilate, care sunt extrem de rapide și uimitor de ușor de configurat. -Serviciile care urmează să fie create de un container DI sunt de obicei definite cu ajutorul fișierelor de configurare în [format NEON |neon:format]. Containerul pe care l-am creat manual în [secțiunea anterioară |container] ar fi scris după cum urmează: +Forma serviciilor pe care containerul DI trebuie să le creeze o definim de obicei folosind fișiere de configurație în [format NEON|neon:format]. Containerul pe care l-am creat manual în [capitolul anterior|container] s-ar scrie astfel: ```neon parameters: @@ -19,16 +19,15 @@ services: - UserController ``` -Notația este foarte scurtă. +Notația este într-adevăr concisă. -Toate dependențele declarate în constructorii claselor `ArticleFactory` și `UserController` sunt găsite și trecute de Nette DI însuși datorită așa-numitei [autowiring], astfel încât nu este nevoie să se specifice nimic în fișierul de configurare. -Astfel, chiar dacă parametrii se schimbă, nu trebuie să modificați nimic în configurație. Nette va regenera automat containerul. Vă puteți concentra acolo pur și simplu pe dezvoltarea aplicației. +Toate dependențele declarate în constructorii claselor `ArticleFactory` și `UserController`, Nette DI le descoperă și le transmite singur datorită așa-numitului [autowiring |autowiring], de aceea nu este nevoie să se specifice nimic în fișierul de configurație. Astfel, chiar dacă parametrii se schimbă, nu trebuie să modificați nimic în configurație. Containerul Nette se regenerează automat. Vă puteți concentra astfel exclusiv pe dezvoltarea aplicației. -Dacă doriți să treceți dependențele folosind setteri, utilizați secțiunea de [configurare |services#setup] pentru a face acest lucru. +Dacă dorim să transmitem dependențe folosind setteri, folosim secțiunea [setup |services#Setup] pentru aceasta. -Nette DI va genera direct codul PHP pentru container. Rezultatul este astfel un fișier `.php` pe care îl puteți deschide și studia. Acest lucru vă permite să vedeți exact cum funcționează containerul. De asemenea, îl puteți depana în IDE și îl puteți parcurge. Și cel mai important: PHP-ul generat este extrem de rapid. +Nette DI generează direct cod PHP pentru container. Rezultatul este deci un fișier `.php`, pe care îl puteți deschide și studia. Datorită acestui fapt, vedeți exact cum funcționează containerul. Îl puteți de asemenea depana în IDE și parcurge pas cu pas. Și cel mai important: PHP-ul generat este extrem de rapid. -Nette DI poate genera, de asemenea, cod de [fabrică |factory] pe baza interfeței furnizate. Prin urmare, în loc de clasa `ArticleFactory`, trebuie doar să creăm o interfață în aplicație: +Nette DI poate genera și cod pentru [fabrici|factory] pe baza interfeței furnizate. De aceea, în loc de clasa `ArticleFactory`, ne va fi suficient să creăm în aplicație doar o interfață: ```php interface ArticleFactory @@ -37,19 +36,19 @@ interface ArticleFactory } ``` -Puteți găsi exemplul complet [pe GitHub |https://github.com/nette-examples/di-example-doc]. +Exemplul complet îl găsiți [pe GitHub|https://github.com/nette-examples/di-example-doc]. -Utilizare autonomă .[#toc-standalone-use] ------------------------------------------ +Utilizare independentă +---------------------- -Utilizarea bibliotecii Nette DI într-o aplicație este foarte simplă. Mai întâi o instalăm cu Composer (pentru că descărcarea fișierelor zip este atât de depășită): +Implementarea bibliotecii Nette DI într-o aplicație este foarte ușoară. Mai întâi o instalăm cu Composer (pentru că descărcarea arhivelor zip este așaaa de învechită): ```shell composer require nette/di ``` -Următorul cod creează o instanță a containerului DI în conformitate cu configurația stocată în fișierul `config.neon`: +Următorul cod creează o instanță a containerului DI conform configurației stocate în fișierul `config.neon`: ```php $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp'); @@ -59,24 +58,23 @@ $class = $loader->load(function ($compiler) { $container = new $class; ``` -Containerul este generat o singură dată, codul său este scris în memoria cache (directorul `__DIR__ . '/temp'` ), iar la solicitările ulterioare este citit doar de acolo. +Containerul se generează o singură dată, codul său se scrie în cache (directorul `__DIR__ . '/temp'`) și la cererile ulterioare se încarcă doar de aici. -Metodele `getService()` sau `getByType()` sunt utilizate pentru a crea și a prelua servicii. Acesta este modul în care creăm obiectul `UserController`: +Pentru crearea și obținerea serviciilor se folosesc metodele `getService()` sau `getByType()`. Astfel creăm obiectul `UserController`: ```php -$database = $container->getByType(UserController::class); -$database->query('...'); +$controller = $container->getByType(UserController::class); +$controller->someMethod(); ``` -În timpul dezvoltării, este util să se activeze modul de reîmprospătare automată, în care containerul este regenerat automat dacă se modifică orice clasă sau fișier de configurare. Trebuie doar să furnizați `true` ca al doilea argument în constructorul `ContainerLoader`. +În timpul dezvoltării este util să activăm modul auto-refresh, în care containerul se regenerează automat dacă se modifică orice clasă sau fișier de configurație. Este suficient să specificăm `true` ca al doilea argument în constructorul `ContainerLoader`. ```php $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp', true); ``` -Utilizarea cu Nette Framework .[#toc-using-it-with-the-nette-framework] ------------------------------------------------------------------------ +Utilizare cu framework-ul Nette +------------------------------- -După cum am arătat, utilizarea Nette DI nu este limitată la aplicațiile scrise în Nette Framework, ci o puteți implementa oriunde cu doar 3 linii de cod. -Cu toate acestea, dacă dezvoltați aplicații în Nette Framework, configurarea și crearea containerului este gestionată de [Bootstrap |application:bootstrap#toc-di-container-configuration]. +Așa cum am arătat, utilizarea Nette DI nu este limitată la aplicațiile scrise în Nette Framework, îl puteți implementa oriunde cu doar 3 rânduri de cod. Dacă însă dezvoltați aplicații în Nette Framework, configurarea și crearea containerului sunt gestionate de [Bootstrap |application:bootstrapping#Configurarea containerului DI]. diff --git a/dependency-injection/ro/passing-dependencies.texy b/dependency-injection/ro/passing-dependencies.texy index 2437edaeb2..3d70d4acba 100644 --- a/dependency-injection/ro/passing-dependencies.texy +++ b/dependency-injection/ro/passing-dependencies.texy @@ -3,22 +3,22 @@ Transmiterea dependențelor <div class=perex> -Argumentele, sau "dependențele" în terminologia DI, pot fi transmise claselor în următoarele moduri principale: +Argumentele, sau în terminologia DI „dependențele”, pot fi transmise claselor în următoarele moduri principale: -* trecerea prin constructor -* transmiterea prin metodă (numită setter) -* prin stabilirea unei proprietăți -* prin metoda, adnotarea sau atributul *inject*. +* transmitere prin constructor +* transmitere prin metodă (așa-numitul setter) +* setarea proprietății (variabilei membru) +* prin metodă, adnotare sau atribut *inject* </div> -În continuare, vom ilustra diferitele variante cu exemple concrete. +Acum vom arăta fiecare variantă cu exemple concrete. -Injectarea constructorilor .[#toc-constructor-injection] -======================================================== +Transmitere prin constructor +============================ -Dependențele sunt transmise ca argumente constructorului atunci când obiectul este creat: +Dependențele sunt transmise în momentul creării obiectului ca argumente ale constructorului: ```php class MyClass @@ -34,9 +34,9 @@ class MyClass $obj = new MyClass($cache); ``` -Această formă este utilă pentru dependențele obligatorii de care clasa are neapărat nevoie pentru a funcționa, deoarece fără ele instanța nu poate fi creată. +Această formă este potrivită pentru dependențele obligatorii, de care clasa are neapărat nevoie pentru funcționarea sa, deoarece fără ele instanța nu va putea fi creată. -Începând cu PHP 8.0, putem utiliza o formă mai scurtă de notație care este echivalentă din punct de vedere funcțional ([constructor property promotion |https://blog.nette.org/ro/php-8-0-prezentare-completa-a-noutatilor#toc-constructor-property-promotion]): +Începând cu PHP 8.0 putem folosi o formă mai scurtă de notație ([constructor property promotion |https://blog.nette.org/ro/php-8-0-complete-overview-of-news#toc-constructor-property-promotion]), care este funcțional echivalentă: ```php // PHP 8.0 @@ -49,7 +49,7 @@ class MyClass } ``` -Începând cu PHP 8.1, o proprietate poate fi marcată cu un indicator `readonly` care declară că conținutul proprietății nu se va modifica: +Începând cu PHP 8.1, proprietatea poate fi marcată cu flag-ul `readonly`, care declară că conținutul proprietății nu se va mai schimba: ```php // PHP 8.1 @@ -62,13 +62,13 @@ class MyClass } ``` -Containerul DI transmite dependențele către constructor în mod automat, utilizând [autowiring |autowiring]. Argumentele care nu pot fi transmise [în |services#Arguments] acest mod (de exemplu, șiruri de caractere, numere, booleeni) se [scriu în configurație |services#Arguments]. +Containerul DI transmite constructorului dependențele automat folosind [autowiring |autowiring]. Argumentele care nu pot fi transmise astfel (de ex. șiruri, numere, booleeni) [le scriem în configurație |services#Argumente]. -Iadul Constructorilor .[#toc-constructor-hell] ----------------------------------------------- +Constructor hell +---------------- -Termenul *constructor hell* se referă la o situație în care un copil moștenește dintr-o clasă părinte al cărei constructor necesită dependențe, iar copilul necesită și el dependențe. De asemenea, acesta trebuie să preia și să transmită dependențele părintelui: +Termenul *constructor hell* desemnează situația în care un descendent moștenește de la o clasă părinte al cărei constructor necesită dependențe, și în același timp descendentul necesită dependențe. În acest caz, trebuie să preia și să transmită și pe cele părintești: ```php abstract class BaseClass @@ -94,11 +94,11 @@ final class MyClass extends BaseClass } ``` -Problema apare atunci când dorim să modificăm constructorul clasei `BaseClass`, de exemplu atunci când se adaugă o nouă dependență. Atunci trebuie să modificăm și toți constructorii copiilor. Ceea ce face ca o astfel de modificare să fie un iad. +Problema apare în momentul în care dorim să schimbăm constructorul clasei `BaseClass`, de exemplu când se adaugă o nouă dependență. Atunci este necesar să modificăm și toți constructorii descendenților. Ceea ce face o astfel de modificare un iad. -Cum să prevenim acest lucru? Soluția este să **prioritizăm compoziția în detrimentul moștenirii**. +Cum să prevenim asta? Soluția este **să preferăm [compoziția în detrimentul moștenirii |faq#De ce se preferă compoziția în locul moștenirii]**. -Așadar, haideți să proiectăm codul în mod diferit. Vom evita clasele abstracte `Base*`. În loc ca `MyClass` să obțină o anumită funcționalitate prin moștenirea de la `BaseClass`, aceasta va avea acea funcționalitate transmisă ca dependență: +Deci vom proiecta codul altfel. Vom evita clasele [abstracte |nette:introduction-to-object-oriented-programming#Clase abstracte] `Base*`. În loc ca `MyClass` să obțină o anumită funcționalitate prin moștenirea de la `BaseClass`, își va lăsa această funcționalitate să-i fie transmisă ca dependență: ```php final class SomeFunctionality @@ -125,10 +125,10 @@ final class MyClass ``` -Injectarea setterilor .[#toc-setter-injection] -============================================== +Transmitere prin setter +======================= -Dependențele sunt transmise prin apelarea unei metode care le stochează într-o proprietate privată. Convenția de denumire obișnuită pentru aceste metode este de forma `set*()`, motiv pentru care sunt numite setters, dar, desigur, pot fi numite în orice alt mod. +Dependențele sunt transmise prin apelarea unei metode care le stochează într-o proprietate privată. Convenția obișnuită de denumire a acestor metode este forma `set*()`, de aceea li se spune setteri, dar pot fi, desigur, numite oricum altfel. ```php class MyClass @@ -145,9 +145,9 @@ $obj = new MyClass; $obj->setCache($cache); ``` -Această metodă este utilă pentru dependențele opționale care nu sunt necesare pentru funcția clasei, deoarece nu este garantat faptul că obiectul le va primi efectiv (adică, că utilizatorul va apela metoda). +Acest mod este potrivit pentru dependențele opționale, care nu sunt necesare pentru funcționarea clasei, deoarece nu este garantat că obiectul va primi efectiv dependența (adică că utilizatorul va apela metoda). -În același timp, această metodă permite ca setterul să fie apelat în mod repetat pentru a modifica dependența. Dacă acest lucru nu este de dorit, adăugați o verificare la metodă sau, începând cu PHP 8.1, marcați proprietatea `$cache` cu steagul `readonly`. +În același timp, acest mod permite apelarea repetată a setterului și astfel modificarea dependenței. Dacă acest lucru nu este dorit, adăugăm o verificare în metodă sau, începând cu PHP 8.1, marcăm proprietatea `$cache` cu flag-ul `readonly`. ```php class MyClass @@ -156,29 +156,28 @@ class MyClass public function setCache(Cache $cache): void { - if ($this->cache) { - throw new RuntimeException('The dependency has already been set'); + if (isset($this->cache)) { + throw new RuntimeException('Dependența a fost deja setată.'); } $this->cache = $cache; } } ``` -Apelarea setterului este definită în configurația containerului DI în [secțiunea setup |services#Setup]. Tot aici este utilizată trecerea automată a dependențelor prin autowiring: +Apelarea setterului o definim în configurația containerului DI în [cheia setup |services#Setup]. Și aici se utilizează transmiterea automată a dependențelor prin autowiring: ```neon services: - - - create: MyClass + - create: MyClass setup: - setCache ``` -Injectarea proprietăților .[#toc-property-injection] -==================================================== +Setarea proprietății +==================== -Dependențele sunt trecute direct în proprietate: +Dependențele sunt transmise prin scrierea directă în proprietatea membru: ```php class MyClass @@ -190,28 +189,27 @@ $obj = new MyClass; $obj->cache = $cache; ``` -Această metodă este considerată nepotrivită deoarece proprietatea trebuie declarată ca fiind `public`. Prin urmare, nu avem niciun control asupra faptului că dependența transmisă va fi de tipul specificat (acest lucru era valabil înainte de PHP 7.4) și pierdem posibilitatea de a reacționa la dependența nou atribuită cu propriul cod, de exemplu pentru a preveni modificările ulterioare. În același timp, proprietatea devine parte a interfeței publice a clasei, ceea ce poate să nu fie de dorit. +Acest mod este considerat nepotrivit, deoarece proprietatea membru trebuie declarată ca `public`. Și, prin urmare, nu avem control asupra faptului că dependența transmisă va fi într-adevăr de tipul dat (valabil înainte de PHP 7.4) și pierdem posibilitatea de a reacționa la dependența nou atribuită cu cod propriu, de exemplu, pentru a preveni modificarea ulterioară. În același timp, proprietatea devine parte a interfeței publice a clasei, ceea ce poate să nu fie de dorit. -Setarea variabilei este definită în configurația containerului DI în [secțiunea de configurare |services#Setup]: +Setarea proprietății o definim în configurația containerului DI în [secțiunea setup |services#Setup]: ```neon services: - - - create: MyClass + - create: MyClass setup: - $cache = @\Cache ``` -Injectați .[#toc-inject] -======================== +Inject +====== -În timp ce cele trei metode anterioare sunt, în general, valabile în toate limbajele orientate pe obiecte, injectarea prin metodă, adnotare sau atributul *inject* este specifică prezentatorilor Nette. Acestea sunt discutate [într-un capitol separat |best-practices:inject-method-attribute]. +În timp ce cele trei moduri anterioare sunt valabile în general în toate limbajele orientate pe obiecte, injectarea prin metodă, adnotare sau atribut *inject* este specifică exclusiv presenterilor din Nette. Despre acestea se discută într-un [capitol separat |best-practices:inject-method-attribute]. -Ce modalitate să alegeți? .[#toc-which-way-to-choose] -===================================================== +Ce mod să alegem? +================= -- constructorul este potrivit pentru dependențele obligatorii de care clasa are nevoie pentru a funcționa -- setterul, pe de altă parte, este potrivit pentru dependențele opționale sau pentru dependențele care pot fi modificate -- variabilele publice nu sunt recomandate +- constructorul este potrivit pentru dependențele obligatorii, de care clasa are neapărat nevoie pentru funcționarea sa +- setterul este, dimpotrivă, potrivit pentru dependențele opționale sau dependențele care pot fi modificate ulterior +- proprietățile publice nu sunt potrivite diff --git a/dependency-injection/ro/services.texy b/dependency-injection/ro/services.texy index 162f918633..08b7683a29 100644 --- a/dependency-injection/ro/services.texy +++ b/dependency-injection/ro/services.texy @@ -1,33 +1,33 @@ -Definiții ale serviciilor -************************* +Definirea serviciilor +********************* .[perex] -Configurația este locul în care se plasează definițiile serviciilor personalizate. Acest lucru se face în secțiunea `services`. +Configurația este locul unde învățăm containerul DI cum să asambleze serviciile individuale și cum să le conecteze cu alte dependențe. Nette oferă o modalitate foarte clară și elegantă de a realiza acest lucru. -De exemplu, astfel creăm un serviciu numit `database`, care va fi o instanță a clasei `PDO`: +Secțiunea `services` din fișierul de configurație în format NEON este locul unde definim serviciile proprii și configurațiile lor. Să vedem un exemplu simplu de definire a unui serviciu numit `database`, care reprezintă o instanță a clasei `PDO`: ```neon services: database: PDO('sqlite::memory:') ``` -Denumirea serviciilor este utilizată pentru a ne permite să le [referim |#Referencing Services] la acestea. În cazul în care un serviciu nu este menționat, nu este necesar să îl denumim. Prin urmare, folosim doar un punct în loc de un nume: +Configurația menționată va rezulta în următoarea metodă factory în [containerul DI|container]: -```neon -services: - - PDO('sqlite::memory:') # serviciu anonim +```php +public function createServiceDatabase(): PDO +{ + return new PDO('sqlite::memory:'); +} ``` -O intrare pe o singură linie poate fi împărțită în mai multe linii pentru a permite adăugarea de chei suplimentare, cum ar fi [configurare |#setup]. Pseudonimul pentru tasta `create:` este `factory:`. +Numele serviciilor ne permit să ne referim la ele în alte părți ale fișierului de configurație, în formatul `@numeServiciu`. Dacă nu este necesar să numim serviciul, putem folosi pur și simplu doar o liniuță: ```neon services: - database: - create: PDO('sqlite::memory:') - setup: ... + - PDO('sqlite::memory:') ``` -Apoi, recuperăm serviciul din containerul DI folosind metoda `getService()` după nume sau, mai bine, metoda `getByType()` după tip: +Pentru a obține un serviciu din containerul DI, putem utiliza metoda `getService()` cu numele serviciului ca parametru, sau metoda `getByType()` cu tipul serviciului: ```php $database = $container->getService('database'); @@ -35,26 +35,28 @@ $database = $container->getByType(PDO::class); ``` -Crearea unui serviciu .[#toc-creating-a-service] -================================================ +Crearea serviciului +=================== -Cel mai adesea, creăm un serviciu prin simpla creare a unei instanțe a unei clase: +De cele mai multe ori, creăm un serviciu pur și simplu prin crearea unei instanțe a unei anumite clase. De exemplu: ```neon services: database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -Ceea ce va genera o metodă factory în [containerul DI |container]: +Dacă avem nevoie să extindem configurația cu alte chei, definiția poate fi împărțită pe mai multe rânduri: -```php -public function createServiceDatabase(): PDO -{ - return new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'secret'); -} +```neon +services: + database: + create: PDO('sqlite::memory:') + setup: ... ``` -Alternativ, o cheie `arguments` poate fi utilizată pentru a transmite [argumente |#Arguments]: +Cheia `create` are aliasul `factory`, ambele variante sunt comune în practică. Cu toate acestea, recomandăm utilizarea `create`. + +Argumentele constructorului sau ale metodei de creare pot fi alternativ scrise în cheia `arguments`: ```neon services: @@ -63,294 +65,303 @@ services: arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret] ``` -O metodă statică poate crea, de asemenea, un serviciu: +Serviciile nu trebuie create doar prin simpla instanțiere a unei clase, ele pot fi, de asemenea, rezultatul apelării metodelor statice sau metodelor altor servicii: ```neon services: - database: My\Database::create(root, secret) + database: DatabaseFactory::create() + router: @routerFactory::create() ``` -Corespunde codului PHP: +Observați că, pentru simplitate, în loc de `->` se folosește `::`, vezi [#Expresii]. Se vor genera aceste metode factory: ```php public function createServiceDatabase(): PDO { - return My\Database::create('root', 'secret'); + return DatabaseFactory::create(); +} + +public function createServiceRouter(): RouteList +{ + return $this->getService('routerFactory')->create(); } ``` -O metodă statică `My\Database::create()` se presupune că are o valoare de returnare definită pe care containerul DI trebuie să o cunoască. Dacă nu o are, scriem tipul în configurație: +Containerul DI trebuie să cunoască tipul serviciului creat. Dacă creăm un serviciu folosind o metodă care nu are specificat tipul returnat, trebuie să specificăm explicit acest tip în configurație: ```neon services: database: - create: My\Database::create(root, secret) + create: DatabaseFactory::create() type: PDO ``` -Nette DI vă oferă facilități de exprimare extrem de puternice pentru a scrie aproape orice. De exemplu, pentru a face [referire |#Referencing Services] la un alt serviciu și a apela metoda acestuia. Pentru simplificare, se folosește `::` în loc de `->`. + +Argumente +========= + +Transmitem argumente constructorului și metodelor într-un mod foarte similar cu cel din PHP însuși: ```neon services: - routerFactory: App\Router\Factory - router: @routerFactory::create() + database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -Corespunde codului PHP: - -```php -public function createServiceRouterFactory(): App\Router\Factory -{ - return new App\Router\Factory; -} +Pentru o mai bună lizibilitate, putem împărți argumentele pe rânduri separate. În acest caz, utilizarea virgulelor este opțională: -public function createServiceRouter(): Router -{ - return $this->getService('routerFactory')->create(); -} +```neon +services: + database: PDO( + 'mysql:host=127.0.0.1;dbname=test' + root + secret + ) ``` -Apelurile la metode pot fi înlănțuite ca în PHP: +Puteți, de asemenea, să numiți argumentele și nu trebuie să vă mai faceți griji cu privire la ordinea lor: ```neon services: - foo: FooFactory::build()::get() + database: PDO( + username: root + password: secret + dsn: 'mysql:host=127.0.0.1;dbname=test' + ) ``` -Corespunde codului PHP: +Dacă doriți să omiteți unele argumente și să folosiți valoarea lor implicită sau să injectați un serviciu folosind [autowiring |autowiring], utilizați underscore `_`: -```php -public function createServiceFoo() -{ - return FooFactory::build()->get(); -} +```neon +services: + foo: Foo(_, %appDir%) ``` +Ca argumente se pot transmite servicii, se pot utiliza parametri și multe altele, vezi [#Expresii]. + -Argumente .[#toc-arguments] -=========================== +Setup +===== -Parametrii numiți pot fi utilizați și pentru a transmite argumente: +În secțiunea `setup` definim metodele care trebuie apelate la crearea serviciului. ```neon services: - database: PDO( - 'mysql:host=127.0.0.1;dbname=test' # pozițional - username: root # numit - password: secret # numit - ) + database: + create: PDO(%dsn%, %user%, %password%) + setup: + - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) ``` -Utilizarea virgulelor este opțională atunci când argumentele sunt împărțite pe mai multe linii. +Acest lucru ar arăta astfel în PHP: -Bineînțeles, putem folosi și [alte servicii |#Referencing Services] sau [parametri |configuration#parameters] ca argumente: +```php +public function createServiceDatabase(): PDO +{ + $service = new PDO('...', '...', '...'); + $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + return $service; +} +``` + +Pe lângă apelarea metodelor, se pot transmite și valori către proprietăți. Este suportată și adăugarea unui element într-un array, care trebuie scris între ghilimele pentru a nu intra în conflict cu sintaxa NEON: ```neon services: - - Foo(@anotherService, %appDir%) + foo: + create: Foo + setup: + - $value = 123 + - '$onClick[]' = [@bar, clickHandler] ``` -Corespunde codului PHP: +Ceea ce în codul PHP ar arăta astfel: ```php -public function createService01(): Foo +public function createServiceFoo(): Foo { - return new Foo($this->getService('anotherService'), '...'); + $service = new Foo; + $service->value = 123; + $service->onClick[] = [$this->getService('bar'), 'clickHandler']; + return $service; } ``` -Dacă primul argument este [autowired |autowiring] și doriți să îl specificați pe al doilea, omiteți-l pe primul cu `_` character, for example `Foo(_, %appDir%)`. Sau, mai bine, treceți doar al doilea argument ca parametru numit, de exemplu `Foo(path: %appDir%)`. - -Nette DI și formatul NEON vă oferă facilități expresive extrem de puternice pentru a scrie aproape orice. Astfel, un argument poate fi un obiect nou creat, puteți apela metode statice, metode ale altor servicii sau chiar funcții globale folosind o notație specială: +În setup se pot apela însă și metode statice sau metode ale altor servicii. Dacă aveți nevoie să transmiteți serviciul curent ca argument, specificați-l ca `@self`: ```neon services: - analyser: My\Analyser( - FilesystemIterator(%appDir%) # creați un obiect - DateTime::createFromFormat('Y-m-d') # apelați metoda statică - @anotherService # trecerea unui alt serviciu - @http.request::getRemoteAddress() # apelarea unei alte metode de serviciu - ::getenv(NetteMode) # apelarea unei funcții globale - ) + foo: + create: Foo + setup: + - My\Helpers::initializeFoo(@self) + - @anotherService::setFoo(@self) ``` -Corespunde codului PHP: +Observați că, pentru simplitate, în loc de `->` se folosește `::`, vezi [#Expresii]. Se va genera o astfel de metodă factory: ```php -public function createServiceAnalyser(): My\Analyser +public function createServiceFoo(): Foo { - return new My\Analyser( - new FilesystemIterator('...'), - DateTime::createFromFormat('Y-m-d'), - $this->getService('anotherService'), - $this->getService('http.request')->getRemoteAddress(), - getenv('NetteMode') - ); + $service = new Foo; + My\Helpers::initializeFoo($service); + $this->getService('anotherService')->setFoo($service); + return $service; } ``` -Funcții speciale .[#toc-special-functions] ------------------------------------------- - -Puteți utiliza, de asemenea, funcții speciale în argumente pentru a transforma sau nega valori: +Expresii +======== -- `not(%arg%)` negarea -- `bool(%arg%)` cast fără pierderi în bool -- `int(%arg%)` lossless cast to int -- `float(%arg%)` lossless cast to float -- `string(%arg%)` lossless cast to string +Nette DI ne oferă expresii extrem de bogate, cu ajutorul cărora putem scrie aproape orice. În fișierele de configurație putem astfel utiliza [parametri |configuration#Parametri]: ```neon -services: - - Foo( - id: int(::getenv('ProjectId')) - productionMode: not(%debugMode%) - ) -``` - -Rescrierea fără pierderi diferă de rescrierea normală din PHP, de exemplu, folosind `(int)`, prin faptul că aruncă o excepție pentru valorile nenumerice. +# parametru +%wwwDir% -Mai multe servicii pot fi trecute ca argumente. Un tablou al tuturor serviciilor de un anumit tip (de exemplu, clasă sau interfață) este creat de funcția `typed()`. Funcția va omite serviciile care au cablarea automată dezactivată și pot fi specificate mai multe tipuri separate prin virgulă. +# valoarea parametrului sub cheie +%mailer.user% -```neon -services: - - BarsDependent( typed(Bar) ) +# parametru în interiorul șirului +'%wwwDir%/images' ``` -De asemenea, puteți transmite automat o matrice de servicii utilizând [autowiring |autowiring#Collection of Services]. - -O matrice a tuturor serviciilor cu o anumită [etichetă |#tags] este creată de funcția `tagged()`. Se pot specifica mai multe etichete separate prin virgulă. +Mai departe, putem crea obiecte, apela metode și funcții: ```neon -services: - - LoggersDependent( tagged(logger) ) -``` +# crearea obiectului +DateTime() +# apelarea metodei statice +Collator::create(%locale%) -Servicii de referință .[#toc-referencing-services] -================================================== +# apelarea funcției PHP +::getenv(DB_USER) +``` -Serviciile individuale sunt referite folosind caracterul `@` and name, so for example `@database`: +Ne putem referi la servicii fie după numele lor, fie după tip: ```neon -services: - - create: Foo(@database) - setup: - - setCacheStorage(@cache.storage) +# serviciu după nume +@database + +# serviciu după tip +@Nette\Database\Connection ``` -Corespunde codului PHP: +Putem folosi sintaxa first-class callable: .{data-version:3.2.0} -```php -public function createService01(): Foo -{ - $service = new Foo($this->getService('database')); - $service->setCacheStorage($this->getService('cache.storage')); - return $service; -} +```neon +# crearea callback-ului, echivalent cu [@user, logout] +@user::logout(...) ``` -Chiar și serviciile anonime pot fi referite folosind un callback, trebuie doar să specificați tipul lor (clasă sau interfață) în loc de numele lor. Cu toate acestea, acest lucru nu este de obicei necesar din cauza [cablării automate |autowiring]. +Putem folosi constante: ```neon -services: - - create: Foo(@Nette\Database\Connection) # sau @\PDO - setup: - - setCacheStorage(@cache.storage) +# constanta clasei +FilesystemIterator::SKIP_DOTS + +# constanta globală o obținem cu funcția PHP constant() +::constant(PHP_VERSION) ``` +Apelurile metodelor pot fi înlănțuite la fel ca în PHP. Doar pentru simplitate, în loc de `->` se folosește `::`: -Configurare .[#toc-setup] -========================= +```neon +DateTime()::format('Y-m-d') +# PHP: (new DateTime())->format('Y-m-d') -În secțiunea de configurare, enumerăm metodele care trebuie apelate la crearea serviciului: +@http.request::getUrl()::getHost() +# PHP: $this->getService('http.request')->getUrl()->getHost() +``` + +Aceste expresii le puteți utiliza oriunde, la [crearea serviciilor |#Crearea serviciului], în [#argumente], în secțiunea [#Setup] sau în [parametri |configuration#Parametri]: ```neon +parameters: + ipAddress: @http.request::getRemoteAddress() + services: database: - create: PDO(%dsn%, %user%, %password%) + create: DatabaseFactory::create( @anotherService::getDsn() ) setup: - - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) + - initialize( ::getenv('DB_USER') ) ``` -Corespunde codului PHP: -```php -public function createServiceDatabase(): PDO -{ - $service = new PDO('...', '...', '...'); - $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - return $service; -} -``` +Funcții speciale +---------------- -Proprietățile pot fi, de asemenea, setate. Adăugarea unui element la o matrice este, de asemenea, acceptată și trebuie scrisă între ghilimele pentru a nu intra în conflict cu sintaxa NEON: +În fișierele de configurație puteți utiliza aceste funcții speciale: +- `not()` negația valorii +- `bool()`, `int()`, `float()`, `string()` conversie de tip fără pierderi la tipul specificat +- `typed()` creează un array al tuturor serviciilor de tipul specificat +- `tagged()` creează un array al tuturor serviciilor cu tag-ul dat ```neon services: - foo: - create: Foo - setup: - - $value = 123 - - '$onClick[]' = [@bar, clickHandler] + - Foo( + id: int(::getenv('ProjectId')) + productionMode: not(%debugMode%) + ) ``` -Corespunde codului PHP: +Spre deosebire de conversia de tip clasică în PHP, cum ar fi de ex. `(int)`, conversia de tip fără pierderi va arunca o excepție pentru valorile non-numerice. -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - $service->value = 123; - $service->onClick[] = [$this->getService('bar'), 'clickHandler']; - return $service; -} +Funcția `typed()` creează un array al tuturor serviciilor de tipul dat (clasă sau interfață). Omite serviciile care au autowiring-ul dezactivat. Se pot specifica și mai multe tipuri separate prin virgulă. + +```neon +services: + - BarsDependent( typed(Bar) ) ``` -Cu toate acestea, metodele statice sau metodele altor servicii pot fi, de asemenea, apelate în configurare. Le transmitem serviciul real ca `@self`: +Puteți transmite array-ul de servicii de un anumit tip ca argument și automat folosind [autowiring |autowiring#Array de servicii]. +Funcția `tagged()` creează apoi un array al tuturor serviciilor cu un anumit tag. Și aici puteți specifica mai multe tag-uri separate prin virgulă. ```neon services: - foo: - create: Foo - setup: - - My\Helpers::initializeFoo(@self) - - @anotherService::setFoo(@self) + - LoggersDependent( tagged(logger) ) ``` -Corespunde codului PHP: -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - My\Helpers::initializeFoo($service); - $this->getService('anotherService')->setFoo($service); - return $service; -} +Autowiring +========== + +Cheia `autowired` permite influențarea comportamentului autowiring-ului pentru un serviciu specific. Pentru detalii, vezi [capitolul despre autowiring|autowiring]. + +```neon +services: + foo: + create: Foo + autowired: false # serviciul foo este exclus din autowiring ``` -Cablare automată .[#toc-autowiring] +Servicii lazy .{data-version:3.2.4} =================================== -Cheia autowired poate fi utilizată pentru a exclude un serviciu de la autowiring sau pentru a influența comportamentul acestuia. Pentru mai multe informații, consultați [capitolul despre autowiring |autowiring]. +Încărcarea leneșă (Lazy loading) este o tehnică care amână crearea unui serviciu până în momentul în care este efectiv necesar. În configurația globală se poate [permite crearea lazy |configuration#Servicii lazy] pentru toate serviciile simultan. Pentru servicii individuale, puteți apoi suprascrie acest comportament: ```neon services: foo: create: Foo - autowired: false # foo este eliminat din autocablarea + lazy: false ``` +Când un serviciu este definit ca lazy, la solicitarea sa din containerul DI, primim un obiect substituent special. Acesta arată și se comportă la fel ca serviciul real, dar inițializarea reală (apelarea constructorului și a setup-ului) are loc abia la primul apel al oricărei metode sau proprietăți ale sale. + +.[note] +Încărcarea leneșă poate fi utilizată numai pentru clasele definite de utilizator, nu și pentru clasele interne PHP. Necesită PHP 8.4 sau o versiune mai recentă. + -Etichete .[#toc-tags] -===================== +Tag-uri +======= -Informațiile despre utilizatori pot fi adăugate la serviciile individuale sub formă de etichete: +Tag-urile servesc la adăugarea de informații suplimentare serviciilor. Puteți adăuga unul sau mai multe tag-uri unui serviciu: ```neon services: @@ -360,7 +371,7 @@ services: - cached ``` -Etichetele pot avea, de asemenea, o valoare: +Tag-urile pot purta și valori: ```neon services: @@ -370,26 +381,26 @@ services: logger: monolog.logger.event ``` -O matrice de servicii cu anumite etichete poate fi transmisă ca argument cu ajutorul funcției `tagged()`. De asemenea, se pot specifica mai multe etichete separate prin virgulă. +Pentru a obține toate serviciile cu anumite tag-uri, puteți utiliza funcția `tagged()`: ```neon services: - LoggersDependent( tagged(logger) ) ``` -Numele serviciilor pot fi obținute din containerul DI folosind metoda `findByTag()`: +În containerul DI puteți obține numele tuturor serviciilor cu un anumit tag folosind metoda `findByTag()`: ```php $names = $container->findByTag('logger'); -// $names este o matrice care conține numele serviciului și valoarea etichetei. -// adică ['foo' => 'monolog.logger.event', ...] +// $names este un array care conține numele serviciului și valoarea tag-ului +// de ex. ['foo' => 'monolog.logger.event', ...] ``` -Modul de injectare .[#toc-inject-mode] -====================================== +Mod Inject +========== -Indicatorul `inject: true` este utilizat pentru a activa trecerea dependențelor prin intermediul variabilelor publice cu adnotarea [inject |best-practices:inject-method-attribute#Inject Attributes] și metodele [inject*() |best-practices:inject-method-attribute#inject Methods]. +Folosind flag-ul `inject: true` se activează transmiterea dependențelor prin proprietăți publice cu adnotarea [inject |best-practices:inject-method-attribute#Atribute Inject] și metodele [inject*() |best-practices:inject-method-attribute#Metode inject]. ```neon services: @@ -398,13 +409,13 @@ services: inject: true ``` -În mod implicit, `inject` este activat numai pentru prezentatori. +În mod implicit, `inject` este activat doar pentru presenteri. -Modificarea serviciilor .[#toc-modification-of-services] -======================================================== +Modificarea serviciilor +======================= -Există o serie de servicii în containerul DI care au fost adăugate prin încorporare sau prin [extensia dumneavoastră |#di-extensions]. Definițiile acestor servicii pot fi modificate în configurație. De exemplu, pentru serviciul `application.application`, care este în mod implicit un obiect `Nette\Application\Application`, putem modifica clasa: +Containerul DI conține multe servicii care au fost adăugate prin extensii încorporate sau [extensii utilizator|extensions]. Puteți modifica definițiile acestor servicii direct în configurație. De exemplu, puteți schimba clasa serviciului `application.application`, care este standard `Nette\Application\Application`, cu alta: ```neon services: @@ -413,9 +424,9 @@ services: alteration: true ``` -Steagul `alteration` este informativ și spune că nu facem decât să modificăm un serviciu existent. +Flag-ul `alteration` este informativ și indică faptul că doar modificăm un serviciu existent. -De asemenea, putem adăuga o configurare: +Putem, de asemenea, completa setup-ul: ```neon services: @@ -426,7 +437,7 @@ services: - '$onStartup[]' = [@resource, init] ``` -Atunci când rescriem un serviciu, este posibil să dorim să eliminăm argumentele, elementele de configurare sau etichetele inițiale, pentru aceasta existând `reset`: +La suprascrierea unui serviciu, putem dori să eliminăm argumentele originale, elementele setup sau tag-urile, pentru aceasta folosim `reset`: ```neon services: @@ -439,7 +450,7 @@ services: - tags ``` -Un serviciu adăugat prin extensie poate fi, de asemenea, eliminat din container: +Dacă doriți să eliminați un serviciu adăugat de o extensie, o puteți face astfel: ```neon services: diff --git a/dependency-injection/ru/@home.texy b/dependency-injection/ru/@home.texy index 5d1773d00f..495ee1282f 100644 --- a/dependency-injection/ru/@home.texy +++ b/dependency-injection/ru/@home.texy @@ -1,24 +1,21 @@ -Инъекция зависимостей -********************* +Nette DI +******** .[perex] -Dependency Injection - это паттерн проектирования, который в корне изменит ваш взгляд на код и разработку. Он открывает путь в мир чистых и устойчивых приложений. +Внедрение зависимостей (Dependency Injection) — это шаблон проектирования, который кардинально изменит ваш взгляд на код и разработку. Он откроет вам путь в мир чисто спроектированных и поддерживаемых приложений. -- [Что такое инжекция зависимостей? |introduction] +- [Что такое внедрение зависимостей? |introduction] - [Глобальное состояние и синглтоны |global-state] - [Передача зависимостей |passing-dependencies] - [Что такое DI-контейнер? |container] -- [Часто задаваемые вопросы |faq] - +- [Часто задаваемые вопросы|faq] -Nette DI --------- -Пакет `nette/di` предоставляет чрезвычайно продвинутый скомпилированный DI контейнер для PHP. +Пакет `nette/di` предоставляет чрезвычайно продвинутый компилируемый DI-контейнер для PHP. -- [Контейнер DI от Nette |nette-container] +- [Nette DI Container |nette-container] - [Конфигурация |configuration] -- [Определения сервисов |services] -- [Автоподключение |autowiring] +- [Определение сервисов |services] +- [Autowiring |autowiring] - [Генерируемые фабрики |factory] -- [Создание расширений для Nette DI |extensions] +- [Создание расширений для Nette DI|extensions] diff --git a/dependency-injection/ru/@left-menu.texy b/dependency-injection/ru/@left-menu.texy index 2080e574e0..51c371afdb 100644 --- a/dependency-injection/ru/@left-menu.texy +++ b/dependency-injection/ru/@left-menu.texy @@ -1,17 +1,17 @@ Внедрение зависимостей ********************** -- [Что такое «внедрение зависимостей»? |introduction] +- [Что такое DI? |introduction] - [Глобальное состояние и синглтоны |global-state] - [Передача зависимостей |passing-dependencies] -- [Что такое «DI-контейнер»? |container] -- [Часто задаваемые вопросы |faq] +- [Что такое DI-контейнер? |container] +- [Часто задаваемые вопросы|faq] Nette DI -------- -- [Nette DI-контейнер |nette-container] -- [Настройка |configuration] -- [Определения сервисов |services] -- [Что такое «автосвязывание» |autowiring] -- [Сгенерированные фабрики |factory] +- [Nette DI Container |nette-container] +- [Конфигурация |configuration] +- [Определение сервисов |services] +- [Autowiring |autowiring] +- [Генерируемые фабрики |factory] - [Создание расширений для Nette DI|extensions] diff --git a/dependency-injection/ru/@meta.texy b/dependency-injection/ru/@meta.texy new file mode 100644 index 0000000000..7f329adfce --- /dev/null +++ b/dependency-injection/ru/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документация Nette}} diff --git a/dependency-injection/ru/autowiring.texy b/dependency-injection/ru/autowiring.texy index 1bf1746b0c..1085b88fe0 100644 --- a/dependency-injection/ru/autowiring.texy +++ b/dependency-injection/ru/autowiring.texy @@ -1,24 +1,24 @@ -Автосвязывание -************** +Autowiring +********** .[perex] -Autowiring, или автосвязывание — это отличная функция, которая может автоматически передавать сервисы в конструктор и другие методы, так что нам совсем не нужно их писать. Это сэкономит вам много времени. +Autowiring — это отличная функция, которая умеет автоматически передавать в конструктор и другие методы требуемые сервисы, так что нам вообще не нужно их писать. Это сэкономит вам много времени. -Это позволяет нам пропустить подавляющее большинство аргументов при написании определений сервисов. Вместо: +Благодаря этому мы можем опустить подавляющее большинство аргументов при написании определений сервисов. Вместо: ```neon services: articles: Model\ArticleRepository(@database, @cache.storage) ``` -Просто напишите: +Достаточно написать: ```neon services: articles: Model\ArticleRepository ``` -Автосвязывание управляется типами, поэтому класс `ArticleRepository` должен быть определен следующим образом: +Autowiring руководствуется типами, поэтому для его работы класс `ArticleRepository` должен быть определен примерно так: ```php namespace Model; @@ -30,22 +30,22 @@ class ArticleRepository } ``` -Чтобы использовать автосвязывание, в контейнере должен быть **только один сервис** для каждого типа. Если бы их было больше, автосвязывание не знало бы, какой из них передавать, и выбрасывало бы исключение: +Чтобы можно было использовать autowiring, для каждого типа в контейнере должен быть **ровно один сервис**. Если их будет больше, autowiring не будет знать, какой из них передать, и выбросит исключение: ```neon services: mainDb: PDO(%dsn%, %user%, %password%) tempDb: PDO('sqlite::memory:') - articles: Model\ArticleRepository # ВЫБРАСЫВАЕТСЯ ИСКЛЮЧЕНИЕ, mainDb и tempDb совпадают + articles: Model\ArticleRepository # ВЫБРОСИТ ИСКЛЮЧЕНИЕ, подходят и mainDb, и tempDb ``` -Решением может быть либо обход автоподключения, либо явное указание имени сервиса (т. е. `articles: Model\ArticleRepository(@mainDb)`). Однако удобнее [отключить|#Disabled-Autowiring] автосвязывание одного сервиса, или [предпочесть|#Preferred-Autowiring] конкретный сервис. +Решением было бы либо обойти autowiring и явно указать имя сервиса (т.е. `articles: Model\ArticleRepository(@mainDb)`). Но удобнее autowiring одного из сервисов [отключить |#Отключение autowiring] или первый сервис [сделать предпочтительным |#Предпочтение autowiring]. -Отключенное автосвязывание .[#toc-disabled-autowiring] ------------------------------------------------------- +Отключение autowiring +--------------------- -Вы можете отключить автоматическое определение зависимостей сервисов с помощью параметра `autowired: no`: +Autowiring сервиса можно отключить с помощью опции `autowired: no`: ```neon services: @@ -53,28 +53,27 @@ services: tempDb: create: PDO('sqlite::memory:') - autowired: false # удаляет tempDb из автосвязывания + autowired: false # сервис tempDb исключен из autowiring - articles: Model\ArticleRepository # передает mainDb в конструктор + articles: Model\ArticleRepository # следовательно, передает mainDb в конструктор ``` -Сервис `articles` не выбрасывает исключение о том, что есть два соответствующих сервиса типа `PDO` (т. е. `mainDb` и `tempDb`), которые могут быть переданы конструктору, поскольку он видит только сервис `mainDb`. +Сервис `articles` не выбросит исключение о том, что существуют два подходящих сервиса типа `PDO` (т.е. `mainDb` и `tempDb`), которые можно передать в конструктор, потому что он видит только сервис `mainDb`. .[note] -Настройка autowiring в Nette работает иначе, чем в Symfony, где опция `autowire: false` говорит, что autowiring не должен использоваться для аргументов конструктора сервиса. -В Nette autowiring используется всегда, будь то аргументы конструктора или любого другого метода. Опция `autowired: false` говорит, что экземпляр сервиса не должен передаваться никуда с использованием autowiring. +Конфигурация autowiring в Nette работает иначе, чем в Symfony, где опция `autowire: false` говорит, что не следует использовать autowiring для аргументов конструктора данного сервиса. В Nette autowiring используется всегда, будь то для аргументов конструктора или любых других методов. Опция `autowired: false` говорит, что экземпляр данного сервиса не должен передаваться никуда с помощью autowiring. -Предпочтительное автосвязывание .[#toc-preferred-autowiring] ------------------------------------------------------------- +Предпочтение autowiring +----------------------- -Если у нас есть несколько сервисов одного типа и один из них имеет опцию `autowired`, этот сервис становится предпочтительным: +Если у нас есть несколько сервисов одного типа и у одного из них указана опция `autowired`, этот сервис становится предпочтительным: ```neon services: mainDb: create: PDO(%dsn%, %user%, %password%) - autowired: PDO # makes it preferred + autowired: PDO # становится предпочтительным tempDb: create: PDO('sqlite::memory:') @@ -82,13 +81,13 @@ services: articles: Model\ArticleRepository ``` -Сервис `articles` не выбрасывает исключение, если есть два совпадающих сервиса `PDO` (т. е. `mainDb` и `tempDb`), но использует предпочтительный сервис, т. е. `mainDb`. +Сервис `articles` не выбросит исключение о том, что существуют два подходящих сервиса типа `PDO` (т.е. `mainDb` и `tempDb`), но использует предпочтительный сервис, то есть `mainDb`. -Коллекция сервисов .[#toc-collection-of-services] -------------------------------------------------- +Массив сервисов +--------------- -Автосвязывание также может передавать массив сервисов определенного типа. Так как PHP не может нативно обозначать тип элементов массива, в дополнение к типу `array` необходимо добавить комментарий phpDoc с типом элемента, например `ClassName[]`: +Autowiring умеет передавать и массивы сервисов определенного типа. Поскольку в PHP нельзя нативно записать тип элементов массива, необходимо помимо типа `array` добавить и phpDoc-комментарий с типом элемента в формате `ClassName[]`: ```php namespace Model; @@ -103,46 +102,45 @@ class ShipManager } ``` -Затем контейнер DI автоматически передает массив сервисов, соответствующих заданному типу. При этом будут пропущены сервисы, у которых отключено автосвязывание. +DI-контейнер затем автоматически передаст массив сервисов, соответствующих данному типу. Он пропустит сервисы, у которых отключен autowiring. -Если вы не можете контролировать форму комментария phpDoc, вы можете передать массив сервисов непосредственно в конфигурации, используя [`typed()`|services#Special-Functions]. +Тип в комментарии может быть также в формате `array<int, Class>` или `list<Class>`. Если вы не можете повлиять на вид phpDoc-комментария, вы можете передать массив сервисов непосредственно в конфигурации с помощью [`typed()` |services#Специальные функции]. -Скалярные аргументы .[#toc-scalar-arguments] --------------------------------------------- +Скалярные аргументы +------------------- -Autowiring может передавать только объекты и массивы объектов. Скалярные аргументы (например, строки, числа, булевы) [записываются в конфигурации |services#Arguments]. -Альтернативой может быть создание [settings-object |best-practices:passing-settings-to-presenters], который инкапсулирует скалярное значение (или несколько значений) как объект, который затем может быть передан снова с помощью autowiring. +Autowiring умеет подставлять только объекты и массивы объектов. Скалярные аргументы (например, строки, числа, булевы значения) [запишем в конфигурации |services#Аргументы]. Альтернативой является создание [объекта настроек |best-practices:passing-settings-to-presenters], который инкапсулирует скалярное значение (или несколько значений) в виде объекта, и его затем можно снова передавать с помощью autowiring. ```php class MySettings { public function __construct( - // readonly можно использовать начиная с PHP 8.1 + // readonly можно использовать с PHP 8.1 public readonly bool $value, ) {} } ``` -Вы создаете сервис, добавляя его в конфигурацию: +Вы создадите из него сервис, добавив его в конфигурацию: ```neon services: - - MySettings('любое значение') + - MySettings('any value') ``` -Затем все классы будут запрашивать его через autowiring. +Все классы затем запросят его с помощью autowiring. -Сужение автосвязывания .[#toc-narrowing-of-autowiring] ------------------------------------------------------- +Сужение autowiring +------------------ -Для отдельных сервисов автоподключение может быть сужено до определенных классов или интерфейсов. +Для отдельных сервисов можно сузить autowiring только до определенных классов или интерфейсов. -Обычно автосвязывание передает функцию каждому параметру метода, типу которого соответствует функция. Сужение означает, что мы указываем условия, которым должны удовлетворять типы, указанные для параметров метода, чтобы им была передана функция. +Обычно autowiring передает сервис в каждый параметр метода, типу которого сервис соответствует. Сужение означает, что мы устанавливаем условия, которым должны соответствовать типы, указанные у параметров методов, чтобы им был передан сервис. -Рассмотрим пример: +Покажем это на примере: ```php class ParentClass @@ -164,42 +162,42 @@ class ChildDependent } ``` -Если бы мы зарегистрировали их все как сервисы, автосвязывание было бы невозможно: +Если бы мы зарегистрировали их все как сервисы, то autowiring завершился бы неудачей: ```neon services: parent: ParentClass child: ChildClass - parentDep: ParentDependent # ВЫБРАСЫВАЕТ ИСКЛЮЧЕНИЕ, parent и child совпадают - childDep: ChildDependent # передает сервис 'child' конструктору + parentDep: ParentDependent # ВЫБРОСИТ ИСКЛЮЧЕНИЕ, подходят сервисы parent и child + childDep: ChildDependent # autowiring передаст сервис child в конструктор ``` -Сервис `parentDep` выбрасывает исключение `Multiple services of type ParentClass found: parent, child` потому что и `parent`, и `child` помещаются в его конструктор, и автосвязывание не может принять решение о том, какой из них выбрать. +Сервис `parentDep` выбросит исключение `Multiple services of type ParentClass found: parent, child`, потому что в его конструктор подходят оба сервиса `parent` и `child`, и autowiring не может решить, какой из них выбрать. -Поэтому для сервиса `child` мы можем сузить его автосвязывание до `ChildClass`: +Поэтому для сервиса `child` мы можем сузить его autowiring до типа `ChildClass`: ```neon services: parent: ParentClass child: create: ChildClass - autowired: ChildClass # альтернатива: 'autowired: self' + autowired: ChildClass # можно также написать 'autowired: self' - parentDep: ParentDependent # ВЫБРАСЫВАЕТ ИСКЛЮЧЕНИЕ, 'child' не может быть автоподключаемым - childDep: ChildDependent # передает сервис 'child' конструктору + parentDep: ParentDependent # autowiring передаст сервис parent в конструктор + childDep: ChildDependent # autowiring передаст сервис child в конструктор ``` -Сервис `parentDep` теперь передается в конструктор сервиса `parentDep`, поскольку теперь это единственный подходящий объект. Сервис `child` больше не передается через автосвязывание. Да, функция `child` по-прежнему имеет тип `ParentClass`, но условие сужения, заданное для типа параметра, больше не применяется, т. е. больше не верно, что `ParentClass` *является супертипом* `ChildClass`. +Теперь в конструктор сервиса `parentDep` передается сервис `parent`, потому что теперь это единственный подходящий объект. Сервис `child` autowiring туда больше не передаст. Да, сервис `child` по-прежнему имеет тип `ParentClass`, но сужающее условие, заданное для типа параметра, больше не выполняется, т.е. неверно, что `ParentClass` *является супертипом* `ChildClass`. -В случае `child`, `autowired: ChildClass` можно записать как `autowired: self`, так как `self` означает текущий тип сервиса. +Для сервиса `child` можно было бы записать `autowired: ChildClass` также как `autowired: self`, поскольку `self` является псевдонимом для класса текущего сервиса. -Ключ `autowired` может включать несколько классов и интерфейсов в качестве массива: +В ключе `autowired` можно указать и несколько классов или интерфейсов в виде массива: ```neon autowired: [BarClass, FooInterface] ``` -Давайте попробуем добавить интерфейсы в пример: +Попробуем дополнить пример еще и интерфейсами: ```php interface FooInterface @@ -239,13 +237,13 @@ class ChildDependent } ``` -Если мы не ограничиваем сервис `child`, он будет соответствовать конструкторам всех классов `FooDependent`, `BarDependent`, `ParentDependent` и `ChildDependent`, а автосвязывание передаст его туда. +Если сервис `child` никак не ограничивать, он будет подходить в конструкторы всех классов `FooDependent`, `BarDependent`, `ParentDependent` и `ChildDependent`, и autowiring его туда передаст. -Однако, если мы сузим автосвязывание до `ChildClass` с помощью `autowired: ChildClass` (или `self`), автосвязывание передает его только конструктору `ChildDependent`, поскольку для него требуется аргумент типа `ChildClass` и `ChildClass` *это тип* `ChildClass`. Ни один другой тип, указанный для других параметров, не является заменой `ChildClass`, поэтому сервис не проходит. +Но если его autowiring сузить до `ChildClass` с помощью `autowired: ChildClass` (или `self`), autowiring передаст его только в конструктор `ChildDependent`, потому что он требует аргумент типа `ChildClass` и верно, что `ChildClass` *имеет тип* `ChildClass`. Никакой другой тип, указанный у других параметров, не является супертипом `ChildClass`, поэтому сервис не передается. -Если мы ограничиваем его на `ParentClass` с помощью `autowired: ParentClass`, то автосвязывание снова передаст его конструктору `ChildDependent` (потому что требуемый тип `ChildClass` является надмножеством `ParentClass`) и конструктору `ParentDependent`, так как необходимый тип `ParentClass` также соответствует. +Если его ограничить до `ParentClass` с помощью `autowired: ParentClass`, autowiring снова передаст его в конструктор `ChildDependent` (потому что требуемый `ChildClass` является супертипом `ParentClass`) и теперь также в конструктор `ParentDependent`, потому что требуемый тип `ParentClass` также подходит. -Если мы ограничиваем его на `FooInterface`, то он всё равно будет подключаться для `ParentDependent` (требуемый тип `ParentClass` является супертипом `FooInterface`) и `ChildDependent`, но дополнительно к конструктору `FooDependent`, но не `BarDependent`, так как `BarInterface` не супертип `FooInterface`. +Если его ограничить до `FooInterface`, он по-прежнему будет автовайриться в `ParentDependent` (требуемый `ParentClass` является супертипом `FooInterface`) и `ChildDependent`, но дополнительно и в конструктор `FooDependent`, однако не в `BarDependent`, поскольку `BarInterface` не является супертипом `FooInterface`. ```neon services: @@ -253,8 +251,8 @@ services: create: ChildClass autowired: FooInterface - fooDep: FooDependent # передает сервис child конструктору - barDep: BarDependent # ВЫБРАСЫВАЕТ ИСКЛЮЧЕНИЕ, ни один сервис не пройдет - parentDep: ParentDependent # передает сервис child конструктору - childDep: ChildDependent # передает сервис child конструктору + fooDep: FooDependent # autowiring передаст child в конструктор + barDep: BarDependent # ВЫБРОСИТ ИСКЛЮЧЕНИЕ, ни один сервис не подходит + parentDep: ParentDependent # autowiring передаст child в конструктор + childDep: ChildDependent # autowiring передаст child в конструктор ``` diff --git a/dependency-injection/ru/configuration.texy b/dependency-injection/ru/configuration.texy index a288072f02..bc18331de9 100644 --- a/dependency-injection/ru/configuration.texy +++ b/dependency-injection/ru/configuration.texy @@ -1,32 +1,33 @@ -Настройка DI-контейнера -*********************** +Конфигурация DI-контейнера +************************** .[perex] -Обзор вариантов конфигурации контейнера Nette DI. +Обзор опций конфигурации для DI-контейнера Nette. -Конфигурационный файл -===================== +Файл конфигурации +================= -Контейнером Nette DI легко управлять с помощью конфигурационных файлов. Обычно они записываются в формате [NEON format|neon:format]. Мы рекомендуем использовать редактирования таких файлов [редакторы с поддержкой|best-practices:editors-and-tools#IDE-Editor] этого формата. +DI-контейнер Nette легко управляется с помощью файлов конфигурации. Они обычно записываются в [формате NEON |neon:format]. Для редактирования рекомендуем [редакторы с поддержкой |best-practices:editors-and-tools#IDE редактор] этого формата. <pre> -"decorator .[prism-token prism-atrule]":[#Decorator]: "Decorator .[prism-token prism-comment]"<br> -"di .[prism-token prism-atrule]":[#DI]: "DI Container .[prism-token prism-comment]"<br> -"extensions .[prism-token prism-atrule]":[#Extensions]: "Install additional DI extensions .[prism-token prism-comment]"<br> -"includes .[prism-token prism-atrule]":[#Including files]: "Including files .[prism-token prism-comment]"<br> -"parameters .[prism-token prism-atrule]":[#Parameters]: "Parameters .[prism-token prism-comment]"<br> -"search .[prism-token prism-atrule]":[#Search]: "Automatic service registration .[prism-token prism-comment]"<br> -"services .[prism-token prism-atrule]":[services]: "Services .[prism-token prism-comment]" +"decorator .[prism-token prism-atrule]":[#decorator]: "Декоратор .[prism-token prism-comment]"<br> +"di .[prism-token prism-atrule]":[#DI]: "DI-контейнер .[prism-token prism-comment]"<br> +"extensions .[prism-token prism-atrule]":[#Расширения]: "Установка других DI-расширений .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[#Включение файлов]: "Включение файлов .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[#Параметры]: "Параметры .[prism-token prism-comment]"<br> +"search .[prism-token prism-atrule]":[#Search]: "Автоматическая регистрация сервисов .[prism-token prism-comment]"<br> +"services .[prism-token prism-atrule]":[services]: "Сервисы .[prism-token prism-comment]" </pre> -Чтобы записать строку, содержащую символ `%`, вы должны экранировать его удвоением до `%%`. .[note] +.[note] +Чтобы записать строку, содержащую символ `%`, необходимо экранировать его удвоением до `%%`. -Параметры .[#toc-parameters] -============================ +Параметры +========= -Можно задать параметры, которые затем могут быть использованы в качестве части определения сервиса. Это может помочь разделить значения, которые вы хотите изменять более регулярно: +В конфигурации можно определить параметры, которые затем можно использовать как часть определений сервисов. Тем самым можно сделать конфигурацию более наглядной или объединить и выделить значения, которые будут меняться. ```neon parameters: @@ -35,9 +36,9 @@ parameters: password: secret ``` -В любом конфигурационном файле можно ссылаться на параметр `foo` через `%foo%` в другом месте. Они также могут использоваться внутри строк, таких как `'%wwwDir%/images'`. +На параметр `dsn` можно сослаться где угодно в конфигурации записью `%dsn%`. Параметры можно использовать и внутри строк, например `'%wwwDir%/images'`. -Параметры не должны быть только строками, они могут быть также значениями массива: +Параметры не обязательно должны быть только строками или числами, они также могут содержать массивы: ```neon parameters: @@ -48,32 +49,32 @@ parameters: languages: [cs, en, de] ``` -Можно ссылаться на отдельный ключ так: `%mailer.user%`. +На конкретный ключ можно сослаться как `%mailer.user%`. -Если вам нужно получить значение любого параметра в вашем коде, например, в классе, то передайте его этому классу. Например, в конструкторе. Не существует глобального объекта конфигурации, который мог бы запрашивать значения параметров. Это противоречит принципу внедрения зависимостей. +Если вам нужно в вашем коде, например, в классе, узнать значение какого-либо параметра, передайте его в этот класс. Например, в конструкторе. Не существует никакого глобального объекта, представляющего конфигурацию, у которого классы запрашивали бы значения параметров. Это было бы нарушением принципа dependency injection. -Сервисы .[#toc-services] -======================== +Сервисы +======= -См. [отдельную главу|services]. +См. [отдельную главу |services]. -Декоратор .[#toc-decorator] -=========================== +Decorator +========= -Как массово редактировать все сервисы определенного типа? Нужно вызвать определенный метод для всех презентеров, наследующих от определенного общего предка? Вот откуда берется декоратор: +Как массово изменить все сервисы определенного типа? Например, вызвать определенный метод у всех презентеров, которые наследуются от конкретного общего предка? Для этого существует decorator. ```neon decorator: - # Для всех сервисов, являющихся экземплярами этого класса или интерфейса - App\Presenters\BasePresenter: + # для всех сервисов, являющихся экземплярами этого класса или интерфейса + App\Presentation\BasePresenter: setup: - - setProjectId(10) # вызываем этот метод - - $absoluteUrls = true # и задаем переменную + - setProjectId(10) # вызовите этот метод + - $absoluteUrls = true # и установите переменную ``` -Декоратор также можно использовать для установки [тегов|services#Tags] или включения [режима внедрения|services#Inject-Mode]. +Decorator также можно использовать для установки [тегов |services#Теги] или включения режима [inject |services#Режим Inject]. ```neon decorator: @@ -86,67 +87,79 @@ decorator: DI === -Технические настройки контейнера DI: +Технические настройки DI-контейнера. ```neon di: - # отображать DIC в панели отладки Tracy? + # показать DI-контейнер в Tracy Bar? debugger: ... # (bool) по умолчанию true - # типы параметров, которые не нужно монтировать автоматически + # типы параметров, которые никогда не следует автовайрить excluded: ... # (string[]) - # класс, от которого наследуется контейнер DI + # разрешить ленивое создание сервисов? + lazy: ... # (bool) по умолчанию false + + # класс, от которого наследуется DI-контейнер parentClass: ... # (string) по умолчанию Nette\DI\Container ``` -Экспорт метаданных .[#toc-metadata-export] ------------------------------------------- +Ленивые сервисы .{data-version:3.2.4} +------------------------------------- + +Настройка `lazy: true` активирует ленивое (отложенное) создание сервисов. Это означает, что сервисы не создаются в момент, когда мы запрашиваем их из DI-контейнера, а только в момент их первого использования. Это может ускорить запуск приложения и снизить потребление памяти, поскольку создаются только те сервисы, которые действительно необходимы в данном запросе. + +Для конкретного сервиса ленивое создание можно [изменить |services#Ленивые сервисы]. + +.[note] +Ленивые объекты можно использовать только для пользовательских классов, а не для внутренних классов PHP. Требуется PHP 8.4 или новее. -Класс контейнера DI также содержит множество метаданных. Вы можете уменьшить его, сократив экспорт метаданных. + +Экспорт метаданных +------------------ + +Класс DI-контейнера также содержит много метаданных. Вы можете уменьшить его размер, сократив экспорт метаданных. ```neon di: export: - # экспортировать параметры + # экспортировать параметры? parameters: false # (bool) по умолчанию true - # экспортировать теги + # экспортировать теги и какие? tags: # (string[]|bool) по умолчанию все - event.subscriber - # экспортировать данные для автоматического подключения + # экспортировать данные для autowiring и какие? types: # (string[]|bool) по умолчанию все - Nette\Database\Connection - Symfony\Component\Console\Application ``` -Если вы не используете массив `$container->parameters`, можно отключить экспорт параметров. Кроме того, вы можете экспортировать только те теги, через которые вы получаете сервисы, используя метод `$container->findByTag(...)`. -Если вы не вызываете этот метод вовсе, можно полностью отключить экспорт тегов, указав значение `false`. +Если вы не используете массив `$container->getParameters()`, вы можете отключить экспорт параметров. Далее вы можете экспортировать только те теги, по которым вы получаете сервисы методом `$container->findByTag(...)`. Если вы вообще не вызываете этот метод, вы можете полностью отключить экспорт тегов с помощью `false`. -Вы можете значительно уменьшить метаданные для автоматического подключения, указав классы, которые вы используете в качестве параметра в методе `$container->getByType()`. -И снова, если вы не вызывайте этот метод вовсе (или только в [application:bootstrap] для получения `Nette\Application\Application`), можно полностью отключить экспорт, указав значение `false`. +Вы можете значительно сократить метаданные для [autowiring |autowiring], указав классы, которые вы используете в качестве параметра метода `$container->getByType()`. И снова, если вы вообще не вызываете этот метод (или только в [bootstrap |application:bootstrapping] для получения `Nette\Application\Application`), вы можете полностью отключить экспорт с помощью `false`. -Расширения .[#toc-extensions] -============================= +Расширения +========== -Регистрация других расширений DI. Таким образом, мы добавляем, например, DI расширение `Dibi\Bridges\Nette\DibiExtension22` под именем `dibi`: +Регистрация дополнительных DI-расширений. Таким образом мы добавим, например, DI-расширение `Dibi\Bridges\Nette\DibiExtension22` под именем `dibi` ```neon extensions: dibi: Dibi\Bridges\Nette\DibiExtension22 ``` -Затем мы настраиваем его в секции, которая также называется `dibi`: +Затем мы конфигурируем его в секции `dibi`: ```neon dibi: host: localhost ``` -Вы также можете добавить класс расширения с параметрами: +В качестве расширения можно добавить и класс, у которого есть параметры: ```neon extensions: @@ -154,10 +167,10 @@ extensions: ``` -Включаемые файлы .[#toc-including-files] -======================================== +Включение файлов +================ -Дополнительные файлы конфигурации могут быть вставлены в секции `includes`: +Другие файлы конфигурации можно включить в секции `includes`: ```neon includes: @@ -166,7 +179,7 @@ includes: - presenters.neon ``` -Название `parameters.php` не является опечаткой, конфигурация также может быть записана в PHP-файле, который возвращает её в виде массива: +Имя `parameters.php` — это не опечатка, конфигурация может быть записана и в PHP-файле, который вернет ее как массив: ```php <?php @@ -179,32 +192,27 @@ return [ ]; ``` -Если в конфигурационных файлах появляются элементы с одинаковыми ключами, они будут [перезаписаны или объединены |#Merging] в случае с массивами. Последующий включенный файл имеет более высокий приоритет, чем предыдущий. Файл, в котором указана секция `includes`, имеет более высокий приоритет, чем файлы, включенные в него. +Если в файлах конфигурации появляются элементы с одинаковыми ключами, они будут перезаписаны или, в случае [массивов, объединены |#Слияние]. Позже включенный файл имеет более высокий приоритет, чем предыдущий. Файл, в котором указана секция `includes`, имеет более высокий приоритет, чем включенные в нем файлы. -Поиск .[#toc-search] -==================== +Search +====== -Автоматическое добавление сервисов в контейнер DI делает работу чрезвычайно приятной. Nette автоматически добавляет презентеры в контейнер, но вы можете легко добавить любые другие классы. +Автоматическое добавление сервисов в DI-контейнер чрезвычайно упрощает работу. Nette автоматически добавляет в контейнер презентеры, но можно легко добавлять и любые другие классы. -Просто укажите, в каких каталогах (и подкаталогах) следует искать классы: +Достаточно указать, в каких каталогах (и подкаталогах) следует искать классы: ```neon search: - # вы сами выбираете названия секций - myForms: - in: %appDir%/Forms - - model: - in: %appDir%/Model + - in: %appDir%/Forms + - in: %appDir%/Model ``` -Обычно, однако, мы не хотим добавлять все классы и интерфейсы, поэтому мы можем отфильтровать их: +Обычно, однако, мы не хотим добавлять абсолютно все классы и интерфейсы, поэтому их можно отфильтровать: ```neon search: - myForms: - in: %appDir%/Forms + - in: %appDir%/Forms # фильтрация по имени файла (string|string[]) files: @@ -215,48 +223,49 @@ search: - *Factory ``` -Или мы можем выбрать классы, которые наследуют или реализуют хотя бы один из следующих классов: +Или мы можем выбирать классы, которые наследуют или реализуют хотя бы один из указанных классов: ```neon search: - myForms: + - in: %appDir% extends: - App\*Form implements: - App\*FormInterface ``` -Вы также можете определить негативные правила, т. е. маски имён классов или предков, и если они соответствуют требованиям, сервис не будет добавлен в контейнер DI: +Можно определить и исключающие правила, т.е. маски имени класса или наследственных предков, которым если соответствует, сервис в DI-контейнер не добавляется: ```neon search: - myForms: + - in: %appDir% exclude: + files: ... classes: ... extends: ... implements: ... ``` -Для дополнительных сервисов можно определить теги: +Всем сервисам можно установить теги: ```neon search: - myForms: + - in: %appDir% tags: ... ``` -Объединение .[#toc-merging] -=========================== +Слияние +======= -Если элементы с одинаковыми ключами появляются в нескольких конфигурационных файлах, они будут перезаписаны или объединены в случае массивов. Более поздний включенный файл имеет более высокий приоритет. +Если в нескольких файлах конфигурации появляются элементы с одинаковыми ключами, они будут перезаписаны или, в случае массивов, объединены. Позже включенный файл имеет более высокий приоритет, чем предыдущий. <table class=table> <tr> <th width=33%>config1.neon</th> <th width=33%>config2.neon</th> - <th>result</th> + <th>результат</th> </tr> <tr> <td> @@ -283,13 +292,13 @@ items: </tr> </table> -Чтобы предотвратить объединение определенного массива, используйте восклицательный знак сразу после имени массива: +Для массивов можно предотвратить слияние, указав восклицательный знак после имени ключа: <table class=table> <tr> <th width=33%>config1.neon</th> <th width=33%>config2.neon</th> - <th>result</th> + <th>результат</th> </tr> <tr> <td> @@ -313,3 +322,5 @@ items: </td> </tr> </table> + +{{maintitle: Конфигурация Dependency Injection}} diff --git a/dependency-injection/ru/container.texy b/dependency-injection/ru/container.texy index 96f3a402e1..0b734735ee 100644 --- a/dependency-injection/ru/container.texy +++ b/dependency-injection/ru/container.texy @@ -1,16 +1,16 @@ -Что такое «DI-контейнер»? -************************* +Что такое DI-контейнер? +*********************** .[perex] -Контейнер внедрения зависимостей (DIC) — это класс, который может инстанцировать и конфигурировать объекты. +Dependency Injection контейнер (DIC) — это класс, который умеет инстанцировать и конфигурировать объекты. -Это может вас удивить, но во многих случаях вам не нужен контейнер для внедрения зависимостей, чтобы воспользоваться преимуществами внедрения зависимостей (сокращенно DI). В конце концов, даже в [предыдущей главе|introduction] мы показывали конкретные примеры DI, и никакой контейнер не был нужен. +Возможно, вас это удивит, но во многих случаях вам не нужен dependency injection контейнер, чтобы использовать преимущества dependency injection (кратко DI). Ведь даже во [вводной главе|introduction] мы на конкретных примерах показали DI, и никакой контейнер не был нужен. -Однако если вам нужно управлять большим количеством различных объектов с множеством зависимостей, контейнер внедрения зависимостей будет действительно полезен. Возможно, это относится к веб-приложениям, построенным на фреймворке. +Однако, если вам нужно управлять большим количеством различных объектов с множеством зависимостей, dependency injection container будет действительно полезен. Что, например, имеет место в веб-приложениях, построенных на фреймворке. -В предыдущей главе мы познакомились с классами `Article` и `UserController`. Оба они имеют некоторые зависимости, а именно базу данных и фабрику `ArticleFactory`. И для этих классов мы сейчас создадим контейнер. Конечно, для такого простого примера не имеет смысла иметь контейнер. Но мы создадим его, чтобы показать, как он выглядит и работает. +В предыдущей главе мы представили классы `Article` и `UserController`. Обе имеют некоторые зависимости, а именно базу данных и фабрику `ArticleFactory`. И для этих классов мы теперь создадим контейнер. Конечно, для такого простого примера нет смысла иметь контейнер. Но мы создадим его, чтобы показать, как он выглядит и работает. -Вот простой жестко закодированный контейнер для приведенного выше примера: +Вот простой жестко закодированный контейнер для приведенного примера: ```php class Container @@ -32,16 +32,16 @@ class Container } ``` -Его использование будет выглядеть так: +Использование выглядело бы следующим образом: ```php $container = new Container; $controller = $container->createUserController(); ``` -Мы просто запрашиваем объект у контейнера, и нам больше не нужно ничего знать о том, как его создать или каковы его зависимости; контейнер знает всё это. Зависимости вводятся контейнером автоматически. В этом его сила. +Мы просто запрашиваем у контейнера объект и больше не должны ничего знать о том, как его создать и какие у него зависимости; все это знает контейнер. Зависимости внедряются контейнером автоматически. В этом его сила. -До сих пор в контейнере всё было жестко закодировано. Поэтому мы сделаем следующий шаг и добавим параметры, чтобы сделать контейнер действительно полезным: +Контейнер пока что имеет все данные, записанные жестко. Сделаем следующий шаг и добавим параметры, чтобы контейнер стал действительно полезным: ```php class Container @@ -70,9 +70,9 @@ $container = new Container([ ]); ``` -Внимательные читатели, возможно, заметили проблему. Каждый раз, когда я получаю объект `UserController`, также создается новый экземпляр `ArticleFactory` и база данных. Мы этого точно не хотим. +Проницательные читатели, возможно, заметили некоторую проблему. Каждый раз, когда я получаю объект `UserController`, также создается новый экземпляр `ArticleFactory` и базы данных. Этого мы определенно не хотим. -Поэтому мы добавляем метод `getService()`, который будет возвращать одни и те же экземпляры снова и снова: +Поэтому добавим метод `getService()`, который будет возвращать одни и те же экземпляры: ```php class Container @@ -87,7 +87,7 @@ class Container public function getService(string $name): object { if (!isset($this->services[$name])) { - // getService('Database') вызывает createDatabase() + // getService('Database') вызовет createDatabase() $method = 'create' . $name; $this->services[$name] = $this->$method(); } @@ -98,9 +98,9 @@ class Container } ``` -В первом вызове, например, `$container->getService('database')` будет создаваться объект базы данных, который он будет хранить в массиве `$services` и возвращать непосредственно на следующем вызове. +При первом вызове, например, `$container->getService('Database')`, он запросит у `createDatabase()` создание объекта базы данных, который сохранит в массиве `$services`, и при следующем вызове вернет его напрямую. -Также мы модифицируем остальную часть контейнера для использования `getService()': +Изменим и остальную часть контейнера, чтобы он использовал `getService()`: ```php class Container @@ -119,9 +119,9 @@ class Container } ``` -Кстати, термин сервис относится к любому объекту, управляемому контейнером. Отсюда и название метода `getService()`. +Кстати, термином сервис обозначается любой объект, управляемый контейнером. Поэтому и название метода `getService()`. -Мы имеем полностью функциональный контейнер DI! И мы можем использовать его. +Готово. У нас есть полнофункциональный DI-контейнер! И мы можем его использовать: ```php $container = new Container([ @@ -134,6 +134,9 @@ $controller = $container->getService('UserController'); $database = $container->getService('Database'); ``` -Как видите, написать DIC не сложно. Примечательно, что сами объекты не знают, что контейнер их создает. Таким образом, можно создать любой объект в PHP таким образом, не влияя на исходный код. +Как видите, написать DIC несложно. Стоит напомнить, что сами объекты не знают, что их создает какой-то контейнер. Таким образом, можно таким образом создавать любой объект в PHP без вмешательства в его исходный код. -Ручное создание и поддержание класса контейнеров может стать кошмаром довольно быстро. Поэтому в следующей главе мы расскажем о [Nette DI-контейнере|nette-container], который может генерировать и обновлять себя практически автоматически. +Ручное создание и поддержка класса контейнера может довольно быстро стать кошмаром. Поэтому в следующей главе мы поговорим о [Nette DI Container|nette-container], который умеет генерироваться и обновляться почти сам. + + +{{maintitle: Что такое Dependency Injection контейнер?}} diff --git a/dependency-injection/ru/extensions.texy b/dependency-injection/ru/extensions.texy index fea62eaef2..850b6672ca 100644 --- a/dependency-injection/ru/extensions.texy +++ b/dependency-injection/ru/extensions.texy @@ -2,16 +2,16 @@ ******************************** .[perex] -Создание контейнера DI в дополнение к файлам конфигурации также влияет на так называемые *расширения*. Мы активируем их в файле конфигурации в секции `extensions`. +На генерацию DI-контейнера, помимо файлов конфигурации, влияют так называемые *расширения*. Мы активируем их в файле конфигурации в секции `extensions`. -Так мы добавляем расширение, представленное классом `BlogExtension` с именем `blog`: +Таким образом мы добавим расширение, представленное классом `BlogExtension`, под именем `blog`: ```neon extensions: blog: BlogExtension ``` -Каждое расширение компилятора наследует от [api:Nette\DI\CompilerExtension] и может реализовать следующие методы, которые вызываются при компиляции DI: +Каждое расширение компилятора наследуется от [api:Nette\DI\CompilerExtension] и может реализовывать следующие методы, которые последовательно вызываются во время сборки DI-контейнера: 1. getConfigSchema() 2. loadConfiguration() @@ -22,18 +22,18 @@ extensions: getConfigSchema() .[method] =========================== -Этот метод вызывается первым. Он определяет схему, используемую для проверки параметров конфигурации. +Этот метод вызывается первым. Он определяет схему для валидации конфигурационных параметров. -Расширения настроены в секции, имя которой совпадает с именем, под которым добавлено расширение, например, `blog`. +Расширение конфигурируется в секции, имя которой совпадает с тем, под которым было добавлено расширение, то есть `blog`: ```neon # то же имя, что и у расширения blog: postsPerPage: 10 - comments: false + allowComments: false ``` -Мы определим схему, описывающую все параметры конфигурации, включая их типы, принятые значения и, возможно, значения по умолчанию: +Создадим схему, описывающую все опции конфигурации, включая их типы, допустимые значения и, возможно, значения по умолчанию: ```php use Nette\Schema\Expect; @@ -50,9 +50,9 @@ class BlogExtension extends Nette\DI\CompilerExtension } ``` -См. [Schema |schema:] для получения информации. Кроме того, можно указать, какие опции могут быть [динамическими |application:bootstrap#Dynamic-Parameters] с помощью `dynamic()', например `Expect::int()->dynamic()`. +Документацию можно найти на странице [Schema |schema:]. Кроме того, можно указать, какие опции могут быть [динамическими |application:bootstrapping#Динамические параметры] с помощью `dynamic()`, например, `Expect::int()->dynamic()`. -Мы получаем доступ к конфигурации через `$this->config`, который является объектом `stdClass`: +К конфигурации мы получаем доступ через переменную `$this->config`, которая является объектом `stdClass`: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -71,7 +71,7 @@ class BlogExtension extends Nette\DI\CompilerExtension loadConfiguration() .[method] ============================= -Этот метод используется для добавления сервисов в контейнер. Это делается с помощью [api:Nette\DI\ContainerBuilder]: +Используется для добавления сервисов в контейнер. Для этого служит [api:Nette\DI\ContainerBuilder]: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -80,25 +80,25 @@ class BlogExtension extends Nette\DI\CompilerExtension { $builder = $this->getContainerBuilder(); $builder->addDefinition($this->prefix('articles')) - ->setFactory(App\Model\HomepageArticles::class, ['@connection']) // or setCreator() + ->setFactory(App\Model\HomepageArticles::class, ['@connection']) // или setCreator() ->addSetup('setLogger', ['@logger']); } } ``` -Конвенция предусматривает префиксирование сервисов, добавленных расширением с его именем, чтобы не возникали конфликтов имен. Это делается с помощью `prefix()', так что если расширение называется 'blog', то служба будет называться `blog.articles`. +Конвенция заключается в том, чтобы префиксировать сервисы, добавленные расширением, его именем, чтобы избежать конфликтов имен. Это делает метод `prefix()`, так что если расширение называется `blog`, сервис будет носить имя `blog.articles`. -Если нам нужно переименовать сервис, мы можем создать псевдоним с его первоначальным именем, чтобы сохранить обратную совместимость. Точно так же делает Nette для `routing.router`, который также доступен под ранним названием `router`. +Если нам нужно переименовать сервис, мы можем для сохранения обратной совместимости создать псевдоним с исходным именем. Аналогично Nette делает, например, для сервиса `routing.router`, который доступен и под прежним именем `router`. ```php $builder->addAlias('router', 'routing.router'); ``` -Получение сервисов из файла .[#toc-retrieve-services-from-a-file] ------------------------------------------------------------------ +Загрузка сервисов из файла +-------------------------- -Мы можем создавать сервисы с помощью API ContainerBuilder, но также можем добавить их через знакомый файл конфигурации NEON и его секцию `services`. Префикс `@extension` представляет текущее расширение. +Сервисы можно создавать не только с помощью API класса ContainerBuilder, но и знакомой записью, используемой в файле конфигурации NEON в секции services. Префикс `@extension` представляет текущее расширение. ```neon services: @@ -112,7 +112,7 @@ services: create: MyBlog\Components\ArticlesList(@extension.articles) ``` -Мы добавим сервисы таким образом: +Сервисы загрузим: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -121,7 +121,7 @@ class BlogExtension extends Nette\DI\CompilerExtension { $builder = $this->getContainerBuilder(); - // загружаем файл конфигурации для расширения + // загрузка файла конфигурации для расширения $this->compiler->loadDefinitionsFromConfig( $this->loadFromFile(__DIR__ . '/blog.neon')['services'], ); @@ -133,7 +133,7 @@ class BlogExtension extends Nette\DI\CompilerExtension beforeCompile() .[method] ========================= -Метод вызывается, когда контейнер содержит все сервисы, добавленные отдельными расширениями в методах `loadConfiguration`, а также файлы конфигурации пользователя. На этом этапе сборки мы можем изменять определения сервиса или добавлять ссылки между ними. Для поиска сервисов по тегам можно использовать метод `findByTag()`, или метод `findByType()` для поиска по классу или интерфейсу. +Метод вызывается в момент, когда контейнер содержит все сервисы, добавленные отдельными расширениями в методах `loadConfiguration`, а также пользовательскими файлами конфигурации. На этом этапе сборки мы можем изменять определения сервисов или дополнять связи между ними. Для поиска сервисов в контейнере по тегам можно использовать метод `findByTag()`, по классу или интерфейсу — метод `findByType()`. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -153,7 +153,7 @@ class BlogExtension extends Nette\DI\CompilerExtension afterCompile() .[method] ======================== -На этой стадии класс контейнера уже генерируется как объект [ClassType |php-generator:#Classes], содержит все методы, которые создаются сервисом, и готов к кэшированию как файл PHP. В данный момент мы можем редактировать код класса. +На этом этапе класс контейнера уже сгенерирован в виде объекта [ClassType |php-generator:#Классы], содержит все методы, которые создают сервисы, и готов к записи в кеш. Результирующий код класса мы можем на этом этапе еще изменить. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -167,12 +167,12 @@ class BlogExtension extends Nette\DI\CompilerExtension ``` -$initialization .[wiki-method] -============================== +$initialization .[method] +========================= -Configurator вызывается кодом инициализации после [создания контейнера |application:bootstrap#index-php], который создается путем записи в объект `$this->initialization` с помощью [метода addBody() |php-generator:#method-and-function-body]. +Класс Configurator после [создания контейнера |application:bootstrapping#index.php] вызывает инициализационный код, который создается записью в объект `$this->initialization` с помощью [метода addBody() |php-generator:#Тела методов и функций]. -Мы покажем пример того, как запустить сессию или сервисы, которые имеют тег `run` с помощью кода инициализации: +Покажем пример, как, например, инициализационным кодом запустить сессию или запустить сервисы, имеющие тег `run`: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -184,7 +184,7 @@ class BlogExtension extends Nette\DI\CompilerExtension $this->initialization->addBody('$this->getService("session")->start()'); } - // сервисы с тегом 'run' должны быть созданы после того, как контейнер будет инстанцирован + // сервисы с тегом run должны быть созданы после инстанцирования контейнера $builder = $this->getContainerBuilder(); foreach ($builder->findByTag('run') as $name => $foo) { $this->initialization->addBody('$this->getService(?);', [$name]); diff --git a/dependency-injection/ru/factory.texy b/dependency-injection/ru/factory.texy index 315896a1e7..80938ff1d6 100644 --- a/dependency-injection/ru/factory.texy +++ b/dependency-injection/ru/factory.texy @@ -1,12 +1,12 @@ -Сгенерированные фабрики -*********************** +Генерируемые фабрики +******************** .[perex] -Nette DI может автоматически генерировать код фабрик на основе интерфейса, что избавляет вас от написания кода. +Nette DI умеет автоматически генерировать код фабрик на основе интерфейсов, что экономит вам написание кода. -Фабрика - это класс, который создает и настраивает объекты. Поэтому он также передает им их зависимости. Пожалуйста, не путайте с паттерном проектирования *factory method*, который описывает особый способ использования фабрик и не относится к данной теме. +Фабрика — это класс, который производит и конфигурирует объекты. Следовательно, он передает им и их зависимости. Пожалуйста, не путайте с паттерном проектирования *factory method*, который описывает специфический способ использования фабрик и не связан с этой темой. -Мы показали, как выглядит такая фабрика, во [вводной главе |introduction#factory]: +Как выглядит такая фабрика, мы показали во [вводной главе |introduction#Фабрика]: ```php class ArticleFactory @@ -23,7 +23,7 @@ class ArticleFactory } ``` -Всё, что вам нужно сделать, это создать интерфейс, а Nette DI сгенерирует его реализацию. Интерфейс должен иметь ровно один метод с именем `create` и объявлять возвращаемый тип: +Nette DI умеет автоматически генерировать код фабрик. Все, что вам нужно сделать, — это создать интерфейс, и Nette DI сгенерирует реализацию. Интерфейс должен иметь ровно один метод с именем `create` и объявлять возвращаемый тип: ```php interface ArticleFactory @@ -32,7 +32,7 @@ interface ArticleFactory } ``` -Итак, фабрика `ArticleFactory` имеет метод `create`, который создает объекты `Article`. Класс `Article` может выглядеть, например, следующим образом: +То есть фабрика `ArticleFactory` имеет метод `create`, который создает объекты `Article`. Класс `Article` может выглядеть, например, следующим образом: ```php class Article @@ -44,16 +44,16 @@ class Article } ``` -Добавьте фабрику в файл конфигурации: +Фабрику добавим в файл конфигурации: ```neon services: - ArticleFactory ``` -Nette DI создаст соответствующую реализацию фабрики. +Nette DI сгенерирует соответствующую реализацию фабрики. -Таким образом, в коде, использующем фабрику, мы запрашиваем объект по интерфейсу, а Nette DI использует сгенерированную реализацию: +В коде, который использует фабрику, мы запросим объект по интерфейсу, и Nette DI использует сгенерированную реализацию: ```php class UserController @@ -65,17 +65,17 @@ class UserController public function foo() { - // let the factory create an object + // позволяем фабрике создать объект $article = $this->articleFactory->create(); } } ``` -Параметризированная фабрика .[#toc-parameterized-factory] -========================================================= +Параметризованная фабрика +========================= -Метод фабрики `create` может принимать параметры, которые он затем передает конструктору. Например, давайте добавим ID автора статьи в класс `Article`: +Фабричный метод `create` может принимать параметры, которые затем передаст в конструктор. Дополним, например, класс `Article` ID автора статьи: ```php class Article @@ -88,7 +88,7 @@ class Article } ``` -Мы также добавим параметр в фабрику: +Параметр добавим также в фабрику: ```php interface ArticleFactory @@ -97,13 +97,13 @@ interface ArticleFactory } ``` -Поскольку параметр в конструкторе и параметр в фабрике имеют одинаковое имя, Nette DI передаст их автоматически. +Благодаря тому, что параметр в конструкторе и параметр в фабрике называются одинаково, Nette DI их совершенно автоматически передаст. -Расширенное определение .[#toc-advanced-definition] -=================================================== +Расширенное определение +======================= -Определение также может быть записано в многострочной форме с помощью ключа `implement`: +Определение можно записать и в многострочном виде с использованием ключа `implement`: ```neon services: @@ -111,9 +111,9 @@ services: implement: ArticleFactory ``` -При написании таким удлиненным способом можно предоставить дополнительные аргументы для конструктора в ключе `arguments` и дополнительную конфигурацию с помощью `setup`, как и для обычных сервисов. +При записи этим более длинным способом можно указать дополнительные аргументы для конструктора в ключе `arguments` и дополнительную конфигурацию с помощью `setup`, так же, как у обычных сервисов. -Пример: Если бы метод `create()` не принимал параметр `$authorId`, мы могли бы указать в конфигурации фиксированное значение, которое передавалось бы в конструктор `Article`: +Пример: если бы метод `create()` не принимал параметр `$authorId`, мы могли бы указать фиксированное значение в конфигурации, которое передавалось бы в конструктор `Article`: ```neon services: @@ -123,7 +123,7 @@ services: authorId: 123 ``` -Или, наоборот, если бы `create()` принимал параметр `$authorId`, но он не был частью конструктора и был передан методом `Article::setAuthorId()`, мы бы обратились к нему в секции `setup`: +Или наоборот, если бы `create()` параметр `$authorId` принимал, но он не был бы частью конструктора и передавался бы методом `Article::setAuthorId()`, мы бы сослались на него в секции `setup`: ```neon services: @@ -134,15 +134,14 @@ services: ``` -Аксессор .[#toc-accessor] -========================= +Accessor +======== -Помимо фабрик, Nette также может генерировать так называемые аксессоры. Это объекты с методом `get()`, которы возвращает конкретный сервис из DI-контейнера. Повторные вызовы `get()` по-прежнему возвращают один и тот же экземпляр. +Nette умеет кроме фабрик генерировать и так называемые accessory. Это объекты с методом `get()`, который возвращает определенный сервис из DI-контейнера. Повторный вызов `get()` возвращает все тот же экземпляр. -Аксессор обеспечивает ленивую загрузку зависимостей. Пусть у нас есть класс, который записывает ошибки в специальную базу данных. Если бы в этом классе соединение с базой данных передавалось конструктором как зависимость, то соединение приходилось бы создавать всегда, хотя на практике ошибка возникает очень редко, и поэтому соединение обычно оставалось бы неиспользованным. -Вместо этого класс передает метод доступа, и только при вызове его `get()` создается объект базы данных: +Accessor предоставляют зависимостям lazy-loading. Представим класс, который записывает ошибки в специальную базу данных. Если бы этот класс получал подключение к базе данных как зависимость через конструктор, подключение всегда должно было бы создаваться, хотя на практике ошибка возникает лишь изредка, и, следовательно, в большинстве случаев соединение оставалось бы неиспользованным. Вместо этого класс передаст себе accessor, и только когда будет вызван его `get()`, произойдет создание объекта базы данных: -Как создать аксессор? Просто напишите интерфейс, и Nette DI сгенерирует реализацию. Интерфейс должен иметь ровно один метод с именем `get` и объявить возвращаемый тип: +Как создать accessor? Достаточно написать интерфейс, и Nette DI сгенерирует реализацию. Интерфейс должен иметь ровно один метод с именем `get` и объявлять возвращаемый тип: ```php interface PDOAccessor @@ -151,7 +150,7 @@ interface PDOAccessor } ``` -Мы добавим аксессор в файл конфигурации, который также содержит определение сервиса, который он вернет: +Accessor добавим в файл конфигурации, где также находится определение сервиса, который он будет возвращать: ```neon services: @@ -159,63 +158,69 @@ services: - PDO(%dsn%, %user%, %password%) ``` -Поскольку метод доступа возвращает службу `PDO`, а в конфигурации есть только один такой сервис, он вернет его. Если сервисов данного типа больше, мы определяем возвращаемый сервис по имени, например `- PDOAccessor(@db1)`. +Поскольку accessor возвращает сервис типа `PDO`, а в конфигурации есть единственный такой сервис, он будет возвращать именно его. Если бы сервисов данного типа было больше, мы бы определили возвращаемый сервис с помощью имени, например, `- PDOAccessor(@db1)`. -Несколько фабрик/аксессоров .[#toc-multifactory-accessor] -========================================================= -До сих пор наши фабрики и аксессоры всегда могли производить или возвращать только один объект. Однако очень легко создать несколько фабрик в сочетании с аксессорами. Интерфейс такого класса будет содержать любое количество методов с именами `create<name>()` и `get<name>()`, например: +Множественная фабрика/аксессор +============================== +Наши фабрики и accessory до сих пор умели всегда производить или возвращать только один объект. Но можно очень легко создать и множественные фабрики, комбинированные с accessory. Интерфейс такого класса будет содержать любое количество методов с именами `create<name>()` и `get<name>()`, например: ```php interface MultiFactory { function createArticle(): Article; - function createFoo(): Model\Foo; function getDb(): PDO; } ``` -Поэтому вместо того, чтобы передавать несколько сгенерированных фабрик и аксессоров, мы собираемся передать ещё одну сложную фабрику, которая может делать больше. +Так что вместо того, чтобы передавать себе несколько сгенерированных фабрик и accessory, мы передадим одну более комплексную фабрику, которая умеет больше. -Как вариант, вместо нескольких методов можно использовать параметры `create()` и `get()`: +Альтернативно, вместо нескольких методов можно использовать `get()` с параметром: ```php interface MultiFactoryAlt { - function create($name); function get($name): PDO; } ``` -Затем `MultiFactory::createArticle()` делает то же самое, что и `MultiFactoryAlt::create('article')`. Однако альтернативная нотация имеет тот недостаток, что неясно, какие значения `$name` поддерживаются, и логически невозможно различить разные возвращаемые значения для разных `$name` в интерфейсе. +Тогда верно, что `MultiFactory::getArticle()` делает то же самое, что и `MultiFactoryAlt::get('article')`. Однако альтернативная запись имеет тот недостаток, что неясно, какие значения `$name` поддерживаются, и логически также нельзя в интерфейсе различить разные возвращаемые значения для разных `$name`. + +Определение списком +------------------- +Таким образом можно определить множественную фабрику в конфигурации: .{data-version:3.2.0} + +```neon +services: + - MultiFactory( + article: Article # определяет createArticle() + db: PDO(%dsn%, %user%, %password%) # определяет getDb() + ) +``` -Определение списка .[#toc-definition-with-a-list] -------------------------------------------------- -А как определить множественную фабрику в конфигурации? Мы создадим три сервиса, которые будут создавать/возвращать, а затем и саму фабрику: +Или мы можем в определении фабрики сослаться на существующие сервисы с помощью ссылки: ```neon services: article: Article - - Model\Foo - PDO(%dsn%, %user%, %password%) - MultiFactory( - article: @article # createArticle() - foo: @Model\Foo # createFoo() - db: @\PDO # getDb() + article: @article # определяет createArticle() + db: @\PDO # определяет getDb() ) ``` -Определения с использованием тегов .[#toc-definition-with-tags] ---------------------------------------------------------------- +Определение с помощью тегов +--------------------------- -Второй вариант — использовать [теги|services#Tags] для определения: +Второй возможностью является использование для определения [тегов |services#Теги]: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter - App\Model\DatabaseAccessor( - db1: @database.db1.context + db1: @database.db1.explorer ) ``` diff --git a/dependency-injection/ru/faq.texy b/dependency-injection/ru/faq.texy index bf7d5b1260..fc535a614f 100644 --- a/dependency-injection/ru/faq.texy +++ b/dependency-injection/ru/faq.texy @@ -1,98 +1,92 @@ -Часто задаваемые вопросы об DI (FAQ) -************************************ +Часто задаваемые вопросы о DI (FAQ) +*********************************** -Является ли DI другим названием для IoC? .[#toc-is-di-another-name-for-ioc] ---------------------------------------------------------------------------- +Является ли DI другим названием для IoC? +---------------------------------------- -Принцип *Inversion of Control* (IoC) - это принцип, сосредоточенный на способе выполнения кода - инициирует ли ваш код внешний код или ваш код интегрирован во внешний код, который затем вызывает его. -IoC - это широкая концепция, включающая [события |nette:glossary#Events], так называемый [голливудский принцип |application:components#Hollywood style] и другие аспекты. -Фабрики, которые являются частью [правила №3: Let the Factory Handle It |introduction#Rule #3: Let the Factory Handle It], и представляют собой инверсию для оператора `new`, также являются компонентами этой концепции. +*Inversion of Control* (IoC) — это принцип, ориентированный на способ запуска кода — запускает ли ваш код чужой код, или ваш код интегрирован в чужой, который его затем вызывает. IoC — это широкий термин, включающий [события |nette:glossary#События Events], так называемый [Голливудский принцип |application:components#Стиль Голливуда] и другие аспекты. Частью этой концепции являются и фабрики, о которых говорит [Правило № 3: пусть это сделает фабрика |introduction#Правило 3: оставь это фабрике], и которые представляют собой инверсию для оператора `new`. -*Dependency Injection* (DI) - это то, как один объект знает о другом объекте, т.е. зависимость. Это паттерн проектирования, который требует явной передачи зависимостей между объектами. +*Dependency Injection* (DI) фокусируется на способе, которым один объект узнает о другом объекте, то есть о его зависимостях. Это паттерн проектирования, который требует явной передачи зависимостей между объектами. -Таким образом, можно сказать, что DI является специфической формой IoC. Однако не все формы IoC подходят с точки зрения чистоты кода. Например, к антипаттернам мы относим все техники, работающие с [глобальным состоянием |global state], или так называемый [Service Locator |#What is a Service Locator]. +Таким образом, можно сказать, что DI является специфической формой IoC. Однако не все формы IoC подходят с точки зрения чистоты кода. Например, к антипаттернам относятся техники, которые работают с [глобальным состоянием |global-state] или так называемый [Service Locator |#Что такое Service Locator]. -Что такое локатор сервисов? .[#toc-what-is-a-service-locator] -------------------------------------------------------------- +Что такое Service Locator? +-------------------------- -Сервисный локатор - это альтернатива инъекции зависимостей. Он работает путем создания центрального хранилища, в котором регистрируются все доступные сервисы или зависимости. Когда объекту нужна зависимость, он запрашивает ее в Service Locator. +Это альтернатива Dependency Injection. Он работает так, что создает центральное хранилище, где зарегистрированы все доступные сервисы или зависимости. Когда объекту нужна зависимость, он запрашивает ее у Service Locator. -Однако, по сравнению с Dependency Injection, этот метод теряет в прозрачности: зависимости не передаются объектам напрямую и поэтому их нелегко идентифицировать, что требует изучения кода для выявления и понимания всех связей. Тестирование также усложняется, поскольку мы не можем просто передать имитационные объекты тестируемым объектам, а должны пройти через Service Locator. Кроме того, Service Locator нарушает дизайн кода, поскольку отдельные объекты должны знать о его существовании, что отличается от Dependency Injection, где объекты не знают о контейнере DI. +Однако по сравнению с Dependency Injection он теряет в прозрачности: зависимости не передаются объектам напрямую и не так легко идентифицируются, что требует изучения кода для выявления и понимания всех связей. Тестирование также сложнее, потому что мы не можем просто передавать mock-объекты тестируемым объектам, а должны делать это через Service Locator. Кроме того, Service Locator нарушает дизайн кода, поскольку отдельные объекты должны знать о его существовании, что отличается от Dependency Injection, где объекты не имеют представления о DI-контейнере. -Когда лучше не использовать DI? .[#toc-when-is-it-better-not-to-use-di] ------------------------------------------------------------------------ +Когда лучше не использовать DI? +------------------------------- -Не существует известных трудностей, связанных с использованием паттерна проектирования Dependency Injection. Напротив, получение зависимостей из глобально доступных мест приводит к [ряду осложнений |global-state], как и использование локатора служб. -Поэтому рекомендуется всегда использовать DI. Это не догматический подход, просто лучшей альтернативы пока не найдено. +Неизвестны никакие трудности, связанные с использованием паттерна проектирования Dependency Injection. Напротив, получение зависимостей из глобально доступных мест приводит к [целому ряду осложнений |global-state], так же как и использование Service Locator. Поэтому целесообразно использовать DI всегда. Это не догматический подход, а просто не была найдена лучшая альтернатива. -Однако существуют определенные ситуации, когда мы не передаем объекты друг другу, а получаем их из глобального пространства. Например, при отладке кода и необходимости сбросить значение переменной в определенный момент программы, измерить длительность определенной части программы или записать сообщение в журнал. -В таких случаях, когда речь идет о временных действиях, которые впоследствии будут удалены из кода, вполне законно использовать глобально доступный дампер, секундомер или логгер. Эти инструменты, в конце концов, не относятся к дизайну кода. +Тем не менее, существуют определенные ситуации, когда мы не передаем объекты, а получаем их из глобального пространства. Например, при отладке кода, когда нужно в конкретной точке программы вывести значение переменной, измерить продолжительность определенной части программы или записать сообщение. В таких случаях, когда речь идет о временных действиях, которые позже будут удалены из кода, легитимно использовать глобально доступный дампер, секундомер или логгер. Эти инструменты не относятся к дизайну кода. -Есть ли у использования DI недостатки? .[#toc-does-using-di-have-its-drawbacks] -------------------------------------------------------------------------------- +Есть ли у использования DI недостатки? +-------------------------------------- -Имеет ли использование Dependency Injection какие-либо недостатки, такие как увеличение сложности написания кода или ухудшение производительности? Что мы теряем, когда начинаем писать код в соответствии с DI? +Влечет ли использование Dependency Injection какие-либо недостатки, такие как повышенная трудоемкость написания кода или ухудшение производительности? Что мы теряем, когда начинаем писать код в соответствии с DI? -DI не влияет на производительность приложения или требования к памяти. Производительность контейнера DI может играть определенную роль, но в случае с [Nette DI | nette-container] контейнер компилируется в чистый PHP, поэтому его накладные расходы во время выполнения приложения практически равны нулю. +DI не влияет на производительность или потребление памяти приложения. Определенную роль может играть производительность DI Container, однако в случае [Nette DI |nette-container] контейнер компилируется в чистый PHP, так что его накладные расходы во время выполнения приложения практически нулевые. -При написании кода необходимо создавать конструкторы, принимающие зависимости. Раньше это могло отнимать много времени, но благодаря современным IDE и [продвижению свойств конструкторов |https://blog.nette.org/ru/php-8-0-polnyj-obzor-novostej#toc-constructor-property-promotion] теперь это дело нескольких секунд. Фабрики могут быть легко созданы с помощью Nette DI и плагина PhpStorm всего за несколько кликов. -С другой стороны, нет необходимости писать синглтоны и статические точки доступа. +При написании кода обычно необходимо создавать конструкторы, принимающие зависимости. Раньше это могло быть утомительно, однако благодаря современным IDE и [constructor property promotion |https://blog.nette.org/ru/php-8-0-complete-overview-of-news#toc-constructor-property-promotion] это теперь вопрос нескольких секунд. Фабрики можно легко генерировать с помощью Nette DI и плагина для PhpStorm щелчком мыши. С другой стороны, отпадает необходимость писать синглтоны и статические точки доступа. -Можно сделать вывод, что правильно спроектированное приложение с использованием DI не короче и не длиннее по сравнению с приложением, использующим синглтоны. Части кода, работающие с зависимостями, просто извлекаются из отдельных классов и переносятся в новые места, т.е. в контейнер DI и фабрики. +Можно констатировать, что правильно спроектированное приложение, использующее DI, не короче и не длиннее по сравнению с приложением, использующим синглтоны. Части кода, работающие с зависимостями, просто изымаются из отдельных классов и переносятся на новые места, то есть в DI-контейнер и фабрики. -Как переписать унаследованное приложение на DI? .[#toc-how-to-rewrite-a-legacy-application-to-di] -------------------------------------------------------------------------------------------------- +Как переписать устаревшее приложение на DI? +------------------------------------------- -Переход от устаревшего приложения к Dependency Injection может быть сложным процессом, особенно для больших и сложных приложений. Важно подходить к этому процессу систематически. +Переход с устаревшего приложения на Dependency Injection может быть сложным процессом, особенно для больших и комплексных приложений. Важно подходить к этому процессу систематически. -- При переходе на Dependency Injection важно, чтобы все члены команды понимали используемые принципы и практики. -- Сначала проведите анализ существующего приложения, чтобы определить ключевые компоненты и их зависимости. Составьте план, какие части будут рефакторизоваться и в каком порядке. -- Реализуйте DI-контейнер или, что еще лучше, используйте существующую библиотеку, такую как Nette DI. -- Постепенно рефакторить каждую часть приложения для использования Dependency Injection. Это может включать модификацию конструкторов или методов для принятия зависимостей в качестве параметров. -- Измените места в коде, где создаются объекты зависимостей, так, чтобы вместо этого зависимости внедрялись контейнером. Это может включать использование фабрик. +- При переходе на Dependency Injection важно, чтобы все члены команды понимали принципы и процедуры, которые используются. +- Сначала проведите анализ существующего приложения и определите ключевые компоненты и их зависимости. Создайте план, какие части будут рефакторены и в каком порядке. +- Реализуйте DI-контейнер или, еще лучше, используйте существующую библиотеку, например, Nette DI. +- Постепенно рефакторьте отдельные части приложения, чтобы они использовали Dependency Injection. Это может включать изменения конструкторов или методов так, чтобы они принимали зависимости в качестве параметров. +- Измените места в коде, где создаются объекты с зависимостями, чтобы вместо этого зависимости внедрялись контейнером. Это может включать использование фабрик. -Помните, что переход на Dependency Injection - это инвестиции в качество кода и долгосрочную устойчивость приложения. Хотя внести эти изменения может быть непросто, результатом должен стать более чистый, модульный и легко тестируемый код, готовый к будущим расширениям и обслуживанию. +Помните, что переход на Dependency Injection — это инвестиция в качество кода и долгосрочную поддерживаемость приложения. Хотя может быть сложно внести эти изменения, результатом должен стать более чистый, модульный и легко тестируемый код, готовый к будущему расширению и обслуживанию. -Почему композиция предпочтительнее наследования? .[#toc-why-composition-is-preferred-over-inheritance] ------------------------------------------------------------------------------------------------------- -Предпочтительнее использовать композицию, а не наследование, так как она служит цели повторного использования кода без необходимости беспокоиться о нисходящем эффекте изменений. Таким образом, это обеспечивает более свободную связь, где нам не нужно беспокоиться о том, что изменение какого-то кода приведет к тому, что другой зависимый код потребует изменений. Типичным примером является ситуация, называемая [ад конструкторов |passing-dependencies#Constructor hell]. +Почему композиция предпочтительнее наследования? +------------------------------------------------ +Предпочтительнее использовать [композицию |nette:introduction-to-object-oriented-programming#Композиция] вместо [наследования |nette:introduction-to-object-oriented-programming#Наследование], потому что она служит для повторного использования кода, не заботясь о последствиях изменений. Она обеспечивает более слабую связь, когда нам не нужно беспокоиться, что изменение какого-либо кода вызовет необходимость изменения другого зависимого кода. Типичным примером является ситуация, называемая [constructor hell |passing-dependencies#Ад конструкторов]. -Можно ли использовать Nette DI Container вне Nette? .[#toc-can-nette-di-container-be-used-outside-of-nette] ------------------------------------------------------------------------------------------------------------ +Можно ли использовать Nette DI Container вне Nette? +--------------------------------------------------- -Безусловно. Nette DI Container является частью Nette, но разработан как отдельная библиотека, которую можно использовать независимо от других частей фреймворка. Просто установите его с помощью Composer, создайте конфигурационный файл, определяющий ваши сервисы, а затем используйте несколько строк PHP-кода для создания DI-контейнера. -И вы можете немедленно начать использовать преимущества Dependency Injection в своих проектах. +Определенно. Nette DI Container является частью Nette, но спроектирован как самостоятельная библиотека, которая может быть использована независимо от других частей фреймворка. Достаточно установить ее с помощью Composer, создать файл конфигурации с определением ваших сервисов и затем с помощью нескольких строк PHP-кода создать DI-контейнер. И сразу можно начать использовать преимущества Dependency Injection в своих проектах. -В главе [Nette DI Container |nette-container] описано, как выглядит конкретный пример использования, включая код. +Как выглядит конкретное использование, включая код, описывает глава [Nette DI Container |nette-container]. -Почему конфигурация находится в файлах NEON? .[#toc-why-is-the-configuration-in-neon-files] -------------------------------------------------------------------------------------------- +Почему конфигурация находится в NEON-файлах? +-------------------------------------------- -NEON - это простой и легко читаемый язык конфигурации, разработанный в Nette для настройки приложений, сервисов и их зависимостей. По сравнению с JSON или YAML, он предлагает гораздо более интуитивно понятные и гибкие возможности для этой цели. В NEON можно естественным образом описать привязки, которые невозможно было бы написать в Symfony & YAML либо вообще, либо только через сложное описание. +NEON — это простой и легко читаемый язык конфигурации, который был разработан в рамках Nette для настройки приложений, сервисов и их зависимостей. По сравнению с JSON или YAML он предлагает для этой цели гораздо более интуитивные и гибкие возможности. В NEON можно естественно описать связи, которые в Symfony & YAML было бы невозможно записать либо вообще, либо только посредством сложного описания. -Замедляет ли парсинг NEON файлов работу приложения? .[#toc-does-parsing-neon-files-slow-down-the-application] -------------------------------------------------------------------------------------------------------------- +Не замедляет ли приложение парсинг NEON-файлов? +----------------------------------------------- -Хотя файлы NEON разбираются очень быстро, этот аспект не имеет особого значения. Причина в том, что разбор файлов происходит только один раз во время первого запуска приложения. После этого код контейнера DI генерируется, сохраняется на диске и выполняется для каждого последующего запроса без необходимости дальнейшего разбора. +Хотя файлы NEON парсятся очень быстро, этот аспект вообще не имеет значения. Причина в том, что парсинг файлов происходит только один раз при первом запуске приложения. Затем генерируется код DI-контейнера, сохраняется на диск и запускается при каждом следующем запросе, без необходимости выполнять дополнительный парсинг. -Именно так это работает в производственной среде. Во время разработки файлы NEON разбираются при каждом изменении их содержимого, что гарантирует, что у разработчика всегда есть актуальный контейнер DI. Как упоминалось ранее, фактический разбор происходит мгновенно. +Так это работает в производственной среде. Во время разработки NEON-файлы парсятся каждый раз, когда происходит изменение их содержимого, чтобы разработчик всегда имел актуальный DI-контейнер. Сам парсинг, как было сказано, — вопрос мгновения. -Как я могу получить доступ к параметрам из конфигурационного файла в своем классе? .[#toc-how-do-i-access-the-parameters-from-the-configuration-file-in-my-class] ------------------------------------------------------------------------------------------------------------------------------------------------------------------ +Как получить доступ к параметрам в файле конфигурации из моего класса? +---------------------------------------------------------------------- -Помните о [правиле №1: "Пусть вам переда |introduction#Rule #1: Let It Be Passed to You]дут". Если класс требует информацию из конфигурационного файла, нам не нужно выяснять, как получить доступ к этой информации; вместо этого мы просто запрашиваем ее - например, через конструктор класса. А передачу мы выполняем в конфигурационном файле. +Будем помнить [Правило № 1: пусть тебе это передадут |introduction#Правило 1: пусть тебе это передадут]. Если класс требует информацию из файла конфигурации, нам не нужно думать, как к этой информации добраться, вместо этого мы просто запросим ее — например, через конструктор класса. А передачу осуществим в файле конфигурации. -В этом примере `%myParameter%` - это место для значения параметра `myParameter`, который будет передан конструктору `MyClass`: +В этом примере `%myParameter%` является заполнителем для значения параметра `myParameter`, который передается в конструктор класса `MyClass`: ```php # config.neon @@ -103,10 +97,10 @@ services: - MyClass(%myParameter%) ``` -Если вы хотите передать несколько параметров или использовать автоподключение, полезно [обернуть параметры в объект |best-practices:passing-settings-to-presenters]. +Чтобы передавать несколько параметров или использовать autowiring, целесообразно [параметры упаковать в объект |best-practices:passing-settings-to-presenters]. -Поддерживает ли Nette интерфейс PSR-11 Container? .[#toc-does-nette-support-psr-11-container-interface] -------------------------------------------------------------------------------------------------------- +Поддерживает ли Nette PSR-11: Container interface? +-------------------------------------------------- -Nette DI Container не поддерживает PSR-11 напрямую. Однако, если вам нужна совместимость между Nette DI Container и библиотеками или фреймворками, которые ожидают контейнерный интерфейс PSR-11, вы можете создать [простой адаптер |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f], который будет служить мостом между Nette DI Container и PSR-11. +Nette DI Container не поддерживает PSR-11 напрямую. Однако, если вам нужна интероперабельность между Nette DI Container и библиотеками или фреймворками, которые ожидают PSR-11 Container Interface, вы можете создать [простой адаптер |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f], который будет служить мостом между Nette DI Container и PSR-11. diff --git a/dependency-injection/ru/global-state.texy b/dependency-injection/ru/global-state.texy index fd015989bb..d0b8d5733a 100644 --- a/dependency-injection/ru/global-state.texy +++ b/dependency-injection/ru/global-state.texy @@ -2,43 +2,42 @@ ******************************** .[perex] -Предупреждение: Следующие конструкции являются симптомами плохого проектирования кода: +Предупреждение: Следующие конструкции являются признаком плохо спроектированного кода: - `Foo::getInstance()` - `DB::insert(...)` - `Article::setDb($db)` - `ClassName::$var` или `static::$var` -Встречаете ли вы в своем коде какие-либо из этих конструкций? Если да, то у вас есть возможность улучшить его. Вы можете подумать, что это обычные конструкции, которые часто встречаются в примерах решений различных библиотек и фреймворков. Если это так, то в их коде есть недостатки. +Встречаются ли некоторые из этих конструкций в вашем коде? Тогда у вас есть возможность его улучшить. Возможно, вы думаете, что это обычные конструкции, которые вы видите, например, и в примерах решений различных библиотек и фреймворков. Если это так, то дизайн их кода нехорош. -Речь не идет о какой-то академической чистоте. Все эти конструкции объединяет одно: они используют глобальное состояние. А это разрушительно сказывается на качестве кода. Классы обманывают о своих зависимостях. Код становится непредсказуемым. Это запутывает разработчиков и снижает их эффективность. +Сейчас мы определенно не говорим о какой-то академической чистоте. Все эти конструкции имеют одно общее: они используют глобальное состояние. А оно оказывает разрушительное воздействие на качество кода. Классы лгут о своих зависимостях. Код становится непредсказуемым. Путает программистов и снижает их эффективность. -В этой главе мы объясним, почему так происходит и как избежать глобального состояния. +В этой главе мы объясним, почему это так, и как избежать глобального состояния. -Глобальное сцепление .[#toc-global-interlinking] ------------------------------------------------- +Глобальная связанность +---------------------- -В идеальном мире объект должен взаимодействовать только с теми объектами, которые были [ему непосредственно переда |passing-dependencies]ны. Если я создам два объекта `A` и `B` и никогда не передам между ними ссылку, то ни `A`, ни `B` не смогут получить доступ к состоянию другого объекта или изменить его. Это очень желательное свойство кода. Это похоже на то, как если бы у нас была батарейка и лампочка; лампочка не загорится, пока вы не подключите ее проводом к батарейке. +В идеальном мире объект должен иметь возможность общаться только с объектами, которые были ему [напрямую переданы |passing-dependencies]. Если я создам два объекта `A` и `B` и никогда не передам ссылку между ними, то ни `A`, ни `B` не смогут получить доступ к другому объекту или изменить его состояние. Это очень желательное свойство кода. Это похоже на то, как если бы у вас были батарейка и лампочка; лампочка не загорится, пока вы не соедините ее с батарейкой проводом. -Однако это не относится к глобальным (статическим) переменным или синглтонам. Объект `A` может *беспроводным* способом получить доступ к объекту `C` и модифицировать его без передачи ссылок, вызвав `C::changeSomething()`. Если объект `B` также подключается к глобальной `C`, то `A` и `B` могут влиять друг на друга через `C`. +Но это не относится к глобальным (статическим) переменным или синглтонам. Объект `A` мог бы *беспроводным* способом получить доступ к объекту `C` и модифицировать его без какой-либо передачи ссылки, вызвав `C::changeSomething()`. Если объект `B` также захватит глобальный `C`, то `A` и `B` могут взаимно влиять друг на друга через `C`. -Использование глобальных переменных вводит новую форму *беспроводной* связи, которая не видна извне. Это создает дымовую завесу, затрудняющую понимание и использование кода. Чтобы действительно понять зависимости, разработчикам приходится читать каждую строчку исходного кода, а не просто знакомиться с интерфейсами классов. Более того, эта запутанность совершенно не нужна. Глобальное состояние используется потому, что оно легко доступно из любой точки и позволяет, например, записывать данные в базу данных через глобальный (статический) метод `DB::insert()`. Однако, как мы увидим, польза от него минимальна, а сложности, которые он создает, весьма серьезны. +Использование глобальных переменных вносит в систему новую форму *беспроводной* связанности, которая не видна снаружи. Создает дымовую завесу, усложняющую понимание и использование кода. Чтобы разработчики действительно поняли зависимости, им нужно прочитать каждую строку исходного кода. Вместо простого ознакомления с интерфейсами классов. К тому же это совершенно излишняя связанность. Глобальное состояние используется потому, что оно легко доступно откуда угодно и позволяет, например, записать в базу данных через глобальный (статический) метод `DB::insert()`. Но, как мы покажем, преимущество, которое это дает, незначительно, в то время как осложнения вызывает фатальные. .[note] -С точки зрения поведения, нет никакой разницы между глобальной и статической переменной. Они одинаково вредны. +С точки зрения поведения нет разницы между глобальной и статической переменной. Они одинаково вредны. -Зловещее действие на расстоянии .[#toc-the-spooky-action-at-a-distance] ------------------------------------------------------------------------ +Жуткое действие на расстоянии +----------------------------- -"Зловещее действие на расстоянии" - так Альберт Эйнштейн в 1935 году назвал явление в квантовой физике, от которого у него мурашки по коже. -Это квантовая запутанность, особенность которой заключается в том, что когда вы измеряете информацию об одной частице, вы немедленно воздействуете на другую частицу, даже если они находятся на расстоянии миллионов световых лет друг от друга. -Это, казалось бы, нарушает фундаментальный закон Вселенной, согласно которому ничто не может двигаться быстрее света. +«Жуткое действие на расстоянии» — так знаменито назвал в 1935 году Альберт Эйнштейн явление в квантовой физике, которое вызывало у него мурашки по коже. +Речь идет о квантовой запутанности, особенностью которой является то, что когда вы измеряете информацию об одной частице, вы немедленно влияете на другую частицу, даже если они находятся на расстоянии миллионов световых лет друг от друга. Что, казалось бы, нарушает основной закон Вселенной, что ничто не может распространяться быстрее света. -В мире программного обеспечения мы можем назвать "spooky action at a distance" ситуацию, когда мы запускаем процесс, который, как нам кажется, изолирован (потому что мы не передавали ему никаких ссылок), но неожиданные взаимодействия и изменения состояния происходят в удаленных местах системы, о которых мы не сообщили объекту. Это может произойти только через глобальное состояние. +В мире программного обеспечения мы можем назвать «жутким действием на расстоянии» ситуацию, когда мы запускаем какой-то процесс, который, как мы полагаем, изолирован (потому что мы не передали ему никаких ссылок), но в отдаленных местах системы происходят неожиданные взаимодействия и изменения состояния, о которых мы не подозревали. Это может произойти только через глобальное состояние. -Представьте, что вы присоединились к команде разработчиков проекта, которая имеет большую, зрелую кодовую базу. Ваш новый руководитель просит вас реализовать новую функцию, и, как хороший разработчик, вы начинаете с написания теста. Но поскольку вы новичок в проекте, вы делаете много исследовательских тестов типа "что произойдет, если я вызову этот метод". И вы пытаетесь написать следующий тест: +Представьте, что вы присоединились к команде разработчиков проекта, у которого обширная и развитая кодовая база. Ваш новый руководитель просит вас реализовать новую функцию, и вы, как правильный разработчик, начинаете с написания теста. Но поскольку вы новичок в проекте, вы проводите много исследовательских тестов типа «что произойдет, если я вызову этот метод». И пробуете написать следующий тест: ```php function testCreditCardCharge() @@ -48,20 +47,17 @@ function testCreditCardCharge() } ``` -Вы запускаете код, возможно, несколько раз, и через некоторое время замечаете на своем телефоне уведомления от банка о том, что при каждом запуске с вашей кредитной карты было списано $100 🤦‍♂️. +Вы запускаете код, возможно, несколько раз, и через некоторое время замечаете на мобильном телефоне уведомления от банка, что при каждом запуске с вашей платежной карты списывалось 100 долларов 🤦‍♂️ -Как тест мог вызвать фактическое списание средств? С кредитной картой не так-то просто работать. Вы должны взаимодействовать с веб-службой третьей стороны, вы должны знать URL этой веб-службы, вы должны войти в систему и так далее. -Ни одна из этих сведений не включена в тест. Хуже того, вы даже не знаете, где эта информация присутствует, и, следовательно, как высмеять внешние зависимости, чтобы каждый прогон не приводил к повторному начислению $100. А как начинающий разработчик, откуда вы могли знать, что то, что вы собираетесь сделать, приведет к тому, что вы станете беднее на 100 долларов? +Как, черт возьми, тест мог вызвать реальное списание денег? Оперировать платежной картой непросто. Нужно общаться со сторонним веб-сервисом, нужно знать URL этого веб-сервиса, нужно войти в систему и так далее. Никакой из этой информации в тесте нет. Хуже того, вы даже не знаете, где эта информация находится, и, следовательно, как замокать внешние зависимости, чтобы каждый запуск не приводил к тому, что снова спишется 100 долларов. И как вы, как новый разработчик, должны были знать, что то, что вы собираетесь сделать, приведет к тому, что вы станете на 100 долларов беднее? Это жуткое действие на расстоянии! -У вас нет другого выбора, кроме как копаться в большом количестве исходного кода, спрашивая старших и более опытных коллег, пока вы не поймете, как работают связи в проекте. -Это связано с тем, что, глядя на интерфейс класса `CreditCard`, вы не можете определить глобальное состояние, которое необходимо инициализировать. Даже просмотр исходного кода класса не подскажет вам, какой метод инициализации нужно вызвать. В лучшем случае, вы можете найти глобальную переменную, к которой обращаются, и попытаться угадать, как ее инициализировать, исходя из этого. +Вам не остается ничего другого, как долго копаться в куче исходных кодов, спрашивать старших и более опытных коллег, пока вы не поймете, как работают связи в проекте. Это вызвано тем, что при взгляде на интерфейс класса `CreditCard` нельзя определить глобальное состояние, которое нужно инициализировать. Даже взгляд в исходный код класса не подскажет вам, какой инициализационный метод нужно вызвать. В лучшем случае вы можете найти глобальную переменную, к которой осуществляется доступ, и из нее попытаться угадать, как ее инициализировать. -Классы в таком проекте - патологические лжецы. Платежная карта делает вид, что вы можете просто инстанцировать ее и вызвать метод `charge()`. Однако она тайно взаимодействует с другим классом, `PaymentGateway`. Даже его интерфейс говорит, что он может быть инициализирован самостоятельно, но на самом деле он берет учетные данные из какого-то конфигурационного файла и так далее. -Разработчикам, написавшим этот код, ясно, что `CreditCard` нуждается в `PaymentGateway`. Они написали код таким образом. Но для новичков это полная загадка и мешает обучению. +Классы в таком проекте — патологические лжецы. Платежная карта делает вид, что ее достаточно инстанцировать и вызвать метод `charge()`. Втайне же она сотрудничает с другим классом `PaymentGateway`, который представляет платежный шлюз. И его интерфейс говорит, что его можно инициализировать самостоятельно, но на самом деле он извлекает учетные данные из какого-то конфигурационного файла и так далее. Разработчикам, написавшим этот код, ясно, что `CreditCard` нуждается в `PaymentGateway`. Они написали код таким образом. Но для любого, кто новичок в проекте, это полная загадка и мешает обучению. -Как исправить ситуацию? Легко. **Пусть API объявляет зависимости.**. +Как исправить ситуацию? Легко. **Пусть API декларирует зависимости.** ```php function testCreditCardCharge() @@ -72,36 +68,35 @@ function testCreditCardCharge() } ``` -Обратите внимание, как внезапно стали очевидны взаимосвязи внутри кода. Объявив, что метод `charge()` нуждается в методе `PaymentGateway`, вам не нужно никого спрашивать о взаимозависимости кода. Вы знаете, что вам нужно создать его экземпляр, и когда вы пытаетесь это сделать, вы сталкиваетесь с тем, что вам нужно предоставить параметры доступа. Без них код даже не запустится. +Обратите внимание, как сразу становятся очевидными связи внутри кода. Тем, что метод `charge()` декларирует, что ему нужен `PaymentGateway`, вам не нужно никого спрашивать о том, как связан код. Вы знаете, что нужно создать его экземпляр, и когда вы попытаетесь это сделать, вы столкнетесь с тем, что нужно предоставить параметры доступа. Без них код даже не запустится. -И самое главное, теперь вы можете поиздеваться над платежным шлюзом, чтобы с вас не снимали 100 долларов каждый раз, когда вы запускаете тест. +И главное, теперь вы можете замокать платежный шлюз, так что при каждом запуске теста с вас не будет списываться 100 долларов. -Глобальное состояние заставляет ваши объекты тайно получать доступ к тому, что не объявлено в их API, и в результате делает ваши API патологическими лжецами. +Глобальное состояние приводит к тому, что ваши объекты могут тайно получать доступ к вещам, которые не объявлены в их API, и в результате делают ваши API патологическими лжецами. -Возможно, вы не думали об этом раньше, но всякий раз, когда вы используете глобальное состояние, вы создаете секретные беспроводные каналы связи. Жуткие удаленные действия заставляют разработчиков читать каждую строчку кода, чтобы понять потенциальное взаимодействие, снижают производительность разработчиков и сбивают с толку новых членов команды. -Если вы один из тех, кто создал код, вы знаете реальные зависимости, но все, кто приходит после вас, ничего не знают. +Возможно, вы раньше не думали об этом так, но всякий раз, когда вы используете глобальное состояние, вы создаете секретные беспроводные каналы связи. Жуткое действие на расстоянии заставляет разработчиков читать каждую строку кода, чтобы понять потенциальные взаимодействия, снижает производительность разработчиков и сбивает с толку новых членов команды. Если вы тот, кто создал код, вы знаете реальные зависимости, но любой, кто придет после вас, будет в растерянности. -Не пишите код, который использует глобальное состояние, предпочитая передавать зависимости. Это и есть инъекция зависимостей. +Не пишите код, использующий глобальное состояние, отдавайте предпочтение передаче зависимостей. То есть dependency injection. -Хрупкость глобального государства .[#toc-brittleness-of-the-global-state] -------------------------------------------------------------------------- +Хрупкость глобального состояния +------------------------------- -В коде, использующем глобальное состояние и синглтоны, никогда нельзя точно сказать, когда и кем это состояние было изменено. Этот риск присутствует уже при инициализации. Следующий код должен создать соединение с базой данных и инициализировать платежный шлюз, но он постоянно выбрасывает исключение, и поиск причины крайне утомителен: +В коде, использующем глобальное состояние и синглтоны, никогда не известно, когда и кто это состояние изменил. Этот риск возникает уже при инициализации. Следующий код должен создать подключение к базе данных и инициализировать платежный шлюз, однако постоянно выбрасывает исключение, и поиск причины чрезвычайно утомителен: ```php PaymentGateway::init(); DB::init('mysql:', 'user', 'password'); ``` -Приходится подробно изучать код, чтобы обнаружить, что объект `PaymentGateway` обращается к другим объектам по беспроводной связи, некоторые из которых требуют подключения к базе данных. Таким образом, вы должны инициализировать базу данных перед `PaymentGateway`. Однако дымовая завеса глобального состояния скрывает это от вас. Сколько времени вы бы сэкономили, если бы API каждого класса не лгал и не объявлял о своих зависимостях? +Вам нужно подробно просмотреть код, чтобы выяснить, что объект `PaymentGateway` беспроводным способом обращается к другим объектам, некоторые из которых требуют подключения к базе данных. То есть необходимо инициализировать базу данных раньше, чем `PaymentGateway`. Однако дымовая завеса глобального состояния это от вас скрывает. Сколько времени вы бы сэкономили, если бы API отдельных классов не лгали и декларировали свои зависимости? ```php $db = new DB('mysql:', 'user', 'password'); $gateway = new PaymentGateway($db, ...); ``` -Аналогичная проблема возникает при использовании глобального доступа к соединению с базой данных: +Подобная проблема возникает и при использовании глобального доступа к подключению к базе данных: ```php use Illuminate\Support\Facades\DB; @@ -115,7 +110,7 @@ class Article } ``` -При вызове метода `save()` неясно, было ли уже создано соединение с базой данных и кто отвечает за его создание. Например, если бы мы захотели изменить соединение с базой данных "на лету", возможно, в целях тестирования, нам, вероятно, пришлось бы создать дополнительные методы, такие как `DB::reconnect(...)` или `DB::reconnectForTest()`. +При вызове метода `save()` неясно, было ли уже создано подключение к базе данных и кто несет ответственность за его создание. Если мы хотим, например, изменять подключение к базе данных во время выполнения, например, для тестов, нам, скорее всего, пришлось бы создать дополнительные методы, такие как `DB::reconnect(...)` или `DB::reconnectForTest()`. Рассмотрим пример: @@ -127,9 +122,9 @@ Foo::doSomething(); $article->save(); ``` -Где мы можем быть уверены, что при вызове `$article->save()` действительно используется тестовая база данных? Что если метод `Foo::doSomething()` изменит глобальное подключение к базе данных? Чтобы выяснить это, нам придется изучить исходный код класса `Foo` и, вероятно, многих других классов. Однако такой подход даст лишь краткосрочный ответ, поскольку в будущем ситуация может измениться. +Где у нас уверенность, что при вызове `$article->save()` действительно используется тестовая база данных? Что, если метод `Foo::doSomething()` изменил глобальное подключение к базе данных? Для выяснения нам пришлось бы изучить исходный код класса `Foo` и, вероятно, многих других классов. Однако этот подход принес бы лишь краткосрочный ответ, поскольку ситуация может измениться в будущем. -Что если мы перенесем соединение с базой данных в статическую переменную внутри класса `Article`? +А что, если мы перенесем подключение к базе данных в статическую переменную внутри класса `Article`? ```php class Article @@ -148,11 +143,11 @@ class Article } ``` -Это совершенно ничего не изменит. Проблема является глобальным состоянием, и не имеет значения, в каком классе она скрывается. В этом случае, как и в предыдущем, у нас нет никакой информации о том, в какую базу данных производится запись, когда вызывается метод `$article->save()`. Любой человек на удаленном конце приложения может в любой момент изменить базу данных с помощью `Article::setDb()`. Под нашими руками. +Это вообще ничего не изменило. Проблема в глобальном состоянии, и совершенно неважно, в каком классе оно скрывается. В этом случае, как и в предыдущем, у нас нет никаких подсказок при вызове метода `$article->save()` о том, в какую базу данных будет произведена запись. Кто угодно на другом конце приложения мог в любой момент с помощью `Article::setDb()` изменить базу данных. У нас под носом. -Глобальное состояние делает наше приложение **очень хрупким**. +Глобальное состояние делает наше приложение **чрезвычайно хрупким**. -Однако есть простой способ решить эту проблему. Просто попросите API объявить зависимости для обеспечения надлежащей функциональности. +Однако существует простой способ справиться с этой проблемой. Достаточно позволить API декларировать зависимости, что обеспечит правильную функциональность. ```php class Article @@ -174,15 +169,15 @@ Foo::doSomething(); $article->save(); ``` -Такой подход устраняет беспокойство о скрытых и неожиданных изменениях соединений с базой данных. Теперь мы точно знаем, где хранится статья, и никакие модификации кода внутри другого несвязанного класса уже не смогут изменить ситуацию. Код больше не хрупкий, а стабильный. +Благодаря этому подходу отпадает беспокойство о скрытых и неожиданных изменениях подключения к базе данных. Теперь у нас есть уверенность, куда сохраняется статья, и никакие изменения кода внутри другого несвязанного класса уже не могут изменить ситуацию. Код больше не хрупкий, а стабильный. -Не пишите код, использующий глобальное состояние, предпочитайте передавать зависимости. Таким образом, инъекция зависимостей. +Не пишите код, использующий глобальное состояние, отдавайте предпочтение передаче зависимостей. То есть dependency injection. -Синглтон .[#toc-singleton] --------------------------- +Singleton +--------- -Синглтон - это паттерн проектирования, который, по [определению |https://en.wikipedia.org/wiki/Singleton_pattern] из знаменитой публикации Gang of Four, ограничивает класс одним экземпляром и предоставляет к нему глобальный доступ. Реализация этого паттерна обычно похожа на следующий код: +Singleton — это паттерн проектирования, который согласно "определению":https://en.wikipedia.org/wiki/Singleton_pattern из известной публикации Gang of Four ограничивает класс единственным экземпляром и предлагает к нему глобальный доступ. Реализация этого паттерна обычно напоминает следующий код: ```php class Singleton @@ -195,38 +190,35 @@ class Singleton return self::$instance; } - // и другие методы, выполняющие функции класса + // и другие методы, выполняющие функции данного класса } ``` -К сожалению, синглтон вносит глобальное состояние в приложение. А как мы показали выше, глобальное состояние нежелательно. Вот почему синглтон считается антипаттерном. +К сожалению, синглтон вводит в приложение глобальное состояние. И, как мы показали выше, глобальное состояние нежелательно. Поэтому синглтон считается антипаттерном. -Не используйте синглтоны в своем коде и замените их другими механизмами. Вам действительно не нужны синглтоны. Однако если вам нужно гарантировать существование единственного экземпляра класса для всего приложения, предоставьте это [DI-контейнеру |container]. -Таким образом, создайте синглтон приложения, или сервис. В результате класс перестанет обеспечивать собственную уникальность (т.е. у него не будет метода `getInstance()` и статической переменной) и будет выполнять только свои функции. Таким образом, он перестанет нарушать принцип единой ответственности. +Не используйте в своем коде синглтоны и замените их другими механизмами. Синглтоны вам действительно не нужны. Однако, если вам нужно гарантировать существование единственного экземпляра класса для всего приложения, оставьте это [DI-контейнеру |container]. Создайте таким образом прикладной синглтон, то есть сервис. Тем самым класс перестанет заниматься обеспечением своей собственной уникальности (т.е. не будет иметь метода `getInstance()` и статической переменной) и будет выполнять только свои функции. Так он перестанет нарушать принцип единственной ответственности. -Глобальное состояние против тестов .[#toc-global-state-versus-tests] --------------------------------------------------------------------- +Глобальное состояние и тесты +---------------------------- -При написании тестов мы предполагаем, что каждый тест является изолированной единицей и никакое внешнее состояние в него не попадает. И никакое состояние не покидает тесты. Когда тест завершается, любое состояние, связанное с тестом, должно быть автоматически удалено сборщиком мусора. Это делает тесты изолированными. Поэтому мы можем запускать тесты в любом порядке. +При написании тестов мы предполагаем, что каждый тест является изолированной единицей и что в него не входит никакое внешнее состояние. И никакое состояние тесты не покидают. После завершения теста все связанное с тестом состояние должно быть автоматически удалено сборщиком мусора. Благодаря этому тесты изолированы. Поэтому мы можем запускать тесты в любом порядке. -Однако если присутствуют глобальные состояния/синглтоны, все эти приятные предположения разрушаются. Состояние может войти в тест и выйти из него. Неожиданно порядок выполнения тестов может иметь значение. +Однако, если присутствуют глобальные состояния/синглтоны, все эти приятные предположения рушатся. Состояние может входить в тест и выходить из него. Внезапно порядок тестов может иметь значение. -Чтобы вообще тестировать синглтоны, разработчикам часто приходится ослаблять их свойства, возможно, позволяя заменять один экземпляр другим. Такие решения в лучшем случае являются хаками, которые создают код, который трудно поддерживать и понимать. Любой тест или метод `tearDown()`, который влияет на любое глобальное состояние, должен отменить эти изменения. +Чтобы вообще иметь возможность тестировать синглтоны, разработчики часто вынуждены ослаблять их свойства, например, разрешая замену экземпляра другим. Такие решения в лучшем случае являются хаком, который создает трудно поддерживаемый и понятный код. Каждый тест или метод `tearDown()`, который влияет на какое-либо глобальное состояние, должен отменять эти изменения. -Глобальное состояние - это самая большая головная боль в модульном тестировании! +Глобальное состояние — самая большая головная боль при юнит-тестировании! -Как исправить ситуацию? Легко. Не пишите код, использующий синглтоны, предпочитайте передавать зависимости. То есть, инъекция зависимостей. +Как исправить ситуацию? Легко. Не пишите код, использующий синглтоны, отдавайте предпочтение передаче зависимостей. То есть dependency injection. -Глобальные константы .[#toc-global-constants] ---------------------------------------------- +Глобальные константы +-------------------- -Глобальное состояние не ограничивается использованием синглтонов и статических переменных, но также может применяться к глобальным константам. +Глобальное состояние не ограничивается только использованием синглтонов и статических переменных, но может касаться и глобальных констант. -Константы, значение которых не предоставляет нам никакой новой (`M_PI`) или полезной (`PREG_BACKTRACK_LIMIT_ERROR`) информации, явно не являются нормальными. -И наоборот, константы, которые служат способом *беспроводной* передачи информации внутри кода, являются ничем иным, как скрытой зависимостью. Например, `LOG_FILE` в следующем примере. -Использование константы `FILE_APPEND` совершенно корректно. +Константы, значение которых не несет нам никакой новой (`M_PI`) или полезной (`PREG_BACKTRACK_LIMIT_ERROR`) информации, однозначно в порядке. Напротив, константы, которые служат способом *беспроводной* передачи информации внутрь кода, являются не чем иным, как скрытой зависимостью. Как, например, `LOG_FILE` в следующем примере. Использование константы `FILE_APPEND` совершенно корректно. ```php const LOG_FILE = '...'; @@ -242,7 +234,7 @@ class Foo } ``` -В этом случае мы должны объявить параметр в конструкторе класса `Foo`, чтобы сделать его частью API: +В этом случае мы должны были бы объявить параметр в конструкторе класса `Foo`, чтобы он стал частью API: ```php class Foo @@ -261,44 +253,42 @@ class Foo } ``` -Теперь мы можем передавать информацию о пути к файлу протоколирования и легко изменять его по мере необходимости, что облегчает тестирование и сопровождение кода. +Теперь мы можем передать информацию о пути к файлу для логирования и легко изменять ее по мере необходимости, что облегчает тестирование и поддержку кода. -Глобальные функции и статические методы .[#toc-global-functions-and-static-methods] ------------------------------------------------------------------------------------ +Глобальные функции и статические методы +--------------------------------------- -Мы хотим подчеркнуть, что использование статических методов и глобальных функций само по себе не является проблематичным. Мы уже объясняли неуместность использования `DB::insert()` и подобных методов, но это всегда было связано с глобальным состоянием, хранящимся в статической переменной. Метод `DB::insert()` требует существования статической переменной, поскольку в ней хранится соединение с базой данных. Без этой переменной реализовать метод было бы невозможно. +Мы хотим подчеркнуть, что само по себе использование статических методов и глобальных функций не является проблематичным. Мы объясняли, в чем заключается нецелесообразность использования `DB::insert()` и подобных методов, но всегда речь шла только о глобальном состоянии, которое хранится в какой-то статической переменной. Метод `DB::insert()` требует существования статической переменной, потому что в ней хранится подключение к базе данных. Без этой переменной было бы невозможно реализовать метод. -Использование детерминированных статических методов и функций, таких как `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` и многих других, полностью соответствует инъекции зависимостей. Эти функции всегда возвращают одни и те же результаты при одних и тех же входных параметрах и поэтому предсказуемы. Они не используют никакого глобального состояния. +Использование детерминированных статических методов и функций, таких как `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` и многих других, полностью соответствует dependency injection. Эти функции всегда возвращают одинаковые результаты для одинаковых входных параметров и, следовательно, предсказуемы. Они не используют никакого глобального состояния. -Однако в PHP есть функции, которые не являются детерминированными. К ним относится, например, функция `htmlspecialchars()`. Ее третий параметр, `$encoding`, если не указан, по умолчанию принимает значение параметра конфигурации `ini_get('default_charset')`. Поэтому рекомендуется всегда указывать этот параметр, чтобы избежать возможного непредсказуемого поведения функции. Nette последовательно делает это. +Однако существуют и функции в PHP, которые не являются детерминированными. К ним относится, например, функция `htmlspecialchars()`. Ее третий параметр `$encoding`, если не указан, по умолчанию имеет значение конфигурационной опции `ini_get('default_charset')`. Поэтому рекомендуется всегда указывать этот параметр и предотвращать тем самым возможное непредсказуемое поведение функции. Nette это последовательно делает. -Некоторые функции, такие как `strtolower()`, `strtoupper()`, и тому подобные, в недавнем прошлом имели недетерминированное поведение и зависели от параметра `setlocale()`. Это вызывало множество осложнений, чаще всего при работе с турецким языком. -Это связано с тем, что турецкий язык различает верхний и нижний регистр `I` с точкой и без точки. Таким образом, `strtolower('I')` возвращал символ `ı`, а `strtoupper('i')` - символ `İ`, что приводило к тому, что приложения выдавали ряд загадочных ошибок. -Однако эта проблема была исправлена в PHP версии 8.2, и функции больше не зависят от локали. +Некоторые функции, такие как `strtolower()`, `strtoupper()` и подобные, в недавнем прошлом вели себя недетерминированно и зависели от настройки `setlocale()`. Это вызывало много осложнений, чаще всего при работе с турецким языком. Он различает как строчную, так и прописную букву `I` с точкой и без точки. Так что `strtolower('I')` возвращало символ `ı`, а `strtoupper('i')` — символ `İ`, что приводило к тому, что приложения начинали вызывать ряд загадочных ошибок. Однако эта проблема была устранена в PHP версии 8.2, и функции больше не зависят от локали. -Это наглядный пример того, как глобальное состояние мучает тысячи разработчиков по всему миру. Решением было заменить его инъекцией зависимостей. +Это хороший пример того, как глобальное состояние доставило хлопот тысячам разработчиков по всему миру. Решением было заменить его на dependency injection. -Когда можно использовать глобальное состояние? .[#toc-when-is-it-possible-to-use-global-state] ----------------------------------------------------------------------------------------------- +Когда можно использовать глобальное состояние? +---------------------------------------------- -Существуют определенные ситуации, когда можно использовать глобальное состояние. Например, при отладке кода, когда вам нужно сбросить значение переменной или измерить длительность определенной части программы. В таких случаях, когда речь идет о временных действиях, которые впоследствии будут удалены из кода, вполне законно использовать глобально доступный дампер или секундомер. Эти инструменты не являются частью дизайна кода. +Существуют определенные специфические ситуации, когда можно использовать глобальное состояние. Например, при отладке кода, когда нужно вывести значение переменной или измерить продолжительность определенной части программы. В таких случаях, которые касаются временных действий, которые позже будут удалены из кода, легитимно использовать глобально доступный дампер или секундомер. Эти инструменты не являются частью дизайна кода. -Другой пример - функции для работы с регулярными выражениями `preg_*`, которые внутренне хранят скомпилированные регулярные выражения в статическом кэше в памяти. Когда вы вызываете одно и то же регулярное выражение несколько раз в разных частях кода, оно компилируется только один раз. Кэш экономит производительность и совершенно незаметен для пользователя, поэтому такое использование можно считать легитимным. +Другим примером являются функции для работы с регулярными выражениями `preg_*`, которые внутренне хранят скомпилированные регулярные выражения в статическом кеше в памяти. Когда вы вызываете одно и то же регулярное выражение несколько раз в разных местах кода, оно компилируется только один раз. Кеш экономит производительность и в то же время для пользователя совершенно невидим, поэтому такое использование можно считать легитимным. -Резюме .[#toc-summary] ----------------------- +Резюме +------ -Мы показали, почему это имеет смысл +Мы рассмотрели, почему имеет смысл: -1) Убрать из кода все статические переменные -2) Объявить зависимости -3) И использовать инъекцию зависимостей +1) Удалить все статические переменные из кода +2) Декларировать зависимости +3) И использовать dependency injection -Обдумывая дизайн кода, помните, что каждый `static $foo` представляет собой проблему. Для того чтобы ваш код стал средой, уважающей DI, необходимо полностью искоренить глобальное состояние и заменить его инъекцией зависимостей. +Когда вы продумываете дизайн кода, помните, что каждое `static $foo` представляет собой проблему. Чтобы ваш код был средой, уважающей DI, необходимо полностью искоренить глобальное состояние и заменить его с помощью dependency injection. -Во время этого процесса вы можете обнаружить, что вам нужно разделить класс, потому что он несет более одной ответственности. Не беспокойтесь об этом; стремитесь к принципу одной ответственности. +В ходе этого процесса вы, возможно, обнаружите, что нужно разделить класс, потому что у него более одной ответственности. Не бойтесь этого; стремитесь к принципу единственной ответственности. -*Я хотел бы поблагодарить Мишко Хевери, чьи статьи, такие как [Flaw: Brittle Global State & Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/], легли в основу этой главы*. +*Я хотел бы поблагодарить Мишко Хеверого, чьи статьи, такие как [Flaw: Brittle Global State & Singletons |https://web.archive.org/web/20230321084133/http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/], легли в основу этой главы.* diff --git a/dependency-injection/ru/introduction.texy b/dependency-injection/ru/introduction.texy index b857b16e93..95c80acebd 100644 --- a/dependency-injection/ru/introduction.texy +++ b/dependency-injection/ru/introduction.texy @@ -1,58 +1,58 @@ -Что такое «внедрение зависимостей»? -*********************************** +Что такое Dependency Injection? +******************************* .[perex] -Эта глава знакомит вас с основными практиками программирования, которым вы должны следовать при написании любого приложения. Это основы, необходимые для написания чистого, понятного и сопровождаемого кода. +Эта глава познакомит вас с основными методами программирования, которым вы должны следовать при написании всех приложений. Это основы, необходимые для написания чистого, понятного и поддерживаемого кода. -Если вы усвоите эти правила и будете следовать им, Nette будет помогать вам на каждом шагу. Он будет выполнять за вас рутинные задачи и обеспечит вам максимальный комфорт, чтобы вы могли сосредоточиться на самой логике. +Если вы освоите эти правила и будете им следовать, Nette будет помогать вам на каждом шагу. Он будет решать за вас рутинные задачи и обеспечит максимальное удобство, чтобы вы могли сосредоточиться на самой логике. -Принципы, которые мы здесь покажем, довольно просты. Вам не о чем беспокоиться. +Принципы, которые мы здесь покажем, довольно просты. Вам не нужно ничего бояться. -Помните свою первую программу? .[#toc-remember-your-first-program] ------------------------------------------------------------------- +Помните свою первую программу? +------------------------------ -Мы понятия не имеем, на каком языке вы её написали, но если бы это был PHP, она, вероятно, выглядела бы примерно так: +Мы не знаем, на каком языке вы ее написали, но если бы это был PHP, она, вероятно, выглядела бы так: ```php -function addition(float $a, float $b): float +function soucet(float $a, float $b): float { return $a + $b; } -echo addition(23, 1); // выводит 24 +echo soucet(23, 1); // выведет 24 ``` -Несколько тривиальных строк кода, но в них скрыто так много ключевых понятий. Мы видим, что есть переменные. Что код разбивается на более мелкие единицы, которыми являются, например, функции. Что мы передаем им входные аргументы, а они возвращают результаты. Всё, чего не хватает — это условия и циклы. +Несколько тривиальных строк кода, но в них скрыто так много ключевых концепций. Что существуют переменные. Что код делится на меньшие единицы, такие как функции. Что мы передаем им входные аргументы, и они возвращают результаты. Не хватает только условий и циклов. -То, что мы передаем функции входные данные, а она возвращает результат — это вполне понятная концепция, которая используется и в других областях, например, в математике. +То, что мы передаем функции входные данные, и она возвращает результат, — это совершенно понятная концепция, которая используется и в других областях, например, в математике. -Функция имеет сигнатуру, которая состоит из её имени, списка параметров и их типов, и, наконец, типа возвращаемого значения. Как пользователей, нас интересует сигнатура; нам обычно не нужно знать ничего о внутренней реализации. +Функция имеет свою сигнатуру, которая состоит из ее имени, списка параметров и их типов, и, наконец, типа возвращаемого значения. Как пользователей, нас интересует сигнатура, о внутренней реализации нам обычно ничего знать не нужно. -Теперь представьте, что сигнатура функции выглядит следующим образом: +Теперь представьте, что сигнатура функции выглядела бы так: ```php -function addition(float $x): float +function soucet(float $x): float ``` -Дополнение с одним параметром? Странно... Как насчет этого? +Сумма с одним параметром? Это странно… А как насчет этого? ```php -function addition(): float +function soucet(): float ``` -Это очень странно, не так ли? Как вы думаете, как используется эта функция? +Это уже действительно очень странно, не так ли? Как используется эта функция? ```php -echo addition(); // что здесь выводится? +echo soucet(); // что она выведет? ``` -Глядя на такой код, мы приходим в замешательство. Не только новичок не поймет его, даже опытный программист не разберется в таком коде. +Глядя на такой код, мы были бы сбиты с толку. Его не понял бы не только новичок, но и опытный программист. -Вам интересно, как на самом деле будет выглядеть такая функция внутри? Откуда она будет брать слагаемые? Скорее всего, она бы *каким-то образом* получала их сама, возможно, следующим образом: +Вы думаете, как бы выглядела такая функция внутри? Откуда она возьмет слагаемые? Очевидно, она бы их *каким-то образом* получила сама, например, так: ```php -function addition(): float +function soucet(): float { $a = Input::get('a'); $b = Input::get('b'); @@ -60,71 +60,71 @@ function addition(): float } ``` -Оказывается, в теле функции есть скрытые привязки к другим функциям (или статическим методам), и чтобы выяснить, откуда на самом деле берутся дополнения, нужно копать дальше. +В теле функции мы обнаружили скрытые связи с другими глобальными функциями или статическими методами. Чтобы выяснить, откуда на самом деле берутся слагаемые, нам нужно копать дальше. -Не так! .[#toc-not-this-way] ----------------------------- +Так нельзя! +----------- -Дизайн, который мы только что показали, является сущностью многих отрицательных признаков: +Дизайн, который мы только что показали, является квинтэссенцией многих негативных черт: -- сигнатура функции делает вид, что ей не нужны слагаемые, что сбивает нас с толку -- мы понятия не имеем, как заставить функцию вычислять с двумя другими числами -- нам пришлось заглянуть в код, чтобы понять, где она берет слагаемые -- мы обнаружили скрытые привязки -- Для полного понимания нам нужно изучить и эти привязки. +- сигнатура функции делала вид, что ей не нужны слагаемые, что сбивало нас с толку +- мы совершенно не знаем, как заставить функцию сложить два других числа +- нам пришлось заглянуть в код, чтобы выяснить, откуда она берет слагаемые +- мы обнаружили скрытые связи +- для полного понимания необходимо изучить и эти связи -А входит ли вообще в задачу функции сложения получение исходных данных? Конечно, нет. В её обязанности входит только сложение. +И вообще, задача функции сложения — получать входные данные? Конечно, нет. Ее ответственность — только само сложение. -Мы не хотим сталкиваться с таким кодом, и уж точно не хотим его писать. Решение простое: вернитесь к основам и просто используйте параметры: +Мы не хотим сталкиваться с таким кодом, и уж точно не хотим его писать. Исправление при этом простое: вернуться к основам и просто использовать параметры: ```php -function addition(float $a, float $b): float +function soucet(float $a, float $b): float { return $a + $b; } ``` -Правило №1: Пусть вам передадут .[#toc-rule-1-let-it-be-passed-to-you] ----------------------------------------------------------------------- +Правило № 1: пусть тебе это передадут +------------------------------------- -Самое важное правило гласит: **все данные, которые нужны функциям или классам, должны быть переданы им**. +Самое важное правило гласит: **все данные, которые нужны функции или классу, должны быть им переданы**. -Вместо того, чтобы придумывать скрытые механизмы, чтобы помочь им как-то добраться до них самим, просто передавайте параметры. Вы сэкономите время, которое уходит на придумывание скрытых способов, которые точно не улучшат ваш код. +Вместо того чтобы изобретать скрытые способы, с помощью которых они могли бы как-то получить их сами, просто передайте параметры. Вы сэкономите время, необходимое на придумывание скрытых путей, которые определенно не улучшат ваш код. -Если вы будете следовать этому правилу всегда и везде, вы на пути к коду без скрытых привязок. К коду, который понятен не только автору, но и любому, кто его потом прочитает. Где все понятно из сигнатур функций и классов и нет необходимости искать скрытые секреты в реализации. +Если вы будете всегда и везде следовать этому правилу, вы на пути к коду без скрытых связей. К коду, который понятен не только автору, но и всем, кто будет его читать после него. Где все понятно из сигнатур функций и классов, и не нужно искать скрытые тайны в реализации. -Эта техника мастерски называется **инъекция зависимости**. А данные называются **зависимости**. Но это простая передача параметров, не более того. +Эта техника профессионально называется **dependency injection**. А эти данные называются **зависимостями.** При этом это обычная передача параметров, ничего больше. .[note] -Пожалуйста, не путайте инъекцию зависимостей, которая является паттерном проектирования, с "контейнером инъекции зависимостей", который является инструментом, совершенно другим. О контейнерах мы поговорим позже. +Пожалуйста, не путайте dependency injection, который является паттерном проектирования, с «dependency injection container», который является инструментом, то есть чем-то диаметрально противоположным. Контейнерам мы посвятим внимание позже. -От функций к классам .[#toc-from-functions-to-classes] ------------------------------------------------------- +От функций к классам +-------------------- -А как классы связаны с этим? Класс — это более сложная сущность, чем простая функция, но и здесь действует правило №1. Просто есть [больше способов передачи аргументов|passing-dependencies]. Например, очень похоже на случай с функцией: +А как с этим связаны классы? Класс — это более сложная единица, чем простая функция, однако правило № 1 действует здесь без исключений. Просто существует [больше возможностей для передачи аргументов|passing-dependencies]. Например, довольно похоже на случай с функцией: ```php -class Math +class Matematika { - public function addition(float $a, float $b): float + public function soucet(float $a, float $b): float { return $a + $b; } } -$math = new Math; -echo $math->addition(23, 1); // 24 +$math = new Matematika; +echo $math->soucet(23, 1); // 24 ``` -Или с помощью других методов, или при помощи конструктора: +Или с помощью других методов, или непосредственно конструктора: ```php -class Addition +class Soucet { public function __construct( private float $a, @@ -132,24 +132,24 @@ class Addition ) { } - public function calculate(): float + public function spocti(): float { return $this->a + $this->b; } } -$addition = new Addition(23, 1); -echo $addition->calculate(); // 24 +$soucet = new Soucet(23, 1); +echo $soucet->spocti(); // 24 ``` -Оба примера полностью соответствуют принципу внедрения зависимостей. +Оба примера полностью соответствуют dependency injection. -Примеры из реальной жизни .[#toc-real-life-examples] ----------------------------------------------------- +Реальные примеры +---------------- -В реальном мире вы не будете писать классы для сложения чисел. Давайте перейдем к примерам из реальной жизни. +В реальном мире вы не будете писать классы для сложения чисел. Давайте перейдем к примерам из практики. Пусть у нас есть класс `Article`, представляющий статью в блоге: @@ -162,23 +162,23 @@ class Article public function save(): void { - // сохранить статью в базе данных + // сохраним статью в базу данных } } ``` -а его использование будет следующим: +и использование будет следующим: ```php $article = new Article; -$article->title = '10 вещей, которые нужно знать о потере веса'; -$article->content = 'Каждый год миллионы людей в ...'; +$article->title = '10 Things You Need to Know About Losing Weight'; +$article->content = 'Every year millions of people in ...'; $article->save(); ``` -Метод `save()` сохранит статью в таблице базы данных. Реализация этого метода с использованием [Nette Database |database:] была бы простым делом, если бы не одна загвоздка: где `Article` получает соединение с базой данных, т. е. объект класса `Nette\Database\Connection`? +Метод `save()` сохраняет статью в таблицу базы данных. Реализовать его с помощью [Nette Database |database:] было бы легко, если бы не одна загвоздка: где `Article` возьмет подключение к базе данных, т. е. объект класса `Nette\Database\Connection`? -Кажется, у нас есть много вариантов. Он может взять его из какой-то статической переменной. Или наследоваться от класса, который будет предоставлять соединение с базой данных. Или воспользоваться так называемым синглтоном. Или воспользоваться так называемыми фасадами, которые используются в Laravel: +Кажется, у нас много вариантов. Он может взять его откуда-то из статической переменной. Или унаследовать от класса, который обеспечивает соединение с базой данных. Или использовать так называемый [синглтон |global-state#Singleton]. Или так называемые фасады, которые используются в Laravel: ```php use Illuminate\Support\Facades\DB; @@ -203,13 +203,13 @@ class Article Или нет? -Давайте вспомним [правило №1: Let It Be Passed to You |#rule #1: Let It Be Passed to You]: все зависимости, которые нужны классу, должны быть переданы ему. Потому что если мы нарушим это правило, то вступим на путь грязного кода, полного скрытых зависимостей, непонятности, и в результате получим приложение, которое будет больно поддерживать и развивать. +Напомним [правилу № 1: пусть тебе это передадут |#Правило 1: пусть тебе это передадут]: все зависимости, которые нужны классу, должны быть ему переданы. Потому что если мы нарушим правило, мы встанем на путь грязного кода, полного скрытых связей, непонятности, и результатом будет приложение, которое будет больно поддерживать и развивать. -Пользователь класса `Article` понятия не имеет, где метод `save()` хранит статью. В таблице базы данных? В какой, в производственной или тестовой? И как ее можно изменить? +Пользователь класса `Article` не знает, куда метод `save()` сохраняет статью. В таблицу базы данных? В какую, рабочую или тестовую? И как это можно изменить? -Пользователь должен посмотреть, как реализован метод `save()`, и находит использование метода `DB::insert()`. Значит, ему придется искать дальше, чтобы выяснить, как этот метод получает соединение с базой данных. А скрытые зависимости могут образовать довольно длинную цепочку. +Пользователь должен посмотреть, как реализован метод `save()`, и найдет использование метода `DB::insert()`. Значит, он должен искать дальше, как этот метод получает соединение с базой данных. А скрытые связи могут образовывать довольно длинную цепочку. -В чистом и хорошо спроектированном коде никогда нет никаких скрытых зависимостей, фасадов Laravel или статических переменных. В чистом и хорошо продуманном коде передаются аргументы: +В чистом и хорошо спроектированном коде никогда не встречаются скрытые связи, фасады Laravel или статические переменные. В чистом и хорошо спроектированном коде передаются аргументы: ```php class Article @@ -224,7 +224,7 @@ class Article } ``` -Еще более практичным, как мы увидим дальше, является использование конструктора: +Еще практичнее, как мы увидим далее, будет конструктор: ```php class Article @@ -245,11 +245,11 @@ class Article ``` .[note] -Если вы опытный программист, вы можете подумать, что у `Article` вообще не должно быть метода `save()`; он должен представлять собой чисто компонент данных, а о сохранении должен позаботиться отдельный репозиторий. В этом есть смысл. Но это выведет нас далеко за рамки данной темы - инъекции зависимостей - и попыток привести простые примеры. +Если вы опытный программист, вы, возможно, подумаете, что `Article` вообще не должен иметь метод `save()`, он должен представлять собой чисто компонент данных, а сохранением должен заниматься отдельный репозиторий. Это имеет смысл. Но так мы бы ушли далеко за рамки темы, которой является dependency injection, и стремления приводить простые примеры. -Если вы, например, собираетесь написать класс, которому для работы требуется база данных, не выясняйте, откуда ее взять, а пусть она будет передана вам. Возможно, в качестве параметра конструктора или другого метода. Объявляйте зависимости. Выявляйте их в API вашего класса. Вы получите понятный и предсказуемый код. +Если вы пишете класс, требующий для своей работы, например, базу данных, не придумывайте, откуда ее взять, а попросите передать ее вам. Например, как параметр конструктора или другого метода. Признайте зависимости. Признайте их в API вашего класса. Вы получите понятный и предсказуемый код. -Как насчет класса, который регистрирует сообщения об ошибках: +А как насчет этого класса, который логирует сообщения об ошибках: ```php class Logger @@ -262,13 +262,13 @@ class Logger } ``` -Как вы думаете, мы выполнили [правило №1: Пусть вам передадут |#rule #1: Let It Be Passed to You]? +Как вы думаете, мы соблюли [правило № 1: пусть тебе это передадут |#Правило 1: пусть тебе это передадут]? -Нет. +Не соблюли. -Класс *получает* ключевую информацию, директорию, содержащую файл журнала, из константы. +Ключевую информацию, то есть каталог с файлом лога, класс *получает сам* из константы. -Посмотрите пример использования: +Посмотрите на пример использования: ```php $logger = new Logger; @@ -276,7 +276,7 @@ $logger->log('Температура 23 °C'); $logger->log('Температура 10 °C'); ``` -Не зная реализации, можете ли вы ответить на вопрос, где записаны сообщения? Не кажется ли вам, что существование константы LOG_DIR необходимо для его работы? И сможете ли вы создать второй экземпляр, который будет писать в другое место? Конечно, нет. +Не зная реализации, смогли бы вы ответить на вопрос, куда записываются сообщения? Пришло бы вам в голову, что для работы необходимо существование константы `LOG_DIR`? И смогли бы вы создать второй экземпляр, который будет записывать в другое место? Определенно нет. Давайте исправим класс: @@ -295,24 +295,24 @@ class Logger } ``` -Теперь класс стал намного понятнее, более настраиваемым и, следовательно, более полезным: +Класс теперь гораздо понятнее, конфигурируемее и, следовательно, полезнее. ```php -$logger = new Logger('/path/to/log.txt'); +$logger = new Logger('/путь/к/логу.txt'); $logger->log('Температура 15 °C'); ``` -Но мне всё равно! .[#toc-but-i-don-t-care] ------------------------------------------- +Но меня это не интересует! +-------------------------- -*«Когда я создаю объект Article и вызываю save(), я не хочу иметь дело с базой данных, я просто хочу, чтобы он был сохранен в той, которую я установил в конфигурации.»* +*«Когда я создаю объект Article и вызываю save(), я не хочу заниматься базой данных, я просто хочу, чтобы он сохранился в ту, которую я настроил в конфигурации.»* -*«Когда я использую Logger, я просто хочу, чтобы сообщение было записано, и я не хочу разбираться с тем, куда. Пусть используются глобальные настройки.»* +*«Когда я использую Logger, я просто хочу, чтобы сообщение записалось, и не хочу думать, куда. Пусть используется глобальная настройка.»* -Это правильные комментарии. +Это правильные замечания. -В качестве примера возьмем класс, который рассылает информационные уведомления и регистрирует в журнале результаты рассылки: +В качестве примера покажем класс, рассылающий новостные письма, который залогирует, как все прошло: ```php class NewsletterDistributor @@ -322,21 +322,21 @@ class NewsletterDistributor $logger = new Logger(/* ... */); try { $this->sendEmails(); - $logger->log('Были разосланы электронные письма'); + $logger->log('Письма были разосланы'); } catch (Exception $e) { - $logger->log('Во время отправки произошла ошибка'); + $logger->log('Произошла ошибка при рассылке'); throw $e; } } } ``` -Улучшенный `Logger`, который больше не использует константу `LOG_DIR`, требует указания пути к файлу в конструкторе. Как решить эту проблему? Классу `NewsletterDistributor` все равно, куда записываются сообщения, он просто хочет их записать. +Улучшенный `Logger`, который больше не использует константу `LOG_DIR`, требует указать путь к файлу в конструкторе. Как это решить? Класс `NewsletterDistributor` совершенно не интересует, куда записываются сообщения, он хочет их просто записать. -Решением снова является [правило №1: Пусть вам передадут |#rule #1: Let It Be Passed to You]: передавайте классу все данные, которые ему нужны. +Решение снова [правило № 1: пусть тебе это передадут |#Правило 1: пусть тебе это передадут]: все данные, которые нужны классу, мы ему передаем. -Поэтому мы передаем путь к журналу в конструктор, который затем используем для создания объекта `Logger`? +Значит ли это, что мы передадим путь к логу через конструктор, который затем используем при создании объекта `Logger`? ```php class NewsletterDistributor @@ -351,7 +351,7 @@ class NewsletterDistributor $logger = new Logger($this->file); ``` -Не так! Потому что путь **не** принадлежит к данным, которые нужны классу `NewsletterDistributor`; ему нужен `Logger`. Классу нужен сам логгер. И именно его мы и передадим: +Не так! Путь **не относится** к данным, которые нужны классу `NewsletterDistributor`; они нужны `Logger`. Чувствуете разницу? Классу `NewsletterDistributor` нужен логгер как таковой. Значит, его мы и передадим: ```php class NewsletterDistributor @@ -365,35 +365,33 @@ class NewsletterDistributor { try { $this->sendEmails(); - $this->logger->log('Были разосланы электронные письма'); + $this->logger->log('Письма были разосланы'); } catch (Exception $e) { - $this->logger->log('Во время отправки произошла ошибка'); + $this->logger->log('Произошла ошибка при рассылке'); throw $e; } } } ``` -Теперь из сигнатур класса `NewsletterDistributor` ясно, что ведение журнала является частью его функциональности. И задача замены логгера на другой, возможно, в целях тестирования, достаточно тривиальна. -Более того, если конструктор класса `Logger` будет изменен, это никак не повлияет на наш класс. +Теперь из сигнатур класса `NewsletterDistributor` ясно, что частью его функциональности является логирование. И задача заменить логгер на другой, например, для тестирования, совершенно тривиальна. Кроме того, если конструктор класса `Logger` изменится, это никак не повлияет на наш класс. -Правило №2: Берите то, что принадлежит вам .[#toc-rule-2-take-what-s-yours] ---------------------------------------------------------------------------- +Правило № 2: бери то, что твое +------------------------------ -Не вводите себя в заблуждение и не позволяйте передавать вам параметры зависимостей. Передавайте зависимости напрямую. +Не позволяйте себя обмануть и не позволяйте передавать вам зависимости ваших зависимостей. Пусть вам передают только ваши зависимости. -Это сделает код, использующий другие объекты, полностью независимым от изменений в их конструкторах. Его API будет более правильным. И самое главное, будет тривиально поменять эти зависимости на другие. +Благодаря этому код, использующий другие объекты, будет полностью независим от изменений их конструкторов. Его API будет правдивее. И главное, будет тривиально заменить эти зависимости на другие. -Новый член семьи .[#toc-new-family-member] ------------------------------------------- +Новый член семьи +---------------- -Команда разработчиков решила создать второй регистратор, который пишет в базу данных. Поэтому мы создаем класс `DatabaseLogger`. Итак, у нас есть два класса, `Logger` и `DatabaseLogger`, один пишет в файл, другой в базу данных... не кажется ли вам такое именование странным? -Не лучше ли переименовать `Logger` в `FileLogger`? Определенно да. +В команде разработчиков было принято решение создать второй логгер, который записывает в базу данных. Создадим класс `DatabaseLogger`. Итак, у нас есть два класса, `Logger` и `DatabaseLogger`, один записывает в файл, другой в базу данных… вам не кажется, что в этом названии что-то странное? Не лучше ли было бы переименовать `Logger` в `FileLogger`? Определенно да. -Но давайте сделаем это по-умному. Мы создадим интерфейс под оригинальным именем: +Но мы сделаем это умнее. Под старым названием создадим интерфейс: ```php interface Logger @@ -402,7 +400,7 @@ interface Logger } ``` -... которые будут реализованы обоими регистраторами: +… который будут реализовывать оба логгера: ```php class FileLogger implements Logger @@ -412,17 +410,17 @@ class DatabaseLogger implements Logger // ... ``` -И благодаря этому не нужно будет ничего менять в остальной части кода, где используется логгер. Например, конструктор класса `NewsletterDistributor` по-прежнему будет удовлетворен тем, что потребует в качестве параметра `Logger`. И только от нас будет зависеть, какой экземпляр мы передадим. +И благодаря этому не нужно будет ничего менять в остальном коде, где используется логгер. Например, конструктор класса `NewsletterDistributor` по-прежнему будет доволен тем, что в качестве параметра требует `Logger`. И только от нас будет зависеть, какой экземпляр мы ему передадим. -**Вот почему мы никогда не добавляем суффикс `Interface` или префикс `I` к именам интерфейсов.** Иначе невозможно было бы так красиво разработать код. +**Поэтому мы никогда не добавляем к именам интерфейсов суффикс `Interface` или префикс `I`.** Иначе было бы невозможно так красиво развивать код. -Хьюстон, у нас проблема .[#toc-houston-we-have-a-problem] ---------------------------------------------------------- +Хьюстон, у нас проблема +----------------------- -В то время как мы можем обойтись одним экземпляром регистратора, будь то файловый или основанный на базе данных, во всем приложении и просто передавать его везде, где что-то регистрируется, с классом `Article` дело обстоит совсем иначе. Мы создаем его экземпляры по мере необходимости, даже несколько раз. Как быть с зависимостью от базы данных в его конструкторе? +В то время как во всем приложении мы можем обойтись одним экземпляром логгера, будь то файловый или баз данных, и просто передавать его везде, где что-то логируется, совершенно иначе обстоит дело с классом `Article`. Его экземпляры мы создаем по мере необходимости, возможно, несколько раз. Как справиться с зависимостью от базы данных в его конструкторе? -Примером может быть контроллер, который должен сохранять статью в базу данных после отправки формы: +В качестве примера может служить контроллер, который после отправки формы должен сохранить статью в базу данных: ```php class EditController extends Controller @@ -437,30 +435,30 @@ class EditController extends Controller } ``` -Возможное решение очевидно: передать объект базы данных в конструктор `EditController` и использовать `$article = new Article($this->db)`. +Возможное решение напрашивается само собой: передадим объект базы данных конструктором в `EditController` и используем `$article = new Article($this->db)`. -Как и в предыдущем случае с `Logger` и путем к файлу, это неправильный подход. База данных является зависимостью не от `EditController`, а от `Article`. Передача базы данных противоречит [правилу #2: бери то, что принадлежит тебе |#rule #2: take what's yours]. Если конструктор класса `Article` изменится (добавится новый параметр), вам придется модифицировать код везде, где создаются экземпляры. Уффф. +Как и в предыдущем случае с `Logger` и путем к файлу, это неправильный подход. База данных — это не зависимость `EditController`, а `Article`. Передача базы данных противоречит [правилу № 2: бери то, что твое |#Правило 2: бери то что твое]. Когда изменится конструктор класса `Article` (добавится новый параметр), придется изменять код во всех местах, где создаются экземпляры. Уфф. -Хьюстон, что вы предлагаете? +Хьюстон, что предлагаешь? -Правило №3: Пусть завод сам разбирается с этим .[#toc-rule-3-let-the-factory-handle-it] ---------------------------------------------------------------------------------------- +Правило № 3: оставь это фабрике +------------------------------- -Устранив скрытые зависимости и передавая все зависимости в качестве аргументов, мы получили более настраиваемые и гибкие классы. И поэтому нам нужно что-то еще, чтобы создавать и конфигурировать эти более гибкие классы для нас. Мы будем называть это фабриками. +Устранив скрытые связи и передавая все зависимости как аргументы, мы получили более конфигурируемые и гибкие классы. Следовательно, нам нужно что-то еще, что создаст и настроит нам эти более гибкие классы. Будем называть это фабриками. -Эмпирическое правило таково: если класс имеет зависимости, оставьте создание их экземпляров фабрике. +Правило гласит: если у класса есть зависимости, пусть создание их экземпляров берет на себя фабрика. -Фабрики - это более разумная замена оператора `new` в мире инъекций зависимостей. +Фабрики — это более умная замена оператору `new` в мире dependency injection. .[note] -Пожалуйста, не путайте с шаблоном проектирования *factory method*, который описывает конкретный способ использования фабрик и не имеет отношения к данной теме. +Пожалуйста, не путайте с паттерном проектирования *factory method*, который описывает специфический способ использования фабрик и не связан с этой темой. -Фабрика .[#toc-factory] ------------------------ +Фабрика +------- -Фабрика - это метод или класс, который производит и настраивает объекты. Мы называем `Article` производящий класс `ArticleFactory`, и он может выглядеть следующим образом: +Фабрика — это метод или класс, который производит и конфигурирует объекты. Класс, производящий `Article`, назовем `ArticleFactory` и он мог бы выглядеть, например, так: ```php class ArticleFactory @@ -477,7 +475,7 @@ class ArticleFactory } ``` -Его использование в контроллере будет выглядеть следующим образом: +Его использование в контроллере будет следующим: ```php class EditController extends Controller @@ -489,7 +487,7 @@ class EditController extends Controller public function formSubmitted($data) { - // позволить фабрике создать объект + // позволим фабрике создать объект $article = $this->articleFactory->create(); $article->title = $data->title; $article->content = $data->content; @@ -498,11 +496,11 @@ class EditController extends Controller } ``` -На данный момент, когда сигнатура конструктора класса `Article` изменяется, единственная часть кода, которая должна реагировать, это сама фабрика `ArticleFactory`. Любой другой код, работающий с объектами `Article`, например `EditController`, не будет затронут. +Если в этот момент изменится сигнатура конструктора класса `Article`, единственная часть кода, которая должна на это отреагировать, — это сама фабрика `ArticleFactory`. Весь остальной код, работающий с объектами `Article`, такой как `EditController`, это никак не затронет. -Возможно, сейчас вы стучите себя по лбу, задаваясь вопросом, помогли ли мы себе вообще. Количество кода выросло, и все это начинает выглядеть подозрительно сложным. +Возможно, вы сейчас стучите себя по лбу, думая, помогли ли мы себе вообще. Количество кода выросло, и все это начинает выглядеть подозрительно сложно. -Не волнуйтесь, скоро мы перейдем к контейнеру Nette DI. А у него есть несколько тузов в рукаве, которые сделают создание приложений с использованием инъекции зависимостей чрезвычайно простым. Например, вместо класса `ArticleFactory` достаточно [написать простой интерфейс |factory]: +Не беспокойтесь, скоро мы доберемся до DI-контейнера Nette. А у него есть ряд козырей в рукаве, которые чрезвычайно упростят создание приложений, использующих dependency injection. Так, например, вместо класса `ArticleFactory` достаточно будет [написать всего лишь интерфейс |factory]: ```php interface ArticleFactory @@ -511,18 +509,18 @@ interface ArticleFactory } ``` -Но мы забегаем вперед, подождите :-) +Но мы забегаем вперед, потерпите еще немного :-) -Резюме .[#toc-summary] ----------------------- +Резюме +------ -В начале этой главы мы обещали показать вам процесс разработки чистого кода. Все, что для этого требуется, это чтобы классы: +В начале этой главы мы обещали показать вам, как проектировать чистый код. Достаточно классам -- [передавать необходимые им зависимости |#Rule #1: Let It Be Passed to You] -- [и наоборот, не передавать то, что им напрямую не нужно |#Rule #2: Take What's Yours] -- [и чтобы объекты с зависимостями лучше всего создавались в фабриках |#Rule #3: Let the Factory Handle it] +1) [передавать зависимости, которые им нужны |#Правило 1: пусть тебе это передадут] +2) [и наоборот, не передавать то, что им напрямую не нужно |#Правило 2: бери то что твое] +3) [и что объекты с зависимостями лучше всего создавать в фабриках |#Правило 3: оставь это фабрике] -На первый взгляд может показаться, что эти три правила не имеют далеко идущих последствий, но они приводят к радикально иному взгляду на проектирование кода. Стоит ли оно того? Разработчики, которые отказались от старых привычек и начали последовательно использовать внедрение зависимостей, считают этот шаг переломным моментом в своей профессиональной жизни. Он открыл для них мир понятных и поддерживаемых приложений. +На первый взгляд это может показаться не так, но эти три правила имеют далеко идущие последствия. Они ведут к радикально иному взгляду на проектирование кода. Стоит ли оно того? Программисты, отбросившие старые привычки и начавшие последовательно использовать dependency injection, считают этот шаг ключевым моментом в своей профессиональной жизни. Им открылся мир понятных и поддерживаемых приложений. -Но что если код не использует инъекцию зависимостей последовательно? Что если он опирается на статические методы или синглтоны? Вызывает ли это какие-либо проблемы? [Да, вызывает, и очень серьез ные |global-state]. +Но что, если код не использует последовательно dependency injection? Что, если он построен на статических методах или синглтонах? Приносит ли это какие-либо проблемы? [Приносит, и очень серьезные |global-state]. diff --git a/dependency-injection/ru/nette-container.texy b/dependency-injection/ru/nette-container.texy index f29a8732a8..781eac7cfa 100644 --- a/dependency-injection/ru/nette-container.texy +++ b/dependency-injection/ru/nette-container.texy @@ -1,10 +1,10 @@ -Nette DI-контейнер +Nette DI Контейнер ****************** .[perex] -Nette DI Container - одна из самых интересных частей фреймворка. Он может генерировать скомпилированные DI-контейнеры, которые чрезвычайно быстры и удивительно просты в настройке. +Nette DI — одна из самых интересных библиотек Nette. Она умеет генерировать и автоматически обновлять скомпилированные DI-контейнеры, которые чрезвычайно быстры и удивительно легко конфигурируются. -Nette DI - это библиотека, которая предоставляет инструменты для генерации, а также автоматического обновления классов контейнеров. Мы инструктируем его (обычно) с помощью конфигурационных файлов. Контейнер, который мы создали вручную в [предыдущем разделе|container], будет записан в конфигурационном [NEON|neon:format] формате следующим образом: +Структуру сервисов, которые должен создавать DI-контейнер, мы обычно определяем с помощью конфигурационных файлов в [формате NEON|neon:format]. Контейнер, который мы вручную создали в [предыдущей главе|container], был бы записан так: ```neon parameters: @@ -19,16 +19,15 @@ services: - UserController ``` -Нотация действительно краткая. +Запись действительно краткая. -Все зависимости, объявленные в конструкторах `ArticleFactory` и `UserController`, автоматически определяются и передаются Nette DI благодаря так называемому [автосвязыванию|autowiring]. Таким образом, вы можете сосредоточиться на разработке. -Поэтому даже если параметры изменятся, вам не нужно ничего менять в конфигурации. Nette автоматически регенерирует контейнер. Вы можете сосредоточиться исключительно на разработке приложения. +Все зависимости, объявленные в конструкторах классов `ArticleFactory` и `UserController`, Nette DI само обнаружит и передаст благодаря так называемому [autowiring|autowiring], поэтому в конфигурационном файле ничего указывать не нужно. Так что даже если параметры изменятся, вам не придется ничего менять в конфигурации. Контейнер Nette автоматически перегенерируется. Вы можете сосредоточиться исключительно на разработке приложения. -Если вы хотите передавать зависимости с помощью сеттера, мы можем добавить секцию [setup|services#Setup] в определение сервиса. +Если мы хотим передавать зависимости с помощью сеттеров, мы используем для этого секцию [setup |services#Setup]. -Nette DI будет фактически генерировать PHP-код для контейнера. Поэтому он чрезвычайно быстр, программист точно знает, что он делает, и даже может переступать через него. Для больших приложений контейнер может состоять из десятков тысяч строк, и поддерживать что-то подобное вручную, вероятно, уже невозможно. +Nette DI генерирует непосредственно PHP-код контейнера. Результатом является файл `.php`, который вы можете открыть и изучить. Благодаря этому вы точно видите, как работает контейнер. Вы также можете отлаживать его в IDE и пошагово выполнять. И главное: сгенерированный PHP чрезвычайно быстр. -Nette DI также может генерировать код [фабрики|factory] на основе интерфейса. Таким образом, вместо класса `ArticleFactory` вам нужно будет просто создать интерфейс: +Nette DI также умеет генерировать код [фабрик|factory] на основе предоставленного интерфейса. Поэтому вместо класса `ArticleFactory` нам достаточно будет создать в приложении только интерфейс: ```php interface ArticleFactory @@ -37,46 +36,45 @@ interface ArticleFactory } ``` -Вы можете найти полный пример [на GitHub|https://github.com/nette-examples/di-example-doc]. +Полный пример вы найдете [на GitHub|https://github.com/nette-examples/di-example-doc]. -Использование без фреймворка .[#toc-standalone-use] ---------------------------------------------------- +Самостоятельное использование +----------------------------- -Использовать Nette DI в приложении очень просто. Сначала мы установим его с помощью Composer (потому что загрузка zip-файлов уже устарела): +Внедрение библиотеки Nette DI в приложение очень просто. Сначала установим ее с помощью Composer (потому что скачивание zip-архивов тааак устарело): ```shell composer require nette/di ``` -Мы сохраним приведенную выше конфигурацию в файле `config.neon` и создадим контейнер, используя класс `Nette\DI\ContainerLoader`: +Следующий код создает экземпляр DI-контейнера согласно конфигурации, сохраненной в файле `config.neon`: ```php $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp'); -$class = $loader->load(function($compiler) { +$class = $loader->load(function ($compiler) { $compiler->loadConfig(__DIR__ . '/config.neon'); }); $container = new $class; ``` -Контейнер создается только один раз, его код записывается в кэш (каталог `__DIR__ . '/temp'`) и при последующих запросах считывается только оттуда. +Контейнер генерируется только один раз, его код записывается в кеш (каталог `__DIR__ . '/temp'`) и при последующих запросах просто загружается оттуда. -Методы `getService()` или `getByType()` используются для создания и получения сервисов. Таким образом мы создаем объект `UserController`: +Для создания и получения сервисов служат методы `getService()` или `getByType()`. Так мы создадим объект `UserController`: ```php -$database = $container->getByType(UserController::class); -$database->query('...'); +$controller = $container->getByType(UserController::class); +$controller->someMethod(); ``` -Во время разработки полезно включить режим автообновления, при котором контейнер автоматически обновляется при изменении любого класса или файла конфигурации. Просто укажите `true` в качестве второго аргумента в конструкторе `ContainerLoader`. +Во время разработки полезно активировать режим автообновления, когда контейнер автоматически перегенерируется, если изменяется какой-либо класс или конфигурационный файл. Достаточно указать в конструкторе `ContainerLoader` второй аргумент `true`. ```php $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp', true); ``` -Использование с Nette Framework .[#toc-using-it-with-the-nette-framework] -------------------------------------------------------------------------- +Использование с фреймворком Nette +--------------------------------- -Как мы показали, использование Nette DI не ограничивается приложениями, написанными на Nette Framework, вы можете развернуть его где угодно, используя всего 3 строки кода. -Если вы разрабатываете приложения в Nette, конфигурирование и создание контейнера осуществляется с помощью класса [Bootstrap|application:bootstrap#toc-di-container-configuration]. +Как мы показали, использование Nette DI не ограничено приложениями, написанными на Nette Framework, вы можете внедрить его где угодно с помощью всего 3 строк кода. Однако, если вы разрабатываете приложения на Nette Framework, за конфигурацию и создание контейнера отвечает [Bootstrap |application:bootstrapping#Конфигурация DI-контейнера]. diff --git a/dependency-injection/ru/passing-dependencies.texy b/dependency-injection/ru/passing-dependencies.texy index 31202bcf86..c798252965 100644 --- a/dependency-injection/ru/passing-dependencies.texy +++ b/dependency-injection/ru/passing-dependencies.texy @@ -3,22 +3,22 @@ <div class=perex> -Аргументы, или «зависимости» в терминологии DI, могут быть переданы классам следующими основными способами: +Аргументы, или в терминологии DI «зависимости», можно передавать в классы следующими основными способами: -* passing by constructor -* passing by method (called a setter) -* by setting a property +* передача через конструктор +* передача через метод (так называемый сеттер) +* установка переменной * методом, аннотацией или атрибутом *inject* </div> -Теперь мы проиллюстрируем различные варианты на конкретных примерах. +Теперь покажем каждый вариант на конкретных примерах. -Внедрение через конструктор .[#toc-constructor-injection] -========================================================= +Передача через конструктор +========================== -Зависимости передаются в качестве аргументов конструктору при создании объекта: +Зависимости передаются в момент создания объекта как аргументы конструктора: ```php class MyClass @@ -34,9 +34,9 @@ class MyClass $obj = new MyClass($cache); ``` -Эта форма полезна для обязательных зависимостей, которые абсолютно необходимы классу для функционирования, так как без них экземпляр не может быть создан. +Эта форма подходит для обязательных зависимостей, которые класс непременно нуждается для своей работы, так как без них экземпляр создать не получится. -Начиная с версии PHP 8.0, мы можем использовать более короткую форму обозначения ([constructor property promotion |https://blog.nette.org/ru/php-8-0-polnyj-obzor-novostej#toc-constructor-property-promotion]), которая функционально эквивалентна: +Начиная с PHP 8.0, мы можем использовать более короткую форму записи ([constructor property promotion |https://blog.nette.org/ru/php-8-0-complete-overview-of-news#toc-constructor-property-promotion]), которая функционально эквивалентна: ```php // PHP 8.0 @@ -49,7 +49,7 @@ class MyClass } ``` -Начиная с версии PHP 8.1, свойство может быть помечено флагом `readonly`, который объявляет, что содержимое свойства не будет изменяться: +Начиная с PHP 8.1, переменную можно пометить флагом `readonly`, который объявляет, что содержимое переменной больше не изменится: ```php // PHP 8.1 @@ -62,13 +62,13 @@ class MyClass } ``` -DI контейнер передает зависимости в конструктор автоматически, используя [autowiring]. Аргументы, которые нельзя передавать таким образом (например, строки, числа, булевы) [записать в конфигурации |services#Arguments]. +DI-контейнер передает зависимости конструктору автоматически с помощью [autowiring |autowiring]. Аргументы, которые таким образом передать нельзя (например, строки, числа, булевы значения), [записываем в конфигурации |services#Аргументы]. -Конструкторский ад .[#toc-constructor-hell] -------------------------------------------- +Ад конструкторов +---------------- -Термин *ад конструктора* относится к ситуации, когда дочерний класс наследуется от родительского класса, конструктор которого требует зависимостей, и дочерний класс тоже требует зависимостей. Он также должен принять и передать зависимости родительского класса: +Термин *constructor hell* (ад конструкторов) обозначает ситуацию, когда потомок наследует от родительского класса, конструктор которого требует зависимости, и в то же время потомок требует зависимости. При этом он должен принять и передать также родительские: ```php abstract class BaseClass @@ -85,7 +85,7 @@ final class MyClass extends BaseClass { private Database $db; - // ⛔ CONSTRUCTOR HELL + // ⛔ АД КОНСТРУКТОРОВ public function __construct(Cache $cache, Database $db) { parent::__construct($cache); @@ -94,11 +94,11 @@ final class MyClass extends BaseClass } ``` -Проблема возникает, когда мы хотим изменить конструктор класса `BaseClass`, например, когда добавляется новая зависимость. Тогда мы должны изменить все конструкторы дочерних классов. Что превращает такую модификацию в ад. +Проблема возникает в момент, когда мы захотим изменить конструктор класса `BaseClass`, например, когда добавится новая зависимость. Тогда необходимо изменить также все конструкторы потомков. Что превращает такое изменение в ад. -Как предотвратить это? Решение заключается в **приоритете композиции над наследованием**. +Как этого избежать? Решение — **отдавать предпочтение [композиции перед наследованием |faq#Почему композиция предпочтительнее наследования]**. -Поэтому давайте спроектируем код по-другому. Мы будем избегать абстрактных классов `Base*`. Вместо того чтобы `MyClass` получал некоторую функциональность, наследуя от `BaseClass`, эта функциональность будет передаваться ему как зависимость: +То есть спроектируем код иначе. Будем избегать [абстрактным |nette:introduction-to-object-oriented-programming#Абстрактные классы] `Base*` классов. Вместо того чтобы `MyClass` получал определенную функциональность путем наследования от `BaseClass`, он получит эту функциональность как зависимость: ```php final class SomeFunctionality @@ -125,10 +125,10 @@ final class MyClass ``` -Внедрение через сеттеры .[#toc-setter-injection] -================================================ +Передача сеттером +================= -Зависимости передаются путем вызова метода, который хранит их в приватном свойстве. Обычно эти методы именуются `set*()`, поэтому их называют сеттерами, но, конечно, они могут называться и по-другому. +Зависимости передаются вызовом метода, который сохраняет их в приватную переменную. Обычное соглашение об именовании этих методов — форма `set*()`, поэтому их называют сеттерами, но они, конечно, могут называться как угодно иначе. ```php class MyClass @@ -145,9 +145,9 @@ $obj = new MyClass; $obj->setCache($cache); ``` -Этот метод полезен для необязательных зависимостей, которые не нужны для функционирования класса, поскольку не гарантируется, что объект действительно получит их (т. е. что пользователь вызовет метод). +Этот способ подходит для необязательных зависимостей, которые не являются необходимыми для работы класса, так как не гарантируется, что объект действительно получит зависимость (т. е. что пользователь вызовет метод). -В то же время, этот метод позволяет неоднократно вызывать сеттер для изменения зависимости. Если это нежелательно, добавьте проверку в метод, или, начиная с PHP 8.1, пометьте свойство `$cache` флагом `readonly`. +В то же время этот способ позволяет вызывать сеттер повторно и таким образом изменять зависимость. Если это нежелательно, добавим в метод проверку, или с PHP 8.1 пометим свойство `$cache` флагом `readonly`. ```php class MyClass @@ -156,7 +156,7 @@ class MyClass public function setCache(Cache $cache): void { - if ($this->cache) { + if (isset($this->cache)) { throw new RuntimeException('The dependency has already been set'); } $this->cache = $cache; @@ -164,21 +164,20 @@ class MyClass } ``` -The setter call is defined in the DI container configuration in [section setup |services#Setup]. Also here the automatic passing of dependencies is used by autowiring: +Вызов сеттера определяем в конфигурации DI-контейнера в [ключе setup |services#Setup]. Здесь также используется автоматическая передача зависимостей с помощью autowiring: ```neon services: - - - create: MyClass + - create: MyClass setup: - setCache ``` -Внедрение через свойства .[#toc-property-injection] -=================================================== +Установка переменной +==================== -Зависимости передаются непосредственно в свойство: +Зависимости передаются записью непосредственно в переменную-член: ```php class MyClass @@ -190,28 +189,27 @@ $obj = new MyClass; $obj->cache = $cache; ``` -Этот метод считается неприемлемым, поскольку свойство должно быть объявлено как `public`. Следовательно, мы не можем контролировать, будет ли переданная зависимость действительно иметь указанный тип (это было верно до версии PHP 7.4), и мы теряем возможность реагировать на новую назначенную зависимость своим собственным кодом, например, чтобы предотвратить последующие изменения. В то же время, свойство становится частью публичного интерфейса класса, что может быть нежелательно. +Этот способ считается неподходящим, поскольку переменная-член должна быть объявлена как `public`. Следовательно, у нас нет контроля над тем, что переданная зависимость действительно будет данного типа (действовало до PHP 7.4), и мы теряем возможность реагировать на вновь назначенную зависимость собственным кодом, например, предотвратить последующее изменение. В то же время переменная становится частью публичного интерфейса класса, что может быть нежелательно. -Настройка переменной определяется в конфигурации контейнера DI в разделе [section setup |services#Setup]: +Установку переменной определяем в конфигурации DI-контейнера в [секции setup |services#Setup]: ```neon services: - - - create: MyClass + - create: MyClass setup: - $cache = @\Cache ``` -Инъекция .[#toc-inject] -======================= +Inject +====== -В то время как предыдущие три метода в целом применимы во всех объектно-ориентированных языках, инъектирование методом, аннотацией или атрибутом *inject* специфично для презентаторов Nette. Они рассматриваются в [отдельной главе |best-practices:inject-method-attribute]. +В то время как предыдущие три способа применимы в целом во всех объектно-ориентированных языках, инъекция методом, аннотацией или атрибутом *inject* специфична исключительно для презентеров в Nette. О них рассказывается в [отдельной главе |best-practices:inject-method-attribute]. -Какой путь выбрать? .[#toc-which-way-to-choose] -=============================================== +Какой способ выбрать? +===================== -- конструктор подходит для обязательных зависимостей, которые необходимы классу для функционирования -- сеттер, с другой стороны, подходит для необязательных зависимостей, или зависимостей, которые могут быть изменены -- публичные переменные не рекомендуются +- конструктор подходит для обязательных зависимостей, которые класс непременно нуждается для своей работы +- сеттер, наоборот, подходит для необязательных зависимостей или зависимостей, которые можно будет изменять в дальнейшем +- публичные переменные не подходят diff --git a/dependency-injection/ru/services.texy b/dependency-injection/ru/services.texy index 3af9d1f2e7..b6601ef372 100644 --- a/dependency-injection/ru/services.texy +++ b/dependency-injection/ru/services.texy @@ -1,33 +1,33 @@ -Определения сервисов +Определение сервисов ******************** .[perex] -Конфигурация — это место, где мы размещаем определения пользовательских сервисов. Делается это в секции `services`. +Конфигурация — это место, где мы учим DI-контейнер, как собирать отдельные сервисы и как связывать их с другими зависимостями. Nette предоставляет очень понятный и элегантный способ достижения этой цели. -Например, вот как мы создаем сервис с именем `database`, который будет экземпляром класса `PDO`: +Секция `services` в конфигурационном файле формата NEON — это место, где мы определяем собственные сервисы и их конфигурации. Посмотрим на простой пример определения сервиса с именем `database`, который представляет экземпляр класса `PDO`: ```neon services: database: PDO('sqlite::memory:') ``` -Именование служб используется для того, чтобы мы могли [ссылаться|#Referencing-Services] на них. Если на сервис не ссылаются, нет необходимости давать ему имя. Поэтому вместо имени мы просто используем двоеточие: +Указанная конфигурация приведет к следующему фабричному методу в [DI-контейнере|container]: -```neon -services: - - PDO('sqlite::memory:') # анонимный сервис +```php +public function createServiceDatabase(): PDO +{ + return new PDO('sqlite::memory:'); +} ``` -Однострочная запись может быть разбита на несколько строк, чтобы можно было добавить дополнительные ключи, например [#setup]. Псевдоним для ключа `create:` - `factory:`. +Имена сервисов позволяют нам ссылаться на них в других частях конфигурационного файла в формате `@имяСервиса`. Если нет необходимости именовать сервис, мы можем просто использовать дефис: ```neon services: - database: - create: PDO('sqlite::memory:') - setup: ... + - PDO('sqlite::memory:') ``` -Затем мы получаем сервис из контейнера DI, используя метод `getService()` по имени, или, что ещё лучше, метод `getByType()` по типу: +Для получения сервиса из DI-контейнера мы можем использовать метод `getService()` с именем сервиса в качестве параметра или метод `getByType()` с типом сервиса: ```php $database = $container->getService('database'); @@ -35,26 +35,28 @@ $database = $container->getByType(PDO::class); ``` -Создание сервиса .[#toc-creating-a-service] -=========================================== +Создание сервиса +================ -Чаще всего мы создаем сервис, просто создавая экземпляр класса: +Обычно мы создаем сервис, просто создавая экземпляр определенного класса. Например: ```neon services: database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -Что создаст фабричный метод в [DI-контейнере|container]: +Если нам нужно расширить конфигурацию дополнительными ключами, определение можно разбить на несколько строк: -```php -public function createServiceDatabase(): PDO -{ - return new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'secret'); -} +```neon +services: + database: + create: PDO('sqlite::memory:') + setup: ... ``` -Альтернативно, ключ `arguments` может быть использован для передачи [аргументов|#Arguments]: +Ключ `create` имеет псевдоним `factory`, оба варианта на практике распространены. Однако мы рекомендуем использовать `create`. + +Аргументы конструктора или метода создания могут быть альтернативно записаны в ключе `arguments`: ```neon services: @@ -63,294 +65,303 @@ services: arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret] ``` -Статический метод также может создать сервис: +Сервисы не обязательно должны создаваться только простым созданием экземпляра класса, они также могут быть результатом вызова статических методов или методов других сервисов: ```neon services: - database: My\Database::create(root, secret) + database: DatabaseFactory::create() + router: @routerFactory::create() ``` -Это эквивалентно коду PHP: +Обратите внимание, что для простоты вместо `->` используется `::`, см. [#выразительные средства]. Будут сгенерированы следующие фабричные методы: ```php public function createServiceDatabase(): PDO { - return My\Database::create('root', 'secret'); + return DatabaseFactory::create(); +} + +public function createServiceRouter(): RouteList +{ + return $this->getService('routerFactory')->create(); } ``` -Предполагается, что статический метод `My\Database::create()` имеет определенное возвращаемое значение, которое должен знать контейнер DI. Если у него его нет, мы записываем тип в конфигурацию: +DI-контейнеру необходимо знать тип созданного сервиса. Если мы создаем сервис с помощью метода, у которого не указан тип возвращаемого значения, мы должны явно указать этот тип в конфигурации: ```neon services: database: - create: My\Database::create(root, secret) + create: DatabaseFactory::create() type: PDO ``` -Nette DI предоставляет вам чрезвычайно мощные средства выражения, позволяющие написать практически всё, что угодно. Например, чтобы [обратиться|#Referencing-Services] к другой службе и вызвать её метод. Для простоты вместо `->` используется `::`. + +Аргументы +========= + +В конструктор и методы мы передаем аргументы способом, очень похожим на сам PHP: ```neon services: - routerFactory: App\Router\Factory - router: @routerFactory::create() + database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -Это эквивалентно коду PHP: +Для лучшей читаемости мы можем разбить аргументы на отдельные строки. В таком случае использование запятых необязательно: -```php -public function createServiceRouterFactory(): App\Router\Factory -{ - return new App\Router\Factory; -} - -public function createServiceRouter(): Router -{ - return $this->getService('routerFactory')->create(); -} +```neon +services: + database: PDO( + 'mysql:host=127.0.0.1;dbname=test' + root + secret + ) ``` -Вызовы методов можно объединять в цепочки, как в PHP: +Аргументы также можно именовать, и тогда не нужно беспокоиться об их порядке: ```neon services: - foo: FooFactory::build()::get() + database: PDO( + username: root + password: secret + dsn: 'mysql:host=127.0.0.1;dbname=test' + ) ``` -Это эквивалентно коду PHP: +Если вы хотите пропустить некоторые аргументы и использовать их значение по умолчанию или подставить сервис с помощью [autowiring|autowiring], используйте подчеркивание: -```php -public function createServiceFoo() -{ - return FooFactory::build()->get(); -} +```neon +services: + foo: Foo(_, %appDir%) ``` +В качестве аргументов можно передавать сервисы, использовать параметры и многое другое, см. [#выразительные средства]. -Аргументы .[#toc-arguments] -=========================== -Именованные параметры также могут использоваться для передачи аргументов: +Setup +===== + +В секции `setup` мы определяем методы, которые должны вызываться при создании сервиса. ```neon services: - database: PDO( - 'mysql:host=127.0.0.1;dbname=test' # позиционный - username: root # именованный - password: secret # именованный - ) + database: + create: PDO(%dsn%, %user%, %password%) + setup: + - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) ``` -Использование запятых необязательно при разбиении аргументов на несколько строк. +В PHP это выглядело бы так: + +```php +public function createServiceDatabase(): PDO +{ + $service = new PDO('...', '...', '...'); + $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + return $service; +} +``` -Конечно, мы также можем использовать [другие сервисы|#Referencing-Services] или [параметры|configuration#Parameters] в качестве аргументов: +Кроме вызова методов, можно также передавать значения в свойства. Поддерживается также добавление элемента в массив, которое необходимо записывать в кавычках, чтобы не конфликтовать с синтаксисом NEON: ```neon services: - - Foo(@anotherService, %appDir%) + foo: + create: Foo + setup: + - $value = 123 + - '$onClick[]' = [@bar, clickHandler] ``` -Соответствует коду PHP: +Что в PHP-коде выглядело бы следующим образом: ```php -public function createService01(): Foo +public function createServiceFoo(): Foo { - return new Foo($this->getService('anotherService'), '...'); + $service = new Foo; + $service->value = 123; + $service->onClick[] = [$this->getService('bar'), 'clickHandler']; + return $service; } ``` -Если первый аргумент - [автомонтируемый|autowiring], а вы хотите указать второй, опустите первый с помощью символа `_`, например `Foo(_, %appDir%)`. Или, что ещё лучше, передавайте только второй аргумент в качестве именованного параметра, например: `Foo(path: %appDir%)`. - -Nette DI и формат NEON дают вам чрезвычайно мощные выразительные средства, позволяющие написать практически всё, что угодно. Таким образом, аргументом может быть вновь созданный объект, вы можете вызывать статические методы, методы других сервисов или даже глобальные функции, используя специальную нотацию: +В setup можно также вызывать статические методы или методы других сервисов. Если вам нужно передать в качестве аргумента текущий сервис, укажите его как `@self`: ```neon services: - analyser: My\Analyser( - FilesystemIterator(%appDir%) # создаем объект - DateTime::createFromFormat('Y-m-d') # вызываем статический метод - @anotherService # передаём другой сервис - @http.request::getRemoteAddress() # вызываем метод другого сервиса - ::getenv(NetteMode) # вызываем глобальную функцию - ) + foo: + create: Foo + setup: + - My\Helpers::initializeFoo(@self) + - @anotherService::setFoo(@self) ``` -Соответствует коду PHP: +Обратите внимание, что для простоты вместо `->` используется `::`, см. [#выразительные средства]. Будет сгенерирован такой фабричный метод: ```php -public function createServiceAnalyser(): My\Analyser +public function createServiceFoo(): Foo { - return new My\Analyser( - new FilesystemIterator('...'), - DateTime::createFromFormat('Y-m-d'), - $this->getService('anotherService'), - $this->getService('http.request')->getRemoteAddress(), - getenv('NetteMode') - ); + $service = new Foo; + My\Helpers::initializeFoo($service); + $this->getService('anotherService')->setFoo($service); + return $service; } ``` -Специальные функции .[#toc-special-functions] ---------------------------------------------- - -Вы также можете использовать специальные функции в аргументах для приведения или отрицания значений: +Выразительные средства +====================== -- `not(%arg%)` отрицание -- `bool(%arg%)` приведение к bool без потерь -- `int(%arg%)` приведение к int без потерь -- `float(%arg%)` приведение к плавающему состоянию без потерь -- `string(%arg%)` приведение к строке без потерь +Nette DI предоставляет нам чрезвычайно богатые выразительные средства, с помощью которых мы можем записать почти все что угодно. В конфигурационных файлах мы можем использовать [параметры |configuration#Параметры]: ```neon -services: - - Foo( - id: int(::getenv('ProjectId')) - productionMode: not(%debugMode%) - ) -``` - -Переписывание без потерь отличается от обычного переписывания в PHP, например используя `(int)`, в том, что оно выбрасывает исключение для нечисловых значений. +# параметр +%wwwDir% -В качестве аргументов можно передавать несколько сервисов. Массив всех сервисов определенного типа (т. е. класса или интерфейса) создается функцией `typed()`. Функция будет опускать сервисы, у которых отключено автоподключение, при этом можно указать несколько типов, разделенных запятой. +# значение параметра под ключом +%mailer.user% -```neon -services: - - BarsDependent( typed(Bar) ) +# параметр внутри строки +'%wwwDir%/images' ``` -Вы также можете передать массив сервисов автоматически, используя [автосвязывание|autowiring#Collection-of-Services]. - -Массив всех сервисов с определенным [тегом|#Tags] создается функцией `tagged()`. Можно указать несколько тегов, разделенных запятой. +Далее создавать объекты, вызывать методы и функции: ```neon -services: - - LoggersDependent( tagged(logger) ) -``` +# создание объекта +DateTime() +# вызов статического метода +Collator::create(%locale%) -Ссылки на сервисы .[#toc-referencing-services] -============================================== +# вызов PHP функции +::getenv(DB_USER) +``` -Ссылки на отдельные сервисы используются с помощью символа `@` и имени, например `@database`: +Ссылаться на сервисы либо по их имени, либо по типу: ```neon -services: - - create: Foo(@database) - setup: - - setCacheStorage(@cache.storage) +# сервис по имени +@database + +# сервис по типу +@Nette\Database\Connection ``` -Соответствует коду PHP: +Использовать синтаксис first-class callable: .{data-version:3.2.0} -```php -public function createService01(): Foo -{ - $service = new Foo($this->getService('database')); - $service->setCacheStorage($this->getService('cache.storage')); - return $service; -} +```neon +# создание callback, аналог [@user, logout] +@user::logout(...) ``` -Даже на анонимные сервисы можно ссылаться с помощью обратного вызова, просто укажите их тип (класс или интерфейс) вместо имени. Однако обычно в этом нет необходимости из-за [автосвязывания|autowiring]. +Использовать константы: ```neon -services: - - create: Foo(@Nette\Database\Connection) # или @\PDO - setup: - - setCacheStorage(@cache.storage) +# константа класса +FilesystemIterator::SKIP_DOTS + +# глобальную константу получим PHP функцией constant() +::constant(PHP_VERSION) ``` +Вызовы методов можно объединять в цепочку так же, как в PHP. Только для простоты вместо `->` используется `::`: -Setup -===== +```neon +DateTime()::format('Y-m-d') +# PHP: (new DateTime())->format('Y-m-d') + +@http.request::getUrl()::getHost() +# PHP: $this->getService('http.request')->getUrl()->getHost() +``` -В секции `setup` мы перечисляем методы, которые будут вызываться при создании сервиса: +Эти выражения можно использовать где угодно, при [создании сервисов |#Создание сервиса], в [аргументах |#Аргументы], в секции [#setup] или [параметрах |configuration#Параметры]: ```neon +parameters: + ipAddress: @http.request::getRemoteAddress() + services: database: - create: PDO(%dsn%, %user%, %password%) + create: DatabaseFactory::create( @anotherService::getDsn() ) setup: - - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) + - initialize( ::getenv('DB_USER') ) ``` -Соответствует коду PHP: -```php -public function createServiceDatabase(): PDO -{ - $service = new PDO('...', '...', '...'); - $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - return $service; -} -``` +Специальные функции +------------------- -Также можно установить свойства. Добавление элемента в массив также поддерживается, и его следует писать в кавычках, чтобы не конфликтовать с синтаксисом NEON: +В конфигурационных файлах вы можете использовать эти специальные функции: +- `not()` отрицание значения +- `bool()`, `int()`, `float()`, `string()` преобразование типа без потерь +- `typed()` создает массив всех сервисов указанного типа +- `tagged()` создает массив всех сервисов с данным тегом ```neon services: - foo: - create: Foo - setup: - - $value = 123 - - '$onClick[]' = [@bar, clickHandler] + - Foo( + id: int(::getenv('ProjectId')) + productionMode: not(%debugMode%) + ) ``` -Соответствует коду PHP: +В отличие от классического приведения типов в PHP, такого как `(int)`, преобразование без потерь вызовет исключение для нечисловых значений. -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - $service->value = 123; - $service->onClick[] = [$this->getService('bar'), 'clickHandler']; - return $service; -} +Функция `typed()` создает массив всех сервисов данного типа (класс или интерфейс). Она пропускает сервисы, у которых отключен autowiring. Можно указать несколько типов, разделенных запятой. + +```neon +services: + - BarsDependent( typed(Bar) ) ``` -Однако статические методы или методы других сервисов также могут быть вызваны в настройке. Мы передаем им фактический сервис как `@self`: +Массив сервисов определенного типа можно передавать как аргумент также автоматически с помощью [autowiring |autowiring#Массив сервисов]. +Функция `tagged()` создает массив всех сервисов с определенным тегом. Здесь также можно указать несколько тегов, разделенных запятой. ```neon services: - foo: - create: Foo - setup: - - My\Helpers::initializeFoo(@self) - - @anotherService::setFoo(@self) + - LoggersDependent( tagged(logger) ) ``` -Соответствует коду PHP: -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - My\Helpers::initializeFoo($service); - $this->getService('anotherService')->setFoo($service); - return $service; -} +Autowiring +========== + +Ключ `autowired` позволяет влиять на поведение autowiring для конкретного сервиса. Для деталей см. [главу об autowiring|autowiring]. + +```neon +services: + foo: + create: Foo + autowired: false # сервис foo исключен из autowiring ``` -Автосвязывание .[#toc-autowiring] -================================= +Ленивые сервисы .{data-version:3.2.4} +===================================== -Ключ `autowired` можно использовать для исключения сервиса из автосвязывания или для влияния на его поведение. Более подробную информацию см. в главе [Автосвязывание|autowiring]. +Ленивая загрузка (Lazy loading) — это техника, которая откладывает создание сервиса до момента, когда он действительно необходим. В глобальной конфигурации можно [включить ленивое создание |configuration#Ленивые сервисы] для всех сервисов сразу. Для отдельных сервисов можно переопределить это поведение: ```neon services: foo: create: Foo - autowired: false # foo удаляется из автосвязывания + lazy: false ``` +Когда сервис определен как ленивый, при его запросе из DI-контейнера мы получаем специальный объект-заместитель. Он выглядит и ведет себя так же, как реальный сервис, но фактическая инициализация (вызов конструктора и setup) происходит только при первом вызове любого его метода или свойства. + +.[note] +Ленивая загрузка может использоваться только для пользовательских классов, а не для внутренних классов PHP. Требуется PHP 8.4 или новее. + -Теги .[#toc-tags] -================= +Теги +==== -Информация о пользователе может быть добавлена к отдельным сервисам в виде тегов: +Теги служат для добавления дополнительной информации к сервисам. Сервису можно добавить один или несколько тегов: ```neon services: @@ -360,7 +371,7 @@ services: - cached ``` -Теги также могут иметь значение: +Теги также могут нести значения: ```neon services: @@ -370,26 +381,26 @@ services: logger: monolog.logger.event ``` -Массив сервисов с определенными тегами может быть передан в качестве аргумента с помощью функции `tagged()`. Можно также указать несколько тегов, разделенных запятой. +Чтобы получить все сервисы с определенными тегами, вы можете использовать функцию `tagged()`: ```neon services: - LoggersDependent( tagged(logger) ) ``` -Имена служб можно получить из контейнера DI с помощью метода `findByTag()`: +В DI-контейнере вы можете получить имена всех сервисов с определенным тегом с помощью метода `findByTag()`: ```php $names = $container->findByTag('logger'); -// $names - массив, содержащий имя сервиса и значение тега -// например ['foo' => 'monolog.logger.event', ...] +// $names - это массив, содержащий имя сервиса и значение тега +// например, ['foo' => 'monolog.logger.event', ...] ``` -Режим внедрения .[#toc-inject-mode] -=================================== +Режим Inject +============ -Флаг `inject: true` используется для активации передачи зависимостей через публичные переменные с помощью аннотации [inject |best-practices:inject-method-attribute#Inject Attributes] и методов [inject*() |best-practices:inject-method-attribute#inject Methods]. +С помощью флага `inject: true` активируется передача зависимостей через публичные переменные с аннотацией [inject |best-practices:inject-method-attribute#Атрибуты Inject] и методы [inject*() |best-practices:inject-method-attribute#Методы inject]. ```neon services: @@ -398,13 +409,13 @@ services: inject: true ``` -По умолчанию `inject` активируется только для презентеров. +По умолчанию `inject` активирован только для презентеров. -Модификация сервисов .[#toc-modification-of-services] -===================================================== +Модификация сервисов +==================== -В контейнере DI есть ряд сервисов, которые были добавлены встроенным или [вашим расширением|#di-extension]. Определения этих сервисов могут быть изменены в конфигурации. Например, для сервиса `application.application`, который по умолчанию является объектом `Nette\Application\Application`, мы можем изменить класс: +DI-контейнер содержит множество сервисов, которые были добавлены с помощью встроенного или [пользовательского расширения|extensions]. Вы можете изменять определения этих сервисов прямо в конфигурации. Например, вы можете изменить класс сервиса `application.application`, который по умолчанию является `Nette\Application\Application`, на другой: ```neon services: @@ -413,9 +424,9 @@ services: alteration: true ``` -Флаг `alteration` является информативным и говорит о том, что мы просто изменяем существующий сервис. +Флаг `alteration` является информативным и указывает, что мы только модифицируем существующий сервис. -Мы также можем добавить `setup`: +Мы также можем дополнить setup: ```neon services: @@ -426,7 +437,7 @@ services: - '$onStartup[]' = [@resource, init] ``` -При перезаписи сервиса мы можем удалить исходные аргументы, элементы `setup` или теги, для которых `reset` является: +При переопределении сервиса мы можем захотеть удалить исходные аргументы, элементы setup или теги, для чего служит `reset`: ```neon services: @@ -439,7 +450,7 @@ services: - tags ``` -Сервис, добавленный расширением, также может быть удален из контейнера: +Если вы хотите удалить сервис, добавленный расширением, вы можете сделать это так: ```neon services: diff --git a/dependency-injection/sl/@home.texy b/dependency-injection/sl/@home.texy index 0e76f144d6..e6abd1050d 100644 --- a/dependency-injection/sl/@home.texy +++ b/dependency-injection/sl/@home.texy @@ -1,24 +1,21 @@ -Injekcija odvisnosti -******************** +Nette DI +******** .[perex] -Vbrizgavanje odvisnosti je načrtovalski vzorec, ki bo temeljito spremenil vaš pogled na kodo in razvoj. Odpira pot v svet čisto zasnovanih in trajnostnih aplikacij. +Dependency Injection je načrtovalski vzorec, ki bo bistveno spremenil vaš pogled na kodo in razvoj. Odprl vam bo pot v svet čisto načrtovanih in vzdržljivih aplikacij. -- [Kaj je vrivanje odvisnosti? |introduction] -- [Globalno stanje in enojni elementi |global-state] +- [Kaj je Dependency Injection? |introduction] +- [Globalno stanje in singletoni |global-state] - [Posredovanje odvisnosti |passing-dependencies] -- [Kaj je zabojnik DI? |container] -- [Pogosto zastavljena vprašanja |faq] - +- [Kaj je DI vsebnik? |container] +- [Pogosto zastavljena vprašanja|faq] -Nette DI --------- -Paket `nette/di` zagotavlja izjemno napreden sestavljeni vsebnik DI za PHP. +Paket `nette/di` ponuja izjemno napreden kompiliran DI vsebnik za PHP. -- [Zabojnik Nette DI |nette-container] +- [Nette DI Vsebnik |nette-container] - [Konfiguracija |configuration] -- [Opredelitve storitev |services] -- [Samodejno napeljevanje |autowiring] +- [Definiranje storitev |services] +- [Autowiring |autowiring] - [Generirane tovarne |factory] -- [Ustvarjanje razširitev za Nette DI |extensions] +- [Ustvarjanje razširitev za Nette DI|extensions] diff --git a/dependency-injection/sl/@left-menu.texy b/dependency-injection/sl/@left-menu.texy index 84855a4098..70ce6dceb5 100644 --- a/dependency-injection/sl/@left-menu.texy +++ b/dependency-injection/sl/@left-menu.texy @@ -1,17 +1,17 @@ -Injekcija odvisnosti +Dependency Injection ******************** - [Kaj je DI? |introduction] - [Globalno stanje in singletoni |global-state] - [Posredovanje odvisnosti |passing-dependencies] -- [Kaj je vsebnik DI? |container] -- [Pogosto zastavljena vprašanja |faq] +- [Kaj je DI vsebnik? |container] +- [Pogosto zastavljena vprašanja|faq] Nette DI -------- -- [Nette DI Container |nette-container] +- [Nette DI Vsebnik |nette-container] - [Konfiguracija |configuration] -- [Opredelitve storitev |services] -- [Samodejno ožičenje |autowiring] +- [Definiranje storitev |services] +- [Autowiring |autowiring] - [Generirane tovarne |factory] -- [Ustvarjanje razširitev za Nette DI |extensions] +- [Ustvarjanje razširitev za Nette DI|extensions] diff --git a/dependency-injection/sl/@meta.texy b/dependency-injection/sl/@meta.texy new file mode 100644 index 0000000000..724324bee5 --- /dev/null +++ b/dependency-injection/sl/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokumentacija}} diff --git a/dependency-injection/sl/autowiring.texy b/dependency-injection/sl/autowiring.texy index db5b689b51..6f70c0b6e5 100644 --- a/dependency-injection/sl/autowiring.texy +++ b/dependency-injection/sl/autowiring.texy @@ -1,24 +1,24 @@ -Avtomatsko napeljevanje -*********************** +Autowiring +********** .[perex] -Autowiring je odlična funkcija, ki lahko samodejno posreduje storitve konstruktorju in drugim metodam, tako da nam jih sploh ni treba pisati. S tem prihranimo veliko časa. +Autowiring je odlična lastnost, ki zna samodejno posredovati v konstruktor in druge metode zahtevane storitve, tako da jih sploh ni treba pisati. Prihrani vam veliko časa. -Tako lahko pri pisanju definicij storitev preskočimo veliko večino argumentov. Namesto: +Zahvaljujoč temu lahko izpustimo večino argumentov pri pisanju definicij storitev. Namesto: ```neon services: articles: Model\ArticleRepository(@database, @cache.storage) ``` -napišite: +Zadostuje napisati: ```neon services: articles: Model\ArticleRepository ``` -Zato je treba razred `ArticleRepository` opredeliti na naslednji način: +Autowiring se ravna po tipih, zato mora biti za delovanje razred `ArticleRepository` definiran približno takole: ```php namespace Model; @@ -30,22 +30,22 @@ class ArticleRepository } ``` -Za uporabo samodejnega napeljevanja mora biti za vsak tip v vsebniku **poudarjena samo ena storitev**. Če bi jih bilo več, autowiring ne bi vedel, katero naj posreduje, in bi zavrgel izjemo: +Da bi lahko uporabili autowiring, mora za vsak tip v vsebniku obstajati **točno ena storitev**. Če bi jih bilo več, autowiring ne bi vedel, katero naj posreduje, in bi vrgel izjemo: ```neon services: mainDb: PDO(%dsn%, %user%, %password%) tempDb: PDO('sqlite::memory:') - articles: Model\ArticleRepository # IZJAVA, tako mainDb kot tempDb se ujemata + articles: Model\ArticleRepository # VRŽE IZJEMO, ustrezata mainDb in tempDb ``` -Rešitev bi bila, da se izognemo samodejnemu usmerjanju in izrecno navedemo ime storitve (tj. `articles: Model\ArticleRepository(@mainDb)`). Vendar pa je bolj priročno, da [onemogočimo |#Disabled autowiring] samodejno posredovanje ene storitve ali [raje |#Preferred Autowiring] prve storitve. +Rešitev bi bila bodisi obiti autowiring in eksplicitno navesti ime storitve (tj. `articles: Model\ArticleRepository(@mainDb)`). Pametneje pa je autowiring ene od storitev [izklopiti |#Izklop autowiringa] ali prvo storitev [dati prednost |#Prednost autowiringa]. -Onemogočeno samodejno napeljevanje .[#toc-disabled-autowiring] --------------------------------------------------------------- +Izklop autowiringa +------------------ -Samodejno ožičenje storitev lahko onemogočite z uporabo možnosti `autowired: no`: +Autowiring storitve lahko izklopimo z uporabo možnosti `autowired: no`: ```neon services: @@ -53,28 +53,27 @@ services: tempDb: create: PDO('sqlite::memory:') - autowired: false # odstrani tempDb iz samodejnega napeljevanja + autowired: false # storitev tempDb je izključena iz autowiringa - articles: Model\ArticleRepository # zato konstruktorju posreduje mainDb + articles: Model\ArticleRepository # zato posreduje v konstruktor mainDb ``` -Storitev `articles` ne vrže izjeme, da obstajata dve ustrezni storitvi tipa `PDO` (tj. `mainDb` in `tempDb`), ki ju je mogoče posredovati konstruktorju, saj vidi samo storitev `mainDb`. +Storitev `articles` ne bo vrgla izjeme, da obstajata dve ustrezni storitvi tipa `PDO` (tj. `mainDb` in `tempDb`), ki ju je mogoče posredovati v konstruktor, ker vidi samo storitev `mainDb`. .[note] -Konfiguracija samodejnega navajanja v Nette deluje drugače kot v Symfonyju, kjer možnost `autowire: false` pravi, da se samodejno navajanje ne sme uporabljati za argumente konstruktorja storitve. -V Nette se autowiring vedno uporablja, ne glede na to, ali gre za argumente konstruktorja ali katere koli druge metode. Možnost `autowired: false` pravi, da se primerek storitve ne sme nikamor posredovati z uporabo samodejnega vodenja. +Konfiguracija autowiringa v Nette deluje drugače kot v Symfonyju, kjer možnost `autowire: false` pove, da se autowiring ne sme uporabljati za argumente konstruktorja dane storitve. V Nette se autowiring uporablja vedno, bodisi za argumente konstruktorja ali katere koli druge metode. Možnost `autowired: false` pove, da instanca dane storitve ne sme biti nikamor posredovana z uporabo autowiringa. -Prednostno samodejno vodenje .[#toc-preferred-autowiring] ---------------------------------------------------------- +Prednost autowiringa +-------------------- -Če imamo več storitev iste vrste in ima ena od njih možnost `autowired`, ta storitev postane prednostna: +Če imamo več storitev istega tipa in pri eni od njih navedemo možnost `autowired`, postane ta storitev prednostna: ```neon services: mainDb: create: PDO(%dsn%, %user%, %password%) - autowired: PDO # daje prednost + autowired: PDO # postane prednostna tempDb: create: PDO('sqlite::memory:') @@ -82,13 +81,13 @@ services: articles: Model\ArticleRepository ``` -Storitev `articles` ne vrže izjeme, da obstajata dve ustrezni storitvi `PDO` (tj. `mainDb` in `tempDb`), temveč uporabi prednostno storitev, tj. `mainDb`. +Storitev `articles` ne bo vrgla izjeme, da obstajata dve ustrezni storitvi tipa `PDO` (tj. `mainDb` in `tempDb`), ampak bo uporabila prednostno storitev, torej `mainDb`. -Zbirka storitev .[#toc-collection-of-services] ----------------------------------------------- +Polje storitev +-------------- -Samodejno napeljevanje lahko posreduje tudi niz storitev določene vrste. Ker PHP ne more nativno zapisati vrste elementov polja, je treba poleg vrste `array` dodati še komentar phpDoc z vrsto elementa, kot je `ClassName[]`: +Autowiring zna posredovati tudi polja storitev določenega tipa. Ker v PHP ni mogoče nativno zapisati tipa elementov polja, je treba poleg tipa `array` dopolniti tudi phpDoc komentar s tipom elementa v obliki `ClassName[]`: ```php namespace Model; @@ -103,46 +102,45 @@ class ShipManager } ``` -Vsebnik DI nato samodejno posreduje polje storitev, ki ustrezajo dani vrsti. Izpusti storitve, ki imajo izklopljeno samodejno napeljavo. +DI vsebnik nato samodejno posreduje polje storitev, ki ustrezajo danemu tipu. Izpusti storitve, ki imajo izklopljen autowiring. -Če ne morete nadzorovati oblike komentarja phpDoc, lahko polje storitev posredujete neposredno v konfiguraciji z uporabo [`typed()` |services#Special Functions]. +Tip v komentarju je lahko tudi v obliki `array<int, Class>` ali `list<Class>`. Če ne morete vplivati na obliko phpDoc komentarja, lahko polje storitev posredujete neposredno v konfiguraciji z uporabo [`typed()` |services#Posebne funkcije]. -Skalarni argumenti .[#toc-scalar-arguments] -------------------------------------------- +Skalarni argumenti +------------------ -Samodejno napeljevanje lahko posreduje samo predmete in polja predmetov. Skalarne argumente (npr. nize, številke, logične vrednosti) [zapišite v konfiguracijo |services#Arguments]. -Druga možnost je, da ustvarite [objekt settings-object |best-practices:passing-settings-to-presenters], ki skalarno vrednost (ali več vrednosti) uokviri kot objekt, ki ga lahko nato ponovno posredujete z uporabo autowiringa. +Autowiring zna vstavljati samo objekte in polja objektov. Skalarne argumente (npr. nize, števila, booleane) [zapišemo v konfiguraciji |services#Argumenti]. Alternativa je ustvariti [settings-objekt |best-practices:passing-settings-to-presenters], ki skalarno vrednost (ali več vrednosti) zapakira v obliko objekta, ki ga nato lahko spet posredujemo z uporabo autowiringa. ```php class MySettings { public function __construct( - // readonly se lahko uporablja od PHP 8.1 + // readonly je mogoče uporabiti od PHP 8.1 public readonly bool $value, ) {} } ``` -Storitev ustvarite tako, da jo dodate v konfiguracijo: +Iz njega ustvarite storitev z dodajanjem v konfiguracijo: ```neon services: - MySettings('any value') ``` -Vsi razredi jo bodo nato zahtevali prek samodejnega povezovanja. +Vsi razredi jo nato zahtevajo z uporabo autowiringa. -Oženje samodejnega napeljevanja .[#toc-narrowing-of-autowiring] ---------------------------------------------------------------- +Omejitev autowiringa +-------------------- -Pri posameznih storitvah lahko samodejno napeljavo zožite na določene razrede ali vmesnike. +Posameznim storitvam lahko autowiring omejimo samo na določene razrede ali vmesnike. -Običajno samodejno usmerjanje posreduje storitev vsakemu parametru metode, katere tipu ustreza storitev. Oženje pomeni, da določimo pogoje, ki jih morajo tipi, določeni za parametre metod, izpolnjevati, da se jim posreduje storitev. +Običajno autowiring storitev posreduje v vsak parameter metode, katerega tipu storitev ustreza. Omejitev pomeni, da določimo pogoje, ki jim morajo ustrezati tipi, navedeni pri parametrih metod, da jim bo storitev posredovana. -Poglejmo primer: +Poglejmo si to na primeru: ```php class ParentClass @@ -164,42 +162,42 @@ class ChildDependent } ``` -Če bi jih vse registrirali kot storitve, bi bila samodejna povezava neuspešna: +Če bi jih vse registrirali kot storitve, bi autowiring spodletel: ```neon services: parent: ParentClass child: ChildClass - parentDep: ParentDependent # IZJEMA, ujemata se tako starš kot otrok - childDep: ChildDependent # konstruktorju posreduje storitev 'child'. + parentDep: ParentDependent # VRŽE IZJEMO, ustrezata storitvi parent in child + childDep: ChildDependent # autowiring posreduje v konstruktor storitev child ``` -Storitev `parentDep` vrže izjemo `Multiple services of type ParentClass found: parent, child`, ker sta tako `parent` kot `child` vključena v njen konstruktor in se samodejna vključitev ne more odločiti, katero bo izbrala. +Storitev `parentDep` vrže izjemo `Multiple services of type ParentClass found: parent, child`, ker v njen konstruktor ustrezata obe storitvi `parent` in `child`, in autowiring ne more odločiti, katero naj izbere. -Pri storitvi `child` lahko torej njeno samodejno vodenje zožimo na `ChildClass`: +Pri storitvi `child` lahko zato omejimo njen autowiring na tip `ChildClass`: ```neon services: parent: ParentClass child: create: ChildClass - autowired: ChildClass # alternative: 'autowired: self' + autowired: ChildClass # lahko napišemo tudi 'autowired: self' - parentDep: ParentDependent # IZJAVA, 'otrok' ne more biti samodejno ožičen - childDep: ChildDependent # posreduje storitev "child" konstruktorju + parentDep: ParentDependent # autowiring posreduje v konstruktor storitev parent + childDep: ChildDependent # autowiring posreduje v konstruktor storitev child ``` -Storitev `parentDep` je zdaj posredovana konstruktorju storitve `parentDep`, saj je zdaj edini ustrezni objekt. Storitev `child` ni več posredovana s samodejnim vodenjem. Da, storitev `child` je še vedno tipa `ParentClass`, vendar ne velja več pogoj za zožitev, ki je podan za tip parametra, tj. ne drži več, da je `ParentClass` *nadtip* `ChildClass`. +Zdaj se v konstruktor storitve `parentDep` posreduje storitev `parent`, ker je zdaj to edini ustrezen objekt. Storitve `child` autowiring tja ne posreduje več. Da, storitev `child` je še vedno tipa `ParentClass`, vendar ne velja več omejitveni pogoj, dan za tip parametra, tj. ne velja, da je `ParentClass` *nadtip* `ChildClass`. -V primeru `child` bi lahko `autowired: ChildClass` zapisali kot `autowired: self`, saj `self` pomeni trenutni tip storitve. +Pri storitvi `child` bi bilo mogoče `autowired: ChildClass` zapisati tudi kot `autowired: self`, ker je `self` nadomestno ime za razred trenutne storitve. -Ključ `autowired` lahko vključuje več razredov in vmesnikov kot polje: +V ključu `autowired` je mogoče navesti tudi več razredov ali vmesnikov kot polje: ```neon autowired: [BarClass, FooInterface] ``` -Poskusimo v primer dodati vmesnike: +Poskusimo primer dopolniti še z vmesniki: ```php interface FooInterface @@ -239,13 +237,13 @@ class ChildDependent } ``` -Če storitve `child` ne omejimo, se bo prilegala konstruktorjem vseh razredov `FooDependent`, `BarDependent`, `ParentDependent` in `ChildDependent` in jo bo tja posredoval autowiring. +Če storitve `child` nikakor ne omejimo, bo ustrezala konstruktorjem vseh razredov `FooDependent`, `BarDependent`, `ParentDependent` in `ChildDependent`, in autowiring jo bo tja posredoval. -Če pa njegovo autowiring omejimo na `ChildClass` z uporabo `autowired: ChildClass` (ali `self`), ga autowiring prenese le v konstruktor `ChildDependent`, saj zahteva argument tipa `ChildClass`, `ChildClass` pa *je tipa* `ChildClass`. Noben tip, ki je naveden za druge parametre, ni nadmnožica `ChildClass`, zato se storitev ne posreduje. +Če pa njen autowiring omejimo na `ChildClass` z `autowired: ChildClass` (ali `self`), jo bo autowiring posredoval samo v konstruktor `ChildDependent`, ker zahteva argument tipa `ChildClass` in velja, da je `ChildClass` *tipa* `ChildClass`. Noben drug tip, naveden pri drugih parametrih, ni nadtip `ChildClass`, zato se storitev ne posreduje. -Če jo omejimo na `ParentClass` z uporabo `autowired: ParentClass`, jo bo samodejno posredovanje spet posredovalo konstruktorju `ChildDependent` (ker je zahtevani tip `ChildClass` nadmnožica `ParentClass`) in tudi konstruktorju `ParentDependent`, ker je zahtevani tip `ParentClass` prav tako ustrezen. +Če jo omejimo na `ParentClass` z `autowired: ParentClass`, jo bo autowiring spet posredoval v konstruktor `ChildDependent` (ker je zahtevani `ChildClass` nadtip `ParentClass`) in na novo tudi v konstruktor `ParentDependent`, ker je zahtevani tip `ParentClass` prav tako ustrezen. -Če ga omejimo na `FooInterface`, se bo še vedno samodejno usmeril na `ParentDependent` (zahtevani tip `ParentClass` je nadtip `FooInterface`) in `ChildDependent`, dodatno pa še na konstruktor `FooDependent`, ne pa na `BarDependent`, saj `BarInterface` ni nadtip `FooInterface`. +Če jo omejimo na `FooInterface`, bo še vedno avtomatsko povezana v `ParentDependent` (zahtevani `ParentClass` je nadtip `FooInterface`) in `ChildDependent`, poleg tega pa tudi v konstruktor `FooDependent`, vendar ne v `BarDependent`, ker `BarInterface` ni nadtip `FooInterface`. ```neon services: @@ -253,8 +251,8 @@ services: create: ChildClass autowired: FooInterface - fooDep: FooDependent # posreduje otroka storitve konstruktorju - barDep: BarDependent # IZJAVA, nobena storitev ne bi bila posredovana - parentDep: ParentDependent # posreduje otroka storitve konstruktorju - childDep: ChildDependent # posreduje otroka storitve konstruktorju + fooDep: FooDependent # autowiring posreduje v konstruktor child + barDep: BarDependent # VRŽE IZJEMO, nobena storitev ne ustreza + parentDep: ParentDependent # autowiring posreduje v konstruktor child + childDep: ChildDependent # autowiring posreduje v konstruktor child ``` diff --git a/dependency-injection/sl/configuration.texy b/dependency-injection/sl/configuration.texy index 66f22356d4..d4b773c1a5 100644 --- a/dependency-injection/sl/configuration.texy +++ b/dependency-injection/sl/configuration.texy @@ -1,32 +1,33 @@ -Konfiguracija zabojnika DI -************************** +Konfiguracija DI vsebnika +************************* .[perex] -Pregled možnosti konfiguracije za zabojnik Nette DI. +Pregled konfiguracijskih možnosti za Nette DI vsebnik. -Datoteka za konfiguracijo .[#toc-configuration-file] -==================================================== +Konfiguracijska datoteka +======================== -Zabojnik Nette DI je mogoče preprosto upravljati s konfiguracijskimi datotekami. Običajno so zapisane v [formatu NEON |neon:format]. Za urejanje priporočamo uporabo [urejevalnikov s podporo za |best-practices:editors-and-tools#ide-editor] ta format. +Nette DI vsebnik se enostavno upravlja s konfiguracijskimi datotekami. Te se običajno zapisujejo v [formatu NEON|neon:format]. Za urejanje priporočamo [urejevalnike s podporo |best-practices:editors-and-tools#IDE urejevalnik] za ta format. <pre> -"decorator .[prism-token prism-atrule]":[#Decorator]: "Decorator .[prism-token prism-comment]"<br> -"di .[prism-token prism-atrule]":[#DI]: "DI Container .[prism-token prism-comment]"<br> -"extensions .[prism-token prism-atrule]":[#Extensions]: "Namesti dodatne razširitve DI .[prism-token prism-comment]"<br> -"includes .[prism-token prism-atrule]":[#Including files]: "Vključuje datoteke .[prism-token prism-comment]"<br> -"parameters .[prism-token prism-atrule]":[#Parameters]: "Parametri .[prism-token prism-comment]"<br> -"search .[prism-token prism-atrule]":[#Search]: "Samodejna registracija storitev .[prism-token prism-comment]"<br> +"decorator .[prism-token prism-atrule]":[#decorator]: "Dekorator .[prism-token prism-comment]"<br> +"di .[prism-token prism-atrule]":[#DI]: "DI vsebnik .[prism-token prism-comment]"<br> +"extensions .[prism-token prism-atrule]":[#Razširitve]: "Namestitev dodatnih DI razširitev .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[#Vključevanje datotek]: "Vključevanje datotek .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[#Parametri]: "Parametri .[prism-token prism-comment]"<br> +"search .[prism-token prism-atrule]":[#Iskanje]: "Samodejna registracija storitev .[prism-token prism-comment]"<br> "services .[prism-token prism-atrule]":[services]: "Storitve .[prism-token prism-comment]" </pre> -Zapisovanje niza, ki vsebuje znak `%`, you must escape it by doubling it to `%%`. .[note] +.[note] +Če želite zapisati niz, ki vsebuje znak `%`, ga morate ubežati z podvojitvijo na `%%`. -Parametri .[#toc-parameters] -============================ +Parametri +========= -Opredelite lahko parametre, ki jih lahko nato uporabite kot del definicij storitev. To lahko pomaga pri ločevanju vrednosti, ki jih boste želeli bolj redno spreminjati. +V konfiguraciji lahko definirate parametre, ki jih lahko nato uporabite kot del definicij storitev. S tem lahko naredite konfiguracijo preglednejšo ali združite in izločite vrednosti, ki se bodo spreminjale. ```neon parameters: @@ -35,9 +36,9 @@ parameters: password: secret ``` -Na parameter `foo` se lahko sklicujete prek `%foo%` drugje v kateri koli konfiguracijski datoteki. Uporabite jih lahko tudi znotraj nizov, kot je `'%wwwDir%/images'`. +Na parameter `dsn` se sklicujemo kjerkoli v konfiguraciji z zapisom `%dsn%`. Parametre lahko uporabljamo tudi znotraj nizov kot `'%wwwDir%/images'`. -Parametri niso nujno samo nizi, lahko so tudi vrednosti v obliki polj: +Parametri niso nujno samo nizi ali števila, lahko vsebujejo tudi polja: ```neon parameters: @@ -48,32 +49,32 @@ parameters: languages: [cs, en, de] ``` -Posamezen ključ lahko označite kot `%mailer.user%`. +Na določen ključ se sklicujemo kot `%mailer.user%`. -Če morate v kodi, na primer v razredu, dobiti vrednost katerega koli parametra, ga predajte temu razredu. Na primer v konstruktorju. Ni globalnega konfiguracijskega objekta, ki bi ga razredi lahko poizvedovali po vrednostih parametrov. To bi bilo v nasprotju z načelom vbrizgavanja odvisnosti. +Če potrebujete v vaši kodi, na primer v razredu, ugotoviti vrednost katerega koli parametra, ga posredujte v ta razred. Na primer v konstruktorju. Ne obstaja noben globalni objekt, ki bi predstavljal konfiguracijo, katerega bi razredi spraševali za vrednosti parametrov. To bi bilo kršenje načela dependency injection. -Storitve .[#toc-services] -========================= +Storitve +======== -Glej [posebno poglavje |services]. +Glej [samostojno poglavje|services]. -Dekorator .[#toc-decorator] -=========================== +Decorator +========= -Kako množično urediti vse storitve določene vrste? Ali morate poklicati določeno metodo za vse predstavnike, ki dedujejo po določenem skupnem predniku? Od tod prihaja dekorator. +Kako množično urediti vse storitve določenega tipa? Na primer poklicati določeno metodo pri vseh presenterjih, ki dedujejo od določenega skupnega prednika? Za to je tu decorator. ```neon decorator: - # za vse storitve, ki so primerki tega razreda ali vmesnika. - App\Presenters\BasePresenter: + # pri vseh storitvah, ki so instanca tega razreda ali vmesnika + App\Presentation\BasePresenter: setup: - - setProjectId(10) # pokliče to metodo + - setProjectId(10) # pokliči to metodo - $absoluteUrls = true # in nastavi spremenljivko ``` -Dekorator lahko uporabite tudi za nastavitev [oznak |services#Tags] ali vklop [načina injiciranja |services#Inject Mode]. +Decorator se lahko uporablja tudi za nastavitev [oznak |services#Oznake] ali vklop načina [inject |services#Način Inject]. ```neon decorator: @@ -86,67 +87,79 @@ decorator: DI === -Tehnične nastavitve vsebnika DI. +Tehnične nastavitve DI vsebnika. ```neon di: - # prikazuje DIC v Tracy Baru? - debugger: ... # (bool) privzeto true + # prikazati DIC v Tracy Bar? + debugger: ... # (bool) privzeto je true - # vrste parametrov, ki jih nikoli ne povežete samodejno + # tipi parametrov, ki jih nikoli ne avtomatsko povezovati excluded: ... # (string[]) - # razred, iz katerega podeduje vsebnik DI - parentClass: ... # (string) privzeto Nette\DI\Container + # dovoliti leno ustvarjanje storitev? + lazy: ... # (bool) privzeto je false + + # razred, od katerega deduje DI vsebnik + parentClass: ... # (string) privzeto je Nette\DI\Container ``` -Izvoz metapodatkov .[#toc-metadata-export] ------------------------------------------- +Lene storitve .{data-version:3.2.4} +----------------------------------- + +Nastavitev `lazy: true` aktivira leno (odloženo) ustvarjanje storitev. To pomeni, da storitve niso dejansko ustvarjene v trenutku, ko jih zahtevamo iz DI vsebnika, ampak šele v trenutku njihove prve uporabe. To lahko pospeši zagon aplikacije in zmanjša pomnilniške zahteve, saj se ustvarijo samo tiste storitve, ki so v danem zahtevku dejansko potrebne. + +Pri določeni storitvi lahko leno ustvarjanje [spremenimo |services#Lazy storitve]. + +.[note] +Lene objekte je mogoče uporabiti samo za uporabniške razrede, ne pa za interne PHP razrede. Zahteva PHP 8.4 ali novejšo različico. + -Tudi razred vsebnika DI vsebuje veliko metapodatkov. Zmanjšate jih lahko tako, da zmanjšate izvoz metapodatkov. +Izvoz metapodatkov +------------------ + +Razred DI vsebnika vsebuje tudi veliko metapodatkov. Lahko ga zmanjšate tako, da zmanjšate izvoz metapodatkov. ```neon di: export: - # za izvoz parametrov? - parameters: false # (bool) privzeto true + # izvoziti parametre? + parameters: false # (bool) privzeto je true # izvoziti oznake in katere? - tags: # (string[]|bool) privzeto so vsi + tags: # (string[]|bool) privzeto so vse - event.subscriber - # izvozi podatke za samodejno napeljavo in katere? + # izvoziti podatke za autowiring in katere? types: # (string[]|bool) privzeto so vsi - Nette\Database\Connection - Symfony\Component\Console\Application ``` -Če ne uporabljate polja `$container->parameters`, lahko onemogočite izvoz parametrov. Poleg tega lahko izvozite samo tiste oznake, prek katerih pridobivate storitve z uporabo metode `$container->findByTag(...)`. -Če metode sploh ne kličete, lahko izvoz značk popolnoma onemogočite z metodo `false`. +Če ne uporabljate polja `$container->getParameters()`, lahko izklopite izvoz parametrov. Nadalje lahko izvozite samo tiste oznake, prek katerih pridobivate storitve z metodo `$container->findByTag(...)`. Če metode sploh ne kličete, lahko popolnoma izklopite izvoz oznak z `false`. -Metapodatke za [samodejno vnašanje |autowiring] lahko bistveno zmanjšate tako, da razrede, ki jih uporabljate, določite kot parameter metode `$container->getByType()`. -In še enkrat, če metode sploh ne pokličete (ali samo v [application:bootstrap], da dobite `Nette\Application\Application`), lahko izvoz popolnoma onemogočite s `false`. +Znatno lahko zmanjšate metapodatke za [samodejnim povezovanjem |autowiring] tako, da navedete razrede, ki jih uporabljate kot parameter metode `$container->getByType()`. In spet, če metode sploh ne kličete (oz. samo v [bootstrapu|application:bootstrapping] za pridobitev `Nette\Application\Application`), lahko izvoz popolnoma izklopite z `false`. -Razširitve .[#toc-extensions] -============================= +Razširitve +========== -Registracija drugih razširitev DI. Tako na primer dodamo razširitev DI `Dibi\Bridges\Nette\DibiExtension22` pod imenom `dibi`: +Registracija dodatnih DI razširitev. Na ta način dodamo npr. DI razširitev `Dibi\Bridges\Nette\DibiExtension22` pod imenom `dibi` ```neon extensions: dibi: Dibi\Bridges\Nette\DibiExtension22 ``` -Nato jo konfiguriramo v njenem razdelku z imenom tudi `dibi`: +Nato jo torej konfiguriramo v sekciji `dibi`: ```neon dibi: host: localhost ``` -Dodate lahko tudi razširitveni razred s parametri: +Kot razširitev lahko dodamo tudi razred, ki ima parametre: ```neon extensions: @@ -154,10 +167,10 @@ extensions: ``` -Vključevanje datotek .[#toc-including-files] -============================================ +Vključevanje datotek +==================== -Dodatne konfiguracijske datoteke lahko vstavite v razdelek `includes`: +Druge konfiguracijske datoteke lahko vključimo v sekciji `includes`: ```neon includes: @@ -166,7 +179,7 @@ includes: - presenters.neon ``` -Ime `parameters.php` ni tipkarska napaka, konfiguracijo lahko zapišete tudi v datoteko PHP, ki jo vrne kot polje: +Ime `parameters.php` ni napaka, konfiguracija je lahko zapisana tudi v PHP datoteki, ki jo vrne kot polje: ```php <?php @@ -179,34 +192,29 @@ return [ ]; ``` -Če se v konfiguracijskih datotekah pojavijo elementi z enakimi ključi, se [prepišejo ali združijo |#Merging] v primeru polj. Kasneje vključena datoteka ima višjo prioriteto kot prejšnja. Datoteka, v kateri je naveden razdelek `includes`, ima višjo prednost kot datoteke, ki so vanjo vključene. +Če se v konfiguracijskih datotekah pojavijo elementi z enakimi ključi, bodo prepisani ali v primeru [polj združeni |#Združevanje]. Kasneje vključena datoteka ima višjo prioriteto kot prejšnja. Datoteka, v kateri je navedena sekcija `includes`, ima višjo prioriteto kot v njej vključene datoteke. -Iskanje .[#toc-search] -====================== +Iskanje +======= -Zaradi samodejnega dodajanja storitev v vsebnik DI je delo zelo prijetno. Nette v vsebnik samodejno doda predstavnike, vendar lahko enostavno dodate tudi katere koli druge razrede. +Samodejno dodajanje storitev v DI vsebnik izjemno olajša delo. Nette samodejno dodaja v vsebnik presenterje, vendar je mogoče enostavno dodajati tudi katere koli druge razrede. -Samo določite, v katerih imenikih (in podimenikih) naj se razredi iščejo: +Zadostuje navesti, v katerih mapah (in podmapah) naj išče razrede: ```neon search: - # imena razdelkov izberete sami. - myForms: - in: %appDir%/Forms - - model: - in: %appDir%/Model + - in: %appDir%/Forms + - in: %appDir%/Model ``` -Običajno ne želimo dodati vseh razredov in vmesnikov, zato jih lahko filtriramo: +Običajno pa ne želimo dodati popolnoma vseh razredov in vmesnikov, zato jih lahko filtriramo: ```neon search: - myForms: - in: %appDir%/Forms + - in: %appDir%/Forms - # filtriranje po imenu datoteke (niz|recept[]) + # filtriranje po imenu datoteke (string|string[]) files: - *Factory.php @@ -215,42 +223,43 @@ search: - *Factory ``` -Lahko pa izberemo razrede, ki dedujejo ali implementirajo vsaj enega od naslednjih razredov: +Ali pa lahko izberemo razrede, ki dedujejo ali implementirajo vsaj enega od navedenih razredov: ```neon search: - myForms: + - in: %appDir% extends: - App\*Form implements: - App\*FormInterface ``` -Določite lahko tudi negativna pravila, tj. maske imen razredov ali prednikov, in če so v skladu z njimi, storitev ne bo dodana v vsebnik DI: +Lahko definiramo tudi izključujoča pravila, tj. maske imena razreda ali dedne prednike, ki če ustrezajo, se storitev v DI vsebnik ne doda: ```neon search: - myForms: + - in: %appDir% exclude: + files: ... classes: ... extends: ... implements: ... ``` -Za dodane storitve lahko nastavite oznake: +Vsem storitvam lahko nastavimo oznake: ```neon search: - myForms: + - in: %appDir% tags: ... ``` -Združevanje .[#toc-merging] -=========================== +Združevanje +=========== -Če se elementi z istimi ključi pojavijo v več konfiguracijskih datotekah, se prepišejo ali združijo v primeru polj. Kasneje vključena datoteka ima večjo prednost. +Če se v več konfiguracijskih datotekah pojavijo elementi z enakimi ključi, bodo prepisani ali v primeru polj združeni. Kasneje vključena datoteka ima višjo prioriteto kot prejšnja. <table class=table> <tr> @@ -283,11 +292,11 @@ items: </tr> </table> -Če želite preprečiti združevanje določenega polja, uporabite izklicnik takoj za imenom polja: +Pri poljih lahko preprečimo združevanje z navedbo klicaja za imenom ključa: <table class=table> <tr> - <th width=33%>config1.neon.</th> + <th width=33%>config1.neon</th> <th width=33%>config2.neon</th> <th>rezultat</th> </tr> @@ -314,5 +323,4 @@ items: </tr> </table> - -{{maintitle: Konfiguracija vrivanja odvisnosti}} +{{maintitle: Konfiguracija Dependency Injection}} diff --git a/dependency-injection/sl/container.texy b/dependency-injection/sl/container.texy index 0f2d09f7f2..6fd042ac1c 100644 --- a/dependency-injection/sl/container.texy +++ b/dependency-injection/sl/container.texy @@ -1,16 +1,16 @@ -Kaj je zabojnik DI? -******************* +Kaj je DI vsebnik? +****************** .[perex] -Zabojnik za vbrizgavanje odvisnosti (DIC) je razred, ki lahko instancira in konfigurira objekte. +Dependency injection vsebnik (DIC) je razred, ki zna instancirati in konfigurirati objekte. -Morda vas bo presenetilo, vendar v številnih primerih ne potrebujete vsebnika za vbrizgavanje odvisnosti, da bi izkoristili prednosti vbrizgavanja odvisnosti (na kratko DI). Navsezadnje smo tudi v [prejšnjem poglavju |introduction] prikazali konkretne primere DI, pri čemer vsebnik ni bil potreben. +Morda vas bo presenetilo, toda v mnogih primerih ne potrebujete dependency injection vsebnika, da bi lahko izkoristili prednosti dependency injection (kratko DI). Saj smo si tudi v [uvodnem poglavju|introduction] na konkretnih primerih DI pokazali in noben vsebnik ni bil potreben. -Če pa morate upravljati veliko število različnih predmetov z veliko odvisnostmi, bo vsebnik za vbrizgavanje odvisnosti resnično uporaben. To je morda primer spletnih aplikacij, zgrajenih na ogrodju. +Če pa morate upravljati veliko število različnih objektov z mnogimi odvisnostmi, bo dependency injection vsebnik resnično koristen. Kar je na primer primer spletnih aplikacij, zgrajenih na ogrodju. -V prejšnjem poglavju smo predstavili razreda `Article` in `UserController`. Oba imata nekaj odvisnosti, in sicer podatkovno bazo in tovarno `ArticleFactory`. In za ta razreda bomo zdaj ustvarili vsebnik. Seveda za tako preprost primer ni smiselno imeti vsebnika. Vendar ga bomo ustvarili, da pokažemo, kako izgleda in deluje. +V prejšnjem poglavju smo si predstavili razreda `Article` in `UserController`. Oba imata neke odvisnosti, in sicer podatkovno bazo in tovarno `ArticleFactory`. In za te razrede si zdaj ustvarimo vsebnik. Seveda za tako preprost primer nima smisla imeti vsebnika. Ampak ga bomo ustvarili, da si pokažemo, kako izgleda in deluje. -Tukaj je preprost vsebnik s trdim kodiranjem za zgornji primer: +Tukaj je preprost hardcoded vsebnik za navedeni primer: ```php class Container @@ -32,16 +32,16 @@ class Container } ``` -Uporaba bi bila videti takole: +Uporaba bi izgledala takole: ```php $container = new Container; $controller = $container->createUserController(); ``` -Za predmet samo zaprosimo vsebnik in nam ni več treba vedeti, kako ga ustvariti ali kakšne so njegove odvisnosti; vsebnik vse to ve. Odvisnosti vsebnik vnese samodejno. V tem je njegova moč. +Vsebniku samo vprašamo za objekt in že nam ni treba vedeti ničesar o tem, kako ga ustvariti in kakšne ima odvisnosti; vse to ve vsebnik. Odvisnosti so z vsebnikom injicirane samodejno. V tem je njegova moč. -Do zdaj je bilo v vsebniku vse zakodirano. Zato naredimo naslednji korak in dodamo parametre, da bo vsebnik resnično uporaben: +Vsebnik ima zaenkrat zapisane vse podatke trdo kodirano. Naredimo torej naslednji korak in dodajmo parametre, da bo vsebnik resnično koristen: ```php class Container @@ -70,9 +70,9 @@ $container = new Container([ ]); ``` -Bolj pozorni bralci so morda opazili težavo. Vsakič, ko dobim predmet `UserController`, se ustvari tudi nova instanca `ArticleFactory` in podatkovna zbirka. Tega vsekakor ne želimo. +Bistri bralci so morda opazili določeno težavo. Vsakič, ko pridobim objekt `UserController`, se ustvari tudi nova instanca `ArticleFactory` in podatkovne baze. Tega zagotovo nočemo. -Zato dodamo metodo `getService()`, ki bo vedno znova vračala iste instance: +Dodajmo zato metodo `getService()`, ki bo vračala vedno iste instance: ```php class Container @@ -87,7 +87,7 @@ class Container public function getService(string $name): object { if (!isset($this->services[$name])) { - // getService('Database') pokliče createDatabase() + // getService('Database') bo klical createDatabase() $method = 'create' . $name; $this->services[$name] = $this->$method(); } @@ -98,9 +98,9 @@ class Container } ``` -Pri prvem klicu npr. na `$container->getService('Database')` bo `createDatabase()` ustvaril objekt podatkovne zbirke, ki ga bo shranil v polje `$services` in ga ob naslednjem klicu neposredno vrnil. +Pri prvem klicu npr. `$container->getService('Database')` si pusti od `createDatabase()` ustvariti objekt podatkovne baze, ki ga shrani v polje `$services` in pri naslednjem klicu ga takoj vrne. -Tudi preostali del vsebnika spremenimo tako, da bo uporabljal `getService()`: +Prilagodimo tudi preostanek vsebnika, da bo uporabljal `getService()`: ```php class Container @@ -119,9 +119,9 @@ class Container } ``` -Mimogrede, izraz storitev se nanaša na kateri koli predmet, ki ga upravlja vsebnik. Od tod tudi ime metode `getService()`. +Mimogrede, izraz storitev se nanaša na kateri koli objekt, ki ga upravlja vsebnik. Zato tudi ime metode `getService()`. -Opravljeno. Imamo popolnoma funkcionalen vsebnik DI! In lahko ga uporabljamo: +Končano. Imamo popolnoma funkcionalen DI vsebnik! In lahko ga uporabimo: ```php $container = new Container([ @@ -134,9 +134,9 @@ $controller = $container->getService('UserController'); $database = $container->getService('Database'); ``` -Kot lahko vidite, ni težko napisati DIC. Pomembno je, da sami objekti ne vedo, da jih ustvarja vsebnik. Tako je mogoče na ta način ustvariti kateri koli objekt v PHP, ne da bi pri tem vplivali na njihovo izvorno kodo. +Kot vidite, napisati DIC ni nič zapletenega. Omeniti velja, da sami objekti ne vedo, da jih ustvarja nek vsebnik. S tem je mogoče tako ustvarjati kateri koli objekt v PHP brez posega v njegovo izvorno kodo. -Ročno ustvarjanje in vzdrževanje razreda vsebnika lahko precej hitro postane nočna mora. Zato bomo v naslednjem poglavju govorili o [vsebniku Nette DI |nette-container], ki se lahko ustvari in posodablja skoraj samodejno. +Ročno ustvarjanje in vzdrževanje razreda vsebnika se lahko precej hitro spremeni v nočno moro. V naslednjem poglavju si zato povemo o [Nette DI Containeru|nette-container], ki se zna generirati in posodabljati skoraj sam. -{{maintitle: Kaj je vsebnik za vbrizgavanje odvisnosti?}} +{{maintitle: Kaj je dependency injection vsebnik?}} diff --git a/dependency-injection/sl/extensions.texy b/dependency-injection/sl/extensions.texy index 6deff98ca1..56ee1b9806 100644 --- a/dependency-injection/sl/extensions.texy +++ b/dependency-injection/sl/extensions.texy @@ -2,18 +2,18 @@ Ustvarjanje razširitev za Nette DI ********************************** .[perex] -Generiranje vsebnika DI poleg konfiguracijskih datotek vpliva tudi na tako imenovane *razširitve*. Aktiviramo jih v konfiguracijski datoteki v razdelku `extensions`. +Generiranje DI vsebnika poleg konfiguracijskih datotek vplivajo še t.i. *razširitve*. Aktiviramo jih v konfiguracijski datoteki v sekciji `extensions`. -Tako dodamo razširitev, ki jo predstavlja razred `BlogExtension` z imenom `blog`: +Tako dodamo razširitev, predstavljeno z razredom `BlogExtension`, pod imenom `blog`: ```neon extensions: blog: BlogExtension ``` -Vsaka razširitev za sestavljanje podeduje od [api:Nette\DI\CompilerExtension] in lahko implementira naslednje metode, ki se kličejo med sestavljanjem DI: +Vsaka razširitev kompilerja deduje od [api:Nette\DI\CompilerExtension] in lahko implementira naslednje metode, ki so postopoma klicane med sestavljanjem DI vsebnika: -getConfigSchema(). +1. getConfigSchema() 2. loadConfiguration() 3. beforeCompile() 4. afterCompile() @@ -22,18 +22,18 @@ getConfigSchema(). getConfigSchema() .[method] =========================== -Ta metoda se pokliče prva. Opredeljuje shemo, ki se uporablja za preverjanje konfiguracijskih parametrov. +Ta metoda se kliče prva. Definira shemo za validacijo konfiguracijskih parametrov. -Razširitve se konfigurirajo v razdelku, katerega ime je enako tistemu, pod katerim je bila razširitev dodana, npr. `blog`. +Razširitev konfiguriramo v sekciji, katere ime je enako tistemu, pod katerim je bila razširitev dodana, torej `blog`: ```neon -# enako ime kot moja razširitev +# enako ime kot ima extension blog: postsPerPage: 10 - comments: false + allowComments: false ``` -Opredelili bomo shemo, ki opisuje vse možnosti konfiguracije, vključno z njihovimi vrstami, sprejetimi vrednostmi in po možnosti privzetimi vrednostmi: +Ustvarimo shemo, ki opisuje vse konfiguracijske možnosti, vključno z njihovimi tipi, dovoljenimi vrednostmi in po potrebi tudi privzetimi vrednostmi: ```php use Nette\Schema\Expect; @@ -50,9 +50,9 @@ class BlogExtension extends Nette\DI\CompilerExtension } ``` -Za dokumentacijo glejte [shemo |schema:]. Poleg tega lahko določite, katere možnosti so lahko [dinamične |application:bootstrap#Dynamic Parameters] z uporabo `dynamic()`, na primer `Expect::int()->dynamic()`. +Dokumentacijo najdete na strani [Shema |schema:]. Poleg tega lahko določimo, katere možnosti so lahko [dinamične |application:bootstrapping#Dinamični parametri] z uporabo `dynamic()`, npr. `Expect::int()->dynamic()`. -Do konfiguracije dostopamo prek `$this->config`, ki je objekt `stdClass`: +Do konfiguracije dostopamo prek spremenljivke `$this->config`, ki je objekt `stdClass`: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -71,7 +71,7 @@ class BlogExtension extends Nette\DI\CompilerExtension loadConfiguration() .[method] ============================= -Ta metoda se uporablja za dodajanje storitev v vsebnik. To storimo s [api:Nette\DI\ContainerBuilder]: +Uporablja se za dodajanje storitev v vsebnik. Za to služi [api:Nette\DI\ContainerBuilder]: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -80,25 +80,25 @@ class BlogExtension extends Nette\DI\CompilerExtension { $builder = $this->getContainerBuilder(); $builder->addDefinition($this->prefix('articles')) - ->setFactory(App\Model\HomepageArticles::class, ['@connection']) // ali setCreator() + ->setFactory(App\Model\HomepageArticles::class, ['@connection']) // or setCreator() ->addSetup('setLogger', ['@logger']); } } ``` -Običajno se storitve, ki jih doda razširitev, opremijo z njenim imenom, tako da ne pride do navzkrižja imen. To stori `prefix()`, tako da če se razširitev imenuje "blog", se bo storitev imenovala `blog.articles`. +Konvencija je, da storitve, dodane z razširitvijo, predponamo z njenim imenom, da ne pride do konflikta imen. To počne metoda `prefix()`, tako da če se razširitev imenuje `blog`, bo storitev nosila ime `blog.articles`. -Če moramo storitev preimenovati, lahko ustvarimo vzdevek z njenim prvotnim imenom, da ohranimo združljivost za nazaj. Podobno stori Nette npr. za `routing.router`, ki je na voljo tudi pod prejšnjim imenom `router`. +Če moramo storitev preimenovati, lahko zaradi ohranjanja povratne združljivosti ustvarimo alias s prvotnim imenom. Podobno to počne Nette npr. pri storitvi `routing.router`, ki je dostopna tudi pod prejšnjim imenom `router`. ```php $builder->addAlias('router', 'routing.router'); ``` -Pridobivanje storitev iz datoteke .[#toc-retrieve-services-from-a-file] ------------------------------------------------------------------------ +Nalaganje storitev iz datoteke +------------------------------ -Storitve lahko ustvarimo z uporabo API ContainerBuilder, lahko pa jih dodamo tudi prek znane konfiguracijske datoteke NEON in njenega razdelka `services`. Predpona `@extension` predstavlja trenutno razširitev. +Storitve ne ustvarjamo samo z API-jem razreda ContainerBuilder, ampak tudi z znanim zapisom, uporabljenim v konfiguracijski datoteki NEON v sekciji services. Predpona `@extension` predstavlja trenutno razširitev. ```neon services: @@ -112,7 +112,7 @@ services: create: MyBlog\Components\ArticlesList(@extension.articles) ``` -Storitve bomo dodajali na ta način: +Storitve naložimo: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -121,7 +121,7 @@ class BlogExtension extends Nette\DI\CompilerExtension { $builder = $this->getContainerBuilder(); - // naloži konfiguracijsko datoteko za razširitev + // nalaganje konfiguracijske datoteke za razširitev $this->compiler->loadDefinitionsFromConfig( $this->loadFromFile(__DIR__ . '/blog.neon')['services'], ); @@ -133,7 +133,7 @@ class BlogExtension extends Nette\DI\CompilerExtension beforeCompile() .[method] ========================= -Metoda se pokliče, ko vsebnik vsebuje vse storitve, ki so jih dodale posamezne razširitve v metodah `loadConfiguration`, in uporabniške konfiguracijske datoteke. V tej fazi sestavljanja lahko nato spreminjamo definicije storitev ali dodajamo povezave med njimi. Za iskanje storitev po oznakah lahko uporabite metodo `findByTag()`, za iskanje po razredih ali vmesnikih pa metodo `findByType()`. +Metoda se kliče v trenutku, ko vsebnik vsebuje vse storitve, dodane z posameznimi razširitvami v metodah `loadConfiguration` in tudi z uporabniškimi konfiguracijskimi datotekami. V tej fazi sestavljanja torej lahko definicije storitev urejamo ali dopolnimo povezave med njimi. Za iskanje storitev v vsebniku po oznakah lahko uporabimo metodo `findByTag()`, po razredu ali vmesniku pa metodo `findByType()`. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -153,7 +153,7 @@ class BlogExtension extends Nette\DI\CompilerExtension afterCompile() .[method] ======================== -V tej fazi je razred vsebnika že ustvarjen kot objekt [ClassType |php-generator:#classes], vsebuje vse metode, ki jih ustvari storitev, in je pripravljen za predpomnjenje kot datoteka PHP. Na tej točki lahko še vedno urejamo kodo nastalega razreda. +V tej fazi je razred vsebnika že generiran v obliki objekta [ClassType |php-generator:#Razredi], vsebuje vse metode, ki ustvarjajo storitve, in je pripravljen za zapis v predpomnilnik. Rezultatno kodo razreda lahko v tej točki še vedno urejamo. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -167,12 +167,12 @@ class BlogExtension extends Nette\DI\CompilerExtension ``` -$initialization .[wiki-method] -============================== +$initialization .[method] +========================= -Konfigurator pokliče inicializacijsko kodo po [ustvarjanju vsebnika |application:bootstrap#index.php], ki se ustvari z zapisom v objekt `$this->initialization` z [metodo addBody() |php-generator:#method-and-function-body]. +Razred Configurator po [ustvarjanju vsebnika |application:bootstrapping#index.php] kliče inicializacijsko kodo, ki se ustvarja z zapisom v objekt `$this->initialization` z uporabo [metode addBody() |php-generator:#Telesa metod in funkcij]. -Prikazali bomo primer, kako z inicializacijsko kodo zaženemo sejo ali zaženemo storitve, ki imajo oznako `run`: +Pokažimo si primer, kako na primer z inicializacijsko kodo zagnati sejo ali zagnati storitve, ki imajo oznako `run`: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -184,7 +184,7 @@ class BlogExtension extends Nette\DI\CompilerExtension $this->initialization->addBody('$this->getService("session")->start()'); } - // storitve z oznako 'run' je treba ustvariti po namestitvi vsebnika. + // storitve z oznako run morajo biti ustvarjene po instanciranju vsebnika $builder = $this->getContainerBuilder(); foreach ($builder->findByTag('run') as $name => $foo) { $this->initialization->addBody('$this->getService(?);', [$name]); diff --git a/dependency-injection/sl/factory.texy b/dependency-injection/sl/factory.texy index e13c164e8e..f4cd79285c 100644 --- a/dependency-injection/sl/factory.texy +++ b/dependency-injection/sl/factory.texy @@ -2,11 +2,11 @@ Generirane tovarne ****************** .[perex] -Nette DI lahko na podlagi vmesnika samodejno ustvari kodo tovarne, kar vam prihrani pisanje kode. +Nette DI zna samodejno generirati kodo tovarn na podlagi vmesnikov, kar vam prihrani pisanje kode. -Tovarna je razred, ki ustvarja in konfigurira predmete. Zato jim posreduje tudi njihove odvisnosti. Ne zamenjujte z oblikovnim vzorcem *tovarovna metoda*, ki opisuje poseben način uporabe tovarn in ni povezan s to temo. +Tovarna je razred, ki izdeluje in konfigurira objekte. Posreduje jim torej tudi njihove odvisnosti. Ne zamenjujte prosim z načrtovalskim vzorcem *factory method*, ki opisuje specifičen način uporabe tovarn in s to temo ni povezan. -V [uvodnem poglavju |introduction#factory] smo pokazali, kako je videti takšna tovarna: +Kako taka tovarna izgleda, smo si pokazali v [uvodnem poglavju |introduction#Tovarna]: ```php class ArticleFactory @@ -23,7 +23,7 @@ class ArticleFactory } ``` -Nette DI lahko samodejno ustvari tovarniško kodo. Vse, kar morate storiti, je, da ustvarite vmesnik in Nette DI bo ustvaril implementacijo. Vmesnik mora imeti natančno eno metodo z imenom `create` in deklarirati tip vrnitve: +Nette DI zna kodo tovarn samodejno generirati. Vse, kar morate storiti, je ustvariti vmesnik in Nette DI bo generiral implementacijo. Vmesnik mora imeti točno eno metodo z imenom `create` in deklarirati povratni tip: ```php interface ArticleFactory @@ -32,7 +32,7 @@ interface ArticleFactory } ``` -Tako ima tovarna `ArticleFactory` metodo `create`, ki ustvari predmete `Article`. Razred `Article` je lahko na primer videti takole: +Torej tovarna `ArticleFactory` ima metodo `create`, ki ustvarja objekte `Article`. Razred `Article` lahko izgleda na primer takole: ```php class Article @@ -44,16 +44,16 @@ class Article } ``` -Dodajte tovarno v konfiguracijsko datoteko: +Tovarno dodamo v konfiguracijsko datoteko: ```neon services: - ArticleFactory ``` -Nette DI bo ustvaril ustrezno implementacijo tovarne. +Nette DI bo generiral ustrezno implementacijo tovarne. -Tako v kodi, ki uporablja tovarno, zahtevamo objekt po vmesniku, Nette DI pa uporabi generirano implementacijo: +V kodi, ki tovarno uporablja, tako zahtevamo objekt po vmesniku in Nette DI bo uporabil generirano implementacijo: ```php class UserController @@ -65,17 +65,17 @@ class UserController public function foo() { - // naj tovarna ustvari predmet + // pustimo tovarni ustvariti objekt $article = $this->articleFactory->create(); } } ``` -Parametrizirana tovarna .[#toc-parameterized-factory] -===================================================== +Parametrizirana tovarna +======================= -Tovarniška metoda `create` lahko sprejme parametre, ki jih nato posreduje konstruktorju. Na primer, dodajmo ID avtorja članka v razred `Article`: +Tovarniška metoda `create` lahko sprejema parametre, ki jih nato posreduje v konstruktor. Dopolnimo na primer razred `Article` z ID avtorja članka: ```php class Article @@ -88,7 +88,7 @@ class Article } ``` -Parameter bomo dodali tudi tovarni: +Parameter dodamo tudi v tovarno: ```php interface ArticleFactory @@ -97,13 +97,13 @@ interface ArticleFactory } ``` -Ker imata parameter v konstruktorju in parameter v tovarni enako ime, ju bo Nette DI samodejno posredoval. +Zahvaljujoč temu, da se parameter v konstruktorju in parameter v tovarni imenujeta enako, jih Nette DI popolnoma samodejno posreduje. -Napredna definicija .[#toc-advanced-definition] -=============================================== +Napredna definicija +=================== -Definicijo lahko zapišete tudi v večvrstični obliki z uporabo tipke `implement`: +Definicijo lahko zapišemo tudi v večvrstični obliki z uporabo ključa `implement`: ```neon services: @@ -111,9 +111,9 @@ services: implement: ArticleFactory ``` -Pri tem daljšem zapisu je mogoče navesti dodatne argumente za konstruktor v ključu `arguments` in dodatno konfiguracijo s ključem `setup`, tako kot pri običajnih storitvah. +Pri zapisu na ta daljši način je mogoče navesti dodatne argumente za konstruktor v ključu `arguments` in dopolnilno konfiguracijo z uporabo `setup`, enako kot pri običajnih storitvah. -Primer: če metoda `create()` ne bi sprejela parametra `$authorId`, bi lahko v konfiguraciji določili fiksno vrednost, ki bi se posredovala konstruktorju `Article`: +Primer: če metoda `create()` ne bi sprejemala parametra `$authorId`, bi lahko navedli fiksno vrednost v konfiguraciji, ki bi se posredovala v konstruktor `Article`: ```neon services: @@ -123,7 +123,7 @@ services: authorId: 123 ``` -Ali obratno, če bi `create()` sprejel parameter `$authorId`, vendar ta ne bi bil del konstruktorja in bi ga posredovala metoda `Article::setAuthorId()`, bi se nanj sklicevali v razdelku `setup`: +Ali obratno, če bi `create()` parameter `$authorId` sprejemala, vendar ne bi bil del konstruktorja in bi se posredoval z metodo `Article::setAuthorId()`, bi se nanj sklicevali v sekciji `setup`: ```neon services: @@ -134,15 +134,14 @@ services: ``` -Accessor .[#toc-accessor] -========================= +Accessor +======== -Poleg tovarn lahko Nette ustvari tudi tako imenovane dostopnike. Accessor je objekt z metodo `get()`, ki vrača določeno storitev iz vsebnika DI. Več klicev `get()` bo vedno vrnilo isto instanco. +Nette zna poleg tovarn generirati tudi t.i. accessorje. Gre za objekte z metodo `get()`, ki vrača določeno storitev iz DI vsebnika. Ponavljajoči klic `get()` vrača vedno isto instanco. -Dostopi prinašajo lenobno nalaganje v odvisnosti. Imejmo razred, ki beleži napake v posebno podatkovno zbirko. Če bi bila povezava s podatkovno bazo posredovana kot odvisnost v njegovem konstruktorju, bi bilo treba povezavo vedno ustvariti, čeprav bi jo uporabili le redko, ko se pojavi napaka, zato bi povezava ostala večinoma neuporabljena. -Namesto tega lahko razred posreduje dostopnik in ko se pokliče njegova metoda `get()`, se šele takrat ustvari objekt podatkovne zbirke: +Accessorji zagotavljajo odvisnostim lazy-loading. Imejmo razred, ki zapisuje napake v posebno podatkovno bazo. Če bi si ta razred pustil povezavo z podatkovno bazo posredovati kot odvisnost prek konstruktorja, bi se morala povezava vedno ustvariti, čeprav se v praksi napaka pojavi le izjemoma in bi torej večinoma povezava ostala neizkoriščena. Namesto tega si razred posreduje accessor in šele ko se pokliče njegov `get()`, pride do ustvarjanja objekta podatkovne baze: -Kako ustvariti accessor? Napišite samo vmesnik in Nette DI bo ustvaril implementacijo. Vmesnik mora imeti natanko eno metodo z imenom `get` in mora deklarirati tip vrnitve: +Kako ustvariti accessor? Zadostuje napisati vmesnik in Nette DI bo generiral implementacijo. Vmesnik mora imeti točno eno metodo z imenom `get` in deklarirati povratni tip: ```php interface PDOAccessor @@ -151,7 +150,7 @@ interface PDOAccessor } ``` -Dostopnik dodajte v konfiguracijsko datoteko skupaj z definicijo storitve, ki jo bo dostopnik vrnil: +Accessor dodamo v konfiguracijsko datoteko, kjer je tudi definicija storitve, ki jo bo vračal: ```neon services: @@ -159,62 +158,68 @@ services: - PDO(%dsn%, %user%, %password%) ``` - `PDO` Ker je v konfiguraciji samo ena takšna storitev, jo bo vrnil. Pri več konfiguriranih storitvah te vrste lahko določite, katera naj se vrne, z uporabo njenega imena, na primer `- PDOAccessor(@db1)`. +Ker accessor vrača storitev tipa `PDO` in je v konfiguraciji edina taka storitev, bo vračal prav njo. Če bi bilo storitev danega tipa več, določimo vračano storitev z imenom, npr. `- PDOAccessor(@db1)`. -Multifactory/Accessor .[#toc-multifactory-accessor] -=================================================== -Do zdaj so lahko tovarne in dostopniki ustvarili ali vrnili samo en predmet. Ustvarimo lahko tudi večfazo v kombinaciji z dostopnikom. Vmesnik takega razreda multifactory je lahko sestavljen iz več metod, imenovanih `create<name>()` in `get<name>()`na primer: +Večkratna tovarna/accessor +========================== +Naše tovarne in accessorji so doslej vedno znali izdelovati ali vračati samo en objekt. Lahko pa zelo enostavno ustvarimo tudi večkratne tovarne, kombinirane z accessorji. Vmesnik takega razreda bo vseboval poljubno število metod z imeni `create<name>()` in `get<name>()`, npr.: ```php interface MultiFactory { function createArticle(): Article; - function createFoo(): Model\Foo; function getDb(): PDO; } ``` -Namesto več ustvarjenih tovarn in dostopov lahko posredujete samo eno kompleksno večfazno tovarno. +Torej namesto da bi si posredovali več generiranih tovarn in accessorjev, posredujemo eno kompleksnejšo tovarno, ki zna več. -Namesto več metod lahko uporabite tudi `create()` in `get()` s parametrom: +Alternativno lahko namesto več metod uporabimo `get()` s parametrom: ```php interface MultiFactoryAlt { - function create($name); function get($name): PDO; } ``` -V tem primeru `MultiFactory::createArticle()` naredi isto kot `MultiFactoryAlt::create('article')`. Vendar ima alternativna sintaksa nekaj pomanjkljivosti. Ni jasno, katere vrednosti `$name` so podprte, v vmesniku pa ni mogoče določiti tipa vrnitve, če uporabljate več različnih vrednosti `$name`. +Potem velja, da `MultiFactory::getArticle()` počne isto kot `MultiFactoryAlt::get('article')`. Vendar ima alternativni zapis to slabost, da ni očitno, katere vrednosti `$name` so podprte in logično tudi ni mogoče v vmesniku ločiti različnih povratnih vrednosti za različne `$name`. -Opredelitev s seznamom .[#toc-definition-with-a-list] ------------------------------------------------------ -Hos opredeliti večpredstavnostno zbirko v svoji konfiguraciji? Ustvarimo tri storitve, ki jih bo vrnila večfazarnica, in samo večfazarnico: +Definicija s seznamom +--------------------- +Na ta način lahko definiramo večkratno tovarno v konfiguraciji: .{data-version:3.2.0} + +```neon +services: + - MultiFactory( + article: Article # definira createArticle() + db: PDO(%dsn%, %user%, %password%) # definira getDb() + ) +``` + +Ali pa se lahko v definiciji tovarne sklicujemo na obstoječe storitve z referenco: ```neon services: article: Article - - Model\Foo - PDO(%dsn%, %user%, %password%) - MultiFactory( - article: @article # createArticle() - foo: @Model\Foo # createFoo() - db: @\PDO # getDb() + article: @article # definira createArticle() + db: @\PDO # definira getDb() ) ``` -Opredelitev z oznakami .[#toc-definition-with-tags] ---------------------------------------------------- +Definicija z oznakami +--------------------- -Druga možnost, kako opredeliti večpredstavnostno zbirko, je uporaba [oznak |services#Tags]: +Druga možnost je uporaba [oznak |services#Oznake] za definicijo: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter - App\Model\DatabaseAccessor( db1: @database.db1.explorer ) diff --git a/dependency-injection/sl/faq.texy b/dependency-injection/sl/faq.texy index 52b344bd7a..b8e129a93b 100644 --- a/dependency-injection/sl/faq.texy +++ b/dependency-injection/sl/faq.texy @@ -1,98 +1,92 @@ -Pogosta vprašanja o DI (FAQ) -**************************** +Pogosto zastavljena vprašanja o DI (FAQ) +**************************************** -Ali je DI drugo ime za IoC? .[#toc-is-di-another-name-for-ioc] --------------------------------------------------------------- +Je DI drugo ime za IoC? +----------------------- -*Inversion of Control* (IoC) je načelo, ki se osredotoča na način izvajanja kode - ali vaša koda sproži zunanjo kodo ali pa je vaša koda vključena v zunanjo kodo, ki jo nato pokliče. -IoC je širok koncept, ki vključuje [dogodke |nette:glossary#Events], tako imenovano [hollywoodsko načelo |application:components#Hollywood style] in druge vidike. -Sestavni deli tega koncepta so tudi tovarne, ki so del [pravila #3: Naj to opravi tovarna |introduction#Rule #3: Let the Factory Handle It], in predstavljajo inverzijo za operator `new`. +*Inversion of Control* (IoC) je načelo, osredotočeno na način, kako se koda izvaja - ali vaša koda izvaja tujo ali je vaša koda integrirana v tujo, ki jo nato kliče. IoC je širok pojem, ki vključuje [dogodke |nette:glossary#Dogodki eventi], tako imenovani [Hollywoodski princip |application:components#Hollywood style] in druge vidike. Del tega koncepta so tudi tovarne, o katerih govori [Pravilo št. 3: pusti tovarni |introduction#Pravilo št. 3: prepusti tovarni], in ki predstavljajo inverzijo za operator `new`. -Pri *Dependency Injection* (DI) gre za to, kako en objekt ve za drug objekt, tj. za odvisnost. Gre za načrtovalski vzorec, ki zahteva izrecno posredovanje odvisnosti med objekti. +*Dependency Injection* (DI) se osredotoča na način, kako en objekt izve za drug objekt, torej za njegove odvisnosti. Gre za načrtovalski vzorec, ki zahteva eksplicitno posredovanje odvisnosti med objekti. -Tako lahko rečemo, da je DI posebna oblika IoC. Vendar pa vse oblike IoC niso primerne z vidika čistosti kode. Med anti-vzorce na primer uvrščamo vse tehnike, ki delajo z [globalnim stanjem |global state], ali tako imenovani [Service Locator |#What is a Service Locator]. +Lahko torej rečemo, da je DI specifična oblika IoC. Vendar niso vse oblike IoC primerne z vidika čistosti kode. Na primer, med antivzorci so tehnike, ki delujejo z [globalnim stanjem |global-state] ali tako imenovani [Service Locator |#Kaj je Service Locator]. -Kaj je iskalnik storitev? .[#toc-what-is-a-service-locator] ------------------------------------------------------------ +Kaj je Service Locator? +----------------------- -Iskalnik storitev je alternativa vbrizganju odvisnosti. Deluje tako, da ustvari osrednje skladišče, v katerem so registrirane vse razpoložljive storitve ali odvisnosti. Ko objekt potrebuje odvisnost, jo zahteva od iskalnika storitev. +Gre za alternativo Dependency Injection. Deluje tako, da ustvari centralno shrambo, kjer so registrirane vse razpoložljive storitve ali odvisnosti. Ko objekt potrebuje odvisnost, zanjo prosi Service Locator. -Vendar v primerjavi z vbrizgavanjem odvisnosti izgublja preglednost: odvisnosti niso neposredno posredovane objektom in jih zato ni mogoče zlahka prepoznati, zato je treba pregledati kodo, da bi odkrili in razumeli vse povezave. Tudi testiranje je bolj zapleteno, saj testiranim objektom ne moremo preprosto posredovati posnemovalnih objektov, temveč moramo to storiti prek iskalnika storitev. Poleg tega Service Locator moti načrtovanje kode, saj se morajo posamezni objekti zavedati njegovega obstoja, kar se razlikuje od vbrizgavanja odvisnosti, kjer objekti ne poznajo vsebnika DI. +V primerjavi z Dependency Injection pa izgublja na transparentnosti: odvisnosti niso objektom posredovane neposredno in niso tako enostavno prepoznavne, kar zahteva pregled kode, da bi bile vse povezave odkrite in razumljene. Testiranje je prav tako bolj zapleteno, ker ne moremo preprosto posredovati mock objektov testiranim objektom, ampak moramo iti prek Service Locatorja. Poleg tega Service Locator krši načrtovanje kode, saj morajo posamezni objekti vedeti za njegov obstoj, kar se razlikuje od Dependency Injection, kjer objekti nimajo vedenja o DI vsebniku. -Kdaj je bolje, da ne uporabljate DI? .[#toc-when-is-it-better-not-to-use-di] ----------------------------------------------------------------------------- +Kdaj je bolje DI ne uporabiti? +------------------------------ -Ni znanih težav, povezanih z uporabo oblikovnega vzorca Dependency Injection. Nasprotno, pridobivanje odvisnosti z globalno dostopnih lokacij povzroča [številne zaplete, |global-state] prav tako kot uporaba iskalnika storitev. -Zato je priporočljivo vedno uporabljati DI. To ni dogmatičen pristop, ampak preprosto ni bilo mogoče najti boljše alternative. +Niso znane nobene težave, povezane z uporabo načrtovalskega vzorca Dependency Injection. Nasprotno, pridobivanje odvisnosti iz globalno dostopnih mest vodi k [celi vrsti zapletov |global-state], enako velja za uporabo Service Locatorja. Zato je primerno uporabljati DI vedno. To ni dogmatski pristop, ampak preprosto ni bila najdena boljša alternativa. -Vendar pa obstajajo določene situacije, v katerih si predmetov ne posredujemo med seboj in jih pridobivamo iz globalnega prostora. Na primer pri razhroščevanju kode, ko moramo izpisati vrednost spremenljivke na določeni točki programa, izmeriti trajanje določenega dela programa ali zabeležiti sporočilo. -V takih primerih, ko gre za začasna dejanja, ki bodo pozneje odstranjena iz kode, je upravičena uporaba globalno dostopnega odlagalnika, štoparice ali loggerja. Ta orodja navsezadnje ne sodijo v zasnovo kode. +Kljub temu obstajajo določene situacije, ko si objektov ne posredujemo in jih pridobimo iz globalnega prostora. Na primer pri razhroščevanju kode, ko morate na določeni točki programa izpisati vrednost spremenljivke, izmeriti trajanje določenega dela programa ali zabeležiti sporočilo. V takih primerih, ko gre za začasna dejanja, ki bodo kasneje odstranjena iz kode, je legitimno uporabiti globalno dostopen dumper, štoparico ali logger. Ti orodji namreč ne spadajo k načrtovanju kode. -Ali ima uporaba DI svoje slabosti? .[#toc-does-using-di-have-its-drawbacks] ---------------------------------------------------------------------------- +Ima uporaba DI svoje slabe strani? +---------------------------------- -Ali ima uporaba vrivanja odvisnosti kakšne slabosti, na primer večjo zapletenost pisanja kode ali slabšo zmogljivost? Kaj izgubimo, ko začnemo pisati kodo v skladu z DI? +Ali uporaba Dependency Injection prinaša kakšne slabosti, kot na primer povečano zahtevnost pisanja kode ali poslabšano zmogljivost? Kaj izgubimo, ko začnemo pisati kodo v skladu z DI? -DI ne vpliva na zmogljivost aplikacije ali pomnilniške zahteve. Delovanje vsebnika DI lahko igra vlogo, vendar je v primeru [Nette DI | nette-container] vsebnik sestavljen v čistem jeziku PHP, zato je njegova obremenitev med izvajanjem aplikacije v bistvu nična. +DI nima vpliva na zmogljivost ali pomnilniške zahteve aplikacije. Določeno vlogo lahko igra zmogljivost DI Containerja, vendar v primeru [Nette DI |nette-container] je vsebnik preveden v čisti PHP, tako da je njegova režija med izvajanjem aplikacije v bistvu nična. -Pri pisanju kode je treba ustvariti konstruktorje, ki sprejemajo odvisnosti. V preteklosti je bilo to lahko zamudno, vendar je zaradi sodobnih IDE in [spodbujanja lastnosti konstruktorjev |https://blog.nette.org/sl/php-8-0-popoln-pregled-novic#toc-constructor-property-promotion] zdaj to vprašanje nekaj sekund. Konstruktorje lahko preprosto ustvarite z uporabo Nette DI in vtičnika PhpStorm z le nekaj kliki. -Po drugi strani pa ni treba pisati singletonov in statičnih točk dostopa. +Pri pisanju kode je včasih treba ustvarjati konstruktorje, ki sprejemajo odvisnosti. Prej je to lahko bilo dolgotrajno, vendar je zahvaljujoč sodobnim IDE in [constructor property promotion |https://blog.nette.org/sl/php-8-0-complete-overview-of-news#toc-constructor-property-promotion] zdaj vprašanje nekaj sekund. Tovarne lahko enostavno generiramo z Nette DI in vtičnikom za PhpStorm s klikom miške. Po drugi strani odpade potreba po pisanju singletonov in statičnih dostopnih točk. -Zaključimo lahko, da pravilno zasnovana aplikacija z uporabo DI ni niti krajša niti daljša v primerjavi z aplikacijo, ki uporablja singletone. Deli kode, ki delajo z odvisnostmi, se preprosto izločijo iz posameznih razredov in premaknejo na nova mesta, tj. v vsebnik DI in tovarne. +Lahko ugotovimo, da pravilno načrtovana aplikacija, ki uporablja DI, v primerjavi z aplikacijo, ki uporablja singletone, ni niti krajša niti daljša. Deli kode, ki delajo z odvisnostmi, so le izvzeti iz posameznih razredov in premaknjeni na nova mesta, torej v DI vsebnik in tovarne. -Kako prepisati starejšo aplikacijo na DI? .[#toc-how-to-rewrite-a-legacy-application-to-di] -------------------------------------------------------------------------------------------- +Kako prenoviti staro aplikacijo na DI? +-------------------------------------- -Prehod iz starejše aplikacije na vbrizgavanje odvisnosti je lahko zahteven proces, zlasti pri velikih in zapletenih aplikacijah. Pomembno je, da se tega procesa lotite sistematično. +Prehod s stare aplikacije na Dependency Injection je lahko zahteven proces, zlasti pri velikih in kompleksnih aplikacijah. Pomembno je, da k temu procesu pristopimo sistematično. -- Pri prehodu na vbrizgavanje odvisnosti je pomembno, da vsi člani ekipe razumejo uporabljena načela in prakse. -- Najprej opravite analizo obstoječe aplikacije, da ugotovite ključne komponente in njihove odvisnosti. Ustvarite načrt, kateri deli bodo preoblikovani in v kakšnem vrstnem redu. -- Izvedite vsebnik DI ali, še bolje, uporabite obstoječo knjižnico, kot je Nette DI. -- Postopoma izboljšajte vsak del aplikacije, da bo uporabljal vbrizgavanje odvisnosti. To lahko vključuje spreminjanje konstruktorjev ali metod, da sprejmejo odvisnosti kot parametre. -- Spremenite mesta v kodi, kjer se ustvarjajo objekti odvisnosti, tako da odvisnosti namesto tega vbrizga vsebnik. To lahko vključuje uporabo tovarn. +- Pri prehodu na Dependency Injection je pomembno, da vsi člani ekipe razumejo načela in postopke, ki se uporabljajo. +- Najprej izvedite analizo obstoječe aplikacije in identificirajte ključne komponente ter njihove odvisnosti. Ustvarite načrt, kateri deli bodo refaktorirani in v kakšnem vrstnem redu. +- Implementirajte DI vsebnik ali še bolje uporabite obstoječo knjižnico, na primer Nette DI. +- Postopoma refaktorirajte posamezne dele aplikacije, da bodo uporabljali Dependency Injection. To lahko vključuje prilagoditve konstruktorjev ali metod tako, da sprejemajo odvisnosti kot parametre. +- Prilagodite mesta v kodi, kjer se ustvarjajo objekti z odvisnostmi, da bodo namesto tega odvisnosti injicirane z vsebnikom. To lahko vključuje uporabo tovarn. -Ne pozabite, da je prehod na vbrizgavanje odvisnosti naložba v kakovost kode in dolgoročno trajnost aplikacije. Čeprav je izvajanje teh sprememb morda zahtevno, mora biti rezultat čistejša, bolj modularna in zlahka testabilna koda, ki je pripravljena na prihodnje razširitve in vzdrževanje. +Ne pozabite, da je prehod na Dependency Injection naložba v kakovost kode in dolgoročno vzdržljivost aplikacije. Čeprav je lahko zahtevno izvesti te spremembe, bi moral biti rezultat čistejša, bolj modularna in enostavno testirana koda, ki je pripravljena za prihodnje razširitve in vzdrževanje. -Zakaj ima sestava prednost pred dedovanjem? .[#toc-why-composition-is-preferred-over-inheritance] -------------------------------------------------------------------------------------------------- -Namesto dedovanja je bolje uporabiti sestavo, saj je tako mogoče kodo ponovno uporabiti, ne da bi bilo treba skrbeti za učinek sprememb. Tako zagotavlja bolj ohlapno vezavo, pri kateri nam ni treba skrbeti, da bi sprememba neke kode povzročila spremembo druge odvisne kode. Tipičen primer je situacija, ki je opredeljena kot [pekel konstruktorjev |passing-dependencies#Constructor hell]. +Zakaj se daje prednost kompoziciji pred dedovanjem? +--------------------------------------------------- +Primerneje je uporabljati [kompozicijo |nette:introduction-to-object-oriented-programming#Kompozicija] namesto [dedovanja |nette:introduction-to-object-oriented-programming#Dedovanje], ker služi za ponovno uporabo kode, ne da bi se morali ukvarjati s posledicami sprememb. Zagotavlja torej ohlapnejšo povezavo, pri kateri se nam ni treba bati, da bo sprememba neke kode povzročila potrebo po spremembi druge odvisne kode. Tipičen primer je situacija, označena kot [constructor hell |passing-dependencies#Constructor hell]. -Ali se lahko vsebnik Nette DI Container uporablja zunaj sistema Nette? .[#toc-can-nette-di-container-be-used-outside-of-nette] ------------------------------------------------------------------------------------------------------------------------------- +Ali je mogoče uporabiti Nette DI Container zunaj Nette? +------------------------------------------------------- -Absolutno. Nette DI Container je del sistema Nette, vendar je zasnovan kot samostojna knjižnica, ki se lahko uporablja neodvisno od drugih delov ogrodja. Preprosto jo namestite s programom Composer, ustvarite konfiguracijsko datoteko, ki opredeljuje vaše storitve, in nato z nekaj vrsticami kode PHP ustvarite vsebnik DI. -V svojih projektih lahko takoj začnete uporabljati prednosti vbrizgavanja odvisnosti (Dependency Injection). +Vsekakor. Nette DI Container je del Nette, vendar je zasnovan kot samostojna knjižnica, ki jo je mogoče uporabiti neodvisno od drugih delov ogrodja. Zadostuje jo namestiti z Composerjem, ustvariti konfiguracijsko datoteko z definicijo vaših storitev in nato z nekaj vrsticami PHP kode ustvariti DI vsebnik. In takoj lahko začnete izkoriščati prednosti Dependency Injection v svojih projektih. -V poglavju [Nette DI Container |nette-container] je opisano, kako je videti določen primer uporabe, vključno s kodo. +Kako izgleda konkretna uporaba, vključno s kodami, opisuje poglavje [Nette DI Container |nette-container]. -Zakaj je konfiguracija v datotekah NEON? .[#toc-why-is-the-configuration-in-neon-files] ---------------------------------------------------------------------------------------- +Zakaj je konfiguracija v NEON datotekah? +---------------------------------------- -NEON je preprost in lahko berljiv konfiguracijski jezik, ki je bil razvit v Nette za nastavljanje aplikacij, storitev in njihovih odvisnosti. V primerjavi z JSON ali YAML v ta namen ponuja veliko bolj intuitivne in prilagodljive možnosti. V jeziku NEON lahko na naraven način opišete povezave, ki jih v jezikih Symfony in YAML sploh ne bi bilo mogoče zapisati ali pa le z zapletenim opisom. +NEON je preprost in lahko berljiv konfiguracijski jezik, ki je bil razvit v okviru Nette za nastavitev aplikacij, storitev in njihovih odvisnosti. V primerjavi z JSONom ali YAMLom ponuja za ta namen veliko bolj intuitivne in fleksibilne možnosti. V NEONu je mogoče naravno opisati povezave, ki jih v Symfony & YAMLu ne bi bilo mogoče zapisati bodisi sploh, bodisi le prek zapletenega opisa. -Ali razčlenjevanje datotek NEON upočasnjuje aplikacijo? .[#toc-does-parsing-neon-files-slow-down-the-application] ------------------------------------------------------------------------------------------------------------------ +Ali razčlenjevanje NEON datotek upočasnjuje aplikacijo? +------------------------------------------------------- -Čeprav se datoteke NEON analizirajo zelo hitro, ta vidik ni pomemben. Razlog za to je, da se razčlenjevanje datotek izvede samo enkrat med prvim zagonom aplikacije. Po tem se ustvarja vsebniška koda DI, ki se shrani na disku in izvede za vsako naslednjo zahtevo brez potrebe po nadaljnjem razčlenjevanju. +Čeprav se datoteke NEON razčlenjujejo zelo hitro, ta vidik sploh ni pomemben. Razlog je, da se razčlenjevanje datotek zgodi samo enkrat ob prvem zagonu aplikacije. Nato se generira koda DI vsebnika, shrani se na disk in se zažene ob vsakem naslednjem zahtevku, ne da bi bilo treba izvajati nadaljnje razčlenjevanje. -Tako deluje v produkcijskem okolju. Med razvojem se datoteke NEON analizirajo vsakič, ko se spremeni njihova vsebina, kar zagotavlja, da ima razvijalec vedno na voljo posodobljen vsebnik DI. Kot je bilo že omenjeno, je dejansko razčlenjevanje vprašanje enega trenutka. +Tako to deluje v produkcijskem okolju. Med razvojem se NEON datoteke razčlenjujejo vsakič, ko pride do spremembe njihove vsebine, da ima razvijalec vedno aktualen DI vsebnik. Samo razčlenjevanje je, kot je bilo rečeno, vprašanje trenutka. -Kako lahko v svojem razredu dostopam do parametrov iz konfiguracijske datoteke? .[#toc-how-do-i-access-the-parameters-from-the-configuration-file-in-my-class] --------------------------------------------------------------------------------------------------------------------------------------------------------------- +Kako iz svojega razreda dostopam do parametrov v konfiguracijski datoteki? +-------------------------------------------------------------------------- -Ne pozabite na [pravilo št. 1: Pustite, da vam ga posredujejo |introduction#Rule #1: Let It Be Passed to You]. Če razred zahteva informacije iz konfiguracijske datoteke, nam ni treba ugotavljati, kako dostopati do teh informacij, temveč jih preprosto zahtevamo - na primer prek konstruktorja razreda. Predajo pa izvedemo v konfiguracijski datoteki. +Imejmo v mislih [Pravilo št. 1: naj ti posredujejo |introduction#Pravilo št. 1: naj ti bo predano]. Če razred zahteva informacije iz konfiguracijske datoteke, nam ni treba razmišljati, kako do teh informacij priti, namesto tega jih preprosto zahtevamo - na primer prek konstruktorja razreda. In posredovanje izvedemo v konfiguracijski datoteki. -V tem primeru je `%myParameter%` nadomestek za vrednost parametra `myParameter`, ki bo posredovan konstruktorju `MyClass`: +V tej predstavitvi je `%myParameter%` nadomestni znak za vrednost parametra `myParameter`, ki se posreduje v konstruktor razreda `MyClass`: ```php # config.neon @@ -103,10 +97,10 @@ services: - MyClass(%myParameter%) ``` -Če želite posredovati več parametrov ali uporabiti samodejno povezovanje, je koristno, da [parametre zavijete v objekt |best-practices:passing-settings-to-presenters]. +Če želite posredovati več parametrov ali izkoristiti autowiring, je primerno [parametre zapakirati v objekt |best-practices:passing-settings-to-presenters]. -Ali Nette podpira vmesnik PSR-11 Container? .[#toc-does-nette-support-psr-11-container-interface] -------------------------------------------------------------------------------------------------- +Ali Nette podpira PSR-11: Container interface? +---------------------------------------------- -Vmesnik Nette DI Container ne podpira PSR-11 neposredno. Če pa potrebujete interoperabilnost med vsebnikom Nette DI Container in knjižnicami ali ogrodji, ki pričakujejo vmesnik PSR-11 Container, lahko ustvarite [preprost adapter |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f], ki služi kot most med Nette DI Container in PSR-11. +Nette DI Container ne podpira PSR-11 neposredno. Vendar, če potrebujete interoperabilnost med Nette DI Containerjem in knjižnicami ali ogrodji, ki pričakujejo PSR-11 Container Interface, lahko ustvarite [preprost adapter |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f], ki bo služil kot most med Nette DI Containerjem in PSR-11. diff --git a/dependency-injection/sl/global-state.texy b/dependency-injection/sl/global-state.texy index 841f1d51ab..960d8c9cb9 100644 --- a/dependency-injection/sl/global-state.texy +++ b/dependency-injection/sl/global-state.texy @@ -2,66 +2,62 @@ Globalno stanje in singletoni ***************************** .[perex] -Opozorilo: Naslednji konstrukti so simptomi slabo načrtovane kode: +Opozorilo: Naslednje konstrukcije so znak slabo načrtovane kode: - `Foo::getInstance()` - `DB::insert(...)` - `Article::setDb($db)` - `ClassName::$var` ali `static::$var` -Ali se v svoji kodi srečujete s temi konstrukcijami? Če je tako, jo lahko izboljšate. Morda menite, da so to običajni konstrukti, ki jih pogosto vidimo v vzorčnih rešitvah različnih knjižnic in ogrodij. Če je tako, je njihova zasnova kode pomanjkljiva. +Ali se nekatere od teh konstrukcij pojavljajo v vaši kodi? Potem imate priložnost za njeno izboljšanje. Morda si mislite, da gre za običajne konstrukcije, ki jih vidite na primer tudi v vzorčnih rešitvah različnih knjižnic in ogrodij. Če je temu tako, potem načrtovanje njihove kode ni dobro. -Tukaj ne govorimo o kakšni akademski čistosti. Vsi ti konstrukti imajo eno skupno lastnost: uporabljajo globalno stanje. To pa uničujoče vpliva na kakovost kode. Razredi so zavajajoči glede svojih odvisnosti. Koda postane nepredvidljiva. Razvijalce zmede in zmanjša njihovo učinkovitost. +Zdaj zagotovo ne govorimo o neki akademski čistosti. Vse te konstrukcije imajo eno skupno: izkoriščajo globalno stanje. In to ima uničujoč vpliv na kakovost kode. Razredi lažejo o svojih odvisnostih. Koda postane nepredvidljiva. Zmede programerje in zmanjšuje njihovo učinkovitost. -V tem poglavju bomo pojasnili, zakaj je tako in kako se izogniti globalnemu stanju. +V tem poglavju si bomo razložili, zakaj je temu tako in kako se globalnemu stanju izogniti. -Globalno medsebojno povezovanje .[#toc-global-interlinking] ------------------------------------------------------------ +Globalna povezanost +------------------- -V idealnem svetu naj bi objekt komuniciral samo z objekti, ki so [mu |passing-dependencies] bili [neposredno posredovani |passing-dependencies]. Če ustvarim dva objekta `A` in `B` in med njima nikoli ne prenesem reference, potem niti `A` niti `B` ne moreta dostopati do stanja drugega objekta ali ga spreminjati. To je zelo zaželena lastnost kode. To je podobno, kot če bi imeli baterijo in žarnico; žarnica se ne bo prižgala, dokler je z žico ne povežete z baterijo. +V idealnem svetu bi moral objekt biti sposoben komunicirati samo z objekti, ki so mu bili [neposredno posredovani |passing-dependencies]. Če ustvarim dva objekta `A` in `B` in nikoli ne posredujem reference med njima, potem se niti `A` niti `B` ne moreta dostopati do drugega objekta ali spremeniti njegovega stanja. To je zelo zaželena lastnost kode. Podobno je, kot če imate baterijo in žarnico; žarnica ne bo svetila, dokler je z baterijo ne povežete z žico. -Vendar to ne velja za globalne (statične) spremenljivke ali singletone. Objekt `A` lahko *brezžično* dostopa do objekta `C` in ga spreminja brez posredovanja referenc, tako da pokliče `C::changeSomething()`. Če objekt `B` prav tako dostopa do globalne spremenljivke `C`, potem lahko `A` in `B` vplivata drug na drugega prek `C`. +To pa ne velja pri globalnih (statičnih) spremenljivkah ali singletonih. Objekt `A` bi se lahko *brezžično* dostopal do objekta `C` in ga modificiral brez kakršnega koli posredovanja reference, s klicem `C::changeSomething()`. Če se objekt `B` prav tako oprime globalnega `C`, potem se `A` in `B` lahko medsebojno vplivata prek `C`. -Uporaba globalnih spremenljivk uvaja novo obliko *brezžične* povezave, ki ni vidna navzven. Ustvarja dimno zaveso, ki otežuje razumevanje in uporabo kode. Za resnično razumevanje odvisnosti morajo razvijalci prebrati vsako vrstico izvorne kode, namesto da bi se seznanili le z vmesniki razredov. Poleg tega je ta zapletenost povsem nepotrebna. Globalno stanje se uporablja, ker je zlahka dostopno od koder koli in omogoča na primer pisanje v podatkovno zbirko prek globalne (statične) metode `DB::insert()`. Vendar pa je, kot bomo videli, korist, ki jo ponuja, minimalna, zapleti, ki jih prinaša, pa so hudi. +Uporaba globalnih spremenljivk v sistem vnaša novo obliko *brezžične* povezanosti, ki od zunaj ni vidna. Ustvarja dimno zaveso, ki otežuje razumevanje in uporabo kode. Da bi razvijalci odvisnosti resnično razumeli, morajo prebrati vsako vrstico izvorne kode. Namesto zgolj seznanitve z vmesnikom razredov. Gre poleg tega za popolnoma nepotrebno povezanost. Globalno stanje se uporablja zato, ker je enostavno dostopno od kjerkoli in omogoča na primer zapis v podatkovno bazo prek globalne (statične) metode `DB::insert()`. Ampak kot si bomo pokazali, je prednost, ki jo to prinaša, neznatna, nasprotno pa povzroča usodne zaplete. .[note] -Kar zadeva obnašanje, ni razlike med globalno in statično spremenljivko. So enako škodljive. +Z vidika obnašanja ni razlike med globalno in statično spremenljivko. Sta enako škodljivi. -Strašljivo delovanje na daljavo .[#toc-the-spooky-action-at-a-distance] ------------------------------------------------------------------------ +Strašljivo delovanje na daljavo +------------------------------- -"Spooky action at a distance" - tako je Albert Einstein leta 1935 poimenoval pojav v kvantni fiziki, ki ga je spravil ob živce. -Gre za kvantno prepletenost, katere posebnost je, da ko izmerite informacijo o enem delcu, takoj vplivate na drug delec, tudi če sta med seboj oddaljena na milijone svetlobnih let. -kar navidezno krši temeljni zakon vesolja, da nič ne more potovati hitreje od svetlobe. +"Strašljivo delovanje na daljavo" - tako je slavno leta 1935 Albert Einstein poimenoval pojav v kvantni fiziki, ki mu je naganjal kurjo polt. +Gre za kvantno prepletenost, katere posebnost je, da ko izmerite informacijo o enem delcu, s tem takoj vplivate na drugi delec, tudi če sta med seboj oddaljena milijone svetlobnih let. Kar navidezno krši osnovni zakon vesolja, da se nič ne more širiti hitreje od svetlobe. -V svetu programske opreme lahko "strašljivo delovanje na daljavo" imenujemo situacijo, ko zaženemo proces, za katerega mislimo, da je izoliran (ker mu nismo posredovali nobenih referenc), vendar se na oddaljenih lokacijah sistema zgodijo nepričakovane interakcije in spremembe stanja, o katerih objektu nismo povedali. To se lahko zgodi le prek globalnega stanja. +V svetu programske opreme lahko "strašljivo delovanje na daljavo" poimenujemo situacijo, ko zaženemo nek proces, za katerega menimo, da je izoliran (ker mu nismo posredovali nobenih referenc), vendar na oddaljenih mestih sistema pride do nepričakovanih interakcij in sprememb stanja, o katerih nismo imeli pojma. Do tega lahko pride samo prek globalnega stanja. -Predstavljajte si, da se pridružite skupini za razvoj projekta, ki ima veliko in zrelo bazo kode. Vaš novi vodja vas prosi, da izvedete novo funkcijo, in kot dober razvijalec začnete s pisanjem testa. Ker pa ste novinec v projektu, naredite veliko raziskovalnih testov tipa "kaj se zgodi, če pokličem to metodo". In poskušate napisati naslednji test: +Predstavljajte si, da se pridružite ekipi razvijalcev projekta, ki ima obsežno napredno kodno bazo. Vaš novi vodja vas prosi za implementacijo nove funkcije in vi kot pravi razvijalec začnete s pisanjem testa. Ker pa ste v projektu novi, delate veliko raziskovalnih testov tipa "kaj se zgodi, če pokličem to metodo". In poskusite napisati naslednji test: ```php function testCreditCardCharge() { - $cc = new CreditCard('1234567890123456', 5, 2028); // številko vaše kartice. + $cc = new CreditCard('1234567890123456', 5, 2028); // številka vaše kartice $cc->charge(100); } ``` -Po določenem času na svojem telefonu opazite obvestila iz banke, da je bilo ob vsakem zagonu na vašo kreditno kartico 🤦‍♂️ zaračunanih 100 dolarjev. +Zaženete kodo, morda večkrat, in po nekem času opazite na mobilnem telefonu obvestila iz banke, da se je ob vsakem zagonu odštelo 100 dolarjev z vaše plačilne kartice 🤦‍♂️ -Kako bi lahko test povzročil dejansko obremenitev? S kreditno kartico ni enostavno upravljati. Sodelovati morate s spletno storitvijo tretje osebe, poznati morate naslov URL te spletne storitve, prijaviti se morate in tako naprej. -Nobena od teh informacij ni vključena v test. Še huje, ne veste niti, kje so te informacije prisotne, in zato ne veste, kako zasmehovati zunanje odvisnosti, da se ob vsakem zagonu ne bi ponovno zaračunalo 100 USD. In kako naj bi kot nov razvijalec vedeli, da bo to, kar boste naredili, privedlo do tega, da boste za 100 dolarjev revnejši? +Kako za vraga je lahko test povzročil dejansko odtegnitev denarja? Upravljanje s plačilno kartico ni enostavno. Morate komunicirati s spletno storitvijo tretje osebe, morate poznati URL te spletne storitve, morate se prijaviti in tako naprej. Nobena od teh informacij ni vsebovana v testu. Še huje, niti ne veste, kje so te informacije prisotne, in torej niti kako mockati zunanje odvisnosti, da vsak zagon ne bi vodil k temu, da se ponovno odšteje 100 dolarjev. In kako ste kot novi razvijalec morali vedeti, da bo to, kar se pripravljate storiti, vodilo k temu, da boste za 100 dolarjev revnejši? To je strašljivo delovanje na daljavo! -Ne preostane vam drugega, kot da se prekopate skozi veliko izvorne kode in pri tem sprašujete starejše in izkušenejše kolege, dokler ne razumete, kako delujejo povezave v projektu. -To je posledica dejstva, da ob pogledu na vmesnik razreda `CreditCard` ne morete določiti globalnega stanja, ki ga je treba inicializirati. Tudi pogled v izvorno kodo razreda vam ne bo povedal, katero metodo za inicializacijo je treba poklicati. V najboljšem primeru lahko poiščete globalno spremenljivko, do katere se dostopa, in na podlagi tega poskušate uganiti, kako jo inicializirati. +Ne preostane vam drugega, kot da se dolgo prebijate skozi veliko izvorne kode, sprašujete starejše in izkušenejše kolege, preden razumete, kako povezave v projektu delujejo. To je posledica tega, da ob pogledu na vmesnik razreda `CreditCard` ni mogoče ugotoviti globalnega stanja, ki ga je treba inicializirati. Celo pogled v izvorno kodo razreda vam ne bo razkril, katero inicializacijsko metodo morate poklicati. V najboljšem primeru lahko najdete globalno spremenljivko, do katere se dostopa, in iz nje poskusite uganiti, kako jo inicializirati. -Razredi v takem projektu so patološki lažnivci. Plačilna kartica se pretvarja, da jo lahko preprosto instancirate in pokličete metodo `charge()`. Vendar na skrivaj sodeluje z drugim razredom, `PaymentGateway`. Tudi njegov vmesnik pravi, da ga je mogoče inicializirati samostojno, v resnici pa iz neke konfiguracijske datoteke potegne poverilnice in tako naprej. -Razvijalcem, ki so napisali to kodo, je jasno, da `CreditCard` potrebuje `PaymentGateway`. Zato so kodo napisali na ta način. Toda za vsakogar, ki je novinec v projektu, je to popolna uganka in ovira učenje. +Razredi v takem projektu so patološki lažnivci. Plačilna kartica se pretvarja, da jo zadostuje instancirati in poklicati metodo `charge()`. Skrito pa sodeluje z drugim razredom `PaymentGateway`, ki predstavlja plačilni prehod. Tudi njen vmesnik pravi, da jo je mogoče inicializirati samostojno, vendar v resnici potegne poverilnice iz neke konfiguracijske datoteke in tako naprej. Razvijalcem, ki so to kodo napisali, je jasno, da `CreditCard` potrebuje `PaymentGateway`. Kodo so napisali na ta način. Ampak za vsakogar, ki je v projektu nov, je to popolna uganka in ovira učenje. -Kako popraviti situacijo? Enostavno. **Pustite, da API razglasi odvisnosti.** +Kako situacijo popraviti? Enostavno. **Pustite API-ju, da deklarira odvisnosti.** ```php function testCreditCardCharge() @@ -72,36 +68,35 @@ function testCreditCardCharge() } ``` -Opazite, kako so odnosi v kodi nenadoma očitni. Z izjavo, da metoda `charge()` potrebuje `PaymentGateway`, vam ni treba nikogar spraševati, kako je koda medsebojno odvisna. Veste, da morate ustvariti njen primerek, in ko to poskušate storiti, naletite na dejstvo, da morate zagotoviti parametre dostopa. Brez njih se koda sploh ne bi mogla zagnati. +Opazite, kako so naenkrat povezave znotraj kode očitne. S tem, ko metoda `charge()` deklarira, da potrebuje `PaymentGateway`, vam ni treba nikogar spraševati, kako je koda povezana. Veste, da morate ustvariti njeno instanco, in ko to poskusite, naletite na to, da morate dodati dostopne parametre. Brez njih kode ne bi bilo mogoče niti zagnati. -In kar je najpomembneje, zdaj lahko zasmehujete plačilni prehod, tako da vam ne bo treba plačati 100 dolarjev vsakič, ko boste zagnali test. +In predvsem zdaj lahko plačilni prehod mockate, tako da se vam ob vsakem zagonu testa ne bo zaračunalo 100 dolarjev. -Globalno stanje povzroča, da lahko vaši objekti skrivaj dostopajo do stvari, ki niso deklarirane v njihovih API-jih, in posledično naredi vaše API-je patološke lažnivce. +Globalno stanje povzroča, da se vaši objekti lahko skrivaj dostopajo do stvari, ki niso deklarirane v njihovem API-ju, in posledično delajo iz vaših API-jev patološke lažnivce. -Morda o tem še niste razmišljali na ta način, toda kadarkoli uporabljate globalno stanje, ustvarjate skrivne brezžične komunikacijske kanale. Strašljivo delovanje na daljavo sili razvijalce, da preberejo vsako vrstico kode, da bi razumeli morebitne interakcije, zmanjšuje produktivnost razvijalcev in zmede nove člane ekipe. -Če ste kodo ustvarili vi, poznate prave odvisnosti, vsi, ki pridejo za vami, pa so nevedni. +Morda o tem prej niste tako razmišljali, ampak kadarkoli uporabljate globalno stanje, ustvarjate skrivne brezžične komunikacijske kanale. Strašljivo delovanje na daljavo sili razvijalce, da berejo vsako vrstico kode, da bi razumeli potencialne interakcije, zmanjšuje produktivnost razvijalcev in zmede nove člane ekipe. Če ste vi tisti, ki ste kodo ustvarili, poznate dejanske odvisnosti, ampak vsakdo, ki pride za vami, je nemočen. -Ne pišite kode, ki uporablja globalno stanje, temveč raje prenašajte odvisnosti. To je vbrizgavanje odvisnosti. +Ne pišite kode, ki izkorišča globalno stanje, dajte prednost posredovanju odvisnosti. Torej dependency injection. -Krhkost globalne države .[#toc-brittleness-of-the-global-state] ---------------------------------------------------------------- +Krhkost globalnega stanja +------------------------- -V kodi, ki uporablja globalno stanje in singletone, nikoli ni gotovo, kdaj in kdo je to stanje spremenil. To tveganje je prisotno že pri inicializaciji. Naslednja koda naj bi ustvarila povezavo s podatkovno bazo in inicializirala plačilni prehod, vendar vedno znova vrže izjemo, iskanje vzroka pa je izredno zamudno: +V kodi, ki uporablja globalno stanje in singletone, nikoli ni gotovo, kdaj in kdo je to stanje spremenil. To tveganje se pojavlja že pri inicializaciji. Naslednja koda naj bi ustvarila povezavo s podatkovno bazo in inicializirala plačilni prehod, vendar nenehno meče izjemo in iskanje vzroka je izjemno dolgotrajno: ```php PaymentGateway::init(); DB::init('mysql:', 'user', 'password'); ``` -Podrobno morate pregledati kodo, da ugotovite, da objekt `PaymentGateway` brezžično dostopa do drugih objektov, od katerih nekateri zahtevajo povezavo s podatkovno bazo. Tako morate inicializirati podatkovno zbirko, preden `PaymentGateway`. Vendar vam to skriva dimna zavesa globalnega stanja. Koliko časa bi prihranili, če API vsakega razreda ne bi lagal in deklariral svojih odvisnosti? +Morate podrobno pregledovati kodo, da ugotovite, da objekt `PaymentGateway` brezžično dostopa do drugih objektov, od katerih nekateri zahtevajo povezavo s podatkovno bazo. Torej je treba inicializirati podatkovno bazo prej kot `PaymentGateway`. Vendar dimna zavesa globalnega stanja to pred vami skriva. Koliko časa bi prihranili, če API posameznih razredov ne bi lagal in bi deklariral svoje odvisnosti? ```php $db = new DB('mysql:', 'user', 'password'); $gateway = new PaymentGateway($db, ...); ``` -Podobna težava se pojavi pri uporabi globalnega dostopa do povezave s podatkovno bazo: +Podobna težava se pojavlja tudi pri uporabi globalnega dostopa do povezave s podatkovno bazo: ```php use Illuminate\Support\Facades\DB; @@ -115,9 +110,9 @@ class Article } ``` -Pri klicu metode `save()` ni gotovo, ali je bila povezava s podatkovno bazo že ustvarjena in kdo je odgovoren za njeno ustvarjanje. Če bi na primer želeli spremeniti povezavo s podatkovno bazo sproti, morda za namene testiranja, bi verjetno morali ustvariti dodatne metode, kot sta `DB::reconnect(...)` ali `DB::reconnectForTest()`. +Pri klicu metode `save()` ni gotovo, ali je bila povezava s podatkovno bazo že ustvarjena in kdo nosi odgovornost za njeno ustvarjanje. Če želimo na primer spreminjati povezavo s podatkovno bazo med izvajanjem, na primer zaradi testov, bi morali najverjetneje ustvariti dodatne metode, kot na primer `DB::reconnect(...)` ali `DB::reconnectForTest()`. -Oglejmo si primer: +Razmislimo o primeru: ```php $article = new Article; @@ -127,9 +122,9 @@ Foo::doSomething(); $article->save(); ``` -Kje se lahko prepričamo, da se testna podatkovna zbirka res uporablja, ko kličemo `$article->save()`? Kaj pa, če je metoda `Foo::doSomething()` spremenila globalno povezavo s podatkovno bazo? Da bi to ugotovili, bi morali pregledati izvorno kodo razreda `Foo` in verjetno še mnogih drugih razredov. Vendar bi takšen pristop zagotovil le kratkoročni odgovor, saj se lahko stanje v prihodnosti spremeni. +Kje imamo gotovost, da se pri klicu `$article->save()` res uporablja testna podatkovna baza? Kaj če je metoda `Foo::doSomething()` spremenila globalno povezavo s podatkovno bazo? Za ugotovitev bi morali pregledati izvorno kodo razreda `Foo` in verjetno tudi mnogih drugih razredov. Ta pristop bi prinesel le kratkoročen odgovor, saj se situacija lahko v prihodnosti spremeni. -Kaj pa, če povezavo s podatkovno bazo prenesemo v statično spremenljivko znotraj razreda `Article`? +In kaj če povezavo s podatkovno bazo premaknemo v statično spremenljivko znotraj razreda `Article`? ```php class Article @@ -148,11 +143,11 @@ class Article } ``` -To ne spremeni ničesar. Problem je globalno stanje in ni pomembno, v katerem razredu se skriva. V tem primeru, tako kot v prejšnjem, nimamo pojma, v katero zbirko podatkov se zapiše, ko se kliče metoda `$article->save()`. Kdorkoli na oddaljenem koncu aplikacije lahko kadarkoli spremeni podatkovno zbirko z uporabo metode `Article::setDb()`. Pod našimi rokami. +S tem se sploh nič ni spremenilo. Težava je globalno stanje in popolnoma vseeno je, v katerem razredu se skriva. V tem primeru, enako kot v prejšnjem, nimamo pri klicu metode `$article->save()` nobenega namiga o tem, v katero bazo podatkov se bo zapisalo. Kdorkoli na drugem koncu aplikacije je lahko kadarkoli z `Article::setDb()` bazo podatkov spremenil. Nam pod rokami. -Zaradi globalnega stanja je naša aplikacija **izjemno občutljiva**. +Globalno stanje naredi našo aplikacijo **izjemno krhko**. -Vendar obstaja preprost način za reševanje te težave. Preprosto zahtevajte, da API razglasi odvisnosti, da se zagotovi pravilno delovanje. +Obstaja pa preprost način, kako se s to težavo spopasti. Zadostuje, da API deklarira odvisnosti, s čimer se zagotovi pravilna funkcionalnost. ```php class Article @@ -174,15 +169,15 @@ Foo::doSomething(); $article->save(); ``` -Ta pristop odpravlja skrb zaradi skritih in nepričakovanih sprememb povezav s podatkovno bazo. Zdaj smo prepričani, kje je shranjen članek, in nobena sprememba kode znotraj drugega nepovezanega razreda ne more več spremeniti stanja. Koda ni več krhka, temveč stabilna. +Zahvaljujoč temu pristopu odpade skrb za skrite in nepričakovane spremembe povezave z bazo podatkov. Zdaj imamo gotovost, kam se članek shranjuje in nobene spremembe kode znotraj druge nepovezane razreda že ne morejo situacije spremeniti. Koda ni več krhka, ampak stabilna. -Ne pišite kode, ki uporablja globalno stanje, temveč raje prenašajte odvisnosti. Tako je na voljo vbrizgavanje odvisnosti (dependency injection). +Ne pišite kode, ki izkorišča globalno stanje, dajte prednost posredovanju odvisnosti. Torej dependency injection. -Singleton .[#toc-singleton] ---------------------------- +Singleton +--------- -Singleton je oblikovni vzorec, ki po [definiciji |https://en.wikipedia.org/wiki/Singleton_pattern] iz znane publikacije Gang of Four omejuje razred na en primerek in mu omogoča globalni dostop. Izvedba tega vzorca je običajno podobna naslednji kodi: +Singleton je načrtovalski vzorec, ki po "definiciji":https://en.wikipedia.org/wiki/Singleton_pattern iz znane publikacije Gang of Four omejuje razred na eno samo instanco in ponuja globalni dostop do nje. Implementacija tega vzorca se običajno podobna naslednji kodi: ```php class Singleton @@ -195,38 +190,35 @@ class Singleton return self::$instance; } - // in druge metode, ki izvajajo funkcije razreda + // in druge metode, ki opravljajo funkcije danega razreda } ``` -Na žalost singleton v aplikacijo vnese globalno stanje. Kot smo pokazali zgoraj, je globalno stanje nezaželeno. Zato singleton velja za protivzorec. +Na žalost singleton v aplikacijo uvaja globalno stanje. In kot smo si pokazali zgoraj, je globalno stanje nezaželeno. Zato je singleton obravnavan kot antipattern. -V svoji kodi ne uporabljajte singletonov in jih nadomestite z drugimi mehanizmi. Singletonov resnično ne potrebujete. Če pa morate zagotoviti obstoj enega primerka razreda za celotno aplikacijo, to prepustite [vsebniku DI |container]. -Tako ustvarite aplikacijski singleton ali storitev. S tem razred ne bo več zagotavljal svoje edinstvenosti (tj. ne bo imel metode `getInstance()` in statične spremenljivke) in bo izvajal le svoje funkcije. Tako bo prenehal kršiti načelo ene odgovornosti. +Ne uporabljajte v svoji kodi singletonov in jih nadomestite z drugimi mehanizmi. Singletonov resnično ne potrebujete. Če pa morate zagotoviti obstoj ene same instance razreda za celotno aplikacijo, pustite to [DI vsebniku |container]. Ustvarite tako aplikacijski singleton, ali storitev. S tem se razred preneha ukvarjati z zagotavljanjem svoje lastne edinstvenosti (tj. ne bo imel metode `getInstance()` in statične spremenljivke) in bo opravljal samo svoje funkcije. Tako ne bo več kršil načela ene same odgovornosti. -Globalno stanje v primerjavi s testi .[#toc-global-state-versus-tests] ----------------------------------------------------------------------- +Globalno stanje proti testom +---------------------------- -Pri pisanju testov predpostavljamo, da je vsak test izolirana enota in da vanj ne vstopa zunanje stanje. In nobeno stanje ne zapusti testov. Ko se test konča, mora zbiralnik smeti samodejno odstraniti vsako stanje, povezano s testom. S tem so testi izolirani. Zato lahko teste izvajamo v poljubnem vrstnem redu. +Pri pisanju testov predpostavljamo, da je vsak test izolirana enota in da vanj ne vstopa nobeno zunanje stanje. In nobeno stanje testov ne zapušča. Po zaključku testa bi moralo biti vse povezano stanje s testom samodejno odstranjeno z garbage collectorjem. Zahvaljujoč temu so testi izolirani. Zato lahko teste izvajamo v poljubnem vrstnem redu. -Če pa so prisotna globalna stanja/singletoni, se vse te lepe predpostavke porušijo. Stanje lahko vstopi v test in izstopi iz njega. Nenadoma je vrstni red testov lahko pomemben. +Če pa so prisotna globalna stanja/singletoni, se vse te prijetne predpostavke razblinijo. Stanje lahko vstopa v test in izstopa iz njega. Naenkrat lahko postane pomemben vrstni red testov. -Da bi razvijalci sploh lahko testirali singletone, morajo pogosto omiliti njihove lastnosti, morda tako, da dovolijo zamenjavo primerka z drugim. Takšne rešitve so v najboljšem primeru kretnje, ki ustvarjajo kodo, ki jo je težko vzdrževati in razumeti. Vsak test ali metoda `tearDown()`, ki vpliva na katero koli globalno stanje, mora te spremembe razveljaviti. +Da bi sploh lahko testirali singletone, morajo razvijalci pogosto sprostiti njihove lastnosti, na primer tako, da dovolijo zamenjavo instance z drugo. Take rešitve so v najboljšem primeru hack, ki ustvarja težko vzdržljivo in razumljivo kodo. Vsak test ali metoda `tearDown()`, ki vpliva na katero koli globalno stanje, mora te spremembe vrniti nazaj. -Globalno stanje je največji glavobol pri testiranju enot! +Globalno stanje je največja bolečina pri unit testiranju! -Kako popraviti situacijo? Enostavno. Ne pišite kode, ki uporablja singletone, ampak raje prenašajte odvisnosti. To je vbrizgavanje odvisnosti. +Kako situacijo popraviti? Enostavno. Ne pišite kode, ki izkorišča singletone, dajte prednost posredovanju odvisnosti. Torej dependency injection. -Globalne konstante .[#toc-global-constants] -------------------------------------------- +Globalne konstante +------------------ -Globalno stanje ni omejeno na uporabo singletonov in statičnih spremenljivk, temveč se lahko uporablja tudi za globalne konstante. +Globalno stanje se ne omejuje samo na uporabo singletonov in statičnih spremenljivk, ampak se lahko nanaša tudi na globalne konstante. -Konstante, katerih vrednost nam ne zagotavlja nobenih novih (`M_PI`) ali koristnih (`PREG_BACKTRACK_LIMIT_ERROR`) informacij, so nedvomno v redu. -Nasprotno pa konstante, ki služijo kot način za *brezžično* posredovanje informacij znotraj kode, niso nič drugega kot skrita odvisnost. Kot je `LOG_FILE` v naslednjem primeru. -Uporaba konstante `FILE_APPEND` je popolnoma pravilna. +Konstante, katerih vrednost nam ne prinaša nobene nove (`M_PI`) ali koristne (`PREG_BACKTRACK_LIMIT_ERROR`) informacije, so nedvomno v redu. Nasprotno pa konstante, ki služijo kot način, kako *brezžično* posredovati informacijo znotraj kode, niso nič drugega kot skrita odvisnost. Kot na primer `LOG_FILE` v naslednjem primeru. Uporaba konstante `FILE_APPEND` je popolnoma pravilna. ```php const LOG_FILE = '...'; @@ -242,7 +234,7 @@ class Foo } ``` -V tem primeru moramo parameter deklarirati v konstruktorju razreda `Foo`, da postane del API-ja: +V tem primeru bi morali deklarirati parameter v konstruktorju razreda `Foo`, da postane del API-ja: ```php class Foo @@ -261,44 +253,42 @@ class Foo } ``` -Zdaj lahko posredujemo informacije o poti do datoteke za beleženje in jih po potrebi preprosto spremenimo, kar olajša testiranje in vzdrževanje kode. +Zdaj lahko posredujemo informacijo o poti do datoteke za beleženje in jo enostavno spreminjamo po potrebi, kar olajša testiranje in vzdrževanje kode. -Globalne funkcije in statične metode .[#toc-global-functions-and-static-methods] --------------------------------------------------------------------------------- +Globalne funkcije in statične metode +------------------------------------ -Poudariti želimo, da uporaba statičnih metod in globalnih funkcij sama po sebi ni problematična. Razložili smo neprimernost uporabe `DB::insert()` in podobnih metod, vedno pa je šlo za globalno stanje, shranjeno v statični spremenljivki. Metoda `DB::insert()` zahteva obstoj statične spremenljivke, ker shranjuje povezavo s podatkovno bazo. Brez te spremenljivke metode ne bi bilo mogoče izvesti. +Želimo poudariti, da sama uporaba statičnih metod in globalnih funkcij ni problematična. Razložili smo, v čem je neprimernost uporabe `DB::insert()` in podobnih metod, vendar je vedno šlo le za zadevo globalnega stanja, ki je shranjeno v neki statični spremenljivki. Metoda `DB::insert()` zahteva obstoj statične spremenljivke, ker je v njej shranjena povezava z bazo podatkov. Brez te spremenljivke bi bilo nemogoče metodo implementirati. -Uporaba determinističnih statičnih metod in funkcij, kot so `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` in številne druge, je popolnoma skladna z vbrizgavanjem odvisnosti. Te funkcije iz istih vhodnih parametrov vedno vrnejo enake rezultate in so zato predvidljive. Ne uporabljajo nobenega globalnega stanja. +Uporaba determinističnih statičnih metod in funkcij, kot na primer `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` in mnogih drugih, je v popolnem skladu z dependency injection. Te funkcije vedno vračajo enake rezultate iz enakih vhodnih parametrov in so torej predvidljive. Ne uporabljajo nobenega globalnega stanja. -Vendar v PHP obstajajo funkcije, ki niso deterministične. Med njimi je na primer funkcija `htmlspecialchars()`. Njen tretji parameter, `$encoding`, če ni določen, je privzeta vrednost konfiguracijske možnosti `ini_get('default_charset')`. Zato je priporočljivo, da ta parameter vedno navedete, da se izognete morebitnemu nepredvidljivemu obnašanju funkcije. Nette to dosledno počne. +Obstajajo pa tudi funkcije v PHP, ki niso deterministične. K njim spada na primer funkcija `htmlspecialchars()`. Njen tretji parameter `$encoding`, če ni naveden, ima kot privzeto vrednost vrednost konfiguracijske možnosti `ini_get('default_charset')`. Zato se priporoča ta parameter vedno navesti in preprečiti morebitno nepredvidljivo obnašanje funkcije. Nette to dosledno počne. -Nekatere funkcije, kot so `strtolower()`, `strtoupper()` in podobne, so imele v bližnji preteklosti nedeterministično obnašanje in so bile odvisne od nastavitve `setlocale()`. To je povzročilo številne zaplete, najpogosteje pri delu s turškim jezikom. -Turški jezik namreč razlikuje med velikimi in malimi črkami `I` s piko in brez nje. Tako je `strtolower('I')` vrnil znak `ı`, `strtoupper('i')` pa znak `İ`, zaradi česar so aplikacije povzročale številne skrivnostne napake. -Vendar je bila ta težava odpravljena v različici PHP 8.2 in funkcije niso več odvisne od lokalnega jezika. +Nekatere funkcije, kot na primer `strtolower()`, `strtoupper()` in podobne, so se v nedavni preteklosti nedeterministično obnašale in bile odvisne od nastavitve `setlocale()`. To je povzročalo veliko zapletov, najpogosteje pri delu s turškim jezikom. Ta namreč razlikuje malo in veliko črko `I` s piko in brez pike. Tako je `strtolower('I')` vračalo znak `ı` in `strtoupper('i')` znak `İ`, kar je vodilo k temu, da so aplikacije začele povzročati vrsto skrivnostnih napak. Ta težava pa je bila odpravljena v PHP različici 8.2 in funkcije niso več odvisne od locale. -To je lep primer, kako je globalno stanje prizadelo na tisoče razvijalcev po vsem svetu. Rešitev je bila zamenjava z vbrizgavanjem odvisnosti. +Gre za lep primer, kako je globalno stanje mučilo na tisoče razvijalcev po vsem svetu. Rešitev je bila zamenjava z dependency injection. -Kdaj je mogoče uporabiti globalno stanje? .[#toc-when-is-it-possible-to-use-global-state] ------------------------------------------------------------------------------------------ +Kdaj je mogoče uporabiti globalno stanje? +----------------------------------------- -V nekaterih posebnih primerih je mogoče uporabiti globalno stanje. Na primer pri razhroščevanju kode, ko morate izpisati vrednost spremenljivke ali izmeriti trajanje določenega dela programa. V takih primerih, ki zadevajo začasna dejanja, ki bodo pozneje odstranjena iz kode, je upravičena uporaba globalno razpoložljivega odlagalnika ali štoparice. Ta orodja niso del zasnove kode. +Obstajajo določene specifične situacije, ko je mogoče izkoristiti globalno stanje. Na primer pri razhroščevanju kode, ko morate izpisati vrednost spremenljivke ali izmeriti trajanje določenega dela programa. V takih primerih, ki se nanašajo na začasna dejanja, ki bodo kasneje odstranjena iz kode, je mogoče legitimno izkoristiti globalno dostopen dumper ali štoparico. Ti orodji namreč niso del načrtovanja kode. -Drug primer so funkcije za delo z regularnimi izrazi `preg_*`, ki interno shranjujejo sestavljene regularne izraze v statični predpomnilnik v pomnilniku. Kadar isti regularni izraz večkrat pokličete v različnih delih kode, se sestavi samo enkrat. Predpomnilnik prihrani zmogljivost, poleg tega pa je za uporabnika popolnoma neviden, zato lahko takšno uporabo štejemo za zakonito. +Drug primer so funkcije za delo z regularnimi izrazi `preg_*`, ki interno shranjujejo prevedene regularne izraze v statični predpomnilnik v pomnilniku. Ko torej kličete isti regularni izraz večkrat na različnih mestih kode, se prevede samo enkrat. Predpomnilnik varčuje z zmogljivostjo in hkrati je za uporabnika popolnoma neviden, zato lahko tako uporabo štejemo za legitimno. -Povzetek .[#toc-summary] ------------------------- +Povzetek +-------- -Pokazali smo, zakaj je smiselno +Pregledali smo, zakaj ima smisel: -1) Odstranite vse statične spremenljivke iz kode -2) Deklarirajte odvisnosti -3) In uporabite vbrizgavanje odvisnosti +1) Odstraniti vse statične spremenljivke iz kode +2) Deklarirati odvisnosti +3) In uporabljati dependency injection -Ko razmišljate o oblikovanju kode, imejte v mislih, da vsaka stran `static $foo` predstavlja težavo. Če želite, da bo vaša koda okolje, ki spoštuje DI, nujno popolnoma izkoreniniti globalno stanje in ga nadomestiti z vbrizgavanjem odvisnosti. +Ko razmišljate o načrtovanju kode, mislite na to, da vsak `static $foo` predstavlja težavo. Da bi vaša koda bila okolje, ki spoštuje DI, je nujno popolnoma izkoreniniti globalno stanje in ga nadomestiti z dependency injection. -Med tem postopkom boste morda ugotovili, da morate razred razdeliti, ker ima več kot eno odgovornost. Ne skrbite zaradi tega; prizadevajte si za načelo ene odgovornosti. +Med tem procesom morda ugotovite, da je treba razred razdeliti, ker ima več kot eno odgovornost. Ne bojte se tega; prizadevajte si za načelo ene same odgovornosti. -*Zahvaljujem se Mišku Heveryju, čigar članki, kot je [Flaw: Brittle Global State & Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/], so podlaga za to poglavje.* +*Rad bi se zahvalil Mišku Heveryju, čigar članki, kot je [Flaw: Brittle Global State & Singletons |https://web.archive.org/web/20230321084133/http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/], so osnova tega poglavja.* diff --git a/dependency-injection/sl/introduction.texy b/dependency-injection/sl/introduction.texy index 89e58a9aec..2a3c0bdcc6 100644 --- a/dependency-injection/sl/introduction.texy +++ b/dependency-injection/sl/introduction.texy @@ -1,58 +1,58 @@ -Kaj je vrivanje odvisnosti? -*************************** +Kaj je Vbrizgavanje odvisnosti? +******************************* .[perex] -V tem poglavju boste spoznali osnovne programerske prakse, ki jih morate upoštevati pri pisanju katere koli aplikacije. To so osnove, ki so potrebne za pisanje čiste, razumljive in vzdrževane kode. +To poglavje vas bo seznanilo z osnovnimi programerskimi postopki, ki jih morate upoštevati pri pisanju vseh aplikacij. Gre za osnove, potrebne za pisanje čiste, razumljive in vzdržljive kode. -Če se naučite teh pravil in jih upoštevate, vam bo Nette pomagal na vsakem koraku. Za vas bo opravljala rutinska opravila in zagotavljala največje udobje, tako da se boste lahko osredotočili na samo logiko. +Če boste ta pravila sprejeli in jih upoštevali, vam bo Nette v vsakem koraku pomagal. Za vas bo reševal rutinske naloge in vam zagotovil maksimalno udobje, da se boste lahko osredotočili na samo logiko. -Načela, ki jih bomo prikazali tukaj, so precej preprosta. Ni vam treba skrbeti za ničesar. +Principi, ki jih bomo tukaj predstavili, so precej preprosti. Ničesar se vam ni treba bati. -Se spomnite svojega prvega programa? .[#toc-remember-your-first-program] ------------------------------------------------------------------------- +Se spomnite svojega prvega programa? +------------------------------------ -Ne vemo, v katerem jeziku ste ga napisali, vendar če je bil PHP, bi bil lahko videti nekako takole: +Ne vemo sicer, v katerem jeziku ste ga napisali, a če bi bil to PHP, bi verjetno izgledal nekako takole: ```php -function addition(float $a, float $b): float +function soucet(float $a, float $b): float { return $a + $b; } -echo addition(23, 1); // odtisi 24 +echo soucet(23, 1); // izpiše 24 ``` -Nekaj trivialnih vrstic kode, v katerih pa se skriva toliko ključnih konceptov. Da obstajajo spremenljivke. Da je koda razdeljena na manjše enote, ki so na primer funkcije. Da jim posredujemo vhodne argumente in da nam vrnejo rezultate. Manjkajo le še pogoji in zanke. +Nekaj trivialnih vrstic kode, a v njih se skriva toliko ključnih konceptov. Da obstajajo spremenljivke. Da se koda deli na manjše enote, kot so na primer funkcije. Da jim predajamo vhodne argumente in one vračajo rezultate. Manjkajo le še pogoji in zanke. -Dejstvo, da funkcija sprejme vhodne podatke in vrne rezultat, je povsem razumljiv koncept, ki se uporablja tudi na drugih področjih, na primer v matematiki. +To, da funkciji predamo vhodne podatke in ona vrne rezultat, je popolnoma razumljiv koncept, ki se uporablja tudi na drugih področjih, kot na primer v matematiki. -Funkcija ima svoj podpis, ki je sestavljen iz njenega imena, seznama parametrov in njihovih tipov ter tipa vrnjene vrednosti. Kot uporabnike nas zanima signatura in nam običajno ni treba vedeti ničesar o notranji implementaciji. +Funkcija ima svojo signaturo, ki jo sestavljajo njeno ime, seznam parametrov in njihovih tipov ter na koncu tip vrnjene vrednosti. Kot uporabnike nas zanima signatura, o notranji implementaciji običajno ne potrebujemo vedeti ničesar. -Predstavljajte si, da bi bil podpis funkcije videti takole: +Zdaj si predstavljajte, da bi signatura funkcije izgledala takole: ```php -function addition(float $x): float +function soucet(float $x): float ``` -Dodatek z enim parametrom? To je čudno... Kaj pa to? +Seštevanje z enim parametrom? To je čudno… Kaj pa takole? ```php -function addition(): float +function soucet(): float ``` -To je res čudno, kajne? Kako se funkcija uporablja? +To pa je že res zelo čudno, kajne? Kako se funkcija sploh uporablja? ```php -echo addition(); // kaj natisne? +echo soucet(); // kaj naj bi izpisalo? ``` -Ob pogledu na takšno kodo bi bili zmedeni. Ne le, da je ne bi razumel začetnik, tudi izkušen programer ne bi razumel takšne kode. +Ob pogledu na takšno kodo bi bili zmedeni. Ne samo, da je ne bi razumel začetnik, takšne kode ne razume niti izkušen programer. -Se sprašujete, kako bi bila takšna funkcija dejansko videti v notranjosti? Kje bi dobila vsote? Verjetno bi jih *nekako* dobila sama, morda takole: +Razmišljate, kako bi takšna funkcija sploh izgledala znotraj? Kje bi vzela seštevance? Očitno bi si jih *na nek način* priskrbela sama, na primer takole: ```php -function addition(): float +function soucet(): float { $a = Input::get('a'); $b = Input::get('b'); @@ -60,71 +60,71 @@ function addition(): float } ``` -Izkazalo se je, da so v telesu funkcije skrite povezave z drugimi funkcijami (ali statičnimi metodami), in da bi ugotovili, od kod dejansko prihajajo seštevalniki, moramo kopati naprej. +V telesu funkcije smo odkrili skrite povezave na druge globalne funkcije ali statične metode. Da bi ugotovili, od kod se seštevanci dejansko vzamejo, moramo raziskovati naprej. -Ne na ta način! .[#toc-not-this-way] ------------------------------------- +Tako ne! +-------- -Zasnova, ki smo jo pravkar prikazali, je bistvo številnih negativnih lastnosti: +Načrt, ki smo ga pravkar predstavili, je bistvo mnogih negativnih lastnosti: -- podpis funkcije se je pretvarjal, da ne potrebuje seštevkov, kar nas je zmotilo -- nimamo pojma, kako bi funkcijo pripravili do tega, da bi računala z dvema drugima številoma -- morali smo pogledati kodo, da smo ugotovili, od kod prihajajo seštevki -- našli smo skrite odvisnosti -- za popolno razumevanje je treba preučiti tudi te odvisnosti +- signatura funkcije se je pretvarjala, da ne potrebuje seštevancev, kar nas je zmedlo +- sploh ne vemo, kako funkcijo pripraviti do tega, da sešteje drugi dve števili +- morali smo pogledati v kodo, da bi ugotovili, kje vzame seštevance +- odkrili smo skrite povezave +- za popolno razumevanje je treba preučiti tudi te povezave -In ali je sploh naloga funkcije seštevanja, da pridobiva vhodne podatke? Seveda ni. Njena naloga je le dodajanje. +In ali je sploh naloga seštevalne funkcije, da si priskrbi vhode? Seveda ni. Njena odgovornost je le samo seštevanje. -S takšno kodo se ne želimo srečati in je zagotovo ne želimo pisati. Rešitev je preprosta: vrnite se k osnovam in uporabite samo parametre: +S takšno kodo se nočemo srečati in je zagotovo nočemo pisati. Popravek je pri tem preprost: vrniti se k osnovam in preprosto uporabiti parametre: ```php -function addition(float $a, float $b): float +function soucet(float $a, float $b): float { return $a + $b; } ``` -Pravilo št. 1: Naj vam ga prenesejo .[#toc-rule-1-let-it-be-passed-to-you] --------------------------------------------------------------------------- +Pravilo št. 1: naj ti bo predano +-------------------------------- -Najpomembnejše pravilo je: **Vse podatke, ki jih potrebujejo funkcije ali razredi, jim je treba posredovati**. +Najpomembnejše pravilo se glasi: **vsi podatki, ki jih funkcije ali razredi potrebujejo, jim morajo biti predani**. -Namesto da bi izumljali skrite načine, kako sami dostopajo do podatkov, jim preprosto posredujte parametre. Prihranili boste čas, ki bi ga porabili za izumljanje skritih poti, ki zagotovo ne bodo izboljšale vaše kode. +Namesto da bi si izmišljali skrite načine, s katerimi bi lahko sami prišli do njih, preprosto predajte parametre. Prihranili boste čas, potreben za izmišljanje skritih poti, ki zagotovo ne bodo izboljšale vaše kode. -Če boste vedno in povsod upoštevali to pravilo, ste na poti do kode brez skritih odvisnosti. Do kode, ki je razumljiva ne le avtorju, temveč tudi vsakomur, ki jo prebere pozneje. Kjer je vse razumljivo iz podpisov funkcij in razredov in ni treba iskati skritih skrivnosti v implementaciji. +Če boste to pravilo vedno in povsod upoštevali, ste na poti h kodi brez skritih povezav. H kodi, ki je razumljiva ne samo avtorju, ampak tudi vsakomur, ki jo bo bral za njim. Kjer je vse razumljivo iz signatur funkcij in razredov in ni treba iskati skritih skrivnosti v implementaciji. -Ta tehnika se strokovno imenuje **vbrizgavanje odvisnosti**. Ti podatki pa se imenujejo **odvisnosti**. To je le običajno posredovanje parametrov, nič več. +Tej tehniki se strokovno reče **dependency injection** (vbrizgavanje odvisnosti). In tem podatkom se reče **odvisnosti.** Pri tem gre za povsem običajno predajanje parametrov, nič več. .[note] -Ne zamenjujte vbrizgavanja odvisnosti, ki je načrtovalski vzorec, z "vsebnikom za vbrizgavanje odvisnosti", ki je orodje, nekaj diametralno različnega. S kontejnerji se bomo ukvarjali pozneje. +Prosimo, ne zamenjujte dependency injection, ki je načrtovalski vzorec, z „dependency injection container“, ki je orodje, torej nekaj diametralno drugačnega. Z vsebniki se bomo ukvarjali kasneje. -Od funkcij do razredov .[#toc-from-functions-to-classes] --------------------------------------------------------- +Od funkcij k razredom +--------------------- -In kako so razredi povezani? Razred je bolj zapletena enota kot preprosta funkcija, vendar tudi tu v celoti velja pravilo št. 1. Obstaja le [več načinov za posredovanje argumentov |passing-dependencies]. Na primer, zelo podobno kot pri funkciji: +In kako so s tem povezani razredi? Razred je kompleksnejša celota kot preprosta funkcija, vendar pravilo št. 1 velja brez izjeme tudi tukaj. Obstaja le [več možnosti, kako predati argumente|passing-dependencies]. Na primer precej podobno kot pri funkciji: ```php -class Math +class Matematika { - public function addition(float $a, float $b): float + public function soucet(float $a, float $b): float { return $a + $b; } } -$math = new Math; -echo $math->addition(23, 1); // 24 +$math = new Matematika; +echo $math->soucet(23, 1); // 24 ``` -Ali prek drugih metod ali neposredno prek konstruktorja: +Ali z drugimi metodami ali neposredno s konstruktorjem: ```php -class Addition +class Soucet { public function __construct( private float $a, @@ -132,26 +132,26 @@ class Addition ) { } - public function calculate(): float + public function spocti(): float { return $this->a + $this->b; } } -$addition = new Addition(23, 1); -echo $addition->calculate(); // 24 +$soucet = new Soucet(23, 1); +echo $soucet->spocti(); // 24 ``` -Oba primera sta v celoti skladna z vbrizgavanjem odvisnosti. +Oba primera sta popolnoma v skladu z dependency injection. -Primeri iz resničnega življenja .[#toc-real-life-examples] ----------------------------------------------------------- +Realni primeri +-------------- -V resničnem svetu ne boste pisali razredov za seštevanje številk. Preidimo na praktične primere. +V resničnem svetu ne boste pisali razredov za seštevanje števil. Premaknimo se k primerom iz prakse. -Imejmo razred `Article`, ki predstavlja objavo na blogu: +Imejmo razred `Article`, ki predstavlja članek na blogu: ```php class Article @@ -162,7 +162,7 @@ class Article public function save(): void { - // shranite članek v zbirko podatkov. + // shranimo članek v podatkovno bazo } } ``` @@ -176,9 +176,9 @@ $article->content = 'Every year millions of people in ...'; $article->save(); ``` -Metoda `save()` bo članek shranila v tabelo podatkovne zbirke. Implementacija z uporabo [podatkovne baze Nette |database:] bo prava mala malica, če ne bi bilo ene zadrege: kje `Article` dobi povezavo s podatkovno bazo, tj. objekt razreda `Nette\Database\Connection`? +Metoda `save()` shrani članek v podatkovno tabelo. Implementirati jo s pomočjo [Nette Database |database:] bi bilo enostavno, če ne bi bilo ene ovire: kje naj `Article` vzame povezavo s podatkovno bazo, tj. objekt razreda `Nette\Database\Connection`? -Zdi se, da imamo veliko možnosti. Lahko jo vzame nekje iz statične spremenljivke. Ali pa podeduje od razreda, ki zagotavlja povezavo s podatkovno bazo. Ali pa izkoristi prednosti [enojnega razreda (singleton) |global-state#Singleton]. Ali pa uporabimo tako imenovane fasade, ki se uporabljajo v Laravelu: +Zdi se, da imamo veliko možnosti. Lahko jo vzame od nekod iz statične spremenljivke. Ali podeduje od razreda, ki zagotovi povezavo s podatkovno bazo. Ali uporabi t.i. [singleton |global-state#Singleton]. Ali t.i. facades, ki se uporabljajo v Laravelu: ```php use Illuminate\Support\Facades\DB; @@ -201,15 +201,15 @@ class Article Odlično, problem smo rešili. -Ali pa smo? +Ali ne? -Spomnimo se na [pravilo št. 1: Naj vam bo posredovano |#rule #1: Let It Be Passed to You]: vse odvisnosti, ki jih razred potrebuje, mu morajo biti posredovane. Če namreč prekršimo to pravilo, smo se podali na pot umazane kode, polne skritih odvisnosti, nerazumljivosti, rezultat pa bo aplikacija, ki jo bo boleče vzdrževati in razvijati. +Spomnimo se [##pravilo št. 1: naj ti bo predano]: vse odvisnosti, ki jih razred potrebuje, mu morajo biti predane. Ker če pravilo kršimo, smo stopili na pot k umazani kodi, polni skritih povezav, nerazumljivosti, in rezultat bo aplikacija, ki jo bo boleče vzdrževati in razvijati. -Uporabnik razreda `Article` nima pojma, kam metoda `save()` shrani članek. V tabeli podatkovne zbirke? V kateri, produkcijski ali testni? In kako jo lahko spremeni? +Uporabnik razreda `Article` ne ve, kam metoda `save()` članek shranjuje. V podatkovno tabelo? V katero, produkcijsko ali testno? In kako je to mogoče spremeniti? -Uporabnik mora pogledati, kako je implementirana metoda `save()`, in najde uporabo metode `DB::insert()`. Torej mora iskati naprej, da bi ugotovil, kako ta metoda pridobi povezavo s podatkovno bazo. In skrite odvisnosti lahko tvorijo precej dolgo verigo. +Uporabnik mora pogledati, kako je implementirana metoda `save()`, in najde uporabo metode `DB::insert()`. Torej mora raziskovati naprej, kako si ta metoda priskrbi podatkovno povezavo. In skrite povezave lahko tvorijo precej dolgo verigo. -V čisti in dobro zasnovani kodi nikoli ni skritih odvisnosti, Laravelovih fasad ali statičnih spremenljivk. V čisti in dobro zasnovani kodi se posredujejo argumenti: +V čisti in dobro zasnovani kodi se nikoli ne pojavljajo skrite povezave, Laravelove facades ali statične spremenljivke. V čisti in dobro zasnovani kodi se predajajo argumenti: ```php class Article @@ -224,7 +224,7 @@ class Article } ``` -Še bolj praktičen pristop, kot bomo videli pozneje, je uporaba konstruktorja: +Še bolj praktično, kot bomo videli kasneje, bo to s konstruktorjem: ```php class Article @@ -245,9 +245,9 @@ class Article ``` .[note] -Če ste izkušen programer, boste morda pomislili, da `Article` sploh ne bi smel imeti metode `save()`; predstavljal bi izključno podatkovno komponento, za shranjevanje pa bi moral skrbeti ločen repozitorij. To je smiselno. Toda to bi daleč preseglo obseg teme, ki je vbrizgavanje odvisnosti, in prizadevanje, da bi navedli preproste primere. +Če ste izkušen programer, morda mislite, da `Article` sploh ne bi smel imeti metode `save()`, moral bi predstavljati zgolj podatkovno komponento in za shranjevanje bi moral skrbeti ločen repozitorij. To ima smisel. Toda s tem bi se oddaljili daleč preko okvira teme, ki je dependency injection, in prizadevanja za navajanje preprostih primerov. -Če napišete razred, ki za svoje delovanje potrebuje na primer podatkovno zbirko, si ne izmišljujte, od kod jo dobiti, temveč naj bo posredovana. Bodisi kot parameter konstruktorja ali druge metode. Priznajte odvisnosti. Priznajte jih v API svojega razreda. Dobili boste razumljivo in predvidljivo kodo. +Če boste pisali razred, ki za svoje delovanje potrebuje npr. podatkovno bazo, ne izmišljajte si, od kod jo dobiti, ampak naj vam jo predajo. Na primer kot parameter konstruktorja ali druge metode. Priznajte odvisnosti. Priznajte jih v API-ju vašega razreda. Dobili boste razumljivo in predvidljivo kodo. Kaj pa ta razred, ki beleži sporočila o napakah: @@ -262,21 +262,21 @@ class Logger } ``` -Kaj menite, ali smo upoštevali [pravilo št. 1: Naj vam ga prenesejo |#rule #1: Let It Be Passed to You]? +Kaj mislite, smo upoštevali [##pravilo št. 1: naj ti bo predano]? Nismo. -Ključne informacije, tj. imenik z datoteko dnevnika, razred pridobi iz konstante. +Ključno informacijo, torej imenik z datoteko z logom, si razred *priskrbi sam* iz konstante. -Oglejte si primer uporabe: +Poglejte primer uporabe: ```php $logger = new Logger; -$logger->log('The temperature is 23 °C'); -$logger->log('The temperature is 10 °C'); +$logger->log('Temperatura je 23 °C'); +$logger->log('Temperatura je 10 °C'); ``` -Ali lahko brez poznavanja izvajanja odgovorite na vprašanje, kje so zapisana sporočila? Ali bi uganili, da je obstoj konstante `LOG_DIR` potreben za njeno delovanje? In ali bi lahko ustvarili drugi primerek, ki bi pisal na drugo lokacijo? Zagotovo ne. +Brez poznavanja implementacije, bi lahko odgovorili na vprašanje, kam se sporočila zapisujejo? Bi pomislili, da je za delovanje potrebna obstoj konstante `LOG_DIR`? In bi lahko ustvarili drugo instanco, ki bo zapisovala drugam? Zagotovo ne. Popravimo razred: @@ -295,24 +295,24 @@ class Logger } ``` -Razred je zdaj veliko bolj razumljiv, nastavljiv in s tem uporaben. +Razred je zdaj veliko bolj razumljiv, nastavljiv in torej uporabnejši. ```php -$logger = new Logger('/path/to/log.txt'); -$logger->log('The temperature is 15 °C'); +$logger = new Logger('/pot/do/loga.txt'); +$logger->log('Temperatura je 15 °C'); ``` -Ampak meni je vseeno! .[#toc-but-i-don-t-care] ----------------------------------------------- +Ampak to me ne zanima! +---------------------- -*"Ko ustvarim objekt Article in kličem save(), se ne želim ukvarjati s podatkovno bazo; želim le, da se shrani v tisto, ki sem jo določil v konfiguraciji."* +*„Ko ustvarim objekt Article in pokličem save(), potem nočem reševati podatkovne baze, preprosto želim, da se shrani v tisto, ki jo imam nastavljeno v konfiguraciji.“* -*"Ko uporabljam Logger, želim samo, da se sporočilo zapiše, in se ne želim ukvarjati s tem, kam. Naj se uporabijo globalne nastavitve."* +*„Ko uporabim Logger, preprosto želim, da se sporočilo zapiše, in nočem reševati kam. Naj se uporabi globalna nastavitev.“* -To so veljavne pripombe. +To so pravilne pripombe. -Kot primer si oglejmo razred, ki pošilja glasila in beleži, kako je potekalo pošiljanje: +Kot primer si bomo pokazali razred, ki pošilja novice (newsletterje) in zabeleži, kako se je izšlo: ```php class NewsletterDistributor @@ -322,27 +322,27 @@ class NewsletterDistributor $logger = new Logger(/* ... */); try { $this->sendEmails(); - $logger->log('Emails have been sent out'); + $logger->log('E-pošta je bila poslana'); } catch (Exception $e) { - $logger->log('An error occurred during the sending'); + $logger->log('Prišlo je do napake pri pošiljanju'); throw $e; } } } ``` -Izboljšani `Logger`, ki ne uporablja več konstante `LOG_DIR`, zahteva navedbo poti do datoteke v konstruktorju. Kako to rešiti? Razredu `NewsletterDistributor` je vseeno, kje so sporočila zapisana; želi jih le zapisati. +Izboljšan `Logger`, ki ne uporablja več konstante `LOG_DIR`, zahteva v konstruktorju navedbo poti do datoteke. Kako to rešiti? Razreda `NewsletterDistributor` sploh ne zanima, kam se sporočila zapisujejo, želi jih le zapisati. -Rešitev je spet [pravilo št. 1: Naj vam ga prenesejo |#rule #1: Let It Be Passed to You]: Predajajte vse podatke, ki jih razred potrebuje. +Rešitev je spet [##pravilo št. 1: naj ti bo predano]: vse podatke, ki jih razred potrebuje, mu predamo. -Ali to torej pomeni, da skozi konstruktor posredujemo pot do dnevnika, ki jo nato uporabimo pri ustvarjanju predmeta `Logger`? +Torej to pomeni, da si preko konstruktorja predamo pot do loga, ki jo nato uporabimo pri ustvarjanju objekta `Logger`? ```php class NewsletterDistributor { public function __construct( - private string $file, // ⛔ NOT THIS WAY! + private string $file, // ⛔ TAKO NE! ) { } @@ -351,7 +351,7 @@ class NewsletterDistributor $logger = new Logger($this->file); ``` -Ne, ne tako! Pot ne sodi med podatke, ki jih potrebuje razred `NewsletterDistributor`; pravzaprav jih potrebuje razred `Logger`. Ali vidite razliko? Razred `NewsletterDistributor` potrebuje sam dnevnik. Zato bomo posredovali le tega: +Tako ne! Pot namreč **ne spada** med podatke, ki jih razred `NewsletterDistributor` potrebuje; te namreč potrebuje `Logger`. Zaznavate razliko? Razred `NewsletterDistributor` potrebuje logger kot takega. Torej si tega predamo: ```php class NewsletterDistributor @@ -365,35 +365,33 @@ class NewsletterDistributor { try { $this->sendEmails(); - $this->logger->log('Emails have been sent out'); + $this->logger->log('E-pošta je bila poslana'); } catch (Exception $e) { - $this->logger->log('An error occurred during the sending'); + $this->logger->log('Prišlo je do napake pri pošiljanju'); throw $e; } } } ``` -Zdaj je iz podpisov razreda `NewsletterDistributor` razvidno, da je del njegove funkcionalnosti tudi beleženje. In naloga zamenjave loggerja z drugim, morda za testiranje, je povsem trivialna. -Poleg tega, če se konstruktor razreda `Logger` spremeni, to ne bo vplivalo na naš razred. +Zdaj je iz signatur razreda `NewsletterDistributor` jasno, da je del njegove funkcionalnosti tudi logiranje. In naloga zamenjati logger za drugega, na primer zaradi testiranja, je popolnoma trivialna. Poleg tega, če bi se konstruktor razreda `Logger` spremenil, to ne bo imelo nobenega vpliva na naš razred. -Pravilo 2: Vzemi, kar je tvoje .[#toc-rule-2-take-what-s-yours] ---------------------------------------------------------------- +Pravilo št. 2: vzemi, kar je tvoje +---------------------------------- -Ne pustite se zavajati in ne dovolite, da bi prešli v odvisnost od vaših odvisnikov. Prepustite le svoje lastne odvisnosti. +Ne pustite se zmesti in ne pustite si predajati odvisnosti svojih odvisnosti. Pustite si predajati le svoje odvisnosti. -Zaradi tega bo koda, ki uporablja druge predmete, popolnoma neodvisna od sprememb v njihovih konstruktorjih. Njen API bo bolj resničen. Predvsem pa bo te odvisnosti trivialno zamenjati z drugimi. +Zahvaljujoč temu bo koda, ki uporablja druge objekte, popolnoma neodvisna od sprememb njihovih konstruktorjev. Njen API bo bolj resničen. In predvsem bo trivialno te odvisnosti zamenjati za druge. -Novi član družine .[#toc-new-family-member] -------------------------------------------- +Nov član družine +---------------- -Razvojna skupina se je odločila, da bo ustvarila drugi logger, ki bo pisal v podatkovno zbirko. Zato ustvarimo razred `DatabaseLogger`. Torej imamo dva razreda, `Logger` in `DatabaseLogger`, eden piše v datoteko, drugi v podatkovno zbirko ... se vam poimenovanje ne zdi čudno? -Ali ne bi bilo bolje preimenovati `Logger` v `FileLogger`? Vsekakor da. +V razvojni ekipi je padla odločitev ustvariti drugi logger, ki zapisuje v podatkovno bazo. Ustvarili bomo torej razred `DatabaseLogger`. Imamo torej dva razreda, `Logger` in `DatabaseLogger`, eden zapisuje v datoteko, drugi v podatkovno bazo … se vam pri tem poimenovanju ne zdi nekaj čudnega? Ali ne bi bilo bolje preimenovati `Logger` v `FileLogger`? Zagotovo da. -Toda naredimo to pametno. Ustvarimo vmesnik pod prvotnim imenom: +Ampak naredili bomo pametno. Pod prvotnim imenom bomo ustvarili vmesnik: ```php interface Logger @@ -402,7 +400,7 @@ interface Logger } ``` -... ki ga bosta izvajala oba zapisovalnika: +… ki ga bosta oba loggerja implementirala: ```php class FileLogger implements Logger @@ -412,17 +410,17 @@ class DatabaseLogger implements Logger // ... ``` -Zaradi tega v preostalem delu kode, kjer se dnevnik uporablja, ne bo treba ničesar spreminjati. Na primer, konstruktor razreda `NewsletterDistributor` se bo še vedno zadovoljil s tem, da bo kot parameter zahteval `Logger`. Od nas pa bo odvisno, kateri primerek bomo posredovali. +In zahvaljujoč temu ne bo treba ničesar spreminjati v preostalem delu kode, kjer se logger uporablja. Na primer konstruktor razreda `NewsletterDistributor` bo še vedno zadovoljen s tem, da kot parameter zahteva `Logger`. In samo od nas bo odvisno, katero instanco mu bomo predali. -**Zato imenom vmesnikov nikoli ne dodajamo končnice `Interface` ali predpone `I`.** V nasprotnem primeru kode ne bi bilo mogoče tako lepo razviti. +**Zato nikoli ne dajemo imenom vmesnikov pripone `Interface` ali predpone `I`.** Sicer ne bi bilo mogoče kode tako lepo razvijati. -Houston, imamo težavo .[#toc-houston-we-have-a-problem] -------------------------------------------------------- +Houston, imamo problem +---------------------- -Medtem ko lahko v celotni aplikaciji uporabimo en sam primerek loggerja, ki temelji na datoteki ali podatkovni zbirki, in ga preprosto posredujemo povsod, kjer se kaj beleži, je pri razredu `Article` precej drugače. Njegove instance ustvarjamo po potrebi, tudi večkrat. Kako ravnati z odvisnostjo od podatkovne zbirke v njegovem konstruktorju? +Medtem ko si lahko v celotni aplikaciji zadostujemo z eno samo instanco loggerja, bodisi datotečnega ali podatkovnega, in ga preprosto predajamo povsod tam, kjer se nekaj logira, je povsem drugače v primeru razreda `Article`. Njegove instance namreč ustvarjamo po potrebi, lahko tudi večkrat. Kako se spopasti s povezavo na podatkovno bazo v njegovem konstruktorju? -Primer je lahko krmilnik, ki mora po oddaji obrazca shraniti članek v zbirko podatkov: +Kot primer lahko služi kontroler, ki mora po oddaji obrazca shraniti članek v podatkovno bazo: ```php class EditController extends Controller @@ -437,30 +435,30 @@ class EditController extends Controller } ``` -Možna rešitev je očitna: konstruktorju `EditController` posredujemo objekt podatkovne zbirke in uporabimo `$article = new Article($this->db)`. +Možna rešitev se ponuja kar sama: pustimo si objekt podatkovne baze predati s konstruktorjem v `EditController` in uporabimo `$article = new Article($this->db)`. -Tako kot v prejšnjem primeru s `Logger` in potjo do datoteke to ni pravi pristop. Podatkovna baza ni odvisna od `EditController`, temveč od `Article`. Posredovanje podatkovne baze je v nasprotju s [pravilom 2: vzemi, kar je tvoje |#rule #2: take what's yours]. Če se konstruktor razreda `Article` spremeni (doda se nov parameter), boste morali spremeniti kodo povsod, kjer se ustvarjajo primerki. Ufff. +Enako kot v prejšnjem primeru z `Logger` in potjo do datoteke, to ni pravilen postopek. Podatkovna baza ni odvisnost `EditController`, ampak `Article`. Predajanje podatkovne baze torej gre proti [pravilu št. 2: vzemi, kar je tvoje |#Pravilo št. 2: vzemi kar je tvoje]. Ko se spremeni konstruktor razreda `Article` (doda se nov parameter), bo treba prilagoditi tudi kodo na vseh mestih, kjer se ustvarjajo instance. Ufff. -Houston, kaj predlagate? +Houston, kaj predlagaš? -Pravilo št. 3: Pustite, da se s tem ukvarja tovarna .[#toc-rule-3-let-the-factory-handle-it] --------------------------------------------------------------------------------------------- +Pravilo št. 3: prepusti tovarni +------------------------------- -Z odpravo skritih odvisnosti in posredovanjem vseh odvisnosti kot argumentov smo pridobili bolj nastavljive in prilagodljive razrede. Zato potrebujemo nekaj drugega, kar bo ustvarilo in konfiguriralo te bolj prilagodljive razrede za nas. Imenovali ga bomo tovarne. +S tem, ko smo odpravili skrite povezave in vse odvisnosti predajamo kot argumente, smo dobili bolj nastavljive in prožne razrede. In zato potrebujemo še nekaj drugega, kar nam bo te prožnejše razrede ustvarilo in konfiguriralo. Temu bomo rekli tovarne. -Velja pravilo: če ima razred odvisnosti, ustvarjanje njihovih primerkov prepustite tovarni. +Pravilo se glasi: če ima razred odvisnosti, prepusti ustvarjanje njihovih instanc tovarni. -Tovarne so pametnejša zamenjava za operater `new` v svetu vbrizgavanja odvisnosti. +Tovarne so pametnejša zamenjava za operator `new` v svetu dependency injection. .[note] -Ne zamenjujte z oblikovnim vzorcem *factory method*, ki opisuje poseben način uporabe tovarn in ni povezan s to temo. +Prosimo, ne zamenjujte z načrtovalskim vzorcem *factory method*, ki opisuje specifičen način uporabe tovarn in s to temo ni povezan. -Tovarna .[#toc-factory] ------------------------ +Tovarna +------- -Tovarna je metoda ali razred, ki ustvarja in konfigurira predmete. Razred, ki proizvaja `Article`, bomo poimenovali `ArticleFactory`, izgledal pa bi lahko takole: +Tovarna je metoda ali razred, ki izdeluje in konfigurira objekte. Razred, ki izdeluje `Article`, bomo poimenovali `ArticleFactory` in bi lahko izgledal na primer takole: ```php class ArticleFactory @@ -477,7 +475,7 @@ class ArticleFactory } ``` -Njegova uporaba v krmilniku je naslednja: +Njegova uporaba v kontrolerju bo naslednja: ```php class EditController extends Controller @@ -489,7 +487,7 @@ class EditController extends Controller public function formSubmitted($data) { - // naj tovarna ustvari predmet + // pustimo tovarni ustvariti objekt $article = $this->articleFactory->create(); $article->title = $data->title; $article->content = $data->content; @@ -498,11 +496,11 @@ class EditController extends Controller } ``` -Če se spremeni podpis konstruktorja razreda `Article`, je na tej točki edini del kode, ki se mora odzvati, sam konstruktor `ArticleFactory`. Na vso drugo kodo, ki dela z objekti `Article`, kot je `EditController`, to ne bo vplivalo. +Če se v tem trenutku spremeni signatura konstruktorja razreda `Article`, je edini del kode, ki se mora na to odzvati, sama tovarna `ArticleFactory`. Vse ostale kode, ki delajo z objekti `Article`, kot na primer `EditController`, se to nikakor ne dotakne. -Morda se sprašujete, ali smo stvari dejansko izboljšali. Količina kode se je povečala in vse skupaj je videti sumljivo zapleteno. +Morda si zdaj trkate po čelu, ali smo si sploh pomagali. Količina kode se je povečala in vse skupaj začenja izgledati sumljivo zapleteno. -Ne skrbite, kmalu bomo prišli do vsebnika Nette DI. Ta pa ima v rokavu več trikov, ki bodo močno poenostavili gradnjo aplikacij z uporabo vbrizgavanja odvisnosti. Na primer, namesto razreda `ArticleFactory` boste morali [napisati |factory] le [preprost vmesnik |factory]: +Ne skrbite, kmalu bomo prišli do Nette DI vsebnika. In ta ima vrsto asov v rokavu, s katerimi gradnjo aplikacij, ki uporabljajo dependency injection, neizmerno poenostavi. Tako na primer namesto razreda `ArticleFactory` bo zadostovalo [napisati zgolj vmesnik |factory]: ```php interface ArticleFactory @@ -511,18 +509,18 @@ interface ArticleFactory } ``` -Vendar prehitevamo sami sebe; bodite potrpežljivi :-) +Ampak to prehitevamo, še počakajte :-) -Povzetek .[#toc-summary] ------------------------- +Povzetek +-------- -Na začetku tega poglavja smo vam obljubili, da vam bomo predstavili postopek za oblikovanje čiste kode. Vse, kar je potrebno, je, da razredi: +Na začetku tega poglavja smo obljubili, da si bomo pokazali postopek, kako načrtovati čisto kodo. Zadostuje razredom -- [posredujejo odvisnosti, ki jih potrebujejo |#Rule #1: Let It Be Passed to You] -- [nasprotno, ne posredujejo tistega, česar neposredno ne potrebujejo |#Rule #2: Take What's Yours] -- [in da je predmete z odvisnostmi najbolje ustvariti v tovarnah |#Rule #3: Let the Factory Handle it] +1) [predajati odvisnosti, ki jih potrebujejo |#Pravilo št. 1: naj ti bo predano] +2) [in nasprotno ne predajati, česar neposredno ne potrebujejo |#Pravilo št. 2: vzemi kar je tvoje] +3) [in da se objekti z odvisnostmi najbolje izdelujejo v tovarnah |#Pravilo št. 3: prepusti tovarni] -Na prvi pogled se zdi, da ta tri pravila nimajo daljnosežnih posledic, vendar vodijo do korenito drugačnega pogleda na oblikovanje kode. Ali je vredno? Razvijalci, ki so opustili stare navade in začeli dosledno uporabljati vbrizgavanje odvisnosti, menijo, da je ta korak ključen trenutek v njihovem poklicnem življenju. Z njim se jim je odprl svet preglednih in vzdržljivih aplikacij. +Morda se na prvi pogled ne zdi tako, a ta tri pravila imajo daljnosežne posledice. Vodijo k radikalno drugačnemu pogledu na načrtovanje kode. Se splača? Programerji, ki so opustili stare navade in začeli dosledno uporabljati dependency injection, menijo, da je ta korak ključni trenutek v njihovem poklicnem življenju. Odprl se jim je svet preglednih in vzdržljivih aplikacij. -Kaj pa, če koda ne uporablja dosledno vbrizgavanja odvisnosti? Kaj pa, če se zanaša na statične metode ali enojne metode? Ali to povzroča težave? [Da, povzroča, in to zelo temeljne |global-state]. +Kaj pa, če koda dosledno ne uporablja dependency injection? Kaj če je zgrajena na statičnih metodah ali singletonih? Ali to prinaša kakšne težave? [Prinaša in zelo bistvene |global-state]. diff --git a/dependency-injection/sl/nette-container.texy b/dependency-injection/sl/nette-container.texy index 0c22fd1564..a74d0f73bc 100644 --- a/dependency-injection/sl/nette-container.texy +++ b/dependency-injection/sl/nette-container.texy @@ -1,10 +1,10 @@ -Zabojnik Nette DI -***************** +Nette DI Vsebnik +**************** .[perex] -Nette DI je ena izmed najbolj zanimivih knjižnic Nette. Ustvari in samodejno posodablja sestavljene vsebnike DI, ki so izjemno hitri in jih je neverjetno enostavno konfigurirati. +Nette DI je ena izmed najbolj zanimivih knjižnic Nette. Zna generirati in samodejno posodabljati prevedene DI vsebnike, ki so izjemno hitri in neverjetno enostavni za konfiguracijo. -Storitve, ki jih ustvarja vsebnik DI, so običajno opredeljene s konfiguracijskimi datotekami v [formatu NEON |neon:format]. Zabojnik, ki smo ga ročno ustvarili v [prejšnjem razdelku |container], bi bil zapisan na naslednji način: +Podobo storitev, ki jih mora ustvarjati DI vsebnik, definiramo običajno s pomočjo konfiguracijskih datotek v [formatu NEON|neon:format]. Vsebnik, ki smo ga ročno ustvarili v [prejšnjem poglavju|container], bi se zapisal takole: ```neon parameters: @@ -19,16 +19,15 @@ services: - UserController ``` -Zapis je zelo kratek. +Zapis je resnično kratek. -Vse odvisnosti, deklarirane v konstruktorjih razredov `ArticleFactory` in `UserController`, poišče in posreduje Nette DI sam zaradi tako imenovanega [samodejnega povezovanja |autowiring], zato v konfiguracijski datoteki ni treba ničesar navajati. -Torej tudi če se parametri spremenijo, vam v konfiguraciji ni treba ničesar spreminjati. Nette bo samodejno regeneriral vsebnik. V njem se lahko osredotočite izključno na razvoj aplikacije. +Vse odvisnosti, deklarirane v konstruktorjih razredov `ArticleFactory` in `UserController`, si Nette DI sam ugotovi in preda zahvaljujoč t.i. [autowiringu|autowiring], v konfiguracijski datoteki zato ni treba ničesar navajati. Torej tudi če pride do spremembe parametrov, vam ni treba v konfiguraciji ničesar spreminjati. Nette vsebnik samodejno pregenerira. Vi se lahko tam osredotočite izključno na razvoj aplikacije. -Če želite odvisnosti posredovati z uporabo nastavljalcev, za to uporabite [nastavitveni |services#setup] razdelek. +Če želimo odvisnosti predajati s pomočjo setterjev, uporabimo za to sekcijo [setup |services#Setup]. -Nette DI bo neposredno ustvaril kodo PHP za vsebnik. Rezultat je torej datoteka `.php`, ki jo lahko odprete in preučite. Tako si lahko natančno ogledate, kako deluje vsebnik. Prav tako ga lahko razhroščate v okolju IDE in ga postopoma pregledujete. In kar je najpomembneje: generirana koda PHP je izjemno hitra. +Nette DI generira neposredno PHP kodo vsebnika. Rezultat je torej datoteka `.php`, ki jo lahko odprete in preučujete. Zahvaljujoč temu natančno vidite, kako vsebnik deluje. Lahko ga tudi razhroščujete v IDE in korakate skozi. In predvsem: generirana PHP koda je izjemno hitra. -Nette DI lahko na podlagi priloženega vmesnika ustvari tudi [tovarniško |factory] kodo. Zato moramo namesto razreda `ArticleFactory` v aplikaciji ustvariti le vmesnik: +Nette DI zna tudi generirati kodo [tovarn|factory] na podlagi posredovanega vmesnika. Zato namesto razreda `ArticleFactory` bo zadostovalo ustvariti v aplikaciji le vmesnik: ```php interface ArticleFactory @@ -37,19 +36,19 @@ interface ArticleFactory } ``` -Celoten primer najdete [na GitHubu |https://github.com/nette-examples/di-example-doc]. +Celoten primer najdete [na GitHubu|https://github.com/nette-examples/di-example-doc]. -Samostojna uporaba .[#toc-standalone-use] ------------------------------------------ +Samostojna uporaba +------------------ -Uporaba knjižnice Nette DI v aplikaciji je zelo preprosta. Najprej jo namestimo s programom Composer (ker je prenašanje datotek zip zelo zastarelo): +Uvedba knjižnice Nette DI v aplikacijo je zelo enostavna. Najprej jo namestimo s Composerjem (ker je prenašanje zipov taaaako zastarelo): ```shell composer require nette/di ``` -Naslednja koda ustvari primerek vsebnika DI v skladu s konfiguracijo, shranjeno v datoteki `config.neon`: +Naslednja koda ustvari instanco DI vsebnika glede na konfiguracijo, shranjeno v datoteki `config.neon`: ```php $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp'); @@ -59,24 +58,23 @@ $class = $loader->load(function ($compiler) { $container = new $class; ``` -Zabojnik se ustvari samo enkrat, njegova koda se zapiše v predpomnilnik (imenik `__DIR__ . '/temp'` ) in se pri naslednjih zahtevah bere samo od tam. +Vsebnik se generira le enkrat, njegova koda se zapiše v predpomnilnik (imenik `__DIR__ . '/temp'`) in pri naslednjih zahtevah se le še od tam naloži. -Za ustvarjanje in pridobivanje storitev se uporabljata metodi `getService()` ali `getByType()`. Tako ustvarimo objekt `UserController`: +Za ustvarjanje in pridobivanje storitev služita metodi `getService()` ali `getByType()`. Tako ustvarimo objekt `UserController`: ```php -$database = $container->getByType(UserController::class); -$database->query('...'); +$controller = $container->getByType(UserController::class); +$controller->someMethod(); ``` -Med razvojem je koristno vklopiti način samodejnega osveževanja, pri katerem se vsebnik samodejno obnovi, če se spremeni kateri koli razred ali konfiguracijska datoteka. Samo navedite `true` kot drugi argument v konstruktorju `ContainerLoader`. +Med razvojem je koristno aktivirati način samodejnega osveževanja, ko se vsebnik samodejno pregenerira, če pride do spremembe kateregakoli razreda ali konfiguracijske datoteke. Zadostuje, da v konstruktorju `ContainerLoader` navedete kot drugi argument `true`. ```php $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp', true); ``` -Uporaba z ogrodjem Nette .[#toc-using-it-with-the-nette-framework] ------------------------------------------------------------------- +Uporaba z ogrodjem Nette +------------------------ -Kot smo pokazali, uporaba Nette DI ni omejena na aplikacije, napisane v ogrodju Nette, temveč jo lahko namestite kjer koli s samo tremi vrsticami kode. -Če pa razvijate aplikacije v okolju Nette Framework, za konfiguracijo in ustvarjanje vsebnika poskrbi [Bootstrap |application:bootstrap#toc-di-container-configuration]. +Kot smo pokazali, uporaba Nette DI ni omejena na aplikacije, napisane v Nette Frameworku, lahko ga s pomočjo le 3 vrstic kode uvedete kjerkoli. Če pa razvijate aplikacije v Nette Frameworku, ima konfiguracijo in ustvarjanje vsebnika na skrbi [Bootstrap |application:bootstrapping#Konfiguracija DI vsebnika]. diff --git a/dependency-injection/sl/passing-dependencies.texy b/dependency-injection/sl/passing-dependencies.texy index 451f940105..aa75ecf278 100644 --- a/dependency-injection/sl/passing-dependencies.texy +++ b/dependency-injection/sl/passing-dependencies.texy @@ -1,24 +1,24 @@ -Podajanje odvisnosti -******************** +Predajanje odvisnosti +********************* <div class=perex> -Argumente ali "odvisnosti" v terminologiji DI lahko razredom posredujete na naslednje glavne načine: +Argumente ali v terminologiji DI „odvisnosti“ lahko v razrede predajamo na naslednje glavne načine: -* posredovanje s konstruktorjem -* posredovanje z metodo (imenovano setter) -* z nastavitvijo lastnosti +* predajanje s konstruktorjem +* predajanje z metodo (t.i. setterjem) +* nastavitev spremenljivke * z metodo, anotacijo ali atributom *inject* </div> -Različne različice bomo zdaj ponazorili s konkretnimi primeri. +Zdaj si bomo posamezne variante pokazali na konkretnih primerih. -Vbrizgavanje konstruktorjev .[#toc-constructor-injection] -========================================================= +Predajanje s konstruktorjem +=========================== -Odvisnosti se konstruktorju ob ustvarjanju predmeta posredujejo kot argumenti: +Odvisnosti se predajajo v trenutku ustvarjanja objekta kot argumenti konstruktorja: ```php class MyClass @@ -34,9 +34,9 @@ class MyClass $obj = new MyClass($cache); ``` -Ta oblika je uporabna za obvezne odvisnosti, ki jih razred nujno potrebuje za delovanje, saj brez njih primerka ni mogoče ustvariti. +Ta oblika je primerna za obvezne odvisnosti, ki jih razred nujno potrebuje za svoje delovanje, saj brez njih instance ne bo mogoče ustvariti. -Od različice PHP 8.0 lahko uporabimo krajšo obliko zapisa ([constructor property promotion |https://blog.nette.org/sl/php-8-0-popoln-pregled-novic#toc-constructor-property-promotion]), ki je funkcionalno enakovredna: +Od PHP 8.0 lahko uporabimo krajšo obliko zapisa ([constructor property promotion |https://blog.nette.org/sl/php-8-0-complete-overview-of-news#toc-constructor-property-promotion]), ki je funkcionalno ekvivalentna: ```php // PHP 8.0 @@ -49,7 +49,7 @@ class MyClass } ``` -Od PHP 8.1 lahko lastnost označimo z zastavico `readonly`, ki izjavlja, da se vsebina lastnosti ne bo spremenila: +Od PHP 8.1 lahko spremenljivko označimo z zastavico `readonly`, ki deklarira, da se vsebina spremenljivke ne bo več spremenila: ```php // PHP 8.1 @@ -62,13 +62,13 @@ class MyClass } ``` -Kontejner DI samodejno posreduje odvisnosti konstruktorju z uporabo [samodejnega povezovanja |autowiring]. Argumente, ki jih ni mogoče posredovati na ta način (npr. nize, številke, logične vrednosti), [zapiše v konfiguracijo |services#Arguments]. +DI vsebnik preda konstruktorju odvisnosti samodejno s pomočjo [autowiringa |autowiring]. Argumente, ki jih na ta način ni mogoče predati (npr. nizi, števila, booleani) [zapišemo v konfiguraciji |services#Argumenti]. -Konstruktorski pekel .[#toc-constructor-hell] ---------------------------------------------- +Constructor hell +---------------- -Izraz *konstruktorski pekel* se nanaša na situacijo, ko potomec podeduje od starševskega razreda, katerega konstruktor zahteva odvisnosti, in tudi potomec zahteva odvisnosti. Prav tako mora prevzeti in prenesti odvisnosti starševskega razreda: +Izraz *constructor hell* označuje situacijo, ko potomec deduje od starševskega razreda, katerega konstruktor zahteva odvisnosti, in hkrati potomec zahteva odvisnosti. Pri tem mora prevzeti in predati tudi starševske: ```php abstract class BaseClass @@ -94,11 +94,11 @@ final class MyClass extends BaseClass } ``` -Težava se pojavi, ko želimo spremeniti konstruktor razreda `BaseClass`, na primer ko dodamo novo odvisnost. Takrat moramo spremeniti tudi vse konstruktorje otrok. Zaradi tega je takšna sprememba pekel. +Težava nastane v trenutku, ko bomo želeli spremeniti konstruktor razreda `BaseClass`, na primer ko se doda nova odvisnost. Potem je namreč treba prilagoditi tudi vse konstruktorje potomcev. Kar iz takšne prilagoditve naredi pekel. -Kako to preprečiti? Rešitev je, da **prednost dajemo kompoziciji pred dedovanjem**. +Kako temu preprečiti? Rešitev je **dajati prednost [kompoziciji pred dedovanjem |faq#Zakaj se daje prednost kompoziciji pred dedovanjem]**. -Zato kodo oblikujmo drugače. Izogibali se bomo abstraktnim `Base*` razredom. Namesto da bi razred `MyClass` pridobil neko funkcionalnost s podedovanjem od razreda `BaseClass`, mu bo ta funkcionalnost posredovana kot odvisnost: +Torej bomo kodo zasnovali drugače. Izogibali se bomo [abstraktnim |nette:introduction-to-object-oriented-programming#Abstraktni razredi] `Base*` razredom. Namesto da bi `MyClass` pridobival določeno funkcionalnost s tem, da deduje od `BaseClass`, si bo to funkcionalnost pustil predati kot odvisnost: ```php final class SomeFunctionality @@ -125,10 +125,10 @@ final class MyClass ``` -Vbrizgavanje množice .[#toc-setter-injection] -============================================= +Predajanje s setterjem +====================== -Odvisnosti se posredujejo s klicem metode, ki jih shrani v zasebne lastnosti. Običajno so te metode poimenovane v obliki `set*()`, zato se imenujejo setterji, seveda pa se lahko imenujejo tudi kako drugače. +Odvisnosti se predajajo s klicem metode, ki jih shrani v zasebno spremenljivko. Običajna konvencija poimenovanja teh metod je oblika `set*()`, zato se jim reče setterji, vendar se lahko seveda imenujejo kakorkoli drugače. ```php class MyClass @@ -145,9 +145,9 @@ $obj = new MyClass; $obj->setCache($cache); ``` -Ta metoda je uporabna za neobvezne odvisnosti, ki niso potrebne za delovanje razreda, saj ni zagotovljeno, da jih bo objekt dejansko prejel (tj. da bo uporabnik poklical metodo). +Ta način je primeren za neobvezne odvisnosti, ki niso nujne za delovanje razreda, saj ni zagotovljeno, da bo objekt odvisnost dejansko prejel (tj. da bo uporabnik metodo poklical). -Hkrati ta metoda omogoča, da se setter večkrat pokliče za spremembo odvisnosti. Če to ni zaželeno, metodi dodajte preverjanje ali pa od različice PHP 8.1 označite lastnost `$cache` z zastavico `readonly`. +Hkrati ta način dopušča ponavljajoče klicanje setterja in s tem spreminjanje odvisnosti. Če to ni zaželeno, dodamo v metodo preverjanje ali od PHP 8.1 označimo lastnost `$cache` z zastavico `readonly`. ```php class MyClass @@ -156,29 +156,28 @@ class MyClass public function setCache(Cache $cache): void { - if ($this->cache) { - throw new RuntimeException('The dependency has already been set'); + if (isset($this->cache)) { + throw new RuntimeException('Odvisnost je že bila nastavljena'); } $this->cache = $cache; } } ``` -Klic setterja je opredeljen v konfiguraciji vsebnika DI v [razdelku setup |services#Setup]. Tudi tu se uporablja samodejno posredovanje odvisnosti s pomočjo funkcije autowiring: +Klic setterja definiramo v konfiguraciji DI vsebnika v [ključu setup |services#Setup]. Tudi tukaj se uporablja samodejno predajanje odvisnosti s pomočjo autowiringa: ```neon services: - - - create: MyClass + - create: MyClass setup: - setCache ``` -Vbrizgavanje lastnosti .[#toc-property-injection] -================================================= +Nastavitev spremenljivke +======================== -Odvisnosti se posredujejo neposredno lastnosti: +Odvisnosti se predajajo z zapisom neposredno v člansko spremenljivko: ```php class MyClass @@ -190,28 +189,27 @@ $obj = new MyClass; $obj->cache = $cache; ``` - `public`Tako nimamo nadzora nad tem, ali bo posredovana odvisnost dejansko določenega tipa (to je veljalo pred PHP 7.4), in izgubimo možnost, da bi se na novo dodeljeno odvisnost odzvali z lastno kodo, na primer da bi preprečili nadaljnje spremembe. Hkrati lastnost postane del javnega vmesnika razreda, kar morda ni zaželeno. +Ta način se šteje za neprimernega, ker mora biti članska spremenljivka deklarirana kot `public`. In zato nimamo nadzora nad tem, da bo predana odvisnost dejansko danega tipa (veljalo pred PHP 7.4) in izgubimo možnost reagirati na novo dodeljeno odvisnost z lastno kodo, na primer preprečiti nadaljnjo spremembo. Hkrati spremenljivka postane del javnega vmesnika razreda, kar morda ni zaželeno. -Nastavitev spremenljivke je opredeljena v konfiguraciji vsebnika DI v [razdelku nastavitev |services#Setup]: +Nastavitev spremenljivke definiramo v konfiguraciji DI vsebnika v [sekciji setup |services#Setup]: ```neon services: - - - create: MyClass + - create: MyClass setup: - $cache = @\Cache ``` -Vbrizgajte .[#toc-inject] -========================= +Inject +====== -Medtem ko so prejšnje tri metode na splošno veljavne v vseh objektno usmerjenih jezikih, je injiciranje z metodo, anotacijo ali atributom *inject* značilno za predstavnike Nette. Obravnavani so v [ločenem poglavju |best-practices:inject-method-attribute]. +Medtem ko prejšnji trije načini veljajo na splošno v vseh objektno usmerjenih jezikih, je vbrizgavanje z metodo, anotacijo ali atributom *inject* specifično izključno za presenterje v Nette. O njih govori [samostojno poglavje |best-practices:inject-method-attribute]. -Kateri način izbrati? .[#toc-which-way-to-choose] -================================================= +Kateri način izbrati? +===================== -- konstruktor je primeren za obvezne odvisnosti, ki jih razred potrebuje za delovanje -- setter pa je po drugi strani primeren za neobvezne odvisnosti ali odvisnosti, ki jih je mogoče spremeniti -- javne spremenljivke niso priporočljive +- konstruktor je primeren za obvezne odvisnosti, ki jih razred nujno potrebuje za svoje delovanje +- setter je nasprotno primeren za neobvezne odvisnosti ali odvisnosti, ki jih je mogoče še naprej spreminjati +- javne spremenljivke niso primerne diff --git a/dependency-injection/sl/services.texy b/dependency-injection/sl/services.texy index 854a61e05a..d3a0e1bc48 100644 --- a/dependency-injection/sl/services.texy +++ b/dependency-injection/sl/services.texy @@ -1,33 +1,33 @@ -Opredelitve storitev +Definiranje storitev ******************** .[perex] -Konfiguracija je mesto, kamor postavimo opredelitve storitev po meri. To storimo v razdelku `services`. +Konfiguracija je mesto, kjer učimo DI vsebnik, kako naj sestavlja posamezne storitve in kako jih povezuje z drugimi odvisnostmi. Nette ponuja zelo pregleden in eleganten način, kako to doseči. -Tako na primer ustvarimo storitev z imenom `database`, ki bo primerek razreda `PDO`: +Sekcija `services` v konfiguracijski datoteki formata NEON je mesto, kjer definiramo lastne storitve in njihove konfiguracije. Poglejmo si preprost primer definicije storitve, imenovane `database`, ki predstavlja instanco razreda `PDO`: ```neon services: database: PDO('sqlite::memory:') ``` -Poimenovanje storitev se uporablja zato, da [se |#Referencing Services] lahko nanje [sklicujemo |#Referencing Services]. Če se na storitev ne sklicujemo, je ni treba poimenovati. Zato namesto imena uporabimo le točko: +Navedena konfiguracija bo vodila do naslednje tovarne metode v [DI vsebniku|container]: -```neon -services: - - PDO('sqlite::memory:') # anonimna storitev +```php +public function createServiceDatabase(): PDO +{ + return new PDO('sqlite::memory:'); +} ``` -Enovrstični vnos lahko razdelimo v več vrstic, da lahko dodamo dodatne tipke, kot je [nastavitev |#setup]. Vzdevek za ključ `create:` je `factory:`. +Imena storitev nam omogočajo, da se nanje sklicujemo v drugih delih konfiguracijske datoteke, in sicer v formatu `@imeStoritve`. Če storitve ni treba poimenovati, lahko preprosto uporabimo le alinejo: ```neon services: - database: - create: PDO('sqlite::memory:') - setup: ... + - PDO('sqlite::memory:') ``` -Storitev nato prikličemo iz vsebnika DI z metodo `getService()` po imenu ali, še bolje, z metodo `getByType()` po vrsti: +Za pridobitev storitve iz DI vsebnika lahko uporabimo metodo `getService()` z imenom storitve kot parametrom ali metodo `getByType()` s tipom storitve: ```php $database = $container->getService('database'); @@ -35,26 +35,28 @@ $database = $container->getByType(PDO::class); ``` -Ustvarjanje storitve .[#toc-creating-a-service] -=============================================== +Ustvarjanje storitve +==================== -Storitev najpogosteje ustvarimo tako, da preprosto ustvarimo primerek razreda: +Večinoma ustvarimo storitev preprosto tako, da ustvarimo instanco določenega razreda. Na primer: ```neon services: database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -To ustvari tovarniško metodo v [vsebniku DI |container]: +Če moramo konfiguracijo razširiti z dodatnimi ključi, lahko definicijo razpišemo v več vrstic: -```php -public function createServiceDatabase(): PDO -{ - return new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'secret'); -} +```neon +services: + database: + create: PDO('sqlite::memory:') + setup: ... ``` -Za posredovanje [argumentov |#Arguments] se lahko uporabi tudi ključ `arguments`: +Ključ `create` ima alias `factory`, obe varianti sta v praksi pogosti. Vendar priporočamo uporabo `create`. + +Argumenti konstruktorja ali ustvarjalne metode so lahko alternativno zapisani v ključu `arguments`: ```neon services: @@ -63,294 +65,303 @@ services: arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret] ``` -Statična metoda lahko ustvari tudi storitev: +Storitve ni treba ustvarjati le s preprostim ustvarjanjem instance razreda, lahko so tudi rezultat klica statičnih metod ali metod drugih storitev: ```neon services: - database: My\Database::create(root, secret) + database: DatabaseFactory::create() + router: @routerFactory::create() ``` -Ustreza kodi PHP: +Opazite, da se za enostavnost namesto `->` uporablja `::`, glej [#Izrazna sredstva]. Generirale se bodo te tovarne metode: ```php public function createServiceDatabase(): PDO { - return My\Database::create('root', 'secret'); + return DatabaseFactory::create(); +} + +public function createServiceRouter(): RouteList +{ + return $this->getService('routerFactory')->create(); } ``` -Predpostavlja se, da ima statična metoda `My\Database::create()` določeno povratno vrednost, ki jo mora vsebnik DI poznati. Če je nima, zapišemo tip v konfiguracijo: +DI vsebnik mora poznati tip ustvarjene storitve. Če ustvarjamo storitev s pomočjo metode, ki nima specificiranega vrnjenega tipa, moramo ta tip eksplicitno navesti v konfiguraciji: ```neon services: database: - create: My\Database::create(root, secret) + create: DatabaseFactory::create() type: PDO ``` -Nette DI vam omogoča izjemno zmogljive izrazne zmogljivosti za zapisovanje skoraj vsega. Na primer za [sklicevanje na |#Referencing Services] drugo storitev in klic njene metode. Zaradi enostavnosti se namesto `->` uporablja `::`. + +Argumenti +========= + +V konstruktor in metode predajamo argumente na način, ki je zelo podoben kot v samem PHP: ```neon services: - routerFactory: App\Router\Factory - router: @routerFactory::create() + database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -Ustreza kodi PHP: - -```php -public function createServiceRouterFactory(): App\Router\Factory -{ - return new App\Router\Factory; -} +Za boljšo berljivost lahko argumente razpišemo v ločene vrstice. V takem primeru je uporaba vejic neobvezna: -public function createServiceRouter(): Router -{ - return $this->getService('routerFactory')->create(); -} +```neon +services: + database: PDO( + 'mysql:host=127.0.0.1;dbname=test' + root + secret + ) ``` -klice metod je mogoče verižno povezati kot v PHP: +Argumente lahko tudi poimenujete in vam ni treba skrbeti za njihov vrstni red: ```neon services: - foo: FooFactory::build()::get() + database: PDO( + username: root + password: secret + dsn: 'mysql:host=127.0.0.1;dbname=test' + ) ``` -Ustreza kodi PHP: +Če želite nekatere argumente izpustiti in uporabiti njihovo privzeto vrednost ali dodati storitev s pomočjo [autowiringa|autowiring], uporabite podčrtaj: -```php -public function createServiceFoo() -{ - return FooFactory::build()->get(); -} +```neon +services: + foo: Foo(_, %appDir%) ``` +Kot argumente lahko predajate storitve, uporabljate parametre in še veliko več, glej [#Izrazna sredstva]. + -Argumenti .[#toc-arguments] -=========================== +Setup +===== -Poimenovane parametre lahko uporabite tudi za posredovanje argumentov: +V sekciji `setup` definiramo metode, ki se morajo poklicati pri ustvarjanju storitve. ```neon services: - database: PDO( - 'mysql:host=127.0.0.1;dbname=test' # pozicijski - username: root # poimenovani - password: secret # named - ) + database: + create: PDO(%dsn%, %user%, %password%) + setup: + - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) ``` -Uporaba vejic ni obvezna, če se argumenti razdelijo v več vrstic. +To bi v PHP izgledalo takole: -Seveda lahko kot argumente uporabimo tudi [druge storitve |#Referencing Services] ali [parametre |configuration#parameters]: +```php +public function createServiceDatabase(): PDO +{ + $service = new PDO('...', '...', '...'); + $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + return $service; +} +``` + +Poleg klicanja metod lahko tudi predajate vrednosti v lastnosti. Podprto je tudi dodajanje elementa v polje, ki ga je treba zapisati v narekovajih, da ne pride do kolizije s sintakso NEON: ```neon services: - - Foo(@anotherService, %appDir%) + foo: + create: Foo + setup: + - $value = 123 + - '$onClick[]' = [@bar, clickHandler] ``` -Ustreza kodi PHP: +Kar bi v PHP kodi izgledalo takole: ```php -public function createService01(): Foo +public function createServiceFoo(): Foo { - return new Foo($this->getService('anotherService'), '...'); + $service = new Foo; + $service->value = 123; + $service->onClick[] = [$this->getService('bar'), 'clickHandler']; + return $service; } ``` -Če je prvi argument [samodejen |autowiring] in želite navesti drugega, izpustite prvega z `_` character, for example `Foo(_, %appDir%)`. Ali še bolje, samo drugi argument predajte kot poimenovani parameter, npr. `Foo(path: %appDir%)`. - -Nette DI in format NEON vam omogočata izjemno močne izrazne možnosti za zapisovanje skoraj vsega. Tako je lahko argument novo ustvarjen objekt, s posebnim zapisom lahko kličete statične metode, metode drugih storitev ali celo globalne funkcije: +V setupu lahko pa kličete tudi statične metode ali metode drugih storitev. Če morate kot argument predati trenutno storitev, jo navedite kot `@self`: ```neon services: - analyser: My\Analyser( - FilesystemIterator(%appDir%) # ustvariti predmet - DateTime::createFromFormat('Y-m-d') # klic statične metode - @anotherService # posredovanje druge storitve - @http.request::getRemoteAddress() # klic druge metode storitve - ::getenv(NetteMode) # klic globalne funkcije - ) + foo: + create: Foo + setup: + - My\Helpers::initializeFoo(@self) + - @anotherService::setFoo(@self) ``` -Ustreza kodi PHP: +Opazite, da se za enostavnost namesto `->` uporablja `::`, glej [#Izrazna sredstva]. Generirala se bo takšna tovarna metoda: ```php -public function createServiceAnalyser(): My\Analyser +public function createServiceFoo(): Foo { - return new My\Analyser( - new FilesystemIterator('...'), - DateTime::createFromFormat('Y-m-d'), - $this->getService('anotherService'), - $this->getService('http.request')->getRemoteAddress(), - getenv('NetteMode') - ); + $service = new Foo; + My\Helpers::initializeFoo($service); + $this->getService('anotherService')->setFoo($service); + return $service; } ``` -Posebne funkcije .[#toc-special-functions] ------------------------------------------- - -Posebne funkcije lahko uporabite tudi v argumentih za izločanje ali zanikanje vrednosti: +Izrazna sredstva +================ -- `not(%arg%)` zanikanje -- `bool(%arg%)` brez izgub odlitek v bool -- `int(%arg%)` brez izgub v int -- `float(%arg%)` brez izgub v float -- `string(%arg%)` brez izgube v niz +Nette DI nam daje izjemno bogata izrazna sredstva, s katerimi lahko zapišemo skoraj karkoli. V konfiguracijskih datotekah lahko tako uporabljamo [parametre |configuration#Parametri]: ```neon -services: - - Foo( - id: int(::getenv('ProjectId')) - productionMode: not(%debugMode%) - ) -``` - -Brezizgubno prepisovanje se razlikuje od običajnega prepisovanja v PHP, npr. z uporabo `(int)`, po tem, da vrže izjemo za neštevilčne vrednosti. +# parameter +%wwwDir% -Kot argumente je mogoče posredovati več storitev. Polje vseh storitev določene vrste (tj. razreda ali vmesnika) ustvari funkcija `typed()`. Funkcija bo izpustila storitve, ki imajo onemogočeno samodejno napeljavo, lahko pa se določi več vrst, ločenih z vejico. +# vrednost parametra pod ključem +%mailer.user% -```neon -services: - - BarsDependent( typed(Bar) ) +# parameter znotraj niza +'%wwwDir%/images' ``` -Polje storitev lahko posredujete tudi samodejno z uporabo [samodejnega napeljevanja |autowiring#Collection of Services]. - -Polje vseh storitev z določeno [oznako |#tags] ustvari funkcija `tagged()`. Navedete lahko več oznak, ločenih z vejico. +Nadalje ustvarjati objekte, klicati metode in funkcije: ```neon -services: - - LoggersDependent( tagged(logger) ) -``` +# ustvarjanje objekta +DateTime() +# klic statične metode +Collator::create(%locale%) -Sklicevanje na storitve .[#toc-referencing-services] -==================================================== +# klic PHP funkcije +::getenv(DB_USER) +``` -Na posamezne storitve se sklicujemo z uporabo znakov `@` and name, so for example `@database`: +Sklicujemo se na storitve bodisi po njihovem imenu ali s pomočjo tipa: ```neon -services: - - create: Foo(@database) - setup: - - setCacheStorage(@cache.storage) +# storitev po imenu +@database + +# storitev po tipu +@Nette\Database\Connection ``` -Ustreza kodi PHP: +Uporabljati first-class callable sintakso: .{data-version:3.2.0} -```php -public function createService01(): Foo -{ - $service = new Foo($this->getService('database')); - $service->setCacheStorage($this->getService('cache.storage')); - return $service; -} +```neon +# ustvarjanje povratnega klica, podobno [@user, logout] +@user::logout(...) ``` -Tudi na anonimne storitve se lahko sklicujete s povratnim klicem, le namesto imena navedite njihovo vrsto (razred ali vmesnik). Vendar to običajno ni potrebno zaradi [samodejnega povezovanja |autowiring]. +Uporabljati konstante: ```neon -services: - - create: Foo(@Nette\Database\Connection) # ali @\PDO - setup: - - setCacheStorage(@cache.storage) +# konstanta razreda +FilesystemIterator::SKIP_DOTS + +# globalno konstanto dobimo s PHP funkcijo constant() +::constant(PHP_VERSION) ``` +Klicanje metod lahko verižimo enako kot v PHP. Le za enostavnost se namesto `->` uporablja `::`: -Nastavitev .[#toc-setup] -======================== +```neon +DateTime()::format('Y-m-d') +# PHP: (new DateTime())->format('Y-m-d') -V razdelku nastavitev so naštete metode, ki jih je treba poklicati pri ustvarjanju storitve: +@http.request::getUrl()::getHost() +# PHP: $this->getService('http.request')->getUrl()->getHost() +``` + +Te izraze lahko uporabljate kjerkoli, pri [ustvarjanju storitev |#Ustvarjanje storitve], v [argumentih |#Argumenti], v sekciji [#setup] ali [parametrih |configuration#Parametri]: ```neon +parameters: + ipAddress: @http.request::getRemoteAddress() + services: database: - create: PDO(%dsn%, %user%, %password%) + create: DatabaseFactory::create( @anotherService::getDsn() ) setup: - - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) + - initialize( ::getenv('DB_USER') ) ``` -Ustreza kodi PHP: -```php -public function createServiceDatabase(): PDO -{ - $service = new PDO('...', '...', '...'); - $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - return $service; -} -``` +Posebne funkcije +---------------- -Nastavite lahko tudi lastnosti. Podprto je tudi dodajanje elementa v polje, ki ga je treba zapisati v narekovajih, da ne bo v nasprotju s sintakso NEON: +V konfiguracijskih datotekah lahko uporabljate te posebne funkcije: +- `not()` negacija vrednosti +- `bool()`, `int()`, `float()`, `string()` pretvorba brez izgube v dani tip +- `typed()` ustvari polje vseh storitev specificiranega tipa +- `tagged()` ustvari polje vseh storitev z dano oznako ```neon services: - foo: - create: Foo - setup: - - $value = 123 - - '$onClick[]' = [@bar, clickHandler] + - Foo( + id: int(::getenv('ProjectId')) + productionMode: not(%debugMode%) + ) ``` -Ustreza kodi PHP: +V primerjavi s klasično pretvorbo v PHP, kot je npr. `(int)`, pretvorba brez izgube vrže izjemo za neštevilske vrednosti. -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - $service->value = 123; - $service->onClick[] = [$this->getService('bar'), 'clickHandler']; - return $service; -} +Funkcija `typed()` ustvari polje vseh storitev danega tipa (razred ali vmesnik). Izpusti storitve, ki imajo izklopljen autowiring. Lahko navedete tudi več tipov, ločenih z vejico. + +```neon +services: + - BarsDependent( typed(Bar) ) ``` -Vendar pa se lahko v nastavitvi kličejo tudi statične metode ali metode drugih storitev. Dejansko storitev jim posredujemo kot `@self`: +Polje storitev določenega tipa lahko predajate kot argument tudi samodejno s pomočjo [autowiringa |autowiring#Polje storitev]. +Funkcija `tagged()` pa ustvarja polje vseh storitev z določeno oznako. Tudi tukaj lahko specificirate več oznak, ločenih z vejico. ```neon services: - foo: - create: Foo - setup: - - My\Helpers::initializeFoo(@self) - - @anotherService::setFoo(@self) + - LoggersDependent( tagged(logger) ) ``` -Ustreza kodi PHP: -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - My\Helpers::initializeFoo($service); - $this->getService('anotherService')->setFoo($service); - return $service; -} +Autowiring +========== + +Ključ `autowired` omogoča vplivanje na obnašanje autowiringa za specifično storitev. Za podrobnosti glej [poglavje o autowiringu|autowiring]. + +```neon +services: + foo: + create: Foo + autowired: false # storitev foo je izključena iz autowiringa ``` -Avtomatska napeljava .[#toc-autowiring] -======================================= +Lazy storitve .{data-version:3.2.4} +=================================== -Ključ za samodejno ožičenje lahko uporabite za izključitev storitve iz samodejnega ožičenja ali za vplivanje na njeno obnašanje. Za več informacij glejte [poglavje o samodejnem ožičenju |autowiring]. +Lazy loading je tehnika, ki odloži ustvarjanje storitve do trenutka, ko je dejansko potrebna. V globalni konfiguraciji lahko [omogočite lazy ustvarjanje |configuration#Lene storitve] za vse storitve hkrati. Za posamezne storitve pa lahko to obnašanje prepišete: ```neon services: foo: create: Foo - autowired: false # foo je odstranjen iz samodejne napeljave + lazy: false ``` +Ko je storitev definirana kot lazy, ob njeni zahtevi iz DI vsebnika dobimo poseben nadomestni objekt. Ta izgleda in se obnaša enako kot dejanska storitev, vendar se dejanska inicializacija (klic konstruktorja in setupa) zgodi šele ob prvem klicu katerekoli njene metode ali lastnosti. + +.[note] +Lazy loading je mogoče uporabiti samo za uporabniške razrede, ne pa za notranje PHP razrede. Zahteva PHP 8.4 ali novejšo različico. + -Oznake .[#toc-tags] -=================== +Oznake +====== -Posameznim storitvam je mogoče dodati informacije o uporabniku v obliki oznak: +Oznake (tags) služijo za dodajanje dopolnilnih informacij k storitvam. Storitvi lahko dodate eno ali več oznak: ```neon services: @@ -360,7 +371,7 @@ services: - cached ``` -Oznake imajo lahko tudi vrednost: +Oznake lahko nosijo tudi vrednosti: ```neon services: @@ -370,14 +381,14 @@ services: logger: monolog.logger.event ``` -Polje storitev z določenimi oznakami lahko posredujete kot argument s funkcijo `tagged()`. Določite lahko tudi več oznak, ki so ločene z vejico. +Da bi dobili vse storitve z določenimi oznakami, lahko uporabite funkcijo `tagged()`: ```neon services: - LoggersDependent( tagged(logger) ) ``` -Imena storitev je mogoče pridobiti iz vsebnika DI z uporabo metode `findByTag()`: +V DI vsebniku lahko dobite imena vseh storitev z določeno oznako s pomočjo metode `findByTag()`: ```php $names = $container->findByTag('logger'); @@ -386,10 +397,10 @@ $names = $container->findByTag('logger'); ``` -Inject Mode .[#toc-inject-mode] -=============================== +Način Inject +============ -Z zastavico `inject: true` se aktivira posredovanje odvisnosti prek javnih spremenljivk z opombo [inject |best-practices:inject-method-attribute#Inject Attributes] in metodami [inject*( |best-practices:inject-method-attribute#inject Methods] ). +S pomočjo zastavice `inject: true` se aktivira predajanje odvisnosti preko javnih spremenljivk z anotacijo [inject |best-practices:inject-method-attribute#Atributi Inject] in metod [inject*() |best-practices:inject-method-attribute#Metode inject]. ```neon services: @@ -398,13 +409,13 @@ services: inject: true ``` -Privzeto je funkcija `inject` aktivirana samo za predstavnike. +Privzeto je `inject` aktiviran samo za presenterje. -Spreminjanje storitev .[#toc-modification-of-services] -====================================================== +Modifikacija storitev +===================== -V vsebniku DI so številne storitve, ki jih je dodala vgrajena ali [vaša razširitev |#di-extensions]. Definicije teh storitev lahko spremenite v konfiguraciji. Na primer, za storitev `application.application`, ki je privzeto objekt `Nette\Application\Application`, lahko spremenimo razred: +DI vsebnik vsebuje veliko storitev, ki so bile dodane preko vgrajene ali [uporabniške razširitve|extensions]. Definicije teh storitev lahko prilagodite neposredno v konfiguraciji. Na primer, lahko spremenite razred storitve `application.application`, ki je standardno `Nette\Application\Application`, na drugega: ```neon services: @@ -413,9 +424,9 @@ services: alteration: true ``` -Oznaka `alteration` je informativna in pove, da samo spreminjamo obstoječo storitev. +Zastavica `alteration` je informativna in pove, da le modificiramo obstoječo storitev. -Dodamo lahko tudi nastavitev: +Lahko tudi dopolnimo setup: ```neon services: @@ -426,7 +437,7 @@ services: - '$onStartup[]' = [@resource, init] ``` -Pri ponovnem pisanju storitve bomo morda želeli odstraniti prvotne argumente, elemente nastavitev ali oznake, za kar je namenjen naslov `reset`: +Pri prepisovanju storitve lahko želimo odstraniti prvotne argumente, postavke setupa ali oznake, za kar služi `reset`: ```neon services: @@ -439,7 +450,7 @@ services: - tags ``` -Storitev, ki je bila dodana z razširitvijo, lahko tudi odstranimo iz vsebnika: +Če želite odstraniti storitev, dodano z razširitvijo, lahko to storite takole: ```neon services: diff --git a/dependency-injection/tr/@home.texy b/dependency-injection/tr/@home.texy index 4d557bb6f0..2e2673922e 100644 --- a/dependency-injection/tr/@home.texy +++ b/dependency-injection/tr/@home.texy @@ -1,24 +1,21 @@ -Bağımlılık Enjeksiyonu -********************** +Nette DI +******** .[perex] -Dependency Injection, koda ve geliştirmeye bakış açınızı temelden değiştirecek bir tasarım modelidir. Temiz tasarlanmış ve sürdürülebilir uygulamalardan oluşan bir dünyaya giden yolu açar. +Dependency Injection, koda ve geliştirmeye bakış açınızı temelden değiştirecek bir tasarım desenidir. Size temiz tasarlanmış ve sürdürülebilir uygulamaların dünyasına giden yolu açacaktır. - [Dependency Injection Nedir? |introduction] -- [Küresel Durum ve Tekiller |global-state] -- [Bağımlılıkları |passing-dependencies]Geçirmek -- [DI Container Nedir? |container] -- [Sıkça Sorulan Sorular |faq] - +- [Küresel Durum ve Singleton'lar |global-state] +- [Bağımlılıkları Geçirme |passing-dependencies] +- [DI Konteyneri Nedir? |container] +- [Sıkça Sorulan Sorular|faq] -Nette DI --------- - `nette/di` paketi PHP için son derece gelişmiş bir derlenmiş DI kapsayıcısı sağlar. +`nette/di` paketi, PHP için son derece gelişmiş, derlenmiş bir DI konteyneri sağlar. -- [Nette DI Konteyner |nette-container] -- [Konfigürasyon |configuration] -- [Hizmet Tanımları |services] +- [Nette DI Konteyneri |nette-container] +- [Yapılandırma |configuration] +- [Servisleri Tanımlama |services] - [Otomatik Kablolama |autowiring] -- [Üretilen Fabrikalar |factory] -- [Nette DI için Uzantılar Oluşturma |extensions] +- [Oluşturulan Fabrikalar |factory] +- [Nette DI için Uzantılar Oluşturma|extensions] diff --git a/dependency-injection/tr/@left-menu.texy b/dependency-injection/tr/@left-menu.texy index 4146c092ba..13be13e5fe 100644 --- a/dependency-injection/tr/@left-menu.texy +++ b/dependency-injection/tr/@left-menu.texy @@ -1,17 +1,17 @@ -Bağımlılık Enjeksiyonu -********************** +Dependency Injection +******************** - [DI Nedir? |introduction] -- [Küresel Durum ve Tekiller |global-state] -- [Bağımlılıkları Geçme |passing-dependencies] -- [DI Konteyner Nedir? |container] -- [Sıkça Sorulan Sorular |faq] +- [Küresel Durum ve Singleton'lar |global-state] +- [Bağımlılıkları Geçirme |passing-dependencies] +- [DI Konteyneri Nedir? |container] +- [Sıkça Sorulan Sorular|faq] Nette DI -------- -- [Nette DI Konteyner |nette-container] -- [Konfigürasyon |configuration] -- [Hizmet Tanımları |services] +- [Nette DI Konteyneri |nette-container] +- [Yapılandırma |configuration] +- [Servisleri Tanımlama |services] - [Otomatik Kablolama |autowiring] -- [Üretilen Fabrikalar |factory] -- [Nette DI için Uzantılar Oluşturma |extensions] +- [Oluşturulan Fabrikalar |factory] +- [Nette DI için Uzantılar Oluşturma|extensions] diff --git a/dependency-injection/tr/@meta.texy b/dependency-injection/tr/@meta.texy new file mode 100644 index 0000000000..8dfe82f311 --- /dev/null +++ b/dependency-injection/tr/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokümantasyonu}} diff --git a/dependency-injection/tr/autowiring.texy b/dependency-injection/tr/autowiring.texy index 4677539383..fa4eb901e5 100644 --- a/dependency-injection/tr/autowiring.texy +++ b/dependency-injection/tr/autowiring.texy @@ -1,24 +1,24 @@ -Otomatik Kablolama -****************** +Otomatik Bağlama (Autowiring) +***************************** .[perex] -Autowiring, servisleri yapıcıya ve diğer yöntemlere otomatik olarak geçirebilen harika bir özelliktir, böylece bunları yazmamıza gerek kalmaz. Size çok zaman kazandırır. +Otomatik bağlama (Autowiring), kurucuya (constructor) ve diğer metotlara gerekli servisleri otomatik olarak aktarabilen harika bir özelliktir, böylece onları hiç yazmamıza gerek kalmaz. Size çok zaman kazandırır. -Bu, hizmet tanımlarını yazarken argümanların büyük çoğunluğunu atlamamızı sağlar. Bunun yerine: +Bu sayede servis tanımlarını yazarken argümanların büyük çoğunluğunu atlayabiliriz. Yerine: ```neon services: articles: Model\ArticleRepository(@database, @cache.storage) ``` -Sadece yaz: +Sadece şunu yazmak yeterlidir: ```neon services: articles: Model\ArticleRepository ``` -Otomatik kablolama tipler tarafından yönlendirilir, bu nedenle `ArticleRepository` sınıfı aşağıdaki gibi tanımlanmalıdır: +Otomatik bağlama tiplere göre yönlendirilir, bu yüzden çalışması için `ArticleRepository` sınıfının yaklaşık olarak şöyle tanımlanması gerekir: ```php namespace Model; @@ -30,22 +30,22 @@ class ArticleRepository } ``` -Autowiring'i kullanmak için, konteynerdeki her tür için **sadece bir servis** olmalıdır. Daha fazlası olsaydı, autowiring hangisini geçireceğini bilemez ve bir istisna atardı: +Otomatik bağlamanın kullanılabilmesi için, her tip için konteynerde **tam olarak bir servis** olmalıdır. Eğer birden fazla olsaydı, otomatik bağlama hangisini aktaracağını bilemez ve bir istisna fırlatırdı: ```neon services: mainDb: PDO(%dsn%, %user%, %password%) tempDb: PDO('sqlite::memory:') - articles: Model\ArticleRepository # İSTİSNA OLUŞTURUR, hem mainDb hem de tempDb eşleşir + articles: Model\ArticleRepository # İSTİSNA FIRLATIR, hem mainDb hem de tempDb uyar ``` -Çözüm, otomatik kablolamayı atlamak ve hizmet adını açıkça belirtmek olabilir (örn. `articles: Model\ArticleRepository(@mainDb)`). Ancak, bir hizmetin veya [tercih |#Preferred Autowiring] edilen ilk hizmetin otomatik kablolamasını [devre dışı |#Disabled autowiring] bırakmak daha uygundur. +Çözüm, ya otomatik bağlamayı atlamak ve servis adını açıkça belirtmek (yani `articles: Model\ArticleRepository(@mainDb)`) ya da daha akıllıca olanı, servislerden birinin otomatik bağlanmasını [kapatmak |#Otomatik Bağlamayı Kapatma] veya ilk servisi [önceliklendirmektir |#Otomatik Bağlama Önceliği]. -Devre Dışı Otomatik Kablolama .[#toc-disabled-autowiring] ---------------------------------------------------------- +Otomatik Bağlamayı Kapatma +-------------------------- -`autowired: no` seçeneğini kullanarak servis otomatik kablolamayı devre dışı bırakabilirsiniz: +Bir servisin otomatik bağlanmasını `autowired: no` seçeneğiyle kapatabiliriz: ```neon services: @@ -53,28 +53,27 @@ services: tempDb: create: PDO('sqlite::memory:') - autowired: false # tempDb'yi otomatik bağlantıdan kaldırır + autowired: false # tempDb servisi otomatik bağlamadan çıkarıldı - articles: Model\ArticleRepository # bu nedenle mainDb'yi kurucuya geçirir + articles: Model\ArticleRepository # bu yüzden kurucuya mainDb aktarılır ``` -`articles` hizmeti, kurucuya aktarılabilecek `PDO` türünde iki eşleşen hizmet (yani `mainDb` ve `tempDb`) olduğu istisnasını atmaz, çünkü yalnızca `mainDb` hizmetini görür. +`articles` servisi, kurucuya aktarılabilecek iki uygun `PDO` tipi servis (yani `mainDb` ve `tempDb`) olduğu için istisna fırlatmaz, çünkü yalnızca `mainDb` servisini görür. .[note] -Nette'de otomatik kablolamanın yapılandırılması, `autowire: false` seçeneğinin hizmet kurucu argümanları için otomatik kablolamanın kullanılmaması gerektiğini söylediği Symfony'den farklı çalışır. -Nette'de, ister kurucunun argümanları ister başka herhangi bir yöntem için olsun, otomatik bağlantı her zaman kullanılır. `autowired: false` seçeneği, servis örneğinin autowiring kullanılarak hiçbir yere aktarılmaması gerektiğini söyler. +Nette'deki otomatik bağlama yapılandırması, Symfony'den farklı çalışır; burada `autowire: false` seçeneği, verilen servisin kurucu argümanları için otomatik bağlamanın kullanılmaması gerektiğini belirtir. Nette'de otomatik bağlama her zaman kullanılır, ister kurucu argümanları için ister başka herhangi bir metot için olsun. `autowired: false` seçeneği, verilen servisin örneğinin otomatik bağlama yoluyla hiçbir yere aktarılmaması gerektiğini belirtir. -Tercih Edilen Otomatik Kablolama .[#toc-preferred-autowiring] -------------------------------------------------------------- +Otomatik Bağlama Önceliği +------------------------- -Aynı türde daha fazla hizmetimiz varsa ve bunlardan biri `autowired` seçeneğine sahipse, bu hizmet tercih edilen hizmet olur: +Aynı tipten birden fazla servisimiz varsa ve bunlardan birinde `autowired` seçeneğini belirtirsek, bu servis tercih edilen olur: ```neon services: mainDb: create: PDO(%dsn%, %user%, %password%) - autowired: PDO # tercih edilmesini sağlar + autowired: PDO # tercih edilen olur tempDb: create: PDO('sqlite::memory:') @@ -82,13 +81,13 @@ services: articles: Model\ArticleRepository ``` -`articles` hizmeti, eşleşen iki `PDO` hizmeti (yani `mainDb` ve `tempDb`) olduğu için istisna oluşturmaz, ancak tercih edilen hizmeti, yani `mainDb` kullanır. +`articles` servisi, iki uygun `PDO` tipi servis (yani `mainDb` ve `tempDb`) olduğu için istisna fırlatmaz, ancak tercih edilen servisi, yani `mainDb`'yi kullanır. -Hizmetlerin Toplanması .[#toc-collection-of-services] ------------------------------------------------------ +Servis Dizileri +--------------- -Otomatik kablolama ayrıca belirli bir türdeki hizmetlerin bir dizisini de geçirebilir. PHP dizi öğelerinin türünü yerel olarak belirtemediğinden, `array` türüne ek olarak, `ClassName[]` gibi öğe türünü içeren bir phpDoc yorumu eklenmelidir: +Otomatik bağlama, belirli bir tipteki servis dizilerini de aktarabilir. PHP'de dizi öğelerinin tipini yerel olarak yazılamadığı için, `array` tipine ek olarak `ClassName[]` formatında öğe tipini içeren bir phpDoc yorumu eklemek gerekir: ```php namespace Model; @@ -103,46 +102,45 @@ class ShipManager } ``` -DI kapsayıcısı daha sonra verilen türle eşleşen bir dizi hizmeti otomatik olarak geçirir. Otomatik kablolamanın kapalı olduğu hizmetleri atlar. +DI konteyneri daha sonra otomatik olarak ilgili tipe karşılık gelen servis dizisini aktarır. Otomatik bağlanması kapalı olan servisleri atlar. -Eğer phpDoc yorumunun biçimini kontrol edemiyorsanız, yapılandırmada doğrudan bir dizi hizmet iletebilirsiniz [`typed()` |services#Special Functions]. +Yorumdaki tip `array<int, Class>` veya `list<Class>` şeklinde de olabilir. Eğer phpDoc yorumunun şeklini etkileyemiyorsanız, servis dizisini doğrudan yapılandırmada [`typed()` |services#Özel Fonksiyonlar] kullanarak aktarabilirsiniz. -Skaler Argümanlar .[#toc-scalar-arguments] ------------------------------------------- +Skaler Argümanlar +----------------- -Autowiring yalnızca nesneleri ve nesne dizilerini aktarabilir. Skaler argümanlar (örn. dizeler, sayılar, booleanlar) [yapılandırmada yazılır |services#Arguments]. -Bir alternatif, skaler bir değeri (veya birden fazla değeri) [nesne |best-practices:passing-settings-to-presenters] olarak kapsülleyen bir [settings-object |best-practices:passing-settings-to-presenters] oluşturmaktır; bu [nesne |best-practices:passing-settings-to-presenters] daha sonra autowiring kullanılarak tekrar aktarılabilir. +Otomatik bağlama yalnızca nesneleri ve nesne dizilerini yerleştirebilir. Skaler argümanları (örn. karakter dizileri, sayılar, booleanlar) [yapılandırmada yazarız |services#Argümanlar]. Alternatif olarak, skaler değeri (veya birden fazla değeri) bir nesne şeklinde kapsülleyen bir [ayarlar nesnesi |best-practices:passing-settings-to-presenters] oluşturmaktır ve bu nesne daha sonra tekrar otomatik bağlama ile aktarılabilir. ```php class MySettings { public function __construct( - // readonly PHP 8.1'den beri kullanılabilir + // readonly PHP 8.1'den itibaren kullanılabilir public readonly bool $value, ) {} } ``` -Yapılandırmaya ekleyerek bir hizmet oluşturursunuz: +Yapılandırmaya ekleyerek ondan bir servis oluşturursunuz: ```neon services: - MySettings('any value') ``` -Daha sonra tüm sınıflar otomatik kablolama yoluyla bunu talep edecektir. +Tüm sınıflar daha sonra onu otomatik bağlama ile talep eder. -Otomatik Kablolamanın Daraltılması .[#toc-narrowing-of-autowiring] ------------------------------------------------------------------- +Otomatik Bağlamayı Daraltma +--------------------------- -Bireysel hizmetler için, otomatik kablolama belirli sınıflara veya arayüzlere daraltılabilir. +Bireysel servisler için otomatik bağlama yalnızca belirli sınıflara veya arayüzlere daraltılabilir. -Normalde, otomatik kablolama hizmeti, hizmetin türüne karşılık gelen her bir yöntem parametresine geçirir. Daraltma, yöntem parametreleri için belirtilen türlerin, hizmetin onlara aktarılması için karşılaması gereken koşulları belirttiğimiz anlamına gelir. +Normalde otomatik bağlama, servisi, tipine uyduğu her metot parametresine aktarır. Daraltma, servisin onlara aktarılması için metot parametrelerinde belirtilen tiplerin uyması gereken koşulları belirlediğimiz anlamına gelir. -Bir örnek verelim: +Bunu bir örnekle gösterelim: ```php class ParentClass @@ -164,42 +162,42 @@ class ChildDependent } ``` -Hepsini hizmet olarak kaydetseydik, otomatik kablolama başarısız olurdu: +Eğer hepsini servis olarak kaydetseydik, otomatik bağlama başarısız olurdu: ```neon services: parent: ParentClass child: ChildClass - parentDep: ParentDependent # İSTİSNAYI KALDIRIR, hem ebeveyn hem de çocuk eşleşir - childDep: ChildDependent # 'child' hizmetini yapıcıya geçirir + parentDep: ParentDependent # İSTİSNA FIRLATIR, hem parent hem de child servisleri uyar + childDep: ChildDependent # otomatik bağlama kurucuya child servisini aktarır ``` -`parentDep` hizmeti `Multiple services of type ParentClass found: parent, child` istisnasını atar çünkü hem `parent` hem de `child` kurucusuna sığar ve otomatik kablolama hangisini seçeceğine karar veremez. +`parentDep` servisi `Multiple services of type ParentClass found: parent, child` istisnasını fırlatır, çünkü kurucusuna hem `parent` hem de `child` servisleri uyar ve otomatik bağlama hangisini seçeceğine karar veremez. -Bu nedenle `child` hizmeti için otomatik kablolamayı `ChildClass` olarak daraltabiliriz: +Bu nedenle, `child` servisi için otomatik bağlanmasını `ChildClass` tipine daraltabiliriz: ```neon services: parent: ParentClass child: create: ChildClass - autowired: ChildClass # alternatif: 'autowired: self' + autowired: ChildClass # 'autowired: self' de yazılabilir - parentDep: ParentDependent # THROWS EXCEPTION, 'child' autowired olamaz - childDep: ChildDependent # 'child' hizmetini yapıcıya geçirir + parentDep: ParentDependent # otomatik bağlama kurucuya parent servisini aktarır + childDep: ChildDependent # otomatik bağlama kurucuya child servisini aktarır ``` -`parentDep` hizmeti artık tek eşleşen nesne olduğu için `parentDep` hizmet kurucusuna aktarılır. `child` hizmeti artık otomatik bağlantı ile aktarılmamaktadır. Evet, `child` hizmeti hala `ParentClass` türündedir, ancak parametre türü için verilen daraltma koşulu artık geçerli değildir, yani `ParentClass` 'nin `ChildClass`'un *bir süper türü* olduğu artık doğru değildir. +Şimdi `parentDep` servisi kurucusuna `parent` servisi aktarılır, çünkü şimdi tek uygun nesne odur. `child` servisini otomatik bağlama artık oraya aktarmaz. Evet, `child` servisi hala `ParentClass` tipindedir, ancak parametre tipi için verilen daraltma koşulu artık geçerli değildir, yani `ParentClass` *'ın* `ChildClass` *'ın üst tipi olduğu* geçerli değildir. -`child` durumunda, `self` mevcut hizmet türü anlamına geldiğinden `autowired: ChildClass`, `autowired: self` olarak yazılabilir. +`child` servisi için `autowired: ChildClass` yerine `autowired: self` de yazılabilirdi, çünkü `self` geçerli servisin sınıfı için bir yer tutucu tanımdır. -`autowired` anahtarı, dizi olarak birkaç sınıf ve arayüz içerebilir: +`autowired` anahtarında, bir dizi olarak birkaç sınıf veya arayüz de belirtilebilir: ```neon autowired: [BarClass, FooInterface] ``` -Örneğe arayüzler eklemeye çalışalım: +Örneği bir arayüzle daha tamamlamayı deneyelim: ```php interface FooInterface @@ -239,13 +237,13 @@ class ChildDependent } ``` -`child` hizmetini sınırlandırmadığımızda, tüm `FooDependent`, `BarDependent`, `ParentDependent` ve `ChildDependent` sınıflarının kurucularına sığacak ve otomatik kablolama onu oraya geçirecektir. +Eğer `child` servisini hiçbir şekilde sınırlamazsak, tüm `FooDependent`, `BarDependent`, `ParentDependent` ve `ChildDependent` sınıflarının kurucularına uyar ve otomatik bağlama onu oraya aktarır. -Ancak, `autowired: ChildClass` (veya `self`) kullanarak `ChildClass` 'a otomatik bağlanmasını daraltırsak, otomatik bağlama sadece `ChildDependent` kurucusuna iletir, çünkü `ChildClass` türünde bir argüman gerektirir ve `ChildClass` * `ChildClass` türündedir. Diğer parametreler için belirtilen başka hiçbir tür `ChildClass`'un bir üst kümesi değildir, bu nedenle hizmet aktarılmaz. +Ancak otomatik bağlanmasını `autowired: ChildClass` (veya `self`) kullanarak `ChildClass`'a daraltırsak, otomatik bağlama onu yalnızca `ChildDependent` kurucusuna aktarır, çünkü `ChildClass` tipinde bir argüman gerektirir ve `ChildClass` *'ın* `ChildClass` *tipinde olduğu* geçerlidir. Diğer parametrelerde belirtilen başka hiçbir tip `ChildClass`'ın üst tipi değildir, bu yüzden servis aktarılmaz. -Eğer `autowired: ParentClass` kullanarak bunu `ParentClass` ile sınırlarsak, otomatik kablolama bunu tekrar `ChildDependent` kurucusuna (gerekli tip `ChildClass`, `ParentClass`'un bir üst kümesi olduğundan) ve `ParentDependent` kurucusuna da iletecektir, çünkü `ParentClass` 'un gerekli tipi de eşleşmektedir. +Eğer onu `autowired: ParentClass` kullanarak `ParentClass`'a sınırlarsak, otomatik bağlama onu tekrar `ChildDependent` kurucusuna (çünkü gerekli `ChildClass`, `ParentClass`'ın üst tipidir) ve yeni olarak `ParentDependent` kurucusuna aktarır, çünkü gerekli `ParentClass` tipi de uygundur. -Eğer `FooInterface` ile kısıtlarsak, `ParentDependent` (gerekli tip `ParentClass`, `FooInterface`'un bir üst tipidir) ve `ChildDependent`'a otomatik olarak bağlanmaya devam eder, ancak ek olarak `FooDependent` kurucusuna bağlanır, ancak `BarInterface`, `FooInterface`'un bir üst tipi olmadığı için `BarDependent`'a bağlanmaz. +Eğer onu `FooInterface`'e sınırlarsak, hala `ParentDependent` (gerekli `ParentClass`, `FooInterface`'in üst tipidir) ve `ChildDependent`'e otomatik bağlanır, ancak ek olarak `FooDependent` kurucusuna da bağlanır, ancak `BarDependent`'e bağlanmaz, çünkü `BarInterface`, `FooInterface`'in üst tipi değildir. ```neon services: @@ -253,8 +251,8 @@ services: create: ChildClass autowired: FooInterface - fooDep: FooDependent # servis çocuğunu yapıcıya geçirir - barDep: BarDependent # İSTİSNA OLUŞTURUR, hiçbir hizmet geçemez - parentDep: ParentDependent # hizmet çocuğunu yapıcıya geçirir - childDep: ChildDependent # hizmet çocuğunu yapıcıya geçirir + fooDep: FooDependent # otomatik bağlama kurucuya child aktarır + barDep: BarDependent # İSTİSNA FIRLATIR, hiçbir servis uymuyor + parentDep: ParentDependent # otomatik bağlama kurucuya child aktarır + childDep: ChildDependent # otomatik bağlama kurucuya child aktarır ``` diff --git a/dependency-injection/tr/configuration.texy b/dependency-injection/tr/configuration.texy index 322cb2075a..41b28cddb2 100644 --- a/dependency-injection/tr/configuration.texy +++ b/dependency-injection/tr/configuration.texy @@ -1,32 +1,33 @@ -DI Konteynerini Yapılandırma -**************************** +DI Konteyner Yapılandırması +*************************** .[perex] Nette DI konteyneri için yapılandırma seçeneklerine genel bakış. -Yapılandırma dosyası +Yapılandırma Dosyası ==================== -Nette DI konteynerinin yapılandırma dosyaları kullanılarak kontrol edilmesi kolaydır. Genellikle [NEON formatında |neon:format] yazılırlar. Düzenleme için bu formatı [destekleyen editörler |best-practices:editors-and-tools#ide-editor] kullanmanızı öneririz. +Nette DI konteyneri, yapılandırma dosyaları aracılığıyla kolayca kontrol edilir. Bunlar genellikle [NEON formatı |neon:format] kullanılarak yazılır. Düzenleme için bu formatı [destekleyen düzenleyiciler |best-practices:editors-and-tools#IDE Editörü] öneririz. <pre> -"decorator .[prism-token prism-atrule]":[#Decorator]: "Dekoratör .[prism-token prism-comment]"<br> -"di .[prism-token prism-atrule]":[#DI]: "DI Konteyner .[prism-token prism-comment]"<br> -"extensions .[prism-token prism-atrule]":[#Extensions]: "Ek DI uzantılarını yükleyin .[prism-token prism-comment]"<br> -"includes .[prism-token prism-atrule]":[#Including files]: "Including files .[prism-token prism-comment]"<br> -"parameters .[prism-token prism-atrule]":[#Parameters]: "Parameters .[prism-token prism-comment]"<br> -"search .[prism-token prism-atrule]":[#Search]: "Otomatik hizmet kaydı .[prism-token prism-comment]"<br> -"services .[prism-token prism-atrule]":[services]: "Services .[prism-token prism-comment]" +"decorator .[prism-token prism-atrule]":[#Dekoratör Decorator]: "Dekoratör .[prism-token prism-comment]"<br> +"di .[prism-token prism-atrule]":[#DI]: "DI konteyner .[prism-token prism-comment]"<br> +"extensions .[prism-token prism-atrule]":[#Uzantılar]: "Diğer DI uzantılarının kurulumu .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[#Dosya Dahil Etme]: "Dosya dahil etme .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[#Parametreler]: "Parametreler .[prism-token prism-comment]"<br> +"search .[prism-token prism-atrule]":[#Arama Search]: "Servislerin otomatik kaydı .[prism-token prism-comment]"<br> +"services .[prism-token prism-atrule]":[services]: "Servisler .[prism-token prism-comment]" </pre> -`%`, you must escape it by doubling it to `%%` karakterini içeren bir dize yazmak için. .[note] +.[note] +`%` karakterini içeren bir karakter dizisi yazmak istiyorsanız, onu `%%` olarak iki katına çıkararak kaçış yapmanız gerekir. -Parametreler .[#toc-parameters] -=============================== +Parametreler +============ -Daha sonra hizmet tanımlarının bir parçası olarak kullanılabilecek parametreler tanımlayabilirsiniz. Bu, daha düzenli olarak değiştirmek isteyeceğiniz değerleri ayırmanıza yardımcı olabilir. +Yapılandırmada, daha sonra servis tanımlarının bir parçası olarak kullanılabilecek parametreler tanımlayabilirsiniz. Bu sayede yapılandırmayı daha anlaşılır hale getirebilir veya değişecek değerleri birleştirebilir ve ayırabilirsiniz. ```neon parameters: @@ -35,9 +36,9 @@ parameters: password: secret ``` -Herhangi bir yapılandırma dosyasının başka bir yerinde `%foo%` aracılığıyla `foo` parametresine başvurabilirsiniz. Ayrıca `'%wwwDir%/images'` gibi dizgilerin içinde de kullanılabilirler. +`dsn` parametresine yapılandırmanın herhangi bir yerinde `%dsn%` yazarak başvururuz. Parametreler, `'%wwwDir%/images'` gibi karakter dizileri içinde de kullanılabilir. -Parametrelerin sadece string olması gerekmez, dizi değerleri de olabilirler: +Parametreler yalnızca karakter dizileri veya sayılar olmak zorunda değildir, diziler de içerebilirler: ```neon parameters: @@ -48,32 +49,32 @@ parameters: languages: [cs, en, de] ``` -Tek tuşa `%mailer.user%` olarak başvurabilirsiniz. +Belirli bir anahtara `%mailer.user%` olarak başvururuz. -Kodunuzda, örneğin sınıfınızda herhangi bir parametrenin değerini almanız gerekiyorsa, o zaman bu sınıfa geçirin. Örneğin, yapıcıda. Sınıfların parametre değerlerini sorgulayabileceği global bir yapılandırma nesnesi yoktur. Bu, bağımlılık enjeksiyonu ilkesine aykırı olacaktır. +Kodunuzda, örneğin bir sınıfta, herhangi bir parametrenin değerini öğrenmeniz gerekiyorsa, onu bu sınıfa aktarın. Örneğin kurucuda. Parametre değerleri için sınıfların sorgulayacağı, yapılandırmayı temsil eden global bir nesne yoktur. Bu, bağımlılık enjeksiyonu ilkesinin ihlali olurdu. -Hizmetler .[#toc-services] -========================== +Servisler +========= -[Ayrı bir bölüme |services] bakınız. +Bkz. [ayrı bölüm |services]. -Dekoratör .[#toc-decorator] -=========================== +Dekoratör (Decorator) +===================== -Belirli bir türdeki tüm hizmetleri toplu olarak nasıl düzenleyebilirim? Belirli bir ortak atadan miras kalan tüm sunucular için belirli bir yöntemi çağırmanız mı gerekiyor? İşte dekoratörün geldiği yer burasıdır. +Belirli bir tipteki tüm servisleri toplu olarak nasıl düzenlersiniz? Örneğin, belirli bir ortak atadan kalıtım alan tüm presenter'larda belirli bir metodu çağırmak? İşte bunun için dekoratör var. ```neon decorator: - # bu sınıfın veya arayüzün örneği olan tüm hizmetler için - App\Presenters\BasePresenter: + # bu sınıfın veya arayüzün örneği olan tüm servislerde + App\Presentation\BasePresenter: setup: - - setProjectId(10) # bu yöntemi çağır - - $absoluteUrls = true # ve değişkeni ayarlayın + - setProjectId(10) # bu metodu çağır + - $absoluteUrls = true # ve değişkeni ayarla ``` -Dekoratör ayrıca [etiketleri |services#Tags] ayarlamak veya [enjeksiyon modunu |services#Inject Mode] açmak için de kullanılabilir. +Dekoratör ayrıca [etiketleri |services#Etiketler Tags] ayarlamak veya [inject modunu |services#Inject Modu] açmak için de kullanılabilir. ```neon decorator: @@ -90,63 +91,75 @@ DI konteynerinin teknik ayarları. ```neon di: - # Tracy Bar'da DIC gösteriyor mu? - debugger: ... # (bool) varsayılan değer true + # DIC'yi Tracy Bar'da göster? + debugger: ... # (bool) varsayılan true'dur - # asla otomatik bağlamadığınız parametre türleri + # asla otomatik bağlanmayacak parametre tipleri excluded: ... # (string[]) - # DI konteynerinin miras aldığı sınıf - parentClass: ... # (string) varsayılan olarak Nette\DI\Container + # servislerin tembel oluşturulmasına izin ver? + lazy: ... # (bool) varsayılan false'dur + + # DI konteynerinin kalıtım aldığı sınıf + parentClass: ... # (string) varsayılan Nette\DI\Container'dır ``` -Meta Veri Aktarımı .[#toc-metadata-export] ------------------------------------------- +Tembel Servisler .{data-version:3.2.4} +-------------------------------------- + +`lazy: true` ayarı, servislerin tembel (ertelenmiş) oluşturulmasını etkinleştirir. Bu, servislerin DI konteynerinden talep edildiği anda değil, ilk kullanıldıkları anda gerçekten oluşturulduğu anlamına gelir. Bu, uygulamanın başlangıcını hızlandırabilir ve bellek gereksinimlerini azaltabilir, çünkü yalnızca ilgili istekte gerçekten ihtiyaç duyulan servisler oluşturulur. + +Belirli bir servis için tembel oluşturma [değiştirilebilir |services#Lazy Servisler]. + +.[note] +Tembel nesneler yalnızca kullanıcı sınıfları için kullanılabilir, dahili PHP sınıfları için kullanılamaz. PHP 8.4 veya daha yenisini gerektirir. + -DI konteyner sınıfı ayrıca çok fazla meta veri içerir. Meta veri dışa aktarımını azaltarak bunu azaltabilirsiniz. +Meta Veri Dışa Aktarma +---------------------- + +DI konteyner sınıfı ayrıca birçok meta veri içerir. Meta veri dışa aktarımını azaltarak onu küçültebilirsiniz. ```neon di: export: - # parametreleri dışa aktarmak için? - parameters: false # (bool) varsayılan değer true + # parametreleri dışa aktar? + parameters: false # (bool) varsayılan true'dur - # etiketleri dışa aktarın ve hangileri? - tags: # (string[]|bool) varsayılan tüm + # etiketleri ve hangilerini dışa aktar? + tags: # (string[]|bool) varsayılan hepsi - event.subscriber - # otomatik kablolama için verileri dışa aktar ve hangisi? - types: # (string[]|bool) varsayılan tüm + # otomatik bağlama için verileri ve hangilerini dışa aktar? + types: # (string[]|bool) varsayılan hepsi - Nette\Database\Connection - Symfony\Component\Console\Application ``` -`$container->parameters` dizisini kullanmıyorsanız, parametre dışa aktarımını devre dışı bırakabilirsiniz. Ayrıca, yalnızca `$container->findByTag(...)` yöntemini kullanarak hizmet aldığınız etiketleri dışa aktarabilirsiniz. -Yöntemi hiç çağırmazsanız, `false` ile etiket dışa aktarımını tamamen devre dışı bırakabilirsiniz. +Eğer `$container->getParameters()` dizisini kullanmıyorsanız, parametre dışa aktarımını kapatabilirsiniz. Ayrıca, yalnızca `$container->findByTag(...)` metoduyla servis aldığınız etiketleri dışa aktarabilirsiniz. Eğer metodu hiç çağırmıyorsanız, etiket dışa aktarımını `false` ile tamamen kapatabilirsiniz. -Kullandığınız sınıfları `$container->getByType()` yöntemine parametre olarak belirterek [otomatik kablolama |autowiring] için meta verileri önemli ölçüde azaltabilirsiniz. -Ve yine, yöntemi hiç çağırmazsanız (veya `Nette\Application\Application` adresini almak için yalnızca [bootstrap |application:bootstrap] içinde çağırırsanız), `false` ile dışa aktarmayı tamamen devre dışı bırakabilirsiniz. +`$container->getByType()` metodunun parametresi olarak kullandığınız sınıfları belirterek [otomatik bağlama |autowiring] için meta veriyi önemli ölçüde azaltabilirsiniz. Ve yine, eğer metodu hiç çağırmıyorsanız (veya yalnızca `Nette\Application\Application` almak için [bootstrap |application:bootstrapping] içinde çağırıyorsanız), dışa aktarımı `false` ile tamamen kapatabilirsiniz. -Uzantılar .[#toc-extensions] -============================ +Uzantılar +========= -Diğer DI uzantılarının kaydı. Bu şekilde, örneğin `Dibi\Bridges\Nette\DibiExtension22` DI uzantısını `dibi` adı altında ekleriz: +Diğer DI uzantılarının kaydı. Bu şekilde örneğin `Dibi\Bridges\Nette\DibiExtension22` DI uzantısını `dibi` adı altında ekleriz ```neon extensions: dibi: Dibi\Bridges\Nette\DibiExtension22 ``` -Daha sonra `dibi` adlı bölümde yapılandırıyoruz: +Daha sonra onu `dibi` bölümünde yapılandırırız: ```neon dibi: host: localhost ``` -Parametreli bir uzantı sınıfı da ekleyebilirsiniz: +Parametreleri olan bir sınıf da uzantı olarak eklenebilir: ```neon extensions: @@ -154,10 +167,10 @@ extensions: ``` -Dosyalar Dahil .[#toc-including-files] -====================================== +Dosya Dahil Etme +================ -Ek yapılandırma dosyaları `includes` bölümüne eklenebilir: +Diğer yapılandırma dosyalarını `includes` bölümüne ekleyebiliriz: ```neon includes: @@ -166,7 +179,7 @@ includes: - presenters.neon ``` -`parameters.php` adı bir yazım hatası değildir, yapılandırma bir dizi olarak döndüren bir PHP dosyasına da yazılabilir: +`parameters.php` adı bir yazım hatası değildir, yapılandırma PHP dosyasında da yazılabilir ve bir dizi olarak döndürülebilir: ```php <?php @@ -179,32 +192,27 @@ return [ ]; ``` -Aynı anahtarlara sahip öğeler yapılandırma dosyalarında görünürse, diziler söz konusu olduğunda bunların [üzerine yazılır veya birleştirilir |#Merging]. Daha sonra dahil edilen dosyanın bir öncekinden daha yüksek önceliği vardır. `includes` bölümünün listelendiği dosya, içine dahil edilen dosyalardan daha yüksek önceliğe sahiptir. +Yapılandırma dosyalarında aynı anahtarlara sahip öğeler görünürse, üzerine yazılır veya [diziler durumunda birleştirilir |#Birleştirme]. Daha sonra dahil edilen dosya, öncekinden daha yüksek önceliğe sahiptir. `includes` bölümünün belirtildiği dosya, içine dahil edilen dosyalardan daha yüksek önceliğe sahiptir. -Arama .[#toc-search] -==================== +Arama (Search) +============== -Hizmetlerin DI konteynerine otomatik olarak eklenmesi, çalışmayı son derece keyifli hale getirir. Nette, kapsayıcıya otomatik olarak sunucuları ekler, ancak diğer sınıfları kolayca ekleyebilirsiniz. +Servislerin DI konteynerine otomatik olarak eklenmesi işi son derece keyifli hale getirir. Nette, presenter'ları otomatik olarak konteynere ekler, ancak diğer herhangi bir sınıfı da kolayca eklemek mümkündür. -Sınıfların hangi dizinlerde (ve alt dizinlerde) aranması gerektiğini belirtmeniz yeterlidir: +Sadece hangi dizinlerde (ve alt dizinlerde) sınıfları araması gerektiğini belirtmek yeterlidir: ```neon search: - # bölüm adlarını kendiniz seçersiniz - myForms: - in: %appDir%/Forms - - model: - in: %appDir%/Model + - in: %appDir%/Forms + - in: %appDir%/Model ``` -Ancak genellikle, tüm sınıfları ve arayüzleri eklemek istemeyiz, bu yüzden onları filtreleyebiliriz: +Ancak genellikle tüm sınıfları ve arayüzleri eklemek istemeyiz, bu yüzden onları filtreleyebiliriz: ```neon search: - myForms: - in: %appDir%/Forms + - in: %appDir%/Forms # dosya adına göre filtreleme (string|string[]) files: @@ -215,48 +223,49 @@ search: - *Factory ``` -Ya da aşağıdaki sınıflardan en az birini miras alan veya uygulayan sınıfları seçebiliriz: +Veya belirtilen sınıflardan en az birini kalıtım alan veya uygulayan sınıfları seçebiliriz: ```neon search: - myForms: + - in: %appDir% extends: - App\*Form implements: - App\*FormInterface ``` -Ayrıca negatif kurallar da tanımlayabilirsiniz, yani sınıf adı maskeleri veya ataları ve bunlara uymaları halinde hizmet DI konteynerine eklenmeyecektir: +Ayrıca dışlama kuralları da tanımlanabilir, yani sınıf adı maskeleri veya kalıtımsal atalar, eğer uyuyorsa servis DI konteynerine eklenmez: ```neon search: - myForms: + - in: %appDir% exclude: + files: ... classes: ... extends: ... implements: ... ``` -Eklenen hizmetler için etiketler belirlenebilir: +Tüm servislere etiketler atanabilir: ```neon search: - myForms: + - in: %appDir% tags: ... ``` -Birleştirme .[#toc-merging] -=========================== +Birleştirme +=========== -Aynı anahtarlara sahip öğeler daha fazla yapılandırma dosyasında görünüyorsa, diziler söz konusu olduğunda bunların üzerine yazılır veya birleştirilir. Daha sonra dahil edilen dosya daha yüksek önceliğe sahiptir. +Birden fazla yapılandırma dosyasında aynı anahtarlara sahip öğeler görünürse, üzerine yazılır veya diziler durumunda birleştirilir. Daha sonra dahil edilen dosya, öncekinden daha yüksek önceliğe sahiptir. <table class=table> <tr> <th width=33%>config1.neon</th> <th width=33%>config2.neon</th> - <th>Sonuç</th> + <th>sonuç</th> </tr> <tr> <td> @@ -283,13 +292,13 @@ items: </tr> </table> -Belirli bir dizinin birleştirilmesini önlemek için dizinin adından hemen sonra ünlem işareti kullanın: +Dizilerde, anahtar adından sonra ünlem işareti belirterek birleştirmeyi önleyebilirsiniz: <table class=table> <tr> <th width=33%>config1.neon</th> <th width=33%>config2.neon</th> - <th>Sonuç</th> + <th>sonuç</th> </tr> <tr> <td> @@ -313,3 +322,5 @@ items: </td> </tr> </table> + +{{maintitle: Bağımlılık Enjeksiyonu Yapılandırması}} diff --git a/dependency-injection/tr/container.texy b/dependency-injection/tr/container.texy index 4f5c60873e..520e53b491 100644 --- a/dependency-injection/tr/container.texy +++ b/dependency-injection/tr/container.texy @@ -2,15 +2,15 @@ DI Konteyner Nedir? ******************* .[perex] -Dependency injection container (DIC), nesneleri örnekleyebilen ve yapılandırabilen bir sınıftır. +Bağımlılık enjeksiyonu konteyneri (DIC), nesneleri örnekleyebilen ve yapılandırabilen bir sınıftır. -Sizi şaşırtabilir, ancak çoğu durumda bağımlılık enjeksiyonundan (kısaca DI) yararlanmak için bir bağımlılık enjeksiyon konteynerine ihtiyacınız yoktur. Sonuçta, [önceki bölümde |introduction] bile DI'nin belirli örneklerini gösterdik ve hiçbir kapsayıcıya ihtiyaç duyulmadı. +Belki sizi şaşırtacak ama birçok durumda bağımlılık enjeksiyonunun (kısaca DI) avantajlarından yararlanmak için bir bağımlılık enjeksiyonu konteynerine ihtiyacınız yoktur. Sonuçta, [giriş bölümü |introduction] içinde bile DI'yi somut örneklerle gösterdik ve hiçbir konteynere gerek yoktu. -Bununla birlikte, çok sayıda bağımlılığa sahip çok sayıda farklı nesneyi yönetmeniz gerekiyorsa, bir bağımlılık enjeksiyon konteyneri gerçekten yararlı olacaktır. Bu belki de bir çerçeve üzerine inşa edilmiş web uygulamaları için geçerlidir. +Ancak, birçok bağımlılığa sahip çok sayıda farklı nesneyi yönetmeniz gerekiyorsa, bir bağımlılık enjeksiyonu konteyneri gerçekten faydalı olacaktır. Bu, örneğin bir framework üzerine kurulu web uygulamaları için geçerlidir. -Bir önceki bölümde `Article` ve `UserController` sınıflarını tanıtmıştık. Her ikisinin de bazı bağımlılıkları vardır, yani veritabanı ve fabrika `ArticleFactory`. Ve bu sınıflar için şimdi bir konteyner oluşturacağız. Elbette, bu kadar basit bir örnek için bir konteynere sahip olmak mantıklı değildir. Ancak nasıl göründüğünü ve çalıştığını göstermek için bir tane oluşturacağız. +Önceki bölümde `Article` ve `UserController` sınıflarını tanıttık. Her ikisinin de bazı bağımlılıkları var, yani veritabanı ve `ArticleFactory` fabrikası. Ve şimdi bu sınıflar için bir konteyner oluşturacağız. Elbette, böylesine basit bir örnek için bir konteynere sahip olmanın anlamı yok. Ama nasıl göründüğünü ve çalıştığını göstermek için onu oluşturacağız. -İşte yukarıdaki örnek için basit bir kodlanmış kapsayıcı: +İşte belirtilen örnek için basit, sabit kodlanmış (hardcoded) bir konteyner: ```php class Container @@ -32,16 +32,16 @@ class Container } ``` -Kullanım şu şekilde olacaktır: +Kullanım şöyle görünürdü: ```php $container = new Container; $controller = $container->createUserController(); ``` -Konteynerden sadece nesneyi isteriz ve artık nesnenin nasıl oluşturulacağı veya bağımlılıklarının ne olduğu hakkında hiçbir şey bilmemize gerek yoktur; konteyner bunların hepsini bilir. Bağımlılıklar konteyner tarafından otomatik olarak enjekte edilir. Bu onun gücüdür. +Konteynere sadece nesneyi sorarız ve artık onu nasıl oluşturacağımızı ve bağımlılıklarının ne olduğunu bilmemize gerek yoktur; tüm bunları konteyner bilir. Bağımlılıklar konteyner tarafından otomatik olarak enjekte edilir. Gücü buradadır. -Şimdiye kadar konteynerde her şey kodlanmıştı. Bu yüzden bir sonraki adımı atıyoruz ve konteyneri gerçekten kullanışlı hale getirmek için parametreler ekliyoruz: +Konteyner şimdilik tüm verileri sabit (hardcoded) olarak yazmıştır. Bu yüzden bir sonraki adımı atacağız ve konteynerin gerçekten kullanışlı olması için parametreler ekleyeceğiz: ```php class Container @@ -70,9 +70,9 @@ $container = new Container([ ]); ``` -Dikkatli okuyucular bir sorun fark etmiş olabilirler. Ne zaman bir nesne alsam `UserController`, yeni bir örnek `ArticleFactory` ve veritabanı da oluşturuluyor. Bunu kesinlikle istemiyoruz. +Dikkatli okuyucular belki de belirli bir sorunu fark etmişlerdir. `UserController` nesnesini her aldığımda, yeni bir `ArticleFactory` örneği ve veritabanı da oluşturulur. Bunu kesinlikle istemiyoruz. -Bu yüzden aynı örnekleri tekrar tekrar döndürecek bir `getService()` yöntemi ekliyoruz: +Bu yüzden, her zaman aynı örnekleri döndürecek olan `getService()` metodunu ekleyeceğiz: ```php class Container @@ -87,7 +87,7 @@ class Container public function getService(string $name): object { if (!isset($this->services[$name])) { - // getService('Database') createDatabase() işlevini çağırır + // getService('Database') createDatabase() çağıracak $method = 'create' . $name; $this->services[$name] = $this->$method(); } @@ -98,9 +98,9 @@ class Container } ``` -Örneğin `$container->getService('Database')` adresine yapılan ilk çağrıda `createDatabase()` bir veritabanı nesnesi oluşturacak, bu nesneyi `$services` dizisinde saklayacak ve bir sonraki çağrıda doğrudan geri döndürecektir. +Örneğin `$container->getService('Database')` ilk çağrıldığında, `createDatabase()`'den veritabanı nesnesini oluşturmasını ister, onu `$services` dizisine kaydeder ve bir sonraki çağrıda doğrudan onu döndürür. -Ayrıca konteynerin geri kalanını `getService()` kullanacak şekilde değiştiriyoruz: +Konteynerin geri kalanını da `getService()` kullanacak şekilde düzenleyeceğiz: ```php class Container @@ -119,9 +119,9 @@ class Container } ``` -Bu arada, hizmet terimi konteyner tarafından yönetilen herhangi bir nesneyi ifade eder. Bu nedenle yöntem adı `getService()`. +Bu arada, servis terimi konteyner tarafından yönetilen herhangi bir nesneyi ifade eder. Bu yüzden metot adı `getService()`'dir. -Tamamdır. Tamamen işlevsel bir DI konteynerimiz var! Ve onu kullanabiliriz: +Bitti. Tamamen işlevsel bir DI konteynerimiz var! Ve onu kullanabiliriz: ```php $container = new Container([ @@ -134,6 +134,9 @@ $controller = $container->getService('UserController'); $database = $container->getService('Database'); ``` -Gördüğünüz gibi, bir DIC yazmak zor değil. Nesnelerin kendilerinin bir kapsayıcının onları yarattığını bilmemeleri dikkat çekicidir. Böylece, PHP'de herhangi bir nesneyi kaynak kodunu etkilemeden bu şekilde oluşturmak mümkündür. +Gördüğünüz gibi, bir DIC yazmak karmaşık bir şey değil. Nesnelerin kendilerinin bir konteyner tarafından oluşturulduğunu bilmediklerini hatırlatmakta fayda var. Bu nedenle, kaynak koduna müdahale etmeden PHP'deki herhangi bir nesneyi bu şekilde oluşturmak mümkündür. -Bir konteyner sınıfını manuel olarak oluşturmak ve sürdürmek oldukça hızlı bir şekilde kabusa dönüşebilir. Bu nedenle, bir sonraki bölümde kendini neredeyse otomatik olarak oluşturabilen ve güncelleyebilen [Nette DI Container |nette-container] hakkında konuşacağız. +Konteyner sınıfını manuel olarak oluşturmak ve bakımını yapmak oldukça hızlı bir şekilde bir kabusa dönüşebilir. Bu yüzden bir sonraki bölümde, neredeyse kendi kendine üretebilen ve güncelleyebilen [Nette DI Konteyner |nette-container] hakkında konuşacağız. + + +{{maintitle: Bağımlılık enjeksiyonu konteyneri nedir?}} diff --git a/dependency-injection/tr/extensions.texy b/dependency-injection/tr/extensions.texy index a3036620d4..0822983ae3 100644 --- a/dependency-injection/tr/extensions.texy +++ b/dependency-injection/tr/extensions.texy @@ -1,17 +1,17 @@ -Nette DI için Uzantılar Oluşturma -********************************* +Nette DI için Uzantı Oluşturma +****************************** .[perex] -Yapılandırma dosyalarına ek olarak bir DI konteyneri oluşturmak, *uzantılar* olarak adlandırılanları da etkiler. Bunları `extensions` bölümündeki yapılandırma dosyasında etkinleştiriyoruz. +DI konteynerinin oluşturulması, yapılandırma dosyalarının yanı sıra *uzantılar* olarak adlandırılanlar tarafından da etkilenir. Bunları yapılandırma dosyasında `extensions` bölümünde etkinleştiririz. -`BlogExtension` sınıfı tarafından temsil edilen uzantıyı `blog` adıyla bu şekilde ekleriz: +Bu şekilde, `BlogExtension` sınıfı tarafından temsil edilen uzantıyı `blog` adı altında ekleriz: ```neon extensions: blog: BlogExtension ``` -Her derleyici uzantısı [api:Nette\DI\CompilerExtension] adresinden miras alır ve DI derlemesi sırasında çağrılan aşağıdaki yöntemleri uygulayabilir: +Her derleyici uzantısı [api:Nette\DI\CompilerExtension]'dan kalıtım alır ve DI konteynerinin oluşturulması sırasında sırayla çağrılan aşağıdaki metotları uygulayabilir: 1. getConfigSchema() 2. loadConfiguration() @@ -22,18 +22,18 @@ Her derleyici uzantısı [api:Nette\DI\CompilerExtension] adresinden miras alır getConfigSchema() .[method] =========================== -Bu yöntem ilk olarak çağrılır. Yapılandırma parametrelerini doğrulamak için kullanılan şemayı tanımlar. +Bu metot ilk olarak çağrılır. Yapılandırma parametrelerinin doğrulanması için şemayı tanımlar. -Uzantılar, adı uzantının eklendiği bölümle aynı olan bir bölümde yapılandırılır, örneğin `blog`. +Uzantıyı, uzantının eklendiği adla aynı olan bölümde, yani `blog`'da yapılandırırız: ```neon -# uzantımla aynı isim +# uzantı adıyla aynı blog: postsPerPage: 10 - comments: false + allowComments: false ``` -Türleri, kabul edilen değerleri ve muhtemelen varsayılan değerleri de dahil olmak üzere tüm yapılandırma seçeneklerini tanımlayan bir şema tanımlayacağız: +Tipleri, izin verilen değerleri ve isteğe bağlı olarak varsayılan değerleri de dahil olmak üzere tüm yapılandırma seçeneklerini açıklayan bir şema oluştururuz: ```php use Nette\Schema\Expect; @@ -50,9 +50,9 @@ class BlogExtension extends Nette\DI\CompilerExtension } ``` -Belgeler için [Şema |schema:] 'ya bakın. Ayrıca, `dynamic()` adresini kullanarak hangi seçeneklerin [dinamik |application:bootstrap#Dynamic Parameters] olabileceğini belirtebilirsiniz, örneğin `Expect::int()->dynamic()`. +Dokümantasyonu [Schema |schema:] sayfasında bulabilirsiniz. Ayrıca, `dynamic()` kullanarak hangi seçeneklerin [dinamik |application:bootstrapping#Dinamik Parametreler] olabileceğini belirleyebilirsiniz, örn. `Expect::int()->dynamic()`. -Yapılandırmaya `$this->config`, bir nesne olan `stdClass` aracılığıyla erişiriz: +Yapılandırmaya, bir `stdClass` nesnesi olan `$this->config` değişkeni aracılığıyla erişiriz: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -71,7 +71,7 @@ class BlogExtension extends Nette\DI\CompilerExtension loadConfiguration() .[method] ============================= -Bu yöntem konteynere hizmet eklemek için kullanılır. Bu işlem [api:Nette\DI\ContainerBuilder] tarafından yapılır: +Konteynere servis eklemek için kullanılır. Bunun için [api:Nette\DI\ContainerBuilder] kullanılır: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -86,19 +86,19 @@ class BlogExtension extends Nette\DI\CompilerExtension } ``` -Kural, bir uzantı tarafından eklenen hizmetlerin önüne kendi adını eklemektir, böylece isim çakışmaları ortaya çıkmaz. Bu `prefix()` tarafından yapılır, bu nedenle uzantı 'blog' olarak adlandırılırsa, hizmet `blog.articles` olarak adlandırılacaktır. +Kural, uzantı tarafından eklenen servisleri adıyla ön eklemektir, böylece isim çakışmaları olmaz. Bunu `prefix()` metodu yapar, yani uzantı adı `blog` ise, servis `blog.articles` adını taşır. -Bir hizmeti yeniden adlandırmamız gerekirse, geriye dönük uyumluluğu korumak için orijinal adıyla bir takma ad oluşturabiliriz. Benzer şekilde, Nette'in örneğin `routing.router` için yaptığı şey budur, bu da daha önceki `router` adı altında da mevcuttur. +Bir servisi yeniden adlandırmamız gerekirse, geriye dönük uyumluluğu korumak için orijinal adla bir takma ad (alias) oluşturabiliriz. Nette bunu benzer şekilde yapar, örn. önceki adı `router` altında da mevcut olan `routing.router` servisi için. ```php $builder->addAlias('router', 'routing.router'); ``` -Hizmetleri Bir Dosyadan Alma .[#toc-retrieve-services-from-a-file] ------------------------------------------------------------------- +Dosyadan Servis Yükleme +----------------------- -ContainerBuilder API'sini kullanarak hizmetler oluşturabiliriz, ancak bunları tanıdık NEON yapılandırma dosyası ve `services` bölümü aracılığıyla da ekleyebiliriz. `@extension` öneki mevcut uzantıyı temsil eder. +Servisleri yalnızca ContainerBuilder sınıfının API'sini kullanarak değil, aynı zamanda yapılandırma dosyasında `services` bölümünde kullanılan bilinen yazımla da oluşturabiliriz. `@extension` ön eki mevcut uzantıyı temsil eder. ```neon services: @@ -112,7 +112,7 @@ services: create: MyBlog\Components\ArticlesList(@extension.articles) ``` -Hizmetleri bu şekilde ekleyeceğiz: +Servisleri yükleriz: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -121,7 +121,7 @@ class BlogExtension extends Nette\DI\CompilerExtension { $builder = $this->getContainerBuilder(); - // uzantı için yapılandırma dosyasını yükle + // uzantı için yapılandırma dosyasını yükleme $this->compiler->loadDefinitionsFromConfig( $this->loadFromFile(__DIR__ . '/blog.neon')['services'], ); @@ -133,7 +133,7 @@ class BlogExtension extends Nette\DI\CompilerExtension beforeCompile() .[method] ========================= -Kapsayıcı, `loadConfiguration` yöntemlerinde bireysel uzantılar tarafından eklenen tüm hizmetleri ve kullanıcı yapılandırma dosyalarını içerdiğinde yöntem çağrılır. Birleştirmenin bu aşamasında, daha sonra hizmet tanımlarını değiştirebilir veya aralarına bağlantılar ekleyebiliriz. Hizmetleri etiketlere göre aramak için `findByTag()` yöntemini veya sınıf ya da arayüze göre aramak için `findByType()` yöntemini kullanabilirsiniz. +Metot, konteynerin `loadConfiguration` metotlarında bireysel uzantılar tarafından eklenen tüm servisleri ve ayrıca kullanıcı yapılandırma dosyalarını içerdiği anda çağrılır. Bu derleme aşamasında, servis tanımlarını düzenleyebilir veya aralarındaki bağlantıları tamamlayabiliriz. Konteynerdeki servisleri etiketlere göre aramak için `findByTag()` metodunu, sınıfa veya arayüze göre aramak için ise `findByType()` metodunu kullanabiliriz. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -153,7 +153,7 @@ class BlogExtension extends Nette\DI\CompilerExtension afterCompile() .[method] ======================== -Bu aşamada, konteyner sınıfı zaten bir [ClassType |php-generator:#classes] nesnesi olarak üretilmiştir, servisin oluşturduğu tüm yöntemleri içerir ve PHP dosyası olarak önbelleğe alınmaya hazırdır. Bu noktada ortaya çıkan sınıf kodunu hala düzenleyebiliriz. +Bu aşamada, konteyner sınıfı zaten bir [ClassType |php-generator:#Sınıflar] nesnesi şeklinde üretilmiştir, servisleri oluşturan tüm metotları içerir ve önbelleğe yazılmaya hazırdır. Sonuç kodunu bu noktada hala düzenleyebiliriz. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -167,24 +167,24 @@ class BlogExtension extends Nette\DI\CompilerExtension ``` -$başlatma .[wiki-method] -======================== +$initialization .[method] +========================= -Yapılandırıcı, [addBody() yöntemi |php-generator:#method-and-function-body] kullanılarak bir `$this->initialization` nesnesine yazılarak oluşturulan [konteyner oluşturulduktan |application:bootstrap#index.php] sonra başlatma kodunu çağırır. +Configurator sınıfı, [konteyner oluşturulduktan sonra |application:bootstrapping#index.php] `$this->initialization` nesnesine [addBody() metodu |php-generator:#Metot ve Fonksiyon Gövdeleri] kullanılarak yazılan başlatma kodunu çağırır. -Bir oturumun nasıl başlatılacağına veya başlatma kodunu kullanarak `run` etiketine sahip hizmetlerin nasıl başlatılacağına dair bir örnek göstereceğiz: +Örneğin, başlatma koduyla oturumu nasıl başlatacağımızı veya `run` etiketine sahip servisleri nasıl çalıştıracağımızı gösteren bir örnek: ```php class BlogExtension extends Nette\DI\CompilerExtension { public function loadConfiguration() { - // otomatik oturum başlatma + // oturumun otomatik başlatılması if ($this->config->session->autoStart) { $this->initialization->addBody('$this->getService("session")->start()'); } - // 'run' etiketine sahip hizmetler konteyner oluşturulduktan sonra oluşturulmalıdır + // run etiketli servisler konteyner örneklendikten sonra oluşturulmalıdır $builder = $this->getContainerBuilder(); foreach ($builder->findByTag('run') as $name => $foo) { $this->initialization->addBody('$this->getService(?);', [$name]); diff --git a/dependency-injection/tr/factory.texy b/dependency-injection/tr/factory.texy index b9552dc9e2..80def9a73c 100644 --- a/dependency-injection/tr/factory.texy +++ b/dependency-injection/tr/factory.texy @@ -1,12 +1,12 @@ -Üretilen Fabrikalar -******************* +Üretilmiş Fabrikalar +******************** .[perex] -Nette DI, arayüze dayalı olarak otomatik olarak fabrika kodu oluşturabilir, bu da sizi kod yazmaktan kurtarır. +Nette DI, arayüzlere dayalı olarak fabrika kodunu otomatik olarak üretebilir, bu da size kod yazmaktan tasarruf sağlar. -Fabrika, nesneleri oluşturan ve yapılandıran bir sınıftır. Bu nedenle bağımlılıklarını da onlara aktarır. Lütfen fabrikaları kullanmanın belirli bir yolunu tanımlayan ve bu konuyla ilgili olmayan *factory method* tasarım kalıbı ile karıştırmayın. +Fabrika, nesneleri üreten ve yapılandıran bir sınıftır. Dolayısıyla onlara bağımlılıklarını da aktarır. Lütfen bunu, fabrikaların belirli bir kullanım şeklini açıklayan ve bu konuyla ilgisi olmayan *factory method* tasarım deseniyle karıştırmayın. - [Giriş bölümünde |introduction#factory] böyle bir fabrikanın neye benzediğini göstermiştik: +Böyle bir fabrikanın nasıl göründüğünü [giriş bölümü |introduction#Fabrika] içinde gösterdik: ```php class ArticleFactory @@ -23,7 +23,7 @@ class ArticleFactory } ``` -Nette DI otomatik olarak fabrika kodu üretebilir. Tek yapmanız gereken bir arayüz oluşturmaktır ve Nette DI bir uygulama üretecektir. Arayüz, `create` adında tam olarak bir yönteme sahip olmalı ve bir dönüş türü bildirmelidir: +Nette DI, fabrika kodunu otomatik olarak üretebilir. Tek yapmanız gereken bir arayüz oluşturmaktır ve Nette DI uygulamayı üretecektir. Arayüzün tam olarak `create` adında bir metodu olmalı ve dönüş değeri tipini bildirmelidir: ```php interface ArticleFactory @@ -32,7 +32,7 @@ interface ArticleFactory } ``` -Dolayısıyla `ArticleFactory` fabrikasının `Article` nesnelerini oluşturan bir `create` yöntemi vardır. Örneğin `Article` sınıfı aşağıdaki gibi görünebilir: +Yani `ArticleFactory` fabrikasının, `Article` nesneleri oluşturan bir `create` metodu vardır. `Article` sınıfı örneğin şöyle görünebilir: ```php class Article @@ -44,16 +44,16 @@ class Article } ``` -Fabrikayı yapılandırma dosyasına ekleyin: +Fabrikayı yapılandırma dosyasına ekleriz: ```neon services: - ArticleFactory ``` -Nette DI ilgili fabrika uygulamasını oluşturacaktır. +Nette DI, fabrikanın ilgili uygulamasını üretecektir. -Böylece, fabrikayı kullanan kodda, nesneyi arayüze göre talep ederiz ve Nette DI üretilen uygulamayı kullanır: +Fabrikayı kullanan kodda, nesneyi arayüze göre talep ederiz ve Nette DI üretilen uygulamayı kullanır: ```php class UserController @@ -65,17 +65,17 @@ class UserController public function foo() { - // fabrikanın bir nesne oluşturmasına izin verin + // fabrikanın nesneyi oluşturmasına izin veririz $article = $this->articleFactory->create(); } } ``` -Parametrelendirilmiş Fabrika .[#toc-parameterized-factory] -========================================================== +Parametreli Fabrika +=================== -`create` fabrika yöntemi, daha sonra kurucuya aktaracağı parametreleri kabul edebilir. Örneğin, `Article` sınıfına bir makale yazarı kimliği ekleyelim: +Fabrika metodu `create`, daha sonra kurucuya aktarılacak parametreleri kabul edebilir. Örneğin, `Article` sınıfına makale yazarının ID'sini ekleyelim: ```php class Article @@ -88,7 +88,7 @@ class Article } ``` -Ayrıca fabrikaya parametre de ekleyeceğiz: +Parametreyi fabrikaya da ekleriz: ```php interface ArticleFactory @@ -97,13 +97,13 @@ interface ArticleFactory } ``` -Yapıcıdaki parametre ve fabrikadaki parametre aynı ada sahip olduğundan, Nette DI bunları otomatik olarak geçirecektir. +Kurucudaki parametre ile fabrikadaki parametrenin aynı adı taşıması sayesinde, Nette DI bunları tamamen otomatik olarak aktarır. -Gelişmiş Tanım .[#toc-advanced-definition] -========================================== +Gelişmiş Tanım +============== -Tanım, `implement` tuşu kullanılarak çok satırlı olarak da yazılabilir: +Tanım, `implement` anahtarını kullanarak çok satırlı bir formda da yazılabilir: ```neon services: @@ -111,9 +111,9 @@ services: implement: ArticleFactory ``` -Bu uzun şekilde yazarken, normal hizmetlerde olduğu gibi `arguments` anahtarındaki kurucu için ek argümanlar ve `setup` kullanarak ek yapılandırma sağlamak mümkündür. +Bu daha uzun yolla yazarken, normal servislerde olduğu gibi `arguments` anahtarında kurucu için ek argümanlar ve `setup` kullanarak ek yapılandırma belirtmek mümkündür. -Örnek: `create()` yöntemi `$authorId` parametresini kabul etmeseydi, yapılandırmada `Article` yapıcısına aktarılacak sabit bir değer belirtebilirdik: +Örnek: Eğer `create()` metodu `$authorId` parametresini kabul etmeseydi, yapılandırmada `Article` kurucusuna aktarılacak sabit bir değer belirtebilirdik: ```neon services: @@ -123,7 +123,7 @@ services: authorId: 123 ``` -Ya da tersine, eğer `create()` parametre kabul etseydi `$authorId` ancak bu parametre kurucunun bir parçası olmasaydı ve `Article::setAuthorId()` yöntemi tarafından aktarılsaydı, bu parametreye `setup` bölümünde atıfta bulunacaktık: +Veya tam tersi, eğer `create()` `$authorId` parametresini kabul etseydi, ancak kurucunun bir parçası olmasaydı ve `Article::setAuthorId()` metoduyla aktarılsaydı, ona `setup` bölümünde başvururduk: ```neon services: @@ -134,15 +134,14 @@ services: ``` -Accessor .[#toc-accessor] -========================= +Erişimci (Accessor) +=================== -Nette, fabrikaların yanı sıra erişimci adı verilen nesneler de üretebilir. Erişimci, DI konteynerinden belirli bir hizmeti döndüren `get()` yöntemine sahip bir nesnedir. Birden fazla `get()` çağrısı her zaman aynı örneği döndürecektir. +Nette, fabrikaların yanı sıra erişimciler (accessor) olarak adlandırılanları da üretebilir. Bunlar, DI konteynerinden belirli bir servisi döndüren bir `get()` metoduna sahip nesnelerdir. `get()`'in tekrarlanan çağrıları her zaman aynı örneği döndürür. -Accessor'lar bağımlılıklara tembel yükleme getirir. Hataları özel bir veritabanına kaydeden bir sınıfımız olsun. Eğer veritabanı bağlantısı sınıfın kurucusunda bir bağımlılık olarak aktarılsaydı, bağlantının her zaman oluşturulması gerekirdi, ancak sadece nadiren bir hata ortaya çıktığında kullanılacağından bağlantı çoğunlukla kullanılmadan kalırdı. -Bunun yerine, sınıf bir erişimci geçebilir ve `get()` yöntemi çağrıldığında, yalnızca o zaman veritabanı nesnesi oluşturulur: +Erişimciler, bağımlılıklara tembel yükleme (lazy-loading) sağlar. Hataları özel bir veritabanına yazan bir sınıfımız olduğunu varsayalım. Eğer bu sınıf veritabanı bağlantısını kurucu bağımlılığı olarak alsaydı, bağlantı her zaman oluşturulmak zorunda kalırdı, ancak pratikte hata yalnızca istisnai olarak ortaya çıkar ve bu nedenle çoğu zaman bağlantı kullanılmadan kalırdı. Bunun yerine, sınıf bir erişimci aktarır ve yalnızca onun `get()` metodu çağrıldığında veritabanı nesnesi oluşturulur: -Bir accessor nasıl oluşturulur? Sadece bir arayüz yazın ve Nette DI uygulamayı oluşturacaktır. Arayüz, `get` adında tam olarak bir yönteme sahip olmalı ve dönüş türünü bildirmelidir: +Bir erişimci nasıl oluşturulur? Sadece bir arayüz yazmanız yeterlidir ve Nette DI uygulamayı üretecektir. Arayüzün tam olarak `get` adında bir metodu olmalı ve dönüş değeri tipini bildirmelidir: ```php interface PDOAccessor @@ -151,7 +150,7 @@ interface PDOAccessor } ``` -Erişiciyi, erişicinin döndüreceği hizmetin tanımıyla birlikte yapılandırma dosyasına ekleyin: +Erişimciyi, döndüreceği servisin tanımının da bulunduğu yapılandırma dosyasına ekleriz: ```neon services: @@ -159,62 +158,68 @@ services: - PDO(%dsn%, %user%, %password%) ``` -Erişimci, `PDO` türünde bir hizmet döndürür ve yapılandırmada bu türden yalnızca bir hizmet olduğu için erişimci bunu döndürür. Bu türde birden fazla yapılandırılmış hizmet varsa, adını kullanarak hangisinin döndürüleceğini belirtebilirsiniz, örneğin `- PDOAccessor(@db1)`. +Erişimci `PDO` tipinde bir servis döndürdüğü ve yapılandırmada bu türden tek bir servis olduğu için, tam olarak onu döndürecektir. Eğer ilgili tipten birden fazla servis olsaydı, döndürülen servisi adıyla belirlerdik, örn. `- PDOAccessor(@db1)`. -Multifactory/Accessor .[#toc-multifactory-accessor] -=================================================== -Şimdiye kadar, fabrikalar ve erişimciler yalnızca tek bir nesne oluşturabiliyor veya döndürebiliyordu. Bir erişimci ile birleştirilmiş bir çoklu fabrika da oluşturulabilir. Böyle bir çoklu fabrika sınıfının arayüzü, aşağıdaki gibi adlandırılan birden fazla yöntemden oluşabilir `create<name>()` ve `get<name>()`Örneğin: +Çoklu Fabrika/Erişimci +====================== +Fabrikalarımız ve erişimcilerimiz şimdiye kadar her zaman yalnızca bir nesne üretebildi veya döndürebildi. Ancak, erişimcilerle birleştirilmiş çoklu fabrikaları da çok kolay bir şekilde oluşturmak mümkündür. Böyle bir sınıfın arayüzü, `create<name>()` ve `get<name>()` adlarına sahip istenilen sayıda metot içerecektir, örn.: ```php interface MultiFactory { function createArticle(): Article; - function createFoo(): Model\Foo; function getDb(): PDO; } ``` -Birden fazla oluşturulmuş fabrika ve erişimci geçirmek yerine, yalnızca bir karmaşık çoklu fabrika geçirebilirsiniz. +Yani, birkaç üretilmiş fabrika ve erişimci aktarmak yerine, daha fazlasını yapabilen daha karmaşık bir fabrika aktarırız. -Alternatif olarak, birden fazla yöntem yerine bir parametre ile `create()` ve `get()` adreslerini kullanabilirsiniz: +Alternatif olarak, birkaç metot yerine parametreli `get()` kullanılabilir: ```php interface MultiFactoryAlt { - function create($name); function get($name): PDO; } ``` -Bu durumda, `MultiFactory::createArticle()`, `MultiFactoryAlt::create('article')` ile aynı şeyi yapar. Ancak, alternatif sözdiziminin birkaç dezavantajı vardır. Hangi `$name` değerlerinin desteklendiği açık değildir ve birden fazla farklı `$name` değeri kullanıldığında dönüş türü arayüzde belirtilemez. +O zaman `MultiFactory::getArticle()`'ın `MultiFactoryAlt::get('article')` ile aynı şeyi yaptığı geçerlidir. Ancak, alternatif yazımın dezavantajı, hangi `$name` değerlerinin desteklendiğinin açık olmaması ve mantıksal olarak arayüzde farklı `$name` için farklı dönüş değerlerini ayırt etmenin mümkün olmamasıdır. -Liste ile Tanımlama .[#toc-definition-with-a-list] --------------------------------------------------- -Yapılandırmanızda bir multifactory tanımlamak için ne yapmalısınız? Multifactory tarafından döndürülecek üç servis ve multifactory'nin kendisini oluşturalım: +Liste ile Tanım +--------------- +Bu şekilde, yapılandırmada çoklu bir fabrika tanımlanabilir: .{data-version:3.2.0} + +```neon +services: + - MultiFactory( + article: Article # createArticle() tanımlar + db: PDO(%dsn%, %user%, %password%) # getDb() tanımlar + ) +``` + +Veya fabrika tanımında mevcut servislere referansla başvurabiliriz: ```neon services: article: Article - - Model\Foo - PDO(%dsn%, %user%, %password%) - MultiFactory( - article: @article # createArticle() - foo: @Model\Foo # createFoo() - db: @\PDO # getDb() + article: @article # createArticle() tanımlar + db: @\PDO # getDb() tanımlar ) ``` -Etiketlerle Tanım .[#toc-definition-with-tags] ----------------------------------------------- +Etiketlerle Tanım +----------------- -Bir multifactory'nin nasıl tanımlanacağına dair bir başka seçenek de [etiket |services#Tags] kullanmaktır: +İkinci seçenek, tanım için [etiketleri |services#Etiketler Tags] kullanmaktır: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter - App\Model\DatabaseAccessor( db1: @database.db1.explorer ) diff --git a/dependency-injection/tr/faq.texy b/dependency-injection/tr/faq.texy index cb6cb89752..298e6277f8 100644 --- a/dependency-injection/tr/faq.texy +++ b/dependency-injection/tr/faq.texy @@ -1,98 +1,92 @@ -DI Hakkında SSS -*************** +DI Hakkında Sıkça Sorulan Sorular (SSS) +*************************************** -DI, IoC için başka bir isim mi? .[#toc-is-di-another-name-for-ioc] ------------------------------------------------------------------- +DI, IoC'nin başka bir adı mıdır? +-------------------------------- -*Inversion of Control* (IoC), kodun yürütülme şekline odaklanan bir ilkedir - kodunuzun harici kodu başlatması veya kodunuzun harici koda entegre olması ve daha sonra onu çağırması. -IoC, [olayları |nette:glossary#Events], [Hollywood İlkesi |application:components#Hollywood style] olarak adlandırılan ilkeyi ve diğer unsurları içeren geniş bir kavramdır. -[Kural #3: Bırakın Fabrika Hall |introduction#Rule #3: Let the Factory Handle It] etsin'in bir parçası olan ve `new` operatörü için tersine çevirmeyi temsil eden fabrikalar da bu kavramın bileşenleridir. +*Kontrolün Tersine Çevrilmesi* (IoC - Inversion of Control), kodun nasıl çalıştırıldığına odaklanan bir ilkedir - kodunuzun yabancı bir kodu mu çalıştırdığı yoksa kodunuzun onu daha sonra çağıran yabancı bir koda mı entegre edildiği. IoC, [olayları |nette:glossary#Olaylar Events], [Hollywood İlkesi |application:components#Hollywood Tarzı] olarak adlandırılanı ve diğer yönleri kapsayan geniş bir kavramdır. Bu konseptin bir parçası, [Kural No. 3: Fabrikaya bırak |introduction#Kural 3: Fabrikaya Bırakın]'da bahsedilen ve `new` operatörü için bir tersine çevirme temsil eden fabrikalardır. -*Dependency Injection* (DI), bir nesnenin başka bir nesne hakkında nasıl bilgi sahibi olduğu, yani bağımlılık ile ilgilidir. Nesneler arasında bağımlılıkların açık bir şekilde aktarılmasını gerektiren bir tasarım modelidir. +*Bağımlılık Enjeksiyonu* (DI - Dependency Injection), bir nesnenin başka bir nesne hakkında, yani bağımlılıkları hakkında nasıl bilgi edindiğine odaklanır. Nesneler arasında bağımlılıkların açıkça aktarılmasını gerektiren bir tasarım desenidir. -Dolayısıyla DI'nin IoC'nin özel bir biçimi olduğu söylenebilir. Bununla birlikte, IoC'nin tüm biçimleri kod saflığı açısından uygun değildir. Örneğin, anti-paternler arasında, [küresel durumla |global state] çalışan veya [Hizmet Bul |#What is a Service Locator]ucu olarak adlandırılan tüm teknikleri dahil ediyoruz. +Dolayısıyla, DI'nin IoC'nin özel bir formu olduğu söylenebilir. Ancak, tüm IoC formları kod temizliği açısından uygun değildir. Örneğin, anti-desenler arasında [global durum |global-state] ile çalışan teknikler veya [Servis Bulucu |#Servis Bulucu Service Locator Nedir] olarak adlandırılan teknikler bulunur. -Hizmet Bulucu Nedir? .[#toc-what-is-a-service-locator] ------------------------------------------------------- +Servis Bulucu (Service Locator) Nedir? +-------------------------------------- -Hizmet Bulucu, Bağımlılık Enjeksiyonuna bir alternatiftir. Mevcut tüm hizmetlerin veya bağımlılıkların kaydedildiği merkezi bir depolama alanı oluşturarak çalışır. Bir nesne bir bağımlılığa ihtiyaç duyduğunda, bunu Hizmet Bulucu'dan talep eder. +Bağımlılık Enjeksiyonu'na bir alternatiftir. Mevcut tüm servislerin veya bağımlılıkların kaydedildiği merkezi bir depo oluşturarak çalışır. Bir nesne bir bağımlılığa ihtiyaç duyduğunda, onu Servis Bulucu'dan ister. -Bununla birlikte, Bağımlılık Enjeksiyonu ile karşılaştırıldığında, şeffaflık kaybeder: bağımlılıklar doğrudan nesnelere aktarılmaz ve bu nedenle kolayca tanımlanamaz, bu da tüm bağlantıları ortaya çıkarmak ve anlamak için kodun incelenmesini gerektirir. Test etmek de daha karmaşıktır, çünkü sahte nesneleri test edilen nesnelere basitçe geçiremeyiz, ancak Hizmet Bulucu'dan geçmemiz gerekir. Ayrıca, Service Locator kodun tasarımını bozar, çünkü her bir nesnenin onun varlığından haberdar olması gerekir, bu da nesnelerin DI konteyneri hakkında hiçbir bilgiye sahip olmadığı Dependency Injection'dan farklıdır. +Ancak, Bağımlılık Enjeksiyonu'na kıyasla şeffaflığını kaybeder: bağımlılıklar nesnelere doğrudan aktarılmaz ve bu nedenle kolayca tanımlanamaz, bu da tüm bağlantıları ortaya çıkarmak ve anlamak için kodun incelenmesini gerektirir. Test etme de daha karmaşıktır, çünkü mock nesnelerini test edilen nesnelere basitçe aktaramayız, bunun yerine Servis Bulucu üzerinden gitmemiz gerekir. Ayrıca, Servis Bulucu kod tasarımını bozar, çünkü bireysel nesnelerin onun varlığından haberdar olması gerekir, bu da nesnelerin DI konteynerinden haberdar olmadığı Bağımlılık Enjeksiyonu'ndan farklıdır. -DI kullanmamak ne zaman daha iyidir? .[#toc-when-is-it-better-not-to-use-di] ----------------------------------------------------------------------------- +DI Ne Zaman Kullanılmamalıdır? +------------------------------ -Dependency Injection tasarım modelinin kullanımıyla ilgili bilinen herhangi bir zorluk yoktur. Aksine, bağımlılıkları global olarak erişilebilir konumlardan elde etmek, bir Hizmet Bulucu kullanmak gibi bir [dizi komplikasyona |global-state] yol açar. -Bu nedenle, her zaman DI kullanılması tavsiye edilir. Bu dogmatik bir yaklaşım değildir, ancak daha iyi bir alternatif bulunamamıştır. +Bağımlılık Enjeksiyonu tasarım deseninin kullanımıyla ilişkili bilinen herhangi bir zorluk yoktur. Aksine, bağımlılıkları global olarak erişilebilir yerlerden almak [bir dizi komplikasyona |global-state] yol açar, aynı şekilde Servis Bulucu kullanımı da. Bu nedenle, DI'yi her zaman kullanmak uygundur. Bu dogmatik bir yaklaşım değildir, sadece daha iyi bir alternatif bulunamamıştır. -Ancak, nesneleri birbirlerine aktarmadığımız ve global alandan elde etmediğimiz bazı durumlar vardır. Örneğin, kodda hata ayıklama yaparken ve programın belirli bir noktasında bir değişken değerini dökmek, programın belirli bir bölümünün süresini ölçmek veya bir mesajı günlüğe kaydetmek gerektiğinde. -Daha sonra koddan kaldırılacak geçici eylemlerle ilgili olan bu gibi durumlarda, global olarak erişilebilir bir dumper, kronometre veya logger kullanmak meşrudur. Sonuçta bu araçlar kodun tasarımına ait değildir. +Yine de, nesneleri aktarmadığımız ve onları global alandan aldığımız belirli durumlar vardır. Örneğin, kod hata ayıklarken, programın belirli bir noktasında bir değişkenin değerini yazdırmanız, programın belirli bir bölümünün süresini ölçmeniz veya bir mesaj kaydetmeniz gerektiğinde. Bu gibi durumlarda, daha sonra koddan kaldırılacak geçici görevler söz konusu olduğunda, global olarak erişilebilir bir dumper, kronometre veya logger kullanmak meşrudur. Bu araçlar çünkü kod tasarımına ait değildir. -DI kullanmanın dezavantajları var mı? .[#toc-does-using-di-have-its-drawbacks] ------------------------------------------------------------------------------- +DI Kullanmanın Dezavantajları Var mı? +------------------------------------- -Bağımlılık Enjeksiyonu kullanmanın kod yazma karmaşıklığının artması veya performansın düşmesi gibi dezavantajları var mıdır? DI'ye uygun kod yazmaya başladığımızda ne kaybederiz? +Bağımlılık Enjeksiyonu kullanmak, örneğin kod yazma zorluğunun artması veya performansın kötüleşmesi gibi herhangi bir dezavantaj içerir mi? DI ile uyumlu kod yazmaya başladığımızda ne kaybederiz? -DI'nin uygulama performansı veya bellek gereksinimleri üzerinde hiçbir etkisi yoktur. DI Konteynerinin performansı bir rol oynayabilir, ancak [Nette DI | nette-container] durumunda, konteyner saf PHP'ye derlenir, bu nedenle uygulama çalışma zamanı sırasında ek yükü esasen sıfırdır. +DI'nin uygulamanın performansı veya bellek gereksinimleri üzerinde bir etkisi yoktur. DI Konteynerinin performansı belirli bir rol oynayabilir, ancak [Nette DI |nette-container] durumunda, konteyner saf PHP'ye derlenir, bu nedenle uygulama çalışma zamanındaki ek yükü (overhead) temelde sıfırdır. -Kod yazarken, bağımlılıkları kabul eden yapıcılar oluşturmak gerekir. Geçmişte, bu zaman alıcı olabilirdi, ancak modern IDE'ler ve [yapıcı özellik tanıtımı |https://blog.nette.org/tr/php-8-0-haberlere-genel-bakis#toc-constructor-property-promotion] sayesinde artık birkaç saniye meselesi. Fabrikalar Nette DI ve bir PhpStorm eklentisi kullanılarak sadece birkaç tıklama ile kolayca oluşturulabilir. -Öte yandan singleton ve statik erişim noktaları yazmaya da gerek kalmıyor. +Kod yazarken, bağımlılıkları kabul eden kurucuları oluşturmak gerekli olabilir. Eskiden bu uzun sürebilirdi, ancak modern IDE'ler ve [kurucu özellik tanıtımı |https://blog.nette.org/tr/php-8-0-complete-overview-of-news#toc-constructor-property-promotion] sayesinde bu artık birkaç saniyelik bir meseledir. Fabrikalar, Nette DI ve PhpStorm için bir eklenti kullanılarak fare tıklamasıyla kolayca üretilebilir. Diğer yandan, singleton'lar ve statik erişim noktaları yazma ihtiyacı ortadan kalkar. -DI kullanan düzgün tasarlanmış bir uygulamanın, tekli sınıf kullanan bir uygulamaya kıyasla ne daha kısa ne de daha uzun olduğu sonucuna varılabilir. Bağımlılıklarla çalışan kod parçaları basitçe tek tek sınıflardan çıkarılır ve DI konteyneri ve fabrikaları gibi yeni konumlara taşınır. +DI kullanan doğru tasarlanmış bir uygulamanın, singleton'ları kullanan bir uygulamayla karşılaştırıldığında ne daha kısa ne de daha uzun olduğu söylenebilir. Bağımlılıklarla çalışan kod bölümleri yalnızca bireysel sınıflardan çıkarılır ve yeni yerlere, yani DI konteynerine ve fabrikalara taşınır. -Eski bir uygulama DI için nasıl yeniden yazılır? .[#toc-how-to-rewrite-a-legacy-application-to-di] --------------------------------------------------------------------------------------------------- +Eski Bir Uygulama DI'ye Nasıl Yeniden Yazılır? +---------------------------------------------- -Eski bir uygulamadan Dependency Injection'a geçiş, özellikle büyük ve karmaşık uygulamalar için zorlu bir süreç olabilir. Bu sürece sistematik bir şekilde yaklaşmak önemlidir. +Eski bir uygulamadan (legacy application) Bağımlılık Enjeksiyonu'na geçiş, özellikle büyük ve karmaşık uygulamalarda zorlu bir süreç olabilir. Bu sürece sistematik olarak yaklaşmak önemlidir. -- Bağımlılık Enjeksiyonuna geçerken, tüm ekip üyelerinin kullanılan ilke ve uygulamaları anlaması önemlidir. -- İlk olarak, temel bileşenleri ve bağımlılıklarını belirlemek için mevcut uygulamanın bir analizini yapın. Hangi parçaların hangi sırayla yeniden düzenleneceğine dair bir plan oluşturun. -- Bir DI konteyneri uygulayın ya da daha iyisi Nette DI gibi mevcut bir kütüphaneyi kullanın. -- Bağımlılık Enjeksiyonunu kullanmak için uygulamanın her bir parçasını kademeli olarak yeniden düzenleyin. Bu, bağımlılıkları parametre olarak kabul etmek için kurucuları veya yöntemleri değiştirmeyi içerebilir. -- Kodda bağımlılık nesnelerinin oluşturulduğu yerleri değiştirin, böylece bağımlılıklar bunun yerine kapsayıcı tarafından enjekte edilir. Bu, fabrikaların kullanımını içerebilir. +- Bağımlılık Enjeksiyonu'na geçerken, tüm takım üyelerinin kullanılan ilkeleri ve prosedürleri anlaması önemlidir. +- İlk olarak, mevcut uygulamanın analizini yapın ve anahtar bileşenleri ve bağımlılıklarını tanımlayın. Hangi bölümlerin yeniden düzenleneceğini (refactored) ve hangi sırayla yapılacağını içeren bir plan oluşturun. +- Bir DI konteyneri uygulayın veya daha da iyisi, örneğin Nette DI gibi mevcut bir kütüphaneyi kullanın. +- Bağımlılık Enjeksiyonu'nu kullanmak için uygulamanın bireysel bölümlerini adım adım yeniden düzenleyin. Bu, bağımlılıkları parametre olarak kabul etmek için kurucuların veya metotların düzenlenmesini içerebilir. +- Kodda bağımlılıkları olan nesnelerin oluşturulduğu yerleri, bunun yerine bağımlılıkların konteyner tarafından enjekte edilmesi için düzenleyin. Bu, fabrikaların kullanımını içerebilir. -Dependency Injection'a geçmenin kod kalitesine ve uygulamanın uzun vadeli sürdürülebilirliğine yapılan bir yatırım olduğunu unutmayın. Bu değişiklikleri yapmak zor olsa da, sonuçta daha temiz, daha modüler ve kolayca test edilebilir, gelecekteki uzantılara ve bakıma hazır bir kod ortaya çıkacaktır. +Bağımlılık Enjeksiyonu'na geçişin kod kalitesine ve uygulamanın uzun vadeli sürdürülebilirliğine yapılan bir yatırım olduğunu unutmayın. Bu değişiklikleri yapmak zorlu olsa da, sonuç daha temiz, daha modüler ve kolayca test edilebilir, gelecekteki genişletmelere ve bakıma hazır bir kod olmalıdır. -Neden kalıtım yerine kompozisyon tercih edilir? .[#toc-why-composition-is-preferred-over-inheritance] ------------------------------------------------------------------------------------------------------ -Değişimin damlama etkisi konusunda endişelenmeye gerek kalmadan kodun yeniden kullanılabilirliği amacına hizmet ettiği için kalıtım yerine bileşimin kullanılması tercih edilir. Böylece, bazı kodların değiştirilmesinin diğer bazı bağımlı kodların değişmesine neden olması konusunda endişelenmemizi gerektirmeyen daha gevşek bir bağlantı sağlar. Tipik bir örnek, [kurucu cehennemi |passing-dependencies#Constructor hell] olarak tanımlanan durumdur. +Neden Kalıtım Yerine Kompozisyon Tercih Edilir? +----------------------------------------------- +Değişikliklerin sonuçları hakkında endişelenmeden kodu yeniden kullanmamıza hizmet ettiği için [kalıtım |nette:introduction-to-object-oriented-programming#Kompozisyon] yerine [kompozisyonu |nette:introduction-to-object-oriented-programming#Kalıtım] kullanmak daha uygundur. Dolayısıyla, bir kod değişikliğinin başka bir bağımlı kodun değiştirilmesi ihtiyacına neden olacağından endişelenmemize gerek olmayan daha gevşek bir bağlantı sağlar. Tipik bir örnek, [kurucu cehennemi |passing-dependencies#Constructor Hell] olarak adlandırılan durumdur. -Nette DI Container Nette dışında kullanılabilir mi? .[#toc-can-nette-di-container-be-used-outside-of-nette] ------------------------------------------------------------------------------------------------------------ +Nette DI Konteyner Nette Dışında Kullanılabilir mi? +--------------------------------------------------- -Kesinlikle. Nette DI Container, Nette'in bir parçasıdır, ancak çerçevenin diğer bölümlerinden bağımsız olarak kullanılabilen bağımsız bir kütüphane olarak tasarlanmıştır. Composer'ı kullanarak kurun, hizmetlerinizi tanımlayan bir yapılandırma dosyası oluşturun ve ardından DI konteynerini oluşturmak için birkaç satır PHP kodu kullanın. -Böylece projelerinizde Dependency Injection'dan hemen yararlanmaya başlayabilirsiniz. +Kesinlikle. Nette DI Konteyner, Nette'nin bir parçasıdır, ancak framework'ün diğer bölümlerinden bağımsız olarak kullanılabilecek bağımsız bir kütüphane olarak tasarlanmıştır. Sadece Composer kullanarak yüklemeniz, servislerinizin tanımıyla bir yapılandırma dosyası oluşturmanız ve ardından birkaç satır PHP kodu kullanarak bir DI konteyneri oluşturmanız yeterlidir. Ve hemen projelerinizde Bağımlılık Enjeksiyonu'nun avantajlarından yararlanmaya başlayabilirsiniz. - [Nette DI Container |nette-container] bölümü, kod da dahil olmak üzere belirli bir kullanım durumunun neye benzediğini açıklar. +Kodlar dahil olmak üzere somut kullanımın nasıl göründüğünü [Nette DI Konteyner |nette-container] bölümü açıklar. -NEON dosyalarındaki yapılandırma neden? .[#toc-why-is-the-configuration-in-neon-files] --------------------------------------------------------------------------------------- +Yapılandırma Neden NEON Dosyalarındadır? +---------------------------------------- -NEON, uygulamaları, hizmetleri ve bunların bağımlılıklarını ayarlamak için Nette içinde geliştirilen basit ve kolay okunabilir bir yapılandırma dilidir. JSON veya YAML ile karşılaştırıldığında, bu amaç için çok daha sezgisel ve esnek seçenekler sunar. NEON'da, Symfony & YAML'de yazılması mümkün olmayan bağları doğal olarak ya hiç ya da sadece karmaşık bir açıklama yoluyla tanımlayabilirsiniz. +NEON, uygulamaları, servisleri ve bağımlılıklarını ayarlamak için Nette kapsamında geliştirilmiş basit ve kolay okunabilir bir yapılandırma dilidir. JSON veya YAML ile karşılaştırıldığında, bu amaç için çok daha sezgisel ve esnek seçenekler sunar. NEON'da, Symfony & YAMLu'da ya hiç yazılamayacak ya da sadece karmaşık bir tanım aracılığıyla yazılabilecek bağlantıları doğal olarak tanımlamak mümkündür. -NEON dosyalarını ayrıştırmak uygulamayı yavaşlatıyor mu? .[#toc-does-parsing-neon-files-slow-down-the-application] ------------------------------------------------------------------------------------------------------------------- +NEON Dosyalarını Ayrıştırmak Uygulamayı Yavaşlatır mı? +------------------------------------------------------ -NEON dosyaları çok hızlı bir şekilde ayrıştırılsa da, bu özellik gerçekten önemli değildir. Bunun nedeni, dosyaların ayrıştırılmasının uygulamanın ilk başlatılması sırasında yalnızca bir kez gerçekleşmesidir. Bundan sonra, DI konteyner kodu oluşturulur, diskte saklanır ve sonraki her istek için daha fazla ayrıştırmaya gerek kalmadan yürütülür. +NEON dosyaları çok hızlı ayrıştırılsa da, bu bakış açısı hiç önemli değildir. Nedeni, dosyaların ayrıştırılmasının yalnızca uygulamanın ilk çalıştırılmasında bir kez gerçekleşmesidir. Daha sonra DI konteynerinin kodu üretilir, diske kaydedilir ve daha fazla ayrıştırma yapmaya gerek kalmadan sonraki her istekte çalıştırılır. -Üretim ortamında bu şekilde çalışır. Geliştirme sırasında, NEON dosyaları içerikleri her değiştiğinde ayrıştırılarak geliştiricinin her zaman güncel bir DI konteynerine sahip olması sağlanır. Daha önce de belirtildiği gibi, gerçek ayrıştırma bir anlık bir meseledir. +Bu, üretim ortamında bu şekilde çalışır. Geliştirme sırasında, geliştiricinin her zaman güncel bir DI konteynerine sahip olması için NEON dosyaları içerikleri her değiştiğinde ayrıştırılır. Ayrıştırmanın kendisi, söylendiği gibi, anlık bir meseledir. -Sınıfımdaki yapılandırma dosyasından parametrelere nasıl erişebilirim? .[#toc-how-do-i-access-the-parameters-from-the-configuration-file-in-my-class] ------------------------------------------------------------------------------------------------------------------------------------------------------ +Sınıfımdan Yapılandırma Dosyasındaki Parametrelere Nasıl Erişirim? +------------------------------------------------------------------ - [Kural #1 |introduction#Rule #1: Let It Be Passed to You]'i aklınızda tutun [: Bırakın Size İletilsin |introduction#Rule #1: Let It Be Passed to You]. Bir sınıf bir yapılandırma dosyasından bilgi gerektiriyorsa, bu bilgiye nasıl erişeceğimizi bulmamız gerekmez; bunun yerine, örneğin sınıf kurucusu aracılığıyla basitçe sorarız. Ve aktarma işlemini yapılandırma dosyasında gerçekleştiririz. +[Kural No. 1: Sana aktarılmasına izin ver |introduction#Kural 1: Size İletilmesini Sağlayın]'i aklımızda bulunduralım. Eğer sınıf yapılandırma dosyasından bilgi gerektiriyorsa, bilgiye nasıl ulaşacağımızı düşünmemize gerek yok, bunun yerine basitçe isteriz - örneğin sınıfın kurucusu aracılığıyla. Ve aktarımı yapılandırma dosyasında gerçekleştiririz. -Bu örnekte `%myParameter%`, `MyClass` kurucusuna aktarılacak olan `myParameter` parametresinin değeri için bir yer tutucudur: +Bu örnekte, `%myParameter%`, `MyClass` sınıfının kurucusuna aktarılan `myParameter` parametresinin değeri için bir yer tutucu semboldür: ```php # config.neon @@ -103,10 +97,10 @@ services: - MyClass(%myParameter%) ``` -Birden fazla parametre geçirmek veya otomatik bağlantı kullanmak istiyorsanız, parametreleri [bir nesneye sarmak |best-practices:passing-settings-to-presenters] yararlı olacaktır. +Daha fazla parametre aktarmak veya otomatik bağlama kullanmak istiyorsanız, [parametreleri bir nesneye sarmak |best-practices:passing-settings-to-presenters] uygundur. -Nette PSR-11 Konteyner arayüzünü destekliyor mu? .[#toc-does-nette-support-psr-11-container-interface] ------------------------------------------------------------------------------------------------------- +Nette PSR-11: Konteyner Arayüzünü Destekliyor mu? +------------------------------------------------- -Nette DI Container PSR-11'i doğrudan desteklemez. Ancak, Nette DI Container ile PSR-11 Container Interface'i bekleyen kütüphaneler veya çerçeveler arasında birlikte çalışabilirliğe ihtiyacınız varsa, Nette DI Container ile PSR-11 arasında bir köprü görevi görecek [basit bir adaptör |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f] oluşturabilirsiniz. +Nette DI Konteyner, PSR-11'i doğrudan desteklemez. Ancak, Nette DI Konteyneri ile PSR-11 Konteyner Arayüzü bekleyen kütüphaneler veya framework'ler arasında birlikte çalışabilirliğe ihtiyacınız varsa, Nette DI Konteyneri ile PSR-11 arasında köprü görevi görecek [basit bir adaptör |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f] oluşturabilirsiniz. diff --git a/dependency-injection/tr/global-state.texy b/dependency-injection/tr/global-state.texy index 72c7d53aa1..b1cb35c9ca 100644 --- a/dependency-injection/tr/global-state.texy +++ b/dependency-injection/tr/global-state.texy @@ -1,44 +1,43 @@ -Küresel Durum ve Singletonlar +Global Durum ve Singleton'lar ***************************** .[perex] -Uyarı: Aşağıdaki yapılar kötü tasarlanmış kodun belirtileridir: +Uyarı: Aşağıdaki yapılar kötü tasarlanmış kodun işaretidir: - `Foo::getInstance()` - `DB::insert(...)` - `Article::setDb($db)` - `ClassName::$var` veya `static::$var` -Kodunuzda bu yapılardan herhangi biriyle karşılaşıyor musunuz? Eğer öyleyse, onu geliştirmek için bir fırsatınız var demektir. Bunların yaygın yapılar olduğunu, genellikle çeşitli kütüphanelerin ve çerçevelerin örnek çözümlerinde görüldüğünü düşünebilirsiniz. Eğer durum buysa, kod tasarımları kusurludur. +Bu yapılardan bazıları kodunuzda bulunuyor mu? O zaman onu iyileştirme fırsatınız var. Belki de bunların, çeşitli kütüphanelerin ve framework'lerin örnek çözümlerinde bile gördüğünüz yaygın yapılar olduğunu düşünüyorsunuzdur. Eğer durum buysa, o zaman kodlarının tasarımı iyi değildir. -Burada akademik bir saflıktan bahsetmiyoruz. Tüm bu yapıların ortak bir noktası var: küresel durum kullanıyorlar. Ve bunun kod kalitesi üzerinde yıkıcı bir etkisi vardır. Sınıflar bağımlılıkları konusunda aldatıcıdır. Kod öngörülemez hale gelir. Geliştiricilerin kafasını karıştırır ve verimliliklerini azaltır. +Şimdi kesinlikle bir tür akademik saflıktan bahsetmiyoruz. Tüm bu yapıların ortak bir yanı var: global durumu kullanıyorlar. Ve bunun kod kalitesi üzerinde yıkıcı bir etkisi var. Sınıflar bağımlılıkları hakkında yalan söylüyor. Kod öngörülemez hale geliyor. Programcıları şaşırtıyor ve verimliliklerini düşürüyor. -Bu bölümde, bunun neden böyle olduğunu ve küresel durumdan nasıl kaçınılacağını açıklayacağız. +Bu bölümde, neden böyle olduğunu ve global durumdan nasıl kaçınılacağını açıklayacağız. -Küresel Bağlantı .[#toc-global-interlinking] --------------------------------------------- +Global Bağlantı +--------------- -İdeal bir dünyada, bir nesne yalnızca [kendisine doğrudan aktarılan |passing-dependencies] nesnelerle iletişim kurmalıdır. İki nesne yaratırsam `A` ve `B` ve aralarında hiçbir zaman bir referans geçmezsem, o zaman ne `A` ne de `B` diğerinin durumuna erişemez veya değiştiremez. Bu, kodun oldukça arzu edilen bir özelliğidir. Bu, bir pil ve bir ampule sahip olmaya benzer; ampul, siz onu bir kabloyla pile bağlamadan yanmaz. +İdeal bir dünyada, bir nesne yalnızca [doğrudan aktarılan |passing-dependencies] nesnelerle iletişim kurabilmelidir. Eğer iki `A` ve `B` nesnesi oluşturursam ve aralarında asla bir referans aktarmazsam, o zaman ne `A` ne de `B`, diğer nesneye erişemez veya durumunu değiştiremez. Bu, kodun çok istenen bir özelliğidir. Bu, bir piliniz ve bir ampulünüz olmasına benzer; ampulü pille bir telle bağlamadığınız sürece yanmaz. -Ancak bu durum global (statik) değişkenler veya tekil değişkenler için geçerli değildir. `A` nesnesi `C` nesnesine *kablosuz* olarak erişebilir ve `C::changeSomething()` adresini çağırarak herhangi bir referans geçişi olmadan onu değiştirebilir. Eğer `B` nesnesi global `C` nesnesine de erişirse, `A` ve `B` birbirlerini `C` üzerinden etkileyebilir. +Ancak bu, global (statik) değişkenler veya singleton'lar için geçerli değildir. `A` nesnesi, `C::changeSomething()` çağırarak herhangi bir referans aktarımı olmadan *kablosuz olarak* `C` nesnesine erişebilir ve onu değiştirebilir. Eğer `B` nesnesi de global `C`'yi ele geçirirse, o zaman `A` ve `B` birbirini `C` aracılığıyla etkileyebilir. -Global değişkenlerin kullanılması, dışarıdan görünmeyen yeni bir *kablosuz* bağlantı biçimi ortaya çıkarır. Bu da kodun anlaşılmasını ve kullanılmasını zorlaştıran bir sis perdesi yaratır. Bağımlılıkları gerçekten anlamak için, geliştiricilerin sadece sınıf arayüzlerine aşina olmak yerine kaynak kodun her satırını okumaları gerekir. Üstelik bu karışıklık tamamen gereksizdir. Global durum, her yerden kolayca erişilebildiği ve örneğin global (statik) bir yöntem aracılığıyla bir veritabanına yazmaya izin verdiği için kullanılır `DB::insert()`. Ancak, göreceğimiz gibi, sunduğu fayda minimum düzeydeyken, ortaya çıkardığı komplikasyonlar ciddidir. +Global değişkenlerin kullanımı, sisteme dışarıdan görünmeyen yeni bir *kablosuz* bağlantı formu katar. Kodun anlaşılmasını ve kullanılmasını zorlaştıran bir sis perdesi oluşturur. Geliştiricilerin bağımlılıkları gerçekten anlamaları için, kaynak kodunun her satırını okumaları gerekir. Sadece sınıf arayüzleriyle tanışmak yerine. Üstelik bu tamamen gereksiz bir bağlantıdır. Global durum, her yerden kolayca erişilebilir olduğu ve örneğin `DB::insert()` global (statik) metodu aracılığıyla veritabanına yazmaya izin verdiği için kullanılır. Ama göstereceğimiz gibi, bunun getirdiği avantaj önemsizdir, aksine neden olduğu komplikasyonlar ölümcüldür. .[note] -Davranış açısından, global ve statik değişken arasında bir fark yoktur. İkisi de eşit derecede zararlıdır. +Davranış açısından global ve statik değişken arasında bir fark yoktur. Eşit derecede zararlıdırlar. -Uzaktaki Ürkütücü Eylem .[#toc-the-spooky-action-at-a-distance] ---------------------------------------------------------------- +Uzaktan Ürkütücü Etki +--------------------- -"Uzaktaki ürkütücü eylem" - Albert Einstein 1935 yılında kuantum fiziğinde kendisini ürküten bir olguyu böyle adlandırmıştı. -Bu kuantum dolanıklığıdır ve özelliği, bir parçacık hakkındaki bilgiyi ölçtüğünüzde, milyonlarca ışık yılı uzakta olsalar bile başka bir parçacığı hemen etkilemenizdir. -Bu da evrenin temel yasası olan hiçbir şeyin ışıktan hızlı gidemeyeceği ilkesini ihlal eder. +"Uzaktan ürkütücü etki" - 1935'te Albert Einstein, kuantum fiziğinde tüylerini diken diken eden bir olguyu bu şekilde ünlü bir şekilde adlandırdı. +Bu, kuantum dolaşıklığıdır ve özelliği, bir parçacık hakkındaki bilgiyi ölçtüğünüzde, milyonlarca ışık yılı uzakta olsalar bile diğer parçacığı anında etkilemenizdir. Bu, görünüşte evrenin temel yasasını, yani hiçbir şeyin ışıktan daha hızlı yayılamayacağını ihlal eder. -Yazılım dünyasında, izole olduğunu düşündüğümüz bir süreci çalıştırdığımız (çünkü ona herhangi bir referans iletmedik), ancak sistemin nesneye söylemediğimiz uzak konumlarında beklenmedik etkileşimlerin ve durum değişikliklerinin meydana geldiği bir durumu "uzaktan ürkütücü eylem" olarak adlandırabiliriz. Bu yalnızca global durum aracılığıyla gerçekleşebilir. +Yazılım dünyasında, "uzaktan ürkütücü etki" olarak, izole olduğunu düşündüğümüz (çünkü ona hiçbir referans aktarmadık) bir süreci başlattığımızda, ancak sistemin uzak yerlerinde haberimiz olmayan beklenmedik etkileşimlerin ve durum değişikliklerinin meydana geldiği durumu adlandırabiliriz. Bu, yalnızca global durum aracılığıyla meydana gelebilir. -Büyük ve olgun bir kod tabanına sahip bir proje geliştirme ekibine katıldığınızı düşünün. Yeni lideriniz sizden yeni bir özelliği hayata geçirmenizi istiyor ve siz de iyi bir geliştirici gibi işe bir test yazarak başlıyorsunuz. Ancak projede yeni olduğunuz için, "bu yöntemi çağırırsam ne olur" türünde çok sayıda keşif testi yaparsınız. Ve aşağıdaki testi yazmaya çalışıyorsunuz: +Geniş, olgun bir kod tabanına sahip bir projenin geliştirici ekibine katıldığınızı hayal edin. Yeni yöneticiniz sizden yeni bir özellik uygulamanızı ister ve siz de doğru bir geliştirici olarak test yazarak başlarsınız. Ama projede yeni olduğunuz için, "bu metodu çağırırsam ne olur" türünde bir sürü keşif testi yaparsınız. Ve aşağıdaki testi yazmayı denersiniz: ```php function testCreditCardCharge() @@ -48,20 +47,17 @@ function testCreditCardCharge() } ``` -Kodu belki birkaç kez çalıştırıyorsunuz ve bir süre sonra telefonunuza bankadan gelen bildirimlerde kodu her çalıştırdığınızda kredi kartınızdan 100 $ çekildiğini fark ediyorsunuz 🤦‍♂️ +Kodu çalıştırırsınız, belki birkaç kez, ve bir süre sonra cep telefonunuzda bankadan bildirimler fark edersiniz, her çalıştırmada ödeme kartınızdan 100 dolar çekildiğini 🤦‍♂️ -Test nasıl olur da gerçek bir ücretlendirmeye neden olabilir? Kredi kartı ile işlem yapmak kolay değildir. Üçüncü taraf bir web hizmetiyle etkileşime girmeniz, bu web hizmetinin URL'sini bilmeniz, oturum açmanız vb. gerekir. -Bu bilgilerin hiçbiri testte yer almıyor. Daha da kötüsü, bu bilgilerin nerede bulunduğunu ve bu nedenle her çalıştırmanın tekrar 100 $ ücretlendirilmesiyle sonuçlanmaması için harici bağımlılıkları nasıl taklit edeceğinizi bile bilmiyorsunuz. Ve yeni bir geliştirici olarak, yapmak üzere olduğunuz şeyin 100 dolar daha fakir olmanıza yol açacağını nereden bilebilirdiniz? +Tanrı aşkına nasıl test gerçek para çekme işlemine neden olabilir? Ödeme kartıyla işlem yapmak kolay değildir. Üçüncü taraf bir web servisiyle iletişim kurmanız, bu web servisinin URL'sini bilmeniz, giriş yapmanız vb. gerekir. Bu bilgilerin hiçbiri testte yer almaz. Daha da kötüsü, bu bilgilerin nerede bulunduğunu bile bilmiyorsunuz ve dolayısıyla her çalıştırmanın tekrar 100 dolar çekilmesine yol açmaması için dış bağımlılıkları nasıl mocklayacağınızı da bilmiyorsunuz. Ve yeni bir geliştirici olarak, yapmaya hazırlandığınız şeyin sizi 100 dolar daha fakir yapacağını nasıl bilmeliydiniz? -Bu uzaktan ürkütücü bir hareket! +Bu uzaktan ürkütücü etki! -Projedeki bağlantıların nasıl çalıştığını anlayana kadar, daha yaşlı ve daha deneyimli meslektaşlarınıza sorarak çok sayıda kaynak kodu incelemekten başka seçeneğiniz yoktur. -Bunun nedeni, `CreditCard` sınıfının arayüzüne baktığınızda, başlatılması gereken global durumu belirleyememenizdir. Sınıfın kaynak koduna bakmak bile size hangi ilklendirme yöntemini çağırmanız gerektiğini söylemeyecektir. En iyi ihtimalle, erişilen global değişkeni bulabilir ve buradan nasıl başlatılacağını tahmin etmeye çalışabilirsiniz. +Yapmaktan başka çareniz yok, uzun süre bir sürü kaynak kodunu eşelemek, daha yaşlı ve deneyimli meslektaşlara sormak, projedeki bağlantıların nasıl çalıştığını anlayana kadar. Bu, `CreditCard` sınıfının arayüzüne bakıldığında, başlatılması gereken global durumu tespit etmenin mümkün olmamasından kaynaklanmaktadır. Hatta sınıfın kaynak koduna bakmak bile hangi başlatma metodunu çağırmanız gerektiğini açığa çıkarmaz. En iyi durumda, erişilen bir global değişken bulabilir ve ondan nasıl başlatılacağını tahmin etmeye çalışabilirsiniz. -Böyle bir projedeki sınıflar patolojik yalancılardır. Ödeme kartı, sadece onu örnekleyebileceğinizi ve `charge()` yöntemini çağırabileceğinizi iddia eder. Ancak, gizlice başka bir sınıf olan `PaymentGateway` ile etkileşim halindedir. Arayüzü bile bağımsız olarak başlatılabileceğini söylüyor, ancak gerçekte bazı yapılandırma dosyalarından kimlik bilgilerini çekiyor vb. -Bu kodu yazan geliştiriciler için `CreditCard` 'un `PaymentGateway`'a ihtiyacı olduğu açıktır. Kodu bu şekilde yazmışlardır. Ancak projede yeni olan herkes için bu tam bir gizemdir ve öğrenmeyi engeller. +Böyle bir projedeki sınıflar patolojik yalancılardır. Ödeme kartı, sadece örneklenip `charge()` metodunun çağrılmasının yeterliymiş gibi davranır. Ancak gizlice, ödeme ağ geçidini temsil eden başka bir `PaymentGateway` sınıfıyla işbirliği yapar. Onun arayüzü de bağımsız olarak başlatılabileceğini söyler, ancak gerçekte kimlik bilgilerini bir yapılandırma dosyasından çeker vb. Bu kodu yazan geliştiricilere, `CreditCard`'ın `PaymentGateway`'e ihtiyaç duyduğu açıktır. Kodu bu şekilde yazdılar. Ama projede yeni olan herkes için bu tam bir gizemdir ve öğrenmeyi engeller. -Bu durum nasıl düzeltilir? Çok kolay. **API'nin bağımlılıkları bildirmesine izin verin.** +Durum nasıl düzeltilir? Kolayca. **API'nin bağımlılıkları bildirmesine izin verin.** ```php function testCreditCardCharge() @@ -72,36 +68,35 @@ function testCreditCardCharge() } ``` -Kod içindeki ilişkilerin birdenbire nasıl belirgin hale geldiğine dikkat edin. `charge()` yönteminin `PaymentGateway` adresine ihtiyaç duyduğunu beyan ederek, kodun nasıl birbirine bağlı olduğunu kimseye sormak zorunda kalmazsınız. Bunun bir örneğini oluşturmanız gerektiğini biliyorsunuz ve bunu yapmaya çalıştığınızda erişim parametreleri sağlamanız gerektiği gerçeğiyle karşılaşıyorsunuz. Onlar olmadan kod çalışmaz bile. +Kod içindeki bağlantıların nasıl birdenbire açık hale geldiğine dikkat edin. `charge()` metodunun `PaymentGateway`'e ihtiyaç duyduğunu bildirmesiyle, kodun nasıl bağlantılı olduğunu kimseye sormanıza gerek kalmaz. Bir örnek oluşturmanız gerektiğini bilirsiniz ve bunu denediğinizde, erişim parametrelerini sağlamanız gerektiğiyle karşılaşırsınız. Onlar olmadan kod çalıştırılamazdı bile. -Ve en önemlisi, artık ödeme ağ geçidini taklit edebilirsiniz, böylece her test çalıştırdığınızda 100 $ ücretlendirilmezsiniz. +Ve en önemlisi, şimdi ödeme ağ geçidini mocklayabilirsiniz, böylece testin her çalıştırılmasında size 100 dolar fatura edilmeyecek. -Küresel durum, nesnelerinizin API'lerinde bildirilmeyen şeylere gizlice erişebilmesine neden olur ve sonuç olarak API'lerinizi patolojik yalancılar haline getirir. +Global durum, nesnelerinizin API'lerinde bildirilmemiş şeylere gizlice erişebilmesine neden olur ve sonuç olarak API'lerinizi patolojik yalancılara dönüştürür. -Daha önce bu şekilde düşünmemiş olabilirsiniz, ancak global state kullandığınızda gizli kablosuz iletişim kanalları oluşturmuş olursunuz. Ürkütücü uzaktan eylem, geliştiricileri potansiyel etkileşimleri anlamak için her kod satırını okumaya zorlar, geliştirici verimliliğini azaltır ve yeni ekip üyelerinin kafasını karıştırır. -Kodu oluşturan kişi sizseniz, gerçek bağımlılıkları bilirsiniz, ancak sizden sonra gelenlerin hiçbir şeyden haberi olmaz. +Belki de daha önce bu şekilde düşünmediniz, ancak ne zaman global durumu kullanırsanız, gizli kablosuz iletişim kanalları oluşturursunuz. Uzaktan ürkütücü eylem, geliştiricileri potansiyel etkileşimleri anlamak için kodun her satırını okumaya zorlar, geliştirici üretkenliğini düşürür ve yeni takım üyelerini şaşırtır. Eğer kodu oluşturan sizseniz, gerçek bağımlılıkları bilirsiniz, ama sizden sonra gelen herkes çaresizdir. -Global durum kullanan kod yazmayın, bağımlılıkları aktarmayı tercih edin. Yani, bağımlılık enjeksiyonu. +Global durumu kullanan kod yazmayın, bağımlılıkların aktarılmasına öncelik verin. Yani bağımlılık enjeksiyonu. -Küresel Devletin Kırılganlığı .[#toc-brittleness-of-the-global-state] ---------------------------------------------------------------------- +Global Durumun Kırılganlığı +--------------------------- -Global state ve singleton kullanan kodlarda, bu state'in ne zaman ve kim tarafından değiştirildiği asla kesin değildir. Bu risk başlatma sırasında zaten mevcuttur. Aşağıdaki kodun bir veritabanı bağlantısı oluşturması ve ödeme ağ geçidini başlatması gerekiyor, ancak sürekli bir istisna atıyor ve nedenini bulmak son derece sıkıcı: +Global durumu ve singleton'ları kullanan kodda, bu durumun ne zaman ve kim tarafından değiştirildiği asla emin değildir. Bu risk zaten başlatma sırasında ortaya çıkar. Aşağıdaki kodun veritabanı bağlantısı oluşturması ve ödeme ağ geçidini başlatması gerekiyor, ancak sürekli istisna fırlatıyor ve nedenini aramak son derece uzun sürüyor: ```php PaymentGateway::init(); DB::init('mysql:', 'user', 'password'); ``` - `PaymentGateway` nesnesinin diğer nesnelere kablosuz olarak eriştiğini ve bunlardan bazılarının veritabanı bağlantısı gerektirdiğini bulmak için kodu ayrıntılı olarak incelemeniz gerekir. Bu nedenle, `PaymentGateway` adresinden önce veritabanını başlatmanız gerekir. Ancak, global durum sis perdesi bunu sizden gizler. Her sınıfın API'si yalan söylemeseydi ve bağımlılıklarını beyan etseydi ne kadar zaman kazanırdınız? +`PaymentGateway` nesnesinin kablosuz olarak diğer nesnelere eriştiğini ve bunlardan bazılarının veritabanı bağlantısı gerektirdiğini öğrenmek için kodu ayrıntılı olarak incelemeniz gerekir. Yani veritabanını `PaymentGateway`'den önce başlatmak gereklidir. Ancak global durumun sis perdesi bunu sizden gizler. Eğer bireysel sınıfların API'leri aldatmasaydı ve bağımlılıklarını bildirseydi ne kadar zaman kazanırdınız? ```php $db = new DB('mysql:', 'user', 'password'); $gateway = new PaymentGateway($db, ...); ``` -Bir veritabanı bağlantısına genel erişim kullanıldığında da benzer bir sorun ortaya çıkar: +Benzer bir sorun, veritabanı bağlantısına global erişim kullanıldığında da ortaya çıkar: ```php use Illuminate\Support\Facades\DB; @@ -115,9 +110,9 @@ class Article } ``` - `save()` yöntemi çağrıldığında, bir veritabanı bağlantısının zaten oluşturulup oluşturulmadığı ve oluşturulmasından kimin sorumlu olduğu kesin değildir. Örneğin, veritabanı bağlantısını anında değiştirmek istersek, belki de test amacıyla, muhtemelen `DB::reconnect(...)` veya `DB::reconnectForTest()` gibi ek yöntemler oluşturmamız gerekecektir. +`save()` metodu çağrıldığında, veritabanı bağlantısının zaten oluşturulup oluşturulmadığı ve onun oluşturulmasından kimin sorumlu olduğu emin değildir. Eğer örneğin testler için çalışma zamanında veritabanı bağlantısını değiştirmek istersek, muhtemelen `DB::reconnect(...)` veya `DB::reconnectForTest()` gibi başka metotlar oluşturmamız gerekirdi. -Bir örnek düşünün: +Bir örnek düşünelim: ```php $article = new Article; @@ -127,9 +122,9 @@ Foo::doSomething(); $article->save(); ``` - `$article->save()` adresini çağırırken test veritabanının gerçekten kullanıldığından nasıl emin olabiliriz? Ya `Foo::doSomething()` yöntemi global veritabanı bağlantısını değiştirdiyse? Bunu öğrenmek için `Foo` sınıfının ve muhtemelen diğer birçok sınıfın kaynak kodunu incelememiz gerekir. Ancak, durum gelecekte değişebileceğinden, bu yaklaşım yalnızca kısa vadeli bir yanıt sağlayacaktır. +`$article->save()` çağrıldığında test veritabanının gerçekten kullanıldığına nerede emin olabiliriz? Ya `Foo::doSomething()` metodu global veritabanı bağlantısını değiştirdiyse? Öğrenmek için `Foo` sınıfının kaynak kodunu ve muhtemelen birçok başka sınıfın da kodunu incelememiz gerekirdi. Ancak bu yaklaşım yalnızca kısa vadeli bir cevap getirirdi, çünkü durum gelecekte değişebilir. -Veritabanı bağlantısını `Article` sınıfının içindeki statik bir değişkene taşırsak ne olur? +Ya veritabanı bağlantısını `Article` sınıfının içindeki bir statik değişkene taşırsak? ```php class Article @@ -148,11 +143,11 @@ class Article } ``` -Bu hiçbir şeyi değiştirmez. Sorun global bir durumdur ve hangi sınıfta saklandığı önemli değildir. Bu durumda, bir öncekinde olduğu gibi, `$article->save()` yöntemi çağrıldığında hangi veritabanına yazıldığına dair hiçbir ipucumuz yoktur. Uygulamanın uzak ucundaki herhangi biri `Article::setDb()` adresini kullanarak istediği zaman veritabanını değiştirebilir. Elimizin altında. +Bu hiçbir şeyi değiştirmedi. Sorun global durumdur ve hangi sınıfta saklandığı hiç fark etmez. Bu durumda, önceki gibi, `$article->save()` metodunu çağırdığımızda hangi veritabanına yazılacağına dair hiçbir ipucumuz yok. Uygulamanın diğer ucundaki herhangi biri, `Article::setDb()` kullanarak veritabanını herhangi bir zamanda değiştirebilirdi. Elimizin altında. -Küresel durum uygulamamızı **son derece kırılgan** hale getirir. +Global durum uygulamamızı **son derece kırılgan** yapar. -Ancak bu sorunun üstesinden gelmenin basit bir yolu vardır. Uygun işlevselliği sağlamak için API'nin bağımlılıkları bildirmesi yeterlidir. +Ancak bu sorunla başa çıkmanın basit bir yolu var. Sadece API'nin bağımlılıkları bildirmesine izin vermek yeterlidir, bu da doğru işlevselliği sağlar. ```php class Article @@ -174,15 +169,15 @@ Foo::doSomething(); $article->save(); ``` -Bu yaklaşım, veritabanı bağlantılarında gizli ve beklenmedik değişiklikler yapılması endişesini ortadan kaldırır. Artık makalenin nerede saklandığından eminiz ve başka bir ilgisiz sınıfın içindeki hiçbir kod değişikliği artık durumu değiştiremez. Kod artık kırılgan değil, kararlı. +Bu yaklaşım sayesinde, veritabanı bağlantısındaki gizli ve beklenmedik değişiklikler hakkında endişe ortadan kalkar. Şimdi makalenin nereye kaydedildiğinden eminiz ve başka ilişkisiz bir sınıfın içindeki kod düzenlemeleri artık durumu değiştiremez. Kod artık kırılgan değil, ama kararlı. -Global durum kullanan kod yazmayın, bağımlılıkları aktarmayı tercih edin. Böylece, bağımlılık enjeksiyonu. +Global durumu kullanan kod yazmayın, bağımlılıkların aktarılmasına öncelik verin. Yani bağımlılık enjeksiyonu. -Singleton .[#toc-singleton] ---------------------------- +Singleton +--------- -Singleton, ünlü Gang of Four yayınındaki [tanımıyla |https://en.wikipedia.org/wiki/Singleton_pattern], bir sınıfı tek bir örnekle sınırlayan ve ona global erişim sunan bir tasarım modelidir. Bu kalıbın uygulaması genellikle aşağıdaki koda benzer: +Singleton, bilinen Gang of Four yayınından "tanıma göre":https://en.wikipedia.org/wiki/Singleton_pattern, sınıfı tek bir örneğe sınırlayan ve ona global erişim sunan bir tasarım desenidir. Bu desenin uygulanması genellikle aşağıdaki koda benzer: ```php class Singleton @@ -195,38 +190,35 @@ class Singleton return self::$instance; } - // ve sınıfın işlevlerini yerine getiren diğer yöntemler + // ve sınıfın verilen işlevlerini yerine getiren diğer metotlar } ``` -Ne yazık ki, singleton uygulamaya global durum ekler. Ve yukarıda gösterdiğimiz gibi, global durum istenmeyen bir durumdur. Bu yüzden singleton bir antipattern olarak kabul edilir. +Maalesef, singleton uygulamaya global durum getirir. Ve yukarıda gösterdiğimiz gibi, global durum istenmez. Bu yüzden singleton bir anti-desen olarak kabul edilir. -Kodunuzda singleton kullanmayın ve bunları başka mekanizmalarla değiştirin. Tekillere gerçekten ihtiyacınız yok. Ancak, tüm uygulama için bir sınıfın tek bir örneğinin varlığını garanti etmeniz gerekiyorsa, bunu [DI konteynerine |container] bırakın. -Böylece, bir uygulama singletonu veya hizmeti oluşturun. Bu, sınıfın kendi benzersizliğini sağlamasını durduracak (yani, bir `getInstance()` yöntemine ve statik bir değişkene sahip olmayacaktır) ve yalnızca işlevlerini yerine getirecektir. Böylece, tek sorumluluk ilkesini ihlal etmeyi durduracaktır. +Kodunuzda singleton'ları kullanmayın ve onları başka mekanizmalarla değiştirin. Singleton'lara gerçekten ihtiyacınız yok. Ancak tüm uygulama için sınıfın tek bir örneğinin varlığını garanti etmeniz gerekiyorsa, bunu [DI konteynerine |container] bırakın. Böylece bir uygulama singleton'u, yani bir servis oluşturun. Bu sayede sınıf kendi benzersizliğini sağlamaya (yani `getInstance()` metodu ve statik değişkene sahip olmayacak) odaklanmayı bırakır ve yalnızca kendi işlevlerini yerine getirir. Böylece Tek Sorumluluk İlkesi'ni ihlal etmeyi bırakır. -Testlere Karşı Küresel Durum .[#toc-global-state-versus-tests] --------------------------------------------------------------- +Global Durum ve Testler +----------------------- -Testleri yazarken, her testin izole bir birim olduğunu ve hiçbir dış durumun teste girmediğini varsayarız. Ve hiçbir durum testleri terk etmez. Bir test tamamlandığında, testle ilişkili tüm durumlar çöp toplayıcı tarafından otomatik olarak kaldırılmalıdır. Bu, testleri yalıtılmış hale getirir. Bu nedenle testleri istediğimiz sırada çalıştırabiliriz. +Test yazarken, her testin izole bir birim olduğunu ve ona hiçbir dış durumun girmediğini varsayarız. Ve hiçbir durum testlerden çıkmaz. Test tamamlandıktan sonra, testle ilgili tüm durumun çöp toplayıcı (garbage collector) tarafından otomatik olarak kaldırılması gerekir. Bu sayede testler izole edilmiştir. Bu yüzden testleri istenilen sırada çalıştırabiliriz. -Ancak, küresel durumlar/singletonlar mevcutsa, tüm bu güzel varsayımlar bozulur. Bir durum bir teste girebilir ve çıkabilir. Birdenbire, testlerin sırası önemli olabilir. +Ancak global durumlar/singleton'lar mevcutsa, tüm bu hoş varsayımlar parçalanır. Durum teste girebilir ve ondan çıkabilir. Birdenbire testlerin sırası önemli olabilir. -Tekil öğeleri test etmek için, geliştiricilerin genellikle bir örneğin başka bir örnekle değiştirilmesine izin vererek özelliklerini gevşetmeleri gerekir. Bu tür çözümler, en iyi ihtimalle, bakımı ve anlaşılması zor kodlar üreten hilelerdir. Herhangi bir global durumu etkileyen herhangi bir test veya yöntem `tearDown()` bu değişiklikleri geri almalıdır. +Singleton'ları test edebilmek için bile, geliştiriciler genellikle özelliklerini gevşetmek zorunda kalır, belki de örneği başkasıyla değiştirmeye izin vererek. Böyle çözümler en iyi durumda bir hack'tir ve bakımı zor, anlaşılır olmayan kod oluşturur. Herhangi bir global durumu etkileyen her test veya `tearDown()` metodu, bu değişiklikleri geri almalıdır. -Global durum, birim testindeki en büyük baş ağrısıdır! +Global durum, birim testi sırasında en büyük baş ağrısıdır! -Bu durum nasıl düzeltilir? Çok kolay. Singleton kullanan kod yazmayın, bağımlılıkları aktarmayı tercih edin. Yani bağımlılık enjeksiyonu. +Durum nasıl düzeltilir? Kolayca. Singleton'ları kullanan kod yazmayın, bağımlılıkların aktarılmasına öncelik verin. Yani bağımlılık enjeksiyonu. -Küresel Sabitler .[#toc-global-constants] ------------------------------------------ +Global Sabitler +--------------- -Küresel durum tekil ve statik değişkenlerin kullanımıyla sınırlı değildir, aynı zamanda küresel sabitler için de geçerli olabilir. +Global durum yalnızca singleton'ların ve statik değişkenlerin kullanımıyla sınırlı değildir, aynı zamanda global sabitlerle de ilgili olabilir. -Değeri bize yeni (`M_PI`) veya faydalı (`PREG_BACKTRACK_LIMIT_ERROR`) bilgi sağlamayan sabitler açıkça tamamdır. -Tersine, kod içinde *kablosuz* bilgi aktarmanın bir yolu olarak hizmet eden sabitler, gizli bir bağımlılıktan başka bir şey değildir. Aşağıdaki örnekte `LOG_FILE` gibi. - `FILE_APPEND` sabitini kullanmak tamamen doğrudur. +Değeri bize yeni (`M_PI`) veya faydalı (`PREG_BACKTRACK_LIMIT_ERROR`) bir bilgi getirmeyen sabitler kesinlikle sorunsuzdur. Aksine, bilgiyi kodun içine *kablosuz olarak* aktarmanın bir yolu olarak hizmet eden sabitler, gizli bir bağımlılıktan başka bir şey değildir. Aşağıdaki örnekteki `LOG_FILE` gibi. `FILE_APPEND` sabitinin kullanımı tamamen doğrudur. ```php const LOG_FILE = '...'; @@ -242,7 +234,7 @@ class Foo } ``` -Bu durumda, API'nin bir parçası haline getirmek için parametreyi `Foo` sınıfının kurucusunda bildirmeliyiz: +Bu durumda, API'nin bir parçası olması için `Foo` sınıfının kurucusunda bir parametre bildirmeliyiz: ```php class Foo @@ -261,44 +253,42 @@ class Foo } ``` -Artık günlük dosyasının yolu hakkında bilgi aktarabilir ve gerektiğinde kolayca değiştirebiliriz, bu da kodu test etmeyi ve bakımını yapmayı kolaylaştırır. +Şimdi kayıt tutma için dosya yolu bilgisini aktarabilir ve ihtiyaca göre kolayca değiştirebiliriz, bu da kodun test edilmesini ve bakımını kolaylaştırır. -Global İşlevler ve Statik Yöntemler .[#toc-global-functions-and-static-methods] -------------------------------------------------------------------------------- +Global Fonksiyonlar ve Statik Metotlar +-------------------------------------- -Statik yöntemlerin ve global fonksiyonların kullanımının kendi içinde sorunlu olmadığını vurgulamak istiyoruz. `DB::insert()` ve benzeri yöntemlerin kullanılmasının uygunsuzluğunu açıkladık, ancak bu her zaman statik bir değişkende saklanan global durum meselesi olmuştur. `DB::insert()` yöntemi, veritabanı bağlantısını sakladığı için statik bir değişkenin varlığını gerektirir. Bu değişken olmadan yöntemi uygulamak imkansızdır. +Statik metotların ve global fonksiyonların kullanımının kendisinin sorunlu olmadığını vurgulamak istiyoruz. `DB::insert()` ve benzeri metotların kullanımının uygunsuzluğunun ne içerdiğini açıkladık, ancak her zaman sadece bir statik değişkende saklanan global durum meselesiydi. `DB::insert()` metodu, içinde veritabanı bağlantısı saklandığı için statik değişkenin varlığını gerektirir. Bu değişken olmadan metodu uygulamak imkansız olurdu. - `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` ve diğerleri gibi deterministik statik yöntem ve fonksiyonların kullanımı bağımlılık enjeksiyonu ile tamamen tutarlıdır. Bu fonksiyonlar her zaman aynı girdi parametrelerinden aynı sonuçları döndürür ve bu nedenle öngörülebilirdir. Herhangi bir global durum kullanmazlar. +`DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` ve birçok diğer gibi deterministik statik metotların ve fonksiyonların kullanımı, bağımlılık enjeksiyonu ile tamamen uyumludur. Bu fonksiyonlar her zaman aynı giriş parametrelerinden aynı sonuçları döndürürler ve bu yüzden öngörülebilirler. Hiçbir global durum kullanmazlar. -Ancak, PHP'de deterministik olmayan işlevler de vardır. Bunlara örnek olarak `htmlspecialchars()` fonksiyonu verilebilir. Üçüncü parametresi olan `$encoding` belirtilmezse, varsayılan olarak `ini_get('default_charset')` yapılandırma seçeneğinin değerini alır. Bu nedenle, fonksiyonun olası öngörülemeyen davranışını önlemek için bu parametrenin her zaman belirtilmesi önerilir. Nette bunu sürekli olarak yapar. +Ancak PHP'de deterministik olmayan fonksiyonlar da vardır. Bunlara örneğin `htmlspecialchars()` fonksiyonu dahildir. Üçüncü parametresi `$encoding`, eğer belirtilmemişse, varsayılan değer olarak `ini_get('default_charset')` yapılandırma seçeneğinin değerine sahiptir. Bu yüzden bu parametreyi her zaman belirtmek ve böylece fonksiyonun olası öngörülemez davranışını önlemek tavsiye edilir. Nette bunu tutarlı bir şekilde yapar. - `strtolower()`, `strtoupper()` ve benzerleri gibi bazı fonksiyonlar yakın geçmişte deterministik olmayan davranışlara sahipti ve `setlocale()` ayarına bağlıydı. Bu, çoğunlukla Türkçe diliyle çalışırken birçok komplikasyona neden olmuştur. -Bunun nedeni, Türkçe dilinin noktalı ve noktasız büyük ve küçük harf `I` arasında ayrım yapmasıdır. Bu yüzden `strtolower('I')`, `ı` karakterini ve `strtoupper('i')`, `İ` karakterini döndürüyordu, bu da uygulamaların bir dizi gizemli hataya neden olmasına yol açıyordu. -Ancak, bu sorun PHP 8.2 sürümünde düzeltilmiştir ve işlevler artık yerel ayara bağlı değildir. +`strtolower()`, `strtoupper()` ve benzeri bazı fonksiyonlar, yakın geçmişte deterministik olmayan şekilde davrandılar ve `setlocale()` ayarına bağımlıydılar. Bu, en sık Türkçe dili ile çalışırken birçok komplikasyona neden oldu. Çünkü o, noktalı ve noktasız küçük ve büyük `I` harfini ayırt eder. Yani `strtolower('I')` `ı` karakterini ve `strtoupper('i')` `İ` karakterini döndürüyordu, bu da uygulamaların bir dizi gizemli hataya neden olmaya başlamasına yol açtı. Ancak bu sorun PHP sürüm 8.2'de kaldırıldı ve fonksiyonlar artık yerel ayara bağımlı değil. -Bu, küresel durumun dünyanın dört bir yanındaki binlerce geliştiriciyi nasıl rahatsız ettiğinin güzel bir örneğidir. Çözüm, bağımlılık enjeksiyonu ile değiştirmekti. +Bu, global durumun tüm dünyada binlerce geliştiriciyi nasıl rahatsız ettiğinin güzel bir örneğidir. Çözüm, onu bağımlılık enjeksiyonu ile değiştirmekti. -Global State Ne Zaman Kullanılabilir? .[#toc-when-is-it-possible-to-use-global-state] -------------------------------------------------------------------------------------- +Global Durum Ne Zaman Kullanılabilir? +------------------------------------- -Global durumu kullanmanın mümkün olduğu bazı özel durumlar vardır. Örneğin, kodda hata ayıklama yaparken bir değişkenin değerini dökmeniz veya programın belirli bir bölümünün süresini ölçmeniz gerekir. Daha sonra koddan kaldırılacak geçici eylemlerle ilgili olan bu gibi durumlarda, global olarak kullanılabilen bir dumper veya kronometre kullanmak meşrudur. Bu araçlar kod tasarımının bir parçası değildir. +Global durumu kullanmanın mümkün olduğu belirli özel durumlar vardır. Örneğin kod hata ayıklarken, bir değişkenin değerini yazdırmanız veya programın belirli bir kısmının süresini ölçmeniz gerektiğinde. Bu gibi durumlarda, daha sonra koddan kaldırılacak geçici eylemlerle ilgili olanlarda, global olarak erişilebilir bir dumper veya kronometre kullanmak meşru olarak mümkündür. Bu araçlar çünkü kod tasarımının bir parçası değildir. -Başka bir örnek, derlenmiş düzenli ifadeleri dahili olarak bellekteki statik bir önbellekte saklayan `preg_*` düzenli ifadelerle çalışma işlevleridir. Aynı düzenli ifadeyi kodun farklı bölümlerinde birden çok kez çağırdığınızda, bu ifade yalnızca bir kez derlenir. Önbellek performans tasarrufu sağlar ve ayrıca kullanıcı için tamamen görünmezdir, bu nedenle bu tür kullanım meşru kabul edilebilir. +Başka bir örnek, dahili olarak derlenmiş düzenli ifadeleri bellekteki statik önbelleğe saklayan `preg_*` düzenli ifadelerle çalışmak için fonksiyonlardır. Yani aynı düzenli ifadeyi kodun farklı yerlerinde birden çok kez çağırdığınızda, yalnızca bir kez derlenir. Önbellek performanstan tasarruf sağlar ve aynı zamanda kullanıcı için tamamen görünmezdir, bu yüzden böyle bir kullanım meşru kabul edilebilir. -Özet .[#toc-summary] --------------------- +Özet +---- -Bunun neden mantıklı olduğunu gösterdik +Neden mantıklı olduğunu ele aldık: -1) Koddan tüm statik değişkenleri kaldırın -2) Bağımlılıkları beyan edin -3) Ve bağımlılık enjeksiyonu kullanın +1) Koddan tüm statik değişkenleri kaldırmak +2) Bağımlılıkları bildirmek +3) Ve bağımlılık enjeksiyonu kullanmak -Kod tasarımını düşünürken, her `static $foo` adresinin bir sorunu temsil ettiğini unutmayın. Kodunuzun DI'ya saygılı bir ortam olması için, global durumu tamamen ortadan kaldırmak ve bağımlılık enjeksiyonu ile değiştirmek çok önemlidir. +Kod tasarımını düşündüğünüzde, her `static $foo`'nun bir sorun teşkil ettiğini düşünün. Kodunuzun DI'ye saygı duyan bir ortam olması için, global durumu tamamen ortadan kaldırmak ve onu bağımlılık enjeksiyonu kullanarak değiştirmek gereklidir. -Bu süreç sırasında, birden fazla sorumluluğu olduğu için bir sınıfı bölmeniz gerektiğini fark edebilirsiniz. Bu konuda endişelenmeyin; tek sorumluluk ilkesi için çaba gösterin. +Bu süreç sırasında, birden fazla sorumluluğu olduğu için sınıfı bölmek gerektiğini keşfedebilirsiniz. Bundan korkmayın; Tek Sorumluluk İlkesi'ni hedefleyin. -*[Flaw: Brittle Global State & Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/] gibi makaleleri bu bölümün temelini oluşturan Miško Hevery'ye teşekkür ederim.* +*Miško Hevery'ye teşekkür etmek isterim, [Flaw: Brittle Global State & Singletons |https://web.archive.org/web/20230321084133/http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/] gibi makaleleri bu bölümün temelini oluşturur.* diff --git a/dependency-injection/tr/introduction.texy b/dependency-injection/tr/introduction.texy index a388e5eb6b..c9db239b87 100644 --- a/dependency-injection/tr/introduction.texy +++ b/dependency-injection/tr/introduction.texy @@ -1,58 +1,58 @@ -Bağımlılık Enjeksiyonu Nedir? -***************************** +Dependency Injection Nedir? +*************************** .[perex] -Bu bölüm size herhangi bir uygulama yazarken izlemeniz gereken temel programlama uygulamalarını tanıtacaktır. Bunlar temiz, anlaşılabilir ve sürdürülebilir kod yazmak için gereken temel bilgilerdir. +Bu bölüm, tüm uygulamaları yazarken uymanız gereken temel programlama uygulamalarını size tanıtacaktır. Bunlar temiz, anlaşılır ve sürdürülebilir kod yazmak için gerekli temellerdir. -Bu kuralları öğrenir ve uygularsanız, Nette her adımda yanınızda olacaktır. Rutin görevleri sizin için halledecek ve maksimum konfor sağlayacaktır, böylece siz mantığın kendisine odaklanabilirsiniz. +Bu kuralları benimser ve uygularsanız, Nette her adımda size yardımcı olacaktır. Rutin görevleri sizin için halledecek ve mantığın kendisine odaklanabilmeniz için size maksimum rahatlık sağlayacaktır. -Burada göstereceğimiz ilkeler oldukça basittir. Hiçbir şey için endişelenmenize gerek yok. +Burada göstereceğimiz prensipler oldukça basittir. Korkacak bir şey yok. -İlk Programınızı Hatırlıyor musunuz? .[#toc-remember-your-first-program] ------------------------------------------------------------------------- +İlk Programınızı Hatırlıyor musunuz? +------------------------------------ -Hangi dilde yazdığınızı bilmiyoruz, ancak PHP olsaydı, şöyle bir şeye benzeyebilirdi: +Hangi dilde yazdığınızı bilmiyoruz, ancak PHP olsaydı, muhtemelen şöyle görünürdü: ```php -function toplami(float $a, float $b): float +function soucet(float $a, float $b): float { return $a + $b; } -echo toplami(23, 1); // 24 yazdırır +echo soucet(23, 1); // 24 yazdırır ``` -Birkaç önemsiz kod satırı, ancak içlerinde çok fazla anahtar kavram gizli. Değişkenler var. Kodun daha küçük birimlere ayrıldığı, örneğin fonksiyonlar. Onlara girdi argümanları iletiyoruz ve onlar da sonuç döndürüyorlar. Eksik olan tek şey koşullar ve döngüler. +Birkaç önemsiz kod satırı, ancak içlerinde çok sayıda anahtar kavram gizli. Değişkenlerin var olduğu. Kodun, örneğin fonksiyonlar gibi daha küçük birimlere ayrıldığı. Onlara girdi argümanları ilettiğimiz ve sonuçları döndürdükleri. Sadece koşullar ve döngüler eksik. -Bir fonksiyonun girdi verilerini alması ve bir sonuç döndürmesi, matematik gibi diğer alanlarda da kullanılan, tamamen anlaşılabilir bir kavramdır. +Fonksiyona girdi verilerini iletmemiz ve bir sonuç döndürmesi, matematikte olduğu gibi diğer alanlarda da kullanılan mükemmel anlaşılır bir kavramdır. -Bir fonksiyonun imzası vardır ve bu imza fonksiyonun adı, parametrelerin listesi ve tipleri ile son olarak geri dönüş değerinin tipinden oluşur. Kullanıcılar olarak biz imzayla ilgileniriz ve genellikle dahili uygulama hakkında bir şey bilmemiz gerekmez. +Bir fonksiyonun, adını, parametrelerinin ve türlerinin bir özetini ve son olarak dönüş değerinin türünü içeren bir imzası vardır. Kullanıcılar olarak bizi ilgilendiren imzadır; genellikle iç uygulama hakkında hiçbir şey bilmemize gerek yoktur. -Şimdi fonksiyon imzasının aşağıdaki gibi olduğunu hayal edin: +Şimdi fonksiyon imzasının şöyle göründüğünü hayal edin: ```php -function toplami(float $x): float +function soucet(float $x): float ``` -Tek parametreli bir ekleme mi? Bu çok garip. Peki ya bu? +Tek parametreli bir toplam mı? Bu garip… Peki ya şöyle? ```php -function toplami(): float +function soucet(): float ``` -Şimdi bu gerçekten garip, değil mi? Fonksiyon nasıl kullanılıyor? +Bu gerçekten çok garip, değil mi? Fonksiyon nasıl kullanılır? ```php -echo toplami(); // ne yazdırıyor? +echo soucet(); // ne yazdırır acaba? ``` -Böyle bir koda baktığımızda kafamız karışır. Sadece yeni başlayanlar değil, deneyimli bir programcı bile böyle bir kodu anlamayacaktır. +Böyle bir koda baktığımızda kafamız karışırdı. Sadece bir başlangıç seviyesindeki kişi anlamazdı, yetenekli bir programcı bile böyle bir kodu anlamazdı. -Böyle bir fonksiyonun içinde gerçekte neye benzeyeceğini merak ediyor musunuz? Toplamları nereden alırdı? Muhtemelen *bir şekilde* kendi kendine alırdı, belki de şu şekilde: +Böyle bir fonksiyonun içinde nasıl görüneceğini merak ediyor musunuz? Toplanacak sayıları nereden alır? Muhtemelen onları *bir şekilde* kendi başına elde ederdi, belki şöyle: ```php -function toplami(): float +function soucet(): float { $a = Input::get('a'); $b = Input::get('b'); @@ -60,71 +60,71 @@ function toplami(): float } ``` -İşlevin gövdesinde diğer işlevlere (veya statik yöntemlere) gizli bağlar olduğu ortaya çıkıyor ve eklerin gerçekte nereden geldiğini bulmak için daha fazla araştırmamız gerekiyor. +Fonksiyon gövdesinde, diğer global fonksiyonlara veya statik metotlara gizli bağlantılar keşfettik. Toplanacak sayıların gerçekten nereden geldiğini bulmak için daha fazla araştırmamız gerekiyor. -Bu şekilde değil! .[#toc-not-this-way] --------------------------------------- +Bu Yol Yanlış! +-------------- Az önce gösterdiğimiz tasarım, birçok olumsuz özelliğin özüdür: -- fonksiyon imzası toplamlara ihtiyaç duymuyormuş gibi davranıyordu, bu da kafamızı karıştırıyordu -- fonksiyonun diğer iki sayı ile nasıl hesaplanacağı hakkında hiçbir fikrimiz yok -- toplamların nereden geldiğini bulmak için koda bakmamız gerekiyordu -- gizli bağımlılıklar bulduk -- tam olarak anlaşılabilmesi için bu bağımlılıkların da incelenmesi gerekir +- fonksiyon imzası, toplanacak sayılara ihtiyaç duymuyormuş gibi davrandı, bu da kafamızı karıştırdı +- fonksiyonun başka iki sayıyı nasıl toplayacağını hiç bilmiyoruz +- toplanacak sayıları nereden aldığını bulmak için koda bakmak zorunda kaldık +- gizli bağlantılar keşfettik +- tam olarak anlamak için bu bağlantıları da incelemek gerekiyor -Peki girdileri tedarik etmek toplama işlevinin görevi midir? Elbette değildir. Onun sorumluluğu sadece eklemektir. +Ve girdileri elde etmek gerçekten toplama fonksiyonunun görevi mi? Tabii ki değil. Sorumluluğu sadece toplama işleminin kendisidir. -Böyle bir kodla karşılaşmak istemeyiz ve kesinlikle yazmak da istemeyiz. Çözüm basit: temellere geri dönün ve sadece parametreleri kullanın: +Böyle bir kodla karşılaşmak istemiyoruz ve kesinlikle yazmak istemiyoruz. Çözüm basit: temellere geri dönün ve sadece parametreleri kullanın: ```php -function toplami(float $a, float $b): float +function soucet(float $a, float $b): float { return $a + $b; } ``` -Kural #1: Sana geçirilsin .[#toc-rule-1-let-it-be-passed-to-you] ----------------------------------------------------------------- +Kural 1: Size İletilmesini Sağlayın +----------------------------------- -En önemli kural şudur: **Fonksiyonların veya sınıfların ihtiyaç duyduğu tüm veriler onlara aktarılmalıdır**. +En önemli kural şudur: **fonksiyonların veya sınıfların ihtiyaç duyduğu tüm veriler onlara iletilmelidir**. -Verilere kendilerinin erişmesi için gizli yollar icat etmek yerine, sadece parametreleri aktarın. Kodunuzu kesinlikle iyileştirmeyecek gizli yollar icat etmek için harcayacağınız zamandan tasarruf edeceksiniz. +Onlara bir şekilde kendi başlarına ulaşabilecekleri gizli yollar icat etmek yerine, parametreleri basitçe iletin. Kodunuzu kesinlikle iyileştirmeyecek gizli yollar icat etmek için gereken zamandan tasarruf edeceksiniz. -Her zaman ve her yerde bu kurala uyarsanız, gizli bağımlılıkları olmayan bir koda doğru yol alırsınız. Sadece yazan için değil, daha sonra okuyan herkes için de anlaşılabilir bir koda. Fonksiyonların ve sınıfların imzalarından her şeyin anlaşılabilir olduğu ve uygulamada gizli sırlar aramaya gerek olmadığı bir kod. +Bu kuralı her zaman ve her yerde uygularsanız, gizli bağlantıları olmayan bir koda giden yoldasınız demektir. Sadece yazar tarafından değil, ondan sonra okuyacak herkes tarafından anlaşılır olan bir koda. Fonksiyonların ve sınıfların imzalarından her şeyin anlaşılabildiği ve uygulamada gizli sırları aramaya gerek olmayan bir koda. -Bu teknik profesyonel olarak **bağımlılık enjeksiyonu** olarak adlandırılır. Ve bu verilere **bağımlılıklar** denir. Bu sadece sıradan bir parametre aktarımıdır, başka bir şey değildir. +Bu tekniğe profesyonel olarak **dependency injection** denir. Ve bu verilere **bağımlılıklar** denir. Aslında, bu sadece parametre iletmedir, başka bir şey değil. .[note] -Lütfen bir tasarım kalıbı olan bağımlılık enjeksiyonunu bir araç olan "bağımlılık enjeksiyon konteyneri" ile karıştırmayın, bu tamamen farklı bir şeydir. Kapsayıcılar ile daha sonra ilgileneceğiz. +Lütfen bir tasarım deseni olan dependency injection ile bir araç olan, yani tamamen farklı bir şey olan "dependency injection container"ı karıştırmayın. Konteynerleri daha sonra ele alacağız. -Fonksiyonlardan Sınıflara .[#toc-from-functions-to-classes] ------------------------------------------------------------ +Fonksiyonlardan Sınıflara +------------------------- -Peki sınıflar nasıl ilişkilidir? Bir sınıf basit bir fonksiyondan daha karmaşık bir birimdir, ancak 1. kural burada da tamamen geçerlidir. Sadece [argümanları aktarmanın daha fazla yolu |passing-dependencies] vardır. Örneğin, bir fonksiyonun durumuna oldukça benzer: +Peki bunun sınıflarla ne ilgisi var? Bir sınıf, basit bir fonksiyondan daha karmaşık bir bütündür, ancak Kural 1 burada da tamamen geçerlidir. Sadece [argümanları iletmenin daha fazla yolu|passing-dependencies] vardır. Örneğin, bir fonksiyona oldukça benzer şekilde: ```php -class Matematik +class Matematika { - public function toplami(float $a, float $b): float + public function soucet(float $a, float $b): float { return $a + $b; } } -$math = new Matematik; -echo $math->toplami(23, 1); // 24 +$math = new Matematika; +echo $math->soucet(23, 1); // 24 ``` -Veya diğer yöntemler aracılığıyla ya da doğrudan kurucu aracılığıyla: +Veya diğer metotlarla ya da doğrudan yapıcı ile: ```php -class Toplami +class Soucet { public function __construct( private float $a, @@ -132,26 +132,26 @@ class Toplami ) { } - public function calculate(): float + public function spocti(): float { return $this->a + $this->b; } } -$toplami = new Toplami(23, 1); -echo $toplami->calculate(); // 24 +$soucet = new Soucet(23, 1); +echo $soucet->spocti(); // 24 ``` -Her iki örnek de bağımlılık enjeksiyonu ile tamamen uyumludur. +Her iki örnek de dependency injection ile tamamen uyumludur. -Gerçek Hayattan Örnekler .[#toc-real-life-examples] ---------------------------------------------------- +Gerçek Hayat Örnekleri +---------------------- -Gerçek dünyada, sayıları toplamak için sınıflar yazmayacaksınız. Şimdi pratik örneklere geçelim. +Gerçek dünyada, sayıları toplamak için sınıflar yazmayacaksınız. Pratik örneklere geçelim. -Bir blog gönderisini temsil eden bir `Article` sınıfımız olsun: +Bir blog makalesini temsil eden bir `Article` sınıfımız olsun: ```php class Article @@ -162,23 +162,23 @@ class Article public function save(): void { - // makaleyi veritabanına kaydedin + // makaleyi veritabanına kaydedeceğiz } } ``` -ve kullanım aşağıdaki gibi olacaktır: +ve kullanımı şöyle olacaktır: ```php $article = new Article; -$article->title = '10 Things You Need to Know About Losing Weight'; -$article->content = 'Every year millions of people in ...'; +$article->title = 'Kilo Verme Hakkında Bilmeniz Gereken 10 Şey'; +$article->content = 'Her yıl milyonlarca insan ...'; $article->save(); ``` - `save()` yöntemi makaleyi bir veritabanı tablosuna kaydedecektir. Bunu [Nette Database |database:] kullanarak uygulamak, bir aksaklık olmasaydı, çocuk oyuncağı olacaktı: `Article` [veritabanı |database:] bağlantısını, yani `Nette\Database\Connection` sınıfının bir nesnesini nereden alacak? +`save()` metodu makaleyi bir veritabanı tablosuna kaydeder. [Nette Database |database:] kullanarak uygulamak çocuk oyuncağı olurdu, ancak bir engel var: `Article` veritabanı bağlantısını, yani `Nette\Database\Connection` sınıfının nesnesini nereden alacak? -Görünüşe göre birçok seçeneğimiz var. Bir yerdeki statik bir değişkenden alabilir. Ya da veritabanı bağlantısı sağlayan bir sınıftan miras alabilir. Ya da bir [singleton |global-state#Singleton]'dan yararlanabilir. Ya da Laravel'de kullanılan sözde facade'leri kullanabilir: +Görünüşe göre birçok seçeneğimiz var. Statik bir değişkenden alabilir. Veya veritabanı bağlantısını sağlayan bir sınıftan miras alabilir. Veya [singleton |global-state#Singleton] olarak adlandırılanı kullanabilir. Veya Laravel'de kullanılan facades olarak adlandırılanları kullanabilir: ```php use Illuminate\Support\Facades\DB; @@ -201,15 +201,15 @@ class Article Harika, sorunu çözdük. -Yoksa biz mi? +Ya da çözmedik mi? -[Kural 1: Sana geçirilsin |#rule #1: Let It Be Passed to You] Geçsin: sınıfın ihtiyaç duyduğu tüm bağımlılıklar ona geçmelidir. Çünkü bu kuralı ihlal edersek, gizli bağımlılıklarla dolu kirli bir koda, anlaşılmazlığa giden bir yola girmiş oluruz ve sonuçta bakımı ve geliştirilmesi sancılı bir uygulama ortaya çıkar. +[##Kural 1: Size İletilmesini Sağlayın] hatırlayalım: sınıfın ihtiyaç duyduğu tüm bağımlılıklar ona iletilmelidir. Çünkü kuralı ihlal edersek, gizli bağlantılarla dolu, anlaşılmaz, kirli bir koda giden yola girmiş oluruz ve sonuç, bakımı ve geliştirilmesi acı verici olacak bir uygulama olur. - `Article` sınıfının kullanıcısı, `save()` yönteminin makaleyi nerede sakladığı konusunda hiçbir fikre sahip değildir. Bir veritabanı tablosunda mı? Hangisinde, üretim mi test mi? Ve nasıl değiştirilebilir? +`Article` sınıfının kullanıcısı, `save()` metodunun makaleyi nereye kaydettiğini bilmiyor. Bir veritabanı tablosuna mı? Hangisine, canlıya mı yoksa test olanına mı? Ve bu nasıl değiştirilebilir? -Kullanıcı `save()` yönteminin nasıl uygulandığına bakmalı ve `DB::insert()` yönteminin kullanımını bulmalıdır. Dolayısıyla, bu yöntemin bir veritabanı bağlantısını nasıl elde ettiğini bulmak için daha fazla araştırma yapması gerekir. Ve gizli bağımlılıklar oldukça uzun bir zincir oluşturabilir. +Kullanıcı, `save()` metodunun nasıl uygulandığına bakmalı ve `DB::insert()` metodunun kullanımını bulmalıdır. Bu yüzden, bu metodun veritabanı bağlantısını nasıl elde ettiğini daha fazla araştırmalıdır. Ve gizli bağlantılar oldukça uzun bir zincir oluşturabilir. -Temiz ve iyi tasarlanmış kodda, hiçbir zaman gizli bağımlılıklar, Laravel cepheleri veya statik değişkenler yoktur. Temiz ve iyi tasarlanmış kodda, argümanlar geçirilir: +Temiz ve iyi tasarlanmış kodda asla gizli bağlantılar, Laravel facades veya statik değişkenler bulunmaz. Temiz ve iyi tasarlanmış kodda argümanlar iletilir: ```php class Article @@ -224,7 +224,7 @@ class Article } ``` -Daha sonra göreceğimiz gibi, daha da pratik bir yaklaşım kurucu aracılığıyla olacaktır: +Daha da pratik olanı, ileride göreceğimiz gibi, yapıcı ile olacaktır: ```php class Article @@ -245,11 +245,11 @@ class Article ``` .[note] -Deneyimli bir programcıysanız, `Article` adresinin bir `save()` yöntemine sahip olmaması gerektiğini düşünebilirsiniz; tamamen bir veri bileşenini temsil etmeli ve ayrı bir depo kaydetme işlemiyle ilgilenmelidir. Bu mantıklıdır. Ancak bu bizi bağımlılık enjeksiyonu olan konunun kapsamının ve basit örnekler sunma çabasının çok ötesine götürecektir. +Eğer deneyimli bir programcıysanız, muhtemelen `Article` sınıfının hiç `save()` metoduna sahip olmaması gerektiğini, tamamen bir veri bileşeni olması gerektiğini ve kaydetme işleminin ayrı bir depo tarafından yapılması gerektiğini düşünüyorsunuzdur. Bu mantıklı. Ancak bu bizi dependency injection konusunun çok ötesine ve basit örnekler verme çabasının dışına çıkarırdı. -Örneğin çalışması için bir veritabanına ihtiyaç duyan bir sınıf yazıyorsanız, veritabanını nereden alacağınızı icat etmeyin, ancak veritabanını geçirin. Ya kurucunun bir parametresi olarak ya da başka bir yöntemle. Bağımlılıkları kabul edin. Bunları sınıfınızın API'sinde kabul edin. Anlaşılabilir ve öngörülebilir bir kod elde edeceksiniz. +Faaliyeti için örneğin bir veritabanı gerektiren bir sınıf yazıyorsanız, onu nereden alacağınızı düşünmeyin, size iletilmesini sağlayın. Belki yapıcı veya başka bir metodun parametresi olarak. Bağımlılıkları kabul edin. Onları sınıfınızın API'sinde kabul edin. Anlaşılır ve öngörülebilir bir kod elde edeceksiniz. -Peki ya hata mesajlarını günlüğe kaydeden bu sınıf? +Peki ya hata mesajlarını günlüğe kaydeden bu sınıfa ne dersiniz: ```php class Logger @@ -262,21 +262,21 @@ class Logger } ``` -Ne düşünüyorsunuz, [kurala 1: Sana geçirilsin |#rule #1: Let It Be Passed to You] uyduk mu? +Sizce [##Kural 1: Size İletilmesini Sağlayın] uyduk mu? -Biz yapmadık. +Uymadık. -Anahtar bilgi, yani günlük dosyasının bulunduğu dizin, sınıfın kendisi tarafından sabitten *elde edilir*. +Anahtar bilgi, yani günlük dosyasının bulunduğu dizin, sınıf tarafından *kendi başına* bir sabitten elde ediliyor. Kullanım örneğine bakın: ```php $logger = new Logger; -$logger->log('The temperature is 23 °C'); -$logger->log('The temperature is 10 °C'); +$logger->log('Sıcaklık 23 °C'); +$logger->log('Sıcaklık 10 °C'); ``` -Uygulamayı bilmeden, mesajların nereye yazıldığı sorusuna cevap verebilir misiniz? Çalışması için `LOG_DIR` sabitinin varlığının gerekli olduğunu tahmin eder miydiniz? Ve farklı bir konuma yazacak ikinci bir örnek oluşturabilir misiniz? Kesinlikle hayır. +Uygulamayı bilmeden, mesajların nereye yazıldığı sorusunu cevaplayabilir miydiniz? Çalışması için `LOG_DIR` sabitinin varlığının gerekli olduğunu düşünür müydünüz? Ve başka bir yere yazacak ikinci bir örnek oluşturabilir miydiniz? Kesinlikle hayır. Sınıfı düzeltelim: @@ -295,24 +295,24 @@ class Logger } ``` -Sınıf artık çok daha anlaşılır, yapılandırılabilir ve dolayısıyla daha kullanışlı. +Sınıf şimdi çok daha anlaşılır, yapılandırılabilir ve dolayısıyla daha kullanışlı. ```php $logger = new Logger('/path/to/log.txt'); -$logger->log('The temperature is 15 °C'); +$logger->log('Sıcaklık 15 °C'); ``` -Ama umurumda değil! .[#toc-but-i-don-t-care] --------------------------------------------- +Ama Bu Beni İlgilendirmiyor! +---------------------------- -*"Bir Article nesnesi oluşturup save() işlevini çağırdığımda, veritabanıyla uğraşmak istemiyorum; sadece yapılandırmada ayarladığım veritabanına kaydedilmesini istiyorum."* +*„Bir Article nesnesi oluşturup save() çağırdığımda, veritabanıyla uğraşmak istemiyorum, sadece yapılandırmada ayarladığım veritabanına kaydedilmesini istiyorum.“* -*"Logger kullandığımda, sadece mesajın yazılmasını istiyorum ve nerede olduğuyla uğraşmak istemiyorum. Bırakın global ayarlar kullanılsın."* +*„Logger kullandığımda, sadece mesajın yazılmasını istiyorum ve nereye yazılacağıyla ilgilenmek istemiyorum. Global ayar kullanılsın.“* -Bunlar geçerli noktalar. +Bunlar doğru yorumlar. -Örnek olarak, haber bültenleri gönderen ve nasıl gittiğini günlüğe kaydeden bir sınıfa bakalım: +Örnek olarak, bültenleri dağıtan ve sonucunu günlüğe kaydeden bir sınıf göstereceğiz: ```php class NewsletterDistributor @@ -322,27 +322,27 @@ class NewsletterDistributor $logger = new Logger(/* ... */); try { $this->sendEmails(); - $logger->log('Emails have been sent out'); + $logger->log('E-postalar gönderildi'); } catch (Exception $e) { - $logger->log('An error occurred during the sending'); + $logger->log('Gönderim sırasında bir hata oluştu'); throw $e; } } } ``` -Artık `LOG_DIR` sabitini kullanmayan geliştirilmiş `Logger`, dosya yolunun yapıcıda belirtilmesini gerektirir. Bu nasıl çözülür? `NewsletterDistributor` sınıfı mesajların nereye yazıldığı ile ilgilenmez; sadece onları yazmak ister. +Artık `LOG_DIR` sabitini kullanmayan geliştirilmiş `Logger`, yapıcısında dosya yolunun belirtilmesini gerektiriyor. Bunu nasıl çözeceğiz? `NewsletterDistributor` sınıfı mesajların nereye yazıldığıyla hiç ilgilenmiyor, sadece onları yazmak istiyor. -Çözüm yine [1 numaralı kuraldır: Bırakın Size |#rule #1: Let It Be Passed to You] Geçsin: sınıfın ihtiyaç duyduğu tüm verileri iletin. +Çözüm yine [##Kural 1: Size İletilmesini Sağlayın]: sınıfın ihtiyaç duyduğu tüm verileri ona iletiyoruz. -Yani bu, günlüğe giden yolu kurucu aracılığıyla ilettiğimiz ve daha sonra `Logger` nesnesini oluştururken kullandığımız anlamına mı geliyor? +Yani bu, `Logger` nesnesini oluştururken kullanacağımız günlük yolunu yapıcı aracılığıyla ileteceğimiz anlamına mı geliyor? ```php class NewsletterDistributor { public function __construct( - private string $file, // ⛔ BU ŞEKILDE DEĞIL! + private string $file, // ⛔ BU ŞEKİLDE DEĞİL! ) { } @@ -351,7 +351,7 @@ class NewsletterDistributor $logger = new Logger($this->file); ``` -Hayır, bu şekilde değil! Yol, `NewsletterDistributor` sınıfının ihtiyaç duyduğu veriler arasında yer almaz; aslında `Logger` 'un buna ihtiyacı vardır. Aradaki farkı görüyor musunuz? `NewsletterDistributor` sınıfının logger'ın kendisine ihtiyacı var. Bu yüzden bunu geçeceğiz: +Bu şekilde değil! Çünkü yol, `NewsletterDistributor` sınıfının ihtiyaç duyduğu veriler arasında **değildir**; bunlara `Logger` ihtiyaç duyar. Farkı anlıyor musunuz? `NewsletterDistributor` sınıfı, logger'ın kendisine ihtiyaç duyar. Bu yüzden onu ileteceğiz: ```php class NewsletterDistributor @@ -365,35 +365,33 @@ class NewsletterDistributor { try { $this->sendEmails(); - $this->logger->log('Emails have been sent out'); + $this->logger->log('E-postalar gönderildi'); } catch (Exception $e) { - $this->logger->log('An error occurred during the sending'); + $this->logger->log('Gönderim sırasında bir hata oluştu'); throw $e; } } } ``` -Artık `NewsletterDistributor` sınıfının imzalarından, günlük tutmanın da işlevselliğinin bir parçası olduğu açıkça anlaşılmaktadır. Ve belki de test için kaydediciyi başka bir kaydediciyle değiştirme görevi tamamen önemsizdir. -Dahası, `Logger` sınıfının kurucusu değişirse, bu bizim sınıfımızı etkilemeyecektir. +Şimdi `NewsletterDistributor` sınıfının imzalarından, işlevselliğinin bir parçası olarak günlüklemenin de olduğu açıktır. Ve logger'ı başka biriyle değiştirmek, örneğin test için, tamamen önemsizdir. Ayrıca, `Logger` sınıfının yapıcısı değişirse, bunun sınıfımız üzerinde hiçbir etkisi olmayacaktır. -Kural #2: Senin Olanı Al .[#toc-rule-2-take-what-s-yours] ---------------------------------------------------------- +Kural 2: Sadece Size Ait Olanı Alın +----------------------------------- -Yanlış yönlendirilmeyin ve bağımlılıklarınızın bağımlılıklarını kendinizin geçmesine izin vermeyin. Sadece kendi bağımlılıklarınızı geçirin. +Kafanızın karışmasına izin vermeyin ve bağımlılıklarınızın bağımlılıklarını size iletmeyin. Sadece kendi bağımlılıklarınızı size iletin. -Bu sayede, diğer nesneleri kullanan kod, yapıcılarındaki değişikliklerden tamamen bağımsız olacaktır. API'si daha doğru olacaktır. Ve hepsinden önemlisi, bu bağımlılıkları başkalarıyla değiştirmek önemsiz olacaktır. +Bu sayede, diğer nesneleri kullanan kod, yapıcılarındaki değişikliklerden tamamen bağımsız olacaktır. API'si daha doğru olacaktır. Ve en önemlisi, bu bağımlılıkları başkalarıyla değiştirmek önemsiz olacaktır. -Yeni Aile Üyesi .[#toc-new-family-member] ------------------------------------------ +Aileye Yeni Üye +--------------- -Geliştirme ekibi, veritabanına yazan ikinci bir logger oluşturmaya karar verdi. Böylece bir `DatabaseLogger` sınıfı oluşturduk. Yani iki sınıfımız var, `Logger` ve `DatabaseLogger`, biri bir dosyaya, diğeri bir veritabanına yazıyor ... adlandırma size de garip gelmiyor mu? - `Logger` 'u `FileLogger` olarak yeniden adlandırmak daha iyi olmaz mıydı? Kesinlikle evet. +Geliştirme ekibinde, veritabanına yazan ikinci bir logger oluşturma kararı alındı. Bu yüzden `DatabaseLogger` sınıfını oluşturacağız. Yani iki sınıfımız var, `Logger` ve `DatabaseLogger`, biri dosyaya yazıyor, diğeri veritabanına… Bu isimlendirmede size garip gelen bir şey yok mu? `Logger`ı `FileLogger` olarak yeniden adlandırmak daha iyi olmaz mıydı? Kesinlikle evet. -Ama bunu akıllıca yapalım. Orijinal isim altında bir arayüz oluşturalım: +Ama bunu akıllıca yapacağız. Orijinal ad altında bir arayüz oluşturacağız: ```php interface Logger @@ -402,7 +400,7 @@ interface Logger } ``` -... her iki kaydedicinin de uygulayacağı: +… her iki logger da bunu uygulayacak: ```php class FileLogger implements Logger @@ -412,17 +410,17 @@ class DatabaseLogger implements Logger // ... ``` -Ve bu nedenle, logger'ın kullanıldığı kodun geri kalanında hiçbir şeyi değiştirmeye gerek kalmayacaktır. Örneğin, `NewsletterDistributor` sınıfının kurucusu hala parametre olarak `Logger` 'a ihtiyaç duymakla yetinecektir. Ve hangi örneği geçireceğimiz bize bağlı olacaktır. +Ve bu sayede, logger'ın kullanıldığı kodun geri kalanında hiçbir şeyi değiştirmeye gerek kalmayacak. Örneğin, `NewsletterDistributor` sınıfının yapıcısı, parametre olarak `Logger` gerektirmesinden hala memnun olacaktır. Ve hangi örneği ona ileteceğimiz bize kalmış olacak. -**Bu yüzden arayüz isimlerine asla `Interface` son ekini veya `I` ön ekini eklemiyoruz.** Aksi takdirde kodu bu kadar güzel geliştirmek mümkün olmazdı. +**Bu nedenle arayüz adlarına asla `Interface` sonekini veya `I` önekini vermeyiz.** Aksi takdirde, kodu bu kadar güzel bir şekilde geliştirmek mümkün olmazdı. -Houston, Bir Sorunumuz Var .[#toc-houston-we-have-a-problem] ------------------------------------------------------------- +Houston, Bir Problemimiz Var +---------------------------- -İster dosya tabanlı ister veritabanı tabanlı olsun, tüm uygulama boyunca tek bir logger örneğiyle idare edebilir ve bir şeyin günlüğe kaydedildiği her yerde onu basitçe geçirebilirken, `Article` sınıfı için durum oldukça farklıdır. Gerektiğinde, hatta birden çok kez örneklerini oluşturuyoruz. Kurucusundaki veritabanı bağımlılığı ile nasıl başa çıkılır? +Tüm uygulamada, ister dosya tabanlı ister veritabanı tabanlı olsun, tek bir logger örneğiyle idare edebilir ve onu bir şeylerin günlüğe kaydedildiği her yere basitçe iletebilirken, `Article` sınıfı durumunda durum oldukça farklıdır. Örneklerini ihtiyaca göre, hatta birden çok kez oluştururuz. Yapıcısındaki veritabanı bağımlılığıyla nasıl başa çıkılır? -Örnek olarak, bir form gönderdikten sonra bir makaleyi veritabanına kaydetmesi gereken bir denetleyici verilebilir: +Örnek olarak, bir form gönderildikten sonra makaleyi veritabanına kaydetmesi gereken bir denetleyici (controller) hizmet edebilir: ```php class EditController extends Controller @@ -437,30 +435,30 @@ class EditController extends Controller } ``` -Olası bir çözüm açıktır: veritabanı nesnesini `EditController` kurucusuna aktarın ve `$article = new Article($this->db)` adresini kullanın. +Olası bir çözüm kendini gösteriyor: veritabanı nesnesini yapıcı aracılığıyla `EditController`'a iletelim ve `$article = new Article($this->db)` kullanalım. -Tıpkı `Logger` ve dosya yolu ile ilgili önceki durumda olduğu gibi, bu doğru bir yaklaşım değildir. Veritabanı `EditController`'un değil, `Article`'un bir bağımlılığıdır. Veritabanını geçmek 2. [kurala |#rule #2: take what's yours] aykırıdır [: senin olanı al |#rule #2: take what's yours]. `Article` sınıf kurucusu değişirse (yeni bir parametre eklenirse), örneklerin oluşturulduğu her yerde kodu değiştirmeniz gerekecektir. Ufff. +Önceki `Logger` ve dosya yolu örneğinde olduğu gibi, bu doğru bir yaklaşım değildir. Veritabanı `EditController`'ın değil, `Article`'ın bir bağımlılığıdır. Bu nedenle veritabanını iletmek [#Kural 2: Sadece Size Ait Olanı Alın] aykırıdır. `Article` sınıfının yapıcısı değiştiğinde (yeni bir parametre eklendiğinde), örneklerin oluşturulduğu tüm yerlerdeki kodu da değiştirmek gerekecektir. Ufff. -Houston, ne öneriyorsun? +Houston, ne önerirsin? -Kural #3: Bırakın Fabrika Halletsin .[#toc-rule-3-let-the-factory-handle-it] ----------------------------------------------------------------------------- +Kural 3: Fabrikaya Bırakın +-------------------------- -Gizli bağımlılıkları ortadan kaldırarak ve tüm bağımlılıkları argüman olarak geçirerek, daha yapılandırılabilir ve esnek sınıflar elde ettik. Bu nedenle, bizim için bu daha esnek sınıfları oluşturmak ve yapılandırmak için başka bir şeye ihtiyacımız var. Biz buna fabrikalar diyeceğiz. +Gizli bağlantıları kaldırarak ve tüm bağımlılıkları argüman olarak ileterek, daha yapılandırılabilir ve esnek sınıflar elde ettik. Ve dolayısıyla, bu daha esnek sınıfları bizim için oluşturacak ve yapılandıracak başka bir şeye ihtiyacımız var. Buna fabrikalar diyeceğiz. -Temel kural şudur: Bir sınıfın bağımlılıkları varsa, örneklerinin oluşturulmasını fabrikaya bırakın. +Kural şudur: Bir sınıfın bağımlılıkları varsa, örneklerinin oluşturulmasını bir fabrikaya bırakın. -Fabrikalar, bağımlılık enjeksiyonu dünyasında `new` operatörü için daha akıllı bir alternatiftir. +Fabrikalar, dependency injection dünyasında `new` operatörünün daha akıllı bir alternatifidir. .[note] -Lütfen fabrikaları kullanmanın belirli bir yolunu tanımlayan ve bu konuyla ilgili olmayan *factory method* tasarım kalıbı ile karıştırmayın. +Lütfen fabrikaların belirli bir kullanım şeklini tanımlayan ve bu konuyla ilgisi olmayan *factory method* tasarım deseniyle karıştırmayın. -Fabrika .[#toc-factory] ------------------------ +Fabrika +------- -Fabrika, nesneleri oluşturan ve yapılandıran bir yöntem veya sınıftır. `Article` üreten sınıfı `ArticleFactory` olarak adlandıracağız ve şu şekilde görünebilir: +Fabrika, nesneleri üreten ve yapılandıran bir metot veya sınıftır. `Article` üreten sınıfı `ArticleFactory` olarak adlandıracağız ve örneğin şöyle görünebilir: ```php class ArticleFactory @@ -477,7 +475,7 @@ class ArticleFactory } ``` -Kontrolördeki kullanımı aşağıdaki gibi olacaktır: +Denetleyicideki kullanımı şöyle olacaktır: ```php class EditController extends Controller @@ -489,7 +487,7 @@ class EditController extends Controller public function formSubmitted($data) { - // fabrikanın bir nesne oluşturmasına izin verin + // fabrikanın nesneyi oluşturmasına izin veriyoruz $article = $this->articleFactory->create(); $article->title = $data->title; $article->content = $data->content; @@ -498,11 +496,11 @@ class EditController extends Controller } ``` -Bu noktada, `Article` sınıf kurucusunun imzası değişirse, kodun tepki vermesi gereken tek kısmı `ArticleFactory` 'un kendisidir. `Article` nesneleriyle çalışan `EditController` gibi diğer tüm kodlar etkilenmeyecektir. +Bu noktada `Article` sınıfının yapıcısının imzası değişirse, buna tepki vermesi gereken tek kod parçası `ArticleFactory` fabrikasının kendisidir. `Article` nesneleriyle çalışan diğer tüm kodlar, örneğin `EditController`, bundan hiçbir şekilde etkilenmeyecektir. -İşleri gerçekten daha iyi hale getirip getirmediğimizi merak ediyor olabilirsiniz. Kod miktarı arttı ve hepsi şüpheli bir şekilde karmaşık görünmeye başladı. +Belki şimdi kendinize hiç yardımcı olup olmadığımızı merak ederek alnınıza vuruyorsunuzdur. Kod miktarı arttı ve her şey şüpheli bir şekilde karmaşık görünmeye başladı. -Merak etmeyin, yakında Nette DI konteynerine geçeceğiz. Ve bağımlılık enjeksiyonu kullanarak uygulama oluşturmayı büyük ölçüde basitleştirecek birkaç hilesi var. Örneğin, `ArticleFactory` sınıfı yerine sadece [basit bir arayüz yazmanız |factory] gerekecek: +Endişelenmeyin, birazdan Nette DI konteynerine geleceğiz. Ve dependency injection kullanan uygulamalar oluşturmayı son derece basitleştiren birçok numarası var. Örneğin, `ArticleFactory` sınıfı yerine [sadece bir arayüz yazın |factory] yeterli olacaktır: ```php interface ArticleFactory @@ -511,18 +509,18 @@ interface ArticleFactory } ``` -Ama biz kendimizi aşıyoruz; lütfen sabırlı olun :-) +Ama acele ediyoruz, biraz daha bekleyin :-) -Özet .[#toc-summary] --------------------- +Özet +---- -Bu bölümün başında size temiz kod tasarlamak için bir süreç göstereceğimize söz vermiştik. Tek gereken sınıflar için +Bu bölümün başında, temiz kod tasarlamak için bir prosedür göstereceğimize söz vermiştik. Sınıflara sadece şunları yapmak yeterlidir: -- [ihtiyaç duydukları bağımlılıkları iletir |#Rule #1: Let It Be Passed to You] -- [tersine, doğrudan ihtiyaç duymadıkları şeyleri vermemek |#Rule #2: Take What's Yours] -- [ve bağımlılıkları olan nesnelerin en iyi fabrikalarda oluşturulduğunu |#Rule #3: Let the Factory Handle it] +1) [ihtiyaç duydukları bağımlılıkları iletin |#Kural 1: Size İletilmesini Sağlayın] +2) [ve doğrudan ihtiyaç duymadıklarını iletmeyin |#Kural 2: Sadece Size Ait Olanı Alın] +3) [ve bağımlılıkları olan nesnelerin en iyi fabrikalarda üretildiği |#Kural 3: Fabrikaya Bırakın] -İlk bakışta, bu üç kuralın geniş kapsamlı sonuçları yokmuş gibi görünebilir, ancak kod tasarımına kökten farklı bir bakış açısı getirirler. Buna değer mi? Eski alışkanlıklarını terk edip bağımlılık enjeksiyonunu tutarlı bir şekilde kullanmaya başlayan geliştiriciler, bu adımı profesyonel yaşamlarında çok önemli bir an olarak görüyorlar. Onlar için açık ve sürdürülebilir uygulamalar dünyasının kapılarını açmıştır. +İlk bakışta öyle görünmeyebilir, ancak bu üç kuralın geniş kapsamlı sonuçları vardır. Kod tasarımına kökten farklı bir bakış açısına yol açarlar. Buna değer mi? Eski alışkanlıklarını bırakan ve tutarlı bir şekilde dependency injection kullanmaya başlayan programcılar, bu adımı profesyonel yaşamlarında önemli bir an olarak görürler. Onlara açık ve sürdürülebilir uygulamaların dünyasını açtı. -Peki ya kod sürekli olarak bağımlılık enjeksiyonu kullanmıyorsa? Ya statik yöntemlere ya da tekillere dayanıyorsa? Bu herhangi bir soruna neden olur mu? [Evet, neden olur, hem de çok temel |global-state] sorunlara. +Peki ya kod tutarlı bir şekilde dependency injection kullanmıyorsa? Statik metotlara veya singleton'lara dayanıyorsa ne olur? Bu herhangi bir sorun yaratır mı? [Getirir ve çok temeldir |global-state]. diff --git a/dependency-injection/tr/nette-container.texy b/dependency-injection/tr/nette-container.texy index 9b5d18c2e6..760f3addcc 100644 --- a/dependency-injection/tr/nette-container.texy +++ b/dependency-injection/tr/nette-container.texy @@ -2,9 +2,9 @@ Nette DI Konteyner ****************** .[perex] -Nette DI, en ilginç Nette kütüphanelerinden biridir. Son derece hızlı ve yapılandırması inanılmaz derecede kolay olan derlenmiş DI kapsayıcılarını oluşturabilir ve otomatik olarak güncelleyebilir. +Nette DI, Nette'nin en ilginç kütüphanelerinden biridir. Son derece hızlı ve şaşırtıcı derecede kolay yapılandırılabilen derlenmiş DI konteynerlerini üretebilir ve otomatik olarak güncelleyebilir. -Bir DI konteyneri tarafından oluşturulacak servisler genellikle [NEON formatında |neon:format] yapılandırma dosyaları kullanılarak tanımlanır. Bir [önceki bölümde |container] manuel olarak oluşturduğumuz konteyner aşağıdaki gibi yazılırdı: +DI konteynerinin oluşturması gereken servislerin şeklini genellikle [NEON formatı|neon:format] yapılandırma dosyaları kullanarak tanımlarız. [önceki bölümde|container] manuel olarak oluşturduğumuz konteyner şöyle yazılırdı: ```neon parameters: @@ -19,16 +19,15 @@ services: - UserController ``` -Notasyon gerçekten çok kısa. +Yazım gerçekten kısa ve özdür. -`ArticleFactory` ve `UserController` sınıflarının kurucularında bildirilen tüm bağımlılıklar, [otomatik |autowiring] bağlantı adı verilen özellik sayesinde Nette DI tarafından bulunur ve aktarılır, bu nedenle yapılandırma dosyasında herhangi bir şey belirtmeye gerek yoktur. -Dolayısıyla parametreler değişse bile yapılandırmada herhangi bir değişiklik yapmanıza gerek yoktur. Nette konteyneri otomatik olarak yeniden oluşturacaktır. Siz de tamamen uygulama geliştirmeye odaklanabilirsiniz. +`ArticleFactory` ve `UserController` sınıflarının yapıcılarında bildirilen tüm bağımlılıklar, Nette DI tarafından sözde [autowiring|autowiring] sayesinde otomatik olarak bulunur ve iletilir, bu nedenle yapılandırma dosyasında hiçbir şey belirtmeye gerek yoktur. Bu nedenle, parametreler değişse bile yapılandırmada hiçbir şeyi değiştirmeniz gerekmez. Nette konteyneri otomatik olarak yeniden oluşturur. Orada tamamen uygulama geliştirmeye odaklanabilirsiniz. -Bağımlılıkları ayarlayıcıları kullanarak aktarmak istiyorsanız, bunu yapmak için [kurulum |services#setup] bölümünü kullanın. +Bağımlılıkları setter'lar kullanarak iletmek istiyorsak, bunun için [setup |services#Setup] bölümünü kullanırız. -Nette DI, konteyner için PHP kodunu doğrudan oluşturacaktır. Sonuç olarak, açıp inceleyebileceğiniz bir `.php` dosyası ortaya çıkar. Bu, konteynerin tam olarak nasıl çalıştığını görmenizi sağlar. Ayrıca IDE'de hata ayıklayabilir ve üzerinden geçebilirsiniz. Ve en önemlisi: üretilen PHP son derece hızlıdır. +Nette DI, konteynerin PHP kodunu doğrudan üretir. Sonuç, açıp inceleyebileceğiniz bir `.php` dosyasıdır. Bu sayede konteynerin tam olarak nasıl çalıştığını görebilirsiniz. Ayrıca IDE'de hata ayıklayabilir ve adım adım ilerleyebilirsiniz. Ve en önemlisi: üretilen PHP son derece hızlıdır. -Nette DI, sağlanan arayüze dayalı olarak [fabrika |factory] kodu da üretebilir. Bu nedenle, `ArticleFactory` sınıfı yerine, uygulamada sadece bir arayüz oluşturmamız gerekir: +Nette DI ayrıca sağlanan arayüze dayalı olarak [fabrikalar|factory] için kod üretebilir. Bu nedenle, `ArticleFactory` sınıfı yerine uygulamada sadece bir arayüz oluşturmamız yeterli olacaktır: ```php interface ArticleFactory @@ -37,19 +36,19 @@ interface ArticleFactory } ``` -Örneğin tamamını [GitHub'da |https://github.com/nette-examples/di-example-doc] bulabilirsiniz. +Örneğin tamamını [GitHub'da|https://github.com/nette-examples/di-example-doc] bulabilirsiniz. -Bağımsız Kullanım .[#toc-standalone-use] ----------------------------------------- +Bağımsız Kullanım +----------------- -Nette DI kütüphanesini bir uygulamada kullanmak çok kolaydır. Önce Composer ile yüklüyoruz (çünkü zip dosyalarını indirmek çok eski): +Nette DI kütüphanesini bir uygulamaya dağıtmak çok kolaydır. Önce Composer ile kurarız (çünkü zip indirmek çooook eski moda): ```shell composer require nette/di ``` -Aşağıdaki kod, `config.neon` dosyasında saklanan yapılandırmaya göre DI konteynerinin bir örneğini oluşturur: +Aşağıdaki kod, `config.neon` dosyasında saklanan yapılandırmaya göre bir DI konteyneri örneği oluşturur: ```php $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp'); @@ -59,24 +58,23 @@ $class = $loader->load(function ($compiler) { $container = new $class; ``` -Konteyner yalnızca bir kez oluşturulur, kodu önbelleğe ( `__DIR__ . '/temp'` dizini) yazılır ve sonraki isteklerde yalnızca oradan okunur. +Konteyner yalnızca bir kez üretilir, kodu önbelleğe (`__DIR__ . '/temp'` dizini) yazılır ve sonraki isteklerde yalnızca buradan yüklenir. -`getService()` veya `getByType()` yöntemleri hizmetleri oluşturmak ve almak için kullanılır. `UserController` nesnesini bu şekilde oluşturuyoruz: +Servisleri oluşturmak ve almak için `getService()` veya `getByType()` metotları kullanılır. Bu şekilde `UserController` nesnesini oluştururuz: ```php -$database = $container->getByType(UserController::class); -$database->query('...'); +$controller = $container->getByType(UserController::class); +$controller->someMethod(); ``` -Geliştirme sırasında, herhangi bir sınıf veya yapılandırma dosyası değiştirildiğinde konteynerin otomatik olarak yeniden oluşturulduğu otomatik yenileme modunu etkinleştirmek yararlıdır. Sadece `ContainerLoader` kurucusunda ikinci argüman olarak `true` girin. +Geliştirme sırasında, herhangi bir sınıf veya yapılandırma dosyası değiştiğinde konteynerin otomatik olarak yeniden oluşturulduğu otomatik yenileme modunu etkinleştirmek faydalıdır. `ContainerLoader` yapıcısında ikinci argüman olarak `true` belirtmek yeterlidir. ```php $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp', true); ``` -Nette Framework ile Kullanımı .[#toc-using-it-with-the-nette-framework] ------------------------------------------------------------------------ +Nette Framework ile Kullanım +---------------------------- -Gösterdiğimiz gibi, Nette DI kullanımı Nette Framework'te yazılan uygulamalarla sınırlı değildir, sadece 3 satır kodla herhangi bir yere dağıtabilirsiniz. -Ancak, Nette Framework'te uygulama geliştiriyorsanız, konteynerin yapılandırılması ve oluşturulması [Bootstrap |application:bootstrap#toc-di-container-configuration] tarafından ele alınır. +Gösterdiğimiz gibi, Nette DI kullanımı Nette Framework ile yazılmış uygulamalarla sınırlı değildir, sadece 3 satır kodla herhangi bir yere dağıtabilirsiniz. Ancak, Nette Framework'te uygulamalar geliştiriyorsanız, konteynerin yapılandırılması ve oluşturulmasından [Bootstrap |application:bootstrapping#DI Konteyner Yapılandırması] sorumludur. diff --git a/dependency-injection/tr/passing-dependencies.texy b/dependency-injection/tr/passing-dependencies.texy index 6cb6795534..515799eede 100644 --- a/dependency-injection/tr/passing-dependencies.texy +++ b/dependency-injection/tr/passing-dependencies.texy @@ -1,24 +1,24 @@ -Bağımlılıkları Geçme -******************** +Bağımlılıkların İletilmesi +************************** <div class=perex> -Argümanlar veya DI terminolojisinde "bağımlılıklar" sınıflara aşağıdaki ana yollarla aktarılabilir: +Argümanlar veya DI terminolojisinde "bağımlılıklar", sınıflara şu ana yollarla iletilebilir: -* kurucu tarafından geçiş -* yöntemle geçiş (setter olarak adlandırılır) -* bir özellik ayarlayarak -* yöntem, ek açıklama veya öznitelik ile *enjekte* +* yapıcı ile iletme +* metot ile iletme (sözde setter) +* değişken ayarlayarak +* *inject* metodu, anotasyonu veya niteliği ile </div> -Şimdi farklı varyantları somut örneklerle açıklayacağız. +Şimdi farklı varyantları belirli örneklerle göstereceğiz. -Kurucu Enjeksiyonu .[#toc-constructor-injection] -================================================ +Yapıcı ile İletme +================= -Nesne oluşturulduğunda bağımlılıklar yapıcıya argüman olarak aktarılır: +Bağımlılıklar, nesne oluşturma anında yapıcı argümanları olarak iletilir: ```php class MyClass @@ -34,9 +34,9 @@ class MyClass $obj = new MyClass($cache); ``` -Bu form, sınıfın çalışması için kesinlikle ihtiyaç duyduğu zorunlu bağımlılıklar için kullanışlıdır, çünkü bunlar olmadan örnek oluşturulamaz. +Bu form, sınıfın işlevi için mutlaka ihtiyaç duyduğu zorunlu bağımlılıklar için uygundur, çünkü onlarsız örnek oluşturulamaz. -PHP 8.0'dan beri, işlevsel olarak eşdeğer olan daha kısa bir gösterim biçimi kullanabiliriz ([constructor property promotion |https://blog.nette.org/tr/php-8-0-haberlere-genel-bakis#toc-constructor-property-promotion]): +PHP 8.0'dan itibaren, işlevsel olarak eşdeğer olan daha kısa bir yazım ([constructor property promotion |https://blog.nette.org/tr/php-8-0-complete-overview-of-news#toc-constructor-property-promotion]) kullanabiliriz: ```php // PHP 8.0 @@ -49,7 +49,7 @@ class MyClass } ``` -PHP 8.1'den itibaren bir özellik, içeriğinin değişmeyeceğini bildiren `readonly` bayrağı ile işaretlenebilir: +PHP 8.1'den itibaren, değişkenin içeriğinin artık değişmeyeceğini bildiren `readonly` bayrağıyla işaretlenebilir: ```php // PHP 8.1 @@ -62,13 +62,13 @@ class MyClass } ``` -DI konteyneri, [otomatik |autowiring] bağlantı kullanarak bağımlılıkları otomatik olarak kurucuya geçirir. Bu şekilde aktarılamayan bağımsız değişkenler (örn. dizeler, sayılar, booleanlar) [yapılandırmada yazılır |services#Arguments]. +DI konteyneri, yapıcıya bağımlılıkları [autowiring |autowiring] kullanarak otomatik olarak iletir. Bu şekilde iletilemeyen argümanlar (örneğin dizeler, sayılar, boolean'lar) [yapılandırmada belirtiriz |services#Argümanlar]. -İnşaatçı Cehennemi .[#toc-constructor-hell] -------------------------------------------- +Constructor Hell +---------------- -*İnşaatçı cehennemi* terimi, bir çocuğun yapıcısı bağımlılıklar gerektiren bir ana sınıftan miras aldığı ve çocuğun da bağımlılıklara ihtiyaç duyduğu bir durumu ifade eder. Ayrıca ebeveynin bağımlılıklarını da devralmalı ve aktarmalıdır: +*Constructor hell* terimi, bir alt sınıfın, yapıcısı bağımlılıklar gerektiren bir üst sınıftan miras aldığı ve aynı zamanda alt sınıfın da bağımlılıklar gerektirdiği durumu ifade eder. Bu durumda, üst sınıfın bağımlılıklarını da alıp iletmesi gerekir: ```php abstract class BaseClass @@ -94,11 +94,11 @@ final class MyClass extends BaseClass } ``` -Sorun, `BaseClass` sınıfının kurucusunu değiştirmek istediğimizde, örneğin yeni bir bağımlılık eklendiğinde ortaya çıkar. O zaman tüm alt sınıfların kurucularını da değiştirmemiz gerekir. Bu da böyle bir değişikliği cehenneme çevirir. +Sorun, `BaseClass` sınıfının yapıcısını değiştirmek istediğimizde ortaya çıkar, örneğin yeni bir bağımlılık eklendiğinde. O zaman tüm alt sınıfların yapıcılarını da değiştirmek gerekir. Bu da böyle bir değişikliği cehenneme çevirir. -Bu nasıl önlenebilir? Çözüm **kalıtım yerine bileşime öncelik vermektir**. +Bundan nasıl kaçınılır? Çözüm, **[kalıtım yerine kompozisyonu |faq#Neden Kalıtım Yerine Kompozisyon Tercih Edilir] tercih etmektir**. -Öyleyse kodu farklı bir şekilde tasarlayalım. Soyut `Base*` sınıflarından kaçınacağız. `MyClass` , `BaseClass`'dan miras alarak bazı işlevler elde etmek yerine, bu işlevleri bir bağımlılık olarak aktaracaktır: +Yani, kodu farklı tasarlayacağız. [Soyut |nette:introduction-to-object-oriented-programming#Soyut Sınıflar] `Base*` sınıflarından kaçınacağız. `MyClass`'ın belirli bir işlevselliği `BaseClass`'tan miras alarak elde etmesi yerine, bu işlevselliği bir bağımlılık olarak almasını sağlayacağız: ```php final class SomeFunctionality @@ -125,10 +125,10 @@ final class MyClass ``` -Ayarlayıcı Enjeksiyonu .[#toc-setter-injection] -=============================================== +Setter ile İletme +================= -Bağımlılıklar, onları özel bir özellikte saklayan bir yöntem çağrılarak aktarılır. Bu yöntemler için olağan adlandırma kuralı `set*()` şeklindedir, bu nedenle ayarlayıcılar olarak adlandırılırlar, ancak elbette başka herhangi bir şekilde adlandırılabilirler. +Bağımlılıklar, onları özel bir değişkende saklayan bir metot çağrılarak iletilir. Bu metotları adlandırmak için yaygın bir kural `set*()` şeklindedir, bu yüzden onlara setter denir, ancak elbette başka herhangi bir şekilde adlandırılabilirler. ```php class MyClass @@ -145,9 +145,9 @@ $obj = new MyClass; $obj->setCache($cache); ``` -Bu yöntem, nesnenin bunları gerçekten alacağı (yani kullanıcının yöntemi çağıracağı) garanti edilmediğinden, sınıf işlevi için gerekli olmayan isteğe bağlı bağımlılıklar için kullanışlıdır. +Bu yöntem, sınıfın işlevi için gerekli olmayan isteğe bağlı bağımlılıklar için uygundur, çünkü nesnenin bağımlılığı gerçekten alacağı garanti edilmez (yani kullanıcının metodu çağıracağı). -Aynı zamanda, bu yöntem bağımlılığı değiştirmek için setter'ın tekrar tekrar çağrılmasına izin verir. Bu istenmiyorsa, yönteme bir kontrol ekleyin veya PHP 8.1'den itibaren `$cache` özelliğini `readonly` bayrağı ile işaretleyin. +Aynı zamanda bu yöntem, setter'ı tekrar tekrar çağırmaya ve böylece bağımlılığı değiştirmeye izin verir. Bu istenmiyorsa, metoda bir kontrol ekleriz veya PHP 8.1'den itibaren `$cache` özelliğini `readonly` bayrağıyla işaretleriz. ```php class MyClass @@ -157,28 +157,27 @@ class MyClass public function setCache(Cache $cache): void { if ($this->cache) { - throw new RuntimeException('The dependency has already been set'); + throw new RuntimeException('Bağımlılık zaten ayarlandı'); } $this->cache = $cache; } } ``` -Setter çağrısı, [kurulum bölümündeki |services#Setup] DI konteyner yapılandırmasında tanımlanır. Ayrıca burada bağımlılıkların otomatik geçişi autowiring tarafından kullanılır: +Setter çağrısını DI konteyneri yapılandırmasında [setup anahtarında |services#Setup] tanımlarız. Burada da autowiring kullanarak otomatik bağımlılık iletimi kullanılır: ```neon services: - - - create: MyClass + - create: MyClass setup: - setCache ``` -Mülkiyet Enjeksiyonu .[#toc-property-injection] -=============================================== +Değişken Ayarlayarak +==================== -Bağımlılıklar doğrudan özelliğe aktarılır: +Bağımlılıklar, doğrudan üye değişkene yazılarak iletilir: ```php class MyClass @@ -190,28 +189,27 @@ $obj = new MyClass; $obj->cache = $cache; ``` -Bu yöntemin uygun olmadığı düşünülmektedir, çünkü özellik `public` olarak bildirilmelidir. Bu nedenle, aktarılan bağımlılığın gerçekten belirtilen türde olup olmayacağı üzerinde hiçbir kontrolümüz yoktur (bu PHP 7.4'ten önce doğruydu) ve yeni atanan bağımlılığa kendi kodumuzla tepki verme yeteneğimizi kaybederiz, örneğin sonraki değişiklikleri önlemek için. Aynı zamanda, özellik sınıfın genel arayüzünün bir parçası haline gelir ki bu da arzu edilen bir durum olmayabilir. +Bu yöntem uygunsuz kabul edilir, çünkü üye değişken `public` olarak bildirilmelidir. Ve dolayısıyla, iletilen bağımlılığın gerçekten verilen türde olacağını kontrol edemeyiz (PHP 7.4 öncesinde geçerliydi) ve yeni atanan bağımlılığa kendi kodumuzla tepki verme, örneğin sonraki değişikliği engelleme yeteneğini kaybederiz. Aynı zamanda değişken, sınıfın genel arayüzünün bir parçası haline gelir, bu da istenmeyebilir. -Değişkenin ayarı, [kurulum bölümündeki |services#Setup] DI konteyner yapılandırmasında tanımlanır: +Değişken ayarını DI konteyneri yapılandırmasında [setup bölümünde |services#Setup] tanımlarız: ```neon services: - - - create: MyClass + - create: MyClass setup: - $cache = @\Cache ``` -Enjeksiyon .[#toc-inject] -========================= +Inject +====== -Önceki üç yöntem genel olarak tüm nesne yönelimli dillerde geçerli olsa da, yöntem, ek açıklama veya *inject* niteliği ile enjekte etme Nette sunucularına özgüdür. Bunlar [ayrı bir bölümde |best-practices:inject-method-attribute] ele alınmaktadır. +Önceki üç yöntem tüm nesne yönelimli dillerde genel olarak geçerliyken, *inject* metodu, anotasyonu veya niteliği ile enjekte etme, Nette'deki presenter'lara özgüdür. Bunlar [ayrı bir bölüm |best-practices:inject-method-attribute] içinde ele alınmaktadır. -Hangi Yolu Seçmeli? .[#toc-which-way-to-choose] -=============================================== +Hangi Yöntemi Seçmeli? +====================== -- yapıcı, sınıfın çalışması için gereken zorunlu bağımlılıklar için uygundur -- setter ise isteğe bağlı bağımlılıklar veya değiştirilebilen bağımlılıklar için uygundur -- genel değişkenler önerilmez +- yapıcı, sınıfın işlevi için mutlaka ihtiyaç duyduğu zorunlu bağımlılıklar için uygundur +- setter ise isteğe bağlı bağımlılıklar veya daha sonra değiştirilebilme olasılığı olan bağımlılıklar için uygundur +- public değişkenler uygun değildir diff --git a/dependency-injection/tr/services.texy b/dependency-injection/tr/services.texy index a4b3fb86c4..d80777fce8 100644 --- a/dependency-injection/tr/services.texy +++ b/dependency-injection/tr/services.texy @@ -1,33 +1,33 @@ -Hizmet Tanımları -**************** +Servislerin Tanımlanması +************************ .[perex] -Yapılandırma, özel hizmetlerin tanımlarını yerleştirdiğimiz yerdir. Bu `services` bölümünde yapılır. +Yapılandırma, DI konteynerine bireysel servisleri nasıl oluşturacağını ve diğer bağımlılıklarla nasıl bağlayacağını öğrettiğimiz yerdir. Nette, bunu başarmak için çok net ve zarif bir yol sunar. -Örneğin, `PDO` sınıfının bir örneği olacak olan `database` adlı bir hizmeti bu şekilde oluşturuyoruz: +NEON formatındaki yapılandırma dosyasındaki `services` bölümü, kendi servislerimizi ve yapılandırmalarını tanımladığımız yerdir. `PDO` sınıfının bir örneğini temsil eden `database` adlı bir servisin basit bir tanım örneğine bakalım: ```neon services: database: PDO('sqlite::memory:') ``` -Hizmetlerin isimlendirilmesi, onlara [referans |#Referencing Services] vermemizi sağlamak için kullanılır. Bir hizmete referans verilmiyorsa, onu adlandırmaya gerek yoktur. Bu yüzden isim yerine sadece bir madde işareti kullanırız: +Yukarıdaki yapılandırma, [DI konteynerinde|container] aşağıdaki fabrika metoduna yol açacaktır: -```neon -services: - - PDO('sqlite::memory:') # anonymous service +```php +public function createServiceDatabase(): PDO +{ + return new PDO('sqlite::memory:'); +} ``` -Tek satırlık bir giriş, [kurulum |#setup] gibi ek anahtarların eklenmesine izin vermek için birden fazla satıra bölünebilir. `create:` tuşu için takma ad `factory:` şeklindedir. +Servis adları, yapılandırma dosyasının diğer bölümlerinde `@servisAdi` formatında onlara başvurmamızı sağlar. Bir servisi adlandırmaya gerek yoksa, basitçe bir tire kullanabiliriz: ```neon services: - database: - create: PDO('sqlite::memory:') - setup: ... + - PDO('sqlite::memory:') ``` -Daha sonra `getService()` yöntemini isme göre ya da daha iyisi `getByType()` yöntemini türe göre kullanarak hizmeti DI konteynerinden alırız: +DI konteynerinden bir servis almak için, parametre olarak servis adıyla `getService()` metodunu veya servis türüyle `getByType()` metodunu kullanabiliriz: ```php $database = $container->getService('database'); @@ -35,26 +35,28 @@ $database = $container->getByType(PDO::class); ``` -Hizmet Oluşturma .[#toc-creating-a-service] -=========================================== +Servis Oluşturma +================ -Çoğu zaman, sadece bir sınıfın örneğini oluşturarak bir hizmet yaratırız: +Çoğunlukla, bir servisi basitçe belirli bir sınıfın bir örneğini oluşturarak oluştururuz. Örneğin: ```neon services: database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -Bu da [DI konteynerinde |container] bir fabrika yöntemi oluşturacaktır: +Yapılandırmayı ek anahtarlarla genişletmemiz gerekirse, tanımı birden çok satıra ayırabiliriz: -```php -public function createServiceDatabase(): PDO -{ - return new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'secret'); -} +```neon +services: + database: + create: PDO('sqlite::memory:') + setup: ... ``` -Alternatif olarak, [argümanları |#Arguments] iletmek için bir anahtar `arguments` kullanılabilir: +`create` anahtarının `factory` takma adı vardır, her iki varyant da pratikte yaygındır. Ancak, `create` kullanmanızı öneririz. + +Yapıcı veya oluşturma metodunun argümanları alternatif olarak `arguments` anahtarında yazılabilir: ```neon services: @@ -63,294 +65,303 @@ services: arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret] ``` -Statik bir yöntem de bir hizmet oluşturabilir: +Servisler sadece bir sınıfın örneğini basitçe oluşturarak oluşturulmak zorunda değildir, aynı zamanda statik metotların veya diğer servislerin metotlarının çağrılmasının sonucu da olabilirler: ```neon services: - database: My\Database::create(root, secret) + database: DatabaseFactory::create() + router: @routerFactory::create() ``` -PHP koduna karşılık gelir: +Basitlik için `->` yerine `::` kullanıldığına dikkat edin, bkz. [##İfade Araçları]. Bu fabrika metotları üretilecektir: ```php public function createServiceDatabase(): PDO { - return My\Database::create('root', 'secret'); + return DatabaseFactory::create(); +} + +public function createServiceRouter(): RouteList +{ + return $this->getService('routerFactory')->create(); } ``` -Statik bir yöntemin `My\Database::create()` DI konteynerinin bilmesi gereken tanımlı bir dönüş değerine sahip olduğu varsayılır. Eğer yoksa, türü yapılandırmaya yazarız: +DI konteynerinin oluşturulan servisin türünü bilmesi gerekir. Belirtilen bir dönüş türü olmayan bir metot kullanarak bir servis oluşturuyorsak, bu türü yapılandırmada açıkça belirtmemiz gerekir: ```neon services: database: - create: My\Database::create(root, secret) + create: DatabaseFactory::create() type: PDO ``` -Nette DI size neredeyse her şeyi yazmanız için son derece güçlü ifade olanakları sunar. Örneğin, başka bir servise [başvurmak |#Referencing Services] ve onun metodunu çağırmak için. Basitlik için `->` yerine `::` kullanılır. + +Argümanlar +========== + +Yapıcıya ve metotlara argümanları, PHP'nin kendisinde olduğu gibi çok benzer bir şekilde iletiyoruz: ```neon services: - routerFactory: App\Router\Factory - router: @routerFactory::create() + database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -PHP koduna karşılık gelir: - -```php -public function createServiceRouterFactory(): App\Router\Factory -{ - return new App\Router\Factory; -} +Daha iyi okunabilirlik için argümanları ayrı satırlara ayırabiliriz. Bu durumda virgül kullanımı isteğe bağlıdır: -public function createServiceRouter(): Router -{ - return $this->getService('routerFactory')->create(); -} +```neon +services: + database: PDO( + 'mysql:host=127.0.0.1;dbname=test' + root + secret + ) ``` -PHP'de olduğu gibi yöntem çağrıları zincirleme olarak birbirine eklenebilir: +Argümanları adlandırabilir ve sıralarıyla ilgilenmek zorunda kalmazsınız: ```neon services: - foo: FooFactory::build()::get() + database: PDO( + username: root + password: secret + dsn: 'mysql:host=127.0.0.1;dbname=test' + ) ``` -PHP koduna karşılık gelir: +Bazı argümanları atlamak ve varsayılan değerlerini kullanmak veya [autowiring|autowiring] kullanarak bir servis eklemek isterseniz, alt çizgi kullanın: -```php -public function createServiceFoo() -{ - return FooFactory::build()->get(); -} +```neon +services: + foo: Foo(_, %appDir%) ``` +Argüman olarak servisleri iletebilir, parametreleri kullanabilir ve çok daha fazlasını yapabilirsiniz, bkz. [##İfade Araçları]. + -Argümanlar .[#toc-arguments] -============================ +Setup +===== -Adlandırılmış parametreler argümanları iletmek için de kullanılabilir: +`setup` bölümünde, servis oluşturulurken çağrılması gereken metotları tanımlarız. ```neon services: - database: PDO( - 'mysql:host=127.0.0.1;dbname=test' # konumsal - username: root # named - password: secret # named - ) + database: + create: PDO(%dsn%, %user%, %password%) + setup: + - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) ``` -Argümanları birden fazla satıra bölerken virgül kullanımı isteğe bağlıdır. +Bu, PHP'de şöyle görünürdü: -Elbette, [diğer hizmetleri |#Referencing Services] veya [parametreleri |configuration#parameters] de argüman olarak kullanabiliriz: +```php +public function createServiceDatabase(): PDO +{ + $service = new PDO('...', '...', '...'); + $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + return $service; +} +``` + +Metotları çağırmanın yanı sıra, özelliklere değerler de iletebilirsiniz. Bir diziye öğe eklemek de desteklenir, bu, NEON sözdizimiyle çakışmaması için tırnak içinde yazılmalıdır: ```neon services: - - Foo(@anotherService, %appDir%) + foo: + create: Foo + setup: + - $value = 123 + - '$onClick[]' = [@bar, clickHandler] ``` -PHP koduna karşılık gelir: +Bu, PHP kodunda şöyle görünürdü: ```php -public function createService01(): Foo +public function createServiceFoo(): Foo { - return new Foo($this->getService('anotherService'), '...'); + $service = new Foo; + $service->value = 123; + $service->onClick[] = [$this->getService('bar'), 'clickHandler']; + return $service; } ``` -İlk bağımsız değişken [otomatik olarak bağlanmışsa |autowiring] ve ikincisini belirtmek istiyorsanız, ilkini `_` character, for example `Foo(_, %appDir%)` ile atlayın. Ya da daha iyisi, yalnızca ikinci bağımsız değişkeni [adlandırılmış |autowiring] bir parametre olarak iletin, örneğin `Foo(path: %appDir%)`. - -Nette DI ve NEON formatı size neredeyse her şeyi yazmanız için son derece güçlü ifade olanakları sağlar. Böylece bir argüman yeni oluşturulmuş bir nesne olabilir, statik metotları, diğer servislerin metotlarını ve hatta özel gösterim kullanarak global fonksiyonları çağırabilirsiniz: +Ancak setup'ta statik metotları veya diğer servislerin metotlarını da çağırabilirsiniz. Argüman olarak mevcut servisi iletmeniz gerekiyorsa, onu `@self` olarak belirtin: ```neon services: - analyser: My\Analyser( - FilesystemIterator(%appDir%) # nesne oluştur - DateTime::createFromFormat('Y-m-d') # statik yöntemi çağırın - @anotherService # başka bir hizmet geçirme - @http.request::getRemoteAddress() # başka bir hizmet yöntemini çağırma - ::getenv(NetteMode) # global bir fonksiyon çağırın - ) + foo: + create: Foo + setup: + - My\Helpers::initializeFoo(@self) + - @anotherService::setFoo(@self) ``` -PHP koduna karşılık gelir: +Basitlik için `->` yerine `::` kullanıldığına dikkat edin, bkz. [##İfade Araçları]. Böyle bir fabrika metodu üretilecektir: ```php -public function createServiceAnalyser(): My\Analyser +public function createServiceFoo(): Foo { - return new My\Analyser( - new FilesystemIterator('...'), - DateTime::createFromFormat('Y-m-d'), - $this->getService('anotherService'), - $this->getService('http.request')->getRemoteAddress(), - getenv('NetteMode') - ); + $service = new Foo; + My\Helpers::initializeFoo($service); + $this->getService('anotherService')->setFoo($service); + return $service; } ``` -Özel Fonksiyonlar .[#toc-special-functions] -------------------------------------------- - -Değerleri atamak veya olumsuzlamak için argümanlarda özel fonksiyonlar da kullanabilirsiniz: +İfade Araçları +============== -- `not(%arg%)` olumsuzlama -- `bool(%arg%)` bool'a kayıpsız döküm -- `int(%arg%)` int'e kayıpsız döküm -- `float(%arg%)` float'a kayıpsız döküm -- `string(%arg%)` dizeye kayıpsız döküm +Nette DI, neredeyse her şeyi yazabileceğimiz son derece zengin ifade araçları sunar. Yapılandırma dosyalarında [parametreler |configuration#Parametreler] kullanabiliriz: ```neon -services: - - Foo( - id: int(::getenv('ProjectId')) - productionMode: not(%debugMode%) - ) -``` - -Kayıpsız yeniden yazım, normal PHP yeniden yazımından farklıdır, örneğin `(int)`, sayısal olmayan değerler için bir istisna atar. +# parametre +%wwwDir% -Birden fazla hizmet argüman olarak aktarılabilir. Belirli bir türdeki (yani, sınıf veya arayüz) tüm hizmetlerin bir dizisi `typed()` işlevi tarafından oluşturulur. İşlev, otomatik kablolamanın devre dışı bırakıldığı hizmetleri atlar ve virgülle ayrılmış birden fazla tür belirtilebilir. +# anahtar altındaki parametre değeri +%mailer.user% -```neon -services: - - BarsDependent( typed(Bar) ) +# dize içindeki parametre +'%wwwDir%/images' ``` -[Otomatik kablolamayı |autowiring#Collection of Services] kullanarak bir dizi hizmeti otomatik olarak da geçirebilirsiniz. - -Belirli bir [etikete |#tags] sahip tüm hizmetlerin bir dizisi `tagged()` işlevi tarafından oluşturulur. Virgülle ayrılmış birden fazla etiket belirtilebilir. +Ayrıca nesneler oluşturabilir, metotları ve fonksiyonları çağırabiliriz: ```neon -services: - - LoggersDependent( tagged(logger) ) -``` +# nesne oluşturma +DateTime() +# statik metot çağırma +Collator::create(%locale%) -Referanslama Hizmetleri .[#toc-referencing-services] -==================================================== +# PHP fonksiyonu çağırma +::getenv(DB_USER) +``` -Bireysel hizmetlere `@` and name, so for example `@database` karakteri kullanılarak atıfta bulunulur: +Servislere adlarıyla veya türleriyle başvurabiliriz: ```neon -services: - - create: Foo(@database) - setup: - - setCacheStorage(@cache.storage) +# ada göre servis +@database + +# türe göre servis +@Nette\Database\Connection ``` -PHP koduna karşılık gelir: +Birinci sınıf çağrılabilir sözdizimini kullanın: .{data-version:3.2.0} -```php -public function createService01(): Foo -{ - $service = new Foo($this->getService('database')); - $service->setCacheStorage($this->getService('cache.storage')); - return $service; -} +```neon +# geri arama oluşturma, [@user, logout] benzeri +@user::logout(...) ``` -Anonim hizmetlere bile bir geri arama kullanılarak başvurulabilir, sadece adları yerine türlerini (sınıf veya arayüz) belirtin. Ancak, [otomatik |autowiring] bağlantı nedeniyle bu genellikle gerekli değildir. +Sabitleri kullanın: ```neon -services: - - create: Foo(@Nette\Database\Connection) # or @\PDO - setup: - - setCacheStorage(@cache.storage) +# sınıf sabiti +FilesystemIterator::SKIP_DOTS + +# global sabiti PHP fonksiyonu constant() ile alırız +::constant(PHP_VERSION) ``` +Metot çağrıları PHP'de olduğu gibi zincirlenebilir. Sadece basitlik için `->` yerine `::` kullanılır: -Kurulum .[#toc-setup] -===================== +```neon +DateTime()::format('Y-m-d') +# PHP: (new DateTime())->format('Y-m-d') -Kurulum bölümünde, hizmet oluşturulurken çağrılacak yöntemleri listeliyoruz: +@http.request::getUrl()::getHost() +# PHP: $this->getService('http.request')->getUrl()->getHost() +``` + +Bu ifadeleri her yerde, [#servis oluşturma], [argümanlarda |#Argümanlar], [#setup] bölümünde veya [parametrelerde |configuration#Parametreler] kullanabilirsiniz: ```neon +parameters: + ipAddress: @http.request::getRemoteAddress() + services: database: - create: PDO(%dsn%, %user%, %password%) + create: DatabaseFactory::create( @anotherService::getDsn() ) setup: - - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) + - initialize( ::getenv('DB_USER') ) ``` -PHP koduna karşılık gelir: -```php -public function createServiceDatabase(): PDO -{ - $service = new PDO('...', '...', '...'); - $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - return $service; -} -``` +Özel Fonksiyonlar +----------------- -Özellikler de ayarlanabilir. Bir diziye eleman eklemek de desteklenir ve NEON sözdizimiyle çakışmaması için tırnak içinde yazılmalıdır: +Yapılandırma dosyalarında şu özel fonksiyonları kullanabilirsiniz: +- `not()` değerin olumsuzlanması +- `bool()`, `int()`, `float()`, `string()` kayıpsız olarak belirtilen türe dönüştürme +- `typed()` belirtilen türdeki tüm servislerin bir dizisini oluşturur +- `tagged()` belirtilen etikete sahip tüm servislerin bir dizisini oluşturur ```neon services: - foo: - create: Foo - setup: - - $value = 123 - - '$onClick[]' = [@bar, clickHandler] + - Foo( + id: int(::getenv('ProjectId')) + productionMode: not(%debugMode%) + ) ``` -PHP koduna karşılık gelir: +PHP'deki klasik tür dönüştürme, örneğin `(int)` gibi, aksine kayıpsız tür dönüştürme sayısal olmayan değerler için bir istisna fırlatır. -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - $service->value = 123; - $service->onClick[] = [$this->getService('bar'), 'clickHandler']; - return $service; -} +`typed()` fonksiyonu, belirtilen türdeki (sınıf veya arayüz) tüm servislerin bir dizisini oluşturur. Autowiring'i devre dışı bırakılmış servisleri atlar. Virgülle ayrılmış birden çok tür de belirtebilirsiniz. + +```neon +services: + - BarsDependent( typed(Bar) ) ``` -Ancak, statik yöntemler veya diğer hizmetlerin yöntemleri de kurulumda çağrılabilir. Onlara asıl servisi `@self` olarak iletiyoruz: +Belirli bir türdeki servislerin dizisini [autowiring |autowiring#Servis Dizileri] kullanarak otomatik olarak argüman olarak da iletebilirsiniz. +`tagged()` fonksiyonu daha sonra belirli bir etikete sahip tüm servislerin bir dizisini oluşturur. Burada da virgülle ayrılmış birden çok etiket belirtebilirsiniz. ```neon services: - foo: - create: Foo - setup: - - My\Helpers::initializeFoo(@self) - - @anotherService::setFoo(@self) + - LoggersDependent( tagged(logger) ) ``` -PHP koduna karşılık gelir: -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - My\Helpers::initializeFoo($service); - $this->getService('anotherService')->setFoo($service); - return $service; -} +Autowiring +========== + +`autowired` anahtarı, belirli bir servis için autowiring davranışını etkilemenizi sağlar. Ayrıntılar için [autowiring bölümü|autowiring] bakın. + +```neon +services: + foo: + create: Foo + autowired: false # foo servisi autowiring'den çıkarıldı ``` -Otomatik Kablolama .[#toc-autowiring] -===================================== +Lazy Servisler .{data-version:3.2.4} +==================================== -Otomatik bağlama anahtarı, bir hizmeti otomatik bağlamanın dışında tutmak veya davranışını etkilemek için kullanılabilir. Daha fazla bilgi için otomatik kablolama [bölümüne |autowiring] bakın. +Lazy loading, bir servisin oluşturulmasını gerçekten ihtiyaç duyulana kadar erteleyen bir tekniktir. Global yapılandırmada, tüm servisler için aynı anda [lazy oluşturmayı etkinleştirin |configuration#Tembel Servisler]. Bireysel servisler için daha sonra bu davranışı geçersiz kılabilirsiniz: ```neon services: foo: create: Foo - autowired: false # foo otomatik bağlantıdan kaldırılır + lazy: false ``` +Bir servis lazy olarak tanımlandığında, DI konteynerinden istendiğinde özel bir yer tutucu nesne alırız. Bu, gerçek servis gibi görünür ve davranır, ancak gerçek başlatma (yapıcı ve setup çağrısı) yalnızca herhangi bir metodunun veya özelliğinin ilk çağrısında gerçekleşir. + +.[note] +Lazy loading yalnızca kullanıcı sınıfları için kullanılabilir, dahili PHP sınıfları için kullanılamaz. PHP 8.4 veya daha yenisini gerektirir. + -Etiketler .[#toc-tags] -====================== +Etiketler (Tags) +================ -Kullanıcı bilgileri tek tek hizmetlere etiket şeklinde eklenebilir: +Etiketler, servislere ek bilgiler eklemek için kullanılır. Bir servise bir veya daha fazla etiket ekleyebilirsiniz: ```neon services: @@ -360,7 +371,7 @@ services: - cached ``` -Etiketler bir değere de sahip olabilir: +Etiketler ayrıca değerler de taşıyabilir: ```neon services: @@ -370,26 +381,26 @@ services: logger: monolog.logger.event ``` -Belirli etiketlere sahip bir dizi hizmet, `tagged()` işlevi kullanılarak argüman olarak aktarılabilir. Virgülle ayrılmış birden fazla etiket de belirtilebilir. +Belirli etiketlere sahip tüm servisleri almak için `tagged()` fonksiyonunu kullanabilirsiniz: ```neon services: - LoggersDependent( tagged(logger) ) ``` -Hizmet adları `findByTag()` yöntemi kullanılarak DI konteynerinden elde edilebilir: +DI konteynerinde, belirli bir etikete sahip tüm servislerin adlarını `findByTag()` metodunu kullanarak alabilirsiniz: ```php $names = $container->findByTag('logger'); -// $names, hizmet adını ve etiket değerini içeren bir dizidir -// i.e. ['foo' => 'monolog.logger.event', ...] +// $names, servis adını ve etiket değerini içeren bir dizidir +// örn. ['foo' => 'monolog.logger.event', ...] ``` -Enjeksiyon Modu .[#toc-inject-mode] -=================================== +Inject Modu +=========== -`inject: true` bayrağı, bağımlılıkların [inject |best-practices:inject-method-attribute#Inject Attributes] ek açıklaması ve [inject*() |best-practices:inject-method-attribute#inject Methods] yöntemleri ile genel değişkenler aracılığıyla aktarılmasını etkinleştirmek için kullanılır. +`inject: true` bayrağı kullanılarak, [inject |best-practices:inject-method-attribute#Inject Nitelikleri] anotasyonuna sahip public değişkenler ve [inject*() |best-practices:inject-method-attribute#inject Metotları] metotları aracılığıyla bağımlılıkların iletilmesi etkinleştirilir. ```neon services: @@ -398,13 +409,13 @@ services: inject: true ``` -Varsayılan olarak, `inject` yalnızca sunum yapanlar için etkinleştirilir. +Varsayılan olarak, `inject` yalnızca presenter'lar için etkinleştirilir. -Hizmetlerin Değiştirilmesi .[#toc-modification-of-services] -=========================================================== +Servislerin Değiştirilmesi +========================== -DI konteynerinde yerleşik veya [uzantınız |#di-extensions] tarafından eklenen bir dizi hizmet vardır. Bu hizmetlerin tanımları yapılandırmada değiştirilebilir. Örneğin, varsayılan olarak bir nesne olan `application.application` hizmeti için `Nette\Application\Application` sınıfını değiştirebiliriz: +DI konteyneri, yerleşik veya [kullanıcı uzantısı|extensions] aracılığıyla eklenen birçok servis içerir. Bu servislerin tanımlarını doğrudan yapılandırmada değiştirebilirsiniz. Örneğin, `application.application` servisinin sınıfını, standart olarak `Nette\Application\Application` olan, başka bir sınıfla değiştirebilirsiniz: ```neon services: @@ -413,9 +424,9 @@ services: alteration: true ``` -`alteration` bayrağı bilgilendiricidir ve sadece mevcut bir hizmeti değiştirdiğimizi söyler. +`alteration` bayrağı bilgilendiricidir ve yalnızca mevcut bir servisi değiştirdiğimizi belirtir. -Ayrıca bir kurulum da ekleyebiliriz: +Setup'ı da tamamlayabiliriz: ```neon services: @@ -426,7 +437,7 @@ services: - '$onStartup[]' = [@resource, init] ``` -Bir hizmeti yeniden yazarken, orijinal argümanları, kurulum öğelerini veya etiketleri kaldırmak isteyebiliriz, `reset` bunun içindir: +Bir servisi yeniden yazarken, orijinal argümanları, setup öğelerini veya etiketleri kaldırmak isteyebiliriz, bunun için `reset` kullanılır: ```neon services: @@ -439,7 +450,7 @@ services: - tags ``` -Uzantı ile eklenen bir hizmet konteynerden de kaldırılabilir: +Bir uzantı tarafından eklenen bir servisi kaldırmak isterseniz, bunu şu şekilde yapabilirsiniz: ```neon services: diff --git a/dependency-injection/uk/@home.texy b/dependency-injection/uk/@home.texy index 3b06c6aa4d..7a92429299 100644 --- a/dependency-injection/uk/@home.texy +++ b/dependency-injection/uk/@home.texy @@ -1,24 +1,21 @@ -Ін'єкція залежності -******************* +Nette DI +******** .[perex] -Dependency Injection - це патерн проектування, який докорінно змінить ваш погляд на код і розробку. Він відкриває шлях до світу чисто спроектованих і стійких додатків. +Dependency Injection — це патерн проектування, який кардинально змінить ваш погляд на код та розробку. Він відкриє вам шлях до світу чисто спроектованих та підтримуваних застосунків. - [Що таке Dependency Injection? |introduction] -- [Глобальний стан та синглетони |global-state] +- [Глобальний стан та синглтони |global-state] - [Передача залежностей |passing-dependencies] - [Що таке DI-контейнер? |container] -- [Часті запитання |faq] - +- [Часті питання|faq] -Nette DI --------- -Пакунок `nette/di` надає надзвичайно просунутий скомпільований DI-контейнер для PHP. +Пакет `nette/di` надає надзвичайно просунутий компільований DI-контейнер для PHP. -- [Контейнер Nette DI |nette-container] +- [Nette DI Container |nette-container] - [Конфігурація |configuration] - [Визначення сервісів |services] -- [Автопідключення |autowiring] -- Створені [фабрики |factory] -- [Створення розширень для Nette DI |extensions] +- [Autowiring |autowiring] +- [Згенеровані фабрики |factory] +- [Створення розширень для Nette DI|extensions] diff --git a/dependency-injection/uk/@left-menu.texy b/dependency-injection/uk/@left-menu.texy index edbb7e6927..1c592f52fd 100644 --- a/dependency-injection/uk/@left-menu.texy +++ b/dependency-injection/uk/@left-menu.texy @@ -1,17 +1,17 @@ -Впровадження залежностей -************************ -- [Що таке "впровадження залежностей"? |introduction] -- [Глобальний стан та синглетони |global-state] +Dependency Injection +******************** +- [Що таке DI? |introduction] +- [Глобальний стан та синглтони |global-state] - [Передача залежностей |passing-dependencies] -- [Що таке "DI-контейнер"? |container] -- [Часті запитання |faq] +- [Що таке DI-контейнер? |container] +- [Часті питання|faq] Nette DI -------- -- [Nette DI-контейнер |nette-container] -- [Налаштування |configuration] +- [Nette DI Container |nette-container] +- [Конфігурація |configuration] - [Визначення сервісів |services] -- [Що таке "автозв'язування" |autowiring] +- [Autowiring |autowiring] - [Згенеровані фабрики |factory] -- [Створення розширень для Nette DI |extensions] +- [Створення розширень для Nette DI|extensions] diff --git a/dependency-injection/uk/@meta.texy b/dependency-injection/uk/@meta.texy new file mode 100644 index 0000000000..96e2d9752a --- /dev/null +++ b/dependency-injection/uk/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документація Nette}} diff --git a/dependency-injection/uk/autowiring.texy b/dependency-injection/uk/autowiring.texy index 908a454513..387589ccd9 100644 --- a/dependency-injection/uk/autowiring.texy +++ b/dependency-injection/uk/autowiring.texy @@ -1,24 +1,24 @@ -Автозв'язування -*************** +Автоматичне підключення +*********************** .[perex] -Autowiring, або автозв'язування - це чудова функція, яка може автоматично передавати сервіси до конструктора та інших методів, так що нам зовсім не потрібно їх писати. Це заощадить вам багато часу. +Автоматичне підключення (Autowiring) — це чудова функція, яка вміє автоматично передавати до конструктора та інших методів необхідні сервіси, тому нам не потрібно їх взагалі писати. Це заощадить вам багато часу. -Це дозволяє нам пропустити переважну більшість аргументів під час написання визначень сервісів. Замість: +Завдяки цьому ми можемо пропустити переважну більшість аргументів при написанні визначень сервісів. Замість: ```neon services: articles: Model\ArticleRepository(@database, @cache.storage) ``` -Просто напишіть: +Достатньо написати: ```neon services: articles: Model\ArticleRepository ``` -Автозв'язування керується типами, тому клас `ArticleRepository` має бути визначений таким чином: +Автоматичне підключення керується типами, тому для його роботи клас `ArticleRepository` має бути визначений приблизно так: ```php namespace Model; @@ -30,22 +30,22 @@ class ArticleRepository } ``` -Щоб використовувати автозв'язування, у контейнері має бути **тільки один сервіс** для кожного типу. Якби їх було більше, автозв'язування не знало б, який із них передавати, і викидало б виняток: +Щоб можна було використовувати автоматичне підключення, для кожного типу в контейнері має бути **рівно один сервіс**. Якщо їх буде більше, автоматичне підключення не знатиме, який з них передати, і викине виняток: ```neon services: mainDb: PDO(%dsn%, %user%, %password%) tempDb: PDO('sqlite::memory:') - articles: Model\ArticleRepository # EXCEPT, mainDb та tempDb співпадають + articles: Model\ArticleRepository # ВИКИНЕ ВИНЯТОК, підходять mainDb і tempDb ``` -Рішенням може бути або обхід автопідключення, або явна вказівка імені сервісу (тобто `articles: Model\ArticleRepository(@mainDb)`). Однак зручніше [відключити |#Disabled-Autowiring] автозв'язування одного сервісу, або віддати [перевагу |#Preferred-Autowiring] конкретному сервісу. +Рішенням було б або обійти автоматичне підключення та явно вказати назву сервісу (тобто `articles: Model\ArticleRepository(@mainDb)`). Але зручніше [вимкнути |#Вимкнення автоматичного підключення] автоматичне підключення одного з сервісів або [надати перевагу |#Перевага автоматичного підключення] першому сервісу. -Вимкнене автозв'язування .[#toc-disabled-autowiring] ----------------------------------------------------- +Вимкнення автоматичного підключення +----------------------------------- -Ви можете вимкнути автоматичне визначення залежностей сервісів за допомогою параметра `autowired: no`: +Автоматичне підключення сервісу можна вимкнути за допомогою опції `autowired: no`: ```neon services: @@ -53,28 +53,27 @@ services: tempDb: create: PDO('sqlite::memory:') - autowired: false # видаляє tempDb з автозв'язування + autowired: false # сервіс tempDb виключено з автоматичного підключення - articles: Model\ArticleRepository # передає mainDb у конструктор + articles: Model\ArticleRepository # отже, передасть до конструктора mainDb ``` -Сервіс `articles` не викидає виняток про те, що є два відповідні сервіси типу `PDO` (тобто `mainDb` і `tempDb`), які можуть бути передані конструктору, оскільки він бачить тільки сервіс `mainDb`. +Сервіс `articles` не викине виняток, що існують два відповідні сервіси типу `PDO` (тобто `mainDb` та `tempDb`), які можна передати до конструктора, оскільки він бачить лише сервіс `mainDb`. .[note] -Налаштування autowiring у Nette працює інакше, ніж у Symfony, де опція `autowire: false` говорить, що autowiring не повинен використовуватися для аргументів конструктора сервісу. -У Nette autowiring використовується завжди, будь то аргументи конструктора або будь-якого іншого методу. Опція `autowired: false` говорить, що екземпляр сервісу не повинен передаватися нікуди з використанням autowiring. +Конфігурація автоматичного підключення в Nette працює інакше, ніж у Symfony, де опція `autowire: false` вказує, що не слід використовувати автоматичне підключення для аргументів конструктора даного сервісу. У Nette автоматичне підключення використовується завжди, чи то для аргументів конструктора, чи для будь-яких інших методів. Опція `autowired: false` вказує, що екземпляр даного сервісу не повинен передаватися нікуди за допомогою автоматичного підключення. -Переважне автозв'язування .[#toc-preferred-autowiring] ------------------------------------------------------- +Перевага автоматичного підключення +---------------------------------- -Якщо у нас є кілька сервісів одного типу і один з них має опцію `autowired`, цей сервіс стає кращим: +Якщо у нас є кілька сервісів одного типу і для одного з них ми вказуємо опцію `autowired`, цей сервіс стає пріоритетним: ```neon services: mainDb: create: PDO(%dsn%, %user%, %password%) - autowired: PDO # робить його кращим + autowired: PDO # стає пріоритетним tempDb: create: PDO('sqlite::memory:') @@ -82,13 +81,13 @@ services: articles: Model\ArticleRepository ``` -Сервіс `articles` не викидає виняток, якщо є два сервіси, що збігаються, `PDO` (тобто `mainDb` і `tempDb`), але використовує кращий сервіс, тобто `mainDb`. +Сервіс `articles` не викине виняток, що існують два відповідні сервіси типу `PDO` (тобто `mainDb` та `tempDb`), але використає пріоритетний сервіс, тобто `mainDb`. -Колекція сервісів .[#toc-collection-of-services] ------------------------------------------------- +Масив сервісів +-------------- -Автозв'язування також може передавати масив сервісів певного типу. Оскільки PHP не може нативно позначати тип елементів масиву, на додаток до типу `array` необхідно додати коментар phpDoc з типом елемента, наприклад `ClassName[]`: +Автоматичне підключення вміє передавати і масиви сервісів певного типу. Оскільки в PHP неможливо нативно записати тип елементів масиву, потрібно крім типу `array` додати phpDoc коментар з типом елемента у форматі `ClassName[]`: ```php namespace Model; @@ -103,46 +102,45 @@ class ShipManager } ``` -Потім контейнер DI автоматично передає масив сервісів, що відповідають заданому типу. При цьому будуть пропущені сервіси, у яких відключено автозв'язування. +DI-контейнер потім автоматично передасть масив сервісів, що відповідають даному типу. Він пропустить сервіси, у яких вимкнено автоматичне підключення. -Якщо ви не можете контролювати форму коментаря phpDoc, ви можете передати масив сервісів безпосередньо в конфігурації, використовуючи [`typed()` |services#Special-Functions]. +Тип у коментарі може бути також у форматі `array<int, Class>` або `list<Class>`. Якщо ви не можете вплинути на вигляд phpDoc коментаря, ви можете передати масив сервісів безпосередньо в конфігурації за допомогою [`typed()` |services#Спеціальні функції]. -Скалярні аргументи .[#toc-scalar-arguments] -------------------------------------------- +Скалярні аргументи +------------------ -Autowiring може передавати тільки об'єкти та масиви об'єктів. Скалярні аргументи (наприклад, рядки, числа, булеви) [записуються в конфігурації |services#Arguments]. -Альтернативою може бути створення [settings-object |best-practices:passing-settings-to-presenters], який інкапсулює скалярне значення (або кілька значень) як об'єкт, який потім може бути переданий знову за допомогою autowiring. +Автоматичне підключення вміє підставляти лише об'єкти та масиви об'єктів. Скалярні аргументи (наприклад, рядки, числа, булеві значення) [запишемо в конфігурації |services#Аргументи]. Альтернативою є створення [об'єкта налаштувань |best-practices:passing-settings-to-presenters], який інкапсулює скалярне значення (або кілька значень) у вигляді об'єкта, і його потім можна знову передавати за допомогою автоматичного підключення. ```php class MySettings { public function __construct( - // readonly можна використовувати починаючи з PHP 8.1 + // readonly можна використовувати з PHP 8.1 public readonly bool $value, ) {} } ``` -Ви створюєте сервіс, додаючи його в конфігурацію: +Ви створите з нього сервіс, додавши до конфігурації: ```neon services: - - MySettings('любое значение') + - MySettings('any value') ``` -Потім усі класи будуть запитувати його через autowiring. +Усі класи потім запитають його за допомогою автоматичного підключення. -Звуження автозв'язування .[#toc-narrowing-of-autowiring] --------------------------------------------------------- +Звуження автоматичного підключення +---------------------------------- -Для окремих сервісів автопідключення може бути звужене до певних класів або інтерфейсів. +Для окремих сервісів можна звузити автоматичне підключення лише до певних класів або інтерфейсів. -Зазвичай автозв'язування передає функцію кожному параметру методу, типу якого відповідає функція. Звуження означає, що ми вказуємо умови, яким мають задовольняти типи, зазначені для параметрів методу, щоб їм було передано функцію. +Зазвичай автоматичне підключення передає сервіс до кожного параметра методу, типу якого сервіс відповідає. Звуження означає, що ми встановлюємо умови, яким повинні відповідати типи, зазначені у параметрах методів, щоб їм було передано сервіс. -Розглянемо приклад: +Покажемо це на прикладі: ```php class ParentClass @@ -164,42 +162,42 @@ class ChildDependent } ``` -Якби ми зареєстрували їх усі як сервіси, автозв'язування було б неможливим: +Якщо ми зареєструємо їх усі як сервіси, то автоматичне підключення зазнає невдачі: ```neon services: parent: ParentClass child: ChildClass - parentDep: ParentDependent # ВИБОРУЄ ВИНЯТОК, parent і child збігаються - childDep: ChildDependent # передає сервіс 'child' конструктору + parentDep: ParentDependent # ВИКИНЕ ВИНЯТОК, підходять сервіси parent і child + childDep: ChildDependent # автоматичне підключення передасть до конструктора сервіс child ``` -Сервіс `parentDep` викидає виняток `Multiple services of type ParentClass found: parent, child` тому що і `parent`, і `child` поміщаються в його конструктор, і автозв'язування не може ухвалити рішення про те, який із них вибрати. +Сервіс `parentDep` викине виняток `Multiple services of type ParentClass found: parent, child`, оскільки до його конструктора підходять обидва сервіси `parent` і `child`, і автоматичне підключення не може вирішити, який з них вибрати. -Тому для сервісу `child` ми можемо звузити його автозв'язування до `ChildClass`: +Тому для сервісу `child` ми можемо звузити його автоматичне підключення до типу `ChildClass`: ```neon services: parent: ParentClass child: create: ChildClass - autowired: ChildClass # альтернатива: 'autowired: self' + autowired: ChildClass # можна написати і 'autowired: self' - parentDep: ParentDependent # ВИБИРАЄ ВИНЯТОК, 'child' не може бути автопідключуваним - childDep: ChildDependent # передає сервіс 'child' конструктору + parentDep: ParentDependent # автоматичне підключення передасть до конструктора сервіс parent + childDep: ChildDependent # автоматичне підключення передасть до конструктора сервіс child ``` -Сервіс `parentDep` тепер передається в конструктор сервісу `parentDep`, оскільки тепер це єдиний відповідний об'єкт. Сервіс `child` більше не передається через автозв'язування. Так, функція `child`, як і раніше, має тип `ParentClass`, але умова звуження, задана для типу параметра, більше не застосовується, тобто більше не вірно, що `ParentClass` *є супертипом* `ChildClass`. +Тепер до конструктора сервісу `parentDep` передається сервіс `parent`, оскільки тепер це єдиний відповідний об'єкт. Сервіс `child` автоматичне підключення туди вже не передасть. Так, сервіс `child` все ще є типу `ParentClass`, але вже не виконується звужуюча умова, задана для типу параметра, тобто не виконується, що `ParentClass` *є надтипом* `ChildClass`. -У випадку `child`, `autowired: ChildClass` можна записати як `autowired: self`, оскільки `self` означає поточний тип сервісу. +Для сервісу `child` можна було б `autowired: ChildClass` записати також як `autowired: self`, оскільки `self` є заповнювачем для класу поточного сервісу. -Ключ `autowired` може включати кілька класів та інтерфейсів як масив: +У ключі `autowired` можна вказати і кілька класів або інтерфейсів як масив: ```neon autowired: [BarClass, FooInterface] ``` -Давайте спробуємо додати інтерфейси в приклад: +Спробуємо доповнити приклад ще інтерфейсами: ```php interface FooInterface @@ -239,13 +237,13 @@ class ChildDependent } ``` -Якщо ми не обмежуємо сервіс `child`, він відповідатиме конструкторам усіх класів `FooDependent`, `BarDependent`, `ParentDependent` і `ChildDependent`, а автозв'язування передасть його туди. +Якщо ми ніяк не обмежимо сервіс `child`, він підійде до конструкторів усіх класів `FooDependent`, `BarDependent`, `ParentDependent` та `ChildDependent`, і автоматичне підключення його туди передасть. -Однак, якщо ми звузимо автозв'язування до `ChildClass` за допомогою `autowired: ChildClass` (або `self`), автозв'язування передає його тільки конструктору `ChildDependent`, оскільки для нього потрібен аргумент типу `ChildClass` і `ChildClass` *це тип* `ChildClass`. Жоден інший тип, вказаний для інших параметрів, не є заміною `ChildClass`, тому сервіс не проходить. +Але якщо ми звузимо його автоматичне підключення до `ChildClass` за допомогою `autowired: ChildClass` (або `self`), автоматичне підключення передасть його лише до конструктора `ChildDependent`, оскільки він вимагає аргумент типу `ChildClass` і виконується умова, що `ChildClass` *є типу* `ChildClass`. Жоден інший тип, зазначений у інших параметрах, не є надтипом `ChildClass`, тому сервіс не передається. -Якщо ми обмежуємо його на `ParentClass` за допомогою `autowired: ParentClass`, то автозв'язування знову передасть його конструктору `ChildDependent` (оскільки потрібний тип `ChildClass` є надмножиною `ParentClass`) і конструктору `ParentDependent`, оскільки необхідний тип `ParentClass` також відповідає. +Якщо ми обмежимо його до `ParentClass` за допомогою `autowired: ParentClass`, автоматичне підключення знову передасть його до конструктора `ChildDependent` (оскільки необхідний `ChildClass` є надтипом `ParentClass`) і тепер також до конструктора `ParentDependent`, оскільки необхідний тип `ParentClass` також є відповідним. -Якщо ми обмежуємо його на `FooInterface`, то він все одно буде підключатися для `ParentDependent` (необхідний тип `ParentClass` є супертипом `FooInterface`) і `ChildDependent`, але додатково до конструктора `FooDependent`, але не `BarDependent`, тому що `BarInterface` не супертип `FooInterface`. +Якщо ми обмежимо його до `FooInterface`, він все одно буде автоматично підключений до `ParentDependent` (необхідний `ParentClass` є надтипом `FooInterface`) та `ChildDependent`, але крім того, і до конструктора `FooDependent`, однак не до `BarDependent`, оскільки `BarInterface` не є надтипом `FooInterface`. ```neon services: @@ -253,8 +251,8 @@ services: create: ChildClass autowired: FooInterface - fooDep: FooDependent # передає сервіс child конструктору - barDep: BarDependent # ВИБИРАЄ ВИНЯТОК, жоден сервіс не пройде - parentDep: ParentDependent # передає сервіс child конструктору - childDep: ChildDependent # передає сервіс child конструктору + fooDep: FooDependent # автоматичне підключення передасть до конструктора child + barDep: BarDependent # ВИКИНЕ ВИНЯТОК, жоден сервіс не відповідає + parentDep: ParentDependent # автоматичне підключення передасть до конструктора child + childDep: ChildDependent # автоматичне підключення передасть до конструктора child ``` diff --git a/dependency-injection/uk/configuration.texy b/dependency-injection/uk/configuration.texy index da99a35c9d..be8b7c4869 100644 --- a/dependency-injection/uk/configuration.texy +++ b/dependency-injection/uk/configuration.texy @@ -1,32 +1,33 @@ -Налаштування DI-контейнера +Конфігурація DI-контейнера ************************** .[perex] -Огляд варіантів конфігурації контейнера Nette DI. +Огляд конфігураційних опцій для Nette DI-контейнера. Конфігураційний файл ==================== -Контейнером Nette DI легко керувати за допомогою конфігураційних файлів. Зазвичай вони записуються у форматі [NEON format |neon:format]. Ми рекомендуємо використовувати для редагування таких файлів [редактори з підтримкою |best-practices:editors-and-tools#IDE-Editor] цього формату. +Nette DI-контейнер легко керується за допомогою конфігураційних файлів. Вони зазвичай записуються у [форматі NEON|neon:format]. Для редагування рекомендуємо [редактори з підтримкою |best-practices:editors-and-tools#IDE редактор] цього формату. <pre> -"decorator .[prism-token prism-atrule]":[#Decorator]: "Decorator .[prism-token prism-comment]"<br> -"di .[prism-token prism-atrule]":[#DI]: "DI Container .[prism-token prism-comment]"<br> -"extensions .[prism-token prism-atrule]":[#Extensions]: "Встановіть додаткові розширення DI .[prism-token prism-comment]"<br> -"includes .[prism-token prism-atrule]":[#Including files]: "Включення файлів .[prism-token prism-comment]"<br> -"parameters .[prism-token prism-atrule]":[#Parameters]: "Параметри .[prism-token prism-comment]"<br> +"decorator .[prism-token prism-atrule]":[#decorator]: "Декоратор .[prism-token prism-comment]"<br> +"di .[prism-token prism-atrule]":[#DI]: "DI-контейнер .[prism-token prism-comment]"<br> +"extensions .[prism-token prism-atrule]":[#Розширення]: "Встановлення додаткових DI-розширень .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[#Включення файлів]: "Включення файлів .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[#Параметри]: "Параметри .[prism-token prism-comment]"<br> "search .[prism-token prism-atrule]":[#Search]: "Автоматична реєстрація сервісів .[prism-token prism-comment]"<br> "services .[prism-token prism-atrule]":[services]: "Сервіси .[prism-token prism-comment]" </pre> -Щоб записати рядок, що містить символ `%`, вы должны экранировать его удвоением до `%%`. .[note] +.[note] +Щоб записати рядок, що містить символ `%`, потрібно його екранувати, подвоївши до `%%`. -Параметри .[#toc-parameters] -============================ +Параметри +========= -Можна задати параметри, які потім можуть бути використані як частина визначення сервісу. Це може допомогти розділити значення, які ви хочете змінювати більш регулярно: +У конфігурації можна визначити параметри, які потім можна використовувати як частину визначень сервісів. Це може зробити конфігурацію більш зрозумілою або об'єднати та виділити значення, які будуть змінюватися. ```neon parameters: @@ -35,9 +36,9 @@ parameters: password: secret ``` -У будь-якому файлі конфігурації можна посилатися на параметр `foo` через `%foo%` в іншому місці. Вони також можуть використовуватися всередині рядків, таких як `'%wwwDir%/images'`. +На параметр `dsn` можна посилатися будь-де в конфігурації записом `%dsn%`. Параметри можна використовувати і всередині рядків, як `'%wwwDir%/images'`. -Параметри не повинні бути тільки рядками, вони можуть бути також значеннями масиву: +Параметри не обов'язково мають бути лише рядками або числами, вони також можуть містити масиви: ```neon parameters: @@ -48,32 +49,32 @@ parameters: languages: [cs, en, de] ``` -Можна посилатися на окремий ключ так: `%mailer.user%`. +На конкретний ключ можна посилатися як `%mailer.user%`. -Якщо вам потрібно отримати значення будь-якого параметра у вашому коді, наприклад, у класі, то передайте його цьому класу. Наприклад, у конструкторі. Не існує глобального об'єкта конфігурації, який міг би запитувати значення параметрів. Це суперечить принципу впровадження залежностей. +Якщо вам потрібно у вашому коді, наприклад, у класі, дізнатися значення будь-якого параметра, передайте його до цього класу. Наприклад, у конструкторі. Не існує жодного глобального об'єкта, що представляє конфігурацію, до якого класи могли б звертатися за значеннями параметрів. Це було б порушенням принципу dependency injection. -Сервіси .[#toc-services] -======================== +Сервіси +======= -Див. [окремий розділ |services]. +Див. [окремий розділ|services]. -Декоратор .[#toc-decorator] -=========================== +Decorator +========= -Як масово редагувати всі сервіси певного типу? Потрібно викликати певний метод для всіх презентерів, що успадковують від певного спільного предка? Ось звідки береться декоратор: +Як масово змінити всі сервіси певного типу? Наприклад, викликати певний метод у всіх presenter'ів, які успадковують від конкретного спільного предка? Для цього існує decorator. ```neon decorator: - # Для всіх сервісів, які є екземплярами цього класу або інтерфейсу - App\Presenters\BasePresenter: + # для всіх сервісів, що є екземплярами цього класу або інтерфейсу + App\Presentation\BasePresenter: setup: - - setProjectId(10) # викликаємо цей метод - - $absoluteUrls = true # і задаємо змінну + - setProjectId(10) # виклич цей метод + - $absoluteUrls = true # і встанови змінну ``` -Декоратор також можна використовувати для встановлення [тегів |services#Tags] або ввімкнення [режиму впровадження |services#Inject-Mode]. +Decorator можна також використовувати для налаштування [тегів |services#Теги] або ввімкнення режиму [inject |services#Режим Inject]. ```neon decorator: @@ -86,67 +87,79 @@ decorator: DI === -Технічні налаштування контейнера DI: +Технічні налаштування DI-контейнера. ```neon di: - # відображати DIC в панелі налагодження Tracy? + # показати DI-контейнер у Tracy Bar? debugger: ... # (bool) за замовчуванням true - # типи параметрів, які не потрібно монтувати автоматично + # типи параметрів, які ніколи не підключати автоматично excluded: ... # (string[]) - # клас, від якого успадковується контейнер DI + # дозволити ліниве створення сервісів? + lazy: ... # (bool) за замовчуванням false + + # клас, від якого успадковується DI-контейнер parentClass: ... # (string) за замовчуванням Nette\DI\Container ``` -Експорт метаданих .[#toc-metadata-export] ------------------------------------------ +Lazy-сервіси .{data-version:3.2.4} +---------------------------------- + +Налаштування `lazy: true` активує ліниве (відкладене) створення сервісів. Це означає, що сервіси не створюються насправді в момент, коли ми їх запитуємо з DI-контейнера, а лише в момент їх першого використання. Це може прискорити запуск програми та зменшити споживання пам'яті, оскільки створюються лише ті сервіси, які дійсно потрібні в даному запиті. + +Для конкретного сервісу ліниве створення можна [змінити |services#Lazy-сервіси]. + +.[note] +Ліниві об'єкти можна використовувати лише для користувацьких класів, а не для внутрішніх класів PHP. Потребує PHP 8.4 або новішої версії. + -Клас контейнера DI також містить безліч метаданих. Ви можете зменшити його, скоротивши експорт метаданих. +Експорт метаданих +----------------- + +Клас DI-контейнера містить також багато метаданих. Ви можете зменшити його розмір, скоротивши експорт метаданих. ```neon di: export: - # експортувати параметри - parameters: false # (bool) за замовчуванням true + # експортувати параметри? + parameters: false # (bool) за замовчуванням true - # експортувати теги - tags: # (string[]|bool) за замовчуванням усі + # експортувати теги і які? + tags: # (string[]|bool) за замовчуванням всі - event.subscriber - # експортувати дані для автоматичного підключення - types: # (string[]|bool) за замовчуванням усі + # експортувати дані для автопідключення і які? + types: # (string[]|bool) за замовчуванням всі - Nette\Database\Connection - Symfony\Component\Console\Application ``` -Якщо ви не використовуєте масив `$container->parameters`, можна вимкнути експорт параметрів. Крім того, ви можете експортувати тільки ті теги, через які ви отримуєте сервіси, використовуючи метод `$container->findByTag(...)`. -Якщо ви не викликаєте цей метод зовсім, можна повністю відключити експорт тегів, вказавши значення `false`. +Якщо ви не використовуєте масив `$container->getParameters()`, ви можете вимкнути експорт параметрів. Далі, ви можете експортувати лише ті теги, через які ви отримуєте сервіси методом `$container->findByTag(...)`. Якщо ви взагалі не викликаєте цей метод, ви можете повністю вимкнути експорт тегів за допомогою `false`. -Ви можете значно зменшити метадані для автоматичного підключення, вказавши класи, які ви використовуєте як параметр у методі `$container->getByType()`. -І знову, якщо ви не викликаєте цей метод зовсім (або тільки в [bootstrap |application:bootstrap] для отримання `Nette\Application\Application`), можна повністю відключити експорт, вказавши значення `false`. +Ви можете значно скоротити метадані для [автоматичного підключення|autowiring], вказавши класи, які ви використовуєте як параметр методу `$container->getByType()`. І знову ж таки, якщо ви взагалі не викликаєте цей метод (або лише в [bootstrap|application:bootstrapping] для отримання `Nette\Application\Application`), ви можете повністю вимкнути експорт за допомогою `false`. -Розширення .[#toc-extensions] -============================= +Розширення +========== -Реєстрація інших розширень DI. Таким чином, ми додаємо, наприклад, DI розширення `Dibi\Bridges\Nette\DibiExtension22` під іменем `dibi`: +Реєстрація додаткових DI-розширень. Таким чином додамо, наприклад, DI-розширення `Dibi\Bridges\Nette\DibiExtension22` під назвою `dibi`. ```neon extensions: dibi: Dibi\Bridges\Nette\DibiExtension22 ``` -Потім ми налаштовуємо його в секції, яка також називається `dibi`: +Потім ми конфігуруємо його в секції `dibi`: ```neon dibi: host: localhost ``` -Ви також можете додати клас розширення з параметрами: +Як розширення можна додати і клас, який має параметри: ```neon extensions: @@ -154,10 +167,10 @@ extensions: ``` -Файли, що включаються .[#toc-including-files] -============================================= +Включення файлів +================ -Додаткові файли конфігурації можуть бути вставлені в секції `includes`: +Додаткові конфігураційні файли можна включити в секції `includes`: ```neon includes: @@ -166,7 +179,7 @@ includes: - presenters.neon ``` -Назва `parameters.php` не є друкарською помилкою, конфігурацію також можна записати в PHP-файлі, який повертає її у вигляді масиву: +Назва `parameters.php` не є помилкою, конфігурація може бути записана також у PHP-файлі, який поверне її як масив: ```php <?php @@ -179,78 +192,74 @@ return [ ]; ``` -Якщо в конфігураційних файлах з'являються елементи з однаковими ключами, вони будуть [перезаписані або об'єднані |#Merging] у випадку з масивами. Наступний включений файл має вищий пріоритет, ніж попередній. Файл, у якому вказано секцію `includes`, має вищий пріоритет, ніж файли, включені в нього. +Якщо в конфігураційних файлах з'являться елементи з однаковими ключами, вони будуть перезаписані, або у випадку [масивів об'єднані |#Об єднання]. Файл, що включається пізніше, має вищий пріоритет, ніж попередній. Файл, у якому вказана секція `includes`, має вищий пріоритет, ніж файли, що включаються в ньому. -Пошук .[#toc-search] -==================== +Search +====== -Автоматичне додавання сервісів у контейнер DI робить роботу надзвичайно приємною. Nette автоматично додає презентери в контейнер, але ви можете легко додати будь-які інші класи. +Автоматичне додавання сервісів до DI-контейнера надзвичайно полегшує роботу. Nette автоматично додає до контейнера presenter'и, але можна легко додавати й будь-які інші класи. -Просто вкажіть, у яких каталогах (і підкаталогах) слід шукати класи: +Достатньо вказати, у яких каталогах (та підкаталогах) слід шукати класи: ```neon search: - # ви самі вибираєте назви секцій - myForms: - in: %appDir%/Forms - - model: - in: %appDir%/Model + - in: %appDir%/Forms + - in: %appDir%/Model ``` -Зазвичай, однак, ми не хочемо додавати всі класи та інтерфейси, тому ми можемо відфільтрувати їх: +Зазвичай, однак, ми не хочемо додавати абсолютно всі класи та інтерфейси, тому їх можна фільтрувати: ```neon search: - myForms: - in: %appDir%/Forms + - in: %appDir%/Forms - # фільтрація за ім'ям файлу (string|string[]) + # фільтрація за назвою файлу (string|string[]) files: - *Factory.php - # фільтрація за ім'ям класу (string|string[]) + # фільтрація за назвою класу (string|string[]) classes: - *Factory ``` -Або ми можемо вибрати класи, які успадковують або реалізують принаймні один із наступних класів: +Або ми можемо вибирати класи, які успадковують або реалізують принаймні один із зазначених класів: ```neon search: - myForms: + - in: %appDir% extends: - App\*Form implements: - App\*FormInterface ``` -Ви також можете визначити негативні правила, тобто маски імен класів або предків, і якщо вони відповідають вимогам, сервіс не буде додано в контейнер DI: +Можна визначити і правила виключення, тобто маски назви класу або предків, які, якщо відповідають, сервіс не додається до DI-контейнера: ```neon search: - myForms: + - in: %appDir% exclude: + files: ... classes: ... extends: ... implements: ... ``` -Для додаткових сервісів можна визначити теги: +Усім сервісам можна встановити теги: ```neon search: - myForms: + - in: %appDir% tags: ... ``` -Об'єднання .[#toc-merging] -========================== +Об'єднання +========== -Якщо елементи з однаковими ключами з'являються в кількох конфігураційних файлах, їх буде перезаписано або об'єднано в разі масивів. Пізніший включений файл має вищий пріоритет. +Якщо у кількох конфігураційних файлах з'являться елементи з однаковими ключами, вони будуть перезаписані, або у випадку масивів об'єднані. Файл, що включається пізніше, має вищий пріоритет, ніж попередній. <table class=table> <tr> @@ -283,13 +292,13 @@ items: </tr> </table> -Щоб запобігти об'єднанню певного масиву, використовуйте знак оклику відразу після імені масиву: +Для масивів можна запобігти об'єднанню, вказавши знак оклику після назви ключа: <table class=table> <tr> <th width=33%>config1.neon</th> <th width=33%>config2.neon</th> - <th>result</th> + <th>результат</th> </tr> <tr> <td> @@ -313,3 +322,5 @@ items: </td> </tr> </table> + +{{maintitle: Конфігурація Dependency Injection}} diff --git a/dependency-injection/uk/container.texy b/dependency-injection/uk/container.texy index fdbfbf1e5b..82010d258f 100644 --- a/dependency-injection/uk/container.texy +++ b/dependency-injection/uk/container.texy @@ -1,16 +1,16 @@ -Що таке "DI-контейнер"? -*********************** +Що таке DI-контейнер? +********************* .[perex] -Контейнер впровадження залежностей (DIC) - це клас, який може інстанціювати та конфігурувати об'єкти. +Dependency injection контейнер (DIC) — це клас, який вміє інстанціювати та конфігурувати об'єкти. -Це може вас здивувати, але в багатьох випадках вам не потрібен контейнер для впровадження залежностей, щоб скористатися перевагами впровадження залежностей (скорочено DI). Зрештою, навіть у [попередньому розділі |introduction] ми показували конкретні приклади DI, і жоден контейнер не був потрібен. +Можливо, вас це здивує, але в багатьох випадках вам не потрібен dependency injection контейнер, щоб скористатися перевагами dependency injection (коротко DI). Адже навіть у [вступному розділі|introduction] ми показали DI на конкретних прикладах, і жоден контейнер не був потрібний. -Однак якщо вам потрібно керувати великою кількістю різних об'єктів з безліччю залежностей, контейнер впровадження залежностей буде дійсно корисним. Можливо, це стосується веб-додатків, побудованих на фреймворку. +Однак, якщо вам потрібно керувати великою кількістю різних об'єктів з багатьма залежностями, dependency injection контейнер буде дійсно корисним. Що, наприклад, стосується веб-додатків, побудованих на фреймворку. -У попередньому розділі ми познайомилися з класами `Article` і `UserController`. Обидва вони мають деякі залежності, а саме базу даних і фабрику `ArticleFactory`. І для цих класів ми зараз створимо контейнер. Звісно, для такого простого прикладу не має сенсу мати контейнер. Але ми створимо його, щоб показати, як він виглядає і працює. +У попередньому розділі ми представили класи `Article` та `UserController`. Обидва мають певні залежності, а саме базу даних та фабрику `ArticleFactory`. І для цих класів ми тепер створимо контейнер. Звичайно, для такого простого прикладу немає сенсу мати контейнер. Але ми створимо його, щоб показати, як він виглядає і працює. -Ось простий жорстко закодований контейнер для наведеного вище прикладу: +Ось простий жорстко закодований контейнер для наведеного прикладу: ```php class Container @@ -32,16 +32,16 @@ class Container } ``` -Його використання матиме такий вигляд: +Використання виглядало б так: ```php $container = new Container; $controller = $container->createUserController(); ``` -Ми просто запитуємо об'єкт у контейнера, і нам більше не потрібно нічого знати про те, як його створити або які його залежності; контейнер знає все це. Залежності вводяться контейнером автоматично. У цьому його сила. +Ми лише запитуємо у контейнера об'єкт і вже не повинні нічого знати про те, як його створити та які у нього залежності; все це знає контейнер. Залежності контейнером вводяться автоматично. У цьому його сила. -Досі в контейнері все було жорстко закодовано. Тому ми зробимо наступний крок і додамо параметри, щоб зробити контейнер дійсно корисним: +Контейнер поки що має всі дані записані жорстко. Зробимо наступний крок і додамо параметри, щоб контейнер став дійсно корисним: ```php class Container @@ -70,9 +70,9 @@ $container = new Container([ ]); ``` -Уважні читачі, можливо, помітили проблему. Кожного разу, коли я отримую об'єкт `UserController`, також створюється новий екземпляр `ArticleFactory` і база даних. Ми цього точно не хочемо. +Уважні читачі, можливо, помітили певну проблему. Кожного разу, коли я отримую об'єкт `UserController`, також створюється новий екземпляр `ArticleFactory` та бази даних. Цього ми точно не хочемо. -Тому ми додаємо метод `getService()`, який буде повертати одні й ті самі екземпляри знову і знову: +Тому додамо метод `getService()`, який буде повертати завжди ті самі екземпляри: ```php class Container @@ -87,7 +87,7 @@ class Container public function getService(string $name): object { if (!isset($this->services[$name])) { - // getService('Database') викликає createDatabase() + // getService('Database') викличе createDatabase() $method = 'create' . $name; $this->services[$name] = $this->$method(); } @@ -98,9 +98,9 @@ class Container } ``` -У першому виклику, наприклад, `$container->getService('database')` створюватиметься об'єкт бази даних, який він зберігатиме в масиві `$services` і повертатиме безпосередньо на наступному виклику. +При першому виклику, наприклад, `$container->getService('Database')`, він попросить `createDatabase()` створити об'єкт бази даних, який збереже в масиві `$services`, а при наступному виклику просто поверне його. -Також ми модифікуємо іншу частину контейнера для використання `getService()': +Змінимо і решту контейнера, щоб він використовував `getService()`: ```php class Container @@ -119,9 +119,9 @@ class Container } ``` -До речі, термін сервіс відноситься до будь-якого об'єкта, керованого контейнером. Звідси і назва методу `getService()`. +До речі, терміном "сервіс" позначається будь-який об'єкт, керований контейнером. Тому й назва методу `getService()`. -Ми маємо повністю функціональний контейнер DI! І ми можемо використовувати його. +Готово. У нас є повністю функціональний DI-контейнер! І ми можемо його використовувати: ```php $container = new Container([ @@ -134,6 +134,9 @@ $controller = $container->getService('UserController'); $database = $container->getService('Database'); ``` -Як бачите, написати DIC не складно. Примітно, що самі об'єкти не знають, що контейнер їх створює. Таким чином, можна створити будь-який об'єкт у PHP таким чином, не впливаючи на вихідний код. +Як бачите, написати DIC не так вже й складно. Варто нагадати, що самі об'єкти не знають, що їх створює якийсь контейнер. Таким чином, можна створювати будь-який об'єкт у PHP без втручання в його вихідний код. -Ручне створення і підтримка класу контейнерів може стати кошмаром досить швидко. Тому в наступному розділі ми розповімо про [Nette DI-контейнер |nette-container], який може генерувати і оновлювати себе практично автоматично. +Ручне створення та підтримка класу контейнера може досить швидко стати кошмаром. Тому в наступному розділі ми поговоримо про [Nette DI Container|nette-container], який вміє генеруватися та оновлюватися майже самостійно. + + +{{maintitle: Що таке dependency injection контейнер?}} diff --git a/dependency-injection/uk/extensions.texy b/dependency-injection/uk/extensions.texy index eba51856f6..c3e3aa8511 100644 --- a/dependency-injection/uk/extensions.texy +++ b/dependency-injection/uk/extensions.texy @@ -2,16 +2,16 @@ ******************************** .[perex] -Створення контейнера DI на додаток до файлів конфігурації також впливає на так звані *розширення*. Ми активуємо їх у файлі конфігурації в секції `extensions`. +На генерацію DI-контейнера, крім конфігураційних файлів, впливають також так звані *розширення*. Ми активуємо їх у конфігураційному файлі в секції `extensions`. -Так ми додаємо розширення, представлене класом `BlogExtension` з іменем `blog`: +Так ми додаємо розширення, представлене класом `BlogExtension`, під назвою `blog`: ```neon extensions: blog: BlogExtension ``` -Кожне розширення компілятора успадковує від [api:Nette\DI\CompilerExtension] і може реалізувати такі методи, які викликаються під час компіляції DI: +Кожне розширення компілятора успадковує від [api:Nette\DI\CompilerExtension] і може реалізовувати наступні методи, які послідовно викликаються під час складання DI-контейнера: 1. getConfigSchema() 2. loadConfiguration() @@ -22,18 +22,18 @@ extensions: getConfigSchema() .[method] =========================== -Цей метод викликається першим. Він визначає схему, що використовується для перевірки параметрів конфігурації. +Цей метод викликається першим. Він визначає схему для валідації конфігураційних параметрів. -Розширення налаштовані в секції, ім'я якої збігається з ім'ям, під яким додано розширення, наприклад, `blog`. +Розширення конфігуруємо в секції, назва якої збігається з тією, під якою було додано розширення, тобто `blog`: ```neon -# те саме ім'я, що й у розширення +# та сама назва, що й у розширення blog: postsPerPage: 10 - comments: false + allowComments: false ``` -Ми визначимо схему, що описує всі параметри конфігурації, включно з їхніми типами, прийнятими значеннями і, можливо, значеннями за замовчуванням: +Створимо схему, що описує всі конфігураційні опції, включаючи їхні типи, допустимі значення та, можливо, значення за замовчуванням: ```php use Nette\Schema\Expect; @@ -50,9 +50,9 @@ class BlogExtension extends Nette\DI\CompilerExtension } ``` -Див. [Schema |schema:] для отримання інформації. Крім того, можна вказати, які опції можуть бути [динамічними |application:bootstrap#Dynamic-Parameters] за допомогою `dynamic()', например `Expect::int()->dynamic()`. +Документацію знайдете на сторінці [Schema |schema:]. Крім того, можна визначити, які опції можуть бути [динамічними |application:bootstrapping#Динамічні параметри] за допомогою `dynamic()`, наприклад `Expect::int()->dynamic()`. -Ми отримуємо доступ до конфігурації через `$this->config`, який є об'єктом `stdClass`: +До конфігурації ми отримуємо доступ через змінну `$this->config`, яка є об'єктом `stdClass`: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -71,7 +71,7 @@ class BlogExtension extends Nette\DI\CompilerExtension loadConfiguration() .[method] ============================= -Цей метод використовується для додавання сервісів у контейнер. Це робиться за допомогою [api:Nette\DI\ContainerBuilder]: +Використовується для додавання сервісів до контейнера. Для цього служить [api:Nette\DI\ContainerBuilder]: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -86,19 +86,19 @@ class BlogExtension extends Nette\DI\CompilerExtension } ``` -Конвенція передбачає префіксування сервісів, доданих розширенням з його ім'ям, щоб не виникали конфлікти імен. Це робиться за допомогою `prefix()', так что если расширение называется 'blog', то служба будет называться `blog.articles`. +Конвенція полягає в тому, щоб префіксувати сервіси, додані розширенням, його назвою, щоб уникнути конфліктів імен. Це робить метод `prefix()`, тому якщо розширення називається `blog`, сервіс матиме назву `blog.articles`. -Якщо нам потрібно перейменувати сервіс, ми можемо створити псевдонім з його початковим ім'ям, щоб зберегти зворотну сумісність. Так само робить Nette для `routing.router`, який також доступний під ранньою назвою `router`. +Якщо потрібно перейменувати сервіс, для збереження зворотної сумісності можна створити псевдонім з оригінальною назвою. Подібно Nette робить, наприклад, для сервісу `routing.router`, який доступний і під попередньою назвою `router`. ```php $builder->addAlias('router', 'routing.router'); ``` -Отримання сервісів із файлу .[#toc-retrieve-services-from-a-file] ------------------------------------------------------------------ +Завантаження сервісів з файлу +----------------------------- -Ми можемо створювати сервіси за допомогою API ContainerBuilder, але також можемо додати їх через знайомий файл конфігурації NEON і його секцію `services`. Префікс `@extension` представляє поточне розширення. +Сервіси можна створювати не лише за допомогою API класу ContainerBuilder, але й відомим записом, що використовується в конфігураційному файлі NEON у секції services. Префікс `@extension` представляє поточне розширення. ```neon services: @@ -112,7 +112,7 @@ services: create: MyBlog\Components\ArticlesList(@extension.articles) ``` -Ми додамо сервіси таким чином: +Завантажимо сервіси: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -121,7 +121,7 @@ class BlogExtension extends Nette\DI\CompilerExtension { $builder = $this->getContainerBuilder(); - // завантажуємо файл конфігурації для розширення + // завантаження конфігураційного файлу для розширення $this->compiler->loadDefinitionsFromConfig( $this->loadFromFile(__DIR__ . '/blog.neon')['services'], ); @@ -133,7 +133,7 @@ class BlogExtension extends Nette\DI\CompilerExtension beforeCompile() .[method] ========================= -Метод викликається, коли контейнер містить усі сервіси, додані окремими розширеннями в методах `loadConfiguration`, а також файли конфігурації користувача. На цьому етапі складання ми можемо змінювати визначення сервісу або додавати посилання між ними. Для пошуку сервісів за тегами можна використовувати метод `findByTag()`, або метод `findByType()` для пошуку за класом або інтерфейсом. +Метод викликається в момент, коли контейнер містить усі сервіси, додані окремими розширеннями в методах `loadConfiguration`, а також користувацькими конфігураційними файлами. На цій стадії складання ми можемо редагувати визначення сервісів або доповнювати зв'язки між ними. Для пошуку сервісів у контейнері за тегами можна використовувати метод `findByTag()`, а за класом чи інтерфейсом - метод `findByType()`. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -153,7 +153,7 @@ class BlogExtension extends Nette\DI\CompilerExtension afterCompile() .[method] ======================== -На цій стадії клас контейнера вже генерується як об'єкт [ClassType |php-generator:#Classes], містить усі методи, які створюються сервісом, і готовий до кешування як файл PHP. Наразі ми можемо редагувати код класу. +На цій фазі клас контейнера вже згенеровано у вигляді об'єкта [ClassType |php-generator:#Класи], він містить усі методи, що створюють сервіси, і готовий до запису в кеш. Кінцевий код класу ми можемо на цьому етапі ще змінити. ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -167,12 +167,12 @@ class BlogExtension extends Nette\DI\CompilerExtension ``` -$initialization .[wiki-method] -============================== +$initialization .[method] +========================= -Configurator викликається кодом ініціалізації після [створення контейнера |application:bootstrap#index-php], який створюється шляхом запису в об'єкт `$this->initialization` за допомогою [методу addBody() |php-generator:#method-and-function-body]. +Клас Configurator після [створення контейнера |application:bootstrapping#index.php] викликає ініціалізаційний код, який створюється записом в об'єкт `$this->initialization` за допомогою [методу addBody() |php-generator:#Тіла методів та функцій]. -Ми покажемо приклад того, як запустити сесію або сервіси, які мають тег `run` за допомогою коду ініціалізації: +Покажемо приклад, як, наприклад, ініціалізаційним кодом запустити сесію або запустити сервіси, що мають тег `run`: ```php class BlogExtension extends Nette\DI\CompilerExtension @@ -184,7 +184,7 @@ class BlogExtension extends Nette\DI\CompilerExtension $this->initialization->addBody('$this->getService("session")->start()'); } - // сервіси з тегом 'run' мають бути створені після того, як контейнер буде інстантовано + // сервіси з тегом run мають бути створені після інстанціювання контейнера $builder = $this->getContainerBuilder(); foreach ($builder->findByTag('run') as $name => $foo) { $this->initialization->addBody('$this->getService(?);', [$name]); diff --git a/dependency-injection/uk/factory.texy b/dependency-injection/uk/factory.texy index e4f50df1e7..c3f0ffeb36 100644 --- a/dependency-injection/uk/factory.texy +++ b/dependency-injection/uk/factory.texy @@ -2,11 +2,11 @@ ******************* .[perex] -Nette DI може автоматично генерувати код фабрик на основі інтерфейсу, що позбавляє вас від написання коду. +Nette DI вміє автоматично генерувати код фабрик на основі інтерфейсів, що заощаджує вам написання коду. -Фабрика - це клас, який створює та налаштовує об'єкти. Тому він також передає їм їхні залежності. Будь ласка, не плутайте з патерном проектування *factory method*, який описує специфічний спосіб використання фабрик і не має відношення до цієї теми. +Фабрика — це клас, який виробляє та конфігурує об'єкти. Отже, вона передає їм і їхні залежності. Будь ласка, не плутайте з патерном проектування *factory method*, який описує специфічний спосіб використання фабрик і не пов'язаний з цією темою. -Ми показали, як виглядає така фабрика у [вступному розділі |introduction#factory]: +Як виглядає така фабрика, ми показали у [вступному розділі |introduction#Фабрика]: ```php class ArticleFactory @@ -23,7 +23,7 @@ class ArticleFactory } ``` -Усе, що вам потрібно зробити, це створити інтерфейс, а Nette DI згенерує його реалізацію. Інтерфейс повинен мати рівно один метод з ім'ям `create` і оголошувати тип, що повертається: +Nette DI вміє автоматично генерувати код фабрик. Все, що вам потрібно зробити, це створити інтерфейс, і Nette DI згенерує реалізацію. Інтерфейс повинен мати рівно один метод з назвою `create` та декларувати тип повернення: ```php interface ArticleFactory @@ -32,7 +32,7 @@ interface ArticleFactory } ``` -Отже, фабрика `ArticleFactory` має метод `create`, який створює об'єкти `Article`. Клас `Article` може виглядати, наприклад, таким чином: +Отже, фабрика `ArticleFactory` має метод `create`, який створює об'єкти `Article`. Клас `Article` може виглядати, наприклад, так: ```php class Article @@ -44,16 +44,16 @@ class Article } ``` -Додайте фабрику до файлу конфігурації: +Фабрику додаємо до конфігураційного файлу: ```neon services: - ArticleFactory ``` -Nette DI створить відповідну реалізацію фабрики. +Nette DI згенерує відповідну реалізацію фабрики. -Таким чином, у коді, що використовує фабрику, ми запитуємо об'єкт за інтерфейсом, а Nette DI використовує згенеровану реалізацію: +У коді, який використовує фабрику, ми запитуємо об'єкт за інтерфейсом, і Nette DI використає згенеровану реалізацію: ```php class UserController @@ -65,17 +65,17 @@ class UserController public function foo() { - // дозволити фабриці створити об'єкт + // дозволимо фабриці створити об'єкт $article = $this->articleFactory->create(); } } ``` -Параметризована фабрика .[#toc-parameterized-factory] -===================================================== +Параметризована фабрика +======================= -Метод фабрики `create` може приймати параметри, які він потім передає конструктору. Наприклад, давайте додамо ID автора статті в клас `Article`: +Фабричний метод `create` може приймати параметри, які потім передасть до конструктора. Доповнимо, наприклад, клас `Article` ідентифікатором автора статті: ```php class Article @@ -88,7 +88,7 @@ class Article } ``` -Ми також додамо параметр до фабрики: +Параметр додамо також до фабрики: ```php interface ArticleFactory @@ -97,13 +97,13 @@ interface ArticleFactory } ``` -Оскільки параметр у конструкторі та параметр у фабриці мають однакове ім'я, Nette DI передасть їх автоматично. +Завдяки тому, що параметр у конструкторі та параметр у фабриці називаються однаково, Nette DI їх повністю автоматично передасть. -Розширене визначення .[#toc-advanced-definition] -================================================ +Розширена дефініція +=================== -Визначення також може бути записано в багаторядковій формі за допомогою ключа `implement`: +Визначення можна записати і в багаторядковому вигляді за допомогою ключа `implement`: ```neon services: @@ -111,9 +111,9 @@ services: implement: ArticleFactory ``` -При написанні таким подовженим способом можна надати додаткові аргументи для конструктора в ключі `arguments` і додаткову конфігурацію за допомогою `setup`, як і для звичайних сервісів. +При записі цим довшим способом можна вказати додаткові аргументи для конструктора в ключі `arguments` та додаткову конфігурацію за допомогою `setup`, так само, як для звичайних сервісів. -Приклад: Якби метод `create()` не приймав параметр `$authorId`, ми могли б вказати в конфігурації фіксоване значення, яке передавалося б у конструктор `Article`: +Приклад: якби метод `create()` не приймав параметр `$authorId`, ми могли б вказати фіксоване значення в конфігурації, яке передавалося б до конструктора `Article`: ```neon services: @@ -123,7 +123,7 @@ services: authorId: 123 ``` -Або, навпаки, якби `create()` приймав параметр `$authorId`, але він не був частиною конструктора і був переданий методом `Article::setAuthorId()`, ми б звернулися до нього в секції `setup`: +Або навпаки, якби `create()` приймав параметр `$authorId`, але він не був би частиною конструктора і передавався б методом `Article::setAuthorId()`, ми б посилалися на нього в секції `setup`: ```neon services: @@ -134,15 +134,14 @@ services: ``` -Аксесор .[#toc-accessor] -======================== +Accessor +======== -Крім фабрик, Nette також може генерувати так звані аксесори. Це об'єкти з методом `get()`, який повертає конкретний сервіс з DI-контейнера. Повторні виклики `get()` як і раніше повертають один і той самий екземпляр. +Nette, крім фабрик, вміє генерувати так звані accessor'и. Це об'єкти з методом `get()`, який повертає певний сервіс з DI-контейнера. Повторний виклик `get()` повертає завжди той самий екземпляр. -Аксесор забезпечує ледаче завантаження залежностей. Нехай у нас є клас, який записує помилки в спеціальну базу даних. Якби в цьому класі з'єднання з базою даних передавалося конструктором як залежність, то з'єднання доводилося б створювати завжди, хоча на практиці помилка виникає дуже рідко, і тому з'єднання зазвичай залишалося б невикористаним. -Замість цього клас передає метод доступу, і тільки при виклику його `get()` створюється об'єкт бази даних: +Accessor'и забезпечують ліниве завантаження (lazy-loading) залежностей. Уявімо клас, який записує помилки до спеціальної бази даних. Якби цей клас отримував підключення до бази даних як залежність через конструктор, підключення завжди б створювалося, хоча на практиці помилка виникає лише зрідка, і тому здебільшого з'єднання залишалося б невикористаним. Замість цього клас передає accessor, і лише коли викликається його `get()`, відбувається створення об'єкта бази даних: -Як створити аксесор? Просто напишіть інтерфейс, і Nette DI згенерує реалізацію. Інтерфейс повинен мати рівно один метод з ім'ям `get` і оголосити тип, що повертається: +Як створити accessor? Достатньо написати інтерфейс, і Nette DI згенерує реалізацію. Інтерфейс повинен мати рівно один метод з назвою `get` та декларувати тип повернення: ```php interface PDOAccessor @@ -151,7 +150,7 @@ interface PDOAccessor } ``` -Ми додамо аксесор у файл конфігурації, який також містить визначення сервісу, який він поверне: +Accessor додаємо до конфігураційного файлу, де також є визначення сервісу, який він буде повертати: ```neon services: @@ -159,63 +158,69 @@ services: - PDO(%dsn%, %user%, %password%) ``` -Оскільки метод доступу повертає службу `PDO`, а в конфігурації є тільки один такий сервіс, він поверне його. Якщо сервісів цього типу більше, ми визначаємо сервіс, що повертається, за іменем, наприклад `- PDOAccessor(@db1)`. +Оскільки accessor повертає сервіс типу `PDO`, а в конфігурації є лише один такий сервіс, він повертатиме саме його. Якщо сервісів даного типу було б більше, ми б визначили сервіс, що повертається, за допомогою назви, наприклад, `- PDOAccessor(@db1)`. -Кілька фабрик/аксесорів .[#toc-multifactory-accessor] -===================================================== -Досі наші фабрики та аксесори завжди могли виробляти або повертати лише один об'єкт. Однак дуже легко створити кілька фабрик у поєднанні з аксесорами. Інтерфейс такого класу міститиме будь-яку кількість методів з іменами `create<name>()` и `get<name>()`, наприклад: +Багаторазова фабрика/accessor +============================= +Наші фабрики та accessor'и досі вміли завжди виробляти або повертати лише один об'єкт. Але можна дуже легко створити і багаторазові фабрики, комбіновані з accessor'ами. Інтерфейс такого класу міститиме довільну кількість методів з назвами `create<name>()` та `get<name>()`, наприклад: ```php interface MultiFactory { function createArticle(): Article; - function createFoo(): Model\Foo; function getDb(): PDO; } ``` -Тому замість того, щоб передавати кілька згенерованих фабрик і аксесорів, ми збираємося передати ще одну складну фабрику, яка може робити більше. +Отже, замість того, щоб передавати кілька згенерованих фабрик та accessor'ів, ми передамо одну більш комплексну фабрику, яка вміє більше. -Як варіант, замість кількох методів можна використовувати параметри `create()` і `get()`: +Альтернативно, замість кількох методів можна використовувати `get()` з параметром: ```php interface MultiFactoryAlt { - function create($name); function get($name): PDO; } ``` -Потім `MultiFactory::createArticle()` робить те саме, що і `MultiFactoryAlt::create('article')`. Однак альтернативна нотація має той недолік, що незрозуміло, які значення `$name` підтримуються, і логічно неможливо розрізнити різні значення, що повертаються, для різних `$name` в інтерфейсі. +Тоді виконується, що `MultiFactory::getArticle()` робить те саме, що й `MultiFactoryAlt::get('article')`. Однак альтернативний запис має той недолік, що незрозуміло, які значення `$name` підтримуються, і логічно також неможливо в інтерфейсі розрізнити різні значення, що повертаються, для різних `$name`. -Визначення списку .[#toc-definition-with-a-list] ------------------------------------------------- -А як визначити множинну фабрику в конфігурації? Ми створимо три сервіси, які будуть створювати/повертати, а потім і саму фабрику: +Визначення списком +------------------ +Таким чином можна визначити багаторазову фабрику в конфігурації: .{data-version:3.2.0} + +```neon +services: + - MultiFactory( + article: Article # визначає createArticle() + db: PDO(%dsn%, %user%, %password%) # визначає getDb() + ) +``` + +Або ми можемо у визначенні фабрики посилатися на існуючі сервіси за допомогою посилання: ```neon services: article: Article - - Model\Foo - PDO(%dsn%, %user%, %password%) - MultiFactory( - article: @article # createArticle() - foo: @Model\Foo # createFoo() - db: @\PDO # getDb() + article: @article # визначає createArticle() + db: @\PDO # визначає getDb() ) ``` -Визначення з використанням тегів .[#toc-definition-with-tags] -------------------------------------------------------------- +Визначення за допомогою тегів +----------------------------- -Другий варіант - використовувати [теги |services#Tags] для визначення: +Другою можливістю є використання для визначення [тегів |services#Теги]: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter - App\Model\DatabaseAccessor( - db1: @database.db1.context + db1: @database.db1.explorer ) ``` diff --git a/dependency-injection/uk/faq.texy b/dependency-injection/uk/faq.texy index c04c4b1a9e..449d37a9da 100644 --- a/dependency-injection/uk/faq.texy +++ b/dependency-injection/uk/faq.texy @@ -1,98 +1,92 @@ -Поширені запитання про DI (FAQ) -******************************* +Часті питання про DI (FAQ) +************************** -Чи є DI іншою назвою для IoC? .[#toc-is-di-another-name-for-ioc] ----------------------------------------------------------------- +Чи є DI іншою назвою для IoC? +----------------------------- -*Inversion of Control* (IoC) - це принцип, який фокусується на тому, як виконується код - чи ваш код ініціює зовнішній код, чи ваш код інтегрується в зовнішній код, який потім викликає його. -IoC - це широке поняття, яке включає в себе [події |nette:glossary#Events], так званий [голлівудський принцип |application:components#Hollywood style] та інші аспекти. -Фабрики, які є частиною [Правила №3: Нехай фабрика розбирається з цим |introduction#Rule #3: Let the Factory Handle It] і представляють собою інверсію для оператора `new`, також є компонентами цієї концепції. +*Inversion of Control* (IoC) — це принцип, зосереджений на способі виконання коду: чи ваш код запускає чужий, чи ваш код інтегрований у чужий, який його потім викликає. IoC — це широкий термін, що охоплює [події |nette:glossary#Події události], так званий [Голлівудський принцип |application:components#Голлівудський стиль] та інші аспекти. Частиною цієї концепції є також фабрики, про які йдеться у [Правило №3: залиште це фабриці |introduction#Правило 3: доручи це фабриці], і які представляють інверсію для оператора `new`. -*Dependency Injection* (DI) - це те, як один об'єкт знає про інший об'єкт, тобто залежність. Це патерн проектування, який вимагає явної передачі залежностей між об'єктами. +*Dependency Injection* (DI) зосереджується на способі, яким один об'єкт дізнається про інший об'єкт, тобто про його залежності. Це патерн проектування, який вимагає явного передавання залежностей між об'єктами. -Таким чином, можна сказати, що DI є специфічною формою IoC. Однак не всі форми IoC підходять з точки зору чистоти коду. Наприклад, до антипаттернів ми відносимо всі техніки, які працюють з [глобальним станом |global state] або так званим [Service Locator |#What is a Service Locator]. +Отже, можна сказати, що DI є специфічною формою IoC. Однак не всі форми IoC є доцільними з точки зору чистоти коду. Наприклад, до антипатернів належать техніки, що працюють з [глобальним станом |global-state] або так званий [Service Locator |#Що таке Service Locator]. -Що таке Service Locator? .[#toc-what-is-a-service-locator] ----------------------------------------------------------- +Що таке Service Locator? +------------------------ -Сервіс-локатор - це альтернатива ін'єкції залежностей. Він працює шляхом створення центрального сховища, де реєструються всі доступні сервіси або залежності. Коли об'єкту потрібна залежність, він запитує її з Service Locator. +Це альтернатива Dependency Injection. Він працює так, що створює центральне сховище, де реєструються всі доступні сервіси або залежності. Коли об'єкту потрібна залежність, він запитує її у Service Locator. -Однак, порівняно з Dependency Injection, він втрачає прозорість: залежності не передаються безпосередньо об'єктам, і тому їх не так легко ідентифікувати, що вимагає вивчення коду, щоб виявити і зрозуміти всі зв'язки. Тестування також ускладнюється, оскільки ми не можемо просто передати імітаційні об'єкти об'єктам, що тестуються, а повинні пройти через Service Locator. Крім того, Service Locator порушує дизайн коду, оскільки окремі об'єкти повинні знати про його існування, що відрізняється від Dependency Injection, де об'єкти не знають про контейнер DI. +Однак, порівняно з Dependency Injection, він втрачає прозорість: залежності не передаються об'єктам безпосередньо і їх не так легко ідентифікувати, що вимагає дослідження коду для виявлення та розуміння всіх зв'язків. Тестування також складніше, оскільки ми не можемо просто передавати mock-об'єкти тестованим об'єктам, а повинні робити це через Service Locator. Крім того, Service Locator порушує дизайн коду, оскільки окремі об'єкти повинні знати про його існування, що відрізняється від Dependency Injection, де об'єкти не мають уявлення про DI-контейнер. -Коли краще не використовувати DI? .[#toc-when-is-it-better-not-to-use-di] -------------------------------------------------------------------------- +Коли краще не використовувати DI? +--------------------------------- -Не існує відомих труднощів, пов'язаних з використанням патерну проектування Ін'єкція залежностей (Dependency Injection). Навпаки, отримання залежностей з глобально доступних місць призводить до [ряду ускладнень |global-state], так само як і використання Service Locator. -Тому бажано завжди використовувати DI. Це не догматичний підхід, просто кращої альтернативи не знайдено. +Немає відомих труднощів, пов'язаних з використанням патерну проектування Dependency Injection. Навпаки, отримання залежностей з глобально доступних місць призводить до [цілої низки ускладнень |global-state], так само як і використання Service Locator. Тому доцільно використовувати DI завжди. Це не догматичний підхід, а просто не було знайдено кращої альтернативи. -Однак є певні ситуації, коли ми не передаємо об'єкти один одному, а отримуємо їх з глобального простору. Наприклад, при налагодженні коду і необхідності вивести значення змінної в певній точці програми, виміряти тривалість виконання певної частини програми або записати повідомлення в лог. -У таких випадках, коли мова йде про тимчасові дії, які згодом будуть видалені з коду, цілком виправданим є використання глобально доступного дампера, секундоміра або логгера. Ці інструменти, зрештою, не належать до проектування коду. +Проте існують певні ситуації, коли ми не передаємо об'єкти, а отримуємо їх з глобального простору. Наприклад, при налагодженні коду, коли потрібно в конкретній точці програми вивести значення змінної, виміряти тривалість певної частини програми або записати повідомлення. У таких випадках, коли йдеться про тимчасові дії, які пізніше будуть видалені з коду, легітимно використовувати глобально доступний дампер, секундомір або логер. Ці інструменти не належать до дизайну коду. -Чи має використання DI свої недоліки? .[#toc-does-using-di-have-its-drawbacks] ------------------------------------------------------------------------------- +Чи має використання DI свої тіньові сторони? +-------------------------------------------- -Чи має використання Dependency Injection якісь недоліки, такі як збільшення складності написання коду чи погіршення продуктивності? Що ми втрачаємо, коли починаємо писати код відповідно до DI? +Чи несе використання Dependency Injection якісь недоліки, такі як підвищена складність написання коду або погіршена продуктивність? Що ми втрачаємо, коли починаємо писати код відповідно до DI? -DI не впливає на продуктивність програми або вимоги до пам'яті. Продуктивність DI-контейнера може відігравати певну роль, але у випадку [Nette DI | nette-container] контейнер компілюється в чистий PHP, тому його накладні витрати під час виконання програми практично дорівнюють нулю. +DI не впливає на продуктивність або споживання пам'яті програми. Певну роль може відігравати продуктивність DI-контейнера, однак у випадку [Nette DI |nette-container] контейнер компілюється в чистий PHP, тому його накладні витрати під час роботи програми практично нульові. -Під час написання коду необхідно створювати конструктори, які приймають залежності. Раніше це могло зайняти багато часу, але завдяки сучасним IDE і розширенню [властивостей конструкторів |https://blog.nette.org/uk/php-8-0-povnij-oglyad-novin#toc-constructor-property-promotion], тепер це справа кількох секунд. За допомогою Nette DI і плагіна PhpStorm можна легко створювати фабрики всього за кілька кліків. -З іншого боку, немає необхідності писати синглетони і статичні точки доступу. +При написанні коду буває необхідно створювати конструктори, що приймають залежності. Раніше це могло бути трудомістким, однак завдяки сучасним IDE та [constructor property promotion |https://blog.nette.org/uk/php-8-0-complete-overview-of-news#toc-constructor-property-promotion] це тепер питання кількох секунд. Фабрики можна легко генерувати за допомогою Nette DI та плагіна для PhpStorm кліком миші. З іншого боку, відпадає потреба писати singleton'и та статичні точки доступу. -Можна зробити висновок, що правильно спроектований додаток з використанням DI не є ні коротшим, ні довшим у порівнянні з додатком, що використовує синглетони. Частини коду, що працюють з залежностями, просто витягуються з окремих класів і переміщуються в нові місця, тобто в контейнер DI і фабрики. +Можна констатувати, що правильно спроектована програма, що використовує DI, не є ні коротшою, ні довшою порівняно з програмою, що використовує singleton'и. Частини коду, що працюють із залежностями, просто вилучаються з окремих класів і переміщуються на нові місця, тобто до DI-контейнера та фабрик. -Як переписати старий додаток на DI? .[#toc-how-to-rewrite-a-legacy-application-to-di] -------------------------------------------------------------------------------------- +Як legacy-додаток переписати на DI? +----------------------------------- -Міграція зі старого додатку на Dependency Injection може бути складним процесом, особливо для великих і складних додатків. Важливо підходити до цього процесу систематично. +Перехід від legacy-додатка до Dependency Injection може бути складним процесом, особливо для великих і комплексних додатків. Важливо підходити до цього процесу систематично. -- При переході на Dependency Injection важливо, щоб всі члени команди розуміли принципи і практики, які використовуються. -- По-перше, проведіть аналіз існуючого додатку, щоб визначити ключові компоненти та їх залежності. Складіть план, які частини будуть рефакторингуватися і в якому порядку. -- Впровадьте DI-контейнер або, ще краще, використовуйте існуючу бібліотеку, таку як Nette DI. -- Поступово рефакторингуйте кожну частину програми для використання Dependency Injection. Це може включати модифікацію конструкторів або методів, щоб вони приймали залежності в якості параметрів. -- Змініть місця в коді, де створюються об'єкти залежностей, так, щоб замість цього залежності інжектувалися контейнером. Це може включати використання фабрик. +- При переході на Dependency Injection важливо, щоб усі члени команди розуміли принципи та процедури, що використовуються. +- Спочатку проведіть аналіз існуючого додатка та ідентифікуйте ключові компоненти та їхні залежності. Створіть план, які частини будуть рефакторені та в якому порядку. +- Реалізуйте DI-контейнер або, ще краще, використайте існуючу бібліотеку, наприклад, Nette DI. +- Поступово рефакторте окремі частини додатка, щоб вони використовували Dependency Injection. Це може включати зміни конструкторів або методів так, щоб вони приймали залежності як параметри. +- Змініть місця в коді, де створюються об'єкти із залежностями, щоб замість цього залежності вводилися контейнером. Це може включати використання фабрик. -Пам'ятайте, що перехід на інжекцію залежностей - це інвестиція в якість коду і довгострокову стійкість програми. Хоча внести ці зміни може бути складно, в результаті ви отримаєте чистіший, більш модульний і легко тестуємий код, готовий до майбутніх розширень і обслуговування. +Пам'ятайте, що перехід на Dependency Injection — це інвестиція в якість коду та довгострокову підтримку додатка. Хоча може бути складно виконати ці зміни, результатом має бути чистіший, модульніший та легко тестований код, готовий до майбутнього розширення та підтримки. -Чому композиція краща за успадкування? .[#toc-why-composition-is-preferred-over-inheritance] --------------------------------------------------------------------------------------------- -Краще використовувати композицію, ніж успадкування, оскільки вона слугує для повторного використання коду без необхідності турбуватися про ефект "просочування" змін. Таким чином, це забезпечує більш вільний зв'язок, коли нам не потрібно турбуватися про те, що зміна одного коду спричинить зміну іншого залежного коду, який також потребує змін. Типовим прикладом є ситуація, яку називають [пеклом конструктора |passing-dependencies#Constructor hell]. +Чому композиції надається перевага перед успадкуванням? +------------------------------------------------------- +Доцільніше використовувати [композицію |nette:introduction-to-object-oriented-programming#Композиція] замість [успадкування |nette:introduction-to-object-oriented-programming#Успадкування], оскільки вона служить для повторного використання коду, не турбуючись про наслідки змін. Таким чином, вона забезпечує вільніший зв'язок, коли нам не потрібно турбуватися, що зміна якогось коду спричинить необхідність зміни іншого залежного коду. Типовим прикладом є ситуація, що позначається як [пекло конструкторів |passing-dependencies#Пекло конструкторів]. -Чи можна використовувати Nette DI Container за межами Nette? .[#toc-can-nette-di-container-be-used-outside-of-nette] --------------------------------------------------------------------------------------------------------------------- +Чи можна використовувати Nette DI Container поза Nette? +------------------------------------------------------- -Безумовно, так. Nette DI Container є частиною Nette, але розроблений як окрема бібліотека, яку можна використовувати незалежно від інших частин фреймворку. Просто встановіть її за допомогою Composer, створіть конфігураційний файл, що визначає ваші сервіси, а потім за допомогою декількох рядків PHP-коду створіть DI-контейнер. -І ви можете одразу ж почати використовувати переваги Dependency Injection у своїх проектах. +Безумовно. Nette DI Container є частиною Nette, але він розроблений як самостійна бібліотека, яка може бути використана незалежно від інших частин фреймворку. Достатньо встановити її за допомогою Composer, створити конфігураційний файл з визначенням ваших сервісів, а потім за допомогою кількох рядків PHP-коду створити DI-контейнер. І одразу можете почати використовувати переваги Dependency Injection у своїх проектах. -У розділі [Контейнер Nette DI |nette-container] описано, як виглядає конкретний варіант використання, включно з кодом. +Як виглядає конкретне використання, включаючи коди, описує розділ [Nette DI Container |nette-container]. -Чому конфігурація зберігається у файлах NEON? .[#toc-why-is-the-configuration-in-neon-files] --------------------------------------------------------------------------------------------- +Чому конфігурація у файлах NEON? +-------------------------------- -NEON - це проста і зрозуміла мова конфігурації, розроблена в рамках Nette для налаштування програм, сервісів та їх залежностей. У порівнянні з JSON або YAML, вона пропонує набагато більш інтуїтивно зрозумілі та гнучкі можливості для цього. У NEON ви можете природно описувати прив'язки, які неможливо було б написати в Symfony & YAML або взагалі, або тільки через складний опис. +NEON — це проста та легко читабельна конфігураційна мова, яка була розроблена в рамках Nette для налаштування додатків, сервісів та їхніх залежностей. Порівняно з JSON або YAML, вона пропонує для цієї мети набагато інтуїтивніші та гнучкіші можливості. У NEON можна природно описати зв'язки, які в Symfony & YAMLu було б неможливо записати або взагалі, або лише за допомогою складного опису. -Чи сповільнює розбір NEON-файлів роботу програми? .[#toc-does-parsing-neon-files-slow-down-the-application] ------------------------------------------------------------------------------------------------------------ +Чи не сповільнює додаток парсинг файлів NEON? +--------------------------------------------- -Хоча NEON-файли розбираються дуже швидко, цей аспект не має особливого значення. Причина в тому, що розбір файлів відбувається лише один раз під час першого запуску програми. Після цього генерується код DI-контейнера, який зберігається на диску і виконується при кожному наступному запиті без необхідності подальшого розбору. +Хоча файли NEON парсяться дуже швидко, цей аспект взагалі не має значення. Причина в тому, що парсинг файлів відбувається лише один раз при першому запуску додатка. Потім генерується код DI-контейнера, зберігається на диску і запускається при кожному наступному запиті, без необхідності виконувати подальший парсинг. -Саме так це працює у виробничому середовищі. Під час розробки NEON-файли аналізуються щоразу, коли змінюється їхній вміст, що гарантує, що розробник завжди має актуальний DI-контейнер. Як згадувалося раніше, фактичний синтаксичний аналіз - це справа однієї миті. +Так це працює в робочому середовищі. Під час розробки файли NEON парсяться кожного разу, коли відбувається зміна їхнього вмісту, щоб розробник завжди мав актуальний DI-контейнер. Сам парсинг, як було сказано, є питанням миттєвості. -Як отримати доступ до параметрів з конфігураційного файлу в моєму класі? .[#toc-how-do-i-access-the-parameters-from-the-configuration-file-in-my-class] -------------------------------------------------------------------------------------------------------------------------------------------------------- +Як отримати доступ до параметрів у конфігураційному файлі з мого класу? +----------------------------------------------------------------------- -Пам'ятайте про [Правило №1: Дозвольте, щоб вам передали |introduction#Rule #1: Let It Be Passed to You]. Якщо класу потрібна інформація з конфігураційного файлу, нам не потрібно з'ясовувати, як отримати доступ до цієї інформації; натомість, ми просто запитуємо її - наприклад, через конструктор класу. І виконуємо передачу у конфігураційному файлі. +Пам'ятаймо [Правило №1: нехай тобі це передадуть |introduction#Правило 1: нехай тобі це передадуть]. Якщо клас вимагає інформацію з конфігураційного файлу, нам не потрібно думати, як отримати цю інформацію, замість цього ми просто просимо її — наприклад, через конструктор класу. А передачу здійснюємо в конфігураційному файлі. -У цьому прикладі `%myParameter%` - це заповнювач для значення параметра `myParameter`, яке буде передано в конструктор `MyClass`: +У цьому прикладі `%myParameter%` є заповнювачем для значення параметра `myParameter`, який передається до конструктора класу `MyClass`: ```php # config.neon @@ -103,10 +97,10 @@ services: - MyClass(%myParameter%) ``` -Якщо ви хочете передати декілька параметрів або використовувати автопідстановку, корисно обернути [параметри в об'єкт |best-practices:passing-settings-to-presenters]. +Якщо ви хочете передавати більше параметрів або використовувати автоматичне підключення, доцільно [упакувати параметри в об'єкт |best-practices:passing-settings-to-presenters]. -Чи підтримує Nette інтерфейс PSR-11 Container? .[#toc-does-nette-support-psr-11-container-interface] ----------------------------------------------------------------------------------------------------- +Чи підтримує Nette PSR-11: Container interface? +----------------------------------------------- -Nette DI Container не підтримує PSR-11 безпосередньо. Однак, якщо вам потрібна сумісність між Nette DI Container і бібліотеками або фреймворками, які очікують інтерфейс PSR-11 Container Interface, ви можете створити [простий адаптер |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f], який слугуватиме мостом між Nette DI Container і PSR-11. +Nette DI Container не підтримує PSR-11 безпосередньо. Однак, якщо вам потрібна взаємодія між Nette DI Container та бібліотеками або фреймворками, які очікують PSR-11 Container Interface, ви можете створити [простий адаптер |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f], який слугуватиме мостом між Nette DI Container та PSR-11. diff --git a/dependency-injection/uk/global-state.texy b/dependency-injection/uk/global-state.texy index 489daa66b6..c573434ce1 100644 --- a/dependency-injection/uk/global-state.texy +++ b/dependency-injection/uk/global-state.texy @@ -1,44 +1,43 @@ -Глобальна держава та одинаки -**************************** +Глобальний стан та singleton'и +****************************** .[perex] -Попередження: Наступні конструкції є симптомами погано спроектованого коду: +Попередження: Наступні конструкції є ознакою погано спроектованого коду: - `Foo::getInstance()` - `DB::insert(...)` - `Article::setDb($db)` - `ClassName::$var` або `static::$var` -Чи зустрічаєте ви якісь з цих конструкцій у своєму коді? Якщо так, то у вас є можливість його покращити. Ви можете подумати, що це звичайні конструкції, які часто зустрічаються у типових рішеннях різних бібліотек та фреймворків. Якщо це так, то їхній дизайн коду є недосконалим. +Чи зустрічаються деякі з цих конструкцій у вашому коді? Тоді у вас є можливість його покращити. Можливо, ви думаєте, що це звичайні конструкції, які ви бачите, наприклад, у демонстраційних рішеннях різних бібліотек та фреймворків. Якщо це так, то дизайн їхнього коду не є добрим. -Ми не говоримо тут про якусь академічну чистоту. Всі ці конструкції мають одну спільну рису: вони використовують глобальний стан. А це руйнівно впливає на якість коду. Класи вводять в оману щодо своїх залежностей. Код стає непередбачуваним. Це заплутує розробників і знижує їх ефективність. +Зараз ми точно не говоримо про якусь академічну чистоту. Всі ці конструкції мають одну спільну рису: вони використовують глобальний стан. А він має руйнівний вплив на якість коду. Класи брешуть про свої залежності. Код стає непередбачуваним. Плутає програмістів та знижує їхню ефективність. -У цій главі ми пояснимо, чому так відбувається і як уникнути глобального стану. +У цьому розділі ми пояснимо, чому це так, і як уникнути глобального стану. -Глобальне взаємозв'язування .[#toc-global-interlinking] -------------------------------------------------------- +Глобальний зв'язок +------------------ -В ідеальному світі об'єкт повинен взаємодіяти лише з тими об'єктами, на які [йому безпосередньо передано |passing-dependencies] посилання. Якщо я створюю два об'єкти `A` та `B` і ніколи не передаю між ними посилання, то ні `A`, ні `B` не можуть отримати доступ до стану іншого об'єкта або змінити його. Це дуже бажана властивість коду. Це як мати батарейку і лампочку; лампочка не засвітиться, поки ви не з'єднаєте її з батарейкою дротом. +В ідеальному світі об'єкт повинен мати можливість спілкуватися лише з об'єктами, які йому були [безпосередньо передані |passing-dependencies]. Якщо я створю два об'єкти `A` та `B` і ніколи не передам посилання між ними, то ні `A`, ні `B` не зможуть отримати доступ до іншого об'єкта або змінити його стан. Це дуже бажана властивість коду. Це схоже на те, якби у вас була батарейка та лампочка; лампочка не світитиме, доки ви не з'єднаєте її з батарейкою дротом. -Однак це не стосується глобальних (статичних) змінних або синглетонів. Об'єкт `A` може отримати *бездротовий* доступ до об'єкта `C` і модифікувати його без передачі посилань, викликавши `C::changeSomething()`. Якщо об'єкт `B` також звертається до глобальної `C`, то `A` і `B` можуть впливати один на одного через `C`. +Але це не стосується глобальних (статичних) змінних або singleton'ів. Об'єкт `A` міг би *бездротово* отримати доступ до об'єкта `C` та модифікувати його без будь-якої передачі посилання, викликавши `C::changeSomething()`. Якщо об'єкт `B` також звернеться до глобального `C`, то `A` та `B` можуть взаємно впливати один на одного через `C`. -Використання глобальних змінних вводить нову форму *бездротового* зв'язку, який не видно ззовні. Це створює димову завісу, яка ускладнює розуміння і використання коду. Щоб по-справжньому зрозуміти залежності, розробникам доводиться читати кожен рядок вихідного коду, замість того, щоб просто ознайомитися з інтерфейсами класів. До того ж, це заплутування абсолютно непотрібне. Глобальний стан використовується тому, що він легко доступний з будь-якого місця і дозволяє, наприклад, писати в базу даних через глобальний (статичний) метод `DB::insert()`. Однак, як ми побачимо, користь від нього мінімальна, а ускладнення, які він вносить, дуже серйозні. +Використання глобальних змінних вносить у систему нову форму *бездротового* зв'язку, яка невидима ззовні. Створює димову завісу, що ускладнює розуміння та використання коду. Щоб розробники дійсно зрозуміли залежності, вони повинні прочитати кожен рядок вихідного коду. Замість простого ознайомлення з інтерфейсом класів. До того ж, це абсолютно зайвий зв'язок. Глобальний стан використовується тому, що він легко доступний звідусіль і дозволяє, наприклад, записати в базу даних через глобальний (статичний) метод `DB::insert()`. Але, як ми покажемо, перевага, яку це дає, незначна, натомість ускладнення це спричиняє фатальні. .[note] -З точки зору поведінки, немає ніякої різниці між глобальною і статичною змінною. Вони однаково шкідливі. +З точки зору поведінки немає різниці між глобальною та статичною змінною. Вони однаково шкідливі. -Страшна дія на відстані .[#toc-the-spooky-action-at-a-distance] ---------------------------------------------------------------- +Моторошна дія на відстані +------------------------- -"Моторошна дія на відстані" - так Альберт Ейнштейн назвав явище у квантовій фізиці, від якого у 1935 році у нього мурашки по шкірі. -Це квантова заплутаність, особливість якої полягає в тому, що коли ви вимірюєте інформацію про одну частинку, ви одразу ж впливаєте на іншу частинку, навіть якщо вони знаходяться на відстані мільйонів світлових років одна від одної. -Що, здавалося б, порушує фундаментальний закон Всесвіту, згідно з яким ніщо не може рухатися швидше за світло. +"Моторошна дія на відстані" - так славетно назвав у 1935 році Альберт Ейнштейн явище в квантовій фізиці, яке викликало у нього мурашки по шкірі. +Йдеться про квантове заплутування, особливістю якого є те, що коли ви вимірюєте інформацію про одну частинку, ви миттєво впливаєте на іншу частинку, навіть якщо вони знаходяться на відстані мільйонів світлових років одна від одної. Що, здавалося б, порушує основний закон Всесвіту, що ніщо не може поширюватися швидше за світло. -У світі програмного забезпечення ми можемо назвати "моторошною дією на відстані" ситуацію, коли ми запускаємо процес, який вважаємо ізольованим (бо не передали йому жодних посилань), але у віддалених місцях системи відбуваються неочікувані взаємодії та зміни стану, про які ми не повідомили об'єкту. Це може відбуватися тільки через глобальний стан. +У світі програмного забезпечення ми можемо назвати "моторошною дією на відстані" ситуацію, коли ми запускаємо якийсь процес, про який вважаємо, що він ізольований (оскільки ми не передали йому жодних посилань), але у віддалених місцях системи відбуваються несподівані взаємодії та зміни стану, про які ми не мали уявлення. Це може статися лише через глобальний стан. -Уявіть, що ви приєдналися до команди розробників проекту, яка має велику, зрілу кодову базу. Ваш новий керівник просить вас реалізувати нову функцію і, як хороший розробник, ви починаєте з написання тесту. Але оскільки ви новачок у проекті, ви робите багато дослідницьких тестів типу "що станеться, якщо я викличу цей метод". І ви намагаєтеся написати наступний тест: +Уявіть, що ви приєдналися до команди розробників проекту, який має велику розвинену кодову базу. Ваш новий керівник просить вас реалізувати нову функцію, і ви, як правильний розробник, починаєте з написання тесту. Але оскільки ви новачок у проекті, ви робите багато дослідницьких тестів типу "що станеться, якщо я викличу цей метод". І спробуєте написати наступний тест: ```php function testCreditCardCharge() @@ -48,20 +47,17 @@ function testCreditCardCharge() } ``` -Ви запускаєте код, можливо, кілька разів, і через деякий час помічаєте на своєму телефоні повідомлення від банку про те, що кожного разу, коли ви його запускали, з вашої кредитної картки було знято $100 🤦‍♂️. +Ви запускаєте код, можливо, кілька разів, і через деякий час помічаєте на мобільному сповіщення від банку, що при кожному запуску з вашої платіжної картки списувалося 100 доларів 🤦‍♂️ -Як тест міг спричинити реальне списання коштів? Працювати з кредитною карткою непросто. Ви повинні взаємодіяти зі стороннім веб-сервісом, знати URL-адресу цього веб-сервісу, увійти в систему і так далі. -Жодна з цих відомостей не міститься в тесті. Навіть гірше, ви навіть не знаєте, де ця інформація присутня, а отже, як імітувати зовнішні залежності так, щоб кожен запуск не призводив до повторного списання 100 доларів. І як розробник-початківець, звідки ви могли знати, що те, що ви збираєтесь робити, призведе до того, що ви станете на 100 доларів біднішими? +Як, чорт забирай, тест міг спричинити реальне списання грошей? Оперувати платіжною карткою непросто. Ви повинні спілкуватися з веб-сервісом третьої сторони, ви повинні знати URL цього веб-сервісу, ви повинні увійти в систему і так далі. Жодна з цих інформацій не міститься в тесті. Ба більше, ви навіть не знаєте, де ця інформація знаходиться, а отже, і як мокувати зовнішні залежності, щоб кожен запуск не призводив до того, що знову списується 100 доларів. І як ви, як новий розробник, мали знати, що те, що ви збираєтеся зробити, призведе до того, що ви станете на 100 доларів біднішими? -На відстані це виглядає моторошно! +Це моторошна дія на відстані! -У вас немає іншого вибору, окрім як перелопатити купу вихідного коду, розпитуючи старших і досвідченіших колег, поки ви не зрозумієте, як працюють зв'язки в проекті. -Це пов'язано з тим, що, дивлячись на інтерфейс класу `CreditCard`, ви не можете визначити глобальний стан, який потрібно ініціалізувати. Навіть перегляд вихідного коду класу не підкаже вам, який метод ініціалізації викликати. У кращому випадку, ви можете знайти глобальну змінну, до якої здійснюється доступ, і спробувати здогадатися, як її ініціалізувати, виходячи з цього. +Вам не залишається нічого іншого, як довго копатися в купі вихідних кодів, питати старших та досвідченіших колег, перш ніж ви зрозумієте, як працюють зв'язки в проекті. Це спричинено тим, що при погляді на інтерфейс класу `CreditCard` неможливо визначити глобальний стан, який потрібно ініціалізувати. Навіть погляд на вихідний код класу вам не підкаже, який ініціалізаційний метод ви маєте викликати. У кращому випадку ви можете знайти глобальну змінну, до якої здійснюється доступ, і з неї спробувати здогадатися, як її ініціалізувати. -Класи в такому проекті - патологічні брехуни. Платіжна картка робить вигляд, що ви можете просто створити її екземпляр і викликати метод `charge()`. Однак вона таємно взаємодіє з іншим класом, `PaymentGateway`. Навіть в її інтерфейсі написано, що вона може ініціалізуватися самостійно, але насправді вона витягує облікові дані з якогось конфігураційного файлу і так далі. -Розробникам, які написали цей код, зрозуміло, що `CreditCard` потрібен `PaymentGateway`. Вони написали код саме так. Але для новачків у проекті це повна загадка і заважає навчанню. +Класи в такому проекті є патологічними брехунами. Платіжна картка вдає, що її достатньо інстанціювати та викликати метод `charge()`. Але приховано вона співпрацює з іншим класом `PaymentGateway`, який представляє платіжний шлюз. Його інтерфейс також говорить, що його можна ініціалізувати окремо, але насправді він витягує облікові дані з якогось конфігураційного файлу і так далі. Розробникам, які написали цей код, зрозуміло, що `CreditCard` потребує `PaymentGateway`. Вони написали код таким чином. Але для кожного, хто є новачком у проекті, це повна загадка і заважає навчанню. -Як виправити ситуацію? Дуже просто. **Дозвольте API оголошувати залежності.** +Як виправити ситуацію? Легко. **Нехай API декларує залежності.** ```php function testCreditCardCharge() @@ -72,36 +68,35 @@ function testCreditCardCharge() } ``` -Зверніть увагу, як взаємозв'язки в коді стають раптово очевидними. Оголосивши, що метод `charge()` потребує `PaymentGateway`, вам не потрібно нікого питати, як код взаємозалежний. Ви знаєте, що маєте створити його екземпляр, і коли ви намагаєтесь це зробити, то стикаєтесь з тим, що вам потрібно вказати параметри доступу. Без них код навіть не запуститься. +Зверніть увагу, як раптом стають очевидними зв'язки всередині коду. Тим, що метод `charge()` декларує, що потребує `PaymentGateway`, вам не потрібно нікого питати про те, як пов'язаний код. Ви знаєте, що повинні створити його екземпляр, і коли спробуєте це зробити, зіткнетеся з тим, що повинні надати параметри доступу. Без них код навіть не запуститься. -І найголовніше, тепер ви можете погратися з платіжним шлюзом, щоб з вас не знімали $100 щоразу, коли ви запускаєте тест. +І головне, тепер ви можете мокувати платіжний шлюз, тож при кожному запуску тесту вам не буде нараховуватися 100 доларів. -Глобальний стан призводить до того, що ваші об'єкти можуть таємно отримувати доступ до речей, які не оголошені в їхніх API, і, як наслідок, робить ваші API патологічними брехунами. +Глобальний стан призводить до того, що ваші об'єкти можуть таємно отримувати доступ до речей, які не задекларовані в їхньому API, і в результаті роблять ваші API патологічними брехунами. -Можливо, ви не замислювалися про це раніше, але кожного разу, коли ви використовуєте глобальний стан, ви створюєте таємні бездротові канали зв'язку. Моторошні віддалені дії змушують розробників читати кожен рядок коду, щоб зрозуміти потенційні взаємодії, знижують продуктивність розробників і заплутують нових членів команди. -Якщо ви той, хто створив код, ви знаєте реальні залежності, але будь-хто, хто прийде після вас, нічого не знає. +Можливо, ви раніше не думали про це так, але кожного разу, коли ви використовуєте глобальний стан, ви створюєте таємні бездротові канали зв'язку. Моторошна дія на відстані змушує розробників читати кожен рядок коду, щоб зрозуміти потенційні взаємодії, знижує продуктивність розробників та плутає нових членів команди. Якщо ви той, хто створив код, ви знаєте справжні залежності, але кожен, хто прийде після вас, безпорадний. -Не пишіть код, який використовує глобальний стан, краще передавайте залежності. Тобто, ін'єкція залежностей. +Не пишіть код, який використовує глобальний стан, надавайте перевагу передачі залежностей. Тобто dependency injection. -Крихкість глобальної держави .[#toc-brittleness-of-the-global-state] --------------------------------------------------------------------- +Крихкість глобального стану +--------------------------- -У коді, який використовує глобальний стан та синглетони, ніколи не можна бути впевненим, коли і ким цей стан було змінено. Цей ризик присутній вже на етапі ініціалізації. Наступний код повинен створити з'єднання з базою даних та ініціалізувати платіжний шлюз, але він постійно видає виключення, і пошук причини цього є надзвичайно нудним: +У коді, який використовує глобальний стан та singleton'и, ніколи не можна бути впевненим, коли і хто цей стан змінив. Цей ризик з'являється вже при ініціалізації. Наступний код має створити підключення до бази даних та ініціалізувати платіжний шлюз, однак постійно викидає виняток, і пошук причини є надзвичайно тривалим: ```php PaymentGateway::init(); DB::init('mysql:', 'user', 'password'); ``` -Ви повинні детально переглянути код, щоб виявити, що об'єкт `PaymentGateway` отримує доступ до інших об'єктів бездротовим способом, деякі з яких вимагають з'єднання з базою даних. Таким чином, ви повинні ініціалізувати базу даних перед `PaymentGateway`. Однак димова завіса глобального стану приховує це від вас. Скільки часу ви б заощадили, якби API кожного класу не брехав і оголошував свої залежності? +Ви повинні детально переглядати код, щоб з'ясувати, що об'єкт `PaymentGateway` бездротово звертається до інших об'єктів, деякі з яких вимагають підключення до бази даних. Отже, необхідно ініціалізувати базу даних раніше, ніж `PaymentGateway`. Однак димова завіса глобального стану це від вас приховує. Скільки часу ви б зекономили, якби API окремих класів не обманювало і декларувало свої залежності? ```php $db = new DB('mysql:', 'user', 'password'); $gateway = new PaymentGateway($db, ...); ``` -Схожа проблема виникає при використанні глобального доступу до з'єднання з базою даних: +Подібна проблема виникає і при використанні глобального доступу до підключення до бази даних: ```php use Illuminate\Support\Facades\DB; @@ -115,7 +110,7 @@ class Article } ``` -При виклику методу `save()` немає впевненості, що з'єднання з базою даних вже створено і хто відповідає за його створення. Наприклад, якщо ми захочемо змінити підключення до бази даних на льоту, можливо, з метою тестування, нам, ймовірно, доведеться створити додаткові методи, такі як `DB::reconnect(...)` або `DB::reconnectForTest()`. +При виклику методу `save()` невідомо, чи було вже створено підключення до бази даних та хто несе відповідальність за його створення. Якщо ми хочемо, наприклад, змінювати підключення до бази даних під час виконання, наприклад, для тестів, нам, ймовірно, довелося б створити додаткові методи, такі як `DB::reconnect(...)` або `DB::reconnectForTest()`. Розглянемо приклад: @@ -127,9 +122,9 @@ Foo::doSomething(); $article->save(); ``` -Де ми можемо бути впевнені, що при виклику `$article->save()` дійсно використовується тестова база даних? Що, якщо метод `Foo::doSomething()` змінив глобальне з'єднання з базою даних? Щоб з'ясувати це, нам доведеться вивчити вихідний код класу `Foo` і, ймовірно, багатьох інших класів. Однак такий підхід дасть лише короткострокову відповідь, оскільки в майбутньому ситуація може змінитися. +Де ми маємо впевненість, що при виклику `$article->save()` дійсно використовується тестова база даних? Що, якщо метод `Foo::doSomething()` змінив глобальне підключення до бази даних? Щоб з'ясувати це, нам довелося б дослідити вихідний код класу `Foo` і, ймовірно, багатьох інших класів. Цей підхід, однак, дав би лише короткострокову відповідь, оскільки ситуація може змінитися в майбутньому. -Що, якщо ми перемістимо підключення до бази даних у статичну змінну всередині класу `Article`? +А що, якщо підключення до бази даних перемістити в статичну змінну всередині класу `Article`? ```php class Article @@ -148,11 +143,11 @@ class Article } ``` -Це нічого не змінить. Проблема в глобальному стані, і не має значення, в якому класі він ховається. У цьому випадку, як і в попередньому, ми не маємо жодного уявлення про те, до якої бази даних відбувається запис, коли викликається метод `$article->save()`. Будь-хто на віддаленому кінці програми може в будь-який момент змінити базу даних за допомогою методу `Article::setDb()`. Під нашими руками. +Це абсолютно нічого не змінило. Проблемою є глобальний стан, і абсолютно байдуже, в якому класі він ховається. У цьому випадку, так само як і в попередньому, ми не маємо при виклику методу `$article->save()` жодного натяку на те, до якої бази даних буде здійснено запис. Будь-хто на іншому кінці програми міг будь-коли за допомогою `Article::setDb()` змінити базу даних. Нам під носом. -Глобальний стан робить наш додаток **надзвичайно вразливим**. +Глобальний стан робить нашу програму **надзвичайно крихкою**. -Однак є простий спосіб вирішити цю проблему. Просто змусьте API декларувати залежності, щоб забезпечити належну функціональність. +Однак існує простий спосіб вирішити цю проблему. Достатньо дозволити API декларувати залежності, що забезпечить правильну функціональність. ```php class Article @@ -174,15 +169,15 @@ Foo::doSomething(); $article->save(); ``` -Такий підхід позбавляє вас від необхідності турбуватися про приховані та неочікувані зміни у з'єднаннях з базою даних. Тепер ми точно знаємо, де зберігається стаття, і жодні модифікації коду в іншому, не пов'язаному з нею класі, більше не зможуть змінити ситуацію. Код більше не крихкий, а стабільний. +Завдяки цьому підходу зникає побоювання щодо прихованих та несподіваних змін підключення до бази даних. Тепер ми маємо впевненість, куди зберігається стаття, і жодні зміни коду всередині іншого непов'язаного класу вже не можуть змінити ситуацію. Код вже не крихкий, а стабільний. -Не пишіть код, який використовує глобальний стан, краще передавайте залежності. Таким чином, ін'єкція залежностей. +Не пишіть код, який використовує глобальний стан, надавайте перевагу передачі залежностей. Тобто dependency injection. -Синглтон .[#toc-singleton] --------------------------- +Singleton +--------- -Синглтон - це патерн проектування, який, за [визначенням |https://en.wikipedia.org/wiki/Singleton_pattern] з відомої публікації Gang of Four, обмежує клас єдиним екземпляром і пропонує глобальний доступ до нього. Реалізація цього патерну зазвичай нагадує наступний код: +Singleton — це патерн проектування, який, згідно з "визначенням":https://en.wikipedia.org/wiki/Singleton_pattern з відомої публікації Gang of Four, обмежує клас єдиним екземпляром і пропонує до нього глобальний доступ. Реалізація цього патерну зазвичай схожа на наступний код: ```php class Singleton @@ -195,38 +190,35 @@ class Singleton return self::$instance; } - // та інші методи, що виконують функції класу + // та інші методи, що виконують функції даного класу } ``` -На жаль, синглтон вводить в додаток глобальний стан. А як ми показали вище, глобальний стан небажаний. Саме тому синглтон вважається антипаттерном. +На жаль, singleton вводить у програму глобальний стан. А як ми показали вище, глобальний стан є небажаним. Тому singleton вважається антипатерном. -Не використовуйте синглетони у своєму коді і замініть їх іншими механізмами. Синглетони насправді не потрібні. Однак, якщо вам потрібно гарантувати існування єдиного екземпляру класу для всього додатку, залиште це [контейнеру DI |container]. -Таким чином, створіть синглтон додатку, або сервіс. Тоді клас не буде надавати власної унікальності (тобто не матиме методу `getInstance()` та статичної змінної) і виконуватиме лише свої функції. Таким чином, він перестане порушувати принцип єдиної відповідальності. +Не використовуйте у своєму коді singleton'и та замініть їх іншими механізмами. Singleton'и вам дійсно не потрібні. Однак, якщо вам потрібно гарантувати існування єдиного екземпляра класу для всієї програми, залиште це на [DI-контейнера |container]. Створіть таким чином аплікаційний singleton, тобто сервіс. Тим самим клас перестане займатися забезпеченням власної унікальності (тобто не матиме методу `getInstance()` та статичної змінної) і виконуватиме лише свої функції. Так він перестане порушувати принцип єдиної відповідальності. -Глобальний стан проти тестів .[#toc-global-state-versus-tests] --------------------------------------------------------------- +Глобальний стан проти тестів +---------------------------- -При написанні тестів ми припускаємо, що кожен тест є ізольованою одиницею, і жоден зовнішній стан не входить до нього. І жоден стан не виходить з тесту. Коли тест завершується, будь-який стан, пов'язаний з тестом, повинен бути автоматично видалений збирачем сміття. Це робить тести ізольованими. Тому ми можемо запускати тести у будь-якому порядку. +При написанні тестів ми припускаємо, що кожен тест є ізольованою одиницею і що до нього не входить жоден зовнішній стан. І жоден стан тести не залишає. Після завершення тесту весь пов'язаний з тестом стан повинен бути автоматично видалений збирачем сміття. Завдяки цьому тести ізольовані. Тому ми можемо запускати тести в будь-якому порядку. -Однак, якщо присутні глобальні стани/синглетони, всі ці гарні припущення руйнуються. Стан може входити і виходити з тесту. Несподівано, порядок виконання тестів може мати значення. +Однак, якщо присутні глобальні стани/singleton'и, всі ці приємні припущення руйнуються. Стан може входити в тест і виходити з нього. Раптом може мати значення порядок тестів. -Щоб взагалі тестувати синглетони, розробникам часто доводиться послаблювати їх властивості, можливо, дозволяючи замінювати один екземпляр іншим. Такі рішення, в кращому випадку, є хаками, які створюють код, який важко підтримувати і розуміти. Будь-який тест або метод `tearDown()`, який впливає на будь-який глобальний стан, повинен скасувати ці зміни. +Щоб взагалі мати можливість тестувати singleton'и, розробники часто змушені послаблювати їхні властивості, наприклад, дозволяючи замінити екземпляр іншим. Такі рішення в кращому випадку є хаком, який створює код, що важко підтримувати та розуміти. Кожен тест або метод `tearDown()`, який впливає на будь-який глобальний стан, повинен ці зміни скасувати. -Глобальний стан - це найбільший головний біль у модульному тестуванні! +Глобальний стан — це найбільший головний біль при юніт-тестуванні! -Як виправити ситуацію? Дуже просто. Не пишіть код, який використовує синглетони, краще передавайте залежності. Тобто, ін'єкція залежностей. +Як виправити ситуацію? Легко. Не пишіть код, який використовує singleton'и, надавайте перевагу передачі залежностей. Тобто dependency injection. -Глобальні константи .[#toc-global-constants] --------------------------------------------- +Глобальні константи +------------------- -Глобальний стан не обмежується використанням синглетонів і статичних змінних, але також може застосовуватися до глобальних констант. +Глобальний стан не обмежується лише використанням singleton'ів та статичних змінних, але може стосуватися також глобальних констант. -З константами, значення яких не надає нам ніякої нової (`M_PI`) або корисної (`PREG_BACKTRACK_LIMIT_ERROR`) інформації, явно все гаразд. -І навпаки, константи, які слугують способом "бездротової" передачі інформації всередині коду, є нічим іншим, як прихованою залежністю. Як `LOG_FILE` у наступному прикладі. -Використання константи `FILE_APPEND` є абсолютно коректним. +Константи, значення яких не приносить нам жодної нової (`M_PI`) або корисної (`PREG_BACKTRACK_LIMIT_ERROR`) інформації, є однозначно в порядку. Навпаки, константи, які служать способом *бездротово* передати інформацію всередину коду, є нічим іншим, як прихованою залежністю. Як, наприклад, `LOG_FILE` у наступному прикладі. Використання константи `FILE_APPEND` є цілком коректним. ```php const LOG_FILE = '...'; @@ -242,7 +234,7 @@ class Foo } ``` -У цьому випадку ми повинні оголосити параметр у конструкторі класу `Foo`, щоб зробити його частиною API: +У цьому випадку ми повинні задекларувати параметр у конструкторі класу `Foo`, щоб він став частиною API: ```php class Foo @@ -261,44 +253,42 @@ class Foo } ``` -Тепер ми можемо передавати інформацію про шлях до файлу логування і легко змінювати його за потреби, що полегшує тестування і підтримку коду. +Тепер ми можемо передати інформацію про шлях до файлу для логування та легко змінювати її за потребою, що полегшує тестування та підтримку коду. -Глобальні функції та статичні методи .[#toc-global-functions-and-static-methods] --------------------------------------------------------------------------------- +Глобальні функції та статичні методи +------------------------------------ -Ми хочемо підкреслити, що використання статичних методів і глобальних функцій саме по собі не є проблематичним. Ми вже пояснювали недоречність використання `DB::insert()` та подібних методів, але завжди йшлося про глобальний стан, що зберігається у статичній змінній. Метод `DB::insert()` вимагає наявності статичної змінної, оскільки він зберігає з'єднання з базою даних. Без цієї змінної реалізація методу була б неможливою. +Хочемо підкреслити, що саме використання статичних методів та глобальних функцій не є проблематичним. Ми пояснювали, в чому полягає недоцільність використання `DB::insert()` та подібних методів, але завжди йшлося лише про глобальний стан, який зберігається в якійсь статичній змінній. Метод `DB::insert()` вимагає існування статичної змінної, оскільки в ній зберігається підключення до бази даних. Без цієї змінної було б неможливо реалізувати метод. -Використання детермінованих статичних методів і функцій, таких як `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` і багатьох інших, цілком узгоджується з ін'єкцією залежностей. Ці функції завжди повертають однакові результати від однакових вхідних параметрів і тому є передбачуваними. Вони не використовують жодного глобального стану. +Використання детермінованих статичних методів та функцій, таких як `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` та багатьох інших, є цілком сумісним з dependency injection. Ці функції завжди повертають однакові результати для однакових вхідних параметрів і тому є передбачуваними. Вони не використовують жодного глобального стану. -Однак, в PHP є функції, які не є детермінованими. До них відноситься, наприклад, функція `htmlspecialchars()`. Її третій параметр, `$encoding`, якщо не вказано, за замовчуванням приймає значення параметра конфігурації `ini_get('default_charset')`. Тому рекомендується завжди вказувати цей параметр, щоб уникнути можливої непередбачуваної поведінки функції. Nette послідовно це робить. +Однак існують і функції в PHP, які не є детермінованими. До них належить, наприклад, функція `htmlspecialchars()`. Її третій параметр `$encoding`, якщо не вказаний, за замовчуванням має значення конфігураційної опції `ini_get('default_charset')`. Тому рекомендується цей параметр завжди вказувати, щоб уникнути можливої непередбачуваної поведінки функції. Nette це послідовно робить. -Деякі функції, такі як `strtolower()`, `strtoupper()`, тощо, у недалекому минулому мали недетерміновану поведінку і залежали від параметра `setlocale()`. Це викликало багато ускладнень, найчастіше при роботі з турецькою мовою. -Це пов'язано з тим, що турецька мова розрізняє верхній і нижній регістр `I` з крапкою і без неї. Тому `strtolower('I')` повертав символ `ı`, а `strtoupper('i')` повертав символ `İ`, що призводило до того, що додатки викликали ряд загадкових помилок. -Однак ця проблема була виправлена у версії PHP 8.2, і функції більше не залежать від локалі. +Деякі функції, такі як `strtolower()`, `strtoupper()` та подібні, в недавньому минулому поводилися недетерміновано і залежали від налаштування `setlocale()`. Це спричиняло багато ускладнень, найчастіше при роботі з турецькою мовою. Вона розрізняє малу та велику літеру `I` з крапкою та без крапки. Отже, `strtolower('I')` повертало символ `ı`, а `strtoupper('i')` — символ `İ`, що призводило до того, що програми починали спричиняти низку загадкових помилок. Ця проблема, однак, була усунена в PHP версії 8.2, і функції вже не залежать від локалі. -Це гарний приклад того, як глобальний стан дошкуляв тисячам розробників по всьому світу. Рішенням було замінити його на ін'єкцію залежностей. +Це гарний приклад того, як глобальний стан завдав клопоту тисячам розробників у всьому світі. Рішенням було замінити його на dependency injection. -Коли можна використовувати глобальну державу? .[#toc-when-is-it-possible-to-use-global-state] ---------------------------------------------------------------------------------------------- +Коли можна використовувати глобальний стан? +------------------------------------------- -Існують певні специфічні ситуації, коли можна використовувати глобальний стан. Наприклад, під час налагодження коду, коли потрібно вивести значення змінної або виміряти тривалість виконання певної частини програми. У таких випадках, коли йдеться про тимчасові дії, які згодом будуть видалені з коду, правомірно використовувати глобально доступний дампер або секундомір. Ці інструменти не є частиною дизайну коду. +Існують певні специфічні ситуації, коли можна використовувати глобальний стан. Наприклад, при налагодженні коду, коли потрібно вивести значення змінної або виміряти тривалість певної частини програми. У таких випадках, що стосуються тимчасових дій, які пізніше будуть видалені з коду, легітимно використовувати глобально доступний дампер або секундомір. Ці інструменти не є частиною дизайну коду. -Інший приклад - функції для роботи з регулярними виразами `preg_*`, які внутрішньо зберігають скомпільовані регулярні вирази в статичному кеші в пам'яті. Коли ви викликаєте один і той самий регулярний вираз кілька разів у різних частинах коду, він компілюється лише один раз. Кеш економить продуктивність, а також є повністю невидимим для користувача, тому таке використання можна вважати легітимним. +Іншим прикладом є функції для роботи з регулярними виразами `preg_*`, які внутрішньо зберігають скомпільовані регулярні вирази в статичному кеші в пам'яті. Коли ви викликаєте той самий регулярний вираз кілька разів у різних місцях коду, він компілюється лише один раз. Кеш економить продуктивність і водночас є для користувача абсолютно невидимим, тому таке використання можна вважати легітимним. -Підсумок .[#toc-summary] ------------------------- +Резюме +------ -Ми показали, чому це має сенс +Ми розглянули, чому має сенс: 1) Видалити всі статичні змінні з коду -2) Оголосити залежності -3) І використовувати ін'єкцію залежностей +2) Декларувати залежності +3) І використовувати dependency injection -Коли ви обмірковуєте дизайн коду, пам'ятайте, що кожне `static $foo` представляє проблему. Для того, щоб ваш код був середовищем, що поважає DI, важливо повністю викорінити глобальний стан і замінити його на ін'єкцію залежностей. +Коли ви продумуєте дизайн коду, пам'ятайте, що кожне `static $foo` становить проблему. Щоб ваш код був середовищем, що поважає DI, необхідно повністю викорінити глобальний стан і замінити його за допомогою dependency injection. -Під час цього процесу ви можете виявити, що вам потрібно розділити клас, оскільки він має більше однієї відповідальності. Не турбуйтеся про це; прагніть до принципу єдиної відповідальності. +Під час цього процесу ви, можливо, виявите, що потрібно розділити клас, оскільки він має більше однієї відповідальності. Не бійтеся цього; прагніть до принципу єдиної відповідальності. -*Я хотів би подякувати Мішко Хевері, чиї статті, такі як [Flaw: Brittle Global State та Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/], лягли в основу цієї глави. Я хотів би подякувати Мішко Хевері.* +*Я хотів би подякувати Мішкові Хевері, чиї статті, такі як [Flaw: Brittle Global State & Singletons |https://web.archive.org/web/20230321084133/http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/], є основою цього розділу.* diff --git a/dependency-injection/uk/introduction.texy b/dependency-injection/uk/introduction.texy index 002eb58b89..81cdcb0fa5 100644 --- a/dependency-injection/uk/introduction.texy +++ b/dependency-injection/uk/introduction.texy @@ -1,58 +1,58 @@ -Що таке "впровадження залежностей"? -*********************************** +Що таке Dependency Injection? +***************************** .[perex] -У цьому розділі ви познайомитеся з основними практиками програмування, яких слід дотримуватися при написанні будь-якої програми. Це основи, необхідні для написання чистого, зрозумілого та зручного для супроводу коду. +Цей розділ познайомить вас з основними практиками програмування, яких слід дотримуватися під час написання будь-яких застосунків. Це основи, необхідні для написання чистого, зрозумілого та підтримуваного коду. -Якщо ви вивчите і будете дотримуватися цих правил, Nette буде поруч з вами на кожному кроці. Він виконає за вас рутинні завдання і забезпечить максимальний комфорт, щоб ви могли зосередитися на самій логіці. +Якщо ви засвоїте ці правила і будете їх дотримуватися, Nette допомагатиме вам на кожному кроці. Він вирішуватиме за вас рутинні завдання та забезпечить максимальний комфорт, щоб ви могли зосередитися на самій логіці. -Принципи, які ми покажемо тут, досить прості. Вам не потрібно ні про що турбуватися. +Принципи, які ми тут покажемо, при цьому досить прості. Вам не потрібно нічого боятися. -Пам'ятаєте свою першу програму? .[#toc-remember-your-first-program] -------------------------------------------------------------------- +Пам'ятаєте свою першу програму? +------------------------------- -Ми не знаємо, якою мовою ви його написали, але якщо це був PHP, то він міг би виглядати приблизно так: +Ми не знаємо, якою мовою ви її написали, але якби це була PHP, вона, ймовірно, виглядала б приблизно так: ```php -function addition(float $a, float $b): float +function soucet(float $a, float $b): float { return $a + $b; } -echo addition(23, 1); // відбитки 24 +echo soucet(23, 1); // виведе 24 ``` -Кілька тривіальних рядків коду, але в них заховано стільки ключових понять. Що є змінні. Що код розбивається на менші одиниці, які є функціями, наприклад. Що ми передаємо їм вхідні аргументи, а вони повертають результати. Не вистачає лише умов та циклів. +Кілька тривіальних рядків коду, але в них приховано стільки ключових концепцій. Що існують змінні. Що код ділиться на менші одиниці, якими, наприклад, є функції. Що ми передаємо їм вхідні аргументи, а вони повертають результати. Не вистачає лише умов та циклів. -Той факт, що функція отримує вхідні дані і повертає результат, є цілком зрозумілою концепцією, яка також використовується в інших галузях, таких як математика. +Те, що ми передаємо дані у функцію, а вона повертає результат, є цілком зрозумілою концепцією, яка використовується і в інших галузях, наприклад, у математиці. -Функція має сигнатуру, яка складається з її імені, списку параметрів та їх типів і, нарешті, типу значення, що повертається. Як користувачів, нас цікавить сигнатура, а про внутрішню реалізацію нам зазвичай не потрібно нічого знати. +Функція має свою сигнатуру, яка складається з її назви, переліку параметрів та їхніх типів, і, нарешті, типу значення, що повертається. Як користувачів, нас цікавить сигнатура, про внутрішню реалізацію нам зазвичай нічого знати не потрібно. -Тепер уявіть, що сигнатура функції має такий вигляд: +Тепер уявіть, що сигнатура функції виглядала б так: ```php -function addition(float $x): float +function soucet(float $x): float ``` -Додавання з одним параметром? Це дивно... А як щодо цього? +Сума з одним параметром? Це дивно… А як щодо цього? ```php -function addition(): float +function soucet(): float ``` -Це дійсно дивно, чи не так? Як використовується ця функція? +Це вже справді дуже дивно, чи не так? Як, напевно, використовується функція? ```php -echo addition(); // що він друкує? +echo soucet(); // що вона, ймовірно, виведе? ``` -Дивлячись на такий код, ми б розгубилися. Його не зрозуміє не тільки новачок, але навіть досвідчений програміст не розбереться в такому коді. +Дивлячись на такий код, ми були б спантеличені. Його не зрозумів би не тільки початківець, такий код не зрозуміє і досвідчений програміст. -Вам цікаво, як така функція виглядатиме всередині? Звідки б вона брала доданки? Напевно, вона буде отримувати їх сама, можливо, ось так: +Ви думаєте, як би така функція виглядала всередині? Звідки вона візьме доданки? Мабуть, вона *якимось чином* отримала б їх сама, наприклад, так: ```php -function addition(): float +function soucet(): float { $a = Input::get('a'); $b = Input::get('b'); @@ -60,71 +60,71 @@ function addition(): float } ``` -Виявляється, в тілі функції є приховані зв'язки з іншими функціями (або статичними методами), і щоб з'ясувати, звідки насправді беруться доданки, треба копати далі. +У тілі функції ми виявили приховані зв'язки з іншими глобальними функціями чи статичними методами. Щоб з'ясувати, звідки насправді беруться доданки, нам потрібно шукати далі. -Але не сюди! .[#toc-not-this-way] ---------------------------------- +Не сюди! +-------- -Дизайн, який ми щойно показали, є суттю багатьох негативних рис: +Дизайн, який ми щойно показали, є сутністю багатьох негативних рис: -- сигнатура функції робила вигляд, що їй не потрібні доданки, що збивало нас з пантелику -- ми поняття не маємо, як змусити функцію обчислювати з двома іншими числами -- довелося зазирнути в код, щоб дізнатися, звідки беруться доданки -- ми знайшли приховані залежності -- для повного розуміння потрібно вивчити і ці залежності +- сигнатура функції вдавала, що не потребує доданків, що нас спантеличувало +- ми взагалі не знаємо, як змусити функцію додати два інші числа +- нам довелося заглянути в код, щоб з'ясувати, звідки вона бере доданки +- ми виявили приховані зв'язки +- для повного розуміння необхідно дослідити і ці зв'язки -І чи взагалі функція додавання повинна отримувати вхідні дані? Звичайно, ні. Її обов'язок - лише додавати. +А чи взагалі завданням функції додавання є отримання вхідних даних? Звісно, ні. Її відповідальність - лише саме додавання. -Ми не хочемо зустрічатися з таким кодом, і ми точно не хочемо його писати. Вихід простий: поверніться до основ і просто використовуйте параметри: +Ми не хочемо зустрічатися з таким кодом, і точно не хочемо його писати. Виправлення при цьому просте: повернутися до основ і просто використовувати параметри: ```php -function addition(float $a, float $b): float +function soucet(float $a, float $b): float { return $a + $b; } ``` -Правило №1: Нехай передадуть вам .[#toc-rule-1-let-it-be-passed-to-you] ------------------------------------------------------------------------ +Правило № 1: нехай тобі це передадуть +------------------------------------- -Найважливішим правилом є: **всі дані, які потрібні функціям або класам, повинні бути передані їм**. +Найважливіше правило звучить так: **усі дані, які потрібні функції або класу, мають бути передані їм**. -Замість того, щоб вигадувати для них приховані шляхи доступу до даних, просто передайте параметри. Ви заощадите час, який могли б витратити на винайдення прихованих шляхів, які точно не покращать ваш код. +Замість того, щоб вигадувати приховані способи, за допомогою яких вони могли б якось отримати їх самі, просто передайте параметри. Ви заощадите час, необхідний для вигадування прихованих шляхів, які точно не покращать ваш код. -Якщо ви завжди і всюди будете дотримуватися цього правила, ви на шляху до коду без прихованих залежностей. До коду, який зрозумілий не тільки автору, але й будь-кому, хто його прочитає. Де все зрозуміло з сигнатур функцій і класів, і не потрібно шукати приховані секрети в реалізації. +Якщо ви завжди і скрізь дотримуватиметеся цього правила, ви на шляху до коду без прихованих зв'язків. До коду, який зрозумілий не лише автору, але й кожному, хто читатиме його після нього. Де все зрозуміло з сигнатур функцій та класів і не потрібно шукати прихованих таємниць у реалізації. -Ця техніка професійно називається **ін'єкція залежностей**. А самі дані називаються **залежностями**. Це звичайна передача параметрів, не більше. +Ця техніка професійно називається **dependency injection**. А ці дані називаються **залежностями.** При цьому це звичайнісінька передача параметрів, нічого більше. .[note] -Будь ласка, не плутайте ін'єкцію залежностей, яка є шаблоном проектування, з "контейнером для ін'єкції залежностей", який є інструментом, тобто чимось діаметрально відмінним. З контейнерами ми розберемося пізніше. +Будь ласка, не плутайте dependency injection, що є патерном проектування, з „dependency injection container“, що є інструментом, тобто чимось діаметрально іншим. Контейнерам ми приділимо увагу пізніше. -Від функцій до класів .[#toc-from-functions-to-classes] -------------------------------------------------------- +Від функцій до класів +--------------------- -А як класи пов'язані між собою? Клас є більш складною одиницею, ніж проста функція, але правило №1 повністю застосовується і тут. Просто є [більше способів передачі аргументів |passing-dependencies]. Наприклад, так само, як і у випадку з функцією: +А як із цим пов'язані класи? Клас - це складніша одиниця, ніж проста функція, однак правило №1 діє тут без винятку. Просто існує [більше способів передачі аргументів |passing-dependencies]. Наприклад, досить схоже на випадок з функцією: ```php -class Math +class Matematika { - public function addition(float $a, float $b): float + public function soucet(float $a, float $b): float { return $a + $b; } } -$math = new Math; -echo $math->addition(23, 1); // 24 +$math = new Matematika; +echo $math->soucet(23, 1); // 24 ``` -Або іншими методами, або безпосередньо через конструктор: +Або за допомогою інших методів, чи безпосередньо конструктора: ```php -class Addition +class Soucet { public function __construct( private float $a, @@ -132,26 +132,26 @@ class Addition ) { } - public function calculate(): float + public function spocti(): float { return $this->a + $this->b; } } -$addition = new Addition(23, 1); -echo $addition->calculate(); // 24 +$soucet = new Soucet(23, 1); +echo $soucet->spocti(); // 24 ``` -Обидва приклади повністю відповідають принципам ін'єкції залежностей. +Обидва приклади повністю відповідають dependency injection. -Приклади з реального життя .[#toc-real-life-examples] ------------------------------------------------------ +Реальні приклади +---------------- -У реальному світі ви не будете писати уроки для додавання чисел. Перейдемо до практичних прикладів. +У реальному світі ви не будете писати класи для додавання чисел. Перейдемо до прикладів із практики. -Нехай у нас є клас `Article`, що представляє запис у блозі: +Маємо клас `Article`, що представляє статтю в блозі: ```php class Article @@ -162,12 +162,12 @@ class Article public function save(): void { - // зберегти статтю в базі даних + // збережемо статтю в базу даних } } ``` -а використання буде наступним: +а використання буде таким: ```php $article = new Article; @@ -176,9 +176,9 @@ $article->content = 'Every year millions of people in ...'; $article->save(); ``` -Метод `save()` збереже статтю в таблицю бази даних. Реалізувати його за допомогою [Nette Database |database:] буде простіше простого, якби не одна заковика: звідки `Article` бере з'єднання з базою даних, тобто об'єкт класу `Nette\Database\Connection`? +Метод `save()` зберігає статтю в таблицю бази даних. Реалізувати його за допомогою [Nette Database |database:] буде легко, якби не одна заковика: де `Article` має взяти підключення до бази даних, тобто об'єкт класу `Nette\Database\Connection`? -Здається, у нас є багато варіантів. Він може взяти його десь зі статичної змінної. Або успадкувати від класу, який забезпечує підключення до бази даних. Або скористатися [синглетоном |global-state#Singleton]. Або використовувати так звані фасади, які використовуються в Laravel: +Здається, у нас багато варіантів. Він може взяти його звідкись зі статичної змінної. Або успадкувати від класу, який забезпечить підключення до бази даних. Або використати так званий [singleton |global-state#Singleton]. Або так звані facades, які використовуються в Laravel: ```php use Illuminate\Support\Facades\DB; @@ -203,13 +203,13 @@ class Article Чи ні? -Згадаймо [правило №1: Let It Be Passed to You |#rule #1: Let It Be Passed to You]: всі залежності, які потрібні класу, повинні бути передані йому. Тому що якщо ми порушуємо це правило, ми стаємо на шлях до брудного коду, повного прихованих залежностей, незрозумілості, і в результаті отримуємо додаток, який буде болісно підтримувати і розвивати. +Нагадаємо [#правило №1: нехай тобі це передадуть |#Правило 1: нехай тобі це передадуть]: усі залежності, які потрібні класу, мають бути передані йому. Тому що якщо ми порушимо правило, ми ступили на шлях до брудного коду, повного прихованих зв'язків, незрозумілості, і результатом буде застосунок, який буде боляче підтримувати та розвивати. -Користувач класу `Article` поняття не має, де метод `save()` зберігає статтю. У таблиці бази даних? В якій саме, робочій чи тестовій? І як її можна змінити? +Користувач класу `Article` не знає, куди метод `save()` зберігає статтю. У таблицю бази даних? В яку, робочу чи тестову? А як це можна змінити? -Користувач дивиться на те, як реалізовано метод `save()`, і знаходить використання методу `DB::insert()`. Отже, йому доводиться шукати далі, щоб дізнатися, як цей метод отримує з'єднання з базою даних. А приховані залежності можуть утворювати досить довгий ланцюжок. +Користувач повинен подивитися, як реалізований метод `save()`, і знайде використання методу `DB::insert()`. Тож він повинен шукати далі, як цей метод отримує підключення до бази даних. А приховані зв'язки можуть утворювати досить довгий ланцюжок. -У чистому і добре спроектованому коді ніколи не буває прихованих залежностей, фасадів Laravel або статичних змінних. У чистому і добре спроектованому коді передаються аргументи: +У чистому та добре спроектованому коді ніколи не зустрічаються приховані зв'язки, фасади Laravel або статичні змінні. У чистому та добре спроектованому коді передаються аргументи: ```php class Article @@ -224,7 +224,7 @@ class Article } ``` -Ще більш практичним підходом, як ми побачимо пізніше, буде використання конструктора: +Ще практичніше, як ми побачимо далі, це буде з конструктором: ```php class Article @@ -245,11 +245,11 @@ class Article ``` .[note] -Якщо ви досвідчений програміст, ви можете подумати, що `Article` взагалі не повинен мати методу `save()`; він повинен представляти суто компонент даних, а збереженням повинен займатися окремий репозиторій. У цьому є сенс. Але це вивело б нас далеко за межі теми, якою є ін'єкція залежностей, і спроби навести прості приклади. +Якщо ви досвідчений програміст, можливо, ви думаєте, що `Article` взагалі не повинен мати метод `save()`, він повинен представляти чисто компонент даних, а про збереження повинен дбати окремий репозиторій. Це має сенс. Але цим ми б вийшли далеко за рамки теми, якою є dependency injection, та прагнення наводити прості приклади. -Якщо ви пишете клас, якому для роботи потрібна, наприклад, база даних, не вигадуйте, звідки її взяти, а передайте її. Або як параметр конструктора, або іншого методу. Визнавайте залежності. Допускайте їх в API вашого класу. Ви отримаєте зрозумілий і передбачуваний код. +Якщо ви будете писати клас, який потребує для своєї роботи, наприклад, базу даних, не вигадуйте, звідки її отримати, а нехай вам її передадуть. Наприклад, як параметр конструктора або іншого методу. Визнайте залежності. Визнайте їх в API вашого класу. Ви отримаєте зрозумілий та передбачуваний код. -А що з цим класом, який записує повідомлення про помилки: +А як щодо цього класу, який логує повідомлення про помилки: ```php class Logger @@ -262,21 +262,21 @@ class Logger } ``` -Як ви думаєте, чи дотримувалися ми [правила №1: Нехай передадуть вам |#rule #1: Let It Be Passed to You]? +Як ви думаєте, чи дотрималися ми [#правило №1: нехай тобі це передадуть |#Правило 1: нехай тобі це передадуть]? Не дотрималися. -Ключова інформація, тобто директорія з лог-файлом, *отримується* самим класом з константи. +Ключову інформацію, тобто каталог із файлом логу, клас *отримує сам* із константи. Подивіться на приклад використання: ```php $logger = new Logger; -$logger->log('The temperature is 23 °C'); -$logger->log('The temperature is 10 °C'); +$logger->log('Температура 23 °C'); +$logger->log('Температура 10 °C'); ``` -Не знаючи реалізації, чи могли б ви відповісти на питання, куди записуються повідомлення? Чи здогадалися б ви, що наявність константи `LOG_DIR` необхідна для його функціонування? І чи змогли б ви створити другий екземпляр, який би писав в інше місце? Звісно, що ні. +Без знання реалізації, чи змогли б ви відповісти на питання, куди записуються повідомлення? Чи спало б вам на думку, що для роботи потрібна константа `LOG_DIR`? А чи змогли б ви створити другий екземпляр, який буде записувати в інше місце? Звісно, ні. Давайте виправимо клас: @@ -295,24 +295,24 @@ class Logger } ``` -Тепер клас став набагато зрозумілішим, конфігурованішим, а отже і кориснішим. +Клас тепер набагато зрозуміліший, конфігурованіший і, отже, корисніший. ```php -$logger = new Logger('/path/to/log.txt'); -$logger->log('The temperature is 15 °C'); +$logger = new Logger('/шлях/до/логу.txt'); +$logger->log('Температура 15 °C'); ``` -Але мені все одно! .[#toc-but-i-don-t-care] -------------------------------------------- +Але мене це не цікавить! +------------------------ -*"Коли я створюю об'єкт Article і викликаю save(), я не хочу мати справу з базою даних; я просто хочу, щоб він був збережений у тій, яку я встановив у конфігурації."* +*«Коли я створюю об'єкт Article і викликаю save(), я не хочу займатися базою даних, я просто хочу, щоб він зберігся в ту, яку я налаштував у конфігурації».* -*"Коли я використовую Logger, я просто хочу, щоб повідомлення було записано, і не хочу розбиратися з тим, куди. Дозвольте використовувати глобальні налаштування."* +*«Коли я використовую Logger, я просто хочу, щоб повідомлення записалося, і не хочу думати куди. Нехай використовуються глобальні налаштування».* Це слушні зауваження. -Як приклад, давайте розглянемо клас, який надсилає розсилки і записує в лог, як це відбувалося: +Як приклад, покажемо клас, що розсилає інформаційні бюлетені, який залогує результат: ```php class NewsletterDistributor @@ -322,27 +322,27 @@ class NewsletterDistributor $logger = new Logger(/* ... */); try { $this->sendEmails(); - $logger->log('Emails have been sent out'); + $logger->log('Електронні листи були надіслані'); } catch (Exception $e) { - $logger->log('An error occurred during the sending'); + $logger->log('Сталася помилка під час надсилання'); throw $e; } } } ``` -Покращений `Logger`, який більше не використовує константу `LOG_DIR`, вимагає вказівки шляху до файлу в конструкторі. Як це вирішити? Класу `NewsletterDistributor` все одно, куди писати повідомлення, він просто хоче їх писати. +Покращений `Logger`, який більше не використовує константу `LOG_DIR`, вимагає в конструкторі вказати шлях до файлу. Як це вирішити? Клас `NewsletterDistributor` зовсім не цікавить, куди записуються повідомлення, він хоче їх просто записати. -Рішення знову ж таки полягає в [правилі №1: Let It Be Passed to You |#rule #1: Let It Be Passed to You]: передайте всі дані, які потрібні класу. +Рішенням знову є [#правило №1: нехай тобі це передадуть |#Правило 1: нехай тобі це передадуть]: усі дані, які потрібні класу, ми йому передаємо. -Чи означає це, що ми передаємо шлях до логу через конструктор, який потім використовуємо при створенні об'єкта `Logger`? +Отже, це означає, що ми передамо шлях до логу через конструктор, який потім використаємо при створенні об'єкта `Logger`? ```php class NewsletterDistributor { public function __construct( - private string $file, // НЕ СЮДИ! + private string $file, // ⛔ НЕ ТАК! ) { } @@ -351,7 +351,7 @@ class NewsletterDistributor $logger = new Logger($this->file); ``` -Ні, не так! Шлях не належить до даних, які потрібні класу `NewsletterDistributor`; насправді він потрібен класу `Logger`. Відчуваєте різницю? Класу `NewsletterDistributor` потрібен сам логгер. Ось його ми і передамо: +Не так! Тому що шлях **не належить** до даних, які потрібні класу `NewsletterDistributor`; вони потрібні `Logger`. Ви відчуваєте різницю? Клас `NewsletterDistributor` потребує логер як такий. Тож його ми й передамо: ```php class NewsletterDistributor @@ -365,35 +365,33 @@ class NewsletterDistributor { try { $this->sendEmails(); - $this->logger->log('Emails have been sent out'); + $this->logger->log('Електронні листи були надіслані'); } catch (Exception $e) { - $this->logger->log('An error occurred during the sending'); + $this->logger->log('Сталася помилка під час надсилання'); throw $e; } } } ``` -Тепер з сигнатур класу `NewsletterDistributor` зрозуміло, що ведення логів також є частиною його функціоналу. І завдання замінити логгер на інший, можливо, для тестування, є абсолютно тривіальним. -Більш того, якщо зміниться конструктор класу `Logger`, то це ніяк не вплине на наш клас. +Тепер із сигнатур класу `NewsletterDistributor` зрозуміло, що частиною його функціональності є логування. А завдання замінити логер на інший, наприклад, для тестування, є абсолютно тривіальним. Крім того, якщо конструктор класу `Logger` зміниться, це ніяк не вплине на наш клас. -Правило №2: Бери своє .[#toc-rule-2-take-what-s-yours] ------------------------------------------------------- +Правило № 2: бери те, що твоє +----------------------------- -Не вводьте себе в оману і не дозволяйте собі проходити повз залежності ваших залежностей. Пройдіть повз власні залежності. +Не дозволяйте себе заплутати і не дозволяйте передавати залежності ваших залежностей. Нехай вам передають лише ваші залежності. -Завдяки цьому код, що використовує інші об'єкти, буде повністю незалежним від змін в їхніх конструкторах. Його API буде більш правдивим. А головне, замінити ці залежності на інші буде тривіально просто. +Завдяки цьому код, що використовує інші об'єкти, буде повністю незалежним від змін їхніх конструкторів. Його API буде правдивішим. І головне, буде тривіально замінити ці залежності на інші. -Новий член сім'ї .[#toc-new-family-member] ------------------------------------------- +Новий член родини +----------------- -Команда розробників вирішила створити другий логгер, який буде писати до бази даних. Тому ми створюємо клас `DatabaseLogger`. Отже, у нас є два класи, `Logger` і `DatabaseLogger`, один пише в файл, інший в базу даних ... Вам не здається дивним таке іменування? -Чи не краще було б перейменувати `Logger` в `FileLogger`? Однозначно так. +У команді розробників було прийнято рішення створити другий логер, який записує в базу даних. Тож ми створимо клас `DatabaseLogger`. Отже, у нас є два класи, `Logger` і `DatabaseLogger`, один записує у файл, інший у базу даних... вам не здається щось дивним у цій назві? Чи не краще було б перейменувати `Logger` на `FileLogger`? Звісно, так. -Але давайте зробимо це з розумом. Створимо інтерфейс під оригінальною назвою: +Але зробимо це розумно. Під оригінальною назвою створимо інтерфейс: ```php interface Logger @@ -402,7 +400,7 @@ interface Logger } ``` -... яку реалізують обидва логери: +... який обидва логери будуть реалізовувати: ```php class FileLogger implements Logger @@ -412,17 +410,17 @@ class DatabaseLogger implements Logger // ... ``` -І тому не потрібно буде нічого змінювати в решті коду, де використовується логгер. Наприклад, конструктор класу `NewsletterDistributor` як і раніше буде задовольнятися передачею `Logger` в якості параметра. А який саме екземпляр ми передамо, буде залежати тільки від нас. +І завдяки цьому не потрібно буде нічого змінювати в решті коду, де використовується логер. Наприклад, конструктор класу `NewsletterDistributor` все ще буде задоволений тим, що вимагає `Logger` як параметр. І тільки від нас залежатиме, який екземпляр ми йому передамо. -**Ось чому ми ніколи не додаємо суфікс `Interface` або префікс `I` до імен інтерфейсів.** Інакше не було б можливості так гарно розробляти код. +**Тому ми ніколи не додаємо до назв інтерфейсів суфікс `Interface` або префікс `I`.** Інакше неможливо було б так гарно розвивати код. -Х'юстон, у нас проблема .[#toc-houston-we-have-a-problem] ---------------------------------------------------------- +Х'юстоне, у нас проблема +------------------------ -У той час як ми можемо обійтися одним екземпляром логгера, файловим або базованим на базі даних, для всього додатку і просто передавати його туди, де щось реєструється, з класом `Article` все зовсім інакше. Ми створюємо його екземпляри за потребою, навіть декілька разів. Як працювати з залежністю від бази даних в його конструкторі? +Хоча в усьому застосунку ми можемо обійтися єдиним екземпляром логера, чи то файлового, чи то базданного, і просто передавати його скрізь, де щось логується, зовсім інакше у випадку класу `Article`. Адже його екземпляри ми створюємо за потребою, навіть кілька разів. Як впоратися зі зв'язком з базою даних у його конструкторі? -Прикладом може бути контролер, який повинен зберігати статтю в базі даних після відправлення форми: +Як приклад може слугувати контролер, який після надсилання форми має зберегти статтю в базу даних: ```php class EditController extends Controller @@ -437,30 +435,30 @@ class EditController extends Controller } ``` -Можливе рішення очевидне: передайте об'єкт бази даних в конструктор `EditController` і використовуйте `$article = new Article($this->db)`. +Можливе рішення напрошується саме собою: передамо об'єкт бази даних конструктором у `EditController` і використаємо `$article = new Article($this->db)`. -Як і в попередньому випадку з `Logger` і шляхом до файлу, це неправильний підхід. База даних є залежністю не від `EditController`, а від `Article`. Передача бази даних суперечить [правилу №2: |#rule #2: take what's yours] бери те, що належить тобі. Якщо змінюється конструктор класу `Article` (додається новий параметр), вам потрібно буде модифікувати код скрізь, де створюються екземпляри. Уффф. +Так само, як у попередньому випадку з `Logger` та шляхом до файлу, це неправильний підхід. База даних не є залежністю `EditController`, а `Article`. Отже, передача бази даних суперечить [правилу №2: бери те, що твоє |#Правило 2: бери те що твоє]. Коли зміниться конструктор класу `Article` (додасться новий параметр), потрібно буде також змінити код у всіх місцях, де створюються екземпляри. Уфф. -Х'юстон, що ти пропонуєш? +Х'юстоне, що ти пропонуєш? -Правило №3: Нехай завод розбирається з цим .[#toc-rule-3-let-the-factory-handle-it] ------------------------------------------------------------------------------------ +Правило № 3: доручи це фабриці +------------------------------ -Усунувши приховані залежності та передавши всі залежності як аргументи, ми отримали більш гнучкі та конфігуровані класи. А отже, нам потрібно щось інше, щоб створювати та налаштовувати ці більш гнучкі класи для нас. Назвемо це фабриками. +Скасувавши приховані зв'язки та передаючи всі залежності як аргументи, ми отримали більш конфігуровані та гнучкі класи. А отже, нам потрібно ще щось, що створить і налаштує ці гнучкіші класи. Ми будемо називати це фабриками. -Емпіричне правило: якщо клас має залежності, залиште створення їх екземплярів фабриці. +Правило звучить так: якщо клас має залежності, доручи створення їхніх екземплярів фабриці. -Фабрики - розумніша заміна оператору `new` у світі ін'єкцій залежності. +Фабрики - це розумніша заміна оператора `new` у світі dependency injection. .[note] -Будь ласка, не плутайте з паттерном проектування *factory method*, який описує специфічний спосіб використання фабрик і не має відношення до цієї теми. +Будь ласка, не плутайте з патерном проектування *factory method*, який описує специфічний спосіб використання фабрик і не пов'язаний з цією темою. -Фабрика .[#toc-factory] ------------------------ +Фабрика +------- -Фабрика - це метод або клас, який створює та налаштовує об'єкти. Назвемо клас, що створює `Article`, `ArticleFactory`, і він може виглядати так: +Фабрика - це метод або клас, який виробляє та конфігурує об'єкти. Клас, що виробляє `Article`, назвемо `ArticleFactory`, і він може виглядати, наприклад, так: ```php class ArticleFactory @@ -477,7 +475,7 @@ class ArticleFactory } ``` -Його використання в контролері буде наступним: +Його використання в контролері буде таким: ```php class EditController extends Controller @@ -489,7 +487,7 @@ class EditController extends Controller public function formSubmitted($data) { - // дозволити фабриці створити об'єкт + // доручаємо фабриці створити об'єкт $article = $this->articleFactory->create(); $article->title = $data->title; $article->content = $data->content; @@ -498,11 +496,11 @@ class EditController extends Controller } ``` -На даний момент, якщо сигнатура конструктора класу `Article` змінюється, єдиною частиною коду, яка повинна відреагувати на це, є сам `ArticleFactory`. Весь інший код, що працює з об'єктами `Article`, наприклад, `EditController`, не постраждає. +Якщо в цей момент зміниться сигнатура конструктора класу `Article`, єдиною частиною коду, яка повинна на це реагувати, є сама фабрика `ArticleFactory`. Весь інший код, який працює з об'єктами `Article`, наприклад `EditController`, це ніяк не зачепить. -Вам може бути цікаво, чи дійсно ми зробили все краще. Обсяг коду збільшився, і все це починає виглядати підозріло складним. +Можливо, ви зараз стукаєте себе по лобі, чи ми взагалі собі допомогли. Кількість коду зросла, і все це починає виглядати підозріло складно. -Не хвилюйтеся, скоро ми дійдемо до контейнера Nette DI. А він має кілька козирів у рукаві, які значно спростять створення додатків з використанням ін'єкції залежностей. Наприклад, замість класу `ArticleFactory` вам потрібно буде [написати |factory] лише [простий інтерфейс |factory]: +Не хвилюйтеся, незабаром ми дійдемо до DI-контейнера Nette. А він має низку козирів у рукаві, які надзвичайно спрощують створення застосунків, що використовують dependency injection. Наприклад, замість класу `ArticleFactory` достатньо буде [написати лише інтерфейс |factory]: ```php interface ArticleFactory @@ -511,18 +509,18 @@ interface ArticleFactory } ``` -Але ми забігаємо наперед, будь ласка, наберіться терпіння :-) +Але ми забігаємо наперед, зачекайте ще :-) -Резюме .[#toc-summary] ----------------------- +Підсумок +-------- -На початку цієї глави ми обіцяли показати вам процес проектування чистого коду. Все, що для цього потрібно, це щоб класи: +На початку цього розділу ми обіцяли показати вам, як проектувати чистий код. Достатньо класам -- [передавати залежності, які їм потрібні |#Rule #1: Let It Be Passed to You] -- [і навпаки, не передавати те, що їм безпосередньо не потрібно |#Rule #2: Take What's Yours] -- [і що об'єкти з залежностями краще створювати на фабриках |#Rule #3: Let the Factory Handle it] +1) [передавати залежності, які їм потрібні |#Правило 1: нехай тобі це передадуть] +2) [і навпаки, не передавати те, що їм безпосередньо не потрібно |#Правило 2: бери те що твоє] +3) [і що об'єкти із залежностями найкраще створювати у фабриках |#Правило 3: доручи це фабриці] -На перший погляд може здатися, що ці три правила не мають далекосяжних наслідків, але вони призводять до радикально іншого погляду на дизайн коду. Чи варто воно того? Розробники, які відмовилися від старих звичок і почали послідовно використовувати ін'єкцію залежностей, вважають цей крок переломним моментом у своєму професійному житті. Він відкрив для них світ зрозумілих і зручних для супроводу додатків. +На перший погляд це може здатися не так, але ці три правила мають далекосяжні наслідки. Вони ведуть до радикально іншого погляду на проектування коду. Чи варте воно того? Програмісти, які відкинули старі звички і почали послідовно використовувати dependency injection, вважають цей крок ключовим моментом у професійному житті. Їм відкрився світ зрозумілих та підтримуваних застосунків. -Але що, якщо код не використовує ін'єкцію залежностей послідовно? Що, якщо він покладається на статичні методи або синглетони? Чи виникають через це проблеми? [Так, створює, і дуже |global-state] серйозні. +А що, якщо код послідовно не використовує dependency injection? Що, якщо він побудований на статичних методах або синглтонах? Чи спричиняє це якісь проблеми? [Спричиняє, і дуже серйозні |global-state]. diff --git a/dependency-injection/uk/nette-container.texy b/dependency-injection/uk/nette-container.texy index 9947621a62..6959fd7129 100644 --- a/dependency-injection/uk/nette-container.texy +++ b/dependency-injection/uk/nette-container.texy @@ -1,10 +1,10 @@ -Nette DI-контейнер +Nette DI Container ****************** .[perex] -Nette DI Container - одна з найцікавіших частин фреймворку. Він може генерувати скомпільовані DI-контейнери, які надзвичайно швидкі та напрочуд прості в налаштуванні. +Nette DI - одна з найцікавіших бібліотек Nette. Вона вміє генерувати та автоматично оновлювати скомпільовані DI-контейнери, які є надзвичайно швидкими та дивовижно легко конфігуруються. -Nette DI - це бібліотека, яка надає інструменти для генерації, а також автоматичного оновлення класів контейнерів. Ми інструктуємо його (зазвичай) за допомогою конфігураційних файлів. Контейнер, який ми створили вручну в [попередньому розділі |container], буде записаний у конфігураційному [NEON |neon:format] форматі наступним чином: +Вигляд сервісів, які має створювати DI-контейнер, ми зазвичай визначаємо за допомогою конфігураційних файлів у [форматі NEON |neon:format]. Контейнер, який ми вручну створили в [попередньому розділі |container], записується так: ```neon parameters: @@ -19,16 +19,15 @@ services: - UserController ``` -Нотація дійсно коротка. +Запис дійсно короткий. -Усі залежності, оголошені в конструкторах `ArticleFactory` і `UserController`, автоматично визначаються і передаються Nette DI завдяки так званому [автозв'язуванню |autowiring]. Таким чином, ви можете зосередитися на розробці. -Тож навіть якщо параметри зміняться, вам не потрібно нічого змінювати в конфігурації. Nette автоматично регенерує контейнер. Ви можете зосередитися виключно на розробці програми. +Усі залежності, оголошені в конструкторах класів `ArticleFactory` та `UserController`, Nette DI самостійно виявляє та передає завдяки так званому [autowiring |autowiring], тому в конфігураційному файлі нічого вказувати не потрібно. Тож навіть якщо параметри зміняться, вам не потрібно нічого змінювати в конфігурації. Контейнер Nette автоматично перегенерується. Ви можете зосередитися виключно на розробці застосунку. -Якщо ви хочете передавати залежності за допомогою сетера, ми можемо додати секцію [setup |services#Setup] у визначення сервісу. +Якщо ми хочемо передавати залежності за допомогою сеттерів, ми використовуємо для цього секцію [setup |services#Setup]. -Nette DI буде фактично генерувати PHP-код для контейнера. Тому він надзвичайно швидкий, програміст точно знає, що він робить, і навіть може переступати через нього. Для великих додатків контейнер може складатися з десятків тисяч рядків, і підтримувати щось подібне вручну, імовірно, вже неможливо. +Nette DI генерує безпосередньо PHP-код контейнера. Результатом є файл `.php`, який ви можете відкрити та вивчити. Завдяки цьому ви точно бачите, як працює контейнер. Ви також можете налагоджувати його в IDE та виконувати покроково. І головне: згенерований PHP надзвичайно швидкий. -Nette DI також може генерувати код [фабрики |factory] на основі інтерфейсу. Таким чином, замість класу `ArticleFactory` вам потрібно буде просто створити інтерфейс: +Nette DI також вміє генерувати код [фабрик |factory] на основі наданого інтерфейсу. Тому замість класу `ArticleFactory` нам достатньо буде створити в застосунку лише інтерфейс: ```php interface ArticleFactory @@ -37,46 +36,45 @@ interface ArticleFactory } ``` -Ви можете знайти повний приклад [на GitHub |https://github.com/nette-examples/di-example-doc]. +Повний приклад ви знайдете [на GitHub |https://github.com/nette-examples/di-example-doc]. -Використання без фреймворка .[#toc-standalone-use] --------------------------------------------------- +Самостійне використання +----------------------- -Використовувати Nette DI в додатку дуже просто. Спочатку ми встановимо його за допомогою Composer (тому що завантаження zip-файлів вже застаріло): +Впровадження бібліотеки Nette DI в застосунок дуже просте. Спочатку встановимо її за допомогою Composer (бо завантаження zip-архівів тааак застаріло): ```shell composer require nette/di ``` -Ми збережемо наведену вище конфігурацію у файлі `config.neon` і створимо контейнер, використовуючи клас `Nette\DI\ContainerLoader`: +Наступний код створить екземпляр DI-контейнера відповідно до конфігурації, збереженої у файлі `config.neon`: ```php $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp'); -$class = $loader->load(function($compiler) { +$class = $loader->load(function ($compiler) { $compiler->loadConfig(__DIR__ . '/config.neon'); }); $container = new $class; ``` -Контейнер створюється тільки один раз, його код записується в кеш (каталог `__DIR__ . '/temp'`) і під час наступних запитів зчитується тільки звідти. +Контейнер генерується лише один раз, його код записується в кеш (каталог `__DIR__ . '/temp'`), а при наступних запитах він просто завантажується звідти. -Методи `getService()` або `getByType()` використовуються для створення та отримання сервісів. Таким чином ми створюємо об'єкт `UserController`: +Для створення та отримання сервісів служать методи `getService()` або `getByType()`. Таким чином ми створимо об'єкт `UserController`: ```php -$database = $container->getByType(UserController::class); -$database->query('...'); +$controller = $container->getByType(UserController::class); +$controller->someMethod(); ``` -Під час розроблення корисно ввімкнути режим автооновлення, за якого контейнер автоматично оновлюється при зміні будь-якого класу або файлу конфігурації. Просто вкажіть `true` як другий аргумент у конструкторі `ContainerLoader`. +Під час розробки корисно активувати режим автоматичного оновлення, коли контейнер автоматично перегенерується, якщо зміниться будь-який клас або конфігураційний файл. Достатньо в конструкторі `ContainerLoader` вказати `true` як другий аргумент. ```php $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp', true); ``` -Використання з Nette Framework .[#toc-using-it-with-the-nette-framework] ------------------------------------------------------------------------- +Використання з фреймворком Nette +-------------------------------- -Як ми показали, використання Nette DI не обмежується додатками, написаними на Nette Framework, ви можете розгорнути його де завгодно, використовуючи всього 3 рядки коду. -Якщо ви розробляєте додатки в Nette, конфігурування і створення контейнера здійснюється за допомогою класу [Bootstrap |application:bootstrap#toc-di-container-configuration]. +Як ми показали, використання Nette DI не обмежується застосунками, написаними на Nette Framework, ви можете впровадити його де завгодно за допомогою лише 3 рядків коду. Однак, якщо ви розробляєте застосунки на Nette Framework, конфігурацією та створенням контейнера займається [Bootstrap |application:bootstrapping#Конфігурація DI-контейнера]. diff --git a/dependency-injection/uk/passing-dependencies.texy b/dependency-injection/uk/passing-dependencies.texy index 5badf00eb0..6601427616 100644 --- a/dependency-injection/uk/passing-dependencies.texy +++ b/dependency-injection/uk/passing-dependencies.texy @@ -3,22 +3,22 @@ <div class=perex> -Аргументи, або "залежності" в термінології DI, можуть бути передані класам такими основними способами: +Аргументи, або в термінології DI «залежності», можна передавати в класи такими основними способами: -* передача за допомогою конструктора -* передача методом (називається setter) -* шляхом встановлення властивості -* методом, анотацією або атрибутом *inject* +* передача конструктором +* передача методом (так званим сеттером) +* встановленням змінної +* методом, анотацією чи атрибутом *inject* </div> -Зараз ми проілюструємо різні варіанти на конкретних прикладах. +Тепер розглянемо кожен варіант на конкретних прикладах. -Впровадження через конструктор .[#toc-constructor-injection] -============================================================ +Передача конструктором +====================== -Залежності передаються як аргументи конструктору під час створення об'єкта: +Залежності передаються в момент створення об'єкта як аргументи конструктора: ```php class MyClass @@ -34,9 +34,9 @@ class MyClass $obj = new MyClass($cache); ``` -Ця форма корисна для обов'язкових залежностей, які абсолютно необхідні класу для функціонування, оскільки без них екземпляр не може бути створений. +Ця форма підходить для обов'язкових залежностей, які клас неодмінно потребує для своєї роботи, оскільки без них неможливо створити екземпляр. -Починаючи з версії PHP 8.0, ми можемо використовувати коротшу форму позначення ([constructor property promotion |https://blog.nette.org/uk/php-8-0-povnij-oglyad-novin#toc-constructor-property-promotion]), яка функціонально еквівалентна: +Починаючи з PHP 8.0, ми можемо використовувати коротшу форму запису ([constructor property promotion |https://blog.nette.org/uk/php-8-0-complete-overview-of-news#toc-constructor-property-promotion]), яка функціонально еквівалентна: ```php // PHP 8.0 @@ -49,7 +49,7 @@ class MyClass } ``` -Починаючи з версії PHP 8.1, властивість може бути позначена прапором `readonly`, який оголошує, що вміст властивості не буде змінюватися: +Починаючи з PHP 8.1, змінну можна позначити прапорцем `readonly`, який оголошує, що вміст змінної більше не зміниться: ```php // PHP 8.1 @@ -62,13 +62,13 @@ class MyClass } ``` -DI контейнер передає залежності в конструктор автоматично, використовуючи [autowiring |autowiring]. Аргументи, які не можна передавати таким чином (наприклад, рядки, числа, булеви) [записати в конфігурації |services#Arguments]. +DI-контейнер автоматично передає залежності конструктору за допомогою [autowiring |autowiring]. Аргументи, які неможливо передати таким чином (наприклад, рядки, числа, логічні значення), [ми записуємо в конфігурації |services#Аргументи]. -Пекло конструктора .[#toc-constructor-hell] -------------------------------------------- +Пекло конструкторів +------------------- -Термін *пекло конструктора* стосується ситуації, коли дочірній клас успадковує від батьківського класу, конструктор якого потребує залежностей, і дочірній клас також потребує залежностей. Він також повинен перейняти і передати залежності батьківського класу: +Термін *constructor hell* (пекло конструкторів) позначає ситуацію, коли нащадок успадковує від батьківського класу, конструктор якого вимагає залежностей, і водночас нащадок також вимагає залежностей. При цьому він повинен прийняти та передати також батьківські: ```php abstract class BaseClass @@ -85,7 +85,7 @@ final class MyClass extends BaseClass { private Database $db; - // ⛔ CONSTRUCTOR HELL + // ⛔ ПЕКЛО КОНСТРУКТОРІВ public function __construct(Cache $cache, Database $db) { parent::__construct($cache); @@ -94,11 +94,11 @@ final class MyClass extends BaseClass } ``` -Проблема виникає, коли ми хочемо змінити конструктор класу `BaseClass`, наприклад, коли додається нова залежність. Тоді нам доведеться модифікувати всі конструктори дочірніх класів. Що перетворює таку модифікацію на справжнє пекло. +Проблема виникає в той момент, коли ми хочемо змінити конструктор класу `BaseClass`, наприклад, коли додається нова залежність. Тоді необхідно також змінити всі конструктори нащадків. Що перетворює таку модифікацію на пекло. -Як цього уникнути? Рішення полягає в тому, щоб **приоритизувати композицію над успадкуванням**. +Як цьому запобігти? Рішенням є **надавати перевагу [композиції над успадкуванням |faq#Чому композиції надається перевага перед успадкуванням]**. -Отже, давайте проектувати код по-іншому. Уникаймо абстрактних класів `Base*`. Замість того, щоб `MyClass` отримував певну функціональність шляхом успадкування від `BaseClass`, він отримає цю функціональність як залежність: +Отже, ми спроектуємо код інакше. Ми будемо уникати [абстрактних |nette:introduction-to-object-oriented-programming#Абстрактні класи] `Base*` класів. Замість того, щоб `MyClass` отримував певну функціональність шляхом успадкування від `BaseClass`, він отримає цю функціональність як залежність: ```php final class SomeFunctionality @@ -125,10 +125,10 @@ final class MyClass ``` -Впровадження через сеттери .[#toc-setter-injection] -=================================================== +Передача сеттером +================= -Залежності передаються шляхом виклику методу, який зберігає їх у приватній властивості. Звичайна конвенція іменування цих методів має вигляд `set*()`, тому їх називають сеттерами, але, звісно, їх можна називати як завгодно. +Залежності передаються викликом методу, який зберігає їх у приватній змінній. Звичайною конвенцією іменування цих методів є форма `set*()`, тому їх називають сеттерами, але вони, звісно, можуть називатися будь-як інакше. ```php class MyClass @@ -145,9 +145,9 @@ $obj = new MyClass; $obj->setCache($cache); ``` -Цей метод корисний для необов'язкових залежностей, які не потрібні для функціонування класу, оскільки не гарантується, що об'єкт дійсно отримає їх (тобто що користувач викличе метод). +Цей спосіб підходить для необов'язкових залежностей, які не є необхідними для роботи класу, оскільки не гарантується, що об'єкт дійсно отримає залежність (тобто, що користувач викличе метод). -Водночас, цей метод дозволяє неодноразово викликати сеттер для зміни залежності. Якщо це небажано, додайте перевірку в метод, або, починаючи з PHP 8.1, позначте властивість `$cache` прапором `readonly`. +Водночас цей спосіб дозволяє викликати сеттер повторно і таким чином змінювати залежність. Якщо це небажано, ми додамо перевірку в метод, або, починаючи з PHP 8.1, позначимо властивість `$cache` прапорцем `readonly`. ```php class MyClass @@ -156,29 +156,28 @@ class MyClass public function setCache(Cache $cache): void { - if ($this->cache) { - throw new RuntimeException('The dependency has already been set'); + if (isset($this->cache)) { + throw new RuntimeException('Залежність вже встановлена'); } $this->cache = $cache; } } ``` -Виклик сеттера визначається в конфігурації контейнера DI в [розділі setup |services#Setup]. Також тут автоматична передача залежностей використовується autowiring: +Виклик сеттера ми визначаємо в конфігурації DI-контейнера в [ключі setup |services#Setup]. Тут також використовується автоматична передача залежностей за допомогою autowiring: ```neon services: - - - create: MyClass + - create: MyClass setup: - setCache ``` -Впровадження через властивості .[#toc-property-injection] -========================================================= +Встановленням змінної +===================== -Залежності передаються безпосередньо у властивість: +Залежності передаються записом безпосередньо в змінну-член: ```php class MyClass @@ -190,28 +189,27 @@ $obj = new MyClass; $obj->cache = $cache; ``` -Цей метод вважається неприйнятним, оскільки властивість має бути оголошена як `public`. Отже, ми не можемо контролювати, чи буде передана залежність справді мати вказаний тип (це було вірно до версії PHP 7.4), і ми втрачаємо можливість реагувати на нову призначену залежність своїм власним кодом, наприклад, щоб запобігти подальшим змінам. Водночас, властивість стає частиною публічного інтерфейсу класу, що може бути небажано. +Цей спосіб вважається недоречним, оскільки змінна-член повинна бути оголошена як `public`. А отже, ми не маємо контролю над тим, що передана залежність буде дійсно зазначеного типу (це було актуально до PHP 7.4), і втрачаємо можливість реагувати на новопризначену залежність власним кодом, наприклад, запобігти подальшій зміні. Водночас змінна стає частиною публічного інтерфейсу класу, що може бути небажаним. -Налаштування змінної визначається в конфігурації контейнера DI в розділі [section setup |services#Setup]: +Встановлення змінної ми визначаємо в конфігурації DI-контейнера в [секції setup |services#Setup]: ```neon services: - - - create: MyClass + - create: MyClass setup: - $cache = @\Cache ``` -Ін'єкція .[#toc-inject] -======================= +Inject +====== -У той час як попередні три методи загалом працюють у всіх об'єктно-орієнтованих мовах, ін'єкція за методом, анотацією або атрибутом *inject* є специфічною для презентаторів Nette. Вони обговорюються в [окремій главі |best-practices:inject-method-attribute]. +Хоча попередні три способи застосовуються загалом у всіх об'єктно-орієнтованих мовах, ін'єкція методом, анотацією чи атрибутом *inject* є специфічною виключно для презентерів у Nette. Про них йдеться в [окремому розділі |best-practices:inject-method-attribute]. -Який шлях обрати? .[#toc-which-way-to-choose] -============================================= +Який спосіб обрати? +=================== -- конструктор підходить для обов'язкових залежностей, які необхідні класу для функціонування -- сеттер, з іншого боку, підходить для необов'язкових залежностей, або залежностей, які можуть бути змінені -- публічні змінні не рекомендуються +- конструктор підходить для обов'язкових залежностей, які клас неодмінно потребує для своєї роботи +- сеттер, навпаки, підходить для необов'язкових залежностей або залежностей, які можна буде змінювати надалі +- публічні змінні не підходять diff --git a/dependency-injection/uk/services.texy b/dependency-injection/uk/services.texy index 10bb015505..1db05bb9d8 100644 --- a/dependency-injection/uk/services.texy +++ b/dependency-injection/uk/services.texy @@ -2,32 +2,32 @@ ******************* .[perex] -Конфігурація - це місце, де ми розміщуємо визначення користувацьких сервісів. Робиться це в секції `services`. +Конфігурація - це місце, де ми навчаємо DI-контейнер, як створювати окремі сервіси та як пов'язувати їх з іншими залежностями. Nette надає дуже зрозумілий та елегантний спосіб досягти цього. -Наприклад, ось як ми створюємо сервіс з іменем `database`, який буде екземпляром класу `PDO`: +Секція `services` у конфігураційному файлі формату NEON - це місце, де ми визначаємо власні сервіси та їхню конфігурацію. Розглянемо простий приклад визначення сервісу під назвою `database`, який представляє екземпляр класу `PDO`: ```neon services: database: PDO('sqlite::memory:') ``` -Іменування служб використовується для того, щоб ми могли [посилатися |#Referencing-Services] на них. Якщо на сервіс не посилаються, немає необхідності давати йому ім'я. Тому замість імені ми просто використовуємо двокрапку: +Наведена конфігурація призведе до створення наступного фабричного методу в [DI-контейнері |container]: -```neon -services: - - PDO('sqlite::memory:') # анонімний сервіс +```php +public function createServiceDatabase(): PDO +{ + return new PDO('sqlite::memory:'); +} ``` -Однорядковий запис може бути розбитий на кілька рядків, щоб можна було додати додаткові ключі, наприклад [setup |#setup]. Псевдонім для ключа `create:` - `factory:`. +Назви сервісів дозволяють нам посилатися на них в інших частинах конфігураційного файлу у форматі `@назваСервісу`. Якщо немає потреби називати сервіс, ми можемо просто використати маркер списку: ```neon services: - database: - create: PDO('sqlite::memory:') - setup: ... + - PDO('sqlite::memory:') ``` -Потім ми отримуємо сервіс із контейнера DI, використовуючи метод `getService()` за ім'ям, або, що ще краще, метод `getByType()` за типом: +Для отримання сервісу з DI-контейнера ми можемо використати метод `getService()` з назвою сервісу як параметром, або метод `getByType()` з типом сервісу: ```php $database = $container->getService('database'); @@ -35,26 +35,28 @@ $database = $container->getByType(PDO::class); ``` -Створення сервісу .[#toc-creating-a-service] -============================================ +Створення сервісу +================= -Найчастіше ми створюємо сервіс, просто створюючи екземпляр класу: +Зазвичай ми створюємо сервіс просто шляхом створення екземпляра певного класу. Наприклад: ```neon services: database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -Що створить фабричний метод у [DI-контейнері |container]: +Якщо нам потрібно розширити конфігурацію додатковими ключами, визначення можна розписати на кілька рядків: -```php -public function createServiceDatabase(): PDO -{ - return new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'secret'); -} +```neon +services: + database: + create: PDO('sqlite::memory:') + setup: ... ``` -Альтернативно, ключ `arguments` може бути використаний для передачі [аргументів |#Arguments]: +Ключ `create` має псевдонім `factory`, обидва варіанти поширені на практиці. Однак ми рекомендуємо використовувати `create`. + +Аргументи конструктора або методу створення можуть бути альтернативно записані в ключі `arguments`: ```neon services: @@ -63,294 +65,303 @@ services: arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret] ``` -Статичний метод також може створити сервіс: +Сервіси не обов'язково створюються лише простим створенням екземпляра класу, вони також можуть бути результатом виклику статичних методів або методів інших сервісів: ```neon services: - database: My\Database::create(root, secret) + database: DatabaseFactory::create() + router: @routerFactory::create() ``` -Це еквівалентно коду PHP: +Зверніть увагу, що для простоти замість `->` використовується `::`, див. [##виразні засоби]. Будуть згенеровані такі фабричні методи: ```php public function createServiceDatabase(): PDO { - return My\Database::create('root', 'secret'); + return DatabaseFactory::create(); +} + +public function createServiceRouter(): RouteList +{ + return $this->getService('routerFactory')->create(); } ``` -Передбачається, що статичний метод `My\Database::create()` має певне значення, що повертається, яке повинен знати контейнер DI. Якщо у нього його немає, ми записуємо тип у конфігурацію: +DI-контейнеру потрібно знати тип створеного сервісу. Якщо ми створюємо сервіс за допомогою методу, який не має вказаного типу повернення, ми повинні явно вказати цей тип у конфігурації: ```neon services: database: - create: My\Database::create(root, secret) + create: DatabaseFactory::create() type: PDO ``` -Nette DI надає вам надзвичайно потужні засоби вираження, що дозволяють написати практично все, що завгодно. Наприклад, щоб [звернутися |#Referencing-Services] до іншої служби і викликати її метод. Для простоти замість `->` використовується `::`. + +Аргументи +========= + +Ми передаємо аргументи в конструктор та методи способом, дуже схожим на сам PHP: ```neon services: - routerFactory: App\Router\Factory - router: @routerFactory::create() + database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret) ``` -Це еквівалентно коду PHP: +Для кращої читабельності ми можемо розписати аргументи на окремі рядки. У такому випадку використання ком є необов'язковим: -```php -public function createServiceRouterFactory(): App\Router\Factory -{ - return new App\Router\Factory; -} - -public function createServiceRouter(): Router -{ - return $this->getService('routerFactory')->create(); -} +```neon +services: + database: PDO( + 'mysql:host=127.0.0.1;dbname=test' + root + secret + ) ``` -Виклики методів можна об'єднувати в ланцюжки, як у PHP: +Ви також можете назвати аргументи і тоді не турбуватися про їхній порядок: ```neon services: - foo: FooFactory::build()::get() + database: PDO( + username: root + password: secret + dsn: 'mysql:host=127.0.0.1;dbname=test' + ) ``` -Це еквівалентно коду PHP: +Якщо ви хочете пропустити деякі аргументи та використати їхнє значення за замовчуванням або підставити сервіс за допомогою [autowiring |autowiring], використовуйте підкреслення: -```php -public function createServiceFoo() -{ - return FooFactory::build()->get(); -} +```neon +services: + foo: Foo(_, %appDir%) ``` +Як аргументи можна передавати сервіси, використовувати параметри та багато іншого, див. [##виразні засоби]. + -Аргументи .[#toc-arguments] -=========================== +Setup +===== -Іменовані параметри також можуть використовуватися для передачі аргументів: +У секції `setup` ми визначаємо методи, які мають викликатися при створенні сервісу. ```neon services: - database: PDO( - 'mysql:host=127.0.0.1;dbname=test' # позиційний - username: root # іменований - password: secret # іменований - ) + database: + create: PDO(%dsn%, %user%, %password%) + setup: + - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) ``` -Використання ком необов'язкове при розбитті аргументів на кілька рядків. +У PHP це виглядало б так: + +```php +public function createServiceDatabase(): PDO +{ + $service = new PDO('...', '...', '...'); + $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + return $service; +} +``` -Звичайно, ми також можемо використовувати [інші сервіси |#Referencing-Services] або [параметри |configuration#Parameters] як аргументи: +Крім виклику методів, можна також передавати значення у властивості. Також підтримується додавання елемента до масиву, що потрібно записувати в лапках, щоб не конфліктувати з синтаксисом NEON: ```neon services: - - Foo(@anotherService, %appDir%) + foo: + create: Foo + setup: + - $value = 123 + - '$onClick[]' = [@bar, clickHandler] ``` -Відповідає коду PHP: +Що в PHP-коді виглядало б наступним чином: ```php -public function createService01(): Foo +public function createServiceFoo(): Foo { - return new Foo($this->getService('anotherService'), '...'); + $service = new Foo; + $service->value = 123; + $service->onClick[] = [$this->getService('bar'), 'clickHandler']; + return $service; } ``` -Якщо перший аргумент - [автомонтований |autowiring], а ви хочете вказати другий, опустіть перший за допомогою символу `_`, например `Foo(_, %appDir%)`. Або, що ще краще, передавайте тільки другий аргумент як іменований параметр, наприклад: `Foo(path: %appDir%)`. - -Nette DI і формат NEON дають вам надзвичайно потужні виразні засоби, що дозволяють написати практично все, що завгодно. Таким чином, аргументом може бути новостворений об'єкт, ви можете викликати статичні методи, методи інших сервісів або навіть глобальні функції, використовуючи спеціальну нотацію: +Однак у setup можна викликати також статичні методи або методи інших сервісів. Якщо вам потрібно передати поточний сервіс як аргумент, вкажіть його як `@self`: ```neon services: - analyser: My\Analyser( - FilesystemIterator(%appDir%) # створюємо об'єкт - DateTime::createFromFormat('Y-m-d') # викликаємо статичний метод - @anotherService # передаємо інший сервіс - @http.request::getRemoteAddress() # викликаємо метод іншого сервісу - ::getenv(NetteMode) # викликаємо глобальну функцію - ) + foo: + create: Foo + setup: + - My\Helpers::initializeFoo(@self) + - @anotherService::setFoo(@self) ``` -Відповідає коду PHP: +Зверніть увагу, що для простоти замість `->` використовується `::`, див. [##виразні засоби]. Буде згенеровано такий фабричний метод: ```php -public function createServiceAnalyser(): My\Analyser +public function createServiceFoo(): Foo { - return new My\Analyser( - new FilesystemIterator('...'), - DateTime::createFromFormat('Y-m-d'), - $this->getService('anotherService'), - $this->getService('http.request')->getRemoteAddress(), - getenv('NetteMode') - ); + $service = new Foo; + My\Helpers::initializeFoo($service); + $this->getService('anotherService')->setFoo($service); + return $service; } ``` -Спеціальні функції .[#toc-special-functions] --------------------------------------------- - -Ви також можете використовувати спеціальні функції в аргументах для приведення або заперечення значень: +Виразні засоби +============== -- `not(%arg%)` заперечення -- `bool(%arg%)` приведення до bool без втрат -- `int(%arg%)` приведення до int без втрат -- `float(%arg%)` приведення до плаваючого стану без втрат -- `string(%arg%)` приведення до рядка без втрат +Nette DI надає нам надзвичайно багаті виразні засоби, за допомогою яких ми можемо записати майже будь-що. Таким чином, у конфігураційних файлах ми можемо використовувати [параметри |configuration#Параметри]: ```neon -services: - - Foo( - id: int(::getenv('ProjectId')) - productionMode: not(%debugMode%) - ) -``` - -Переписування без втрат відрізняється від звичайного переписування в PHP, наприклад використовуючи `(int)`, у тому, що воно викидає виняток для нечислових значень. +# параметр +%wwwDir% -Як аргументи можна передавати кілька сервісів. Масив усіх сервісів певного типу (тобто класу або інтерфейсу) створюється функцією `typed()`. Функція буде опускати сервіси, у яких відключено автопідключення, при цьому можна вказати кілька типів, розділених комою. +# значення параметра за ключем +%mailer.user% -```neon -services: - - BarsDependent( typed(Bar) ) +# параметр всередині рядка +'%wwwDir%/images' ``` -Ви також можете передати масив сервісів автоматично, використовуючи [автозв'язування |autowiring#Collection-of-Services]. - -Масив усіх сервісів із певним [тегом |#Tags] створюється функцією `tagged()`. Можна вказати кілька тегів, розділених комою. +Далі створювати об'єкти, викликати методи та функції: ```neon -services: - - LoggersDependent( tagged(logger) ) -``` +# створення об'єкта +DateTime() +# виклик статичного методу +Collator::create(%locale%) -Посилання на сервіси .[#toc-referencing-services] -================================================= +# виклик функції PHP +::getenv(DB_USER) +``` -Посилання на окремі сервіси використовуються за допомогою символу `@` и имени, например `@database`: +Посилатися на сервіси або за їхньою назвою, або за типом: ```neon -services: - - create: Foo(@database) - setup: - - setCacheStorage(@cache.storage) +# сервіс за назвою +@database + +# сервіс за типом +@Nette\Database\Connection ``` -Відповідає коду PHP: +Використовувати синтаксис first-class callable: .{data-version:3.2.0} -```php -public function createService01(): Foo -{ - $service = new Foo($this->getService('database')); - $service->setCacheStorage($this->getService('cache.storage')); - return $service; -} +```neon +# створення callback, аналог [@user, logout] +@user::logout(...) ``` -Навіть на анонімні сервіси можна посилатися за допомогою зворотного виклику, просто вкажіть їхній тип (клас або інтерфейс) замість імені. Однак зазвичай у цьому немає потреби через [автозв'язування |autowiring]. +Використовувати константи: ```neon -services: - - create: Foo(@Nette\Database\Connection) # или @\PDO - setup: - - setCacheStorage(@cache.storage) +# константа класу +FilesystemIterator::SKIP_DOTS + +# глобальну константу отримуємо функцією PHP constant() +::constant(PHP_VERSION) ``` +Виклики методів можна ланцюгувати так само, як у PHP. Лише для простоти замість `->` використовується `::`: -Setup .[#toc-setup] -=================== +```neon +DateTime()::format('Y-m-d') +# PHP: (new DateTime())->format('Y-m-d') -У секції `setup` ми перераховуємо методи, які будуть викликатися під час створення сервісу: +@http.request::getUrl()::getHost() +# PHP: $this->getService('http.request')->getUrl()->getHost() +``` + +Ці вирази можна використовувати будь-де, при [створенні сервісів |#Створення сервісу], в [аргументах |#Аргументи], у секції [#setup] або [параметрах |configuration#Параметри]: ```neon +parameters: + ipAddress: @http.request::getRemoteAddress() + services: database: - create: PDO(%dsn%, %user%, %password%) + create: DatabaseFactory::create( @anotherService::getDsn() ) setup: - - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) + - initialize( ::getenv('DB_USER') ) ``` -Відповідає коду PHP: -```php -public function createServiceDatabase(): PDO -{ - $service = new PDO('...', '...', '...'); - $service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - return $service; -} -``` +Спеціальні функції +------------------ -Також можна встановити властивості. Додавання елемента в масив також підтримується, і його слід писати в лапках, щоб не конфліктувати із синтаксисом NEON: +У конфігураційних файлах ви можете використовувати ці спеціальні функції: +- `not()` заперечення значення +- `bool()`, `int()`, `float()`, `string()` перетворення типу без втрат +- `typed()` створює масив усіх сервісів зазначеного типу +- `tagged()` створює масив усіх сервісів із заданим тегом ```neon services: - foo: - create: Foo - setup: - - $value = 123 - - '$onClick[]' = [@bar, clickHandler] + - Foo( + id: int(::getenv('ProjectId')) + productionMode: not(%debugMode%) + ) ``` -Відповідає коду PHP: +На відміну від класичного перетворення типів у PHP, такого як `(int)`, перетворення без втрат викине виняток для нечислових значень. -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - $service->value = 123; - $service->onClick[] = [$this->getService('bar'), 'clickHandler']; - return $service; -} +Функція `typed()` створює масив усіх сервісів даного типу (класу або інтерфейсу). Вона пропускає сервіси, у яких вимкнено autowiring. Можна вказати кілька типів, розділених комою. + +```neon +services: + - BarsDependent( typed(Bar) ) ``` -Однак статичні методи або методи інших сервісів також можуть бути викликані в налаштуванні. Ми передаємо їм фактичний сервіс як `@self`: +Масив сервісів певного типу ви також можете передавати як аргумент автоматично за допомогою [autowiring |autowiring#Масив сервісів]. +Функція `tagged()` створює масив усіх сервісів з певним тегом. Тут також можна вказати кілька тегів, розділених комою. ```neon services: - foo: - create: Foo - setup: - - My\Helpers::initializeFoo(@self) - - @anotherService::setFoo(@self) + - LoggersDependent( tagged(logger) ) ``` -Відповідає коду PHP: -```php -public function createServiceFoo(): Foo -{ - $service = new Foo; - My\Helpers::initializeFoo($service); - $this->getService('anotherService')->setFoo($service); - return $service; -} +Autowiring +========== + +Ключ `autowired` дозволяє впливати на поведінку autowiring для конкретного сервісу. Детальніше див. [розділ про autowiring |autowiring]. + +```neon +services: + foo: + create: Foo + autowired: false # сервіс foo виключено з autowiring ``` -Автозв'язування .[#toc-autowiring] +Lazy-сервіси .{data-version:3.2.4} ================================== -Ключ `autowired` можна використовувати для виключення сервісу з автозв'язування або для впливу на його поведінку. Детальнішу інформацію див. у розділі [Автозв'язування |autowiring]. +Lazy loading (ліниве завантаження) - це техніка, яка відкладає створення сервісу до моменту, коли він дійсно потрібен. У глобальній конфігурації можна [увімкнути ліниве створення |configuration#Lazy-сервіси] для всіх сервісів одночасно. Для окремих сервісів ви можете змінити цю поведінку: ```neon services: foo: create: Foo - autowired: false # foo видаляється з автозв'язування + lazy: false ``` +Коли сервіс визначено як lazy, при його запиті з DI-контейнера ми отримуємо спеціальний об'єкт-заступник. Він виглядає і поводиться так само, як реальний сервіс, але фактична ініціалізація (виклик конструктора та setup) відбувається лише при першому виклику будь-якого його методу або властивості. -Теги .[#toc-tags] -================= +.[note] +Lazy loading можна використовувати лише для користувацьких класів, а не для внутрішніх класів PHP. Потребує PHP 8.4 або новішої версії. + + +Теги +==== -Інформація про користувача може бути додана до окремих сервісів у вигляді тегів: +Теги служать для додавання додаткової інформації до сервісів. До сервісу можна додати один або кілька тегів: ```neon services: @@ -360,7 +371,7 @@ services: - cached ``` -Теги також можуть мати значення: +Теги також можуть містити значення: ```neon services: @@ -370,26 +381,26 @@ services: logger: monolog.logger.event ``` -Масив сервісів з певними тегами може бути переданий як аргумент за допомогою функції `tagged()`. Можна також вказати кілька тегів, розділених комою. +Щоб отримати всі сервіси з певними тегами, ви можете використати функцію `tagged()`: ```neon services: - LoggersDependent( tagged(logger) ) ``` -Імена служб можна отримати з контейнера DI за допомогою методу `findByTag()`: +У DI-контейнері ви можете отримати назви всіх сервісів з певним тегом за допомогою методу `findByTag()`: ```php $names = $container->findByTag('logger'); -// $names - масив, що містить ім'я сервісу і значення тега -// наприклад ['foo' => 'monolog.logger.event', ...] +// $names - це масив, що містить назву сервісу та значення тегу +// напр. ['foo' => 'monolog.logger.event', ...] ``` -Режим впровадження .[#toc-inject-mode] -====================================== +Режим Inject +============ -Прапор `inject: true` використовується для активації передавання залежностей через публічні змінні за допомогою анотації [inject |best-practices:inject-method-attribute#Inject Attributes] і методів [inject*() |best-practices:inject-method-attribute#inject Methods]. +За допомогою прапорця `inject: true` активується передача залежностей через публічні змінні з анотацією [inject |best-practices:inject-method-attribute#Атрибути Inject] та методи [inject*() |best-practices:inject-method-attribute#Методи inject]. ```neon services: @@ -398,13 +409,13 @@ services: inject: true ``` -За замовчуванням `inject` активується тільки для презентерів. +За замовчуванням `inject` активовано лише для презентерів. -Модифікація сервісів .[#toc-modification-of-services] -===================================================== +Модифікація сервісів +==================== -У контейнері DI є низка сервісів, які були додані вбудованим або [вашим розширенням |#di-extension]. Визначення цих сервісів можуть бути змінені в конфігурації. Наприклад, для сервісу `application.application`, який за замовчуванням є об'єктом `Nette\Application\Application`, ми можемо змінити клас: +DI-контейнер містить багато сервісів, які були додані за допомогою вбудованого або [користувацького розширення |extensions]. Ви можете змінювати визначення цих сервісів безпосередньо в конфігурації. Наприклад, ви можете змінити клас сервісу `application.application`, який за замовчуванням є `Nette\Application\Application`, на інший: ```neon services: @@ -413,9 +424,9 @@ services: alteration: true ``` -Прапор `alteration` є інформативним і говорить про те, що ми просто змінюємо існуючий сервіс. +Прапорець `alteration` є інформативним і вказує, що ми лише модифікуємо існуючий сервіс. -Ми також можемо додати `setup`: +Ми також можемо доповнити setup: ```neon services: @@ -426,7 +437,7 @@ services: - '$onStartup[]' = [@resource, init] ``` -Під час перезапису сервісу ми можемо видалити вихідні аргументи, елементи `setup` або теги, для яких `reset` є: +При переписуванні сервісу ми можемо захотіти видалити початкові аргументи, елементи setup або теги, для чого служить `reset`: ```neon services: @@ -439,7 +450,7 @@ services: - tags ``` -Сервіс, доданий розширенням, також може бути видалено з контейнера: +Якщо ви хочете видалити сервіс, доданий розширенням, ви можете зробити це так: ```neon services: diff --git a/dibi/cs/@home.texy b/dibi/cs/@home.texy new file mode 100644 index 0000000000..81a8da8847 --- /dev/null +++ b/dibi/cs/@home.texy @@ -0,0 +1,658 @@ +Dibi: Šikovná Database Abstraction Library pro PHP +************************************************** + +Nejnovější stabilní verzi Dibi instalujte pomocí [Composer|best-practices:composer] příkazem: + +``` +composer require dibi/dibi +``` + +Přehled verzí najdete na stánce [Releases | https://github.com/dg/dibi/releases]. + +Vyžaduje PHP 8.0 nebo vyšší. + + +Připojení k databázi +==================== + +Databázové spojení je reprezentováno objektem [Dibi\Connection|api:]: + +```php +$database = new Dibi\Connection([ + 'driver' => 'mysqli', + 'host' => 'localhost', + 'username' => 'root', + 'password' => '***', + 'database' => 'table', +]); + +$result = $database->query('SELECT * FROM users'); +``` + +Alternativně můžete používat statický registr `dibi`, který udržuje v globálně dostupném úložišti objekt spojení a nad ním volá všechny funkce: + +```php +dibi::connect([ + 'driver' => 'mysqli', + 'host' => 'localhost', + 'username' => 'root', + 'password' => '***', + 'database' => 'test', + 'charset' => 'utf8', +]); + +$result = dibi::query('SELECT * FROM users'); +``` + +V případě chyby připojení se vyhodí `Dibi\Exception`. + + +Dotazy +====== + +Databázové dotazy pokládáme metodou `query()`, která vrací [Dibi\Result |api:Dibi\Result]. Řádky jako objekty [Dibi\Row |api:Dibi\Row]. + +Všechny příklady si můžete zkoušet [online na hřišti |https://repl.it/@DavidGrudl/dibi-playground]. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; +} + +// pole všech řádků +$all = $result->fetchAll(); + +// pole všech řádků, klíčem je 'id' +$all = $result->fetchAssoc('id'); + +// asociativní pole id => name +$pairs = $result->fetchPairs('id', 'name'); + +// počet řádků výsledku, pokud je znám, nebo počet ovlivněných řádků +$count = $result->getRowCount(); +``` + +Metoda fetchAssoc() umí vracet i [složitější asociativní pole |#Výsledek jako asociativní pole]. + +Do dotazu lze velmi snadno přidávat i parametry, všimněte si otazníku: + +```php +$result = $database->query('SELECT * FROM users WHERE name = ? AND active = ?', $name, $active); + +// nebo +$result = $database->query('SELECT * FROM users WHERE name = ?', $name, 'AND active = ?', $active);); + +$ids = [10, 20, 30]; +$result = $database->query('SELECT * FROM users WHERE id IN (?)', $ids); +``` + +<div class=warning> +**POZOR, nikdy dotazy neskládejte jako řetězce, vznikla by zranitelnost [SQL injection |https://cs.wikipedia.org/wiki/SQL_injection]** +/-- +$database->query('SELECT * FROM users WHERE id = ' . $id); // ŠPATNĚ!!! +\-- +</div> + +Místo otazníku lze používat i tzv. [#modifikátory]. + +```php +$result = $database->query('SELECT * FROM users WHERE name = %s', $name); +``` + +V případě selhání `query()` vyhodí buď `Dibi\Exception`, nebo některého z potomků: + +- [ConstraintViolationException |api:Dibi\ConstraintViolationException] - porušení nějakého omezení pro tabulku +- [ForeignKeyConstraintViolationException |api:Dibi\ForeignKeyConstraintViolationException] - neplatný cizí klíč +- [NotNullConstraintViolationException |api:Dibi\NotNullConstraintViolationException] - porušení podmínky NOT NULL +- [UniqueConstraintViolationException |api:Dibi\UniqueConstraintViolationException] - koliduje unikátní index + +Dotazy lze pokládat také pomocí zkratek: + +```php +// vrátí asociativní pole id => name, zkratka pro query(...)->fetchPairs() +$pairs = $database->fetchPairs('SELECT id, name FROM users'); + +// vrátí pole všech řádků, zkratka pro query(...)->fetchAll() +$rows = $database->fetchAll('SELECT * FROM users'); + +// vrátí řádek, zkratka pro query(...)->fetch() +$row = $database->fetch('SELECT * FROM users WHERE id = ?', $id); + +// vrátí buňku, zkratka pro query(...)->fetchSingle() +$name = $database->fetchSingle('SELECT name FROM users WHERE id = ?', $id); +``` + + +Modifikátory +============ + +Kromě zástupného symbolu `?` můžeme používat i modifikátory: + +| %s | string +| %sN | string, ale '' se přeloží jako NULL +| %bin | binární data +| %b | boolean +| %i | integer +| %iN | integer, ale 0 se přeloží jako NULL +| %f | float +| %d | datum (očekává DateTime, string nebo UNIX timestamp) +| %dt | datum & čas (očekává DateTime, string nebo UNIX timestamp) +| %n | identifikátor, tedy název tabulky či sloupce +| %N | identifikátor, považuje tečku za běžný znak +| %SQL | SQL - přímo vloží do SQL (alternativou je Dibi\Literal) +| %ex | expanduje pole +| %lmt | speciální - doplní do dotazu LIMIT +| %ofs | speciální - doplní do dotazu OFFSET + +Příklad: + +```php +$result = $database->query('SELECT * FROM users WHERE name = %s', $name); +``` + +Pokud `$name` je `null`, vloží se do SQL příkazu `NULL`. + +Pokud proměnná je pole, tak se modifikátor aplikuje na všechny jeho prvky a ty se vloží do SQL oddělené čárkami: + +```php +$ids = [10, '20', 30]; +$result = $database->query('SELECT * FROM users WHERE id IN (%i)', $ids); +// SELECT * FROM users WHERE id IN (10, 20, 30) +``` + +Modifikátor `%n` využijete v případě, že název tabulky nebo sloupce je proměnnou. (Pozor, nedovolte uživateli manipulovat s obsahem takové proměnné): + +```php +$table = 'blog.users'; +$column = 'name'; +$result = $database->query('SELECT * FROM %n WHERE %n = ?', $table, $column, $value); +// SELECT * FROM `blog`.`users` WHERE `name` = 'Jim' +``` + +Pro operátor LIKE jsou k dispozici čtyři speciální modifikátory: + +| %like~ | výraz začíná řetězcem +| %~like | výraz končí řetězcem +| %~like~ | výraz obsahuje řetězec +| `%like` | výraz je řetězec + +Hledej jména začínající na určitý řetězec: + +```php +$result = $database->query('SELECT * FROM table WHERE name LIKE %like~', $query); +``` + + +Modifikátory polí +================= + +Parameterem vkládaným do SQL dotazu může být i pole. Tyto modifikátory určují, jak z něj sestavit SQL příkaz: + +| %and | | `key1 = value1 AND key2 = value2 AND ...` +| %or | | `key1 = value1 OR key2 = value2 OR ...` +| %a | assoc | `key1 = value1, key2 = value2, ...` +| %l %in | list | `(val1, val2, ...)` +| %v | values | `(key1, key2, ...) VALUES (value1, value2, ...)` +| %m | multi | `(key1, key2, ...) VALUES (value1, value2, ...), (value1, value2, ...), ...` +| %by | řazení | `key1 ASC, key2 DESC ...` +| %n | názvy | `key1, key2 AS alias, ...` + +Příklad: + +```php +$arr = [ + 'a' => 'hello', + 'b' => true, +]; + +$database->query('INSERT INTO table %v', $arr); +// INSERT INTO `table` (`a`, `b`) VALUES ('hello', 1) + +$database->query('UPDATE `table` SET %a', $arr); +// UPDATE `table` SET `a`='hello', `b`=1 +``` + +V klauzuli WHERE lze použít modifikátory `%and` nebo `%or`: + +```php +$result = $database->query('SELECT * FROM users WHERE %and', [ + 'name' => $name, + 'year' => $year, +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND `year` = 1978 +``` + +Viz také [#Složitější dotazy]. + +Modifikátor `%by` slouží k řazení, v klíčích uvedeme sloupce a hodnotou bude boolean určující, zda řadit vzestupně: + +```php +$result = $database->query('SELECT id FROM author ORDER BY %by', [ + 'id' => true, // vzestupně + 'name' => false, // sestupně +]); +// SELECT id FROM author ORDER BY `id`, `name` DESC +``` + + +Insert, Update & Delete +======================= + +Data vkládáme do SQL dotazu jako asociativní pole. Modifikátory ani zástupný znak `?` není nutné v těchto případech uvádět. + +```php +$database->query('INSERT INTO users', [ + 'name' => $name, + 'year' => $year, +]); +// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) + +$id = $database->getInsertId(); // vrátí auto-increment vloženého záznamu + +$id = $database->getInsertId($sequence); // nebo hodnotu sekvence +``` + +Vícenásobný INSERT: + +```php +$database->query( + 'INSERT INTO users', + [ + 'name' => 'Jim', + 'year' => 1978, + ], + [ + 'name' => 'Jack', + 'year' => 1987, + ] +); +// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) +``` + +Mazání: + +```php +$database->query('DELETE FROM users WHERE id = ?', $id); + +// vrací počet smazaných řádků +$affectedRows = $database->getAffectedRows(); +``` + +Úprava záznamů: + +```php +$database->query('UPDATE users SET', [ + 'name' => $name, + 'year' => $year, +], 'WHERE id = ?', $id); +// UPDATE users SET `name` = 'Jim', `year` = 1978 WHERE id = 123 + +// vrací počet změněných řádků +$affectedRows = $database->getAffectedRows(); +``` + +Vložení záznamu, nebo úprava, pokud již existuje: + +```php +$database->query('INSERT INTO users', [ + 'id' => $id, + 'name' => $name, + 'year' => $year, +], 'ON DUPLICATE KEY UPDATE %a', [ // tady už modifikátor %a uvést musíme + 'name' => $name, + 'year' => $year, +]); +// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) +// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 +``` + + +Transakce +========= + +Pro práci s transakcemi slouží čtveřice metod: + +```php +$database->beginTransaction(); // zahájení transakce + +$database->commit(); // potvrzení + +$database->rollback(); // vrácení zpět + +$database->transaction(function () { + // nejaka akce +}); +``` + + +Testování +========= + +Abyste si mohli trošku s Dibi hrát, je tu připravena metoda `test()`, které předáte parametry stejně jako `query()`, ovšem místo provedení SQL příkazu se tento barevně vypíše na obrazovku. + +Výsledky dotazu je možné vypsat jako tabulku pomocí `$result->dump()`. + +K dispozici jsou dále proměnné: + +```php +dibi::$sql; // poslední SQL příklaz +dibi::$elapsedTime; // jeho doba trvání v sec +dibi::$numOfQueries; // celkem SQL příkazů +dibi::$totalTime; // celkový čas v sec +``` + + +Složitější dotazy +================= + +Parametrem může být také objekt `DateTime`. + +```php +$result = $database->query('SELECT * FROM users WHERE created < ?', new DateTime); + +$database->query('INSERT INTO users', [ + 'created' => new DateTime, +]); +``` + +Nebo SQL literál: + +```php +$database->query('UPDATE table SET', [ + 'date' => $database->literal('NOW()'), +]); +// UPDATE table SET `date` = NOW() +``` + +Nebo výraz, ve kterém lze používat zástupné znaky `?` nebo modifikátory: + +```php +$database->query('UPDATE `table` SET', [ + 'title' => $database::expression('SHA1(?)', 'tajne'), +]); +// UPDATE `table` SET `title` = SHA1('tajne') +``` + +Při update lze modifikátory uvádět přímo v klíčích: + +```php +$database->query('UPDATE table SET', [ + 'date%SQL' => 'NOW()', // %SQL znamená SQL ;) +]); +// UPDATE table SET `date` = NOW() +``` + +V podmínkách (tj. u modifikátorů `%and` a `%or`) není nutné uvádět klíče: + +```php +$result = $database->query('SELECT * FROM `table` WHERE %and', [ + 'number > 10', + 'number < 100', +]); +// SELECT * FROM `table` WHERE (number > 10) AND (number < 100) +``` + +V položkách lze používat i modifikátory nebo zástupné znaky: + +```php +$result = $database->query('SELECT * FROM `table` WHERE %and', [ + ['number > ?', 10], // nebo $database::expression('number > ?', 10) + ['number < ?', 100], + ['%or', [ + 'left' => 1, + 'top' => 2, + ]], +]); +// SELECT * FROM `table` WHERE (number > 10) AND (number < 100) AND (`left` = 1 OR `top` = 2) +``` + +Modifikátor `%ex` vloží do SQL všechny prvky pole: + +```php +$result = $database->query('SELECT * FROM `table` WHERE %ex', [ + $database::expression('left = ?', 1), + 'AND', + 'top IS NULL', +]); +// SELECT * FROM `table` WHERE left = 1 AND top IS NULL +``` + + +Podmínky v SQL příkazu +====================== + +Podmíněné SQL příkazy se ovládají pomocí tří modifikátorů `%if`, `%else` a `%end`. První z nich `%if` se musí nacházet zcela na konci řetězce představujícího SQL a za ním následuje proměnná: + +```php +$user = ??? + +$result = $database->query(' + SELECT * + FROM table + %if', isset($user), 'WHERE user=%s', $user, '%end + ORDER BY name +'); +``` + +Podmínku lze doplnit o část `%else`: + +```php +$result = $database->query(' + SELECT * + FROM %if', $cond, 'one_table %else second_table +'); +``` + +Podmínky můžete zanořovat do sebe. + + +Identifikátory a řetězce v SQL +============================== + +Samotné SQL prochází zpracováním, aby vyhovovalo konvencím dané databáze. Identifikátory (jména tabulek a sloupců) lze uvozovat do hranatých závorek nebo zpětných uvozovek, dále řetězce jednoduchými či dvojitými uvozovkami, nicméně na server se pošle vždy to, co databáze žádá. Příklad + +```php +$database->query("UPDATE `table` SET [status]='I''m fine'"); +// MySQL: UPDATE `table` SET `status`='I\'m fine' +// ODBC: UPDATE [table] SET [status]='I''m fine' +``` + +Uvozovka se uvnitř řetězce v SQL zapisuje zdvojením. + + +Výsledek jako asociativní pole +============================== + +Příklad: vrátí výsledky jako asociativního pole, kde klíčem bude hodnota políčka `id`: + +```php +$assoc = $result->fetchAssoc('id'); +``` + +Největší síla funkce `fetchAssoc()` se projeví u SQL dotazu spojujícího několik tabulek s různými typy vazeb. Databáze z toho udělá plochou tabulku, fetchAssoc jí vrátí tvar. + +Příklad: Mějme tabulku zákazníků a objednávek (vazba N:M) a položíme dotaz: + +```php +$result = $database->query(' + SELECT customer_id, customers.name, order_id, orders.number, ... + FROM customers + INNER JOIN orders USING (customer_id) + WHERE ... +'); +``` + +A rádi bychom získali vnořené asociativní pole podle ID zákazníka a poté podle ID objednávky: + +```php +$all = $result->fetchAssoc('customer_id|order_id'); + +// budeme jej procházet takto: +foreach ($all as $customerId => $orders) { + foreach ($orders as $orderId => $order) { + ... + } +} +``` + +Asociativní deskriptor má obdobnou syntax, jako když pole píšete pomocí přiřazení v PHP. Tedy `'customer_id|order_id'` představuje sérii přiřazení `$all[$customerId][$orderId] = $row;`, postupně pro všechny řádky. + +Někdy by se hodilo, aby se asociovalo podle jména zákazníka namísto jeho ID: + +```php +$all = $result->fetchAssoc('name|order_id'); + +// k prvkům pak přistupujeme třeba takto: +$order = $all['Arnold Rimmer'][$orderId]; +``` + +Co když ale existuje více zákazníků se stejným jménem? Tabulka by měla mít spíš tvar: + +```php +$row = $all['Arnold Rimmer'][0][$orderId]; +$row = $all['Arnold Rimmer'][1][$orderId]; +... +``` + +Rozlišujeme tedy více možných Rimmerů pomocí klasického pole. Asociativní deskriptor má opět formát podobný přiřazování, s tím, že sekvenční pole představuje `[]`: + +```php +$all = $result->fetchAssoc('name[]order_id'); + +// iterujeme všechny Arnoldy ve výsledcích +foreach ($all['Arnold Rimmer'] as $arnoldOrders) { + foreach ($arnoldOrders as $orderId => $order) { + ... + } +} +``` + +Vrátíme se k příkladu s deskriptorem `'customer_id|order_id'` a zkusíme vypsat objednávky jednotlivých zákazníků: + +```php +$all = $result->fetchAssoc('customer_id|order_id'); + +foreach ($all as $customerId => $orders) { + echo "Objednávky zákazníka $customerId": + + foreach ($orders as $orderId => $order) { + echo "Číslo dokladu: $order->number"; + // jméno zákazníka je v $order->name + } +} +``` + +Bylo by hezké místo ID zákazníka vypsat jeho jméno. Jenže to bychom museli dohledávat v poli `$orders`. Výsledky si proto necháme upravit do takovéhoto tvaru: + +```php +$all[$customerId]->name = 'John Doe'; +$all[$customerId]->order_id[$orderId] = $row; +$all[$customerId]->order_id[$orderId2] = $row2; +``` + +Tedy mezi `$customerId` a `$orderId` vložíme ještě mezičlánek. Tentokrát ne číslované indexy, jaké jsme použili pro odlišení jednotlivých Rimmerů, ale rovnou databázový záznam. Řešení je velmi podobné, jen si stačí zapamatovat, že záznam symbolizuje šipka: + +```php +$all = $result->fetchAssoc('customer_id->order_id'); + +foreach ($all as $customerId => $row) { + echo "Objednávky zákazníka $row->name": + + foreach ($row->order_id as $orderId => $order) { + echo "Číslo dokladu: $order->number"; + } +} +``` + + +Prefixy & substituce +==================== + +Názvy tabulek a sloupců mohou obsahovat proměnné části. Ty si nejprve nadefinujeme: + +```php +// vytvoří novou substituci :blog: ==> wp_ +$database->substitute('blog', 'wp_'); +``` + +a poté použijeme v SQL. Všimněte si, že v SQL jsou uvozeny dvojtečkama: + +```php +$database->query("UPDATE [:blog:items] SET [text]='Hello World'"); +// UPDATE `wp_items` SET `text`='Hello World' +``` + + +Datové typy buňek +================= + +Dibi automaticky detekuje typy jednotlivých sloupců dotazu a převádí buňky na nativní typy PHP. Typ můžeme určit i manuálně. Možné typy najdete ve třídě [Dibi\Type |api:Dibi\Type]. + +```php +$result->setType('id', Dibi\Type::INTEGER); // id bude integer +$row = $result->fetch(); + +is_int($row->id) // true +``` + + +Logování +======== + +Dibi má v sobě zabudovaný logger, kterým můžete sledovat všechny vykonané SQL příkazy a měřit délku jejich trvání. Aktivace: + +```php +$database->connect([ + 'driver' => 'sqlite', + 'database' => 'sample.sdb', + 'profiler' => [ + 'file' => 'file.log', + ], +]); +``` + +Šikovnější profiler je panel pro Tracy, který se aktivuje při propojení s Nette. + + +Připojení do [Nette |https://nette.org] +======================================= + +V konfiguračním souboru zaregistrujeme DI rozšíření a přidáme sekci `dibi` - tím se vytvoří potřebné objekty a také databázový panel v [Tracy |https://tracy.nette.org] debugger baru. + +```neon +extensions: + dibi: Dibi\Bridges\Nette\DibiExtension22 + +dibi: + host: localhost + username: root + password: *** + database: foo + lazy: true +``` + +Poté objekt spojení [získáme jako službu z DI kontejneru |https://doc.nette.org/di-usage], např.: + +```php +class Model +{ + private $database; + + public function __construct(Dibi\Connection $database) + { + $this->database = $database; + } +} +``` + + +Komunitní rozšíření +=================== + +Nad Dibi staví nejrůznější knihovny, ORM a rozšíření. Celý jejich seznam najdete na "Packagistu":https://packagist.org/packages/dibi/dibi/dependents?order_by=downloads&requires=require. + + +{{maintitle: Dibi – Šikovná Database Abstraction Library pro PHP}} diff --git a/dibi/cs/@menu.texy b/dibi/cs/@menu.texy new file mode 100644 index 0000000000..271662aad7 --- /dev/null +++ b/dibi/cs/@menu.texy @@ -0,0 +1,4 @@ +- [Úvod | @home] +- "Blog .[link-external]":https://phpfashion.com/category/dibi +- "API .[link-external]":https://api.nette.org/dibi/ +- "GitHub .[link-external]":https://github.com/dg/dibi diff --git a/dibi/cs/@meta.texy b/dibi/cs/@meta.texy new file mode 100644 index 0000000000..49d44d0cfa --- /dev/null +++ b/dibi/cs/@meta.texy @@ -0,0 +1 @@ +{{sitename: Dibi Dokumentace}} diff --git a/dibi/en/@home.texy b/dibi/en/@home.texy new file mode 100644 index 0000000000..d41949423c --- /dev/null +++ b/dibi/en/@home.texy @@ -0,0 +1,657 @@ +Dibi: Smart Database Abstraction Library for PHP +************************************************ + +To install the latest stable Dibi version, use the [Composer|best-practices:composer] command: + +``` +composer require dibi/dibi +``` + +You can find version overview on the [Releases | https://github.com/dg/dibi/releases] page. + +Requires PHP 8.0 or newer. + + +Connecting to Database +====================== + +The database connection is represented by the [Dibi\Connection|api:] object: + +```php +$database = new Dibi\Connection([ + 'driver' => 'mysqli', + 'host' => 'localhost', + 'username' => 'root', + 'password' => '***', + 'database' => 'table', +]); + +$result = $database->query('SELECT * FROM users'); +``` + +Alternatively, you can use the `dibi` static registry, which maintains a connection object in globally accessible storage and calls all functions on it: + +```php +dibi::connect([ + 'driver' => 'mysqli', + 'host' => 'localhost', + 'username' => 'root', + 'password' => '***', + 'database' => 'test', + 'charset' => 'utf8', +]); + +$result = dibi::query('SELECT * FROM users'); +``` + +In case of a connection error, it throws `Dibi\Exception`. + + +Queries +======= + +We query the database using the `query()` method, which returns [Dibi\Result |api:Dibi\Result]. Rows are returned as [Dibi\Row |api:Dibi\Row] objects. + +You can try all the examples [online at the playground |https://repl.it/@DavidGrudl/dibi-playground]. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; +} + +// array of all rows +$all = $result->fetchAll(); + +// array of all rows, keyed by 'id' +$all = $result->fetchAssoc('id'); + +// associative pairs id => name +$pairs = $result->fetchPairs('id', 'name'); + +// number of result rows, if known, or number of affected rows +$count = $result->getRowCount(); +``` + +The fetchAssoc() method can return [more complex associative arrays |#Result as associative array]. + +You can easily add parameters to the query - note the question mark: + +```php +$result = $database->query('SELECT * FROM users WHERE name = ? AND active = ?', $name, $active); + +// or +$result = $database->query('SELECT * FROM users WHERE name = ?', $name, 'AND active = ?', $active); + +$ids = [10, 20, 30]; +$result = $database->query('SELECT * FROM users WHERE id IN (?)', $ids); +``` + +<div class=warning> +**WARNING: never concatenate parameters into SQL queries, as this would create [SQL injection |https://en.wikipedia.org/wiki/SQL_injection] vulnerability** +/-- +$database->query('SELECT * FROM users WHERE id = ' . $id); // BAD!!! +\-- +</div> + +Instead of question marks, you can also use so-called [#modifiers]. + +```php +$result = $database->query('SELECT * FROM users WHERE name = %s', $name); +``` + +In case of failure, `query()` throws either `Dibi\Exception` or one of its descendants: + +- [ConstraintViolationException |api:Dibi\ConstraintViolationException] - violation of some table constraint +- [ForeignKeyConstraintViolationException |api:Dibi\ForeignKeyConstraintViolationException] - invalid foreign key +- [NotNullConstraintViolationException |api:Dibi\NotNullConstraintViolationException] - violation of the NOT NULL condition +- [UniqueConstraintViolationException |api:Dibi\UniqueConstraintViolationException] - collision with unique index + +You can also use shortcut methods: + +```php +// returns associative pairs id => name, shortcut for query(...)->fetchPairs() +$pairs = $database->fetchPairs('SELECT id, name FROM users'); + +// returns array of all rows, shortcut for query(...)->fetchAll() +$rows = $database->fetchAll('SELECT * FROM users'); + +// returns row, shortcut for query(...)->fetch() +$row = $database->fetch('SELECT * FROM users WHERE id = ?', $id); + +// returns cell, shortcut for query(...)->fetchSingle() +$name = $database->fetchSingle('SELECT name FROM users WHERE id = ?', $id); +``` + + +Modifiers +========= + +In addition to the `?` placeholder, we can also use modifiers: + +| %s | string +| %sN | string, but '' translates as NULL +| %bin | binary data +| %b | boolean +| %i | integer +| %iN | integer, but 0 translates as NULL +| %f | float +| %d | date (accepts DateTime, string or UNIX timestamp) +| %dt | datetime (accepts DateTime, string or UNIX timestamp) +| %n | identifier, i.e. table or column name +| %N | identifier, treats period as ordinary character +| %SQL | SQL - directly inserts into SQL (alternative is Dibi\Literal) +| %ex | expands array +| %lmt | special - adds LIMIT to the query +| %ofs | special - adds OFFSET to the query + +Example: + +```php +$result = $database->query('SELECT * FROM users WHERE name = %s', $name); +``` + +If `$name` is `null`, `NULL` is inserted into the SQL statement. + +If the variable is an array, the modifier is applied to all of its elements and they are inserted into SQL separated by commas: + +```php +$ids = [10, '20', 30]; +$result = $database->query('SELECT * FROM users WHERE id IN (%i)', $ids); +// SELECT * FROM users WHERE id IN (10, 20, 30) +``` + +The `%n` modifier is used when the table or column name is a variable. (Beware: do not allow the user to manipulate the content of such a variable): + +```php +$table = 'blog.users'; +$column = 'name'; +$result = $database->query('SELECT * FROM %n WHERE %n = ?', $table, $column, $value); +// SELECT * FROM `blog`.`users` WHERE `name` = 'Jim' +``` + +Four special modifiers are available for the LIKE operator: + +| %like~ | expression starts with string +| %~like | expression ends with string +| %~like~ | expression contains string +| `%like` | expression matches string + +Search for names starting with a certain string: + +```php +$result = $database->query('SELECT * FROM table WHERE name LIKE %like~', $query); +``` + + +Array Modifiers +=============== + +The parameter inserted into an SQL query can also be an array. These modifiers determine how to construct the SQL statement from it: + +| %and | | `key1 = value1 AND key2 = value2 AND ...` +| %or | | `key1 = value1 OR key2 = value2 OR ...` +| %a | assoc | `key1 = value1, key2 = value2, ...` +| %l %in | list | `(val1, val2, ...)` +| %v | values | `(key1, key2, ...) VALUES (value1, value2, ...)` +| %m | multi | `(key1, key2, ...) VALUES (value1, value2, ...), (value1, value2, ...), ...` +| %by | ordering | `key1 ASC, key2 DESC ...` +| %n | names | `key1, key2 AS alias, ...` + +Example: + +```php +$arr = [ + 'a' => 'hello', + 'b' => true, +]; + +$database->query('INSERT INTO table %v', $arr); +// INSERT INTO `table` (`a`, `b`) VALUES ('hello', 1) + +$database->query('UPDATE `table` SET %a', $arr); +// UPDATE `table` SET `a`='hello', `b`=1 +``` + +In the WHERE clause, you can use `%and` or `%or` modifiers: + +```php +$result = $database->query('SELECT * FROM users WHERE %and', [ + 'name' => $name, + 'year' => $year, +]); +// SELECT * FROM users WHERE `name` = 'Jim' AND `year` = 1978 +``` + +See also [#Complex queries]. + +The `%by` modifier is used for sorting - keys specify the columns, and the boolean value determines whether to sort in ascending order: + +```php +$result = $database->query('SELECT id FROM author ORDER BY %by', [ + 'id' => true, // ascending + 'name' => false, // descending +]); +// SELECT id FROM author ORDER BY `id`, `name` DESC +``` + + +Insert, Update & Delete +======================= + +We insert data into SQL queries as associative arrays. Modifiers and the `?` placeholder are not necessary in these cases. + +```php +$database->query('INSERT INTO users', [ + 'name' => $name, + 'year' => $year, +]); +// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) + +$id = $database->getInsertId(); // returns the auto-increment of the inserted record + +$id = $database->getInsertId($sequence); // or sequence value +``` + +Multiple INSERT: + +```php +$database->query( + 'INSERT INTO users', + [ + 'name' => 'Jim', + 'year' => 1978, + ], + [ + 'name' => 'Jack', + 'year' => 1987, + ] +); +// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987) +``` + +Deleting: + +```php +$database->query('DELETE FROM users WHERE id = ?', $id); + +// returns number of deleted rows +$affectedRows = $database->getAffectedRows(); +``` + +Updating records: + +```php +$database->query('UPDATE users SET', [ + 'name' => $name, + 'year' => $year, +], 'WHERE id = ?', $id); +// UPDATE users SET `name` = 'Jim', `year` = 1978 WHERE id = 123 + +// returns the number of updated rows +$affectedRows = $database->getAffectedRows(); +``` + +Substitute any identifier: + +```php +$database->query('INSERT INTO users', [ + 'id' => $id, + 'name' => $name, + 'year' => $year, +], 'ON DUPLICATE KEY UPDATE %a', [ // here the modifier %a must be used + 'name' => $name, + 'year' => $year, +]); +// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) +// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 +``` + + +Transaction +=========== + +There are four methods for dealing with transactions: + +```php +$database->beginTransaction(); + +$database->commit(); + +$database->rollback(); + +$database->transaction(function () { + // some action +}); +``` + + +Testing +======= + +In order to play with Dibi a little, there is a `test()` method that you pass parameters like to `query()`, but instead of executing the SQL statement, it is echoed on the screen. + +The query results can be echoed as a table using `$result->dump()`. + +These variables are also available: + +```php +dibi::$sql; // the latest SQL query +dibi::$elapsedTime; // its duration in sec +dibi::$numOfQueries; +dibi::$totalTime; +``` + + +Complex Queries +=============== + +The parameter may also be an object `DateTime`. + +```php +$result = $database->query('SELECT * FROM users WHERE created < ?', new DateTime); + +$database->query('INSERT INTO users', [ + 'created' => new DateTime, +]); +``` + +Or SQL literal: + +```php +$database->query('UPDATE table SET', [ + 'date' => $database->literal('NOW()'), +]); +// UPDATE table SET `date` = NOW() +``` + +Or an expression in which you can use `?` or modifiers: + +```php +$database->query('UPDATE `table` SET', [ + 'title' => $database::expression('SHA1(?)', 'secret'), +]); +// UPDATE `table` SET `title` = SHA1('secret') +``` + +When updating, modifiers can be placed directly in the keys: + +```php +$database->query('UPDATE table SET', [ + 'date%SQL' => 'NOW()', // %SQL means SQL ;) +]); +// UPDATE table SET `date` = NOW() +``` + +In conditions (ie, for `%and` and `%or` modifiers), it is not necessary to specify the keys: + +```php +$result = $database->query('SELECT * FROM `table` WHERE %and', [ + 'number > 10', + 'number < 100', +]); +// SELECT * FROM `table` WHERE (number > 10) AND (number < 100) +``` + +Modifiers or placeholders can also be used in expressions: + +```php +$result = $database->query('SELECT * FROM `table` WHERE %and', [ + ['number > ?', 10], // or $database::expression('number > ?', 10) + ['number < ?', 100], + ['%or', [ + 'left' => 1, + 'top' => 2, + ]], +]); +// SELECT * FROM `table` WHERE (number > 10) AND (number < 100) AND (`left` = 1 OR `top` = 2) +``` + +The `%ex` modifier inserts all items of the array into SQL: + +```php +$result = $database->query('SELECT * FROM `table` WHERE %ex', [ + $database::expression('left = ?', 1), + 'AND', + 'top IS NULL', +]); +// SELECT * FROM `table` WHERE left = 1 AND top IS NULL +``` + + +Conditions in SQL Statements +============================ + +Conditional SQL statements are controlled by three modifiers: `%if`, `%else`, and `%end`. The `%if` must be at the end of the string representing SQL and is followed by a variable: + +```php +$user = ??? + +$result = $database->query(' + SELECT * + FROM table + %if', isset($user), 'WHERE user=%s', $user, '%end + ORDER BY name +'); +``` + +The condition can be supplemented with an `%else` section: + +```php +$result = $database->query(' + SELECT * + FROM %if', $cond, 'one_table %else second_table +'); +``` + +Conditions can be nested within each other. + + +Identifiers and Strings in SQL +============================== + +SQL itself goes through processing to meet the conventions of the given database. Identifiers (table and column names) can be enclosed in square brackets or backticks, and strings in single or double quotes, but the server always sends what the database requires. Example: + +```php +$database->query("UPDATE `table` SET [status]='I''m fine'"); +// MySQL: UPDATE `table` SET `status`='I\'m fine' +// ODBC: UPDATE [table] SET [status]='I''m fine' +``` + +Quotes inside strings in SQL are written by doubling them. + + +Result as Associative Array +=========================== + +Example: returns results as an associative array where the key will be the value of the `id` field: + +```php +$assoc = $result->fetchAssoc('id'); +``` + +The greatest power of `fetchAssoc()` is demonstrated in SQL queries joining several tables with different types of relationships. The database creates a flat table, fetchAssoc restores the shape. + +Example: Let's have a customer and order table (N:M relationship) and query: + +```php +$result = $database->query(' + SELECT customer_id, customers.name, order_id, orders.number, ... + FROM customers + INNER JOIN orders USING (customer_id) + WHERE ... +'); +``` + +And we'd like to get a nested associative array by Customer ID and then by Order ID: + +```php +$all = $result->fetchAssoc('customer_id|order_id'); + +// we will iterate like this: +foreach ($all as $customerId => $orders) { + foreach ($orders as $orderId => $order) { + ... + } +} +``` + +The associative descriptor has similar syntax to when you write arrays using assignment in PHP. Thus `'customer_id|order_id'` represents the assignment series `$all[$customerId][$orderId] = $row;` sequentially for all rows. + +Sometimes it would be useful to associate by the customer's name instead of their ID: + +```php +$all = $result->fetchAssoc('name|order_id'); + +// elements are then accessed like this: +$order = $all['Arnold Rimmer'][$orderId]; +``` + +But what if there are multiple customers with the same name? The table should have the form: + +```php +$row = $all['Arnold Rimmer'][0][$orderId]; +$row = $all['Arnold Rimmer'][1][$orderId]; +... +``` + +So we distinguish multiple possible Rimmers using a regular array. The associative descriptor again has a format similar to assignment, with sequential arrays represented by `[]`: + +```php +$all = $result->fetchAssoc('name[]order_id'); + +// we iterate all Arnolds in the results +foreach ($all['Arnold Rimmer'] as $arnoldOrders) { + foreach ($arnoldOrders as $orderId => $order) { + ... + } +} +``` + +Returning to the example with the `customer_id|order_id` descriptor, let's try to list orders for each customer: + +```php +$all = $result->fetchAssoc('customer_id|order_id'); + +foreach ($all as $customerId => $orders) { + echo "Orders for customer $customerId": + + foreach ($orders as $orderId => $order) { + echo "Document number: $order->number"; + // customer name is in $order->name + } +} +``` + +It would be nice to display the customer name instead of ID. But we would have to look it up in the `$orders` array. So let's modify the results to have this shape: + +```php +$all[$customerId]->name = 'John Doe'; +$all[$customerId]->order_id[$orderId] = $row; +$all[$customerId]->order_id[$orderId2] = $row2; +``` + +So, between `$customerId` and `$orderId`, we insert an intermediate element. This time not numbered indexes as we used to distinguish individual Rimmers, but directly a database record. The solution is very similar - just remember that a record is symbolized by an arrow: + +```php +$all = $result->fetchAssoc('customer_id->order_id'); + +foreach ($all as $customerId => $row) { + echo "Orders for customer $row->name": + + foreach ($row->order_id as $orderId => $order) { + echo "Document number: $order->number"; + } +} +``` + + +Prefixes & Substitutions +======================== + +Table and column names can contain variable parts. You will first define them: + +```php +// create new substitution :blog: ==> wp_ +$database->substitute('blog', 'wp_'); +``` + +and then use them in SQL. Note that in SQL they are enclosed in colons: + +```php +$database->query("UPDATE [:blog:items] SET [text]='Hello World'"); +// UPDATE `wp_items` SET `text`='Hello World' +``` + + +Field Data Types +================ + +Dibi automatically detects the types of individual query columns and converts cells to native PHP types. We can also specify the type manually. Possible types can be found in the [Dibi\Type |api:Dibi\Type] class. + +```php +$result->setType('id', Dibi\Type::INTEGER); // id will be integer +$row = $result->fetch(); + +is_int($row->id) // true +``` + + +Logging +======= + +Dibi has a built-in logger that lets you track all executed SQL statements and measure the duration of their execution. Activation: + +```php +$database->connect([ + 'driver' => 'sqlite', + 'database' => 'sample.sdb', + 'profiler' => [ + 'file' => 'file.log', + ], +]); +``` + +A more versatile profiler is the Tracy panel, which is activated when connecting to Nette. + + +Connect to [Nette |https://nette.org] +===================================== + +In the configuration file, we register the DI extension and add the `dibi` section - this creates the required objects and also the database panel in the [Tracy |https://tracy.nette.org] debugger bar. + +```neon +extensions: + dibi: Dibi\Bridges\Nette\DibiExtension22 + +dibi: + host: localhost + username: root + password: *** + database: foo + lazy: true +``` + +Then the connection object can be [obtained as a service from the DI container |https://doc.nette.org/di-usage], e.g.: + +```php +class Model +{ + private $database; + + public function __construct(Dibi\Connection $database) + { + $this->database = $database; + } +} +``` + + +Community Extensions +==================== + +Various libraries, ORMs and extensions are built on top of Dibi. You can find a complete list of them on "Packagist":https://packagist.org/packages/dibi/dibi/dependents?order_by=downloads&requires=require. + +{{maintitle: Dibi – Smart Database Abstraction Library for PHP}} diff --git a/dibi/en/@menu.texy b/dibi/en/@menu.texy new file mode 100644 index 0000000000..4add6468f0 --- /dev/null +++ b/dibi/en/@menu.texy @@ -0,0 +1,3 @@ +- [Home | @home] +- "API .[link-external]":https://api.nette.org/dibi/ +- "GitHub .[link-external]":https://github.com/dg/dibi diff --git a/dibi/en/@meta.texy b/dibi/en/@meta.texy new file mode 100644 index 0000000000..b9ca163d2f --- /dev/null +++ b/dibi/en/@meta.texy @@ -0,0 +1 @@ +{{sitename: Dibi Documentation}} diff --git a/dibi/meta.json b/dibi/meta.json new file mode 100644 index 0000000000..ef3653f6bc --- /dev/null +++ b/dibi/meta.json @@ -0,0 +1,5 @@ +{ + "version": "5.x", + "repo": "dg/dibi", + "composer": "dibi/dibi" +} diff --git a/forms/bg/@home.texy b/forms/bg/@home.texy index 80a75b363c..ca9dfebeda 100644 --- a/forms/bg/@home.texy +++ b/forms/bg/@home.texy @@ -1,31 +1,31 @@ -Формуляри -********* +Nette Forms +*********** <div class=perex> -Библиотеката Nette Forms направи революция в създаването на уеб формуляри. Трябваше само да напишете няколко ясни реда код и вече имахте формуляр, включително визуализация, JavaScript и валидиране на сървъра, както и водеща в индустрията сигурност. Нека разгледаме как +Nette Forms донесоха революция в създаването на уеб форми. Изведнъж стана достатъчно да напишете няколко разбираеми реда код и имахте готова форма, включително рендиране, JavaScript и сървърна валидация, и освен това отлично защитена. Ще ви покажем как: -- създаване на удобни за ползване формуляри -- валидиране на изпратените данни. -- да показвате артикулите точно, когато е необходимо. +- да създавате удобни за потребителя форми +- да валидирате изпратените данни +- да рендирате елементи точно според нуждите </div> -С помощта на Nette Forms можете да намалите рутинните задачи, като например писането на валидации (както от страна на сървъра, така и от страна на клиента), и да сведете до минимум вероятността от грешки и проблеми със сигурността. +Използвайки Nette Forms, ще избегнете редица рутинни задачи, като например писане на валидация (при това двойна, на страната на сървъра и клиента), ще минимизирате вероятността от възникване на грешки и пропуски в сигурността. -Формулярите могат да се използват като част от приложение на Nette (напр. в презентатори) или отделно. Тъй като употребата е малко по-различна и в двата случая, сме подготвили отделни инструкции за вас: +Формите можете да използвате или като част от Nette Приложение (т.е. в презентери), или напълно самостоятелно. Тъй като в двата случая използването се различава малко, подготвихме за вас два урока: <div class="wiki-buttons"> -<div> "Формуляри в Presenters .[wiki-button]":in-presenter </div> -<div> "Самостоятелни формуляри .[wiki-button]":standalone </div> +<div> "Форми в презентери .[wiki-button]":in-presenter </div> +<div> "Форми самостоятелно .[wiki-button]":standalone </div> </div> Инсталация ---------- -Изтеглете и инсталирайте пакета с помощта на [Composer |best-practices:composer]: +Изтеглете и инсталирайте библиотеката с помощта на [Composer|best-practices:composer]: ```shell composer require nette/forms diff --git a/forms/bg/@left-menu.texy b/forms/bg/@left-menu.texy index 3d94d2bbbe..489560e8be 100644 --- a/forms/bg/@left-menu.texy +++ b/forms/bg/@left-menu.texy @@ -1,14 +1,14 @@ -Формуляри -********* -- [Преглед |@home] -- [Формуляри в презентациите |in-presenter] -- [Самостоятелни формуляри |standalone] -- [Елементи на формата |controls] -- [Валидиране |validation] -- [Рендериране |rendering] -- [Настройване |configuration] +Nette Forms +*********** +- [Въведение |@home] +- [Форми в презентери|in-presenter] +- [Форми самостоятелно|standalone] +- [Формулярни елементи |controls] +- [Валидация |validation] +- [Рендиране |rendering] +- [Конфигурация |configuration] Допълнително четене ******************* -- [Най-добри практики |best-practices:] +- [Ръководства и процедури |best-practices:] diff --git a/forms/bg/@meta.texy b/forms/bg/@meta.texy new file mode 100644 index 0000000000..57804a1127 --- /dev/null +++ b/forms/bg/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документация на Nette}} diff --git a/forms/bg/configuration.texy b/forms/bg/configuration.texy index 29bc263c67..a0e73d82cc 100644 --- a/forms/bg/configuration.texy +++ b/forms/bg/configuration.texy @@ -1,8 +1,8 @@ -Персонализиране на формуляри -**************************** +Конфигурация на формуляри +************************* .[perex] -Можете да промените [съобщенията за грешки на формуляра |validation] по подразбиране в конфигурацията. +В конфигурацията могат да се променят съобщенията за грешки във формуляри по подразбиране [съобщения за грешки във формуляри|validation]. ```neon forms: @@ -30,32 +30,32 @@ forms: Nette\Forms\Controls\CsrfProtection::Protection: 'Your session has expired. Please return to the home page and try again.' ``` -Ето превода на руски език: +Ето превода на български език: ```neon forms: messages: - Equal: 'Пожалуйста, введите %s.' - NotEqual: 'Это значение не должно быть %s.' - Filled: 'Это поле обязательно для заполнения.' - Blank: 'Это поле должно быть пустым.' - MinLength: 'Пожалуйста, введите не менее %d символов.' - MaxLength: 'Пожалуйста, введите не более %d символов.' - Length: 'Пожалуйста, введите значение длиной от %d до %d символов.' - Email: 'Пожалуйста, введите действительный адрес электронной почты.' - URL: 'Пожалуйста, введите действительный URL.' - Integer: 'Пожалуйста, введите действительное целое число.' - Float: 'Пожалуйста, введите действительное вещественное число.' - Min: 'Пожалуйста, введите значение, большее или равное %d.' - Max: 'Пожалуйста, введите значение, меньшее или равное %d.' - Range: 'Пожалуйста, введите значение между %d и %d.' - MaxFileSize: 'Размер загружаемого файла не может превышать %d байт.' - MaxPostSize: 'Загруженные данные превышают лимит в %d байт.' - MimeType: 'Загруженный файл не соответствует ожидаемому формату.' - Image: 'Загружаемый файл должен быть изображением в формате JPEG, GIF, PNG или WebP.' - Nette\Forms\Controls\SelectBox::Valid: 'Пожалуйста, выберите действительный вариант.' - Nette\Forms\Controls\UploadControl::Valid: 'Во время загрузки файла произошла ошибка.' - Nette\Forms\Controls\CsrfProtection::Protection: 'Ваш сеанс истек. Пожалуйста, вернитесь на главную страницу и повторите попытку.' + Equal: 'Моля, въведете %s.' + NotEqual: 'Тази стойност не трябва да бъде %s.' + Filled: 'Това поле е задължително.' + Blank: 'Това поле трябва да бъде празно.' + MinLength: 'Моля, въведете поне %d знака.' + MaxLength: 'Моля, въведете не повече от %d знака.' + Length: 'Моля, въведете стойност с дължина между %d и %d знака.' + Email: 'Моля, въведете валиден имейл адрес.' + URL: 'Моля, въведете валиден URL адрес.' + Integer: 'Моля, въведете валидно цяло число.' + Float: 'Моля, въведете валидно число.' + Min: 'Моля, въведете стойност, по-голяма или равна на %d.' + Max: 'Моля, въведете стойност, по-малка или равна на %d.' + Range: 'Моля, въведете стойност между %d и %d.' + MaxFileSize: 'Размерът на качения файл може да бъде до %d байта.' + MaxPostSize: 'Качените данни надвишават ограничението от %d байта.' + MimeType: 'Каченият файл не е в очаквания формат.' + Image: 'Каченият файл трябва да бъде изображение във формат JPEG, GIF, PNG или WebP.' + Nette\Forms\Controls\SelectBox::Valid: 'Моля, изберете валидна опция.' + Nette\Forms\Controls\UploadControl::Valid: 'Възникна грешка при качване на файла.' + Nette\Forms\Controls\CsrfProtection::Protection: 'Вашата сесия изтече. Моля, върнете се на началната страница и опитайте отново.' ``` -Ако не използвате цялата рамка и следователно дори конфигурационните файлове, можете да промените съобщенията за грешки по подразбиране директно в полето `Nette\Forms\Validator::$messages`. +Ако не използвате целия framework и следователно не използвате конфигурационни файлове, можете да промените съобщенията за грешки по подразбиране директно в масива `Nette\Forms\Validator::$messages`. diff --git a/forms/bg/controls.texy b/forms/bg/controls.texy index 9b6407375b..cf98691e43 100644 --- a/forms/bg/controls.texy +++ b/forms/bg/controls.texy @@ -1,253 +1,346 @@ -Контроли на формуляра -********************* +Елементи на формуляр +******************** .[perex] -Преглед на вградените контроли на формуляра. +Преглед на стандартните елементи на формуляр. -addText(string|int $name, $label=null): TextInput .[method] -=========================================================== +addText(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +================================================================================================== -Добавя текстово поле с един ред (клас [TextInput |api:Nette\Forms\Controls\TextInput]). Ако потребителят не попълни полето, се връща празен низ `''`, или използвайте `setNullable()`, за да върнете `null`. +Добавя едноредово текстово поле (клас [TextInput |api:Nette\Forms\Controls\TextInput]). Ако потребителят не попълни полето, връща празен низ `''`, или чрез `setNullable()` може да се укаже да връща `null`. ```php -$form->addText('name', 'Имя:') +$form->addText('name', 'Име:') ->setRequired() ->setNullable(); ``` -Този метод автоматично проверява UTF-8, подрязва левите и десните интервали и премахва прекъсванията на редовете, които могат да бъдат изпратени от атакуващ. +Автоматично валидира UTF-8, премахва водещите и крайните интервали и премахва знаците за нов ред, които атакуващ би могъл да изпрати. -Максималната дължина може да бъде ограничена с помощта на `setMaxLength()`. Функцията [addFilter() |validation#Modifying-Input-Values] позволява да се промени стойността, въведена от потребителя. +Максималната дължина може да се ограничи чрез `setMaxLength()`. Промяна на въведената от потребителя стойност позволява [addFilter() |validation#Модификация на входа]. -Използвайте `setHtmlType()`, за да промените [типа на |https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types] входния елемент на `search`, `tel`, `url`, `range`, `date`, `datetime-local`, `month`, `time`, `week`, `color`. Препоръчваме да използвате [addInteger |#addInteger] и [addEmail |#addEmail] вместо `number` и `email`, тъй като те осигуряват валидиране от страна на сървъра. +Чрез `setHtmlType()` може да се промени визуалният характер на текстовото поле на типове като `search`, `tel` или `url` вижте [спецификация|https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types]. Помнете, че промяната на типа е само визуална и не замества функцията за валидация. За тип `url` е препоръчително да се добави специфично [правило URL |validation#Текстови полета]. -```php -$form->addText('color', 'Выберите цвет:') - ->setHtmlType('color') - ->addRule($form::Pattern, 'недопустимое значение', '[0-9a-f]{6}'); -``` +.[note] +За други типове входове, като `number`, `range`, `email`, `date`, `datetime-local`, `time` и `color`, използвайте специализирани методи като [#addInteger], [#addFloat], [#addEmail] [#addDate], [#addTime], [#addDateTime] и [#addColor], които осигуряват сървърна валидация. Типовете `month` и `week` засега не се поддържат напълно във всички браузъри. -Един елемент може да бъде зададен на така наречената "празна стойност", която е нещо подобно на стойността по подразбиране, но ако потребителят не я презапише, тя връща празен низ или `null`. +На елемента може да се зададе т.нар. empty-value, което е нещо като стойност по подразбиране, но ако потребителят не я промени, елементът връща празен низ или `null`. ```php $form->addText('phone', 'Телефон:') ->setHtmlType('tel') - ->setEmptyValue('+420'); + ->setEmptyValue('+359'); ``` addTextArea(string|int $name, $label=null): TextArea .[method] ============================================================== -Добавя многоредово текстово поле (клас [TextArea |api:Nette\Forms\Controls\TextArea]). Ако потребителят не попълни полето, се връща празен низ `''` или използвайте `setNullable()`, за да върнете `null`. +Добавя поле за въвеждане на многоредов текст (клас [TextArea |api:Nette\Forms\Controls\TextArea]). Ако потребителят не попълни полето, връща празен низ `''`, или чрез `setNullable()` може да се укаже да връща `null`. ```php -$form->addTextArea('note', 'Примечание:') - ->addRule($form::MaxLength, 'Ваша заметка слишком длинная', 10000); +$form->addTextArea('note', 'Бележка:') + ->addRule($form::MaxLength, 'Бележката е твърде дълга', 10000); ``` -Автоматично проверява UTF-8 и нормализира прекъсванията на редовете до `\n`. За разлика от едноредовото поле за въвеждане, то не съкращава белите полета. +Автоматично валидира UTF-8 и нормализира разделителите на редове на `\n`. За разлика от едноредовото входно поле, не се извършва премахване на интервали. -Максималната дължина може да бъде ограничена с помощта на `setMaxLength()`. Функцията [addFilter() |validation#Modifying-Input-Values] ви позволява да промените стойността, въведена от потребителя. Можете да зададете така наречената "празна стойност", като използвате `setEmptyValue()`. +Максималната дължина може да се ограничи чрез `setMaxLength()`. Промяна на въведената от потребителя стойност позволява [addFilter() |validation#Модификация на входа]. Може да се зададе т.нар. empty-value чрез `setEmptyValue()`. addInteger(string|int $name, $label=null): TextInput .[method] ============================================================== -Добавя поле за въвеждане на цяло число (клас [TextInput |api:Nette\Forms\Controls\TextInput]). Връща цяло число или `null`, ако потребителят не е въвел нищо. +Добавя поле за въвеждане на цяло число (клас [TextInput |api:Nette\Forms\Controls\TextInput]). Връща или integer, или `null`, ако потребителят не въведе нищо. + +```php +$form->addInteger('year', 'Година:') + ->addRule($form::Range, 'Годината трябва да бъде в диапазона от %d до %d.', [1900, 2023]); +``` + +Елементът се рендира като `<input type="number">`. Чрез използване на метода `setHtmlType()` може да се промени типът на `range` за показване под формата на плъзгач, или на `text`, ако предпочитате стандартно текстово поле без специалното поведение на тип `number`. + + +addFloat(string|int $name, $label=null): TextInput .[method]{data-version:3.1.12} +================================================================================= + +Добавя поле за въвеждане на десетично число (клас [TextInput |api:Nette\Forms\Controls\TextInput]). Връща или float, или `null`, ако потребителят не въведе нищо. ```php -$form->addInteger('level', 'Уровень:') +$form->addFloat('level', 'Ниво:') ->setDefaultValue(0) - ->addRule($form::Range, 'Уровень должен быть между %d и %d.', [0, 100]); + ->addRule($form::Range, 'Нивото трябва да бъде в диапазона от %d до %d.', [0, 100]); ``` +Елементът се рендира като `<input type="number">`. Чрез използване на метода `setHtmlType()` може да се промени типът на `range` за показване под формата на плъзгач, или на `text`, ако предпочитате стандартно текстово поле без специалното поведение на тип `number`. + +Nette и браузърът Chrome приемат като разделител на десетичните места както запетая, така и точка. За да бъде тази функционалност достъпна и във Firefox, е препоръчително да се зададе атрибутът `lang` или за дадения елемент, или за цялата страница, например `<html lang="bg">`. + -addEmail(string|int $name, $label=null): TextInput .[method] -============================================================ +addEmail(string|int $name, $label=null, int $maxLength=255): TextInput .[method] +================================================================================ -Добавя валидирано поле за имейл адрес (клас [TextInput |api:Nette\Forms\Controls\TextInput]). Ако потребителят не е попълнил полето, се връща празен низ `''` или използвайте `setNullable()`, за да върнете `null`. +Добавя поле за въвеждане на имейл адрес (клас [TextInput |api:Nette\Forms\Controls\TextInput]). Ако потребителят не попълни полето, връща празен низ `''`, или чрез `setNullable()` може да се укаже да връща `null`. ```php $form->addEmail('email', 'Имейл:'); ``` -Проверява дали стойността е валиден имейл адрес. Не се проверява дали домейнът действително съществува, проверява се само синтаксисът. Автоматично проверява UTF-8, подрязва левите и десните интервали. +Оверява дали стойността е валиден имейл адрес. Не се проверява дали домейнът действително съществува, проверява се само синтаксисът. Автоматично валидира UTF-8, премахва водещите и крайните интервали. -Максималната дължина може да бъде ограничена с помощта на `setMaxLength()`. Функцията [addFilter() |validation#Modifying-Input-Values] ви позволява да промените стойността, въведена от потребителя. Можете да зададете така наречената "празна стойност", като използвате `setEmptyValue()`. +Максималната дължина може да се ограничи чрез `setMaxLength()`. Промяна на въведената от потребителя стойност позволява [addFilter() |validation#Модификация на входа]. Може да се зададе т.нар. empty-value чрез `setEmptyValue()`. -addPassword(string|int $name, $label=null): TextInput .[method] -=============================================================== +addPassword(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +====================================================================================================== -Добавя поле за парола (клас [TextInput |api:Nette\Forms\Controls\TextInput]). +Добавя поле за въвеждане на парола (клас [TextInput |api:Nette\Forms\Controls\TextInput]). ```php -$form->addPassword('password', 'Пароль:') +$form->addPassword('password', 'Парола:') ->setRequired() - ->addRule($form::MinLength, 'Пароль должен быть длиной не менее %d символов', 8) - ->addRule($form::Pattern, 'Пароль должен содержать цифры', '.*[0-9].*'); + ->addRule($form::MinLength, 'Паролата трябва да съдържа поне %d знака', 8) + ->addRule($form::Pattern, 'Трябва да съдържа цифра', '.*[0-9].*'); ``` -Когато формулярът се изпрати отново, входът ще бъде празен. Той автоматично проверява UTF-8, подрязва левите и десните интервали и премахва прекъсванията на редовете, които могат да бъдат изпратени от атакуващ. +При повторно показване на формуляра полето ще бъде празно. Автоматично валидира UTF-8, премахва водещите и крайните интервали и премахва знаците за нов ред, които атакуващ би могъл да изпрати. addCheckbox(string|int $name, $caption=null): Checkbox .[method] ================================================================ -Добавя квадратче за отметка (клас [Checkbox |api:Nette\Forms\Controls\Checkbox]). Полето връща или `true`, или `false`, в зависимост от това дали е маркирано. +Добавя чекбокс (клас [Checkbox |api:Nette\Forms\Controls\Checkbox]). Връща стойност `true` или `false`, в зависимост от това дали е отметнат. ```php -$form->addCheckbox('agree', 'Я согласен с условиями') - ->setRequired('Вы должны согласиться с нашими условиями'); +$form->addCheckbox('agree', 'Съгласен съм с условията') + ->setRequired('Необходимо е да се съгласите с условията'); ``` -addCheckboxList(string|int $name, $label=null, array $items=null): CheckboxList .[method] -========================================================================================= +addCheckboxList(string|int $name, $label=null, ?array $items=null): CheckboxList .[method] +========================================================================================== -Добавя списък с квадратчета за избор на няколко елемента (клас [CheckboxList |api:Nette\Forms\Controls\CheckboxList]). Връща масив от ключове на избрани елементи. Методът `getSelectedItems()` връща стойности вместо ключове. +Добавя чекбоксове за избор на няколко елемента (клас [CheckboxList |api:Nette\Forms\Controls\CheckboxList]). Връща масив от ключовете на избраните елементи. Методът `getSelectedItems()` връща стойностите вместо ключовете. ```php -$form->addCheckboxList('colors', 'Цвета:', [ - 'r' => 'red', - 'g' => 'green', - 'b' => 'blue', +$form->addCheckboxList('colors', 'Цветове:', [ + 'r' => 'червен', + 'g' => 'зелен', + 'b' => 'син', ]); ``` -Предаваме масив от елементи като трети параметър или метод `setItems()`. +Масивът с предлаганите елементи предаваме като трети параметър или чрез метода `setItems()`. -Можете да използвате `setDisabled(['r', 'g'])` за деактивиране на отделни елементи. +Чрез `setDisabled(['r', 'g'])` могат да се деактивират отделни елементи. -Елементът автоматично проверява дали не е извършена манипулация и дали избраните елементи наистина са едни от предлаганите и не са били деактивирани. Методът `getRawValue()` може да се използва за получаване на елементи, изпратени без тази важна проверка. +Елементът автоматично проверява дали не е настъпило подправяне и дали избраните елементи са действително едни от предлаганите и не са били деактивирани. Чрез метода `getRawValue()` могат да се получат изпратените елементи без тази важна проверка. -При задаване на стойности по подразбиране се проверява също дали те са един от предлаганите елементи, в противен случай се прави изключение. Тази проверка може да бъде деактивирана с помощта на `checkDefaultValue(false)`. +При задаване на избраните по подразбиране елементи също проверява дали те са едни от предлаганите, в противен случай хвърля изключение. Тази проверка може да се изключи чрез `checkDefaultValue(false)`. +Ако изпращате формуляра с метод `GET`, можете да изберете по-компактен начин за пренос на данни, който спестява размер на query string-а. Активира се чрез задаване на HTML атрибут на формуляра: + +```php +$form->setHtmlAttribute('data-nette-compact'); +``` -addRadioList(string|int $name, $label=null, array $items=null): RadioList .[method] -=================================================================================== -Добавя радио бутони (клас [RadioList |api:Nette\Forms\Controls\RadioList]). Връща ключа на избрания елемент или `null`, ако потребителят не е избрал нищо. Методът `getSelectedItem()` връща стойност вместо ключ. +addRadioList(string|int $name, $label=null, ?array $items=null): RadioList .[method] +==================================================================================== + +Добавя радио бутони (клас [RadioList |api:Nette\Forms\Controls\RadioList]). Връща ключа на избрания елемент, или `null`, ако потребителят не е избрал нищо. Методът `getSelectedItem()` връща стойността вместо ключа. ```php $sex = [ - 'm' => 'male', - 'f' => 'female', + 'm' => 'мъж', + 'f' => 'жена', ]; $form->addRadioList('gender', 'Пол:', $sex); ``` -Предаваме масив от елементи като трети параметър или на метода `setItems()`. +Масивът с предлаганите елементи предаваме като трети параметър или чрез метода `setItems()`. -Можете да използвате `setDisabled(['m'])` за деактивиране на отделни елементи. +Чрез `setDisabled(['m', 'f'])` могат да се деактивират отделни елементи. -Елементът автоматично проверява дали не е извършена манипулация и дали избраният елемент наистина е един от предлаганите и не е бил деактивиран. Методът `getRawValue()` може да се използва за извличане на изпратен елемент без тази важна проверка. +Елементът автоматично проверява дали не е настъпило подправяне и дали избраният елемент е действително един от предлаганите и не е бил деактивиран. Чрез метода `getRawValue()` може да се получи изпратеният елемент без тази важна проверка. -При настройката по подразбиране се проверява дали това е един от предлаганите елементи, в противен случай се подава изключение. Тази проверка може да бъде деактивирана с помощта на `checkDefaultValue(false)`. +При задаване на избрания по подразбиране елемент също проверява дали той е един от предлаганите, в противен случай хвърля изключение. Тази проверка може да се изключи чрез `checkDefaultValue(false)`. -addSelect(string|int $name, $label=null, array $items=null): SelectBox .[method] -================================================================================ +addSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): SelectBox .[method] +================================================================================================== -Добавя поле за избор (клас [SelectBox |api:Nette\Forms\Controls\SelectBox]). Връща ключа на избрания елемент или `null`, ако потребителят не е избрал нищо. Методът `getSelectedItem()` връща стойност вместо ключ. +Добавя селект бокс (клас [SelectBox |api:Nette\Forms\Controls\SelectBox]). Връща ключа на избрания елемент, или `null`, ако потребителят не е избрал нищо. Методът `getSelectedItem()` връща стойността вместо ключа. ```php $countries = [ - 'CZ' => 'Чешская республика', + 'BG' => 'България', + 'CZ' => 'Чешка република', 'SK' => 'Словакия', - 'GB' => 'Великобритания', ]; -$form->addSelect('country', 'Страна:', $countries) - ->setDefaultValue('SK'); +$form->addSelect('country', 'Държава:', $countries) + ->setDefaultValue('BG'); ``` -Предаваме масив от елементи като трети параметър или метод `setItems()`. Масивът от елементи може да бъде и двуизмерен: +Масивът с предлаганите елементи предаваме като трети параметър или чрез метода `setItems()`. Елементите могат да бъдат и двумерен масив: ```php $countries = [ - 'Europe' => [ - 'CZ' => 'Чешская республика', + 'Европа' => [ + 'BG' => 'България', + 'CZ' => 'Чешка република', 'SK' => 'Словакия', - 'GB' => 'Великобритания', ], 'CA' => 'Канада', - 'US' => 'США', - '?' => 'другая', + 'US' => 'САЩ', + '?' => 'друга', ]; ``` -В блоковете за селекция първият елемент често има специално значение, той служи като призив за действие. Използвайте метода `setPrompt()`, за да добавите такъв запис. +При селект боксовете често първият елемент има специално значение, служи като призив за действие. За добавяне на такъв елемент служи методът `setPrompt()`. ```php -$form->addSelect('country', 'Страна:', $countries) - ->setPrompt('Выберите страну'); +$form->addSelect('country', 'Държава:', $countries) + ->setPrompt('Изберете държава'); ``` -Можете също така да използвате `setDisabled(['CZ', 'SK'])` за деактивиране на отделни елементи. +Чрез `setDisabled(['CZ', 'SK'])` могат да се деактивират отделни елементи. -Елементът автоматично проверява дали не е извършена манипулация и дали избраният елемент наистина е един от предлаганите и не е бил деактивиран. Методът `getRawValue()` може да се използва за извличане на изпратен елемент без тази важна проверка. +Елементът автоматично проверява дали не е настъпило подправяне и дали избраният елемент е действително един от предлаганите и не е бил деактивиран. Чрез метода `getRawValue()` може да се получи изпратеният елемент без тази важна проверка. -При настройката по подразбиране се проверява дали това е един от предлаганите елементи, в противен случай се подава изключение. Тази проверка може да бъде деактивирана с помощта на `checkDefaultValue(false)`. +При задаване на избрания по подразбиране елемент също проверява дали той е един от предлаганите, в противен случай хвърля изключение. Тази проверка може да се изключи чрез `checkDefaultValue(false)`. -addMultiSelect(string|int $name, $label=null, array $items=null): MultiSelectBox .[method] -========================================================================================== +addMultiSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): MultiSelectBox .[method] +============================================================================================================ -Добавя поле за множествен избор (клас [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox]). Връща масив от ключове на избрани елементи. Методът `getSelectedItems()` връща стойности вместо ключове. +Добавя селект бокс за избор на няколко елемента (клас [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox]). Връща масив от ключовете на избраните елементи. Методът `getSelectedItems()` връща стойностите вместо ключовете. ```php -$form->addMultiSelect('countries', 'Страны:', $countries); +$form->addMultiSelect('countries', 'Държави:', $countries); ``` -Предаваме масив от елементи като трети параметър или метод `setItems()`. Масивът от елементи може да бъде и двуизмерен. +Масивът с предлаганите елементи предаваме като трети параметър или чрез метода `setItems()`. Елементите могат да бъдат и двумерен масив. -Можете да използвате `setDisabled(['CZ', 'SK'])` за деактивиране на отделни елементи. +Чрез `setDisabled(['CZ', 'SK'])` могат да се деактивират отделни елементи. -Елементът автоматично проверява дали не е извършена манипулация и дали избраните елементи наистина са едни от предлаганите и не са били деактивирани. Методът `getRawValue()` може да се използва за получаване на елементи, изпратени без тази важна проверка. +Елементът автоматично проверява дали не е настъпило подправяне и дали избраните елементи са действително едни от предлаганите и не са били деактивирани. Чрез метода `getRawValue()` могат да се получат изпратените елементи без тази важна проверка. -При задаване на стойности по подразбиране се проверява също дали те са един от предлаганите елементи, в противен случай се прави изключение. Тази проверка може да бъде деактивирана с помощта на `checkDefaultValue(false)`. +При задаване на избраните по подразбиране елементи също проверява дали те са едни от предлаганите, в противен случай хвърля изключение. Тази проверка може да се изключи чрез `checkDefaultValue(false)`. addUpload(string|int $name, $label=null): UploadControl .[method] ================================================================= -Добавя поле за качване на файлове (клас [UploadControl |api:Nette\Forms\Controls\UploadControl]). Връща обект [FileUpload |http:request#FileUpload], дори ако потребителят не е качил файл, което може да се установи с помощта на метода `FileUpload::hasFile()`. +Добавя поле за качване на файл (клас [UploadControl |api:Nette\Forms\Controls\UploadControl]). Връща обект [FileUpload |http:request#FileUpload] и то дори в случай, че потребителят не е изпратил никакъв файл, което може да се установи чрез метода `FileUpload::hasFile()`. ```php $form->addUpload('avatar', 'Аватар:') - ->addRule($form::Image, 'Аватар должен быть в формате JPEG, PNG, GIF или WebP') - ->addRule($form::MaxFileSize, 'Максимальный размер - 1 МБ', 1024 * 1024); + ->addRule($form::Image, 'Аватарът трябва да е JPEG, PNG, GIF, WebP или AVIF.') + ->addRule($form::MaxFileSize, 'Максималният размер е 1 MB.', 1024 * 1024); ``` -Ако файлът е качен неправилно, формулярът не е изпратен успешно и се показва грешка. Т.е. не е необходимо да се проверява методът `FileUpload::isOk()`. +Ако файлът не успее да се качи коректно, формулярът не е успешно изпратен и се показва грешка. Т.е. при успешно изпращане не е необходимо да се проверява методът `FileUpload::isOk()`. -Не се доверявайте на оригиналното име на файла, върнато от метода `FileUpload::getName()`, тъй като клиентът може да изпрати злонамерено име на файла с цел да повреди или да отвлече приложението ви. +Никога не вярвайте на оригиналното име на файла, върнато от метода `FileUpload::getName()`, клиентът може да е изпратил злонамерено име на файл с намерение да повреди или хакне вашето приложение. -Правилата `MimeType` и `Image` определят желания тип файл или изображение по неговата сигнатура. Целостта на целия файл не се проверява. Можете да разберете дали дадено изображение е повредено, например като се опитате да [го изтеглите |http:request#toImage]. +Правилата `MimeType` и `Image` откриват изисквания тип въз основа на сигнатурата на файла и не проверяват неговата цялост. Дали изображението не е повредено може да се установи например чрез опит за неговото [зареждане |http:request#toImage]. addMultiUpload(string|int $name, $label=null): UploadControl .[method] ====================================================================== -Добавя поле за качване на множество файлове (клас [UploadControl |api:Nette\Forms\Controls\UploadControl]). Връща масив от обекти [FileUpload |http:request#FileUpload]. Методът `FileUpload::hasFile()` ще върне `true` за всеки от тях. +Добавя поле за качване на няколко файла едновременно (клас [UploadControl |api:Nette\Forms\Controls\UploadControl]). Връща масив от обекти [FileUpload |http:request#FileUpload]. Методът `FileUpload::hasFile()` при всеки от тях ще връща `true`. + +```php +$form->addMultiUpload('files', 'Файлове:') + ->addRule($form::MaxLength, 'Могат да бъдат качени максимум %d файла', 10); +``` + +Ако някой файл не успее да се качи коректно, формулярът не е успешно изпратен и се показва грешка. Т.е. при успешно изпращане не е необходимо да се проверява методът `FileUpload::isOk()`. + +Никога не вярвайте на оригиналните имена на файловете, върнати от метода `FileUpload::getName()`, клиентът може да е изпратил злонамерено име на файл с намерение да повреди или хакне вашето приложение. + +Правилата `MimeType` и `Image` откриват изисквания тип въз основа на сигнатурата на файла и не проверяват неговата цялост. Дали изображението не е повредено може да се установи например чрез опит за неговото [зареждане |http:request#toImage]. + + +addDate(string|int $name, $label=null): DateTimeControl .[method]{data-version:3.1.14} +====================================================================================== + +Добавя поле, което позволява на потребителя лесно да въведе дата, състояща се от година, месец и ден (клас [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Като стойност по подразбиране приема или обекти, имплементиращи интерфейса `DateTimeInterface`, низ с време, или число, представляващо UNIX timestamp. Същото важи и за аргументите на правилата `Min`, `Max` или `Range`, които дефинират минималната и максималната разрешена дата. + +```php +$form->addDate('date', 'Дата:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'Датата трябва да е поне преди един месец.', new DateTime('-1 month')); +``` + +Стандартно връща обект `DateTimeImmutable`, чрез метода `setFormat()` можете да специфицирате [текстов формат|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] или timestamp: + +```php +$form->addDate('date', 'Дата:') + ->setFormat('Y-m-d'); +``` + + +addTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=============================================================================================================== + +Добавя поле, което позволява на потребителя лесно да въведе час, състоящ се от часове, минути и по избор и секунди (клас [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Като стойност по подразбиране приема или обекти, имплементиращи интерфейса `DateTimeInterface`, низ с време, или число, представляващо UNIX timestamp. От тези входове се използва само информацията за времето, датата се игнорира. Същото важи и за аргументите на правилата `Min`, `Max` или `Range`, които дефинират минималния и максималния разрешен час. Ако зададената минимална стойност е по-висока от максималната, се създава времеви диапазон, преминаващ през полунощ. + +```php +$form->addTime('time', 'Час:', withSeconds: true) + ->addRule($form::Range, 'Часът трябва да бъде в диапазона от %s до %s.', ['12:30', '13:30']); +``` + +Стандартно връща обект `DateTimeImmutable` (с дата 1 януари година 1), чрез метода `setFormat()` можете да специфицирате [текстов формат|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters]: + +```php +$form->addTime('time', 'Час:') + ->setFormat('H:i'); +``` + + +addDateTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=================================================================================================================== + +Добавя поле, което позволява на потребителя лесно да въведе дата и час, състоящи се от година, месец, ден, часове, минути и по избор и секунди (клас [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Като стойност по подразбиране приема или обекти, имплементиращи интерфейса `DateTimeInterface`, низ с време, или число, представляващо UNIX timestamp. Същото важи и за аргументите на правилата `Min`, `Max` или `Range`, които дефинират минималната и максималната разрешена дата. + +```php +$form->addDateTime('datetime', 'Дата и час:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'Датата трябва да е поне преди един месец.', new DateTime('-1 month')); +``` + +Стандартно връща обект `DateTimeImmutable`, чрез метода `setFormat()` можете да специфицирате [текстов формат|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] или timestamp: ```php -$form->addMultiUpload('files', 'Файлы:') - ->addRule($form::MaxLength, 'Может быть загружено не более %d файлов', 10); +$form->addDateTime('datetime') + ->setFormat(DateTimeControl::FormatTimestamp); ``` -Ако един от файловете не е качен правилно, формулярът няма да бъде изпратен успешно и ще бъде показана грешка. Т.е. не е необходимо да се проверява методът `FileUpload::isOk()`. -Не се доверявайте на оригиналните имена на файловете, върнати от метода `FileUpload::getName()`, тъй като клиентът може да изпрати злонамерено име на файл с намерението да повреди или отвлече вашето приложение. +addColor(string|int $name, $label=null): ColorPicker .[method]{data-version:3.1.14} +=================================================================================== + +Добавя поле за избор на цвят (клас [ColorPicker |api:Nette\Forms\Controls\ColorPicker]). Цветът е низ във формата `#rrggbb`. Ако потребителят не направи избор, се връща черен цвят `#000000`. -Правилата `MimeType` и `Image` идентифицират желания тип файл или изображение чрез неговия подпис. Целостта на целия файл не се проверява. Можете да разберете дали дадено изображение е повредено, например като се опитате да [го изтеглите |http:request#toImage]. +```php +$form->addColor('color', 'Цвят:') + ->setDefaultValue('#3C8ED7'); +``` -addHidden(string|int $name, string $default=null): HiddenField .[method] -======================================================================== +addHidden(string|int $name, ?string $default=null): HiddenField .[method] +========================================================================= Добавя скрито поле (клас [HiddenField |api:Nette\Forms\Controls\HiddenField]). @@ -255,7 +348,9 @@ addHidden(string|int $name, string $default=null): HiddenField .[method] $form->addHidden('userid'); ``` -Използвайте `setNullable()`, за да го модифицирате така, че да връща `null` вместо празен низ. Функцията [addFilter() |validation#Modifying-Input-Values] ви позволява да промените представената стойност. +Чрез `setNullable()` може да се настрои да връща `null` вместо празен низ. Промяна на изпратената стойност позволява [addFilter() |validation#Модификация на входа]. + +Въпреки че елементът е скрит, е **важно да се осъзнае**, че стойността все още може да бъде модифицирана или подправена от атакуващ. Винаги щателно проверявайте и валидирайте всички получени стойности на сървърна страна, за да се предотвратят рискове за сигурността, свързани с манипулиране на данни. addSubmit(string|int $name, $caption=null): SubmitButton .[method] @@ -264,17 +359,17 @@ addSubmit(string|int $name, $caption=null): SubmitButton .[method] Добавя бутон за изпращане (клас [SubmitButton |api:Nette\Forms\Controls\SubmitButton]). ```php -$form->addSubmit('submit', 'Зарегистрироваться'); +$form->addSubmit('submit', 'Изпрати'); ``` -Във формуляра може да има повече от един бутон за изпращане: +Във формуляра е възможно да има и няколко бутона за изпращане: ```php -$form->addSubmit('register', 'Зарегистрироваться'); -$form->addSubmit('cancel', 'Отмена'); +$form->addSubmit('register', 'Регистрирай се'); +$form->addSubmit('cancel', 'Отказ'); ``` -За да разберете коя от тях е била натисната, използвайте +За да установите кой от тях е бил кликнат, използвайте: ```php if ($form['register']->isSubmittedBy()) { @@ -282,48 +377,48 @@ if ($form['register']->isSubmittedBy()) { } ``` -Ако не искате да валидирате формуляра при натискане на бутон за изпращане (например бутоните *Cancel* или *Preview*), можете да го забраните с функцията [setValidationScope() |validation#Disabling-Validation]. +Ако не искате да валидирате целия формуляр при натискане на бутона (например при бутони *Отказ* или *Преглед*), използвайте [setValidationScope() |validation#Изключване на валидацията]. addButton(string|int $name, $caption): Button .[method] ======================================================= -Добавя бутон (клас [Button |api:Nette\Forms\Controls\Button]) без функция за изпращане. Това е полезно за свързване на друга функционалност към идентификатора, например действие на JavaScript. +Добавя бутон (клас [Button |api:Nette\Forms\Controls\Button]), който няма функция за изпращане. Може следователно да се използва за някаква друга функция, напр. извикване на JavaScript функция при кликване. ```php -$form->addButton('raise', 'Поднять зарплату') +$form->addButton('raise', 'Увеличи заплатата') ->setHtmlAttribute('onclick', 'raiseSalary()'); ``` -addImageButton(string|int $name, string $src=null, string $alt=null): ImageButton .[method] -=========================================================================================== +addImageButton(string|int $name, ?string $src=null, ?string $alt=null): ImageButton .[method] +============================================================================================= -Добавя бутон за изпращане като изображение (клас [ImageButton |api:Nette\Forms\Controls\ImageButton]). +Добавя бутон за изпращане под формата на изображение (клас [ImageButton |api:Nette\Forms\Controls\ImageButton]). ```php $form->addImageButton('submit', '/path/to/image'); ``` -Ако се използва повече от един бутон за изпращане, можете да разберете кой от тях е бил натиснат с `$form['submit']->isSubmittedBy()`. +При използване на няколко бутона за изпращане може да се установи кой е бил кликнат, чрез `$form['submit']->isSubmittedBy()`. addContainer(string|int $name): Container .[method] =================================================== -Добавя подформа (клас [Container |api:Nette\Forms\Container]) или контейнер, който може да се третира по същия начин като форма. Това означава, че можете да използвате методи като `setDefaults()` или `getValues()`. +Добавя подформуляр (клас [Container|api:Nette\Forms\Container]), или контейнер, в който могат да се добавят други елементи по същия начин, както ги добавяме към формуляра. Работят и методите `setDefaults()` или `getValues()`. ```php $sub1 = $form->addContainer('first'); -$sub1->addText('name', 'Ваше имя:'); +$sub1->addText('name', 'Вашето име:'); $sub1->addEmail('email', 'Имейл:'); $sub2 = $form->addContainer('second'); -$sub2->addText('name', 'Ваше имя:'); +$sub2->addText('name', 'Вашето име:'); $sub2->addEmail('email', 'Имейл:'); ``` -След това изпратените данни се връщат като многомерна структура: +Изпратените данни след това връща като многомерна структура: ```php [ @@ -339,110 +434,112 @@ $sub2->addEmail('email', 'Имейл:'); ``` -Преглед на настройките .[#toc-overview-of-settings] -=================================================== +Преглед на настройките +====================== -Можем да извикаме следните методи за всички елементи (за пълен преглед вижте [документацията на API |https://api.nette.org/forms/master/Nette/Forms/Controls.html]): +При всички елементи можем да извикваме следните методи (пълен преглед в [API документация|https://api.nette.org/forms/master/Nette/Forms/Controls.html]): .[table-form-methods language-php] -| `setDefaultValue($value)` | задаване на стойността по подразбиране -| `getValue()` | извличане на текущата стойност -| `setOmitted()` | [пропуснати стойности |#omitted values] -| `setDisabled()` | [деактивиране на входовете |#disabling inputs] +| `setDefaultValue($value)` | задава стойност по подразбиране +| `getValue()` | получава текущата стойност +| `setOmitted()` | [#пропускане на стойност] +| `setDisabled()` | [#деактивиране на елементи] -Рендъринг: +Рендиране: .[table-form-methods language-php] -| `setCaption()` | промяна на заглавието на елемента -| `setTranslator()` | задайте [преводач |rendering#translating] -| `setHtmlAttribute()` | задаване на [атрибут на |rendering#HTML attributes] елемент [HTML |rendering#HTML attributes] -| `setHtmlId()` | задаване на атрибут на HTML `id` -| `setHtmlType()` | задаване на атрибут на HTML `type` -| `setHtmlName()` | задаване на атрибут на HTML `name` -| `setOption()` | задава [визуализиране на данни |rendering#Options] - -Валидиране: +| `setCaption($caption)` | променя етикета на елемента +| `setTranslator($translator)` | задава [преводач |rendering#Превод] +| `setHtmlAttribute($name, $value)` | задава [HTML атрибут |rendering#HTML атрибути] на елемента +| `setHtmlId($id)` | задава HTML атрибут `id` +| `setHtmlType($type)` | задава HTML атрибут `type` +| `setHtmlName($name)` | задава HTML атрибут `name` +| `setOption($key, $value)` | [настройка за рендиране |rendering#Options] + +Валидация: .[table-form-methods language-php] -| `setRequired()` | [задължително поле |validation] -| `addRule()` | задаване на [правило за валидиране |validation#Rules] -| `addCondition()`, `addConditionOn()` | задаване на [условие за валидиране |validation#Conditions] -| `addError()` | [предаване на съобщение за грешка |validation#Processing-Errors] +| `setRequired()` | [задължителен елемент |validation] +| `addRule()` | задава [правило за валидация |validation#Правила] +| `addCondition()`, `addConditionOn()` | задава [условие за валидация |validation#Условия] +| `addError($message)` | [предаване на съобщение за грешка |validation#Грешки при обработка] -Следните методи могат да бъдат извикани за елементите `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`: +При елементите `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` могат да се извикват следните методи: .[table-form-methods language-php] -| `setNullable()`| задава дали функцията getValue() да връща `null` вместо празен низ -| `setEmptyValue()` | задава специална стойност, която се третира като празен низ -| `setMaxLength()` | задава максималния брой разрешени символи -| `addFilter()` | [промяна на входните стойности |validation#Modifying-Input-Values] +| `setNullable()` | задава дали getValue() да връща `null` вместо празен низ +| `setEmptyValue($value)` | задава специална стойност, която се счита за празен низ +| `setMaxLength($length)` | задава максималния брой разрешени знаци +| `addFilter($filter)` | [редактиране на въведеното |validation#Модификация на входа] -пропуснати стойности .[#toc-omitted-values] -------------------------------------------- +Пропускане на стойност +====================== -Ако не се интересувате от стойността, въведена от потребителя, можем да използваме `setOmitted()`, за да я пропуснем в резултата, предоставен от метода `$form->getValues()` или предаден на обработващите програми. Това е подходящо за различни пароли за валидиране, полета за защита от спам и др. +Ако попълнената от потребителя стойност не ни интересува, можем чрез `setOmitted()` да я пропуснем от резултата на метода `$form->getValues()` или от данните, предавани на хендлърите. Това е полезно за различни пароли за проверка, антиспам елементи и т.н. ```php -$form->addPassword('passwordVerify', 'Повторите пароль:') - ->setRequired('Введите пароль ещё раз, чтобы проверить опечатку') - ->addRule($form::Equal, 'Несоответствие пароля', $form['password']) +$form->addPassword('passwordVerify', 'Парола за проверка:') + ->setRequired('Моля, въведете паролата отново за проверка') + ->addRule($form::Equal, 'Паролите не съвпадат', $form['password']) ->setOmitted(); ``` -Деактивиране на входните елементи .[#toc-disabling-inputs] ----------------------------------------------------------- +Деактивиране на елементи +======================== -За да деактивирате въвеждането, можете да се обадите на `setDisabled()`. Такова поле не може да се редактира от потребителя. +Елементите могат да се деактивират чрез `setDisabled()`. Такъв елемент потребителят не може да редактира. ```php -$form->addText('username', 'Имя пользователя:') +$form->addText('username', 'Потребителско име:') ->setDisabled(); ``` -Обърнете внимание, че браузърът изобщо не изпраща забранените полета към сървъра, така че те дори няма да бъдат открити в данните, върнати от функцията `$form->getValues()`. +Деактивираните елементи браузърът изобщо не изпраща на сървъра, т.е. няма да ги намерите и в данните, върнати от функцията `$form->getValues()`. Ако обаче зададете `setOmitted(false)`, Nette ще включи в тези данни тяхната стойност по подразбиране. -Ако задавате стойност по подразбиране за дадено поле, трябва да го направите само след като сте го деактивирали: +При извикване на `setDisabled()` от съображения за сигурност **се изтрива стойността на елемента**. Ако задавате стойност по подразбиране, е необходимо да го направите след неговото деактивиране: ```php -$form->addText('username', 'Имя пользователя:') +$form->addText('username', 'Потребителско име:') ->setDisabled() ->setDefaultValue($userName); ``` +Алтернатива на деактивираните елементи са елементите с HTML атрибут `readonly`, които браузърът изпраща на сървъра. Въпреки че елементът е само за четене, е **важно да се осъзнае**, че неговата стойност все още може да бъде модифицирана или подправена от атакуващ. + -Потребителски контроли .[#toc-custom-controls] -============================================== +Персонализирани елементи +======================== -В допълнение към широкия набор от вградени контроли на формуляра можете да добавяте свои собствени контроли, както следва +Освен широката гама от вградени елементи на формуляр, можете да добавяте към формуляра собствени елементи по следния начин: ```php $form->addComponent(new DateInput('Дата:'), 'date'); -// алтернативен синтаксис: $form['date'] = new DateInput('date:'); +// алтернативен синтаксис: $form['date'] = new DateInput('Дата:'); ``` .[note] -Формата е потомък на класа [Container | component-model:#Container], а елементите са потомци на [Component | component-model:#Component]. +Формулярът е наследник на класа [Container |component-model:#Container], а отделните елементи са наследници на [Component |component-model:#Component]. -Има начин да се дефинират нови методи на формуляра за добавяне на персонализирани елементи (например '$form->addZip()'). Това са така наречените методи за разширение. Недостатъкът е, че подсказките за код в редакторите няма да работят за тях. +Съществува начин да се дефинират нови методи на формуляра, служещи за добавяне на собствени елементи (напр. `$form->addZip()`). Това са т.нар. extension methods. Недостатъкът е, че за тях няма да работи подсказването в редакторите. ```php use Nette\Forms\Container; -// метод addZip(string $name, string $label = null) -Container::extensionMethod('addZip', function (Container $form, string $name, string $label = null) { +// добавяме метод addZip(string $name, ?string $label = null) +Container::extensionMethod('addZip', function (Container $form, string $name, ?string $label = null) { return $form->addText($name, $label) - ->addRule($form::Pattern, 'Не менее 5 номеров', '[0-9]{5}'); + ->addRule($form::Pattern, 'Поне 5 цифри', '[0-9]{5}'); }); -//използване -$form->addZip('zip', 'ZIP код:'); +// използване +$form->addZip('zip', 'Пощенски код:'); ``` -Ниски полета .[#toc-low-level-fields] -===================================== +Елементи на ниско ниво +====================== -Не е необходимо да извиквате '$form->addXyz()', за да добавите елемент към формуляра. Вместо това елементите на формуляра могат да се въвеждат изключително в шаблони. Това е полезно, ако трябва да създавате динамични елементи, например: +Могат да се използват и елементи, които записваме само в шаблона и не ги добавяме към формуляра с някой от методите `$form->addXyz()`. Когато например изписваме записи от база данни и предварително не знаем колко ще бъдат и какви ще бъдат техните ID, и искаме при всеки ред да покажем чекбокс или радио бутон, е достатъчно да го кодираме в шаблона: ```latte {foreach $items as $item} @@ -450,13 +547,13 @@ $form->addZip('zip', 'ZIP код:'); {/foreach} ``` -Стойностите могат да бъдат изтеглени след подаването им: +А след изпращане стойността установяваме: ```php $data = $form->getHttpData($form::DataText, 'sel[]'); $data = $form->getHttpData($form::DataText | $form::DataKeys, 'sel[]'); ``` -В първия параметър посочвате типа на елемента (`DataFile` за `type=file`, `DataLine` за едноредови входове като `text`, `password` или `email` и `DataText` за останалите). Вторият параметър съответства на атрибута на HTML `name`. Ако искате да запазите ключовете, можете да комбинирате първия параметър с `DataKeys`. Това е полезно за `select`, `radioList` или `checkboxList`. +където първият параметър е типът на елемента (`DataFile` за `type=file`, `DataLine` за едноредови входове като `text`, `password`, `email` и др. и `DataText` за всички останали), а вторият параметър `sel[]` съответства на HTML атрибута name. Типът на елемента можем да комбинираме със стойността `DataKeys`, която запазва ключовете на елементите. Това е полезно особено за `select`, `radioList` и `checkboxList`. -Функцията `getHttpData()` връща обработените данни. В този случай това винаги ще бъде масив от валидни низове UTF-8, независимо от това какво е изпратил атакуващият чрез формуляра. Това е алтернатива на директната обработка на `$_POST` или `$_GET`, ако искате да получавате сигурни данни. +Същественото е, че `getHttpData()` връща санирана стойност, в този случай това винаги ще бъде масив от валидни UTF-8 низове, независимо какво би се опитал да подхвърли атакуващ на сървъра. Това е аналог на директната работа с `$_POST` или `$_GET`, но със съществената разлика, че винаги връща чисти данни, така както сте свикнали при стандартните елементи на Nette формулярите. diff --git a/forms/bg/in-presenter.texy b/forms/bg/in-presenter.texy index ae2285540f..1778ed84f5 100644 --- a/forms/bg/in-presenter.texy +++ b/forms/bg/in-presenter.texy @@ -1,36 +1,36 @@ -Формуляри за водещите -********************* +Форми в презентерите +******************** .[perex] -Nette Forms значително опростява създаването и обработката на уеб формуляри. В тази глава ще научите как да използвате формуляри в презентаторите. +Nette Forms значително улесняват създаването и обработката на уеб форми. В тази глава ще се запознаете с използването на форми в презентерите. -Ако искате да ги използвате напълно самостоятелно, без останалата част от рамката, има ръководство за [самостоятелни форми |standalone]. +Ако се интересувате как да ги използвате напълно самостоятелно без останалата част от framework-а, ръководството за [самостоятелна употреба |standalone] е за вас. -Първа форма .[#toc-first-form] -============================== +Първа форма +=========== -Ще се опитаме да напишем прост формуляр за регистрация. Кодът ще изглежда по следния начин: +Нека опитаме да напишем проста форма за регистрация. Кодът ѝ ще бъде следният: ```php use Nette\Application\UI\Form; $form = new Form; -$form->addText('name', 'Имя:'); -$form->addPassword('password', 'Пароль:'); -$form->addSubmit('send', 'Зарегистрироваться'); +$form->addText('name', 'Име:'); +$form->addPassword('password', 'Парола:'); +$form->addSubmit('send', 'Регистрирай се'); $form->onSuccess[] = [$this, 'formSucceeded']; ``` -и в браузъра резултатът трябва да изглежда така: +и ще се покаже в браузъра по следния начин: -[* form-en.webp *] +[* form-cs.webp *] -Формулярът в презентатора е обект от клас `Nette\Application\UI\Form`, неговият предшественик `Nette\Forms\Form` е за офлайн употреба. Добавихме към него полета за име, парола и бутон за изпращане. И накрая, в реда с `$form->onSuccess` се казва, че след подаване и успешно валидиране трябва да се извика методът `$this->formSucceeded()`. +Формата в презентера е обект от класа `Nette\Application\UI\Form`, неговият предшественик `Nette\Forms\Form` е предназначен за самостоятелна употреба. Добавихме към нея така наречените елементи име, парола и бутон за изпращане. И накрая, редът с `$form->onSuccess` казва, че след изпращане и успешна валидация трябва да се извика методът `$this->formSucceeded()`. -От гледна точка на водещия формулярът е общ компонент. Затова той се третира като компонент и се включва в презентатора чрез [метода factory |application:components#Factory-Methods]. Това ще изглежда по следния начин: +От гледна точка на презентера, формата е обикновен компонент. Затова се третира като компонент и се включва в презентера чрез [фабричен метод |application:components#Фабрични методи]. Ще изглежда така: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} use Nette; use Nette\Application\UI\Form; @@ -40,102 +40,99 @@ class HomePresenter extends Nette\Application\UI\Presenter { $form = new Form; $form->addText('name', 'Име:'); - $form->addPassword('password', 'Password:'); - $form->addSubmit('send', 'Register'); + $form->addPassword('password', 'Парола:'); + $form->addSubmit('send', 'Регистрирай се'); $form->onSuccess[] = [$this, 'formSucceeded']; return $form; } public function formSucceeded(Form $form, $data): void { - // тук ще обработваме данните, изпратени от формата + // тук обработваме данните, изпратени от формата // $data->name съдържа името - // $data->password съдържа парола - $this->flashMessage('Регистрирахте се успешно.'); + // $data->password съдържа паролата + $this->flashMessage('Бяхте успешно регистриран.'); $this->redirect('Home:'); } } ``` -А визуализирането в шаблона се извършва с помощта на тага `{control}`: +И в шаблона рендираме формата с тага `{control}`: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} <h1>Регистрация</h1> {control registrationForm} ``` -И това е всичко :-) Имаме функционална и отлично [защитена |#Защита от уязвимостей] форма. +И това всъщност е всичко :-) Имаме функционална и перфектно [защитена |#Защита от уязвимости] форма. -Сега сигурно си мислите, че това е станало твърде бързо, и се чудите как е възможно да се извика методът `formSucceeded()` и какви параметри получава. Разбира се, че сте прави, заслужава обяснение. +И сега вероятно си мислите, че това беше твърде прибързано, чудите се как е възможно да се извика методът `formSucceeded()` и какви са параметрите, които получава. Разбира се, прави сте, това заслужава обяснение. -Нете измисли готин механизъм, който нарекохме [холивудски стил |application:components#Hollywood-Style]. Вместо постоянно да питате дали нещо се е случило ("дали формулярът е изпратен?", "дали е изпратен правилно?" или "дали е манипулиран?"), вие казвате на рамката "когато формулярът е изпратен правилно, извикайте този метод". Ако програмирате на JavaScript, този стил на програмиране ви е познат. Пишете функции, които се извикват при настъпване на определено [събитие |nette:glossary#Events]. И езикът им предава съответните аргументи. +Nette всъщност идва със свеж механизъм, който наричаме [Холивудски стил |application:components#Hollywood style]. Вместо вие като разработчик постоянно да питате дали нещо се е случило („формата изпратена ли е?“, „изпратена ли е валидно?“, „фалшифицирана ли е?“), казвате на framework-а „когато формата е валидно попълнена, извикай този метод“ и оставяте останалата работа на него. Ако програмирате на JavaScript, този стил на програмиране ви е добре познат. Пишете функции, които се извикват, когато настъпи определено [събитие |nette:glossary#Събития events]. И езикът им предава съответните аргументи. -Ето как е конструиран кодът на презентатора по-горе. Масивът `$form->onSuccess` е списък с обратни връзки на PHP, които Nette ще извика, когато формулярът е изпратен и правилно попълнен. -Като част от [жизнения цикъл на презентатора |application:presenters#Life-Cycle-of-Presenter], това се нарича сигнал, така че те се извикват след метода `action*` и преди метода `render*`. -И предава на всяко обратно извикване самата форма в първия параметър и изпратените данни като обект [ArrayHash |utils:arrays#ArrayHash] във втория. Можете да пропуснете първия параметър, ако нямате нужда от обекта на формата. Вторият параметър може да бъде още по-удобен, но за това ще стане дума [по-късно |#Mapping-to-Classes]. +Точно така е изграден и горният код на презентера. Масивът `$form->onSuccess` представлява списък от PHP callback-ове, които Nette извиква в момента, когато формата е изпратена и правилно попълнена (т.е. е валидна). В рамките на [жизнения цикъл на презентера |application:presenters#Жизнен цикъл на презентера] това е така нареченият сигнал, така че те се извикват след метода `action*` и преди метода `render*`. И на всеки callback предава като първи параметър самата форма, а като втори - изпратените данни под формата на обект [ArrayHash |utils:arrays#ArrayHash]. Можете да пропуснете първия параметър, ако не се нуждаете от обекта на формата. А вторият параметър може да бъде по-хитър, но за това [по-късно |#Мапване към класове]. -Обектът `$data` съдържа свойства `name` и `password` с данни, въведени от потребителя. Обикновено изпращаме данните директно за по-нататъшна обработка, която може да бъде например вмъкване в база данни. При обработката обаче може да възникне грешка, например потребителското име вече е заето. В този случай изпращаме грешката обратно към формата с `addError()` и я оставяме да се прерисува със съобщение за грешка: +Обектът `$data` съдържа ключовете `name` и `password` с данните, които потребителят е попълнил. Обикновено данните се изпращат директно за по-нататъшна обработка, което може да бъде например вмъкване в база данни. По време на обработката обаче може да възникне грешка, например потребителското име вече е заето. В такъв случай предаваме грешката обратно към формата чрез `addError()` и я оставяме да се рендира отново, заедно със съобщението за грешка. ```php -$form->addError('Извините, имя пользователя уже используется.'); +$form->addError('Извиняваме се, потребителското име вече се използва.'); ``` -В допълнение към `onSuccess`, има и `onSubmit`: обратните извиквания се извикват винаги след изпращането на формуляра, дори ако той не е попълнен правилно. И накрая, `onError`: обратните повиквания се извикват само ако подаването е невалидно. Те се извикват дори ако обезсилим формуляр в `onSuccess` или `onSubmit`, като използваме `addError()`. +Освен `onSuccess` съществува и `onSubmit`: callback-овете се извикват винаги след изпращане на формата, дори ако тя не е попълнена правилно. И също `onError`: callback-овете се извикват само ако изпращането не е валидно. Те се извикват дори ако във `onSuccess` или `onSubmit` направим формата невалидна чрез `addError()`. -След като обработим формуляра, ще ви пренасочим към следващата страница. Това предотвратява повторното изпращане на формуляра по невнимание, когато щракнете върху *опресняване*, *назад* или преместите историята на браузъра си. +След обработка на формата пренасочваме към следващата страница. Това предотвратява нежелано повторно изпращане на формата чрез бутона *обнови*, *назад* или чрез движение в историята на браузъра. -Опитайте да добавите повече [контроли на формуляра |controls]. +Опитайте да добавите и други [елементи на формата |controls]. -Достъп до контролите .[#toc-access-to-controls] -=============================================== +Достъп до елементите +==================== -Формулярът е компонент на презентатора, в нашия случай с име `registrationForm` (по името на фабричния метод `createComponentRegistrationForm`), така че навсякъде в презентатора можете да получите достъп до формуляра, като използвате +Формата е компонент на презентера, в нашия случай наречена `registrationForm` (според името на фабричния метод `createComponentRegistrationForm`), така че навсякъде в презентера можете да получите достъп до формата чрез: ```php $form = $this->getComponent('registrationForm'); -//алтернативен синтаксис: $form = $this['registrationForm']; +// алтернативен синтаксис: $form = $this['registrationForm']; ``` -Също така отделните контроли на формата са компоненти, така че можете да имате достъп до тях по същия начин: +Отделните елементи на формата също са компоненти, така че можете да получите достъп до тях по същия начин: ```php $input = $form->getComponent('name'); // или $input = $form['name']; $button = $form->getComponent('send'); // или $button = $form['send']; ``` -Контролите се изтриват с помощта на функцията за изтриване: +Елементите се премахват с помощта на unset: ```php unset($form['name']); ``` -Правила за валидиране .[#toc-validation-rules] -============================================== +Правила за валидация +==================== -Думата *valid* е използвана няколко пъти, но формулярът все още няма правила за валидиране. Нека поправим това. +Споменахме думата *валидна*, но формата все още няма правила за валидация. Нека поправим това. -Името ще бъде задължително, затова ще го маркираме с метода `setRequired()`, чийто аргумент е текстът на съобщението за грешка, което ще се покаже, ако потребителят не успее да го попълни. Ако не е посочен аргумент, се използва съобщението за грешка по подразбиране. +Името ще бъде задължително, затова го маркираме с метода `setRequired()`, чийто аргумент е текстът на съобщението за грешка, което ще се покаже, ако потребителят не попълни името. Ако не посочим аргумент, ще се използва съобщението за грешка по подразбиране. ```php -$form->addText('name', 'Имя:') - ->setRequired('Пожалуйста, введите имя.'); +$form->addText('name', 'Име:') + ->setRequired('Моля, въведете име'); ``` -Ако се опитате да изпратите формуляра, без да сте го попълнили, ще се появи съобщение за грешка и браузърът или сървърът ще отхвърлят формуляра, докато не го попълните. +Опитайте да изпратите формата без попълнено име и ще видите, че ще се покаже съобщение за грешка и браузърът или сървърът ще я отхвърлят, докато не попълните полето. -В същото време не можете да заблудите системата, като например въведете само интервали в полето за въвеждане. В никакъв случай. Nette автоматично подрязва левия и десния бял интервал. Опитайте, това е нещо, което винаги трябва да правите при въвеждането на всеки отделен ред, но често се забравя. Nette прави това автоматично. (Можете да се опитате да измамите формата и да изпратите многоредов низ като име. Дори и тук Nette няма да се заблуди и прекъсванията на редовете ще бъдат заменени с интервали). +В същото време няма да измамите системата, като напишете само интервали в полето. Няма начин. Nette автоматично премахва водещите и крайните интервали. Опитайте. Това е нещо, което винаги трябва да правите с всеки едноредов вход, но често се забравя. Nette го прави автоматично. (Можете да опитате да измамите формата и да изпратите многоредов низ като име. Дори тук Nette няма да се обърка и ще преобразува новите редове в интервали.) -Формулярът винаги се проверява от страна на сървъра, но се генерира и проверка на JavaScript, която се извършва бързо и потребителят веднага разбира за грешката, без да се налага да изпраща формуляра към сървъра. С това се занимава скриптът `netteForms.js`. -Вмъкнете го в шаблона за оформление: +Формата винаги се валидира от страна на сървъра, но също така се генерира JavaScript валидация, която се извършва мигновено и потребителят научава за грешката веднага, без да е необходимо да изпраща формата до сървъра. За това отговаря скриптът `netteForms.js`. Вмъкнете го в шаблона на лейаута: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Ако разгледате изходния код на страницата с формуляра, можете да забележите, че Nette вмъква задължителни полета в елементи с клас CSS `required`. Опитайте се да добавите следния стил към шаблона и тагът "Име" ще бъде червен. Елегантно маркираме задължителните полета за потребителите: +Ако погледнете изходния код на страницата с формата, може да забележите, че Nette вмъква задължителните елементи в елементи с CSS клас `required`. Опитайте да добавите следния стил в шаблона и етикетът „Име“ ще стане червен. Така елегантно ще маркираме задължителните елементи за потребителите: ```latte <style> @@ -143,96 +140,96 @@ $form->addText('name', 'Имя:') </style> ``` -Допълнителни правила за валидиране ще бъдат добавени чрез метода `addRule()`. Първият параметър е правилото, вторият е текстът на съобщението за грешка, последван от незадължителен аргумент за правило за валидиране. Какво означава това? +Добавяме допълнителни правила за валидация с метода `addRule()`. Първият параметър е правилото, вторият отново е текстът на съобщението за грешка, а може да последва и аргумент на правилото за валидация. Какво означава това? -Формулярът ще получи още един незадължителен елемент за въвеждане *възраст*, като условието е той да бъде число (`addInteger()`) и да е в определени граници (`$form::Range`). И тук ще използваме третия аргумент `addRule()`, самия диапазон: +Ще разширим формата с ново незадължително поле „възраст“, което трябва да бъде цяло число (`addInteger()`) и освен това в допустим диапазон (`$form::Range`). И тук ще използваме третия параметър на метода `addRule()`, с който ще предадем на валидатора необходимия диапазон като двойка `[от, до]`: ```php -$form->addInteger('age', 'Возраст:') - ->addRule($form::Range, 'Вы должны быть старше 18 лет и иметь возраст до 120 лет.', [18, 120]); +$form->addInteger('age', 'Възраст:') + ->addRule($form::Range, 'Възрастта трябва да е между 18 и 120', [18, 120]); ``` .[tip] -Ако потребителят не попълни полето, правилата за валидиране няма да бъдат проверени, тъй като полето е незадължително. +Ако потребителят не попълни полето, правилата за валидация няма да бъдат проверени, тъй като елементът е незадължителен. -Очевидно е, че тук има място за малко преработване. В съобщението за грешка и в третия параметър числата са изброени двойно, което не е идеално. Ако създаваме [многоезичен формуляр |rendering#translating] и съобщението, съдържащо числата, трябва да бъде преведено на няколко езика, това би затруднило промяната на стойностите. По тази причина можем да използваме заместващи символи `%d`: +Тук възниква възможност за малък рефакторинг. В съобщението за грешка и в третия параметър числата са посочени дублирано, което не е идеално. Ако създавахме [многоезични форми |rendering#Превод] и съобщението, съдържащо числа, беше преведено на няколко езика, евентуалната промяна на стойностите би била затруднена. Поради тази причина е възможно да се използват плейсхолдъри `%d` и Nette ще допълни стойностите: ```php - ->addRule($form::Range, 'Вы должны быть старше %d лет и иметь возраст до %d лет.', [18, 120]); + ->addRule($form::Range, 'Възрастта трябва да бъде от %d до %d години', [18, 120]); ``` -Нека се върнем към полето *парола*, да го направим *задължително* и да проверим минималната дължина на паролата (`$form::MinLength`), като отново използваме заместители в съобщението: +Да се върнем към елемента `password`, който също ще направим задължителен и ще проверим минималната дължина на паролата (`$form::MinLength`), отново с използване на плейсхолдър: ```php -$form->addPassword('password', 'Пароль:') - ->setRequired('Выберите пароль') - ->addRule($form::MinLength, 'Ваш пароль должен быть длиной не менее %d', 8); +$form->addPassword('password', 'Парола:') + ->setRequired('Изберете парола') + ->addRule($form::MinLength, 'Паролата трябва да съдържа поне %d знака', 8); ``` -Ще добавим поле към формуляра `passwordVerify`, в което потребителят въвежда отново паролата, за да я валидира. С помощта на правила за валидиране проверяваме дали двете пароли (`$form::Equal`) са еднакви. И даваме връзка към първата парола като аргумент, като използваме [квадратни скоби |#Access-to-Controls]: +Ще добавим към формата и поле `passwordVerify`, където потребителят ще въведе паролата още веднъж, за проверка. С помощта на правилата за валидация ще проверим дали двете пароли са еднакви (`$form::Equal`). И като параметър ще дадем препратка към първата парола с помощта на [квадратни скоби |#Достъп до елементите]: ```php -$form->addPassword('passwordVerify', 'Повторите пароль:') - ->setRequired('Введите пароль ещё раз, чтобы проверить опечатку') - ->addRule($form::Equal, 'Несоответствие пароля', $form['password']) +$form->addPassword('passwordVerify', 'Парола за проверка:') + ->setRequired('Моля, въведете паролата отново за проверка') + ->addRule($form::Equal, 'Паролите не съвпадат', $form['password']) ->setOmitted(); ``` -С помощта на `setOmitted()`, маркираме елемент, чиято стойност всъщност не ни интересува и който съществува само за валидиране. Стойността му не се предава на `$data`. +С помощта на `setOmitted()` маркирахме елемент, чиято стойност всъщност не ни интересува и който съществува само с цел валидация. Стойността не се предава на `$data`. -Имаме напълно функционален формуляр с валидиране в PHP и JavaScript. Възможностите за валидиране в Nette са много по-широки, можете да създавате условия, да показвате и скривате части от страницата в зависимост от тях и т.н. Можете да научите повече за всичко това в главата [Проверка на формата |validation]. +С това имаме напълно функционална форма с валидация както в PHP, така и в JavaScript. Възможностите за валидация на Nette са много по-широки, могат да се създават условия, според тях да се показват и скриват части от страницата и т.н. Всичко ще научите в главата за [валидация на форми |validation]. -Стойности по подразбиране .[#toc-default-values] -================================================ +Стойности по подразбиране +========================= -Често задаваме стойности по подразбиране за контролите на формуляра: +Обикновено задаваме стойности по подразбиране на елементите на формата: ```php $form->addEmail('email', 'Имейл') ->setDefaultValue($lastUsedEmail); ``` -Често е полезно да зададете стойности по подразбиране за всички контроли едновременно. Например, когато формулярът се използва за редактиране на записи. Прочитаме запис от базата данни и го задаваме като стойност по подразбиране: +Често е полезно да се зададат стойности по подразбиране на всички елементи едновременно. Например, когато формата се използва за редактиране на записи. Прочитаме записа от базата данни и задаваме стойностите по подразбиране: ```php //$row = ['name' => 'John', 'age' => '33', /* ... */]; $form->setDefaults($row); ``` -Извикайте `setDefaults()` след дефиниране на контролите. +Извиквайте `setDefaults()` след дефинирането на елементите. -Форма за показване .[#toc-rendering-the-form] -============================================= +Рендиране на формата +==================== -По подразбиране формулярът се показва като таблица. Отделните контроли следват основните насоки за достъпност на уеб страниците. Всички етикети се генерират като елементи `<label>` и са свързани с техните елементи, като щракването върху маркер ще премести курсора върху съответния елемент. +По подразбиране формата се рендира като таблица. Отделните елементи отговарят на основното правило за достъпност - всички етикети са написани като `<label>` и са свързани със съответния елемент на формата. При кликване върху етикета курсорът автоматично се появява в полето на формата. -Можем да зададем всякакви атрибути на HTML за всеки елемент. Например, добавете заместител: +На всеки елемент можем да задаваме произволни HTML атрибути. Например, да добавим placeholder: ```php -$form->addInteger('age', 'Возраст:') - ->setHtmlAttribute('placeholder', 'Пожалуйста, заполните возраст'); +$form->addInteger('age', 'Възраст:') + ->setHtmlAttribute('placeholder', 'Моля, попълнете възрастта'); ``` -Всъщност има много начини за визуализиране на формуляри, повече подробности в глава [Изобразяване" |rendering]. +Има наистина много начини за рендиране на форма, така че на това е посветена [отделна глава за рендиране |rendering]. -Съпоставяне с класове .[#toc-mapping-to-classes] -================================================ +Мапване към класове +=================== -Нека се върнем към обработката на данни от формуляри. Методът `getValues()` връща подадените данни като обект `ArrayHash`. Тъй като това е общ клас, нещо като `stdClass`, при работа с него ще ни липсват някои удобни функции, като например допълване на кода за свойства в редактори или статичен анализ на кода. Това може да бъде решено чрез създаване на отделен клас за всяка форма, чиито свойства представляват отделните контроли. Например: +Да се върнем към метода `formSucceeded()`, който във втория параметър `$data` получава изпратените данни като обект `ArrayHash`. Тъй като това е генеричен клас, нещо като `stdClass`, при работа с него ще ни липсва известно удобство, като например подсказване на свойствата в редакторите или статичен анализ на кода. Това може да се реши, като за всяка форма имаме конкретен клас, чиито свойства представляват отделните елементи. Напр.: ```php class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -От PHP 8.0 можете да използвате този елегантен запис, който използва конструктор: +Алтернативно можете да използвате конструктор: ```php class RegistrationFormData @@ -246,27 +243,29 @@ class RegistrationFormData } ``` -Как казвате на Nette да връща данни като обекти от този клас? По-лесно е, отколкото си мислите. Всичко, което трябва да направите, е да посочите класа като параметър от тип `$data` в обработващото устройство: +Свойствата на класа с данни могат да бъдат и enum-и и те ще бъдат автоматично мапнати. .{data-version:3.2.4} + +Как да кажем на Nette да ни връща данните като обекти от този клас? По-лесно, отколкото си мислите. Достатъчно е само да посочите класа като тип на параметъра `$data` в обработващия метод: ```php public function formSucceeded(Form $form, RegistrationFormData $data): void { - // $name - екземпляр RegistrationFormData + // $data е инстанция на RegistrationFormData $name = $data->name; // ... } ``` -Можете също така да посочите `array` като тип и тогава данните ще бъдат предадени като масив. +Като тип може да се посочи и `array` и тогава данните ще бъдат предадени като масив. -По същия начин можете да използвате метода `getValues()`, на който като параметър подаваме името на класа или обекта, който трябва да се хидратира: +По подобен начин може да се използва и функцията `getValues()`, на която предаваме името на класа или обекта за хидратиране като параметър: ```php $data = $form->getValues(RegistrationFormData::class); $name = $data->name; ``` -Ако формулярите се състоят от структура на няколко нива, съставена от контейнери, създайте отделен клас за всеки от тях: +Ако формите образуват многостепенна структура, съставена от контейнери, създайте отделен клас за всеки: ```php $form = new Form; @@ -283,32 +282,34 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` -От типа на свойството `$person` съпоставянето ще разбере, че трябва да съпостави контейнера с класа `PersonFormData`. Ако свойството ще съдържа масив от контейнери, посочете типа `array` и предайте класа, който ще бъде съпоставен директно с контейнера: +Мапването след това разпознава от типа на свойството `$person`, че трябва да мапне контейнера към класа `PersonFormData`. Ако свойството съдържа масив от контейнери, посочете типа `array` и предайте класа за мапване директно на контейнера: ```php $person->setMappedType(PersonFormData::class); ``` +Можете да генерирате дизайна на класа с данни за формата с помощта на метода `Nette\Forms\Blueprint::dataClass($form)`, който ще го изведе на страницата на браузъра. След това е достатъчно да маркирате кода с кликване и да го копирате в проекта. .{data-version:3.1.15} + -Множество бутони за изпращане .[#toc-multiple-submit-buttons] -============================================================= +Множество бутони +================ -Ако формулярът съдържа повече от един бутон, обикновено трябва да се разграничи кой бутон е бил щракнат. Можем да създадем собствена функция за всеки бутон. Задайте го като обработващо устройство за [събитието |nette:glossary#Events] `onClick`: +Ако формата има повече от един бутон, обикновено трябва да разграничим кой от тях е бил натиснат. Можем да създадем собствена обработваща функция за всеки бутон. Ще я зададем като хендлър за [събитието |nette:glossary#Събития events] `onClick`: ```php -$form->addSubmit('save', 'Сохранить') +$form->addSubmit('save', 'Запази') ->onClick[] = [$this, 'saveButtonPressed']; -$form->addSubmit('delete', 'Удалить') +$form->addSubmit('delete', 'Изтрий') ->onClick[] = [$this, 'deleteButtonPressed']; ``` -Тези обслужващи програми се извикват също само ако формулярът е валиден, както в случая със събитието `onSuccess`. Разликата е, че първият параметър може да бъде обектът на бутона за изпращане, а не формата, в зависимост от типа, който сте задали: +Тези хендлъри се извикват само в случай на валидно попълнена форма, точно както при събитието `onSuccess`. Разликата е, че като първи параметър вместо формата може да се предаде изпращащият бутон, в зависимост от типа, който посочите: ```php public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) @@ -318,62 +319,61 @@ public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) } ``` -Когато формулярът се изпрати с бутона <kbd>Enter</kbd>, той се третира по същия начин, както ако беше изпратен с първия бутон. +Когато формата се изпрати с бутона <kbd>Enter</kbd>, се счита, че е изпратена с първия бутон. -Събитието onAnchor .[#toc-event-onanchor] -========================================= +Събитие onAnchor +================ -Когато създавате формуляр с фабричен метод (например `createComponentRegistrationForm`), той все още не знае дали е бил изпратен и с какви данни е бил изпратен. Но има случаи, в които е необходимо да знаем изпратените стойности - може би от тях зависи външният вид на формуляра или се използват за зависими списъци и т.н. +Когато изграждаме формата във фабричния метод (като например `createComponentRegistrationForm`), тя все още не знае дали е била изпратена, нито с какви данни. Но има случаи, когато трябва да знаем изпратените стойности, например по-нататъшният вид на формата зависи от тях, или ни трябват за зависими selectbox-ове и т.н. -Затова можем да направим така, че кодът, създаващ формата, да се извиква, когато формата е прикачена, т.е. вече е свързана с презентатора и знае изпратените данни. Ще поставим този код в масива `$onAnchor`: +Затова можете да оставите частта от кода, която изгражда формата, да бъде извикана едва в момента, когато тя е т. нар. „закотвена“, т.е. вече е свързана с презентера и знае своите изпратени данни. Предаваме такъв код в масива `$onAnchor`: ```php -$country = $form->addSelect('country', 'Country:', $this->model->getCountries()); -$city = $form->addSelect('city', 'City:'); +$country = $form->addSelect('country', 'Държава:', $this->model->getCountries()); +$city = $form->addSelect('city', 'Град:'); $form->onAnchor[] = function () use ($country, $city) { - // тази функция ще бъде извикана, когато формата разпознае данните, с които е изпратена - // така че можете да използвате метода getValue(). + // тази функция ще се извика, когато формата знае дали е била изпратена и с какви данни + // следователно може да се използва методът getValue() $val = $country->getValue(); $city->setItems($val ? $this->model->getCities($val) : []); }; ``` -Защита от уязвимост .[#toc-vulnerability-protection] -==================================================== +Защита от уязвимости +==================== -Рамката на Nette полага големи усилия за гарантиране на сигурността и тъй като формулярите са най-често срещаната форма на въвеждане на данни от потребителя, формулярите на Nette са толкова добри, колкото и непробиваеми. +Nette Framework поставя голям акцент върху сигурността и затова стриктно се грижи за добрата защита на формите. Прави го напълно прозрачно и не изисква ръчна настройка. -В допълнение към защитата на формулярите срещу атаки от известни уязвимости, като например [Cross-Site Scripting (XSS) |nette:glossary#Cross-Site-Scripting-XSS] и [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site-Request-Forgery-CSRF], тя изпълнява много малки задачи по сигурността, за които вече не е необходимо да мислите. +Освен че защитава формите от атаки [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] и [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF], той извършва много малки защити, за които вече не е нужно да мислите. -Например той филтрира всички контролни символи от входните данни и валидира кодирането UTF-8, така че данните от формуляра ви винаги ще бъдат чисти. За полетата за избор и радиосписъците се проверява дали избраните елементи са наистина от предлаганите елементи и дали няма фалшиви. Вече споменахме, че при въвеждане на текст от един ред тя премахва символите от края на реда, които атакуващият може да изпрати там. При многоредово въвеждане се нормализират символите в края на реда. И така нататък. +Например, филтрира всички контролни знаци от входовете и проверява валидността на UTF-8 кодирането, така че данните от формата винаги ще бъдат чисти. При select box-ове и radio list-ове проверява дали избраните елементи са наистина от предлаганите и не е имало фалшификация. Вече споменахме, че при едноредови текстови входове премахва знаците за край на ред, които нападателят може да е изпратил. При многоредови входове пък нормализира знаците за край на ред. И така нататък. -Nette елиминира за вас уязвимостите в сигурността, за които повечето програмисти дори не подозират, че съществуват. +Nette решава вместо вас рисковете за сигурността, за които много програмисти дори не подозират, че съществуват. -Споменатата по-горе CSRF атака се състои в това, че нападателят примамва жертвата да посети страница, която безшумно изпълнява заявка в браузъра на жертвата към сървъра, в който жертвата е влязла в момента, и сървърът приема, че заявката е направена от жертвата по нейна собствена воля. По този начин Nette предотвратява изпращането на формуляра чрез POST от друг домейн. Ако по някаква причина искате да деактивирате защитата и да позволите формулярът да бъде изпратен от друг домейн, използвайте +Споменатата CSRF атака се състои в това, че нападателят примамва жертвата към страница, която незабелязано в браузъра на жертвата изпълнява заявка към сървъра, на който жертвата е влязла, и сървърът смята, че заявката е изпълнена от жертвата по нейна воля. Затова Nette предотвратява изпращането на POST форма от друг домейн. Ако по някаква причина искате да изключите защитата и да позволите изпращането на формата от друг домейн, използвайте: ```php -$form->allowCrossOrigin(); // ПРЕДУПРЕЖДЕНИЕ! Деактивира защитата! +$form->allowCrossOrigin(); // ВНИМАНИЕ! Изключва защитата! ``` -Тази защита използва бисквитка на SameSite с име `_nss`. Защитата с бисквитките на SameSite може да не е 100% надеждна, затова е добре да включите защитата с токени: +Тази защита използва SameSite cookie, наречена `_nss`. Защитата чрез SameSite cookie може да не е 100% надеждна, затова е препоръчително да включите и защита чрез токен: ```php $form->addProtection(); ``` -Силно препоръчително е да приложите тази защита към формулярите в административната част на приложението, които модифицират чувствителни данни. Рамката се защитава от CSRF атака чрез генериране и валидиране на токен за удостоверяване, който се съхранява в сесията (аргументът е съобщение за грешка, което се показва, ако токенът е изтекъл). Затова е необходимо да бъде стартирана сесия, преди да бъде показан формулярът. В административната част на сайта сесията обикновено вече е започнала, тъй като потребителят е влязъл в системата. -В противен случай стартирайте сесията, като използвате метода `Nette\Http\Session::start()`. +Препоръчваме да защитавате по този начин формите в административната част на сайта, които променят чувствителни данни в приложението. Framework-ът се защитава срещу CSRF атака чрез генериране и проверка на оторизационен токен, който се съхранява в сесията. Затова е необходимо да имате отворена сесия преди показването на формата. В административната част на сайта обикновено сесията вече е стартирана поради влизането на потребителя. В противен случай стартирайте сесията с метода `Nette\Http\Session::start()`. -Използване на един формуляр в няколко презентатора .[#toc-using-one-form-in-multiple-presenters] -================================================================================================ +Същата форма в множество презентери +=================================== -Ако трябва да използвате един формуляр в няколко презентатора, препоръчваме ви да създадете фабрика за него, която след това да предадете на презентатора. Подходящо място за такъв клас е например директорията `app/Forms`. +Ако трябва да използвате една и съща форма в множество презентери, препоръчваме да създадете фабрика за нея, която след това да предадете на презентера. Подходящо място за такъв клас е например директорията `app/Forms`. -Един фабричен клас може да изглежда по следния начин: +Фабричният клас може да изглежда например така: ```php use Nette\Application\UI\Form; @@ -383,14 +383,14 @@ class SignInFormFactory public function create(): Form { $form = new Form; - $form->addText('name', 'Имя:'); - $form->addSubmit('send', 'Войти'); + $form->addText('name', 'Име:'); + $form->addSubmit('send', 'Вход'); return $form; } } ``` -Изискваме от класа да създаде формуляр в метода на фабриката за компоненти в презентатора: +Искаме от класа да произведе формата във фабричния метод за компоненти в презентера: ```php public function __construct( @@ -401,14 +401,14 @@ public function __construct( protected function createComponentSignInForm(): Form { $form = $this->formFactory->create(); - //можем да променим формата, например етикета на бутона - $form['login']->setCaption('Продолжить'); - $form->onSuccess[] = [$this, 'signInFormSubmitted']; // и добавяне на обработваща програма + // можем да променим формата, тук например променяме етикета на бутона + $form['send']->setCaption('Продължи'); + $form->onSuccess[] = [$this, 'signInFormSuceeded']; // и добавяме хендлър return $form; } ``` -Обработчикът на формуляри може да бъде получен и чрез метода factory: +Хендлърът за обработка на формата може да бъде предоставен и от фабриката: ```php use Nette\Application\UI\Form; @@ -419,13 +419,13 @@ class SignInFormFactory { $form = new Form; $form->addText('name', 'Име:'); - $form->addSubmit('send', 'login'); + $form->addSubmit('send', 'Вход'); $form->onSuccess[] = function (Form $form, $data): void { - // тук обработваме подадената форма + // тук извършваме обработката на формата }; return $form; } } ``` -И така, запознахме се накратко с формите в Nette. Опитайте се да потърсите вдъхновение в каталога с [примери |https://github.com/nette/forms/tree/master/examples] в дистрибуцията. +И така, направихме бързо въведение във формите в Nette. Опитайте да разгледате и директорията [examples |https://github.com/nette/forms/tree/master/examples] в дистрибуцията, където ще намерите още вдъхновение. diff --git a/forms/bg/rendering.texy b/forms/bg/rendering.texy index a9347307a8..953d4618f1 100644 --- a/forms/bg/rendering.texy +++ b/forms/bg/rendering.texy @@ -1,33 +1,35 @@ -Изобразяване на форми -********************* +Рендиране на форми +****************** -Външният вид на формите може да бъде много разнообразен. На практика можем да се сблъскаме с две крайности. От една страна, има нужда от визуализиране на поредица от формуляри в дадено приложение, които визуално си приличат един на друг, и оценяваме лесното визуализиране без шаблон с помощта на `$form->render()`. Такъв обикновено е случаят с административните интерфейси. +Външният вид на формите може да бъде много разнообразен. На практика можем да срещнем две крайности. От една страна, стои нуждата в приложението да се рендират редица форми, които визуално си приличат като две капки вода, и ще оценим лесното рендиране без шаблон с помощта на `$form->render()`. Обикновено това е случаят с административните интерфейси. -От друга страна, има различни формуляри, в които всеки един е уникален. Техният външен вид се описва най-добре с помощта на езика HTML в шаблона. И разбира се, освен двете споменати крайности, ще срещнем много форми, които попадат някъде по средата. +От друга страна, има разнообразни форми, за които важи: всяка е оригинал. Техният вид най-добре се описва с HTML език в шаблона на формата. И разбира се, освен двете споменати крайности, ще срещнем много форми, които се намират някъде по средата. -Рендъринг с Latte .[#toc-rendering-with-latte] -============================================== +Рендиране с помощта на Latte +============================ -[Системата за шаблони Latte |latte:] улеснява значително изчертаването на форми и техните елементи. Първо ще ви покажем как да визуализирате формулярите ръчно, елемент по елемент, за да получите пълен контрол върху кода. По-късно ще ви покажем как да [автоматизирате |#Automatic-Rendering] такова визуализиране. +[Шаблонната система Latte|latte:] значително улеснява рендирането на форми и техните елементи. Първо ще покажем как да рендираме формите ръчно, елемент по елемент, и така да получим пълен контрол над кода. По-късно ще покажем как такова рендиране може да бъде [автоматизирано |#Автоматично рендиране]. + +Можете да генерирате дизайна на Latte шаблона за формата с помощта на метода `Nette\Forms\Blueprint::latte($form)`, който го извежда на страницата на браузъра. След това просто маркирайте кода с кликване и го копирайте в проекта си. .{data-version:3.1.15} `{control}` ----------- -Най-лесният начин за показване на формуляр е да го запишете в шаблон: +Най-лесният начин да рендирате форма е да напишете в шаблона: ```latte {control signInForm} ``` -Външният вид на визуализираната форма може да бъде променен чрез персонализиране на [Renderer |#Renderer] и [отделните контроли |#HTML-Attributes]. +Можете да повлияете на външния вид на така рендираната форма чрез конфигуриране на [#Renderer] и [отделните елементи |#HTML атрибути]. `n:name` -------- -Много е лесно да свържете дефиницията на формуляра в кода на PHP с кода на HTML. Просто добавете атрибутите `n:name`. Толкова е лесно! +Дефиницията на формата в PHP кода може да бъде изключително лесно свързана с HTML кода. Достатъчно е само да добавите атрибутите `n:name`. Толкова е лесно! ```php protected function createComponentSignInForm(): Form @@ -43,10 +45,10 @@ protected function createComponentSignInForm(): Form ```latte <form n:name=signInForm class=form> <div> - <label n:name=username>Имя пользователя: <input n:name=username size=20 autofocus></label> + <label n:name=username>Потребителско име: <input n:name=username size=20 autofocus></label> </div> <div> - <label n:name=password>Пароль: <input n:name=password></label> + <label n:name=password>Парола: <input n:name=password></label> </div> <div> <input n:name=send class="btn btn-default"> @@ -54,10 +56,9 @@ protected function createComponentSignInForm(): Form </form> ``` -Външният вид на получения HTML код е изцяло във ваши ръце. Ако използвате атрибута `n:name` с `<select>`, `<button>` или `<textarea>` елементи, вътрешното им съдържание ще бъде попълнено автоматично. -Освен това етикетът `<form n:name>` създава локална променлива `$form` с нарисуван обект на формата, а затварящият таг `</form>` прерисува всички ненарисувани скрити елементи (същото важи и за `{form} ... {/form}`). +Имате пълен контрол над вида на получения HTML код. Ако използвате атрибута `n:name` при елементите `<select>`, `<button>` или `<textarea>`, тяхното вътрешно съдържание ще се попълни автоматично. Тагът `<form n:name>` освен това създава локална променлива `$form` с обекта на рендираната форма, а затварящият `</form>` рендира всички нерендирани скрити елементи (същото важи и за `{form} ... {/form}`). -Не забравяйте обаче да изведете възможните съобщения за грешки. Както тези, добавени към отделните елементи чрез метода `addError()` (с помощта на `{inputError}`), така и тези, добавени директно към формуляра (върнати от метода `$form->getOwnErrors()`): +Не трябва обаче да забравяме да рендираме възможните съобщения за грешки. Както тези, които са добавени към отделните елементи с метода `addError()` (с помощта на `{inputError}`), така и тези, добавени директно към формата (връщат се от `$form->getOwnErrors()`): ```latte <form n:name=signInForm class=form> @@ -66,11 +67,11 @@ protected function createComponentSignInForm(): Form </ul> <div> - <label n:name=username>Имя пользователя: <input n:name=username size=20 autofocus></label> + <label n:name=username>Потребителско име: <input n:name=username size=20 autofocus></label> <span class=error n:ifcontent>{inputError username}</span> </div> <div> - <label n:name=password>Пароль: <input n:name=password></label> + <label n:name=password>Парола: <input n:name=password></label> <span class=error n:ifcontent>{inputError password}</span> </div> <div> @@ -79,7 +80,7 @@ protected function createComponentSignInForm(): Form </form> ``` -По-сложните елементи на формуляра, като например RadioList или CheckboxList, могат да се показват елемент по елемент: +По-сложни елементи на формата, като RadioList или CheckboxList, могат да се рендират по този начин, поотделно: ```latte {foreach $form[gender]->getItems() as $key => $label} @@ -88,16 +89,10 @@ protected function createComponentSignInForm(): Form ``` -Код на офертата `{formPrint}` .[#toc-formprint] ------------------------------------------------ - -Можете да генерирате подобен код Latte за формата, като използвате тага `{formPrint}`. Ако го поставите в шаблон, ще видите черновия код вместо обичайното визуализиране. След това просто го изберете и го копирайте в проекта си. - - `{label}` `{input}` ------------------- -Не искате ли да мислите за всеки елемент кой HTML елемент да използвате за него в шаблона? `<input>`, `<textarea>` и т.н.? Решението е универсалният етикет `{input}`: +Не искате да мислите за всеки елемент какъв HTML елемент да използвате за него в шаблона, дали `<input>`, `<textarea>` и т.н.? Решението е универсалният таг `{input}`: ```latte <form n:name=signInForm class=form> @@ -106,11 +101,11 @@ protected function createComponentSignInForm(): Form </ul> <div> - {label username}Имя пользователя: {input username, size: 20, autofocus: true}{/label} + {label username}Потребителско име: {input username, size: 20, autofocus: true}{/label} {inputError username} </div> <div> - {label password}Пароль: {input password}{/label} + {label password}Парола: {input password}{/label} {inputError password} </div> <div> @@ -119,9 +114,9 @@ protected function createComponentSignInForm(): Form </form> ``` -Ако формулярът използва преводач, текстът в таговете `{label}` ще бъде преведен. +Ако формата използва преводач, текстът вътре в таговете `{label}` ще бъде преведен. -По-сложните елементи на формуляра, като например RadioList или CheckboxList, могат да се визуализират елемент по елемент: +И в този случай по-сложни елементи на формата, като RadioList или CheckboxList, могат да се рендират поотделно: ```latte {foreach $form[gender]->items as $key => $label} @@ -129,20 +124,19 @@ protected function createComponentSignInForm(): Form {/foreach} ``` -За показване на `<input>` в елемента Checkbox използвайте `{input myCheckbox:}`. HTML атрибутите трябва да бъдат разделени със запетая `{input myCheckbox:, class: required}`. +За да рендирате само `<input>` в елемента Checkbox, използвайте `{input myCheckbox:}`. В този случай винаги разделяйте HTML атрибутите със запетая `{input myCheckbox:, class: required}`. `{inputError}` -------------- -Извежда съобщение за грешка за елемента на формата, ако има такова. Съобщението обикновено е обвито в HTML елемент за целите на стилизирането. -Избягвайте да показвате празен елемент, ако няма съобщение, като използвате `n:ifcontent`: +Извежда съобщение за грешка за елемента на формата, ако има такова. Обикновено обвиваме съобщението в HTML елемент за стилизиране. Можете елегантно да предотвратите рендирането на празен елемент, ако няма съобщение, с помощта на `n:ifcontent`: ```latte <span class=error n:ifcontent>{inputError $input}</span> ``` -Можем да открием наличието на грешка с помощта на метода `hasErrors()` и да зададем съответния клас на родителския елемент: +Можем да проверим наличието на грешка с метода `hasErrors()` и съответно да зададем клас на родителския елемент: ```latte <div n:class="$form[username]->hasErrors() ? 'error'"> @@ -155,14 +149,13 @@ protected function createComponentSignInForm(): Form `{form}` -------- -Етикети `{form signInForm}...{/form}` са алтернатива на `<form n:name="signInForm">...</form>`. +Таговете `{form signInForm}...{/form}` са алтернатива на `<form n:name="signInForm">...</form>`. -Автоматично визуализиране .[#toc-automatic-rendering] ------------------------------------------------------ +Автоматично рендиране +--------------------- -С помощта на таговете `{input}` и `{label}` можем лесно да създадем общ шаблон за всяка форма. Тя ще извърши итерация и ще изобрази последователно всички елементи на формуляра, с изключение на скритите елементи, които се изобразяват автоматично при завършване на формуляра с помощта на `</form>` етикет. -Той ще очаква името на визуализираната форма в променливата `$form`. +Благодарение на таговете `{input}` и `{label}` можем лесно да създадем общ шаблон за всяка форма. Той ще итерира последователно и ще рендира всички нейни елементи, с изключение на скритите елементи, които ще се рендират автоматично при затваряне на формата с тага `</form>`. Името на рендираната форма ще се очаква в променливата `$form`. ```latte <form n:name=$form class=form> @@ -179,16 +172,15 @@ protected function createComponentSignInForm(): Form </form> ``` -Сдвоените самозатварящи се тагове `{label .../}` се използват за показване на таговете, идващи от дефиницията на формуляра в PHP кода. +Използваните самозатварящи се двойни тагове `{label .../}` показват етикети, идващи от дефиницията на формата в PHP кода. -Можете да запазите този общ шаблон в `basic-form.latte` и за визуализиране на формуляра просто да го включите и да предадете името на формуляра (или инстанцията) на `$form`: +Запазете този общ шаблон например във файл `basic-form.latte` и за да рендирате формата, е достатъчно да го включите и да предадете името (или инстанцията) на формата в параметъра `$form`: ```latte {include basic-form.latte, form: signInForm} ``` -Ако искате да повлияете на външния вид на една конкретна форма и да визуализирате един елемент по различен начин, най-лесният начин е да подготвите блокове в шаблона, които можете да презапишете по-късно. -Блоковете могат да имат и [динамични имена |latte:template-inheritance#Dynamic-Block-Names], така че можете да вмъкнете в тях името на елемента, който искате да нарисувате. Например: +Ако при рендирането на една конкретна форма искате да се намесите във вида й и например да рендирате един елемент по различен начин, тогава най-лесният начин е да подготвите предварително блокове в шаблона, които след това ще могат да бъдат презаписани. Блоковете могат да имат и [динамични имена |latte:template-inheritance#Динамични имена на блокове], така че в тях може да се вмъкне и името на рендирания елемент. Например: ```latte ... @@ -197,7 +189,7 @@ protected function createComponentSignInForm(): Form ... ``` -За даден елемент, например `username`, се създава блок `input-username`, който може лесно да бъде заменен с тага [{embed} |latte:template-inheritance#Unit-Inheritance]: +За елемент, напр. `username`, така се създава блок `input-username`, който може лесно да бъде презаписан с помощта на тага [{embed} |latte:template-inheritance#Единично наследяване]: ```latte {embed basic-form.latte, form: signInForm} @@ -209,7 +201,7 @@ protected function createComponentSignInForm(): Form {/embed} ``` -Алтернативно, цялото съдържание на шаблона `basic-form.latte` може да бъде [определено |latte:template-inheritance#Definitions] като блок, включително параметъра `$form`: +Алтернативно, цялото съдържание на шаблона `basic-form.latte` може да бъде [дефинирано |latte:template-inheritance#Дефиниции] като блок, включително параметъра `$form`: ```latte {define basic-form, $form} @@ -219,7 +211,7 @@ protected function createComponentSignInForm(): Form {/define} ``` -Това ще улесни донякъде използването му: +Благодарение на това извикването му ще бъде малко по-лесно: ```latte {embed basic-form, signInForm} @@ -227,31 +219,31 @@ protected function createComponentSignInForm(): Form {/embed} ``` -Необходимо е да импортирате блока само на едно място - в началото на шаблона за оформление: +При това е достатъчно блокът да се импортира само на едно място, и то в началото на шаблона на лейаута: ```latte {import basic-form.latte} ``` -Специални случаи .[#toc-special-cases] --------------------------------------- +Специални случаи +---------------- -Ако трябва да покажете само вътрешното съдържание на формуляра, без да `<form>` & `</form>` HTML тагове, например в AJAX заявка, можете да отваряте и затваряте формуляра, като използвате `{formContext} … {/formContext}`. В логически смисъл той работи подобно на `{form}`, като тук ви позволява да използвате други тагове, за да рисувате елементи на формата, но в същото време не рисува нищо. +Ако трябва да рендирате само вътрешната част на формата без HTML таговете `<form>`, например при изпращане на снипети, скрийте ги с помощта на атрибута `n:tag-if`: ```latte -{formContext signForm} +<form n:name=signInForm n:tag-if=false> <div> - <label n:name=username>Имя пользователя: <input n:name=username></label> + <label n:name=username>Потребителско име: <input n:name=username></label> {inputError username} </div> -{/formContext} +</form> ``` -Тагът `formContainer` помага за визуализиране на входните данни в контейнера на формата. +С рендирането на елементи вътре във формулярния контейнер ще помогне тагът `{formContainer}`. ```latte -<p>Which news you wish to receive:</p> +<p>Кои новини желаете да получавате:</p> {formContainer emailNews} <ul> @@ -262,27 +254,27 @@ protected function createComponentSignInForm(): Form ``` -Изобразяване без Latte .[#toc-rendering-without-latte] -====================================================== +Рендиране без Latte +=================== -Най-лесният начин за показване на формата е да извикате +Най-лесният начин да рендирате форма е да извикате: ```php $form->render(); ``` -Външният вид на визуализираната форма може да бъде променен чрез персонализиране на [Renderer |#Renderer] и [отделните контроли |#HTML-Attributes]. +Можете да повлияете на външния вид на така рендираната форма чрез конфигуриране на [#Renderer] и [отделните елементи |#HTML атрибути]. -Ръчно визуализиране .[#toc-manual-rendering] --------------------------------------------- +Ръчно рендиране +--------------- -Всеки елемент на формата има методи, които генерират HTML код за полето и етикета на формата. Те могат да го върнат като низ или обект [Nette\Utils\Html |utils:html-elements]: +Всеки елемент на формата разполага с методи, които генерират HTML код за полето на формата и етикетите. Те могат да го връщат или като низ, или като обект [Nette\Utils\Html|utils:html-elements]: - `getControl(): Html|string` връща HTML кода на елемента -- `getLabel($caption = null): Html|string|null` връща HTML кода на етикета, ако има такъв. +- `getLabel($caption = null): Html|string|null` връща HTML кода на етикета, ако съществува -Това позволява показването на формуляра елемент по елемент: +Така формата може да се рендира елемент по елемент: ```php <?php $form->render('begin') ?> @@ -305,47 +297,46 @@ $form->render(); <?php $form->render('end') ?> ``` -Докато за някои елементи `getControl()` връща единичен HTML елемент (напр. `<input>`, `<select>` и т.н.), а за други се връща цяла част от HTML код (CheckboxList, RadioList). -В този случай методите, генериращи отделни входове и етикети, могат да се използват за всеки елемент поотделно: +Докато при някои елементи `getControl()` връща единствен HTML елемент (напр. `<input>`, `<select>` и т.н.), при други връща цял фрагмент HTML код (CheckboxList, RadioList). В такъв случай можете да използвате методи, които генерират отделни input-и и етикети, за всеки елемент поотделно: -- `getControlPart($key = null): ?Html` връща HTML кода на един елемент. -- `getLabelPart($key = null): ?Html` връща HTML код за етикет на единичен елемент +- `getControlPart($key = null): ?Html` връща HTML кода на един елемент +- `getLabelPart($key = null): ?Html` връща HTML кода на етикета на един елемент .[note] -По исторически причини тези методи са предхождани от `get`, но `generate` би било по-добре, тъй като създава и връща нов елемент `Html` всеки път, когато бъде извикан. +Тези методи имат префикс `get` по исторически причини, но `generate` би бил по-добър, тъй като при всяко извикване създава и връща нов елемент `Html`. -Renderer .[#toc-renderer] -========================= +Renderer +======== -Това е обектът, който осигурява визуализирането на формата. Тя може да бъде настроена чрез метода `$form->setRenderer`. Контролът се предоставя, когато се извика методът `$form->render()`. +Това е обект, осигуряващ рендирането на формата. Той може да бъде зададен с метода `$form->setRenderer`. Контролът му се предава при извикване на метода `$form->render()`. -Ако не зададем персонализиран рендер, ще се използва рендерът по подразбиране [api:Nette\Forms\Rendering\DefaultFormRenderer]. Това ще изобрази елементите на формуляра като HTML таблица. Резултатът е следният: +Ако не зададем собствен renderer, ще бъде използван renderer-ът по подразбиране [api:Nette\Forms\Rendering\DefaultFormRenderer]. Той рендира елементите на формата под формата на HTML таблица. Изходът изглежда така: ```latte <table> <tr class="required"> - <th><label class="required" for="frm-name">Name:</label></th> + <th><label class="required" for="frm-name">Име:</label></th> <td><input type="text" class="text" name="name" id="frm-name" required value=""></td> </tr> <tr class="required"> - <th><label class="required" for="frm-age">Age:</label></th> + <th><label class="required" for="frm-age">Възраст:</label></th> <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> </tr> <tr> - <th><label>Gender:</label></th> + <th><label>Пол:</label></th> ... ``` -Дали да използвате таблица, зависи от вас, но много уеб дизайнери предпочитат друго оформление, например списък. Можем да настроим `DefaultFormRenderer` така, че изобщо да не се извежда в таблица. Трябва само да зададем съответните [обвивки $wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers]. Първият индекс винаги представлява област, а вторият индекс - елемент. Всички съответни области са показани на фигурата: +Дали да се използва или не таблица за скелета на формата е спорно и много уеб дизайнери предпочитат друг markup. Например дефиниционен списък. Затова ще преконфигурираме `DefaultFormRenderer` така, че да рендира формата под формата на списък. Конфигурацията се извършва чрез редактиране на масива [$wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers]. Първият индекс винаги представлява областта, а вторият - нейния атрибут. Отделните области са показани на изображението: -[* form-areas-en.webp *] +[* defaultformrenderer.webp *] -По подразбиране групата `controls` е обвита в. `<table>`и всеки `pair` представлява ред от таблицата `<tr>` съдържащи двойката `label` и `control` (клетки `<th>` и `<td>`). Нека да променим всички тези обвиващи елементи. Ще опаковаме `controls` в `<dl>`, оставете `pair` на мира, поставете `label` в `<dt>` и увийте `control` в `<dd>`: +Стандартно групата елементи `controls` е обвита в таблица `<table>`, всеки `pair` представлява ред на таблицата `<tr>`, а двойката `label` и `control` са клетки `<th>` и `<td>`. Сега ще променим обвиващите елементи. Ще вмъкнем областта `controls` в контейнер `<dl>`, ще оставим областта `pair` без контейнер, ще вмъкнем `label` в `<dt>` и накрая ще обвием `control` с тагове `<dd>`: ```php $renderer = $form->getRenderer(); @@ -357,193 +348,192 @@ $renderer->wrappers['control']['container'] = 'dd'; $form->render(); ``` -Това ще доведе до следния фрагмент: +Резултатът е следният HTML код: ```latte <dl> - <dt><label class="required" for="frm-name">Name:</label></dt> + <dt><label class="required" for="frm-name">Име:</label></dt> <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> - <dt><label class="required" for="frm-age">Age:</label></dt> + <dt><label class="required" for="frm-age">Възраст:</label></dt> <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> - <dt><label>Gender:</label></dt> + <dt><label>Пол:</label></dt> ... </dl> ``` -Обвивките могат да влияят на много атрибути. Например: +В масива wrappers може да се повлияе на редица други атрибути: -- добавяне на специални CSS класове към всеки вход на формата -- да различавате четни и нечетни линии -- рисуване на задължителни и незадължителни елементи по различен начин -- задаване дали съобщенията за грешки да се показват над формата или до всеки елемент +- добавяне на CSS класове към отделните типове елементи на формата +- разграничаване на четни и нечетни редове с CSS клас +- визуално разграничаване на задължителни и незадължителни елементи +- определяне дали съобщенията за грешки да се показват директно при елементите или над формата -Опции .[#toc-options] ---------------------- +Options +------- -Поведението на Renderer може да се контролира и чрез задаване на *опции* за отделните елементи на формата. По този начин можете да зададете подсказка за инструменти, която да се показва до полето за въвеждане: +Поведението на Renderer-а може да се контролира и чрез задаване на *options* на отделните елементи на формата. По този начин може да се зададе описание, което ще се изведе до входното поле: ```php $form->addText('phone', 'Номер:') - ->setOption('description', 'Этот номер останется скрытым'); + ->setOption('description', 'Този номер ще остане скрит'); ``` -Ако искаме да поставим HTML съдържание в него, използваме класа [Html |utils:html-elements]. +Ако искаме да поставим HTML съдържание в него, ще използваме класа [Html |utils:html-elements] ```php use Nette\Utils\Html; -$form->addText('phone', 'Телефон:') +$form->addText('phone', 'Номер:') ->setOption('description', Html::el('p') - ->setHtml('<a href="...">Условия предоставления услуг.</a>') + ->setHtml('<a href="...">Условия за съхранение на Вашия номер</a>') ); ``` .[tip] -Вместо етикет може да се използва и елементът Html: `$form->addCheckbox('conditions', $label)`. +Html елементът може да се използва и вместо етикет: `$form->addCheckbox('conditions', $label)`. -Групиране на входните данни .[#toc-grouping-inputs] ---------------------------------------------------- +Групиране на елементи +--------------------- -Полетата за въвеждане могат да се комбинират в набори от визуални полета чрез създаване на група: +Renderer-ът позволява групиране на елементи във визуални групи (fieldset-и): ```php -$form->addGroup('Персональные данные'); +$form->addGroup('Лични данни'); ``` -Създаването на нова група я активира - всички елементи, добавени по-нататък, се добавят към тази група. Можете да създадете формуляр по следния начин: +След създаване на нова група, тя става активна и всеки новодобавен елемент се добавя и към нея. Така че формата може да се изгражда по този начин: ```php $form = new Form; -$form->addGroup('Персональные данные'); -$form->addText('name', 'Ваше имя:'); -$form->addInteger('age', 'Ваш возраст:'); -$form->addEmail('email', 'Имейл:'); +$form->addGroup('Лични данни'); +$form->addText('name', 'Вашето име:'); +$form->addInteger('age', 'Вашата възраст:'); +$form->addEmail('email', 'Email:'); -$form->addGroup('Адрес доставки'); -$form->addCheckbox('send', 'Отправить по адресу'); +$form->addGroup('Адрес за доставка'); +$form->addCheckbox('send', 'Изпрати на адрес'); $form->addText('street', 'Улица:'); -$form->addText('city', 'Город:'); -$form->addSelect('country', 'Страна:', $countries); +$form->addText('city', 'Град:'); +$form->addSelect('country', 'Държава:', $countries); ``` +Renderer-ът първо рендира групите и едва след това елементите, които не принадлежат към никоя група. + -Поддръжка на Bootstrap .[#toc-bootstrap-support] ------------------------------------------------- +Поддръжка за Bootstrap +---------------------- -Можете да намерите [примери за |https://github.com/nette/forms/tree/master/examples] настройки на рендера за [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], [Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] и [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php] +[В примерите |https://github.com/nette/forms/tree/master/examples] ще намерите примери как да конфигурирате Renderer за [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], [Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] и [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php] -Атрибути на HTML .[#toc-html-attributes] -======================================== +HTML атрибути +============= -Можете да задавате всякакви HTML атрибути за контролите на формуляра, като използвате `setHtmlAttribute(string $name, $value = true)`: +За задаване на произволни HTML атрибути на елементите на формата използваме метода `setHtmlAttribute(string $name, $value = true)`: ```php -$form->addInteger('number', 'Number:') - ->setHtmlAttribute('class', 'bigNumbers'); +$form->addInteger('number', 'Номер:') + ->setHtmlAttribute('class', 'big-number'); -$form->addSelect('rank', 'Order:', ['price', 'name']) - ->setHtmlAttribute('onchange', 'submit()'); // извикване на JS функцията submit() при промяна +$form->addSelect('rank', 'Сортиране по:', ['цена', 'име']) + ->setHtmlAttribute('onchange', 'submit()'); // изпрати при промяна -//прилага се за <form> +// За задаване на атрибути на самия <form> $form->setHtmlAttribute('id', 'myForm'); ``` -Задаване на типа на влизане: +Спецификация на типа на елемента: ```php -$form->addText('tel', 'Ваш телефон:') +$form->addText('tel', 'Вашият телефон:') ->setHtmlType('tel') - ->setHtmlAttribute('placeholder', 'Пожалуйста, заполните ваш телефон'); + ->setHtmlAttribute('placeholder', 'въведете телефон'); ``` -Можем да зададем HTML атрибут за отделните елементи в списъците с радиоклипове или квадратчета с различни стойности за всеки от тях. -Обърнете внимание на двоеточието след `style:`, за да се гарантира, че стойността е избрана от ключа: +.[warning] +Задаването на типа и други атрибути служи само за визуални цели. Проверката на коректността на входовете трябва да се извършва на сървъра, което се осигурява чрез избор на подходящ [елемент на формата|controls] и посочване на [правила за валидация|validation]. + +На отделните елементи в radio или checkbox списъци можем да зададем HTML атрибут с различни стойности за всеки от тях. Обърнете внимание на двоеточието след `style:`, което осигурява избор на стойност според ключа: ```php -$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue']; +$colors = ['r' => 'червен', 'g' => 'зелен', 'b' => 'син']; $styles = ['r' => 'background:red', 'g' => 'background:green']; -$form->addCheckboxList('colors', 'Цвета:', $colors) +$form->addCheckboxList('colors', 'Цветове:', $colors) ->setHtmlAttribute('style:', $styles); ``` -Показва се като: +Извежда: ```latte -<label><input type="checkbox" name="colors[]" style="background:red" value="r">red</label> -<label><input type="checkbox" name="colors[]" style="background:green" value="g">green</label> -<label><input type="checkbox" name="colors[]" value="b">blue</label> +<label><input type="checkbox" name="colors[]" style="background:red" value="r">червен</label> +<label><input type="checkbox" name="colors[]" style="background:green" value="g">зелен</label> +<label><input type="checkbox" name="colors[]" value="b">син</label> ``` -Въпросителният знак може да се използва за булев HTML атрибут (който няма стойност, например `readonly`): +За задаване на логически атрибути, като `readonly`, можем да използваме запис с въпросителен знак: ```php -$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue']; -$form->addCheckboxList('colors', 'Colors:', $colors) - ->setHtmlAttribute('readonly?', 'r'); // използвайте масив за няколко ключа, например ['r', 'g'] +$form->addCheckboxList('colors', 'Цветове:', $colors) + ->setHtmlAttribute('readonly?', 'r'); // за повече ключове използвайте масив, напр. ['r', 'g'] ``` -Показва се като: +Извежда: ```latte -<label><input type="checkbox" name="colors[]" readonly value="r">red</label> -<label><input type="checkbox" name="colors[]" value="g">green</label> -<label><input type="checkbox" name="colors[]" value="b">blue</label> +<label><input type="checkbox" name="colors[]" readonly value="r">червен</label> +<label><input type="checkbox" name="colors[]" value="g">зелен</label> +<label><input type="checkbox" name="colors[]" value="b">син</label> ``` -За кутиите за избор методът `setHtmlAttribute()` задава атрибутите на елемента `<select>`. Ако искаме да зададем атрибути за всеки -`<option>`, ще използваме метода `setOptionAttribute()`. Освен това двоеточието и въпросителният знак, използвани по-горе, работят: +В случай на selectbox-ове методът `setHtmlAttribute()` задава атрибути на елемента `<select>`. Ако искаме да зададем атрибути на отделните `<option>`, използваме метода `setOptionAttribute()`. Записите с двоеточие и въпросителен знак, посочени по-горе, също работят: ```php -$form->addSelect('colors', 'Цвета:', $colors) +$form->addSelect('colors', 'Цветове:', $colors) ->setOptionAttribute('style:', $styles); ``` -Показва се като: +Извежда: ```latte <select name="colors"> - <option value="r" style="background:red">red</option> - <option value="g" style="background:green">green</option> - <option value="b">blue</option> + <option value="r" style="background:red">червен</option> + <option value="g" style="background:green">зелен</option> + <option value="b">син</option> </select> ``` -Прототипи .[#toc-prototypes] ----------------------------- +Прототипи +--------- -Алтернативен начин за задаване на HTML атрибути е да се промени шаблонът, от който се генерира HTML елементът. Шаблонът е обект `Html` и се връща от метода `getControlPrototype()`: +Алтернативен начин за задаване на HTML атрибути е чрез модифициране на шаблона, от който се генерира HTML елементът. Шаблонът е обект `Html` и се връща от метода `getControlPrototype()`: ```php -$input = $form->addInteger('number'); +$input = $form->addInteger('number', 'Номер:'); $html = $input->getControlPrototype(); // <input> -$html->class('big-number'); // <input class="big-number"> +$html->class('big-number'); // <input class="big-number"> ``` -Шаблонът на етикета, върнат от метода `getLabelPrototype()`, също може да бъде променен по този начин: +По този начин може да се модифицира и шаблонът на етикета, който се връща от `getLabelPrototype()`: ```php $html = $input->getLabelPrototype(); // <label> -$html->class('distinctive'); // <label class="distinctive"> +$html->class('distinctive'); // <label class="distinctive"> ``` -За елементите Checkbox, CheckboxList и RadioList можете да повлияете на шаблона на елемента, който обвива елемента. Той се връща от `getContainerPrototype()`. По подразбиране това е "празен" елемент, така че не се показва нищо, но ако му дадете име, то ще се покаже: +При елементите Checkbox, CheckboxList и RadioList можете да повлияете на шаблона на елемента, който обвива целия елемент. Той се връща от `getContainerPrototype()`. В състояние по подразбиране това е „празен“ елемент, така че нищо не се рендира, но като му зададем име, той ще се рендира: ```php $input = $form->addCheckbox('send'); -echo $input->getControl(); -// <label><input type="checkbox" name="send"></label> - $html = $input->getContainerPrototype(); $html->setName('div'); // <div> $html->class('check'); // <div class="check"> @@ -551,50 +541,49 @@ echo $input->getControl(); // <div class="check"><label><input type="checkbox" name="send"></label></div> ``` -В случай на CheckboxList и RadioList можете също така да повлияете на образеца за разделител на елементите, върнат от метода `getSeparatorPrototype()`. По подразбиране това е елементът `<br>`. Ако го промените на сдвоен елемент, той ще обгръща отделните елементи, вместо да ги разделя. -Можете също така да повлияете на шаблона на HTML елемента на етикетите на елементите, които връща методът `getItemLabelPrototype()`. +В случай на CheckboxList и RadioList може да се повлияе и на шаблона на разделителя на отделните елементи, който се връща от метода `getSeparatorPrototype()`. В състояние по подразбиране това е елементът `<br>`. Ако го промените на двоен елемент, той ще обвива отделните елементи, вместо да ги разделя. Освен това може да се повлияе на шаблона на HTML елемента на етикета при отделните елементи, който се връща от `getItemLabelPrototype()`. -Превод .[#toc-translating] -========================== +Превод +====== -Ако програмирате многоезично приложение, вероятно ще ви се наложи да визуализирате формуляра на различни езици. За тази цел в Nette Framework е дефиниран интерфейс за превод [api:Nette\Localization\Translator]. В Nette няма реализация по подразбиране, можете да изберете според нуждите си от няколко готови решения, които можете да намерите в [Componette |https://componette.org/search/localization]. В тяхната документация е описано как да конфигурирате преводача. +Ако програмирате многоезично приложение, вероятно ще трябва да рендирате формата в различни езикови версии. За тази цел Nette Framework дефинира интерфейс за превод [api:Nette\Localization\Translator]. В Nette няма имплементация по подразбиране, можете да избирате според нуждите си от няколко готови решения, които ще намерите на [Componette |https://componette.org/search/localization]. В тяхната документация ще научите как да конфигурирате преводача. -Формулярът поддържа извеждане на текст чрез преводача. Предаваме го с помощта на метода `setTranslator()`: +Формите поддържат извеждане на текстове чрез преводач. Предаваме им го с помощта на метода `setTranslator()`: ```php $form->setTranslator($translator); ``` -Отсега нататък не само всички етикети, но и всички съобщения за грешки или записи в полетата за избор ще бъдат преведени на друг език. +От този момент нататък не само всички етикети, но и всички съобщения за грешки или елементи на select box-ове ще бъдат преведени на друг език. -Възможно е да зададете различен преводач за отделните елементи на формуляра или да деактивирате напълно превода с `null`: +При отделните елементи на формата е възможно да се зададе друг преводач или преводът да се изключи напълно със стойност `null`: ```php -$form->addSelect('carModel', 'Model:', $cars) +$form->addSelect('carModel', 'Модел:', $cars) ->setTranslator(null); ``` -За [правилата за валидиране на |validation] преводача се предават и специфични параметри, например за правило: +При [правилата за валидация|validation] на преводача се предават и специфични параметри, например при правилото: ```php -$form->addPassword('password', 'Password:') - ->addRule($form::MinLength, 'Password has to be at least %d characters long', 8) +$form->addPassword('password', 'Парола:') + ->addRule($form::MinLength, 'Паролата трябва да съдържа поне %d знака', 8); ``` -преводачът се извиква със следните параметри: +се извиква преводачът с тези параметри: ```php -$translator->translate('Password has to be at least %d characters long', 8); +$translator->translate('Паролата трябва да съдържа поне %d знака', 8); ``` -и по този начин може да избере правилната форма за множествено число на думата `characters` от броя. +и следователно може да избере правилната форма за множествено число на думата `знака` според броя. -Събитието onRender .[#toc-event-onrender] -========================================= +Събитие onRender +================ -Точно преди формата да бъде визуализирана, можем да извикаме нашия код. Така например може да се добавят HTML класове към елементите на формуляра, за да се показват правилно. Нека да добавим кода към масива 'onRender': +Точно преди формата да се рендира, можем да извикаме наш код. Той може например да добави HTML класове към елементите на формата за правилно показване. Добавяме кода към масива `onRender`: ```php $form->onRender[] = function ($form) { diff --git a/forms/bg/standalone.texy b/forms/bg/standalone.texy index 5c2c5fc550..6a4e89a8a9 100644 --- a/forms/bg/standalone.texy +++ b/forms/bg/standalone.texy @@ -1,43 +1,43 @@ -Формуляри, използвани офлайн -**************************** +Форми, използвани самостоятелно +******************************* .[perex] -Nette Forms значително опростява създаването и обработката на уеб формуляри. Можете да ги използвате в приложенията си напълно самостоятелно, без останалата част от рамката, както ще демонстрираме в тази глава. +Nette Forms значително улесняват създаването и обработката на уеб форми. Можете да ги използвате във вашите приложения напълно самостоятелно, без останалата част от framework-а, което ще покажем в тази глава. -Въпреки това, ако използвате приложение Nette и Presenters, има ръководство за вас: [Forms in Presenters |in-presenter]. +Но ако използвате Nette Application и презентери, за вас е предназначено ръководството за [използване в презентери|in-presenter]. -Първата форма .[#toc-first-form] -================================ +Първа форма +=========== -Ще се опитаме да напишем прост формуляр за регистрация. Нейният код ще изглежда по следния начин ("пълен код":https://gist.github.com/dg/370a7e3094d9ba9a9e913b8e2a2dc851) +Нека опитаме да напишем проста форма за регистрация. Кодът й ще бъде следният ("целия код":https://gist.github.com/dg/57878c1a413ae8ef0c1d83f02c43ef3f): ```php use Nette\Forms\Form; $form = new Form; -$form->addText('name', 'Имя:'); -$form->addPassword('password', 'Пароль:'); -$form->addSubmit('send', 'Зарегистрироваться'); +$form->addText('name', 'Име:'); +$form->addPassword('password', 'Парола:'); +$form->addSubmit('send', 'Регистриране'); ``` -И нека направим визуализацията: +Много лесно можем да я рендираме: ```php $form->render(); ``` -и резултатът трябва да изглежда така: +и в браузъра ще се покаже така: -[* form-en.webp *] +[* form-cs.webp *] -Формата е обект от клас `Nette\Forms\Form` (клас `Nette\Application\UI\Form` се използва в презентаторите). Добавихме име на контрола, парола и бутон за изпращане. +Формата е обект от класа `Nette\Forms\Form` (класът `Nette\Application\UI\Form` се използва в презентери). Добавихме към нея т.нар. елементи име, парола и бутон за изпращане. -Сега ще анимираме формата. Чрез запитване към `$form->isSuccess()`, ние ще разберем дали формулярът е бил изпратен и дали е бил попълнен правилно. Ако отговорът е "да", ще нулираме данните. След като дефинираме формата, ще добавим: +А сега да оживим формата. С проверка на `$form->isSuccess()` ще разберем дали формата е била изпратена и дали е била попълнена валидно. Ако да, ще изведем данните. Следователно, след дефиницията на формата добавяме: ```php if ($form->isSuccess()) { - echo 'Формулярът е попълнен и изпратен правилно'; + echo 'Формата беше правилно попълнена и изпратена'; $data = $form->getValues(); // $data->name съдържа името // $data->password съдържа паролата @@ -45,32 +45,32 @@ if ($form->isSuccess()) { } ``` -Методът `getValues()` връща изпратените данни като обект [ArrayHash |utils:arrays#ArrayHash]. [По-късно |#Mapping-to-Classes] ще покажем как да промените това. Променливата `$data` съдържа ключовете `name` и `password` с данни, въведени от потребителя. +Методът `getValues()` връща изпратените данни под формата на обект [ArrayHash |utils:arrays#ArrayHash]. Как да променим това, ще покажем [по-късно |#Мапиране към класове]. Обектът `$data` съдържа ключове `name` и `password` с данните, които е попълнил потребителят. -Обикновено изпращаме данните директно за по-нататъшна обработка, която може да бъде напр. въвеждане в база данни. При обработката обаче може да възникне грешка, например ако потребителското име вече е заето. В този случай изпращаме грешката обратно към формата с `addError()` и я оставяме да се прерисува със съобщение за грешка: +Обикновено данните веднага се изпращат за по-нататъшна обработка, което може да бъде например вмъкване в база данни. По време на обработката обаче може да възникне грешка, например потребителското име вече е заето. В такъв случай предаваме грешката обратно към формата с помощта на `addError()` и я оставяме да се рендира отново, заедно със съобщението за грешка. ```php -$form->addError('Извините, имя пользователя уже используется.'); +$form->addError('Извиняваме се, това потребителско име вече се използва.'); ``` -След като формулярът бъде обработен, ще ви пренасочим към следващата страница. Това предотвратява повторното изпращане на формуляра по невнимание, когато кликнете върху *опресняване*, *назад* или навигирате в историята на браузъра. +След обработката на формата пренасочваме към следващата страница. Това предотвратява нежеланото повторно изпращане на формата с бутона *обнови*, *назад* или чрез движение в историята на браузъра. -По подразбиране формулярът се изпраща чрез POST на същата страница. И двете могат да бъдат променени: +Формата стандартно се изпраща с метод POST и то към същата страница. И двете могат да се променят: ```php $form->setAction('/submit.php'); $form->setMethod('GET'); ``` -И това е всичко :-) Имаме функционална и отлично [защитена |#Защита от уязвимостей] форма. +И това всъщност е всичко :-) Имаме функционална и перфектно [защитена |#Защита от уязвимости] форма. -Опитайте да добавите повече контроли на [формуляра |controls]. +Опитайте да добавите и други [елементи на формата|controls]. -Достъп до контролните уреди .[#toc-access-to-controls] -====================================================== +Достъп до елементи +================== -Формулярът и отделните му контроли се наричат компоненти. Те създават дърво на компонентите, чийто корен е формулярът. Достъпът до отделните контроли се осъществява, както следва +Формата и нейните отделни елементи наричаме компоненти. Те образуват дърво от компоненти, където коренът е именно формата. До отделните елементи на формата можем да достигнем по следния начин: ```php $input = $form->getComponent('name'); @@ -80,37 +80,36 @@ $button = $form->getComponent('send'); // алтернативен синтаксис: $button = $form['send']; ``` -Контролите се изтриват с помощта на функцията за изтриване: +Елементите се премахват с помощта на unset: ```php unset($form['name']); ``` -Правила за валидиране .[#toc-validation-rules] -============================================== +Правила за валидация +==================== -Думата *valid* е използвана няколко пъти, но формулярът все още няма правила за валидиране. Нека поправим това. +Споменахме думата *валидна*, но формата засега няма никакви правила за валидация. Нека поправим това. -Името ще бъде задължително, затова ще го маркираме с метода `setRequired()`, чийто аргумент е текстът на съобщението за грешка, което ще се покаже, ако потребителят не успее да го попълни. Ако не е посочен аргумент, се използва съобщението за грешка по подразбиране. +Името ще бъде задължително, затова го маркираме с метода `setRequired()`, чийто аргумент е текстът на съобщението за грешка, което ще се покаже, ако потребителят не попълни името. Ако не посочим аргумент, ще се използва съобщението за грешка по подразбиране. ```php -$form->addText('name', 'Имя:') - ->setRequired('Пожалуйста, введите имя.'); +$form->addText('name', 'Име:') + ->setRequired('Моля, въведете име'); ``` -Ако се опитате да изпратите формуляра, без да сте го попълнили, ще се появи съобщение за грешка и браузърът или сървърът ще отхвърлят формуляра, докато не го попълните. +Опитайте да изпратите формата без попълнено име и ще видите, че ще се покаже съобщение за грешка и браузърът или сървърът ще я отхвърлят, докато не попълните полето. -В същото време не можете да заблудите системата, като например въведете само интервали в полето за въвеждане. В никакъв случай. Nette автоматично подрязва левия и десния бял интервал. Опитайте, това е нещо, което винаги трябва да правите при въвеждането на всеки отделен ред, но често се забравя. Nette прави това автоматично. (Можете да се опитате да измамите формата и да изпратите многоредов низ като име. Дори и тук Nette няма да се заблуди и прекъсванията на редовете ще бъдат заменени с интервали). +Същевременно системата няма да ви измами, като напишете в полето например само интервали. Не. Nette автоматично премахва левите и десните интервали. Опитайте. Това е нещо, което винаги трябва да правите с всеки едноредов input, но често се забравя. Nette го прави автоматично. (Можете да опитате да измамите формата и да изпратите многоредов низ като име. И тук Nette няма да се обърка и ще промени новите редове на интервали.) -Формулярът винаги се проверява от страна на сървъра, но се генерира и проверка на JavaScript, която се извършва бързо и потребителят веднага разбира за грешката, без да се налага да изпраща формуляра към сървъра. С това се занимава скриптът `netteForms.js`. -Добавете го към страницата: +Формата винаги се валидира от страна на сървъра, но също така се генерира JavaScript валидация, която протича мигновено и потребителят научава за грешката веднага, без да е необходимо да изпраща формата на сървъра. За това се грижи скриптът `netteForms.js`. Вмъкнете го в страницата: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Ако разгледате изходния код на страницата с формуляра, можете да забележите, че Nette вмъква задължителни полета в елементи с клас CSS `required`. Опитайте се да добавите следния стил към шаблона и тагът "Име" ще бъде червен. Елегантно маркираме задължителните полета за потребителите: +Ако погледнете в изходния код на страницата с формата, можете да забележите, че Nette вмъква задължителните елементи в елементи с CSS клас `required`. Опитайте да добавите следния стил в шаблона и надписът „Име“ ще бъде червен. Така елегантно маркираме задължителните елементи за потребителите: ```latte <style> @@ -118,96 +117,96 @@ $form->addText('name', 'Имя:') </style> ``` -Допълнителни правила за валидиране ще бъдат добавени чрез метода `addRule()`. Първият параметър е правилото, вторият е текстът на съобщението за грешка, последван от незадължителен аргумент за правило за валидиране. Какво означава това? +Други правила за валидация добавяме с метода `addRule()`. Първият параметър е правилото, вторият е отново текстът на съобщението за грешка и може да последва още аргумент на правилото за валидация. Какво се има предвид? -Формулярът ще получи още един незадължителен елемент за въвеждане *възраст*, като условието е той да бъде число (`addInteger()`) и да е в определени граници (`$form::Range`). И тук ще използваме третия аргумент `addRule()`, самия диапазон: +Ще разширим формата с ново незадължително поле „възраст“, което трябва да бъде цяло число (`addInteger()`) и освен това в разрешен диапазон (`$form::Range`). И тук именно ще използваме третия параметър на метода `addRule()`, с който ще предадем на валидатора изисквания диапазон като двойка `[от, до]`: ```php -$form->addInteger('age', 'Возраст:') - ->addRule($form::Range, 'Вы должны быть старше 18 лет и иметь возраст до 120 лет.', [18, 120]); +$form->addInteger('age', 'Възраст:') + ->addRule($form::Range, 'Възрастта трябва да е между 18 и 120', [18, 120]); ``` .[tip] -Ако потребителят не попълни полето, правилата за валидиране няма да бъдат проверени, тъй като полето е незадължително. +Ако потребителят не попълни полето, правилата за валидация няма да се проверяват, тъй като елементът е незадължителен. -Очевидно е, че тук има място за малко преработване. В съобщението за грешка и в третия параметър числата са изброени двойно, което не е идеално. Ако създаваме [многоезичен формуляр |rendering#translating] и съобщението, съдържащо числата, трябва да бъде преведено на няколко езика, това би затруднило промяната на стойностите. По тази причина можем да използваме заместващи символи `%d`: +Тук възниква възможност за дребен рефакторинг. В съобщението за грешка и в третия параметър числата са посочени дублирано, което не е идеално. Ако създавахме [многоезични форми |rendering#Превод] и съобщението, съдържащо числа, беше преведено на няколко езика, евентуалната промяна на стойностите би се затруднила. Поради тази причина е възможно да се използват заместващи знаци `%d` и Nette ще допълни стойностите: ```php - ->addRule($form::Range, 'Вы должны быть старше %d лет и иметь возраст до %d лет.', [18, 120]); + ->addRule($form::Range, 'Възрастта трябва да е между %d и %d години', [18, 120]); ``` -Нека се върнем към полето *парола*, да го направим *задължително* и да проверим минималната дължина на паролата (`$form::MinLength`), като отново използваме заместители в съобщението: +Да се върнем към елемента `password`, който също ще направим задължителен и ще проверим минималната дължина на паролата (`$form::MinLength`), отново с използване на заместващ знак: ```php -$form->addPassword('password', 'Пароль:') - ->setRequired('Выберите пароль') - ->addRule($form::MinLength, 'Ваш пароль должен быть длиной не менее %d', 8); +$form->addPassword('password', 'Парола:') + ->setRequired('Изберете парола') + ->addRule($form::MinLength, 'Паролата трябва да има поне %d знака', 8); ``` -Ще добавим поле към формуляра `passwordVerify`, в което потребителят въвежда отново паролата, за да я валидира. С помощта на правила за валидиране проверяваме дали двете пароли (`$form::Equal`) са еднакви. И даваме връзка към първата парола като аргумент, като използваме [квадратни скоби |#Access-to-Controls]: +Ще добавим към формата още поле `passwordVerify`, където потребителят ще въведе паролата още веднъж, за проверка. С помощта на правилата за валидация ще проверим дали двете пароли са еднакви (`$form::Equal`). И като параметър ще дадем препратка към първата парола с помощта на [квадратни скоби |#Достъп до елементи]: ```php -$form->addPassword('passwordVerify', 'Повторите пароль:') - ->setRequired('Введите пароль ещё раз, чтобы проверить опечатку') - ->addRule($form::Equal, 'Несоответствие пароля', $form['password']) +$form->addPassword('passwordVerify', 'Парола за проверка:') + ->setRequired('Моля, въведете паролата отново за проверка') + ->addRule($form::Equal, 'Паролите не съвпадат', $form['password']) ->setOmitted(); ``` -С помощта на `setOmitted()`, маркираме елемент, чиято стойност всъщност не ни интересува и който съществува само за валидиране. Стойността му не се предава на `$data`. +С помощта на `setOmitted()` маркирахме елемент, чиято стойност всъщност не ни интересува и който съществува само поради валидация. Стойността не се предава в `$data`. -Имаме напълно функционален формуляр с валидиране в PHP и JavaScript. Възможностите за валидиране в Nette са много по-широки, можете да създавате условия, да показвате и скривате части от страницата в зависимост от тях и т.н. Можете да научите всичко за това в главата [Проверка на формата |validation]. +С това имаме готова напълно функционална форма с валидация в PHP и JavaScript. Валидационните способности на Nette са далеч по-широки, могат да се създават условия, според тях да се показват и скриват части от страницата и т.н. Всичко ще научите в главата за [валидация на форми|validation]. -Стойности по подразбиране .[#toc-default-values] -================================================ +Стойности по подразбиране +========================= -Често задаваме стойности по подразбиране за контролите на формуляра: +На елементите на формата обикновено задаваме стойности по подразбиране: ```php -$form->addEmail('email', 'Имейл') +$form->addEmail('email', 'E-mail') ->setDefaultValue($lastUsedEmail); ``` -Често е полезно да зададете стойности по подразбиране за всички контроли едновременно. Например, когато формулярът се използва за редактиране на записи. Прочитаме запис от базата данни и го задаваме като стойност по подразбиране: +Често е полезно да се зададат стойности по подразбиране на всички елементи едновременно. Например, когато формата служи за редактиране на записи. Прочитаме записа от базата данни и задаваме стойностите по подразбиране: ```php //$row = ['name' => 'John', 'age' => '33', /* ... */]; $form->setDefaults($row); ``` -Извикайте `setDefaults()` след дефиниране на контролите. +Извиквайте `setDefaults()` след дефинирането на елементите. -Форма за показване .[#toc-rendering-the-form] -============================================= +Рендиране на формата +==================== -По подразбиране формулярът се показва като таблица. Отделните контроли следват основните насоки за достъпност на уеб страниците. Всички етикети се генерират като елементи `<label>` и са свързани с техните елементи, като щракването върху маркер ще премести курсора върху съответния елемент. +Стандартно формата се рендира като таблица. Отделните елементи отговарят на основното правило за достъпност - всички надписи са записани като `<label>` и са свързани със съответния елемент на формата. При кликване върху надписа курсорът автоматично се появява в полето на формата. -Можем да зададем всякакви атрибути на HTML за всеки елемент. Например, добавете заместител: +На всеки елемент можем да задаваме произволни HTML атрибути. Например да добавим placeholder: ```php -$form->addInteger('age', 'Возраст:') - ->setHtmlAttribute('placeholder', 'Пожалуйста, заполните возраст'); +$form->addInteger('age', 'Възраст:') + ->setHtmlAttribute('placeholder', 'Моля, попълнете възрастта'); ``` -Всъщност има много начини за визуализиране на формуляри, повече подробности в глава [Изобразяване |rendering]. +Начините за рендиране на форма са наистина много, затова на това е посветена [самостоятелна глава за рендиране|rendering]. -Съпоставяне с класове .[#toc-mapping-to-classes] -================================================ +Мапиране към класове +==================== -Нека се върнем към обработката на данни от формуляри. Методът `getValues()` връща подадените данни като обект `ArrayHash`. Тъй като това е общ клас, нещо като `stdClass`, при работа с него ще ни липсват някои удобни функции, като например попълване на кода за свойства в редактори или статичен анализ на кода. Това може да бъде решено чрез създаване на отделен клас за всяка форма, чиито свойства представляват отделните контроли. Например: +Да се върнем към обработката на данните от формата. Методът `getValues()` ни връщаше изпратените данни като обект `ArrayHash`. Тъй като това е генеричен клас, нещо като `stdClass`, при работа с него ще ни липсва определен комфорт, като например подсказване на свойствата в редакторите или статичен анализ на кода. Това би могло да се реши, като за всяка форма имаме конкретен клас, чиито свойства представляват отделните елементи. Напр.: ```php class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -От PHP 8.0 можете да използвате този елегантен запис, който използва конструктор: +Алтернативно можете да използвате конструктор: ```php class RegistrationFormData @@ -221,16 +220,18 @@ class RegistrationFormData } ``` -Как да кажем на Nette да ни връща данни като обекти от този клас? По-лесно е, отколкото си мислите. Всичко, което трябва да направите, е да посочите името на класа или обекта, който искате да хидратирате, като параметър: +Свойствата на класа с данни могат да бъдат и enum-и и те ще бъдат автоматично мапирани. .{data-version:3.2.4} + +Как да кажем на Nette да ни връща данните като обекти от този клас? По-лесно, отколкото си мислите. Достатъчно е само името на класа или обектът за хидратиране да се посочи като параметър: ```php $data = $form->getValues(RegistrationFormData::class); $name = $data->name; ``` -Можете също така да посочите `'array'` като параметър и тогава данните се връщат като масив. +Като параметър може да се посочи също `'array'` и тогава данните ще се върнат като масив. -Ако формулярите се състоят от структура на няколко нива, съставена от контейнери, създайте отделен клас за всеки от тях: +Ако формите образуват многостепенна структура, съставена от контейнери, създайте за всеки отделен клас: ```php $form = new Form; @@ -247,26 +248,28 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` -От типа на свойството `$person` съпоставянето ще разбере, че трябва да съпостави контейнера с класа `PersonFormData`. Ако свойството ще съдържа масив от контейнери, посочете типа `array` и предайте класа, който ще бъде съпоставен директно с контейнера: +Мапирането след това от типа на свойството `$person` ще разбере, че трябва да мапира контейнера към класа `PersonFormData`. Ако свойството съдържа масив от контейнери, посочете тип `array` и предайте класа за мапиране директно на контейнера: ```php $person->setMappedType(PersonFormData::class); ``` +Можете да генерирате дизайна на класа с данни на формата с помощта на метода `Nette\Forms\Blueprint::dataClass($form)`, който го извежда на страницата на браузъра. След това е достатъчно да маркирате кода с кликване и да го копирате в проекта. .{data-version:3.1.15} + -Множество бутони за изпращане .[#toc-multiple-submit-buttons] -============================================================= +Повече бутони +============= -Ако формулярът съдържа повече от един бутон, обикновено трябва да се разграничи кой бутон е бил щракнат. Методът `isSubmittedBy()` на бутона ни връща тази информация: +Ако формата има повече от един бутон, обикновено трябва да разграничим кой от тях е бил натиснат. Тази информация ни връща методът `isSubmittedBy()` на бутона: ```php -$form->addSubmit('save', 'Сохранить'); -$form->addSubmit('delete', 'Удалить'); +$form->addSubmit('save', 'Запазване'); +$form->addSubmit('delete', 'Изтриване'); if ($form->isSuccess()) { if ($form['save']->isSubmittedBy()) { @@ -279,37 +282,36 @@ if ($form->isSuccess()) { } ``` -Не пропускайте `$form->isSuccess()` за целите на валидирането. +Не пропускайте проверката `$form->isSuccess()`, с нея ще проверите валидността на данните. -Когато формулярът се изпрати с бутона <kbd>Enter</kbd>, той се третира по същия начин, както ако беше изпратен с първия бутон. +Когато формата се изпрати с бутона <kbd>Enter</kbd>, се счита, че е изпратена с първия бутон. -Защита срещу уязвимости .[#toc-vulnerability-protection] -======================================================== +Защита от уязвимости +==================== -Рамката на Nette полага големи усилия за гарантиране на сигурността и тъй като формулярите са най-често срещаният вид въвеждане на данни от потребителя, формулярите на Nette са толкова добри, колкото и непробиваеми. +Nette Framework поставя голям акцент върху сигурността и затова стриктно се грижи за доброто обезопасяване на формите. -В допълнение към защитата на формулярите срещу атаки от известни уязвимости, като например [Cross-Site Scripting (XSS) |nette:glossary#Cross-Site-Scripting-XSS] и [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site-Request-Forgery-CSRF], тя изпълнява много малки задачи по сигурността, за които вече не е необходимо да мислите. +Освен че формите защитават от атаки [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] и [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF], той прави много дребни защити, за които вие вече не трябва да мислите. -Например той филтрира всички контролни символи от входните данни и валидира кодирането UTF-8, така че данните от формуляра ви винаги ще бъдат чисти. За полетата за избор и радиосписъците се проверява дали избраните елементи са наистина от предлаганите елементи и дали няма фалшиви. Вече споменахме, че при въвеждане на текст от един ред тя премахва символите от края на реда, които атакуващият може да изпрати там. При многоредово въвеждане се нормализират символите в края на реда. И така нататък. +Например, филтрира от входовете всички контролни знаци и проверява валидността на UTF-8 кодирането, така че данните от формата винаги ще бъдат чисти. При select кутиите и radio списъците проверява дали избраните елементи са били действително от предлаганите и дали не е имало подправяне. Вече споменахме, че при едноредовите текстови входове премахва знаците за край на ред, които нападателят е могъл да изпрати. При многоредовите входове пък нормализира знаците за край на ред. И така нататък. -Nette елиминира за вас уязвимостите в сигурността, за които повечето програмисти дори не подозират, че съществуват. +Nette решава вместо вас рисковете за сигурността, за които много програмисти дори не подозират, че съществуват. -Споменатата по-горе CSRF атака се състои в това, че нападателят примамва жертвата да посети страница, която безшумно изпълнява заявка в браузъра на жертвата към сървъра, в който жертвата е влязла в момента, и сървърът приема, че заявката е направена от жертвата по нейно желание. По този начин Nette предотвратява изпращането на формуляра чрез POST от друг домейн. Ако по някаква причина искате да деактивирате защитата и да позволите формулярът да бъде изпратен от друг домейн, използвайте +Споменатата CSRF атака се състои в това, че нападателят примамва жертвата на страница, която незабележимо в браузъра на жертвата изпълнява заявка към сървъра, на който жертвата е влязла, и сървърът смята, че заявката е била изпълнена от жертвата по нейна воля. Затова Nette предотвратява изпращането на POST форма от друг домейн. Ако по някаква причина искате да изключите защитата и да позволите изпращането на формата от друг домейн, използвайте: ```php -$form->allowCrossOrigin(); // ПРЕДУПРЕЖДЕНИЕ! Деактивира защитата! +$form->allowCrossOrigin(); // ВНИМАНИЕ! Изключва защитата! ``` -Тази защита използва бисквитка на SameSite с име `_nss`. Затова създайте формуляра преди първия изход, за да може да се изпрати "бисквитката". +Тази защита използва SameSite бисквитка с име `_nss`. Затова създавайте обекта на формата преди изпращането на първия изход, за да може бисквитката да бъде изпратена. -Защитата на бисквитките на SameSite може да не е 100% надеждна, затова е добре да активирате защитата с токени: +Защитата с помощта на SameSite бисквитка може да не е 100% надеждна, затова е препоръчително да включите и защита с помощта на токен: ```php $form->addProtection(); ``` -Силно препоръчително е да приложите тази защита към формулярите в административната част на приложението, които модифицират чувствителни данни. Рамката се защитава от CSRF атака чрез генериране и валидиране на токен за удостоверяване, който се съхранява в сесията (аргументът е съобщение за грешка, което се показва, ако токенът е изтекъл). Затова е необходимо да бъде стартирана сесия, преди да бъде показан формулярът. В административната част на сайта сесията обикновено вече е започнала, тъй като потребителят е влязъл в системата. -В противен случай стартирайте сесията, като използвате метода `Nette\Http\Session::start()`. +Препоръчваме да защитавате по този начин формите в административната част на сайта, които променят чувствителни данни в приложението. Framework-ът се защитава срещу CSRF атака чрез генериране и проверка на оторизационен токен, който се съхранява в сесията. Затова е необходимо преди показването на формата да има отворена сесия. В административната част на сайта обикновено сесията вече е стартирана поради влизането на потребителя. В противен случай стартирайте сесията с метода `Nette\Http\Session::start()`. -И така, запознахме се накратко с формите в Нете. Опитайте се да потърсите вдъхновение в директорията с [примери |https://github.com/nette/forms/tree/master/examples] в дистрибуцията. +Така, преминахме през бързо въведение във формите в Nette. Опитайте да разгледате още директорията [examples|https://github.com/nette/forms/tree/master/examples] в дистрибуцията, където ще намерите повече вдъхновение. diff --git a/forms/bg/validation.texy b/forms/bg/validation.texy index 742c787c3c..2342c274de 100644 --- a/forms/bg/validation.texy +++ b/forms/bg/validation.texy @@ -1,173 +1,184 @@ -Валидиране на формуляри -*********************** +Валидация на форми +****************** -Задължителни елементи, които трябва да се попълнят .[#toc-required-controls] -============================================================================ +Задължителни елементи +===================== -Контролите се маркират като задължителни с помощта на метода `setRequired()`, чийто аргумент е текстът на [съобщението за грешка |#Сообщения об ошибке], което ще се покаже, ако потребителят не успее да го изпълни. Ако не е посочен аргумент, се използва съобщението за грешка по подразбиране. +Задължителните елементи маркираме с метода `setRequired()`, чийто аргумент е текстът на [#Съобщения за грешки], който ще се покаже, ако потребителят не попълни елемента. Ако не посочим аргумент, ще се използва съобщението за грешка по подразбиране. ```php -$form->addText('name', 'Имя:') - ->setRequired('Пожалуйста, заполните ваше имя.'); +$form->addText('name', 'Име:') + ->setRequired('Моля, въведете име'); ``` -Правила .[#toc-rules] -===================== +Правила +======= -Добавяме правила за валидиране към контролите, като използваме метода `addRule()`. Първият параметър е правилото, вторият е [съобщението за грешка |#Сообщения об ошибке], а третият е аргументът на правилото за валидиране. +Правилата за валидация добавяме към елементите с метода `addRule()`. Първият параметър е правилото, вторият е текстът на [#Съобщения за грешки] и третият е аргументът на правилото за валидация. ```php -$form->addPassword('password', 'Пароль:') - ->addRule($form::MinLength, 'Пароль должен состоять не менее чем из %d символов', 8); +$form->addPassword('password', 'Парола:') + ->addRule($form::MinLength, 'Паролата трябва да има поне %d знака', 8); ``` -Nette разполага с редица вградени правила, чиито имена са константи от класа `Nette\Forms\Form`: +**Правилата за валидация се проверяват само в случай, че потребителят е попълнил елемента.** -Можем да използваме следните правила за всички контроли: +Nette идва с цяла редица предварително дефинирани правила, чиито имена са константи на класа `Nette\Forms\Form`. При всички елементи можем да използваме тези правила: -| константа | описание | аргументи +| константа | описание | тип аргумент |------- -| `Required` | псевдоним `setRequired()` | - -| `Filled` | псевдоним `setRequired()` | - -| `Blank` | не трябва да се попълва | - +| `Required` | задължителен елемент, псевдоним за `setRequired()` | - +| `Filled` | задължителен елемент, псевдоним за `setRequired()` | - +| `Blank` | елементът не трябва да бъде попълнен | - | `Equal` | стойността е равна на параметъра | `mixed` -| `NotEqual` | стойността не е равна на | `mixed` -| `IsIn` | стойността е равна на някой елемент от масива | `array` -| `IsNotIn` | стойността не е равна на нито един елемент от масива | `array` -| `Valid` | записът преминава валидиране (за [Условие |#Условия]) | - +| `NotEqual` | стойността не е равна на параметъра | `mixed` +| `IsIn` | стойността е равна на някой елемент в масива | `array` +| `IsNotIn` | стойността не е равна на никой елемент в масива | `array` +| `Valid` | елементът попълнен ли е правилно? (за [#Условия]) | - -За контролите `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` могат да се използват и следните правила: -| `MinLength` | минимална дължина на реда `int` -| `MaxLength` | максимална дължина на реда `int` -| `Length` | дължината в диапазон или точната дължина | двойка `[int, int]` или `int` -| `Email` | валиден имейл адрес | - -| `URL` | валиден URL адрес | - -| `Pattern` | съвпада с регулярен модел | `string` -| `PatternInsensitive` | като `Pattern`, но без да се отчитат малките и големите букви. | `string` -| `Integer` | цяло число | - -| `Numeric` | псевдоним `Integer` | - -| `Float` | цяло число или число с плаваща запетая | - -| `Min` | минимална стойност в цяло число | `int\|float` -| `Max` | максимална стойност в цяло число | `int\|float` -| `Range` | стойност в диапазона | para `[int\|float, int\|float]` +Текстови полета +--------------- -Правилата `Integer`, `Numeric` и `Float` автоматично преобразуват стойността в цяло число (или съответно в число с плаваща запетая). Освен това правилото `URL` приема и адрес без схема (напр. `nette.org`) и допълва схемата (`https://nette.org`). -Изразите в `Pattern` и `PatternInsensitive` трябва да са валидни за цялата стойност, т.е. все едно са обвити в `^` и `$`. +При елементите `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`, `addFloat()` могат да се използват и някои от следните правила: + +| `MinLength` | минимална дължина на текста | `int` +| `MaxLength` | максимална дължина на текста | `int` +| `Length` | дължина в диапазон или точна дължина | двойка `[int, int]` или `int` +| `Email` | валиден имейл адрес | - +| `URL` | абсолютен URL | - +| `Pattern` | съответства на регулярен израз | `string` +| `PatternInsensitive` | като `Pattern`, но независимо от големината на буквите | `string` +| `Integer` | целочислена стойност | - +| `Numeric` | псевдоним за `Integer` | - +| `Float` | число | - +| `Min` | минимална стойност на числов елемент | `int\|float` +| `Max` | максимална стойност на числов елемент | `int\|float` +| `Range` | стойност в диапазон | двойка `[int\|float, int\|float]` -Следните правила могат да се използват и за контроли `addUpload()`, `addMultiUpload()`: +Правилата за валидация `Integer`, `Numeric` и `Float` веднага преобразуват стойността в integer съответно float. Освен това правилото `URL` приема и адрес без схема (напр. `nette.org`) и допълва схемата (`https://nette.org`). Изразът в `Pattern` и `PatternIcase` трябва да важи за цялата стойност, т.е. сякаш е обгърнат със знаците `^` и `$`. -| `MaxFileSize` | максимален размер на файла | `int` -| `MimeType` | Тип MIME, приема заместващи символи (`'video/*'`) | `string\|string[]` -| `Image` | изтегленият файл е JPEG, PNG, GIF, WebP | - -| `Pattern` | името на файла отговаря на регулярен израз | `string` -| `PatternInsensitive` | като `Pattern`, но без да се отчитат малките и големите букви. | `string` -За `MimeType` и `Image` се изисква разширението на PHP `fileinfo`. Правилният тип на файла или изображението се определя от неговия подпис. Целостта на целия файл не се проверява. Можете да проверите дали дадено изображение е повредено, например като се опитате да [го заредите |http:request#toImage]. +Брой елементи +------------- -За контролите `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()` могат да се използват и следните правила, за да се ограничи броят на избраните елементи, съответно изтеглените файлове: +При елементите `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()` могат да се използват и следните правила за ограничаване на броя на избраните елементи съответно качените файлове: | `MinLength` | минимален брой | `int` | `MaxLength` | максимален брой | `int` -| `Length` | сума в диапазон или точна сума | параграф `[int, int]` или `int` +| `Length` | брой в диапазон или точен брой | двойка `[int, int]` или `int` + + +Качване на файлове +------------------ + +При елементите `addUpload()`, `addMultiUpload()` могат да се използват и следните правила: + +| `MaxFileSize` | максимален размер на файла в байтове | `int` +| `MimeType` | MIME тип, разрешени са заместващи знаци (`'video/*'`) | `string\|string[]` +| `Image` | изображение JPEG, PNG, GIF, WebP, AVIF | - +| `Pattern` | името на файла съответства на регулярен израз | `string` +| `PatternInsensitive` | като `Pattern`, но независимо от големината на буквите | `string` + +`MimeType` и `Image` изискват PHP разширението `fileinfo`. Дали файлът или изображението е от изисквания тип се открива въз основа на неговата сигнатура и **не се проверява целостта на целия файл.** Дали изображението не е повредено може да се установи например чрез опит за неговото [зареждане |http:request#toImage]. -Съобщения за грешки .[#toc-error-messages] ------------------------------------------- +Съобщения за грешки +=================== -Всички предварително дефинирани правила, с изключение на `Pattern` и `PatternInsensitive`, имат съобщение за грешка по подразбиране, така че можете да ги пропуснете. Въпреки това, като предавате и формулирате всички отделни съобщения, ще направите формуляра по-удобен за ползване. +Всички предварително дефинирани правила с изключение на `Pattern` и `PatternInsensitive` имат съобщение за грешка по подразбиране, така че то може да бъде пропуснато. Въпреки това, като посочите и формулирате всички съобщения по мярка, ще направите формата по-удобна за потребителя. -Можете да промените съобщенията по подразбиране в [configuration |forms:configuration], като промените текстовете в масива `Nette\Forms\Validator::$messages` или като използвате [преводач |rendering#translating]. +Можете да промените съобщенията по подразбиране в [конфигурацията|forms:configuration], като редактирате текстовете в масива `Nette\Forms\Validator::$messages` или като използвате [преводач |rendering#Превод]. -В текста на съобщенията за грешка могат да се използват следните заместващи символи: +В текста на съобщенията за грешки могат да се използват следните заместващи низове: -| `%d` | поетапно замества правилата след аргументите -| `%n$d` | замества аргумента на n-тото правило -| `%label` | заменя с етикет на полето (без двоеточие) -| `%name` | замества името на полето (например `name`) -| `%value` | заменя със стойност, въведена от потребителя +| `%d` | заменя последователно с аргументите на правилото +| `%n$d` | заменя с n-тия аргумент на правилото +| `%label` | заменя с надписа на елемента (без двоеточие) +| `%name` | заменя с името на елемента (напр. `name`) +| `%value` | заменя с въведената от потребителя стойност ```php -$form->addText('name', 'Имя:') - ->setRequired('Пожалуйста, заполните %label'); +$form->addText('name', 'Име:') + ->setRequired('Моля, попълнете %label'); $form->addInteger('id', 'ID:') - ->addRule($form::Range, 'не менее %d и не более %d', [5, 10]); + ->addRule($form::Range, 'поне %d и най-много %d', [5, 10]); $form->addInteger('id', 'ID:') - ->addRule($form::Range, 'не более %2$d и не менее %1$d', [5, 10]); + ->addRule($form::Range, 'най-много %2$d и поне %1$d', [5, 10]); ``` -Условия .[#toc-conditions] -========================== +Условия +======= -В допълнение към правилата за валидиране могат да се задават и условия. Те се задават по същия начин като правилата, но вместо `addCondition()` използваме `addRule()` и, разбира се, ги оставяме без съобщение за грешка (условието просто пита): +Освен правила могат да се добавят и условия. Те се записват подобно на правилата, само че вместо `addRule()` използваме метода `addCondition()` и разбира се не посочваме никакво съобщение за грешка (условието само пита): ```php -$form->addPassword('password', 'Password:') - // ако паролата не е по-дълга от 8 символа ... +$form->addPassword('password', 'Парола:') + // ако паролата не е по-дълга от 8 знака ->addCondition($form::MaxLength, 8) - // ... тогава трябва да съдържа число - ->addRule($form::Pattern, 'Must contain a number', '.*[0-9].*'; + // тогава трябва да съдържа цифра + ->addRule($form::Pattern, 'Трябва да съдържа цифра', '.*[0-9].*'); ``` -Условието може да бъде обвързано с елемент, различен от текущия, с помощта на `addConditionOn()`. Първият параметър е връзка към полето. В следния случай имейлът ще се изисква само ако полето е маркирано (т.е. стойността му е `true`): +Условието може да бъде обвързано и с друг елемент освен текущия с помощта на `addConditionOn()`. Като първи параметър посочваме референция към елемента. В този пример имейлът ще бъде задължителен само тогава, когато се отметне чекбоксът (неговата стойност ще бъде true): ```php -$form->addCheckbox('newsletters', 'send me newsletters'); +$form->addCheckbox('newsletters', 'изпращайте ми бюлетини'); -$form->addEmail('email', 'Email:') - // ако квадратчето е маркирано ... +$form->addEmail('email', 'Имейл:') + // ако чекбоксът е отметнат ->addConditionOn($form['newsletters'], $form::Equal, true) - // ... изискване на имейл - ->setRequired('Въведете вашия имейл адрес'); + // тогава изисквай имейл + ->setRequired('Въведете имейл адрес'); ``` -Условията могат да бъдат групирани в сложни структури с помощта на методите `elseCondition()` и `endCondition()`. +От условията могат да се създават комплексни структури с помощта на `elseCondition()` и `endCondition()`: ```php $form->addText(/* ... */) - ->addCondition(/* ... */) // ако първото условие е изпълнено - ->addConditionOn(/* ... */) // и второто условие и за другия елемент - ->addRule(/* ... */) // изисква това правило + ->addCondition(/* ... */) // ако е изпълнено първото условие + ->addConditionOn(/* ... */) // и второто условие на друг елемент + ->addRule(/* ... */) // изисквай това правило ->elseCondition() // ако второто условие не е изпълнено - ->addRule(/* ... */) // изисква спазването на тези правила + ->addRule(/* ... */) // изисквай тези правила ->addRule(/* ... */) ->endCondition() // връщаме се към първото условие ->addRule(/* ... */); ``` -В Nette е много лесно да се реагира на изпълнението или неизпълнението на дадено условие от страна на JavaScript, като се използва методът `toggle()`, вж. раздел [Динамичен JavaScript |#Динамический JavaScript]. +В Nette може много лесно да се реагира на изпълнението или неизпълнението на условие и от страна на JavaScript с помощта на метода `toggle()`, виж [#Динамичен JavaScript]. -Връзки между контролите .[#toc-references-between-controls] -=========================================================== +Референция към друг елемент +=========================== -Аргумент на правило или условие може да бъде препратка към друг елемент. Например можете динамично да потвърдите, че `text` има толкова знаци, колкото са посочени в полето `length`: +Като аргумент на правило или условие може да се предаде и друг елемент на формата. Правилото тогава ще използва стойността, въведена по-късно от потребителя в браузъра. Така може например динамично да се валидира, че елементът `password` съдържа същия низ като елемента `password_confirm`: ```php -$form->addInteger('length'); -$form->addText('text') - ->addRule($form::Length, null, $form['length']); +$form->addPassword('password', 'Парола'); +$form->addPassword('password_confirm', 'Потвърдете паролата') + ->addRule($form::Equal, 'Въведените пароли не съвпадат', $form['password']); ``` -Потребителски правила и условия .[#toc-custom-rules-and-conditions] -=================================================================== +Персонализирани правила и условия +================================= -Понякога се сблъскваме със ситуация, в която вградените правила за валидиране в Nette не са достатъчни и трябва да валидираме данните от потребителя по наш собствен начин. В Nette това е много лесно! +Понякога се озоваваме в ситуация, когато вградените правила за валидация в Nette не са достатъчни и трябва да валидираме данните от потребителя по свой начин. В Nette това е много лесно! -Можете да предадете всяко обратно извикване като първи параметър на методите `addRule()` или `addCondition()`. Обратното извикване приема самия елемент като първи параметър и връща булева стойност, указваща, че проверката е била успешна. Когато добавяте правило с `addRule()`, можете да подадете допълнителни аргументи, които се предават като втори параметър. +На методите `addRule()` или `addCondition()` може да се предаде като първи параметър произволен callback. Той приема като първи параметър самия елемент и връща булева стойност, определяща дали валидацията е преминала успешно. При добавяне на правило с `addRule()` е възможно да се зададат и други аргументи, те след това се предават като втори параметър. -По този начин може да се създаде персонализиран набор от валидатори като клас със статични методи: +Така можем да създадем собствен набор от валидатори като клас със статични методи: ```php class MyValidators { - // проверява дали стойността е делима на аргумента + // проверява дали стойността се дели на аргумента public static function validateDivisibility(BaseControl $input, $arg): bool { return $input->getValue() % $arg === 0; @@ -175,23 +186,23 @@ class MyValidators public static function validateEmailDomain(BaseControl $input, $domain) { - // допълнителни валидатори + // други валидатори } } ``` -Тогава използването е много просто: +Използването след това е много лесно: ```php $form->addInteger('num') ->addRule( [MyValidators::class, 'validateDivisibility'], - 'Значение должно быть кратно %d', + 'Стойността трябва да е кратна на %d', 8, ); ``` -В JavaScript могат да се добавят и потребителски правила за валидиране. Единственото изискване е правилото да е статичен метод. Името му за валидатора на JavaScript се създава чрез конкатенация на името на класа без обратни наклонени черти `\`, подчеркивания `_` и името на метода. Например запишете `App\MyValidators::validateDivisibility` като `AppMyValidators_validateDivisibility` и го добавете към обекта `Nette.validators`: +Персонализирани правила за валидация могат да се добавят и в JavaScript. Условието е правилото да бъде статичен метод. Неговото име за JavaScript валидатора се образува чрез свързване на името на класа без обратни наклонени черти `\`, долна черта `_` и името на метода. Напр. `App\MyValidators::validateDivisibility` записваме като `AppMyValidators_validateDivisibility` и добавяме към обекта `Nette.validators`: ```js Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => { @@ -200,12 +211,12 @@ Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => ``` -Събитие OnValidate .[#toc-event-onvalidate] -=========================================== +Събитие onValidate +================== -След като формулярът бъде изпратен, валидирането се извършва чрез проверка на отделните правила, добавени с `addRule()`, и след това чрез извикване на [събитието |nette:glossary#Events] `onValidate`. Неговият манипулатор може да се използва за допълнително валидиране, обикновено за проверка дали комбинация от стойности в няколко елемента на формуляра е правилна. +След изпращане на формата се извършва валидация, при която се проверяват отделните правила, добавени с `addRule()`, и след това се извиква [събитие |nette:glossary#Събития events] `onValidate`. Неговият handler може да се използва за допълнителна валидация, типично проверка на правилната комбинация от стойности в няколко елемента на формата. -Ако бъде открита грешка, тя се предава на формуляра чрез метода `addError()`. Това може да бъде извикано за конкретен елемент или директно за формата. +Ако се открие грешка, я предаваме на формата с метода `addError()`. Той може да се извика или на конкретен елемент, или директно на формата. ```php protected function createComponentSignInForm(): Form @@ -219,16 +230,16 @@ protected function createComponentSignInForm(): Form public function validateSignInForm(Form $form, \stdClass $data): void { if ($data->foo > 1 && $data->bar > 5) { - $form->addError('Эта комбинация невозможна.'); + $form->addError('Тази комбинация не е възможна.'); } } ``` -Грешки при обработката .[#toc-processing-errors] -================================================ +Грешки при обработка +==================== -В много случаи откриваме грешка при обработката на самия формуляр, например когато записваме нов запис в базата данни и срещаме дублиран ключ. В този случай предаваме грешката обратно към формуляра, като използваме метода `addError()`. Това може да бъде извикано за конкретен елемент или директно за формата: +В много случаи научаваме за грешката едва когато обработваме валидната форма, например записваме нов елемент в базата данни и се натъкваме на дублиране на ключове. В такъв случай отново предаваме грешката на формата с метода `addError()`. Той може да се извика или на конкретен елемент, или директно на формата: ```php try { @@ -238,72 +249,71 @@ try { } catch (Nette\Security\AuthenticationException $e) { if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) { - $form->addError('Неверный пароль.'); + $form->addError('Невалидна парола.'); } } ``` -Ако е възможно, препоръчваме да добавите грешката директно към елемента на формата, тъй като при използване на изобразяването по подразбиране тя ще се показва до него. +Ако е възможно, препоръчваме да прикачите грешката директно към елемента на формата, тъй като тя ще се покаже до него при използване на рендеръра по подразбиране. ```php -$form['date']->addError('Извините, эта дата уже занята.'); +$form['date']->addError('Извиняваме се, но тази дата вече е заета.'); ``` -Можете да извикате `addError()` няколко пъти, за да предадете няколко съобщения за грешки на дадена форма или елемент. Извличате ги с функцията `getErrors()`. +Можете да извиквате `addError()` многократно и така да предавате на формата или елемента повече съобщения за грешки. Получавате ги с `getErrors()`. -Обърнете внимание, че `$form->getErrors()` връща обобщение на всички съобщения за грешки, дори и тези, които са били предадени директно на отделни елементи, а не само директно на формата. Съобщенията за грешки, предадени само на формата, се извличат чрез `$form->getOwnErrors()`. +Внимание, `$form->getErrors()` връща резюме на всички съобщения за грешки, включително тези, които са били предадени директно на отделни елементи, не само директно на формата. Съобщенията за грешки, предадени само на формата, получавате чрез `$form->getOwnErrors()`. -Промяна на входните стойности .[#toc-modifying-input-values] -============================================================ +Модификация на входа +==================== -С помощта на метода `addFilter()`, можем да променим стойността, въведена от потребителя. В този пример ще разрешим и премахнем интервалите в пощенския код: +С помощта на метода `addFilter()` можем да променим въведената от потребителя стойност. В този пример ще толерираме и премахваме интервали в пощенския код: ```php -$form->addText('zip', 'postcode:') +$form->addText('zip', 'Пощенски код:') ->addFilter(function ($value) { - return str_replace(' ', '', $value); // премахване на интервалите от индекса zip + return str_replace(' ', '', $value); // премахваме интервалите от пощенския код }) - ->addRule($form::Pattern, 'Postcode is not five digits', '\d{5}'; + ->addRule($form::Pattern, 'Пощенският код не е във формат от пет цифри', '\d{5}'); ``` -Филтърът е включен между правилата за валидиране и условията и следователно зависи от реда на методите, т.е. филтърът и правилото се извикват в същия ред като реда на методите `addFilter()` и `addRule()`. +Филтърът се интегрира между правилата за валидация и условията и следователно редът на методите има значение, т.е. филтърът и правилото се извикват в такъв ред, какъвто е редът на методите `addFilter()` и `addRule()`. -Удостоверяване на JavaScript .[#toc-javascript-validation] -========================================================== +JavaScript валидация +==================== -Езикът на правилата и условията за валидиране е мощен. Въпреки че всички конструкции работят както от страна на сървъра, така и от страна на клиента, в JavaScript. Правилата се предават в атрибути на HTML `data-nette-rules` като JSON. -Самото валидиране се извършва от друг скрипт, който прихваща всички събития на формуляра `submit`, преминава през всички входове и изпълнява съответните валидирания. +Езикът за формулиране на условия и правила е много мощен. Всички конструкции при това работят както от страна на сървъра, така и от страна на JavaScript. Пренасят се в HTML атрибути `data-nette-rules` като JSON. Самата валидация след това се извършва от скрипт, който прихваща събитието `submit` на формата, преминава през отделните елементи и извършва съответната валидация. -Този скрипт е `netteForms.js`, който е достъпен от няколко възможни източника: +Този скрипт е `netteForms.js` и е достъпен от няколко възможни източника: -Можете да вградите скрипта директно в HTML страница от CDN: +Можете да вмъкнете скрипта директно в HTML страницата от CDN: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Или копирайте локално в обща папка на проекта (например от `vendor/nette/forms/src/assets/netteForms.min.js`): +Или да го копирате локално в публичната папка на проекта (напр. от `vendor/nette/forms/src/assets/netteForms.min.js`): ```latte <script src="/path/to/netteForms.min.js"></script> ``` -Или инсталирайте чрез [npm |https://www.npmjs.com/package/nette-forms]: +Или да го инсталирате чрез [npm|https://www.npmjs.com/package/nette-forms]: ```shell npm install nette-forms ``` -След това го изтеглете и стартирайте: +И след това да го заредите и стартирате: ```js import netteForms from 'nette-forms'; netteForms.initOnLoad(); ``` -Освен това можете да го изтеглите директно от папката `vendor`: +Алтернативно можете да го заредите директно от папката `vendor`: ```js import netteForms from '../path/to/vendor/nette/forms/src/assets/netteForms.js'; @@ -311,10 +321,10 @@ netteForms.initOnLoad(); ``` -Динамичен JavaScript .[#toc-dynamic-javascript] -=============================================== +Динамичен JavaScript +==================== -Искате ли да показвате полета с адреси само ако потребителят реши да изпрати елемента по пощата? Няма проблем. Ключът е двойка методи `addCondition()` и `toggle()`: +Искате да покажете полетата за въвеждане на адрес само ако потребителят избере стоката да бъде изпратена по пощата? Няма проблем. Ключът е двойката методи `addCondition()` & `toggle()`: ```php $form->addCheckbox('send_it') @@ -322,25 +332,25 @@ $form->addCheckbox('send_it') ->toggle('#address-container'); ``` -Този код казва, че ако условието е изпълнено, т.е. ако квадратчето за отметка е маркирано, HTML елементът `#address-container` ще стане видим. И обратното. Така че поставяме елементите на формата с адреса на получателя в контейнер с този идентификатор и когато щракнем върху квадратчето за отметка, те се скриват или показват. С това се занимава скриптът `netteForms.js`. +Този код казва, че когато условието е изпълнено, т.е. когато чекбоксът е отметнат, ще бъде видим HTML елементът `#address-container`. И обратното. Елементите на формата с адреса на получателя така поставяме в контейнер с това ID и при кликване върху чекбокса те се скриват или показват. Това осигурява скриптът `netteForms.js`. -Всеки селектор може да бъде предаден като аргумент на метода `toggle()`. По исторически причини буквено-цифровият низ без други специални знаци се третира като идентификатор на елемента, точно както ако беше предшестван от `#`. Второй необязательный параметр позволяет нам изменить поведение, т. е. если бы мы использовали `toggle('#address-container', false)`, елементът ще се показва само когато квадратчето за отметка е премахнато. +Като аргумент на метода `toggle()` може да се предаде произволен селектор. По исторически причини буквено-цифров низ без други специални знаци се разбира като ID на елемент, т.е. същото, като че ли му предхожда знакът `#`. Вторият незадължителен параметър позволява да се обърне поведението, т.е. ако използваме `toggle('#address-container', false)`, елементът ще се покаже обратно само тогава, ако чекбоксът не е отметнат. -Изпълнението по подразбиране на JavaScript променя свойството `hidden` за елементите. Въпреки това можем лесно да променим поведението, например чрез добавяне на анимация. Просто заместете метода `Nette.toggle` в JavaScript със свое собствено решение: +Имплементацията по подразбиране в JavaScript променя свойството `hidden` на елементите. Поведението обаче можем лесно да променим, например да добавим анимация. Достатъчно е в JavaScript да презапишем метода `Nette.toggle` със собствено решение: ```js Nette.toggle = (selector, visible, srcElement, event) => { document.querySelectorAll(selector).forEach((el) => { - // скрыть или показать 'el' в зависимости от значения 'visible' + // скриваме или показваме 'el' според стойността на 'visible' }); }; ``` -Деактивиране на валидирането .[#toc-disabling-validation] -========================================================= +Изключване на валидацията +========================= -В някои случаи е необходимо да се деактивира валидирането. Ако бутонът за изпращане не трябва да се валидира след изпращане (като например бутон *Cancel* или *Preview*), можете да деактивирате валидирането, като извикате `$submit->setValidationScope([])`. Можете също така да валидирате формуляра частично, като посочите елементи за валидиране. +Понякога може да се наложи да изключите валидацията. Ако натискането на бутона за изпращане не трябва да извършва валидация (подходящо за бутони *Cancel* или *Preview*), я изключваме с метода `$submit->setValidationScope([])`. Ако трябва да извършва само частична валидация, можем да определим кои полета или контейнери на формата трябва да се валидират. ```php $form->addText('name') @@ -352,15 +362,15 @@ $details->addInteger('age') $details->addInteger('age2') ->setRequired('age2'); -$form->addSubmit('send1'); // проверява цялата форма +$form->addSubmit('send1'); // Валидира цялата форма $form->addSubmit('send2') - ->setValidationScope([]); // нищо не се валидира + ->setValidationScope([]); // Не валидира изобщо $form->addSubmit('send3') - ->setValidationScope([$form['name']]); // Валидира само полето 'name' + ->setValidationScope([$form['name']]); // Валидира само елемента name $form->addSubmit('send4') - ->setValidationScope([$form['details']['age']]; // Проверява се само полето 'age' + ->setValidationScope([$form['details']['age']]); // Валидира само елемента age $form->addSubmit('send5') - ->setValidationScope([$form['details']]); // Проверява контейнер 'details' + ->setValidationScope([$form['details']]); // Валидира контейнера details ``` -[Събитието onValidate |#Событие onValidate] на формата се извиква винаги и не зависи от `setValidationScope`. Събитието `onValidate` on container се извиква само когато този контейнер е зададен за частично валидиране. +`setValidationScope` не влияе на [#Събитие onValidate] на формата, която ще бъде извикана винаги. Събитието `onValidate` на контейнера ще бъде извикано само ако този контейнер е маркиран за частична валидация. diff --git a/forms/cs/@home.texy b/forms/cs/@home.texy index 70dd5d0105..496012fa21 100644 --- a/forms/cs/@home.texy +++ b/forms/cs/@home.texy @@ -1,5 +1,5 @@ -Formuláře -********* +Nette Forms +*********** <div class=perex> diff --git a/forms/cs/@left-menu.texy b/forms/cs/@left-menu.texy index 6289eb99ec..dbf61e5dbe 100644 --- a/forms/cs/@left-menu.texy +++ b/forms/cs/@left-menu.texy @@ -1,5 +1,5 @@ -Formuláře -********* +Nette Forms +*********** - [Úvod |@home] - [Formuláře v presenterech|in-presenter] - [Formuláře samostatně|standalone] diff --git a/forms/cs/@meta.texy b/forms/cs/@meta.texy new file mode 100644 index 0000000000..462d9add80 --- /dev/null +++ b/forms/cs/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokumentace}} diff --git a/forms/cs/configuration.texy b/forms/cs/configuration.texy index ffbd314602..313f3faa12 100644 --- a/forms/cs/configuration.texy +++ b/forms/cs/configuration.texy @@ -52,7 +52,7 @@ forms: MaxFileSize: 'Velikost nahraného souboru může být nejvýše %d bytů.' MaxPostSize: 'Nahraná data překračují limit %d bytů.' MimeType: 'Nahraný soubor není v očekávaném formátu.' - Image: 'Nahraný soubor musí být obraz ve formátu JPEG, GIF, PNG nebo WebP.' + Image: 'Nahraný soubor musí být obraz ve formátu JPEG, GIF, PNG, WebP nebo AVIF.' Nette\Forms\Controls\SelectBox::Valid: 'Vyberte prosím platnou možnost.' Nette\Forms\Controls\UploadControl::Valid: 'Při nahrávání souboru došlo k chybě.' Nette\Forms\Controls\CsrfProtection::Protection: 'Vaše relace vypršela. Vraťte se na domovskou stránku a zkuste to znovu.' diff --git a/forms/cs/controls.texy b/forms/cs/controls.texy index 2ac4b81719..8b3396fa72 100644 --- a/forms/cs/controls.texy +++ b/forms/cs/controls.texy @@ -5,8 +5,8 @@ Formulářové prvky Přehled standardních formulářových prvků. -addText(string|int $name, $label=null): TextInput .[method] -=========================================================== +addText(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +================================================================================================== Přidá jednořádkové textové políčko (třída [TextInput |api:Nette\Forms\Controls\TextInput]). Pokud uživatel pole nevyplní, vrací prázdný řetězec `''`, nebo pomocí `setNullable()` lze určit, aby vracel `null`. @@ -18,15 +18,12 @@ $form->addText('name', 'Jméno:') Automaticky validuje UTF-8, ořezává levo- a pravostranné mezery a odstraňuje odřádkování, které by mohl odeslat útočník. -Maximální délku lze omezit pomocí `setMaxLength()`. Pozměnit uživatelem vloženou hodnotu umožňuje [addFilter()|validation#Úprava vstupu]. +Maximální délku lze omezit pomocí `setMaxLength()`. Pozměnit uživatelem vloženou hodnotu umožňuje [addFilter() |validation#Úprava vstupu]. -Pomocí `setHtmlType()` lze změnit [charakter|https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types] vstupního prvku na `search`, `tel`, `url`, `range`, `date`, `datetime-local`, `month`, `time`, `week`, `color`. Místo typů `number` a `email` doporučujeme použít [#addInteger] a [#addEmail], které disponují validací na straně serveru. +Pomocí `setHtmlType()` lze změnit vizuální charakter textového pole na typy jako `search`, `tel` nebo `url` viz [specifikace|https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types]. Pamatujte, že změna typu je pouze vizuální a nezastupuje funkci validace. Pro typ `url` je vhodné přidat specifické validační [pravidlo URL |validation#Textové vstupy]. -```php -$form->addText('color', 'Vyberte barvu:') - ->setHtmlType('color') - ->addRule($form::Pattern, 'invalid value', '[0-9a-f]{6}'); -``` +.[note] +Pro další typy vstupů, jako `number`, `range`, `email`, `date`, `datetime-local`, `time` a `color`, použijte specializované metody jako [#addInteger], [#addFloat], [#addEmail] [#addDate], [#addTime], [#addDateTime] a [#addColor], které zajišťují serverovou validaci. Typy `month` a `week` zatím nejsou plně podporovány ve všech prohlížečích. Prvku lze nastavit tzv. empty-value, což je něco jako výchozí hodnota, ale pokud ji uživatel nezmění, vrátí prvek prázdný řetězec či `null`. @@ -49,7 +46,7 @@ $form->addTextArea('note', 'Poznámka:') Automaticky validuje UTF-8 a normalizuje oddělovače řádků na `\n`. Na rozdíl od jednořádkového vstupního políčka k žádnému ořezávání mezer nedochází. -Maximální délku lze omezit pomocí `setMaxLength()`. Pozměnit uživatelem vloženou hodnotu umožňuje [addFilter()|validation#Úprava vstupu]. Lze nastavit tzv. empty-value pomocí `setEmptyValue()`. +Maximální délku lze omezit pomocí `setMaxLength()`. Pozměnit uživatelem vloženou hodnotu umožňuje [addFilter() |validation#Úprava vstupu]. Lze nastavit tzv. empty-value pomocí `setEmptyValue()`. addInteger(string|int $name, $label=null): TextInput .[method] @@ -58,14 +55,31 @@ addInteger(string|int $name, $label=null): TextInput .[method] Přidá políčko pro zadání celočíselného čísla (třída [TextInput |api:Nette\Forms\Controls\TextInput]). Vrací buď integer, nebo `null`, pokud uživatel nic nezadá. ```php -$form->addInteger('level', 'Úroveň:') +$form->addInteger('year', 'Rok:') + ->addRule($form::Range, 'Rok musí být v rozsahu od %d do %d.', [1900, 2023]); +``` + +Prvek se vykresluje jako `<input type="number">`. Použitím metody `setHtmlType()` lze změnit typ na `range` pro zobrazení v podobě posuvníku, nebo na `text`, pokud preferujete standardní textové pole bez speciálního chování typu `number`. + + +addFloat(string|int $name, $label=null): TextInput .[method]{data-version:3.1.12} +================================================================================= + +Přidá políčko pro zadání desetinného čísla (třída [TextInput |api:Nette\Forms\Controls\TextInput]). Vrací buď float, nebo `null`, pokud uživatel nic nezadá. + +```php +$form->addFloat('level', 'Úroveň:') ->setDefaultValue(0) - ->addRule($form::Range, 'Úroveň musí být v rozsahu mezi %d a %d.', [0, 100]); + ->addRule($form::Range, 'Úroveň musí být v rozsahu od %d do %d.', [0, 100]); ``` +Prvek se vykresluje jako `<input type="number">`. Použitím metody `setHtmlType()` lze změnit typ na `range` pro zobrazení v podobě posuvníku, nebo na `text`, pokud preferujete standardní textové pole bez speciálního chování typu `number`. -addEmail(string|int $name, $label=null): TextInput .[method] -============================================================ +Nette a prohlížeč Chrome akceptují jako oddělovač desetinných míst jak čárku, tak tečku. Aby byla tato funkcionalita dostupná i ve Firefoxu, je doporučeno nastavit atribut `lang` buď pro daný prvek nebo pro celou stránku, například `<html lang="cs">`. + + +addEmail(string|int $name, $label=null, int $maxLength=255): TextInput .[method] +================================================================================ Přidá políčko pro zadání e-mailové adresy (třída [TextInput |api:Nette\Forms\Controls\TextInput]). Pokud uživatel pole nevyplní, vrací prázdný řetězec `''`, nebo pomocí `setNullable()` lze určit, aby vracel `null`. @@ -75,11 +89,11 @@ $form->addEmail('email', 'E-mail:'); Ověří, zda je hodnota platná e-mailová adresa. Neověřuje se, zda doména skutečně existuje, ověřuje se pouze syntaxe. Automaticky validuje UTF-8, ořezává levo- a pravostranné mezery. -Maximální délku lze omezit pomocí `setMaxLength()`. Pozměnit uživatelem vloženou hodnotu umožňuje [addFilter()|validation#Úprava vstupu]. Lze nastavit tzv. empty-value pomocí `setEmptyValue()`. +Maximální délku lze omezit pomocí `setMaxLength()`. Pozměnit uživatelem vloženou hodnotu umožňuje [addFilter() |validation#Úprava vstupu]. Lze nastavit tzv. empty-value pomocí `setEmptyValue()`. -addPassword(string|int $name, $label=null): TextInput .[method] -=============================================================== +addPassword(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +====================================================================================================== Přidá políčko pro zadání hesla (třída [TextInput |api:Nette\Forms\Controls\TextInput]). @@ -104,8 +118,8 @@ $form->addCheckbox('agree', 'Souhlasím s podmínkami') ``` -addCheckboxList(string|int $name, $label=null, array $items=null): CheckboxList .[method] -========================================================================================= +addCheckboxList(string|int $name, $label=null, ?array $items=null): CheckboxList .[method] +========================================================================================== Přidá zaškrtávací políčka pro výběr více položek (třída [CheckboxList |api:Nette\Forms\Controls\CheckboxList]). Vrací pole klíčů vybraných položek. Metoda `getSelectedItems()` vrací hodnoty místo klíčů. @@ -125,9 +139,15 @@ Prvek automaticky kontroluje, že nedošlo k podvržení a že vybrané položky Při nastavení výchozích vybraných položek také kontroluje, že jde o jedny z nabízených, jinak vyhodí výjimku. Tuto kontrolu lze vypnout pomocí `checkDefaultValue(false)`. +Pokud odesíláte formulář metodou `GET`, můžete zvolit kompaktnější způsob přenosu dat, který šetří velikost query stringu. Aktivuje se nastavením HTML atributu formuláře: -addRadioList(string|int $name, $label=null, array $items=null): RadioList .[method] -=================================================================================== +```php +$form->setHtmlAttribute('data-nette-compact'); +``` + + +addRadioList(string|int $name, $label=null, ?array $items=null): RadioList .[method] +==================================================================================== Přidá přepínací tlačítka (třída [RadioList |api:Nette\Forms\Controls\RadioList]). Vrací klíč vybrané položky, nebo `null`, pokud uživatel nic nevybral. Metoda `getSelectedItem()` vrací hodnotu místo klíče. @@ -148,8 +168,8 @@ Prvek automaticky kontroluje, že nedošlo k podvržení a že vybraná položka Při nastavení výchozí vybrané položky také kontroluje, že jde o jednou z nabízených, jinak vyhodí výjimku. Tuto kontrolu lze vypnout pomocí `checkDefaultValue(false)`. -addSelect(string|int $name, $label=null, array $items=null): SelectBox .[method] -================================================================================ +addSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): SelectBox .[method] +================================================================================================== Přidá select box (třída [SelectBox |api:Nette\Forms\Controls\SelectBox]). Vrací klíč vybrané položky, nebo `null`, pokud uživatel nic nevybral. Metoda `getSelectedItem()` vrací hodnotu místo klíče. @@ -193,8 +213,8 @@ Prvek automaticky kontroluje, že nedošlo k podvržení a že vybraná položka Při nastavení výchozí vybrané položky také kontroluje, že jde o jednou z nabízených, jinak vyhodí výjimku. Tuto kontrolu lze vypnout pomocí `checkDefaultValue(false)`. -addMultiSelect(string|int $name, $label=null, array $items=null): MultiSelectBox .[method] -========================================================================================== +addMultiSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): MultiSelectBox .[method] +============================================================================================================ Přidá select box pro výběr více položek (třída [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox]). Vrací pole klíčů vybraných položek. Metoda `getSelectedItems()` vrací hodnoty místo klíčů. @@ -214,11 +234,11 @@ Při nastavení výchozích vybraných položek také kontroluje, že jde o jedn addUpload(string|int $name, $label=null): UploadControl .[method] ================================================================= -Přidá políčko pro upload souboru (třída [UploadControl |api:Nette\Forms\Controls\UploadControl]). Vrací objekt [FileUpload|http:request#FileUpload] a to i v případě, že uživatel žádný soubor neodeslal, což lze zjistit metodou `FileUpload::hasFile()`. +Přidá políčko pro upload souboru (třída [UploadControl |api:Nette\Forms\Controls\UploadControl]). Vrací objekt [FileUpload |http:request#FileUpload] a to i v případě, že uživatel žádný soubor neodeslal, což lze zjistit metodou `FileUpload::hasFile()`. ```php $form->addUpload('avatar', 'Avatar:') - ->addRule($form::Image, 'Avatar musí být JPEG, PNG, GIF or WebP.') + ->addRule($form::Image, 'Avatar musí být JPEG, PNG, GIF, WebP or AVIF.') ->addRule($form::MaxFileSize, 'Maximální velikost je 1 MB.', 1024 * 1024); ``` @@ -226,13 +246,13 @@ Pokud se soubor nepodaří korektně nahrát, formulář není úspěšně odesl Nikdy nevěřte originálnímu názvu souboru vráceného metodou `FileUpload::getName()`, klient mohl odeslat škodlivý název souboru s úmyslem poškodit nebo hacknout vaši aplikaci. -Pravidla `MimeType` a `Image` detekují požadovaný typ na základě signatury souboru a neověřují jeho integritu. Zda není obrázek poškozený lze zjistit například pokusem o jeho [načtení|http:request#toImage]. +Pravidla `MimeType` a `Image` detekují požadovaný typ na základě signatury souboru a neověřují jeho integritu. Zda není obrázek poškozený lze zjistit například pokusem o jeho [načtení |http:request#toImage]. addMultiUpload(string|int $name, $label=null): UploadControl .[method] ====================================================================== -Přidá políčko pro upload více souboru najednou (třída [UploadControl |api:Nette\Forms\Controls\UploadControl]). Vrací pole objektů [FileUpload|http:request#FileUpload]. Metoda `FileUpload::hasFile()` u každého z nich bude vracet `true`. +Přidá políčko pro upload více souboru najednou (třída [UploadControl |api:Nette\Forms\Controls\UploadControl]). Vrací pole objektů [FileUpload |http:request#FileUpload]. Metoda `FileUpload::hasFile()` u každého z nich bude vracet `true`. ```php $form->addMultiUpload('files', 'Soubory:') @@ -243,11 +263,84 @@ Pokud se některý soubor nepodaří korektně nahrát, formulář není úspě Nikdy nevěřte originálním názvům souborů vráceným metodou `FileUpload::getName()`, klient mohl odeslat škodlivý název souboru s úmyslem poškodit nebo hacknout vaši aplikaci. -Pravidla `MimeType` a `Image` detekují požadovaný typ na základě signatury souboru a neověřují jeho integritu. Zda není obrázek poškozený lze zjistit například pokusem o jeho [načtení|http:request#toImage]. +Pravidla `MimeType` a `Image` detekují požadovaný typ na základě signatury souboru a neověřují jeho integritu. Zda není obrázek poškozený lze zjistit například pokusem o jeho [načtení |http:request#toImage]. + + +addDate(string|int $name, $label=null): DateTimeControl .[method]{data-version:3.1.14} +====================================================================================== + +Přidá políčko, které umožní uživateli snadno zadat datum skládající se z roku, měsíce a dne (třída [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Jako výchozí hodnotu akceptuje buď objekty implementující rozhraní `DateTimeInterface`, řetězec s časem, nebo číslo představující UNIX timestamp. Totéž platí pro argumenty pravidel `Min`, `Max` nebo `Range`, jež definují minimální a maximální povolený datum. + +```php +$form->addDate('date', 'Datum:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'Datum musí být minimálně měsíc staré.', new DateTime('-1 month')); +``` + +Standardně vrací objekt `DateTimeImmutable`, metodou `setFormat()` můžete specifikovat [textový formát|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] či timestamp: + +```php +$form->addDate('date', 'Datum:') + ->setFormat('Y-m-d'); +``` + +addTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=============================================================================================================== -addHidden(string|int $name, string $default=null): HiddenField .[method] -======================================================================== +Přidá políčko, které umožní uživateli snadno zadat čas skládající se z hodin, minut a volitelně i sekund (třída [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Jako výchozí hodnotu akceptuje buď objekty implementující rozhraní `DateTimeInterface`, řetězec s časem, nebo číslo představující UNIX timestamp. Z těchto vstupů je využita pouze časová informace, datum je ignorováno. Totéž platí pro argumenty pravidel `Min`, `Max` nebo `Range`, jež definují minimální a maximální povolený čas. Pokud je nastavená minimální hodnota vyšší než maximální, vytvoří se časový rozsah přesahující půlnoc. + +```php +$form->addTime('time', 'Čas:', withSeconds: true) + ->addRule($form::Range, 'Čas musí být v rozsahu od %d do %d.', ['12:30', '13:30']); +``` + +Standardně vrací objekt `DateTimeImmutable` (s datem 1. ledna roku 1), metodou `setFormat()` můžete specifikovat [textový formát|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters]: + +```php +$form->addTime('time', 'Čas:') + ->setFormat('H:i'); +``` + + +addDateTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=================================================================================================================== + +Přidá políčko, které umožní uživateli snadno zadat datum a čas skládající se z roku, měsíce, dne, hodin, minut a volitelně i sekund (třída [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Jako výchozí hodnotu akceptuje buď objekty implementující rozhraní `DateTimeInterface`, řetězec s časem, nebo číslo představující UNIX timestamp. Totéž platí pro argumenty pravidel `Min`, `Max` nebo `Range`, jež definují minimální a maximální povolený datum. + +```php +$form->addDateTime('datetime', 'Datum a čas:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'Datum musí být minimálně měsíc staré.', new DateTime('-1 month')); +``` + +Standardně vrací objekt `DateTimeImmutable`, metodou `setFormat()` můžete specifikovat [textový formát|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] či timestamp: + +```php +$form->addDateTime('datetime') + ->setFormat(DateTimeControl::FormatTimestamp); +``` + + +addColor(string|int $name, $label=null): ColorPicker .[method]{data-version:3.1.14} +=================================================================================== + +Přidá políčko pro výběr barvy (třída [ColorPicker |api:Nette\Forms\Controls\ColorPicker]). Barva je řetězec ve tvaru `#rrggbb`. Pokud uživatel volbu neprovede, vrátí se černá barva `#000000`. + +```php +$form->addColor('color', 'Barva:') + ->setDefaultValue('#3C8ED7'); +``` + + +addHidden(string|int $name, ?string $default=null): HiddenField .[method] +========================================================================= Přidá skryté pole (třída [HiddenField |api:Nette\Forms\Controls\HiddenField]). @@ -255,7 +348,9 @@ Přidá skryté pole (třída [HiddenField |api:Nette\Forms\Controls\HiddenField $form->addHidden('userid'); ``` -Pomocí `setNullable()` lze nastavit, aby vracel `null` místo prázdného řetězce. Pozměnit odeslanou hodnotu umožňuje [addFilter()|validation#Úprava vstupu]. +Pomocí `setNullable()` lze nastavit, aby vracel `null` místo prázdného řetězce. Pozměnit odeslanou hodnotu umožňuje [addFilter() |validation#Úprava vstupu]. + +Ačkoli je prvek skrytý, je **důležité si uvědomit**, že hodnota může být stále modifikována nebo podvržena útočníkem. Vždy důkladně ověřujte a validujte všechny přijaté hodnoty na serverové straně, aby se předešlo bezpečnostním rizikům spojeným s manipulací dat. addSubmit(string|int $name, $caption=null): SubmitButton .[method] @@ -282,7 +377,7 @@ if ($form['register']->isSubmittedBy()) { } ``` -Pokud nechcete validovat celý formulář při stisknutí tlačítka (například u tlačítek *Zrušit* nebo *Náhled*), použijte [setValidationScope()|validation#Vypnutí validace]. +Pokud nechcete validovat celý formulář při stisknutí tlačítka (například u tlačítek *Zrušit* nebo *Náhled*), použijte [setValidationScope() |validation#Vypnutí validace]. addButton(string|int $name, $caption): Button .[method] @@ -296,8 +391,8 @@ $form->addButton('raise', 'Zvýšit plat') ``` -addImageButton(string|int $name, string $src=null, string $alt=null): ImageButton .[method] -=========================================================================================== +addImageButton(string|int $name, ?string $src=null, ?string $alt=null): ImageButton .[method] +============================================================================================= Přidá odesílací tlačítko v podobě obrázku (třída [ImageButton |api:Nette\Forms\Controls\ImageButton]). @@ -346,26 +441,26 @@ U všech prvků můžeme volat následující metody (kompletní přehled v [API .[table-form-methods language-php] | `setDefaultValue($value)` | nastaví výchozí hodnotu -| `getValue()` | získat aktuální hodnotu +| `getValue()` | získá aktuální hodnotu | `setOmitted()` | [#vynechání hodnoty] | `setDisabled()` | [#deaktivace prvků] Vykreslování: .[table-form-methods language-php] | `setCaption($caption)` | změní popisku prvku -| `setTranslator($translator)` | nastaví [překladač|rendering#Překládání] +| `setTranslator($translator)` | nastaví [překladač |rendering#Překládání] | `setHtmlAttribute($name, $value)` | nastaví [HTML atribut |rendering#HTML atributy] elementu | `setHtmlId($id)` | nastaví HTML atribut `id` | `setHtmlType($type)` | nastaví HTML atribut `type` | `setHtmlName($name)` | nastaví HTML atribut `name` -| `setOption($key, $value)` | [nastavení pro vykreslování|rendering#Options] +| `setOption($key, $value)` | [nastavení pro vykreslování |rendering#Options] Validace: .[table-form-methods language-php] | `setRequired()` | [povinný prvek |validation] -| `addRule()` | nastavení [validační pravidlo |validation#Pravidla] -| `addCondition()`, `addConditionOn()` | nastaví [validační podmínku|validation#Podmínky] -| `addError($message)` | [předání chybové zprávy|validation#chyby-pri-zpracovani] +| `addRule()` | nastaví [validační pravidlo |validation#Pravidla] +| `addCondition()`, `addConditionOn()` | nastaví [validační podmínku |validation#Podmínky] +| `addError($message)` | [předání chybové zprávy |validation#Chyby při zpracování] U prvků `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` lze volat následující metody: @@ -377,7 +472,7 @@ U prvků `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addIntege Vynechání hodnoty ------------------ +================= Pokud nás uživatelem vyplněná hodnota nezajímá, můžeme ji pomocí `setOmitted()` vynechat z výsledku metody `$form->getValues()` nebo z dat předávaných do handlerů. To se hodí pro různá hesla pro kontrolu, antispamové prvky atd. @@ -390,7 +485,7 @@ $form->addPassword('passwordVerify', 'Heslo pro kontrolu:') Deaktivace prvků ----------------- +================ Prvky lze deaktivovat pomocí `setDisabled()`. Takový prvek nemůže uživatel editovat. @@ -399,9 +494,9 @@ $form->addText('username', 'Uživatelské jméno:') ->setDisabled(); ``` -Upozorňujeme, že disablované prvky prohlížeč vůbec neodesílá na server, tedy je ani nenajdete v datech vrácených funkcí `$form->getValues()`. +Disablované prvky prohlížeč vůbec neodesílá na server, tedy je ani nenajdete v datech vrácených funkcí `$form->getValues()`. Pokud však nastavíte `setOmitted(false)`, Nette do těchto dat zahrne jejich výchozí hodnotu. -Pokud prvku nastavujete výchozí hodnotu, je tak nutné učinit až po jeho deaktivaci: +Při volání `setDisabled()` se z bezpečnostních důvodů **smaže hodnota prvku**. Pokud nastavujete výchozí hodnotu, je tak nutné učinit až po jeho deaktivaci: ```php $form->addText('username', 'Uživatelské jméno:') @@ -409,6 +504,8 @@ $form->addText('username', 'Uživatelské jméno:') ->setDefaultValue($userName); ``` +Alternativou disablovaných prvků jsou prvky s HTML atributem `readonly`, které prohlížeč na server posílá. Ačkoliv je prvek pouze pro čtení, je **důležité si uvědomit**, že jeho hodnota může být stále modifikována nebo podvržena útočníkem. + Vlastní prvky ============= @@ -421,15 +518,15 @@ $form->addComponent(new DateInput('Datum:'), 'date'); ``` .[note] -Formulář je potomkem třídy [Container| component-model:#Container] a jednotlivé prvky jsou potomky [Component | component-model:#Component]. +Formulář je potomkem třídy [Container |component-model:#Container] a jednotlivé prvky jsou potomky [Component |component-model:#Component]. Existuje způsob, jak definovat nové metody formuláře sloužící k přidávání vlastních prvků (např. `$form->addZip()`). Jde o tzv. extension methods. Nevýhoda je, že pro ně nebude fungovat napovídání v editorech. ```php use Nette\Forms\Container; -// přidáme metodu addZip(string $name, string $label = null) -Container::extensionMethod('addZip', function (Container $form, string $name, string $label = null) { +// přidáme metodu addZip(string $name, ?string $label = null) +Container::extensionMethod('addZip', function (Container $form, string $name, ?string $label = null) { return $form->addText($name, $label) ->addRule($form::Pattern, 'Alespoň 5 čísel', '[0-9]{5}'); }); @@ -460,20 +557,3 @@ $data = $form->getHttpData($form::DataText | $form::DataKeys, 'sel[]'); kde první parametr je typ elementu (`DataFile` pro `type=file`, `DataLine` pro jednořádkové vstupy jako `text`, `password`, `email` apod. a `DataText` pro všechny ostatní) a druhý parametr `sel[]` odpovídá HTML atributu name. Typ elementu můžeme kombinovat s hodnotou `DataKeys`, která zachová klíče prvků. To se hodí zejména pro `select`, `radioList` a `checkboxList`. Podstatné je, že `getHttpData()` vrací sanitizovanou hodnotu, v tomto případě to bude vždy pole validních UTF-8 řetězců, ať už by se pokusil útočník serveru podstrčit cokoliv. Jde o obdobu přímé práce s `$_POST` nebo `$_GET` avšak s tím podstatným rozdílem, že vždy vrací čistá data, tak, jak jste zvyklí u standardních prvků Nette formulářů. - - - -/--comment -Prvky je rovněž možné přidat pomocí metody [setItems() |api:Nette\Forms\Controls\SelectBox::setItems()]. Pokud chceme místo klíčů položek získat přímo jejich hodnoty, můžeme toho docílit druhým argumentem: - -```php -$form->addSelect('country', 'Země:') - ->setItems($countries, false); -``` - -```php -// pro vypsání možností do 1 řádku -$form->addRadioList('gender', 'Pohlaví:', $sex) - ->getSeparatorPrototype()->setName(null); -``` -\-- diff --git a/forms/cs/in-presenter.texy b/forms/cs/in-presenter.texy index f3174f1c0a..2c827d31f7 100644 --- a/forms/cs/in-presenter.texy +++ b/forms/cs/in-presenter.texy @@ -19,7 +19,7 @@ $form = new Form; $form->addText('name', 'Jméno:'); $form->addPassword('password', 'Heslo:'); $form->addSubmit('send', 'Registrovat'); -$form->onSuccess[] = [$this, 'formSucceeded']; +$form->onSuccess[] = $this->formSucceeded(...); ``` a v prohlížeči se zobrazí takto: @@ -30,7 +30,7 @@ Formulář v presenteru je objekt třídy `Nette\Application\UI\Form`, její př Z pohledu presenteru je formulář běžná komponenta. Proto se s ním jako s komponentou zachází a začleníme ji do presenteru pomocí [tovární metody |application:components#Tovární metody]. Bude to vypadat takto: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} use Nette; use Nette\Application\UI\Form; @@ -42,11 +42,11 @@ class HomePresenter extends Nette\Application\UI\Presenter $form->addText('name', 'Jméno:'); $form->addPassword('password', 'Heslo:'); $form->addSubmit('send', 'Registrovat'); - $form->onSuccess[] = [$this, 'formSucceeded']; + $form->onSuccess[] = $this->formSucceeded(...); return $form; } - public function formSucceeded(Form $form, $data): void + private function formSucceeded(Form $form, $data): void { // tady zpracujeme data odeslaná formulářem // $data->name obsahuje jméno @@ -59,21 +59,19 @@ class HomePresenter extends Nette\Application\UI\Presenter A v šabloně formulář vykreslíme značkou `{control}`: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} <h1>Registrace</h1> {control registrationForm} ``` -A to je vlastně vše :-) Máme funkční a perfektně [zabezpečený|#Ochrana před zranitelnostmi] formulář. +A to je vlastně vše :-) Máme funkční a perfektně [zabezpečený |#Ochrana před zranitelnostmi] formulář. A teď si nejspíš říkáte, že to bylo moc hrr, přemýšlíte, jak je možné, že se zavolá metoda `formSucceeded()` a co jsou parametry, které dostává. Jistě, máte pravdu, tohle si zaslouží vysvětlení. -Nette totiž přichází se svěžím mechanismem, kterému říkáme [Hollywood style|application:components#Hollywood style]. Místo toho, abyste se jako vývojář musel neustále vyptávat, jestli se něco událo („byl formulář odeslaný?“, „byl odeslaný validně?“ a „nedošlo k jeho podvržení?“), řeknete frameworku „až bude formulář validně vyplněný, zavolej tuhle metodu“ a necháte další práci na něm. Pokud programujete v JavaScriptu, tento styl programování důvěrně znáte. Píšete funkce, které se volají, až nastane určitá [událost|nette:glossary#Události]. A jazyk jim předává příslušné argumenty. +Nette totiž přichází se svěžím mechanismem, kterému říkáme [Hollywood style |application:components#Hollywood style]. Místo toho, abyste se jako vývojář musel neustále vyptávat, jestli se něco událo („byl formulář odeslaný?“, „byl odeslaný validně?“ a „nedošlo k jeho podvržení?“), řeknete frameworku „až bude formulář validně vyplněný, zavolej tuhle metodu“ a necháte další práci na něm. Pokud programujete v JavaScriptu, tento styl programování důvěrně znáte. Píšete funkce, které se volají, až nastane určitá [událost |nette:glossary#události]. A jazyk jim předává příslušné argumenty. -Právě takhle je postaven i výše uvedený kód presenteru. Pole `$form->onSuccess` představuje seznam PHP callbacků, které Nette zavolá v okamžiku, kdy je formulář odeslán a správně vyplněn (tj. je validní). -V rámci [životního cyklu presenteru |application:presenters#zivotni-cyklus-presenteru] jde o tzv. signál, volají se tedy po `action*` metodě a před `render*` metodou. -A každému callbacku předá jako první parametr samotný formulář a jako druhý odeslaná data v podobě objektu [ArrayHash |utils:arrays#ArrayHash]. První parametr můžete vynechat, pokud objekt formuláře nepotřebujete. A druhý parametr umí být mazanější, ale o tom až [později|#Mapování na třídy]. +Právě takhle je postaven i výše uvedený kód presenteru. Pole `$form->onSuccess` představuje seznam PHP callbacků, které Nette zavolá v okamžiku, kdy je formulář odeslán a správně vyplněn (tj. je validní). V rámci [životního cyklu presenteru |application:presenters#Životní cyklus presenteru] jde o tzv. signál, volají se tedy po `action*` metodě a před `render*` metodou. A každému callbacku předá jako první parametr samotný formulář a jako druhý odeslaná data v podobě objektu [ArrayHash |utils:arrays#ArrayHash]. První parametr můžete vynechat, pokud objekt formuláře nepotřebujete. A druhý parametr umí být mazanější, ale o tom až [později |#Mapování na třídy]. Objekt `$data` obsahuje klíče `name` a `password` s údaji, které vyplnil uživatel. Obvykle data rovnou posíláme k dalšímu zpracování, což může být například vložení do databáze. Během zpracování se ale může objevit chyba, například uživatelské jméno už je obsazené. V takovém případě chybu předáme zpět do formuláře pomocí `addError()` a necháme jej vykreslit znovu, i s chybovou hláškou. @@ -128,11 +126,10 @@ Zkuste si odeslat formulář bez vyplněného jména a uvidíte, že se zobrazí Zároveň systém neošidíte tím, že do políčka napíšete třeba jen mezery. Kdepak. Nette levo- i pravostranné mezery automaticky odstraňuje. Vyzkoušejte si to. Je to věc, kterou byste měli s každým jednořádkovým inputem vždy udělat, ale často se na to zapomíná. Nette to dělá automaticky. (Můžete zkusit ošálit formulář a jako jméno poslat víceřádkový řetězec. Ani tady se Nette nenechá zmást a odřádkování změní na mezery.) -Formulář se vždy validuje na straně serveru, ale také se generuje JavaScriptová validace, která proběhne bleskově a uživatel se o chybě dozví okamžitě, bez nutnosti formulář odesílat na server. Tohle má na starosti skript `netteForms.js`. -Vložte jej do šablony layoutu: +Formulář se vždy validuje na straně serveru, ale také se generuje JavaScriptová validace, která proběhne bleskově a uživatel se o chybě dozví okamžitě, bez nutnosti formulář odesílat na server. Tohle má na starosti skript `netteForms.js`. Vložte jej do šablony layoutu: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` Pokud se podíváte do zdrojového kódu stránky s formulářem, můžete si všimnout, že Nette povinné prvky vkládá do elementů s CSS třídou `required`. Zkuste přidat do šablony následující stylopis a popiska „Jméno“ bude červená. Elegantně tak uživatelům vyznačíme povinné prvky: @@ -169,7 +166,7 @@ $form->addPassword('password', 'Heslo:') ->addRule($form::MinLength, 'Heslo musí mít alespoň %d znaků', 8); ``` -Přidáme do formuláře ještě políčko `passwordVerify`, kde uživatel zadá heslo ještě jednou, pro kontrolu. Pomocí validačních pravidel zkontrolujeme, zda jsou obě hesla stejná (`$form::Equal`). A jako parametr dáme odvolávku na první heslo pomocí [hranatých závorek|#Přístup k prvkům]: +Přidáme do formuláře ještě políčko `passwordVerify`, kde uživatel zadá heslo ještě jednou, pro kontrolu. Pomocí validačních pravidel zkontrolujeme, zda jsou obě hesla stejná (`$form::Equal`). A jako parametr dáme odvolávku na první heslo pomocí [hranatých závorek |#Přístup k prvkům]: ```php $form->addPassword('passwordVerify', 'Heslo pro kontrolu:') @@ -227,25 +224,27 @@ Vraťme se k metodě `formSucceeded()`, která ve druhém parametru `$data` dost class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -Od PHP 8.0 můžete použít tento elegantní zápis, který využívá konstruktor: +Alternativně můžete využít konstruktor: ```php class RegistrationFormData { public function __construct( public string $name, - public int $age, + public ?int $age, public string $password, ) { } } ``` +Property datové třídy mohou být také enumy a dojde k jejich automatickému namapování. .{data-version:3.2.4} + Jak říci Nette, aby nám data vracel jako objekty této třídy? Snadněji než si myslíte. Stačí pouze třídu uvést jako typ parametru `$data` v obslužné metodě: ```php @@ -283,7 +282,7 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` @@ -294,24 +293,26 @@ Mapování pak z typu property `$person` pozná, že má kontejner mapovat na t $person->setMappedType(PersonFormData::class); ``` +Návrh datové třídy formuláře si můžete nechat vygenerovat pomocí metody `Nette\Forms\Blueprint::dataClass($form)`, která ji vypíše do stránky prohlížeče. Kód pak stačí kliknutím označit a zkopírovat do projektu. .{data-version:3.1.15} + Více tlačítek ============= -Pokud má formulář více než jedno tlačítko, potřebujeme zpravidla rozlišit, které z nich bylo stlačeno. Můžeme si pro každé tlačítko vytvořit vlastní obslužnou funkci. Nastavíme ji jako handler pro [událost|nette:glossary#Události] `onClick`: +Pokud má formulář více než jedno tlačítko, potřebujeme zpravidla rozlišit, které z nich bylo stlačeno. Můžeme si pro každé tlačítko vytvořit vlastní obslužnou funkci. Nastavíme ji jako handler pro [událost |nette:glossary#události] `onClick`: ```php $form->addSubmit('save', 'Uložit') - ->onClick[] = [$this, 'saveButtonPressed']; + ->onClick[] = $this->saveButtonPressed(...); $form->addSubmit('delete', 'Smazat') - ->onClick[] = [$this, 'deleteButtonPressed']; + ->onClick[] = $this->deleteButtonPressed(...); ``` Tyto handlery se volají pouze v případě validně vyplněného formuláře, stejně jako v případě události `onSuccess`. Rozdíl je v tom, že jako první parametr se místo formulář může předat odesílací tlačítko, záleží na typu, který uvedete: ```php -public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) +private function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) { $form = $button->getForm(); // ... @@ -346,7 +347,7 @@ Ochrana před zranitelnostmi Nette Framework klade velký důraz na bezpečnost a proto úzkostlivě dbá na dobré zabezpečení formulářů. Dělá to zcela transparentně a nevyžaduje manuálně nic nastavovat. -Kromě toho, že formuláře ochrání před útokem [Cross Site Scripting (XSS) |nette:glossary#cross-site-scripting-xss] a [Cross-Site Request Forgery (CSRF)|nette:glossary#Cross-Site Request Forgery (CSRF)], dělá spoustu drobných zabezpečení, na které vy už nemusíte myslet. +Kromě toho, že formuláře ochrání před útokem [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] a [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF], dělá spoustu drobných zabezpečení, na které vy už nemusíte myslet. Tak třeba odfiltruje ze vstupů všechny kontrolní znaky a prověří validitu UTF-8 kódování, takže data z formuláře budou vždycky čistá. U select boxů a radio listů ověřuje, že vybrané položky byly skutečně z nabízených a nedošlo k podvrhu. Už jsme zmiňovali, že u jednořádkových textových vstupů ostraňuje znaky konce řádků, které tam mohl poslat útočník. U víceřádkových vstupů zase normalizuje znaky pro konce řádků. A tak dále. @@ -364,8 +365,7 @@ Tato ochrana využívá SameSite cookie pojmenovanou `_nss`. Ochrana pomocí Sam $form->addProtection(); ``` -Doporučujeme takto chránit formuláře v administrační části webu, které mění citlivá data v aplikaci. Framework se proti útoku CSRF brání vygenerováním a ověřováním autorizačního tokenu, který se ukládá do session. Proto je nutné před zobrazením formuláře mít otevřenou session. V administrační části webu obvykle už session nastartovaná je kvůli přihlášení uživatele. -Jinak session nastartujte metodou `Nette\Http\Session::start()`. +Doporučujeme takto chránit formuláře v administrační části webu, které mění citlivá data v aplikaci. Framework se proti útoku CSRF brání vygenerováním a ověřováním autorizačního tokenu, který se ukládá do session. Proto je nutné před zobrazením formuláře mít otevřenou session. V administrační části webu obvykle už session nastartovaná je kvůli přihlášení uživatele. Jinak session nastartujte metodou `Nette\Http\Session::start()`. Stejný formulář ve více presenterech @@ -403,7 +403,7 @@ protected function createComponentSignInForm(): Form $form = $this->formFactory->create(); // můžeme formulář pozměnit, zde například měníme popisku na tlačítku $form['send']->setCaption('Pokračovat'); - $form->onSuccess[] = [$this, 'signInFormSuceeded']; // a přidáme handler + $form->onSuccess[] = $this->signInFormSuceeded(...); // a přidáme handler return $form; } ``` @@ -429,13 +429,3 @@ class SignInFormFactory ``` Tak, máme za sebou rychlý úvod do formulářů v Nette. Zkuste se ještě podívat do adresáře [examples|https://github.com/nette/forms/tree/master/examples] v distrubuci, kde najdete další inspiraci. - - -/--comment -Občas se může hodit formulář resetovat do stavu před jeho odesláním. To je možné zařídit zavoláním metody `reset()` na odeslaném formuláři: -```php -$form->isSubmitted(); // true -$form->reset(); //formulář je nyní ve stavu, jako by nebyl nikdy odeslán -$form->isSubmitted(); // false -``` -\--- diff --git a/forms/cs/rendering.texy b/forms/cs/rendering.texy index ec272f49fb..e1231ad302 100644 --- a/forms/cs/rendering.texy +++ b/forms/cs/rendering.texy @@ -11,6 +11,8 @@ Vykreslení pomocí Latte [Šablonovací sytém Latte|latte:] zásadně usnadňuje vykreslení formulářů a jejich prvků. Nejprve si ukážeme, jak formuláře vykreslovat ručně po jednotlivých prvcích a tím získat plnou kontrolu nad kódem. Později si ukážeme, jak lze takové vykreslování [zautomatizovat |#Automatické vykreslování]. +Návrh Latte šablony formuláře si můžete nechat vygenerovat pomocí metody `Nette\Forms\Blueprint::latte($form)`, která jej vypíše do stránky prohlížeče. Kód pak stačí kliknutím označit a zkopírovat do projektu. .{data-version:3.1.15} + `{control}` ----------- @@ -21,7 +23,7 @@ Nejjednodušší způsob, jak vykreslit formulář, je napsat v šabloně: {control signInForm} ``` -Ovlivnit podobu takto vykresleného formuláře lze konfigurací [Rendereru|#Renderer] a [jednotlivých prvků|#HTML atributy]. +Ovlivnit podobu takto vykresleného formuláře lze konfigurací [Rendereru |#Renderer] a [jednotlivých prvků |#HTML atributy]. `n:name` @@ -54,8 +56,7 @@ protected function createComponentSignInForm(): Form </form> ``` -Podobu výsledného HTML kódu máte plně ve svých rukou. Pokud atribut `n:name` použijete u elementů `<select>`, `<button>` nebo `<textarea>`, jejich vnitřní obsah se automaticky doplní. -Značka `<form n:name>` navíc vytvoří lokální proměnnou `$form` s objektem kresleného formuláře a uzavírací `</form>` vykreslí všechny nevykreslené hidden prvky (totéž platí i pro `{form} ... {/form}`). +Podobu výsledného HTML kódu máte plně ve svých rukou. Pokud atribut `n:name` použijete u elementů `<select>`, `<button>` nebo `<textarea>`, jejich vnitřní obsah se automaticky doplní. Značka `<form n:name>` navíc vytvoří lokální proměnnou `$form` s objektem kresleného formuláře a uzavírací `</form>` vykreslí všechny nevykreslené hidden prvky (totéž platí i pro `{form} ... {/form}`). Nesmíme ovšem zapomenout na vykreslení možných chybových zpráv. A to jak těch, které se metodou `addError()` přidaly k jednotlivým prvkům (pomocí `{inputError}`), tak i těch přidaných přímo k formuláři (vrací je `$form->getOwnErrors()`): @@ -88,12 +89,6 @@ Složitější formulářové prvky, jako je RadioList nebo CheckboxList, lze ta ``` -Návrh kódu `{formPrint}` .[#toc-formprint] ------------------------------------------- - -Podobný Latte kód pro formulář si můžete nechat vygenerovat pomocí značky `{formPrint}`. Pokud ji umístíte do šablony, místo běžného vykreslení se zobrazí návrh kódu. Ten pak stačí označit a zkopírovat do projektu. - - `{label}` `{input}` ------------------- @@ -135,8 +130,7 @@ Pro vykreslení samotného `<input>` v prvku Checkbox použijte `{input myCheckb `{inputError}` -------------- -Vypíše chybovou zprávu k formulářovému prvku, pokud nějakou má. Zprávu obvykle zabalíme do HTML elementu kvůli stylování. -Předejít vykreslování prázdného elementu, pokud zpráva není, lze elegantně pomocí `n:ifcontent`: +Vypíše chybovou zprávu k formulářovému prvku, pokud nějakou má. Zprávu obvykle zabalíme do HTML elementu kvůli stylování. Předejít vykreslování prázdného elementu, pokud zpráva není, lze elegantně pomocí `n:ifcontent`: ```latte <span class=error n:ifcontent>{inputError $input}</span> @@ -161,8 +155,7 @@ Značky `{form signInForm}...{/form}` jsou alternativou k `<form n:name="signInF Automatické vykreslování ------------------------ -Díky značkám `{input}` a `{label}` můžeme snadno vytvořit obecnou šablonu pro jakýkoliv formulář. Bude postupně iterovat a vykreslovat všechny jeho prvky, kromě hidden prvků, které se vykreslí automaticky při ukončení formuláře značkou `</form>`. -Název vykreslovaného formuláře bude očekávat v proměnné `$form`. +Díky značkám `{input}` a `{label}` můžeme snadno vytvořit obecnou šablonu pro jakýkoliv formulář. Bude postupně iterovat a vykreslovat všechny jeho prvky, kromě hidden prvků, které se vykreslí automaticky při ukončení formuláře značkou `</form>`. Název vykreslovaného formuláře bude očekávat v proměnné `$form`. ```latte <form n:name=$form class=form> @@ -187,8 +180,7 @@ Tuto obecnou šablonu si uložte třeba do souboru `basic-form.latte` a pro vykr {include basic-form.latte, form: signInForm} ``` -Pokud byste při vykreslování jednoho určitého formuláře chtěli do jeho podoby zasáhnout a třeba jeden prvek vykreslit jinak, pak je nejjednodušší cestou si v šabloně předpřipravit bloky, které bude možné následně přepsat. -Bloky mohou mít také [dynamické názvy |latte:template-inheritance#dynamicke-nazvy-bloku], lze do nich tak vložit i jméno vykreslovaného prvku. Například: +Pokud byste při vykreslování jednoho určitého formuláře chtěli do jeho podoby zasáhnout a třeba jeden prvek vykreslit jinak, pak je nejjednodušší cestou si v šabloně předpřipravit bloky, které bude možné následně přepsat. Bloky mohou mít také [dynamické názvy |latte:template-inheritance#Dynamické názvy bloků], lze do nich tak vložit i jméno vykreslovaného prvku. Například: ```latte ... @@ -197,7 +189,7 @@ Bloky mohou mít také [dynamické názvy |latte:template-inheritance#dynamicke- ... ``` -Pro prvek např. `username` tak vznikne blok `input-username`, který lze snadno přepsat použitím značky [{embed} |latte:template-inheritance#jednotkova-dedicnost]: +Pro prvek např. `username` tak vznikne blok `input-username`, který lze snadno přepsat použitím značky [{embed} |latte:template-inheritance#Jednotková dědičnost]: ```latte {embed basic-form.latte, form: signInForm} @@ -209,7 +201,7 @@ Pro prvek např. `username` tak vznikne blok `input-username`, který lze snadno {/embed} ``` -Alternativně lze celý obsah šablony `basic-form.latte` [definovat |latte:template-inheritance#definice] jako blok, včetně parametru `$form`: +Alternativně lze celý obsah šablony `basic-form.latte` [definovat |latte:template-inheritance#Definice] jako blok, včetně parametru `$form`: ```latte {define basic-form, $form} @@ -237,15 +229,15 @@ Blok přitom stačí importovat na jediném místě a to na začátku šablony l Speciální případy ----------------- -Pokud potřebujete vykreslit jen vnitřní část formuláře bez HTML značek `<form>` & `</form>`, například při AJAXovém požadavku, můžete formulář otevří a uzavřít do `{formContext} … {/formContext}`. Funguje podobně jako `<form n:form>` či `{form}` v logickém smyslu, tady umožní používat ostatní značky pro kreslení prvků formuláře, ale přitom nic nevykreslí. +Pokud potřebujete vykreslit jen vnitřní část formuláře bez HTML značek `<form>`, například při posílání snippetů, skryjte je pomocí atributu `n:tag-if`: ```latte -{formContext signForm} +<form n:name=signInForm n:tag-if=false> <div> <label n:name=username>Username: <input n:name=username></label> {inputError username} </div> -{/formContext} +</form> ``` S vykreslením prvků uvnitř formulářového kontejneru pomůže tag `{formContainer}`. @@ -271,7 +263,7 @@ Nejjednodušší způsob, jak vykreslit formulář, je zavolat: $form->render(); ``` -Ovlivnit podobu takto vykresleného formuláře lze konfigurací [Rendereru|#Renderer] a [jednotlivých prvků|#HTML atributy]. +Ovlivnit podobu takto vykresleného formuláře lze konfigurací [Rendereru |#Renderer] a [jednotlivých prvků |#HTML atributy]. Manuální vykreslení @@ -305,8 +297,7 @@ Formulář tak lze vykreslovat po jednotlivých elementech: <?php $form->render('end') ?> ``` -Zatímco u některých prvků vrací `getControl()` jediný HTML element (např. `<input>`, `<select>` apod.), u jiných celý kus HTML kódu (CheckboxList, RadioList). -V takovém případě můžete využít metod, které generují jednotlivé inputy a popisky, pro každou položku zvlášt: +Zatímco u některých prvků vrací `getControl()` jediný HTML element (např. `<input>`, `<select>` apod.), u jiných celý kus HTML kódu (CheckboxList, RadioList). V takovém případě můžete využít metod, které generují jednotlivé inputy a popisky, pro každou položku zvlášt: - `getControlPart($key = null): ?Html` vrací HTML kód jedné položky - `getLabelPart($key = null): ?Html` vrací HTML kód popisky jedené položky @@ -434,6 +425,8 @@ $form->addText('city', 'City:'); $form->addSelect('country', 'Country:', $countries); ``` +Renderer nejprve vykresluje skupiny a teprve poté prvky, které do žádné skupiny nepatří. + Podpora pro Bootstrap --------------------- @@ -444,7 +437,7 @@ Podpora pro Bootstrap HTML atributy ============= -Formulářovým prvkům můžeme nastavovat libovolné HTML atributy pomocí `setHtmlAttribute(string $name, $value = true)`: +Pro nastavení libovolných HTML atributů formulářových prvků použijeme metodu `setHtmlAttribute(string $name, $value = true)`: ```php $form->addInteger('number', 'Číslo:') @@ -454,11 +447,11 @@ $form->addSelect('rank', 'Řazení dle:', ['ceny', 'názvu']) ->setHtmlAttribute('onchange', 'submit()'); // při změně odeslat -// chceme-li to samé udělat pro <form> +// Pro nastavení atributů samotného <form> $form->setHtmlAttribute('id', 'myForm'); ``` -Nastavení typu: +Specifikace typu prvku: ```php $form->addText('tel', 'Váš telefon:') @@ -466,8 +459,10 @@ $form->addText('tel', 'Váš telefon:') ->setHtmlAttribute('placeholder', 'napište telefon'); ``` -Jednotlivým položkám v radio nebo checkbox listech můžeme nastavit HTML atribut s rozdílnými hodnotami pro každou z nich. -Povšimněte si dvojtečky za `style:`, která zajistí volbu hodnoty podle klíče: +.[warning] +Nastavení typu a dalších atributů slouží jen pro vizuální účely. Ověření správnosti vstupů musí probíhat na serveru, což zajístíte volbou vhodného [formulářového prvku|controls] a uvedením [validačních pravidel|validation]. + +Jednotlivým položkám v radio nebo checkbox listech můžeme nastavit HTML atribut s rozdílnými hodnotami pro každou z nich. Povšimněte si dvojtečky za `style:`, která zajistí volbu hodnoty podle klíče: ```php $colors = ['r' => 'červená', 'g' => 'zelená', 'b' => 'modrá']; @@ -484,10 +479,9 @@ Vypíše: <label><input type="checkbox" name="colors[]" value="b">modrá</label> ``` -Pokud jde o logický HTML atribut (který nemá hodnotu, např. `readonly`), můžeme použít zápis s otazníkem: +Pro nastavení logických atributů, jako je `readonly`, můžeme použít zápis s otazníkem: ```php -$colors = ['r' => 'červená', 'g' => 'zelená', 'b' => 'modrá']; $form->addCheckboxList('colors', 'Barvy:', $colors) ->setHtmlAttribute('readonly?', 'r'); // pro více klíču použijte pole, např. ['r', 'g'] ``` @@ -500,8 +494,7 @@ Vypíše: <label><input type="checkbox" name="colors[]" value="b">modrá</label> ``` -V případě selectboxů metoda `setHtmlAttribute()` nastavuje atributy elementu `<select>`. Pokud chceme nastavit atributy jednotlivým -`<option>`, použijeme metodu `setOptionAttribute()`. Fungují i zápisy s dvojtečkou a otazníkem uvedené výše: +V případě selectboxů metoda `setHtmlAttribute()` nastavuje atributy elementu `<select>`. Pokud chceme nastavit atributy jednotlivým `<option>`, použijeme metodu `setOptionAttribute()`. Fungují i zápisy s dvojtečkou a otazníkem uvedené výše: ```php $form->addSelect('colors', 'Barvy:', $colors) @@ -541,9 +534,6 @@ U prvků Checkbox, CheckboxList a RadioList můžete ovlivnit předlohu elementu ```php $input = $form->addCheckbox('send'); -echo $input->getControl(); -// <label><input type="checkbox" name="send"></label> - $html = $input->getContainerPrototype(); $html->setName('div'); // <div> $html->class('check'); // <div class="check"> @@ -551,8 +541,7 @@ echo $input->getControl(); // <div class="check"><label><input type="checkbox" name="send"></label></div> ``` -V připadě CheckboxList a RadioList lze ovlivnit i předlohu oddělovače jednotlivých položek, který vrací metoda `getSeparatorPrototype()`. Ve výchozím stavu je to element `<br>`. Pokud jej změníte na párový element, bude jednotlivé položky obalovat místo oddělovat. -A dále lze ovlivnit předlohu HTML elementu popisky u jednotlivých položek, který vrací `getItemLabelPrototype()`. +V připadě CheckboxList a RadioList lze ovlivnit i předlohu oddělovače jednotlivých položek, který vrací metoda `getSeparatorPrototype()`. Ve výchozím stavu je to element `<br>`. Pokud jej změníte na párový element, bude jednotlivé položky obalovat místo oddělovat. A dále lze ovlivnit předlohu HTML elementu popisky u jednotlivých položek, který vrací `getItemLabelPrototype()`. Překládání @@ -579,7 +568,7 @@ U [validačních pravidel|validation] se translatoru předávají i specifické ```php $form->addPassword('password', 'Heslo:') - ->addCondition($form::MinLength, 'Heslo musí mít alespoň %d znaků', 8); + ->addRule($form::MinLength, 'Heslo musí mít alespoň %d znaků', 8); ``` se volá translator s těmito parametry: diff --git a/forms/cs/standalone.texy b/forms/cs/standalone.texy index 965c271724..1b980ceeda 100644 --- a/forms/cs/standalone.texy +++ b/forms/cs/standalone.texy @@ -45,7 +45,7 @@ if ($form->isSuccess()) { } ``` -Metoda `getValues()` vrací odeslaná data v podobě objektu [ArrayHash |utils:arrays#ArrayHash]. Jak to změnit si ukážeme [později|#Mapování na třídy]. Objekt `$data` obsahuje klíče `name` a `password` s údaji, které vyplnil uživatel. +Metoda `getValues()` vrací odeslaná data v podobě objektu [ArrayHash |utils:arrays#ArrayHash]. Jak to změnit si ukážeme [později |#Mapování na třídy]. Objekt `$data` obsahuje klíče `name` a `password` s údaji, které vyplnil uživatel. Obvykle data rovnou posíláme k dalšímu zpracování, což může být například vložení do databáze. Během zpracování se ale může objevit chyba, například uživatelské jméno už je obsazené. V takovém případě chybu předáme zpět do formuláře pomocí `addError()` a necháme jej vykreslit znovu, i s chybovou hláškou. @@ -62,7 +62,7 @@ $form->setAction('/submit.php'); $form->setMethod('GET'); ``` -A to je vlastně vše :-) Máme funkční a perfektně [zabezpečený|#Ochrana před zranitelnostmi] formulář. +A to je vlastně vše :-) Máme funkční a perfektně [zabezpečený |#Ochrana před zranitelnostmi] formulář. Zkuste si přidat i další [formulářové prvky|controls]. @@ -103,11 +103,10 @@ Zkuste si odeslat formulář bez vyplněného jména a uvidíte, že se zobrazí Zároveň systém neošidíte tím, že do políčka napíšete třeba jen mezery. Kdepak. Nette levo- i pravostranné mezery automaticky odstraňuje. Vyzkoušejte si to. Je to věc, kterou byste měli s každým jednořádkovým inputem vždy udělat, ale často se na to zapomíná. Nette to dělá automaticky. (Můžete zkusit ošálit formulář a jako jméno poslat víceřádkový řetězec. Ani tady se Nette nenechá zmást a odřádkování změní na mezery.) -Formulář se vždy validuje na straně serveru, ale také se generuje JavaScriptová validace, která proběhne bleskově a uživatel se o chybě dozví okamžitě, bez nutnosti formulář odesílat na server. Tohle má na starosti skript `netteForms.js`. -Vložte jej do stránky: +Formulář se vždy validuje na straně serveru, ale také se generuje JavaScriptová validace, která proběhne bleskově a uživatel se o chybě dozví okamžitě, bez nutnosti formulář odesílat na server. Tohle má na starosti skript `netteForms.js`. Vložte jej do stránky: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` Pokud se podíváte do zdrojového kódu stránky s formulářem, můžete si všimnout, že Nette povinné prvky vkládá do elementů s CSS třídou `required`. Zkuste přidat do šablony následující stylopis a popiska „Jméno“ bude červená. Elegantně tak uživatelům vyznačíme povinné prvky: @@ -144,7 +143,7 @@ $form->addPassword('password', 'Heslo:') ->addRule($form::MinLength, 'Heslo musí mít alespoň %d znaků', 8); ``` -Přidáme do formuláře ještě políčko `passwordVerify`, kde uživatel zadá heslo ještě jednou, pro kontrolu. Pomocí validačních pravidel zkontrolujeme, zda jsou obě hesla stejná (`$form::Equal`). A jako parametr dáme odvolávku na první heslo pomocí [hranatých závorek|#Přístup k prvkům]: +Přidáme do formuláře ještě políčko `passwordVerify`, kde uživatel zadá heslo ještě jednou, pro kontrolu. Pomocí validačních pravidel zkontrolujeme, zda jsou obě hesla stejná (`$form::Equal`). A jako parametr dáme odvolávku na první heslo pomocí [hranatých závorek |#Přístup k prvkům]: ```php $form->addPassword('passwordVerify', 'Heslo pro kontrolu:') @@ -202,25 +201,27 @@ Vraťme se ke zpracování formulářových dat. Metoda `getValues()` nám vrace class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -Od PHP 8.0 můžete použít tento elegantní zápis, který využívá konstruktor: +Alternativně můžete využít konstruktor: ```php class RegistrationFormData { public function __construct( public string $name, - public int $age, + public ?int $age, public string $password, ) { } } ``` +Property datové třídy mohou být také enumy a dojde k jejich automatickému namapování. .{data-version:3.2.4} + Jak říci Nette, aby nám data vracel jako objekty této třídy? Snadněji než si myslíte. Stačí pouze název třídy nebo objekt k hydrataci uvést jako parametr: ```php @@ -247,7 +248,7 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` @@ -258,6 +259,8 @@ Mapování pak z typu property `$person` pozná, že má kontejner mapovat na t $person->setMappedType(PersonFormData::class); ``` +Návrh datové třídy formuláře si můžete nechat vygenerovat pomocí metody `Nette\Forms\Blueprint::dataClass($form)`, která ji vypíše do stránky prohlížeče. Kód pak stačí kliknutím označit a zkopírovat do projektu. .{data-version:3.1.15} + Více tlačítek ============= @@ -289,7 +292,7 @@ Ochrana před zranitelnostmi Nette Framework klade velký důraz na bezpečnost a proto úzkostlivě dbá na dobré zabezpečení formulářů. -Kromě toho, že formuláře ochrání před útokem [Cross Site Scripting (XSS) |nette:glossary#cross-site-scripting-xss] a [Cross-Site Request Forgery (CSRF)|nette:glossary#Cross-Site Request Forgery (CSRF)], dělá spoustu drobných zabezpečení, na které vy už nemusíte myslet. +Kromě toho, že formuláře ochrání před útokem [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] a [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF], dělá spoustu drobných zabezpečení, na které vy už nemusíte myslet. Tak třeba odfiltruje ze vstupů všechny kontrolní znaky a prověří validitu UTF-8 kódování, takže data z formuláře budou vždycky čistá. U select boxů a radio listů ověřuje, že vybrané položky byly skutečně z nabízených a nedošlo k podvrhu. Už jsme zmiňovali, že u jednořádkových textových vstupů ostraňuje znaky konce řádků, které tam mohl poslat útočník. U víceřádkových vstupů zase normalizuje znaky pro konce řádků. A tak dále. @@ -309,7 +312,6 @@ Ochrana pomocí SameSite cookie nemusí být 100% spolehlivá, proto je vhodné $form->addProtection(); ``` -Doporučujeme takto chránit formuláře v administrační části webu, které mění citlivá data v aplikaci. Framework se proti útoku CSRF brání vygenerováním a ověřováním autorizačního tokenu, který se ukládá do session. Proto je nutné před zobrazením formuláře mít otevřenou session. V administrační části webu obvykle už session nastartovaná je kvůli přihlášení uživatele. -Jinak session nastartujte metodou `Nette\Http\Session::start()`. +Doporučujeme takto chránit formuláře v administrační části webu, které mění citlivá data v aplikaci. Framework se proti útoku CSRF brání vygenerováním a ověřováním autorizačního tokenu, který se ukládá do session. Proto je nutné před zobrazením formuláře mít otevřenou session. V administrační části webu obvykle už session nastartovaná je kvůli přihlášení uživatele. Jinak session nastartujte metodou `Nette\Http\Session::start()`. Tak, máme za sebou rychlý úvod do formulářů v Nette. Zkuste se ještě podívat do adresáře [examples|https://github.com/nette/forms/tree/master/examples] v distrubuci, kde najdete další inspiraci. diff --git a/forms/cs/validation.texy b/forms/cs/validation.texy index b20f5f60fe..479e12df74 100644 --- a/forms/cs/validation.texy +++ b/forms/cs/validation.texy @@ -23,9 +23,9 @@ $form->addPassword('password', 'Heslo:') ->addRule($form::MinLength, 'Heslo musí mít alespoň %d znaků', 8); ``` -Nette přichází s celou řadu předdefinovaných pravidel, jejichž názvy jsou konstanty třídy `Nette\Forms\Form`. +**Validační pravidla se ověřují pouze v případě, že uživatel prvek vyplnil.** -U všech prvků můžeme použít tyto pravidla: +Nette přichází s celou řadu předdefinovaných pravidel, jejichž názvy jsou konstanty třídy `Nette\Forms\Form`. U všech prvků můžeme použít tyto pravidla: | konstanta | popis | typ argumentu |------- @@ -38,7 +38,11 @@ U všech prvků můžeme použít tyto pravidla: | `IsNotIn` | hodnota se nerovná žádné položce v poli | `array` | `Valid` | je prvek vyplněn správně? (pro [#podmínky]) | - -U prvků `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` lze použít i následující pravidla: + +Textové vstupy +-------------- + +U prvků `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`, `addFloat()` lze použít i některá následující pravidla: | `MinLength` | minimální délka textu | `int` | `MaxLength` | maximální délka textu | `int` @@ -54,18 +58,11 @@ U prvků `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addIntege | `Max` | maximální hodnota číselného prvku | `int\|float` | `Range` | hodnota v rozsahu | dvojice `[int\|float, int\|float]` -Validační pravidla `Integer`, `Numeric` a `Float` rovnou převádí hodnotu na integer resp. float. A dále pravidlo `URL` akceptuje i adresu bez schématu (např. `nette.org`) a schéma doplní (`https://nette.org`). -Výraz v `Pattern` a `PatternIcase` musí platit pro celou hodnotu, tj. jako by byl obalen znaky `^` a `$`. - -U prvků `addUpload()`, `addMultiUpload()` lze použít i následující pravidla: +Validační pravidla `Integer`, `Numeric` a `Float` rovnou převádí hodnotu na integer resp. float. A dále pravidlo `URL` akceptuje i adresu bez schématu (např. `nette.org`) a schéma doplní (`https://nette.org`). Výraz v `Pattern` a `PatternIcase` musí platit pro celou hodnotu, tj. jako by byl obalen znaky `^` a `$`. -| `MaxFileSize` | maximální velikost souboru | `int` -| `MimeType` | MIME type, povoleny zástupné znaky (`'video/*'`) | `string\|string[]` -| `Image` | obrázek JPEG, PNG, GIF, WebP | - -| `Pattern` | jméno souboru vyhovuje regulárnímu výrazu | `string` -| `PatternInsensitive` | jako `Pattern`, ale nezávislé na velikosti písmen | `string` -`MimeType` a `Image` vyžadují PHP rozšíření `fileinfo`. Že je soubor či obrázek požadovaného typu detekují na základě jeho signatury a **neověřují integritu celého souboru.** Zda není obrázek poškozený lze zjistit například pokusem o jeho [načtení|http:request#toImage]. +Počet položek +------------- U prvků `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()` lze použít i následující pravidla pro omezení počtu vybraných položek resp. uploadovaných souborů: @@ -74,12 +71,26 @@ U prvků `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()` lze použí | `Length` | počet v rozsahu nebo přesný počet | dvojice `[int, int]` nebo `int` -Chybové hlášky +Upload souborů -------------- +U prvků `addUpload()`, `addMultiUpload()` lze použít i následující pravidla: + +| `MaxFileSize` | maximální velikost souboru v bajtech | `int` +| `MimeType` | MIME type, povoleny zástupné znaky (`'video/*'`) | `string\|string[]` +| `Image` | obrázek JPEG, PNG, GIF, WebP, AVIF | - +| `Pattern` | jméno souboru vyhovuje regulárnímu výrazu | `string` +| `PatternInsensitive` | jako `Pattern`, ale nezávislé na velikosti písmen | `string` + +`MimeType` a `Image` vyžadují PHP rozšíření `fileinfo`. Že je soubor či obrázek požadovaného typu detekují na základě jeho signatury a **neověřují integritu celého souboru.** Zda není obrázek poškozený lze zjistit například pokusem o jeho [načtení |http:request#toImage]. + + +Chybové hlášky +============== + Všechny předdefinované pravidla s výjimkou `Pattern` a `PatternInsensitive` mají výchozí chybovou hlášku, takže ji lze vynechat. Nicméně uvedením a formulací všech hlášek na míru uděláte formulář uživatelsky přívětivější. -Změnit výchozí hlášky můžete v [konfiguraci|forms:configuration], úpravou textů v poli `Nette\Forms\Validator::$messages` nebo použitím [translatoru|rendering#Překládání]. +Změnit výchozí hlášky můžete v [konfiguraci|forms:configuration], úpravou textů v poli `Nette\Forms\Validator::$messages` nebo použitím [translatoru |rendering#Překládání]. V textu chybových hlášek lze používat tyto zástupné řetězce: @@ -140,18 +151,18 @@ $form->addText(/* ... */) ->addRule(/* ... */); ``` -V Nette lze velmi snadno reagovat na splnění či nesplnění podmíny i na straně JavaScriptu pomocí metody `toggle()`, viz [#dynamický JavaScript]. +V Nette lze velmi snadno reagovat na splnění či nesplnění podmínky i na straně JavaScriptu pomocí metody `toggle()`, viz [#dynamický JavaScript]. -Reference mezi prvky -==================== +Reference na jiný prvek +======================= -Jako argument pravidla či podmínky lze uvádět referenci na jiný prvek. Takto lze např. dynamicky validovat, že prvek `text` má tolik znaků, jako je hodnota prvku `length`: +Jako argument pravidla či podmínky lze předat i jiný prvek formuláře. Pravidlo potom použije hodnotu vloženou později uživatelem v prohlížeči. Takto lze např. dynamicky validovat, že prvek `password` obsahuje stejný řetězec jako prvek `password_confirm`: ```php -$form->addInteger('length'); -$form->addText('text') - ->addRule($form::Length, null, $form['length']); +$form->addPassword('password', 'Heslo'); +$form->addPassword('password_confirm', 'Potvrďte heslo') + ->addRule($form::Equal, 'Zadaná hesla se neshodují', $form['password']); ``` @@ -160,7 +171,7 @@ Vlastní pravidla a podmínky Občas se dostaneme do situace, kdy nám vestavěná validační pravidla v Nette nestačí a potřebujeme data od uživatele validovat po svém. V Nette je to velmi jednoduché! -Metodám `addRule()` či `addCondition()` lze první parametr předat libovolný callback. Ten přijímá jako první parametr samotný prvek a vrací boolean hodnotu určující, zda validace proběhla v pořádku. Při přidávání pravidla pomocí `addRule()` je možné zadat i další argumenty, ty jsou pak předány jako druhý parametr. +Metodám `addRule()` či `addCondition()` lze jako první parametr předat libovolný callback. Ten přijímá jako první parametr samotný prvek a vrací boolean hodnotu určující, zda validace proběhla v pořádku. Při přidávání pravidla pomocí `addRule()` je možné zadat i další argumenty, ty jsou pak předány jako druhý parametr. Vlastní sadu validátorů tak můžeme vytvořit jako třídu se statickými metodami: @@ -203,7 +214,7 @@ Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => Událost onValidate ================== -Po odeslání formuláře se provádí validace, kdy se zkontrolují jednotlivá pravidla přidaná pomocí `addRule()` a následně se vyvolá [událost|nette:glossary#Události] `onValidate`. Její handler lze využít k doplňkové validaci, typicky ověření správné kombinace hodnot ve více prvcích formuláře. +Po odeslání formuláře se provádí validace, kdy se zkontrolují jednotlivá pravidla přidaná pomocí `addRule()` a následně se vyvolá [událost |nette:glossary#události] `onValidate`. Její handler lze využít k doplňkové validaci, typicky ověření správné kombinace hodnot ve více prvcích formuláře. Pokud se odhalí chyba, předáme ji do formuláře metodou `addError()`. Tu lze volat buď na konkrétním prvku, nebo přímo na formuláři. @@ -212,11 +223,11 @@ protected function createComponentSignInForm(): Form { $form = new Form; // ... - $form->onValidate[] = [$this, 'validateSignInForm']; + $form->onValidate[] = $this->validateSignInForm(...); return $form; } -public function validateSignInForm(Form $form, \stdClass $data): void +private function validateSignInForm(Form $form, \stdClass $data): void { if ($data->foo > 1 && $data->bar > 5) { $form->addError('Tato kombinace není možná.'); @@ -273,15 +284,14 @@ Filtr se začlení mezi validační pravidla a podmínky a tedy záleží na po JavaScriptová validace ====================== -Jazyk pro formulování podmínek a pravidel je velice silný. Všechny konstrukce přitom fungují jak na straně serveru, tak i na straně JavaScriptu. Přenáší se v HTML atributech `data-nette-rules` jako JSON. -Samotnou validaci pak provádí skript, který odchytí událost formuláře `submit`, projde jednotlivé prvky a vykoná příslušnou validaci. +Jazyk pro formulování podmínek a pravidel je velice silný. Všechny konstrukce přitom fungují jak na straně serveru, tak i na straně JavaScriptu. Přenáší se v HTML atributech `data-nette-rules` jako JSON. Samotnou validaci pak provádí skript, který odchytí událost formuláře `submit`, projde jednotlivé prvky a vykoná příslušnou validaci. Tím skriptem je `netteForms.js` a je dostupný z více možných zdrojů: Skript můžete vložit přímo do HTML stránky ze CDN: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` Nebo zkopírovat lokálně do veřejné složky projektu (např. z `vendor/nette/forms/src/assets/netteForms.min.js`): @@ -352,7 +362,7 @@ $details->addInteger('age') $details->addInteger('age2') ->setRequired('age2'); -$form->addSubmit('send1'); // Validuje celý formuláře +$form->addSubmit('send1'); // Validuje celý formulář $form->addSubmit('send2') ->setValidationScope([]); // Nevaliduje vůbec $form->addSubmit('send3') diff --git a/forms/de/@home.texy b/forms/de/@home.texy index 35d6e24c9a..bdc4d7fb9a 100644 --- a/forms/de/@home.texy +++ b/forms/de/@home.texy @@ -1,31 +1,31 @@ -Formulare -********* +Nette Forms +*********** <div class=perex> -Nette Forms hat die Erstellung von Webformularen revolutioniert. Alles, was Sie tun mussten, war, ein paar klare Codezeilen zu schreiben, und schon hatten Sie ein Formular, einschließlich Rendering, JavaScript und Servervalidierung, plus branchenführender Sicherheit. Sehen wir uns an, wie +Nette Forms haben die Erstellung von Webformularen revolutioniert. Plötzlich genügten ein paar verständliche Codezeilen, und Sie hatten ein fertiges Formular inklusive Rendering, JavaScript- und serverseitiger Validierung und dazu noch erstklassig gesichert. Wir zeigen Ihnen, wie Sie -- freundliche Formulare erstellen +- benutzerfreundliche Formulare erstellen - gesendete Daten validieren -- Elemente genau nach Bedarf zeichnen +- Elemente genau nach Bedarf rendern </div> -Mit Nette Forms können Sie Routineaufgaben wie das Schreiben von Validierungen (sowohl auf Server- als auch auf Client-Seite) verringern und die Wahrscheinlichkeit von Fehlern und Sicherheitsproblemen minimieren. +Durch die Verwendung von Nette Forms vermeiden Sie eine ganze Reihe von Routineaufgaben, wie das Schreiben von Validierungen (noch dazu doppelt, auf Server- und Clientseite), minimieren die Wahrscheinlichkeit von Fehlern und Sicherheitslücken. -Sie können die Formulare entweder als Teil der Nette-Anwendung (z.B. in Presentern) oder eigenständig verwenden. Da die Verwendung in beiden Fällen leicht unterschiedlich ist, haben wir eine separate Anleitung für Sie vorbereitet: +Formulare können entweder als Teil einer Nette-Anwendung (also in Presentern) oder völlig eigenständig verwendet werden. Da sich die Verwendung in beiden Fällen leicht unterscheidet, haben wir zwei Anleitungen für Sie vorbereitet: <div class="wiki-buttons"> <div> "Formulare in Presentern .[wiki-button]":in-presenter </div> -<div> "Formulare als eigenständige Anwendung .[wiki-button]":standalone </div> +<div> "Eigenständige Formulare .[wiki-button]":standalone </div> </div> -Einrichtung ------------ +Installation +------------ -Laden Sie das Paket herunter und installieren Sie es mit [Composer |best-practices:composer]: +Sie können die Bibliothek mit dem Werkzeug [Composer|best-practices:composer] herunterladen und installieren: ```shell composer require nette/forms diff --git a/forms/de/@left-menu.texy b/forms/de/@left-menu.texy index a85cfb629f..c988dac7e9 100644 --- a/forms/de/@left-menu.texy +++ b/forms/de/@left-menu.texy @@ -1,14 +1,14 @@ -Formulare -********* -- [Überblick |@home] -- [Formulare in Presentern |in-presenter] -- [Eigenständige Formulare |standalone] -- [Formular-Steuerelemente |controls] +Nette Forms +*********** +- [Einführung |@home] +- [Formulare in Presentern|in-presenter] +- [Formulare eigenständig|standalone] +- [Formularelemente |controls] - [Validierung |validation] - [Rendering |rendering] -- [Konfiguration |Configuration] +- [Konfiguration |configuration] Weitere Lektüre *************** -- [Bewährte Praktiken |best-practices:] +- [Anleitungen und Verfahren |best-practices:] diff --git a/forms/de/@meta.texy b/forms/de/@meta.texy new file mode 100644 index 0000000000..b3b806b2ca --- /dev/null +++ b/forms/de/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokumentation}} diff --git a/forms/de/configuration.texy b/forms/de/configuration.texy index 6e21ecf6f6..19b8d55c2e 100644 --- a/forms/de/configuration.texy +++ b/forms/de/configuration.texy @@ -1,8 +1,8 @@ -Formulare konfigurieren -*********************** +Konfiguration von Formularen +**************************** .[perex] -Sie können die [Standard-Fehlermeldungen für Formulare |validation] in der Konfiguration ändern. +In der Konfiguration können die standardmäßigen [Fehlermeldungen von Formularen|validation] geändert werden. ```neon forms: @@ -42,20 +42,20 @@ forms: MinLength: 'Bitte geben Sie mindestens %d Zeichen ein.' MaxLength: 'Bitte geben Sie nicht mehr als %d Zeichen ein.' Length: 'Bitte geben Sie einen Wert mit einer Länge zwischen %d und %d Zeichen ein.' - Email: 'Bitte geben Sie eine gültige E-Mail Adresse ein.' + Email: 'Bitte geben Sie eine gültige E-Mail-Adresse ein.' URL: 'Bitte geben Sie eine gültige URL ein.' - Integer: 'Bitte geben Sie eine gültige Ganzzahl ein.' + Integer: 'Bitte geben Sie eine gültige ganze Zahl ein.' Float: 'Bitte geben Sie eine gültige Zahl ein.' - Min: 'Bitte geben Sie einen Wert größer als oder gleich %d ein.' - Max: 'Bitte geben Sie einen Wert ein, der kleiner oder gleich %d ist.' + Min: 'Bitte geben Sie einen Wert größer oder gleich %d ein.' + Max: 'Bitte geben Sie einen Wert kleiner oder gleich %d ein.' Range: 'Bitte geben Sie einen Wert zwischen %d und %d ein.' - MaxFileSize: 'Die Größe der hochgeladenen Datei kann bis zu %d Bytes betragen.' - MaxPostSize: 'Die hochgeladenen Daten überschreiten die Grenze von %d Bytes.' + MaxFileSize: 'Die Größe der hochgeladenen Datei darf maximal %d Bytes betragen.' + MaxPostSize: 'Die hochgeladenen Daten überschreiten das Limit von %d Bytes.' MimeType: 'Die hochgeladene Datei hat nicht das erwartete Format.' - Image: 'Die hochgeladene Datei muss ein Bild im Format JPEG, GIF, PNG oder WebP sein.' + Image: 'Die hochgeladene Datei muss ein Bild im Format JPEG, GIF, PNG, WebP oder AVIF sein.' Nette\Forms\Controls\SelectBox::Valid: 'Bitte wählen Sie eine gültige Option aus.' Nette\Forms\Controls\UploadControl::Valid: 'Beim Hochladen der Datei ist ein Fehler aufgetreten.' - Nette\Forms\Controls\CsrfProtection::Protection: 'Ihre Session ist abgelaufen. Bitte kehren Sie zur Startseite zurück und versuchen Sie es erneut.' + Nette\Forms\Controls\CsrfProtection::Protection: 'Ihre Sitzung ist abgelaufen. Bitte kehren Sie zur Startseite zurück und versuchen Sie es erneut.' ``` -Wenn Sie nicht das gesamte Framework und damit auch nicht die Konfigurationsdateien verwenden, können Sie die Standardfehlermeldungen direkt im Feld `Nette\Forms\Validator::$messages` ändern. +Wenn Sie nicht das gesamte Framework und somit auch keine Konfigurationsdateien verwenden, können Sie die standardmäßigen Fehlermeldungen direkt im Array `Nette\Forms\Validator::$messages` ändern. diff --git a/forms/de/controls.texy b/forms/de/controls.texy index bc3e9ac752..d961df40fd 100644 --- a/forms/de/controls.texy +++ b/forms/de/controls.texy @@ -1,14 +1,14 @@ -Formular-Steuerelemente -*********************** +Formularelemente +**************** .[perex] -Übersicht über die eingebauten Formularsteuerelemente. +Übersicht über die standardmäßigen Formularelemente. -addText(string|int $name, $label=null): TextInput .[method] -=========================================================== +addText(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +================================================================================================== -Fügt ein einzeiliges Textfeld hinzu (Klasse [TextInput |api:Nette\Forms\Controls\TextInput]). Wenn der Benutzer das Feld nicht ausfüllt, wird eine leere Zeichenkette `''` zurückgegeben, oder verwenden Sie `setNullable()`, um dies zu ändern und `null` zurückzugeben. +Fügt ein einzeiliges Texteingabefeld hinzu (Klasse [TextInput |api:Nette\Forms\Controls\TextInput]). Wenn der Benutzer das Feld nicht ausfüllt, gibt es eine leere Zeichenkette `''` zurück, oder mit `setNullable()` kann festgelegt werden, dass `null` zurückgegeben wird. ```php $form->addText('name', 'Name:') @@ -16,265 +16,360 @@ $form->addText('name', 'Name:') ->setNullable(); ``` -Es validiert automatisch UTF-8, schneidet linke und rechte Leerzeichen ab und entfernt Zeilenumbrüche, die von einem Angreifer gesendet werden könnten. +Validiert automatisch UTF-8, schneidet führende und nachfolgende Leerzeichen ab und entfernt Zeilenumbrüche, die ein Angreifer senden könnte. -Die maximale Länge kann mit `setMaxLength()` begrenzt werden. Mit [addFilter() |validation#Modifying Input Values] können Sie den vom Benutzer eingegebenen Wert ändern. +Die maximale Länge kann mit `setMaxLength()` begrenzt werden. Der vom Benutzer eingegebene Wert kann mit [addFilter() |validation#Anpassung der Eingabe] geändert werden. -Verwenden Sie `setHtmlType()`, um das [Zeichen |https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types] des Eingabeelements in `search`, `tel`, `url`, `range`, `date`, `datetime-local`, `month`, `time`, `week`, `color` zu ändern. Anstelle der Typen `number` und `email` empfehlen wir die Verwendung von [addInteger |#addInteger] und [addEmail |#addEmail], die eine serverseitige Validierung ermöglichen. +Mit `setHtmlType()` kann der visuelle Charakter des Textfeldes auf Typen wie `search`, `tel` oder `url` geändert werden, siehe [Spezifikation|https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types]. Beachten Sie, dass die Typänderung nur visuell ist und keine Validierungsfunktion ersetzt. Für den Typ `url` ist es ratsam, eine spezifische Validierungs[regel URL |validation#Texteingaben] hinzuzufügen. -```php -$form->addText('color', 'Choose color:') - ->setHtmlType('color') - ->addRule($form::Pattern, 'invalid value', '[0-9a-f]{6}'); -``` +.[note] +Für andere Eingabetypen wie `number`, `range`, `email`, `date`, `datetime-local`, `time` und `color` verwenden Sie spezialisierte Methoden wie [#addInteger], [#addFloat], [#addEmail] [#addDate], [#addTime], [#addDateTime] und [#addColor], die die serverseitige Validierung sicherstellen. Die Typen `month` und `week` werden derzeit noch nicht in allen Browsern vollständig unterstützt. -Für das Element kann der sogenannte empty-value gesetzt werden, der so etwas wie der Standardwert ist, aber, wenn der Benutzer ihn nicht überschreibt, einen leeren String oder `null` zurückgibt. +Dem Element kann ein sogenannter Empty-Value zugewiesen werden, was so etwas wie ein Standardwert ist, aber wenn der Benutzer ihn nicht ändert, gibt das Element eine leere Zeichenkette oder `null` zurück. ```php -$form->addText('phone', 'Phone:') +$form->addText('phone', 'Telefon:') ->setHtmlType('tel') - ->setEmptyValue('+420'); + ->setEmptyValue('+49'); // Beispiel für DE ``` addTextArea(string|int $name, $label=null): TextArea .[method] ============================================================== -Fügt ein mehrzeiliges Textfeld (Klasse [TextArea |api:Nette\Forms\Controls\TextArea]) hinzu. Wenn der Benutzer das Feld nicht ausfüllt, wird eine leere Zeichenkette `''` zurückgegeben, oder verwenden Sie `setNullable()`, um dies zu ändern und `null` zurückzugeben. +Fügt ein Feld zur Eingabe von mehrzeiligem Text hinzu (Klasse [TextArea |api:Nette\Forms\Controls\TextArea]). Wenn der Benutzer das Feld nicht ausfüllt, gibt es eine leere Zeichenkette `''` zurück, oder mit `setNullable()` kann festgelegt werden, dass `null` zurückgegeben wird. ```php -$form->addTextArea('note', 'Note:') - ->addRule($form::MaxLength, 'Your note is way too long', 10000); +$form->addTextArea('note', 'Anmerkung:') + ->addRule($form::MaxLength, 'Anmerkung ist zu lang', 10000); ``` -Validiert automatisch UTF-8 und normalisiert Zeilenumbrüche auf `\n`. Im Gegensatz zu einem einzeiligen Eingabefeld wird der Leerraum nicht abgeschnitten. +Validiert automatisch UTF-8 und normalisiert Zeilentrenner auf `\n`. Im Gegensatz zum einzeiligen Eingabefeld erfolgt kein Abschneiden von Leerzeichen. -Die maximale Länge kann mit `setMaxLength()` begrenzt werden. Mit [addFilter() |validation#Modifying Input Values] können Sie den vom Benutzer eingegebenen Wert ändern. Mit `setEmptyValue()` können Sie den sogenannten Leerwert setzen. +Die maximale Länge kann mit `setMaxLength()` begrenzt werden. Der vom Benutzer eingegebene Wert kann mit [addFilter() |validation#Anpassung der Eingabe] geändert werden. Mit `setEmptyValue()` kann ein sogenannter Empty-Value festgelegt werden. addInteger(string|int $name, $label=null): TextInput .[method] ============================================================== -Fügt Eingabefeld für Ganzzahl (Klasse [TextInput |api:Nette\Forms\Controls\TextInput]) hinzu. Gibt entweder eine Ganzzahl oder `null` zurück, wenn der Benutzer nichts eingibt. +Fügt ein Feld zur Eingabe einer ganzen Zahl hinzu (Klasse [TextInput |api:Nette\Forms\Controls\TextInput]). Gibt entweder einen Integer oder `null` zurück, wenn der Benutzer nichts eingibt. + +```php +$form->addInteger('year', 'Jahr:') + ->addRule($form::Range, 'Das Jahr muss im Bereich von %d bis %d liegen.', [1900, 2023]); +``` + +Das Element wird als `<input type="number">` gerendert. Mit der Methode `setHtmlType()` kann der Typ auf `range` geändert werden, um eine Darstellung als Schieberegler zu erhalten, oder auf `text`, wenn Sie ein Standard-Textfeld ohne das spezielle Verhalten des Typs `number` bevorzugen. + + +addFloat(string|int $name, $label=null): TextInput .[method]{data-version:3.1.12} +================================================================================= + +Fügt ein Feld zur Eingabe einer Dezimalzahl hinzu (Klasse [TextInput |api:Nette\Forms\Controls\TextInput]). Gibt entweder einen Float oder `null` zurück, wenn der Benutzer nichts eingibt. ```php -$form->addInteger('level', 'Level:') +$form->addFloat('level', 'Level:') ->setDefaultValue(0) - ->addRule($form::Range, 'Level must be between %d and %d.', [0, 100]); + ->addRule($form::Range, 'Das Level muss im Bereich von %d bis %d liegen.', [0, 100]); ``` +Das Element wird als `<input type="number">` gerendert. Mit der Methode `setHtmlType()` kann der Typ auf `range` geändert werden, um eine Darstellung als Schieberegler zu erhalten, oder auf `text`, wenn Sie ein Standard-Textfeld ohne das spezielle Verhalten des Typs `number` bevorzugen. + +Nette und der Chrome-Browser akzeptieren sowohl Komma als auch Punkt als Dezimaltrennzeichen. Damit diese Funktionalität auch in Firefox verfügbar ist, wird empfohlen, das Attribut `lang` entweder für das betreffende Element oder für die gesamte Seite zu setzen, beispielsweise `<html lang="de">`. + -addEmail(string|int $name, $label=null): TextInput .[method] -============================================================ +addEmail(string|int $name, $label=null, int $maxLength=255): TextInput .[method] +================================================================================ -Fügt ein E-Mail-Adressfeld mit Gültigkeitsprüfung hinzu (Klasse [TextInput |api:Nette\Forms\Controls\TextInput]). Wenn der Benutzer das Feld nicht ausfüllt, wird eine leere Zeichenkette `''` zurückgegeben, oder verwenden Sie `setNullable()`, um dies zu ändern und `null` zurückzugeben. +Fügt ein Feld zur Eingabe einer E-Mail-Adresse hinzu (Klasse [TextInput |api:Nette\Forms\Controls\TextInput]). Wenn der Benutzer das Feld nicht ausfüllt, gibt es eine leere Zeichenkette `''` zurück, oder mit `setNullable()` kann festgelegt werden, dass `null` zurückgegeben wird. ```php -$form->addEmail('email', 'Email:'); +$form->addEmail('email', 'E-Mail:'); ``` -Überprüft, ob der Wert eine gültige E-Mail-Adresse ist. Es wird nicht überprüft, ob die Domäne tatsächlich existiert, nur die Syntax wird überprüft. Validiert automatisch UTF-8, schneidet linke und rechte Leerzeichen ab. +Überprüft, ob der Wert eine gültige E-Mail-Adresse ist. Es wird nicht überprüft, ob die Domain tatsächlich existiert, es wird nur die Syntax überprüft. Validiert automatisch UTF-8, schneidet führende und nachfolgende Leerzeichen ab. -Die maximale Länge kann mit `setMaxLength()` begrenzt werden. Mit [addFilter() |validation#Modifying Input Values] können Sie den vom Benutzer eingegebenen Wert ändern. Mit `setEmptyValue()` können Sie den sogenannten Leerwert setzen. +Die maximale Länge kann mit `setMaxLength()` begrenzt werden. Der vom Benutzer eingegebene Wert kann mit [addFilter() |validation#Anpassung der Eingabe] geändert werden. Mit `setEmptyValue()` kann ein sogenannter Empty-Value festgelegt werden. -addPassword(string|int $name, $label=null): TextInput .[method] -=============================================================== +addPassword(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +====================================================================================================== -Fügt Passwortfeld hinzu (Klasse [TextInput |api:Nette\Forms\Controls\TextInput]). +Fügt ein Feld zur Eingabe eines Passworts hinzu (Klasse [TextInput |api:Nette\Forms\Controls\TextInput]). ```php -$form->addPassword('password', 'Password:') +$form->addPassword('password', 'Passwort:') ->setRequired() - ->addRule($form::MinLength, 'Password has to be at least %d characters long', 8) - ->addRule($form::Pattern, 'Password must contain a number', '.*[0-9].*'); + ->addRule($form::MinLength, 'Das Passwort muss mindestens %d Zeichen lang sein', 8) + ->addRule($form::Pattern, 'Muss eine Ziffer enthalten', '.*[0-9].*'); ``` -Wenn Sie das Formular erneut absenden, ist die Eingabe leer. Es validiert automatisch UTF-8, schneidet linke und rechte Leerzeichen ab und entfernt Zeilenumbrüche, die von einem Angreifer gesendet werden könnten. +Beim erneuten Anzeigen des Formulars ist das Feld leer. Validiert automatisch UTF-8, schneidet führende und nachfolgende Leerzeichen ab und entfernt Zeilenumbrüche, die ein Angreifer senden könnte. addCheckbox(string|int $name, $caption=null): Checkbox .[method] ================================================================ -Fügt ein Kontrollkästchen (Klasse [Checkbox |api:Nette\Forms\Controls\Checkbox]) hinzu. Das Feld gibt entweder `true` oder `false` zurück, je nachdem, ob es markiert ist. +Fügt ein Kontrollkästchen hinzu (Klasse [Checkbox |api:Nette\Forms\Controls\Checkbox]). Gibt entweder `true` oder `false` zurück, je nachdem, ob es aktiviert ist. ```php -$form->addCheckbox('agree', 'I agree with terms') - ->setRequired('You must agree with our terms'); +$form->addCheckbox('agree', 'Ich stimme den Bedingungen zu') + ->setRequired('Sie müssen den Bedingungen zustimmen'); ``` -addCheckboxList(string|int $name, $label=null, array $items=null): CheckboxList .[method] -========================================================================================= +addCheckboxList(string|int $name, $label=null, ?array $items=null): CheckboxList .[method] +========================================================================================== -Fügt eine Liste von Kontrollkästchen zur Auswahl mehrerer Elemente hinzu (Klasse [CheckboxList |api:Nette\Forms\Controls\CheckboxList]). Gibt das Array der Schlüssel der ausgewählten Elemente zurück. Die Methode `getSelectedItems()` gibt Werte anstelle von Schlüsseln zurück. +Fügt Kontrollkästchen zur Auswahl mehrerer Elemente hinzu (Klasse [CheckboxList |api:Nette\Forms\Controls\CheckboxList]). Gibt ein Array der Schlüssel der ausgewählten Elemente zurück. Die Methode `getSelectedItems()` gibt die Werte anstelle der Schlüssel zurück. ```php -$form->addCheckboxList('colors', 'Colors:', [ - 'r' => 'red', - 'g' => 'green', - 'b' => 'blue', +$form->addCheckboxList('colors', 'Farben:', [ + 'r' => 'rot', + 'g' => 'grün', + 'b' => 'blau', ]); ``` -Wir übergeben das Array der Elemente als dritten Parameter oder mit der Methode `setItems()`. +Das Array der angebotenen Elemente übergeben wir als dritten Parameter oder mit der Methode `setItems()`. -Sie können mit `setDisabled(['r', 'g'])` verwenden, um einzelne Elemente zu deaktivieren. +Mit `setDisabled(['r', 'g'])` können einzelne Elemente deaktiviert werden. -Das Element überprüft automatisch, dass keine Fälschung vorliegt und dass die ausgewählten Einträge tatsächlich zu den angebotenen gehören und nicht deaktiviert wurden. Die Methode `getRawValue()` kann verwendet werden, um eingereichte Artikel ohne diese wichtige Prüfung abzurufen. +Das Element überprüft automatisch, dass keine Manipulation stattgefunden hat und dass die ausgewählten Elemente tatsächlich zu den angebotenen gehören und nicht deaktiviert wurden. Mit der Methode `getRawValue()` können die gesendeten Elemente ohne diese wichtige Überprüfung abgerufen werden. -Wenn Standardwerte gesetzt werden, wird auch geprüft, ob sie zu den angebotenen Artikeln gehören, andernfalls wird eine Ausnahme geworfen. Diese Prüfung kann mit `checkDefaultValue(false)` ausgeschaltet werden. +Bei der Einstellung der standardmäßig ausgewählten Elemente wird ebenfalls überprüft, ob es sich um angebotene Elemente handelt, andernfalls wird eine Ausnahme ausgelöst. Diese Prüfung kann mit `checkDefaultValue(false)` deaktiviert werden. +Wenn Sie das Formular mit der Methode `GET` senden, können Sie eine kompaktere Datenübertragungsmethode wählen, die die Größe des Query-Strings spart. Sie wird durch Setzen des HTML-Attributs des Formulars aktiviert: + +```php +$form->setHtmlAttribute('data-nette-compact'); +``` -addRadioList(string|int $name, $label=null, array $items=null): RadioList .[method] -=================================================================================== -Fügt Optionsfelder hinzu (Klasse [RadioList |api:Nette\Forms\Controls\RadioList]). Gibt den Schlüssel des ausgewählten Elements zurück, oder `null`, wenn der Benutzer nichts ausgewählt hat. Die Methode `getSelectedItem()` gibt einen Wert statt eines Schlüssels zurück. +addRadioList(string|int $name, $label=null, ?array $items=null): RadioList .[method] +==================================================================================== + +Fügt Optionsschaltflächen hinzu (Klasse [RadioList |api:Nette\Forms\Controls\RadioList]). Gibt den Schlüssel des ausgewählten Elements zurück oder `null`, wenn der Benutzer nichts ausgewählt hat. Die Methode `getSelectedItem()` gibt den Wert anstelle des Schlüssels zurück. ```php $sex = [ - 'm' => 'male', - 'f' => 'female', + 'm' => 'männlich', + 'f' => 'weiblich', ]; -$form->addRadioList('gender', 'Gender:', $sex); +$form->addRadioList('gender', 'Geschlecht:', $sex); ``` -Wir übergeben das Array der Elemente als dritten Parameter oder mit der Methode `setItems()`. +Das Array der angebotenen Elemente übergeben wir als dritten Parameter oder mit der Methode `setItems()`. -Sie können mit `setDisabled(['m'])` verwenden, um einzelne Elemente zu deaktivieren. +Mit `setDisabled(['m', 'f'])` können einzelne Elemente deaktiviert werden. -Das Element überprüft automatisch, dass keine Fälschung vorliegt und dass das ausgewählte Element tatsächlich eines der angebotenen ist und nicht deaktiviert wurde. Die Methode `getRawValue()` kann verwendet werden, um das eingereichte Element ohne diese wichtige Prüfung abzurufen. +Das Element überprüft automatisch, dass keine Manipulation stattgefunden hat und dass das ausgewählte Element tatsächlich zu den angebotenen gehört und nicht deaktiviert wurde. Mit der Methode `getRawValue()` kann das gesendete Element ohne diese wichtige Überprüfung abgerufen werden. -Wenn der Standardwert eingestellt ist, wird auch geprüft, ob es sich um einen der angebotenen Einträge handelt, andernfalls wird eine Ausnahme ausgelöst. Diese Prüfung kann mit `checkDefaultValue(false)` ausgeschaltet werden. +Bei der Einstellung des standardmäßig ausgewählten Elements wird ebenfalls überprüft, ob es sich um ein angebotenes Element handelt, andernfalls wird eine Ausnahme ausgelöst. Diese Prüfung kann mit `checkDefaultValue(false)` deaktiviert werden. -addSelect(string|int $name, $label=null, array $items=null): SelectBox .[method] -================================================================================ +addSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): SelectBox .[method] +================================================================================================== -Fügt ein Auswahlfeld hinzu (Klasse [SelectBox |api:Nette\Forms\Controls\SelectBox]). Gibt den Schlüssel des ausgewählten Elements zurück, oder `null`, wenn der Benutzer nichts ausgewählt hat. Die Methode `getSelectedItem()` gibt einen Wert statt eines Schlüssels zurück. +Fügt eine Select-Box hinzu (Klasse [SelectBox |api:Nette\Forms\Controls\SelectBox]). Gibt den Schlüssel des ausgewählten Elements zurück oder `null`, wenn der Benutzer nichts ausgewählt hat. Die Methode `getSelectedItem()` gibt den Wert anstelle des Schlüssels zurück. ```php $countries = [ - 'CZ' => 'Czech republic', - 'SK' => 'Slovakia', - 'GB' => 'United Kingdom', + 'DE' => 'Deutschland', + 'AT' => 'Österreich', + 'CH' => 'Schweiz', ]; -$form->addSelect('country', 'Country:', $countries) - ->setDefaultValue('SK'); +$form->addSelect('country', 'Land:', $countries) + ->setDefaultValue('AT'); ``` -Wir übergeben das Array der Elemente als dritten Parameter oder mit der Methode `setItems()`. Das Array der Elemente kann auch zweidimensional sein: +Das Array der angebotenen Elemente übergeben wir als dritten Parameter oder mit der Methode `setItems()`. Die Elemente können auch ein zweidimensionales Array sein (für `<optgroup>`): ```php $countries = [ - 'Europe' => [ - 'CZ' => 'Czech republic', - 'SK' => 'Slovakia', - 'GB' => 'United Kingdom', + 'Europa' => [ + 'DE' => 'Deutschland', + 'AT' => 'Österreich', + 'CH' => 'Schweiz', ], - 'CA' => 'Canada', + 'CA' => 'Kanada', 'US' => 'USA', - '?' => 'other', + '?' => 'andere', ]; ``` -Bei Auswahlfeldern hat der erste Eintrag oft eine besondere Bedeutung, er dient als Call-to-Action. Verwenden Sie die Methode `setPrompt()`, um einen solchen Eintrag hinzuzufügen. +Bei Select-Boxen hat das erste Element oft eine besondere Bedeutung, es dient als Aufforderung zur Aktion. Zum Hinzufügen eines solchen Elements dient die Methode `setPrompt()`. ```php -$form->addSelect('country', 'Country:', $countries) - ->setPrompt('Pick a country'); +$form->addSelect('country', 'Land:', $countries) + ->setPrompt('Wählen Sie ein Land'); ``` -Sie können mit `setDisabled(['CZ', 'SK'])` können Sie einzelne Einträge deaktivieren. +Mit `setDisabled(['DE', 'AT'])` können einzelne Elemente deaktiviert werden. -Das Element prüft automatisch, dass keine Fälschung vorliegt und dass das ausgewählte Element tatsächlich eines der angebotenen ist und nicht deaktiviert wurde. Die Methode `getRawValue()` kann verwendet werden, um das eingereichte Element ohne diese wichtige Prüfung abzurufen. +Das Element überprüft automatisch, dass keine Manipulation stattgefunden hat und dass das ausgewählte Element tatsächlich zu den angebotenen gehört und nicht deaktiviert wurde. Mit der Methode `getRawValue()` kann das gesendete Element ohne diese wichtige Überprüfung abgerufen werden. -Wenn der Standardwert eingestellt ist, wird auch geprüft, ob es sich um einen der angebotenen Einträge handelt, andernfalls wird eine Ausnahme ausgelöst. Diese Prüfung kann mit `checkDefaultValue(false)` ausgeschaltet werden. +Bei der Einstellung des standardmäßig ausgewählten Elements wird ebenfalls überprüft, ob es sich um ein angebotenes Element handelt, andernfalls wird eine Ausnahme ausgelöst. Diese Prüfung kann mit `checkDefaultValue(false)` deaktiviert werden. -addMultiSelect(string|int $name, $label=null, array $items=null): MultiSelectBox .[method] -========================================================================================== +addMultiSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): MultiSelectBox .[method] +============================================================================================================ -Fügt ein Auswahlfeld mit mehreren Auswahlmöglichkeiten hinzu (Klasse [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox]). Gibt das Array der Schlüssel der ausgewählten Elemente zurück. Die Methode `getSelectedItems()` gibt Werte anstelle von Schlüsseln zurück. +Fügt eine Select-Box zur Auswahl mehrerer Elemente hinzu (Klasse [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox]). Gibt ein Array der Schlüssel der ausgewählten Elemente zurück. Die Methode `getSelectedItems()` gibt die Werte anstelle der Schlüssel zurück. ```php -$form->addMultiSelect('countries', 'Countries:', $countries); +$form->addMultiSelect('countries', 'Länder:', $countries); ``` -Wir übergeben das Array der Elemente als dritten Parameter oder mit der Methode `setItems()`. Das Array der Elemente kann auch zweidimensional sein. +Das Array der angebotenen Elemente übergeben wir als dritten Parameter oder mit der Methode `setItems()`. Die Elemente können auch ein zweidimensionales Array sein (für `<optgroup>`). -Sie können mit `setDisabled(['CZ', 'SK'])` verwenden, um einzelne Elemente zu deaktivieren. +Mit `setDisabled(['DE', 'AT'])` können einzelne Elemente deaktiviert werden. -Das Element prüft automatisch, dass keine Fälschung vorliegt und dass die ausgewählten Einträge tatsächlich zu den angebotenen gehören und nicht deaktiviert wurden. Die Methode `getRawValue()` kann verwendet werden, um eingereichte Artikel ohne diese wichtige Prüfung abzurufen. +Das Element überprüft automatisch, dass keine Manipulation stattgefunden hat und dass die ausgewählten Elemente tatsächlich zu den angebotenen gehören und nicht deaktiviert wurden. Mit der Methode `getRawValue()` können die gesendeten Elemente ohne diese wichtige Überprüfung abgerufen werden. -Wenn Standardwerte gesetzt werden, wird auch geprüft, ob sie zu den angebotenen Artikeln gehören, andernfalls wird eine Ausnahme geworfen. Diese Prüfung kann mit `checkDefaultValue(false)` ausgeschaltet werden. +Bei der Einstellung der standardmäßig ausgewählten Elemente wird ebenfalls überprüft, ob es sich um angebotene Elemente handelt, andernfalls wird eine Ausnahme ausgelöst. Diese Prüfung kann mit `checkDefaultValue(false)` deaktiviert werden. addUpload(string|int $name, $label=null): UploadControl .[method] ================================================================= -Fügt ein Datei-Upload-Feld hinzu (Klasse [UploadControl |api:Nette\Forms\Controls\UploadControl]). Gibt das [FileUpload-Objekt |http:request#FileUpload] zurück, auch wenn der Benutzer keine Datei hochgeladen hat, was mit der Methode `FileUpload::hasFile()` herausgefunden werden kann. +Fügt ein Feld zum Hochladen einer Datei hinzu (Klasse [UploadControl |api:Nette\Forms\Controls\UploadControl]). Gibt ein [FileUpload |http:request#FileUpload]-Objekt zurück, auch wenn der Benutzer keine Datei gesendet hat, was mit der Methode `FileUpload::hasFile()` überprüft werden kann. ```php $form->addUpload('avatar', 'Avatar:') - ->addRule($form::Image, 'Avatar must be JPEG, PNG, GIF or WebP') - ->addRule($form::MaxFileSize, 'Maximum size is 1 MB', 1024 * 1024); + ->addRule($form::Image, 'Avatar muss JPEG, PNG, GIF, WebP oder AVIF sein.') + ->addRule($form::MaxFileSize, 'Maximale Größe ist 1 MB.', 1024 * 1024); // 1 MB in Bytes ``` -Wenn die Datei nicht korrekt hochgeladen wurde, wurde das Formular nicht erfolgreich abgeschickt und es wird ein Fehler angezeigt. D.h. es ist nicht notwendig, die Methode `FileUpload::isOk()` zu überprüfen. +Wenn die Datei nicht korrekt hochgeladen werden kann, wird das Formular nicht erfolgreich gesendet und ein Fehler angezeigt. D.h. bei erfolgreichem Senden muss die Methode `FileUpload::isOk()` nicht überprüft werden. -Vertrauen Sie nicht auf den ursprünglichen Dateinamen, der von der Methode `FileUpload::getName()` zurückgegeben wird. +Vertrauen Sie niemals dem ursprünglichen Dateinamen, der von der Methode `FileUpload::getName()` zurückgegeben wird, der Client könnte einen schädlichen Dateinamen gesendet haben, um Ihre Anwendung zu beschädigen oder zu hacken. -Die Regeln `MimeType` und `Image` erkennen den gewünschten Typ einer Datei oder eines Bildes anhand ihrer Signatur. Die Integrität der gesamten Datei wird nicht geprüft. Sie können herausfinden, ob ein Bild nicht beschädigt ist, indem Sie beispielsweise versuchen, [es zu laden |http:request#toImage]. +Die Regeln `MimeType` und `Image` erkennen den erforderlichen Typ anhand der Dateisignatur und überprüfen nicht die Integrität der Datei. Ob ein Bild beschädigt ist, kann beispielsweise durch den Versuch, es [zu laden |http:request#toImage], festgestellt werden. addMultiUpload(string|int $name, $label=null): UploadControl .[method] ====================================================================== -Fügt ein Feld zum Hochladen mehrerer Dateien hinzu (Klasse [UploadControl |api:Nette\Forms\Controls\UploadControl]). Gibt ein Array von Objekten [FileUpload |http:request#FileUpload] zurück. Die Methode `FileUpload::hasFile()` gibt `true` für jedes dieser Objekte zurück. +Fügt ein Feld zum gleichzeitigen Hochladen mehrerer Dateien hinzu (Klasse [UploadControl |api:Nette\Forms\Controls\UploadControl]). Gibt ein Array von [FileUpload |http:request#FileUpload]-Objekten zurück. Die Methode `FileUpload::hasFile()` gibt bei jedem von ihnen `true` zurück, wenn eine Datei hochgeladen wurde. ```php -$form->addMultiUpload('files', 'Files:') - ->addRule($form::MaxLength, 'A maximum of %d files can be uploaded', 10); +$form->addMultiUpload('files', 'Dateien:') + ->addRule($form::MaxLength, 'Maximal können %d Dateien hochgeladen werden', 10); ``` -Wenn eine der Dateien nicht korrekt hochgeladen werden kann, wurde das Formular nicht erfolgreich übermittelt und es wird ein Fehler angezeigt. Es ist also nicht notwendig, die Methode `FileUpload::isOk()` zu überprüfen. +Wenn eine der Dateien nicht korrekt hochgeladen werden kann, wird das Formular nicht erfolgreich gesendet und ein Fehler angezeigt. D.h. bei erfolgreichem Senden muss die Methode `FileUpload::isOk()` nicht für jede Datei überprüft werden, da das Formular als Ganzes ungültig wäre. + +Vertrauen Sie niemals den ursprünglichen Dateinamen, die von der Methode `FileUpload::getName()` zurückgegeben werden, der Client könnte schädliche Dateinamen gesendet haben, um Ihre Anwendung zu beschädigen oder zu hacken. + +Die Regeln `MimeType` und `Image` erkennen den erforderlichen Typ anhand der Dateisignatur und überprüfen nicht die Integrität der Datei. Ob ein Bild beschädigt ist, kann beispielsweise durch den Versuch, es [zu laden |http:request#toImage], festgestellt werden. + -Vertrauen Sie nicht auf die von der Methode `FileUpload::getName()` zurückgegebenen Originaldateinamen. Ein Client könnte einen bösartigen Dateinamen mit der Absicht senden, Ihre Anwendung zu beschädigen oder zu hacken. +addDate(string|int $name, $label=null): DateTimeControl .[method]{data-version:3.1.14} +====================================================================================== -Die Regeln `MimeType` und `Image` erkennen den gewünschten Typ einer Datei oder eines Bildes anhand ihrer Signatur. Die Integrität der gesamten Datei wird nicht geprüft. Sie können herausfinden, ob ein Bild nicht beschädigt ist, indem Sie beispielsweise versuchen, [es zu laden |http:request#toImage]. +Fügt ein Feld hinzu, das es dem Benutzer ermöglicht, einfach ein Datum bestehend aus Jahr, Monat und Tag einzugeben (Klasse [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). +Als Standardwert akzeptiert es entweder Objekte, die die Schnittstelle `DateTimeInterface` implementieren, eine Zeichenkette mit der Zeit oder eine Zahl, die einen UNIX-Zeitstempel darstellt. Dasselbe gilt für die Argumente der Regeln `Min`, `Max` oder `Range`, die das minimal und maximal zulässige Datum definieren. -addHidden(string|int $name, string $default=null): HiddenField .[method] -======================================================================== +```php +$form->addDate('date', 'Datum:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'Das Datum muss mindestens einen Monat alt sein.', new DateTime('-1 month')); +``` -Fügt ein verstecktes Feld (Klasse [HiddenField |api:Nette\Forms\Controls\HiddenField]) hinzu. +Standardmäßig gibt es ein `DateTimeImmutable`-Objekt zurück, mit der Methode `setFormat()` können Sie das [Textformat|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] oder den Zeitstempel angeben: + +```php +$form->addDate('date', 'Datum:') + ->setFormat('Y-m-d'); +``` + + +addTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=============================================================================================================== + +Fügt ein Feld hinzu, das es dem Benutzer ermöglicht, einfach eine Zeit bestehend aus Stunden, Minuten und optional auch Sekunden einzugeben (Klasse [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Als Standardwert akzeptiert es entweder Objekte, die die Schnittstelle `DateTimeInterface` implementieren, eine Zeichenkette mit der Zeit oder eine Zahl, die einen UNIX-Zeitstempel darstellt. Aus diesen Eingaben wird nur die Zeitinformation verwendet, das Datum wird ignoriert. Dasselbe gilt für die Argumente der Regeln `Min`, `Max` oder `Range`, die die minimal und maximal zulässige Zeit definieren. Wenn der minimale Wert höher als der maximale ist, wird ein Zeitbereich erstellt, der Mitternacht überschreitet. + +```php +$form->addTime('time', 'Zeit:', withSeconds: true) + ->addRule($form::Range, 'Die Zeit muss im Bereich von %d bis %d liegen.', ['12:30', '13:30']); +``` + +Standardmäßig gibt es ein `DateTimeImmutable`-Objekt zurück (mit dem Datum 1. Januar des Jahres 1), mit der Methode `setFormat()` können Sie das [Textformat|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] angeben: + +```php +$form->addTime('time', 'Zeit:') + ->setFormat('H:i'); +``` + + +addDateTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=================================================================================================================== + +Fügt ein Feld hinzu, das es dem Benutzer ermöglicht, einfach Datum und Uhrzeit bestehend aus Jahr, Monat, Tag, Stunden, Minuten und optional auch Sekunden einzugeben (Klasse [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Als Standardwert akzeptiert es entweder Objekte, die die Schnittstelle `DateTimeInterface` implementieren, eine Zeichenkette mit der Zeit oder eine Zahl, die einen UNIX-Zeitstempel darstellt. Dasselbe gilt für die Argumente der Regeln `Min`, `Max` oder `Range`, die das minimal und maximal zulässige Datum und die Uhrzeit definieren. + +```php +$form->addDateTime('datetime', 'Datum und Uhrzeit:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'Das Datum muss mindestens einen Monat alt sein.', new DateTime('-1 month')); +``` + +Standardmäßig gibt es ein `DateTimeImmutable`-Objekt zurück, mit der Methode `setFormat()` können Sie das [Textformat|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] oder den Zeitstempel angeben: + +```php +$form->addDateTime('datetime') + ->setFormat(DateTimeControl::FormatTimestamp); +``` + + +addColor(string|int $name, $label=null): ColorPicker .[method]{data-version:3.1.14} +=================================================================================== + +Fügt ein Feld zur Farbauswahl hinzu (Klasse [ColorPicker |api:Nette\Forms\Controls\ColorPicker]). Die Farbe ist eine Zeichenkette im Format `#rrggbb`. Wenn der Benutzer keine Auswahl trifft, wird die schwarze Farbe `#000000` zurückgegeben. + +```php +$form->addColor('color', 'Farbe:') + ->setDefaultValue('#3C8ED7'); +``` + + +addHidden(string|int $name, ?string $default=null): HiddenField .[method] +========================================================================= + +Fügt ein verstecktes Feld hinzu (Klasse [HiddenField |api:Nette\Forms\Controls\HiddenField]). ```php $form->addHidden('userid'); ``` -Verwenden Sie `setNullable()`, um die Rückgabe von `null` anstelle eines leeren Strings zu ändern. Mit [addFilter() |validation#Modifying Input Values] können Sie den übergebenen Wert ändern. +Mit `setNullable()` kann festgelegt werden, dass `null` anstelle einer leeren Zeichenkette zurückgegeben wird. Der gesendete Wert kann mit [addFilter() |validation#Anpassung der Eingabe] geändert werden. + +Obwohl das Element versteckt ist, ist es **wichtig zu beachten**, dass der Wert immer noch von einem Angreifer geändert oder gefälscht werden kann. Überprüfen und validieren Sie immer gründlich alle empfangenen Werte auf der Serverseite, um Sicherheitsrisiken im Zusammenhang mit Datenmanipulation zu vermeiden. addSubmit(string|int $name, $caption=null): SubmitButton .[method] ================================================================== -Fügt die Schaltfläche Submit (Klasse [SubmitButton |api:Nette\Forms\Controls\SubmitButton]) hinzu. +Fügt eine Senden-Schaltfläche hinzu (Klasse [SubmitButton |api:Nette\Forms\Controls\SubmitButton]). ```php -$form->addSubmit('submit', 'Register'); +$form->addSubmit('submit', 'Senden'); ``` -Es ist möglich, mehr als einen Submit-Button im Formular zu haben: +Es ist möglich, mehrere Senden-Schaltflächen in einem Formular zu haben: ```php -$form->addSubmit('register', 'Register'); -$form->addSubmit('cancel', 'Cancel'); +$form->addSubmit('register', 'Registrieren'); +$form->addSubmit('cancel', 'Abbrechen'); ``` -Um herauszufinden, welche von ihnen angeklickt wurde, verwenden Sie: +Um herauszufinden, welche davon geklickt wurde, verwenden Sie: ```php if ($form['register']->isSubmittedBy()) { @@ -282,48 +377,48 @@ if ($form['register']->isSubmittedBy()) { } ``` -Wenn Sie das Formular nicht validieren wollen, wenn ein Submit-Button gedrückt wird (wie *Abbrechen* oder *Vorschau*), können Sie dies mit [setValidationScope() |validation#Disabling Validation] ausschalten. +Wenn Sie das gesamte Formular beim Klicken auf eine Schaltfläche nicht validieren möchten (z. B. bei Schaltflächen *Abbrechen* oder *Vorschau*), verwenden Sie [setValidationScope() |validation#Validierung deaktivieren]. addButton(string|int $name, $caption): Button .[method] ======================================================= -Fügt eine Schaltfläche (Klasse [Button |api:Nette\Forms\Controls\Button]) ohne Sendefunktion hinzu. Sie ist nützlich, um andere Funktionen an die id zu binden, zum Beispiel eine JavaScript-Aktion. +Fügt eine Schaltfläche hinzu (Klasse [Button |api:Nette\Forms\Controls\Button]), die keine Sende-Funktion hat. Sie kann also für eine andere Funktion verwendet werden, z. B. zum Aufrufen einer JavaScript-Funktion beim Klicken. ```php -$form->addButton('raise', 'Raise salary') +$form->addButton('raise', 'Gehalt erhöhen') ->setHtmlAttribute('onclick', 'raiseSalary()'); ``` -addImageButton(string|int $name, string $src=null, string $alt=null): ImageButton .[method] -=========================================================================================== +addImageButton(string|int $name, ?string $src=null, ?string $alt=null): ImageButton .[method] +============================================================================================= -Fügt einen Submit-Button in Form eines Bildes hinzu (Klasse [ImageButton |api:Nette\Forms\Controls\ImageButton]). +Fügt eine Senden-Schaltfläche in Form eines Bildes hinzu (Klasse [ImageButton |api:Nette\Forms\Controls\ImageButton]). ```php -$form->addImageButton('submit', '/path/to/image'); +$form->addImageButton('submit', '/pfad/zum/bild'); ``` -Wenn Sie mehrere Submit-Buttons verwenden, können Sie herausfinden, welcher Button angeklickt wurde mit `$form['submit']->isSubmittedBy()`. +Bei Verwendung mehrerer Senden-Schaltflächen kann mit `$form['submit']->isSubmittedBy()` ermittelt werden, welche geklickt wurde. addContainer(string|int $name): Container .[method] =================================================== -Fügt ein Unterformular (Klasse [Container |api:Nette\Forms\Container]) oder einen Container hinzu, der genauso behandelt werden kann wie ein Formular. Das heißt, Sie können Methoden wie `setDefaults()` oder `getValues()` verwenden. +Fügt ein Unterformular (Klasse [Container|api:Nette\Forms\Container]), also einen Container, hinzu, dem weitere Elemente auf die gleiche Weise hinzugefügt werden können, wie wir sie dem Formular hinzufügen. Auch die Methoden `setDefaults()` oder `getValues()` funktionieren. ```php $sub1 = $form->addContainer('first'); -$sub1->addText('name', 'Your name:'); -$sub1->addEmail('email', 'Email:'); +$sub1->addText('name', 'Ihr Name:'); +$sub1->addEmail('email', 'E-Mail:'); $sub2 = $form->addContainer('second'); -$sub2->addText('name', 'Your name:'); -$sub2->addEmail('email', 'Email:'); +$sub2->addText('name', 'Ihr Name:'); +$sub2->addEmail('email', 'E-Mail:'); ``` -Die gesendeten Daten werden dann als multidimensionale Struktur zurückgegeben: +Die gesendeten Daten werden dann als mehrdimensionale Struktur zurückgegeben: ```php [ @@ -339,110 +434,112 @@ Die gesendeten Daten werden dann als multidimensionale Struktur zurückgegeben: ``` -Übersicht der Einstellungen .[#toc-overview-of-settings] -======================================================== +Übersicht der Einstellungen +=========================== -Für alle Elemente können wir die folgenden Methoden aufrufen (siehe [API-Dokumentation |https://api.nette.org/forms/master/Nette/Forms/Controls.html] für eine vollständige Übersicht): +Für alle Elemente können wir die folgenden Methoden aufrufen (vollständige Übersicht in der [API-Dokumentation|https://api.nette.org/forms/master/Nette/Forms/Controls.html]): .[table-form-methods language-php] -| `setDefaultValue($value)` | setzt den Standardwert -| `getValue()` | liefert den aktuellen Wert -| `setOmitted()` | [Ausgelassene Werte |#omitted values] -| `setDisabled()` | [Deaktivieren von Eingaben |#disabling inputs] +| `setDefaultValue($value)` | Setzt den Standardwert +| `getValue()` | Ruft den aktuellen Wert ab +| `setOmitted()` | [#Wert auslassen] +| `setDisabled()` | [#Elemente deaktivieren] Rendering: .[table-form-methods language-php] -| `setCaption($caption)`| Ändern der Beschriftung des Eintrags -| `setTranslator($translator)` | setzt den [Übersetzer |rendering#translating] -| `setHtmlAttribute($name, $value)` | setzt das [HTML-Attribut |rendering#HTML attributes] des Elements -| `setHtmlId($id)` | setzt das HTML-Attribut `id` -| `setHtmlType($type)` | setzt HTML-Attribut `type` -| `setHtmlName($name)`| setzt HTML-Attribut `name` -| `setOption($key, $value)` | legt [Rendering-Datenfest |rendering#Options] +| `setCaption($caption)` | Ändert die Beschriftung des Elements +| `setTranslator($translator)` | Setzt den [Übersetzer |rendering#Übersetzung] +| `setHtmlAttribute($name, $value)` | Setzt ein [HTML-Attribut |rendering#HTML-Attribute] des Elements +| `setHtmlId($id)` | Setzt das HTML-Attribut `id` +| `setHtmlName($name)` | Setzt das HTML-Attribut `name` +| `setHtmlType($type)` | Setzt das HTML-Attribut `type` +| `setOption($key, $value)` | [Einstellungen für das Rendering |rendering#Options] Validierung: .[table-form-methods language-php] -| `setRequired()` | [Pflichtfeld |validation] -| `addRule()` | [Validierungsregel hinzufügen |validation#Rules] -| `addCondition()`, `addConditionOn()` | [Validierungsbedingung hinzufügen |validation#Conditions] -| `addError($message)`| [Fehlermeldung übergeben |validation#processing-errors] +| `setRequired()` | [Pflichtfeld |validation] +| `addRule()` | Setzt eine [Validierungsregel |validation#Regeln] +| `addCondition()`, `addConditionOn()` | Setzt eine [Validierungsbedingung |validation#Bedingungen] +| `addError($message)` | [Fehlermeldung übergeben |validation#Fehler bei der Verarbeitung] -Die folgenden Methoden können für die Elemente `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` aufgerufen werden: +Für die Elemente `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` können die folgenden Methoden aufgerufen werden: .[table-form-methods language-php] -| `setNullable()`| legt fest, ob getValue() `null` statt eines leeren Strings zurückgibt -| `setEmptyValue($value)` | legt den speziellen Wert fest, der als leerer String behandelt wird -| `setMaxLength($length)`| legt die maximale Anzahl der erlaubten Zeichen fest -| `addFilter($filter)`| [Ändern von Eingabewerten |validation#Modifying Input Values] +| `setNullable()` | Legt fest, ob getValue() `null` anstelle einer leeren Zeichenkette zurückgibt +| `setEmptyValue($value)` | Setzt einen speziellen Wert, der als leere Zeichenkette betrachtet wird +| `setMaxLength($length)` | Setzt die maximale Anzahl erlaubter Zeichen +| `addFilter($filter)` | [Eingabe anpassen |validation#Anpassung der Eingabe] -Ausgelassene Werte .[#toc-omitted-values] ------------------------------------------ +Wert auslassen +============== -Wenn Sie an dem vom Benutzer eingegebenen Wert nicht interessiert sind, können wir `setOmitted()` verwenden, um ihn aus dem Ergebnis auszulassen, das von der Methode `$form->getValues​()` geliefert oder an Handler übergeben wird. Dies eignet sich für verschiedene Passwörter zur Überprüfung, Antispam-Felder usw. +Wenn uns der vom Benutzer ausgefüllte Wert nicht interessiert, können wir ihn mit `setOmitted()` aus dem Ergebnis der Methode `$form->getValues()` oder aus den an die Handler übergebenen Daten auslassen. Dies ist nützlich für verschiedene Kontrollpasswörter, Anti-Spam-Elemente usw. ```php -$form->addPassword('passwordVerify', 'Password again:') - ->setRequired('Fill your password again to check for typo') - ->addRule($form::Equal, 'Password mismatch', $form['password']) +$form->addPassword('passwordVerify', 'Passwort zur Kontrolle:') + ->setRequired('Bitte geben Sie das Passwort zur Kontrolle noch einmal ein') + ->addRule($form::Equal, 'Die Passwörter stimmen nicht überein', $form['password']) ->setOmitted(); ``` -Deaktivieren von Eingaben .[#toc-disabling-inputs] --------------------------------------------------- +Elemente deaktivieren +===================== -Um eine Eingabe zu deaktivieren, können Sie `setDisabled()` aufrufen. Ein solches Feld kann vom Benutzer nicht bearbeitet werden. +Elemente können mit `setDisabled()` deaktiviert werden. Ein solches Element kann der Benutzer nicht bearbeiten. ```php -$form->addText('username', 'User name:') +$form->addText('username', 'Benutzername:') ->setDisabled(); ``` -Beachten Sie, dass der Browser die deaktivierten Felder gar nicht an den Server sendet, so dass Sie sie nicht einmal in den von der Funktion `$form->getValues()` zurückgegebenen Daten finden werden. +Deaktivierte Elemente sendet der Browser überhaupt nicht an den Server, daher finden Sie sie auch nicht in den von der Funktion `$form->getValues()` zurückgegebenen Daten. Wenn Sie jedoch `setOmitted(false)` einstellen, schließt Nette ihren Standardwert in diese Daten ein. -Wenn Sie einen Standardwert für ein Feld festlegen, dürfen Sie dies erst tun, nachdem Sie das Feld deaktiviert haben: +Beim Aufruf von `setDisabled()` wird aus Sicherheitsgründen **der Wert des Elements gelöscht**. Wenn Sie einen Standardwert festlegen, muss dies nach der Deaktivierung erfolgen: ```php -$form->addText('username', 'User name:') +$form->addText('username', 'Benutzername:') ->setDisabled() ->setDefaultValue($userName); ``` +Eine Alternative zu deaktivierten Elementen sind Elemente mit dem HTML-Attribut `readonly`, die der Browser an den Server sendet. Obwohl das Element nur lesbar ist, ist es **wichtig zu beachten**, dass sein Wert immer noch von einem Angreifer geändert oder gefälscht werden kann. + -Benutzerdefinierte Steuerelemente .[#toc-custom-controls] -========================================================= +Eigene Elemente +=============== -Neben der großen Auswahl an eingebauten Steuerelementen können Sie dem Formular wie folgt benutzerdefinierte Steuerelemente hinzufügen: +Neben der breiten Palette an integrierten Formularelementen können Sie dem Formular auf diese Weise eigene Elemente hinzufügen: ```php -$form->addComponent(new DateInput('Date:'), 'date'); +$form->addComponent(new DateInput('Datum:'), 'date'); // alternative Syntax: $form['date'] = new DateInput('Datum:'); ``` .[note] -Das Formular ist ein Abkömmling der Klasse [Container | component-model:#Container] und die Elemente sind Abkömmlinge von [Component | component-model:#Component]. +Das Formular ist ein Nachkomme der Klasse [Container |component-model:#Container] und die einzelnen Elemente sind Nachkommen von [Component |component-model:#Component]. -Es gibt eine Möglichkeit, neue Formularmethoden zu definieren, um benutzerdefinierte Elemente hinzuzufügen (z. B. `$form->addZip()`). Dies sind die sogenannten Erweiterungsmethoden. Der Nachteil ist, dass Codehinweise in Editoren für sie nicht funktionieren. +Es gibt eine Möglichkeit, neue Methoden des Formulars zu definieren, die zum Hinzufügen eigener Elemente dienen (z. B. `$form->addZip()`). Dies sind sogenannte Extension Methods. Der Nachteil ist, dass die Code-Vervollständigung in Editoren für sie nicht funktioniert. ```php use Nette\Forms\Container; -// fügt Methode addZip(string $name, string $label = null) -Container::extensionMethod('addZip', function (Container $form, string $name, string $label = null) { +// wir fügen die Methode addZip(string $name, ?string $label = null) hinzu +Container::extensionMethod('addZip', function (Container $form, string $name, ?string $label = null) { return $form->addText($name, $label) - ->addRule($form::Pattern, 'Mindestens 5 Zahlen', '[0-9]{5}'); + ->addRule($form::Pattern, 'Mindestens 5 Ziffern', '[0-9]{5}'); }); // Verwendung -$form->addZip('zip', 'Postleitzahl:'); +$form->addZip('zip', 'PLZ:'); ``` -Low-Level-Felder .[#toc-low-level-fields] -========================================= +Low-Level-Elemente +================== -Um ein Element zum Formular hinzuzufügen, müssen Sie nicht `$form->addXyz()` aufrufen. Formularelemente können stattdessen ausschließlich in Vorlagen eingeführt werden. Dies ist nützlich, wenn Sie z. B. dynamische Elemente erzeugen müssen: +Es können auch Elemente verwendet werden, die wir nur im Template schreiben und nicht mit einer der `$form->addXyz()`-Methoden zum Formular hinzufügen. Wenn wir beispielsweise Datensätze aus der Datenbank ausgeben und im Voraus nicht wissen, wie viele es sein werden und welche IDs sie haben werden, und wir bei jeder Zeile eine Checkbox oder einen Radiobutton anzeigen möchten, reicht es aus, dies im Template zu codieren: ```latte {foreach $items as $item} @@ -450,13 +547,13 @@ Um ein Element zum Formular hinzuzufügen, müssen Sie nicht `$form->addXyz()` a {/foreach} ``` -Nach dem Absenden können Sie die Werte abrufen: +Und nach dem Absenden ermitteln wir den Wert: ```php $data = $form->getHttpData($form::DataText, 'sel[]'); $data = $form->getHttpData($form::DataText | $form::DataKeys, 'sel[]'); ``` -Im ersten Parameter geben Sie den Elementtyp an (`DataFile` für `type=file`, `DataLine` für einzeilige Eingaben wie `text`, `password` oder `email` und `DataText` für die übrigen). Der zweite Parameter entspricht dem HTML-Attribut `name`. Wenn Sie Schlüssel beibehalten müssen, können Sie den ersten Parameter mit `DataKeys` kombinieren. Dies ist nützlich für `select`, `radioList` oder `checkboxList`. +wobei der erste Parameter der Elementtyp ist (`DataFile` für `type=file`, `DataLine` für einzeilige Eingaben wie `text`, `password`, `email` usw. und `DataText` für alle anderen wie `checkbox`, `radio`, `textarea`) und der zweite Parameter `sel[]` dem HTML-Attribut `name` entspricht. Den Elementtyp können wir mit dem Wert `DataKeys` kombinieren, der die Schlüssel der Elemente beibehält. Dies ist besonders nützlich für `select`, `radioList` und `checkboxList`. -`getHttpData()` gibt die bereinigte Eingabe zurück. In diesem Fall handelt es sich immer um ein Array gültiger UTF-8-Strings, unabhängig davon, was der Angreifer über das Formular gesendet hat. Es ist eine Alternative zur direkten Arbeit mit `$_POST` oder `$_GET`, wenn Sie sichere Daten erhalten möchten. +Wichtig ist, dass `getHttpData()` einen bereinigten Wert zurückgibt, in diesem Fall wird es immer ein Array gültiger UTF-8-Zeichenketten sein, egal was ein Angreifer versuchen würde, dem Server unterzuschieben. Dies ist analog zur direkten Arbeit mit `$_POST` oder `$_GET`, jedoch mit dem wesentlichen Unterschied, dass immer saubere Daten zurückgegeben werden, so wie Sie es von den Standardelementen der Nette-Formulare gewohnt sind. diff --git a/forms/de/in-presenter.texy b/forms/de/in-presenter.texy index 41f8158caf..9e76d4bae8 100644 --- a/forms/de/in-presenter.texy +++ b/forms/de/in-presenter.texy @@ -1,36 +1,36 @@ -Formulare in Moderatoren -************************ +Formulare in Presentern +*********************** .[perex] -Nette Forms erleichtert die Erstellung und Bearbeitung von Webformularen erheblich. In diesem Kapitel lernen Sie, wie Sie Formulare in Presentern verwenden können. +Nette Forms erleichtern die Erstellung und Verarbeitung von Webformularen erheblich. In diesem Kapitel lernen Sie die Verwendung von Formularen innerhalb von Presentern kennen. -Wenn Sie daran interessiert sind, sie völlig eigenständig ohne den Rest des Frameworks zu verwenden, gibt es eine Anleitung für [eigenständige Formulare |standalone]. +Wenn Sie daran interessiert sind, wie man sie völlig eigenständig ohne den Rest des Frameworks verwendet, ist die Anleitung zur [eigenständigen Verwendung|standalone] für Sie bestimmt. -Erstes Formular .[#toc-first-form] -================================== +Erstes Formular +=============== -Wir werden versuchen, ein einfaches Registrierungsformular zu schreiben. Sein Code wird wie folgt aussehen: +Versuchen wir, ein einfaches Registrierungsformular zu schreiben. Sein Code wird wie folgt aussehen: ```php use Nette\Application\UI\Form; $form = new Form; $form->addText('name', 'Name:'); -$form->addPassword('password', 'Password:'); -$form->addSubmit('send', 'Sign up'); +$form->addPassword('password', 'Passwort:'); +$form->addSubmit('send', 'Registrieren'); $form->onSuccess[] = [$this, 'formSucceeded']; ``` -und im Browser sollte das Ergebnis wie folgt aussehen: +und im Browser wird es so angezeigt: -[* form-en.webp *] +[* form-cs.webp *] -Das Formular im Presenter ist ein Objekt der Klasse `Nette\Application\UI\Form`, sein Vorgänger `Nette\Forms\Form` ist für den eigenständigen Gebrauch gedacht. Wir haben ihm die Felder Name, Passwort und Sendebutton hinzugefügt. Schließlich besagt die Zeile mit `$form->onSuccess`, dass nach dem Absenden und der erfolgreichen Validierung die Methode `$this->formSucceeded()` aufgerufen werden soll. +Ein Formular im Presenter ist ein Objekt der Klasse `Nette\Application\UI\Form`, sein Vorgänger `Nette\Forms\Form` ist für die eigenständige Verwendung bestimmt. Wir haben ihm sogenannte Elemente Name, Passwort und eine Senden-Schaltfläche hinzugefügt. Und schließlich besagt die Zeile mit `$form->onSuccess`, dass nach dem Senden und erfolgreicher Validierung die Methode `$this->formSucceeded()` aufgerufen werden soll. -Aus der Sicht des Präsentators ist das Formular eine gemeinsame Komponente. Daher wird es als Komponente behandelt und mit der [Factory-Methode |application:components#Factory Methods] in den Presenter eingebunden. Das sieht dann wie folgt aus: +Aus Sicht des Presenters ist das Formular eine gewöhnliche Komponente. Daher wird es wie eine Komponente behandelt und wir integrieren es in den Presenter mithilfe einer [Factory-Methode |application:components#Factory-Methoden]. Das wird so aussehen: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} use Nette; use Nette\Application\UI\Form; @@ -40,102 +40,99 @@ class HomePresenter extends Nette\Application\UI\Presenter { $form = new Form; $form->addText('name', 'Name:'); - $form->addPassword('password', 'Password:'); - $form->addSubmit('send', 'Sign up'); + $form->addPassword('password', 'Passwort:'); + $form->addSubmit('send', 'Registrieren'); $form->onSuccess[] = [$this, 'formSucceeded']; return $form; } public function formSucceeded(Form $form, $data): void { - // hier werden die vom Formular gesendeten Daten verarbeitet - // $data->name enthält Name + // hier verarbeiten wir die vom Formular gesendeten Daten + // $data->name enthält den Namen // $data->password enthält das Passwort - $this->flashMessage('Sie haben sich erfolgreich angemeldet.'); + $this->flashMessage('Sie wurden erfolgreich registriert.'); $this->redirect('Home:'); } } ``` -Und das Rendern in der Vorlage erfolgt mit dem Tag `{control}`: +Und im Template rendern wir das Formular mit dem Tag `{control}`: -```latte .{file:app/Presenters/templates/Home/default.latte} -<h1>Registration</h1> +```latte .{file:app/Presentation/Home/default.latte} +<h1>Registrierung</h1> {control registrationForm} ``` -Und das ist alles :-) Wir haben ein funktionierendes und perfekt [gesichertes |#Vulnerability Protection] Formular. +Und das ist eigentlich alles :-) Wir haben ein funktionsfähiges und perfekt [gesichertes |#Schutz vor Schwachstellen] Formular. -Jetzt denken Sie wahrscheinlich, dass es zu schnell ging und fragen sich, wie es möglich ist, dass die Methode `formSucceeded()` aufgerufen wird und welche Parameter sie erhält. Sicher, Sie haben Recht, das verdient eine Erklärung. +Und jetzt denken Sie wahrscheinlich, dass das zu schnell ging, und fragen sich, wie es möglich ist, dass die Methode `formSucceeded()` aufgerufen wird und was die Parameter sind, die sie erhält. Sicher, Sie haben Recht, das verdient eine Erklärung. -Nette hat sich einen coolen Mechanismus einfallen lassen, den wir [Hollywood-Stil |application:components#Hollywood style] nennen. Anstatt ständig nachzufragen, ob etwas passiert ist ("wurde das Formular abgeschickt?", "wurde es gültig abgeschickt?" oder "wurde es nicht gefälscht?"), sagt man dem Framework "wenn das Formular gültig ausgefüllt ist, rufe diese Methode auf" und lässt es weiterarbeiten. Wenn Sie in JavaScript programmieren, sind Sie mit dieser Art der Programmierung vertraut. Sie schreiben Funktionen, die aufgerufen werden, wenn ein bestimmtes [Ereignis |nette:glossary#Events] eintritt. Und die Sprache übergibt die entsprechenden Argumente an sie. +Nette verwendet nämlich einen frischen Mechanismus, den wir [Hollywood style |application:components#Hollywood Style] nennen. Anstatt dass Sie als Entwickler ständig fragen müssen, ob etwas passiert ist („wurde das Formular gesendet?“, „wurde es gültig gesendet?“ und „wurde es nicht gefälscht?“), sagen Sie dem Framework „wenn das Formular gültig ausgefüllt ist, rufe diese Methode auf“ und überlassen ihm die weitere Arbeit. Wenn Sie in JavaScript programmieren, kennen Sie diesen Programmierstil genau. Sie schreiben Funktionen, die aufgerufen werden, wenn ein bestimmtes [Ereignis |nette:glossary#Events Ereignisse] eintritt. Und die Sprache übergibt ihnen die entsprechenden Argumente. -So ist der obige Presenter-Code aufgebaut. Das Array `$form->onSuccess` stellt die Liste der PHP-Rückrufe dar, die Nette aufruft, wenn das Formular eingereicht und korrekt ausgefüllt wurde. -Im [Lebenszyklus des Presenters |application:presenters#Life Cycle of Presenter] handelt es sich um ein so genanntes Signal, das heißt, sie werden nach der Methode `action*` und vor der Methode `render*` aufgerufen. -Und es übergibt jedem Callback im ersten Parameter das Formular selbst und im zweiten Parameter die gesendeten Daten als Objekt [ArrayHash |utils:arrays#ArrayHash]. Sie können den ersten Parameter weglassen, wenn Sie das Formularobjekt nicht benötigen. Der zweite Parameter kann sogar noch praktischer sein, aber dazu [später |#Mapping to Classes] mehr. +Genau so ist auch der oben genannte Presenter-Code aufgebaut. Das Array `$form->onSuccess` stellt eine Liste von PHP-Callbacks dar, die Nette aufruft, wenn das Formular gesendet und korrekt ausgefüllt wurde (d. h. es ist gültig). Im Rahmen des [Lebenszyklus des Presenters |application:presenters#Lebenszyklus des Presenters] handelt es sich um ein sogenanntes Signal, sie werden also nach der `action*`-Methode und vor der `render*`-Methode aufgerufen. Und jedem Callback übergibt es als ersten Parameter das Formular selbst und als zweiten die gesendeten Daten in Form eines [ArrayHash |utils:arrays#ArrayHash]-Objekts (oder einer benutzerdefinierten Klasse, siehe unten). Den ersten Parameter können Sie weglassen, wenn Sie das Formularobjekt nicht benötigen. Und der zweite Parameter kann cleverer sein, aber dazu [später mehr |#Mapping auf Klassen]. -Das Objekt `$data` enthält die Eigenschaften `name` und `password` mit den vom Benutzer eingegebenen Daten. Normalerweise senden wir die Daten direkt zur weiteren Verarbeitung, z. B. zum Einfügen in die Datenbank. Es kann jedoch ein Fehler bei der Verarbeitung auftreten, z. B. wenn der Benutzername bereits vergeben ist. In diesem Fall geben wir den Fehler mit `addError()` an das Formular zurück und lassen es neu zeichnen, mit einer Fehlermeldung: +Das Objekt `$data` enthält die Schlüssel `name` und `password` mit den Daten, die der Benutzer eingegeben hat. Normalerweise senden wir die Daten direkt zur weiteren Verarbeitung, was beispielsweise das Einfügen in die Datenbank sein kann. Während der Verarbeitung kann jedoch ein Fehler auftreten, z. B. wenn der Benutzername bereits vergeben ist. In diesem Fall übergeben wir den Fehler mit `addError()` zurück an das Formular und lassen es erneut rendern, auch mit der Fehlermeldung. ```php -$form->addError('Sorry, username is already in use.'); +$form->addError('Entschuldigung, der Benutzername wird bereits verwendet.'); ``` -Neben `onSuccess` gibt es auch `onSubmit`: Callbacks werden immer nach dem Absenden des Formulars aufgerufen, auch wenn es nicht korrekt ausgefüllt ist. Und schließlich `onError`: Rückrufe werden nur aufgerufen, wenn die Übermittlung nicht gültig ist. Sie werden sogar aufgerufen, wenn wir das Formular in `onSuccess` oder `onSubmit` mit `addError()` ungültig machen. +Neben `onSuccess` gibt es noch `onSubmit`: Callbacks werden immer nach dem Senden des Formulars aufgerufen, auch wenn es nicht korrekt ausgefüllt ist. Und weiter `onError`: Callbacks werden nur aufgerufen, wenn das Senden nicht gültig ist. Sie werden sogar dann aufgerufen, wenn wir in `onSuccess` oder `onSubmit` das Formular mit `addError()` ungültig machen. -Nach der Bearbeitung des Formulars werden wir zur nächsten Seite weiterleiten. Dadurch wird verhindert, dass das Formular unbeabsichtigt durch Anklicken des *Refresh*- oder *Back*-Buttons oder durch Verschieben des Browserverlaufs erneut abgeschickt wird. +Nach der Verarbeitung des Formulars leiten wir auf eine andere Seite weiter. Dies verhindert das unbeabsichtigte erneute Senden des Formulars durch die Schaltflächen *Aktualisieren*, *Zurück* oder durch die Bewegung im Browserverlauf. -Versuchen Sie, weitere [Formularsteuerelemente |controls] hinzuzufügen. +Versuchen Sie, auch weitere [Formularelemente|controls] hinzuzufügen. -Zugang zu Steuerelementen .[#toc-access-to-controls] -==================================================== +Zugriff auf Elemente +==================== -Das Formular ist eine Komponente des Presenters, in unserem Fall mit dem Namen `registrationForm` (nach dem Namen der Factory-Methode `createComponentRegistrationForm`), so dass Sie von jeder Stelle des Presenters aus auf das Formular zugreifen können: +Das Formular ist eine Komponente des Presenters, in unserem Fall namens `registrationForm` (nach dem Namen der Factory-Methode `createComponentRegistrationForm`), sodass Sie überall im Presenter mit Folgendem auf das Formular zugreifen können: ```php $form = $this->getComponent('registrationForm'); // alternative Syntax: $form = $this['registrationForm']; ``` -Auch die einzelnen Steuerelemente des Formulars sind Komponenten, so dass Sie auf sie auf die gleiche Weise zugreifen können: +Auch die einzelnen Formularelemente sind Komponenten, daher können Sie auf die gleiche Weise darauf zugreifen: ```php $input = $form->getComponent('name'); // oder $input = $form['name']; $button = $form->getComponent('send'); // oder $button = $form['send']; ``` -Steuerelemente werden mit unset entfernt: +Elemente werden mit `unset` entfernt: ```php unset($form['name']); ``` -Überprüfungsregeln .[#toc-validation-rules] -=========================================== +Validierungsregeln +================== -Das Wort *valid* wurde mehrere Male verwendet, aber das Formular hat noch keine Validierungsregeln. Das müssen wir ändern. +Das Wort *gültig* fiel, aber das Formular hat bisher keine Validierungsregeln. Lassen Sie uns das beheben. -Der Name wird obligatorisch sein, also werden wir ihn mit der Methode `setRequired()` markieren, deren Argument der Text der Fehlermeldung ist, die angezeigt wird, wenn der Benutzer sie nicht ausfüllt. Wenn kein Argument angegeben wird, wird die Standard-Fehlermeldung verwendet. +Der Name wird obligatorisch sein, daher markieren wir ihn mit der Methode `setRequired()`, deren Argument der Text der Fehlermeldung ist, die angezeigt wird, wenn der Benutzer den Namen nicht ausfüllt. Wenn kein Argument angegeben wird, wird die Standardfehlermeldung verwendet. ```php $form->addText('name', 'Name:') - ->setRequired('Please fill your name.'); + ->setRequired('Bitte geben Sie einen Namen ein'); ``` -Versuchen Sie, das Formular abzuschicken, ohne den Namen auszufüllen, und Sie werden sehen, dass eine Fehlermeldung angezeigt wird und der Browser oder Server das Formular ablehnt, bis Sie es ausgefüllt haben. +Versuchen Sie, das Formular ohne ausgefüllten Namen abzusenden, und Sie werden sehen, dass eine Fehlermeldung angezeigt wird und der Browser oder Server es ablehnt, bis Sie das Feld ausfüllen. -Gleichzeitig können Sie das System nicht austricksen, indem Sie z. B. nur Leerzeichen in die Eingabe eintippen. Das geht nicht. Nette schneidet automatisch linke und rechte Leerzeichen ab. Probieren Sie es aus. Das sollten Sie bei jeder einzeiligen Eingabe immer tun, aber das wird oft vergessen. Nette macht es automatisch. (Sie können versuchen, die Formulare zu täuschen und eine mehrzeilige Zeichenkette als Namen zu senden. Auch hier lässt sich Nette nicht täuschen und die Zeilenumbrüche werden in Leerzeichen umgewandelt.) +Gleichzeitig können Sie das System nicht austricksen, indem Sie beispielsweise nur Leerzeichen in das Feld eingeben. Nein. Nette entfernt automatisch führende und nachfolgende Leerzeichen. Probieren Sie es aus. Das ist etwas, das Sie bei jedem einzeiligen Eingabefeld immer tun sollten, aber oft vergessen wird. Nette tut dies automatisch. (Sie können versuchen, das Formular auszutricksen und als Namen eine mehrzeilige Zeichenkette zu senden. Auch hier lässt sich Nette nicht täuschen und ändert Zeilenumbrüche in Leerzeichen.) -Das Formular wird immer serverseitig validiert, aber es wird auch eine JavaScript-Validierung generiert, die schnell ist und dem Benutzer den Fehler sofort anzeigt, ohne dass das Formular an den Server gesendet werden muss. Dies wird durch das Skript `netteForms.js` erledigt. -Fügen Sie es in die Layout-Vorlage ein: +Das Formular wird immer serverseitig validiert, aber es wird auch eine JavaScript-Validierung generiert, die blitzschnell abläuft, und der Benutzer erfährt sofort von dem Fehler, ohne das Formular an den Server senden zu müssen. Dafür ist das Skript `netteForms.js` verantwortlich. Fügen Sie es in das Layout-Template ein: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Wenn Sie in den Quellcode der Seite mit dem Formular schauen, werden Sie feststellen, dass Nette die erforderlichen Felder in Elemente mit einer CSS-Klasse `required` einfügt. Versuchen Sie, den folgenden Stil in die Vorlage einzufügen, und die Bezeichnung "Name" wird rot sein. Auf elegante Weise markieren wir die erforderlichen Felder für die Benutzer: +Wenn Sie sich den Quellcode der Seite mit dem Formular ansehen, können Sie feststellen, dass Nette Pflichtfelder in Elemente mit der CSS-Klasse `required` einfügt. Versuchen Sie, das folgende Stylesheet zum Template hinzuzufügen, und die Beschriftung „Name“ wird rot. So markieren wir elegant Pflichtfelder für die Benutzer: ```latte <style> @@ -143,110 +140,112 @@ Wenn Sie in den Quellcode der Seite mit dem Formular schauen, werden Sie festste </style> ``` -Zusätzliche Validierungsregeln werden mit der Methode `addRule()` hinzugefügt. Der erste Parameter ist rule, der zweite ist wieder der Text der Fehlermeldung, und das optionale Argument validation rule kann folgen. Was bedeutet das? +Weitere Validierungsregeln fügen wir mit der Methode `addRule()` hinzu. Der erste Parameter ist die Regel, der zweite ist wieder der Text der Fehlermeldung, und es kann noch ein Argument der Validierungsregel folgen. Was ist damit gemeint? -Das Formular erhält eine weitere optionale Eingabe *Alter* mit der Bedingung, dass es eine Zahl sein muss (`addInteger()`) und in bestimmten Grenzen (`$form::Range`). Und hier werden wir das dritte Argument von `addRule()` verwenden, den Bereich selbst: +Wir erweitern das Formular um ein neues optionales Feld „Alter“, das eine ganze Zahl sein muss (`addInteger()`) und außerdem in einem erlaubten Bereich (`$form::Range`) liegen muss. Und hier verwenden wir genau den dritten Parameter der Methode `addRule()`, mit dem wir dem Validator den erforderlichen Bereich als Paar `[von, bis]` übergeben: ```php -$form->addInteger('age', 'Age:') - ->addRule($form::Range, 'You must be older 18 years and be under 120.', [18, 120]); +$form->addInteger('age', 'Alter:') + ->addRule($form::Range, 'Das Alter muss zwischen 18 und 120 liegen', [18, 120]); ``` .[tip] -Wenn der Benutzer das Feld nicht ausfüllt, werden die Validierungsregeln nicht überprüft, da das Feld optional ist. +Wenn der Benutzer das Feld nicht ausfüllt, werden die Validierungsregeln nicht überprüft, da das Element optional ist. -Offensichtlich ist Raum für ein kleines Refactoring vorhanden. In der Fehlermeldung und im dritten Parameter sind die Zahlen doppelt aufgeführt, was nicht ideal ist. Wenn wir ein [mehrsprachiges Formular |rendering#translating] erstellen würden und die Nachricht mit den Zahlen in mehrere Sprachen übersetzt werden müsste, würde dies die Änderung der Werte erschweren. Aus diesem Grund können die Ersatzzeichen `%d` verwendet werden: +Hier entsteht Raum für ein kleines Refactoring. In der Fehlermeldung und im dritten Parameter sind die Zahlen doppelt aufgeführt, was nicht ideal ist. Wenn wir [mehrsprachige Formulare |rendering#Übersetzung] erstellen würden und die Meldung mit Zahlen in mehrere Sprachen übersetzt würde, würde eine spätere Änderung der Werte erschwert. Aus diesem Grund können Platzhalter `%d` verwendet werden, und Nette füllt die Werte ein: ```php - ->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]); + ->addRule($form::Range, 'Das Alter muss zwischen %d und %d Jahren liegen', [18, 120]); ``` -Kehren wir zum Feld *Kennwort* zurück, machen es *erforderlich* und überprüfen die Mindestlänge des Kennworts (`$form::MinLength`), wiederum unter Verwendung der Ersatzzeichen in der Nachricht: +Kehren wir zum Element `password` zurück, das wir ebenfalls obligatorisch machen und noch die minimale Passwortlänge überprüfen (`$form::MinLength`), wieder unter Verwendung des Platzhalters: ```php -$form->addPassword('password', 'Password:') - ->setRequired('Pick a password') - ->addRule($form::MinLength, 'Your password has to be at least %d long', 8); +$form->addPassword('password', 'Passwort:') + ->setRequired('Wählen Sie ein Passwort') + ->addRule($form::MinLength, 'Das Passwort muss mindestens %d Zeichen lang sein', 8); ``` -Wir fügen dem Formular ein Feld `passwordVerify` hinzu, in das der Benutzer das Passwort erneut eingibt, um es zu überprüfen. Mit Hilfe von Validierungsregeln überprüfen wir, ob beide Passwörter gleich sind (`$form::Equal`). Und als Argument geben wir in [eckigen Klammern |#Access to Controls] einen Verweis auf das erste Kennwort an: +Wir fügen dem Formular noch ein Feld `passwordVerify` hinzu, in das der Benutzer das Passwort zur Kontrolle noch einmal eingibt. Mit Validierungsregeln überprüfen wir, ob beide Passwörter übereinstimmen (`$form::Equal`). Und als Parameter geben wir einen Verweis auf das erste Passwort mithilfe von [eckigen Klammern |#Zugriff auf Elemente] an: ```php -$form->addPassword('passwordVerify', 'Password again:') - ->setRequired('Fill your password again to check for typo') - ->addRule($form::Equal, 'Password mismatch', $form['password']) +$form->addPassword('passwordVerify', 'Passwort zur Kontrolle:') + ->setRequired('Bitte geben Sie das Passwort zur Kontrolle noch einmal ein') + ->addRule($form::Equal, 'Die Passwörter stimmen nicht überein', $form['password']) ->setOmitted(); ``` -Mit `setOmitted()` markieren wir ein Element, dessen Wert uns nicht wirklich interessiert und das nur zur Validierung existiert. Sein Wert wird nicht an `$data` übergeben. +Mit `setOmitted()` haben wir das Element markiert, dessen Wert uns eigentlich egal ist und das nur aus Validierungsgründen existiert. Der Wert wird nicht an `$data` übergeben. -Wir haben ein voll funktionsfähiges Formular mit Validierung in PHP und JavaScript. Die Validierungsmöglichkeiten von Nette sind viel umfassender, Sie können Bedingungen erstellen, Teile einer Seite entsprechend anzeigen und ausblenden usw. Sie können alles im Kapitel über [Formularvalidierung |validation] nachlesen. +Damit haben wir ein voll funktionsfähiges Formular mit Validierung in PHP und JavaScript fertiggestellt. Die Validierungsfähigkeiten von Nette sind weitaus umfangreicher, es können Bedingungen erstellt, Teile der Seite entsprechend ein- und ausgeblendet werden usw. Alles erfahren Sie im Kapitel über [Formularvalidierung|validation]. -Standardwerte .[#toc-default-values] -==================================== +Standardwerte +============= -Wir setzen oft Standardwerte für Formularsteuerelemente: +Formularelementen weisen wir üblicherweise Standardwerte zu: ```php -$form->addEmail('email', 'Email') +$form->addEmail('email', 'E-Mail') ->setDefaultValue($lastUsedEmail); ``` -Oft ist es sinnvoll, Standardwerte für alle Steuerelemente gleichzeitig festzulegen. Zum Beispiel, wenn das Formular verwendet wird, um Datensätze zu bearbeiten. Wir lesen den Datensatz aus der Datenbank und legen ihn als Standardwerte fest: +Oft ist es nützlich, Standardwerte für alle Elemente gleichzeitig festzulegen. Zum Beispiel, wenn das Formular zur Bearbeitung von Datensätzen dient. Wir lesen den Datensatz aus der Datenbank und setzen die Standardwerte: ```php //$row = ['name' => 'John', 'age' => '33', /* ... */]; $form->setDefaults($row); ``` -Rufen Sie `setDefaults()` auf, nachdem Sie die Steuerelemente definiert haben. +Rufen Sie `setDefaults()` erst nach der Definition der Elemente auf. -Rendering des Formulars .[#toc-rendering-the-form] -================================================== +Rendering des Formulars +======================= -Standardmäßig wird das Formular als Tabelle gerendert. Die einzelnen Steuerelemente entsprechen den grundlegenden Richtlinien für die Barrierefreiheit im Web. Alle Beschriftungen werden als `<label>` Elemente generiert und sind mit ihren Eingaben verknüpft; ein Klick auf die Beschriftung bewegt den Cursor auf die Eingabe. +Standardmäßig wird das Formular als Tabelle gerendert. Die einzelnen Elemente erfüllen die grundlegende Zugänglichkeitsregel – alle Beschriftungen sind als `<label>` geschrieben und mit dem entsprechenden Formularelement verknüpft. Beim Klicken auf die Beschriftung erscheint der Cursor automatisch im Formularfeld. -Wir können für jedes Element beliebige HTML-Attribute festlegen. Fügen Sie zum Beispiel einen Platzhalter hinzu: +Jedem Element können wir beliebige HTML-Attribute zuweisen. Zum Beispiel einen Platzhalter hinzufügen: ```php -$form->addInteger('age', 'Age:') - ->setHtmlAttribute('placeholder', 'Please fill in the age'); +$form->addInteger('age', 'Alter:') + ->setHtmlAttribute('placeholder', 'Bitte geben Sie Ihr Alter an'); ``` -Es gibt wirklich viele Möglichkeiten, ein Formular zu rendern, daher ist dieses Kapitel dem [Rendering |rendering] gewidmet. +Es gibt wirklich viele Möglichkeiten, ein Formular zu rendern, daher ist dem ein [eigenes Kapitel über Rendering|rendering] gewidmet. -Mapping auf Klassen .[#toc-mapping-to-classes] -============================================== +Mapping auf Klassen +=================== -Kehren wir zur Methode `formSucceeded()` zurück, die im zweiten Parameter `$data` die gesendeten Daten als `ArrayHash` Objekt erhält. Da es sich hierbei um eine generische Klasse handelt, ähnlich wie `stdClass`, fehlen uns einige Annehmlichkeiten bei der Arbeit mit ihr, wie z. B. die Codevervollständigung für Eigenschaften in Editoren oder die statische Codeanalyse. Dies könnte durch eine spezifische Klasse für jedes Formular gelöst werden, deren Eigenschaften die einzelnen Steuerelemente darstellen. Z.B.: +Kehren wir zur Methode `formSucceeded()` zurück, die im zweiten Parameter `$data` die gesendeten Daten als `ArrayHash`-Objekt (oder `stdClass`) erhält. Da es sich um eine generische Klasse handelt, fehlt uns bei der Arbeit damit ein gewisser Komfort, wie z. B. die Autovervollständigung von Eigenschaften in Editoren oder die statische Codeanalyse. Dies könnte gelöst werden, indem wir für jedes Formular eine spezifische Klasse hätten, deren Eigenschaften die einzelnen Elemente repräsentieren. Z. B.: ```php class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -Ab PHP 8.0 können Sie diese elegante Notation verwenden, die einen Konstruktor benutzt: +Alternativ können Sie den Konstruktor verwenden (seit PHP 8.0 mit Property Promotion): ```php class RegistrationFormData { public function __construct( public string $name, - public int $age, + public ?int $age, public string $password, ) { } } ``` -Wie sagt man Nette, dass es Daten als Objekte dieser Klasse zurückgeben soll? Einfacher als Sie denken. Alles, was Sie tun müssen, ist, die Klasse als Typ des `$data` Parameters im Handler anzugeben: +Die Eigenschaften der Datenklasse können auch Enums sein und werden automatisch zugeordnet. .{data-version:3.2.4} + +Wie sagen wir Nette, dass es uns Daten als Objekte dieser Klasse zurückgeben soll? Einfacher als Sie denken. Es genügt, die Klasse als Typ des Parameters `$data` in der Handler-Methode anzugeben: ```php public function formSucceeded(Form $form, RegistrationFormData $data): void @@ -257,16 +256,16 @@ public function formSucceeded(Form $form, RegistrationFormData $data): void } ``` -Sie können auch `array` als Typ angeben, dann werden die Daten als Array übergeben. +Als Typ kann auch `array` angegeben werden, dann werden die Daten als assoziatives Array übergeben. -In ähnlicher Weise können Sie die Methode `getValues()` verwenden, die wir als Klassenname oder Objekt an hydrate als Parameter übergeben: +Auf ähnliche Weise kann auch die Methode `getValues()` verwendet werden, der wir den Klassennamen oder ein Objekt zur Hydratisierung als Parameter übergeben: ```php $data = $form->getValues(RegistrationFormData::class); $name = $data->name; ``` -Wenn die Formulare aus einer mehrstufigen Struktur bestehen, die sich aus Containern zusammensetzt, erstellen Sie für jeden Container eine eigene Klasse: +Wenn Formulare eine mehrstufige Struktur aus Containern bilden, erstellen Sie für jeden eine separate Klasse: ```php $form = new Form; @@ -283,32 +282,34 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` -Das Mapping weiß dann anhand des Eigenschaftstyps `$person`, dass es den Container auf die Klasse `PersonFormData` abbilden soll. Wenn die Eigenschaft ein Array von Containern enthalten würde, geben Sie den Typ `array` an und übergeben Sie die Klasse, die direkt auf den Container abgebildet werden soll: +Das Mapping erkennt dann am Typ der Eigenschaft `$person`, dass der Container auf die Klasse `PersonFormData` abgebildet werden soll. Wenn die Eigenschaft ein Array von Containern enthalten würde, geben Sie den Typ `array` an und übergeben Sie die Mapping-Klasse direkt an den Container: ```php $person->setMappedType(PersonFormData::class); ``` +Den Entwurf der Datenklasse des Formulars können Sie sich mit der Methode `Nette\Forms\Blueprint::dataClass($form)` generieren lassen, die ihn auf der Browserseite ausgibt. Den Code können Sie dann einfach per Klick markieren und in Ihr Projekt kopieren. .{data-version:3.1.15} + -Mehrere Submit-Buttons .[#toc-multiple-submit-buttons] -====================================================== +Mehrere Schaltflächen +===================== -Wenn das Formular mehr als eine Schaltfläche hat, müssen wir normalerweise unterscheiden, welche Schaltfläche gedrückt wurde. Wir können für jede Schaltfläche eine eigene Funktion erstellen. Legen Sie sie als Handler für das [Ereignis |nette:glossary#Events] `onClick` fest: +Wenn ein Formular mehr als eine Schaltfläche hat, müssen wir in der Regel unterscheiden, welche davon gedrückt wurde. Wir können für jede Schaltfläche eine eigene Handler-Funktion erstellen. Wir setzen sie als Handler für das [Ereignis |nette:glossary#Events Ereignisse] `onClick`: ```php -$form->addSubmit('save', 'Save') +$form->addSubmit('save', 'Speichern') ->onClick[] = [$this, 'saveButtonPressed']; -$form->addSubmit('delete', 'Delete') +$form->addSubmit('delete', 'Löschen') ->onClick[] = [$this, 'deleteButtonPressed']; ``` -Diese Handler werden auch nur dann aufgerufen, wenn das Formular gültig ist, wie im Fall des Ereignisses `onSuccess`. Der Unterschied besteht darin, dass der erste Parameter das Submit-Button-Objekt anstelle des Formulars sein kann, je nachdem, welchen Typ Sie angeben: +Diese Handler werden nur im Falle eines gültig ausgefüllten Formulars aufgerufen, genau wie beim `onSuccess`-Ereignis. Der Unterschied besteht darin, dass als erster Parameter anstelle des Formulars die sendende Schaltfläche übergeben werden kann, abhängig vom Typ, den Sie angeben: ```php public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) @@ -318,62 +319,61 @@ public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) } ``` -Wenn ein Formular mit der <kbd>Eingabetaste</kbd> abgeschickt wird, wird es so behandelt, als ob es mit der ersten Schaltfläche abgeschickt worden wäre. +Wenn das Formular mit der <kbd>Enter</kbd>-Taste gesendet wird, wird dies so behandelt, als ob es mit der ersten Schaltfläche gesendet wurde. -Ereignis onAnchor .[#toc-event-onanchor] -======================================== +Ereignis onAnchor +================= -Wenn Sie ein Formular in einer Factory-Methode (wie `createComponentRegistrationForm`) erstellen, weiß es noch nicht, ob es abgeschickt wurde oder mit welchen Daten es abgeschickt wurde. Aber es gibt Fälle, in denen wir die übermittelten Werte kennen müssen, vielleicht hängt es von ihnen ab, wie das Formular aussehen wird, oder sie werden für abhängige Auswahlfelder verwendet usw. +Wenn wir in der Factory-Methode (wie z. B. `createComponentRegistrationForm`) das Formular zusammenstellen, weiß es noch nicht, ob es gesendet wurde oder mit welchen Daten. Es gibt jedoch Fälle, in denen wir die gesendeten Werte kennen müssen, z. B. wenn sich die weitere Form des Formulars danach richtet oder wir sie für abhängige Select-Boxen benötigen usw. -Daher kann der Code, der das Formular erstellt, aufgerufen werden, wenn es verankert ist, d.h. wenn es bereits mit dem Präsentator verknüpft ist und die übermittelten Daten kennt. Wir werden diesen Code in das Array `$onAnchor` einfügen: +Den Teil des Codes, der das Formular zusammenstellt, können Sie daher erst aufrufen lassen, wenn es sogenannte verankert ist, d. h. bereits mit dem Presenter verbunden ist und seine gesendeten Daten kennt. Einen solchen Code übergeben wir an das Array `$onAnchor`: ```php -$country = $form->addSelect('country', 'Country:', $this->model->getCountries()); -$city = $form->addSelect('city', 'City:'); +$country = $form->addSelect('country', 'Staat:', $this->model->getCountries()); +$city = $form->addSelect('city', 'Stadt:'); $form->onAnchor[] = function () use ($country, $city) { - // diese Funktion wird aufgerufen, wenn das Formular weiß, dass es mit Daten gesendet wurde - // damit Sie die Methode getValue() verwenden können + // diese Funktion wird erst aufgerufen, wenn das Formular weiß, ob es gesendet wurde und mit welchen Daten + // es kann also die Methode getValue() verwendet werden $val = $country->getValue(); $city->setItems($val ? $this->model->getCities($val) : []); }; ``` -Schutz vor Schwachstellen .[#toc-vulnerability-protection] -========================================================== +Schutz vor Schwachstellen +========================= -Nette Framework gibt sich große Mühe, sicher zu sein, und da Formulare die häufigste Benutzereingabe sind, sind Nette-Formulare so gut wie unangreifbar. Alles wird dynamisch und transparent verwaltet, nichts muss manuell eingestellt werden. +Das Nette Framework legt großen Wert auf Sicherheit und achtet daher sorgfältig auf die gute Absicherung von Formularen. Dies geschieht völlig transparent und erfordert keine manuelle Konfiguration. -Zusätzlich zum Schutz der Formulare vor Angriffen auf bekannte Schwachstellen wie [Cross-Site Scripting (XSS) |nette:glossary#cross-site-scripting-xss] und [Cross-Site Request Forgery (CSRF |nette:glossary#cross-site-request-forgery-csrf]) übernimmt es viele kleine Sicherheitsaufgaben, an die Sie nicht mehr denken müssen. +Neben dem Schutz von Formularen vor [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS]- und [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF]-Angriffen bietet es viele kleine Sicherungen, an die Sie nicht mehr denken müssen. -So filtert es zum Beispiel alle Steuerzeichen aus den Eingaben heraus und überprüft die Gültigkeit der UTF-8-Kodierung, so dass die Daten aus dem Formular immer sauber sind. Bei Auswahlfeldern und Auswahllisten wird überprüft, ob die ausgewählten Elemente tatsächlich aus den angebotenen ausgewählt wurden und keine Fälschung vorliegt. Wie bereits erwähnt, werden bei einzeiligen Texteingaben Zeichen am Zeilenende entfernt, die ein Angreifer dort einfügen könnte. Bei mehrzeiligen Eingaben werden die Zeichen am Zeilenende normalisiert. Und so weiter. +So filtert es beispielsweise alle Steuerzeichen aus den Eingaben und überprüft die Gültigkeit der UTF-8-Kodierung, sodass die Daten aus dem Formular immer sauber sind. Bei Select-Boxen und Radio-Listen wird überprüft, ob die ausgewählten Elemente tatsächlich zu den angebotenen gehörten und keine Manipulation stattgefunden hat. Wir haben bereits erwähnt, dass bei einzeiligen Texteingaben Zeilenendezeichen entfernt werden, die ein Angreifer senden könnte. Bei mehrzeiligen Eingaben werden wiederum die Zeilenendezeichen normalisiert. Und so weiter. -Nette behebt für Sie Sicherheitslücken, von deren Existenz die meisten Programmierer keine Ahnung haben. +Nette löst für Sie Sicherheitsprobleme, von denen viele Programmierer nicht einmal wissen, dass sie existieren. -Der erwähnte CSRF-Angriff besteht darin, dass ein Angreifer das Opfer dazu verleitet, eine Seite zu besuchen, die im Browser des Opfers unbemerkt eine Anfrage an den Server ausführt, bei dem das Opfer gerade angemeldet ist, und der Server glaubt, dass die Anfrage vom Opfer absichtlich gestellt wurde. Daher verhindert Nette, dass das Formular per POST von einer anderen Domäne übermittelt wird. Wenn Sie aus irgendeinem Grund den Schutz ausschalten und die Übermittlung des Formulars von einer anderen Domäne aus zulassen möchten, verwenden Sie: +Der erwähnte CSRF-Angriff besteht darin, dass ein Angreifer das Opfer auf eine Seite lockt, die unbemerkt im Browser des Opfers eine Anfrage an den Server stellt, bei dem das Opfer angemeldet ist, und der Server annimmt, dass die Anfrage vom Opfer selbst ausgeführt wurde. Daher verhindert Nette standardmäßig das Senden von POST-Formularen von einer anderen Domain. Wenn Sie aus irgendeinem Grund den Schutz deaktivieren und das Senden von Formularen von einer anderen Domain erlauben möchten, verwenden Sie: ```php -$form->allowCrossOrigin(); // ACHTUNG! Schaltet den Schutz aus! +$form->allowCrossOrigin(); // ACHTUNG! Deaktiviert den Schutz! ``` -Dieser Schutz verwendet ein SameSite-Cookie namens `_nss`. Der SameSite-Cookie-Schutz ist möglicherweise nicht zu 100 % zuverlässig, weshalb es ratsam ist, den Token-Schutz zu aktivieren: +Dieser Schutz verwendet ein SameSite-Cookie namens `_nss`. Der Schutz durch SameSite-Cookies ist möglicherweise nicht 100 % zuverlässig, daher ist es ratsam, auch den Schutz durch ein Token zu aktivieren: ```php $form->addProtection(); ``` -Es wird dringend empfohlen, diesen Schutz auf die Formulare in einem administrativen Teil Ihrer Anwendung anzuwenden, in dem sensible Daten geändert werden. Das Framework schützt vor einem CSRF-Angriff, indem es ein Authentifizierungs-Token generiert und validiert, das in einer Session gespeichert wird (das Argument ist die Fehlermeldung, die angezeigt wird, wenn das Token abgelaufen ist). Aus diesem Grund muss vor der Anzeige des Formulars eine Session gestartet werden. Im Verwaltungsteil der Website ist die Session in der Regel bereits durch die Anmeldung des Benutzers gestartet. -Ansonsten starten Sie die Session mit der Methode `Nette\Http\Session::start()`. +Wir empfehlen, Formulare im Administrationsbereich der Website, die sensible Daten in der Anwendung ändern, auf diese Weise zu schützen. Das Framework wehrt sich gegen CSRF-Angriffe, indem es ein Autorisierungs-Token generiert und überprüft, das in der Session gespeichert wird. Daher muss vor dem Anzeigen des Formulars eine Session geöffnet sein. Im Administrationsbereich der Website ist die Session normalerweise bereits aufgrund der Benutzeranmeldung gestartet. Andernfalls starten Sie die Session mit der Methode `Nette\Http\Session::start()`. -Ein Formular in mehreren Presentern verwenden .[#toc-using-one-form-in-multiple-presenters] -=========================================================================================== +Gleiches Formular in mehreren Presentern +======================================== -Wenn Sie ein Formular in mehreren Presentern verwenden müssen, empfehlen wir Ihnen, eine Factory dafür zu erstellen, die Sie dann an den Presenter weitergeben. Ein geeigneter Ort für eine solche Klasse ist z. B. das Verzeichnis `app/Forms`. +Wenn Sie ein Formular in mehreren Presentern verwenden müssen, empfehlen wir, dafür eine Factory zu erstellen, die Sie dann an den Presenter übergeben. Ein geeigneter Speicherort für eine solche Klasse ist z. B. das Verzeichnis `app/Forms`. -Die Fabrikklasse könnte wie folgt aussehen: +Die Factory-Klasse könnte beispielsweise so aussehen: ```php use Nette\Application\UI\Form; @@ -384,7 +384,7 @@ class SignInFormFactory { $form = new Form; $form->addText('name', 'Name:'); - $form->addSubmit('send', 'Log in'); + $form->addSubmit('send', 'Anmelden'); return $form; } } @@ -401,14 +401,14 @@ public function __construct( protected function createComponentSignInForm(): Form { $form = $this->formFactory->create(); - // können wir das Formular ändern, hier zum Beispiel ändern wir die Beschriftung der Schaltfläche - $form['login']->setCaption('Continue'); - $form->onSuccess[] = [$this, 'signInFormSubmitted']; // und fügen den Handler + // wir können das Formular ändern, hier ändern wir beispielsweise die Beschriftung auf der Schaltfläche + $form['send']->setCaption('Weiter'); + $form->onSuccess[] = [$this, 'signInFormSuceeded']; // und fügen einen Handler hinzu return $form; } ``` -Der Handler für die Formularverarbeitung kann auch von der Fabrik geliefert werden: +Der Handler zur Verarbeitung des Formulars kann auch bereits von der Factory geliefert werden: ```php use Nette\Application\UI\Form; @@ -421,11 +421,11 @@ class SignInFormFactory $form->addText('name', 'Name:'); $form->addSubmit('send', 'Anmelden'); $form->onSuccess[] = function (Form $form, $data): void { - // wir bearbeiten unser eingereichtes Formular hier + // hier führen wir die Verarbeitung des Formulars durch }; return $form; } } ``` -So, das war eine kurze Einführung in Formulare in Nette. Schauen Sie im Verzeichnis [examples |https://github.com/nette/forms/tree/master/examples] in der Distribution nach, um weitere Anregungen zu erhalten. +So, wir haben eine schnelle Einführung in Formulare in Nette hinter uns. Versuchen Sie, sich noch im Verzeichnis [examples|https://github.com/nette/forms/tree/master/examples] in der Distribution umzusehen, wo Sie weitere Inspiration finden. diff --git a/forms/de/rendering.texy b/forms/de/rendering.texy index ca24a4ae2f..00528b657d 100644 --- a/forms/de/rendering.texy +++ b/forms/de/rendering.texy @@ -1,33 +1,35 @@ -Formulare Rendering -******************* +Rendern von Formularen +********************** -Das Erscheinungsbild von Formularen kann sehr unterschiedlich sein. In der Praxis können wir auf zwei Extreme stoßen. Einerseits besteht die Notwendigkeit, eine Reihe von Formularen in einer Anwendung darzustellen, die sich optisch ähneln, und wir schätzen die einfache Darstellung ohne Vorlage mit `$form->render()`. Dies ist normalerweise bei Verwaltungsschnittstellen der Fall. +Das Aussehen von Formularen kann sehr vielfältig sein. In der Praxis können wir auf zwei Extreme stoßen. Auf der einen Seite steht die Notwendigkeit, in der Anwendung eine Reihe von Formularen zu rendern, die sich visuell wie ein Ei dem anderen gleichen, und wir schätzen das einfache Rendern ohne Vorlage mit `$form->render()`. Dies ist normalerweise bei Verwaltungsoberflächen der Fall. -Andererseits gibt es verschiedene Formulare, von denen jedes einzelne einzigartig ist. Ihr Aussehen wird am besten durch die HTML-Sprache in der Vorlage beschrieben. Und natürlich werden wir neben den beiden genannten Extremen auch viele Formulare finden, die irgendwo dazwischen liegen. +Auf der anderen Seite gibt es vielfältige Formulare, bei denen gilt: jedes ein Unikat ist. Ihre Form beschreiben wir am besten mit der HTML-Sprache in der Formularvorlage. Und natürlich stoßen wir neben den beiden genannten Extremen auf viele Formulare, die sich irgendwo dazwischen bewegen. -Rendering mit Latte .[#toc-rendering-with-latte] -================================================ +Rendern mit Latte +================= -Das [Latte-Templating-System |latte:] erleichtert das Rendern von Formularen und ihren Elementen grundlegend. Wir werden zunächst zeigen, wie man Formulare manuell, Element für Element, rendern kann, um die volle Kontrolle über den Code zu erhalten. Später werden wir zeigen, wie man dieses Rendering [automatisieren |#Automatic rendering] kann. +Das [Latte Template-System |latte:] erleichtert das Rendern von Formularen und ihren Elementen erheblich. Zuerst zeigen wir, wie man Formulare manuell Element für Element rendert und so die volle Kontrolle über den Code erhält. Später zeigen wir, wie man ein solches Rendering [automatisieren |#Automatisches Rendering] kann. + +Den Entwurf der Latte-Vorlage für ein Formular können Sie sich mit der Methode `Nette\Forms\Blueprint::latte($form)` generieren lassen, die ihn auf der Browserseite ausgibt. Den Code können Sie dann einfach per Klick markieren und in Ihr Projekt kopieren. .{data-version:3.1.15} `{control}` ----------- -Der einfachste Weg, ein Formular zu rendern, ist, es in eine Vorlage zu schreiben: +Der einfachste Weg, ein Formular zu rendern, ist, im Template zu schreiben: ```latte {control signInForm} ``` -Das Aussehen des gerenderten Formulars kann durch die Konfiguration des [Renderers |#Renderer] und [einzelner Steuerelemente |#HTML Attributes] verändert werden. +Das Aussehen des so gerenderten Formulars kann durch Konfiguration des [Renderers |#Renderer] und der [einzelnen Elemente |#HTML-Attribute] beeinflusst werden. `n:name` -------- -Es ist sehr einfach, die Formulardefinition in PHP-Code mit HTML-Code zu verknüpfen. Fügen Sie einfach die `n:name` Attribute hinzu. So einfach ist das! +Die Definition des Formulars im PHP-Code lässt sich extrem einfach mit dem HTML-Code verknüpfen. Es genügt, die Attribute `n:name` hinzuzufügen. So einfach ist das! ```php protected function createComponentSignInForm(): Form @@ -46,7 +48,7 @@ protected function createComponentSignInForm(): Form <label n:name=username>Username: <input n:name=username size=20 autofocus></label> </div> <div> - <label n:name=password>Password: <input n:name=password></label> + <label n:name=password>Passwort: <input n:name=password></label> </div> <div> <input n:name=send class="btn btn-default"> @@ -54,10 +56,9 @@ protected function createComponentSignInForm(): Form </form> ``` -Das Aussehen des resultierenden HTML-Codes liegt ganz in Ihrer Hand. Wenn Sie das Attribut `n:name` mit `<select>`, `<button>` oder `<textarea>` Elementen verwenden, wird deren interner Inhalt automatisch ausgefüllt. -Darüber hinaus erzeugt der `<form n:name>` Tag eine lokale Variable `$form` mit dem gezeichneten Formularobjekt und das schließende `</form>` zeichnet alle nicht gezeichneten versteckten Elemente (das gleiche gilt für `{form} ... {/form}`). +Die Form des resultierenden HTML-Codes liegt vollständig in Ihren Händen. Wenn Sie das Attribut `n:name` bei den Elementen `<select>`, `<button>` oder `<textarea>` verwenden, wird ihr innerer Inhalt automatisch ergänzt. Das Tag `<form n:name>` erstellt außerdem eine lokale Variable `$form` mit dem Objekt des gerenderten Formulars, und das schließende `</form>` rendert alle nicht gerenderten versteckten Elemente (dasselbe gilt auch für `{form} ... {/form}`). -Wir dürfen jedoch nicht vergessen, mögliche Fehlermeldungen zu rendern. Sowohl solche, die durch die Methode `addError()` zu einzelnen Elementen hinzugefügt wurden (mit `{inputError}`), als auch solche, die direkt zum Formular hinzugefügt wurden (zurückgegeben von `$form->getOwnErrors()`): +Wir dürfen jedoch nicht vergessen, mögliche Fehlermeldungen zu rendern. Sowohl diejenigen, die mit der Methode `addError()` zu einzelnen Elementen hinzugefügt wurden (mithilfe von `{inputError}`), als auch diejenigen, die direkt zum Formular hinzugefügt wurden (von `$form->getOwnErrors()` zurückgegeben): ```latte <form n:name=signInForm class=form> @@ -70,7 +71,7 @@ Wir dürfen jedoch nicht vergessen, mögliche Fehlermeldungen zu rendern. Sowohl <span class=error n:ifcontent>{inputError username}</span> </div> <div> - <label n:name=password>Password: <input n:name=password></label> + <label n:name=password>Passwort: <input n:name=password></label> <span class=error n:ifcontent>{inputError password}</span> </div> <div> @@ -79,7 +80,7 @@ Wir dürfen jedoch nicht vergessen, mögliche Fehlermeldungen zu rendern. Sowohl </form> ``` -Komplexere Formularelemente, wie z. B. RadioList oder CheckboxList, können Element für Element gerendert werden: +Komplexere Formularelemente wie RadioList oder CheckboxList können so Element für Element gerendert werden: ```latte {foreach $form[gender]->getItems() as $key => $label} @@ -88,16 +89,10 @@ Komplexere Formularelemente, wie z. B. RadioList oder CheckboxList, können Elem ``` -Code-Vorschlag `{formPrint}` .[#toc-formprint] ----------------------------------------------- - -Sie können einen ähnlichen Latte-Code für ein Formular mit dem Tag `{formPrint}` erzeugen. Wenn Sie ihn in eine Vorlage einfügen, sehen Sie den Code-Entwurf anstelle des normalen Renderings. Wählen Sie ihn dann einfach aus und kopieren Sie ihn in Ihr Projekt. - - `{label}` `{input}` ------------------- -Wollen Sie nicht für jedes Element überlegen, welches HTML-Element Sie dafür in der Vorlage verwenden wollen, ob `<input>`, `<textarea>` usw.? Die Lösung ist der universelle `{input}` Tag: +Möchten Sie nicht bei jedem Element überlegen, welches HTML-Element Sie im Template verwenden sollen, ob `<input>`, `<textarea>` usw.? Die Lösung ist das universelle Tag `{input}`: ```latte <form n:name=signInForm class=form> @@ -110,7 +105,7 @@ Wollen Sie nicht für jedes Element überlegen, welches HTML-Element Sie dafür {inputError username} </div> <div> - {label password}Password: {input password}{/label} + {label password}Passwort: {input password}{/label} {inputError password} </div> <div> @@ -119,9 +114,9 @@ Wollen Sie nicht für jedes Element überlegen, welches HTML-Element Sie dafür </form> ``` -Wenn das Formular einen Übersetzer verwendet, wird der Text innerhalb der `{label}` Tags übersetzt. +Wenn das Formular einen Translator verwendet, wird der Text innerhalb der `{label}`-Tags übersetzt. -Auch hier gilt, dass komplexere Formularelemente wie RadioList oder CheckboxList Element für Element gerendert werden können: +Auch in diesem Fall können komplexere Formularelemente wie RadioList oder CheckboxList Element für Element gerendert werden: ```latte {foreach $form[gender]->items as $key => $label} @@ -129,20 +124,19 @@ Auch hier gilt, dass komplexere Formularelemente wie RadioList oder CheckboxList {/foreach} ``` -Zum Rendern des `<input>` selbst im Element Checkbox zu rendern, verwenden Sie `{input myCheckbox:}`. HTML-Attribute müssen durch ein Komma getrennt werden `{input myCheckbox:, class: required}`. +Zum Rendern des reinen `<input>` in einem Checkbox-Element verwenden Sie `{input myCheckbox:}`. HTML-Attribute trennen Sie in diesem Fall immer mit einem Komma `{input myCheckbox:, class: required}`. `{inputError}` -------------- -Gibt eine Fehlermeldung für das Formularelement aus, wenn es eine hat. Die Meldung wird normalerweise in ein HTML-Element zur Gestaltung eingeschlossen. -Die Vermeidung der Darstellung eines leeren Elements, wenn es keine Meldung gibt, kann elegant mit `n:ifcontent` erfolgen: +Gibt die Fehlermeldung für ein Formularelement aus, falls eine vorhanden ist. Die Meldung wird normalerweise zur Gestaltung in ein HTML-Element verpackt. Das Rendern eines leeren Elements, wenn keine Meldung vorhanden ist, lässt sich elegant mit `n:ifcontent` verhindern: ```latte <span class=error n:ifcontent>{inputError $input}</span> ``` -Wir können das Vorhandensein eines Fehlers mit der Methode `hasErrors()` erkennen und die Klasse des übergeordneten Elements entsprechend einstellen: +Das Vorhandensein eines Fehlers können wir mit der Methode `hasErrors()` feststellen und entsprechend die Klasse des übergeordneten Elements setzen: ```latte <div n:class="$form[username]->hasErrors() ? 'error'"> @@ -155,14 +149,13 @@ Wir können das Vorhandensein eines Fehlers mit der Methode `hasErrors()` erkenn `{form}` -------- -Tags `{form signInForm}...{/form}` sind eine Alternative zu `<form n:name="signInForm">...</form>`. +Die Tags `{form signInForm}...{/form}` sind eine Alternative zu `<form n:name="signInForm">...</form>`. -Automatisches Rendering .[#toc-automatic-rendering] ---------------------------------------------------- +Automatisches Rendering +----------------------- -Mit den Tags `{input}` und `{label}` können wir auf einfache Weise eine generische Vorlage für ein beliebiges Formular erstellen. Sie durchläuft alle Elemente des Formulars und rendert sie nacheinander, mit Ausnahme der versteckten Elemente, die automatisch gerendert werden, wenn das Formular mit dem `</form>` Tag beendet wird. -Sie erwartet den Namen des gerenderten Formulars in der Variablen `$form`. +Dank der Tags `{input}` und `{label}` können wir leicht eine allgemeine Vorlage für jedes beliebige Formular erstellen. Es wird nacheinander alle seine Elemente iterieren und rendern, außer den versteckten Elementen, die beim Schließen des Formulars mit dem Tag `</form>` automatisch gerendert werden. Der Name des zu rendernden Formulars wird in der Variablen `$form` erwartet. ```latte <form n:name=$form class=form> @@ -179,16 +172,15 @@ Sie erwartet den Namen des gerenderten Formulars in der Variablen `$form`. </form> ``` -Die verwendeten selbstschließenden Paar-Tags `{label .../}` zeigen die Bezeichnungen aus der Formulardefinition im PHP-Code an. +Die verwendeten selbstschließenden paarigen Tags `{label .../}` zeigen Beschriftungen an, die aus der Formulardefinition im PHP-Code stammen. -Sie können diese generische Vorlage in der Datei `basic-form.latte` speichern und das Formular rendern, indem Sie es einbinden und den Formularnamen (oder die Instanz) an den Parameter `$form` übergeben: +Speichern Sie diese allgemeine Vorlage beispielsweise in der Datei `basic-form.latte` und zum Rendern des Formulars genügt es, sie zu inkludieren und den Namen (oder die Instanz) des Formulars an den Parameter `$form` zu übergeben: ```latte {include basic-form.latte, form: signInForm} ``` -Wenn Sie das Aussehen eines bestimmten Formulars beeinflussen und ein Element anders zeichnen möchten, ist es am einfachsten, in der Vorlage Blöcke vorzubereiten, die später überschrieben werden können. -Blöcke können auch [dynamische Namen |latte:template-inheritance#dynamic-block-names] haben, so dass Sie den Namen des zu zeichnenden Elements in sie einfügen können. Zum Beispiel: +Wenn Sie beim Rendern eines bestimmten Formulars in dessen Form eingreifen und beispielsweise ein Element anders rendern möchten, ist der einfachste Weg, im Template Blöcke vorzubereiten, die anschließend überschrieben werden können. Blöcke können auch [dynamische Namen |latte:template-inheritance#Dynamische Blocknamen] haben, sodass man auch den Namen des zu rendernden Elements einfügen kann. Zum Beispiel: ```latte ... @@ -197,7 +189,7 @@ Blöcke können auch [dynamische Namen |latte:template-inheritance#dynamic-block ... ``` -Für das Element z.B. `username` wird der Block `input-username` erstellt, der mit dem Tag [{embed} |latte:template-inheritance#unit-inheritance] einfach überschrieben werden kann: +Für ein Element z. B. `username` entsteht so der Block `input-username`, der leicht mit dem Tag [{embed} |latte:template-inheritance#Einheiten-Vererbung] überschrieben werden kann: ```latte {embed basic-form.latte, form: signInForm} @@ -209,7 +201,7 @@ Für das Element z.B. `username` wird der Block `input-username` erstellt, der m {/embed} ``` -Alternativ kann auch der gesamte Inhalt der Vorlage `basic-form.latte` als Block [definiert |latte:template-inheritance#definitions] werden, einschließlich des Parameters `$form`: +Alternativ kann der gesamte Inhalt der Vorlage `basic-form.latte` als Block [definiert |latte:template-inheritance#Definition] werden, einschließlich des Parameters `$form`: ```latte {define basic-form, $form} @@ -219,7 +211,7 @@ Alternativ kann auch der gesamte Inhalt der Vorlage `basic-form.latte` als Block {/define} ``` -Dadurch wird die Verwendung etwas einfacher: +Dadurch wird sein Aufruf etwas einfacher: ```latte {embed basic-form, signInForm} @@ -227,31 +219,31 @@ Dadurch wird die Verwendung etwas einfacher: {/embed} ``` -Sie brauchen den Block nur noch an einer Stelle zu importieren, nämlich am Anfang der Layout-Vorlage: +Den Block genügt es dabei an einer einzigen Stelle zu importieren, und zwar am Anfang der Layout-Vorlage: ```latte {import basic-form.latte} ``` -Besondere Fälle .[#toc-special-cases] -------------------------------------- +Spezialfälle +------------ -Wenn Sie nur den inneren Inhalt eines Formulars darstellen müssen, ohne `<form>` & `</form>` HTML-Tags, z. B. in einer AJAX-Anfrage, können Sie das Formular mit `{formContext} … {/formContext}` öffnen und schließen. Es funktioniert im logischen Sinne ähnlich wie `{form}`, hier erlaubt es Ihnen, andere Tags zu verwenden, um Formularelemente zu zeichnen, aber gleichzeitig zeichnet es nichts. +Wenn Sie nur den inneren Teil des Formulars ohne die HTML-Tags `<form>` rendern müssen, beispielsweise beim Senden von Snippets, verbergen Sie sie mit dem Attribut `n:tag-if`: ```latte -{formContext signForm} +<form n:name=signInForm n:tag-if=false> <div> <label n:name=username>Username: <input n:name=username></label> {inputError username} </div> -{/formContext} +</form> ``` -Das Tag `formContainer` hilft beim Rendern von Eingaben innerhalb eines Formular-Containers. +Beim Rendern von Elementen innerhalb eines Formularcontainers hilft das Tag `{formContainer}`. ```latte -<p>Which news you wish to receive:</p> +<p>Welche Nachrichten möchten Sie erhalten:</p> {formContainer emailNews} <ul> @@ -262,8 +254,8 @@ Das Tag `formContainer` hilft beim Rendern von Eingaben innerhalb eines Formular ``` -Rendering ohne Latte .[#toc-rendering-without-latte] -==================================================== +Rendern ohne Latte +================== Der einfachste Weg, ein Formular zu rendern, ist der Aufruf: @@ -271,18 +263,18 @@ Der einfachste Weg, ein Formular zu rendern, ist der Aufruf: $form->render(); ``` -Das Aussehen des gerenderten Formulars kann durch Konfiguration des [Renderers |#Renderer] und [einzelner Steuerelemente |#HTML Attributes] geändert werden. +Das Aussehen des so gerenderten Formulars kann durch Konfiguration des [Renderers |#Renderer] und der [einzelnen Elemente |#HTML-Attribute] beeinflusst werden. -Manuelles Rendering .[#toc-manual-rendering] --------------------------------------------- +Manuelles Rendern +----------------- -Jedes Formularelement hat Methoden, die den HTML-Code für das Formularfeld und die Beschriftung erzeugen. Sie können ihn entweder als String oder als [Nette\Utils\Html-Objekt |utils:html-elements] zurückgeben: +Jedes Formularelement verfügt über Methoden, die den HTML-Code des Formularfeldes und der Beschriftung generieren. Sie können ihn entweder als Zeichenkette oder als [Nette\Utils\Html |utils:html-elements]-Objekt zurückgeben: - `getControl(): Html|string` gibt den HTML-Code des Elements zurück - `getLabel($caption = null): Html|string|null` gibt den HTML-Code der Beschriftung zurück, falls vorhanden -So kann das Formular Element für Element gerendert werden: +Das Formular kann so Element für Element gerendert werden: ```php <?php $form->render('begin') ?> @@ -305,22 +297,21 @@ So kann das Formular Element für Element gerendert werden: <?php $form->render('end') ?> ``` -Während für einige Elemente `getControl()` ein einzelnes HTML-Element zurückgibt (z. B. `<input>`, `<select>` usw.), gibt es für andere ein ganzes Stück HTML-Code zurück (CheckboxList, RadioList). -In diesem Fall können Sie Methoden verwenden, die einzelne Eingaben und Beschriftungen für jedes Element separat erzeugen: +Während bei einigen Elementen `getControl()` ein einzelnes HTML-Element zurückgibt (z. B. `<input>`, `<select>` usw.), gibt es bei anderen ein ganzes Stück HTML-Code zurück (CheckboxList, RadioList). In diesem Fall können Sie Methoden verwenden, die einzelne Eingaben und Beschriftungen für jedes Element separat generieren: - `getControlPart($key = null): ?Html` gibt den HTML-Code eines einzelnen Elements zurück -- `getLabelPart($key = null): ?Html` gibt den HTML-Code für die Beschriftung eines einzelnen Eintrags zurück +- `getLabelPart($key = null): ?Html` gibt den HTML-Code der Beschriftung eines einzelnen Elements zurück .[note] -Aus historischen Gründen wird diesen Methoden das Präfix `get` vorangestellt, aber `generate` wäre besser, da es bei jedem Aufruf ein neues Element `Html` erzeugt und zurückgibt. +Diese Methoden haben aus historischen Gründen das Präfix `get`, aber `generate` wäre besser, da bei jedem Aufruf ein neues `Html`-Element erstellt und zurückgegeben wird. -Renderer .[#toc-renderer] -========================= +Renderer +======== -Es handelt sich um ein Objekt, das das Rendering des Formulars ermöglicht. Es kann durch die Methode `$form->setRenderer` gesetzt werden. Es wird beim Aufruf der Methode `$form->render()` an die Kontrolle übergeben. +Dies ist ein Objekt, das das Rendern des Formulars sicherstellt. Es kann mit der Methode `$form->setRenderer` festgelegt werden. Ihm wird die Kontrolle übergeben, wenn die Methode `$form->render()` aufgerufen wird. -Wenn wir keinen benutzerdefinierten Renderer festlegen, wird der Standard-Renderer [api:Nette\Forms\Rendering\DefaultFormRenderer] verwendet. Dieser rendert die Formularelemente als HTML-Tabelle. Die Ausgabe sieht wie folgt aus: +Wenn wir keinen eigenen Renderer festlegen, wird der Standard-Renderer [api:Nette\Forms\Rendering\DefaultFormRenderer] verwendet. Dieser rendert die Formularelemente in Form einer HTML-Tabelle. Die Ausgabe sieht so aus: ```latte <table> @@ -331,21 +322,21 @@ Wenn wir keinen benutzerdefinierten Renderer festlegen, wird der Standard-Render </tr> <tr class="required"> - <th><label class="required" for="frm-age">Age:</label></th> + <th><label class="required" for="frm-age">Alter:</label></th> <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> </tr> <tr> - <th><label>Gender:</label></th> + <th><label>Geschlecht:</label></th> ... ``` -Es bleibt Ihnen überlassen, ob Sie eine Tabelle verwenden oder nicht, und viele Webdesigner bevorzugen andere Auszeichnungen, zum Beispiel eine Liste. Wir können `DefaultFormRenderer` so konfigurieren, dass es überhaupt nicht in eine Tabelle gerendert wird. Wir müssen nur die richtigen [$wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers] setzen. Der erste Index steht immer für einen Bereich und der zweite für ein Element. Alle entsprechenden Bereiche sind im Bild zu sehen: +Ob man für das Formulargerüst eine Tabelle verwenden soll oder nicht, ist umstritten, und viele Webdesigner bevorzugen ein anderes Markup. Zum Beispiel eine Definitionsliste. Wir konfigurieren daher den `DefaultFormRenderer` so um, dass er das Formular als Liste rendert. Die Konfiguration erfolgt durch Bearbeitung des Feldes [$wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers]. Der erste Index stellt immer den Bereich dar und der zweite sein Attribut. Die einzelnen Bereiche zeigt das Bild: -[* form-areas-en.webp *] +[* defaultformrenderer.webp *] -Standardmäßig wird eine Gruppe von `controls` in `<table>`eingeschlossen, und jede `pair` ist eine Tabellenzeile `<tr>` mit einem Paar von `label` und `control` (Zellen `<th>` und `<td>`). Lassen Sie uns all diese Umhüllungselemente ändern. Wir packen `controls` in `<dl>`ein, belassen `pair` für sich, fügen `label` in `<dt>` und verpacken `control` in `<dd>`: +Standardmäßig ist die Gruppe der Elemente `controls` von einer Tabelle `<table>` umschlossen, jedes `pair` stellt eine Tabellenzeile `<tr>` dar, und die Paare `label` und `control` sind Zellen `<th>` und `<td>`. Nun ändern wir die umschließenden Elemente. Den Bereich `controls` legen wir in einen Container `<dl>`, den Bereich `pair` lassen wir ohne Container, `label` legen wir in `<dt>` und schließlich `control` umschließen wir mit `<dd>`-Tags: ```php $renderer = $form->getRenderer(); @@ -357,7 +348,7 @@ $renderer->wrappers['control']['container'] = 'dd'; $form->render(); ``` -Das Ergebnis ist der folgende Ausschnitt: +Das Ergebnis ist dieser HTML-Code: ```latte <dl> @@ -366,184 +357,183 @@ Das Ergebnis ist der folgende Ausschnitt: <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> - <dt><label class="required" for="frm-age">Age:</label></dt> + <dt><label class="required" for="frm-age">Alter:</label></dt> <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> - <dt><label>Gender:</label></dt> + <dt><label>Geschlecht:</label></dt> ... </dl> ``` -Wrapper können viele Attribute beeinflussen. Zum Beispiel: +Im Wrappers-Array können viele weitere Attribute beeinflusst werden: -- spezielle CSS-Klassen zu jeder Formulareingabe hinzufügen -- Unterscheidung zwischen ungeraden und geraden Zeilen -- Erforderliche und optionale Zeichen unterschiedlich gestalten -- einstellen, ob Fehlermeldungen oberhalb des Formulars oder in der Nähe der einzelnen Elemente angezeigt werden +- CSS-Klassen zu einzelnen Typen von Formularelementen hinzufügen +- ungerade und gerade Zeilen durch CSS-Klassen unterscheiden +- Pflichtfelder und optionale Felder visuell unterscheiden +- bestimmen, ob Fehlermeldungen direkt bei den Elementen oder über dem Formular angezeigt werden -Optionen .[#toc-options] ------------------------- +Options +------- -Das Verhalten von Renderer kann auch durch das Setzen von *Optionen* für einzelne Formularelemente gesteuert werden. Auf diese Weise können Sie den Tooltip einstellen, der neben dem Eingabefeld angezeigt wird: +Das Verhalten des Renderers kann auch durch das Setzen von *options* auf einzelnen Formularelementen gesteuert werden. So kann eine Beschreibung festgelegt werden, die neben dem Eingabefeld ausgegeben wird: ```php -$form->addText('phone', 'Number:') - ->setOption('description', 'This number will remain hidden'); +$form->addText('phone', 'Nummer:') + ->setOption('description', 'Diese Nummer bleibt verborgen'); ``` -Wenn wir einen HTML-Inhalt darin platzieren wollen, verwenden wir die Klasse [Html |utils:html-elements]. +Wenn wir darin HTML-Inhalt platzieren möchten, verwenden wir die Klasse [Html |utils:html-elements] ```php use Nette\Utils\Html; -$form->addText('phone', 'Phone:') +$form->addText('phone', 'Nummer:') ->setOption('description', Html::el('p') - ->setHtml('<a href="...">Terms of service.</a>') + ->setHtml('<a href="...">Bedingungen zur Speicherung Ihrer Nummer</a>') ); ``` .[tip] -Das Html-Element kann auch anstelle des Labels verwendet werden: `$form->addCheckbox('conditions', $label)`. +Ein Html-Element kann auch anstelle eines Labels verwendet werden: `$form->addCheckbox('conditions', $label)`. -Eingaben gruppieren .[#toc-grouping-inputs] -------------------------------------------- +Gruppierung von Elementen +------------------------- -Renderer ermöglicht die Gruppierung von Elementen in visuelle Gruppen (Fieldsets): +Der Renderer ermöglicht das Gruppieren von Elementen in visuelle Gruppen (Fieldsets): ```php -$form->addGroup('Personal data'); +$form->addGroup('Persönliche Daten'); ``` -Durch das Erstellen einer neuen Gruppe wird diese aktiviert - alle weiteren Elemente werden dieser Gruppe hinzugefügt. Sie können ein Formular wie folgt erstellen: +Nachdem eine neue Gruppe erstellt wurde, wird diese aktiv, und jedes neu hinzugefügte Element wird gleichzeitig auch zu ihr hinzugefügt. Das Formular kann also auf diese Weise aufgebaut werden: ```php $form = new Form; -$form->addGroup('Personal data'); -$form->addText('name', 'Your name:'); -$form->addInteger('age', 'Your age:'); -$form->addEmail('email', 'Email:'); +$form->addGroup('Persönliche Daten'); +$form->addText('name', 'Ihr Name:'); +$form->addInteger('age', 'Ihr Alter:'); +$form->addEmail('email', 'E-Mail:'); -$form->addGroup('Shipping address'); -$form->addCheckbox('send', 'Ship to address'); -$form->addText('street', 'Street:'); -$form->addText('city', 'City:'); -$form->addSelect('country', 'Country:', $countries); +$form->addGroup('Lieferadresse'); +$form->addCheckbox('send', 'An Adresse liefern'); +$form->addText('street', 'Straße:'); +$form->addText('city', 'Stadt:'); +$form->addSelect('country', 'Land:', $countries); ``` +Der Renderer rendert zuerst die Gruppen und erst danach die Elemente, die zu keiner Gruppe gehören. -Bootstrap-Unterstützung .[#toc-bootstrap-support] -------------------------------------------------- -Sie können [Beispiele |https://github.com/nette/forms/tree/master/examples] für die Konfiguration des Renderers für [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], [Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] und [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php] finden +Unterstützung für Bootstrap +--------------------------- +[In den Beispielen |https://github.com/nette/forms/tree/master/examples] finden Sie Beispiele, wie Sie den Renderer für [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], [Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] und [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php] konfigurieren. -HTML-Attribute .[#toc-html-attributes] -====================================== -Mit `setHtmlAttribute(string $name, $value = true)` können Sie beliebige HTML-Attribute für Formularsteuerelemente festlegen: +HTML-Attribute +============== + +Um beliebige HTML-Attribute für Formularelemente festzulegen, verwenden wir die Methode `setHtmlAttribute(string $name, $value = true)`: ```php -$form->addInteger('number', 'Zahl:') - ->setHtmlAttribute('class', 'Große Zahl'); +$form->addInteger('number', 'Nummer:') + ->setHtmlAttribute('class', 'big-number'); $form->addSelect('rank', 'Sortieren nach:', ['Preis', 'Name']) - ->setHtmlAttribute('onchange', 'submit()'); // ruft JS-Funktion submit() bei Änderung auf + ->setHtmlAttribute('onchange', 'submit()'); // bei Änderung senden -// Anwendung auf <form> +// Um Attribute des <form>-Elements selbst festzulegen $form->setHtmlAttribute('id', 'myForm'); ``` -Einstellung des Eingabetyps: +Spezifikation des Elementtyps: ```php -$form->addText('tel', 'Your telephone:') +$form->addText('tel', 'Ihr Telefon:') ->setHtmlType('tel') - ->setHtmlAttribute('placeholder', 'Please, fill in your telephone'); + ->setHtmlAttribute('placeholder', 'Telefonnummer eingeben'); ``` -Wir können HTML-Attribute für einzelne Elemente in Radio- oder Checkbox-Listen mit unterschiedlichen Werten für jedes dieser Elemente festlegen. -Beachten Sie den Doppelpunkt nach `style:`, um sicherzustellen, dass der Wert nach Schlüssel ausgewählt wird: +.[warning] +Die Einstellung des Typs und anderer Attribute dient nur zu visuellen Zwecken. Die Überprüfung der Richtigkeit der Eingaben muss serverseitig erfolgen, was durch die Wahl des geeigneten [Formularelements |controls] und die Angabe von [Validierungsregeln |validation] sichergestellt wird. + +Einzelnen Elementen in Radio- oder Checkbox-Listen können wir HTML-Attribute mit unterschiedlichen Werten für jedes von ihnen zuweisen. Beachten Sie den Doppelpunkt nach `style:`, der die Auswahl des Wertes nach Schlüssel sicherstellt: ```php -$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue']; +$colors = ['r' => 'rot', 'g' => 'grün', 'b' => 'blau']; $styles = ['r' => 'background:red', 'g' => 'background:green']; -$form->addCheckboxList('colors', 'Colors:', $colors) +$form->addCheckboxList('colors', 'Farben:', $colors) ->setHtmlAttribute('style:', $styles); ``` -Renders: +Gibt aus: ```latte -<label><input type="checkbox" name="colors[]" style="background:red" value="r">red</label> -<label><input type="checkbox" name="colors[]" style="background:green" value="g">green</label> -<label><input type="checkbox" name="colors[]" value="b">blue</label> +<label><input type="checkbox" name="colors[]" style="background:red" value="r">rot</label> +<label><input type="checkbox" name="colors[]" style="background:green" value="g">grün</label> +<label><input type="checkbox" name="colors[]" value="b">blau</label> ``` -Für ein logisches HTML-Attribut (das keinen Wert hat, wie `readonly`) können Sie ein Fragezeichen verwenden: +Um logische Attribute wie `readonly` festzulegen, können wir eine Schreibweise mit Fragezeichen verwenden: ```php -$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue']; -$form->addCheckboxList('colors', 'Colors:', $colors) - ->setHtmlAttribute('readonly?', 'r'); // use array for multiple keys, e.g. ['r', 'g'] +$form->addCheckboxList('colors', 'Farben:', $colors) + ->setHtmlAttribute('readonly?', 'r'); // für mehrere Schlüssel verwenden Sie ein Array, z. B. ['r', 'g'] ``` -Renders: +Gibt aus: ```latte -<label><input type="checkbox" name="colors[]" readonly value="r">red</label> -<label><input type="checkbox" name="colors[]" value="g">green</label> -<label><input type="checkbox" name="colors[]" value="b">blue</label> +<label><input type="checkbox" name="colors[]" readonly value="r">rot</label> +<label><input type="checkbox" name="colors[]" value="g">grün</label> +<label><input type="checkbox" name="colors[]" value="b">blau</label> ``` -Bei Selectboxen setzt die Methode `setHtmlAttribute()` die Attribute des `<select>` Elements. Wenn wir die Attribute für jedes der folgenden Elemente setzen wollen -`<option>`setzen wollen, verwenden wir die Methode `setOptionAttribute()`. Auch der Doppelpunkt und das Fragezeichen, die oben verwendet wurden, funktionieren: +Bei Selectboxen setzt die Methode `setHtmlAttribute()` Attribute des `<select>`-Elements. Wenn wir Attribute für einzelne `<option>` setzen möchten, verwenden wir die Methode `setOptionAttribute()`. Auch Schreibweisen mit Doppelpunkt und Fragezeichen, wie oben erwähnt, funktionieren: ```php -$form->addSelect('colors', 'Colors:', $colors) +$form->addSelect('colors', 'Farben:', $colors) ->setOptionAttribute('style:', $styles); ``` -Rendert: +Gibt aus: ```latte <select name="colors"> - <option value="r" style="background:red">red</option> - <option value="g" style="background:green">green</option> - <option value="b">blue</option> + <option value="r" style="background:red">rot</option> + <option value="g" style="background:green">grün</option> + <option value="b">blau</option> </select> ``` -Prototypen .[#toc-prototypes] ------------------------------ +Prototypen +---------- -Eine alternative Möglichkeit, HTML-Attribute zu setzen, besteht darin, die Vorlage zu ändern, aus der das HTML-Element erzeugt wird. Das Template ist ein `Html` Objekt und wird von der Methode `getControlPrototype()` zurückgegeben: +Eine alternative Methode zum Festlegen von HTML-Attributen besteht darin, die Vorlage zu ändern, aus der das HTML-Element generiert wird. Die Vorlage ist ein `Html`-Objekt und wird von der Methode `getControlPrototype()` zurückgegeben: ```php -$input = $form->addInteger('number'); +$input = $form->addInteger('number', 'Nummer:'); $html = $input->getControlPrototype(); // <input> $html->class('big-number'); // <input class="big-number"> ``` -Die von `getLabelPrototype()` zurückgegebene Etikettenvorlage kann ebenfalls auf diese Weise geändert werden: +Auf diese Weise kann auch die Vorlage der Beschriftung geändert werden, die von `getLabelPrototype()` zurückgegeben wird: ```php $html = $input->getLabelPrototype(); // <label> -$html->class('unverwechselbar'); // <label class="unverwechselbar"> +$html->class('distinctive'); // <label class="distinctive"> ``` -Für Checkbox-, CheckboxList- und RadioList-Elemente können Sie die Elementvorlage beeinflussen, die das Element umhüllt. Sie wird von `getContainerPrototype()` zurückgegeben. Standardmäßig ist es ein "leeres" Element, so dass nichts gerendert wird, aber wenn Sie ihm einen Namen geben, wird es gerendert: +Bei den Elementen Checkbox, CheckboxList und RadioList können Sie die Vorlage des Elements beeinflussen, das das gesamte Element umschließt. Sie wird von `getContainerPrototype()` zurückgegeben. Standardmäßig ist dies ein „leeres“ Element, sodass nichts gerendert wird, aber indem wir ihm einen Namen geben, wird es gerendert: ```php $input = $form->addCheckbox('send'); -echo $input->getControl(); -// <label><input type="checkbox" name="send"></label> - $html = $input->getContainerPrototype(); $html->setName('div'); // <div> $html->class('check'); // <div class="check"> @@ -551,50 +541,49 @@ echo $input->getControl(); // <div class="check"><label><input type="checkbox" name="send"></label></div> ``` -Im Falle von CheckboxList und RadioList ist es auch möglich, das von der Methode `getSeparatorPrototype()` zurückgegebene Elementtrennmuster zu beeinflussen. Standardmäßig ist es ein Element `<br>`. Wenn Sie es in ein Paarelement ändern, umhüllt es die einzelnen Elemente, anstatt sie zu trennen. -Es ist auch möglich, die HTML-Elementvorlage der Elementbeschriftungen zu beeinflussen, die `getItemLabelPrototype()` zurückgibt. +Im Fall von CheckboxList und RadioList kann auch die Vorlage des Trennzeichens der einzelnen Elemente beeinflusst werden, das von der Methode `getSeparatorPrototype()` zurückgegeben wird. Standardmäßig ist dies das Element `<br>`. Wenn Sie es in ein paarweises Element ändern, wird es die einzelnen Elemente umschließen anstatt sie zu trennen. Weiterhin kann die Vorlage des HTML-Elements der Beschriftung bei den einzelnen Elementen beeinflusst werden, das von `getItemLabelPrototype()` zurückgegeben wird. -Übersetzen .[#toc-translating] -============================== +Übersetzung +=========== -Wenn Sie eine mehrsprachige Anwendung programmieren, müssen Sie das Formular wahrscheinlich in verschiedenen Sprachen darstellen. Das Nette Framework definiert zu diesem Zweck eine Übersetzungsschnittstelle [api:Nette\Localization\Translator]. Es gibt keine Standardimplementierung in Nette, Sie können je nach Bedarf aus mehreren fertigen Lösungen wählen, die Sie auf [Componette |https://componette.org/search/localization] finden. Die Dokumentation beschreibt, wie Sie den Übersetzer konfigurieren können. +Wenn Sie eine mehrsprachige Anwendung programmieren, müssen Sie das Formular wahrscheinlich in verschiedenen Sprachversionen rendern. Das Nette Framework definiert zu diesem Zweck eine Schnittstelle für die Übersetzung [api:Nette\Localization\Translator]. In Nette gibt es keine Standardimplementierung, Sie können je nach Bedarf aus mehreren fertigen Lösungen wählen, die Sie auf [Componette |https://componette.org/search/localization] finden. In deren Dokumentation erfahren Sie, wie Sie den Translator konfigurieren. -Das Formular unterstützt die Ausgabe von Text durch den Übersetzer. Wir übergeben ihn mit der Methode `setTranslator()`: +Formulare unterstützen die Ausgabe von Texten über den Translator. Wir übergeben ihn ihnen mit der Methode `setTranslator()`: ```php $form->setTranslator($translator); ``` -Von nun an werden nicht nur alle Beschriftungen, sondern auch alle Fehlermeldungen oder Selectbox-Einträge in eine andere Sprache übersetzt. +Ab diesem Zeitpunkt werden nicht nur alle Beschriftungen, sondern auch alle Fehlermeldungen oder Elemente von Select-Boxen in eine andere Sprache übersetzt. -Es ist möglich, für einzelne Formularelemente einen anderen Übersetzer einzustellen oder die Übersetzung mit `null` komplett zu deaktivieren: +Bei einzelnen Formularelementen ist es dabei möglich, einen anderen Übersetzer einzustellen oder die Übersetzung mit dem Wert `null` vollständig zu deaktivieren: ```php -$form->addSelect('carModel', 'Model:', $cars) +$form->addSelect('carModel', 'Modell:', $cars) ->setTranslator(null); ``` -Für [Validierungsregeln |validation] werden auch spezifische Parameter an den Übersetzer übergeben, zum Beispiel für die Regel: +Bei [Validierungsregeln |validation] werden dem Translator auch spezifische Parameter übergeben, beispielsweise bei der Regel: ```php -$form->addPassword('password', 'Password:') - ->addRule($form::MinLength, 'Password has to be at least %d characters long', 8) +$form->addPassword('password', 'Passwort:') + ->addRule($form::MinLength, 'Das Passwort muss mindestens %d Zeichen lang sein', 8); ``` -Der Übersetzer wird mit den folgenden Parametern aufgerufen: +wird der Translator mit diesen Parametern aufgerufen: ```php -$translator->translate('Password has to be at least %d characters long', 8); +$translator->translate('Das Passwort muss mindestens %d Zeichen lang sein', 8); ``` -und kann so die korrekte Pluralform für das Wort `characters` nach Anzahl wählen. +und kann somit die richtige Pluralform des Wortes `Zeichen` entsprechend der Anzahl wählen. -Ereignis onRender .[#toc-event-onrender] -======================================== +Ereignis onRender +================= -Kurz bevor das Formular gerendert wird, können wir unseren Code aufrufen lassen. Dieser kann zum Beispiel HTML-Klassen zu den Formularelementen hinzufügen, damit sie richtig angezeigt werden. Wir fügen den Code in das Array `onRender` ein: +Kurz bevor das Formular gerendert wird, können wir unseren Code aufrufen lassen. Dieser kann beispielsweise den Formularelementen HTML-Klassen für die korrekte Anzeige hinzufügen. Den Code fügen wir zum Array `onRender` hinzu: ```php $form->onRender[] = function ($form) { diff --git a/forms/de/standalone.texy b/forms/de/standalone.texy index a0574fe054..e3679d704d 100644 --- a/forms/de/standalone.texy +++ b/forms/de/standalone.texy @@ -1,76 +1,76 @@ -Eigenständig verwendete Formulare -********************************* +Formulare separat verwenden +*************************** .[perex] -Nette Forms vereinfacht die Erstellung und Verarbeitung von Webformularen erheblich. Sie können sie in Ihren Anwendungen völlig eigenständig ohne den Rest des Frameworks verwenden, was wir in diesem Kapitel demonstrieren werden. +Nette Forms erleichtert die Erstellung und Verarbeitung von Webformularen erheblich. Sie können sie in Ihren Anwendungen völlig unabhängig vom Rest des Frameworks verwenden, was wir in diesem Kapitel zeigen werden. -Wenn Sie jedoch Nette Application und Presenter verwenden, gibt es eine Anleitung für Sie: [Formulare in Presentern |in-presenter]. +Wenn Sie jedoch Nette Application und Presenter verwenden, ist die Anleitung zur [Verwendung in Presentern |in-presenter] für Sie bestimmt. -Erstes Formular .[#toc-first-form] -================================== +Erstes Formular +=============== -Wir werden versuchen, ein einfaches Registrierungsformular zu schreiben. Sein Code wird wie folgt aussehen ("vollständiger Code":https://gist.github.com/dg/370a7e3094d9ba9a9e913b8e2a2dc851): +Versuchen wir, ein einfaches Registrierungsformular zu schreiben. Sein Code wird wie folgt aussehen ("Gesamter Code":https://gist.github.com/dg/57878c1a413ae8ef0c1d83f02c43ef3f): ```php use Nette\Forms\Form; $form = new Form; $form->addText('name', 'Name:'); -$form->addPassword('password', 'Password:'); -$form->addSubmit('send', 'Sign up'); +$form->addPassword('password', 'Passwort:'); +$form->addSubmit('send', 'Registrieren'); ``` -Und nun wollen wir es rendern: +Wir können es sehr einfach rendern: ```php $form->render(); ``` -und das Ergebnis sollte so aussehen: +und im Browser wird es so angezeigt: -[* form-en.webp *] +[* form-cs.webp *] -Das Formular ist ein Objekt der Klasse `Nette\Forms\Form` (die Klasse `Nette\Application\UI\Form` wird in Presentern verwendet). Wir haben die Steuerelemente Name, Kennwort und Schaltfläche "Senden" hinzugefügt. +Das Formular ist ein Objekt der Klasse `Nette\Forms\Form` (die Klasse `Nette\Application\UI\Form` wird in Presentern verwendet). Wir haben sogenannte Elemente hinzugefügt: Name, Passwort und einen Senden-Button. -Nun werden wir das Formular erneut aufrufen. Indem wir `$form->isSuccess()` befragen, finden wir heraus, ob das Formular abgeschickt wurde und ob es gültig ausgefüllt wurde. Wenn ja, werden wir die Daten ausgeben. Nach der Definition des Formulars werden wir hinzufügen: +Und jetzt beleben wir das Formular. Durch Abfrage von `$form->isSuccess()` stellen wir fest, ob das Formular gesendet wurde und ob es gültig ausgefüllt wurde. Wenn ja, geben wir die Daten aus. Hinter die Formulardefinition fügen wir also hinzu: ```php if ($form->isSuccess()) { - echo 'Das Formular wurde korrekt ausgefüllt und übermittelt'; + echo 'Formular wurde korrekt ausgefüllt und gesendet'; $data = $form->getValues(); - // $data->name enthält Name + // $data->name enthält den Namen // $data->password enthält das Passwort var_dump($data); } ``` -Die Methode `getValues()` gibt die gesendeten Daten in Form eines Objekts [ArrayHash |utils:arrays#ArrayHash] zurück. Wir werden [später |#Mapping to Classes] zeigen, wie man dies ändern kann. Die Variable `$data` enthält die Schlüssel `name` und `password` mit den vom Benutzer eingegebenen Daten. +Die Methode `getValues()` gibt die gesendeten Daten in Form eines [ArrayHash |utils:arrays#ArrayHash] Objekts zurück. Wie man das ändert, zeigen wir [später |#Mapping auf Klassen]. Das Objekt `$data` enthält die Schlüssel `name` und `password` mit den vom Benutzer eingegebenen Daten. -Normalerweise senden wir die Daten direkt zur weiteren Verarbeitung, die z.B. das Einfügen in die Datenbank sein kann. Bei der Verarbeitung kann jedoch ein Fehler auftreten, z. B. wenn der Benutzername bereits vergeben ist. In diesem Fall geben wir den Fehler mit `addError()` an das Formular zurück und lassen es neu zeichnen, mit einer Fehlermeldung: +Normalerweise senden wir die Daten direkt zur weiteren Verarbeitung, was zum Beispiel das Einfügen in eine Datenbank sein kann. Während der Verarbeitung kann jedoch ein Fehler auftreten, z. B. ist der Benutzername bereits vergeben. In diesem Fall geben wir den Fehler mit `addError()` an das Formular zurück und lassen es erneut rendern, zusammen mit der Fehlermeldung. ```php -$form->addError('Sorry, username is already in use.'); +$form->addError('Entschuldigung, dieser Benutzername wird bereits verwendet.'); ``` -Nach der Verarbeitung des Formulars leiten wir auf die nächste Seite weiter. Dadurch wird verhindert, dass das Formular durch Klicken auf die Schaltfläche *Aktualisieren* oder *Zurück* oder durch Verschieben des Browserverlaufs unbeabsichtigt erneut gesendet wird. +Nach der Verarbeitung des Formulars leiten wir auf die nächste Seite weiter. Dies verhindert das unbeabsichtigte erneute Senden des Formulars durch die Schaltflächen *Aktualisieren*, *Zurück* oder durch die Navigation im Browserverlauf. -Standardmäßig wird das Formular mit der POST-Methode an dieselbe Seite gesendet. Beides kann geändert werden: +Das Formular wird standardmäßig per POST-Methode an dieselbe Seite gesendet. Beides kann geändert werden: ```php $form->setAction('/submit.php'); $form->setMethod('GET'); ``` -Und das ist alles :-) Wir haben ein funktionierendes und perfekt [gesichertes |#Vulnerability Protection] Formular. +Und das ist eigentlich alles :-) Wir haben ein funktionierendes und perfekt [gesichertes |#Schutz vor Schwachstellen] Formular. -Versuchen Sie, weitere [Formularsteuerelemente |controls] hinzuzufügen. +Versuchen Sie, auch weitere [Formularelemente |controls] hinzuzufügen. -Zugang zu Steuerelementen .[#toc-access-to-controls] -==================================================== +Zugriff auf Elemente +==================== -Das Formular und seine einzelnen Steuerelemente werden als Komponenten bezeichnet. Sie bilden einen Komponentenbaum, dessen Wurzel das Formular ist. Sie können auf die einzelnen Steuerelemente wie folgt zugreifen: +Das Formular und seine einzelnen Elemente nennen wir Komponenten. Sie bilden einen Komponentenbaum, dessen Wurzel das Formular ist. Auf die einzelnen Elemente des Formulars greifen wir folgendermaßen zu: ```php $input = $form->getComponent('name'); @@ -80,37 +80,36 @@ $button = $form->getComponent('send'); // alternative Syntax: $button = $form['send']; ``` -Steuerelemente werden mit unset entfernt: +Elemente werden mit unset entfernt: ```php unset($form['name']); ``` -Überprüfungsregeln .[#toc-validation-rules] -=========================================== +Validierungsregeln +================== -Das Wort *valid* wurde hier verwendet, aber das Formular hat noch keine Validierungsregeln. Das müssen wir ändern. +Das Wort *gültig* wurde erwähnt, aber das Formular hat noch keine Validierungsregeln. Ändern wir das. -Der Name wird obligatorisch sein, also werden wir ihn mit der Methode `setRequired()` markieren, deren Argument der Text der Fehlermeldung ist, die angezeigt wird, wenn der Benutzer ihn nicht ausfüllt. Wenn kein Argument angegeben wird, wird die Standard-Fehlermeldung verwendet. +Der Name ist ein Pflichtfeld, daher markieren wir ihn mit der Methode `setRequired()`, deren Argument der Text der Fehlermeldung ist, die angezeigt wird, wenn der Benutzer den Namen nicht eingibt. Wenn kein Argument angegeben wird, wird die Standardfehlermeldung verwendet. ```php $form->addText('name', 'Name:') - ->setRequired('Please enter a name.'); + ->setRequired('Bitte geben Sie einen Namen ein'); ``` -Versuchen Sie, das Formular abzuschicken, ohne den Namen auszufüllen, und Sie werden sehen, dass eine Fehlermeldung angezeigt wird und der Browser oder Server das Formular ablehnt, bis Sie es ausgefüllt haben. +Versuchen Sie, das Formular ohne ausgefüllten Namen zu senden, und Sie werden sehen, dass eine Fehlermeldung angezeigt wird und der Browser oder Server es ablehnt, bis Sie das Feld ausfüllen. -Gleichzeitig können Sie das System nicht austricksen, indem Sie z. B. nur Leerzeichen in die Eingabe eintippen. Das geht nicht. Nette schneidet automatisch linke und rechte Leerzeichen ab. Probieren Sie es aus. Das sollten Sie bei jeder einzeiligen Eingabe immer tun, aber das wird oft vergessen. Nette macht es automatisch. (Sie können versuchen, die Formulare zu täuschen und eine mehrzeilige Zeichenkette als Namen zu senden. Auch hier lässt sich Nette nicht täuschen und die Zeilenumbrüche werden in Leerzeichen umgewandelt.) +Gleichzeitig können Sie das System nicht austricksen, indem Sie beispielsweise nur Leerzeichen in das Feld eingeben. Nein. Nette entfernt automatisch führende und nachfolgende Leerzeichen. Probieren Sie es aus. Das ist etwas, was Sie immer bei jedem einzeiligen Input tun sollten, aber oft vergessen wird. Nette erledigt das automatisch. (Sie können versuchen, das Formular zu täuschen und einen mehrzeiligen String als Namen zu senden. Auch hier lässt sich Nette nicht täuschen und wandelt Zeilenumbrüche in Leerzeichen um.) -Das Formular wird immer serverseitig validiert, aber es wird auch eine JavaScript-Validierung generiert, die schnell ist und dem Benutzer den Fehler sofort anzeigt, ohne dass das Formular an den Server gesendet werden muss. Dies wird durch das Skript `netteForms.js` erledigt. -Fügen Sie es in die Seite ein: +Das Formular wird immer serverseitig validiert, aber es wird auch eine JavaScript-Validierung generiert, die blitzschnell abläuft und der Benutzer sofort über den Fehler informiert wird, ohne das Formular an den Server senden zu müssen. Dafür sorgt das Skript `netteForms.js`. Fügen Sie es in die Seite ein: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Wenn Sie sich den Quellcode der Seite mit dem Formular ansehen, werden Sie feststellen, dass Nette die erforderlichen Felder in Elemente mit einer CSS-Klasse `required` einfügt. Versuchen Sie, der Vorlage den folgenden Stil hinzuzufügen, und die Bezeichnung "Name" wird rot sein. Auf elegante Weise markieren wir die erforderlichen Felder für die Benutzer: +Wenn Sie sich den Quellcode der Seite mit dem Formular ansehen, können Sie feststellen, dass Nette Pflichtelemente in Elemente mit der CSS-Klasse `required` einfügt. Versuchen Sie, das folgende Stylesheet in die Vorlage einzufügen, und die Beschriftung „Name“ wird rot. So kennzeichnen wir elegant die Pflichtelemente für die Benutzer: ```latte <style> @@ -118,96 +117,96 @@ Wenn Sie sich den Quellcode der Seite mit dem Formular ansehen, werden Sie fests </style> ``` -Zusätzliche Validierungsregeln werden mit der Methode `addRule()` hinzugefügt. Der erste Parameter ist rule, der zweite ist wieder der Text der Fehlermeldung, und das optionale Argument validation rule kann folgen. Was bedeutet das? +Weitere Validierungsregeln fügen wir mit der Methode `addRule()` hinzu. Der erste Parameter ist die Regel, der zweite ist wieder der Text der Fehlermeldung, und es kann noch ein Argument für die Validierungsregel folgen. Was ist damit gemeint? -Das Formular erhält eine weitere optionale Eingabe *Alter* mit der Bedingung, dass es eine Zahl sein muss (`addInteger()`) und in bestimmten Grenzen (`$form::Range`). Und hier werden wir das dritte Argument von `addRule()` verwenden, den Bereich selbst: +Wir erweitern das Formular um ein neues optionales Feld „Alter“, das eine ganze Zahl sein muss (`addInteger()`) und zusätzlich in einem erlaubten Bereich liegen muss (`$form::Range`). Und hier verwenden wir den dritten Parameter der Methode `addRule()`, mit dem wir dem Validator den gewünschten Bereich als Paar `[von, bis]` übergeben: ```php -$form->addInteger('age', 'Age:') - ->addRule($form::Range, 'You must be older 18 years and be under 120.', [18, 120]); +$form->addInteger('age', 'Alter:') + ->addRule($form::Range, 'Das Alter muss zwischen 18 und 120 liegen', [18, 120]); ``` .[tip] -Wenn der Benutzer das Feld nicht ausfüllt, werden die Validierungsregeln nicht überprüft, da das Feld optional ist. +Wenn der Benutzer das Feld nicht ausfüllt, werden die Validierungsregeln nicht überprüft, da das Element optional ist. -Offensichtlich ist Raum für ein kleines Refactoring vorhanden. In der Fehlermeldung und im dritten Parameter sind die Zahlen doppelt aufgeführt, was nicht ideal ist. Wenn wir ein [mehrsprachiges Formular |rendering#translating] erstellen würden und die Meldung mit den Zahlen in mehrere Sprachen übersetzt werden müsste, würde dies die Änderung der Werte erschweren. Aus diesem Grund können die Ersatzzeichen `%d` verwendet werden: +Hier entsteht Raum für ein kleines Refactoring. In der Fehlermeldung und im dritten Parameter sind die Zahlen doppelt aufgeführt, was nicht ideal ist. Wenn wir [mehrsprachige Formulare |rendering#Übersetzung] erstellen würden und die Meldung, die Zahlen enthält, in mehrere Sprachen übersetzt würde, würde eine spätere Änderung der Werte erschwert. Aus diesem Grund können Platzhalter `%d` verwendet werden, und Nette füllt die Werte ein: ```php - ->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]); + ->addRule($form::Range, 'Das Alter muss zwischen %d und %d Jahren liegen', [18, 120]); ``` -Kehren wir zum Feld *Kennwort* zurück, machen es *erforderlich* und überprüfen die Mindestlänge des Kennworts (`$form::MinLength`), wiederum unter Verwendung der Ersatzzeichen in der Nachricht: +Kehren wir zum Element `password` zurück, das wir ebenfalls als Pflichtfeld definieren und zusätzlich die Mindestlänge des Passworts überprüfen (`$form::MinLength`), wieder unter Verwendung des Platzhalters: ```php -$form->addPassword('password', 'Password:') - ->setRequired('Pick a password') - ->addRule($form::MinLength, 'Your password has to be at least %d long', 8); +$form->addPassword('password', 'Passwort:') + ->setRequired('Wählen Sie ein Passwort') + ->addRule($form::MinLength, 'Das Passwort muss mindestens %d Zeichen lang sein', 8); ``` -Wir fügen dem Formular ein Feld `passwordVerify` hinzu, in das der Benutzer das Kennwort erneut eingibt, um es zu überprüfen. Mit Hilfe von Validierungsregeln überprüfen wir, ob beide Passwörter gleich sind (`$form::Equal`). Und als Argument geben wir in [eckigen Klammern |#Access to Controls] einen Verweis auf das erste Kennwort an: +Wir fügen dem Formular noch das Feld `passwordVerify` hinzu, in das der Benutzer das Passwort zur Kontrolle erneut eingibt. Mithilfe von Validierungsregeln überprüfen wir, ob beide Passwörter übereinstimmen (`$form::Equal`). Und als Parameter geben wir einen Verweis auf das erste Passwort mithilfe von [eckigen Klammern |#Zugriff auf Elemente]: ```php -$form->addPassword('passwordVerify', 'Password again:') - ->setRequired('Fill your password again to check for typo') - ->addRule($form::Equal, 'Password mismatch', $form['password']) +$form->addPassword('passwordVerify', 'Passwort zur Kontrolle:') + ->setRequired('Bitte geben Sie das Passwort zur Kontrolle erneut ein') + ->addRule($form::Equal, 'Die Passwörter stimmen nicht überein', $form['password']) ->setOmitted(); ``` -Mit `setOmitted()` markieren wir ein Element, dessen Wert uns nicht wirklich interessiert und das nur zur Validierung existiert. Sein Wert wird nicht an `$data` übergeben. +Mit `setOmitted()` haben wir ein Element markiert, dessen Wert uns eigentlich egal ist und das nur aus Validierungsgründen existiert. Der Wert wird nicht an `$data` übergeben. -Wir haben ein voll funktionsfähiges Formular mit Validierung in PHP und JavaScript. Die Validierungsmöglichkeiten von Nette sind viel umfangreicher, Sie können Bedingungen erstellen, Teile einer Seite entsprechend anzeigen und ausblenden usw. Sie können alles im Kapitel über [Formularvalidierung |validation] nachlesen. +Damit haben wir ein voll funktionsfähiges Formular mit Validierung in PHP und JavaScript. Die Validierungsfähigkeiten von Nette sind weitaus umfangreicher, es können Bedingungen erstellt, Teile der Seite darauf basierend ein- und ausgeblendet werden usw. Alles erfahren Sie im Kapitel über [Formularvalidierung |validation]. -Standardwerte .[#toc-default-values] -==================================== +Standardwerte +============= -Wir setzen oft Standardwerte für Formularsteuerelemente: +Elementen des Formulars weisen wir üblicherweise Standardwerte zu: ```php -$form->addEmail('email', 'Email') +$form->addEmail('email', 'E-mail') ->setDefaultValue($lastUsedEmail); ``` -Oft ist es sinnvoll, Standardwerte für alle Steuerelemente gleichzeitig festzulegen. Zum Beispiel, wenn das Formular verwendet wird, um Datensätze zu bearbeiten. Wir lesen den Datensatz aus der Datenbank und legen ihn als Standardwerte fest: +Oft ist es nützlich, allen Elementen gleichzeitig Standardwerte zuzuweisen. Zum Beispiel, wenn das Formular zur Bearbeitung von Datensätzen dient. Wir lesen den Datensatz aus der Datenbank und setzen die Standardwerte: ```php //$row = ['name' => 'John', 'age' => '33', /* ... */]; $form->setDefaults($row); ``` -Rufen Sie `setDefaults()` auf, nachdem Sie die Steuerelemente definiert haben. +Rufen Sie `setDefaults()` erst nach der Definition der Elemente auf. -Rendering des Formulars .[#toc-rendering-the-form] -================================================== +Rendering des Formulars +======================= -Standardmäßig wird das Formular als Tabelle gerendert. Die einzelnen Steuerelemente entsprechen den grundlegenden Richtlinien für die Barrierefreiheit im Web. Alle Beschriftungen werden als `<label>` Elemente generiert und sind mit ihren Eingaben verknüpft; ein Klick auf die Beschriftung bewegt den Cursor auf die Eingabe. +Standardmäßig wird das Formular als Tabelle gerendert. Die einzelnen Elemente erfüllen die grundlegende Zugänglichkeitsregel – alle Beschriftungen sind als `<label>` geschrieben und mit dem entsprechenden Formularelement verknüpft. Beim Klicken auf die Beschriftung erscheint der Cursor automatisch im Formularfeld. -Wir können für jedes Element beliebige HTML-Attribute festlegen. Fügen Sie zum Beispiel einen Platzhalter hinzu: +Jedem Element können wir beliebige HTML-Attribute zuweisen. Zum Beispiel einen Platzhalter hinzufügen: ```php -$form->addInteger('age', 'Age:') - ->setHtmlAttribute('placeholder', 'Please fill in the age'); +$form->addInteger('age', 'Alter:') + ->setHtmlAttribute('placeholder', 'Bitte geben Sie das Alter ein'); ``` -Es gibt wirklich viele Möglichkeiten, ein Formular zu rendern, daher ist dieses Kapitel dem [Rendering |rendering] gewidmet. +Es gibt wirklich viele Möglichkeiten, ein Formular zu rendern, daher gibt es ein [eigenes Kapitel zum Rendering |rendering]. -Mapping auf Klassen .[#toc-mapping-to-classes] -============================================== +Mapping auf Klassen +=================== -Kehren wir zur Verarbeitung der Formulardaten zurück. Die Methode `getValues()` gibt die übermittelten Daten als `ArrayHash` Objekt zurück. Da es sich hierbei um eine generische Klasse handelt, ähnlich wie `stdClass`, fehlen uns einige Annehmlichkeiten bei der Arbeit damit, wie z. B. die Codevervollständigung für Eigenschaften in Editoren oder die statische Codeanalyse. Dies könnte durch eine spezifische Klasse für jedes Formular gelöst werden, deren Eigenschaften die einzelnen Steuerelemente darstellen. Z.B.: +Kehren wir zur Verarbeitung der Formulardaten zurück. Die Methode `getValues()` gab uns die gesendeten Daten als `ArrayHash`-Objekt zurück. Da es sich um eine generische Klasse handelt, ähnlich wie `stdClass`, fehlt uns bei der Arbeit damit ein gewisser Komfort, wie z.B. die Code-Vervollständigung für Properties in Editoren oder die statische Codeanalyse. Dies könnte gelöst werden, indem wir für jedes Formular eine spezifische Klasse hätten, deren Properties die einzelnen Elemente repräsentieren. Z.B.: ```php class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -Ab PHP 8.0 können Sie diese elegante Notation verwenden, die einen Konstruktor benutzt: +Alternativ können Sie einen Konstruktor verwenden: ```php class RegistrationFormData @@ -221,16 +220,18 @@ class RegistrationFormData } ``` -Wie kann man Nette anweisen, Daten als Objekte dieser Klasse zurückzugeben? Einfacher als Sie denken. Alles, was Sie tun müssen, ist, den Namen der Klasse oder des zu hydrierenden Objekts als Parameter anzugeben: +Die Properties der Datenklasse können auch Enums sein und werden automatisch zugeordnet. .{data-version:3.2.4} + +Wie sagen wir Nette, dass es die Daten als Objekte dieser Klasse zurückgeben soll? Einfacher als Sie denken. Es reicht aus, den Klassennamen oder das zu hydratisierende Objekt als Parameter anzugeben: ```php $data = $form->getValues(RegistrationFormData::class); $name = $data->name; ``` -Ein `'array'` kann ebenfalls als Parameter angegeben werden, und die Daten werden dann als Array zurückgegeben. +Als Parameter kann auch `'array'` angegeben werden, dann werden die Daten als Array zurückgegeben. -Wenn die Formulare aus einer mehrstufigen Struktur bestehen, die sich aus Containern zusammensetzt, erstellen Sie für jeden Container eine eigene Klasse: +Wenn Formulare eine mehrstufige Struktur aus Containern bilden, erstellen Sie für jeden eine separate Klasse: ```php $form = new Form; @@ -247,26 +248,28 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` -Das Mapping weiß dann anhand des Eigenschaftstyps `$person`, dass es den Container auf die Klasse `PersonFormData` abbilden soll. Wenn die Eigenschaft ein Array von Containern enthalten würde, geben Sie den Typ `array` an und übergeben Sie die Klasse, die direkt auf den Container abgebildet werden soll: +Das Mapping erkennt dann am Typ der Property `$person`, dass der Container der Klasse `PersonFormData` zugeordnet werden soll. Wenn die Property ein Array von Containern enthält, geben Sie den Typ `array` an und übergeben Sie die zuzuordnende Klasse direkt an den Container: ```php $person->setMappedType(PersonFormData::class); ``` +Den Entwurf der Datenklasse des Formulars können Sie sich mit der Methode `Nette\Forms\Blueprint::dataClass($form)` generieren lassen, die ihn auf der Browserseite ausgibt. Den Code können Sie dann einfach per Klick markieren und in Ihr Projekt kopieren. .{data-version:3.1.15} + -Mehrere Submit-Buttons .[#toc-multiple-submit-buttons] -====================================================== +Mehrere Buttons +=============== -Wenn das Formular mehr als eine Schaltfläche hat, müssen wir normalerweise unterscheiden, welche Schaltfläche gedrückt wurde. Die Methode `isSubmittedBy()` der Schaltfläche gibt uns diese Information zurück: +Wenn ein Formular mehr als einen Button hat, müssen wir in der Regel unterscheiden, welcher davon gedrückt wurde. Diese Information liefert uns die Methode `isSubmittedBy()` des Buttons: ```php $form->addSubmit('save', 'Speichern'); -$form->addSubmit('löschen', 'Löschen'); +$form->addSubmit('delete', 'Löschen'); if ($form->isSuccess()) { if ($form['save']->isSubmittedBy()) { @@ -279,37 +282,36 @@ if ($form->isSuccess()) { } ``` -Lassen Sie die `$form->isSuccess()` nicht aus, um die Gültigkeit der Daten zu überprüfen. +Lassen Sie die Abfrage `$form->isSuccess()` nicht aus, sie überprüft die Gültigkeit der Daten. -Wenn ein Formular mit der <kbd>Eingabetaste</kbd> abgeschickt wird, wird es so behandelt, als ob es mit der ersten Schaltfläche abgeschickt worden wäre. +Wenn das Formular durch Drücken von <kbd>Enter</kbd> gesendet wird, wird dies so behandelt, als ob der erste Button gesendet wurde. -Schutz vor Schwachstellen .[#toc-vulnerability-protection] -========================================================== +Schutz vor Schwachstellen +========================= -Nette Framework gibt sich große Mühe, sicher zu sein, und da Formulare die häufigste Benutzereingabe sind, sind Nette-Formulare so gut wie unangreifbar. +Das Nette Framework legt großen Wert auf Sicherheit und achtet daher sorgfältig auf die gute Absicherung von Formularen. -Zusätzlich zum Schutz der Formulare gegen bekannte Schwachstellen wie [Cross-Site Scripting (XSS) |nette:glossary#cross-site-scripting-xss] und [Cross-Site Request Forgery (CSRF |nette:glossary#cross-site-request-forgery-csrf] ) übernimmt es viele kleine Sicherheitsaufgaben, an die Sie nicht mehr denken müssen. +Neben dem Schutz der Formulare vor [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] und [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF] Angriffen, führt es viele kleine Sicherheitsmaßnahmen durch, an die Sie nicht mehr denken müssen. -So filtert es beispielsweise alle Steuerzeichen aus den Eingaben heraus und überprüft die Gültigkeit der UTF-8-Kodierung, so dass die Daten aus dem Formular immer sauber sind. Bei Auswahlfeldern und Auswahllisten wird überprüft, ob die ausgewählten Elemente tatsächlich aus den angebotenen ausgewählt wurden und keine Fälschung vorliegt. Wie bereits erwähnt, werden bei einzeiligen Texteingaben Zeichen am Zeilenende entfernt, die ein Angreifer dort einfügen könnte. Bei mehrzeiligen Eingaben werden die Zeichen am Zeilenende normalisiert. Und so weiter. +So filtert es beispielsweise alle Steuerzeichen aus den Eingaben und überprüft die Gültigkeit der UTF-8-Kodierung, sodass die Daten aus dem Formular immer sauber sind. Bei Select-Boxen und Radio-Listen wird überprüft, ob die ausgewählten Elemente tatsächlich aus den angebotenen stammten und keine Fälschung stattgefunden hat. Wir haben bereits erwähnt, dass bei einzeiligen Texteingaben Zeilenumbruchzeichen entfernt werden, die ein Angreifer dort hätte senden können. Bei mehrzeiligen Eingaben werden die Zeilenumbruchzeichen normalisiert. Und so weiter. -Nette behebt für Sie Sicherheitslücken, von deren Existenz die meisten Programmierer keine Ahnung haben. +Nette löst für Sie Sicherheitsrisiken, von denen viele Programmierer nicht einmal wissen, dass sie existieren. -Der erwähnte CSRF-Angriff besteht darin, dass ein Angreifer das Opfer dazu verleitet, eine Seite zu besuchen, die im Browser des Opfers unbemerkt eine Anfrage an den Server ausführt, bei dem das Opfer gerade angemeldet ist, und der Server glaubt, dass die Anfrage vom Opfer absichtlich gestellt wurde. Daher verhindert Nette, dass das Formular per POST von einer anderen Domäne übermittelt wird. Wenn Sie aus irgendeinem Grund den Schutz ausschalten und die Übermittlung des Formulars von einer anderen Domäne aus zulassen möchten, verwenden Sie: +Der erwähnte CSRF-Angriff besteht darin, dass ein Angreifer das Opfer auf eine Seite lockt, die unauffällig im Browser des Opfers eine Anfrage an den Server stellt, bei dem das Opfer angemeldet ist, und der Server annimmt, dass die Anfrage vom Opfer aus freiem Willen ausgeführt wurde. Daher verhindert Nette das Senden von POST-Formularen von einer anderen Domain. Wenn Sie aus irgendeinem Grund den Schutz deaktivieren und das Senden von Formularen von einer anderen Domain erlauben möchten, verwenden Sie: ```php -$form->allowCrossOrigin(); // ACHTUNG! Schaltet den Schutz aus! +$form->allowCrossOrigin(); // VORSICHT! Deaktiviert den Schutz! ``` -Dieser Schutz verwendet ein SameSite-Cookie namens `_nss`. Erstellen Sie daher ein Formular, bevor Sie die erste Ausgabe spülen, damit das Cookie gesendet werden kann. +Dieser Schutz verwendet ein SameSite-Cookie namens `_nss`. Erstellen Sie daher das Formularobjekt, bevor die erste Ausgabe gesendet wird, damit das Cookie gesendet werden kann. -Der SameSite-Cookie-Schutz ist möglicherweise nicht zu 100 % zuverlässig, weshalb es ratsam ist, den Token-Schutz zu aktivieren: +Der Schutz durch das SameSite-Cookie ist möglicherweise nicht 100% zuverlässig, daher ist es ratsam, zusätzlich den Schutz durch ein Token zu aktivieren: ```php $form->addProtection(); ``` -Es wird dringend empfohlen, diesen Schutz auf die Formulare in einem administrativen Teil Ihrer Anwendung anzuwenden, in dem sensible Daten geändert werden. Das Framework schützt vor einem CSRF-Angriff, indem es ein Authentifizierungs-Token generiert und validiert, das in einer Session gespeichert wird (das Argument ist die Fehlermeldung, die angezeigt wird, wenn das Token abgelaufen ist). Aus diesem Grund muss vor der Anzeige des Formulars eine Session gestartet werden. Im Verwaltungsteil der Website ist die Session in der Regel bereits durch die Anmeldung des Benutzers gestartet. -Ansonsten starten Sie die Session mit der Methode `Nette\Http\Session::start()`. +Wir empfehlen, Formulare im Administrationsbereich der Website, die sensible Daten in der Anwendung ändern, auf diese Weise zu schützen. Das Framework wehrt sich gegen CSRF-Angriffe, indem es ein Autorisierungstoken generiert und überprüft, das in der Session gespeichert wird. Daher muss die Session vor dem Anzeigen des Formulars geöffnet sein. Im Administrationsbereich der Website ist die Session normalerweise bereits aufgrund der Benutzeranmeldung gestartet. Andernfalls starten Sie die Session mit der Methode `Nette\Http\Session::start()`. -So, das war eine kurze Einführung in Formulare in Nette. Schauen Sie in das Verzeichnis der [Beispiele |https://github.com/nette/forms/tree/master/examples] in der Distribution, um weitere Anregungen zu erhalten. +So, das war eine schnelle Einführung in Formulare in Nette. Werfen Sie noch einen Blick in das Verzeichnis [examples |https://github.com/nette/forms/tree/master/examples] in der Distribution, dort finden Sie weitere Inspiration. diff --git a/forms/de/validation.texy b/forms/de/validation.texy index 29dd24e760..ba5253e59a 100644 --- a/forms/de/validation.texy +++ b/forms/de/validation.texy @@ -1,173 +1,184 @@ -Validierung von Formularen -************************** +Formularvalidierung +******************* -Erforderliche Steuerelemente .[#toc-required-controls] -====================================================== +Pflichtelemente +=============== -Steuerelemente werden mit der Methode `setRequired()` als erforderlich markiert, deren Argument der Text der [Fehlermeldung |#Error Messages] ist, die angezeigt wird, wenn der Benutzer sie nicht ausfüllt. Wenn kein Argument angegeben wird, wird die Standard-Fehlermeldung verwendet. +Pflichtelemente markieren wir mit der Methode `setRequired()`, deren Argument der Text der [#Fehlermeldungen] ist, die angezeigt wird, wenn der Benutzer das Element nicht ausfüllt. Wenn kein Argument angegeben wird, wird die Standardfehlermeldung verwendet. ```php $form->addText('name', 'Name:') - ->setRequired('Please fill your name.'); + ->setRequired('Bitte geben Sie einen Namen ein'); ``` -Regeln .[#toc-rules] -==================== +Regeln +====== -Mit der Methode `addRule()` fügen wir den Steuerelementen Validierungsregeln hinzu. Der erste Parameter ist die Regel, der zweite die [Fehlermeldung |#Error Messages] und der dritte das Argument für die Überprüfungsregel. +Validierungsregeln fügen wir Elementen mit der Methode `addRule()` hinzu. Der erste Parameter ist die Regel, der zweite ist der Text der [#Fehlermeldungen] und der dritte ist das Argument der Validierungsregel. ```php -$form->addPassword('password', 'Password:') - ->addRule($form::MinLength, 'Password must be at least %d characters', 8); +$form->addPassword('password', 'Passwort:') + ->addRule($form::MinLength, 'Das Passwort muss mindestens %d Zeichen lang sein', 8); ``` -Nette verfügt über eine Reihe von eingebauten Regeln, deren Namen Konstanten der Klasse `Nette\Forms\Form` sind: +**Validierungsregeln werden nur überprüft, wenn der Benutzer das Element ausgefüllt hat.** + +Nette bringt eine ganze Reihe vordefinierter Regeln mit, deren Namen Konstanten der Klasse `Nette\Forms\Form` sind. Für alle Elemente können wir diese Regeln verwenden: + +| Konstante | Beschreibung | Argumenttyp +|------------|----------------------------------------|--------------- +| `Required` | Pflichtelement, Alias für `setRequired()` | - +| `Filled` | Pflichtelement, Alias für `setRequired()` | - +| `Blank` | Element darf nicht ausgefüllt sein | - +| `Equal` | Wert ist gleich dem Parameter | `mixed` +| `NotEqual` | Wert ist nicht gleich dem Parameter | `mixed` +| `IsIn` | Wert ist gleich einem Element im Array | `array` +| `IsNotIn` | Wert ist keinem Element im Array gleich | `array` +| `Valid` | Ist das Element korrekt ausgefüllt? (für [#Bedingungen]) | - + + +Texteingaben +------------ + +Für die Elemente `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`, `addFloat()` können auch einige der folgenden Regeln verwendet werden: + +| `MinLength` | Minimale Textlänge | `int` +| `MaxLength` | Maximale Textlänge | `int` +| `Length` | Länge im Bereich oder genaue Länge | Paar `[int, int]` oder `int` +| `Email` | Gültige E-Mail-Adresse | - +| `URL` | Absolute URL | - +| `Pattern` | Entspricht dem regulären Ausdruck | `string` +| `PatternInsensitive` | Wie `Pattern`, aber Groß-/Kleinschreibung egal | `string` +| `Integer` | Ganzzahliger Wert | - +| `Numeric` | Alias für `Integer` | - +| `Float` | Zahl | - +| `Min` | Minimalwert des numerischen Elements | `int\|float` +| `Max` | Maximalwert des numerischen Elements | `int\|float` +| `Range` | Wert im Bereich | Paar `[int\|float, int\|float]` -Wir können die folgenden Regeln für alle Steuerelemente verwenden: +Die Validierungsregeln `Integer`, `Numeric` und `Float` konvertieren den Wert direkt in Integer bzw. Float. Des Weiteren akzeptiert die Regel `URL` auch Adressen ohne Schema (z. B. `nette.org`) und ergänzt das Schema (`https://nette.org`). Der Ausdruck in `Pattern` und `PatternIcase` muss für den gesamten Wert gelten, d.h. als ob er von den Zeichen `^` und `$` umschlossen wäre. -| Konstante | Beschreibung | Argumente -|------- -| `Required` | alias von `setRequired()` | - -| `Filled` | alias von `setRequired()` | - -| `Blank` | darf nicht gefüllt werden | - -| `Equal` | Wert ist gleich dem Parameter | `mixed` -| `NotEqual` | Wert ist nicht gleich Parameter | `mixed` -| `IsIn` | Wert ist gleich einem Element im Array | `array` -| `IsNotIn` | Wert ist nicht gleich einem Element im Array | `array` -| `Valid` | Eingabe besteht Validierung (für [Bedingungen |#conditions]) | - -Für die Steuerelemente `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` können auch die folgenden Regeln verwendet werden: +Anzahl der Elemente +------------------- -| `MinLength` | minimale Stringlänge | `int` -| `MaxLength` | maximale Länge der Zeichenkette | `int` -| `Length` | Länge im Bereich oder exakte Länge | Paar `[int, int]` oder `int` -| `Email` | gültige E-Mail Adresse | - -| `URL` | gültige URL | - -| `Pattern` | entspricht regulärem Muster | `string` -| `PatternInsensitive` | wie `Pattern`, aber Groß-/Kleinschreibung wird nicht berücksichtigt | `string` -| `Integer` | ganze Zahl | - -| `Numeric` | Alias von `Integer` | - -| `Float` | Ganzzahl oder Fließkommazahl | - -| `Min` | Minimum des Integer-Wertes | `int\|float` -| `Max` | Maximum des ganzzahligen Wertes | `int\|float` -| `Range` | Wert im Bereich | Paar `[int\|float, int\|float]` +Für die Elemente `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()` können auch die folgenden Regeln zur Begrenzung der Anzahl ausgewählter Elemente bzw. hochgeladener Dateien verwendet werden: -Die Regeln `Integer`, `Numeric` und `Float` konvertieren den Wert automatisch in Ganzzahl (bzw. Gleitkommazahl). Außerdem akzeptiert die Regel `URL` auch eine Adresse ohne Schema (z. B. `nette.org`) und vervollständigt das Schema (`https://nette.org`). -Die Ausdrücke in `Pattern` und `PatternInsensitive` müssen für den gesamten Wert gültig sein, d. h. so, als ob er in die Zeichen `^` and `$` eingeschlossen wäre. +| `MinLength` | Minimale Anzahl | `int` +| `MaxLength` | Maximale Anzahl | `int` +| `Length` | Anzahl im Bereich oder genaue Anzahl | Paar `[int, int]` oder `int` -Für die Steuerelemente `addUpload()`, `addMultiUpload()` können auch die folgenden Regeln verwendet werden: -| `MaxFileSize` | maximale Dateigröße | `int` -| `MimeType` | MIME-Typ, akzeptiert Wildcards (`'video/*'`) | `string\|string[]` -| `Image` | hochgeladene Datei ist JPEG, PNG, GIF, WebP | - -| `Pattern` | Dateiname entspricht regulärem Ausdruck | `string` -| `PatternInsensitive` | wie `Pattern`, aber Groß-/Kleinschreibung wird nicht berücksichtigt | `string` +Datei-Uploads +------------- -Für `MimeType` und `Image` ist die PHP-Erweiterung `fileinfo` erforderlich. Ob eine Datei oder ein Bild dem erforderlichen Typ entspricht, wird anhand ihrer Signatur erkannt. Die Integrität der gesamten Datei wird nicht geprüft. Sie können herausfinden, ob ein Bild nicht beschädigt ist, indem Sie beispielsweise versuchen, [es zu laden |http:request#toImage]. +Für die Elemente `addUpload()`, `addMultiUpload()` können auch die folgenden Regeln verwendet werden: -Für die Steuerelemente `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()` können auch die folgenden Regeln verwendet werden, um die Anzahl der ausgewählten Elemente bzw. hochgeladenen Dateien zu begrenzen: +| `MaxFileSize` | Maximale Dateigröße in Bytes | `int` +| `MimeType` | MIME-Typ, Platzhalter erlaubt (`'video/*'`) | `string\|string[]` +| `Image` | Bild JPEG, PNG, GIF, WebP, AVIF | - +| `Pattern` | Dateiname entspricht dem regulären Ausdruck | `string` +| `PatternInsensitive` | Wie `Pattern`, aber Groß-/Kleinschreibung egal | `string` -| `MinLength` | minimale Anzahl | `int` -| `MaxLength` | maximale Anzahl | `int` -| `Length` | Anzahl im Bereich oder genaue Anzahl | Paar `[int, int]` oder `int` +`MimeType` und `Image` erfordern die PHP-Erweiterung `fileinfo`. Ob es sich um eine Datei oder ein Bild des gewünschten Typs handelt, wird anhand seiner Signatur erkannt und **die Integrität der gesamten Datei wird nicht überprüft.** Ob ein Bild beschädigt ist, kann beispielsweise durch den Versuch, es zu [Laden |http:request#toImage], festgestellt werden. -Fehlermeldungen .[#toc-error-messages] --------------------------------------- +Fehlermeldungen +=============== -Alle vordefinierten Regeln außer `Pattern` und `PatternInsensitive` haben eine Standard-Fehlermeldung, so dass sie weggelassen werden können. Wenn Sie jedoch alle benutzerdefinierten Meldungen übergeben und formulieren, wird das Formular benutzerfreundlicher. +Alle vordefinierten Regeln außer `Pattern` und `PatternInsensitive` haben eine Standardfehlermeldung, sodass sie weggelassen werden kann. Durch die Angabe und Formulierung aller Meldungen nach Maß machen Sie das Formular jedoch benutzerfreundlicher. -Sie können die Standardmeldungen in [forms:configuration] ändern, indem Sie die Texte im Array `Nette\Forms\Validator::$messages` ändern oder den [Übersetzer |rendering#translating] verwenden. +Die Standardmeldungen können Sie in der [Konfiguration |forms:configuration] ändern, indem Sie die Texte im Array `Nette\Forms\Validator::$messages` bearbeiten oder einen [Übersetzer |rendering#Übersetzung] verwenden. -Die folgenden Platzhalter können im Text der Fehlermeldungen verwendet werden: +Im Text der Fehlermeldungen können diese Platzhalter verwendet werden: -| `%d` | ersetzt schrittweise die Regeln nach den Argumenten -| `%n$d` | ersetzt durch das n-te Regelargument -| `%label` | ersetzt durch Feldbezeichnung (ohne Doppelpunkt) -| `%name` | ersetzt durch den Feldnamen (z.B. `name`) -| `%value` | ersetzt durch den vom Benutzer eingegebenen Wert +| `%d` | Ersetzt nacheinander durch die Argumente der Regel +| `%n$d` | Ersetzt durch das n-te Argument der Regel +| `%label` | Ersetzt durch die Beschriftung des Elements (ohne Doppelpunkt) +| `%name` | Ersetzt durch den Namen des Elements (z.B. `name`) +| `%value` | Ersetzt durch den vom Benutzer eingegebenen Wert ```php $form->addText('name', 'Name:') - ->setRequired('Please fill in %label'); + ->setRequired('Bitte füllen Sie %label aus'); $form->addInteger('id', 'ID:') - ->addRule($form::Range, 'at least %d and no more than %d', [5, 10]); + ->addRule($form::Range, 'mindestens %d und höchstens %d', [5, 10]); $form->addInteger('id', 'ID:') - ->addRule($form::Range, 'no more than %2$d and at least %1$d', [5, 10]); + ->addRule($form::Range, 'höchstens %2$d und mindestens %1$d', [5, 10]); ``` -Bedingungen .[#toc-conditions] -============================== +Bedingungen +=========== -Neben den Validierungsregeln können auch Bedingungen festgelegt werden. Sie werden ähnlich wie Regeln gesetzt, jedoch verwenden wir `addRule()` anstelle von `addCondition()` und lassen sie natürlich ohne Fehlermeldung stehen (die Bedingung fragt nur): +Neben Regeln können auch Bedingungen hinzugefügt werden. Diese werden ähnlich wie Regeln geschrieben, nur verwenden wir statt `addRule()` die Methode `addCondition()` und geben natürlich keine Fehlermeldung an (die Bedingung fragt nur): ```php $form->addPassword('password', 'Passwort:') - // wenn das Passwort nicht länger als 8 Zeichen ist ... + // wenn das Passwort nicht länger als 8 Zeichen ist ->addCondition($form::MaxLength, 8) - // ... dann muss es eine Zahl enthalten - ->addRule($form::Pattern, 'Muss Zahl enthalten', '.*[0-9].*'); + // dann muss es eine Ziffer enthalten + ->addRule($form::Pattern, 'Muss eine Ziffer enthalten', '.*[0-9].*'); ``` -Die Bedingung kann mit einem anderen Element als dem aktuellen verknüpft werden, indem `addConditionOn()` verwendet wird. Der erste Parameter ist ein Verweis auf das Feld. Im folgenden Fall ist die E-Mail nur erforderlich, wenn das Kontrollkästchen aktiviert ist (d. h. sein Wert ist `true`): +Eine Bedingung kann auch an ein anderes Element als das aktuelle gebunden werden, indem `addConditionOn()` verwendet wird. Als ersten Parameter geben wir eine Referenz auf das Element an. In diesem Beispiel wird die E-Mail nur dann erforderlich sein, wenn die Checkbox angekreuzt ist (ihr Wert wird true sein): ```php -$form->addCheckbox('newsletters', 'Newsletter versenden'); +$form->addCheckbox('newsletters', 'Senden Sie mir Newsletter'); -$form->addEmail('email', 'Email:') - // wenn das Kontrollkästchen aktiviert ist ... +$form->addEmail('email', 'E-Mail:') + // wenn die Checkbox angekreuzt ist ->addConditionOn($form['newsletters'], $form::Equal, true) - // ... erfordern E-Mail - ->setRequired('Füllen Sie Ihre E-Mail-Adresse aus'); + // dann fordere E-Mail an + ->setRequired('Geben Sie eine E-Mail-Adresse ein'); ``` -Bedingungen können mit den Methoden `elseCondition()` und `endCondition()` zu komplexen Strukturen gruppiert werden. +Aus Bedingungen können komplexe Strukturen mithilfe von `elseCondition()` und `endCondition()` erstellt werden: ```php $form->addText(/* ... */) ->addCondition(/* ... */) // wenn die erste Bedingung erfüllt ist - ->addConditionOn(/* ... */) // und die zweite Bedingung auch für ein anderes Element - ->addRule(/* ... */) // erfordert diese Regel + ->addConditionOn(/* ... */) // und die zweite Bedingung an einem anderen Element + ->addRule(/* ... */) // fordere diese Regel an ->elseCondition() // wenn die zweite Bedingung nicht erfüllt ist - ->addRule(/* ... */) // erfordert diese Regeln + ->addRule(/* ... */) // fordere diese Regeln an ->addRule(/* ... */) ->endCondition() // wir kehren zur ersten Bedingung zurück ->addRule(/* ... */); ``` -In Nette ist es sehr einfach, auf die Erfüllung oder Nichterfüllung einer Bedingung auf der JavaScript-Seite mit der Methode `toggle()` zu reagieren, siehe [Dynamisches JavaScript |#Dynamic JavaScript]. +In Nette ist es sehr einfach, auf die Erfüllung oder Nichterfüllung einer Bedingung auch auf der JavaScript-Seite mit der Methode `toggle()` zu reagieren, siehe [#Dynamisches JavaScript]. -Referenzen zwischen Controls .[#toc-references-between-controls] -================================================================ +Referenz auf ein anderes Element +================================ -Das Regel- oder Bedingungsargument kann ein Verweis auf ein anderes Element sein. Sie können zum Beispiel dynamisch überprüfen, ob `text` so viele Zeichen hat wie der Wert des Feldes `length` beträgt: +Als Argument einer Regel oder Bedingung kann auch ein anderes Formularelement übergeben werden. Die Regel verwendet dann den Wert, der später vom Benutzer im Browser eingegeben wird. So kann z. B. dynamisch validiert werden, dass das Element `password` denselben String enthält wie das Element `password_confirm`: ```php -$form->addInteger('length'); -$form->addText('text') - ->addRule($form::Length, null, $form['length']); +$form->addPassword('password', 'Passwort'); +$form->addPassword('password_confirm', 'Passwort bestätigen') + ->addRule($form::Equal, 'Die eingegebenen Passwörter stimmen nicht überein', $form['password']); ``` -Benutzerdefinierte Regeln und Bedingungen .[#toc-custom-rules-and-conditions] -============================================================================= +Benutzerdefinierte Regeln und Bedingungen +========================================= -Manchmal kommen wir in eine Situation, in der die eingebauten Validierungsregeln in Nette nicht ausreichen und wir die Daten des Benutzers auf unsere eigene Weise validieren müssen. In Nette ist das sehr einfach! +Manchmal geraten wir in eine Situation, in der die eingebauten Validierungsregeln in Nette nicht ausreichen und wir die Benutzerdaten auf unsere eigene Weise validieren müssen. In Nette ist das sehr einfach! -Sie können einen beliebigen Callback als ersten Parameter an die Methoden `addRule()` oder `addCondition()` übergeben. Der Callback akzeptiert das Element selbst als ersten Parameter und gibt einen booleschen Wert zurück, der angibt, ob die Validierung erfolgreich war. Beim Hinzufügen einer Regel mit `addRule()` können zusätzliche Argumente übergeben werden, die dann als zweiter Parameter übergeben werden. +Den Methoden `addRule()` oder `addCondition()` kann als erster Parameter ein beliebiger Callback übergeben werden. Dieser erhält als ersten Parameter das Element selbst und gibt einen booleschen Wert zurück, der angibt, ob die Validierung erfolgreich war. Beim Hinzufügen einer Regel mit `addRule()` können auch weitere Argumente angegeben werden, die dann als zweiter Parameter übergeben werden. -Der benutzerdefinierte Satz von Validatoren kann somit als Klasse mit statischen Methoden erstellt werden: +Einen eigenen Satz von Validatoren können wir als Klasse mit statischen Methoden erstellen: ```php class MyValidators { - // prüft, ob der Wert durch das Argument teilbar ist + // testet, ob der Wert durch das Argument teilbar ist public static function validateDivisibility(BaseControl $input, $arg): bool { return $input->getValue() % $arg === 0; @@ -175,7 +186,7 @@ class MyValidators public static function validateEmailDomain(BaseControl $input, $domain) { - // zusätzliche Prüfer + // weitere Validatoren } } ``` @@ -186,12 +197,12 @@ Die Verwendung ist dann sehr einfach: $form->addInteger('num') ->addRule( [MyValidators::class, 'validateDivisibility'], - 'The value must be a multiple of %d', + 'Der Wert muss ein Vielfaches von %d sein', 8, ); ``` -Benutzerdefinierte Validierungsregeln können auch zu JavaScript hinzugefügt werden. Die einzige Bedingung ist, dass die Regel eine statische Methode sein muss. Ihr Name für den JavaScript-Validator wird durch Verkettung des Klassennamens ohne Backslashes `\`, the underscore `_` und des Methodennamens gebildet. Schreiben Sie zum Beispiel `App\MyValidators::validateDivisibility` als `AppMyValidators_validateDivisibility` und fügen Sie es dem Objekt `Nette.validators` hinzu: +Benutzerdefinierte Validierungsregeln können auch zu JavaScript hinzugefügt werden. Voraussetzung ist, dass die Regel eine statische Methode ist. Ihr Name für den JavaScript-Validator wird durch Verkettung des Klassennamens ohne Backslashes `\`, eines Unterstrichs `_` und des Methodennamens gebildet. Z. B. schreiben wir `App\MyValidators::validateDivisibility` als `AppMyValidators_validateDivisibility` und fügen es zum Objekt `Nette.validators` hinzu: ```js Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => { @@ -200,12 +211,12 @@ Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => ``` -Ereignis onValidate .[#toc-event-onvalidate] -============================================ +Ereignis onValidate +=================== -Nach dem Absenden des Formulars wird die Validierung durchgeführt, indem die einzelnen von `addRule()` hinzugefügten Regeln überprüft und dann das [Ereignis |nette:glossary#Events] `onValidate` aufgerufen wird. Dessen Handler kann für zusätzliche Validierungen verwendet werden, in der Regel um die korrekte Kombination von Werten in mehreren Formularelementen zu überprüfen. +Nach dem Absenden des Formulars wird die Validierung durchgeführt, bei der die einzelnen mit `addRule()` hinzugefügten Regeln überprüft werden und anschließend das [Ereignis |nette:glossary#Events Ereignisse] `onValidate` ausgelöst wird. Sein Handler kann für zusätzliche Validierungen verwendet werden, typischerweise zur Überprüfung der korrekten Kombination von Werten in mehreren Formularelementen. -Wenn ein Fehler festgestellt wird, wird er mit der Methode `addError()` an das Formular weitergegeben. Diese kann entweder für ein bestimmtes Element oder direkt für das Formular aufgerufen werden. +Wenn ein Fehler entdeckt wird, übergeben wir ihn mit der Methode `addError()` an das Formular. Diese kann entweder auf einem bestimmten Element oder direkt auf dem Formular aufgerufen werden. ```php protected function createComponentSignInForm(): Form @@ -225,10 +236,10 @@ public function validateSignInForm(Form $form, \stdClass $data): void ``` -Verarbeitung von Fehlern .[#toc-processing-errors] -================================================== +Fehler bei der Verarbeitung +=========================== -In vielen Fällen entdecken wir einen Fehler, wenn wir ein gültiges Formular verarbeiten, z. B. wenn wir einen neuen Eintrag in die Datenbank schreiben und auf einen doppelten Schlüssel stoßen. In diesem Fall geben wir den Fehler mit der Methode `addError()` an das Formular zurück. Diese Methode kann entweder für ein bestimmtes Element oder direkt für das Formular aufgerufen werden: +In vielen Fällen erfahren wir erst von einem Fehler, wenn wir das gültige Formular verarbeiten, z. B. wenn wir einen neuen Eintrag in die Datenbank schreiben und auf einen doppelten Schlüssel stoßen. In diesem Fall übergeben wir den Fehler erneut mit der Methode `addError()` an das Formular. Diese kann entweder auf einem bestimmten Element oder direkt auf dem Formular aufgerufen werden: ```php try { @@ -238,53 +249,52 @@ try { } catch (Nette\Security\AuthenticationException $e) { if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) { - $form->addError('Invalid password.'); + $form->addError('Ungültiges Passwort.'); } } ``` -Wenn möglich, empfehlen wir, den Fehler direkt zum Formularelement hinzuzufügen, da er bei Verwendung des Standard-Renderers daneben angezeigt wird. +Wenn möglich, empfehlen wir, den Fehler direkt an das Formularelement anzuhängen, da er bei Verwendung des Standard-Renderers daneben angezeigt wird. ```php -$form['date']->addError('Sorry, this date is already taken.'); +$form['date']->addError('Entschuldigung, aber dieses Datum ist bereits vergeben.'); ``` -Sie können `addError()` wiederholt aufrufen, um mehrere Fehlermeldungen an ein Formular oder Element zu übergeben. Sie erhalten sie mit `getErrors()`. +Sie können `addError()` wiederholt aufrufen und so dem Formular oder Element mehrere Fehlermeldungen übergeben. Sie erhalten sie mit `getErrors()`. -Beachten Sie, dass `$form->getErrors()` eine Zusammenfassung aller Fehlermeldungen zurückgibt, auch derjenigen, die direkt an einzelne Elemente übergeben wurden, nicht nur direkt an das Formular. Fehlermeldungen, die nur an das Formular übergeben werden, werden über `$form->getOwnErrors()` abgerufen. +Achtung, `$form->getErrors()` gibt eine Zusammenfassung aller Fehlermeldungen zurück, auch derjenigen, die direkt an einzelne Elemente übergeben wurden, nicht nur direkt an das Formular. Fehlermeldungen, die nur an das Formular übergeben wurden, erhalten Sie über `$form->getOwnErrors()`. -Ändern von Eingabewerten .[#toc-modifying-input-values] -======================================================= +Anpassung der Eingabe +===================== -Mit der Methode `addFilter()` können wir den vom Benutzer eingegebenen Wert ändern. In diesem Beispiel werden wir Leerzeichen in der Postleitzahl tolerieren und entfernen: +Mit der Methode `addFilter()` können wir den vom Benutzer eingegebenen Wert ändern. In diesem Beispiel tolerieren und entfernen wir Leerzeichen in der Postleitzahl: ```php -$form->addText('zip', 'Postleitzahl:') +$form->addText('zip', 'PLZ:') ->addFilter(function ($value) { - return str_replace(' ', '', $value); // Leerzeichen aus der Postleitzahl entfernen + return str_replace(' ', '', $value); // entfernen Leerzeichen aus der PLZ }) - ->addRule($form::Pattern, 'Die Postleitzahl ist nicht fünfstellig', '\d{5}'); + ->addRule($form::Pattern, 'PLZ ist nicht im Format von fünf Ziffern', '\d{5}'); ``` -Der Filter ist zwischen den Validierungsregeln und -bedingungen eingefügt und hängt daher von der Reihenfolge der Methoden ab, d. h. der Filter und die Regel werden in der gleichen Reihenfolge aufgerufen wie die Methoden `addFilter()` und `addRule()`. +Der Filter wird zwischen Validierungsregeln und Bedingungen eingefügt, daher hängt es von der Reihenfolge der Methoden ab, d. h. Filter und Regel werden in der Reihenfolge aufgerufen, in der die Methoden `addFilter()` und `addRule()` stehen. -JavaScript-Überprüfung .[#toc-javascript-validation] -==================================================== +JavaScript-Validierung +====================== -Die Sprache der Validierungsregeln und -bedingungen ist mächtig. Auch wenn alle Konstruktionen sowohl server- als auch clientseitig funktionieren, in JavaScript. Die Regeln werden in HTML-Attributen `data-nette-rules` als JSON übertragen. -Die Validierung selbst wird von einem anderen Skript durchgeführt, das alle `submit` Ereignisse des Formulars abfängt, über alle Eingaben iteriert und entsprechende Validierungen durchführt. +Die Sprache zur Formulierung von Bedingungen und Regeln ist sehr mächtig. Alle Konstrukte funktionieren dabei sowohl serverseitig als auch clientseitig in JavaScript. Sie werden in HTML-Attributen `data-nette-rules` als JSON übertragen. Die eigentliche Validierung führt dann ein Skript durch, das das `submit`-Ereignis des Formulars abfängt, die einzelnen Elemente durchläuft und die entsprechende Validierung durchführt. -Dieses Skript ist `netteForms.js`, das von mehreren möglichen Quellen erhältlich ist: +Dieses Skript ist `netteForms.js` und ist aus mehreren möglichen Quellen verfügbar: -Sie können das Skript direkt in die HTML-Seite aus dem CDN einbetten: +Sie können das Skript direkt von einem CDN in die HTML-Seite einbinden: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Oder kopieren Sie es lokal in den öffentlichen Ordner des Projekts (z. B. von `vendor/nette/forms/src/assets/netteForms.min.js`): +Oder lokal in den öffentlichen Ordner des Projekts kopieren (z. B. aus `vendor/nette/forms/src/assets/netteForms.min.js`): ```latte <script src="/path/to/netteForms.min.js"></script> @@ -296,14 +306,14 @@ Oder über [npm |https://www.npmjs.com/package/nette-forms] installieren: npm install nette-forms ``` -Und dann laden und ausführen: +Und anschließend laden und starten: ```js import netteForms from 'nette-forms'; netteForms.initOnLoad(); ``` -Alternativ können Sie es auch direkt aus dem Ordner `vendor` laden: +Alternativ können Sie es direkt aus dem `vendor`-Ordner laden: ```js import netteForms from '../path/to/vendor/nette/forms/src/assets/netteForms.js'; @@ -311,10 +321,10 @@ netteForms.initOnLoad(); ``` -Dynamisches JavaScript .[#toc-dynamic-javascript] -================================================= +Dynamisches JavaScript +====================== -Sie möchten die Adressfelder nur dann anzeigen, wenn der Benutzer die Ware per Post verschicken möchte? Das ist kein Problem. Der Schlüssel ist ein Paar von Methoden `addCondition()` & `toggle()`: +Möchten Sie die Felder zur Eingabe der Adresse nur anzeigen, wenn der Benutzer den Versand per Post wählt? Kein Problem. Der Schlüssel ist das Methodenpaar `addCondition()` & `toggle()`: ```php $form->addCheckbox('send_it') @@ -322,25 +332,25 @@ $form->addCheckbox('send_it') ->toggle('#address-container'); ``` -Dieser Code besagt, dass das HTML-Element `#address-container` sichtbar wird, wenn die Bedingung erfüllt ist, d. h. wenn das Kontrollkästchen markiert ist. Und vice versa. Wir platzieren also die Formularelemente mit der Adresse des Empfängers in einem Container mit dieser ID, und wenn das Ankreuzfeld angeklickt wird, werden sie ein- oder ausgeblendet. Dies wird durch das Skript `netteForms.js` gesteuert. +Dieser Code besagt, dass, wenn die Bedingung erfüllt ist, d. h. wenn die Checkbox angekreuzt ist, das HTML-Element `#address-container` sichtbar sein wird. Und umgekehrt. Die Formularelemente mit der Empfängeradresse platzieren wir also in einem Container mit dieser ID, und beim Klicken auf die Checkbox werden sie ausgeblendet oder angezeigt. Dafür sorgt das Skript `netteForms.js`. -Jeder Selektor kann als Argument an die Methode `toggle()` übergeben werden. Aus historischen Gründen wird eine alphanumerische Zeichenkette ohne andere Sonderzeichen als Element-ID behandelt, so als ob ihr das `#` character. The second optional parameter allows us to reverse the behavior, i.e. if we used `toggle('#address-container', false)` vorangestellt wäre. Das Element würde nur angezeigt werden, wenn das Kontrollkästchen nicht angekreuzt ist. +Als Argument der Methode `toggle()` kann ein beliebiger Selektor übergeben werden. Aus historischen Gründen wird ein alphanumerischer String ohne weitere Sonderzeichen als ID des Elements verstanden, also genauso, als ob ihm das Zeichen `#` vorangestellt wäre. Der zweite optionale Parameter ermöglicht es, das Verhalten umzukehren, d. h. wenn wir `toggle('#address-container', false)` verwenden würden, würde das Element umgekehrt nur dann angezeigt, wenn die Checkbox nicht angekreuzt wäre. -Die Standardimplementierung von JavaScript ändert die Eigenschaft `hidden` für Elemente. Wir können das Verhalten jedoch leicht ändern, indem wir beispielsweise eine Animation hinzufügen. Überschreiben Sie einfach die Methode `Nette.toggle` in JavaScript mit einer benutzerdefinierten Lösung: +Die Standardimplementierung in JavaScript ändert die `hidden`-Eigenschaft der Elemente. Das Verhalten können wir jedoch leicht ändern, z. B. eine Animation hinzufügen. Es genügt, die Methode `Nette.toggle` in JavaScript durch eine eigene Lösung zu überschreiben: ```js Nette.toggle = (selector, visible, srcElement, event) => { document.querySelectorAll(selector).forEach((el) => { - // hide or show 'el' according to the value of 'visible' + // Blenden Sie 'el' entsprechend dem Wert von 'visible' ein oder aus }); }; ``` -Deaktivieren der Validierung .[#toc-disabling-validation] -========================================================= +Validierung deaktivieren +======================== -In bestimmten Fällen müssen Sie die Gültigkeitsprüfung deaktivieren. Wenn eine Submit-Schaltfläche nach dem Absenden keine Validierung durchführen soll (z. B. *Abbrechen* oder *Vorschau*), können Sie die Validierung durch den Aufruf von `$submit->setValidationScope([])` deaktivieren. Sie können das Formular auch teilweise validieren, indem Sie Elemente angeben, die validiert werden sollen. +Manchmal kann es nützlich sein, die Validierung zu deaktivieren. Wenn das Drücken eines Sende-Buttons keine Validierung durchführen soll (geeignet für *Abbrechen*- oder *Vorschau*-Buttons), deaktivieren wir sie mit der Methode `$submit->setValidationScope([])`. Wenn nur eine teilweise Validierung durchgeführt werden soll, können wir festlegen, welche Felder oder Formularcontainer validiert werden sollen. ```php $form->addText('name') @@ -354,13 +364,13 @@ $details->addInteger('age2') $form->addSubmit('send1'); // Validiert das gesamte Formular $form->addSubmit('send2') - ->setValidationScope([]); // Überprüft nichts + ->setValidationScope([]); // Validiert überhaupt nicht $form->addSubmit('send3') - ->setValidationScope([$form['name']]); // Prüft nur das Feld 'name' + ->setValidationScope([$form['name']]); // Validiert nur das Element name $form->addSubmit('send4') - ->setValidationScope([$form['details']['age']]); // Validiert nur das Feld 'age' + ->setValidationScope([$form['details']['age']]); // Validiert nur das Element age $form->addSubmit('send5') - ->setValidationScope([$form['details']]); // Validiert den 'details'-Container + ->setValidationScope([$form['details']]); // Validiert den Container details ``` -[Das Ereignis onValidate |#Event onValidate] auf dem Formular wird immer aufgerufen und wird von `setValidationScope` nicht beeinflusst. `onValidate` Ereignis auf dem Container wird nur aufgerufen, wenn dieser Container für eine Teilvalidierung angegeben ist. +`setValidationScope` beeinflusst nicht das [#Ereignis onValidate] des Formulars, das immer aufgerufen wird. Das `onValidate`-Ereignis eines Containers wird nur ausgelöst, wenn dieser Container für die Teilvalidierung markiert ist. diff --git a/forms/el/@home.texy b/forms/el/@home.texy index a3e1839d27..1250a801a3 100644 --- a/forms/el/@home.texy +++ b/forms/el/@home.texy @@ -1,31 +1,31 @@ -Έντυπα -****** +Nette Forms +*********** <div class=perex> -Η Nette Forms έχει φέρει επανάσταση στη δημιουργία διαδικτυακών φορμών. Το μόνο που είχατε να κάνετε ήταν να γράψετε μερικές σαφείς γραμμές κώδικα και είχατε μια φόρμα, συμπεριλαμβανομένης της απόδοσης, της JavaScript και της επικύρωσης διακομιστή, καθώς και κορυφαία ασφάλεια. Ας δούμε πώς +Το Nette Forms έφερε επανάσταση στη δημιουργία φορμών web. Ξαφνικά, αρκούσε να γράψετε μερικές κατανοητές γραμμές κώδικα και είχατε έτοιμη μια φόρμα, συμπεριλαμβανομένης της απόδοσης, της επικύρωσης JavaScript και server-side, και επιπλέον εξαιρετικά ασφαλισμένη. Θα δείξουμε πώς -- να δημιουργήσετε φιλικές φόρμες -- να επικυρώνετε τα δεδομένα που αποστέλλονται -- να σχεδιάζετε στοιχεία ακριβώς όπως χρειάζεται +- να δημιουργείτε φιλικές φόρμες +- να επικυρώνετε τα υποβληθέντα δεδομένα +- να αποδίδετε τα στοιχεία ακριβώς όπως χρειάζεται </div> -Με τη Nette Forms, μπορείτε να μειώσετε τις εργασίες ρουτίνας, όπως η συγγραφή επικύρωσης (τόσο στην πλευρά του διακομιστή όσο και στην πλευρά του πελάτη), και να ελαχιστοποιήσετε την πιθανότητα σφαλμάτων και ζητημάτων ασφαλείας. +Χρησιμοποιώντας το Nette Forms αποφεύγετε μια ολόκληρη σειρά από ρουτίνες εργασίες, όπως η συγγραφή επικύρωσης (επιπλέον διπλής, από την πλευρά του server και του client), ελαχιστοποιείτε την πιθανότητα εμφάνισης σφαλμάτων και κενών ασφαλείας. -Μπορείτε να χρησιμοποιήσετε τις φόρμες είτε ως μέρος της εφαρμογής Nette (δηλαδή σε παρουσιαστές) είτε αυτόνομα. Επειδή η χρήση είναι ελαφρώς διαφορετική και στις δύο περιπτώσεις, έχουμε ετοιμάσει ξεχωριστές οδηγίες για εσάς: +Μπορείτε να χρησιμοποιήσετε τις φόρμες είτε ως μέρος της Εφαρμογής Nette (δηλαδή σε presenters), είτε εντελώς αυτόνομα. Επειδή και στις δύο περιπτώσεις η χρήση διαφέρει λίγο, ετοιμάσαμε για εσάς δύο οδηγούς: <div class="wiki-buttons"> -<div> "Φόρμες σε παρουσιαστές .[wiki-button]":in-presenter </div> -<div> "Φόρμες αυτόνομες .[wiki-button]":standalone </div> +<div> "Φόρμες σε presenters .[wiki-button]":in-presenter </div> +<div> "Φόρμες αυτόνομα .[wiki-button]":standalone </div> </div> Εγκατάσταση ----------- -Κατεβάστε και εγκαταστήστε το πακέτο χρησιμοποιώντας το [Composer |best-practices:composer]: +Κατεβάστε και εγκαταστήστε τη βιβλιοθήκη χρησιμοποιώντας το εργαλείο [Composer|best-practices:composer]: ```shell composer require nette/forms diff --git a/forms/el/@left-menu.texy b/forms/el/@left-menu.texy index 700aacd34e..bbf4c1020e 100644 --- a/forms/el/@left-menu.texy +++ b/forms/el/@left-menu.texy @@ -1,14 +1,14 @@ -Έντυπα -****** -- [Επισκόπηση |@home] -- [Φόρμες στους παρουσιαστές |in-presenter] -- [Φόρμες αυτόνομες |standalone] -- [Έλεγχοι φόρμας |controls] +Nette Forms +*********** +- [Εισαγωγή |@home] +- [Φόρμες σε presenters|in-presenter] +- [Φόρμες αυτόνομα|standalone] +- [Στοιχεία φόρμας |controls] - [Επικύρωση |validation] -- [Απεικόνιση |rendering] -- [Διαμόρφωση |Configuration] +- [Απόδοση |rendering] +- [Διαμόρφωση |configuration] Περαιτέρω ανάγνωση ****************** -- [Βέλτιστες πρακτικές |best-practices:] +- [Οδηγοί και διαδικασίες |best-practices:] diff --git a/forms/el/@meta.texy b/forms/el/@meta.texy new file mode 100644 index 0000000000..88e29852c7 --- /dev/null +++ b/forms/el/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Τεκμηρίωση}} diff --git a/forms/el/configuration.texy b/forms/el/configuration.texy index 6d26f12bcf..44fcbe1719 100644 --- a/forms/el/configuration.texy +++ b/forms/el/configuration.texy @@ -2,7 +2,7 @@ ***************** .[perex] -Μπορείτε να αλλάξετε τα προεπιλεγμένα [μηνύματα σφάλματος φόρμας |validation] στη διαμόρφωση. +Στη διαμόρφωση, μπορείτε να αλλάξετε τα προεπιλεγμένα [μηνύματα σφάλματος φόρμας|validation]. ```neon forms: @@ -30,4 +30,32 @@ forms: Nette\Forms\Controls\CsrfProtection::Protection: 'Your session has expired. Please return to the home page and try again.' ``` -Εάν δεν χρησιμοποιείτε ολόκληρο το πλαίσιο και συνεπώς ούτε καν τα αρχεία διαμόρφωσης, μπορείτε να αλλάξετε τα προεπιλεγμένα μηνύματα σφάλματος απευθείας στο πεδίο `Nette\Forms\Validator::$messages`. +Εδώ είναι η ελληνική μετάφραση: + +```neon +forms: + messages: + Equal: 'Παρακαλώ εισάγετε %s.' + NotEqual: 'Αυτή η τιμή δεν πρέπει να είναι %s.' + Filled: 'Αυτό το πεδίο είναι υποχρεωτικό.' + Blank: 'Αυτό το πεδίο πρέπει να είναι κενό.' + MinLength: 'Παρακαλώ εισάγετε τουλάχιστον %d χαρακτήρες.' + MaxLength: 'Παρακαλώ εισάγετε το πολύ %d χαρακτήρες.' + Length: 'Παρακαλώ εισάγετε μια τιμή μήκους μεταξύ %d και %d χαρακτήρων.' + Email: 'Παρακαλώ εισάγετε μια έγκυρη διεύθυνση email.' + URL: 'Παρακαλώ εισάγετε ένα έγκυρο URL.' + Integer: 'Παρακαλώ εισάγετε έναν έγκυρο ακέραιο αριθμό.' + Float: 'Παρακαλώ εισάγετε έναν έγκυρο αριθμό.' + Min: 'Παρακαλώ εισάγετε μια τιμή μεγαλύτερη ή ίση με %d.' + Max: 'Παρακαλώ εισάγετε μια τιμή μικρότερη ή ίση με %d.' + Range: 'Παρακαλώ εισάγετε μια τιμή μεταξύ %d και %d.' + MaxFileSize: 'Το μέγεθος του ανεβασμένου αρχείου μπορεί να είναι έως %d bytes.' + MaxPostSize: 'Τα ανεβασμένα δεδομένα υπερβαίνουν το όριο των %d bytes.' + MimeType: 'Το ανεβασμένο αρχείο δεν είναι στην αναμενόμενη μορφή.' + Image: 'Το ανεβασμένο αρχείο πρέπει να είναι εικόνα σε μορφή JPEG, GIF, PNG, WebP ή AVIF.' + Nette\Forms\Controls\SelectBox::Valid: 'Παρακαλώ επιλέξτε μια έγκυρη επιλογή.' + Nette\Forms\Controls\UploadControl::Valid: 'Παρουσιάστηκε σφάλμα κατά τη μεταφόρτωση του αρχείου.' + Nette\Forms\Controls\CsrfProtection::Protection: 'Η συνεδρία σας έχει λήξει. Παρακαλώ επιστρέψτε στην αρχική σελίδα και προσπαθήστε ξανά.' +``` + +Εάν δεν χρησιμοποιείτε ολόκληρο το framework και επομένως ούτε τα αρχεία διαμόρφωσης, μπορείτε να αλλάξετε τα προεπιλεγμένα μηνύματα σφάλματος απευθείας στον πίνακα `Nette\Forms\Validator::$messages`. diff --git a/forms/el/controls.texy b/forms/el/controls.texy index 978a60c42e..faa1fdb363 100644 --- a/forms/el/controls.texy +++ b/forms/el/controls.texy @@ -1,280 +1,375 @@ -Έλεγχοι φόρμας -************** +Στοιχεία φόρμας +*************** .[perex] -Επισκόπηση των ενσωματωμένων στοιχείων ελέγχου φόρμας. +Επισκόπηση των τυπικών στοιχείων φόρμας. -addText(string|int $name, $label=null): TextInput .[method] -=========================================================== +addText(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +================================================================================================== -Προσθέτει πεδίο κειμένου μονής γραμμής (κλάση [TextInput |api:Nette\Forms\Controls\TextInput]). Αν ο χρήστης δεν συμπληρώσει το πεδίο, επιστρέφει ένα κενό αλφαριθμητικό `''`, ή χρησιμοποιεί το `setNullable()` για να το αλλάξει και να επιστρέφει `null`. +Προσθέτει ένα πεδίο κειμένου μίας γραμμής (κλάση [TextInput |api:Nette\Forms\Controls\TextInput]). Εάν ο χρήστης δεν συμπληρώσει το πεδίο, επιστρέφει ένα κενό string `''`, ή με τη χρήση του `setNullable()` μπορεί να οριστεί να επιστρέφει `null`. ```php -$form->addText('name', 'Name:') +$form->addText('name', 'Όνομα:') ->setRequired() ->setNullable(); ``` -Επικυρώνει αυτόματα UTF-8, κόβει τα αριστερά και δεξιά κενά διαστήματος και αφαιρεί τα διαλείμματα γραμμής που θα μπορούσαν να σταλούν από έναν εισβολέα. +Επικυρώνει αυτόματα το UTF-8, αφαιρεί τα κενά στην αρχή και στο τέλος και αφαιρεί τις αλλαγές γραμμής που θα μπορούσε να στείλει ένας εισβολέας. -Το μέγιστο μήκος μπορεί να περιοριστεί χρησιμοποιώντας το `setMaxLength()`. Η [addFilter() |validation#Modifying Input Values] σας επιτρέπει να αλλάξετε την τιμή που εισάγει ο χρήστης. +Το μέγιστο μήκος μπορεί να περιοριστεί με τη χρήση του `setMaxLength()`. Η τροποποίηση της τιμής που εισήγαγε ο χρήστης είναι δυνατή με το [addFilter() |validation#Τροποποίηση Εισόδου]. -Χρησιμοποιήστε το `setHtmlType()` για να αλλάξετε τον [χαρακτήρα |https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types] του στοιχείου εισόδου σε `search`, `tel`, `url`, `range`, `date`, `datetime-local`, `month`, `time`, `week`, `color`. Αντί των τύπων `number` και `email`, συνιστούμε τη χρήση των τύπων [addInteger |#addInteger] και [addEmail |#addEmail], οι οποίοι παρέχουν επικύρωση από την πλευρά του διακομιστή. +Με τη χρήση του `setHtmlType()` μπορεί να αλλάξει ο οπτικός χαρακτήρας του πεδίου κειμένου σε τύπους όπως `search`, `tel` ή `url` δείτε τις [προδιαγραφές|https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types]. Να θυμάστε ότι η αλλαγή τύπου είναι μόνο οπτική και δεν αντικαθιστά τη λειτουργία επικύρωσης. Για τον τύπο `url` είναι σκόπιμο να προστεθεί ένας συγκεκριμένος [κανόνας επικύρωσης URL |validation#Είσοδοι Κειμένου]. -```php -$form->addText('color', 'Choose color:') - ->setHtmlType('color') - ->addRule($form::Pattern, 'invalid value', '[0-9a-f]{6}'); -``` +.[note] +Για άλλους τύπους εισόδου, όπως `number`, `range`, `email`, `date`, `datetime-local`, `time` και `color`, χρησιμοποιήστε τις εξειδικευμένες μεθόδους όπως [#addInteger], [#addFloat], [#addEmail] [#addDate], [#addTime], [#addDateTime] και [#addColor], οι οποίες εξασφαλίζουν την επικύρωση από την πλευρά του διακομιστή. Οι τύποι `month` και `week` δεν υποστηρίζονται ακόμη πλήρως σε όλα τα προγράμματα περιήγησης. -Για το στοιχείο μπορεί να οριστεί η λεγόμενη κενή τιμή (empty-value), η οποία είναι κάτι σαν την προεπιλεγμένη τιμή, αλλά αν ο χρήστης δεν την αντικαταστήσει, επιστρέφει κενή συμβολοσειρά ή `null`. +Στο στοιχείο μπορεί να οριστεί η λεγόμενη empty-value, η οποία είναι κάτι σαν προεπιλεγμένη τιμή, αλλά αν ο χρήστης δεν την αλλάξει, το στοιχείο επιστρέφει κενό string ή `null`. ```php -$form->addText('phone', 'Phone:') +$form->addText('phone', 'Τηλέφωνο:') ->setHtmlType('tel') - ->setEmptyValue('+420'); + ->setEmptyValue('+30'); ``` addTextArea(string|int $name, $label=null): TextArea .[method] ============================================================== -Προσθέτει ένα πεδίο κειμένου πολλαπλών γραμμών (κλάση [TextArea |api:Nette\Forms\Controls\TextArea]). Αν ο χρήστης δεν συμπληρώσει το πεδίο, επιστρέφει ένα κενό αλφαριθμητικό `''`, ή χρησιμοποιεί το `setNullable()` για να το αλλάξει και να επιστρέφει `null`. +Προσθέτει ένα πεδίο για την εισαγωγή κειμένου πολλαπλών γραμμών (κλάση [TextArea |api:Nette\Forms\Controls\TextArea]). Εάν ο χρήστης δεν συμπληρώσει το πεδίο, επιστρέφει ένα κενό string `''`, ή με τη χρήση του `setNullable()` μπορεί να οριστεί να επιστρέφει `null`. ```php -$form->addTextArea('note', 'Note:') - ->addRule($form::MaxLength, 'Your note is way too long', 10000); +$form->addTextArea('note', 'Σημείωση:') + ->addRule($form::MaxLength, 'Η σημείωση είναι πολύ μεγάλη', 10000); ``` -Επικυρώνει αυτόματα το UTF-8 και κανονικοποιεί τα διαλείμματα γραμμής στο `\n`. Σε αντίθεση με ένα πεδίο εισαγωγής μιας γραμμής, δεν κόβει το κενό διάστημα. +Επικυρώνει αυτόματα το UTF-8 και κανονικοποιεί τους διαχωριστές γραμμών σε `\n`. Σε αντίθεση με το πεδίο εισόδου μίας γραμμής, δεν γίνεται καμία αφαίρεση κενών. -Το μέγιστο μήκος μπορεί να περιοριστεί χρησιμοποιώντας το `setMaxLength()`. Η [addFilter() |validation#Modifying Input Values] σας επιτρέπει να αλλάξετε την τιμή που εισάγει ο χρήστης. Μπορείτε να ορίσετε τη λεγόμενη κενή τιμή χρησιμοποιώντας το `setEmptyValue()`. +Το μέγιστο μήκος μπορεί να περιοριστεί με τη χρήση του `setMaxLength()`. Η τροποποίηση της τιμής που εισήγαγε ο χρήστης είναι δυνατή με το [addFilter() |validation#Τροποποίηση Εισόδου]. Μπορεί να οριστεί η λεγόμενη empty-value με τη χρήση του `setEmptyValue()`. addInteger(string|int $name, $label=null): TextInput .[method] ============================================================== -Προσθέτει πεδίο εισόδου για ακέραιο αριθμό (κλάση [TextInput |api:Nette\Forms\Controls\TextInput]). Επιστρέφει είτε έναν ακέραιο είτε το `null` εάν ο χρήστης δεν εισάγει τίποτα. +Προσθέτει ένα πεδίο για την εισαγωγή ακέραιου αριθμού (κλάση [TextInput |api:Nette\Forms\Controls\TextInput]). Επιστρέφει είτε integer, είτε `null`, εάν ο χρήστης δεν εισάγει τίποτα. + +```php +$form->addInteger('year', 'Έτος:') + ->addRule($form::Range, 'Το έτος πρέπει να είναι στο εύρος από %d έως %d.', [1900, 2023]); +``` + +Το στοιχείο αποδίδεται ως `<input type="number">`. Με τη χρήση της μεθόδου `setHtmlType()` μπορεί να αλλάξει ο τύπος σε `range` για εμφάνιση με τη μορφή ολισθητήρα, ή σε `text`, εάν προτιμάτε ένα τυπικό πεδίο κειμένου χωρίς την ειδική συμπεριφορά του τύπου `number`. + + +addFloat(string|int $name, $label=null): TextInput .[method]{data-version:3.1.12} +================================================================================= + +Προσθέτει ένα πεδίο για την εισαγωγή δεκαδικού αριθμού (κλάση [TextInput |api:Nette\Forms\Controls\TextInput]). Επιστρέφει είτε float, είτε `null`, εάν ο χρήστης δεν εισάγει τίποτα. ```php -$form->addInteger('level', 'Level:') +$form->addFloat('level', 'Επίπεδο:') ->setDefaultValue(0) - ->addRule($form::Range, 'Level must be between %d and %d.', [0, 100]); + ->addRule($form::Range, 'Το επίπεδο πρέπει να είναι στο εύρος από %d έως %d.', [0, 100]); ``` +Το στοιχείο αποδίδεται ως `<input type="number">`. Με τη χρήση της μεθόδου `setHtmlType()` μπορεί να αλλάξει ο τύπος σε `range` για εμφάνιση με τη μορφή ολισθητήρα, ή σε `text`, εάν προτιμάτε ένα τυπικό πεδίο κειμένου χωρίς την ειδική συμπεριφορά του τύπου `number`. + +Το Nette και ο περιηγητής Chrome αποδέχονται τόσο το κόμμα όσο και την τελεία ως διαχωριστικό δεκαδικών ψηφίων. Για να είναι διαθέσιμη αυτή η λειτουργικότητα και στον Firefox, συνιστάται να ορίσετε το χαρακτηριστικό `lang` είτε για το συγκεκριμένο στοιχείο είτε για ολόκληρη τη σελίδα, για παράδειγμα `<html lang="el">`. + -addEmail(string|int $name, $label=null): TextInput .[method] -============================================================ +addEmail(string|int $name, $label=null, int $maxLength=255): TextInput .[method] +================================================================================ -Προσθέτει πεδίο διεύθυνσης ηλεκτρονικού ταχυδρομείου με έλεγχο εγκυρότητας (κλάση [TextInput |api:Nette\Forms\Controls\TextInput]). Αν ο χρήστης δεν συμπληρώσει το πεδίο, επιστρέφει ένα κενό αλφαριθμητικό `''`, ή χρησιμοποιεί το `setNullable()` για να το αλλάξει και να επιστρέφει `null`. +Προσθέτει ένα πεδίο για την εισαγωγή διεύθυνσης email (κλάση [TextInput |api:Nette\Forms\Controls\TextInput]). Εάν ο χρήστης δεν συμπληρώσει το πεδίο, επιστρέφει ένα κενό string `''`, ή με τη χρήση του `setNullable()` μπορεί να οριστεί να επιστρέφει `null`. ```php -$form->addEmail('email', 'Email:'); +$form->addEmail('email', 'E-mail:'); ``` -Επαληθεύει ότι η τιμή είναι έγκυρη διεύθυνση ηλεκτρονικού ταχυδρομείου. Δεν επαληθεύει ότι ο τομέας υπάρχει πραγματικά, μόνο η σύνταξη επαληθεύεται. Επικυρώνει αυτόματα UTF-8, κόβει τα αριστερά και δεξιά κενά. +Επαληθεύει εάν η τιμή είναι έγκυρη διεύθυνση email. Δεν επαληθεύεται εάν ο τομέας υπάρχει πραγματικά, επαληθεύεται μόνο η σύνταξη. Επικυρώνει αυτόματα το UTF-8, αφαιρεί τα κενά στην αρχή και στο τέλος. -Το μέγιστο μήκος μπορεί να περιοριστεί με τη χρήση του `setMaxLength()`. Η [addFilter() |validation#Modifying Input Values] σας επιτρέπει να αλλάξετε την τιμή που εισάγει ο χρήστης. Μπορείτε να ορίσετε τη λεγόμενη κενή τιμή χρησιμοποιώντας το `setEmptyValue()`. +Το μέγιστο μήκος μπορεί να περιοριστεί με τη χρήση του `setMaxLength()`. Η τροποποίηση της τιμής που εισήγαγε ο χρήστης είναι δυνατή με το [addFilter() |validation#Τροποποίηση Εισόδου]. Μπορεί να οριστεί η λεγόμενη empty-value με τη χρήση του `setEmptyValue()`. -addPassword(string|int $name, $label=null): TextInput .[method] -=============================================================== +addPassword(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +====================================================================================================== -Προσθέτει πεδίο κωδικού πρόσβασης (κλάση [TextInput |api:Nette\Forms\Controls\TextInput]). +Προσθέτει ένα πεδίο για την εισαγωγή κωδικού πρόσβασης (κλάση [TextInput |api:Nette\Forms\Controls\TextInput]). ```php -$form->addPassword('password', 'Password:') +$form->addPassword('password', 'Κωδικός πρόσβασης:') ->setRequired() - ->addRule($form::MinLength, 'Password has to be at least %d characters long', 8) - ->addRule($form::Pattern, 'Password must contain a number', '.*[0-9].*'); + ->addRule($form::MinLength, 'Ο κωδικός πρόσβασης πρέπει να έχει τουλάχιστον %d χαρακτήρες', 8) + ->addRule($form::Pattern, 'Πρέπει να περιέχει αριθμό', '.*[0-9].*'); ``` -Όταν ξαναστείλετε τη φόρμα, η είσοδος θα είναι κενή. Επικυρώνει αυτόματα UTF-8, κόβει τα αριστερά και δεξιά κενά διαστήματος και αφαιρεί τα διαλείμματα γραμμής που θα μπορούσαν να σταλούν από έναν εισβολέα. +Κατά την εκ νέου εμφάνιση της φόρμας, το πεδίο θα είναι κενό. Επικυρώνει αυτόματα το UTF-8, αφαιρεί τα κενά στην αρχή και στο τέλος και αφαιρεί τις αλλαγές γραμμής που θα μπορούσε να στείλει ένας εισβολέας. addCheckbox(string|int $name, $caption=null): Checkbox .[method] ================================================================ -Προσθέτει ένα πλαίσιο ελέγχου (κλάση [Checkbox |api:Nette\Forms\Controls\Checkbox]). Το πεδίο επιστρέφει είτε `true` είτε `false`, ανάλογα με το αν είναι τσεκαρισμένο. +Προσθέτει ένα πλαίσιο ελέγχου (κλάση [Checkbox |api:Nette\Forms\Controls\Checkbox]). Επιστρέφει την τιμή είτε `true` είτε `false`, ανάλογα με το αν είναι επιλεγμένο. ```php -$form->addCheckbox('agree', 'I agree with terms') - ->setRequired('You must agree with our terms'); +$form->addCheckbox('agree', 'Συμφωνώ με τους όρους') + ->setRequired('Είναι απαραίτητο να συμφωνήσετε με τους όρους'); ``` -addCheckboxList(string|int $name, $label=null, array $items=null): CheckboxList .[method] -========================================================================================= +addCheckboxList(string|int $name, $label=null, ?array $items=null): CheckboxList .[method] +========================================================================================== -Προσθέτει λίστα με πλαίσια ελέγχου για την επιλογή πολλαπλών στοιχείων (κλάση [CheckboxList |api:Nette\Forms\Controls\CheckboxList]). Επιστρέφει τον πίνακα με τα κλειδιά των επιλεγμένων στοιχείων. Η μέθοδος `getSelectedItems()` επιστρέφει τιμές αντί για κλειδιά. +Προσθέτει πλαίσια ελέγχου για την επιλογή πολλαπλών στοιχείων (κλάση [CheckboxList |api:Nette\Forms\Controls\CheckboxList]). Επιστρέφει έναν πίνακα με τα κλειδιά των επιλεγμένων στοιχείων. Η μέθοδος `getSelectedItems()` επιστρέφει τις τιμές αντί για τα κλειδιά. ```php -$form->addCheckboxList('colors', 'Colors:', [ - 'r' => 'red', - 'g' => 'green', - 'b' => 'blue', +$form->addCheckboxList('colors', 'Χρώματα:', [ + 'r' => 'κόκκινο', + 'g' => 'πράσινο', + 'b' => 'μπλε', ]); ``` -Παραδίδουμε τον πίνακα των στοιχείων ως τρίτη παράμετρο ή με τη μέθοδο `setItems()`. +Τον πίνακα των προσφερόμενων στοιχείων τον παραδίδουμε ως τρίτη παράμετρο ή με τη μέθοδο `setItems()`. -Μπορείτε να χρησιμοποιήσετε `setDisabled(['r', 'g'])` για να απενεργοποιήσετε μεμονωμένα στοιχεία. +Με τη χρήση του `setDisabled(['r', 'g'])` μπορούν να απενεργοποιηθούν μεμονωμένα στοιχεία. -Το στοιχείο ελέγχει αυτόματα ότι δεν έχει γίνει πλαστογράφηση και ότι τα επιλεγμένα στοιχεία είναι πράγματι ένα από τα προσφερόμενα και δεν έχουν απενεργοποιηθεί. Η μέθοδος `getRawValue()` μπορεί να χρησιμοποιηθεί για να ανακτήσετε τα προσφερόμενα στοιχεία χωρίς αυτόν τον σημαντικό έλεγχο. +Το στοιχείο ελέγχει αυτόματα ότι δεν έχει γίνει πλαστογράφηση και ότι τα επιλεγμένα στοιχεία είναι πράγματι ένα από τα προσφερόμενα και δεν έχουν απενεργοποιηθεί. Με τη μέθοδο `getRawValue()` μπορούν να ληφθούν τα υποβληθέντα στοιχεία χωρίς αυτόν τον σημαντικό έλεγχο. -Όταν ορίζονται προεπιλεγμένες τιμές, ελέγχει επίσης ότι είναι ένα από τα προσφερόμενα στοιχεία, διαφορετικά πετάει μια εξαίρεση. Αυτός ο έλεγχος μπορεί να απενεργοποιηθεί με τη μέθοδο `checkDefaultValue(false)`. +Κατά τον ορισμό των προεπιλεγμένων επιλεγμένων στοιχείων, ελέγχει επίσης ότι είναι ένα από τα προσφερόμενα, διαφορετικά προκαλεί εξαίρεση. Αυτός ο έλεγχος μπορεί να απενεργοποιηθεί με τη χρήση του `checkDefaultValue(false)`. +Εάν υποβάλλετε τη φόρμα με τη μέθοδο `GET`, μπορείτε να επιλέξετε έναν πιο συμπαγή τρόπο μετάδοσης δεδομένων, ο οποίος εξοικονομεί μέγεθος στο query string. Ενεργοποιείται ορίζοντας το HTML attribute της φόρμας: + +```php +$form->setHtmlAttribute('data-nette-compact'); +``` -addRadioList(string|int $name, $label=null, array $items=null): RadioList .[method] -=================================================================================== -Προσθέτει κουμπιά επιλογής (κλάση [RadioList |api:Nette\Forms\Controls\RadioList]). Επιστρέφει το κλειδί του επιλεγμένου στοιχείου ή `null` αν ο χρήστης δεν επέλεξε τίποτα. Η μέθοδος `getSelectedItem()` επιστρέφει μια τιμή αντί για ένα κλειδί. +addRadioList(string|int $name, $label=null, ?array $items=null): RadioList .[method] +==================================================================================== + +Προσθέτει κουμπιά επιλογής (κλάση [RadioList |api:Nette\Forms\Controls\RadioList]). Επιστρέφει το κλειδί του επιλεγμένου στοιχείου, ή `null`, εάν ο χρήστης δεν επέλεξε τίποτα. Η μέθοδος `getSelectedItem()` επιστρέφει την τιμή αντί για το κλειδί. ```php $sex = [ - 'm' => 'male', - 'f' => 'female', + 'm' => 'άνδρας', + 'f' => 'γυναίκα', ]; -$form->addRadioList('gender', 'Gender:', $sex); +$form->addRadioList('gender', 'Φύλο:', $sex); ``` -Περνάμε τον πίνακα των στοιχείων ως τρίτη παράμετρο, ή με τη μέθοδο `setItems()`. +Τον πίνακα των προσφερόμενων στοιχείων τον παραδίδουμε ως τρίτη παράμετρο ή με τη μέθοδο `setItems()`. -Μπορείτε να χρησιμοποιήσετε `setDisabled(['m'])` για να απενεργοποιήσετε μεμονωμένα στοιχεία. +Με τη χρήση του `setDisabled(['m', 'f'])` μπορούν να απενεργοποιηθούν μεμονωμένα στοιχεία. -Το στοιχείο ελέγχει αυτόματα ότι δεν έχει γίνει πλαστογράφηση και ότι το επιλεγμένο στοιχείο είναι πράγματι ένα από τα προσφερόμενα και δεν έχει απενεργοποιηθεί. Η μέθοδος `getRawValue()` μπορεί να χρησιμοποιηθεί για την ανάκτηση του προσφερόμενου στοιχείου χωρίς αυτόν τον σημαντικό έλεγχο. +Το στοιχείο ελέγχει αυτόματα ότι δεν έχει γίνει πλαστογράφηση και ότι το επιλεγμένο στοιχείο είναι πράγματι ένα από τα προσφερόμενα και δεν έχει απενεργοποιηθεί. Με τη μέθοδο `getRawValue()` μπορεί να ληφθεί το υποβληθέν στοιχείο χωρίς αυτόν τον σημαντικό έλεγχο. -Όταν έχει οριστεί η προεπιλεγμένη τιμή, ελέγχει επίσης ότι είναι ένα από τα προσφερόμενα στοιχεία, διαφορετικά πετάει μια εξαίρεση. Αυτός ο έλεγχος μπορεί να απενεργοποιηθεί με τη μέθοδο `checkDefaultValue(false)`. +Κατά τον ορισμό του προεπιλεγμένου επιλεγμένου στοιχείου, ελέγχει επίσης ότι είναι ένα από τα προσφερόμενα, διαφορετικά προκαλεί εξαίρεση. Αυτός ο έλεγχος μπορεί να απενεργοποιηθεί με τη χρήση του `checkDefaultValue(false)`. -addSelect(string|int $name, $label=null, array $items=null): SelectBox .[method] -================================================================================ +addSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): SelectBox .[method] +================================================================================================== -Προσθέτει πλαίσιο επιλογής (κλάση [SelectBox |api:Nette\Forms\Controls\SelectBox]). Επιστρέφει το κλειδί του επιλεγμένου στοιχείου ή `null` αν ο χρήστης δεν επέλεξε τίποτα. Η μέθοδος `getSelectedItem()` επιστρέφει μια τιμή αντί για ένα κλειδί. +Προσθέτει ένα select box (κλάση [SelectBox |api:Nette\Forms\Controls\SelectBox]). Επιστρέφει το κλειδί του επιλεγμένου στοιχείου, ή `null`, εάν ο χρήστης δεν επέλεξε τίποτα. Η μέθοδος `getSelectedItem()` επιστρέφει την τιμή αντί για το κλειδί. ```php $countries = [ - 'CZ' => 'Czech republic', - 'SK' => 'Slovakia', - 'GB' => 'United Kingdom', + 'CZ' => 'Τσεχία', + 'SK' => 'Σλοβακία', + 'GR' => 'Ελλάδα', ]; -$form->addSelect('country', 'Country:', $countries) - ->setDefaultValue('SK'); +$form->addSelect('country', 'Χώρα:', $countries) + ->setDefaultValue('GR'); ``` -Περνάμε τον πίνακα των στοιχείων ως τρίτη παράμετρο ή με τη μέθοδο `setItems()`. Ο πίνακας στοιχείων μπορεί επίσης να είναι δισδιάστατος: +Τον πίνακα των προσφερόμενων στοιχείων τον παραδίδουμε ως τρίτη παράμετρο ή με τη μέθοδο `setItems()`. Τα στοιχεία μπορούν να είναι και δισδιάστατος πίνακας: ```php $countries = [ 'Europe' => [ - 'CZ' => 'Czech republic', - 'SK' => 'Slovakia', - 'GB' => 'United Kingdom', + 'CZ' => 'Τσεχία', + 'SK' => 'Σλοβακία', + 'GR' => 'Ελλάδα', ], - 'CA' => 'Canada', - 'US' => 'USA', - '?' => 'other', + 'CA' => 'Καναδάς', + 'US' => 'ΗΠΑ', + '?' => 'άλλη', ]; ``` -Για πλαίσια επιλογής, το πρώτο στοιχείο έχει συχνά ιδιαίτερη σημασία, χρησιμεύει ως κλήση προς δράση. Χρησιμοποιήστε τη μέθοδο `setPrompt()` για να προσθέσετε μια τέτοια εγγραφή. +Στα select boxes, συχνά το πρώτο στοιχείο έχει ειδική σημασία, χρησιμεύει ως προτροπή για δράση. Για την προσθήκη ενός τέτοιου στοιχείου χρησιμοποιείται η μέθοδος `setPrompt()`. ```php -$form->addSelect('country', 'Country:', $countries) - ->setPrompt('Pick a country'); +$form->addSelect('country', 'Χώρα:', $countries) + ->setPrompt('Επιλέξτε χώρα'); ``` -Μπορείτε να χρησιμοποιήσετε `setDisabled(['CZ', 'SK'])` για να απενεργοποιήσετε μεμονωμένα στοιχεία. +Με τη χρήση του `setDisabled(['CZ', 'SK'])` μπορούν να απενεργοποιηθούν μεμονωμένα στοιχεία. -Το στοιχείο ελέγχει αυτόματα ότι δεν έχει γίνει πλαστογράφηση και ότι το επιλεγμένο στοιχείο είναι πράγματι ένα από τα προσφερόμενα και δεν έχει απενεργοποιηθεί. Η μέθοδος `getRawValue()` μπορεί να χρησιμοποιηθεί για την ανάκτηση του προσφερόμενου στοιχείου χωρίς αυτόν τον σημαντικό έλεγχο. +Το στοιχείο ελέγχει αυτόματα ότι δεν έχει γίνει πλαστογράφηση και ότι το επιλεγμένο στοιχείο είναι πράγματι ένα από τα προσφερόμενα και δεν έχει απενεργοποιηθεί. Με τη μέθοδο `getRawValue()` μπορεί να ληφθεί το υποβληθέν στοιχείο χωρίς αυτόν τον σημαντικό έλεγχο. -Όταν έχει οριστεί η προεπιλεγμένη τιμή, ελέγχει επίσης ότι είναι ένα από τα προσφερόμενα στοιχεία, διαφορετικά πετάει μια εξαίρεση. Αυτός ο έλεγχος μπορεί να απενεργοποιηθεί με τη μέθοδο `checkDefaultValue(false)`. +Κατά τον ορισμό του προεπιλεγμένου επιλεγμένου στοιχείου, ελέγχει επίσης ότι είναι ένα από τα προσφερόμενα, διαφορετικά προκαλεί εξαίρεση. Αυτός ο έλεγχος μπορεί να απενεργοποιηθεί με τη χρήση του `checkDefaultValue(false)`. -addMultiSelect(string|int $name, $label=null, array $items=null): MultiSelectBox .[method] -========================================================================================== +addMultiSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): MultiSelectBox .[method] +============================================================================================================ -Προσθέτει πλαίσιο επιλογής πολλαπλών επιλογών (κλάση [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox]). Επιστρέφει τον πίνακα των κλειδιών των επιλεγμένων στοιχείων. Η μέθοδος `getSelectedItems()` επιστρέφει τιμές αντί για κλειδιά. +Προσθέτει ένα select box για την επιλογή πολλαπλών στοιχείων (κλάση [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox]). Επιστρέφει έναν πίνακα με τα κλειδιά των επιλεγμένων στοιχείων. Η μέθοδος `getSelectedItems()` επιστρέφει τις τιμές αντί για τα κλειδιά. ```php -$form->addMultiSelect('countries', 'Countries:', $countries); +$form->addMultiSelect('countries', 'Χώρες:', $countries); ``` -Παραδίδουμε τον πίνακα των στοιχείων ως τρίτη παράμετρο ή με τη μέθοδο `setItems()`. Ο πίνακας στοιχείων μπορεί επίσης να είναι δισδιάστατος. +Τον πίνακα των προσφερόμενων στοιχείων τον παραδίδουμε ως τρίτη παράμετρο ή με τη μέθοδο `setItems()`. Τα στοιχεία μπορούν να είναι και δισδιάστατος πίνακας. -Μπορείτε να χρησιμοποιήσετε `setDisabled(['CZ', 'SK'])` για να απενεργοποιήσετε μεμονωμένα στοιχεία. +Με τη χρήση του `setDisabled(['CZ', 'SK'])` μπορούν να απενεργοποιηθούν μεμονωμένα στοιχεία. -Το στοιχείο ελέγχει αυτόματα ότι δεν έχει γίνει πλαστογράφηση και ότι τα επιλεγμένα στοιχεία είναι πράγματι ένα από τα προσφερόμενα και δεν έχουν απενεργοποιηθεί. Η μέθοδος `getRawValue()` μπορεί να χρησιμοποιηθεί για να ανακτήσετε τα προσφερόμενα στοιχεία χωρίς αυτόν τον σημαντικό έλεγχο. +Το στοιχείο ελέγχει αυτόματα ότι δεν έχει γίνει πλαστογράφηση και ότι τα επιλεγμένα στοιχεία είναι πράγματι ένα από τα προσφερόμενα και δεν έχουν απενεργοποιηθεί. Με τη μέθοδο `getRawValue()` μπορούν να ληφθούν τα υποβληθέντα στοιχεία χωρίς αυτόν τον σημαντικό έλεγχο. -Όταν ορίζονται προεπιλεγμένες τιμές, ελέγχει επίσης ότι είναι ένα από τα προσφερόμενα στοιχεία, διαφορετικά πετάει μια εξαίρεση. Αυτός ο έλεγχος μπορεί να απενεργοποιηθεί με τη μέθοδο `checkDefaultValue(false)`. +Κατά τον ορισμό των προεπιλεγμένων επιλεγμένων στοιχείων, ελέγχει επίσης ότι είναι ένα από τα προσφερόμενα, διαφορετικά προκαλεί εξαίρεση. Αυτός ο έλεγχος μπορεί να απενεργοποιηθεί με τη χρήση του `checkDefaultValue(false)`. addUpload(string|int $name, $label=null): UploadControl .[method] ================================================================= -Προσθέτει πεδίο μεταφόρτωσης αρχείων (κλάση [UploadControl |api:Nette\Forms\Controls\UploadControl]). Επιστρέφει το αντικείμενο [FileUpload |http:request#FileUpload], ακόμη και αν ο χρήστης δεν έχει ανεβάσει αρχείο, κάτι που μπορεί να διαπιστωθεί από τη μέθοδο `FileUpload::hasFile()`. +Προσθέτει ένα πεδίο για τη μεταφόρτωση αρχείου (κλάση [UploadControl |api:Nette\Forms\Controls\UploadControl]). Επιστρέφει ένα αντικείμενο [FileUpload |http:request#FileUpload] ακόμα και στην περίπτωση που ο χρήστης δεν υπέβαλε κανένα αρχείο, κάτι που μπορεί να διαπιστωθεί με τη μέθοδο `FileUpload::hasFile()`. ```php $form->addUpload('avatar', 'Avatar:') - ->addRule($form::Image, 'Avatar must be JPEG, PNG, GIF or WebP') - ->addRule($form::MaxFileSize, 'Maximum size is 1 MB', 1024 * 1024); + ->addRule($form::Image, 'Το Avatar πρέπει να είναι JPEG, PNG, GIF, WebP ή AVIF.') + ->addRule($form::MaxFileSize, 'Το μέγιστο μέγεθος είναι 1 MB.', 1024 * 1024); ``` -Εάν το αρχείο δεν έχει μεταφορτωθεί σωστά, η φόρμα δεν υποβλήθηκε επιτυχώς και εμφανίζεται ένα σφάλμα. Δηλαδή, δεν είναι απαραίτητο να ελεγχθεί η μέθοδος `FileUpload::isOk()`. +Εάν το αρχείο δεν μεταφορτωθεί σωστά, η φόρμα δεν υποβάλλεται επιτυχώς και εμφανίζεται σφάλμα. Δηλαδή, κατά την επιτυχή υποβολή δεν χρειάζεται να επαληθεύσετε τη μέθοδο `FileUpload::isOk()`. -Μην εμπιστεύεστε το αρχικό όνομα αρχείου που επιστρέφεται από τη μέθοδο `FileUpload::getName()`, ένας πελάτης θα μπορούσε να στείλει ένα κακόβουλο όνομα αρχείου με σκοπό να καταστρέψει ή να χακάρει την εφαρμογή σας. +Ποτέ μην εμπιστεύεστε το αρχικό όνομα του αρχείου που επιστρέφεται από τη μέθοδο `FileUpload::getName()`, ο πελάτης θα μπορούσε να έχει στείλει ένα κακόβουλο όνομα αρχείου με σκοπό να βλάψει ή να χακάρει την εφαρμογή σας. -Οι κανόνες `MimeType` και `Image` ανιχνεύουν τον απαιτούμενο τύπο αρχείου ή εικόνας από την υπογραφή του. Η ακεραιότητα ολόκληρου του αρχείου δεν ελέγχεται. Μπορείτε να διαπιστώσετε αν μια εικόνα δεν είναι κατεστραμμένη, για παράδειγμα, προσπαθώντας να [τη φορτώσετε |http:request#toImage]. +Οι κανόνες `MimeType` και `Image` ανιχνεύουν τον απαιτούμενο τύπο βάσει της υπογραφής του αρχείου και δεν επαληθεύουν την ακεραιότητά του. Το αν μια εικόνα είναι κατεστραμμένη μπορεί να διαπιστωθεί, για παράδειγμα, προσπαθώντας να τη [φορτώσετε |http:request#toImage]. addMultiUpload(string|int $name, $label=null): UploadControl .[method] ====================================================================== -Προσθέτει πεδίο μεταφόρτωσης πολλαπλών αρχείων (κλάση [UploadControl |api:Nette\Forms\Controls\UploadControl]). Επιστρέφει έναν πίνακα αντικειμένων [FileUpload |http:request#FileUpload]. Η μέθοδος `FileUpload::hasFile()` θα επιστρέψει `true` για κάθε ένα από αυτά. +Προσθέτει ένα πεδίο για τη μεταφόρτωση πολλαπλών αρχείων ταυτόχρονα (κλάση [UploadControl |api:Nette\Forms\Controls\UploadControl]). Επιστρέφει έναν πίνακα αντικειμένων [FileUpload |http:request#FileUpload]. Η μέθοδος `FileUpload::hasFile()` σε καθένα από αυτά θα επιστρέφει `true`. ```php -$form->addMultiUpload('files', 'Files:') - ->addRule($form::MaxLength, 'A maximum of %d files can be uploaded', 10); +$form->addMultiUpload('files', 'Αρχεία:') + ->addRule($form::MaxLength, 'Μπορούν να μεταφορτωθούν το πολύ %d αρχεία', 10); ``` -Εάν ένα από τα αρχεία δεν μπορέσει να φορτωθεί σωστά, η φόρμα δεν υποβλήθηκε με επιτυχία και εμφανίζεται ένα σφάλμα. Δηλαδή, δεν είναι απαραίτητο να ελεγχθεί η μέθοδος `FileUpload::isOk()`. +Εάν κάποιο αρχείο δεν μεταφορτωθεί σωστά, η φόρμα δεν υποβάλλεται επιτυχώς και εμφανίζεται σφάλμα. Δηλαδή, κατά την επιτυχή υποβολή δεν χρειάζεται να επαληθεύσετε τη μέθοδο `FileUpload::isOk()`. + +Ποτέ μην εμπιστεύεστε τα αρχικά ονόματα των αρχείων που επιστρέφονται από τη μέθοδο `FileUpload::getName()`, ο πελάτης θα μπορούσε να έχει στείλει ένα κακόβουλο όνομα αρχείου με σκοπό να βλάψει ή να χακάρει την εφαρμογή σας. + +Οι κανόνες `MimeType` και `Image` ανιχνεύουν τον απαιτούμενο τύπο βάσει της υπογραφής του αρχείου και δεν επαληθεύουν την ακεραιότητά του. Το αν μια εικόνα είναι κατεστραμμένη μπορεί να διαπιστωθεί, για παράδειγμα, προσπαθώντας να τη [φορτώσετε |http:request#toImage]. + -Μην εμπιστεύεστε τα αρχικά ονόματα αρχείων που επιστρέφονται από τη μέθοδο `FileUpload::getName()`, ένας πελάτης θα μπορούσε να στείλει ένα κακόβουλο όνομα αρχείου με σκοπό να καταστρέψει ή να χακάρει την εφαρμογή σας. +addDate(string|int $name, $label=null): DateTimeControl .[method]{data-version:3.1.14} +====================================================================================== -Οι κανόνες `MimeType` και `Image` ανιχνεύουν τον απαιτούμενο τύπο αρχείου ή εικόνας από την υπογραφή του. Η ακεραιότητα ολόκληρου του αρχείου δεν ελέγχεται. Μπορείτε να διαπιστώσετε αν μια εικόνα δεν είναι κατεστραμμένη, για παράδειγμα, προσπαθώντας να [τη φορτώσετε |http:request#toImage]. +Προσθέτει ένα πεδίο που επιτρέπει στον χρήστη να εισάγει εύκολα μια ημερομηνία που αποτελείται από έτος, μήνα και ημέρα (κλάση [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). +Ως προεπιλεγμένη τιμή δέχεται είτε αντικείμενα που υλοποιούν το interface `DateTimeInterface`, ένα string με χρόνο, είτε έναν αριθμό που αντιπροσωπεύει UNIX timestamp. Το ίδιο ισχύει για τα ορίσματα των κανόνων `Min`, `Max` ή `Range`, τα οποία ορίζουν την ελάχιστη και μέγιστη επιτρεπόμενη ημερομηνία. -addHidden(string|int $name, string $default=null): HiddenField .[method] -======================================================================== +```php +$form->addDate('date', 'Ημερομηνία:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'Η ημερομηνία πρέπει να είναι τουλάχιστον ενός μηνός παλιά.', new DateTime('-1 month')); +``` -Προσθέτει κρυφό πεδίο (κλάση [HiddenField |api:Nette\Forms\Controls\HiddenField]). +Συνήθως επιστρέφει ένα αντικείμενο `DateTimeImmutable`, με τη μέθοδο `setFormat()` μπορείτε να καθορίσετε τη [μορφή κειμένου|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] ή timestamp: + +```php +$form->addDate('date', 'Ημερομηνία:') + ->setFormat('Y-m-d'); +``` + + +addTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=============================================================================================================== + +Προσθέτει ένα πεδίο που επιτρέπει στον χρήστη να εισάγει εύκολα έναν χρόνο που αποτελείται από ώρες, λεπτά και προαιρετικά δευτερόλεπτα (κλάση [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Ως προεπιλεγμένη τιμή δέχεται είτε αντικείμενα που υλοποιούν το interface `DateTimeInterface`, ένα string με χρόνο, είτε έναν αριθμό που αντιπροσωπεύει UNIX timestamp. Από αυτές τις εισόδους χρησιμοποιείται μόνο η πληροφορία του χρόνου, η ημερομηνία αγνοείται. Το ίδιο ισχύει για τα ορίσματα των κανόνων `Min`, `Max` ή `Range`, τα οποία ορίζουν τον ελάχιστο και μέγιστο επιτρεπόμενο χρόνο. Εάν η καθορισμένη ελάχιστη τιμή είναι υψηλότερη από τη μέγιστη, δημιουργείται ένα χρονικό εύρος που υπερβαίνει τα μεσάνυχτα. + +```php +$form->addTime('time', 'Ώρα:', withSeconds: true) + ->addRule($form::Range, 'Η ώρα πρέπει να είναι στο εύρος από %s έως %s.', ['12:30', '13:30']); +``` + +Συνήθως επιστρέφει ένα αντικείμενο `DateTimeImmutable` (με ημερομηνία 1 Ιανουαρίου του έτους 1), με τη μέθοδο `setFormat()` μπορείτε να καθορίσετε τη [μορφή κειμένου|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters]: + +```php +$form->addTime('time', 'Ώρα:') + ->setFormat('H:i'); +``` + + +addDateTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=================================================================================================================== + +Προσθέτει ένα πεδίο που επιτρέπει στον χρήστη να εισάγει εύκολα ημερομηνία και ώρα που αποτελείται από έτος, μήνα, ημέρα, ώρες, λεπτά και προαιρετικά δευτερόλεπτα (κλάση [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Ως προεπιλεγμένη τιμή δέχεται είτε αντικείμενα που υλοποιούν το interface `DateTimeInterface`, ένα string με χρόνο, είτε έναν αριθμό που αντιπροσωπεύει UNIX timestamp. Το ίδιο ισχύει για τα ορίσματα των κανόνων `Min`, `Max` ή `Range`, τα οποία ορίζουν την ελάχιστη και μέγιστη επιτρεπόμενη ημερομηνία. + +```php +$form->addDateTime('datetime', 'Ημερομηνία και ώρα:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'Η ημερομηνία πρέπει να είναι τουλάχιστον ενός μηνός παλιά.', new DateTime('-1 month')); +``` + +Συνήθως επιστρέφει ένα αντικείμενο `DateTimeImmutable`, με τη μέθοδο `setFormat()` μπορείτε να καθορίσετε τη [μορφή κειμένου|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] ή timestamp: + +```php +$form->addDateTime('datetime') + ->setFormat(DateTimeControl::FormatTimestamp); +``` + + +addColor(string|int $name, $label=null): ColorPicker .[method]{data-version:3.1.14} +=================================================================================== + +Προσθέτει ένα πεδίο για την επιλογή χρώματος (κλάση [ColorPicker |api:Nette\Forms\Controls\ColorPicker]). Το χρώμα είναι ένα string στη μορφή `#rrggbb`. Εάν ο χρήστης δεν κάνει επιλογή, επιστρέφεται το μαύρο χρώμα `#000000`. + +```php +$form->addColor('color', 'Χρώμα:') + ->setDefaultValue('#3C8ED7'); +``` + + +addHidden(string|int $name, ?string $default=null): HiddenField .[method] +========================================================================= + +Προσθέτει ένα κρυφό πεδίο (κλάση [HiddenField |api:Nette\Forms\Controls\HiddenField]). ```php $form->addHidden('userid'); ``` -Χρησιμοποιήστε το `setNullable()` για να το αλλάξετε ώστε να επιστρέφει `null` αντί για κενό αλφαριθμητικό. Η [addFilter() |validation#Modifying Input Values] σας επιτρέπει να αλλάξετε την υποβαλλόμενη τιμή. +Με τη χρήση του `setNullable()` μπορεί να οριστεί να επιστρέφει `null` αντί για κενό string. Η τροποποίηση της υποβληθείσας τιμής είναι δυνατή με το [addFilter() |validation#Τροποποίηση Εισόδου]. + +Παρόλο που το στοιχείο είναι κρυφό, είναι **σημαντικό να συνειδητοποιήσετε** ότι η τιμή μπορεί ακόμα να τροποποιηθεί ή να πλαστογραφηθεί από έναν εισβολέα. Πάντα επαληθεύετε και επικυρώνετε διεξοδικά όλες τις λαμβανόμενες τιμές στην πλευρά του διακομιστή για να αποφύγετε κινδύνους ασφαλείας που σχετίζονται με τη χειραγώγηση δεδομένων. addSubmit(string|int $name, $caption=null): SubmitButton .[method] ================================================================== -Προσθέτει κουμπί υποβολής (κλάση [SubmitButton |api:Nette\Forms\Controls\SubmitButton]). +Προσθέτει ένα κουμπί υποβολής (κλάση [SubmitButton |api:Nette\Forms\Controls\SubmitButton]). ```php -$form->addSubmit('submit', 'Register'); +$form->addSubmit('submit', 'Υποβολή'); ``` -Είναι δυνατόν να υπάρχουν περισσότερα από ένα κουμπιά υποβολής στη φόρμα: +Στη φόρμα είναι δυνατόν να υπάρχουν και περισσότερα κουμπιά υποβολής: ```php -$form->addSubmit('register', 'Register'); -$form->addSubmit('cancel', 'Cancel'); +$form->addSubmit('register', 'Εγγραφή'); +$form->addSubmit('cancel', 'Ακύρωση'); ``` -Για να μάθετε ποιο από αυτά έγινε κλικ, χρησιμοποιήστε: +Για να διαπιστώσετε σε ποιο από αυτά έγινε κλικ, χρησιμοποιήστε: ```php if ($form['register']->isSubmittedBy()) { @@ -282,48 +377,48 @@ if ($form['register']->isSubmittedBy()) { } ``` -Εάν δεν θέλετε να επικυρώνετε τη φόρμα όταν πατάτε ένα κουμπί υποβολής (όπως τα κουμπιά *Ακύρωση* ή *Προεπισκόπηση*), μπορείτε να το απενεργοποιήσετε με την [setValidationScope() |validation#Disabling Validation]. +Εάν δεν θέλετε να επικυρώσετε ολόκληρη τη φόρμα κατά το πάτημα του κουμπιού (για παράδειγμα, στα κουμπιά *Ακύρωση* ή *Προεπισκόπηση*), χρησιμοποιήστε το [setValidationScope() |validation#Απενεργοποίηση Επικύρωσης]. addButton(string|int $name, $caption): Button .[method] ======================================================= -Προσθέτει κουμπί (κλάση [Button |api:Nette\Forms\Controls\Button]) χωρίς λειτουργία submit. Είναι χρήσιμο για τη σύνδεση άλλων λειτουργιών με το id, για παράδειγμα μια ενέργεια JavaScript. +Προσθέτει ένα κουμπί (κλάση [Button |api:Nette\Forms\Controls\Button]), το οποίο δεν έχει λειτουργία υποβολής. Μπορεί επομένως να χρησιμοποιηθεί για κάποια άλλη λειτουργία, π.χ. κλήση μιας συνάρτησης JavaScript κατά το κλικ. ```php -$form->addButton('raise', 'Raise salary') +$form->addButton('raise', 'Αύξηση μισθού') ->setHtmlAttribute('onclick', 'raiseSalary()'); ``` -addImageButton(string|int $name, string $src=null, string $alt=null): ImageButton .[method] -=========================================================================================== +addImageButton(string|int $name, ?string $src=null, ?string $alt=null): ImageButton .[method] +============================================================================================= -Προσθέτει κουμπί υποβολής με τη μορφή εικόνας (κλάση [ImageButton |api:Nette\Forms\Controls\ImageButton]). +Προσθέτει ένα κουμπί υποβολής με τη μορφή εικόνας (κλάση [ImageButton |api:Nette\Forms\Controls\ImageButton]). ```php $form->addImageButton('submit', '/path/to/image'); ``` -Όταν χρησιμοποιείτε πολλαπλά κουμπιά υποβολής, μπορείτε να μάθετε ποιο από αυτά έχει πατηθεί με την εντολή `$form['submit']->isSubmittedBy()`. +Κατά τη χρήση πολλαπλών κουμπιών υποβολής, μπορείτε να διαπιστώσετε σε ποιο έγινε κλικ, χρησιμοποιώντας το `$form['submit']->isSubmittedBy()`. addContainer(string|int $name): Container .[method] =================================================== -Προσθέτει μια υπο-φόρμα (κλάση [Container |api:Nette\Forms\Container]), ή έναν περιέκτη, ο οποίος μπορεί να αντιμετωπιστεί με τον ίδιο τρόπο όπως μια φόρμα. Αυτό σημαίνει ότι μπορείτε να χρησιμοποιήσετε μεθόδους όπως `setDefaults()` ή `getValues()`. +Προσθέτει μια υποφόρμα (κλάση [Container|api:Nette\Forms\Container]), ή αλλιώς container, στο οποίο μπορούν να προστεθούν άλλα στοιχεία με τον ίδιο τρόπο που τα προσθέτουμε στη φόρμα. Λειτουργούν επίσης οι μέθοδοι `setDefaults()` ή `getValues()`. ```php $sub1 = $form->addContainer('first'); -$sub1->addText('name', 'Your name:'); +$sub1->addText('name', 'Το όνομά σας:'); $sub1->addEmail('email', 'Email:'); $sub2 = $form->addContainer('second'); -$sub2->addText('name', 'Your name:'); +$sub2->addText('name', 'Το όνομά σας:'); $sub2->addEmail('email', 'Email:'); ``` -Τα δεδομένα που αποστέλλονται επιστρέφονται στη συνέχεια ως πολυδιάστατη δομή: +Τα υποβληθέντα δεδομένα επιστρέφονται στη συνέχεια ως πολυδιάστατη δομή: ```php [ @@ -339,99 +434,101 @@ $sub2->addEmail('email', 'Email:'); ``` -Επισκόπηση των ρυθμίσεων .[#toc-overview-of-settings] -===================================================== +Επισκόπηση ρυθμίσεων +==================== -Για όλα τα στοιχεία μπορούμε να καλέσουμε τις ακόλουθες μεθόδους (δείτε την [τεκμηρίωση API |https://api.nette.org/forms/master/Nette/Forms/Controls.html] για μια πλήρη επισκόπηση): +Σε όλα τα στοιχεία μπορούμε να καλέσουμε τις ακόλουθες μεθόδους (πλήρης επισκόπηση στην [τεκμηρίωση API|https://api.nette.org/forms/master/Nette/Forms/Controls.html]): .[table-form-methods language-php] -| `setDefaultValue($value)` | θέτει την προεπιλεγμένη τιμή -| `getValue()` | λήψη τρέχουσας τιμής -| `setOmitted()` | [παραλειπόμενες τιμές |#omitted values] -| `setDisabled()` | [απενεργοποίηση εισόδων |#disabling inputs] +| `setDefaultValue($value)` | ορίζει την προεπιλεγμένη τιμή +| `getValue()` | λαμβάνει την τρέχουσα τιμή +| `setOmitted()` | [#Παράλειψη τιμής] +| `setDisabled()` | [#Απενεργοποίηση στοιχείων] Απόδοση: .[table-form-methods language-php] -| `setCaption($caption)`| αλλαγή της λεζάντας του στοιχείου -| `setTranslator($translator)` | Ορισμός [μεταφραστή |rendering#translating] -| `setHtmlAttribute($name, $value)` | ορίζει το [χαρακτηριστικό HTML |rendering#HTML attributes] του στοιχείου -| `setHtmlId($id)` | ορίζει το χαρακτηριστικό HTML `id` -| `setHtmlType($type)` | ορίζει το χαρακτηριστικό HTML `type` -| `setHtmlName($name)`| ορίζει το χαρακτηριστικό HTML `name` -| `setOption($key, $value)` | ορίζει [δεδομένα απόδοσης |rendering#Options] +| `setCaption($caption)` | αλλάζει την ετικέτα του στοιχείου +| `setTranslator($translator)` | ορίζει τον [μεταφραστή |rendering#Μετάφραση] +| `setHtmlAttribute($name, $value)` | ορίζει το [HTML attribute |rendering#Χαρακτηριστικά HTML] του στοιχείου +| `setHtmlId($id)` | ορίζει το HTML attribute `id` +| `setHtmlType($type)` | ορίζει το HTML attribute `type` +| `setHtmlName($name)` | ορίζει το HTML attribute `name` +| `setOption($key, $value)` | [ρυθμίσεις για απόδοση |rendering#Options] Επικύρωση: .[table-form-methods language-php] -| `setRequired()` | [υποχρεωτικό πεδίο |validation] -| `addRule()` | set [validation rule |validation#Rules] -| `addCondition()`, `addConditionOn()` | set [validation condition |validation#Conditions] -| `addError($message)`| [περνώντας μήνυμα σφάλματος |validation#processing-errors] +| `setRequired()` | [υποχρεωτικό στοιχείο |validation] +| `addRule()` | ορίζει τον [κανόνα επικύρωσης |validation#Κανόνες] +| `addCondition()`, `addConditionOn()` | ορίζει τη [συνθήκη επικύρωσης |validation#Συνθήκες] +| `addError($message)` | [παράδοση μηνύματος σφάλματος |validation#Σφάλματα κατά την Επεξεργασία] -Οι ακόλουθες μέθοδοι μπορούν να κληθούν για τα στοιχεία `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`: +Στα στοιχεία `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` μπορούν να κληθούν οι ακόλουθες μέθοδοι: .[table-form-methods language-php] -| `setNullable()`| καθορίζει αν η getValue() επιστρέφει `null` αντί για κενό αλφαριθμητικό. -| `setEmptyValue($value)` | ορίζει την ειδική τιμή που αντιμετωπίζεται ως κενή συμβολοσειρά -| `setMaxLength($length)`| ορίζει τον μέγιστο αριθμό επιτρεπόμενων χαρακτήρων -| `addFilter($filter)`| [τροποποίηση τιμών εισόδου |validation#Modifying Input Values] +| `setNullable()` | ορίζει αν το getValue() θα επιστρέψει `null` αντί για κενό string +| `setEmptyValue($value)` | ορίζει μια ειδική τιμή που θεωρείται κενό string +| `setMaxLength($length)` | ορίζει τον μέγιστο αριθμό επιτρεπόμενων χαρακτήρων +| `addFilter($filter)` | [επεξεργασία εισόδου |validation#Τροποποίηση Εισόδου] -Παραλειφθείσες τιμές .[#toc-omitted-values] -------------------------------------------- +Παράλειψη τιμής +=============== -Αν δεν σας ενδιαφέρει η τιμή που εισάγει ο χρήστης, μπορούμε να χρησιμοποιήσουμε το `setOmitted()` για να την παραλείψουμε από το αποτέλεσμα που παρέχεται από τη μέθοδο `$form->getValues​()` ή που περνάει στους χειριστές. Αυτό είναι κατάλληλο για διάφορους κωδικούς πρόσβασης για επαλήθευση, πεδία antispam κ.λπ. +Εάν η τιμή που συμπλήρωσε ο χρήστης δεν μας ενδιαφέρει, μπορούμε να την παραλείψουμε από το αποτέλεσμα της μεθόδου `$form->getValues()` ή από τα δεδομένα που παραδίδονται στους handlers με τη χρήση του `setOmitted()`. Αυτό είναι χρήσιμο για διάφορους κωδικούς ελέγχου, στοιχεία antispam κ.λπ. ```php -$form->addPassword('passwordVerify', 'Password again:') - ->setRequired('Fill your password again to check for typo') - ->addRule($form::Equal, 'Password mismatch', $form['password']) +$form->addPassword('passwordVerify', 'Κωδικός για έλεγχο:') + ->setRequired('Παρακαλώ εισάγετε τον κωδικό ξανά για έλεγχο') + ->addRule($form::Equal, 'Οι κωδικοί δεν ταιριάζουν', $form['password']) ->setOmitted(); ``` -Απενεργοποίηση εισόδων .[#toc-disabling-inputs] ------------------------------------------------ +Απενεργοποίηση στοιχείων +======================== -Για να απενεργοποιήσετε μια είσοδο, μπορείτε να καλέσετε το `setDisabled()`. Ένα τέτοιο πεδίο δεν μπορεί να επεξεργαστεί από τον χρήστη. +Τα στοιχεία μπορούν να απενεργοποιηθούν με τη χρήση του `setDisabled()`. Ένα τέτοιο στοιχείο δεν μπορεί να επεξεργαστεί ο χρήστης. ```php -$form->addText('username', 'User name:') +$form->addText('username', 'Όνομα χρήστη:') ->setDisabled(); ``` -Σημειώστε ότι το πρόγραμμα περιήγησης δεν στέλνει καθόλου τα απενεργοποιημένα πεδία στο διακομιστή, οπότε δεν θα τα βρείτε καν στα δεδομένα που επιστρέφει η συνάρτηση `$form->getValues()`. +Τα απενεργοποιημένα στοιχεία ο περιηγητής δεν τα στέλνει καθόλου στον διακομιστή, επομένως δεν θα τα βρείτε ούτε στα δεδομένα που επιστρέφει η συνάρτηση `$form->getValues()`. Ωστόσο, εάν ορίσετε `setOmitted(false)`, το Nette θα συμπεριλάβει την προεπιλεγμένη τους τιμή σε αυτά τα δεδομένα. -Εάν ορίζετε μια προεπιλεγμένη τιμή για ένα πεδίο, πρέπει να το κάνετε μόνο αφού το απενεργοποιήσετε: +Κατά την κλήση του `setDisabled()`, για λόγους ασφαλείας **διαγράφεται η τιμή του στοιχείου**. Εάν ορίζετε μια προεπιλεγμένη τιμή, είναι απαραίτητο να το κάνετε μετά την απενεργοποίησή του: ```php -$form->addText('username', 'User name:') +$form->addText('username', 'Όνομα χρήστη:') ->setDisabled() ->setDefaultValue($userName); ``` +Μια εναλλακτική λύση στα απενεργοποιημένα στοιχεία είναι τα στοιχεία με το HTML attribute `readonly`, τα οποία ο περιηγητής στέλνει στον διακομιστή. Παρόλο που το στοιχείο είναι μόνο για ανάγνωση, είναι **σημαντικό να συνειδητοποιήσετε** ότι η τιμή του μπορεί ακόμα να τροποποιηθεί ή να πλαστογραφηθεί από έναν εισβολέα. + -Προσαρμοσμένα στοιχεία ελέγχου .[#toc-custom-controls] -====================================================== +Προσαρμοσμένα στοιχεία +====================== -Εκτός από το ευρύ φάσμα των ενσωματωμένων στοιχείων ελέγχου της φόρμας, μπορείτε να προσθέσετε προσαρμοσμένα στοιχεία ελέγχου στη φόρμα ως εξής: +Εκτός από την ευρεία γκάμα ενσωματωμένων στοιχείων φόρμας, μπορείτε να προσθέσετε προσαρμοσμένα στοιχεία στη φόρμα με αυτόν τον τρόπο: ```php -$form->addComponent(new DateInput('Date:'), 'date'); -// εναλλακτική σύνταξη: Ημερομηνία:'), +$form->addComponent(new DateInput('Ημερομηνία:'), 'date'); +// εναλλακτική σύνταξη: $form['date'] = new DateInput('Ημερομηνία:'); ``` .[note] -Η φόρμα είναι απόγονος της κλάσης [Container | component-model:#Container] και τα στοιχεία είναι απόγονοι της κλάσης [Component | component-model:#Component]. +Η φόρμα είναι απόγονος της κλάσης [Container |component-model:#Container] και τα επιμέρους στοιχεία είναι απόγονοι του [Component |component-model:#Component]. -Υπάρχει τρόπος ορισμού νέων μεθόδων φόρμας για την προσθήκη προσαρμοσμένων στοιχείων (π.χ. `$form->addZip()`). Αυτές είναι οι λεγόμενες μέθοδοι επέκτασης. Το μειονέκτημα είναι ότι οι υποδείξεις κώδικα στους συντάκτες δεν θα λειτουργούν για αυτές. +Υπάρχει ένας τρόπος να ορίσετε νέες μεθόδους φόρμας που χρησιμεύουν για την προσθήκη προσαρμοσμένων στοιχείων (π.χ. `$form->addZip()`). Πρόκειται για τις λεγόμενες extension methods. Το μειονέκτημα είναι ότι η αυτόματη συμπλήρωση στους επεξεργαστές δεν θα λειτουργεί για αυτές. ```php use Nette\Forms\Container; -// προσθέτει τη μέθοδο addZip(string $name, string $label = null) -Container::extensionMethod('addZip', function (Container $form, string $name, string $label = null) { +// προσθέτουμε τη μέθοδο addZip(string $name, ?string $label = null) +Container::extensionMethod('addZip', function (Container $form, string $name, ?string $label = null) { return $form->addText($name, $label) - ->addRule($form::Pattern, 'At least 5 numbers', '[0-9]{5}'); + ->addRule($form::Pattern, 'Τουλάχιστον 5 αριθμοί', '[0-9]{5}'); }); // χρήση @@ -439,10 +536,10 @@ $form->addZip('zip', 'ZIP code:'); ``` -Πεδία χαμηλού επιπέδου .[#toc-low-level-fields] -=============================================== +Στοιχεία χαμηλού επιπέδου +========================= -Για να προσθέσετε ένα στοιχείο στη φόρμα, δεν χρειάζεται να καλέσετε το `$form->addXyz()`. Αντ' αυτού, τα στοιχεία της φόρμας μπορούν να εισαχθούν αποκλειστικά σε πρότυπα. Αυτό είναι χρήσιμο εάν, για παράδειγμα, πρέπει να δημιουργήσετε δυναμικά στοιχεία: +Μπορούν να χρησιμοποιηθούν και στοιχεία που γράφουμε μόνο στο template και δεν τα προσθέτουμε στη φόρμα με κάποια από τις μεθόδους `$form->addXyz()`. Για παράδειγμα, όταν εμφανίζουμε εγγραφές από τη βάση δεδομένων και δεν ξέρουμε εκ των προτέρων πόσες θα είναι και ποια θα είναι τα ID τους, και θέλουμε σε κάθε γραμμή να εμφανίσουμε ένα checkbox ή ένα radio button, αρκεί να το κωδικοποιήσουμε στο template: ```latte {foreach $items as $item} @@ -450,13 +547,13 @@ $form->addZip('zip', 'ZIP code:'); {/foreach} ``` -Μετά την υποβολή, μπορείτε να ανακτήσετε τις τιμές: +Και μετά την υποβολή, βρίσκουμε την τιμή: ```php $data = $form->getHttpData($form::DataText, 'sel[]'); $data = $form->getHttpData($form::DataText | $form::DataKeys, 'sel[]'); ``` -Στην πρώτη παράμετρο, καθορίζετε τον τύπο του στοιχείου (`DataFile` για το `type=file`, `DataLine` για μονογραμμικές εισόδους όπως `text`, `password` ή `email` και `DataText` για τις υπόλοιπες). Η δεύτερη παράμετρος αντιστοιχεί στο χαρακτηριστικό HTML `name`. Εάν πρέπει να διατηρήσετε τα κλειδιά, μπορείτε να συνδυάσετε την πρώτη παράμετρο με το `DataKeys`. Αυτό είναι χρήσιμο για τα `select`, `radioList` ή `checkboxList`. +όπου η πρώτη παράμετρος είναι ο τύπος του στοιχείου (`DataFile` για `type=file`, `DataLine` για εισόδους μίας γραμμής όπως `text`, `password`, `email` κ.λπ. και `DataText` για όλα τα υπόλοιπα) και η δεύτερη παράμετρος `sel[]` αντιστοιχεί στο HTML attribute name. Μπορούμε να συνδυάσουμε τον τύπο του στοιχείου με την τιμή `DataKeys`, η οποία διατηρεί τα κλειδιά των στοιχείων. Αυτό είναι ιδιαίτερα χρήσιμο για `select`, `radioList` και `checkboxList`. -Το `getHttpData()` επιστρέφει καθαρισμένη είσοδο. Σε αυτή την περίπτωση, θα είναι πάντα πίνακας έγκυρων συμβολοσειρών UTF-8, ανεξάρτητα από το τι επιτιθέμενος στέλνει από τη φόρμα. Είναι μια εναλλακτική λύση για να εργαστείτε απευθείας με το `$_POST` ή το `$_GET`, αν θέλετε να λαμβάνετε ασφαλή δεδομένα. +Το ουσιαστικό είναι ότι το `getHttpData()` επιστρέφει μια απολυμασμένη τιμή, σε αυτή την περίπτωση θα είναι πάντα ένας πίνακας έγκυρων UTF-8 strings, ανεξάρτητα από το τι θα προσπαθούσε να υποβάλει ένας εισβολέας στον διακομιστή. Πρόκειται για μια αναλογία της άμεσης εργασίας με το `$_POST` ή το `$_GET`, αλλά με τη σημαντική διαφορά ότι επιστρέφει πάντα καθαρά δεδομένα, όπως είστε συνηθισμένοι με τα τυπικά στοιχεία των φορμών Nette. diff --git a/forms/el/in-presenter.texy b/forms/el/in-presenter.texy index 9221cee979..7cc037f5d3 100644 --- a/forms/el/in-presenter.texy +++ b/forms/el/in-presenter.texy @@ -1,36 +1,36 @@ -Φόρμες σε Παρουσιαστές -********************** +Φόρμες στους presenters +*********************** .[perex] -Η Nette Forms διευκολύνει δραματικά τη δημιουργία και την επεξεργασία φορμών ιστού. Σε αυτό το κεφάλαιο, θα μάθετε πώς να χρησιμοποιείτε φόρμες μέσα στους παρουσιαστές. +Οι Nette Forms διευκολύνουν κατά πολύ τη δημιουργία και την επεξεργασία φορμών ιστού. Σε αυτό το κεφάλαιο, θα μάθετε πώς να χρησιμοποιείτε φόρμες μέσα στους presenters. -Αν σας ενδιαφέρει να τις χρησιμοποιήσετε εντελώς αυτόνομα χωρίς το υπόλοιπο πλαίσιο, υπάρχει ένας οδηγός για [αυτόνομες φόρμες |standalone]. +Αν ενδιαφέρεστε για το πώς να τις χρησιμοποιήσετε εντελώς αυτόνομα χωρίς το υπόλοιπο framework, ο οδηγός για [αυτόνομη χρήση |standalone] είναι για εσάς. -Πρώτη φόρμα .[#toc-first-form] -============================== +Η πρώτη φόρμα +============= -Θα προσπαθήσουμε να γράψουμε μια απλή φόρμα εγγραφής. Ο κώδικάς της θα μοιάζει ως εξής: +Ας δοκιμάσουμε να γράψουμε μια απλή φόρμα εγγραφής. Ο κώδικάς της θα είναι ο εξής: ```php use Nette\Application\UI\Form; $form = new Form; -$form->addText('name', 'Name:'); -$form->addPassword('password', 'Password:'); -$form->addSubmit('send', 'Sign up'); +$form->addText('name', 'Όνομα:'); +$form->addPassword('password', 'Κωδικός πρόσβασης:'); +$form->addSubmit('send', 'Εγγραφή'); $form->onSuccess[] = [$this, 'formSucceeded']; ``` -και στο πρόγραμμα περιήγησης το αποτέλεσμα θα πρέπει να μοιάζει με αυτό: +και στον περιηγητή θα εμφανιστεί έτσι: -[* form-en.webp *] +[* form-cs.webp *] -Η φόρμα στον παρουσιαστή είναι ένα αντικείμενο της κλάσης `Nette\Application\UI\Form`, ο προκάτοχός της `Nette\Forms\Form` προορίζεται για αυτόνομη χρήση. Προσθέσαμε σε αυτήν τα πεδία name, password και το κουμπί αποστολής. Τέλος, η γραμμή με το `$form->onSuccess` λέει ότι μετά την υποβολή και την επιτυχή επικύρωση, θα πρέπει να κληθεί η μέθοδος `$this->formSucceeded()`. +Μια φόρμα σε έναν presenter είναι ένα αντικείμενο της κλάσης `Nette\Application\UI\Form`, ο προκάτοχός της `Nette\Forms\Form` προορίζεται για αυτόνομη χρήση. Προσθέσαμε σε αυτήν τα λεγόμενα στοιχεία όνομα, κωδικό πρόσβασης και ένα κουμπί υποβολής. Και τέλος, η γραμμή με το `$form->onSuccess` λέει ότι μετά την υποβολή και την επιτυχή επικύρωση, πρέπει να κληθεί η μέθοδος `$this->formSucceeded()`. -Από τη σκοπιά του παρουσιαστή, η φόρμα είναι ένα κοινό συστατικό. Ως εκ τούτου, αντιμετωπίζεται ως συστατικό και ενσωματώνεται στον παρουσιαστή χρησιμοποιώντας τη [μέθοδο factory |application:components#Factory Methods]. Αυτό θα έχει την εξής μορφή: +Από την οπτική γωνία του presenter, η φόρμα είναι ένα συνηθισμένο component. Επομένως, αντιμετωπίζεται ως component και την ενσωματώνουμε στον presenter χρησιμοποιώντας [factory methods |application:components#Μέθοδοι Εργοστασίου]. Θα μοιάζει κάπως έτσι: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} use Nette; use Nette\Application\UI\Form; @@ -39,103 +39,100 @@ class HomePresenter extends Nette\Application\UI\Presenter protected function createComponentRegistrationForm(): Form { $form = new Form; - $form->addText('name', 'Name:'); - $form->addPassword('password', 'Password:'); - $form->addSubmit('send', 'Sign up'); + $form->addText('name', 'Όνομα:'); + $form->addPassword('password', 'Κωδικός πρόσβασης:'); + $form->addSubmit('send', 'Εγγραφή'); $form->onSuccess[] = [$this, 'formSucceeded']; return $form; } public function formSucceeded(Form $form, $data): void { - // εδώ θα επεξεργαστούμε τα δεδομένα που αποστέλλονται από τη φόρμα - // $data->name περιέχει όνομα - // $data->password περιέχει κωδικό πρόσβασης - $this->flashMessage('You have successfully signed up.'); + // εδώ επεξεργαζόμαστε τα δεδομένα που υποβλήθηκαν από τη φόρμα + // το $data->name περιέχει το όνομα + // το $data->password περιέχει τον κωδικό πρόσβασης + $this->flashMessage('Εγγραφήκατε με επιτυχία.'); $this->redirect('Home:'); } } ``` -Και η απόδοση στο πρότυπο γίνεται με τη χρήση της ετικέτας `{control}`: +Και στο template, αποδίδουμε τη φόρμα με την ετικέτα `{control}`: -```latte .{file:app/Presenters/templates/Home/default.latte} -<h1>Registration</h1> +```latte .{file:app/Presentation/Home/default.latte} +<h1>Εγγραφή</h1> {control registrationForm} ``` -Και αυτό είναι όλο :-) Έχουμε μια λειτουργική και απόλυτα [ασφαλής |#Vulnerability Protection] φόρμα. +Και αυτό είναι όλο :-) Έχουμε μια λειτουργική και τέλεια [ασφαλή |#Προστασία από ευπάθειες] φόρμα. -Τώρα πιθανόν να σκέφτεστε ότι ήταν πολύ γρήγορα, αναρωτιέστε πώς είναι δυνατόν να καλείται η μέθοδος `formSucceeded()` και ποιες είναι οι παράμετροι που παίρνει. Σίγουρα, έχετε δίκιο, αυτό αξίζει μια εξήγηση. +Και τώρα πιθανότατα σκέφτεστε ότι αυτό ήταν πολύ γρήγορο, αναρωτιέστε πώς είναι δυνατόν να κληθεί η μέθοδος `formSucceeded()` και ποιες είναι οι παράμετροι που λαμβάνει. Σίγουρα, έχετε δίκιο, αυτό αξίζει εξήγηση. -Η Nette καταλήγει σε έναν ωραίο μηχανισμό, τον οποίο ονομάζουμε [Hollywood style |application:components#Hollywood style]. Αντί να χρειάζεται να ρωτάτε διαρκώς αν έχει συμβεί κάτι ("υποβλήθηκε η φόρμα;", "υποβλήθηκε έγκυρα;" ή "δεν πλαστογραφήθηκε;"), λέτε στο πλαίσιο "όταν η φόρμα ολοκληρωθεί έγκυρα, καλέστε αυτή τη μέθοδο" και αφήνετε την περαιτέρω εργασία σε αυτό. Αν προγραμματίζετε σε JavaScript, είστε εξοικειωμένοι με αυτό το στυλ προγραμματισμού. Γράφετε συναρτήσεις που καλούνται όταν συμβαίνει ένα συγκεκριμένο [γεγονός |nette:glossary#Events]. Και η γλώσσα περνάει σε αυτές τα κατάλληλα ορίσματα. +Η Nette εισάγει έναν φρέσκο μηχανισμό, τον οποίο ονομάζουμε [Hollywood style |application:components#Hollywood Style]. Αντί εσείς, ως προγραμματιστής, να πρέπει συνεχώς να ρωτάτε αν συνέβη κάτι («υποβλήθηκε η φόρμα;», «υποβλήθηκε έγκυρα;» και «δεν παραποιήθηκε;»), λέτε στο framework «όταν η φόρμα συμπληρωθεί έγκυρα, κάλεσε αυτή τη μέθοδο» και αφήνετε την υπόλοιπη δουλειά σε αυτό. Αν προγραμματίζετε σε JavaScript, αυτό το στυλ προγραμματισμού σας είναι πολύ οικείο. Γράφετε συναρτήσεις που καλούνται όταν συμβεί ένα συγκεκριμένο [γεγονός |nette:glossary#Events]. Και η γλώσσα τους περνά τα κατάλληλα ορίσματα. -Με αυτόν τον τρόπο είναι δομημένος ο παραπάνω κώδικας του παρουσιαστή. Η συστοιχία `$form->onSuccess` αντιπροσωπεύει τη λίστα με τα callbacks της PHP που θα καλέσει η Nette όταν η φόρμα υποβληθεί και συμπληρωθεί σωστά. -Μέσα [στον κύκλο ζωής του παρουσιαστή |application:presenters#Life Cycle of Presenter] είναι ένα λεγόμενο σήμα, οπότε καλούνται μετά τη μέθοδο `action*` και πριν τη μέθοδο `render*`. -Και περνάει σε κάθε callback την ίδια τη φόρμα στην πρώτη παράμετρο και τα δεδομένα που αποστέλλονται ως αντικείμενο [ArrayHash |utils:arrays#ArrayHash] στη δεύτερη. Μπορείτε να παραλείψετε την πρώτη παράμετρο αν δεν χρειάζεστε το αντικείμενο της φόρμας. Η δεύτερη παράμετρος μπορεί να είναι ακόμη πιο χρήσιμη, αλλά γι' αυτό [αργότερα |#Mapping to Classes]. +Ακριβώς έτσι είναι δομημένος και ο παραπάνω κώδικας του presenter. Ο πίνακας `$form->onSuccess` αντιπροσωπεύει μια λίστα από PHP callbacks που η Nette καλεί τη στιγμή που η φόρμα υποβάλλεται και συμπληρώνεται σωστά (δηλαδή είναι έγκυρη). Στο πλαίσιο του [κύκλου ζωής του presenter |application:presenters#Κύκλος ζωής του presenter] πρόκειται για ένα λεγόμενο σήμα, οπότε καλούνται μετά τη μέθοδο `action*` και πριν από τη μέθοδο `render*`. Και σε κάθε callback, περνά ως πρώτη παράμετρο την ίδια τη φόρμα και ως δεύτερη τα υποβληθέντα δεδομένα με τη μορφή ενός αντικειμένου [ArrayHash |utils:arrays#ArrayHash]. Μπορείτε να παραλείψετε την πρώτη παράμετρο αν δεν χρειάζεστε το αντικείμενο της φόρμας. Και η δεύτερη παράμετρος μπορεί να είναι πιο έξυπνη, αλλά γι' αυτό θα μιλήσουμε [αργότερα |#Αντιστοίχιση σε κλάσεις]. -Το αντικείμενο `$data` περιέχει τις ιδιότητες `name` και `password` με τα δεδομένα που εισήγαγε ο χρήστης. Συνήθως στέλνουμε τα δεδομένα απευθείας για περαιτέρω επεξεργασία, η οποία μπορεί να είναι, για παράδειγμα, η εισαγωγή στη βάση δεδομένων. Ωστόσο, μπορεί να προκύψει κάποιο σφάλμα κατά την επεξεργασία, για παράδειγμα, το όνομα χρήστη είναι ήδη κατειλημμένο. Σε αυτή την περίπτωση, περνάμε το σφάλμα πίσω στη φόρμα χρησιμοποιώντας το `addError()` και αφήνουμε να ξανασχεδιαστεί, με ένα μήνυμα σφάλματος: +Το αντικείμενο `$data` περιέχει τα κλειδιά `name` και `password` με τα δεδομένα που συμπλήρωσε ο χρήστης. Συνήθως, στέλνουμε αμέσως τα δεδομένα για περαιτέρω επεξεργασία, η οποία μπορεί να είναι, για παράδειγμα, η εισαγωγή στη βάση δεδομένων. Ωστόσο, κατά την επεξεργασία μπορεί να προκύψει σφάλμα, για παράδειγμα, το όνομα χρήστη είναι ήδη κατειλημμένο. Σε αυτή την περίπτωση, επιστρέφουμε το σφάλμα στη φόρμα χρησιμοποιώντας την `addError()` και την αφήνουμε να αποδοθεί ξανά, μαζί με το μήνυμα σφάλματος. ```php -$form->addError('Sorry, username is already in use.'); +$form->addError('Λυπούμαστε, το όνομα χρήστη χρησιμοποιείται ήδη.'); ``` -Εκτός από το `onSuccess`, υπάρχει και το `onSubmit`: τα callbacks καλούνται πάντα μετά την υποβολή της φόρμας, ακόμη και αν αυτή δεν έχει συμπληρωθεί σωστά. Και τέλος `onError`: τα callbacks καλούνται μόνο αν η υποβολή δεν είναι έγκυρη. Καλούνται ακόμη και αν ακυρώσουμε τη φόρμα στο `onSuccess` ή στο `onSubmit` χρησιμοποιώντας το `addError()`. +Εκτός από το `onSuccess`, υπάρχει επίσης το `onSubmit`: τα callbacks καλούνται πάντα μετά την υποβολή της φόρμας, ακόμη και αν δεν έχει συμπληρωθεί σωστά. Και επίσης το `onError`: τα callbacks καλούνται μόνο αν η υποβολή δεν είναι έγκυρη. Καλούνται ακόμη και αν στο `onSuccess` ή στο `onSubmit` ακυρώσουμε την εγκυρότητα της φόρμας χρησιμοποιώντας την `addError()`. -Μετά την επεξεργασία της φόρμας, θα κάνουμε ανακατεύθυνση στην επόμενη σελίδα. Αυτό αποτρέπει την ακούσια επανυποβολή της φόρμας με το πάτημα του κουμπιού *refresh*, *back*, ή την μετακίνηση του ιστορικού του προγράμματος περιήγησης. +Μετά την επεξεργασία της φόρμας, ανακατευθύνουμε σε άλλη σελίδα. Αυτό αποτρέπει την ακούσια επανυποβολή της φόρμας με το κουμπί *ανανέωση*, *πίσω* ή με την κίνηση στο ιστορικό του περιηγητή. -Δοκιμάστε να προσθέσετε περισσότερα [στοιχεία ελέγχου φόρμας |controls]. +Δοκιμάστε να προσθέσετε και άλλα [στοιχεία φόρμας|controls]. -Πρόσβαση στα στοιχεία ελέγχου .[#toc-access-to-controls] -======================================================== +Πρόσβαση στα στοιχεία +===================== -Η φόρμα είναι ένα συστατικό του παρουσιαστή, στην περίπτωσή μας με το όνομα `registrationForm` (από το όνομα της μεθόδου κατασκευής `createComponentRegistrationForm`), οπότε οπουδήποτε στον παρουσιαστή μπορείτε να φτάσετε στη φόρμα χρησιμοποιώντας: +Η φόρμα είναι ένα component του presenter, στην περίπτωσή μας ονομάζεται `registrationForm` (από το όνομα της factory method `createComponentRegistrationForm`), οπότε οπουδήποτε στον presenter μπορείτε να αποκτήσετε πρόσβαση στη φόρμα χρησιμοποιώντας: ```php $form = $this->getComponent('registrationForm'); -// εναλλακτική σύνταξη: Φόρμα εγγραφής'], +// εναλλακτική σύνταξη: $form = $this['registrationForm']; ``` -Επίσης, τα μεμονωμένα στοιχεία ελέγχου της φόρμας είναι συστατικά, οπότε μπορείτε να έχετε πρόσβαση σε αυτά με τον ίδιο τρόπο: +Τα μεμονωμένα στοιχεία της φόρμας είναι επίσης components, επομένως μπορείτε να αποκτήσετε πρόσβαση σε αυτά με τον ίδιο τρόπο: ```php -$input = $form->getComponent('name'); // ή $input = $form['name'], -$button = $form->getComponent('send'); // ή $button = $form['send'], +$input = $form->getComponent('name'); // ή $input = $form['name']; +$button = $form->getComponent('send'); // ή $button = $form['send']; ``` -Τα στοιχεία ελέγχου αφαιρούνται με τη χρήση unset: +Τα στοιχεία αφαιρούνται χρησιμοποιώντας το unset: ```php unset($form['name']); ``` -Κανόνες επικύρωσης .[#toc-validation-rules] -=========================================== +Κανόνες επικύρωσης +================== -Η λέξη *valid* χρησιμοποιήθηκε αρκετές φορές, αλλά η φόρμα δεν έχει ακόμη κανόνες επικύρωσης. Ας το διορθώσουμε. +Αναφέρθηκε η λέξη *έγκυρη*, αλλά η φόρμα δεν έχει ακόμη κανόνες επικύρωσης. Ας το διορθώσουμε αυτό. -Το όνομα θα είναι υποχρεωτικό, οπότε θα το επισημάνουμε με τη μέθοδο `setRequired()`, της οποίας το όρισμα είναι το κείμενο του μηνύματος λάθους που θα εμφανιστεί αν ο χρήστης δεν το συμπληρώσει. Αν δεν δοθεί κανένα όρισμα, χρησιμοποιείται το προεπιλεγμένο μήνυμα σφάλματος. +Το όνομα θα είναι υποχρεωτικό, γι' αυτό το επισημαίνουμε με τη μέθοδο `setRequired()`, το όρισμα της οποίας είναι το κείμενο του μηνύματος σφάλματος που θα εμφανιστεί εάν ο χρήστης δεν συμπληρώσει το όνομα. Εάν δεν παρέχουμε όρισμα, θα χρησιμοποιηθεί το προεπιλεγμένο μήνυμα σφάλματος. ```php -$form->addText('name', 'Name:') - ->setRequired('Please fill your name.'); +$form->addText('name', 'Όνομα:') + ->setRequired('Παρακαλώ εισάγετε ένα όνομα'); ``` -Δοκιμάστε να στείλετε τη φόρμα χωρίς να έχει συμπληρωθεί το όνομα και θα δείτε ότι θα εμφανιστεί ένα μήνυμα σφάλματος και το πρόγραμμα περιήγησης ή ο διακομιστής θα την απορρίψει μέχρι να το συμπληρώσετε. +Δοκιμάστε να υποβάλετε τη φόρμα χωρίς να συμπληρώσετε το όνομα και θα δείτε ότι θα εμφανιστεί ένα μήνυμα σφάλματος και ο περιηγητής ή ο διακομιστής θα την απορρίπτει μέχρι να συμπληρώσετε το πεδίο. -Ταυτόχρονα, δεν θα μπορείτε να εξαπατήσετε το σύστημα πληκτρολογώντας μόνο κενά στην είσοδο, για παράδειγμα. Με κανέναν τρόπο. Η Nette κόβει αυτόματα τα κενά αριστερά και δεξιά. Δοκιμάστε το. Είναι κάτι που πρέπει να κάνετε πάντα με κάθε είσοδο μιας γραμμής, αλλά συχνά ξεχνιέται. Η Nette το κάνει αυτόματα. (Μπορείτε να προσπαθήσετε να ξεγελάσετε τις φόρμες και να στείλετε μια πολυγραμμή συμβολοσειρά ως όνομα. Ακόμα και σε αυτή την περίπτωση, η Nette δεν θα ξεγελαστεί και τα διαλείμματα γραμμής θα μετατραπούν σε κενά). +Ταυτόχρονα, δεν μπορείτε να ξεγελάσετε το σύστημα γράφοντας, για παράδειγμα, μόνο κενά στο πεδίο. Όχι. Η Nette αφαιρεί αυτόματα τα κενά στην αρχή και στο τέλος. Δοκιμάστε το. Είναι κάτι που πρέπει πάντα να κάνετε με κάθε input μίας γραμμής, αλλά συχνά ξεχνιέται. Η Nette το κάνει αυτόματα. (Μπορείτε να δοκιμάσετε να ξεγελάσετε τη φόρμα και να στείλετε μια συμβολοσειρά πολλών γραμμών ως όνομα. Ούτε εδώ η Nette δεν θα μπερδευτεί και θα αλλάξει τις αλλαγές γραμμής σε κενά.) -Η φόρμα επικυρώνεται πάντα από την πλευρά του διακομιστή, αλλά δημιουργείται επίσης επικύρωση μέσω JavaScript, η οποία είναι γρήγορη και ο χρήστης γνωρίζει το σφάλμα αμέσως, χωρίς να χρειάζεται να στείλει τη φόρμα στον διακομιστή. Αυτό το χειρίζεται η δέσμη ενεργειών `netteForms.js`. -Εισάγεται στο πρότυπο διάταξης: +Η φόρμα επικυρώνεται πάντα από την πλευρά του διακομιστή, αλλά δημιουργείται επίσης επικύρωση JavaScript, η οποία εκτελείται αστραπιαία και ο χρήστης ενημερώνεται για το σφάλμα αμέσως, χωρίς να χρειάζεται να υποβάλει τη φόρμα στον διακομιστή. Αυτό το χειρίζεται το script `netteForms.js`. Εισαγάγετέ το στο template του layout: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Αν κοιτάξετε στον πηγαίο κώδικα της σελίδας με τη φόρμα, μπορεί να παρατηρήσετε ότι η Nette εισάγει τα απαιτούμενα πεδία σε στοιχεία με κλάση CSS `required`. Δοκιμάστε να προσθέσετε το ακόλουθο στυλ στο πρότυπο, και η ετικέτα "Όνομα" θα είναι κόκκινη. Κομψά, επισημαίνουμε τα υποχρεωτικά πεδία για τους χρήστες: +Αν κοιτάξετε τον πηγαίο κώδικα της σελίδας με τη φόρμα, μπορείτε να παρατηρήσετε ότι η Nette εισάγει τα υποχρεωτικά στοιχεία σε στοιχεία με την κλάση CSS `required`. Δοκιμάστε να προσθέσετε το ακόλουθο φύλλο στυλ στο template και η ετικέτα "Όνομα" θα γίνει κόκκινη. Με αυτόν τον κομψό τρόπο, επισημαίνουμε τα υποχρεωτικά στοιχεία στους χρήστες: ```latte <style> @@ -143,96 +140,96 @@ $form->addText('name', 'Name:') </style> ``` -Πρόσθετοι κανόνες επικύρωσης θα προστεθούν με τη μέθοδο `addRule()`. Η πρώτη παράμετρος είναι ο κανόνας, η δεύτερη είναι και πάλι το κείμενο του μηνύματος σφάλματος και μπορεί να ακολουθήσει το προαιρετικό όρισμα του κανόνα επικύρωσης. Τι σημαίνει αυτό; +Προσθέτουμε περαιτέρω κανόνες επικύρωσης με τη μέθοδο `addRule()`. Η πρώτη παράμετρος είναι ο κανόνας, η δεύτερη είναι πάλι το κείμενο του μηνύματος σφάλματος και μπορεί να ακολουθήσει ένα όρισμα του κανόνα επικύρωσης. Τι σημαίνει αυτό; -Η φόρμα θα λάβει μια άλλη προαιρετική είσοδο *ηλικία* με την προϋπόθεση, ότι πρέπει να είναι αριθμός (`addInteger()`) και σε συγκεκριμένα όρια (`$form::Range`). Και εδώ θα χρησιμοποιήσουμε το τρίτο όρισμα του `addRule()`, το ίδιο το εύρος: +Θα επεκτείνουμε τη φόρμα με ένα νέο προαιρετικό πεδίο "ηλικία", το οποίο πρέπει να είναι ακέραιος αριθμός (`addInteger()`) και επιπλέον εντός επιτρεπτού εύρους (`$form::Range`). Και εδώ ακριβώς θα χρησιμοποιήσουμε την τρίτη παράμετρο της μεθόδου `addRule()`, με την οποία περνάμε στον επικυρωτή το απαιτούμενο εύρος ως ζεύγος `[από, έως]`: ```php -$form->addInteger('age', 'Age:') - ->addRule($form::Range, 'You must be older 18 years and be under 120.', [18, 120]); +$form->addInteger('age', 'Ηλικία:') + ->addRule($form::Range, 'Η ηλικία πρέπει να είναι από 18 έως 120', [18, 120]); ``` .[tip] -Εάν ο χρήστης δεν συμπληρώσει το πεδίο, οι κανόνες επικύρωσης δεν θα επαληθευτούν, επειδή το πεδίο είναι προαιρετικό. +Εάν ο χρήστης δεν συμπληρώσει το πεδίο, οι κανόνες επικύρωσης δεν θα ελεγχθούν, καθώς το στοιχείο είναι προαιρετικό. -Προφανώς υπάρχει περιθώριο για ένα μικρό refactoring. Στο μήνυμα σφάλματος και στην τρίτη παράμετρο, οι αριθμοί παρατίθενται εις διπλούν, πράγμα που δεν είναι ιδανικό. Αν δημιουργούσαμε μια [πολύγλωσση φόρμα |rendering#translating] και το μήνυμα που περιέχει αριθμούς θα έπρεπε να μεταφραστεί σε πολλές γλώσσες, θα δυσκόλευε την αλλαγή των τιμών. Για το λόγο αυτό, μπορούν να χρησιμοποιηθούν οι υποκατάστατοι χαρακτήρες `%d`: +Εδώ υπάρχει χώρος για μια μικρή αναδιάρθρωση (refactoring). Στο μήνυμα σφάλματος και στην τρίτη παράμετρο, οι αριθμοί αναφέρονται διπλά, πράγμα που δεν είναι ιδανικό. Αν δημιουργούσαμε [πολύγλωσσες φόρμες |rendering#Μετάφραση] και το μήνυμα που περιέχει αριθμούς μεταφραζόταν σε πολλές γλώσσες, θα δυσκόλευε μια πιθανή αλλαγή των τιμών. Για το λόγο αυτό, είναι δυνατόν να χρησιμοποιηθούν οι χαρακτήρες υποκατάστασης `%d` και η Nette θα συμπληρώσει τις τιμές: ```php - ->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]); + ->addRule($form::Range, 'Η ηλικία πρέπει να είναι από %d έως %d ετών', [18, 120]); ``` -Ας επιστρέψουμε στο πεδίο *password*, ας το κάνουμε *απαιτούμενο* και ας ελέγξουμε το ελάχιστο μήκος του κωδικού πρόσβασης (`$form::MinLength`), χρησιμοποιώντας και πάλι τους χαρακτήρες αντικατάστασης στο μήνυμα: +Ας επιστρέψουμε στο στοιχείο `password`, το οποίο θα καταστήσουμε επίσης υποχρεωτικό και θα ελέγξουμε επιπλέον το ελάχιστο μήκος του κωδικού πρόσβασης (`$form::MinLength`), χρησιμοποιώντας πάλι τον χαρακτήρα υποκατάστασης: ```php -$form->addPassword('password', 'Password:') - ->setRequired('Pick a password') - ->addRule($form::MinLength, 'Your password has to be at least %d long', 8); +$form->addPassword('password', 'Κωδικός πρόσβασης:') + ->setRequired('Επιλέξτε έναν κωδικό πρόσβασης') + ->addRule($form::MinLength, 'Ο κωδικός πρόσβασης πρέπει να έχει τουλάχιστον %d χαρακτήρες', 8); ``` -Θα προσθέσουμε ένα πεδίο `passwordVerify` στη φόρμα, όπου ο χρήστης θα εισάγει ξανά τον κωδικό πρόσβασης, για έλεγχο. Χρησιμοποιώντας κανόνες επικύρωσης, ελέγχουμε αν και οι δύο κωδικοί πρόσβασης είναι ίδιοι (`$form::Equal`). Και ως όρισμα δίνουμε μια αναφορά στον πρώτο κωδικό πρόσβασης χρησιμοποιώντας [αγκύλες |#Access to Controls]: +Θα προσθέσουμε στη φόρμα ένα ακόμη πεδίο `passwordVerify`, όπου ο χρήστης θα εισάγει τον κωδικό πρόσβασης ξανά, για έλεγχο. Χρησιμοποιώντας κανόνες επικύρωσης, θα ελέγξουμε αν οι δύο κωδικοί πρόσβασης είναι ίδιοι (`$form::Equal`). Και ως παράμετρο θα δώσουμε μια αναφορά στον πρώτο κωδικό πρόσβασης χρησιμοποιώντας [αγκύλες |#Πρόσβαση στα στοιχεία]: ```php -$form->addPassword('passwordVerify', 'Password again:') - ->setRequired('Fill your password again to check for typo') - ->addRule($form::Equal, 'Password mismatch', $form['password']) +$form->addPassword('passwordVerify', 'Κωδικός πρόσβασης για έλεγχο:') + ->setRequired('Παρακαλώ εισάγετε τον κωδικό πρόσβασης ξανά για έλεγχο') + ->addRule($form::Equal, 'Οι κωδικοί πρόσβασης δεν ταιριάζουν', $form['password']) ->setOmitted(); ``` -Χρησιμοποιώντας το `setOmitted()`, επισημαίνουμε ένα στοιχείο του οποίου η τιμή δεν μας ενδιαφέρει πραγματικά και το οποίο υπάρχει μόνο για επικύρωση. Η τιμή του δεν περνάει στο `$data`. +Με τη χρήση της `setOmitted()`, επισημάναμε το στοιχείο του οποίου η τιμή στην πραγματικότητα δεν μας ενδιαφέρει και το οποίο υπάρχει μόνο για λόγους επικύρωσης. Η τιμή δεν θα περάσει στο `$data`. -Έχουμε μια πλήρως λειτουργική φόρμα με επικύρωση σε PHP και JavaScript. Οι δυνατότητες επικύρωσης της Nette είναι πολύ ευρύτερες, μπορείτε να δημιουργήσετε συνθήκες, να εμφανίσετε και να αποκρύψετε τμήματα μιας σελίδας σύμφωνα με αυτές, κ.λπ. Μπορείτε να μάθετε τα πάντα στο κεφάλαιο για την [επικύρωση φορμών |validation]. +Με αυτό, έχουμε μια πλήρως λειτουργική φόρμα με επικύρωση σε PHP και JavaScript. Οι δυνατότητες επικύρωσης της Nette είναι πολύ ευρύτερες, μπορούν να δημιουργηθούν συνθήκες, να εμφανίζονται και να αποκρύπτονται τμήματα της σελίδας βάσει αυτών, κ.λπ. Όλα θα τα μάθετε στο κεφάλαιο για την [επικύρωση φορμών|validation]. -Προεπιλεγμένες τιμές .[#toc-default-values] -=========================================== +Προεπιλεγμένες τιμές +==================== -Συχνά ορίζουμε προεπιλεγμένες τιμές για τα στοιχεία ελέγχου φόρμας: +Συνήθως ορίζουμε προεπιλεγμένες τιμές για τα στοιχεία της φόρμας: ```php -$form->addEmail('email', 'Email') +$form->addEmail('email', 'E-mail') ->setDefaultValue($lastUsedEmail); ``` -Συχνά είναι χρήσιμο να ορίσουμε προεπιλεγμένες τιμές για όλα τα στοιχεία ελέγχου ταυτόχρονα. Για παράδειγμα, όταν η φόρμα χρησιμοποιείται για την επεξεργασία εγγραφών. Διαβάζουμε την εγγραφή από τη βάση δεδομένων και την ορίζουμε ως προεπιλεγμένες τιμές: +Συχνά είναι χρήσιμο να ορίσουμε προεπιλεγμένες τιμές για όλα τα στοιχεία ταυτόχρονα. Για παράδειγμα, όταν η φόρμα χρησιμοποιείται για την επεξεργασία εγγραφών. Διαβάζουμε την εγγραφή από τη βάση δεδομένων και ορίζουμε τις προεπιλεγμένες τιμές: ```php //$row = ['name' => 'John', 'age' => '33', /* ... */]; $form->setDefaults($row); ``` -Καλέστε το `setDefaults()` μετά τον ορισμό των στοιχείων ελέγχου. +Καλέστε την `setDefaults()` μετά τον ορισμό των στοιχείων. -Απεικόνιση της φόρμας .[#toc-rendering-the-form] -================================================ +Απόδοση φόρμας +============== -Από προεπιλογή, η φόρμα απεικονίζεται ως πίνακας. Τα επιμέρους στοιχεία ελέγχου ακολουθούν τις βασικές οδηγίες προσβασιμότητας ιστού. Όλες οι ετικέτες παράγονται ως `<label>` στοιχεία και συνδέονται με τις εισόδους τους, κάνοντας κλικ στην ετικέτα μετακινείται ο δρομέας στην είσοδο. +Από προεπιλογή, η φόρμα αποδίδεται ως πίνακας. Τα μεμονωμένα στοιχεία πληρούν τον βασικό κανόνα προσβασιμότητας - όλες οι ετικέτες γράφονται ως `<label>` και συνδέονται με το αντίστοιχο στοιχείο της φόρμας. Όταν κάνετε κλικ στην ετικέτα, ο δρομέας εμφανίζεται αυτόματα στο πεδίο της φόρμας. -Μπορούμε να ορίσουμε οποιαδήποτε χαρακτηριστικά HTML για κάθε στοιχείο. Για παράδειγμα, να προσθέσουμε ένα placeholder: +Μπορούμε να ορίσουμε οποιαδήποτε HTML attributes για κάθε στοιχείο. Για παράδειγμα, να προσθέσουμε ένα placeholder: ```php -$form->addInteger('age', 'Age:') - ->setHtmlAttribute('placeholder', 'Please fill in the age'); +$form->addInteger('age', 'Ηλικία:') + ->setHtmlAttribute('placeholder', 'Παρακαλώ συμπληρώστε την ηλικία'); ``` -Υπάρχουν πραγματικά πολλοί τρόποι για την απόδοση μιας φόρμας, γι' αυτό και είναι αφιερωμένο [κεφάλαιο για την απόδοση |rendering]. +Υπάρχουν πραγματικά πολλοί τρόποι για να αποδοθεί μια φόρμα, γι' αυτό υπάρχει ένα [ξεχωριστό κεφάλαιο για την απόδοση|rendering]. -Χαρτογράφηση σε κλάσεις .[#toc-mapping-to-classes] -================================================== +Αντιστοίχιση σε κλάσεις +======================= -Ας επιστρέψουμε στη μέθοδο `formSucceeded()`, η οποία στη δεύτερη παράμετρο `$data` λαμβάνει τα δεδομένα που αποστέλλονται ως αντικείμενο `ArrayHash`. Επειδή πρόκειται για μια γενική κλάση, κάτι σαν το `stdClass`, θα μας λείψουν κάποιες ευκολίες κατά την εργασία με αυτήν, όπως η συμπλήρωση κώδικα για ιδιότητες σε επεξεργαστές ή στατική ανάλυση κώδικα. Αυτό θα μπορούσε να λυθεί με την ύπαρξη μιας συγκεκριμένης κλάσης για κάθε φόρμα, της οποίας οι ιδιότητες αντιπροσωπεύουν τα επιμέρους στοιχεία ελέγχου. Π.χ: +Ας επιστρέψουμε στη μέθοδο `formSucceeded()`, η οποία στη δεύτερη παράμετρο `$data` λαμβάνει τα υποβληθέντα δεδομένα ως αντικείμενο `ArrayHash`. Επειδή πρόκειται για μια γενική κλάση, κάτι σαν `stdClass`, θα μας λείψει κάποια άνεση κατά την εργασία μαζί της, όπως η πρόταση properties στους επεξεργαστές ή η στατική ανάλυση κώδικα. Αυτό θα μπορούσε να λυθεί έχοντας μια συγκεκριμένη κλάση για κάθε φόρμα, της οποίας οι properties αντιπροσωπεύουν τα μεμονωμένα στοιχεία. Π.χ.: ```php class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -Από την PHP 8.0, μπορείτε να χρησιμοποιήσετε αυτόν τον κομψό συμβολισμό που χρησιμοποιεί έναν κατασκευαστή: +Εναλλακτικά, μπορείτε να χρησιμοποιήσετε τον κατασκευαστή: ```php class RegistrationFormData @@ -246,27 +243,29 @@ class RegistrationFormData } ``` -Πώς μπορείτε να πείτε στη Nette να επιστρέφει δεδομένα ως αντικείμενα αυτής της κλάσης; Πιο εύκολα απ' ό,τι νομίζετε. Το μόνο που έχετε να κάνετε είναι να καθορίσετε την κλάση ως τύπο της παραμέτρου `$data` στον χειριστή: +Οι ιδιότητες της κλάσης δεδομένων μπορούν επίσης να είναι enum και θα αντιστοιχιστούν αυτόματα. .{data-version:3.2.4} + +Πώς να πούμε στη Nette να μας επιστρέφει τα δεδομένα ως αντικείμενα αυτής της κλάσης; Πιο εύκολα από ό,τι νομίζετε. Αρκεί απλώς να δηλώσετε την κλάση ως τύπο της παραμέτρου `$data` στη μέθοδο χειρισμού: ```php public function formSucceeded(Form $form, RegistrationFormData $data): void { - // $name είναι instance of RegistrationFormData + // το $data είναι μια παρουσία του RegistrationFormData $name = $data->name; // ... } ``` -Μπορείτε επίσης να καθορίσετε το `array` ως τύπο και τότε θα περάσει τα δεδομένα ως πίνακα. +Ως τύπος μπορεί επίσης να δηλωθεί το `array` και τότε τα δεδομένα θα περάσουν ως πίνακας. -Με παρόμοιο τρόπο, μπορείτε να χρησιμοποιήσετε τη μέθοδο `getValues()`, την οποία περνάμε ως όνομα κλάσης ή αντικειμένου προς ενυδάτωση ως παράμετρο: +Με παρόμοιο τρόπο μπορεί να χρησιμοποιηθεί και η συνάρτηση `getValues()`, στην οποία περνάμε το όνομα της κλάσης ή το αντικείμενο προς ενυδάτωση ως παράμετρο: ```php $data = $form->getValues(RegistrationFormData::class); $name = $data->name; ``` -Εάν οι φόρμες αποτελούνται από μια δομή πολλαπλών επιπέδων που αποτελείται από δοχεία, δημιουργήστε μια ξεχωριστή κλάση για κάθε ένα από αυτά: +Εάν οι φόρμες σχηματίζουν μια πολυεπίπεδη δομή αποτελούμενη από containers, δημιουργήστε μια ξεχωριστή κλάση για καθένα: ```php $form = new Form; @@ -283,32 +282,34 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` -Η αντιστοίχιση γνωρίζει τότε από τον τύπο της ιδιότητας `$person` ότι πρέπει να αντιστοιχίσει το δοχείο στην κλάση `PersonFormData`. Εάν η ιδιότητα θα περιέχει έναν πίνακα από δοχεία, δώστε τον τύπο `array` και περάστε την κλάση που θα αντιστοιχιστεί απευθείας στο δοχείο: +Η αντιστοίχιση τότε από τον τύπο της property `$person` καταλαβαίνει ότι πρέπει να αντιστοιχίσει το container στην κλάση `PersonFormData`. Εάν η property περιείχε έναν πίνακα από containers, δηλώστε τον τύπο `array` και περάστε την κλάση για αντιστοίχιση απευθείας στο container: ```php $person->setMappedType(PersonFormData::class); ``` +Μπορείτε να ζητήσετε τη δημιουργία του σχεδίου της κλάσης δεδομένων της φόρμας χρησιμοποιώντας τη μέθοδο `Nette\Forms\Blueprint::dataClass($form)`, η οποία θα το εκτυπώσει στη σελίδα του περιηγητή. Στη συνέχεια, αρκεί να επιλέξετε τον κώδικα με κλικ και να τον αντιγράψετε στο έργο σας. .{data-version:3.1.15} + -Πολλαπλά κουμπιά υποβολής .[#toc-multiple-submit-buttons] -========================================================= +Πολλαπλά κουμπιά +================ -Εάν η φόρμα έχει περισσότερα από ένα κουμπιά, συνήθως πρέπει να διακρίνουμε ποιο από αυτά έχει πατηθεί. Μπορούμε να δημιουργήσουμε δική μας συνάρτηση για κάθε κουμπί. Ορίστε την ως χειριστή για το [συμβάν |nette:glossary#Events] `onClick`: +Εάν η φόρμα έχει περισσότερα από ένα κουμπιά, συνήθως χρειαζόμαστε να διακρίνουμε ποιο από αυτά πατήθηκε. Μπορούμε να δημιουργήσουμε τη δική μας συνάρτηση χειρισμού για κάθε κουμπί. Θα την ορίσουμε ως handler για το [γεγονός |nette:glossary#Events] `onClick`: ```php -$form->addSubmit('save', 'Save') +$form->addSubmit('save', 'Αποθήκευση') ->onClick[] = [$this, 'saveButtonPressed']; -$form->addSubmit('delete', 'Delete') +$form->addSubmit('delete', 'Διαγραφή') ->onClick[] = [$this, 'deleteButtonPressed']; ``` -Αυτοί οι χειριστές καλούνται επίσης μόνο στην περίπτωση που η φόρμα είναι έγκυρη, όπως στην περίπτωση του συμβάντος `onSuccess`. Η διαφορά είναι ότι η πρώτη παράμετρος μπορεί να είναι το αντικείμενο submit button αντί της φόρμας, ανάλογα με τον τύπο που καθορίζετε: +Αυτοί οι handlers καλούνται μόνο στην περίπτωση έγκυρα συμπληρωμένης φόρμας, όπως και στην περίπτωση του συμβάντος `onSuccess`. Η διαφορά είναι ότι ως πρώτη παράμετρος, αντί για τη φόρμα, μπορεί να περάσει το κουμπί υποβολής, ανάλογα με τον τύπο που θα δηλώσετε: ```php public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) @@ -318,62 +319,61 @@ public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) } ``` -Όταν μια φόρμα υποβάλλεται με το πλήκτρο <kbd>Enter</kbd>, αντιμετωπίζεται σαν να είχε υποβληθεί με το πρώτο κουμπί. +Όταν η φόρμα υποβάλλεται με το πλήκτρο <kbd>Enter</kbd>, θεωρείται σαν να υποβλήθηκε με το πρώτο κουμπί. -Γεγονός onAnchor .[#toc-event-onanchor] -======================================= +Το συμβάν onAnchor +================== -Όταν κατασκευάζετε μια φόρμα σε μια εργοστασιακή μέθοδο (όπως η `createComponentRegistrationForm`), δεν γνωρίζει ακόμα αν έχει υποβληθεί ή τα δεδομένα με τα οποία υποβλήθηκε. Υπάρχουν όμως περιπτώσεις όπου πρέπει να γνωρίζουμε τις τιμές που έχουν υποβληθεί, ίσως εξαρτάται από αυτές το πώς θα είναι η μορφή της φόρμας, ή χρησιμοποιούνται για εξαρτημένα selectboxes, κλπ. +Όταν στη factory method (όπως π.χ. η `createComponentRegistrationForm`) κατασκευάζουμε τη φόρμα, αυτή δεν γνωρίζει ακόμη αν υποβλήθηκε, ούτε με ποια δεδομένα. Υπάρχουν όμως περιπτώσεις όπου χρειαζόμαστε να γνωρίζουμε τις υποβληθείσες τιμές, για παράδειγμα, η περαιτέρω μορφή της φόρμας εξαρτάται από αυτές, ή τις χρειαζόμαστε για εξαρτώμενα selectboxes κ.λπ. -Επομένως, μπορείτε να έχετε τον κώδικα που κατασκευάζει τη φόρμα να καλείται όταν αυτή είναι αγκυρωμένη, δηλαδή είναι ήδη συνδεδεμένη με τον παρουσιαστή και γνωρίζει τα υποβληθέντα δεδομένα της. Θα τοποθετήσουμε έναν τέτοιο κώδικα στον πίνακα `$onAnchor`: +Μπορείτε λοιπόν να αφήσετε ένα μέρος του κώδικα που κατασκευάζει τη φόρμα να κληθεί μόνο τη στιγμή που είναι, όπως λέγεται, αγκυρωμένη, δηλαδή είναι ήδη συνδεδεμένη με τον presenter και γνωρίζει τα υποβληθέντα δεδομένα της. Τέτοιο κώδικα τον περνάμε στον πίνακα `$onAnchor`: ```php -$country = $form->addSelect('country', 'Country:', $this->model->getCountries()); -$city = $form->addSelect('city', 'City:'); +$country = $form->addSelect('country', 'Χώρα:', $this->model->getCountries()); +$city = $form->addSelect('city', 'Πόλη:'); $form->onAnchor[] = function () use ($country, $city) { - // αυτή η συνάρτηση θα κληθεί όταν η φόρμα γνωρίζει τα δεδομένα με τα οποία υποβλήθηκε - // έτσι ώστε να μπορείτε να χρησιμοποιήσετε τη μέθοδο getValue() + // αυτή η συνάρτηση καλείται όταν η φόρμα γνωρίζει αν έχει υποβληθεί και με ποια δεδομένα + // επομένως, η μέθοδος getValue() μπορεί να χρησιμοποιηθεί $val = $country->getValue(); - $city->setItems($val ? $this->model->getCities($val): []); + $city->setItems($val ? $this->model->getCities($val) : []); }; ``` -Vulnerability Protection .[#toc-vulnerability-protection] -========================================================= +Προστασία από ευπάθειες +======================= -Το Nette Framework καταβάλλει μεγάλη προσπάθεια για να είναι ασφαλές και δεδομένου ότι οι φόρμες είναι η πιο συνηθισμένη είσοδος του χρήστη, οι φόρμες του Nette είναι σχεδόν αδιαπέραστες. Όλα διατηρούνται δυναμικά και διαφανώς, τίποτα δεν χρειάζεται να ρυθμιστεί χειροκίνητα. +Το Nette Framework δίνει μεγάλη έμφαση στην ασφάλεια και γι' αυτό φροντίζει σχολαστικά για την καλή ασφάλεια των φορμών. Το κάνει εντελώς διαφανώς και δεν απαιτεί χειροκίνητη ρύθμιση. -Εκτός από την προστασία των φορμών από επιθέσεις που στοχεύουν σε γνωστές ευπάθειες όπως [Cross-Site Scripting (XSS) |nette:glossary#cross-site-scripting-xss] και [Cross-Site Request Forgery (CSRF |nette:glossary#cross-site-request-forgery-csrf]), κάνει πολλές μικρές εργασίες ασφαλείας που δεν χρειάζεται πλέον να σκέφτεστε. +Εκτός από την προστασία των φορμών από επιθέσεις [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] και [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF], κάνει πολλές μικρές ασφαλιστικές δικλείδες για τις οποίες εσείς δεν χρειάζεται πλέον να σκέφτεστε. -Για παράδειγμα, φιλτράρει όλους τους χαρακτήρες ελέγχου από τις εισόδους και ελέγχει την εγκυρότητα της κωδικοποίησης UTF-8, ώστε τα δεδομένα από τη φόρμα να είναι πάντα καθαρά. Για τα πλαίσια επιλογής και τις λίστες επιλογής, επαληθεύει ότι τα επιλεγμένα στοιχεία ήταν όντως από τα προσφερόμενα και ότι δεν υπήρξε πλαστογράφηση. Έχουμε ήδη αναφέρει ότι για την εισαγωγή κειμένου σε μία γραμμή, αφαιρεί τους χαρακτήρες τέλους γραμμής που θα μπορούσε να στείλει εκεί ένας εισβολέας. Για εισόδους πολλαπλών γραμμών, ομαλοποιεί τους χαρακτήρες στο τέλος της γραμμής. Και ούτω καθεξής. +Για παράδειγμα, φιλτράρει από τις εισόδους όλους τους χαρακτήρες ελέγχου και ελέγχει την εγκυρότητα της κωδικοποίησης UTF-8, έτσι ώστε τα δεδομένα από τη φόρμα να είναι πάντα καθαρά. Στα select boxes και radio lists, ελέγχει ότι τα επιλεγμένα στοιχεία ήταν πραγματικά από τα προσφερόμενα και ότι δεν υπήρξε παραποίηση. Έχουμε ήδη αναφέρει ότι στα text inputs μίας γραμμής αφαιρεί τους χαρακτήρες τέλους γραμμής που θα μπορούσε να στείλει ένας εισβολέας. Στα inputs πολλών γραμμών, κανονικοποιεί τους χαρακτήρες τέλους γραμμής. Και ούτω καθεξής. -Η Nette διορθώνει για εσάς ευπάθειες ασφαλείας που οι περισσότεροι προγραμματιστές δεν έχουν ιδέα ότι υπάρχουν. +Η Nette λύνει για εσάς κινδύνους ασφαλείας που πολλοί προγραμματιστές ούτε καν υποψιάζονται ότι υπάρχουν. -Η επίθεση CSRF που αναφέρθηκε είναι ότι ένας επιτιθέμενος παρασύρει το θύμα να επισκεφθεί μια σελίδα που εκτελεί σιωπηλά ένα αίτημα στο πρόγραμμα περιήγησης του θύματος προς τον διακομιστή όπου το θύμα είναι συνδεδεμένο αυτή τη στιγμή και ο διακομιστής πιστεύει ότι το αίτημα έγινε από το θύμα κατά βούληση. Ως εκ τούτου, η Nette εμποδίζει την υποβολή της φόρμας μέσω POST από άλλο τομέα. Αν για κάποιο λόγο θέλετε να απενεργοποιήσετε την προστασία και να επιτρέψετε την υποβολή της φόρμας από άλλο τομέα, χρησιμοποιήστε: +Η αναφερόμενη επίθεση CSRF συνίσταται στο ότι ο εισβολέας προσελκύει το θύμα σε μια σελίδα που εκτελεί διακριτικά στον περιηγητή του θύματος ένα αίτημα προς τον διακομιστή στον οποίο το θύμα είναι συνδεδεμένο, και ο διακομιστής πιστεύει ότι το αίτημα εκτελέστηκε από το θύμα με τη θέλησή του. Γι' αυτό η Nette αποτρέπει την υποβολή φορμών POST από άλλο domain. Εάν για κάποιο λόγο θέλετε να απενεργοποιήσετε την προστασία και να επιτρέψετε την υποβολή της φόρμας από άλλο domain, χρησιμοποιήστε: ```php $form->allowCrossOrigin(); // ΠΡΟΣΟΧΗ! Απενεργοποιεί την προστασία! ``` -Αυτή η προστασία χρησιμοποιεί ένα cookie SameSite με όνομα `_nss`. Η προστασία των cookies SameSite μπορεί να μην είναι 100% αξιόπιστη, οπότε είναι καλή ιδέα να ενεργοποιήσετε την προστασία token: +Αυτή η προστασία χρησιμοποιεί ένα SameSite cookie με το όνομα `_nss`. Η προστασία μέσω SameSite cookie μπορεί να μην είναι 100% αξιόπιστη, γι' αυτό είναι σκόπιμο να ενεργοποιήσετε επιπλέον την προστασία μέσω token: ```php $form->addProtection(); ``` -Συνιστάται ιδιαίτερα να εφαρμόζετε αυτή την προστασία στις φόρμες σε ένα διοικητικό τμήμα της εφαρμογής σας που αλλάζει ευαίσθητα δεδομένα. Το πλαίσιο προστατεύει από μια επίθεση CSRF δημιουργώντας και επικυρώνοντας το διακριτικό ελέγχου ταυτότητας που αποθηκεύεται σε μια περίοδο λειτουργίας (το επιχείρημα είναι το μήνυμα σφάλματος που εμφανίζεται εάν το διακριτικό έχει λήξει). Γι' αυτό είναι απαραίτητο να έχει ξεκινήσει μια συνεδρία πριν από την εμφάνιση της φόρμας. Στο τμήμα διαχείρισης του ιστότοπου, η σύνοδος συνήθως έχει ήδη ξεκινήσει, λόγω της σύνδεσης του χρήστη. -Διαφορετικά, ξεκινήστε τη σύνοδο με τη μέθοδο `Nette\Http\Session::start()`. +Συνιστούμε να προστατεύετε με αυτόν τον τρόπο τις φόρμες στο διαχειριστικό τμήμα του ιστότοπου που αλλάζουν ευαίσθητα δεδομένα στην εφαρμογή. Το framework αμύνεται έναντι της επίθεσης CSRF δημιουργώντας και επαληθεύοντας ένα token εξουσιοδότησης, το οποίο αποθηκεύεται στο session. Επομένως, είναι απαραίτητο να έχετε ανοιχτό το session πριν από την εμφάνιση της φόρμας. Στο διαχειριστικό τμήμα του ιστότοπου, το session είναι συνήθως ήδη ενεργοποιημένο λόγω της σύνδεσης του χρήστη. Διαφορετικά, ξεκινήστε το session με τη μέθοδο `Nette\Http\Session::start()`. -Χρήση μιας φόρμας σε πολλούς παρουσιαστές .[#toc-using-one-form-in-multiple-presenters] -======================================================================================= +Η ίδια φόρμα σε πολλούς presenters +================================== -Εάν πρέπει να χρησιμοποιήσετε μια φόρμα σε περισσότερους από έναν παρουσιαστές, σας συνιστούμε να δημιουργήσετε ένα εργοστάσιο για αυτήν, το οποίο στη συνέχεια θα μεταβιβάσετε στον παρουσιαστή. Μια κατάλληλη τοποθεσία για μια τέτοια κλάση είναι, για παράδειγμα, ο κατάλογος `app/Forms`. +Εάν χρειάζεστε να χρησιμοποιήσετε την ίδια φόρμα σε πολλούς presenters, συνιστούμε να δημιουργήσετε ένα factory για αυτήν, το οποίο στη συνέχεια θα περάσετε στον presenter. Μια κατάλληλη τοποθεσία για μια τέτοια κλάση είναι, για παράδειγμα, ο κατάλογος `app/Forms`. -Η κλάση factory θα μπορούσε να μοιάζει ως εξής: +Η κλάση factory μπορεί να μοιάζει κάπως έτσι: ```php use Nette\Application\UI\Form; @@ -383,14 +383,14 @@ class SignInFormFactory public function create(): Form { $form = new Form; - $form->addText('name', 'Name:'); - $form->addSubmit('send', 'Log in'); + $form->addText('name', 'Όνομα:'); + $form->addSubmit('send', 'Σύνδεση'); return $form; } } ``` -Ζητάμε από την κλάση να παράγει τη φόρμα στη μέθοδο factory για τα στοιχεία στον παρουσιαστή: +Ζητάμε από την κλάση να κατασκευάσει τη φόρμα στη factory method για components στον presenter: ```php public function __construct( @@ -401,14 +401,14 @@ public function __construct( protected function createComponentSignInForm(): Form { $form = $this->formFactory->create(); - // μπορούμε να αλλάξουμε τη φόρμα, εδώ για παράδειγμα αλλάζουμε την ετικέτα στο κουμπί - $form['login']->setCaption('Continue'); - $form->onSuccess[] = [$this, 'signInFormSubmitted']; // και προσθέτουμε χειριστή + // μπορούμε να τροποποιήσουμε τη φόρμα, εδώ για παράδειγμα αλλάζουμε την ετικέτα στο κουμπί + $form['send']->setCaption('Συνέχεια'); + $form->onSuccess[] = [$this, 'signInFormSuceeded']; // και προσθέτουμε τον handler return $form; } ``` -Ο χειριστής επεξεργασίας της φόρμας μπορεί επίσης να παραδοθεί από το εργοστάσιο: +Ο handler για την επεξεργασία της φόρμας μπορεί επίσης να παρασχεθεί ήδη από το factory: ```php use Nette\Application\UI\Form; @@ -418,14 +418,14 @@ class SignInFormFactory public function create(): Form { $form = new Form; - $form->addText('name', 'Name:'); - $form->addSubmit('send', 'Log in'); + $form->addText('name', 'Όνομα:'); + $form->addSubmit('send', 'Σύνδεση'); $form->onSuccess[] = function (Form $form, $data): void { - // επεξεργαζόμαστε την υποβληθείσα φόρμα μας εδώ + // εδώ εκτελούμε την επεξεργασία της φόρμας }; return $form; } } ``` -Έτσι, έχουμε μια γρήγορη εισαγωγή στις φόρμες στη Nette. Δοκιμάστε να κοιτάξετε στον κατάλογο [παραδειγμάτων |https://github.com/nette/forms/tree/master/examples] της διανομής για περισσότερη έμπνευση. +Λοιπόν, έχουμε πίσω μας μια γρήγορη εισαγωγή στις φόρμες στη Nette. Δοκιμάστε να ρίξετε μια ματιά στον κατάλογο [examples |https://github.com/nette/forms/tree/master/examples] στη διανομή, όπου θα βρείτε περαιτέρω έμπνευση. diff --git a/forms/el/rendering.texy b/forms/el/rendering.texy index d56be8d3f5..56c525c592 100644 --- a/forms/el/rendering.texy +++ b/forms/el/rendering.texy @@ -1,33 +1,35 @@ -Μορφές απόδοσης -*************** +Απόδοση φορμών +************** -Η εμφάνιση των μορφών μπορεί να είναι πολύ διαφορετική. Στην πράξη, μπορούμε να συναντήσουμε δύο άκρα. Από τη μία πλευρά, υπάρχει η ανάγκη απόδοσης μιας σειράς φορμών σε μια εφαρμογή που είναι οπτικά παρόμοιες μεταξύ τους και εκτιμούμε την εύκολη απόδοση χωρίς πρότυπο με τη χρήση του `$form->render()`. Αυτή είναι συνήθως η περίπτωση των διοικητικών διεπαφών. +Η εμφάνιση των φορμών μπορεί να είναι πολύ διαφορετική. Στην πράξη, μπορούμε να συναντήσουμε δύο άκρα. Από τη μία πλευρά, υπάρχει η ανάγκη να αποδοθούν στην εφαρμογή πολλές φόρμες που είναι οπτικά παρόμοιες σαν δύο σταγόνες νερό, και εκτιμούμε την εύκολη απόδοση χωρίς πρότυπο χρησιμοποιώντας την `$form->render()`. Αυτή είναι συνήθως η περίπτωση των διαχειριστικών διεπαφών. -Από την άλλη πλευρά, υπάρχουν διάφορες φόρμες όπου η κάθε μία είναι μοναδική. Η εμφάνισή τους περιγράφεται καλύτερα με τη χρήση της γλώσσας HTML στο πρότυπο. Και φυσικά, εκτός από τα δύο προαναφερθέντα άκρα, θα συναντήσουμε πολλές φόρμες που βρίσκονται κάπου ενδιάμεσα. +Από την άλλη πλευρά, υπάρχουν ποικίλες φόρμες όπου ισχύει: κάθε κομμάτι, ένα πρωτότυπο. Η μορφή τους περιγράφεται καλύτερα με τη γλώσσα HTML στο πρότυπο της φόρμας. Και φυσικά, εκτός από τα δύο αναφερόμενα άκρα, θα συναντήσουμε πολλές φόρμες που κινούνται κάπου στη μέση. -Απεικόνιση με Latte .[#toc-rendering-with-latte] -================================================ +Απόδοση με χρήση Latte +====================== -Το [σύστημα προτύπων Latte |latte:] διευκολύνει ουσιαστικά την απόδοση των μορφών και των στοιχείων τους. Αρχικά, θα δείξουμε πώς να αποδίδουμε τις φόρμες χειροκίνητα, στοιχείο προς στοιχείο, για να αποκτήσουμε πλήρη έλεγχο του κώδικα. Αργότερα θα δείξουμε πώς να [αυτοματοποιήσουμε |#Automatic rendering] την εν λόγω απόδοση. +Το [σύστημα προτύπων Latte |latte:] διευκολύνει σημαντικά την απόδοση των φορμών και των στοιχείων τους. Πρώτα θα δείξουμε πώς να αποδίδετε τις φόρμες χειροκίνητα, στοιχείο προς στοιχείο, αποκτώντας έτσι πλήρη έλεγχο του κώδικα. Αργότερα θα δείξουμε πώς μπορεί αυτή η απόδοση να [αυτοματοποιηθεί |#Αυτόματη απόδοση]. + +Μπορείτε να ζητήσετε τη δημιουργία του σχεδίου του προτύπου Latte της φόρμας χρησιμοποιώντας τη μέθοδο `Nette\Forms\Blueprint::latte($form)`, η οποία θα το εκτυπώσει στη σελίδα του προγράμματος περιήγησης. Στη συνέχεια, αρκεί να επιλέξετε τον κώδικα με κλικ και να τον αντιγράψετε στο έργο σας. .{data-version:3.1.15} `{control}` ----------- -Ο ευκολότερος τρόπος για να αποδώσετε μια φόρμα είναι να γράψετε σε ένα πρότυπο: +Ο απλούστερος τρόπος για να αποδοθεί μια φόρμα είναι να γράψετε στο πρότυπο: ```latte {control signInForm} ``` -Η εμφάνιση της φόρμας που αποδίδεται μπορεί να αλλάξει με τη διαμόρφωση του [Renderer |#Renderer] και των [επιμέρους στοιχείων ελέγχου |#HTML Attributes]. +Μπορείτε να επηρεάσετε την εμφάνιση της φόρμας που αποδίδεται με αυτόν τον τρόπο διαμορφώνοντας τον [#Renderer] και τα [μεμονωμένα στοιχεία ελέγχου |#Χαρακτηριστικά HTML]. `n:name` -------- -Είναι εξαιρετικά εύκολο να συνδέσετε τον ορισμό της φόρμας σε κώδικα PHP με κώδικα HTML. Απλά προσθέστε τα χαρακτηριστικά `n:name`. Τόσο εύκολο είναι! +Ο ορισμός της φόρμας στον κώδικα PHP μπορεί να συνδεθεί εξαιρετικά εύκολα με τον κώδικα HTML. Αρκεί απλώς να προσθέσετε τα χαρακτηριστικά `n:name`. Είναι τόσο εύκολο! ```php protected function createComponentSignInForm(): Form @@ -43,10 +45,10 @@ protected function createComponentSignInForm(): Form ```latte <form n:name=signInForm class=form> <div> - <label n:name=username>Username: <input n:name=username size=20 autofocus></label> + <label n:name=username>Όνομα χρήστη: <input n:name=username size=20 autofocus></label> </div> <div> - <label n:name=password>Password: <input n:name=password></label> + <label n:name=password>Κωδικός πρόσβασης: <input n:name=password></label> </div> <div> <input n:name=send class="btn btn-default"> @@ -54,10 +56,9 @@ protected function createComponentSignInForm(): Form </form> ``` -Η εμφάνιση του κώδικα HTML που προκύπτει είναι εξ ολοκλήρου στα χέρια σας. Αν χρησιμοποιήσετε το χαρακτηριστικό `n:name` με `<select>`, `<button>` ή `<textarea>` στοιχεία, το εσωτερικό τους περιεχόμενο συμπληρώνεται αυτόματα. -Επιπλέον, το `<form n:name>` ετικέτα δημιουργεί μια τοπική μεταβλητή `$form` με το αντικείμενο της φόρμας που σχεδιάστηκε και το κλείσιμο `</form>` σχεδιάζει όλα τα μη σχεδιασμένα κρυμμένα στοιχεία (το ίδιο ισχύει και για το `{form} ... {/form}`). +Έχετε τον πλήρη έλεγχο της μορφής του τελικού κώδικα HTML. Εάν χρησιμοποιήσετε το χαρακτηριστικό `n:name` στα στοιχεία `<select>`, `<button>` ή `<textarea>`, το εσωτερικό τους περιεχόμενο θα συμπληρωθεί αυτόματα. Επιπλέον, η ετικέτα `<form n:name>` δημιουργεί μια τοπική μεταβλητή `$form` με το αντικείμενο της αποδιδόμενης φόρμας και η κλείνουσα `</form>` αποδίδει όλα τα μη αποδοθέντα κρυφά στοιχεία (το ίδιο ισχύει και για `{form} ... {/form}`). -Ωστόσο, δεν πρέπει να ξεχνάμε την απόδοση πιθανών μηνυμάτων σφάλματος. Τόσο εκείνα που προστέθηκαν σε μεμονωμένα στοιχεία από τη μέθοδο `addError()` (με τη χρήση του `{inputError}`) όσο και εκείνα που προστέθηκαν απευθείας στη φόρμα (επιστρέφονται από το `$form->getOwnErrors()`): +Ωστόσο, δεν πρέπει να ξεχάσουμε να αποδώσουμε τα πιθανά μηνύματα σφάλματος. Και αυτά που προστέθηκαν με τη μέθοδο `addError()` στα μεμονωμένα στοιχεία (χρησιμοποιώντας `{inputError}`), και αυτά που προστέθηκαν απευθείας στη φόρμα (τα επιστρέφει η `$form->getOwnErrors()`): ```latte <form n:name=signInForm class=form> @@ -66,11 +67,11 @@ protected function createComponentSignInForm(): Form </ul> <div> - <label n:name=username>Username: <input n:name=username size=20 autofocus></label> + <label n:name=username>Όνομα χρήστη: <input n:name=username size=20 autofocus></label> <span class=error n:ifcontent>{inputError username}</span> </div> <div> - <label n:name=password>Password: <input n:name=password></label> + <label n:name=password>Κωδικός πρόσβασης: <input n:name=password></label> <span class=error n:ifcontent>{inputError password}</span> </div> <div> @@ -79,7 +80,7 @@ protected function createComponentSignInForm(): Form </form> ``` -Τα πιο σύνθετα στοιχεία της φόρμας, όπως η RadioList ή η CheckboxList, μπορούν να αποδοθούν ανά στοιχείο: +Πιο σύνθετα στοιχεία φόρμας, όπως το RadioList ή το CheckboxList, μπορούν να αποδοθούν με αυτόν τον τρόπο ανά μεμονωμένο στοιχείο: ```latte {foreach $form[gender]->getItems() as $key => $label} @@ -88,16 +89,10 @@ protected function createComponentSignInForm(): Form ``` -Πρόταση κώδικα `{formPrint}` .[#toc-formprint] ----------------------------------------------- - -Μπορείτε να δημιουργήσετε έναν παρόμοιο κωδικό Latte για μια φόρμα χρησιμοποιώντας την ετικέτα `{formPrint}`. Αν το τοποθετήσετε σε ένα πρότυπο, θα δείτε το σχέδιο κώδικα αντί για την κανονική απόδοση. Στη συνέχεια, απλά επιλέξτε το και αντιγράψτε το στο έργο σας. - - `{label}` `{input}` ------------------- -Δεν θέλετε να σκεφτείτε για κάθε στοιχείο ποιο στοιχείο HTML να χρησιμοποιήσετε για αυτό στο πρότυπο, είτε `<input>`, `<textarea>` κ.λπ. Η λύση είναι η καθολική ετικέτα `{input}`: +Δεν θέλετε να σκέφτεστε για κάθε στοιχείο ποιο στοιχείο HTML να χρησιμοποιήσετε στο πρότυπο, αν `<input>`, `<textarea>` κ.λπ.; Η λύση είναι η καθολική ετικέτα `{input}`: ```latte <form n:name=signInForm class=form> @@ -106,11 +101,11 @@ protected function createComponentSignInForm(): Form </ul> <div> - {label username}Username: {input username, size: 20, autofocus: true}{/label} + {label username}Όνομα χρήστη: {input username, size: 20, autofocus: true}{/label} {inputError username} </div> <div> - {label password}Password: {input password}{/label} + {label password}Κωδικός πρόσβασης: {input password}{/label} {inputError password} </div> <div> @@ -121,7 +116,7 @@ protected function createComponentSignInForm(): Form Εάν η φόρμα χρησιμοποιεί μεταφραστή, το κείμενο μέσα στις ετικέτες `{label}` θα μεταφραστεί. -Και πάλι, πιο σύνθετα στοιχεία φόρμας, όπως RadioList ή CheckboxList, μπορούν να αποδοθούν ανά στοιχείο: +Ακόμη και σε αυτή την περίπτωση, πιο σύνθετα στοιχεία φόρμας, όπως το RadioList ή το CheckboxList, μπορούν να αποδοθούν ανά μεμονωμένο στοιχείο: ```latte {foreach $form[gender]->items as $key => $label} @@ -129,20 +124,19 @@ protected function createComponentSignInForm(): Form {/foreach} ``` -Για να αποδώσετε το `<input>` στο στοιχείο Checkbox, χρησιμοποιήστε το `{input myCheckbox:}`. Τα χαρακτηριστικά HTML πρέπει να διαχωρίζονται με κόμμα `{input myCheckbox:, class: required}`. +Για να αποδώσετε μόνο το `<input>` στο στοιχείο Checkbox, χρησιμοποιήστε `{input myCheckbox:}`. Σε αυτή την περίπτωση, διαχωρίζετε πάντα τα χαρακτηριστικά HTML με κόμμα `{input myCheckbox:, class: required}`. `{inputError}` -------------- -Εκτυπώνει ένα μήνυμα σφάλματος για το στοιχείο της φόρμας, αν έχει τέτοιο. Το μήνυμα είναι συνήθως τυλιγμένο σε ένα στοιχείο HTML για μορφοποίηση. -Η αποφυγή της απόδοσης ενός κενού στοιχείου αν δεν υπάρχει μήνυμα μπορεί να γίνει κομψά με το `n:ifcontent`: +Εκτυπώνει το μήνυμα σφάλματος για ένα στοιχείο φόρμας, αν υπάρχει. Συνήθως τυλίγουμε το μήνυμα σε ένα στοιχείο HTML για στυλ. Μπορείτε να αποτρέψετε την απόδοση ενός κενού στοιχείου εάν δεν υπάρχει μήνυμα, κομψά με τη χρήση του `n:ifcontent`: ```latte <span class=error n:ifcontent>{inputError $input}</span> ``` -Μπορούμε να ανιχνεύσουμε την παρουσία ενός σφάλματος χρησιμοποιώντας τη μέθοδο `hasErrors()` και να ορίσουμε ανάλογα την κλάση του γονικού στοιχείου: +Μπορούμε να ελέγξουμε την παρουσία σφάλματος με τη μέθοδο `hasErrors()` και ανάλογα να ορίσουμε την κλάση στο γονικό στοιχείο: ```latte <div n:class="$form[username]->hasErrors() ? 'error'"> @@ -155,14 +149,13 @@ protected function createComponentSignInForm(): Form `{form}` -------- -Ετικέτες `{form signInForm}...{/form}` είναι μια εναλλακτική λύση για `<form n:name="signInForm">...</form>`. +Οι ετικέτες `{form signInForm}...{/form}` είναι μια εναλλακτική λύση για το `<form n:name="signInForm">...</form>`. -Αυτόματη απόδοση .[#toc-automatic-rendering] --------------------------------------------- +Αυτόματη απόδοση +---------------- -Με τις ετικέτες `{input}` και `{label}`, μπορούμε εύκολα να δημιουργήσουμε ένα γενικό πρότυπο για οποιαδήποτε φόρμα. Θα επαναλαμβάνει και θα αποδίδει όλα τα στοιχεία της διαδοχικά, εκτός από τα κρυμμένα στοιχεία, τα οποία αποδίδονται αυτόματα όταν η φόρμα τερματίζεται με την εντολή `</form>` ετικέτα. -Θα περιμένει το όνομα της αποδιδόμενης φόρμας στη μεταβλητή `$form`. +Χάρη στις ετικέτες `{input}` και `{label}`, μπορούμε εύκολα να δημιουργήσουμε ένα γενικό πρότυπο για οποιαδήποτε φόρμα. Θα επαναλαμβάνει και θα αποδίδει διαδοχικά όλα τα στοιχεία της, εκτός από τα κρυφά στοιχεία, τα οποία αποδίδονται αυτόματα κατά το κλείσιμο της φόρμας με την ετικέτα `</form>`. Το όνομα της αποδιδόμενης φόρμας θα αναμένεται στη μεταβλητή `$form`. ```latte <form n:name=$form class=form> @@ -179,16 +172,15 @@ protected function createComponentSignInForm(): Form </form> ``` -Οι χρησιμοποιούμενες αυτοκλειόμενες ετικέτες ζεύγους `{label .../}` εμφανίζουν τις ετικέτες που προέρχονται από τον ορισμό της φόρμας στον κώδικα PHP. +Οι χρησιμοποιούμενες αυτοκλειόμενες ζευγαρωτές ετικέτες `{label .../}` εμφανίζουν τις ετικέτες που προέρχονται από τον ορισμό της φόρμας στον κώδικα PHP. -Μπορείτε να αποθηκεύσετε αυτό το γενικό πρότυπο στο αρχείο `basic-form.latte` και για να αποδώσετε τη φόρμα, απλά να το συμπεριλάβετε και να περάσετε το όνομα της φόρμας (ή την περίπτωση) στην παράμετρο `$form`: +Αποθηκεύστε αυτό το γενικό πρότυπο, για παράδειγμα, στο αρχείο `basic-form.latte` και για να αποδώσετε τη φόρμα, αρκεί να το συμπεριλάβετε και να περάσετε το όνομα (ή την παρουσία) της φόρμας στην παράμετρο `$form`: ```latte {include basic-form.latte, form: signInForm} ``` -Αν θέλετε να επηρεάσετε την εμφάνιση μιας συγκεκριμένης φόρμας και να σχεδιάσετε ένα στοιχείο διαφορετικά, τότε ο ευκολότερος τρόπος είναι να προετοιμάσετε μπλοκ στο πρότυπο που μπορούν να αντικατασταθούν αργότερα. -Τα μπλοκ μπορούν επίσης να έχουν [δυναμικά ονόματα |latte:template-inheritance#dynamic-block-names], ώστε να μπορείτε να εισάγετε σε αυτά το όνομα του στοιχείου που πρόκειται να σχεδιαστεί. Για παράδειγμα: +Αν θέλετε να παρέμβετε στη μορφή μιας συγκεκριμένης φόρμας κατά την απόδοσή της και, για παράδειγμα, να αποδώσετε ένα στοιχείο διαφορετικά, ο ευκολότερος τρόπος είναι να προετοιμάσετε μπλοκ στο πρότυπο που θα μπορούν στη συνέχεια να αντικατασταθούν. Τα μπλοκ μπορούν επίσης να έχουν [δυναμικά ονόματα μπλοκ |latte:template-inheritance#Δυναμικά ονόματα μπλοκ], οπότε μπορείτε να εισαγάγετε σε αυτά και το όνομα του αποδιδόμενου στοιχείου. Για παράδειγμα: ```latte ... @@ -197,7 +189,7 @@ protected function createComponentSignInForm(): Form ... ``` -Για το στοιχείο π.χ. `username` αυτό δημιουργεί το μπλοκ `input-username`, το οποίο μπορεί εύκολα να παρακαμφθεί με τη χρήση της ετικέτας [{embed} |latte:template-inheritance#unit-inheritance]: +Για το στοιχείο, π.χ., `username`, δημιουργείται έτσι το μπλοκ `input-username`, το οποίο μπορεί εύκολα να αντικατασταθεί χρησιμοποιώντας την [ετικέτα {embed} |latte:template-inheritance#Κληρονομικότητα μονάδας embed]: ```latte {embed basic-form.latte, form: signInForm} @@ -209,7 +201,7 @@ protected function createComponentSignInForm(): Form {/embed} ``` -Εναλλακτικά, ολόκληρο το περιεχόμενο του προτύπου `basic-form.latte` μπορεί να [οριστεί |latte:template-inheritance#definitions] ως μπλοκ, συμπεριλαμβανομένης της παραμέτρου `$form`: +Εναλλακτικά, μπορείτε να [define |latte:template-inheritance#Ορισμοί define] ολόκληρο το περιεχόμενο του template `basic-form.latte` ως μπλοκ, συμπεριλαμβανομένης της παραμέτρου `$form`: ```latte {define basic-form, $form} @@ -219,7 +211,7 @@ protected function createComponentSignInForm(): Form {/define} ``` -Αυτό θα διευκολύνει ελαφρώς τη χρήση του: +Χάρη σε αυτό, η κλήση του θα είναι ελαφρώς απλούστερη: ```latte {embed basic-form, signInForm} @@ -227,31 +219,31 @@ protected function createComponentSignInForm(): Form {/embed} ``` -Χρειάζεται να εισάγετε το μπλοκ μόνο σε ένα σημείο, στην αρχή του προτύπου διάταξης: +Το μπλοκ αρκεί να εισαχθεί σε ένα μόνο σημείο, στην αρχή του προτύπου της διάταξης: ```latte {import basic-form.latte} ``` -Ειδικές περιπτώσεις .[#toc-special-cases] ------------------------------------------ +Ειδικές περιπτώσεις +------------------- -Αν χρειάζεται να αποδώσετε μόνο το εσωτερικό περιεχόμενο μιας φόρμας χωρίς `<form>` & `</form>` ετικέτες HTML, για παράδειγμα, σε μια αίτηση AJAX, μπορείτε να ανοίξετε και να κλείσετε τη φόρμα με το `{formContext} … {/formContext}`. Λειτουργεί παρόμοια με το `{form}` με μια λογική έννοια, εδώ σας επιτρέπει να χρησιμοποιήσετε άλλες ετικέτες για να σχεδιάσετε στοιχεία της φόρμας, αλλά ταυτόχρονα δεν σχεδιάζει τίποτα. +Εάν χρειάζεστε να αποδώσετε μόνο το εσωτερικό μέρος της φόρμας χωρίς τις ετικέτες HTML `<form>`, για παράδειγμα κατά την αποστολή αποσπασμάτων, αποκρύψτε τις χρησιμοποιώντας το χαρακτηριστικό `n:tag-if`: ```latte -{formContext signForm} +<form n:name=signInForm n:tag-if=false> <div> - <label n:name=username>Username: <input n:name=username></label> + <label n:name=username>Όνομα χρήστη: <input n:name=username></label> {inputError username} </div> -{/formContext} +</form> ``` -Η ετικέτα `formContainer` βοηθά στην απόδοση των εισόδων μέσα σε ένα δοχείο φόρμας. +Η ετικέτα `{formContainer}` βοηθά στην απόδοση των στοιχείων μέσα σε ένα κοντέινερ φόρμας. ```latte -<p>Which news you wish to receive:</p> +<p>Ποιες ειδήσεις θέλετε να λαμβάνετε:</p> {formContainer emailNews} <ul> @@ -262,27 +254,27 @@ protected function createComponentSignInForm(): Form ``` -Εκτέλεση χωρίς Latte .[#toc-rendering-without-latte] -==================================================== +Απόδοση χωρίς Latte +=================== -Ο ευκολότερος τρόπος για να αποδώσετε μια φόρμα είναι να καλέσετε: +Ο απλούστερος τρόπος για να αποδοθεί μια φόρμα είναι να καλέσετε: ```php $form->render(); ``` -Η εμφάνιση της φόρμας που αποδίδεται μπορεί να αλλάξει με τη ρύθμιση των παραμέτρων [του Renderer |#Renderer] και των [μεμονωμένων στοιχείων ελέγχου |#HTML Attributes]. +Μπορείτε να επηρεάσετε την εμφάνιση της φόρμας που αποδίδεται με αυτόν τον τρόπο διαμορφώνοντας τον [#Renderer] και τα [μεμονωμένα στοιχεία ελέγχου |#Χαρακτηριστικά HTML]. -Χειροκίνητη απόδοση .[#toc-manual-rendering] --------------------------------------------- +Χειροκίνητη απόδοση +------------------- -Κάθε στοιχείο φόρμας διαθέτει μεθόδους που δημιουργούν τον κώδικα HTML για το πεδίο και την ετικέτα της φόρμας. Μπορούν να τον επιστρέψουν είτε ως συμβολοσειρά είτε ως αντικείμενο [Nette\Utils\Html |utils:html-elements]: +Κάθε στοιχείο φόρμας διαθέτει μεθόδους που παράγουν τον κώδικα HTML του πεδίου της φόρμας και της ετικέτας. Μπορούν να τον επιστρέψουν είτε ως συμβολοσειρά είτε ως αντικείμενο [Nette\Utils\Html |utils:html-elements]: - `getControl(): Html|string` επιστρέφει τον κώδικα HTML του στοιχείου -- `getLabel($caption = null): Html|string|null` επιστρέφει τον κώδικα HTML της ετικέτας, εάν υπάρχει. +- `getLabel($caption = null): Html|string|null` επιστρέφει τον κώδικα HTML της ετικέτας, αν υπάρχει -Αυτό επιτρέπει την απόδοση της φόρμας ανά στοιχείο: +Έτσι, η φόρμα μπορεί να αποδοθεί ανά μεμονωμένο στοιχείο: ```php <?php $form->render('begin') ?> @@ -305,47 +297,46 @@ $form->render(); <?php $form->render('end') ?> ``` -Ενώ για ορισμένα στοιχεία το `getControl()` επιστρέφει ένα μόνο στοιχείο HTML (π.χ. `<input>`, `<select>` κ.λπ.), για άλλα επιστρέφει ένα ολόκληρο κομμάτι κώδικα HTML (CheckboxList, RadioList). -Σε αυτή την περίπτωση, μπορείτε να χρησιμοποιήσετε μεθόδους που παράγουν μεμονωμένες εισόδους και ετικέτες, για κάθε στοιχείο ξεχωριστά: +Ενώ σε ορισμένα στοιχεία η `getControl()` επιστρέφει ένα μοναδικό στοιχείο HTML (π.χ. `<input>`, `<select>` κ.λπ.), σε άλλα επιστρέφει ένα ολόκληρο κομμάτι κώδικα HTML (CheckboxList, RadioList). Σε αυτή την περίπτωση, μπορείτε να χρησιμοποιήσετε μεθόδους που παράγουν μεμονωμένες εισόδους και ετικέτες, για κάθε στοιχείο ξεχωριστά: -- `getControlPart($key = null): ?Html` επιστρέφει τον κώδικα HTML ενός μεμονωμένου στοιχείου. -- `getLabelPart($key = null): ?Html` επιστρέφει τον κώδικα HTML για την ετικέτα ενός μεμονωμένου στοιχείου. +- `getControlPart($key = null): ?Html` επιστρέφει τον κώδικα HTML ενός μεμονωμένου στοιχείου +- `getLabelPart($key = null): ?Html` επιστρέφει τον κώδικα HTML της ετικέτας ενός μεμονωμένου στοιχείου .[note] -Αυτές οι μέθοδοι έχουν ως πρόθεμα το `get` για ιστορικούς λόγους, αλλά το `generate` θα ήταν καλύτερο, καθώς δημιουργεί και επιστρέφει ένα νέο στοιχείο `Html` σε κάθε κλήση. +Αυτές οι μέθοδοι έχουν το πρόθεμα `get` για ιστορικούς λόγους, αλλά το `generate` θα ήταν καλύτερο, επειδή σε κάθε κλήση δημιουργούν και επιστρέφουν ένα νέο στοιχείο `Html`. -Renderer .[#toc-renderer] -========================= +Renderer +======== -Είναι ένα αντικείμενο που παρέχει την απόδοση της φόρμας. Μπορεί να οριστεί από τη μέθοδο `$form->setRenderer`. Μεταβιβάζεται ο έλεγχος όταν καλείται η μέθοδος `$form->render()`. +Πρόκειται για ένα αντικείμενο που εξασφαλίζει την απόδοση της φόρμας. Μπορεί να οριστεί με τη μέθοδο `$form->setRenderer`. Ο έλεγχος του περνά όταν καλείται η μέθοδος `$form->render()`. -Εάν δεν ορίσουμε έναν προσαρμοσμένο renderer, θα χρησιμοποιηθεί ο προεπιλεγμένος renderer [api:Nette\Forms\Rendering\DefaultFormRenderer]. Αυτός θα αποδώσει τα στοιχεία της φόρμας ως πίνακα HTML. Η έξοδος μοιάζει με αυτό: +Εάν δεν ορίσουμε δικό μας renderer, θα χρησιμοποιηθεί ο προεπιλεγμένος renderer [api:Nette\Forms\Rendering\DefaultFormRenderer]. Αυτός αποδίδει τα στοιχεία της φόρμας με τη μορφή πίνακα HTML. Η έξοδος μοιάζει κάπως έτσι: ```latte <table> <tr class="required"> - <th><label class="required" for="frm-name">Name:</label></th> + <th><label class="required" for="frm-name">Όνομα:</label></th> <td><input type="text" class="text" name="name" id="frm-name" required value=""></td> </tr> <tr class="required"> - <th><label class="required" for="frm-age">Age:</label></th> + <th><label class="required" for="frm-age">Ηλικία:</label></th> <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> </tr> <tr> - <th><label>Gender:</label></th> + <th><label>Φύλο:</label></th> ... ``` -Πολλοί σχεδιαστές ιστοσελίδων προτιμούν διαφορετικά markups, για παράδειγμα μια λίστα. Μπορούμε να ρυθμίσουμε το `DefaultFormRenderer` έτσι ώστε να μην αποδίδεται καθόλου σε πίνακα. Απλά πρέπει να ορίσουμε τα κατάλληλα [$wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers]. Ο πρώτος δείκτης αντιπροσωπεύει πάντα μια περιοχή και ο δεύτερος το στοιχείο της. Όλες οι αντίστοιχες περιοχές εμφανίζονται στην εικόνα: +Το αν θα χρησιμοποιηθεί ή όχι πίνακας για τη δομή της φόρμας είναι αμφιλεγόμενο και πολλοί σχεδιαστές ιστοσελίδων προτιμούν άλλη σήμανση. Για παράδειγμα, μια λίστα ορισμών. Θα αναδιαμορφώσουμε λοιπόν τον `DefaultFormRenderer` έτσι ώστε να αποδώσει τη φόρμα με τη μορφή λίστας. Η διαμόρφωση γίνεται επεξεργαζόμενοι τον πίνακα [$wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers]. Ο πρώτος δείκτης αντιπροσωπεύει πάντα την περιοχή και ο δεύτερος το χαρακτηριστικό της. Οι μεμονωμένες περιοχές απεικονίζονται στην εικόνα: -[* form-areas-en.webp *] +[* defaultformrenderer.webp *] -Από προεπιλογή, μια ομάδα `controls` τυλίγεται σε `<table>`, και κάθε `pair` είναι μια γραμμή πίνακα `<tr>` που περιέχει ένα ζεύγος `label` και `control` (κελιά `<th>` και `<td>`). Ας αλλάξουμε όλα αυτά τα στοιχεία περιτύλιξης. Θα τυλίξουμε το `controls` σε `<dl>`, θα αφήσουμε το `pair` μόνο του, θα βάλουμε το `label` στο `<dt>` και θα τυλίξουμε το `control` στο `<dd>`: +Από προεπιλογή, η ομάδα στοιχείων `controls` περιβάλλεται από έναν πίνακα `<table>`, κάθε `pair` αντιπροσωπεύει μια γραμμή του πίνακα `<tr>` και το ζεύγος `label` και `control` είναι κελιά `<th>` και `<td>`. Τώρα θα αλλάξουμε τα περιβάλλοντα στοιχεία. Θα εισαγάγουμε την περιοχή `controls` σε ένα container `<dl>`, την περιοχή `pair` θα την αφήσουμε χωρίς container, το `label` θα το εισαγάγουμε σε `<dt>` και τέλος το `control` θα το περιβάλλουμε με ετικέτες `<dd>`: ```php $renderer = $form->getRenderer(); @@ -357,193 +348,192 @@ $renderer->wrappers['control']['container'] = 'dd'; $form->render(); ``` -Αποτελέσματα στο ακόλουθο απόσπασμα: +Το αποτέλεσμα είναι ο ακόλουθος κώδικας HTML: ```latte <dl> - <dt><label class="required" for="frm-name">Name:</label></dt> + <dt><label class="required" for="frm-name">Όνομα:</label></dt> <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> - <dt><label class="required" for="frm-age">Age:</label></dt> + <dt><label class="required" for="frm-age">Ηλικία:</label></dt> <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> - <dt><label>Gender:</label></dt> + <dt><label>Φύλο:</label></dt> ... </dl> ``` -Τα περιτυλίγματα μπορούν να επηρεάσουν πολλά χαρακτηριστικά. Για παράδειγμα: +Στον πίνακα wrappers μπορείτε να επηρεάσετε πολλά άλλα χαρακτηριστικά: -- να προσθέσετε ειδικές κλάσεις CSS σε κάθε είσοδο φόρμας -- διάκριση μεταξύ μονών και ζυγών γραμμών -- να σχεδιάζετε διαφορετικά τα υποχρεωτικά και τα προαιρετικά -- ορίστε, αν τα μηνύματα σφάλματος εμφανίζονται πάνω από τη φόρμα ή κοντά σε κάθε στοιχείο +- προσθήκη κλάσεων CSS σε μεμονωμένους τύπους στοιχείων φόρμας +- διάκριση μονών και ζυγών γραμμών με κλάση CSS +- οπτική διάκριση υποχρεωτικών και προαιρετικών στοιχείων +- καθορισμός αν τα μηνύματα σφάλματος θα εμφανίζονται απευθείας στα στοιχεία ή πάνω από τη φόρμα -Επιλογές .[#toc-options] ------------------------- +Options +------- -Η συμπεριφορά του Renderer μπορεί επίσης να ελεγχθεί με τον καθορισμό *επιλογών* σε μεμονωμένα στοιχεία της φόρμας. Με αυτόν τον τρόπο μπορείτε να ορίσετε το tooltip που εμφανίζεται δίπλα στο πεδίο εισαγωγής: +Η συμπεριφορά του Renderer μπορεί επίσης να ελεγχθεί ορίζοντας *options* στα μεμονωμένα στοιχεία της φόρμας. Με αυτόν τον τρόπο μπορείτε να ορίσετε την ετικέτα που θα εκτυπωθεί δίπλα στο πεδίο εισαγωγής: ```php -$form->addText('phone', 'Number:') - ->setOption('description', 'This number will remain hidden'); +$form->addText('phone', 'Αριθμός:') + ->setOption('description', 'Αυτός ο αριθμός θα παραμείνει κρυφός'); ``` -Αν θέλουμε να τοποθετήσουμε περιεχόμενο HTML σε αυτό, χρησιμοποιούμε την κλάση [Html |utils:html-elements]. +Εάν θέλουμε να τοποθετήσουμε περιεχόμενο HTML σε αυτό, θα χρησιμοποιήσουμε την [κλάση Html |utils:html-elements] ```php use Nette\Utils\Html; -$form->addText('phone', 'Phone:') +$form->addText('phone', 'Αριθμός:') ->setOption('description', Html::el('p') - ->setHtml('<a href="...">Terms of service.</a>') + ->setHtml('<a href="...">Όροι διατήρησης του αριθμού σας</a>') ); ``` .[tip] -Το στοιχείο Html μπορεί επίσης να χρησιμοποιηθεί αντί της ετικέτας: `$form->addCheckbox('conditions', $label)`. +Το στοιχείο Html μπορεί επίσης να χρησιμοποιηθεί αντί για ετικέτα: `$form->addCheckbox('conditions', $label)`. -Ομαδοποίηση εισόδων .[#toc-grouping-inputs] -------------------------------------------- +Ομαδοποίηση στοιχείων +--------------------- -Το Renderer επιτρέπει την ομαδοποίηση στοιχείων σε οπτικές ομάδες (σύνολα πεδίων): +Ο Renderer επιτρέπει την ομαδοποίηση στοιχείων σε οπτικές ομάδες (fieldsets): ```php -$form->addGroup('Personal data'); +$form->addGroup('Προσωπικά δεδομένα'); ``` -Η δημιουργία νέας ομάδας την ενεργοποιεί - όλα τα στοιχεία που προστίθενται περαιτέρω προστίθενται σε αυτή την ομάδα. Μπορείτε να δημιουργήσετε μια φόρμα ως εξής: +Μετά τη δημιουργία μιας νέας ομάδας, αυτή γίνεται ενεργή και κάθε νεοεισερχόμενο στοιχείο προστίθεται ταυτόχρονα και σε αυτήν. Έτσι, η φόρμα μπορεί να κατασκευαστεί με αυτόν τον τρόπο: ```php $form = new Form; -$form->addGroup('Personal data'); -$form->addText('name', 'Your name:'); -$form->addInteger('age', 'Your age:'); +$form->addGroup('Προσωπικά δεδομένα'); +$form->addText('name', 'Το όνομά σας:'); +$form->addInteger('age', 'Η ηλικία σας:'); $form->addEmail('email', 'Email:'); -$form->addGroup('Shipping address'); -$form->addCheckbox('send', 'Ship to address'); -$form->addText('street', 'Street:'); -$form->addText('city', 'City:'); -$form->addSelect('country', 'Country:', $countries); +$form->addGroup('Διεύθυνση αποστολής'); +$form->addCheckbox('send', 'Αποστολή στη διεύθυνση'); +$form->addText('street', 'Οδός:'); +$form->addText('city', 'Πόλη:'); +$form->addSelect('country', 'Χώρα:', $countries); ``` +Ο Renderer αποδίδει πρώτα τις ομάδες και μετά τα στοιχεία που δεν ανήκουν σε καμία ομάδα. + -Υποστήριξη Bootstrap .[#toc-bootstrap-support] ----------------------------------------------- +Υποστήριξη για Bootstrap +------------------------ -Μπορείτε να βρείτε [παραδείγματα |https://github.com/nette/forms/tree/master/examples] διαμόρφωσης του Renderer για [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], [Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] και [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php] +[Στα παραδείγματα |https://github.com/nette/forms/tree/master/examples] θα βρείτε παραδείγματα για το πώς να διαμορφώσετε τον Renderer για [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], [Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] και [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php] -Χαρακτηριστικά HTML .[#toc-html-attributes] -=========================================== +Χαρακτηριστικά HTML +=================== -Μπορείτε να ορίσετε οποιαδήποτε χαρακτηριστικά HTML σε στοιχεία ελέγχου φόρμας χρησιμοποιώντας το `setHtmlAttribute(string $name, $value = true)`: +Για να ορίσετε οποιαδήποτε χαρακτηριστικά HTML για τα στοιχεία της φόρμας, χρησιμοποιούμε τη μέθοδο `setHtmlAttribute(string $name, $value = true)`: ```php -$form->addInteger('number', 'Number:') +$form->addInteger('number', 'Αριθμός:') ->setHtmlAttribute('class', 'big-number'); -$form->addSelect('rank', 'Order by:', ['price', 'name']) - ->setHtmlAttribute('onchange', 'submit()'); // καλεί τη συνάρτηση JS submit() κατά την αλλαγή +$form->addSelect('rank', 'Ταξινόμηση κατά:', ['τιμής', 'ονόματος']) + ->setHtmlAttribute('onchange', 'submit()'); // υποβολή κατά την αλλαγή -// applying on <form> +// Για να ορίσετε χαρακτηριστικά για το ίδιο το <form> $form->setHtmlAttribute('id', 'myForm'); ``` -Ρύθμιση τύπου εισόδου: +Προδιαγραφή του τύπου του στοιχείου: ```php -$form->addText('tel', 'Your telephone:') +$form->addText('tel', 'Το τηλέφωνό σας:') ->setHtmlType('tel') - ->setHtmlAttribute('placeholder', 'Please, fill in your telephone'); + ->setHtmlAttribute('placeholder', 'γράψτε το τηλέφωνο'); ``` -Μπορούμε να ορίσουμε το χαρακτηριστικό HTML σε μεμονωμένα στοιχεία σε λίστες ραδιοφώνου ή checkbox με διαφορετικές τιμές για κάθε ένα από αυτά. -Προσέξτε την άνω και κάτω τελεία μετά το `style:` για να διασφαλίσετε ότι η τιμή επιλέγεται με βάση το κλειδί: +.[warning] +Η ρύθμιση του τύπου και άλλων χαρακτηριστικών χρησιμεύει μόνο για οπτικούς σκοπούς. Η επαλήθευση της ορθότητας των εισόδων πρέπει να γίνεται στον διακομιστή, πράγμα που εξασφαλίζετε επιλέγοντας το κατάλληλο [στοιχείο φόρμας |controls] και δηλώνοντας [κανόνες επικύρωσης |validation]. + +Μπορούμε να ορίσουμε χαρακτηριστικά HTML για μεμονωμένα στοιχεία σε λίστες radio ή checkbox με διαφορετικές τιμές για καθένα από αυτά. Παρατηρήστε την άνω και κάτω τελεία μετά το `style:`, η οποία εξασφαλίζει την επιλογή της τιμής βάσει κλειδιού: ```php -$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue']; +$colors = ['r' => 'κόκκινο', 'g' => 'πράσινο', 'b' => 'μπλε']; $styles = ['r' => 'background:red', 'g' => 'background:green']; -$form->addCheckboxList('colors', 'Colors:', $colors) +$form->addCheckboxList('colors', 'Χρώματα:', $colors) ->setHtmlAttribute('style:', $styles); ``` -Renders: +Εκτυπώνει: ```latte -<label><input type="checkbox" name="colors[]" style="background:red" value="r">red</label> -<label><input type="checkbox" name="colors[]" style="background:green" value="g">green</label> -<label><input type="checkbox" name="colors[]" value="b">blue</label> +<label><input type="checkbox" name="colors[]" style="background:red" value="r">κόκκινο</label> +<label><input type="checkbox" name="colors[]" style="background:green" value="g">πράσινο</label> +<label><input type="checkbox" name="colors[]" value="b">μπλε</label> ``` -Για ένα λογικό χαρακτηριστικό HTML (το οποίο δεν έχει τιμή, όπως το `readonly`), μπορείτε να χρησιμοποιήσετε ένα ερωτηματικό: +Για να ορίσετε λογικά χαρακτηριστικά, όπως το `readonly`, μπορούμε να χρησιμοποιήσουμε τη σύνταξη με ερωτηματικό: ```php -$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue']; -$form->addCheckboxList('colors', 'Colors:', $colors) - ->setHtmlAttribute('readonly?', 'r'); // χρήση πίνακα για πολλαπλά κλειδιά, π.χ. ['r', 'g'] +$form->addCheckboxList('colors', 'Χρώματα:', $colors) + ->setHtmlAttribute('readonly?', 'r'); // για πολλαπλά κλειδιά χρησιμοποιήστε έναν πίνακα, π.χ. ['r', 'g'] ``` -Renders: +Εκτυπώνει: ```latte -<label><input type="checkbox" name="colors[]" readonly value="r">red</label> -<label><input type="checkbox" name="colors[]" value="g">green</label> -<label><input type="checkbox" name="colors[]" value="b">blue</label> +<label><input type="checkbox" name="colors[]" readonly value="r">κόκκινο</label> +<label><input type="checkbox" name="colors[]" value="g">πράσινο</label> +<label><input type="checkbox" name="colors[]" value="b">μπλε</label> ``` -Για τα selectboxes, η μέθοδος `setHtmlAttribute()` ορίζει τα χαρακτηριστικά του `<select>` στοιχείου. Αν θέλουμε να ορίσουμε τα χαρακτηριστικά για κάθε -`<option>`, θα χρησιμοποιήσουμε τη μέθοδο `setOptionAttribute()`. Επίσης, η άνω τελεία και το ερωτηματικό που χρησιμοποιήθηκαν παραπάνω λειτουργούν: +Στην περίπτωση των selectbox, η μέθοδος `setHtmlAttribute()` ορίζει τα χαρακτηριστικά του στοιχείου `<select>`. Εάν θέλουμε να ορίσουμε χαρακτηριστικά για μεμονωμένα `<option>`, χρησιμοποιούμε τη μέθοδο `setOptionAttribute()`. Λειτουργούν επίσης οι συντάξεις με άνω και κάτω τελεία και ερωτηματικό που αναφέρθηκαν παραπάνω: ```php -$form->addSelect('colors', 'Colors:', $colors) +$form->addSelect('colors', 'Χρώματα:', $colors) ->setOptionAttribute('style:', $styles); ``` -Renders: +Εκτυπώνει: ```latte <select name="colors"> - <option value="r" style="background:red">red</option> - <option value="g" style="background:green">green</option> - <option value="b">blue</option> + <option value="r" style="background:red">κόκκινο</option> + <option value="g" style="background:green">πράσινο</option> + <option value="b">μπλε</option> </select> ``` -Πρωτότυπα .[#toc-prototypes] ----------------------------- +Πρωτότυπα +--------- -Ένας εναλλακτικός τρόπος για να ορίσετε χαρακτηριστικά HTML είναι να τροποποιήσετε το πρότυπο από το οποίο παράγεται το στοιχείο HTML. Το πρότυπο είναι ένα αντικείμενο `Html` και επιστρέφεται από τη μέθοδο `getControlPrototype()`: +Ένας εναλλακτικός τρόπος ορισμού των χαρακτηριστικών HTML συνίσταται στην τροποποίηση του προτύπου από το οποίο παράγεται το στοιχείο HTML. Το πρότυπο είναι ένα αντικείμενο `Html` και το επιστρέφει η μέθοδος `getControlPrototype()`: ```php -$input = $form->addInteger('number'); +$input = $form->addInteger('number', 'Αριθμός:'); $html = $input->getControlPrototype(); // <input> $html->class('big-number'); // <input class="big-number"> ``` -Το πρότυπο ετικέτας που επιστρέφεται από την `getLabelPrototype()` μπορεί επίσης να τροποποιηθεί με αυτόν τον τρόπο: +Με αυτόν τον τρόπο μπορείτε να τροποποιήσετε και το πρότυπο της ετικέτας, το οποίο επιστρέφει η `getLabelPrototype()`: ```php $html = $input->getLabelPrototype(); // <label> $html->class('distinctive'); // <label class="distinctive"> ``` -Για τα στοιχεία Checkbox, CheckboxList και RadioList μπορείτε να επηρεάσετε το πρότυπο στοιχείου που περιβάλλει το στοιχείο. Επιστρέφεται από το `getContainerPrototype()`. Από προεπιλογή είναι ένα "κενό" στοιχείο, οπότε δεν αποδίδεται τίποτα, αλλά δίνοντάς του ένα όνομα θα αποδίδεται: +Στα στοιχεία Checkbox, CheckboxList και RadioList μπορείτε να επηρεάσετε το πρότυπο του στοιχείου που περιβάλλει ολόκληρο το στοιχείο. Το επιστρέφει η `getContainerPrototype()`. Στην προεπιλεγμένη κατάσταση, πρόκειται για ένα «κενό» στοιχείο, οπότε δεν αποδίδεται τίποτα, αλλά ορίζοντας του ένα όνομα, θα αποδίδεται: ```php $input = $form->addCheckbox('send'); -echo $input->getControl(); -// <label><input type="checkbox" name="send"></label> - $html = $input->getContainerPrototype(); $html->setName('div'); // <div> $html->class('check'); // <div class="check"> @@ -551,50 +541,49 @@ echo $input->getControl(); // <div class="check"><label><input type="checkbox" name="send"></label></div> ``` -Στην περίπτωση των CheckboxList και RadioList είναι επίσης δυνατό να επηρεαστεί το μοτίβο διαχωρισμού στοιχείων που επιστρέφεται από τη μέθοδο `getSeparatorPrototype()`. Από προεπιλογή, είναι ένα στοιχείο `<br>`. Αν το αλλάξετε σε στοιχείο ζεύγους, θα τυλίγει τα μεμονωμένα στοιχεία αντί να τα διαχωρίζει. -Είναι επίσης δυνατό να επηρεάσετε το πρότυπο στοιχείου HTML των ετικετών στοιχείων, το οποίο επιστρέφει η μέθοδος `getItemLabelPrototype()`. +Στην περίπτωση των CheckboxList και RadioList μπορείτε να επηρεάσετε και το πρότυπο του διαχωριστή των μεμονωμένων στοιχείων, το οποίο επιστρέφει η μέθοδος `getSeparatorPrototype()`. Στην προεπιλεγμένη κατάσταση, είναι το στοιχείο `<br>`. Εάν το αλλάξετε σε ζευγαρωτό στοιχείο, θα περιβάλλει τα μεμονωμένα στοιχεία αντί να τα διαχωρίζει. Και επιπλέον, μπορείτε να επηρεάσετε το πρότυπο του στοιχείου HTML της ετικέτας στα μεμονωμένα στοιχεία, το οποίο επιστρέφει η `getItemLabelPrototype()`. -Μετάφραση .[#toc-translating] -============================= +Μετάφραση +========= -Αν προγραμματίζετε μια πολύγλωσση εφαρμογή, πιθανόν να χρειαστεί να αποδώσετε τη φόρμα σε διαφορετικές γλώσσες. Το Nette Framework ορίζει μια διεπαφή μετάφρασης για το σκοπό αυτό [api:Nette\Localization\Translator]. Δεν υπάρχει προεπιλεγμένη υλοποίηση στο Nette, μπορείτε να επιλέξετε ανάλογα με τις ανάγκες σας από διάφορες έτοιμες λύσεις που μπορείτε να βρείτε στο [Componette |https://componette.org/search/localization]. Η τεκμηρίωσή τους σας ενημερώνει για τον τρόπο διαμόρφωσης του μεταφραστή. +Εάν προγραμματίζετε μια πολύγλωσση εφαρμογή, πιθανότατα θα χρειαστείτε να αποδώσετε τη φόρμα σε διάφορες γλωσσικές εκδόσεις. Το Nette Framework ορίζει για αυτόν τον σκοπό μια διεπαφή για μετάφραση [api:Nette\Localization\Translator]. Στη Nette δεν υπάρχει προεπιλεγμένη υλοποίηση, μπορείτε να επιλέξετε ανάλογα με τις ανάγκες σας από διάφορες έτοιμες λύσεις που θα βρείτε στο [Componette |https://componette.org/search/localization]. Στην τεκμηρίωσή τους θα μάθετε πώς να διαμορφώσετε τον μεταφραστή. -Η φόρμα υποστηρίζει την έξοδο κειμένου μέσω του μεταφραστή. Το περνάμε χρησιμοποιώντας τη μέθοδο `setTranslator()`: +Οι φόρμες υποστηρίζουν την εκτύπωση κειμένων μέσω του μεταφραστή. Τον περνάμε σε αυτές χρησιμοποιώντας τη μέθοδο `setTranslator()`: ```php $form->setTranslator($translator); ``` -Από εδώ και στο εξής, όχι μόνο όλες οι ετικέτες, αλλά και όλα τα μηνύματα σφάλματος ή οι καταχωρίσεις σε πλαίσια επιλογής θα μεταφράζονται σε άλλη γλώσσα. +Από αυτή τη στιγμή, όχι μόνο όλες οι ετικέτες, αλλά και όλα τα μηνύματα σφάλματος ή τα στοιχεία των πλαισίων επιλογής μεταφράζονται σε άλλη γλώσσα. -Είναι δυνατό να ορίσετε έναν διαφορετικό μεταφραστή για μεμονωμένα στοιχεία της φόρμας ή να απενεργοποιήσετε εντελώς τη μετάφραση με το `null`: +Στα μεμονωμένα στοιχεία της φόρμας, είναι δυνατόν να οριστεί διαφορετικός μεταφραστής ή να απενεργοποιηθεί εντελώς η μετάφραση με την τιμή `null`: ```php -$form->addSelect('carModel', 'Model:', $cars) +$form->addSelect('carModel', 'Μοντέλο:', $cars) ->setTranslator(null); ``` -Για τους [κανόνες επικύρωσης |validation], συγκεκριμένες παράμετροι μεταβιβάζονται επίσης στον μεταφραστή, για παράδειγμα για τον κανόνα: +Στους [κανόνες επικύρωσης|validation], περνούν στον μεταφραστή και συγκεκριμένες παράμετροι, για παράδειγμα στον κανόνα: ```php -$form->addPassword('password', 'Password:') - ->addRule($form::MinLength, 'Password has to be at least %d characters long', 8) +$form->addPassword('password', 'Κωδικός πρόσβασης:') + ->addRule($form::MinLength, 'Ο κωδικός πρόσβασης πρέπει να έχει τουλάχιστον %d χαρακτήρες', 8); ``` -ο μεταφραστής καλείται με τις ακόλουθες παραμέτρους: +καλείται ο μεταφραστής με αυτές τις παραμέτρους: ```php -$translator->translate('Password has to be at least %d characters long', 8); +$translator->translate('Ο κωδικός πρόσβασης πρέπει να έχει τουλάχιστον %d χαρακτήρες', 8); ``` -και έτσι μπορεί να επιλέξει τη σωστή μορφή πληθυντικού αριθμού για τη λέξη `characters` by count. +και επομένως μπορεί να επιλέξει τη σωστή μορφή πληθυντικού για τη λέξη `χαρακτήρες` ανάλογα με τον αριθμό. -Γεγονός onRender .[#toc-event-onrender] -======================================= +Το συμβάν onRender +================== -Λίγο πριν την απόδοση της φόρμας, μπορούμε να έχουμε την κλήση του κώδικά μας. Αυτό μπορεί, για παράδειγμα, να προσθέσει κλάσεις HTML στα στοιχεία της φόρμας για τη σωστή εμφάνιση. Προσθέτουμε τον κώδικα στον πίνακα `onRender`: +Λίγο πριν αποδοθεί η φόρμα, μπορούμε να αφήσουμε να κληθεί ο κώδικάς μας. Αυτός μπορεί, για παράδειγμα, να συμπληρώσει τα στοιχεία της φόρμας με κλάσεις HTML για σωστή εμφάνιση. Προσθέτουμε τον κώδικα στον πίνακα `onRender`: ```php $form->onRender[] = function ($form) { diff --git a/forms/el/standalone.texy b/forms/el/standalone.texy index 67275ad240..b660fa0178 100644 --- a/forms/el/standalone.texy +++ b/forms/el/standalone.texy @@ -1,116 +1,115 @@ -Φόρμες που χρησιμοποιούνται αυτόνομα -************************************ +Αυτόνομες Φόρμες +**************** .[perex] -Η Nette Forms διευκολύνει δραματικά τη δημιουργία και την επεξεργασία φορμών web. Μπορείτε να τις χρησιμοποιήσετε στις εφαρμογές σας εντελώς μόνες τους χωρίς το υπόλοιπο πλαίσιο, κάτι που θα παρουσιάσουμε σε αυτό το κεφάλαιο. +Οι φόρμες Nette διευκολύνουν κατά τάξεις μεγέθους τη δημιουργία και την επεξεργασία φορμών ιστού. Μπορείτε να τις χρησιμοποιήσετε στις εφαρμογές σας εντελώς ανεξάρτητα από το υπόλοιπο framework, όπως θα δείξουμε σε αυτό το κεφάλαιο. -Ωστόσο, αν χρησιμοποιείτε το Nette Application και τους παρουσιαστές, υπάρχει ένας οδηγός για εσάς: [Φόρμες στους παρουσιαστές |in-presenter]. +Ωστόσο, εάν χρησιμοποιείτε το Nette Application και presenters, ο οδηγός για [χρήση σε presenters|in-presenter] είναι για εσάς. -Πρώτη φόρμα .[#toc-first-form] -============================== +Πρώτη Φόρμα +=========== -Θα προσπαθήσουμε να γράψουμε μια απλή φόρμα εγγραφής. Ο κώδικάς της θα μοιάζει ως εξής("πλήρης κώδικας":https://gist.github.com/dg/370a7e3094d9ba9a9e913b8e2a2dc851): +Ας προσπαθήσουμε να γράψουμε μια απλή φόρμα εγγραφής. Ο κώδικάς της θα είναι ο ακόλουθος ("πλήρης κώδικας":https://gist.github.com/dg/57878c1a413ae8ef0c1d83f02c43ef3f): ```php use Nette\Forms\Form; $form = new Form; -$form->addText('name', 'Name:'); -$form->addPassword('password', 'Password:'); -$form->addSubmit('send', 'Sign up'); +$form->addText('name', 'Όνομα:'); +$form->addPassword('password', 'Κωδικός πρόσβασης:'); +$form->addSubmit('send', 'Εγγραφή'); ``` -Και ας την απεικονίσουμε: +Μπορούμε να την αποδώσουμε πολύ εύκολα: ```php $form->render(); ``` -και το αποτέλεσμα θα πρέπει να μοιάζει με αυτό: +και θα εμφανιστεί στον περιηγητή ως εξής: -[* form-en.webp *] +[* form-cs.webp *] -Η φόρμα είναι ένα αντικείμενο της κλάσης `Nette\Forms\Form` (η κλάση `Nette\Application\UI\Form` χρησιμοποιείται στους παρουσιαστές). Προσθέσαμε σε αυτήν τα στοιχεία ελέγχου όνομα, κωδικός πρόσβασης και κουμπί αποστολής. +Η φόρμα είναι ένα αντικείμενο της κλάσης `Nette\Forms\Form` (η κλάση `Nette\Application\UI\Form` χρησιμοποιείται σε presenters). Προσθέσαμε σε αυτή τα λεγόμενα στοιχεία: όνομα, κωδικό πρόσβασης και ένα κουμπί υποβολής. -Τώρα θα αναβιώσουμε τη φόρμα. Ρωτώντας το `$form->isSuccess()`, θα μάθουμε αν η φόρμα υποβλήθηκε και αν συμπληρώθηκε έγκυρα. Εάν ναι, θα απορρίψουμε τα δεδομένα. Μετά τον ορισμό της φόρμας θα προσθέσουμε: +Τώρα, ας ζωντανέψουμε τη φόρμα. Ρωτώντας `$form->isSuccess()`, θα μάθουμε αν η φόρμα υποβλήθηκε και αν συμπληρώθηκε έγκυρα. Αν ναι, θα εμφανίσουμε τα δεδομένα. Έτσι, μετά τον ορισμό της φόρμας, προσθέτουμε: ```php if ($form->isSuccess()) { - echo 'The form has been filled in and submitted correctly'; + echo 'Η φόρμα υποβλήθηκε και επικυρώθηκε με επιτυχία'; $data = $form->getValues(); - // $data->name περιέχει όνομα - // $data->password περιέχει κωδικό πρόσβασης + // το $data->name περιέχει το όνομα + // το $data->password περιέχει τον κωδικό πρόσβασης var_dump($data); } ``` -Η μέθοδος `getValues()` επιστρέφει τα δεδομένα που αποστέλλονται με τη μορφή ενός αντικειμένου [ArrayHash |utils:arrays#ArrayHash]. Θα δείξουμε πώς να το αλλάξουμε αυτό [αργότερα |#Mapping to Classes]. Η μεταβλητή `$data` περιέχει τα κλειδιά `name` και `password` με τα δεδομένα που εισήγαγε ο χρήστης. +Η μέθοδος `getValues()` επιστρέφει τα υποβληθέντα δεδομένα με τη μορφή ενός αντικειμένου [ArrayHash |utils:arrays#ArrayHash]. Θα δείξουμε πώς να το αλλάξουμε [αργότερα |#Αντιστοίχιση σε Κλάσεις]. Το αντικείμενο `$data` περιέχει τα κλειδιά `name` και `password` με τα δεδομένα που συμπλήρωσε ο χρήστης. -Συνήθως στέλνουμε τα δεδομένα απευθείας για περαιτέρω επεξεργασία, η οποία μπορεί να είναι, για παράδειγμα, η εισαγωγή στη βάση δεδομένων. Ωστόσο, μπορεί να προκύψει κάποιο σφάλμα κατά την επεξεργασία, για παράδειγμα, το όνομα χρήστη είναι ήδη κατειλημμένο. Σε αυτή την περίπτωση, περνάμε το σφάλμα πίσω στη φόρμα χρησιμοποιώντας το `addError()` και αφήνουμε να ξανασχεδιαστεί, με ένα μήνυμα σφάλματος: +Συνήθως, στέλνουμε τα δεδομένα απευθείας για περαιτέρω επεξεργασία, η οποία μπορεί να είναι, για παράδειγμα, η εισαγωγή σε μια βάση δεδομένων. Ωστόσο, κατά την επεξεργασία, μπορεί να προκύψει σφάλμα, όπως ένα όνομα χρήστη που είναι ήδη κατειλημμένο. Σε αυτή την περίπτωση, επιστρέφουμε το σφάλμα στη φόρμα χρησιμοποιώντας το `addError()` και την αφήνουμε να αποδοθεί ξανά, μαζί με το μήνυμα σφάλματος. ```php -$form->addError('Sorry, username is already in use.'); +$form->addError('Συγγνώμη, αυτό το όνομα χρήστη χρησιμοποιείται ήδη.'); ``` -Μετά την επεξεργασία της φόρμας, θα κάνουμε ανακατεύθυνση στην επόμενη σελίδα. Αυτό αποτρέπει την ακούσια επαναφορά της φόρμας με το πάτημα του κουμπιού *refresh*, *back*, ή την μετακίνηση του ιστορικού του προγράμματος περιήγησης. +Μετά την επεξεργασία της φόρμας, ανακατευθύνουμε σε άλλη σελίδα. Αυτό αποτρέπει την ακούσια επανυποβολή της φόρμας με το κουμπί *ανανέωση*, *πίσω* ή με την κίνηση στο ιστορικό του προγράμματος περιήγησης. -Από προεπιλογή, η φόρμα αποστέλλεται με τη μέθοδο POST στην ίδια σελίδα. Και οι δύο μπορούν να αλλάξουν: +Η φόρμα υποβάλλεται από προεπιλογή χρησιμοποιώντας τη μέθοδο POST στην ίδια σελίδα. Και τα δύο μπορούν να αλλάξουν: ```php $form->setAction('/submit.php'); $form->setMethod('GET'); ``` -Και αυτό είναι όλο :-) Έχουμε μια λειτουργική και απόλυτα [ασφαλής |#Vulnerability Protection] φόρμα. +Και αυτό είναι όλο :-) Έχουμε μια λειτουργική και τέλεια [ασφαλή |#Προστασία από Ευπάθειες] φόρμα. -Δοκιμάστε να προσθέσετε περισσότερα [στοιχεία ελέγχου της φόρμας |controls]. +Προσπαθήστε να προσθέσετε και άλλα [στοιχεία φόρμας|controls]. -Πρόσβαση στα στοιχεία ελέγχου .[#toc-access-to-controls] -======================================================== +Πρόσβαση στα Στοιχεία +===================== -Η φόρμα και τα επιμέρους στοιχεία ελέγχου της ονομάζονται στοιχεία. Δημιουργούν ένα δέντρο συστατικών, όπου η ρίζα είναι η φόρμα. Μπορείτε να αποκτήσετε πρόσβαση στα επιμέρους στοιχεία ελέγχου ως εξής: +Ονομάζουμε τη φόρμα και τα μεμονωμένα στοιχεία της components. Σχηματίζουν ένα δέντρο components, όπου η ρίζα είναι η φόρμα. Μπορούμε να αποκτήσουμε πρόσβαση στα μεμονωμένα στοιχεία της φόρμας ως εξής: ```php $input = $form->getComponent('name'); -// εναλλακτική σύνταξη: όνομα'], +// εναλλακτική σύνταξη: $input = $form['name']; $button = $form->getComponent('send'); -// εναλλακτική σύνταξη: $button = $form['send'], +// εναλλακτική σύνταξη: $button = $form['send']; ``` -Τα στοιχεία ελέγχου αφαιρούνται με τη χρήση unset: +Τα στοιχεία αφαιρούνται χρησιμοποιώντας το unset: ```php unset($form['name']); ``` -Κανόνες επικύρωσης .[#toc-validation-rules] -=========================================== +Κανόνες Επικύρωσης +================== -Η λέξη *εγκυρότητα* χρησιμοποιήθηκε εδώ, αλλά η φόρμα δεν έχει ακόμη κανόνες επικύρωσης. Ας το διορθώσουμε. +Αναφέραμε τη λέξη *έγκυρη*, αλλά η φόρμα δεν έχει ακόμη κανόνες επικύρωσης. Ας το διορθώσουμε αυτό. -Το όνομα θα είναι υποχρεωτικό, οπότε θα το επισημάνουμε με τη μέθοδο `setRequired()`, της οποίας το όρισμα είναι το κείμενο του μηνύματος λάθους που θα εμφανιστεί αν ο χρήστης δεν το συμπληρώσει. Αν δεν δοθεί κανένα όρισμα, χρησιμοποιείται το προεπιλεγμένο μήνυμα σφάλματος. +Το όνομα θα είναι υποχρεωτικό, οπότε θα το επισημάνουμε με τη μέθοδο `setRequired()`. Το όρισμά της είναι το κείμενο του μηνύματος σφάλματος που θα εμφανιστεί εάν ο χρήστης δεν συμπληρώσει το όνομα. Εάν δεν παρέχουμε όρισμα, θα χρησιμοποιηθεί το προεπιλεγμένο μήνυμα σφάλματος. ```php -$form->addText('name', 'Name:') - ->setRequired('Please enter a name.'); +$form->addText('name', 'Όνομα:') + ->setRequired('Παρακαλώ εισάγετε το όνομά σας.'); ``` -Δοκιμάστε να στείλετε τη φόρμα χωρίς να έχει συμπληρωθεί το όνομα και θα δείτε ότι θα εμφανιστεί ένα μήνυμα σφάλματος και το πρόγραμμα περιήγησης ή ο διακομιστής θα την απορρίψει μέχρι να το συμπληρώσετε. +Προσπαθήστε να υποβάλετε τη φόρμα χωρίς να συμπληρώσετε το όνομα και θα δείτε ότι εμφανίζεται ένα μήνυμα σφάλματος, και ο περιηγητής ή ο διακομιστής θα αρνηθεί να την αποδεχτεί μέχρι να συμπληρώσετε το πεδίο. -Ταυτόχρονα, δεν θα μπορείτε να εξαπατήσετε το σύστημα πληκτρολογώντας μόνο κενά στην είσοδο, για παράδειγμα. Με κανέναν τρόπο. Η Nette κόβει αυτόματα τα κενά αριστερά και δεξιά. Δοκιμάστε το. Είναι κάτι που πρέπει να κάνετε πάντα με κάθε είσοδο μιας γραμμής, αλλά συχνά ξεχνιέται. Η Nette το κάνει αυτόματα. (Μπορείτε να προσπαθήσετε να ξεγελάσετε τις φόρμες και να στείλετε μια πολυγραμμή συμβολοσειρά ως όνομα. Ακόμα και σε αυτή την περίπτωση, η Nette δεν θα ξεγελαστεί και τα διαλείμματα γραμμής θα μετατραπούν σε κενά). +Ταυτόχρονα, δεν μπορείτε να εξαπατήσετε το σύστημα πληκτρολογώντας μόνο κενά στο πεδίο. Όχι. Το Nette αφαιρεί αυτόματα τα αρχικά και τα τελικά κενά. Δοκιμάστε το. Είναι κάτι που πρέπει πάντα να κάνετε με κάθε input μίας γραμμής, αλλά συχνά ξεχνιέται. Το Nette το κάνει αυτόματα. (Μπορείτε να προσπαθήσετε να εξαπατήσετε τη φόρμα στέλνοντας μια συμβολοσειρά πολλαπλών γραμμών ως όνομα. Ακόμα και εδώ, το Nette δεν θα ξεγελαστεί και θα αλλάξει τις αλλαγές γραμμής σε κενά.) -Η φόρμα επικυρώνεται πάντα στην πλευρά του διακομιστή, αλλά δημιουργείται επίσης επικύρωση μέσω JavaScript, η οποία είναι γρήγορη και ο χρήστης γνωρίζει το σφάλμα αμέσως, χωρίς να χρειάζεται να στείλει τη φόρμα στον διακομιστή. Αυτό το χειρίζεται η δέσμη ενεργειών `netteForms.js`. -Προσθέστε το στη σελίδα: +Η φόρμα επικυρώνεται πάντα από την πλευρά του διακομιστή, αλλά δημιουργείται επίσης επικύρωση JavaScript, η οποία εκτελείται αμέσως και ο χρήστης ενημερώνεται αμέσως για το σφάλμα, χωρίς να χρειάζεται να υποβάλει τη φόρμα στον διακομιστή. Αυτό γίνεται από το σενάριο `netteForms.js`. Εισάγετέ το στη σελίδα: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Αν κοιτάξετε στον πηγαίο κώδικα της σελίδας με τη φόρμα, μπορεί να παρατηρήσετε ότι η Nette εισάγει τα απαιτούμενα πεδία σε στοιχεία με κλάση CSS `required`. Δοκιμάστε να προσθέσετε το ακόλουθο στυλ στο πρότυπο, και η ετικέτα "Όνομα" θα είναι κόκκινη. Κομψά, επισημαίνουμε τα υποχρεωτικά πεδία για τους χρήστες: +Αν κοιτάξετε τον πηγαίο κώδικα της σελίδας με τη φόρμα, μπορείτε να παρατηρήσετε ότι το Nette εισάγει τα υποχρεωτικά στοιχεία σε στοιχεία με την κλάση CSS `required`. Προσπαθήστε να προσθέσετε το ακόλουθο φύλλο στυλ στο πρότυπο και η ετικέτα «Όνομα» θα είναι κόκκινη. Με αυτόν τον τρόπο, επισημαίνουμε κομψά τα υποχρεωτικά στοιχεία για τους χρήστες: ```latte <style> @@ -118,96 +117,96 @@ $form->addText('name', 'Name:') </style> ``` -Πρόσθετοι κανόνες επικύρωσης θα προστεθούν με τη μέθοδο `addRule()`. Η πρώτη παράμετρος είναι ο κανόνας, η δεύτερη είναι και πάλι το κείμενο του μηνύματος σφάλματος και μπορεί να ακολουθήσει το προαιρετικό όρισμα του κανόνα επικύρωσης. Τι σημαίνει αυτό; +Προσθέτουμε περαιτέρω κανόνες επικύρωσης χρησιμοποιώντας τη μέθοδο `addRule()`. Η πρώτη παράμετρος είναι ο κανόνας, η δεύτερη είναι ξανά το κείμενο του μηνύματος σφάλματος, και μπορεί να ακολουθήσει ένα όρισμα κανόνα επικύρωσης. Τι σημαίνει αυτό; -Η φόρμα θα λάβει μια άλλη προαιρετική είσοδο *ηλικία* με την προϋπόθεση, ότι πρέπει να είναι αριθμός (`addInteger()`) και σε συγκεκριμένα όρια (`$form::Range`). Και εδώ θα χρησιμοποιήσουμε το τρίτο όρισμα του `addRule()`, το ίδιο το εύρος: +Θα επεκτείνουμε τη φόρμα με ένα νέο προαιρετικό πεδίο «ηλικία», το οποίο πρέπει να είναι ακέραιος αριθμός (`addInteger()`) και επιπλέον εντός επιτρεπόμενου εύρους (`$form::Range`). Και εδώ θα χρησιμοποιήσουμε την τρίτη παράμετρο της μεθόδου `addRule()`, με την οποία περνάμε το απαιτούμενο εύρος στον επικυρωτή ως ζεύγος `[από, έως]`: ```php -$form->addInteger('age', 'Age:') - ->addRule($form::Range, 'You must be older 18 years and be under 120.', [18, 120]); +$form->addInteger('age', 'Ηλικία:') + ->addRule($form::Range, 'Η ηλικία πρέπει να είναι μεταξύ 18 και 120.', [18, 120]); ``` .[tip] -Εάν ο χρήστης δεν συμπληρώσει το πεδίο, οι κανόνες επικύρωσης δεν θα επαληθευτούν, επειδή το πεδίο είναι προαιρετικό. +Εάν ο χρήστης δεν συμπληρώσει το πεδίο, οι κανόνες επικύρωσης δεν θα ελεγχθούν, καθώς το στοιχείο είναι προαιρετικό. -Προφανώς υπάρχει περιθώριο για ένα μικρό refactoring. Στο μήνυμα σφάλματος και στην τρίτη παράμετρο, οι αριθμοί παρατίθενται εις διπλούν, πράγμα που δεν είναι ιδανικό. Αν δημιουργούσαμε μια [πολύγλωσση φόρμα |rendering#translating] και το μήνυμα που περιέχει αριθμούς θα έπρεπε να μεταφραστεί σε πολλές γλώσσες, θα δυσκόλευε την αλλαγή των τιμών. Για το λόγο αυτό, μπορούν να χρησιμοποιηθούν οι υποκατάστατοι χαρακτήρες `%d`: +Εδώ υπάρχει περιθώριο για μια μικρή αναδιάρθρωση. Στο μήνυμα σφάλματος και στην τρίτη παράμετρο, οι αριθμοί αναφέρονται διπλά, κάτι που δεν είναι ιδανικό. Εάν δημιουργούσαμε [πολύγλωσσες φόρμες |rendering#Μετάφραση] και το μήνυμα που περιέχει αριθμούς μεταφραζόταν σε πολλές γλώσσες, θα ήταν δύσκολο να αλλάξουμε τις τιμές αργότερα. Για το λόγο αυτό, είναι δυνατό να χρησιμοποιηθούν σύμβολα κράτησης θέσης `%d`, και το Nette θα συμπληρώσει τις τιμές: ```php - ->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]); + ->addRule($form::Range, 'Η ηλικία πρέπει να είναι μεταξύ %d και %d.', [18, 120]); ``` -Ας επιστρέψουμε στο πεδίο *password*, ας το κάνουμε *απαιτούμενο* και ας ελέγξουμε το ελάχιστο μήκος του κωδικού πρόσβασης (`$form::MinLength`), χρησιμοποιώντας και πάλι τους χαρακτήρες αντικατάστασης στο μήνυμα: +Ας επιστρέψουμε στο στοιχείο `password`, το οποίο θα κάνουμε επίσης υποχρεωτικό και θα ελέγξουμε επίσης το ελάχιστο μήκος του κωδικού πρόσβασης (`$form::MinLength`), χρησιμοποιώντας ξανά ένα σύμβολο κράτησης θέσης: ```php -$form->addPassword('password', 'Password:') - ->setRequired('Pick a password') - ->addRule($form::MinLength, 'Your password has to be at least %d long', 8); +$form->addPassword('password', 'Κωδικός πρόσβασης:') + ->setRequired('Επιλέξτε έναν κωδικό πρόσβασης.') + ->addRule($form::MinLength, 'Ο κωδικός πρόσβασης πρέπει να έχει μήκος τουλάχιστον %d χαρακτήρων.', 8); ``` -Θα προσθέσουμε ένα πεδίο `passwordVerify` στη φόρμα, όπου ο χρήστης θα εισάγει ξανά τον κωδικό πρόσβασης, για έλεγχο. Χρησιμοποιώντας κανόνες επικύρωσης, ελέγχουμε αν και οι δύο κωδικοί πρόσβασης είναι ίδιοι (`$form::Equal`). Και ως όρισμα δίνουμε μια αναφορά στον πρώτο κωδικό πρόσβασης χρησιμοποιώντας [αγκύλες |#Access to Controls]: +Θα προσθέσουμε ένα πεδίο `passwordVerify` στη φόρμα, όπου ο χρήστης εισάγει ξανά τον κωδικό πρόσβασης για επαλήθευση. Χρησιμοποιώντας κανόνες επικύρωσης, θα ελέγξουμε αν οι δύο κωδικοί πρόσβασης είναι ίδιοι (`$form::Equal`). Και ως παράμετρο, θα δώσουμε μια αναφορά στον πρώτο κωδικό πρόσβασης χρησιμοποιώντας [τετράγωνες αγκύλες |#Πρόσβαση στα Στοιχεία]: ```php -$form->addPassword('passwordVerify', 'Password again:') - ->setRequired('Fill your password again to check for typo') - ->addRule($form::Equal, 'Password mismatch', $form['password']) +$form->addPassword('passwordVerify', 'Κωδικός πρόσβασης για έλεγχο:') + ->setRequired('Παρακαλώ εισάγετε ξανά τον κωδικό πρόσβασης για έλεγχο.') + ->addRule($form::Equal, 'Οι κωδικοί πρόσβασης δεν ταιριάζουν.', $form['password']) ->setOmitted(); ``` -Χρησιμοποιώντας το `setOmitted()`, επισημαίνουμε ένα στοιχείο του οποίου η τιμή δεν μας ενδιαφέρει πραγματικά και το οποίο υπάρχει μόνο για επικύρωση. Η τιμή του δεν περνάει στο `$data`. +Χρησιμοποιώντας το `setOmitted()`, επισημάναμε ένα στοιχείο του οποίου η τιμή δεν μας ενδιαφέρει πραγματικά και το οποίο υπάρχει μόνο για λόγους επικύρωσης. Η τιμή δεν περνά στο `$data`. -Έχουμε μια πλήρως λειτουργική φόρμα με επικύρωση σε PHP και JavaScript. Οι δυνατότητες επικύρωσης της Nette είναι πολύ ευρύτερες, μπορείτε να δημιουργήσετε συνθήκες, να εμφανίσετε και να αποκρύψετε τμήματα μιας σελίδας σύμφωνα με αυτές, κ.λπ. Μπορείτε να μάθετε τα πάντα στο κεφάλαιο για την [επικύρωση φορμών |validation]. +Με αυτό, έχουμε μια πλήρως λειτουργική φόρμα με επικύρωση τόσο σε PHP όσο και σε JavaScript. Οι δυνατότητες επικύρωσης του Nette είναι πολύ ευρύτερες. Μπορείτε να δημιουργήσετε συνθήκες, να εμφανίσετε και να αποκρύψετε τμήματα της σελίδας με βάση αυτές, κ.λπ. Θα μάθετε τα πάντα στο κεφάλαιο για την [επικύρωση φορμών|validation]. -Προεπιλεγμένες τιμές .[#toc-default-values] -=========================================== +Προεπιλεγμένες Τιμές +==================== -Συχνά ορίζουμε προεπιλεγμένες τιμές για τα στοιχεία ελέγχου φόρμας: +Συνήθως ορίζουμε προεπιλεγμένες τιμές για τα στοιχεία της φόρμας: ```php -$form->addEmail('email', 'Email') +$form->addEmail('email', 'E-mail') ->setDefaultValue($lastUsedEmail); ``` -Συχνά είναι χρήσιμο να ορίσουμε προεπιλεγμένες τιμές για όλα τα στοιχεία ελέγχου ταυτόχρονα. Για παράδειγμα, όταν η φόρμα χρησιμοποιείται για την επεξεργασία εγγραφών. Διαβάζουμε την εγγραφή από τη βάση δεδομένων και την ορίζουμε ως προεπιλεγμένες τιμές: +Συχνά είναι χρήσιμο να ορίζουμε προεπιλεγμένες τιμές για όλα τα στοιχεία ταυτόχρονα. Για παράδειγμα, όταν η φόρμα χρησιμοποιείται για την επεξεργασία εγγραφών. Διαβάζουμε την εγγραφή από τη βάση δεδομένων και ορίζουμε τις προεπιλεγμένες τιμές: ```php //$row = ['name' => 'John', 'age' => '33', /* ... */]; $form->setDefaults($row); ``` -Καλέστε το `setDefaults()` μετά τον ορισμό των στοιχείων ελέγχου. +Καλέστε το `setDefaults()` μετά τον ορισμό των στοιχείων. -Απεικόνιση της φόρμας .[#toc-rendering-the-form] -================================================ +Απόδοση Φόρμας +============== -Από προεπιλογή, η φόρμα απεικονίζεται ως πίνακας. Τα επιμέρους στοιχεία ελέγχου ακολουθούν τις βασικές οδηγίες προσβασιμότητας ιστού. Όλες οι ετικέτες παράγονται ως `<label>` στοιχεία και συνδέονται με τις εισόδους τους, κάνοντας κλικ στην ετικέτα μετακινείται ο δρομέας στην είσοδο. +Από προεπιλογή, η φόρμα αποδίδεται ως πίνακας. Τα μεμονωμένα στοιχεία πληρούν τον βασικό κανόνα προσβασιμότητας - όλες οι ετικέτες γράφονται ως `<label>` και συνδέονται με το αντίστοιχο στοιχείο της φόρμας. Όταν κάνετε κλικ στην ετικέτα, ο κέρσορας εμφανίζεται αυτόματα στο πεδίο της φόρμας. -Μπορούμε να ορίσουμε οποιαδήποτε χαρακτηριστικά HTML για κάθε στοιχείο. Για παράδειγμα, να προσθέσουμε ένα placeholder: +Μπορούμε να ορίσουμε οποιαδήποτε χαρακτηριστικά HTML για κάθε στοιχείο. Για παράδειγμα, προσθέστε ένα placeholder: ```php -$form->addInteger('age', 'Age:') - ->setHtmlAttribute('placeholder', 'Please fill in the age'); +$form->addInteger('age', 'Ηλικία:') + ->setHtmlAttribute('placeholder', 'Παρακαλώ συμπληρώστε την ηλικία σας'); ``` -Υπάρχουν πραγματικά πολλοί τρόποι για την απόδοση μιας φόρμας, γι' αυτό και είναι αφιερωμένο [κεφάλαιο για την απόδοση |rendering]. +Υπάρχουν πραγματικά πολλοί τρόποι για την απόδοση μιας φόρμας, οπότε υπάρχει ένα [ξεχωριστό κεφάλαιο για την απόδοση|rendering] αφιερωμένο σε αυτό. -Χαρτογράφηση σε κλάσεις .[#toc-mapping-to-classes] -================================================== +Αντιστοίχιση σε Κλάσεις +======================= -Ας επιστρέψουμε στην επεξεργασία δεδομένων φόρμας. Η μέθοδος `getValues()` επέστρεψε τα υποβληθέντα δεδομένα ως αντικείμενο `ArrayHash`. Επειδή πρόκειται για μια γενική κλάση, κάτι σαν την `stdClass`, θα μας λείψουν κάποιες ευκολίες κατά την εργασία με αυτήν, όπως η συμπλήρωση κώδικα για ιδιότητες σε επεξεργαστές ή η στατική ανάλυση κώδικα. Αυτό θα μπορούσε να λυθεί με την ύπαρξη μιας συγκεκριμένης κλάσης για κάθε φόρμα, της οποίας οι ιδιότητες αντιπροσωπεύουν τα επιμέρους στοιχεία ελέγχου. Π.χ: +Ας επιστρέψουμε στην επεξεργασία των δεδομένων της φόρμας. Η μέθοδος `getValues()` μας επέστρεψε τα υποβληθέντα δεδομένα ως αντικείμενο `ArrayHash`. Επειδή πρόκειται για μια γενική κλάση, κάτι σαν `stdClass`, θα μας λείψει κάποια άνεση όταν εργαζόμαστε με αυτήν, όπως η αυτόματη συμπλήρωση ιδιοτήτων στους επεξεργαστές ή η στατική ανάλυση κώδικα. Αυτό θα μπορούσε να λυθεί έχοντας μια συγκεκριμένη κλάση για κάθε φόρμα, της οποίας οι ιδιότητες αντιπροσωπεύουν τα μεμονωμένα στοιχεία. Π.χ.: ```php class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -Από την PHP 8.0, μπορείτε να χρησιμοποιήσετε αυτόν τον κομψό συμβολισμό που χρησιμοποιεί έναν κατασκευαστή: +Εναλλακτικά, μπορείτε να χρησιμοποιήσετε έναν κατασκευαστή: ```php class RegistrationFormData @@ -221,16 +220,18 @@ class RegistrationFormData } ``` -Πώς να πούμε στη Nette να μας επιστρέψει δεδομένα ως αντικείμενα αυτής της κλάσης; Πιο εύκολα απ' ό,τι νομίζετε. Το μόνο που έχετε να κάνετε είναι να καθορίσετε το όνομα της κλάσης ή του αντικειμένου που θέλετε να ενυδατώσετε ως παράμετρο: +Οι ιδιότητες της κλάσης δεδομένων μπορούν επίσης να είναι enum και θα αντιστοιχιστούν αυτόματα. .{data-version:3.2.4} + +Πώς να πούμε στο Nette να επιστρέψει τα δεδομένα ως αντικείμενα αυτής της κλάσης; Πιο εύκολα από ό,τι νομίζετε. Απλά δώστε το όνομα της κλάσης ή το αντικείμενο για ενυδάτωση ως παράμετρο: ```php $data = $form->getValues(RegistrationFormData::class); $name = $data->name; ``` -Μια διεύθυνση `'array'` μπορεί επίσης να καθοριστεί ως παράμετρος και, στη συνέχεια, τα δεδομένα επιστρέφονται ως πίνακας. +Η παράμετρος μπορεί επίσης να είναι `'array'`, και τότε τα δεδομένα θα επιστραφούν ως πίνακας. -Εάν οι φόρμες αποτελούνται από μια δομή πολλαπλών επιπέδων που αποτελείται από δοχεία, δημιουργήστε μια ξεχωριστή κλάση για κάθε ένα από αυτά: +Εάν οι φόρμες σχηματίζουν μια πολυεπίπεδη δομή που αποτελείται από containers, δημιουργήστε μια ξεχωριστή κλάση για κάθε μία: ```php $form = new Form; @@ -247,69 +248,70 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` -Η αντιστοίχιση γνωρίζει τότε από τον τύπο της ιδιότητας `$person` ότι πρέπει να αντιστοιχίσει το δοχείο στην κλάση `PersonFormData`. Εάν η ιδιότητα θα περιέχει έναν πίνακα από δοχεία, δώστε τον τύπο `array` και περάστε την κλάση που θα αντιστοιχιστεί απευθείας στο δοχείο: +Η αντιστοίχιση θα αναγνωρίσει τότε από τον τύπο της ιδιότητας `$person` ότι πρέπει να αντιστοιχίσει το container στην κλάση `PersonFormData`. Εάν η ιδιότητα περιείχε έναν πίνακα από containers, καθορίστε τον τύπο `array` και περάστε την κλάση για αντιστοίχιση απευθείας στο container: ```php $person->setMappedType(PersonFormData::class); ``` +Μπορείτε να δημιουργήσετε το σχέδιο της κλάσης δεδομένων της φόρμας χρησιμοποιώντας τη μέθοδο `Nette\Forms\Blueprint::dataClass($form)`, η οποία θα το εκτυπώσει στη σελίδα του προγράμματος περιήγησης. Στη συνέχεια, απλά επιλέξτε τον κώδικα με κλικ και αντιγράψτε τον στο έργο σας. .{data-version:3.1.15} + -Πολλαπλά κουμπιά υποβολής .[#toc-multiple-submit-buttons] -========================================================= +Πολλαπλά Κουμπιά +================ -Εάν η φόρμα έχει περισσότερα από ένα κουμπιά, συνήθως πρέπει να διακρίνουμε ποιο από αυτά έχει πατηθεί. Η μέθοδος `isSubmittedBy()` του κουμπιού μας επιστρέφει αυτές τις πληροφορίες: +Εάν η φόρμα έχει περισσότερα από ένα κουμπιά, συνήθως πρέπει να διακρίνουμε ποιο από αυτά πατήθηκε. Η μέθοδος `isSubmittedBy()` του κουμπιού θα μας επιστρέψει αυτή την πληροφορία: ```php -$form->addSubmit('save', 'Save'); -$form->addSubmit('delete', 'Delete'); +$form->addSubmit('save', 'Αποθήκευση'); +$form->addSubmit('delete', 'Διαγραφή'); if ($form->isSuccess()) { if ($form['save']->isSubmittedBy()) { - // ... + // αποθήκευση δεδομένων } if ($form['delete']->isSubmittedBy()) { - // ... + // διαγραφή δεδομένων } } ``` -Μην παραλείψετε το `$form->isSuccess()` για να ελέγξετε την εγκυρότητα των δεδομένων. +Μην παραλείψετε την ερώτηση `$form->isSuccess()`, καθώς επαληθεύει την εγκυρότητα των δεδομένων. -Όταν μια φόρμα υποβάλλεται με το πλήκτρο <kbd>Enter</kbd>, αντιμετωπίζεται σαν να είχε υποβληθεί με το πρώτο πλήκτρο. +Όταν μια φόρμα υποβάλλεται πατώντας το πλήκτρο <kbd>Enter</kbd>, θεωρείται ότι υποβλήθηκε από το πρώτο κουμπί. -Προστασία από τρωτά σημεία .[#toc-vulnerability-protection] -=========================================================== +Προστασία από Ευπάθειες +======================= -Το Nette Framework καταβάλλει μεγάλη προσπάθεια για να είναι ασφαλές και δεδομένου ότι οι φόρμες είναι η πιο συνηθισμένη είσοδος του χρήστη, οι φόρμες του Nette είναι σχεδόν αδιαπέραστες. +Το Nette Framework δίνει μεγάλη έμφαση στην ασφάλεια και, ως εκ τούτου, φροντίζει σχολαστικά για την καλή ασφάλεια των φορμών. -Εκτός από την προστασία των φορμών από επιθέσεις γνωστών ευπαθειών, όπως [Cross-Site Scripting (XSS) |nette:glossary#cross-site-scripting-xss] και [Cross-Site Request Forgery (CSRF |nette:glossary#cross-site-request-forgery-csrf] ), κάνει πολλές μικρές εργασίες ασφαλείας που δεν χρειάζεται πλέον να σκέφτεστε. +Εκτός από την προστασία των φορμών από επιθέσεις [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] και [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF], εφαρμόζει πολλές μικρές προστασίες για τις οποίες δεν χρειάζεται πλέον να ανησυχείτε. -Για παράδειγμα, φιλτράρει όλους τους χαρακτήρες ελέγχου από τις εισόδους και ελέγχει την εγκυρότητα της κωδικοποίησης UTF-8, ώστε τα δεδομένα από τη φόρμα να είναι πάντα καθαρά. Για τα πλαίσια επιλογής και τις λίστες επιλογής, επαληθεύει ότι τα επιλεγμένα στοιχεία ήταν όντως από τα προσφερόμενα και ότι δεν υπήρξε πλαστογράφηση. Έχουμε ήδη αναφέρει ότι για την εισαγωγή κειμένου σε μία γραμμή, αφαιρεί τους χαρακτήρες τέλους γραμμής που θα μπορούσε να στείλει εκεί ένας εισβολέας. Για εισόδους πολλαπλών γραμμών, ομαλοποιεί τους χαρακτήρες στο τέλος της γραμμής. Και ούτω καθεξής. +Για παράδειγμα, φιλτράρει όλους τους χαρακτήρες ελέγχου από την είσοδο και επαληθεύει την εγκυρότητα της κωδικοποίησης UTF-8, έτσι ώστε τα δεδομένα από τη φόρμα να είναι πάντα καθαρά. Για τα πλαίσια επιλογής και τις λίστες radio, επαληθεύει ότι τα επιλεγμένα στοιχεία ήταν πράγματι από τα προσφερόμενα και ότι δεν υπήρξε πλαστογράφηση. Έχουμε ήδη αναφέρει ότι αφαιρεί τους χαρακτήρες τέλους γραμμής από τις εισόδους κειμένου μίας γραμμής, τους οποίους θα μπορούσε να στείλει ένας εισβολέας. Για τις εισόδους πολλαπλών γραμμών, κανονικοποιεί τους χαρακτήρες τέλους γραμμής. Και ούτω καθεξής. -Η Nette διορθώνει για εσάς ευπάθειες ασφαλείας που οι περισσότεροι προγραμματιστές δεν έχουν ιδέα ότι υπάρχουν. +Το Nette αντιμετωπίζει για εσάς κινδύνους ασφαλείας που πολλοί προγραμματιστές δεν γνωρίζουν καν ότι υπάρχουν. -Η αναφερόμενη επίθεση CSRF είναι ότι ένας επιτιθέμενος παρασύρει το θύμα να επισκεφθεί μια σελίδα που εκτελεί σιωπηλά ένα αίτημα στο πρόγραμμα περιήγησης του θύματος προς τον διακομιστή όπου το θύμα είναι συνδεδεμένο αυτή τη στιγμή και ο διακομιστής πιστεύει ότι το αίτημα έγινε από το θύμα κατά βούληση. Ως εκ τούτου, η Nette εμποδίζει την υποβολή της φόρμας μέσω POST από άλλο τομέα. Αν για κάποιο λόγο θέλετε να απενεργοποιήσετε την προστασία και να επιτρέψετε την υποβολή της φόρμας από άλλο τομέα, χρησιμοποιήστε: +Η προαναφερθείσα επίθεση CSRF συνίσταται στο ότι ο εισβολέας δελεάζει το θύμα σε μια σελίδα που εκτελεί διακριτικά ένα αίτημα στον διακομιστή στον οποίο είναι συνδεδεμένο το θύμα, στο πρόγραμμα περιήγησης του θύματος, και ο διακομιστής πιστεύει ότι το αίτημα εκτελέστηκε από το θύμα με δική του βούληση. Επομένως, το Nette αποτρέπει την υποβολή μιας φόρμας POST από άλλο domain. Εάν, για κάποιο λόγο, θέλετε να απενεργοποιήσετε την προστασία και να επιτρέψετε την υποβολή της φόρμας από άλλο domain, χρησιμοποιήστε: ```php $form->allowCrossOrigin(); // ΠΡΟΣΟΧΗ! Απενεργοποιεί την προστασία! ``` -Αυτή η προστασία χρησιμοποιεί ένα cookie SameSite με όνομα `_nss`. Επομένως, δημιουργήστε μια φόρμα πριν από την εκκαθάριση της πρώτης εξόδου, ώστε να μπορεί να σταλεί το cookie. +Αυτή η προστασία χρησιμοποιεί ένα SameSite cookie με όνομα `_nss`. Επομένως, δημιουργήστε το αντικείμενο της φόρμας πριν στείλετε την πρώτη έξοδο, ώστε το cookie να μπορεί να σταλεί. -Η προστασία των cookies SameSite μπορεί να μην είναι 100% αξιόπιστη, οπότε είναι καλή ιδέα να ενεργοποιήσετε την προστασία με συμβολικά στοιχεία: +Η προστασία με SameSite cookie μπορεί να μην είναι 100% αξιόπιστη, οπότε συνιστάται να ενεργοποιήσετε επίσης την προστασία με token: ```php $form->addProtection(); ``` -Συνιστάται ιδιαίτερα να εφαρμόζετε αυτή την προστασία στις φόρμες σε ένα διοικητικό τμήμα της εφαρμογής σας που αλλάζει ευαίσθητα δεδομένα. Το πλαίσιο προστατεύει από μια επίθεση CSRF δημιουργώντας και επικυρώνοντας το διακριτικό ελέγχου ταυτότητας που αποθηκεύεται σε μια περίοδο λειτουργίας (το επιχείρημα είναι το μήνυμα σφάλματος που εμφανίζεται εάν το διακριτικό έχει λήξει). Γι' αυτό είναι απαραίτητο να έχει ξεκινήσει μια συνεδρία πριν από την εμφάνιση της φόρμας. Στο τμήμα διαχείρισης του ιστότοπου, η σύνοδος συνήθως έχει ήδη ξεκινήσει, λόγω της σύνδεσης του χρήστη. -Διαφορετικά, ξεκινήστε τη σύνοδο με τη μέθοδο `Nette\Http\Session::start()`. +Συνιστούμε την προστασία των φορμών στο διαχειριστικό τμήμα του ιστότοπου που τροποποιούν ευαίσθητα δεδομένα στην εφαρμογή με αυτόν τον τρόπο. Το framework αμύνεται έναντι επιθέσεων CSRF δημιουργώντας και επαληθεύοντας ένα token εξουσιοδότησης που αποθηκεύεται στο session. Επομένως, είναι απαραίτητο να έχετε ανοιχτό το session πριν εμφανίσετε τη φόρμα. Στο διαχειριστικό τμήμα του ιστότοπου, το session συνήθως έχει ήδη ξεκινήσει λόγω της σύνδεσης του χρήστη. Διαφορετικά, ξεκινήστε το session με τη μέθοδο `Nette\Http\Session::start()`. -Έτσι, έχουμε μια γρήγορη εισαγωγή στις φόρμες στη Nette. Δοκιμάστε να κοιτάξετε στον κατάλογο [examples |https://github.com/nette/forms/tree/master/examples] της διανομής για περισσότερη έμπνευση. +Λοιπόν, αυτή ήταν μια γρήγορη εισαγωγή στις φόρμες στο Nette. Προσπαθήστε να ρίξετε μια ματιά στον κατάλογο [examples|https://github.com/nette/forms/tree/master/examples] στη διανομή, όπου θα βρείτε περισσότερη έμπνευση. diff --git a/forms/el/validation.texy b/forms/el/validation.texy index 0cee3fe0f0..b17739b2b7 100644 --- a/forms/el/validation.texy +++ b/forms/el/validation.texy @@ -1,173 +1,184 @@ -Επικύρωση φορμών +Επικύρωση Φορμών **************** -Απαιτούμενοι έλεγχοι .[#toc-required-controls] -============================================== +Υποχρεωτικά Στοιχεία +==================== -Τα στοιχεία ελέγχου χαρακτηρίζονται ως υποχρεωτικά με τη μέθοδο `setRequired()`, της οποίας το όρισμα είναι το κείμενο του [μηνύματος σφάλματος |#Error Messages] που θα εμφανιστεί αν ο χρήστης δεν το συμπληρώσει. Εάν δεν δοθεί κανένα όρισμα, χρησιμοποιείται το προεπιλεγμένο μήνυμα σφάλματος. +Επισημαίνουμε τα υποχρεωτικά στοιχεία με τη μέθοδο `setRequired()`. Το όρισμά της είναι το κείμενο του [μηνύματος σφάλματος |#Μηνύματα Σφάλματος] που θα εμφανιστεί εάν ο χρήστης δεν συμπληρώσει το στοιχείο. Εάν δεν παρέχουμε όρισμα, θα χρησιμοποιηθεί το προεπιλεγμένο μήνυμα σφάλματος. ```php -$form->addText('name', 'Name:') - ->setRequired('Please fill your name.'); +$form->addText('name', 'Όνομα:') + ->setRequired('Παρακαλώ εισάγετε το όνομά σας.'); ``` -Κανόνες .[#toc-rules] -===================== +Κανόνες +======= -Προσθέτουμε κανόνες επικύρωσης σε στοιχεία ελέγχου με τη μέθοδο `addRule()`. Η πρώτη παράμετρος είναι ο κανόνας, η δεύτερη είναι το [μήνυμα σφάλματος |#Error Messages] και η τρίτη είναι το όρισμα του κανόνα επικύρωσης. +Προσθέτουμε κανόνες επικύρωσης στα στοιχεία χρησιμοποιώντας τη μέθοδο `addRule()`. Η πρώτη παράμετρος είναι ο κανόνας, η δεύτερη είναι το κείμενο του [μηνύματος σφάλματος |#Μηνύματα Σφάλματος] και η τρίτη είναι το όρισμα του κανόνα επικύρωσης. ```php -$form->addPassword('password', 'Password:') - ->addRule($form::MinLength, 'Password must be at least %d characters', 8); +$form->addPassword('password', 'Κωδικός πρόσβασης:') + ->addRule($form::MinLength, 'Ο κωδικός πρόσβασης πρέπει να έχει μήκος τουλάχιστον %d χαρακτήρων.', 8); ``` -Η Nette έρχεται με έναν αριθμό ενσωματωμένων κανόνων των οποίων τα ονόματα είναι σταθερές της κλάσης `Nette\Forms\Form`: +**Οι κανόνες επικύρωσης ελέγχονται μόνο εάν ο χρήστης συμπληρώσει το στοιχείο.** -Μπορούμε να χρησιμοποιήσουμε τους ακόλουθους κανόνες για όλα τα στοιχεία ελέγχου: +Το Nette έρχεται με μια σειρά προκαθορισμένων κανόνων, των οποίων τα ονόματα είναι σταθερές της κλάσης `Nette\Forms\Form`. Μπορούμε να χρησιμοποιήσουμε αυτούς τους κανόνες για όλα τα στοιχεία: -| σταθερά | περιγραφή | επιχειρήματα +| σταθερά | περιγραφή | τύπος ορίσματος |------- -| `Required` | ψευδώνυμο του `setRequired()` | - -| `Filled` | ψευδώνυμο του `setRequired()` | - -| `Blank` | δεν πρέπει να συμπληρωθεί | - +| `Required` | υποχρεωτικό στοιχείο, ψευδώνυμο για `setRequired()` | - +| `Filled` | υποχρεωτικό στοιχείο, ψευδώνυμο για `setRequired()` | - +| `Blank` | το στοιχείο δεν πρέπει να συμπληρωθεί | - | `Equal` | η τιμή είναι ίση με την παράμετρο | `mixed` | `NotEqual` | η τιμή δεν είναι ίση με την παράμετρο | `mixed` -| `IsIn` | η τιμή είναι ίση με κάποιο στοιχείο του πίνακα | `array` -| `IsNotIn` | η τιμή δεν είναι ίση με κάποιο στοιχείο του πίνακα | `array` -| `Valid` | η είσοδος περνάει την επικύρωση (για τις [συνθήκες |#conditions]) | - - -Για τα στοιχεία ελέγχου `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` μπορούν επίσης να χρησιμοποιηθούν οι ακόλουθοι κανόνες: - -| `MinLength` | ελάχιστο μήκος συμβολοσειράς | `int` -| `MaxLength` | μέγιστο μήκος συμβολοσειράς | `int` -| `Length` | μήκος στο εύρος ή ακριβές μήκος | ζεύγος `[int, int]` ή `int` -| `Email` | έγκυρη διεύθυνση ηλεκτρονικού ταχυδρομείου | - -| `URL` | έγκυρη διεύθυνση URL | - -| `Pattern` | ταιριάζει με κανονικό μοτίβο | `string` -| `PatternInsensitive` | όπως το `Pattern`, αλλά χωρίς να λαμβάνεται υπόψη η πεζότητα | `string` -| `Integer` | ακέραιος αριθμός | - -| `Numeric` | ψευδώνυμο του `Integer` | - -| `Float` | ακέραιος ή αριθμός κινητής υποδιαστολής | - -| `Min` | ελάχιστο της ακέραιης τιμής | `int\|float` -| `Max` | μέγιστο της ακέραιης τιμής | `int\|float` -| `Range` | τιμή στο εύρος | ζεύγος `[int\|float, int\|float]` - -Οι κανόνες `Integer`, `Numeric` και `Float` μετατρέπουν αυτόματα την τιμή σε ακέραιο (ή float αντίστοιχα). Επιπλέον, ο κανόνας `URL` δέχεται επίσης μια διεύθυνση χωρίς σχήμα (π.χ. `nette.org`) και συμπληρώνει το σχήμα (`https://nette.org`). -Οι εκφράσεις στους κανόνες `Pattern` και `PatternInsensitive` πρέπει να ισχύουν για ολόκληρη την τιμή, δηλαδή σαν να ήταν τυλιγμένη στους χαρακτήρες `^` and `$`. - -Για τους ελέγχους `addUpload()`, `addMultiUpload()` μπορούν επίσης να χρησιμοποιηθούν οι ακόλουθοι κανόνες: - -| `MaxFileSize` | μέγιστο μέγεθος αρχείου | `int` -| `MimeType` | τύπος MIME, δέχεται μπαλαντέρ (`'video/*'`) | `string\|string[]` -| `Image` | το ανεβασμένο αρχείο είναι JPEG, PNG, GIF, WebP | - -| `Pattern` | το όνομα του αρχείου ταιριάζει με κανονική έκφραση | `string` -| `PatternInsensitive` | όπως το `Pattern`, αλλά χωρίς να λαμβάνεται υπόψη η πεζότητα | `string` - -Τα `MimeType` και `Image` απαιτούν την επέκταση PHP `fileinfo`. Το αν ένα αρχείο ή μια εικόνα είναι του απαιτούμενου τύπου ανιχνεύεται από την υπογραφή του. Η ακεραιότητα ολόκληρου του αρχείου δεν ελέγχεται. Μπορείτε να διαπιστώσετε αν μια εικόνα δεν είναι κατεστραμμένη, για παράδειγμα, προσπαθώντας να [τη φορτώσετε |http:request#toImage]. - -Για τα στοιχεία ελέγχου `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()` μπορούν επίσης να χρησιμοποιηθούν οι ακόλουθοι κανόνες για τον περιορισμό του αριθμού των επιλεγμένων στοιχείων, αντίστοιχα των φορτωμένων αρχείων: - -| `MinLength` | minimal count | `int` +| `IsIn` | η τιμή είναι ίση με ένα από τα στοιχεία του πίνακα | `array` +| `IsNotIn` | η τιμή δεν είναι ίση με κανένα στοιχείο του πίνακα | `array` +| `Valid` | είναι το στοιχείο συμπληρωμένο σωστά; (για [#συνθήκες]) | - + + +Είσοδοι Κειμένου +---------------- + +Για τα στοιχεία `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`, `addFloat()`, μπορούν επίσης να χρησιμοποιηθούν ορισμένοι από τους ακόλουθους κανόνες: + +| `MinLength` | ελάχιστο μήκος κειμένου | `int` +| `MaxLength` | μέγιστο μήκος κειμένου | `int` +| `Length` | μήκος εντός εύρους ή ακριβές μήκος | ζεύγος `[int, int]` ή `int` +| `Email` | έγκυρη διεύθυνση email | - +| `URL` | απόλυτο URL | - +| `Pattern` | ταιριάζει με την κανονική έκφραση | `string` +| `PatternInsensitive` | όπως το `Pattern`, αλλά χωρίς διάκριση πεζών-κεφαλαίων | `string` +| `Integer` | ακέραια τιμή | - +| `Numeric` | ψευδώνυμο για `Integer` | - +| `Float` | αριθμός | - +| `Min` | ελάχιστη τιμή αριθμητικού στοιχείου | `int\|float` +| `Max` | μέγιστη τιμή αριθμητικού στοιχείου | `int\|float` +| `Range` | τιμή εντός εύρους | ζεύγος `[int\|float, int\|float]` + +Οι κανόνες επικύρωσης `Integer`, `Numeric` και `Float` μετατρέπουν αμέσως την τιμή σε ακέραιο ή δεκαδικό, αντίστοιχα. Επιπλέον, ο κανόνας `URL` δέχεται επίσης μια διεύθυνση χωρίς σχήμα (π.χ. `nette.org`) και προσθέτει το σχήμα (`https://nette.org`). Η έκφραση στο `Pattern` και το `PatternIcase` πρέπει να ισχύει για ολόκληρη την τιμή, δηλαδή σαν να ήταν περικλεισμένη από τους χαρακτήρες `^` και `$`. + + +Αριθμός Στοιχείων +----------------- + +Για τα στοιχεία `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()`, μπορούν επίσης να χρησιμοποιηθούν οι ακόλουθοι κανόνες για τον περιορισμό του αριθμού των επιλεγμένων στοιχείων ή των ανεβασμένων αρχείων, αντίστοιχα: + +| `MinLength` | ελάχιστος αριθμός | `int` | `MaxLength` | μέγιστος αριθμός | `int` -| `Length` | count in range or exact count | ζεύγος `[int, int]` ή `int` +| `Length` | αριθμός εντός εύρους ή ακριβής αριθμός | ζεύγος `[int, int]` ή `int` + + +Ανέβασμα Αρχείων +---------------- + +Για τα στοιχεία `addUpload()`, `addMultiUpload()`, μπορούν επίσης να χρησιμοποιηθούν οι ακόλουθοι κανόνες: + +| `MaxFileSize` | μέγιστο μέγεθος αρχείου σε bytes | `int` +| `MimeType` | Τύπος MIME, επιτρέπονται χαρακτήρες μπαλαντέρ (`'video/*'`) | `string\|string[]` +| `Image` | εικόνα JPEG, PNG, GIF, WebP, AVIF | - +| `Pattern` | το όνομα αρχείου ταιριάζει με την κανονική έκφραση | `string` +| `PatternInsensitive` | όπως το `Pattern`, αλλά χωρίς διάκριση πεζών-κεφαλαίων | `string` + +Τα `MimeType` και `Image` απαιτούν την επέκταση PHP `fileinfo`. Το αν ένα αρχείο ή μια εικόνα είναι του απαιτούμενου τύπου ανιχνεύεται με βάση την υπογραφή του και **δεν επαληθεύει την ακεραιότητα ολόκληρου του αρχείου.** Το αν μια εικόνα είναι κατεστραμμένη μπορεί να προσδιοριστεί, για παράδειγμα, προσπαθώντας να την [φορτώσετε |http:request#toImage]. -Μηνύματα σφάλματος .[#toc-error-messages] ------------------------------------------ +Μηνύματα Σφάλματος +================== -Όλοι οι προκαθορισμένοι κανόνες εκτός από τους `Pattern` και `PatternInsensitive` έχουν ένα προεπιλεγμένο μήνυμα σφάλματος, οπότε μπορούν να παραλειφθούν. Ωστόσο, περνώντας και διατυπώνοντας όλα τα προσαρμοσμένα μηνύματα, θα κάνετε τη φόρμα πιο φιλική προς το χρήστη. +Όλοι οι προκαθορισμένοι κανόνες, εκτός από τα `Pattern` και `PatternInsensitive`, έχουν ένα προεπιλεγμένο μήνυμα σφάλματος, οπότε μπορεί να παραλειφθεί. Ωστόσο, καθορίζοντας και διατυπώνοντας όλα τα μηνύματα κατά παραγγελία, θα κάνετε τη φόρμα πιο φιλική προς τον χρήστη. -Μπορείτε να αλλάξετε τα προεπιλεγμένα μηνύματα στο [forms:configuration], τροποποιώντας τα κείμενα στον πίνακα `Nette\Forms\Validator::$messages` ή χρησιμοποιώντας [τον μεταφραστή |rendering#translating]. +Μπορείτε να αλλάξετε τα προεπιλεγμένα μηνύματα στην [διαμόρφωση|forms:configuration], επεξεργαζόμενοι τα κείμενα στον πίνακα `Nette\Forms\Validator::$messages`, ή χρησιμοποιώντας έναν [μεταφραστή |rendering#Μετάφραση]. -Τα ακόλουθα μπαλαντέρ μπορούν να χρησιμοποιηθούν στο κείμενο των μηνυμάτων σφάλματος: +Οι ακόλουθες συμβολοσειρές κράτησης θέσης μπορούν να χρησιμοποιηθούν στο κείμενο των μηνυμάτων σφάλματος: -| `%d` | αντικαθιστά σταδιακά τους κανόνες μετά τα επιχειρήματα -| `%n$d` | αντικαθιστά με το νιοστό όρισμα του κανόνα -| `%label` | αντικαθιστά με την ετικέτα πεδίου (χωρίς άνω και κάτω τελεία) -| `%name` | αντικαθιστά με το όνομα του πεδίου (π.χ. `name`) -| `%value` | αντικαθιστά με την τιμή που εισάγει ο χρήστης +| `%d` | αντικαθίσταται διαδοχικά από τα ορίσματα του κανόνα +| `%n$d` | αντικαθίσταται από το n-οστό όρισμα του κανόνα +| `%label` | αντικαθίσταται από την ετικέτα του στοιχείου (χωρίς την άνω και κάτω τελεία) +| `%name` | αντικαθίσταται από το όνομα του στοιχείου (π.χ. `name`) +| `%value` | αντικαθίσταται από την τιμή που εισήγαγε ο χρήστης ```php -$form->addText('name', 'Name:') - ->setRequired('Please fill in %label'); +$form->addText('name', 'Όνομα:') + ->setRequired('Παρακαλώ συμπληρώστε το %label'); $form->addInteger('id', 'ID:') - ->addRule($form::Range, 'at least %d and no more than %d', [5, 10]); + ->addRule($form::Range, 'τουλάχιστον %d και το πολύ %d', [5, 10]); $form->addInteger('id', 'ID:') - ->addRule($form::Range, 'no more than %2$d and at least %1$d', [5, 10]); + ->addRule($form::Range, 'το πολύ %2$d και τουλάχιστον %1$d', [5, 10]); ``` -Συνθήκες .[#toc-conditions] -=========================== +Συνθήκες +======== -Εκτός από τους κανόνες επικύρωσης, μπορούν να οριστούν και συνθήκες. Ορίζονται όπως και οι κανόνες, ωστόσο χρησιμοποιούμε το `addRule()` αντί του `addCondition()` και φυσικά, το αφήνουμε χωρίς μήνυμα σφάλματος (η συνθήκη απλώς ρωτάει): +Εκτός από τους κανόνες, μπορούν επίσης να προστεθούν συνθήκες. Γράφονται παρόμοια με τους κανόνες, αλλά αντί για `addRule()`, χρησιμοποιούμε τη μέθοδο `addCondition()`, και φυσικά, δεν παρέχουμε κανένα μήνυμα σφάλματος (η συνθήκη απλώς ρωτά): ```php -$form->addPassword('password', 'Password:') - // εάν ο κωδικός πρόσβασης δεν υπερβαίνει τους 8 χαρακτήρες ... +$form->addPassword('password', 'Κωδικός πρόσβασης:') + // εάν ο κωδικός πρόσβασης δεν είναι μεγαλύτερος από 8 χαρακτήρες ->addCondition($form::MaxLength, 8) - // ... τότε πρέπει να περιέχει έναν αριθμό - ->addRule($form::Pattern, 'Must contain number', '.*[0-9].*'); + // τότε πρέπει να περιέχει ένα ψηφίο + ->addRule($form::Pattern, 'Πρέπει να περιέχει ένα ψηφίο.', '.*[0-9].*'); ``` -Η συνθήκη μπορεί να συνδεθεί με ένα διαφορετικό στοιχείο από το τρέχον χρησιμοποιώντας το `addConditionOn()`. Η πρώτη παράμετρος είναι μια αναφορά στο πεδίο. Στην ακόλουθη περίπτωση, το email θα απαιτείται μόνο εάν το πλαίσιο ελέγχου είναι επιλεγμένο (δηλαδή η τιμή του είναι `true`): +Η συνθήκη μπορεί επίσης να συνδεθεί με ένα στοιχείο διαφορετικό από το τρέχον χρησιμοποιώντας το `addConditionOn()`. Ως πρώτη παράμετρο, παρέχουμε μια αναφορά στο στοιχείο. Σε αυτό το παράδειγμα, το email θα είναι υποχρεωτικό μόνο εάν το πλαίσιο ελέγχου είναι επιλεγμένο (η τιμή του θα είναι true): ```php -$form->addCheckbox('newsletters', 'send me newsletters'); +$form->addCheckbox('newsletters', 'στείλτε μου ενημερωτικά δελτία'); -$form->addEmail('email', 'Email:') - // αν το πλαίσιο ελέγχου είναι επιλεγμένο ... +$form->addEmail('email', 'E-mail:') + // εάν το πλαίσιο ελέγχου είναι επιλεγμένο ->addConditionOn($form['newsletters'], $form::Equal, true) - // ... απαιτούν email - ->setRequired('Fill your email address'); + // τότε απαιτήστε το email + ->setRequired('Παρακαλώ εισάγετε τη διεύθυνση email σας.'); ``` -Οι συνθήκες μπορούν να ομαδοποιηθούν σε σύνθετες δομές με τις μεθόδους `elseCondition()` και `endCondition()`. +Μπορείτε να δημιουργήσετε σύνθετες δομές από συνθήκες χρησιμοποιώντας τα `elseCondition()` και `endCondition()`: ```php $form->addText(/* ... */) - ->addCondition(/* ... */) // εάν πληρούται η πρώτη προϋπόθεση - ->addConditionOn(/* ... */) // και η δεύτερη συνθήκη σε ένα άλλο στοιχείο επίσης - ->addRule(/* ... */) // απαιτούν αυτόν τον κανόνα - ->elseCondition() // αν η δεύτερη συνθήκη δεν ικανοποιείται - ->addRule(/* ... */) // απαιτεί αυτούς τους κανόνες + ->addCondition(/* ... */) // εάν η πρώτη συνθήκη πληρούται + ->addConditionOn(/* ... */) // και η δεύτερη συνθήκη σε άλλο στοιχείο + ->addRule(/* ... */) // απαιτήστε αυτόν τον κανόνα + ->elseCondition() // εάν η δεύτερη συνθήκη δεν πληρούται + ->addRule(/* ... */) // απαιτήστε αυτούς τους κανόνες ->addRule(/* ... */) ->endCondition() // επιστρέφουμε στην πρώτη συνθήκη ->addRule(/* ... */); ``` -Στη Nette, είναι πολύ εύκολο να αντιδράσετε στην εκπλήρωση ή όχι μιας συνθήκης από την πλευρά της JavaScript χρησιμοποιώντας τη μέθοδο `toggle()`, βλέπε [Δυναμική JavaScript |#Dynamic JavaScript]. +Στο Nette, είναι πολύ εύκολο να αντιδράσετε στην εκπλήρωση ή μη εκπλήρωση μιας συνθήκης επίσης στην πλευρά του JavaScript χρησιμοποιώντας τη μέθοδο `toggle()`, δείτε [#δυναμικό-javascript]. -Αναφορές μεταξύ στοιχείων ελέγχου .[#toc-references-between-controls] -===================================================================== +Αναφορά σε Άλλο Στοιχείο +======================== -Το όρισμα του κανόνα ή της συνθήκης μπορεί να είναι μια αναφορά σε ένα άλλο στοιχείο. Για παράδειγμα, μπορείτε να επικυρώσετε δυναμικά ότι το `text` έχει τόσους χαρακτήρες όσοι είναι οι τιμές του πεδίου `length`: +Ένα άλλο στοιχείο φόρμας μπορεί επίσης να περάσει ως όρισμα σε έναν κανόνα ή μια συνθήκη. Ο κανόνας θα χρησιμοποιήσει τότε την τιμή που εισήγαγε αργότερα ο χρήστης στο πρόγραμμα περιήγησης. Με αυτόν τον τρόπο, μπορείτε, για παράδειγμα, να επικυρώσετε δυναμικά ότι το στοιχείο `password` περιέχει την ίδια συμβολοσειρά με το στοιχείο `password_confirm`: ```php -$form->addInteger('length'); -$form->addText('text') - ->addRule($form::Length, null, $form['length']); +$form->addPassword('password', 'Κωδικός πρόσβασης'); +$form->addPassword('password_confirm', 'Επιβεβαιώστε τον κωδικό πρόσβασης') + ->addRule($form::Equal, 'Οι κωδικοί πρόσβασης δεν ταιριάζουν.', $form['password']); ``` -Προσαρμοσμένοι κανόνες και προϋποθέσεις .[#toc-custom-rules-and-conditions] -=========================================================================== +Προσαρμοσμένοι Κανόνες και Συνθήκες +=================================== -Μερικές φορές μπαίνουμε σε μια κατάσταση όπου οι ενσωματωμένοι κανόνες επικύρωσης της Nette δεν είναι αρκετοί και πρέπει να επικυρώσουμε τα δεδομένα από τον χρήστη με τον δικό μας τρόπο. Στη Nette αυτό είναι πολύ εύκολο! +Μερικές φορές βρισκόμαστε σε μια κατάσταση όπου οι ενσωματωμένοι κανόνες επικύρωσης στο Nette δεν είναι αρκετοί και πρέπει να επικυρώσουμε τα δεδομένα του χρήστη με τον δικό μας τρόπο. Στο Nette, αυτό είναι πολύ εύκολο! -Μπορείτε να περάσετε οποιοδήποτε callback ως πρώτη παράμετρο στις μεθόδους `addRule()` ή `addCondition()`. Το callback δέχεται το ίδιο το στοιχείο ως πρώτη παράμετρο και επιστρέφει μια boolean τιμή που δείχνει αν η επικύρωση ήταν επιτυχής. Κατά την προσθήκη ενός κανόνα με τη χρήση της μεθόδου `addRule()`, μπορούν να περάσουν επιπλέον ορίσματα, τα οποία στη συνέχεια περνούν ως δεύτερη παράμετρος. +Μπορείτε να περάσετε οποιαδήποτε επανάκληση ως πρώτη παράμετρο στις μεθόδους `addRule()` ή `addCondition()`. Η επανάκληση δέχεται το ίδιο το στοιχείο ως πρώτη παράμετρο και επιστρέφει μια boolean τιμή που υποδεικνύει εάν η επικύρωση ήταν επιτυχής. Κατά την προσθήκη ενός κανόνα χρησιμοποιώντας το `addRule()`, είναι επίσης δυνατό να καθοριστούν πρόσθετα ορίσματα, τα οποία στη συνέχεια περνούν ως δεύτερη παράμετρος. -Το προσαρμοσμένο σύνολο επικυρωτών μπορεί έτσι να δημιουργηθεί ως κλάση με στατικές μεθόδους: +Μπορούμε να δημιουργήσουμε το δικό μας σύνολο επικυρωτών ως κλάση με στατικές μεθόδους: ```php class MyValidators { - // ελέγχει αν η τιμή διαιρείται με το όρισμα + // ελέγχει εάν η τιμή είναι διαιρετή από το όρισμα public static function validateDivisibility(BaseControl $input, $arg): bool { return $input->getValue() % $arg === 0; @@ -175,7 +186,7 @@ class MyValidators public static function validateEmailDomain(BaseControl $input, $domain) { - // πρόσθετοι επικυρωτές + // άλλοι επικυρωτές } } ``` @@ -186,12 +197,12 @@ class MyValidators $form->addInteger('num') ->addRule( [MyValidators::class, 'validateDivisibility'], - 'The value must be a multiple of %d', + 'Η τιμή πρέπει να είναι πολλαπλάσιο του %d.', 8, ); ``` -Μπορούν επίσης να προστεθούν προσαρμοσμένοι κανόνες επικύρωσης σε JavaScript. Η μόνη απαίτηση είναι ότι ο κανόνας πρέπει να είναι μια στατική μέθοδος. Το όνομά του για τον επικυρωτή JavaScript δημιουργείται από τη συνένωση του ονόματος της κλάσης χωρίς κάθετους `\`, the underscore `_`, και του ονόματος της μεθόδου. Για παράδειγμα, γράψτε το `App\MyValidators::validateDivisibility` ως `AppMyValidators_validateDivisibility` και προσθέστε το στο αντικείμενο `Nette.validators`: +Προσαρμοσμένοι κανόνες επικύρωσης μπορούν επίσης να προστεθούν στο JavaScript. Η προϋπόθεση είναι ότι ο κανόνας είναι μια στατική μέθοδος. Το όνομά του για τον επικυρωτή JavaScript δημιουργείται συνδυάζοντας το όνομα της κλάσης χωρίς ανάστροφες καθέτους `\`, μια κάτω παύλα `_` και το όνομα της μεθόδου. Για παράδειγμα, το `App\MyValidators::validateDivisibility` γράφεται ως `AppMyValidators_validateDivisibility` και προστίθεται στο αντικείμενο `Nette.validators`: ```js Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => { @@ -200,12 +211,12 @@ Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => ``` -Γεγονός onValidate .[#toc-event-onvalidate] -=========================================== +Συμβάν onValidate +================= -Μετά την υποβολή της φόρμας, η επικύρωση πραγματοποιείται με τον έλεγχο των επιμέρους κανόνων που έχουν προστεθεί από το `addRule()` και στη συνέχεια με την κλήση του [συμβάντος |nette:glossary#Events] `onValidate`. Ο χειριστής του μπορεί να χρησιμοποιηθεί για πρόσθετη επικύρωση, συνήθως για την επαλήθευση του σωστού συνδυασμού τιμών σε πολλαπλά στοιχεία της φόρμας. +Μετά την υποβολή της φόρμας, πραγματοποιείται επικύρωση, όπου ελέγχονται οι μεμονωμένοι κανόνες που προστέθηκαν χρησιμοποιώντας το `addRule()`, και στη συνέχεια ενεργοποιείται το [συμβάν |nette:glossary#Events] `onValidate`. Ο χειριστής του μπορεί να χρησιμοποιηθεί για συμπληρωματική επικύρωση, συνήθως για την επαλήθευση του σωστού συνδυασμού τιμών σε πολλαπλά στοιχεία της φόρμας. -Εάν εντοπιστεί σφάλμα, αυτό μεταβιβάζεται στη φόρμα χρησιμοποιώντας τη μέθοδο `addError()`. Αυτή μπορεί να κληθεί είτε σε ένα συγκεκριμένο στοιχείο είτε απευθείας στη φόρμα. +Εάν εντοπιστεί σφάλμα, το περνάμε στη φόρμα χρησιμοποιώντας τη μέθοδο `addError()`. Αυτή μπορεί να κληθεί είτε σε ένα συγκεκριμένο στοιχείο είτε απευθείας στη φόρμα. ```php protected function createComponentSignInForm(): Form @@ -219,16 +230,16 @@ protected function createComponentSignInForm(): Form public function validateSignInForm(Form $form, \stdClass $data): void { if ($data->foo > 1 && $data->bar > 5) { - $form->addError('This combination is not possible.'); + $form->addError('Αυτός ο συνδυασμός δεν είναι δυνατός.'); } } ``` -Σφάλματα επεξεργασίας .[#toc-processing-errors] -=============================================== +Σφάλματα κατά την Επεξεργασία +============================= -Σε πολλές περιπτώσεις, ανακαλύπτουμε ένα σφάλμα όταν επεξεργαζόμαστε μια έγκυρη φόρμα, π.χ. όταν γράφουμε μια νέα εγγραφή στη βάση δεδομένων και συναντάμε ένα διπλότυπο κλειδί. Σε αυτή την περίπτωση, περνάμε το σφάλμα πίσω στη φόρμα χρησιμοποιώντας τη μέθοδο `addError()`. Αυτή μπορεί να κληθεί είτε σε ένα συγκεκριμένο στοιχείο είτε απευθείας στη φόρμα: +Σε πολλές περιπτώσεις, μαθαίνουμε για ένα σφάλμα μόνο όταν επεξεργαζόμαστε μια έγκυρη φόρμα, για παράδειγμα, όταν γράφουμε ένα νέο στοιχείο στη βάση δεδομένων και συναντάμε διπλότυπα κλειδιά. Σε αυτή την περίπτωση, περνάμε ξανά το σφάλμα στη φόρμα χρησιμοποιώντας τη μέθοδο `addError()`. Αυτή μπορεί να κληθεί είτε σε ένα συγκεκριμένο στοιχείο είτε απευθείας στη φόρμα: ```php try { @@ -238,72 +249,71 @@ try { } catch (Nette\Security\AuthenticationException $e) { if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) { - $form->addError('Invalid password.'); + $form->addError('Μη έγκυρος κωδικός πρόσβασης.'); } } ``` -Εάν είναι δυνατόν, συνιστούμε να προσθέσετε το σφάλμα απευθείας στο στοιχείο της φόρμας, καθώς θα εμφανίζεται δίπλα του όταν χρησιμοποιείτε τον προεπιλεγμένο renderer. +Εάν είναι δυνατόν, συνιστούμε να επισυνάψετε το σφάλμα απευθείας στο στοιχείο της φόρμας, καθώς θα εμφανιστεί δίπλα του όταν χρησιμοποιείτε τον προεπιλεγμένο renderer. ```php -$form['date']->addError('Sorry, this date is already taken.'); +$form['date']->addError('Συγγνώμη, αλλά αυτή η ημερομηνία είναι ήδη κατειλημμένη.'); ``` -Μπορείτε να καλέσετε επανειλημμένα το `addError()` για να περάσετε πολλαπλά μηνύματα σφάλματος σε μια φόρμα ή ένα στοιχείο. Τα λαμβάνετε με το `getErrors()`. +Μπορείτε να καλέσετε το `addError()` επανειλημμένα για να περάσετε πολλαπλά μηνύματα σφάλματος στη φόρμα ή στο στοιχείο. Μπορείτε να τα λάβετε χρησιμοποιώντας το `getErrors()`. -Σημειώστε ότι το `$form->getErrors()` επιστρέφει μια σύνοψη όλων των μηνυμάτων σφάλματος, ακόμη και εκείνων που έχουν περάσει απευθείας σε μεμονωμένα στοιχεία, όχι μόνο απευθείας στη φόρμα. Τα μηνύματα σφαλμάτων που περνούν μόνο στη φόρμα ανακτώνται μέσω του `$form->getOwnErrors()`. +Προσοχή, το `$form->getErrors()` επιστρέφει μια σύνοψη όλων των μηνυμάτων σφάλματος, συμπεριλαμβανομένων εκείνων που παραδόθηκαν απευθείας σε μεμονωμένα στοιχεία, όχι μόνο απευθείας στη φόρμα. Μπορείτε να λάβετε τα μηνύματα σφάλματος που παραδόθηκαν μόνο στη φόρμα μέσω του `$form->getOwnErrors()`. -Τροποποίηση τιμών εισόδου .[#toc-modifying-input-values] -======================================================== +Τροποποίηση Εισόδου +=================== -Χρησιμοποιώντας τη μέθοδο `addFilter()`, μπορούμε να τροποποιήσουμε την τιμή που εισάγει ο χρήστης. Σε αυτό το παράδειγμα, θα ανεχτούμε και θα αφαιρέσουμε τα κενά στον ταχυδρομικό κώδικα: +Χρησιμοποιώντας τη μέθοδο `addFilter()`, μπορούμε να τροποποιήσουμε την τιμή που εισήγαγε ο χρήστης. Σε αυτό το παράδειγμα, θα ανεχτούμε και θα αφαιρέσουμε κενά στον ταχυδρομικό κώδικα: ```php -$form->addText('zip', 'Postcode:') +$form->addText('zip', 'Τ.Κ.:') ->addFilter(function ($value) { - return str_replace(' ', '', $value); // αφαίρεση των κενών από τον ταχυδρομικό κώδικα + return str_replace(' ', '', $value); // αφαιρούμε τα κενά από τον Τ.Κ. }) - ->addRule($form::Pattern, 'The postal code is not five digits', '\d{5}'); + ->addRule($form::Pattern, 'Ο Τ.Κ. δεν είναι στη μορφή πέντε ψηφίων.', '\d{5}'); ``` -Το φίλτρο περιλαμβάνεται μεταξύ των κανόνων επικύρωσης και των συνθηκών και επομένως εξαρτάται από τη σειρά των μεθόδων, δηλαδή το φίλτρο και ο κανόνας καλούνται με την ίδια σειρά που είναι η σειρά των μεθόδων `addFilter()` και `addRule()`. +Το φίλτρο ενσωματώνεται μεταξύ των κανόνων επικύρωσης και των συνθηκών, οπότε η σειρά των μεθόδων έχει σημασία, δηλαδή το φίλτρο και ο κανόνας καλούνται με την ίδια σειρά όπως οι μέθοδοι `addFilter()` και `addRule()`. -Επικύρωση JavaScript .[#toc-javascript-validation] -================================================== +Επικύρωση JavaScript +==================== -Η γλώσσα των κανόνων επικύρωσης και των συνθηκών είναι ισχυρή. Παρόλο που όλες οι κατασκευές λειτουργούν τόσο στην πλευρά του διακομιστή όσο και στην πλευρά του πελάτη, στη JavaScript. Οι κανόνες μεταφέρονται σε χαρακτηριστικά HTML `data-nette-rules` ως JSON. -Η ίδια η επικύρωση χειρίζεται από ένα άλλο σενάριο, το οποίο αγκιστρώνει όλα τα συμβάντα της φόρμας `submit`, επαναλαμβάνει όλες τις εισόδους και εκτελεί τις αντίστοιχες επικυρώσεις. +Η γλώσσα για τη διατύπωση συνθηκών και κανόνων είναι πολύ ισχυρή. Όλες οι κατασκευές λειτουργούν τόσο στην πλευρά του διακομιστή όσο και στην πλευρά του JavaScript. Μεταφέρονται σε χαρακτηριστικά HTML `data-nette-rules` ως JSON. Η ίδια η επικύρωση εκτελείται από ένα σενάριο που παρακολουθεί το συμβάν `submit` της φόρμας, διατρέχει τα μεμονωμένα στοιχεία και εκτελεί την αντίστοιχη επικύρωση. -Αυτό το σενάριο είναι το `netteForms.js`, το οποίο είναι διαθέσιμο από διάφορες πιθανές πηγές: +Αυτό το σενάριο είναι το `netteForms.js` και είναι διαθέσιμο από πολλές πιθανές πηγές: -Μπορείτε να ενσωματώσετε το σενάριο απευθείας στη σελίδα HTML από το CDN: +Μπορείτε να εισαγάγετε το σενάριο απευθείας στη σελίδα HTML από ένα CDN: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -ή να το αντιγράψετε τοπικά στο δημόσιο φάκελο του έργου (π.χ. από το `vendor/nette/forms/src/assets/netteForms.min.js`): +Ή να το αντιγράψετε τοπικά στον δημόσιο φάκελο του έργου (π.χ. από το `vendor/nette/forms/src/assets/netteForms.min.js`): ```latte <script src="/path/to/netteForms.min.js"></script> ``` -Ή εγκαταστήστε μέσω [του npm |https://www.npmjs.com/package/nette-forms]: +Ή να το εγκαταστήσετε μέσω [npm|https://www.npmjs.com/package/nette-forms]: ```shell npm install nette-forms ``` -Και στη συνέχεια φορτώστε και εκτελέστε: +Και στη συνέχεια να το φορτώσετε και να το εκτελέσετε: ```js import netteForms from 'nette-forms'; netteForms.initOnLoad(); ``` -Εναλλακτικά, μπορείτε να το φορτώσετε απευθείας από το φάκελο `vendor`: +Εναλλακτικά, μπορείτε να το φορτώσετε απευθείας από τον φάκελο `vendor`: ```js import netteForms from '../path/to/vendor/nette/forms/src/assets/netteForms.js'; @@ -311,10 +321,10 @@ netteForms.initOnLoad(); ``` -Δυναμική JavaScript .[#toc-dynamic-javascript] -============================================== +Δυναμικό JavaScript +=================== -Θέλετε να εμφανίσετε τα πεδία διεύθυνσης μόνο αν ο χρήστης επιλέξει να στείλει τα αγαθά ταχυδρομικώς; Κανένα πρόβλημα. Το κλειδί είναι ένα ζεύγος μεθόδων `addCondition()` & `toggle()`: +Θέλετε να εμφανίσετε τα πεδία για την εισαγωγή της διεύθυνσης μόνο εάν ο χρήστης επιλέξει την αποστολή των αγαθών ταχυδρομικώς; Κανένα πρόβλημα. Το κλειδί είναι το ζεύγος μεθόδων `addCondition()` & `toggle()`: ```php $form->addCheckbox('send_it') @@ -322,25 +332,25 @@ $form->addCheckbox('send_it') ->toggle('#address-container'); ``` -Αυτός ο κώδικας λέει ότι όταν ικανοποιείται η συνθήκη, δηλαδή όταν το πλαίσιο ελέγχου είναι επιλεγμένο, το στοιχείο HTML `#address-container` θα είναι ορατό. Και το αντίστροφο. Έτσι, τοποθετούμε τα στοιχεία της φόρμας με τη διεύθυνση του παραλήπτη σε ένα δοχείο με αυτό το ID, και όταν πατηθεί το πλαίσιο ελέγχου, αυτά αποκρύπτονται ή εμφανίζονται. Αυτό το χειρίζεται το σενάριο `netteForms.js`. +Αυτός ο κώδικας λέει ότι όταν η συνθήκη πληρούται, δηλαδή όταν το πλαίσιο ελέγχου είναι επιλεγμένο, το στοιχείο HTML `#address-container` θα είναι ορατό. Και αντίστροφα. Έτσι, τοποθετούμε τα στοιχεία της φόρμας με τη διεύθυνση του παραλήπτη σε ένα container με αυτό το ID, και όταν κάνουμε κλικ στο πλαίσιο ελέγχου, θα αποκρυφθούν ή θα εμφανιστούν. Αυτό διασφαλίζεται από το σενάριο `netteForms.js`. -Οποιοσδήποτε επιλογέας μπορεί να περάσει ως όρισμα στη μέθοδο `toggle()`. Για ιστορικούς λόγους, μια αλφαριθμητική συμβολοσειρά χωρίς άλλους ειδικούς χαρακτήρες αντιμετωπίζεται ως αναγνωριστικό στοιχείου, το ίδιο όπως αν προηγούνταν το `#` character. The second optional parameter allows us to reverse the behavior, i.e. if we used `toggle('#address-container', false)`, το στοιχείο θα εμφανιζόταν μόνο αν το πλαίσιο ελέγχου ήταν απενεργοποιημένο. +Οποιοσδήποτε επιλογέας μπορεί να περάσει ως όρισμα στη μέθοδο `toggle()`. Για ιστορικούς λόγους, μια αλφαριθμητική συμβολοσειρά χωρίς άλλους ειδικούς χαρακτήρες νοείται ως το ID του στοιχείου, δηλαδή σαν να προηγείται ο χαρακτήρας `#`. Η δεύτερη προαιρετική παράμετρος επιτρέπει την αντιστροφή της συμπεριφοράς, δηλαδή αν χρησιμοποιούσαμε `toggle('#address-container', false)`, το στοιχείο θα εμφανιζόταν μόνο εάν το πλαίσιο ελέγχου δεν ήταν επιλεγμένο. -Η προεπιλεγμένη εφαρμογή JavaScript αλλάζει την ιδιότητα `hidden` για τα στοιχεία. Ωστόσο, μπορούμε εύκολα να αλλάξουμε τη συμπεριφορά, για παράδειγμα προσθέτοντας ένα animation. Απλά υπερκαλύψτε τη μέθοδο `Nette.toggle` στη JavaScript με μια προσαρμοσμένη λύση: +Η προεπιλεγμένη υλοποίηση στο JavaScript αλλάζει την ιδιότητα `hidden` των στοιχείων. Ωστόσο, μπορούμε εύκολα να αλλάξουμε τη συμπεριφορά, για παράδειγμα, προσθέτοντας μια κίνηση. Απλά αντικαταστήστε τη μέθοδο `Nette.toggle` στο JavaScript με τη δική σας λύση: ```js Nette.toggle = (selector, visible, srcElement, event) => { document.querySelectorAll(selector).forEach((el) => { - // hide or show 'el' according to the value of 'visible' + // απόκρυψη ή εμφάνιση του 'el' βάσει της τιμής 'visible' }); }; ``` -Απενεργοποίηση της επικύρωσης .[#toc-disabling-validation] -========================================================== +Απενεργοποίηση Επικύρωσης +========================= -Σε ορισμένες περιπτώσεις, πρέπει να απενεργοποιήσετε την επικύρωση. Εάν ένα κουμπί υποβολής δεν πρέπει να εκτελεί επικύρωση μετά την υποβολή (για παράδειγμα το κουμπί *Ακύρωση* ή *Προεπισκόπηση*), μπορείτε να απενεργοποιήσετε την επικύρωση καλώντας το `$submit->setValidationScope([])`. Μπορείτε επίσης να επικυρώσετε τη φόρμα μερικώς καθορίζοντας στοιχεία προς επικύρωση. +Μερικές φορές μπορεί να είναι χρήσιμο να απενεργοποιήσετε την επικύρωση. Εάν το πάτημα ενός κουμπιού υποβολής δεν πρέπει να εκτελεί επικύρωση (κατάλληλο για κουμπιά *Ακύρωση* ή *Προεπισκόπηση*), μπορούμε να την απενεργοποιήσουμε χρησιμοποιώντας τη μέθοδο `$submit->setValidationScope([])`. Εάν πρέπει να εκτελεί μόνο μερική επικύρωση, μπορούμε να καθορίσουμε ποια πεδία ή containers φόρμας πρέπει να επικυρωθούν. ```php $form->addText('name') @@ -354,13 +364,13 @@ $details->addInteger('age2') $form->addSubmit('send1'); // Επικυρώνει ολόκληρη τη φόρμα $form->addSubmit('send2') - ->setValidationScope([]); // Δεν επικυρώνει τίποτα + ->setValidationScope([]); // Δεν επικυρώνει καθόλου $form->addSubmit('send3') - ->setValidationScope([$form['name']]); // Επικυρώνει μόνο το πεδίο 'όνομα' + ->setValidationScope([$form['name']]); // Επικυρώνει μόνο το στοιχείο name $form->addSubmit('send4') - ->setValidationScope([$form['details']['age']]); // Επικυρώνει μόνο το πεδίο 'age'. + ->setValidationScope([$form['details']['age']]); // Επικυρώνει μόνο το στοιχείο age $form->addSubmit('send5') - ->setValidationScope([$form['details']]); // Επικυρώνει το δοχείο 'details'. + ->setValidationScope([$form['details']]); // Επικυρώνει το container details ``` -[Το συμβάν onValidate |#Event onValidate] στη φόρμα καλείται πάντα και δεν επηρεάζεται από το `setValidationScope`. Το συμβάν `onValidate` στο δοχείο καλείται μόνο όταν αυτό το δοχείο καθορίζεται για μερική επικύρωση. +Το `setValidationScope` δεν επηρεάζει το [#συμβάν onValidate] στη φόρμα, το οποίο θα καλείται πάντα. Το συμβάν `onValidate` σε ένα container θα ενεργοποιείται μόνο εάν αυτό το container έχει επισημανθεί για μερική επικύρωση. diff --git a/forms/en/@home.texy b/forms/en/@home.texy index 2f4c2d1b11..a9b2549490 100644 --- a/forms/en/@home.texy +++ b/forms/en/@home.texy @@ -1,24 +1,24 @@ -Forms -***** +Nette Forms +*********** <div class=perex> -Nette Forms has revolutionized the creation of web forms. All you had to do was write a few clear lines of code and you had a form, including rendering, JavaScript, and server validation, plus industry-leading security. Let's see how +Nette Forms revolutionized the creation of web forms. Suddenly, writing just a few clear lines of code was enough to get a complete form, including rendering, JavaScript and server-side validation, plus top-notch security. We'll show you how to: -- create friendly forms -- validate sent data -- draw elements exactly as needed +- create user-friendly forms +- validate submitted data +- render elements exactly as needed </div> -With Nette Forms, you can diminish routine tasks like writing validation (on both server and client-side), and minimizing the likelihood of errors and security issues. +Using Nette Forms, you can avoid many routine tasks, such as writing validation logic (both server-side and client-side), and minimize the probability of errors and security vulnerabilities. -You can use the forms either as a part of the Nette Application (ie in presenters) or standalone. Because the use is slightly different in both cases, we have prepared separated instructions for you: +You can use forms either as part of a Nette Application (i.e., in presenters) or completely standalone. Since the usage differs slightly in both cases, we have prepared separate guides for you: <div class="wiki-buttons"> -<div> "Forms in presenters .[wiki-button]":in-presenter </div> -<div> "Forms standalone .[wiki-button]":standalone </div> +<div> "Forms in Presenters .[wiki-button]":in-presenter </div> +<div> "Forms Standalone .[wiki-button]":standalone </div> </div> diff --git a/forms/en/@left-menu.texy b/forms/en/@left-menu.texy index a3d71b3d07..f23cd43594 100644 --- a/forms/en/@left-menu.texy +++ b/forms/en/@left-menu.texy @@ -1,5 +1,5 @@ -Forms -***** +Nette Forms +*********** - [Overview |@home] - [Forms in Presenters|in-presenter] - [Forms Standalone|standalone] @@ -9,6 +9,6 @@ Forms - [Configuration] -Further reading +Further Reading *************** - [Best practices |best-practices:] diff --git a/forms/en/@meta.texy b/forms/en/@meta.texy new file mode 100644 index 0000000000..42471908b0 --- /dev/null +++ b/forms/en/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Documentation}} diff --git a/forms/en/configuration.texy b/forms/en/configuration.texy index 735dea55c0..ea3c55f991 100644 --- a/forms/en/configuration.texy +++ b/forms/en/configuration.texy @@ -1,5 +1,5 @@ -Configuring Forms -***************** +Forms Configuration +******************* .[perex] You can change the default [form error messages|validation] in the configuration. @@ -24,10 +24,38 @@ forms: MaxFileSize: 'The size of the uploaded file can be up to %d bytes.' MaxPostSize: 'The uploaded data exceeds the limit of %d bytes.' MimeType: 'The uploaded file is not in the expected format.' - Image: 'The uploaded file must be image in format JPEG, GIF, PNG or WebP.' + Image: 'The uploaded file must be image in format JPEG, GIF, PNG, WebP or AVIF.' Nette\Forms\Controls\SelectBox::Valid: 'Please select a valid option.' Nette\Forms\Controls\UploadControl::Valid: 'An error occurred during file upload.' Nette\Forms\Controls\CsrfProtection::Protection: 'Your session has expired. Please return to the home page and try again.' ``` +/--comment + + + + + + + + + + + + + + + + + + + + + + + + + +\-- + If you are not using the whole framework and therefore not even the configuration files, you can change the default error messages directly in the `Nette\Forms\Validator::$messages` field. diff --git a/forms/en/controls.texy b/forms/en/controls.texy index 4b59401647..0850b9ab5e 100644 --- a/forms/en/controls.texy +++ b/forms/en/controls.texy @@ -2,13 +2,13 @@ Form Controls ************* .[perex] -Overview of built-in form controls. +Overview of standard form controls. -addText(string|int $name, $label=null): TextInput .[method] -=========================================================== +addText(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +================================================================================================== -Adds single line text field (class [TextInput |api:Nette\Forms\Controls\TextInput]). If the user does not fill in the field, it returns an empty string `''`, or use `setNullable()` to change it to return `null`. +Adds a single-line text input field (class [TextInput |api:Nette\Forms\Controls\TextInput]). If the user does not fill in the field, it returns an empty string `''`, or use `setNullable()` to make it return `null` instead. ```php $form->addText('name', 'Name:') @@ -16,19 +16,16 @@ $form->addText('name', 'Name:') ->setNullable(); ``` -It automatically validates UTF-8, trims left and right whitespaces, and removes line breaks that could be sent by an attacker. +Automatically validates UTF-8, trims leading and trailing whitespace, and removes line breaks that could be sent by an attacker. -The maximum length can be limited using `setMaxLength()`. The [addFilter()|validation#Modifying Input Values] allows you to change the user-entered value. +The maximum length can be limited using `setMaxLength()`. The [addFilter() |validation#Modifying Input Values] method allows modifying the value entered by the user. -Use `setHtmlType()` to change the [character|https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types] of input element to `search`, `tel`, `url`, `range`, `date`, `datetime-local`, `month`, `time`, `week`, `color`. Instead of the `number` and `email` types, we recommend using [#addInteger] and [#addEmail], which provide server-side validation. +Using `setHtmlType()`, you can change the visual appearance of the text field to types like `search`, `tel`, or `url` as defined in the [specification|https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types]. Remember that changing the type is purely visual and does not replace validation functionality. For the `url` type, it's advisable to add a specific [URL validation rule |validation#Text inputs]. -```php -$form->addText('color', 'Choose color:') - ->setHtmlType('color') - ->addRule($form::Pattern, 'invalid value', '[0-9a-f]{6}'); -``` +.[note] +For other input types like `number`, `range`, `email`, `date`, `datetime-local`, `time`, and `color`, use specialized methods such as [#addInteger], [#addFloat], [#addEmail], [#addDate], [#addTime], [#addDateTime], and [#addColor], which provide server-side validation. The types `month` and `week` are not yet fully supported by all browsers. -The so-called empty-value can be set for the element, which is something like the default value, but if the user does not overwrite it, returns empty string or `null`. +An "empty value" can be set for the control. This acts somewhat like a default value, but if the user doesn't change it, the control returns an empty string or `null`. ```php $form->addText('phone', 'Phone:') @@ -40,63 +37,80 @@ $form->addText('phone', 'Phone:') addTextArea(string|int $name, $label=null): TextArea .[method] ============================================================== -Adds a multiline text field (class [TextArea |api:Nette\Forms\Controls\TextArea]). If the user does not fill in the field, it returns an empty string `''`, or use `setNullable()` to change it to return `null`. +Adds a multi-line text input field (class [TextArea |api:Nette\Forms\Controls\TextArea]). If the user doesn't fill in the field, it returns an empty string `''`, or use `setNullable()` to make it return `null` instead. ```php $form->addTextArea('note', 'Note:') ->addRule($form::MaxLength, 'Your note is way too long', 10000); ``` -Automatically validates UTF-8 and normalizes line breaks to `\n`. Unlike a single-line input field, it does not trim the whitespace. +Automatically validates UTF-8 and normalizes line endings to `\n`. Unlike the single-line input field, no whitespace trimming occurs. -The maximum length can be limited using `setMaxLength()`. The [addFilter()|validation#Modifying Input Values] allows you to change the user-entered value. You can set the so-called empty-value using `setEmptyValue()`. +The maximum length can be limited using `setMaxLength()`. The [addFilter() |validation#Modifying Input Values] method allows modifying the user-entered value. An empty value can be set using `setEmptyValue()`. addInteger(string|int $name, $label=null): TextInput .[method] ============================================================== -Adds input field for integer (class [TextInput |api:Nette\Forms\Controls\TextInput]). Returns either an integer or `null` if the user does not enter anything. +Adds an input field for entering an integer (class [TextInput |api:Nette\Forms\Controls\TextInput]). Returns either an integer or `null` if the user enters nothing. + +```php +$form->addInteger('year', 'Year:') + ->addRule($form::Range, 'The year must be between %d and %d.', [1900, 2023]); +``` + +The control renders as `<input type="number">`. Using the `setHtmlType()` method, you can change the type to `range` for display as a slider, or to `text` if you prefer a standard text field without the special behavior of the `number` type. + + +addFloat(string|int $name, $label=null): TextInput .[method]{data-version:3.1.12} +================================================================================= + +Adds an input field for entering a floating-point number (class [TextInput |api:Nette\Forms\Controls\TextInput]). Returns either a float or `null` if the user enters nothing. ```php -$form->addInteger('level', 'Level:') - ->setDefaultValue(0) - ->addRule($form::Range, 'Level must be between %d and %d.', [0, 100]); +$form->addFloat('level', 'Level:') + ->setDefaultValue(0.0) // Explicitly set float default + ->addRule($form::Range, 'The level must be between %f and %f.', [0.0, 100.0]); // Use float range and %f ``` +The control renders as `<input type="number">`. Using the `setHtmlType()` method, you can change the type to `range` for display as a slider, or to `text` if you prefer a standard text field without the special behavior of the `number` type. + +Nette and the Chrome browser accept both a comma and a dot as decimal separators. To enable this functionality in Firefox as well, it is recommended to set the `lang` attribute either for the specific control or for the entire page, for example, `<html lang="en">`. + -addEmail(string|int $name, $label=null): TextInput .[method] -============================================================ +addEmail(string|int $name, $label=null, int $maxLength=255): TextInput .[method] +================================================================================ -Adds email address field with validity check (class [TextInput |api:Nette\Forms\Controls\TextInput]). If the user does not fill in the field, it returns an empty string `''`, or use `setNullable()` to change it to return `null`. +Adds an input field for entering an email address (class [TextInput |api:Nette\Forms\Controls\TextInput]). If the user doesn't fill in the field, it returns an empty string `''`, or use `setNullable()` to make it return `null` instead. ```php -$form->addEmail('email', 'Email:'); +$form->addEmail('email', 'E-mail:'); ``` -Verifies that the value is a valid email address. It does not verify that the domain actually exists, only the syntax is verified. Automatically validates UTF-8, trims left and right whitespaces. +Validates that the value is a valid email address. It does not check if the domain actually exists, only the syntax is verified. Automatically validates UTF-8 and trims leading and trailing whitespace. -The maximum length can be limited using `setMaxLength()`. The [addFilter()|validation#Modifying Input Values] allows you to change the user-entered value. You can set the so-called empty-value using `setEmptyValue()`. +The maximum length can be limited using `setMaxLength()`. The [addFilter() |validation#Modifying Input Values] method allows modifying the user-entered value. An empty value can be set using `setEmptyValue()`. -addPassword(string|int $name, $label=null): TextInput .[method] -=============================================================== +addPassword(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +====================================================================================================== -Adds password field (class [TextInput |api:Nette\Forms\Controls\TextInput]). +Adds a password input field (class [TextInput |api:Nette\Forms\Controls\TextInput]). ```php $form->addPassword('password', 'Password:') ->setRequired() - ->addRule($form::MinLength, 'Password has to be at least %d characters long', 8) + ->addRule($form::MinLength, 'Password must be at least %d characters long', 8) ->addRule($form::Pattern, 'Password must contain a number', '.*[0-9].*'); ``` -When you re-send the form, the input will be blank. It automatically validates UTF-8, trims left and right whitespaces, and removes line breaks that could be sent by an attacker. +When the form is redisplayed, the field will be empty. Automatically validates UTF-8, trims leading and trailing whitespace, and removes line breaks that could be sent by an attacker. addCheckbox(string|int $name, $caption=null): Checkbox .[method] ================================================================ -Adds a checkbox (class [Checkbox |api:Nette\Forms\Controls\Checkbox]). The field returns either `true` or `false`, depending on whether it is checked. +Adds a checkbox (class [Checkbox |api:Nette\Forms\Controls\Checkbox]). Returns `true` or `false`, depending on whether it is checked. ```php $form->addCheckbox('agree', 'I agree with terms') @@ -104,10 +118,10 @@ $form->addCheckbox('agree', 'I agree with terms') ``` -addCheckboxList(string|int $name, $label=null, array $items=null): CheckboxList .[method] -========================================================================================= +addCheckboxList(string|int $name, $label=null, ?array $items=null): CheckboxList .[method] +========================================================================================== -Adds list of checkboxes for selecting multiple items (class [CheckboxList |api:Nette\Forms\Controls\CheckboxList]). Returns the array of keys of the selected items. The `getSelectedItems()` method returns values instead of keys. +Adds a list of checkboxes for selecting multiple items (class [CheckboxList |api:Nette\Forms\Controls\CheckboxList]). Returns an array of the keys of the selected items. The `getSelectedItems()` method returns the values instead of keys. ```php $form->addCheckboxList('colors', 'Colors:', [ @@ -117,19 +131,25 @@ $form->addCheckboxList('colors', 'Colors:', [ ]); ``` -We pass the array of items as the third parameter, or by the `setItems()` method. +Pass the array of offered items as the third parameter or using the `setItems()` method. -You can use `setDisabled(['r', 'g'])` to disable individual items. +Use `setDisabled(['r', 'g'])` to disable individual items. -The element automatically checks that there has been no forgery and that the selected items are actually one of the offered ones and have not been disabled. The `getRawValue()` method can be used to retrieve submitted items without this important check. +The control automatically checks that no forgery occurred and that the selected items are indeed among the offered ones and were not disabled. The `getRawValue()` method can be used to retrieve submitted items without this important check. -When default values are set, it also checks that they are one of the offered items, otherwise it throws an exception. This check can be turned off with `checkDefaultValue(false)`. +When setting default selected items, it also checks that they are among the offered ones, otherwise it throws an exception. This check can be disabled using `checkDefaultValue(false)`. +If you are submitting the form using the `GET` method, you can choose a more compact data transfer method that saves query string size. Activate it by setting an HTML attribute on the form: + +```php +$form->setHtmlAttribute('data-nette-compact'); +``` -addRadioList(string|int $name, $label=null, array $items=null): RadioList .[method] -=================================================================================== -Adds radio buttons (class [RadioList |api:Nette\Forms\Controls\RadioList]). Returns the key of the selected item, or `null` if the user did not select anything. The `getSelectedItem()` method returns a value instead of a key. +addRadioList(string|int $name, $label=null, ?array $items=null): RadioList .[method] +==================================================================================== + +Adds radio buttons (class [RadioList |api:Nette\Forms\Controls\RadioList]). Returns the key of the selected item, or `null` if the user selected nothing. The `getSelectedItem()` method returns the value instead of the key. ```php $sex = [ @@ -139,23 +159,23 @@ $sex = [ $form->addRadioList('gender', 'Gender:', $sex); ``` -We pass the array of items as the third parameter, or by the `setItems()` method. +Pass the array of offered items as the third parameter or using the `setItems()` method. -You can use `setDisabled(['m'])` to disable individual items. +Use `setDisabled(['m'])` to disable individual items. -The element automatically checks that there has been no forgery and that the selected item is actually one of the offered ones and has not been disabled. The `getRawValue()` method can be used to retrieve the submitted item without this important check. +The control automatically checks that no forgery occurred and that the selected item is indeed one of the offered ones and was not disabled. The `getRawValue()` method can be used to retrieve the submitted item without this important check. -When default value is set, it also checks that it is one of the offered items, otherwise it throws an exception. This check can be turned off with `checkDefaultValue(false)`. +When setting the default selected item, it also checks that it is one of the offered ones, otherwise it throws an exception. This check can be disabled using `checkDefaultValue(false)`. -addSelect(string|int $name, $label=null, array $items=null): SelectBox .[method] -================================================================================ +addSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): SelectBox .[method] +================================================================================================== -Adds select box (class [SelectBox |api:Nette\Forms\Controls\SelectBox]). Returns the key of the selected item, or `null` if the user did not select anything. The `getSelectedItem()` method returns a value instead of a key. +Adds a select box (class [SelectBox |api:Nette\Forms\Controls\SelectBox]). Returns the key of the selected item, or `null` if the user selected nothing. The `getSelectedItem()` method returns the value instead of the key. ```php $countries = [ - 'CZ' => 'Czech republic', + 'CZ' => 'Czech Republic', 'SK' => 'Slovakia', 'GB' => 'United Kingdom', ]; @@ -164,12 +184,12 @@ $form->addSelect('country', 'Country:', $countries) ->setDefaultValue('SK'); ``` -We pass the array of items as the third parameter, or by the `setItems()` method. The array of items can also be two-dimensional: +Pass the array of offered items as the third parameter or using the `setItems()` method. Items can also be a two-dimensional array (representing optgroups): ```php $countries = [ 'Europe' => [ - 'CZ' => 'Czech republic', + 'CZ' => 'Czech Republic', 'SK' => 'Slovakia', 'GB' => 'United Kingdom', ], @@ -179,92 +199,167 @@ $countries = [ ]; ``` -For select boxes, the first item often has a special meaning, it serves as a call-to-action. Use the `setPrompt()` method to add such an entry. +In select boxes, the first item often has a special meaning, serving as a prompt to action. Use the `setPrompt()` method to add such an item. ```php $form->addSelect('country', 'Country:', $countries) - ->setPrompt('Pick a country'); + ->setPrompt('Choose a country'); ``` -You can use `setDisabled(['CZ', 'SK'])` to disable individual items. +Use `setDisabled(['CZ', 'SK'])` to disable individual items. -The element automatically checks that there has been no forgery and that the selected item is actually one of the offered ones and has not been disabled. The `getRawValue()` method can be used to retrieve the submitted item without this important check. +The control automatically checks that no forgery occurred and that the selected item is indeed one of the offered ones and was not disabled. The `getRawValue()` method can be used to retrieve the submitted item without this important check. -When default value is set, it also checks that it is one of the offered items, otherwise it throws an exception. This check can be turned off with `checkDefaultValue(false)`. +When setting the default selected item, it also checks that it is one of the offered ones, otherwise it throws an exception. This check can be disabled using `checkDefaultValue(false)`. -addMultiSelect(string|int $name, $label=null, array $items=null): MultiSelectBox .[method] -========================================================================================== +addMultiSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): MultiSelectBox .[method] +============================================================================================================ -Adds multichoice select box (class [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox]). Returns the array of keys of the selected items. The `getSelectedItems()` method returns values instead of keys. +Adds a select box for selecting multiple items (class [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox]). Returns an array of the keys of the selected items. The `getSelectedItems()` method returns the values instead of keys. ```php $form->addMultiSelect('countries', 'Countries:', $countries); ``` -We pass the array of items as the third parameter, or by the `setItems()` method. The array of items can also be two-dimensional. +Pass the array of offered items as the third parameter or using the `setItems()` method. Items can also be a two-dimensional array. -You can use `setDisabled(['CZ', 'SK'])` to disable individual items. +Use `setDisabled(['CZ', 'SK'])` to disable individual items. -The element automatically checks that there has been no forgery and that the selected items are actually one of the offered ones and have not been disabled. The `getRawValue()` method can be used to retrieve submitted items without this important check. +The control automatically checks that no forgery occurred and that the selected items are indeed among the offered ones and were not disabled. The `getRawValue()` method can be used to retrieve submitted items without this important check. -When default values are set, it also checks that they are one of the offered items, otherwise it throws an exception. This check can be turned off with `checkDefaultValue(false)`. +When setting default selected items, it also checks that they are among the offered ones, otherwise it throws an exception. This check can be disabled using `checkDefaultValue(false)`. addUpload(string|int $name, $label=null): UploadControl .[method] ================================================================= -Adds file upload field (class [UploadControl |api:Nette\Forms\Controls\UploadControl]). Returns the [FileUpload |http:request#FileUpload] object, even if the user has not uploaded a file, which can be find out by the `FileUpload::hasFile()` method. +Adds a file upload field (class [UploadControl |api:Nette\Forms\Controls\UploadControl]). Returns a [FileUpload |http:request#FileUpload] object, even if the user did not upload any file, which can be checked using the `FileUpload::hasFile()` method. ```php $form->addUpload('avatar', 'Avatar:') - ->addRule($form::Image, 'Avatar must be JPEG, PNG, GIF or WebP') - ->addRule($form::MaxFileSize, 'Maximum size is 1 MB', 1024 * 1024); + ->addRule($form::Image, 'Avatar must be JPEG, PNG, GIF, WebP or AVIF.') + ->addRule($form::MaxFileSize, 'Maximum size is 1 MB.', 1024 * 1024); ``` -If the file did not upload correctly, the form was not submitted successfully and an error is displayed. I.e. it is not necessary to check the `FileUpload::isOk()` method. +If the file fails to upload correctly, the form is not successfully submitted, and an error is displayed. That is, upon successful submission, it is not necessary to check the `FileUpload::isOk()` method. -Do not trust the original file name returned by method `FileUpload::getName()`, a client could send a malicious filename with the intention to corrupt or hack your application. +Never trust the original file name returned by the `FileUpload::getName()` method; the client could have sent a malicious file name with the intent to damage or hack your application. -Rules `MimeType` and `Image` detect required type of file or image by its signature. The integrity of the entire file is not checked. You can find out if an image is not corrupted for example by trying to [load it|http:request#toImage]. +The `MimeType` and `Image` rules detect the required type based on the file's signature and do not verify its integrity. Whether an image is corrupted can be determined, for example, by attempting to [load it |http:request#toImage]. addMultiUpload(string|int $name, $label=null): UploadControl .[method] ====================================================================== -Adds multiple file upload field (class [UploadControl |api:Nette\Forms\Controls\UploadControl]). Returns an array of objects [FileUpload |http:request#FileUpload]. The `FileUpload::hasFile()` method will return `true` for each of them. +Adds a field for uploading multiple files at once (class [UploadControl |api:Nette\Forms\Controls\UploadControl]). Returns an array of [FileUpload |http:request#FileUpload] objects. The `FileUpload::hasFile()` method for each of them will return `true`. ```php $form->addMultiUpload('files', 'Files:') - ->addRule($form::MaxLength, 'A maximum of %d files can be uploaded', 10); + ->addRule($form::MaxLength, 'Maximum of %d files can be uploaded.', 10); // Added period +``` + +If any file fails to upload correctly, the form is not successfully submitted, and an error is displayed. That is, upon successful submission, it is not necessary to check the `FileUpload::isOk()` method for each file. + +Never trust the original file names returned by the `FileUpload::getName()` method; the client could have sent malicious file names with the intent to damage or hack your application. + +The `MimeType` and `Image` rules detect the required type based on the file's signature and do not verify its integrity. Whether an image is corrupted can be determined, for example, by attempting to [load it |http:request#toImage]. + + +addDate(string|int $name, $label=null): DateTimeControl .[method]{data-version:3.1.14} +====================================================================================== + +Adds a field that allows the user to easily enter a date consisting of year, month, and day (class [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +As a default value, it accepts objects implementing the `DateTimeInterface`, a string containing time, or a number representing a UNIX timestamp. The same applies to the arguments of the `Min`, `Max`, or `Range` rules, which define the minimum and maximum allowed dates. + +```php +$form->addDate('date', 'Date:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'The date must be at least one month old.', new DateTime('-1 month')); +``` + +By default, it returns a `DateTimeImmutable` object. Using the `setFormat()` method, you can specify a [text format|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] or a timestamp: + +```php +$form->addDate('date', 'Date:') + ->setFormat('Y-m-d'); +``` + + +addTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=============================================================================================================== + +Adds a field that allows the user to easily enter a time consisting of hours, minutes, and optionally seconds (class [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +As a default value, it accepts objects implementing the `DateTimeInterface`, a string containing time, or a number representing a UNIX timestamp. Only the time information from these inputs is used; the date is ignored. The same applies to the arguments of the `Min`, `Max`, or `Range` rules, which define the minimum and maximum allowed times. If the set minimum value is higher than the maximum, a time range spanning midnight is created. + +```php +$form->addTime('time', 'Time:', withSeconds: true) + ->addRule($form::Range, 'Time must be between %s and %s.', ['12:30', '13:30']); // Use %s for time strings +``` + +By default, it returns a `DateTimeImmutable` object (with the date set to January 1, year 1). Using the `setFormat()` method, you can specify a [text format|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters]: + +```php +$form->addTime('time', 'Time:') + ->setFormat('H:i'); +``` + + +addDateTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=================================================================================================================== + +Adds a field that allows the user to easily enter both date and time, consisting of year, month, day, hours, minutes, and optionally seconds (class [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +As a default value, it accepts objects implementing the `DateTimeInterface`, a string containing time, or a number representing a UNIX timestamp. The same applies to the arguments of the `Min`, `Max`, or `Range` rules, which define the minimum and maximum allowed date and time. + +```php +$form->addDateTime('datetime', 'Date and Time:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'The date must be at least one month old.', new DateTime('-1 month')); +``` + +By default, it returns a `DateTimeImmutable` object. Using the `setFormat()` method, you can specify a [text format|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] or a timestamp: + +```php +$form->addDateTime('datetime') + ->setFormat(DateTimeControl::FormatTimestamp); ``` -If one of the files fails to upload correctly, the form was not submitted successfully and an error is displayed. I.e. it is not necessary to check the `FileUpload::isOk()` method. -Do not trust the original file names returned by method `FileUpload::getName()`, a client could send a malicious filename with the intention to corrupt or hack your application. +addColor(string|int $name, $label=null): ColorPicker .[method]{data-version:3.1.14} +=================================================================================== + +Adds a color picker field (class [ColorPicker |api:Nette\Forms\Controls\ColorPicker]). The color is returned as a string in the format `#rrggbb`. If the user does not make a selection, it returns black `#000000`. -Rules `MimeType` and `Image` detect required type of file or image by its signature. The integrity of the entire file is not checked. You can find out if an image is not corrupted for example by trying to [load it|http:request#toImage]. +```php +$form->addColor('color', 'Color:') + ->setDefaultValue('#3C8ED7'); +``` -addHidden(string|int $name, string $default=null): HiddenField .[method] -======================================================================== +addHidden(string|int $name, ?string $default=null): HiddenField .[method] +========================================================================= -Adds hidden field (class [HiddenField |api:Nette\Forms\Controls\HiddenField]). +Adds a hidden field (class [HiddenField |api:Nette\Forms\Controls\HiddenField]). ```php $form->addHidden('userid'); ``` -Use `setNullable()` to change it to return `null` instead of an empty string. The [addFilter()|validation#Modifying Input Values] allows you to change the submitted value. +Use `setNullable()` to make it return `null` instead of an empty string. The [addFilter() |validation#Modifying Input Values] method allows modifying the submitted value. + +Although the control is hidden, **it is important to realize** that its value can still be modified or spoofed by an attacker. Always thoroughly verify and validate all received values on the server side to prevent security risks associated with data manipulation. addSubmit(string|int $name, $caption=null): SubmitButton .[method] ================================================================== -Adds submit button (class [SubmitButton |api:Nette\Forms\Controls\SubmitButton]). +Adds a submit button (class [SubmitButton |api:Nette\Forms\Controls\SubmitButton]). ```php -$form->addSubmit('submit', 'Register'); +$form->addSubmit('submit', 'Submit'); ``` It is possible to have more than one submit button in the form: @@ -274,7 +369,7 @@ $form->addSubmit('register', 'Register'); $form->addSubmit('cancel', 'Cancel'); ``` -To find out which of them was clicked, use: +To determine which one was clicked, use: ```php if ($form['register']->isSubmittedBy()) { @@ -282,13 +377,13 @@ if ($form['register']->isSubmittedBy()) { } ``` -If you don't want to validate the form when a submit button is pressed (such as *Cancel* or *Preview* buttons), you can turn it off with [setValidationScope()|validation#Disabling Validation]. +If you do not want to validate the entire form when a button is pressed (for example, for *Cancel* or *Preview* buttons), use [setValidationScope() |validation#Disabling Validation]. addButton(string|int $name, $caption): Button .[method] ======================================================= -Adds button (class [Button |api:Nette\Forms\Controls\Button]) without submit function. It is useful for binding other functionality to id, for example a JavaScript action. +Adds a button (class [Button |api:Nette\Forms\Controls\Button]) that does not have a submit function. It can therefore be used for other functions, e.g., calling a JavaScript function on click. ```php $form->addButton('raise', 'Raise salary') @@ -296,22 +391,22 @@ $form->addButton('raise', 'Raise salary') ``` -addImageButton(string|int $name, string $src=null, string $alt=null): ImageButton .[method] -=========================================================================================== +addImageButton(string|int $name, ?string $src=null, ?string $alt=null): ImageButton .[method] +============================================================================================= -Adds submit button in form of an image (class [ImageButton |api:Nette\Forms\Controls\ImageButton]). +Adds a submit button in the form of an image (class [ImageButton |api:Nette\Forms\Controls\ImageButton]). ```php -$form->addImageButton('submit', '/path/to/image'); +$form->addImageButton('submit', '/path/to/image.png', 'Submit'); ``` -When using multiple submit buttons, you can find out which one was clicked with `$form['submit']->isSubmittedBy()`. +When using multiple submit buttons, you can determine which one was clicked using `$form['submit']->isSubmittedBy()`. addContainer(string|int $name): Container .[method] =================================================== -Adds a sub-form (class [Container|api:Nette\Forms\Container]), or a container, which can be treated the same way as a form. That means you can use methods like `setDefaults()` or `getValues()`. +Adds a sub-form (class [Container|api:Nette\Forms\Container]), or a container, into which other controls can be added in the same way as they are added to the form. Methods like `setDefaults()` or `getValues()` work as well. ```php $sub1 = $form->addContainer('first'); @@ -323,7 +418,7 @@ $sub2->addText('name', 'Your name:'); $sub2->addEmail('email', 'Email:'); ``` -The sent data is then returned as a multidimensional structure: +The submitted data is then returned as a multi-dimensional structure: ```php [ @@ -342,66 +437,66 @@ The sent data is then returned as a multidimensional structure: Overview of Settings ==================== -For all elements we can call the following methods (see [API documentation|https://api.nette.org/forms/master/Nette/Forms/Controls.html] for a complete overview): +For all controls, we can call the following methods (see the [API documentation|https://api.nette.org/forms/master/Nette/Forms/Controls.html] for a complete overview): .[table-form-methods language-php] | `setDefaultValue($value)` | sets the default value -| `getValue()` | get current value -| `setOmitted()` | [#omitted values] -| `setDisabled()` | [#disabling inputs] +| `getValue()` | get the current value +| `setOmitted()` | [#Omitted Values] +| `setDisabled()` | [#Disabling Inputs] Rendering: .[table-form-methods language-php] -| `setCaption($caption)` | change the caption of the item -| `setTranslator($translator)` | sets [translator|rendering#translating] -| `setHtmlAttribute($name, $value)` | sets the [HTML attribute |rendering#HTML attributes] of the element +| `setCaption($caption)` | changes the control's label +| `setTranslator($translator)` | sets the [translator |rendering#Translating] +| `setHtmlAttribute($name, $value)` | sets an [HTML attribute |rendering#HTML Attributes] for the element | `setHtmlId($id)` | sets the HTML attribute `id` -| `setHtmlType($type)` | sets HTML attribute `type` -| `setHtmlName($name)` | sets HTML attribute `name` -| `setOption($key, $value)` | sets [rendering data|rendering#Options] +| `setHtmlType($type)` | sets the HTML attribute `type` +| `setHtmlName($name)` | sets the HTML attribute `name` +| `setOption($key, $value)` | [sets rendering options |rendering#Options] Validation: .[table-form-methods language-php] -| `setRequired()` | [mandatory field |validation] -| `addRule()` | set [validation rule |validation#Rules] -| `addCondition()`, `addConditionOn()` | set [validation condition|validation#Conditions] -| `addError($message)` | [passing error message|validation#processing-errors] +| `setRequired()` | makes the control [required |validation] +| `addRule()` | adds a [validation rule |validation#Rules] +| `addCondition()`, `addConditionOn()` | sets a [validation condition |validation#Conditions] +| `addError($message)` | [adds an error message |validation#Processing Errors] -The following methods can be called for the `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` items: +For controls `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`, the following methods can be called: .[table-form-methods language-php] -| `setNullable()` | sets whether getValue() returns `null` instead of empty string -| `setEmptyValue($value)` | sets the special value which is treated as empty string -| `setMaxLength($length)` | sets the maximum number of allowed characters -| `addFilter($filter)` | [modifying Input Values |validation#Modifying Input Values] +| `setNullable()` | sets whether getValue() returns `null` instead of an empty string +| `setEmptyValue($value)` | sets a special value that is considered an empty string +| `setMaxLength($length)` | sets the maximum allowed number of characters +| `addFilter($filter)` | [modifies input |validation#Modifying Input Values] Omitted Values --------------- +============== -If you are not interested in the value entered by the user, we can use `setOmitted()` to omit it from the result provided by the `$form->getValues​()` method or passed to handlers. This is suitable for various passwords for verification, antispam fields, etc. +If we are not interested in the value filled in by the user, we can use `setOmitted()` to exclude it from the result of the `$form->getValues()` method or from the data passed to handlers. This is useful for various password confirmation fields, anti-spam controls, etc. ```php $form->addPassword('passwordVerify', 'Password again:') ->setRequired('Fill your password again to check for typo') - ->addRule($form::Equal, 'Password mismatch', $form['password']) + ->addRule($form::Equal, 'Passwords do not match', $form['password']) ->setOmitted(); ``` Disabling Inputs ----------------- +================ -In order to disable an input, you can call `setDisabled()`. Such a field cannot be edited by the user. +Controls can be disabled using `setDisabled()`. A disabled control cannot be edited by the user. ```php $form->addText('username', 'User name:') ->setDisabled(); ``` -Note that the browser does not send the disabled fields to the server at all, so you won't even find them in the data returned by the `$form->getValues()` function. +Disabled controls are not sent by the browser to the server at all, so you won't find them in the data returned by the `$form->getValues()` function. However, if you set `setOmitted(false)`, Nette will include their default value in this data. -If you are setting a default value for a field, you must do so only after disabling it: +When `setDisabled()` is called, the **control's value is cleared** for security reasons. If you are setting a default value, it is necessary to do so after disabling it: ```php $form->addText('username', 'User name:') @@ -409,11 +504,13 @@ $form->addText('username', 'User name:') ->setDefaultValue($userName); ``` +An alternative to disabled controls are controls with the HTML `readonly` attribute, which the browser does send to the server. Although the control is read-only, **it is important to realize** that its value can still be modified or spoofed by an attacker. + Custom Controls =============== -Besides wide range of built-in form controls you can add custom controls to the form as follows: +Besides the wide range of built-in form controls, you can add custom controls to the form in this way: ```php $form->addComponent(new DateInput('Date:'), 'date'); @@ -421,15 +518,15 @@ $form->addComponent(new DateInput('Date:'), 'date'); ``` .[note] -The form is a descendent of the class [Container| component-model:#Container] and the elements are descendants of [Component | component-model:#Component]. +The form is a descendant of the [Container |component-model:#Container] class, and individual controls are descendants of the [Component |component-model:#Component] class. -There is a way to define new form methods for adding custom elements (eg `$form->addZip()`). These are the so-called extension methods. The downside is that code hints in editors won't work for them. +There is a way to define new form methods for adding custom controls (e.g., `$form->addZip()`). These are called extension methods. The disadvantage is that code completion in editors will not work for them. ```php use Nette\Forms\Container; -// adds method addZip(string $name, string $label = null) -Container::extensionMethod('addZip', function (Container $form, string $name, string $label = null) { +// adds method addZip(string $name, ?string $label = null) +Container::extensionMethod('addZip', function (Container $form, string $name, ?string $label = null) { return $form->addText($name, $label) ->addRule($form::Pattern, 'At least 5 numbers', '[0-9]{5}'); }); @@ -442,7 +539,7 @@ $form->addZip('zip', 'ZIP code:'); Low-Level Fields ================ -To add an item to the form, you don't have to call `$form->addXyz()`. Form items can be introduced exclusively in templates instead. This is useful if you, for example, need to generate dynamic items: +It's also possible to use controls that are only written in the template and not added to the form using any of the `$form->addXyz()` methods. For example, when listing records from a database where we don't know in advance how many there will be or what their IDs will be, and we want to display a checkbox or radio button for each row, we can simply code it in the template: ```latte {foreach $items as $item} @@ -450,13 +547,13 @@ To add an item to the form, you don't have to call `$form->addXyz()`. Form items {/foreach} ``` -After submission, you can retrieve the values: +And after submission, we retrieve the value: ```php $data = $form->getHttpData($form::DataText, 'sel[]'); $data = $form->getHttpData($form::DataText | $form::DataKeys, 'sel[]'); ``` -In the first parameter, you specify element type (`DataFile` for `type=file`, `DataLine` for one-line inputs like `text`, `password` or `email` and `DataText` for the rest). The second parameter matches HTML attribute `name`. If you need to preserve keys, you can combine the first parameter with `DataKeys`. This is useful for `select`, `radioList` or `checkboxList`. +where the first parameter is the element type (`DataFile` for `type=file`, `DataLine` for single-line inputs like `text`, `password`, `email`, etc., and `DataText` for all others) and the second parameter `sel[]` corresponds to the HTML name attribute. We can combine the element type with the `DataKeys` value, which preserves the keys of the elements. This is particularly useful for `select`, `radioList`, and `checkboxList`. -The `getHttpData()` returns sanitized input. In this case, it will always be array of valid UTF-8 strings, no matter what the attacker sent by the form. It's an alternative to working with `$_POST` or `$_GET` directly if you want to receive safe data. +Crucially, `getHttpData()` returns a sanitized value. In this case, it will always be an array of valid UTF-8 strings, regardless of what an attacker might try to submit to the server. This is analogous to working directly with `$_POST` or `$_GET`, but with the significant difference that it always returns clean data, just as you are accustomed to with standard Nette form controls. diff --git a/forms/en/in-presenter.texy b/forms/en/in-presenter.texy index e09ebee337..b4cde43641 100644 --- a/forms/en/in-presenter.texy +++ b/forms/en/in-presenter.texy @@ -2,15 +2,15 @@ Forms in Presenters ******************* .[perex] -Nette Forms make it dramatically easier to create and process web forms. In this chapter, you will learn how to use forms inside presenters. +Nette Forms significantly simplify the creation and processing of web forms. In this chapter, you will learn how to use forms inside presenters. -If you are interested in using them completely standalone without the rest of the framework, there is a guide for [standalone forms|standalone]. +If you are interested in using them completely standalone without the rest of the framework, there is a guide for [standalone usage|standalone]. First Form ========== -We will try to write a simple registration form. Its code will look like this: +Let's try writing a simple registration form. Its code will be as follows: ```php use Nette\Application\UI\Form; @@ -19,18 +19,18 @@ $form = new Form; $form->addText('name', 'Name:'); $form->addPassword('password', 'Password:'); $form->addSubmit('send', 'Sign up'); -$form->onSuccess[] = [$this, 'formSucceeded']; +$form->onSuccess[] = $this->formSucceeded(...); ``` -and in the browser the result should look like this: +and in the browser, it will be displayed like this: [* form-en.webp *] -The form in the presenter is an object of the class `Nette\Application\UI\Form`, its predecessor `Nette\Forms\Form` is intended for standalone use. We added the fields name, password and sending button to it. Finally, the line with `$form->onSuccess` says that after submission and successful validation, the `$this->formSucceeded()` method should be called. +A form in a presenter is an object of the `Nette\Application\UI\Form` class; its predecessor `Nette\Forms\Form` is intended for standalone use. We added controls named name, password, and a submit button. Finally, the line `$form->onSuccess` states that after submission and successful validation, the method `$this->formSucceeded()` should be called. -From the presenter's point of view, the form is a common component. Therefore, it is treated as a component and incorporated into the presenter using [factory method |application:components#Factory Methods]. It will look like this: +From the presenter's perspective, the form is a regular component. Therefore, it is treated as a component and integrated into the presenter using a [factory method |application:components#Factory Methods]. It will look like this: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/HomePresenter.php} use Nette; use Nette\Application\UI\Form; @@ -42,11 +42,11 @@ class HomePresenter extends Nette\Application\UI\Presenter $form->addText('name', 'Name:'); $form->addPassword('password', 'Password:'); $form->addSubmit('send', 'Sign up'); - $form->onSuccess[] = [$this, 'formSucceeded']; + $form->onSuccess[] = $this->formSucceeded(...); return $form; } - public function formSucceeded(Form $form, $data): void + private function formSucceeded(Form $form, $data): void { // here we will process the data sent by the form // $data->name contains name @@ -57,55 +57,53 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -And render in template is done using `{control}` tag: +And in the template, the form is rendered using the `{control}` tag: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} <h1>Registration</h1> {control registrationForm} ``` -And that is all :-) We have a functional and perfectly [secured |#Vulnerability Protection] form. +And that's basically everything :-) We have a functional and perfectly [secured |#Vulnerability Protection] form. -Now you're probably thinking that it was too fast, wondering how it's possible that the method `formSucceeded()` is called and what the parameters it gets. Sure, you're right, this deserves an explanation. +Now you're probably thinking it was too fast, wondering how it's possible that the `formSucceeded()` method is called and what parameters it receives. Yes, you're right, this deserves an explanation. -Nette comes up with a cool mechanism, which we call [Hollywood style |application:components#Hollywood style]. Instead of having to constantly ask if something has happened ("was the form submitted?", "was it validly submitted?" or "was it not forged?"), you say to the framework "when the form is validly completed, call this method" and leave further work on it. If you program in JavaScript, you are familiar with this style of programming. You write functions that are called when a certain [event|nette:glossary#Events] occurs. And the language passes the appropriate arguments to them. +Nette introduces a refreshing mechanism called [Hollywood style |application:components#Hollywood Style]. Instead of you, as a developer, having to constantly ask if something happened ('was the form submitted?', 'was it submitted validly?', and 'was it not forged?'), you tell the framework 'when the form is validly filled, call this method' and leave the subsequent work to it. If you program in JavaScript, you are intimately familiar with this style of programming. You write functions that are called when a certain [event |nette:glossary#Events] occurs. And the language passes the appropriate arguments to them. -This is how the above presenter code is built. Array `$form->onSuccess` represents the list of PHP callbacks that Nette will call when the form is submitted and filled in correctly. -Within the [presenter's life cycle |application:presenters#Life Cycle of Presenter] it is a so-called signal, so they are called after the `action*` method and before the `render*` method. -And it passes to each callback the form itself in the first parameter and the sent data as object [ArrayHash |utils:arrays#ArrayHash] in the second. You can omit the first parameter if you do not need the form object. The second parameter can be even more handy, but about that [later|#Mapping to Classes]. +This is precisely how the presenter code above is constructed. The `$form->onSuccess` array represents a list of PHP callbacks that Nette calls the moment the form is submitted and correctly filled (i.e., it is valid). Within the [presenter life cycle |application:presenters#Presenter Life Cycle], this is a so-called signal, so they are called after the `action*` method and before the `render*` method. And to each callback, it passes the form itself as the first parameter and the submitted data as an [ArrayHash |utils:arrays#ArrayHash] object (or stdClass, or a custom class) as the second. You can omit the first parameter if you don't need the form object. The second parameter can be smarter, but more on that [later |#Mapping to Classes]. -The `$data` object contains the `name` and `password` properties with the data entered by the user. Usually we send the data directly for further processing, which can be, for example, insertion into the database. However, an error may occur during processing, for example, the username is already taken. In this case, we pass the error back to the form using `addError()` and let it redrawn, with an error message: +The `$data` object contains the `name` and `password` properties with the data entered by the user. Usually, we send the data directly for further processing, which might be, for example, insertion into a database. However, an error might occur during processing, for instance, the username is already taken. In such a case, we pass the error back to the form using `addError()` and let it be rendered again, along with the error message. ```php $form->addError('Sorry, username is already in use.'); ``` -In addition to `onSuccess`, there is also `onSubmit`: callbacks are always called after the form is submitted, even if it is not filled in correctly. And finally `onError`: callbacks are called only if the submission is not valid. They are even called if we invalidate the form in `onSuccess` or `onSubmit` using `addError()`. +Besides `onSuccess`, there is also `onSubmit`: callbacks are called whenever the form is submitted, even if it is not filled correctly. And also `onError`: callbacks are called only if the submission is not valid. They are even called if we invalidate the form in `onSuccess` or `onSubmit` using `addError()`. -After processing the form, we will redirect to the next page. This prevents the form from being unintentionally resubmitted by clicking the *refresh*, *back* button, or moving the browser history. +After processing the form, we redirect to another page. This prevents the unwanted resubmission of the form by using the *refresh*, *back* button, or navigating through browser history. -Try adding more [form controls|controls]. +Try adding other [form controls|controls]. Access to Controls ================== -The form is a component of the presenter, in our case named `registrationForm` (after the name of the factory method `createComponentRegistrationForm`), so anywhere in the presenter you can get to the form using: +The form is a component of the presenter, in our case named `registrationForm` (after the factory method name `createComponentRegistrationForm`), so anywhere in the presenter, you can access the form using: ```php $form = $this->getComponent('registrationForm'); // alternative syntax: $form = $this['registrationForm']; ``` -Also individual form controls are components, so you can access them in the same way: +Individual form controls are also components, so you can access them in the same way: ```php $input = $form->getComponent('name'); // or $input = $form['name']; $button = $form->getComponent('send'); // or $button = $form['send']; ``` -Controls are removed using unset: +Controls are removed using `unset`: ```php unset($form['name']); @@ -115,27 +113,26 @@ unset($form['name']); Validation Rules ================ -The word *valid* was used several times, but the form has no validation rules yet. Let's fix it. +The word *valid* was mentioned, but the form doesn't have any validation rules yet. Let's fix that. -The name will be mandatory, so we will mark it with the method `setRequired()`, whose argument is the text of the error message that will be displayed if the user does not fill it. If no argument is given, the default error message is used. +The name will be mandatory, so we mark it using the `setRequired()` method. Its argument is the text of the error message displayed if the user doesn't fill in the name. If the argument is omitted, a default error message is used. ```php $form->addText('name', 'Name:') - ->setRequired('Please fill your name.'); + ->setRequired('Please enter your name.'); ``` -Try to submit the form without the name filled in and you will see that an error message is displayed and the browser or server will reject it until you fill it. +Try submitting the form without filling in the name, and you'll see an error message displayed, and the browser or server will reject it until you fill in the field. -At the same time, you will not be able cheat the system by typing only spaces in the input, for example. No way. Nette automatically trims left and right whitespace. Try it. It's something you should always do with every single-line input, but it's often forgotten. Nette does it automatically. (You can try to fool the forms and send a multiline string as the name. Even here, Nette won't be fooled and the line breaks will change to spaces.) +At the same time, you can't cheat the system by entering only spaces in the field, for example. No way. Nette automatically trims leading and trailing whitespace. Try it out. It's something you should always do with every single-line input, but it's often forgotten. Nette does it automatically. (You can try to fool the form and send a multi-line string as the name. Even here, Nette won't be tricked, and line breaks will be converted to spaces.) -The form is always validated on the server side, but JavaScript validation is also generated, which is quick and the user knows of the error immediately, without having to send the form to the server. This is handled by the script `netteForms.js`. -Insert it into the layout template: +The form is always validated on the server side, but JavaScript validation is also generated, which runs instantly, and the user learns about the error immediately, without needing to submit the form to the server. This is handled by the `netteForms.js` script. Include it in your layout template: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -If you look in the source code of the page with form, you may notice that Nette inserts the required fields into elements with a CSS class `required`. Try adding the following style to the template, and the "Name" label will be red. Elegantly, we mark the required fields for the users: +If you look at the source code of the page with the form, you might notice that Nette wraps required controls in elements with the CSS class `required`. Try adding the following stylesheet to your template, and the 'Name' label will be red. This elegantly highlights required fields for users: ```latte <style> @@ -143,57 +140,57 @@ If you look in the source code of the page with form, you may notice that Nette </style> ``` -Additional validation rules will be added by method `addRule()`. The first parameter is rule, the second is again the text of the error message, and the optional validation rule argument can follow. What does that mean? +We add further validation rules using the `addRule()` method. The first parameter is the rule, the second is again the text of the error message, and an argument for the validation rule may follow. What does that mean? -The form will get another optional input *age* with the condition, that it has to be a number (`addInteger()`) and in certain boundaries (`$form::Range`). And here we will use the third argument of `addRule()`, the range itself: +Let's extend the form with a new optional field 'age', which must be an integer (`addInteger()`) and also within an allowed range (`Form::Range`). Here we will use the third parameter of the `addRule()` method to pass the required range to the validator as a pair `[min, max]`: ```php $form->addInteger('age', 'Age:') - ->addRule($form::Range, 'You must be older 18 years and be under 120.', [18, 120]); + ->addRule($form::Range, 'Age must be between 18 and 120.', [18, 120]); ``` .[tip] -If the user does not fill in the field, the validation rules will not be verified, because the field is optional. +If the user doesn't fill in the field, the validation rules will not be checked, as the element is optional. -Obviously room for a small refactoring is available. In the error message and in the third parameter, the numbers are listed in duplicate, which is not ideal. If we were creating a [multilingual form|rendering#translating] and the message containing numbers would have to be translated into multiple languages, it would make it more difficult to change values. For this reason, substitute characters `%d` can be used: +This creates room for a small refactoring. In the error message and the third parameter, the numbers are duplicated, which is not ideal. If we were creating [multilingual forms |rendering#Translating] and the message containing numbers were translated into multiple languages, changing the values would become difficult. For this reason, placeholders `%d` can be used, and Nette will substitute the values: ```php - ->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]); + ->addRule($form::Range, 'Age must be between %d and %d years.', [18, 120]); ``` -Let's return to the *password* field, make it *required*, and verify the minimum password length (`$form::MinLength`), again using the substitute characters in the message: +Let's return to the `password` control, make it required as well, and also verify the minimum password length (`Form::MinLength`), again using a placeholder in the message: ```php $form->addPassword('password', 'Password:') ->setRequired('Pick a password') - ->addRule($form::MinLength, 'Your password has to be at least %d long', 8); + ->addRule($form::MinLength, 'Your password must be at least %d characters long.', 8); ``` -We will add a field `passwordVerify` to the form, where the user enters the password again, for checking. Using validation rules, we check whether both passwords are the same (`$form::Equal`). And as an argument we give a reference to the first password using [square brackets |#Access to Controls]: +Let's add another field `passwordVerify` to the form, where the user enters the password again for confirmation. Using validation rules, we check if both passwords are the same (`Form::Equal`). As the argument, we provide a reference to the first password using [square brackets |#Access to Controls]: ```php $form->addPassword('passwordVerify', 'Password again:') ->setRequired('Fill your password again to check for typo') - ->addRule($form::Equal, 'Password mismatch', $form['password']) + ->addRule($form::Equal, 'Passwords do not match.', $form['password']) ->setOmitted(); ``` -Using `setOmitted()`, we marked an element whose value we don't really care about and which exists only for validation. Its value is not passed to `$data`. +Using `setOmitted()`, we marked a control whose value we don't actually care about and which exists only for validation purposes. Its value is not passed to `$data`. -We have a fully functional form with validation in PHP and JavaScript. Nette's validation capabilities are much broader, you can create conditions, display and hide parts of a page according to them, etc. You can find out everything in the chapter on [form validation|validation]. +With this, we have a fully functional form with validation in both PHP and JavaScript. Nette's validation capabilities are much broader; conditions can be created, parts of the page can be shown or hidden based on them, etc. You will learn everything in the chapter on [form validation|validation]. Default Values ============== -We often set default values for form controls: +We commonly set default values for form controls: ```php $form->addEmail('email', 'Email') ->setDefaultValue($lastUsedEmail); ``` -It is often useful to set default values for all controls at once. For example, when the form is used to edit records. We read the record from the database and set it as default values: +It's often useful to set default values for all controls simultaneously. For example, when the form is used for editing records. We read the record from the database and set the default values: ```php //$row = ['name' => 'John', 'age' => '33', /* ... */]; @@ -206,47 +203,49 @@ Call `setDefaults()` after defining the controls. Rendering the Form ================== -By default, the form is rendered as a table. The individual controls follows basic web accessibility guidelines. All labels are generated as `<label>` elements and are associated with their inputs, clicking on the label moves the cursor on the input. +By default, the form is rendered as a table. Individual controls adhere to basic web accessibility rules - all labels are written as `<label>` elements and associated with their respective form controls. Clicking on the label automatically focuses the cursor in the form field. -We can set any HTML attributes for each element. For example, add a placeholder: +We can set arbitrary HTML attributes for each control. For example, add a placeholder: ```php $form->addInteger('age', 'Age:') ->setHtmlAttribute('placeholder', 'Please fill in the age'); ``` -There are really a lot of ways to render a form, so it's dedicated [chapter about rendering|rendering]. +There are truly many ways to render a form, so a [separate chapter on rendering|rendering] is dedicated to it. Mapping to Classes ================== -Let's go back to the `formSucceeded()` method, which in the second parameter `$data` gets the sent data as an `ArrayHash` object. Because this is a generic class, something like `stdClass`, we will lack some convenience when working with it, such as code completition for properties in editors or static code analysis. This could be solved by having a specific class for each form, whose properties represent the individual controls. E.g.: +Let's return to the `formSucceeded()` method, which receives the submitted data in the second parameter `$data` as an `ArrayHash` object (or `stdClass`). Because it's a generic class, similar to `stdClass`, we lack certain conveniences when working with it, such as property autocompletion in editors or static code analysis. This could be solved by having a specific class for each form, whose properties represent the individual controls. E.g.: ```php class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -As of PHP 8.0, you can use this elegant notation that uses a constructor: +Alternatively, you can use a constructor: ```php class RegistrationFormData { public function __construct( public string $name, - public int $age, + public ?int $age, public string $password, ) { } } ``` -How to tell Nette to return data as objects of this class? Easier than you think. All you have to do is specify the class as the type of the `$data` parameter in the handler: +Properties of the data class can also be enums, and they will be automatically mapped. .{data-version:3.2.4} + +How do we tell Nette to return data as objects of this class? Easier than you might think. Simply specify the class as the type of the `$data` parameter in the handler method: ```php public function formSucceeded(Form $form, RegistrationFormData $data): void @@ -257,16 +256,16 @@ public function formSucceeded(Form $form, RegistrationFormData $data): void } ``` -You can also specify `array` as the type and then it will pass the data as an array. +You can also specify `array` as the type, and then the data will be passed as an array. -In a similar way, you can use the `getValues()` method, which we pass as a class name or object to hydrate as a parameter: +Similarly, you can use the `getValues()` method, passing the class name or an object to hydrate as a parameter: ```php $data = $form->getValues(RegistrationFormData::class); $name = $data->name; ``` -If the forms consist of a multi-level structure composed of containers, create a separate class for each one: +If the forms have a multi-level structure composed of containers, create a separate class for each: ```php $form = new Form; @@ -283,50 +282,52 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` -The mapping then knows from the `$person` property type that it should map the container to the `PersonFormData` class. If the property would contain an array of containers, provide the `array` type and pass the class to be mapped directly to the container: +The mapping then infers from the type of the `$person` property that it should map the container to the `PersonFormData` class. If the property were to contain an array of containers, specify the type `array` and pass the class to be mapped directly to the container: ```php $person->setMappedType(PersonFormData::class); ``` +You can generate a proposal for the form's data class using the `Nette\Forms\Blueprint::dataClass($form)` method, which prints it to the browser page. Then, simply click to select and copy the code into your project. .{data-version:3.1.15} + Multiple Submit Buttons ======================= -If the form has more than one button, we usually need to distinguish which one was pressed. We can create own function for each button. Set it as a handler for the `onClick` [event|nette:glossary#Events]: +If the form has more than one button, we usually need to distinguish which one was pressed. We can create a separate handler function for each button. Set it as a handler for the `onClick` [event |nette:glossary#Events]: ```php $form->addSubmit('save', 'Save') - ->onClick[] = [$this, 'saveButtonPressed']; + ->onClick[] = $this->saveButtonPressed(...); $form->addSubmit('delete', 'Delete') - ->onClick[] = [$this, 'deleteButtonPressed']; + ->onClick[] = $this->deleteButtonPressed(...); ``` -These handlers are also called only in the case form is valid, as in the case of the `onSuccess` event. The difference is that the first parameter can be the submit button object instead of the form, depending on the type you specify: +These handlers are called only if the form is validly filled (unless validation is disabled for the button), just like the `onSuccess` event. The difference is that the first parameter passed can be the submit button object instead of the form, depending on the type hint you specify: ```php -public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) +private function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) { $form = $button->getForm(); // ... } ``` -When a form is submitted with the <kbd>Enter</kbd> key, it is treated as if it had been submitted with the first button. +When the form is submitted by pressing the <kbd>Enter</kbd> key, it is treated as if it were submitted by the first submit button. Event onAnchor ============== -When you build a form in a factory method (such as `createComponentRegistrationForm`), it doesn't yet know if it has been submitted or the data it was submitted with. But there are cases where we need to know the submitted values, perhaps it's depend on them what the form will look like, or they are used for dependent selectboxes, etc. +When you build a form in a factory method (like `createComponentRegistrationForm`), it doesn't yet know if it has been submitted or with what data. However, there are cases where we need to know the submitted values, perhaps the form's appearance depends on them, or they are needed for dependent select boxes, etc. -Therefore, you can have the code that builds the form called when it is anchored, i.e. it is already linked to the presenter and knows its submitted data. We will put such code into the `$onAnchor` array: +Therefore, you can have the code that builds the form called only when it is 'anchored,' meaning it is already connected to the presenter and knows its submitted data. Place such code in the `$onAnchor` array: ```php $country = $form->addSelect('country', 'Country:', $this->model->getCountries()); @@ -344,34 +345,33 @@ $form->onAnchor[] = function () use ($country, $city) { Vulnerability Protection ======================== -Nette Framework puts a great effort to be safe and since forms are the most common user input, Nette forms are as good as impenetrable. All is maintained dynamically and transparently, nothing has to be set manually. +Nette Framework places great emphasis on security and therefore meticulously ensures the security of forms. It does this completely transparently and requires no manual setup. -In addition to protecting the forms against attacks targeted at well-known vulnerabilities such as [Cross-Site Scripting (XSS) |nette:glossary#cross-site-scripting-xss] and [Cross-Site Request Forgery (CSRF)|nette:glossary#cross-site-request-forgery-csrf], it does a lot of small security tasks that you no longer have to think about. +Besides protecting forms against attacks like [Cross-Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] and [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF], it performs many small security measures that you no longer need to think about. -For example, it filters out all control characters from the inputs and checks the validity of the UTF-8 encoding, so that the data from the form will always be clean. For select boxes and radio lists, it verifies that the selected items were actually from the offered ones and there was no forgery. We've already mentioned that for single-line text input, it removes end-of-line characters that an attacker could send there. For multiline inputs, it normalizes the end-of-line characters. And so on. +For example, it filters all control characters from inputs and checks the validity of UTF-8 encoding, ensuring the data from the form is always clean. For select boxes and radio lists, it verifies that the selected items were actually among the offered ones and that no forgery occurred. We've already mentioned that for single-line text inputs, it removes end-of-line characters that an attacker might send. For multi-line inputs, it normalizes end-of-line characters. And so on. -Nette fixes security vulnerabilities for you that most programmers have no idea exist. +Nette handles security risks for you that many programmers aren't even aware exist. -The mentioned CSRF attack is that an attacker lures the victim to visit a page that silently executes a request in the victim's browser to the server where the victim is currently logged in, and the server believes that the request was made by the victim at will. Therefore, Nette prevents the form from being submitted via POST from another domain. If for some reason you want to turn off protection and allow the form to be submitted from another domain, use: +The mentioned CSRF attack involves an attacker luring a victim to a page that silently executes a request in the victim's browser to the server where the victim is logged in. The server then believes the request was made by the victim willingly. Therefore, Nette prevents POST forms from being submitted from a different domain. If, for some reason, you want to disable this protection and allow form submission from another domain, use: ```php -$form->allowCrossOrigin(); // ATTENTION! Turns off protection! +$form->allowCrossOrigin(); // WARNING! Disables protection! ``` -This protection uses a SameSite cookie named `_nss`. SameSite cookie protection may not be 100% reliable, so it's a good idea to turn on token protection: +This protection uses a SameSite cookie named `_nss`. SameSite cookie protection might not be 100% reliable, so it's advisable to also enable token protection: ```php $form->addProtection(); ``` -It's strongly recommended to apply this protection to the forms in an administrative part of your application which changes sensitive data. The framework protects against a CSRF attack by generating and validating authentication token that is stored in a session (the argument is the error message shown if the token has expired). That's why it is necessary to have an session started before displaying the form. In the administration part of the website, the session is usually already started, due to the user's login. -Otherwise, start the session with the method `Nette\Http\Session::start()`. +It is strongly recommended to apply this protection to forms in the administrative parts of your website that modify sensitive data. The framework defends against CSRF attacks by generating and verifying an authorization token stored in the session. Therefore, it is necessary to have a session started before displaying the form. In the administrative part of a website, the session is usually already started due to user login. Otherwise, start the session using `Nette\Http\Session::start()`. Using One Form in Multiple Presenters ===================================== -If you need to use one form in more than one presenter, we recommend that you create a factory for it, which you then pass on to the presenter. A suitable location for such a class is, for example, the directory `app/Forms`. +If you need to use the same form in multiple presenters, we recommend creating a factory for it, which you then inject into the presenters. A suitable location for such a class is, for example, the `app/Forms` directory. The factory class might look like this: @@ -390,7 +390,7 @@ class SignInFormFactory } ``` -We ask the class to produce the form in the factory method for components in the presenter: +We request the class to produce the form in the component factory method within the presenter: ```php public function __construct( @@ -403,12 +403,12 @@ protected function createComponentSignInForm(): Form $form = $this->formFactory->create(); // we can change the form, here for example we change the label on the button $form['login']->setCaption('Continue'); - $form->onSuccess[] = [$this, 'signInFormSubmitted']; // and add handler + $form->onSuccess[] = $this->signInFormSubmitted(...); // and add handler return $form; } ``` -The form processing handler can also be delivered from the factory: +The form processing handler can also be provided by the factory itself: ```php use Nette\Application\UI\Form; @@ -428,4 +428,4 @@ class SignInFormFactory } ``` -So, we have a quick introduction to forms in Nette. Try looking in the [examples |https://github.com/nette/forms/tree/master/examples] directory in the distribution for more inspiration. +So, we've covered a quick introduction to forms in Nette. Try looking in the [examples |https://github.com/nette/forms/tree/master/examples] directory in the distribution for more inspiration. diff --git a/forms/en/rendering.texy b/forms/en/rendering.texy index 27c71677e4..ccb19528ba 100644 --- a/forms/en/rendering.texy +++ b/forms/en/rendering.texy @@ -1,33 +1,35 @@ Forms Rendering *************** -The appearance of forms can be very diverse. In practice, we can encounter two extremes. On one hand, there is a need to render a series of forms in an application that are visually similar to each other, and we appreciate the easy rendering without a template using `$form->render()`. This is usually the case with administrative interfaces. +The appearance of forms can be very diverse. In practice, we may encounter two extremes. On one hand, there is the need to render numerous forms in an application that are visually identical, and we appreciate the easy rendering without a template using `$form->render()`. This is typically the case with administrative interfaces. -On the other hand, there are various forms where each one is unique. Their appearance is best described using HTML language in the template. And of course, in addition to both mentioned extremes, we will encounter many forms that fall somewhere in between. +On the other hand, there are diverse forms where each one is unique. Their appearance is best described using HTML in the form template. And of course, besides these two extremes, we encounter many forms that fall somewhere in between. Rendering With Latte ==================== -The [Latte templating system|latte:] fundamentally facilitates the rendering of forms and their elements. First, we'll show how to render forms manually, element by element, to gain full control over the code. Later we will show how to [automate |#Automatic rendering] such rendering. +The [Latte templating system |latte:] significantly simplifies the rendering of forms and their elements. First, we'll demonstrate how to render forms manually, element by element, to gain full control over the code. Later, we will show how such rendering can be [automated |#Automatic Rendering]. + +You can generate the Latte template for the form using the `Nette\Forms\Blueprint::latte($form)` method, which outputs it to the browser page. Then, simply click to select the code and copy it into your project. .{data-version:3.1.15} `{control}` ----------- -The easiest way to render a form is to write in a template: +The simplest way to render a form is to write in the template: ```latte {control signInForm} ``` -The look of the rendered form can be changed by configuring [#Renderer] and [individual controls|#HTML Attributes]. +The appearance of the rendered form can be influenced by configuring the [#Renderer] and [individual controls |#HTML Attributes]. `n:name` -------- -It is extremely easy to link the form definition in PHP code with HTML code. Just add the `n:name` attributes. That's how easy it is! +Linking the form definition in PHP code with HTML code is extremely easy. Just add the `n:name` attributes. It's that simple! ```php protected function createComponentSignInForm(): Form @@ -54,10 +56,9 @@ protected function createComponentSignInForm(): Form </form> ``` -The look of the resulting HTML code is entirely in your hands. If you use the `n:name` attribute with `<select>`, `<button>` or `<textarea>` elements, their internal content is automatically filled in. -In addition, the `<form n:name>` tag creates a local variable `$form` with the drawn form object and the closing `</form>` draws all undrawn hidden elements (the same applies to `{form} ... {/form}`). +You have full control over the appearance of the resulting HTML code. If you use the `n:name` attribute with `<select>`, `<button>`, or `<textarea>` elements, their inner content is automatically populated. Additionally, the `<form n:name>` tag creates a local variable `$form` containing the rendered form object, and the closing `</form>` tag renders any unrendered hidden controls (the same applies to `{form} ... {/form}`). -However, we must not forget to render possible error messages. Both those that were added to individual elements by the `addError()` method (using `{inputError}`) and those added directly to the form (returned by `$form->getOwnErrors()`): +However, we must not forget to render potential error messages. This includes errors added to individual controls using the `addError()` method (rendered via `{inputError}`) and errors added directly to the form (returned by `$form->getOwnErrors()`): ```latte <form n:name=signInForm class=form> @@ -79,7 +80,7 @@ However, we must not forget to render possible error messages. Both those that w </form> ``` -More complex form elements, such as RadioList or CheckboxList, can be rendered item by item: +More complex form controls, like RadioList or CheckboxList, can be rendered item by item like this: ```latte {foreach $form[gender]->getItems() as $key => $label} @@ -88,16 +89,10 @@ More complex form elements, such as RadioList or CheckboxList, can be rendered i ``` -Code Proposal `{formPrint}` .[#toc-formprint] ---------------------------------------------- - -You can generate a similar Latte code for a form using the `{formPrint}` tag. If you place it in a template, you will see the draft code instead of the normal rendering. Then just select it and copy it into your project. - - `{label}` `{input}` ------------------- -Don't you want to think for each element what HTML element to use for it in the template, whether `<input>`, `<textarea>` etc.? The solution is the universal `{input}` tag: +Prefer not to think about which HTML element to use for each control in the template, whether it's `<input>`, `<textarea>`, etc.? The solution is the universal `{input}` tag: ```latte <form n:name=signInForm class=form> @@ -121,7 +116,7 @@ Don't you want to think for each element what HTML element to use for it in the If the form uses a translator, the text inside the `{label}` tags will be translated. -Again, more complex form elements, such as RadioList or CheckboxList, can be rendered item by item: +Again, more complex form controls, such as RadioList or CheckboxList, can be rendered item by item: ```latte {foreach $form[gender]->items as $key => $label} @@ -129,20 +124,19 @@ Again, more complex form elements, such as RadioList or CheckboxList, can be ren {/foreach} ``` -To render the `<input>` itself in the Checkbox item, use `{input myCheckbox:}`. HTML attributes must be separated by a comma `{input myCheckbox:, class: required}`. +To render just the `<input>` for a Checkbox control, use `{input myCheckbox:}`. In this case, always separate HTML attributes with a comma: `{input myCheckbox:, class: required}`. `{inputError}` -------------- -Prints an error message for the form element, if it has one. The message is usually wrapped in an HTML element for styling. -Avoiding rendering an empty element if there is no message can be elegantly done with `n:ifcontent`: +Displays the error message for a form control, if one exists. The message is usually wrapped in an HTML element for styling. Preventing the rendering of an empty element when there is no message can be elegantly achieved using `n:ifcontent`: ```latte <span class=error n:ifcontent>{inputError $input}</span> ``` -We can detect the presence of an error using the `hasErrors()` method and set the class of the parent element accordingly: +We can check for the presence of an error using the `hasErrors()` method and set the class of the parent element accordingly: ```latte <div n:class="$form[username]->hasErrors() ? 'error'"> @@ -155,14 +149,13 @@ We can detect the presence of an error using the `hasErrors()` method and set th `{form}` -------- -Tags `{form signInForm}...{/form}` are an alternative to `<form n:name="signInForm">...</form>`. +The tags `{form signInForm}...{/form}` are an alternative to `<form n:name="signInForm">...</form>`. Automatic Rendering ------------------- -With the `{input}` and `{label}` tags, we can easily create a generic template for any form. It will iterate and render all of its elements sequentially, except for hidden elements, which are rendered automatically when the form is terminated with the `</form>` tag. -It will expect the name of the rendered form in the `$form` variable. +Thanks to the `{input}` and `{label}` tags, we can easily create a generic template for any form. It will iterate through and render all its controls, except for hidden controls, which are rendered automatically when the form is closed with the `</form>` tag. It expects the name of the form to be rendered in the `$form` variable. ```latte <form n:name=$form class=form> @@ -179,16 +172,15 @@ It will expect the name of the rendered form in the `$form` variable. </form> ``` -The used self-closing pair tags `{label .../}` show the labels coming from the form definition in the PHP code. +The self-closing pair tags `{label .../}` used here display the labels originating from the form definition in the PHP code. -You can save this generic template in the `basic-form.latte` file and to render the form, just include it and pass the form name (or instance) to the `$form` parameter: +Save this generic template, for example, in the file `basic-form.latte`. To render the form, simply include it and pass the form name (or instance) to the `$form` parameter: ```latte {include basic-form.latte, form: signInForm} ``` -If you would like to influence the appearance of one particular form and draw one element differently, then the easiest way is to prepare blocks in the template that can be overwritten later. -Blocks can also have [dynamic names |latte:template-inheritance#dynamic-block-names], so you can insert the name of the element to be drawn into them. For example: +If you want to modify the appearance of a specific form during rendering, perhaps rendering one control differently, the easiest way is to prepare blocks in the template that can be subsequently overridden. Blocks can also have [dynamic names |latte:template-inheritance#Dynamic Block Names], allowing you to insert the name of the rendered control. For example: ```latte ... @@ -197,7 +189,7 @@ Blocks can also have [dynamic names |latte:template-inheritance#dynamic-block-na ... ``` -For the element e.g. `username` this creates the block `input-username`, which can be easily overridden by using the tag [{embed} |latte:template-inheritance#unit-inheritance]: +For a control named, e.g., `username`, this creates the block `input-username`, which can be easily overridden using the [{embed} |latte:template-inheritance#Unit Inheritance] tag: ```latte {embed basic-form.latte, form: signInForm} @@ -209,7 +201,7 @@ For the element e.g. `username` this creates the block `input-username`, which c {/embed} ``` -Alternatively, the entire contents of the `basic-form.latte` template can be [defined |latte:template-inheritance#definitions] as a block, including the `$form` parameter: +Alternatively, the entire content of the `basic-form.latte` template can be [defined |latte:template-inheritance#Definitions] as a block, including the `$form` parameter: ```latte {define basic-form, $form} @@ -219,7 +211,7 @@ Alternatively, the entire contents of the `basic-form.latte` template can be [de {/define} ``` -This will make it slightly easier to use: +This makes calling it slightly simpler: ```latte {embed basic-form, signInForm} @@ -227,7 +219,7 @@ This will make it slightly easier to use: {/embed} ``` -You only need to import the block in one place, at the beginning of the layout template: +The block only needs to be imported in one place, at the beginning of the layout template: ```latte {import basic-form.latte} @@ -237,18 +229,18 @@ You only need to import the block in one place, at the beginning of the layout t Special Cases ------------- -If you only need to render the inner content of a form without `<form>` & `</form>` HTML tags, for example, in an AJAX request, you can open and close the form with `{formContext} … {/formContext}`. It works similarly to `{form}` in a logical sense, here it allows you to use other tags to draw form elements, but at the same time it doesn't draw anything. +If you need to render only the inner part of the form without the `<form>` HTML tags, for example, when sending snippets, hide them using the `n:tag-if` attribute: ```latte -{formContext signForm} +<form n:name=signInForm n:tag-if=false> <div> <label n:name=username>Username: <input n:name=username></label> {inputError username} </div> -{/formContext} +</form> ``` -Tag `formContainer` helps with rendering of inputs inside a form container. +The `{formContainer}` tag helps with rendering controls inside a form container. ```latte <p>Which news you wish to receive:</p> @@ -271,16 +263,16 @@ The easiest way to render a form is to call: $form->render(); ``` -The look of the rendered form can be changed by configuring [#Renderer] and [individual controls|#HTML Attributes]. +The appearance of the rendered form can be influenced by configuring the [#Renderer] and [individual controls |#HTML Attributes]. Manual Rendering ---------------- -Each form element has methods that generate the HTML code for the form field and label. They can return it as either a string or a [Nette\Utils\Html|utils:html-elements] object: +Each form control has methods that generate the HTML code for the form field and its label. They can return it either as a string or a [Nette\Utils\Html |utils:html-elements] object: -- `getControl(): Html|string` returns the HTML code of the element -- `getLabel($caption = null): Html|string|null` returns the HTML code of the label, if any +- `getControl(): Html|string` returns the HTML code of the control +- `getLabel($caption = null): Html|string|null` returns the HTML code of the label, if it exists This allows the form to be rendered element by element: @@ -305,22 +297,21 @@ This allows the form to be rendered element by element: <?php $form->render('end') ?> ``` -While for some elements `getControl()` returns a single HTML element (e.g. `<input>`, `<select>` etc.), for others it returns a whole piece of HTML code (CheckboxList, RadioList). -In this case, you can use methods that generate individual inputs and labels, for each item separately: +While for some controls `getControl()` returns a single HTML element (e.g., `<input>`, `<select>`, etc.), for others it returns a complete piece of HTML code (CheckboxList, RadioList). In such cases, you can use methods that generate individual inputs and labels for each item separately: - `getControlPart($key = null): ?Html` returns the HTML code of a single item - `getLabelPart($key = null): ?Html` returns the HTML code for the label of a single item .[note] -These methods are prefixed with `get` for historical reasons, but `generate` would be better, as it creates and returns a new `Html` element on each call. +These methods have the prefix `get` for historical reasons, but `generate` would be more appropriate, as they create and return a new `Html` element upon each call. Renderer ======== -It is an object that provides rendering of the form. It can be set by the `$form->setRenderer` method. It is passed control when the `$form->render()` method is called. +This is an object responsible for rendering the form. It can be set using the `$form->setRenderer()` method. Control is passed to it when the `$form->render()` method is called. -If we don't set a custom renderer, the default renderer [api:Nette\Forms\Rendering\DefaultFormRenderer] will be used. This will render the form elements as an HTML table. The output looks like this: +If we do not set a custom renderer, the default renderer [api:Nette\Forms\Rendering\DefaultFormRenderer] will be used. This renders the form controls into an HTML table. The output looks like this: ```latte <table> @@ -341,11 +332,11 @@ If we don't set a custom renderer, the default renderer [api:Nette\Forms\Renderi ... ``` -It's up to you, whether to use a table or not, and many web designers prefer different markups, for example a list. We may configure `DefaultFormRenderer` so it would not render into a table at all. We just have to set proper [$wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers]. The first index always represents an area and the second one it's element. All respective areas are shown in the picture: +Whether to use a table for the form structure is debatable, and many web designers prefer different markup, such as a definition list. Therefore, we will reconfigure `DefaultFormRenderer` to render the form as a list. Configuration is done by editing the [$wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers] array. The first index always represents an area, and the second its attribute. The individual areas are shown in the picture: [* form-areas-en.webp *] -By default a group of `controls` is wrapped in `<table>`, and every `pair` is a table row `<tr>` containing a pair of `label` and `control` (cells `<th>` and `<td>`). Let's change all those wrapper elements. We will wrap `controls` into `<dl>`, leave `pair` by itself, put `label` into `<dt>` and wrap `control` into `<dd>`: +By default, the `controls` group is wrapped in `<table>`, each `pair` represents a table row `<tr>`, and the `label` and `control` pair are cells `<th>` and `<td>`. Now we will change the wrapping elements. We will place the `controls` area into a `<dl>` container, leave the `pair` area without a container, put the `label` into `<dt>`, and finally wrap the `control` with `<dd>` tags: ```php $renderer = $form->getRenderer(); @@ -357,7 +348,7 @@ $renderer->wrappers['control']['container'] = 'dd'; $form->render(); ``` -Results into the following snippet: +This results in the following HTML code: ```latte <dl> @@ -376,25 +367,25 @@ Results into the following snippet: </dl> ``` -Wrappers can affect many attributes. For example: +The wrappers array allows influencing many other attributes: -- add special CSS classes to each form input -- distinguish between odd and even lines -- make required and optional draw differently -- set, whether error messages are shown above the form or close to each element +- adding CSS classes to individual types of form controls +- distinguishing odd and even rows with CSS classes +- visually distinguishing required and optional items +- determining whether error messages are displayed directly next to controls or above the form Options ------- -The behavior of Renderer can also be controlled by setting *options* on individual form elements. This way you can set the tooltip that is displayed next to the input field: +The behavior of the Renderer can also be controlled by setting *options* on individual form controls. This way, you can set a description that appears next to the input field: ```php $form->addText('phone', 'Number:') ->setOption('description', 'This number will remain hidden'); ``` -If we want to place HTML content into it, we use [Html |utils:html-elements] class. +If we want to place HTML content within it, we use the [Html |utils:html-elements] class: ```php use Nette\Utils\Html; @@ -406,19 +397,19 @@ $form->addText('phone', 'Phone:') ``` .[tip] -Html element can be also used instead of label: `$form->addCheckbox('conditions', $label)`. +An Html element can also be used instead of a label: `$form->addCheckbox('conditions', $label)`. Grouping Inputs --------------- -Renderer allows to group elements into visual groups (fieldsets): +The Renderer allows grouping controls into visual groups (fieldsets): ```php $form->addGroup('Personal data'); ``` -Creating new group activates it - all elements added further are added to this group. You may build a form like this: +After creating a new group, it becomes active, and every newly added control is also added to it. Thus, the form can be built this way: ```php $form = new Form; @@ -434,31 +425,33 @@ $form->addText('city', 'City:'); $form->addSelect('country', 'Country:', $countries); ``` +The renderer draws groups first, followed by the controls that do not belong to any group. + Bootstrap Support ----------------- -You can find [examples |https://github.com/nette/forms/tree/master/examples] of configuration of Renderer for [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], [Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] and [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php] +You can find examples in the [examples directory |https://github.com/nette/forms/tree/master/examples] showing how to configure the Renderer for [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], [Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58], and [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php]. HTML Attributes =============== -You can set any HTML attributes to form controls using `setHtmlAttribute(string $name, $value = true)`: +To set any HTML attributes for form controls, use the `setHtmlAttribute(string $name, $value = true)` method: ```php $form->addInteger('number', 'Number:') ->setHtmlAttribute('class', 'big-number'); $form->addSelect('rank', 'Order by:', ['price', 'name']) - ->setHtmlAttribute('onchange', 'submit()'); // calls JS function submit() on change + ->setHtmlAttribute('onchange', 'submit()'); // submit the form on change -// applying on <form> +// To set attributes of the <form> element itself $form->setHtmlAttribute('id', 'myForm'); ``` -Setting input type: +Specifying the type of control: ```php $form->addText('tel', 'Your telephone:') @@ -466,8 +459,10 @@ $form->addText('tel', 'Your telephone:') ->setHtmlAttribute('placeholder', 'Please, fill in your telephone'); ``` -We can set HTML attribute to individual items in radio or checkbox lists with different values for each of them. -Note the colon after `style:` to ensure that the value is selected by key: +.[warning] +Setting the type and other attributes is only for visual purposes. Verification of input correctness must occur on the server side, which you ensure by choosing an appropriate [form control |controls] and specifying [validation rules |validation]. + +For individual items in radio or checkbox lists, we can set an HTML attribute with different values for each. Notice the colon after `style:`, which ensures the value is selected based on the key: ```php $colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue']; @@ -484,12 +479,11 @@ Renders: <label><input type="checkbox" name="colors[]" value="b">blue</label> ``` -For a logical HTML attribute (which has no value, such as `readonly`), you can use a question mark: +For setting boolean attributes, such as `readonly`, we can use the notation with a question mark: ```php -$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue']; $form->addCheckboxList('colors', 'Colors:', $colors) - ->setHtmlAttribute('readonly?', 'r'); // use array for multiple keys, e.g. ['r', 'g'] + ->setHtmlAttribute('readonly?', 'r'); // use an array for multiple keys, e.g., ['r', 'g'] ``` Renders: @@ -500,8 +494,7 @@ Renders: <label><input type="checkbox" name="colors[]" value="b">blue</label> ``` -For selectboxes, the `setHtmlAttribute()` method sets the attributes of the `<select>` element. If we want to set the attributes for each -`<option>`, we will use the method `setOptionAttribute()`. Also, the colon and question mark used above work: +For select boxes, the `setHtmlAttribute()` method sets the attributes of the `<select>` element. If we want to set attributes for individual `<option>` elements, we use the `setOptionAttribute()` method. The colon and question mark notations mentioned above also work: ```php $form->addSelect('colors', 'Colors:', $colors) @@ -525,7 +518,7 @@ Prototypes An alternative way to set HTML attributes is to modify the template from which the HTML element is generated. The template is an `Html` object and is returned by the `getControlPrototype()` method: ```php -$input = $form->addInteger('number'); +$input = $form->addInteger('number', 'Number:'); $html = $input->getControlPrototype(); // <input> $html->class('big-number'); // <input class="big-number"> ``` @@ -537,13 +530,10 @@ $html = $input->getLabelPrototype(); // <label> $html->class('distinctive'); // <label class="distinctive"> ``` -For Checkbox, CheckboxList and RadioList items you can influence the element template that wraps the item. It is returned by `getContainerPrototype()`. By default it is an "empty" element, so nothing is rendered, but by giving it a name it will be rendered: +For Checkbox, CheckboxList, and RadioList controls, you can influence the template of the element that wraps the entire control. It is returned by `getContainerPrototype()`. By default, it is an "empty" element, so nothing is rendered, but by giving it a name, it will be rendered: ```php $input = $form->addCheckbox('send'); -echo $input->getControl(); -// <label><input type="checkbox" name="send"></label> - $html = $input->getContainerPrototype(); $html->setName('div'); // <div> $html->class('check'); // <div class="check"> @@ -551,50 +541,49 @@ echo $input->getControl(); // <div class="check"><label><input type="checkbox" name="send"></label></div> ``` -In the case of CheckboxList and RadioList it is also possible to influence the item separator pattern returned by the method `getSeparatorPrototype()`. By default, it is an element `<br>`. If you change it to a pair element, it will wrap the individual items instead of separating them. -It is also possible to influence the HTML element template of the item labels, which returns `getItemLabelPrototype()`. +In the case of CheckboxList and RadioList, you can also influence the template of the separator for individual items, returned by the `getSeparatorPrototype()` method. By default, it is the `<br>` element. If you change it to a pair element, it will wrap the individual items instead of separating them. Furthermore, you can influence the template of the HTML element for the labels of individual items, returned by `getItemLabelPrototype()`. Translating =========== -If you are programming a multilingual application, you will probably need to render the form in different languages. The Nette Framework defines a translation interface for this purpose [api:Nette\Localization\Translator]. There is no default implementation in Nette, you can choose according to your needs from several ready-made solutions you can find on [Componette |https://componette.org/search/localization]. Their documentation tells you how to configure the translator. +If you are developing a multilingual application, you will likely need to render the form in different language versions. The Nette Framework defines a translation interface for this purpose: [api:Nette\Localization\Translator]. Nette does not have a default implementation; you can choose from several ready-made solutions found on [Componette |https://componette.org/search/localization] according to your needs. Their documentation explains how to configure the translator. -The form supports outputting text through the translator. We pass it using the `setTranslator()` method: +Forms support outputting texts via the translator. We pass it using the `setTranslator()` method: ```php $form->setTranslator($translator); ``` -From now on, not only all labels, but also all error messages or select box entries will be translated into another language. +From this point on, not only all labels but also all error messages or items in select boxes will be translated into the target language. -It is possible to set a different translator for individual form elements or to disable translation completely with `null`: +It is possible to set a different translator for individual form controls or disable translation completely by setting the value to `null`: ```php $form->addSelect('carModel', 'Model:', $cars) ->setTranslator(null); ``` -For [validation rules |validation], specific parameters are also passed to the translator, for example for rule: +For [validation rules |validation], specific parameters are also passed to the translator. For example, for the rule: ```php $form->addPassword('password', 'Password:') - ->addRule($form::MinLength, 'Password has to be at least %d characters long', 8) + ->addRule($form::MinLength, 'Password must be at least %d characters long', 8); ``` -the translator is called with the following parameters: +the translator is called with these parameters: ```php -$translator->translate('Password has to be at least %d characters long', 8); +$translator->translate('Password must be at least %d characters long', 8); ``` -and thus can choose the correct plural form for the word `characters` by count. +and thus can choose the correct plural form for the word `characters` based on the count. Event onRender ============== -Just before the form is rendered, we can have our code invoked. This can, for example, add HTML classes to the form elements for proper display. We add the code to the `onRender` array: +Just before the form is rendered, we can have our code invoked. This code can, for example, add HTML classes to the form controls for correct display. We add the code to the `onRender` array: ```php $form->onRender[] = function ($form) { diff --git a/forms/en/standalone.texy b/forms/en/standalone.texy index 13d1285515..0260668a0e 100644 --- a/forms/en/standalone.texy +++ b/forms/en/standalone.texy @@ -2,15 +2,15 @@ Forms Used Standalone ********************* .[perex] -Nette Forms make it dramatically easier to create and process web forms. You can use them in your applications completely on their own without the rest of the framework, which we'll demonstrate in this chapter. +Nette Forms dramatically simplify the creation and processing of web forms. You can use them in your applications completely standalone, without the rest of the framework, as demonstrated in this chapter. -However, if you use Nette Application and presenters, there is a guide for you: [forms in presenters|in-presenter]. +However, if you use Nette Application and presenters, there is a dedicated guide for you: [forms in presenters |in-presenter]. First Form ========== -We will try to write a simple registration form. Its code will look like this ("full code":https://gist.github.com/dg/370a7e3094d9ba9a9e913b8e2a2dc851): +Let's try writing a simple registration form. Its code will be as follows ("full code":https://gist.github.com/dg/370a7e3094d9ba9a9e913b8e2a2dc851): ```php use Nette\Forms\Form; @@ -21,39 +21,39 @@ $form->addPassword('password', 'Password:'); $form->addSubmit('send', 'Sign up'); ``` -And let's render it: +And let's render it very easily: ```php $form->render(); ``` -and the result should look like this: +The result in the browser should look like this: [* form-en.webp *] -The form is an object of the `Nette\Forms\Form` class (the `Nette\Application\UI\Form` class is used in presenters). We added the controls name, password and sending button to it. +The form is an object of the `Nette\Forms\Form` class (the `Nette\Application\UI\Form` class is used in presenters). We added controls named 'name', 'password', and a submit button to it. -Now we will revive the form. By asking `$form->isSuccess()`, we will find out whether the form was submitted and whether it was filled in validly. If so, we will dump the data. After the definition of the form we will add: +Now let's bring the form to life. By querying `$form->isSuccess()`, we find out if the form was submitted and if it was filled in validly. If so, we will output the data. After the form definition, add: ```php if ($form->isSuccess()) { - echo 'The form has been filled in and submitted correctly'; + echo 'Form was filled and submitted successfully'; $data = $form->getValues(); - // $data->name contains name - // $data->password contains password + // $data->name contains the name + // $data->password contains the password var_dump($data); } ``` -Method `getValues()` returns the sent data in the form of an object [ArrayHash |utils:arrays#ArrayHash]. We will show how to change this [later |#Mapping to Classes]. The variable `$data` contains keys `name` and `password` with data entered by the user. +The `getValues()` method returns the submitted data as an [ArrayHash |utils:arrays#ArrayHash] object. We will show how to change this [later |#Mapping to Classes]. The `$data` object contains the keys `name` and `password` with the data entered by the user. -Usually we send the data directly for further processing, which can be, for example, insertion into the database. However, an error may occur during processing, for example, the username is already taken. In this case, we pass the error back to the form using `addError()` and let it redrawn, with an error message: +Usually, we send the data directly for further processing, such as inserting it into a database. However, an error might occur during processing, for example, if the username is already taken. In this case, we pass the error back to the form using `addError()` and let it be rendered again, along with the error message. ```php -$form->addError('Sorry, username is already in use.'); +$form->addError('Sorry, this username is already taken.'); ``` -After processing the form, we will redirect to the next page. This prevents the form from being unintentionally resubmitted by clicking the *refresh*, *back* button, or moving the browser history. +After processing the form, we redirect to the next page. This prevents the form from being unintentionally resubmitted by clicking the *refresh* or *back* buttons, or by navigating through browser history. By default, the form is sent using the POST method to the same page. Both can be changed: @@ -62,15 +62,15 @@ $form->setAction('/submit.php'); $form->setMethod('GET'); ``` -And that is all :-) We have a functional and perfectly [secured |#Vulnerability Protection] form. +And that's basically it :-) We have a functional and perfectly [secured |#Vulnerability Protection] form. -Try adding more [form controls|controls]. +Try adding other [form controls |controls] as well. Access to Controls ================== -The form and its individual controls are called components. They create a component tree, where the root is the form. You can access the individual controls as follows: +The form and its individual controls are called components. They form a component tree, where the form is the root. You can access individual form controls like this: ```php $input = $form->getComponent('name'); @@ -80,7 +80,7 @@ $button = $form->getComponent('send'); // alternative syntax: $button = $form['send']; ``` -Controls are removed using unset: +Controls are removed using `unset`: ```php unset($form['name']); @@ -90,27 +90,26 @@ unset($form['name']); Validation Rules ================ -The word *valid* was used here, but the form has no validation rules yet. Let's fix it. +The word *valid* was mentioned, but the form doesn't have any validation rules yet. Let's fix that. -The name will be mandatory, so we will mark it with the method `setRequired()`, whose argument is the text of the error message that will be displayed if the user does not fill it. If no argument is given, the default error message is used. +The name will be required, so we mark it with the `setRequired()` method. Its argument is the text of the error message displayed if the user doesn't fill in the name. If no argument is provided, the default error message is used. ```php $form->addText('name', 'Name:') ->setRequired('Please enter a name.'); ``` -Try to submit the form without the name filled in and you will see that an error message is displayed and the browser or server will reject it until you fill it. +Try submitting the form without filling in the name, and you'll see an error message appear. The browser or server will reject it until you fill in the field. -At the same time, you will not be able cheat the system by typing only spaces in the input, for example. No way. Nette automatically trims left and right whitespace. Try it. It's something you should always do with every single-line input, but it's often forgotten. Nette does it automatically. (You can try to fool the forms and send a multiline string as the name. Even here, Nette won't be fooled and the line breaks will change to spaces.) +At the same time, you can't cheat the system by typing only spaces into the input. No way. Nette automatically trims leading and trailing whitespace. Try it. It's something you should always do with every single-line input, but it's often forgotten. Nette does it automatically. (You can try to trick the form by sending a multi-line string as the name. Even here, Nette won't be fooled, and line breaks will be converted to spaces.) -The form is always validated on the server side, but JavaScript validation is also generated, which is quick and the user knows of the error immediately, without having to send the form to the server. This is handled by the script `netteForms.js`. -Add it to the page: +The form is always validated on the server side, but JavaScript validation is also generated. This runs instantly, and the user learns about errors immediately, without needing to submit the form to the server. This is handled by the `netteForms.js` script. Insert it into the page: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -If you look in the source code of the page with form, you may notice that Nette inserts the required fields into elements with a CSS class `required`. Try adding the following style to the template, and the "Name" label will be red. Elegantly, we mark the required fields for the users: +If you look at the source code of the page with the form, you might notice that Nette inserts required controls into elements with the CSS class `required`. Try adding the following stylesheet to the template, and the "Name" label will turn red. This elegantly highlights required controls for users: ```latte <style> @@ -118,44 +117,44 @@ If you look in the source code of the page with form, you may notice that Nette </style> ``` -Additional validation rules will be added by method `addRule()`. The first parameter is rule, the second is again the text of the error message, and the optional validation rule argument can follow. What does that mean? +We add other validation rules using the `addRule()` method. The first parameter is the rule, the second is again the text of the error message, and an optional validation rule argument can follow. What does that mean? -The form will get another optional input *age* with the condition, that it has to be a number (`addInteger()`) and in certain boundaries (`$form::Range`). And here we will use the third argument of `addRule()`, the range itself: +Let's extend the form with a new optional field "age", which must be an integer (`addInteger()`) and within an allowed range (`$form::Range`). Here we will use the third parameter of the `addRule()` method to pass the required range to the validator as a pair `[min, max]`: ```php $form->addInteger('age', 'Age:') - ->addRule($form::Range, 'You must be older 18 years and be under 120.', [18, 120]); + ->addRule($form::Range, 'Age must be between 18 and 120.', [18, 120]); ``` .[tip] -If the user does not fill in the field, the validation rules will not be verified, because the field is optional. +If the user does not fill in the field, the validation rules will not be checked, as the control is optional. -Obviously room for a small refactoring is available. In the error message and in the third parameter, the numbers are listed in duplicate, which is not ideal. If we were creating a [multilingual form|rendering#translating] and the message containing numbers would have to be translated into multiple languages, it would make it more difficult to change values. For this reason, substitute characters `%d` can be used: +This creates room for a small refactoring. The numbers are duplicated in the error message and the third parameter, which isn't ideal. If we were creating [multilingual forms |rendering#Translating] and the message containing numbers were translated into multiple languages, changing the values would become difficult. For this reason, placeholders `%d` can be used, and Nette will fill in the values: ```php - ->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]); + ->addRule($form::Range, 'Age must be between %d and %d years.', [18, 120]); ``` -Let's return to the *password* field, make it *required*, and verify the minimum password length (`$form::MinLength`), again using the substitute characters in the message: +Let's return to the `password` control, make it required as well, and also verify the minimum password length (`$form::MinLength`), again using a placeholder in the message: ```php $form->addPassword('password', 'Password:') - ->setRequired('Pick a password') - ->addRule($form::MinLength, 'Your password has to be at least %d long', 8); + ->setRequired('Choose a password') + ->addRule($form::MinLength, 'Password must be at least %d characters long', 8); ``` -We will add a field `passwordVerify` to the form, where the user enters the password again, for checking. Using validation rules, we check whether both passwords are the same (`$form::Equal`). And as an argument we give a reference to the first password using [square brackets |#Access to Controls]: +Let's add another field `passwordVerify` to the form, where the user enters the password again for verification. Using validation rules, we check if both passwords are the same (`$form::Equal`). As the parameter, we provide a reference to the first password using [square brackets |#Access to Controls]: ```php $form->addPassword('passwordVerify', 'Password again:') - ->setRequired('Fill your password again to check for typo') - ->addRule($form::Equal, 'Password mismatch', $form['password']) + ->setRequired('Please enter the password again for verification') + ->addRule($form::Equal, 'Passwords do not match', $form['password']) ->setOmitted(); ``` -Using `setOmitted()`, we marked an element whose value we don't really care about and which exists only for validation. Its value is not passed to `$data`. +Using `setOmitted()`, we marked a control whose value we don't really care about and which exists only for validation purposes. Its value is not passed to `$data`. -We have a fully functional form with validation in PHP and JavaScript. Nette's validation capabilities are much broader, you can create conditions, display and hide parts of a page according to them, etc. You can find out everything in the chapter on [form validation|validation]. +With this, we have a fully functional form with validation in both PHP and JavaScript. Nette's validation capabilities are much broader; you can create conditions, show and hide parts of the page based on them, etc. You will learn everything in the chapter on [form validation |validation]. Default Values @@ -168,7 +167,7 @@ $form->addEmail('email', 'Email') ->setDefaultValue($lastUsedEmail); ``` -It is often useful to set default values for all controls at once. For example, when the form is used to edit records. We read the record from the database and set it as default values: +It's often useful to set default values for all controls simultaneously, for example, when the form is used for editing records. We read the record from the database and set its values as defaults: ```php //$row = ['name' => 'John', 'age' => '33', /* ... */]; @@ -181,33 +180,33 @@ Call `setDefaults()` after defining the controls. Rendering the Form ================== -By default, the form is rendered as a table. The individual controls follows basic web accessibility guidelines. All labels are generated as `<label>` elements and are associated with their inputs, clicking on the label moves the cursor on the input. +By default, the form is rendered as a table. Individual controls adhere to basic accessibility guidelines - all labels are generated as `<label>` elements and associated with their respective form controls. Clicking on a label automatically places the cursor in the form field. -We can set any HTML attributes for each element. For example, add a placeholder: +We can set arbitrary HTML attributes for each control. For example, add a placeholder: ```php $form->addInteger('age', 'Age:') ->setHtmlAttribute('placeholder', 'Please fill in the age'); ``` -There are really a lot of ways to render a form, so it's dedicated [chapter about rendering|rendering]. +There are many ways to render a form, so a [separate chapter is dedicated to rendering |rendering]. Mapping to Classes ================== -Let's go back to form data processing. Method `getValues()` returned the submitted data as an `ArrayHash` object. Because this is a generic class, something like `stdClass`, we will lack some convenience when working with it, such as code completition for properties in editors or static code analysis. This could be solved by having a specific class for each form, whose properties represent the individual controls. E.g.: +Let's return to processing form data. The `getValues()` method returned the submitted data as an `ArrayHash` object. Since this is a generic class, like `stdClass`, we lack certain conveniences when working with it, such as property autocompletion in editors or static code analysis. This could be solved by having a specific class for each form, whose properties represent the individual controls. E.g.: ```php class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -As of PHP 8.0, you can use this elegant notation that uses a constructor: +Alternatively, you can use the constructor: ```php class RegistrationFormData @@ -221,14 +220,16 @@ class RegistrationFormData } ``` -How to tell Nette to return data to us as objects of this class? Easier than you think. All you have to do is specify the class name or object to hydrate as a parameter: +Properties of the data class can also be enums, and they will be automatically mapped. .{data-version:3.2.4} + +How do we tell Nette to return data as objects of this class? Easier than you might think. All you need to do is specify the class name or the object to hydrate as a parameter: ```php $data = $form->getValues(RegistrationFormData::class); $name = $data->name; ``` -An `'array'` can also be specified as a parameter, and then the data returns as an array. +You can also specify `'array'` as the parameter, and the data will be returned as an array. If the forms consist of a multi-level structure composed of containers, create a separate class for each one: @@ -247,22 +248,24 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` -The mapping then knows from the `$person` property type that it should map the container to the `PersonFormData` class. If the property would contain an array of containers, provide the `array` type and pass the class to be mapped directly to the container: +The mapping then knows from the `$person` property type that it should map the container to the `PersonFormData` class. If the property were to contain an array of containers, specify the `array` type and pass the class to be mapped directly to the container: ```php $person->setMappedType(PersonFormData::class); ``` +You can have a proposal for the form's data class generated using the `Nette\Forms\Blueprint::dataClass($form)` method, which will print it to the browser page. You can then simply click to select and copy the code into your project. .{data-version:3.1.15} + Multiple Submit Buttons ======================= -If the form has more than one button, we usually need to distinguish which one was pressed. The method `isSubmittedBy()` of the button returns this information to us: +If the form has more than one button, we usually need to distinguish which one was pressed. The button's `isSubmittedBy()` method returns this information: ```php $form->addSubmit('save', 'Save'); @@ -279,37 +282,36 @@ if ($form->isSuccess()) { } ``` -Do not omit the `$form->isSuccess()` to verify the validity of the data. +Do not omit the `$form->isSuccess()` check; it verifies the validity of the data. -When a form is submitted with the <kbd>Enter</kbd> key, it is treated as if it had been submitted with the first button. +When a form is submitted by pressing the <kbd>Enter</kbd> key, it is treated as if it were submitted by the first button. Vulnerability Protection ======================== -Nette Framework puts a great effort to be safe and since forms are the most common user input, Nette forms are as good as impenetrable. +Nette Framework places a strong emphasis on security and therefore meticulously ensures the proper security of forms. -In addition to protecting the forms against attack well-known vulnerabilities such as [Cross-Site Scripting (XSS) |nette:glossary#cross-site-scripting-xss] and [Cross-Site Request Forgery (CSRF)|nette:glossary#cross-site-request-forgery-csrf] it does a lot of small security tasks that you no longer have to think about. +In addition to protecting forms against well-known vulnerabilities like [Cross-Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] and [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF], it performs many small security measures that you no longer need to think about. -For example, it filters out all control characters from the inputs and checks the validity of the UTF-8 encoding, so that the data from the form will always be clean. For select boxes and radio lists, it verifies that the selected items were actually from the offered ones and there was no forgery. We've already mentioned that for single-line text input, it removes end-of-line characters that an attacker could send there. For multiline inputs, it normalizes the end-of-line characters. And so on. +For example, it filters all control characters from inputs and checks the validity of UTF-8 encoding, ensuring that the data from the form will always be clean. For select boxes and radio lists, it verifies that the selected items were actually among the offered options and that no forgery occurred. We've already mentioned that for single-line text inputs, it removes end-of-line characters that an attacker might send. For multi-line inputs, it normalizes end-of-line characters. And so on. -Nette fixes security vulnerabilities for you that most programmers have no idea exist. +Nette handles security risks for you that many programmers are unaware even exist. -The mentioned CSRF attack is that an attacker lures the victim to visit a page that silently executes a request in the victim's browser to the server where the victim is currently logged in, and the server believes that the request was made by the victim at will. Therefore, Nette prevents the form from being submitted via POST from another domain. If for some reason you want to turn off protection and allow the form to be submitted from another domain, use: +The mentioned CSRF attack involves an attacker luring a victim to a page that silently executes a request in the victim's browser to the server where the victim is currently logged in. The server believes that the request was made by the victim voluntarily. Therefore, Nette prevents the form from being submitted via POST from a different domain. If, for some reason, you want to disable protection and allow the form to be submitted from another domain, use: ```php -$form->allowCrossOrigin(); // ATTENTION! Turns off protection! +$form->allowCrossOrigin(); // WARNING! Disables protection! ``` -This protection uses a SameSite cookie named `_nss`. Therefore, create a form before flushing the first output so that the cookie can be sent. +This protection uses a SameSite cookie named `_nss`. Therefore, create the form object before sending the first output so that the cookie can be sent. -SameSite cookie protection may not be 100% reliable, so it's a good idea to turn on token protection: +SameSite cookie protection may not be 100% reliable, so it's advisable to also enable token protection: ```php $form->addProtection(); ``` -It's strongly recommended to apply this protection to the forms in an administrative part of your application which changes sensitive data. The framework protects against a CSRF attack by generating and validating authentication token that is stored in a session (the argument is the error message shown if the token has expired). That's why it is necessary to have an session started before displaying the form. In the administration part of the website, the session is usually already started, due to the user's login. -Otherwise, start the session with the method `Nette\Http\Session::start()`. +We strongly recommend applying this protection to forms in the administrative part of your application that modify sensitive data. The framework defends against CSRF attacks by generating and validating an authorization token stored in the session. Therefore, it is necessary to have a session started before displaying the form. In the administrative part of the website, the session is usually already started due to user login. Otherwise, start the session using the `Nette\Http\Session::start()` method. -So, we have a quick introduction to forms in Nette. Try looking in the [examples |https://github.com/nette/forms/tree/master/examples] directory in the distribution for more inspiration. +So, we've had a quick introduction to forms in Nette. Try looking in the [examples |https://github.com/nette/forms/tree/master/examples] directory in the distribution for more inspiration. diff --git a/forms/en/validation.texy b/forms/en/validation.texy index fd50eb7e4f..d9a9626cc6 100644 --- a/forms/en/validation.texy +++ b/forms/en/validation.texy @@ -5,133 +5,144 @@ Forms Validation Required Controls ================= -Controls are marked as required with the method `setRequired()`, whose argument is the text of the [error message|#Error Messages] that will be displayed if the user does not fill it. If no argument is given, the default error message is used. +Controls are marked as required using the `setRequired()` method. Its argument is the text of the [error message |#Error Messages] that will be displayed if the user does not fill in the control. If no argument is provided, the default error message is used. ```php $form->addText('name', 'Name:') - ->setRequired('Please fill your name.'); + ->setRequired('Please fill in your name.'); ``` Rules ===== -We add validation rules to controls with the `addRule()` method. The first parameter is the rule, the second is the [error message|#Error Messages], and the third is the validation rule argument. +We add validation rules to controls using the `addRule()` method. The first parameter is the rule, the second is the [error message |#Error Messages], and the third is the validation rule argument. ```php $form->addPassword('password', 'Password:') - ->addRule($form::MinLength, 'Password must be at least %d characters', 8); + ->addRule($form::MinLength, 'Password must be at least %d characters long', 8); ``` -Nette comes with a number of built-in rules whose names are constants of the `Nette\Forms\Form` class: +**Validation rules are checked only if the user has filled in the control.** -We can use the following rules for all controls: +Nette comes with several predefined rules whose names are constants of the `Nette\Forms\Form` class. We can apply these rules to all controls: -| constant | description | arguments +| constant | description | argument type |------- -| `Required` | alias of `setRequired()` | - -| `Filled` | alias of `setRequired()` | - -| `Blank` | must not be filled | - -| `Equal` | value is equal to parameter | `mixed` -| `NotEqual` | value is not be equal to parameter | `mixed` -| `IsIn` | value is equal to some element in the array | `array` -| `IsNotIn` | value does not equal any element in the array | `array` -| `Valid` | input passes validation (for [#conditions]) | - - -For controls `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` the following rules can also be used: - -| `MinLength` | minimal string length | `int` -| `MaxLength` | maximal string length | `int` +| `Required` | required control, alias for `setRequired()` | - +| `Filled` | required control, alias for `setRequired()` | - +| `Blank` | control must not be filled | - +| `Equal` | value must be equal to the parameter | `mixed` +| `NotEqual` | value must not be equal to the parameter | `mixed` +| `IsIn` | value must be one of the items in the array | `array` +| `IsNotIn` | value must not be any of the items in the array | `array` +| `Valid` | is the control filled correctly? (for [#Conditions]) | - + + +Text inputs +----------- + +For controls `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`, `addFloat()`, some of the following rules can also be applied: + +| `MinLength` | minimum text length | `int` +| `MaxLength` | maximum text length | `int` | `Length` | length in range or exact length | pair `[int, int]` or `int` | `Email` | valid email address | - -| `URL` | valid URL | - -| `Pattern` | matches regular pattern | `string` +| `URL` | absolute URL | - +| `Pattern` | matches regular expression | `string` | `PatternInsensitive` | like `Pattern`, but case-insensitive | `string` -| `Integer` | integer | - -| `Numeric` | alias of `Integer` | - -| `Float` | integer or floating point number | - -| `Min` | minimum of the integer value | `int\|float` -| `Max` | maximum of the integer value | `int\|float` -| `Range` | value in the range | pair `[int\|float, int\|float]` +| `Integer` | integer value | - +| `Numeric` | alias for `Integer` | - +| `Float` | number | - +| `Min` | minimum value of a numeric control | `int\|float` +| `Max` | maximum value of a numeric control | `int\|float` +| `Range` | value in range | pair `[int\|float, int\|float]` -The `Integer`, `Numeric` a `Float` rules automatically convert the value to integer (or float respectively). Furthermore, the `URL` rule also accepts an address without a schema (eg `nette.org`) and completes the schema (`https://nette.org`). -The expressions in `Pattern` and `PatternInsensitive` must be valid for the whole value, ie as if it were wrapped in the characters `^` and `$`. +The validation rules `Integer`, `Numeric`, and `Float` automatically convert the value to an integer or float, respectively. Furthermore, the `URL` rule also accepts an address without a scheme (e.g., `nette.org`) and completes the scheme (`https://nette.org`). The expression in `Pattern` and `PatternInsensitive` must be valid for the entire value, i.e., as if it were wrapped in `^` and `$` characters. -For controls `addUpload()`, `addMultiUpload()` the following rules can also be used: -| `MaxFileSize` | maximal file size | `int` -| `MimeType` | MIME type, accepts wildcards (`'video/*'`) | `string\|string[]` -| `Image` | uploaded file is JPEG, PNG, GIF, WebP | - -| `Pattern` | file name matches regular expression | `string` -| `PatternInsensitive` | like `Pattern`, but case-insensitive | `string` +Number of Items +--------------- -The `MimeType` and `Image` require PHP extension `fileinfo`. Whether a file or image is of the required type is detected by its signature. The integrity of the entire file is not checked. You can find out if an image is not corrupted for example by trying to [load it|http:request#toImage]. +For controls `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()`, you can also use the following rules to limit the number of selected items or uploaded files: -For controls `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()` the following rules can also be used to limit the number of selected items, respectively uploaded files: - -| `MinLength` | minimal count | `int` -| `MaxLength` | maximal count | `int` +| `MinLength` | minimum count | `int` +| `MaxLength` | maximum count | `int` | `Length` | count in range or exact count | pair `[int, int]` or `int` +File Upload +----------- + +For controls `addUpload()`, `addMultiUpload()`, the following rules can also be used: + +| `MaxFileSize` | maximum file size in bytes | `int` +| `MimeType` | MIME type, wildcards allowed (`'video/*'`) | `string\|string[]` +| `Image` | JPEG, PNG, GIF, WebP, AVIF image | - +| `Pattern` | file name matches regular expression | `string` +| `PatternInsensitive` | like `Pattern`, but case-insensitive | `string` + +`MimeType` and `Image` require the PHP extension `fileinfo`. Whether a file or image is of the required type is detected based on its signature, and **the integrity of the entire file is not checked.** You can determine if an image is corrupted, for example, by trying to [load it |http:request#toImage]. + + Error Messages --------------- +============== -All predefined rules except `Pattern` and `PatternInsensitive` have a default error message, so they it be omitted. However, by passing and formulating all customized messages, you will make the form more user-friendly. +All predefined rules except `Pattern` and `PatternInsensitive` have a default error message, so they can be omitted. However, by providing and formulating all custom messages tailored to your needs, you will make the form more user-friendly. -You can change the default messages in [forms:configuration], by modifying the texts in the `Nette\Forms\Validator::$messages` array or by using [translator|rendering#translating]. +You can change the default messages in the [configuration |forms:configuration], by modifying the texts in the `Nette\Forms\Validator::$messages` array, or by using a [translator |rendering#Translating]. -The following wildcards can be used in the text of error messages: +The following placeholder strings can be used in the text of error messages: -| `%d` | gradually replaces the rules after the arguments -| `%n$d` | replaces with the nth rule argument -| `%label` | replaces with field label (without colon) -| `%name` | replaces with field name (eg `name`) -| `%value` | replaces with value entered by the user +| `%d` | replaced sequentially by rule arguments +| `%n$d` | replaced by the n-th rule argument +| `%label` | replaced by the control label (without the colon) +| `%name` | replaced by the control name (e.g., `name`) +| `%value` | replaced by the value entered by the user ```php $form->addText('name', 'Name:') ->setRequired('Please fill in %label'); $form->addInteger('id', 'ID:') - ->addRule($form::Range, 'at least %d and no more than %d', [5, 10]); + ->addRule($form::Range, 'at least %d and at most %d', [5, 10]); $form->addInteger('id', 'ID:') - ->addRule($form::Range, 'no more than %2$d and at least %1$d', [5, 10]); + ->addRule($form::Range, 'at most %2$d and at least %1$d', [5, 10]); ``` Conditions ========== -Besides validation rules, conditions can be set. They are set much like rules, yet we use `addRule()` instead of `addCondition()` and of course, we leave it without an error message (the condition just asks): +In addition to rules, conditions can also be added. They are written similarly to rules, but instead of `addRule()`, we use the `addCondition()` method, and naturally, we don't provide an error message (the condition only asks): ```php $form->addPassword('password', 'Password:') - // if password is not longer than 8 characters ... + // if the password length is not greater than 8 ->addCondition($form::MaxLength, 8) - // ... then it must contain a number - ->addRule($form::Pattern, 'Must contain number', '.*[0-9].*'); + // then it must contain a digit + ->addRule($form::Pattern, 'Must contain a digit', '.*[0-9].*'); ``` -Condition can be linked to a different element than the current one using `addConditionOn()`. The first parameter is a reference to the field. In the following case, the email will only be required if the checkbox is checked (ie. its value is `true`): +The condition can be linked to a control other than the current one using `addConditionOn()`. The first parameter is a reference to the control. In this example, the email will be required only if the checkbox is checked (i.e., its value is true): ```php -$form->addCheckbox('newsletters', 'send me newsletters'); +$form->addCheckbox('newsletters', 'Send me newsletters'); $form->addEmail('email', 'Email:') - // if checkbox is checked ... + // if the checkbox is checked ->addConditionOn($form['newsletters'], $form::Equal, true) - // ... require email - ->setRequired('Fill your email address'); + // then require the email + ->setRequired('Enter your email address'); ``` -Conditions can be grouped into complex structures with `elseCondition()` and `endCondition()` methods. +Conditions can be formed into complex structures using `elseCondition()` and `endCondition()`: ```php $form->addText(/* ... */) ->addCondition(/* ... */) // if the first condition is met - ->addConditionOn(/* ... */) // and the second condition on another element too + ->addConditionOn(/* ... */) // and the second condition on another control is also met ->addRule(/* ... */) // require this rule ->elseCondition() // if the second condition is not met ->addRule(/* ... */) // require these rules @@ -140,29 +151,29 @@ $form->addText(/* ... */) ->addRule(/* ... */); ``` -In Nette, it is very easy to react to the fulfillment or not of a condition on the JavaScript side using the `toggle()` method, see [#Dynamic JavaScript]. +In Nette, it is very easy to react to the fulfillment or non-fulfillment of a condition on the JavaScript side using the `toggle()` method, see [#Dynamic JavaScript]. -References Between Controls -=========================== +Reference to Another Control +============================ -The rule or condition argument can be a reference to another element. For example, you can dynamically validate that the `text` has as many characters as the value of the `length` field is: +You can also pass another form control as an argument to a rule or condition. The rule will then use the value entered later by the user in the browser. This can be used, for example, to dynamically validate that the `password` control contains the same string as the `password_confirm` control: ```php -$form->addInteger('length'); -$form->addText('text') - ->addRule($form::Length, null, $form['length']); +$form->addPassword('password', 'Password'); +$form->addPassword('password_confirm', 'Confirm Password') + ->addRule($form::Equal, 'The passwords do not match', $form['password']); ``` Custom Rules and Conditions =========================== -Sometimes we get into a situation where the built-in validation rules in Nette are not enough and we need to validate the data from the user in our own way. In Nette this is very easy! +Sometimes we encounter situations where the built-in validation rules in Nette are insufficient, and we need to validate user data in our own way. In Nette, this is very simple! -You can pass any callback as the first parameter to the `addRule()` or `addCondition()` methods. The callback accepts the element itself as the first parameter and returns a boolean value indicating whether the validation was successful. When adding a rule using `addRule()`, additional arguments can be passed, and these are then passed as the second parameter. +You can pass any callback as the first parameter to the `addRule()` or `addCondition()` methods. The callback accepts the control itself as the first parameter and returns a boolean value indicating whether the validation was successful. When adding a rule using `addRule()`, additional arguments can be provided, which are then passed as the second parameter. -The custom set of validators can thus be created as a class with static methods: +A custom set of validators can thus be created as a class with static methods: ```php class MyValidators @@ -175,12 +186,12 @@ class MyValidators public static function validateEmailDomain(BaseControl $input, $domain) { - // additional validators + // other validators } } ``` -The usage is then very simple: +Usage is then very straightforward: ```php $form->addInteger('num') @@ -191,7 +202,7 @@ $form->addInteger('num') ); ``` -Custom validation rules can also be added to JavaScript. The only requirement is that the rule must be a static method. Its name for the JavaScript validator is created by concatenating the class name without backslashes `\`, the underscore `_`, and the method name. For example, write `App\MyValidators::validateDivisibility` as `AppMyValidators_validateDivisibility` and add it to the `Nette.validators` object: +Custom validation rules can also be added to JavaScript. The condition is that the rule must be a static method. Its name for the JavaScript validator is formed by concatenating the class name without backslashes `\`, an underscore `_`, and the method name. For example, `App\MyValidators::validateDivisibility` is written as `AppMyValidators_validateDivisibility` and added to the `Nette.validators` object: ```js Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => { @@ -203,20 +214,20 @@ Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => Event onValidate ================ -After the form is submitted, validation is performed by checking the individual rules added by `addRule()` and then calling [event|nette:glossary#Events] `onValidate`. Its handler can be used for additional validation, typically to verify the correct combination of values in multiple form elements. +After the form is submitted, validation is performed, checking the individual rules added using `addRule()`, and subsequently, the `onValidate` [event |nette:glossary#Events] is triggered. Its handler can be used for additional validation, typically verifying the correct combination of values in multiple form controls. -If an error is detected, it is passed to the form using the `addError()` method. This can be called either on a specific element or directly on the form. +If an error is detected, it is passed to the form using the `addError()` method. This can be called either on a specific control or directly on the form. ```php protected function createComponentSignInForm(): Form { $form = new Form; // ... - $form->onValidate[] = [$this, 'validateSignInForm']; + $form->onValidate[] = $this->validateSignInForm(...); return $form; } -public function validateSignInForm(Form $form, \stdClass $data): void +private function validateSignInForm(Form $form, \stdClass $data): void { if ($data->foo > 1 && $data->bar > 5) { $form->addError('This combination is not possible.'); @@ -228,7 +239,7 @@ public function validateSignInForm(Form $form, \stdClass $data): void Processing Errors ================= -In many cases, we discover an error when we are processing a valid form, e.g. when we write a new entry to the database and encounter a duplicate key. In this case, we pass the error back to the form using the `addError()` method. This can be called either on a specific item or directly on the form: +In many cases, we only discover an error when processing a valid form, for example, when writing a new entry to the database and encountering a duplicate key. In such a case, we again pass the error back to the form using the `addError()` method. This can be called either on a specific control or directly on the form: ```php try { @@ -243,67 +254,66 @@ try { } ``` -If possible, we recommend adding the error directly to the form element, as it will appear next to it when using the default renderer. +If possible, we recommend adding the error directly to the form control, as it will be displayed next to it when using the default renderer. ```php $form['date']->addError('Sorry, this date is already taken.'); ``` -You can call `addError()` repeatedly to pass multiple error messages to a form or element. You get them with `getErrors()`. +You can call `addError()` repeatedly to pass multiple error messages to a form or control. You can retrieve them using `getErrors()`. -Note that `$form->getErrors()` returns a summary of all error messages, even those passed directly to individual elements, not just directly to the form. Error messages passed only to the form are retrieved via `$form->getOwnErrors()`. +Note that `$form->getErrors()` returns a summary of all error messages, including those passed directly to individual controls, not just directly to the form. Error messages passed only to the form can be retrieved via `$form->getOwnErrors()`. Modifying Input Values ====================== -Using the `addFilter()` method, we can modify the value entered by the user. In this example, we will tolerate and remove spaces in the zip code: +Using the `addFilter()` method, we can modify the value entered by the user. In this example, we will tolerate and remove spaces in the postal code: ```php -$form->addText('zip', 'Postcode:') +$form->addText('zip', 'Postal Code:') ->addFilter(function ($value) { - return str_replace(' ', '', $value); // remove spaces from the postcode + return str_replace(' ', '', $value); // remove spaces from the postal code }) - ->addRule($form::Pattern, 'The postal code is not five digits', '\d{5}'); + ->addRule($form::Pattern, 'Postal code is not five digits', '\d{5}'); ``` -The filter is included between the validation rules and conditions and therefore depends on the order of the methods, ie the filter and the rule are called in the same order as is the order of the `addFilter()` and `addRule()` methods. +The filter is integrated among validation rules and conditions, and thus the order of methods matters, i.e., the filter and rule are called in the same order as the `addFilter()` and `addRule()` methods are listed. JavaScript Validation ===================== -The language of validation rules and conditions is powerful. Even though all constructions work both server-side and client-side, in JavaScript. Rules are transferred in HTML attributes `data-nette-rules` as JSON. -The validation itself is handled by another script, which hooks all form's `submit` events, iterates over all inputs and runs respective validations. +The language for formulating conditions and rules is very powerful. All constructs work both on the server side and on the client side in JavaScript. They are transferred in HTML attributes `data-nette-rules` as JSON. The validation itself is handled by a script that intercepts the form's `submit` event, iterates through the individual controls, and performs the corresponding validation. -This script is `netteForms.js`, which is available from several possible sources: +This script is `netteForms.js`, and it is available from several possible sources: -You can embed the script directly into the HTML page from the CDN: +You can embed the script directly into the HTML page from a CDN: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Or copy locally to the public folder of the project (e.g. from `vendor/nette/forms/src/assets/netteForms.min.js`): +Or copy it locally to the public folder of your project (e.g., from `vendor/nette/forms/src/assets/netteForms.min.js`): ```latte <script src="/path/to/netteForms.min.js"></script> ``` -Or install via [npm|https://www.npmjs.com/package/nette-forms]: +Or install it via [npm |https://www.npmjs.com/package/nette-forms]: ```shell npm install nette-forms ``` -And then load and run: +And then load and run it: ```js import netteForms from 'nette-forms'; netteForms.initOnLoad(); ``` -Alternatively, you can load it directly from the folder `vendor`: +Alternatively, you can load it directly from the `vendor` folder: ```js import netteForms from '../path/to/vendor/nette/forms/src/assets/netteForms.js'; @@ -314,7 +324,7 @@ netteForms.initOnLoad(); Dynamic JavaScript ================== -Do you only want to show the address fields only if the user chooses to send the goods by post? No problem. The key is a pair of methods `addCondition()` & `toggle()`: +Want to display the address fields only if the user chooses to have the goods sent by post? No problem. The key is the pair of methods `addCondition()` & `toggle()`: ```php $form->addCheckbox('send_it') @@ -322,11 +332,11 @@ $form->addCheckbox('send_it') ->toggle('#address-container'); ``` -This code says that when the condition is met, that is, when the checkbox is checked, the HTML element `#address-container` will be visible. And vice versa. So, we place the form elements with the recipient's address in a container with that ID, and when the checkbox is clicked, they are hidden or shown. This is handled by the `netteForms.js` script. +This code states that when the condition is met (i.e., when the checkbox is checked), the HTML element `#address-container` will be visible, and vice versa. So, we place the form controls with the recipient's address in a container with this ID, and they will hide or show when the checkbox is clicked. This is handled by the `netteForms.js` script. -Any selector can be passed as an argument to the `toggle()` method. For historical reasons, an alphanumeric string with no other special characters is treated as an element ID, the same as if it were preceded by the `#` character. The second optional parameter allows us to reverse the behavior, i.e. if we used `toggle('#address-container', false)`, the element would be displayed only if the checkbox was unchecked. +Any selector can be passed as an argument to the `toggle()` method. For historical reasons, an alphanumeric string without other special characters is treated as an element ID, just as if it were preceded by the `#` character. The second optional parameter allows reversing the behavior; for instance, if we used `toggle('#address-container', false)`, the element would be displayed only if the checkbox was *not* checked. -The default JavaScript implementation changes the `hidden` property for elements. However, we can easily change the behavior, for example by adding an animation. Just override the `Nette.toggle` method in JavaScript with a custom solution: +The default JavaScript implementation changes the `hidden` property of the elements. However, we can easily change the behavior, for example, by adding an animation. Just override the `Nette.toggle` method in JavaScript with a custom solution: ```js Nette.toggle = (selector, visible, srcElement, event) => { @@ -340,7 +350,7 @@ Nette.toggle = (selector, visible, srcElement, event) => { Disabling Validation ==================== -In certain cases, you need to disable validation. If a submit button isn't supposed to run validation after submitting (for example *Cancel* or *Preview* button), you can disable the validation by calling `$submit->setValidationScope([])`. You can also validate the form partially by specifying items to be validated. +Sometimes it might be useful to disable validation. If pressing a submit button should not perform validation (suitable for *Cancel* or *Preview* buttons), we disable it using the `$submit->setValidationScope([])` method. If it should perform only partial validation, we can specify which fields or form containers should be validated. ```php $form->addText('name') @@ -356,11 +366,11 @@ $form->addSubmit('send1'); // Validates the whole form $form->addSubmit('send2') ->setValidationScope([]); // Validates nothing $form->addSubmit('send3') - ->setValidationScope([$form['name']]); // Validates only 'name' field + ->setValidationScope([$form['name']]); // Validates only the 'name' control $form->addSubmit('send4') - ->setValidationScope([$form['details']['age']]); // Validates only 'age' field + ->setValidationScope([$form['details']['age']]); // Validates only the 'age' control $form->addSubmit('send5') - ->setValidationScope([$form['details']]); // Validates 'details' container + ->setValidationScope([$form['details']]); // Validates the 'details' container ``` -[#Event onValidate] on the form is always invoked and is not affected by the `setValidationScope`. `onValidate` event on the container is invoked only when this container is specified for partial validation. +`setValidationScope` does not affect the [#Event onValidate] on the form, which will always be called. The `onValidate` event on a container will only be triggered if that container is marked for partial validation. diff --git a/forms/es/@home.texy b/forms/es/@home.texy index 47da9427be..1d23b3b784 100644 --- a/forms/es/@home.texy +++ b/forms/es/@home.texy @@ -1,31 +1,31 @@ -Formularios +Nette Forms *********** <div class=perex> -Nette Forms ha revolucionado la creación de formularios web. Todo lo que tenía que hacer era escribir unas pocas líneas de código claras y ya tenía un formulario, incluyendo renderizado, JavaScript y validación de servidor, además de una seguridad líder en la industria. Veamos cómo +Nette Forms revolucionó la creación de formularios web. De repente, bastaba con escribir unas pocas líneas de código comprensibles para tener un formulario completo, incluyendo la representación, validación JavaScript y del lado del servidor, y además con una seguridad de primer nivel. Mostraremos cómo - crear formularios amigables - validar los datos enviados -- dibujar elementos exactamente como se necesitan +- representar elementos exactamente según sea necesario </div> -Con Nette Forms, puede reducir tareas rutinarias como escribir la validación (tanto en el lado del servidor como del cliente) y minimizar la probabilidad de errores y problemas de seguridad. +Usando Nette Forms, evitará una serie de tareas rutinarias, como escribir validaciones (además, dobles, en el lado del servidor y del cliente), minimizará la probabilidad de errores y agujeros de seguridad. -Puede utilizar los formularios como parte de la aplicación Nette (es decir, en los presentadores) o de forma independiente. Dado que el uso es ligeramente diferente en ambos casos, hemos preparado instrucciones separadas para usted: +Puede usar formularios como parte de Nette Application (es decir, en presenters), o completamente de forma independiente. Dado que el uso difiere ligeramente en ambos casos, hemos preparado dos tutoriales para usted: <div class="wiki-buttons"> -<div> "Formularios en presentadores .[wiki-button]":in-presenter </div> -<div> "Formularios independientes .[wiki-button]":standalone </div> +<div> "Formularios en presenters .[wiki-button]":in-presenter </div> +<div> "Formularios de forma independiente .[wiki-button]":standalone </div> </div> Instalación ----------- -Descargue e instale el paquete utilizando [Composer |best-practices:composer]: +Puede descargar e instalar la librería usando [Composer|best-practices:composer]: ```shell composer require nette/forms diff --git a/forms/es/@left-menu.texy b/forms/es/@left-menu.texy index a3e9657567..697c17770f 100644 --- a/forms/es/@left-menu.texy +++ b/forms/es/@left-menu.texy @@ -1,14 +1,14 @@ -Formularios +Nette Forms *********** -- [Visión general |@home] -- [Formularios en Presentadores |in-presenter] -- [Formularios independientes |standalone] -- [Controles de formularios |controls] +- [Introducción |@home] +- [Formularios en presenters|in-presenter] +- [Formularios de forma independiente|standalone] +- [Elementos de formulario |controls] - [Validación |validation] - [Representación |rendering] - [Configuración |configuration] -Más información -*************** -- [Buenas prácticas |best-practices:] +Lectura adicional +***************** +- [Tutoriales y procedimientos |best-practices:] diff --git a/forms/es/@meta.texy b/forms/es/@meta.texy new file mode 100644 index 0000000000..1670b124ad --- /dev/null +++ b/forms/es/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Documentación}} diff --git a/forms/es/configuration.texy b/forms/es/configuration.texy index e1899ec662..01d4c852b7 100644 --- a/forms/es/configuration.texy +++ b/forms/es/configuration.texy @@ -2,7 +2,7 @@ Configuración de formularios **************************** .[perex] -Puede cambiar los [mensajes de error |validation] predeterminados de los formularios en la configuración. +En la configuración se pueden cambiar los [mensajes de error predeterminados de los formularios|validation]. ```neon forms: @@ -38,24 +38,24 @@ forms: Equal: 'Por favor, introduzca %s.' NotEqual: 'Este valor no debe ser %s.' Filled: 'Este campo es obligatorio.' - Blank: 'Este campo debe estar en blanco.' + Blank: 'Este campo debe estar vacío.' MinLength: 'Por favor, introduzca al menos %d caracteres.' - MaxLength: 'No introduzca más de %d caracteres.' - Length: 'Por favor, introduzca un valor entre %d y %d caracteres.' - Email: 'Introduzca una dirección de correo electrónico válida.' - URL: 'Introduzca una URL válida.' - Integer: 'Introduzca un número entero válido.' - Float: 'Introduzca un número válido.' + MaxLength: 'Por favor, introduzca no más de %d caracteres.' + Length: 'Por favor, introduzca un valor de entre %d y %d caracteres.' + Email: 'Por favor, introduzca una dirección de correo electrónico válida.' + URL: 'Por favor, introduzca una URL válida.' + Integer: 'Por favor, introduzca un número entero válido.' + Float: 'Por favor, introduzca un número válido.' Min: 'Por favor, introduzca un valor mayor o igual a %d.' Max: 'Por favor, introduzca un valor menor o igual a %d.' - Rango: 'Por favor, introduce un valor entre %d y %d.' - MaxFileSize: 'El tamaño del archivo subido puede ser de hasta %d bytes.' + Range: 'Por favor, introduzca un valor entre %d y %d.' + MaxFileSize: 'El tamaño del archivo subido puede ser como máximo de %d bytes.' MaxPostSize: 'Los datos subidos exceden el límite de %d bytes.' - MimeType: 'El archivo subido no tiene el formato esperado.' - Imagen: 'El archivo subido debe ser una imagen en formato JPEG, GIF, PNG o WebP.' + MimeType: 'El archivo subido no está en el formato esperado.' + Image: 'El archivo subido debe ser una imagen en formato JPEG, GIF, PNG, WebP o AVIF.' Nette\Forms\Controls\SelectBox::Valid: 'Por favor, seleccione una opción válida.' - Nette\Forms\Controls\UploadControl::Valid: 'Se ha producido un error al cargar el archivo.' - Nette\Forms\Controls\CsrfProtection::Protection: 'Su sesión ha expirado. Por favor, vuelva a la página de inicio e inténtelo de nuevo.' + Nette\Forms\Controls\UploadControl::Valid: 'Ocurrió un error durante la subida del archivo.' + Nette\Forms\Controls\CsrfProtection::Protection: 'Su sesión ha expirado. Por favor, regrese a la página de inicio e inténtelo de nuevo.' ``` -Si no utiliza todo el framework y, por tanto, ni siquiera los archivos de configuración, puede cambiar los mensajes de error predeterminados directamente en el campo `Nette\Forms\Validator::$messages`. +Si no utiliza todo el framework y, por lo tanto, tampoco los archivos de configuración, puede cambiar los mensajes de error predeterminados directamente en el array `Nette\Forms\Validator::$messages`. diff --git a/forms/es/controls.texy b/forms/es/controls.texy index a1ee2d5426..1f7aa3bac5 100644 --- a/forms/es/controls.texy +++ b/forms/es/controls.texy @@ -1,14 +1,14 @@ -Controles de formularios -************************ +Elementos de formulario +*********************** .[perex] -Visión general de los controles de formulario incorporados. +Resumen de los elementos de formulario estándar. -addText(string|int $name, $label=null): TextInput .[method] -=========================================================== +addText(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +================================================================================================== -Añade un campo de texto de una sola línea (clase [TextInput |api:Nette\Forms\Controls\TextInput]). Si el usuario no rellena el campo, devuelve una cadena vacía `''`, o utiliza `setNullable()` para cambiarlo y que devuelva `null`. +Agrega un campo de texto de una línea (clase [TextInput |api:Nette\Forms\Controls\TextInput]). Si el usuario no rellena el campo, devuelve una cadena vacía `''`, o mediante `setNullable()` se puede especificar que devuelva `null`. ```php $form->addText('name', 'Nombre:') @@ -16,265 +16,360 @@ $form->addText('name', 'Nombre:') ->setNullable(); ``` -Valida automáticamente UTF-8, recorta los espacios en blanco a izquierda y derecha, y elimina los saltos de línea que podría enviar un atacante. +Valida automáticamente UTF-8, recorta los espacios iniciales y finales y elimina los saltos de línea que un atacante podría enviar. -La longitud máxima puede limitarse mediante `setMaxLength()`. La [función addFilter() |validation#Modifying Input Values] permite cambiar el valor introducido por el usuario. +La longitud máxima se puede limitar mediante `setMaxLength()`. Modificar el valor introducido por el usuario permite [addFilter() |validation#Modificación de la entrada]. -Utilice `setHtmlType()` para cambiar el [carácter |https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types] del elemento de entrada a `search`, `tel`, `url`, `range`, `date`, `datetime-local`, `month`, `time`, `week`, `color`. En lugar de los tipos `number` y `email`, recomendamos utilizar [addInteger |#addInteger] y [addEmail |#addEmail], que proporcionan validación del lado del servidor. +Mediante `setHtmlType()` se puede cambiar el carácter visual del campo de texto a tipos como `search`, `tel` o `url` ver [especificación|https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types]. Recuerde que el cambio de tipo es solo visual y no reemplaza la función de validación. Para el tipo `url` es conveniente agregar una regla de validación específica [URL |validation#Entradas de texto]. -```php -$form->addText('color', 'Elige color:') - ->setHtmlType('color') - ->addRule($form::Pattern, 'valor no válido', '[0-9a-f]{6}'); -``` +.[note] +Para otros tipos de entradas, como `number`, `range`, `email`, `date`, `datetime-local`, `time` y `color`, utilice métodos especializados como [#addInteger], [#addFloat], [#addEmail] [#addDate], [#addTime], [#addDateTime] y [#addColor], que aseguran la validación del lado del servidor. Los tipos `month` y `week` aún no son totalmente compatibles con todos los navegadores. -Se puede establecer el llamado empty-value para el elemento, que es algo así como el valor por defecto, pero si el usuario no lo sobrescribe, devuelve cadena vacía o `null`. +Al elemento se le puede establecer el llamado empty-value, que es algo así como un valor predeterminado, pero si el usuario no lo cambia, el elemento devuelve una cadena vacía o `null`. ```php $form->addText('phone', 'Teléfono:') ->setHtmlType('tel') - ->setEmptyValue('+420'); + ->setEmptyValue('+34'); ``` addTextArea(string|int $name, $label=null): TextArea .[method] ============================================================== -Añade un campo de texto multilínea (clase [TextArea |api:Nette\Forms\Controls\TextArea]). Si el usuario no rellena el campo, devuelve una cadena vacía `''`, o utiliza `setNullable()` para cambiarlo y que devuelva `null`. +Agrega un campo para introducir texto multilínea (clase [TextArea |api:Nette\Forms\Controls\TextArea]). Si el usuario no rellena el campo, devuelve una cadena vacía `''`, o mediante `setNullable()` se puede especificar que devuelva `null`. ```php $form->addTextArea('note', 'Nota:') - ->addRule($form::MaxLength, 'Tu nota es demasiado larga', 10000); + ->addRule($form::MaxLength, 'La nota es demasiado larga', 10000); ``` -Valida automáticamente UTF-8 y normaliza los saltos de línea a `\n`. A diferencia de un campo de entrada de una sola línea, no recorta los espacios en blanco. +Valida automáticamente UTF-8 y normaliza los separadores de línea a `\n`. A diferencia del campo de entrada de una sola línea, no se realiza ningún recorte de espacios. -La longitud máxima puede limitarse mediante `setMaxLength()`. La [función addFilter() |validation#Modifying Input Values] permite cambiar el valor introducido por el usuario. Puede establecer el llamado valor vacío utilizando `setEmptyValue()`. +La longitud máxima se puede limitar mediante `setMaxLength()`. Modificar el valor introducido por el usuario permite [addFilter() |validation#Modificación de la entrada]. Se puede establecer el llamado empty-value mediante `setEmptyValue()`. addInteger(string|int $name, $label=null): TextInput .[method] ============================================================== -Añade un campo de entrada para números enteros (clase [TextInput |api:Nette\Forms\Controls\TextInput]). Devuelve un entero o `null` si el usuario no introduce nada. +Agrega un campo para introducir un número entero (clase [TextInput |api:Nette\Forms\Controls\TextInput]). Devuelve un integer, o `null` si el usuario no introduce nada. + +```php +$form->addInteger('year', 'Año:') + ->addRule($form::Range, 'El año debe estar en el rango de %d a %d.', [1900, 2023]); +``` + +El elemento se renderiza como `<input type="number">`. Usando el método `setHtmlType()` se puede cambiar el tipo a `range` para mostrarlo como un control deslizante, o a `text`, si prefiere un campo de texto estándar sin el comportamiento especial del tipo `number`. + + +addFloat(string|int $name, $label=null): TextInput .[method]{data-version:3.1.12} +================================================================================= + +Agrega un campo para introducir un número decimal (clase [TextInput |api:Nette\Forms\Controls\TextInput]). Devuelve un float, o `null` si el usuario no introduce nada. ```php -$form->addInteger('level', 'Nivel:') +$form->addFloat('level', 'Nivel:') ->setDefaultValue(0) - ->addRule($form::Range, 'El nivel debe estar entre %d y %d.', [0, 100]); + ->addRule($form::Range, 'El nivel debe estar en el rango de %d a %d.', [0, 100]); ``` +El elemento se renderiza como `<input type="number">`. Usando el método `setHtmlType()` se puede cambiar el tipo a `range` para mostrarlo como un control deslizante, o a `text`, si prefiere un campo de texto estándar sin el comportamiento especial del tipo `number`. + +Nette y el navegador Chrome aceptan tanto la coma como el punto como separador decimal. Para que esta funcionalidad esté disponible también en Firefox, se recomienda establecer el atributo `lang` ya sea para el elemento dado o para toda la página, por ejemplo `<html lang="es">`. + -addEmail(string|int $name, $label=null): TextInput .[method] -============================================================ +addEmail(string|int $name, $label=null, int $maxLength=255): TextInput .[method] +================================================================================ -Añade un campo de dirección de correo electrónico con comprobación de validez (clase [TextInput |api:Nette\Forms\Controls\TextInput]). Si el usuario no rellena el campo, devuelve una cadena vacía `''`, o utiliza `setNullable()` para cambiarlo y que devuelva `null`. +Agrega un campo para introducir una dirección de correo electrónico (clase [TextInput |api:Nette\Forms\Controls\TextInput]). Si el usuario no rellena el campo, devuelve una cadena vacía `''`, o mediante `setNullable()` se puede especificar que devuelva `null`. ```php -$form->addEmail('email', 'Email:'); +$form->addEmail('email', 'E-mail:'); ``` -Verifica que el valor es una dirección de correo electrónico válida. No verifica que el dominio exista realmente, sólo se verifica la sintaxis. Valida automáticamente UTF-8, recorta los espacios en blanco a izquierda y derecha. +Verifica si el valor es una dirección de correo electrónico válida. No se verifica si el dominio realmente existe, solo se verifica la sintaxis. Valida automáticamente UTF-8, recorta los espacios iniciales y finales. -La longitud máxima puede limitarse utilizando `setMaxLength()`. La [función addFilter() |validation#Modifying Input Values] permite cambiar el valor introducido por el usuario. Puede establecer el llamado valor vacío utilizando `setEmptyValue()`. +La longitud máxima se puede limitar mediante `setMaxLength()`. Modificar el valor introducido por el usuario permite [addFilter() |validation#Modificación de la entrada]. Se puede establecer el llamado empty-value mediante `setEmptyValue()`. -addPassword(string|int $name, $label=null): TextInput .[method] -=============================================================== +addPassword(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +====================================================================================================== -Añade campo de contraseña (clase [TextInput |api:Nette\Forms\Controls\TextInput]). +Agrega un campo para introducir una contraseña (clase [TextInput |api:Nette\Forms\Controls\TextInput]). ```php $form->addPassword('password', 'Contraseña:') ->setRequired() ->addRule($form::MinLength, 'La contraseña debe tener al menos %d caracteres', 8) - ->addRule($form::Pattern, 'La contraseña debe contener un número', '.*[0-9].*'); + ->addRule($form::Pattern, 'Debe contener un dígito', '.*[0-9].*'); ``` -Al reenviar el formulario, la entrada estará en blanco. Valida automáticamente UTF-8, recorta los espacios en blanco a izquierda y derecha y elimina los saltos de línea que podría enviar un atacante. +Al volver a mostrar el formulario, el campo estará vacío. Valida automáticamente UTF-8, recorta los espacios iniciales y finales y elimina los saltos de línea que un atacante podría enviar. addCheckbox(string|int $name, $caption=null): Checkbox .[method] ================================================================ -Añade una casilla de verificación (clase [Checkbox |api:Nette\Forms\Controls\Checkbox]). El campo devuelve `true` o `false`, dependiendo de si está marcado. +Agrega una casilla de verificación (clase [Checkbox |api:Nette\Forms\Controls\Checkbox]). Devuelve el valor `true` o `false`, dependiendo de si está marcada. ```php -$form->addCheckbox('agree', 'Estoy de acuerdo con las condiciones') - ->setRequired('Debe aceptar nuestros términos'); +$form->addCheckbox('agree', 'Acepto los términos y condiciones') + ->setRequired('Es necesario aceptar los términos y condiciones'); ``` -addCheckboxList(string|int $name, $label=null, array $items=null): CheckboxList .[method] -========================================================================================= +addCheckboxList(string|int $name, $label=null, ?array $items=null): CheckboxList .[method] +========================================================================================== -Añade una lista de casillas de verificación para seleccionar varios elementos (clase [CheckboxList |api:Nette\Forms\Controls\CheckboxList]). Devuelve el array de claves de los elementos seleccionados. El método `getSelectedItems()` devuelve valores en lugar de claves. +Agrega casillas de verificación para seleccionar múltiples elementos (clase [CheckboxList |api:Nette\Forms\Controls\CheckboxList]). Devuelve un array de claves de los elementos seleccionados. El método `getSelectedItems()` devuelve los valores en lugar de las claves. ```php -$form->addCheckboxList('colors', 'Colors:', [ - 'r' => 'red', - 'g' => 'green', - 'b' => 'blue', +$form->addCheckboxList('colors', 'Colores:', [ + 'r' => 'rojo', + 'g' => 'verde', + 'b' => 'azul', ]); ``` -Pasamos el array de elementos como tercer parámetro, o por el método `setItems()`. +El array de elementos ofrecidos se pasa como tercer parámetro o mediante el método `setItems()`. -Puede utilizar `setDisabled(['r', 'g'])` para desactivar elementos individuales. +Mediante `setDisabled(['r', 'g'])` se pueden desactivar elementos individuales. -El elemento comprueba automáticamente que no ha habido falsificación y que los elementos seleccionados son realmente uno de los ofrecidos y no han sido desactivados. Se puede utilizar el método `getRawValue()` para recuperar los elementos enviados sin esta importante comprobación. +El elemento comprueba automáticamente que no haya habido falsificación y que los elementos seleccionados sean realmente uno de los ofrecidos y no hayan sido desactivados. Mediante el método `getRawValue()` se pueden obtener los elementos enviados sin esta importante comprobación. -Cuando se establecen valores por defecto, también comprueba que son uno de los elementos ofrecidos, de lo contrario lanza una excepción. Esta comprobación puede desactivarse con `checkDefaultValue(false)`. +Al establecer los elementos seleccionados por defecto, también comprueba que sean uno de los ofrecidos, de lo contrario lanza una excepción. Esta comprobación se puede desactivar mediante `checkDefaultValue(false)`. +Si envía el formulario mediante el método `GET`, puede elegir un método de transmisión de datos más compacto que ahorra tamaño de la cadena de consulta. Se activa estableciendo el atributo HTML del formulario: + +```php +$form->setHtmlAttribute('data-nette-compact'); +``` -addRadioList(string|int $name, $label=null, array $items=null): RadioList .[method] -=================================================================================== -Añade botones de radio (clase [RadioList |api:Nette\Forms\Controls\RadioList]). Devuelve la clave del elemento seleccionado, o `null` si el usuario no seleccionó nada. El método `getSelectedItem()` devuelve un valor en lugar de una clave. +addRadioList(string|int $name, $label=null, ?array $items=null): RadioList .[method] +==================================================================================== + +Agrega botones de opción (clase [RadioList |api:Nette\Forms\Controls\RadioList]). Devuelve la clave del elemento seleccionado, o `null` si el usuario no seleccionó nada. El método `getSelectedItem()` devuelve el valor en lugar de la clave. ```php $sex = [ - 'm' => 'male', - 'f' => 'female', + 'm' => 'hombre', + 'f' => 'mujer', ]; -$form->addRadioList('gender', 'Gender:', $sex); +$form->addRadioList('gender', 'Género:', $sex); ``` -Pasamos el array de elementos como tercer parámetro, o por el método `setItems()`. +El array de elementos ofrecidos se pasa como tercer parámetro o mediante el método `setItems()`. -Puede utilizar `setDisabled(['m'])` para desactivar elementos individuales. +Mediante `setDisabled(['m', 'f'])` se pueden desactivar elementos individuales. -El elemento comprueba automáticamente que no ha habido falsificación y que el elemento seleccionado es realmente uno de los ofrecidos y no ha sido desactivado. Se puede utilizar el método `getRawValue()` para recuperar el elemento presentado sin esta importante comprobación. +El elemento comprueba automáticamente que no haya habido falsificación y que el elemento seleccionado sea realmente uno de los ofrecidos y no haya sido desactivado. Mediante el método `getRawValue()` se puede obtener el elemento enviado sin esta importante comprobación. -Cuando se establece el valor por defecto, también comprueba que es uno de los elementos ofrecidos, de lo contrario lanza una excepción. Esta comprobación puede desactivarse con `checkDefaultValue(false)`. +Al establecer el elemento seleccionado por defecto, también comprueba que sea uno de los ofrecidos, de lo contrario lanza una excepción. Esta comprobación se puede desactivar mediante `checkDefaultValue(false)`. -addSelect(string|int $name, $label=null, array $items=null): SelectBox .[method] -================================================================================ +addSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): SelectBox .[method] +================================================================================================== -Añade una caja de selección (clase [SelectBox |api:Nette\Forms\Controls\SelectBox]). Devuelve la clave del elemento seleccionado, o `null` si el usuario no seleccionó nada. El método `getSelectedItem()` devuelve un valor en lugar de una clave. +Agrega un cuadro de selección (clase [SelectBox |api:Nette\Forms\Controls\SelectBox]). Devuelve la clave del elemento seleccionado, o `null` si el usuario no seleccionó nada. El método `getSelectedItem()` devuelve el valor en lugar de la clave. ```php $countries = [ - 'CZ' => 'Czech republic', - 'SK' => 'Slovakia', - 'GB' => 'United Kingdom', + 'ES' => 'España', + 'MX' => 'México', + 'AR' => 'Argentina', ]; -$form->addSelect('country', 'Country:', $countries) - ->setDefaultValue('SK'); +$form->addSelect('country', 'País:', $countries) + ->setDefaultValue('MX'); ``` -Pasamos la matriz de elementos como tercer parámetro, o mediante el método `setItems()`. La matriz de elementos también puede ser bidimensional: +El array de elementos ofrecidos se pasa como tercer parámetro o mediante el método `setItems()`. Los elementos también pueden ser un array bidimensional: ```php $countries = [ - 'Europe' => [ - 'CZ' => 'Czech republic', - 'SK' => 'Slovakia', - 'GB' => 'United Kingdom', + 'Europa' => [ + 'ES' => 'España', + 'FR' => 'Francia', + 'DE' => 'Alemania', ], - 'CA' => 'Canada', - 'US' => 'USA', - '?' => 'other', + 'CA' => 'Canadá', + 'MX' => 'México', + '?' => 'otro', ]; ``` -Para las cajas de selección, el primer elemento a menudo tiene un significado especial, sirve como una llamada a la acción. Utilice el método `setPrompt()` para añadir una entrada de este tipo. +En los cuadros de selección, a menudo el primer elemento tiene un significado especial, sirve como llamada a la acción. Para agregar tal elemento, se utiliza el método `setPrompt()`. ```php -$form->addSelect('country', 'Country:', $countries) - ->setPrompt('Pick a country'); +$form->addSelect('country', 'País:', $countries) + ->setPrompt('Seleccione un país'); ``` -Puede utilizar `setDisabled(['CZ', 'SK'])` para desactivar elementos individuales. +Mediante `setDisabled(['ES', 'FR'])` se pueden desactivar elementos individuales. -El elemento comprueba automáticamente que no ha habido falsificación y que el elemento seleccionado es realmente uno de los ofrecidos y no ha sido desactivado. Se puede utilizar el método `getRawValue()` para recuperar el elemento presentado sin esta importante comprobación. +El elemento comprueba automáticamente que no haya habido falsificación y que el elemento seleccionado sea realmente uno de los ofrecidos y no haya sido desactivado. Mediante el método `getRawValue()` se puede obtener el elemento enviado sin esta importante comprobación. -Cuando se establece el valor por defecto, también comprueba que es uno de los elementos ofrecidos, de lo contrario lanza una excepción. Esta comprobación puede desactivarse con `checkDefaultValue(false)`. +Al establecer el elemento seleccionado por defecto, también comprueba que sea uno de los ofrecidos, de lo contrario lanza una excepción. Esta comprobación se puede desactivar mediante `checkDefaultValue(false)`. -addMultiSelect(string|int $name, $label=null, array $items=null): MultiSelectBox .[method] -========================================================================================== +addMultiSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): MultiSelectBox .[method] +============================================================================================================ -Añade una caja de selección multielección (clase [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox]). Devuelve el array de claves de los elementos seleccionados. El método `getSelectedItems()` devuelve valores en lugar de claves. +Agrega un cuadro de selección para elegir múltiples elementos (clase [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox]). Devuelve un array de claves de los elementos seleccionados. El método `getSelectedItems()` devuelve los valores en lugar de las claves. ```php -$form->addMultiSelect('countries', 'Countries:', $countries); +$form->addMultiSelect('countries', 'Países:', $countries); ``` -Pasamos la matriz de elementos como tercer parámetro, o mediante el método `setItems()`. La matriz de elementos también puede ser bidimensional. +El array de elementos ofrecidos se pasa como tercer parámetro o mediante el método `setItems()`. Los elementos también pueden ser un array bidimensional. -Puede utilizar `setDisabled(['CZ', 'SK'])` para desactivar elementos individuales. +Mediante `setDisabled(['ES', 'FR'])` se pueden desactivar elementos individuales. -El elemento comprueba automáticamente que no ha habido falsificación y que los elementos seleccionados son realmente uno de los ofrecidos y no han sido desactivados. Se puede utilizar el método `getRawValue()` para recuperar los elementos enviados sin esta importante comprobación. +El elemento comprueba automáticamente que no haya habido falsificación y que los elementos seleccionados sean realmente uno de los ofrecidos y no hayan sido desactivados. Mediante el método `getRawValue()` se pueden obtener los elementos enviados sin esta importante comprobación. -Cuando se establecen valores por defecto, también comprueba que son uno de los elementos ofrecidos, de lo contrario lanza una excepción. Esta comprobación puede desactivarse con `checkDefaultValue(false)`. +Al establecer los elementos seleccionados por defecto, también comprueba que sean uno de los ofrecidos, de lo contrario lanza una excepción. Esta comprobación se puede desactivar mediante `checkDefaultValue(false)`. addUpload(string|int $name, $label=null): UploadControl .[method] ================================================================= -Añade el campo de subida de ficheros (clase [UploadControl |api:Nette\Forms\Controls\UploadControl]). Devuelve el objeto [FileUpload |http:request#FileUpload], incluso si el usuario no ha subido un archivo, lo cual puede averiguarse mediante el método `FileUpload::hasFile()`. +Agrega un campo para subir un archivo (clase [UploadControl |api:Nette\Forms\Controls\UploadControl]). Devuelve un objeto [FileUpload |http:request#FileUpload] incluso si el usuario no envió ningún archivo, lo cual se puede verificar con el método `FileUpload::hasFile()`. ```php $form->addUpload('avatar', 'Avatar:') - ->addRule($form::Image, 'Avatar must be JPEG, PNG, GIF or WebP') - ->addRule($form::MaxFileSize, 'Maximum size is 1 MB', 1024 * 1024); + ->addRule($form::Image, 'El avatar debe ser JPEG, PNG, GIF, WebP o AVIF.') + ->addRule($form::MaxFileSize, 'El tamaño máximo es 1 MB.', 1024 * 1024); ``` -Si el fichero no se ha subido correctamente, el formulario no se ha enviado correctamente y se muestra un error. Es decir, no es necesario comprobar el método `FileUpload::isOk()`. +Si el archivo no se sube correctamente, el formulario no se envía con éxito y se muestra un error. Es decir, en caso de envío exitoso, no es necesario verificar el método `FileUpload::isOk()`. -No confíe en el nombre de archivo original devuelto por el método `FileUpload::getName()`, un cliente podría enviar un nombre de archivo malicioso con la intención de corromper o hackear su aplicación. +Nunca confíe en el nombre original del archivo devuelto por el método `FileUpload::getName()`, el cliente podría haber enviado un nombre de archivo malicioso con la intención de dañar o hackear su aplicación. -Las reglas `MimeType` y `Image` detectan el tipo de archivo o imagen requerido por su firma. No se comprueba la integridad de todo el archivo. Puedes averiguar si una imagen no está corrupta, por ejemplo, intentando [cargarla |http:request#toImage]. +Las reglas `MimeType` e `Image` detectan el tipo requerido basándose en la firma del archivo y no verifican su integridad. Si la imagen está dañada se puede verificar, por ejemplo, intentando [cargarla |http:request#toImage]. addMultiUpload(string|int $name, $label=null): UploadControl .[method] ====================================================================== -Añade un campo de carga múltiple de archivos (clase [UploadControl |api:Nette\Forms\Controls\UploadControl]). Devuelve un array de objetos [FileUpload |http:request#FileUpload]. El método `FileUpload::hasFile()` devolverá `true` para cada uno de ellos. +Agrega un campo para subir múltiples archivos a la vez (clase [UploadControl |api:Nette\Forms\Controls\UploadControl]). Devuelve un array de objetos [FileUpload |http:request#FileUpload]. El método `FileUpload::hasFile()` en cada uno de ellos devolverá `true`. ```php -$form->addMultiUpload('files', 'Files:') - ->addRule($form::MaxLength, 'A maximum of %d files can be uploaded', 10); +$form->addMultiUpload('files', 'Archivos:') + ->addRule($form::MaxLength, 'Se pueden subir como máximo %d archivos', 10); ``` -Si uno de los archivos no se carga correctamente, el formulario no se ha enviado correctamente y se muestra un error. Es decir, no es necesario comprobar el método `FileUpload::isOk()`. +Si alguno de los archivos no se sube correctamente, el formulario no se envía con éxito y se muestra un error. Es decir, en caso de envío exitoso, no es necesario verificar el método `FileUpload::isOk()`. + +Nunca confíe en los nombres originales de los archivos devueltos por el método `FileUpload::getName()`, el cliente podría haber enviado un nombre de archivo malicioso con la intención de dañar o hackear su aplicación. + +Las reglas `MimeType` e `Image` detectan el tipo requerido basándose en la firma del archivo y no verifican su integridad. Si la imagen está dañada se puede verificar, por ejemplo, intentando [cargarla |http:request#toImage]. + -No confíe en los nombres de archivo originales devueltos por el método `FileUpload::getName()`, un cliente podría enviar un nombre de archivo malicioso con la intención de corromper o piratear su aplicación. +addDate(string|int $name, $label=null): DateTimeControl .[method]{data-version:3.1.14} +====================================================================================== -Las reglas `MimeType` y `Image` detectan el tipo de archivo o imagen requerido por su firma. No se comprueba la integridad de todo el archivo. Puedes averiguar si una imagen no está corrupta, por ejemplo, intentando [cargarla |http:request#toImage]. +Agrega un campo que permite al usuario introducir fácilmente una fecha compuesta por año, mes y día (clase [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). +Como valor predeterminado, acepta objetos que implementan la interfaz `DateTimeInterface`, una cadena con la hora o un número que representa un timestamp UNIX. Lo mismo se aplica a los argumentos de las reglas `Min`, `Max` o `Range`, que definen la fecha mínima y máxima permitida. -addHidden(string|int $name, string $default=null): HiddenField .[method] -======================================================================== +```php +$form->addDate('date', 'Fecha:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'La fecha debe tener al menos un mes de antigüedad.', new DateTime('-1 month')); +``` -Añade un campo oculto (clase [HiddenField |api:Nette\Forms\Controls\HiddenField]). +Por defecto, devuelve un objeto `DateTimeImmutable`, con el método `setFormat()` puede especificar el [formato de texto|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] o timestamp: + +```php +$form->addDate('date', 'Fecha:') + ->setFormat('Y-m-d'); +``` + + +addTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=============================================================================================================== + +Agrega un campo que permite al usuario introducir fácilmente una hora compuesta por horas, minutos y opcionalmente segundos (clase [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Como valor predeterminado, acepta objetos que implementan la interfaz `DateTimeInterface`, una cadena con la hora o un número que representa un timestamp UNIX. De estas entradas, solo se utiliza la información de la hora, la fecha se ignora. Lo mismo se aplica a los argumentos de las reglas `Min`, `Max` o `Range`, que definen la hora mínima y máxima permitida. Si el valor mínimo establecido es mayor que el máximo, se crea un rango de tiempo que cruza la medianoche. + +```php +$form->addTime('time', 'Hora:', withSeconds: true) + ->addRule($form::Range, 'La hora debe estar en el rango de %d a %d.', ['12:30', '13:30']); +``` + +Por defecto, devuelve un objeto `DateTimeImmutable` (con la fecha 1 de enero del año 1), con el método `setFormat()` puede especificar el [formato de texto|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters]: + +```php +$form->addTime('time', 'Hora:') + ->setFormat('H:i'); +``` + + +addDateTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=================================================================================================================== + +Agrega un campo que permite al usuario introducir fácilmente una fecha y hora compuesta por año, mes, día, horas, minutos y opcionalmente segundos (clase [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Como valor predeterminado, acepta objetos que implementan la interfaz `DateTimeInterface`, una cadena con la hora o un número que representa un timestamp UNIX. Lo mismo se aplica a los argumentos de las reglas `Min`, `Max` o `Range`, que definen la fecha mínima y máxima permitida. + +```php +$form->addDateTime('datetime', 'Fecha y hora:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'La fecha debe tener al menos un mes de antigüedad.', new DateTime('-1 month')); +``` + +Por defecto, devuelve un objeto `DateTimeImmutable`, con el método `setFormat()` puede especificar el [formato de texto|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] o timestamp: + +```php +$form->addDateTime('datetime') + ->setFormat(DateTimeControl::FormatTimestamp); +``` + + +addColor(string|int $name, $label=null): ColorPicker .[method]{data-version:3.1.14} +=================================================================================== + +Agrega un campo para seleccionar un color (clase [ColorPicker |api:Nette\Forms\Controls\ColorPicker]). El color es una cadena en formato `#rrggbb`. Si el usuario no realiza la selección, se devuelve el color negro `#000000`. + +```php +$form->addColor('color', 'Color:') + ->setDefaultValue('#3C8ED7'); +``` + + +addHidden(string|int $name, ?string $default=null): HiddenField .[method] +========================================================================= + +Agrega un campo oculto (clase [HiddenField |api:Nette\Forms\Controls\HiddenField]). ```php $form->addHidden('userid'); ``` -Utiliza `setNullable()` para cambiarlo y que devuelva `null` en lugar de una cadena vacía. La [función addFilter() |validation#Modifying Input Values] permite cambiar el valor enviado. +Mediante `setNullable()` se puede establecer que devuelva `null` en lugar de una cadena vacía. Modificar el valor enviado permite [addFilter() |validation#Modificación de la entrada]. + +Aunque el elemento está oculto, es **importante tener en cuenta** que el valor aún puede ser modificado o falsificado por un atacante. Siempre verifique y valide minuciosamente todos los valores recibidos en el lado del servidor para prevenir riesgos de seguridad asociados con la manipulación de datos. addSubmit(string|int $name, $caption=null): SubmitButton .[method] ================================================================== -Añade un botón de envío (clase [SubmitButton |api:Nette\Forms\Controls\SubmitButton]). +Agrega un botón de envío (clase [SubmitButton |api:Nette\Forms\Controls\SubmitButton]). ```php -$form->addSubmit('submit', 'Register'); +$form->addSubmit('submit', 'Enviar'); ``` -Es posible tener más de un botón de envío en el formulario: +En el formulario es posible tener múltiples botones de envío: ```php -$form->addSubmit('register', 'Register'); -$form->addSubmit('cancel', 'Cancel'); +$form->addSubmit('register', 'Registrar'); +$form->addSubmit('cancel', 'Cancelar'); ``` -Para saber cuál de ellos fue pulsado, utilice: +Para determinar en cuál de ellos se hizo clic, use: ```php if ($form['register']->isSubmittedBy()) { @@ -282,44 +377,44 @@ if ($form['register']->isSubmittedBy()) { } ``` -Si no desea validar el formulario cuando se pulsa un botón de envío (como los botones *Cancelar* o *Preview*), puede desactivarlo con [setValidationScope() |validation#Disabling Validation]. +Si no desea validar todo el formulario al presionar un botón (por ejemplo, en los botones *Cancelar* o *Vista previa*), use [setValidationScope() |validation#Desactivación de la validación]. addButton(string|int $name, $caption): Button .[method] ======================================================= -Añade un botón (clase [Button |api:Nette\Forms\Controls\Button]) sin función de envío. Es útil para vincular otra funcionalidad a id, por ejemplo una acción JavaScript. +Agrega un botón (clase [Button |api:Nette\Forms\Controls\Button]) que no tiene función de envío. Por lo tanto, se puede usar para alguna otra función, por ejemplo, llamar a una función JavaScript al hacer clic. ```php -$form->addButton('raise', 'Raise salary') +$form->addButton('raise', 'Aumentar salario') ->setHtmlAttribute('onclick', 'raiseSalary()'); ``` -addImageButton(string|int $name, string $src=null, string $alt=null): ImageButton .[method] -=========================================================================================== +addImageButton(string|int $name, ?string $src=null, ?string $alt=null): ImageButton .[method] +============================================================================================= -Añade un botón de envío en forma de imagen (clase [ImageButton |api:Nette\Forms\Controls\ImageButton]). +Agrega un botón de envío en forma de imagen (clase [ImageButton |api:Nette\Forms\Controls\ImageButton]). ```php -$form->addImageButton('submit', '/path/to/image'); +$form->addImageButton('submit', '/ruta/a/imagen.png'); ``` -Cuando se utilizan varios botones de envío, puede averiguar cuál fue pulsado con `$form['submit']->isSubmittedBy()`. +Al usar múltiples botones de envío, se puede determinar en cuál se hizo clic mediante `$form['submit']->isSubmittedBy()`. addContainer(string|int $name): Container .[method] =================================================== -Añade un subformulario (clase [Container |api:Nette\Forms\Container]), o un contenedor, que puede ser tratado de la misma manera que un formulario. Esto significa que puede utilizar métodos como `setDefaults()` o `getValues()`. +Agrega un subformulario (clase [Container|api:Nette\Forms\Container]), o contenedor, al que se pueden agregar otros elementos de la misma manera que los agregamos al formulario. También funcionan los métodos `setDefaults()` o `getValues()`. ```php $sub1 = $form->addContainer('first'); -$sub1->addText('name', 'Tu nombre:'); +$sub1->addText('name', 'Su nombre:'); $sub1->addEmail('email', 'Email:'); -$sub2 = $form->addContainer('segundo'); -$sub2->addText('name', 'Tu nombre:'); +$sub2 = $form->addContainer('second'); +$sub2->addText('name', 'Su nombre:'); $sub2->addEmail('email', 'Email:'); ``` @@ -339,110 +434,112 @@ Los datos enviados se devuelven como una estructura multidimensional: ``` -Visión general de la configuración .[#toc-overview-of-settings] -=============================================================== +Resumen de configuración +======================== -Para todos los elementos podemos llamar a los siguientes métodos (véase la [documentación de la API |https://api.nette.org/forms/master/Nette/Forms/Controls.html] para una visión completa): +En todos los elementos podemos llamar a los siguientes métodos (resumen completo en la [documentación de la API|https://api.nette.org/forms/master/Nette/Forms/Controls.html]): .[table-form-methods language-php] -| `setDefaultValue($value)` | establece el valor por defecto -| `getValue()` | obtiene el valor actual -| `setOmitted()` | omitir [valores |#omitted values] -| `setDisabled()` | [desactivar entradas |#disabling inputs] +| `setDefaultValue($value)` | establece el valor predeterminado +| `getValue()` | obtener el valor actual +| `setOmitted()` | [#Omisión de valor] +| `setDisabled()` | [#Desactivación de elementos] -Renderización: +Renderizado: .[table-form-methods language-php] -| `setCaption($caption)`| cambia el título del elemento -| `setTranslator($translator)` | establece el [traductor |rendering#translating] -| `setHtmlAttribute($name, $value)` | establece el [atributo |rendering#HTML attributes] HTML del elemento -| `setHtmlId($id)` | establece el atributo HTML `id` -| `setHtmlType($type)` | establece el atributo HTML `type` -| `setHtmlName($name)`| establece el atributo HTML `name` -| `setOption($key, $value)` | establece los [datos de renderizado |rendering#Options] +| `setCaption($caption)` | cambia la etiqueta del elemento +| `setTranslator($translator)` | establece el [traductor |rendering#Traducción] +| `setHtmlAttribute($name, $value)` | establece el [atributo HTML |rendering#Atributos HTML] del elemento +| `setHtmlId($id)` | establece el atributo HTML `id` +| `setHtmlType($type)` | establece el atributo HTML `type` +| `setHtmlName($name)` | establece el atributo HTML `name` +| `setOption($key, $value)` | [configuración para renderizado |rendering#Opciones] Validación: .[table-form-methods language-php] -| `setRequired()` | [Campo obligatorio|validation] -| `addRule()` | [Establecer regla de validación|validation#Rules] -| `addCondition()`, `addConditionOn()` | [Establecer condición de validación |validation#Conditions] -| `addError($message)` | [Pasar mensaje de error|validation#processing-errors] +| `setRequired()` | [elemento obligatorio |validation] +| `addRule()` | establece la [regla de validación |validation#Reglas] +| `addCondition()`, `addConditionOn()` | establece la [condición de validación |validation#Condiciones] +| `addError($message)` | [entrega de mensaje de error |validation#Errores durante el procesamiento] -Los siguientes métodos pueden ser llamados para los elementos `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`: +En los elementos `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` se pueden llamar los siguientes métodos: .[table-form-methods language-php] -| `setNullable()`| establece si getValue() devuelve `null` en lugar de una cadena vacía -| `setEmptyValue($value)` | establece el valor especial que se trata como cadena vacía -| `setMaxLength($length)`| establece el número máximo de caracteres permitidos -| `addFilter($filter)` | [Modificación de los valores de entrada |validation#Modifying Input Values] +| `setNullable()` | establece si getValue() devolverá `null` en lugar de una cadena vacía +| `setEmptyValue($value)` | establece un valor especial que se considera una cadena vacía +| `setMaxLength($length)` | establece el número máximo de caracteres permitidos +| `addFilter($filter)` | [modificación de entrada |validation#Modificación de la entrada] -Valores omitidos .[#toc-omitted-values] ---------------------------------------- +Omisión de valor +================ -Si no nos interesa el valor introducido por el usuario, podemos utilizar `setOmitted()` para omitirlo del resultado proporcionado por el método `$form->getValues​()` o pasado a los manejadores. Esto es adecuado para varias contraseñas de verificación, campos antispam, etc. +Si el valor rellenado por el usuario no nos interesa, podemos omitirlo del resultado del método `$form->getValues()` o de los datos pasados a los handlers mediante `setOmitted()`. Esto es útil para diversas contraseñas de verificación, elementos antispam, etc. ```php -$form->addPassword('passwordVerify', 'Contraseña de nuevo:') - ->setRequired('Rellena tu contraseña de nuevo para comprobar si hay algún error tipográfico') - ->addRule($form::Equal, 'Contraseña incorrecta', $form['password']) +$form->addPassword('passwordVerify', 'Contraseña para verificación:') + ->setRequired('Por favor, introduzca la contraseña de nuevo para verificar') + ->addRule($form::Equal, 'Las contraseñas no coinciden', $form['password']) ->setOmitted(); ``` -Desactivar entradas .[#toc-disabling-inputs] --------------------------------------------- +Desactivación de elementos +========================== -Para desactivar una entrada, puede llamar a `setDisabled()`. Un campo de este tipo no puede ser editado por el usuario. +Los elementos se pueden desactivar mediante `setDisabled()`. Tal elemento no puede ser editado por el usuario. ```php -$form->addText('username', 'User name:') +$form->addText('username', 'Nombre de usuario:') ->setDisabled(); ``` -Tenga en cuenta que el navegador no envía los campos deshabilitados al servidor en absoluto, por lo que ni siquiera los encontrará en los datos devueltos por la función `$form->getValues()`. +Los elementos deshabilitados no son enviados por el navegador al servidor, por lo tanto, tampoco los encontrará en los datos devueltos por la función `$form->getValues()`. Sin embargo, si establece `setOmitted(false)`, Nette incluirá su valor predeterminado en estos datos. -Si establece un valor predeterminado para un campo, debe hacerlo sólo después de desactivarlo: +Al llamar a `setDisabled()`, por razones de seguridad, **se borra el valor del elemento**. Si establece un valor predeterminado, es necesario hacerlo después de desactivarlo: ```php -$form->addText('username', 'User name:') +$form->addText('username', 'Nombre de usuario:') ->setDisabled() ->setDefaultValue($userName); ``` +Una alternativa a los elementos deshabilitados son los elementos con el atributo HTML `readonly`, que el navegador sí envía al servidor. Aunque el elemento es solo de lectura, es **importante tener en cuenta** que su valor aún puede ser modificado o falsificado por un atacante. + -Controles personalizados .[#toc-custom-controls] -================================================ +Elementos personalizados +======================== -Además de la amplia gama de controles de formulario incorporados, puede añadir controles personalizados al formulario como se indica a continuación: +Además de la amplia gama de elementos de formulario incorporados, puede agregar elementos personalizados al formulario de esta manera: ```php -$form->addComponent(new DateInput('Date:'), 'date'); -// alternative syntax: $form['date'] = new DateInput('Date:'); +$form->addComponent(new DateInput('Fecha:'), 'date'); +// sintaxis alternativa: $form['date'] = new DateInput('Fecha:'); ``` .[note] -El formulario es descendiente de la clase [Container | component-model:#Container] y los elementos son descendientes de [Component | component-model:#Component]. +El formulario es descendiente de la clase [Container |component-model:#Container] y los elementos individuales son descendientes de [Component |component-model:#Component]. -Existe una forma de definir nuevos métodos de formulario para añadir elementos personalizados (por ejemplo `$form->addZip()`). Estos son los llamados métodos de extensión. El inconveniente es que las sugerencias de código en los editores no funcionarán para ellos. +Existe una forma de definir nuevos métodos de formulario que sirven para agregar elementos personalizados (por ejemplo, `$form->addZip()`). Se trata de los llamados extension methods. La desventaja es que el autocompletado en los editores no funcionará para ellos. ```php use Nette\Forms\Container; -// adds method addZip(string $name, string $label = null) -Container::extensionMethod('addZip', function (Container $form, string $name, string $label = null) { +// agregamos el método addZip(string $name, ?string $label = null) +Container::extensionMethod('addZip', function (Container $form, string $name, ?string $label = null) { return $form->addText($name, $label) - ->addRule($form::Pattern, 'At least 5 numbers', '[0-9]{5}'); + ->addRule($form::Pattern, 'Al menos 5 números', '[0-9]{5}'); }); -// usage -$form->addZip('zip', 'ZIP code:'); +// uso +$form->addZip('zip', 'Código postal:'); ``` -Campos de bajo nivel .[#toc-low-level-fields] -============================================= +Elementos de bajo nivel +======================= -Para añadir un elemento al formulario, no es necesario llamar a `$form->addXyz()`. En su lugar, los elementos del formulario pueden introducirse exclusivamente en las plantillas. Esto es útil si, por ejemplo, necesita generar elementos dinámicos: +También se pueden usar elementos que escribimos solo en la plantilla y no los agregamos al formulario con alguno de los métodos `$form->addXyz()`. Por ejemplo, si mostramos registros de la base de datos y no sabemos de antemano cuántos habrá ni qué ID tendrán, y queremos mostrar una casilla de verificación o un botón de opción en cada fila, basta con codificarlo en la plantilla: ```latte {foreach $items as $item} @@ -450,13 +547,13 @@ Para añadir un elemento al formulario, no es necesario llamar a `$form->addXyz( {/foreach} ``` -Tras el envío, puede recuperar los valores: +Y después de enviar, obtenemos el valor: ```php $data = $form->getHttpData($form::DataText, 'sel[]'); $data = $form->getHttpData($form::DataText | $form::DataKeys, 'sel[]'); ``` -En el primer parámetro, se especifica el tipo de elemento (`DataFile` para `type=file`, `DataLine` para entradas de una línea como `text`, `password` o `email` y `DataText` para el resto). El segundo parámetro coincide con el atributo HTML `name`. Si necesita conservar las claves, puede combinar el primer parámetro con `DataKeys`. Esto es útil para `select`, `radioList` o `checkboxList`. +donde el primer parámetro es el tipo de elemento (`DataFile` para `type=file`, `DataLine` para entradas de una línea como `text`, `password`, `email`, etc. y `DataText` para todos los demás) y el segundo parámetro `sel[]` corresponde al atributo HTML name. Podemos combinar el tipo de elemento con el valor `DataKeys`, que conserva las claves de los elementos. Esto es especialmente útil para `select`, `radioList` y `checkboxList`. -`getHttpData()` devuelve la entrada desinfectada. En este caso, siempre será un array de cadenas UTF-8 válidas, sin importar el atacante enviado por el formulario. Es una alternativa a trabajar con `$_POST` o `$_GET` directamente si quieres recibir datos seguros. +Lo importante es que `getHttpData()` devuelve un valor sanitizado, en este caso siempre será un array de cadenas UTF-8 válidas, sin importar lo que un atacante intente pasar al servidor. Es análogo al trabajo directo con `$_POST` o `$_GET`, pero con la diferencia esencial de que siempre devuelve datos limpios, tal como está acostumbrado con los elementos estándar de los formularios Nette. diff --git a/forms/es/in-presenter.texy b/forms/es/in-presenter.texy index 2dddcee064..024789f9bc 100644 --- a/forms/es/in-presenter.texy +++ b/forms/es/in-presenter.texy @@ -1,16 +1,16 @@ -Formularios en Presentadores -**************************** +Formularios en presenters +************************* .[perex] -Nette Forms facilita enormemente la creación y el procesamiento de formularios web. En este capítulo, aprenderá a utilizar formularios dentro de los presentadores. +Nette Forms facilita enormemente la creación y el procesamiento de formularios web. En este capítulo, aprenderá a usar formularios dentro de los presenters. -Si estás interesado en utilizarlos de forma completamente autónoma sin el resto del framework, existe una guía para formularios [autónomos |standalone]. +Si le interesa cómo usarlos de forma completamente independiente sin el resto del framework, la guía para [uso independiente|standalone] es para usted. -Primer formulario .[#toc-first-form] -==================================== +Primer formulario +================= -Intentaremos escribir un sencillo formulario de registro. Su código será el siguiente: +Intentemos escribir un formulario de registro simple. Su código será el siguiente: ```php use Nette\Application\UI\Form; @@ -18,19 +18,19 @@ use Nette\Application\UI\Form; $form = new Form; $form->addText('name', 'Nombre:'); $form->addPassword('password', 'Contraseña:'); -$form->addSubmit('send', 'Regístrate'); +$form->addSubmit('send', 'Registrar'); $form->onSuccess[] = [$this, 'formSucceeded']; ``` -y en el navegador el resultado debería verse así: +y en el navegador se mostrará así: -[* form-en.webp *] +[* form-cs.webp *] -El formulario en el presentador es un objeto de la clase `Nette\Application\UI\Form`, su predecesor `Nette\Forms\Form` está pensado para uso independiente. Le añadimos los campos nombre, contraseña y botón de envío. Por último, la línea con `$form->onSuccess` dice que después de la presentación y la validación exitosa, el método `$this->formSucceeded()` debe ser llamado. +El formulario en el presenter es un objeto de la clase `Nette\Application\UI\Form`, su predecesor `Nette\Forms\Form` está destinado a un uso independiente. Le hemos agregado los llamados elementos nombre, contraseña y botón de envío. Y finalmente, la línea con `$form->onSuccess` dice que después del envío y la validación exitosa, se debe llamar al método `$this->formSucceeded()`. -Desde el punto de vista del presentador, el formulario es un componente común. Por lo tanto, se trata como un componente y se incorpora al presentador utilizando [el método factory |application:components#Factory Methods]. Tendrá el siguiente aspecto: +Desde el punto de vista del presenter, el formulario es un componente común. Por lo tanto, se trata como un componente y lo incorporamos al presenter mediante un [método de fábrica |application:components#Métodos de fábrica]. Se verá así: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} use Nette; use Nette\Application\UI\Form; @@ -41,101 +41,98 @@ class HomePresenter extends Nette\Application\UI\Presenter $form = new Form; $form->addText('name', 'Nombre:'); $form->addPassword('password', 'Contraseña:'); - $form->addSubmit('send', 'Regístrate'); + $form->addSubmit('send', 'Registrar'); $form->onSuccess[] = [$this, 'formSucceeded']; return $form; } public function formSucceeded(Form $form, $data): void { - // aquí procesaremos los datos enviados por el formulario - // $data->name contiene nombre - // $data->password contiene contraseña - $this->flashMessage('You have successfully signed up.'); + // aquí procesamos los datos enviados por el formulario + // $data->name contiene el nombre + // $data->password contiene la contraseña + $this->flashMessage('Ha sido registrado exitosamente.'); $this->redirect('Home:'); } } ``` -Y el renderizado en la plantilla se realiza utilizando la etiqueta `{control}`: +Y en la plantilla renderizamos el formulario con la etiqueta `{control}`: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} <h1>Registro</h1> {control registrationForm} ``` -Y eso es todo :-) Tenemos un formulario funcional y perfectamente [seguro |#Vulnerability Protection]. +Y eso es todo :-) Tenemos un formulario funcional y perfectamente [seguro |#Protección contra vulnerabilidades]. -Ahora probablemente estés pensando que ha sido demasiado rápido, preguntándote cómo es posible que se llame al método `formSucceeded()` y qué parámetros obtiene. Claro, tienes razón, esto merece una explicación. +Y ahora probablemente esté pensando que fue demasiado rápido, se pregunta cómo es posible que se llame al método `formSucceeded()` y cuáles son los parámetros que recibe. Ciertamente, tiene razón, esto merece una explicación. -A Nette se le ocurre un mecanismo genial, que llamamos [estilo Hollywood |application:components#Hollywood style]. En lugar de tener que preguntar constantemente si ha pasado algo ("¿se ha enviado el formulario?", "¿se ha enviado válidamente?" o "¿no se ha falsificado?"), le dices al framework "cuando el formulario se complete válidamente, llama a este método" y dejas que siga trabajando en ello. Si programas en JavaScript, estás familiarizado con este estilo de programación. Escribes funciones que son llamadas cuando ocurre un determinado [evento |nette:glossary#Events]. Y el lenguaje les pasa los argumentos apropiados. +Nette presenta un mecanismo fresco que llamamos [Hollywood style |application:components#Estilo Hollywood]. En lugar de que usted, como desarrollador, tenga que preguntar constantemente si algo sucedió ("¿se envió el formulario?", "¿se envió válidamente?" y "¿no fue falsificado?"), le dice al framework "cuando el formulario esté válidamente completado, llama a este método" y deja el trabajo adicional en sus manos. Si programa en JavaScript, este estilo de programación le resultará familiar. Escribe funciones que se llaman cuando ocurre un cierto [evento |nette:glossary#Eventos]. Y el lenguaje les pasa los argumentos apropiados. -Así es como se construye el código del presentador anterior. Array `$form->onSuccess` representa la lista de callbacks PHP que Nette llamará cuando el formulario sea enviado y rellenado correctamente. -Dentro del [ciclo de vida |application:presenters#Life Cycle of Presenter] del presentador es una llamada señal, por lo que son llamadas después del método `action*` y antes del método `render*`. -Y pasa a cada callback el propio formulario en el primer parámetro y los datos enviados como objeto [ArrayHash |utils:arrays#ArrayHash] en el segundo. Puedes omitir el primer parámetro si no necesitas el objeto formulario. El segundo parámetro puede ser aún más útil, pero sobre eso [más adelante |#Mapping to Classes]. +Así es exactamente como está construido el código del presenter anterior. El array `$form->onSuccess` representa una lista de callbacks de PHP que Nette llama en el momento en que el formulario se envía y se completa correctamente (es decir, es válido). Dentro del [ciclo de vida del presenter |application:presenters#Ciclo de vida del presenter], esto es una llamada señal, por lo que se llaman después del método `action*` y antes del método `render*`. Y a cada callback le pasa como primer parámetro el propio formulario y como segundo los datos enviados en forma de objeto [ArrayHash |utils:arrays#ArrayHash]. Puede omitir el primer parámetro si no necesita el objeto del formulario. Y el segundo parámetro puede ser más inteligente, pero hablaremos de eso [más adelante |#Mapeo a clases]. -El objeto `$data` contiene las propiedades `name` y `password` con los datos introducidos por el usuario. Normalmente enviamos los datos directamente para su posterior procesamiento, que puede ser, por ejemplo, su inserción en la base de datos. Sin embargo, puede producirse un error durante el procesamiento, por ejemplo, que el nombre de usuario ya esté ocupado. En este caso, pasamos el error de vuelta al formulario usando `addError()` y dejamos que se redibuje, con un mensaje de error: +El objeto `$data` contiene las claves `name` y `password` con los datos que el usuario completó. Normalmente, enviamos los datos directamente para su posterior procesamiento, que puede ser, por ejemplo, la inserción en una base de datos. Sin embargo, durante el procesamiento puede ocurrir un error, por ejemplo, el nombre de usuario ya está ocupado. En tal caso, devolvemos el error al formulario usando `addError()` y dejamos que se renderice de nuevo, con el mensaje de error. ```php $form->addError('Lo sentimos, el nombre de usuario ya está en uso.'); ``` -Además de `onSuccess`, existe también `onSubmit`: los callbacks son llamados siempre después del envío del formulario, incluso si no se ha rellenado correctamente. Y finalmente `onError`: las llamadas de retorno sólo se activan si el envío no es válido. Incluso son llamados si invalidamos el formulario en `onSuccess` o `onSubmit` usando `addError()`. +Además de `onSuccess`, también existe `onSubmit`: los callbacks se llaman siempre después de enviar el formulario, incluso si no está correctamente completado. Y además `onError`: los callbacks se llaman solo si el envío no es válido. Se llaman incluso si invalidamos el formulario en `onSuccess` o `onSubmit` usando `addError()`. -Después de procesar el formulario, redirigiremos a la página siguiente. Esto evita que el formulario sea reenviado involuntariamente pulsando el botón *refresh*, *back*, o moviendo el historial del navegador. +Después de procesar el formulario, redirigimos a la siguiente página. Esto evita el reenvío no deseado del formulario con el botón *actualizar*, *atrás* o moviéndose en el historial del navegador. -Prueba a añadir más [controles de formulario |controls]. +Intente agregar también otros [elementos de formulario|controls]. -Acceso a los controles .[#toc-access-to-controls] -================================================= +Acceso a los elementos +====================== -El formulario es un componente del presentador, en nuestro caso llamado `registrationForm` (por el nombre del método de fábrica `createComponentRegistrationForm`), por lo que en cualquier parte del presentador se puede acceder al formulario utilizando: +El formulario es un componente del presenter, en nuestro caso llamado `registrationForm` (según el nombre del método de fábrica `createComponentRegistrationForm`), por lo que en cualquier lugar del presenter puede acceder al formulario mediante: ```php $form = $this->getComponent('registrationForm'); // sintaxis alternativa: $form = $this['registrationForm']; ``` -También los controles individuales del formulario son componentes, por lo que puede acceder a ellos de la misma manera: +Los elementos individuales del formulario también son componentes, por lo que puede acceder a ellos de la misma manera: ```php -$input = $form->getComponent('name'); // or $input = $form['name']; -$button = $form->getComponent('send'); // or $button = $form['send']; +$input = $form->getComponent('name'); // o $input = $form['name']; +$button = $form->getComponent('send'); // o $button = $form['send']; ``` -Los controles se eliminan utilizando unset: +Los elementos se eliminan usando unset: ```php unset($form['name']); ``` -Reglas de validación .[#toc-validation-rules] -============================================= +Reglas de validación +==================== -La palabra *valid* se usó varias veces, pero el formulario aún no tiene reglas de validación. Vamos a arreglarlo. +Se mencionó la palabra *válido*, pero el formulario aún no tiene reglas de validación. Vamos a solucionarlo. -El nombre será obligatorio, así que lo marcaremos con el método `setRequired()`, cuyo argumento es el texto del mensaje de error que se mostrará si el usuario no lo rellena. Si no se da ningún argumento, se utilizará el mensaje de error por defecto. +El nombre será obligatorio, por lo que lo marcamos con el método `setRequired()`, cuyo argumento es el texto del mensaje de error que se mostrará si el usuario no completa el nombre. Si no se proporciona el argumento, se utilizará el mensaje de error predeterminado. ```php $form->addText('name', 'Nombre:') - ->setRequired('Por favor, introduzca su nombre.'); + ->setRequired('Por favor, introduzca el nombre'); ``` -Intente enviar el formulario sin el nombre rellenado y verá que se muestra un mensaje de error y el navegador o servidor lo rechazará hasta que lo rellene. +Intente enviar el formulario sin completar el nombre y verá que se muestra un mensaje de error y el navegador o servidor lo rechazará hasta que complete el campo. -Al mismo tiempo, no podrá engañar al sistema escribiendo sólo espacios en la entrada, por ejemplo. De ninguna manera. Nette recorta automáticamente los espacios en blanco a izquierda y derecha. Pruébalo. Es algo que debería hacer siempre con cada entrada de una sola línea, pero a menudo se olvida. Nette lo hace automáticamente. (Puede intentar engañar a los formularios y enviar una cadena multilínea como nombre. Incluso en este caso, Nette no se dejará engañar y los saltos de línea cambiarán a espacios). +Al mismo tiempo, el sistema no se deja engañar si escribe, por ejemplo, solo espacios en el campo. De ninguna manera. Nette elimina automáticamente los espacios iniciales y finales. Pruébelo. Es algo que siempre debería hacer con cada input de una sola línea, pero a menudo se olvida. Nette lo hace automáticamente. (Puede intentar engañar al formulario y enviar una cadena multilínea como nombre. Incluso aquí, Nette no se dejará engañar y cambiará los saltos de línea por espacios.) -El formulario siempre se valida en el lado del servidor, pero también se genera la validación JavaScript, que es rápida y el usuario conoce el error inmediatamente, sin tener que enviar el formulario al servidor. De esto se encarga el script `netteForms.js`. -Insértelo en la plantilla de diseño: +El formulario siempre se valida en el lado del servidor, pero también se genera una validación JavaScript, que se ejecuta instantáneamente y el usuario se entera del error de inmediato, sin necesidad de enviar el formulario al servidor. Esto lo maneja el script `netteForms.js`. Insértelo en la plantilla de layout: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Si mira en el código fuente de la página con formulario, puede observar que Nette inserta los campos obligatorios en elementos con una clase CSS `required`. Pruebe a añadir el siguiente estilo a la plantilla, y la etiqueta "Nombre" será de color rojo. Elegantemente, marcamos los campos obligatorios para los usuarios: +Si mira el código fuente de la página con el formulario, puede notar que Nette inserta los elementos obligatorios en elementos con la clase CSS `required`. Intente agregar la siguiente hoja de estilos a la plantilla y la etiqueta "Nombre" será roja. De esta manera, marcamos elegantemente los elementos obligatorios para los usuarios: ```latte <style> @@ -143,96 +140,96 @@ Si mira en el código fuente de la página con formulario, puede observar que Ne </style> ``` -Se añadirán reglas de validación adicionales mediante el método `addRule()`. El primer parámetro es la regla, el segundo es de nuevo el texto del mensaje de error, y el argumento opcional regla de validación puede seguir. ¿Qué significa esto? +Agregamos otras reglas de validación con el método `addRule()`. El primer parámetro es la regla, el segundo es nuevamente el texto del mensaje de error y puede seguir un argumento de la regla de validación. ¿Qué significa esto? -El formulario recibirá otra entrada opcional *edad* con la condición, que tiene que ser un número (`addInteger()`) y en ciertos límites (`$form::Range`). Y aquí usaremos el tercer argumento de `addRule()`, el propio rango: +Ampliaremos el formulario con un nuevo campo opcional "edad", que debe ser un número entero (`addInteger()`) y además estar en un rango permitido (`$form::Range`). Y aquí es donde usaremos el tercer parámetro del método `addRule()`, con el que pasaremos al validador el rango requerido como un par `[desde, hasta]`: ```php $form->addInteger('age', 'Edad:') - ->addRule($form::Range, 'Debes ser mayor de 18 años y tener menos de 120.', [18, 120]); + ->addRule($form::Range, 'La edad debe estar entre 18 y 120', [18, 120]); ``` .[tip] -Si el usuario no rellena el campo, las reglas de validación no se verificarán, porque el campo es opcional. +Si el usuario no completa el campo, las reglas de validación no se verificarán, ya que el elemento es opcional. -Obviamente hay espacio para una pequeña refactorización. En el mensaje de error y en el tercer parámetro, los números aparecen por duplicado, lo que no es lo ideal. Si estuviéramos creando un [formulario multilingüe |rendering#translating] y el mensaje que contiene los números tuviera que traducirse a varios idiomas, dificultaría el cambio de valores. Por esta razón, se pueden utilizar los caracteres sustitutos `%d`: +Aquí surge espacio para una pequeña refactorización. En el mensaje de error y en el tercer parámetro, los números se indican de forma duplicada, lo cual no es ideal. Si estuviéramos creando [formularios multilingües |rendering#Traducción] y el mensaje que contiene números se tradujera a varios idiomas, dificultaría un posible cambio de valores. Por esta razón, es posible usar los placeholders `%d` y Nette completará los valores: ```php - ->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]); + ->addRule($form::Range, 'La edad debe estar entre %d y %d años', [18, 120]); ``` -Volvamos al campo *contraseña*, hagámoslo *requerido*, y verifiquemos la longitud mínima de la contraseña (`$form::MinLength`), de nuevo utilizando los caracteres sustitutos en el mensaje: +Volvamos al elemento `password`, que también haremos obligatorio y además verificaremos la longitud mínima de la contraseña (`$form::MinLength`), nuevamente usando el placeholder: ```php $form->addPassword('password', 'Contraseña:') - ->setRequired('Elige una contraseña') - ->addRule($form::MinLength, 'Su contraseña debe tener una longitud mínima de %d', 8); + ->setRequired('Elija una contraseña') + ->addRule($form::MinLength, 'La contraseña debe tener al menos %d caracteres', 8); ``` -Añadiremos un campo `passwordVerify` al formulario, donde el usuario introduzca de nuevo la contraseña, para su comprobación. Usando reglas de validación, comprobamos si ambas contraseñas son iguales (`$form::Equal`). Y como argumento damos una referencia a la primera contraseña usando [corchetes |#Access to Controls]: +Agregaremos al formulario otro campo `passwordVerify`, donde el usuario ingresará la contraseña nuevamente, para verificar. Usando reglas de validación, verificaremos si ambas contraseñas son iguales (`$form::Equal`). Y como parámetro, daremos una referencia a la primera contraseña usando [corchetes |#Acceso a los elementos]: ```php -$form->addPassword('passwordVerify', 'Contraseña de nuevo:') - ->setRequired('Rellene de nuevo su contraseña para comprobar si hay algún error tipográfico') - ->addRule($form::Equal, 'Contraseña incorrecta', $form['password']) +$form->addPassword('passwordVerify', 'Contraseña para verificar:') + ->setRequired('Por favor, introduzca la contraseña de nuevo para verificar') + ->addRule($form::Equal, 'Las contraseñas no coinciden', $form['password']) ->setOmitted(); ``` -Usando `setOmitted()`, marcamos un elemento cuyo valor realmente no nos importa y que existe sólo para validación. Su valor no se pasa a `$data`. +Con `setOmitted()`, hemos marcado un elemento cuyo valor en realidad no nos importa y que existe solo con fines de validación. El valor no se pasará a `$data`. -Tenemos un formulario completamente funcional con validación en PHP y JavaScript. Las capacidades de validación de Nette son mucho más amplias, puedes crear condiciones, mostrar y ocultar partes de una página de acuerdo a ellas, etc. Puedes encontrarlo todo en el capítulo sobre validación de [formularios |validation]. +Con esto, tenemos un formulario completamente funcional con validación en PHP y JavaScript. Las capacidades de validación de Nette son mucho más amplias, se pueden crear condiciones, hacer que partes de la página se muestren y oculten según ellas, etc. Todo lo aprenderá en el capítulo sobre [validación de formularios|validation]. -Valores por defecto .[#toc-default-values] -========================================== +Valores predeterminados +======================= -A menudo establecemos valores por defecto para los controles de formulario: +Normalmente establecemos valores predeterminados para los elementos del formulario: ```php -$form->addEmail('email', 'Email') +$form->addEmail('email', 'E-mail') ->setDefaultValue($lastUsedEmail); ``` -A menudo es útil establecer valores por defecto para todos los controles a la vez. Por ejemplo, cuando el formulario se utiliza para editar registros. Leemos el registro de la base de datos y lo establecemos como valores por defecto: +A menudo es útil establecer valores predeterminados para todos los elementos a la vez. Por ejemplo, cuando el formulario se usa para editar registros. Leemos el registro de la base de datos y establecemos los valores predeterminados: ```php //$row = ['name' => 'John', 'age' => '33', /* ... */]; $form->setDefaults($row); ``` -Llama a `setDefaults()` después de definir los controles. +Llame a `setDefaults()` después de definir los elementos. -Representación del formulario .[#toc-rendering-the-form] -======================================================== +Renderizado del formulario +========================== -Por defecto, el formulario se muestra como una tabla. Los controles individuales siguen las pautas básicas de accesibilidad web. Todas las etiquetas se generan como elementos `<label>` y están asociadas a sus entradas; al hacer clic en la etiqueta, el cursor se desplaza a la entrada. +Por defecto, el formulario se renderiza como una tabla. Los elementos individuales cumplen la regla básica de accesibilidad: todas las etiquetas se escriben como `<label>` y están vinculadas al elemento de formulario correspondiente. Al hacer clic en la etiqueta, el cursor aparece automáticamente en el campo del formulario. -Podemos establecer cualquier atributo HTML para cada elemento. Por ejemplo, añadir un marcador de posición: +Podemos establecer cualquier atributo HTML para cada elemento. Por ejemplo, agregar un placeholder: ```php $form->addInteger('age', 'Edad:') - ->setHtmlAttribute('placeholder', 'Por favor, introduzca la edad'); + ->setHtmlAttribute('placeholder', 'Por favor, complete la edad'); ``` -Realmente hay muchas formas de renderizar un formulario, por lo que es un [capítulo dedicado al renderizado |rendering]. +Hay realmente muchas formas de renderizar un formulario, por lo que hay un [capítulo separado dedicado al renderizado|rendering]. -Mapeo a Clases .[#toc-mapping-to-classes] -========================================= +Mapeo a clases +============== -Volvamos al método `formSucceeded()`, que en el segundo parámetro `$data` obtiene los datos enviados como un objeto `ArrayHash`. Al tratarse de una clase genérica, algo así como `stdClass`, careceremos de algunas comodidades a la hora de trabajar con ella, como la compleción de código para las propiedades en editores o el análisis estático de código. Esto podría solucionarse teniendo una clase específica para cada formulario, cuyas propiedades representen los controles individuales. Ej: +Volvamos al método `formSucceeded()`, que en el segundo parámetro `$data` recibe los datos enviados como un objeto `ArrayHash`. Dado que es una clase genérica, algo así como `stdClass`, nos faltará cierta comodidad al trabajar con ella, como el autocompletado de propiedades en los editores o el análisis estático de código. Esto podría resolverse teniendo una clase específica para cada formulario, cuyas propiedades representen los elementos individuales. Por ejemplo: ```php class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -A partir de PHP 8.0, puedes usar esta elegante notación que usa un constructor: +Alternativamente, puede usar el constructor: ```php class RegistrationFormData @@ -246,27 +243,29 @@ class RegistrationFormData } ``` -¿Cómo decirle a Nette que devuelva datos como objetos de esta clase? Más fácil de lo que piensa. Todo lo que tiene que hacer es especificar la clase como tipo del parámetro `$data` en el manejador: +Las propiedades de la clase de datos también pueden ser enums y se mapearán automáticamente. .{data-version:3.2.4} + +¿Cómo decirle a Nette que nos devuelva los datos como objetos de esta clase? Más fácil de lo que piensa. Simplemente especifique la clase como el tipo del parámetro `$data` en el método manejador: ```php public function formSucceeded(Form $form, RegistrationFormData $data): void { - // $name es una instancia de RegistrationFormData + // $data es una instancia de RegistrationFormData $name = $data->name; // ... } ``` -También puedes especificar `array` como tipo y entonces pasará los datos como un array. +Como tipo también se puede especificar `array` y luego los datos se pasarán como un array. -De manera similar, puede utilizar el método `getValues()`, que pasamos como nombre de clase u objeto a hidratar como parámetro: +De manera similar, también se puede usar la función `getValues()`, a la que pasamos el nombre de la clase o el objeto a hidratar como parámetro: ```php $data = $form->getValues(RegistrationFormData::class); $name = $data->name; ``` -Si los formularios consisten en una estructura multinivel compuesta por contenedores, cree una clase distinta para cada uno de ellos: +Si los formularios forman una estructura multinivel compuesta por contenedores, cree una clase separada para cada uno: ```php $form = new Form; @@ -283,32 +282,34 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` -El mapeo entonces sabe por el tipo de propiedad `$person` que debe mapear el contenedor a la clase `PersonFormData`. Si la propiedad contiene una matriz de contenedores, proporcione el tipo `array` y pase la clase que debe asignarse directamente al contenedor: +El mapeo entonces, a partir del tipo de la propiedad `$person`, sabe que debe mapear el contenedor a la clase `PersonFormData`. Si la propiedad contuviera un array de contenedores, especifique el tipo `array` y pase la clase para el mapeo directamente al contenedor: ```php $person->setMappedType(PersonFormData::class); ``` +Puede hacer que el diseño de la clase de datos del formulario se genere usando el método `Nette\Forms\Blueprint::dataClass($form)`, que lo imprimirá en la página del navegador. Luego, simplemente haga clic para seleccionar el código y cópielo en su proyecto. .{data-version:3.1.15} + -Botones de envío múltiples .[#toc-multiple-submit-buttons] -========================================================== +Múltiples botones +================= -Si el formulario tiene más de un botón, usualmente necesitamos distinguir cuál fue presionado. Podemos crear una función propia para cada botón. Establecerlo como un controlador para el [evento |nette:glossary#Events] `onClick`: +Si el formulario tiene más de un botón, generalmente necesitamos distinguir cuál de ellos fue presionado. Podemos crear nuestra propia función de manejo para cada botón. La establecemos como handler para el [evento |nette:glossary#Eventos] `onClick`: ```php -$form->addSubmit('save', 'Save') +$form->addSubmit('save', 'Guardar') ->onClick[] = [$this, 'saveButtonPressed']; -$form->addSubmit('delete', 'Delete') +$form->addSubmit('delete', 'Eliminar') ->onClick[] = [$this, 'deleteButtonPressed']; ``` -Estos manejadores también son llamados sólo en el caso de que el formulario sea válido, como en el caso del evento `onSuccess`. La diferencia es que el primer parámetro puede ser el objeto submit button en lugar del formulario, dependiendo del tipo que especifiques: +Estos handlers se llaman solo en caso de un formulario válidamente completado, al igual que en el caso del evento `onSuccess`. La diferencia es que como primer parámetro, en lugar del formulario, se puede pasar el botón de envío, dependiendo del tipo que especifique: ```php public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) @@ -318,62 +319,61 @@ public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) } ``` -Cuando un formulario se envía con la tecla <kbd>Intro</kbd>, se trata como si se hubiera enviado con el primer botón. +Cuando el formulario se envía con la tecla <kbd>Enter</kbd>, se considera como si se hubiera enviado con el primer botón. -Evento onAnchor .[#toc-event-onanchor] -====================================== +Evento onAnchor +=============== -Cuando construyes un formulario en un método de fábrica (como `createComponentRegistrationForm`), aún no sabe si ha sido enviado o los datos con los que fue enviado. Pero hay casos en los que necesitamos conocer los valores enviados, quizás dependa de ellos el aspecto que tendrá el formulario, o se utilicen para selectboxes dependientes, etc. +Cuando construimos el formulario en el método de fábrica (como `createComponentRegistrationForm`), este aún no sabe si fue enviado, ni con qué datos. Pero hay casos en los que necesitamos conocer los valores enviados, por ejemplo, si la forma posterior del formulario depende de ellos, o si los necesitamos para select boxes dependientes, etc. -Por lo tanto, se puede hacer que el código que construye el formulario sea llamado cuando está anclado, es decir, ya está vinculado al presentador y conoce sus datos enviados. Pondremos tal código en el array `$onAnchor`: +Por lo tanto, puede dejar que parte del código que construye el formulario se llame solo en el momento en que está, por así decirlo, anclado, es decir, ya está conectado al presenter y conoce sus datos enviados. Pasamos dicho código al array `$onAnchor`: ```php -$country = $form->addSelect('country', 'Country:', $this->model->getCountries()); -$city = $form->addSelect('city', 'City:'); +$country = $form->addSelect('country', 'Estado:', $this->model->getCountries()); +$city = $form->addSelect('city', 'Ciudad:'); $form->onAnchor[] = function () use ($country, $city) { - // esta función será llamada cuando el formulario conozca los datos con los que fue enviado - // por lo que puede utilizar el método getValue() + // esta función se llamará solo cuando el formulario sepa si fue enviado y con qué datos + // por lo tanto, se puede usar el método getValue() $val = $country->getValue(); $city->setItems($val ? $this->model->getCities($val) : []); }; ``` -Protección contra vulnerabilidades .[#toc-vulnerability-protection] -=================================================================== +Protección contra vulnerabilidades +================================== -Nette Framework pone un gran esfuerzo en ser seguro y dado que los formularios son la entrada más común del usuario, los formularios de Nette son tan buenos como impenetrables. Todo se mantiene de forma dinámica y transparente, no hay que configurar nada manualmente. +Nette Framework pone gran énfasis en la seguridad y, por lo tanto, se preocupa meticulosamente por la buena seguridad de los formularios. Lo hace de forma totalmente transparente y no requiere configurar nada manualmente. -Además de proteger los formularios contra ataques dirigidos a vulnerabilidades bien conocidas como [Cross-Site Scripting (XSS) |nette:glossary#cross-site-scripting-xss] y [Cross-Site Request Forgery (CSRF) |nette:glossary#cross-site-request-forgery-csrf], hace un montón de pequeñas tareas de seguridad en las que ya no tienes que pensar. +Además de proteger los formularios contra ataques [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] y [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF], realiza muchas pequeñas protecciones en las que ya no tiene que pensar. -Por ejemplo, filtra todos los caracteres de control de las entradas y comprueba la validez de la codificación UTF-8, para que los datos del formulario estén siempre limpios. En el caso de las casillas de selección y las listas de radio, verifica que los elementos seleccionados sean realmente de los ofrecidos y que no haya habido ninguna falsificación. Ya hemos mencionado que para la entrada de texto de una sola línea, elimina los caracteres de final de línea que un atacante podría enviar allí. Para entradas multilínea, normaliza los caracteres de final de línea. Y así sucesivamente. +Por ejemplo, filtra todos los caracteres de control de las entradas y verifica la validez de la codificación UTF-8, por lo que los datos del formulario siempre estarán limpios. En los select boxes y radio lists, verifica que los elementos seleccionados fueran realmente de los ofrecidos y que no hubo falsificación. Ya mencionamos que en las entradas de texto de una sola línea, elimina los caracteres de fin de línea que un atacante podría haber enviado. En las entradas multilínea, normaliza los caracteres de fin de línea. Y así sucesivamente. -Nette soluciona para usted vulnerabilidades de seguridad que la mayoría de los programadores no tienen ni idea de que existen. +Nette resuelve por usted los riesgos de seguridad que muchos programadores ni siquiera saben que existen. -El mencionado ataque CSRF consiste en que un atacante atrae a la víctima para que visite una página que ejecuta silenciosamente una petición en el navegador de la víctima al servidor donde la víctima está conectada en ese momento, y el servidor cree que la petición ha sido realizada por la víctima a voluntad. Por lo tanto, Nette impide que el formulario sea enviado vía POST desde otro dominio. Si por alguna razón desea desactivar la protección y permitir que el formulario sea enviado desde otro dominio, utilice: +El ataque CSRF mencionado consiste en que un atacante atrae a la víctima a una página que ejecuta discretamente una petición en el navegador de la víctima al servidor en el que la víctima ha iniciado sesión, y el servidor cree que la petición fue realizada por la víctima por su propia voluntad. Por lo tanto, Nette evita el envío de formularios POST desde otro dominio. Si por alguna razón desea desactivar la protección y permitir el envío de formularios desde otro dominio, use: ```php $form->allowCrossOrigin(); // ¡ATENCIÓN! ¡Desactiva la protección! ``` -Esta protección utiliza una cookie de SameSite llamada `_nss`. La protección de cookies SameSite puede no ser 100% fiable, por lo que es una buena idea activar la protección de token: +Esta protección utiliza una cookie SameSite llamada `_nss`. La protección mediante cookie SameSite puede no ser 100% confiable, por lo que es recomendable activar también la protección mediante token: ```php $form->addProtection(); ``` -Se recomienda encarecidamente aplicar esta protección a los formularios en una parte administrativa de su aplicación que cambie datos sensibles. El framework protege contra un ataque CSRF generando y validando el token de autenticación que se almacena en una sesión (el argumento es el mensaje de error que se muestra si el token ha caducado). Por eso es necesario tener una sesión iniciada antes de mostrar el formulario. En la parte de administración del sitio web, la sesión suele estar ya iniciada, debido al login del usuario. -En caso contrario, inicie la sesión con el método `Nette\Http\Session::start()`. +Recomendamos proteger de esta manera los formularios en la parte administrativa del sitio web que modifican datos sensibles en la aplicación. El framework se defiende contra el ataque CSRF generando y verificando un token de autorización que se almacena en la sesión. Por lo tanto, es necesario tener una sesión abierta antes de mostrar el formulario. En la parte administrativa del sitio web, la sesión generalmente ya está iniciada debido al inicio de sesión del usuario. De lo contrario, inicie la sesión con el método `Nette\Http\Session::start()`. -Uso de un formulario en varios presentadores .[#toc-using-one-form-in-multiple-presenters] -========================================================================================== +Mismo formulario en múltiples presenters +======================================== -Si necesitas utilizar un formulario en más de un presentador, te recomendamos que crees una fábrica para él, que luego pasarás al presentador. Una ubicación adecuada para una clase de este tipo es, por ejemplo, el directorio `app/Forms`. +Si necesita usar un formulario en múltiples presenters, recomendamos crear una fábrica para él, que luego pasará al presenter. Una ubicación adecuada para tal clase es, por ejemplo, el directorio `app/Forms`. -La clase de fábrica podría tener el siguiente aspecto: +La clase de fábrica podría verse así: ```php use Nette\Application\UI\Form; @@ -383,14 +383,14 @@ class SignInFormFactory public function create(): Form { $form = new Form; - $form->addText('name', 'Name:'); - $form->addSubmit('send', 'Log in'); + $form->addText('name', 'Nombre:'); + $form->addSubmit('send', 'Iniciar sesión'); return $form; } } ``` -Pedimos a la clase que produzca el formulario en el método de fábrica para componentes en el presentador: +Solicitamos a la clase que fabrique el formulario en el método de fábrica de componentes en el presenter: ```php public function __construct( @@ -401,14 +401,14 @@ public function __construct( protected function createComponentSignInForm(): Form { $form = $this->formFactory->create(); - // podemos cambiar el formulario, aquí por ejemplo cambiamos la etiqueta del botón - $form['login']->setCaption('Continue'); - $form->onSuccess[] = [$this, 'signInFormSubmitted']; // y añadimos el manejador + // podemos modificar el formulario, aquí por ejemplo cambiamos la etiqueta del botón + $form['send']->setCaption('Continuar'); + $form->onSuccess[] = [$this, 'signInFormSucceeded']; // y agregamos el handler return $form; } ``` -El manejador de procesamiento del formulario también puede ser entregado desde la fábrica: +El handler para procesar el formulario también puede ser proporcionado desde la fábrica: ```php use Nette\Application\UI\Form; @@ -418,14 +418,14 @@ class SignInFormFactory public function create(): Form { $form = new Form; - $form->addText('name', 'Name:'); - $form->addSubmit('send', 'Log in'); + $form->addText('name', 'Nombre:'); + $form->addSubmit('send', 'Iniciar sesión'); $form->onSuccess[] = function (Form $form, $data): void { - // procesamos aquí el formulario enviado + // aquí realizamos el procesamiento del formulario }; return $form; } } ``` -Así, tenemos una rápida introducción a los formularios en Nette. Intenta buscar en el directorio de [ejemplos |https://github.com/nette/forms/tree/master/examples] en la distribución para más inspiración. +Bien, hemos tenido una rápida introducción a los formularios en Nette. Intente mirar también en el directorio [examples|https://github.com/nette/forms/tree/master/examples] en la distribución, donde encontrará más inspiración. diff --git a/forms/es/rendering.texy b/forms/es/rendering.texy index 91ce9f6d5e..68d2466002 100644 --- a/forms/es/rendering.texy +++ b/forms/es/rendering.texy @@ -1,33 +1,35 @@ Renderizado de formularios ************************** -La apariencia de las formas puede ser muy diversa. En la práctica, podemos encontrarnos con dos extremos. Por un lado, existe la necesidad de renderizar una serie de formularios en una aplicación que son visualmente similares entre sí, y apreciamos la fácil renderización sin plantilla utilizando `$form->render()`. Este suele ser el caso de las interfaces administrativas. +La apariencia de los formularios puede ser muy diversa. En la práctica, podemos encontrar dos extremos. Por un lado, está la necesidad de renderizar en la aplicación una serie de formularios que son visualmente similares como dos gotas de agua, y apreciaremos el fácil renderizado sin plantilla usando `$form->render()`. Este suele ser el caso de las interfaces de administración. -Por otro lado, hay varios formularios en los que cada uno es único. Su aspecto se describe mejor utilizando el lenguaje HTML en la plantilla. Y, por supuesto, además de los dos extremos mencionados, nos encontraremos con muchos formularios que se sitúan en algún punto intermedio. +Por otro lado, están los formularios diversos donde aplica: cada pieza es original. Su forma se describe mejor usando el lenguaje HTML en la plantilla del formulario. Y, por supuesto, además de ambos extremos mencionados, encontraremos muchos formularios que se mueven en algún punto intermedio. -Renderizado con Latte .[#toc-rendering-with-latte] -================================================== +Renderizado mediante Latte +========================== -El [sistema de plantillas |latte:] Latte facilita fundamentalmente el renderizado de formularios y sus elementos. En primer lugar, mostraremos cómo renderizar formularios manualmente, elemento por elemento, para obtener un control total sobre el código. Más adelante mostraremos cómo [automatizar |#Automatic rendering] dicho renderizado. +El [sistema de plantillas Latte|latte:] facilita fundamentalmente el renderizado de formularios y sus elementos. Primero mostraremos cómo renderizar formularios manualmente, elemento por elemento, y así obtener control total sobre el código. Más adelante mostraremos cómo se puede [automatizar |#Renderizado automático] dicho renderizado. + +Puede hacer que el diseño de la plantilla Latte del formulario se genere usando el método `Nette\Forms\Blueprint::latte($form)`, que lo imprimirá en la página del navegador. Luego, simplemente haga clic para seleccionar el código y cópielo en su proyecto. .{data-version:3.1.15} `{control}` ----------- -La forma más sencilla de renderizar un formulario es escribirlo en una plantilla: +La forma más sencilla de renderizar un formulario es escribir en la plantilla: ```latte {control signInForm} ``` -El aspecto del formulario renderizado puede cambiarse configurando [el Renderer |#Renderer] y los [controles |#HTML Attributes] individuales. +Se puede influir en la apariencia del formulario renderizado de esta manera configurando el [#Renderer] y los [elementos individuales |#Atributos HTML]. `n:name` -------- -Es extremadamente fácil enlazar la definición del formulario en código PHP con código HTML. Basta con añadir los atributos `n:name`. ¡Así de fácil! +La definición del formulario en el código PHP se puede vincular muy fácilmente con el código HTML. Simplemente agregue los atributos `n:name`. ¡Así de fácil! ```php protected function createComponentSignInForm(): Form @@ -43,10 +45,10 @@ protected function createComponentSignInForm(): Form ```latte <form n:name=signInForm class=form> <div> - <label n:name=username>Nombre de usuario: <input n:name=username size=20 autofocus></label> + <label n:name=username>Username: <input n:name=username size=20 autofocus></label> </div> <div> - <label n:name=password>Contraseña: <input n:name=password></label> + <label n:name=password>Password: <input n:name=password></label> </div> <div> <input n:name=send class="btn btn-default"> @@ -54,10 +56,9 @@ protected function createComponentSignInForm(): Form </form> ``` -El aspecto del código HTML resultante está totalmente en tus manos. Si utiliza el atributo `n:name` con `<select>`, `<button>` o `<textarea>` su contenido interno se rellena automáticamente. -Además, la etiqueta `<form n:name>` crea una variable local `$form` con el objeto formulario dibujado y la etiqueta de cierre `</form>` dibuja todos los elementos ocultos no dibujados (lo mismo se aplica a `{form} ... {/form}`). +Tiene control total sobre la forma del código HTML resultante. Si usa el atributo `n:name` en los elementos `<select>`, `<button>` o `<textarea>`, su contenido interno se completará automáticamente. La etiqueta `<form n:name>` además crea una variable local `$form` con el objeto del formulario que se está renderizando y el cierre `</form>` renderiza todos los elementos ocultos no renderizados (lo mismo aplica a `{form} ... {/form}`). -Sin embargo, no debemos olvidar renderizar los posibles mensajes de error. Tanto los añadidos a elementos individuales por el método `addError()` (usando `{inputError}`) como los añadidos directamente al formulario (devueltos por `$form->getOwnErrors()`): +Sin embargo, no debemos olvidar renderizar los posibles mensajes de error. Tanto los que se agregaron a elementos individuales con el método `addError()` (usando `{inputError}`), como los agregados directamente al formulario (devueltos por `$form->getOwnErrors()`): ```latte <form n:name=signInForm class=form> @@ -66,11 +67,11 @@ Sin embargo, no debemos olvidar renderizar los posibles mensajes de error. Tanto </ul> <div> - <label n:name=username>Nombre de usuario: <input n:name=username size=20 autofocus></label> + <label n:name=username>Username: <input n:name=username size=20 autofocus></label> <span class=error n:ifcontent>{inputError username}</span> </div> <div> - <label n:name=password>Contraseña: <input n:name=password></label> + <label n:name=password>Password: <input n:name=password></label> <span class=error n:ifcontent>{inputError password}</span> </div> <div> @@ -79,7 +80,7 @@ Sin embargo, no debemos olvidar renderizar los posibles mensajes de error. Tanto </form> ``` -Los elementos de formulario más complejos, como RadioList o CheckboxList, pueden renderizarse elemento a elemento: +Los elementos de formulario más complejos, como RadioList o CheckboxList, se pueden renderizar así por elementos individuales: ```latte {foreach $form[gender]->getItems() as $key => $label} @@ -88,16 +89,10 @@ Los elementos de formulario más complejos, como RadioList o CheckboxList, puede ``` -Propuesta de código `{formPrint}` .[#toc-formprint] ---------------------------------------------------- - -Puede generar un código Latte similar para un formulario utilizando la etiqueta `{formPrint}`. Si lo colocas en una plantilla, verás el borrador del código en lugar de la representación normal. Luego sólo tienes que seleccionarlo y copiarlo en tu proyecto. - - `{label}` `{input}` ------------------- -¿No quieres pensar para cada elemento qué elemento HTML utilizar para él en la plantilla, ya sea `<input>`, `<textarea>` etc.? La solución es la etiqueta universal `{input}`: +¿No quiere pensar para cada elemento qué elemento HTML usar en la plantilla, si `<input>`, `<textarea>`, etc.? La solución es la etiqueta universal `{input}`: ```latte <form n:name=signInForm class=form> @@ -106,11 +101,11 @@ Puede generar un código Latte similar para un formulario utilizando la etiqueta </ul> <div> - {label username}Nombre de usuario: {input username, size: 20, autofocus: true}{/label} + {label username}Username: {input username, size: 20, autofocus: true}{/label} {inputError username} </div> <div> - {label password}Contraseña: {input password}{/label} + {label password}Password: {input password}{/label} {inputError password} </div> <div> @@ -121,7 +116,7 @@ Puede generar un código Latte similar para un formulario utilizando la etiqueta Si el formulario utiliza un traductor, el texto dentro de las etiquetas `{label}` será traducido. -De nuevo, los elementos de formulario más complejos, como RadioList o CheckboxList, se pueden renderizar elemento por elemento: +Incluso en este caso, los elementos de formulario más complejos, como RadioList o CheckboxList, se pueden renderizar por elementos individuales: ```latte {foreach $form[gender]->items as $key => $label} @@ -129,20 +124,19 @@ De nuevo, los elementos de formulario más complejos, como RadioList o CheckboxL {/foreach} ``` -Para representar el `<input>` en el elemento Checkbox, utilice `{input myCheckbox:}`. Los atributos HTML deben ir separados por una coma `{input myCheckbox:, class: required}`. +Para renderizar solo el `<input>` en el elemento Checkbox, use `{input myCheckbox:}`. Los atributos HTML en este caso siempre se separan con coma `{input myCheckbox:, class: required}`. `{inputError}` -------------- -Imprime un mensaje de error para el elemento del formulario, si tiene uno. El mensaje normalmente se envuelve en un elemento HTML para estilizarlo. -Evitar mostrar un elemento vacío si no hay mensaje puede hacerse elegantemente con `n:ifcontent`: +Muestra el mensaje de error para un elemento de formulario, si tiene alguno. El mensaje generalmente se envuelve en un elemento HTML para estilizarlo. Evitar renderizar un elemento vacío si no hay mensaje se puede hacer elegantemente usando `n:ifcontent`: ```latte <span class=error n:ifcontent>{inputError $input}</span> ``` -Podemos detectar la presencia de un error utilizando el método `hasErrors()` y establecer la clase del elemento padre en consecuencia: +Podemos verificar la presencia de un error con el método `hasErrors()` y establecer la clase del elemento padre en consecuencia: ```latte <div n:class="$form[username]->hasErrors() ? 'error'"> @@ -155,14 +149,13 @@ Podemos detectar la presencia de un error utilizando el método `hasErrors()` y `{form}` -------- -Etiquetas `{form signInForm}...{/form}` son una alternativa a `<form n:name="signInForm">...</form>`. +Las etiquetas `{form signInForm}...{/form}` son una alternativa a `<form n:name="signInForm">...</form>`. -Renderizado automático .[#toc-automatic-rendering] --------------------------------------------------- +Renderizado automático +---------------------- -Con las etiquetas `{input}` y `{label}`, podemos crear fácilmente una plantilla genérica para cualquier formulario. Iterará y renderizará todos sus elementos secuencialmente, excepto los elementos ocultos, que se renderizan automáticamente cuando el formulario termina con la etiqueta `</form>` . -Esperará el nombre del formulario renderizado en la variable `$form`. +Gracias a las etiquetas `{input}` y `{label}`, podemos crear fácilmente una plantilla genérica para cualquier formulario. Iterará y renderizará gradualmente todos sus elementos, excepto los elementos ocultos, que se renderizarán automáticamente al cerrar el formulario con la etiqueta `</form>`. Esperará el nombre del formulario a renderizar en la variable `$form`. ```latte <form n:name=$form class=form> @@ -179,16 +172,15 @@ Esperará el nombre del formulario renderizado en la variable `$form`. </form> ``` -Las etiquetas de par de cierre automático utilizadas `{label .../}` muestran las etiquetas procedentes de la definición del formulario en el código PHP. +Las etiquetas pares autocerradas `{label .../}` utilizadas muestran las etiquetas provenientes de la definición del formulario en el código PHP. -Puedes guardar esta plantilla genérica en el archivo `basic-form.latte` y para renderizar el formulario, sólo tienes que incluirla y pasar el nombre del formulario (o instancia) al parámetro `$form`: +Guarde esta plantilla genérica, por ejemplo, en el archivo `basic-form.latte` y para renderizar el formulario, simplemente inclúyala y pase el nombre (o instancia) del formulario al parámetro `$form`: ```latte {include basic-form.latte, form: signInForm} ``` -Si desea influir en la apariencia de un formulario en particular y dibujar un elemento de manera diferente, entonces la forma más fácil es preparar bloques en la plantilla que pueden ser sobrescritos más tarde. -Los bloques también pueden tener [nombres dinámicos |latte:template-inheritance#dynamic-block-names], por lo que puede insertar en ellos el nombre del elemento a dibujar. Por ejemplo: +Si al renderizar un formulario específico quisiera intervenir en su forma y, por ejemplo, renderizar un elemento de manera diferente, entonces la forma más sencilla es preparar bloques en la plantilla que luego se puedan sobrescribir. Los bloques también pueden tener [nombres dinámicos |latte:template-inheritance#Nombres de bloque dinámicos], por lo que también se puede insertar el nombre del elemento que se está renderizando. Por ejemplo: ```latte ... @@ -197,7 +189,7 @@ Los bloques también pueden tener [nombres dinámicos |latte:template-inheritanc ... ``` -Para el elemento e.g. `username` esto crea el bloque `input-username`, que puede ser fácilmente anulado usando la etiqueta [{embed} |latte:template-inheritance#unit-inheritance]: +Para un elemento, por ejemplo, `username`, se creará el bloque `input-username`, que se puede sobrescribir fácilmente usando la etiqueta [{embed} |latte:template-inheritance#Herencia de unidades embed]: ```latte {embed basic-form.latte, form: signInForm} @@ -209,7 +201,7 @@ Para el elemento e.g. `username` esto crea el bloque `input-username`, que puede {/embed} ``` -Alternativamente, todo el contenido de la plantilla `basic-form.latte` puede [definirse |latte:template-inheritance#definitions] como un bloque, incluido el parámetro `$form`: +Alternativamente, todo el contenido de la plantilla `basic-form.latte` se puede [definir |latte:template-inheritance#Definiciones define] como un bloque, incluido el parámetro `$form`: ```latte {define basic-form, $form} @@ -219,7 +211,7 @@ Alternativamente, todo el contenido de la plantilla `basic-form.latte` puede [de {/define} ``` -Esto facilitará ligeramente su uso: +Gracias a esto, su llamada será ligeramente más simple: ```latte {embed basic-form, signInForm} @@ -227,31 +219,31 @@ Esto facilitará ligeramente su uso: {/embed} ``` -Sólo tendrá que importar el bloque en un lugar, al principio de la plantilla de diseño: +El bloque solo necesita importarse en un solo lugar, al principio de la plantilla de layout: ```latte {import basic-form.latte} ``` -Casos especiales .[#toc-special-cases] --------------------------------------- +Casos especiales +---------------- -Si sólo necesita renderizar el contenido interno de un formulario sin `<form>` & `</form>` por ejemplo, en una petición AJAX, puedes abrir y cerrar el formulario con `{formContext} … {/formContext}`. Funciona de forma similar a `{form}` en un sentido lógico, aquí te permite usar otras etiquetas para dibujar elementos del formulario, pero al mismo tiempo no dibuja nada. +Si necesita renderizar solo la parte interna del formulario sin las etiquetas HTML `<form>`, por ejemplo, al enviar snippets, ocúltelas usando el atributo `n:tag-if`: ```latte -{formContext signForm} +<form n:name=signInForm n:tag-if=false> <div> - <label n:name=username>Nombre de usuario: <input n:name=username></label> + <label n:name=username>Username: <input n:name=username></label> {inputError username} </div> -{/formContext} +</form> ``` -La etiqueta `formContainer` ayuda con la representación de entradas dentro de un contenedor de formulario. +La etiqueta `{formContainer}` ayuda a renderizar elementos dentro de un contenedor de formulario. ```latte -<p>Which news you wish to receive:</p> +<p>Qué noticias desea recibir:</p> {formContainer emailNews} <ul> @@ -262,8 +254,8 @@ La etiqueta `formContainer` ayuda con la representación de entradas dentro de u ``` -Renderizado sin Latte .[#toc-rendering-without-latte] -===================================================== +Renderizado sin Latte +===================== La forma más sencilla de renderizar un formulario es llamar a: @@ -271,18 +263,18 @@ La forma más sencilla de renderizar un formulario es llamar a: $form->render(); ``` -El aspecto del formulario renderizado puede cambiarse configurando [el Renderer |#Renderer] y los [controles individuales |#HTML Attributes]. +Se puede influir en la apariencia del formulario renderizado de esta manera configurando el [#Renderer] y los [elementos individuales |#Atributos HTML]. -Renderizado manual .[#toc-manual-rendering] -------------------------------------------- +Renderizado manual +------------------ -Cada elemento de formulario tiene métodos que generan el código HTML para el campo y la etiqueta del formulario. Pueden devolverlo como una cadena o como un objeto [Nette\Utils\Html |utils:html-elements]: +Cada elemento de formulario dispone de métodos que generan el código HTML del campo de formulario y la etiqueta. Pueden devolverlo como una cadena o como un objeto [Nette\Utils\Html|utils:html-elements]: - `getControl(): Html|string` devuelve el código HTML del elemento - `getLabel($caption = null): Html|string|null` devuelve el código HTML de la etiqueta, si existe -Esto permite mostrar el formulario elemento por elemento: +Así, el formulario se puede renderizar elemento por elemento: ```php <?php $form->render('begin') ?> @@ -305,47 +297,46 @@ Esto permite mostrar el formulario elemento por elemento: <?php $form->render('end') ?> ``` -Mientras que para algunos elementos `getControl()` devuelve un único elemento HTML (por ejemplo `<input>`, `<select>` etc.), para otros devuelve una pieza entera de código HTML (CheckboxList, RadioList). -En este caso, puede utilizar métodos que generen entradas y etiquetas individuales, para cada elemento por separado: +Mientras que para algunos elementos `getControl()` devuelve un único elemento HTML (por ejemplo, `<input>`, `<select>`, etc.), para otros devuelve una pieza completa de código HTML (CheckboxList, RadioList). En tal caso, puede utilizar métodos que generan inputs y etiquetas individuales, para cada elemento por separado: -- `getControlPart($key = null): ?Html` devuelve el código HTML de un único elemento -- `getLabelPart($key = null): ?Html` devuelve el código HTML de la etiqueta de un único elemento +- `getControlPart($key = null): ?Html` devuelve el código HTML de un elemento +- `getLabelPart($key = null): ?Html` devuelve el código HTML de la etiqueta de un elemento .[note] -Estos métodos llevan el prefijo `get` por razones históricas, pero `generate` sería mejor, ya que crea y devuelve un nuevo elemento `Html` en cada llamada. +Estos métodos tienen el prefijo `get` por razones históricas, pero `generate` sería mejor, porque en cada llamada crean y devuelven un nuevo elemento `Html`. -Renderizador .[#toc-renderer] -============================= +Renderer +======== -Es un objeto que proporciona el renderizado del formulario. Puede ser establecido por el método `$form->setRenderer`. Se le pasa el control cuando se llama al método `$form->render()`. +Es un objeto que se encarga de renderizar el formulario. Se puede establecer mediante el método `$form->setRenderer`. Se le pasa el control cuando se llama al método `$form->render()`. -Si no establecemos un renderizador personalizado, se utilizará el renderizador por defecto [api:Nette\Forms\Rendering\DefaultFormRenderer]. Esto renderizará los elementos del formulario como una tabla HTML. El resultado es el siguiente: +Si no establecemos nuestro propio renderer, se utilizará el renderer predeterminado [api:Nette\Forms\Rendering\DefaultFormRenderer]. Este renderiza los elementos del formulario en forma de tabla HTML. La salida se ve así: ```latte <table> <tr class="required"> - <th><label class="required" for="frm-name">Name:</label></th> + <th><label class="required" for="frm-name">Nombre:</label></th> <td><input type="text" class="text" name="name" id="frm-name" required value=""></td> </tr> <tr class="required"> - <th><label class="required" for="frm-age">Age:</label></th> + <th><label class="required" for="frm-age">Edad:</label></th> <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> </tr> <tr> - <th><label>Gender:</label></th> + <th><label>Género:</label></th> ... ``` -Depende de usted, si desea utilizar una tabla o no, y muchos diseñadores web prefieren diferentes marcas, por ejemplo, una lista. Podemos configurar `DefaultFormRenderer` para que no se muestre en una tabla. Sólo tenemos que establecer [$wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers] adecuados. El primer índice siempre representa un área y el segundo un elemento. Todas las áreas respectivas se muestran en la imagen: +Si usar o no una tabla para la estructura del formulario es discutible y muchos diseñadores web prefieren otro marcado. Por ejemplo, una lista de definición. Por lo tanto, reconfiguraremos `DefaultFormRenderer` para que renderice el formulario en forma de lista. La configuración se realiza editando el array [$wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers]. El primer índice siempre representa el área y el segundo su atributo. Las áreas individuales se muestran en la imagen: -[* form-areas-en.webp *] +[* defaultformrenderer-es.webp *] *** Áreas del formulario al usar DefaultFormRenderer *** -Por defecto, un grupo de `controls` se envuelve en `<table>`y cada `pair` es una fila de tabla `<tr>` que contiene un par de `label` y `control` (celdas `<th>` y `<td>`). Vamos a cambiar todos esos elementos envolventes. Envolveremos `controls` en `<dl>`dejaremos `pair` solo, pondremos `label` en `<dt>` y envolveremos `control` en `<dd>`: +Por defecto, el grupo de elementos `controls` está envuelto en una tabla `<table>`, cada `pair` representa una fila de la tabla `<tr>` y el par `label` y `control` son celdas `<th>` y `<td>`. Ahora cambiaremos los elementos envolventes. El área `controls` la insertaremos en un contenedor `<dl>`, el área `pair` la dejaremos sin contenedor, `label` la insertaremos en `<dt>` y finalmente `control` la envolveremos con etiquetas `<dd>`: ```php $renderer = $form->getRenderer(); @@ -357,193 +348,192 @@ $renderer->wrappers['control']['container'] = 'dd'; $form->render(); ``` -El resultado es el siguiente fragmento: +El resultado es este código HTML: ```latte <dl> - <dt><label class="required" for="frm-name">Name:</label></dt> + <dt><label class="required" for="frm-name">Nombre:</label></dt> <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> - <dt><label class="required" for="frm-age">Age:</label></dt> + <dt><label class="required" for="frm-age">Edad:</label></dt> <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> - <dt><label>Gender:</label></dt> + <dt><label>Género:</label></dt> ... </dl> ``` -Las envolturas pueden afectar a muchos atributos. Por ejemplo: +En el array wrappers se pueden influir en muchos otros atributos: -- añadir clases CSS especiales a cada entrada del formulario -- distinguir entre líneas pares e impares -- hacer que lo obligatorio y lo opcional se dibujen de forma diferente -- establecer si los mensajes de error se muestran encima del formulario o cerca de cada elemento +- agregar clases CSS a tipos individuales de elementos de formulario +- distinguir con clase CSS las filas pares e impares +- distinguir visualmente los elementos obligatorios y opcionales +- determinar si los mensajes de error se muestran directamente junto a los elementos o sobre el formulario -Opciones .[#toc-options] ------------------------- +Opciones +-------- -El comportamiento del Renderizador también puede controlarse estableciendo *opciones* en elementos individuales del formulario. De esta forma puede establecer el tooltip que se muestra junto al campo de entrada: +El comportamiento del Renderer también se puede controlar estableciendo *options* en los elementos de formulario individuales. Así se puede establecer la descripción que se mostrará junto al campo de entrada: ```php -$form->addText('phone', 'Number:') - ->setOption('description', 'This number will remain hidden'); +$form->addText('phone', 'Número:') + ->setOption('description', 'Este número permanecerá oculto'); ``` -Si queremos colocar contenido HTML en él, usamos la clase [Html |utils:html-elements]. +Si queremos colocar contenido HTML en él, utilizaremos la clase [Html |utils:html-elements]: ```php use Nette\Utils\Html; -$form->addText('phone', 'Phone:') +$form->addText('phone', 'Número:') ->setOption('description', Html::el('p') - ->setHtml('<a href="...">Terms of service.</a>') + ->setHtml('<a href="...">Condiciones de almacenamiento de su número</a>') ); ``` .[tip] -También se puede utilizar el elemento Html en lugar de la etiqueta: `$form->addCheckbox('conditions', $label)`. +El elemento Html también se puede usar en lugar de la etiqueta: `$form->addCheckbox('conditions', $label)`. -Agrupación de entradas .[#toc-grouping-inputs] ----------------------------------------------- +Agrupación de elementos +----------------------- -El renderizador permite agrupar elementos en grupos visuales (fieldsets): +El Renderer permite agrupar elementos en grupos visuales (fieldsets): ```php -$form->addGroup('Personal data'); +$form->addGroup('Datos personales'); ``` -La creación de un nuevo grupo lo activa - todos los elementos añadidos posteriormente se añaden a este grupo. Usted puede construir un formulario como este: +Después de crear un nuevo grupo, este se vuelve activo y cada elemento recién agregado también se agrega a él. Por lo tanto, el formulario se puede construir de esta manera: ```php $form = new Form; -$form->addGroup('Personal data'); -$form->addText('name', 'Your name:'); -$form->addInteger('age', 'Your age:'); +$form->addGroup('Datos personales'); +$form->addText('name', 'Su nombre:'); +$form->addInteger('age', 'Su edad:'); $form->addEmail('email', 'Email:'); -$form->addGroup('Shipping address'); -$form->addCheckbox('send', 'Ship to address'); -$form->addText('street', 'Street:'); -$form->addText('city', 'City:'); -$form->addSelect('country', 'Country:', $countries); +$form->addGroup('Dirección de envío'); +$form->addCheckbox('send', 'Enviar a dirección'); +$form->addText('street', 'Calle:'); +$form->addText('city', 'Ciudad:'); +$form->addSelect('country', 'País:', $countries); ``` +El Renderer primero renderiza los grupos y solo después los elementos que no pertenecen a ningún grupo. + -Soporte Bootstrap .[#toc-bootstrap-support] -------------------------------------------- +Soporte para Bootstrap +---------------------- -Puede encontrar [ejemplos |https://github.com/nette/forms/tree/master/examples] de configuración de Renderer para [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], [Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] y [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php] +[En los ejemplos |https://github.com/nette/forms/tree/master/examples] encontrará ejemplos de cómo configurar el Renderer para [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], [Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] y [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php]. -Atributos HTML .[#toc-html-attributes] -====================================== +Atributos HTML +============== -Puede establecer cualquier atributo HTML a los controles de formulario utilizando `setHtmlAttribute(string $name, $value = true)`: +Para establecer cualquier atributo HTML de los elementos de formulario, usamos el método `setHtmlAttribute(string $name, $value = true)`: ```php $form->addInteger('number', 'Número:') - ->setHtmlAttribute('class', 'número-grande'); + ->setHtmlAttribute('class', 'big-number'); -$form->addSelect('range', 'Ordenar por:', ['precio', 'nombre']) - ->setHtmlAttribute('onchange', 'submit()'); // llama a la función JS submit() al cambiar +$form->addSelect('rank', 'Ordenar por:', ['precio', 'nombre']) + ->setHtmlAttribute('onchange', 'submit()'); // enviar al cambiar -// aplicando en <form> +// Para establecer atributos del propio <form> $form->setHtmlAttribute('id', 'myForm'); ``` -Configuración del tipo de entrada: +Especificación del tipo de elemento: ```php $form->addText('tel', 'Su teléfono:') ->setHtmlType('tel') - ->setHtmlAttribute('placeholder', 'Por favor, introduzca su teléfono'); + ->setHtmlAttribute('placeholder', 'escriba el teléfono'); ``` -Podemos establecer el atributo HTML a elementos individuales en listas de radio o casillas de verificación con valores diferentes para cada uno de ellos. -Tenga en cuenta los dos puntos después de `style:` para asegurarse de que el valor se selecciona por clave: +.[warning] +Establecer el tipo y otros atributos sirve solo para fines visuales. La verificación de la corrección de las entradas debe realizarse en el servidor, lo que se asegura eligiendo el [elemento de formulario|controls] adecuado e indicando las [reglas de validación|validation]. + +Podemos establecer atributos HTML con valores diferentes para cada uno de los elementos individuales en listas de radio o checkbox. Observe los dos puntos después de `style:`, que aseguran la elección del valor según la clave: ```php -$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue']; +$colors = ['r' => 'rojo', 'g' => 'verde', 'b' => 'azul']; $styles = ['r' => 'background:red', 'g' => 'background:green']; -$form->addCheckboxList('colors', 'Colors:', $colors) +$form->addCheckboxList('colors', 'Colores:', $colors) ->setHtmlAttribute('style:', $styles); ``` -Renders: +Imprime: ```latte -<label><input type="checkbox" name="colors[]" style="background:red" value="r">red</label> -<label><input type="checkbox" name="colors[]" style="background:green" value="g">green</label> -<label><input type="checkbox" name="colors[]" value="b">blue</label> +<label><input type="checkbox" name="colors[]" style="background:red" value="r">rojo</label> +<label><input type="checkbox" name="colors[]" style="background:green" value="g">verde</label> +<label><input type="checkbox" name="colors[]" value="b">azul</label> ``` -Para un atributo HTML lógico (que no tiene valor, como `readonly`), puede utilizar un signo de interrogación: +Para establecer atributos booleanos, como `readonly`, podemos usar la notación con un signo de interrogación: ```php -$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue']; -$form->addCheckboxList('colors', 'Colors:', $colors) - ->setHtmlAttribute('readonly?', 'r'); // use array for multiple keys, e.g. ['r', 'g'] +$form->addCheckboxList('colors', 'Colores:', $colors) + ->setHtmlAttribute('readonly?', 'r'); // para múltiples claves use un array, ej. ['r', 'g'] ``` -Renderiza: +Imprime: ```latte -<label><input type="checkbox" name="colors[]" readonly value="r">red</label> -<label><input type="checkbox" name="colors[]" value="g">green</label> -<label><input type="checkbox" name="colors[]" value="b">blue</label> +<label><input type="checkbox" name="colors[]" readonly value="r">rojo</label> +<label><input type="checkbox" name="colors[]" value="g">verde</label> +<label><input type="checkbox" name="colors[]" value="b">azul</label> ``` -Para las cajas de selección, el método `setHtmlAttribute()` establece los atributos del elemento `<select>` del elemento. Si queremos establecer los atributos para cada -`<option>`utilizaremos el método `setOptionAttribute()`. También funcionan los dos puntos y el signo de interrogación utilizados anteriormente: +En el caso de los selectbox, el método `setHtmlAttribute()` establece los atributos del elemento `<select>`. Si queremos establecer atributos para los `<option>` individuales, usamos el método `setOptionAttribute()`. También funcionan las notaciones con dos puntos y signo de interrogación mencionadas anteriormente: ```php $form->addSelect('colors', 'Colores:', $colors) ->setOptionAttribute('style:', $styles); ``` -Renders: +Imprime: ```latte <select name="colors"> - <option value="r" style="background:red">red</option> - <option value="g" style="background:green">green</option> - <option value="b">blue</option> + <option value="r" style="background:red">rojo</option> + <option value="g" style="background:green">verde</option> + <option value="b">azul</option> </select> ``` -Prototipos .[#toc-prototypes] ------------------------------ +Prototipos +---------- -Una forma alternativa de establecer atributos HTML es modificar la plantilla a partir de la cual se genera el elemento HTML. La plantilla es un objeto `Html` y es devuelta por el método `getControlPrototype()`: +Una forma alternativa de establecer atributos HTML consiste en modificar la plantilla a partir de la cual se genera el elemento HTML. La plantilla es un objeto `Html` y la devuelve el método `getControlPrototype()`: ```php -$input = $form->addInteger('number'); +$input = $form->addInteger('number', 'Número:'); $html = $input->getControlPrototype(); // <input> $html->class('big-number'); // <input class="big-number"> ``` -La plantilla de etiqueta devuelta por `getLabelPrototype()` también puede modificarse de esta forma: +De esta manera, también se puede modificar la plantilla de la etiqueta, que devuelve `getLabelPrototype()`: ```php $html = $input->getLabelPrototype(); // <label> $html->class('distinctive'); // <label class="distinctive"> ``` -Para los elementos Checkbox, CheckboxList y RadioList se puede influir en la plantilla de elemento que envuelve al elemento. Es devuelto por `getContainerPrototype()`. Por defecto es un elemento "vacío", por lo que no se renderiza nada, pero dándole un nombre se renderizará: +En los elementos Checkbox, CheckboxList y RadioList, puede influir en la plantilla del elemento que envuelve todo el elemento. La devuelve `getContainerPrototype()`. En el estado predeterminado, es un elemento "vacío", por lo que no se renderiza nada, pero al establecerle un nombre, se renderizará: ```php $input = $form->addCheckbox('send'); -echo $input->getControl(); -// <label><input type="checkbox" name="send"></label> - $html = $input->getContainerPrototype(); $html->setName('div'); // <div> $html->class('check'); // <div class="check"> @@ -551,50 +541,49 @@ echo $input->getControl(); // <div class="check"><label><input type="checkbox" name="send"></label></div> ``` -En el caso de CheckboxList y RadioList también es posible influir en el patrón separador de elementos devuelto por el método `getSeparatorPrototype()`. Por defecto, es un elemento `<br>`. Si lo cambia a un elemento par, envolverá los elementos individuales en lugar de separarlos. -También es posible influir en el patrón del elemento HTML de las etiquetas de los elementos, que devuelve `getItemLabelPrototype()`. +En el caso de CheckboxList y RadioList, también se puede influir en la plantilla del separador de elementos individuales, que devuelve el método `getSeparatorPrototype()`. En el estado predeterminado, es el elemento `<br>`. Si lo cambia a un elemento par, envolverá los elementos individuales en lugar de separarlos. Y además, se puede influir en la plantilla del elemento HTML de la etiqueta en los elementos individuales, que devuelve `getItemLabelPrototype()`. -Traducción .[#toc-translating] -============================== +Traducción +========== -Si está programando una aplicación multilingüe, probablemente necesitará renderizar el formulario en diferentes idiomas. Nette Framework define una interfaz de traducción para este propósito [api:Nette\Localization\Translator]. No existe una implementación por defecto en Nette, puedes elegir según tus necesidades entre varias soluciones ya preparadas que puedes encontrar en [Componette |https://componette.org/search/localization]. Su documentación le explica cómo configurar el traductor. +Si programa una aplicación multilingüe, probablemente necesitará renderizar el formulario en diferentes versiones lingüísticas. Nette Framework define para este propósito una interfaz para la traducción [api:Nette\Localization\Translator]. En Nette no hay una implementación predeterminada, puede elegir según sus necesidades entre varias soluciones listas que encontrará en [Componette |https://componette.org/search/localization]. En su documentación aprenderá cómo configurar el traductor. -El formulario soporta la salida de texto a través del traductor. Lo pasamos usando el método `setTranslator()`: +Los formularios admiten la impresión de textos a través del traductor. Se lo pasamos usando el método `setTranslator()`: ```php $form->setTranslator($translator); ``` -A partir de ahora, no sólo todas las etiquetas, sino también todos los mensajes de error o las entradas de las casillas de selección se traducirán a otro idioma. +A partir de este momento, no solo todas las etiquetas, sino también todos los mensajes de error o elementos de select boxes se traducirán a otro idioma. -Es posible configurar un traductor diferente para elementos individuales del formulario o desactivar completamente la traducción con `null`: +Para elementos de formulario individuales, es posible establecer un traductor diferente o desactivar completamente la traducción con el valor `null`: ```php -$form->addSelect('carModel', 'Model:', $cars) +$form->addSelect('carModel', 'Modelo:', $cars) ->setTranslator(null); ``` -Para las [reglas de validación |validation], también se pasan parámetros específicos al traductor, por ejemplo para la regla: +Para las [reglas de validación|validation], también se pasan parámetros específicos al traductor, por ejemplo, para la regla: ```php -$form->addPassword('password', 'Password:') - ->addRule($form::MinLength, 'Password has to be at least %d characters long', 8) +$form->addPassword('password', 'Contraseña:') + ->addRule($form::MinLength, 'La contraseña debe tener al menos %d caracteres', 8); ``` -se llama al traductor con los siguientes parámetros: +se llama al traductor con estos parámetros: ```php -$translator->translate('Password has to be at least %d characters long', 8); +$translator->translate('La contraseña debe tener al menos %d caracteres', 8); ``` -y así puede elegir la forma plural correcta para la palabra `characters` por recuento. +y, por lo tanto, puede elegir la forma plural correcta de la palabra `caracteres` según el número. -Evento onRender .[#toc-event-onrender] -====================================== +Evento onRender +=============== -Justo antes de que el formulario sea renderizado, podemos invocar nuestro código. Esto puede, por ejemplo, añadir clases HTML a los elementos del formulario para su correcta visualización. Añadimos el código al array `onRender`: +Justo antes de que se renderice el formulario, podemos hacer que se llame nuestro código. Este puede, por ejemplo, complementar los elementos del formulario con clases HTML para una correcta visualización. Agregamos el código al array `onRender`: ```php $form->onRender[] = function ($form) { diff --git a/forms/es/standalone.texy b/forms/es/standalone.texy index 22066da21c..15159afe91 100644 --- a/forms/es/standalone.texy +++ b/forms/es/standalone.texy @@ -1,16 +1,16 @@ -Formularios utilizados de forma autónoma -**************************************** +Formularios utilizados de forma independiente +********************************************* .[perex] -Los Formularios Nette facilitan enormemente la creación y el procesamiento de formularios web. Puedes utilizarlos en tus aplicaciones completamente solos sin el resto del framework, lo que demostraremos en este capítulo. +Nette Forms facilita enormemente la creación y el procesamiento de formularios web. Puede usarlos en sus aplicaciones de forma completamente independiente del resto del framework, como mostraremos en este capítulo. -Sin embargo, si usas Nette Application y presentadores, hay una guía para ti: formularios [en presentadores |in-presenter]. +Pero si utiliza Nette Application y presenters, la guía para [uso en presenters|in-presenter] es para usted. -Primer formulario .[#toc-first-form] -==================================== +Primer formulario +================= -Intentaremos escribir un sencillo formulario de registro. Su código tendrá este aspecto ("código completo":https://gist.github.com/dg/370a7e3094d9ba9a9e913b8e2a2dc851): +Intentemos escribir un formulario de registro simple. Su código será el siguiente ("código completo":https://gist.github.com/dg/57878c1a413ae8ef0c1d83f02c43ef3f): ```php use Nette\Forms\Form; @@ -18,99 +18,98 @@ use Nette\Forms\Form; $form = new Form; $form->addText('name', 'Nombre:'); $form->addPassword('password', 'Contraseña:'); -$form->addSubmit('send', 'Regístrate'); +$form->addSubmit('send', 'Registrarse'); ``` -Y vamos a renderizarlo: +Lo renderizamos muy fácilmente: ```php $form->render(); ``` -y el resultado debería verse así: +y en el navegador se mostrará así: -[* form-en.webp *] +[* form-es.webp *] *** Formulario de registro simple *** -El formulario es un objeto de la clase `Nette\Forms\Form` (la clase `Nette\Application\UI\Form` se utiliza en los presentadores). Le añadimos los controles nombre, contraseña y botón de envío. +El formulario es un objeto de la clase `Nette\Forms\Form` (la clase `Nette\Application\UI\Form` se usa en presenters). Le hemos añadido los llamados elementos nombre, contraseña y un botón de envío. -Ahora reactivaremos el formulario. Preguntando a `$form->isSuccess()`, averiguaremos si el formulario fue enviado y si fue rellenado válidamente. En caso afirmativo, volcaremos los datos. Tras la definición del formulario añadiremos: +Y ahora vamos a darle vida al formulario. Preguntando a `$form->isSuccess()` averiguamos si el formulario fue enviado y si se rellenó válidamente. Si es así, mostramos los datos. Detrás de la definición del formulario, añadimos: ```php if ($form->isSuccess()) { - echo 'El formulario se ha rellenado y enviado correctamente'; + echo 'El formulario se rellenó correctamente y se envió'; $data = $form->getValues(); - // $data->name contains name - // $data->password contains password + // $data->name contiene el nombre + // $data->password contiene la contraseña var_dump($data); } ``` -El método `getValues()` devuelve los datos enviados en forma de objeto [ArrayHash |utils:arrays#ArrayHash]. [Más adelante |#Mapping to Classes] mostraremos cómo modificar esto. La variable `$data` contiene las claves `name` y `password` con los datos introducidos por el usuario. +El método `getValues()` devuelve los datos enviados en forma de objeto [ArrayHash |utils:arrays#ArrayHash]. Mostraremos cómo cambiar esto [más adelante |#Mapeo a clases]. El objeto `$data` contiene las claves `name` y `password` con los datos que el usuario rellenó. -Normalmente enviamos los datos directamente para su posterior procesamiento, que puede ser, por ejemplo, su inserción en la base de datos. Sin embargo, puede producirse un error durante el procesamiento, por ejemplo, que el nombre de usuario ya esté ocupado. En este caso, pasamos el error de vuelta al formulario usando `addError()` y dejamos que se redibuje, con un mensaje de error: +Normalmente, enviamos los datos directamente para su posterior procesamiento, que puede ser, por ejemplo, insertarlos en la base de datos. Sin embargo, durante el procesamiento puede ocurrir un error, por ejemplo, que el nombre de usuario ya esté ocupado. En tal caso, devolvemos el error al formulario usando `addError()` y dejamos que se renderice de nuevo, junto con el mensaje de error. ```php -$form->addError('Lo sentimos, el nombre de usuario ya está en uso.'); +$form->addError('Lo sentimos, este nombre de usuario ya está en uso.'); ``` -Después de procesar el formulario, redirigiremos a la página siguiente. Esto evita que el formulario sea reenviado involuntariamente haciendo clic en el botón *refresh*, *back*, o moviendo el historial del navegador. +Después de procesar el formulario, redirigimos a la página siguiente. Esto evita el reenvío no deseado del formulario con el botón *actualizar*, *atrás* o moviéndose en el historial del navegador. -Por defecto, el formulario se envía usando el método POST a la misma página. Ambos pueden cambiarse: +El formulario se envía por defecto mediante el método POST y a la misma página. Ambos se pueden cambiar: ```php $form->setAction('/submit.php'); $form->setMethod('GET'); ``` -Y eso es todo :-) Tenemos un formulario funcional y perfectamente [asegurado |#Vulnerability Protection]. +Y eso es todo :-) Tenemos un formulario funcional y perfectamente [seguro |#Protección contra vulnerabilidades]. -Prueba a añadir más [controles de formulario |controls]. +Intente añadir también otros [elementos de formulario|controls]. -Acceso a los controles .[#toc-access-to-controls] -================================================= +Acceso a los elementos +====================== -El formulario y sus controles individuales se denominan componentes. Crean un árbol de componentes, cuya raíz es el formulario. Puede acceder a los controles individuales de la siguiente manera: +Llamamos componentes tanto al formulario como a sus elementos individuales. Forman un árbol de componentes, donde la raíz es precisamente el formulario. Podemos acceder a los elementos individuales del formulario de esta manera: ```php $input = $form->getComponent('name'); -// sintaxis alternativa: $input = $form['nombre']; +// sintaxis alternativa: $input = $form['name']; $button = $form->getComponent('send'); -// sintaxis alternativa: $button = $form['enviar']; +// sintaxis alternativa: $button = $form['send']; ``` -Los controles se eliminan utilizando unset: +Los elementos se eliminan usando `unset`: ```php unset($form['name']); ``` -Reglas de validación .[#toc-validation-rules] -============================================= +Reglas de validación +==================== -Aquí se usó la palabra *valid*, pero el formulario aún no tiene reglas de validación. Arreglémoslo. +Se mencionó la palabra *válido,* pero el formulario aún no tiene reglas de validación. Vamos a corregirlo. -El nombre será obligatorio, así que lo marcaremos con el método `setRequired()`, cuyo argumento es el texto del mensaje de error que se mostrará si el usuario no lo rellena. Si no se da ningún argumento, se utilizará el mensaje de error por defecto. +El nombre será obligatorio, por lo que lo marcamos con el método `setRequired()`, cuyo argumento es el texto del mensaje de error que se mostrará si el usuario no rellena el nombre. Si no se proporciona el argumento, se utilizará el mensaje de error predeterminado. ```php $form->addText('name', 'Nombre:') - ->setRequired('Por favor, introduzca su nombre.'); + ->setRequired('Por favor, introduzca su nombre'); ``` -Intente enviar el formulario sin el nombre rellenado y verá que se muestra un mensaje de error y el navegador o servidor lo rechazará hasta que lo rellene. +Intente enviar el formulario sin rellenar el nombre y verá que se muestra un mensaje de error y el navegador o el servidor lo rechazarán hasta que rellene el campo. -Al mismo tiempo, no podrá engañar al sistema escribiendo sólo espacios en la entrada, por ejemplo. De ninguna manera. Nette recorta automáticamente los espacios en blanco a izquierda y derecha. Pruébalo. Es algo que debería hacer siempre con cada entrada de una sola línea, pero a menudo se olvida. Nette lo hace automáticamente. (Puede intentar engañar a los formularios y enviar una cadena multilínea como nombre. Incluso en este caso, Nette no se dejará engañar y los saltos de línea cambiarán a espacios). +Al mismo tiempo, no puede engañar al sistema escribiendo, por ejemplo, solo espacios en el campo. De ninguna manera. Nette elimina automáticamente los espacios iniciales y finales. Pruébelo usted mismo. Es algo que siempre debería hacer con cada input de una sola línea, pero a menudo se olvida. Nette lo hace automáticamente. (Puede intentar engañar al formulario y enviar una cadena de varias líneas como nombre. Incluso aquí, Nette no se deja engañar y convierte los saltos de línea en espacios). -El formulario siempre se valida en el lado del servidor, pero también se genera validación JavaScript, que es rápida y el usuario conoce el error inmediatamente, sin tener que enviar el formulario al servidor. De esto se encarga el script `netteForms.js`. -Añádelo a la página: +El formulario siempre se valida en el lado del servidor, pero también se genera una validación JavaScript, que se ejecuta instantáneamente y el usuario se entera del error de inmediato, sin necesidad de enviar el formulario al servidor. Esto lo gestiona el script `netteForms.js`. Insértelo en la página: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Si miras en el código fuente de la página con formulario, puedes notar que Nette inserta los campos requeridos en elementos con una clase CSS `required`. Pruebe a añadir el siguiente estilo a la plantilla, y la etiqueta "Nombre" será de color rojo. Elegantemente, marcamos los campos obligatorios para los usuarios: +Si mira el código fuente de la página con el formulario, puede notar que Nette inserta los elementos obligatorios en elementos con la clase CSS `required`. Intente añadir la siguiente hoja de estilos a la plantilla y la etiqueta "Nombre" será roja. De esta manera, marcamos elegantemente los elementos obligatorios para los usuarios: ```latte <style> @@ -118,96 +117,96 @@ Si miras en el código fuente de la página con formulario, puedes notar que Net </style> ``` -Se añadirán reglas de validación adicionales mediante el método `addRule()`. El primer parámetro es la regla, el segundo es de nuevo el texto del mensaje de error, y el argumento opcional regla de validación puede seguir. ¿Qué significa esto? +Añadimos otras reglas de validación con el método `addRule()`. El primer parámetro es la regla, el segundo es nuevamente el texto del mensaje de error y puede seguir un argumento de la regla de validación. ¿Qué significa esto? -El formulario recibirá otra entrada opcional *edad* con la condición, que tiene que ser un número (`addInteger()`) y en ciertos límites (`$form::Range`). Y aquí usaremos el tercer argumento de `addRule()`, el propio rango: +Ampliaremos el formulario con un nuevo campo opcional "edad", que debe ser un número entero (`addInteger()`) y además estar en un rango permitido (`$form::Range`). Y aquí es donde usaremos el tercer parámetro del método `addRule()`, con el que pasamos al validador el rango requerido como un par `[desde, hasta]`: ```php $form->addInteger('age', 'Edad:') - ->addRule($form::Range, 'Debes ser mayor de 18 años y tener menos de 120.', [18, 120]); + ->addRule($form::Range, 'La edad debe estar entre 18 y 120', [18, 120]); ``` .[tip] -Si el usuario no rellena el campo, las reglas de validación no se verificarán, porque el campo es opcional. +Si el usuario no rellena el campo, las reglas de validación no se verificarán, ya que el elemento es opcional. -Obviamente hay espacio para una pequeña refactorización. En el mensaje de error y en el tercer parámetro, los números aparecen por duplicado, lo que no es lo ideal. Si estuviéramos creando un [formulario multilingüe |rendering#translating] y el mensaje que contiene los números tuviera que traducirse a varios idiomas, dificultaría el cambio de valores. Por esta razón, se pueden utilizar los caracteres sustitutos `%d`: +Aquí surge espacio para una pequeña refactorización. En el mensaje de error y en el tercer parámetro, los números se indican de forma duplicada, lo cual no es ideal. Si estuviéramos creando [formularios multilingües |rendering#Traducción] y el mensaje que contiene números se tradujera a varios idiomas, dificultaría un posible cambio de valores. Por esta razón, es posible usar los marcadores de posición `%d` y Nette completará los valores: ```php - ->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]); + ->addRule($form::Range, 'La edad debe estar entre %d y %d años', [18, 120]); ``` -Volvamos al campo *contraseña*, hagámoslo *requerido*, y verifiquemos la longitud mínima de la contraseña (`$form::MinLength`), de nuevo usando los caracteres sustitutos en el mensaje: +Volvamos al elemento `password`, que también haremos obligatorio y además verificaremos la longitud mínima de la contraseña (`$form::MinLength`), nuevamente usando un marcador de posición: ```php $form->addPassword('password', 'Contraseña:') - ->setRequired('Elige una contraseña') - ->addRule($form::MinLength, 'Su contraseña debe tener una longitud mínima de %d', 8); + ->setRequired('Elija una contraseña') + ->addRule($form::MinLength, 'La contraseña debe tener al menos %d caracteres', 8); ``` -Añadiremos un campo `passwordVerify` al formulario, donde el usuario introduzca de nuevo la contraseña, para su comprobación. Usando reglas de validación, comprobamos si ambas contraseñas son iguales (`$form::Equal`). Y como argumento damos una referencia a la primera contraseña usando [corchetes |#Access to Controls]: +Añadimos al formulario otro campo `passwordVerify`, donde el usuario introduce la contraseña de nuevo, para verificar. Usando reglas de validación, comprobamos si ambas contraseñas son iguales (`$form::Equal`). Y como parámetro, damos una referencia a la primera contraseña usando [corchetes |#Acceso a los elementos]: ```php -$form->addPassword('passwordVerify', 'Contraseña de nuevo:') - ->setRequired('Rellene de nuevo su contraseña para comprobar si hay algún error tipográfico') - ->addRule($form::Equal, 'Contraseña incorrecta', $form['password']) +$form->addPassword('passwordVerify', 'Contraseña para verificar:') + ->setRequired('Por favor, introduzca la contraseña de nuevo para verificarla') + ->addRule($form::Equal, 'Las contraseñas no coinciden', $form['password']) ->setOmitted(); ``` -Usando `setOmitted()`, marcamos un elemento cuyo valor realmente no nos importa y que existe sólo para validación. Su valor no se pasa a `$data`. +Con `setOmitted()` hemos marcado un elemento cuyo valor en realidad no nos importa y que existe solo por motivos de validación. El valor no se pasará a `$data`. -Tenemos un formulario completamente funcional con validación en PHP y JavaScript. Las capacidades de validación de Nette son mucho más amplias, puedes crear condiciones, mostrar y ocultar partes de una página de acuerdo a ellas, etc. Puedes encontrarlo todo en el capítulo sobre validación de [formularios |validation]. +Con esto, tenemos un formulario completamente funcional con validación en PHP y JavaScript. Las capacidades de validación de Nette son mucho más amplias, se pueden crear condiciones, mostrar y ocultar partes de la página según ellas, etc. Todo lo aprenderá en el capítulo sobre [validación de formularios|validation]. -Valores por defecto .[#toc-default-values] -========================================== +Valores por defecto +=================== -A menudo establecemos valores por defecto para los controles de formulario: +Normalmente establecemos valores por defecto para los elementos del formulario: ```php -$form->addEmail('email', 'Email') +$form->addEmail('email', 'E-mail') ->setDefaultValue($lastUsedEmail); ``` -A menudo es útil establecer valores por defecto para todos los controles a la vez. Por ejemplo, cuando el formulario se utiliza para editar registros. Leemos el registro de la base de datos y lo establecemos como valores por defecto: +A menudo es útil establecer valores por defecto para todos los elementos a la vez. Por ejemplo, cuando el formulario se utiliza para editar registros. Leemos el registro de la base de datos y establecemos los valores por defecto: ```php //$row = ['name' => 'John', 'age' => '33', /* ... */]; $form->setDefaults($row); ``` -Llama a `setDefaults()` después de definir los controles. +Llame a `setDefaults()` después de definir los elementos. -Representación del formulario .[#toc-rendering-the-form] -======================================================== +Renderizado del formulario +========================== -Por defecto, el formulario se muestra como una tabla. Los controles individuales siguen las pautas básicas de accesibilidad web. Todas las etiquetas se generan como elementos `<label>` y están asociadas a sus entradas; al hacer clic en la etiqueta, el cursor se desplaza a la entrada. +Por defecto, el formulario se renderiza como una tabla. Los elementos individuales cumplen la regla básica de accesibilidad: todas las etiquetas se escriben como `<label>` y están vinculadas al elemento de formulario correspondiente. Al hacer clic en la etiqueta, el cursor aparece automáticamente en el campo del formulario. -Podemos establecer cualquier atributo HTML para cada elemento. Por ejemplo, añadir un marcador de posición: +Podemos establecer atributos HTML arbitrarios para cada elemento. Por ejemplo, añadir un placeholder: ```php $form->addInteger('age', 'Edad:') ->setHtmlAttribute('placeholder', 'Por favor, introduzca la edad'); ``` -Realmente hay muchas maneras de renderizar un formulario, así que es un [capítulo dedicado al renderizado |rendering]. +Hay realmente muchas formas de renderizar un formulario, por lo que se dedica a ello un [capítulo separado sobre renderizado|rendering]. -Mapeo a Clases .[#toc-mapping-to-classes] -========================================= +Mapeo a clases +============== -Volvamos al procesamiento de los datos del formulario. El método `getValues()` devuelve los datos enviados como un objeto `ArrayHash`. Al tratarse de una clase genérica, algo así como `stdClass`, careceremos de algunas comodidades a la hora de trabajar con ella, como la compleción de código para propiedades en editores o el análisis estático de código. Esto podría solucionarse teniendo una clase específica para cada formulario, cuyas propiedades representen los controles individuales. Ej: +Volvamos al procesamiento de los datos del formulario. El método `getValues()` nos devolvía los datos enviados como un objeto `ArrayHash`. Dado que es una clase genérica, algo así como `stdClass`, nos faltará cierta comodidad al trabajar con ella, como el autocompletado de propiedades en los editores o el análisis estático de código. Esto podría resolverse teniendo una clase específica para cada formulario, cuyas propiedades representen los elementos individuales. Por ejemplo: ```php class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -A partir de PHP 8.0, puedes usar esta elegante notación que usa un constructor: +Alternativamente, puede utilizar un constructor: ```php class RegistrationFormData @@ -221,16 +220,18 @@ class RegistrationFormData } ``` -¿Cómo decirle a Nette que nos devuelva datos como objetos de esta clase? Más fácil de lo que piensas. Todo lo que tienes que hacer es especificar el nombre de la clase u objeto a hidratar como parámetro: +Las propiedades de la clase de datos también pueden ser enums y se mapearán automáticamente. .{data-version:3.2.4} + +¿Cómo decirle a Nette que nos devuelva los datos como objetos de esta clase? Más fácil de lo que piensa. Simplemente indique el nombre de la clase o el objeto a hidratar como parámetro: ```php $data = $form->getValues(RegistrationFormData::class); $name = $data->name; ``` -También se puede especificar un `'array'` como parámetro, y entonces los datos vuelven como un array. +También se puede indicar `'array'` como parámetro y entonces los datos se devolverán como un array. -Si los formularios consisten en una estructura de varios niveles compuesta por contenedores, cree una clase distinta para cada uno de ellos: +Si los formularios forman una estructura multinivel compuesta por contenedores, cree una clase separada para cada uno: ```php $form = new Form; @@ -247,69 +248,70 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` -El mapeo entonces sabe por el tipo de propiedad `$person` que debe mapear el contenedor a la clase `PersonFormData`. Si la propiedad contiene una matriz de contenedores, proporcione el tipo `array` y pase la clase que debe asignarse directamente al contenedor: +El mapeo entonces, a partir del tipo de la propiedad `$person`, sabe que debe mapear el contenedor a la clase `PersonFormData`. Si la propiedad contuviera un array de contenedores, indique el tipo `array` y pase la clase para el mapeo directamente al contenedor: ```php $person->setMappedType(PersonFormData::class); ``` +Puede generar el diseño de la clase de datos del formulario usando el método `Nette\Forms\Blueprint::dataClass($form)`, que lo imprimirá en la página del navegador. Luego, simplemente seleccione el código haciendo clic y cópielo en su proyecto. .{data-version:3.1.15} + -Botones de envío múltiples .[#toc-multiple-submit-buttons] -========================================================== +Múltiples botones +================= -Si el formulario tiene más de un botón, normalmente necesitamos distinguir cuál ha sido pulsado. El método `isSubmittedBy()` del botón nos devuelve esta información: +Si el formulario tiene más de un botón, generalmente necesitamos distinguir cuál de ellos fue presionado. Esta información nos la devuelve el método `isSubmittedBy()` del botón: ```php -$form->addSubmit('save', 'Save'); -$form->addSubmit('delete', 'Delete'); +$form->addSubmit('save', 'Guardar'); +$form->addSubmit('delete', 'Eliminar'); if ($form->isSuccess()) { if ($form['save']->isSubmittedBy()) { - // ... + // procesar guardar } if ($form['delete']->isSubmittedBy()) { - // ... + // procesar eliminar } } ``` -No omitas el `$form->isSuccess()` para verificar la validez de los datos. +No omita la consulta `$form->isSuccess()`, verifica la validez de los datos. -Cuando se envía un formulario con la tecla <kbd>Intro</kbd>, se trata como si se hubiera enviado con el primer botón. +Cuando el formulario se envía con la tecla <kbd>Enter</kbd>, se considera como si se hubiera enviado con el primer botón. -Protección contra vulnerabilidades .[#toc-vulnerability-protection] -=================================================================== +Protección contra vulnerabilidades +================================== -Nette Framework pone un gran esfuerzo en ser seguro y dado que los formularios son la entrada más común del usuario, los formularios de Nette son tan buenos como impenetrables. +Nette Framework pone gran énfasis en la seguridad y, por lo tanto, se preocupa escrupulosamente por la buena seguridad de los formularios. -Además de proteger los formularios contra ataques de vulnerabilidades bien conocidas como [Cross-Site Scripting (XSS) |nette:glossary#cross-site-scripting-xss] y [Cross-Site Request Forgery (CSRF) |nette:glossary#cross-site-request-forgery-csrf] hace un montón de pequeñas tareas de seguridad en las que ya no tienes que pensar. +Además de proteger los formularios contra ataques [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] y [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF], realiza muchas pequeñas medidas de seguridad en las que ya no tiene que pensar. -Por ejemplo, filtra todos los caracteres de control de las entradas y comprueba la validez de la codificación UTF-8, para que los datos del formulario estén siempre limpios. En el caso de las casillas de selección y las listas de radio, verifica que los elementos seleccionados sean realmente de los ofrecidos y que no haya habido ninguna falsificación. Ya hemos mencionado que para la entrada de texto de una sola línea, elimina los caracteres de final de línea que un atacante podría enviar allí. Para entradas multilínea, normaliza los caracteres de final de línea. Y así sucesivamente. +Por ejemplo, filtra todos los caracteres de control de las entradas y verifica la validez de la codificación UTF-8, por lo que los datos del formulario siempre estarán limpios. En los select boxes y radio lists, verifica que los elementos seleccionados fueran realmente de los ofrecidos y que no hubo suplantación. Ya mencionamos que en las entradas de texto de una sola línea elimina los caracteres de fin de línea que un atacante podría haber enviado. En las entradas de varias líneas, normaliza los caracteres de fin de línea. Y así sucesivamente. -Nette soluciona para usted vulnerabilidades de seguridad que la mayoría de los programadores no tienen ni idea de que existen. +Nette resuelve por usted los riesgos de seguridad que muchos programadores ni siquiera saben que existen. -El mencionado ataque CSRF consiste en que un atacante atrae a la víctima para que visite una página que ejecuta silenciosamente una petición en el navegador de la víctima al servidor donde la víctima está conectada en ese momento, y el servidor cree que la petición ha sido realizada por la víctima a voluntad. Por lo tanto, Nette impide que el formulario sea enviado vía POST desde otro dominio. Si por alguna razón desea desactivar la protección y permitir que el formulario sea enviado desde otro dominio, utilice: +El ataque CSRF mencionado consiste en que un atacante atrae a la víctima a una página que ejecuta discretamente una solicitud en el navegador de la víctima al servidor en el que la víctima está conectada, y el servidor cree que la solicitud fue realizada por la víctima por su propia voluntad. Por lo tanto, Nette evita el envío de formularios POST desde otro dominio. Si por alguna razón desea desactivar la protección y permitir el envío de formularios desde otro dominio, use: ```php -$form->allowCrossOrigin(); // ¡ATENCIÓN! ¡Desactiva la protección! +$form->allowCrossOrigin(); // ¡CUIDADO! Desactiva la protección! ``` -Esta protección utiliza una cookie SameSite llamada `_nss`. Por lo tanto, cree un formulario antes de enviar la primera salida para que la cookie pueda ser enviada. +Esta protección utiliza una cookie SameSite llamada `_nss`. Por lo tanto, cree el objeto de formulario antes de enviar la primera salida, para que la cookie pueda ser enviada. -La protección de cookies SameSite puede no ser 100% fiable, por lo que es una buena idea activar la protección de token: +La protección mediante la cookie SameSite puede no ser 100% fiable, por lo que es recomendable activar también la protección mediante token: ```php $form->addProtection(); ``` -Es muy recomendable aplicar esta protección a los formularios en una parte administrativa de su aplicación que cambie datos sensibles. El framework protege contra un ataque CSRF generando y validando el token de autenticación que se almacena en una sesión (el argumento es el mensaje de error que se muestra si el token ha caducado). Por eso es necesario tener una sesión iniciada antes de mostrar el formulario. En la parte de administración del sitio web, la sesión suele estar ya iniciada, debido al login del usuario. -En caso contrario, inicie la sesión con el método `Nette\Http\Session::start()`. +Recomendamos proteger de esta manera los formularios en la parte de administración del sitio web que modifican datos sensibles en la aplicación. El framework se defiende contra el ataque CSRF generando y verificando un token de autorización que se almacena en la sesión. Por lo tanto, es necesario tener la sesión abierta antes de mostrar el formulario. En la parte de administración del sitio web, la sesión generalmente ya está iniciada debido al inicio de sesión del usuario. De lo contrario, inicie la sesión con el método `Nette\Http\Session::start()`. -Así, tenemos una rápida introducción a los formularios en Nette. Intente buscar en el directorio de [ejemplos |https://github.com/nette/forms/tree/master/examples] en la distribución para más inspiración. +Bien, hemos cubierto una introducción rápida a los formularios en Nette. Intente también mirar el directorio [examples|https://github.com/nette/forms/tree/master/examples] en la distribución, donde encontrará más inspiración. diff --git a/forms/es/validation.texy b/forms/es/validation.texy index 9b7e35bc31..f3e6d61efd 100644 --- a/forms/es/validation.texy +++ b/forms/es/validation.texy @@ -2,196 +2,207 @@ Validación de formularios ************************* -Controles obligatorios .[#toc-required-controls] -================================================ +Elementos obligatorios +====================== -Los controles se marcan como obligatorios con el método `setRequired()`, cuyo argumento es el texto del [mensaje |#Error Messages] de error que se mostrará si el usuario no lo rellena. Si no se da ningún argumento, se utiliza el mensaje de error por defecto. +Marcamos los elementos obligatorios con el método `setRequired()`, cuyo argumento es el texto del [mensaje de error |#Mensajes de error] que se mostrará si el usuario no rellena el elemento. Si no se proporciona el argumento, se utilizará el mensaje de error predeterminado. ```php $form->addText('name', 'Nombre:') - ->setRequired('Por favor, introduzca su nombre.'); + ->setRequired('Por favor, introduzca su nombre'); ``` -Reglas .[#toc-rules] -==================== +Reglas +====== -Añadimos reglas de validación a los controles con el método `addRule()`. El primer parámetro es la regla, el segundo es el [mensaje de error |#Error Messages] y el tercero es el argumento de la regla de validación. +Añadimos reglas de validación a los elementos con el método `addRule()`. El primer parámetro es la regla, el segundo es el texto del [mensaje de error |#Mensajes de error] y el tercero es el argumento de la regla de validación. ```php $form->addPassword('password', 'Contraseña:') ->addRule($form::MinLength, 'La contraseña debe tener al menos %d caracteres', 8); ``` -Nette viene con una serie de reglas incorporadas cuyos nombres son constantes de la clase `Nette\Forms\Form`: +**Las reglas de validación solo se verifican si el usuario ha rellenado el elemento.** -Podemos utilizar las siguientes reglas para todos los controles: +Nette viene con una serie de reglas predefinidas, cuyos nombres son constantes de la clase `Nette\Forms\Form`. Podemos usar estas reglas para todos los elementos: -| constante | descripción | argumentos +| constante | descripción | tipo de argumento |------- -| `Required` | alias de `setRequired()` | - -| `Filled` | Alias de `setRequired()` -| `Blank` | no debe rellenarse | - -| `Equal` | valor es igual al parámetro | `mixed` -| `NotEqual` | valor no es ser igual a parámetro | `mixed` -| `IsIn` | valor es igual a algún elemento del array | `array` -| `IsNotIn` | El valor no es igual a ningún elemento de la matriz. `array` -| `Valid` | la entrada pasa la validación (para [condiciones |#conditions]) | - - -Para los controles `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` también se pueden utilizar las siguientes reglas: - -| `MinLength` | longitud mínima de cadena | `int` -| `MaxLength` | longitud máxima de cadena | `int` -| `Length` | longitud en el rango o longitud exacta | par `[int, int]` o `int` +| `Required` | elemento obligatorio, alias para `setRequired()` | - +| `Filled` | elemento obligatorio, alias para `setRequired()` | - +| `Blank` | el elemento no debe ser rellenado | - +| `Equal` | el valor es igual al parámetro | `mixed` +| `NotEqual` | el valor no es igual al parámetro | `mixed` +| `IsIn` | el valor es igual a uno de los elementos del array | `array` +| `IsNotIn` | el valor no es igual a ninguno de los elementos del array | `array` +| `Valid` | ¿está el elemento rellenado correctamente? (para [#condiciones]) | - + + +Entradas de texto +----------------- + +Para los elementos `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`, `addFloat()`, también se pueden usar algunas de las siguientes reglas: + +| `MinLength` | longitud mínima del texto | `int` +| `MaxLength` | longitud máxima del texto | `int` +| `Length` | longitud en un rango o longitud exacta | par `[int, int]` o `int` | `Email` | dirección de correo electrónico válida | - -| `URL` | URL válida | - -| `Pattern` | coincide con patrón regular | `string` -| `PatternInsensitive` | como `Pattern`, pero sin distinguir mayúsculas de minúsculas | `string` -| `Integer` | número entero | - -| `Numeric` | alias de `Integer` | - -| `Float` | número entero o de coma flotante | - -| `MIN` | mínimo del valor entero | `int\|float` -| `MAX` | máximo del valor entero | `int\|float` +| `URL` | URL absoluta | - +| `Pattern` | coincide con la expresión regular | `string` +| `PatternInsensitive` | como `Pattern`, pero insensible a mayúsculas/minúsculas | `string` +| `Integer` | valor entero | - +| `Numeric` | alias para `Integer` | - +| `Float` | número | - +| `Min` | valor mínimo del elemento numérico | `int\|float` +| `Max` | valor máximo del elemento numérico | `int\|float` | `Range` | valor en el rango | par `[int\|float, int\|float]` -Las reglas `Integer`, `Numeric` a `Float` convierten automáticamente el valor a entero (o flotante respectivamente). Además, la regla `URL` también acepta una dirección sin esquema (por ejemplo, `nette.org`) y completa el esquema (`https://nette.org`). -Las expresiones de `Pattern` y `PatternInsensitive` deben ser válidas para todo el valor, es decir, como si estuviera envuelto en los caracteres `^` and `$`. +Las reglas de validación `Integer`, `Numeric` y `Float` convierten directamente el valor a entero o flotante, respectivamente. Además, la regla `URL` también acepta una dirección sin esquema (p. ej., `nette.org`) y añade el esquema (`https://nette.org`). La expresión en `Pattern` y `PatternIcase` debe aplicarse a todo el valor, es decir, como si estuviera envuelta en los caracteres `^` y `$`. + -Para los controles `addUpload()`, `addMultiUpload()` también se pueden utilizar las siguientes reglas: +Número de elementos +------------------- -| `MaxFileSize` | tamaño máximo del archivo | `int` -| `MimeType` | tipo de MIME, acepta comodines (`'video/*'`) | `string\|string[]` -| `Image` | el archivo cargado es JPEG, PNG, GIF, WebP | - -| `Pattern` | nombre de archivo coincide con expresión regular | `string` -| `PatternInsensitive` | como `Pattern`, pero sin distinguir entre mayúsculas y minúsculas | `string` +Para los elementos `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()`, también se pueden usar las siguientes reglas para limitar el número de elementos seleccionados o archivos cargados: -`MimeType` y `Image` requieren la extensión PHP `fileinfo`. Si un archivo o imagen es del tipo requerido se detecta por su firma. No se comprueba la integridad de todo el archivo. Puede averiguar si una imagen no está dañada, por ejemplo, intentando [cargarla |http:request#toImage]. +| `MinLength` | número mínimo | `int` +| `MaxLength` | número máximo | `int` +| `Length` | número en un rango o número exacto | par `[int, int]` o `int` -Para los controles `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()` también se pueden utilizar las siguientes reglas para limitar el número de elementos seleccionados, respectivamente archivos cargados: -| `MinLength` | recuento mínimo | `int` -| `MaxLength` | recuento máximo `int` -| `Length` | recuento en rango o recuento exacto | par `[int, int]` o `int` +Carga de archivos +----------------- +Para los elementos `addUpload()`, `addMultiUpload()`, también se pueden usar las siguientes reglas: -Mensajes de error .[#toc-error-messages] ----------------------------------------- +| `MaxFileSize` | tamaño máximo del archivo en bytes | `int` +| `MimeType` | Tipo MIME, se permiten comodines (`'video/*'`) | `string\|string[]` +| `Image` | imagen JPEG, PNG, GIF, WebP, AVIF | - +| `Pattern` | el nombre del archivo coincide con la expresión regular | `string` +| `PatternInsensitive` | como `Pattern`, pero insensible a mayúsculas/minúsculas | `string` -Todas las reglas predefinidas excepto `Pattern` y `PatternInsensitive` tienen un mensaje de error por defecto, por lo que pueden omitirse. Sin embargo, al pasar y formular todos los mensajes personalizados, hará que el formulario sea más fácil de usar. +`MimeType` e `Image` requieren la extensión PHP `fileinfo`. Detectan si el archivo o imagen es del tipo requerido basándose en su firma y **no verifican la integridad de todo el archivo.** Si una imagen está dañada se puede detectar, por ejemplo, intentando [cargarla |http:request#toImage]. -Puede cambiar los mensajes por defecto en [forms:configuration], modificando los textos de la matriz `Nette\Forms\Validator::$messages` o utilizando [el traductor |rendering#translating]. -Se pueden utilizar los siguientes comodines en el texto de los mensajes de error: +Mensajes de error +================= -| `%d` | reemplaza gradualmente las reglas después de los argumentos -| `%n$d` | sustituye por el enésimo argumento de regla -| `%label` | sustituye por la etiqueta del campo (sin dos puntos) -| `%name` | sustituye por el nombre del campo (por ejemplo, `name`) -| `%value` | sustituye por el valor introducido por el usuario. +Todas las reglas predefinidas, excepto `Pattern` y `PatternInsensitive`, tienen un mensaje de error predeterminado, por lo que se puede omitir. Sin embargo, al proporcionar y formular todos los mensajes a medida, hará que el formulario sea más fácil de usar. + +Puede cambiar los mensajes predeterminados en la [configuración|forms:configuration], editando los textos en el array `Nette\Forms\Validator::$messages` o usando un [traductor |rendering#Traducción]. + +En el texto de los mensajes de error se pueden usar estas cadenas de marcador de posición: + +| `%d` | reemplaza secuencialmente con los argumentos de la regla +| `%n$d` | reemplaza con el n-ésimo argumento de la regla +| `%label` | reemplaza con la etiqueta del elemento (sin dos puntos) +| `%name` | reemplaza con el nombre del elemento (p. ej. `name`) +| `%value` | reemplaza con el valor introducido por el usuario ```php $form->addText('name', 'Nombre:') ->setRequired('Por favor, rellene %label'); $form->addInteger('id', 'ID:') - ->addRule($form::Range, 'Al menos %d y no más de %d', [5, 10]); + ->addRule($form::Range, 'al menos %d y como máximo %d', [5, 10]); $form->addInteger('id', 'ID:') - ->addRule($form::Range, 'No más de %2$d y al menos %1$d', [5, 10]); + ->addRule($form::Range, 'como máximo %2$d y al menos %1$d', [5, 10]); ``` -Condiciones .[#toc-conditions] -============================== +Condiciones +=========== -Además de las reglas de validación, se pueden establecer condiciones. Se establecen de forma muy parecida a las reglas, aunque utilizamos `addRule()` en lugar de `addCondition()` y, por supuesto, lo dejamos sin mensaje de error (la condición sólo pregunta): +Además de las reglas, también se pueden añadir condiciones. Se escriben de forma similar a las reglas, solo que en lugar de `addRule()` usamos el método `addCondition()` y, por supuesto, no proporcionamos ningún mensaje de error (la condición solo pregunta): ```php $form->addPassword('password', 'Contraseña:') - // si la contraseña no tiene más de 8 caracteres ... + // si la contraseña no tiene más de 8 caracteres ->addCondition($form::MaxLength, 8) - // ... entonces debe contener un número - ->addRule($form::Pattern, 'Debe contener número', '.*[0-9].*'); + // entonces debe contener un dígito + ->addRule($form::Pattern, 'Debe contener un dígito', '.*[0-9].*'); ``` -La condición puede vincularse a un elemento distinto del actual utilizando `addConditionOn()`. El primer parámetro es una referencia al campo. En el siguiente caso, el correo electrónico sólo será necesario si la casilla de verificación está marcada (es decir, su valor es `true`): +La condición también se puede vincular a un elemento diferente al actual usando `addConditionOn()`. Como primer parámetro, proporcionamos una referencia al elemento. En este ejemplo, el correo electrónico será obligatorio solo si se marca la casilla de verificación (su valor será `true`): ```php -$form->addCheckbox('boletines', 'enviarme boletines'); +$form->addCheckbox('newsletters', 'Enviarme boletines'); -$form->addEmail('email', 'Email:') - // si la casilla está marcada ... +$form->addEmail('email', 'E-mail:') + // si la casilla de verificación está marcada ->addConditionOn($form['newsletters'], $form::Equal, true) - // ... requiere email - ->setRequired('Introduzca su dirección de correo electrónico'); + // entonces requiere el correo electrónico + ->setRequired('Introduzca una dirección de correo electrónico'); ``` -Las condiciones pueden agruparse en estructuras complejas con los métodos `elseCondition()` y `endCondition()`. +Se pueden crear estructuras complejas a partir de condiciones usando `elseCondition()` y `endCondition()`: ```php $form->addText(/* ... */) ->addCondition(/* ... */) // si se cumple la primera condición - ->addConditionOn(/* ... */) // y la segunda condición en otro elemento también + ->addConditionOn(/* ... */) // y la segunda condición en otro elemento ->addRule(/* ... */) // requiere esta regla - ->elseCondition() // si no se cumple la segunda condición + ->elseCondition() // si la segunda condición no se cumple ->addRule(/* ... */) // requiere estas reglas ->addRule(/* ... */) ->endCondition() // volvemos a la primera condición ->addRule(/* ... */); ``` -En Nette, es muy fácil reaccionar al cumplimiento o no de una condición en la parte JavaScript utilizando el método `toggle()`, véase [JavaScript dinámico |#Dynamic JavaScript]. +En Nette, es muy fácil reaccionar al cumplimiento o incumplimiento de una condición también en el lado de JavaScript usando el método `toggle()`, consulte [#JavaScript dinámico]. -Referencias entre Controles .[#toc-references-between-controls] -=============================================================== +Referencia a otro elemento +========================== -El argumento de la regla o condición puede ser una referencia a otro elemento. Por ejemplo, puede validar dinámicamente que `text` tenga tantos caracteres como el valor del campo `length`: +También se puede pasar otro elemento del formulario como argumento de una regla o condición. La regla entonces usará el valor introducido posteriormente por el usuario en el navegador. De esta manera, se puede validar dinámicamente, por ejemplo, que el elemento `password` contenga la misma cadena que el elemento `password_confirm`: ```php -$form->addInteger('length'); -$form->addText('text') - ->addRule($form::Length, null, $form['length']); +$form->addPassword('password', 'Contraseña'); +$form->addPassword('password_confirm', 'Confirmar contraseña') + ->addRule($form::Equal, 'Las contraseñas introducidas no coinciden', $form['password']); ``` -Reglas y condiciones personalizadas .[#toc-custom-rules-and-conditions] -======================================================================= +Reglas y condiciones personalizadas +=================================== -A veces nos encontramos en una situación en la que las reglas de validación incorporadas en Nette no son suficientes y necesitamos validar los datos del usuario a nuestra manera. En Nette esto es muy fácil. +A veces nos encontramos en una situación en la que las reglas de validación incorporadas en Nette no son suficientes y necesitamos validar los datos del usuario a nuestra manera. ¡En Nette esto es muy simple! -Puede pasar cualquier callback como primer parámetro a los métodos `addRule()` o `addCondition()`. El callback acepta el propio elemento como primer parámetro y devuelve un valor booleano que indica si la validación se ha realizado correctamente. Cuando se añade una regla utilizando `addRule()`, se pueden pasar argumentos adicionales, que se pasan como segundo parámetro. +Se puede pasar cualquier callback como primer parámetro a los métodos `addRule()` o `addCondition()`. Este recibe el propio elemento como primer parámetro y devuelve un valor booleano que indica si la validación se realizó correctamente. Al añadir una regla con `addRule()`, también es posible especificar argumentos adicionales, que luego se pasan como segundo parámetro. -De este modo, el conjunto personalizado de validadores puede crearse como una clase con métodos estáticos: +Así podemos crear nuestro propio conjunto de validadores como una clase con métodos estáticos: ```php class MyValidators { // comprueba si el valor es divisible por el argumento - public static function validateDivisibility(BaseControl $input, $arg): bool + public static function validateDivisibility(Nette\Forms\Control $input, $arg): bool { return $input->getValue() % $arg === 0; } - public static function validateEmailDomain(BaseControl $input, $domain) + public static function validateEmailDomain(Nette\Forms\Control $input, $domain) { - // validadores adicionales + // otros validadores } } ``` -El uso es muy sencillo: +El uso es entonces muy simple: ```php $form->addInteger('num') ->addRule( [MyValidators::class, 'validateDivisibility'], - 'The value must be a multiple of %d', + 'El valor debe ser un múltiplo de %d', 8, ); ``` -Las reglas de validación personalizadas también se pueden añadir a JavaScript. El único requisito es que la regla sea un método estático. Su nombre para el validador JavaScript se crea concatenando el nombre de la clase sin barras invertidas `\`, the underscore `_`, y el nombre del método. Por ejemplo, escriba `App\MyValidators::validateDivisibility` como `AppMyValidators_validateDivisibility` y añádalo al objeto `Nette.validators`: +Las reglas de validación personalizadas también se pueden añadir a JavaScript. La condición es que la regla sea un método estático. Su nombre para el validador JavaScript se crea concatenando el nombre de la clase sin barras invertidas `\`, un guion bajo `_` y el nombre del método. Por ejemplo, `App\MyValidators::validateDivisibility` se escribe como `AppMyValidators_validateDivisibility` y se añade al objeto `Nette.validators`: ```js Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => { @@ -200,12 +211,12 @@ Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => ``` -Evento onValidate .[#toc-event-onvalidate] -========================================== +Evento onValidate +================= -Una vez enviado el formulario, la validación se realiza comprobando las reglas individuales añadidas por `addRule()` y llamando al [evento |nette:glossary#Events] `onValidate`. Su manejador puede ser usado para validación adicional, típicamente para verificar la correcta combinación de valores en múltiples elementos del formulario. +Después de enviar el formulario, se realiza la validación, donde se comprueban las reglas individuales añadidas mediante `addRule()` y luego se dispara el [evento |nette:glossary#Eventos] `onValidate`. Su handler se puede usar para validación adicional, típicamente para verificar la combinación correcta de valores en múltiples elementos del formulario. -Si se detecta un error, se pasa al formulario utilizando el método `addError()`. Este puede ser llamado en un elemento específico o directamente en el formulario. +Si se detecta un error, lo pasamos al formulario con el método `addError()`. Se puede llamar en un elemento específico o directamente en el formulario. ```php protected function createComponentSignInForm(): Form @@ -219,16 +230,16 @@ protected function createComponentSignInForm(): Form public function validateSignInForm(Form $form, \stdClass $data): void { if ($data->foo > 1 && $data->bar > 5) { - $form->addError('This combination is not possible.'); + $form->addError('Esta combinación no es posible.'); } } ``` -Tratamiento de errores .[#toc-processing-errors] -================================================ +Errores durante el procesamiento +================================ -En muchos casos, descubrimos un error cuando estamos procesando un formulario válido, por ejemplo, cuando escribimos una nueva entrada en la base de datos y nos encontramos con una clave duplicada. En este caso, devolvemos el error al formulario utilizando el método `addError()`. Este método puede ejecutarse en un elemento específico o directamente en el formulario: +En muchos casos, nos enteramos del error solo cuando procesamos un formulario válido, por ejemplo, al escribir un nuevo elemento en la base de datos y encontrar una duplicidad de claves. En tal caso, nuevamente pasamos el error al formulario con el método `addError()`. Se puede llamar en un elemento específico o directamente en el formulario: ```php try { @@ -238,72 +249,71 @@ try { } catch (Nette\Security\AuthenticationException $e) { if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) { - $form->addError('Invalid password.'); + $form->addError('Contraseña inválida.'); } } ``` -Si es posible, recomendamos añadir el error directamente al elemento del formulario, ya que aparecerá junto a él cuando se utilice el renderizador por defecto. +Si es posible, recomendamos adjuntar el error directamente al elemento del formulario, ya que se mostrará junto a él al usar el renderizador predeterminado. ```php -$form['date']->addError('Sorry, this date is already taken.'); +$form['date']->addError('Lo sentimos, pero esta fecha ya está ocupada.'); ``` -Puedes llamar repetidamente a `addError()` para pasar múltiples mensajes de error a un formulario o elemento. Se obtienen con `getErrors()`. +Puede llamar a `addError()` repetidamente para pasar múltiples mensajes de error al formulario o elemento. Los obtiene usando `getErrors()`. -Tenga en cuenta que `$form->getErrors()` devuelve un resumen de todos los mensajes de error, incluso los pasados directamente a elementos individuales, no sólo directamente al formulario. Los mensajes de error pasados sólo al formulario se recuperan mediante `$form->getOwnErrors()`. +Tenga en cuenta que `$form->getErrors()` devuelve un resumen de todos los mensajes de error, incluidos los que se pasaron directamente a elementos individuales, no solo directamente al formulario. Los mensajes de error pasados solo al formulario se obtienen a través de `$form->getOwnErrors()`. -Modificación de los valores de entrada .[#toc-modifying-input-values] -===================================================================== +Modificación de la entrada +========================== -Utilizando el método `addFilter()`, podemos modificar el valor introducido por el usuario. En este ejemplo, toleraremos y eliminaremos espacios en el código postal: +Usando el método `addFilter()`, podemos modificar el valor introducido por el usuario. En este ejemplo, toleraremos y eliminaremos los espacios en el código postal: ```php -$form->addText('zip', 'Postcode:') +$form->addText('zip', 'Código Postal:') ->addFilter(function ($value) { - return str_replace(' ', '', $value); // remove spaces from the postcode + return str_replace(' ', '', $value); // eliminamos los espacios del código postal }) - ->addRule($form::Pattern, 'The postal code is not five digits', '\d{5}'); + ->addRule($form::Pattern, 'El código postal no tiene el formato de cinco dígitos', '\d{5}'); ``` -El filtro se incluye entre las reglas de validación y las condiciones y por lo tanto depende del orden de los métodos, es decir, el filtro y la regla se llaman en el mismo orden que el de los métodos `addFilter()` y `addRule()`. +El filtro se integra entre las reglas y condiciones de validación, por lo que el orden de los métodos importa, es decir, el filtro y la regla se llaman en el mismo orden que los métodos `addFilter()` y `addRule()`. -Validación JavaScript .[#toc-javascript-validation] -=================================================== +Validación JavaScript +===================== -El lenguaje de reglas y condiciones de validación es poderoso. Aunque todas las construcciones funcionan tanto del lado del servidor como del lado del cliente, en JavaScript. Las reglas se transfieren en atributos HTML `data-nette-rules` como JSON. -La validación en sí es manejada por otro script, que engancha todos los eventos del formulario `submit`, itera sobre todas las entradas y ejecuta las validaciones respectivas. +El lenguaje para formular condiciones y reglas es muy potente. Todas las construcciones funcionan tanto en el lado del servidor como en el lado de JavaScript. Se transmiten en atributos HTML `data-nette-rules` como JSON. La validación en sí la realiza un script que captura el evento `submit` del formulario, recorre los elementos individuales y realiza la validación correspondiente. -Este script es `netteForms.js`, que está disponible en varias fuentes posibles: +Ese script es `netteForms.js` y está disponible desde múltiples fuentes posibles: -Puedes incrustar el script directamente en la página HTML desde el CDN: +Puede insertar el script directamente en la página HTML desde un CDN: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -O copiarlo localmente en la carpeta pública del proyecto (por ejemplo, desde `vendor/nette/forms/src/assets/netteForms.min.js`): +O copiarlo localmente a la carpeta pública del proyecto (p. ej., desde `vendor/nette/forms/src/assets/netteForms.min.js`): ```latte <script src="/path/to/netteForms.min.js"></script> ``` -O instalar a través de [npm |https://www.npmjs.com/package/nette-forms]: +O instalarlo a través de [npm|https://www.npmjs.com/package/nette-forms]: ```shell npm install nette-forms ``` -Y luego cargar y ejecutar: +Y luego cargarlo y ejecutarlo: ```js import netteForms from 'nette-forms'; netteForms.initOnLoad(); ``` -También puede cargarlo directamente desde la carpeta `vendor`: +Alternativamente, puede cargarlo directamente desde la carpeta `vendor`: ```js import netteForms from '../path/to/vendor/nette/forms/src/assets/netteForms.js'; @@ -311,10 +321,10 @@ netteForms.initOnLoad(); ``` -JavaScript dinámico .[#toc-dynamic-javascript] -============================================== +JavaScript dinámico +=================== -¿Quiere mostrar los campos de dirección sólo si el usuario elige enviar la mercancía por correo? No hay problema. La clave es un par de métodos `addCondition()` & `toggle()`: +¿Quiere mostrar los campos para introducir la dirección solo si el usuario elige enviar la mercancía por correo? No hay problema. La clave es el par de métodos `addCondition()` & `toggle()`: ```php $form->addCheckbox('send_it') @@ -322,25 +332,25 @@ $form->addCheckbox('send_it') ->toggle('#address-container'); ``` -Este código dice que cuando se cumple la condición, es decir, cuando la casilla de verificación está marcada, el elemento HTML `#address-container` será visible. Y viceversa. Así, colocamos los elementos del formulario con la dirección del destinatario en un contenedor con ese ID, y cuando se pulsa la casilla de verificación, se ocultan o se muestran. De esto se encarga el script `netteForms.js`. +Este código dice que cuando se cumple la condición, es decir, cuando la casilla de verificación está marcada, el elemento HTML `#address-container` será visible. Y viceversa. Así, colocamos los elementos del formulario con la dirección del destinatario en un contenedor con este ID y al hacer clic en la casilla de verificación se ocultarán o mostrarán. Esto lo asegura el script `netteForms.js`. -Se puede pasar cualquier selector como argumento al método `toggle()`. Por razones históricas, una cadena alfanumérica sin otros caracteres especiales se trata como un ID de elemento, igual que si fuera precedida por el `#` character. The second optional parameter allows us to reverse the behavior, i.e. if we used `toggle('#address-container', false)`, el elemento se mostraría sólo si la casilla de verificación estuviera desmarcada. +Como argumento del método `toggle()`, se puede pasar cualquier selector. Por razones históricas, una cadena alfanumérica sin otros caracteres especiales se entiende como el ID del elemento, es decir, igual que si le precediera el carácter `#`. El segundo parámetro opcional permite invertir el comportamiento, es decir, si usáramos `toggle('#address-container', false)`, el elemento se mostraría solo si la casilla de verificación no estuviera marcada. -La implementación por defecto de JavaScript cambia la propiedad `hidden` para los elementos. Sin embargo, podemos cambiar fácilmente el comportamiento, por ejemplo añadiendo una animación. Basta con anular el método `Nette.toggle` en JavaScript con una solución personalizada: +La implementación predeterminada en JavaScript cambia la propiedad `hidden` de los elementos. Sin embargo, podemos cambiar fácilmente el comportamiento, por ejemplo, añadir una animación. Simplemente sobrescriba el método `Nette.toggle` en JavaScript con su propia solución: ```js Nette.toggle = (selector, visible, srcElement, event) => { document.querySelectorAll(selector).forEach((el) => { - // ocultar o mostrar 'el' según el valor de 'visible + // ocultamos o mostramos 'el' según el valor de 'visible' }); }; ``` -Desactivación de la validación .[#toc-disabling-validation] -=========================================================== +Desactivación de la validación +============================== -En algunos casos, es necesario desactivar la validación. Si un botón de envío no se supone que ejecute la validación después del envío (por ejemplo *Cancelar* o *Preview*), puedes desactivar la validación llamando a `$submit->setValidationScope([])`. También puede validar el formulario parcialmente especificando los elementos a validar. +A veces puede ser útil desactivar la validación. Si al presionar un botón de envío no se debe realizar la validación (adecuado para botones *Cancelar* o *Vista previa*), la desactivamos con el método `$submit->setValidationScope([])`. Si debe realizar solo una validación parcial, podemos especificar qué campos o contenedores de formulario se deben validar. ```php $form->addText('name') @@ -356,11 +366,11 @@ $form->addSubmit('send1'); // Valida todo el formulario $form->addSubmit('send2') ->setValidationScope([]); // No valida nada $form->addSubmit('send3') - ->setValidationScope([$form['nombre']]); // Valida sólo el campo 'nombre + ->setValidationScope([$form['name']]); // Valida solo el elemento name $form->addSubmit('send4') - ->setValidationScope([$form['details']['age']]); // Valida sólo el campo 'edad + ->setValidationScope([$form['details']['age']]); // Valida solo el elemento age $form->addSubmit('send5') - ->setValidationScope([$form['detalles']]); // Valida el contenedor 'detalles + ->setValidationScope([$form['details']]); // Valida el contenedor details ``` -El evento [onValidate |#Event onValidate] en el formulario se invoca siempre y no se ve afectado por `setValidationScope`. El evento `onValidate` en el contenedor se invoca sólo cuando este contenedor está especificado para validación parcial. +`setValidationScope` no afecta al [#evento onValidate] del formulario, que siempre será llamado. El evento `onValidate` de un contenedor solo se activará si ese contenedor está marcado para validación parcial. diff --git a/forms/fr/@home.texy b/forms/fr/@home.texy index 58ba918e5b..d5fdcc6546 100644 --- a/forms/fr/@home.texy +++ b/forms/fr/@home.texy @@ -1,23 +1,23 @@ -Formulaires +Nette Forms *********** <div class=perex> -Nette Forms a révolutionné la création de formulaires Web. Tout ce que vous aviez à faire était d'écrire quelques lignes de code claires et vous aviez un formulaire, y compris le rendu, le JavaScript et la validation du serveur, plus une sécurité de pointe. Voyons comment +Nette Forms a révolutionné la création de formulaires web. Soudain, il suffisait d'écrire quelques lignes de code compréhensibles pour avoir un formulaire complet, y compris le rendu, la validation JavaScript et côté serveur, et en plus, extrêmement sécurisé. Nous allons vous montrer comment - créer des formulaires conviviaux -- valider les données envoyées -- dessiner les éléments exactement comme il faut +- valider les données soumises +- rendre les éléments exactement selon les besoins </div> -Avec Nette Forms, vous pouvez diminuer les tâches de routine comme l'écriture de la validation (tant du côté serveur que du côté client), et minimiser la probabilité d'erreurs et de problèmes de sécurité. +En utilisant Nette Forms, vous éviterez de nombreuses tâches routinières, comme l'écriture de la validation (de plus, double, côté serveur et client), vous minimiserez la probabilité d'erreurs et de failles de sécurité. -Vous pouvez utiliser les formulaires comme une partie de l'application Nette (c'est-à-dire dans les présentateurs) ou de façon autonome. Comme l'utilisation est légèrement différente dans les deux cas, nous avons préparé des instructions séparées pour vous : +Vous pouvez utiliser les formulaires soit comme partie intégrante de l'Application Nette (c'est-à-dire dans les presenters), soit de manière totalement autonome. Comme l'utilisation diffère légèrement dans les deux cas, nous avons préparé deux tutoriels pour vous : <div class="wiki-buttons"> -<div> "Formulaires dans les présentateurs .[wiki-button]":in-presenter </div> +<div> "Formulaires dans les presenters .[wiki-button]":in-presenter </div> <div> "Formulaires autonomes .[wiki-button]":standalone </div> </div> @@ -25,7 +25,7 @@ Vous pouvez utiliser les formulaires comme une partie de l'application Nette (c' Installation ------------ -Téléchargez et installez le paquet en utilisant [Composer |best-practices:composer]: +Téléchargez et installez la bibliothèque à l'aide de l'outil [Composer|best-practices:composer] : ```shell composer require nette/forms diff --git a/forms/fr/@left-menu.texy b/forms/fr/@left-menu.texy index 485a8260ea..cc724a0d4c 100644 --- a/forms/fr/@left-menu.texy +++ b/forms/fr/@left-menu.texy @@ -1,14 +1,14 @@ -Formulaires +Nette Forms *********** -- [Vue d'ensemble |@home] -- [Formulaires dans Presenters |in-presenter] +- [Introduction |@home] +- [Formulaires dans les presenters |in-presenter] - [Formulaires autonomes |standalone] -- [Contrôles de formulaires |controls] +- [Éléments de formulaire |controls] - [Validation |validation] - [Rendu |rendering] -- [Configuration] +- [Configuration |configuration] -Autres lectures -*************** -- [Meilleures pratiques |best-practices:] +Lectures complémentaires +************************ +- [Tutoriels et bonnes pratiques |best-practices:] diff --git a/forms/fr/@meta.texy b/forms/fr/@meta.texy new file mode 100644 index 0000000000..72ae4b8db8 --- /dev/null +++ b/forms/fr/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentation Nette}} diff --git a/forms/fr/configuration.texy b/forms/fr/configuration.texy index c32f952744..b649796647 100644 --- a/forms/fr/configuration.texy +++ b/forms/fr/configuration.texy @@ -2,7 +2,7 @@ Configuration des formulaires ***************************** .[perex] -Vous pouvez modifier les [messages d'erreur |validation] par défaut des [formulaires |validation] dans la configuration. +Dans la configuration, il est possible de modifier les [messages d'erreur des formulaires|validation] par défaut. ```neon forms: @@ -36,26 +36,26 @@ Voici la traduction française : forms: messages: Equal: 'Veuillez entrer %s.' - NotEqual: 'Cette valeur ne doit pas être %s.' - Filled: 'Ce champ est obligatoire.' - Blank: 'Ce champ doit être vide.' - MinLength: 'Veuillez saisir au moins %d caractères.' - MaxLength: 'Veuillez saisir un maximum de %d caractères.' - Length: 'Veuillez saisir une valeur comprise entre %d et %d caractères.' - Email: 'Veuillez saisir une adresse électronique valide.' - URL: 'Veuillez saisir une URL valide.' - Integer: 'Veuillez saisir un nombre entier valide.' + NotEqual: 'Cette valeur ne devrait pas être %s.' + Filled: 'Ce champ est requis.' + Blank: 'Ce champ devrait être vide.' + MinLength: 'Veuillez entrer au moins %d caractères.' + MaxLength: 'Veuillez ne pas entrer plus de %d caractères.' + Length: 'Veuillez entrer une valeur de %d à %d caractères.' + Email: 'Veuillez entrer une adresse e-mail valide.' + URL: 'Veuillez entrer une URL valide.' + Integer: 'Veuillez entrer un entier valide.' Float: 'Veuillez entrer un nombre valide.' Min: 'Veuillez entrer une valeur supérieure ou égale à %d.' Max: 'Veuillez entrer une valeur inférieure ou égale à %d.' Range: 'Veuillez entrer une valeur entre %d et %d.' - MaxFileSize: 'La taille du fichier téléchargé peut aller jusqu''à %d bytes.' + MaxFileSize: 'La taille du fichier téléchargé peut être au maximum de %d octets.' MaxPostSize: 'Les données téléchargées dépassent la limite de %d octets.' - MimeType: 'Le fichier téléchargé n''a pas le format attendu.' - Image: 'Le fichier téléchargé doit être une image au format JPEG, GIF, PNG ou WebP.' + MimeType: 'Le fichier téléchargé n\'est pas dans le format attendu.' + Image: 'Le fichier téléchargé doit être une image au format JPEG, GIF, PNG, WebP ou AVIF.' Nette\Forms\Controls\SelectBox::Valid: 'Veuillez sélectionner une option valide.' - Nette\Forms\Controls\UploadControl::Valid: 'Une erreur s''est produite lors du téléchargement du fichier.' - Nette\Forms\Controls\CsrfProtection::Protection: 'Votre session a expiré. Veuillez retourner à la page d''accueil et réessayer.' + Nette\Forms\Controls\UploadControl::Valid: 'Une erreur est survenue lors du téléchargement du fichier.' + Nette\Forms\Controls\CsrfProtection::Protection: 'Votre session a expiré. Veuillez retourner à la page d\'accueil et réessayer.' ``` -Si vous n'utilisez pas l'ensemble du framework et donc pas même les fichiers de configuration, vous pouvez modifier les messages d'erreur par défaut directement dans le champ `Nette\Forms\Validator::$messages`. +Si vous n'utilisez pas l'ensemble du framework et donc pas les fichiers de configuration, vous pouvez modifier les messages d'erreur par défaut directement dans le tableau `Nette\Forms\Validator::$messages`. diff --git a/forms/fr/controls.texy b/forms/fr/controls.texy index 89528a8818..851deaf09a 100644 --- a/forms/fr/controls.texy +++ b/forms/fr/controls.texy @@ -1,253 +1,346 @@ -Contrôles de formulaires -************************ +Éléments de formulaire +********************** .[perex] -Aperçu des contrôles de formulaires intégrés. +Aperçu des éléments de formulaire standard. -addText(string|int $name, $label=null): TextInput .[method] -=========================================================== +addText(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +================================================================================================== -Ajoute un champ de texte à une ligne (classe [TextInput |api:Nette\Forms\Controls\TextInput]). Si l'utilisateur ne remplit pas le champ, il renvoie une chaîne vide `''`, ou utilise `setNullable()` pour le modifier et renvoyer `null`. +Ajoute un champ de texte sur une seule ligne (classe [TextInput |api:Nette\Forms\Controls\TextInput]). Si l'utilisateur ne remplit pas le champ, il renvoie une chaîne vide `''`, ou `setNullable()` peut être utilisé pour spécifier qu'il renvoie `null`. ```php -$form->addText('name', 'Name:') +$form->addText('name', 'Nom :') ->setRequired() ->setNullable(); ``` -Il valide automatiquement l'UTF-8, coupe les espaces à gauche et à droite et supprime les sauts de ligne qui pourraient être envoyés par un attaquant. +Valide automatiquement l'UTF-8, supprime les espaces de début et de fin et supprime les sauts de ligne qu'un attaquant pourrait envoyer. -La longueur maximale peut être limitée en utilisant `setMaxLength()`. La [fonction addFilter() |validation#Modifying Input Values] permet de modifier la valeur saisie par l'utilisateur. +La longueur maximale peut être limitée à l'aide de `setMaxLength()`. La modification de la valeur saisie par l'utilisateur est possible avec [addFilter() |validation#Modification de l entrée]. -Utilisez `setHtmlType()` pour changer le [caractère |https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types] de l'élément d'entrée en `search`, `tel`, `url`, `range`, `date`, `datetime-local`, `month`, `time`, `week`, `color`. Au lieu des types `number` et `email`, nous vous recommandons d'utiliser [addInteger |#addInteger] et [addEmail |#addEmail], qui fournissent une validation côté serveur. +Avec `setHtmlType()`, le caractère visuel du champ de texte peut être modifié en types tels que `search`, `tel` ou `url`, voir la [spécification|https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types]. N'oubliez pas que le changement de type est purement visuel et ne remplace pas la fonction de validation. Pour le type `url`, il est conseillé d'ajouter une [règle de validation URL |validation#Entrées textuelles] spécifique. -```php -$form->addText('color', 'Choose color:') - ->setHtmlType('color') - ->addRule($form::Pattern, 'invalid value', '[0-9a-f]{6}'); -``` +.[note] +Pour d'autres types d'entrées, tels que `number`, `range`, `email`, `date`, `datetime-local`, `time` et `color`, utilisez des méthodes spécialisées comme [#addInteger], [#addFloat], [#addEmail] [#addDate], [#addTime], [#addDateTime] et [#addColor], qui assurent la validation côté serveur. Les types `month` et `week` ne sont pas encore entièrement pris en charge par tous les navigateurs. -La valeur dite vide peut être définie pour l'élément, qui ressemble à la valeur par défaut, mais si l'utilisateur ne l'écrase pas, elle renvoie une chaîne vide ou `null`. +Une valeur vide (empty-value) peut être définie pour l'élément, ce qui est similaire à une valeur par défaut, mais si l'utilisateur ne la modifie pas, l'élément renvoie une chaîne vide ou `null`. ```php -$form->addText('phone', 'Phone:') +$form->addText('phone', 'Téléphone :') ->setHtmlType('tel') - ->setEmptyValue('+420'); + ->setEmptyValue('+33'); ``` addTextArea(string|int $name, $label=null): TextArea .[method] ============================================================== -Ajoute un champ de texte multiligne (classe [TextArea |api:Nette\Forms\Controls\TextArea]). Si l'utilisateur ne remplit pas le champ, il renvoie une chaîne vide `''`, ou utilise `setNullable()` pour le modifier et renvoyer `null`. +Ajoute un champ pour la saisie de texte multiligne (classe [TextArea |api:Nette\Forms\Controls\TextArea]). Si l'utilisateur ne remplit pas le champ, il renvoie une chaîne vide `''`, ou `setNullable()` peut être utilisé pour spécifier qu'il renvoie `null`. ```php -$form->addTextArea('note', 'Note:') - ->addRule($form::MaxLength, 'Your note is way too long', 10000); +$form->addTextArea('note', 'Note :') + ->addRule($form::MaxLength, 'La note est trop longue', 10000); ``` -Valide automatiquement l'UTF-8 et normalise les sauts de ligne à `\n`. Contrairement à un champ de saisie à ligne unique, il ne coupe pas les espaces blancs. +Valide automatiquement l'UTF-8 et normalise les séparateurs de ligne en `\n`. Contrairement au champ de saisie sur une seule ligne, aucun découpage des espaces n'est effectué. -La longueur maximale peut être limitée en utilisant `setMaxLength()`. La fonction [addFilter() |validation#Modifying Input Values] vous permet de modifier la valeur saisie par l'utilisateur. Vous pouvez définir la valeur dite vide à l'aide de `setEmptyValue()`. +La longueur maximale peut être limitée à l'aide de `setMaxLength()`. La modification de la valeur saisie par l'utilisateur est possible avec [addFilter() |validation#Modification de l entrée]. Une valeur vide peut être définie à l'aide de `setEmptyValue()`. addInteger(string|int $name, $label=null): TextInput .[method] ============================================================== -Ajoute un champ de saisie pour un nombre entier (classe [TextInput |api:Nette\Forms\Controls\TextInput]). Renvoie soit un entier, soit `null` si l'utilisateur ne saisit rien. +Ajoute un champ pour la saisie d'un nombre entier (classe [TextInput |api:Nette\Forms\Controls\TextInput]). Renvoie soit un entier, soit `null` si l'utilisateur ne saisit rien. + +```php +$form->addInteger('year', 'Année :') + ->addRule($form::Range, 'L\'année doit être comprise entre %d et %d.', [1900, 2023]); +``` + +L'élément est rendu comme `<input type="number">`. En utilisant la méthode `setHtmlType()`, le type peut être changé en `range` pour un affichage sous forme de curseur, ou en `text` si vous préférez un champ de texte standard sans le comportement spécial du type `number`. + + +addFloat(string|int $name, $label=null): TextInput .[method]{data-version:3.1.12} +================================================================================= + +Ajoute un champ pour la saisie d'un nombre décimal (classe [TextInput |api:Nette\Forms\Controls\TextInput]). Renvoie soit un float, soit `null` si l'utilisateur ne saisit rien. ```php -$form->addInteger('level', 'Level:') +$form->addFloat('level', 'Niveau :') ->setDefaultValue(0) - ->addRule($form::Range, 'Level must be between %d and %d.', [0, 100]); + ->addRule($form::Range, 'Le niveau doit être compris entre %d et %d.', [0, 100]); ``` +L'élément est rendu comme `<input type="number">`. En utilisant la méthode `setHtmlType()`, le type peut être changé en `range` pour un affichage sous forme de curseur, ou en `text` si vous préférez un champ de texte standard sans le comportement spécial du type `number`. + +Nette et le navigateur Chrome acceptent à la fois la virgule et le point comme séparateurs décimaux. Pour que cette fonctionnalité soit également disponible dans Firefox, il est recommandé de définir l'attribut `lang` soit pour l'élément concerné, soit pour la page entière, par exemple `<html lang="fr">`. + -addEmail(string|int $name, $label=null): TextInput .[method] -============================================================ +addEmail(string|int $name, $label=null, int $maxLength=255): TextInput .[method] +================================================================================ -Ajoute un champ d'adresse électronique avec contrôle de validité (classe [TextInput |api:Nette\Forms\Controls\TextInput]). Si l'utilisateur ne remplit pas le champ, il renvoie une chaîne vide `''`, ou utilise `setNullable()` pour le modifier et renvoyer `null`. +Ajoute un champ pour la saisie d'une adresse e-mail (classe [TextInput |api:Nette\Forms\Controls\TextInput]). Si l'utilisateur ne remplit pas le champ, il renvoie une chaîne vide `''`, ou `setNullable()` peut être utilisé pour spécifier qu'il renvoie `null`. ```php -$form->addEmail('email', 'Email:'); +$form->addEmail('email', 'E-mail :'); ``` -Vérifie que la valeur est une adresse électronique valide. Il ne vérifie pas que le domaine existe réellement, seule la syntaxe est vérifiée. Valide automatiquement l'UTF-8, supprime les espaces à gauche et à droite. +Vérifie si la valeur est une adresse e-mail valide. Ne vérifie pas si le domaine existe réellement, vérifie uniquement la syntaxe. Valide automatiquement l'UTF-8, supprime les espaces de début et de fin. -La longueur maximale peut être limitée en utilisant `setMaxLength()`. La [fonction addFilter() |validation#Modifying Input Values] vous permet de modifier la valeur saisie par l'utilisateur. Vous pouvez définir la valeur dite vide à l'aide de `setEmptyValue()`. +La longueur maximale peut être limitée à l'aide de `setMaxLength()`. La modification de la valeur saisie par l'utilisateur est possible avec [addFilter() |validation#Modification de l entrée]. Une valeur vide peut être définie à l'aide de `setEmptyValue()`. -addPassword(string|int $name, $label=null): TextInput .[method] -=============================================================== +addPassword(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +====================================================================================================== -Ajoute un champ de mot de passe (classe [TextInput |api:Nette\Forms\Controls\TextInput]). +Ajoute un champ pour la saisie d'un mot de passe (classe [TextInput |api:Nette\Forms\Controls\TextInput]). ```php -$form->addPassword('password', 'Password:') +$form->addPassword('password', 'Mot de passe :') ->setRequired() - ->addRule($form::MinLength, 'Password has to be at least %d characters long', 8) - ->addRule($form::Pattern, 'Password must contain a number', '.*[0-9].*'); + ->addRule($form::MinLength, 'Le mot de passe doit comporter au moins %d caractères', 8) + ->addRule($form::Pattern, 'Doit contenir un chiffre', '.*[0-9].*'); ``` -Lorsque vous renvoyez le formulaire, l'entrée sera vide. Il valide automatiquement l'UTF-8, coupe les espaces à gauche et à droite et supprime les sauts de ligne qui pourraient être envoyés par un attaquant. +Lors du réaffichage du formulaire, le champ sera vide. Valide automatiquement l'UTF-8, supprime les espaces de début et de fin et supprime les sauts de ligne qu'un attaquant pourrait envoyer. addCheckbox(string|int $name, $caption=null): Checkbox .[method] ================================================================ -Ajoute une case à cocher (classe [Checkbox |api:Nette\Forms\Controls\Checkbox]). Le champ renvoie soit `true` soit `false`, selon qu'il est coché ou non. +Ajoute une case à cocher (classe [Checkbox |api:Nette\Forms\Controls\Checkbox]). Renvoie la valeur `true` ou `false`, selon qu'elle est cochée ou non. ```php -$form->addCheckbox('agree', 'I agree with terms') - ->setRequired('You must agree with our terms'); +$form->addCheckbox('agree', 'J\'accepte les conditions') + ->setRequired('Il est nécessaire d\'accepter les conditions'); ``` -addCheckboxList(string|int $name, $label=null, array $items=null): CheckboxList .[method] -========================================================================================= +addCheckboxList(string|int $name, $label=null, ?array $items=null): CheckboxList .[method] +========================================================================================== -Ajoute une liste de cases à cocher pour sélectionner plusieurs éléments (classe [CheckboxList |api:Nette\Forms\Controls\CheckboxList]). Renvoie le tableau des clés des éléments sélectionnés. La méthode `getSelectedItems()` renvoie des valeurs au lieu des clés. +Ajoute des cases à cocher pour sélectionner plusieurs éléments (classe [CheckboxList |api:Nette\Forms\Controls\CheckboxList]). Renvoie un tableau des clés des éléments sélectionnés. La méthode `getSelectedItems()` renvoie les valeurs au lieu des clés. ```php -$form->addCheckboxList('colors', 'Colors:', [ - 'r' => 'red', - 'g' => 'green', - 'b' => 'blue', +$form->addCheckboxList('colors', 'Couleurs :', [ + 'r' => 'rouge', + 'g' => 'vert', + 'b' => 'bleu', ]); ``` -Nous passons le tableau d'éléments comme troisième paramètre, ou par la méthode `setItems()`. +Le tableau des éléments proposés est passé comme troisième paramètre ou via la méthode `setItems()`. -Vous pouvez utiliser `setDisabled(['r', 'g'])` pour désactiver des éléments individuels. +Avec `setDisabled(['r', 'g'])`, il est possible de désactiver des éléments individuels. -L'élément vérifie automatiquement qu'il n'y a pas eu de falsification et que les éléments sélectionnés font bien partie des éléments proposés et n'ont pas été désactivés. La méthode `getRawValue()` peut être utilisée pour récupérer les éléments proposés sans cette importante vérification. +L'élément vérifie automatiquement qu'il n'y a pas eu de falsification et que les éléments sélectionnés font bien partie de ceux proposés et n'ont pas été désactivés. La méthode `getRawValue()` permet d'obtenir les éléments envoyés sans ce contrôle important. -Lorsque des valeurs par défaut sont définies, la méthode vérifie également qu'il s'agit de l'un des éléments proposés, sinon elle lève une exception. Cette vérification peut être désactivée avec `checkDefaultValue(false)`. +Lors de la définition des éléments sélectionnés par défaut, il vérifie également qu'il s'agit bien de l'un des éléments proposés, sinon il lève une exception. Ce contrôle peut être désactivé avec `checkDefaultValue(false)`. +Si vous soumettez le formulaire avec la méthode `GET`, vous pouvez choisir un mode de transmission des données plus compact, ce qui économise la taille de la chaîne de requête. Il est activé en définissant l'attribut HTML du formulaire : + +```php +$form->setHtmlAttribute('data-nette-compact'); +``` -addRadioList(string|int $name, $label=null, array $items=null): RadioList .[method] -=================================================================================== -Ajoute des boutons radio (classe [RadioList |api:Nette\Forms\Controls\RadioList]). Renvoie la clé de l'élément sélectionné, ou `null` si l'utilisateur n'a rien sélectionné. La méthode `getSelectedItem()` renvoie une valeur au lieu d'une clé. +addRadioList(string|int $name, $label=null, ?array $items=null): RadioList .[method] +==================================================================================== + +Ajoute des boutons radio (classe [RadioList |api:Nette\Forms\Controls\RadioList]). Renvoie la clé de l'élément sélectionné, ou `null` si l'utilisateur n'a rien sélectionné. La méthode `getSelectedItem()` renvoie la valeur au lieu de la clé. ```php $sex = [ - 'm' => 'male', - 'f' => 'female', + 'm' => 'homme', + 'f' => 'femme', ]; -$form->addRadioList('gender', 'Gender:', $sex); +$form->addRadioList('gender', 'Sexe :', $sex); ``` -Nous passons le tableau d'éléments comme troisième paramètre, ou par la méthode `setItems()`. +Le tableau des éléments proposés est passé comme troisième paramètre ou via la méthode `setItems()`. -Vous pouvez utiliser `setDisabled(['m'])` pour désactiver des éléments individuels. +Avec `setDisabled(['m', 'f'])`, il est possible de désactiver des éléments individuels. -L'élément vérifie automatiquement qu'il n'y a pas eu de falsification et que l'élément sélectionné est bien l'un de ceux proposés et n'a pas été désactivé. La méthode `getRawValue()` peut être utilisée pour récupérer l'élément soumis sans cette importante vérification. +L'élément vérifie automatiquement qu'il n'y a pas eu de falsification et que l'élément sélectionné fait bien partie de ceux proposés et n'a pas été désactivé. La méthode `getRawValue()` permet d'obtenir l'élément envoyé sans ce contrôle important. -Lorsque la valeur par défaut est définie, elle vérifie également qu'il s'agit de l'un des éléments proposés, sinon elle lève une exception. Cette vérification peut être désactivée avec `checkDefaultValue(false)`. +Lors de la définition de l'élément sélectionné par défaut, il vérifie également qu'il s'agit bien de l'un des éléments proposés, sinon il lève une exception. Ce contrôle peut être désactivé avec `checkDefaultValue(false)`. -addSelect(string|int $name, $label=null, array $items=null): SelectBox .[method] -================================================================================ +addSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): SelectBox .[method] +================================================================================================== -Ajoute une boîte de sélection (classe [SelectBox |api:Nette\Forms\Controls\SelectBox]). Renvoie la clé de l'élément sélectionné, ou `null` si l'utilisateur n'a rien sélectionné. La méthode `getSelectedItem()` renvoie une valeur au lieu d'une clé. +Ajoute une boîte de sélection (classe [SelectBox |api:Nette\Forms\Controls\SelectBox]). Renvoie la clé de l'élément sélectionné, ou `null` si l'utilisateur n'a rien sélectionné. La méthode `getSelectedItem()` renvoie la valeur au lieu de la clé. ```php $countries = [ - 'CZ' => 'Czech republic', - 'SK' => 'Slovakia', - 'GB' => 'United Kingdom', + 'FR' => 'France', + 'BE' => 'Belgique', + 'CH' => 'Suisse', ]; -$form->addSelect('country', 'Country:', $countries) - ->setDefaultValue('SK'); +$form->addSelect('country', 'Pays :', $countries) + ->setDefaultValue('BE'); ``` -Nous passons le tableau d'éléments comme troisième paramètre, ou par la méthode `setItems()`. Le tableau d'éléments peut également être bidimensionnel : +Le tableau des éléments proposés est passé comme troisième paramètre ou via la méthode `setItems()`. Les éléments peuvent également être un tableau à deux dimensions : ```php $countries = [ 'Europe' => [ - 'CZ' => 'Czech republic', - 'SK' => 'Slovakia', - 'GB' => 'United Kingdom', + 'FR' => 'France', + 'BE' => 'Belgique', + 'CH' => 'Suisse', ], 'CA' => 'Canada', 'US' => 'USA', - '?' => 'other', + '?' => 'autre', ]; ``` -Pour les boîtes de sélection, le premier élément a souvent une signification particulière, il sert d'appel à l'action. Utilisez la méthode `setPrompt()` pour ajouter une telle entrée. +Pour les boîtes de sélection, le premier élément a souvent une signification spéciale, servant d'invite à l'action. La méthode `setPrompt()` est utilisée pour ajouter un tel élément. ```php -$form->addSelect('country', 'Country:', $countries) - ->setPrompt('Pick a country'); +$form->addSelect('country', 'Pays :', $countries) + ->setPrompt('Choisissez un pays'); ``` -Vous pouvez utiliser `setDisabled(['CZ', 'SK'])` pour désactiver des éléments individuels. +Avec `setDisabled(['FR', 'BE'])`, il est possible de désactiver des éléments individuels. -L'élément vérifie automatiquement qu'il n'y a pas eu de falsification et que l'élément sélectionné est bien l'un de ceux proposés et n'a pas été désactivé. La méthode `getRawValue()` peut être utilisée pour récupérer l'élément soumis sans cette importante vérification. +L'élément vérifie automatiquement qu'il n'y a pas eu de falsification et que l'élément sélectionné fait bien partie de ceux proposés et n'a pas été désactivé. La méthode `getRawValue()` permet d'obtenir l'élément envoyé sans ce contrôle important. -Lorsque la valeur par défaut est définie, elle vérifie également qu'il s'agit de l'un des éléments proposés, sinon elle lève une exception. Cette vérification peut être désactivée avec `checkDefaultValue(false)`. +Lors de la définition de l'élément sélectionné par défaut, il vérifie également qu'il s'agit bien de l'un des éléments proposés, sinon il lève une exception. Ce contrôle peut être désactivé avec `checkDefaultValue(false)`. -addMultiSelect(string|int $name, $label=null, array $items=null): MultiSelectBox .[method] -========================================================================================== +addMultiSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): MultiSelectBox .[method] +============================================================================================================ -Ajoute une boîte de sélection multichoix (classe [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox]). Renvoie le tableau des clés des éléments sélectionnés. La méthode `getSelectedItems()` renvoie des valeurs au lieu des clés. +Ajoute une boîte de sélection pour choisir plusieurs éléments (classe [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox]). Renvoie un tableau des clés des éléments sélectionnés. La méthode `getSelectedItems()` renvoie les valeurs au lieu des clés. ```php -$form->addMultiSelect('countries', 'Countries:', $countries); +$form->addMultiSelect('countries', 'Pays :', $countries); ``` -Nous passons le tableau d'éléments comme troisième paramètre, ou par la méthode `setItems()`. Le tableau d'éléments peut également être bidimensionnel. +Le tableau des éléments proposés est passé comme troisième paramètre ou via la méthode `setItems()`. Les éléments peuvent également être un tableau à deux dimensions. -Vous pouvez utiliser `setDisabled(['CZ', 'SK'])` pour désactiver des éléments individuels. +Avec `setDisabled(['FR', 'BE'])`, il est possible de désactiver des éléments individuels. -L'élément vérifie automatiquement qu'il n'y a pas eu de falsification et que les éléments sélectionnés font bien partie des éléments proposés et n'ont pas été désactivés. La méthode `getRawValue()` peut être utilisée pour récupérer les éléments proposés sans cette importante vérification. +L'élément vérifie automatiquement qu'il n'y a pas eu de falsification et que les éléments sélectionnés font bien partie de ceux proposés et n'ont pas été désactivés. La méthode `getRawValue()` permet d'obtenir les éléments envoyés sans ce contrôle important. -Lorsque des valeurs par défaut sont définies, la méthode vérifie également qu'il s'agit de l'un des éléments proposés, sinon elle lève une exception. Cette vérification peut être désactivée avec `checkDefaultValue(false)`. +Lors de la définition des éléments sélectionnés par défaut, il vérifie également qu'il s'agit bien de l'un des éléments proposés, sinon il lève une exception. Ce contrôle peut être désactivé avec `checkDefaultValue(false)`. addUpload(string|int $name, $label=null): UploadControl .[method] ================================================================= -Ajoute un champ de téléchargement de fichier (classe [UploadControl |api:Nette\Forms\Controls\UploadControl]). Renvoie l'objet [FileUpload |http:request#FileUpload], même si l'utilisateur n'a pas téléchargé de fichier, ce qui peut être découvert par la méthode `FileUpload::hasFile()`. +Ajoute un champ pour le téléchargement de fichier (classe [UploadControl |api:Nette\Forms\Controls\UploadControl]). Renvoie un objet [FileUpload |http:request#FileUpload], même si l'utilisateur n'a envoyé aucun fichier, ce qui peut être vérifié avec la méthode `FileUpload::hasFile()`. ```php -$form->addUpload('avatar', 'Avatar:') - ->addRule($form::Image, 'Avatar must be JPEG, PNG, GIF or WebP') - ->addRule($form::MaxFileSize, 'Maximum size is 1 MB', 1024 * 1024); +$form->addUpload('avatar', 'Avatar :') + ->addRule($form::Image, 'L\'avatar doit être au format JPEG, PNG, GIF, WebP ou AVIF.') + ->addRule($form::MaxFileSize, 'La taille maximale est de 1 Mo.', 1024 * 1024); ``` -Si le fichier n'a pas été téléchargé correctement, le formulaire n'a pas été soumis avec succès et une erreur est affichée. C'est-à-dire qu'il n'est pas nécessaire de vérifier la méthode `FileUpload::isOk()`. +Si le fichier ne parvient pas à être téléchargé correctement, le formulaire n'est pas soumis avec succès et une erreur s'affiche. C'est-à-dire qu'en cas de soumission réussie, il n'est pas nécessaire de vérifier la méthode `FileUpload::isOk()`. -Ne vous fiez pas au nom de fichier original renvoyé par la méthode `FileUpload::getName()`, un client pourrait envoyer un nom de fichier malveillant dans le but de corrompre ou de pirater votre application. +Ne faites jamais confiance au nom de fichier original renvoyé par la méthode `FileUpload::getName()`, le client pourrait avoir envoyé un nom de fichier malveillant dans le but d'endommager ou de pirater votre application. -Les règles `MimeType` et `Image` détectent le type de fichier ou d'image requis par sa signature. L'intégrité de l'ensemble du fichier n'est pas vérifiée. Vous pouvez savoir si une image n'est pas corrompue, par exemple en essayant de [la charger |http:request#toImage]. +Les règles `MimeType` et `Image` détectent le type requis en fonction de la signature du fichier et ne vérifient pas son intégrité. Pour savoir si une image n'est pas endommagée, on peut par exemple essayer de la [charger |http:request#toImage]. addMultiUpload(string|int $name, $label=null): UploadControl .[method] ====================================================================== -Ajoute un champ de téléchargement de fichiers multiples (classe [UploadControl |api:Nette\Forms\Controls\UploadControl]). Retourne un tableau d'objets [FileUpload |http:request#FileUpload]. La méthode `FileUpload::hasFile()` renverra `true` pour chacun d'entre eux. +Ajoute un champ pour le téléchargement de plusieurs fichiers à la fois (classe [UploadControl |api:Nette\Forms\Controls\UploadControl]). Renvoie un tableau d'objets [FileUpload |http:request#FileUpload]. La méthode `FileUpload::hasFile()` pour chacun d'eux renverra `true`. ```php -$form->addMultiUpload('files', 'Files:') - ->addRule($form::MaxLength, 'A maximum of %d files can be uploaded', 10); +$form->addMultiUpload('files', 'Fichiers :') + ->addRule($form::MaxLength, 'Vous pouvez télécharger au maximum %d fichiers', 10); ``` -Si l'un des fichiers ne parvient pas à être téléchargé correctement, le formulaire n'a pas été soumis avec succès et une erreur est affichée. C'est-à-dire qu'il n'est pas nécessaire de vérifier la méthode `FileUpload::isOk()`. +Si l'un des fichiers ne parvient pas à être téléchargé correctement, le formulaire n'est pas soumis avec succès et une erreur s'affiche. C'est-à-dire qu'en cas de soumission réussie, il n'est pas nécessaire de vérifier la méthode `FileUpload::isOk()`. + +Ne faites jamais confiance aux noms de fichiers originaux renvoyés par la méthode `FileUpload::getName()`, le client pourrait avoir envoyé un nom de fichier malveillant dans le but d'endommager ou de pirater votre application. + +Les règles `MimeType` et `Image` détectent le type requis en fonction de la signature du fichier et ne vérifient pas son intégrité. Pour savoir si une image n'est pas endommagée, on peut par exemple essayer de la [charger |http:request#toImage]. + -Ne vous fiez pas aux noms de fichiers originaux renvoyés par la méthode `FileUpload::getName()`, un client pourrait envoyer un nom de fichier malveillant dans le but de corrompre ou de pirater votre application. +addDate(string|int $name, $label=null): DateTimeControl .[method]{data-version:3.1.14} +====================================================================================== -Les règles `MimeType` et `Image` détectent le type de fichier ou d'image requis par sa signature. L'intégrité de l'ensemble du fichier n'est pas vérifiée. Vous pouvez savoir si une image n'est pas corrompue, par exemple en essayant de [la charger |http:request#toImage]. +Ajoute un champ qui permet à l'utilisateur de saisir facilement une date composée de l'année, du mois et du jour (classe [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Comme valeur par défaut, il accepte soit des objets implémentant l'interface `DateTimeInterface`, une chaîne de caractères avec l'heure, ou un nombre représentant un timestamp UNIX. Il en va de même pour les arguments des règles `Min`, `Max` ou `Range`, qui définissent la date minimale et maximale autorisée. + +```php +$form->addDate('date', 'Date :') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'La date doit dater d\'au moins un mois.', new DateTime('-1 month')); +``` + +Par défaut, il renvoie un objet `DateTimeImmutable`, avec la méthode `setFormat()`, vous pouvez spécifier un [format texte|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] ou un timestamp : + +```php +$form->addDate('date', 'Date :') + ->setFormat('Y-m-d'); +``` -addHidden(string|int $name, string $default=null): HiddenField .[method] -======================================================================== +addTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=============================================================================================================== + +Ajoute un champ qui permet à l'utilisateur de saisir facilement une heure composée des heures, des minutes et éventuellement des secondes (classe [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Comme valeur par défaut, il accepte soit des objets implémentant l'interface `DateTimeInterface`, une chaîne de caractères avec l'heure, ou un nombre représentant un timestamp UNIX. De ces entrées, seule l'information temporelle est utilisée, la date est ignorée. Il en va de même pour les arguments des règles `Min`, `Max` ou `Range`, qui définissent l'heure minimale et maximale autorisée. Si la valeur minimale définie est supérieure à la valeur maximale, une plage horaire dépassant minuit est créée. + +```php +$form->addTime('time', 'Heure :', withSeconds: true) + ->addRule($form::Range, 'L\'heure doit être comprise entre %d et %d.', ['12:30', '13:30']); +``` + +Par défaut, il renvoie un objet `DateTimeImmutable` (avec la date du 1er janvier de l'an 1), avec la méthode `setFormat()`, vous pouvez spécifier un [format texte|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] : + +```php +$form->addTime('time', 'Heure :') + ->setFormat('H:i'); +``` + + +addDateTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=================================================================================================================== + +Ajoute un champ qui permet à l'utilisateur de saisir facilement une date et une heure composées de l'année, du mois, du jour, des heures, des minutes et éventuellement des secondes (classe [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Comme valeur par défaut, il accepte soit des objets implémentant l'interface `DateTimeInterface`, une chaîne de caractères avec l'heure, ou un nombre représentant un timestamp UNIX. Il en va de même pour les arguments des règles `Min`, `Max` ou `Range`, qui définissent la date minimale et maximale autorisée. + +```php +$form->addDateTime('datetime', 'Date et heure :') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'La date doit dater d\'au moins un mois.', new DateTime('-1 month')); +``` + +Par défaut, il renvoie un objet `DateTimeImmutable`, avec la méthode `setFormat()`, vous pouvez spécifier un [format texte|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] ou un timestamp : + +```php +$form->addDateTime('datetime') + ->setFormat(DateTimeControl::FormatTimestamp); +``` + + +addColor(string|int $name, $label=null): ColorPicker .[method]{data-version:3.1.14} +=================================================================================== + +Ajoute un champ pour choisir une couleur (classe [ColorPicker |api:Nette\Forms\Controls\ColorPicker]). La couleur est une chaîne de caractères au format `#rrggbb`. Si l'utilisateur ne fait pas de choix, la couleur noire `#000000` est renvoyée. + +```php +$form->addColor('color', 'Couleur :') + ->setDefaultValue('#3C8ED7'); +``` + + +addHidden(string|int $name, ?string $default=null): HiddenField .[method] +========================================================================= Ajoute un champ caché (classe [HiddenField |api:Nette\Forms\Controls\HiddenField]). @@ -255,7 +348,9 @@ Ajoute un champ caché (classe [HiddenField |api:Nette\Forms\Controls\HiddenFiel $form->addHidden('userid'); ``` -Utilisez `setNullable()` pour le modifier afin qu'il renvoie `null` au lieu d'une chaîne vide. La [fonction addFilter() |validation#Modifying Input Values] permet de modifier la valeur soumise. +Avec `setNullable()`, il est possible de définir qu'il renvoie `null` au lieu d'une chaîne vide. La modification de la valeur envoyée est possible avec [addFilter() |validation#Modification de l entrée]. + +Bien que l'élément soit caché, il est **important de noter** que la valeur peut toujours être modifiée ou falsifiée par un attaquant. Vérifiez et validez toujours soigneusement toutes les valeurs reçues côté serveur pour prévenir les risques de sécurité liés à la manipulation des données. addSubmit(string|int $name, $caption=null): SubmitButton .[method] @@ -264,17 +359,17 @@ addSubmit(string|int $name, $caption=null): SubmitButton .[method] Ajoute un bouton de soumission (classe [SubmitButton |api:Nette\Forms\Controls\SubmitButton]). ```php -$form->addSubmit('submit', 'Register'); +$form->addSubmit('submit', 'Envoyer'); ``` -Il est possible d'avoir plus d'un bouton d'envoi dans le formulaire : +Il est possible d'avoir plusieurs boutons de soumission dans un formulaire : ```php -$form->addSubmit('register', 'Register'); -$form->addSubmit('cancel', 'Cancel'); +$form->addSubmit('register', 'S\'inscrire'); +$form->addSubmit('cancel', 'Annuler'); ``` -Pour savoir lequel d'entre eux a été cliqué, utilisez : +Pour savoir sur lequel on a cliqué, utilisez : ```php if ($form['register']->isSubmittedBy()) { @@ -282,48 +377,48 @@ if ($form['register']->isSubmittedBy()) { } ``` -Si vous ne voulez pas valider le formulaire lorsqu'un bouton d'envoi est pressé (comme les boutons *Annulation* ou *Aperçu*), vous pouvez le désactiver avec [setValidationScope() |validation#Disabling Validation]. +Si vous ne souhaitez pas valider l'ensemble du formulaire lors de l'appui sur un bouton (par exemple, pour les boutons *Annuler* ou *Aperçu*), utilisez [setValidationScope() |validation#Désactivation de la validation]. addButton(string|int $name, $caption): Button .[method] ======================================================= -Ajoute un bouton (classe [Button |api:Nette\Forms\Controls\Button]) sans fonction de soumission. Il est utile pour lier une autre fonctionnalité à l'id, par exemple une action JavaScript. +Ajoute un bouton (classe [Button |api:Nette\Forms\Controls\Button]) qui n'a pas de fonction de soumission. Il peut donc être utilisé pour une autre fonction, par exemple appeler une fonction JavaScript lors d'un clic. ```php -$form->addButton('raise', 'Raise salary') +$form->addButton('raise', 'Augmenter le salaire') ->setHtmlAttribute('onclick', 'raiseSalary()'); ``` -addImageButton(string|int $name, string $src=null, string $alt=null): ImageButton .[method] -=========================================================================================== +addImageButton(string|int $name, ?string $src=null, ?string $alt=null): ImageButton .[method] +============================================================================================= -Ajoute un bouton d'envoi sous la forme d'une image (classe [ImageButton |api:Nette\Forms\Controls\ImageButton]). +Ajoute un bouton de soumission sous forme d'image (classe [ImageButton |api:Nette\Forms\Controls\ImageButton]). ```php -$form->addImageButton('submit', '/path/to/image'); +$form->addImageButton('submit', '/chemin/vers/image'); ``` -Lorsque vous utilisez plusieurs boutons d'envoi, vous pouvez savoir lequel a été cliqué avec la commande `$form['submit']->isSubmittedBy()`. +En utilisant plusieurs boutons de soumission, on peut savoir sur lequel on a cliqué à l'aide de `$form['submit']->isSubmittedBy()`. addContainer(string|int $name): Container .[method] =================================================== -Ajoute un sous-formulaire (classe [Container |api:Nette\Forms\Container]), ou un conteneur, qui peut être traité de la même manière qu'un formulaire. Cela signifie que vous pouvez utiliser des méthodes comme `setDefaults()` ou `getValues()`. +Ajoute un sous-formulaire (classe [Container|api:Nette\Forms\Container]), ou conteneur, auquel on peut ajouter d'autres éléments de la même manière qu'on les ajoute au formulaire. Les méthodes `setDefaults()` ou `getValues()` fonctionnent également. ```php $sub1 = $form->addContainer('first'); -$sub1->addText('name', 'Your name:'); -$sub1->addEmail('email', 'Email:'); +$sub1->addText('name', 'Votre nom :'); +$sub1->addEmail('email', 'Email :'); $sub2 = $form->addContainer('second'); -$sub2->addText('name', 'Your name:'); -$sub2->addEmail('email', 'Email:'); +$sub2->addText('name', 'Votre nom :'); +$sub2->addEmail('email', 'Email :'); ``` -Les données envoyées sont ensuite renvoyées sous la forme d'une structure multidimensionnelle : +Les données envoyées sont alors renvoyées sous forme de structure multidimensionnelle : ```php [ @@ -339,110 +434,112 @@ Les données envoyées sont ensuite renvoyées sous la forme d'une structure mul ``` -Aperçu des paramètres .[#toc-overview-of-settings] -================================================== +Aperçu des paramètres +===================== -Pour tous les éléments, nous pouvons appeler les méthodes suivantes (voir la [documentation API |https://api.nette.org/forms/master/Nette/Forms/Controls.html] pour un aperçu complet) : +Pour tous les éléments, nous pouvons appeler les méthodes suivantes (aperçu complet dans la [documentation API|https://api.nette.org/forms/master/Nette/Forms/Controls.html]) : .[table-form-methods language-php] -| `setDefaultValue($value)` | définit la valeur par défaut -| `getValue()` | obtient la valeur actuelle -| `setOmitted()` | [valeurs omises |#omitted values] -| `setDisabled()` | [désactiver les entrées |#disabling inputs] +| `setDefaultValue($value)` | définit la valeur par défaut +| `getValue()` | obtient la valeur actuelle +| `setOmitted()` | [#omission de valeur] +| `setDisabled()` | [#désactivation des éléments] Rendu : .[table-form-methods language-php] -| `setCaption($caption)`| changer la légende de l'élément -| `setTranslator($translator)` | définit le [traducteur |rendering#translating] -| `setHtmlAttribute($name, $value)` | définit l'[attribut HTML |rendering#HTML attributes] de l'élément -| `setHtmlId($id)` | définit l'attribut HTML `id` -| `setHtmlType($type)` | définit l'attribut HTML `type` -| `setHtmlName($name)`| définit l'attribut HTML `name` -| `setOption($key, $value)` | définit les [données de rendu |rendering#Options] +| `setCaption($caption)` | modifie l'étiquette de l'élément +| `setTranslator($translator)` | définit le [traducteur |rendering#Traduction] +| `setHtmlAttribute($name, $value)` | définit l'[attribut HTML |rendering#Attributs HTML] de l'élément +| `setHtmlId($id)` | définit l'attribut HTML `id` +| `setHtmlType($type)` | définit l'attribut HTML `type` +| `setHtmlName($name)` | définit l'attribut HTML `name` +| `setOption($key, $value)` | [options de rendu |rendering#Options] Validation : .[table-form-methods language-php] -| `setRequired()` | [champ obligatoire |validation] -| `addRule()` | règle [de validation |validation#Rules] -| `addCondition()`, `addConditionOn()` | fixer la [condition de validation |validation#Conditions] -| `addError($message)`| [passer le message d'erreur |validation#processing-errors] +| `setRequired()` | [élément requis |validation] +| `addRule()` | définit une [règle de validation |validation#Règles] +| `addCondition()`, `addConditionOn()` | définit une [condition de validation |validation#Conditions] +| `addError($message)` | [transmission du message d'erreur |validation#Erreurs lors du traitement] -Les méthodes suivantes peuvent être appelées pour les éléments `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`: +Pour les éléments `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`, les méthodes suivantes peuvent être appelées : .[table-form-methods language-php] -| `setNullable()`| détermine si getValue() renvoie `null` au lieu d'une chaîne vide. -| `setEmptyValue($value)` | définit la valeur spéciale qui est traitée comme une chaîne vide -| `setMaxLength($length)`| définit le nombre maximum de caractères autorisés -| `addFilter($filter)`| [modifier les valeurs d'entrée |validation#Modifying Input Values] +| `setNullable()` | définit si getValue() renvoie `null` au lieu d'une chaîne vide +| `setEmptyValue($value)` | définit une valeur spéciale considérée comme une chaîne vide +| `setMaxLength($length)` | définit le nombre maximal de caractères autorisés +| `addFilter($filter)` | [modification de l'entrée |validation#Modification de l entrée] -Valeurs omises .[#toc-omitted-values] -------------------------------------- +Omission de valeur +================== -Si la valeur saisie par l'utilisateur ne vous intéresse pas, nous pouvons utiliser `setOmitted()` pour l'omettre du résultat fourni par la méthode `$form->getValues​()` ou transmis aux gestionnaires. Cela convient pour divers mots de passe à des fins de vérification, des champs antispam, etc. +Si la valeur remplie par l'utilisateur ne nous intéresse pas, nous pouvons l'omettre du résultat de la méthode `$form->getValues()` ou des données transmises aux handlers à l'aide de `setOmitted()`. Ceci est utile pour divers mots de passe de contrôle, éléments anti-spam, etc. ```php -$form->addPassword('passwordVerify', 'Password again:') - ->setRequired('Fill your password again to check for typo') - ->addRule($form::Equal, 'Password mismatch', $form['password']) +$form->addPassword('passwordVerify', 'Mot de passe pour vérification :') + ->setRequired('Veuillez saisir à nouveau le mot de passe pour vérification') + ->addRule($form::Equal, 'Les mots de passe ne correspondent pas', $form['password']) ->setOmitted(); ``` -Désactiver les entrées .[#toc-disabling-inputs] ------------------------------------------------ +Désactivation des éléments +========================== -Pour désactiver une entrée, vous pouvez appeler `setDisabled()`. Un tel champ ne peut pas être modifié par l'utilisateur. +Les éléments peuvent être désactivés à l'aide de `setDisabled()`. Un tel élément ne peut pas être modifié par l'utilisateur. ```php -$form->addText('username', 'User name:') +$form->addText('username', 'Nom d\'utilisateur :') ->setDisabled(); ``` -Notez que le navigateur n'envoie pas du tout les champs désactivés au serveur, de sorte que vous ne les trouverez même pas dans les données renvoyées par la fonction `$form->getValues()`. +Les éléments désactivés ne sont pas du tout envoyés par le navigateur au serveur, vous ne les trouverez donc pas dans les données renvoyées par la fonction `$form->getValues()`. Cependant, si vous définissez `setOmitted(false)`, Nette inclura leur valeur par défaut dans ces données. -Si vous définissez une valeur par défaut pour un champ, vous devez le faire uniquement après l'avoir désactivé : +Lors de l'appel de `setDisabled()`, la **valeur de l'élément est effacée** pour des raisons de sécurité. Si vous définissez une valeur par défaut, il est donc nécessaire de le faire après sa désactivation : ```php -$form->addText('username', 'User name:') +$form->addText('username', 'Nom d\'utilisateur :') ->setDisabled() ->setDefaultValue($userName); ``` +Une alternative aux éléments désactivés sont les éléments avec l'attribut HTML `readonly`, que le navigateur envoie au serveur. Bien que l'élément soit en lecture seule, il est **important de noter** que sa valeur peut toujours être modifiée ou falsifiée par un attaquant. + -Contrôles personnalisés .[#toc-custom-controls] -=============================================== +Éléments personnalisés +====================== -Outre le large éventail de contrôles de formulaire intégrés, vous pouvez ajouter des contrôles personnalisés au formulaire comme suit : +En plus de la large gamme d'éléments de formulaire intégrés, vous pouvez ajouter vos propres éléments personnalisés au formulaire de cette manière : ```php -$form->addComponent(new DateInput('Date:'), 'date'); -// syntaxe alternative: $form['date'] = new DateInput('Date:'); +$form->addComponent(new DateInput('Date :'), 'date'); +// syntaxe alternative : $form['date'] = new DateInput('Date :'); ``` .[note] -Le formulaire est un descendant de la classe [Container | component-model:#Container] et les éléments sont des descendants de [Component | component-model:#Component]. +Le formulaire est un enfant de la classe [Container |component-model:#Container] et les éléments individuels sont des enfants de [Component |component-model:#Component]. -Il est possible de définir de nouvelles méthodes de formulaire pour ajouter des éléments personnalisés (par exemple `$form->addZip()`). Il s'agit des méthodes dites d'extension. L'inconvénient est que les astuces de code dans les éditeurs ne fonctionnent pas pour elles. +Il existe une manière de définir de nouvelles méthodes de formulaire servant à ajouter des éléments personnalisés (par ex. `$form->addZip()`). Il s'agit de ce qu'on appelle les méthodes d'extension. L'inconvénient est que l'autocomplétion dans les éditeurs ne fonctionnera pas pour elles. ```php use Nette\Forms\Container; -// ajoute la méthode addZip(string $name, string $label = null) -Container::extensionMethod('addZip', function (Container $form, string $name, string $label = null) { +// ajoutons la méthode addZip(string $name, ?string $label = null) +Container::extensionMethod('addZip', function (Container $form, string $name, ?string $label = null) { return $form->addText($name, $label) - ->addRule($form::Pattern, 'At least 5 numbers', '[0-9]{5}'); + ->addRule($form::Pattern, 'Au moins 5 chiffres', '[0-9]{5}'); }); // utilisation -$form->addZip('zip', 'ZIP code:'); +$form->addZip('zip', 'Code postal :'); ``` -Champs de bas niveau .[#toc-low-level-fields] -============================================= +Éléments de bas niveau +====================== -Pour ajouter un élément au formulaire, il n'est pas nécessaire d'appeler `$form->addXyz()`. Les éléments de formulaire peuvent être introduits exclusivement dans les modèles. Ceci est utile si vous devez, par exemple, générer des éléments dynamiques : +Il est également possible d'utiliser des éléments que nous écrivons uniquement dans le template et que nous n'ajoutons pas au formulaire avec l'une des méthodes `$form->addXyz()`. Par exemple, lorsque nous affichons des enregistrements d'une base de données et que nous ne savons pas à l'avance combien il y en aura ni quels seront leurs ID, et que nous voulons afficher une case à cocher ou un bouton radio pour chaque ligne, il suffit de le coder dans le template : ```latte {foreach $items as $item} @@ -450,13 +547,13 @@ Pour ajouter un élément au formulaire, il n'est pas nécessaire d'appeler `$fo {/foreach} ``` -Après la soumission, vous pouvez récupérer les valeurs : +Et après soumission, nous obtenons la valeur : ```php $data = $form->getHttpData($form::DataText, 'sel[]'); $data = $form->getHttpData($form::DataText | $form::DataKeys, 'sel[]'); ``` -Dans le premier paramètre, vous spécifiez le type d'élément (`DataFile` pour `type=file`, `DataLine` pour les entrées d'une ligne comme `text`, `password` ou `email` et `DataText` pour le reste). Le deuxième paramètre correspond à l'attribut HTML `name`. Si vous devez préserver les clés, vous pouvez combiner le premier paramètre avec `DataKeys`. Ceci est utile pour `select`, `radioList` ou `checkboxList`. +où le premier paramètre est le type d'élément (`DataFile` pour `type=file`, `DataLine` pour les entrées sur une seule ligne comme `text`, `password`, `email`, etc. et `DataText` pour tous les autres) et le deuxième paramètre `sel[]` correspond à l'attribut HTML name. Nous pouvons combiner le type d'élément avec la valeur `DataKeys`, qui préserve les clés des éléments. Ceci est particulièrement utile pour `select`, `radioList` et `checkboxList`. -L'adresse `getHttpData()` renvoie des données d'entrée nettoyées. Dans ce cas, il s'agira toujours d'un tableau de chaînes UTF-8 valides, quel que soit l'attaquant envoyé par le formulaire. C'est une alternative au travail avec `$_POST` ou `$_GET` directement si vous voulez recevoir des données sûres. +L'essentiel est que `getHttpData()` renvoie une valeur assainie, dans ce cas, ce sera toujours un tableau de chaînes UTF-8 valides, quoi que l'attaquant ait tenté de soumettre au serveur. C'est analogue au travail direct avec `$_POST` ou `$_GET`, mais avec la différence essentielle qu'il renvoie toujours des données propres, comme vous en avez l'habitude avec les éléments standard des formulaires Nette. diff --git a/forms/fr/in-presenter.texy b/forms/fr/in-presenter.texy index 0fb310f2cb..442ef5b88a 100644 --- a/forms/fr/in-presenter.texy +++ b/forms/fr/in-presenter.texy @@ -1,36 +1,36 @@ -Formulaires dans Presenters -*************************** +Formulaires dans les presenters +******************************* .[perex] -Les formulaires Nette facilitent considérablement la création et le traitement des formulaires Web. Dans ce chapitre, vous allez apprendre à utiliser les formulaires dans les Presenters. +Nette Forms facilite grandement la création et le traitement des formulaires web. Dans ce chapitre, vous apprendrez à utiliser les formulaires à l'intérieur des presenters. -Si vous souhaitez les utiliser de manière complètement autonome sans le reste du framework, il existe un guide pour les [formulaires autonomes |standalone]. +Si vous vous demandez comment les utiliser de manière totalement autonome sans le reste du framework, le guide pour une [utilisation autonome|standalone] est fait pour vous. -Premier formulaire .[#toc-first-form] -===================================== +Premier formulaire +================== -Nous allons essayer d'écrire un formulaire d'inscription simple. Son code ressemblera à ceci : +Essayons d'écrire un formulaire d'inscription simple. Son code sera le suivant : ```php use Nette\Application\UI\Form; $form = new Form; -$form->addText('name', 'Name:'); -$form->addPassword('password', 'Password:'); -$form->addSubmit('send', 'Sign up'); +$form->addText('name', 'Nom :'); +$form->addPassword('password', 'Mot de passe :'); +$form->addSubmit('send', 'S\'inscrire'); $form->onSuccess[] = [$this, 'formSucceeded']; ``` -et dans le navigateur, le résultat devrait ressembler à ceci : +et dans le navigateur, il s'affichera comme ceci : -[* form-en.webp *] +[* form-cs.webp *] -Le formulaire dans le présentateur est un objet de la classe `Nette\Application\UI\Form`, son prédécesseur `Nette\Forms\Form` est destiné à une utilisation autonome. Nous lui avons ajouté les champs nom, mot de passe et bouton d'envoi. Enfin, la ligne avec `$form->onSuccess` indique qu'après soumission et validation réussie, la méthode `$this->formSucceeded()` doit être appelée. +Le formulaire dans le presenter est un objet de la classe `Nette\Application\UI\Form`, son prédécesseur `Nette\Forms\Form` est destiné à une utilisation autonome. Nous y avons ajouté des éléments appelés nom, mot de passe et un bouton d'envoi. Et enfin, la ligne avec `$form->onSuccess` indique qu'après l'envoi et la validation réussie, la méthode `$this->formSucceeded()` doit être appelée. -Du point de vue du présentateur, le formulaire est un composant commun. Par conséquent, il est traité comme un composant et incorporé dans le présentateur à l'aide de la [méthode factory |application:components#Factory Methods]. Cela ressemblera à ceci : +Du point de vue du presenter, le formulaire est un composant courant. Par conséquent, il est traité comme un composant et intégré au presenter à l'aide d'une [méthode factory |application:components#Méthodes Factory]. Cela ressemblera à ceci : -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} use Nette; use Nette\Application\UI\Form; @@ -39,103 +39,100 @@ class HomePresenter extends Nette\Application\UI\Presenter protected function createComponentRegistrationForm(): Form { $form = new Form; - $form->addText('name', 'Name:'); - $form->addPassword('password', 'Password:'); - $form->addSubmit('send', 'Sign up'); + $form->addText('name', 'Nom :'); + $form->addPassword('password', 'Mot de passe :'); + $form->addSubmit('send', 'S\'inscrire'); $form->onSuccess[] = [$this, 'formSucceeded']; return $form; } public function formSucceeded(Form $form, $data): void { - // ici nous allons traiter les données envoyées par le formulaire + // ici nous traitons les données envoyées par le formulaire // $data->name contient le nom - // $data->password contient password - $this->flashMessage('Vous vous êtes inscrit avec succès.'); + // $data->password contient le mot de passe + $this->flashMessage('Vous avez été enregistré avec succès.'); $this->redirect('Home:'); } } ``` -Et le rendu dans le modèle est effectué à l'aide de la balise `{control}`: +Et dans le template, nous affichons le formulaire avec la balise `{control}` : -```latte .{file:app/Presenters/templates/Home/default.latte} -<h1>Registration</h1> +```latte .{file:app/Presentation/Home/default.latte} +<h1>Inscription</h1> {control registrationForm} ``` -Et c'est tout :-) Nous avons un formulaire fonctionnel et parfaitement [sécurisé |#Vulnerability Protection]. +Et c'est tout :-) Nous avons un formulaire fonctionnel et parfaitement [sécurisé |#Protection contre les vulnérabilités]. -Maintenant, vous pensez probablement que c'était trop rapide, vous vous demandez comment il est possible que la méthode `formSucceeded()` soit appelée et quels sont les paramètres qu'elle reçoit. Bien sûr, vous avez raison, cela mérite une explication. +Et maintenant, vous vous dites probablement que c'était trop rapide, vous vous demandez comment il est possible que la méthode `formSucceeded()` soit appelée et quels sont les paramètres qu'elle reçoit. Bien sûr, vous avez raison, cela mérite une explication. -Nette propose un mécanisme sympa, que nous appelons le [style Hollywood |application:components#Hollywood style]. Au lieu de devoir constamment demander si quelque chose s'est produit ("le formulaire a-t-il été soumis ?", "a-t-il été soumis de manière valide ?" ou "n'a-t-il pas été forgé ?"), vous dites au framework "lorsque le formulaire est valablement rempli, appelez cette méthode" et laissez la suite du travail. Si vous programmez en JavaScript, vous êtes familier avec ce style de programmation. Vous écrivez des fonctions qui sont appelées lorsqu'un certain [événement |nette:glossary#Events] se produit. Et le langage leur transmet les arguments appropriés. +Nette propose en effet un mécanisme rafraîchissant que nous appelons le [style Hollywood |application:components#Style Hollywood]. Au lieu que vous, en tant que développeur, deviez constamment demander si quelque chose s'est passé ("le formulaire a-t-il été envoyé ?", "a-t-il été envoyé valablement ?" et "n'a-t-il pas été falsifié ?"), vous dites au framework "une fois que le formulaire sera valablement rempli, appelle cette méthode" et vous lui laissez le reste du travail. Si vous programmez en JavaScript, vous connaissez bien ce style de programmation. Vous écrivez des fonctions qui sont appelées lorsqu'un certain [événement |nette:glossary#Événements events] se produit. Et le langage leur transmet les arguments appropriés. -C'est ainsi que le code du présentateur ci-dessus est construit. Le tableau `$form->onSuccess` représente la liste des callbacks PHP que Nette appellera lorsque le formulaire sera soumis et rempli correctement. -Dans le [cycle de vie du présentateur |application:presenters#Life Cycle of Presenter], il s'agit de ce que l'on appelle un signal. Ils sont donc appelés après la méthode `action*` et avant la méthode `render*`. -Il transmet à chaque callback le formulaire lui-même dans le premier paramètre et les données envoyées sous forme d'objet [ArrayHash |utils:arrays#ArrayHash] dans le second. Vous pouvez omettre le premier paramètre si vous n'avez pas besoin de l'objet formulaire. Le second paramètre peut être encore plus pratique, mais nous y reviendrons [plus tard |#Mapping to Classes]. +C'est précisément ainsi qu'est construit le code du presenter ci-dessus. Le tableau `$form->onSuccess` représente une liste de callbacks PHP que Nette appellera au moment où le formulaire est envoyé et correctement rempli (c'est-à-dire qu'il est valide). Dans le cadre du [cycle de vie du presenter |application:presenters#Cycle de vie du presenter], il s'agit d'un signal, ils sont donc appelés après la méthode `action*` et avant la méthode `render*`. Et à chaque callback, il passe comme premier paramètre le formulaire lui-même et comme second les données envoyées sous forme d'objet [ArrayHash |utils:arrays#ArrayHash]. Vous pouvez omettre le premier paramètre si vous n'avez pas besoin de l'objet formulaire. Et le second paramètre peut être plus malin, mais nous en reparlerons [plus tard |#Mappage sur les classes]. -L'objet `$data` contient les propriétés `name` et `password` avec les données saisies par l'utilisateur. Habituellement, nous envoyons les données directement pour un traitement ultérieur, qui peut être, par exemple, l'insertion dans la base de données. Toutefois, une erreur peut se produire pendant le traitement, par exemple, le nom d'utilisateur est déjà pris. Dans ce cas, nous renvoyons l'erreur au formulaire en utilisant `addError()` et le laissons se redessiner, avec un message d'erreur : +L'objet `$data` contient les clés `name` et `password` avec les informations que l'utilisateur a remplies. Habituellement, nous envoyons directement les données pour un traitement ultérieur, ce qui peut être par exemple une insertion dans la base de données. Cependant, une erreur peut survenir pendant le traitement, par exemple le nom d'utilisateur est déjà pris. Dans ce cas, nous renvoyons l'erreur au formulaire à l'aide de `addError()` et le laissons se réafficher, avec le message d'erreur. ```php -$form->addError('Sorry, username is already in use.'); +$form->addError('Désolé, ce nom d\'utilisateur est déjà pris.'); ``` -En plus de `onSuccess`, il y a aussi `onSubmit`: les callbacks sont toujours appelés après l'envoi du formulaire, même s'il n'est pas rempli correctement. Et enfin `onError`: les callbacks ne sont appelés que si la soumission n'est pas valide. Ils sont même appelés si nous invalidons le formulaire dans `onSuccess` ou `onSubmit` en utilisant `addError()`. +En plus de `onSuccess`, il existe également `onSubmit` : les callbacks sont toujours appelés après l'envoi du formulaire, même s'il n'est pas correctement rempli. Et ensuite `onError` : les callbacks ne sont appelés que si l'envoi n'est pas valide. Ils sont même appelés si, dans `onSuccess` ou `onSubmit`, nous invalidons le formulaire avec `addError()`. -Après avoir traité le formulaire, nous redirigeons vers la page suivante. Cela permet d'éviter que le formulaire soit involontairement resoumis en cliquant sur le bouton *refresh*, *back*, ou en déplaçant l'historique du navigateur. +Après le traitement du formulaire, nous redirigeons vers la page suivante. Cela évite la ré-soumission involontaire du formulaire par le bouton *actualiser*, *retour* ou par le déplacement dans l'historique du navigateur. -Essayez d'ajouter d'autres [contrôles de formulaire |controls]. +Essayez d'ajouter d'autres [éléments de formulaire|controls]. -Accès aux contrôles .[#toc-access-to-controls] -============================================== +Accès aux éléments +================== -Le formulaire est un composant du présentateur, nommé dans notre cas `registrationForm` (d'après le nom de la méthode d'usine `createComponentRegistrationForm`), donc n'importe où dans le présentateur vous pouvez accéder au formulaire en utilisant : +Le formulaire est un composant du presenter, dans notre cas nommé `registrationForm` (d'après le nom de la méthode factory `createComponentRegistrationForm`), donc n'importe où dans le presenter, vous pouvez accéder au formulaire via : ```php $form = $this->getComponent('registrationForm'); -// syntaxe alternative: $form = $this['registrationForm']; +// syntaxe alternative : $form = $this['registrationForm']; ``` -Les contrôles individuels du formulaire sont également des composants, vous pouvez donc y accéder de la même manière : +Les éléments individuels du formulaire sont également des composants, vous pouvez donc y accéder de la même manière : ```php $input = $form->getComponent('name'); // ou $input = $form['name']; $button = $form->getComponent('send'); // ou $button = $form['send']; ``` -Les contrôles sont supprimés à l'aide de unset : +Les éléments sont supprimés avec unset : ```php unset($form['name']); ``` -Règles de validation .[#toc-validation-rules] -============================================= +Règles de validation +==================== -Le mot *valide* a été utilisé plusieurs fois, mais le formulaire n'a pas encore de règles de validation. Corrigeons cela. +Le mot *valide* a été mentionné, mais le formulaire n'a pas encore de règles de validation. Corrigeons cela. -Le nom sera obligatoire, nous le marquerons donc avec la méthode `setRequired()`, dont l'argument est le texte du message d'erreur qui sera affiché si l'utilisateur ne le remplit pas. Si aucun argument n'est donné, le message d'erreur par défaut est utilisé. +Le nom sera obligatoire, nous le marquerons donc avec la méthode `setRequired()`, dont l'argument est le texte du message d'erreur qui s'affichera si l'utilisateur ne remplit pas le nom. Si l'argument n'est pas fourni, le message d'erreur par défaut sera utilisé. ```php -$form->addText('name', 'Name:') - ->setRequired('Please fill your name.'); +$form->addText('name', 'Nom :') + ->setRequired('Veuillez entrer un nom'); ``` -Essayez de soumettre le formulaire sans que le nom soit rempli et vous verrez qu'un message d'erreur s'affiche et que le navigateur ou le serveur le rejettera jusqu'à ce que vous le remplissiez. +Essayez d'envoyer le formulaire sans remplir le nom et vous verrez que le message d'erreur s'affichera et que le navigateur ou le serveur le refusera jusqu'à ce que vous remplissiez le champ. -En même temps, vous ne pourrez pas tromper le système en ne tapant que des espaces dans la saisie, par exemple. Pas question. Nette supprime automatiquement les espaces à gauche et à droite. Essayez-le. C'est quelque chose que vous devriez toujours faire avec chaque entrée d'une seule ligne, mais on l'oublie souvent. Nette le fait automatiquement. (Vous pouvez essayer de tromper les formulaires et envoyer une chaîne de caractères multiligne comme nom. Même dans ce cas, Nette ne sera pas dupe et les sauts de ligne se transformeront en espaces). +En même temps, vous ne tromperez pas le système en tapant, par exemple, uniquement des espaces dans le champ. Non. Nette supprime automatiquement les espaces de début et de fin. Essayez. C'est quelque chose que vous devriez toujours faire avec chaque input sur une seule ligne, mais on l'oublie souvent. Nette le fait automatiquement. (Vous pouvez essayer de tromper le formulaire et envoyer une chaîne multiligne comme nom. Même ici, Nette ne se laissera pas berner et changera les sauts de ligne en espaces.) -Le formulaire est toujours validé du côté du serveur, mais la validation JavaScript est également générée, ce qui est rapide et l'utilisateur connaît l'erreur immédiatement, sans avoir à envoyer le formulaire au serveur. Ceci est géré par le script `netteForms.js`. -Insérez-le dans le modèle de mise en page : +Le formulaire est toujours validé côté serveur, mais une validation JavaScript est également générée, qui s'exécute instantanément et l'utilisateur est informé de l'erreur immédiatement, sans avoir besoin d'envoyer le formulaire au serveur. C'est le script `netteForms.js` qui s'en charge. Insérez-le dans le template de layout : ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Si vous regardez dans le code source de la page avec formulaire, vous pouvez remarquer que Nette insère les champs obligatoires dans des éléments avec une classe CSS `required`. Essayez d'ajouter le style suivant au modèle, et l'étiquette "Nom" sera rouge. De manière élégante, nous marquons les champs obligatoires pour les utilisateurs : +Si vous regardez le code source de la page avec le formulaire, vous remarquerez peut-être que Nette insère les éléments requis dans des éléments avec la classe CSS `required`. Essayez d'ajouter la feuille de style suivante au template et l'étiquette "Nom" sera rouge. Nous marquons ainsi élégamment les éléments requis pour les utilisateurs : ```latte <style> @@ -143,110 +140,112 @@ Si vous regardez dans le code source de la page avec formulaire, vous pouvez rem </style> ``` -Des règles de validation supplémentaires seront ajoutées par la méthode `addRule()`. Le premier paramètre est la règle, le second est à nouveau le texte du message d'erreur, et l'argument facultatif de la règle de validation peut suivre. Qu'est-ce que cela signifie ? +Nous ajoutons d'autres règles de validation avec la méthode `addRule()`. Le premier paramètre est la règle, le second est à nouveau le texte du message d'erreur et il peut y avoir un argument de règle de validation. Qu'est-ce que cela signifie ? -Le formulaire recevra une autre entrée facultative *âge* avec la condition qu'il s'agisse d'un nombre (`addInteger()`) et qu'il soit dans certaines limites (`$form::Range`). Et ici, nous utiliserons le troisième argument de `addRule()`, la plage elle-même : +Nous étendons le formulaire avec un nouveau champ facultatif "âge", qui doit être un entier (`addInteger()`) et de plus dans une plage autorisée (`$form::Range`). Et c'est ici que nous utilisons le troisième paramètre de la méthode `addRule()`, avec lequel nous passons la plage requise au validateur sous forme de paire `[de, à]` : ```php -$form->addInteger('age', 'Age:') - ->addRule($form::Range, 'You must be older 18 years and be under 120.', [18, 120]); +$form->addInteger('age', 'Âge :') + ->addRule($form::Range, 'L\'âge doit être compris entre 18 et 120', [18, 120]); ``` .[tip] -Si l'utilisateur ne remplit pas le champ, les règles de validation ne seront pas vérifiées, car le champ est facultatif. +Si l'utilisateur ne remplit pas le champ, les règles de validation ne seront pas vérifiées, car l'élément est facultatif. -Il y a évidemment de la place pour un petit remaniement. Dans le message d'erreur et dans le troisième paramètre, les nombres sont listés en double, ce qui n'est pas idéal. Si nous créions un [formulaire multilingue |rendering#translating] et que le message contenant les chiffres devait être traduit en plusieurs langues, il serait plus difficile de modifier les valeurs. C'est pourquoi on peut utiliser les caractères de substitution `%d`: +Il y a ici place pour un petit refactoring. Dans le message d'erreur et dans le troisième paramètre, les nombres sont indiqués en double, ce qui n'est pas idéal. Si nous créions des [formulaires multilingues |rendering#Traduction] et que le message contenant des nombres était traduit dans plusieurs langues, un éventuel changement de valeurs serait compliqué. Pour cette raison, il est possible d'utiliser les placeholders `%d` et Nette complétera les valeurs : ```php - ->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]); + ->addRule($form::Range, 'L\'âge doit être compris entre %d et %d ans', [18, 120]); ``` -Revenons au champ *mot de passe*, rendons-le *obligatoire* et vérifions la longueur minimale du mot de passe (`$form::MinLength`), en utilisant à nouveau les caractères de substitution du message : +Revenons à l'élément `password`, que nous rendrons également obligatoire et vérifierons la longueur minimale du mot de passe (`$form::MinLength`), en utilisant à nouveau le placeholder : ```php -$form->addPassword('password', 'Password:') - ->setRequired('Pick a password') - ->addRule($form::MinLength, 'Your password has to be at least %d long', 8); +$form->addPassword('password', 'Mot de passe :') + ->setRequired('Choisissez un mot de passe') + ->addRule($form::MinLength, 'Le mot de passe doit comporter au moins %d caractères', 8); ``` -Nous ajouterons un champ `passwordVerify` au formulaire, dans lequel l'utilisateur saisira à nouveau le mot de passe, pour vérification. En utilisant les règles de validation, nous vérifions si les deux mots de passe sont les mêmes (`$form::Equal`). Et comme argument, nous donnons une référence au premier mot de passe en utilisant des [crochets |#Access to Controls]: +Ajoutons au formulaire un champ `passwordVerify`, où l'utilisateur saisira à nouveau le mot de passe, pour vérification. À l'aide des règles de validation, nous vérifions si les deux mots de passe sont identiques (`$form::Equal`). Et comme paramètre, nous donnons une référence au premier mot de passe en utilisant des [crochets |#Accès aux éléments] : ```php -$form->addPassword('passwordVerify', 'Password again:') - ->setRequired('Fill your password again to check for typo') - ->addRule($form::Equal, 'Password mismatch', $form['password']) +$form->addPassword('passwordVerify', 'Mot de passe pour vérification :') + ->setRequired('Veuillez saisir à nouveau le mot de passe pour vérification') + ->addRule($form::Equal, 'Les mots de passe ne correspondent pas', $form['password']) ->setOmitted(); ``` -Avec `setOmitted()`, nous marquons un élément dont la valeur ne nous intéresse pas vraiment et qui n'existe que pour la validation. Sa valeur n'est pas transmise à `$data`. +Avec `setOmitted()`, nous avons marqué l'élément dont la valeur ne nous importe pas vraiment et qui n'existe que pour la validation. La valeur n'est pas transmise à `$data`. -Nous avons un formulaire entièrement fonctionnel avec validation en PHP et JavaScript. Les capacités de validation de Nette sont beaucoup plus larges, vous pouvez créer des conditions, afficher et masquer des parties d'une page en fonction de celles-ci, etc. Vous pouvez tout découvrir dans le chapitre sur la [validation des formulaires |validation]. +Nous avons ainsi un formulaire entièrement fonctionnel avec validation en PHP et JavaScript. Les capacités de validation de Nette sont beaucoup plus larges, on peut créer des conditions, afficher et masquer des parties de la page en fonction d'elles, etc. Vous apprendrez tout cela dans le chapitre sur la [validation des formulaires|validation]. -Valeurs par défaut .[#toc-default-values] -========================================= +Valeurs par défaut +================== -Nous définissons souvent des valeurs par défaut pour les contrôles de formulaires : +Nous définissons couramment des valeurs par défaut pour les éléments de formulaire : ```php -$form->addEmail('email', 'Email') +$form->addEmail('email', 'E-mail') ->setDefaultValue($lastUsedEmail); ``` -Il est souvent utile de définir des valeurs par défaut pour tous les contrôles à la fois. Par exemple, lorsque le formulaire est utilisé pour modifier des enregistrements. Nous lisons l'enregistrement depuis la base de données et le définissons comme valeur par défaut : +Il est souvent utile de définir les valeurs par défaut de tous les éléments en même temps. Par exemple, lorsque le formulaire sert à modifier des enregistrements. Nous lisons l'enregistrement de la base de données et définissons les valeurs par défaut : ```php //$row = ['name' => 'John', 'age' => '33', /* ... */]; $form->setDefaults($row); ``` -Appelez `setDefaults()` après avoir défini les contrôles. +Appelez `setDefaults()` après avoir défini les éléments. -Rendu du formulaire .[#toc-rendering-the-form] -============================================== +Rendu du formulaire +=================== -Par défaut, le formulaire est rendu sous forme de tableau. Les contrôles individuels suivent les directives de base en matière d'accessibilité du Web. Toutes les étiquettes sont générées en tant qu'éléments `<label>` et sont associées à leurs entrées. Un clic sur l'étiquette déplace le curseur sur l'entrée. +Par défaut, le formulaire est rendu sous forme de tableau. Les éléments individuels respectent la règle d'accessibilité de base - toutes les étiquettes sont écrites comme `<label>` et liées à l'élément de formulaire correspondant. En cliquant sur l'étiquette, le curseur apparaît automatiquement dans le champ de formulaire. -Nous pouvons définir n'importe quel attribut HTML pour chaque élément. Par exemple, ajouter un espace réservé : +Nous pouvons définir des attributs HTML arbitraires pour chaque élément. Par exemple, ajouter un placeholder : ```php -$form->addInteger('age', 'Age:') - ->setHtmlAttribute('placeholder', 'Please fill in the age'); +$form->addInteger('age', 'Âge :') + ->setHtmlAttribute('placeholder', 'Veuillez remplir l\'âge'); ``` -Il y a vraiment beaucoup de façons de rendre un formulaire, c'est pourquoi ce [chapitre |rendering] est consacré [au rendu |rendering]. +Il existe vraiment de nombreuses façons de rendre un formulaire, c'est pourquoi un [chapitre distinct sur le rendu|rendering] y est consacré. -Mappage vers les classes .[#toc-mapping-to-classes] -=================================================== +Mappage sur les classes +======================= -Revenons à la méthode `formSucceeded()`, qui, dans le second paramètre `$data`, reçoit les données envoyées sous la forme d'un objet `ArrayHash`. Comme il s'agit d'une classe générique, un peu comme `stdClass`, il nous manquera certaines commodités pour travailler avec elle, comme la complétion du code pour les propriétés dans les éditeurs ou l'analyse statique du code. Cela pourrait être résolu en ayant une classe spécifique pour chaque formulaire, dont les propriétés représentent les contrôles individuels. Par exemple : +Revenons à la méthode `formSucceeded()`, qui reçoit dans le deuxième paramètre `$data` les données envoyées sous forme d'objet `ArrayHash`. Comme il s'agit d'une classe générique, quelque chose comme `stdClass`, il nous manquera un certain confort lors de son utilisation, comme la suggestion des propriétés dans les éditeurs ou l'analyse statique du code. Cela pourrait être résolu en ayant une classe spécifique pour chaque formulaire, dont les propriétés représentent les éléments individuels. Par exemple : ```php class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -A partir de PHP 8.0, vous pouvez utiliser cette notation élégante qui utilise un constructeur : +Alternativement, vous pouvez utiliser un constructeur : ```php class RegistrationFormData { public function __construct( public string $name, - public int $age, + public ?int $age, public string $password, ) { } } ``` -Comment dire à Nette de retourner les données sous forme d'objets de cette classe ? C'est plus facile que vous ne le pensez. Tout ce que vous avez à faire est de spécifier la classe comme type du paramètre `$data` dans le gestionnaire : +Les propriétés de la classe de données peuvent également être des enums et elles seront automatiquement mappées. .{data-version:3.2.4} + +Comment dire à Nette de nous retourner les données sous forme d'objets de cette classe ? Plus facilement que vous ne le pensez. Il suffit d'indiquer la classe comme type du paramètre `$data` dans la méthode de gestion : ```php public function formSucceeded(Form $form, RegistrationFormData $data): void @@ -257,16 +256,16 @@ public function formSucceeded(Form $form, RegistrationFormData $data): void } ``` -Vous pouvez également spécifier `array` comme type et les données seront alors transmises sous forme de tableau. +Comme type, on peut également indiquer `array` et alors les données seront passées sous forme de tableau. -De la même manière, vous pouvez utiliser la méthode `getValues()`, que nous passons comme nom de classe ou objet à hydrater en tant que paramètre : +De la même manière, on peut utiliser la méthode `getValues()`, à laquelle on passe le nom de la classe ou l'objet à hydrater comme paramètre : ```php $data = $form->getValues(RegistrationFormData::class); $name = $data->name; ``` -Si les formulaires consistent en une structure à plusieurs niveaux composée de conteneurs, créez une classe distincte pour chacun d'eux : +Si les formulaires forment une structure à plusieurs niveaux composée de conteneurs, créez une classe distincte pour chacun : ```php $form = new Form; @@ -283,32 +282,34 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` -Le mappage sait alors, à partir du type de propriété `$person`, qu'il doit mapper le conteneur à la classe `PersonFormData`. Si la propriété contient un tableau de conteneurs, fournissez le type `array` et passez la classe à mapper directement au conteneur : +Le mappage reconnaîtra alors à partir du type de la propriété `$person` qu'il doit mapper le conteneur sur la classe `PersonFormData`. Si la propriété contenait un tableau de conteneurs, indiquez le type `array` et passez la classe pour le mappage directement au conteneur : ```php $person->setMappedType(PersonFormData::class); ``` +Vous pouvez faire générer la conception de la classe de données du formulaire à l'aide de la méthode `Nette\Forms\Blueprint::dataClass($form)`, qui l'affichera dans la page du navigateur. Il suffit ensuite de cliquer pour sélectionner le code et de le copier dans le projet. .{data-version:3.1.15} + -Boutons d'envoi multiples .[#toc-multiple-submit-buttons] -========================================================= +Plusieurs boutons +================= -Si le formulaire comporte plus d'un bouton, nous avons généralement besoin de distinguer lequel a été pressé. Nous pouvons créer notre propre fonction pour chaque bouton. Définissez-la comme gestionnaire de l'[événement |nette:glossary#Events] `onClick`: +Si le formulaire a plus d'un bouton, nous devons généralement distinguer lequel a été pressé. Nous pouvons créer notre propre fonction de gestion pour chaque bouton. Nous la définissons comme handler pour l'[événement |nette:glossary#Événements events] `onClick` : ```php -$form->addSubmit('save', 'Save') +$form->addSubmit('save', 'Enregistrer') ->onClick[] = [$this, 'saveButtonPressed']; -$form->addSubmit('delete', 'Delete') +$form->addSubmit('delete', 'Supprimer') ->onClick[] = [$this, 'deleteButtonPressed']; ``` -Ces gestionnaires sont également appelés uniquement dans le cas où le formulaire est valide, comme dans le cas de l'événement `onSuccess`. La différence est que le premier paramètre peut être l'objet bouton de soumission au lieu du formulaire, selon le type que vous spécifiez : +Ces handlers ne sont appelés que dans le cas d'un formulaire valablement rempli, tout comme dans le cas de l'événement `onSuccess`. La différence est que comme premier paramètre, au lieu du formulaire, le bouton d'envoi peut être passé, cela dépend du type que vous indiquez : ```php public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) @@ -318,62 +319,61 @@ public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) } ``` -Lorsqu'un formulaire est soumis avec la touche <kbd>Entrée</kbd>, il est traité comme s'il avait été soumis avec le premier bouton. +Lorsque le formulaire est soumis avec la touche <kbd>Entrée</kbd>, il est considéré comme s'il avait été soumis avec le premier bouton. -Événement onAnchor .[#toc-event-onanchor] -========================================= +Événement onAnchor +================== -Lorsque vous construisez un formulaire à l'aide d'une méthode d'usine (comme `createComponentRegistrationForm`), il ne sait pas encore s'il a été soumis ou avec quelles données il a été soumis. Mais il existe des cas où nous avons besoin de connaître les valeurs soumises, peut-être que l'aspect du formulaire en dépend, ou qu'elles sont utilisées pour des boîtes de sélection dépendantes, etc. +Lorsque nous assemblons le formulaire dans la méthode factory (comme par exemple `createComponentRegistrationForm`), celui-ci ne sait pas encore s'il a été soumis, ni avec quelles données. Mais il y a des cas où nous avons besoin de connaître les valeurs soumises, par exemple si la forme ultérieure du formulaire en dépend, ou si nous en avons besoin pour des selectbox dépendants, etc. -Par conséquent, vous pouvez faire en sorte que le code qui construit le formulaire soit appelé lorsqu'il est ancré, c'est-à-dire qu'il est déjà lié au présentateur et qu'il connaît ses données soumises. Nous placerons ce code dans le tableau `$onAnchor`: +Une partie du code assemblant le formulaire peut donc être appelée uniquement au moment où il est dit ancré, c'est-à-dire qu'il est déjà connecté au presenter et connaît ses données soumises. Nous passons un tel code dans le tableau `$onAnchor` : ```php -$country = $form->addSelect('country', 'Country:', $this->model->getCountries()); -$city = $form->addSelect('city', 'City:'); +$country = $form->addSelect('country', 'État :', $this->model->getCountries()); +$city = $form->addSelect('city', 'Ville :'); $form->onAnchor[] = function () use ($country, $city) { - // cette fonction sera appelée lorsque le formulaire connaîtra les données avec lesquelles il a été soumis - // pour que vous puissiez utiliser la méthode getValue() + // cette fonction sera appelée seulement lorsque le formulaire saura s'il a été soumis et avec quelles données + // on peut donc utiliser la méthode getValue() $val = $country->getValue(); $city->setItems($val ? $this->model->getCities($val) : []); }; ``` -Protection contre les vulnérabilités .[#toc-vulnerability-protection] -===================================================================== +Protection contre les vulnérabilités +==================================== -Nette Framework fait de gros efforts pour être sûr et comme les formulaires sont les entrées les plus courantes de l'utilisateur, les formulaires Nette sont aussi bien impénétrables. Tout est géré de manière dynamique et transparente, rien ne doit être réglé manuellement. +Nette Framework accorde une grande importance à la sécurité et veille donc scrupuleusement à la bonne sécurisation des formulaires. Il le fait de manière totalement transparente et ne nécessite aucune configuration manuelle. -En plus de protéger les formulaires contre les attaques visant des vulnérabilités bien connues comme le [Cross-Site Scripting (XSS) |nette:glossary#cross-site-scripting-xss] et le [Cross-Site Request Forgery (CSRF) |nette:glossary#cross-site-request-forgery-csrf], il effectue de nombreuses petites tâches de sécurité auxquelles vous ne devez plus penser. +En plus de protéger les formulaires contre les attaques [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] et [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF], il effectue de nombreuses petites sécurisations auxquelles vous n'avez plus à penser. -Par exemple, il filtre tous les caractères de contrôle des entrées et vérifie la validité de l'encodage UTF-8, de sorte que les données du formulaire sont toujours propres. Pour les cases de sélection et les listes radio, il vérifie que les éléments sélectionnés font bien partie des éléments proposés et qu'il n'y a pas eu de falsification. Nous avons déjà mentionné que pour les entrées de texte sur une seule ligne, il supprime les caractères de fin de ligne qu'un attaquant pourrait y envoyer. Pour les entrées multilignes, il normalise les caractères de fin de ligne. Et ainsi de suite. +Par exemple, il filtre tous les caractères de contrôle des entrées et vérifie la validité de l'encodage UTF-8, de sorte que les données du formulaire seront toujours propres. Pour les select box et les radio lists, il vérifie que les éléments sélectionnés étaient bien parmi ceux proposés et qu'il n'y a pas eu de falsification. Nous avons déjà mentionné que pour les entrées de texte sur une seule ligne, il supprime les caractères de fin de ligne qu'un attaquant aurait pu envoyer. Pour les entrées multilignes, il normalise les caractères de fin de ligne. Et ainsi de suite. -Nette corrige pour vous les failles de sécurité dont la plupart des programmeurs ignorent l'existence. +Nette résout pour vous les risques de sécurité dont de nombreux programmeurs ignorent même l'existence. -L'attaque CSRF mentionnée consiste en ce qu'un attaquant attire la victime à visiter une page qui exécute silencieusement une requête dans le navigateur de la victime vers le serveur où la victime est actuellement connectée, et le serveur croit que la requête a été faite par la victime à volonté. Par conséquent, Nette empêche que le formulaire soit soumis via POST à partir d'un autre domaine. Si, pour une raison quelconque, vous souhaitez désactiver la protection et autoriser la soumission du formulaire à partir d'un autre domaine, utilisez : +L'attaque CSRF mentionnée consiste en ce qu'un attaquant attire la victime sur une page qui exécute discrètement dans le navigateur de la victime une requête vers le serveur sur lequel la victime est connectée, et le serveur croit que la requête a été exécutée par la victime de sa propre volonté. C'est pourquoi Nette empêche l'envoi d'un formulaire POST depuis un autre domaine. Si pour une raison quelconque vous souhaitez désactiver la protection et autoriser l'envoi du formulaire depuis un autre domaine, utilisez : ```php $form->allowCrossOrigin(); // ATTENTION ! Désactive la protection ! ``` -Cette protection utilise un cookie SameSite nommé `_nss`. La protection par cookie SameSite peut ne pas être fiable à 100 %, il est donc préférable d'activer la protection par jeton : +Cette protection utilise un cookie SameSite nommé `_nss`. La protection via le cookie SameSite peut ne pas être fiable à 100%, il est donc conseillé d'activer également la protection par jeton : ```php $form->addProtection(); ``` -Il est fortement recommandé d'appliquer cette protection aux formulaires d'une partie administrative de votre application qui modifie des données sensibles. Le framework protège contre une attaque CSRF en générant et en validant le jeton d'authentification qui est stocké dans une session (l'argument est le message d'erreur affiché si le jeton a expiré). C'est pourquoi il est nécessaire de démarrer une session avant d'afficher le formulaire. Dans la partie administration du site, la session est généralement déjà démarrée, en raison du login de l'utilisateur. -Sinon, démarrez la session avec la méthode `Nette\Http\Session::start()`. +Nous recommandons de protéger ainsi les formulaires dans la partie administrative du site web, qui modifient des données sensibles dans l'application. Le framework se défend contre l'attaque CSRF en générant et en vérifiant un jeton d'autorisation qui est stocké dans la session. Par conséquent, il est nécessaire d'avoir une session ouverte avant d'afficher le formulaire. Dans la partie administrative du site web, la session est généralement déjà démarrée en raison de la connexion de l'utilisateur. Sinon, démarrez la session avec la méthode `Nette\Http\Session::start()`. -Utilisation d'un formulaire dans plusieurs présentateurs .[#toc-using-one-form-in-multiple-presenters] -====================================================================================================== +Même formulaire dans plusieurs presenters +========================================= -Si vous devez utiliser un formulaire dans plusieurs présentateurs, nous vous recommandons de créer une fabrique pour ce formulaire, que vous transmettrez ensuite au présentateur. Un emplacement approprié pour une telle classe est, par exemple, le répertoire `app/Forms`. +Si vous avez besoin d'utiliser un même formulaire dans plusieurs presenters, nous vous recommandons de créer une factory pour celui-ci, que vous passerez ensuite au presenter via l'injection de dépendances. Un emplacement approprié pour une telle classe est par exemple le répertoire `app/Forms`. -La classe de fabrique pourrait ressembler à ceci : +La classe factory peut ressembler à ceci : ```php use Nette\Application\UI\Form; @@ -383,14 +383,14 @@ class SignInFormFactory public function create(): Form { $form = new Form; - $form->addText('name', 'Name:'); - $form->addSubmit('send', 'Log in'); + $form->addText('name', 'Nom :'); + $form->addSubmit('send', 'Se connecter'); return $form; } } ``` -Nous demandons à la classe de produire le formulaire dans la méthode d'usine pour les composants du présentateur : +Nous demandons à la classe de fabriquer le formulaire dans la méthode factory pour les composants dans le presenter : ```php public function __construct( @@ -401,14 +401,14 @@ public function __construct( protected function createComponentSignInForm(): Form { $form = $this->formFactory->create(); - // nous pouvons modifier le formulaire, ici par exemple nous changeons le libellé du bouton - $form['login']->setCaption('Continue'); - $form->onSuccess[] = [$this, 'signInFormSubmitted']; // et ajouter le gestionnaire + // nous pouvons modifier le formulaire, ici par exemple nous changeons l'étiquette sur le bouton + $form['send']->setCaption('Continuer'); + $form->onSuccess[] = [$this, 'signInFormSuceeded']; // et ajoutons un handler return $form; } ``` -Le gestionnaire de traitement du formulaire peut également être fourni par la fabrique : +Le handler pour le traitement du formulaire peut également être fourni par la factory : ```php use Nette\Application\UI\Form; @@ -418,14 +418,14 @@ class SignInFormFactory public function create(): Form { $form = new Form; - $form->addText('name', 'Name:'); - $form->addSubmit('send', 'Log in'); + $form->addText('name', 'Nom :'); + $form->addSubmit('send', 'Se connecter'); $form->onSuccess[] = function (Form $form, $data): void { - // nous traitons ici notre formulaire soumis + // ici nous effectuons le traitement du formulaire }; return $form; } } ``` -Ainsi, nous avons une introduction rapide aux formulaires dans Nette. Essayez de regarder dans le répertoire des [exemples |https://github.com/nette/forms/tree/master/examples] dans la distribution pour plus d'inspiration. +Voilà, nous avons eu une introduction rapide aux formulaires dans Nette. Essayez de regarder également dans le répertoire [exemples|https://github.com/nette/forms/tree/master/examples] de la distribution, où vous trouverez plus d'inspiration. diff --git a/forms/fr/rendering.texy b/forms/fr/rendering.texy index 2915a9b2c9..75299a5126 100644 --- a/forms/fr/rendering.texy +++ b/forms/fr/rendering.texy @@ -1,33 +1,35 @@ Rendu des formulaires ********************* -L'aspect des formes peut être très varié. Dans la pratique, nous pouvons rencontrer deux extrêmes. D'une part, il est nécessaire de rendre une série de formulaires dans une application qui sont visuellement similaires les uns aux autres, et nous apprécions le rendu facile sans modèle à l'aide de `$form->render()`. C'est généralement le cas des interfaces administratives. +L'apparence des formulaires peut être très variée. En pratique, nous pouvons rencontrer deux extrêmes. D'un côté, il y a le besoin de rendre dans l'application de nombreux formulaires qui se ressemblent visuellement comme deux gouttes d'eau, et nous apprécierons un rendu facile sans template à l'aide de `$form->render()`. C'est généralement le cas des interfaces d'administration. -D'autre part, il existe différents formulaires dont chacun est unique. Leur apparence est mieux décrite en utilisant le langage HTML dans le modèle. Et bien sûr, en plus des deux extrêmes mentionnés, nous rencontrerons de nombreux formulaires qui se situent quelque part entre les deux. +De l'autre côté, il y a des formulaires variés où la règle est : chaque pièce est originale. Leur forme est mieux décrite par le langage HTML dans le template du formulaire. Et bien sûr, en plus des deux extrêmes mentionnés, nous rencontrerons de nombreux formulaires qui se situent quelque part entre les deux. -Rendu avec Latte .[#toc-rendering-with-latte] -============================================= +Rendu avec Latte +================ -Le [système de templates Latte |latte:] facilite fondamentalement le rendu des formulaires et de leurs éléments. Nous allons d'abord montrer comment rendre les formulaires manuellement, élément par élément, afin d'avoir un contrôle total sur le code. Plus tard, nous montrerons comment [automatiser |#Automatic rendering] ce rendu. +Le [Système de templates Latte|latte:] facilite grandement le rendu des formulaires et de leurs éléments. Nous allons d'abord montrer comment rendre les formulaires manuellement, élément par élément, et ainsi obtenir un contrôle total sur le code. Plus tard, nous montrerons comment ce rendu peut être [automatisé |#Rendu automatique]. + +Vous pouvez faire générer le design du template Latte du formulaire à l'aide de la méthode `Nette\Forms\Blueprint::latte($form)`, qui l'affichera dans la page du navigateur. Il suffit ensuite de cliquer pour sélectionner le code et de le copier dans le projet. .{data-version:3.1.15} `{control}` ----------- -La façon la plus simple de rendre un formulaire est d'écrire dans un modèle : +La manière la plus simple de rendre un formulaire est d'écrire dans le template : ```latte {control signInForm} ``` -L'aspect du formulaire rendu peut être modifié en configurant le [Renderer |#Renderer] et les [contrôles individuels |#HTML Attributes]. +L'apparence du formulaire ainsi rendu peut être influencée par la configuration du [#Renderer] et des [éléments individuels |#Attributs HTML]. `n:name` -------- -Il est extrêmement facile de lier la définition du formulaire dans le code PHP avec le code HTML. Il suffit d'ajouter les attributs `n:name`. C'est aussi simple que cela ! +La définition du formulaire dans le code PHP peut être très facilement liée au code HTML. Il suffit d'ajouter des attributs `n:name`. C'est aussi simple que ça ! ```php protected function createComponentSignInForm(): Form @@ -54,10 +56,9 @@ protected function createComponentSignInForm(): Form </form> ``` -L'aspect du code HTML résultant est entièrement entre vos mains. Si vous utilisez l'attribut `n:name` avec `<select>`, `<button>` ou `<textarea>` leur contenu interne est automatiquement rempli. -En outre, la balise `<form n:name>` crée une variable locale `$form` avec l'objet de formulaire dessiné et la balise de fermeture `</form>` dessine tous les éléments cachés non dessinés (il en va de même pour `{form} ... {/form}`). +Vous avez un contrôle total sur la forme du code HTML résultant. Si vous utilisez l'attribut `n:name` sur les éléments `<select>`, `<button>` ou `<textarea>`, leur contenu interne sera automatiquement complété. La balise `<form n:name>` crée en outre une variable locale `$form` avec l'objet du formulaire dessiné et la fermeture `</form>` rend tous les éléments cachés non rendus (il en va de même pour `{form} ... {/form}`). -Il ne faut cependant pas oublier de rendre les éventuels messages d'erreur. Aussi bien ceux qui ont été ajoutés aux éléments individuels par la méthode `addError()` (en utilisant `{inputError}`) que ceux ajoutés directement au formulaire (renvoyés par `$form->getOwnErrors()`) : +Nous ne devons cependant pas oublier de rendre les éventuels messages d'erreur. Aussi bien ceux qui ont été ajoutés aux éléments individuels avec la méthode `addError()` (à l'aide de `{inputError}`), que ceux ajoutés directement au formulaire (renvoyés par `$form->getOwnErrors()`) : ```latte <form n:name=signInForm class=form> @@ -79,7 +80,7 @@ Il ne faut cependant pas oublier de rendre les éventuels messages d'erreur. Aus </form> ``` -Les éléments de formulaire plus complexes, tels que RadioList ou CheckboxList, peuvent être rendus élément par élément : +Les éléments de formulaire plus complexes, tels que RadioList ou CheckboxList, peuvent être rendus ainsi par éléments individuels : ```latte {foreach $form[gender]->getItems() as $key => $label} @@ -88,16 +89,10 @@ Les éléments de formulaire plus complexes, tels que RadioList ou CheckboxList, ``` -Proposition de code `{formPrint}` .[#toc-formprint] ---------------------------------------------------- - -Vous pouvez générer un code latte similaire pour un formulaire en utilisant la balise `{formPrint}`. Si vous le placez dans un modèle, vous verrez le projet de code au lieu du rendu normal. Il suffit ensuite de le sélectionner et de le copier dans votre projet. - - `{label}` `{input}` ------------------- -Ne voulez-vous pas penser pour chaque élément à l'élément HTML à utiliser pour lui dans le modèle, que ce soit `<input>`, `<textarea>` etc. La solution est la balise universelle `{input}`: +Vous ne voulez pas réfléchir pour chaque élément à quel élément HTML utiliser dans le template, que ce soit `<input>`, `<textarea>`, etc. ? La solution est la balise universelle `{input}` : ```latte <form n:name=signInForm class=form> @@ -119,9 +114,9 @@ Ne voulez-vous pas penser pour chaque élément à l'élément HTML à utiliser </form> ``` -Si le formulaire utilise un traducteur, le texte contenu dans les balises `{label}` sera traduit. +Si le formulaire utilise un traducteur, le texte à l'intérieur des balises `{label}` sera traduit. -Là encore, les éléments de formulaire plus complexes, tels que RadioList ou CheckboxList, peuvent être rendus élément par élément : +Même dans ce cas, les éléments de formulaire plus complexes, tels que RadioList ou CheckboxList, peuvent être rendus par éléments individuels : ```latte {foreach $form[gender]->items as $key => $label} @@ -129,20 +124,19 @@ Là encore, les éléments de formulaire plus complexes, tels que RadioList ou C {/foreach} ``` -Pour rendre l'élément `<input>` dans l'élément Checkbox, utilisez `{input myCheckbox:}`. Les attributs HTML doivent être séparés par une virgule `{input myCheckbox:, class: required}`. +Pour rendre uniquement le `<input>` dans l'élément Checkbox, utilisez `{input myCheckbox:}`. Les attributs HTML dans ce cas doivent toujours être séparés par une virgule `{input myCheckbox:, class: required}`. `{inputError}` -------------- -Imprime un message d'erreur pour l'élément de formulaire, s'il en a un. Le message est généralement enveloppé dans un élément HTML pour le style. -Pour éviter de rendre un élément vide s'il n'y a pas de message, il est possible de le faire de manière élégante avec `n:ifcontent`: +Affiche le message d'erreur pour l'élément de formulaire, s'il en a un. Le message est généralement enveloppé dans un élément HTML pour le style. Empêcher le rendu d'un élément vide si le message n'existe pas peut être fait élégamment avec `n:ifcontent` : ```latte <span class=error n:ifcontent>{inputError $input}</span> ``` -Nous pouvons détecter la présence d'une erreur à l'aide de la méthode `hasErrors()` et définir la classe de l'élément parent en conséquence : +La présence d'une erreur peut être vérifiée avec la méthode `hasErrors()` et en fonction de cela, définir une classe sur l'élément parent : ```latte <div n:class="$form[username]->hasErrors() ? 'error'"> @@ -155,14 +149,13 @@ Nous pouvons détecter la présence d'une erreur à l'aide de la méthode `hasEr `{form}` -------- -Les étiquettes `{form signInForm}...{/form}` sont une alternative à `<form n:name="signInForm">...</form>`. +Les balises `{form signInForm}...{/form}` sont une alternative à `<form n:name="signInForm">...</form>`. -Rendu automatique .[#toc-automatic-rendering] ---------------------------------------------- +Rendu automatique +----------------- -Avec les balises `{input}` et `{label}`, nous pouvons facilement créer un modèle générique pour n'importe quel formulaire. Il itérera et rendra tous ses éléments séquentiellement, à l'exception des éléments cachés, qui sont rendus automatiquement lorsque le formulaire est terminé par la balise `</form>` . -Il attendra le nom du formulaire rendu dans la variable `$form`. +Grâce aux balises `{input}` et `{label}`, nous pouvons facilement créer un template générique pour n'importe quel formulaire. Il itérera et rendra progressivement tous ses éléments, à l'exception des éléments cachés, qui seront rendus automatiquement à la fermeture du formulaire par la balise `</form>`. Le nom du formulaire à rendre sera attendu dans la variable `$form`. ```latte <form n:name=$form class=form> @@ -179,16 +172,15 @@ Il attendra le nom du formulaire rendu dans la variable `$form`. </form> ``` -Les balises à paire auto-fermante utilisées `{label .../}` affichent les étiquettes provenant de la définition du formulaire dans le code PHP. +Les balises paires auto-fermantes `{label .../}` utilisées affichent les étiquettes provenant de la définition du formulaire dans le code PHP. -Vous pouvez enregistrer ce modèle générique dans le fichier `basic-form.latte` et pour rendre le formulaire, il suffit de l'inclure et de passer le nom du formulaire (ou son instance) au paramètre `$form`: +Enregistrez ce template générique par exemple dans le fichier `basic-form.latte` et pour rendre le formulaire, il suffit de l'inclure et de passer le nom (ou l'instance) du formulaire au paramètre `$form` : ```latte {include basic-form.latte, form: signInForm} ``` -Si vous souhaitez influencer l'apparence d'un formulaire particulier et dessiner un élément différemment, le moyen le plus simple est de préparer des blocs dans le modèle qui peuvent être écrasés ultérieurement. -Les blocs peuvent également avoir des [noms dynamiques |latte:template-inheritance#dynamic-block-names], vous pouvez donc y insérer le nom de l'élément à dessiner. Par exemple : +Si vous souhaitez intervenir dans l'apparence d'un formulaire particulier lors de son rendu et, par exemple, rendre un élément différemment, le moyen le plus simple est de préparer des blocs dans le template qui pourront être ensuite surchargés. Les blocs peuvent également avoir des [noms dynamiques |latte:template-inheritance#Noms de blocs dynamiques], on peut donc y insérer le nom de l'élément rendu. Par exemple : ```latte ... @@ -197,7 +189,7 @@ Les blocs peuvent également avoir des [noms dynamiques |latte:template-inherita ... ``` -Pour l'élément `username`, cela crée le bloc `input-username`, qui peut facilement être remplacé par la balise [{embed} |latte:template-inheritance#unit-inheritance]: +Pour l'élément, par exemple `username`, un bloc `input-username` sera créé, qui peut être facilement surchargé en utilisant la balise [{embed} |latte:template-inheritance#Héritage unitaire] : ```latte {embed basic-form.latte, form: signInForm} @@ -209,7 +201,7 @@ Pour l'élément `username`, cela crée le bloc `input-username`, qui peut facil {/embed} ``` -Par ailleurs, tout le contenu du modèle `basic-form.latte` peut être [défini |latte:template-inheritance#definitions] comme un bloc, y compris le paramètre `$form`: +Alternativement, tout le contenu du template `basic-form.latte` peut être [défini |latte:template-inheritance#Définitions] comme un bloc, y compris le paramètre `$form` : ```latte {define basic-form, $form} @@ -219,7 +211,7 @@ Par ailleurs, tout le contenu du modèle `basic-form.latte` peut être [défini {/define} ``` -Cela rendra son utilisation légèrement plus facile : +Grâce à cela, son appel sera légèrement plus simple : ```latte {embed basic-form, signInForm} @@ -227,31 +219,31 @@ Cela rendra son utilisation légèrement plus facile : {/embed} ``` -Vous ne devez importer le bloc qu'à un seul endroit, au début du modèle de mise en page : +Il suffit d'importer le bloc à un seul endroit, au début du template de layout : ```latte {import basic-form.latte} ``` -Cas particuliers .[#toc-special-cases] --------------------------------------- +Cas spéciaux +------------ -Si vous avez seulement besoin de rendre le contenu interne d'un formulaire sans `<form>` & `</form>` HTML, par exemple dans une requête AJAX, vous pouvez ouvrir et fermer le formulaire à l'aide de `{formContext} … {/formContext}`. Son fonctionnement est similaire à celui de `{form}` d'un point de vue logique, puisqu'il vous permet d'utiliser d'autres balises pour dessiner des éléments de formulaire, mais en même temps, il ne dessine rien. +Si vous avez besoin de rendre uniquement la partie interne du formulaire sans les balises HTML `<form>`, par exemple lors de l'envoi de snippets, masquez-les à l'aide de l'attribut `n:tag-if` : ```latte -{formContext signForm} +<form n:name=signInForm n:tag-if=false> <div> <label n:name=username>Username: <input n:name=username></label> {inputError username} </div> -{/formContext} +</form> ``` -La balise `formContainer` aide à rendre les entrées à l'intérieur d'un conteneur de formulaire. +La balise `{formContainer}` aide au rendu des éléments à l'intérieur d'un conteneur de formulaire. ```latte -<p>Which news you wish to receive:</p> +<p>Quelles nouvelles souhaitez-vous recevoir :</p> {formContainer emailNews} <ul> @@ -262,27 +254,27 @@ La balise `formContainer` aide à rendre les entrées à l'intérieur d'un conte ``` -Rendu sans Latte .[#toc-rendering-without-latte] -================================================ +Rendu sans Latte +================ -La façon la plus simple de rendre un formulaire est d'appeler : +La manière la plus simple de rendre un formulaire est d'appeler : ```php $form->render(); ``` -L'apparence du formulaire rendu peut être modifiée en configurant le [Renderer |#Renderer] et les [contrôles individuels |#HTML Attributes]. +L'apparence du formulaire ainsi rendu peut être influencée par la configuration du [#Renderer] et des [éléments individuels |#Attributs HTML]. -Rendu manuel .[#toc-manual-rendering] -------------------------------------- +Rendu manuel +------------ -Chaque élément de formulaire possède des méthodes qui génèrent le code HTML pour le champ de formulaire et l'étiquette. Elles peuvent le renvoyer sous la forme d'une chaîne ou d'un objet [Nette\Utils\Html |utils:html-elements]: +Chaque élément de formulaire dispose de méthodes qui génèrent le code HTML du champ de formulaire et de l'étiquette. Elles peuvent le retourner soit sous forme de chaîne de caractères, soit sous forme d'objet [Nette\Utils\Html|utils:html-elements] : -- `getControl(): Html|string` renvoie le code HTML de l'élément -- `getLabel($caption = null): Html|string|null` renvoie le code HTML de l'étiquette, le cas échéant. +- `getControl(): Html|string` retourne le code HTML de l'élément +- `getLabel($caption = null): Html|string|null` retourne le code HTML de l'étiquette, si elle existe -Cela permet de rendre le formulaire élément par élément : +Le formulaire peut ainsi être rendu élément par élément : ```php <?php $form->render('begin') ?> @@ -305,47 +297,46 @@ Cela permet de rendre le formulaire élément par élément : <?php $form->render('end') ?> ``` -Alors que pour certains éléments, `getControl()` renvoie un seul élément HTML (par ex. `<input>`, `<select>` etc.), pour d'autres, il renvoie un morceau entier de code HTML (CheckboxList, RadioList). -Dans ce cas, vous pouvez utiliser des méthodes qui génèrent des entrées et des étiquettes individuelles, pour chaque élément séparément : +Alors que pour certains éléments, `getControl()` retourne un seul élément HTML (par ex. `<input>`, `<select>`, etc.), pour d'autres, il retourne un morceau entier de code HTML (CheckboxList, RadioList). Dans ce cas, vous pouvez utiliser des méthodes qui génèrent des inputs et des étiquettes individuels, pour chaque élément séparément : -- `getControlPart($key = null): ?Html` renvoie le code HTML d'un seul élément -- `getLabelPart($key = null): ?Html` renvoie le code HTML de l'étiquette d'un seul élément. +- `getControlPart($key = null): ?Html` retourne le code HTML d'un élément individuel +- `getLabelPart($key = null): ?Html` retourne le code HTML de l'étiquette d'un élément individuel .[note] -Ces méthodes sont préfixées par `get` pour des raisons historiques, mais `generate` serait plus approprié, car il crée et renvoie un nouvel élément `Html` à chaque appel. +Ces méthodes ont pour des raisons historiques le préfixe `get`, mais `generate` serait meilleur, car à chaque appel, elles créent et retournent un nouvel élément `Html`. -Renderer .[#toc-renderer] -========================= +Renderer +======== -C'est un objet qui assure le rendu du formulaire. Il peut être défini par la méthode `$form->setRenderer`. Le contrôle lui est transmis lorsque la méthode `$form->render()` est appelée. +C'est un objet assurant le rendu du formulaire. Il peut être défini avec la méthode `$form->setRenderer`. Le contrôle lui est transmis lors de l'appel de la méthode `$form->render()`. -Si nous ne définissons pas de moteur de rendu personnalisé, le moteur de rendu par défaut [api:Nette\Forms\Rendering\DefaultFormRenderer] sera utilisé. Il rendra les éléments du formulaire sous la forme d'un tableau HTML. Le résultat ressemble à ceci : +Si nous ne définissons pas notre propre renderer, le renderer par défaut [api:Nette\Forms\Rendering\DefaultFormRenderer] sera utilisé. Celui-ci rend les éléments du formulaire sous forme de tableau HTML. La sortie ressemble à ceci : ```latte <table> <tr class="required"> - <th><label class="required" for="frm-name">Name:</label></th> + <th><label class="required" for="frm-name">Nom :</label></th> <td><input type="text" class="text" name="name" id="frm-name" required value=""></td> </tr> <tr class="required"> - <th><label class="required" for="frm-age">Age:</label></th> + <th><label class="required" for="frm-age">Âge :</label></th> <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> </tr> <tr> - <th><label>Gender:</label></th> + <th><label>Sexe :</label></th> ... ``` -C'est à vous de décider si vous voulez utiliser un tableau ou non, et de nombreux concepteurs de sites Web préfèrent des balises différentes, par exemple une liste. Nous pouvons configurer `DefaultFormRenderer` pour qu'il ne soit pas rendu dans un tableau du tout. Il suffit de définir les [$wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers] appropriés. Le premier indice représente toujours une zone et le second un élément. Toutes les zones respectives sont indiquées dans l'image : +L'utilisation ou non d'un tableau pour la structure du formulaire est discutable et de nombreux webdesigners préfèrent un autre balisage. Par exemple, une liste de définitions. Nous allons donc reconfigurer `DefaultFormRenderer` pour qu'il rende le formulaire sous forme de liste. La configuration se fait en modifiant le tableau [$wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers]. Le premier index représente toujours la zone et le second son attribut. Les différentes zones sont illustrées par l'image : -[* form-areas-en.webp *] +[* defaultformrenderer.webp *] -Par défaut, un groupe de `controls` est entouré de `<table>`et chaque `pair` est une ligne de tableau `<tr>` contenant une paire de `label` et `control` (cellules `<th>` et `<td>`). Changeons tous ces éléments d'habillage. Nous allons envelopper `controls` dans `<dl>`laisser `pair` seul, mettre `label` dans `<dt>` et transformer `control` en `<dd>`: +Par défaut, le groupe d'éléments `controls` est enveloppé dans un tableau `<table>`, chaque `pair` représente une ligne de tableau `<tr>` et la paire `label` et `control` sont les cellules `<th>` et `<td>`. Nous allons maintenant changer les éléments enveloppants. Nous insérons la zone `controls` dans un conteneur `<dl>`, laissons la zone `pair` sans conteneur, insérons `label` dans `<dt>` et enfin enveloppons `control` avec les balises `<dd>` : ```php $renderer = $form->getRenderer(); @@ -357,51 +348,51 @@ $renderer->wrappers['control']['container'] = 'dd'; $form->render(); ``` -Ce qui donne l'extrait suivant : +Le résultat est ce code HTML : ```latte <dl> - <dt><label class="required" for="frm-name">Name:</label></dt> + <dt><label class="required" for="frm-name">Nom :</label></dt> <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> - <dt><label class="required" for="frm-age">Age:</label></dt> + <dt><label class="required" for="frm-age">Âge :</label></dt> <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> - <dt><label>Gender:</label></dt> + <dt><label>Sexe :</label></dt> ... </dl> ``` -Les wrappers peuvent affecter de nombreux attributs. Par exemple : +Dans le tableau wrappers, on peut influencer de nombreux autres attributs : -- ajouter des classes CSS spéciales à chaque entrée du formulaire -- distinguer les lignes paires et impaires -- faire en sorte que les éléments obligatoires et facultatifs soient dessinés différemment -- déterminer si les messages d'erreur doivent être affichés au-dessus du formulaire ou près de chaque élément. +- ajouter des classes CSS aux types individuels d'éléments de formulaire +- distinguer par une classe CSS les lignes paires et impaires +- distinguer visuellement les éléments obligatoires et facultatifs +- déterminer si les messages d'erreur s'affichent directement à côté des éléments ou au-dessus du formulaire -Options .[#toc-options] ------------------------ +Options +------- -Le comportement du Renderer peut également être contrôlé en définissant des *options* sur des éléments de formulaire individuels. Ainsi, vous pouvez définir l'infobulle qui s'affiche à côté du champ de saisie : +Le comportement du Renderer peut également être contrôlé en définissant des *options* sur les éléments de formulaire individuels. On peut ainsi définir une description qui sera affichée à côté du champ de saisie : ```php -$form->addText('phone', 'Number:') - ->setOption('description', 'This number will remain hidden'); +$form->addText('phone', 'Numéro :') + ->setOption('description', 'Ce numéro restera caché'); ``` -Si nous voulons y placer du contenu HTML, nous utilisons la classe [Html |utils:html-elements]. +Si nous voulons y placer du contenu HTML, nous utilisons la classe [Html |utils:html-elements] ```php use Nette\Utils\Html; -$form->addText('phone', 'Phone:') +$form->addText('phone', 'Numéro :') ->setOption('description', Html::el('p') - ->setHtml('<a href="...">Terms of service.</a>') + ->setHtml('<a href="...">Conditions de conservation de votre numéro</a>') ); ``` @@ -409,141 +400,140 @@ $form->addText('phone', 'Phone:') L'élément Html peut également être utilisé à la place de l'étiquette : `$form->addCheckbox('conditions', $label)`. -Regroupement des entrées .[#toc-grouping-inputs] ------------------------------------------------- +Regroupement d'éléments +----------------------- -Le Renderer permet de regrouper des éléments en groupes visuels (fieldsets) : +Le Renderer permet de regrouper les éléments en groupes visuels (fieldsets) : ```php -$form->addGroup('Personal data'); +$form->addGroup('Données personnelles'); ``` -La création d'un nouveau groupe l'active - tous les éléments ajoutés par la suite sont ajoutés à ce groupe. Vous pouvez construire un formulaire comme ceci : +Après la création d'un nouveau groupe, celui-ci devient actif et chaque nouvel élément ajouté est également ajouté à ce groupe. Le formulaire peut donc être construit de cette manière : ```php $form = new Form; -$form->addGroup('Personal data'); -$form->addText('name', 'Your name:'); -$form->addInteger('age', 'Your age:'); -$form->addEmail('email', 'Email:'); +$form->addGroup('Données personnelles'); +$form->addText('name', 'Votre nom :'); +$form->addInteger('age', 'Votre âge :'); +$form->addEmail('email', 'Email :'); -$form->addGroup('Shipping address'); -$form->addCheckbox('send', 'Ship to address'); -$form->addText('street', 'Street:'); -$form->addText('city', 'City:'); -$form->addSelect('country', 'Country:', $countries); +$form->addGroup('Adresse de livraison'); +$form->addCheckbox('send', 'Expédier à l\'adresse'); +$form->addText('street', 'Rue :'); +$form->addText('city', 'Ville :'); +$form->addSelect('country', 'Pays :', $countries); ``` +Le Renderer rend d'abord les groupes, puis les éléments qui n'appartiennent à aucun groupe. + -Support Bootstrap .[#toc-bootstrap-support] -------------------------------------------- +Support pour Bootstrap +---------------------- -Vous trouverez des [exemples |https://github.com/nette/forms/tree/master/examples] de configuration du moteur de rendu pour [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], [Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] et [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php]. +[Dans les exemples |https://github.com/nette/forms/tree/master/examples], vous trouverez des exemples sur la façon de configurer le Renderer pour [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], [Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] et [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php] -Attributs HTML .[#toc-html-attributes] -====================================== +Attributs HTML +============== -Vous pouvez définir n'importe quel attribut HTML pour les contrôles de formulaires en utilisant `setHtmlAttribute(string $name, $value = true)`: +Pour définir des attributs HTML arbitraires pour les éléments de formulaire, utilisez la méthode `setHtmlAttribute(string $name, $value = true)` : ```php -$form->addInteger('number', 'Number:') +$form->addInteger('number', 'Numéro :') ->setHtmlAttribute('class', 'big-number'); -$form->addSelect('rank', 'Order by:', ['price', 'name']) - ->setHtmlAttribute('onchange', 'submit()'); // appelle la fonction JS submit() en cas de modification. +$form->addSelect('rank', 'Trier par :', ['prix', 'nom']) + ->setHtmlAttribute('onchange', 'submit()'); // envoyer lors du changement -// application sur <form> +// Pour définir les attributs de <form> lui-même $form->setHtmlAttribute('id', 'myForm'); ``` -Définition du type d'entrée : +Spécification du type d'élément : ```php -$form->addText('tel', 'Your telephone:') +$form->addText('tel', 'Votre téléphone :') ->setHtmlType('tel') - ->setHtmlAttribute('placeholder', 'Please, fill in your telephone'); + ->setHtmlAttribute('placeholder', 'écrivez le téléphone'); ``` -Nous pouvons définir l'attribut HTML à des éléments individuels dans des listes de radio ou de cases à cocher avec des valeurs différentes pour chacun d'eux. -Notez les deux points après `style:` pour vous assurer que la valeur est sélectionnée par la clé : +.[warning] +La définition du type et d'autres attributs sert uniquement à des fins visuelles. La vérification de l'exactitude des entrées doit avoir lieu côté serveur, ce que vous assurez en choisissant un [élément de formulaire|controls] approprié et en indiquant des [règles de validation|validation]. + +Nous pouvons définir des attributs HTML avec des valeurs différentes pour chacun des éléments individuels dans les listes radio ou checkbox. Notez les deux-points après `style:`, qui assurent le choix de la valeur selon la clé : ```php -$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue']; +$colors = ['r' => 'rouge', 'g' => 'vert', 'b' => 'bleu']; $styles = ['r' => 'background:red', 'g' => 'background:green']; -$form->addCheckboxList('colors', 'Colors:', $colors) +$form->addCheckboxList('colors', 'Couleurs :', $colors) ->setHtmlAttribute('style:', $styles); ``` -Renders : +Affiche : ```latte -<label><input type="checkbox" name="colors[]" style="background:red" value="r">red</label> -<label><input type="checkbox" name="colors[]" style="background:green" value="g">green</label> -<label><input type="checkbox" name="colors[]" value="b">blue</label> +<label><input type="checkbox" name="colors[]" style="background:red" value="r">rouge</label> +<label><input type="checkbox" name="colors[]" style="background:green" value="g">vert</label> +<label><input type="checkbox" name="colors[]" value="b">bleu</label> ``` -Pour un attribut HTML logique (qui n'a pas de valeur, comme `readonly`), vous pouvez utiliser un point d'interrogation : +Pour définir des attributs logiques, comme `readonly`, nous pouvons utiliser la notation avec un point d'interrogation : ```php -$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue']; -$form->addCheckboxList('colors', 'Colors:', $colors) - ->setHtmlAttribute('readonly?', 'r'); // utiliser un tableau pour les clés multiples, par exemple ['r', 'g']. +$form->addCheckboxList('colors', 'Couleurs :', $colors) + ->setHtmlAttribute('readonly?', 'r'); // pour plusieurs clés, utilisez un tableau, par ex. ['r', 'g'] ``` -Rendu : +Affiche : ```latte -<label><input type="checkbox" name="colors[]" readonly value="r">red</label> -<label><input type="checkbox" name="colors[]" value="g">green</label> -<label><input type="checkbox" name="colors[]" value="b">blue</label> +<label><input type="checkbox" name="colors[]" readonly value="r">rouge</label> +<label><input type="checkbox" name="colors[]" value="g">vert</label> +<label><input type="checkbox" name="colors[]" value="b">bleu</label> ``` -Pour les boîtes de sélection, la méthode `setHtmlAttribute()` définit les attributs de l'élément `<select>` de l'élément. Si nous voulons définir les attributs pour chaque -`<option>`nous utiliserons la méthode `setOptionAttribute()`. De même, les deux points et le point d'interrogation utilisés ci-dessus fonctionnent : +Dans le cas des selectbox, la méthode `setHtmlAttribute()` définit les attributs de l'élément `<select>`. Si nous voulons définir les attributs des `<option>` individuels, nous utilisons la méthode `setOptionAttribute()`. Les notations avec deux-points et point d'interrogation mentionnées ci-dessus fonctionnent également : ```php -$form->addSelect('colors', 'Colors:', $colors) +$form->addSelect('colors', 'Couleurs :', $colors) ->setOptionAttribute('style:', $styles); ``` -Rendu : +Affiche : ```latte <select name="colors"> - <option value="r" style="background:red">red</option> - <option value="g" style="background:green">green</option> - <option value="b">blue</option> + <option value="r" style="background:red">rouge</option> + <option value="g" style="background:green">vert</option> + <option value="b">bleu</option> </select> ``` -Prototypes .[#toc-prototypes] ------------------------------ +Prototypes +---------- -Une autre façon de définir les attributs HTML consiste à modifier le modèle à partir duquel l'élément HTML est généré. Le modèle est un objet `Html` et est renvoyé par la méthode `getControlPrototype()`: +Une manière alternative de définir les attributs HTML consiste à modifier le modèle à partir duquel l'élément HTML est généré. Le modèle est un objet `Html` et est retourné par la méthode `getControlPrototype()` : ```php -$input = $form->addInteger('number'); +$input = $form->addInteger('number', 'Numéro :'); $html = $input->getControlPrototype(); // <input> $html->class('big-number'); // <input class="big-number"> ``` -Le modèle d'étiquette renvoyé par `getLabelPrototype()` peut également être modifié de cette manière : +De cette manière, on peut également modifier le modèle de l'étiquette, retourné par `getLabelPrototype()` : ```php $html = $input->getLabelPrototype(); // <label> -$html->class('distinctif'); // <label class="distinctif"> +$html->class('distinctive'); // <label class="distinctive"> ``` -Pour les éléments Checkbox, CheckboxList et RadioList, vous pouvez influencer le modèle d'élément qui enveloppe l'élément. Il est renvoyé par `getContainerPrototype()`. Par défaut, c'est un élément "vide", donc rien n'est rendu, mais en lui donnant un nom, il sera rendu : +Pour les éléments Checkbox, CheckboxList et RadioList, vous pouvez influencer le modèle de l'élément qui enveloppe l'élément entier. Il est retourné par `getContainerPrototype()`. Par défaut, il s'agit d'un élément "vide", donc rien n'est rendu, mais en lui définissant un nom, il sera rendu : ```php $input = $form->addCheckbox('send'); -echo $input->getControl(); -// <label><input type="checkbox" name="send"></label> - $html = $input->getContainerPrototype(); $html->setName('div'); // <div> $html->class('check'); // <div class="check"> @@ -551,50 +541,49 @@ echo $input->getControl(); // <div class="check"><label><input type="checkbox" name="send"></label></div> ``` -Dans le cas des CheckboxList et RadioList, il est également possible d'influencer le modèle de séparateur d'éléments renvoyé par la méthode `getSeparatorPrototype()`. Par défaut, il s'agit d'un élément `<br>`. Si vous le changez en un élément de paire, il enveloppera les éléments individuels au lieu de les séparer. -Il est également possible d'influencer le modèle d'élément HTML des étiquettes d'éléments, qui renvoie `getItemLabelPrototype()`. +Dans le cas de CheckboxList et RadioList, on peut également influencer le modèle du séparateur des éléments individuels, retourné par la méthode `getSeparatorPrototype()`. Par défaut, c'est l'élément `<br>`. Si vous le changez en un élément pair, il enveloppera les éléments individuels au lieu de les séparer. Et de plus, on peut influencer le modèle de l'élément HTML de l'étiquette pour les éléments individuels, retourné par `getItemLabelPrototype()`. -Traduire .[#toc-translating] -============================ +Traduction +========== -Si vous programmez une application multilingue, vous aurez probablement besoin de rendre le formulaire dans différentes langues. Le Framework Nette définit une interface de traduction à cet effet [api:Nette\Localization\Translator]. Il n'y a pas d'implémentation par défaut dans Nette, vous pouvez choisir en fonction de vos besoins parmi plusieurs solutions prêtes à l'emploi que vous pouvez trouver sur [Componette |https://componette.org/search/localization]. Leur documentation vous indique comment configurer le traducteur. +Si vous programmez une application multilingue, vous aurez probablement besoin de rendre le formulaire dans différentes versions linguistiques. Nette Framework définit à cet effet une interface pour la traduction [api:Nette\Localization\Translator]. Il n'y a pas d'implémentation par défaut dans Nette, vous pouvez choisir parmi plusieurs solutions prêtes à l'emploi selon vos besoins, que vous trouverez sur [Componette |https://componette.org/search/localization]. Dans leur documentation, vous apprendrez comment configurer le traducteur. -Le formulaire supporte la sortie de texte par le traducteur. Nous le passons en utilisant la méthode `setTranslator()`: +Les formulaires prennent en charge l'affichage de textes via un traducteur. Nous le leur passons à l'aide de la méthode `setTranslator()` : ```php $form->setTranslator($translator); ``` -À partir de maintenant, non seulement toutes les étiquettes, mais aussi tous les messages d'erreur ou les entrées des boîtes de sélection seront traduits dans une autre langue. +À partir de ce moment, non seulement toutes les étiquettes, mais aussi tous les messages d'erreur ou les éléments des select box seront traduits dans une autre langue. -Il est possible de définir un traducteur différent pour chaque élément de formulaire ou de désactiver complètement la traduction à l'aide de `null`: +Pour les éléments de formulaire individuels, il est possible de définir un traducteur différent ou de désactiver complètement la traduction avec la valeur `null` : ```php -$form->addSelect('carModel', 'Model:', $cars) +$form->addSelect('carModel', 'Modèle :', $cars) ->setTranslator(null); ``` -Pour les [règles de validation |validation], des paramètres spécifiques sont également transmis au traducteur, par exemple pour la règle : +Pour les [règles de validation|validation], des paramètres spécifiques sont également transmis au traducteur, par exemple pour la règle : ```php -$form->addPassword('password', 'Password:') - ->addRule($form::MinLength, 'Password has to be at least %d characters long', 8) +$form->addPassword('password', 'Mot de passe :') + ->addRule($form::MinLength, 'Le mot de passe doit comporter au moins %d caractères', 8); ``` -le traducteur est appelé avec les paramètres suivants : +le traducteur est appelé avec ces paramètres : ```php -$translator->translate('Password has to be at least %d characters long', 8); +$translator->translate('Le mot de passe doit comporter au moins %d caractères', 8); ``` -et peut ainsi choisir la forme plurielle correcte pour le mot `characters` par comptage. +et peut donc choisir la forme correcte du pluriel pour le mot `caractères` en fonction du nombre. -Événement onRender .[#toc-event-onrender] -========================================= +Événement onRender +================== -Juste avant que le formulaire ne soit rendu, nous pouvons faire appel à notre code. Celui-ci peut, par exemple, ajouter des classes HTML aux éléments du formulaire pour un affichage correct. Nous ajoutons le code au tableau `onRender`: +Juste avant que le formulaire ne soit rendu, nous pouvons faire exécuter notre code. Celui-ci peut par exemple ajouter des classes HTML aux éléments de formulaire pour un affichage correct. Nous ajoutons le code au tableau `onRender` : ```php $form->onRender[] = function ($form) { diff --git a/forms/fr/standalone.texy b/forms/fr/standalone.texy index ea3843cd3e..1e70ddaf39 100644 --- a/forms/fr/standalone.texy +++ b/forms/fr/standalone.texy @@ -1,43 +1,43 @@ -Formulaires utilisés de manière autonome -**************************************** +Formulaires utilisés indépendamment +*********************************** .[perex] -Les formulaires Nette facilitent considérablement la création et le traitement des formulaires Web. Vous pouvez les utiliser dans vos applications de manière complètement autonome, sans le reste du framework, ce que nous allons démontrer dans ce chapitre. +Nette Forms facilite grandement la création et le traitement des formulaires web. Vous pouvez les utiliser dans vos applications de manière totalement indépendante du reste du framework, ce que nous allons montrer dans ce chapitre. -Cependant, si vous utilisez Nette Application et des présentateurs, il existe un guide pour vous : [forms in presenters |in-presenter]. +Cependant, si vous utilisez Nette Application et les presenters, le guide pour l'[utilisation dans les presenters |in-presenter] est fait pour vous. -Premier formulaire .[#toc-first-form] -===================================== +Premier formulaire +================== -Nous allons essayer d'écrire un formulaire d'inscription simple. Son code ressemblera à ceci ("code complet":https://gist.github.com/dg/370a7e3094d9ba9a9e913b8e2a2dc851) : +Essayons d'écrire un formulaire d'inscription simple. Son code sera le suivant ("code complet":https://gist.github.com/dg/57878c1a413ae8ef0c1d83f02c43ef3f) : ```php use Nette\Forms\Form; $form = new Form; -$form->addText('name', 'Name:'); -$form->addPassword('password', 'Password:'); -$form->addSubmit('send', 'Sign up'); +$form->addText('name', 'Nom:'); +$form->addPassword('password', 'Mot de passe:'); +$form->addSubmit('send', 'S\'inscrire'); ``` -Et rendons-le : +Nous pouvons le rendre très facilement : ```php $form->render(); ``` -et le résultat devrait ressembler à ceci : +et dans le navigateur, il s'affichera comme ceci : [* form-en.webp *] -Le formulaire est un objet de la classe `Nette\Forms\Form` (la classe `Nette\Application\UI\Form` est utilisée dans les présentateurs). Nous y avons ajouté les contrôles nom, mot de passe et bouton d'envoi. +Le formulaire est un objet de la classe `Nette\Forms\Form` (la classe `Nette\Application\UI\Form` est utilisée dans les presenters). Nous y avons ajouté ce qu'on appelle les éléments nom, mot de passe et un bouton d'envoi. -Maintenant nous allons relancer le formulaire. En demandant à `$form->isSuccess()`, nous saurons si le formulaire a été soumis et s'il a été rempli de manière valide. Si c'est le cas, nous allons vider les données. Après la définition du formulaire, nous ajouterons : +Maintenant, animons le formulaire. En interrogeant `$form->isSuccess()`, nous vérifions si le formulaire a été soumis et s'il a été rempli de manière valide. Si oui, nous affichons les données. Ajoutons donc après la définition du formulaire : ```php if ($form->isSuccess()) { - echo 'Le formulaire a été rempli et soumis correctement'; + echo 'Le formulaire a été correctement rempli et soumis'; $data = $form->getValues(); // $data->name contient le nom // $data->password contient le mot de passe @@ -45,72 +45,71 @@ if ($form->isSuccess()) { } ``` -La méthode `getValues()` retourne les données envoyées sous la forme d'un objet [ArrayHash |utils:arrays#ArrayHash]. Nous verrons [plus tard |#Mapping to Classes] comment modifier cela. La variable `$data` contient les clés `name` et `password` avec les données saisies par l'utilisateur. +La méthode `getValues()` retourne les données soumises sous forme d'objet [ArrayHash |utils:arrays#ArrayHash]. Nous verrons [plus tard |#Mappage sur des classes] comment changer cela. L'objet `$data` contient les clés `name` et `password` avec les informations saisies par l'utilisateur. -Habituellement, nous envoyons les données directement pour un traitement ultérieur, qui peut être, par exemple, l'insertion dans la base de données. Toutefois, une erreur peut se produire pendant le traitement, par exemple, le nom d'utilisateur est déjà pris. Dans ce cas, nous renvoyons l'erreur au formulaire en utilisant `addError()` et le laissons se redessiner, avec un message d'erreur : +Habituellement, nous envoyons directement les données pour un traitement ultérieur, qui peut être par exemple une insertion dans la base de données. Cependant, une erreur peut survenir pendant le traitement, par exemple, le nom d'utilisateur est déjà pris. Dans ce cas, nous transmettons l'erreur au formulaire à l'aide de `addError()` et le laissons se rendre à nouveau, avec le message d'erreur. ```php -$form->addError('Sorry, username is already in use.'); +$form->addError('Désolé, ce nom d\'utilisateur est déjà utilisé.'); ``` -Après avoir traité le formulaire, nous allons rediriger vers la page suivante. Cela permet d'éviter que le formulaire soit involontairement soumis à nouveau en cliquant sur le bouton *refresh*, *back*, ou en déplaçant l'historique du navigateur. +Après le traitement du formulaire, nous redirigeons vers la page suivante. Cela évite la soumission répétée involontaire du formulaire par le bouton *actualiser*, *retour* ou par le déplacement dans l'historique du navigateur. -Par défaut, le formulaire est envoyé à la même page en utilisant la méthode POST. Les deux méthodes peuvent être modifiées : +Le formulaire est envoyé par défaut par la méthode POST et vers la même page. Les deux peuvent être modifiés : ```php $form->setAction('/submit.php'); $form->setMethod('GET'); ``` -Et c'est tout :-) Nous avons un formulaire fonctionnel et parfaitement [sécurisé |#Vulnerability Protection]. +Et c'est à peu près tout :-) Nous avons un formulaire fonctionnel et parfaitement [sécurisé |#Protection contre les vulnérabilités]. -Essayez d'ajouter d'autres [contrôles de formulaire |controls]. +Essayez d'ajouter d'autres [éléments de formulaire |controls]. -Accès aux contrôles .[#toc-access-to-controls] -============================================== +Accès aux éléments +================== -Le formulaire et ses contrôles individuels sont appelés des composants. Ils créent une arborescence de composants, dont la racine est le formulaire. Vous pouvez accéder aux contrôles individuels comme suit : +Nous appelons le formulaire et ses éléments individuels des composants. Ils forment un arbre de composants, dont la racine est précisément le formulaire. Nous pouvons accéder aux éléments individuels du formulaire de cette manière : ```php $input = $form->getComponent('name'); -// syntaxe alternative: $input = $form['name']; +// syntaxe alternative : $input = $form['name']; $button = $form->getComponent('send'); -// syntaxe alternative: $button = $form['send']; +// syntaxe alternative : $button = $form['send']; ``` -Les contrôles sont supprimés à l'aide de unset : +Les éléments sont supprimés à l'aide de unset : ```php unset($form['name']); ``` -Règles de validation .[#toc-validation-rules] -============================================= +Règles de validation +==================== -Le mot *valide* a été utilisé ici, mais le formulaire n'a pas encore de règles de validation. Corrigeons cela. +Nous avons mentionné le mot *valide,* mais le formulaire n'a pour l'instant aucune règle de validation. Corrigeons cela. -Le nom sera obligatoire, nous le marquerons donc avec la méthode `setRequired()`, dont l'argument est le texte du message d'erreur qui sera affiché si l'utilisateur ne le remplit pas. Si aucun argument n'est donné, le message d'erreur par défaut est utilisé. +Le nom sera obligatoire, nous le marquerons donc avec la méthode `setRequired()`, dont l'argument est le texte du message d'erreur qui s'affichera si l'utilisateur ne remplit pas le nom. Si nous n'indiquons pas d'argument, le message d'erreur par défaut sera utilisé. ```php -$form->addText('name', 'Name:') - ->setRequired('Please enter a name.'); +$form->addText('name', 'Nom:') + ->setRequired('Veuillez saisir un nom'); ``` -Essayez de soumettre le formulaire sans que le nom soit rempli et vous verrez qu'un message d'erreur s'affiche et que le navigateur ou le serveur le rejettera jusqu'à ce que vous le remplissiez. +Essayez de soumettre le formulaire sans remplir le nom et vous verrez que le message d'erreur s'affichera et que le navigateur ou le serveur le refusera jusqu'à ce que vous remplissiez le champ. -En même temps, vous ne pourrez pas tromper le système en ne tapant que des espaces dans la saisie, par exemple. Pas question. Nette supprime automatiquement les espaces à gauche et à droite. Essayez-le. C'est quelque chose que vous devriez toujours faire avec chaque entrée d'une seule ligne, mais on l'oublie souvent. Nette le fait automatiquement. (Vous pouvez essayer de tromper les formulaires et envoyer une chaîne de caractères multiligne comme nom. Même dans ce cas, Nette ne sera pas dupe et les sauts de ligne se transformeront en espaces). +En même temps, vous ne tromperez pas le système en tapant par exemple uniquement des espaces dans le champ. Non. Nette supprime automatiquement les espaces de début et de fin. Essayez-le. C'est quelque chose que vous devriez toujours faire avec chaque input sur une seule ligne, mais on l'oublie souvent. Nette le fait automatiquement. (Vous pouvez essayer de tromper le formulaire et envoyer une chaîne de caractères multiligne comme nom. Même ici, Nette ne se laisse pas berner et transforme les retours à la ligne en espaces.) -Le formulaire est toujours validé du côté du serveur, mais la validation JavaScript est également générée, ce qui est rapide et l'utilisateur connaît l'erreur immédiatement, sans avoir à envoyer le formulaire au serveur. Ceci est géré par le script `netteForms.js`. -Ajoutez-le à la page : +Le formulaire est toujours validé côté serveur, mais une validation JavaScript est également générée, qui s'exécute instantanément et l'utilisateur est informé de l'erreur immédiatement, sans avoir besoin d'envoyer le formulaire au serveur. C'est le script `netteForms.js` qui s'en charge. Insérez-le dans la page : ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Si vous regardez dans le code source de la page avec formulaire, vous pouvez remarquer que Nette insère les champs obligatoires dans des éléments avec une classe CSS `required`. Essayez d'ajouter le style suivant au modèle, et l'étiquette "Nom" sera rouge. De manière élégante, nous marquons les champs obligatoires pour les utilisateurs : +Si vous regardez le code source de la page avec le formulaire, vous remarquerez peut-être que Nette insère les éléments obligatoires dans des éléments avec la classe CSS `required`. Essayez d'ajouter la feuille de style suivante au template et le libellé "Nom" sera rouge. Nous marquons ainsi élégamment les éléments obligatoires pour les utilisateurs : ```latte <style> @@ -118,96 +117,96 @@ Si vous regardez dans le code source de la page avec formulaire, vous pouvez rem </style> ``` -Des règles de validation supplémentaires seront ajoutées par la méthode `addRule()`. Le premier paramètre est la règle, le second est à nouveau le texte du message d'erreur, et l'argument facultatif de la règle de validation peut suivre. Qu'est-ce que cela signifie ? +Nous ajoutons d'autres règles de validation avec la méthode `addRule()`. Le premier paramètre est la règle, le deuxième est à nouveau le texte du message d'erreur et il peut encore suivre un argument de la règle de validation. Qu'est-ce que cela signifie ? -Le formulaire recevra une autre entrée facultative *âge* avec la condition qu'il s'agisse d'un nombre (`addInteger()`) et qu'il soit dans certaines limites (`$form::Range`). Et ici, nous utiliserons le troisième argument de `addRule()`, la plage elle-même : +Nous étendons le formulaire avec un nouveau champ facultatif "âge", qui doit être un entier (`addInteger()`) et en plus dans une plage autorisée (`$form::Range`). Et c'est ici que nous utilisons le troisième paramètre de la méthode `addRule()`, par lequel nous passons au validateur la plage requise sous forme de paire `[de, à]` : ```php -$form->addInteger('age', 'Age:') - ->addRule($form::Range, 'You must be older 18 years and be under 120.', [18, 120]); +$form->addInteger('age', 'Âge:') + ->addRule($form::Range, 'L\'âge doit être compris entre 18 et 120 ans', [18, 120]); ``` .[tip] -Si l'utilisateur ne remplit pas le champ, les règles de validation ne seront pas vérifiées, car le champ est facultatif. +Si l'utilisateur ne remplit pas le champ, les règles de validation ne seront pas vérifiées, car l'élément est facultatif. -Il y a évidemment de la place pour un petit remaniement. Dans le message d'erreur et dans le troisième paramètre, les nombres sont listés en double, ce qui n'est pas idéal. Si nous créions un [formulaire multilingue |rendering#translating] et que le message contenant les chiffres devait être traduit en plusieurs langues, il serait plus difficile de modifier les valeurs. C'est pourquoi on peut utiliser les caractères de substitution `%d`: +Ici, il y a de la place pour un petit refactoring. Dans le message d'erreur et dans le troisième paramètre, les chiffres sont indiqués en double, ce qui n'est pas idéal. Si nous créions des [formulaires multilingues |rendering#Traduction] et que le message contenant des chiffres était traduit dans plusieurs langues, un éventuel changement de valeurs serait compliqué. Pour cette raison, il est possible d'utiliser les placeholders `%d` et Nette complétera les valeurs : ```php - ->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]); + ->addRule($form::Range, 'L\'âge doit être compris entre %d et %d ans', [18, 120]); ``` -Revenons au champ *mot de passe*, rendons-le *obligatoire* et vérifions la longueur minimale du mot de passe (`$form::MinLength`), en utilisant à nouveau les caractères de substitution du message : +Revenons à l'élément `password`, que nous rendrons également obligatoire et vérifierons en plus la longueur minimale du mot de passe (`$form::MinLength`), à nouveau en utilisant le placeholder : ```php -$form->addPassword('password', 'Password:') - ->setRequired('Pick a password') - ->addRule($form::MinLength, 'Your password has to be at least %d long', 8); +$form->addPassword('password', 'Mot de passe:') + ->setRequired('Choisissez un mot de passe') + ->addRule($form::MinLength, 'Le mot de passe doit comporter au moins %d caractères', 8); ``` -Nous ajouterons un champ `passwordVerify` au formulaire, dans lequel l'utilisateur saisira à nouveau le mot de passe, pour vérification. En utilisant les règles de validation, nous vérifions si les deux mots de passe sont les mêmes (`$form::Equal`). Et comme argument, nous donnons une référence au premier mot de passe en utilisant des [crochets |#Access to Controls]: +Ajoutons au formulaire un champ `passwordVerify`, où l'utilisateur saisira à nouveau le mot de passe, pour vérification. À l'aide des règles de validation, nous vérifions si les deux mots de passe sont identiques (`$form::Equal`). Et comme paramètre, nous donnons une référence au premier mot de passe en utilisant des [crochets |#Accès aux éléments] : ```php -$form->addPassword('passwordVerify', 'Password again:') - ->setRequired('Fill your password again to check for typo') - ->addRule($form::Equal, 'Password mismatch', $form['password']) +$form->addPassword('passwordVerify', 'Mot de passe pour vérification:') + ->setRequired('Veuillez saisir à nouveau le mot de passe pour vérification') + ->addRule($form::Equal, 'Les mots de passe ne correspondent pas', $form['password']) ->setOmitted(); ``` -Avec `setOmitted()`, nous marquons un élément dont la valeur ne nous intéresse pas vraiment et qui n'existe que pour la validation. Sa valeur n'est pas transmise à `$data`. +Avec `setOmitted()`, nous avons marqué l'élément dont la valeur ne nous importe pas vraiment et qui n'existe que pour des raisons de validation. La valeur ne sera pas transmise à `$data`. -Nous avons un formulaire entièrement fonctionnel avec validation en PHP et JavaScript. Les capacités de validation de Nette sont beaucoup plus larges, vous pouvez créer des conditions, afficher et masquer des parties d'une page en fonction de celles-ci, etc. Vous pouvez tout découvrir dans le chapitre sur la [validation des formulaires |validation]. +Nous avons ainsi un formulaire entièrement fonctionnel avec validation en PHP et JavaScript. Les capacités de validation de Nette sont beaucoup plus larges, il est possible de créer des conditions, de faire afficher et masquer des parties de la page en fonction d'elles, etc. Vous apprendrez tout cela dans le chapitre sur la [validation des formulaires |validation]. -Valeurs par défaut .[#toc-default-values] -========================================= +Valeurs par défaut +================== -Nous définissons souvent des valeurs par défaut pour les contrôles de formulaires : +Nous définissons couramment des valeurs par défaut pour les éléments du formulaire : ```php -$form->addEmail('email', 'Email') +$form->addEmail('email', 'E-mail') ->setDefaultValue($lastUsedEmail); ``` -Il est souvent utile de définir des valeurs par défaut pour tous les contrôles à la fois. Par exemple, lorsque le formulaire est utilisé pour modifier des enregistrements. Nous lisons l'enregistrement depuis la base de données et le définissons comme valeur par défaut : +Il est souvent utile de définir les valeurs par défaut pour tous les éléments en même temps. Par exemple, lorsque le formulaire sert à modifier des enregistrements. Nous lisons l'enregistrement de la base de données et définissons les valeurs par défaut : ```php //$row = ['name' => 'John', 'age' => '33', /* ... */]; $form->setDefaults($row); ``` -Appelez `setDefaults()` après avoir défini les contrôles. +Appelez `setDefaults()` après la définition des éléments. -Rendu du formulaire .[#toc-rendering-the-form] -============================================== +Rendu du formulaire +=================== -Par défaut, le formulaire est rendu sous forme de tableau. Les contrôles individuels suivent les directives de base en matière d'accessibilité du Web. Toutes les étiquettes sont générées en tant qu'éléments `<label>` et sont associées à leurs entrées. Un clic sur l'étiquette déplace le curseur sur l'entrée. +Par défaut, le formulaire est rendu sous forme de tableau. Les éléments individuels respectent la règle d'accessibilité de base - tous les libellés sont écrits en tant que `<label>` et liés à l'élément de formulaire correspondant. En cliquant sur le libellé, le curseur apparaît automatiquement dans le champ du formulaire. -Nous pouvons définir n'importe quel attribut HTML pour chaque élément. Par exemple, ajouter un espace réservé : +Nous pouvons définir des attributs HTML arbitraires pour chaque élément. Par exemple, ajouter un placeholder : ```php -$form->addInteger('age', 'Age:') - ->setHtmlAttribute('placeholder', 'Please fill in the age'); +$form->addInteger('age', 'Âge:') + ->setHtmlAttribute('placeholder', 'Veuillez remplir l\'âge'); ``` -Il y a vraiment beaucoup de façons de rendre un formulaire, c'est pourquoi ce [chapitre |rendering] est consacré [au rendu |rendering]. +Il existe vraiment un grand nombre de façons de rendre un formulaire, c'est pourquoi un [chapitre séparé sur le rendu |rendering] y est consacré. -Mappage vers les classes .[#toc-mapping-to-classes] -=================================================== +Mappage sur des classes +======================= -Revenons au traitement des données du formulaire. La méthode `getValues()` a renvoyé les données soumises sous la forme d'un objet `ArrayHash`. Comme il s'agit d'une classe générique, un peu comme `stdClass`, il nous manquera certaines commodités lorsque nous travaillerons avec elle, comme la complétion du code pour les propriétés dans les éditeurs ou l'analyse statique du code. Cela pourrait être résolu en ayant une classe spécifique pour chaque formulaire, dont les propriétés représentent les contrôles individuels. Par exemple : +Revenons au traitement des données du formulaire. La méthode `getValues()` nous retournait les données soumises sous forme d'objet `ArrayHash`. Comme il s'agit d'une classe générique, quelque chose comme `stdClass`, il nous manquera un certain confort lors de son utilisation, comme l'autocomplétion des propriétés dans les éditeurs ou l'analyse statique du code. Cela pourrait être résolu en ayant une classe spécifique pour chaque formulaire, dont les propriétés représentent les éléments individuels. Par exemple : ```php class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -A partir de PHP 8.0, vous pouvez utiliser cette notation élégante qui utilise un constructeur : +Alternativement, vous pouvez utiliser un constructeur : ```php class RegistrationFormData @@ -221,16 +220,18 @@ class RegistrationFormData } ``` -Comment dire à Nette de nous retourner les données sous forme d'objets de cette classe ? C'est plus facile que vous ne le pensez. Tout ce que vous avez à faire est de spécifier le nom de la classe ou de l'objet à hydrater comme paramètre : +Les propriétés de la classe de données peuvent également être des enums et leur mappage se fera automatiquement. .{data-version:3.2.4} + +Comment dire à Nette de nous retourner les données sous forme d'objets de cette classe ? Plus facilement que vous ne le pensez. Il suffit d'indiquer le nom de la classe ou l'objet à hydrater comme paramètre : ```php $data = $form->getValues(RegistrationFormData::class); $name = $data->name; ``` -On peut également spécifier un `'array'` comme paramètre, et les données reviennent alors sous forme de tableau. +Il est également possible d'indiquer `'array'` comme paramètre, et les données seront alors retournées sous forme de tableau. -Si les formulaires consistent en une structure à plusieurs niveaux composée de conteneurs, créez une classe distincte pour chacun d'eux : +Si les formulaires forment une structure à plusieurs niveaux composée de conteneurs, créez une classe distincte pour chacun : ```php $form = new Form; @@ -247,26 +248,28 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` -Le mappage sait alors, à partir du type de propriété `$person`, qu'il doit mapper le conteneur à la classe `PersonFormData`. Si la propriété contient un tableau de conteneurs, fournissez le type `array` et passez la classe à mapper directement au conteneur : +Le mappage reconnaîtra alors à partir du type de la propriété `$person` qu'il doit mapper le conteneur sur la classe `PersonFormData`. Si la propriété contenait un tableau de conteneurs, indiquez le type `array` et passez la classe pour le mappage directement au conteneur : ```php $person->setMappedType(PersonFormData::class); ``` +Vous pouvez faire générer la conception de la classe de données du formulaire à l'aide de la méthode `Nette\Forms\Blueprint::dataClass($form)`, qui l'affichera dans la page du navigateur. Il suffit ensuite de cliquer pour sélectionner le code et de le copier dans votre projet. .{data-version:3.1.15} + -Boutons d'envoi multiples .[#toc-multiple-submit-buttons] -========================================================= +Plusieurs boutons +================= -Si le formulaire comporte plus d'un bouton, nous devons généralement distinguer lequel a été pressé. La méthode `isSubmittedBy()` du bouton nous renvoie cette information : +Si le formulaire a plus d'un bouton, nous avons généralement besoin de distinguer lequel d'entre eux a été pressé. Cette information nous est retournée par la méthode `isSubmittedBy()` du bouton : ```php -$form->addSubmit('save', 'Save'); -$form->addSubmit('delete', 'Delete'); +$form->addSubmit('save', 'Enregistrer'); +$form->addSubmit('delete', 'Supprimer'); if ($form->isSuccess()) { if ($form['save']->isSubmittedBy()) { @@ -279,37 +282,36 @@ if ($form->isSuccess()) { } ``` -N'omettez pas le `$form->isSuccess()` pour vérifier la validité des données. +Ne sautez pas la requête `$form->isSuccess()`, elle vérifie la validité des données. -Lorsqu'un formulaire est soumis avec la touche <kbd>Entrée</kbd>, il est traité comme s'il avait été soumis avec le premier bouton. +Lorsque le formulaire est soumis avec la touche <kbd>Entrée</kbd>, cela est considéré comme s'il avait été soumis par le premier bouton. -Protection contre les vulnérabilités .[#toc-vulnerability-protection] -===================================================================== +Protection contre les vulnérabilités +==================================== -Nette Framework fait de gros efforts pour être sûr et comme les formulaires sont les entrées les plus courantes de l'utilisateur, les formulaires Nette sont aussi impénétrables que possible. +Nette Framework accorde une grande importance à la sécurité et veille donc scrupuleusement à la bonne sécurisation des formulaires. -En plus de protéger les formulaires contre les attaques de vulnérabilités bien connues comme le [Cross-Site Scripting (XSS) |nette:glossary#cross-site-scripting-xss] et le [Cross-Site Request Forgery (CSRF) |nette:glossary#cross-site-request-forgery-csrf], il effectue de nombreuses petites tâches de sécurité auxquelles vous n'avez plus à penser. +En plus de protéger les formulaires contre les attaques [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] et [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF], il effectue de nombreuses petites sécurisations auxquelles vous n'avez plus besoin de penser. -Par exemple, il filtre tous les caractères de contrôle des entrées et vérifie la validité de l'encodage UTF-8, de sorte que les données du formulaire sont toujours propres. Pour les cases de sélection et les listes radio, il vérifie que les éléments sélectionnés font bien partie des éléments proposés et qu'il n'y a pas eu de falsification. Nous avons déjà mentionné que pour les entrées de texte sur une seule ligne, il supprime les caractères de fin de ligne qu'un attaquant pourrait y envoyer. Pour les entrées multilignes, il normalise les caractères de fin de ligne. Et ainsi de suite. +Par exemple, il filtre tous les caractères de contrôle des entrées et vérifie la validité de l'encodage UTF-8, de sorte que les données du formulaire seront toujours propres. Pour les select box et les radio lists, il vérifie que les éléments sélectionnés provenaient bien des options proposées et qu'il n'y a pas eu de falsification. Nous avons déjà mentionné que pour les entrées de texte sur une seule ligne, il supprime les caractères de fin de ligne qu'un attaquant aurait pu y envoyer. Pour les entrées multilignes, il normalise les caractères de fin de ligne. Et ainsi de suite. -Nette corrige pour vous les failles de sécurité dont la plupart des programmeurs ignorent l'existence. +Nette résout pour vous les risques de sécurité dont beaucoup de programmeurs ne soupçonnent même pas l'existence. -L'attaque CSRF mentionnée consiste en ce qu'un attaquant attire la victime à visiter une page qui exécute silencieusement une requête dans le navigateur de la victime vers le serveur où la victime est actuellement connectée, et le serveur croit que la requête a été faite par la victime à volonté. Par conséquent, Nette empêche que le formulaire soit soumis via POST à partir d'un autre domaine. Si, pour une raison quelconque, vous souhaitez désactiver la protection et autoriser la soumission du formulaire à partir d'un autre domaine, utilisez : +L'attaque CSRF mentionnée consiste en ce qu'un attaquant attire la victime sur une page qui exécute discrètement dans le navigateur de la victime une requête vers le serveur sur lequel la victime est connectée, et le serveur croit que la requête a été exécutée par la victime de sa propre volonté. C'est pourquoi Nette empêche la soumission d'un formulaire POST depuis un autre domaine. Si pour une raison quelconque vous souhaitez désactiver la protection et autoriser la soumission du formulaire depuis un autre domaine, utilisez : ```php $form->allowCrossOrigin(); // ATTENTION ! Désactive la protection ! ``` -Cette protection utilise un cookie SameSite nommé `_nss`. Par conséquent, créez un formulaire avant d'envoyer la première sortie afin que le cookie puisse être envoyé. +Cette protection utilise un cookie SameSite nommé `_nss`. Créez donc l'objet formulaire avant d'envoyer la première sortie, afin que le cookie puisse être envoyé. -La protection par cookie SameSite peut ne pas être fiable à 100%, c'est pourquoi il est préférable d'activer la protection par jeton : +La protection par cookie SameSite peut ne pas être fiable à 100%, il est donc conseillé d'activer également la protection par jeton : ```php $form->addProtection(); ``` -Il est fortement recommandé d'appliquer cette protection aux formulaires d'une partie administrative de votre application qui modifie des données sensibles. Le framework protège contre une attaque CSRF en générant et en validant le jeton d'authentification qui est stocké dans une session (l'argument est le message d'erreur affiché si le jeton a expiré). C'est pourquoi il est nécessaire de démarrer une session avant d'afficher le formulaire. Dans la partie administration du site, la session est généralement déjà démarrée, en raison du login de l'utilisateur. -Sinon, démarrez la session avec la méthode `Nette\Http\Session::start()`. +Nous recommandons de protéger ainsi les formulaires dans la partie administration du site, qui modifient des données sensibles dans l'application. Le framework se défend contre l'attaque CSRF en générant et en vérifiant un jeton d'autorisation qui est stocké dans la session. Il est donc nécessaire d'avoir une session ouverte avant d'afficher le formulaire. Dans la partie administration du site, la session est généralement déjà démarrée en raison de la connexion de l'utilisateur. Sinon, démarrez la session avec la méthode `Nette\Http\Session::start()`. -Ainsi, nous avons une introduction rapide aux formulaires dans Nette. Essayez de regarder dans le répertoire d'[exemples |https://github.com/nette/forms/tree/master/examples] de la distribution pour plus d'inspiration. +Voilà, nous avons fait une rapide introduction aux formulaires dans Nette. Essayez de jeter un œil au répertoire [examples|https://github.com/nette/forms/tree/master/examples] dans la distribution, où vous trouverez plus d'inspiration. diff --git a/forms/fr/validation.texy b/forms/fr/validation.texy index 361456d8c7..0303e327d9 100644 --- a/forms/fr/validation.texy +++ b/forms/fr/validation.texy @@ -2,180 +2,191 @@ Validation des formulaires ************************** -Contrôles obligatoires .[#toc-required-controls] -================================================ +Éléments obligatoires +===================== -Les contrôles sont marqués comme obligatoires par la méthode `setRequired()`, dont l'argument est le texte du [message d'erreur |#Error Messages] qui sera affiché si l'utilisateur ne le remplit pas. Si aucun argument n'est donné, le message d'erreur par défaut est utilisé. +Nous marquons les éléments obligatoires avec la méthode `setRequired()`, dont l'argument est le texte du [message d'erreur |#Messages d erreur] qui s'affiche si l'utilisateur ne remplit pas l'élément. Si nous n'indiquons pas d'argument, le message d'erreur par défaut est utilisé. ```php -$form->addText('name', 'Name:') - ->setRequired('Please fill your name.'); +$form->addText('name', 'Nom:') + ->setRequired('Veuillez saisir un nom'); ``` -Règles .[#toc-rules] -==================== +Règles +====== -Nous ajoutons des règles de validation aux contrôles avec la méthode `addRule()`. Le premier paramètre est la règle, le deuxième est le [message d'erreur |#Error Messages], et le troisième est l'argument de la règle de validation. +Nous ajoutons des règles de validation aux éléments avec la méthode `addRule()`. Le premier paramètre est la règle, le deuxième est le texte du [message d'erreur |#Messages d erreur] et le troisième est l'argument de la règle de validation. ```php -$form->addPassword('password', 'Password:') - ->addRule($form::MinLength, 'Password must be at least %d characters', 8); +$form->addPassword('password', 'Mot de passe:') + ->addRule($form::MinLength, 'Le mot de passe doit comporter au moins %d caractères', 8); ``` -Nette est livré avec un certain nombre de règles intégrées dont les noms sont des constantes de la classe `Nette\Forms\Form`: +**Les règles de validation ne sont vérifiées que si l'utilisateur a rempli l'élément.** -Nous pouvons utiliser les règles suivantes pour tous les contrôles : +Nette est livré avec un certain nombre de règles prédéfinies, dont les noms sont des constantes de la classe `Nette\Forms\Form`. Nous pouvons utiliser ces règles pour tous les éléments : -| constante | description | arguments +| constante | description | type d'argument |------- -| `Required` | alias de `setRequired()` | - -| `Filled` | alias de `setRequired()` | - -| `Blank` | ne doit pas être rempli | - +| `Required` | élément obligatoire, alias pour `setRequired()` | - +| `Filled` | élément obligatoire, alias pour `setRequired()` | - +| `Blank` | l'élément ne doit pas être rempli | - | `Equal` | la valeur est égale au paramètre | `mixed` | `NotEqual` | la valeur n'est pas égale au paramètre | `mixed` -| `IsIn` | la valeur est égale à un élément du tableau | `array` -| `IsNotIn` | la valeur n'est égale à aucun élément du tableau | `array` -| `Valid` | L'entrée passe la validation (pour les [conditions |#conditions]) | - - -Pour les contrôles `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` les règles suivantes peuvent également être utilisées : - -| `MinLength` | longueur minimale de la chaîne | `int` -| `MaxLength` | longueur maximale de la chaîne de caractères | `int` -| `Length` | longueur dans l'intervalle ou longueur exacte | paire `[int, int]` ou `int` -| `Email` | adresse électronique valide | - -| `URL` | URL valide | - -| `Pattern` | correspond à un modèle régulier | `string` -| `PatternInsensitive` | comme `Pattern`, mais insensible à la casse | `string` -| `Integer` | nombre entier | - -| `Numeric` | alias de `Integer` | - -| `Float` | nombre entier ou à virgule flottante | - -| `Min` | minimum de la valeur entière | `int\|float` -| `Max` | maximum de la valeur entière | `int\|float` -| `Range` | valeur dans l'intervalle | paire `[int\|float, int\|float]` - -Les règles `Integer`, `Numeric` et `Float` convertissent automatiquement la valeur en nombre entier (ou flottant respectivement). En outre, la règle `URL` accepte également une adresse sans schéma (par exemple `nette.org`) et complète le schéma (`https://nette.org`). -Les expressions de `Pattern` et `PatternInsensitive` doivent être valables pour la valeur entière, c'est-à-dire comme si elle était enveloppée dans les caractères `^` and `$`. - -Pour les contrôles `addUpload()`, `addMultiUpload()`, les règles suivantes peuvent également être utilisées : - -| `MaxFileSize` | taille maximale du fichier | `int` -`MimeType` | type MIME, accepte les caractères génériques (`'video/*'`) | type MIME, accepte les caractères génériques ( ) | type MIME, accepte les caractères génériques ( ) `string\|string[]` -| `Image` | le fichier téléchargé est un JPEG, PNG, GIF, WebP | - -| `Pattern` | le nom du fichier correspond à une expression régulière | `string` +| `IsIn` | la valeur est égale à l'un des éléments du tableau | `array` +| `IsNotIn` | la valeur n'est égale à aucun des éléments du tableau | `array` +| `Valid` | l'élément est-il rempli correctement ? (pour [#conditions]) | - + + +Entrées textuelles +------------------ + +Pour les éléments `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`, `addFloat()`, certaines des règles suivantes peuvent également être utilisées : + +| `MinLength` | longueur minimale du texte | `int` +| `MaxLength` | longueur maximale du texte | `int` +| `Length` | longueur dans une plage ou longueur exacte | paire `[int, int]` ou `int` +| `Email` | adresse e-mail valide | - +| `URL` | URL absolue | - +| `Pattern` | correspond à l'expression régulière | `string` | `PatternInsensitive` | comme `Pattern`, mais insensible à la casse | `string` +| `Integer` | valeur entière | - +| `Numeric` | alias pour `Integer` | - +| `Float` | nombre | - +| `Min` | valeur minimale de l'élément numérique | `int\|float` +| `Max` | valeur maximale de l'élément numérique | `int\|float` +| `Range` | valeur dans une plage | paire `[int\|float, int\|float]` + +Les règles de validation `Integer`, `Numeric` et `Float` convertissent directement la valeur en entier resp. flottant. De plus, la règle `URL` accepte également une adresse sans schéma (par ex. `nette.org`) et complète le schéma (`https://nette.org`). L'expression dans `Pattern` et `PatternIcase` doit s'appliquer à toute la valeur, c'est-à-dire comme si elle était entourée des caractères `^` et `$`. + -Les sites `MimeType` et `Image` nécessitent l'extension PHP `fileinfo`. Le fait qu'un fichier ou une image soit du type requis est détecté par sa signature. L'intégrité de l'ensemble du fichier n'est pas vérifiée. Vous pouvez savoir si une image n'est pas corrompue, par exemple en essayant de [la charger |http:request#toImage]. +Nombre d'éléments +----------------- -Pour les contrôles `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()`, les règles suivantes peuvent également être utilisées pour limiter le nombre d'éléments sélectionnés, respectivement de fichiers téléchargés : +Pour les éléments `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()`, les règles suivantes peuvent également être utilisées pour limiter le nombre d'éléments sélectionnés resp. de fichiers uploadés : -| `MinLength` | compte minimal | `int` -| `MaxLength` | compte maximal | `int` -| `Length` | compte dans l'intervalle ou compte exact | paire `[int, int]` ou `int` +| `MinLength` | nombre minimum | `int` +| `MaxLength` | nombre maximum | `int` +| `Length` | nombre dans une plage ou nombre exact | paire `[int, int]` ou `int` -Messages d'erreur .[#toc-error-messages] ----------------------------------------- +Upload de fichiers +------------------ -Toutes les règles prédéfinies, à l'exception de `Pattern` et `PatternInsensitive`, ont un message d'erreur par défaut ; elles peuvent donc être omises. Toutefois, en passant et en formulant tous les messages personnalisés, vous rendrez le formulaire plus convivial. +Pour les éléments `addUpload()`, `addMultiUpload()`, les règles suivantes peuvent également être utilisées : -Vous pouvez changer les messages par défaut dans [forms:configuration], en modifiant les textes dans le tableau `Nette\Forms\Validator::$messages` ou en utilisant [translator |rendering#translating]. +| `MaxFileSize` | taille maximale du fichier en octets | `int` +| `MimeType` | type MIME, caractères génériques autorisés (`'video/*'`) | `string\|string[]` +| `Image` | image JPEG, PNG, GIF, WebP, AVIF | - +| `Pattern` | le nom du fichier correspond à l'expression régulière | `string` +| `PatternInsensitive` | comme `Pattern`, mais insensible à la casse | `string` + +`MimeType` et `Image` nécessitent l'extension PHP `fileinfo`. Le fait qu'un fichier ou une image soit du type requis est détecté sur la base de sa signature et **ne vérifie pas l'intégrité de l'ensemble du fichier.** On peut vérifier si une image n'est pas endommagée, par exemple, en essayant de la [charger |http:request#toImage]. + + +Messages d'erreur +================= -Les caractères de remplacement suivants peuvent être utilisés dans le texte des messages d'erreur : +Toutes les règles prédéfinies à l'exception de `Pattern` et `PatternInsensitive` ont un message d'erreur par défaut, il est donc possible de l'omettre. Cependant, en indiquant et en formulant tous les messages sur mesure, vous rendrez le formulaire plus convivial. -| `%d` | remplace progressivement les règles après les arguments -| `%n$d` | remplace par le nième argument de règle -| `%label` | remplace par le libellé du champ (sans les deux points) -| `%name` | remplace par le nom du champ (par exemple `name`) -| `%value` | remplace par la valeur entrée par l'utilisateur +Vous pouvez modifier les messages par défaut dans la [configuration|forms:configuration], en modifiant les textes dans le tableau `Nette\Forms\Validator::$messages` ou en utilisant un [traducteur |rendering#Traduction]. + +Dans le texte des messages d'erreur, les chaînes de remplacement suivantes peuvent être utilisées : + +| `%d` | remplace successivement par les arguments de la règle +| `%n$d` | remplace par le n-ième argument de la règle +| `%label` | remplace par le libellé de l'élément (sans les deux points) +| `%name` | remplace par le nom de l'élément (par ex. `name`) +| `%value` | remplace par la valeur saisie par l'utilisateur ```php -$form->addText('name', 'Name:') - ->setRequired('Please fill in %label'); +$form->addText('name', 'Nom:') + ->setRequired('Veuillez remplir %label'); $form->addInteger('id', 'ID:') - ->addRule($form::Range, 'at least %d and no more than %d', [5, 10]); + ->addRule($form::Range, 'au moins %d et au plus %d', [5, 10]); $form->addInteger('id', 'ID:') - ->addRule($form::Range, 'no more than %2$d and at least %1$d', [5, 10]); + ->addRule($form::Range, 'au plus %2$d et au moins %1$d', [5, 10]); ``` -Conditions .[#toc-conditions] -============================= +Conditions +========== -Outre les règles de validation, il est possible de définir des conditions. Elles sont définies de la même manière que les règles, mais nous utilisons `addRule()` au lieu de `addCondition()` et, bien sûr, nous ne laissons pas de message d'erreur (la condition demande simplement) : +En plus des règles, il est également possible d'ajouter des conditions. Elles s'écrivent de la même manière que les règles, sauf qu'au lieu de `addRule()`, nous utilisons la méthode `addCondition()` et, bien sûr, nous n'indiquons aucun message d'erreur (la condition ne fait que demander) : ```php -$form->addPassword('password', 'Password:') - // si le mot de passe ne dépasse pas 8 caractères ... +$form->addPassword('password', 'Mot de passe:') + // si le mot de passe n'est pas plus long que 8 caractères ->addCondition($form::MaxLength, 8) - // ... alors il doit contenir un nombre - ->addRule($form::Pattern, 'Must contain number', '.*[0-9].*'); + // alors il doit contenir un chiffre + ->addRule($form::Pattern, 'Doit contenir un chiffre', '.*[0-9].*'); ``` -La condition peut être liée à un élément différent de l'élément actuel en utilisant `addConditionOn()`. Le premier paramètre est une référence au champ. Dans le cas suivant, l'e-mail ne sera requis que si la case est cochée (c'est-à-dire si sa valeur est `true`) : +La condition peut également être liée à un autre élément que l'élément actuel à l'aide de `addConditionOn()`. Comme premier paramètre, nous indiquons une référence à l'élément. Dans cet exemple, l'e-mail ne sera obligatoire que si la case à cocher est cochée (sa valeur sera true) : ```php -$form->addCheckbox('newsletters', 'send me newsletters'); +$form->addCheckbox('newsletters', 'envoyez-moi les newsletters'); -$form->addEmail('email', 'Email:') - // si la case est cochée ... +$form->addEmail('email', 'E-mail:') + // si la case à cocher est cochée ->addConditionOn($form['newsletters'], $form::Equal, true) - // ... nécessite un email - ->setRequired('Remplissez votre adresse e-mail'); + // alors exiger l'e-mail + ->setRequired('Saisissez une adresse e-mail'); ``` -Les conditions peuvent être regroupées en structures complexes avec les méthodes `elseCondition()` et `endCondition()`. +Il est possible de créer des structures complexes à partir de conditions à l'aide de `elseCondition()` et `endCondition()` : ```php $form->addText(/* ... */) ->addCondition(/* ... */) // si la première condition est remplie - ->addConditionOn(/* ... */) // et la deuxième condition sur un autre élément aussi - ->addRule(/* ... */) // nécessite cette règle + ->addConditionOn(/* ... */) // et la deuxième condition sur un autre élément + ->addRule(/* ... */) // exiger cette règle ->elseCondition() // si la deuxième condition n'est pas remplie - ->addRule(/* ... */) // nécessite ces règles + ->addRule(/* ... */) // exiger ces règles ->addRule(/* ... */) - ->endCondition() // on revient à la première condition + ->endCondition() // nous revenons à la première condition ->addRule(/* ... */); ``` -Dans Nette, il est très facile de réagir à la réalisation ou non d'une condition du côté JavaScript en utilisant la méthode `toggle()`, voir [Dynamic JavaScript |#Dynamic JavaScript]. +Dans Nette, il est très facile de réagir à la satisfaction ou non d'une condition également côté JavaScript à l'aide de la méthode `toggle()`, voir [#javascript dynamique]. -Références entre les contrôles .[#toc-references-between-controls] -================================================================== +Référence à un autre élément +============================ -L'argument de la règle ou de la condition peut être une référence à un autre élément. Par exemple, vous pouvez valider dynamiquement que le champ `text` comporte autant de caractères que la valeur du champ `length`: +Comme argument de règle ou de condition, il est également possible de passer un autre élément du formulaire. La règle utilisera alors la valeur saisie ultérieurement par l'utilisateur dans le navigateur. Ainsi, il est possible, par exemple, de valider dynamiquement que l'élément `password` contient la même chaîne que l'élément `password_confirm` : ```php -$form->addInteger('length'); -$form->addText('text') - ->addRule($form::Length, null, $form['length']); +$form->addPassword('password', 'Mot de passe'); +$form->addPassword('password_confirm', 'Confirmez le mot de passe') + ->addRule($form::Equal, 'Les mots de passe saisis ne correspondent pas', $form['password']); ``` -Règles et conditions personnalisées .[#toc-custom-rules-and-conditions] -======================================================================= +Règles et conditions personnalisées +=================================== -Parfois, nous nous trouvons dans une situation où les règles de validation intégrées dans Nette ne sont pas suffisantes et nous devons valider les données de l'utilisateur à notre manière. Avec Nette, c'est très facile ! +Parfois, nous nous trouvons dans une situation où les règles de validation intégrées dans Nette ne suffisent pas et nous devons valider les données de l'utilisateur à notre manière. Dans Nette, c'est très simple ! -Vous pouvez passer n'importe quel callback comme premier paramètre aux méthodes `addRule()` ou `addCondition()`. La callback accepte l'élément lui-même comme premier paramètre et renvoie une valeur booléenne indiquant si la validation a réussi. Lors de l'ajout d'une règle à l'aide de `addRule()`, il est possible de transmettre des arguments supplémentaires, qui sont ensuite transmis comme deuxième paramètre. +Aux méthodes `addRule()` ou `addCondition()`, il est possible de passer n'importe quel callback comme premier paramètre. Celui-ci reçoit comme premier paramètre l'élément lui-même et retourne une valeur booléenne indiquant si la validation s'est déroulée correctement. Lors de l'ajout d'une règle à l'aide de `addRule()`, il est possible de spécifier d'autres arguments, qui sont ensuite passés comme deuxième paramètre. -L'ensemble personnalisé de validateurs peut donc être créé comme une classe avec des méthodes statiques : +Nous pouvons ainsi créer notre propre ensemble de validateurs sous forme de classe avec des méthodes statiques : ```php class MyValidators { // teste si la valeur est divisible par l'argument - public static function validateDivisibility(BaseControl $input, $arg): bool + public static function validateDivisibility(Nette\Forms\Control $input, $arg): bool { return $input->getValue() % $arg === 0; } - public static function validateEmailDomain(BaseControl $input, $domain) + public static function validateEmailDomain(Nette\Forms\Control $input, $domain) { - // validateurs supplémentaires + // autres validateurs } } ``` @@ -186,12 +197,12 @@ L'utilisation est alors très simple : $form->addInteger('num') ->addRule( [MyValidators::class, 'validateDivisibility'], - 'The value must be a multiple of %d', + 'La valeur doit être un multiple de %d', 8, ); ``` -Les règles de validation personnalisées peuvent également être ajoutées à JavaScript. La seule exigence est que la règle doit être une méthode statique. Son nom pour le validateur JavaScript est créé en concaténant le nom de la classe sans les barres obliques inversées `\`, the underscore `_`, et le nom de la méthode. Par exemple, écrivez `App\MyValidators::validateDivisibility` sous la forme `AppMyValidators_validateDivisibility` et ajoutez-la à l'objet `Nette.validators`: +Il est également possible d'ajouter des règles de validation personnalisées à JavaScript. La condition est que la règle soit une méthode statique. Son nom pour le validateur JavaScript est formé en joignant le nom de la classe sans les barres obliques inverses `\`, le trait de soulignement `_` et le nom de la méthode. Par exemple, `App\MyValidators::validateDivisibility` s'écrira `AppMyValidators_validateDivisibility` et sera ajouté à l'objet `Nette.validators`: ```js Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => { @@ -200,12 +211,12 @@ Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => ``` -Événement onValidate .[#toc-event-onvalidate] -============================================= +Événement onValidate +==================== -Après l'envoi du formulaire, la validation est effectuée en vérifiant les règles individuelles ajoutées par `addRule()`, puis en appelant l'[événement |nette:glossary#Events] `onValidate`. Son gestionnaire peut être utilisé pour une validation supplémentaire, généralement pour vérifier la combinaison correcte de valeurs dans plusieurs éléments du formulaire. +Après la soumission du formulaire, une validation est effectuée, au cours de laquelle les règles individuelles ajoutées à l'aide de `addRule()` sont vérifiées, puis l'[événement |nette:glossary#Événements events] `onValidate` est déclenché. Son gestionnaire peut être utilisé pour une validation supplémentaire, typiquement pour vérifier la combinaison correcte de valeurs dans plusieurs éléments du formulaire. -Si une erreur est détectée, elle est transmise au formulaire à l'aide de la méthode `addError()`. Celle-ci peut être appelée soit sur un élément spécifique, soit directement sur le formulaire. +Si une erreur est détectée, nous la transmettons au formulaire à l'aide de la méthode `addError()`. Celle-ci peut être appelée soit sur un élément spécifique, soit directement sur le formulaire. ```php protected function createComponentSignInForm(): Form @@ -219,16 +230,16 @@ protected function createComponentSignInForm(): Form public function validateSignInForm(Form $form, \stdClass $data): void { if ($data->foo > 1 && $data->bar > 5) { - $form->addError('Cette combinaison n'est pas possible.'); + $form->addError('Cette combinaison n\'est pas possible.'); } } ``` -Erreurs de traitement .[#toc-processing-errors] -=============================================== +Erreurs lors du traitement +========================== -Dans de nombreux cas, nous découvrons une erreur lors du traitement d'un formulaire valide, par exemple lorsque nous écrivons une nouvelle entrée dans la base de données et que nous rencontrons un doublon de clé. Dans ce cas, nous transmettons l'erreur au formulaire à l'aide de la méthode `addError()`. Cette méthode peut être appelée soit sur un élément spécifique, soit directement sur le formulaire : +Dans de nombreux cas, nous ne découvrons une erreur qu'au moment où nous traitons un formulaire valide, par exemple lors de l'écriture d'un nouvel élément dans la base de données et que nous rencontrons une duplication de clés. Dans ce cas, nous transmettons à nouveau l'erreur au formulaire à l'aide de la méthode `addError()`. Celle-ci peut être appelée soit sur un élément spécifique, soit directement sur le formulaire : ```php try { @@ -238,72 +249,71 @@ try { } catch (Nette\Security\AuthenticationException $e) { if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) { - $form->addError('Invalid password.'); + $form->addError('Mot de passe invalide.'); } } ``` -Si possible, nous recommandons d'ajouter l'erreur directement à l'élément de formulaire, car elle apparaîtra à côté de celui-ci lors de l'utilisation du moteur de rendu par défaut. +Si possible, nous recommandons d'attacher l'erreur directement à l'élément du formulaire, car elle s'affichera à côté de lui lors de l'utilisation du moteur de rendu par défaut. ```php -$form['date']->addError('Sorry, this date is already taken.'); +$form['date']->addError('Désolé, mais cette date est déjà prise.'); ``` -Vous pouvez appeler `addError()` à plusieurs reprises pour transmettre plusieurs messages d'erreur à un formulaire ou à un élément. Vous les obtenez avec `getErrors()`. +Vous pouvez appeler `addError()` à plusieurs reprises et ainsi transmettre plusieurs messages d'erreur au formulaire ou à l'élément. Vous les obtenez à l'aide de `getErrors()`. -Notez que `$form->getErrors()` renvoie un résumé de tous les messages d'erreur, même ceux transmis directement aux éléments individuels, et pas seulement ceux transmis directement au formulaire. Les messages d'erreur passés uniquement au formulaire sont récupérés via `$form->getOwnErrors()`. +Attention, `$form->getErrors()` retourne un résumé de tous les messages d'erreur, y compris ceux qui ont été transmis directement aux éléments individuels, et pas seulement directement au formulaire. Les messages d'erreur transmis uniquement au formulaire sont obtenus via `$form->getOwnErrors()`. -Modification des valeurs d'entrée .[#toc-modifying-input-values] -================================================================ +Modification de l'entrée +======================== -En utilisant la méthode `addFilter()`, nous pouvons modifier la valeur saisie par l'utilisateur. Dans cet exemple, nous allons tolérer et supprimer les espaces dans le code postal : +À l'aide de la méthode `addFilter()`, nous pouvons modifier la valeur saisie par l'utilisateur. Dans cet exemple, nous tolérerons et supprimerons les espaces dans le code postal : ```php -$form->addText('zip', 'Postcode:') +$form->addText('zip', 'Code postal:') ->addFilter(function ($value) { - return str_replace(' ', '', $value); // supprime les espaces du code postal + return str_replace(' ', '', $value); // nous supprimons les espaces du code postal }) - ->addRule($form::Pattern, 'Le code postal n'est pas composé de cinq chiffres', '\d{5}'); + ->addRule($form::Pattern, 'Le code postal n\'est pas sous la forme de cinq chiffres', '\d{5}'); ``` -Le filtre est inclus entre les règles et les conditions de validation et dépend donc de l'ordre des méthodes, c'est-à-dire que le filtre et la règle sont appelés dans le même ordre que celui des méthodes `addFilter()` et `addRule()`. +Le filtre s'insère entre les règles de validation et les conditions, et l'ordre des méthodes est donc important, c'est-à-dire que le filtre et la règle sont appelés dans l'ordre où les méthodes `addFilter()` et `addRule()` sont placées. -Validation JavaScript .[#toc-javascript-validation] -=================================================== +Validation JavaScript +===================== -Le langage des règles et conditions de validation est puissant. Même si toutes les constructions fonctionnent aussi bien du côté serveur que du côté client, en JavaScript. Les règles sont transférées dans des attributs HTML `data-nette-rules` sous forme de JSON. -La validation elle-même est gérée par un autre script, qui s'accroche à tous les événements du formulaire `submit`, itère sur toutes les entrées et exécute les validations respectives. +Le langage pour formuler les conditions et les règles est très puissant. Toutes les constructions fonctionnent à la fois côté serveur et côté JavaScript. Elles sont transmises dans les attributs HTML `data-nette-rules` sous forme de JSON. La validation elle-même est ensuite effectuée par un script qui intercepte l'événement `submit` du formulaire, parcourt les éléments individuels et effectue la validation appropriée. -Ce script est `netteForms.js`, qui est disponible à partir de plusieurs sources possibles : +Ce script est `netteForms.js` et est disponible à partir de plusieurs sources possibles : -Vous pouvez intégrer le script directement dans la page HTML à partir du CDN : +Vous pouvez insérer le script directement dans la page HTML depuis un CDN : ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Ou le copier localement dans le dossier public du projet (par exemple à partir de `vendor/nette/forms/src/assets/netteForms.min.js`) : +Ou le copier localement dans le dossier public du projet (par ex. depuis `vendor/nette/forms/src/assets/netteForms.min.js`) : ```latte <script src="/path/to/netteForms.min.js"></script> ``` -Ou installer via [npm |https://www.npmjs.com/package/nette-forms]: +Ou l'installer via [npm|https://www.npmjs.com/package/nette-forms] : ```shell npm install nette-forms ``` -Et ensuite charger et exécuter : +Et ensuite le charger et l'exécuter : ```js import netteForms from 'nette-forms'; netteForms.initOnLoad(); ``` -Sinon, vous pouvez le charger directement depuis le dossier `vendor`: +Alternativement, vous pouvez le charger directement depuis le dossier `vendor` : ```js import netteForms from '../path/to/vendor/nette/forms/src/assets/netteForms.js'; @@ -311,10 +321,10 @@ netteForms.initOnLoad(); ``` -JavaScript dynamique .[#toc-dynamic-javascript] -=============================================== +JavaScript dynamique +==================== -Vous souhaitez afficher les champs d'adresse uniquement si l'utilisateur choisit d'envoyer les marchandises par la poste ? Aucun problème. La clé est une paire de méthodes `addCondition()` & `toggle()`: +Voulez-vous afficher les champs pour saisir l'adresse uniquement si l'utilisateur choisit d'envoyer la marchandise par la poste ? Aucun problème. La clé est la paire de méthodes `addCondition()` & `toggle()` : ```php $form->addCheckbox('send_it') @@ -322,25 +332,25 @@ $form->addCheckbox('send_it') ->toggle('#address-container'); ``` -Ce code indique que lorsque la condition est remplie, c'est-à-dire lorsque la case est cochée, l'élément HTML `#address-container` sera visible. Et vice versa. Ainsi, nous plaçons les éléments de formulaire contenant l'adresse du destinataire dans un conteneur avec cet ID, et lorsque la case à cocher est activée, ils sont cachés ou affichés. Cette opération est gérée par le script `netteForms.js`. +Ce code dit que lorsque la condition est remplie, c'est-à-dire lorsque la case à cocher est cochée, l'élément HTML `#address-container` sera visible. Et inversement. Nous plaçons donc les éléments du formulaire avec l'adresse du destinataire dans un conteneur avec cet ID, et lors du clic sur la case à cocher, ils se masquent ou s'affichent. C'est le script `netteForms.js` qui s'en charge. -Tout sélecteur peut être transmis comme argument à la méthode `toggle()`. Pour des raisons historiques, une chaîne alphanumérique ne comportant aucun autre caractère spécial est traitée comme un identifiant d'élément, comme si elle était précédée de l'adresse `#` character. The second optional parameter allows us to reverse the behavior, i.e. if we used `toggle('#address-container', false)`. L'élément ne serait affiché que si la case à cocher n'était pas cochée. +Comme argument de la méthode `toggle()`, il est possible de passer n'importe quel sélecteur. Pour des raisons historiques, une chaîne alphanumérique sans autres caractères spéciaux est comprise comme l'ID de l'élément, c'est-à-dire comme si elle était précédée du caractère `#`. Le deuxième paramètre facultatif permet d'inverser le comportement, c'est-à-dire que si nous utilisions `toggle('#address-container', false)`, l'élément ne s'afficherait au contraire que si la case à cocher n'était pas cochée. -L'implémentation JavaScript par défaut modifie la propriété `hidden` pour les éléments. Toutefois, nous pouvons facilement modifier le comportement, par exemple en ajoutant une animation. Il suffit de remplacer la méthode `Nette.toggle` en JavaScript par une solution personnalisée : +L'implémentation par défaut en JavaScript modifie la propriété `hidden` des éléments. Cependant, nous pouvons facilement changer le comportement, par exemple en ajoutant une animation. Il suffit de remplacer la méthode `Nette.toggle` en JavaScript par notre propre solution : ```js Nette.toggle = (selector, visible, srcElement, event) => { document.querySelectorAll(selector).forEach((el) => { - // hide or show 'el' according to the value of 'visible' + // nous masquons ou affichons 'el' selon la valeur de 'visible' }); }; ``` -Désactiver la validation .[#toc-disabling-validation] -===================================================== +Désactivation de la validation +============================== -Dans certains cas, vous devez désactiver la validation. Si un bouton d'envoi n'est pas censé exécuter la validation après l'envoi (par exemple le bouton *Annulation* ou *Aperçu*), vous pouvez désactiver la validation en appelant `$submit->setValidationScope([])`. Vous pouvez également valider partiellement le formulaire en spécifiant les éléments à valider. +Parfois, il peut être utile de désactiver la validation. Si l'appui sur le bouton d'envoi ne doit pas effectuer de validation (approprié pour les boutons *Annuler* ou *Aperçu*), nous la désactivons avec la méthode `$submit->setValidationScope([])`. S'il doit effectuer une validation partielle, nous pouvons spécifier quels champs ou conteneurs de formulaire doivent être validés. ```php $form->addText('name') @@ -352,15 +362,15 @@ $details->addInteger('age') $details->addInteger('age2') ->setRequired('age2'); -$form->addSubmit('send1'); // Valide l'ensemble du formulaire +$form->addSubmit('send1'); // Valide tout le formulaire $form->addSubmit('send2') - ->setValidationScope([]); // Ne valide rien. + ->setValidationScope([]); // Ne valide rien du tout $form->addSubmit('send3') - ->setValidationScope([$form['name']]); // Ne valide que le champ 'name'. + ->setValidationScope([$form['name']]); // Valide uniquement l'élément name $form->addSubmit('send4') - ->setValidationScope([$form['details']['age']]); // Valide uniquement le champ 'age'. + ->setValidationScope([$form['details']['age']]); // Valide uniquement l'élément age $form->addSubmit('send5') - ->setValidationScope([$form['details']]); // Valide le conteneur 'details'. + ->setValidationScope([$form['details']]); // Valide le conteneur details ``` -L'[événement onValidate |#Event onValidate] sur le formulaire est toujours invoqué et n'est pas affecté par l'appel `setValidationScope`. L'événement `onValidate` sur le conteneur est invoqué uniquement lorsque ce conteneur est spécifié pour la validation partielle. +`setValidationScope` n'affecte pas l'[#événement onValidate] du formulaire, qui sera toujours appelée. L'événement `onValidate` du conteneur ne sera déclenché que si ce conteneur est marqué pour une validation partielle. diff --git a/forms/hu/@home.texy b/forms/hu/@home.texy index 2d19a0ee4b..2a5dfbfe40 100644 --- a/forms/hu/@home.texy +++ b/forms/hu/@home.texy @@ -1,31 +1,31 @@ -Nyomtatványok -************* +Nette Forms +*********** <div class=perex> -A Nette Forms forradalmasította a webes űrlapok készítését. Csak néhány világos kódsort kellett írni, és máris kész volt az űrlap, beleértve a renderelést, a JavaScriptet és a kiszolgálói érvényesítést, valamint az iparágvezető biztonságot. Lássuk, hogyan +A Nette Forms forradalmasította a webes űrlapok létrehozását. Hirtelen elég volt néhány érthető sor kódot írni, és kész volt az űrlap, beleértve a renderelést, a JavaScript és szerveroldali validációt, ráadásul csúcsminőségű biztonsággal. Megmutatjuk, hogyan -- barátságos űrlapok létrehozása -- validálja a küldött adatokat -- az elemeket pontosan a szükséges módon rajzolja ki +- hozzunk létre felhasználóbarát űrlapokat +- validáljuk az elküldött adatokat +- rendereljük az elemeket pontosan az igények szerint </div> -A Nette Forms segítségével csökkentheti az olyan rutinfeladatokat, mint az érvényesítés megírása (mind a szerver-, mind a kliensoldalon), valamint minimalizálhatja a hibák és a biztonsági problémák valószínűségét. +A Nette Forms használatával elkerülheti a rutin feladatok egész sorát, mint például a validáció írását (ráadásul kétszer, szerver- és kliensoldalon), minimalizálhatja a hibák és biztonsági rések kialakulásának valószínűségét. -Az űrlapokat a Nette alkalmazás részeként (azaz prezenterekben) vagy önállóan is használhatja. Mivel a felhasználás mindkét esetben kissé eltérő, ezért külön utasításokat készítettünk Önnek: +Az űrlapokat használhatja a Nette Alkalmazás részeként (azaz presenterekben), vagy teljesen önállóan. Mivel mindkét esetben a használat kissé eltérő, két útmutatót készítettünk Önnek: <div class="wiki-buttons"> -<div> "Forms in presenters .[wiki-button]":in-presenter </div> -<div> "Forms standalone .[wiki-button]":standalone </div> +<div> "Űrlapok presenterekben .[wiki-button]":in-presenter </div> +<div> "Űrlapok önállóan .[wiki-button]":standalone </div> </div> Telepítés --------- -Töltse le és telepítse a csomagot a [Composer |best-practices:composer] segítségével: +A könyvtárat a [Composer|best-practices:composer] eszközzel töltheti le és telepítheti: ```shell composer require nette/forms diff --git a/forms/hu/@left-menu.texy b/forms/hu/@left-menu.texy index d43d84264e..6fda5dc323 100644 --- a/forms/hu/@left-menu.texy +++ b/forms/hu/@left-menu.texy @@ -1,14 +1,14 @@ -Nyomtatványok -************* -- [Áttekintés |@home] -- [Formanyomtatványok a Presentersben |in-presenter] -- [Önálló űrlapok |standalone] -- [Nyomtatvány vezérlők |controls] -- [Érvényesítés |validation] +Nette Forms +*********** +- [Bevezetés |@home] +- [Űrlapok presenterekben|in-presenter] +- [Űrlapok önállóan|standalone] +- [Űrlap elemek |controls] +- [Validáció |validation] - [Renderelés |rendering] -- [Konfiguráció |Configuration] +- [Konfiguráció |configuration] -További olvasnivalók -******************** -- [Legjobb gyakorlatok |best-practices:] +További olvasmányok +******************* +- [Útmutatók és eljárások |best-practices:] diff --git a/forms/hu/@meta.texy b/forms/hu/@meta.texy new file mode 100644 index 0000000000..c172d1cda5 --- /dev/null +++ b/forms/hu/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette dokumentáció}} diff --git a/forms/hu/configuration.texy b/forms/hu/configuration.texy index 87066c55ab..d4b7a767c0 100644 --- a/forms/hu/configuration.texy +++ b/forms/hu/configuration.texy @@ -1,8 +1,8 @@ -Formanyomtatványok konfigurálása -******************************** +Űrlapok konfigurálása +********************* .[perex] -A konfigurációban megváltoztathatja az [űrlapok |validation] alapértelmezett [hibaüzeneteit |validation]. +A konfigurációban megváltoztathatók az alapértelmezett [űrlap hibaüzenetek|validation]. ```neon forms: @@ -30,4 +30,32 @@ forms: Nette\Forms\Controls\CsrfProtection::Protection: 'Your session has expired. Please return to the home page and try again.' ``` -Ha nem a teljes keretrendszert használja, és így a konfigurációs fájlokat sem, akkor az alapértelmezett hibaüzeneteket közvetlenül a `Nette\Forms\Validator::$messages` mezőben módosíthatja. +Itt a magyar fordítás: + +```neon +forms: + messages: + Equal: 'Kérjük, adja meg a %s értéket.' + NotEqual: 'Ez az érték nem lehet %s.' + Filled: 'Ez a mező kötelező.' + Blank: 'Ennek a mezőnek üresnek kell lennie.' + MinLength: 'Kérjük, adjon meg legalább %d karaktert.' + MaxLength: 'Kérjük, legfeljebb %d karaktert adjon meg.' + Length: 'Kérjük, adjon meg egy %d és %d karakter közötti értéket.' + Email: 'Kérjük, adjon meg egy érvényes e-mail címet.' + URL: 'Kérjük, adjon meg egy érvényes URL-t.' + Integer: 'Kérjük, adjon meg egy érvényes egész számot.' + Float: 'Kérjük, adjon meg egy érvényes számot.' + Min: 'Kérjük, adjon meg egy %d vagy annál nagyobb értéket.' + Max: 'Kérjük, adjon meg egy %d vagy annál kisebb értéket.' + Range: 'Kérjük, adjon meg egy %d és %d közötti értéket.' + MaxFileSize: 'A feltöltött fájl mérete legfeljebb %d bájt lehet.' + MaxPostSize: 'A feltöltött adatok meghaladják a %d bájtos korlátot.' + MimeType: 'A feltöltött fájl nem a várt formátumban van.' + Image: 'A feltöltött fájlnak JPEG, GIF, PNG, WebP vagy AVIF formátumú képnek kell lennie.' + Nette\Forms\Controls\SelectBox::Valid: 'Kérjük, válasszon érvényes opciót.' + Nette\Forms\Controls\UploadControl::Valid: 'Hiba történt a fájl feltöltése során.' + Nette\Forms\Controls\CsrfProtection::Protection: 'A munkamenete lejárt. Kérjük, térjen vissza a kezdőlapra, és próbálja újra.' +``` + +Ha nem használja a teljes keretrendszert, és így a konfigurációs fájlokat sem, megváltoztathatja az alapértelmezett hibaüzeneteket közvetlenül a `Nette\Forms\Validator::$messages` tömbben. diff --git a/forms/hu/controls.texy b/forms/hu/controls.texy index 8c00e73adc..46c606fbef 100644 --- a/forms/hu/controls.texy +++ b/forms/hu/controls.texy @@ -1,280 +1,375 @@ -Nyomtatvány vezérlők -******************** +Űrlap elemek +************ .[perex] -A beépített űrlapvezérlők áttekintése. +A standard űrlap elemek áttekintése. -addText(string|int $name, $label=null): TextInput .[method] -=========================================================== +addText(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +================================================================================================== -Egysoros szövegmező hozzáadása ( [TextInput |api:Nette\Forms\Controls\TextInput] osztály). Ha a felhasználó nem tölti ki a mezőt, akkor egy üres karakterláncot ad vissza `''`, vagy a `setNullable()` segítségével megváltoztatja, hogy `null` adjon vissza. +Hozzáad egy egysoros szöveges mezőt (osztály: [TextInput |api:Nette\Forms\Controls\TextInput]). Ha a felhasználó nem tölti ki a mezőt, üres stringet `''` ad vissza, vagy a `setNullable()` segítségével beállítható, hogy `null`-t adjon vissza. ```php -$form->addText('name', 'Name:') +$form->addText('name', 'Név:') ->setRequired() ->setNullable(); ``` -Automatikusan érvényesíti az UTF-8 szabványt, levágja a bal és jobb oldali fehérjeleket, és eltávolítja a támadó által küldhető sortöréseket. +Automatikusan validálja az UTF-8 kódolást, levágja a bal- és jobboldali szóközöket, és eltávolítja azokat az újsor karaktereket, amelyeket egy támadó küldhetett. -A maximális hossz a `setMaxLength()` segítségével korlátozható. Az [addFilter() |validation#Modifying Input Values] lehetővé teszi a felhasználó által megadott érték megváltoztatását. +A maximális hosszúságot a `setMaxLength()` segítségével lehet korlátozni. A felhasználó által bevitt érték módosítását az [addFilter() |validation#Bemenet módosítása] teszi lehetővé. -A `setHtmlType()` segítségével a `search`, `tel`, `url`, `range`, `date`, `datetime-local`, `month`, `time`, `week`, `color` beviteli elem [karakterét |https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types] módosíthatja. A `number` és `email` típusok helyett az [addInteger |#addInteger] és az [addEmail |#addEmail] típusok használatát javasoljuk, amelyek szerveroldali érvényesítést biztosítanak. +A `setHtmlType()` segítségével megváltoztatható a szöveges mező vizuális jellege olyan típusokra, mint a `search`, `tel` vagy `url`, lásd a [specifikációt|https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types]. Ne feledje, hogy a típusváltoztatás csak vizuális, és nem helyettesíti a validálási funkciót. Az `url` típushoz célszerű hozzáadni egy specifikus [URL validálási szabályt |validation#Szöveges bevitelek]. -```php -$form->addText('color', 'Choose color:') - ->setHtmlType('color') - ->addRule($form::Pattern, 'invalid value', '[0-9a-f]{6}'); -``` +.[note] +Más beviteli típusokhoz, mint például a `number`, `range`, `email`, `date`, `datetime-local`, `time` és `color`, használjon specializált metódusokat, mint a [#addInteger], [#addFloat], [#addEmail] [#addDate], [#addTime], [#addDateTime] és [#addColor], amelyek biztosítják a szerveroldali validációt. A `month` és `week` típusok még nem támogatottak teljes mértékben minden böngészőben. -Az elemhez beállítható az úgynevezett üres-érték, ami valami olyasmi, mint az alapértelmezett érték, de ha a felhasználó nem írja felül, akkor üres stringet vagy `null` ad vissza. +Az elemhez beállítható ún. empty-value, ami valami olyasmi, mint az alapértelmezett érték, de ha a felhasználó nem változtatja meg, az elem üres stringet vagy `null`-t ad vissza. ```php -$form->addText('phone', 'Phone:') +$form->addText('phone', 'Telefon:') ->setHtmlType('tel') - ->setEmptyValue('+420'); + ->setEmptyValue('+36'); ``` addTextArea(string|int $name, $label=null): TextArea .[method] ============================================================== -Hozzáad egy többsoros szövegmezőt ( [TextArea |api:Nette\Forms\Controls\TextArea] osztály). Ha a felhasználó nem tölti ki a mezőt, akkor egy üres karakterláncot ad vissza `''`, vagy a `setNullable()` segítségével megváltoztatja, hogy `null` adjon vissza. +Hozzáad egy mezőt többsoros szöveg bevitelére (osztály: [TextArea |api:Nette\Forms\Controls\TextArea]). Ha a felhasználó nem tölti ki a mezőt, üres stringet `''` ad vissza, vagy a `setNullable()` segítségével beállítható, hogy `null`-t adjon vissza. ```php -$form->addTextArea('note', 'Note:') - ->addRule($form::MaxLength, 'Your note is way too long', 10000); +$form->addTextArea('note', 'Megjegyzés:') + ->addRule($form::MaxLength, 'A megjegyzés túl hosszú', 10000); ``` -Automatikusan érvényesíti az UTF-8 formátumot és normalizálja a sortöréseket a `\n`. Az egysoros beviteli mezővel ellentétben nem vágja le a szóközöket. +Automatikusan validálja az UTF-8 kódolást és normalizálja a sorelválasztókat `\n`-re. Ellentétben az egysoros beviteli mezővel, itt nem történik szóközök levágása. -A maximális hossz a `setMaxLength()` segítségével korlátozható. Az [addFilter() |validation#Modifying Input Values] lehetővé teszi a felhasználó által beírt érték megváltoztatását. Az úgynevezett üres értéket a `setEmptyValue()` segítségével állíthatja be. +A maximális hosszúságot a `setMaxLength()` segítségével lehet korlátozni. A felhasználó által bevitt érték módosítását az [addFilter() |validation#Bemenet módosítása] teszi lehetővé. Beállítható ún. empty-value a `setEmptyValue()` segítségével. addInteger(string|int $name, $label=null): TextInput .[method] ============================================================== -Egész számok beviteli mezőjének hozzáadása ( [TextInput |api:Nette\Forms\Controls\TextInput] osztály). Egész számot vagy a `null` értéket adja vissza, ha a felhasználó nem ad meg semmit. +Hozzáad egy mezőt egész számok bevitelére (osztály: [TextInput |api:Nette\Forms\Controls\TextInput]). Vagy integert ad vissza, vagy `null`-t, ha a felhasználó nem ad meg semmit. + +```php +$form->addInteger('year', 'Év:') + ->addRule($form::Range, 'Az évnek %d és %d között kell lennie.', [1900, 2023]); +``` + +Az elem `<input type="number">`-ként jelenik meg. A `setHtmlType()` metódus használatával a típust `range`-re lehet változtatni a csúszka formájában történő megjelenítéshez, vagy `text`-re, ha a standard szöveges mezőt részesíti előnyben a `number` típus speciális viselkedése nélkül. + + +addFloat(string|int $name, $label=null): TextInput .[method]{data-version:3.1.12} +================================================================================= + +Hozzáad egy mezőt tizedes számok bevitelére (osztály: [TextInput |api:Nette\Forms\Controls\TextInput]). Vagy floatot ad vissza, vagy `null`-t, ha a felhasználó nem ad meg semmit. ```php -$form->addInteger('level', 'Level:') +$form->addFloat('level', 'Szint:') ->setDefaultValue(0) - ->addRule($form::Range, 'Level must be between %d and %d.', [0, 100]); + ->addRule($form::Range, 'A szintnek %d és %d között kell lennie.', [0, 100]); ``` +Az elem `<input type="number">`-ként jelenik meg. A `setHtmlType()` metódus használatával a típust `range`-re lehet változtatni a csúszka formájában történő megjelenítéshez, vagy `text`-re, ha a standard szöveges mezőt részesíti előnyben a `number` típus speciális viselkedése nélkül. + +A Nette és a böngésző tizedes elválasztóként elfogadja mind a vesszőt, mind a pontot. Annak érdekében, hogy ez a funkcionalitás a Firefoxban is elérhető legyen, ajánlott beállítani a `lang` attribútumot vagy az adott elemre, vagy az egész oldalra, például `<html lang="hu">`. + -addEmail(string|int $name, $label=null): TextInput .[method] -============================================================ +addEmail(string|int $name, $label=null, int $maxLength=255): TextInput .[method] +================================================================================ -E-mail cím mező hozzáadása érvényességi ellenőrzéssel ( [TextInput |api:Nette\Forms\Controls\TextInput] osztály). Ha a felhasználó nem tölti ki a mezőt, akkor egy üres karakterláncot ad vissza `''`, vagy a `setNullable()` segítségével megváltoztatja, hogy `null` adjon vissza. +Hozzáad egy mezőt e-mail cím bevitelére (osztály: [TextInput |api:Nette\Forms\Controls\TextInput]). Ha a felhasználó nem tölti ki a mezőt, üres stringet `''` ad vissza, vagy a `setNullable()` segítségével beállítható, hogy `null`-t adjon vissza. ```php -$form->addEmail('email', 'Email:'); +$form->addEmail('email', 'E-mail:'); ``` -Ellenőrzi, hogy az érték érvényes e-mail cím-e. Nem ellenőrzi, hogy a domain valóban létezik-e, csak a szintaxisát ellenőrzi. Automatikusan érvényesíti az UTF-8 szabványt, a bal és jobb oldali fehérjeleket levágja. +Ellenőrzi, hogy az érték érvényes e-mail cím-e. Nem ellenőrzi, hogy a domain valóban létezik-e, csak a szintaxist ellenőrzi. Automatikusan validálja az UTF-8 kódolást, levágja a bal- és jobboldali szóközöket. -A maximális hossz a `setMaxLength()` segítségével korlátozható. Az [addFilter() |validation#Modifying Input Values] lehetővé teszi a felhasználó által megadott érték megváltoztatását. Az úgynevezett üres értéket a `setEmptyValue()` segítségével állíthatja be. +A maximális hosszúságot a `setMaxLength()` segítségével lehet korlátozni. A felhasználó által bevitt érték módosítását az [addFilter() |validation#Bemenet módosítása] teszi lehetővé. Beállítható ún. empty-value a `setEmptyValue()` segítségével. -addPassword(string|int $name, $label=null): TextInput .[method] -=============================================================== +addPassword(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +====================================================================================================== -Jelszó mező hozzáadása ( [TextInput |api:Nette\Forms\Controls\TextInput] osztály). +Hozzáad egy mezőt jelszó bevitelére (osztály: [TextInput |api:Nette\Forms\Controls\TextInput]). ```php -$form->addPassword('password', 'Password:') +$form->addPassword('password', 'Jelszó:') ->setRequired() - ->addRule($form::MinLength, 'Password has to be at least %d characters long', 8) - ->addRule($form::Pattern, 'Password must contain a number', '.*[0-9].*'); + ->addRule($form::MinLength, 'A jelszónak legalább %d karakter hosszúnak kell lennie', 8) + ->addRule($form::Pattern, 'Tartalmaznia kell számjegyet', '.*[0-9].*'); ``` -Amikor újra elküldi az űrlapot, a bevitel üres lesz. Automatikusan érvényesíti az UTF-8 kódot, levágja a bal és jobb oldali fehérjeleket, és eltávolítja a támadó által küldhető sortöréseket. +Az űrlap újramegjelenítésekor a mező üres lesz. Automatikusan validálja az UTF-8 kódolást, levágja a bal- és jobboldali szóközöket, és eltávolítja azokat az újsor karaktereket, amelyeket egy támadó küldhetett. addCheckbox(string|int $name, $caption=null): Checkbox .[method] ================================================================ -Hozzáad egy jelölőnégyzetet ( [Checkbox |api:Nette\Forms\Controls\Checkbox] osztály). A mező a `true` vagy a `false` címet adja vissza, attól függően, hogy be van-e jelölve. +Hozzáad egy jelölőnégyzetet (osztály: [Checkbox |api:Nette\Forms\Controls\Checkbox]). Vagy `true` vagy `false` értéket ad vissza, attól függően, hogy be van-e jelölve. ```php -$form->addCheckbox('agree', 'I agree with terms') - ->setRequired('You must agree with our terms'); +$form->addCheckbox('agree', 'Elfogadom a feltételeket') + ->setRequired('El kell fogadni a feltételeket'); ``` -addCheckboxList(string|int $name, $label=null, array $items=null): CheckboxList .[method] -========================================================================================= +addCheckboxList(string|int $name, $label=null, ?array $items=null): CheckboxList .[method] +========================================================================================== -Több elem kiválasztására szolgáló jelölőnégyzetek listájának hozzáadása ( [CheckboxList |api:Nette\Forms\Controls\CheckboxList] osztály). Visszaadja a kiválasztott elemek kulcsainak tömbjét. A `getSelectedItems()` módszer kulcsok helyett értékeket ad vissza. +Hozzáad jelölőnégyzeteket több elem kiválasztásához (osztály: [CheckboxList |api:Nette\Forms\Controls\CheckboxList]). A kiválasztott elemek kulcsainak tömbjét adja vissza. A `getSelectedItems()` metódus az értékeket adja vissza a kulcsok helyett. ```php -$form->addCheckboxList('colors', 'Colors:', [ - 'r' => 'red', - 'g' => 'green', - 'b' => 'blue', +$form->addCheckboxList('colors', 'Színek:', [ + 'r' => 'piros', + 'g' => 'zöld', + 'b' => 'kék', ]); ``` -Az elemek tömbjét harmadik paraméterként, vagy a `setItems()` módszerrel adjuk át. +A kínált elemek tömbjét harmadik paraméterként vagy a `setItems()` metódussal adjuk át. -Használhatja a `setDisabled(['r', 'g'])` az egyes elemek letiltására. +A `setDisabled(['r', 'g'])` segítségével letilthatók az egyes elemek. -Az elem automatikusan ellenőrzi, hogy nem történt-e hamisítás, és hogy a kiválasztott elemek valóban a felkínált elemek közé tartoznak-e, és nem lettek-e letiltva. A `getRawValue()` módszerrel a beküldött elemek e fontos ellenőrzés nélkül is lekérdezhetők. +Az elem automatikusan ellenőrzi, hogy nem történt-e hamisítás, és hogy a kiválasztott elemek valóban a kínáltak közül valók-e, és nem voltak-e letiltva. A `getRawValue()` metódussal lekérhetők az elküldött elemek e fontos ellenőrzés nélkül. -Alapértelmezett értékek beállítása esetén azt is ellenőrzi, hogy azok a felkínált elemek közé tartoznak-e, ellenkező esetben kivételt dob. Ez az ellenőrzés kikapcsolható a `checkDefaultValue(false)` segítségével. +Az alapértelmezett kiválasztott elemek beállításakor is ellenőrzi, hogy azok a kínáltak közül valók-e, különben kivételt dob. Ezt az ellenőrzést a `checkDefaultValue(false)` segítségével lehet kikapcsolni. +Ha az űrlapot `GET` metódussal küldi el, választhat egy kompaktabb adatátviteli módot, amely csökkenti a query string méretét. Ez az űrlap HTML attribútumának beállításával aktiválható: + +```php +$form->setHtmlAttribute('data-nette-compact'); +``` -addRadioList(string|int $name, $label=null, array $items=null): RadioList .[method] -=================================================================================== -Rádiógombok hozzáadása ( [RadioList |api:Nette\Forms\Controls\RadioList] osztály). Visszaadja a kiválasztott elem kulcsát, vagy `null`, ha a felhasználó nem választott ki semmit. A `getSelectedItem()` metódus kulcs helyett értéket ad vissza. +addRadioList(string|int $name, $label=null, ?array $items=null): RadioList .[method] +==================================================================================== + +Hozzáad rádiógombokat (osztály: [RadioList |api:Nette\Forms\Controls\RadioList]). A kiválasztott elem kulcsát adja vissza, vagy `null`-t, ha a felhasználó nem választott semmit. A `getSelectedItem()` metódus az értéket adja vissza a kulcs helyett. ```php $sex = [ - 'm' => 'male', - 'f' => 'female', + 'm' => 'férfi', + 'f' => 'nő', ]; -$form->addRadioList('gender', 'Gender:', $sex); +$form->addRadioList('gender', 'Nem:', $sex); ``` -Az elemek tömbjét harmadik paraméterként adjuk át, vagy a `setItems()` módszerrel. +A kínált elemek tömbjét harmadik paraméterként vagy a `setItems()` metódussal adjuk át. -Használhatja a `setDisabled(['m'])` az egyes elemek letiltására. +A `setDisabled(['m', 'f'])` segítségével letilthatók az egyes elemek. -Az elem automatikusan ellenőrzi, hogy nem történt-e hamisítás, és hogy a kiválasztott elem valóban a felkínált elemek egyike-e, és nem lett-e letiltva. A `getRawValue()` módszerrel a beküldött elemet e fontos ellenőrzés nélkül is lekérdezheti. +Az elem automatikusan ellenőrzi, hogy nem történt-e hamisítás, és hogy a kiválasztott elem valóban a kínáltak közül való-e, és nem volt-e letiltva. A `getRawValue()` metódussal lekérhető az elküldött elem e fontos ellenőrzés nélkül. -Alapértelmezett érték beállítása esetén azt is ellenőrzi, hogy a felajánlott elemek egyike-e, ellenkező esetben kivételt dob. Ez az ellenőrzés kikapcsolható a `checkDefaultValue(false)` segítségével. +Az alapértelmezett kiválasztott elem beállításakor is ellenőrzi, hogy az a kínáltak közül való-e, különben kivételt dob. Ezt az ellenőrzést a `checkDefaultValue(false)` segítségével lehet kikapcsolni. -addSelect(string|int $name, $label=null, array $items=null): SelectBox .[method] -================================================================================ +addSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): SelectBox .[method] +================================================================================================== -Kijelölő doboz hozzáadása ( [SelectBox |api:Nette\Forms\Controls\SelectBox] osztály). Visszaadja a kiválasztott elem kulcsát, vagy `null`, ha a felhasználó nem választott ki semmit. A `getSelectedItem()` metódus kulcs helyett értéket ad vissza. +Hozzáad egy select boxot (osztály: [SelectBox |api:Nette\Forms\Controls\SelectBox]). A kiválasztott elem kulcsát adja vissza, vagy `null`-t, ha a felhasználó nem választott semmit. A `getSelectedItem()` metódus az értéket adja vissza a kulcs helyett. ```php $countries = [ - 'CZ' => 'Czech republic', - 'SK' => 'Slovakia', - 'GB' => 'United Kingdom', + 'CZ' => 'Cseh Köztársaság', + 'SK' => 'Szlovákia', + 'GB' => 'Nagy-Britannia', ]; -$form->addSelect('country', 'Country:', $countries) +$form->addSelect('country', 'Ország:', $countries) ->setDefaultValue('SK'); ``` -Az elemek tömbjét harmadik paraméterként adjuk át, vagy a `setItems()` módszerrel. Az elemek tömbje lehet kétdimenziós is: +A kínált elemek tömbjét harmadik paraméterként vagy a `setItems()` metódussal adjuk át. Az elemek lehetnek kétdimenziós tömbök is: ```php $countries = [ - 'Europe' => [ - 'CZ' => 'Czech republic', - 'SK' => 'Slovakia', - 'GB' => 'United Kingdom', + 'Európa' => [ + 'CZ' => 'Cseh Köztársaság', + 'SK' => 'Szlovákia', + 'GB' => 'Nagy-Britannia', ], - 'CA' => 'Canada', + 'CA' => 'Kanada', 'US' => 'USA', - '?' => 'other', + '?' => 'más', ]; ``` -A kiválasztó dobozok esetében az első elemnek gyakran különleges jelentősége van, ez a call-to-action funkciót látja el. Ilyen bejegyzés hozzáadásához használja a `setPrompt()` módszert. +A select boxoknál gyakran az első elemnek speciális jelentése van, felhívásként szolgál. Ilyen elem hozzáadására a `setPrompt()` metódus szolgál. ```php -$form->addSelect('country', 'Country:', $countries) - ->setPrompt('Pick a country'); +$form->addSelect('country', 'Ország:', $countries) + ->setPrompt('Válasszon országot'); ``` -Használhatja a `setDisabled(['CZ', 'SK'])` az egyes elemek letiltására. +A `setDisabled(['CZ', 'SK'])` segítségével letilthatók az egyes elemek. -Az elem automatikusan ellenőrzi, hogy nem történt-e hamisítás, és hogy a kiválasztott elem valóban a felkínált elemek egyike-e, és nem lett-e letiltva. A `getRawValue()` módszerrel a beküldött elemet e fontos ellenőrzés nélkül is lekérdezheti. +Az elem automatikusan ellenőrzi, hogy nem történt-e hamisítás, és hogy a kiválasztott elem valóban a kínáltak közül való-e, és nem volt-e letiltva. A `getRawValue()` metódussal lekérhető az elküldött elem e fontos ellenőrzés nélkül. -Alapértelmezett érték beállítása esetén azt is ellenőrzi, hogy a felajánlott elemek egyike-e, ellenkező esetben kivételt dob. Ez az ellenőrzés kikapcsolható a `checkDefaultValue(false)` segítségével. +Az alapértelmezett kiválasztott elem beállításakor is ellenőrzi, hogy az a kínáltak közül való-e, különben kivételt dob. Ezt az ellenőrzést a `checkDefaultValue(false)` segítségével lehet kikapcsolni. -addMultiSelect(string|int $name, $label=null, array $items=null): MultiSelectBox .[method] -========================================================================================== +addMultiSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): MultiSelectBox .[method] +============================================================================================================ -Hozzáadja a többválasztós kiválasztó dobozt ( [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox] osztály). Visszaadja a kiválasztott elemek kulcsainak tömbjét. A `getSelectedItems()` módszer kulcsok helyett értékeket ad vissza. +Hozzáad egy select boxot több elem kiválasztásához (osztály: [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox]). A kiválasztott elemek kulcsainak tömbjét adja vissza. A `getSelectedItems()` metódus az értékeket adja vissza a kulcsok helyett. ```php -$form->addMultiSelect('countries', 'Countries:', $countries); +$form->addMultiSelect('countries', 'Országok:', $countries); ``` -Az elemek tömbjét harmadik paraméterként, vagy a `setItems()` módszerrel adjuk át. Az elemek tömbje lehet kétdimenziós is. +A kínált elemek tömbjét harmadik paraméterként vagy a `setItems()` metódussal adjuk át. Az elemek lehetnek kétdimenziós tömbök is. -Használhatja a `setDisabled(['CZ', 'SK'])` az egyes elemek letiltására. +A `setDisabled(['CZ', 'SK'])` segítségével letilthatók az egyes elemek. -Az elem automatikusan ellenőrzi, hogy nem történt-e hamisítás, és hogy a kiválasztott elemek valóban a felkínált elemek közé tartoznak-e, és nem lettek-e letiltva. A `getRawValue()` módszerrel a beküldött elemek e fontos ellenőrzés nélkül is lekérdezhetők. +Az elem automatikusan ellenőrzi, hogy nem történt-e hamisítás, és hogy a kiválasztott elemek valóban a kínáltak közül valók-e, és nem voltak-e letiltva. A `getRawValue()` metódussal lekérhetők az elküldött elemek e fontos ellenőrzés nélkül. -Alapértelmezett értékek beállítása esetén azt is ellenőrzi, hogy azok a felkínált elemek közé tartoznak-e, ellenkező esetben kivételt dob. Ez az ellenőrzés kikapcsolható a `checkDefaultValue(false)` segítségével. +Az alapértelmezett kiválasztott elemek beállításakor is ellenőrzi, hogy azok a kínáltak közül valók-e, különben kivételt dob. Ezt az ellenőrzést a `checkDefaultValue(false)` segítségével lehet kikapcsolni. addUpload(string|int $name, $label=null): UploadControl .[method] ================================================================= -Fájlfeltöltő mező hozzáadása ( [UploadControl |api:Nette\Forms\Controls\UploadControl] osztály). Visszaadja a [FileUpload |http:request#FileUpload] objektumot, még akkor is, ha a felhasználó nem töltött fel fájlt, amit a `FileUpload::hasFile()` metódussal lehet kideríteni. +Hozzáad egy mezőt fájl feltöltéséhez (osztály: [UploadControl |api:Nette\Forms\Controls\UploadControl]). Egy [FileUpload |http:request#FileUpload] objektumot ad vissza, még akkor is, ha a felhasználó nem küldött fájlt, amit a `FileUpload::hasFile()` metódussal lehet ellenőrizni. ```php $form->addUpload('avatar', 'Avatar:') - ->addRule($form::Image, 'Avatar must be JPEG, PNG, GIF or WebP') - ->addRule($form::MaxFileSize, 'Maximum size is 1 MB', 1024 * 1024); + ->addRule($form::Image, 'Az avatarnak JPEG, PNG, GIF, WebP vagy AVIF formátumúnak kell lennie.') + ->addRule($form::MaxFileSize, 'A maximális méret 1 MB.', 1024 * 1024); ``` -Ha a fájl feltöltése nem volt megfelelő, akkor az űrlap nem lett sikeresen elküldve, és hibaüzenet jelenik meg. Vagyis nem szükséges a `FileUpload::isOk()` metódus ellenőrzésére. +Ha a fájl feltöltése nem sikerül megfelelően, az űrlap nem kerül sikeresen elküldésre, és hiba jelenik meg. Vagyis sikeres elküldés esetén nem szükséges ellenőrizni a `FileUpload::isOk()` metódust. -Ne bízzon a `FileUpload::getName()` módszer által visszaküldött eredeti fájlnévben, az ügyfél rosszindulatú fájlnevet küldhet azzal a szándékkal, hogy megrongálja vagy feltörje az alkalmazást. +Soha ne bízzon a `FileUpload::getName()` metódus által visszaadott eredeti fájlnévben, a kliens rosszindulatú fájlnevet küldhetett azzal a szándékkal, hogy kárt okozzon vagy feltörje az alkalmazását. -A `MimeType` és a `Image` szabályok az aláírás alapján ismerik fel a kívánt fájl- vagy képtípust. A teljes fájl sértetlenségét nem ellenőrzik. Azt, hogy egy kép nem sérült-e, például a [betöltési |http:request#toImage] próbálkozással állapíthatja meg. +A `MimeType` és `Image` szabályok a fájl aláírása alapján észlelik a kívánt típust, és nem ellenőrzik annak integritását. Azt, hogy a kép nem sérült-e, például a [betöltésének |http:request#toImage] megkísérlésével lehet megállapítani. addMultiUpload(string|int $name, $label=null): UploadControl .[method] ====================================================================== -Több fájlfeltöltő mező hozzáadása ( [UploadControl |api:Nette\Forms\Controls\UploadControl] osztály). Visszaadja a [FileUpload |http:request#FileUpload] objektumok tömbjét. A `FileUpload::hasFile()` metódus mindegyikükhöz a `true` metódust adja vissza. +Hozzáad egy mezőt több fájl egyidejű feltöltéséhez (osztály: [UploadControl |api:Nette\Forms\Controls\UploadControl]). [FileUpload |http:request#FileUpload] objektumok tömbjét adja vissza. A `FileUpload::hasFile()` metódus mindegyiknél `true`-t fog visszaadni. ```php -$form->addMultiUpload('files', 'Files:') - ->addRule($form::MaxLength, 'A maximum of %d files can be uploaded', 10); +$form->addMultiUpload('files', 'Fájlok:') + ->addRule($form::MaxLength, 'Legfeljebb %d fájlt lehet feltölteni', 10); ``` -Ha az egyik fájl feltöltése nem sikerül megfelelően, akkor az űrlapot nem sikeresen küldték el, és hibaüzenet jelenik meg. Vagyis nem szükséges ellenőrizni a `FileUpload::isOk()` metódust. +Ha valamelyik fájl feltöltése nem sikerül megfelelően, az űrlap nem kerül sikeresen elküldésre, és hiba jelenik meg. Vagyis sikeres elküldés esetén nem szükséges ellenőrizni a `FileUpload::isOk()` metódust. + +Soha ne bízzon a `FileUpload::getName()` metódus által visszaadott eredeti fájlnevekben, a kliens rosszindulatú fájlnevet küldhetett azzal a szándékkal, hogy kárt okozzon vagy feltörje az alkalmazását. + +A `MimeType` és `Image` szabályok a fájl aláírása alapján észlelik a kívánt típust, és nem ellenőrzik annak integritását. Azt, hogy a kép nem sérült-e, például a [betöltésének |http:request#toImage] megkísérlésével lehet megállapítani. + -Ne bízzon a `FileUpload::getName()` módszer által visszaküldött eredeti fájlnevekben, egy ügyfél rosszindulatú fájlnevet küldhet azzal a szándékkal, hogy megrongálja vagy feltörje az alkalmazást. +addDate(string|int $name, $label=null): DateTimeControl .[method]{data-version:3.1.14} +====================================================================================== -A `MimeType` és a `Image` szabályok az aláírás alapján ismerik fel a kívánt fájl- vagy képtípust. A teljes fájl sértetlenségét nem ellenőrzik. Azt, hogy egy kép nem sérült-e, például a [betöltési |http:request#toImage] próbálkozással állapíthatja meg. +Hozzáad egy mezőt, amely lehetővé teszi a felhasználó számára, hogy könnyen megadjon egy dátumot, amely évből, hónapból és napból áll (osztály: [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). +Alapértelmezett értékként elfogadja vagy a `DateTimeInterface` interfészt implementáló objektumokat, egy időt tartalmazó stringet, vagy egy UNIX timestamp-et képviselő számot. Ugyanez vonatkozik a `Min`, `Max` vagy `Range` szabályok argumentumaira is, amelyek meghatározzák a minimális és maximális megengedett dátumot. -addHidden(string|int $name, string $default=null): HiddenField .[method] -======================================================================== +```php +$form->addDate('date', 'Dátum:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'A dátumnak legalább egy hónaposnak kell lennie.', new DateTime('-1 month')); +``` -Rejtett mező hozzáadása ( [HiddenField |api:Nette\Forms\Controls\HiddenField] osztály). +Alapértelmezés szerint `DateTimeImmutable` objektumot ad vissza, a `setFormat()` metódussal megadhatja a [szöveges formátumot|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] vagy a timestamp-et: + +```php +$form->addDate('date', 'Dátum:') + ->setFormat('Y-m-d'); +``` + + +addTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=============================================================================================================== + +Hozzáad egy mezőt, amely lehetővé teszi a felhasználó számára, hogy könnyen megadjon egy időt, amely órákból, percekből és opcionálisan másodpercekből áll (osztály: [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Alapértelmezett értékként elfogadja vagy a `DateTimeInterface` interfészt implementáló objektumokat, egy időt tartalmazó stringet, vagy egy UNIX timestamp-et képviselő számot. Ezekből a bemenetekből csak az időinformáció kerül felhasználásra, a dátum figyelmen kívül marad. Ugyanez vonatkozik a `Min`, `Max` vagy `Range` szabályok argumentumaira is, amelyek meghatározzák a minimális és maximális megengedett időt. Ha a beállított minimális érték magasabb, mint a maximális, akkor egy éjfélen átnyúló időtartomány jön létre. + +```php +$form->addTime('time', 'Idő:', withSeconds: true) + ->addRule($form::Range, 'Az időnek %d és %d között kell lennie.', ['12:30', '13:30']); +``` + +Alapértelmezés szerint `DateTimeImmutable` objektumot ad vissza (az 1. év január 1-jei dátummal), a `setFormat()` metódussal megadhatja a [szöveges formátumot|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters]: + +```php +$form->addTime('time', 'Idő:') + ->setFormat('H:i'); +``` + + +addDateTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=================================================================================================================== + +Hozzáad egy mezőt, amely lehetővé teszi a felhasználó számára, hogy könnyen megadjon egy dátumot és időt, amely évből, hónapból, napból, órákból, percekből és opcionálisan másodpercekből áll (osztály: [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Alapértelmezett értékként elfogadja vagy a `DateTimeInterface` interfészt implementáló objektumokat, egy időt tartalmazó stringet, vagy egy UNIX timestamp-et képviselő számot. Ugyanez vonatkozik a `Min`, `Max` vagy `Range` szabályok argumentumaira is, amelyek meghatározzák a minimális és maximális megengedett dátumot. + +```php +$form->addDateTime('datetime', 'Dátum és idő:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'A dátumnak legalább egy hónaposnak kell lennie.', new DateTime('-1 month')); +``` + +Alapértelmezés szerint `DateTimeImmutable` objektumot ad vissza, a `setFormat()` metódussal megadhatja a [szöveges formátumot|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] vagy a timestamp-et: + +```php +$form->addDateTime('datetime') + ->setFormat(DateTimeControl::FormatTimestamp); +``` + + +addColor(string|int $name, $label=null): ColorPicker .[method]{data-version:3.1.14} +=================================================================================== + +Hozzáad egy mezőt színválasztáshoz (osztály: [ColorPicker |api:Nette\Forms\Controls\ColorPicker]). A szín egy `#rrggbb` formátumú string. Ha a felhasználó nem választ, a fekete szín `#000000` kerül visszaadásra. + +```php +$form->addColor('color', 'Szín:') + ->setDefaultValue('#3C8ED7'); +``` + + +addHidden(string|int $name, ?string $default=null): HiddenField .[method] +========================================================================= + +Hozzáad egy rejtett mezőt (osztály: [HiddenField |api:Nette\Forms\Controls\HiddenField]). ```php $form->addHidden('userid'); ``` -Használja a `setNullable()` címet, hogy üres karakterlánc helyett `null` -t adjon vissza. Az [addFilter() |validation#Modifying Input Values] lehetővé teszi a beküldött érték megváltoztatását. +A `setNullable()` segítségével beállítható, hogy `null`-t adjon vissza üres string helyett. Az elküldött érték módosítását az [addFilter() |validation#Bemenet módosítása] teszi lehetővé. + +Bár az elem rejtett, **fontos tudatosítani**, hogy az értékét egy támadó továbbra is módosíthatja vagy hamisíthatja. Mindig alaposan ellenőrizze és validálja az összes fogadott értéket a szerveroldalon, hogy megelőzze az adatmanipulációval kapcsolatos biztonsági kockázatokat. addSubmit(string|int $name, $caption=null): SubmitButton .[method] ================================================================== -Hozzáadja a submit gombot ( [SubmitButton |api:Nette\Forms\Controls\SubmitButton] osztály). +Hozzáad egy küldés gombot (osztály: [SubmitButton |api:Nette\Forms\Controls\SubmitButton]). ```php -$form->addSubmit('submit', 'Register'); +$form->addSubmit('submit', 'Küldés'); ``` -Lehetséges, hogy egynél több submit gomb legyen az űrlapon: +Az űrlapon több küldés gomb is lehet: ```php -$form->addSubmit('register', 'Register'); -$form->addSubmit('cancel', 'Cancel'); +$form->addSubmit('register', 'Regisztráció'); +$form->addSubmit('cancel', 'Mégse'); ``` -Ahhoz, hogy megtudja, melyikre kattintottak, használja a következőt: +Annak megállapításához, hogy melyikre kattintottak, használja a következőt: ```php if ($form['register']->isSubmittedBy()) { @@ -282,48 +377,48 @@ if ($form['register']->isSubmittedBy()) { } ``` -Ha nem akarja érvényesíteni az űrlapot, amikor egy beküldő gombot megnyomnak (például a *Cancel* vagy *Preview* gombokat), akkor a [setValidationScope() |validation#Disabling Validation] segítségével kikapcsolhatja. +Ha nem szeretné az egész űrlapot validálni a gomb megnyomásakor (például a *Mégse* vagy *Előnézet* gomboknál), használja a [setValidationScope() |validation#Validáció kikapcsolása] metódust. addButton(string|int $name, $caption): Button .[method] ======================================================= -Hozzáad egy gombot ( [Button |api:Nette\Forms\Controls\Button] osztály) submit funkció nélkül. Hasznos más funkciók id-hez kötéséhez, például egy JavaScript művelethez. +Hozzáad egy gombot (osztály: [Button |api:Nette\Forms\Controls\Button]), amelynek nincs küldési funkciója. Használható tehát valamilyen más funkcióra, pl. JavaScript függvény meghívására kattintáskor. ```php -$form->addButton('raise', 'Raise salary') +$form->addButton('raise', 'Fizetésemelés') ->setHtmlAttribute('onclick', 'raiseSalary()'); ``` -addImageButton(string|int $name, string $src=null, string $alt=null): ImageButton .[method] -=========================================================================================== +addImageButton(string|int $name, ?string $src=null, ?string $alt=null): ImageButton .[method] +============================================================================================= -Hozzáadja a submit gombot kép formájában ( [ImageButton |api:Nette\Forms\Controls\ImageButton] osztály). +Hozzáad egy kép formájú küldés gombot (osztály: [ImageButton |api:Nette\Forms\Controls\ImageButton]). ```php -$form->addImageButton('submit', '/path/to/image'); +$form->addImageButton('submit', '/útvonal/a/képhez.png'); ``` -Több submit gomb használata esetén megtudhatja, hogy melyikre kattintott a `$form['submit']->isSubmittedBy()`. +Több küldés gomb használatakor a `$form['submit']->isSubmittedBy()` segítségével megállapítható, hogy melyikre kattintottak. addContainer(string|int $name): Container .[method] =================================================== -Hozzáad egy al-űrlapot ( [Container |api:Nette\Forms\Container] osztály), vagy konténert, amely ugyanúgy kezelhető, mint egy űrlap. Ez azt jelenti, hogy használhatja az olyan módszereket, mint a `setDefaults()` vagy a `getValues()`. +Hozzáad egy alűrlapot (osztály: [Container|api:Nette\Forms\Container]), vagyis egy konténert, amelybe ugyanúgy lehet további elemeket hozzáadni, mint ahogy az űrlaphoz adjuk őket. Működnek a `setDefaults()` vagy `getValues()` metódusok is. ```php $sub1 = $form->addContainer('first'); -$sub1->addText('name', 'Your name:'); +$sub1->addText('name', 'Neved:'); $sub1->addEmail('email', 'Email:'); $sub2 = $form->addContainer('second'); -$sub2->addText('name', 'Your name:'); +$sub2->addText('name', 'Neved:'); $sub2->addEmail('email', 'Email:'); ``` -Az elküldött adatokat ezután többdimenziós struktúraként adja vissza: +Az elküldött adatok ezután többdimenziós struktúraként kerülnek visszaadásra: ```php [ @@ -339,99 +434,101 @@ Az elküldött adatokat ezután többdimenziós struktúraként adja vissza: ``` -A beállítások áttekintése .[#toc-overview-of-settings] -====================================================== +Beállítások áttekintése +======================= -Minden elemhez a következő metódusokat hívhatjuk meg (a teljes áttekintésért lásd az [API dokumentációt |https://api.nette.org/forms/master/Nette/Forms/Controls.html] ): +Minden elemnél meghívhatjuk a következő metódusokat (teljes áttekintés az [API dokumentációban|https://api.nette.org/forms/master/Nette/Forms/Controls.html]): .[table-form-methods language-php] -| `setDefaultValue($value)` | az alapértelmezett érték beállítása -| `getValue()` | aktuális érték lekérése -| `setOmitted()` | [kihagyott értékek |#omitted values] -| `setDisabled()` | [bemenetek letiltása |#disabling inputs] +| `setDefaultValue($value)` | beállítja az alapértelmezett értéket +| `getValue()` | lekéri az aktuális értéket +| `setOmitted()` | [#Érték kihagyása] +| `setDisabled()` | [#Elemek letiltása] -Renderelés: +Megjelenítés: .[table-form-methods language-php] -| `setCaption($caption)`| az elem feliratának módosítása -| `setTranslator($translator)` | [fordító beállítása|rendering#translating] -| `setHtmlAttribute($name, $value)` | beállítja az elem [HTML-attribútumát |rendering#HTML attributes]. -| `setHtmlId($id)` | beállítja a HTML-attribútumot `id` -| `setHtmlType($type)` | HTML-attribútum beállítása `type` -| `setHtmlName($name)`| HTML-attribútum beállítása `name` -| `setOption($key, $value)` | beállítja [a renderelési adatokat |rendering#Options] - -Érvényesítés: +| `setCaption($caption)` | megváltoztatja az elem címkéjét +| `setTranslator($translator)` | beállítja a [fordítót |rendering#Fordítás] +| `setHtmlAttribute($name, $value)` | beállítja az elem [HTML attribútumát |rendering#HTML attribútumok] +| `setHtmlId($id)` | beállítja a HTML `id` attribútumot +| `setHtmlType($type)` | beállítja a HTML `type` attribútumot +| `setHtmlName($name)` | beállítja a HTML `name` attribútumot +| `setOption($key, $value)` | [megjelenítési beállítások |rendering#Options] + +Validáció: .[table-form-methods language-php] -| `setRequired()` | [kötelező mező |validation] -| `addRule()` | [érvényesítési szabály beállítása|validation#Rules] -| `addCondition()`, `addConditionOn()` | [érvényesítési feltétel beállítása|validation#Conditions] -| `addError($message)`| [hibaüzenet átadása |validation#processing-errors] +| `setRequired()` | [kötelező elem |validation] +| `addRule()` | beállítja az [érvényesítési szabályt |validation#Szabályok] +| `addCondition()`, `addConditionOn()` | beállítja az [érvényesítési feltételt |validation#Feltételek] +| `addError($message)` | [hibaüzenet átadása |validation#Hibák a feldolgozás során] -A következő metódusok hívhatók a `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` elemekhez: +Az `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` elemeknél a következő metódusokat lehet meghívni: .[table-form-methods language-php] -| `setNullable()`| beállítja, hogy a getValue() az üres karakterlánc helyett a `null` értéket adja-e vissza. -| `setEmptyValue($value)` | beállítja a speciális értéket, amelyet üres karakterláncként kezel. -| `setMaxLength($length)`| beállítja a megengedett karakterek maximális számát. -| `addFilter($filter)`| [bemeneti értékek módosítása |validation#Modifying Input Values] +| `setNullable()` | beállítja, hogy a getValue() `null`-t adjon-e vissza üres string helyett +| `setEmptyValue($value)` | beállít egy speciális értéket, amely üres stringnek minősül +| `setMaxLength($length)` | beállítja a megengedett karakterek maximális számát +| `addFilter($filter)` | [bemenet módosítása |validation#Bemenet módosítása] -Kihagyott értékek .[#toc-omitted-values] ----------------------------------------- +Érték kihagyása +=============== -Ha nem érdekel a felhasználó által megadott érték, a `setOmitted()` segítségével kihagyhatjuk azt a `$form->getValues​()` metódus által szolgáltatott vagy a kezelőknek átadott eredményből. Ez alkalmas különböző jelszavak ellenőrzésére, spamellenes mezőkhöz stb. +Ha nem érdekel minket a felhasználó által kitöltött érték, a `setOmitted()` segítségével kihagyhatjuk a `$form->getValues()` metódus eredményéből vagy a handlereknek átadott adatokból. Ez hasznos lehet különböző ellenőrző jelszavaknál, antispam elemeknél stb. ```php -$form->addPassword('passwordVerify', 'Password again:') - ->setRequired('Fill your password again to check for typo') - ->addRule($form::Equal, 'Password mismatch', $form['password']) +$form->addPassword('passwordVerify', 'Jelszó újra:') + ->setRequired('Kérjük, adja meg a jelszót újra az ellenőrzéshez') + ->addRule($form::Equal, 'A jelszavak nem egyeznek', $form['password']) ->setOmitted(); ``` -Bemenetek letiltása .[#toc-disabling-inputs] --------------------------------------------- +Elemek letiltása +================ -Egy bemenet letiltásához hívja a `setDisabled()` címet. Az ilyen mezőt a felhasználó nem szerkesztheti. +Az elemeket a `setDisabled()` segítségével lehet letiltani. Egy ilyen elemet a felhasználó nem tud szerkeszteni. ```php -$form->addText('username', 'User name:') +$form->addText('username', 'Felhasználónév:') ->setDisabled(); ``` -Vegye figyelembe, hogy a böngésző egyáltalán nem küldi el a letiltott mezőket a szerverre, így a `$form->getValues()` függvény által visszaküldött adatokban sem fogja megtalálni őket. +A letiltott elemeket a böngésző egyáltalán nem küldi el a szerverre, tehát nem is találja meg őket a `$form->getValues()` függvény által visszaadott adatokban. Ha azonban beállítja a `setOmitted(false)` értéket, a Nette ezekbe az adatokba belefoglalja az alapértelmezett értéküket. -Ha alapértelmezett értéket állít be egy mezőhöz, akkor ezt csak a mező letiltása után szabad megtennie: +A `setDisabled()` hívásakor biztonsági okokból **törlődik az elem értéke**. Ha alapértelmezett értéket állít be, azt a letiltás után kell megtenni: ```php -$form->addText('username', 'User name:') +$form->addText('username', 'Felhasználónév:') ->setDisabled() ->setDefaultValue($userName); ``` +A letiltott elemek alternatívája a `readonly` HTML attribútummal rendelkező elemek, amelyeket a böngésző elküld a szerverre. Bár az elem csak olvasható, **fontos tudatosítani**, hogy az értékét egy támadó továbbra is módosíthatja vagy hamisíthatja. + -Egyéni vezérlők .[#toc-custom-controls] -======================================= +Egyéni elemek +============= -A beépített űrlapvezérlők széles skálája mellett egyéni vezérlőket is hozzáadhat az űrlaphoz az alábbiak szerint: +A beépített űrlap elemek széles skálája mellett egyéni elemeket is hozzáadhat az űrlaphoz a következő módon: ```php -$form->addComponent(new DateInput('Date:'), 'date'); -// alternatív szintaxis: $form['date'] = new DateInput('Date:'); +$form->addComponent(new DateInput('Dátum:'), 'date'); +// alternatív szintaxis: $form['date'] = new DateInput('Dátum:'); ``` .[note] -A form a [Container | component-model:#Container] osztály leszármazottja, az elemek pedig a [Component | component-model:#Component] osztály leszármazottai. +Az űrlap a [Container |component-model:#Container] osztály leszármazottja, az egyes elemek pedig a [Component |component-model:#Component] leszármazottai. -Van lehetőség új űrlapmódszerek definiálására az egyéni elemek hozzáadásához (pl. `$form->addZip()`). Ezek az úgynevezett kiterjesztési metódusok. Hátrányuk, hogy a szerkesztőkben a kódsegédletek nem működnek ezeknél. +Létezik egy módszer új űrlap metódusok definiálására, amelyek egyéni elemek hozzáadására szolgálnak (pl. `$form->addZip()`). Ezek az ún. extension methods. Hátrányuk, hogy a szerkesztőkben nem fog működni rájuk a kódkiegészítés. ```php use Nette\Forms\Container; -// adds method addZip(string $name, string $label = null) -Container::extensionMethod('addZip', function (Container $form, string $name, string $label = null) { +// hozzáadjuk az addZip(string $name, ?string $label = null) metódust +Container::extensionMethod('addZip', function (Container $form, string $name, ?string $label = null) { return $form->addText($name, $label) - ->addRule($form::Pattern, 'Legalább 5 szám', '[0-9]{5}'); + ->addRule($form::Pattern, 'Legalább 5 számjegy', '[0-9]{5}'); }); // használat @@ -439,10 +536,10 @@ $form->addZip('zip', 'Irányítószám:'); ``` -Alacsony szintű mezők .[#toc-low-level-fields] -============================================== +Alacsony szintű elemek +====================== -Egy elem hozzáadásához az űrlaphoz nem kell meghívni a `$form->addXyz()` címet. Az űrlapelemeket ehelyett kizárólag sablonokban lehet bevezetni. Ez akkor hasznos, ha például dinamikus elemeket kell létrehozni: +Használhatunk olyan elemeket is, amelyeket csak a sablonban írunk le, és nem adjuk hozzá az űrlaphoz valamelyik `$form->addXyz()` metódussal. Ha például adatbázisból listázunk rekordokat, és előre nem tudjuk, hány lesz belőlük és milyen ID-jük lesz, és minden sornál szeretnénk egy checkboxot vagy radio buttont megjeleníteni, elég azt a sablonban kódolni: ```latte {foreach $items as $item} @@ -450,13 +547,13 @@ Egy elem hozzáadásához az űrlaphoz nem kell meghívni a `$form->addXyz()` c {/foreach} ``` -A beküldés után lekérdezheti az értékeket: +És elküldés után lekérjük az értéket: ```php $data = $form->getHttpData($form::DataText, 'sel[]'); $data = $form->getHttpData($form::DataText | $form::DataKeys, 'sel[]'); ``` -Az első paraméterben megadhatja az elem típusát (`DataFile` a `type=file`, `DataLine` az egysoros bemenetekhez, mint a `text`, `password` vagy `email` és `DataText` a többi). A második paraméter megfelel a `name` HTML-attribútumnak. Ha meg kell őrizni a kulcsokat, akkor az első paramétert kombinálhatja a `DataKeys` paraméterrel. Ez a `select`, `radioList` vagy `checkboxList` esetében hasznos. +ahol az első paraméter az elem típusa (`DataFile` a `type=file`-hoz, `DataLine` az egysoros bemenetekhez, mint `text`, `password`, `email` stb., és `DataText` az összes többihez), a második paraméter `sel[]` pedig a HTML `name` attribútumnak felel meg. Az elem típusát kombinálhatjuk a `DataKeys` értékkel, amely megőrzi az elemek kulcsait. Ez különösen hasznos a `select`, `radioList` és `checkboxList` esetén. -A `getHttpData()` szanált bemenetet ad vissza. Ebben az esetben mindig érvényes UTF-8-as karakterláncok tömbje lesz, függetlenül attól, hogy az űrlap által küldött támadó mit küldött. Ez egy alternatívája a `$_POST` vagy `$_GET` közvetlen munkájának, ha biztonságos adatokat szeretne kapni. +Lényeges, hogy a `getHttpData()` szanitizált értéket ad vissza, ebben az esetben ez mindig érvényes UTF-8 stringek tömbje lesz, függetlenül attól, hogy a támadó mit próbált a szervernek becsempészni. Ez hasonló a közvetlen `$_POST` vagy `$_GET` kezeléséhez, azzal a lényeges különbséggel, hogy mindig tiszta adatokat ad vissza, ahogy azt a standard Nette űrlap elemeknél megszokhattuk. diff --git a/forms/hu/in-presenter.texy b/forms/hu/in-presenter.texy index d3bc0884a4..5fe623b39d 100644 --- a/forms/hu/in-presenter.texy +++ b/forms/hu/in-presenter.texy @@ -1,36 +1,36 @@ -Formanyomtatványok az előadóknál -******************************** +Űrlapok presenterekben +********************** .[perex] -A Nette Forms jelentősen megkönnyíti a webes űrlapok létrehozását és feldolgozását. Ebben a fejezetben megtanulja, hogyan használhatja az űrlapokat a prezentereken belül. +A Nette Forms rendkívül megkönnyíti a webes űrlapok létrehozását és feldolgozását. Ebben a fejezetben megismerkedhet az űrlapok használatával a presentereken belül. -Ha teljesen önállóan, a keretrendszer többi része nélkül szeretné használni őket, akkor van egy útmutató az [önálló űrlapokhoz |standalone]. +Ha érdekli, hogyan használhatja őket teljesen önállóan a keretrendszer többi része nélkül, akkor az [önálló használat|standalone] útmutatója Önnek szól. -Első űrlap .[#toc-first-form] -============================= +Első űrlap +========== -Megpróbálunk írni egy egyszerű regisztrációs űrlapot. A kódja így fog kinézni: +Próbáljunk meg írni egy egyszerű regisztrációs űrlapot. A kódja a következő lesz: ```php use Nette\Application\UI\Form; $form = new Form; -$form->addText('name', 'Name:'); -$form->addPassword('password', 'Password:'); -$form->addSubmit('send', 'Sign up'); +$form->addText('name', 'Név:'); +$form->addPassword('password', 'Jelszó:'); +$form->addSubmit('send', 'Regisztráció'); $form->onSuccess[] = [$this, 'formSucceeded']; ``` -A böngészőben az eredménynek így kell kinéznie: +és a böngészőben így jelenik meg: -[* form-en.webp *] +[* form-cs.webp *] -A prezenterben lévő űrlap a `Nette\Application\UI\Form` osztály objektuma, elődje a `Nette\Forms\Form` önálló használatra készült. Hozzáadtuk a név, a jelszó és a küldés gomb mezőit. Végül a `$form->onSuccess` sorban az áll, hogy a beküldés és a sikeres érvényesítés után a `$this->formSucceeded()` metódust kell meghívni. +Az űrlap a presenterben egy `Nette\Application\UI\Form` osztály objektuma, elődje, a `Nette\Forms\Form` önálló használatra készült. Hozzáadtunk ún. név, jelszó elemeket és egy küldés gombot. Végül a `$form->onSuccess` sor azt mondja, hogy elküldés és sikeres validálás után meg kell hívni a `$this->formSucceeded()` metódust. -A bemutató szempontjából az űrlap egy közös komponens. Ezért komponensként kezeljük, és a [factory metódus |application:components#Factory Methods] segítségével beépítjük a prezentálóba. Ez így fog kinézni: +A presenter szempontjából az űrlap egy szokásos komponens. Ezért komponensként kezeljük, és a presenterbe egy [factory metódus |application:components#Factory metódusok] segítségével illesztjük be. Ez így fog kinézni: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} use Nette; use Nette\Application\UI\Form; @@ -41,14 +41,14 @@ class HomePresenter extends Nette\Application\UI\Presenter $form = new Form; $form->addText('name', 'Név:'); $form->addPassword('password', 'Jelszó:'); - $form->addSubmit('send', 'Regisztrálj'); + $form->addSubmit('send', 'Regisztráció'); $form->onSuccess[] = [$this, 'formSucceeded']; return $form; } public function formSucceeded(Form $form, $data): void { - // itt fogjuk feldolgozni az űrlap által küldött adatokat. + // itt dolgozzuk fel az űrlappal küldött adatokat // $data->name tartalmazza a nevet // $data->password tartalmazza a jelszót $this->flashMessage('Sikeresen regisztrált.'); @@ -57,85 +57,82 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -A sablonban történő megjelenítés pedig a `{control}` tag használatával történik: +És a sablonban az űrlapot a `{control}` taggel jelenítjük meg: -```latte .{file:app/Presenters/templates/Home/default.latte} -<h1>Registration</h1> +```latte .{file:app/Presentation/Home/default.latte} +<h1>Regisztráció</h1> {control registrationForm} ``` -És ez minden :-) Van egy működőképes és tökéletesen [biztonságos |#Vulnerability Protection] űrlapunk. +És ez tulajdonképpen minden :-) Van egy működő és tökéletesen [biztonságos |#Védelem a sebezhetőségekkel szemben] űrlapunk. -Most valószínűleg azt gondolod, hogy ez túl gyors volt, és azon tűnődsz, hogy hogyan lehetséges, hogy a `formSucceeded()` metódus meghívásra kerül, és milyen paramétereket kap. Persze, igazad van, ez megérdemel egy magyarázatot. +És most valószínűleg azt gondolja, hogy ez túl gyors volt, azon tűnődik, hogyan lehetséges, hogy meghívódik a `formSucceeded()` metódus, és mik azok a paraméterek, amelyeket kap. Persze, igaza van, ez magyarázatot érdemel. -Nette klassz mechanizmussal állt elő, amit [hollywoodi stílusnak |application:components#Hollywood style] nevezünk. Ahelyett, hogy állandóan kérdezgetni kellene, hogy történt-e valami ("elküldték-e az űrlapot?", "érvényesen elküldték-e?" vagy "nem hamisították-e?"), azt mondod a keretrendszernek, hogy "ha az űrlap érvényesen kitöltött, hívd meg ezt a metódust", és hagyd rajta a további munkát. Ha JavaScriptben programozol, akkor ismered ezt a programozási stílust. Olyan függvényeket írsz, amelyeket akkor hívsz meg, amikor egy bizonyos [esemény |nette:glossary#Events] bekövetkezik. A nyelv pedig átadja nekik a megfelelő argumentumokat. +A Nette ugyanis egy friss mechanizmussal érkezik, amelyet [Hollywood style |application:components#Hollywood style]-nak nevezünk. Ahelyett, hogy fejlesztőként állandóan kérdezgetnie kellene, hogy történt-e valami („el lett küldve az űrlap?”, „érvényesen lett elküldve?” és „nem hamisították-e meg?”), azt mondja a keretrendszernek: „amikor az űrlap érvényesen ki van töltve, hívd meg ezt a metódust”, és a további munkát ráhagyja. Ha JavaScriptben programozik, ezt a programozási stílust jól ismeri. Függvényeket ír, amelyek akkor hívódnak meg, amikor egy bizonyos [esemény |nette:glossary#Eventek események] bekövetkezik. És a nyelv átadja nekik a megfelelő argumentumokat. -Így épül fel a fenti prezenter kódja. A `$form->onSuccess` array a PHP visszahívások listáját jelenti, amelyeket a Nette akkor hív meg, ha az űrlapot elküldték és helyesen kitöltötték. -A [prezenter életciklusán |application:presenters#Life Cycle of Presenter] belül ez egy úgynevezett szignál, tehát a `action*` metódus után és a `render*` metódus előtt hívódnak meg. -És minden callbacknek átadja magát az űrlapot az első paraméterben, és a beküldött adatokat [ArrayHash |utils:arrays#ArrayHash] objektumként a másodikban. Az első paramétert elhagyhatjuk, ha nincs szükségünk a form objektumra. A második paraméter még hasznosabb lehet, de erről [később |#Mapping to Classes]. +Pontosan így épül fel a fenti presenter kód is. A `$form->onSuccess` tömb PHP callbackek listáját képviseli, amelyeket a Nette akkor hív meg, amikor az űrlap elküldésre kerül és helyesen van kitöltve (azaz érvényes). A [presenter életciklusa |application:presenters#Presenter életciklusa] keretében ez egy ún. signal, tehát az `action*` metódus után és a `render*` metódus előtt hívódnak meg. És minden callbacknek átadja első paraméterként magát az űrlapot, második paraméterként pedig az elküldött adatokat egy [ArrayHash |utils:arrays#ArrayHash] objektum formájában. Az első paramétert kihagyhatja, ha nincs szüksége az űrlap objektumra. A második paraméter pedig lehet okosabb, de erről majd [később |#Leképezés osztályokra]. -A `$data` objektum tartalmazza a `name` és `password` tulajdonságokat a felhasználó által megadott adatokkal. Általában az adatokat közvetlenül elküldjük további feldolgozásra, ami lehet például az adatbázisba való beillesztés. A feldolgozás során azonban előfordulhat hiba, például a felhasználónév már foglalt. Ebben az esetben a `addError()` segítségével visszaadjuk a hibát az űrlapnak, és hagyjuk, hogy újra kirajzolódjon, hibaüzenettel: +A `$data` objektum tartalmazza a `name` és `password` kulcsokat azokkal az adatokkal, amelyeket a felhasználó kitöltött. Általában az adatokat azonnal továbbítjuk további feldolgozásra, ami lehet például adatbázisba való beszúrás. A feldolgozás során azonban hiba léphet fel, például a felhasználónév már foglalt. Ebben az esetben a hibát visszaküldjük az űrlapnak az `addError()` segítségével, és hagyjuk újra megjeleníteni, a hibaüzenettel együtt. ```php -$form->addError('Sorry, username is already in use.'); +$form->addError('Sajnáljuk, a felhasználónév már foglalt.'); ``` -A `onSuccess` mellett létezik a `onSubmit` is : a visszahívások mindig az űrlap elküldése után hívódnak meg, még akkor is, ha az űrlap nem lett helyesen kitöltve. És végül a `onError`: a callbackek csak akkor hívódnak meg, ha a beküldés nem érvényes. Ezek még akkor is meghívódnak, ha a `onSuccess` vagy a `onSubmit` segítségével érvénytelenítjük az űrlapot a `addError()` segítségével. +Az `onSuccess` mellett létezik még az `onSubmit`: a callbackek mindig az űrlap elküldése után hívódnak meg, akkor is, ha nincs helyesen kitöltve. Továbbá az `onError`: a callbackek csak akkor hívódnak meg, ha az elküldés nem érvényes. Akkor is meghívódnak, ha az `onSuccess` vagy `onSubmit` során érvénytelenítjük az űrlapot az `addError()` segítségével. -Az űrlap feldolgozása után átirányítjuk a következő oldalra. Ez megakadályozza, hogy az űrlapot véletlenül újra beküldjék a *frissítés*, *vissza* gombra kattintva, vagy a böngésző előzményeit mozgatva. +Az űrlap feldolgozása után átirányítunk a következő oldalra. Ez megakadályozza az űrlap nem kívánt újraküldését a *frissítés*, *vissza* gombbal vagy a böngésző előzményeiben való mozgással. -Próbáljon meg több [űrlapvezérlőt |controls] hozzáadni. +Próbáljon meg hozzáadni további [űrlap elemeket|controls] is. -Hozzáférés a vezérlőkhöz .[#toc-access-to-controls] -=================================================== +Elemekhez való hozzáférés +========================= -Az űrlap a prezenter egyik komponense, esetünkben a `registrationForm` névvel (a `createComponentRegistrationForm` gyári metódus neve után), így a prezenterben bárhol elérhetjük az űrlapot a következővel: +Az űrlap a presenter komponense, esetünkben `registrationForm` néven (a `createComponentRegistrationForm` factory metódus neve alapján), így bárhol a presenterben hozzáférhet az űrlaphoz a következőképpen: ```php $form = $this->getComponent('registrationForm'); // alternatív szintaxis: $form = $this['registrationForm']; ``` -Az egyes űrlapvezérlők is komponensek, így ugyanígy elérhetjük őket: +Az egyes űrlap elemek is komponensek, ezért ugyanúgy hozzáférhet hozzájuk: ```php $input = $form->getComponent('name'); // vagy $input = $form['name']; $button = $form->getComponent('send'); // vagy $button = $form['send']; ``` -A vezérlőelemek eltávolítása az unset használatával történik: +Az elemeket az `unset` segítségével távolíthatja el: ```php unset($form['name']); ``` -Érvényesítési szabályok .[#toc-validation-rules] -================================================ +Validálási szabályok +==================== -Az *érvényes* szót többször használták, de az űrlapnak még nincsenek érvényesítési szabályai. Javítsuk ki. +Elhangzott a *valid* szó, de az űrlapnak még nincsenek validálási szabályai. Javítsuk ezt ki. -A név kötelező lesz, ezért jelöljük meg a `setRequired()` metódussal, amelynek argumentuma a hibaüzenet szövege, amely akkor jelenik meg, ha a felhasználó nem tölti ki. Ha nem adunk meg argumentumot, akkor az alapértelmezett hibaüzenetet használjuk. +A név kötelező lesz, ezért megjelöljük a `setRequired()` metódussal, amelynek argumentuma a hibaüzenet szövege, amely akkor jelenik meg, ha a felhasználó nem tölti ki a nevet. Ha nem adunk meg argumentumot, az alapértelmezett hibaüzenet kerül felhasználásra. ```php -$form->addText('name', 'Name:') - ->setRequired('Please fill your name.'); +$form->addText('name', 'Név:') + ->setRequired('Kérjük, adja meg a nevét'); ``` -Próbáljuk meg elküldeni az űrlapot a név kitöltése nélkül, és látni fogjuk, hogy hibaüzenet jelenik meg, és a böngésző vagy a szerver elutasítja a kitöltésig. +Próbálja meg elküldeni az űrlapot kitöltetlen névvel, és látni fogja, hogy megjelenik a hibaüzenet, és a böngésző vagy a szerver elutasítja, amíg ki nem tölti a mezőt. -Ugyanakkor nem fogja tudni becsapni a rendszert azzal, hogy például csak szóközöket ír be a beviteli mezőbe. Szó sem lehet róla. A Nette automatikusan levágja a bal és jobb oldali szóközöket. Próbálja ki! Ezt mindig meg kellene tennie minden egysoros bevitelnél, de gyakran elfelejtik. A Nette automatikusan elvégzi. (Megpróbálhatja becsapni az űrlapokat, és többsoros karakterláncot küldhet névként. A Nette még ebben az esetben sem fog becsapni, és a sortörések szóközökre változnak). +Ugyanakkor a rendszert nem csaphatja be azzal, hogy a mezőbe például csak szóközöket ír. Dehogy. A Nette automatikusan eltávolítja a bal- és jobboldali szóközöket. Próbálja ki. Ez egy olyan dolog, amit minden egysoros inputtal mindig meg kellene tennie, de gyakran elfelejtik. A Nette ezt automatikusan megteszi. (Megpróbálhatja becsapni az űrlapot, és névként többsoros stringet küldeni. A Nette itt sem hagyja magát megtéveszteni, és az újsorokat szóközökre cseréli.) -Az űrlap mindig a szerveroldalon validálódik, de a JavaScript validáció is generálódik, ami gyors, és a felhasználó azonnal értesül a hibáról, anélkül, hogy az űrlapot a szerverre kellene küldeni. Ezt a `netteForms.js` szkript kezeli. -Ezt illessze be az elrendezési sablonba: +Az űrlap mindig a szerveroldalon validálódik, de JavaScript validáció is generálódik, amely villámgyorsan lefut, és a felhasználó azonnal értesül a hibáról, anélkül, hogy az űrlapot el kellene küldenie a szerverre. Ezt a `netteForms.js` szkript végzi. Illessze be a layout sablonba: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Ha megnézi az űrlapot tartalmazó oldal forráskódját, észreveheti, hogy a Nette a szükséges mezőket a `required` CSS osztályú elemekbe illeszti be. Próbálja meg a következő stílust hozzáadni a sablonhoz, és a "Név" felirat piros lesz. Elegánsan jelöljük a kötelező mezőket a felhasználók számára: +Ha megnézi az űrlapot tartalmazó oldal forráskódját, észreveheti, hogy a Nette a kötelező elemeket `required` CSS osztállyal rendelkező elemekbe helyezi. Próbálja meg hozzáadni a következő stíluslapot a sablonhoz, és a „Név” címke piros lesz. Elegánsan jelöljük így a felhasználóknak a kötelező elemeket: ```latte <style> @@ -143,96 +140,96 @@ Ha megnézi az űrlapot tartalmazó oldal forráskódját, észreveheti, hogy a </style> ``` -További érvényesítési szabályokat a `addRule()` módszerrel adunk hozzá. Az első paraméter a szabály, a második ismét a hibaüzenet szövege, és az opcionális érvényesítési szabály argumentum következhet. Mit jelent ez? +További validálási szabályokat az `addRule()` metódussal adunk hozzá. Az első paraméter a szabály, a második ismét a hibaüzenet szövege, és még következhet a validálási szabály argumentuma. Mit jelent ez? -Az űrlap kap egy másik opcionális bemenetet *életkor* azzal a feltétellel, hogy annak számnak kell lennie (`addInteger()`) és bizonyos határok között (`$form::Range`). És itt fogjuk használni a `addRule()` harmadik argumentumát , magát a tartományt: +Az űrlapot kibővítjük egy új, nem kötelező „életkor” mezővel, amelynek egész számnak kell lennie (`addInteger()`), és ráadásul a megengedett tartományban (`$form::Range`). És itt pontosan kihasználjuk az `addRule()` metódus harmadik paraméterét, amellyel átadjuk a validátornak a kívánt tartományt egy `[tól, ig]` párként: ```php -$form->addInteger('age', 'Age:') - ->addRule($form::Range, 'You must be older 18 years and be under 120.', [18, 120]); +$form->addInteger('age', 'Életkor:') + ->addRule($form::Range, 'Az életkornak 18 és 120 között kell lennie', [18, 120]); ``` .[tip] -Ha a felhasználó nem tölti ki a mezőt, az érvényesítési szabályok nem lesznek ellenőrizve, mivel a mező opcionális. +Ha a felhasználó nem tölti ki a mezőt, a validálási szabályok nem kerülnek ellenőrzésre, mivel az elem nem kötelező. -Nyilvánvalóan van hely egy kis refaktorálásra. A hibaüzenetben és a harmadik paraméterben a számok duplikáltan szerepelnek, ami nem ideális. Ha [többnyelvű űrlapot |rendering#translating] hoznánk létre, és a számokat tartalmazó üzenetet több nyelvre kellene lefordítani, ez megnehezítené az értékek módosítását. Emiatt a `%d` helyettesítő karakterek használhatók: +Itt van lehetőség egy kis refaktorálásra. A hibaüzenetben és a harmadik paraméterben a számok duplikáltan szerepelnek, ami nem ideális. Ha [többnyelvű űrlapokat |rendering#Fordítás] hoznánk létre, és a számokat tartalmazó üzenet több nyelvre lenne lefordítva, megnehezítené az értékek esetleges megváltoztatását. Ebből az okból kifolyólag használhatók a `%d` helyettesítő karakterek, és a Nette kiegészíti az értékeket: ```php - ->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]); + ->addRule($form::Range, 'Az életkornak %d és %d év között kell lennie', [18, 120]); ``` -Térjünk vissza a *jelszó* mezőhöz, tegyük *követelményessé*, és ellenőrizzük a minimális jelszóhosszúságot (`$form::MinLength`), ismét az üzenetben szereplő helyettesítő karakterek segítségével: +Térjünk vissza a `password` elemhez, amelyet szintén kötelezővé teszünk, és még ellenőrizzük a jelszó minimális hosszát (`$form::MinLength`), ismét a helyettesítő karakter használatával: ```php -$form->addPassword('password', 'Password:') - ->setRequired('Pick a password') - ->addRule($form::MinLength, 'Your password has to be at least %d long', 8); +$form->addPassword('password', 'Jelszó:') + ->setRequired('Válasszon jelszót') + ->addRule($form::MinLength, 'A jelszónak legalább %d karakter hosszúnak kell lennie', 8); ``` -Adjunk hozzá egy `passwordVerify` mezőt az űrlaphoz, ahol a felhasználó újra beírja a jelszót, az ellenőrzéshez. Érvényesítési szabályok segítségével ellenőrizzük, hogy mindkét jelszó azonos-e (`$form::Equal`). Érvként pedig szögletes [zárójelek |#Access to Controls] segítségével megadjuk az első jelszóra való hivatkozást: +Hozzáadunk az űrlaphoz még egy `passwordVerify` mezőt, ahol a felhasználó még egyszer megadja a jelszót, ellenőrzés céljából. Validálási szabályokkal ellenőrizzük, hogy mindkét jelszó azonos-e (`$form::Equal`). És paraméterként hivatkozást adunk az első jelszóra a [szögletes zárójelek |#Elemekhez való hozzáférés] segítségével: ```php -$form->addPassword('passwordVerify', 'Password again:') - ->setRequired('Fill your password again to check for typo') - ->addRule($form::Equal, 'Password mismatch', $form['password']) +$form->addPassword('passwordVerify', 'Jelszó újra:') + ->setRequired('Kérjük, adja meg a jelszót újra az ellenőrzéshez') + ->addRule($form::Equal, 'A jelszavak nem egyeznek', $form['password']) ->setOmitted(); ``` -A `setOmitted()` segítségével egy olyan elemet jelöltünk meg, amelynek értéke nem igazán érdekel minket, és amely csak az érvényesítés miatt létezik. Az értékét nem adjuk át a `$data`. +A `setOmitted()` segítségével megjelöltük azt az elemet, amelynek az értékére valójában nem vagyunk kíváncsiak, és amely csak a validáció miatt létezik. Az érték nem kerül átadásra a `$data`-ba. -Egy teljesen működőképes űrlapunk van, PHP és JavaScript validációval. A Nette validálási lehetőségei sokkal szélesebb körűek, létrehozhatunk feltételeket, megjeleníthetjük és elrejthetjük az oldal egyes részeit ezek alapján, stb. Mindent megtudhat az [űrlapok validálásáról |validation] szóló fejezetben. +Ezzel kész is van egy teljesen működőképes űrlapunk validációval PHP-ban és JavaScriptben is. A Nette validálási képességei sokkal szélesebbek, lehet feltételeket létrehozni, azok alapján megjeleníteni és elrejteni az oldal részeit stb. Mindent megtudhat az [űrlap validációról|validation] szóló fejezetben. -Alapértelmezett értékek .[#toc-default-values] -============================================== +Alapértelmezett értékek +======================= -Gyakran állítunk be alapértelmezett értékeket űrlapvezérlőkhöz: +Az űrlap elemeinek általában beállítunk alapértelmezett értékeket: ```php -$form->addEmail('email', 'Email') +$form->addEmail('email', 'E-mail') ->setDefaultValue($lastUsedEmail); ``` -Gyakran hasznos, ha egyszerre állítjuk be az összes vezérlőelem alapértelmezett értékeit. Például amikor az űrlapot rekordok szerkesztésére használjuk. Beolvassuk a rekordot az adatbázisból, és beállítjuk az alapértelmezett értékeket: +Gyakran hasznos az összes elem alapértelmezett értékét egyszerre beállítani. Például, ha az űrlap rekordok szerkesztésére szolgál. Kiolvassuk a rekordot az adatbázisból, és beállítjuk az alapértelmezett értékeket: ```php //$row = ['name' => 'John', 'age' => '33', /* ... */]; $form->setDefaults($row); ``` -A vezérlőelemek definiálása után hívjuk meg a `setDefaults()` címet. +Hívja meg a `setDefaults()`-t az elemek definiálása után. -Az űrlap megjelenítése .[#toc-rendering-the-form] -================================================= +Űrlap megjelenítése +=================== -Az űrlap alapértelmezés szerint táblázatként jelenik meg. Az egyes vezérlőelemek az alapvető webes hozzáférhetőségi irányelveket követik. Minden címke `<label>` elemként jön létre, és a bemenetükhöz kapcsolódik, a címkére kattintva a kurzor a bemenetre kerül. +Alapértelmezés szerint az űrlap táblázatként jelenik meg. Az egyes elemek megfelelnek az alapvető hozzáférhetőségi szabálynak - minden címke `<label>`-ként van megírva és összekapcsolva a megfelelő űrlap elemmel. A címkére kattintva a kurzor automatikusan az űrlap mezőbe kerül. -Az egyes elemekhez bármilyen HTML-attribútumot beállíthatunk. Például adjunk hozzá egy helyőrzőt: +Minden elemhez beállíthatunk tetszőleges HTML attribútumokat. Például hozzáadhatunk egy placeholdert: ```php -$form->addInteger('age', 'Age:') - ->setHtmlAttribute('placeholder', 'Please fill in the age'); +$form->addInteger('age', 'Életkor:') + ->setHtmlAttribute('placeholder', 'Kérjük, töltse ki az életkort'); ``` -Valóban sokféleképpen lehet megjeleníteni egy űrlapot, ezért ez egy külön [fejezet a megjelenítésről |rendering]. +Az űrlap megjelenítésének módjai valóban nagyon sokfélék, ezért ennek egy [külön fejezetet szentelünk a megjelenítésről|rendering]. -Osztályok leképezése .[#toc-mapping-to-classes] -=============================================== +Leképezés osztályokra +===================== -Térjünk vissza a `formSucceeded()` metódushoz, amelynek második paraméterében a `$data` a `ArrayHash` objektumként kapja meg az elküldött adatokat. Mivel ez egy általános osztály, valami olyasmi, mint a `stdClass`, hiányozni fog néhány kényelmi funkció a vele való munka során, például a tulajdonságok kódkiegészítése a szerkesztőkben vagy a statikus kódelemzésben. Ezt úgy lehetne megoldani, hogy minden egyes űrlaphoz külön osztályunk van, amelynek tulajdonságai az egyes vezérlőelemeket képviselik. Pl: +Térjünk vissza a `formSucceeded()` metódushoz, amely a második paraméterben, a `$data`-ban kapja meg az elküldött adatokat `ArrayHash` objektumként. Mivel ez egy generikus osztály, valami olyasmi, mint a `stdClass`, hiányozni fog belőle bizonyos kényelem a munkavégzés során, mint például a property-k kódkiegészítése a szerkesztőkben vagy a statikus kódelemzés. Ezt meg lehetne oldani azzal, hogy minden űrlaphoz lenne egy konkrét osztályunk, amelynek property-jei az egyes elemeket reprezentálják. Pl.: ```php class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -A PHP 8.0-tól kezdve használhatja ezt az elegáns jelölést, amely egy konstruktort használ: +Alternatívaként használhatja a konstruktort: ```php class RegistrationFormData @@ -246,27 +243,29 @@ class RegistrationFormData } ``` -Hogyan mondhatjuk meg a Nette-nek, hogy az adatokat ennek az osztálynak az objektumaiként adja vissza? Könnyebb, mint gondolnád. Mindössze annyit kell tennie, hogy a kezelőben a `$data` paraméter típusaként megadja az osztályt: +Az adatosztály property-jei lehetnek enumok is, és automatikusan leképeződnek. .{data-version:3.2.4} + +Hogyan mondjuk meg a Nette-nek, hogy az adatokat ennek az osztálynak az objektumaiként adja vissza? Könnyebben, mint gondolná. Elég csak az osztályt megadni a `$data` paraméter típusaként a kezelő metódusban: ```php public function formSucceeded(Form $form, RegistrationFormData $data): void { - // $name is instance of RegistrationFormData + // $data a RegistrationFormData példánya $name = $data->name; // ... } ``` -Megadhatja a `array` típust is, és akkor az adatokat tömbként adja át. +Típusként megadható az `array` is, és akkor az adatokat tömbként adja át. -Hasonló módon használhatjuk a `getValues()` metódust is, amelynek paramétereként átadjuk az osztály nevét vagy a hidratálandó objektumot: +Hasonló módon használható a `getValues()` függvény is, amelynek az osztály nevét vagy a hidratálandó objektumot paraméterként adjuk át: ```php $data = $form->getValues(RegistrationFormData::class); $name = $data->name; ``` -Ha az űrlapok konténerekből álló többszintű struktúrából állnak, hozzunk létre mindegyikhez külön osztályt: +Ha az űrlapok többszintű struktúrát alkotnak konténerekből, hozzon létre mindegyikhez külön osztályt: ```php $form = new Form; @@ -283,32 +282,34 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` -A leképezés ekkor a `$person` tulajdonságtípusból tudja, hogy a konténert a `PersonFormData` osztályhoz kell leképeznie. Ha a tulajdonság tárolók tömbjét tartalmazná, adja meg a `array` típust, és adja át a leképezendő osztályt közvetlenül a tárolóhoz: +A leképezés ezután a `$person` property típusából felismeri, hogy a konténert a `PersonFormData` osztályra kell leképezni. Ha a property konténerek tömbjét tartalmazná, adja meg az `array` típust, és a leképezendő osztályt adja át közvetlenül a konténernek: ```php $person->setMappedType(PersonFormData::class); ``` +Az űrlap adatosztályának tervét legeneráltathatja a `Nette\Forms\Blueprint::dataClass($form)` metódussal, amely kiírja azt a böngésző oldalára. A kódot ezután elég kattintással kijelölni és bemásolni a projektbe. .{data-version:3.1.15} + -Többszörös beküldőgombok .[#toc-multiple-submit-buttons] -======================================================== +Több gomb +========= -Ha az űrlapon egynél több gomb van, általában meg kell különböztetnünk, hogy melyiket nyomta meg. Minden egyes gombhoz létrehozhatunk saját függvényt. Állítsuk be kezelőként a `onClick` [eseményhez |nette:glossary#Events]: +Ha az űrlapnak több mint egy gombja van, általában meg kell különböztetnünk, hogy melyiket nyomták meg. Minden gombhoz létrehozhatunk saját kezelő függvényt. Ezt beállítjuk a [esemény |nette:glossary#Eventek események] `onClick` kezelőjeként: ```php -$form->addSubmit('save', 'Save') +$form->addSubmit('save', 'Mentés') ->onClick[] = [$this, 'saveButtonPressed']; -$form->addSubmit('delete', 'Delete') +$form->addSubmit('delete', 'Törlés') ->onClick[] = [$this, 'deleteButtonPressed']; ``` -Ezek a kezelők is csak abban az esetben hívódnak meg, ha az űrlap érvényes, mint a `onSuccess` esemény esetében. A különbség az, hogy az első paraméter a megadott típustól függően az űrlap helyett a submit gomb objektuma lehet: +Ezek a handlerek csak érvényesen kitöltött űrlap esetén hívódnak meg, ugyanúgy, mint az `onSuccess` esemény esetén. A különbség az, hogy első paraméterként az űrlap helyett átadható a küldő gomb, attól függően, hogy milyen típust ad meg: ```php public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) @@ -318,62 +319,61 @@ public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) } ``` -Ha egy űrlapot az <kbd>Enter</kbd> billentyűvel küldünk el, a rendszer úgy kezeli, mintha az első gombbal küldtük volna el. +Amikor az űrlapot az <kbd>Enter</kbd> gombbal küldik el, az úgy tekintendő, mintha az első gombbal küldték volna el. -Esemény onAnchor .[#toc-event-onanchor] -======================================= +onAnchor esemény +================ -Amikor egy űrlapot egy gyári metódusban (például a `createComponentRegistrationForm`) építünk fel, még nem tudja, hogy elküldték-e, vagy hogy milyen adatokkal küldték el. Vannak azonban olyan esetek, amikor tudnunk kell a beküldött értékeket, esetleg tőlük függ, hogy milyen lesz az űrlap kinézete, vagy függő selectboxokhoz használjuk őket, stb. +Amikor a factory metódusban (mint pl. a `createComponentRegistrationForm`) összeállítjuk az űrlapot, az még nem tudja, hogy el lett-e küldve, sem azt, hogy milyen adatokkal. Vannak azonban esetek, amikor szükségünk van az elküldött értékek ismeretére, például ezek alapján alakul az űrlap további formája, vagy szükségünk van rájuk a függő selectboxokhoz stb. -Ezért lehet, hogy az űrlapot felépítő kódot akkor hívjuk meg, amikor lehorgonyzott, azaz már kapcsolódik a prezenterhez, és ismeri a beküldött adatokat. Az ilyen kódot a `$onAnchor` tömbbe fogjuk elhelyezni: +Az űrlapot összeállító kódrészletet ezért hagyhatjuk meghívni csak abban a pillanatban, amikor az ún. lehorgonyzott, tehát már kapcsolódik a presenterhez és ismeri az elküldött adatait. Ilyen kódot adunk át az `$onAnchor` tömbbe: ```php -$country = $form->addSelect('country', 'Country:', $this->model->getCountries()); -$city = $form->addSelect('city', 'City:'); +$country = $form->addSelect('country', 'Ország:', $this->model->getCountries()); +$city = $form->addSelect('city', 'Város:'); $form->onAnchor[] = function () use ($country, $city) { - // ez a függvény akkor hívódik meg, ha az űrlap ismeri a beküldött adatokat. - // így használhatja a getValue() metódust. + // ez a függvény csak akkor hívódik meg, amikor az űrlap már tudja, hogy el lett-e küldve és milyen adatokkal + // tehát használható a getValue() metódus $val = $country->getValue(); $city->setItems($val ? $this->model->getCities($val) : []); }; ``` -Sebezhetőségi védelem .[#toc-vulnerability-protection] -====================================================== +Védelem a sebezhetőségekkel szemben +=================================== -A Nette Framework nagy erőfeszítéseket tesz a biztonság érdekében, és mivel az űrlapok a leggyakoribb felhasználói bevitel, a Nette űrlapok szinte áthatolhatatlanok. Mindent dinamikusan és átláthatóan tart fenn, semmit sem kell manuálisan beállítani. +A Nette Framework nagy hangsúlyt fektet a biztonságra, ezért gondosan ügyel az űrlapok jó védelmére. Ezt teljesen átláthatóan teszi, és nem igényel manuális beállítást. -Amellett, hogy megvédi az űrlapokat az olyan jól ismert sebezhetőségekre irányuló támadások ellen, mint a [Cross-Site Scripting (XSS |nette:glossary#cross-site-scripting-xss] ) és a [Cross-Site Request Forgery (CSRF |nette:glossary#cross-site-request-forgery-csrf]), rengeteg apró biztonsági feladatot is elvégez, amelyekre már nem kell gondolnia. +Amellett, hogy az űrlapokat megvédi a [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] és a [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF] támadásoktól, számos apró biztonsági intézkedést tesz, amelyekre Önnek már nem kell gondolnia. -Például kiszűri az összes vezérlő karaktert a bemenetekből, és ellenőrzi az UTF-8 kódolás érvényességét, így az űrlapból származó adatok mindig tiszták lesznek. A kiválasztó dobozok és rádiólisták esetében ellenőrzi, hogy a kiválasztott elemek valóban a felkínáltak közül kerültek-e ki, és nem történt-e hamisítás. Már említettük, hogy az egysoros szövegbevitel esetében eltávolítja a sor végi karaktereket, amelyeket egy támadó oda küldhet. Többsoros bevitel esetén normalizálja a sor végi karaktereket. És így tovább. +Például kiszűri a bemenetekből az összes vezérlőkaraktert és ellenőrzi az UTF-8 kódolás érvényességét, így az űrlap adatai mindig tiszták lesznek. A select boxoknál és radio listáknál ellenőrzi, hogy a kiválasztott elemek valóban a kínáltak közül valók voltak-e, és nem történt-e hamisítás. Már említettük, hogy az egysoros szöveges bemeneteknél eltávolítja a sorvégi karaktereket, amelyeket egy támadó küldhetett. A többsoros bemeneteknél pedig normalizálja a sorvégi karaktereket. És így tovább. -A Nette olyan biztonsági réseket is kijavít az Ön számára, amelyekről a legtöbb programozónak fogalma sincs, hogy léteznek. +A Nette megoldja Ön helyett azokat a biztonsági kockázatokat, amelyekről sok programozó nem is tudja, hogy léteznek. -Az említett CSRF-támadás lényege, hogy a támadó egy olyan oldal meglátogatására csábítja az áldozatot, amely némán végrehajt egy kérést az áldozat böngészőjében a szerver felé, ahol az áldozat éppen bejelentkezve van, és a szerver azt hiszi, hogy a kérést az áldozat akaratából tette. Ezért a Nette megakadályozza, hogy az űrlapot egy másik tartományból POST-on keresztül küldjék el. Ha valamilyen oknál fogva ki akarja kapcsolni a védelmet, és engedélyezni szeretné, hogy az űrlapot egy másik tartományból küldjék el, használja a következőt: +Az említett CSRF támadás lényege, hogy a támadó ráveszi az áldozatot egy olyan oldalra, amely észrevétlenül végrehajt egy kérést az áldozat böngészőjében arra a szerverre, amelyen az áldozat be van jelentkezve, és a szerver azt hiszi, hogy a kérést az áldozat saját akaratából hajtotta végre. Ezért a Nette megakadályozza a POST űrlap elküldését más domainről. Ha valamilyen okból ki szeretné kapcsolni a védelmet, és engedélyezni szeretné az űrlap elküldését más domainről, használja a következőt: ```php $form->allowCrossOrigin(); // FIGYELEM! Kikapcsolja a védelmet! ``` -Ez a védelem a `_nss` nevű SameSite cookie-t használja. A SameSite cookie-védelem nem biztos, hogy 100%-ig megbízható, ezért érdemes bekapcsolni a token-védelmet: +Ez a védelem a `_nss` nevű SameSite cookie-t használja. A SameSite cookie segítségével történő védelem nem feltétlenül 100%-ban megbízható, ezért célszerű bekapcsolni a token alapú védelmet is: ```php $form->addProtection(); ``` -Erősen ajánlott ezt a védelmet alkalmazni az alkalmazás adminisztrációs részében lévő, érzékeny adatokat módosító űrlapokra. A keretrendszer a CSRF-támadás ellen a munkamenetben tárolt hitelesítési token generálásával és érvényesítésével védekezik (az érv a token lejárta esetén megjelenő hibaüzenet). Ezért szükséges, hogy az űrlap megjelenítése előtt elinduljon egy munkamenet. A webhely adminisztrációs részében a munkamenet általában már elindult, a felhasználó bejelentkezése miatt. -Ellenkező esetben a munkamenetet a `Nette\Http\Session::start()` metódussal kell elindítani. +Javasoljuk, hogy így védje az adminisztrációs felületen lévő űrlapokat, amelyek érzékeny adatokat módosítanak az alkalmazásban. A keretrendszer a CSRF támadás ellen egy engedélyezési token generálásával és ellenőrzésével védekezik, amely a sessionben tárolódik. Ezért szükséges, hogy az űrlap megjelenítése előtt nyitva legyen a session. Az adminisztrációs felületen általában már el van indítva a session a felhasználó bejelentkezése miatt. Ellenkező esetben indítsa el a sessiont a `Nette\Http\Session::start()` metódussal. -Egy űrlap használata több bemutatóban .[#toc-using-one-form-in-multiple-presenters] -=================================================================================== +Ugyanaz az űrlap több presenterben +================================== -Ha egy űrlapot több prezenterben kell használnia, javasoljuk, hogy hozzon létre egy gyárat, amelyet aztán átad a prezenternek. Egy ilyen osztály megfelelő helye például a `app/Forms` könyvtár. +Ha ugyanazt az űrlapot több presenterben is használni szeretné, javasoljuk, hogy hozzon létre hozzá egy factory-t, amelyet aztán átad a presenternek. Egy ilyen osztály megfelelő helye például az `app/Forms` könyvtár. -A gyári osztály így nézhet ki: +A factory osztály például így nézhet ki: ```php use Nette\Application\UI\Form; @@ -383,14 +383,14 @@ class SignInFormFactory public function create(): Form { $form = new Form; - $form->addText('name', 'Name:'); - $form->addSubmit('send', 'Log in'); + $form->addText('name', 'Név:'); + $form->addSubmit('send', 'Bejelentkezés'); return $form; } } ``` -A gyári metódusban az osztálytól azt kérjük, hogy állítsa elő az űrlapot a prezenterben lévő komponensek számára: +Az osztályt megkérjük az űrlap legyártására a presenter komponens factory metódusában: ```php public function __construct( @@ -401,14 +401,14 @@ public function __construct( protected function createComponentSignInForm(): Form { $form = $this->formFactory->create(); - // megváltoztathatjuk az űrlapot, itt például a gomb címkéjét változtatjuk meg. - $form['login']->setCaption('Continue'); - $form->onSuccess[] = [$this, 'signInFormSubmitted']; // és hozzáadjuk a kezelőt. + // módosíthatjuk az űrlapot, itt például megváltoztatjuk a gomb címkéjét + $form['send']->setCaption('Folytatás'); + $form->onSuccess[] = [$this, 'signInFormSuceeded']; // és hozzáadunk egy handlert return $form; } ``` -Az űrlapfeldolgozás kezelője is a gyárból szállítható: +Az űrlap feldolgozására szolgáló handler is származhat már a factory-ból: ```php use Nette\Application\UI\Form; @@ -421,11 +421,11 @@ class SignInFormFactory $form->addText('name', 'Név:'); $form->addSubmit('send', 'Bejelentkezés'); $form->onSuccess[] = function (Form $form, $data): void { - // itt feldolgozzuk a beküldött űrlapunkat + // itt végezzük el az űrlap feldolgozását }; return $form; } } ``` -Tehát, van egy gyors bevezetés az űrlapok Nette-ben. További inspirációért próbáljon meg szétnézni a disztribúcióban található [példák |https://github.com/nette/forms/tree/master/examples] könyvtárában. +Nos, túl vagyunk a Nette űrlapok gyors bevezetésén. Próbáljon meg még belenézni a disztribúció [examples|https://github.com/nette/forms/tree/master/examples] könyvtárába, ahol további inspirációt találhat. diff --git a/forms/hu/rendering.texy b/forms/hu/rendering.texy index 813324306e..2362b0a634 100644 --- a/forms/hu/rendering.texy +++ b/forms/hu/rendering.texy @@ -1,33 +1,35 @@ -Forms Rendering -*************** +Űrlapok megjelenítése +********************* -A formák megjelenése nagyon változatos lehet. A gyakorlatban két szélsőséggel találkozhatunk. Egyrészt szükség van arra, hogy egy alkalmazásban egy sor, egymáshoz vizuálisan hasonló űrlapot jelenítsünk meg, és értékeljük a `$form->render()` segítségével történő egyszerű, sablon nélküli megjelenítést. Ez általában az adminisztrációs felületek esetében fordul elő. +Az űrlapok megjelenése nagyon változatos lehet. A gyakorlatban két szélsőséggel találkozhatunk. Az egyik oldalon az az igény áll, hogy az alkalmazásban számos olyan űrlapot jelenítsünk meg, amelyek vizuálisan hasonlítanak egymásra, mint két tojás, és értékelnénk az egyszerű megjelenítést sablon nélkül a `$form->render()` segítségével. Ez általában az adminisztrációs felületek esete. -Másrészt vannak különböző űrlapok, ahol mindegyik egyedi. Megjelenésüket a legjobban a sablonban található HTML nyelv segítségével lehet leírni. És természetesen a két említett szélsőség mellett számos olyan űrlappal is találkozunk, amelyek valahol a kettő között helyezkednek el. +A másik oldalon pedig ott vannak a változatos űrlapok, amelyekre igaz: minden darab egyedi. Formájukat leginkább HTML nyelven írhatjuk le az űrlap sablonjában. És természetesen a két említett szélsőségen kívül számos olyan űrlappal találkozunk, amelyek valahol a kettő között helyezkednek el. -Renderelés Latte-val .[#toc-rendering-with-latte] -================================================= +Megjelenítés Latte segítségével +=============================== -A [Latte templating rendszer |latte:] alapvetően megkönnyíti az űrlapok és elemeik megjelenítését. Először megmutatjuk, hogyan lehet az űrlapokat manuálisan, elemenként renderelni, hogy teljes kontrollt kapjunk a kód felett. Később megmutatjuk, hogyan lehet [automatizálni |#Automatic rendering] ezt a renderelést. +A [Latte sablonrendszer|latte:] alapvetően megkönnyíti az űrlapok és elemeik megjelenítését. Először megmutatjuk, hogyan lehet az űrlapokat manuálisan, elemenként megjeleníteni, és ezzel teljes kontrollt szerezni a kód felett. Később megmutatjuk, hogyan lehet ezt a megjelenítést [automatizálni |#Automatikus megjelenítés]. + +Az űrlap Latte sablonjának tervét legeneráltathatja a `Nette\Forms\Blueprint::latte($form)` metódussal, amely kiírja azt a böngésző oldalára. A kódot ezután elég egy kattintással kijelölni és bemásolni a projektbe. .{data-version:3.1.15} `{control}` ----------- -A legegyszerűbb módja egy űrlap megjelenítésének, ha egy sablonba írjuk: +Az űrlap megjelenítésének legegyszerűbb módja, ha a sablonba beírjuk: ```latte {control signInForm} ``` -A renderelt űrlap kinézete a [Renderer |#Renderer] és az [egyes vezérlőelemek |#HTML Attributes] konfigurálásával változtatható. +Az így megjelenített űrlap kinézetét a [#Renderer] és az [egyes elemek |#HTML attribútumok] konfigurálásával lehet befolyásolni. `n:name` -------- -Rendkívül egyszerű a PHP-kódban lévő űrlapdefiníciót HTML-kóddal összekapcsolni. Csak hozzá kell adni a `n:name` attribútumokat. Ennyire egyszerű! +Az űrlap definícióját a PHP kódban rendkívül egyszerűen össze lehet kapcsolni a HTML kóddal. Csak hozzá kell adni a `n:name` attribútumokat. Ennyire egyszerű! ```php protected function createComponentSignInForm(): Form @@ -54,10 +56,9 @@ protected function createComponentSignInForm(): Form </form> ``` -Az eredményül kapott HTML-kód kinézete teljes mértékben az Ön kezében van. Ha a `n:name` attribútumot használja a `<select>`, `<button>` vagy a `<textarea>` elemeket használ, azok belső tartalma automatikusan kitöltődik. -Ezen kívül a `<form n:name>` tag létrehoz egy helyi változót `$form` a rajzolt űrlapobjektummal és a záró `</form>` kirajzolja az összes ki nem rajzolt rejtett elemet (ugyanez vonatkozik a `{form} ... {/form}`). +Az eredményül kapott HTML kód formáját teljes mértékben Ön irányítja. Ha az `n:name` attribútumot a `<select>`, `<button>` vagy `<textarea>` elemeknél használja, azok belső tartalma automatikusan kiegészül. A `<form n:name>` tag ezenkívül létrehoz egy lokális `$form` változót a rajzolt űrlap objektumával, és a záró `</form>` megjeleníti az összes meg nem jelenített rejtett elemet (ugyanez érvényes a `{form} ... {/form}`-ra is). -Nem szabad azonban megfeledkeznünk az esetleges hibaüzenetek megjelenítéséről sem. Mindazokat, amelyeket a `addError()` módszerrel (a `{inputError}` segítségével), mind azokat, amelyeket a módszerrel adtunk hozzá az egyes elemekhez, és azokat is, amelyeket közvetlenül az űrlaphoz adtunk hozzá (a `$form->getOwnErrors()` által visszaadottak): +Nem szabad azonban elfelejtenünk a lehetséges hibaüzenetek megjelenítését. Mindazokat, amelyeket az `addError()` metódussal adtak hozzá az egyes elemekhez (a `{inputError}` segítségével), mindazokat, amelyeket közvetlenül az űrlaphoz adtak hozzá (ezeket a `$form->getOwnErrors()` adja vissza): ```latte <form n:name=signInForm class=form> @@ -79,7 +80,7 @@ Nem szabad azonban megfeledkeznünk az esetleges hibaüzenetek megjelenítésér </form> ``` -Az összetettebb űrlapelemek, mint például a RadioList vagy CheckboxList, elemenként is megjeleníthetők: +A bonyolultabb űrlap elemeket, mint a RadioList vagy a CheckboxList, így lehet elemenként megjeleníteni: ```latte {foreach $form[gender]->getItems() as $key => $label} @@ -88,16 +89,10 @@ Az összetettebb űrlapelemek, mint például a RadioList vagy CheckboxList, ele ``` -Kódjavaslat `{formPrint}` .[#toc-formprint] -------------------------------------------- - -Hasonló Latte kódot generálhat egy űrlaphoz a `{formPrint}` tag használatával. Ha ezt egy sablonba helyezi, a normál megjelenítés helyett a kódtervezetet fogja látni. Ezután csak válassza ki, és másolja be a projektjébe. - - `{label}` `{input}` ------------------- -Nem akarja átgondolni, hogy minden egyes elemhez milyen HTML elemet használjon a sablonban, akár `<input>`, `<textarea>` stb. A megoldás az univerzális `{input}` tag: +Nem akar minden elemnél azon gondolkodni, hogy milyen HTML elemet használjon hozzá a sablonban, legyen az `<input>`, `<textarea>` stb? A megoldás az univerzális `{input}` tag: ```latte <form n:name=signInForm class=form> @@ -119,9 +114,9 @@ Nem akarja átgondolni, hogy minden egyes elemhez milyen HTML elemet használjon </form> ``` -Ha az űrlap fordítót használ, a `{label}` címkékben lévő szöveg le lesz fordítva. +Ha az űrlap fordítót használ, a `{label}` tageken belüli szöveg lefordításra kerül. -Az összetettebb űrlapelemek, mint például a RadioList vagy CheckboxList, elemenként is megjeleníthetők: +Ebben az esetben is a bonyolultabb űrlap elemeket, mint a RadioList vagy a CheckboxList, elemenként lehet megjeleníteni: ```latte {foreach $form[gender]->items as $key => $label} @@ -129,20 +124,19 @@ Az összetettebb űrlapelemek, mint például a RadioList vagy CheckboxList, ele {/foreach} ``` -A `<input>` magának a Checkbox elemben való megjelenítéséhez használja a `{input myCheckbox:}` címet. A HTML-attribútumokat vesszővel kell elválasztani `{input myCheckbox:, class: required}`. +Magának az `<input>` elemnek a megjelenítéséhez a Checkbox elemben használja a `{input myCheckbox:}`-t. A HTML attribútumokat ebben az esetben mindig vesszővel válassza el `{input myCheckbox:, class: required}`. `{inputError}` -------------- -Hibaüzenetet ír ki az űrlapelemhez, ha van ilyen. Az üzenet általában egy HTML-elembe van csomagolva a formázás érdekében. -Az üres elem megjelenítésének elkerülése, ha nincs üzenet, elegánsan megoldható a `n:ifcontent` segítségével: +Kiírja a hibaüzenetet az űrlap elemhez, ha van ilyen. Az üzenetet általában HTML elembe csomagoljuk a stílusozás miatt. Az üres elem megjelenítésének elkerülését, ha nincs üzenet, elegánsan meg lehet oldani az `n:ifcontent` segítségével: ```latte <span class=error n:ifcontent>{inputError $input}</span> ``` -A `hasErrors()` metódus segítségével észlelhetjük a hiba jelenlétét, és ennek megfelelően állíthatjuk be a szülőelem osztályát: +A hiba jelenlétét a `hasErrors()` metódussal ellenőrizhetjük, és ennek megfelelően beállíthatjuk a szülő elem osztályát: ```latte <div n:class="$form[username]->hasErrors() ? 'error'"> @@ -155,14 +149,13 @@ A `hasErrors()` metódus segítségével észlelhetjük a hiba jelenlétét, és `{form}` -------- -Címkék `{form signInForm}...{/form}` alternatívája a `<form n:name="signInForm">...</form>`. +A `{form signInForm}...{/form}` tagek alternatívái a `<form n:name="signInForm">...</form>`-nak. -Automatikus renderelés .[#toc-automatic-rendering] --------------------------------------------------- +Automatikus megjelenítés +------------------------ -A `{input}` és `{label}` címkékkel könnyen létrehozhatunk egy általános sablont bármilyen űrlaphoz. Ez az összes elemét szekvenciálisan végigjárja és megjeleníti, kivéve a rejtett elemeket, amelyek automatikusan megjelenítésre kerülnek, amikor az űrlapot a `</form>` címkével zárul. -A megjelenített űrlap nevét a `$form` változóban várja el. +Az `{input}` és `{label}` tageknek köszönhetően könnyen létrehozhatunk egy általános sablont bármilyen űrlaphoz. Fokozatosan iterál és megjeleníti az összes elemét, kivéve a rejtett elemeket, amelyek automatikusan megjelennek az űrlap lezárásakor a `</form>` taggel. A megjelenítendő űrlap nevét a `$form` változóban fogja várni. ```latte <form n:name=$form class=form> @@ -179,16 +172,15 @@ A megjelenített űrlap nevét a `$form` változóban várja el. </form> ``` -A használt önzáró párcímkék `{label .../}` a PHP kódban az űrlap definíciójából származó címkéket jelenítik meg. +A használt önlezáró páros `{label .../}` tagek a PHP kódban lévő űrlap definícióból származó címkéket jelenítik meg. -Ezt az általános sablont elmentheti a `basic-form.latte` fájlba, és az űrlap megjelenítéséhez csak be kell építenie, és át kell adnia az űrlap nevét (vagy példányát) a `$form` paraméterhez: +Ezt az általános sablont mentse el például a `basic-form.latte` fájlba, és az űrlap megjelenítéséhez elég csak beilleszteni és átadni az űrlap nevét (vagy példányát) a `$form` paraméterbe: ```latte {include basic-form.latte, form: signInForm} ``` -Ha egy adott űrlap megjelenését szeretnénk befolyásolni, és egy elemet másképp rajzolni, akkor a legegyszerűbb, ha a sablonban olyan blokkokat készítünk, amelyeket később felülírhatunk. -A blokkok [dinamikus nevet |latte:template-inheritance#dynamic-block-names] is kaphatnak, így a kirajzolandó elem nevét is beillesztheti beléjük. Például: +Ha egy adott űrlap megjelenítésekor bele szeretne szólni a formájába, és például egy elemet másképp szeretne megjeleníteni, akkor a legegyszerűbb út, ha a sablonban előre elkészít blokkokat, amelyeket később felül lehet írni. A blokkoknak lehetnek [dinamikus nevek |latte:template-inheritance#Dinamikus blokknevek] is, így beléjük lehet illeszteni a megjelenítendő elem nevét is. Például: ```latte ... @@ -197,7 +189,7 @@ A blokkok [dinamikus nevet |latte:template-inheritance#dynamic-block-names] is k ... ``` -A pl. `username` elemhez ez létrehozza a `input-username` blokkot, amely könnyen felülírható a [{embed} |latte:template-inheritance#unit-inheritance] tag használatával: +Egy `username` nevű elemhez így létrejön az `input-username` blokk, amelyet könnyen felül lehet írni a [{embed} |latte:template-inheritance#Egység öröklődés embed] tag használatával: ```latte {embed basic-form.latte, form: signInForm} @@ -209,7 +201,7 @@ A pl. `username` elemhez ez létrehozza a `input-username` blokkot, amely könny {/embed} ``` -Alternatívaként a `basic-form.latte` sablon teljes tartalma [definiálható |latte:template-inheritance#definitions] blokkként, beleértve a `$form` paramétert is: +Alternatívaként a `basic-form.latte` sablon teljes tartalmát [definiálni |latte:template-inheritance#Definíciók define] lehet blokként, beleértve a `$form` paramétert is: ```latte {define basic-form, $form} @@ -219,7 +211,7 @@ Alternatívaként a `basic-form.latte` sablon teljes tartalma [definiálható |l {/define} ``` -Ez némileg megkönnyíti a használatát: +Ennek köszönhetően kissé egyszerűbb lesz a hívása: ```latte {embed basic-form, signInForm} @@ -227,31 +219,31 @@ Ez némileg megkönnyíti a használatát: {/embed} ``` -A blokkot csak egy helyre kell importálnia, az elrendezési sablon elejére: +A blokkot pedig elég egyetlen helyen importálni, a layout sablon elején: ```latte {import basic-form.latte} ``` -Speciális esetek .[#toc-special-cases] --------------------------------------- +Speciális esetek +---------------- -Ha csak egy űrlap belső tartalmát kell megjelenítenie anélkül, hogy a `<form>` & `</form>` HTML-címkéket, például egy AJAX-kérés során, akkor a `{formContext} … {/formContext}` segítségével nyithatja meg és zárhatja be az űrlapot. Logikai értelemben hasonlóan működik, mint a `{form}`, itt lehetővé teszi, hogy más címkéket használjon az űrlapelemek megrajzolásához, ugyanakkor nem rajzol semmit. +Ha csak az űrlap belső részét kell megjeleníteni a `<form>` HTML tagek nélkül, például snippek küldésekor, rejtse el őket az `n:tag-if` attribútummal: ```latte -{formContext signForm} +<form n:name=signInForm n:tag-if=false> <div> <label n:name=username>Username: <input n:name=username></label> {inputError username} </div> -{/formContext} +</form> ``` -A `formContainer` címke az űrlapkonténeren belüli bemenetek megjelenítését segíti. +Az elemek megjelenítésében az űrlap konténeren belül segít a `{formContainer}` tag. ```latte -<p>Which news you wish to receive:</p> +<p>Melyik híreket szeretné megkapni:</p> {formContainer emailNews} <ul> @@ -262,27 +254,27 @@ A `formContainer` címke az űrlapkonténeren belüli bemenetek megjelenítésé ``` -Renderelés Latte nélkül .[#toc-rendering-without-latte] -======================================================= +Megjelenítés Latte nélkül +========================= -A legegyszerűbb módja egy űrlap megjelenítésének a következő hívás: +Az űrlap megjelenítésének legegyszerűbb módja a következő hívás: ```php $form->render(); ``` -A renderelt űrlap kinézete a [Renderer |#Renderer] és az [egyes vezérlőelemek |#HTML Attributes] konfigurálásával változtatható. +Az így megjelenített űrlap kinézetét a [#Renderer] és az [egyes elemek |#HTML attribútumok] konfigurálásával lehet befolyásolni. -Kézi renderelés .[#toc-manual-rendering] ----------------------------------------- +Manuális megjelenítés +--------------------- -Minden űrlapelem rendelkezik olyan metódusokkal, amelyek az űrlapmező és a címke HTML-kódját generálják. A metódusok vagy egy karakterlánc vagy egy [Nette\Utils\Html |utils:html-elements] objektum formájában adhatják vissza: +Minden űrlap elem rendelkezik metódusokkal, amelyek generálják az űrlap mező és a címke HTML kódját. Ezt visszaadhatják vagy stringként, vagy [Nette\Utils\Html|utils:html-elements] objektumként: -- `getControl(): Html|string` az elem HTML kódját adja vissza -- A `getLabel($caption = null): Html|string|null` a címke HTML-kódját adja vissza, ha van ilyen. +- `getControl(): Html|string` visszaadja az elem HTML kódját +- `getLabel($caption = null): Html|string|null` visszaadja a címke HTML kódját, ha létezik -Ez lehetővé teszi az űrlap elemenkénti megjelenítését: +Az űrlapot így elemenként lehet megjeleníteni: ```php <?php $form->render('begin') ?> @@ -305,47 +297,46 @@ Ez lehetővé teszi az űrlap elemenkénti megjelenítését: <?php $form->render('end') ?> ``` -Míg néhány elem esetében a `getControl()` egyetlen HTML-elemet ad vissza (pl. `<input>`, `<select>` stb.), mások esetében egy egész HTML-kódot ad vissza (CheckboxList, RadioList). -Ebben az esetben használhat olyan metódusokat, amelyek külön-külön generálnak beviteli és címkézési adatokat, minden egyes elemhez külön-külön: +Míg egyes elemeknél a `getControl()` egyetlen HTML elemet ad vissza (pl. `<input>`, `<select>` stb.), másoknál egy egész HTML kódrészletet (CheckboxList, RadioList). Ebben az esetben használhatja azokat a metódusokat, amelyek az egyes inputokat és címkéket generálják, minden elemhez külön: -- `getControlPart($key = null): ?Html` egy elem HTML-kódját adja vissza. -- `getLabelPart($key = null): ?Html` visszaadja egy elem címkéjének HTML-kódját. +- `getControlPart($key = null): ?Html` visszaadja egy elem HTML kódját +- `getLabelPart($key = null): ?Html` visszaadja egy elem címkéjének HTML kódját .[note] -Ezek a metódusok történelmi okokból a `get` előtaggal vannak ellátva, de a `generate` jobb lenne, mivel minden egyes híváskor egy új `Html` elemet hoz létre és ad vissza. +Ezeknek a metódusoknak történelmi okokból `get` prefixük van, de jobb lenne a `generate`, mert minden híváskor új `Html` elemet hoznak létre és adnak vissza. -Renderer .[#toc-renderer] -========================= +Renderer +======== -Ez egy objektum, amely az űrlap renderelését biztosítja. A `$form->setRenderer` metódussal lehet beállítani. A `$form->render()` metódus meghívásakor adják át a vezérlést. +Ez egy objektum, amely biztosítja az űrlap megjelenítését. Ezt a `$form->setRenderer` metódussal lehet beállítani. A vezérlés átadódik neki a `$form->render()` metódus hívásakor. -Ha nem állítunk be egyéni renderelőt, akkor az alapértelmezett renderelőt [api:Nette\Forms\Rendering\DefaultFormRenderer] fogja használni. Ez az űrlap elemeit HTML táblázatként rendereli. A kimenet így néz ki: +Ha nem állítunk be saját renderert, az alapértelmezett megjelenítő [api:Nette\Forms\Rendering\DefaultFormRenderer] kerül felhasználásra. Ez az űrlap elemeit HTML táblázat formájában jeleníti meg. A kimenet így néz ki: ```latte <table> <tr class="required"> - <th><label class="required" for="frm-name">Name:</label></th> + <th><label class="required" for="frm-name">Név:</label></th> <td><input type="text" class="text" name="name" id="frm-name" required value=""></td> </tr> <tr class="required"> - <th><label class="required" for="frm-age">Age:</label></th> + <th><label class="required" for="frm-age">Életkor:</label></th> <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> </tr> <tr> - <th><label>Gender:</label></th> + <th><label>Nem:</label></th> ... ``` -Sok webdesigner más jelöléseket, például listát preferál. Beállíthatjuk a `DefaultFormRenderer` címet úgy, hogy egyáltalán ne rendereljük táblázatba. Csak megfelelő [$wrappereket |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers] kell beállítanunk. Az első index mindig egy területet, a második pedig egy elemet jelöl. A képen az összes megfelelő terület látható: +Az, hogy használjunk-e táblázatot az űrlap vázához, vitatható, és sok webdesigner más jelölést részesít előnyben. Például a definíciós listát. Ezért újrakonfiguráljuk a `DefaultFormRenderer`-t úgy, hogy az űrlapot lista formájában jelenítse meg. A konfiguráció a [$wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers] tömb szerkesztésével történik. Az első index mindig a területet, a második pedig annak attribútumát jelenti. Az egyes területeket az ábra mutatja: -[* form-areas-en.webp *] +[* defaultformrenderer.webp *] -Alapértelmezés szerint a `controls` egy csoportja be van csomagolva egy `<table>`, és minden egyes `pair` egy táblázat sora `<tr>` amely a `label` és a `control` egy párját tartalmazza (cellák `<th>` és `<td>`). Változtassuk meg ezeket a burkolóelemeket. A `controls` -t be fogjuk csomagolni `<dl>`, a `pair` -t önmagában hagyjuk, a `label` -t a következőbe tesszük `<dt>` és a `control` -t a következőbe csomagoljuk `<dd>`: +Alapértelmezés szerint az `controls` elemcsoport egy `<table>`-ba van csomagolva, minden `pair` egy táblázat sort (`<tr>`) képvisel, a `label` és `control` páros pedig cellák (`<th>` és `<td>`). Most megváltoztatjuk a csomagoló elemeket. A `controls` területet egy `<dl>` konténerbe helyezzük, a `pair` területet konténer nélkül hagyjuk, a `label`-t `<dt>`-be, végül a `control`-t `<dd>` tagekkel csomagoljuk: ```php $renderer = $form->getRenderer(); @@ -357,193 +348,192 @@ $renderer->wrappers['control']['container'] = 'dd'; $form->render(); ``` -Az eredmény a következő részlet: +Az eredmény ez a HTML kód: ```latte <dl> - <dt><label class="required" for="frm-name">Name:</label></dt> + <dt><label class="required" for="frm-name">Név:</label></dt> <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> - <dt><label class="required" for="frm-age">Age:</label></dt> + <dt><label class="required" for="frm-age">Életkor:</label></dt> <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> - <dt><label>Gender:</label></dt> + <dt><label>Nem:</label></dt> ... </dl> ``` -A burkolók számos attribútumot befolyásolhatnak. Például: +A wrappers tömbben számos további attribútumot lehet befolyásolni: -- speciális CSS osztályok hozzáadása minden egyes űrlapbemenethez -- megkülönböztetni a páratlan és páros sorokat -- a kötelező és opcionális elemek eltérő rajzolása -- állítsa be, hogy a hibaüzenetek az űrlap felett vagy az egyes elemek közelében jelenjenek meg. +- CSS osztályok hozzáadása az egyes űrlap elem típusokhoz +- CSS osztállyal megkülönböztetni a páros és páratlan sorokat +- vizuálisan megkülönböztetni a kötelező és választható elemeket +- meghatározni, hogy a hibaüzenetek közvetlenül az elemeknél vagy az űrlap felett jelenjenek-e meg -Opciók .[#toc-options] ----------------------- +Options +------- -A Renderer viselkedése az egyes űrlapelemek *opciók* beállításával is szabályozható. Így beállítható a beviteli mező mellett megjelenő tooltip: +A Renderer viselkedését az egyes űrlap elemeken beállított *options* segítségével is lehet irányítani. Így lehet beállítani a leírást, amely a beviteli mező mellett jelenik meg: ```php -$form->addText('phone', 'Number:') - ->setOption('description', 'This number will remain hidden'); +$form->addText('phone', 'Szám:') + ->setOption('description', 'Ez a szám rejtve marad'); ``` -Ha HTML tartalmat szeretnénk elhelyezni benne, akkor a [Html |utils:html-elements] osztályt használjuk. +Ha HTML tartalmat szeretnénk elhelyezni benne, használjuk a [Html |utils:html-elements] osztályt: ```php use Nette\Utils\Html; -$form->addText('phone', 'Phone:') +$form->addText('phone', 'Szám:') ->setOption('description', Html::el('p') - ->setHtml('<a href="...">Terms of service.</a>') + ->setHtml('<a href="...">A szám megőrzésének feltételei</a>') ); ``` .[tip] -Html elem is használható a címke helyett: `$form->addCheckbox('conditions', $label)`. +A Html elemet a címke helyett is lehet használni: `$form->addCheckbox('conditions', $label)`. -Bemenetek csoportosítása .[#toc-grouping-inputs] ------------------------------------------------- +Elemek csoportosítása +--------------------- -A renderelő lehetővé teszi az elemek vizuális csoportokba (mezőcsoportokba) való csoportosítását: +A Renderer lehetővé teszi az elemek vizuális csoportokba (fieldsetekbe) való csoportosítását: ```php -$form->addGroup('Personal data'); +$form->addGroup('Személyes adatok'); ``` -Új csoport létrehozása aktiválja azt - minden további hozzáadott elemet hozzáad ehhez a csoporthoz. Egy űrlapot így építhetünk fel: +Új csoport létrehozása után ez válik aktívvá, és minden újonnan hozzáadott elem egyúttal hozzáadódik ehhez a csoporthoz is. Tehát az űrlapot így lehet építeni: ```php $form = new Form; -$form->addGroup('Personal data'); -$form->addText('name', 'Your name:'); -$form->addInteger('age', 'Your age:'); +$form->addGroup('Személyes adatok'); +$form->addText('name', 'Neved:'); +$form->addInteger('age', 'Korod:'); $form->addEmail('email', 'Email:'); -$form->addGroup('Shipping address'); -$form->addCheckbox('send', 'Ship to address'); -$form->addText('street', 'Street:'); -$form->addText('city', 'City:'); -$form->addSelect('country', 'Country:', $countries); +$form->addGroup('Szállítási cím'); +$form->addCheckbox('send', 'Szállítás címre'); +$form->addText('street', 'Utca:'); +$form->addText('city', 'Város:'); +$form->addSelect('country', 'Ország:', $countries); ``` +A Renderer először a csoportokat jeleníti meg, és csak utána azokat az elemeket, amelyek egyik csoportba sem tartoznak. + -Bootstrap támogatás .[#toc-bootstrap-support] ---------------------------------------------- +Támogatás a Bootstraphez +------------------------ -[Példákat |https://github.com/nette/forms/tree/master/examples] talál a [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], [Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] és [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php] Renderer konfigurációjára. +[A példákban |https://github.com/nette/forms/tree/master/examples] találhatók minták arra, hogyan konfigurálja a Renderert a [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], [Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] és [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php] számára. -HTML attribútumok .[#toc-html-attributes] -========================================= +HTML attribútumok +================= -A `setHtmlAttribute(string $name, $value = true)` segítségével bármilyen HTML-attribútumot beállíthat az űrlapvezérlőkhöz: +Tetszőleges HTML attribútumok beállításához az űrlap elemekhez használja a `setHtmlAttribute(string $name, $value = true)` metódust: ```php $form->addInteger('number', 'Szám:') ->setHtmlAttribute('class', 'big-number'); -$form->addSelect('rank', 'Order by:', ['price', 'name']) - ->setHtmlAttribute('onchange', 'submit()'); // változáskor meghívja a submit() JS függvényt. +$form->addSelect('rank', 'Rendezés:', ['ár', 'név']) + ->setHtmlAttribute('onchange', 'submit()'); // változáskor küldés -// <form>-ra való alkalmazás +// A <form> elem attribútumainak beállításához $form->setHtmlAttribute('id', 'myForm'); ``` -Input type beállítása: +Az elem típusának specifikációja: ```php -$form->addText('tel', 'Your telephone:') +$form->addText('tel', 'Telefonod:') ->setHtmlType('tel') - ->setHtmlAttribute('placeholder', 'Please, fill in your telephone'); + ->setHtmlAttribute('placeholder', 'írja be a telefonszámot'); ``` -HTML attribútumot állíthatunk be a rádió- vagy jelölőnégyzet-listák egyes elemeihez, mindegyikhez különböző értékekkel. -Figyeljük meg a kettőspontot a `style:` után, hogy az értéket a kulcs szerint válasszuk ki: +.[warning] +A típus és más attribútumok beállítása csak vizuális célokat szolgál. A bemenetek helyességének ellenőrzése a szerveroldalon kell, hogy történjen, amit a megfelelő [űrlap elem|controls] kiválasztásával és [érvényesítési szabályok|validation] megadásával biztosíthat. + +A rádió- vagy checkbox listák egyes elemeihez beállíthatunk HTML attribútumot különböző értékekkel mindegyikhez. Figyelje meg a kettőspontot a `style:` után, amely biztosítja az érték kiválasztását kulcs szerint: ```php -$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue']; +$colors = ['r' => 'piros', 'g' => 'zöld', 'b' => 'kék']; $styles = ['r' => 'background:red', 'g' => 'background:green']; -$form->addCheckboxList('colors', 'Colors:', $colors) +$form->addCheckboxList('colors', 'Színek:', $colors) ->setHtmlAttribute('style:', $styles); ``` -Renders: +Kiírja: ```latte -<label><input type="checkbox" name="colors[]" style="background:red" value="r">red</label> -<label><input type="checkbox" name="colors[]" style="background:green" value="g">green</label> -<label><input type="checkbox" name="colors[]" value="b">blue</label> +<label><input type="checkbox" name="colors[]" style="background:red" value="r">piros</label> +<label><input type="checkbox" name="colors[]" style="background:green" value="g">zöld</label> +<label><input type="checkbox" name="colors[]" value="b">kék</label> ``` -A logikai HTML-attribútumok (amelyeknek nincs értéke, mint például a `readonly`) esetében kérdőjelet használhat: +Logikai attribútumok, mint a `readonly`, beállításához használhatunk kérdőjeles írásmódot: ```php -$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue']; -$form->addCheckboxList('colors', 'Colors:', $colors) - ->setHtmlAttribute('readonly?', 'r'); // több kulcs esetén használjunk tömböt, pl. ['r', 'g'] +$form->addCheckboxList('colors', 'Színek:', $colors) + ->setHtmlAttribute('readonly?', 'r'); // több kulcshoz használjon tömböt, pl. ['r', 'g'] ``` -Renders: +Kiírja: ```latte -<label><input type="checkbox" name="colors[]" readonly value="r">red</label> -<label><input type="checkbox" name="colors[]" value="g">green</label> -<label><input type="checkbox" name="colors[]" value="b">blue</label> +<label><input type="checkbox" name="colors[]" readonly value="r">piros</label> +<label><input type="checkbox" name="colors[]" value="g">zöld</label> +<label><input type="checkbox" name="colors[]" value="b">kék</label> ``` -A selectboxok esetében a `setHtmlAttribute()` módszer beállítja az attribútumokat a `<select>` elemhez. Ha az attribútumokat minden egyes -`<option>`, a `setOptionAttribute()` módszert fogjuk használni. A fent használt kettőspont és kérdőjel is működik: +Select boxok esetén a `setHtmlAttribute()` metódus a `<select>` elem attribútumait állítja be. Ha az egyes `<option>` elemek attribútumait szeretnénk beállítani, használjuk a `setOptionAttribute()` metódust. Működnek a fentebb említett kettőspontos és kérdőjeles írásmódok is: ```php -$form->addSelect('colors', 'Colors:', $colors) +$form->addSelect('colors', 'Színek:', $colors) ->setOptionAttribute('style:', $styles); ``` -Renders: +Kiírja: ```latte <select name="colors"> - <option value="r" style="background:red">red</option> - <option value="g" style="background:green">green</option> - <option value="b">blue</option> + <option value="r" style="background:red">piros</option> + <option value="g" style="background:green">zöld</option> + <option value="b">kék</option> </select> ``` -Prototípusok .[#toc-prototypes] -------------------------------- +Prototípusok +------------ -A HTML-attribútumok beállításának alternatív módja a sablon módosítása, amelyből a HTML-elem generálódik. A sablon egy `Html` objektum, és a `getControlPrototype()` metódus adja vissza: +A HTML attribútumok beállításának alternatív módja a minta módosítása, amelyből a HTML elem generálódik. A minta egy `Html` objektum, és a `getControlPrototype()` metódus adja vissza: ```php -$input = $form->addInteger('number'); +$input = $form->addInteger('number', 'Szám:'); $html = $input->getControlPrototype(); // <input> -$html->class('big-number'); // <input class="big-number"> +$html->class('big-number'); // <input class="big-number"> ``` -A `getLabelPrototype()` által visszaadott címke sablon is módosítható ilyen módon: +Ezzel a módszerrel módosítható a címke mintája is, amelyet a `getLabelPrototype()` ad vissza: ```php $html = $input->getLabelPrototype(); // <label> -$html->class('distinctive'); // <label class="distinctive"> +$html->class('distinctive'); // <label class="distinctive"> ``` -A Checkbox, CheckboxList és RadioList elemek esetében befolyásolhatja az elemet körülvevő elemsablont. Ezt a `getContainerPrototype()` adja vissza. Alapértelmezés szerint ez egy "üres" elem, tehát semmi sem kerül megjelenítésre, de ha nevet adunk neki, akkor megjelenítésre kerül: +A Checkbox, CheckboxList és RadioList elemeknél befolyásolhatja annak az elemnek a mintáját, amely az egész elemet csomagolja. Ezt a `getContainerPrototype()` adja vissza. Alapértelmezett állapotban ez egy „üres” elem, tehát semmi sem jelenik meg, de azzal, hogy nevet adunk neki, megjelenítésre kerül: ```php $input = $form->addCheckbox('send'); -echo $input->getControl(); -// <label><input type="checkbox" name="send"></label> - $html = $input->getContainerPrototype(); $html->setName('div'); // <div> $html->class('check'); // <div class="check"> @@ -551,50 +541,49 @@ echo $input->getControl(); // <div class="check"><label><input type="checkbox" name="send"></label></div> ``` -A CheckboxList és RadioList esetében a `getSeparatorPrototype()` metódus által visszaadott elemelválasztó mintát is lehet befolyásolni. Alapértelmezés szerint ez egy elem `<br>`. Ha ezt páros elemre változtatjuk, akkor az egyes elemeket elválasztás helyett be fogja burkolni. -Lehetőség van az elemcímkék HTML elemsablonjának befolyásolására is, amely a `getItemLabelPrototype()` metódust adja vissza. +CheckboxList és RadioList esetén befolyásolható az egyes elemek elválasztójának mintája is, amelyet a `getSeparatorPrototype()` metódus ad vissza. Alapértelmezett állapotban ez a `<br>` elem. Ha páros elemre változtatja, akkor az egyes elemeket csomagolni fogja elválasztás helyett. Továbbá befolyásolható az egyes elemek címkéjének HTML elem mintája is, amelyet a `getItemLabelPrototype()` ad vissza. -Fordítás .[#toc-translating] -============================ +Fordítás +======== -Ha többnyelvű alkalmazást programoz, valószínűleg különböző nyelveken kell megjelenítenie az űrlapot. A Nette Framework erre a célra egy fordítási felületet definiál [api:Nette\Localization\Translator]. A Nette-ben nincs alapértelmezett implementáció, igényei szerint választhat több kész megoldás közül, amelyeket a [Componette-en |https://componette.org/search/localization] talál. A dokumentációjukból megtudhatja, hogyan kell a fordítót konfigurálni. +Ha többnyelvű alkalmazást programoz, valószínűleg szüksége lesz az űrlap különböző nyelvi változatokban történő megjelenítésére. A Nette Framework ehhez definiál egy fordítási interfészt [api:Nette\Localization\Translator]. A Nette-ben nincs alapértelmezett implementáció, választhat igényei szerint több kész megoldás közül, amelyeket a [Componette |https://componette.org/search/localization] oldalon talál. Dokumentációjukban megtudhatja, hogyan konfigurálja a fordítót. -Az űrlap támogatja a szövegek kiadását a fordítóprogramon keresztül. Ezt a `setTranslator()` metódus segítségével adjuk át: +Az űrlapok támogatják a szövegek fordítón keresztüli kiírását. Ezt a `setTranslator()` metódussal adjuk át nekik: ```php $form->setTranslator($translator); ``` -Mostantól kezdve nemcsak az összes címke, hanem az összes hibaüzenet vagy a kiválasztó dobozok bejegyzései is le lesznek fordítva egy másik nyelvre. +Ettől a pillanattól kezdve nemcsak az összes címke, hanem az összes hibaüzenet vagy select box elem is lefordításra kerül egy másik nyelvre. -Lehetőség van az egyes űrlapelemekhez más fordítót beállítani, vagy a fordítást teljesen kikapcsolni a `null` címmel: +Az egyes űrlap elemeknél lehetőség van más fordító beállítására vagy a fordítás teljes kikapcsolására `null` értékkel: ```php -$form->addSelect('carModel', 'Model:', $cars) +$form->addSelect('carModel', 'Modell:', $cars) ->setTranslator(null); ``` -Az [érvényesítési szabályok |validation] esetében a fordítónak specifikus paramétereket is át kell adni, például a szabály esetében: +Az [érvényesítési szabályoknál|validation] a fordítónak specifikus paraméterek is átadásra kerülnek, például a szabálynál: ```php -$form->addPassword('password', 'Password:') - ->addRule($form::MinLength, 'Password has to be at least %d characters long', 8) +$form->addPassword('password', 'Jelszó:') + ->addRule($form::MinLength, 'A jelszónak legalább %d karakter hosszúnak kell lennie', 8); ``` -A fordítót a következő paraméterekkel hívjuk meg: +a fordító ezekkel a paraméterekkel hívódik meg: ```php -$translator->translate('Password has to be at least %d characters long', 8); +$translator->translate('A jelszónak legalább %d karakter hosszúnak kell lennie', 8); ``` -így a `characters` szóhoz a megfelelő többes számot tudja kiválasztani. +és így kiválaszthatja a `karakter` szó helyes többes számú alakját a szám alapján. -Esemény onRender .[#toc-event-onrender] -======================================= +onRender esemény +================ -Közvetlenül az űrlap renderelése előtt meghívhatjuk a kódunkat. Ez például HTML-osztályokat adhat az űrlap elemeihez a megfelelő megjelenítés érdekében. A kódot a `onRender` tömbhöz adjuk hozzá: +Közvetlenül azelőtt, hogy az űrlap megjelenne, meghívathatjuk a kódunkat. Ez például kiegészítheti az űrlap elemeket HTML osztályokkal a helyes megjelenítés érdekében. A kódot az `onRender` tömbhöz adjuk hozzá: ```php $form->onRender[] = function ($form) { diff --git a/forms/hu/standalone.texy b/forms/hu/standalone.texy index bcb4b6c8be..8d53892ddf 100644 --- a/forms/hu/standalone.texy +++ b/forms/hu/standalone.texy @@ -2,42 +2,42 @@ ************************* .[perex] -A Nette Forms jelentősen megkönnyíti a webes űrlapok létrehozását és feldolgozását. Alkalmazásaiban teljesen önállóan, a keretrendszer többi része nélkül is használhatja őket, amit ebben a fejezetben bemutatunk. +A Nette Forms nagyságrendekkel megkönnyíti a webes űrlapok létrehozását és feldolgozását. Alkalmazásaiban teljesen önállóan is használhatja őket a keretrendszer többi része nélkül, amit ebben a fejezetben bemutatunk. -Ha azonban Nette alkalmazást és prezentereket használ, akkor van egy útmutató az Ön számára: [Forms in presenters |in-presenter]. +Ha azonban a Nette Applicationt és a presentereket használja, akkor a [használat presenterekben|in-presenter] útmutató Önnek szól. -Első űrlap .[#toc-first-form] -============================= +Első űrlap +========== -Megpróbálunk írni egy egyszerű regisztrációs űrlapot. A kódja így fog kinézni ("teljes kód":https://gist.github.com/dg/370a7e3094d9ba9a9e913b8e2a2dc851): +Próbáljunk meg írni egy egyszerű regisztrációs űrlapot. A kódja a következő lesz ("teljes kód":https://gist.github.com/dg/57878c1a413ae8ef0c1d83f02c43ef3f): ```php use Nette\Forms\Form; $form = new Form; -$form->addText('name', 'Name:'); -$form->addPassword('password', 'Password:'); -$form->addSubmit('send', 'Sign up'); +$form->addText('name', 'Név:'); +$form->addPassword('password', 'Jelszó:'); +$form->addSubmit('send', 'Regisztráció'); ``` -És rendereljük le: +Nagyon könnyen megjeleníthetjük: ```php $form->render(); ``` -Az eredménynek így kell kinéznie: +és a böngészőben így jelenik meg: -[* form-en.webp *] +[* form-cs.webp *] -Az űrlap a `Nette\Forms\Form` osztály objektuma (a `Nette\Application\UI\Form` osztály a prezenterekben használatos). Hozzáadtuk a vezérlőelemeket név, jelszó és küldés gomb. +Az űrlap a `Nette\Forms\Form` osztály objektuma (a `Nette\Application\UI\Form` osztályt a presenterekben használják). Hozzáadtuk az úgynevezett név, jelszó elemeket és egy küldés gombot. -Most újraélesztjük az űrlapot. A `$form->isSuccess()` megkérdezésével megtudjuk, hogy az űrlapot elküldtük-e, és hogy érvényesen töltöttük-e ki. Ha igen, akkor ki fogjuk dobni az adatokat. Az űrlap definíciója után hozzáadjuk: +Most pedig keltsük életre az űrlapot. A `$form->isSuccess()` lekérdezésével megtudjuk, hogy az űrlapot elküldték-e és érvényesen töltötték-e ki. Ha igen, kiírjuk az adatokat. Az űrlapdefiníció után tehát hozzáadjuk: ```php if ($form->isSuccess()) { - echo 'Az űrlapot helyesen töltöttük ki és küldtük el'; + echo 'Az űrlap helyesen lett kitöltve és elküldve'; $data = $form->getValues(); // $data->name tartalmazza a nevet // $data->password tartalmazza a jelszót @@ -45,32 +45,32 @@ if ($form->isSuccess()) { } ``` -A `getValues()` metódus az elküldött adatokat egy [ArrayHash |utils:arrays#ArrayHash] objektum formájában adja vissza. [Később |#Mapping to Classes] megmutatjuk, hogyan lehet ezt megváltoztatni. A `$data` változó tartalmazza a `name` és `password` kulcsokat a felhasználó által megadott adatokkal. +A `getValues()` metódus az elküldött adatokat [ArrayHash |utils:arrays#ArrayHash] objektum formájában adja vissza. Hogy ezt hogyan lehet megváltoztatni, azt [később |#Osztályokra való leképezés] mutatjuk be. A `$data` objektum tartalmazza a `name` és `password` kulcsokat a felhasználó által megadott adatokkal. -Általában az adatokat közvetlenül küldjük el további feldolgozásra, ami lehet például az adatbázisba való beillesztés. A feldolgozás során azonban előfordulhat hiba, például a felhasználónév már foglalt. Ebben az esetben a hibát a `addError()` segítségével visszaadjuk az űrlapnak, és hagyjuk, hogy újra kirajzolódjon, hibaüzenettel: +Általában az adatokat azonnal további feldolgozásra küldjük, ami lehet például adatbázisba való beszúrás. A feldolgozás során azonban hiba léphet fel, például a felhasználónév már foglalt. Ebben az esetben a hibát az `addError()` segítségével visszaküldjük az űrlapnak, és újra megjelenítjük, a hibaüzenettel együtt. ```php -$form->addError('Sorry, username is already in use.'); +$form->addError('Elnézést, ezt a felhasználónevet már használja valaki.'); ``` -Az űrlap feldolgozása után átirányítjuk a következő oldalra. Ez megakadályozza, hogy az űrlapot véletlenül újra elküldjék a *frissítés*, *vissza* gombra kattintva, vagy a böngésző előzményeit mozgatva. +Az űrlap feldolgozása után átirányítunk a következő oldalra. Ez megakadályozza az űrlap nem kívánt újraküldését a *frissítés*, *vissza* gombbal vagy a böngésző előzményeiben való mozgással. -Alapértelmezés szerint az űrlapot POST módszerrel küldjük el ugyanarra az oldalra. Mindkettő megváltoztatható: +Az űrlap alapértelmezés szerint POST metódussal és ugyanarra az oldalra küldődik. Mindkettő megváltoztatható: ```php $form->setAction('/submit.php'); $form->setMethod('GET'); ``` -És ez minden :-) Van egy működőképes és tökéletesen [biztosított |#Vulnerability Protection] űrlapunk. +És ez tulajdonképpen minden :-) Van egy működő és tökéletesen [biztonságos |#Védelem a sebezhetőségek ellen] űrlapunk. -Próbáljon meg több [űrlapvezérlőt |controls] hozzáadni. +Próbáljon meg hozzáadni más [űrlap elemeket|controls] is. -Hozzáférés a vezérlőkhöz .[#toc-access-to-controls] -=================================================== +Elemekhez való hozzáférés +========================= -Az űrlapot és az egyes vezérlőelemeket komponenseknek nevezzük. Ezek egy komponensfát hoznak létre, amelynek gyökere az űrlap. Az egyes vezérlőelemeket a következőképpen érheti el: +Az űrlapot és annak egyes elemeit komponenseknek nevezzük. Komponensfát alkotnak, ahol a gyökér maga az űrlap. Az űrlap egyes elemeihez a következő módon férhetünk hozzá: ```php $input = $form->getComponent('name'); @@ -80,37 +80,36 @@ $button = $form->getComponent('send'); // alternatív szintaxis: $button = $form['send']; ``` -A vezérlőelemek eltávolítása az unset használatával történik: +Az elemeket az unset segítségével távolítjuk el: ```php unset($form['name']); ``` -Érvényesítési szabályok .[#toc-validation-rules] -================================================ +Validációs szabályok +==================== -Itt az *érvényes* szót használták, de az űrlapnak még nincsenek érvényesítési szabályai. Javítsuk ki. +Elhangzott az *érvényes* szó, de az űrlapnak még nincsenek validációs szabályai. Javítsuk ki ezt. -A név kötelező lesz, ezért jelöljük meg a `setRequired()` metódussal, amelynek argumentuma a hibaüzenet szövege, amely akkor jelenik meg, ha a felhasználó nem tölti ki. Ha nem adunk meg argumentumot, akkor az alapértelmezett hibaüzenetet használjuk. +A név kötelező lesz, ezért a `setRequired()` metódussal jelöljük meg, amelynek argumentuma a hibaüzenet szövege, amely akkor jelenik meg, ha a felhasználó nem tölti ki a nevet. Ha nem adunk meg argumentumot, az alapértelmezett hibaüzenet kerül felhasználásra. ```php -$form->addText('name', 'Name:') - ->setRequired('Please enter a name.'); +$form->addText('name', 'Név:') + ->setRequired('Kérjük, adja meg a nevét'); ``` -Próbáljuk meg elküldeni az űrlapot a név kitöltése nélkül, és látni fogjuk, hogy hibaüzenet jelenik meg, és a böngésző vagy a szerver elutasítja a kitöltésig. +Próbálja meg elküldeni az űrlapot a név kitöltése nélkül, és látni fogja, hogy hibaüzenet jelenik meg, és a böngésző vagy a szerver addig elutasítja, amíg ki nem tölti a mezőt. -Ugyanakkor nem fogja tudni becsapni a rendszert azzal, hogy például csak szóközöket ír be a beviteli mezőbe. Szó sem lehet róla. A Nette automatikusan levágja a bal és jobb oldali szóközöket. Próbálja ki! Ezt mindig meg kellene tennie minden egysoros bevitelnél, de gyakran elfelejtik. A Nette automatikusan elvégzi. (Megpróbálhatja becsapni az űrlapokat, és többsoros karakterláncot küldhet névként. A Nette még ebben az esetben sem fog becsapni, és a sortörések szóközökre változnak). +Ugyanakkor a rendszert nem lehet becsapni azzal, hogy például csak szóközöket ír a mezőbe. Nem. A Nette automatikusan eltávolítja a bal és jobb oldali szóközöket. Próbálja ki. Ezt minden egysoros beviteli mezővel meg kellene tenni, de gyakran elfelejtik. A Nette ezt automatikusan megteszi. (Megpróbálhatja becsapni az űrlapot, és névként többsoros stringet küldeni. A Nette itt sem hagyja magát becsapni, és a sortöréseket szóközökre cseréli.) -Az űrlap mindig a szerveroldalon validálódik, de a JavaScript validáció is generálódik, ami gyors, és a felhasználó azonnal értesül a hibáról, anélkül, hogy az űrlapot a szerverre kellene küldeni. Ezt a `netteForms.js` szkript kezeli. -Adja hozzá az oldalhoz: +Az űrlap mindig a szerveroldalon validálódik, de JavaScript validáció is generálódik, amely villámgyorsan lefut, és a felhasználó azonnal értesül a hibáról, anélkül, hogy az űrlapot el kellene küldenie a szerverre. Ezt a `netteForms.js` szkript végzi. Illessze be az oldalba: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Ha megnézi az űrlapot tartalmazó oldal forráskódját, észreveheti, hogy a Nette a szükséges mezőket a `required` CSS osztályú elemekbe illeszti be. Próbálja meg hozzáadni a következő stílust a sablonhoz, és a "Név" felirat piros lesz. Elegánsan jelöljük a kötelező mezőket a felhasználók számára: +Ha megnézi az űrlapot tartalmazó oldal forráskódját, észreveheti, hogy a Nette a kötelező elemeket `required` CSS osztállyal rendelkező elemekbe helyezi. Próbálja meg hozzáadni a következő stíluslapot a sablonhoz, és a „Név” címke piros lesz. Így elegánsan jelölhetjük meg a felhasználók számára a kötelező elemeket: ```latte <style> @@ -118,96 +117,96 @@ Ha megnézi az űrlapot tartalmazó oldal forráskódját, észreveheti, hogy a </style> ``` -További érvényesítési szabályokat a `addRule()` módszerrel adunk hozzá. Az első paraméter a szabály, a második ismét a hibaüzenet szövege, és az opcionális érvényesítési szabály argumentum következhet. Mit jelent ez? +További validációs szabályokat az `addRule()` metódussal adunk hozzá. Az első paraméter a szabály, a második ismét a hibaüzenet szövege, és még következhet a validációs szabály argumentuma. Mit jelent ez? -Az űrlap kap egy másik opcionális bemenetet *életkor* azzal a feltétellel, hogy annak számnak kell lennie (`addInteger()`) és bizonyos határok között (`$form::Range`). És itt fogjuk használni a `addRule()` harmadik argumentumát , magát a tartományt: +Az űrlapot kibővítjük egy új, nem kötelező „kor” mezővel, amelynek egész számnak kell lennie (`addInteger()`), és ezen felül egy megengedett tartományban kell lennie (`$form::Range`). És itt pontosan az `addRule()` metódus harmadik paraméterét használjuk, amellyel átadjuk a validátornak a kívánt tartományt `[tól, ig]` párként: ```php -$form->addInteger('age', 'Age:') - ->addRule($form::Range, 'You must be older 18 years and be under 120.', [18, 120]); +$form->addInteger('age', 'Életkor:') + ->addRule($form::Range, 'Az életkornak 18 és 120 között kell lennie', [18, 120]); ``` .[tip] -Ha a felhasználó nem tölti ki a mezőt, akkor az érvényesítési szabályok nem lesznek ellenőrizve, mivel a mező opcionális. +Ha a felhasználó nem tölti ki a mezőt, a validációs szabályok nem kerülnek ellenőrzésre, mivel az elem nem kötelező. -Nyilvánvalóan van hely egy kis refaktorálásra. A hibaüzenetben és a harmadik paraméterben a számok duplikáltan szerepelnek, ami nem ideális. Ha [többnyelvű űrlapot |rendering#translating] hoznánk létre, és a számokat tartalmazó üzenetet több nyelvre kellene lefordítani, ez megnehezítené az értékek módosítását. Emiatt a `%d` helyettesítő karakterek használhatók: +Itt van lehetőség egy kis refaktorálásra. A hibaüzenetben és a harmadik paraméterben a számok duplikáltan szerepelnek, ami nem ideális. Ha [többnyelvű űrlapokat |rendering#Fordítás] hoznánk létre, és a számokat tartalmazó üzenet több nyelvre lenne lefordítva, megnehezítené az értékek esetleges megváltoztatását. Emiatt lehetséges a `%d` helyettesítő karakterek használata, és a Nette kiegészíti az értékeket: ```php - ->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]); + ->addRule($form::Range, 'Az életkornak %d és %d év között kell lennie', [18, 120]); ``` -Térjünk vissza a *jelszó* mezőhöz, tegyük *követelményessé*, és ellenőrizzük a minimális jelszóhosszúságot (`$form::MinLength`), ismét az üzenetben szereplő helyettesítő karakterek segítségével: +Térjünk vissza a `password` elemhez, amelyet szintén kötelezővé teszünk, és még ellenőrizzük a jelszó minimális hosszát (`$form::MinLength`), ismét a helyettesítő karakter használatával: ```php -$form->addPassword('password', 'Password:') - ->setRequired('Pick a password') - ->addRule($form::MinLength, 'Your password has to be at least %d long', 8); +$form->addPassword('password', 'Jelszó:') + ->setRequired('Válasszon jelszót') + ->addRule($form::MinLength, 'A jelszónak legalább %d karakter hosszúnak kell lennie', 8); ``` -Adjunk hozzá egy `passwordVerify` mezőt az űrlaphoz, ahol a felhasználó újra beírja a jelszót, az ellenőrzéshez. Érvényesítési szabályok segítségével ellenőrizzük, hogy mindkét jelszó azonos-e (`$form::Equal`). Érvként pedig szögletes [zárójelek |#Access to Controls] segítségével megadjuk az első jelszóra való hivatkozást: +Adunk hozzá az űrlaphoz még egy `passwordVerify` mezőt, ahol a felhasználó még egyszer megadja a jelszót, ellenőrzés céljából. A validációs szabályok segítségével ellenőrizzük, hogy a két jelszó megegyezik-e (`$form::Equal`). És paraméterként hivatkozást adunk az első jelszóra a [szögletes zárójelek |#Elemekhez való hozzáférés] segítségével: ```php -$form->addPassword('passwordVerify', 'Password again:') - ->setRequired('Fill your password again to check for typo') - ->addRule($form::Equal, 'Password mismatch', $form['password']) +$form->addPassword('passwordVerify', 'Jelszó ellenőrzéshez:') + ->setRequired('Kérjük, adja meg a jelszót még egyszer ellenőrzés céljából') + ->addRule($form::Equal, 'A jelszavak nem egyeznek', $form['password']) ->setOmitted(); ``` -A `setOmitted()` segítségével egy olyan elemet jelöltünk meg, amelynek értéke nem igazán érdekel minket, és amely csak az érvényesítés miatt létezik. Az értékét nem adjuk át a `$data`. +A `setOmitted()` segítségével megjelöltük azt az elemet, amelynek az értéke valójában nem számít, és amely csak a validáció miatt létezik. Az érték nem kerül átadásra a `$data`-ba. -Egy teljesen működőképes űrlapunk van, PHP és JavaScript validációval. A Nette validálási lehetőségei sokkal szélesebb körűek, létrehozhatunk feltételeket, megjeleníthetjük és elrejthetjük az oldal egyes részeit ezek alapján, stb. Mindent megtudhat az [űrlapok validálásáról |validation] szóló fejezetben. +Ezzel van egy teljesen működőképes űrlapunk validációval PHP-ban és JavaScriptben is. A Nette validációs képességei sokkal szélesebbek, lehet feltételeket létrehozni, azok alapján megjeleníteni és elrejteni az oldal részeit stb. Mindent megtudhat az [űrlapok validációjáról|validation] szóló fejezetben. -Alapértelmezett értékek .[#toc-default-values] -============================================== +Alapértelmezett értékek +======================= -Gyakran állítunk be alapértelmezett értékeket űrlapvezérlőkhöz: +Az űrlap elemeinek általában alapértelmezett értékeket állítunk be: ```php -$form->addEmail('email', 'Email') +$form->addEmail('email', 'E-mail') ->setDefaultValue($lastUsedEmail); ``` -Gyakran hasznos, ha egyszerre állítjuk be az összes vezérlőelem alapértelmezett értékeit. Például amikor az űrlapot rekordok szerkesztésére használjuk. Beolvassuk a rekordot az adatbázisból, és beállítjuk az alapértelmezett értékeket: +Gyakran hasznos az összes elem alapértelmezett értékét egyszerre beállítani. Például, ha az űrlap rekordok szerkesztésére szolgál. Beolvassuk a rekordot az adatbázisból, és beállítjuk az alapértelmezett értékeket: ```php //$row = ['name' => 'John', 'age' => '33', /* ... */]; $form->setDefaults($row); ``` -A vezérlőelemek definiálása után hívjuk meg a `setDefaults()` címet. +Hívja a `setDefaults()` metódust az elemek definiálása után. -Az űrlap megjelenítése .[#toc-rendering-the-form] -================================================= +Űrlap megjelenítése +=================== -Az űrlap alapértelmezés szerint táblázatként jelenik meg. Az egyes vezérlőelemek az alapvető webes hozzáférhetőségi irányelveket követik. Minden címke `<label>` elemként jön létre, és a bemenetükhöz kapcsolódik, a címkére kattintva a kurzor a bemenetre kerül. +Alapértelmezés szerint az űrlap táblázatként jelenik meg. Az egyes elemek megfelelnek az alapvető hozzáférhetőségi szabálynak - minden címke `<label>`-ként van megírva, és a megfelelő űrlap elemhez van kapcsolva. A címkére kattintva a kurzor automatikusan az űrlap mezőjébe kerül. -Az egyes elemekhez bármilyen HTML-attribútumot beállíthatunk. Például adjunk hozzá egy helyőrzőt: +Minden elemhez beállíthatunk tetszőleges HTML attribútumokat. Például hozzáadhatunk egy placeholdert: ```php -$form->addInteger('age', 'Age:') - ->setHtmlAttribute('placeholder', 'Please fill in the age'); +$form->addInteger('age', 'Életkor:') + ->setHtmlAttribute('placeholder', 'Kérjük, töltse ki az életkort'); ``` -Valóban sokféleképpen lehet megjeleníteni egy űrlapot, ezért ez egy külön [fejezet a megjelenítésről |rendering]. +Az űrlap megjelenítésének módjai valóban nagyon sokfélék, ezért ennek [külön fejezetet szentelünk a megjelenítésről|rendering]. -Osztályok leképezése .[#toc-mapping-to-classes] -=============================================== +Osztályokra való leképezés +========================== -Térjünk vissza az űrlapadatok feldolgozásához. A `getValues()` metódus a `ArrayHash` objektumként adta vissza a beküldött adatokat. Mivel ez egy általános osztály, valami olyasmi, mint a `stdClass`, hiányozni fog néhány kényelmi funkció a vele való munka során, például a tulajdonságok kódkiegészítése a szerkesztőkben vagy a statikus kódelemzésben. Ezt úgy lehetne megoldani, ha minden űrlaphoz külön osztály lenne, amelynek tulajdonságai az egyes vezérlőelemeket képviselik. Pl: +Térjünk vissza az űrlapadatok feldolgozásához. A `getValues()` metódus az elküldött adatokat `ArrayHash` objektumként adta vissza. Mivel ez egy generikus osztály, valami olyasmi, mint a `stdClass`, hiányozni fog belőle bizonyos kényelem a vele való munka során, mint például a propertyk súgása a szerkesztőkben vagy a statikus kódelemzés. Ezt úgy lehetne megoldani, hogy minden űrlaphoz lenne egy konkrét osztályunk, amelynek propertyjei az egyes elemeket reprezentálják. Pl.: ```php class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -A PHP 8.0-tól kezdve használhatja ezt az elegáns jelölést, amely egy konstruktort használ: +Alternatívaként használhatja a konstruktort: ```php class RegistrationFormData @@ -221,16 +220,18 @@ class RegistrationFormData } ``` -Hogyan mondjuk meg a Nette-nek, hogy az adatokat ennek az osztálynak az objektumaként adja vissza nekünk? Egyszerűbb, mint gondolnád. Mindössze annyit kell tennünk, hogy paraméterként megadjuk a hidratálandó osztály nevét vagy objektumát: +Az adatosztály propertyjei lehetnek enumok is, és automatikusan leképezésre kerülnek. .{data-version:3.2.4} + +Hogyan mondjuk meg a Nette-nek, hogy az adatokat ennek az osztálynak az objektumaiként adja vissza? Könnyebben, mint gondolná. Csak az osztály nevét vagy a hidratálandó objektumot kell paraméterként megadni: ```php $data = $form->getValues(RegistrationFormData::class); $name = $data->name; ``` -A `'array'` is megadható paraméterként, és akkor az adatok tömbként térnek vissza. +Paraméterként megadható az `'array'` is, és akkor az adatokat tömbként adja vissza. -Ha az űrlapok konténerekből álló többszintű struktúrából állnak, hozzon létre mindegyikhez külön osztályt: +Ha az űrlapok többszintű struktúrát alkotnak konténerekből, hozzon létre mindegyikhez külön osztályt: ```php $form = new Form; @@ -247,22 +248,24 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` -A leképezés ekkor a `$person` tulajdonságtípusból tudja, hogy a konténert a `PersonFormData` osztályhoz kell leképeznie. Ha a tulajdonság tárolók tömbjét tartalmazná, adja meg a `array` típust, és adja át a leképezendő osztályt közvetlenül a tárolóhoz: +A leképezés ezután a `$person` property típusából tudja, hogy a konténert a `PersonFormData` osztályra kell leképeznie. Ha a property konténerek tömbjét tartalmazná, adja meg az `array` típust, és adja át a leképezendő osztályt közvetlenül a konténernek: ```php $person->setMappedType(PersonFormData::class); ``` +Az űrlap adatosztályának tervét legeneráltathatja a `Nette\Forms\Blueprint::dataClass($form)` metódussal, amely kiírja azt a böngésző oldalára. A kódot ezután elég egy kattintással kijelölni és a projektbe másolni. .{data-version:3.1.15} + -Többszörös beküldőgombok .[#toc-multiple-submit-buttons] -======================================================== +Több gomb +========= -Ha az űrlapon egynél több gomb van, általában meg kell különböztetnünk, hogy melyiket nyomta meg. A gomb `isSubmittedBy()` metódusa adja vissza nekünk ezt az információt: +Ha az űrlapnak több mint egy gombja van, általában meg kell különböztetnünk, melyiket nyomták meg. Ezt az információt a gomb `isSubmittedBy()` metódusa adja vissza: ```php $form->addSubmit('save', 'Mentés'); @@ -279,37 +282,36 @@ if ($form->isSuccess()) { } ``` -Ne hagyjuk ki a `$form->isSuccess()`, hogy ellenőrizzük az adatok érvényességét. +Ne hagyja ki a `$form->isSuccess()` lekérdezést, ezzel ellenőrzi az adatok érvényességét. -Ha egy űrlapot az <kbd>Enter</kbd> billentyűvel küldünk be, azt úgy kezeljük, mintha az első gombbal küldtük volna be. +Amikor az űrlapot az <kbd>Enter</kbd> gombbal küldik el, úgy veszi, mintha az első gombbal küldték volna el. -Sebezhetőségi védelem .[#toc-vulnerability-protection] -====================================================== +Védelem a sebezhetőségek ellen +============================== -A Nette Framework nagy erőfeszítéseket tesz a biztonság érdekében, és mivel az űrlapok a leggyakoribb felhasználói bevitel, a Nette űrlapok szinte áthatolhatatlanok. +A Nette Framework nagy hangsúlyt fektet a biztonságra, ezért gondosan ügyel az űrlapok megfelelő védelmére. -Amellett, hogy védi az űrlapokat az olyan jól ismert sebezhetőségek támadása ellen, mint a [Cross-Site Scripting (XSS |nette:glossary#cross-site-scripting-xss] ) és a [Cross-Site Request Forgery (CSRF |nette:glossary#cross-site-request-forgery-csrf] ), sok apró biztonsági feladatot is elvégez, amelyekre már nem kell gondolnia. +Amellett, hogy az űrlapokat megvédi a [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] és a [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF] támadásoktól, számos apró biztonsági intézkedést tesz, amelyekre Önnek már nem kell gondolnia. -Például kiszűri az összes vezérlő karaktert a bemenetekből, és ellenőrzi az UTF-8 kódolás érvényességét, így az űrlapból származó adatok mindig tiszták lesznek. A kiválasztó dobozok és rádiólisták esetében ellenőrzi, hogy a kiválasztott elemek valóban a felkínáltak közül kerültek-e ki, és nem történt-e hamisítás. Már említettük, hogy az egysoros szövegbevitel esetében eltávolítja a sor végi karaktereket, amelyeket egy támadó oda küldhet. Többsoros bevitel esetén normalizálja a sor végi karaktereket. És így tovább. +Például kiszűri az összes vezérlőkaraktert a bemenetekből, és ellenőrzi az UTF-8 kódolás érvényességét, így az űrlap adatai mindig tiszták lesznek. A select boxoknál és radio listáknál ellenőrzi, hogy a kiválasztott elemek valóban a felajánlottak közül valók-e, és nem történt-e hamisítás. Már említettük, hogy az egysoros szöveges beviteleknél eltávolítja a sorvégi karaktereket, amelyeket a támadó küldhetett volna. A többsoros beviteleknél pedig normalizálja a sorvégi karaktereket. És így tovább. -A Nette olyan biztonsági réseket is kijavít az Ön számára, amelyekről a legtöbb programozónak fogalma sincs, hogy léteznek. +A Nette megoldja Ön helyett azokat a biztonsági kockázatokat, amelyekről sok programozó nem is tudja, hogy léteznek. -Az említett CSRF-támadás lényege, hogy a támadó egy olyan oldal meglátogatására csábítja az áldozatot, amely némán végrehajt egy kérést az áldozat böngészőjében a szerver felé, ahová az áldozat éppen be van jelentkezve, és a szerver azt hiszi, hogy a kérést az áldozat akaratából tette. Ezért a Nette megakadályozza, hogy az űrlapot egy másik tartományból POST-on keresztül küldjék el. Ha valamilyen oknál fogva ki akarja kapcsolni a védelmet, és engedélyezni szeretné, hogy az űrlapot egy másik tartományból küldjék el, használja a következőt: +Az említett CSRF támadás lényege, hogy a támadó egy olyan oldalra csalja az áldozatot, amely észrevétlenül végrehajt egy kérést az áldozat böngészőjében ahhoz a szerverhez, amelyen az áldozat be van jelentkezve, és a szerver azt hiszi, hogy a kérést az áldozat saját akaratából hajtotta végre. Ezért a Nette megakadályozza a POST űrlapok küldését más domainről. Ha valamilyen okból ki akarja kapcsolni a védelmet, és engedélyezni szeretné az űrlap küldését más domainről, használja a következőt: ```php $form->allowCrossOrigin(); // FIGYELEM! Kikapcsolja a védelmet! ``` -Ez a védelem a `_nss` nevű SameSite cookie-t használja. Ezért hozzon létre egy űrlapot az első kimenet kiürítése előtt, hogy a cookie-t el lehessen küldeni. +Ez a védelem a `_nss` nevű SameSite cookie-t használja. Ezért hozza létre az űrlap objektumot még az első kimenet elküldése előtt, hogy a cookie elküldhető legyen. -A SameSite cookie-védelem nem biztos, hogy 100%-ig megbízható, ezért érdemes bekapcsolni a token-védelmet: +A SameSite cookie-val történő védelem nem feltétlenül 100%-ban megbízható, ezért célszerű bekapcsolni a token alapú védelmet is: ```php $form->addProtection(); ``` -Erősen ajánlott ezt a védelmet alkalmazni az alkalmazás adminisztrációs részében lévő, érzékeny adatokat módosító űrlapokra. A keretrendszer a CSRF-támadás ellen a munkamenetben tárolt hitelesítési token generálásával és érvényesítésével védekezik (az érv a token lejárta esetén megjelenő hibaüzenet). Ezért szükséges, hogy az űrlap megjelenítése előtt elinduljon egy munkamenet. A webhely adminisztrációs részében a munkamenet általában már elindult, a felhasználó bejelentkezése miatt. -Ellenkező esetben a munkamenetet a `Nette\Http\Session::start()` metódussal kell elindítani. +Javasoljuk, hogy így védje azokat az űrlapokat a webhely adminisztrációs részében, amelyek érzékeny adatokat módosítanak az alkalmazásban. A keretrendszer a CSRF támadás ellen egy engedélyezési token generálásával és ellenőrzésével védekezik, amelyet a sessionben tárol. Ezért az űrlap megjelenítése előtt nyitott sessionre van szükség. A webhely adminisztrációs részében általában már elindult a session a felhasználó bejelentkezése miatt. Ellenkező esetben indítsa el a sessiont a `Nette\Http\Session::start()` metódussal. -Tehát van egy gyors bevezetésünk a Nette űrlapokba. További inspirációért próbálja megkeresni a disztribúcióban található [példák |https://github.com/nette/forms/tree/master/examples] könyvtárát. +Nos, ezzel végeztünk a Nette űrlapjainak gyors bemutatásával. Próbáljon meg még belenézni a disztribúció [examples|https://github.com/nette/forms/tree/master/examples] könyvtárába, ahol további inspirációt találhat. diff --git a/forms/hu/validation.texy b/forms/hu/validation.texy index c75844fbdb..78249dc48a 100644 --- a/forms/hu/validation.texy +++ b/forms/hu/validation.texy @@ -1,173 +1,184 @@ -Nyomtatványok érvényesítés -************************** +Űrlap validáció +*************** -Kötelező vezérlők .[#toc-required-controls] -=========================================== +Kötelező elemek +=============== -A vezérlőelemeket a `setRequired()` metódussal jelöljük meg kötelezőnek, amelynek argumentuma a [hibaüzenet |#Error Messages] szövege, amely akkor jelenik meg, ha a felhasználó nem tölti ki. Ha nem adunk meg argumentumot, akkor az alapértelmezett hibaüzenetet használjuk. +A kötelező elemeket a `setRequired()` metódussal jelöljük meg, amelynek argumentuma a [#hibaüzenetek] hibaüzenet szövege, amely akkor jelenik meg, ha a felhasználó nem tölti ki az elemet. Ha nem adunk meg argumentumot, az alapértelmezett hibaüzenet kerül felhasználásra. ```php -$form->addText('name', 'Name:') - ->setRequired('Please fill your name.'); +$form->addText('name', 'Név:') + ->setRequired('Kérjük, adja meg a nevét'); ``` -Szabályok .[#toc-rules] -======================= +Szabályok +========= -A `addRule()` módszerrel adunk hozzá érvényesítési szabályokat a vezérlőkhöz. Az első paraméter a szabály, a második a [hibaüzenet |#Error Messages], a harmadik pedig az érvényesítési szabály argumentuma. +Validációs szabályokat az elemekhez az `addRule()` metódussal adunk hozzá. Az első paraméter a szabály, a második a [#hibaüzenetek] hibaüzenet szövege, a harmadik pedig a validációs szabály argumentuma. ```php -$form->addPassword('password', 'Password:') - ->addRule($form::MinLength, 'Password must be at least %d characters', 8); +$form->addPassword('password', 'Jelszó:') + ->addRule($form::MinLength, 'A jelszónak legalább %d karakter hosszúnak kell lennie', 8); ``` -A Nette számos beépített szabályt tartalmaz, amelyek neve a `Nette\Forms\Form` osztály konstansa: +**A validációs szabályok csak akkor kerülnek ellenőrzésre, ha a felhasználó kitöltötte az elemet.** -A következő szabályokat használhatjuk az összes vezérlőelemhez: +A Nette számos előre definiált szabállyal rendelkezik, amelyek nevei a `Nette\Forms\Form` osztály konstansai. Minden elemhez használhatjuk ezeket a szabályokat: -| konstans | leírás | érvek +| konstans | leírás | argumentum típusa |------- -| `Required` | `setRequired()` álneve | - - -| `Filled` | a `setRequired()` álneve | - -| `Blank` | nem szabad kitölteni | - -| `Equal` | érték egyenlő a paraméterrel | `mixed` -| `NotEqual` | érték nem egyenlő a paraméterrel | `mixed` -| `IsIn` | érték egyenlő a tömb valamelyik elemével | `array` -| `IsNotIn` | érték nem egyenlő a tömb egyetlen elemével sem | `array` -| `Valid` | a bemenet átmegy az érvényesítésen ( [feltételek |#conditions] esetén) | - - -A `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` vezérlőkhöz a következő szabályok is használhatók: - -| `MinLength` | minimális karakterlánc-hossz | `int` -| `MaxLength` | maximális karakterlánc-hossz | `int` -| `Length` | hossz a tartományban vagy pontos hossz | pár `[int, int]` vagy `int` -| `Email` | érvényes e-mail cím | - -| `URL` | érvényes URL | - -| `Pattern` | megfelel a szabályos mintának | `string` -| `PatternInsensitive` | mint a `Pattern`, de a nagy- és kisbetűket figyelmen kívül hagyja | `string` -| `Integer` | egész szám | - -| `Numeric` | `Integer` álneve | - -| `Float` | egész vagy lebegőpontos szám | - -| `Min` | az egész szám értékének minimuma | `int\|float` -| `Max` | az egész szám értékének maximuma | `int\|float` -| `Range` | érték a tartományban | pár `[int\|float, int\|float]` +| `Required` | kötelező elem, alias a `setRequired()` számára | - +| `Filled` | kötelező elem, alias a `setRequired()` számára | - +| `Blank` | az elem nem lehet kitöltve | - +| `Equal` | az érték megegyezik a paraméterrel | `mixed` +| `NotEqual` | az érték nem egyezik meg a paraméterrel | `mixed` +| `IsIn` | az érték megegyezik a tömb valamelyik elemével | `array` +| `IsNotIn` | az érték nem egyezik meg a tömb egyik elemével sem | `array` +| `Valid` | az elem helyesen van kitöltve? ([#feltételek] számára) | - + -A `Integer`, `Numeric` és `Float` szabályok automatikusan egész számra (illetve lebegőszámra) konvertálják az értéket. Továbbá a `URL` szabály elfogad egy séma nélküli címet is (pl. `nette.org`), és kiegészíti a sémát (`https://nette.org`). -A `Pattern` és a `PatternInsensitive` kifejezéseknek a teljes értékre érvényesnek kell lenniük, azaz mintha a `^` and `$` karakterekbe lenne csomagolva. +Szöveges bevitelek +------------------ + +Az `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`, `addFloat()` elemekhez használhatók a következő szabályok is: + +| `MinLength` | minimális szöveghossz | `int` +| `MaxLength` | maximális szöveghossz | `int` +| `Length` | hossz tartományban vagy pontos hossz | pár `[int, int]` vagy `int` +| `Email` | érvényes e-mail cím | - +| `URL` | abszolút URL | - +| `Pattern` | megfelel a reguláris kifejezésnek | `string` +| `PatternInsensitive` | mint a `Pattern`, de kis- és nagybetű érzéketlen | `string` +| `Integer` | egész szám érték | - +| `Numeric` | alias az `Integer` számára | - +| `Float` | szám | - +| `Min` | numerikus elem minimális értéke | `int\|float` +| `Max` | numerikus elem maximális értéke | `int\|float` +| `Range` | érték tartományban | pár `[int\|float, int\|float]` -A `addUpload()`, `addMultiUpload()` vezérlőkre a következő szabályok is alkalmazhatók: +Az `Integer`, `Numeric` és `Float` validációs szabályok azonnal átalakítják az értéket integerre, illetve floatra. Továbbá az `URL` szabály elfogadja a séma nélküli címet is (pl. `nette.org`), és kiegészíti a sémát (`https://nette.org`). A `Pattern` és `PatternIcase` kifejezésnek az egész értékre kell érvényesnek lennie, azaz mintha `^` és `$` karakterekkel lenne körbevéve. -| `MaxFileSize` | maximális fájlméret | `int` -| `MimeType` | MIME-típus, elfogadja a helyettesítő karaktereket (`'video/*'`) | `string\|string[]` -| `Image` | a feltöltött fájl JPEG, PNG, GIF, WebP | - -| `Pattern` | a fájl neve megfelel a reguláris kifejezésnek | `string` -| `PatternInsensitive` | mint a `Pattern`, de a nagy- és kisbetűket nem érzékeli | `string` -A `MimeType` és a `Image` a `fileinfo` PHP kiterjesztést igényli. Az, hogy egy fájl vagy kép a kívánt típusba tartozik-e, az aláírás alapján állapítható meg. A teljes fájl sértetlenségét nem ellenőrzi a rendszer. Azt, hogy egy kép nem sérült-e, például úgy lehet megállapítani, ha megpróbáljuk [betölteni |http:request#toImage]. +Elemek száma +------------ -A `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()` vezérlőkhöz a következő szabályok is használhatók a kiválasztott elemek, illetve a feltöltött fájlok számának korlátozására: +Az `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()` elemekhez használhatók a következő szabályok is a kiválasztott elemek, illetve feltöltött fájlok számának korlátozására: | `MinLength` | minimális szám | `int` | `MaxLength` | maximális szám | `int` -| `Length` | szám a tartományban vagy pontos szám | pár `[int, int]` vagy `int` +| `Length` | szám tartományban vagy pontos szám | pár `[int, int]` vagy `int` + + +Fájlfeltöltések +--------------- + +Az `addUpload()`, `addMultiUpload()` elemekhez használhatók a következő szabályok is: + +| `MaxFileSize` | maximális fájlméret bájtban | `int` +| `MimeType` | MIME típus, helyettesítő karakterek engedélyezettek (`'video/*'`) | `string\|string[]` +| `Image` | JPEG, PNG, GIF, WebP, AVIF kép | - +| `Pattern` | a fájlnév megfelel a reguláris kifejezésnek | `string` +| `PatternInsensitive` | mint a `Pattern`, de kis- és nagybetű érzéketlen | `string` + +A `MimeType` és `Image` szabályokhoz szükség van a `fileinfo` PHP kiterjesztésre. Azt, hogy a fájl vagy kép a kívánt típusú-e, az aláírása alapján észlelik, és **nem ellenőrzik az egész fájl integritását.** Azt, hogy a kép nem sérült-e, például a [betöltésével |http:request#toImage] lehet megállapítani. -Hibaüzenetek .[#toc-error-messages] ------------------------------------ +Hibaüzenetek +============ -A `Pattern` és a `PatternInsensitive` kivételével minden előre definiált szabály rendelkezik alapértelmezett hibaüzenettel, így ezek elhagyhatók. Az összes testreszabott üzenet átadásával és megfogalmazásával azonban felhasználóbarátabbá teszi az űrlapot. +Minden előre definiált szabálynak, kivéve a `Pattern` és `PatternInsensitive` szabályokat, van alapértelmezett hibaüzenete, így azt el lehet hagyni. Azonban az összes üzenet testreszabott megadásával és megfogalmazásával felhasználóbarátabbá teheti az űrlapot. -Az alapértelmezett üzeneteket a [forms:configuration-ban |forms:configuration], a `Nette\Forms\Validator::$messages` tömbben lévő szövegek módosításával vagy a [translator |rendering#translating] használatával módosíthatja. +Az alapértelmezett üzeneteket megváltoztathatja a [konfigurációban|forms:configuration], a `Nette\Forms\Validator::$messages` tömb szövegeinek módosításával, vagy a [fordító |rendering#Fordítás] használatával. -A hibaüzenetek szövegében a következő helyettesítő karakterek használhatók: +A hibaüzenetek szövegében a következő helyettesítő stringek használhatók: -| `%d` | fokozatosan helyettesíti a szabályokat az argumentumok után. -| `%n$d` | az n-edik szabály argumentumával helyettesíti. -| `%label` | a mező címkéjével helyettesíti (kettőspont nélkül) -| `%name` | a mező nevével helyettesíti (pl. `name`). -| `%value` | a felhasználó által megadott értékkel helyettesíti. +| `%d` | sorban helyettesíti a szabály argumentumaival +| `%n$d` | helyettesíti a szabály n-edik argumentumával +| `%label` | helyettesíti az elem címkéjével (kettőspont nélkül) +| `%name` | helyettesíti az elem nevével (pl. `name`) +| `%value` | helyettesíti a felhasználó által beírt értékkel ```php -$form->addText('name', 'Name:') - ->setRequired('Please fill in %label'); +$form->addText('name', 'Név:') + ->setRequired('Kérjük, töltse ki a %label mezőt'); $form->addInteger('id', 'ID:') - ->addRule($form::Range, 'at least %d and no more than %d', [5, 10]); + ->addRule($form::Range, 'legalább %d és legfeljebb %d', [5, 10]); $form->addInteger('id', 'ID:') - ->addRule($form::Range, 'no more than %2$d and at least %1$d', [5, 10]); + ->addRule($form::Range, 'legfeljebb %2$d és legalább %1$d', [5, 10]); ``` -Feltételek .[#toc-conditions] -============================= +Feltételek +========== -Az érvényesítési szabályok mellett feltételek is beállíthatók. Ezek beállítása hasonlóan történik, mint a szabályoké, azonban a `addCondition()` helyett a `addRule()` címet használjuk, és természetesen hibaüzenet nélkül hagyjuk (a feltétel csak kérdez): +A szabályokon kívül feltételeket is hozzáadhatunk. Ezeket hasonlóan írjuk, mint a szabályokat, csak az `addRule()` helyett az `addCondition()` metódust használjuk, és természetesen nem adunk meg hibaüzenetet (a feltétel csak kérdez): ```php $form->addPassword('password', 'Jelszó:') - // ha a jelszó nem hosszabb 8 karakternél ... + // ha a jelszó nem hosszabb 8 karakternél ->addCondition($form::MaxLength, 8) - // ... akkor egy számot kell tartalmaznia. - ->addRule($form::Pattern, 'Számot kell tartalmaznia', '.*[0-9].*'); + // akkor számjegyet kell tartalmaznia + ->addRule($form::Pattern, 'Számjegyet kell tartalmaznia', '.*[0-9].*'); ``` -A feltétel a `addConditionOn()` segítségével az aktuális elemtől eltérő elemhez is kapcsolható. Az első paraméter a mezőre való hivatkozás. A következő esetben az e-mail csak akkor lesz kötelező, ha a jelölőnégyzet be van jelölve (azaz az értéke `true`): +A feltételt az aktuális elemen kívül más elemhez is köthetjük az `addConditionOn()` segítségével. Első paraméterként az elemre való hivatkozást adjuk meg. Ebben a példában az e-mail csak akkor lesz kötelező, ha a checkbox be van jelölve (az értéke true lesz): ```php -$form->addCheckbox('newsletters', 'send me newsletters'); +$form->addCheckbox('newsletters', 'küldjenek nekem hírleveleket'); -$form->addEmail('email', 'Email:') - // ha a jelölőnégyzet be van jelölve ... +$form->addEmail('email', 'E-mail:') + // ha a checkbox be van jelölve ->addConditionOn($form['newsletters'], $form::Equal, true) - // ... megköveteli az email + // akkor követelje meg az e-mailt ->setRequired('Adja meg az e-mail címét'); ``` -A feltételek a `elseCondition()` és a `endCondition()` metódusokkal összetett struktúrákba csoportosíthatók. +A feltételekből komplex struktúrákat hozhatunk létre az `elseCondition()` és `endCondition()` segítségével: ```php $form->addText(/* ... */) - ->addCondition(/* ... */) // ha az első feltétel teljesül. - ->addConditionOn(/* ... */) // és a második feltétel egy másik elemre is. - ->addRule(/* ... */) // megköveteli ezt a szabályt + ->addCondition(/* ... */) // ha az első feltétel teljesül + ->addConditionOn(/* ... */) // és a második feltétel egy másik elemen + ->addRule(/* ... */) // követelje meg ezt a szabályt ->elseCondition() // ha a második feltétel nem teljesül - ->addRule(/* ... */) // szükség van ezekre a szabályokra. + ->addRule(/* ... */) // követelje meg ezeket a szabályokat ->addRule(/* ... */) ->endCondition() // visszatérünk az első feltételhez ->addRule(/* ... */); ``` -A Nette-ben nagyon egyszerűen reagálhatunk egy feltétel teljesülésére vagy nem teljesülésére a JavaScript oldalán a `toggle()` metódus segítségével, lásd [Dynamic JavaScript |#Dynamic JavaScript]. +A Nette-ben nagyon könnyen reagálhatunk a feltétel teljesülésére vagy nem teljesülésére JavaScript oldalon is a `toggle()` metódus segítségével, lásd [#dinamikus JavaScript]. -Hivatkozások a vezérlők között .[#toc-references-between-controls] -================================================================== +Hivatkozás más elemre +===================== -A szabály vagy feltétel argumentum lehet hivatkozás egy másik elemre. Például dinamikusan érvényesítheti, hogy a `text` annyi karaktert tartalmazzon, ahány karakter a `length` mező értéke: +Szabály vagy feltétel argumentumaként más űrlap elemet is átadhatunk. A szabály ezután a felhasználó által később a böngészőben beírt értéket használja. Így például dinamikusan validálhatjuk, hogy a `password` elem ugyanazt a stringet tartalmazza-e, mint a `password_confirm` elem: ```php -$form->addInteger('length'); -$form->addText('text') - ->addRule($form::Length, null, $form['length']); +$form->addPassword('password', 'Jelszó'); +$form->addPassword('password_confirm', 'Jelszó megerősítése') + ->addRule($form::Equal, 'A megadott jelszavak nem egyeznek', $form['password']); ``` -Egyéni szabályok és feltételek .[#toc-custom-rules-and-conditions] -================================================================== +Egyéni szabályok és feltételek +============================== -Néha olyan helyzetbe kerülünk, amikor a Nette beépített érvényesítési szabályai nem elegendőek, és a felhasználó adatait saját módon kell érvényesítenünk. A Nette-ben ez nagyon egyszerű! +Néha olyan helyzetbe kerülünk, amikor a Nette beépített validációs szabályai nem elegendőek, és a felhasználótól származó adatokat a saját módunkon kell validálnunk. A Nette-ben ez nagyon egyszerű! -A `addRule()` vagy a `addCondition()` metódusok első paramétereként bármilyen visszahívást átadhatunk. A callback első paraméterként magát az elemet fogadja el, és egy bóluszi értéket ad vissza, amely jelzi, hogy az érvényesítés sikeres volt-e. Szabály hozzáadásakor a `addRule()` segítségével további argumentumok adhatók át, amelyek második paraméterként kerülnek átadásra. +Az `addRule()` vagy `addCondition()` metódusoknak első paraméterként tetszőleges callbacket adhatunk át. Ez első paraméterként magát az elemet kapja, és boolean értéket ad vissza, amely meghatározza, hogy a validáció rendben lezajlott-e. Az `addRule()` segítségével történő szabály hozzáadásakor további argumentumokat is megadhatunk, ezeket aztán második paraméterként adjuk át. -Az egyéni érvényesítőkészlet tehát statikus metódusokkal rendelkező osztályként hozható létre: +Így létrehozhatunk egy saját validátor készletet osztályként statikus metódusokkal: ```php class MyValidators { - // teszteli, hogy az érték osztható-e az argumentummal. + // teszteli, hogy az érték osztható-e az argumentummal public static function validateDivisibility(BaseControl $input, $arg): bool { return $input->getValue() % $arg === 0; @@ -180,18 +191,18 @@ class MyValidators } ``` -A használat nagyon egyszerű: +A használat ezután nagyon egyszerű: ```php $form->addInteger('num') ->addRule( [MyValidators::class, 'validateDivisibility'], - 'The value must be a multiple of %d', + 'Az értéknek a %d szám többszörösének kell lennie', 8, ); ``` -Egyéni érvényesítési szabályokat is hozzá lehet adni a JavaScripthez. Az egyetlen követelmény, hogy a szabály statikus metódus legyen. Nevét a JavaScript-érvényesítő számára az osztály neve (backslashes nélkül) `\`, the underscore `_`, és a metódus neve egymás mellé fűzésével hozzuk létre. Például írjuk a `App\MyValidators::validateDivisibility` -t `AppMyValidators_validateDivisibility` néven, és adjuk hozzá a `Nette.validators` objektumhoz: +Egyéni validációs szabályokat JavaScripthez is hozzáadhatunk. A feltétel az, hogy a szabály statikus metódus legyen. A neve a JavaScript validátor számára az osztály nevének a visszaperjelek `\` nélküli, aláhúzásjel `_` és a metódus nevének összekapcsolásával jön létre. Pl. az `App\MyValidators::validateDivisibility`-t `AppMyValidators_validateDivisibility`-ként írjuk, és hozzáadjuk a `Nette.validators` objektumhoz: ```js Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => { @@ -200,12 +211,12 @@ Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => ``` -Esemény onValidate .[#toc-event-onvalidate] -=========================================== +onValidate esemény +================== -Az űrlap elküldése után az érvényesítés a `addRule()` által hozzáadott egyedi szabályok ellenőrzésével, majd a `onValidate`[esemény |nette:glossary#Events] meghívásával történik. Kezelője további érvényesítésre használható, jellemzően több űrlapelem értékének helyes kombinációjának ellenőrzésére. +Az űrlap elküldése után validáció történik, amely során ellenőrzésre kerülnek az `addRule()` segítségével hozzáadott egyes szabályok, majd kiváltódik az [esemény |nette:glossary#Eventek események] `onValidate`. Ennek a kezelőjét (handler) kiegészítő validációra használhatjuk, tipikusan az értékek helyes kombinációjának ellenőrzésére több űrlap elemben. -Ha hibát észlel, azt a `addError()` metódus segítségével továbbítja az űrlapnak. Ezt vagy egy adott elemen, vagy közvetlenül az űrlapon lehet meghívni. +Ha hibát észlelünk, azt az `addError()` metódussal adjuk át az űrlapnak. Ezt vagy egy konkrét elemen, vagy közvetlenül az űrlapon hívhatjuk meg. ```php protected function createComponentSignInForm(): Form @@ -225,10 +236,10 @@ public function validateSignInForm(Form $form, \stdClass $data): void ``` -Hibák feldolgozása .[#toc-processing-errors] -============================================ +Hibák a feldolgozás során +========================= -Sok esetben egy érvényes űrlap feldolgozása közben fedezünk fel hibát, például amikor új bejegyzést írunk az adatbázisba, és egy duplikált kulcsot találunk. Ebben az esetben a `addError()` metódus segítségével adjuk vissza a hibát az űrlapnak. Ezt vagy egy adott elemen, vagy közvetlenül az űrlapon hívhatjuk meg: +Sok esetben csak akkor értesülünk a hibáról, amikor az érvényes űrlapot dolgozzuk fel, például új elemet írunk az adatbázisba, és kulcsduplikációba ütközünk. Ebben az esetben a hibát ismét az `addError()` metódussal adjuk át az űrlapnak. Ezt vagy egy konkrét elemen, vagy közvetlenül az űrlapon hívhatjuk meg: ```php try { @@ -238,72 +249,71 @@ try { } catch (Nette\Security\AuthenticationException $e) { if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) { - $form->addError('Invalid password.'); + $form->addError('Érvénytelen jelszó.'); } } ``` -Ha lehetséges, javasoljuk, hogy a hibát közvetlenül az űrlap elemhez adjuk hozzá, mivel az alapértelmezett renderelő használata esetén a hiba mellette fog megjelenni. +Ha lehetséges, javasoljuk, hogy a hibát közvetlenül az űrlap eleméhez csatolja, mert az alapértelmezett renderer használatakor mellette jelenik meg. ```php -$form['date']->addError('Sorry, this date is already taken.'); +$form['date']->addError('Elnézést, de ez a dátum már foglalt.'); ``` -A `addError()` többször is meghívható, hogy több hibaüzenetet adjon át egy űrlapnak vagy elemnek. Ezeket a `getErrors()` segítségével kapja meg. +Az `addError()` metódust ismételten meghívhatja, és így több hibaüzenetet adhat át az űrlapnak vagy elemnek. Ezeket a `getErrors()` segítségével szerezheti meg. -Vegye figyelembe, hogy a `$form->getErrors()` az összes hibaüzenet összefoglalóját adja vissza, még a közvetlenül az egyes elemeknek átadottakat is, nem csak közvetlenül az űrlapnak. A csak az űrlapnak átadott hibaüzeneteket a `$form->getOwnErrors()` segítségével kapjuk meg. +Figyelem, a `$form->getErrors()` az összes hibaüzenet összegzését adja vissza, beleértve azokat is, amelyeket közvetlenül az egyes elemekhez adtak át, nem csak közvetlenül az űrlaphoz. A csak az űrlaphoz átadott hibaüzeneteket a `$form->getOwnErrors()` segítségével szerezheti meg. -A beviteli értékek módosítása .[#toc-modifying-input-values] -============================================================ +Bemenet módosítása +================== -A `addFilter()` módszerrel módosíthatjuk a felhasználó által megadott értéket. Ebben a példában megtűrjük és eltávolítjuk a szóközöket az irányítószámban: +Az `addFilter()` metódus segítségével módosíthatjuk a felhasználó által beírt értéket. Ebben a példában toleráljuk és eltávolítjuk a szóközöket az irányítószámban: ```php $form->addText('zip', 'Irányítószám:') ->addFilter(function ($value) { - return str_replace(' ', '', $value); // eltávolítjuk a szóközöket az irányítószámból. + return str_replace(' ', '', $value); // eltávolítjuk a szóközöket az irányítószámból }) - ->addRule($form::Pattern, 'Az irányítószám nem ötjegyű', '\d{5}'); + ->addRule($form::Pattern, 'Az irányítószám nem öt számjegyű', '\d{5}'); ``` -A szűrő az érvényesítési szabályok és feltételek között szerepel, ezért függ a módszerek sorrendjétől, azaz a szűrő és a szabály meghívása ugyanabban a sorrendben történik, mint a `addFilter()` és a `addRule()` módszerek sorrendje. +A szűrő beépül a validációs szabályok és feltételek közé, tehát a metódusok sorrendje számít, azaz a szűrő és a szabály abban a sorrendben hívódik meg, ahogy az `addFilter()` és `addRule()` metódusok sorrendje van. -JavaScript érvényesítés .[#toc-javascript-validation] -===================================================== +JavaScript validáció +==================== -Az érvényesítési szabályok és feltételek nyelve nagy teljesítményű. Annak ellenére, hogy minden konstrukció szerver- és kliensoldalon egyaránt működik, JavaScriptben. A szabályok átadása HTML-attribútumokban történik `data-nette-rules` JSON-ként. -Magát az érvényesítést egy másik szkript kezeli, amely az űrlap összes `submit` eseményét megakasztja, iterálja az összes bemenetet és lefuttatja a megfelelő érvényesítéseket. +A feltételek és szabályok megfogalmazásának nyelve nagyon erős. Minden konstrukció működik mind a szerveroldalon, mind a JavaScript oldalon. HTML attribútumokban `data-nette-rules` JSON formátumban kerülnek átadásra. Magát a validációt pedig egy szkript végzi, amely elfogja az űrlap `submit` eseményét, végigmegy az egyes elemeken, és végrehajtja a megfelelő validációt. -Ez a szkript a `netteForms.js`, amely több lehetséges forrásból is elérhető: +Ez a szkript a `netteForms.js`, és több lehetséges forrásból érhető el: -A szkriptet közvetlenül a CDN-ből származó HTML-oldalba ágyazhatja be: +A szkriptet közvetlenül beillesztheti a HTML oldalba egy CDN-ről: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Vagy másolja be helyileg a projekt nyilvános mappájába (pl. a `vendor/nette/forms/src/assets/netteForms.min.js`): +Vagy másolja helyileg a projekt nyilvános mappájába (pl. a `vendor/nette/forms/src/assets/netteForms.min.js` fájlból): ```latte <script src="/path/to/netteForms.min.js"></script> ``` -Vagy telepítse az [npm |https://www.npmjs.com/package/nette-forms] segítségével: +Vagy telepítse [npm|https://www.npmjs.com/package/nette-forms] segítségével: ```shell npm install nette-forms ``` -Aztán töltsd be és futtasd: +Majd töltse be és futtassa: ```js import netteForms from 'nette-forms'; netteForms.initOnLoad(); ``` -Alternatívaként közvetlenül a `vendor` mappából is betöltheti: +Alternatívaként betöltheti közvetlenül a `vendor` mappából: ```js import netteForms from '../path/to/vendor/nette/forms/src/assets/netteForms.js'; @@ -311,10 +321,10 @@ netteForms.initOnLoad(); ``` -Dinamikus JavaScript .[#toc-dynamic-javascript] -=============================================== +Dinamikus JavaScript +==================== -Csak akkor szeretné megjeleníteni a cím mezőt, ha a felhasználó úgy dönt, hogy postai úton küldi el az árut? Nem probléma. A kulcs a `addCondition()` & `toggle()` metóduspár: +Szeretné megjeleníteni a cím megadására szolgáló mezőket csak akkor, ha a felhasználó postai kézbesítést választ? Semmi probléma. A kulcs az `addCondition()` & `toggle()` metóduspár: ```php $form->addCheckbox('send_it') @@ -322,25 +332,25 @@ $form->addCheckbox('send_it') ->toggle('#address-container'); ``` -Ez a kód azt mondja, hogy ha a feltétel teljesül, vagyis ha a jelölőnégyzet be van jelölve, akkor a `#address-container` HTML-elem láthatóvá válik. És fordítva. Tehát a címzett címét tartalmazó űrlapelemeket egy ilyen azonosítóval rendelkező konténerbe helyezzük, és amikor a jelölőnégyzetre kattintunk, elrejtjük vagy megjelenítjük őket. Ezt a `netteForms.js` szkript kezeli. +Ez a kód azt mondja, hogy amikor a feltétel teljesül, tehát amikor a checkbox be van jelölve, a `#address-container` HTML elem látható lesz. És fordítva. A címzett címét tartalmazó űrlap elemeket tehát egy ilyen ID-jű konténerbe helyezzük, és a checkboxra kattintva elrejtődnek vagy megjelennek. Ezt a `netteForms.js` szkript biztosítja. -A `toggle()` metódusnak argumentumként bármilyen szelektor átadható. Történelmi okokból a más speciális karaktereket nem tartalmazó alfanumerikus karakterláncot elemazonosítóként kezeljük, ugyanúgy, mintha a `#` character. The second optional parameter allows us to reverse the behavior, i.e. if we used `toggle('#address-container', false)` lenne előtte, az elem csak akkor jelenne meg, ha a jelölőnégyzet nincs bejelölve. +A `toggle()` metódus argumentumaként tetszőleges selectort adhatunk át. Történelmi okokból az alfanumerikus string további speciális karakterek nélkül elem ID-ként értelmeződik, tehát ugyanúgy, mintha `#` karakter előzné meg. A második, nem kötelező paraméter lehetővé teszi a viselkedés megfordítását, azaz ha a `toggle('#address-container', false)`-t használnánk, az elem éppen ellenkezőleg, csak akkor jelenne meg, ha a checkbox nem lenne bejelölve. -Az alapértelmezett JavaScript implementáció megváltoztatja az elemek `hidden` tulajdonságát. A viselkedést azonban könnyen megváltoztathatjuk, például egy animáció hozzáadásával. Csak írjuk felül a `Nette.toggle` metódust JavaScriptben egy egyéni megoldással: +Az alapértelmezett implementáció JavaScriptben az elemek `hidden` propertyjét változtatja meg. A viselkedést azonban könnyen megváltoztathatjuk, például animációt adhatunk hozzá. Elég JavaScriptben felülírni a `Nette.toggle` metódust saját megoldással: ```js Nette.toggle = (selector, visible, srcElement, event) => { document.querySelectorAll(selector).forEach((el) => { - // hide or show 'el' according to the value of 'visible' + // elrejtjük vagy megjelenítjük az 'el'-t a 'visible' értékétől függően }); }; ``` -Az érvényesítés letiltása .[#toc-disabling-validation] -====================================================== +Validáció kikapcsolása +====================== -Bizonyos esetekben le kell tiltani az érvényesítést. Ha egy beküldőgombnak nem kellene érvényesítést futtatnia a beküldés után (például *Mondás* vagy *Közlemény* gomb), akkor az érvényesítést a `$submit->setValidationScope([])` meghívásával tilthatja le. Az űrlapot részlegesen is érvényesítheti az érvényesítendő elemek megadásával. +Néha hasznos lehet a validáció kikapcsolása. Ha a küldés gomb megnyomása nem kell, hogy validációt végezzen (alkalmas *Cancel* vagy *Preview* gombokhoz), kikapcsoljuk a `$submit->setValidationScope([])` metódussal. Ha csak részleges validációt kell végeznie, megadhatjuk, mely mezők vagy űrlap konténerek validálódjanak. ```php $form->addText('name') @@ -352,15 +362,15 @@ $details->addInteger('age') $details->addInteger('age2') ->setRequired('age2'); -$form->addSubmit('send1'); // Az egész űrlap hitelesítése +$form->addSubmit('send1'); // Az egész űrlapot validálja $form->addSubmit('send2') - ->setValidationScope([]); // Nem érvényesít semmit. + ->setValidationScope([]); // Egyáltalán nem validál $form->addSubmit('send3') - ->setValidationScope([$form['name']]); // Csak a 'name' mezőt érvényesíti. + ->setValidationScope([$form['name']]); // Csak a name elemet validálja $form->addSubmit('send4') - ->setValidationScope([$form['details']['age']]); // Csak az 'age' mezőt érvényesíti. + ->setValidationScope([$form['details']['age']]); // Csak az age elemet validálja $form->addSubmit('send5') - ->setValidationScope([$form['details']]); // Validálja a 'details' konténert. + ->setValidationScope([$form['details']]); // A details konténert validálja ``` -[Az onValidate esemény |#Event onValidate] az űrlapon mindig meghívásra kerül, és nem befolyásolja a `setValidationScope`. `onValidate` esemény a konténeren csak akkor hívódik meg, ha ez a konténer részleges érvényesítésre van megadva. +A `setValidationScope` nem befolyásolja a [#onValidate esemény] eseményt az űrlapon, amely mindig meghívásra kerül. A konténer `onValidate` eseménye csak akkor kerül kiváltásra, ha ez a konténer részleges validációra van megjelölve. diff --git a/forms/it/@home.texy b/forms/it/@home.texy index 24c6687ba9..d6b50b2441 100644 --- a/forms/it/@home.texy +++ b/forms/it/@home.texy @@ -1,31 +1,31 @@ -Moduli -****** +Nette Forms +*********** <div class=perex> -Nette Forms ha rivoluzionato la creazione di moduli web. Bastava scrivere poche e chiare righe di codice per avere un modulo, con tanto di rendering, JavaScript e convalida del server, oltre a una sicurezza leader nel settore. Vediamo come +Nette Forms ha rivoluzionato la creazione di form web. Improvvisamente, bastava scrivere poche righe di codice comprensibili per avere un form completo, inclusa la resa, la validazione JavaScript e lato server, e inoltre estremamente sicuro. Vediamo come: -- creare moduli semplici -- convalidare i dati inviati -- disegnare gli elementi esattamente come necessario +- creare form user-friendly +- validare i dati inviati +- rendere gli elementi esattamente secondo necessità </div> -Con Nette Forms è possibile ridurre le attività di routine come la scrittura della convalida (sia lato server che lato client) e ridurre al minimo la probabilità di errori e problemi di sicurezza. +Utilizzando Nette Forms, eviterai una serie di compiti ripetitivi, come scrivere la validazione (inoltre doppia, lato server e client), minimizzerai la probabilità di errori e falle di sicurezza. -I moduli possono essere utilizzati come parte dell'applicazione Nette (ad esempio nei presentatori) o in modo indipendente. Poiché l'utilizzo è leggermente diverso in entrambi i casi, abbiamo preparato per voi istruzioni separate: +Puoi utilizzare i form sia come parte dell'applicazione Nette (cioè nei presenter), sia completamente autonomamente. Poiché nei due casi l'uso differisce leggermente, abbiamo preparato per te due guide: <div class="wiki-buttons"> -<div> "Moduli nei presentatori .[wiki-button]":in-presenter </div> -<div> "Moduli standalone .[wiki-button]":standalone </div> +<div> "Form nei presenter .[wiki-button]":in-presenter </div> +<div> "Form autonomi .[wiki-button]":standalone </div> </div> Installazione ------------- -Scaricare e installare il pacchetto utilizzando [Composer |best-practices:composer]: +È possibile scaricare e installare la libreria utilizzando lo strumento [Composer|best-practices:composer]: ```shell composer require nette/forms diff --git a/forms/it/@left-menu.texy b/forms/it/@left-menu.texy index 9e37516c87..fc13168966 100644 --- a/forms/it/@left-menu.texy +++ b/forms/it/@left-menu.texy @@ -1,14 +1,14 @@ -Moduli -****** -- [Panoramica |@home] -- [Moduli nei presentatori |in-presenter] -- [Moduli standalone |standalone] -- [Controlli dei moduli |controls] -- [Convalida |validation] +Nette Forms +*********** +- [Introduzione |@home] +- [Form nei presenter|in-presenter] +- [Form autonomi|standalone] +- [Elementi del form |controls] +- [Validazione |validation] - [Rendering |rendering] -- [Configurazione |Configuration] +- [Configurazione |configuration] Ulteriori letture ***************** -- [Migliori pratiche |best-practices:] +- [Guide e procedure |best-practices:] diff --git a/forms/it/@meta.texy b/forms/it/@meta.texy new file mode 100644 index 0000000000..4647d0c8a2 --- /dev/null +++ b/forms/it/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentazione Nette}} diff --git a/forms/it/configuration.texy b/forms/it/configuration.texy index 0ffc35dacb..8c67992399 100644 --- a/forms/it/configuration.texy +++ b/forms/it/configuration.texy @@ -1,8 +1,8 @@ -Configurazione dei moduli -************************* +Configurazione dei form +*********************** .[perex] -È possibile modificare i [messaggi di errore |validation] predefiniti dei [moduli |validation] nella configurazione. +Nella configurazione è possibile modificare i [messaggi di errore predefiniti dei form |validation]. ```neon forms: @@ -30,4 +30,32 @@ forms: Nette\Forms\Controls\CsrfProtection::Protection: 'Your session has expired. Please return to the home page and try again.' ``` -Se non si utilizza l'intero framework e quindi nemmeno i file di configurazione, è possibile modificare i messaggi di errore predefiniti direttamente nel campo `Nette\Forms\Validator::$messages`. +Ecco la traduzione italiana: + +```neon +forms: + messages: + Equal: 'Inserisci %s.' + NotEqual: 'Questo valore non dovrebbe essere %s.' + Filled: 'Questo campo è obbligatorio.' + Blank: 'Questo campo dovrebbe essere vuoto.' + MinLength: 'Inserisci almeno %d caratteri.' + MaxLength: 'Inserisci non più di %d caratteri.' + Length: 'Inserisci un valore lungo tra %d e %d caratteri.' + Email: 'Inserisci un indirizzo email valido.' + URL: 'Inserisci un URL valido.' + Integer: 'Inserisci un numero intero valido.' + Float: 'Inserisci un numero valido.' + Min: 'Inserisci un valore maggiore o uguale a %d.' + Max: 'Inserisci un valore minore o uguale a %d.' + Range: 'Inserisci un valore compreso tra %d e %d.' + MaxFileSize: 'La dimensione del file caricato può essere al massimo di %d byte.' + MaxPostSize: 'I dati caricati superano il limite di %d byte.' + MimeType: 'Il file caricato non è nel formato previsto.' + Image: 'Il file caricato deve essere un\'immagine in formato JPEG, GIF, PNG, WebP o AVIF.' + Nette\Forms\Controls\SelectBox::Valid: 'Seleziona un\'opzione valida.' + Nette\Forms\Controls\UploadControl::Valid: 'Si è verificato un errore durante il caricamento del file.' + Nette\Forms\Controls\CsrfProtection::Protection: 'La tua sessione è scaduta. Torna alla home page e riprova.' +``` + +Se non utilizzi l'intero framework e quindi nemmeno i file di configurazione, puoi modificare i messaggi di errore predefiniti direttamente nell'array `Nette\Forms\Validator::$messages`. diff --git a/forms/it/controls.texy b/forms/it/controls.texy index a8bd71747e..2e80b5e1f6 100644 --- a/forms/it/controls.texy +++ b/forms/it/controls.texy @@ -1,253 +1,346 @@ -Controlli del modulo -******************** +Elementi del Form +***************** .[perex] -Panoramica dei controlli di modulo incorporati. +Panoramica degli elementi standard del form. -addText(string|int $name, $label=null): TextInput .[method] -=========================================================== +addText(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +================================================================================================== -Aggiunge un campo di testo a riga singola (classe [TextInput |api:Nette\Forms\Controls\TextInput]). Se l'utente non compila il campo, restituisce una stringa vuota `''`, oppure si può usare `setNullable()` per modificarlo e restituire `null`. +Aggiunge un campo di testo a riga singola (classe [TextInput |api:Nette\Forms\Controls\TextInput]). Se l'utente non compila il campo, restituisce una stringa vuota `''`, oppure tramite `setNullable()` è possibile specificare che restituisca `null`. ```php -$form->addText('name', 'Name:') +$form->addText('name', 'Nome:') ->setRequired() ->setNullable(); ``` -Convalida automaticamente UTF-8, taglia gli spazi bianchi a destra e a sinistra e rimuove le interruzioni di riga che potrebbero essere inviate da un utente malintenzionato. +Valida automaticamente UTF-8, rimuove gli spazi iniziali e finali e rimuove gli a capo che un utente malintenzionato potrebbe inviare. -La lunghezza massima può essere limitata utilizzando `setMaxLength()`. Il metodo [addFilter() |validation#Modifying Input Values] consente di modificare il valore inserito dall'utente. +La lunghezza massima può essere limitata tramite `setMaxLength()`. Modificare il valore inserito dall'utente è possibile tramite [addFilter() |validation#Modifica dell Input]. -Utilizzare `setHtmlType()` per cambiare il [carattere |https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types] dell'elemento di input in `search`, `tel`, `url`, `range`, `date`, `datetime-local`, `month`, `time`, `week`, `color`. Al posto dei tipi `number` e `email`, si consiglia di usare [addInteger |#addInteger] e [addEmail |#addEmail], che forniscono una validazione lato server. +Tramite `setHtmlType()` è possibile modificare l'aspetto visivo del campo di testo in tipi come `search`, `tel` o `url`, vedi [specifiche |https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types]. Ricorda che la modifica del tipo è solo visiva e non sostituisce la funzione di validazione. Per il tipo `url` è opportuno aggiungere una specifica [regola URL |validation#Input di Testo]. -```php -$form->addText('color', 'Choose color:') - ->setHtmlType('color') - ->addRule($form::Pattern, 'invalid value', '[0-9a-f]{6}'); -``` +.[note] +Per altri tipi di input, come `number`, `range`, `email`, `date`, `datetime-local`, `time` e `color`, utilizzare metodi specializzati come [#addInteger], [#addFloat], [#addEmail] [#addDate], [#addTime], [#addDateTime] e [#addColor], che garantiscono la validazione lato server. I tipi `month` e `week` non sono ancora pienamente supportati in tutti i browser. -È possibile impostare il cosiddetto valore vuoto per l'elemento, che è qualcosa di simile al valore predefinito, ma se l'utente non lo sovrascrive, restituisce una stringa vuota o `null`. +All'elemento può essere impostato il cosiddetto empty-value, che è qualcosa come un valore predefinito, ma se l'utente non lo modifica, l'elemento restituisce una stringa vuota o `null`. ```php -$form->addText('phone', 'Phone:') +$form->addText('phone', 'Telefono:') ->setHtmlType('tel') - ->setEmptyValue('+420'); + ->setEmptyValue('+39'); ``` addTextArea(string|int $name, $label=null): TextArea .[method] ============================================================== -Aggiunge un campo di testo multilinea (classe [TextArea |api:Nette\Forms\Controls\TextArea]). Se l'utente non compila il campo, restituisce una stringa vuota `''`, oppure si può usare `setNullable()` per modificarlo e restituire `null`. +Aggiunge un campo per l'inserimento di testo multilinea (classe [TextArea |api:Nette\Forms\Controls\TextArea]). Se l'utente non compila il campo, restituisce una stringa vuota `''`, oppure tramite `setNullable()` è possibile specificare che restituisca `null`. ```php -$form->addTextArea('note', 'Note:') - ->addRule($form::MaxLength, 'Your note is way too long', 10000); +$form->addTextArea('note', 'Nota:') + ->addRule($form::MaxLength, 'La nota è troppo lunga', 10000); ``` -Convalida automaticamente UTF-8 e normalizza le interruzioni di riga a `\n`. A differenza di un campo di input a una riga, non taglia gli spazi bianchi. +Valida automaticamente UTF-8 e normalizza i separatori di riga in `\n`. A differenza del campo di input a riga singola, non viene eseguita alcuna rimozione degli spazi. -La lunghezza massima può essere limitata utilizzando `setMaxLength()`. Il metodo [addFilter() |validation#Modifying Input Values] consente di modificare il valore immesso dall'utente. È possibile impostare il cosiddetto valore vuoto utilizzando `setEmptyValue()`. +La lunghezza massima può essere limitata tramite `setMaxLength()`. Modificare il valore inserito dall'utente è possibile tramite [addFilter() |validation#Modifica dell Input]. È possibile impostare il cosiddetto empty-value tramite `setEmptyValue()`. addInteger(string|int $name, $label=null): TextInput .[method] ============================================================== -Aggiunge un campo di input per numeri interi (classe [TextInput |api:Nette\Forms\Controls\TextInput]). Restituisce un intero o `null` se l'utente non inserisce nulla. +Aggiunge un campo per l'inserimento di un numero intero (classe [TextInput |api:Nette\Forms\Controls\TextInput]). Restituisce un intero o `null` se l'utente non inserisce nulla. + +```php +$form->addInteger('year', 'Anno:') + ->addRule($form::Range, 'L\'anno deve essere compreso tra %d e %d.', [1900, 2023]); +``` + +L'elemento viene renderizzato come `<input type="number">`. Utilizzando il metodo `setHtmlType()` è possibile cambiare il tipo in `range` per la visualizzazione come slider, o in `text` se si preferisce un campo di testo standard senza il comportamento speciale del tipo `number`. + + +addFloat(string|int $name, $label=null): TextInput .[method]{data-version:3.1.12} +================================================================================= + +Aggiunge un campo per l'inserimento di un numero decimale (classe [TextInput |api:Nette\Forms\Controls\TextInput]). Restituisce un float o `null` se l'utente non inserisce nulla. ```php -$form->addInteger('level', 'Level:') +$form->addFloat('level', 'Livello:') ->setDefaultValue(0) - ->addRule($form::Range, 'Level must be between %d and %d.', [0, 100]); + ->addRule($form::Range, 'Il livello deve essere compreso tra %d e %d.', [0, 100]); ``` +L'elemento viene renderizzato come `<input type="number">`. Utilizzando il metodo `setHtmlType()` è possibile cambiare il tipo in `range` per la visualizzazione come slider, o in `text` se si preferisce un campo di testo standard senza il comportamento speciale del tipo `number`. + +Nette e il browser Chrome accettano sia la virgola che il punto come separatore decimale. Affinché questa funzionalità sia disponibile anche in Firefox, si consiglia di impostare l'attributo `lang` per l'elemento specifico o per l'intera pagina, ad esempio `<html lang="it">`. + -addEmail(string|int $name, $label=null): TextInput .[method] -============================================================ +addEmail(string|int $name, $label=null, int $maxLength=255): TextInput .[method] +================================================================================ -Aggiunge un campo per l'indirizzo e-mail con controllo di validità (classe [TextInput |api:Nette\Forms\Controls\TextInput]). Se l'utente non compila il campo, restituisce una stringa vuota `''`, oppure si può usare `setNullable()` per modificarlo e restituire `null`. +Aggiunge un campo per l'inserimento di un indirizzo email (classe [TextInput |api:Nette\Forms\Controls\TextInput]). Se l'utente non compila il campo, restituisce una stringa vuota `''`, oppure tramite `setNullable()` è possibile specificare che restituisca `null`. ```php -$form->addEmail('email', 'Email:'); +$form->addEmail('email', 'E-mail:'); ``` -Verifica che il valore sia un indirizzo e-mail valido. Non verifica l'effettiva esistenza del dominio, ma solo la sintassi. Convalida automaticamente UTF-8, taglia gli spazi bianchi a destra e a sinistra. +Verifica se il valore è un indirizzo email valido. Non verifica se il dominio esiste realmente, verifica solo la sintassi. Valida automaticamente UTF-8, rimuove gli spazi iniziali e finali. -La lunghezza massima può essere limitata utilizzando `setMaxLength()`. Il metodo [addFilter() |validation#Modifying Input Values] consente di modificare il valore inserito dall'utente. È possibile impostare il cosiddetto valore vuoto utilizzando `setEmptyValue()`. +La lunghezza massima può essere limitata tramite `setMaxLength()`. Modificare il valore inserito dall'utente è possibile tramite [addFilter() |validation#Modifica dell Input]. È possibile impostare il cosiddetto empty-value tramite `setEmptyValue()`. -addPassword(string|int $name, $label=null): TextInput .[method] -=============================================================== +addPassword(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +====================================================================================================== -Aggiunge il campo password (classe [TextInput |api:Nette\Forms\Controls\TextInput]). +Aggiunge un campo per l'inserimento della password (classe [TextInput |api:Nette\Forms\Controls\TextInput]). ```php $form->addPassword('password', 'Password:') ->setRequired() - ->addRule($form::MinLength, 'Password has to be at least %d characters long', 8) - ->addRule($form::Pattern, 'Password must contain a number', '.*[0-9].*'); + ->addRule($form::MinLength, 'La password deve avere almeno %d caratteri', 8) + ->addRule($form::Pattern, 'Deve contenere un numero', '.*[0-9].*'); ``` -Quando si invia nuovamente il modulo, l'input sarà vuoto. Convalida automaticamente UTF-8, taglia gli spazi bianchi a destra e a sinistra e rimuove le interruzioni di riga che potrebbero essere inviate da un utente malintenzionato. +Alla successiva visualizzazione del form, il campo sarà vuoto. Valida automaticamente UTF-8, rimuove gli spazi iniziali e finali e rimuove gli a capo che un utente malintenzionato potrebbe inviare. addCheckbox(string|int $name, $caption=null): Checkbox .[method] ================================================================ -Aggiunge una casella di controllo (classe [Checkbox |api:Nette\Forms\Controls\Checkbox]). Il campo restituisce `true` o `false`, a seconda che sia selezionato o meno. +Aggiunge una casella di controllo (classe [Checkbox |api:Nette\Forms\Controls\Checkbox]). Restituisce il valore `true` o `false`, a seconda che sia selezionata o meno. ```php -$form->addCheckbox('agree', 'I agree with terms') - ->setRequired('You must agree with our terms'); +$form->addCheckbox('agree', 'Accetto i termini e le condizioni') + ->setRequired('È necessario accettare i termini e le condizioni'); ``` -addCheckboxList(string|int $name, $label=null, array $items=null): CheckboxList .[method] -========================================================================================= +addCheckboxList(string|int $name, $label=null, ?array $items=null): CheckboxList .[method] +========================================================================================== -Aggiunge un elenco di caselle di controllo per la selezione di più elementi (classe [CheckboxList |api:Nette\Forms\Controls\CheckboxList]). Restituisce l'array di chiavi degli elementi selezionati. Il metodo `getSelectedItems()` restituisce i valori invece delle chiavi. +Aggiunge caselle di controllo per la selezione di più elementi (classe [CheckboxList |api:Nette\Forms\Controls\CheckboxList]). Restituisce un array delle chiavi degli elementi selezionati. Il metodo `getSelectedItems()` restituisce i valori invece delle chiavi. ```php -$form->addCheckboxList('colors', 'Colors:', [ - 'r' => 'red', - 'g' => 'green', - 'b' => 'blue', +$form->addCheckboxList('colors', 'Colori:', [ + 'r' => 'rosso', + 'g' => 'verde', + 'b' => 'blu', ]); ``` -Si passa l'array di elementi come terzo parametro o con il metodo `setItems()`. +L'array degli elementi offerti viene passato come terzo parametro o tramite il metodo `setItems()`. -È possibile utilizzare `setDisabled(['r', 'g'])` per disabilitare singoli elementi. +Tramite `setDisabled(['r', 'g'])` è possibile disattivare singoli elementi. -L'elemento controlla automaticamente che non ci siano state contraffazioni e che gli elementi selezionati siano effettivamente tra quelli proposti e non siano stati disabilitati. Il metodo `getRawValue()` può essere utilizzato per recuperare gli elementi inviati senza questo importante controllo. +L'elemento controlla automaticamente che non ci sia stato un tentativo di manomissione e che gli elementi selezionati siano effettivamente tra quelli offerti e non siano stati disattivati. Tramite il metodo `getRawValue()` è possibile ottenere gli elementi inviati senza questo importante controllo. -Quando sono impostati dei valori predefiniti, controlla anche che siano uno degli elementi offerti, altrimenti lancia un'eccezione. Questo controllo può essere disattivato con `checkDefaultValue(false)`. +Durante l'impostazione degli elementi selezionati predefiniti, controlla anche che siano tra quelli offerti, altrimenti genera un'eccezione. Questo controllo può essere disattivato tramite `checkDefaultValue(false)`. +Se invii il form con il metodo `GET`, puoi scegliere un modo più compatto per trasferire i dati, che risparmia la dimensione della query string. Si attiva impostando l'attributo HTML del form: + +```php +$form->setHtmlAttribute('data-nette-compact'); +``` -addRadioList(string|int $name, $label=null, array $items=null): RadioList .[method] -=================================================================================== -Aggiunge pulsanti di opzione (classe [RadioList |api:Nette\Forms\Controls\RadioList]). Restituisce la chiave dell'elemento selezionato o `null` se l'utente non ha selezionato nulla. Il metodo `getSelectedItem()` restituisce un valore invece di una chiave. +addRadioList(string|int $name, $label=null, ?array $items=null): RadioList .[method] +==================================================================================== + +Aggiunge pulsanti di opzione (classe [RadioList |api:Nette\Forms\Controls\RadioList]). Restituisce la chiave dell'elemento selezionato, o `null` se l'utente non ha selezionato nulla. Il metodo `getSelectedItem()` restituisce il valore invece della chiave. ```php $sex = [ - 'm' => 'male', - 'f' => 'female', + 'm' => 'uomo', + 'f' => 'donna', ]; -$form->addRadioList('gender', 'Gender:', $sex); +$form->addRadioList('gender', 'Sesso:', $sex); ``` -Si passa l'array di elementi come terzo parametro o con il metodo `setItems()`. +L'array degli elementi offerti viene passato come terzo parametro o tramite il metodo `setItems()`. -È possibile utilizzare `setDisabled(['m'])` per disabilitare singoli elementi. +Tramite `setDisabled(['m', 'f'])` è possibile disattivare singoli elementi. -L'elemento controlla automaticamente che non ci siano state contraffazioni e che l'elemento selezionato sia effettivamente uno di quelli proposti e non sia stato disabilitato. Il metodo `getRawValue()` può essere utilizzato per recuperare l'elemento inviato senza questo importante controllo. +L'elemento controlla automaticamente che non ci sia stato un tentativo di manomissione e che l'elemento selezionato sia effettivamente uno di quelli offerti e non sia stato disattivato. Tramite il metodo `getRawValue()` è possibile ottenere l'elemento inviato senza questo importante controllo. -Quando il valore predefinito è impostato, controlla anche che sia uno degli elementi offerti, altrimenti lancia un'eccezione. Questo controllo può essere disattivato con `checkDefaultValue(false)`. +Durante l'impostazione dell'elemento selezionato predefinito, controlla anche che sia uno di quelli offerti, altrimenti genera un'eccezione. Questo controllo può essere disattivato tramite `checkDefaultValue(false)`. -addSelect(string|int $name, $label=null, array $items=null): SelectBox .[method] -================================================================================ +addSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): SelectBox .[method] +================================================================================================== -Aggiunge una casella di selezione (classe [SelectBox |api:Nette\Forms\Controls\SelectBox]). Restituisce la chiave dell'elemento selezionato o `null` se l'utente non ha selezionato nulla. Il metodo `getSelectedItem()` restituisce un valore invece di una chiave. +Aggiunge un select box (classe [SelectBox |api:Nette\Forms\Controls\SelectBox]). Restituisce la chiave dell'elemento selezionato, o `null` se l'utente non ha selezionato nulla. Il metodo `getSelectedItem()` restituisce il valore invece della chiave. ```php $countries = [ - 'CZ' => 'Czech republic', - 'SK' => 'Slovakia', - 'GB' => 'United Kingdom', + 'IT' => 'Italia', + 'DE' => 'Germania', + 'GB' => 'Regno Unito', ]; -$form->addSelect('country', 'Country:', $countries) - ->setDefaultValue('SK'); +$form->addSelect('country', 'Paese:', $countries) + ->setDefaultValue('IT'); ``` -Si passa l'array di elementi come terzo parametro o con il metodo `setItems()`. L'array di elementi può anche essere bidimensionale: +L'array degli elementi offerti viene passato come terzo parametro o tramite il metodo `setItems()`. Gli elementi possono essere anche un array bidimensionale: ```php $countries = [ - 'Europe' => [ - 'CZ' => 'Czech republic', - 'SK' => 'Slovakia', - 'GB' => 'United Kingdom', + 'Europa' => [ + 'IT' => 'Italia', + 'DE' => 'Germania', + 'GB' => 'Regno Unito', ], 'CA' => 'Canada', 'US' => 'USA', - '?' => 'other', + '?' => 'altro', ]; ``` -Per le caselle di selezione, il primo elemento ha spesso un significato speciale, in quanto funge da invito all'azione. Utilizzare il metodo `setPrompt()` per aggiungere una voce di questo tipo. +Nei select box, spesso il primo elemento ha un significato speciale, serve come invito all'azione. Per aggiungere un tale elemento serve il metodo `setPrompt()`. ```php -$form->addSelect('country', 'Country:', $countries) - ->setPrompt('Pick a country'); +$form->addSelect('country', 'Paese:', $countries) + ->setPrompt('Scegli un paese'); ``` -È possibile utilizzare `setDisabled(['CZ', 'SK'])` per disabilitare singole voci. +Tramite `setDisabled(['IT', 'DE'])` è possibile disattivare singoli elementi. -L'elemento controlla automaticamente che non ci siano state contraffazioni e che l'elemento selezionato sia effettivamente uno di quelli proposti e non sia stato disabilitato. Il metodo `getRawValue()` può essere utilizzato per recuperare l'elemento inviato senza questo importante controllo. +L'elemento controlla automaticamente che non ci sia stato un tentativo di manomissione e che l'elemento selezionato sia effettivamente uno di quelli offerti e non sia stato disattivato. Tramite il metodo `getRawValue()` è possibile ottenere l'elemento inviato senza questo importante controllo. -Quando il valore predefinito è impostato, controlla anche che sia uno degli elementi offerti, altrimenti lancia un'eccezione. Questo controllo può essere disattivato con `checkDefaultValue(false)`. +Durante l'impostazione dell'elemento selezionato predefinito, controlla anche che sia uno di quelli offerti, altrimenti genera un'eccezione. Questo controllo può essere disattivato tramite `checkDefaultValue(false)`. -addMultiSelect(string|int $name, $label=null, array $items=null): MultiSelectBox .[method] -========================================================================================== +addMultiSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): MultiSelectBox .[method] +============================================================================================================ -Aggiunge una casella di selezione a scelta multipla (classe [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox]). Restituisce l'array di chiavi degli elementi selezionati. Il metodo `getSelectedItems()` restituisce i valori invece delle chiavi. +Aggiunge un select box per la selezione di più elementi (classe [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox]). Restituisce un array delle chiavi degli elementi selezionati. Il metodo `getSelectedItems()` restituisce i valori invece delle chiavi. ```php -$form->addMultiSelect('countries', 'Countries:', $countries); +$form->addMultiSelect('countries', 'Paesi:', $countries); ``` -La matrice di elementi viene passata come terzo parametro o con il metodo `setItems()`. L'array di elementi può anche essere bidimensionale. +L'array degli elementi offerti viene passato come terzo parametro o tramite il metodo `setItems()`. Gli elementi possono essere anche un array bidimensionale. -È possibile utilizzare `setDisabled(['CZ', 'SK'])` per disabilitare singoli elementi. +Tramite `setDisabled(['IT', 'DE'])` è possibile disattivare singoli elementi. -L'elemento controlla automaticamente che non ci siano state contraffazioni e che gli elementi selezionati siano effettivamente tra quelli proposti e non siano stati disabilitati. Il metodo `getRawValue()` può essere utilizzato per recuperare gli elementi inviati senza questo importante controllo. +L'elemento controlla automaticamente che non ci sia stato un tentativo di manomissione e che gli elementi selezionati siano effettivamente tra quelli offerti e non siano stati disattivati. Tramite il metodo `getRawValue()` è possibile ottenere gli elementi inviati senza questo importante controllo. -Quando sono impostati dei valori predefiniti, controlla anche che siano uno degli elementi offerti, altrimenti lancia un'eccezione. Questo controllo può essere disattivato con `checkDefaultValue(false)`. +Durante l'impostazione degli elementi selezionati predefiniti, controlla anche che siano tra quelli offerti, altrimenti genera un'eccezione. Questo controllo può essere disattivato tramite `checkDefaultValue(false)`. addUpload(string|int $name, $label=null): UploadControl .[method] ================================================================= -Aggiunge il campo di caricamento dei file (classe [UploadControl |api:Nette\Forms\Controls\UploadControl]). Restituisce l'oggetto [FileUpload |http:request#FileUpload], anche se l'utente non ha caricato un file, cosa che può essere scoperta con il metodo `FileUpload::hasFile()`. +Aggiunge un campo per l'upload di un file (classe [UploadControl |api:Nette\Forms\Controls\UploadControl]). Restituisce un oggetto [FileUpload |http:request#FileUpload] anche nel caso in cui l'utente non abbia inviato alcun file, il che può essere verificato con il metodo `FileUpload::hasFile()`. ```php $form->addUpload('avatar', 'Avatar:') - ->addRule($form::Image, 'Avatar must be JPEG, PNG, GIF or WebP') - ->addRule($form::MaxFileSize, 'Maximum size is 1 MB', 1024 * 1024); + ->addRule($form::Image, 'L\'avatar deve essere JPEG, PNG, GIF, WebP o AVIF.') + ->addRule($form::MaxFileSize, 'La dimensione massima è 1 MB.', 1024 * 1024); ``` -Se il file non è stato caricato correttamente, il modulo non è stato inviato con successo e viene visualizzato un errore. Non è quindi necessario controllare il metodo `FileUpload::isOk()`. +Se il file non viene caricato correttamente, il form non viene inviato con successo e viene visualizzato un errore. Cioè, in caso di invio riuscito, non è necessario verificare il metodo `FileUpload::isOk()`. -Non fidatevi del nome originale del file restituito dal metodo `FileUpload::getName()`, un client potrebbe inviare un nome di file dannoso con l'intenzione di corrompere o hackerare l'applicazione. +Non fidarti mai del nome originale del file restituito dal metodo `FileUpload::getName()`, il client potrebbe aver inviato un nome di file dannoso con l'intenzione di danneggiare o hackerare la tua applicazione. -Le regole `MimeType` e `Image` rilevano il tipo di file o immagine richiesto in base alla sua firma. L'integrità dell'intero file non viene controllata. È possibile scoprire se un'immagine non è danneggiata, ad esempio provando a [caricarla |http:request#toImage]. +Le regole `MimeType` e `Image` rilevano il tipo richiesto in base alla firma del file e non ne verificano l'integrità. Se l'immagine non è danneggiata può essere verificato, ad esempio, tentando di [caricarla |http:request#toImage]. addMultiUpload(string|int $name, $label=null): UploadControl .[method] ====================================================================== -Aggiunge un campo per il caricamento di più file (classe [UploadControl |api:Nette\Forms\Controls\UploadControl]). Restituisce un array di oggetti [FileUpload |http:request#FileUpload]. Il metodo `FileUpload::hasFile()` restituirà `true` per ciascuno di essi. +Aggiunge un campo per l'upload di più file contemporaneamente (classe [UploadControl |api:Nette\Forms\Controls\UploadControl]). Restituisce un array di oggetti [FileUpload |http:request#FileUpload]. Il metodo `FileUpload::hasFile()` per ciascuno di essi restituirà `true`. ```php -$form->addMultiUpload('files', 'Files:') - ->addRule($form::MaxLength, 'A maximum of %d files can be uploaded', 10); +$form->addMultiUpload('files', 'File:') + ->addRule($form::MaxLength, 'È possibile caricare al massimo %d file', 10); ``` -Se uno dei file non viene caricato correttamente, il modulo non è stato inviato correttamente e viene visualizzato un errore. Non è quindi necessario controllare il metodo `FileUpload::isOk()`. +Se uno qualsiasi dei file non viene caricato correttamente, il form non viene inviato con successo e viene visualizzato un errore. Cioè, in caso di invio riuscito, non è necessario verificare il metodo `FileUpload::isOk()`. + +Non fidarti mai dei nomi originali dei file restituiti dal metodo `FileUpload::getName()`, il client potrebbe aver inviato un nome di file dannoso con l'intenzione di danneggiare o hackerare la tua applicazione. + +Le regole `MimeType` e `Image` rilevano il tipo richiesto in base alla firma del file e non ne verificano l'integrità. Se l'immagine non è danneggiata può essere verificato, ad esempio, tentando di [caricarla |http:request#toImage]. + -Non fidatevi dei nomi originali dei file restituiti dal metodo `FileUpload::getName()`, un client potrebbe inviare un nome di file dannoso con l'intenzione di corrompere o hackerare l'applicazione. +addDate(string|int $name, $label=null): DateTimeControl .[method]{data-version:3.1.14} +====================================================================================== -Le regole `MimeType` e `Image` rilevano il tipo di file o immagine richiesto in base alla sua firma. L'integrità dell'intero file non viene controllata. È possibile scoprire se un'immagine non è danneggiata, ad esempio provando a [caricarla |http:request#toImage]. +Aggiunge un campo che consente all'utente di inserire facilmente una data composta da anno, mese e giorno (classe [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Come valore predefinito accetta oggetti che implementano l'interfaccia `DateTimeInterface`, una stringa con l'ora o un numero che rappresenta il timestamp UNIX. Lo stesso vale per gli argomenti delle regole `Min`, `Max` o `Range`, che definiscono la data minima e massima consentita. + +```php +$form->addDate('date', 'Data:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'La data deve essere almeno un mese fa.', new DateTime('-1 month')); +``` + +Standard restituisce un oggetto `DateTimeImmutable`, con il metodo `setFormat()` puoi specificare il [formato testuale |https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] o timestamp: + +```php +$form->addDate('date', 'Data:') + ->setFormat('Y-m-d'); +``` -addHidden(string|int $name, string $default=null): HiddenField .[method] -======================================================================== +addTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=============================================================================================================== + +Aggiunge un campo che consente all'utente di inserire facilmente un'ora composta da ore, minuti e facoltativamente secondi (classe [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Come valore predefinito accetta oggetti che implementano l'interfaccia `DateTimeInterface`, una stringa con l'ora o un numero che rappresenta il timestamp UNIX. Da questi input viene utilizzata solo l'informazione sull'ora, la data viene ignorata. Lo stesso vale per gli argomenti delle regole `Min`, `Max` o `Range`, che definiscono l'ora minima e massima consentita. Se il valore minimo impostato è superiore al massimo, viene creato un intervallo di tempo che supera la mezzanotte. + +```php +$form->addTime('time', 'Ora:', withSeconds: true) + ->addRule($form::Range, 'L\'ora deve essere compresa tra %d e %d.', ['12:30', '13:30']); +``` + +Standard restituisce un oggetto `DateTimeImmutable` (con data 1 gennaio anno 1), con il metodo `setFormat()` puoi specificare il [formato testuale |https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters]: + +```php +$form->addTime('time', 'Ora:') + ->setFormat('H:i'); +``` + + +addDateTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=================================================================================================================== + +Aggiunge un campo che consente all'utente di inserire facilmente data e ora composte da anno, mese, giorno, ore, minuti e facoltativamente secondi (classe [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Come valore predefinito accetta oggetti che implementano l'interfaccia `DateTimeInterface`, una stringa con l'ora o un numero che rappresenta il timestamp UNIX. Lo stesso vale per gli argomenti delle regole `Min`, `Max` o `Range`, che definiscono la data minima e massima consentita. + +```php +$form->addDateTime('datetime', 'Data e ora:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'La data deve essere almeno un mese fa.', new DateTime('-1 month')); +``` + +Standard restituisce un oggetto `DateTimeImmutable`, con il metodo `setFormat()` puoi specificare il [formato testuale |https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] o timestamp: + +```php +$form->addDateTime('datetime') + ->setFormat(DateTimeControl::FormatTimestamp); +``` + + +addColor(string|int $name, $label=null): ColorPicker .[method]{data-version:3.1.14} +=================================================================================== + +Aggiunge un campo per la selezione del colore (classe [ColorPicker |api:Nette\Forms\Controls\ColorPicker]). Il colore è una stringa nel formato `#rrggbb`. Se l'utente non effettua la scelta, viene restituito il colore nero `#000000`. + +```php +$form->addColor('color', 'Colore:') + ->setDefaultValue('#3C8ED7'); +``` + + +addHidden(string|int $name, ?string $default=null): HiddenField .[method] +========================================================================= Aggiunge un campo nascosto (classe [HiddenField |api:Nette\Forms\Controls\HiddenField]). @@ -255,26 +348,28 @@ Aggiunge un campo nascosto (classe [HiddenField |api:Nette\Forms\Controls\Hidden $form->addHidden('userid'); ``` -Utilizzare `setNullable()` per modificarlo in modo che restituisca `null` invece di una stringa vuota. Il metodo [addFilter() |validation#Modifying Input Values] consente di modificare il valore inviato. +Tramite `setNullable()` è possibile impostare che restituisca `null` invece di una stringa vuota. Modificare il valore inviato è possibile tramite [addFilter() |validation#Modifica dell Input]. + +Sebbene l'elemento sia nascosto, è **importante rendersi conto** che il valore può ancora essere modificato o falsificato da un utente malintenzionato. Verifica e convalida sempre attentamente tutti i valori ricevuti lato server per prevenire rischi di sicurezza associati alla manipolazione dei dati. addSubmit(string|int $name, $caption=null): SubmitButton .[method] ================================================================== -Aggiunge il pulsante di invio (classe [SubmitButton |api:Nette\Forms\Controls\SubmitButton]). +Aggiunge un pulsante di invio (classe [SubmitButton |api:Nette\Forms\Controls\SubmitButton]). ```php -$form->addSubmit('submit', 'Register'); +$form->addSubmit('submit', 'Invia'); ``` -È possibile avere più di un pulsante di invio nel modulo: +Nel form è possibile avere anche più pulsanti di invio: ```php -$form->addSubmit('register', 'Register'); -$form->addSubmit('cancel', 'Cancel'); +$form->addSubmit('register', 'Registrati'); +$form->addSubmit('cancel', 'Annulla'); ``` -Per sapere quale di essi è stato cliccato, utilizzare: +Per scoprire su quale di essi è stato cliccato, usa: ```php if ($form['register']->isSubmittedBy()) { @@ -282,22 +377,22 @@ if ($form['register']->isSubmittedBy()) { } ``` -Se non si desidera convalidare il modulo quando viene premuto un pulsante di invio (come i pulsanti *Cancel* o *Preview*), è possibile disattivarlo con [setValidationScope() |validation#Disabling Validation]. +Se non vuoi validare l'intero form alla pressione del pulsante (ad esempio per i pulsanti *Annulla* o *Anteprima*), usa [setValidationScope() |validation#Disabilitazione della Validazione]. addButton(string|int $name, $caption): Button .[method] ======================================================= -Aggiunge un pulsante (classe [Button |api:Nette\Forms\Controls\Button]) senza funzione di invio. È utile per legare altre funzionalità all'id, ad esempio un'azione JavaScript. +Aggiunge un pulsante (classe [Button |api:Nette\Forms\Controls\Button]), che non ha funzione di invio. Può quindi essere utilizzato per qualche altra funzione, ad esempio chiamare una funzione JavaScript al clic. ```php -$form->addButton('raise', 'Raise salary') +$form->addButton('raise', 'Aumenta stipendio') ->setHtmlAttribute('onclick', 'raiseSalary()'); ``` -addImageButton(string|int $name, string $src=null, string $alt=null): ImageButton .[method] -=========================================================================================== +addImageButton(string|int $name, ?string $src=null, ?string $alt=null): ImageButton .[method] +============================================================================================= Aggiunge un pulsante di invio sotto forma di immagine (classe [ImageButton |api:Nette\Forms\Controls\ImageButton]). @@ -305,25 +400,25 @@ Aggiunge un pulsante di invio sotto forma di immagine (classe [ImageButton |api: $form->addImageButton('submit', '/path/to/image'); ``` -Quando si usano più pulsanti di invio, si può sapere quale è stato cliccato con `$form['submit']->isSubmittedBy()`. +Utilizzando più pulsanti di invio, è possibile scoprire su quale è stato cliccato tramite `$form['submit']->isSubmittedBy()`. addContainer(string|int $name): Container .[method] =================================================== -Aggiunge un sub-form (classe [Container |api:Nette\Forms\Container]), o un contenitore, che può essere trattato come un form. Ciò significa che si possono usare metodi come `setDefaults()` o `getValues()`. +Aggiunge un sottoform (classe [Container |api:Nette\Forms\Container]), ovvero un contenitore, al quale è possibile aggiungere altri elementi nello stesso modo in cui li aggiungiamo al form. Funzionano anche i metodi `setDefaults()` o `getValues()`. ```php $sub1 = $form->addContainer('first'); -$sub1->addText('name', 'Your name:'); +$sub1->addText('name', 'Il tuo nome:'); $sub1->addEmail('email', 'Email:'); $sub2 = $form->addContainer('second'); -$sub2->addText('name', 'Your name:'); +$sub2->addText('name', 'Il tuo nome:'); $sub2->addEmail('email', 'Email:'); ``` -I dati inviati vengono restituiti come struttura multidimensionale: +I dati inviati vengono quindi restituiti come una struttura multidimensionale: ```php [ @@ -339,97 +434,99 @@ I dati inviati vengono restituiti come struttura multidimensionale: ``` -Panoramica delle impostazioni .[#toc-overview-of-settings] -========================================================== +Panoramica delle impostazioni +============================= -Per tutti gli elementi si possono richiamare i seguenti metodi (si veda la [documentazione dell'API |https://api.nette.org/forms/master/Nette/Forms/Controls.html] per una panoramica completa): +Su tutti gli elementi possiamo chiamare i seguenti metodi (panoramica completa nella [documentazione API |https://api.nette.org/forms/master/Nette/Forms/Controls.html]): .[table-form-methods language-php] -| `setDefaultValue($value)` | imposta il valore predefinito -| `getValue()` | ottiene il valore corrente -| `setOmitted()` | [omettere i valori |#omitted values] -| `setDisabled()` | [disabilitare gli ingressi |#disabling inputs] +| `setDefaultValue($value)` | imposta il valore predefinito +| `getValue()` | ottiene il valore attuale +| `setOmitted()` | [#Omissione del valore] +| `setDisabled()` | [#Disattivazione degli elementi] -Rendering: +Renderizzazione: .[table-form-methods language-php] -| `setCaption($caption)`| modifica la didascalia dell'elemento -| `setTranslator($translator)` | imposta [il traduttore |rendering#translating] -| `setHtmlAttribute($name, $value)` | imposta l'[attributo HTML |rendering#HTML attributes] dell'elemento -| `setHtmlId($id)` | imposta l'attributo HTML `id` -| `setHtmlType($type)` | imposta l'attributo HTML `type` -| `setHtmlName($name)`| imposta l'attributo HTML `name` -| `setOption($key, $value)` | imposta i [dati di rendering |rendering#Options] - -Convalida: +| `setCaption($caption)` | modifica l'etichetta dell'elemento +| `setTranslator($translator)` | imposta il [traduttore |rendering#Traduzione] +| `setHtmlAttribute($name, $value)` | imposta l'[attributo HTML |rendering#Attributi HTML] dell'elemento +| `setHtmlId($id)` | imposta l'attributo HTML `id` +| `setHtmlType($type)` | imposta l'attributo HTML `type` +| `setHtmlName($name)` | imposta l'attributo HTML `name` +| `setOption($key, $value)` | [impostazione per la renderizzazione |rendering#Options] + +Validazione: .[table-form-methods language-php] -| `setRequired()` | [campo obbligatorio |validation] -| `addRule()` | imposta [regola di validazione |validation#Rules] -| `addCondition()`, `addConditionOn()` | impostare [condizione di validazione |validation#Conditions] -| `addError($message)`| [passaggio del messaggio di errore |validation#processing-errors] +| `setRequired()` | [elemento obbligatorio |validation] +| `addRule()` | imposta la [regola di validazione |validation#Regole] +| `addCondition()`, `addConditionOn()` | imposta la [condizione di validazione |validation#Condizioni] +| `addError($message)` | [passaggio del messaggio di errore |validation#Errori durante l Elaborazione] -I seguenti metodi possono essere richiamati per gli elementi `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`: +Sugli elementi `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` è possibile chiamare i seguenti metodi: .[table-form-methods language-php] -| `setNullable()`| imposta se getValue() restituisce `null` invece della stringa vuota -| `setEmptyValue($value)` | imposta il valore speciale che viene trattato come stringa vuota -| `setMaxLength($length)`| imposta il numero massimo di caratteri consentiti -| `addFilter($filter)`| [modificare i valori di input |validation#Modifying Input Values] +| `setNullable()` | imposta se getValue() restituirà `null` invece di una stringa vuota +| `setEmptyValue($value)` | imposta un valore speciale che viene considerato come stringa vuota +| `setMaxLength($length)` | imposta il numero massimo di caratteri consentiti +| `addFilter($filter)` | [modifica dell'input |validation#Modifica dell Input] -Valori omessi .[#toc-omitted-values] ------------------------------------- +Omissione del valore +==================== -Se non si è interessati al valore inserito dall'utente, si può usare `setOmitted()` per ometterlo dal risultato fornito dal metodo `$form->getValues​()` o passato ai gestori. Questo è adatto per varie password di verifica, campi antispam, ecc. +Se il valore compilato dall'utente non ci interessa, possiamo ometterlo dal risultato del metodo `$form->getValues()` o dai dati passati agli handler tramite `setOmitted()`. Questo è utile per varie password di controllo, elementi antispam, ecc. ```php -$form->addPassword('passwordVerify', 'Password again:') - ->setRequired('Fill your password again to check for typo') - ->addRule($form::Equal, 'Password mismatch', $form['password']) +$form->addPassword('passwordVerify', 'Password di controllo:') + ->setRequired('Inserisci nuovamente la password per controllo') + ->addRule($form::Equal, 'Le password non corrispondono', $form['password']) ->setOmitted(); ``` -Disabilitazione degli ingressi .[#toc-disabling-inputs] -------------------------------------------------------- +Disattivazione degli elementi +============================= -Per disabilitare un input, si può chiamare `setDisabled()`. Questo campo non può essere modificato dall'utente. +Gli elementi possono essere disattivati tramite `setDisabled()`. Un tale elemento non può essere modificato dall'utente. ```php -$form->addText('username', 'User name:') +$form->addText('username', 'Nome utente:') ->setDisabled(); ``` -Si noti che il browser non invia affatto i campi disabilitati al server, quindi non li si troverà nemmeno nei dati restituiti dalla funzione `$form->getValues()`. +Gli elementi disabilitati non vengono inviati dal browser al server, quindi non li troverete nei dati restituiti dalla funzione `$form->getValues()`. Tuttavia, se impostate `setOmitted(false)`, Nette includerà in questi dati il loro valore predefinito. -Se si imposta un valore predefinito per un campo, è necessario farlo solo dopo averlo disabilitato: +Quando si chiama `setDisabled()`, per motivi di sicurezza **il valore dell'elemento viene cancellato**. Se si imposta un valore predefinito, è necessario farlo dopo la sua disattivazione: ```php -$form->addText('username', 'User name:') +$form->addText('username', 'Nome utente:') ->setDisabled() ->setDefaultValue($userName); ``` +Un'alternativa agli elementi disabilitati sono gli elementi con l'attributo HTML `readonly`, che il browser invia al server. Sebbene l'elemento sia solo di lettura, è **importante rendersi conto** che il suo valore può ancora essere modificato o falsificato da un utente malintenzionato. + -Controlli personalizzati .[#toc-custom-controls] -================================================ +Elementi personalizzati +======================= -Oltre all'ampia gamma di controlli incorporati nel modulo, è possibile aggiungere controlli personalizzati al modulo come segue: +Oltre alla vasta gamma di elementi di form integrati, è possibile aggiungere elementi personalizzati al form in questo modo: ```php -$form->addComponent(new DateInput('Date:'), 'date'); -// sintassi alternativa: $form['date'] = new DateInput('Date:'); +$form->addComponent(new DateInput('Data:'), 'date'); +// sintassi alternativa: $form['date'] = new DateInput('Data:'); ``` .[note] -Il modulo è un discendente della classe [Container | component-model:#Container] e gli elementi sono discendenti di [Component | component-model:#Component]. +Il form è un discendente della classe [Container |component-model:#Container] e i singoli elementi sono discendenti di [Component |component-model:#Component]. -Esiste un modo per definire nuovi metodi del modulo per aggiungere elementi personalizzati (ad esempio `$form->addZip()`). Questi sono i cosiddetti metodi di estensione. Lo svantaggio è che i suggerimenti di codice negli editor non funzionano per questi metodi. +Esiste un modo per definire nuovi metodi del form che servono ad aggiungere elementi personalizzati (es. `$form->addZip()`). Si tratta delle cosiddette extension methods. Lo svantaggio è che per esse non funzionerà il suggerimento negli editor. ```php use Nette\Forms\Container; -// aggiunge il metodo addZip(string $nome, string $etichetta = null) -Container::extensionMethod('addZip', function (Container $form, string $name, string $label = null) { +// aggiungiamo il metodo addZip(string $name, ?string $label = null) +Container::extensionMethod('addZip', function (Container $form, string $name, ?string $label = null) { return $form->addText($name, $label) ->addRule($form::Pattern, 'Almeno 5 numeri', '[0-9]{5}'); }); @@ -439,10 +536,10 @@ $form->addZip('zip', 'Codice postale:'); ``` -Campi di basso livello .[#toc-low-level-fields] -=============================================== +Elementi di basso livello +========================= -Per aggiungere un elemento al form, non è necessario chiamare `$form->addXyz()`. Gli elementi del modulo possono essere introdotti esclusivamente nei modelli. Questo è utile se, ad esempio, si ha la necessità di generare elementi dinamici: +È possibile utilizzare anche elementi che scriviamo solo nel template e non aggiungiamo al form con uno dei metodi `$form->addXyz()`. Ad esempio, quando elenchiamo record da un database e non sappiamo in anticipo quanti ce ne saranno e quali ID avranno, e vogliamo visualizzare una checkbox o un radio button per ogni riga, basta codificarlo nel template: ```latte {foreach $items as $item} @@ -450,13 +547,13 @@ Per aggiungere un elemento al form, non è necessario chiamare `$form->addXyz()` {/foreach} ``` -Dopo l'invio, è possibile recuperare i valori: +E dopo l'invio scopriamo il valore: ```php $data = $form->getHttpData($form::DataText, 'sel[]'); $data = $form->getHttpData($form::DataText | $form::DataKeys, 'sel[]'); ``` -Nel primo parametro si specifica il tipo di elemento (`DataFile` per `type=file`, `DataLine` per gli input a una riga come `text`, `password` o `email` e `DataText` per gli altri). Il secondo parametro corrisponde all'attributo HTML `name`. Se è necessario preservare le chiavi, si può combinare il primo parametro con `DataKeys`. Questo è utile per `select`, `radioList` o `checkboxList`. +dove il primo parametro è il tipo di elemento (`DataFile` per `type=file`, `DataLine` per input a riga singola come `text`, `password`, `email` ecc. e `DataText` per tutti gli altri) e il secondo parametro `sel[]` corrisponde all'attributo HTML name. Il tipo di elemento può essere combinato con il valore `DataKeys`, che conserva le chiavi degli elementi. Questo è particolarmente utile per `select`, `radioList` e `checkboxList`. -`getHttpData()` restituisce un input sanificato. In questo caso, sarà sempre un array di stringhe UTF-8 valide, indipendentemente dall'attaccante inviato dal modulo. È un'alternativa al lavoro diretto con `$_POST` o `$_GET`, se si vogliono ricevere dati sicuri. +È essenziale che `getHttpData()` restituisca un valore sanificato, in questo caso sarà sempre un array di stringhe UTF-8 valide, indipendentemente da ciò che un utente malintenzionato potrebbe tentare di inviare al server. È analogo al lavoro diretto con `$_POST` o `$_GET`, ma con la differenza sostanziale che restituisce sempre dati puliti, come siete abituati con gli elementi standard dei form Nette. diff --git a/forms/it/in-presenter.texy b/forms/it/in-presenter.texy index a7b68bbb17..4743cf2fc6 100644 --- a/forms/it/in-presenter.texy +++ b/forms/it/in-presenter.texy @@ -1,36 +1,36 @@ -Moduli in Presentatori -********************** +Form nei presenter +****************** .[perex] -Nette Forms semplifica notevolmente la creazione e l'elaborazione dei moduli Web. In questo capitolo imparerete a utilizzare i moduli all'interno dei presentatori. +Nette Forms facilita enormemente la creazione e l'elaborazione dei form web. In questo capitolo imparerete come utilizzare i form all'interno dei presenter. -Se siete interessati a utilizzarli in modo completamente autonomo senza il resto del framework, esiste una guida per i [moduli autonomi |standalone]. +Se siete interessati a come usarli completamente da soli senza il resto del framework, è per voi la guida per l'[uso indipendente |standalone]. -Primo modulo .[#toc-first-form] -=============================== +Primo form +========== -Proviamo a scrivere un semplice modulo di registrazione. Il suo codice sarà simile a questo: +Proviamo a scrivere un semplice form di registrazione. Il suo codice sarà il seguente: ```php use Nette\Application\UI\Form; $form = new Form; -$form->addText('name', 'Name:'); +$form->addText('name', 'Nome:'); $form->addPassword('password', 'Password:'); -$form->addSubmit('send', 'Sign up'); +$form->addSubmit('send', 'Registrati'); $form->onSuccess[] = [$this, 'formSucceeded']; ``` -e nel browser il risultato dovrebbe apparire come questo: +e nel browser verrà visualizzato così: -[* form-en.webp *] +[* form-cs.webp *] -Il modulo nel presentatore è un oggetto della classe `Nette\Application\UI\Form`, il suo predecessore `Nette\Forms\Form` è destinato a un uso indipendente. Abbiamo aggiunto i campi nome, password e pulsante di invio. Infine, la riga con `$form->onSuccess` dice che dopo l'invio e l'esito positivo della convalida, deve essere chiamato il metodo `$this->formSucceeded()`. +Il form nel presenter è un oggetto della classe `Nette\Application\UI\Form`, il suo predecessore `Nette\Forms\Form` è destinato all'uso indipendente. Vi abbiamo aggiunto i cosiddetti elementi nome, password e pulsante di invio. E infine, la riga con `$form->onSuccess` dice che dopo l'invio e la validazione riuscita, deve essere chiamato il metodo `$this->formSucceeded()`. -Dal punto di vista del presentatore, il modulo è un componente comune. Pertanto, viene trattato come un componente e incorporato nel presentatore con il [metodo factory |application:components#Factory Methods]. L'aspetto sarà il seguente: +Dal punto di vista del presenter, il form è un componente comune. Pertanto, viene trattato come un componente e lo integriamo nel presenter tramite un [metodo factory |application:components#Metodi Factory]. Sarà simile a questo: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} use Nette; use Nette\Application\UI\Form; @@ -39,9 +39,9 @@ class HomePresenter extends Nette\Application\UI\Presenter protected function createComponentRegistrationForm(): Form { $form = new Form; - $form->addText('name', 'Name:'); + $form->addText('name', 'Nome:'); $form->addPassword('password', 'Password:'); - $form->addSubmit('send', 'Sign up'); + $form->addSubmit('send', 'Registrati'); $form->onSuccess[] = [$this, 'formSucceeded']; return $form; } @@ -49,93 +49,90 @@ class HomePresenter extends Nette\Application\UI\Presenter public function formSucceeded(Form $form, $data): void { // qui elaboriamo i dati inviati dal form - // $data->name contiene nome + // $data->name contiene il nome // $data->password contiene la password - $this->flashMessage('Ti sei iscritto con successo.'); + $this->flashMessage('Sei stato registrato con successo.'); $this->redirect('Home:'); } } ``` -E il rendering nel template viene effettuato utilizzando il tag `{control}`: +E nel template renderizziamo il form con il tag `{control}`: -```latte .{file:app/Presenters/templates/Home/default.latte} -<h1>Registration</h1> +```latte .{file:app/Presentation/Home/default.latte} +<h1>Registrazione</h1> {control registrationForm} ``` -E questo è tutto :-) Abbiamo un modulo funzionale e perfettamente [protetto |#Vulnerability Protection]. +E questo è praticamente tutto :-) Abbiamo un form funzionante e perfettamente [protetto |#Protezione dalle vulnerabilità]. -Ora starete pensando che è stato troppo veloce, chiedendovi come sia possibile che venga chiamato il metodo `formSucceeded()` e quali siano i parametri che riceve. Certo, avete ragione, questo merita una spiegazione. +E ora probabilmente state pensando che sia stato troppo veloce, vi state chiedendo come sia possibile che venga chiamato il metodo `formSucceeded()` e quali siano i parametri che riceve. Certo, avete ragione, questo merita una spiegazione. -Nette propone un meccanismo interessante, che chiamiamo [stile Hollywood |application:components#Hollywood style]. Invece di dover chiedere continuamente se è successo qualcosa ("il modulo è stato inviato?", "è stato inviato in modo valido?" o "non è stato inviato?"), si dice al framework "quando il modulo è completato in modo valido, chiama questo metodo" e si lascia lavorare ulteriormente. Chi programma in JavaScript ha familiarità con questo stile di programmazione. Si scrivono funzioni che vengono chiamate quando si verifica un determinato [evento |nette:glossary#Events]. Il linguaggio passa loro gli argomenti appropriati. +Nette infatti introduce un meccanismo fresco, che chiamiamo [Hollywood style |application:components#Stile Hollywood]. Invece di dovervi chiedere costantemente come sviluppatori se è successo qualcosa ("il form è stato inviato?", "è stato inviato validamente?" e "non è stato manomesso?"), dite al framework "quando il form sarà compilato validamente, chiama questo metodo" e lasciate il resto del lavoro a lui. Se programmate in JavaScript, questo stile di programmazione vi è familiare. Scrivete funzioni che vengono chiamate quando si verifica un certo [evento |nette:glossary#Eventi]. E il linguaggio passa loro gli argomenti appropriati. -Questo è il modo in cui è costruito il codice del presentatore di cui sopra. L'array `$form->onSuccess` rappresenta l'elenco dei callback PHP che Nette chiamerà quando il modulo viene inviato e compilato correttamente. -All'interno del [ciclo di vita del presentatore |application:presenters#Life Cycle of Presenter], si tratta di un cosiddetto segnale, quindi vengono chiamate dopo il metodo `action*` e prima del metodo `render*`. -A ogni callback passa il modulo stesso nel primo parametro e i dati inviati come oggetto [ArrayHash |utils:arrays#ArrayHash] nel secondo. È possibile omettere il primo parametro se non si ha bisogno dell'oggetto form. Il secondo parametro può essere ancora più utile, ma ne parleremo [più avanti |#Mapping to Classes]. +È proprio così che è costruito anche il codice del presenter sopra riportato. L'array `$form->onSuccess` rappresenta un elenco di callback PHP che Nette chiama nel momento in cui il form viene inviato e compilato correttamente (cioè è valido). Nell'ambito del [ciclo di vita del presenter |application:presenters#Ciclo di vita del presenter] si tratta del cosiddetto segnale, vengono quindi chiamati dopo il metodo `action*` e prima del metodo `render*`. E ad ogni callback passa come primo parametro il form stesso e come secondo i dati inviati sotto forma di oggetto [ArrayHash |utils:arrays#ArrayHash]. Il primo parametro può essere omesso se non si necessita dell'oggetto form. E il secondo parametro può essere più intelligente, ma di questo parleremo [più avanti |#Mappatura su classi]. -L'oggetto `$data` contiene le proprietà `name` e `password` con i dati inseriti dall'utente. Di solito si inviano direttamente i dati per una successiva elaborazione, che può essere, ad esempio, l'inserimento nel database. Tuttavia, è possibile che si verifichi un errore durante l'elaborazione, ad esempio che il nome utente sia già stato preso. In questo caso, passiamo l'errore al form utilizzando `addError()` e lasciamo che venga ridisegnato, con un messaggio di errore: +L'oggetto `$data` contiene le chiavi `name` e `password` con i dati compilati dall'utente. Di solito inviamo i dati direttamente per un'ulteriore elaborazione, che può essere ad esempio l'inserimento nel database. Durante l'elaborazione, però, può verificarsi un errore, ad esempio il nome utente è già occupato. In tal caso, restituiamo l'errore al form tramite `addError()` e lo facciamo renderizzare di nuovo, anche con il messaggio di errore. ```php -$form->addError('Sorry, username is already in use.'); +$form->addError('Ci dispiace, il nome utente è già in uso.'); ``` -Oltre a `onSuccess`, c'è anche `onSubmit`: le callback vengono sempre richiamate dopo l'invio del modulo, anche se non è stato compilato correttamente. E infine `onError`: le callback vengono chiamate solo se l'invio non è valido. Vengono richiamati anche se si invalida il form in `onSuccess` o `onSubmit` utilizzando `addError()`. +Oltre a `onSuccess` esiste anche `onSubmit`: i callback vengono chiamati sempre dopo l'invio del form, anche se non è compilato correttamente. E inoltre `onError`: i callback vengono chiamati solo se l'invio non è valido. Vengono chiamati anche se in `onSuccess` o `onSubmit` invalidiamo il form tramite `addError()`. -Dopo aver elaborato il modulo, si passa alla pagina successiva. In questo modo si evita che il modulo venga involontariamente ripresentato facendo clic sul pulsante *refresh*, *back* o spostando la cronologia del browser. +Dopo l'elaborazione del form, reindirizziamo alla pagina successiva. Ciò impedisce l'invio involontario ripetuto del form tramite il pulsante *aggiorna*, *indietro* o muovendosi nella cronologia del browser. -Provare ad aggiungere altri [controlli al modulo |controls]. +Provate ad aggiungere anche altri [elementi del form |controls]. -Accesso ai controlli .[#toc-access-to-controls] -=============================================== +Accesso agli elementi +===================== -Il form è un componente del presenter, nel nostro caso chiamato `registrationForm` (dal nome del metodo di fabbrica `createComponentRegistrationForm`), quindi in qualsiasi punto del presenter è possibile accedere al form utilizzando: +Il form è un componente del presenter, nel nostro caso chiamato `registrationForm` (dal nome del metodo factory `createComponentRegistrationForm`), quindi ovunque nel presenter potete accedere al form tramite: ```php $form = $this->getComponent('registrationForm'); // sintassi alternativa: $form = $this['registrationForm']; ``` -Anche i singoli controlli del modulo sono componenti, quindi è possibile accedervi nello stesso modo: +Anche i singoli elementi del form sono componenti, quindi potete accedervi allo stesso modo: ```php -$input = $form->getComponent('name'); // oppure $input = $form['name']; -$button = $form->getComponent('send'); // oppure $button = $form['send']; +$input = $form->getComponent('name'); // o $input = $form['name']; +$button = $form->getComponent('send'); // o $button = $form['send']; ``` -I controlli vengono rimossi utilizzando unset: +Gli elementi vengono rimossi tramite unset: ```php unset($form['name']); ``` -Regole di validazione .[#toc-validation-rules] -============================================== +Regole di validazione +===================== -La parola *valido* è stata usata più volte, ma il modulo non ha ancora regole di convalida. Risolviamo il problema. +Abbiamo menzionato la parola *valido,* ma il form per ora non ha regole di validazione. Rimediamo. -Il nome sarà obbligatorio, quindi lo contrassegneremo con il metodo `setRequired()`, il cui argomento è il testo del messaggio di errore che verrà visualizzato se l'utente non lo compila. Se non viene fornito alcun argomento, viene utilizzato il messaggio di errore predefinito. +Il nome sarà obbligatorio, quindi lo contrassegniamo con il metodo `setRequired()`, il cui argomento è il testo del messaggio di errore che verrà visualizzato se l'utente non compila il nome. Se non specifichiamo l'argomento, verrà utilizzato il messaggio di errore predefinito. ```php -$form->addText('name', 'Name:') - ->setRequired('Please fill your name.'); +$form->addText('name', 'Nome:') + ->setRequired('Inserisci il nome per favore'); ``` -Provate a inviare il modulo senza il nome compilato e vedrete che verrà visualizzato un messaggio di errore e il browser o il server lo rifiuteranno finché non lo compilate. +Provate a inviare il form senza compilare il nome e vedrete che verrà visualizzato un messaggio di errore e il browser o il server lo rifiuteranno finché non compilerete il campo. -Allo stesso tempo, non sarà possibile ingannare il sistema digitando solo spazi nell'input, ad esempio. Non c'è modo. Nette taglia automaticamente gli spazi bianchi a destra e a sinistra. Provate. È un'operazione che si dovrebbe sempre fare per ogni inserimento di una singola riga, ma che spesso viene dimenticata. Nette lo fa automaticamente. (Si può provare a ingannare i moduli e inviare una stringa multilinea come nome. Anche in questo caso, Nette non si lascia ingannare e le interruzioni di riga si trasformano in spazi). +Allo stesso tempo, non potete ingannare il sistema scrivendo nel campo, ad esempio, solo spazi. Niente da fare. Nette rimuove automaticamente gli spazi iniziali e finali. Provate. È una cosa che dovreste fare sempre con ogni input a riga singola, ma spesso viene dimenticata. Nette lo fa automaticamente. (Potete provare a ingannare il form e inviare una stringa multilinea come nome. Nemmeno qui Nette si lascia ingannare e trasforma gli a capo in spazi.) -Il modulo viene sempre convalidato sul lato server, ma viene generata anche una convalida JavaScript, che è rapida e l'utente viene a conoscenza dell'errore immediatamente, senza dover inviare il modulo al server. Questo è gestito dallo script `netteForms.js`. -Inserirlo nel modello di layout: +Il form viene sempre validato lato server, ma viene generata anche una validazione JavaScript, che avviene istantaneamente e l'utente viene informato dell'errore immediatamente, senza dover inviare il form al server. Questo è gestito dallo script `netteForms.js`. Inseritelo nel template del layout: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Se si guarda nel codice sorgente della pagina con il modulo, si può notare che Nette inserisce i campi obbligatori in elementi con una classe CSS `required`. Provate ad aggiungere il seguente stile al modello e l'etichetta "Nome" diventerà rossa. In modo elegante, contrassegniamo i campi obbligatori per gli utenti: +Se guardate il codice sorgente della pagina con il form, potete notare che Nette inserisce gli elementi obbligatori in elementi con la classe CSS `required`. Provate ad aggiungere al template il seguente foglio di stile e l'etichetta "Nome" diventerà rossa. In questo modo elegante segnaliamo agli utenti gli elementi obbligatori: ```latte <style> @@ -143,96 +140,96 @@ Se si guarda nel codice sorgente della pagina con il modulo, si può notare che </style> ``` -Ulteriori regole di validazione saranno aggiunte con il metodo `addRule()`. Il primo parametro è la regola, il secondo è di nuovo il testo del messaggio di errore e l'argomento opzionale regola di validazione può seguire. Che cosa significa? +Aggiungiamo ulteriori regole di validazione con il metodo `addRule()`. Il primo parametro è la regola, il secondo è di nuovo il testo del messaggio di errore e può ancora seguire un argomento della regola di validazione. Cosa si intende con questo? -Il modulo riceverà un altro input opzionale *età* con la condizione che deve essere un numero (`addInteger()`) e in determinati limiti (`$form::Range`). E qui useremo il terzo argomento di `addRule()`, l'intervallo stesso: +Estendiamo il form con un nuovo campo opzionale "età", che deve essere un numero intero (`addInteger()`) e inoltre in un intervallo consentito (`$form::Range`). E qui useremo proprio il terzo parametro del metodo `addRule()`, con cui passiamo al validatore l'intervallo richiesto come coppia `[da, a]`: ```php -$form->addInteger('age', 'Age:') - ->addRule($form::Range, 'You must be older 18 years and be under 120.', [18, 120]); +$form->addInteger('age', 'Età:') + ->addRule($form::Range, 'L\'età deve essere compresa tra 18 e 120', [18, 120]); ``` .[tip] -Se l'utente non compila il campo, le regole di validazione non saranno verificate, perché il campo è opzionale. +Se l'utente non compila il campo, le regole di validazione non verranno verificate, poiché l'elemento è opzionale. -Ovviamente c'è spazio per un piccolo refactoring. Nel messaggio di errore e nel terzo parametro, i numeri sono elencati in duplice copia, il che non è ideale. Se stessimo creando un [modulo multilingue |rendering#translating] e il messaggio contenente i numeri dovesse essere tradotto in più lingue, sarebbe più difficile modificare i valori. Per questo motivo, è possibile utilizzare i caratteri sostitutivi `%d`: +Qui si crea spazio per un piccolo refactoring. Nel messaggio di errore e nel terzo parametro, i numeri sono indicati in modo duplicato, il che non è ideale. Se stessimo creando [form multilingue |rendering#Traduzione] e il messaggio contenente numeri fosse tradotto in più lingue, un'eventuale modifica dei valori diventerebbe più difficile. Per questo motivo, è possibile utilizzare i segnaposto `%d` e Nette completerà i valori: ```php - ->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]); + ->addRule($form::Range, 'L\'età deve essere compresa tra %d e %d anni', [18, 120]); ``` -Torniamo al campo *password*, rendiamolo *richiesto* e verifichiamo la lunghezza minima della password (`$form::MinLength`), sempre utilizzando i caratteri sostitutivi del messaggio: +Torniamo all'elemento `password`, che renderemo anch'esso obbligatorio e verificheremo inoltre la lunghezza minima della password (`$form::MinLength`), sempre utilizzando il segnaposto: ```php $form->addPassword('password', 'Password:') - ->setRequired('Pick a password') - ->addRule($form::MinLength, 'Your password has to be at least %d long', 8); + ->setRequired('Scegli una password') + ->addRule($form::MinLength, 'La password deve avere almeno %d caratteri', 8); ``` -Aggiungeremo un campo `passwordVerify` al modulo, dove l'utente inserisce nuovamente la password, per il controllo. Utilizzando le regole di validazione, verifichiamo se le due password sono uguali (`$form::Equal`). E come argomento diamo un riferimento alla prima password usando le [parentesi quadre |#Access to Controls]: +Aggiungiamo al form anche il campo `passwordVerify`, dove l'utente inserirà nuovamente la password, per controllo. Tramite le regole di validazione verifichiamo se entrambe le password sono uguali (`$form::Equal`). E come parametro diamo un riferimento alla prima password usando le [parentesi quadre |#Accesso agli elementi]: ```php -$form->addPassword('passwordVerify', 'Password again:') - ->setRequired('Fill your password again to check for typo') - ->addRule($form::Equal, 'Password mismatch', $form['password']) +$form->addPassword('passwordVerify', 'Password di controllo:') + ->setRequired('Inserisci nuovamente la password per controllo') + ->addRule($form::Equal, 'Le password non corrispondono', $form['password']) ->setOmitted(); ``` -Con `setOmitted()`, contrassegniamo un elemento il cui valore non ci interessa e che esiste solo per la validazione. Il suo valore non viene passato a `$data`. +Tramite `setOmitted()` abbiamo contrassegnato l'elemento il cui valore in realtà non ci interessa e che esiste solo per motivi di validazione. Il valore non viene passato a `$data`. -Abbiamo un modulo completamente funzionale con validazione in PHP e JavaScript. Le capacità di validazione di Nette sono molto più ampie: si possono creare condizioni, visualizzare e nascondere parti di una pagina in base a esse, ecc. Potete trovare tutte le informazioni nel capitolo sulla [convalida dei moduli |validation]. +Con questo abbiamo un form completamente funzionante con validazione sia in PHP che in JavaScript. Le capacità di validazione di Nette sono molto più ampie, si possono creare condizioni, far visualizzare e nascondere parti della pagina in base ad esse, ecc. Tutto questo lo imparerete nel capitolo sulla [validazione dei form |validation]. -Valori predefiniti .[#toc-default-values] -========================================= +Valori predefiniti +================== -Spesso si impostano valori predefiniti per i controlli dei moduli: +Agli elementi del form impostiamo comunemente valori predefiniti: ```php -$form->addEmail('email', 'Email') +$form->addEmail('email', 'E-mail') ->setDefaultValue($lastUsedEmail); ``` -Spesso è utile impostare i valori predefiniti per tutti i controlli contemporaneamente. Ad esempio, quando il modulo viene utilizzato per modificare i record. Leggiamo il record dal database e lo impostiamo come valore predefinito: +Spesso è utile impostare i valori predefiniti per tutti gli elementi contemporaneamente. Ad esempio, quando il form serve per modificare record. Leggiamo il record dal database e impostiamo i valori predefiniti: ```php //$row = ['name' => 'John', 'age' => '33', /* ... */]; $form->setDefaults($row); ``` -Chiamare `setDefaults()` dopo aver definito i controlli. +Chiamate `setDefaults()` dopo aver definito gli elementi. -Rendering del modulo .[#toc-rendering-the-form] -=============================================== +Renderizzazione del form +======================== -Per impostazione predefinita, il modulo viene reso come una tabella. I singoli controlli seguono le linee guida di base per l'accessibilità del Web. Tutte le etichette sono generate come elementi `<label>` e sono associate ai rispettivi input; facendo clic sull'etichetta si sposta il cursore sull'input. +Standardmente il form viene renderizzato come una tabella. I singoli elementi soddisfano la regola base di accessibilità - tutte le etichette sono scritte come `<label>` e collegate al rispettivo elemento del form. Cliccando sull'etichetta, il cursore appare automaticamente nel campo del form. -Possiamo impostare qualsiasi attributo HTML per ogni elemento. Ad esempio, aggiungere un segnaposto: +A ogni elemento possiamo impostare attributi HTML arbitrari. Ad esempio, aggiungere un placeholder: ```php -$form->addInteger('age', 'Age:') - ->setHtmlAttribute('placeholder', 'Please fill in the age'); +$form->addInteger('age', 'Età:') + ->setHtmlAttribute('placeholder', 'Inserisci l\'età per favore'); ``` -Ci sono davvero molti modi per rendere un modulo, quindi è un [capitolo |rendering] dedicato [al rendering |rendering]. +I modi per renderizzare un form sono davvero tanti, quindi c'è un [capitolo separato sulla renderizzazione |rendering]. -Mappatura delle classi .[#toc-mapping-to-classes] -================================================= +Mappatura su classi +=================== -Torniamo al metodo `formSucceeded()`, che nel secondo parametro `$data` ottiene i dati inviati come oggetto `ArrayHash`. Poiché si tratta di una classe generica, come `stdClass`, mancheranno alcune comodità quando si lavora con essa, come il completamento del codice per le proprietà negli editor o l'analisi statica del codice. Questo potrebbe essere risolto con una classe specifica per ogni modulo, le cui proprietà rappresentano i singoli controlli. Ad esempio: +Torniamo al metodo `formSucceeded()`, che nel secondo parametro `$data` riceve i dati inviati come oggetto `ArrayHash`. Poiché si tratta di una classe generica, qualcosa come `stdClass`, ci mancherà una certa comodità nel lavorare con essa, come il suggerimento delle proprietà negli editor o l'analisi statica del codice. Questo potrebbe essere risolto avendo una classe specifica per ogni form, le cui proprietà rappresentano i singoli elementi. Ad esempio: ```php class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -A partire da PHP 8.0, è possibile utilizzare questa elegante notazione che utilizza un costruttore: +In alternativa, puoi utilizzare il costruttore: ```php class RegistrationFormData @@ -246,27 +243,29 @@ class RegistrationFormData } ``` -Come dire a Nette di restituire i dati come oggetti di questa classe? È più facile di quanto si pensi. Basta specificare la classe come tipo del parametro `$data` nel gestore: +Le proprietà della classe dati possono anche essere enum e verranno mappate automaticamente. .{data-version:3.2.4} + +Come dire a Nette di restituirci i dati come oggetti di questa classe? Più facile di quanto pensiate. Basta semplicemente indicare la classe come tipo del parametro `$data` nel metodo handler: ```php public function formSucceeded(Form $form, RegistrationFormData $data): void { - // $nome è un'istanza di RegistrationFormData + // $data è un'istanza di RegistrationFormData $name = $data->name; // ... } ``` -Si può anche specificare `array` come tipo e i dati verranno passati come array. +Come tipo si può indicare anche `array` e allora i dati verranno passati come array. -In modo simile, si può usare il metodo `getValues()`, al quale si passa come parametro il nome della classe o dell'oggetto da idratare: +In modo analogo si può usare anche la funzione `getValues()`, alla quale passiamo il nome della classe o l'oggetto da idratare come parametro: ```php $data = $form->getValues(RegistrationFormData::class); $name = $data->name; ``` -Se i moduli sono costituiti da una struttura a più livelli composta da contenitori, creare una classe separata per ciascuno di essi: +Se i form formano una struttura multilivello composta da container, create una classe separata per ciascuno: ```php $form = new Form; @@ -283,32 +282,34 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` -La mappatura sa quindi dal tipo di proprietà `$person` che deve mappare il contenitore alla classe `PersonFormData`. Se la proprietà contiene un array di contenitori, fornire il tipo `array` e passare la classe da mappare direttamente al contenitore: +La mappatura riconoscerà quindi dal tipo della proprietà `$person` che deve mappare il container sulla classe `PersonFormData`. Se la proprietà contenesse un array di container, specificate il tipo `array` e passate la classe per la mappatura direttamente al container: ```php $person->setMappedType(PersonFormData::class); ``` +Puoi farti generare il design della classe dati del form tramite il metodo `Nette\Forms\Blueprint::dataClass($form)`, che la stamperà nella pagina del browser. Basta quindi cliccare per selezionare il codice e copiarlo nel progetto. .{data-version:3.1.15} + -Pulsanti di invio multipli .[#toc-multiple-submit-buttons] -========================================================== +Più pulsanti +============ -Se il modulo ha più di un pulsante, di solito dobbiamo distinguere quale è stato premuto. Possiamo creare una funzione per ogni pulsante. Impostarla come gestore dell'[evento |nette:glossary#Events] `onClick`: +Se il form ha più di un pulsante, di solito abbiamo bisogno di distinguere quale di essi è stato premuto. Possiamo creare una funzione handler separata per ogni pulsante. La impostiamo come handler per l'[evento |nette:glossary#Eventi] `onClick`: ```php -$form->addSubmit('save', 'Save') +$form->addSubmit('save', 'Salva') ->onClick[] = [$this, 'saveButtonPressed']; -$form->addSubmit('delete', 'Delete') +$form->addSubmit('delete', 'Elimina') ->onClick[] = [$this, 'deleteButtonPressed']; ``` -Anche questi gestori sono chiamati solo nel caso in cui il modulo sia valido, come nel caso dell'evento `onSuccess`. La differenza è che il primo parametro può essere l'oggetto pulsante di invio invece del modulo, a seconda del tipo specificato: +Questi handler vengono chiamati solo nel caso di un form compilato validamente, proprio come nel caso dell'evento `onSuccess`. La differenza è che come primo parametro, invece del form, può essere passato il pulsante di invio, dipende dal tipo che specificate: ```php public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) @@ -318,62 +319,61 @@ public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) } ``` -Quando un modulo viene inviato con il tasto <kbd>Invio</kbd>, viene trattato come se fosse stato inviato con il primo pulsante. +Quando il form viene inviato con il tasto <kbd>Invio</kbd>, viene considerato come se fosse stato inviato con il primo pulsante. -Evento onAnchor .[#toc-event-onanchor] -====================================== +Evento onAnchor +=============== -Quando si costruisce un form con un metodo di fabbrica (come `createComponentRegistrationForm`), non si sa ancora se è stato inviato o i dati con cui è stato inviato. Ma ci sono casi in cui è necessario conoscere i valori inviati, forse perché da essi dipende l'aspetto del form, oppure perché sono usati per selectbox dipendenti, ecc. +Quando nel metodo factory (come ad esempio `createComponentRegistrationForm`) costruiamo il form, questo non sa ancora se è stato inviato, né con quali dati. Ci sono però casi in cui abbiamo bisogno di conoscere i valori inviati, ad esempio se da essi dipende l'ulteriore aspetto del form, o se ne abbiamo bisogno per selectbox dipendenti, ecc. -Pertanto, è possibile far sì che il codice che costruisce il form venga richiamato quando è ancorato, cioè è già collegato al presentatore e conosce i dati inviati. Questo codice sarà inserito nell'array `$onAnchor`: +La parte del codice che costruisce il form può quindi essere fatta chiamare solo nel momento in cui è cosiddetto ancorato, cioè è già collegato al presenter e conosce i suoi dati inviati. Tale codice lo passiamo all'array `$onAnchor`: ```php -$country = $form->addSelect('country', 'Country:', $this->model->getCountries()); -$city = $form->addSelect('city', 'City:'); +$country = $form->addSelect('country', 'Stato:', $this->model->getCountries()); +$city = $form->addSelect('city', 'Città:'); $form->onAnchor[] = function () use ($country, $city) { - // questa funzione sarà richiamata quando il form saprà che i dati sono stati inviati con - // in modo da poter utilizzare il metodo getValue() + // questa funzione viene chiamata solo quando il form sa se è stato inviato e con quali dati + // si può quindi usare il metodo getValue() $val = $country->getValue(); $city->setItems($val ? $this->model->getCities($val) : []); }; ``` -Protezione dalle vulnerabilità .[#toc-vulnerability-protection] -=============================================================== +Protezione dalle vulnerabilità +============================== -Nette Framework si impegna a fondo per essere sicuro e, poiché i moduli sono l'input più comune dell'utente, i moduli di Nette sono praticamente impenetrabili. Tutto viene mantenuto in modo dinamico e trasparente, nulla deve essere impostato manualmente. +Nette Framework pone grande enfasi sulla sicurezza e quindi si preoccupa meticolosamente della buona protezione dei form. Lo fa in modo completamente trasparente e non richiede alcuna impostazione manuale. -Oltre a proteggere i moduli da attacchi mirati a vulnerabilità ben note come [Cross-Site Scripting (XSS) |nette:glossary#cross-site-scripting-xss] e [Cross-Site Request Forgery (CSRF) |nette:glossary#cross-site-request-forgery-csrf], svolge molte piccole operazioni di sicurezza a cui non è più necessario pensare. +Oltre a proteggere i form dagli attacchi [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] e [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF], implementa molte piccole misure di sicurezza a cui non dovete più pensare. -Ad esempio, filtra tutti i caratteri di controllo dagli input e controlla la validità della codifica UTF-8, in modo che i dati del modulo siano sempre puliti. Per le caselle di selezione e gli elenchi di scelta, verifica che le voci selezionate siano effettivamente quelle proposte e che non ci siano state contraffazioni. Abbiamo già detto che per gli input di testo a riga singola, rimuove i caratteri di fine riga che un utente malintenzionato potrebbe inviare. Per gli input multilinea, normalizza i caratteri di fine riga. E così via. +Ad esempio, filtra tutti i caratteri di controllo dagli input e verifica la validità della codifica UTF-8, quindi i dati dal form saranno sempre puliti. Per i select box e i radio list, verifica che gli elementi selezionati fossero effettivamente tra quelli offerti e che non ci sia stata manomissione. Abbiamo già menzionato che per gli input di testo a riga singola rimuove i caratteri di fine riga che un utente malintenzionato potrebbe aver inviato. Per gli input multilinea, invece, normalizza i caratteri di fine riga. E così via. -Nette risolve per voi vulnerabilità di sicurezza che la maggior parte dei programmatori non ha idea che esistano. +Nette risolve per voi i rischi di sicurezza di cui molti programmatori non sospettano nemmeno l'esistenza. -L'attacco CSRF menzionato consiste nel fatto che un aggressore attira la vittima a visitare una pagina che esegue silenziosamente una richiesta nel browser della vittima al server in cui la vittima è attualmente loggata, e il server crede che la richiesta sia stata fatta dalla vittima a suo piacimento. Pertanto, Nette impedisce l'invio del modulo tramite POST da un altro dominio. Se per qualche motivo si desidera disattivare la protezione e consentire l'invio del modulo da un altro dominio, utilizzare: +L'attacco CSRF menzionato consiste nel fatto che un utente malintenzionato attira la vittima su una pagina che esegue discretamente nel browser della vittima una richiesta al server su cui la vittima è loggata, e il server crede che la richiesta sia stata eseguita dalla vittima di sua volontà. Pertanto, Nette impedisce l'invio di form POST da un dominio diverso. Se per qualche motivo volete disattivare la protezione e consentire l'invio del form da un dominio diverso, usate: ```php $form->allowCrossOrigin(); // ATTENZIONE! Disattiva la protezione! ``` -Questa protezione utilizza un cookie SameSite denominato `_nss`. La protezione dei cookie SameSite potrebbe non essere affidabile al 100%, quindi è una buona idea attivare la protezione dei token: +Questa protezione utilizza un cookie SameSite chiamato `_nss`. La protezione tramite cookie SameSite potrebbe non essere affidabile al 100%, quindi è consigliabile attivare anche la protezione tramite token: ```php $form->addProtection(); ``` -Si consiglia vivamente di applicare questa protezione ai moduli di una parte amministrativa dell'applicazione che modificano dati sensibili. Il framework protegge da un attacco CSRF generando e validando il token di autenticazione che viene memorizzato in una sessione (l'argomento è il messaggio di errore mostrato se il token è scaduto). Per questo motivo è necessario che sia avviata una sessione prima di visualizzare il modulo. Nella parte amministrativa del sito web, di solito la sessione è già avviata, grazie al login dell'utente. -Altrimenti, avviare la sessione con il metodo `Nette\Http\Session::start()`. +Raccomandiamo di proteggere in questo modo i form nella parte amministrativa del sito, che modificano dati sensibili nell'applicazione. Il framework si difende dall'attacco CSRF generando e verificando un token di autorizzazione, che viene salvato nella sessione. Pertanto, è necessario avere una sessione aperta prima di visualizzare il form. Nella parte amministrativa del sito, di solito la sessione è già avviata a causa del login dell'utente. Altrimenti, avviate la sessione con il metodo `Nette\Http\Session::start()`. -Utilizzo di un modulo in più presentatori .[#toc-using-one-form-in-multiple-presenters] -======================================================================================= +Stesso form in più presenter +============================ -Se si ha la necessità di utilizzare un modulo in più di un presentatore, si consiglia di creare un factory per esso, da passare poi al presentatore. Un luogo adatto per questa classe è, ad esempio, la cartella `app/Forms`. +Se avete bisogno di utilizzare lo stesso form in più presenter, vi consigliamo di creare una factory per esso, che poi passerete al presenter. Una posizione adatta per una tale classe è ad esempio la directory `app/Forms`. -La classe factory potrebbe avere il seguente aspetto: +La classe factory può assomigliare a questo: ```php use Nette\Application\UI\Form; @@ -383,14 +383,14 @@ class SignInFormFactory public function create(): Form { $form = new Form; - $form->addText('name', 'Name:'); - $form->addSubmit('send', 'Log in'); + $form->addText('name', 'Nome:'); + $form->addSubmit('send', 'Accedi'); return $form; } } ``` -Chiediamo alla classe di produrre il modulo nel metodo factory per i componenti del presentatore: +Chiediamo alla classe di produrre il form nel metodo factory per i componenti nel presenter: ```php public function __construct( @@ -401,14 +401,14 @@ public function __construct( protected function createComponentSignInForm(): Form { $form = $this->formFactory->create(); - // possiamo modificare il form, qui ad esempio cambiamo l'etichetta del pulsante - $form['login']->setCaption('Continue'); - $form->onSuccess[] = [$this, 'signInFormSubmitted']; // e aggiungiamo il gestore + // possiamo modificare il form, qui ad esempio cambiamo l'etichetta sul pulsante + $form['send']->setCaption('Continua'); + $form->onSuccess[] = [$this, 'signInFormSucceeded']; // e aggiungiamo l'handler return $form; } ``` -Anche il gestore dell'elaborazione del modulo può essere fornito dal factory: +L'handler per l'elaborazione del form può anche essere fornito già dalla factory: ```php use Nette\Application\UI\Form; @@ -418,14 +418,14 @@ class SignInFormFactory public function create(): Form { $form = new Form; - $form->addText('name', 'Name:'); - $form->addSubmit('send', 'Log in'); + $form->addText('name', 'Nome:'); + $form->addSubmit('send', 'Accedi'); $form->onSuccess[] = function (Form $form, $data): void { - // elaboriamo qui il nostro modulo inviato + // qui eseguiamo l'elaborazione del form }; return $form; } } ``` -Abbiamo così una rapida introduzione ai moduli in Nette. Provate a dare un'occhiata alla cartella [degli esempi |https://github.com/nette/forms/tree/master/examples] nella distribuzione per trovare ulteriori spunti. +Bene, abbiamo completato una rapida introduzione ai form in Nette. Provate a dare un'occhiata anche alla directory [examples |https://github.com/nette/forms/tree/master/examples] nella distribuzione, dove troverete ulteriore ispirazione. diff --git a/forms/it/rendering.texy b/forms/it/rendering.texy index b1ac2c635b..df2923bfad 100644 --- a/forms/it/rendering.texy +++ b/forms/it/rendering.texy @@ -1,33 +1,35 @@ -Rendering dei moduli -******************** +Renderizzazione dei Form +************************ -L'aspetto delle forme può essere molto vario. In pratica, si possono incontrare due estremi. Da un lato, c'è la necessità di rendere una serie di moduli in un'applicazione che siano visivamente simili tra loro e si apprezza la facilità di renderli senza un modello usando `$form->render()`. Questo è solitamente il caso delle interfacce amministrative. +L'aspetto dei form può essere molto vario. In pratica, possiamo incontrare due estremi. Da un lato, c'è la necessità di renderizzare nell'applicazione una serie di form che sono visivamente simili come due gocce d'acqua, e apprezzeremo la facile renderizzazione senza template tramite `$form->render()`. Questo è solitamente il caso delle interfacce di amministrazione. -D'altra parte, esistono vari moduli, ognuno dei quali è unico. Il loro aspetto è meglio descritto utilizzando il linguaggio HTML nel template. E naturalmente, oltre ai due estremi citati, incontreremo molti moduli che si collocano a metà strada. +Dall'altro lato, ci sono form diversi, dove vale la regola: ogni pezzo è un originale. La loro forma è meglio descritta dal linguaggio HTML nel template del form. E naturalmente, oltre ai due estremi menzionati, incontreremo molti form che si trovano da qualche parte nel mezzo. -Rendering con Latte .[#toc-rendering-with-latte] -================================================ +Renderizzazione tramite Latte +============================= -Il [sistema di template Latte |latte:] facilita fondamentalmente il rendering dei moduli e dei loro elementi. In primo luogo, mostreremo come rendere i moduli manualmente, elemento per elemento, per ottenere il pieno controllo sul codice. In seguito mostreremo come [automatizzare |#Automatic rendering] tale rendering. +Il [sistema di templating Latte |latte:] facilita enormemente la renderizzazione dei form e dei loro elementi. Prima mostreremo come renderizzare i form manualmente, elemento per elemento, ottenendo così il pieno controllo sul codice. Successivamente mostreremo come tale renderizzazione possa essere [automatizzata |#Renderizzazione automatica]. + +Puoi farti generare il design del template Latte del form tramite il metodo `Nette\Forms\Blueprint::latte($form)`, che lo stamperà nella pagina del browser. Basta quindi cliccare per selezionare il codice e copiarlo nel progetto. .{data-version:3.1.15} `{control}` ----------- -Il modo più semplice per rendere un modulo è scrivere in un modello: +Il modo più semplice per renderizzare un form è scrivere nel template: ```latte {control signInForm} ``` -L'aspetto del modulo renderizzato può essere modificato configurando il [Renderer |#Renderer] e i [singoli controlli |#HTML Attributes]. +È possibile influenzare l'aspetto di un form così renderizzato configurando il [#Renderer] e i [singoli elementi |#Attributi HTML]. `n:name` -------- -È estremamente facile collegare la definizione del modulo nel codice PHP con il codice HTML. Basta aggiungere gli attributi `n:name`. È facilissimo! +La definizione del form nel codice PHP può essere collegata molto facilmente al codice HTML. Basta aggiungere gli attributi `n:name`. È così facile! ```php protected function createComponentSignInForm(): Form @@ -54,10 +56,9 @@ protected function createComponentSignInForm(): Form </form> ``` -L'aspetto del codice HTML risultante è interamente nelle vostre mani. Se si utilizza l'attributo `n:name` con `<select>`, `<button>` o `<textarea>` il loro contenuto interno viene riempito automaticamente. -Inoltre, il tag `<form n:name>` crea una variabile locale `$form` con l'oggetto del modulo disegnato e la chiusura `</form>` disegna tutti gli elementi nascosti non disegnati (lo stesso vale per `{form} ... {/form}`). +Avete il pieno controllo sull'aspetto del codice HTML risultante. Se utilizzate l'attributo `n:name` sugli elementi `<select>`, `<button>` o `<textarea>`, il loro contenuto interno verrà completato automaticamente. Il tag `<form n:name>` crea inoltre una variabile locale `$form` con l'oggetto del form disegnato e il tag di chiusura `</form>` renderizza tutti gli elementi nascosti non renderizzati (lo stesso vale anche per `{form} ... {/form}`). -Tuttavia, non bisogna dimenticare di rendere i possibili messaggi di errore. Sia quelli aggiunti ai singoli elementi dal metodo `addError()` (utilizzando `{inputError}`), sia quelli aggiunti direttamente al modulo (restituiti da `$form->getOwnErrors()`): +Non dobbiamo però dimenticare di renderizzare eventuali messaggi di errore. Sia quelli aggiunti ai singoli elementi con il metodo `addError()` (tramite `{inputError}`), sia quelli aggiunti direttamente al form (restituiti da `$form->getOwnErrors()`): ```latte <form n:name=signInForm class=form> @@ -79,7 +80,7 @@ Tuttavia, non bisogna dimenticare di rendere i possibili messaggi di errore. Sia </form> ``` -Elementi di form più complessi, come RadioList o CheckboxList, possono essere resi elemento per elemento: +Elementi del form più complessi, come RadioList o CheckboxList, possono essere renderizzati in questo modo per singoli elementi: ```latte {foreach $form[gender]->getItems() as $key => $label} @@ -88,16 +89,10 @@ Elementi di form più complessi, come RadioList o CheckboxList, possono essere r ``` -Proposta di codice `{formPrint}` .[#toc-formprint] --------------------------------------------------- - -È possibile generare un codice Latte simile per un modulo usando il tag `{formPrint}`. Se lo si inserisce in un modello, si vedrà la bozza di codice invece del normale rendering. È sufficiente selezionarlo e copiarlo nel progetto. - - `{label}` `{input}` ------------------- -Non si vuole pensare, per ogni elemento, a quale elemento HTML usare per esso nel template, se `<input>`, `<textarea>` ecc. La soluzione è il tag universale `{input}`: +Non vuoi pensare per ogni elemento quale elemento HTML usare nel template, se `<input>`, `<textarea>`, ecc.? La soluzione è il tag universale `{input}`: ```latte <form n:name=signInForm class=form> @@ -119,9 +114,9 @@ Non si vuole pensare, per ogni elemento, a quale elemento HTML usare per esso ne </form> ``` -Se il modulo utilizza un traduttore, il testo all'interno del tag `{label}` verrà tradotto. +Se il form utilizza un traduttore, il testo all'interno dei tag `{label}` verrà tradotto. -Anche in questo caso, elementi di form più complessi, come RadioList o CheckboxList, possono essere resi elemento per elemento: +Anche in questo caso, elementi del form più complessi, come RadioList o CheckboxList, possono essere renderizzati per singoli elementi: ```latte {foreach $form[gender]->items as $key => $label} @@ -129,20 +124,19 @@ Anche in questo caso, elementi di form più complessi, come RadioList o Checkbox {/foreach} ``` -Per rendere l'elemento `<input>` nell'elemento Checkbox, utilizzare `{input myCheckbox:}`. Gli attributi HTML devono essere separati da una virgola `{input myCheckbox:, class: required}`. +Per renderizzare solo l'`<input>` nell'elemento Checkbox, usa `{input myCheckbox:}`. Gli attributi HTML in questo caso vanno sempre separati da virgola `{input myCheckbox:, class: required}`. `{inputError}` -------------- -Stampa un messaggio di errore per l'elemento del modulo, se ne ha uno. Il messaggio è solitamente avvolto in un elemento HTML per lo styling. -Evitare di rendere un elemento vuoto se non c'è un messaggio può essere fatto elegantemente con `n:ifcontent`: +Stampa il messaggio di errore per l'elemento del form, se ne ha uno. Il messaggio viene solitamente racchiuso in un elemento HTML per lo styling. Evitare la renderizzazione di un elemento vuoto, se il messaggio non c'è, è possibile elegantemente tramite `n:ifcontent`: ```latte <span class=error n:ifcontent>{inputError $input}</span> ``` -Possiamo rilevare la presenza di un errore usando il metodo `hasErrors()` e impostare la classe dell'elemento genitore di conseguenza: +Possiamo verificare la presenza di un errore con il metodo `hasErrors()` e in base a ciò impostare una classe sull'elemento genitore: ```latte <div n:class="$form[username]->hasErrors() ? 'error'"> @@ -158,11 +152,10 @@ Possiamo rilevare la presenza di un errore usando il metodo `hasErrors()` e impo I tag `{form signInForm}...{/form}` sono un'alternativa a `<form n:name="signInForm">...</form>`. -Rendering automatico .[#toc-automatic-rendering] ------------------------------------------------- +Renderizzazione automatica +-------------------------- -Con i tag `{input}` e `{label}`, si può facilmente creare un modello generico per qualsiasi form. Esso itererà e renderà tutti i suoi elementi in modo sequenziale, tranne gli elementi nascosti, che vengono resi automaticamente quando il form viene terminato con il tag `</form>` . -Si aspetta il nome del form reso nella variabile `$form`. +Grazie ai tag `{input}` e `{label}` possiamo facilmente creare un template generico per qualsiasi form. Itererà e renderizzerà gradualmente tutti i suoi elementi, ad eccezione degli elementi nascosti, che verranno renderizzati automaticamente alla chiusura del form con il tag `</form>`. Il nome del form da renderizzare sarà atteso nella variabile `$form`. ```latte <form n:name=$form class=form> @@ -179,16 +172,15 @@ Si aspetta il nome del form reso nella variabile `$form`. </form> ``` -I tag a coppia autochiudente utilizzati `{label .../}` mostrano le etichette provenienti dalla definizione del modulo nel codice PHP. +I tag di coppia auto-chiudenti `{label .../}` utilizzati visualizzano le etichette provenienti dalla definizione del form nel codice PHP. -Si può salvare questo modello generico nel file `basic-form.latte` e per rendere il modulo, basta includerlo e passare il nome del modulo (o l'istanza) al parametro `$form`: +Salvate questo template generico ad esempio nel file `basic-form.latte` e per renderizzare il form basta includerlo e passare il nome (o l'istanza) del form al parametro `$form`: ```latte {include basic-form.latte, form: signInForm} ``` -Se si desidera influenzare l'aspetto di un particolare modulo e disegnare un elemento in modo diverso, il modo più semplice è preparare dei blocchi nel modello che possono essere sovrascritti in seguito. -I blocchi possono anche avere [nomi dinamici |latte:template-inheritance#dynamic-block-names], per cui è possibile inserire il nome dell'elemento da disegnare. Ad esempio: +Se durante la renderizzazione di un particolare form voleste intervenire sulla sua forma e ad esempio renderizzare un elemento diversamente, allora il modo più semplice è preparare nel template dei blocchi che potranno essere successivamente sovrascritti. I blocchi possono avere anche [nomi dinamici |latte:template-inheritance#Nomi dinamici dei blocchi], quindi è possibile inserirvi anche il nome dell'elemento renderizzato. Ad esempio: ```latte ... @@ -197,7 +189,7 @@ I blocchi possono anche avere [nomi dinamici |latte:template-inheritance#dynamic ... ``` -Per l'elemento `username` viene creato il blocco `input-username`, che può essere facilmente sovrascritto utilizzando il tag [{embed} |latte:template-inheritance#unit-inheritance]: +Per l'elemento ad esempio `username` verrà così creato il blocco `input-username`, che può essere facilmente sovrascritto utilizzando il tag [{embed} |latte:template-inheritance#Ereditarietà unitaria embed]: ```latte {embed basic-form.latte, form: signInForm} @@ -209,7 +201,7 @@ Per l'elemento `username` viene creato il blocco `input-username`, che può esse {/embed} ``` -In alternativa, l'intero contenuto del template `basic-form.latte` può essere [definito |latte:template-inheritance#definitions] come un blocco, compreso il parametro `$form`: +In alternativa, l'intero contenuto del template `basic-form.latte` può essere [definito |latte:template-inheritance#Definizioni define] come blocco, incluso il parametro `$form`: ```latte {define basic-form, $form} @@ -219,7 +211,7 @@ In alternativa, l'intero contenuto del template `basic-form.latte` può essere [ {/define} ``` -In questo modo sarà un po' più facile da usare: +Grazie a ciò, la sua chiamata sarà leggermente più semplice: ```latte {embed basic-form, signInForm} @@ -227,31 +219,31 @@ In questo modo sarà un po' più facile da usare: {/embed} ``` -È sufficiente importare il blocco in un solo punto, all'inizio del modello di layout: +Il blocco basta importarlo in un unico punto, all'inizio del template di layout: ```latte {import basic-form.latte} ``` -Casi speciali .[#toc-special-cases] ------------------------------------ +Casi speciali +------------- -Se si ha bisogno di rendere solo il contenuto interno di un modulo senza `<form>` & `</form>` HTML, ad esempio in una richiesta AJAX, si può aprire e chiudere il modulo con `{formContext} … {/formContext}`. Funziona in modo simile a `{form}` in senso logico, ma permette di usare altri tag per disegnare gli elementi del modulo, ma allo stesso tempo non disegna nulla. +Se hai bisogno di renderizzare solo la parte interna del form senza i tag HTML `<form>`, ad esempio quando invii snippet, nascondili usando l'attributo `n:tag-if`: ```latte -{formContext signForm} +<form n:name=signInForm n:tag-if=false> <div> <label n:name=username>Username: <input n:name=username></label> {inputError username} </div> -{/formContext} +</form> ``` -Il tag `formContainer` aiuta a rendere gli input all'interno di un contenitore di form. +Con la renderizzazione degli elementi all'interno del contenitore del form aiuta il tag `{formContainer}`. ```latte -<p>Which news you wish to receive:</p> +<p>Quali notizie desideri ricevere:</p> {formContainer emailNews} <ul> @@ -262,27 +254,27 @@ Il tag `formContainer` aiuta a rendere gli input all'interno di un contenitore d ``` -Rendering senza Latte .[#toc-rendering-without-latte] -===================================================== +Renderizzazione senza Latte +=========================== -Il modo più semplice per rendere un modulo è chiamare: +Il modo più semplice per renderizzare un form è chiamare: ```php $form->render(); ``` -L'aspetto del modulo renderizzato può essere modificato configurando il [Renderer |#Renderer] e i [singoli controlli |#HTML Attributes]. +È possibile influenzare l'aspetto di un form così renderizzato configurando il [#Renderer] e i [singoli elementi |#Attributi HTML]. -Rendering manuale .[#toc-manual-rendering] ------------------------------------------- +Renderizzazione manuale +----------------------- -Ogni elemento del modulo ha metodi che generano il codice HTML per il campo e l'etichetta del modulo. Possono restituirlo sotto forma di stringa o di oggetto [Nette\Utils\Html |utils:html-elements]: +Ogni elemento del form dispone di metodi che generano il codice HTML del campo del form e dell'etichetta. Possono restituirlo sia come stringa che come oggetto [Nette\Utils\Html |utils:html-elements]: - `getControl(): Html|string` restituisce il codice HTML dell'elemento -- `getLabel($caption = null): Html|string|null` restituisce il codice HTML dell'eventuale etichetta +- `getLabel($caption = null): Html|string|null` restituisce il codice HTML dell'etichetta, se esiste -Questo permette di rendere il modulo elemento per elemento: +Il form può quindi essere renderizzato per singoli elementi: ```php <?php $form->render('begin') ?> @@ -305,47 +297,46 @@ Questo permette di rendere il modulo elemento per elemento: <?php $form->render('end') ?> ``` -Mentre per alcuni elementi `getControl()` restituisce un singolo elemento HTML (ad es. `<input>`, `<select>` ecc.), per altri restituisce un intero pezzo di codice HTML (CheckboxList, RadioList). -In questo caso, si possono usare metodi che generano input ed etichette individuali, per ogni elemento separatamente: +Mentre per alcuni elementi `getControl()` restituisce un singolo elemento HTML (es. `<input>`, `<select>`, ecc.), per altri restituisce un intero pezzo di codice HTML (CheckboxList, RadioList). In tal caso, potete utilizzare metodi che generano singoli input ed etichette, per ogni elemento separatamente: - `getControlPart($key = null): ?Html` restituisce il codice HTML di un singolo elemento - `getLabelPart($key = null): ?Html` restituisce il codice HTML dell'etichetta di un singolo elemento .[note] -Questi metodi hanno il prefisso `get` per ragioni storiche, ma sarebbe meglio `generate`, che crea e restituisce un nuovo elemento `Html` a ogni chiamata. +Questi metodi hanno per motivi storici il prefisso `get`, ma sarebbe stato meglio `generate`, perché ad ogni chiamata creano e restituiscono un nuovo elemento `Html`. -Renderer .[#toc-renderer] -========================= +Renderer +======== -È un oggetto che fornisce il rendering del modulo. Può essere impostato dal metodo `$form->setRenderer`. Viene passato il controllo quando viene chiamato il metodo `$form->render()`. +È un oggetto che si occupa della renderizzazione del form. Può essere impostato con il metodo `$form->setRenderer`. Gli viene passato il controllo quando viene chiamato il metodo `$form->render()`. -Se non si imposta un renderer personalizzato, verrà utilizzato il renderer predefinito [api:Nette\Forms\Rendering\DefaultFormRenderer]. Questo renderà gli elementi del modulo come una tabella HTML. L'output appare come questo: +Se non impostiamo un renderer personalizzato, verrà utilizzato il renderer predefinito [api:Nette\Forms\Rendering\DefaultFormRenderer]. Questo renderizza gli elementi del form sotto forma di tabella HTML. L'output è simile a questo: ```latte <table> <tr class="required"> - <th><label class="required" for="frm-name">Name:</label></th> + <th><label class="required" for="frm-name">Nome:</label></th> <td><input type="text" class="text" name="name" id="frm-name" required value=""></td> </tr> <tr class="required"> - <th><label class="required" for="frm-age">Age:</label></th> + <th><label class="required" for="frm-age">Età:</label></th> <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> </tr> <tr> - <th><label>Gender:</label></th> + <th><label>Sesso:</label></th> ... ``` -Sta a voi decidere se usare una tabella o meno e molti web designer preferiscono markup diversi, per esempio un elenco. Possiamo configurare `DefaultFormRenderer` in modo che non venga reso in una tabella. Basta impostare i [$wrapper |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers] appropriati. Il primo indice rappresenta sempre un'area e il secondo un elemento. Tutte le rispettive aree sono mostrate nell'immagine: +Se utilizzare o meno una tabella per la struttura del form è discutibile e molti web designer preferiscono un markup diverso. Ad esempio, una lista di definizioni. Riconfigureremo quindi `DefaultFormRenderer` in modo che renderizzi il form sotto forma di lista. La configurazione viene eseguita modificando l'array [$wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers]. Il primo indice rappresenta sempre l'area e il secondo il suo attributo. Le singole aree sono illustrate nell'immagine: -[* form-areas-en.webp *] +[* defaultformrenderer.webp *] -Per impostazione predefinita, un gruppo di `controls` è avvolto in `<table>`e ogni `pair` è una riga di tabella `<tr>` contenente una coppia di `label` e `control` (celle `<th>` e `<td>`). Cambiamo tutti questi elementi wrapper. Avvolgeremo `controls` in `<dl>`lasciamo `pair` da solo, inseriamo `label` in `<dt>` e inseriamo `control` in `<dd>`: +Standardmente il gruppo di elementi `controls` è avvolto da una tabella `<table>`, ogni `pair` rappresenta una riga della tabella `<tr>` e la coppia `label` e `control` sono le celle `<th>` e `<td>`. Ora cambieremo gli elementi contenitori. L'area `controls` la inseriremo nel contenitore `<dl>`, l'area `pair` la lasceremo senza contenitore, `label` la inseriremo in `<dt>` e infine `control` lo avvolgeremo con i tag `<dd>`: ```php $renderer = $form->getRenderer(); @@ -357,193 +348,192 @@ $renderer->wrappers['control']['container'] = 'dd'; $form->render(); ``` -Il risultato è il seguente snippet: +Il risultato è questo codice HTML: ```latte <dl> - <dt><label class="required" for="frm-name">Name:</label></dt> + <dt><label class="required" for="frm-name">Nome:</label></dt> <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> - <dt><label class="required" for="frm-age">Age:</label></dt> + <dt><label class="required" for="frm-age">Età:</label></dt> <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> - <dt><label>Gender:</label></dt> + <dt><label>Sesso:</label></dt> ... </dl> ``` -I wrapper possono influenzare molti attributi. Ad esempio: +Nell'array wrappers è possibile influenzare tutta una serie di altri attributi: -- aggiungere classi CSS speciali a ciascun input del modulo -- distinguere tra linee pari e dispari -- disegnare in modo diverso gli elementi obbligatori e quelli opzionali -- impostare se i messaggi di errore vengono mostrati sopra il modulo o vicino a ogni elemento +- aggiungere classi CSS a singoli tipi di elementi del form +- distinguere con classi CSS le righe pari e dispari +- distinguere visivamente gli elementi obbligatori e facoltativi +- determinare se i messaggi di errore verranno visualizzati direttamente accanto agli elementi o sopra il form -Opzioni .[#toc-options] ------------------------ +Options +------- -Il comportamento del Renderer può essere controllato anche impostando delle *opzioni* sui singoli elementi del modulo. In questo modo è possibile impostare il tooltip che viene visualizzato accanto al campo di input: +Il comportamento del Renderer può essere controllato anche impostando *options* sui singoli elementi del form. In questo modo è possibile impostare una descrizione che verrà visualizzata accanto al campo di input: ```php -$form->addText('phone', 'Number:') - ->setOption('description', 'This number will remain hidden'); +$form->addText('phone', 'Numero:') + ->setOption('description', 'Questo numero rimarrà nascosto'); ``` -Se si vuole inserire del contenuto HTML, si usa la classe [Html |utils:html-elements]. +Se vogliamo inserirvi contenuto HTML, utilizziamo la classe [Html |utils:html-elements] ```php use Nette\Utils\Html; -$form->addText('phone', 'Phone:') +$form->addText('phone', 'Numero:') ->setOption('description', Html::el('p') - ->setHtml('<a href="...">Terms of service.</a>') + ->setHtml('<a href="...">Condizioni di conservazione del tuo numero</a>') ); ``` .[tip] -L'elemento Html può essere utilizzato anche al posto di label: `$form->addCheckbox('conditions', $label)`. +L'elemento Html può essere utilizzato anche al posto dell'etichetta: `$form->addCheckbox('conditions', $label)`. -Raggruppare gli input .[#toc-grouping-inputs] ---------------------------------------------- +Raggruppamento degli elementi +----------------------------- -Il renderer consente di raggruppare gli elementi in gruppi visivi (fieldset): +Il Renderer consente di raggruppare gli elementi in gruppi visivi (fieldset): ```php -$form->addGroup('Personal data'); +$form->addGroup('Dati personali'); ``` -La creazione di un nuovo gruppo lo attiva: tutti gli elementi aggiunti successivamente vengono aggiunti a questo gruppo. Si può costruire un modulo come questo: +Dopo aver creato un nuovo gruppo, questo diventa attivo e ogni elemento appena aggiunto viene aggiunto anche ad esso. Quindi il form può essere costruito in questo modo: ```php $form = new Form; -$form->addGroup('Personal data'); -$form->addText('name', 'Your name:'); -$form->addInteger('age', 'Your age:'); +$form->addGroup('Dati personali'); +$form->addText('name', 'Il tuo nome:'); +$form->addInteger('age', 'La tua età:'); $form->addEmail('email', 'Email:'); -$form->addGroup('Shipping address'); -$form->addCheckbox('send', 'Ship to address'); -$form->addText('street', 'Street:'); -$form->addText('city', 'City:'); -$form->addSelect('country', 'Country:', $countries); +$form->addGroup('Indirizzo di spedizione'); +$form->addCheckbox('send', 'Spedisci all\'indirizzo'); +$form->addText('street', 'Via:'); +$form->addText('city', 'Città:'); +$form->addSelect('country', 'Paese:', $countries); ``` +Il Renderer renderizza prima i gruppi e solo dopo gli elementi che non appartengono a nessun gruppo. -Supporto Bootstrap .[#toc-bootstrap-support] --------------------------------------------- -È possibile trovare [esempi |https://github.com/nette/forms/tree/master/examples] di configurazione del Renderer per [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], [Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] e [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php] +Supporto per Bootstrap +---------------------- +[Negli esempi |https://github.com/nette/forms/tree/master/examples] troverete esempi su come configurare il Renderer per [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], [Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] e [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php] -Attributi HTML .[#toc-html-attributes] -====================================== -È possibile impostare qualsiasi attributo HTML ai controlli dei moduli utilizzando `setHtmlAttribute(string $name, $value = true)`: +Attributi HTML +============== + +Per impostare attributi HTML arbitrari degli elementi del form, utilizziamo il metodo `setHtmlAttribute(string $name, $value = true)`: ```php -$form->addInteger('number', 'Number:') +$form->addInteger('number', 'Numero:') ->setHtmlAttribute('class', 'big-number'); $form->addSelect('rank', 'Ordina per:', ['prezzo', 'nome']) - ->setHtmlAttribute('onchange', 'submit()'); // richiama la funzione JS submit() al momento della modifica + ->setHtmlAttribute('onchange', 'submit()'); // invia alla modifica -// applicazione su <form> +// Per impostare gli attributi del <form> stesso $form->setHtmlAttribute('id', 'myForm'); ``` -Impostazione del tipo di input: +Specifica del tipo di elemento: ```php -$form->addText('tel', 'Your telephone:') +$form->addText('tel', 'Il tuo telefono:') ->setHtmlType('tel') - ->setHtmlAttribute('placeholder', 'Please, fill in your telephone'); + ->setHtmlAttribute('placeholder', 'scrivi il telefono'); ``` -È possibile impostare l'attributo HTML per le singole voci degli elenchi di radio o di caselle di controllo con valori diversi per ciascuna di esse. -Notate i due punti dopo `style:` per garantire che il valore sia selezionato per chiave: +.[warning] +L'impostazione del tipo e di altri attributi serve solo a scopi visivi. La verifica della correttezza degli input deve avvenire lato server, cosa che si garantisce scegliendo l'[elemento del form |controls] appropriato e indicando le [regole di validazione |validation]. + +Alle singole voci nelle liste radio o checkbox possiamo impostare un attributo HTML con valori diversi per ciascuna di esse. Notate i due punti dopo `style:`, che assicurano la scelta del valore in base alla chiave: ```php -$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue']; +$colors = ['r' => 'rosso', 'g' => 'verde', 'b' => 'blu']; $styles = ['r' => 'background:red', 'g' => 'background:green']; -$form->addCheckboxList('colors', 'Colors:', $colors) +$form->addCheckboxList('colors', 'Colori:', $colors) ->setHtmlAttribute('style:', $styles); ``` -Rende: +Stampa: ```latte -<label><input type="checkbox" name="colors[]" style="background:red" value="r">red</label> -<label><input type="checkbox" name="colors[]" style="background:green" value="g">green</label> -<label><input type="checkbox" name="colors[]" value="b">blue</label> +<label><input type="checkbox" name="colors[]" style="background:red" value="r">rosso</label> +<label><input type="checkbox" name="colors[]" style="background:green" value="g">verde</label> +<label><input type="checkbox" name="colors[]" value="b">blu</label> ``` -Per un attributo HTML logico (che non ha valore, come `readonly`), si può usare un punto interrogativo: +Per impostare attributi logici, come `readonly`, possiamo usare la scrittura con il punto interrogativo: ```php -$colors = ['r' => 'rosso', 'g' => 'verde', 'b' => 'blu']; -$form->addCheckboxList('colors', 'Colors:', $colors) - ->setHtmlAttribute('readonly?', 'r'); // utilizzare un array per più chiavi, ad esempio ['r', 'g']. +$form->addCheckboxList('colors', 'Colori:', $colors) + ->setHtmlAttribute('readonly?', 'r'); // per più chiavi usa un array, es. ['r', 'g'] ``` -Render: +Stampa: ```latte -<label><input type="checkbox" name="colors[]" readonly value="r">red</label> -<label><input type="checkbox" name="colors[]" value="g">green</label> -<label><input type="checkbox" name="colors[]" value="b">blue</label> +<label><input type="checkbox" name="colors[]" readonly value="r">rosso</label> +<label><input type="checkbox" name="colors[]" value="g">verde</label> +<label><input type="checkbox" name="colors[]" value="b">blu</label> ``` -Per le selectbox, il metodo `setHtmlAttribute()` imposta gli attributi dell'elemento `<select>` dell'elemento. Se si vogliono impostare gli attributi per ogni elemento -`<option>`utilizzeremo il metodo `setOptionAttribute()`. Inoltre, i due punti e il punto interrogativo usati sopra funzionano: +Nel caso dei selectbox, il metodo `setHtmlAttribute()` imposta gli attributi dell'elemento `<select>`. Se vogliamo impostare attributi ai singoli `<option>`, usiamo il metodo `setOptionAttribute()`. Funzionano anche le scritture con i due punti e il punto interrogativo indicate sopra: ```php -$form->addSelect('colors', 'Colors:', $colors) +$form->addSelect('colors', 'Colori:', $colors) ->setOptionAttribute('style:', $styles); ``` -Rendering: +Stampa: ```latte <select name="colors"> - <option value="r" style="background:red">red</option> - <option value="g" style="background:green">green</option> - <option value="b">blue</option> + <option value="r" style="background:red">rosso</option> + <option value="g" style="background:green">verde</option> + <option value="b">blu</option> </select> ``` -Prototipi .[#toc-prototypes] ----------------------------- +Prototipi +--------- -Un modo alternativo per impostare gli attributi HTML è modificare il modello da cui viene generato l'elemento HTML. Il modello è un oggetto `Html` e viene restituito dal metodo `getControlPrototype()`: +Un modo alternativo per impostare gli attributi HTML consiste nel modificare il prototipo da cui viene generato l'elemento HTML. Il prototipo è un oggetto `Html` e viene restituito dal metodo `getControlPrototype()`: ```php -$input = $form->addInteger('numero'); +$input = $form->addInteger('number', 'Numero:'); $html = $input->getControlPrototype(); // <input> -$html->class('big-number'); // <input class="big-number"> +$html->class('big-number'); // <input class="big-number"> ``` -Anche il modello di etichetta restituito da `getLabelPrototype()` può essere modificato in questo modo: +In questo modo è possibile modificare anche il prototipo dell'etichetta, restituito da `getLabelPrototype()`: ```php $html = $input->getLabelPrototype(); // <label> -$html->class('distinctive'); // <label class="distinctive"> +$html->class('distinctive'); // <label class="distinctive"> ``` -Per gli elementi Checkbox, CheckboxList e RadioList è possibile influenzare il modello di elemento che avvolge l'elemento. Viene restituito da `getContainerPrototype()`. Per impostazione predefinita è un elemento "vuoto", quindi non viene reso nulla, ma dandogli un nome verrà reso: +Per gli elementi Checkbox, CheckboxList e RadioList è possibile influenzare il prototipo dell'elemento che avvolge l'intero elemento. Viene restituito da `getContainerPrototype()`. Nello stato predefinito è un elemento "vuoto", quindi non viene renderizzato nulla, ma impostandogli un nome, verrà renderizzato: ```php $input = $form->addCheckbox('send'); -echo $input->getControl(); -// <label><input type="checkbox" name="send"></label> - $html = $input->getContainerPrototype(); $html->setName('div'); // <div> $html->class('check'); // <div class="check"> @@ -551,50 +541,49 @@ echo $input->getControl(); // <div class="check"><label><input type="checkbox" name="send"></label></div> ``` -Nel caso di CheckboxList e RadioList è anche possibile influenzare il modello di separatore di elementi restituito dal metodo `getSeparatorPrototype()`. Per impostazione predefinita, è un elemento `<br>`. Se lo si cambia in un elemento coppia, avvolgerà i singoli elementi invece di separarli. -È anche possibile influenzare il modello di elemento HTML delle etichette degli elementi, che restituisce `getItemLabelPrototype()`. +Nel caso di CheckboxList e RadioList è possibile influenzare anche il prototipo del separatore delle singole voci, restituito dal metodo `getSeparatorPrototype()`. Nello stato predefinito è l'elemento `<br>`. Se lo si modifica in un elemento di coppia, avvolgerà le singole voci invece di separarle. Inoltre, è possibile influenzare il prototipo dell'elemento HTML dell'etichetta delle singole voci, restituito da `getItemLabelPrototype()`. -Tradurre .[#toc-translating] -============================ +Traduzione +========== -Se state programmando un'applicazione multilingue, probabilmente avrete bisogno di rendere il modulo in diverse lingue. Il framework Nette definisce un'interfaccia di traduzione per questo scopo [api:Nette\Localization\Translator]. Non esiste un'implementazione predefinita in Nette, ma potete scegliere in base alle vostre esigenze tra diverse soluzioni già pronte che potete trovare su [Componette |https://componette.org/search/localization]. La loro documentazione spiega come configurare il traduttore. +Se programmi un'applicazione multilingue, probabilmente avrai bisogno di renderizzare il form in diverse versioni linguistiche. Nette Framework definisce a tale scopo un'interfaccia per la traduzione [api:Nette\Localization\Translator]. In Nette non c'è un'implementazione predefinita, puoi scegliere in base alle tue esigenze tra diverse soluzioni pronte, che trovi su [Componette |https://componette.org/search/localization]. Nella loro documentazione scoprirai come configurare il traduttore. -Il modulo supporta l'output di testo attraverso il traduttore. Lo passiamo utilizzando il metodo `setTranslator()`: +I form supportano la stampa di testi tramite il traduttore. Glielo passiamo tramite il metodo `setTranslator()`: ```php $form->setTranslator($translator); ``` -D'ora in poi, non solo tutte le etichette, ma anche tutti i messaggi di errore o le voci delle caselle di selezione saranno tradotti in un'altra lingua. +Da questo momento, non solo tutte le etichette, ma anche tutti i messaggi di errore o le voci dei select box verranno tradotti in un'altra lingua. -È possibile impostare un traduttore diverso per i singoli elementi del modulo o disabilitare completamente la traduzione con `null`: +Per i singoli elementi del form è possibile impostare un traduttore diverso o disattivare completamente la traduzione con il valore `null`: ```php -$form->addSelect('carModel', 'Model:', $cars) +$form->addSelect('carModel', 'Modello:', $cars) ->setTranslator(null); ``` -Per le [regole di convalida |validation], vengono passati al traduttore anche parametri specifici, ad esempio per la regola: +Alle [regole di validazione |validation] vengono passati al traduttore anche parametri specifici, ad esempio per la regola: ```php $form->addPassword('password', 'Password:') - ->addRule($form::MinLength, 'Password has to be at least %d characters long', 8) + ->addRule($form::MinLength, 'La password deve avere almeno %d caratteri', 8); ``` -il traduttore viene chiamato con i seguenti parametri: +viene chiamato il traduttore con questi parametri: ```php -$translator->translate('Password has to be at least %d characters long', 8); +$translator->translate('La password deve avere almeno %d caratteri', 8); ``` -e quindi può scegliere la forma plurale corretta per la parola `characters` dal conteggio. +e quindi può scegliere la forma plurale corretta della parola `caratteri` in base al numero. -Evento onRender .[#toc-event-onrender] -====================================== +Evento onRender +=============== -Poco prima che il form venga reso, possiamo invocare il nostro codice. Questo può, per esempio, aggiungere classi HTML agli elementi del modulo per una corretta visualizzazione. Aggiungiamo il codice all'array `onRender`: +Poco prima che il form venga renderizzato, possiamo far chiamare il nostro codice. Questo può, ad esempio, aggiungere classi HTML agli elementi del form per una corretta visualizzazione. Aggiungiamo il codice all'array `onRender`: ```php $form->onRender[] = function ($form) { diff --git a/forms/it/standalone.texy b/forms/it/standalone.texy index 20ac7858c9..945581ef15 100644 --- a/forms/it/standalone.texy +++ b/forms/it/standalone.texy @@ -1,76 +1,76 @@ -Moduli utilizzati autonomamente -******************************* +Form Usati Autonomamente +************************ .[perex] -I Nette Forms semplificano notevolmente la creazione e l'elaborazione dei moduli web. È possibile utilizzarli nelle proprie applicazioni completamente da soli, senza il resto del framework, come dimostreremo in questo capitolo. +Nette Forms semplifica enormemente la creazione e l'elaborazione dei form web. Potete usarli nelle vostre applicazioni in modo completamente autonomo, senza il resto del framework, come mostreremo in questo capitolo. -Tuttavia, se utilizzate Nette Application e i presenter, c'è una guida per voi: i [moduli nei presenter |in-presenter]. +Tuttavia, se utilizzate Nette Application e i presenter, la guida per l'[utilizzo nei presenter|in-presenter] è quella che fa per voi. -Primo modulo .[#toc-first-form] -=============================== +Primo Form +========== -Proviamo a scrivere un semplice modulo di registrazione. Il suo codice sarà simile a questo ("codice completo":https://gist.github.com/dg/370a7e3094d9ba9a9e913b8e2a2dc851): +Proviamo a scrivere un semplice form di registrazione. Il suo codice sarà il seguente ("codice completo":https://gist.github.com/dg/57878c1a413ae8ef0c1d83f02c43ef3f): ```php use Nette\Forms\Form; $form = new Form; -$form->addText('name', 'Name:'); +$form->addText('name', 'Nome:'); $form->addPassword('password', 'Password:'); -$form->addSubmit('send', 'Sign up'); +$form->addSubmit('send', 'Registrati'); ``` -E rendiamolo: +Possiamo renderizzarlo molto facilmente: ```php $form->render(); ``` -e il risultato dovrebbe apparire come questo: +e nel browser apparirà così: -[* form-en.webp *] +[* form-cs.webp *] -Il modulo è un oggetto della classe `Nette\Forms\Form` (la classe `Nette\Application\UI\Form` è utilizzata nei presentatori). Abbiamo aggiunto i controlli nome, password e pulsante di invio. +Il form è un oggetto della classe `Nette\Forms\Form` (la classe `Nette\Application\UI\Form` viene utilizzata nei presenter). Abbiamo aggiunto ad esso gli elementi chiamati nome, password e il pulsante di invio. -Ora rilanceremo il modulo. Chiedendo a `$form->isSuccess()`, scopriremo se il modulo è stato inviato e se è stato compilato in modo valido. In caso affermativo, si effettuerà il dump dei dati. Dopo la definizione del modulo, aggiungeremo: +Ora diamo vita al form. Interrogando `$form->isSuccess()` scopriremo se il form è stato inviato e se è stato compilato validamente. Se sì, stamperemo i dati. Quindi, dopo la definizione del form, aggiungiamo: ```php if ($form->isSuccess()) { - echo 'Il modulo è stato compilato e inviato correttamente'; + echo 'Il form è stato compilato correttamente e inviato'; $data = $form->getValues(); - // $data->name contiene nome + // $data->name contiene il nome // $data->password contiene la password var_dump($data); } ``` -Il metodo `getValues()` restituisce i dati inviati sotto forma di oggetto [ArrayHash |utils:arrays#ArrayHash]. Mostreremo [in seguito |#Mapping to Classes] come modificarlo. La variabile `$data` contiene le chiavi `name` e `password` con i dati inseriti dall'utente. +Il metodo `getValues()` restituisce i dati inviati sotto forma di oggetto [ArrayHash |utils:arrays#ArrayHash]. Vedremo [più tardi |#Mapping su Classi] come cambiare questo comportamento. L'oggetto `$data` contiene le chiavi `name` e `password` con i dati inseriti dall'utente. -Di solito i dati vengono inviati direttamente per un'ulteriore elaborazione, che può essere, ad esempio, l'inserimento nel database. Tuttavia, durante l'elaborazione può verificarsi un errore, ad esempio il nome utente è già stato preso. In questo caso, si passa l'errore al modulo utilizzando `addError()` e lo si fa ridisegnare, con un messaggio di errore: +Di solito, inviamo i dati direttamente per un'ulteriore elaborazione, che potrebbe essere, ad esempio, l'inserimento nel database. Tuttavia, durante l'elaborazione può verificarsi un errore, ad esempio il nome utente è già occupato. In tal caso, restituiamo l'errore al form utilizzando `addError()` e lo facciamo renderizzare di nuovo, insieme al messaggio di errore. ```php -$form->addError('Sorry, username is already in use.'); +$form->addError('Siamo spiacenti, questo nome utente è già in uso.'); ``` -Dopo aver elaborato il modulo, si passa alla pagina successiva. In questo modo si evita che il modulo venga involontariamente ripresentato facendo clic sul pulsante *refresh*, *back* o spostando la cronologia del browser. +Dopo aver elaborato il form, reindirizziamo a un'altra pagina. Ciò impedisce l'invio involontario ripetuto del form tramite il pulsante *aggiorna*, *indietro* o spostandosi nella cronologia del browser. -Per impostazione predefinita, il modulo viene inviato con il metodo POST alla stessa pagina. Entrambi possono essere modificati: +Il form viene inviato per default con il metodo POST alla stessa pagina. Entrambi possono essere modificati: ```php $form->setAction('/submit.php'); $form->setMethod('GET'); ``` -E questo è tutto :-) Abbiamo un modulo funzionale e perfettamente [protetto |#Vulnerability Protection]. +E questo è tutto :-) Abbiamo un form funzionante e perfettamente [sicuro |#Protezione dalle Vulnerabilità]. -Provate ad aggiungere altri [controlli al modulo |controls]. +Provate ad aggiungere anche altri [elementi del form|controls]. -Accesso ai controlli .[#toc-access-to-controls] -=============================================== +Accesso agli Elementi +===================== -Il modulo e i suoi controlli individuali sono chiamati componenti. Essi creano un albero di componenti, la cui radice è il modulo. È possibile accedere ai singoli controlli nel modo seguente: +Chiamiamo componenti sia il form che i suoi singoli elementi. Formano un albero di componenti, dove la radice è proprio il form. Possiamo accedere ai singoli elementi del form in questo modo: ```php $input = $form->getComponent('name'); @@ -80,37 +80,36 @@ $button = $form->getComponent('send'); // sintassi alternativa: $button = $form['send']; ``` -I controlli vengono rimossi utilizzando unset: +Gli elementi vengono rimossi usando unset: ```php unset($form['name']); ``` -Regole di validazione .[#toc-validation-rules] -============================================== +Regole di Validazione +===================== -Qui è stata usata la parola *valido*, ma il modulo non ha ancora regole di convalida. Risolviamo il problema. +Abbiamo menzionato la parola *valido,* ma il form non ha ancora regole di validazione. Rimediamo. -Il nome sarà obbligatorio, quindi lo contrassegneremo con il metodo `setRequired()`, il cui argomento è il testo del messaggio di errore che verrà visualizzato se l'utente non lo compila. Se non viene fornito alcun argomento, viene utilizzato il messaggio di errore predefinito. +Il nome sarà obbligatorio, quindi lo marchiamo con il metodo `setRequired()`, il cui argomento è il testo del messaggio di errore che verrà visualizzato se l'utente non compila il nome. Se non specifichiamo l'argomento, verrà utilizzato il messaggio di errore predefinito. ```php -$form->addText('name', 'Name:') - ->setRequired('Please enter a name.'); +$form->addText('name', 'Nome:') + ->setRequired('Inserisci un nome'); ``` -Se si prova a inviare il modulo senza il nome compilato, si vedrà che viene visualizzato un messaggio di errore e il browser o il server lo rifiuteranno finché non verrà compilato. +Provate a inviare il form senza compilare il nome e vedrete che verrà visualizzato un messaggio di errore e il browser o il server lo rifiuteranno finché non compilerete il campo. -Allo stesso tempo, non sarà possibile ingannare il sistema digitando solo spazi nell'input, ad esempio. Non c'è modo. Nette taglia automaticamente gli spazi bianchi a destra e a sinistra. Provate. È un'operazione che si dovrebbe sempre fare per ogni inserimento di una singola riga, ma che spesso viene dimenticata. Nette lo fa automaticamente. (Si può provare a ingannare i moduli e inviare una stringa multilinea come nome. Anche in questo caso, Nette non si lascia ingannare e le interruzioni di riga si trasformano in spazi). +Allo stesso tempo, non ingannerete il sistema scrivendo, ad esempio, solo spazi nel campo. Niente affatto. Nette rimuove automaticamente gli spazi iniziali e finali. Provate. È una cosa che dovreste sempre fare con ogni input a riga singola, ma spesso viene dimenticata. Nette lo fa automaticamente. (Potete provare a ingannare il form e inviare una stringa multilinea come nome. Anche qui, Nette non si lascerà ingannare e convertirà gli a capo in spazi.) -Il modulo viene sempre convalidato sul lato server, ma viene generata anche una convalida JavaScript, che è rapida e l'utente viene a conoscenza dell'errore immediatamente, senza dover inviare il modulo al server. Questo è gestito dallo script `netteForms.js`. -Aggiungerlo alla pagina: +Il form viene sempre validato lato server, ma viene generata anche una validazione JavaScript, che avviene istantaneamente e l'utente viene informato dell'errore immediatamente, senza dover inviare il form al server. Questo è gestito dallo script `netteForms.js`. Inseritelo nella pagina: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Se si guarda nel codice sorgente della pagina con il modulo, si può notare che Nette inserisce i campi obbligatori in elementi con una classe CSS `required`. Provate ad aggiungere il seguente stile al template e l'etichetta "Nome" diventerà rossa. In modo elegante, contrassegniamo i campi obbligatori per gli utenti: +Se guardate il codice sorgente della pagina con il form, potete notare che Nette inserisce gli elementi obbligatori in elementi con la classe CSS `required`. Provate ad aggiungere il seguente foglio di stile al template e l'etichetta "Nome" diventerà rossa. In questo modo, indichiamo elegantemente agli utenti gli elementi obbligatori: ```latte <style> @@ -118,119 +117,121 @@ Se si guarda nel codice sorgente della pagina con il modulo, si può notare che </style> ``` -Ulteriori regole di validazione saranno aggiunte con il metodo `addRule()`. Il primo parametro è la regola, il secondo è di nuovo il testo del messaggio di errore e l'argomento opzionale regola di validazione può seguire. Che cosa significa? +Aggiungiamo ulteriori regole di validazione con il metodo `addRule()`. Il primo parametro è la regola, il secondo è di nuovo il testo del messaggio di errore e può seguire un argomento della regola di validazione. Cosa significa? -Il modulo riceverà un altro input opzionale *età* con la condizione che deve essere un numero (`addInteger()`) e in determinati limiti (`$form::Range`). E qui useremo il terzo argomento di `addRule()`, l'intervallo stesso: +Estendiamo il form con un nuovo campo opzionale "età", che deve essere un numero intero (`addInteger()`) e inoltre in un intervallo consentito (`$form::Range`). E qui utilizzeremo proprio il terzo parametro del metodo `addRule()`, con cui passiamo al validatore l'intervallo richiesto come coppia `[da, a]`: ```php -$form->addInteger('age', 'Age:') - ->addRule($form::Range, 'You must be older 18 years and be under 120.', [18, 120]); +$form->addInteger('age', 'Età:') + ->addRule($form::Range, 'L\'età deve essere compresa tra 18 e 120', [18, 120]); ``` .[tip] -Se l'utente non compila il campo, le regole di validazione non saranno verificate, perché il campo è opzionale. +Se l'utente non compila il campo, le regole di validazione non verranno verificate, poiché l'elemento è opzionale. -Ovviamente c'è spazio per un piccolo refactoring. Nel messaggio di errore e nel terzo parametro, i numeri sono elencati in duplice copia, il che non è ideale. Se stessimo creando un [modulo multilingue |rendering#translating] e il messaggio contenente i numeri dovesse essere tradotto in più lingue, sarebbe più difficile modificare i valori. Per questo motivo, è possibile utilizzare i caratteri sostitutivi `%d`: +Qui c'è spazio per un piccolo refactoring. Nel messaggio di errore e nel terzo parametro, i numeri sono indicati in modo duplicato, il che non è ideale. Se stessimo creando [form multilingue |rendering#Traduzione] e il messaggio contenente numeri fosse tradotto in più lingue, un'eventuale modifica dei valori sarebbe più difficile. Per questo motivo, è possibile utilizzare i segnaposto `%d` e Nette completerà i valori: ```php - ->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]); + ->addRule($form::Range, 'L\'età deve essere compresa tra %d e %d anni', [18, 120]); ``` -Torniamo al campo *password*, rendiamolo *richiesto* e verifichiamo la lunghezza minima della password (`$form::MinLength`), sempre utilizzando i caratteri sostitutivi del messaggio: +Torniamo all'elemento `password`, che renderemo anch'esso obbligatorio e verificheremo anche la lunghezza minima della password (`$form::MinLength`), sempre utilizzando il segnaposto: ```php $form->addPassword('password', 'Password:') - ->setRequired('Pick a password') - ->addRule($form::MinLength, 'Your password has to be at least %d long', 8); + ->setRequired('Scegli una password') + ->addRule($form::MinLength, 'La password deve contenere almeno %d caratteri', 8); ``` -Aggiungeremo un campo `passwordVerify` al modulo, dove l'utente inserisce nuovamente la password, per il controllo. Utilizzando le regole di validazione, verifichiamo se le due password sono uguali (`$form::Equal`). E come argomento diamo un riferimento alla prima password usando le [parentesi quadre |#Access to Controls]: +Aggiungiamo al form anche il campo `passwordVerify`, dove l'utente inserirà nuovamente la password, per verifica. Utilizzando le regole di validazione, verificheremo se entrambe le password sono uguali (`$form::Equal`). E come parametro, daremo un riferimento alla prima password usando le [parentesi quadre |#Accesso agli Elementi]: ```php -$form->addPassword('passwordVerify', 'Password again:') - ->setRequired('Fill your password again to check for typo') - ->addRule($form::Equal, 'Password mismatch', $form['password']) +$form->addPassword('passwordVerify', 'Password per verifica:') + ->setRequired('Inserisci nuovamente la password per verifica') + ->addRule($form::Equal, 'Le password non corrispondono', $form['password']) ->setOmitted(); ``` -Con `setOmitted()`, contrassegniamo un elemento il cui valore non ci interessa e che esiste solo per la validazione. Il suo valore non viene passato a `$data`. +Con `setOmitted()` abbiamo contrassegnato l'elemento il cui valore in realtà non ci interessa e che esiste solo ai fini della validazione. Il valore non verrà passato a `$data`. -Abbiamo un modulo completamente funzionale con validazione in PHP e JavaScript. Le capacità di validazione di Nette sono molto più ampie: si possono creare condizioni, visualizzare e nascondere parti di una pagina in base a esse, ecc. Potete trovare tutte le informazioni nel capitolo sulla [convalida dei moduli |validation]. +Con questo, abbiamo un form completamente funzionante con validazione in PHP e JavaScript. Le capacità di validazione di Nette sono molto più ampie, è possibile creare condizioni, far visualizzare e nascondere parti della pagina in base ad esse, ecc. Imparerete tutto nel capitolo sulla [validazione dei form|validation]. -Valori predefiniti .[#toc-default-values] -========================================= +Valori Predefiniti +================== -Spesso si impostano valori predefiniti per i controlli dei moduli: +Di solito impostiamo valori predefiniti per gli elementi del form: ```php -$form->addEmail('email', 'Email') +$form->addEmail('email', 'E-mail') ->setDefaultValue($lastUsedEmail); ``` -Spesso è utile impostare i valori predefiniti per tutti i controlli contemporaneamente. Ad esempio, quando il modulo viene utilizzato per modificare i record. Leggiamo il record dal database e lo impostiamo come valore predefinito: +Spesso è utile impostare valori predefiniti per tutti gli elementi contemporaneamente. Ad esempio, quando il form serve per modificare record. Leggiamo il record dal database e impostiamo i valori predefiniti: ```php //$row = ['name' => 'John', 'age' => '33', /* ... */]; $form->setDefaults($row); ``` -Chiamare `setDefaults()` dopo aver definito i controlli. +Chiamate `setDefaults()` dopo aver definito gli elementi. -Rendering del modulo .[#toc-rendering-the-form] -=============================================== +Rendering del Form +================== -Per impostazione predefinita, il modulo viene reso come una tabella. I singoli controlli seguono le linee guida di base per l'accessibilità del Web. Tutte le etichette sono generate come elementi `<label>` e sono associate ai rispettivi input; facendo clic sull'etichetta si sposta il cursore sull'input. +Per default, il form viene renderizzato come una tabella. I singoli elementi soddisfano la regola di base dell'accessibilità: tutte le etichette sono scritte come `<label>` e collegate all'elemento del form corrispondente. Facendo clic sull'etichetta, il cursore appare automaticamente nel campo del form. -Possiamo impostare qualsiasi attributo HTML per ogni elemento. Ad esempio, aggiungere un segnaposto: +Possiamo impostare attributi HTML arbitrari per ogni elemento. Ad esempio, aggiungere un placeholder: ```php -$form->addInteger('age', 'Age:') - ->setHtmlAttribute('placeholder', 'Please fill in the age'); +$form->addInteger('age', 'Età:') + ->setHtmlAttribute('placeholder', 'Inserisci l\'età'); ``` -Ci sono davvero molti modi per rendere un modulo, quindi è un [capitolo |rendering] dedicato [alla resa |rendering]. +Ci sono davvero molti modi per renderizzare un form, quindi c'è un [capitolo separato sul rendering|rendering] dedicato a questo. -Mappatura delle classi .[#toc-mapping-to-classes] -================================================= +Mapping su Classi +================= -Torniamo all'elaborazione dei dati del modulo. Il metodo `getValues()` restituisce i dati inviati come oggetto `ArrayHash`. Poiché si tratta di una classe generica, come `stdClass`, ci mancheranno alcune comodità per lavorare con essa, come il completamento del codice per le proprietà negli editor o l'analisi statica del codice. Questo potrebbe essere risolto con una classe specifica per ogni modulo, le cui proprietà rappresentano i singoli controlli. Ad esempio: +Torniamo all'elaborazione dei dati del form. Il metodo `getValues()` ci restituiva i dati inviati come oggetto `ArrayHash`. Poiché si tratta di una classe generica, qualcosa come `stdClass`, ci mancherà una certa comodità nel lavorarci, come il suggerimento delle proprietà negli editor o l'analisi statica del codice. Questo potrebbe essere risolto avendo una classe specifica per ogni form, le cui proprietà rappresentano i singoli elementi. Ad esempio: ```php class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -A partire da PHP 8.0, è possibile utilizzare questa elegante notazione che utilizza un costruttore: +In alternativa, potete usare il costruttore: ```php class RegistrationFormData { public function __construct( public string $name, - public int $age, + public ?int $age, public string $password, ) { } } ``` -Come dire a Nette di restituirci i dati come oggetti di questa classe? È più facile di quanto si pensi. Basta specificare come parametro il nome della classe o dell'oggetto da idratare: +Le proprietà della classe dati possono anche essere enum e verranno mappate automaticamente. .{data-version:3.2.4} + +Come dire a Nette di restituirci i dati come oggetti di questa classe? Più facile di quanto pensiate. Basta specificare il nome della classe o l'oggetto da idratare come parametro: ```php $data = $form->getValues(RegistrationFormData::class); $name = $data->name; ``` -Anche `'array'` può essere specificato come parametro e i dati vengono restituiti come array. +Come parametro si può specificare anche `'array'` e allora i dati verranno restituiti come array. -Se i moduli consistono in una struttura a più livelli composta da contenitori, creare una classe separata per ciascuno di essi: +Se i form formano una struttura multilivello composta da container, create una classe separata per ognuno: ```php $form = new Form; @@ -247,26 +248,28 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` -La mappatura sa quindi dal tipo di proprietà `$person` che deve mappare il contenitore alla classe `PersonFormData`. Se la proprietà contiene un array di contenitori, fornire il tipo `array` e passare la classe da mappare direttamente al contenitore: +Il mapping riconoscerà quindi dal tipo della proprietà `$person` che deve mappare il container sulla classe `PersonFormData`. Se la proprietà contenesse un array di container, specificate il tipo `array` e passate la classe per il mapping direttamente al container: ```php $person->setMappedType(PersonFormData::class); ``` +Potete far generare il design della classe dati del form usando il metodo `Nette\Forms\Blueprint::dataClass($form)`, che lo stamperà sulla pagina del browser. Quindi basta selezionare il codice con un clic e copiarlo nel progetto. .{data-version:3.1.15} + -Pulsanti di invio multipli .[#toc-multiple-submit-buttons] -========================================================== +Più Pulsanti +============ -Se il modulo ha più di un pulsante, di solito dobbiamo distinguere quale è stato premuto. Il metodo `isSubmittedBy()` del pulsante ci restituisce questa informazione: +Se il form ha più di un pulsante, di solito dobbiamo distinguere quale è stato premuto. Questa informazione ci viene restituita dal metodo `isSubmittedBy()` del pulsante: ```php -$form->addSubmit('save', 'Save'); -$form->addSubmit('delete', 'Delete'); +$form->addSubmit('save', 'Salva'); +$form->addSubmit('delete', 'Elimina'); if ($form->isSuccess()) { if ($form['save']->isSubmittedBy()) { @@ -279,37 +282,36 @@ if ($form->isSuccess()) { } ``` -Non omettere `$form->isSuccess()` per verificare la validità dei dati. +Non omettete la domanda `$form->isSuccess()`, verificherete così la validità dei dati. -Quando un modulo viene inviato con il tasto <kbd>Invio</kbd>, viene trattato come se fosse stato inviato con il primo pulsante. +Quando il form viene inviato premendo il tasto <kbd>Invio</kbd>, viene considerato come se fosse stato inviato dal primo pulsante. -Protezione dalle vulnerabilità .[#toc-vulnerability-protection] -=============================================================== +Protezione dalle Vulnerabilità +============================== -Nette Framework si impegna a fondo per essere sicuro e poiché i moduli sono l'input più comune dell'utente, i moduli di Nette sono praticamente impenetrabili. +Nette Framework pone grande enfasi sulla sicurezza e quindi si preoccupa meticolosamente della buona protezione dei form. -Oltre a proteggere i moduli da vulnerabilità ben note come [Cross-Site Scripting (XSS) |nette:glossary#cross-site-scripting-xss] e [Cross-Site Request Forgery (CSRF) |nette:glossary#cross-site-request-forgery-csrf], Nette esegue molte piccole operazioni di sicurezza a cui non è più necessario pensare. +Oltre a proteggere i form dagli attacchi [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] e [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF], implementa molte piccole misure di sicurezza a cui non dovete più pensare. -Ad esempio, filtra tutti i caratteri di controllo dagli input e controlla la validità della codifica UTF-8, in modo che i dati del modulo siano sempre puliti. Per le caselle di selezione e gli elenchi di scelta, verifica che le voci selezionate siano effettivamente quelle proposte e che non ci siano state contraffazioni. Abbiamo già detto che per gli input di testo a riga singola, rimuove i caratteri di fine riga che un utente malintenzionato potrebbe inviare. Per gli input multilinea, normalizza i caratteri di fine riga. E così via. +Ad esempio, filtra tutti i caratteri di controllo dagli input e verifica la validità della codifica UTF-8, quindi i dati del form saranno sempre puliti. Per le select box e le radio list, verifica che gli elementi selezionati fossero effettivamente tra quelli offerti e che non ci sia stata alcuna falsificazione. Abbiamo già menzionato che per gli input di testo a riga singola, rimuove i caratteri di fine riga che un attaccante potrebbe aver inviato. Per gli input multilinea, invece, normalizza i caratteri di fine riga. E così via. -Nette risolve per voi vulnerabilità di sicurezza che la maggior parte dei programmatori non ha idea che esistano. +Nette risolve per voi i rischi di sicurezza di cui molti programmatori non sospettano nemmeno l'esistenza. -L'attacco CSRF menzionato consiste nel fatto che un aggressore attira la vittima a visitare una pagina che esegue silenziosamente una richiesta nel browser della vittima al server in cui la vittima è attualmente loggata, e il server crede che la richiesta sia stata fatta dalla vittima a suo piacimento. Pertanto, Nette impedisce l'invio del modulo tramite POST da un altro dominio. Se per qualche motivo si desidera disattivare la protezione e consentire l'invio del modulo da un altro dominio, utilizzare: +L'attacco CSRF menzionato consiste nel fatto che un attaccante attira la vittima su una pagina che esegue discretamente una richiesta nel browser della vittima al server su cui la vittima è loggata, e il server crede che la richiesta sia stata eseguita dalla vittima di sua spontanea volontà. Pertanto, Nette impedisce l'invio di form POST da un altro dominio. Se per qualche motivo volete disabilitare la protezione e consentire l'invio del form da un altro dominio, usate: ```php -$form->allowCrossOrigin(); // ATTENZIONE! Disattiva la protezione! +$form->allowCrossOrigin(); // ATTENZIONE! Disabilita la protezione! ``` -Questa protezione utilizza un cookie SameSite chiamato `_nss`. Pertanto, si deve creare un modulo prima di inviare il primo output, in modo che il cookie possa essere inviato. +Questa protezione utilizza un cookie SameSite chiamato `_nss`. Pertanto, create l'oggetto form prima di inviare il primo output, in modo che il cookie possa essere inviato. -La protezione dei cookie di SameSite potrebbe non essere affidabile al 100%, quindi è una buona idea attivare la protezione dei token: +La protezione tramite cookie SameSite potrebbe non essere affidabile al 100%, quindi è consigliabile abilitare anche la protezione tramite token: ```php $form->addProtection(); ``` -Si consiglia vivamente di applicare questa protezione ai moduli di una parte amministrativa dell'applicazione che modificano dati sensibili. Il framework protegge da un attacco CSRF generando e validando il token di autenticazione che viene memorizzato in una sessione (l'argomento è il messaggio di errore mostrato se il token è scaduto). Per questo motivo è necessario che sia avviata una sessione prima di visualizzare il modulo. Nella parte amministrativa del sito web, di solito la sessione è già avviata, grazie al login dell'utente. -Altrimenti, avviare la sessione con il metodo `Nette\Http\Session::start()`. +Si consiglia di proteggere in questo modo i form nella parte amministrativa del sito che modificano dati sensibili nell'applicazione. Il framework si difende dall'attacco CSRF generando e verificando un token di autorizzazione, che viene memorizzato nella sessione. Pertanto, è necessario avere una sessione aperta prima di visualizzare il form. Nella parte amministrativa del sito, di solito la sessione è già avviata a causa del login dell'utente. Altrimenti, avviate la sessione con il metodo `Nette\Http\Session::start()`. -Ecco una rapida introduzione ai moduli in Nette. Per ulteriori spunti, si consiglia di consultare la directory degli [esempi |https://github.com/nette/forms/tree/master/examples] nella distribuzione. +Bene, abbiamo completato una rapida introduzione ai form in Nette. Provate a dare un'occhiata alla directory [examples|https://github.com/nette/forms/tree/master/examples] nella distribuzione, dove troverete ulteriore ispirazione. diff --git a/forms/it/validation.texy b/forms/it/validation.texy index 1602645bce..8de77d926d 100644 --- a/forms/it/validation.texy +++ b/forms/it/validation.texy @@ -1,168 +1,179 @@ -Convalida dei moduli +Validazione dei Form ******************** -Controlli richiesti .[#toc-required-controls] -============================================= +Elementi Obbligatori +==================== -I controlli vengono contrassegnati come obbligatori con il metodo `setRequired()`, il cui argomento è il testo del [messaggio di errore |#Error Messages] che verrà visualizzato se l'utente non lo compila. Se non viene fornito alcun argomento, viene utilizzato il messaggio di errore predefinito. +Gli elementi obbligatori vengono contrassegnati con il metodo `setRequired()`, il cui argomento è il testo del [#Messaggi di errore] che verrà visualizzato se l'utente non compila l'elemento. Se l'argomento non viene fornito, verrà utilizzato il messaggio di errore predefinito. ```php -$form->addText('name', 'Name:') - ->setRequired('Please fill your name.'); +$form->addText('name', 'Nome:') + ->setRequired('Inserisci un nome'); ``` -Regole .[#toc-rules] -==================== +Regole +====== -Con il metodo `addRule()` si aggiungono regole di convalida ai controlli. Il primo parametro è la regola, il secondo è il [messaggio di errore |#Error Messages] e il terzo è l'argomento della regola di validazione. +Aggiungiamo regole di validazione agli elementi usando il metodo `addRule()`. Il primo parametro è la regola, il secondo è il testo del [#Messaggi di errore] e il terzo è l'argomento della regola di validazione. ```php $form->addPassword('password', 'Password:') - ->addRule($form::MinLength, 'Password must be at least %d characters', 8); + ->addRule($form::MinLength, 'La password deve contenere almeno %d caratteri', 8); ``` -Nette dispone di una serie di regole incorporate, i cui nomi sono costanti della classe `Nette\Forms\Form`: +**Le regole di validazione vengono verificate solo se l'utente ha compilato l'elemento.** -Possiamo utilizzare le seguenti regole per tutti i controlli: +Nette include una serie di regole predefinite, i cui nomi sono costanti della classe `Nette\Forms\Form`. Possiamo usare queste regole per tutti gli elementi: -| costante | descrizione | argomenti +| costante | descrizione | tipo argomento |------- -| `Required` | alias di `setRequired()` | - -| `Filled` | alias di `setRequired()` | - -| `Blank` | non deve essere riempito | - +| `Required` | elemento obbligatorio, alias per `setRequired()` | - +| `Filled` | elemento obbligatorio, alias per `setRequired()` | - +| `Blank` | l'elemento non deve essere compilato | - | `Equal` | il valore è uguale al parametro | `mixed` | `NotEqual` | il valore non è uguale al parametro | `mixed` -| `IsIn` | il valore è uguale a qualche elemento dell'array | `array` -| `IsNotIn` | il valore non è uguale a nessun elemento dell'array | `array` -| `Valid` | l'input supera la convalida (per le [condizioni |#conditions]) | - +| `IsIn` | il valore è uguale a uno degli elementi nell'array | `array` +| `IsNotIn` | il valore non è uguale a nessuno degli elementi nell'array | `array` +| `Valid` | l'elemento è compilato correttamente? (per [#Condizioni]) | - + -Per i controlli `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` possono essere utilizzate anche le seguenti regole: +Input di Testo +-------------- -| `MinLength` | lunghezza minima della stringa | `int` -| `MaxLength` | lunghezza massima della stringa | `int` +Per gli elementi `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`, `addFloat()` è possibile utilizzare anche alcune delle seguenti regole: + +| `MinLength` | lunghezza minima del testo | `int` +| `MaxLength` | lunghezza massima del testo | `int` | `Length` | lunghezza nell'intervallo o lunghezza esatta | coppia `[int, int]` o `int` | `Email` | indirizzo e-mail valido | - -| `URL` | URL valido | - -| `Pattern` | corrisponde al modello regolare | `string` -| `PatternInsensitive` | come `Pattern`, ma senza distinzione tra maiuscole e minuscole | `string` -| `Integer` | numero intero | - -| `Numeric` | pseudonimo di `Integer` | - - -| `Float` | numero intero o in virgola mobile | - -| `Min` | minimo del valore intero | `int\|float` -| `Max` | massimo del valore intero | `int\|float` +| `URL` | URL assoluto | - +| `Pattern` | corrisponde all'espressione regolare | `string` +| `PatternInsensitive` | come `Pattern`, ma case-insensitive | `string` +| `Integer` | valore intero | - +| `Numeric` | alias per `Integer` | - +| `Float` | numero | - +| `Min` | valore minimo dell'elemento numerico | `int\|float` +| `Max` | valore massimo dell'elemento numerico | `int\|float` | `Range` | valore nell'intervallo | coppia `[int\|float, int\|float]` -Le regole `Integer`, `Numeric` e `Float` convertono automaticamente il valore in numero intero (o in virgola mobile, rispettivamente). Inoltre, la regola `URL` accetta anche un indirizzo senza schema (ad esempio `nette.org`) e completa lo schema (`https://nette.org`). -Le espressioni in `Pattern` e `PatternInsensitive` devono essere valide per l'intero valore, cioè come se fosse avvolto nei caratteri `^` and `$`. +Le regole di validazione `Integer`, `Numeric` e `Float` convertono direttamente il valore in integer o float rispettivamente. Inoltre, la regola `URL` accetta anche un indirizzo senza schema (es. `nette.org`) e aggiunge lo schema (`https://nette.org`). L'espressione in `Pattern` e `PatternIcase` deve corrispondere all'intero valore, cioè come se fosse racchiusa tra i caratteri `^` e `$`. + -Per i controlli `addUpload()`, `addMultiUpload()` si possono utilizzare anche le seguenti regole: +Numero di Elementi +------------------ -| `MaxFileSize` | dimensione massima del file | `int` -| `MimeType` | Tipo MIME, accetta caratteri jolly (`'video/*'`) | `string\|string[]` -| `Image` | il file caricato è JPEG, PNG, GIF, WebP | - - | il nome del file corrisponde a una regola regolare. -| `Pattern` | il nome del file corrisponde a un'espressione regolare | `string` -| `PatternInsensitive` | come `Pattern`, ma senza distinzione tra maiuscole e minuscole | `string` +Per gli elementi `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()` è possibile utilizzare anche le seguenti regole per limitare il numero di elementi selezionati o file caricati: -`MimeType` e `Image` richiedono l'estensione PHP `fileinfo`. Il fatto che un file o un'immagine sia del tipo richiesto viene rilevato dalla sua firma. L'integrità dell'intero file non viene controllata. È possibile scoprire se un'immagine non è danneggiata, ad esempio provando a [caricarla |http:request#toImage]. +| `MinLength` | numero minimo | `int` +| `MaxLength` | numero massimo | `int` +| `Length` | numero nell'intervallo o numero esatto | coppia `[int, int]` o `int` -Per i controlli `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()` è possibile utilizzare le seguenti regole per limitare il numero di elementi selezionati, rispettivamente di file caricati: -| `MinLength` | conteggio minimo | `int` -| `MaxLength` | conteggio massimo | `int` -| `Length` | conteggio nell'intervallo o conteggio esatto | coppia `[int, int]` o `int` +Upload di File +-------------- +Per gli elementi `addUpload()`, `addMultiUpload()` è possibile utilizzare anche le seguenti regole: -Messaggi di errore .[#toc-error-messages] ------------------------------------------ +| `MaxFileSize` | dimensione massima del file in byte | `int` +| `MimeType` | tipo MIME, consentiti caratteri jolly (`'video/*'`) | `string\|string[]` +| `Image` | immagine JPEG, PNG, GIF, WebP, AVIF | - +| `Pattern` | il nome del file corrisponde all'espressione regolare | `string` +| `PatternInsensitive` | come `Pattern`, ma case-insensitive | `string` -Tutte le regole predefinite, tranne `Pattern` e `PatternInsensitive`, hanno un messaggio di errore predefinito, quindi possono essere omesse. Tuttavia, passando e formulando tutti i messaggi personalizzati, si renderà il modulo più facile da usare. +`MimeType` e `Image` richiedono l'estensione PHP `fileinfo`. Rilevano se un file o un'immagine è del tipo richiesto in base alla sua firma e **non verificano l'integrità dell'intero file.** È possibile verificare se un'immagine è danneggiata, ad esempio, provando a [caricarla |http:request#toImage]. -È possibile modificare i messaggi predefiniti in [configuration |forms:configuration], modificando i testi dell'array `Nette\Forms\Validator::$messages` o utilizzando [il traduttore |rendering#translating]. -I seguenti caratteri jolly possono essere usati nel testo dei messaggi di errore: +Messaggi di Errore +================== -| `%d` | sostituisce gradualmente le regole dopo gli argomenti -| `%n$d` | sostituisce con l'argomento dell'ennesima regola -| `%label` | sostituisce l'etichetta del campo (senza i due punti) -| `%name` | sostituisce con il nome del campo (es. `name`) +Tutte le regole predefinite, ad eccezione di `Pattern` e `PatternInsensitive`, hanno un messaggio di errore predefinito, quindi può essere omesso. Tuttavia, specificando e formulando tutti i messaggi su misura, renderete il form più user-friendly. + +Potete modificare i messaggi predefiniti nella [configurazione|forms:configuration], modificando i testi nell'array `Nette\Forms\Validator::$messages` o utilizzando un [traduttore |rendering#Traduzione]. + +Nel testo dei messaggi di errore è possibile utilizzare le seguenti stringhe segnaposto: + +| `%d` | sostituisce progressivamente con gli argomenti della regola +| `%n$d` | sostituisce con l'n-esimo argomento della regola +| `%label` | sostituisce con l'etichetta dell'elemento (senza i due punti) +| `%name` | sostituisce con il nome dell'elemento (es. `name`) | `%value` | sostituisce con il valore inserito dall'utente ```php -$form->addText('name', 'Name:') - ->setRequired('Please fill in %label'); +$form->addText('name', 'Nome:') + ->setRequired('Compila %label'); $form->addInteger('id', 'ID:') - ->addRule($form::Range, 'at least %d and no more than %d', [5, 10]); + ->addRule($form::Range, 'almeno %d e al massimo %d', [5, 10]); $form->addInteger('id', 'ID:') - ->addRule($form::Range, 'no more than %2$d and at least %1$d', [5, 10]); + ->addRule($form::Range, 'al massimo %2$d e almeno %1$d', [5, 10]); ``` -Condizioni .[#toc-conditions] -============================= +Condizioni +========== -Oltre alle regole di validazione, è possibile impostare delle condizioni. Si impostano come le regole, ma si usa `addRule()` invece di `addCondition()` e, naturalmente, si lascia senza messaggio di errore (la condizione chiede solo): +Oltre alle regole, è possibile aggiungere anche condizioni. Queste si scrivono in modo simile alle regole, ma invece di `addRule()` usiamo il metodo `addCondition()` e ovviamente non specifichiamo alcun messaggio di errore (la condizione si limita a chiedere): ```php $form->addPassword('password', 'Password:') - // se la password non è più lunga di 8 caratteri ... + // se la password non è più lunga di 8 caratteri ->addCondition($form::MaxLength, 8) - // ... allora deve contenere un numero + // allora deve contenere un numero ->addRule($form::Pattern, 'Deve contenere un numero', '.*[0-9].*'); ``` -La condizione può essere collegata a un elemento diverso da quello corrente usando `addConditionOn()`. Il primo parametro è un riferimento al campo. Nel caso seguente, l'e-mail sarà richiesta solo se la casella di controllo è selezionata (cioè il suo valore è `true`): +La condizione può essere legata anche a un elemento diverso da quello corrente usando `addConditionOn()`. Come primo parametro, specifichiamo un riferimento all'elemento. In questo esempio, l'e-mail sarà obbligatoria solo se la checkbox è selezionata (il suo valore sarà true): ```php -$form->addCheckbox('newsletters', 'send me newsletters'); +$form->addCheckbox('newsletters', 'inviami le newsletter'); -$form->addEmail('email', 'Email:') - // se la casella di controllo è selezionata ... +$form->addEmail('email', 'E-mail:') + // se la checkbox è selezionata ->addConditionOn($form['newsletters'], $form::Equal, true) - // ... richiede l'email - ->setRequired('Inserisci il tuo indirizzo e-mail'); + // allora richiedi l'e-mail + ->setRequired('Inserisci l\'indirizzo e-mail'); ``` -Le condizioni possono essere raggruppate in strutture complesse con i metodi `elseCondition()` e `endCondition()`. +È possibile creare strutture complesse di condizioni usando `elseCondition()` e `endCondition()`: ```php $form->addText(/* ... */) ->addCondition(/* ... */) // se la prima condizione è soddisfatta - ->addConditionOn(/* ... */) // e la seconda condizione anche su un altro elemento - ->addRule(/* ... */) // richiede questa regola + ->addConditionOn(/* ... */) // e la seconda condizione su un altro elemento + ->addRule(/* ... */) // richiedi questa regola ->elseCondition() // se la seconda condizione non è soddisfatta - ->addRule(/* ... */) // richiede queste regole + ->addRule(/* ... */) // richiedi queste regole ->addRule(/* ... */) - ->endCondition() // si torna alla prima condizione + ->endCondition() // torniamo alla prima condizione ->addRule(/* ... */); ``` -In Nette, è molto facile reagire all'adempimento o meno di una condizione sul lato JavaScript, usando il metodo `toggle()`, vedere [JavaScript dinamico |#Dynamic JavaScript]. +In Nette è molto facile reagire al soddisfacimento o meno di una condizione anche lato JavaScript usando il metodo `toggle()`, vedi [#JavaScript dinamico]. -Riferimenti tra controlli .[#toc-references-between-controls] -============================================================= +Riferimento a un Altro Elemento +=============================== -L'argomento della regola o della condizione può essere un riferimento a un altro elemento. Ad esempio, è possibile convalidare dinamicamente che il campo `text` abbia un numero di caratteri pari al valore del campo `length`: +Come argomento di una regola o condizione, è possibile passare anche un altro elemento del form. La regola utilizzerà quindi il valore inserito successivamente dall'utente nel browser. In questo modo è possibile, ad esempio, validare dinamicamente che l'elemento `password` contenga la stessa stringa dell'elemento `password_confirm`: ```php -$form->addInteger('length'); -$form->addText('text') - ->addRule($form::Length, null, $form['length']); +$form->addPassword('password', 'Password'); +$form->addPassword('password_confirm', 'Conferma password') + ->addRule($form::Equal, 'Le password inserite non corrispondono', $form['password']); ``` -Regole e condizioni personalizzate .[#toc-custom-rules-and-conditions] -====================================================================== +Regole e Condizioni Personalizzate +================================== -A volte ci troviamo in una situazione in cui le regole di convalida integrate in Nette non sono sufficienti e dobbiamo convalidare i dati dell'utente a modo nostro. In Nette questo è molto semplice! +A volte ci troviamo in una situazione in cui le regole di validazione integrate in Nette non sono sufficienti e abbiamo bisogno di validare i dati dell'utente a modo nostro. In Nette è molto semplice! -È possibile passare un qualsiasi callback come primo parametro ai metodi `addRule()` o `addCondition()`. La callback accetta l'elemento stesso come primo parametro e restituisce un valore booleano che indica se la validazione ha avuto successo. Quando si aggiunge una regola usando `addRule()`, si possono passare ulteriori argomenti, che vengono passati come secondo parametro. +Ai metodi `addRule()` o `addCondition()` è possibile passare qualsiasi callback come primo parametro. Questo riceve l'elemento stesso come primo parametro e restituisce un valore booleano che indica se la validazione è andata a buon fine. Quando si aggiunge una regola usando `addRule()`, è possibile specificare anche altri argomenti, che vengono poi passati come secondo parametro. -L'insieme personalizzato di validatori può quindi essere creato come una classe con metodi statici: +Possiamo quindi creare il nostro set di validatori come una classe con metodi statici: ```php class MyValidators @@ -175,23 +186,23 @@ class MyValidators public static function validateEmailDomain(BaseControl $input, $domain) { - // validatori aggiuntivi + // altri validatori } } ``` -L'utilizzo è quindi molto semplice: +L'uso è quindi molto semplice: ```php $form->addInteger('num') ->addRule( [MyValidators::class, 'validateDivisibility'], - 'The value must be a multiple of %d', + 'Il valore deve essere un multiplo di %d', 8, ); ``` -Le regole di validazione personalizzate possono essere aggiunte anche a JavaScript. L'unico requisito è che la regola deve essere un metodo statico. Il suo nome per il validatore JavaScript viene creato concatenando il nome della classe senza backslash `\`, the underscore `_`, e il nome del metodo. Ad esempio, scrivere `App\MyValidators::validateDivisibility` come `AppMyValidators_validateDivisibility` e aggiungerlo all'oggetto `Nette.validators`: +Le regole di validazione personalizzate possono essere aggiunte anche a JavaScript. La condizione è che la regola sia un metodo statico. Il suo nome per il validatore JavaScript viene creato unendo il nome della classe senza backslash `\`, un underscore `_` e il nome del metodo. Ad esempio, `App\MyValidators::validateDivisibility` lo scriviamo come `AppMyValidators_validateDivisibility` e lo aggiungiamo all'oggetto `Nette.validators`: ```js Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => { @@ -200,12 +211,12 @@ Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => ``` -Evento onValidate .[#toc-event-onvalidate] -========================================== +Evento onValidate +================= -Dopo l'invio del modulo, la validazione viene eseguita controllando le singole regole aggiunte da `addRule()` e richiamando l'[evento |nette:glossary#Events] `onValidate`. Il suo gestore può essere usato per ulteriori convalide, in genere per verificare la corretta combinazione di valori in più elementi del modulo. +Dopo l'invio del form, viene eseguita la validazione, durante la quale vengono controllate le singole regole aggiunte tramite `addRule()` e successivamente viene attivato l'[evento |nette:glossary#Eventi] `onValidate`. Il suo handler può essere utilizzato per una validazione aggiuntiva, tipicamente per verificare la corretta combinazione di valori in più elementi del form. -Se viene rilevato un errore, questo viene passato al modulo utilizzando il metodo `addError()`. Questo può essere richiamato sia su un elemento specifico che direttamente sul modulo. +Se viene rilevato un errore, lo passiamo al form usando il metodo `addError()`. Questo può essere chiamato su un elemento specifico o direttamente sul form. ```php protected function createComponentSignInForm(): Form @@ -225,10 +236,10 @@ public function validateSignInForm(Form $form, \stdClass $data): void ``` -Errori di elaborazione .[#toc-processing-errors] -================================================ +Errori durante l'Elaborazione +============================= -In molti casi, si scopre un errore durante l'elaborazione di un modulo valido, ad esempio quando si scrive una nuova voce nel database e si incontra una chiave duplicata. In questo caso, si trasmette l'errore al modulo con il metodo `addError()`. Questo metodo può essere richiamato su un elemento specifico o direttamente sul modulo: +In molti casi, veniamo a conoscenza di un errore solo nel momento in cui elaboriamo un form valido, ad esempio quando inseriamo un nuovo elemento nel database e incontriamo una duplicazione di chiavi. In tal caso, passiamo nuovamente l'errore al form usando il metodo `addError()`. Questo può essere chiamato su un elemento specifico o direttamente sul form: ```php try { @@ -238,72 +249,71 @@ try { } catch (Nette\Security\AuthenticationException $e) { if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) { - $form->addError('Invalid password.'); + $form->addError('Password non valida.'); } } ``` -Se possibile, si consiglia di aggiungere l'errore direttamente all'elemento del form, poiché apparirà accanto a esso quando si utilizza il renderer predefinito. +Se possibile, si consiglia di allegare l'errore direttamente all'elemento del form, poiché verrà visualizzato accanto ad esso quando si utilizza il renderer predefinito. ```php -$form['date']->addError('Sorry, this date is already taken.'); +$form['date']->addError('Siamo spiacenti, ma questa data è già occupata.'); ``` -È possibile chiamare ripetutamente `addError()` per passare più messaggi di errore a un form o a un elemento. Si ottengono con `getErrors()`. +Potete chiamare `addError()` ripetutamente per passare più messaggi di errore al form o all'elemento. Li ottenete usando `getErrors()`. -Si noti che `$form->getErrors()` restituisce un riepilogo di tutti i messaggi di errore, anche quelli passati direttamente a singoli elementi, non solo direttamente al modulo. I messaggi di errore passati solo al modulo vengono recuperati con `$form->getOwnErrors()`. +Attenzione, `$form->getErrors()` restituisce un riepilogo di tutti i messaggi di errore, anche quelli passati direttamente ai singoli elementi, non solo direttamente al form. I messaggi di errore passati solo al form si ottengono tramite `$form->getOwnErrors()`. -Modifica dei valori di input .[#toc-modifying-input-values] -=========================================================== +Modifica dell'Input +=================== -Utilizzando il metodo `addFilter()`, possiamo modificare il valore inserito dall'utente. In questo esempio, tollereremo e rimuoveremo gli spazi nel codice postale: +Usando il metodo `addFilter()` possiamo modificare il valore inserito dall'utente. In questo esempio, tollereremo e rimuoveremo gli spazi nel CAP (Codice di Avviamento Postale): ```php -$form->addText('zip', 'Codice postale:') +$form->addText('zip', 'CAP:') ->addFilter(function ($value) { - return str_replace(' ', '', $value); // rimuove gli spazi dal codice postale + return str_replace(' ', '', $value); // rimuoviamo gli spazi dal CAP }) - ->addRule($form::Pattern, 'Il codice postale non è di cinque cifre', '\d{5}'); + ->addRule($form::Pattern, 'Il CAP non è nel formato di cinque cifre', '\d{5}'); ``` -Il filtro è incluso tra le regole di validazione e le condizioni e quindi dipende dall'ordine dei metodi, cioè il filtro e la regola sono richiamati nello stesso ordine dei metodi `addFilter()` e `addRule()`. +Il filtro viene inserito tra le regole di validazione e le condizioni, quindi l'ordine dei metodi è importante, cioè il filtro e la regola vengono chiamati nello stesso ordine in cui sono presenti i metodi `addFilter()` e `addRule()`. -Convalida JavaScript .[#toc-javascript-validation] -================================================== +Validazione JavaScript +====================== -Il linguaggio delle regole e delle condizioni di validazione è potente. Anche se tutti i costrutti funzionano sia lato server che lato client, in JavaScript. Le regole sono trasferite in attributi HTML `data-nette-rules` come JSON. -La validazione stessa è gestita da un altro script, che aggancia tutti gli eventi `submit` del modulo, itera su tutti gli input ed esegue le rispettive validazioni. +Il linguaggio per formulare condizioni e regole è molto potente. Tutte le costruzioni funzionano sia lato server che lato JavaScript. Vengono trasmesse negli attributi HTML `data-nette-rules` come JSON. La validazione stessa viene quindi eseguita da uno script che intercetta l'evento `submit` del form, scorre i singoli elementi ed esegue la validazione appropriata. -Questo script è `netteForms.js`, disponibile da diverse fonti: +Questo script è `netteForms.js` ed è disponibile da diverse fonti possibili: -È possibile incorporare lo script direttamente nella pagina HTML dal CDN: +Potete inserire lo script direttamente nella pagina HTML da una CDN: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Oppure copiare localmente nella cartella pubblica del progetto (ad esempio da `vendor/nette/forms/src/assets/netteForms.min.js`): +Oppure copiarlo localmente nella cartella pubblica del progetto (ad es. da `vendor/nette/forms/src/assets/netteForms.min.js`): ```latte <script src="/path/to/netteForms.min.js"></script> ``` -Oppure installare tramite [npm |https://www.npmjs.com/package/nette-forms]: +Oppure installarlo tramite [npm|https://www.npmjs.com/package/nette-forms]: ```shell npm install nette-forms ``` -E poi caricare ed eseguire: +E successivamente caricarlo ed eseguirlo: ```js import netteForms from 'nette-forms'; netteForms.initOnLoad(); ``` -In alternativa, è possibile caricarlo direttamente dalla cartella `vendor`: +In alternativa, potete caricarlo direttamente dalla cartella `vendor`: ```js import netteForms from '../path/to/vendor/nette/forms/src/assets/netteForms.js'; @@ -311,10 +321,10 @@ netteForms.initOnLoad(); ``` -JavaScript dinamico .[#toc-dynamic-javascript] -============================================== +JavaScript Dinamico +=================== -Volete mostrare i campi dell'indirizzo solo se l'utente sceglie di inviare la merce per posta? Nessun problema. La chiave è una coppia di metodi `addCondition()` e `toggle()`: +Volete visualizzare i campi per l'inserimento dell'indirizzo solo se l'utente sceglie di spedire la merce per posta? Nessun problema. La chiave è la coppia di metodi `addCondition()` & `toggle()`: ```php $form->addCheckbox('send_it') @@ -322,25 +332,25 @@ $form->addCheckbox('send_it') ->toggle('#address-container'); ``` -Questo codice dice che quando la condizione è soddisfatta, cioè quando la casella di controllo è selezionata, l'elemento HTML `#address-container` sarà visibile. E viceversa. Quindi, inseriamo gli elementi del modulo con l'indirizzo del destinatario in un contenitore con quell'ID e, quando si fa clic sulla casella di controllo, essi vengono nascosti o mostrati. Questo viene gestito dallo script `netteForms.js`. +Questo codice dice che quando la condizione è soddisfatta, cioè quando la checkbox è selezionata, l'elemento HTML `#address-container` sarà visibile. E viceversa. Quindi, posizioniamo gli elementi del form con l'indirizzo del destinatario in un container con questo ID e, facendo clic sulla checkbox, verranno nascosti o visualizzati. Questo è gestito dallo script `netteForms.js`. -Qualsiasi selettore può essere passato come argomento al metodo `toggle()`. Per ragioni storiche, una stringa alfanumerica senza altri caratteri speciali viene trattata come un ID di elemento, come se fosse preceduta dal simbolo `#` character. The second optional parameter allows us to reverse the behavior, i.e. if we used `toggle('#address-container', false)`, l'elemento verrebbe visualizzato solo se la casella di controllo fosse deselezionata. +Come argomento del metodo `toggle()` è possibile passare qualsiasi selettore. Per motivi storici, una stringa alfanumerica senza altri caratteri speciali viene intesa come ID dell'elemento, cioè come se fosse preceduta dal carattere `#`. Il secondo parametro opzionale consente di invertire il comportamento, cioè se usassimo `toggle('#address-container', false)`, l'elemento verrebbe visualizzato solo se la checkbox non fosse selezionata. -L'implementazione predefinita di JavaScript modifica la proprietà `hidden` per gli elementi. Tuttavia, è possibile modificare facilmente il comportamento, ad esempio aggiungendo un'animazione. È sufficiente sovrascrivere il metodo `Nette.toggle` in JavaScript con una soluzione personalizzata: +L'implementazione predefinita in JavaScript modifica la proprietà `hidden` degli elementi. Tuttavia, possiamo facilmente modificare il comportamento, ad esempio aggiungendo un'animazione. Basta sovrascrivere il metodo `Nette.toggle` in JavaScript con la propria soluzione: ```js Nette.toggle = (selector, visible, srcElement, event) => { document.querySelectorAll(selector).forEach((el) => { - // hide or show 'el' according to the value of 'visible' + // nascondiamo o mostriamo 'el' in base al valore 'visible' }); }; ``` -Disabilitare la validazione .[#toc-disabling-validation] -======================================================== +Disabilitazione della Validazione +================================= -In alcuni casi, è necessario disabilitare la convalida. Se un pulsante di invio non deve eseguire la convalida dopo l'invio (per esempio il pulsante *Annulla* o *Anteprima*), si può disabilitare la convalida chiamando `$submit->setValidationScope([])`. È anche possibile convalidare parzialmente il modulo, specificando gli elementi da convalidare. +A volte può essere utile disabilitare la validazione. Se la pressione del pulsante di invio non deve eseguire la validazione (adatto per i pulsanti *Annulla* o *Anteprima*), la disabilitiamo con il metodo `$submit->setValidationScope([])`. Se deve eseguire solo una validazione parziale, possiamo specificare quali campi o container del form devono essere validati. ```php $form->addText('name') @@ -348,19 +358,19 @@ $form->addText('name') $details = $form->addContainer('details'); $details->addInteger('age') - ->setRequired('age'); + ->setRequired('età'); $details->addInteger('age2') - ->setRequired('age2'); + ->setRequired('età2'); -$form->addSubmit('send1'); // Convalida l'intero modulo +$form->addSubmit('send1'); // Valida l'intero form $form->addSubmit('send2') - ->setValidationScope([]); // Non convalida nulla + ->setValidationScope([]); // Non valida affatto $form->addSubmit('send3') - ->setValidationScope([$form['name']]); // Valida solo il campo 'name'. + ->setValidationScope([$form['name']]); // Valida solo l'elemento name $form->addSubmit('send4') - ->setValidationScope([$form['details']['age']]); // Convalida solo il campo "età". + ->setValidationScope([$form['details']['age']]); // Valida solo l'elemento age $form->addSubmit('send5') - ->setValidationScope([$form['details']]); // Convalida il contenitore 'details'. + ->setValidationScope([$form['details']]); // Valida il container details ``` -L'[evento onValidate |#Event onValidate] sul form è sempre invocato e non è influenzato dall'evento `setValidationScope`. `onValidate` sul contenitore è invocato solo quando questo contenitore è specificato per la validazione parziale. +`setValidationScope` non influisce sull'[#evento onValidate] del form, che verrà chiamato sempre. L'evento `onValidate` del container verrà attivato solo se questo container è contrassegnato per la validazione parziale. diff --git a/forms/ja/@home.texy b/forms/ja/@home.texy new file mode 100644 index 0000000000..a29a5b780e --- /dev/null +++ b/forms/ja/@home.texy @@ -0,0 +1,32 @@ +Nette Forms +*********** + +<div class=perex> + +Nette Forms は Web フォームの作成に革命をもたらしました。突然、数行のわかりやすいコードを書くだけで、レンダリング、JavaScriptおよびサーバーサイドの検証を含む完成したフォームが得られ、さらに最高レベルのセキュリティが確保されました。以下を示します: + +- 使いやすいフォームの作成 +- 送信されたデータの検証 +- 必要に応じて要素を正確にレンダリング + +</div> + + +Nette Forms を使用することで、検証の記述(さらに、サーバーサイドとクライアントサイドの2つの検証)などの多くの日常的なタスクを回避し、エラーやセキュリティホールの発生確率を最小限に抑えることができます。 + +フォームは、Nette アプリケーションの一部として(つまり Presenter 内で)、または完全に独立して使用できます。両方の場合で使い方が少し異なるため、2つのガイドを用意しました: + +<div class="wiki-buttons"> +<div> "Presenter 内のフォーム .[wiki-button]":in-presenter </div> +<div> "独立したフォーム .[wiki-button]":standalone </div> +</div> + + +インストール +------ + +[Composer|best-practices:composer]を使用してライブラリをダウンロードし、インストールします: + +```shell +composer require nette/forms +``` diff --git a/forms/ja/@left-menu.texy b/forms/ja/@left-menu.texy new file mode 100644 index 0000000000..466166e097 --- /dev/null +++ b/forms/ja/@left-menu.texy @@ -0,0 +1,14 @@ +Nette Forms +*********** +- [はじめに |@home] +- [Presenter 内のフォーム|in-presenter] +- [独立したフォーム|standalone] +- [フォームコントロール |controls] +- [検証 |validation] +- [レンダリング |rendering] +- [設定 |configuration] + + +参考文献 +**** +- [ガイドとベストプラクティス |best-practices:] diff --git a/forms/ja/@meta.texy b/forms/ja/@meta.texy new file mode 100644 index 0000000000..d3c41dc3d7 --- /dev/null +++ b/forms/ja/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette ドキュメンテーション}} diff --git a/forms/ja/configuration.texy b/forms/ja/configuration.texy new file mode 100644 index 0000000000..c70c6e8417 --- /dev/null +++ b/forms/ja/configuration.texy @@ -0,0 +1,61 @@ +フォームの設定 +******* + +.[perex] +設定で、デフォルトの[フォームのエラーメッセージ|validation]を変更できます。 + +```neon +forms: + messages: + Equal: 'Please enter %s.' + NotEqual: 'This value should not be %s.' + Filled: 'This field is required.' + Blank: 'This field should be blank.' + MinLength: 'Please enter at least %d characters.' + MaxLength: 'Please enter no more than %d characters.' + Length: 'Please enter a value between %d and %d characters long.' + Email: 'Please enter a valid email address.' + URL: 'Please enter a valid URL.' + Integer: 'Please enter a valid integer.' + Float: 'Please enter a valid number.' + Min: 'Please enter a value greater than or equal to %d.' + Max: 'Please enter a value less than or equal to %d.' + Range: 'Please enter a value between %d and %d.' + MaxFileSize: 'The size of the uploaded file can be up to %d bytes.' + MaxPostSize: 'The uploaded data exceeds the limit of %d bytes.' + MimeType: 'The uploaded file is not in the expected format.' + Image: 'The uploaded file must be image in format JPEG, GIF, PNG or WebP.' + Nette\Forms\Controls\SelectBox::Valid: 'Please select a valid option.' + Nette\Forms\Controls\UploadControl::Valid: 'An error occurred during file upload.' + Nette\Forms\Controls\CsrfProtection::Protection: 'Your session has expired. Please return to the home page and try again.' +``` + +こちらは日本語訳です: + +```neon +forms: + messages: + Equal: '%sを入力してください。' + NotEqual: 'この値は%sであってはなりません。' + Filled: 'このフィールドは必須です。' + Blank: 'このフィールドは空である必要があります。' + MinLength: '少なくとも%d文字入力してください。' + MaxLength: '%d文字以内で入力してください。' + Length: '%d文字から%d文字の間で入力してください。' + Email: '有効なメールアドレスを入力してください。' + URL: '有効なURLを入力してください。' + Integer: '有効な整数を入力してください。' + Float: '有効な数値を入力してください。' + Min: '%d以上の値を入力してください。' + Max: '%d以下の値を入力してください。' + Range: '%dから%dの間の値を入力してください。' + MaxFileSize: 'アップロードされたファイルのサイズは最大%dバイトまでです。' + MaxPostSize: 'アップロードされたデータは%dバイトの制限を超えています。' + MimeType: 'アップロードされたファイルは期待される形式ではありません。' + Image: 'アップロードされたファイルはJPEG、GIF、PNG、WebP、またはAVIF形式の画像である必要があります。' + Nette\Forms\Controls\SelectBox::Valid: '有効なオプションを選択してください。' + Nette\Forms\Controls\UploadControl::Valid: 'ファイルアップロード中にエラーが発生しました。' + Nette\Forms\Controls\CsrfProtection::Protection: 'セッションの有効期限が切れました。ホームページに戻って再度お試しください。' +``` + +フレームワーク全体を使用せず、したがって設定ファイルも使用しない場合は、`Nette\Forms\Validator::$messages` 配列で直接デフォルトのエラーメッセージを変更できます。 diff --git a/forms/ja/controls.texy b/forms/ja/controls.texy new file mode 100644 index 0000000000..bc828fdb30 --- /dev/null +++ b/forms/ja/controls.texy @@ -0,0 +1,559 @@ +フォーム要素 +****** + +.[perex] +標準的なフォーム要素の概要。 + + +addText(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +================================================================================================== + +一行テキストフィールド(クラス[TextInput |api:Nette\Forms\Controls\TextInput])を追加します。ユーザーがフィールドを空にした場合、空の文字列 `''` を返します。または、`setNullable()` を使用して `null` を返すように指定できます。 + +```php +$form->addText('name', '名前:') + ->setRequired() + ->setNullable(); +``` + +自動的にUTF-8を検証し、左右の空白をトリミングし、攻撃者が送信する可能性のある改行を削除します。 + +最大長は `setMaxLength()` で制限できます。ユーザーが入力した値を変更するには、[addFilter() |validation#入力の変更] を使用します。 + +`setHtmlType()` を使用して、テキストフィールドの視覚的な特性を `search`、`tel`、`url` などのタイプに変更できます([仕様|https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types]を参照)。タイプの変更は視覚的なものであり、検証機能を代替するものではないことに注意してください。`url` タイプの場合、特定の検証[URLルール |validation#テキスト入力]を追加することをお勧めします。 + +.[note] +`number`、`range`、`email`、`date`、`datetime-local`、`time`、`color` などの他の入力タイプについては、[#addInteger]、[#addFloat]、[#addEmail]、[#addDate]、[#addTime]、[#addDateTime]、[#addColor] などの専用メソッドを使用してください。これらはサーバーサイドの検証を保証します。`month` および `week` タイプは、まだすべてのブラウザで完全にサポートされているわけではありません。 + +要素には、いわゆる空の値(empty-value)を設定できます。これはデフォルト値のようなものですが、ユーザーが変更しない場合、要素は空の文字列または `null` を返します。 + +```php +$form->addText('phone', '電話番号:') + ->setHtmlType('tel') + ->setEmptyValue('+81'); +``` + + +addTextArea(string|int $name, $label=null): TextArea .[method] +============================================================== + +複数行テキストを入力するためのフィールド(クラス[TextArea |api:Nette\Forms\Controls\TextArea])を追加します。ユーザーがフィールドを空にした場合、空の文字列 `''` を返します。または、`setNullable()` を使用して `null` を返すように指定できます。 + +```php +$form->addTextArea('note', '備考:') + ->addRule($form::MaxLength, '備考が長すぎます', 10000); +``` + +自動的にUTF-8を検証し、改行区切り文字を `\n` に正規化します。一行入力フィールドとは異なり、空白のトリミングは行われません。 + +最大長は `setMaxLength()` で制限できます。ユーザーが入力した値を変更するには、[addFilter() |validation#入力の変更] を使用します。`setEmptyValue()` を使用して、いわゆる空の値を設定できます。 + + +addInteger(string|int $name, $label=null): TextInput .[method] +============================================================== + +整数を入力するためのフィールド(クラス[TextInput |api:Nette\Forms\Controls\TextInput])を追加します。ユーザーが何も入力しない場合は、整数または `null` を返します。 + +```php +$form->addInteger('year', '年:') + ->addRule($form::Range, '年は %d から %d の範囲である必要があります。', [1900, 2023]); +``` + +要素は `<input type="number">` としてレンダリングされます。`setHtmlType()` メソッドを使用して、タイプを `range` に変更してスライダーとして表示したり、`number` タイプの特別な動作なしで標準のテキストフィールドを好む場合は `text` に変更したりできます。 + + +addFloat(string|int $name, $label=null): TextInput .[method]{data-version:3.1.12} +================================================================================= + +浮動小数点数を入力するためのフィールド(クラス[TextInput |api:Nette\Forms\Controls\TextInput])を追加します。ユーザーが何も入力しない場合は、浮動小数点数または `null` を返します。 + +```php +$form->addFloat('level', 'レベル:') + ->setDefaultValue(0) + ->addRule($form::Range, 'レベルは %d から %d の範囲である必要があります。', [0, 100]); +``` + +要素は `<input type="number">` としてレンダリングされます。`setHtmlType()` メソッドを使用して、タイプを `range` に変更してスライダーとして表示したり、`number` タイプの特別な動作なしで標準のテキストフィールドを好む場合は `text` に変更したりできます。 + +NetteとChromeブラウザは、小数点区切り文字としてカンマとピリオドの両方を受け入れます。Firefoxでもこの機能を利用できるようにするには、特定の要素またはページ全体に `lang` 属性を設定することをお勧めします。例:`<html lang="ja">`。 + + +addEmail(string|int $name, $label=null, int $maxLength=255): TextInput .[method] +================================================================================ + +メールアドレスを入力するためのフィールド(クラス[TextInput |api:Nette\Forms\Controls\TextInput])を追加します。ユーザーがフィールドを空にした場合、空の文字列 `''` を返します。または、`setNullable()` を使用して `null` を返すように指定できます。 + +```php +$form->addEmail('email', 'メールアドレス:'); +``` + +値が有効なメールアドレスであるかどうかを検証します。ドメインが実際に存在するかどうかは検証されず、構文のみが検証されます。自動的にUTF-8を検証し、左右の空白をトリミングします。 + +最大長は `setMaxLength()` で制限できます。ユーザーが入力した値を変更するには、[addFilter() |validation#入力の変更] を使用します。`setEmptyValue()` を使用して、いわゆる空の値を設定できます。 + + +addPassword(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +====================================================================================================== + +パスワードを入力するためのフィールド(クラス[TextInput |api:Nette\Forms\Controls\TextInput])を追加します。 + +```php +$form->addPassword('password', 'パスワード:') + ->setRequired() + ->addRule($form::MinLength, 'パスワードは少なくとも %d 文字必要です', 8) + ->addRule($form::Pattern, '数字を含める必要があります', '.*[0-9].*'); +``` + +フォームを再表示すると、フィールドは空になります。自動的にUTF-8を検証し、左右の空白をトリミングし、攻撃者が送信する可能性のある改行を削除します。 + + +addCheckbox(string|int $name, $caption=null): Checkbox .[method] +================================================================ + +チェックボックス(クラス[Checkbox |api:Nette\Forms\Controls\Checkbox])を追加します。チェックされているかどうかに応じて、`true` または `false` の値を返します。 + +```php +$form->addCheckbox('agree', '利用規約に同意します') + ->setRequired('利用規約に同意する必要があります'); +``` + + +addCheckboxList(string|int $name, $label=null, ?array $items=null): CheckboxList .[method] +========================================================================================== + +複数の項目を選択するためのチェックボックス(クラス[CheckboxList |api:Nette\Forms\Controls\CheckboxList])を追加します。選択された項目のキーの配列を返します。`getSelectedItems()` メソッドはキーの代わりに値を返します。 + +```php +$form->addCheckboxList('colors', '色:', [ + 'r' => '赤', + 'g' => '緑', + 'b' => '青', +]); +``` + +提供される項目の配列は、3番目のパラメータまたは `setItems()` メソッドで渡します。 + +`setDisabled(['r', 'g'])` を使用して、個々の項目を無効にできます。 + +要素は、改ざんが発生していないこと、選択された項目が実際に提供されたものの1つであり、無効にされていないことを自動的にチェックします。`getRawValue()` メソッドを使用すると、この重要なチェックなしで送信された項目を取得できます。 + +デフォルトで選択された項目を設定する場合も、それらが提供されたものの1つであることを確認します。そうでない場合は例外をスローします。このチェックは `checkDefaultValue(false)` で無効にできます。 + +`GET` メソッドでフォームを送信する場合、クエリ文字列のサイズを節約する、よりコンパクトなデータ転送方法を選択できます。これは、フォームのHTML属性を設定することで有効になります: + +```php +$form->setHtmlAttribute('data-nette-compact'); +``` + + +addRadioList(string|int $name, $label=null, ?array $items=null): RadioList .[method] +==================================================================================== + +ラジオボタン(クラス[RadioList |api:Nette\Forms\Controls\RadioList])を追加します。選択された項目のキーを返します。ユーザーが何も選択しない場合は `null` を返します。`getSelectedItem()` メソッドはキーの代わりに値を返します。 + +```php +$sex = [ + 'm' => '男性', + 'f' => '女性', +]; +$form->addRadioList('gender', '性別:', $sex); +``` + +提供される項目の配列は、3番目のパラメータまたは `setItems()` メソッドで渡します。 + +`setDisabled(['m', 'f'])` を使用して、個々の項目を無効にできます。 + +要素は、改ざんが発生していないこと、選択された項目が実際に提供されたものの1つであり、無効にされていないことを自動的にチェックします。`getRawValue()` メソッドを使用すると、この重要なチェックなしで送信された項目を取得できます。 + +デフォルトで選択された項目を設定する場合も、それが提供されたものの1つであることを確認します。そうでない場合は例外をスローします。このチェックは `checkDefaultValue(false)` で無効にできます。 + + +addSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): SelectBox .[method] +================================================================================================== + +セレクトボックス(クラス[SelectBox |api:Nette\Forms\Controls\SelectBox])を追加します。選択された項目のキーを返します。ユーザーが何も選択しない場合は `null` を返します。`getSelectedItem()` メソッドはキーの代わりに値を返します。 + +```php +$countries = [ + 'CZ' => 'チェコ共和国', + 'SK' => 'スロバキア', + 'GB' => 'イギリス', +]; + +$form->addSelect('country', '国:', $countries) + ->setDefaultValue('SK'); +``` + +提供される項目の配列は、3番目のパラメータまたは `setItems()` メソッドで渡します。項目は2次元配列にすることもできます: + +```php +$countries = [ + 'ヨーロッパ' => [ // 日本語のグループ名 + 'CZ' => 'チェコ共和国', + 'SK' => 'スロバキア', + 'GB' => 'イギリス', + ], + 'CA' => 'カナダ', + 'US' => 'アメリカ合衆国', + '?' => 'その他', +]; +``` + +セレクトボックスでは、最初の項目が特別な意味を持つことがよくあります。アクションを促すために使用されます。このような項目を追加するには `setPrompt()` メソッドを使用します。 + +```php +$form->addSelect('country', '国:', $countries) + ->setPrompt('国を選択してください'); +``` + +`setDisabled(['CZ', 'SK'])` を使用して、個々の項目を無効にできます。 + +要素は、改ざんが発生していないこと、選択された項目が実際に提供されたものの1つであり、無効にされていないことを自動的にチェックします。`getRawValue()` メソッドを使用すると、この重要なチェックなしで送信された項目を取得できます。 + +デフォルトで選択された項目を設定する場合も、それが提供されたものの1つであることを確認します。そうでない場合は例外をスローします。このチェックは `checkDefaultValue(false)` で無効にできます。 + + +addMultiSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): MultiSelectBox .[method] +============================================================================================================ + +複数の項目を選択するためのセレクトボックス(クラス[MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox])を追加します。選択された項目のキーの配列を返します。`getSelectedItems()` メソッドはキーの代わりに値を返します。 + +```php +$form->addMultiSelect('countries', '国:', $countries); +``` + +提供される項目の配列は、3番目のパラメータまたは `setItems()` メソッドで渡します。項目は2次元配列にすることもできます。 + +`setDisabled(['CZ', 'SK'])` を使用して、個々の項目を無効にできます。 + +要素は、改ざんが発生していないこと、選択された項目が実際に提供されたものの1つであり、無効にされていないことを自動的にチェックします。`getRawValue()` メソッドを使用すると、この重要なチェックなしで送信された項目を取得できます。 + +デフォルトで選択された項目を設定する場合も、それらが提供されたものの1つであることを確認します。そうでない場合は例外をスローします。このチェックは `checkDefaultValue(false)` で無効にできます。 + + +addUpload(string|int $name, $label=null): UploadControl .[method] +================================================================= + +ファイルアップロード用のフィールド(クラス[UploadControl |api:Nette\Forms\Controls\UploadControl])を追加します。ユーザーがファイルを送信しなかった場合でも、[FileUpload |http:request#FileUpload] オブジェクトを返します。これは `FileUpload::hasFile()` メソッドで確認できます。 + +```php +$form->addUpload('avatar', 'アバター:') + ->addRule($form::Image, 'アバターはJPEG、PNG、GIF、WebP、またはAVIFである必要があります。') + ->addRule($form::MaxFileSize, '最大サイズは1MBです。', 1024 * 1024); +``` + +ファイルが正しくアップロードされなかった場合、フォームは正常に送信されず、エラーが表示されます。つまり、正常に送信された場合、`FileUpload::isOk()` メソッドを確認する必要はありません。 + +`FileUpload::getName()` メソッドによって返される元のファイル名を決して信用しないでください。クライアントは、アプリケーションを破損またはハッキングする意図で悪意のあるファイル名を送信した可能性があります。 + +`MimeType` および `Image` ルールは、ファイルのシグネチャに基づいて要求されたタイプを検出し、その整合性を検証しません。画像が破損していないかどうかは、たとえば[読み込み |http:request#toImage]を試みることで確認できます。 + + +addMultiUpload(string|int $name, $label=null): UploadControl .[method] +====================================================================== + +複数のファイルを一度にアップロードするためのフィールド(クラス[UploadControl |api:Nette\Forms\Controls\UploadControl])を追加します。[FileUpload |http:request#FileUpload] オブジェクトの配列を返します。それぞれの `FileUpload::hasFile()` メソッドは `true` を返します。 + +```php +$form->addMultiUpload('files', 'ファイル:') + ->addRule($form::MaxLength, '最大 %d ファイルまでアップロードできます', 10); +``` + +いずれかのファイルが正しくアップロードされなかった場合、フォームは正常に送信されず、エラーが表示されます。つまり、正常に送信された場合、`FileUpload::isOk()` メソッドを確認する必要はありません。 + +`FileUpload::getName()` メソッドによって返される元のファイル名を決して信用しないでください。クライアントは、アプリケーションを破損またはハッキングする意図で悪意のあるファイル名を送信した可能性があります。 + +`MimeType` および `Image` ルールは、ファイルのシグネチャに基づいて要求されたタイプを検出し、その整合性を検証しません。画像が破損していないかどうかは、たとえば[読み込み |http:request#toImage]を試みることで確認できます。 + + +addDate(string|int $name, $label=null): DateTimeControl .[method]{data-version:3.1.14} +====================================================================================== + +ユーザーが年、月、日で構成される日付を簡単に入力できるフィールド(クラス[DateTimeControl |api:Nette\Forms\Controls\DateTimeControl])を追加します。 + +デフォルト値として、`DateTimeInterface` インターフェースを実装するオブジェクト、時間を含む文字列、またはUNIXタイムスタンプを表す数値を受け入れます。最小および最大許容日付を定義する `Min`、`Max`、または `Range` ルールの引数についても同様です。 + +```php +$form->addDate('date', '日付:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, '日付は少なくとも1か月前である必要があります。', new DateTime('-1 month')); +``` + +デフォルトでは `DateTimeImmutable` オブジェクトを返します。`setFormat()` メソッドを使用して、[テキスト形式|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters]またはタイムスタンプを指定できます: + +```php +$form->addDate('date', '日付:') + ->setFormat('Y-m-d'); +``` + + +addTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=============================================================================================================== + +ユーザーが時、分、およびオプションで秒で構成される時間を簡単に入力できるフィールド(クラス[DateTimeControl |api:Nette\Forms\Controls\DateTimeControl])を追加します。 + +デフォルト値として、`DateTimeInterface` インターフェースを実装するオブジェクト、時間を含む文字列、またはUNIXタイムスタンプを表す数値を受け入れます。これらの入力からは時間情報のみが使用され、日付は無視されます。最小および最大許容時間を定義する `Min`、`Max`、または `Range` ルールの引数についても同様です。最小値が最大値より大きい場合、深夜を超える時間範囲が作成されます。 + +```php +$form->addTime('time', '時間:', withSeconds: true) + ->addRule($form::Range, '時間は %d から %d の範囲である必要があります。', ['12:30', '13:30']); +``` + +デフォルトでは `DateTimeImmutable` オブジェクト(日付は1月1日)を返します。`setFormat()` メソッドを使用して、[テキスト形式|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters]を指定できます: + +```php +$form->addTime('time', '時間:') + ->setFormat('H:i'); +``` + + +addDateTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=================================================================================================================== + +ユーザーが年、月、日、時、分、およびオプションで秒で構成される日付と時間を簡単に入力できるフィールド(クラス[DateTimeControl |api:Nette\Forms\Controls\DateTimeControl])を追加します。 + +デフォルト値として、`DateTimeInterface` インターフェースを実装するオブジェクト、時間を含む文字列、またはUNIXタイムスタンプを表す数値を受け入れます。最小および最大許容日付を定義する `Min`、`Max`、または `Range` ルールの引数についても同様です。 + +```php +$form->addDateTime('datetime', '日時:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, '日付は少なくとも1か月前である必要があります。', new DateTime('-1 month')); +``` + +デフォルトでは `DateTimeImmutable` オブジェクトを返します。`setFormat()` メソッドを使用して、[テキスト形式|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters]またはタイムスタンプを指定できます: + +```php +$form->addDateTime('datetime') + ->setFormat(DateTimeControl::FormatTimestamp); +``` + + +addColor(string|int $name, $label=null): ColorPicker .[method]{data-version:3.1.14} +=================================================================================== + +色を選択するためのフィールド(クラス[ColorPicker |api:Nette\Forms\Controls\ColorPicker])を追加します。色は `#rrggbb` 形式の文字列です。ユーザーが選択しない場合、黒色 `#000000` が返されます。 + +```php +$form->addColor('color', '色:') + ->setDefaultValue('#3C8ED7'); +``` + + +addHidden(string|int $name, ?string $default=null): HiddenField .[method] +========================================================================= + +隠しフィールド(クラス[HiddenField |api:Nette\Forms\Controls\HiddenField])を追加します。 + +```php +$form->addHidden('userid'); +``` + +`setNullable()` を使用して、空の文字列の代わりに `null` を返すように設定できます。送信された値を変更するには、[addFilter() |validation#入力の変更] を使用します。 + +要素は隠されていますが、値は依然として攻撃者によって変更または偽造される可能性があることを**認識することが重要です**。データの操作に関連するセキュリティリスクを回避するために、サーバー側で受信したすべての値を常に徹底的に検証および検証してください。 + + +addSubmit(string|int $name, $caption=null): SubmitButton .[method] +================================================================== + +送信ボタン(クラス[SubmitButton |api:Nette\Forms\Controls\SubmitButton])を追加します。 + +```php +$form->addSubmit('submit', '送信'); +``` + +フォームには複数の送信ボタンを含めることができます: + +```php +$form->addSubmit('register', '登録'); +$form->addSubmit('cancel', 'キャンセル'); +``` + +どちらがクリックされたかを確認するには、次を使用します: + +```php +if ($form['register']->isSubmittedBy()) { + // ... +} +``` + +ボタンを押したときにフォーム全体を検証したくない場合(たとえば、*キャンセル*または*プレビュー*ボタンの場合)、[setValidationScope() |validation#検証の無効化] を使用します。 + + +addButton(string|int $name, $caption): Button .[method] +======================================================= + +送信機能を持たないボタン(クラス[Button |api:Nette\Forms\Controls\Button])を追加します。したがって、クリック時にJavaScript関数を呼び出すなど、他の機能に使用できます。 + +```php +$form->addButton('raise', '給与を上げる') + ->setHtmlAttribute('onclick', 'raiseSalary()'); +``` + + +addImageButton(string|int $name, ?string $src=null, ?string $alt=null): ImageButton .[method] +============================================================================================= + +画像形式の送信ボタン(クラス[ImageButton |api:Nette\Forms\Controls\ImageButton])を追加します。 + +```php +$form->addImageButton('submit', '/path/to/image'); +``` + +複数の送信ボタンを使用する場合、`$form['submit']->isSubmittedBy()` を使用してどちらがクリックされたかを確認できます。 + + +addContainer(string|int $name): Container .[method] +=================================================== + +サブフォーム(クラス[Container|api:Nette\Forms\Container])、つまりコンテナを追加します。これには、フォームに追加するのと同じ方法で他の要素を追加できます。`setDefaults()` または `getValues()` メソッドも機能します。 + +```php +$sub1 = $form->addContainer('first'); +$sub1->addText('name', 'あなたの名前:'); +$sub1->addEmail('email', 'メールアドレス:'); + +$sub2 = $form->addContainer('second'); +$sub2->addText('name', 'あなたの名前:'); +$sub2->addEmail('email', 'メールアドレス:'); +``` + +送信されたデータは、多次元構造として返されます: + +```php +[ + 'first' => [ + 'name' => /* ... */, + 'email' => /* ... */, + ], + 'second' => [ + 'name' => /* ... */, + 'email' => /* ... */, + ], +] +``` + + +設定の概要 +===== + +すべての要素で、次のメソッドを呼び出すことができます(完全な概要は[APIドキュメント|https://api.nette.org/forms/master/Nette/Forms/Controls.html]を参照): + +.[table-form-methods language-php] +| `setDefaultValue($value)` | デフォルト値を設定します +| `getValue()` | 現在の値を取得します +| `setOmitted()` | [#値の省略] +| `setDisabled()` | [#要素の無効化] + +レンダリング: +.[table-form-methods language-php] +| `setCaption($caption)` | 要素のキャプションを変更します +| `setTranslator($translator)` | [トランスレータ |rendering#翻訳]を設定します +| `setHtmlAttribute($name, $value)` | 要素の[HTML属性 |rendering#HTML属性]を設定します +| `setHtmlId($id)` | HTML属性 `id` を設定します +| `setHtmlType($type)` | HTML属性 `type` を設定します +| `setHtmlName($name)` | HTML属性 `name` を設定します +| `setOption($key, $value)` | [レンダリング設定 |rendering#Options] + +検証: +.[table-form-methods language-php] +| `setRequired()` | [必須要素 |validation] +| `addRule()` | [検証ルール |validation#ルール]を設定します +| `addCondition()`, `addConditionOn()` | [検証条件 |validation#条件]を設定します +| `addError($message)` | [エラーメッセージの受け渡し |validation#処理中のエラー] + +`addText()`、`addPassword()`、`addTextArea()`、`addEmail()`、`addInteger()` 要素では、次のメソッドを呼び出すことができます: + +.[table-form-methods language-php] +| `setNullable()` | getValue() が空の文字列の代わりに `null` を返すかどうかを設定します +| `setEmptyValue($value)` | 空の文字列と見なされる特別な値を設定します +| `setMaxLength($length)` | 許可される最大文字数を設定します +| `addFilter($filter)` | [入力の変更 |validation#入力の変更] + + +値の省略 +==== + +ユーザーが入力した値に関心がない場合は、`setOmitted()` を使用して `$form->getValues()` メソッドの結果またはハンドラに渡されるデータから省略できます。これは、さまざまな確認用パスワード、アンチスパム要素などに役立ちます。 + +```php +$form->addPassword('passwordVerify', '確認用パスワード:') + ->setRequired('確認のため、もう一度パスワードを入力してください') + ->addRule($form::Equal, 'パスワードが一致しません', $form['password']) + ->setOmitted(); +``` + + +要素の無効化 +====== + +要素は `setDisabled()` で無効にできます。このような要素はユーザーが編集できません。 + +```php +$form->addText('username', 'ユーザー名:') + ->setDisabled(); +``` + +無効化された要素はブラウザによってサーバーに送信されないため、`$form->getValues()` 関数によって返されるデータには含まれません。ただし、`setOmitted(false)` を設定すると、Netteはこれらのデータにデフォルト値を含めます。 + +`setDisabled()` を呼び出すと、セキュリティ上の理由から**要素の値が削除されます**。デフォルト値を設定する場合は、無効化した後に行う必要があります: + +```php +$form->addText('username', 'ユーザー名:') + ->setDisabled() + ->setDefaultValue($userName); +``` + +無効化された要素の代替として、HTML属性 `readonly` を持つ要素があります。これはブラウザによってサーバーに送信されます。要素は読み取り専用ですが、その値は依然として攻撃者によって変更または偽造される可能性があることを**認識することが重要です**。 + + +カスタム要素 +====== + +組み込みの幅広いフォーム要素に加えて、次の方法でフォームにカスタム要素を追加できます: + +```php +$form->addComponent(new DateInput('日付:'), 'date'); +// 代替構文: $form['date'] = new DateInput('日付:'); +``` + +.[note] +フォームは[Container |component-model:#Container]クラスの子であり、個々の要素は[Component |component-model:#Component]の子です。 + +カスタム要素を追加するための新しいフォームメソッド(例:`$form->addZip()`)を定義する方法があります。これは拡張メソッドと呼ばれます。欠点は、エディタでの補完が機能しないことです。 + +```php +use Nette\Forms\Container; + +// メソッド addZip(string $name, ?string $label = null) を追加します +Container::extensionMethod('addZip', function (Container $form, string $name, ?string $label = null) { + return $form->addText($name, $label) + ->addRule($form::Pattern, '少なくとも5桁の数字', '[0-9]{5}'); +}); + +// 使用法 +$form->addZip('zip', '郵便番号:'); +``` + + +低レベル要素 +====== + +テンプレートにのみ記述し、`$form->addXyz()` メソッドのいずれかでフォームに追加しない要素を使用することもできます。たとえば、データベースからレコードを出力し、事前にいくつあり、どのようなIDを持つかわからず、各行にチェックボックスまたはラジオボタンを表示したい場合、テンプレートでコーディングするだけで十分です: + +```latte +{foreach $items as $item} + <p><input type=checkbox name="sel[]" value={$item->id}> {$item->name}</p> +{/foreach} +``` + +そして、送信後に値を確認します: + +```php +$data = $form->getHttpData($form::DataText, 'sel[]'); +$data = $form->getHttpData($form::DataText | $form::DataKeys, 'sel[]'); +``` + +ここで、最初のパラメータは要素のタイプ(`type=file` の場合は `DataFile`、`text`、`password`、`email` などの一行入力の場合は `DataLine`、その他すべての場合は `DataText`)であり、2番目のパラメータ `sel[]` はHTML属性 `name` に対応します。要素のタイプは、要素のキーを保持する `DataKeys` 値と組み合わせることができます。これは、特に `select`、`radioList`、`checkboxList` に役立ちます。 + +重要なのは、`getHttpData()` がサニタイズされた値を返すことです。この場合、攻撃者がサーバーに何を送信しようとしても、常に有効なUTF-8文字列の配列になります。これは、`$_POST` または `$_GET` を直接操作するのと似ていますが、重要な違いは、常にクリーンなデータを返すことです。これは、標準のNetteフォーム要素で慣れているとおりです。 diff --git a/forms/ja/in-presenter.texy b/forms/ja/in-presenter.texy new file mode 100644 index 0000000000..77d943bd88 --- /dev/null +++ b/forms/ja/in-presenter.texy @@ -0,0 +1,431 @@ +Presenter内のフォーム +*************** + +.[perex] +Nette Formsは、Webフォームの作成と処理を大幅に簡素化します。この章では、Presenter内でフォームを使用する方法を学びます。 + +フレームワークの残りの部分なしで完全にスタンドアロンで使用する方法に興味がある場合は、[スタンドアロンでの使用|standalone]のガイドが用意されています。 + + +最初のフォーム +======= + +簡単な登録フォームを作成してみましょう。そのコードは次のようになります: + +```php +use Nette\Application\UI\Form; + +$form = new Form; +$form->addText('name', '名前:'); +$form->addPassword('password', 'パスワード:'); +$form->addSubmit('send', '登録'); +$form->onSuccess[] = [$this, 'formSucceeded']; +``` + +そして、ブラウザでは次のように表示されます: + +[* form-cs.webp *] + +Presenter内のフォームは `Nette\Application\UI\Form` クラスのオブジェクトであり、その前身である `Nette\Forms\Form` はスタンドアロンでの使用を目的としています。名前、パスワード、送信ボタンといういわゆる要素を追加しました。そして最後に、`$form->onSuccess` の行は、送信され、正常に検証された後、`$this->formSucceeded()` メソッドを呼び出す必要があることを示しています。 + +Presenterの観点から見ると、フォームは通常のコンポーネントです。したがって、コンポーネントとして扱われ、[ファクトリメソッド |application:components#ファクトリメソッド]を使用してPresenterに組み込まれます。次のようになります: + +```php .{file:app/Presentation/Home/HomePresenter.php} +use Nette; +use Nette\Application\UI\Form; + +class HomePresenter extends Nette\Application\UI\Presenter +{ + protected function createComponentRegistrationForm(): Form + { + $form = new Form; + $form->addText('name', '名前:'); + $form->addPassword('password', 'パスワード:'); + $form->addSubmit('send', '登録'); + $form->onSuccess[] = [$this, 'formSucceeded']; + return $form; + } + + public function formSucceeded(Form $form, $data): void + { + // ここでフォームから送信されたデータを処理します + // $data->name には名前が含まれます + // $data->password にはパスワードが含まれます + $this->flashMessage('正常に登録されました。'); + $this->redirect('Home:'); + } +} +``` + +そして、テンプレートでは `{control}` タグを使用してフォームをレンダリングします: + +```latte .{file:app/Presentation/Home/default.latte} +<h1>登録</h1> + +{control registrationForm} +``` + +そして、それがすべてです :-) 機能的で完全に[保護された |#脆弱性からの保護]フォームがあります。 + +そして今、あなたはおそらくそれが速すぎたと思って、`formSucceeded()` メソッドがどのように呼び出されるのか、そしてそれが受け取るパラメータは何なのか疑問に思っているでしょう。確かに、あなたは正しいです、これは説明に値します。 + +Netteは、[ハリウッドスタイル |application:components#ハリウッドスタイル]と呼ばれる新鮮なメカニズムを導入しています。開発者として常に何かが起こったかどうか(「フォームは送信されましたか?」、「有効に送信されましたか?」、「改ざんされませんでしたか?」)を尋ねる代わりに、フレームワークに「フォームが有効に記入されたら、このメソッドを呼び出して」と言い、残りの作業を任せます。JavaScriptでプログラミングしている場合、このプログラミングスタイルには精通しています。特定の[イベント |nette:glossary#イベント]が発生したときに呼び出される関数を記述します。そして、言語はそれらに適切な引数を渡します。 + +上記Presenterコードもまさにこのように構築されています。`$form->onSuccess` 配列は、フォームが送信され、正しく記入された(つまり、有効である)瞬間にNetteが呼び出すPHPコールバックのリストを表します。[presenterのライフサイクル |application:presenters#Presenterのライフサイクル]のコンテキストでは、これはいわゆるシグナルであり、`action*` メソッドの後、`render*` メソッドの前に呼び出されます。そして、各コールバックに最初のパラメータとしてフォーム自体を渡し、2番目のパラメータとして送信されたデータを[ArrayHash |utils:arrays#ArrayHash]オブジェクト(または指定されたクラス)の形式で渡します。フォームオブジェクトが必要ない場合は、最初のパラメータを省略できます。そして、2番目のパラメータはより賢くなることができますが、それについては[後で |#クラスへのマッピング]説明します。 + +`$data` オブジェクトには、ユーザーが入力したデータを含む `name` および `password` プロパティが含まれています。通常、データはさらに処理するために直接送信されます。たとえば、データベースへの挿入などです。ただし、処理中にエラーが発生する可能性があります。たとえば、ユーザー名がすでに使用されている場合などです。その場合、`addError()` を使用してエラーをフォームに戻し、エラーメッセージとともに再度レンダリングさせます。 + +```php +$form->addError('申し訳ありませんが、そのユーザー名は既に使用されています。'); +``` + +`onSuccess` に加えて、`onSubmit` もあります:コールバックは、フォームが送信されたときに常に呼び出されます。正しく記入されていない場合でも。さらに `onError`:コールバックは、送信が有効でない場合にのみ呼び出されます。`onSuccess` または `onSubmit` で `addError()` を使用してフォームを無効にした場合でも呼び出されます。 + +フォームを処理した後、次のページにリダイレクトします。これにより、*更新*ボタン、*戻る*ボタン、またはブラウザ履歴の移動によってフォームが意図せず再送信されるのを防ぎます。 + +他の[フォーム要素|controls]も追加してみてください。 + + +要素へのアクセス +======== + +フォームはPresenterのコンポーネントであり、この場合は `registrationForm` という名前です(ファクトリメソッド `createComponentRegistrationForm` の名前に基づく)。したがって、Presenter内のどこからでもフォームにアクセスできます: + +```php +$form = $this->getComponent('registrationForm'); +// 代替構文: $form = $this['registrationForm']; +``` + +フォームの個々の要素もコンポーネントであるため、同じ方法でアクセスできます: + +```php +$input = $form->getComponent('name'); // または $input = $form['name']; +$button = $form->getComponent('send'); // または $button = $form['send']; +``` + +要素はunsetを使用して削除されます: + +```php +unset($form['name']); +``` + + +検証ルール +===== + +*有効*という言葉が出ましたが、フォームにはまだ検証ルールがありません。それを修正しましょう。 + +名前は必須なので、`setRequired()` メソッドでマークします。その引数は、ユーザーが名前を入力しなかった場合に表示されるエラーメッセージのテキストです。引数を指定しない場合は、デフォルトのエラーメッセージが使用されます。 + +```php +$form->addText('name', '名前:') + ->setRequired('名前を入力してください'); +``` + +名前を入力せずにフォームを送信してみてください。エラーメッセージが表示され、フィールドに入力するまでブラウザまたはサーバーがそれを拒否することがわかります。 + +同時に、フィールドにスペースだけを入力してもシステムをだますことはできません。いいえ。Netteは左右の空白を自動的に削除します。試してみてください。これは、すべての一行入力で常に行うべきことですが、忘れられがちです。Netteはそれを自動的に行います。(フォームをだまして、名前として複数行の文字列を送信してみてください。ここでもNetteはだまされず、改行をスペースに変更します。) + +フォームは常にサーバー側で検証されますが、JavaScript検証も生成されます。これは瞬時に実行され、ユーザーはフォームをサーバーに送信することなく、すぐにエラーを知ることができます。これは `netteForms.js` スクリプトが担当します。 レイアウトテンプレートに挿入します: + +```latte +<script src="https://unpkg.com/nette-forms@3"></script> +``` + +フォームのあるページのソースコードを見ると、Netteが必須要素をHTML要素の `required` 属性としてマークしていることに気付くかもしれません(またはCSSクラス `required` を持つ要素に挿入)。テンプレートに次のスタイルシートを追加してみてください。「名前」ラベルが赤くなります。これにより、必須要素をユーザーにエレガントに示すことができます: + +```latte +<style> +.required label { color: maroon } +</style> +``` + +他の検証ルールは `addRule()` メソッドで追加します。最初のパラメータはルール、2番目は再びエラーメッセージのテキストであり、検証ルールの引数が続く場合があります。これはどういう意味ですか? + +フォームに新しいオプションのフィールド「年齢」を追加します。これは整数(`addInteger()`)であり、さらに許容範囲(`$form::Range`)内である必要があります。そして、ここで `addRule()` メソッドの3番目のパラメータを使用します。これにより、必要な範囲をペア `[from, to]` としてバリデータに渡します: + +```php +$form->addInteger('age', '年齢:') + ->addRule($form::Range, '年齢は18歳から120歳の間である必要があります', [18, 120]); +``` + +.[tip] +ユーザーがフィールドを入力しない場合、要素はオプションであるため、検証ルールはチェックされません。 + +ここでは、小さなリファクタリングの余地があります。エラーメッセージと3番目のパラメータで数値が重複して記載されており、これは理想的ではありません。[多言語フォーム |rendering#翻訳]を作成し、数値を含むメッセージが複数の言語に翻訳された場合、値の変更が困難になります。このため、プレースホルダー `%d` を使用でき、Netteが値を補完します: + +```php + ->addRule($form::Range, '年齢は %d 歳から %d 歳の間である必要があります', [18, 120]); +``` + +`password` 要素に戻りましょう。これも必須にし、パスワードの最小長(`Form::MinLength`)も検証します。ここでもプレースホルダーを使用します: + +```php +$form->addPassword('password', 'パスワード:') + ->setRequired('パスワードを選択してください') + ->addRule($form::MinLength, 'パスワードは少なくとも %d 文字必要です', 8); +``` + +フォームにフィールド `passwordVerify` を追加します。ここでユーザーは確認のためにパスワードをもう一度入力します。検証ルールを使用して、両方のパスワードが同じかどうかを確認します(`Form::Equal`)。そして、パラメータとして、[角括弧 |#要素へのアクセス]を使用して最初のパスワードへの参照を与えます: + +```php +$form->addPassword('passwordVerify', '確認用パスワード:') + ->setRequired('確認のため、もう一度パスワードを入力してください') + ->addRule($form::Equal, 'パスワードが一致しません', $form['password']) + ->setOmitted(); +``` + +`setOmitted()` を使用して、実際には値に関心がなく、検証目的でのみ存在する要素をマークしました。値は `$data` に渡されません。 + +これで、PHPとJavaScriptの両方で検証を備えた完全に機能するフォームが完成しました。Netteの検証機能ははるかに広範であり、条件を作成したり、それらに基づいてページのパーツを表示および非表示にしたりできます。すべては[フォームの検証|validation]に関する章で学びます。 + + +デフォルト値 +====== + +フォーム要素には通常、デフォルト値を設定します: + +```php +$form->addEmail('email', 'メールアドレス') + ->setDefaultValue($lastUsedEmail); +``` + +すべての要素に同時にデフォルト値を設定すると便利なことがよくあります。たとえば、フォームがレコードの編集に使用される場合などです。データベースからレコードを読み取り、デフォルト値を設定します: + +```php +//$row = ['name' => 'John', 'age' => '33', /* ... */]; +$form->setDefaults($row); +``` + +要素を定義した後に `setDefaults()` を呼び出します。 + + +フォームのレンダリング +=========== + +デフォルトでは、フォームはテーブルとしてレンダリングされます。個々の要素は基本的なアクセシビリティルールを満たしています - すべてのラベルは `<label>` として記述され、対応するフォーム要素に関連付けられています。ラベルをクリックすると、カーソルが自動的にフォームフィールドに表示されます。 + +各要素に任意のHTML属性を設定できます。たとえば、プレースホルダーを追加します: + +```php +$form->addInteger('age', '年齢:') + ->setHtmlAttribute('placeholder', '年齢を入力してください'); +``` + +フォームをレンダリングする方法は本当にたくさんあるので、[レンダリングに関する別の章|rendering]があります。 + + +クラスへのマッピング +========== + +2番目のパラメータ `$data` で送信されたデータを `ArrayHash` オブジェクトとして受け取る `formSucceeded()` メソッドに戻りましょう。これは `stdClass` のようなジェネリッククラスであるため、エディタでのプロパティの補完や静的コード分析など、特定の快適さが欠けています。これは、各フォームに特定のクラスを持たせることで解決できます。そのプロパティは個々の要素を表します。例: + +```php +class RegistrationFormData +{ + public string $name; + public ?int $age; + public string $password; +} +``` + +または、コンストラクタを使用することもできます: + +```php +class RegistrationFormData +{ + public function __construct( + public string $name, + public ?int $age, + public string $password, + ) { + } +} +``` + +データクラスのプロパティはenumにすることもでき、自動的にマッピングされます。 .{data-version:3.2.4} + +Netteにこのクラスのオブジェクトとしてデータを返すように指示するにはどうすればよいですか?思ったより簡単です。ハンドラメソッドの `$data` パラメータの型としてクラスを指定するだけです: + +```php +public function formSucceeded(Form $form, RegistrationFormData $data): void +{ + // $data は RegistrationFormData のインスタンスです + $name = $data->name; + // ... +} +``` + +型として `array` を指定することもでき、その場合、データは配列として渡されます。 + +同様に、`getValues()` 関数を使用することもできます。これには、クラス名またはハイドレートするオブジェクトをパラメータとして渡します: + +```php +$data = $form->getValues(RegistrationFormData::class); +$name = $data->name; +``` + +フォームがコンテナで構成される多層構造を形成する場合、それぞれに個別のクラスを作成します: + +```php +$form = new Form; +$person = $form->addContainer('person'); +$person->addText('firstName'); +/* ... */ + +class PersonFormData +{ + public string $firstName; + public string $lastName; +} + +class RegistrationFormData +{ + public PersonFormData $person; + public ?int $age; + public string $password; +} +``` + +マッピングは、プロパティ `$person` の型から、コンテナを `PersonFormData` クラスにマッピングする必要があることを認識します。プロパティにコンテナの配列が含まれている場合は、型 `array` を指定し、マッピングするクラスをコンテナに直接渡します: + +```php +$person->setMappedType(PersonFormData::class); +``` + +フォームのデータクラスの設計は、`Nette\Forms\Blueprint::dataClass($form)` メソッドを使用して生成できます。これはブラウザページに出力されます。コードをクリックして選択し、プロジェクトにコピーするだけです。 .{data-version:3.1.15} + + +複数のボタン +====== + +フォームに複数のボタンがある場合、通常、どちらが押されたかを区別する必要があります。各ボタンに独自のハンドラ関数を作成できます。[イベント |nette:glossary#イベント] `onClick` のハンドラとして設定します: + +```php +$form->addSubmit('save', '保存') + ->onClick[] = [$this, 'saveButtonPressed']; + +$form->addSubmit('delete', '削除') + ->onClick[] = [$this, 'deleteButtonPressed']; +``` + +これらのハンドラは、`onSuccess` イベントの場合と同様に、有効に記入されたフォームの場合にのみ呼び出されます。違いは、最初のパラメータとしてフォームの代わりに送信ボタンを渡すことができる点です。指定した型によって異なります: + +```php +public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) +{ + $form = $button->getForm(); + // ... +} +``` + +フォームが<kbd>Enter</kbd>キーで送信された場合、最初のボタンで送信されたかのように扱われます。 + + +onAnchorイベント +============ + +ファクトリメソッド(例:`createComponentRegistrationForm`)でフォームを組み立てるとき、フォームはまだ送信されたかどうか、またはどのデータで送信されたかを知りません。しかし、送信された値を知る必要がある場合があります。たとえば、フォームのさらなる形状がそれらに依存する場合や、依存セレクトボックスなどに必要な場合などです。 + +したがって、フォームを組み立てるコードの一部は、いわゆるアンカーされたとき、つまりPresenterに接続され、送信されたデータを知っているときにのみ呼び出すことができます。そのようなコードを `$onAnchor` 配列に渡します: + +```php +$country = $form->addSelect('country', '国:', $this->model->getCountries()); +$city = $form->addSelect('city', '市:'); + +$form->onAnchor[] = function () use ($country, $city) { + // この関数は、フォームが送信されたかどうか、およびどのデータで送信されたかを知っているときにのみ呼び出されます + // したがって、getValue() メソッドを使用できます + $val = $country->getValue(); + $city->setItems($val ? $this->model->getCities($val) : []); +}; +``` + + +脆弱性からの保護 +======== + +Nette Frameworkはセキュリティを非常に重視しており、したがってフォームの適切な保護に細心の注意を払っています。これは完全に透過的に行われ、手動で何も設定する必要はありません。 + +フォームを[クロスサイトスクリプティング (XSS) |nette:glossary#Cross-Site Scripting XSS]および[クロスサイトリクエストフォージェリ (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF]攻撃から保護することに加えて、多くの小さなセキュリティ対策を実行するため、もはや考える必要はありません。 + +たとえば、入力からすべての制御文字を除去し、UTF-8エンコーディングの有効性を検証するため、フォームからのデータは常にクリーンになります。セレクトボックスとラジオリストでは、選択された項目が実際に提供されたものであり、改ざんされていないことを検証します。一行テキスト入力では、攻撃者がそこに送信した可能性のある改行文字を削除することをすでに述べました。複数行入力では、改行文字を正規化します。などなど。 + +Netteは、多くのプログラマーが存在することさえ知らないセキュリティリスクを処理します。 + +言及されたCSRF攻撃は、攻撃者が被害者をページに誘い込み、被害者のブラウザで被害者がログインしているサーバーへのリクエストを密かに実行し、サーバーがリクエストが被害者自身の意志で実行されたと信じ込ませることにあります。したがって、Netteは異なるドメインからのPOSTフォームの送信を防ぎます。何らかの理由で保護を無効にし、異なるドメインからのフォームの送信を許可したい場合は、次を使用します: + +```php +$form->allowCrossOrigin(); // 注意!保護を無効にします! +``` + +この保護は、`_nss` という名前のSameSite Cookieを使用します。SameSite Cookieによる保護は100%信頼できるとは限らないため、トークンによる保護も有効にすることをお勧めします: + +```php +$form->addProtection(); +``` + +アプリケーション内の機密データを変更するWebサイトの管理部分のフォームをこのように保護することをお勧めします。フレームワークは、セッションに保存される認証トークンを生成および検証することによってCSRF攻撃から防御します。したがって、フォームを表示する前にセッションを開いておく必要があります。Webサイトの管理部分では、通常、ユーザーログインのためにセッションはすでに開始されています。 それ以外の場合は、`Nette\Http\Session::start()` メソッドでセッションを開始します。 + + +複数のPresenterで同じフォームを使用する +======================== + +複数のPresenterで1つのフォームを使用する必要がある場合は、そのためのファクトリを作成し、それをPresenterに渡すことをお勧めします。このようなクラスの適切な場所は、たとえば `app/Forms` ディレクトリです。 + +ファクトリクラスは次のようになります: + +```php +use Nette\Application\UI\Form; + +class SignInFormFactory +{ + public function create(): Form + { + $form = new Form; + $form->addText('name', '名前:'); + $form->addSubmit('send', 'ログイン'); + return $form; + } +} +``` + +Presenterのコンポーネントファクトリメソッドで、クラスにフォームの作成を依頼します: + +```php +public function __construct( + private SignInFormFactory $formFactory, +) { +} + +protected function createComponentSignInForm(): Form +{ + $form = $this->formFactory->create(); + // フォームを変更できます。ここでは、たとえばボタンのキャプションを変更します + $form['send']->setCaption('続行'); + $form->onSuccess[] = [$this, 'signInFormSuceeded']; // そしてハンドラを追加します + return $form; +} +``` + +フォーム処理ハンドラは、ファクトリから提供することもできます: + +```php +use Nette\Application\UI\Form; + +class SignInFormFactory +{ + public function create(): Form + { + $form = new Form; + $form->addText('name', '名前:'); + $form->addSubmit('send', 'ログイン'); + $form->onSuccess[] = function (Form $form, $data): void { + // ここでフォーム処理を実行します + }; + return $form; + } +} +``` + +これで、Netteのフォームの簡単な紹介が終わりました。[examples|https://github.com/nette/forms/tree/master/examples]ディレクトリを調べて、さらなるインスピレーションを見つけてみてください。 diff --git a/forms/ja/rendering.texy b/forms/ja/rendering.texy new file mode 100644 index 0000000000..fee220e501 --- /dev/null +++ b/forms/ja/rendering.texy @@ -0,0 +1,592 @@ +フォームのレンダリング +*********** + +フォームの外観は非常に多様です。実際には、2つの極端なケースに遭遇する可能性があります。一方では、アプリケーションで視覚的に互いに似ている多くのフォームをレンダリングする必要があり、`$form->render()` を使ってテンプレートなしで簡単にレンダリングできる点が重宝されます。これは通常、管理インターフェースの場合です。 + +一方、それぞれがオリジナルである多様なフォームがあります。それらの外観は、フォームテンプレート内でHTML言語を使って記述するのが最適です。そしてもちろん、両方の極端なケースに加えて、その中間のどこかに位置する多くのフォームに遭遇します。 + + +Latteによるレンダリング +============== + +[テンプレートシステムLatte|latte:]は、フォームとそのコントロールのレンダリングを大幅に簡素化します。まず、個々のコントロールを手動でレンダリングしてコードを完全に制御する方法を示します。後で、そのようなレンダリングを[自動化する |#自動レンダリング]方法を示します。 + +フォームのLatteテンプレートの設計案は、`Nette\Forms\Blueprint::latte($form)` メソッドを使って生成させることができ、これはブラウザページに出力されます。コードをクリックして選択し、プロジェクトにコピーするだけです。 .{data-version:3.1.15} + + +`{control}` +----------- + +フォームをレンダリングする最も簡単な方法は、テンプレートに次のように記述することです: + +```latte +{control signInForm} +``` + +このようにレンダリングされたフォームの外観は、[#Renderer]と[個々のコントロール |#HTML属性]の設定によって影響を受ける可能性があります。 + + +`n:name` +-------- + +PHPコードでのフォーム定義は、HTMLコードと非常に簡単に連携できます。`n:name` 属性を追加するだけです。とても簡単です! + +```php +protected function createComponentSignInForm(): Form +{ + $form = new Form; + $form->addText('username')->setRequired(); + $form->addPassword('password')->setRequired(); + $form->addSubmit('send'); + return $form; +} +``` + +```latte +<form n:name=signInForm class=form> + <div> + <label n:name=username>Username: <input n:name=username size=20 autofocus></label> + </div> + <div> + <label n:name=password>Password: <input n:name=password></label> + </div> + <div> + <input n:name=send class="btn btn-default"> + </div> +</form> +``` + +結果となるHTMLコードの形は、完全にあなたのコントロール下にあります。`n:name` 属性を `<select>`、`<button>`、または `<textarea>` 要素で使用すると、それらの内部コンテンツが自動的に補完されます。`<form n:name>` タグは、レンダリングされるフォームのオブジェクトを持つローカル変数 `$form` も作成し、閉じタグ `</form>` はレンダリングされていないすべての隠しコントロールをレンダリングします(`{form} ... {/form}` にも同じことが当てはまります)。 + +ただし、発生しうるエラーメッセージのレンダリングを忘れてはいけません。`addError()` メソッドによって個々のコントロールに追加されたもの(`{inputError}` を使用)と、フォームに直接追加されたもの(`$form->getOwnErrors()` によって返される)の両方です: + +```latte +<form n:name=signInForm class=form> + <ul class="errors" n:ifcontent> + <li n:foreach="$form->getOwnErrors() as $error">{$error}</li> + </ul> + + <div> + <label n:name=username>Username: <input n:name=username size=20 autofocus></label> + <span class=error n:ifcontent>{inputError username}</span> + </div> + <div> + <label n:name=password>Password: <input n:name=password></label> + <span class=error n:ifcontent>{inputError password}</span> + </div> + <div> + <input n:name=send class="btn btn-default"> + </div> +</form> +``` + +RadioListやCheckboxListなどのより複雑なフォームコントロールは、個々の項目ごとにこのようにレンダリングできます: + +```latte +{foreach $form[gender]->getItems() as $key => $label} + <label n:name="gender:$key"><input n:name="gender:$key"> {$label}</label> +{/foreach} +``` + + +`{label}` `{input}` +------------------- + +各コントロールについて、テンプレートでどのHTML要素、`<input>`、`<textarea>` など、を使用するか考えたくないですか?解決策は、汎用タグ `{input}` です: + +```latte +<form n:name=signInForm class=form> + <ul class="errors" n:ifcontent> + <li n:foreach="$form->getOwnErrors() as $error">{$error}</li> + </ul> + + <div> + {label username}Username: {input username, size: 20, autofocus: true}{/label} + {inputError username} + </div> + <div> + {label password}Password: {input password}{/label} + {inputError password} + </div> + <div> + {input send, class: "btn btn-default"} + </div> +</form> +``` + +フォームがトランスレータを使用する場合、`{label}` タグ内のテキストは翻訳されます。 + +この場合でも、RadioListやCheckboxListなどのより複雑なフォームコントロールは、個々の項目ごとにレンダリングできます: + +```latte +{foreach $form[gender]->items as $key => $label} + {label gender:$key}{input gender:$key} {$label}{/label} +{/foreach} +``` + +Checkboxコントロールの `<input>` 自体をレンダリングするには、`{input myCheckbox:}` を使用します。この場合、HTML属性は常にカンマで区切ります `{input myCheckbox:, class: required}`。 + + +`{inputError}` +-------------- + +フォームコントロールにエラーメッセージがある場合、それを表示します。メッセージは通常、スタイリングのためにHTML要素でラップされます。 メッセージがない場合に空の要素のレンダリングを防ぐには、`n:ifcontent` をエレガントに使用できます: + +```latte +<span class=error n:ifcontent>{inputError $input}</span> +``` + +エラーの存在は `hasErrors()` メソッドで確認でき、それに応じて親要素にクラスを設定できます: + +```latte +<div n:class="$form[username]->hasErrors() ? 'error'"> + {input username} + {inputError username} +</div> +``` + + +`{form}` +-------- + +`{form signInForm}...{/form}` タグは `<form n:name="signInForm">...</form>` の代替です。 + + +自動レンダリング +-------- + +`{input}` および `{label}` タグのおかげで、任意のフォームの汎用テンプレートを簡単に作成できます。それはすべてのコントロールを反復処理してレンダリングしますが、`</form>` タグでフォームが終了するときに自動的にレンダリングされる隠しコントロールは除きます。レンダリングされるフォームの名前は、変数 `$form` で期待されます。 + +```latte +<form n:name=$form class=form> + <ul class="errors" n:ifcontent> + <li n:foreach="$form->getOwnErrors() as $error">{$error}</li> + </ul> + + <div n:foreach="$form->getControls() as $input" + n:if="$input->getOption(type) !== hidden"> + {label $input /} + {input $input} + {inputError $input} + </div> +</form> +``` + +使用される自己終了ペアタグ `{label .../}` は、PHPコードのフォーム定義から来るラベルを表示します。 + +この汎用テンプレートを、たとえば `basic-form.latte` ファイルに保存し、フォームをレンダリングするには、それをインクルードしてフォームの名前(またはインスタンス)を `$form` パラメータに渡すだけです: + +```latte +{include basic-form.latte, form: signInForm} +``` + +特定のフォームをレンダリングする際にその外観に手を加え、例えば一つのコントロールを異なる方法でレンダリングしたい場合は、最も簡単な方法は、後で上書きできるブロックをテンプレートに事前に準備することです。 ブロックは[動的な名前 |latte:template-inheritance#動的ブロック名]を持つこともできるため、レンダリングされるコントロールの名前を挿入することもできます。たとえば: + +```latte +... + {label $input /} + {block "input-{$input->name}"}{input $input}{/block} +... +``` + +例えば `username` コントロールの場合、ブロック `input-username` が作成され、[{embed} |latte:template-inheritance#Unit inheritance]タグを使用して簡単に上書きできます: + +```latte +{embed basic-form.latte, form: signInForm} + {block input-username} + <span class=important> + {include parent} + </span> + {/block} +{/embed} +``` + +または、`basic-form.latte` テンプレートの全内容を、`$form` パラメータを含めてブロックとして[定義する |latte:template-inheritance#Definitions]こともできます: + +```latte +{define basic-form, $form} + <form n:name=$form class=form> + ... + </form> +{/define} +``` + +これにより、その呼び出しがわずかに簡単になります: + +```latte +{embed basic-form, signInForm} + ... +{/embed} +``` + +ブロックは、レイアウトテンプレートの冒頭の1か所でインポートするだけで十分です: + +```latte +{import basic-form.latte} +``` + + +特殊なケース +------ + +HTMLタグ `<form>` なしでフォームの内部部分のみをレンダリングする必要がある場合、たとえばスニペットを送信する場合、`n:tag-if` 属性を使用してそれらを非表示にします: + +```latte +<form n:name=signInForm n:tag-if=false> + <div> + <label n:name=username>Username: <input n:name=username></label> + {inputError username} + </div> +</form> +``` + +フォームコンテナ内のコントロールのレンダリングは、`{formContainer}` タグが役立ちます。 + +```latte +<p>どのニュースを受け取りたいですか:</p> + +{formContainer emailNews} +<ul> + <li>{input sport} {label sport /}</li> + <li>{input science} {label science /}</li> +</ul> +{/formContainer} +``` + + +Latteなしでのレンダリング +=============== + +フォームをレンダリングする最も簡単な方法は、次を呼び出すことです: + +```php +$form->render(); +``` + +このようにレンダリングされたフォームの外観は、[#Renderer]と[個々のコントロール |#HTML属性]の設定によって影響を受ける可能性があります。 + + +手動レンダリング +-------- + +各フォームコントロールには、フォームフィールドとラベルのHTMLコードを生成するメソッドがあります。それらは文字列または[Nette\Utils\Html|utils:html-elements]オブジェクトとして返すことができます: + +- `getControl(): Html|string` コントロールのHTMLコードを返します +- `getLabel($caption = null): Html|string|null` ラベルが存在する場合、そのHTMLコードを返します + +したがって、フォームは個々のコントロールごとにレンダリングできます: + +```php +<?php $form->render('begin') ?> +<?php $form->render('errors') ?> + +<div> + <?= $form['name']->getLabel() ?> + <?= $form['name']->getControl() ?> + <span class=error><?= htmlspecialchars($form['name']->getError()) ?></span> +</div> + +<div> + <?= $form['age']->getLabel() ?> + <?= $form['age']->getControl() ?> + <span class=error><?= htmlspecialchars($form['age']->getError()) ?></span> +</div> + +// ... + +<?php $form->render('end') ?> +``` + +一部のコントロールでは `getControl()` が単一のHTML要素(例:`<input>`、`<select>` など)を返しますが、他のコントロール(CheckboxList、RadioList)ではHTMLコード全体を返します。 その場合、各項目について個々の入力とラベルを生成するメソッドを使用できます: + +- `getControlPart($key = null): ?Html` 1つの項目のHTMLコードを返します +- `getLabelPart($key = null): ?Html` 1つの項目のラベルのHTMLコードを返します + +.[note] +これらのメソッドは歴史的な理由から `get` プレフィックスを持っていますが、呼び出すたびに新しい `Html` 要素を作成して返すため、`generate` の方が適切です。 + + +Renderer +======== + +これはフォームのレンダリングを担当するオブジェクトです。`$form->setRenderer` メソッドで設定できます。`$form->render()` メソッドが呼び出されると、制御が渡されます。 + +カスタムレンダラーを設定しない場合、デフォルトのレンダラー [api:Nette\Forms\Rendering\DefaultFormRenderer] が使用されます。これはフォームコントロールをHTMLテーブルとしてレンダリングします。出力は次のようになります: + +```latte +<table> +<tr class="required"> + <th><label class="required" for="frm-name">名前:</label></th> + + <td><input type="text" class="text" name="name" id="frm-name" required value=""></td> +</tr> + +<tr class="required"> + <th><label class="required" for="frm-age">年齢:</label></th> + + <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> +</tr> + +<tr> + <th><label>性別:</label></th> + ... +``` + +フォームの骨格にテーブルを使用するかどうかは議論の余地があり、多くのWebデザイナーは異なるマークアップを好みます。たとえば、定義リストです。したがって、`DefaultFormRenderer` を再構成して、フォームをリストとしてレンダリングします。設定は [$wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers] 配列を編集することによって行われます。最初のインデックスは常に領域を表し、2番目はその属性を表します。個々の領域は画像で示されています: + +[* defaultformrenderer.webp *] + +デフォルトでは、コントロールグループ `controls` はテーブル `<table>` でラップされ、各 `pair` はテーブル行 `<tr>` を表し、`label` と `control` のペアはセル `<th>` と `<td>` です。次に、ラッピング要素を変更します。`controls` 領域を `<dl>` コンテナに挿入し、`pair` 領域をコンテナなしのままにし、`label` を `<dt>` に挿入し、最後に `control` を `<dd>` タグでラップします: + +```php +$renderer = $form->getRenderer(); +$renderer->wrappers['controls']['container'] = 'dl'; +$renderer->wrappers['pair']['container'] = null; +$renderer->wrappers['label']['container'] = 'dt'; +$renderer->wrappers['control']['container'] = 'dd'; + +$form->render(); +``` + +結果は次のHTMLコードです: + +```latte +<dl> + <dt><label class="required" for="frm-name">名前:</label></dt> + + <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> + + + <dt><label class="required" for="frm-age">年齢:</label></dt> + + <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> + + + <dt><label>性別:</label></dt> + ... +</dl> +``` + +wrappers配列では、他の多くの属性に影響を与えることができます: + +- 個々のフォームコントロールタイプにCSSクラスを追加する +- 奇数行と偶数行をCSSクラスで区別する +- 必須項目とオプション項目を視覚的に区別する +- エラーメッセージをコントロールのすぐ隣に表示するか、フォームの上に表示するかを決定する + + +Options +------- + +Rendererの動作は、個々のフォームコントロールに *options* を設定することによっても制御できます。これにより、入力フィールドの隣に表示される説明を設定できます: + +```php +$form->addText('phone', '番号:') + ->setOption('description', 'この番号は非公開のままになります'); +``` + +HTMLコンテンツを含めたい場合は、[Html |utils:html-elements] クラスを使用します: + +```php +use Nette\Utils\Html; + +$form->addText('phone', '番号:') + ->setOption('description', Html::el('p') + ->setHtml('<a href="...">お客様の番号の保管条件</a>') + ); +``` + +.[tip] +Htmlコントロールはラベルの代わりに使用することもできます:`$form->addCheckbox('conditions', $label)`。 + + +コントロールのグループ化 +------------ + +Rendererを使用すると、コントロールを視覚的なグループ(fieldset)にグループ化できます: + +```php +$form->addGroup('個人データ'); +``` + +新しいグループを作成すると、それがアクティブになり、新しく追加された各コントロールもそれに追加されます。したがって、フォームはこのように構築できます: + +```php +$form = new Form; +$form->addGroup('個人データ'); +$form->addText('name', 'あなたの名前:'); +$form->addInteger('age', 'あなたの年齢:'); +$form->addEmail('email', 'メールアドレス:'); + +$form->addGroup('配送先住所'); +$form->addCheckbox('send', '住所に配送する'); +$form->addText('street', '通り:'); +$form->addText('city', '市:'); +$form->addSelect('country', '国:', $countries); +``` + +Rendererは最初にグループをレンダリングし、次にどのグループにも属さないコントロールをレンダリングします。 + + +Bootstrapのサポート +-------------- + +[例 |https://github.com/nette/forms/tree/master/examples]では、[Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58]、[Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58]、[Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php] 用にレンダラーを設定する方法の例を見つけることができます。 + + +HTML属性 +====== + +フォームコントロールの任意のHTML属性を設定するには、`setHtmlAttribute(string $name, $value = true)` メソッドを使用します: + +```php +$form->addInteger('number', '番号:') + ->setHtmlAttribute('class', 'big-number'); + +$form->addSelect('rank', '並び替え:', ['価格', '名前']) + ->setHtmlAttribute('onchange', 'submit()'); // 変更時に送信 + + +// <form> 自体の属性を設定する場合 +$form->setHtmlAttribute('id', 'myForm'); +``` + +コントロールのタイプを指定します: + +```php +$form->addText('tel', 'あなたの電話番号:') + ->setHtmlType('tel') + ->setHtmlAttribute('placeholder', '電話番号を入力してください'); +``` + +.[warning] +タイプやその他の属性の設定は、視覚的な目的のみです。入力の正確性の検証はサーバー側で行う必要があり、適切な[フォームコントロール|controls]を選択し、[検証ルール|validation]を指定することで保証されます。 + +ラジオまたはチェックボックスリストの個々の項目に、それぞれ異なる値を持つHTML属性を設定できます。 キーに基づいて値を選択することを保証する `style:` の後のコロンに注意してください: + +```php +$colors = ['r' => '赤', 'g' => '緑', 'b' => '青']; +$styles = ['r' => 'background:red', 'g' => 'background:green']; +$form->addCheckboxList('colors', '色:', $colors) + ->setHtmlAttribute('style:', $styles); +``` + +出力: + +```latte +<label><input type="checkbox" name="colors[]" style="background:red" value="r">赤</label> +<label><input type="checkbox" name="colors[]" style="background:green" value="g">緑</label> +<label><input type="checkbox" name="colors[]" value="b">青</label> +``` + +`readonly` などの論理属性を設定するには、疑問符付きの表記を使用できます: + +```php +$form->addCheckboxList('colors', '色:', $colors) + ->setHtmlAttribute('readonly?', 'r'); // 複数のキーには配列を使用します。例:['r', 'g'] +``` + +出力: + +```latte +<label><input type="checkbox" name="colors[]" readonly value="r">赤</label> +<label><input type="checkbox" name="colors[]" value="g">緑</label> +<label><input type="checkbox" name="colors[]" value="b">青</label> +``` + +セレクトボックスの場合、`setHtmlAttribute()` メソッドは `<select>` 要素の属性を設定します。個々の`<option>` の属性を設定したい場合は、`setOptionAttribute()` メソッドを使用します。上記で述べたコロンと疑問符付きの表記も機能します: + +```php +$form->addSelect('colors', '色:', $colors) + ->setOptionAttribute('style:', $styles); +``` + +出力: + +```latte +<select name="colors"> + <option value="r" style="background:red">赤</option> + <option value="g" style="background:green">緑</option> + <option value="b">青</option> +</select> +``` + + +プロトタイプ +------ + +HTML属性を設定する代替方法は、HTML要素が生成されるテンプレートを変更することです。テンプレートは `Html` オブジェクトであり、`getControlPrototype()` メソッドによって返されます: + +```php +$input = $form->addInteger('number', '番号:'); +$html = $input->getControlPrototype(); // <input> +$html->class('big-number'); // <input class="big-number"> +``` + +この方法で、`getLabelPrototype()` によって返されるラベルのテンプレートも変更できます: + +```php +$html = $input->getLabelPrototype(); // <label> +$html->class('distinctive'); // <label class="distinctive"> +``` + +Checkbox、CheckboxList、RadioListコントロールの場合、コントロール全体をラップする要素のテンプレートに影響を与えることができます。これは `getContainerPrototype()` によって返されます。デフォルトの状態では「空の」要素であるため、何もレンダリングされませんが、名前を設定することでレンダリングされます: + +```php +$input = $form->addCheckbox('send'); +$html = $input->getContainerPrototype(); +$html->setName('div'); // <div> +$html->class('check'); // <div class="check"> +echo $input->getControl(); +// <div class="check"><label><input type="checkbox" name="send"></label></div> +``` + +CheckboxListとRadioListの場合、個々の項目を区切るセパレータのテンプレートにも影響を与えることができます。これは `getSeparatorPrototype()` メソッドによって返されます。デフォルトの状態では `<br>` 要素です。ペア要素に変更すると、区切る代わりに個々の項目をラップします。 さらに、個々の項目のラベルのHTML要素のテンプレートに影響を与えることができます。これは `getItemLabelPrototype()` によって返されます。 + + +翻訳 +========== + +多言語アプリケーションをプログラミングしている場合、おそらくフォームを異なる言語バージョンでレンダリングする必要があります。Nette Frameworkはこの目的のために翻訳用のインターフェース [api:Nette\Localization\Translator] を定義しています。Netteにはデフォルトの実装はありません。[Componette |https://componette.org/search/localization]で見つけることができるいくつかの既製のソリューションから、ニーズに応じて選択できます。それらのドキュメントで、トランスレータの設定方法を学ぶことができます。 + +フォームは、トランスレータを介したテキストの出力をサポートしています。`setTranslator()` メソッドを使用して渡します: + +```php +$form->setTranslator($translator); +``` + +この時点から、すべてのラベルだけでなく、すべてのエラーメッセージやセレクトボックスの項目も別の言語に翻訳されます。 + +個々のフォームコントロールについて、異なるトランスレータを設定したり、`null` 値で翻訳を完全に無効にしたりすることが可能です: + +```php +$form->addSelect('carModel', 'モデル:', $cars) + ->setTranslator(null); +``` + +[検証ルール|validation]の場合、特定のパラメータもトランスレータに渡されます。たとえば、ルールの場合は: + +```php +$form->addPassword('password', 'パスワード:') + ->addRule($form::MinLength, 'パスワードは少なくとも %d 文字必要です', 8); +``` + +トランスレータは次のパラメータで呼び出されます: + +```php +$translator->translate('パスワードは少なくとも %d 文字必要です', 8); +``` + +したがって、数に応じて単語 `文字` の正しい複数形を選択できます。 + + +onRenderイベント +============ + +フォームがレンダリングされる直前に、コードを実行させることができます。たとえば、フォームコントロールに正しい表示のためのHTMLクラスを追加できます。コードを `onRender` 配列に追加します: + +```php +$form->onRender[] = function ($form) { + BootstrapCSS::initialize($form); +}; +``` diff --git a/forms/ja/standalone.texy b/forms/ja/standalone.texy new file mode 100644 index 0000000000..3be4150cfb --- /dev/null +++ b/forms/ja/standalone.texy @@ -0,0 +1,317 @@ +スタンドアロンで使用されるフォーム +***************** + +.[perex] +Nette Forms は、Web フォームの作成と処理を大幅に簡素化します。この章で示すように、フレームワークの残りの部分なしで、アプリケーションで完全に独立して使用できます。 + +ただし、Nette Application と Presenter を使用している場合は、[Presenter での使用 |in-presenter] ガイドが用意されています。 + + +最初のフォーム +======= + +簡単な登録フォームを作成してみましょう。そのコードは次のようになります ("全コード":https://gist.github.com/dg/57878c1a413ae8ef0c1d83f02c43ef3f): + +```php +use Nette\Forms\Form; + +$form = new Form; +$form->addText('name', '名前:'); +$form->addPassword('password', 'パスワード:'); +$form->addSubmit('send', '登録'); +``` + +非常に簡単にレンダリングできます: + +```php +$form->render(); +``` + +そしてブラウザではこのように表示されます: + +[* form-cs.webp *] + +フォームは `Nette\Forms\Form` クラスのオブジェクトです(`Nette\Application\UI\Form` クラスは Presenter で使用されます)。名前、パスワード、送信ボタンというコントロールを追加しました。 + +では、フォームを動作させてみましょう。`$form->isSuccess()` を問い合わせることで、フォームが送信され、有効に記入されたかどうかを確認します。もしそうなら、データを出力します。フォーム定義の後ろに以下を追加します: + +```php +if ($form->isSuccess()) { + echo 'フォームは正しく記入され、送信されました'; + $data = $form->getValues(); + // $data->name には名前が含まれます + // $data->password にはパスワードが含まれます + var_dump($data); +} +``` + +`getValues()` メソッドは、送信されたデータを [ArrayHash |utils:arrays#ArrayHash] オブジェクトの形式で返します。これを変更する方法は[後で |#クラスへのマッピング]示します。オブジェクト `$data` には、ユーザーが入力したデータを含む `name` と `password` キーが含まれています。 + +通常、データはすぐにさらなる処理に送られます。これは、例えばデータベースへの挿入などです。しかし、処理中にエラーが発生する可能性があります。例えば、ユーザー名が既に使用されている場合などです。その場合、`addError()` を使用してエラーをフォームに戻し、エラーメッセージとともに再度レンダリングさせます。 + +```php +$form->addError('申し訳ありませんが、このユーザー名は既に使用されています。'); +``` + +フォームを処理した後、次のページにリダイレクトします。これにより、*更新*ボタン、*戻る*ボタン、またはブラウザの履歴の移動によってフォームが意図せず再送信されるのを防ぎます。 + +フォームはデフォルトで POST メソッドを使用して同じページに送信されます。両方とも変更できます: + +```php +$form->setAction('/submit.php'); +$form->setMethod('GET'); +``` + +そして、これで終わりです :-) 機能的で完全に[安全な |#脆弱性からの保護]フォームができました。 + +他の[フォームコントロール|controls]も追加してみてください。 + + +コントロールへのアクセス +============ + +フォームとその個々のコントロールをコンポーネントと呼びます。これらはコンポーネントツリーを形成し、フォームがルートになります。フォームの個々のコントロールには、次の方法でアクセスできます: + +```php +$input = $form->getComponent('name'); +// 代替構文: $input = $form['name']; + +$button = $form->getComponent('send'); +// 代替構文: $button = $form['send']; +``` + +コントロールは unset を使用して削除されます: + +```php +unset($form['name']); +``` + + +検証ルール +===== + +*有効*という言葉が出てきましたが、フォームにはまだ検証ルールがありません。それを修正しましょう。 + +名前は必須なので、`setRequired()` メソッドでマークします。その引数は、ユーザーが名前を入力しなかった場合に表示されるエラーメッセージのテキストです。引数を指定しない場合は、デフォルトのエラーメッセージが使用されます。 + +```php +$form->addText('name', '名前:') + ->setRequired('名前を入力してください'); +``` + +フォームに名前を入力せずに送信してみてください。エラーメッセージが表示され、フィールドに入力するまでブラウザまたはサーバーが拒否することがわかります。 + +同時に、フィールドにスペースだけを入力してもシステムをだますことはできません。いいえ。Nette は左側と右側のスペースを自動的に削除します。試してみてください。これは、すべての単一行入力で常に行うべきことですが、忘れられがちです。Nette は自動的に行います。(フォームをだまして、名前として複数行の文字列を送信してみてください。ここでも Nette はだまされず、改行をスペースに変更します。) + +フォームは常にサーバー側で検証されますが、JavaScript 検証も生成されます。これは瞬時に実行され、ユーザーはフォームをサーバーに送信することなく、すぐにエラーを知ることができます。これはスクリプト `netteForms.js` が担当します。 ページに挿入してください: + +```latte +<script src="https://unpkg.com/nette-forms@3"></script> +``` + +フォームのあるページのソースコードを見ると、Nette が必須コントロールを CSS クラス `required` を持つ要素に挿入していることに気づくかもしれません。テンプレートに次のスタイルシートを追加してみてください。「名前」ラベルが赤くなります。このようにして、必須コントロールをユーザーにエレガントに示します: + +```latte +<style> +.required label { color: maroon } +</style> +``` + +`addRule()` メソッドを使用して、さらに検証ルールを追加します。最初のパラメータはルール、2番目は再びエラーメッセージのテキストで、検証ルールの引数が続く場合があります。これはどういう意味ですか? + +フォームに新しいオプションのフィールド「年齢」を追加します。これは整数(`addInteger()`)であり、さらに許可された範囲内(`$form::Range`)である必要があります。そして、ここでまさに `addRule()` メソッドの3番目のパラメータを使用します。これにより、バリデーターに必要な範囲をペア `[from, to]` として渡します: + +```php +$form->addInteger('age', '年齢:') + ->addRule($form::Range, '年齢は18歳から120歳の間でなければなりません', [18, 120]); +``` + +.[tip] +ユーザーがフィールドに入力しない場合、コントロールはオプションであるため、検証ルールはチェックされません。 + +ここで、小さなリファクタリングの余地があります。エラーメッセージと3番目のパラメータでは、数値が重複して記載されており、これは理想的ではありません。[多言語フォーム |rendering#翻訳]を作成し、数値を含むメッセージが複数の言語に翻訳された場合、値の変更が困難になります。このため、プレースホルダー `%d` を使用することが可能で、Nette が値を補完します: + +```php + ->addRule($form::Range, '年齢は %d 歳から %d 歳の間でなければなりません', [18, 120]); +``` + +`password` コントロールに戻りましょう。これも必須とし、さらにパスワードの最小長(`$form::MinLength`)を検証します。再びプレースホルダーを使用します: + +```php +$form->addPassword('password', 'パスワード:') + ->setRequired('パスワードを選択してください') + ->addRule($form::MinLength, 'パスワードは少なくとも %d 文字必要です', 8); +``` + +フォームにフィールド `passwordVerify` を追加します。ここでユーザーは確認のためにパスワードを再度入力します。検証ルールを使用して、両方のパスワードが同じかどうかを確認します(`$form::Equal`)。そして、パラメータとして、[角括弧 |#コントロールへのアクセス]を使用して最初のパスワードへの参照を与えます: + +```php +$form->addPassword('passwordVerify', '確認用パスワード:') + ->setRequired('確認のため、パスワードをもう一度入力してください') + ->addRule($form::Equal, 'パスワードが一致しません', $form['password']) + ->setOmitted(); +``` + +`setOmitted()` を使用して、値が実際には重要ではなく、検証目的でのみ存在するコントロールをマークしました。値は `$data` に渡されません。 + +これで、PHP と JavaScript の両方で検証を備えた完全に機能するフォームが完成しました。Nette の検証機能ははるかに広範で、条件を作成したり、それに応じてページの一部を表示したり非表示にしたりすることができます。すべては[フォーム検証|validation]に関する章で学びます。 + + +デフォルト値 +====== + +フォームコントロールには通常、デフォルト値を設定します: + +```php +$form->addEmail('email', 'Eメール') + ->setDefaultValue($lastUsedEmail); +``` + +すべてのコントロールに同時にデフォルト値を設定すると便利なことがよくあります。例えば、フォームがレコードの編集に使用される場合などです。データベースからレコードを読み取り、デフォルト値を設定します: + +```php +//$row = ['name' => 'John', 'age' => '33', /* ... */]; +$form->setDefaults($row); +``` + +コントロールを定義した後に `setDefaults()` を呼び出します。 + + +フォームのレンダリング +=========== + +デフォルトでは、フォームはテーブルとしてレンダリングされます。個々のコントロールは基本的なアクセシビリティルールを満たしています - すべてのラベルは `<label>` として記述され、対応するフォームコントロールに関連付けられています。ラベルをクリックすると、カーソルは自動的にフォームフィールドに表示されます。 + +各コントロールに任意の HTML 属性を設定できます。例えば、プレースホルダーを追加します: + +```php +$form->addInteger('age', '年齢:') + ->setHtmlAttribute('placeholder', '年齢を入力してください'); +``` + +フォームをレンダリングする方法は本当にたくさんあるので、[レンダリングに関する別の章|rendering]があります。 + + +クラスへのマッピング +========== + +フォームデータの処理に戻りましょう。`getValues()` メソッドは、送信されたデータを `ArrayHash` オブジェクトとして返しました。これは `stdClass` のような汎用クラスであるため、エディタでのプロパティの補完やコードの静的解析など、特定の快適さが欠けています。これは、各フォームに特定のクラスを用意し、そのプロパティが個々のコントロールを表すようにすることで解決できます。例: + +```php +class RegistrationFormData +{ + public string $name; + public ?int $age; + public string $password; +} +``` + +あるいは、コンストラクタを利用することもできます: + +```php +class RegistrationFormData +{ + public function __construct( + public string $name, + public int $age, + public string $password, + ) { + } +} +``` + +データクラスのプロパティは Enum にすることもでき、自動的にマッピングされます。 .{data-version:3.2.4} + +Nette にこのクラスのオブジェクトとしてデータを返すように指示するにはどうすればよいでしょうか?思ったよりも簡単です。クラス名またはハイドレートするオブジェクトをパラメータとして指定するだけです: + +```php +$data = $form->getValues(RegistrationFormData::class); +$name = $data->name; +``` + +パラメータとして `'array'` を指定することもでき、その場合はデータを配列として返します。 + +フォームがコンテナからなる多層構造を形成する場合、それぞれに個別のクラスを作成します: + +```php +$form = new Form; +$person = $form->addContainer('person'); +$person->addText('firstName'); +/* ... */ + +class PersonFormData +{ + public string $firstName; + public string $lastName; +} + +class RegistrationFormData +{ + public PersonFormData $person; + public ?int $age; + public string $password; +} +``` + +マッピングは、プロパティ `$person` の型から、コンテナを `PersonFormData` クラスにマッピングする必要があることを認識します。プロパティにコンテナの配列が含まれている場合は、型 `array` を指定し、マッピングするクラスをコンテナに直接渡します: + +```php +$person->setMappedType(PersonFormData::class); +``` + +フォームのデータクラスの設計は、`Nette\Forms\Blueprint::dataClass($form)` メソッドを使用して生成させることができます。これはブラウザページに出力されます。コードをクリックして選択し、プロジェクトにコピーするだけです。 .{data-version:3.1.15} + + +複数のボタン +====== + +フォームに複数のボタンがある場合、通常、どのボタンが押されたかを区別する必要があります。この情報は、ボタンの `isSubmittedBy()` メソッドによって返されます: + +```php +$form->addSubmit('save', '保存'); +$form->addSubmit('delete', '削除'); + +if ($form->isSuccess()) { + if ($form['save']->isSubmittedBy()) { + // ... + } + + if ($form['delete']->isSubmittedBy()) { + // ... + } +} +``` + +`$form->isSuccess()` の問い合わせを省略しないでください。データの有効性を検証します。 + +フォームが<kbd>Enter</kbd>キーで送信されると、最初のボタンで送信されたものとして扱われます。 + + +脆弱性からの保護 +======== + +Nette Framework はセキュリティを非常に重視しており、そのためフォームの適切な保護に細心の注意を払っています。 + +フォームを[クロスサイトスクリプティング(XSS) |nette:glossary#Cross-Site Scripting XSS]攻撃や[クロスサイトリクエストフォージェリ(CSRF) |nette:glossary#Cross-Site Request Forgery CSRF]攻撃から保護することに加えて、あなたが考える必要のない多くの小さなセキュリティ対策を行っています。 + +例えば、入力からすべての制御文字をフィルタリングし、UTF-8 エンコーディングの有効性を検証するため、フォームからのデータは常にクリーンになります。セレクトボックスやラジオリストでは、選択された項目が実際に提供されたものの中から選ばれたものであり、偽装されていないことを検証します。単一行のテキスト入力では、攻撃者が送信した可能性のある改行文字を削除することについては既に述べました。複数行の入力では、改行文字を正規化します。などなど。 + +Nette は、多くのプログラマーが存在することすら知らないセキュリティリスクをあなたに代わって解決します。 + +前述の CSRF 攻撃は、攻撃者が被害者を、被害者がログインしているサーバーに対して、被害者のブラウザで密かにリクエストを実行するページに誘導し、サーバーがそのリクエストが被害者自身の意志で行われたと誤解することに基づいています。そのため、Nette は他のドメインからの POST フォームの送信を防ぎます。何らかの理由で保護を無効にし、他のドメインからフォームを送信できるようにしたい場合は、次を使用します: + +```php +$form->allowCrossOrigin(); // 注意!保護を無効にします! +``` + +この保護は `_nss` という名前の SameSite クッキーを利用します。そのため、クッキーを送信できるように、最初の出力が送信される前にフォームオブジェクトを作成してください。 + +SameSite クッキーによる保護は 100% 信頼できるとは限らないため、トークンによる保護も有効にすることをお勧めします: + +```php +$form->addProtection(); +``` + +アプリケーション内の機密データを変更するウェブサイトの管理部分のフォームをこのように保護することをお勧めします。フレームワークは、セッションに保存される認証トークンを生成および検証することによって CSRF 攻撃から防御します。そのため、フォームを表示する前にセッションを開いておく必要があります。ウェブサイトの管理部分では、通常、ユーザーのログインのためにセッションは既に開始されています。 それ以外の場合は、`Nette\Http\Session::start()` メソッドでセッションを開始します。 + +これで、Nette のフォームの簡単な紹介は終わりです。配布物の [examples|https://github.com/nette/forms/tree/master/examples] ディレクトリも見て、さらなるインスピレーションを得てください。 diff --git a/forms/ja/validation.texy b/forms/ja/validation.texy new file mode 100644 index 0000000000..d28667dfc5 --- /dev/null +++ b/forms/ja/validation.texy @@ -0,0 +1,376 @@ +フォーム検証 +****** + + +必須コントロール +======== + +必須コントロールは `setRequired()` メソッドでマークします。その引数は、ユーザーがコントロールに入力しなかった場合に表示される[#エラーメッセージ]のテキストです。引数を指定しない場合は、デフォルトのエラーメッセージが使用されます。 + +```php +$form->addText('name', '名前:') + ->setRequired('名前を入力してください'); +``` + + +ルール +=== + +検証ルールは `addRule()` メソッドを使用してコントロールに追加します。最初のパラメータはルール、2番目は[#エラーメッセージ]のテキスト、3番目は検証ルールの引数です。 + +```php +$form->addPassword('password', 'パスワード:') + ->addRule($form::MinLength, 'パスワードは少なくとも %d 文字必要です', 8); +``` + +**検証ルールは、ユーザーがコントロールに入力した場合にのみ検証されます。** + +Nette には、`Nette\Forms\Form` クラスの定数である名前を持つ、多数の事前定義されたルールが付属しています。すべてのコントロールでこれらのルールを使用できます: + +| 定数 | 説明 | 引数の型 +|------- +| `Required` | 必須コントロール、`setRequired()` のエイリアス | - +| `Filled` | 必須コントロール、`setRequired()` のエイリアス | - +| `Blank` | コントロールは入力してはいけません | - +| `Equal` | 値がパラメータと等しい | `mixed` +| `NotEqual` | 値がパラメータと等しくない | `mixed` +| `IsIn` | 値が配列内のいずれかの項目と等しい | `array` +| `IsNotIn` | 値が配列内のどの項目とも等しくない | `array` +| `Valid` | コントロールは正しく入力されていますか? ([#条件]用) | - + + +テキスト入力 +------ + +`addText()`、`addPassword()`、`addTextArea()`、`addEmail()`、`addInteger()`、`addFloat()` コントロールでは、次のルールのいくつかを使用することもできます: + +| `MinLength` | テキストの最小長 | `int` +| `MaxLength` | テキストの最大長 | `int` +| `Length` | 範囲内の長さまたは正確な長さ | ペア `[int, int]` または `int` +| `Email` | 有効なメールアドレス | - +| `URL` | 絶対URL | - +| `Pattern` | 正規表現に一致する | `string` +| `PatternInsensitive` | `Pattern` と同様ですが、大文字と小文字を区別しません | `string` +| `Integer` | 整数値 | - +| `Numeric` | `Integer` のエイリアス | - +| `Float` | 数値 | - +| `Min` | 数値コントロールの最小値 | `int\|float` +| `Max` | 数値コントロールの最大値 | `int\|float` +| `Range` | 範囲内の値 | ペア `[int\|float, int\|float]` + +検証ルール `Integer`、`Numeric`、`Float` は、値をそれぞれ整数または浮動小数点数に直接変換します。さらに、ルール `URL` はスキーマのないアドレス(例:`nette.org`)も受け入れ、スキーマを追加します(`https://nette.org`)。`Pattern` と `PatternIcase` の式は、値全体に対して有効でなければなりません。つまり、`^` と `$` 文字で囲まれているかのように扱われます。 + + +項目数 +--- + +`addMultiUpload()`、`addCheckboxList()`、`addMultiSelect()` コントロールでは、選択された項目またはアップロードされたファイルの数を制限するために、次のルールを使用することもできます: + +| `MinLength` | 最小数 | `int` +| `MaxLength` | 最大数 | `int` +| `Length` | 範囲内の数または正確な数 | ペア `[int, int]` または `int` + + +ファイルアップロード +---------- + +`addUpload()`、`addMultiUpload()` コントロールでは、次のルールを使用することもできます: + +| `MaxFileSize` | ファイルの最大サイズ(バイト単位) | `int` +| `MimeType` | MIME タイプ、ワイルドカード許可 (`'video/*'`) | `string\|string[]` +| `Image` | JPEG、PNG、GIF、WebP、AVIF 画像 | - +| `Pattern` | ファイル名が正規表現に一致する | `string` +| `PatternInsensitive` | `Pattern` と同様ですが、大文字と小文字を区別しません | `string` + +`MimeType` と `Image` は PHP 拡張機能 `fileinfo` を必要とします。ファイルまたは画像が必要なタイプであるかどうかは、その署名に基づいて検出され、**ファイル全体の整合性は検証されません。** 画像が破損していないかどうかは、例えば[読み込み |http:request#toImage]を試みることで確認できます。 + + +エラーメッセージ +======== + +`Pattern` と `PatternInsensitive` を除くすべての事前定義されたルールにはデフォルトのエラーメッセージがあるため、省略できます。ただし、すべてのメッセージをカスタマイズして記述することで、フォームはよりユーザーフレンドリーになります。 + +デフォルトのメッセージは、[設定|forms:configuration]で、`Nette\Forms\Validator::$messages` 配列内のテキストを編集するか、[トランスレータ |rendering#翻訳]を使用することで変更できます。 + +エラーメッセージのテキストでは、次のプレースホルダー文字列を使用できます: + +| `%d` | ルールの引数で順次置き換えられます +| `%n$d` | ルールの n 番目の引数で置き換えられます +| `%label` | コントロールのラベルで置き換えられます(コロンなし) +| `%name` | コントロールの名前で置き換えられます(例:`name`) +| `%value` | ユーザーが入力した値で置き換えられます + +```php +$form->addText('name', '名前:') + ->setRequired('%label を入力してください'); + +$form->addInteger('id', 'ID:') + ->addRule($form::Range, '最小 %d、最大 %d', [5, 10]); + +$form->addInteger('id', 'ID:') + ->addRule($form::Range, '最大 %2$d、最小 %1$d', [5, 10]); +``` + + +条件 +======== + +ルールに加えて、条件を追加することもできます。これらはルールと同様に記述されますが、`addRule()` の代わりに `addCondition()` メソッドを使用し、もちろんエラーメッセージは指定しません(条件は単に問い合わせるだけです): + +```php +$form->addPassword('password', 'パスワード:') + // パスワードが8文字より長くない場合 + ->addCondition($form::MaxLength, 8) + // 数字を含まなければならない + ->addRule($form::Pattern, '数字を含める必要があります', '.*[0-9].*'); +``` + +条件は、`addConditionOn()` を使用して現在のコントロール以外のコントロールにバインドすることもできます。最初のパラメータとして、コントロールへの参照を指定します。この例では、チェックボックスがチェックされた場合(その値が true になる場合)にのみ、メールが必須になります: + +```php +$form->addCheckbox('newsletters', 'ニュースレターを購読する'); + +$form->addEmail('email', 'Eメール:') + // チェックボックスがチェックされている場合 + ->addConditionOn($form['newsletters'], $form::Equal, true) + // メールを要求する + ->setRequired('メールアドレスを入力してください'); +``` + +`elseCondition()` と `endCondition()` を使用して、条件から複雑な構造を作成できます: + +```php +$form->addText(/* ... */) + ->addCondition(/* ... */) // 最初の条件が満たされた場合 + ->addConditionOn(/* ... */) // そして別のコントロールの2番目の条件 + ->addRule(/* ... */) // このルールを要求する + ->elseCondition() // 2番目の条件が満たされない場合 + ->addRule(/* ... */) // これらのルールを要求する + ->addRule(/* ... */) + ->endCondition() // 最初の条件に戻る + ->addRule(/* ... */); +``` + +Nette では、`toggle()` メソッドを使用して、JavaScript 側で条件の充足または不充足に非常に簡単に対応できます。詳細は[#ダイナミック JavaScript]を参照してください。 + + +他のコントロールへの参照 +============ + +ルールまたは条件の引数として、フォームの別のコントロールを渡すことができます。ルールは、後でユーザーがブラウザで入力した値を使用します。このようにして、例えば、`password` コントロールが `password_confirm` コントロールと同じ文字列を含むことを動的に検証できます: + +```php +$form->addPassword('password', 'パスワード'); +$form->addPassword('password_confirm', 'パスワードを確認') + ->addRule($form::Equal, '入力されたパスワードが一致しません', $form['password']); +``` + + +カスタムルールと条件 +========== + +Nette の組み込み検証ルールでは不十分で、ユーザーからのデータを独自の方法で検証する必要がある状況に陥ることがあります。Nette ではこれは非常に簡単です! + +`addRule()` または `addCondition()` メソッドに、最初のパラメータとして任意のコールバックを渡すことができます。コールバックは、最初のパラメータとしてコントロール自体を受け取り、検証が正常に行われたかどうかを示すブール値を返します。`addRule()` を使用してルールを追加する場合、追加の引数を指定することもでき、それらは2番目のパラメータとして渡されます。 + +したがって、静的メソッドを持つクラスとして独自のバリデーターセットを作成できます: + +```php +class MyValidators +{ + // 値が引数で割り切れるかどうかをテストします + public static function validateDivisibility(BaseControl $input, $arg): bool + { + return $input->getValue() % $arg === 0; + } + + public static function validateEmailDomain(BaseControl $input, $domain) + { + // 他のバリデーター + } +} +``` + +使用方法は非常に簡単です: + +```php +$form->addInteger('num') + ->addRule( + [MyValidators::class, 'validateDivisibility'], + '値は %d の倍数でなければなりません', + 8, + ); +``` + +カスタム検証ルールは JavaScript にも追加できます。条件は、ルールが静的メソッドであることです。JavaScript バリデーター用の名前は、バックスラッシュ `\` を含まないクラス名、アンダースコア `_`、およびメソッド名を連結して作成されます。例えば、`App\MyValidators::validateDivisibility` は `AppMyValidators_validateDivisibility` として記述され、`Nette.validators` オブジェクトに追加されます: + +```js +Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => { + return val % args === 0; +}; +``` + + +onValidate イベント +=============== + +フォームが送信されると、検証が実行され、`addRule()` を使用して追加された個々のルールがチェックされ、その後 [イベント |nette:glossary#イベント] `onValidate` がトリガーされます。そのハンドラは、追加の検証、通常は複数のフォームコントロールの値の正しい組み合わせの検証に使用できます。 + +エラーが検出された場合、`addError()` メソッドを使用してフォームに渡します。これは、特定のコントロールまたはフォーム自体で呼び出すことができます。 + +```php +protected function createComponentSignInForm(): Form +{ + $form = new Form; + // ... + $form->onValidate[] = [$this, 'validateSignInForm']; + return $form; +} + +public function validateSignInForm(Form $form, \stdClass $data): void +{ + if ($data->foo > 1 && $data->bar > 5) { + $form->addError('この組み合わせは不可能です。'); + } +} +``` + + +処理中のエラー +======= + +多くの場合、有効なフォームを処理しているときにのみエラーが判明します。例えば、新しい項目をデータベースに書き込んでいるときにキーの重複に遭遇した場合などです。この場合、`addError()` メソッドを使用してエラーをフォームに再度渡します。これは、特定のコントロールまたはフォーム自体で呼び出すことができます: + +```php +try { + $data = $form->getValues(); + $this->user->login($data->username, $data->password); + $this->redirect('Home:'); + +} catch (Nette\Security\AuthenticationException $e) { + if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) { + $form->addError('無効なパスワードです。'); + } +} +``` + +可能であれば、エラーをフォームコントロールに直接添付することをお勧めします。デフォルトのレンダラーを使用すると、その隣に表示されるためです。 + +```php +$form['date']->addError('申し訳ありませんが、この日付は既に予約されています。'); +``` + +`addError()` を繰り返し呼び出して、フォームまたはコントロールに複数のエラーメッセージを渡すことができます。`getErrors()` を使用して取得します。 + +注意:`$form->getErrors()` は、個々のコントロールに直接渡されたものも含め、すべてのエラーメッセージの要約を返します。フォームにのみ渡されたエラーメッセージは `$form->getOwnErrors()` で取得できます。 + + +入力の変更 +===== + +`addFilter()` メソッドを使用して、ユーザーが入力した値を変更できます。この例では、郵便番号のスペースを許容し、削除します: + +```php +$form->addText('zip', '郵便番号:') + ->addFilter(function ($value) { + return str_replace(' ', '', $value); // 郵便番号からスペースを削除します + }) + ->addRule($form::Pattern, '郵便番号は5桁の数字ではありません', '\d{5}'); +``` + +フィルタは検証ルールと条件の間に組み込まれるため、メソッドの順序が重要です。つまり、フィルタとルールは `addFilter()` と `addRule()` メソッドの順序で呼び出されます。 + + +JavaScript 検証 +============= + +条件とルールを定式化するための言語は非常に強力です。すべての構造はサーバー側と JavaScript 側の両方で機能します。 それらは HTML 属性 `data-nette-rules` に JSON として転送されます。実際の検証は、フォームの `submit` イベントをキャッチし、個々のコントロールを反復処理して適切な検証を実行するスクリプトによって行われます。 + +そのスクリプトは `netteForms.js` であり、複数の可能なソースから利用できます: + +スクリプトは CDN から直接 HTML ページに埋め込むことができます: + +```latte +<script src="https://unpkg.com/nette-forms@3"></script> +``` + +または、プロジェクトの公開フォルダにローカルにコピーします(例:`vendor/nette/forms/src/assets/netteForms.min.js` から): + +```latte +<script src="/path/to/netteForms.min.js"></script> +``` + +または、[npm|https://www.npmjs.com/package/nette-forms] を介してインストールします: + +```shell +npm install nette-forms +``` + +そして、ロードして実行します: + +```js +import netteForms from 'nette-forms'; +netteForms.initOnLoad(); +``` + +あるいは、`vendor` フォルダから直接ロードすることもできます: + +```js +import netteForms from '../path/to/vendor/nette/forms/src/assets/netteForms.js'; +netteForms.initOnLoad(); +``` + + +ダイナミック JavaScript +================= + +ユーザーが商品を郵送で送ることを選択した場合にのみ、住所入力フィールドを表示したいですか?問題ありません。キーは `addCondition()` と `toggle()` のペアです: + +```php +$form->addCheckbox('send_it') + ->addCondition($form::Equal, true) + ->toggle('#address-container'); +``` + +このコードは、条件が満たされたとき、つまりチェックボックスがチェックされたときに、HTML 要素 `#address-container` が表示されることを示しています。そしてその逆も同様です。受信者の住所を持つフォームコントロールをこの ID を持つコンテナに配置すると、チェックボックスをクリックすると非表示または表示されます。これはスクリプト `netteForms.js` が保証します。 + +`toggle()` メソッドの引数として、任意のセレクタを渡すことができます。歴史的な理由から、他の特殊文字を含まない英数字文字列は要素の ID として解釈されます。つまり、`#` 文字が前に付いているのと同じです。2番目のオプションのパラメータを使用すると、動作を反転させることができます。つまり、`toggle('#address-container', false)` を使用した場合、要素はチェックボックスがチェックされていない場合にのみ表示されます。 + +JavaScript のデフォルトの実装は、要素の `hidden` プロパティを変更します。ただし、例えばアニメーションを追加するなど、動作を簡単に変更できます。JavaScript で `Nette.toggle` メソッドを独自のソリューションで上書きするだけです: + +```js +Nette.toggle = (selector, visible, srcElement, event) => { + document.querySelectorAll(selector).forEach((el) => { + // 'visible' の値に応じて 'el' を非表示または表示します + }); +}; +``` + + +検証の無効化 +====== + +検証を無効にすると便利な場合があります。送信ボタンの押下が検証を実行しないようにする場合(*Cancel*または*Preview*ボタンに適しています)、`$submit->setValidationScope([])` メソッドで無効にします。部分的な検証のみを実行する場合は、検証するフィールドまたはフォームコンテナを指定できます。 + +```php +$form->addText('name') + ->setRequired(); + +$details = $form->addContainer('details'); +$details->addInteger('age') + ->setRequired('age'); +$details->addInteger('age2') + ->setRequired('age2'); + +$form->addSubmit('send1'); // フォーム全体を検証します +$form->addSubmit('send2') + ->setValidationScope([]); // まったく検証しません +$form->addSubmit('send3') + ->setValidationScope([$form['name']]); // name コントロールのみを検証します +$form->addSubmit('send4') + ->setValidationScope([$form['details']['age']]); // age コントロールのみを検証します +$form->addSubmit('send5') + ->setValidationScope([$form['details']]); // details コンテナを検証します +``` + +`setValidationScope` は、フォームの[#onValidate イベント]には影響しません。これは常に呼び出されます。コンテナの `onValidate` イベントは、このコンテナが部分検証用にマークされている場合にのみトリガーされます。 diff --git a/forms/pl/@home.texy b/forms/pl/@home.texy index 4f4f635833..bf3015cfd6 100644 --- a/forms/pl/@home.texy +++ b/forms/pl/@home.texy @@ -1,31 +1,31 @@ -Formularze -********** +Nette Forms +*********** <div class=perex> -Nette Forms zrewolucjonizowały tworzenie formularzy internetowych. Nagle mogłeś napisać kilka łatwych do zrozumienia linii kodu i miałeś kompletny formularz, w tym renderowanie, walidację JavaScript i serwera oraz najwyższe bezpieczeństwo. Pokażemy ci, jak +Nette Forms zrewolucjonizowały tworzenie formularzy internetowych. Nagle wystarczyło napisać kilka zrozumiałych linii kodu, aby mieć gotowy formularz wraz z renderowaniem, walidacją JavaScriptową i serwerową, a dodatkowo doskonale zabezpieczony. Pokażemy, jak: -- tworzyć przyjazne dla użytkownika formularze -- zatwierdzić przedłożone dane -- renderować elementy dokładnie tak jak trzeba +- tworzyć przyjazne formularze +- walidować przesłane dane +- renderować elementy dokładnie według potrzeb </div> -Korzystając z Nette Forms, unikasz wielu rutynowych zadań, takich jak pisanie walidacji (i podwójnej walidacji, po stronie serwera i klienta), minimalizując prawdopodobieństwo wystąpienia błędów i dziur w zabezpieczeniach. +Używając Nette Forms, unikniesz wielu rutynowych zadań, takich jak pisanie walidacji (dodatkowo podwójnej, po stronie serwera i klienta), zminimalizujesz prawdopodobieństwo wystąpienia błędów i luk bezpieczeństwa. -Formy można używać zarówno jako część aplikacji Nette (czyli w presenterech) lub całkowicie samodzielnie. Ponieważ w obu przypadkach użycie jest nieco inne, przygotowaliśmy dwa tutoriale: +Formularze można używać albo jako część Aplikacji Nette (czyli w presenterach), albo całkowicie samodzielnie. Ponieważ w obu przypadkach użycie nieco się różni, przygotowaliśmy dla Ciebie dwa poradniki: <div class="wiki-buttons"> -<div> "Formularze w prezenterach .[wiki-button]":in-presenter </div> -<div> "Formy na własną rękę .[wiki-button]":standalone </div> +<div> "Formularze w presenterach .[wiki-button]":in-presenter </div> +<div> "Formularze samodzielnie .[wiki-button]":standalone </div> </div> Instalacja ---------- -Pobierz i zainstaluj bibliotekę za pomocą [Composera |best-practices:composer]: +Bibliotekę pobierzesz i zainstalujesz za pomocą narzędzia [Composer|best-practices:composer]: ```shell composer require nette/forms diff --git a/forms/pl/@left-menu.texy b/forms/pl/@left-menu.texy index ba98a039e7..cbc806cc5c 100644 --- a/forms/pl/@left-menu.texy +++ b/forms/pl/@left-menu.texy @@ -1,14 +1,14 @@ -Formularze -********** -- [Dom |@home] -- [Formy w presenterech |in-presenter] -- [Formularze oddzielnie |standalone] +Nette Forms +*********** +- [Wprowadzenie |@home] +- [Formularze w presenterach|in-presenter] +- [Formularze samodzielnie|standalone] - [Elementy formularza |controls] - [Walidacja |validation] -- [Rendering |rendering] +- [Renderowanie |rendering] - [Konfiguracja |configuration] -Dalsze czytanie -*************** -- [Samouczki i procedury |best-practices:] +Dalsza lektura +************** +- [Przewodniki i dobre praktyki |best-practices:] diff --git a/forms/pl/@meta.texy b/forms/pl/@meta.texy new file mode 100644 index 0000000000..61ac92d1af --- /dev/null +++ b/forms/pl/@meta.texy @@ -0,0 +1 @@ +{{sitename: Dokumentacja Nette}} diff --git a/forms/pl/configuration.texy b/forms/pl/configuration.texy index e415f04016..fcea0e4cf4 100644 --- a/forms/pl/configuration.texy +++ b/forms/pl/configuration.texy @@ -2,7 +2,7 @@ Konfiguracja formularzy *********************** .[perex] -W konfiguracji można zmienić domyślne [komunikaty błędów formularzy |validation]. +W konfiguracji można zmienić domyślne [komunikaty błędów formularzy|validation]. ```neon forms: @@ -30,32 +30,32 @@ forms: Nette\Forms\Controls\CsrfProtection::Protection: 'Your session has expired. Please return to the home page and try again.' ``` -Oto angielskie tłumaczenie: +Oto polskie tłumaczenie: ```neon forms: messages: - Equal: 'Zadejte %s.' - NotEqual: 'Tato hodnota by neměla být %s.' - Filled: 'Toto pole je povinné.' - Blank: 'Toto pole by mělo být prázdné.' - MinLength: 'Zadejte prosím alespoň %d znaků.' - MaxLength: 'Zadejte prosím maximálně %d znaků.' - Length: 'Zadejte prosím hodnotu %d až %d znaků dlouho.' - Email: 'Zadejte platnou e-mailovou adresu.' - URL: 'Zadejte prosím platné URL.' - Integer: 'Zadejte platné celé číslo.' - Float: 'Zadejte platné číslo.' - Min: 'Zadejte prosím hodnotu větší nebo rovnou %d.' - Max: 'Zadejte prosím hodnotu menší nebo rovnou %d.' - Range: 'Zadejte hodnotu mezi %d a %d.' - MaxFileSize: 'Velikost nahraného souboru může být nejvýše %d bytů.' - MaxPostSize: 'Nahraná data překračují limit %d bytů.' - MimeType: 'Nahraný soubor není v očekávaném formátu.' - Image: 'Nahraný soubor musí být obraz ve formátu JPEG, GIF, PNG nebo WebP.' - Nette\Forms\Controls\SelectBox::Valid: 'Vyberte prosím platnou možnost.' - Nette\Forms\Controls\UploadControl::Valid: 'Při nahrávání souboru došlo k chybě.' - Nette\Forms\Controls\CsrfProtection::Protection: 'Vaše relace vypršela. Vraťte se na domovskou stránku a zkuste to znovu.' + Equal: 'Proszę podać %s.' + NotEqual: 'Ta wartość nie powinna być %s.' + Filled: 'To pole jest wymagane.' + Blank: 'To pole powinno być puste.' + MinLength: 'Proszę podać co najmniej %d znaków.' + MaxLength: 'Proszę podać maksymalnie %d znaków.' + Length: 'Proszę podać wartość o długości od %d do %d znaków.' + Email: 'Proszę podać prawidłowy adres e-mail.' + URL: 'Proszę podać prawidłowy adres URL.' + Integer: 'Proszę podać prawidłową liczbę całkowitą.' + Float: 'Proszę podać prawidłową liczbę.' + Min: 'Proszę podać wartość większą lub równą %d.' + Max: 'Proszę podać wartość mniejszą lub równą %d.' + Range: 'Proszę podać wartość między %d a %d.' + MaxFileSize: 'Rozmiar przesłanego pliku może wynosić maksymalnie %d bajtów.' + MaxPostSize: 'Przesłane dane przekraczają limit %d bajtów.' + MimeType: 'Przesłany plik nie jest w oczekiwanym formacie.' + Image: 'Przesłany plik musi być obrazem w formacie JPEG, GIF, PNG, WebP lub AVIF.' + Nette\Forms\Controls\SelectBox::Valid: 'Proszę wybrać prawidłową opcję.' + Nette\Forms\Controls\UploadControl::Valid: 'Wystąpił błąd podczas przesyłania pliku.' + Nette\Forms\Controls\CsrfProtection::Protection: 'Twoja sesja wygasła. Proszę wrócić na stronę główną i spróbować ponownie.' ``` -Jeśli nie używasz całego frameworka, a więc nawet plików konfiguracyjnych, możesz zmienić domyślne komunikaty o błędach bezpośrednio w polu `Nette\Forms\Validator::$messages`. +Jeśli nie używasz całego frameworka, a więc i plików konfiguracyjnych, możesz zmienić domyślne komunikaty błędów bezpośrednio w tablicy `Nette\Forms\Validator::$messages`. diff --git a/forms/pl/controls.texy b/forms/pl/controls.texy index 397c90cc97..95b89b9ac8 100644 --- a/forms/pl/controls.texy +++ b/forms/pl/controls.texy @@ -5,249 +5,342 @@ Elementy formularza Przegląd standardowych elementów formularza. -addText(string|int $name, $label=null): TextInput .[method] -=========================================================== +addText(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +================================================================================================== -Dodaje jednolinijkowe pole tekstowe (klasa [TextInput |api:Nette\Forms\Controls\TextInput]). Jeśli użytkownik nie wypełni pola, zwraca pusty ciąg `''`, lub `setNullable()` może być użyty do określenia, że zwraca `null`. +Dodaje jednoliniowe pole tekstowe (klasa [TextInput |api:Nette\Forms\Controls\TextInput]). Jeśli użytkownik nie wypełni pola, zwraca pusty string `''`, lub za pomocą `setNullable()` można określić, aby zwracał `null`. ```php -$form->addText('name', 'Jméno:') +$form->addText('name', 'Imię:') ->setRequired() ->setNullable(); ``` -Automatycznie waliduje UTF-8, obcina lewe i prawe spacje oraz usuwa przerwy w linii, które mógłby wysłać atakujący. +Automatycznie waliduje UTF-8, przycina lewo- i prawostronne spacje oraz usuwa znaki nowej linii, które mógłby wysłać atakujący. -Maksymalna długość może być ograniczona za pomocą `setMaxLength()`. Modyfikacja wartości wprowadzonej przez użytkownika umożliwia [addFilter() |validation#Modifying-Input-Values]. +Maksymalną długość można ograniczyć za pomocą `setMaxLength()`. Zmianę wartości wprowadzonej przez użytkownika umożliwia [addFilter() |validation#Modyfikacja danych wejściowych]. -Używając `setHtmlType()`, [charakter |https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types] elementu wejściowego można zmienić na `search`, `tel`, `url`, `range`, `date`, `datetime-local`, `month`, `time`, `week`, `color`. Zalecamy użycie [addInteger |#addInteger] i [addEmail |#addEmail] zamiast typów `number` i `email`, które mają walidację po stronie serwera. +Za pomocą `setHtmlType()` można zmienić wizualny charakter pola tekstowego na typy takie jak `search`, `tel` lub `url` zobacz [specyfikację|https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types]. Pamiętaj, że zmiana typu jest tylko wizualna i nie zastępuje funkcji walidacji. Dla typu `url` wskazane jest dodanie specyficznej reguły walidacji [URL |validation#Wejścia tekstowe]. -```php -$form->addText('color', 'Vyberte barvu:') - ->setHtmlType('color') - ->addRule($form::Pattern, 'invalid value', '[0-9a-f]{6}'); -``` +.[note] +Dla innych typów wejść, takich jak `number`, `range`, `email`, `date`, `datetime-local`, `time` i `color`, użyj specjalizowanych metod jak [#addInteger], [#addFloat], [#addEmail] [#addDate], [#addTime], [#addDateTime] i [#addColor], które zapewniają walidację po stronie serwera. Typy `month` i `week` na razie nie są w pełni obsługiwane we wszystkich przeglądarkach. -Element może być ustawiony na empty-value, co jest jak wartość domyślna, ale jeśli użytkownik nie zmieni go, element zwróci pusty ciąg lub `null`. +Elementowi można ustawić tzw. pustą wartość (empty-value), co jest czymś w rodzaju wartości domyślnej, ale jeśli użytkownik jej nie zmieni, element zwróci pusty string lub `null`. ```php $form->addText('phone', 'Telefon:') ->setHtmlType('tel') - ->setEmptyValue('+420'); + ->setEmptyValue('+48'); ``` addTextArea(string|int $name, $label=null): TextArea .[method] ============================================================== -Dodaje tablicę do wprowadzania tekstu wieloliniowego (klasa [TextArea |api:Nette\Forms\Controls\TextArea]). Jeśli użytkownik nie wypełni pola, zwraca pusty ciąg `''`, lub `setNullable()` może być użyty do określenia, że zwraca `null`. +Dodaje pole do wprowadzania tekstu wieloliniowego (klasa [TextArea |api:Nette\Forms\Controls\TextArea]). Jeśli użytkownik nie wypełni pola, zwraca pusty string `''`, lub za pomocą `setNullable()` można określić, aby zwracał `null`. ```php -$form->addTextArea('note', 'Poznámka:') - ->addRule($form::MaxLength, 'Poznámka je příliš dlouhá', 10000); +$form->addTextArea('note', 'Notatka:') + ->addRule($form::MaxLength, 'Notatka jest zbyt długa', 10000); ``` -Automatycznie sprawdza UTF-8 i normalizuje separatory linii do `\n`. W przeciwieństwie do pola wejściowego z jednym wierszem, nie występuje przycinanie spacji. +Automatycznie waliduje UTF-8 i normalizuje separatory linii do `\n`. W przeciwieństwie do jednoliniowego pola wejściowego nie dochodzi do przycinania spacji. -Maksymalna długość może być ograniczona przy użyciu `setMaxLength()`. Funkcja [addFilter() |validation#Modifying-Input-Values] pozwala na modyfikację wartości wprowadzonej przez użytkownika. Wartość pusta może być ustawiona przy użyciu `setEmptyValue()`. +Maksymalną długość można ograniczyć za pomocą `setMaxLength()`. Zmianę wartości wprowadzonej przez użytkownika umożliwia [addFilter() |validation#Modyfikacja danych wejściowych]. Można ustawić tzw. pustą wartość za pomocą `setEmptyValue()`. addInteger(string|int $name, $label=null): TextInput .[method] ============================================================== -Dodaje pole do wprowadzania liczby całkowitej (klasa [TextInput |api:Nette\Forms\Controls\TextInput]). Zwraca liczbę całkowitą lub `null`, jeśli użytkownik nic nie wprowadzi. +Dodaje pole do wprowadzania liczby całkowitej (klasa [TextInput |api:Nette\Forms\Controls\TextInput]). Zwraca albo integer, albo `null`, jeśli użytkownik nic nie wpisze. + +```php +$form->addInteger('year', 'Rok:') + ->addRule($form::Range, 'Rok musi być w zakresie od %d do %d.', [1900, 2023]); +``` + +Element renderuje się jako `<input type="number">`. Użyciem metody `setHtmlType()` można zmienić typ na `range` do wyświetlania w postaci suwaka, lub na `text`, jeśli preferujesz standardowe pole tekstowe bez specjalnego zachowania typu `number`. + + +addFloat(string|int $name, $label=null): TextInput .[method]{data-version:3.1.12} +================================================================================= + +Dodaje pole do wprowadzania liczby dziesiętnej (klasa [TextInput |api:Nette\Forms\Controls\TextInput]). Zwraca albo float, albo `null`, jeśli użytkownik nic nie wpisze. ```php -$form->addInteger('level', 'Úroveň:') +$form->addFloat('level', 'Poziom:') ->setDefaultValue(0) - ->addRule($form::Range, 'Úroveň musí být v rozsahu mezi %d a %d.', [0, 100]); + ->addRule($form::Range, 'Poziom musi być w zakresie od %d do %d.', [0, 100]); ``` +Element renderuje się jako `<input type="number">`. Użyciem metody `setHtmlType()` można zmienić typ na `range` do wyświetlania w postaci suwaka, lub na `text`, jeśli preferujesz standardowe pole tekstowe bez specjalnego zachowania typu `number`. + +Nette i przeglądarka Chrome akceptują jako separator miejsc dziesiętnych zarówno przecinek, jak i kropkę. Aby ta funkcjonalność była dostępna również w Firefoksie, zaleca się ustawienie atrybutu `lang` albo dla danego elementu, albo dla całej strony, na przykład `<html lang="pl">`. + -addEmail(string|int $name, $label=null): TextInput .[method] -============================================================ +addEmail(string|int $name, $label=null, int $maxLength=255): TextInput .[method] +================================================================================ -Dodaje pole do wpisania adresu e-mail (klasa [TextInput |api:Nette\Forms\Controls\TextInput]). Jeśli użytkownik nie wypełni pola, zwraca pusty ciąg `''`, lub można określić `setNullable()`, aby zwrócić `null`. +Dodaje pole do wprowadzania adresu e-mail (klasa [TextInput |api:Nette\Forms\Controls\TextInput]). Jeśli użytkownik nie wypełni pola, zwraca pusty string `''`, lub za pomocą `setNullable()` można określić, aby zwracał `null`. ```php $form->addEmail('email', 'E-mail:'); ``` -Sprawdza, czy podana wartość jest prawidłowym adresem e-mail. Nie sprawdza, czy domena faktycznie istnieje, sprawdzana jest tylko składnia. Automatycznie waliduje UTF-8, obcina lewe i prawe spacje. +Sprawdza, czy wartość jest prawidłowym adresem e-mail. Nie sprawdza się, czy domena faktycznie istnieje, sprawdza się tylko składnię. Automatycznie waliduje UTF-8, przycina lewo- i prawostronne spacje. -Maksymalna długość może być ograniczona przy użyciu `setMaxLength()`. [AddFilter() |validation#Modifying-Input-Values] może być użyty do modyfikacji wartości wprowadzonej przez użytkownika. Pusta wartość może być ustawiona przy użyciu `setEmptyValue()`. +Maksymalną długość można ograniczyć za pomocą `setMaxLength()`. Zmianę wartości wprowadzonej przez użytkownika umożliwia [addFilter() |validation#Modyfikacja danych wejściowych]. Można ustawić tzw. pustą wartość za pomocą `setEmptyValue()`. -addPassword(string|int $name, $label=null): TextInput .[method] -=============================================================== +addPassword(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +====================================================================================================== -Dodaje pole do wpisania hasła (klasa [TextInput |api:Nette\Forms\Controls\TextInput]). +Dodaje pole do wprowadzania hasła (klasa [TextInput |api:Nette\Forms\Controls\TextInput]). ```php -$form->addPassword('password', 'Heslo:') +$form->addPassword('password', 'Hasło:') ->setRequired() - ->addRule($form::MinLength, 'Heslo musí mít alespoň %d znaků', 8) - ->addRule($form::Pattern, 'Musí obsahovat číslici', '.*[0-9].*'); + ->addRule($form::MinLength, 'Hasło musi mieć co najmniej %d znaków', 8) + ->addRule($form::Pattern, 'Musi zawierać cyfrę', '.*[0-9].*'); ``` -Gdy formularz zostanie ponownie wyświetlony, pole będzie puste. Automatycznie waliduje UTF-8, obcina lewe i prawe spacje oraz usuwa przerwy w linii, które mogłyby zostać wysłane przez atakującego. +Przy ponownym wyświetleniu formularza pole będzie puste. Automatycznie waliduje UTF-8, przycina lewo- i prawostronne spacje oraz usuwa znaki nowej linii, które mógłby wysłać atakujący. addCheckbox(string|int $name, $caption=null): Checkbox .[method] ================================================================ -Dodaje pole wyboru (klasa Checkbox). Zwraca wartość albo `true` albo `false`, w zależności od tego czy jest zaznaczona. +Dodaje pole wyboru (klasa [Checkbox |api:Nette\Forms\Controls\Checkbox]). Zwraca wartość albo `true`, albo `false`, w zależności od tego, czy jest zaznaczone. ```php -$form->addCheckbox('agree', 'Souhlasím s podmínkami') - ->setRequired('Je potřeba souhlasit s podmínkami'); +$form->addCheckbox('agree', 'Zgadzam się z warunkami') + ->setRequired('Konieczna jest zgoda na warunki'); ``` -addCheckboxList(string|int $name, $label=null, array $items=null): CheckboxList .[method] -========================================================================================= +addCheckboxList(string|int $name, $label=null, ?array $items=null): CheckboxList .[method] +========================================================================================== -Dodaje pola wyboru do zaznaczania wielu elementów (klasa [CheckboxList |api:Nette\Forms\Controls\CheckboxList]). Zwraca tablicę kluczy dla wybranych elementów. Metoda `getSelectedItems()` zwraca wartości zamiast kluczy. +Dodaje pola wyboru do wyboru wielu pozycji (klasa [CheckboxList |api:Nette\Forms\Controls\CheckboxList]). Zwraca tablicę kluczy wybranych pozycji. Metoda `getSelectedItems()` zwraca wartości zamiast kluczy. ```php -$form->addCheckboxList('colors', 'Barvy:', [ - 'r' => 'červená', - 'g' => 'zelená', - 'b' => 'modrá', +$form->addCheckboxList('colors', 'Kolory:', [ + 'r' => 'czerwony', + 'g' => 'zielony', + 'b' => 'niebieski', ]); ``` -Przekaż tablicę oferowanych elementów jako trzeci parametr lub metodą `setItems()`. +Tablicę oferowanych pozycji przekazujemy jako trzeci parametr lub metodą `setItems()`. -Korzystanie z `setDisabled(['r', 'g'])` metoda do dezaktywacji poszczególnych elementów. +Za pomocą `setDisabled(['r', 'g'])` można dezaktywować poszczególne pozycje. -Element ten automatycznie sprawdza, czy nie doszło do oszustwa oraz czy wybrane pozycje są rzeczywiście jednymi z oferowanych i nie zostały dezaktywowane. Metoda `getRawValue()` może być użyta do odzyskania elementów wysłanych bez tego ważnego sprawdzenia. +Element automatycznie kontroluje, czy nie doszło do fałszerstwa i czy wybrane pozycje są rzeczywiście jednymi z oferowanych i nie zostały dezaktywowane. Metodą `getRawValue()` można uzyskać wysłane pozycje bez tej ważnej kontroli. -Podczas ustawiania domyślnych wybranych elementów sprawdza również, czy są one jednym z oferowanych elementów, w przeciwnym razie rzuca wyjątek. To sprawdzanie można wyłączyć za pomocą `checkDefaultValue(false)`. +Przy ustawianiu domyślnych wybranych pozycji również kontroluje, czy są to jedne z oferowanych, w przeciwnym razie rzuca wyjątek. Tę kontrolę można wyłączyć za pomocą `checkDefaultValue(false)`. +Jeśli wysyłasz formularz metodą `GET`, możesz wybrać bardziej kompaktowy sposób przesyłania danych, który oszczędza rozmiar query stringu. Aktywuje się go ustawieniem atrybutu HTML formularza: + +```php +$form->setHtmlAttribute('data-nette-compact'); +``` -addRadioList(string|int $name, $label=null, array $items=null): RadioList .[method] -=================================================================================== -Dodaje przyciski radiowe (klasa [RadioList |api:Nette\Forms\Controls\RadioList]). Zwraca klucz wybranego elementu lub `null`, jeśli użytkownik nic nie wybrał. Metoda `getSelectedItem()` zwraca wartość zamiast klucza. +addRadioList(string|int $name, $label=null, ?array $items=null): RadioList .[method] +==================================================================================== + +Dodaje przyciski opcji (klasa [RadioList |api:Nette\Forms\Controls\RadioList]). Zwraca klucz wybranej pozycji, lub `null`, jeśli użytkownik nic nie wybrał. Metoda `getSelectedItem()` zwraca wartość zamiast klucza. ```php $sex = [ - 'm' => 'muž', - 'f' => 'žena', + 'm' => 'mężczyzna', + 'f' => 'kobieta', ]; -$form->addRadioList('gender', 'Pohlaví:', $sex); +$form->addRadioList('gender', 'Płeć:', $sex); ``` -Przekaż tablicę oferowanych elementów jako trzeci parametr lub metody `setItems()`. +Tablicę oferowanych pozycji przekazujemy jako trzeci parametr lub metodą `setItems()`. -Korzystanie z `setDisabled(['m', 'f'])` metoda do dezaktywacji poszczególnych elementów. +Za pomocą `setDisabled(['m', 'f'])` można dezaktywować poszczególne pozycje. -Element automatycznie sprawdza, czy nie doszło do podebrania oraz czy wybrana pozycja jest rzeczywiście jedną z oferowanych pozycji i nie została dezaktywowana. Metoda `getRawValue()` może być użyta do odzyskania przesłanego elementu bez tego ważnego sprawdzenia. +Element automatycznie kontroluje, czy nie doszło do fałszerstwa i czy wybrana pozycja jest rzeczywiście jedną z oferowanych i nie została dezaktywowana. Metodą `getRawValue()` można uzyskać wysłaną pozycję bez tej ważnej kontroli. -Po ustawieniu domyślnego wybranego elementu sprawdza również, czy jest to jeden z oferowanych elementów, w przeciwnym razie rzuca wyjątek. To sprawdzanie można wyłączyć za pomocą `checkDefaultValue(false)`. +Przy ustawianiu domyślnej wybranej pozycji również kontroluje, czy jest to jedna z oferowanych, w przeciwnym razie rzuca wyjątek. Tę kontrolę można wyłączyć za pomocą `checkDefaultValue(false)`. -addSelect(string|int $name, $label=null, array $items=null): SelectBox .[method] -================================================================================ +addSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): SelectBox .[method] +================================================================================================== -Dodaje pole wyboru (klasa [SelectBox |api:Nette\Forms\Controls\SelectBox]). Zwraca klucz wybranego elementu lub `null`, jeśli użytkownik nic nie wybrał. Metoda `getSelectedItem()` zwraca wartość zamiast klucza. +Dodaje pole wyboru (klasa [SelectBox |api:Nette\Forms\Controls\SelectBox]). Zwraca klucz wybranej pozycji, lub `null`, jeśli użytkownik nic nie wybrał. Metoda `getSelectedItem()` zwraca wartość zamiast klucza. ```php $countries = [ - 'CZ' => 'Česká Republika', - 'SK' => 'Slovensko', - 'GB' => 'Velká Británie', + 'CZ' => 'Republika Czeska', + 'PL' => 'Polska', + 'GB' => 'Wielka Brytania', ]; -$form->addSelect('country', 'Země:', $countries) - ->setDefaultValue('SK'); +$form->addSelect('country', 'Kraj:', $countries) + ->setDefaultValue('PL'); ``` -Przekaż tablicę oferowanych elementów jako trzeci parametr lub przez metodę `setItems()` Elementy mogą być tablicami dwuwymiarowymi: +Tablicę oferowanych pozycji przekazujemy jako trzeci parametr lub metodą `setItems()`. Pozycje mogą być również tablicą dwuwymiarową: ```php $countries = [ - 'Europe' => [ - 'CZ' => 'Česká Republika', - 'SK' => 'Slovensko', - 'GB' => 'Velká Británie', + 'Europa' => [ + 'CZ' => 'Republika Czeska', + 'PL' => 'Polska', + 'GB' => 'Wielka Brytania', ], 'CA' => 'Kanada', 'US' => 'USA', - '?' => 'jiná', + '?' => 'inna', ]; ``` -W przypadku select boxów pierwszy element ma często specjalne znaczenie, służy jako call to action. Do dodania takiego elementu służy metoda `setPrompt()`. +W polach wyboru często pierwsza pozycja ma specjalne znaczenie, służy jako wezwanie do działania (prompt). Do dodania takiej pozycji służy metoda `setPrompt()`. ```php -$form->addSelect('country', 'Země:', $countries) - ->setPrompt('Zvolte zemi'); +$form->addSelect('country', 'Kraj:', $countries) + ->setPrompt('Wybierz kraj'); ``` -Korzystanie z `setDisabled(['CZ', 'SK'])` metoda do dezaktywacji poszczególnych elementów. +Za pomocą `setDisabled(['CZ', 'SK'])` można dezaktywować poszczególne pozycje. -Pozycja automatycznie sprawdza, czy nie doszło do oszustwa oraz czy wybrany przedmiot jest rzeczywiście jednym z oferowanych przedmiotów i nie został dezaktywowany. Metoda `getRawValue()` może być użyta do odzyskania przesłanego elementu bez tego ważnego sprawdzenia. +Element automatycznie kontroluje, czy nie doszło do fałszerstwa i czy wybrana pozycja jest rzeczywiście jedną z oferowanych i nie została dezaktywowana. Metodą `getRawValue()` można uzyskać wysłaną pozycję bez tej ważnej kontroli. -Gdy ustawiony jest domyślny wybrany element, sprawdza również, czy jest to jeden z oferowanych elementów, w przeciwnym razie rzuca wyjątek. To sprawdzanie można wyłączyć za pomocą `checkDefaultValue(false)`. +Przy ustawianiu domyślnej wybranej pozycji również kontroluje, czy jest to jedna z oferowanych, w przeciwnym razie rzuca wyjątek. Tę kontrolę można wyłączyć za pomocą `checkDefaultValue(false)`. -addMultiSelect(string|int $name, $label=null, array $items=null): MultiSelectBox .[method] -========================================================================================== +addMultiSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): MultiSelectBox .[method] +============================================================================================================ -Dodaje select box do wyboru wielu elementów (klasa [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox]). Zwraca tablicę kluczy dla wybranych elementów. Metoda `getSelectedItems()` zwraca wartości zamiast kluczy. +Dodaje pole wyboru do wyboru wielu pozycji (klasa [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox]). Zwraca tablicę kluczy wybranych pozycji. Metoda `getSelectedItems()` zwraca wartości zamiast kluczy. ```php -$form->addMultiSelect('countries', 'Země:', $countries); +$form->addMultiSelect('countries', 'Kraje:', $countries); ``` -Przekaż tablicę oferowanych elementów jako trzeci parametr lub przez metodę `setItems()` Elementy mogą być tablicami dwuwymiarowymi. +Tablicę oferowanych pozycji przekazujemy jako trzeci parametr lub metodą `setItems()`. Pozycje mogą być również tablicą dwuwymiarową. -Korzystanie z `setDisabled(['CZ', 'SK'])` aby dezaktywować poszczególne elementy. +Za pomocą `setDisabled(['CZ', 'SK'])` można dezaktywować poszczególne pozycje. -Element ten automatycznie sprawdza, czy nie doszło do oszustwa oraz czy wybrane pozycje są rzeczywiście jednymi z oferowanych i nie zostały dezaktywowane. Metoda `getRawValue()` może być użyta do odzyskania przesłanych elementów bez tego ważnego sprawdzenia. +Element automatycznie kontroluje, czy nie doszło do fałszerstwa i czy wybrane pozycje są rzeczywiście jednymi z oferowanych i nie zostały dezaktywowane. Metodą `getRawValue()` można uzyskać wysłane pozycje bez tej ważnej kontroli. -Podczas ustawiania domyślnych wybranych elementów sprawdza również, czy są one jednym z oferowanych elementów, w przeciwnym razie rzuca wyjątek. To sprawdzanie można wyłączyć za pomocą `checkDefaultValue(false)`. +Przy ustawianiu domyślnych wybranych pozycji również kontroluje, czy są to jedne z oferowanych, w przeciwnym razie rzuca wyjątek. Tę kontrolę można wyłączyć za pomocą `checkDefaultValue(false)`. addUpload(string|int $name, $label=null): UploadControl .[method] ================================================================= -Dodaje pole wyboru dla przesyłania plików (klasa [UploadControl |api:Nette\Forms\Controls\UploadControl]). Zwraca obiekt [FileUpload |http:request#FileUpload] nawet jeśli użytkownik nie przesłał żadnego pliku, co można wykryć za pomocą metody `FileUpload::hasFile()`. +Dodaje pole do przesyłania pliku (klasa [UploadControl |api:Nette\Forms\Controls\UploadControl]). Zwraca obiekt [FileUpload |http:request#FileUpload] i to nawet w przypadku, gdy użytkownik nie wysłał żadnego pliku, co można sprawdzić metodą `FileUpload::hasFile()`. ```php -$form->addUpload('avatar', 'Avatar:') - ->addRule($form::Image, 'Avatar musí být JPEG, PNG, GIF or WebP.') - ->addRule($form::MaxFileSize, 'Maximální velikost je 1 MB.', 1024 * 1024); +$form->addUpload('avatar', 'Awatar:') + ->addRule($form::Image, 'Awatar musi być JPEG, PNG, GIF, WebP lub AVIF.') + ->addRule($form::MaxFileSize, 'Maksymalny rozmiar to 1 MB.', 1024 * 1024); ``` -Jeśli plik nie zostanie załadowany poprawnie, formularz nie zostanie pomyślnie przesłany i zostanie wyświetlony błąd. Tzn. Jeśli przesłanie jest udane, nie ma potrzeby sprawdzania metody `FileUpload::isOk()`. +Jeśli plik nie zostanie poprawnie przesłany, formularz nie jest pomyślnie wysłany i wyświetli się błąd. Tj. przy pomyślnym wysłaniu nie ma potrzeby weryfikować metody `FileUpload::isOk()`. -Nigdy nie ufaj oryginalnej nazwie pliku zwróconej przez metodę `FileUpload::getName()`, klient mógł przesłać złośliwą nazwę pliku z zamiarem uszkodzenia lub zhakowania twojej aplikacji. +Nigdy nie ufaj oryginalnej nazwie pliku zwróconej przez metodę `FileUpload::getName()`, klient mógł wysłać szkodliwą nazwę pliku z zamiarem uszkodzenia lub zhakowania Twojej aplikacji. -Reguły `MimeType` i `Image` wykrywają żądany typ na podstawie sygnatury pliku i nie sprawdzają jego integralności. Możesz określić, czy obraz nie jest uszkodzony, na przykład próbując go [odzyskać |http:request#toImage]. +Reguły `MimeType` i `Image` wykrywają wymagany typ na podstawie sygnatury pliku i nie weryfikują jego integralności. Czy obrazek nie jest uszkodzony można sprawdzić na przykład próbą jego [wczytania |http:request#toImage]. addMultiUpload(string|int $name, $label=null): UploadControl .[method] ====================================================================== -Dodaje pole wyboru do przesyłania wielu plików jednocześnie (klasa [UploadControl |api:Nette\Forms\Controls\UploadControl]). Zwraca tablicę obiektów [FileUpload |http:request#FileUpload]. Metoda `FileUpload::hasFile()` dla każdego z nich zwróci `true`. +Dodaje pole do przesyłania wielu plików naraz (klasa [UploadControl |api:Nette\Forms\Controls\UploadControl]). Zwraca tablicę obiektów [FileUpload |http:request#FileUpload]. Metoda `FileUpload::hasFile()` u każdego z nich będzie zwracać `true`. ```php -$form->addMultiUpload('files', 'Soubory:') - ->addRule($form::MaxLength, 'Maximálně lze nahrát %d souborů', 10); +$form->addMultiUpload('files', 'Pliki:') + ->addRule($form::MaxLength, 'Maksymalnie można przesłać %d plików', 10); ``` -Jeśli jakikolwiek plik nie zostanie załadowany poprawnie, formularz nie zostanie pomyślnie przesłany i zostanie wyświetlony błąd. Tzn. Jeśli złożenie jest udane, nie ma potrzeby sprawdzania metody `FileUpload::isOk()`. +Jeśli któryś plik nie zostanie poprawnie przesłany, formularz nie jest pomyślnie wysłany i wyświetli się błąd. Tj. przy pomyślnym wysłaniu nie ma potrzeby weryfikować metody `FileUpload::isOk()`. + +Nigdy nie ufaj oryginalnym nazwom plików zwróconym przez metodę `FileUpload::getName()`, klient mógł wysłać szkodliwą nazwę pliku z zamiarem uszkodzenia lub zhakowania Twojej aplikacji. + +Reguły `MimeType` i `Image` wykrywają wymagany typ na podstawie sygnatury pliku i nie weryfikują jego integralności. Czy obrazek nie jest uszkodzony można sprawdzić na przykład próbą jego [wczytania |http:request#toImage]. + -Nigdy nie ufaj oryginalnym nazwom plików zwracanym przez metodę `FileUpload::getName()`, klient mógł przesłać złośliwą nazwę pliku z zamiarem uszkodzenia lub zhakowania twojej aplikacji. +addDate(string|int $name, $label=null): DateTimeControl .[method]{data-version:3.1.14} +====================================================================================== -Reguły `MimeType` i `Image` wykrywają żądany typ na podstawie sygnatury pliku i nie sprawdzają jego integralności. Możesz określić, czy obraz nie jest uszkodzony, na przykład próbując go [odzyskać |http:request#toImage]. +Dodaje pole, które umożliwia użytkownikowi łatwe wprowadzenie daty składającej się z roku, miesiąca i dnia (klasa [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Jako wartość domyślną akceptuje albo obiekty implementujące interfejs `DateTimeInterface`, string z czasem, albo liczbę reprezentującą timestamp UNIX. To samo dotyczy argumentów reguł `Min`, `Max` lub `Range`, które definiują minimalną i maksymalną dozwoloną datę. + +```php +$form->addDate('date', 'Data:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'Data musi być co najmniej miesiąc stara.', new DateTime('-1 month')); +``` + +Standardowo zwraca obiekt `DateTimeImmutable`, metodą `setFormat()` możesz specyfikować [format tekstowy|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] lub timestamp: + +```php +$form->addDate('date', 'Data:') + ->setFormat('Y-m-d'); +``` -addHidden(string|int $name, string $default=null): HiddenField .[method] -======================================================================== +addTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=============================================================================================================== + +Dodaje pole, które umożliwia użytkownikowi łatwe wprowadzenie czasu składającego się z godzin, minut i opcjonalnie sekund (klasa [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Jako wartość domyślną akceptuje albo obiekty implementujące interfejs `DateTimeInterface`, string z czasem, albo liczbę reprezentującą timestamp UNIX. Z tych wejść wykorzystywana jest tylko informacja o czasie, data jest ignorowana. To samo dotyczy argumentów reguł `Min`, `Max` lub `Range`, które definiują minimalny i maksymalny dozwolony czas. Jeśli ustawiona minimalna wartość jest wyższa niż maksymalna, tworzy się zakres czasowy przekraczający północ. + +```php +$form->addTime('time', 'Czas:', withSeconds: true) + ->addRule($form::Range, 'Czas musi być w zakresie od %d do %d.', ['12:30', '13:30']); +``` + +Standardowo zwraca obiekt `DateTimeImmutable` (z datą 1. stycznia roku 1), metodą `setFormat()` możesz specyfikować [format tekstowy|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters]: + +```php +$form->addTime('time', 'Czas:') + ->setFormat('H:i'); +``` + + +addDateTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=================================================================================================================== + +Dodaje pole, które umożliwia użytkownikowi łatwe wprowadzenie daty i czasu składających się z roku, miesiąca, dnia, godzin, minut i opcjonalnie sekund (klasa [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Jako wartość domyślną akceptuje albo obiekty implementujące interfejs `DateTimeInterface`, string z czasem, albo liczbę reprezentującą timestamp UNIX. To samo dotyczy argumentów reguł `Min`, `Max` lub `Range`, które definiują minimalną i maksymalną dozwoloną datę. + +```php +$form->addDateTime('datetime', 'Data i czas:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'Data musi być co najmniej miesiąc stara.', new DateTime('-1 month')); +``` + +Standardowo zwraca obiekt `DateTimeImmutable`, metodą `setFormat()` możesz specyfikować [format tekstowy|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] lub timestamp: + +```php +$form->addDateTime('datetime') + ->setFormat(DateTimeControl::FormatTimestamp); +``` + + +addColor(string|int $name, $label=null): ColorPicker .[method]{data-version:3.1.14} +=================================================================================== + +Dodaje pole do wyboru koloru (klasa [ColorPicker |api:Nette\Forms\Controls\ColorPicker]). Kolor jest stringiem w formacie `#rrggbb`. Jeśli użytkownik nie dokona wyboru, zwrócony zostanie czarny kolor `#000000`. + +```php +$form->addColor('color', 'Kolor:') + ->setDefaultValue('#3C8ED7'); +``` + + +addHidden(string|int $name, ?string $default=null): HiddenField .[method] +========================================================================= Dodaje ukryte pole (klasa [HiddenField |api:Nette\Forms\Controls\HiddenField]). @@ -255,7 +348,9 @@ Dodaje ukryte pole (klasa [HiddenField |api:Nette\Forms\Controls\HiddenField]). $form->addHidden('userid'); ``` -Używając `setNullable()`, można go ustawić, aby zwracał `null` zamiast pustego łańcucha. Modyfikuje wartość przesłaną przez [addFilter() |validation#Modifying-Input-Values]. +Za pomocą `setNullable()` można ustawić, aby zwracał `null` zamiast pustego stringa. Zmianę wysłanej wartości umożliwia [addFilter() |validation#Modyfikacja danych wejściowych]. + +Chociaż element jest ukryty, **ważne jest, aby pamiętać**, że wartość może być nadal modyfikowana lub sfałszowana przez atakującego. Zawsze dokładnie sprawdzaj i waliduj wszystkie otrzymane wartości po stronie serwera, aby zapobiec ryzykom bezpieczeństwa związanym z manipulacją danymi. addSubmit(string|int $name, $caption=null): SubmitButton .[method] @@ -264,14 +359,14 @@ addSubmit(string|int $name, $caption=null): SubmitButton .[method] Dodaje przycisk wysyłania (klasa [SubmitButton |api:Nette\Forms\Controls\SubmitButton]). ```php -$form->addSubmit('submit', 'Odeslat'); +$form->addSubmit('submit', 'Wyślij'); ``` -Możliwe jest również posiadanie wielu przycisków submit w formularzu: +W formularzu można mieć również więcej przycisków wysyłania: ```php -$form->addSubmit('register', 'Register'); -$form->addSubmit('cancel', 'Cancel'); +$form->addSubmit('register', 'Zarejestruj'); +$form->addSubmit('cancel', 'Anuluj'); ``` Aby dowiedzieć się, który z nich został kliknięty, użyj: @@ -282,48 +377,48 @@ if ($form['register']->isSubmittedBy()) { } ``` -Jeśli nie chcesz walidować całego formularza po kliknięciu przycisku (na przykład dla przycisków *Cancel* lub *Preview*), użyj funkcji [setValidationScope() |validation#Disabling-Validation]. +Jeśli nie chcesz walidować całego formularza po naciśnięciu przycisku (na przykład przy przyciskach *Anuluj* lub *Podgląd*), użyj [setValidationScope() |validation#Wyłączenie walidacji]. addButton(string|int $name, $caption): Button .[method] ======================================================= -Dodaje przycisk (klasa [Button |api:Nette\Forms\Controls\Button]), który nie posiada funkcji wysyłania. Może więc zostać wykorzystany do jakiejś innej funkcji, np. wywołania funkcji JavaScript po kliknięciu. +Dodaje przycisk (klasa [Button |api:Nette\Forms\Controls\Button]), który nie ma funkcji wysyłania. Można go więc wykorzystać do jakiejś innej funkcji, np. wywołania funkcji JavaScript po kliknięciu. ```php -$form->addButton('raise', 'Zvýšit plat') +$form->addButton('raise', 'Podnieś pensję') ->setHtmlAttribute('onclick', 'raiseSalary()'); ``` -addImageButton(string|int $name, string $src=null, string $alt=null): ImageButton .[method] -=========================================================================================== +addImageButton(string|int $name, ?string $src=null, ?string $alt=null): ImageButton .[method] +============================================================================================= -Dodaje przycisk submit w postaci obrazka (klasa [ImageButton |api:Nette\Forms\Controls\ImageButton]). +Dodaje przycisk wysyłania w postaci obrazka (klasa [ImageButton |api:Nette\Forms\Controls\ImageButton]). ```php $form->addImageButton('submit', '/path/to/image'); ``` -W przypadku korzystania z wielu przycisków wysyłania, możliwe jest sprawdzenie, który z nich został kliknięty przez `$form['submit']->isSubmittedBy()`. +Przy użyciu wielu przycisków wysyłania można dowiedzieć się, który został kliknięty, za pomocą `$form['submit']->isSubmittedBy()`. addContainer(string|int $name): Container .[method] =================================================== -Dodaje podformularz (klasa [Container |api:Nette\Forms\Container]), czyli kontener, do którego można dodawać inne elementy w taki sam sposób, jak dodaje się je do formularza. Sprawdzają się również metody `setDefaults()` lub `getValues()`. +Dodaje podformularz (klasa [Container|api:Nette\Forms\Container]), czyli kontener, do którego można dodawać kolejne elementy w ten sam sposób, jak dodajemy je do formularza. Działają również metody `setDefaults()` lub `getValues()`. ```php $sub1 = $form->addContainer('first'); -$sub1->addText('name', 'Your name:'); +$sub1->addText('name', 'Twoje imię:'); $sub1->addEmail('email', 'Email:'); $sub2 = $form->addContainer('second'); -$sub2->addText('name', 'Your name:'); +$sub2->addText('name', 'Twoje imię:'); $sub2->addEmail('email', 'Email:'); ``` -Następnie zwraca przekazane dane jako strukturę wielowymiarową: +Wysłane dane zwraca następnie jako strukturę wielowymiarową: ```php [ @@ -339,110 +434,112 @@ Następnie zwraca przekazane dane jako strukturę wielowymiarową: ``` -Przegląd ustawień .[#toc-overview-of-settings] -============================================== +Przegląd ustawień +================= -Dla wszystkich elementów możemy wywołać następujące metody (pełny przegląd znajduje się w [dokumentacji API |https://api.nette.org/forms/master/Nette/Forms/Controls.html]): +U wszystkich elementów możemy wywoływać następujące metody (kompletny przegląd w [dokumentacji API|https://api.nette.org/forms/master/Nette/Forms/Controls.html]): .[table-form-methods language-php] -| `setDefaultValue($value)`| ustawia wartość domyślną -| `getValue()` | uzyskać aktualną wartość -| `setOmitted()` | [pomiń wartość |#Omitted-Values] -| `setDisabled()` | [dezaktywować elementy |#Disabling-Inputs] +| `setDefaultValue($value)` | ustawia wartość domyślną +| `getValue()` | pobiera aktualną wartość +| `setOmitted()` | [#pominięcie-wartości] +| `setDisabled()` | [#dezaktywacja-elementów] -Rendering: +Renderowanie: .[table-form-methods language-php] -| `setCaption($caption)`| zmienia etykietę elementu -| `setTranslator($translator)` | ustawia [kompilator |rendering#translating] -| `setHtmlAttribute($name, $value)` | ustawia [atrybut HTML |rendering#HTML-Attributes] elementu -| `setHtmlId($id)` | ustawia atrybut HTML `id` -| `setHtmlType($type)` | ustawia atrybut HTML `type` -| `setHtmlName($name)`| ustawia atrybut HTML `name` -| `setOption($key, $value)` | [Ustawienia renderingu |rendering#Options] +| `setCaption($caption)` | zmienia etykietę elementu +| `setTranslator($translator)` | ustawia [tłumacza |rendering#Tłumaczenie] +| `setHtmlAttribute($name, $value)` | ustawia [atrybut HTML |rendering#Atrybuty HTML] elementu +| `setHtmlId($id)` | ustawia atrybut HTML `id` +| `setHtmlType($type)` | ustawia atrybut HTML `type` +| `setHtmlName($name)` | ustawia atrybut HTML `name` +| `setOption($key, $value)` | [ustawienia dla renderowania |rendering#Opcje] Walidacja: .[table-form-methods language-php] -| `setRequired()` | [Element obowiązkowy |validation] -| `addRule()` | Ustawienia [reguł walidacji |validation#Rules] -| `addCondition()`, `addConditionOn()` | Ustawić [warunek walidacji |validation#Conditions] -| `addError($message)`| [Przekazanie komunikatu o błędzie |validation#Processing-Errors] +| `setRequired()` | [element wymagany |validation] +| `addRule()` | ustawienie [reguły walidacyjnej |validation#Reguły] +| `addCondition()`, `addConditionOn()` | ustawia [warunek walidacyjny |validation#Warunki] +| `addError($message)` | [przekazanie komunikatu błędu |validation#Błędy podczas przetwarzania] -Poniższe metody można wywołać dla elementów `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`: +U elementów `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`, `addFloat()` można wywoływać następujące metody: .[table-form-methods language-php] -| `setNullable()` | określa, czy funkcja getValue() zwróci `null` zamiast pustego łańcucha -| `setEmptyValue($value)`| ustawia specjalną wartość, która jest traktowana jako pusty łańcuch -| `setMaxLength($length)`| określa maksymalną liczbę dozwolonych znaków -| `addFilter($filter)`| [edit input |validation#Modifying-Input-Values] +| `setNullable()` | ustawia, czy getValue() zwróci `null` zamiast pustego stringa +| `setEmptyValue($value)` | ustawia specjalną wartość, która jest uważana za pusty string +| `setMaxLength($length)` | ustawia maksymalną liczbę dozwolonych znaków +| `addFilter($filter)` | [modyfikacja wejścia |validation#Modyfikacja danych wejściowych] -Pomijanie wartości .[#toc-omitted-values] ------------------------------------------ +Pominięcie wartości +=================== -Jeśli nie interesuje nas wartość wypełniona przez użytkownika, możemy użyć `setOmitted()`, aby pominąć ją w wyniku metody `$form->getValues()` lub w danych przekazywanych do handlerów. Jest to przydatne dla różnych sprawdzarek haseł, elementów antyspamowych itp. +Jeśli wartość wprowadzona przez użytkownika nas nie interesuje, możemy ją za pomocą `setOmitted()` pominąć w wyniku metody `$form->getValues()` lub w danych przekazywanych do handlerów. Jest to przydatne dla różnych haseł kontrolnych, elementów antyspamowych itp. ```php -$form->addPassword('passwordVerify', 'Heslo pro kontrolu:') - ->setRequired('Zadejte prosím heslo ještě jednou pro kontrolu') - ->addRule($form::Equal, 'Hesla se neshodují', $form['password']) +$form->addPassword('passwordVerify', 'Hasło do kontroli:') + ->setRequired('Proszę podać hasło jeszcze raz do kontroli') + ->addRule($form::Equal, 'Hasła się nie zgadzają', $form['password']) ->setOmitted(); ``` -Dezaktywacja elementów .[#toc-disabling-inputs] ------------------------------------------------ +Dezaktywacja elementów +====================== -Elementy mogą być dezaktywowane za pomocą `setDisabled()`. Użytkownik nie może edytować takich elementów. +Elementy można dezaktywować za pomocą `setDisabled()`. Takiego elementu użytkownik nie może edytować. ```php -$form->addText('username', 'Uživatelské jméno:') +$form->addText('username', 'Nazwa użytkownika:') ->setDisabled(); ``` -Należy pamiętać, że elementy wyłączone nie są w ogóle wysyłane przez przeglądarkę do serwera, więc nie znajdziemy ich w danych zwracanych przez funkcję `$form->getValues()`. +Wyłączone elementy przeglądarka w ogóle nie wysyła na serwer, więc nie znajdziesz ich w danych zwróconych przez funkcję `$form->getValues()`. Jeśli jednak ustawisz `setOmitted(false)`, Nette do tych danych dołączy ich wartość domyślną. -Jeśli ustawisz wartość domyślną dla elementu, musisz to zrobić dopiero po jego dezaktywacji: +Przy wywołaniu `setDisabled()` ze względów bezpieczeństwa **usuwana jest wartość elementu**. Jeśli ustawiasz wartość domyślną, należy to zrobić dopiero po jego dezaktywacji: ```php -$form->addText('username', 'Uživatelské jméno:') +$form->addText('username', 'Nazwa użytkownika:') ->setDisabled() ->setDefaultValue($userName); ``` +Alternatywą dla wyłączonych elementów są elementy z atrybutem HTML `readonly`, które przeglądarka wysyła na serwer. Chociaż element jest tylko do odczytu, **ważne jest, aby pamiętać**, że jego wartość może być nadal modyfikowana lub sfałszowana przez atakującego. + -Elementy niestandardowe .[#toc-custom-controls] -=============================================== +Własne elementy +=============== -Oprócz szerokiej gamy wbudowanych elementów formularza, możesz dodać do swojego formularza elementy niestandardowe w następujący sposób: +Oprócz szerokiej gamy wbudowanych elementów formularza możesz dodawać do formularza własne elementy w ten sposób: ```php -$form->addComponent(new DateInput('Datum:'), 'date'); -// alternativní syntax: $form['date'] = new DateInput('Datum:'); +$form->addComponent(new DateInput('Data:'), 'date'); +// alternatywna składnia: $form['date'] = new DateInput('Data:'); ``` .[note] -Formularz jest potomkiem klasy [Container | component-model:#Container], a elementy są potomkami [Component | component-model:#Component]. +Formularz jest potomkiem klasy [Container |component-model:#Container], a poszczególne elementy są potomkami [Component |component-model:#Component]. -Istnieje sposób definiowania nowych metod formularza do dodawania niestandardowych elementów (np. `$form->addZip()`). Są one nazywane metodami rozszerzenia. Wadą jest to, że podpowiedzi edytora nie będą dla nich działać. +Istnieje sposób, jak zdefiniować nowe metody formularza służące do dodawania własnych elementów (np. `$form->addZip()`). Są to tzw. metody rozszerzające (extension methods). Wadą jest to, że dla nich nie będzie działać podpowiadanie w edytorach. ```php use Nette\Forms\Container; -// přidáme metodu addZip(string $name, string $label = null) -Container::extensionMethod('addZip', function (Container $form, string $name, string $label = null) { +// dodajemy metodę addZip(string $name, ?string $label = null) +Container::extensionMethod('addZip', function (Container $form, string $name, ?string $label = null) { return $form->addText($name, $label) - ->addRule($form::Pattern, 'Alespoň 5 čísel', '[0-9]{5}'); + ->addRule($form::Pattern, 'Co najmniej 5 cyfr', '[0-9]{5}'); }); -// použití -$form->addZip('zip', 'Kod ZIP:'); +// użycie +$form->addZip('zip', 'Kod pocztowy:'); ``` -Elementy niskiego poziomu .[#toc-low-level-fields] -================================================== +Elementy niskopoziomowe +======================= -Można również wykorzystać elementy, które zapisujemy tylko w szablonie i nie dodajemy ich do formularza, korzystając z jednej z metod `$form->addXyz()`. Przykładowo, jeśli wypisujemy rekordy z bazy danych i nie wiemy z góry, ile ich będzie i jakie będą miały ID, a chcemy wyświetlić checkbox lub radio button dla każdego wiersza, wystarczy, że zakodujemy to w szablonie: +Można również używać elementów, które zapiszemy tylko w szablonie i nie dodamy ich do formularza za pomocą którejś z metod `$form->addXyz()`. Kiedy na przykład wypisujemy rekordy z bazy danych i z góry nie wiemy, ile ich będzie i jakie będą miały ID, a chcemy przy każdym wierszu wyświetlić checkbox lub radio button, wystarczy zakodować go w szablonie: ```latte {foreach $items as $item} @@ -450,13 +547,13 @@ Można również wykorzystać elementy, które zapisujemy tylko w szablonie i ni {/foreach} ``` -A po złożeniu wartości dowiemy się: +A po wysłaniu wartość odczytamy: ```php $data = $form->getHttpData($form::DataText, 'sel[]'); $data = $form->getHttpData($form::DataText | $form::DataKeys, 'sel[]'); ``` -gdzie pierwszy parametr to typ elementu (`DataFile` dla `type=file`, `DataLine` dla wpisów jednowierszowych jak `text`, `password`, `email` itd. oraz `DataText` dla wszystkich pozostałych), a drugi parametr `sel[]` odpowiada nazwie atrybutu HTML. Typ elementu można połączyć z wartością `DataKeys`, która zachowuje klucze elementów. Jest to szczególnie przydatne dla `select`, `radioList` i `checkboxList`. +gdzie pierwszy parametr to typ elementu (`DataFile` dla `type=file`, `DataLine` dla jednoliniowych wejść jak `text`, `password`, `email` itp. i `DataText` dla wszystkich pozostałych), a drugi parametr `sel[]` odpowiada atrybutowi HTML name. Typ elementu możemy łączyć z wartością `DataKeys`, która zachowa klucze elementów. Jest to szczególnie przydatne dla `select`, `radioList` i `checkboxList`. -Co ważne, `getHttpData()` zwraca sanitowaną wartość, w takim przypadku zawsze będzie to tablica prawidłowych łańcuchów UTF-8, bez względu na to, co atakujący może próbować zasunąć serwer. Jest to podobne do pracy bezpośrednio z `$_POST` lub `$_GET`, ale z ważną różnicą, że zawsze zwraca czyste dane, tak jak jesteś przyzwyczajony do standardowych elementów formularza Nette. +Istotne jest, że `getHttpData()` zwraca oczyszczoną wartość, w tym przypadku będzie to zawsze tablica prawidłowych stringów UTF-8, niezależnie od tego, co próbowałby podsunąć serwerowi atakujący. Jest to odpowiednik bezpośredniej pracy z `$_POST` lub `$_GET`, ale z tą istotną różnicą, że zawsze zwraca czyste dane, tak jak jesteś przyzwyczajony w standardowych elementach formularzy Nette. diff --git a/forms/pl/in-presenter.texy b/forms/pl/in-presenter.texy index ff8db944ce..512ebedc83 100644 --- a/forms/pl/in-presenter.texy +++ b/forms/pl/in-presenter.texy @@ -1,36 +1,36 @@ -Formy w presenterech -******************** +Formularze w prezenterach +************************* .[perex] -Nette Forms znacznie ułatwiają tworzenie i przetwarzanie formularzy internetowych. W tym rozdziale dowiesz się, jak używać formularzy wewnątrz prezenterów. +Nette Forms znacznie ułatwiają tworzenie i przetwarzanie formularzy internetowych. W tym rozdziale zapoznasz się z używaniem formularzy wewnątrz prezenterów. -Jeśli jesteś zainteresowany używaniem ich całkowicie samodzielnie bez reszty frameworka, jest dla Ciebie [samodzielny |standalone] tutorial. +Jeśli interesuje Cię, jak używać ich całkowicie samodzielnie bez reszty frameworka, przeznaczony jest dla Ciebie przewodnik do [samodzielnego użycia|standalone]. -Pierwszy formularz .[#toc-first-form] -===================================== +Pierwszy formularz +================== -Spróbujmy napisać prosty formularz rejestracyjny. Jego kod będzie wyglądał następująco: +Spróbujemy napisać prosty formularz rejestracyjny. Jego kod będzie następujący: ```php use Nette\Application\UI\Form; $form = new Form; -$form->addText('name', 'Jméno:'); -$form->addPassword('password', 'Heslo:'); -$form->addSubmit('send', 'Registrovat'); +$form->addText('name', 'Imię:'); +$form->addPassword('password', 'Hasło:'); +$form->addSubmit('send', 'Zarejestruj'); $form->onSuccess[] = [$this, 'formSucceeded']; ``` -i zostanie on wyświetlony w przeglądarce w następujący sposób: +a w przeglądarce wyświetli się tak: [* form-cs.webp *] -Formularz w prezenterze jest obiektem klasy `Nette\Application\UI\Form`, jego poprzednik `Nette\Forms\Form` jest przeznaczony do samodzielnego użytku. Dodałem elementy nazwy, hasła i przycisku submit. Wreszcie, linia z `$form->onSuccess` mówi, aby wywołać metodę `$this->formSucceeded()` po złożeniu i pomyślnym zatwierdzeniu. +Formularz w prezenterze to obiekt klasy `Nette\Application\UI\Form`, jej poprzednik `Nette\Forms\Form` jest przeznaczony do samodzielnego użytku. Dodaliśmy do niego tzw. elementy imię, hasło i przycisk wysyłania. A na końcu linia z `$form->onSuccess` mówi, że po wysłaniu i pomyślnej walidacji ma zostać wywołana metoda `$this->formSucceeded()`. -Z punktu widzenia prezentera formularz jest normalnym komponentem. Dlatego traktujemy go jako komponent i włączamy do prezentera za pomocą [metody factory |application:components#Factory-Methods]. Będzie to wyglądało tak: +Z punktu widzenia prezentera formularz jest zwykłym komponentem. Dlatego traktuje się go jak komponent i włączamy go do prezentera za pomocą [metody fabrykującej |application:components#Metody fabrykujące]. Będzie to wyglądać tak: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} use Nette; use Nette\Application\UI\Form; @@ -39,103 +39,100 @@ class HomePresenter extends Nette\Application\UI\Presenter protected function createComponentRegistrationForm(): Form { $form = new Form; - $form->addText('name', 'Jméno:'); - $form->addPassword('password', 'Heslo:'); - $form->addSubmit('send', 'Registrovat'); + $form->addText('name', 'Imię:'); + $form->addPassword('password', 'Hasło:'); + $form->addSubmit('send', 'Zarejestruj'); $form->onSuccess[] = [$this, 'formSucceeded']; return $form; } public function formSucceeded(Form $form, $data): void { - // tutaj przetwarzamy dane przesłane przez formularz - // $data->name zawiera jméno - // $data->password zawiera heslo - $this->flashMessage('Byl jste úspěšně registrován.'); - $this->redirect('Strona główna:'); + // tutaj przetwarzamy dane wysłane formularzem + // $data->name zawiera imię + // $data->password zawiera hasło + $this->flashMessage('Zostałeś pomyślnie zarejestrowany.'); + $this->redirect('Home:'); } } ``` -A w szablonie renderujemy formularz z tagiem `{control}`: +A w szablonie formularz renderujemy znacznikiem `{control}`: -```latte .{file:app/Presenters/templates/Home/default.latte} -<h1>Registrace</h1> +```latte .{file:app/Presentation/Home/default.latte} +<h1>Rejestracja</h1> {control registrationForm} ``` -I to wszystko :-) Mamy funkcjonalną i doskonale [zabezpieczoną |#Vulnerability-Protection] formę. +I to właściwie wszystko :-) Mamy działający i doskonale [zabezpieczony |#Ochrona przed podatnościami] formularz. -I teraz pewnie myślisz, to było za dużo hrr, zastanawiając się jak to możliwe, że wywołujemy metodę `formSucceeded()` i jakie są parametry, które ona otrzymuje. Jasne, masz rację, to zasługuje na wyjaśnienie. +A teraz pewnie myślisz, że to było za szybko, zastanawiasz się, jak to możliwe, że wywołuje się metoda `formSucceeded()` i jakie są parametry, które otrzymuje. Oczywiście, masz rację, to zasługuje na wyjaśnienie. -Bo Nette wymyśla świeży mechanizm, który nazywamy [stylem hollywoodzkim |application:components#Hollywood-Style]. Zamiast konieczności ciągłego zadawania sobie jako deweloper pytania, czy coś się stało ("czy formularz został przesłany?", "czy został przesłany poprawnie?" i "czy był manipulowany?"), mówisz frameworkowi "kiedy formularz jest ważnie złożony, wywołaj tę metodę" i pozostaw resztę pracy jemu. Jeśli programujesz w JavaScript, jesteś zaznajomiony z tym stylem programowania. Piszesz funkcje, które są wywoływane w momencie wystąpienia [zdarzenia |nette:glossary#Events]. A język przekazuje im odpowiednie argumenty. +Nette bowiem wprowadza świeży mechanizm, który nazywamy [Hollywood style |application:components#Styl Hollywood]. Zamiast tego, abyś jako programista musiał ciągle pytać, czy coś się wydarzyło („czy formularz został wysłany?”, „czy został wysłany poprawnie?” i „czy nie doszło do jego sfałszowania?”), mówisz frameworkowi „kiedy formularz będzie poprawnie wypełniony, wywołaj tę metodę” i zostawiasz dalszą pracę jemu. Jeśli programujesz w JavaScripcie, ten styl programowania znasz doskonale. Piszesz funkcje, które są wywoływane, gdy nastąpi określone [zdarzenie |nette:glossary#Eventy zdarzenia]. A język przekazuje im odpowiednie argumenty. -Powyższy kod prezentera jest zbudowany właśnie w ten sposób. Pole `$form->onSuccess` reprezentuje listę wywołań zwrotnych PHP, które Nette wywoła, gdy formularz zostanie przesłany i wypełniony poprawnie (tzn. jest ważny). -W ramach [cyklu życia prezentera |application:presenters#Life-Cycle-of-Presenter] są one wywoływane po metodzie `action*`, a przed metodą `render*`. -I dla każdego callbacka przekazuje sam formularz jako pierwszy parametr i przesłane dane w postaci obiektu [ArrayHash |utils:arrays#ArrayHash] jako drugi. Możesz pominąć pierwszy parametr, jeśli nie potrzebujesz obiektu formularza. A drugi parametr może być bardziej przebiegły, ale o tym więcej [później |#Mapping-to-Classes]. +Właśnie tak zbudowany jest również powyższy kod prezentera. Tablica `$form->onSuccess` reprezentuje listę callbacków PHP, które Nette wywoła w momencie, gdy formularz zostanie wysłany i poprawnie wypełniony (tj. jest ważny). W ramach [cyklu życia prezentera |application:presenters#Cykl życia presentera] jest to tzw. sygnał, wywoływane są więc po metodzie `action*` i przed metodą `render*`. A każdemu callbackowi przekazuje jako pierwszy parametr sam formularz, a jako drugi wysłane dane w postaci obiektu [ArrayHash |utils:arrays#ArrayHash]. Pierwszy parametr możesz pominąć, jeśli obiekt formularza nie jest potrzebny. A drugi parametr potrafi być sprytniejszy, ale o tym [później |#Mapowanie na klasy]. -Obiekt `$data` zawiera klucze `name` i `password` z danymi wypełnionymi przez użytkownika. Zazwyczaj od razu wysyłamy dane do dalszej obróbki, którą może być np. wstawienie ich do bazy danych. Podczas przetwarzania może jednak wystąpić błąd, na przykład nazwa użytkownika jest już zajęta. W tym przypadku przekazujemy błąd z powrotem do formularza za pomocą `addError()` i mamy go renderować ponownie, z komunikatem o błędzie. +Obiekt `$data` zawiera właściwości `name` i `password` z danymi, które wypełnił użytkownik. Zazwyczaj dane od razu wysyłamy do dalszego przetwarzania, co może być na przykład wstawienie do bazy danych. Podczas przetwarzania może jednak pojawić się błąd, na przykład nazwa użytkownika jest już zajęta. W takim przypadku błąd przekazujemy z powrotem do formularza za pomocą `addError()` i pozwalamy mu wyrenderować się ponownie, wraz z komunikatem błędu. ```php -$form->addError('Omlouváme se, uživatelské jméno už někdo používá.'); +$form->addError('Przepraszamy, nazwa użytkownika jest już zajęta.'); ``` -Oprócz `onSuccess` jest jeszcze `onSubmit`: callbacki są wywoływane zawsze po przesłaniu formularza, nawet jeśli nie jest on wypełniony poprawnie. I `onError`: callbacki są wywoływane tylko wtedy, gdy zgłoszenie nie jest ważne. Zostaną one nawet wywołane, jeśli `onSuccess` lub `onSubmit` unieważni formularz za pomocą `addError()`. +Oprócz `onSuccess` istnieje jeszcze `onSubmit`: callbacki są wywoływane zawsze po wysłaniu formularza, nawet jeśli nie jest on poprawnie wypełniony. A dalej `onError`: callbacki są wywoływane tylko jeśli wysłanie nie jest ważne. Wywołają się nawet wtedy, jeśli w `onSuccess` lub `onSubmit` unieważnimy formularz za pomocą `addError()`. -Po przetworzeniu formularza przekierowujemy na kolejną stronę. Zapobiega to niezamierzonemu ponownemu przesłaniu formularza za pomocą przycisków *refresh*, *back* lub historii przeglądarki. +Po przetworzeniu formularza przekierowujemy na następną stronę. Zapobiega to niechcianemu ponownemu wysłaniu formularza przyciskiem *odśwież*, *wstecz* lub poruszaniem się w historii przeglądarki. -Spróbuj dodać również inne [elementy formularza |controls]. +Spróbuj dodać również inne [elementy formularza|controls]. -Dostęp do elementów .[#toc-access-to-controls] -============================================== +Dostęp do elementów +=================== -Formularz jest komponentem prezentera, w tym przypadku nazwanym `registrationForm` (od nazwy metody fabrycznej `createComponentRegistrationForm`), więc w dowolnym miejscu prezentera można uzyskać dostęp do formularza za pomocą: +Formularz jest komponentem prezentera, w naszym przypadku nazwanym `registrationForm` (według nazwy metody fabrykującej `createComponentRegistrationForm`), więc gdziekolwiek w prezenterze dostaniesz się do formularza za pomocą: ```php $form = $this->getComponent('registrationForm'); -// alternativní syntax: $form = $this['registrationForm']; +// alternatywna składnia: $form = $this['registrationForm']; ``` -Komponenty są również elementami formularza, więc możesz uzyskać do nich dostęp w ten sam sposób: +Komponentami są również poszczególne elementy formularza, dlatego dostaniesz się do nich w ten sam sposób: ```php -$input = $form->getComponent('name'); // nebo $input = $form['name']; -$button = $form->getComponent('send'); // nebo $button = $form['send'] +$input = $form->getComponent('name'); // lub $input = $form['name']; +$button = $form->getComponent('send'); // lub $button = $form['send']; ``` -Elementy są usuwane za pomocą unset: +Elementy usuwa się za pomocą `unset`: ```php unset($form['name']); ``` -Zasady walidacji .[#toc-validation-rules] -========================================= +Reguły walidacyjne +================== -Słowo *valid,* ale formularz nie ma jeszcze reguł walidacji. Naprawmy to. +Padło tu słowo *ważny,* ale formularz na razie nie ma żadnych reguł walidacyjnych. Naprawmy to. -Nazwa będzie obowiązkowa, więc oznaczymy ją metodą `setRequired()`, której argumentem jest tekst komunikatu o błędzie, który zostanie wyświetlony, jeśli użytkownik nie wypełni nazwy. Jeśli nie podano żadnego argumentu, użyty zostanie domyślny komunikat o błędzie. +Imię będzie obowiązkowe, dlatego oznaczymy je metodą `setRequired()`, której argumentem jest tekst komunikatu błędu, który wyświetli się, jeśli użytkownik nie wypełni imienia. Jeśli nie podamy argumentu, użyty zostanie domyślny komunikat błędu. ```php -$form->addText('name', 'Jméno:') - ->setRequired('Zadejte prosím jméno'); +$form->addText('name', 'Imię:') + ->setRequired('Proszę podać imię'); ``` -Spróbuj przesłać formularz bez wypełnienia nazwy, a zobaczysz, że pojawi się komunikat o błędzie, a przeglądarka lub serwer odrzuci go, dopóki nie wypełnisz pola. +Spróbuj wysłać formularz bez wypełnionego imienia, a zobaczysz, że wyświetli się komunikat błędu, a przeglądarka lub serwer będzie go odrzucać, dopóki nie wypełnisz pola. -Jednocześnie nie oszukuj systemu, wpisując w pole np. same spacje. Nie rób tego. Nette automatycznie usuwa zarówno lewe jak i prawe spacje. Spróbuj. Jest to rodzaj rzeczy, którą powinieneś zawsze robić z każdym jednolinijkowym wejściem, ale często się o tym zapomina. Nette robi to automatycznie (możesz spróbować oszukać formularz, aby wysłać ciąg wieloliniowy jako nazwę. Nawet tutaj Nette nie da się oszukać i zmieni podziały linii na spacje). +Jednocześnie systemu nie oszukasz, wpisując w pole na przykład same spacje. Nic z tego. Nette lewo- i prawostronne spacje automatycznie usuwa. Wypróbuj to. To rzecz, którą powinieneś zawsze robić z każdym jednoliniowym inputem, ale często się o tym zapomina. Nette robi to automatycznie. (Możesz spróbować oszukać formularz i jako imię wysłać wieloliniowy string. Nawet tutaj Nette nie da się zmylić i znaki nowej linii zamieni na spacje.) -Formularz jest zawsze walidowany po stronie serwera, ale generuje również walidację JavaScript, która jest wykonywana w mgnieniu oka, a użytkownik dowiaduje się o błędzie natychmiast, bez konieczności wysyłania formularza na serwer. Zajmuje się tym skrypt `netteForms.js`. -Wstaw go do szablonu układu: +Formularz zawsze waliduje się po stronie serwera, ale generuje się również walidacja JavaScriptowa, która przebiega błyskawicznie, a użytkownik dowiaduje się o błędzie natychmiast, bez konieczności wysyłania formularza na serwer. Za to odpowiada skrypt `netteForms.js`. Wstaw go do szablonu layoutu: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Jeśli spojrzysz na kod źródłowy strony formularza, możesz zauważyć, że Nette wstawia wymagane elementy z klasą CSS `required`. Spróbuj dodać następujący arkusz stylów do szablonu, a etykieta "Nazwa" będzie czerwona. To elegancko podkreśli wymagane elementy dla użytkownika: +Jeśli spojrzysz do kodu źródłowego strony z formularzem, możesz zauważyć, że Nette obowiązkowe elementy wstawia do elementów z klasą CSS `required`. Spróbuj dodać do szablonu następujący arkusz stylów, a etykieta „Imię” będzie czerwona. Elegancko w ten sposób oznaczamy użytkownikom obowiązkowe elementy: ```latte <style> @@ -143,96 +140,96 @@ Jeśli spojrzysz na kod źródłowy strony formularza, możesz zauważyć, że N </style> ``` -Dodaj więcej reguł walidacji używając metody `addRule()`. Pierwszy parametr to reguła, drugi to ponownie tekst komunikatu o błędzie, po którym może nastąpić argument reguła walidacji. Co należy przez to rozumieć? +Kolejne reguły walidacyjne dodajemy metodą `addRule()`. Pierwszy parametr to reguła, drugi to ponownie tekst komunikatu błędu, a może jeszcze nastąpić argument reguły walidacyjnej. Co to oznacza? -Rozszerzamy formularz o nowe, opcjonalne pole "wiek", które musi być liczbą całkowitą (`addInteger()`), a także mieścić się w dozwolonym zakresie (`$form::Range`). Tutaj używamy trzeciego parametru metody `addRule()`, aby przekazać wymagany zakres do walidatora jako parę `[od, do]`: +Formularz rozszerzymy o nowe nieobowiązkowe pole „wiek”, które musi być liczbą całkowitą (`addInteger()`) i dodatkowo w dozwolonym zakresie (`$form::Range`). I tutaj właśnie wykorzystamy trzeci parametr metody `addRule()`, którym przekażemy walidatorowi wymagany zakres jako parę `[od, do]`: ```php -$form->addInteger('age', 'Věk:') - ->addRule($form::Range, 'Věk musí být od 18 do 120', [18, 120]); +$form->addInteger('age', 'Wiek:') + ->addRule($form::Range, 'Wiek musi być od 18 do 120', [18, 120]); ``` .[tip] -Jeśli użytkownik nie wypełni pola, reguły walidacji nie zostaną sprawdzone, ponieważ element jest opcjonalny. +Jeśli użytkownik nie wypełni pola, reguły walidacyjne nie będą sprawdzane, ponieważ element jest nieobowiązkowy. -Jest tu miejsce na drobną refaktoryzację. W komunikacie o błędzie i w trzecim parametrze liczby są zduplikowane, co nie jest idealne. Jeśli tworzyliśmy [formularze wielojęzyczne |rendering#translating], a wiadomość zawierająca liczby została przetłumaczona na wiele języków, utrudniłoby to zmianę wartości w razie potrzeby. Z tego powodu można użyć znaków zastępczych `%d`, a Nette wypełni wartości: +Tutaj powstaje przestrzeń na drobny refactoring. W komunikacie błędu i w trzecim parametrze liczby są podane podwójnie, co nie jest idealne. Gdybyśmy tworzyli [formularze wielojęzyczne |rendering#Tłumaczenie], a komunikat zawierający liczby byłby przetłumaczony na wiele języków, utrudniłoby to ewentualną zmianę wartości. Z tego powodu możliwe jest użycie symboli zastępczych `%d`, a Nette uzupełni wartości: ```php - ->addRule($form::Range, 'Věk musí být od %d do %d let', [18, 120]); + ->addRule($form::Range, 'Wiek musi być od %d do %d lat', [18, 120]); ``` -Wróćmy do elementu `password`, który również czynimy obowiązkowym, i sprawdźmy minimalną długość hasła (`$form::MinLength`), ponownie używając znaku wieloznacznego: +Wróćmy do elementu `password`, który również uczynimy obowiązkowym i jeszcze zweryfikujemy minimalną długość hasła (`$form::MinLength`), ponownie z wykorzystaniem symbolu zastępczego: ```php -$form->addPassword('password', 'Heslo:') - ->setRequired('Zvolte si heslo') - ->addRule($form::MinLength, 'Heslo musí mít alespoň %d znaků', 8); +$form->addPassword('password', 'Hasło:') + ->setRequired('Wybierz hasło') + ->addRule($form::MinLength, 'Hasło musi mieć co najmniej %d znaków', 8); ``` -Dodajmy do formularza pole `passwordVerify`, w którym użytkownik wpisuje hasło jeszcze raz, w celu weryfikacji. Korzystając z reguł walidacji sprawdzamy, czy oba hasła są takie same (`$form::Equal`). A jako parametr umieszczamy odwołanie do pierwszego hasła za pomocą [nawiasów kwadratowych |#Access-to-Controls]: +Dodamy do formularza jeszcze pole `passwordVerify`, gdzie użytkownik poda hasło jeszcze raz, do kontroli. Za pomocą reguł walidacyjnych sprawdzimy, czy oba hasła są takie same (`$form::Equal`). A jako parametr podamy odwołanie do pierwszego hasła za pomocą [nawiasów kwadratowych |#Dostęp do elementów]: ```php -$form->addPassword('passwordVerify', 'Heslo pro kontrolu:') - ->setRequired('Zadejte prosím heslo ještě jednou pro kontrolu') - ->addRule($form::Equal, 'Hesla se neshodují', $form['password']) +$form->addPassword('passwordVerify', 'Hasło do kontroli:') + ->setRequired('Proszę podać hasło jeszcze raz do kontroli') + ->addRule($form::Equal, 'Hasła się nie zgadzają', $form['password']) ->setOmitted(); ``` -Za pomocą `setOmitted()` zaznaczyliśmy element, którego wartość tak naprawdę nas nie obchodzi i który istnieje tylko ze względu na walidację. Wartość nie jest przekazywana do `$data`. +Za pomocą `setOmitted()` oznaczyliśmy element, na którego wartości właściwie nam nie zależy i który istnieje tylko w celu walidacji. Wartość nie zostanie przekazana do `$data`. -W ten sposób mamy w pełni funkcjonalny formularz z walidacją w PHP i JavaScript. Możliwości walidacji Nette są znacznie szersze, możesz tworzyć warunki, mieć części strony wyświetlane i ukryte zgodnie z nimi itp. Możesz dowiedzieć się wszystkiego na ten temat w rozdziale poświęconym [walidacji formularzy |validation]. +Tym samym mamy gotowy w pełni funkcjonalny formularz z walidacją w PHP i JavaScript. Możliwości walidacyjne Nette są znacznie szersze, można tworzyć warunki, pozwalać według nich wyświetlać i ukrywać części strony itp. Wszystkiego dowiesz się w rozdziale o [walidacji formularzy|validation]. -Wartości domyślne .[#toc-default-values] -======================================== +Wartości domyślne +================= -Standardowo ustawiamy wartości domyślne dla elementów formularza: +Elementom formularza często ustawiamy wartości domyślne: ```php $form->addEmail('email', 'E-mail') ->setDefaultValue($lastUsedEmail); ``` -Często przydatne jest ustawienie wartości domyślnych dla wszystkich elementów jednocześnie. Na przykład, gdy formularz jest używany do edycji rekordów. Odczytaj rekord z bazy danych i ustaw wartości domyślne: +Często przydaje się ustawienie wartości domyślnych wszystkim elementom jednocześnie. Na przykład, gdy formularz służy do edycji rekordów. Odczytujemy rekord z bazy danych i ustawiamy wartości domyślne: ```php //$row = ['name' => 'John', 'age' => '33', /* ... */]; $form->setDefaults($row); ``` -Po zdefiniowaniu elementów wywołaj `setDefaults()`. +Wywołuj `setDefaults()` dopiero po zdefiniowaniu elementów. -Rendering formularza .[#toc-rendering-the-form] -=============================================== +Renderowanie formularza +======================= -Domyślnie formularz jest renderowany jako tabela. Poszczególne elementy spełniają podstawową zasadę dostępności - wszystkie etykiety są zapisane jako `<label>` i powiązany z odpowiednim elementem formularza. Po kliknięciu na etykietę, kursor automatycznie pojawia się w polu formularza. +Standardowo formularz renderuje się jako tabela. Poszczególne elementy spełniają podstawową zasadę dostępności - wszystkie etykiety są zapisane jako `<label>` i powiązane z odpowiednim elementem formularza. Po kliknięciu na etykietę kursor automatycznie pojawia się w polu formularza. -Dla każdego elementu możemy ustawić dowolne atrybuty HTML. Na przykład dodać placeholder: +Każdemu elementowi możemy ustawiać dowolne atrybuty HTML. Na przykład dodać placeholder: ```php -$form->addInteger('age', 'Věk:') - ->setHtmlAttribute('placeholder', 'Prosím vyplňte věk'); +$form->addInteger('age', 'Wiek:') + ->setHtmlAttribute('placeholder', 'Proszę wypełnić wiek'); ``` -Istnieje wiele sposobów renderowania formularza, dlatego [na temat renderowania|rendering] jest [osobny rozdział |rendering]. +Sposobów renderowania formularza jest naprawdę wiele, dlatego poświęcono temu [osobny rozdział o renderowaniu|rendering]. -Odwzorowanie na klasy .[#toc-mapping-to-classes] -================================================ +Mapowanie na klasy +================== -Wróćmy do metody `formSucceeded()`, która w drugim parametrze `$data` pobiera wysłane dane jako obiekt `ArrayHash`. Ponieważ jest to klasa generyczna, coś w rodzaju `stdClass`, zabraknie nam pewnych udogodnień podczas pracy z nią, takich jak uzupełnianie kodu dla właściwości w edytorach czy statyczna analiza kodu. Można to rozwiązać poprzez posiadanie specyficznej klasy dla każdego formularza, której właściwości reprezentują poszczególne kontrolki. Np: +Wróćmy do metody `formSucceeded()`, która w drugim parametrze `$data` otrzymuje wysłane dane jako obiekt `ArrayHash`. Ponieważ jest to klasa generyczna, coś jak `stdClass`, podczas pracy z nią będzie nam brakować pewnego komfortu, jak na przykład podpowiadania właściwości w edytorach czy statycznej analizy kodu. Można by to rozwiązać, tworząc dla każdego formularza konkretną klasę, której właściwości reprezentują poszczególne elementy. Np.: ```php class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -Od PHP 8.0 możesz użyć tej eleganckiej notacji, która wykorzystuje konstruktor: +Alternatywnie możesz wykorzystać konstruktor: ```php class RegistrationFormData @@ -246,27 +243,29 @@ class RegistrationFormData } ``` -Jak powiedzieć Nette, aby zwracała dane jako obiekty tej klasy? Łatwiejsze niż myślisz. Wystarczy określić klasę jako typ parametru `$data` w metodzie handler: +Właściwości klasy danych mogą być również enumami i zostaną automatycznie zmapowane. .{data-version:3.2.4} + +Jak powiedzieć Nette, aby zwracał nam dane jako obiekty tej klasy? Łatwiej niż myślisz. Wystarczy tylko podać klasę jako typ parametru `$data` w metodzie obsługującej: ```php public function formSucceeded(Form $form, RegistrationFormData $data): void { - // $name je instancja RegistrationFormData + // $data jest instancją RegistrationFormData $name = $data->name; // ... } ``` -Możesz również określić `array` jako typ, a następnie przekazać dane jako tablicę. +Jako typ można również podać `array` a wtedy dane przekaże jako tablicę. -W podobny sposób można użyć funkcji `getValues()`, aby przekazać jako parametr nazwę klasy lub obiektu, który ma zostać uwodniony: +Podobnym sposobem można używać również funkcji `getValues()`, której nazwę klasy lub obiekt do hydratacji przekażemy jako parametr: ```php $data = $form->getValues(RegistrationFormData::class); $name = $data->name; ``` -Jeśli formularze tworzą wielopoziomową strukturę złożoną z kontenerów, utwórz dla każdego z nich osobną klasę: +Jeśli formularze tworzą wielopoziomową strukturę złożoną z kontenerów, utwórz dla każdego osobną klasę: ```php $form = new Form; @@ -283,32 +282,34 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` -Mapowanie wie wtedy z typu właściwości `$person`, że powinno mapować kontener do klasy `PersonFormData`. Jeśli właściwość zawierałaby tablicę kontenerów, określ typ `array` i przekaż klasę mapowania bezpośrednio do kontenera: +Mapowanie następnie z typu właściwości `$person` rozpozna, że ma mapować kontener na klasę `PersonFormData`. Jeśli właściwość zawierałaby tablicę kontenerów, podaj typ `array` a klasę do mapowania przekaż bezpośrednio kontenerowi: ```php $person->setMappedType(PersonFormData::class); ``` +Projekt klasy danych formularza możesz sobie wygenerować za pomocą metody `Nette\Forms\Blueprint::dataClass($form)`, która wypisze go na stronie przeglądarki. Kod następnie wystarczy kliknięciem zaznaczyć i skopiować do projektu. .{data-version:3.1.15} + -Więcej przycisków .[#toc-multiple-submit-buttons] -================================================= +Wiele przycisków +================ -Jeśli formularz ma więcej niż jeden przycisk, zwykle musimy rozróżnić, który z nich został naciśnięty. Możemy stworzyć niestandardowy handler dla każdego przycisku. Ustawiamy go jako handler dla [zdarzenia |nette:glossary#Events] `onClick`: +Jeśli formularz ma więcej niż jeden przycisk, zazwyczaj potrzebujemy rozróżnić, który z nich został naciśnięty. Możemy dla każdego przycisku utworzyć własną funkcję obsługującą. Ustawimy ją jako handler dla [zdarzenia |nette:glossary#Eventy zdarzenia] `onClick`: ```php -$form->addSubmit('save', 'Uložit') +$form->addSubmit('save', 'Zapisz') ->onClick[] = [$this, 'saveButtonPressed']; -$form->addSubmit('delete', 'Smazat') +$form->addSubmit('delete', 'Usuń') ->onClick[] = [$this, 'deleteButtonPressed']; ``` -Te handlery są wywoływane tylko wtedy, gdy formularz jest poprawnie wypełniony, podobnie jak zdarzenie `onSuccess`. Różnica polega na tym, że przycisk submit może być przekazany jako pierwszy parametr zamiast formularza, w zależności od typu, który określisz: +Te handlery są wywoływane tylko w przypadku poprawnie wypełnionego formularza, tak samo jak w przypadku zdarzenia `onSuccess`. Różnica polega na tym, że jako pierwszy parametr zamiast formularza może zostać przekazany przycisk wysyłania, zależy to od typu, który podasz: ```php public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) @@ -318,62 +319,61 @@ public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) } ``` -Gdy formularz zostanie przesłany za pomocą przycisku <kbd>Enter</kbd>, jest traktowany tak, jakby został przesłany za pomocą pierwszego przycisku. +Kiedy formularz zostanie wysłany przyciskiem <kbd>Enter</kbd>, traktuje się to tak, jakby został wysłany pierwszym przyciskiem. -Zdarzenie onAnchor .[#toc-event-onanchor] -========================================= +Zdarzenie onAnchor +================== -Kiedy budujemy formularz w metodzie fabrycznej (takiej jak `createComponentRegistrationForm`), nie wie on jeszcze, czy został złożony, ani z jakimi danymi. Istnieją jednak przypadki, w których musimy znać przekazane wartości, być może w celu wyprowadzenia następnego formularza formularza lub dla zależnych pól wyboru itp. +Kiedy w metodzie fabrykującej (jak np. `createComponentRegistrationForm`) budujemy formularz, ten jeszcze nie wie, czy został wysłany, ani z jakimi danymi. Są jednak przypadki, gdy potrzebujemy znać wysłane wartości, na przykład od nich zależy dalsza postać formularza, lub potrzebujemy ich do zależnych pól wyboru itp. -Dlatego możesz mieć kod budujący formularz wywoływany tylko wtedy, gdy jest zakotwiczony, czyli jest już połączony z prezenterem i zna jego przesłane dane. Taki kod przekazujemy do pola `$onAnchor`: +Część kodu budującego formularz możesz więc pozwolić wywołać dopiero w momencie, gdy jest tzw. zakotwiczony, czyli jest już połączony z prezenterem i zna swoje wysłane dane. Taki kod przekażemy do tablicy `$onAnchor`: ```php -$country = $form->addSelect('country', 'State:', $this->model->getCountries()); +$country = $form->addSelect('country', 'Państwo:', $this->model->getCountries()); $city = $form->addSelect('city', 'Miasto:'); -$form->onAnchor[] = function () use ($country, $city) - // ta funkcja zostanie wywołana, gdy formularz będzie wiedział, czy został przesłany i z jakimi danymi - // więc możesz użyć metody getValue() +$form->onAnchor[] = function () use ($country, $city) { + // ta funkcja zostanie wywołana dopiero, gdy formularz będzie wiedział, czy został wysłany i z jakimi danymi + // można więc używać metody getValue() $val = $country->getValue(); $city->setItems($val ? $this->model->getCities($val) : []); }; ``` -Ochrona przed podatnością na zagrożenia .[#toc-vulnerability-protection] -======================================================================== +Ochrona przed podatnościami +=========================== -Nette Framework przykłada dużą wagę do bezpieczeństwa i dlatego skrupulatnie dba o to, aby formularze były dobrze zabezpieczone. Robi to w sposób całkowicie przejrzysty i nie wymaga ręcznej konfiguracji. +Nette Framework kładzie duży nacisk na bezpieczeństwo i dlatego skrupulatnie dba o dobre zabezpieczenie formularzy. Robi to całkowicie transparentnie i nie wymaga ręcznego ustawiania czegokolwiek. -Oprócz ochrony formularzy przed atakami [Cross Site Scripting (XSS) |nette:glossary#Cross-Site-Scripting-XSS] i [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site-Request-Forgery-CSRF], robi wiele małych rzeczy związanych z bezpieczeństwem, o których nie musisz myśleć. +Oprócz tego, że formularze chronią przed atakiem [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] i [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF], wykonuje wiele drobnych zabezpieczeń, o których Ty już nie musisz myśleć. -Na przykład filtruje wszystkie znaki kontrolne z danych wejściowych i sprawdza ważność kodowania UTF-8, więc dane formularza zawsze będą czyste. W przypadku pól wyboru i arkuszy radiowych weryfikuje, czy wybrane pozycje rzeczywiście pochodziły z oferowanych i nie zostały spreparowane. Wspomnieliśmy już, że dla jednolinijkowych danych tekstowych, usuwa ona znaki końca linii, które mógłby wysłać atakujący. Dla wejść wieloliniowych normalizuje znaki dla przerw w linii. I tak dalej. +Na przykład odfiltrowuje ze wejść wszystkie znaki kontrolne i sprawdza poprawność kodowania UTF-8, dzięki czemu dane z formularza zawsze będą czyste. W polach wyboru i listach radio sprawdza, czy wybrane pozycje były rzeczywiście z oferowanych i czy nie doszło do fałszerstwa. Już wspominaliśmy, że w jednoliniowych wejściach tekstowych usuwa znaki końca linii, które mógł tam wysłać atakujący. W wieloliniowych wejściach z kolei normalizuje znaki końca linii. I tak dalej. -Nette rozwiązuje za Ciebie zagrożenia bezpieczeństwa, o których istnieniu wielu programistów nawet nie wie. +Nette rozwiązuje za Ciebie ryzyka bezpieczeństwa, o których wielu programistów nawet nie wie, że istnieją. -Wspomniany wcześniej atak CSRF polega na tym, że atakujący zwabia ofiarę na stronę, która w subtelny sposób wykonuje w przeglądarce ofiary żądanie do serwera, na którym ofiara jest zalogowana, a serwer zakłada, że żądanie zostało wykonane przez ofiarę z własnej woli. Dlatego Nette zapobiega wysyłaniu formularza POST z innej domeny. Jeśli z jakiegoś powodu chcesz wyłączyć ochronę i pozwolić na wysłanie formularza z innej domeny, użyj: +Wspomniany atak CSRF polega na tym, że atakujący zwabia ofiarę na stronę, która niepozornie w przeglądarce ofiary wykonuje żądanie do serwera, na którym ofiara jest zalogowana, a serwer uważa, że żądanie wykonała ofiara z własnej woli. Dlatego Nette zapobiega wysyłaniu formularza POST z innej domeny. Jeśli z jakiegoś powodu chcesz wyłączyć ochronę i pozwolić na wysyłanie formularza z innej domeny, użyj: ```php $form->allowCrossOrigin(); // UWAGA! Wyłącza ochronę! ``` -Ta ochrona wykorzystuje plik cookie SameSite o nazwie `_nss`. Ochrona plików cookie SameSite może nie być w 100% niezawodna, dlatego zaleca się włączenie ochrony tokenów: +Ta ochrona wykorzystuje ciasteczko SameSite o nazwie `_nss`. Ochrona za pomocą ciasteczka SameSite może nie być w 100% niezawodna, dlatego warto włączyć jeszcze ochronę za pomocą tokenu: ```php $form->addProtection(); ``` -Zalecane jest zabezpieczenie w ten sposób formularzy w części administracyjnej witryny, które zmieniają wrażliwe dane w aplikacji. Framework broni się przed atakami CSRF, generując i weryfikując token autoryzacji, który jest przechowywany w sesji. W związku z tym konieczne jest, aby sesja była otwarta, zanim formularz będzie mógł być przeglądany. W części administracyjnej witryny sesja jest już zazwyczaj rozpoczęta z powodu zalogowania się użytkownika. -W przeciwnym razie rozpocznij sesję za pomocą metody `Nette\Http\Session::start()`. +Zalecamy w ten sposób chronić formularze w administracyjnej części witryny, które zmieniają wrażliwe dane w aplikacji. Framework broni się przed atakiem CSRF poprzez wygenerowanie i weryfikację tokenu autoryzacyjnego, który jest przechowywany w sesji. Dlatego konieczne jest, aby przed wyświetleniem formularza sesja była otwarta. W administracyjnej części witryny zazwyczaj sesja jest już uruchomiona z powodu logowania użytkownika. W przeciwnym razie uruchom sesję metodą `Nette\Http\Session::start()`. -Ten sam formularz w wielu prezenterach .[#toc-using-one-form-in-multiple-presenters] -==================================================================================== +Ten sam formularz w wielu prezenterach +====================================== -Jeśli potrzebujesz użyć jednego formularza w wielu prezenterach, zalecamy utworzenie dla niego fabryki, którą następnie przekazujesz do prezentera. Odpowiednim miejscem dla takiej klasy jest na przykład katalog `app/Forms`. +Jeśli potrzebujesz użyć jednego formularza w wielu prezenterach, zalecamy stworzenie dla niego fabryki, którą następnie przekażesz do prezentera. Odpowiednim miejscem dla takiej klasy jest np. katalog `app/Forms`. -Klasa fabryczna może wyglądać tak: +Klasa fabryki może wyglądać na przykład tak: ```php use Nette\Application\UI\Form; @@ -383,14 +383,14 @@ class SignInFormFactory public function create(): Form { $form = new Form; - $form->addText('name', 'Jméno:'); - $form->addSubmit('send', 'Přihlásit se'); + $form->addText('name', 'Imię:'); + $form->addSubmit('send', 'Zaloguj się'); return $form; } } ``` -Prosimy klasę o wyprodukowanie formularza w metodzie factory na komponentach w prezenterze: +Klasę poprosimy o wyprodukowanie formularza w metodzie fabrykującej na komponenty w prezenterze: ```php public function __construct( @@ -401,14 +401,14 @@ public function __construct( protected function createComponentSignInForm(): Form { $form = $this->formFactory->create(); - // możemy modyfikować formularz, tutaj na przykład zmieniamy etykietę na przycisku - $form['send']->setCaption('Pokračovat'); - $form->onSuccess[] = [$this, 'signInFormSuceeded']; // i dodaj handler + // możemy formularz zmodyfikować, tutaj na przykład zmieniamy etykietę na przycisku + $form['send']->setCaption('Kontynuuj'); + $form->onSuccess[] = [$this, 'signInFormSuceeded']; // i dodajemy handler return $form; } ``` -Obsługa formularza może być również dostarczona z fabryki: +Handler do przetwarzania formularza może być również dostarczony już z fabryki: ```php use Nette\Application\UI\Form; @@ -418,14 +418,14 @@ class SignInFormFactory public function create(): Form { $form = new Form; - $form->addText('name', 'Jméno:'); - $form->addSubmit('send', 'Přihlásit se'); + $form->addText('name', 'Imię:'); + $form->addSubmit('send', 'Zaloguj się'); $form->onSuccess[] = function (Form $form, $data): void { - // zde provedeme zpracování formuláře + // tutaj wykonamy przetwarzanie formularza }; return $form; } } ``` -Mamy więc za sobą szybkie wprowadzenie do formularzy w Nette. Spróbuj spojrzeć w katalogu [przykładów |https://github.com/nette/forms/tree/master/examples] w dystrubucji, aby uzyskać więcej inspiracji. +Tak, mamy za sobą szybkie wprowadzenie do formularzy w Nette. Spróbuj jeszcze zajrzeć do katalogu [examples|https://github.com/nette/forms/tree/master/examples] w dystrybucji, gdzie znajdziesz dalszą inspirację. diff --git a/forms/pl/rendering.texy b/forms/pl/rendering.texy index b224e8d73d..7d24fb440a 100644 --- a/forms/pl/rendering.texy +++ b/forms/pl/rendering.texy @@ -1,33 +1,35 @@ -Rendering formularzy -******************** +Renderowanie formularzy +*********************** -Wygląd form może być bardzo zróżnicowany. W praktyce możemy spotkać się z dwoma skrajnościami. Z jednej strony istnieje potrzeba renderowania w aplikacji serii formularzy, które są do siebie wizualnie podobne, a my cenimy sobie łatwe renderowanie bez szablonu za pomocą `$form->render()`. Taka sytuacja ma miejsce zazwyczaj w przypadku interfejsów administracyjnych. +Wygląd formularzy może być bardzo różnorodny. W praktyce możemy napotkać dwa ekstrema. Z jednej strony stoi potrzeba renderowania w aplikacji wielu formularzy, które są wizualnie podobne jak dwie krople wody, i docenimy łatwe renderowanie bez szablonu za pomocą `$form->render()`. Jest to zazwyczaj przypadek interfejsów administracyjnych. -Z drugiej strony istnieją różne formularze, gdzie każdy z nich jest unikalny. Ich wygląd najlepiej opisać za pomocą języka HTML w szablonie. I oczywiście oprócz obu wymienionych skrajności spotkamy wiele form, które mieszczą się gdzieś pomiędzy. +Z drugiej strony mamy różnorodne formularze, gdzie obowiązuje zasada: co sztuka, to oryginał. Ich postać najlepiej opiszemy językiem HTML w szablonie formularza. I oczywiście oprócz obu wspomnianych ekstremów napotkamy wiele formularzy, które znajdują się gdzieś pomiędzy. -Rendering z Latte .[#toc-rendering-with-latte] -============================================== +Renderowanie za pomocą Latte +============================ -[System szablonów Latte |latte:] zasadniczo ułatwia renderowanie formularzy i ich elementów. Najpierw pokażemy, jak ręcznie renderować formularze element po elemencie i dzięki temu zyskać pełną kontrolę nad kodem. Później pokażemy, jak [zautomatyzować |#Automatické vykreslování] takie renderowanie. +[System szablonów Latte|latte:] znacznie ułatwia renderowanie formularzy i ich elementów. Najpierw pokażemy, jak renderować formularze ręcznie po poszczególnych elementach i tym samym uzyskać pełną kontrolę nad kodem. Później pokażemy, jak można takie renderowanie [zautomatyzować |#Automatyczne renderowanie]. + +Projekt szablonu Latte formularza możesz sobie wygenerować za pomocą metody `Nette\Forms\Blueprint::latte($form)`, która wypisze go na stronie przeglądarki. Kod następnie wystarczy kliknięciem zaznaczyć i skopiować do projektu. .{data-version:3.1.15} `{control}` ----------- -Najprostszym sposobem renderowania formularza jest zapisanie go w szablonie: +Najprostszym sposobem renderowania formularza jest napisanie w szablonie: ```latte {control signInForm} ``` -Możesz wpłynąć na wygląd renderowanego formularza, konfigurując [Renderer |#Renderer] i [poszczególne elementy |#HTML-Attributes]. +Można wpłynąć na wygląd tak renderowanego formularza konfigurując [#Renderer] i [poszczególne elementy |#Atrybuty HTML]. `n:name` -------- -Niezwykle łatwo jest połączyć definicję formularza w kodzie PHP z kodem HTML. Wystarczy dodać atrybuty `n:name`. To takie proste! +Definicję formularza w kodzie PHP można niezwykle łatwo powiązać z kodem HTML. Wystarczy tylko uzupełnić atrybuty `n:name`. Takie to proste! ```php protected function createComponentSignInForm(): Form @@ -43,10 +45,10 @@ protected function createComponentSignInForm(): Form ```latte <form n:name=signInForm class=form> <div> - <label n:name=username>Username: <input n:name=username size=20 autofocus></label> + <label n:name=username>Nazwa użytkownika: <input n:name=username size=20 autofocus></label> </div> <div> - <label n:name=password>Password: <input n:name=password></label> + <label n:name=password>Hasło: <input n:name=password></label> </div> <div> <input n:name=send class="btn btn-default"> @@ -54,10 +56,9 @@ protected function createComponentSignInForm(): Form </form> ``` -Forma powstałego kodu HTML jest całkowicie w Twoich rękach. Jeśli używasz atrybutu `n:name` na elementach `<select>`, `<button>` lub `<textarea>`, ich wewnętrzna zawartość jest automatycznie uzupełniana. -Znacznik `<form n:name>` dodatkowo tworzy zmienną lokalną `$form` z obiektem formularza kreskówki i zamknięciem `</form>` renderuje wszystkie niewykreślone elementy ukryte (to samo dotyczy strony `{form} ... {/form}`). +Postać wynikowego kodu HTML masz w pełni w swoich rękach. Jeśli atrybut `n:name` użyjesz w elementach `<select>`, `<button>` lub `<textarea>`, ich wewnętrzna zawartość zostanie automatycznie uzupełniona. Znacznik `<form n:name>` dodatkowo tworzy lokalną zmienną `$form` z obiektem renderowanego formularza, a zamykający `</form>` renderuje wszystkie niewyrenderowane elementy ukryte (to samo dotyczy również `{form} ... {/form}`). -Nie możemy jednak zapominać o oddaniu ewentualnych komunikatów o błędach. Zarówno te dodane do elementów metodą `addError()` (przy użyciu `{inputError}`), jak i te dodane bezpośrednio do formularza (zwrócone przez `$form->getOwnErrors()`): +Nie możemy jednak zapomnieć o renderowaniu możliwych komunikatów błędów. Zarówno tych, które metodą `addError()` zostały dodane do poszczególnych elementów (za pomocą `{inputError}`), jak i tych dodanych bezpośrednio do formularza (zwraca je `$form->getOwnErrors()`): ```latte <form n:name=signInForm class=form> @@ -66,11 +67,11 @@ Nie możemy jednak zapominać o oddaniu ewentualnych komunikatów o błędach. Z </ul> <div> - <label n:name=username>Username: <input n:name=username size=20 autofocus></label> + <label n:name=username>Nazwa użytkownika: <input n:name=username size=20 autofocus></label> <span class=error n:ifcontent>{inputError username}</span> </div> <div> - <label n:name=password>Password: <input n:name=password></label> + <label n:name=password>Hasło: <input n:name=password></label> <span class=error n:ifcontent>{inputError password}</span> </div> <div> @@ -79,7 +80,7 @@ Nie możemy jednak zapominać o oddaniu ewentualnych komunikatów o błędach. Z </form> ``` -Bardziej złożone elementy formularza, takie jak RadioList lub CheckboxList, mogą być renderowane element po elemencie w ten sposób: +Bardziej złożone elementy formularza, takie jak RadioList lub CheckboxList, można w ten sposób renderować po poszczególnych pozycjach: ```latte {foreach $form[gender]->getItems() as $key => $label} @@ -88,16 +89,10 @@ Bardziej złożone elementy formularza, takie jak RadioList lub CheckboxList, mo ``` -Projektowanie kodu `{formPrint}` .[#toc-formprint] --------------------------------------------------- - -Możesz mieć podobny kod Latte dla swojego formularza wygenerowanego za pomocą tagu `{formPrint}` Jeśli umieścisz go w szablonie, zobaczysz sugestię kodu zamiast normalnego renderowania. Następnie wystarczy go zaznaczyć i skopiować do swojego projektu. - - `{label}` `{input}` ------------------- -Dla każdego elementu nie chcesz myśleć o tym, jaki element HTML zastosować dla niego w szablonie, czy `<input>`, `<textarea>` itp.? Rozwiązaniem jest uniwersalny tag `{input}`: +Nie chcesz przy każdym elemencie zastanawiać się, jaki element HTML dla niego użyć w szablonie, czy `<input>`, `<textarea>` itp? Rozwiązaniem jest uniwersalny znacznik `{input}`: ```latte <form n:name=signInForm class=form> @@ -106,11 +101,11 @@ Dla każdego elementu nie chcesz myśleć o tym, jaki element HTML zastosować d </ul> <div> - {label username}Username: {input username, size: 20, autofocus: true}{/label} + {label username}Nazwa użytkownika: {input username, size: 20, autofocus: true}{/label} {inputError username} </div> <div> - {label password}Password: {input password}{/label} + {label password}Hasło: {input password}{/label} {inputError password} </div> <div> @@ -119,9 +114,9 @@ Dla każdego elementu nie chcesz myśleć o tym, jaki element HTML zastosować d </form> ``` -Jeśli formularz korzysta z translatora, tekst wewnątrz znaczników `{label}` zostanie przetłumaczony. +Jeśli formularz używa translatora, tekst wewnątrz znaczników `{label}` będzie tłumaczony. -Ponownie, bardziej złożone elementy formularza, takie jak RadioList lub CheckboxList, mogą być renderowane na podstawie poszczególnych pozycji: +Również w tym przypadku bardziej złożone elementy formularza, takie jak RadioList lub CheckboxList, można renderować po poszczególnych pozycjach: ```latte {foreach $form[gender]->items as $key => $label} @@ -129,20 +124,19 @@ Ponownie, bardziej złożone elementy formularza, takie jak RadioList lub Checkb {/foreach} ``` -Aby oddać rzeczywisty `<input>` w elemencie Checkbox, należy użyć `{input myCheckbox:}`. W tym przypadku zawsze należy oddzielić atrybuty HTML przecinkiem `{input myCheckbox:, class: required}`. +Do renderowania samego `<input>` w elemencie Checkbox użyj `{input myCheckbox:}`. Atrybuty HTML w tym przypadku zawsze oddzielaj przecinkiem `{input myCheckbox:, class: required}`. `{inputError}` -------------- -Wypisuje komunikat o błędzie dla elementu formularza, jeśli go posiada. Zazwyczaj zawijamy wiadomość w element HTML w celu stylizacji. -Unikanie renderowania pustego elementu, jeśli nie ma wiadomości, można elegancko zrobić z `n:ifcontent`: +Wypisuje komunikat błędu dla elementu formularza, jeśli jakiś ma. Komunikat zazwyczaj opakowujemy w element HTML w celu stylizacji. Zapobiec renderowaniu pustego elementu, jeśli komunikatu nie ma, można elegancko za pomocą `n:ifcontent`: ```latte <span class=error n:ifcontent>{inputError $input}</span> ``` -Możemy wykryć obecność błędu za pomocą metody `hasErrors()` i odpowiednio ustawić klasę elementu nadrzędnego: +Obecność błędu możemy sprawdzić metodą `hasErrors()` i według tego ustawić klasę nadrzędnemu elementowi: ```latte <div n:class="$form[username]->hasErrors() ? 'error'"> @@ -155,14 +149,13 @@ Możemy wykryć obecność błędu za pomocą metody `hasErrors()` i odpowiednio `{form}` -------- -Tagi `{form signInForm}...{/form}` są alternatywą dla `<form n:name="signInForm">...</form>`. +Znaczniki `{form signInForm}...{/form}` są alternatywą dla `<form n:name="signInForm">...</form>`. -Automatyczne renderowanie .[#toc-automatic-rendering] ------------------------------------------------------ +Automatyczne renderowanie +------------------------- -Dzięki znacznikom `{input}` i `{label}` możemy w prosty sposób stworzyć ogólny szablon dla dowolnego formularza. Będzie on iterował i renderował wszystkie swoje elementy sekwencyjnie, z wyjątkiem elementów ukrytych, które są renderowane automatycznie, gdy formularz zostanie zamknięty za pomocą znacznika `</form>`. -Będzie oczekiwał nazwy wyrenderowanego formularza w zmiennej `$form`. +Dzięki znacznikom `{input}` i `{label}` możemy łatwo stworzyć ogólny szablon dla dowolnego formularza. Będzie on stopniowo iterował i renderował wszystkie jego elementy, oprócz elementów ukrytych, które renderują się automatycznie przy zakończeniu formularza znacznikiem `</form>`. Nazwę renderowanego formularza będzie oczekiwał w zmiennej `$form`. ```latte <form n:name=$form class=form> @@ -179,16 +172,15 @@ Będzie oczekiwał nazwy wyrenderowanego formularza w zmiennej `$form`. </form> ``` -Użyte samozamykające się znaczniki parami `{label .../}` pokazują etykiety pochodzące z definicji formularza w kodzie PHP. +Użyte samozamykające się znaczniki parzyste `{label .../}` wyświetlają etykiety pochodzące z definicji formularza w kodzie PHP. -Na przykład zapisz ten ogólny szablon w pliku `basic-form.latte` i aby wyrenderować formularz, wystarczy go inline i przekazać nazwę formularza (lub instancję) do parametru `$form`: +Ten ogólny szablon zapisz sobie na przykład do pliku `basic-form.latte`, a do renderowania formularza wystarczy go dołączyć i przekazać nazwę (lub instancję) formularza do parametru `$form`: ```latte {include basic-form.latte, form: signInForm} ``` -Jeśli chciałbyś ingerować w formularz podczas renderowania jednego konkretnego formularza, a być może renderować jeden element inaczej, to najprostszym sposobem jest posiadanie w szablonie gotowych bloków, które można później nadpisać. -Bloki mogą mieć również [dynamiczne nazwy |latte:template-inheritance#Dynamic-Block-Names], więc można do nich wstawić nazwę elementu, który ma być narysowany. Na przykład: +Gdybyś przy renderowaniu jednego określonego formularza chciał wpłynąć na jego postać i na przykład jeden element wyrenderować inaczej, najprostszą drogą jest przygotowanie sobie w szablonie bloków, które będzie można następnie nadpisać. Bloki mogą mieć również [nazwy dynamiczne |latte:template-inheritance#Dynamiczne nazwy bloków], można w nie wstawić również nazwę renderowanego elementu. Na przykład: ```latte ... @@ -197,7 +189,7 @@ Bloki mogą mieć również [dynamiczne nazwy |latte:template-inheritance#Dynami ... ``` -Dla elementu np. `username` tworzy to blok `input-username`, który można łatwo nadpisać za pomocą znacznika [{embed} |latte:template-inheritance#Unit-Inheritance]: +Dla elementu np. `username` powstanie blok `input-username`, który można łatwo nadpisać użyciem znacznika [{embed} |latte:template-inheritance#Dziedziczenie jednostkowe]: ```latte {embed basic-form.latte, form: signInForm} @@ -209,7 +201,7 @@ Dla elementu np. `username` tworzy to blok `input-username`, który można łatw {/embed} ``` -Alternatywnie, cała zawartość szablonu `basic-form.latte` może [być zdefiniowana |latte:template-inheritance#Definitions] jako blok, łącznie z parametrem `$form`: +Alternatywnie można całą zawartość szablonu `basic-form.latte` [zdefiniować |latte:template-inheritance#Definicje define] jako blok, włącznie z parametrem `$form`: ```latte {define basic-form, $form} @@ -219,7 +211,7 @@ Alternatywnie, cała zawartość szablonu `basic-form.latte` może [być zdefini {/define} ``` -Dzięki temu będzie nieco łatwiejszy do wywołania: +Dzięki temu jego wywołanie będzie nieco prostsze: ```latte {embed basic-form, signInForm} @@ -227,31 +219,31 @@ Dzięki temu będzie nieco łatwiejszy do wywołania: {/embed} ``` -Blok wystarczy zaimportować tylko w jednym miejscu, na początku szablonu układu: +Blok przy tym wystarczy zaimportować w jednym miejscu, na początku szablonu layoutu: ```latte {import basic-form.latte} ``` -Szczególne przypadki .[#toc-special-cases] ------------------------------------------- +Przypadki specjalne +------------------- -Jeśli chcesz wyrenderować tylko wewnętrzną część formularza bez znaczników HTML `<form>` & `</form>`, na przykład w żądaniu AJAX, możesz otworzyć i zamknąć formularz na `{formContext} … {/formContext}`. Działa to podobnie do `<form n:form>` lub `{form}` w sensie logicznym, tutaj pozwoli Ci użyć innych znaczników do rysowania elementów formularza, ale jednocześnie nie rysuje niczego. +Jeśli potrzebujesz wyrenderować tylko wewnętrzną część formularza bez znaczników HTML `<form>`, na przykład przy wysyłaniu snippetów, ukryj je za pomocą atrybutu `n:tag-if`: ```latte -{formContext signForm} +<form n:name=signInForm n:tag-if=false> <div> - <label n:name=username>Username: <input n:name=username></label> + <label n:name=username>Nazwa użytkownika: <input n:name=username></label> {inputError username} </div> -{/formContext} +</form> ``` -Znacznik `{formContainer}` pomoże w rysowaniu elementów wewnątrz kontenera formularza. +Z renderowaniem elementów wewnątrz kontenera formularza pomoże tag `{formContainer}`. ```latte -<p>Which news you wish to receive:</p> +<p>Które wiadomości chcesz otrzymywać:</p> {formContainer emailNews} <ul> @@ -262,8 +254,8 @@ Znacznik `{formContainer}` pomoże w rysowaniu elementów wewnątrz kontenera fo ``` -Rendering bez Latte .[#toc-rendering-without-latte] -=================================================== +Renderowanie bez Latte +====================== Najprostszym sposobem renderowania formularza jest wywołanie: @@ -271,18 +263,18 @@ Najprostszym sposobem renderowania formularza jest wywołanie: $form->render(); ``` -Możesz wpłynąć na wygląd renderowanego formularza, konfigurując [Renderer |#Renderer] i [poszczególne elementy |#HTML-Attributes]. +Można wpłynąć na wygląd tak renderowanego formularza konfigurując [#Renderer] i [poszczególne elementy |#Atrybuty HTML]. -Ręczne renderowanie .[#toc-manual-rendering] --------------------------------------------- +Ręczne renderowanie +------------------- -Każdy element formularza posiada metody, które generują kod HTML dla pola formularza i etykiet. Mogą one zwrócić go jako ciąg znaków lub obiekt [Nette\Utils\Html |utils:html-elements]: +Każdy element formularza dysponuje metodami, które generują kod HTML pola formularza i etykiety. Mogą go zwracać albo jako string, albo obiekt [Nette\Utils\Html|utils:html-elements]: - `getControl(): Html|string` zwraca kod HTML elementu -- `getLabel($caption = null): Html|string|null` zwraca kod HTML etykiety, jeżeli taki istnieje +- `getLabel($caption = null): Html|string|null` zwraca kod HTML etykiety, jeśli istnieje -Formularz może być więc renderowany element po elemencie: +Formularz można więc renderować po poszczególnych elementach: ```php <?php $form->render('begin') ?> @@ -305,47 +297,46 @@ Formularz może być więc renderowany element po elemencie: <?php $form->render('end') ?> ``` -Podczas gdy dla niektórych elementów `getControl()` zwraca pojedynczy element HTML (np. `<input>`, `<select>` itp.), dla innych cały fragment kodu HTML (CheckboxList, RadioList). -W takim przypadku możesz użyć metod, które generują indywidualne wejścia i etykiety, dla każdego elementu osobno: +Podczas gdy u niektórych elementów `getControl()` zwraca pojedynczy element HTML (np. `<input>`, `<select>` itp.), u innych cały fragment kodu HTML (CheckboxList, RadioList). W takim przypadku możesz wykorzystać metody, które generują poszczególne inputy i etykiety, dla każdej pozycji osobno: -- `getControlPart($key = null): ?Html` zwraca kod HTML jednego elementu -- `getLabelPart($key = null): ?Html` zwraca kod HTML dla etykiety jednego elementu +- `getControlPart($key = null): ?Html` zwraca kod HTML jednej pozycji +- `getLabelPart($key = null): ?Html` zwraca kod HTML etykiety jednej pozycji .[note] -Metody te mają przedrostek `get`, ze względów historycznych, ale `generate` byłby lepszy, ponieważ tworzy i zwraca nowy element `Html` przy każdym wywołaniu. +Te metody mają z historycznych powodów prefiks `get`, ale lepszy byłby `generate`, ponieważ przy każdym wywołaniu tworzą i zwracają nowy element `Html`. -Renderer .[#toc-renderer] -========================= +Renderer +======== -Jest to obiekt, który renderuje formularz. Można go ustawić za pomocą metody `$form->setRenderer` Jest przekazywany kontroli, gdy wywoływana jest metoda `$form->render()`. +Jest to obiekt zapewniający renderowanie formularza. Można go ustawić metodą `$form->setRenderer`. Przekazuje mu się sterowanie przy wywołaniu metody `$form->render()`. -Jeśli nie ustawimy niestandardowego renderera, zostanie użyty domyślny renderer [api:Nette\Forms\Rendering\DefaultFormRenderer]. Spowoduje to renderowanie elementów formularza jako tabeli HTML. Dane wyjściowe wyglądają tak: +Jeśli nie ustawimy własnego renderera, zostanie użyty domyślny renderer [api:Nette\Forms\Rendering\DefaultFormRenderer]. Ten renderuje elementy formularza w postaci tabeli HTML. Wyjście wygląda tak: ```latte <table> <tr class="required"> - <th><label class="required" for="frm-name">Jméno:</label></th> + <th><label class="required" for="frm-name">Imię:</label></th> <td><input type="text" class="text" name="name" id="frm-name" required value=""></td> </tr> <tr class="required"> - <th><label class="required" for="frm-age">Věk:</label></th> + <th><label class="required" for="frm-age">Wiek:</label></th> <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> </tr> <tr> - <th><label>Pohlaví:</label></th> + <th><label>Płeć:</label></th> ... ``` -To, czy używać tabeli do szkieletu formularza, czy nie, jest kontrowersyjne i wielu projektantów stron internetowych preferuje inny narzut. Na przykład lista definicji. Dlatego przekonfigurujmy `DefaultFormRenderer`, aby renderować formularz jako listę. Konfiguracja odbywa się poprzez edycję pola [$wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers]. Pierwszy indeks zawsze reprezentuje region, a drugi jego atrybut. Poszczególne pola zostały przedstawione na rysunku: +Czy używać, czy nie używać tabeli dla szkieletu formularza, jest kwestią sporną, a wielu webdesignerów preferuje inne znaczniki. Na przykład listę definicji. Przekonfigurujemy więc `DefaultFormRenderer` tak, aby formularz wyrenderował w postaci listy. Konfiguracja odbywa się przez edycję tablicy [$wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers]. Pierwszy indeks zawsze reprezentuje obszar, a drugi jego atrybut. Poszczególne obszary ilustruje obrazek: [* defaultformrenderer.webp *] -Domyślnie grupa elementów `controls` jest opakowana w tablicę `<table>`, każdy `pair` reprezentuje wiersz tabeli `<tr>` a pary `label` i `control` są komórkami `<th>` a `<td>`. Teraz zmieniamy elementy opakowujące. Do pojemnika wkładamy obszar `controls` `<dl>`, pozostawić obszar `pair` niezamknięty, a `label` umieścić w `<dt>` i wreszcie `control` jest owinięty tagami `<dd>`: +Standardowo grupa elementów `controls` jest opakowana tabelą `<table>`, każdy `pair` reprezentuje wiersz tabeli `<tr>`, a para `label` i `control` są komórkami `<th>` i `<td>`. Teraz zmienimy elementy opakowujące. Obszar `controls` włożymy do kontenera `<dl>`, obszar `pair` zostawimy bez kontenera, `label` włożymy do `<dt>`, a na końcu `control` opakujemy znacznikami `<dd>`: ```php $renderer = $form->getRenderer(); @@ -357,193 +348,192 @@ $renderer->wrappers['control']['container'] = 'dd'; $form->render(); ``` -W rezultacie otrzymujemy taki oto kod HTML: +Wynikiem jest ten kod HTML: ```latte <dl> - <dt><label class="required" for="frm-name">Jméno:</label></dt> + <dt><label class="required" for="frm-name">Imię:</label></dt> <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> - <dt><label class="required" for="frm-age">Věk:</label></dt> + <dt><label class="required" for="frm-age">Wiek:</label></dt> <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> - <dt><label>Pohlaví:</label></dt> + <dt><label>Płeć:</label></dt> ... </dl> ``` -W polu wrappers można wpływać na szereg innych atrybutów: +W tablicy wrappers można wpłynąć na wiele innych atrybutów: -- dodać klasy CSS do poszczególnych typów elementów formularza -- rozróżnienie między nieparzystymi i parzystymi wierszami według klasy CSS -- wizualne rozróżnienie elementów wymaganych i opcjonalnych -- określić, czy komunikaty o błędach są wyświetlane bezpośrednio przy elementach czy nad formularzem +- dodawać klasy CSS poszczególnym typom elementów formularza +- rozróżniać klasą CSS wiersze parzyste i nieparzyste +- wizualnie odróżniać pozycje obowiązkowe i opcjonalne +- określać, czy komunikaty błędów wyświetlą się bezpośrednio przy elementach, czy nad formularzem -Opcje .[#toc-options] ---------------------- +Opcje +----- -Zachowanie Renderera może być również kontrolowane poprzez ustawienie *opcji* na poszczególnych elementach formularza. W ten sposób można ustawić etykietę, która jest wyświetlana obok pola wejściowego: +Zachowanie Renderera można kontrolować również ustawiając *opcje* na poszczególnych elementach formularza. W ten sposób można ustawić opis, który wypisze się obok pola wejściowego: ```php -$form->addText('phone', 'Číslo:') - ->setOption('description', 'Toto číslo zůstane skryté'); +$form->addText('phone', 'Numer telefonu:') + ->setOption('description', 'Ten numer pozostanie ukryty'); ``` -Jeśli chcemy umieścić w nim treść HTML, używamy klasy [Html |utils:html-elements] +Jeśli chcemy w nim umieścić zawartość HTML, wykorzystamy klasę [Html |utils:html-elements] ```php use Nette\Utils\Html; -$form->addText('phone', 'Číslo:') +$form->addText('phone', 'Numer telefonu:') ->setOption('description', Html::el('p') - ->setHtml('<a href="...">Podmínky uchovávání Vašeho čísla</a>') + ->setHtml('<a href="...">Warunki przechowywania Twojego numeru</a>') ); ``` .[tip] -Zamiast etykiety można również użyć elementu Html: `$form->addCheckbox('conditions', $label)`. +Element Html można wykorzystać również zamiast etykiety: `$form->addCheckbox('conditions', $label)`. -Grupowanie elementów .[#toc-grouping-inputs] --------------------------------------------- +Grupowanie elementów +-------------------- -Renderer pozwala na grupowanie elementów w wizualne grupy (fieldsets): +Renderer umożliwia grupowanie elementów w wizualne grupy (fieldsety): ```php -$form->addGroup('Personal data'); +$form->addGroup('Dane osobowe'); ``` -Po utworzeniu nowej grupy staje się ona aktywna i każdy nowo dodany element jest do niej dodawany. Zatem formularz można zbudować w ten sposób: +Po utworzeniu nowej grupy staje się ona aktywna i każdy nowo dodany element jest jednocześnie dodawany również do niej. Więc formularz można budować w ten sposób: ```php $form = new Form; -$form->addGroup('Personal data'); -$form->addText('name', 'Your name:'); -$form->addInteger('age', 'Your age:'); +$form->addGroup('Dane osobowe'); +$form->addText('name', 'Twoje imię:'); +$form->addInteger('age', 'Twój wiek:'); $form->addEmail('email', 'Email:'); -$form->addGroup('Shipping address'); -$form->addCheckbox('send', 'Ship to address'); -$form->addText('street', 'Street:'); -$form->addText('city', 'City:'); -$form->addSelect('country', 'Country:', $countries); +$form->addGroup('Adres wysyłki'); +$form->addCheckbox('send', 'Wyślij na adres'); +$form->addText('street', 'Ulica:'); +$form->addText('city', 'Miasto:'); +$form->addSelect('country', 'Kraj:', $countries); ``` +Renderer najpierw renderuje grupy, a dopiero potem elementy, które do żadnej grupy nie należą. + -Wsparcie dla Bootstrap .[#toc-bootstrap-support] ------------------------------------------------- +Wsparcie dla Bootstrap +---------------------- -[W przykładach |https://github.com/nette/forms/tree/master/examples] znajdziesz przykłady jak skonfigurować Renderer dla [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], [Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] i [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php] +[W przykładach |https://github.com/nette/forms/tree/master/examples] znajdziesz przykłady, jak skonfigurować Renderer dla [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], [Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] i [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php] -Atrybuty HTML .[#toc-html-attributes] -===================================== +Atrybuty HTML +============= -Możemy ustawić dowolne atrybuty HTML do elementów formularza za pomocą `setHtmlAttribute(string $name, $value = true)`: +Do ustawienia dowolnych atrybutów HTML elementów formularza użyjemy metody `setHtmlAttribute(string $name, $value = true)`: ```php -$form->addInteger('liczba', 'Liczba:') +$form->addInteger('number', 'Numer:') ->setHtmlAttribute('class', 'big-number'); -$form->addSelect('rank', 'Rank by:', ['cena', 'nazwa']) - ->setHtmlAttribute('onchange', 'submit()'); // submit on change +$form->addSelect('rank', 'Sortuj wg:', ['ceny', 'nazwy']) + ->setHtmlAttribute('onchange', 'submit()'); // wysłać przy zmianie -// jeśli chcemy zrobić to samo dla <form> +// Do ustawienia atrybutów samego <form> $form->setHtmlAttribute('id', 'myForm'); ``` -Ustawienia typu: +Specyfikacja typu elementu: ```php -$form->addText('tel', 'Váš telefon:') +$form->addText('tel', 'Twój telefon:') ->setHtmlType('tel') - ->setHtmlAttribute('placeholder', 'napište telefon'); + ->setHtmlAttribute('placeholder', 'wpisz numer telefonu'); ``` -Możemy ustawić atrybut HTML dla poszczególnych pozycji w listach radio lub checkbox z różnymi wartościami dla każdej z nich. -Zwróć uwagę na dwukropek po `style:`, który zapewni, że wartość zostanie wybrana zgodnie z kluczem: +.[warning] +Ustawienie typu i innych atrybutów służy tylko do celów wizualnych. Weryfikacja poprawności wejść musi odbywać się na serwerze, co zapewnisz wyborem odpowiedniego [elementu formularza|controls] i podaniem [reguł walidacyjnych|validation]. + +Poszczególnym pozycjom w listach radio lub checkbox możemy ustawić atrybut HTML z różnymi wartościami dla każdej z nich. Zwróć uwagę na dwukropek za `style:`, który zapewnia wybór wartości według klucza: ```php -$colors = ['r' => 'červená', 'g' => 'zelená', 'b' => 'modrá']; +$colors = ['r' => 'czerwony', 'g' => 'zielony', 'b' => 'niebieski']; $styles = ['r' => 'background:red', 'g' => 'background:green']; -$form->addCheckboxList('colors', 'Barvy:', $colors) +$form->addCheckboxList('colors', 'Kolory:', $colors) ->setHtmlAttribute('style:', $styles); ``` -Listy: +Wypisze: ```latte -<label><input type="checkbox" name="colors[]" style="background:red" value="r">červená</label> -<label><input type="checkbox" name="colors[]" style="background:green" value="g">zelená</label> -<label><input type="checkbox" name="colors[]" value="b">modrá</label> +<label><input type="checkbox" name="colors[]" style="background:red" value="r">czerwony</label> +<label><input type="checkbox" name="colors[]" style="background:green" value="g">zielony</label> +<label><input type="checkbox" name="colors[]" value="b">niebieski</label> ``` -Jeśli jest to logiczny atrybut HTML (który nie ma wartości, np. `readonly`), możemy użyć notacji znaku zapytania: +Do ustawienia atrybutów logicznych, takich jak `readonly`, możemy użyć zapisu ze znakiem zapytania: ```php -$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue']; -$form->addCheckboxList('colors', 'Colors:', $colors) - ->setHtmlAttribute('readonly?', 'r'); // dla wielu kluczy należy użyć tablicy, np. ['r', 'g'] +$form->addCheckboxList('colors', 'Kolory:', $colors) + ->setHtmlAttribute('readonly?', 'r'); // dla wielu kluczy użyj tablicy, np. ['r', 'g'] ``` -Listy: +Wypisze: ```latte -<label><input type="checkbox" name="colors[]" readonly value="r">červená</label> -<label><input type="checkbox" name="colors[]" value="g">zelená</label> -<label><input type="checkbox" name="colors[]" value="b">modrá</label> +<label><input type="checkbox" name="colors[]" readonly value="r">czerwony</label> +<label><input type="checkbox" name="colors[]" value="g">zielony</label> +<label><input type="checkbox" name="colors[]" value="b">niebieski</label> ``` -W przypadku selectboxów metoda `setHtmlAttribute()` ustawia atrybuty elementu `<select>`. Jeśli chcemy ustawić atrybuty dla poszczególnych -`<option>`używamy metody `setOptionAttribute()`. Powyższe zapisy z dwukropkiem i znakiem zapytania również działają: +W przypadku pól wyboru metoda `setHtmlAttribute()` ustawia atrybuty elementu `<select>`. Jeśli chcemy ustawić atrybuty poszczególnym `<option>`, użyjemy metody `setOptionAttribute()`. Działają również zapisy z dwukropkiem i znakiem zapytania podane wyżej: ```php -$form->addSelect('colors', 'Barvy:', $colors) +$form->addSelect('colors', 'Kolory:', $colors) ->setOptionAttribute('style:', $styles); ``` -Listy: +Wypisze: ```latte <select name="colors"> - <option value="r" style="background:red">červená</option> - <option value="g" style="background:green">zelená</option> - <option value="b">modrá</option> + <option value="r" style="background:red">czerwony</option> + <option value="g" style="background:green">zielony</option> + <option value="b">niebieski</option> </select> ``` -Prototypy .[#toc-prototypes] ----------------------------- +Prototypy +--------- -Alternatywnym sposobem ustawiania atrybutów HTML jest modyfikacja szablonu, z którego generowany jest element HTML. Szablon jest obiektem `Html` i jest zwracany przez metodę `getControlPrototype()`: +Alternatywny sposób ustawiania atrybutów HTML polega na modyfikacji wzorca, z którego generowany jest element HTML. Wzorcem jest obiekt `Html` i zwraca go metoda `getControlPrototype()`: ```php -$input = $form->addInteger('number', 'Číslo:'); +$input = $form->addInteger('number', 'Numer:'); $html = $input->getControlPrototype(); // <input> -$html->class('big-number'); // <input class="big-number"> +$html->class('big-number'); // <input class="big-number"> ``` -W ten sposób można również modyfikować szablon etykiety zwracany przez `getLabelPrototype()`: +W ten sposób można modyfikować również wzorzec etykiety, który zwraca `getLabelPrototype()`: ```php -$html = $input->getLabelPrototype(); // <label>. -$html->class('distinctive'); // <label class="distinctive">. +$html = $input->getLabelPrototype(); // <label> +$html->class('distinctive'); // <label class="distinctive"> ``` -W przypadku elementów Checkbox, CheckboxList i RadioList można wpływać na szablon elementu, który zawija element. Jest on zwracany przez `getContainerPrototype()`. Domyślnie jest to element "pusty", więc nic nie jest renderowane, ale nadając mu nazwę, zostanie wyrenderowany: +U elementów Checkbox, CheckboxList i RadioList możesz wpłynąć na wzorzec elementu, który cały element opakowuje. Zwraca go `getContainerPrototype()`. W stanie domyślnym jest to „pusty” element, więc nic się nie renderuje, ale przez ustawienie mu nazwy, będzie się renderować: ```php $input = $form->addCheckbox('send'); -echo $input->getControl(); -// <label><input type="checkbox" name="send"></label>. - $html = $input->getContainerPrototype(); $html->setName('div'); // <div> $html->class('check'); // <div class="check"> @@ -551,50 +541,49 @@ echo $input->getControl(); // <div class="check"><label><input type="checkbox" name="send"></label></div> ``` -W przypadku CheckboxList i RadioList można również wpływać na szablon separatora elementów zwracany przez metodę `getSeparatorPrototype()`. Domyślnie jest to element `<br>`. Jeśli zmienisz go na element pary, będzie on zawijał poszczególne elementy zamiast je oddzielać. -I możesz również wpłynąć na szablon elementu HTML elementu single-item label, który zwraca `getItemLabelPrototype()`. +W przypadku CheckboxList i RadioList można wpłynąć również na wzorzec separatora poszczególnych pozycji, który zwraca metoda `getSeparatorPrototype()`. W stanie domyślnym jest to element `<br>`. Jeśli zmienisz go na element parzysty, będzie poszczególne pozycje opakowywał zamiast oddzielać. A dalej można wpłynąć na wzorzec elementu HTML etykiety u poszczególnych pozycji, który zwraca `getItemLabelPrototype()`. -Tłumaczenie .[#toc-translating] -=============================== +Tłumaczenie +=========== -Jeśli programujesz wielojęzyczną aplikację, prawdopodobnie będziesz musiał renderować formularz w różnych językach. Nette Framework definiuje w tym celu interfejs translacji [api:Nette\Localization\Translator]. W Nette nie ma domyślnej implementacji, możesz wybrać według swoich potrzeb spośród kilku gotowych rozwiązań, które znajdziesz na [Componette |https://componette.org/search/localization]. Ich dokumentacja podpowiada, jak skonfigurować translator. +Jeśli programujesz aplikację wielojęzyczną, prawdopodobnie będziesz potrzebować wyrenderować formularz w różnych wersjach językowych. Nette Framework w tym celu definiuje interfejs do tłumaczenia [api:Nette\Localization\Translator]. W Nette nie ma żadnej domyślnej implementacji, możesz wybrać według swoich potrzeb z kilku gotowych rozwiązań, które znajdziesz na [Componette |https://componette.org/search/localization]. W ich dokumentacji dowiesz się, jak konfigurować translator. -Formularz obsługuje wyprowadzanie tekstu przez translator. Przekazujemy go za pomocą metody `setTranslator()`: +Formularze obsługują wypisywanie tekstów przez translator. Przekażemy im go za pomocą metody `setTranslator()`: ```php $form->setTranslator($translator); ``` -Od tej pory nie tylko wszystkie etykiety, ale także wszystkie komunikaty o błędach czy wpisy w polach wyboru będą tłumaczone na inny język. +Od tej chwili nie tylko wszystkie etykiety, ale i wszystkie komunikaty błędów lub pozycje pól wyboru zostaną przetłumaczone na inny język. -Możliwe jest ustawienie innego tłumacza dla poszczególnych elementów formularza lub całkowite wyłączenie tłumaczenia za pomocą `null`: +U poszczególnych elementów formularza można przy tym ustawić inny translator lub tłumaczenie całkowicie wyłączyć wartością `null`: ```php $form->addSelect('carModel', 'Model:', $cars) ->setTranslator(null); ``` -W przypadku [reguł walidacji |validation] do tłumacza przekazywane są również określone parametry, na przykład dla reguły: +U [reguł walidacyjnych|validation] translatorowi przekazywane są również specyficzne parametry, na przykład u reguły: ```php -$form->addPassword('password', 'Password:') - ->addRule($form::MinLength, 'Password has to be at least %d characters long', 8) +$form->addPassword('password', 'Hasło:') + ->addRule($form::MinLength, 'Hasło musi mieć co najmniej %d znaków', 8); ``` -translator jest wywoływany z następującymi parametrami: +wywoływany jest translator z tymi parametrami: ```php -$translator->translate('Password has to be at least %d characters long', 8); +$translator->translate('Hasło musi mieć co najmniej %d znaków', 8); ``` -a więc może wybrać poprawną formę liczby mnogiej dla słowa `characters` przez count. +a więc może wybrać poprawną formę liczby mnogiej u słowa `znaków` według liczby. -Zdarzenie onRender .[#toc-event-onrender] -========================================= +Zdarzenie onRender +================== -Tuż przed wyrenderowaniem formularza możemy zlecić wywołanie naszego kodu. Może to na przykład dodać klasy HTML do elementów formularza w celu prawidłowego wyświetlania. Dodajemy kod do pola `onRender`: +Tuż przed tym, jak formularz zostanie wyrenderowany, możemy pozwolić wywołać nasz kod. Ten może na przykład uzupełnić elementom formularza klasy HTML dla poprawnego wyświetlenia. Kod dodamy do tablicy `onRender`: ```php $form->onRender[] = function ($form) { diff --git a/forms/pl/standalone.texy b/forms/pl/standalone.texy index 82353d1de2..3943cbbd2c 100644 --- a/forms/pl/standalone.texy +++ b/forms/pl/standalone.texy @@ -1,116 +1,115 @@ -Formularze stosowane oddzielnie +Formularze używane samodzielnie ******************************* .[perex] -Nette Forms znacznie ułatwiają tworzenie i przetwarzanie formularzy internetowych. Możesz ich używać w swoich aplikacjach zupełnie samodzielnie bez reszty frameworka, co zademonstrujemy w tym rozdziale. +Nette Forms znacznie ułatwiają tworzenie i przetwarzanie formularzy internetowych. Możesz ich używać w swoich aplikacjach całkowicie samodzielnie, bez reszty frameworka, co pokażemy w tym rozdziale. -Jeśli jednak korzystasz z Nette Application i prezenterów, to [w prezenterach|in-presenter] znajdziesz tutorial do [wykorzystania |in-presenter]. +Jeśli jednak używasz Nette Application i prezenterów, przeznaczony jest dla Ciebie przewodnik dotyczący [użycia w prezenterach|in-presenter]. -Pierwszy formularz .[#toc-first-form] -===================================== +Pierwszy formularz +================== -Spróbujmy napisać prosty formularz rejestracyjny. Jego kod będzie następujący ("pełny kod":https://gist.github.com/dg/57878c1a413ae8ef0c1d83f02c43ef3f): +Spróbujmy napisać prosty formularz rejestracyjny. Jego kod będzie następujący ("cały kod":https://gist.github.com/dg/57878c1a413ae8ef0c1d83f02c43ef3f): ```php use Nette\Forms\Form; $form = new Form; -$form->addText('name', 'Jméno:'); -$form->addPassword('password', 'Heslo:'); -$form->addSubmit('send', 'Registrovat'); +$form->addText('name', 'Imię:'); +$form->addPassword('password', 'Hasło:'); +$form->addSubmit('send', 'Zarejestruj'); ``` -Możemy ją oddać w bardzo prosty sposób: +Bardzo łatwo go wyrenderujemy: ```php $form->render(); ``` -i zostanie on wyświetlony w przeglądarce w następujący sposób: +a w przeglądarce wyświetli się tak: [* form-cs.webp *] -Forma jest obiektem klasy `Nette\Forms\Form` (klasa `Nette\Application\UI\Form` jest używana w prezenterze). Dodałem elementy nazwy, hasła i przycisku submit. +Formularz jest obiektem klasy `Nette\Forms\Form` (klasa `Nette\Application\UI\Form` jest używana w prezenterach). Dodaliśmy do niego tzw. elementy: imię, hasło i przycisk wysyłający. -A teraz zajmiemy się animacją formularza. Poprzez zapytanie `$form->isSuccess()` możemy sprawdzić, czy formularz został przesłany i czy został poprawnie wypełniony. Jeśli tak, to wydrukujemy te dane. Zatem po definicji formularza dodajemy: +A teraz ożywimy formularz. Pytając `$form->isSuccess()` dowiemy się, czy formularz został wysłany i czy został wypełniony poprawnie. Jeśli tak, wypiszemy dane. Za definicją formularza dopiszemy więc: ```php if ($form->isSuccess()) { - echo 'Formularz został poprawnie wypełniony i przesłany'; + echo 'Formularz został poprawnie wypełniony i wysłany'; $data = $form->getValues(); - // $data->name zawiera nazwę + // $data->name zawiera imię // $data->password zawiera hasło var_dump($data); } ``` -Metoda `getValues()` zwraca przekazane dane w postaci obiektu [ArrayHash |utils:arrays#ArrayHash]. Jak to zmienić pokażemy [później |#Mapping-to-Classes]. Obiekt `$data` zawiera klucze `name` i `password` z danymi wypełnionymi przez użytkownika. +Metoda `getValues()` zwraca przesłane dane w postaci obiektu [ArrayHash |utils:arrays#ArrayHash]. Jak to zmienić, pokażemy [później |#Mapowanie na klasy]. Obiekt `$data` zawiera klucze `name` i `password` z danymi, które wypełnił użytkownik. -Zazwyczaj wysyłamy dane bezpośrednio do dalszej obróbki, którą może być np. wstawienie ich do bazy danych. Podczas przetwarzania może jednak wystąpić błąd, na przykład nazwa użytkownika jest już zajęta. W tym przypadku przekazujemy błąd z powrotem do formularza za pomocą `addError()` i mamy go renderować ponownie, z komunikatem o błędzie. +Zwykle dane od razu wysyłamy do dalszego przetwarzania, co może być na przykład wstawienie do bazy danych. Podczas przetwarzania może jednak pojawić się błąd, na przykład nazwa użytkownika jest już zajęta. W takim przypadku błąd przekazujemy z powrotem do formularza za pomocą `addError()` i pozwalamy mu wyrenderować się ponownie, wraz z komunikatem o błędzie. ```php -$form->addError('Omlouváme se, toto uživatelské jméno už někdo používá.'); +$form->addError('Przepraszamy, ta nazwa użytkownika jest już zajęta.'); ``` -Po przetworzeniu formularza przekierowujemy na kolejną stronę. Zapobiega to niezamierzonemu ponownemu przesłaniu formularza za pomocą przycisków *refresh*, *back* lub historii przeglądarki. +Po przetworzeniu formularza przekierowujemy na następną stronę. Zapobiega to niechcianemu ponownemu wysłaniu formularza przyciskiem *odśwież*, *wstecz* lub poruszaniem się w historii przeglądarki. -Domyślnie formularz jest wysyłany metodą POST na tę samą stronę. Jedno i drugie można zmienić: +Formularz standardowo wysyłany jest metodą POST i to na tę samą stronę. Oba te ustawienia można zmienić: ```php $form->setAction('/submit.php'); $form->setMethod('GET'); ``` -To wszystko :-) Mamy funkcjonalny i doskonale [zabezpieczony |#Vulnerability-Protection] formularz. +I to właściwie wszystko :-) Mamy działający i doskonale [zabezpieczony |#Ochrona przed lukami w zabezpieczeniach] formularz. -Spróbuj dodać inne [elementy formularza |controls]. +Spróbuj dodać także inne [elementy formularza|controls]. -Dostęp do elementów .[#toc-access-to-controls] -============================================== +Dostęp do elementów +=================== -Formularz i jego poszczególne elementy nazywane są komponentami. Tworzą one drzewo komponentów, gdzie korzeniem jest formularz. Do każdego elementu formularza można uzyskać dostęp w następujący sposób: +Formularz i jego poszczególne elementy nazywamy komponentami. Tworzą one drzewo komponentów, gdzie korzeniem jest właśnie formularz. Do poszczególnych elementów formularza dostaniemy się w ten sposób: ```php $input = $form->getComponent('name'); -// alternativní syntax: $input = $form['name']; +// alternatywna składnia: $input = $form['name']; $button = $form->getComponent('send'); -// alternativní syntax: $button = $form['send']; +// alternatywna składnia: $button = $form['send']; ``` -Elementy są usuwane za pomocą unset: +Elementy usuwa się za pomocą unset: ```php unset($form['name']); ``` -Zasady walidacji .[#toc-validation-rules] -========================================= +Reguły walidacji +================ -Słowo *valid,* ale formularz nie ma jeszcze reguł walidacji. Naprawmy to. +Padło tu słowo *poprawny,* ale formularz na razie nie ma żadnych reguł walidacji. Naprawmy to. -Nazwa będzie obowiązkowa, więc oznaczymy ją metodą `setRequired()`, której argumentem jest tekst komunikatu o błędzie, który zostanie wyświetlony, jeśli użytkownik nie wypełni nazwy. Jeśli nie podano żadnego argumentu, użyty zostanie domyślny komunikat o błędzie. +Imię będzie obowiązkowe, dlatego oznaczymy je metodą `setRequired()`, której argumentem jest tekst komunikatu o błędzie, który wyświetli się, jeśli użytkownik nie wypełni imienia. Jeśli nie podamy argumentu, użyty zostanie domyślny komunikat o błędzie. ```php -$form->addText('name', 'Jméno:') - ->setRequired('Zadejte prosím jméno'); +$form->addText('name', 'Imię:') + ->setRequired('Proszę podać imię'); ``` -Spróbuj przesłać formularz bez wypełnienia nazwy, a zobaczysz, że pojawi się komunikat o błędzie, a przeglądarka lub serwer odrzuci go, dopóki nie wypełnisz pola. +Spróbuj wysłać formularz bez wypełnionego imienia, a zobaczysz, że wyświetli się komunikat o błędzie, a przeglądarka lub serwer będzie go odrzucać, dopóki nie wypełnisz pola. -Jednocześnie nie oszukuj systemu, wpisując w pole np. same spacje. Nie rób tego. Nette automatycznie usuwa zarówno lewe jak i prawe spacje. Spróbuj. Jest to rodzaj rzeczy, którą powinieneś zawsze robić z każdym jednolinijkowym wejściem, ale często się o tym zapomina. Nette robi to automatycznie (możesz spróbować oszukać formularz, aby wysłać ciąg wieloliniowy jako nazwę. Nawet tutaj Nette nie da się oszukać i zmieni podziały linii na spacje). +Jednocześnie systemu nie oszukasz, wpisując w pole na przykład same spacje. Nic z tego. Nette automatycznie usuwa spacje z lewej i prawej strony. Wypróbuj to. To jest rzecz, którą powinieneś zawsze robić z każdym jednoliniowym inputem, ale często się o tym zapomina. Nette robi to automatycznie. (Możesz spróbować oszukać formularz i jako imię wysłać wieloliniowy ciąg znaków. Nawet tutaj Nette nie da się zwieść i zamieni znaki nowej linii na spacje.) -Formularz jest zawsze walidowany po stronie serwera, ale generuje również walidację JavaScript, która jest wykonywana w mgnieniu oka, a użytkownik dowiaduje się o błędzie natychmiast, bez konieczności wysyłania formularza na serwer. Zajmuje się tym skrypt `netteForms.js`. -Wstaw go na stronę: +Formularz zawsze jest walidowany po stronie serwera, ale generowana jest również walidacja JavaScriptowa, która przebiega błyskawicznie, a użytkownik dowiaduje się o błędzie natychmiast, bez konieczności wysyłania formularza na serwer. Za to odpowiada skrypt `netteForms.js`. Wstaw go na stronę: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Jeśli spojrzysz na kod źródłowy strony formularza, możesz zauważyć, że Nette wstawia wymagane elementy z klasą CSS `required`. Spróbuj dodać następujący arkusz stylów do szablonu, a etykieta "Nazwa" będzie czerwona. Dzięki temu w elegancki sposób zaznaczymy dla użytkowników elementy obowiązkowe: +Jeśli spojrzysz do kodu źródłowego strony z formularzem, możesz zauważyć, że Nette obowiązkowe elementy wstawia do elementów z klasą CSS `required`. Spróbuj dodać do szablonu następujący arkusz stylów, a etykieta „Imię” będzie czerwona. W ten sposób elegancko zaznaczymy użytkownikom obowiązkowe elementy: ```latte <style> @@ -118,96 +117,96 @@ Jeśli spojrzysz na kod źródłowy strony formularza, możesz zauważyć, że N </style> ``` -Dodaj więcej reguł walidacji używając metody `addRule()`. Pierwszy parametr to reguła, drugi to ponownie tekst komunikatu o błędzie, po którym może nastąpić argument reguła walidacji. Co należy przez to rozumieć? +Kolejne reguły walidacji dodamy metodą `addRule()`. Pierwszy parametr to reguła, drugi to ponownie tekst komunikatu o błędzie, a może jeszcze nastąpić argument reguły walidacji. Co to oznacza? -Rozszerzamy formularz o nowe, opcjonalne pole "wiek", które musi być liczbą całkowitą (`addInteger()`), a także mieścić się w dozwolonym zakresie (`$form::Range`). W tym miejscu używamy trzeciego parametru metody `addRule()`, aby przekazać wymagany zakres do walidatora jako parę `[od, do]`: +Formularz rozszerzymy o nowe, nieobowiązkowe pole „wiek”, które musi być liczbą całkowitą (`addInteger()`) i dodatkowo w dozwolonym zakresie (`$form::Range`). I tutaj właśnie wykorzystamy trzeci parametr metody `addRule()`, którym przekażemy walidatorowi wymagany zakres jako parę `[od, do]`: ```php -$form->addInteger('age', 'Věk:') - ->addRule($form::Range, 'Věk musí být od 18 do 120', [18, 120]); +$form->addInteger('age', 'Wiek:') + ->addRule($form::Range, 'Wiek musi być od 18 do 120', [18, 120]); ``` .[tip] -Jeśli użytkownik nie wypełni pola, reguły walidacji nie zostaną sprawdzone, ponieważ element jest opcjonalny. +Jeśli użytkownik nie wypełni pola, reguły walidacji nie będą sprawdzane, ponieważ element jest nieobowiązkowy. -Jest tu miejsce na drobną refaktoryzację. W komunikacie o błędzie i w trzecim parametrze liczby są zduplikowane, co nie jest idealne. Jeśli tworzyliśmy [formularze wielojęzyczne |rendering#translating], a wiadomość zawierająca liczby została przetłumaczona na wiele języków, utrudniłoby to zmianę wartości w razie potrzeby. Z tego powodu można użyć znaków zastępczych `%d`, a Nette wypełni wartości: +Tutaj pojawia się miejsce na mały refactoring. W komunikacie o błędzie i w trzecim parametrze liczby są podane podwójnie, co nie jest idealne. Gdybyśmy tworzyli [formularze wielojęzyczne |rendering#Tłumaczenie] a komunikat zawierający liczby byłby przetłumaczony na wiele języków, utrudniłoby to ewentualną zmianę wartości. Z tego powodu można użyć symboli zastępczych `%d`, a Nette uzupełni wartości: ```php - ->addRule($form::Range, 'Věk musí být od %d do %d let', [18, 120]); + ->addRule($form::Range, 'Wiek musi wynosić od %d do %d lat', [18, 120]); ``` -Wróćmy do elementu `password`, który również czynimy obowiązkowym, i sprawdźmy minimalną długość hasła (`$form::MinLength`), ponownie używając znaku wieloznacznego: +Wróćmy do elementu `password`, który również uczynimy obowiązkowym i jeszcze sprawdzimy minimalną długość hasła (`$form::MinLength`), ponownie wykorzystując symbol zastępczy: ```php -$form->addPassword('password', 'Heslo:') - ->setRequired('Zvolte si heslo') - ->addRule($form::MinLength, 'Heslo musí mít alespoň %d znaků', 8); +$form->addPassword('password', 'Hasło:') + ->setRequired('Wybierz hasło') + ->addRule($form::MinLength, 'Hasło musi mieć co najmniej %d znaków', 8); ``` -Dodajmy do formularza pole `passwordVerify`, w którym użytkownik wpisuje hasło jeszcze raz, w celu weryfikacji. Korzystając z reguł walidacji sprawdzamy, czy oba hasła są takie same (`$form::Equal`). A jako parametr umieszczamy odwołanie do pierwszego hasła za pomocą [nawiasów kwadratowych |#Access-to-Controls]: +Dodamy do formularza jeszcze pole `passwordVerify`, gdzie użytkownik wprowadzi hasło jeszcze raz, dla kontroli. Za pomocą reguł walidacji sprawdzimy, czy oba hasła są takie same (`$form::Equal`). A jako parametr podamy odwołanie do pierwszego hasła za pomocą [nawiasów kwadratowych |#Dostęp do elementów]: ```php -$form->addPassword('passwordVerify', 'Heslo pro kontrolu:') - ->setRequired('Zadejte prosím heslo ještě jednou pro kontrolu') - ->addRule($form::Equal, 'Hesla se neshodují', $form['password']) +$form->addPassword('passwordVerify', 'Hasło do weryfikacji:') + ->setRequired('Proszę wprowadzić hasło ponownie w celu weryfikacji') + ->addRule($form::Equal, 'Hasła nie pasują', $form['password']) ->setOmitted(); ``` -Za pomocą `setOmitted()` zaznaczyliśmy element, którego wartość tak naprawdę nas nie obchodzi i który istnieje tylko ze względu na walidację. Wartość nie jest przekazywana do `$data`. +Za pomocą `setOmitted()` oznaczyliśmy element, którego wartość nas właściwie nie obchodzi i który istnieje tylko ze względu na walidację. Wartość nie zostanie przekazana do `$data`. -W ten sposób mamy w pełni funkcjonalny formularz z walidacją w PHP i JavaScript. Możliwości walidacji Nette są znacznie szersze, możesz tworzyć warunki, mieć części strony wyświetlane i ukryte zgodnie z nimi itp. Możesz dowiedzieć się wszystkiego na ten temat w rozdziale poświęconym [walidacji formularzy |validation]. +Tym samym mamy gotowy, w pełni funkcjonalny formularz z walidacją w PHP i JavaScript. Możliwości walidacyjne Nette są znacznie szersze, można tworzyć warunki, pozwalać na ich podstawie wyświetlać i ukrywać części strony itp. Wszystkiego dowiesz się w rozdziale o [walidacji formularzy|validation]. -Wartości domyślne .[#toc-default-values] -======================================== +Wartości domyślne +================= -Standardowo ustawiamy wartości domyślne dla elementów formularza: +Elementom formularza często ustawiamy wartości domyślne: ```php $form->addEmail('email', 'E-mail') ->setDefaultValue($lastUsedEmail); ``` -Często przydatne jest ustawienie wartości domyślnych dla wszystkich elementów jednocześnie. Na przykład, gdy formularz jest używany do edycji rekordów. Odczytaj rekord z bazy danych i ustaw wartości domyślne: +Często przydaje się ustawienie wartości domyślnych dla wszystkich elementów jednocześnie. Na przykład, gdy formularz służy do edycji rekordów. Odczytujemy rekord z bazy danych i ustawiamy wartości domyślne: ```php -//$row = ['name' => 'John', 'age' => '33', /* ... */]; +// $row = ['name' => 'John', 'age' => '33', /* ... */]; $form->setDefaults($row); ``` -Po zdefiniowaniu elementów wywołaj `setDefaults()`. +Wywołuj `setDefaults()` dopiero po zdefiniowaniu elementów. -Rendering formularza .[#toc-rendering-the-form] -=============================================== +Renderowanie formularza +======================= -Domyślnie formularz jest renderowany jako tabela. Poszczególne elementy spełniają podstawową zasadę dostępności - wszystkie etykiety są zapisane jako `<label>` i powiązany z odpowiednim elementem formularza. Po kliknięciu na etykietę, kursor automatycznie pojawia się w polu formularza. +Standardowo formularz renderuje się jako tabela. Poszczególne elementy spełniają podstawową zasadę dostępności - wszystkie etykiety są zapisane jako `<label>` i powiązane z odpowiednim elementem formularza. Po kliknięciu na etykietę kursor automatycznie pojawia się w polu formularza. -Dla każdego elementu możemy ustawić dowolne atrybuty HTML. Na przykład dodać placeholder: +Każdemu elementowi możemy ustawiać dowolne atrybuty HTML. Na przykład dodać placeholder: ```php -$form->addInteger('age', 'Věk:') - ->setHtmlAttribute('placeholder', 'Prosím vyplňte věk'); +$form->addInteger('age', 'Wiek:') + ->setHtmlAttribute('placeholder', 'Proszę podać wiek'); ``` -Istnieje wiele sposobów renderowania formularza, dlatego [na temat renderowania |rendering] jest [osobny rozdział |rendering]. +Sposobów na wyrenderowanie formularza jest naprawdę wiele, dlatego poświęcono temu [osobny rozdział o renderowaniu|rendering]. -Odwzorowanie na klasy .[#toc-mapping-to-classes] -================================================ +Mapowanie na klasy +================== -Wróćmy do przetwarzania danych z formularza. Metoda `getValues()` zwróciła przesłane dane jako obiekt `ArrayHash`. Ponieważ jest to klasa generyczna, coś jak `stdClass`, zabraknie nam pewnych wygód podczas pracy z nią, takich jak uzupełnianie kodu dla właściwości w edytorach czy statyczna analiza kodu. Można to rozwiązać poprzez posiadanie specyficznej klasy dla każdego formularza, której właściwości reprezentują poszczególne kontrolki. Np: +Wróćmy do przetwarzania danych formularza. Metoda `getValues()` zwracała nam przesłane dane jako obiekt `ArrayHash`. Ponieważ jest to klasa generyczna, coś w rodzaju `stdClass`, podczas pracy z nią zabraknie nam pewnego komfortu, jak na przykład podpowiadania właściwości w edytorach czy statycznej analizy kodu. Można by to rozwiązać, tworząc dla każdego formularza konkretną klasę, której właściwości reprezentują poszczególne elementy. Np.: ```php class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -Od PHP 8.0 możesz użyć tej eleganckiej notacji, która wykorzystuje konstruktor: +Alternatywnie możesz wykorzystać konstruktor: ```php class RegistrationFormData @@ -221,16 +220,18 @@ class RegistrationFormData } ``` -Jak powiedzieć Nette, aby zwracała dane jako obiekty tej klasy? Łatwiejsze niż myślisz. Wystarczy, że jako parametr podasz nazwę klasy lub obiektu do nawodnienia: +Właściwości klasy danych mogą być również typu enum i zostaną automatycznie zmapowane. .{data-version:3.2.4} + +Jak powiedzieć Nette, aby zwracał nam dane jako obiekty tej klasy? Łatwiej niż myślisz. Wystarczy tylko nazwę klasy lub obiekt do hydratacji podać jako parametr: ```php $data = $form->getValues(RegistrationFormData::class); $name = $data->name; ``` -Możesz również określić `'array'` jako parametr, a następnie zwrócić dane jako tablicę. +Jako parametr można podać również `'array'` i wtedy dane zwróci jako tablicę. -Jeśli formularze tworzą wielopoziomową strukturę składającą się z kontenerów, utwórz dla każdego z nich osobną klasę: +Jeśli formularze tworzą wielopoziomową strukturę złożoną z kontenerów, utwórz dla każdego osobną klasę: ```php $form = new Form; @@ -247,26 +248,28 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` -Mapowanie wie wtedy z typu właściwości `$person`, że powinno mapować kontener do klasy `PersonFormData`. Jeśli właściwość zawierałaby tablicę kontenerów, określ typ `array` i przekaż klasę mapowania bezpośrednio do kontenera: +Mapowanie następnie z typu właściwości `$person` rozpozna, że ma kontener mapować na klasę `PersonFormData`. Jeśli właściwość zawierałaby tablicę kontenerów, podaj typ `array` i klasę do mapowania przekaż bezpośrednio kontenerowi: ```php $person->setMappedType(PersonFormData::class); ``` +Projekt klasy danych formularza możesz wygenerować za pomocą metody `Nette\Forms\Blueprint::dataClass($form)`, która wypisze go na stronie przeglądarki. Kod wystarczy następnie kliknięciem zaznaczyć i skopiować do projektu. .{data-version:3.1.15} + -Więcej przycisków .[#toc-multiple-submit-buttons] -================================================= +Wiele przycisków +================ -Jeśli formularz ma więcej niż jeden przycisk, zwykle musimy rozróżnić, który z nich został naciśnięty. Informacja ta jest zwracana przez metodę `isSubmittedBy()` button: +Jeśli formularz ma więcej niż jeden przycisk, zazwyczaj potrzebujemy rozróżnić, który z nich został naciśnięty. Tę informację zwróci nam metoda `isSubmittedBy()` przycisku: ```php -$form->addSubmit('save', 'Uložit'); -$form->addSubmit('delete', 'Smazat'); +$form->addSubmit('save', 'Zapisz'); +$form->addSubmit('delete', 'Usuń'); if ($form->isSuccess()) { if ($form['save']->isSubmittedBy()) { @@ -279,37 +282,36 @@ if ($form->isSuccess()) { } ``` -Nie pomijaj zapytania do `$form->isSuccess()`, to zweryfikuje poprawność danych. +Nie pomijaj zapytania `$form->isSuccess()`, sprawdzisz w ten sposób poprawność danych. -Po przesłaniu formularza przyciskiem <kbd>Enter</kbd>, jest on traktowany tak, jakby został przesłany pierwszym przyciskiem. +Gdy formularz zostanie wysłany przyciskiem <kbd>Enter</kbd>, traktuje się to tak, jakby został wysłany pierwszym przyciskiem. -Ochrona przed podatnością na zagrożenia .[#toc-vulnerability-protection] -======================================================================== +Ochrona przed lukami w zabezpieczeniach +======================================= -Nette Framework przykłada dużą wagę do bezpieczeństwa i dlatego skrupulatnie dba o to, aby formularze były dobrze zabezpieczone. +Nette Framework kładzie duży nacisk na bezpieczeństwo i dlatego skrupulatnie dba o dobre zabezpieczenie formularzy. -Oprócz ochrony formularzy przed atakami [Cross Site Scripting (XSS) |nette:glossary#Cross-Site-Scripting-XSS] i [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site-Request-Forgery-CSRF], robi wiele małych rzeczy związanych z bezpieczeństwem, o których nie musisz myśleć. +Oprócz tego, że formularze chronią przed atakiem [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] i [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF], wykonuje wiele drobnych zabezpieczeń, o których Ty już nie musisz myśleć. -Na przykład filtruje wszystkie znaki kontrolne z danych wejściowych i sprawdza ważność kodowania UTF-8, więc dane formularza zawsze będą czyste. W przypadku pól wyboru i arkuszy radiowych weryfikuje, czy wybrane pozycje rzeczywiście pochodziły z oferowanych i nie zostały spreparowane. Wspomnieliśmy już, że dla jednolinijkowych danych tekstowych, usuwa ona znaki końca linii, które mógłby wysłać atakujący. Dla wejść wieloliniowych normalizuje znaki dla przerw w linii. I tak dalej. +Na przykład odfiltrowuje ze wejść wszystkie znaki kontrolne i sprawdza poprawność kodowania UTF-8, dzięki czemu dane z formularza zawsze będą czyste. W przypadku select boxów i list radio sprawdza, czy wybrane pozycje rzeczywiście pochodziły z oferowanych i czy nie doszło do fałszerstwa. Już wspominaliśmy, że w przypadku jednoliniowych wejść tekstowych usuwa znaki końca linii, które mógł tam wysłać atakujący. W przypadku wejść wieloliniowych z kolei normalizuje znaki końca linii. I tak dalej. -Nette rozwiązuje za Ciebie zagrożenia bezpieczeństwa, o których istnieniu wielu programistów nawet nie wie. +Nette rozwiązuje za Ciebie ryzyka bezpieczeństwa, o których wielu programistów nawet nie wie, że istnieją. -Wspomniany wcześniej atak CSRF polega na tym, że atakujący zwabia ofiarę na stronę, która w subtelny sposób wykonuje w przeglądarce ofiary żądanie do serwera, na którym ofiara jest zalogowana, a serwer zakłada, że żądanie zostało wykonane przez ofiarę z własnej woli. Dlatego Nette zapobiega wysyłaniu formularza POST z innej domeny. Jeśli z jakiegoś powodu chcesz wyłączyć ochronę i pozwolić na wysłanie formularza z innej domeny, użyj: +Wspomniany atak CSRF polega na tym, że atakujący zwabia ofiarę na stronę, która niepostrzeżenie w przeglądarce ofiary wykonuje żądanie do serwera, na którym ofiara jest zalogowana, a serwer sądzi, że żądanie wykonała ofiara z własnej woli. Dlatego Nette zapobiega wysyłaniu formularza POST z innej domeny. Jeśli z jakiegoś powodu chcesz wyłączyć ochronę i pozwolić na wysyłanie formularza z innej domeny, użyj: ```php $form->allowCrossOrigin(); // UWAGA! Wyłącza ochronę! ``` -To zabezpieczenie wykorzystuje plik cookie SameSite o nazwie `_nss`. Dlatego utwórz obiekt formularza przed wysłaniem pierwszego wyjścia, aby plik cookie mógł zostać wysłany. +Ta ochrona wykorzystuje ciasteczko SameSite o nazwie `_nss`. Twórz zatem obiekt formularza jeszcze przed wysłaniem pierwszego wyjścia, aby można było wysłać ciasteczko. -Ochrona plików cookie SameSite może nie być w 100% niezawodna, dlatego zaleca się włączenie również ochrony tokenów: +Ochrona za pomocą ciasteczka SameSite może nie być w 100% niezawodna, dlatego warto włączyć jeszcze ochronę za pomocą tokenu: ```php $form->addProtection(); ``` -Zalecane jest zabezpieczenie formularzy w części administracyjnej witryny, które zmieniają wrażliwe dane w aplikacji. Framework broni się przed atakami CSRF, generując i weryfikując token autoryzacji, który jest przechowywany w sesji. W związku z tym konieczne jest, aby sesja była otwarta, zanim formularz będzie mógł być przeglądany. W części administracyjnej witryny sesja jest już zazwyczaj rozpoczęta z powodu zalogowania się użytkownika. -W przeciwnym razie rozpocznij sesję za pomocą metody `Nette\Http\Session::start()`. +Zalecamy w ten sposób chronić formularze w części administracyjnej strony, które zmieniają wrażliwe dane w aplikacji. Framework broni się przed atakiem CSRF, generując i weryfikując token autoryzacyjny, który jest przechowywany w sesji. Dlatego konieczne jest, aby przed wyświetleniem formularza sesja była otwarta. W części administracyjnej strony zazwyczaj sesja jest już uruchomiona ze względu na logowanie użytkownika. W przeciwnym razie uruchom sesję metodą `Nette\Http\Session::start()`. -Tak więc, mamy za sobą szybkie wprowadzenie do formularzy w Nette. Spróbuj ponownie spojrzeć na katalog [przykładów |https://github.com/nette/forms/tree/master/examples] w dystrubucji, aby uzyskać więcej inspiracji. +Tak, mamy za sobą szybkie wprowadzenie do formularzy w Nette. Spróbuj jeszcze zajrzeć do katalogu [examples|https://github.com/nette/forms/tree/master/examples] w dystrybucji, gdzie znajdziesz więcej inspiracji. diff --git a/forms/pl/validation.texy b/forms/pl/validation.texy index 75e7c62d54..3e58147ce0 100644 --- a/forms/pl/validation.texy +++ b/forms/pl/validation.texy @@ -1,132 +1,143 @@ -Zatwierdzanie formularzy -************************ +Walidacja formularzy +******************** -Wymagane elementy .[#toc-required-controls] -=========================================== +Elementy obowiązkowe +==================== -Elementy obowiązkowe oznaczane są metodą `setRequired()`, której argumentem jest tekst [komunikatu o błędzie |#Error-Messages], który zostanie wyświetlony, jeśli użytkownik nie wypełni elementu. Jeśli nie podano żadnego argumentu, używany jest domyślny komunikat o błędzie. +Elementy obowiązkowe oznaczamy metodą `setRequired()`, której argumentem jest tekst [komunikatu o błędzie |#Komunikaty o błędach], który wyświetli się, jeśli użytkownik nie wypełni elementu. Jeśli nie podamy argumentu, użyty zostanie domyślny komunikat o błędzie. ```php -$form->addText('name', 'Jméno:') - ->setRequired('Zadejte prosím jméno'); +$form->addText('name', 'Imię:') + ->setRequired('Proszę podać imię'); ``` -Zasady .[#toc-rules] -==================== +Reguły +====== -Reguły walidacyjne są dodawane do elementów za pomocą metody `addRule()`. Pierwszym parametrem jest reguła, drugim tekst [komunikatu o błędzie |#Error-Messages], a trzecim argument reguły walidacyjnej. +Reguły walidacji dodajemy do elementów metodą `addRule()`. Pierwszy parametr to reguła, drugi to tekst [komunikatu o błędzie |#Komunikaty o błędach], a trzeci to argument reguły walidacji. ```php -$form->addPassword('password', 'Heslo:') - ->addRule($form::MinLength, 'Heslo musí mít alespoň %d znaků', 8); +$form->addPassword('password', 'Hasło:') + ->addRule($form::MinLength, 'Hasło musi mieć co najmniej %d znaków', 8); ``` -Nette posiada szereg predefiniowanych reguł, których nazwy są stałymi klasy `Nette\Forms\Form`. +**Reguły walidacji są sprawdzane tylko wtedy, gdy użytkownik wypełnił element.** -Zasady te możemy stosować dla wszystkich elementów: +Nette dostarcza całą gamę predefiniowanych reguł, których nazwy są stałymi klasy `Nette\Forms\Form`. Dla wszystkich elementów możemy użyć tych reguł: -| stały | opis | typ argumentu +| stała | opis | typ argumentu |------- -| `Required` | element obowiązkowy, alias dla `setRequired()` | -. -| `Filled` | element obowiązkowy, alias dla `setRequired()` | -. -| `Blank` | element nie może być wypełniony | -. -| `Equal` | wartość równa się parametrowi | `mixed` +| `Required` | element obowiązkowy, alias dla `setRequired()` | - +| `Filled` | element obowiązkowy, alias dla `setRequired()` | - +| `Blank` | element nie może być wypełniony | - +| `Equal` | wartość jest równa parametrowi | `mixed` | `NotEqual` | wartość nie jest równa parametrowi | `mixed` -| `IsIn` | wartość jest równa jakiemuś elementowi w polu |. `array` -| `IsNotIn` | wartość nie jest równa żadnej pozycji w tablicy |. `array` -| `Valid` | czy element został wypełniony poprawnie? (dla [warunków |#Conditions]) | - +| `IsIn` | wartość jest równa jednemu z elementów w tablicy | `array` +| `IsNotIn` | wartość nie jest równa żadnemu z elementów w tablicy | `array` +| `Valid` | czy element jest wypełniony poprawnie? (dla [#Warunki]) | - + + +Wejścia tekstowe +---------------- -Poniższe reguły mogą być również stosowane dla elementów `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`: +Dla elementów `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`, `addFloat()` można użyć również niektórych z następujących reguł: | `MinLength` | minimalna długość tekstu | `int` | `MaxLength` | maksymalna długość tekstu | `int` -| `Length` | długość zakresu lub dokładna długość | para `[int, int]` lub `int` -| `Email` | ważny adres e-mail | - -| `URL` | bezwzględny URL | -. +| `Length` | długość w zakresie lub dokładna długość | para `[int, int]` lub `int` +| `Email` | prawidłowy adres e-mail | - +| `URL` | absolutny URL | - | `Pattern` | pasuje do wyrażenia regularnego | `string` -| `PatternInsensitive` | jak `Pattern`, ale z rozróżnianiem wielkości liter | `string` +| `PatternInsensitive` | jak `Pattern`, ale niezależne od wielkości liter | `string` | `Integer` | wartość całkowita | - -| `Numeric` | alias dla `Integer` | -. +| `Numeric` | alias dla `Integer` | - | `Float` | liczba | - | `Min` | minimalna wartość elementu numerycznego | `int\|float` | `Max` | maksymalna wartość elementu numerycznego | `int\|float` | `Range` | wartość w zakresie | para `[int\|float, int\|float]` -Reguły walidacji `Integer`, `Numeric` i `Float` bezpośrednio konwertują wartość na liczbę całkowitą i float odpowiednio. Ponadto reguła `URL` przyjmuje adres bez schematu (np. `nette.org`) i dodaje schemat (`https://nette.org`). -Wyrażenie w `Pattern` i `PatternIcase` musi być ważne dla całej wartości, czyli tak jakby było zawinięte przez `^` a `$`. +Reguły walidacji `Integer`, `Numeric` i `Float` od razu konwertują wartość na integer lub float. Ponadto reguła `URL` akceptuje również adres bez schematu (np. `nette.org`) i dodaje schemat (`https://nette.org`). Wyrażenie w `Pattern` i `PatternIcase` musi pasować do całej wartości, tzn. tak jakby było otoczone znakami `^` i `$`. -Poniższe zasady mogą być również stosowane dla elementów `addUpload()`, `addMultiUpload()`: -| `MaxFileSize` | maksymalny rozmiar pliku | `int` -| `MimeType` | Typ MIME, dozwolone symbole wieloznaczne (`'video/*'`) | `string\|string[]` -| `Image` | obraz JPEG, PNG, GIF, WebP | -. -| `Pattern` | nazwa pliku pasuje do wyrażenia regularnego | `string` -| `PatternInsensitive` | jak `Pattern`, ale z rozróżnianiem wielkości liter | `string` - -`MimeType` i `Image` wymagają rozszerzenia PHP `fileinfo`. Wykrywają one, że plik lub obraz jest pożądanego typu na podstawie jego sygnatury i **nie sprawdzają integralności całego pliku.** Możesz powiedzieć, czy obraz jest uszkodzony, próbując go [załadować |http:request#toImage], na przykład. +Liczba elementów +---------------- -Dla elementów `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()` można również zastosować następujące reguły, aby ograniczyć liczbę wybranych elementów lub przesłanych plików: +Dla elementów `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()` można użyć również następujących reguł do ograniczenia liczby wybranych elementów lub przesłanych plików: | `MinLength` | minimalna liczba | `int` | `MaxLength` | maksymalna liczba | `int` -| `Length` | liczba w zakresie lub dokładna liczba | par `[int, int]` lub `int` +| `Length` | liczba w zakresie lub dokładna liczba | para `[int, int]` lub `int` -Komunikaty o błędach .[#toc-error-messages] -------------------------------------------- +Przesyłanie plików +------------------ -Wszystkie predefiniowane reguły oprócz `Pattern` i `PatternInsensitive` mają domyślny komunikat o błędzie, więc można go pominąć. Jednak wymieniając i formułując wszystkie wiadomości w niestandardowy sposób, sprawisz, że formularz będzie bardziej przyjazny dla użytkownika. +Dla elementów `addUpload()`, `addMultiUpload()` można użyć również następujących reguł: -Domyślne komunikaty można zmienić w [konfiguracji |forms:configuration], edytując tekst w polu `Nette\Forms\Validator::$messages` lub korzystając z [translatora |rendering#translating]. +| `MaxFileSize` | maksymalny rozmiar pliku w bajtach | `int` +| `MimeType` | Typ MIME, dozwolone symbole wieloznaczne (`'video/*'`) | `string\|string[]` +| `Image` | obraz JPEG, PNG, GIF, WebP, AVIF | - +| `Pattern` | nazwa pliku pasuje do wyrażenia regularnego | `string` +| `PatternInsensitive` | jak `Pattern`, ale niezależne od wielkości liter | `string` -W tekście komunikatów o błędach można stosować następujące łańcuchy zastępcze: +`MimeType` i `Image` wymagają rozszerzenia PHP `fileinfo`. To, czy plik lub obraz jest wymaganego typu, wykrywają na podstawie jego sygnatury i **nie weryfikują integralności całego pliku.** Czy obraz nie jest uszkodzony, można sprawdzić na przykład próbując go [załadować |http:request#toImage]. -| `%d` | zastępuje kolejno argumenty reguły -| `%n$d` | zastąpić po n-tym argumencie reguły -| `%label` | replace po etykiecie elementu (bez dwukropka) -| `%name` | zastąpić po nazwie elementu (np. `name`) -| `%value` | zastąpić po wartości wprowadzonej przez użytkownika + +Komunikaty o błędach +==================== + +Wszystkie predefiniowane reguły z wyjątkiem `Pattern` i `PatternInsensitive` mają domyślny komunikat o błędzie, więc można go pominąć. Jednak podanie i sformułowanie wszystkich komunikatów na miarę sprawi, że formularz będzie bardziej przyjazny dla użytkownika. + +Zmienić domyślne komunikaty można w [konfiguracji|forms:configuration], edytując teksty w tablicy `Nette\Forms\Validator::$messages` lub używając [translatora |rendering#Tłumaczenie]. + +W tekście komunikatów o błędach można używać następujących symboli zastępczych: + +| `%d` | zastępuje kolejno argumentami reguły +| `%n$d` | zastępuje n-tym argumentem reguły +| `%label` | zastępuje etykietą elementu (bez dwukropka) +| `%name` | zastępuje nazwą elementu (np. `name`) +| `%value` | zastępuje wartością wprowadzoną przez użytkownika ```php -$form->addText('name', 'Jméno:') - ->setRequired('Vyplňte prosím %label'); +$form->addText('name', 'Imię:') + ->setRequired('Proszę wypełnić %label'); $form->addInteger('id', 'ID:') - ->addRule($form::Range, 'nejméně %d a nejvíce %d', [5, 10]); + ->addRule($form::Range, 'co najmniej %d i co najwyżej %d', [5, 10]); $form->addInteger('id', 'ID:') - ->addRule($form::Range, 'nejvíce %2$d a nejméně %1$d', [5, 10]); + ->addRule($form::Range, 'co najwyżej %2$d i co najmniej %1$d', [5, 10]); ``` -Warunki .[#toc-conditions] -========================== +Warunki +======= -Oprócz reguł, można również dodać warunki. Pisze się je podobnie jak reguły, ale zamiast `addRule()` używamy metody `addCondition()` i oczywiście nie dołączamy żadnego komunikatu o błędzie (warunek tylko pyta): +Oprócz reguł można dodawać również warunki. Zapisuje się je podobnie jak reguły, tylko zamiast `addRule()` używamy metody `addCondition()` i oczywiście nie podajemy żadnego komunikatu o błędzie (warunek tylko pyta): ```php $form->addPassword('password', 'Hasło:') // jeśli hasło nie jest dłuższe niż 8 znaków ->addCondition($form::MaxLength, 8) - // wtedy musi zawierać cyfrę + // to musi zawierać cyfrę ->addRule($form::Pattern, 'Musi zawierać cyfrę', '.*[0-9].*'); ``` -Możesz również związać warunek z elementem innym niż bieżący, używając `addConditionOn()`. Pierwszy parametr jest referencją do elementu. W tym przykładzie e-mail będzie obowiązkowy tylko wtedy, gdy pole wyboru jest zaznaczone (jego wartość będzie prawdziwa): +Warunek można powiązać również z innym elementem niż aktualny za pomocą `addConditionOn()`. Jako pierwszy parametr podajemy referencję do elementu. W tym przykładzie e-mail będzie obowiązkowy tylko wtedy, gdy zaznaczy się checkbox (jego wartość będzie true): ```php -$form->addCheckbox('newsletters', 'send me newsletters'); +$form->addCheckbox('newsletters', 'wysyłaj mi newslettery'); -$form->addEmail('email', 'Email:') - // jeśli pole wyboru jest zaznaczone - ->addConditionOn($form['newsletters']], $form::Equal, true) - // następnie zażądać e-maila - ->setRequired('Wpisz adres e-mail'); +$form->addEmail('email', 'E-mail:') + // jeśli checkbox jest zaznaczony + ->addConditionOn($form['newsletters'], $form::Equal, true) + // to wymagaj e-maila + ->setRequired('Podaj adres e-mail'); ``` -Złożone struktury mogą być tworzone z warunków przy użyciu `elseCondition()` i `endCondition()`: +Z warunków można tworzyć złożone struktury za pomocą `elseCondition()` i `endCondition()`: ```php $form->addText(/* ... */) @@ -136,38 +147,38 @@ $form->addText(/* ... */) ->elseCondition() // jeśli drugi warunek nie jest spełniony ->addRule(/* ... */) // wymagaj tych reguł ->addRule(/* ... */) - ->endCondition() // powrót do pierwszego warunku + ->endCondition() // wracamy do pierwszego warunku ->addRule(/* ... */); ``` -W Nett bardzo łatwo jest również reagować na spełnienie lub niespełnienie warunku po stronie JavaScript za pomocą metody `toggle()`, patrz [dynamiczny JavaScript |#Dynamic-JavaScript]. +W Nette można bardzo łatwo reagować na spełnienie lub niespełnienie warunku również po stronie JavaScriptu za pomocą metody `toggle()`, zobacz [#Dynamiczny JavaScript]. -Odniesienia między elementami .[#toc-references-between-controls] -================================================================= +Odwołanie do innego elementu +============================ -Odwołanie do innego elementu może być podane jako argument do reguły lub warunku. Tak więc, na przykład, możliwe jest dynamiczne sprawdzenie, czy element `text` ma tyle znaków, ile wynosi wartość elementu `length`: +Jako argument reguły lub warunku można przekazać również inny element formularza. Reguła następnie użyje wartości wprowadzonej później przez użytkownika w przeglądarce. W ten sposób można np. dynamicznie walidować, że element `password` zawiera ten sam ciąg znaków co element `password_confirm`: ```php -$form->addInteger('length'); -$form->addText('text') - ->addRule($form::Length, null, $form['length']); +$form->addPassword('password', 'Hasło'); +$form->addPassword('password_confirm', 'Potwierdź hasło') + ->addRule($form::Equal, 'Podane hasła nie pasują', $form['password']); ``` -Niestandardowe zasady i warunki .[#toc-custom-rules-and-conditions] -=================================================================== +Własne reguły i warunki +======================= -Od czasu do czasu trafiamy na sytuacje, w których wbudowane w Nette reguły walidacji nie są wystarczające i musimy walidować dane użytkownika na swój własny sposób. W Nette jest to bardzo proste! +Czasami dochodzimy do sytuacji, gdy wbudowane reguły walidacji w Nette nam nie wystarczają i potrzebujemy zweryfikować dane od użytkownika po swojemu. W Nette jest to bardzo proste! -Jako pierwszy parametr możesz przekazać dowolne wywołanie zwrotne do metod `addRule()` lub `addCondition()`. Akceptuje sam element jako pierwszy parametr i zwraca wartość boolean wskazującą, czy walidacja zakończyła się sukcesem. Podczas dodawania reguły za pomocą `addRule()` można przekazać dodatkowe argumenty, które są wtedy przekazywane jako drugi parametr. +Metodom `addRule()` lub `addCondition()` można jako pierwszy parametr przekazać dowolny callback. Przyjmuje on jako pierwszy parametr sam element i zwraca wartość boolean określającą, czy walidacja przebiegła pomyślnie. Przy dodawaniu reguły za pomocą `addRule()` można podać również dodatkowe argumenty, które są następnie przekazywane jako drugi parametr. -Możemy więc stworzyć własny zestaw walidatorów jako klasę z metodami statycznymi: +Własny zestaw walidatorów możemy więc utworzyć jako klasę z metodami statycznymi: ```php class MyValidators { - // sprawdza, czy wartość jest podzielna przez argument + // testuje, czy wartość jest podzielna przez argument public static function validateDivisibility(BaseControl $input, $arg): bool { return $input->getValue() % $arg === 0; @@ -175,23 +186,23 @@ class MyValidators public static function validateEmailDomain(BaseControl $input, $domain) { - // dodatkowe walidatory + // inne walidatory } } ``` -Użytkowanie jest wtedy bardzo proste: +Użycie jest następnie bardzo proste: ```php $form->addInteger('num') ->addRule( [MyValidators::class, 'validateDivisibility'], - 'Hodnota musí být násobkem čísla %d', + 'Wartość musi być wielokrotnością liczby %d', 8, ); ``` -Niestandardowe reguły walidacji mogą być dodane do JavaScript. Jedynym warunkiem jest to, że reguła musi być metodą statyczną. Jego nazwa dla walidatora JavaScript jest tworzona przez konkatenację nazwy klasy bez backslashes `\`, podtržítka `_` i nazwy metody. Na przykład, `App\MyValidators::validateDivisibility` jest zapisany jako `AppMyValidators_validateDivisibility` i dodany do obiektu `Nette.validators`: +Własne reguły walidacji można dodawać również do JavaScriptu. Warunkiem jest, aby reguła była metodą statyczną. Jej nazwa dla walidatora JavaScriptowego powstaje przez połączenie nazwy klasy bez ukośników wstecznych `\`, podkreślenia `_` i nazwy metody. Np. `App\MyValidators::validateDivisibility` zapiszemy jako `AppMyValidators_validateDivisibility` i dodamy do obiektu `Nette.validators`: ```js Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => { @@ -200,12 +211,12 @@ Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => ``` -Zdarzenie OnValidate .[#toc-event-onvalidate] -============================================= +Zdarzenie onValidate +==================== -Po przesłaniu formularza, walidacja odbywa się poprzez sprawdzenie poszczególnych reguł dodanych przez `addRule()`, a następnie wywołanie [zdarzenia |nette:glossary#Events] `onValidate`. Jego handler może być użyty do dodatkowej walidacji, zazwyczaj do sprawdzenia poprawnej kombinacji wartości w wielu elementach formularza. +Po wysłaniu formularza przeprowadzana jest walidacja, podczas której sprawdzane są poszczególne reguły dodane za pomocą `addRule()`, a następnie wywoływane jest [zdarzenie |nette:glossary#Eventy zdarzenia] `onValidate`. Jego handler można wykorzystać do dodatkowej walidacji, typowo sprawdzenia poprawnej kombinacji wartości w wielu elementach formularza. -Jeśli zostanie wykryty błąd, jest on przekazywany do formularza za pomocą metody `addError()`. Można to wywołać na konkretnym elemencie lub bezpośrednio na formularzu. +Jeśli zostanie wykryty błąd, przekazujemy go do formularza metodą `addError()`. Można ją wywołać albo na konkretnym elemencie, albo bezpośrednio na formularzu. ```php protected function createComponentSignInForm(): Form @@ -219,91 +230,90 @@ protected function createComponentSignInForm(): Form public function validateSignInForm(Form $form, \stdClass $data): void { if ($data->foo > 1 && $data->bar > 5) { - $form->addError('Tato kombinace není možná.'); + $form->addError('Ta kombinacja nie jest możliwa.'); } } ``` -Błędy w przetwarzaniu .[#toc-processing-errors] -=============================================== +Błędy podczas przetwarzania +=========================== -W wielu przypadkach o błędzie dowiadujemy się dopiero w trakcie przetwarzania poprawnego formularza, np. wprowadzamy nową pozycję do bazy danych i napotykamy na zduplikowany klucz. W tym przypadku błąd jest ponownie przekazywany do formularza za pomocą metody `addError()`. Można to wywołać zarówno na konkretnym elemencie, jak i bezpośrednio na formularzu: +W wielu przypadkach o błędzie dowiadujemy się dopiero w momencie, gdy przetwarzamy poprawny formularz, na przykład zapisujemy nową pozycję do bazy danych i napotykamy na duplikat kluczy. W takim przypadku błąd ponownie przekazujemy do formularza metodą `addError()`. Można ją wywołać albo na konkretnym elemencie, albo bezpośrednio na formularzu: ```php try { $data = $form->getValues(); $this->user->login($data->username, $data->password); - $this->redirect('Strona główna:'); + $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) { - $form->addError('Neplatné heslo.'); + $form->addError('Nieprawidłowe hasło.'); } } ``` -Jeśli to możliwe, zalecamy dołączenie błędu bezpośrednio do elementu formularza, ponieważ pojawi się on obok niego podczas korzystania z domyślnego renderera. +Jeśli to możliwe, zalecamy dołączenie błędu bezpośrednio do elementu formularza, ponieważ zostanie on wyświetlony obok niego przy użyciu domyślnego renderera. ```php -$form['date']->addError('Omlouváme se, ale toto datum již je zabrané.'); +$form['date']->addError('Przepraszamy, ale ta data jest już zajęta.'); ``` -Możesz wywołać `addError()` wielokrotnie, aby przekazać wiele komunikatów o błędach do formularza lub elementu. Można je uzyskać korzystając ze strony `getErrors()`. +Możesz wywoływać `addError()` wielokrotnie i w ten sposób przekazać formularzowi lub elementowi więcej komunikatów o błędach. Uzyskasz je za pomocą `getErrors()`. -Zauważ, że `$form->getErrors()` zwraca podsumowanie wszystkich komunikatów o błędach, nawet tych, które zostały przekazane bezpośrednio do poszczególnych elementów, a nie tylko bezpośrednio do formularza. Komunikaty o błędach przekazywane tylko do formularza są pobierane przez `$form->getOwnErrors()`. +Uwaga, `$form->getErrors()` zwraca podsumowanie wszystkich komunikatów o błędach, również tych, które zostały przekazane bezpośrednio poszczególnym elementom, a nie tylko bezpośrednio formularzowi. Komunikaty o błędach przekazane tylko formularzowi uzyskasz przez `$form->getOwnErrors()`. -Wprowadzanie danych .[#toc-modifying-input-values] -================================================== +Modyfikacja danych wejściowych +============================== Za pomocą metody `addFilter()` możemy zmodyfikować wartość wprowadzoną przez użytkownika. W tym przykładzie będziemy tolerować i usuwać spacje w kodzie pocztowym: ```php -$form->addText('zip', 'zip:') +$form->addText('zip', 'Kod pocztowy:') ->addFilter(function ($value) { - return str_replace(' ', '', $value); // usuń spacje z kodu pocztowego + return str_replace(' ', '', $value); // usuwamy spacje z kodu pocztowego }) - ->addRule($form::Pattern, 'ZIP code is not in five '\d{5}'); + ->addRule($form::Pattern, 'Kod pocztowy nie ma formatu pięciu cyfr', '\d{5}'); ``` -Filtr jest zawarty pomiędzy regułami walidacji i warunkami, a więc zależy od kolejności metod, tzn. filtr i reguła są wywoływane w kolejności metod `addFilter()` i `addRule()`. +Filtr jest włączany między reguły walidacji i warunki, a zatem zależy od kolejności metod, tzn. filtr i reguła są wywoływane w takiej kolejności, jak kolejność metod `addFilter()` i `addRule()`. -Walidacja w JavaScript .[#toc-javascript-validation] -==================================================== +Walidacja JavaScript +==================== -Język do formułowania warunków i zasad jest bardzo potężny. Wszystkie konstrukcje działają zarówno po stronie serwera, jak i po stronie JavaScript. Jest on przekazywany jako JSON w atrybutach HTML strony `data-nette-rules`. -Sama walidacja jest następnie wykonywana przez skrypt, który przechwytuje zdarzenie formularza `submit`, przechodzi przez elementy i wykonuje odpowiednią walidację. +Język do formułowania warunków i reguł jest bardzo potężny. Wszystkie konstrukcje działają zarówno po stronie serwera, jak i po stronie JavaScriptu. Są one przekazywane w atrybutach HTML `data-nette-rules` jako JSON. Samą walidację przeprowadza następnie skrypt, który przechwytuje zdarzenie formularza `submit`, przechodzi przez poszczególne elementy i wykonuje odpowiednią walidację. -Ten skrypt to `netteForms.js` i jest dostępny z wielu możliwych źródeł: +Tym skryptem jest `netteForms.js` i jest dostępny z wielu możliwych źródeł: -Możesz osadzić skrypt bezpośrednio na stronie HTML z CDN: +Skrypt można wstawić bezpośrednio na stronę HTML z CDN: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Albo skopiuj go lokalnie do publicznego folderu projektu (np. z `vendor/nette/forms/src/assets/netteForms.min.js`): +Lub skopiować lokalnie do publicznego folderu projektu (np. z `vendor/nette/forms/src/assets/netteForms.min.js`): ```latte <script src="/path/to/netteForms.min.js"></script> ``` -Lub zainstalować za pośrednictwem [npm: |https://www.npmjs.com/package/nette-forms] +Lub zainstalować przez [npm|https://www.npmjs.com/package/nette-forms]: ```shell npm install nette-forms ``` -A potem załadować i uruchomić: +A następnie załadować i uruchomić: ```js import netteForms from 'nette-forms'; netteForms.initOnLoad(); ``` -Alternatywnie można załadować go bezpośrednio z folderu `vendor`: +Alternatywnie można go załadować bezpośrednio z folderu `vendor`: ```js import netteForms from '../path/to/vendor/nette/forms/src/assets/netteForms.js'; @@ -311,10 +321,10 @@ netteForms.initOnLoad(); ``` -Dynamiczny JavaScript .[#toc-dynamic-javascript] -================================================ +Dynamiczny JavaScript +===================== -Czy chcesz pokazać pola wprowadzania adresu tylko wtedy, gdy użytkownik wybierze wysyłkę towarów pocztą? Nie ma problemu. Kluczem jest para metod `addCondition()` & `toggle()`: +Chcesz wyświetlić pola do wprowadzenia adresu tylko wtedy, gdy użytkownik wybierze wysyłkę towaru pocztą? Żaden problem. Kluczem jest para metod `addCondition()` & `toggle()`: ```php $form->addCheckbox('send_it') @@ -322,25 +332,25 @@ $form->addCheckbox('send_it') ->toggle('#address-container'); ``` -Ten kod mówi, że gdy warunek zostanie spełniony, czyli gdy pole wyboru zostanie zaznaczone, element HTML `#address-container` będzie widoczny. I na odwrót. Umieszczamy więc elementy formularza z adresem odbiorcy w kontenerze o tym ID, a po kliknięciu pola wyboru są one ukrywane lub pokazywane. Zajmuje się tym skrypt `netteForms.js`. +Ten kod mówi, że gdy warunek jest spełniony, czyli gdy checkbox jest zaznaczony, widoczny będzie element HTML `#address-container`. I odwrotnie. Elementy formularza z adresem odbiorcy umieścimy więc w kontenerze o tym ID, a po kliknięciu na checkbox ukryją się lub pokażą. Zapewnia to skrypt `netteForms.js`. -Jako argument do metody `toggle()` można przekazać dowolny selektor. Ze względów historycznych ciąg alfanumeryczny bez żadnych innych znaków specjalnych jest traktowany jako identyfikator elementu, czyli tak samo, jakby był poprzedzony znakiem `#`. Druhý nepovinný parametr umožňuje obrátit chování, tj. pokud bychom použili `toggle('#address-container', false)`, element byłby wyświetlany tylko wtedy, gdy pole wyboru byłoby odznaczone. +Jako argument metody `toggle()` można przekazać dowolny selektor. Z historycznych powodów alfanumeryczny ciąg znaków bez dodatkowych znaków specjalnych jest traktowany jako ID elementu, czyli tak samo, jakby poprzedzał go znak `#`. Drugi, opcjonalny parametr pozwala odwrócić zachowanie, tzn. gdybyśmy użyli `toggle('#address-container', false)`, element zostałby wyświetlony tylko wtedy, gdy checkbox nie byłby zaznaczony. -Domyślna implementacja JavaScript zmienia właściwość `hidden` dla elementów. Możemy jednak łatwo zmienić zachowanie, na przykład dodając animację. Wystarczy nadpisać metodę `Nette.toggle` w JavaScript z niestandardowym rozwiązaniem: +Domyślna implementacja w JavaScript zmienia właściwość `hidden` elementów. Zachowanie można jednak łatwo zmienić, na przykład dodać animację. Wystarczy w JavaScript nadpisać metodę `Nette.toggle` własnym rozwiązaniem: ```js Nette.toggle = (selector, visible, srcElement, event) => { document.querySelectorAll(selector).forEach((el) => { - // skryjeme nebo zobrazime 'el' dle hodnoty 'visible' + // ukrywamy lub pokazujemy 'el' zgodnie z wartością 'visible' }); }; ``` -Wyłączenie walidacji .[#toc-disabling-validation] -================================================= +Wyłączenie walidacji +==================== -Czasami przydatne może być wyłączenie walidacji. Jeśli naciśnięcie przycisku submit nie ma wykonywać walidacji (odpowiednie dla przycisków *Cancel* lub *Preview*), wyłączamy go za pomocą metody `$submit->setValidationScope([])` Jeśli ma on wykonywać tylko częściową walidację, możemy określić, które pola lub kontenery formularza mają być walidowane. +Czasami może się przydać wyłączenie walidacji. Jeśli naciśnięcie przycisku wysyłającego nie ma przeprowadzać walidacji (odpowiednie dla przycisków *Anuluj* lub *Podgląd*), wyłączymy ją metodą `$submit->setValidationScope([])`. Jeśli ma przeprowadzać tylko częściową walidację, możemy określić, które pola lub kontenery formularza mają być walidowane. ```php $form->addText('name') @@ -348,19 +358,19 @@ $form->addText('name') $details = $form->addContainer('details'); $details->addInteger('age') - ->setRequired('age'); + ->setRequired('wiek'); $details->addInteger('age2') - ->setRequired('age2'); + ->setRequired('wiek2'); $form->addSubmit('send1'); // Waliduje cały formularz $form->addSubmit('send2') - ->setValidationScope([]); // Nie przeprowadza walidacji w ogóle + ->setValidationScope([]); // Nie waliduje wcale $form->addSubmit('send3') ->setValidationScope([$form['name']]); // Waliduje tylko element name $form->addSubmit('send4') - ->setValidationScope([$form['details']['age']]); // Sprawdzanie poprawności tylko elementu "age". + ->setValidationScope([$form['details']['age']]); // Waliduje tylko element age $form->addSubmit('send5') - ->setValidationScope([$form['details']]); // Waliduje pojemnik z danymi szczegółowymi + ->setValidationScope([$form['details']]); // Waliduje kontener details ``` -`setValidationScope` Nie wpłynie to na [zdarzenie onValidate |#Event-onValidate] na formularzu, które zawsze będzie wywoływane. Zdarzenie `onValidate` na kontenerze zostanie odpalone tylko wtedy, gdy ten kontener zostanie oznaczony do częściowej walidacji. +`setValidationScope` nie wpływa na [#zdarzenie onValidate] formularza, które zostanie wywołane zawsze. Zdarzenie `onValidate` kontenera zostanie wywołane tylko wtedy, gdy ten kontener jest oznaczony do częściowej walidacji. diff --git a/forms/pt/@home.texy b/forms/pt/@home.texy index 6fa400dd19..6a9f3c2857 100644 --- a/forms/pt/@home.texy +++ b/forms/pt/@home.texy @@ -1,31 +1,31 @@ -Formulários +Nette Forms *********** <div class=perex> -A Nette Forms revolucionou a criação de formulários web. Tudo que você tinha que fazer era escrever algumas linhas claras de código e você tinha um formulário, incluindo renderização, JavaScript e validação do servidor, além de segurança líder da indústria. Vamos ver como +Nette Forms revolucionou a criação de formulários web. De repente, bastava escrever algumas linhas de código compreensíveis e você tinha um formulário completo, incluindo renderização, validação JavaScript e do lado do servidor, e além disso, extremamente seguro. Mostraremos como -- criar formas amigáveis +- criar formulários amigáveis - validar os dados enviados -- desenhar elementos exatamente como necessário +- renderizar elementos exatamente conforme necessário </div> -Com Nette Forms, você pode diminuir tarefas rotineiras como validação de escrita (tanto no lado do servidor quanto do cliente), e minimizar a probabilidade de erros e problemas de segurança. +Ao usar Nette Forms, você evitará uma série de tarefas rotineiras, como escrever validação (além disso, dupla, no lado do servidor e do cliente), minimizará a probabilidade de erros e falhas de segurança. -Você pode usar os formulários como parte da Aplicação Nette (ou seja, em apresentadores) ou autônomos. Como o uso é ligeiramente diferente em ambos os casos, preparamos instruções separadas para você: +Você pode usar formulários como parte da Aplicação Nette (ou seja, em presenters) ou de forma completamente independente. Como o uso difere um pouco em ambos os casos, preparamos dois guias para você: <div class="wiki-buttons"> -<div> "Formulários em apresentadores .[wiki-button]":in-presenter </div> -<div> "Formulários autônomos .[wiki-button]":standalone </div> +<div> "Formulários em presenters .[wiki-button]":in-presenter </div> +<div> "Formulários independentes .[wiki-button]":standalone </div> </div> Instalação ---------- -Baixe e instale o pacote usando [o Composer |best-practices:composer]: +Faça o download e instale a biblioteca usando a ferramenta [Composer|best-practices:composer]: ```shell composer require nette/forms diff --git a/forms/pt/@left-menu.texy b/forms/pt/@left-menu.texy index 9cf08d55c4..9cf4dd5b9b 100644 --- a/forms/pt/@left-menu.texy +++ b/forms/pt/@left-menu.texy @@ -1,14 +1,14 @@ -Formulários +Nette Forms *********** -- [Visão geral |@home] -- [Formulários em Apresentadores |in-presenter] -- [Formulários autônomos |standalone] -- [Controles de formulário |controls] +- [Introdução |@home] +- [Formulários em presenters|in-presenter] +- [Formulários independentes|standalone] +- [Elementos de formulário |controls] - [Validação |validation] - [Renderização |rendering] -- [Configuração |Configuration] +- [Configuração |configuration] Leitura adicional ***************** -- [As melhores práticas |best-practices:] +- [Guias e melhores práticas |best-practices:] diff --git a/forms/pt/@meta.texy b/forms/pt/@meta.texy new file mode 100644 index 0000000000..41a853b6aa --- /dev/null +++ b/forms/pt/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentação Nette}} diff --git a/forms/pt/configuration.texy b/forms/pt/configuration.texy index 27e2ba4896..e3538aa4f6 100644 --- a/forms/pt/configuration.texy +++ b/forms/pt/configuration.texy @@ -2,7 +2,7 @@ Configuração de Formulários *************************** .[perex] -Você pode alterar as [mensagens de erro de forma |validation] padrão na configuração. +Na configuração, é possível alterar as [mensagens de erro padrão dos formulários|validation]. ```neon forms: @@ -30,4 +30,32 @@ forms: Nette\Forms\Controls\CsrfProtection::Protection: 'Your session has expired. Please return to the home page and try again.' ``` -Se você não estiver usando toda a estrutura e, portanto, nem mesmo os arquivos de configuração, você pode alterar as mensagens de erro padrão diretamente no campo `Nette\Forms\Validator::$messages`. +Aqui está a tradução para o português: + +```neon +forms: + messages: + Equal: 'Por favor, insira %s.' + NotEqual: 'Este valor não deve ser %s.' + Filled: 'Este campo é obrigatório.' + Blank: 'Este campo deve estar em branco.' + MinLength: 'Por favor, insira pelo menos %d caracteres.' + MaxLength: 'Por favor, insira no máximo %d caracteres.' + Length: 'Por favor, insira um valor entre %d e %d caracteres.' + Email: 'Por favor, insira um endereço de e-mail válido.' + URL: 'Por favor, insira uma URL válida.' + Integer: 'Por favor, insira um número inteiro válido.' + Float: 'Por favor, insira um número válido.' + Min: 'Por favor, insira um valor maior ou igual a %d.' + Max: 'Por favor, insira um valor menor ou igual a %d.' + Range: 'Por favor, insira um valor entre %d e %d.' + MaxFileSize: 'O tamanho do arquivo enviado pode ser de no máximo %d bytes.' + MaxPostSize: 'Os dados enviados excedem o limite de %d bytes.' + MimeType: 'O arquivo enviado não está no formato esperado.' + Image: 'O arquivo enviado deve ser uma imagem no formato JPEG, GIF, PNG, WebP ou AVIF.' + Nette\Forms\Controls\SelectBox::Valid: 'Por favor, selecione uma opção válida.' + Nette\Forms\Controls\UploadControl::Valid: 'Ocorreu um erro durante o upload do arquivo.' + Nette\Forms\Controls\CsrfProtection::Protection: 'Sua sessão expirou. Por favor, retorne à página inicial e tente novamente.' +``` + +Se você não usa o framework completo e, portanto, nem os arquivos de configuração, pode alterar as mensagens de erro padrão diretamente no array `Nette\Forms\Validator::$messages`. diff --git a/forms/pt/controls.texy b/forms/pt/controls.texy index 9dcebf3609..4b7b77ebbf 100644 --- a/forms/pt/controls.texy +++ b/forms/pt/controls.texy @@ -1,277 +1,372 @@ -Controles de formulário +Elementos de Formulário *********************** .[perex] -Visão geral dos controles de formulário incorporados. +Visão geral dos elementos de formulário padrão. -addText(string|int $name, $label=null): TextInput .[method] -=========================================================== +addText(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +================================================================================================== -Adiciona campo de texto de linha única (classe [TextInput |api:Nette\Forms\Controls\TextInput]). Se o usuário não preencher o campo, retorna uma string vazia `''`, ou usa `setNullable()` para alterá-lo para retornar `null`. +Adiciona um campo de texto de linha única (classe [TextInput |api:Nette\Forms\Controls\TextInput]). Se o usuário não preencher o campo, retorna uma string vazia `''`, ou usando `setNullable()` pode-se especificar que retorne `null`. ```php -$form->addText('name', 'Name:') +$form->addText('name', 'Nome:') ->setRequired() ->setNullable(); ``` -Ela valida automaticamente UTF-8, apara os espaços em branco à esquerda e à direita e remove quebras de linha que poderiam ser enviadas por um atacante. +Valida automaticamente UTF-8, remove espaços à esquerda e à direita e remove quebras de linha que um invasor poderia enviar. -O comprimento máximo pode ser limitado usando `setMaxLength()`. O [addFilter() |validation#Modifying Input Values] permite alterar o valor inserido pelo usuário. +O comprimento máximo pode ser limitado usando `setMaxLength()`. Modificar o valor inserido pelo usuário é possível com [addFilter() |validation#Modificação da entrada]. -Use `setHtmlType()` para mudar o [caráter |https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types] do elemento de entrada para `search`, `tel`, `url`, `range`, `date`, `datetime-local`, `month`, `time`, `week`, `color`. Em vez dos tipos `number` e `email`, recomendamos usar [addInteger |#addInteger] e [addEmail |#addEmail], que fornecem validação do lado do servidor. +Usando `setHtmlType()`, é possível alterar o caractere visual do campo de texto para tipos como `search`, `tel` ou `url`, veja a [especificação|https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types]. Lembre-se que a alteração do tipo é apenas visual e não substitui a função de validação. Para o tipo `url`, é apropriado adicionar uma [regra URL |validation#Entradas de texto] de validação específica. -```php -$form->addText('color', 'Choose color:') - ->setHtmlType('color') - ->addRule($form::Pattern, 'invalid value', '[0-9a-f]{6}'); -``` +.[note] +Para outros tipos de entrada, como `number`, `range`, `email`, `date`, `datetime-local`, `time` e `color`, use métodos especializados como [#addInteger], [#addFloat], [#addEmail] [#addDate], [#addTime], [#addDateTime] e [#addColor], que garantem a validação do lado do servidor. Os tipos `month` e `week` ainda não são totalmente suportados em todos os navegadores. -O chamado valor vazio pode ser definido para o elemento, que é algo como o valor padrão, mas se o usuário não o sobrescrever, retorna uma string vazia ou `null`. +Ao elemento pode ser definido o chamado empty-value, que é algo como um valor padrão, mas se o usuário não o alterar, o elemento retorna uma string vazia ou `null`. ```php -$form->addText('phone', 'Phone:') +$form->addText('phone', 'Telefone:') ->setHtmlType('tel') - ->setEmptyValue('+420'); + ->setEmptyValue('+55'); ``` addTextArea(string|int $name, $label=null): TextArea .[method] ============================================================== -Adiciona um campo de texto com várias linhas (classe [TextArea |api:Nette\Forms\Controls\TextArea]). Se o usuário não preencher o campo, ele retorna uma string vazia `''`, ou use `setNullable()` para alterá-lo para retornar `null`. +Adiciona um campo para inserir texto multilinha (classe [TextArea |api:Nette\Forms\Controls\TextArea]). Se o usuário não preencher o campo, retorna uma string vazia `''`, ou usando `setNullable()` pode-se especificar que retorne `null`. ```php -$form->addTextArea('note', 'Note:') - ->addRule($form::MaxLength, 'Your note is way too long', 10000); +$form->addTextArea('note', 'Nota:') + ->addRule($form::MaxLength, 'A nota é muito longa', 10000); ``` -Valida automaticamente o UTF-8 e normaliza as quebras de linha para `\n`. Ao contrário de um campo de entrada de linha única, ele não apara o espaço em branco. +Valida automaticamente UTF-8 e normaliza os separadores de linha para `\n`. Ao contrário do campo de entrada de linha única, não ocorre remoção de espaços. -O comprimento máximo pode ser limitado usando `setMaxLength()`. O [addFilter() |validation#Modifying Input Values] permite alterar o valor inserido pelo usuário. Você pode definir o chamado valor vazio usando `setEmptyValue()`. +O comprimento máximo pode ser limitado usando `setMaxLength()`. Modificar o valor inserido pelo usuário é possível com [addFilter() |validation#Modificação da entrada]. É possível definir o chamado empty-value usando `setEmptyValue()`. addInteger(string|int $name, $label=null): TextInput .[method] ============================================================== -Adiciona campo de entrada para o número inteiro (classe [TextInput |api:Nette\Forms\Controls\TextInput]). Retorna ou um número inteiro ou `null` se o usuário não inserir nada. +Adiciona um campo para inserir um número inteiro (classe [TextInput |api:Nette\Forms\Controls\TextInput]). Retorna um inteiro ou `null` se o usuário não inserir nada. + +```php +$form->addInteger('year', 'Ano:') + ->addRule($form::Range, 'O ano deve estar no intervalo de %d a %d.', [1900, 2023]); +``` + +O elemento é renderizado como `<input type="number">`. Usando o método `setHtmlType()`, é possível alterar o tipo para `range` para exibição na forma de um controle deslizante, ou para `text`, se preferir um campo de texto padrão sem o comportamento especial do tipo `number`. + + +addFloat(string|int $name, $label=null): TextInput .[method]{data-version:3.1.12} +================================================================================= + +Adiciona um campo para inserir um número decimal (classe [TextInput |api:Nette\Forms\Controls\TextInput]). Retorna um float ou `null` se o usuário não inserir nada. ```php -$form->addInteger('level', 'Level:') +$form->addFloat('level', 'Nível:') ->setDefaultValue(0) - ->addRule($form::Range, 'Level must be between %d and %d.', [0, 100]); + ->addRule($form::Range, 'O nível deve estar no intervalo de %d a %d.', [0, 100]); ``` +O elemento é renderizado como `<input type="number">`. Usando o método `setHtmlType()`, é possível alterar o tipo para `range` para exibição na forma de um controle deslizante, ou para `text`, se preferir um campo de texto padrão sem o comportamento especial do tipo `number`. + +Nette e o navegador Chrome aceitam tanto vírgula quanto ponto como separador decimal. Para que essa funcionalidade esteja disponível também no Firefox, é recomendado definir o atributo `lang` para o elemento específico ou para a página inteira, por exemplo, `<html lang="pt-BR">`. + -addEmail(string|int $name, $label=null): TextInput .[method] -============================================================ +addEmail(string|int $name, $label=null, int $maxLength=255): TextInput .[method] +================================================================================ -Adiciona campo de endereço de e-mail com verificação de validade (classe [TextInput |api:Nette\Forms\Controls\TextInput]). Se o usuário não preencher o campo, devolve uma string vazia `''`, ou usa `setNullable()` para alterá-lo para retornar `null`. +Adiciona um campo para inserir um endereço de e-mail (classe [TextInput |api:Nette\Forms\Controls\TextInput]). Se o usuário não preencher o campo, retorna uma string vazia `''`, ou usando `setNullable()` pode-se especificar que retorne `null`. ```php -$form->addEmail('email', 'Email:'); +$form->addEmail('email', 'E-mail:'); ``` -Verifica que o valor é um endereço de e-mail válido. Não verifica que o domínio realmente existe, apenas a sintaxe é verificada. Valida automaticamente o UTF-8, apara espaços em branco à esquerda e à direita. +Verifica se o valor é um endereço de e-mail válido. Não verifica se o domínio realmente existe, apenas a sintaxe é verificada. Valida automaticamente UTF-8, remove espaços à esquerda e à direita. -O comprimento máximo pode ser limitado usando `setMaxLength()`. O [addFilter() |validation#Modifying Input Values] permite alterar o valor inserido pelo usuário. Você pode definir o chamado valor vazio usando `setEmptyValue()`. +O comprimento máximo pode ser limitado usando `setMaxLength()`. Modificar o valor inserido pelo usuário é possível com [addFilter() |validation#Modificação da entrada]. É possível definir o chamado empty-value usando `setEmptyValue()`. -addPassword(string|int $name, $label=null): TextInput .[method] -=============================================================== +addPassword(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +====================================================================================================== -Adiciona o campo de senha (classe [TextInput |api:Nette\Forms\Controls\TextInput]). +Adiciona um campo para inserir uma senha (classe [TextInput |api:Nette\Forms\Controls\TextInput]). ```php -$form->addPassword('password', 'Password:') +$form->addPassword('password', 'Senha:') ->setRequired() - ->addRule($form::MinLength, 'Password has to be at least %d characters long', 8) - ->addRule($form::Pattern, 'Password must contain a number', '.*[0-9].*'); + ->addRule($form::MinLength, 'A senha deve ter pelo menos %d caracteres', 8) + ->addRule($form::Pattern, 'Deve conter um dígito', '.*[0-9].*'); ``` -Quando você reenviar o formulário, a entrada estará em branco. Ela valida automaticamente o UTF-8, apara os espaços em branco à esquerda e à direita e remove quebras de linha que poderiam ser enviadas por um atacante. +Ao reexibir o formulário, o campo estará vazio. Valida automaticamente UTF-8, remove espaços à esquerda e à direita e remove quebras de linha que um invasor poderia enviar. addCheckbox(string|int $name, $caption=null): Checkbox .[method] ================================================================ -Acrescenta uma caixa de seleção ( [caixa de seleção de |api:Nette\Forms\Controls\Checkbox] classe). O campo retorna ou `true` ou `false`, dependendo se está marcado. +Adiciona uma caixa de seleção (classe [Checkbox |api:Nette\Forms\Controls\Checkbox]). Retorna o valor `true` ou `false`, dependendo se está marcada. ```php -$form->addCheckbox('agree', 'I agree with terms') - ->setRequired('You must agree with our terms'); +$form->addCheckbox('agree', 'Concordo com os termos') + ->setRequired('É necessário concordar com os termos'); ``` -addCheckboxList(string|int $name, $label=null, array $items=null): CheckboxList .[method] -========================================================================================= +addCheckboxList(string|int $name, $label=null, ?array $items=null): CheckboxList .[method] +========================================================================================== -Adiciona lista de caixas de seleção para selecionar vários itens (classe [CheckboxList |api:Nette\Forms\Controls\CheckboxList]). Retorna a matriz de chaves dos itens selecionados. O método `getSelectedItems()` retorna valores em vez de chaves. +Adiciona caixas de seleção para escolher vários itens (classe [CheckboxList |api:Nette\Forms\Controls\CheckboxList]). Retorna um array das chaves dos itens selecionados. O método `getSelectedItems()` retorna os valores em vez das chaves. ```php -$form->addCheckboxList('colors', 'Colors:', [ - 'r' => 'red', - 'g' => 'green', - 'b' => 'blue', +$form->addCheckboxList('colors', 'Cores:', [ + 'r' => 'vermelho', + 'g' => 'verde', + 'b' => 'azul', ]); ``` -Passamos o conjunto de itens como o terceiro parâmetro, ou pelo método `setItems()`. +O array de itens oferecidos é passado como terceiro parâmetro ou pelo método `setItems()`. -Você pode usar `setDisabled(['r', 'g'])` para desativar itens individuais. +Usando `setDisabled(['r', 'g'])`, é possível desativar itens individuais. -O elemento verifica automaticamente que não houve falsificação e que os itens selecionados são realmente um dos oferecidos e não foram desativados. O método `getRawValue()` pode ser usado para recuperar os itens submetidos sem esta importante verificação. +O elemento verifica automaticamente se não houve falsificação e se os itens selecionados estão realmente entre os oferecidos e não foram desativados. O método `getRawValue()` permite obter os itens enviados sem essa importante verificação. -Quando os valores padrão são definidos, ele também verifica se eles são um dos itens oferecidos, caso contrário, ele lança uma exceção. Esta verificação pode ser desativada com `checkDefaultValue(false)`. +Ao definir os itens selecionados padrão, também verifica se eles estão entre os oferecidos, caso contrário, lança uma exceção. Essa verificação pode ser desativada usando `checkDefaultValue(false)`. +Se você enviar o formulário pelo método `GET`, pode escolher um método de transmissão de dados mais compacto, que economiza o tamanho da query string. Ele é ativado definindo o atributo HTML do formulário: + +```php +$form->setHtmlAttribute('data-nette-compact'); +``` -addRadioList(string|int $name, $label=null, array $items=null): RadioList .[method] -=================================================================================== -Acrescenta botões de rádio (classe [RadioList |api:Nette\Forms\Controls\RadioList]). Devolve a chave do item selecionado, ou `null` caso o usuário não tenha selecionado nada. O método `getSelectedItem()` retorna um valor em vez de uma chave. +addRadioList(string|int $name, $label=null, ?array $items=null): RadioList .[method] +==================================================================================== + +Adiciona botões de opção (classe [RadioList |api:Nette\Forms\Controls\RadioList]). Retorna a chave do item selecionado, ou `null` se o usuário não selecionou nada. O método `getSelectedItem()` retorna o valor em vez da chave. ```php $sex = [ - 'm' => 'male', - 'f' => 'female', + 'm' => 'masculino', + 'f' => 'feminino', ]; -$form->addRadioList('gender', 'Gender:', $sex); +$form->addRadioList('gender', 'Sexo:', $sex); ``` -Passamos o conjunto de itens como o terceiro parâmetro, ou pelo método `setItems()`. +O array de itens oferecidos é passado como terceiro parâmetro ou pelo método `setItems()`. -Você pode usar `setDisabled(['m'])` para desativar itens individuais. +Usando `setDisabled(['m', 'f'])`, é possível desativar itens individuais. -O elemento verifica automaticamente que não houve falsificação e que o item selecionado é realmente um dos oferecidos e não foi desativado. O método `getRawValue()` pode ser usado para recuperar o item submetido sem esta importante verificação. +O elemento verifica automaticamente se não houve falsificação e se o item selecionado está realmente entre os oferecidos e não foi desativado. O método `getRawValue()` permite obter o item enviado sem essa importante verificação. -Quando o valor padrão é definido, ele também verifica se ele é um dos itens oferecidos, caso contrário, ele lança uma exceção. Esta verificação pode ser desativada com `checkDefaultValue(false)`. +Ao definir o item selecionado padrão, também verifica se ele está entre os oferecidos, caso contrário, lança uma exceção. Essa verificação pode ser desativada usando `checkDefaultValue(false)`. -addSelect(string|int $name, $label=null, array $items=null): SelectBox .[method] -================================================================================ +addSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): SelectBox .[method] +================================================================================================== -Adiciona caixa de seleção (classe [SelectBox |api:Nette\Forms\Controls\SelectBox]). Devolve a chave do item selecionado, ou `null` caso o usuário não tenha selecionado nada. O método `getSelectedItem()` retorna um valor em vez de uma chave. +Adiciona uma caixa de seleção (classe [SelectBox |api:Nette\Forms\Controls\SelectBox]). Retorna a chave do item selecionado, ou `null` se o usuário não selecionou nada. O método `getSelectedItem()` retorna o valor em vez da chave. ```php $countries = [ - 'CZ' => 'Czech republic', - 'SK' => 'Slovakia', - 'GB' => 'United Kingdom', + 'BR' => 'Brasil', + 'PT' => 'Portugal', + 'GB' => 'Reino Unido', ]; -$form->addSelect('country', 'Country:', $countries) - ->setDefaultValue('SK'); +$form->addSelect('country', 'País:', $countries) + ->setDefaultValue('BR'); ``` -Passamos o conjunto de itens como o terceiro parâmetro, ou pelo método `setItems()`. A matriz de itens também pode ser bidimensional: +O array de itens oferecidos é passado como terceiro parâmetro ou pelo método `setItems()`. Os itens também podem ser um array bidimensional: ```php $countries = [ - 'Europe' => [ - 'CZ' => 'Czech republic', - 'SK' => 'Slovakia', - 'GB' => 'United Kingdom', + 'Europa' => [ + 'CZ' => 'República Tcheca', + 'SK' => 'Eslováquia', + 'GB' => 'Reino Unido', ], - 'CA' => 'Canada', - 'US' => 'USA', - '?' => 'other', + 'CA' => 'Canadá', + 'US' => 'EUA', + '?' => 'outro', ]; ``` -Para caixas seletas, o primeiro item muitas vezes tem um significado especial, serve como uma chamada para ação. Use o método `setPrompt()` para adicionar tal entrada. +Nas caixas de seleção, o primeiro item geralmente tem um significado especial, servindo como um prompt para ação. Para adicionar tal item, use o método `setPrompt()`. ```php -$form->addSelect('country', 'Country:', $countries) - ->setPrompt('Pick a country'); +$form->addSelect('country', 'País:', $countries) + ->setPrompt('Escolha um país'); ``` -Você pode usar `setDisabled(['CZ', 'SK'])` para desativar itens individuais. +Usando `setDisabled(['CZ', 'SK'])`, é possível desativar itens individuais. -O elemento verifica automaticamente que não houve falsificação e que o item selecionado é realmente um dos oferecidos e não foi desativado. O método `getRawValue()` pode ser usado para recuperar o item submetido sem esta importante verificação. +O elemento verifica automaticamente se não houve falsificação e se o item selecionado está realmente entre os oferecidos e não foi desativado. O método `getRawValue()` permite obter o item enviado sem essa importante verificação. -Quando o valor padrão é definido, ele também verifica se ele é um dos itens oferecidos, caso contrário, ele lança uma exceção. Esta verificação pode ser desativada com `checkDefaultValue(false)`. +Ao definir o item selecionado padrão, também verifica se ele está entre os oferecidos, caso contrário, lança uma exceção. Essa verificação pode ser desativada usando `checkDefaultValue(false)`. -addMultiSelect(string|int $name, $label=null, array $items=null): MultiSelectBox .[method] -========================================================================================== +addMultiSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): MultiSelectBox .[method] +============================================================================================================ -Adiciona caixa de seleção de múltipla escolha (classe [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox]). Devolve a matriz de chaves dos itens selecionados. O método `getSelectedItems()` retorna valores em vez de chaves. +Adiciona uma caixa de seleção para escolher vários itens (classe [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox]). Retorna um array das chaves dos itens selecionados. O método `getSelectedItems()` retorna os valores em vez das chaves. ```php -$form->addMultiSelect('countries', 'Countries:', $countries); +$form->addMultiSelect('countries', 'Países:', $countries); ``` -Passamos o conjunto de itens como o terceiro parâmetro, ou pelo método `setItems()`. A matriz de itens também pode ser bidimensional. +O array de itens oferecidos é passado como terceiro parâmetro ou pelo método `setItems()`. Os itens também podem ser um array bidimensional. -Você pode usar `setDisabled(['CZ', 'SK'])` para desativar itens individuais. +Usando `setDisabled(['CZ', 'SK'])`, é possível desativar itens individuais. -O elemento verifica automaticamente que não houve falsificação e que os itens selecionados são realmente um dos oferecidos e não foram desativados. O método `getRawValue()` pode ser usado para recuperar os itens submetidos sem esta importante verificação. +O elemento verifica automaticamente se não houve falsificação e se os itens selecionados estão realmente entre os oferecidos e não foram desativados. O método `getRawValue()` permite obter os itens enviados sem essa importante verificação. -Quando os valores padrão são definidos, ele também verifica se eles são um dos itens oferecidos, caso contrário, ele lança uma exceção. Esta verificação pode ser desativada com `checkDefaultValue(false)`. +Ao definir os itens selecionados padrão, também verifica se eles estão entre os oferecidos, caso contrário, lança uma exceção. Essa verificação pode ser desativada usando `checkDefaultValue(false)`. addUpload(string|int $name, $label=null): UploadControl .[method] ================================================================= -Adiciona o campo upload do arquivo (classe [UploadControl |api:Nette\Forms\Controls\UploadControl]). Retorna o objeto [FileUpload |http:request#FileUpload], mesmo que o usuário não tenha feito upload de um arquivo, o que pode ser descoberto pelo método `FileUpload::hasFile()`. +Adiciona um campo para upload de arquivo (classe [UploadControl |api:Nette\Forms\Controls\UploadControl]). Retorna um objeto [FileUpload |http:request#FileUpload], mesmo que o usuário não tenha enviado nenhum arquivo, o que pode ser verificado pelo método `FileUpload::hasFile()`. ```php $form->addUpload('avatar', 'Avatar:') - ->addRule($form::Image, 'Avatar must be JPEG, PNG, GIF or WebP') - ->addRule($form::MaxFileSize, 'Maximum size is 1 MB', 1024 * 1024); + ->addRule($form::Image, 'O avatar deve ser JPEG, PNG, GIF, WebP ou AVIF.') + ->addRule($form::MaxFileSize, 'O tamanho máximo é 1 MB.', 1024 * 1024 /* 1 MB em bytes */); ``` -Se o arquivo não foi carregado corretamente, o formulário não foi enviado com sucesso e um erro é exibido. Ou seja, não é necessário verificar o método `FileUpload::isOk()`. +Se o arquivo não for carregado corretamente, o formulário não é enviado com sucesso e um erro é exibido. Ou seja, em caso de envio bem-sucedido, não é necessário verificar o método `FileUpload::isOk()`. -Não confie no nome do arquivo original devolvido pelo método `FileUpload::getName()`, um cliente poderia enviar um nome de arquivo malicioso com a intenção de corromper ou invadir seu aplicativo. +Nunca confie no nome original do arquivo retornado pelo método `FileUpload::getName()`, o cliente pode ter enviado um nome de arquivo malicioso com a intenção de danificar ou hackear sua aplicação. -As regras `MimeType` e `Image` detectam o tipo de arquivo ou imagem exigida por sua assinatura. A integridade do arquivo inteiro não é verificada. Você pode descobrir se uma imagem não está corrompida, por exemplo, ao tentar [carregá-la |http:request#toImage]. +As regras `MimeType` e `Image` detectam o tipo necessário com base na assinatura do arquivo e não verificam sua integridade. Se a imagem está danificada pode ser verificado, por exemplo, tentando [carregá-la |http:request#toImage]. addMultiUpload(string|int $name, $label=null): UploadControl .[method] ====================================================================== -Adiciona o campo de carregamento de múltiplos arquivos (classe [UploadControl |api:Nette\Forms\Controls\UploadControl]). Retorna um array de objetos [FileUpload |http:request#FileUpload]. O método `FileUpload::hasFile()` retornará `true` para cada um deles. +Adiciona um campo para upload de vários arquivos de uma vez (classe [UploadControl |api:Nette\Forms\Controls\UploadControl]). Retorna um array de objetos [FileUpload |http:request#FileUpload]. O método `FileUpload::hasFile()` em cada um deles retornará `true`. ```php -$form->addMultiUpload('files', 'Files:') - ->addRule($form::MaxLength, 'A maximum of %d files can be uploaded', 10); +$form->addMultiUpload('files', 'Arquivos:') + ->addRule($form::MaxLength, 'No máximo %d arquivos podem ser enviados', 10); ``` -Se um dos arquivos não for carregado corretamente, o formulário não foi enviado com sucesso e um erro é exibido. Ou seja, não é necessário verificar o método `FileUpload::isOk()`. +Se algum arquivo não for carregado corretamente, o formulário não é enviado com sucesso e um erro é exibido. Ou seja, em caso de envio bem-sucedido, não é necessário verificar o método `FileUpload::isOk()`. + +Nunca confie nos nomes originais dos arquivos retornados pelo método `FileUpload::getName()`, o cliente pode ter enviado um nome de arquivo malicioso com a intenção de danificar ou hackear sua aplicação. + +As regras `MimeType` e `Image` detectam o tipo necessário com base na assinatura do arquivo e não verificam sua integridade. Se a imagem está danificada pode ser verificado, por exemplo, tentando [carregá-la |http:request#toImage]. + -Não confie nos nomes originais dos arquivos devolvidos pelo método `FileUpload::getName()`, um cliente poderia enviar um nome de arquivo malicioso com a intenção de corromper ou invadir sua aplicação. +addDate(string|int $name, $label=null): DateTimeControl .[method]{data-version:3.1.14} +====================================================================================== -As regras `MimeType` e `Image` detectam o tipo de arquivo ou imagem exigida por sua assinatura. A integridade do arquivo inteiro não é verificada. Você pode descobrir se uma imagem não está corrompida, por exemplo, ao tentar [carregá-la |http:request#toImage]. +Adiciona um campo que permite ao usuário inserir facilmente uma data composta por ano, mês e dia (classe [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). +Como valor padrão, aceita objetos que implementam a interface `DateTimeInterface`, uma string com a hora, ou um número representando um timestamp UNIX. O mesmo se aplica aos argumentos das regras `Min`, `Max` ou `Range`, que definem a data mínima e máxima permitida. -addHidden(string|int $name, string $default=null): HiddenField .[method] -======================================================================== +```php +$form->addDate('date', 'Data:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'A data deve ter pelo menos um mês.', new DateTime('-1 month')); +``` -Adiciona campo oculto (classe [HiddenField |api:Nette\Forms\Controls\HiddenField]). +Por padrão, retorna um objeto `DateTimeImmutable`, com o método `setFormat()` você pode especificar o [formato de texto|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] ou timestamp: + +```php +$form->addDate('date', 'Data:') + ->setFormat('Y-m-d'); +``` + + +addTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=============================================================================================================== + +Adiciona um campo que permite ao usuário inserir facilmente uma hora composta por horas, minutos e opcionalmente segundos (classe [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Como valor padrão, aceita objetos que implementam a interface `DateTimeInterface`, uma string com a hora, ou um número representando um timestamp UNIX. Desses inputs, apenas a informação de tempo é utilizada, a data é ignorada. O mesmo se aplica aos argumentos das regras `Min`, `Max` ou `Range`, que definem a hora mínima e máxima permitida. Se o valor mínimo definido for maior que o máximo, cria-se um intervalo de tempo que ultrapassa a meia-noite. + +```php +$form->addTime('time', 'Hora:', withSeconds: true) + ->addRule($form::Range, 'A hora deve estar no intervalo de %d a %d.', ['12:30', '13:30']); +``` + +Por padrão, retorna um objeto `DateTimeImmutable` (com a data de 1º de janeiro do ano 1), com o método `setFormat()` você pode especificar o [formato de texto|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters]: + +```php +$form->addTime('time', 'Hora:') + ->setFormat('H:i'); +``` + + +addDateTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=================================================================================================================== + +Adiciona um campo que permite ao usuário inserir facilmente data e hora compostas por ano, mês, dia, horas, minutos e opcionalmente segundos (classe [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Como valor padrão, aceita objetos que implementam a interface `DateTimeInterface`, uma string com a hora, ou um número representando um timestamp UNIX. O mesmo se aplica aos argumentos das regras `Min`, `Max` ou `Range`, que definem a data mínima e máxima permitida. + +```php +$form->addDateTime('datetime', 'Data e hora:') + ->setDefaultValue(new \DateTime) + ->addRule($form::Min, 'A data deve ter pelo menos um mês.', new \DateTime('-1 month')); +``` + +Por padrão, retorna um objeto `DateTimeImmutable`, com o método `setFormat()` você pode especificar o [formato de texto|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] ou timestamp: + +```php +$form->addDateTime('datetime') + ->setFormat(DateTimeControl::FormatTimestamp); +``` + + +addColor(string|int $name, $label=null): ColorPicker .[method]{data-version:3.1.14} +=================================================================================== + +Adiciona um campo para seleção de cor (classe [ColorPicker |api:Nette\Forms\Controls\ColorPicker]). A cor é uma string no formato `#rrggbb`. Se o usuário não fizer a escolha, retorna a cor preta `#000000`. + +```php +$form->addColor('color', 'Cor:') + ->setDefaultValue('#3C8ED7'); +``` + + +addHidden(string|int $name, ?string $default=null): HiddenField .[method] +========================================================================= + +Adiciona um campo oculto (classe [HiddenField |api:Nette\Forms\Controls\HiddenField]). ```php $form->addHidden('userid'); ``` -Use `setNullable()` para mudá-lo para retornar `null` ao invés de um fio vazio. O [addFilter() |validation#Modifying Input Values] permite que você altere o valor submetido. +Usando `setNullable()`, pode-se definir que retorne `null` em vez de uma string vazia. Modificar o valor enviado é possível com [addFilter() |validation#Modificação da entrada]. + +Embora o elemento esteja oculto, é **importante notar** que o valor ainda pode ser modificado ou falsificado por um invasor. Sempre verifique e valide cuidadosamente todos os valores recebidos no lado do servidor para evitar riscos de segurança associados à manipulação de dados. addSubmit(string|int $name, $caption=null): SubmitButton .[method] ================================================================== -Adiciona botão submeter (classe [SubmitButton |api:Nette\Forms\Controls\SubmitButton]). +Adiciona um botão de envio (classe [SubmitButton |api:Nette\Forms\Controls\SubmitButton]). ```php -$form->addSubmit('submit', 'Register'); +$form->addSubmit('submit', 'Enviar'); ``` -É possível ter mais de um botão de envio no formulário: +No formulário, é possível ter vários botões de envio: ```php -$form->addSubmit('register', 'Register'); -$form->addSubmit('cancel', 'Cancel'); +$form->addSubmit('register', 'Registrar'); +$form->addSubmit('cancel', 'Cancelar'); ``` Para descobrir qual deles foi clicado, use: @@ -282,48 +377,48 @@ if ($form['register']->isSubmittedBy()) { } ``` -Se você não quiser validar o formulário quando um botão de envio for pressionado (como *Cancelar* ou *Pré-visualizar* botões), você pode desligá-lo com [setValidationScope() |validation#Disabling Validation]. +Se você não quiser validar o formulário inteiro ao pressionar o botão (por exemplo, para botões *Cancelar* ou *Visualizar*), use [setValidationScope() |validation#Desativação da validação]. addButton(string|int $name, $caption): Button .[method] ======================================================= -Adiciona botão ( [Botão de |api:Nette\Forms\Controls\Button] classe) sem função de submissão. É útil para vincular outras funcionalidades à identificação, por exemplo, uma ação JavaScript. +Adiciona um botão (classe [Button |api:Nette\Forms\Controls\Button]) que não tem função de envio. Pode ser usado para alguma outra função, por exemplo, chamar uma função JavaScript ao clicar. ```php -$form->addButton('raise', 'Raise salary') +$form->addButton('raise', 'Aumentar salário') ->setHtmlAttribute('onclick', 'raiseSalary()'); ``` -addImageButton(string|int $name, string $src=null, string $alt=null): ImageButton .[method] -=========================================================================================== +addImageButton(string|int $name, ?string $src=null, ?string $alt=null): ImageButton .[method] +============================================================================================= -Adiciona botão submeter em forma de uma imagem (classe [ImageButton |api:Nette\Forms\Controls\ImageButton]). +Adiciona um botão de envio na forma de uma imagem (classe [ImageButton |api:Nette\Forms\Controls\ImageButton]). ```php $form->addImageButton('submit', '/path/to/image'); ``` -Ao usar vários botões de envio, você pode descobrir qual deles foi clicado com `$form['submit']->isSubmittedBy()`. +Ao usar vários botões de envio, é possível descobrir qual foi clicado usando `$form['submit']->isSubmittedBy()`. addContainer(string|int $name): Container .[method] =================================================== -Adiciona um subforma ( [Container |api:Nette\Forms\Container] classe), ou um recipiente, que pode ser tratado da mesma forma que um formulário. Isso significa que você pode usar métodos como `setDefaults()` ou `getValues()`. +Adiciona um subformulário (classe [Container|api:Nette\Forms\Container]), ou seja, um contêiner, ao qual é possível adicionar outros elementos da mesma forma que os adicionamos ao formulário. Os métodos `setDefaults()` ou `getValues()` também funcionam. ```php $sub1 = $form->addContainer('first'); -$sub1->addText('name', 'Your name:'); +$sub1->addText('name', 'Seu nome:'); $sub1->addEmail('email', 'Email:'); $sub2 = $form->addContainer('second'); -$sub2->addText('name', 'Your name:'); +$sub2->addText('name', 'Seu nome:'); $sub2->addEmail('email', 'Email:'); ``` -Os dados enviados são então devolvidos como uma estrutura multidimensional: +Os dados enviados retornam como uma estrutura multidimensional: ```php [ @@ -339,110 +434,112 @@ Os dados enviados são então devolvidos como uma estrutura multidimensional: ``` -Visão geral das configurações .[#toc-overview-of-settings] -========================================================== +Visão geral das configurações +============================= -Para todos os elementos, podemos chamar os seguintes métodos (ver [documentação API |https://api.nette.org/forms/master/Nette/Forms/Controls.html] para uma visão geral completa): +Para todos os elementos, podemos chamar os seguintes métodos (visão geral completa na [documentação da API|https://api.nette.org/forms/master/Nette/Forms/Controls.html]): .[table-form-methods language-php] -| `setDefaultValue($value)` | define o valor padrão -| `getValue()` | obter valor atual -| `setOmitted()` | [valores omitidos |#omitted values] -| `setDisabled()` | [desabilitando entradas |#disabling inputs] +| `setDefaultValue($value)` | define o valor padrão +| `getValue()` | obtém o valor atual +| `setOmitted()` | [#Omissão de valor] +| `setDisabled()` | [#Desativação de elementos] Renderização: .[table-form-methods language-php] -| `setCaption($caption)`| altere o título do item -| `setTranslator($translator)` | define o [tradutor |rendering#translating] -| `setHtmlAttribute($name, $value)` | define o [atributo HTML |rendering#HTML attributes] do elemento -| `setHtmlId($id)` | define o atributo HTML `id` -| `setHtmlType($type)` | define o atributo HTML `type` -| `setHtmlName($name)`| define o atributo HTML `name` -| `setOption($key, $value)` | define [os dados de renderização |rendering#Options] +| `setCaption($caption)` | altera o rótulo do elemento +| `setTranslator($translator)` | define o [tradutor |rendering#Tradução] +| `setHtmlAttribute($name, $value)` | define o [atributo HTML |rendering#Atributos HTML] do elemento +| `setHtmlId($id)` | define o atributo HTML `id` +| `setHtmlType($type)` | define o atributo HTML `type` +| `setHtmlName($name)` | define o atributo HTML `name` +| `setOption($key, $value)` | [configurações para renderização |rendering#Options] Validação: .[table-form-methods language-php] -| `setRequired()` | [campo obrigatório |validation] -| `addRule()` | estabelecer [regra de validação |validation#Rules] -| `addCondition()`, `addConditionOn()` | estabelecer [condição de validação |validation#Conditions] -| `addError($message)`| [passando mensagem de erro |validation#processing-errors] +| `setRequired()` | [elemento obrigatório |validation] +| `addRule()` | define a [regra de validação |validation#Regras] +| `addCondition()`, `addConditionOn()` | define a [condição de validação |validation#Condições] +| `addError($message)` | [passagem de mensagem de erro |validation#Erros durante o processamento] -Os seguintes métodos podem ser chamados para o `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` itens: +Para os elementos `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`, podem ser chamados os seguintes métodos: .[table-form-methods language-php] -| `setNullable()`| define se getValue() retorna `null` em vez de string vazia -| `setEmptyValue($value)` | define o valor especial que é tratado como cadeia vazia -| `setMaxLength($length)`| define o número máximo de caracteres permitidos -| `addFilter($filter)`| [modificando os valores de entrada |validation#Modifying Input Values] +| `setNullable()` | define se getValue() retorna `null` em vez de string vazia +| `setEmptyValue($value)` | define um valor especial que é considerado uma string vazia +| `setMaxLength($length)` | define o número máximo de caracteres permitidos +| `addFilter($filter)` | [modificação da entrada |validation#Modificação da entrada] -Valores omitidos .[#toc-omitted-values] ---------------------------------------- +Omissão de valor +================ -Se você não estiver interessado no valor inserido pelo usuário, podemos usar `setOmitted()` para omiti-lo do resultado fornecido pelo método `$form->getValues​()` ou passado para os manipuladores. Isto é adequado para várias senhas de verificação, campos antispam, etc. +Se o valor preenchido pelo usuário não nos interessa, podemos omiti-lo do resultado do método `$form->getValues()` ou dos dados passados para os handlers usando `setOmitted()`. Isso é útil para várias senhas de verificação, elementos antispam, etc. ```php -$form->addPassword('passwordVerify', 'Password again:') - ->setRequired('Fill your password again to check for typo') - ->addRule($form::Equal, 'Password mismatch', $form['password']) +$form->addPassword('passwordVerify', 'Senha para verificação:') + ->setRequired('Por favor, digite a senha novamente para verificação') + ->addRule($form::Equal, 'As senhas não coincidem', $form['password']) ->setOmitted(); ``` -Desativação de entradas .[#toc-disabling-inputs] ------------------------------------------------- +Desativação de elementos +======================== -A fim de desativar uma entrada, você pode ligar para `setDisabled()`. Tal campo não pode ser editado pelo usuário. +Elementos podem ser desativados usando `setDisabled()`. Tal elemento não pode ser editado pelo usuário. ```php -$form->addText('username', 'User name:') +$form->addText('username', 'Nome de usuário:') ->setDisabled(); ``` -Note que o navegador não envia os campos desativados para o servidor, portanto você não os encontrará nem mesmo nos dados devolvidos pela função `$form->getValues()`. +Elementos desativados não são enviados pelo navegador para o servidor, portanto, você não os encontrará nos dados retornados pela função `$form->getValues()`. No entanto, se você definir `setOmitted(false)`, o Nette incluirá seu valor padrão nesses dados. -Se você estiver definindo um valor padrão para um campo, você deve fazê-lo somente após desativá-lo: +Ao chamar `setDisabled()`, por razões de segurança, **o valor do elemento é apagado**. Se você estiver definindo um valor padrão, é necessário fazê-lo após desativá-lo: ```php -$form->addText('username', 'User name:') +$form->addText('username', 'Nome de usuário:') ->setDisabled() ->setDefaultValue($userName); ``` +Uma alternativa aos elementos desativados são elementos com o atributo HTML `readonly`, que o navegador envia para o servidor. Embora o elemento seja apenas para leitura, é **importante notar** que seu valor ainda pode ser modificado ou falsificado por um invasor. + -Controles personalizados .[#toc-custom-controls] -================================================ +Elementos personalizados +======================== -Além de uma ampla gama de controles embutidos no formulário, você pode adicionar controles personalizados ao formulário da seguinte forma: +Além da ampla gama de elementos de formulário embutidos, você pode adicionar elementos personalizados ao formulário desta forma: ```php -$form->addComponent(new DateInput('Date:'), 'date'); -// sintaxe alternativa: $form['date'] = novo DateInput('Date:'); +$form->addComponent(new DateInput('Data:'), 'date'); +// sintaxe alternativa: $form['date'] = new DateInput('Data:'); ``` .[note] -A forma é um descendente da classe [Container | component-model:#Container] e os elementos são descendentes de [Componente | component-model:#Component]. +O formulário é um descendente da classe [Container |component-model:#Container] e os elementos individuais são descendentes de [Component |component-model:#Component]. -Há uma maneira de definir novos métodos de formulário para adicionar elementos personalizados (por exemplo, `$form->addZip()`). Estes são os chamados métodos de extensão. O lado negativo é que as dicas de código nos editores não funcionarão para eles. +Existe uma maneira de definir novos métodos de formulário para adicionar elementos personalizados (por exemplo, `$form->addZip()`). São os chamados métodos de extensão. A desvantagem é que a sugestão nos editores não funcionará para eles. ```php use Nette\Forms\Container; -// adiciona método addZip(string $name, string $label = null) -Container::extensionMethod('addZip', function (Container $form, string $name, string $label = null) { +// adicionamos o método addZip(string $name, ?string $label = null) +Container::extensionMethod('addZip', function (Container $form, string $name, ?string $label = null) { return $form->addText($name, $label) - ->addRule($form::Pattern, 'At least 5 numbers', '[0-9]{5}'); + ->addRule($form::Pattern, 'Pelo menos 5 números', '[0-9]{5}'); }); // uso -$form->addZip('zip', 'ZIP code:'); +$form->addZip('zip', 'CEP:'); ``` -Campos de baixo nível .[#toc-low-level-fields] -============================================== +Elementos de baixo nível +======================== -Para adicionar um item ao formulário, você não precisa ligar para `$form->addXyz()`. Os itens do formulário podem ser introduzidos exclusivamente em modelos. Isto é útil se você, por exemplo, precisar gerar itens dinâmicos: +Também é possível usar elementos que escrevemos apenas no template e não os adicionamos ao formulário com algum dos métodos `$form->addXyz()`. Por exemplo, ao listar registros do banco de dados sem saber antecipadamente quantos serão e quais serão seus IDs, e queremos exibir uma caixa de seleção ou botão de opção para cada linha, basta codificá-lo no template: ```latte {foreach $items as $item} @@ -450,13 +547,13 @@ Para adicionar um item ao formulário, você não precisa ligar para `$form->add {/foreach} ``` -Após a apresentação, você pode recuperar os valores: +E após o envio, obtemos o valor: ```php $data = $form->getHttpData($form::DataText, 'sel[]'); $data = $form->getHttpData($form::DataText | $form::DataKeys, 'sel[]'); ``` -No primeiro parâmetro, você especifica o tipo de elemento (`DataFile` para `type=file`, `DataLine` para entradas de uma linha como `text`, `password` ou `email` e `DataText` para o resto). O segundo parâmetro corresponde ao atributo HTML `name`. Se você precisar preservar chaves, você pode combinar o primeiro parâmetro com `DataKeys`. Isto é útil para `select`, `radioList` ou `checkboxList`. +onde o primeiro parâmetro é o tipo do elemento (`DataFile` para `type=file`, `DataLine` para entradas de linha única como `text`, `password`, `email`, etc. e `DataText` para todos os outros) e o segundo parâmetro `sel[]` corresponde ao atributo HTML name. O tipo do elemento pode ser combinado com o valor `DataKeys`, que preserva as chaves dos elementos. Isso é especialmente útil para `select`, `radioList` e `checkboxList`. -O `getHttpData()` retorna entrada higienizada. Neste caso, será sempre um conjunto de cordas UTF-8 válidas, não importando o que o atacante tenha enviado pelo formulário. É uma alternativa ao trabalho com `$_POST` ou `$_GET` diretamente, se você quiser receber dados seguros. +O essencial é que `getHttpData()` retorna um valor sanitizado, neste caso, será sempre um array de strings UTF-8 válidas, independentemente do que um invasor tente enviar ao servidor. É análogo ao trabalho direto com `$_POST` ou `$_GET`, mas com a diferença essencial de que sempre retorna dados limpos, como você está acostumado com os elementos padrão dos formulários Nette. diff --git a/forms/pt/in-presenter.texy b/forms/pt/in-presenter.texy index 81cf7df2ae..955d4dc3f3 100644 --- a/forms/pt/in-presenter.texy +++ b/forms/pt/in-presenter.texy @@ -1,36 +1,36 @@ -Formulários em Apresentadores -***************************** +Formulários em Presenters +************************* .[perex] -Os Formulários Nette facilitam drasticamente a criação e o processamento de formulários web. Neste capítulo, você aprenderá como utilizar os formulários dentro dos apresentadores. +Nette Forms facilitam enormemente a criação e processamento de formulários web. Neste capítulo, você aprenderá a usar formulários dentro de presenters. -Se você estiver interessado em utilizá-los completamente autônomos sem o restante da estrutura, existe um guia para [formulários autônomos |standalone]. +Se você está interessado em como usá-los de forma totalmente independente do resto do framework, o guia para [uso independente|standalone] é para você. -Primeiro Formulário .[#toc-first-form] -====================================== +Primeiro formulário +=================== -Tentaremos escrever um simples formulário de registro. Seu código terá este aspecto: +Vamos tentar escrever um formulário de registro simples. Seu código será o seguinte: ```php use Nette\Application\UI\Form; $form = new Form; -$form->addText('name', 'Name:'); -$form->addPassword('password', 'Password:'); -$form->addSubmit('send', 'Sign up'); +$form->addText('name', 'Nome:'); +$form->addPassword('password', 'Senha:'); +$form->addSubmit('send', 'Registrar'); $form->onSuccess[] = [$this, 'formSucceeded']; ``` -e no navegador o resultado deve ser parecido com este: +e no navegador será exibido assim: -[* form-en.webp *] +[* form-cs.webp *] -A forma no apresentador é um objeto da classe `Nette\Application\UI\Form`, seu predecessor `Nette\Forms\Form` é destinado ao uso autônomo. Acrescentamos os campos nome, senha e botão de envio. Finalmente, a linha com `$form->onSuccess` diz que após a submissão e validação bem sucedida, o método `$this->formSucceeded()` deve ser chamado. +O formulário no presenter é um objeto da classe `Nette\Application\UI\Form`, seu predecessor `Nette\Forms\Form` é destinado ao uso independente. Adicionamos a ele os chamados elementos nome, senha e botão de envio. E, finalmente, a linha com `$form->onSuccess` diz que após o envio e validação bem-sucedida, o método `$this->formSucceeded()` deve ser chamado. -Do ponto de vista do apresentador, a forma é um componente comum. Portanto, ele é tratado como um componente e incorporado ao apresentador usando [o método de fábrica |application:components#Factory Methods]. Será parecido com isto: +Do ponto de vista do presenter, o formulário é um componente comum. Portanto, ele é tratado como um componente e incorporado ao presenter usando [métodos de fábrica |application:components#Métodos de fábrica]. Ficará assim: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} use Nette; use Nette\Application\UI\Form; @@ -39,103 +39,100 @@ class HomePresenter extends Nette\Application\UI\Presenter protected function createComponentRegistrationForm(): Form { $form = new Form; - $form->addText('name', 'Name:'); - $form->addPassword('password', 'Password:'); - $form->addSubmit('send', 'Sign up'); + $form->addText('name', 'Nome:'); + $form->addPassword('password', 'Senha:'); + $form->addSubmit('send', 'Registrar'); $form->onSuccess[] = [$this, 'formSucceeded']; return $form; } public function formSucceeded(Form $form, $data): void { - // aqui processaremos os dados enviados pelo formulário - // $data->name contém nome + // aqui processamos os dados enviados pelo formulário + // $data->name contém o nome // $data->password contém a senha - $this->flashMessage('Você se inscreveu com sucesso'); + $this->flashMessage('Você foi registrado com sucesso.'); $this->redirect('Home:'); } } ``` -E a renderização em modelo é feita usando a tag `{control}`: +E no template, renderizamos o formulário com a tag `{control}`: -```latte .{file:app/Presenters/templates/Home/default.latte} -<h1>Registration</h1> +```latte .{file:app/Presentation/Home/default.latte} +<h1>Registro</h1> {control registrationForm} ``` -E isso é tudo :-) Temos uma forma funcional e perfeitamente [segura |#Vulnerability Protection]. +E isso é basicamente tudo :-) Temos um formulário funcional e perfeitamente [seguro |#Proteção contra vulnerabilidades]. -Agora você provavelmente está pensando que foi muito rápido, perguntando-se como é possível que o método `formSucceeded()` seja chamado e quais são os parâmetros que ele obtém. Claro, você está certo, isto merece uma explicação. +E agora você provavelmente está pensando que foi muito rápido, imaginando como é possível que o método `formSucceeded()` seja chamado e quais são os parâmetros que ele recebe. Certamente, você está certo, isso merece uma explicação. -A Nette vem com um mecanismo legal, que chamamos de [estilo hollywoodiano |application:components#Hollywood style]. Em vez de ter que perguntar constantemente se algo aconteceu ("o formulário foi submetido?", "foi submetido validamente?" ou "não foi forjado?"), você diz à estrutura "quando o formulário for validamente preenchido, chame este método" e deixe mais trabalho sobre ele. Se você programar em JavaScript, você está familiarizado com este estilo de programação. Você escreve funções que são chamadas quando um determinado [evento |nette:glossary#Events] ocorre. E a linguagem passa os argumentos apropriados para elas. +Nette introduz um mecanismo inovador que chamamos de [estilo Hollywood |application:components#Estilo Hollywood]. Em vez de você, como desenvolvedor, ter que perguntar constantemente se algo aconteceu ("o formulário foi enviado?", "foi enviado validamente?" e "não foi falsificado?"), você diz ao framework "quando o formulário estiver validamente preenchido, chame este método" e deixa o trabalho restante para ele. Se você programa em JavaScript, conhece bem este estilo de programação. Você escreve funções que são chamadas quando um determinado [evento |nette:glossary#Eventos] ocorre. E a linguagem passa os argumentos apropriados para elas. -É assim que o código do apresentador acima é construído. O Array `$form->onSuccess` representa a lista de ligações de retorno do PHP que a Nette chamará quando o formulário for enviado e preenchido corretamente. -Dentro do [ciclo de vida do apresentador |application:presenters#Life Cycle of Presenter] é um chamado sinal, por isso são chamados depois do método `action*` e antes do método `render*`. -E passa para cada chamada de retorno o próprio formulário no primeiro parâmetro e os dados enviados como objeto [ArrayHash |utils:arrays#ArrayHash] no segundo. Você pode omitir o primeiro parâmetro se não precisar do objeto formulário. O segundo parâmetro pode ser ainda mais útil, mas sobre isso [mais tarde |#Mapping to Classes]. +É exatamente assim que o código do presenter acima é construído. O array `$form->onSuccess` representa uma lista de callbacks PHP que o Nette chama no momento em que o formulário é enviado e preenchido corretamente (ou seja, é válido). Dentro do [ciclo de vida do presenter |application:presenters#Ciclo de vida do presenter], isso é chamado de sinal, eles são chamados após o método `action*` e antes do método `render*`. E para cada callback, ele passa como primeiro parâmetro o próprio formulário e como segundo os dados enviados na forma de um objeto [ArrayHash |utils:arrays#ArrayHash] por padrão (ou uma classe/array mapeado). Você pode omitir o primeiro parâmetro se não precisar do objeto do formulário. E o segundo parâmetro pode ser mais inteligente, mas falaremos sobre isso [mais tarde |#Mapeamento para classes]. -O objeto `$data` contém as propriedades `name` e `password` com os dados inseridos pelo usuário. Normalmente, enviamos os dados diretamente para processamento posterior, que podem ser, por exemplo, inseridos no banco de dados. Entretanto, pode ocorrer um erro durante o processamento, por exemplo, o nome de usuário já está tomado. Neste caso, passamos o erro de volta ao formulário usando `addError()` e deixamos que ele seja redesenhado, com uma mensagem de erro: +O objeto `$data` contém as chaves `name` e `password` com os dados que o usuário preencheu. Geralmente, enviamos os dados diretamente para processamento adicional, que pode ser, por exemplo, inserção no banco de dados. Durante o processamento, no entanto, pode ocorrer um erro, por exemplo, o nome de usuário já está em uso. Nesse caso, passamos o erro de volta para o formulário usando `addError()` e o deixamos renderizar novamente, com a mensagem de erro. ```php -$form->addError('Sorry, username is already in use.'); +$form->addError('Desculpe, o nome de usuário já está em uso.'); ``` -Além de `onSuccess`, há também `onSubmit`: as chamadas de retorno são sempre feitas após o envio do formulário, mesmo que este não seja preenchido corretamente. E finalmente `onError`: as chamadas de retorno são chamadas somente se a submissão não for válida. Elas são mesmo chamadas se invalidarmos o formulário em `onSuccess` ou `onSubmit`, usando `addError()`. +Além de `onSuccess`, existe também `onSubmit`: os callbacks são chamados sempre após o envio do formulário, mesmo que não esteja preenchido corretamente. E também `onError`: os callbacks são chamados apenas se o envio não for válido. Eles são chamados mesmo se invalidarmos o formulário em `onSuccess` ou `onSubmit` usando `addError()`. -Após o processamento do formulário, redirecionaremos para a página seguinte. Isto evita que o formulário seja reapresentado involuntariamente clicando no botão *refresh*, *back*, ou movendo o histórico do navegador. +Após processar o formulário, redirecionamos para a próxima página. Isso evita o reenvio indesejado do formulário pelo botão *atualizar*, *voltar* ou movimento no histórico do navegador. -Tente adicionar mais [controles de formulário |controls]. +Tente adicionar outros [elementos de formulário|controls]. -Acesso aos controles .[#toc-access-to-controls] -=============================================== +Acesso aos elementos +==================== -O formulário é um componente do apresentador, em nosso caso chamado `registrationForm` (após o nome do método de fábrica `createComponentRegistrationForm`), portanto, em qualquer lugar do apresentador você pode chegar ao formulário usando o mesmo: +O formulário é um componente do presenter, em nosso caso chamado `registrationForm` (pelo nome do método de fábrica `createComponentRegistrationForm`), então em qualquer lugar no presenter você pode acessar o formulário usando: ```php $form = $this->getComponent('registrationForm'); // sintaxe alternativa: $form = $this['registrationForm']; ``` -Também os controles de formulários individuais são componentes, para que você possa acessá-los da mesma forma: +Os elementos individuais do formulário também são componentes, então você pode acessá-los da mesma maneira: ```php $input = $form->getComponent('name'); // ou $input = $form['name']; $button = $form->getComponent('send'); // ou $button = $form['send']; ``` -Os controles são removidos com o uso de controles não ajustados: +Os elementos são removidos usando unset: ```php unset($form['name']); ``` -Regras de validação .[#toc-validation-rules] -============================================ +Regras de validação +=================== -A palavra *valido* foi usada várias vezes, mas o formulário ainda não tem regras de validação. Vamos corrigi-lo. +A palavra *válido* foi mencionada, mas o formulário ainda não tem regras de validação. Vamos corrigir isso. -O nome será obrigatório, portanto o marcaremos com o método `setRequired()`, cujo argumento é o texto da mensagem de erro que será exibida se o usuário não a preencher. Se não for apresentado nenhum argumento, será utilizada a mensagem de erro padrão. +O nome será obrigatório, então o marcamos com o método `setRequired()`, cujo argumento é o texto da mensagem de erro que será exibida se o usuário não preencher o nome. Se o argumento não for fornecido, a mensagem de erro padrão será usada. ```php -$form->addText('name', 'Name:') - ->setRequired('Please fill your name.'); +$form->addText('name', 'Nome:') + ->setRequired('Por favor, insira o nome'); ``` -Tente enviar o formulário sem o nome preenchido e você verá que uma mensagem de erro é exibida e o navegador ou servidor irá rejeitá-lo até que você o preencha. +Tente enviar o formulário sem preencher o nome e você verá que uma mensagem de erro será exibida e o navegador ou servidor o rejeitará até que você preencha o campo. -Ao mesmo tempo, você não poderá enganar o sistema digitando apenas espaços na entrada, por exemplo. De jeito nenhum. A Nette apara automaticamente os espaços em branco à esquerda e à direita. Experimente. É algo que você deve sempre fazer com cada entrada de linha, mas muitas vezes é esquecido. A Nette o faz automaticamente. (Você pode tentar enganar os formulários e enviar uma cadeia de várias linhas como o nome. Mesmo aqui, Nette não será enganado e as quebras de linha mudarão para espaços). +Ao mesmo tempo, você não pode enganar o sistema escrevendo apenas espaços no campo. De jeito nenhum. O Nette remove automaticamente os espaços à esquerda e à direita. Experimente. É algo que você deve fazer com cada entrada de linha única, mas muitas vezes é esquecido. O Nette faz isso automaticamente. (Você pode tentar enganar o formulário e enviar uma string multilinha como nome. Mesmo aqui, o Nette não se deixa enganar e transforma as quebras de linha em espaços.) -O formulário é sempre validado no lado do servidor, mas a validação JavaScript também é gerada, o que é rápido e o usuário sabe do erro imediatamente, sem ter que enviar o formulário para o servidor. Isto é tratado pelo script `netteForms.js`. -Insira-o no modelo de layout: +O formulário é sempre validado no lado do servidor, mas também é gerada uma validação JavaScript, que ocorre instantaneamente e o usuário é informado sobre o erro imediatamente, sem a necessidade de enviar o formulário ao servidor. Isso é feito pelo script `netteForms.js`. Insira-o no template de layout: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Se você olhar no código fonte da página com formulário, você pode notar que Nette insere os campos necessários em elementos com uma classe CSS `required`. Tente adicionar o seguinte estilo ao modelo, e a etiqueta "Name" (Nome) será vermelha. Elegantemente, marcamos os campos requeridos para os usuários: +Se você olhar o código-fonte da página com o formulário, poderá notar que o Nette insere os elementos obrigatórios em elementos com a classe CSS `required`. Tente adicionar a seguinte folha de estilo ao template e o rótulo "Nome" ficará vermelho. Elegantemente, marcamos os elementos obrigatórios para os usuários: ```latte <style> @@ -143,130 +140,132 @@ Se você olhar no código fonte da página com formulário, você pode notar que </style> ``` -Regras de validação adicionais serão adicionadas pelo método `addRule()`. O primeiro parâmetro é a regra, o segundo é novamente o texto da mensagem de erro e o argumento da regra de validação opcional pode seguir. O que isso significa? +Outras regras de validação são adicionadas com o método `addRule()`. O primeiro parâmetro é a regra, o segundo é novamente o texto da mensagem de erro e pode ainda seguir um argumento da regra de validação. O que isso significa? -O formulário terá outra entrada opcional *idade* com a condição, que deve ser um número (`addInteger()`) e em certos limites (`$form::Range`). E aqui usaremos o terceiro argumento de `addRule()`, a própria faixa: +Vamos estender o formulário com um novo campo opcional "idade", que deve ser um número inteiro (`addInteger()`) e, além disso, dentro de um intervalo permitido (`Form::Range`). E aqui usaremos o terceiro parâmetro do método `addRule()`, pelo qual passamos o intervalo necessário ao validador como um par `[de, até]`: ```php -$form->addInteger('age', 'Age:') - ->addRule($form::Range, 'You must be older 18 years and be under 120.', [18, 120]); +$form->addInteger('age', 'Idade:') + ->addRule($form::Range, 'A idade deve ser entre 18 e 120', [18, 120]); ``` .[tip] -Se o usuário não preencher o campo, as regras de validação não serão verificadas, pois o campo é opcional. +Se o usuário não preencher o campo, as regras de validação não serão verificadas, pois o elemento é opcional. -Obviamente, há espaço para uma pequena refatoração. Na mensagem de erro e no terceiro parâmetro, os números são listados em duplicata, o que não é o ideal. Se estivéssemos criando um [formulário multilíngüe |rendering#translating] e a mensagem contendo números tivesse que ser traduzida para vários idiomas, seria mais difícil mudar os valores. Por este motivo, podem ser utilizados caracteres substitutos `%d`: +Aqui surge espaço para uma pequena refatoração. Na mensagem de erro e no terceiro parâmetro, os números são mencionados duplicadamente, o que não é ideal. Se estivéssemos criando [formulários multilíngues |rendering#Tradução] e a mensagem contendo números fosse traduzida para vários idiomas, dificultaria uma possível alteração dos valores. Por esse motivo, é possível usar os marcadores `%d` e o Nette completará os valores: ```php - ->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]); + ->addRule($form::Range, 'A idade deve ser entre %d e %d anos', [18, 120]); ``` -Vamos voltar ao campo *password*, torná-lo *requerido*, e verificar o comprimento mínimo da senha (`$form::MinLength`), novamente usando os caracteres substitutos na mensagem: +Voltemos ao elemento `password`, que também tornaremos obrigatório e verificaremos o comprimento mínimo da senha (`$form::MinLength`), novamente usando o marcador: ```php -$form->addPassword('password', 'Password:') - ->setRequired('Pick a password') - ->addRule($form::MinLength, 'Your password has to be at least %d long', 8); +$form->addPassword('password', 'Senha:') + ->setRequired('Escolha uma senha') + ->addRule($form::MinLength, 'A senha deve ter pelo menos %d caracteres', 8); ``` -Adicionaremos um campo `passwordVerify` ao formulário, onde o usuário digita a senha novamente, para verificação. Usando regras de validação, verificamos se as duas senhas são as mesmas (`$form::Equal`). E como argumento, fazemos uma referência à primeira senha usando [colchetes |#Access to Controls]: +Adicionaremos ao formulário ainda o campo `passwordVerify`, onde o usuário digita a senha novamente, para verificação. Usando regras de validação, verificamos se ambas as senhas são iguais (`$form::Equal`). E como parâmetro, damos uma referência à primeira senha usando [colchetes |#Acesso aos elementos]: ```php -$form->addPassword('passwordVerify', 'Password again:') - ->setRequired('Fill your password again to check for typo') - ->addRule($form::Equal, 'Password mismatch', $form['password']) +$form->addPassword('passwordVerify', 'Senha para verificação:') + ->setRequired('Por favor, digite a senha novamente para verificação') + ->addRule($form::Equal, 'As senhas não coincidem', $form['password']) ->setOmitted(); ``` -Usando `setOmitted()`, marcamos um elemento cujo valor realmente não nos interessa e que existe apenas para validação. Seu valor não é passado para `$data`. +Usando `setOmitted()`, marcamos o elemento cujo valor realmente não nos importa e que existe apenas para fins de validação. O valor não é passado para `$data`. -Temos um formulário totalmente funcional com validação em PHP e JavaScript. As capacidades de validação da Nette são muito mais amplas, você pode criar condições, exibir e ocultar partes de uma página de acordo com elas, etc. Você pode descobrir tudo no capítulo sobre [validação de formulários |validation]. +Com isso, temos um formulário totalmente funcional com validação em PHP e JavaScript. As capacidades de validação do Nette são muito mais amplas, é possível criar condições, deixar partes da página serem exibidas e ocultadas com base nelas, etc. Tudo será explicado no capítulo sobre [validação de formulários|validation]. -Valores por default .[#toc-default-values] -========================================== +Valores padrão +============== -Muitas vezes definimos valores padrão para controles de formulários: +Normalmente, definimos valores padrão para os elementos do formulário: ```php -$form->addEmail('email', 'Email') +$form->addEmail('email', 'E-mail') ->setDefaultValue($lastUsedEmail); ``` -Muitas vezes é útil definir valores padrão para todos os controles ao mesmo tempo. Por exemplo, quando o formulário é usado para editar registros. Nós lemos o registro do banco de dados e o definimos como valores padrão: +Muitas vezes, é útil definir valores padrão para todos os elementos de uma vez. Por exemplo, quando o formulário serve para editar registros. Lemos o registro do banco de dados e definimos os valores padrão: ```php //$row = ['name' => 'John', 'age' => '33', /* ... */]; $form->setDefaults($row); ``` -Ligue para `setDefaults()` após definir os controles. +Chame `setDefaults()` após definir os elementos. -Renderização do formulário .[#toc-rendering-the-form] -===================================================== +Renderização do formulário +========================== -Por padrão, o formulário é apresentado como uma tabela. Os controles individuais seguem as diretrizes básicas de acessibilidade da web. Todas as etiquetas são geradas como `<label>` e estão associados às suas entradas, clicando na etiqueta move o cursor sobre a entrada. +Por padrão, o formulário é renderizado como uma tabela. Os elementos individuais cumprem a regra básica de acessibilidade - todos os rótulos são escritos como `<label>` e vinculados ao elemento de formulário correspondente. Ao clicar no rótulo, o cursor aparece automaticamente no campo do formulário. -Podemos definir quaisquer atributos HTML para cada elemento. Por exemplo, acrescente um espaço reservado: +Podemos definir quaisquer atributos HTML para cada elemento. Por exemplo, adicionar um placeholder: ```php -$form->addInteger('age', 'Age:') - ->setHtmlAttribute('placeholder', 'Please fill in the age'); +$form->addInteger('age', 'Idade:') + ->setHtmlAttribute('placeholder', 'Por favor, preencha a idade'); ``` -Há realmente muitas maneiras de renderizar uma forma, por isso é um [capítulo |rendering] dedicado [à renderização |rendering]. +Existem realmente muitas maneiras de renderizar um formulário, então há um [capítulo separado sobre renderização|rendering] dedicado a isso. -Mapeamento para as classes .[#toc-mapping-to-classes] -===================================================== +Mapeamento para classes .{mapeamento-para-classes} +================================================== -Voltemos ao método `formSucceeded()`, que no segundo parâmetro `$data` recebe os dados enviados como um objeto `ArrayHash`. Como esta é uma classe genérica, algo como `stdClass`, nos faltará alguma conveniência ao trabalhar com ela, como a complementação de código para propriedades em editores ou análise de código estático. Isto poderia ser resolvido tendo uma classe específica para cada formulário, cujas propriedades representam os controles individuais. Por exemplo, a classe +Voltemos ao método `formSucceeded()`, que no segundo parâmetro `$data` recebe os dados enviados como um objeto `ArrayHash`. Como é uma classe genérica, algo como `stdClass`, sentiremos falta de certo conforto ao trabalhar com ela, como sugestão de propriedades nos editores ou análise estática de código. Isso poderia ser resolvido tendo uma classe específica para cada formulário, cujas propriedades representam os elementos individuais. Por exemplo: ```php class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -A partir do PHP 8.0, você pode usar esta elegante notação que usa um construtor: +Alternativamente, você pode usar o construtor: ```php class RegistrationFormData { public function __construct( public string $name, - public int $age, + public ?int $age, public string $password, ) { } } ``` -Como dizer à Nette para retornar dados como objetos desta classe? Mais fácil do que você pensa. Tudo que você precisa fazer é especificar a classe como o tipo do parâmetro `$data` no manipulador: +As propriedades da classe de dados também podem ser enumerações e serão mapeadas automaticamente. .{data-version:3.2.4} + +Como dizer ao Nette para nos retornar os dados como objetos desta classe? Mais fácil do que você pensa. Basta apenas indicar a classe como o tipo do parâmetro `$data` no método manipulador: ```php public function formSucceeded(Form $form, RegistrationFormData $data): void { - // $name é instância de RegistrationFormData + // $name é uma instância de RegistrationFormData $name = $data->name; // ... } ``` -Você também pode especificar `array` como o tipo e então ele passará os dados como uma matriz. +Como tipo, também pode ser indicado `array` e então os dados são passados como um array. -De maneira semelhante, você pode usar o método `getValues()`, que passamos como nome de classe ou objeto para hidratar como parâmetro: +Da mesma forma, pode-se usar o método `getValues()`, ao qual o nome da classe ou o objeto a ser hidratado é passado como parâmetro: ```php $data = $form->getValues(RegistrationFormData::class); $name = $data->name; ``` -Se as formas consistem em uma estrutura de vários níveis composta de recipientes, crie uma classe separada para cada um deles: +Se os formulários formarem uma estrutura multinível composta por contêineres, crie uma classe separada para cada um: ```php $form = new Form; @@ -283,32 +282,34 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` -O mapeamento então sabe pelo tipo de propriedade `$person` que deve mapear o container para a classe `PersonFormData`. Se a propriedade contiver um conjunto de containers, forneça o tipo `array` e passe a classe a ser mapeada diretamente para o container: +O mapeamento então, a partir do tipo da propriedade `$person`, reconhece que deve mapear o contêiner para a classe `PersonFormData`. Se a propriedade contivesse um array de contêineres, indique o tipo `array` e passe a classe para mapeamento diretamente para o contêiner: ```php $person->setMappedType(PersonFormData::class); ``` +Você pode ter o design da classe de dados do formulário gerado usando o método `Nette\Forms\Blueprint::dataClass($form)`, que o imprime na página do navegador. O código então só precisa ser clicado, marcado e copiado para o projeto. .{data-version:3.1.15} + -Botões de submissão múltipla .[#toc-multiple-submit-buttons] -============================================================ +Vários botões +============= -Se o formulário tiver mais de um botão, geralmente precisamos distinguir qual deles foi pressionado. Podemos criar uma função própria para cada botão. Configure-o como um manipulador para o [evento |nette:glossary#Events] `onClick`: +Se o formulário tiver mais de um botão, geralmente precisamos distinguir qual deles foi pressionado. Podemos criar nossa própria função manipuladora para cada botão. Definimo-la como um handler para o [evento |nette:glossary#Eventos] `onClick`: ```php -$form->addSubmit('save', 'Save') +$form->addSubmit('save', 'Salvar') ->onClick[] = [$this, 'saveButtonPressed']; -$form->addSubmit('delete', 'Delete') +$form->addSubmit('delete', 'Excluir') ->onClick[] = [$this, 'deleteButtonPressed']; ``` -Estes manipuladores também são chamados apenas no formulário do caso é válido, como no caso do evento `onSuccess`. A diferença é que o primeiro parâmetro pode ser o objeto do botão submeter em vez do formulário, dependendo do tipo especificado: +Esses handlers são chamados apenas no caso de um formulário validamente preenchido, assim como no caso do evento `onSuccess`. A diferença é que, como primeiro parâmetro, em vez do formulário, pode ser passado o botão de envio, dependendo do tipo que você indicar: ```php public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) @@ -318,62 +319,61 @@ public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) } ``` -Quando um formulário é apresentado com a chave <kbd>Enter</kbd>, ele é tratado como se tivesse sido apresentado com o primeiro botão. +Quando o formulário é enviado com a tecla <kbd>Enter</kbd>, considera-se como se tivesse sido enviado pelo primeiro botão. -Evento na Âncora .[#toc-event-onanchor] -======================================= +Evento onAnchor +=============== -Quando você constrói um formulário em um método de fábrica (como `createComponentRegistrationForm`), ele ainda não sabe se foi submetido ou se os dados com os quais foi submetido. Mas há casos em que precisamos saber os valores apresentados, talvez dependa deles, como será o formulário, ou são usados para caixas de seleção dependentes, etc. +Quando, no método de fábrica (como `createComponentRegistrationForm`), montamos o formulário, ele ainda não sabe se foi enviado, nem com quais dados. Mas há casos em que precisamos conhecer os valores enviados, talvez a forma adicional do formulário dependa deles, ou precisemos deles para selectboxes dependentes, etc. -Portanto, você pode ter o código que constrói o formulário chamado quando ele está ancorado, ou seja, já está ligado ao apresentador e conhece seus dados apresentados. Colocaremos tal código na matriz `$onAnchor`: +Portanto, a parte do código que monta o formulário pode ser deixada para ser chamada apenas no momento em que ele está, por assim dizer, ancorado, ou seja, já está conectado ao presenter e conhece seus dados enviados. Tal código é passado para o array `$onAnchor`: ```php -$country = $form->addSelect('country', 'Country:', $this->model->getCountries()); -$city = $form->addSelect('city', 'City:'); +$country = $form->addSelect('country', 'País:', $this->model->getCountries()); +$city = $form->addSelect('city', 'Cidade:'); $form->onAnchor[] = function () use ($country, $city) { - // esta função será chamada quando o formulário souber os dados com os quais foi submetido - // para que você possa usar o método getValue() + // esta função será chamada quando o formulário souber se foi enviado e com quais dados + // portanto, é possível usar o método getValue() $val = $country->getValue(); $city->setItems($val ? $this->model->getCities($val) : []); }; ``` -Proteção contra vulnerabilidades .[#toc-vulnerability-protection] -================================================================= +Proteção contra vulnerabilidades .{proteção-contra-vulnerabilidades} +==================================================================== -Nette Framework coloca um grande esforço para ser seguro e como os formulários são a entrada mais comum dos usuários, os formulários Nette são tão bons quanto impenetráveis. Tudo é mantido de forma dinâmica e transparente, nada tem que ser ajustado manualmente. +O Nette Framework dá grande ênfase à segurança e, portanto, cuida meticulosamente da boa segurança dos formulários. Faz isso de forma totalmente transparente e não requer nenhuma configuração manual. -Além de proteger os formulários contra ataques direcionados a vulnerabilidades bem conhecidas como o [Cross-Site Scripting (XSS) |nette:glossary#cross-site-scripting-xss] e o [Cross-Site Request Forgery (CSRF) |nette:glossary#cross-site-request-forgery-csrf], ele faz muitas pequenas tarefas de segurança que você não precisa mais pensar. +Além de proteger os formulários contra ataques de [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] e [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF], ele realiza muitas pequenas proteções nas quais você não precisa mais pensar. -Por exemplo, ele filtra todos os caracteres de controle das entradas e verifica a validade da codificação UTF-8, para que os dados do formulário estejam sempre limpos. Para caixas de seleção e listas de rádio, ele verifica que os itens selecionados foram realmente dos oferecidos e que não houve falsificação. Já mencionamos que para entrada de texto de uma linha, ele remove caracteres de fim de linha que um atacante poderia enviar para lá. Para entradas de várias linhas, ele normaliza os caracteres de fim de linha. E assim por diante. +Por exemplo, ele filtra todos os caracteres de controle das entradas e verifica a validade da codificação UTF-8, para que os dados do formulário estejam sempre limpos. Para caixas de seleção e listas de rádio, ele verifica se os itens selecionados estavam realmente entre os oferecidos e não foram falsificados. Já mencionamos que, para entradas de texto de linha única, ele remove os caracteres de fim de linha que um invasor poderia ter enviado. Para entradas multilinha, ele normaliza os caracteres de fim de linha. E assim por diante. -Nette corrige as vulnerabilidades de segurança para você que a maioria dos programadores não tem idéia de que existem. +O Nette resolve para você riscos de segurança que muitos programadores nem sabem que existem. -O referido ataque do CSRF é que um agressor atrai a vítima para visitar uma página que silenciosamente executa um pedido no navegador da vítima ao servidor onde a vítima está atualmente logada, e o servidor acredita que o pedido foi feito pela vítima à sua vontade. Portanto, a Nette impede que o formulário seja submetido via POST a partir de outro domínio. Se por algum motivo você quiser desligar a proteção e permitir que o formulário seja submetido a partir de outro domínio, use: +O ataque CSRF mencionado consiste em um invasor atrair a vítima para uma página que, discretamente no navegador da vítima, executa uma requisição ao servidor no qual a vítima está logada, e o servidor acredita que a requisição foi feita pela vítima por vontade própria. Portanto, o Nette impede o envio de formulários POST de outro domínio. Se, por algum motivo, você quiser desativar a proteção e permitir o envio do formulário de outro domínio, use: ```php -$form->allowCrossOrigin(); // ATENÇÃO! Desligue a proteção! +$form->allowCrossOrigin(); // ATENÇÃO! Desativa a proteção! ``` -Esta proteção utiliza um cookie SameSite chamado `_nss`. A proteção do cookie SameSite pode não ser 100% confiável, por isso é uma boa idéia ativar a proteção simbólica: +Esta proteção utiliza um cookie SameSite chamado `_nss`. A proteção usando o cookie SameSite pode não ser 100% confiável, por isso é aconselhável ativar também a proteção por token: ```php $form->addProtection(); ``` -É fortemente recomendado aplicar esta proteção aos formulários em uma parte administrativa de seu pedido que altera dados sensíveis. A estrutura protege contra um ataque CSRF gerando e validando o token de autenticação que é armazenado em uma sessão (o argumento é a mensagem de erro mostrada se o token tiver expirado). É por isso que é necessário ter uma sessão iniciada antes de exibir o formulário. Na parte administrativa do site, a sessão normalmente já é iniciada, devido ao login do usuário. -Caso contrário, inicie a sessão com o método `Nette\Http\Session::start()`. +Recomendamos proteger assim os formulários na parte administrativa do site, que alteram dados sensíveis na aplicação. O framework se defende contra o ataque CSRF gerando e verificando um token de autorização, que é armazenado na sessão. Portanto, é necessário ter a sessão aberta antes de exibir o formulário. Na parte administrativa do site, a sessão geralmente já está iniciada devido ao login do usuário. Caso contrário, inicie a sessão com o método `Nette\Http\Session::start()`. -Usando um formulário em vários apresentadores .[#toc-using-one-form-in-multiple-presenters] -=========================================================================================== +Mesmo formulário em vários presenters +===================================== -Se você precisar usar um formulário em mais de um apresentador, recomendamos que você crie uma fábrica para isso, que depois você passa para o apresentador. Um local adequado para tal classe é, por exemplo, o diretório `app/Forms`. +Se você precisar usar o mesmo formulário em vários presenters, recomendamos criar uma fábrica para ele, que você então passará para o presenter. Um local adequado para tal classe é, por exemplo, o diretório `app/Forms`. -A classe da fábrica pode se parecer com esta: +A classe de fábrica pode parecer assim: ```php use Nette\Application\UI\Form; @@ -383,14 +383,14 @@ class SignInFormFactory public function create(): Form { $form = new Form; - $form->addText('name', 'Name:'); - $form->addSubmit('send', 'Log in'); + $form->addText('name', 'Nome:'); + $form->addSubmit('send', 'Entrar'); return $form; } } ``` -Pedimos à classe que produza o formulário no método de fábrica para componentes no apresentador: +Pedimos à classe para produzir o formulário no método de fábrica para componentes no presenter: ```php public function __construct( @@ -401,14 +401,14 @@ public function __construct( protected function createComponentSignInForm(): Form { $form = $this->formFactory->create(); - // podemos mudar o formulário, aqui por exemplo, mudamos a etiqueta no botão - $form['login']->setCaption('Continuar'); - $form->onSuccess[] = [$this, 'signInFormSubmitted']; // e adicionar manipulador + // podemos modificar o formulário, aqui por exemplo mudamos o rótulo no botão + $form['send']->setCaption('Continuar'); + $form->onSuccess[] = [$this, 'signInFormSuceeded']; // e adicionamos o handler return $form; } ``` -O manipulador de processamento de formulários também pode ser entregue a partir da fábrica: +O handler para processamento do formulário também pode ser fornecido pela fábrica: ```php use Nette\Application\UI\Form; @@ -418,14 +418,14 @@ class SignInFormFactory public function create(): Form { $form = new Form; - $form->addText('name', 'Name:'); - $form->addSubmit('send', 'Log in'); + $form->addText('name', 'Nome:'); + $form->addSubmit('send', 'Entrar'); $form->onSuccess[] = function (Form $form, $data): void { - // processamos aqui nosso formulário submetido + // aqui realizamos o processamento do formulário }; return $form; } } ``` -Portanto, temos uma rápida introdução aos formulários em Nette. Tente procurar no diretório de [exemplos |https://github.com/nette/forms/tree/master/examples] na distribuição para obter mais inspiração. +Então, tivemos uma introdução rápida aos formulários no Nette. Tente dar uma olhada no diretório [examples|https://github.com/nette/forms/tree/master/examples] na distribuição, onde encontrará mais inspiração. diff --git a/forms/pt/rendering.texy b/forms/pt/rendering.texy index 37a6d94fdc..520d0fb787 100644 --- a/forms/pt/rendering.texy +++ b/forms/pt/rendering.texy @@ -1,33 +1,35 @@ -Prestação de formulários -************************ +Renderização de Formulários +*************************** -A aparência das formas pode ser muito diversificada. Na prática, podemos encontrar dois extremos. Por um lado, há a necessidade de renderizar uma série de formulários em uma aplicação que são visualmente semelhantes entre si, e apreciamos a fácil renderização sem um modelo usando `$form->render()`. Este é geralmente o caso das interfaces administrativas. +A aparência dos formulários pode variar muito. Na prática, podemos encontrar dois extremos. Por um lado, há a necessidade de renderizar vários formulários na aplicação que são visualmente tão semelhantes quanto dois ovos, e apreciamos a renderização fácil sem um template usando `$form->render()`. Este é geralmente o caso das interfaces administrativas. -Por outro lado, existem várias formas onde cada uma é única. Sua aparência é melhor descrita usando a linguagem HTML no modelo. E é claro que, além dos dois extremos mencionados, vamos encontrar muitos formulários que se enquadram em algum lugar no meio. +Por outro lado, existem formulários diversos onde a regra é: cada peça é um original. A sua forma é melhor descrita usando a linguagem HTML no template do formulário. E, claro, além dos dois extremos mencionados, encontraremos muitos formulários que se situam algures entre eles. -Renderização com Latte .[#toc-rendering-with-latte] -=================================================== +Renderização usando Latte +========================= -O [sistema de modelos Latte |latte:] facilita fundamentalmente a renderização de formulários e seus elementos. Primeiro, mostraremos como renderizar formulários manualmente, elemento por elemento, para obter controle total sobre o código. Mais tarde, mostraremos como [automatizar |#Automatic rendering] tal renderização. +O [Sistema de templates Latte|latte:] facilita fundamentalmente a renderização de formulários e dos seus controlos. Primeiro, mostraremos como renderizar formulários manualmente, controlo por controlo, obtendo assim controlo total sobre o código. Mais tarde, mostraremos como essa renderização pode ser [automatizada |#Renderização automática]. + +Pode gerar o design do template Latte do formulário usando o método `Nette\Forms\Blueprint::latte($form)`, que o imprime na página do navegador. Depois, basta clicar para selecionar o código e copiá-lo para o seu projeto. .{data-version:3.1.15} `{control}` ----------- -A maneira mais fácil de renderizar um formulário é escrever em um modelo: +A maneira mais simples de renderizar um formulário é escrever no template: ```latte {control signInForm} ``` -A aparência da forma renderizada pode ser alterada configurando o [Renderer |#Renderer] e os [controles individuais |#HTML Attributes]. +A aparência do formulário renderizado desta forma pode ser influenciada configurando o [#Renderer] e os [controlos individuais |#Atributos HTML]. `n:name` -------- -É extremamente fácil ligar a definição do formulário em código PHP com o código HTML. Basta adicionar os atributos `n:name`. É assim que é fácil! +A definição do formulário no código PHP pode ser ligada de forma extremamente fácil ao código HTML. Basta adicionar atributos `n:name`. É tão fácil! ```php protected function createComponentSignInForm(): Form @@ -54,10 +56,9 @@ protected function createComponentSignInForm(): Form </form> ``` -A aparência do código HTML resultante está inteiramente em suas mãos. Se você usar o atributo `n:name` com `<select>`, `<button>` ou `<textarea>` elementos, seu conteúdo interno é automaticamente preenchido. -Além disso, o `<form n:name>` cria uma variável local `$form` com o objeto do formulário desenhado e o fechamento `</form>` desenha todos os elementos ocultos não desenhados (o mesmo se aplica a `{form} ... {/form}`). +Tem controlo total sobre a forma do código HTML resultante. Se usar o atributo `n:name` nos elementos `<select>`, `<button>` ou `<textarea>`, o seu conteúdo interno será preenchido automaticamente. Além disso, a tag `<form n:name>` cria uma variável local `$form` com o objeto do formulário a ser desenhado, e a tag de fecho `</form>` renderiza todos os controlos ocultos não renderizados (o mesmo se aplica a `{form} ... {/form}`). -No entanto, não devemos esquecer de entregar possíveis mensagens de erro. Tanto as que foram adicionadas a elementos individuais pelo método `addError()` (usando `{inputError}`) como as que foram adicionadas diretamente ao formulário (devolvidas por `$form->getOwnErrors()`): +No entanto, não devemos esquecer de renderizar possíveis mensagens de erro. Tanto aquelas que foram adicionadas aos controlos individuais pelo método `addError()` (usando `{inputError}`), como aquelas adicionadas diretamente ao formulário (retornadas por `$form->getOwnErrors()`): ```latte <form n:name=signInForm class=form> @@ -79,7 +80,7 @@ No entanto, não devemos esquecer de entregar possíveis mensagens de erro. Tant </form> ``` -Elementos de formulário mais complexos, tais como RadioList ou CheckboxList, podem ser renderizados item por item: +Controlos de formulário mais complexos, como RadioList ou CheckboxList, podem ser renderizados item por item desta forma: ```latte {foreach $form[gender]->getItems() as $key => $label} @@ -88,16 +89,10 @@ Elementos de formulário mais complexos, tais como RadioList ou CheckboxList, po ``` -Proposta de Código `{formPrint}` .[#toc-formprint] --------------------------------------------------- - -Você pode gerar um código Latte similar para um formulário usando a tag `{formPrint}`. Se você colocá-lo em um modelo, você verá o rascunho do código ao invés da renderização normal. Então basta selecioná-lo e copiá-lo para seu projeto. - - `{label}` `{input}` ------------------- -Você não quer pensar para cada elemento que elemento HTML usar para ele no template, se `<input>`, `<textarea>` etc.? A solução é a tag universal `{input}`: +Não quer pensar em que elemento HTML usar no template para cada controlo, seja `<input>`, `<textarea>`, etc.? A solução é a tag universal `{input}`: ```latte <form n:name=signInForm class=form> @@ -119,9 +114,9 @@ Você não quer pensar para cada elemento que elemento HTML usar para ele no tem </form> ``` -Se o formulário utilizar um tradutor, o texto dentro das tags `{label}` será traduzido. +Se o formulário usar um tradutor, o texto dentro das tags `{label}` será traduzido. -Novamente, elementos mais complexos da forma, tais como RadioList ou CheckboxList, podem ser renderizados item por item: +Mesmo neste caso, controlos de formulário mais complexos, como RadioList ou CheckboxList, podem ser renderizados item por item: ```latte {foreach $form[gender]->items as $key => $label} @@ -129,20 +124,19 @@ Novamente, elementos mais complexos da forma, tais como RadioList ou CheckboxLis {/foreach} ``` -Para tornar o `<input>` no próprio item Checkbox, use `{input myCheckbox:}`. Os atributos HTML devem ser separados por uma vírgula `{input myCheckbox:, class: required}`. +Para renderizar apenas o `<input>` no controlo Checkbox, use `{input myCheckbox:}`. Neste caso, separe sempre os atributos HTML com uma vírgula `{input myCheckbox:, class: required}`. `{inputError}` -------------- -Imprime uma mensagem de erro para o elemento do formulário, se ele tiver um. A mensagem é geralmente envolvida em um elemento HTML para estilização. -Evitar renderizar um elemento vazio, se não houver mensagem, pode ser feito elegantemente com `n:ifcontent`: +Exibe a mensagem de erro para um controlo de formulário, se houver alguma. A mensagem geralmente é envolvida num elemento HTML para estilização. Evitar a renderização de um elemento vazio, se não houver mensagem, pode ser feito elegantemente usando `n:ifcontent`: ```latte <span class=error n:ifcontent>{inputError $input}</span> ``` -Podemos detectar a presença de um erro usando o método `hasErrors()` e definir a classe do elemento pai de acordo: +A presença de um erro pode ser verificada com o método `hasErrors()` e, com base nisso, definir a classe do elemento pai: ```latte <div n:class="$form[username]->hasErrors() ? 'error'"> @@ -155,14 +149,13 @@ Podemos detectar a presença de um erro usando o método `hasErrors()` e definir `{form}` -------- -Etiquetas `{form signInForm}...{/form}` são uma alternativa para `<form n:name="signInForm">...</form>`. +As tags `{form signInForm}...{/form}` são uma alternativa a `<form n:name="signInForm">...</form>`. -Renderização automática .[#toc-automatic-rendering] ---------------------------------------------------- +Renderização automática +----------------------- -Com as etiquetas `{input}` e `{label}`, podemos criar facilmente um modelo genérico para qualquer formulário. Ele irá iterar e renderizar todos os seus elementos sequencialmente, exceto os elementos ocultos, que são renderizados automaticamente quando o formulário é encerrado com o `</form>` tag. -Esperará o nome da forma renderizada na variável `$form`. +Graças às tags `{input}` e `{label}`, podemos facilmente criar um template genérico para qualquer formulário. Ele iterará e renderizará sequencialmente todos os seus controlos, exceto os controlos ocultos, que são renderizados automaticamente ao fechar o formulário com a tag `</form>`. O nome do formulário a ser renderizado será esperado na variável `$form`. ```latte <form n:name=$form class=form> @@ -179,16 +172,15 @@ Esperará o nome da forma renderizada na variável `$form`. </form> ``` -As etiquetas de par auto-encerramento utilizadas `{label .../}` mostram as etiquetas provenientes da definição do formulário no código PHP. +As tags de par auto-fechadas `{label .../}` usadas exibem os rótulos provenientes da definição do formulário no código PHP. -Você pode salvar este modelo genérico no arquivo `basic-form.latte` e para renderizar o formulário, basta incluí-lo e passar o nome do formulário (ou instância) para o parâmetro `$form`: +Guarde este template genérico, por exemplo, no ficheiro `basic-form.latte` e para renderizar o formulário, basta incluí-lo e passar o nome (ou instância) do formulário para o parâmetro `$form`: ```latte {include basic-form.latte, form: signInForm} ``` -Se você gostaria de influenciar a aparência de uma determinada forma e desenhar um elemento de maneira diferente, então a maneira mais fácil é preparar blocos no modelo que podem ser sobregravados posteriormente. -Os blocos também podem ter [nomes dinâmicos |latte:template-inheritance#dynamic-block-names], de modo que você pode inserir o nome do elemento a ser desenhado neles. Por exemplo, o nome do elemento a ser desenhado: +Se quiser intervir na forma de um formulário específico durante a renderização e, por exemplo, renderizar um controlo de forma diferente, o caminho mais simples é preparar blocos no template que possam ser sobrescritos posteriormente. Os blocos também podem ter [nomes dinâmicos |latte:template-inheritance#Nomes de Blocos Dinâmicos], pelo que pode inserir o nome do controlo a ser renderizado neles. Por exemplo: ```latte ... @@ -197,7 +189,7 @@ Os blocos também podem ter [nomes dinâmicos |latte:template-inheritance#dynami ... ``` -Para o elemento, por exemplo `username`, isto cria o bloco `input-username`, que pode ser facilmente anulado usando a etiqueta [{embed} |latte:template-inheritance#unit-inheritance]: +Para o controlo, por exemplo, `username`, será criado o bloco `input-username`, que pode ser facilmente sobrescrito usando a tag [{embed} |latte:template-inheritance#Herança de Unidade]: ```latte {embed basic-form.latte, form: signInForm} @@ -209,7 +201,7 @@ Para o elemento, por exemplo `username`, isto cria o bloco `input-username`, que {/embed} ``` -Alternativamente, todo o conteúdo do modelo `basic-form.latte` pode ser [definido |latte:template-inheritance#definitions] como um bloco, incluindo o parâmetro `$form`: +Alternativamente, todo o conteúdo do template `basic-form.latte` pode ser [definido |latte:template-inheritance#Definições] como um bloco, incluindo o parâmetro `$form`: ```latte {define basic-form, $form} @@ -219,7 +211,7 @@ Alternativamente, todo o conteúdo do modelo `basic-form.latte` pode ser [defini {/define} ``` -Isto tornará o uso um pouco mais fácil: +Graças a isso, a sua chamada será ligeiramente mais simples: ```latte {embed basic-form, signInForm} @@ -227,31 +219,31 @@ Isto tornará o uso um pouco mais fácil: {/embed} ``` -Você só precisa importar o bloco em um lugar, no início do modelo de layout: +O bloco só precisa ser importado num único local, no início do template de layout: ```latte {import basic-form.latte} ``` -Casos especiais .[#toc-special-cases] -------------------------------------- +Casos especiais +--------------- -Se você precisar apenas renderizar o conteúdo interno de uma forma sem `<form>` & `</form>` Tags HTML, por exemplo, em um pedido AJAX, você pode abrir e fechar o formulário com `{formContext} … {/formContext}`. Funciona de forma similar a `{form}` em um sentido lógico, aqui permite que você use outras tags para desenhar elementos do formulário, mas ao mesmo tempo não desenha nada. +Se precisar de renderizar apenas a parte interna do formulário sem as tags HTML `<form>`, por exemplo, ao enviar snippets, oculte-as usando o atributo `n:tag-if`: ```latte -{formContext signForm} +<form n:name=signInForm n:tag-if=false> <div> <label n:name=username>Username: <input n:name=username></label> {inputError username} </div> -{/formContext} +</form> ``` -A etiqueta `formContainer` ajuda na renderização de entradas dentro de um contêiner de formulário. +A tag `{formContainer}` ajuda na renderização de controlos dentro de um contêiner de formulário. ```latte -<p>Which news you wish to receive:</p> +<p>Quais notícias deseja receber:</p> {formContainer emailNews} <ul> @@ -262,27 +254,27 @@ A etiqueta `formContainer` ajuda na renderização de entradas dentro de um cont ``` -Renderização sem Latte .[#toc-rendering-without-latte] -====================================================== +Renderização sem Latte +====================== -A maneira mais fácil de entregar um formulário é telefonar: +A maneira mais simples de renderizar um formulário é chamar: ```php $form->render(); ``` -A aparência da forma renderizada pode ser alterada configurando o [Renderer |#Renderer] e os [controles individuais |#HTML Attributes]. +A aparência do formulário renderizado desta forma pode ser influenciada configurando o [#Renderer] e os [controlos individuais |#Atributos HTML]. -Renderização manual .[#toc-manual-rendering] --------------------------------------------- +Renderização manual +------------------- -Cada elemento do formulário tem métodos que geram o código HTML para o campo e etiqueta do formulário. Eles podem devolvê-lo como uma string ou como um objeto [Nette\UtilsHtml |utils:html-elements]: +Cada controlo de formulário possui métodos que geram o código HTML do campo de formulário e do rótulo. Podem retorná-lo como uma string ou como um objeto [Nette\Utils\Html|utils:html-elements]: -- `getControl(): Html|string` retorna o código HTML do elemento -- `getLabel($caption = null): Html|string|null` retorna o código HTML da etiqueta, se houver +- `getControl(): Html|string` retorna o código HTML do controlo +- `getLabel($caption = null): Html|string|null` retorna o código HTML do rótulo, se existir -Isto permite que a forma seja renderizada elemento por elemento: +O formulário pode, assim, ser renderizado controlo por controlo: ```php <?php $form->render('begin') ?> @@ -305,47 +297,46 @@ Isto permite que a forma seja renderizada elemento por elemento: <?php $form->render('end') ?> ``` -Enquanto para alguns elementos `getControl()` retorna um único elemento HTML (por exemplo `<input>`, `<select>` etc.), para outros retorna um código HTML inteiro (CheckboxList, RadioList). -Neste caso, pode-se usar métodos que geram entradas e etiquetas individuais, para cada item separadamente: +Enquanto para alguns controlos `getControl()` retorna um único elemento HTML (por exemplo, `<input>`, `<select>`, etc.), para outros retorna um pedaço inteiro de código HTML (CheckboxList, RadioList). Nesse caso, pode usar métodos que geram inputs e rótulos individuais, para cada item separadamente: -- `getControlPart($key = null): ?Html` retorna o código HTML de um único item -- `getLabelPart($key = null): ?Html` retorna o código HTML para a etiqueta de um único item +- `getControlPart($key = null): ?Html` retorna o código HTML de um item +- `getLabelPart($key = null): ?Html` retorna o código HTML do rótulo de um item .[note] -Estes métodos são prefixados com `get` por razões históricas, mas `generate` seria melhor, pois cria e devolve um novo elemento `Html` em cada chamada. +Estes métodos têm o prefixo `get` por razões históricas, mas `generate` seria melhor, pois a cada chamada criam e retornam um novo elemento `Html`. -Renderizador .[#toc-renderer] -============================= +Renderer +======== -É um objeto que proporciona a renderização da forma. Ele pode ser definido pelo método `$form->setRenderer`. Ele é passado no controle quando o método `$form->render()` é chamado. +É um objeto que garante a renderização do formulário. Pode ser definido pelo método `$form->setRenderer`. O controlo é passado para ele quando o método `$form->render()` é chamado. -Se não definirmos um renderizador personalizado, será usado o renderizador padrão [api:Nette\Forms\Rendering\DefaultFormRenderer]. Isto renderizará os elementos do formulário como uma tabela HTML. A saída é parecida com esta: +Se não definirmos o nosso próprio renderizador, será usado o renderizador padrão [api:Nette\Forms\Rendering\DefaultFormRenderer]. Ele renderiza os controlos do formulário na forma de uma tabela HTML. A saída parece-se com isto: ```latte <table> <tr class="required"> - <th><label class="required" for="frm-name">Name:</label></th> + <th><label class="required" for="frm-name">Nome:</label></th> <td><input type="text" class="text" name="name" id="frm-name" required value=""></td> </tr> <tr class="required"> - <th><label class="required" for="frm-age">Age:</label></th> + <th><label class="required" for="frm-age">Idade:</label></th> <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> </tr> <tr> - <th><label>Gender:</label></th> + <th><label>Sexo:</label></th> ... ``` -Depende de você, se usar uma mesa ou não, e muitos web designers preferem marcações diferentes, por exemplo, uma lista. Podemos configurar `DefaultFormRenderer` para que ela não se torne uma mesa. Só temos que definir os [embrulhos |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers] adequados. O primeiro índice sempre representa uma área e o segundo é seu elemento. Todas as áreas respectivas são mostradas na figura: +Se usar ou não uma tabela para a estrutura do formulário é discutível, e muitos web designers preferem outra marcação. Por exemplo, uma lista de definição. Reconfiguraremos, portanto, o `DefaultFormRenderer` para que ele renderize o formulário na forma de uma lista. A configuração é feita editando o array [$wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers]. O primeiro índice representa sempre a área e o segundo o seu atributo. As áreas individuais são mostradas na imagem: -[* form-areas-en.webp *] +[* defaultformrenderer.webp *] -Por padrão, um grupo de `controls` está envolto em `<table>`e cada `pair` é uma linha de tabela `<tr>` contendo um par de `label` e `control` (células `<th>` e `<td>`). Vamos mudar todos esses elementos do invólucro. Vamos embrulhar `controls` em `<dl>`deixe `pair` por si só, coloque `label` em `<dt>` e embrulhe `control` em `<dd>`: +Por padrão, o grupo de controlos `controls` é envolvido por uma tabela `<table>`, cada `pair` representa uma linha da tabela `<tr>` e o par `label` e `control` são células `<th>` e `<td>`. Agora mudaremos os elementos envolventes. Inseriremos a área `controls` num contêiner `<dl>`, deixaremos a área `pair` sem contêiner, inseriremos `label` em `<dt>` e, finalmente, envolveremos `control` com as tags `<dd>`: ```php $renderer = $form->getRenderer(); @@ -357,193 +348,192 @@ $renderer->wrappers['control']['container'] = 'dd'; $form->render(); ``` -Resultados no seguinte trecho: +O resultado é este código HTML: ```latte <dl> - <dt><label class="required" for="frm-name">Name:</label></dt> + <dt><label class="required" for="frm-name">Nome:</label></dt> <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> - <dt><label class="required" for="frm-age">Age:</label></dt> + <dt><label class="required" for="frm-age">Idade:</label></dt> <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> - <dt><label>Gender:</label></dt> + <dt><label>Sexo:</label></dt> ... </dl> ``` -Os embaladores podem afetar muitos atributos. Por exemplo: +No array wrappers, é possível influenciar toda uma gama de outros atributos: -- adicionar classes especiais de CSS a cada entrada de formulário -- distinguir entre linhas ímpares e pares -- fazer o sorteio obrigatório e opcional de forma diferente -- definido, se as mensagens de erro são mostradas acima do formulário ou perto de cada elemento +- adicionar classes CSS a tipos individuais de controlos de formulário +- distinguir linhas pares e ímpares com classes CSS +- distinguir visualmente itens obrigatórios e opcionais +- determinar se as mensagens de erro serão exibidas diretamente nos controlos ou acima do formulário -Opções .[#toc-options] ----------------------- +Options +------- -O comportamento do Renderer também pode ser controlado ajustando *opções* em elementos individuais da forma. Desta forma, é possível definir a ponta da ferramenta que é exibida ao lado do campo de entrada: +O comportamento do Renderer também pode ser controlado definindo *options* nos controlos de formulário individuais. Assim, pode-se definir um rótulo que será exibido ao lado do campo de entrada: ```php -$form->addText('phone', 'Number:') - ->setOption('description', 'This number will remain hidden'); +$form->addText('phone', 'Número:') + ->setOption('description', 'Este número permanecerá oculto'); ``` -Se quisermos colocar conteúdo HTML dentro dele, usamos a classe [Html |utils:html-elements]. +Se quisermos colocar conteúdo HTML nele, usamos a classe [Html |utils:html-elements] ```php use Nette\Utils\Html; -$form->addText('phone', 'Phone:') +$form->addText('phone', 'Número:') ->setOption('description', Html::el('p') - ->setHtml('<a href="...">Terms of service.</a>') + ->setHtml('<a href="...">Termos de armazenamento do seu número</a>') ); ``` .[tip] -O elemento Html também pode ser usado no lugar do rótulo: `$form->addCheckbox('conditions', $label)`. +O elemento Html também pode ser usado em vez de um rótulo: `$form->addCheckbox('conditions', $label)`. -Agrupamento de entradas .[#toc-grouping-inputs] ------------------------------------------------ +Agrupamento de controlos +------------------------ -O Renderer permite agrupar elementos em grupos visuais (conjuntos de campos): +O Renderer permite agrupar controlos em grupos visuais (fieldsets): ```php -$form->addGroup('Personal data'); +$form->addGroup('Dados Pessoais'); ``` -A criação de um novo grupo o ativa - todos os elementos adicionados são acrescentados a este grupo. Você pode construir uma forma como esta: +Após criar um novo grupo, ele torna-se ativo e cada controlo recém-adicionado também é adicionado a ele. Assim, o formulário pode ser construído desta forma: ```php $form = new Form; -$form->addGroup('Personal data'); -$form->addText('name', 'Your name:'); -$form->addInteger('age', 'Your age:'); +$form->addGroup('Dados Pessoais'); +$form->addText('name', 'Seu nome:'); +$form->addInteger('age', 'Sua idade:'); $form->addEmail('email', 'Email:'); -$form->addGroup('Shipping address'); -$form->addCheckbox('send', 'Ship to address'); -$form->addText('street', 'Street:'); -$form->addText('city', 'City:'); -$form->addSelect('country', 'Country:', $countries); +$form->addGroup('Endereço de entrega'); +$form->addCheckbox('send', 'Enviar para o endereço'); +$form->addText('street', 'Rua:'); +$form->addText('city', 'Cidade:'); +$form->addSelect('country', 'País:', $countries); ``` +O Renderer primeiro renderiza os grupos e só depois os controlos que não pertencem a nenhum grupo. + -Suporte de Bootstrap .[#toc-bootstrap-support] ----------------------------------------------- +Suporte para Bootstrap +---------------------- -Você pode encontrar [exemplos de |https://github.com/nette/forms/tree/master/examples] configuração do Renderer para [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], [Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] e [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php] +[Nos exemplos |https://github.com/nette/forms/tree/master/examples] encontrará exemplos de como configurar o Renderer para [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], [Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] e [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php] -Atributos HTML .[#toc-html-attributes] -====================================== +Atributos HTML +============== -Você pode definir quaisquer atributos HTML para controles de formulários usando `setHtmlAttribute(string $name, $value = true)`: +Para definir quaisquer atributos HTML para controlos de formulário, usamos o método `setHtmlAttribute(string $name, $value = true)`: ```php -$form->addInteger('número', 'Número:') +$form->addInteger('number', 'Número:') ->setHtmlAttribute('class', 'big-number'); -$form->addSelect('rank', 'Order by:', ['price', 'name']) - ->setHtmlAttribute('onchange', 'submit()'); // chama a função JS submit() on change +$form->addSelect('rank', 'Ordenar por:', ['preço', 'nome']) + ->setHtmlAttribute('onchange', 'submit()'); // enviar ao alterar -// aplicando em <form> +// Para definir atributos do próprio <form> $form->setHtmlAttribute('id', 'myForm'); ``` -Tipo de ajuste de entrada: +Especificação do tipo de controlo: ```php -$form->addText('tel', 'Your telephone:') +$form->addText('tel', 'Seu telefone:') ->setHtmlType('tel') - ->setHtmlAttribute('placeholder', 'Please, fill in your telephone'); + ->setHtmlAttribute('placeholder', 'escreva o telefone'); ``` -Podemos definir atributo HTML a itens individuais em listas de rádio ou checkbox com valores diferentes para cada um deles. -Observe os dois pontos após `style:` para garantir que o valor seja selecionado por chave: +.[warning] +A definição do tipo e outros atributos servem apenas para fins visuais. A verificação da correção das entradas deve ocorrer no servidor, o que é garantido pela escolha do [controlo de formulário|controls] apropriado e pela indicação das [regras de validação|validation]. + +Para itens individuais em listas de rádio ou checkbox, podemos definir um atributo HTML com valores diferentes para cada um deles. Observe os dois pontos após `style:`, que garantem a seleção do valor pela chave: ```php -$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue']; +$colors = ['r' => 'vermelho', 'g' => 'verde', 'b' => 'azul']; $styles = ['r' => 'background:red', 'g' => 'background:green']; -$form->addCheckboxList('colors', 'Colors:', $colors) +$form->addCheckboxList('colors', 'Cores:', $colors) ->setHtmlAttribute('style:', $styles); ``` -Renderizadores: +Exibe: ```latte -<label><input type="checkbox" name="colors[]" style="background:red" value="r">red</label> -<label><input type="checkbox" name="colors[]" style="background:green" value="g">green</label> -<label><input type="checkbox" name="colors[]" value="b">blue</label> +<label><input type="checkbox" name="colors[]" style="background:red" value="r">vermelho</label> +<label><input type="checkbox" name="colors[]" style="background:green" value="g">verde</label> +<label><input type="checkbox" name="colors[]" value="b">azul</label> ``` -Para um atributo HTML lógico (que não tem valor, como `readonly`), você pode usar um ponto de interrogação: +Para definir atributos lógicos, como `readonly`, podemos usar a notação com ponto de interrogação: ```php -$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue']; -$form->addCheckboxList('colors', 'Colors:', $colors) - ->setHtmlAttribute('readonly?', 'r'); // use array para múltiplas chaves, por exemplo ['r', 'g']. +$form->addCheckboxList('colors', 'Cores:', $colors) + ->setHtmlAttribute('readonly?', 'r'); // para mais chaves use um array, ex. ['r', 'g'] ``` -Renderizadores: +Exibe: ```latte -<label><input type="checkbox" name="colors[]" readonly value="r">red</label> -<label><input type="checkbox" name="colors[]" value="g">green</label> -<label><input type="checkbox" name="colors[]" value="b">blue</label> +<label><input type="checkbox" name="colors[]" readonly value="r">vermelho</label> +<label><input type="checkbox" name="colors[]" value="g">verde</label> +<label><input type="checkbox" name="colors[]" value="b">azul</label> ``` -Para as caixas de seleção, o método `setHtmlAttribute()` define os atributos do `<select>` elemento. Se quisermos definir os atributos para cada -`<option>`usaremos o método `setOptionAttribute()`. Além disso, o cólon e o ponto de interrogação utilizados acima funcionam: +No caso de selectboxes, o método `setHtmlAttribute()` define os atributos do elemento `<select>`. Se quisermos definir atributos para os `<option>` individuais, usamos o método `setOptionAttribute()`. As notações com dois pontos e ponto de interrogação mencionadas acima também funcionam: ```php -$form->addSelect('colors', 'Colors:', $colors) +$form->addSelect('colors', 'Cores:', $colors) ->setOptionAttribute('style:', $styles); ``` -Renderizadores: +Exibe: ```latte <select name="colors"> - <option value="r" style="background:red">red</option> - <option value="g" style="background:green">green</option> - <option value="b">blue</option> + <option value="r" style="background:red">vermelho</option> + <option value="g" style="background:green">verde</option> + <option value="b">azul</option> </select> ``` -Protótipos .[#toc-prototypes] ------------------------------ +Protótipos +---------- -Uma forma alternativa de definir atributos HTML é modificar o modelo a partir do qual o elemento HTML é gerado. O template é um objeto `Html` e é retornado pelo método `getControlPrototype()`: +Uma maneira alternativa de definir atributos HTML consiste em modificar o modelo a partir do qual o elemento HTML é gerado. O modelo é um objeto `Html` e é retornado pelo método `getControlPrototype()`: ```php -$input = $form->addInteger('número'); +$input = $form->addInteger('number', 'Número:'); $html = $input->getControlPrototype(); // <input> -$html->classe('big-number'); // <input class="big-number"> +$html->class('big-number'); // <input class="big-number"> ``` -O modelo de etiqueta devolvido por `getLabelPrototype()` também pode ser modificado desta forma: +Desta forma, também é possível modificar o modelo do rótulo, que é retornado por `getLabelPrototype()`: ```php $html = $input->getLabelPrototype(); // <label> -$html->class('distinctive'); // <label class="distinctive"> +$html->class('distinctive'); // <label class="distinctive"> ``` -Para os itens Checkbox, CheckboxList e RadioList você pode influenciar o modelo do elemento que envolve o item. Ele é devolvido por `getContainerPrototype()`. Por padrão é um elemento "vazio", portanto nada é renderizado, mas ao dar-lhe um nome, ele será renderizado: +Para os controlos Checkbox, CheckboxList e RadioList, pode influenciar o modelo do elemento que envolve todo o controlo. Ele é retornado por `getContainerPrototype()`. No estado padrão, é um elemento "vazio", então nada é renderizado, mas ao definir um nome para ele, ele será renderizado: ```php -$input = $form->addCheckbox('enviar'); -echo $input->getControl(); -// <label><input type="checkbox" name="send"></label> - +$input = $form->addCheckbox('send'); $html = $input->getContainerPrototype(); $html->setName('div'); // <div> $html->class('check'); // <div class="check"> @@ -551,50 +541,49 @@ echo $input->getControl(); // <div class="check"><label><input type="checkbox" name="send"></label></div> ``` -No caso da CheckboxList e RadioList também é possível influenciar o padrão separador de itens devolvido pelo método `getSeparatorPrototype()`. Por padrão, ele é um elemento `<br>`. Se você o mudar para um elemento de par, ele envolverá os itens individuais em vez de separá-los. -Também é possível influenciar o modelo de elemento HTML das etiquetas dos itens, que retorna `getItemLabelPrototype()`. +No caso de CheckboxList e RadioList, também é possível influenciar o modelo do separador dos itens individuais, que é retornado pelo método `getSeparatorPrototype()`. No estado padrão, é o elemento `<br>`. Se o alterar para um elemento de par, ele envolverá os itens individuais em vez de separá-los. E, além disso, é possível influenciar o modelo do elemento HTML do rótulo nos itens individuais, que é retornado por `getItemLabelPrototype()`. -Traduzindo .[#toc-translating] -============================== +Tradução +======== -Se você estiver programando uma aplicação multilíngüe, provavelmente precisará renderizar o formulário em diferentes idiomas. O Nette Framework define uma interface de tradução para este fim [api:Nette\Localization\Translator]. Não há uma implementação padrão em Nette, você pode escolher de acordo com suas necessidades entre várias soluções prontas que você pode encontrar na [Componette |https://componette.org/search/localization]. Sua documentação lhe diz como configurar o tradutor. +Se está a programar uma aplicação multilíngue, provavelmente precisará renderizar o formulário em diferentes versões de idioma. O Nette Framework define uma interface para tradução para este propósito [api:Nette\Localization\Translator]. No Nette, não há implementação padrão, pode escolher de acordo com as suas necessidades entre várias soluções prontas que encontra no [Componette |https://componette.org/search/localization]. Na sua documentação, aprenderá como configurar o tradutor. -O formulário suporta a saída de texto através do tradutor. Nós o passamos usando o método `setTranslator()`: +Os formulários suportam a exibição de textos através do tradutor. Passamos para eles usando o método `setTranslator()`: ```php $form->setTranslator($translator); ``` -De agora em diante, não apenas todas as etiquetas, mas também todas as mensagens de erro ou entradas de caixa de seleção serão traduzidas para outro idioma. +A partir deste momento, não apenas todos os rótulos, mas também todas as mensagens de erro ou itens de caixas de seleção serão traduzidos para outro idioma. -É possível definir um tradutor diferente para elementos individuais do formulário ou desativar completamente a tradução com `null`: +Para controlos de formulário individuais, é possível definir um tradutor diferente ou desativar completamente a tradução com o valor `null`: ```php -$form->addSelect('carModel', 'Model:', $cars) +$form->addSelect('carModel', 'Modelo:', $cars) ->setTranslator(null); ``` -Para [regras de validação |validation], parâmetros específicos também são passados para o tradutor, por exemplo, para regra: +Para [regras de validação|validation], parâmetros específicos também são passados ao tradutor, por exemplo, para a regra: ```php -$form->addPassword('password', 'Password:') - ->addRule($form::MinLength, 'Password has to be at least %d characters long', 8) +$form->addPassword('password', 'Senha:') + ->addRule($form::MinLength, 'A senha deve ter pelo menos %d caracteres', 8); ``` -o tradutor é chamado com os seguintes parâmetros: +o tradutor é chamado com estes parâmetros: ```php -$translator->translate('Password has to be at least %d characters long', 8); +$translator->translate('A senha deve ter pelo menos %d caracteres', 8); ``` -e assim pode escolher a forma plural correta para a palavra `characters` por contagem. +e, portanto, pode escolher a forma plural correta da palavra `caracteres` de acordo com o número. -Evento onRender .[#toc-event-onrender] -====================================== +Evento onRender +=============== -Pouco antes de o formulário ser entregue, podemos ter nosso código invocado. Isto pode, por exemplo, adicionar classes HTML aos elementos do formulário para uma exibição adequada. Acrescentamos o código à matriz `onRender`: +Pouco antes de o formulário ser renderizado, podemos deixar o nosso código ser chamado. Ele pode, por exemplo, adicionar classes HTML aos controlos do formulário para exibição correta. Adicionamos o código ao array `onRender`: ```php $form->onRender[] = function ($form) { diff --git a/forms/pt/standalone.texy b/forms/pt/standalone.texy index 2bcad570b5..d11c0202c9 100644 --- a/forms/pt/standalone.texy +++ b/forms/pt/standalone.texy @@ -1,76 +1,76 @@ -Formulários utilizados autônomos -******************************** +Formulários Usados Sozinhos +*************************** .[perex] -Os Formulários Nette facilitam drasticamente a criação e o processamento de formulários web. Você pode usá-los em suas aplicações completamente por conta própria sem o resto da estrutura, o que demonstraremos neste capítulo. +Os Nette Forms facilitam muito a criação e o processamento de formulários web. Pode usá-los nas suas aplicações de forma totalmente independente do restante do framework, como mostraremos neste capítulo. -Entretanto, se você utiliza o aplicativo Nette e apresentadores, há um guia para você: [formulários em apresentadores |in-presenter]. +No entanto, se usa Nette Application e presenters, o guia para [uso em presenters|in-presenter] é para si. -Primeiro Formulário .[#toc-first-form] -====================================== +Primeiro formulário +=================== -Tentaremos escrever um simples formulário de registro. Seu código será parecido com este ("código completo":https://gist.github.com/dg/370a7e3094d9ba9a9e913b8e2a2dc851): +Vamos tentar escrever um formulário de registo simples. O código será o seguinte ("código completo":https://gist.github.com/dg/57878c1a413ae8ef0c1d83f02c43ef3f): ```php use Nette\Forms\Form; $form = new Form; -$form->addText('name', 'Name:'); -$form->addPassword('password', 'Password:'); -$form->addSubmit('send', 'Sign up'); +$form->addText('name', 'Nome:'); +$form->addPassword('password', 'Senha:'); +$form->addSubmit('send', 'Registar'); ``` -E vamos renderizá-lo: +Podemos renderizá-lo facilmente: ```php $form->render(); ``` -e o resultado deve ser parecido com este: +e no navegador ele será exibido assim: -[* form-en.webp *] +[* form-cs.webp *] -O formulário é um objeto da classe `Nette\Forms\Form` (a classe `Nette\Application\UI\Form` é usada em apresentadores). Adicionamos o nome dos controles, a senha e o botão de envio a ele. +O formulário é um objeto da classe `Nette\Forms\Form` (a classe `Nette\Application\UI\Form` é usada em presenters). Adicionamos a ele os chamados controlos nome, senha e um botão de envio. -Agora vamos reavivar a forma. Perguntando `$form->isSuccess()`, descobriremos se o formulário foi enviado e se foi preenchido de forma válida. Se for o caso, despejaremos os dados. Após a definição do formulário, acrescentaremos: +E agora vamos dar vida ao formulário. Perguntando `$form->isSuccess()`, descobrimos se o formulário foi enviado e se foi preenchido de forma válida. Se sim, exibimos os dados. Após a definição do formulário, adicionamos: ```php if ($form->isSuccess()) { - echo 'O formulário foi preenchido e enviado corretamente'; + echo 'Formulário foi preenchido corretamente e enviado'; $data = $form->getValues(); - // $data->name contém nome + // $data->name contém o nome // $data->password contém a senha var_dump($data); } ``` -O método `getValues()` retorna os dados enviados na forma de um objeto [ArrayHash |utils:arrays#ArrayHash]. Mostraremos como mudar isso [mais tarde |#Mapping to Classes]. A variável `$data` contém as chaves `name` e `password` com os dados inseridos pelo usuário. +O método `getValues()` retorna os dados enviados na forma de um objeto [ArrayHash |utils:arrays#ArrayHash]. Mostraremos como alterar isso [mais tarde |#Mapeamento para classes]. O objeto `$data` contém as chaves `name` e `password` com os dados que o utilizador preencheu. -Normalmente, enviamos os dados diretamente para processamento posterior, que pode ser, por exemplo, inserido no banco de dados. Entretanto, pode ocorrer um erro durante o processamento, por exemplo, o nome de usuário já está tomado. Neste caso, passamos o erro de volta ao formulário usando `addError()` e deixamos que ele seja redesenhado, com uma mensagem de erro: +Normalmente, enviamos os dados diretamente para processamento posterior, que pode ser, por exemplo, inserção no banco de dados. No entanto, durante o processamento, pode ocorrer um erro, por exemplo, o nome de utilizador já está em uso. Nesse caso, passamos o erro de volta para o formulário usando `addError()` e o deixamos renderizar novamente, junto com a mensagem de erro. ```php -$form->addError('Sorry, username is already in use.'); +$form->addError('Desculpe, este nome de utilizador já está em uso.'); ``` -Após o processamento do formulário, redirecionaremos para a página seguinte. Isto evita que o formulário seja reapresentado involuntariamente clicando no botão *refresh*, *back*, ou movendo o histórico do navegador. +Após processar o formulário, redirecionamos para a próxima página. Isso evita o reenvio indesejado do formulário pelo botão *atualizar*, *voltar* ou movendo-se no histórico do navegador. -Por padrão, o formulário é enviado usando o método POST para a mesma página. Ambos podem ser modificados: +O formulário é enviado por padrão pelo método POST para a mesma página. Ambos podem ser alterados: ```php $form->setAction('/submit.php'); $form->setMethod('GET'); ``` -E isso é tudo :-) Temos uma forma funcional e perfeitamente [segura |#Vulnerability Protection]. +E isso é basicamente tudo :-) Temos um formulário funcional e perfeitamente [seguro |#Proteção contra vulnerabilidades]. -Tente adicionar mais [controles de formulário |controls]. +Tente adicionar também outros [controlos de formulário|controls]. -Acesso aos controles .[#toc-access-to-controls] -=============================================== +Acesso aos controlos +==================== -A forma e seus controles individuais são chamados de componentes. Eles criam uma árvore de componentes, onde a raiz é a forma. Você pode acessar os controles individuais da seguinte forma: +Chamamos o formulário e os seus controlos individuais de componentes. Eles formam uma árvore de componentes, onde a raiz é o formulário. Podemos aceder aos controlos individuais do formulário desta forma: ```php $input = $form->getComponent('name'); @@ -80,37 +80,36 @@ $button = $form->getComponent('send'); // sintaxe alternativa: $button = $form['send']; ``` -Os controles são removidos com o uso de controles não ajustados: +Os controlos são removidos usando unset: ```php unset($form['name']); ``` -Regras de validação .[#toc-validation-rules] -============================================ +Regras de validação +=================== -A palavra *valido* foi usada aqui, mas o formulário ainda não tem regras de validação. Vamos corrigi-lo. +A palavra *válido* foi mencionada, mas o formulário ainda não possui regras de validação. Vamos corrigir isso. -O nome será obrigatório, portanto o marcaremos com o método `setRequired()`, cujo argumento é o texto da mensagem de erro que será exibida se o usuário não a preencher. Se não for apresentado nenhum argumento, será utilizada a mensagem de erro padrão. +O nome será obrigatório, então marcamo-lo com o método `setRequired()`, cujo argumento é o texto da mensagem de erro que será exibida se o utilizador não preencher o nome. Se o argumento não for fornecido, a mensagem de erro padrão será usada. ```php -$form->addText('name', 'Name:') - ->setRequired('Please enter a name.'); +$form->addText('name', 'Nome:') + ->setRequired('Por favor, insira o nome'); ``` -Tente enviar o formulário sem o nome preenchido e você verá que uma mensagem de erro é exibida e o navegador ou servidor irá rejeitá-lo até que você o preencha. +Tente enviar o formulário sem preencher o nome e verá que uma mensagem de erro será exibida e o navegador ou servidor o rejeitará até que preencha o campo. -Ao mesmo tempo, você não poderá enganar o sistema digitando apenas espaços na entrada, por exemplo. De jeito nenhum. A Nette apara automaticamente os espaços em branco à esquerda e à direita. Experimente. É algo que você deve fazer sempre com cada entrada de linha, mas muitas vezes é esquecido. A Nette o faz automaticamente. (Você pode tentar enganar os formulários e enviar uma cadeia de várias linhas como o nome. Mesmo aqui, Nette não será enganado e as quebras de linha mudarão para espaços). +Ao mesmo tempo, não enganará o sistema digitando, por exemplo, apenas espaços no campo. De jeito nenhum. Nette remove automaticamente os espaços à esquerda e à direita. Experimente. É algo que deveria sempre fazer com cada input de linha única, mas muitas vezes é esquecido. Nette faz isso automaticamente. (Pode tentar enganar o formulário e enviar uma string de várias linhas como nome. Mesmo aqui, Nette não se deixa enganar e transforma as quebras de linha em espaços.) -O formulário é sempre validado no lado do servidor, mas a validação JavaScript também é gerada, o que é rápido e o usuário sabe do erro imediatamente, sem ter que enviar o formulário para o servidor. Isto é tratado pelo script `netteForms.js`. -Acrescente-o à página: +O formulário é sempre validado no lado do servidor, mas também é gerada uma validação JavaScript, que ocorre instantaneamente e o utilizador é informado sobre o erro imediatamente, sem a necessidade de enviar o formulário ao servidor. Isso é feito pelo script `netteForms.js`. Insira-o na página: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Se você olhar no código fonte da página com formulário, você pode notar que Nette insere os campos necessários em elementos com uma classe CSS `required`. Tente adicionar o seguinte estilo ao modelo, e a etiqueta "Name" (Nome) será vermelha. Elegantemente, marcamos os campos requeridos para os usuários: +Se olhar o código-fonte da página com o formulário, poderá notar que Nette insere os controlos obrigatórios em elementos com a classe CSS `required`. Tente adicionar a seguinte folha de estilo ao template e o rótulo "Nome" ficará vermelho. Desta forma, marcamos elegantemente os controlos obrigatórios para os utilizadores: ```latte <style> @@ -118,96 +117,96 @@ Se você olhar no código fonte da página com formulário, você pode notar que </style> ``` -Regras de validação adicionais serão adicionadas pelo método `addRule()`. O primeiro parâmetro é a regra, o segundo é novamente o texto da mensagem de erro e o argumento da regra de validação opcional pode seguir. O que isso significa? +Adicionamos outras regras de validação com o método `addRule()`. O primeiro parâmetro é a regra, o segundo é novamente o texto da mensagem de erro e pode haver ainda um argumento da regra de validação. O que isso significa? -O formulário terá outra entrada opcional *idade* com a condição, que deve ser um número (`addInteger()`) e em certos limites (`$form::Range`). E aqui usaremos o terceiro argumento de `addRule()`, a própria faixa: +Vamos estender o formulário com um novo campo opcional "idade", que deve ser um número inteiro (`addInteger()`) e, além disso, dentro de um intervalo permitido (`$form::Range`). E aqui usaremos o terceiro parâmetro do método `addRule()`, pelo qual passamos o intervalo desejado ao validador como um par `[de, até]`: ```php -$form->addInteger('age', 'Age:') - ->addRule($form::Range, 'You must be older 18 years and be under 120.', [18, 120]); +$form->addInteger('age', 'Idade:') + ->addRule($form::Range, 'A idade deve ser entre 18 e 120', [18, 120]); ``` .[tip] -Se o usuário não preencher o campo, as regras de validação não serão verificadas, pois o campo é opcional. +Se o utilizador não preencher o campo, as regras de validação não serão verificadas, pois o controlo é opcional. -Obviamente, há espaço para uma pequena refatoração. Na mensagem de erro e no terceiro parâmetro, os números são listados em duplicata, o que não é o ideal. Se estivéssemos criando um [formulário multilíngüe |rendering#translating] e a mensagem contendo números tivesse que ser traduzida para vários idiomas, seria mais difícil mudar os valores. Por este motivo, podem ser utilizados caracteres substitutos `%d`: +Aqui surge espaço para uma pequena refatoração. Na mensagem de erro e no terceiro parâmetro, os números são listados em duplicidade, o que não é ideal. Se estivéssemos a criar [formulários multilíngues |rendering#Tradução] e a mensagem contendo números fosse traduzida para vários idiomas, uma eventual alteração dos valores seria dificultada. Por esse motivo, é possível usar os placeholders `%d` e Nette preencherá os valores: ```php - ->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]); + ->addRule($form::Range, 'A idade deve ser entre %d e %d anos', [18, 120]); ``` -Vamos voltar ao campo *password*, torná-lo *requerido*, e verificar o comprimento mínimo da senha (`$form::MinLength`), novamente usando os caracteres substitutos na mensagem: +Voltemos ao controlo `password`, que também tornaremos obrigatório e ainda verificaremos o comprimento mínimo da senha (`$form::MinLength`), novamente usando o placeholder: ```php -$form->addPassword('password', 'Password:') - ->setRequired('Pick a password') - ->addRule($form::MinLength, 'Your password has to be at least %d long', 8); +$form->addPassword('password', 'Senha:') + ->setRequired('Escolha uma senha') + ->addRule($form::MinLength, 'A senha deve ter pelo menos %d caracteres', 8); ``` -Adicionaremos um campo `passwordVerify` ao formulário, onde o usuário digita a senha novamente, para verificação. Usando regras de validação, verificamos se as duas senhas são as mesmas (`$form::Equal`). E como argumento, fazemos uma referência à primeira senha usando [colchetes |#Access to Controls]: +Adicionamos ao formulário também o campo `passwordVerify`, onde o utilizador digita a senha novamente, para verificação. Usando regras de validação, verificamos se ambas as senhas são iguais (`$form::Equal`). E como parâmetro, damos uma referência à primeira senha usando [colchetes |#Acesso aos controlos]: ```php -$form->addPassword('passwordVerify', 'Password again:') - ->setRequired('Fill your password again to check for typo') - ->addRule($form::Equal, 'Password mismatch', $form['password']) +$form->addPassword('passwordVerify', 'Senha para verificação:') + ->setRequired('Por favor, digite a senha novamente para verificação') + ->addRule($form::Equal, 'As senhas não coincidem', $form['password']) ->setOmitted(); ``` -Usando `setOmitted()`, marcamos um elemento cujo valor realmente não nos interessa e que existe apenas para validação. Seu valor não é passado para `$data`. +Usando `setOmitted()`, marcamos o controlo cujo valor realmente não nos importa e que existe apenas para fins de validação. O valor não é passado para `$data`. -Temos um formulário totalmente funcional com validação em PHP e JavaScript. As capacidades de validação da Nette são muito mais amplas, você pode criar condições, exibir e ocultar partes de uma página de acordo com elas, etc. Você pode descobrir tudo no capítulo sobre [validação de formulários |validation]. +Com isso, temos um formulário totalmente funcional com validação em PHP e JavaScript. As capacidades de validação de Nette são muito mais amplas, é possível criar condições, exibir e ocultar partes da página com base nelas, etc. Aprenderá tudo no capítulo sobre [validação de formulários|validation]. -Valores por default .[#toc-default-values] -========================================== +Valores padrão +============== -Muitas vezes definimos valores padrão para controles de formulários: +Normalmente, definimos valores padrão para os controlos do formulário: ```php -$form->addEmail('email', 'Email') +$form->addEmail('email', 'E-mail') ->setDefaultValue($lastUsedEmail); ``` -Muitas vezes é útil definir valores padrão para todos os controles ao mesmo tempo. Por exemplo, quando o formulário é usado para editar registros. Nós lemos o registro do banco de dados e o definimos como valores padrão: +Muitas vezes, é útil definir valores padrão para todos os controlos simultaneamente. Por exemplo, quando o formulário é usado para editar registos. Lemos o registo do banco de dados e definimos os valores padrão: ```php //$row = ['name' => 'John', 'age' => '33', /* ... */]; $form->setDefaults($row); ``` -Ligue para `setDefaults()` após definir os controles. +Chame `setDefaults()` após a definição dos controlos. -Renderização do formulário .[#toc-rendering-the-form] -===================================================== +Renderização do formulário +========================== -Por padrão, o formulário é apresentado como uma tabela. Os controles individuais seguem as diretrizes básicas de acessibilidade da web. Todas as etiquetas são geradas como `<label>` e estão associados às suas entradas, clicando na etiqueta move o cursor sobre a entrada. +Por padrão, o formulário é renderizado como uma tabela. Os controlos individuais cumprem a regra básica de acessibilidade - todos os rótulos são escritos como `<label>` e vinculados ao controlo de formulário correspondente. Ao clicar no rótulo, o cursor aparece automaticamente no campo do formulário. -Podemos definir quaisquer atributos HTML para cada elemento. Por exemplo, acrescente um espaço reservado: +Podemos definir atributos HTML arbitrários para cada controlo. Por exemplo, adicionar um placeholder: ```php -$form->addInteger('age', 'Age:') - ->setHtmlAttribute('placeholder', 'Please fill in the age'); +$form->addInteger('age', 'Idade:') + ->setHtmlAttribute('placeholder', 'Por favor, preencha a idade'); ``` -Há realmente muitas maneiras de renderizar uma forma, por isso é um [capítulo |rendering] dedicado [à renderização |rendering]. +Existem realmente muitas maneiras de renderizar um formulário, então há um [capítulo separado sobre renderização|rendering] dedicado a isso. -Mapeamento para as classes .[#toc-mapping-to-classes] -===================================================== +Mapeamento para classes +======================= -Vamos voltar para o processamento de dados. O método `getValues()` devolveu os dados submetidos como um objeto `ArrayHash`. Como esta é uma classe genérica, algo como `stdClass`, nos faltará alguma conveniência ao trabalhar com ela, como preenchimento de código para propriedades em editores ou análise de código estático. Isto poderia ser resolvido tendo uma classe específica para cada formulário, cujas propriedades representam os controles individuais. Por exemplo, a classe +Voltemos ao processamento dos dados do formulário. O método `getValues()` retornou-nos os dados enviados como um objeto `ArrayHash`. Como é uma classe genérica, algo como `stdClass`, sentiremos falta de certo conforto ao trabalhar com ela, como sugestões de propriedades em editores ou análise estática de código. Isso poderia ser resolvido tendo uma classe específica para cada formulário, cujas propriedades representam os controlos individuais. Por exemplo: ```php class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -A partir do PHP 8.0, você pode usar esta elegante notação que usa um construtor: +Alternativamente, pode usar o construtor: ```php class RegistrationFormData @@ -221,16 +220,18 @@ class RegistrationFormData } ``` -Como dizer à Nette para nos retornar dados como objetos desta classe? Mais fácil do que você pensa. Tudo que você precisa fazer é especificar o nome da classe ou objeto a ser hidratado como um parâmetro: +As propriedades da classe de dados também podem ser enums e serão mapeadas automaticamente. .{data-version:3.2.4} + +Como dizer ao Nette para nos retornar os dados como objetos desta classe? Mais fácil do que pensa. Basta fornecer o nome da classe ou o objeto a ser hidratado como parâmetro: ```php $data = $form->getValues(RegistrationFormData::class); $name = $data->name; ``` -Um `'array'` também pode ser especificado como um parâmetro, e então os dados retornam como uma matriz. +Também é possível fornecer `'array'` como parâmetro e, em seguida, os dados serão retornados como um array. -Se as formas consistem em uma estrutura de vários níveis composta de recipientes, crie uma classe separada para cada um deles: +Se os formulários formarem uma estrutura multinível composta por contêineres, crie uma classe separada para cada um: ```php $form = new Form; @@ -247,26 +248,28 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` -O mapeamento então sabe pelo tipo de propriedade `$person` que deve mapear o container para a classe `PersonFormData`. Se a propriedade contiver um conjunto de containers, forneça o tipo `array` e passe a classe a ser mapeada diretamente para o container: +O mapeamento então reconhece pelo tipo da propriedade `$person` que deve mapear o contêiner para a classe `PersonFormData`. Se a propriedade contiver um array de contêineres, especifique o tipo `array` e passe a classe para mapeamento diretamente para o contêiner: ```php $person->setMappedType(PersonFormData::class); ``` +Pode gerar o design da classe de dados do formulário usando o método `Nette\Forms\Blueprint::dataClass($form)`, que o exibirá na página do navegador. Em seguida, basta clicar para selecionar o código e copiá-lo para o projeto. .{data-version:3.1.15} + -Botões de submissão múltipla .[#toc-multiple-submit-buttons] -============================================================ +Múltiplos botões +================ -Se o formulário tiver mais de um botão, geralmente precisamos distinguir qual deles foi pressionado. O método `isSubmittedBy()` do botão nos devolve esta informação: +Se o formulário tiver mais de um botão, geralmente precisamos distinguir qual deles foi pressionado. Essa informação é retornada pelo método `isSubmittedBy()` do botão: ```php -$form->addSubmit('save', 'Save'); -$form->addSubmit('delete', 'Delete'); +$form->addSubmit('save', 'Salvar'); +$form->addSubmit('delete', 'Excluir'); if ($form->isSuccess()) { if ($form['save']->isSubmittedBy()) { @@ -279,37 +282,36 @@ if ($form->isSuccess()) { } ``` -Não omitir o `$form->isSuccess()` para verificar a validade dos dados. +Não pule a verificação `$form->isSuccess()`, ela verifica a validade dos dados. -Quando um formulário é apresentado com a chave <kbd>Enter</kbd>, ele é tratado como se tivesse sido apresentado com o primeiro botão. +Quando o formulário é enviado pressionando a tecla <kbd>Enter</kbd>, é considerado como se tivesse sido enviado pelo primeiro botão. -Proteção contra vulnerabilidades .[#toc-vulnerability-protection] -================================================================= +Proteção contra vulnerabilidades +================================ -Nette Framework coloca um grande esforço para ser seguro e como os formulários são a entrada mais comum dos usuários, os formulários Nette são tão bons quanto impenetráveis. +O Nette Framework dá grande ênfase à segurança e, portanto, cuida meticulosamente da boa segurança dos formulários. -Além de proteger os formulários contra ataques de vulnerabilidades conhecidas, como o [Cross-Site Scripting (XSS) |nette:glossary#cross-site-scripting-xss] e o [Cross-Site Request Forgery (CSRF |nette:glossary#cross-site-request-forgery-csrf] ), ele faz muitas pequenas tarefas de segurança que você não precisa mais pensar. +Além de proteger os formulários contra ataques [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] e [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF], ele realiza muitas pequenas medidas de segurança com as quais já não precisa de se preocupar. -Por exemplo, ele filtra todos os caracteres de controle das entradas e verifica a validade da codificação UTF-8, para que os dados do formulário estejam sempre limpos. Para caixas de seleção e listas de rádio, ele verifica que os itens selecionados foram realmente dos oferecidos e que não houve falsificação. Já mencionamos que para entrada de texto de uma linha, ele remove caracteres de fim de linha que um atacante poderia enviar para lá. Para entradas de várias linhas, ele normaliza os caracteres de fim de linha. E assim por diante. +Por exemplo, ele filtra todos os caracteres de controlo das entradas e verifica a validade da codificação UTF-8, para que os dados do formulário estejam sempre limpos. Para caixas de seleção e listas de rádio, ele verifica se os itens selecionados eram realmente das opções oferecidas e se não houve falsificação. Já mencionamos que, para entradas de texto de linha única, ele remove os caracteres de fim de linha que um invasor poderia ter enviado. Para entradas de várias linhas, ele normaliza os caracteres de fim de linha. E assim por diante. -Nette corrige vulnerabilidades de segurança para você que a maioria dos programadores não tem idéia de que existem. +Nette resolve para si riscos de segurança que muitos programadores nem sabem que existem. -O referido ataque do CSRF é que um agressor atrai a vítima para visitar uma página que silenciosamente executa um pedido no navegador da vítima ao servidor onde a vítima está atualmente logada, e o servidor acredita que o pedido foi feito pela vítima à sua vontade. Portanto, a Nette impede que o formulário seja submetido via POST a partir de outro domínio. Se por algum motivo você quiser desligar a proteção e permitir que o formulário seja submetido a partir de outro domínio, use: +O ataque CSRF mencionado consiste no facto de que o invasor atrai a vítima para uma página que, discretamente no navegador da vítima, executa uma requisição ao servidor no qual a vítima está logada, e o servidor acredita que a requisição foi executada pela vítima por sua própria vontade. Portanto, Nette impede o envio de formulários POST de outro domínio. Se, por algum motivo, quiser desativar a proteção e permitir o envio de formulários de outro domínio, use: ```php -$form->allowCrossOrigin(); // ATENÇÃO! Desliga a proteção! +$form->allowCrossOrigin(); // ATENÇÃO! Desativa a proteção! ``` -Esta proteção utiliza um cookie SameSite chamado `_nss`. Portanto, crie um formulário antes de descarregar a primeira saída para que o cookie possa ser enviado. +Esta proteção usa um cookie SameSite chamado `_nss`. Portanto, crie o objeto do formulário antes de enviar a primeira saída, para que o cookie possa ser enviado. -A proteção de cookies do SameSite pode não ser 100% confiável, por isso é uma boa idéia ativar a proteção simbólica: +A proteção usando o cookie SameSite pode não ser 100% confiável, por isso é aconselhável ativar também a proteção por token: ```php $form->addProtection(); ``` -É fortemente recomendado aplicar esta proteção aos formulários em uma parte administrativa de seu pedido que altera dados sensíveis. A estrutura protege contra um ataque CSRF gerando e validando o token de autenticação que é armazenado em uma sessão (o argumento é a mensagem de erro mostrada se o token tiver expirado). É por isso que é necessário ter uma sessão iniciada antes de exibir o formulário. Na parte administrativa do site, a sessão normalmente já é iniciada, devido ao login do usuário. -Caso contrário, inicie a sessão com o método `Nette\Http\Session::start()`. +Recomendamos proteger desta forma os formulários na parte administrativa do site, que alteram dados sensíveis na aplicação. O framework defende-se contra o ataque CSRF gerando e verificando um token de autorização, que é armazenado na sessão. Portanto, é necessário ter a sessão aberta antes de exibir o formulário. Na parte administrativa do site, a sessão geralmente já está iniciada devido ao login do utilizador. Caso contrário, inicie a sessão com o método `Nette\Http\Session::start()`. -Portanto, temos uma rápida introdução aos formulários em Nette. Tente procurar no diretório de [exemplos |https://github.com/nette/forms/tree/master/examples] na distribuição para obter mais inspiração. +Então, passamos por uma rápida introdução aos formulários em Nette. Tente dar uma olhada no diretório [examples|https://github.com/nette/forms/tree/master/examples] na distribuição, onde encontrará mais inspiração. diff --git a/forms/pt/validation.texy b/forms/pt/validation.texy index fe1ab31f0d..3e4f378d64 100644 --- a/forms/pt/validation.texy +++ b/forms/pt/validation.texy @@ -1,168 +1,179 @@ -Validação de Formulários +Validação de formulários ************************ -Controles requeridos .[#toc-required-controls] -============================================== +Controlos obrigatórios +====================== -Os controles são marcados conforme necessário com o método `setRequired()`, cujo argumento é o texto da [mensagem de erro |#Error Messages] que será exibida caso o usuário não a preencha. Se nenhum argumento for apresentado, é usada a mensagem de erro padrão. +Marcamos os controlos obrigatórios com o método `setRequired()`, cujo argumento é o texto da [mensagem de erro |#Mensagens de erro], que será exibida se o utilizador não preencher o controlo. Se o argumento não for fornecido, a mensagem de erro padrão será usada. ```php -$form->addText('name', 'Name:') - ->setRequired('Please fill your name.'); +$form->addText('name', 'Nome:') + ->setRequired('Por favor, insira o nome'); ``` -Regras .[#toc-rules] -==================== +Regras +====== -Acrescentamos regras de validação aos controles com o método `addRule()`. O primeiro parâmetro é a regra, o segundo é a [mensagem de erro |#Error Messages], e o terceiro é o argumento da regra de validação. +Adicionamos regras de validação aos controlos usando o método `addRule()`. O primeiro parâmetro é a regra, o segundo é o texto da [mensagem de erro |#Mensagens de erro] e o terceiro é o argumento da regra de validação. ```php -$form->addPassword('password', 'Password:') - ->addRule($form::MinLength, 'Password must be at least %d characters', 8); +$form->addPassword('password', 'Senha:') + ->addRule($form::MinLength, 'A senha deve ter pelo menos %d caracteres', 8); ``` -Nette vem com uma série de regras embutidas cujos nomes são constantes da classe `Nette\Forms\Form`: +**As regras de validação são verificadas apenas se o utilizador preencher o controlo.** -Podemos utilizar as seguintes regras para todos os controles: +Nette vem com uma série de regras predefinidas, cujos nomes são constantes da classe `Nette\Forms\Form`. Podemos usar estas regras para todos os controlos: -| constante | descrição | argumentos +| constante | descrição | tipo de argumento |------- -| `Required` | alias de `setRequired()` | - -| `Filled` | alias de `setRequired()` | - -| `Blank` | não deve ser preenchido | - +| `Required` | controlo obrigatório, alias para `setRequired()` | - +| `Filled` | controlo obrigatório, alias para `setRequired()` | - +| `Blank` | o controlo não deve ser preenchido | - | `Equal` | o valor é igual ao parâmetro | `mixed` | `NotEqual` | o valor não é igual ao parâmetro | `mixed` -| `IsIn` | o valor é igual a algum elemento da matriz | `array` -| `IsNotIn` | valor não é igual a nenhum elemento da matriz | `array` -| `Valid` | validação dos passes de entrada (para [condições |#conditions]) | - +| `IsIn` | o valor é igual a um dos itens no array | `array` +| `IsNotIn` | o valor não é igual a nenhum item no array | `array` +| `Valid` | o controlo está preenchido corretamente? (para [#Condições]) | - + -Para os controles `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` também podem ser usadas as seguintes regras: +Entradas de texto +----------------- -| `MinLength` | comprimento mínimo das cordas | `int` -| `MaxLength` | comprimento máximo das cordas | `int` -| `Length` | comprimento no alcance ou comprimento exato | par `[int, int]` ou `int` +Para os controlos `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`, `addFloat()`, algumas das seguintes regras também podem ser usadas: + +| `MinLength` | comprimento mínimo do texto | `int` +| `MaxLength` | comprimento máximo do texto | `int` +| `Length` | comprimento no intervalo ou comprimento exato | par `[int, int]` ou `int` | `Email` | endereço de e-mail válido | - -| `URL` | URL válida | - -| `Pattern` | corresponde ao padrão regular | `string` -| `PatternInsensitive` | como `Pattern`, mas não sensível a maiúsculas e minúsculas | `string` -| `Integer` | integer | - -| `Numeric` | alias de `Integer` | - -| `Float` | número inteiro ou de ponto flutuante | - -| `Min` | mínimo do valor inteiro | `int\|float` -| `Max` | máximo do valor inteiro | `int\|float` -| `Range` | valor na faixa | par `[int\|float, int\|float]` +| `URL` | URL absoluta | - +| `Pattern` | corresponde à expressão regular | `string` +| `PatternInsensitive` | como `Pattern`, mas insensível a maiúsculas/minúsculas | `string` +| `Integer` | valor inteiro | - +| `Numeric` | alias para `Integer` | - +| `Float` | número | - +| `Min` | valor mínimo do controlo numérico | `int\|float` +| `Max` | valor máximo do controlo numérico | `int\|float` +| `Range` | valor no intervalo | par `[int\|float, int\|float]` + +As regras de validação `Integer`, `Numeric` e `Float` convertem diretamente o valor para inteiro ou float, respetivamente. Além disso, a regra `URL` também aceita um endereço sem esquema (por exemplo, `nette.org`) e adiciona o esquema (`https://nette.org`). A expressão em `Pattern` e `PatternIcase` deve corresponder a todo o valor, ou seja, como se estivesse envolvida pelos caracteres `^` e `$`. -As regras `Integer`, `Numeric` a `Float` convertem automaticamente o valor em inteiro (ou float, respectivamente). Além disso, a regra `URL` também aceita um endereço sem esquema (por exemplo `nette.org`) e completa o esquema (`https://nette.org`). -As expressões em `Pattern` e `PatternInsensitive` devem ser válidas para todo o valor, ou seja, como se estivesse embrulhado nos caracteres `^` and `$`. -Para controles `addUpload()`, `addMultiUpload()` as seguintes regras também podem ser usadas: +Número de itens +--------------- -| `MaxFileSize` | tamanho máximo do arquivo | `int` -| `MimeType` | tipo MIME, aceita wildcards (`'video/*'`) | `string\|string[]` -| `Image` | o arquivo carregado é JPEG, PNG, GIF, WebP | - -| `Pattern` | o nome do arquivo corresponde à expressão regular | `string` -| `PatternInsensitive` | como `Pattern`, mas não sensível a maiúsculas e minúsculas | `string` +Para os controlos `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()`, as seguintes regras também podem ser usadas para limitar o número de itens selecionados ou ficheiros enviados: -Os sites `MimeType` e `Image` requerem extensão PHP `fileinfo`. Se um arquivo ou imagem é do tipo requerido é detectado por sua assinatura. A integridade do arquivo inteiro não é verificada. Você pode descobrir se uma imagem não está corrompida, por exemplo, ao tentar [carregá-la |http:request#toImage]. +| `MinLength` | número mínimo | `int` +| `MaxLength` | número máximo | `int` +| `Length` | número no intervalo ou número exato | par `[int, int]` ou `int` -Para os controles `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()` as seguintes regras também podem ser usadas para limitar o número de itens selecionados, respectivamente arquivos carregados: -| `MinLength` | contagem mínima | `int` -| `MaxLength` | contagem máxima | `int` -| `Length` | contagem no intervalo ou contagem exata | par `[int, int]` ou `int` +Upload de ficheiros +------------------- +Para os controlos `addUpload()`, `addMultiUpload()`, as seguintes regras também podem ser usadas: -Mensagens de erro .[#toc-error-messages] ----------------------------------------- +| `MaxFileSize` | tamanho máximo do ficheiro em bytes | `int` +| `MimeType` | tipo MIME, curingas permitidos (`'video/*'`) | `string\|string[]` +| `Image` | imagem JPEG, PNG, GIF, WebP, AVIF | - +| `Pattern` | nome do ficheiro corresponde à expressão regular | `string` +| `PatternInsensitive` | como `Pattern`, mas insensível a maiúsculas/minúsculas | `string` -Todas as regras predefinidas, exceto `Pattern` e `PatternInsensitive`, têm uma mensagem de erro padrão, portanto, ela é omitida. Entretanto, ao passar e formular todas as mensagens personalizadas, você tornará o formulário mais fácil de usar. +`MimeType` e `Image` exigem a extensão PHP `fileinfo`. Elas detetam se um ficheiro ou imagem é do tipo desejado com base na sua assinatura e **não verificam a integridade de todo o ficheiro.** Se uma imagem não está danificada pode ser verificado, por exemplo, tentando [carregá-la |http:request#toImage]. -Você pode alterar as mensagens padrão em [configuração |forms:configuration], modificando os textos na matriz `Nette\Forms\Validator::$messages` ou usando o [tradutor |rendering#translating]. -Os seguintes wildcards podem ser usados no texto das mensagens de erro: +Mensagens de erro +================= -| `%d` | substitui gradualmente as regras após os argumentos -| `%n$d` | substitui com o n-ésimo argumento de regra -| `%label` | substitui com etiqueta de campo (sem dois pontos) -| `%name` | substitui pelo nome do campo (ex: `name`) -| `%value` | substitui pelo valor inserido pelo usuário +Todas as regras predefinidas, exceto `Pattern` e `PatternInsensitive`, têm uma mensagem de erro padrão, então ela pode ser omitida. No entanto, fornecer e formular todas as mensagens sob medida tornará o formulário mais amigável ao utilizador. + +Pode alterar as mensagens padrão na [configuração|forms:configuration], editando os textos no array `Nette\Forms\Validator::$messages` ou usando um [tradutor |rendering#Tradução]. + +No texto das mensagens de erro, podem ser usadas as seguintes strings de placeholder: + +| `%d` | substitui sequencialmente pelos argumentos da regra +| `%n$d` | substitui pelo n-ésimo argumento da regra +| `%label` | substitui pelo rótulo do controlo (sem dois pontos) +| `%name` | substitui pelo nome do controlo (por exemplo, `name`) +| `%value` | substitui pelo valor inserido pelo utilizador ```php -$form->addText('name', 'Name:') - ->setRequired('Please fill in %label'); +$form->addText('name', 'Nome:') + ->setRequired('Preencha por favor %label'); $form->addInteger('id', 'ID:') - ->addRule($form::Range, 'at least %d and no more than %d', [5, 10]); + ->addRule($form::Range, 'pelo menos %d e no máximo %d', [5, 10]); $form->addInteger('id', 'ID:') - ->addRule($form::Range, 'no more than %2$d and at least %1$d', [5, 10]); + ->addRule($form::Range, 'no máximo %2$d e pelo menos %1$d', [5, 10]); ``` -Condições .[#toc-conditions] -============================ +Condições +========= -Além das regras de validação, podem ser estabelecidas condições. Elas são muito parecidas com regras, mas usamos `addRule()` em vez de `addCondition()` e, claro, deixamos isso sem uma mensagem de erro (a condição só pede): +Além das regras, também é possível adicionar condições. Elas são escritas de forma semelhante às regras, mas em vez de `addRule()`, usamos o método `addCondition()` e, obviamente, não fornecemos nenhuma mensagem de erro (a condição apenas pergunta): ```php -$form->addPassword('password', 'Password:') - // se a senha não for maior do que 8 caracteres ... +$form->addPassword('password', 'Senha:') + // se a senha não tiver mais de 8 caracteres ->addCondition($form::MaxLength, 8) - // ... então ele deve conter um número - ->addRule($form::Pattern, 'Deve conter número', '.*[0-9].*'); + // então deve conter um dígito + ->addRule($form::Pattern, 'Deve conter um dígito', '.*[0-9].*'); ``` -A condição pode ser ligada a um elemento diferente do atual usando `addConditionOn()`. O primeiro parâmetro é uma referência ao campo. No caso seguinte, o e-mail só será necessário se a caixa de seleção estiver marcada (ou seja, seu valor é `true`): +A condição também pode ser vinculada a outro controlo que não o atual, usando `addConditionOn()`. Como primeiro parâmetro, fornecemos uma referência ao controlo. Neste exemplo, o e-mail será obrigatório apenas se a caixa de seleção for marcada (o seu valor será true): ```php -$form->addCheckbox('newsletters', 'send me newsletters'); +$form->addCheckbox('newsletters', 'enviar-me newsletters'); -$form->addEmail('email', 'Email:') - // se a caixa de seleção estiver marcada ... - ->addConditionOn($form['newsletters'], $form::Igual, verdadeiro) - // ... exigir e-mail - ->setRequired('Preencha seu endereço de e-mail'); +$form->addEmail('email', 'E-mail:') + // se a caixa de seleção estiver marcada + ->addConditionOn($form['newsletters'], $form::Equal, true) + // então exija o e-mail + ->setRequired('Insira o endereço de e-mail'); ``` -As condições podem ser agrupadas em estruturas complexas com os métodos `elseCondition()` e `endCondition()`. +É possível criar estruturas complexas a partir de condições usando `elseCondition()` e `endCondition()`: ```php $form->addText(/* ... */) - ->addCondition(/* ... */) // se a primeira condição for cumprida - ->addConditionOn(/* ... */) // e a segunda condição em outro elemento também - ->addRule(/* ... */) // exigem esta regra - ->elseCondition() // se a segunda condição não for cumprida - ->addRule(/* ... */) // exigem estas regras + ->addCondition(/* ... */) // se a primeira condição for atendida + ->addConditionOn(/* ... */) // e a segunda condição em outro controlo + ->addRule(/* ... */) // exija esta regra + ->elseCondition() // se a segunda condição não for atendida + ->addRule(/* ... */) // exija estas regras ->addRule(/* ... */) ->endCondition() // voltamos à primeira condição ->addRule(/* ... */); ``` -Em Nette, é muito fácil reagir ao cumprimento ou não de uma condição no lado JavaScript usando o método `toggle()`, veja [Dynamic JavaScript |#Dynamic JavaScript]. +Em Nette, é muito fácil reagir ao cumprimento ou não cumprimento de uma condição também no lado do JavaScript usando o método `toggle()`, veja [#JavaScript dinâmico]. -Referências entre controles .[#toc-references-between-controls] -=============================================================== +Referência a outro controlo +=========================== -A regra ou argumento de condição pode ser uma referência a outro elemento. Por exemplo, você pode validar dinamicamente que o `text` tem tantos caracteres quanto o valor do campo `length`: +Como argumento de uma regra ou condição, também é possível passar outro controlo do formulário. A regra então usará o valor inserido posteriormente pelo utilizador no navegador. Desta forma, é possível, por exemplo, validar dinamicamente que o controlo `password` contém a mesma string que o controlo `password_confirm`: ```php -$form->addInteger('length'); -$form->addText('text') - ->addRule($form::Length, null, $form['length']); +$form->addPassword('password', 'Senha'); +$form->addPassword('password_confirm', 'Confirme a senha') + ->addRule($form::Equal, 'As senhas inseridas não coincidem', $form['password']); ``` -Regras e condições personalizadas .[#toc-custom-rules-and-conditions] -===================================================================== +Regras e condições personalizadas +================================= -Às vezes, chegamos a uma situação em que as regras de validação incorporadas em Nette não são suficientes e precisamos validar os dados do usuário à nossa própria maneira. Em Nette isto é muito fácil! +Ocasionalmente, chegamos a uma situação em que as regras de validação incorporadas em Nette não são suficientes e precisamos validar os dados do utilizador à nossa maneira. Em Nette, isso é muito simples! -Você pode passar qualquer ligação de retorno como primeiro parâmetro para os métodos `addRule()` ou `addCondition()`. A chamada de retorno aceita o próprio elemento como o primeiro parâmetro e retorna um valor booleano indicando se a validação foi bem sucedida. Ao adicionar uma regra usando `addRule()`, argumentos adicionais podem ser passados, e estes são então passados como o segundo parâmetro. +Aos métodos `addRule()` ou `addCondition()`, é possível passar qualquer callback como primeiro parâmetro. Ele recebe o próprio controlo como primeiro parâmetro e retorna um valor booleano indicando se a validação foi bem-sucedida. Ao adicionar uma regra usando `addRule()`, é possível fornecer argumentos adicionais, que são então passados como segundo parâmetro. -O conjunto personalizado de validadores pode assim ser criado como uma classe com métodos estáticos: +Podemos criar o nosso próprio conjunto de validadores como uma classe com métodos estáticos: ```php class MyValidators @@ -175,7 +186,7 @@ class MyValidators public static function validateEmailDomain(BaseControl $input, $domain) { - // validadores adicionais + // outros validadores } } ``` @@ -186,12 +197,12 @@ O uso é então muito simples: $form->addInteger('num') ->addRule( [MyValidators::class, 'validateDivisibility'], - 'The value must be a multiple of %d', + 'O valor deve ser um múltiplo de %d', 8, ); ``` -Regras de validação personalizadas também podem ser adicionadas ao JavaScript. A única exigência é que a regra deve ser um método estático. Seu nome para o validador JavaScript é criado concatenando o nome da classe sem contrabarra `\`, the underscore `_`, e o nome do método. Por exemplo, escreva `App\MyValidators::validateDivisibility` como `AppMyValidators_validateDivisibility` e adicione-o ao objeto `Nette.validators`: +Regras de validação personalizadas também podem ser adicionadas ao JavaScript. A condição é que a regra seja um método estático. O seu nome para o validador JavaScript é formado pela junção do nome da classe sem barras invertidas `\`, um sublinhado `_` e o nome do método. Por exemplo, `App\MyValidators::validateDivisibility` é escrito como `AppMyValidators_validateDivisibility` e adicionado ao objeto `Nette.validators`: ```js Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => { @@ -200,12 +211,12 @@ Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => ``` -Evento sobreValidar .[#toc-event-onvalidate] -============================================ +Evento onValidate +================= -Após o envio do formulário, a validação é feita verificando as regras individuais adicionadas por `addRule()` e depois ligando para o [evento |nette:glossary#Events] `onValidate`. Seu manipulador pode ser usado para validação adicional, normalmente para verificar a combinação correta de valores em múltiplos elementos do formulário. +Após o envio do formulário, a validação é realizada, onde as regras individuais adicionadas via `addRule()` são verificadas e, em seguida, o [evento |nette:glossary#Eventos] `onValidate` é disparado. O seu handler pode ser usado para validação adicional, tipicamente para verificar a combinação correta de valores em múltiplos controlos do formulário. -Se um erro for detectado, ele é passado para o formulário usando o método `addError()`. Isto pode ser chamado em um elemento específico ou diretamente no formulário. +Se um erro for detetado, passamos para o formulário usando o método `addError()`. Ele pode ser chamado num controlo específico ou diretamente no formulário. ```php protected function createComponentSignInForm(): Form @@ -219,16 +230,16 @@ protected function createComponentSignInForm(): Form public function validateSignInForm(Form $form, \stdClass $data): void { if ($data->foo > 1 && $data->bar > 5) { - $form->addError('This combination is not possible.'); + $form->addError('Esta combinação não é possível.'); } } ``` -Erros de processamento .[#toc-processing-errors] -================================================ +Erros durante o processamento +============================= -Em muitos casos, descobrimos um erro quando estamos processando um formulário válido, por exemplo, quando escrevemos uma nova entrada no banco de dados e encontramos uma chave duplicada. Neste caso, passamos o erro de volta para o formulário usando o método `addError()`. Isto pode ser chamado tanto em um item específico ou diretamente no formulário: +Em muitos casos, descobrimos um erro apenas no momento em que estamos a processar um formulário válido, por exemplo, ao inserir um novo item no banco de dados e encontrar uma duplicidade de chaves. Nesse caso, passamos novamente o erro para o formulário usando o método `addError()`. Ele pode ser chamado num controlo específico ou diretamente no formulário: ```php try { @@ -238,72 +249,71 @@ try { } catch (Nette\Security\AuthenticationException $e) { if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) { - $form->addError('Invalid password.'); + $form->addError('Senha inválida.'); } } ``` -Se possível, recomendamos adicionar o erro diretamente ao elemento do formulário, pois ele aparecerá ao lado dele ao utilizar o renderizador padrão. +Se possível, recomendamos anexar o erro diretamente ao controlo do formulário, pois ele será exibido ao lado dele ao usar o renderizador padrão. ```php -$form['date']->addError('Sorry, this date is already taken.'); +$form['date']->addError('Desculpe, mas esta data já está ocupada.'); ``` -Você pode ligar para `addError()` repetidamente para passar múltiplas mensagens de erro a um formulário ou elemento. Você as recebe com `getErrors()`. +Pode chamar `addError()` repetidamente para passar várias mensagens de erro ao formulário ou controlo. Pode obtê-las usando `getErrors()`. -Observe que `$form->getErrors()` retorna um resumo de todas as mensagens de erro, mesmo aquelas transmitidas diretamente a elementos individuais, e não apenas diretamente ao formulário. As mensagens de erro passadas apenas para o formulário são recuperadas via `$form->getOwnErrors()`. +Atenção, `$form->getErrors()` retorna um resumo de todas as mensagens de erro, incluindo aquelas que foram passadas diretamente para controlos individuais, não apenas diretamente para o formulário. Mensagens de erro passadas apenas para o formulário podem ser obtidas via `$form->getOwnErrors()`. -Modificando valores de entrada .[#toc-modifying-input-values] -============================================================= +Modificação da entrada +====================== -Usando o método `addFilter()`, podemos modificar o valor inserido pelo usuário. Neste exemplo, toleraremos e removeremos espaços no código postal: +Usando o método `addFilter()`, podemos modificar o valor inserido pelo utilizador. Neste exemplo, toleraremos e removeremos espaços no código postal: ```php -$form->addText('zip', 'Postcode:') +$form->addText('zip', 'Código Postal:') ->addFilter(function ($value) { - return str_replace(' ', '', $value); // remover espaços do código postal + return str_replace(' ', '', $value); // removemos espaços do código postal }) - ->addRule($form::Pattern, 'O código postal não tem cinco dígitos', '\d{5}'); + ->addRule($form::Pattern, 'Código Postal não está no formato de cinco dígitos', '\d{5}'); ``` -O filtro é incluído entre as regras e condições de validação e, portanto, depende da ordem dos métodos, ou seja, o filtro e a regra são chamados na mesma ordem que é a ordem dos métodos `addFilter()` e `addRule()`. +O filtro é integrado entre as regras de validação e condições, portanto, a ordem dos métodos importa, ou seja, o filtro e a regra são chamados na mesma ordem que os métodos `addFilter()` e `addRule()`. -Validação JavaScript .[#toc-javascript-validation] -================================================== +Validação JavaScript +==================== -A linguagem das regras e condições de validação é poderosa. Embora todas as construções funcionem tanto no lado do servidor quanto no lado do cliente, em JavaScript. As regras são transferidas em atributos HTML `data-nette-rules` como JSON. -A validação em si é manipulada por outro script, que anilha todos os eventos `submit` do formulário, itera sobre todas as entradas e executa as respectivas validações. +A linguagem para formular condições e regras é muito poderosa. Todas as construções funcionam tanto no lado do servidor quanto no lado do JavaScript. Elas são transferidas em atributos HTML `data-nette-rules` como JSON. A validação em si é então realizada por um script que captura o evento `submit` do formulário, percorre os controlos individuais e executa a validação apropriada. -Este roteiro é `netteForms.js`, que está disponível em várias fontes possíveis: +Esse script é `netteForms.js` e está disponível em várias fontes possíveis: -Você pode incorporar o script diretamente na página HTML a partir do CDN: +Pode inserir o script diretamente na página HTML a partir de um CDN: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Ou copiar localmente para a pasta pública do projeto (por exemplo, de `vendor/nette/forms/src/assets/netteForms.min.js`): +Ou copiá-lo localmente para a pasta pública do projeto (por exemplo, de `vendor/nette/forms/src/assets/netteForms.min.js`): ```latte <script src="/path/to/netteForms.min.js"></script> ``` -Ou instalar via [npm |https://www.npmjs.com/package/nette-forms]: +Ou instalar via [npm|https://www.npmjs.com/package/nette-forms]: ```shell npm install nette-forms ``` -E depois carregar e correr: +E, em seguida, carregar e executar: ```js import netteForms from 'nette-forms'; netteForms.initOnLoad(); ``` -Alternativamente, você pode carregá-lo diretamente da pasta `vendor`: +Alternativamente, pode carregá-lo diretamente da pasta `vendor`: ```js import netteForms from '../path/to/vendor/nette/forms/src/assets/netteForms.js'; @@ -311,10 +321,10 @@ netteForms.initOnLoad(); ``` -JavaScript dinâmico .[#toc-dynamic-javascript] -============================================== +JavaScript dinâmico +=================== -Você só quer mostrar os campos de endereço se o usuário optar por enviar a mercadoria pelo correio? Não há problema. A chave é um par de métodos `addCondition()` & `toggle()`: +Quer exibir os campos para inserir o endereço apenas se o utilizador escolher enviar o produto pelo correio? Sem problemas. A chave é o par de métodos `addCondition()` & `toggle()`: ```php $form->addCheckbox('send_it') @@ -322,25 +332,25 @@ $form->addCheckbox('send_it') ->toggle('#address-container'); ``` -Este código diz que quando a condição for preenchida, ou seja, quando a caixa de seleção for marcada, o elemento HTML `#address-container` estará visível. E vice versa. Assim, colocamos os elementos do formulário com o endereço do destinatário em um container com essa identificação, e quando a caixa de seleção é clicada, eles são escondidos ou mostrados. Isto é tratado pelo script `netteForms.js`. +Este código diz que quando a condição é atendida, ou seja, quando a caixa de seleção está marcada, o elemento HTML `#address-container` será visível. E vice-versa. Assim, colocamos os controlos do formulário com o endereço do destinatário num contêiner com este ID e, ao clicar na caixa de seleção, eles serão ocultados ou exibidos. Isso é garantido pelo script `netteForms.js`. -Qualquer seletor pode ser passado como argumento para o método `toggle()`. Por razões históricas, uma cadeia alfanumérica sem outros caracteres especiais é tratada como um elemento de identificação, o mesmo que se fosse precedida pelo `#` character. The second optional parameter allows us to reverse the behavior, i.e. if we used `toggle('#address-container', false)`, o elemento só seria exibido se a caixa de seleção fosse desmarcada. +Como argumento do método `toggle()`, é possível passar qualquer seletor. Por razões históricas, uma string alfanumérica sem outros caracteres especiais é entendida como o ID do elemento, ou seja, da mesma forma que se fosse precedida pelo caractere `#`. O segundo parâmetro opcional permite inverter o comportamento, ou seja, se usássemos `toggle('#address-container', false)`, o elemento seria exibido apenas se a caixa de seleção não estivesse marcada. -A implementação padrão do JavaScript muda a propriedade `hidden` para os elementos. Entretanto, podemos mudar facilmente o comportamento, por exemplo, adicionando uma animação. Basta anular o método `Nette.toggle` em JavaScript com uma solução personalizada: +A implementação padrão em JavaScript altera a propriedade `hidden` dos elementos. No entanto, podemos facilmente alterar o comportamento, por exemplo, adicionando uma animação. Basta sobrescrever o método `Nette.toggle` em JavaScript com a sua própria solução: ```js Nette.toggle = (selector, visible, srcElement, event) => { document.querySelectorAll(selector).forEach((el) => { - // hide or show 'el' according to the value of 'visible' + // ocultamos ou exibimos 'el' de acordo com o valor 'visible' }); }; ``` -Desabilitando a Validação .[#toc-disabling-validation] -====================================================== +Desativação da validação +======================== -Em certos casos, é necessário desativar a validação. Se um botão submeter não deve executar a validação após a submissão (por exemplo *Cancelar* ou *Prever* botão), você pode desabilitar a validação ligando para `$submit->setValidationScope([])`. Você também pode validar o formulário parcialmente, especificando os itens a serem validados. +Às vezes, pode ser útil desativar a validação. Se o pressionamento de um botão de envio não deve realizar a validação (adequado para botões *Cancelar* ou *Visualizar*), desativamo-la com o método `$submit->setValidationScope([])`. Se deve realizar apenas validação parcial, podemos especificar quais campos ou contêineres de formulário devem ser validados. ```php $form->addText('name') @@ -352,15 +362,15 @@ $details->addInteger('age') $details->addInteger('age2') ->setRequired('age2'); -$form->addSubmit('send1'); // valida o formulário completo +$form->addSubmit('send1'); // Valida o formulário inteiro $form->addSubmit('send2') ->setValidationScope([]); // Não valida nada $form->addSubmit('send3') - ->setValidationScope([$form['name']]); // valida apenas o campo 'name' (nome) + ->setValidationScope([$form['name']]); // Valida apenas o controlo name $form->addSubmit('send4') - ->setValidationScope([$form['details']['age']]); // Valida apenas o campo 'age + ->setValidationScope([$form['details']['age']]); // Valida apenas o controlo age $form->addSubmit('send5') - ->setValidationScope([$form['details'])); // valida 'details' container + ->setValidationScope([$form['details']]); // Valida o contêiner details ``` -[O evento onValidate |#Event onValidate] no formulário é sempre invocado e não é afetado pelo `setValidationScope`. `onValidate` O evento[onValidate |#Event onValidate] no container é invocado somente quando este container é especificado para validação parcial. +`setValidationScope` não afeta o [#evento onValidate] no formulário, que será chamado sempre. O evento `onValidate` num contêiner será disparado apenas se este contêiner estiver marcado para validação parcial. diff --git a/forms/ro/@home.texy b/forms/ro/@home.texy index c63aaeb3a8..8890cf4096 100644 --- a/forms/ro/@home.texy +++ b/forms/ro/@home.texy @@ -1,31 +1,31 @@ -Formulare -********* +Nette Forms +*********** <div class=perex> -Nette Forms a revoluționat crearea de formulare web. Tot ce trebuia să faci era să scrii câteva linii clare de cod și aveai un formular, inclusiv randare, JavaScript și validare pe server, plus securitate de top în industrie. Să vedem cum +Nette Forms a revoluționat crearea formularelor web. Dintr-o dată, a fost suficient să scrieți câteva rânduri de cod clare și aveați un formular complet, inclusiv randare, validare JavaScript și pe server, și, în plus, extrem de securizat. Vom arăta cum: -- creați formulare prietenoase -- validați datele trimise -- desenați elementele exact așa cum este necesar +- să creați formulare prietenoase +- să validați datele trimise +- să randati elementele exact după nevoie </div> -Cu Nette Forms, puteți diminua sarcinile de rutină, cum ar fi scrierea validării (atât pe partea serverului, cât și pe partea clientului) și minimizarea probabilității de erori și probleme de securitate. +Utilizând Nette Forms, veți evita o serie întreagă de sarcini de rutină, cum ar fi scrierea validării (în plus, dublă, pe partea de server și client), veți minimiza probabilitatea apariției erorilor și a găurilor de securitate. -Puteți utiliza formularele fie ca parte a aplicației Nette (de exemplu, în prezentatori), fie de sine stătător. Deoarece utilizarea este ușor diferită în ambele cazuri, am pregătit pentru dumneavoastră instrucțiuni separate: +Formularele pot fi utilizate fie ca parte a Nette Application (adică în presenteri), fie complet independent. Deoarece în ambele cazuri utilizarea diferă puțin, am pregătit pentru dvs. două tutoriale: <div class="wiki-buttons"> -<div> "Forms in presenters .[wiki-button]":in-presenter </div> -<div> "Forms standalone .[wiki-button]":standalone </div> +<div> "Formulare în presenteri .[wiki-button]":in-presenter </div> +<div> "Formulare independent .[wiki-button]":standalone </div> </div> -Instalare .[#toc-installation] ------------------------------- +Instalare +--------- -Descărcați și instalați pachetul folosind [Composer |best-practices:composer]: +Descărcați și instalați biblioteca folosind [Composer|best-practices:composer]: ```shell composer require nette/forms diff --git a/forms/ro/@left-menu.texy b/forms/ro/@left-menu.texy index 0bd08676b8..a361d6d5de 100644 --- a/forms/ro/@left-menu.texy +++ b/forms/ro/@left-menu.texy @@ -1,14 +1,14 @@ -Formulare -********* -- [Prezentare generală |@home] -- [Formularele din prezentări |in-presenter] -- [Formularele de sine stătătoare |standalone] -- [Controale pentru formulare |controls] +Nette Forms +*********** +- [Introducere |@home] +- [Formulare în presenteri|in-presenter] +- [Formulare independent|standalone] +- [Elemente de formular |controls] - [Validare |validation] -- [Redare |rendering] -- [Configurație |Configuration] +- [Randare |rendering] +- [Configurație |configuration] Lectură suplimentară ******************** -- [Cele mai bune practici |best-practices:] +- [Tutoriale și proceduri |best-practices:] diff --git a/forms/ro/@meta.texy b/forms/ro/@meta.texy new file mode 100644 index 0000000000..9c744b37d6 --- /dev/null +++ b/forms/ro/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentație Nette}} diff --git a/forms/ro/configuration.texy b/forms/ro/configuration.texy index f574240d03..bdbed9ef7a 100644 --- a/forms/ro/configuration.texy +++ b/forms/ro/configuration.texy @@ -2,7 +2,7 @@ Configurarea formularelor ************************* .[perex] -Puteți modifica [mesajele de eroare |validation] implicite ale [formularelor |validation] în configurare. +În configurație se pot modifica [mesajele de eroare implicite ale formularelor|validation]. ```neon forms: @@ -30,4 +30,32 @@ forms: Nette\Forms\Controls\CsrfProtection::Protection: 'Your session has expired. Please return to the home page and try again.' ``` -Dacă nu utilizați întregul cadru și, prin urmare, nici măcar fișierele de configurare, puteți modifica mesajele de eroare implicite direct în câmpul `Nette\Forms\Validator::$messages`. +Aici este traducerea în română: + +```neon +forms: + messages: + Equal: 'Introduceți %s.' + NotEqual: 'Această valoare nu ar trebui să fie %s.' + Filled: 'Acest câmp este obligatoriu.' + Blank: 'Acest câmp ar trebui să fie gol.' + MinLength: 'Introduceți cel puțin %d caractere.' + MaxLength: 'Introduceți maximum %d caractere.' + Length: 'Introduceți o valoare între %d și %d caractere.' + Email: 'Introduceți o adresă de e-mail validă.' + URL: 'Introduceți un URL valid.' + Integer: 'Introduceți un număr întreg valid.' + Float: 'Introduceți un număr valid.' + Min: 'Introduceți o valoare mai mare sau egală cu %d.' + Max: 'Introduceți o valoare mai mică sau egală cu %d.' + Range: 'Introduceți o valoare între %d și %d.' + MaxFileSize: 'Dimensiunea fișierului încărcat poate fi de maximum %d octeți.' + MaxPostSize: 'Datele încărcate depășesc limita de %d octeți.' + MimeType: 'Fișierul încărcat nu este în formatul așteptat.' + Image: 'Fișierul încărcat trebuie să fie o imagine în format JPEG, GIF, PNG, WebP sau AVIF.' + Nette\Forms\Controls\SelectBox::Valid: 'Selectați o opțiune validă.' + Nette\Forms\Controls\UploadControl::Valid: 'A apărut o eroare la încărcarea fișierului.' + Nette\Forms\Controls\CsrfProtection::Protection: 'Sesiunea dvs. a expirat. Vă rugăm să reveniți la pagina principală și să încercați din nou.' +``` + +Dacă nu utilizați întregul framework și deci nici fișierele de configurare, puteți modifica mesajele de eroare implicite direct în array-ul `Nette\Forms\Validator::$messages`. diff --git a/forms/ro/controls.texy b/forms/ro/controls.texy index e099cf0568..4305128641 100644 --- a/forms/ro/controls.texy +++ b/forms/ro/controls.texy @@ -1,253 +1,346 @@ -Controale de formular -********************* +Elemente de formular +******************** .[perex] -Prezentare generală a controalelor de formular încorporate. +Prezentare generală a elementelor de formular standard. -addText(string|int $name, $label=null): TextInput .[method] -=========================================================== +addText(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +================================================================================================== -Adaugă un câmp de text cu o singură linie (clasa [TextInput |api:Nette\Forms\Controls\TextInput]). Dacă utilizatorul nu completează câmpul, acesta returnează un șir de caractere gol `''`, sau se poate folosi `setNullable()` pentru a schimba acest câmp și a returna `null`. +Adaugă un câmp text pe o singură linie (clasa [TextInput |api:Nette\Forms\Controls\TextInput]). Dacă utilizatorul nu completează câmpul, returnează un șir gol `''`, sau folosind `setNullable()` se poate specifica să returneze `null`. ```php -$form->addText('name', 'Name:') +$form->addText('name', 'Nume:') ->setRequired() ->setNullable(); ``` -Validează automat UTF-8, taie spațiile albe din stânga și din dreapta și elimină întreruperile de linie care ar putea fi trimise de un atacator. +Validează automat UTF-8, elimină spațiile de la început și sfârșit și elimină sfârșiturile de linie pe care un atacator le-ar putea trimite. Parametrul `$cols` este depreciat și nu este utilizat. -Lungimea maximă poate fi limitată folosind `setMaxLength()`. [AddFilter() |validation#Modifying Input Values] vă permite să modificați valoarea introdusă de utilizator. +Lungimea maximă poate fi limitată folosind `setMaxLength()`. Modificarea valorii introduse de utilizator este posibilă prin [addFilter() |validation#Modificarea intrării]. -Utilizați `setHtmlType()` pentru a schimba [caracterul |https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types] elementului de intrare în `search`, `tel`, `url`, `range`, `date`, `datetime-local`, `month`, `time`, `week`, `color`. În locul tipurilor `number` și `email`, vă recomandăm să utilizați [addInteger |#addInteger] și [addEmail |#addEmail], care oferă validare pe server. +Folosind `setHtmlType()` se poate schimba caracterul vizual al câmpului text la tipuri precum `search`, `tel` sau `url` vezi [specificație|https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types]. Rețineți că schimbarea tipului este doar vizuală și nu înlocuiește funcția de validare. Pentru tipul `url` este recomandat să se adauge o [regulă de validare specifică URL |validation#Intrări de text]. -```php -$form->addText('color', 'Choose color:') - ->setHtmlType('color') - ->addRule($form::Pattern, 'invalid value', '[0-9a-f]{6}'); -``` +.[note] +Pentru alte tipuri de intrări, cum ar fi `number`, `range`, `email`, `date`, `datetime-local`, `time` și `color`, utilizați metode specializate precum [#addInteger], [#addFloat], [#addEmail] [#addDate], [#addTime], [#addDateTime] și [#addColor], care asigură validarea pe server. Tipurile `month` și `week` nu sunt încă pe deplin suportate în toate browserele. -Pentru element se poate seta așa-numita "empty-value", care este ceva de genul valorii implicite, dar, dacă utilizatorul nu o suprascrie, returnează un șir de caractere gol sau `null`. +Elementului i se poate seta așa-numita empty-value, care este ceva asemănător valorii implicite, dar dacă utilizatorul nu o schimbă, elementul returnează un șir gol sau `null`. ```php -$form->addText('phone', 'Phone:') +$form->addText('phone', 'Telefon:') ->setHtmlType('tel') - ->setEmptyValue('+420'); + ->setEmptyValue('+40'); ``` addTextArea(string|int $name, $label=null): TextArea .[method] ============================================================== -Adaugă un câmp de text cu mai multe linii (clasa [TextArea |api:Nette\Forms\Controls\TextArea]). Dacă utilizatorul nu completează câmpul, acesta returnează un șir de caractere gol `''`, sau se poate folosi `setNullable()` pentru a schimba acest câmp și a returna `null`. +Adaugă un câmp pentru introducerea textului multilinie (clasa [TextArea |api:Nette\Forms\Controls\TextArea]). Dacă utilizatorul nu completează câmpul, returnează un șir gol `''`, sau folosind `setNullable()` se poate specifica să returneze `null`. ```php -$form->addTextArea('note', 'Note:') - ->addRule($form::MaxLength, 'Your note is way too long', 10000); +$form->addTextArea('note', 'Notă:') + ->addRule($form::MaxLength, 'Nota este prea lungă', 10000); ``` -Validează automat UTF-8 și normalizează întreruperile de linie la `\n`. Spre deosebire de un câmp de introducere a textului pe o singură linie, nu taie spațiile albe. +Validează automat UTF-8 și normalizează separatorii de linie la `\n`. Spre deosebire de câmpul de intrare pe o singură linie, nu are loc nicio eliminare a spațiilor. -Lungimea maximă poate fi limitată folosind `setMaxLength()`. [AddFilter() |validation#Modifying Input Values] vă permite să modificați valoarea introdusă de utilizator. Puteți seta așa-numita valoare goală folosind `setEmptyValue()`. +Lungimea maximă poate fi limitată folosind `setMaxLength()`. Modificarea valorii introduse de utilizator este posibilă prin [addFilter() |validation#Modificarea intrării]. Se poate seta așa-numita empty-value folosind `setEmptyValue()`. addInteger(string|int $name, $label=null): TextInput .[method] ============================================================== -Adaugă un câmp de intrare pentru numere întregi (clasa [TextInput |api:Nette\Forms\Controls\TextInput]). Returnează fie un număr întreg, fie `null` dacă utilizatorul nu introduce nimic. +Adaugă un câmp pentru introducerea unui număr întreg (clasa [TextInput |api:Nette\Forms\Controls\TextInput]). Returnează fie un integer, fie `null`, dacă utilizatorul nu introduce nimic. + +```php +$form->addInteger('year', 'An:') + ->addRule($form::Range, 'Anul trebuie să fie în intervalul de la %d la %d.', [1900, 2023]); +``` + +Elementul se randează ca `<input type="number">`. Folosind metoda `setHtmlType()` se poate schimba tipul la `range` pentru afișare sub formă de glisor, sau la `text`, dacă preferați un câmp text standard fără comportamentul special al tipului `number`. + + +addFloat(string|int $name, $label=null): TextInput .[method]{data-version:3.1.12} +================================================================================= + +Adaugă un câmp pentru introducerea unui număr zecimal (clasa [TextInput |api:Nette\Forms\Controls\TextInput]). Returnează fie un float, fie `null`, dacă utilizatorul nu introduce nimic. ```php -$form->addInteger('level', 'Level:') +$form->addFloat('level', 'Nivel:') ->setDefaultValue(0) - ->addRule($form::Range, 'Level must be between %d and %d.', [0, 100]); + ->addRule($form::Range, 'Nivelul trebuie să fie în intervalul de la %d la %d.', [0, 100]); ``` +Elementul se randează ca `<input type="number">`. Folosind metoda `setHtmlType()` se poate schimba tipul la `range` pentru afișare sub formă de glisor, sau la `text`, dacă preferați un câmp text standard fără comportamentul special al tipului `number`. + +Nette și browserul Chrome acceptă atât virgula, cât și punctul ca separator zecimal. Pentru ca această funcționalitate să fie disponibilă și în Firefox, se recomandă setarea atributului `lang` fie pentru elementul respectiv, fie pentru întreaga pagină, de exemplu `<html lang="ro">`. + -addEmail(string|int $name, $label=null): TextInput .[method] -============================================================ +addEmail(string|int $name, $label=null, int $maxLength=255): TextInput .[method] +================================================================================ -Adaugă un câmp de adresă de e-mail cu verificare a validității (clasa [TextInput |api:Nette\Forms\Controls\TextInput]). Dacă utilizatorul nu completează câmpul, acesta returnează un șir de caractere gol `''`, sau se poate folosi `setNullable()` pentru a schimba acest câmp și a returna `null`. +Adaugă un câmp pentru introducerea unei adrese de e-mail (clasa [TextInput |api:Nette\Forms\Controls\TextInput]). Dacă utilizatorul nu completează câmpul, returnează un șir gol `''`, sau folosind `setNullable()` se poate specifica să returneze `null`. ```php -$form->addEmail('email', 'Email:'); +$form->addEmail('email', 'E-mail:'); ``` -Verifică dacă valoarea este o adresă de e-mail validă. Nu se verifică dacă domeniul există cu adevărat, ci doar sintaxa. Validează automat UTF-8, taie spațiile albe din stânga și din dreapta. +Verifică dacă valoarea este o adresă de e-mail validă. Nu se verifică dacă domeniul există efectiv, se verifică doar sintaxa. Validează automat UTF-8, elimină spațiile de la început și sfârșit. -Lungimea maximă poate fi limitată folosind `setMaxLength()`. [AddFilter() |validation#Modifying Input Values] vă permite să modificați valoarea introdusă de utilizator. Puteți seta așa-numita "valoare goală" folosind `setEmptyValue()`. +Lungimea maximă poate fi limitată folosind `setMaxLength()`. Modificarea valorii introduse de utilizator este posibilă prin [addFilter() |validation#Modificarea intrării]. Se poate seta așa-numita empty-value folosind `setEmptyValue()`. -addPassword(string|int $name, $label=null): TextInput .[method] -=============================================================== +addPassword(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +====================================================================================================== -Adaugă câmpul de parolă (clasa [TextInput |api:Nette\Forms\Controls\TextInput]). +Adaugă un câmp pentru introducerea parolei (clasa [TextInput |api:Nette\Forms\Controls\TextInput]). Parametrul `$cols` este depreciat și nu este utilizat. ```php -$form->addPassword('password', 'Password:') +$form->addPassword('password', 'Parolă:') ->setRequired() - ->addRule($form::MinLength, 'Password has to be at least %d characters long', 8) - ->addRule($form::Pattern, 'Password must contain a number', '.*[0-9].*'); + ->addRule($form::MinLength, 'Parola trebuie să aibă cel puțin %d caractere', 8) + ->addRule($form::Pattern, 'Trebuie să conțină o cifră', '.*[0-9].*'); ``` -La retrimiterea formularului, câmpul de intrare va fi gol. Validează automat UTF-8, taie spațiile albe din stânga și din dreapta și elimină întreruperile de linie care ar putea fi trimise de un atacator. +La reafișarea formularului, câmpul va fi gol. Validează automat UTF-8, elimină spațiile de la început și sfârșit și elimină sfârșiturile de linie pe care un atacator le-ar putea trimite. addCheckbox(string|int $name, $caption=null): Checkbox .[method] ================================================================ -Adaugă o casetă de selectare (clasa [Checkbox |api:Nette\Forms\Controls\Checkbox]). Câmpul returnează fie `true`, fie `false`, în funcție de faptul dacă este bifat sau nu. +Adaugă o căsuță de bifat (clasa [Checkbox |api:Nette\Forms\Controls\Checkbox]). Returnează valoarea `true` sau `false`, în funcție de dacă este bifată. ```php -$form->addCheckbox('agree', 'I agree with terms') - ->setRequired('You must agree with our terms'); +$form->addCheckbox('agree', 'Sunt de acord cu termenii') + ->setRequired('Este necesar să fiți de acord cu termenii'); ``` -addCheckboxList(string|int $name, $label=null, array $items=null): CheckboxList .[method] -========================================================================================= +addCheckboxList(string|int $name, $label=null, ?array $items=null): CheckboxList .[method] +========================================================================================== -Adaugă o listă de căsuțe de bifat pentru selectarea mai multor elemente (clasa [CheckboxList |api:Nette\Forms\Controls\CheckboxList]). Returnează matricea de chei a elementelor selectate. Metoda `getSelectedItems()` returnează valori în loc de chei. +Adaugă căsuțe de bifat pentru selectarea mai multor elemente (clasa [CheckboxList |api:Nette\Forms\Controls\CheckboxList]). Returnează un array al cheilor elementelor selectate. Metoda `getSelectedItems()` returnează valorile în loc de chei. ```php -$form->addCheckboxList('colors', 'Colors:', [ - 'r' => 'red', - 'g' => 'green', - 'b' => 'blue', +$form->addCheckboxList('colors', 'Culori:', [ + 'r' => 'roșu', + 'g' => 'verde', + 'b' => 'albastru', ]); ``` -Trecem matricea de elemente ca al treilea parametru sau prin metoda `setItems()`. +Array-ul de elemente oferite îl transmitem ca al treilea parametru sau prin metoda `setItems()`. -Puteți utiliza `setDisabled(['r', 'g'])` pentru a dezactiva elemente individuale. +Folosind `setDisabled(['r', 'g'])` se pot dezactiva elemente individuale. -Elementul verifică în mod automat dacă nu a existat nicio falsificare și dacă elementele selectate sunt într-adevăr unul dintre cele oferite și nu au fost dezactivate. Metoda `getRawValue()` poate fi utilizată pentru a prelua elementele trimise fără această verificare importantă. +Elementul verifică automat că nu a avut loc o falsificare și că elementele selectate sunt într-adevăr unele dintre cele oferite și nu au fost dezactivate. Prin metoda `getRawValue()` se pot obține elementele trimise fără această verificare importantă. -În cazul în care sunt stabilite valori implicite, se verifică, de asemenea, dacă acestea sunt unul dintre elementele oferite, în caz contrar se aruncă o excepție. Această verificare poate fi dezactivată cu `checkDefaultValue(false)`. +La setarea elementelor selectate implicit, verifică de asemenea că acestea sunt unele dintre cele oferite, altfel aruncă o excepție. Această verificare poate fi dezactivată folosind `checkDefaultValue(false)`. +Dacă trimiteți formularul prin metoda `GET`, puteți alege un mod mai compact de transmitere a datelor, care economisește dimensiunea query string-ului. Se activează prin setarea atributului HTML al formularului: + +```php +$form->setHtmlAttribute('data-nette-compact'); +``` -addRadioList(string|int $name, $label=null, array $items=null): RadioList .[method] -=================================================================================== -Adaugă butoane radio (clasa [RadioList |api:Nette\Forms\Controls\RadioList]). Returnează cheia elementului selectat sau `null` dacă utilizatorul nu a selectat nimic. Metoda `getSelectedItem()` returnează o valoare în loc de o cheie. +addRadioList(string|int $name, $label=null, ?array $items=null): RadioList .[method] +==================================================================================== + +Adaugă butoane radio (clasa [RadioList |api:Nette\Forms\Controls\RadioList]). Returnează cheia elementului selectat, sau `null`, dacă utilizatorul nu a selectat nimic. Metoda `getSelectedItem()` returnează valoarea în loc de cheie. ```php $sex = [ - 'm' => 'male', - 'f' => 'female', + 'm' => 'bărbat', + 'f' => 'femeie', ]; -$form->addRadioList('gender', 'Gender:', $sex); +$form->addRadioList('gender', 'Sex:', $sex); ``` -Trecem matricea de elemente ca al treilea parametru, sau prin metoda `setItems()`. +Array-ul de elemente oferite îl transmitem ca al treilea parametru sau prin metoda `setItems()`. -Puteți utiliza `setDisabled(['m'])` pentru a dezactiva elemente individuale. +Folosind `setDisabled(['m', 'f'])` se pot dezactiva elemente individuale. -Elementul verifică automat dacă nu a existat nicio falsificare și dacă elementul selectat este de fapt unul dintre cele oferite și nu a fost dezactivat. Metoda `getRawValue()` poate fi utilizată pentru a prelua elementul propus fără această verificare importantă. +Elementul verifică automat că nu a avut loc o falsificare și că elementul selectat este într-adevăr unul dintre cele oferite și nu a fost dezactivat. Prin metoda `getRawValue()` se poate obține elementul trimis fără această verificare importantă. -În cazul în care este stabilită o valoare implicită, se verifică, de asemenea, dacă este unul dintre elementele oferite, în caz contrar se aruncă o excepție. Această verificare poate fi dezactivată cu `checkDefaultValue(false)`. +La setarea elementului selectat implicit, verifică de asemenea că acesta este unul dintre cele oferite, altfel aruncă o excepție. Această verificare poate fi dezactivată folosind `checkDefaultValue(false)`. -addSelect(string|int $name, $label=null, array $items=null): SelectBox .[method] -================================================================================ +addSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): SelectBox .[method] +================================================================================================== -Adaugă caseta de selectare (clasa [SelectBox |api:Nette\Forms\Controls\SelectBox]). Returnează cheia elementului selectat sau `null` dacă utilizatorul nu a selectat nimic. Metoda `getSelectedItem()` returnează o valoare în loc de o cheie. +Adaugă un select box (clasa [SelectBox |api:Nette\Forms\Controls\SelectBox]). Returnează cheia elementului selectat, sau `null`, dacă utilizatorul nu a selectat nimic. Metoda `getSelectedItem()` returnează valoarea în loc de cheie. ```php $countries = [ - 'CZ' => 'Czech republic', - 'SK' => 'Slovakia', - 'GB' => 'United Kingdom', + 'RO' => 'România', + 'MD' => 'Moldova', + 'GB' => 'Marea Britanie', ]; -$form->addSelect('country', 'Country:', $countries) - ->setDefaultValue('SK'); +$form->addSelect('country', 'Țara:', $countries) + ->setDefaultValue('RO'); ``` -Trecem matricea de elemente ca al treilea parametru, sau prin metoda `setItems()`. Rețeaua de elemente poate fi, de asemenea, bidimensională: +Array-ul de elemente oferite îl transmitem ca al treilea parametru sau prin metoda `setItems()`. Elementele pot fi și un array bidimensional: ```php $countries = [ 'Europe' => [ - 'CZ' => 'Czech republic', - 'SK' => 'Slovakia', - 'GB' => 'United Kingdom', + 'RO' => 'România', + 'MD' => 'Moldova', + 'GB' => 'Marea Britanie', ], 'CA' => 'Canada', - 'US' => 'USA', - '?' => 'other', + 'US' => 'SUA', + '?' => 'altă', ]; ``` -În cazul căsuțelor de selectare, primul element are adesea o semnificație specială, servind ca un apel la acțiune. Utilizați metoda `setPrompt()` pentru a adăuga o astfel de intrare. +La select box-uri, adesea primul element are o semnificație specială, servește ca îndemn la acțiune. Pentru adăugarea unui astfel de element servește metoda `setPrompt()`. ```php -$form->addSelect('country', 'Country:', $countries) - ->setPrompt('Pick a country'); +$form->addSelect('country', 'Țara:', $countries) + ->setPrompt('Alegeți țara'); ``` -Puteți utiliza `setDisabled(['CZ', 'SK'])` pentru a dezactiva elemente individuale. +Folosind `setDisabled(['RO', 'MD'])` se pot dezactiva elemente individuale. -Elementul verifică în mod automat dacă nu a existat nicio falsificare și dacă elementul selectat este într-adevăr unul dintre cele oferite și nu a fost dezactivat. Metoda `getRawValue()` poate fi utilizată pentru a prelua elementul propus fără această verificare importantă. +Elementul verifică automat că nu a avut loc o falsificare și că elementul selectat este într-adevăr unul dintre cele oferite și nu a fost dezactivat. Prin metoda `getRawValue()` se poate obține elementul trimis fără această verificare importantă. -În cazul în care este stabilită o valoare implicită, se verifică, de asemenea, dacă este unul dintre elementele oferite, în caz contrar se aruncă o excepție. Această verificare poate fi dezactivată cu `checkDefaultValue(false)`. +La setarea elementului selectat implicit, verifică de asemenea că acesta este unul dintre cele oferite, altfel aruncă o excepție. Această verificare poate fi dezactivată folosind `checkDefaultValue(false)`. -addMultiSelect(string|int $name, $label=null, array $items=null): MultiSelectBox .[method] -========================================================================================== +addMultiSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): MultiSelectBox .[method] +============================================================================================================ -Adaugă caseta de selectare a mai multor opțiuni (clasa [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox]). Returnează matricea de chei a elementelor selectate. Metoda `getSelectedItems()` returnează valori în loc de chei. +Adaugă un select box pentru selectarea mai multor elemente (clasa [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox]). Returnează un array al cheilor elementelor selectate. Metoda `getSelectedItems()` returnează valorile în loc de chei. ```php -$form->addMultiSelect('countries', 'Countries:', $countries); +$form->addMultiSelect('countries', 'Țări:', $countries); ``` -Trecem matricea de elemente ca al treilea parametru sau prin metoda `setItems()`. Rețeaua de elemente poate fi, de asemenea, bidimensională. +Array-ul de elemente oferite îl transmitem ca al treilea parametru sau prin metoda `setItems()`. Elementele pot fi și un array bidimensional. -Puteți utiliza `setDisabled(['CZ', 'SK'])` pentru a dezactiva elemente individuale. +Folosind `setDisabled(['RO', 'MD'])` se pot dezactiva elemente individuale. -Elementul verifică în mod automat dacă nu a existat nicio falsificare și dacă elementele selectate sunt într-adevăr unul dintre cele oferite și nu au fost dezactivate. Metoda `getRawValue()` poate fi utilizată pentru a prelua elementele trimise fără această verificare importantă. +Elementul verifică automat că nu a avut loc o falsificare și că elementele selectate sunt într-adevăr unele dintre cele oferite și nu au fost dezactivate. Prin metoda `getRawValue()` se pot obține elementele trimise fără această verificare importantă. -În cazul în care sunt stabilite valori implicite, se verifică, de asemenea, dacă acestea sunt unul dintre elementele oferite, în caz contrar se aruncă o excepție. Această verificare poate fi dezactivată cu `checkDefaultValue(false)`. +La setarea elementelor selectate implicit, verifică de asemenea că acestea sunt unele dintre cele oferite, altfel aruncă o excepție. Această verificare poate fi dezactivată folosind `checkDefaultValue(false)`. addUpload(string|int $name, $label=null): UploadControl .[method] ================================================================= -Adaugă câmpul de încărcare a fișierelor (clasa [UploadControl |api:Nette\Forms\Controls\UploadControl]). Returnează obiectul [FileUpload |http:request#FileUpload], chiar dacă utilizatorul nu a încărcat un fișier, lucru care poate fi aflat prin metoda `FileUpload::hasFile()`. +Adaugă un câmp pentru încărcarea fișierului (clasa [UploadControl |api:Nette\Forms\Controls\UploadControl]). Returnează un obiect [FileUpload |http:request#FileUpload], chiar și în cazul în care utilizatorul nu a trimis niciun fișier, ceea ce se poate verifica prin metoda `FileUpload::hasFile()`. ```php $form->addUpload('avatar', 'Avatar:') - ->addRule($form::Image, 'Avatar must be JPEG, PNG, GIF or WebP') - ->addRule($form::MaxFileSize, 'Maximum size is 1 MB', 1024 * 1024); + ->addRule($form::Image, 'Avatarul trebuie să fie JPEG, PNG, GIF, WebP sau AVIF.') + ->addRule($form::MaxFileSize, 'Dimensiunea maximă este 1 MB.', 1024 * 1024); ``` -Dacă fișierul nu a fost încărcat corect, formularul nu a fost trimis cu succes și se afișează o eroare. Adică nu este necesar să se verifice metoda `FileUpload::isOk()`. +Dacă fișierul nu reușește să se încarce corect, formularul nu este trimis cu succes și se afișează o eroare. Adică, la trimiterea cu succes nu este nevoie să se verifice metoda `FileUpload::isOk()`. -Nu vă încredeți în numele original al fișierului returnat de metoda `FileUpload::getName()`, un client ar putea trimite un nume de fișier malițios cu intenția de a vă corupe sau de a vă pirata aplicația. +Nu aveți niciodată încredere în numele original al fișierului returnat de metoda `FileUpload::getName()`, clientul ar fi putut trimite un nume de fișier malițios cu intenția de a deteriora sau hackui aplicația dvs. -Regulile `MimeType` și `Image` detectează tipul necesar de fișier sau imagine prin semnătura acestuia. Integritatea întregului fișier nu este verificată. Puteți afla dacă o imagine nu este coruptă, de exemplu, încercând să [o încărcați |http:request#toImage]. +Regulile `MimeType` și `Image` detectează tipul solicitat pe baza semnăturii fișierului și nu verifică integritatea acestuia. Dacă imaginea nu este deteriorată se poate verifica, de exemplu, prin încercarea de a o [încărca |http:request#toImage]. addMultiUpload(string|int $name, $label=null): UploadControl .[method] ====================================================================== -Adaugă câmpul de încărcare a mai multor fișiere (clasa [UploadControl |api:Nette\Forms\Controls\UploadControl]). Returnează o matrice de obiecte [FileUpload |http:request#FileUpload]. Metoda `FileUpload::hasFile()` va returna `true` pentru fiecare dintre ele. +Adaugă un câmp pentru încărcarea mai multor fișiere simultan (clasa [UploadControl |api:Nette\Forms\Controls\UploadControl]). Returnează un array de obiecte [FileUpload |http:request#FileUpload]. Metoda `FileUpload::hasFile()` pentru fiecare dintre ele va returna `true`. ```php -$form->addMultiUpload('files', 'Files:') - ->addRule($form::MaxLength, 'A maximum of %d files can be uploaded', 10); +$form->addMultiUpload('files', 'Fișiere:') + ->addRule($form::MaxLength, 'Maxim se pot încărca %d fișiere', 10); ``` -Dacă unul dintre fișiere nu reușește să fie încărcat corect, formularul nu a fost trimis cu succes și se afișează o eroare. Adică nu este necesar să verificați metoda `FileUpload::isOk()`. +Dacă vreun fișier nu reușește să se încarce corect, formularul nu este trimis cu succes și se afișează o eroare. Adică, la trimiterea cu succes nu este nevoie să se verifice metoda `FileUpload::isOk()`. + +Nu aveți niciodată încredere în numele originale ale fișiierelor returnate de metoda `FileUpload::getName()`, clientul ar fi putut trimite un nume de fișier malițios cu intenția de a deteriora sau hackui aplicația dvs. + +Regulile `MimeType` și `Image` detectează tipul solicitat pe baza semnăturii fișierului și nu verifică integritatea acestuia. Dacă imaginea nu este deteriorată se poate verifica, de exemplu, prin încercarea de a o [încărca |http:request#toImage]. + -Nu vă încredeți în numele de fișier original returnat de metoda `FileUpload::getName()`, un client ar putea trimite un nume de fișier malițios cu intenția de a vă corupe sau de a vă pirata aplicația. +addDate(string|int $name, $label=null): DateTimeControl .[method]{data-version:3.1.14} +====================================================================================== -Regulile `MimeType` și `Image` detectează tipul necesar de fișier sau imagine prin semnătura acestuia. Integritatea întregului fișier nu este verificată. Puteți afla dacă o imagine nu este coruptă, de exemplu, încercând să [o încărcați |http:request#toImage]. +Adaugă un câmp care permite utilizatorului să introducă ușor o dată formată din an, lună și zi (clasa [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Ca valoare implicită acceptă fie obiecte care implementează interfața `DateTimeInterface`, un șir cu timpul, fie un număr reprezentând timestamp UNIX. Același lucru este valabil și pentru argumentele regulilor `Min`, `Max` sau `Range`, care definesc data minimă și maximă permisă. + +```php +$form->addDate('date', 'Data:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'Data trebuie să fie cu cel puțin o lună în urmă.', new DateTime('-1 month')); +``` + +Standard returnează un obiect `DateTimeImmutable`, prin metoda `setFormat()` puteți specifica [formatul text|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] sau timestamp: + +```php +$form->addDate('date', 'Data:') + ->setFormat('Y-m-d'); +``` -addHidden(string|int $name, string $default=null): HiddenField .[method] -======================================================================== +addTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=============================================================================================================== + +Adaugă un câmp care permite utilizatorului să introducă ușor un timp format din ore, minute și opțional secunde (clasa [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Ca valoare implicită acceptă fie obiecte care implementează interfața `DateTimeInterface`, un șir cu timpul, fie un număr reprezentând timestamp UNIX. Din aceste intrări este utilizată doar informația de timp, data este ignorată. Același lucru este valabil și pentru argumentele regulilor `Min`, `Max` sau `Range`, care definesc timpul minim și maxim permis. Dacă valoarea minimă setată este mai mare decât cea maximă, se creează un interval de timp care depășește miezul nopții. + +```php +$form->addTime('time', 'Ora:', withSeconds: true) + ->addRule($form::Range, 'Ora trebuie să fie în intervalul de la %d la %d.', ['12:30', '13:30']); +``` + +Standard returnează un obiect `DateTimeImmutable` (cu data 1 ianuarie anul 1), prin metoda `setFormat()` puteți specifica [formatul text|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters]: + +```php +$form->addTime('time', 'Ora:') + ->setFormat('H:i'); +``` + + +addDateTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=================================================================================================================== + +Adaugă un câmp care permite utilizatorului să introducă ușor data și ora formate din an, lună, zi, ore, minute și opțional secunde (clasa [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Ca valoare implicită acceptă fie obiecte care implementează interfața `DateTimeInterface`, un șir cu timpul, fie un număr reprezentând timestamp UNIX. Același lucru este valabil și pentru argumentele regulilor `Min`, `Max` sau `Range`, care definesc data minimă și maximă permisă. + +```php +$form->addDateTime('datetime', 'Data și ora:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'Data trebuie să fie cu cel puțin o lună în urmă.', new DateTime('-1 month')); +``` + +Standard returnează un obiect `DateTimeImmutable`, prin metoda `setFormat()` puteți specifica [formatul text|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] sau timestamp: + +```php +$form->addDateTime('datetime') + ->setFormat(DateTimeControl::FormatTimestamp); +``` + + +addColor(string|int $name, $label=null): ColorPicker .[method]{data-version:3.1.14} +=================================================================================== + +Adaugă un câmp pentru selectarea culorii (clasa [ColorPicker |api:Nette\Forms\Controls\ColorPicker]). Culoarea este un șir în formatul `#rrggbb`. Dacă utilizatorul nu face nicio selecție, se returnează culoarea neagră `#000000`. + +```php +$form->addColor('color', 'Culoare:') + ->setDefaultValue('#3C8ED7'); +``` + + +addHidden(string|int $name, ?string $default=null): HiddenField .[method] +========================================================================= Adaugă un câmp ascuns (clasa [HiddenField |api:Nette\Forms\Controls\HiddenField]). @@ -255,26 +348,28 @@ Adaugă un câmp ascuns (clasa [HiddenField |api:Nette\Forms\Controls\HiddenFiel $form->addHidden('userid'); ``` -Folosiți `setNullable()` pentru a-l schimba pentru a returna `null` în loc de un șir gol. [AddFilter() |validation#Modifying Input Values] vă permite să modificați valoarea trimisă. +Folosind `setNullable()` se poate seta să returneze `null` în loc de șir gol. Modificarea valorii trimise este posibilă prin [addFilter() |validation#Modificarea intrării]. + +Deși elementul este ascuns, este **important să rețineți** că valoarea poate fi totuși modificată sau falsificată de un atacator. Verificați și validați întotdeauna cu atenție toate valorile primite pe partea serverului pentru a preveni riscurile de securitate asociate cu manipularea datelor. addSubmit(string|int $name, $caption=null): SubmitButton .[method] ================================================================== -Adaugă butonul de trimitere (clasa [SubmitButton |api:Nette\Forms\Controls\SubmitButton]). +Adaugă un buton de trimitere (clasa [SubmitButton |api:Nette\Forms\Controls\SubmitButton]). ```php -$form->addSubmit('submit', 'Register'); +$form->addSubmit('submit', 'Trimite'); ``` -Este posibil să existe mai mult de un buton de trimitere în formular: +În formular este posibil să aveți și mai multe butoane de trimitere: ```php -$form->addSubmit('register', 'Register'); -$form->addSubmit('cancel', 'Cancel'); +$form->addSubmit('register', 'Înregistrează-te'); +$form->addSubmit('cancel', 'Anulează'); ``` -Pentru a afla care dintre ele a fost apăsat, utilizați: +Pentru a afla pe care dintre ele s-a făcut clic, utilizați: ```php if ($form['register']->isSubmittedBy()) { @@ -282,48 +377,48 @@ if ($form['register']->isSubmittedBy()) { } ``` -Dacă nu doriți să validați formularul atunci când este apăsat un buton de trimitere (cum ar fi butoanele *Cancel* sau *Preview*), puteți dezactiva acest lucru cu [setValidationScope() |validation#Disabling Validation]. +Dacă nu doriți să validați întregul formular la apăsarea butonului (de exemplu, la butoanele *Anulează* sau *Previzualizare*), utilizați [setValidationScope() |validation#Dezactivarea validării]. addButton(string|int $name, $caption): Button .[method] ======================================================= -Adaugă un buton (clasa [Button |api:Nette\Forms\Controls\Button]) fără funcția de trimitere. Este util pentru a lega o altă funcționalitate la id, de exemplu o acțiune JavaScript. +Adaugă un buton (clasa [Button |api:Nette\Forms\Controls\Button]), care nu are funcție de trimitere. Poate fi deci utilizat pentru o altă funcție, de ex. apelarea unei funcții JavaScript la clic. ```php -$form->addButton('raise', 'Raise salary') +$form->addButton('raise', 'Mărește salariul') ->setHtmlAttribute('onclick', 'raiseSalary()'); ``` -addImageButton(string|int $name, string $src=null, string $alt=null): ImageButton .[method] -=========================================================================================== +addImageButton(string|int $name, ?string $src=null, ?string $alt=null): ImageButton .[method] +============================================================================================= -Adaugă un buton de trimitere sub forma unei imagini (clasa [ImageButton |api:Nette\Forms\Controls\ImageButton]). +Adaugă un buton de trimitere sub formă de imagine (clasa [ImageButton |api:Nette\Forms\Controls\ImageButton]). ```php $form->addImageButton('submit', '/path/to/image'); ``` -Atunci când se utilizează mai multe butoane de trimitere, puteți afla care dintre ele a fost apăsat cu ajutorul funcției `$form['submit']->isSubmittedBy()`. +La utilizarea mai multor butoane de trimitere se poate afla pe care s-a făcut clic folosind `$form['submit']->isSubmittedBy()`. addContainer(string|int $name): Container .[method] =================================================== -Adaugă un subformular (clasa [Container |api:Nette\Forms\Container]) sau un container, care poate fi tratat la fel ca un formular. Aceasta înseamnă că puteți utiliza metode precum `setDefaults()` sau `getValues()`. +Adaugă un subformular (clasa [Container|api:Nette\Forms\Container]), adică un container, în care se pot adăuga alte elemente în același mod în care le adăugăm în formular. Funcționează și metodele `setDefaults()` sau `getValues()`. ```php $sub1 = $form->addContainer('first'); -$sub1->addText('name', 'Your name:'); +$sub1->addText('name', 'Numele dvs.:'); $sub1->addEmail('email', 'Email:'); $sub2 = $form->addContainer('second'); -$sub2->addText('name', 'Your name:'); +$sub2->addText('name', 'Numele dvs.:'); $sub2->addEmail('email', 'Email:'); ``` -Datele trimise sunt apoi returnate sub forma unei structuri multidimensionale: +Datele trimise le returnează apoi ca o structură multidimensională: ```php [ @@ -339,110 +434,112 @@ Datele trimise sunt apoi returnate sub forma unei structuri multidimensionale: ``` -Prezentare generală a setărilor .[#toc-overview-of-settings] -============================================================ +Prezentare generală a setărilor +=============================== -Pentru toate elementele putem apela următoarele metode (consultați [documentația API |https://api.nette.org/forms/master/Nette/Forms/Controls.html] pentru o prezentare completă): +La toate elementele putem apela următoarele metode (prezentare completă în [documentația API|https://api.nette.org/forms/master/Nette/Forms/Controls.html]): .[table-form-methods language-php] -| `setDefaultValue($value)` | stabilește valoarea implicită -| `getValue()` | obține valoarea curentă -| `setOmitted()` | [valori omise |#omitted values] -| `setDisabled()` | [dezactivarea intrărilor |#disabling inputs] +| `setDefaultValue($value)` | setează valoarea implicită +| `getValue()` | obține valoarea curentă +| `setOmitted()` | [#Omiterea valorii] +| `setDisabled()` | [#Dezactivarea elementelor] -Renderizare: +Randare: .[table-form-methods language-php] -| `setCaption($caption)`| modificarea legendei elementului -| `setTranslator($translator)` | setează [traducătorul |rendering#translating] -| `setHtmlAttribute($name, $value)` | setează [atributul HTML |rendering#HTML attributes] al elementului -| `setHtmlId($id)` | setează atributul HTML `id` -| `setHtmlType($type)` | setează atributul HTML `type` -| `setHtmlName($name)`| stabilește atributul HTML `name` -| `setOption($key, $value)` | stabilește [datele de redare |rendering#Options] +| `setCaption($caption)` | schimbă eticheta elementului +| `setTranslator($translator)` | setează [traducătorul |rendering#Traducere] +| `setHtmlAttribute($name, $value)` | setează [atributul HTML |rendering#Atribute HTML] al elementului +| `setHtmlId($id)` | setează atributul HTML `id` +| `setHtmlType($type)` | setează atributul HTML `type` +| `setHtmlName($name)` | setează atributul HTML `name` +| `setOption($key, $value)` | [opțiuni de randare |rendering#Opțiuni] Validare: .[table-form-methods language-php] -| `setRequired()` | [câmp obligatoriu |validation] -| `addRule()` | setează [regula de validare |validation#Rules] -| `addCondition()`, `addConditionOn()` | setați [condiția de validare |validation#Conditions] -| `addError($message)`| [transmiterea mesajului de eroare |validation#processing-errors] +| `setRequired()` | [element obligatoriu |validation] +| `addRule()` | setarea [regulii de validare |validation#Reguli] +| `addCondition()`, `addConditionOn()` | setează [condiția de validare |validation#Condiții] +| `addError($message)` | [transmiterea mesajului de eroare |validation#Erori în timpul procesării] -Următoarele metode pot fi apelate pentru elementele `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`: +La elementele `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` se pot apela următoarele metode: .[table-form-methods language-php] -| `setNullable()`| stabilește dacă getValue() returnează `null` în loc de un șir de caractere gol. -| `setEmptyValue($value)` | stabilește valoarea specială care este tratată ca șir de caractere gol -| `setMaxLength($length)`| stabilește numărul maxim de caractere permise -| `addFilter($filter)`| [modificarea valorilor de intrare |validation#Modifying Input Values] +| `setNullable()` | setează dacă getValue() va returna `null` în loc de șir gol +| `setEmptyValue($value)` | setează o valoare specială care este considerată șir gol +| `setMaxLength($length)` | setează numărul maxim de caractere permise +| `addFilter($filter)` | [modificarea intrării |validation#Modificarea intrării] -Valori omise .[#toc-omitted-values] ------------------------------------ +Omiterea valorii +================ -Dacă nu vă interesează valoarea introdusă de utilizator, putem folosi `setOmitted()` pentru a o omite din rezultatul furnizat de metoda `$form->getValues​()` sau transmis către gestionari. Acest lucru este potrivit pentru diverse parole de verificare, câmpuri antispam etc. +Dacă valoarea completată de utilizator nu ne interesează, o putem omite din rezultatul metodei `$form->getValues()` sau din datele transmise handlerilor folosind `setOmitted()`. Acest lucru este util pentru diverse parole de verificare, elemente antispam etc. ```php -$form->addPassword('passwordVerify', 'Password again:') - ->setRequired('Fill your password again to check for typo') - ->addRule($form::Equal, 'Password mismatch', $form['password']) +$form->addPassword('passwordVerify', 'Parola pentru verificare:') + ->setRequired('Vă rugăm introduceți parola încă o dată pentru verificare') + ->addRule($form::Equal, 'Parolele nu se potrivesc', $form['password']) ->setOmitted(); ``` -Dezactivarea intrărilor .[#toc-disabling-inputs] ------------------------------------------------- +Dezactivarea elementelor +======================== -Pentru a dezactiva o intrare, puteți apela `setDisabled()`. Un astfel de câmp nu poate fi editat de către utilizator. +Elementele pot fi dezactivate folosind `setDisabled()`. Un astfel de element nu poate fi editat de utilizator. ```php -$form->addText('username', 'User name:') +$form->addText('username', 'Nume utilizator:') ->setDisabled(); ``` -Rețineți că browserul nu trimite deloc câmpurile dezactivate către server, astfel încât nici măcar nu le veți găsi în datele returnate de funcția `$form->getValues()`. +Elementele dezactivate nu sunt trimise deloc de browser către server, deci nu le veți găsi nici în datele returnate de funcția `$form->getValues()`. Dacă însă setați `setOmitted(false)`, Nette va include în aceste date valoarea lor implicită. -Dacă setați o valoare implicită pentru un câmp, trebuie să faceți acest lucru numai după ce l-ați dezactivat: +La apelarea `setDisabled()`, din motive de securitate **se șterge valoarea elementului**. Dacă setați o valoare implicită, este necesar să o faceți după dezactivarea acestuia: ```php -$form->addText('username', 'User name:') +$form->addText('username', 'Nume utilizator:') ->setDisabled() ->setDefaultValue($userName); ``` +O alternativă la elementele dezactivate sunt elementele cu atributul HTML `readonly`, pe care browserul le trimite la server. Deși elementul este doar pentru citire, este **important să rețineți** că valoarea sa poate fi totuși modificată sau falsificată de un atacator. + -Controale personalizate .[#toc-custom-controls] -=============================================== +Elemente personalizate +====================== -Pe lângă gama largă de controale de formular încorporate, puteți adăuga controale personalizate la formular, după cum urmează: +Pe lângă gama largă de elemente de formular încorporate, puteți adăuga elemente personalizate în formular în acest mod: ```php -$form->addComponent(new DateInput('Date:'), 'date'); -// sintaxa alternativă: $form['data'] = new DateInput('Data:'); +$form->addComponent(new DateInput('Data:'), 'date'); +// sintaxă alternativă: $form['date'] = new DateInput('Data:'); ``` .[note] -Formularul este un descendent al clasei [Container | component-model:#Container], iar elementele sunt descendenți ai clasei [Component | component-model:#Component]. +Formularul este un descendent al clasei [Container |component-model:#Container], iar elementele individuale sunt descendenți ai [Component |component-model:#Component]. -Există o modalitate de a defini noi metode de formular pentru adăugarea de elemente personalizate (de exemplu, `$form->addZip()`). Acestea sunt așa-numitele metode de extensie. Dezavantajul este că indicii de cod din editori nu vor funcționa pentru ele. +Există o modalitate de a defini noi metode ale formularului care servesc la adăugarea elementelor personalizate (de ex. `$form->addZip()`). Este vorba de așa-numitele extension methods. Dezavantajul este că sugestiile din editori nu vor funcționa pentru ele. ```php use Nette\Forms\Container; -// adaugă metoda addZip(string $name, string $label = null) -Container::extensionMethod('addZip', function (Container $form, string $name, string $label = null) { +// adăugăm metoda addZip(string $name, ?string $label = null) +Container::extensionMethod('addZip', function (Container $form, string $name, ?string $label = null) { return $form->addText($name, $label) - ->addRule($form::Pattern, 'At least 5 numbers', '[0-9]{5}'); + ->addRule($form::Pattern, 'Cel puțin 5 cifre', '[0-9]{5}'); }); // utilizare -$form->addZip('zip', 'ZIP code:'); +$form->addZip('zip', 'Cod poștal:'); ``` -Câmpuri de nivel scăzut .[#toc-low-level-fields] -================================================ +Elemente de nivel scăzut +======================== -Pentru a adăuga un element la formular, nu trebuie să apelați `$form->addXyz()`. În schimb, elementele din formular pot fi introduse exclusiv în șabloane. Acest lucru este util dacă, de exemplu, trebuie să generați elemente dinamice: +Se pot utiliza și elemente pe care le scriem doar în șablon și nu le adăugăm în formular prin una dintre metodele `$form->addXyz()`. De exemplu, când afișăm înregistrări din baza de date și nu știm dinainte câte vor fi și ce ID-uri vor avea, și dorim să afișăm la fiecare rând un checkbox sau un radio button, este suficient să îl codăm în șablon: ```latte {foreach $items as $item} @@ -450,13 +547,13 @@ Pentru a adăuga un element la formular, nu trebuie să apelați `$form->addXyz( {/foreach} ``` -După trimitere, puteți prelua valorile: +Și după trimitere aflăm valoarea: ```php $data = $form->getHttpData($form::DataText, 'sel[]'); $data = $form->getHttpData($form::DataText | $form::DataKeys, 'sel[]'); ``` -În primul parametru, specificați tipul de element (`DataFile` pentru `type=file`, `DataLine` pentru intrările cu o singură linie, cum ar fi `text`, `password` sau `email` și `DataText` pentru restul). Al doilea parametru corespunde atributului HTML `name`. Dacă trebuie să păstrați cheile, puteți combina primul parametru cu `DataKeys`. Acest lucru este util pentru `select`, `radioList` sau `checkboxList`. +unde primul parametru este tipul elementului (`DataFile` pentru `type=file`, `DataLine` pentru intrări pe o singură linie precum `text`, `password`, `email` etc. și `DataText` pentru toate celelalte) iar al doilea parametru `sel[]` corespunde atributului HTML name. Tipul elementului îl putem combina cu valoarea `DataKeys`, care păstrează cheile elementelor. Acest lucru este util în special pentru `select`, `radioList` și `checkboxList`. - `getHttpData()` returnează datele de intrare aseptizate. În acest caz, acesta va fi întotdeauna un array de șiruri UTF-8 valide, indiferent de atacatorul trimis de formular. Este o alternativă la lucrul direct cu `$_POST` sau `$_GET` dacă doriți să primiți date sigure. +Esențial este că `getHttpData()` returnează o valoare sanitarizată, în acest caz va fi întotdeauna un array de șiruri UTF-8 valide, indiferent ce ar încerca un atacator să strecoare serverului. Este o analogie a lucrului direct cu `$_POST` sau `$_GET`, dar cu diferența esențială că returnează întotdeauna date curate, așa cum sunteți obișnuiți la elementele standard ale formularelor Nette. diff --git a/forms/ro/in-presenter.texy b/forms/ro/in-presenter.texy index fb18bf4c66..98aed25f44 100644 --- a/forms/ro/in-presenter.texy +++ b/forms/ro/in-presenter.texy @@ -1,36 +1,36 @@ -Formularele din Prezentări -************************** +Formulare în presentere +*********************** .[perex] -Nette Forms facilitează în mod dramatic crearea și procesarea formularelor web. În acest capitol, veți învăța cum să utilizați formularele în cadrul programelor de prezentare. +Nette Forms facilitează enorm crearea și procesarea formularelor web. În acest capitol, veți învăța cum să utilizați formularele în interiorul presenterelor. -Dacă vă interesează să le folosiți complet independent, fără restul cadrului, există un ghid pentru [formulare independente |standalone]. +Dacă sunteți interesat de cum să le utilizați complet independent, fără restul framework-ului, ghidul pentru [utilizare independentă|standalone] este pentru dumneavoastră. -Primul formular .[#toc-first-form] -================================== +Primul formular +=============== -Vom încerca să scriem un formular de înregistrare simplu. Codul acestuia va arăta astfel: +Să încercăm să scriem un formular simplu de înregistrare. Codul său va fi următorul: ```php use Nette\Application\UI\Form; $form = new Form; -$form->addText('name', 'Name:'); -$form->addPassword('password', 'Password:'); -$form->addSubmit('send', 'Sign up'); +$form->addText('name', 'Nume:'); +$form->addPassword('password', 'Parolă:'); +$form->addSubmit('send', 'Înregistrează-te'); $form->onSuccess[] = [$this, 'formSucceeded']; ``` -iar în browser, rezultatul ar trebui să arate astfel: +și în browser se va afișa astfel: -[* form-en.webp *] +[* form-cs.webp *] -Formularul din prezentator este un obiect din clasa `Nette\Application\UI\Form`, predecesorul său, `Nette\Forms\Form`, este destinat utilizării de sine stătătoare. I-am adăugat câmpurile nume, parolă și butonul de trimitere. În cele din urmă, linia cu `$form->onSuccess` spune că, după trimiterea și validarea cu succes, trebuie apelată metoda `$this->formSucceeded()`. +Formularul în presenter este un obiect al clasei `Nette\Application\UI\Form`, predecesorul său `Nette\Forms\Form` este destinat utilizării independente. Am adăugat în el elementele numite nume, parolă și un buton de trimitere. Și în final, linia cu `$form->onSuccess` spune că după trimitere și validare reușită, trebuie apelată metoda `$this->formSucceeded()`. -Din punctul de vedere al prezentatorului, formularul este o componentă comună. Prin urmare, acesta este tratat ca o componentă și încorporat în prezentator folosind [metoda factory |application:components#Factory Methods]. Acesta va arăta astfel: +Din perspectiva presenterului, formularul este o componentă obișnuită. Prin urmare, este tratat ca o componentă și îl vom integra în presenter folosind [metode factory |application:components#Metode factory]. Va arăta astfel: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} use Nette; use Nette\Application\UI\Form; @@ -39,103 +39,100 @@ class HomePresenter extends Nette\Application\UI\Presenter protected function createComponentRegistrationForm(): Form { $form = new Form; - $form->addText('name', 'Name:'); - $form->addPassword('password', 'Password:'); - $form->addSubmit('send', 'Sign up'); + $form->addText('name', 'Nume:'); + $form->addPassword('password', 'Parolă:'); + $form->addSubmit('send', 'Înregistrează-te'); $form->onSuccess[] = [$this, 'formSucceeded']; return $form; } public function formSucceeded(Form $form, $data): void { - // aici vom procesa datele trimise de formular - // $data->name conține nume + // aici procesăm datele trimise de formular + // $data->name conține numele // $data->password conține parola - $this->flashMessage('You have successfully signed up.'); + $this->flashMessage('Ați fost înregistrat cu succes.'); $this->redirect('Home:'); } } ``` -Iar redarea în șablon se face cu ajutorul etichetei `{control}`: +Și în șablon, randăm formularul cu tag-ul `{control}`: -```latte .{file:app/Presenters/templates/Home/default.latte} -<h1>Registration</h1> +```latte .{file:app/Presentation/Home/default.latte} +<h1>Înregistrare</h1> {control registrationForm} ``` -Și asta e tot :-) Avem un formular funcțional și perfect [securizat |#Vulnerability Protection]. +Și asta e tot :-) Avem un formular funcțional și perfect [securizat |#Protecția împotriva vulnerabilităților]. -Acum probabil că vă gândiți că a fost prea rapid, întrebându-vă cum este posibil ca metoda `formSucceeded()` să fie apelată și ce parametri primește. Sigur, aveți dreptate, acest lucru merită o explicație. +Și acum probabil vă gândiți că a fost prea rapid, vă întrebați cum este posibil să fie apelată metoda `formSucceeded()` și care sunt parametrii pe care îi primește. Sigur, aveți dreptate, acest lucru merită o explicație. -Nette vine cu un mecanism interesant, pe care îl numim [stilul Hollywood |application:components#Hollywood style]. În loc să trebuiască să întrebați constant dacă s-a întâmplat ceva ("a fost trimis formularul?", "a fost trimis în mod valid?" sau "nu a fost falsificat?"), îi spuneți framework-ului "când formularul este completat în mod valid, apelați această metodă" și lăsați să lucreze mai departe la ea. Dacă programați în JavaScript, sunteți familiarizat cu acest stil de programare. Scrieți funcții care sunt apelate atunci când apare un anumit [eveniment |nette:glossary#Events]. Iar limbajul le trece argumentele corespunzătoare. +Nette vine cu un mecanism proaspăt, pe care îl numim [Stil Hollywood |application:components#Stilul Hollywood]. În loc ca dumneavoastră, ca dezvoltator, să trebuiască să întrebați constant dacă s-a întâmplat ceva („a fost trimis formularul?”, „a fost trimis valid?” și „nu a fost falsificat?”), spuneți framework-ului „când formularul este completat valid, apelează această metodă” și lăsați restul muncii pe seama lui. Dacă programați în JavaScript, acest stil de programare vă este familiar. Scrieți funcții care sunt apelate atunci când are loc un anumit [eveniment |nette:glossary#Evenimente]. Și limbajul le transmite argumentele corespunzătoare. -Acesta este modul în care este construit codul prezentatorului de mai sus. Array `$form->onSuccess` reprezintă lista de callback-uri PHP pe care Nette le va apela atunci când formularul este trimis și completat corect. -În cadrul [ciclului de viață al prezentatorului |application:presenters#Life Cycle of Presenter] este un așa-numit semnal, astfel încât acestea sunt apelate după metoda `action*` și înainte de metoda `render*`. -Și transmite la fiecare callback formularul propriu-zis în primul parametru și datele trimise ca obiect [ArrayHash |utils:arrays#ArrayHash] în al doilea. Puteți omite primul parametru dacă nu aveți nevoie de obiectul formular. Al doilea parametru poate fi chiar mai util, dar despre asta vom vorbi [mai târziu |#Mapping to Classes]. +Exact așa este construit și codul presenterului de mai sus. Array-ul `$form->onSuccess` reprezintă o listă de callback-uri PHP, pe care Nette le apelează în momentul în care formularul este trimis și completat corect (adică este valid). În cadrul [ciclului de viață al presenterului |application:presenters#Ciclul de viață al presenterului], este vorba despre un așa-numit semnal, deci sunt apelate după metoda `action*` și înainte de metoda `render*`. Și fiecărui callback îi transmite ca prim parametru formularul însuși și ca al doilea, datele trimise sub forma unui obiect [ArrayHash |utils:arrays#ArrayHash]. Primul parametru poate fi omis dacă nu aveți nevoie de obiectul formularului. Iar al doilea parametru poate fi mai inteligent, dar despre asta [mai târziu |#Maparea la clase]. -Obiectul `$data` conține proprietățile `name` și `password` cu datele introduse de utilizator. De obicei, trimitem datele direct pentru o prelucrare ulterioară, care poate fi, de exemplu, inserarea în baza de date. Cu toate acestea, poate apărea o eroare în timpul procesării, de exemplu, numele de utilizator este deja ocupat. În acest caz, transmitem eroarea înapoi la formular folosind `addError()` și îl lăsăm să se redeseneze, cu un mesaj de eroare: +Obiectul `$data` conține proprietățile `name` și `password` cu datele completate de utilizator. De obicei, trimitem datele direct pentru procesare ulterioară, ceea ce poate fi, de exemplu, inserarea în baza de date. În timpul procesării, însă, poate apărea o eroare, de exemplu, numele de utilizator este deja ocupat. În acest caz, transmitem eroarea înapoi în formular folosind `addError()` și îl lăsăm să fie randat din nou, inclusiv cu mesajul de eroare. ```php -$form->addError('Sorry, username is already in use.'); +$form->addError('Ne pare rău, numele de utilizator este deja folosit de altcineva.'); ``` -În plus față de `onSuccess`, există și `onSubmit`: callback-urile sunt întotdeauna apelate după ce formularul este trimis, chiar dacă nu este completat corect. Și, în sfârșit, `onError`: callback-urile sunt apelate numai dacă trimiterea nu este validă. Ele sunt apelate chiar dacă invalidăm formularul în `onSuccess` sau `onSubmit` folosind `addError()`. +Pe lângă `onSuccess`, mai există și `onSubmit`: callback-urile sunt apelate întotdeauna după trimiterea formularului, chiar și atunci când nu este completat corect. Și, de asemenea, `onError`: callback-urile sunt apelate doar dacă trimiterea nu este validă. Sunt apelate chiar și atunci când în `onSuccess` sau `onSubmit` invalidăm formularul folosind `addError()`. -După procesarea formularului, vom redirecționa către pagina următoare. Acest lucru împiedică retrimiterea neintenționată a formularului prin apăsarea butonului *refresh*, *back* sau prin mutarea istoricului browserului. +După procesarea formularului, redirecționăm către pagina următoare. Acest lucru previne retrimiterea nedorită a formularului prin butonul *reîmprospătare*, *înapoi* sau prin navigarea în istoricul browserului. -Încercați să adăugați mai multe [controale de formular |controls]. +Încercați să adăugați și alte [elemente de formular|controls]. -Accesul la controale .[#toc-access-to-controls] -=============================================== +Accesul la elemente +=================== -Formularul este o componentă a dispozitivului de prezentare, în cazul nostru numit `registrationForm` (după numele metodei de fabrică `createComponentRegistrationForm`), astfel încât oriunde în dispozitiv se poate ajunge la formular folosind: +Formularul este o componentă a presenterului, în cazul nostru numită `registrationForm` (după numele metodei factory `createComponentRegistrationForm`), așa că oriunde în presenter puteți accesa formularul folosind: ```php $form = $this->getComponent('registrationForm'); -// sintaxa alternativă: $form = $this['registrationForm']; +// sintaxă alternativă: $form = $this['registrationForm']; ``` -De asemenea, controalele individuale ale formularului sunt componente, deci le puteți accesa în același mod: +Componentele sunt și elementele individuale ale formularului, de aceea le puteți accesa în același mod: ```php $input = $form->getComponent('name'); // sau $input = $form['name']; $button = $form->getComponent('send'); // sau $button = $form['send']; ``` -Controalele sunt eliminate cu ajutorul funcției unset: +Elementele se elimină folosind `unset`: ```php unset($form['name']); ``` -Reguli de validare .[#toc-validation-rules] -=========================================== +Reguli de validare +================== -Cuvântul *valid* a fost folosit de mai multe ori, dar formularul nu are încă reguli de validare. Să rezolvăm problema. +S-a menționat cuvântul *valid*, dar formularul nu are încă nicio regulă de validare. Să remediem acest lucru. -Numele va fi obligatoriu, așa că îl vom marca cu metoda `setRequired()`, al cărei argument este textul mesajului de eroare care va fi afișat în cazul în care utilizatorul nu îl completează. Dacă nu se dă niciun argument, se folosește mesajul de eroare implicit. +Numele va fi obligatoriu, de aceea îl marcăm cu metoda `setRequired()`, al cărei argument este textul mesajului de eroare care se afișează dacă utilizatorul nu completează numele. Dacă nu specificăm argumentul, se va folosi mesajul de eroare implicit. ```php -$form->addText('name', 'Name:') - ->setRequired('Please fill your name.'); +$form->addText('name', 'Nume:') + ->setRequired('Vă rugăm să introduceți numele'); ``` -Încercați să trimiteți formularul fără ca numele să fie completat și veți vedea că se afișează un mesaj de eroare, iar browserul sau serverul îl va respinge până când îl veți completa. +Încercați să trimiteți formularul fără a completa numele și veți vedea că se afișează un mesaj de eroare, iar browserul sau serverul îl va respinge până când completați câmpul. -În același timp, nu veți putea păcăli sistemul introducând doar spații în câmp, de exemplu. În niciun caz. Nette taie automat spațiile albe din stânga și din dreapta. Încercați. Este un lucru pe care ar trebui să-l faceți întotdeauna la fiecare intrare pe o singură linie, dar este adesea uitat. Nette o face automat. (Puteți încerca să păcăliți formularele și să trimiteți un șir de caractere multiliniar ca nume. Chiar și în acest caz, Nette nu se va lăsa păcălit și întreruperile de linie se vor schimba în spații). +În același timp, nu puteți păcăli sistemul scriind, de exemplu, doar spații în câmp. Nici vorbă. Nette elimină automat spațiile de la începutul și sfârșitul șirului. Încercați. Este un lucru pe care ar trebui să-l faceți întotdeauna cu fiecare input de o singură linie, dar adesea se uită. Nette o face automat. (Puteți încerca să păcăliți formularul și să trimiteți un șir multilinie ca nume. Nici aici Nette nu se lasă păcălit și transformă sfârșiturile de rând în spații.) -Formularul este întotdeauna validat pe partea serverului, dar este generată și validarea JavaScript, care este rapidă, iar utilizatorul știe imediat de eroare, fără a fi nevoie să trimită formularul la server. Acest lucru este gestionat de scriptul `netteForms.js`. -Introduceți-l în șablonul de prezentare: +Formularul este întotdeauna validat pe partea de server, dar se generează și o validare JavaScript, care se execută instantaneu, iar utilizatorul află despre eroare imediat, fără a fi nevoie să trimită formularul la server. Acest lucru este gestionat de scriptul `netteForms.js`. Inserați-l în șablonul de layout: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Dacă vă uitați în codul sursă al paginii cu formular, puteți observa că Nette inserează câmpurile obligatorii în elemente cu o clasă CSS `required`. Încercați să adăugați următorul stil în șablon, iar eticheta "Name" va fi roșie. În mod elegant, marcăm câmpurile obligatorii pentru utilizatori: +Dacă vă uitați la codul sursă al paginii cu formularul, puteți observa că Nette inserează elementele obligatorii în elemente cu clasa CSS `required`. Încercați să adăugați următorul stil în șablon și eticheta „Nume” va fi roșie. Astfel, marcăm elegant elementele obligatorii pentru utilizatori: ```latte <style> @@ -143,96 +140,96 @@ Dacă vă uitați în codul sursă al paginii cu formular, puteți observa că N </style> ``` -Reguli de validare suplimentare vor fi adăugate prin metoda `addRule()`. Primul parametru este regula, al doilea este din nou textul mesajului de eroare, iar argumentul opțional al regulii de validare poate urma. Ce înseamnă acest lucru? +Alte reguli de validare le adăugăm cu metoda `addRule()`. Primul parametru este regula, al doilea este din nou textul mesajului de eroare și poate urma un argument al regulii de validare. Ce înseamnă asta? -Formularul va primi o altă intrare opțională *age* cu condiția ca aceasta să fie un număr (`addInteger()`) și să se încadreze în anumite limite (`$form::Range`). Și aici vom folosi al treilea argument din `addRule()`, intervalul propriu-zis: +Vom extinde formularul cu un nou câmp opțional „vârstă”, care trebuie să fie un număr întreg (`addInteger()`) și, în plus, într-un interval permis (`$form::Range`). Și aici vom folosi al treilea parametru al metodei `addRule()`, prin care transmitem validatorului intervalul dorit ca o pereche `[de la, până la]`: ```php -$form->addInteger('age', 'Age:') - ->addRule($form::Range, 'You must be older 18 years and be under 120.', [18, 120]); +$form->addInteger('age', 'Vârsta:') + ->addRule($form::Range, 'Vârsta trebuie să fie între 18 și 120', [18, 120]); ``` .[tip] -În cazul în care utilizatorul nu completează câmpul, regulile de validare nu vor fi verificate, deoarece câmpul este opțional. +Dacă utilizatorul nu completează câmpul, regulile de validare nu vor fi verificate, deoarece elementul este opțional. -Evident, există loc pentru o mică refactorizare. În mesajul de eroare și în cel de-al treilea parametru, numerele sunt listate în dublu exemplar, ceea ce nu este ideal. Dacă am crea un [formular multilingv |rendering#translating], iar mesajul care conține numere ar trebui tradus în mai multe limbi, ar îngreuna modificarea valorilor. Din acest motiv, se pot utiliza caracterele de substituție `%d`: +Aici apare spațiu pentru o mică refactorizare. În mesajul de eroare și în al treilea parametru, numerele sunt menționate duplicat, ceea ce nu este ideal. Dacă am crea [formulare multilingve |rendering#Traducere] și mesajul care conține numere ar fi tradus în mai multe limbi, o eventuală modificare a valorilor ar fi dificilă. Din acest motiv, este posibil să folosim substituenți `%d` și Nette va completa valorile: ```php - ->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]); + ->addRule($form::Range, 'Vârsta trebuie să fie între %d și %d ani', [18, 120]); ``` -Să ne întoarcem la câmpul *parolă*, să îl facem *obligatoriu* și să verificăm lungimea minimă a parolei (`$form::MinLength`), folosind din nou caracterele de substituție din mesaj: +Să ne întoarcem la elementul `password`, pe care îl vom face, de asemenea, obligatoriu și vom verifica lungimea minimă a parolei (`$form::MinLength`), din nou folosind substituentul: ```php -$form->addPassword('password', 'Password:') - ->setRequired('Pick a password') - ->addRule($form::MinLength, 'Your password has to be at least %d long', 8); +$form->addPassword('password', 'Parolă:') + ->setRequired('Alegeți o parolă') + ->addRule($form::MinLength, 'Parola trebuie să aibă cel puțin %d caractere', 8); ``` -Vom adăuga un câmp `passwordVerify` la formular, în care utilizatorul introduce din nou parola, pentru verificare. Folosind reguli de validare, vom verifica dacă ambele parole sunt identice (`$form::Equal`). Iar ca argument vom da o referință la prima parolă folosind [paranteze pătrate |#Access to Controls]: +Adăugăm în formular și câmpul `passwordVerify`, unde utilizatorul introduce parola încă o dată, pentru verificare. Folosind regulile de validare, verificăm dacă ambele parole sunt identice (`$form::Equal`). Și ca parametru, dăm o referință la prima parolă folosind [paranteze drepte |#Accesul la elemente]: ```php -$form->addPassword('passwordVerify', 'Password again:') - ->setRequired('Fill your password again to check for typo') - ->addRule($form::Equal, 'Password mismatch', $form['password']) +$form->addPassword('passwordVerify', 'Parola pentru verificare:') + ->setRequired('Vă rugăm introduceți parola încă o dată pentru verificare') + ->addRule($form::Equal, 'Parolele nu se potrivesc', $form['password']) ->setOmitted(); ``` -Folosind `setOmitted()`, marcăm un element a cărui valoare nu ne interesează cu adevărat și care există doar pentru validare. Valoarea sa nu este transmisă la `$data`. +Cu `setOmitted()`, am marcat elementul a cărui valoare nu ne interesează de fapt și care există doar în scopul validării. Valoarea nu se transmite în `$data`. -Avem un formular complet funcțional cu validare în PHP și JavaScript. Capacitățile de validare ale Nette sunt mult mai largi, puteți crea condiții, afișa și ascunde părți ale unei pagini în funcție de acestea etc. Puteți afla totul în capitolul dedicat [validării formularelor |validation]. +Astfel, avem un formular complet funcțional cu validare în PHP și JavaScript. Capacitățile de validare ale Nette sunt mult mai largi, se pot crea condiții, se pot afișa și ascunde părți ale paginii în funcție de acestea etc. Veți afla totul în capitolul despre [validarea formularelor|validation]. -Valori implicite .[#toc-default-values] -======================================= +Valori implicite +================ -Adesea se stabilesc valori implicite pentru controalele de formular: +Elementelor formularului le setăm în mod obișnuit valori implicite: ```php -$form->addEmail('email', 'Email') +$form->addEmail('email', 'E-mail') ->setDefaultValue($lastUsedEmail); ``` -Adesea este util să setați valorile implicite pentru toate controalele deodată. De exemplu, atunci când formularul este utilizat pentru a edita înregistrări. Citim înregistrarea din baza de date și o setăm ca valoare implicită: +Adesea este util să setăm valori implicite pentru toate elementele simultan. De exemplu, când formularul servește pentru editarea înregistrărilor. Citim înregistrarea din baza de date și setăm valorile implicite: ```php //$row = ['name' => 'John', 'age' => '33', /* ... */]; $form->setDefaults($row); ``` -Apelați `setDefaults()` după definirea controalelor. +Apelați `setDefaults()` după definirea elementelor. -Redarea formularului .[#toc-rendering-the-form] -=============================================== +Randarea formularului +===================== -În mod implicit, formularul este redat sub forma unui tabel. Controalele individuale respectă orientările de bază privind accesibilitatea web. Toate etichetele sunt generate ca `<label>` elemente și sunt asociate cu intrările lor, făcând clic pe etichetă se mută cursorul pe intrare. +În mod standard, formularul se randează ca un tabel. Elementele individuale respectă regula de bază a accesibilității - toate etichetele sunt scrise ca `<label>` și legate de elementul de formular corespunzător. La clic pe etichetă, cursorul apare automat în câmpul formularului. -Putem seta orice atribute HTML pentru fiecare element. De exemplu, adăugați un marcaj de loc: +Fiecărui element îi putem seta atribute HTML arbitrare. De exemplu, adăugăm un placeholder: ```php -$form->addInteger('age', 'Age:') - ->setHtmlAttribute('placeholder', 'Please fill in the age'); +$form->addInteger('age', 'Vârsta:') + ->setHtmlAttribute('placeholder', 'Vă rugăm să completați vârsta'); ``` -Există într-adevăr o mulțime de moduri de redare a unui formular, așa că este un [capitol |rendering] dedicat [redării |rendering]. +Există într-adevăr o mare varietate de moduri de a randa un formular, așa că există un [capitol separat despre randare|rendering] dedicat acestui subiect. -Maparea în clase .[#toc-mapping-to-classes] -=========================================== +Maparea la clase +================ -Să ne întoarcem la metoda `formSucceeded()`, care, în al doilea parametru `$data`, primește datele trimise sub forma unui obiect `ArrayHash`. Deoarece aceasta este o clasă generică, ceva de genul `stdClass`, ne vor lipsi unele facilități atunci când lucrăm cu ea, cum ar fi completarea codului pentru proprietăți în editori sau analiza statică a codului. Acest lucru ar putea fi rezolvat prin existența unei clase specifice pentru fiecare formular, ale cărei proprietăți să reprezinte controalele individuale. De ex: +Să ne întoarcem la metoda `formSucceeded()`, care în al doilea parametru `$data` primește datele trimise ca obiect `ArrayHash`. Deoarece este o clasă generică, ceva de genul `stdClass`, ne va lipsi un anumit confort în lucrul cu ea, cum ar fi sugerarea proprietăților în editori sau analiza statică a codului. Acest lucru ar putea fi rezolvat având o clasă specifică pentru fiecare formular, ale cărei proprietăți reprezintă elementele individuale. De ex.: ```php class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -Începând cu PHP 8.0, puteți utiliza această notație elegantă care folosește un constructor: +Alternativ, puteți utiliza constructorul: ```php class RegistrationFormData @@ -246,27 +243,29 @@ class RegistrationFormData } ``` -Cum să îi spuneți lui Nette să returneze datele ca obiecte din această clasă? Mai ușor decât credeți. Tot ceea ce trebuie să faceți este să specificați clasa ca tip al parametrului `$data` în handler: +Proprietățile clasei de date pot fi, de asemenea, enum-uri și vor fi mapate automat. .{data-version:3.2.4} + +Cum să spunem Nette să ne returneze datele ca obiecte ale acestei clase? Mai ușor decât credeți. Este suficient doar să specificați clasa ca tip al parametrului `$data` în metoda handler: ```php public function formSucceeded(Form $form, RegistrationFormData $data): void { - // $name este o instanță de RegistrationFormData + // $data este o instanță a RegistrationFormData $name = $data->name; // ... } ``` -De asemenea, puteți specifica `array` ca tip și atunci datele vor fi transmise ca o matrice. +Ca tip se poate specifica și `array` și atunci datele vor fi transmise ca array. -În mod similar, puteți utiliza metoda `getValues()`, pe care o trecem ca nume de clasă sau obiect de hidratat ca parametru: +În mod similar, se poate utiliza și metoda `getValues()`, căreia îi transmitem numele clasei sau obiectul de hidratat ca parametru: ```php $data = $form->getValues(RegistrationFormData::class); $name = $data->name; ``` -În cazul în care formularele constau într-o structură pe mai multe niveluri compusă din containere, creați o clasă separată pentru fiecare dintre ele: +Dacă formularele formează o structură multinivel compusă din containere, creați o clasă separată pentru fiecare: ```php $form = new Form; @@ -283,32 +282,34 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` -În acest caz, cartografierea știe din tipul de proprietate `$person` că trebuie să mapeze containerul la clasa `PersonFormData`. În cazul în care proprietatea ar trebui să conțină o matrice de containere, furnizați tipul `array` și treceți clasa care urmează să fie mapată direct la container: +Maparea va recunoaște apoi din tipul proprietății `$person` că trebuie să mapeze containerul la clasa `PersonFormData`. Dacă proprietatea ar conține un array de containere, specificați tipul `array` și transmiteți clasa pentru mapare direct containerului: ```php $person->setMappedType(PersonFormData::class); ``` +Puteți genera designul clasei de date a formularului folosind metoda `Nette\Forms\Blueprint::dataClass($form)`, care o va afișa în pagina browserului. Apoi, este suficient să selectați codul cu un clic și să-l copiați în proiect. .{data-version:3.1.15} + -Butoane de trimitere multiple .[#toc-multiple-submit-buttons] -============================================================= +Mai multe butoane +================= -În cazul în care formularul are mai multe butoane, de obicei trebuie să distingem care dintre ele a fost apăsat. Putem crea o funcție proprie pentru fiecare buton. Setați-o ca gestionar pentru [evenimentul |nette:glossary#Events] `onClick`: +Dacă formularul are mai mult de un buton, de obicei trebuie să distingem care dintre ele a fost apăsat. Putem crea o funcție handler proprie pentru fiecare buton. O setăm ca handler pentru [evenimentul |nette:glossary#Evenimente] `onClick`: ```php -$form->addSubmit('save', 'Save') +$form->addSubmit('save', 'Salvează') ->onClick[] = [$this, 'saveButtonPressed']; -$form->addSubmit('delete', 'Delete') +$form->addSubmit('delete', 'Șterge') ->onClick[] = [$this, 'deleteButtonPressed']; ``` -Acești gestionari sunt, de asemenea, apelați numai în cazul în care formularul este valid, ca în cazul evenimentului `onSuccess`. Diferența constă în faptul că primul parametru poate fi obiectul butonului de trimitere în loc de formular, în funcție de tipul pe care îl specificați: +Acești handleri sunt apelați doar în cazul unui formular completat valid, la fel ca în cazul evenimentului `onSuccess`. Diferența este că, în loc de formular, ca prim parametru se poate transmite butonul de trimitere, depinde de tipul pe care îl specificați: ```php public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) @@ -318,62 +319,61 @@ public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) } ``` -Atunci când un formular este trimis cu tasta <kbd>Enter</kbd>, acesta este tratat ca și cum ar fi fost trimis cu primul buton. +Când formularul este trimis cu tasta <kbd>Enter</kbd>, se consideră ca și cum ar fi fost trimis cu primul buton. -Eveniment onAnchor .[#toc-event-onanchor] -========================================= +Evenimentul onAnchor +==================== -Atunci când construiți un formular printr-o metodă factory (cum ar fi `createComponentRegistrationForm`), acesta nu știe încă dacă a fost trimis sau datele cu care a fost trimis. Dar există cazuri în care trebuie să cunoaștem valorile trimise, poate că de ele depinde cum va arăta formularul, sau sunt folosite pentru casetele de selectare dependente etc. +Când construim formularul în metoda factory (cum ar fi `createComponentRegistrationForm`), acesta nu știe încă dacă a fost trimis, nici cu ce date. Dar există cazuri în care avem nevoie să cunoaștem valorile trimise, de exemplu, forma ulterioară a formularului depinde de ele, sau avem nevoie de ele pentru selectbox-uri dependente etc. -Prin urmare, puteți face ca codul care construiește formularul să fie apelat atunci când acesta este ancorat, adică este deja legat de prezentator și cunoaște datele trimise. Vom plasa un astfel de cod în matricea `$onAnchor`: +O parte a codului care construiește formularul poate fi, prin urmare, lăsată să fie apelată doar în momentul în care este așa-numit ancorat, adică este deja conectat la presenter și cunoaște datele sale trimise. Un astfel de cod îl transmitem în array-ul `$onAnchor`: ```php -$country = $form->addSelect('country', 'Country:', $this->model->getCountries()); -$city = $form->addSelect('city', 'City:'); +$country = $form->addSelect('country', 'Țara:', $this->model->getCountries()); +$city = $form->addSelect('city', 'Oraș:'); $form->onAnchor[] = function () use ($country, $city) { - // această funcție va fi apelată atunci când formularul cunoaște datele cu care a fost trimis. - // astfel încât puteți utiliza metoda getValue() + // această funcție va fi apelată doar când formularul știe dacă a fost trimis și cu ce date + // deci se poate folosi metoda getValue() $val = $country->getValue(); - $city->setItems($val ? $this->model->getCities($val): []); + $city->setItems($val ? $this->model->getCities($val) : []); }; ``` -Protecția împotriva vulnerabilităților .[#toc-vulnerability-protection] -======================================================================= +Protecția împotriva vulnerabilităților +====================================== -Nette Framework depune un mare efort pentru a fi sigur și, deoarece formularele sunt cele mai frecvente intrări ale utilizatorului, formularele Nette sunt ca și impenetrabile. Totul este menținut în mod dinamic și transparent, nimic nu trebuie setat manual. +Nette Framework pune un accent deosebit pe securitate și, prin urmare, acordă o atenție deosebită securizării formularelor. Face acest lucru complet transparent și nu necesită setări manuale. -Pe lângă protejarea formularelor împotriva atacurilor care vizează vulnerabilități bine cunoscute, cum ar fi Cross-Site [Scripting (XSS |nette:glossary#cross-site-scripting-xss] ) și [Cross-Site Request Forgery (CSRF |nette:glossary#cross-site-request-forgery-csrf]), face o mulțime de mici sarcini de securitate la care nu mai trebuie să vă gândiți. +Pe lângă faptul că protejează formularele împotriva atacurilor [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] și [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF], realizează o mulțime de mici măsuri de securitate la care nu mai trebuie să vă gândiți. -De exemplu, filtrează toate caracterele de control din intrări și verifică validitatea codificării UTF-8, astfel încât datele din formular vor fi întotdeauna curate. Pentru căsuțele de selectare și listele radio, verifică dacă elementele selectate sunt într-adevăr dintre cele oferite și dacă nu a existat nicio falsificare. Am menționat deja că, pentru introducerea textului pe o singură linie, elimină caracterele de sfârșit de linie pe care un atacator le-ar putea trimite acolo. În cazul intrărilor de text pe mai multe linii, normalizează caracterele de sfârșit de linie. Și așa mai departe. +De exemplu, filtrează toate caracterele de control din intrări și verifică validitatea codificării UTF-8, astfel încât datele din formular vor fi întotdeauna curate. La select box-uri și liste radio, verifică dacă elementele selectate au fost într-adevăr dintre cele oferite și nu a avut loc o falsificare. Am menționat deja că la intrările text de o singură linie elimină caracterele de sfârșit de rând, pe care un atacator le-ar fi putut trimite. La intrările multilinie, normalizează caracterele de sfârșit de rând. Și așa mai departe. -Nette rezolvă pentru dumneavoastră vulnerabilitățile de securitate despre care majoritatea programatorilor habar nu au că există. +Nette rezolvă pentru dumneavoastră riscurile de securitate despre care mulți programatori nici nu bănuiesc că există. -Atacul CSRF menționat constă în faptul că un atacator atrage victima să viziteze o pagină care execută în tăcere o cerere în browserul victimei către serverul unde victima este conectată în acel moment, iar serverul crede că cererea a fost făcută de către victimă în mod voit. Prin urmare, Nette împiedică trimiterea formularului prin POST de pe un alt domeniu. Dacă dintr-un motiv oarecare doriți să dezactivați protecția și să permiteți ca formularul să fie trimis de pe un alt domeniu, utilizați: +Atacul CSRF menționat constă în faptul că atacatorul atrage victima pe o pagină care execută discret în browserul victimei o cerere către serverul pe care victima este autentificată, iar serverul crede că cererea a fost executată de victimă din proprie voință. De aceea, Nette împiedică trimiterea formularului POST de pe un alt domeniu. Dacă, din anumite motive, doriți să dezactivați protecția și să permiteți trimiterea formularului de pe un alt domeniu, utilizați: ```php $form->allowCrossOrigin(); // ATENȚIE! Dezactivează protecția! ``` -Această protecție utilizează un cookie SameSite numit `_nss`. Este posibil ca protecția prin cookie SameSite să nu fie 100% fiabilă, așa că este o idee bună să activați protecția prin token: +Această protecție utilizează un cookie SameSite numit `_nss`. Protecția prin cookie SameSite poate să nu fie 100% fiabilă, de aceea este recomandat să activați și protecția prin token: ```php $form->addProtection(); ``` -Este foarte recomandat să aplicați această protecție formularelor dintr-o parte administrativă a aplicației dumneavoastră care modifică date sensibile. Cadrul protejează împotriva unui atac CSRF prin generarea și validarea token-ului de autentificare care este stocat într-o sesiune (argumentul este mesajul de eroare afișat în cazul în care token-ul a expirat). De aceea, este necesar să aveți o sesiune pornită înainte de a afișa formularul. În partea de administrare a site-ului web, de obicei, sesiunea este deja începută, datorită autentificării utilizatorului. -În caz contrar, începeți sesiunea cu metoda `Nette\Http\Session::start()`. +Recomandăm protejarea în acest mod a formularelor din partea de administrare a site-ului, care modifică date sensibile în aplicație. Framework-ul se apără împotriva atacului CSRF prin generarea și verificarea unui token de autorizare, care se stochează în sesiune (session). De aceea, este necesar ca sesiunea să fie deschisă înainte de afișarea formularului. În partea de administrare a site-ului, de obicei, sesiunea este deja pornită datorită autentificării utilizatorului. Altfel, porniți sesiunea cu metoda `Nette\Http\Session::start()`. -Utilizarea unui formular în mai multe prezentări .[#toc-using-one-form-in-multiple-presenters] -============================================================================================== +Același formular în mai multe presentere +======================================== -Dacă aveți nevoie să utilizați un formular în mai multe prezentări, vă recomandăm să creați o fabrică pentru acesta, pe care apoi să o transmiteți prezentatorului. O locație potrivită pentru o astfel de clasă este, de exemplu, directorul `app/Forms`. +Dacă aveți nevoie să utilizați același formular în mai multe presentere, vă recomandăm să creați o fabrică pentru acesta, pe care apoi să o transmiteți presenterului. O locație potrivită pentru o astfel de clasă este, de exemplu, directorul `app/Forms`. -Clasa factory ar putea arăta astfel: +Clasa fabrică poate arăta, de exemplu, astfel: ```php use Nette\Application\UI\Form; @@ -383,14 +383,14 @@ class SignInFormFactory public function create(): Form { $form = new Form; - $form->addText('name', 'Name:'); - $form->addSubmit('send', 'Log in'); + $form->addText('name', 'Nume:'); + $form->addSubmit('send', 'Conectează-te'); return $form; } } ``` -Cerem clasei să producă formularul în metoda factory pentru componentele din prezentator: +Solicităm clasei să producă formularul în metoda factory pentru componente din presenter: ```php public function __construct( @@ -401,14 +401,14 @@ public function __construct( protected function createComponentSignInForm(): Form { $form = $this->formFactory->create(); - // putem modifica formularul, aici de exemplu schimbăm eticheta de pe buton - $form['login']->setCaption('Continue'); - $form->onSuccess[] = [$this, 'signInFormSubmitted']; // și adăugăm handlerul + // putem modifica formularul, aici de exemplu schimbăm eticheta butonului + $form['send']->setCaption('Continuă'); + $form->onSuccess[] = [$this, 'signInFormSuceeded']; // și adăugăm handler return $form; } ``` -Gestionarul de procesare a formularului poate fi, de asemenea, livrat din fabrică: +Handlerul pentru procesarea formularului poate fi, de asemenea, furnizat deja din fabrică: ```php use Nette\Application\UI\Form; @@ -418,14 +418,14 @@ class SignInFormFactory public function create(): Form { $form = new Form; - $form->addText('name', 'Name:'); - $form->addSubmit('send', 'Log in'); + $form->addText('name', 'Nume:'); + $form->addSubmit('send', 'Conectează-te'); $form->onSuccess[] = function (Form $form, $data): void { - // procesăm formularul nostru trimis aici + // aici efectuăm procesarea formularului }; return $form; } } ``` -Așadar, avem o scurtă introducere în formulare în Nette. Încercați să căutați în directorul de [exemple |https://github.com/nette/forms/tree/master/examples] din distribuție pentru mai multă inspirație. +Așadar, am parcurs o introducere rapidă în formularele din Nette. Încercați să vă uitați și în directorul [examples|https://github.com/nette/forms/tree/master/examples] din distribuție, unde veți găsi mai multă inspirație. diff --git a/forms/ro/rendering.texy b/forms/ro/rendering.texy index 1eb25cc947..21ed2448b7 100644 --- a/forms/ro/rendering.texy +++ b/forms/ro/rendering.texy @@ -1,33 +1,35 @@ -Redarea formularelor -******************** +Randarea formularelor +********************* -Aspectul formelor poate fi foarte divers. În practică, putem întâlni două extreme. Pe de o parte, există necesitatea de a reda într-o aplicație o serie de formulare care să fie asemănătoare din punct de vedere vizual, iar noi apreciem redarea ușoară fără șablon cu ajutorul `$form->render()`. Acesta este, de obicei, cazul interfețelor administrative. +Aspectul formularelor poate fi foarte divers. În practică, putem întâlni două extreme. Pe de o parte, există nevoia de a randa în aplicație o serie de formulare care sunt vizual asemănătoare ca două picături de apă și apreciem randarea ușoară fără șablon folosind `$form->render()`. Acesta este de obicei cazul interfețelor de administrare. -Pe de altă parte, există diverse formulare în care fiecare este unic. Aspectul lor este cel mai bine descris folosind limbajul HTML în șablon. Și, bineînțeles, pe lângă cele două extreme menționate, vom întâlni multe forme care se încadrează undeva la mijloc. +Pe de altă parte, există formulare diverse, unde regula este: fiecare piesă este originală. Forma lor este cel mai bine descrisă folosind limbajul HTML în șablonul formularului. Și, desigur, pe lângă cele două extreme menționate, vom întâlni multe formulare care se situează undeva între. -Renderizare cu Latte .[#toc-rendering-with-latte] -================================================= +Randarea cu Latte +================= - [Sistemul de modelare Latte |latte:] facilitează în mod fundamental redarea formularelor și a elementelor acestora. În primul rând, vom arăta cum să redăm formularele manual, element cu element, pentru a obține un control total asupra codului. Ulterior vom arăta cum să [automatizăm |#Automatic rendering] această redare. +[Sistemul de șabloane Latte|latte:] facilitează fundamental randarea formularelor și a elementelor acestora. Mai întâi, vom arăta cum să randăm formularele manual, element cu element, obținând astfel control deplin asupra codului. Mai târziu, vom arăta cum se poate [automatiza |#Randare automată] o astfel de randare. + +Puteți genera designul șablonului Latte al formularului folosind metoda `Nette\Forms\Blueprint::latte($form)`, care îl va afișa în pagina browserului. Apoi, este suficient să selectați codul cu un clic și să-l copiați în proiect. .{data-version:3.1.15} `{control}` ----------- -Cel mai simplu mod de a reda un formular este de a scrie într-un șablon: +Cel mai simplu mod de a randa un formular este să scrieți în șablon: ```latte {control signInForm} ``` -Aspectul formularului redat poate fi modificat prin configurarea [Renderer |#Renderer] și a [controalelor individuale |#HTML Attributes]. +Puteți influența aspectul formularului randat astfel prin configurarea [Rendererului |#Renderer] și a [elementelor individuale |#Atribute HTML]. `n:name` -------- -Este extrem de ușor să legați definiția formularului din codul PHP cu codul HTML. Trebuie doar să adăugați atributele `n:name`. Atât de simplu este! +Definirea formularului în codul PHP poate fi extrem de ușor legată de codul HTML. Este suficient doar să adăugați atributele `n:name`. Atât de simplu este! ```php protected function createComponentSignInForm(): Form @@ -54,10 +56,9 @@ protected function createComponentSignInForm(): Form </form> ``` -Aspectul codului HTML rezultat este în întregime în mâinile dumneavoastră. Dacă utilizați atributul `n:name` cu `<select>`, `<button>` sau `<textarea>` conținutul lor intern este completat automat. -În plus, atributul `<form n:name>` creează o variabilă locală `$form` cu obiectul de formular desenat și cu eticheta de închidere `</form>` desenează toate elementele ascunse nedesenate (același lucru este valabil și pentru `{form} ... {/form}`). +Aveți control deplin asupra aspectului codului HTML rezultat. Dacă utilizați atributul `n:name` la elementele `<select>`, `<button>` sau `<textarea>`, conținutul lor intern se va completa automat. Tag-ul `<form n:name>` creează, în plus, o variabilă locală `$form` cu obiectul formularului randat, iar tag-ul de închidere `</form>` randează toate elementele hidden nerandate (același lucru este valabil și pentru `{form} ... {/form}`). -Cu toate acestea, nu trebuie să uităm să redăm eventualele mesaje de eroare. Atât pe cele care au fost adăugate la elementele individuale prin metoda `addError()` (folosind `{inputError}`), cât și pe cele adăugate direct în formular (returnate de `$form->getOwnErrors()`): +Nu trebuie însă să uităm de randarea posibilelor mesaje de eroare. Atât cele care au fost adăugate la elementele individuale prin metoda `addError()` (folosind `{inputError}`), cât și cele adăugate direct la formular (returnate de `$form->getOwnErrors()`): ```latte <form n:name=signInForm class=form> @@ -79,7 +80,7 @@ Cu toate acestea, nu trebuie să uităm să redăm eventualele mesaje de eroare. </form> ``` -Elementele de formular mai complexe, cum ar fi RadioList sau CheckboxList, pot fi redate element cu element: +Elementele de formular mai complexe, cum ar fi RadioList sau CheckboxList, pot fi randate astfel, element cu element: ```latte {foreach $form[gender]->getItems() as $key => $label} @@ -88,16 +89,10 @@ Elementele de formular mai complexe, cum ar fi RadioList sau CheckboxList, pot f ``` -Propunere de cod `{formPrint}` .[#toc-formprint] ------------------------------------------------- - -Puteți genera un cod Latte similar pentru un formular folosind eticheta `{formPrint}`. Dacă îl plasați într-un șablon, veți vedea proiectul de cod în loc de redarea normală. Apoi, nu trebuie decât să îl selectați și să îl copiați în proiectul dvs. - - `{label}` `{input}` ------------------- -Nu vreți să vă gândiți pentru fiecare element ce element HTML să folosiți pentru el în șablon, dacă `<input>`, `<textarea>` etc.? Soluția este tag-ul universal `{input}`: +Nu doriți să vă gândiți la fiecare element ce element HTML să folosiți pentru el în șablon, dacă `<input>`, `<textarea>` etc.? Soluția este tag-ul universal `{input}`: ```latte <form n:name=signInForm class=form> @@ -119,9 +114,9 @@ Nu vreți să vă gândiți pentru fiecare element ce element HTML să folosiți </form> ``` -Dacă formularul utilizează un traducător, textul din interiorul etichetelor `{label}` va fi tradus. +Dacă formularul utilizează un translator, textul din interiorul tag-urilor `{label}` va fi tradus. -Din nou, elementele de formular mai complexe, cum ar fi RadioList sau CheckboxList, pot fi redate element cu element: +Chiar și în acest caz, elementele de formular mai complexe, cum ar fi RadioList sau CheckboxList, pot fi randate element cu element: ```latte {foreach $form[gender]->items as $key => $label} @@ -129,20 +124,19 @@ Din nou, elementele de formular mai complexe, cum ar fi RadioList sau CheckboxLi {/foreach} ``` -Pentru a reda elementul `<input>` însuși în elementul Checkbox, utilizați `{input myCheckbox:}`. Atributele HTML trebuie să fie separate prin virgulă `{input myCheckbox:, class: required}`. +Pentru a randa doar `<input>` în elementul Checkbox, utilizați `{input myCheckbox:}`. Atributele HTML în acest caz separați-le întotdeauna cu virgulă `{input myCheckbox:, class: required}`. `{inputError}` -------------- -Tipărește un mesaj de eroare pentru elementul de formular, dacă acesta are unul. Mesajul este de obicei inclus într-un element HTML pentru stilizare. -Evitarea redării unui element gol în cazul în care nu există un mesaj poate fi realizată în mod elegant cu `n:ifcontent`: +Afișează mesajul de eroare pentru elementul formularului, dacă are unul. Mesajul este de obicei încapsulat într-un element HTML pentru stilizare. Puteți preveni randarea elementului gol, dacă nu există mesaj, elegant folosind `n:ifcontent`: ```latte <span class=error n:ifcontent>{inputError $input}</span> ``` -Putem detecta prezența unei erori folosind metoda `hasErrors()` și putem seta clasa elementului părinte în mod corespunzător: +Putem verifica prezența unei erori cu metoda `hasErrors()` și, în funcție de aceasta, seta clasa elementului părinte: ```latte <div n:class="$form[username]->hasErrors() ? 'error'"> @@ -155,14 +149,13 @@ Putem detecta prezența unei erori folosind metoda `hasErrors()` și putem seta `{form}` -------- -Etichete `{form signInForm}...{/form}` sunt o alternativă la `<form n:name="signInForm">...</form>`. +Tag-urile `{form signInForm}...{/form}` sunt o alternativă la `<form n:name="signInForm">...</form>`. -Redare automată .[#toc-automatic-rendering] -------------------------------------------- +Randare automată +---------------- -Cu ajutorul etichetelor `{input}` și `{label}`, putem crea cu ușurință un șablon generic pentru orice formular. Acesta va itera și va reda toate elementele sale secvențial, cu excepția elementelor ascunse, care sunt redate automat atunci când formularul este încheiat cu tag-ul `</form>` . -Se va aștepta ca numele formularului redat să fie introdus în variabila `$form`. +Datorită tag-urilor `{input}` și `{label}`, putem crea cu ușurință un șablon generic pentru orice formular. Acesta va itera și va randa succesiv toate elementele sale, cu excepția elementelor hidden, care se randează automat la închiderea formularului cu tag-ul `</form>`. Numele formularului randat va fi așteptat în variabila `$form`. ```latte <form n:name=$form class=form> @@ -179,16 +172,15 @@ Se va aștepta ca numele formularului redat să fie introdus în variabila `$for </form> ``` -Etichetele pereche cu închidere automată utilizate `{label .../}` afișează etichetele care provin din definiția formularului din codul PHP. +Tag-urile pereche auto-închise `{label .../}` utilizate afișează etichetele provenite din definirea formularului în codul PHP. -Puteți salva acest șablon generic în fișierul `basic-form.latte`, iar pentru a reda formularul, trebuie doar să îl includeți și să treceți numele formularului (sau instanța) la parametrul `$form`: +Salvați acest șablon generic, de exemplu, în fișierul `basic-form.latte` și pentru a randa formularul, este suficient să-l includeți și să transmiteți numele (sau instanța) formularului în parametrul `$form`: ```latte {include basic-form.latte, form: signInForm} ``` -Dacă doriți să influențați aspectul unui anumit formular și să desenați un element în mod diferit, cel mai simplu este să pregătiți blocuri în șablon care pot fi suprascrise ulterior. -De asemenea, blocurile pot avea [nume dinamice |latte:template-inheritance#dynamic-block-names], astfel încât puteți introduce în ele numele elementului care urmează să fie desenat. De exemplu: +Dacă doriți să interveniți în aspectul unui anumit formular în timpul randării și, de exemplu, să randați un element diferit, cea mai simplă cale este să pregătiți blocuri în șablon, care vor putea fi ulterior suprascrise. Blocurile pot avea și [nume dinamice |latte:template-inheritance#Denumiri dinamice de blocuri], astfel încât se poate insera în ele și numele elementului randat. De exemplu: ```latte ... @@ -197,7 +189,7 @@ De asemenea, blocurile pot avea [nume dinamice |latte:template-inheritance#dynam ... ``` -Pentru elementul de exemplu `username`, se creează blocul `input-username`, care poate fi ușor de înlocuit prin utilizarea tag-ului [{embed} |latte:template-inheritance#unit-inheritance]: +Pentru elementul, de exemplu, `username`, se va crea astfel blocul `input-username`, care poate fi ușor suprascris folosind tag-ul [{embed} |latte:template-inheritance#Moștenirea unitară embed]: ```latte {embed basic-form.latte, form: signInForm} @@ -209,7 +201,7 @@ Pentru elementul de exemplu `username`, se creează blocul `input-username`, car {/embed} ``` -Alternativ, întregul conținut al șablonului `basic-form.latte` poate fi [definit |latte:template-inheritance#definitions] ca un bloc, inclusiv parametrul `$form`: +Alternativ, întregul conținut al șablonului `basic-form.latte` poate fi [definit |latte:template-inheritance#Definiții define] ca un bloc, inclusiv parametrul `$form`: ```latte {define basic-form, $form} @@ -219,7 +211,7 @@ Alternativ, întregul conținut al șablonului `basic-form.latte` poate fi [defi {/define} ``` -Acest lucru îl va face puțin mai ușor de utilizat: +Datorită acestui fapt, apelarea sa va fi puțin mai simplă: ```latte {embed basic-form, signInForm} @@ -227,31 +219,31 @@ Acest lucru îl va face puțin mai ușor de utilizat: {/embed} ``` -Trebuie doar să importați blocul într-un singur loc, la începutul modelului de machetă: +Blocul trebuie importat într-un singur loc, și anume la începutul șablonului de layout: ```latte {import basic-form.latte} ``` -Cazuri speciale .[#toc-special-cases] -------------------------------------- +Cazuri speciale +--------------- -Dacă aveți nevoie doar de redarea conținutului interior al unui formular fără `<form>` & `</form>` HTML, de exemplu, într-o solicitare AJAX, puteți deschide și închide formularul cu `{formContext} … {/formContext}`. Funcționează similar cu `{form}` în sens logic, aici vă permite să utilizați alte etichete pentru a desena elementele formularului, dar în același timp nu desenează nimic. +Dacă aveți nevoie să randați doar partea interioară a formularului fără tag-urile HTML `<form>`, de exemplu la trimiterea snippet-urilor, ascundeți-le folosind atributul `n:tag-if`: ```latte -{formContext signForm} +<form n:name=signInForm n:tag-if=false> <div> <label n:name=username>Username: <input n:name=username></label> {inputError username} </div> -{/formContext} +</form> ``` -Tag-ul `formContainer` ajută la redarea intrărilor în interiorul unui container de formular. +Tag-ul `{formContainer}` ajută la randarea elementelor din interiorul containerului formularului. ```latte -<p>Which news you wish to receive:</p> +<p>Ce știri doriți să primiți:</p> {formContainer emailNews} <ul> @@ -262,27 +254,27 @@ Tag-ul `formContainer` ajută la redarea intrărilor în interiorul unui contain ``` -Rendering fără Latte .[#toc-rendering-without-latte] -==================================================== +Randare fără Latte +================== -Cel mai simplu mod de a reda un formular este de a apela: +Cel mai simplu mod de a randa un formular este să apelați: ```php $form->render(); ``` -Aspectul formularului redat poate fi modificat prin configurarea [Renderer |#Renderer] și a [controalelor individuale |#HTML Attributes]. +Puteți influența aspectul formularului randat astfel prin configurarea [Rendererului |#Renderer] și a [elementelor individuale |#Atribute HTML]. -Redare manuală .[#toc-manual-rendering] ---------------------------------------- +Randare manuală +--------------- -Fiecare element de formular are metode care generează codul HTML pentru câmpul și eticheta formularului. Acestea îl pot returna fie sub forma unui șir de caractere, fie sub forma unui obiect [Nette\Utils\Html |utils:html-elements]: +Fiecare element de formular dispune de metode care generează codul HTML al câmpului formularului și al etichetei. Îl pot returna fie ca șir, fie ca obiect [Nette\Utils\Html|utils:html-elements]: - `getControl(): Html|string` returnează codul HTML al elementului -- `getLabel($caption = null): Html|string|null` returnează codul HTML al etichetei, dacă există. +- `getLabel($caption = null): Html|string|null` returnează codul HTML al etichetei, dacă există -Acest lucru permite ca formularul să fie redat element cu element: +Formularul poate fi astfel randat element cu element: ```php <?php $form->render('begin') ?> @@ -305,47 +297,46 @@ Acest lucru permite ca formularul să fie redat element cu element: <?php $form->render('end') ?> ``` -În timp ce pentru unele elemente, `getControl()` returnează un singur element HTML (de exemplu. `<input>`, `<select>` etc.), pentru altele se returnează o întreagă bucată de cod HTML (CheckboxList, RadioList). -În acest caz, puteți utiliza metode care generează intrări și etichete individuale, pentru fiecare element în parte: +În timp ce la unele elemente `getControl()` returnează un singur element HTML (de ex. `<input>`, `<select>` etc.), la altele returnează o întreagă bucată de cod HTML (CheckboxList, RadioList). Într-un astfel de caz, puteți utiliza metode care generează input-uri și etichete individuale, pentru fiecare element în parte: -- `getControlPart($key = null): ?Html` returnează codul HTML al unui singur element -- `getLabelPart($key = null): ?Html` returnează codul HTML pentru eticheta unui singur element +- `getControlPart($key = null): ?Html` returnează codul HTML al unui element +- `getLabelPart($key = null): ?Html` returnează codul HTML al etichetei unui element .[note] -Aceste metode sunt prefixate cu `get` din motive istorice, dar `generate` ar fi mai bine, deoarece creează și returnează un nou element `Html` la fiecare apel. +Aceste metode au din motive istorice prefixul `get`, dar mai bun ar fi `generate`, deoarece la fiecare apelare creează și returnează un nou element `Html`. -Redator .[#toc-renderer] -======================== +Renderer +======== -Este un obiect care asigură redarea formularului. Acesta poate fi setat prin metoda `$form->setRenderer`. Este trecut în control atunci când este apelată metoda `$form->render()`. +Este un obiect care asigură randarea formularului. Acesta poate fi setat cu metoda `$form->setRenderer`. I se transmite controlul la apelarea metodei `$form->render()`. -În cazul în care nu setează un renderizator personalizat, se va utiliza renderizatorul implicit [api:Nette\Forms\Rendering\DefaultFormRenderer]. Aceasta va reda elementele formularului sub forma unui tabel HTML. Rezultatul arată astfel: +Dacă nu setăm propriul renderer, va fi utilizat rendererul implicit [api:Nette\Forms\Rendering\DefaultFormRenderer]. Acesta randează elementele formularului sub formă de tabel HTML. Ieșirea arată astfel: ```latte <table> <tr class="required"> - <th><label class="required" for="frm-name">Name:</label></th> + <th><label class="required" for="frm-name">Nume:</label></th> <td><input type="text" class="text" name="name" id="frm-name" required value=""></td> </tr> <tr class="required"> - <th><label class="required" for="frm-age">Age:</label></th> + <th><label class="required" for="frm-age">Vârstă:</label></th> <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> </tr> <tr> - <th><label>Gender:</label></th> + <th><label>Gen:</label></th> ... ``` -Depinde de dvs. dacă doriți să utilizați un tabel sau nu, iar mulți designeri web preferă diferite marcaje, de exemplu o listă. Putem configura `DefaultFormRenderer` astfel încât să nu fie redat deloc într-un tabel. Trebuie doar să setăm [$wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers] corespunzătoare. Primul index reprezintă întotdeauna o zonă, iar al doilea un element. Toate zonele respective sunt prezentate în imagine: +Dacă să folosim sau nu un tabel pentru structura formularului este discutabil, iar mulți webdesigneri preferă alt markup. De exemplu, o listă de definiții. Vom reconfigura, prin urmare, `DefaultFormRenderer` astfel încât să randeze formularul sub formă de listă. Configurarea se realizează prin editarea array-ului [$wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers]. Primul index reprezintă întotdeauna zona, iar al doilea atributul său. Zonele individuale sunt ilustrate în imagine: -[* form-areas-en.webp *] +[* defaultformrenderer.webp *] -În mod implicit, un grup de `controls` este învelit în `<table>`, iar fiecare `pair` este un rând de tabel `<tr>` care conține o pereche de `label` și `control` (celule `<th>` și `<td>`). Haideți să schimbăm toate aceste elemente de înfășurare. Vom transforma `controls` în `<dl>`, vom lăsa `pair` de unul singur, vom pune `label` în `<dt>` și vom transforma `control` în `<dd>`: +În mod standard, grupul de elemente `controls` este încapsulat într-un tabel `<table>`, fiecare `pair` reprezintă un rând de tabel `<tr>`, iar perechea `label` și `control` sunt celule `<th>` și `<td>`. Acum vom schimba elementele încapsulatoare. Vom insera zona `controls` într-un container `<dl>`, vom lăsa zona `pair` fără container, vom insera `label` în `<dt>` și, în final, vom încapsula `control` cu tag-urile `<dd>`: ```php $renderer = $form->getRenderer(); @@ -357,193 +348,192 @@ $renderer->wrappers['control']['container'] = 'dd'; $form->render(); ``` -Rezultă următorul fragment: +Rezultatul este acest cod HTML: ```latte <dl> - <dt><label class="required" for="frm-name">Name:</label></dt> + <dt><label class="required" for="frm-name">Nume:</label></dt> <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> - <dt><label class="required" for="frm-age">Age:</label></dt> + <dt><label class="required" for="frm-age">Vârstă:</label></dt> <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> - <dt><label>Gender:</label></dt> + <dt><label>Gen:</label></dt> ... </dl> ``` -Wrappers pot afecta multe atribute. De exemplu: +În array-ul wrappers se pot influența multe alte atribute: -- adăugați clase CSS speciale la fiecare intrare din formular -- faceți distincția între liniile pare și impare -- faceți ca obligatoriu și opțional să fie desenate diferit -- setați, dacă mesajele de eroare sunt afișate deasupra formularului sau în apropierea fiecărui element +- adăugarea claselor CSS la tipurile individuale de elemente de formular +- distingerea rândurilor pare și impare prin clasa CSS +- diferențierea vizuală a elementelor obligatorii și opționale +- determinarea dacă mesajele de eroare se afișează direct lângă elemente sau deasupra formularului -Opțiuni .[#toc-options] ------------------------ +Opțiuni +------- -Comportamentul lui Renderer poate fi controlat și prin setarea *opțiunilor* pe elementele individuale ale formularului. Astfel, puteți seta tooltip-ul care este afișat lângă câmpul de intrare: +Comportamentul Rendererului poate fi controlat și prin setarea *opțiunilor* pe elementele individuale ale formularului. Astfel, se poate seta o descriere care se va afișa lângă câmpul de intrare: ```php -$form->addText('phone', 'Number:') - ->setOption('description', 'This number will remain hidden'); +$form->addText('phone', 'Număr:') + ->setOption('description', 'Acest număr va rămâne ascuns'); ``` -Dacă dorim să plasăm conținut HTML în el, folosim clasa [Html |utils:html-elements]. +Dacă dorim să plasăm conținut HTML în el, utilizăm clasa [Html |utils:html-elements] ```php use Nette\Utils\Html; -$form->addText('phone', 'Phone:') +$form->addText('phone', 'Număr:') ->setOption('description', Html::el('p') - ->setHtml('<a href="...">Terms of service.</a>') + ->setHtml('<a href="...">Condițiile de păstrare a numărului dumneavoastră</a>') ); ``` .[tip] -Elementul Html poate fi folosit și în locul etichetei: `$form->addCheckbox('conditions', $label)`. +Elementul Html poate fi utilizat și în locul etichetei: `$form->addCheckbox('conditions', $label)`. -Gruparea intrărilor .[#toc-grouping-inputs] -------------------------------------------- +Gruparea elementelor +-------------------- -Renderer permite gruparea elementelor în grupuri vizuale (fieldsets): +Rendererul permite gruparea elementelor în grupuri vizuale (fieldset-uri): ```php -$form->addGroup('Personal data'); +$form->addGroup('Date personale'); ``` -Crearea unui nou grup îl activează - toate elementele adăugate ulterior sunt adăugate la acest grup. Puteți construi un formular în felul următor: +După crearea unui nou grup, acesta devine activ și fiecare element nou adăugat este, de asemenea, adăugat în el. Deci, formularul poate fi construit în acest mod: ```php $form = new Form; -$form->addGroup('Personal data'); -$form->addText('name', 'Your name:'); -$form->addInteger('age', 'Your age:'); +$form->addGroup('Date personale'); +$form->addText('name', 'Numele dumneavoastră:'); +$form->addInteger('age', 'Vârsta dumneavoastră:'); $form->addEmail('email', 'Email:'); -$form->addGroup('Shipping address'); -$form->addCheckbox('send', 'Ship to address'); -$form->addText('street', 'Street:'); -$form->addText('city', 'City:'); -$form->addSelect('country', 'Country:', $countries); +$form->addGroup('Adresa de livrare'); +$form->addCheckbox('send', 'Livrează la adresă'); +$form->addText('street', 'Stradă:'); +$form->addText('city', 'Oraș:'); +$form->addSelect('country', 'Țara:', $countries); ``` +Rendererul randează mai întâi grupurile și abia apoi elementele care nu aparțin niciunui grup. -Suport Bootstrap .[#toc-bootstrap-support] ------------------------------------------- -Puteți găsi [exemple |https://github.com/nette/forms/tree/master/examples] de configurare a Renderer pentru [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], Bootstrap [3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] și [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php] +Suport pentru Bootstrap +----------------------- + +[În exemple |https://github.com/nette/forms/tree/master/examples] veți găsi exemple despre cum să configurați Rendererul pentru [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], [Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] și [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php] -Atribute HTML .[#toc-html-attributes] -===================================== +Atribute HTML +============= -Puteți seta orice atribute HTML pentru controalele de formular folosind `setHtmlAttribute(string $name, $value = true)`: +Pentru a seta atribute HTML arbitrare ale elementelor formularului, folosim metoda `setHtmlAttribute(string $name, $value = true)`: ```php -$form->addInteger('number', 'Number:') +$form->addInteger('number', 'Număr:') ->setHtmlAttribute('class', 'big-number'); -$form->addSelect('rank', 'Order by:', ['price', 'name']) - ->setHtmlAttribute('onchange', 'submit()'); // solicită funcția JS submit() la schimbare +$form->addSelect('rank', 'Sortare după:', ['preț', 'nume']) + ->setHtmlAttribute('onchange', 'submit()'); // trimite la modificare -// se aplică la <form> +// Pentru a seta atributele formularului <form> în sine $form->setHtmlAttribute('id', 'myForm'); ``` -Setarea tipului de intrare: +Specificarea tipului elementului: ```php -$form->addText('tel', 'Your telephone:') +$form->addText('tel', 'Telefonul dumneavoastră:') ->setHtmlType('tel') - ->setHtmlAttribute('placeholder', 'Please, fill in your telephone'); + ->setHtmlAttribute('placeholder', 'scrieți telefonul'); ``` -Putem seta atributul HTML pentru elementele individuale din listele radio sau checkbox cu valori diferite pentru fiecare dintre ele. -Rețineți două puncte după `style:` pentru a vă asigura că valoarea este selectată prin cheie: +.[warning] +Setarea tipului și a altor atribute servește doar în scopuri vizuale. Verificarea corectitudinii intrărilor trebuie să aibă loc pe server, ceea ce asigurați prin alegerea [elementului de formular|controls] adecvat și specificarea [regulilor de validare|validation]. + +Elementelor individuale din listele radio sau checkbox le putem seta un atribut HTML cu valori diferite pentru fiecare dintre ele. Observați două puncte după `style:`, care asigură alegerea valorii după cheie: ```php -$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue']; +$colors = ['r' => 'roșu', 'g' => 'verde', 'b' => 'albastru']; $styles = ['r' => 'background:red', 'g' => 'background:green']; -$form->addCheckboxList('colors', 'Colors:', $colors) +$form->addCheckboxList('colors', 'Culori:', $colors) ->setHtmlAttribute('style:', $styles); ``` -Redă: +Afișează: ```latte -<label><input type="checkbox" name="colors[]" style="background:red" value="r">red</label> -<label><input type="checkbox" name="colors[]" style="background:green" value="g">green</label> -<label><input type="checkbox" name="colors[]" value="b">blue</label> +<label><input type="checkbox" name="colors[]" style="background:red" value="r">roșu</label> +<label><input type="checkbox" name="colors[]" style="background:green" value="g">verde</label> +<label><input type="checkbox" name="colors[]" value="b">albastru</label> ``` -Pentru un atribut HTML logic (care nu are nicio valoare, cum ar fi `readonly`), puteți utiliza un semn de întrebare: +Pentru a seta atribute logice, cum ar fi `readonly`, putem folosi notația cu semn de întrebare: ```php -$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue']; -$form->addCheckboxList('colors', 'Colors:', $colors) - ->setHtmlAttribute('readonly?', 'r'); // se utilizează matrice pentru mai multe chei, de exemplu ['r', 'g'] +$form->addCheckboxList('colors', 'Culori:', $colors) + ->setHtmlAttribute('readonly?', 'r'); // pentru mai multe chei utilizați un array, de ex. ['r', 'g'] ``` -Redă: +Afișează: ```latte -<label><input type="checkbox" name="colors[]" readonly value="r">red</label> -<label><input type="checkbox" name="colors[]" value="g">green</label> -<label><input type="checkbox" name="colors[]" value="b">blue</label> +<label><input type="checkbox" name="colors[]" readonly value="r">roșu</label> +<label><input type="checkbox" name="colors[]" value="g">verde</label> +<label><input type="checkbox" name="colors[]" value="b">albastru</label> ``` -Pentru casetele de selectare, metoda `setHtmlAttribute()` setează atributele elementelor de tip `<select>` element. Dacă dorim să setăm atributele pentru fiecare -`<option>`, vom folosi metoda `setOptionAttribute()`. De asemenea, funcționează și cele două puncte și semnul de întrebare folosite mai sus: +În cazul selectbox-urilor, metoda `setHtmlAttribute()` setează atributele elementului `<select>`. Dacă dorim să setăm atributele elementelor individuale `<option>`, folosim metoda `setOptionAttribute()`. Funcționează și notațiile cu două puncte și semn de întrebare menționate mai sus: ```php -$form->addSelect('colors', 'Colors:', $colors) +$form->addSelect('colors', 'Culori:', $colors) ->setOptionAttribute('style:', $styles); ``` -Redă: +Afișează: ```latte <select name="colors"> - <option value="r" style="background:red">red</option> - <option value="g" style="background:green">green</option> - <option value="b">blue</option> + <option value="r" style="background:red">roșu</option> + <option value="g" style="background:green">verde</option> + <option value="b">albastru</option> </select> ``` -Prototipuri .[#toc-prototypes] ------------------------------- +Prototipuri +----------- -O modalitate alternativă de a seta atributele HTML este de a modifica șablonul din care este generat elementul HTML. Șablonul este un obiect `Html` și este returnat de metoda `getControlPrototype()`: +O modalitate alternativă de setare a atributelor HTML constă în modificarea șablonului din care se generează elementul HTML. Șablonul este un obiect `Html` și este returnat de metoda `getControlPrototype()`: ```php -$input = $form->addInteger('number'); +$input = $form->addInteger('number', 'Număr:'); $html = $input->getControlPrototype(); // <input> $html->class('big-number'); // <input class="big-number"> ``` -Șablonul de etichetă returnat de `getLabelPrototype()` poate fi, de asemenea, modificat în acest mod: +În acest mod se poate modifica și șablonul etichetei, returnat de `getLabelPrototype()`: ```php $html = $input->getLabelPrototype(); // <label> $html->class('distinctive'); // <label class="distinctive"> ``` -Pentru elementele Checkbox, CheckboxList și RadioList puteți influența șablonul elementului care înfășoară elementul. Acesta este returnat de `getContainerPrototype()`. În mod implicit, este un element "gol", deci nu este redat nimic, dar dacă i se dă un nume, acesta va fi redat: +La elementele Checkbox, CheckboxList și RadioList puteți influența șablonul elementului care încapsulează întregul element. Acesta este returnat de `getContainerPrototype()`. În starea implicită, este un element „gol”, deci nu se randează nimic, dar prin setarea numelui său, va începe să se randeze: ```php $input = $form->addCheckbox('send'); -echo $input->getControl(); -// <label><input type="checkbox" name="send"></label> - $html = $input->getContainerPrototype(); $html->setName('div'); // <div> $html->class('check'); // <div class="check"> @@ -551,50 +541,49 @@ echo $input->getControl(); // <div class="check"><label><input type="checkbox" name="send"></label></div> ``` -În cazul CheckboxList și RadioList este, de asemenea, posibil să se influențeze modelul separatorului de elemente returnat de metoda `getSeparatorPrototype()`. În mod implicit, acesta este un element `<br>`. Dacă îl schimbați într-un element pereche, acesta va înfășura elementele individuale în loc să le separe. -De asemenea, este posibil să se influențeze șablonul elementului HTML al etichetelor elementelor, care returnează `getItemLabelPrototype()`. +În cazul CheckboxList și RadioList, se poate influența și șablonul separatorului elementelor individuale, returnat de metoda `getSeparatorPrototype()`. În starea implicită, este elementul `<br>`. Dacă îl schimbați într-un element pereche, va încapsula elementele individuale în loc să le separe. Și, în plus, se poate influența șablonul elementului HTML al etichetei la elementele individuale, returnat de `getItemLabelPrototype()`. -Traducerea .[#toc-translating] -============================== +Traducere +========= -Dacă programați o aplicație multilingvă, probabil că va trebui să redați formularul în diferite limbi. Cadrul Nette Framework definește o interfață de traducere în acest scop [api:Nette\Localization\Translator]. Nu există o implementare implicită în Nette, puteți alege în funcție de nevoile dumneavoastră din mai multe soluții gata făcute pe care le puteți găsi pe [Componette |https://componette.org/search/localization]. Documentația acestora vă indică modul de configurare a traducătorului. +Dacă programați o aplicație multilingvă, probabil veți avea nevoie să randați formularul în diferite versiuni lingvistice. Nette Framework definește în acest scop o interfață pentru traducere [api:Nette\Localization\Translator]. În Nette nu există o implementare implicită, puteți alege în funcție de nevoile dumneavoastră din mai multe soluții gata făcute, pe care le găsiți pe [Componette |https://componette.org/search/localization]. În documentația lor veți afla cum să configurați translatorul. -Formularul suportă ieșirea de text prin intermediul traducătorului. Îl transmitem folosind metoda `setTranslator()`: +Formularele suportă afișarea textelor prin translator. Îl transmitem folosind metoda `setTranslator()`: ```php $form->setTranslator($translator); ``` -De acum înainte, nu numai toate etichetele, ci și toate mesajele de eroare sau intrările din căsuțele de selectare vor fi traduse în altă limbă. +De acum înainte, nu numai toate etichetele, ci și toate mesajele de eroare sau elementele select box-urilor vor fi traduse într-o altă limbă. -Este posibil să setați un traducător diferit pentru elementele individuale ale formularului sau să dezactivați complet traducerea cu `null`: +La elementele individuale ale formularului este posibil să setați un alt traducător sau să dezactivați complet traducerea cu valoarea `null`: ```php $form->addSelect('carModel', 'Model:', $cars) ->setTranslator(null); ``` -Pentru [regulile de validare |validation], parametrii specifici sunt, de asemenea, trecuți traducătorului, de exemplu pentru regula: +La [regulile de validare|validation], translatorului i se transmit și parametri specifici, de exemplu la regula: ```php -$form->addPassword('password', 'Password:') - ->addRule($form::MinLength, 'Password has to be at least %d characters long', 8) +$form->addPassword('password', 'Parolă:') + ->addRule($form::MinLength, 'Parola trebuie să aibă cel puțin %d caractere', 8); ``` -traducătorul este apelat cu următorii parametri: +se apelează translatorul cu acești parametri: ```php -$translator->translate('Password has to be at least %d characters long', 8); +$translator->translate('Parola trebuie să aibă cel puțin %d caractere', 8); ``` -și astfel poate alege forma corectă de plural pentru cuvântul `characters` by count. +și, prin urmare, poate alege forma corectă de plural a cuvântului `caractere` în funcție de număr. -Evenimentul onRender .[#toc-event-onrender] -=========================================== +Evenimentul onRender +==================== -Chiar înainte ca formularul să fie redat, putem invoca codul nostru. Acesta poate, de exemplu, să adauge clase HTML la elementele formularului pentru o afișare corectă. Adăugăm codul în matricea `onRender`: +Chiar înainte ca formularul să fie randat, putem lăsa să fie apelat codul nostru. Acesta poate, de exemplu, să completeze elementele formularului cu clase HTML pentru afișarea corectă. Adăugăm codul în array-ul `onRender`: ```php $form->onRender[] = function ($form) { diff --git a/forms/ro/standalone.texy b/forms/ro/standalone.texy index 34ec299bb0..58562a54a5 100644 --- a/forms/ro/standalone.texy +++ b/forms/ro/standalone.texy @@ -1,43 +1,43 @@ -Formulare utilizate Standalone -****************************** +Formulare utilizate independent +******************************* .[perex] -Nette Forms facilitează în mod dramatic crearea și procesarea formularelor web. Le puteți utiliza în aplicațiile dumneavoastră complet singure, fără restul cadrului, lucru pe care îl vom demonstra în acest capitol. +Nette Forms facilitează enorm crearea și procesarea formularelor web. Le puteți utiliza în aplicațiile dvs. complet independent de restul framework-ului, așa cum vom demonstra în acest capitol. -Cu toate acestea, dacă folosiți Nette Application și [prezentări |in-presenter], există un ghid pentru dumneavoastră: [Formulare în prezentări |in-presenter]. +Dacă însă utilizați Nette Application și presentere, ghidul pentru [utilizarea în presentere|in-presenter] este destinat dvs. -Primul formular .[#toc-first-form] -================================== +Primul formular +=============== -Vom încerca să scriem un formular de înregistrare simplu. Codul acestuia va arăta astfel ("full code":https://gist.github.com/dg/370a7e3094d9ba9a9e913b8e2a2dc851): +Vom încerca să scriem un formular simplu de înregistrare. Codul său va fi următorul ("codul complet":https://gist.github.com/dg/57878c1a413ae8ef0c1d83f02c43ef3f): ```php use Nette\Forms\Form; $form = new Form; -$form->addText('name', 'Name:'); -$form->addPassword('password', 'Password:'); -$form->addSubmit('send', 'Sign up'); +$form->addText('name', 'Nume:'); +$form->addPassword('password', 'Parolă:'); +$form->addSubmit('send', 'Înregistrare'); ``` -Și haideți să îl redăm: +Îl putem randa foarte ușor: ```php $form->render(); ``` -iar rezultatul ar trebui să arate așa: +și în browser se va afișa astfel: -[* form-en.webp *] +[* form-cs.webp *] -Formularul este un obiect din clasa `Nette\Forms\Form` (clasa `Nette\Application\UI\Form` este utilizată în prezentatori). I-am adăugat controalele nume, parolă și butonul de trimitere. +Formularul este un obiect al clasei `Nette\Forms\Form` (clasa `Nette\Application\UI\Form` se utilizează în presentere). Am adăugat în el așa-numitele elemente nume, parolă și buton de trimitere. -Acum vom reactiva formularul. Întrebând `$form->isSuccess()`, vom afla dacă formularul a fost trimis și dacă a fost completat în mod valid. Dacă da, vom descărca datele. După definirea formularului vom adăuga: +Acum vom anima formularul. Prin interogarea `$form->isSuccess()` vom afla dacă formularul a fost trimis și dacă a fost completat valid. Dacă da, vom afișa datele. După definiția formularului, vom adăuga: ```php if ($form->isSuccess()) { - echo 'The form has been filled in and submitted correctly'; + echo 'Formularul a fost completat corect și trimis'; $data = $form->getValues(); // $data->name conține numele // $data->password conține parola @@ -45,72 +45,71 @@ if ($form->isSuccess()) { } ``` -Metoda `getValues()` returnează datele trimise sub forma unui obiect [ArrayHash |utils:arrays#ArrayHash]. Vom arăta cum să modificăm acest lucru [mai târziu |#Mapping to Classes]. Variabila `$data` conține cheile `name` și `password` cu datele introduse de utilizator. +Metoda `getValues()` returnează datele trimise sub forma unui obiect [ArrayHash |utils:arrays#ArrayHash]. Cum să schimbăm acest lucru vom arăta [mai târziu |#Maparea pe clase]. Obiectul `$data` conține cheile `name` și `password` cu datele completate de utilizator. -De obicei, trimitem datele direct pentru o prelucrare ulterioară, care poate fi, de exemplu, inserarea în baza de date. Cu toate acestea, poate apărea o eroare în timpul procesării, de exemplu, numele de utilizator este deja ocupat. În acest caz, transmitem eroarea înapoi la formular folosind `addError()` și îl lăsăm să se redeseneze, cu un mesaj de eroare: +De obicei, trimitem datele direct pentru procesare ulterioară, cum ar fi inserarea într-o bază de date. Însă, în timpul procesării poate apărea o eroare, de exemplu, numele de utilizator este deja ocupat. În acest caz, transmitem eroarea înapoi formularului folosind `addError()` și îl lăsăm să se randeze din nou, inclusiv cu mesajul de eroare. ```php -$form->addError('Sorry, username is already in use.'); +$form->addError('Ne pare rău, acest nume de utilizator este deja folosit.'); ``` -După procesarea formularului, vom redirecționa către pagina următoare. Acest lucru împiedică retrimiterea neintenționată a formularului prin apăsarea butonului *refresh*, *back* sau prin mutarea istoricului browserului. +După procesarea formularului, redirecționăm către pagina următoare. Acest lucru previne retrimiterea nedorită a formularului prin butonul *reîmprospătare*, *înapoi* sau prin navigarea în istoricul browserului. -În mod implicit, formularul este trimis folosind metoda POST către aceeași pagină. Ambele pot fi modificate: +Formularul se trimite standard prin metoda POST și către aceeași pagină. Ambele pot fi modificate: ```php $form->setAction('/submit.php'); $form->setMethod('GET'); ``` -Și asta e tot :-) Avem un formular funcțional și perfect [securizat |#Vulnerability Protection]. +Și cam asta e tot :-) Avem un formular funcțional și perfect [securizat |#Protecția împotriva vulnerabilităților]. -Încercați să adăugați mai multe [controale de formular |controls]. +Încercați să adăugați și alte [elemente de formular|controls]. -Accesul la controale .[#toc-access-to-controls] -=============================================== +Accesul la elemente +=================== -Formularul și controalele sale individuale se numesc componente. Acestea creează un arbore de componente, în care rădăcina este formularul. Puteți accesa controalele individuale după cum urmează: +Formularul și elementele sale individuale le numim componente. Ele formează un arbore de componente, unde rădăcina este chiar formularul. Putem accesa elementele individuale ale formularului în acest mod: ```php $input = $form->getComponent('name'); -// sintaxa alternativă: $input = $form['name']; +// sintaxă alternativă: $input = $form['name']; $button = $form->getComponent('send'); // sintaxă alternativă: $button = $form['send']; ``` -Controalele sunt eliminate folosind unset: +Elementele se elimină folosind unset: ```php unset($form['name']); ``` -Reguli de validare .[#toc-validation-rules] -=========================================== +Reguli de validare +================== -Aici a fost folosit cuvântul *valid*, dar formularul nu are încă reguli de validare. Să reparăm acest lucru. +Am menționat cuvântul *valid*, dar formularul nu are încă nicio regulă de validare. Să remediem acest lucru. -Numele va fi obligatoriu, așa că îl vom marca cu metoda `setRequired()`, al cărei argument este textul mesajului de eroare care va fi afișat în cazul în care utilizatorul nu îl completează. Dacă nu se dă niciun argument, se va folosi mesajul de eroare implicit. +Numele va fi obligatoriu, așa că îl vom marca cu metoda `setRequired()`, al cărei argument este textul mesajului de eroare care se va afișa dacă utilizatorul nu completează numele. Dacă nu specificăm argumentul, se va utiliza mesajul de eroare implicit. ```php -$form->addText('name', 'Name:') - ->setRequired('Please enter a name.'); +$form->addText('name', 'Nume:') + ->setRequired('Vă rugăm să introduceți numele'); ``` -Încercați să trimiteți formularul fără ca numele să fie completat și veți vedea că se afișează un mesaj de eroare, iar browserul sau serverul îl va respinge până când îl veți completa. +Încercați să trimiteți formularul fără a completa numele și veți vedea că se afișează un mesaj de eroare, iar browserul sau serverul îl va respinge până când nu completați câmpul. -În același timp, nu veți putea păcăli sistemul introducând doar spații în câmp, de exemplu. În niciun caz. Nette taie automat spațiile albe din stânga și din dreapta. Încercați. Este un lucru pe care ar trebui să-l faceți întotdeauna la fiecare intrare pe o singură linie, dar este adesea uitat. Nette o face automat. (Puteți încerca să păcăliți formularele și să trimiteți un șir de caractere multiliniar ca nume. Chiar și în acest caz, Nette nu se va lăsa păcălit și întreruperile de linie se vor schimba în spații). +În același timp, nu puteți păcăli sistemul scriind, de exemplu, doar spații în câmp. Nici vorbă. Nette elimină automat spațiile de la începutul și sfârșitul șirului. Încercați. Este un lucru pe care ar trebui să-l faceți întotdeauna cu fiecare input de o singură linie, dar adesea se uită. Nette o face automat. (Puteți încerca să păcăliți formularul și să trimiteți un șir multilinie ca nume. Nici aici Nette nu se lasă păcălit și transformă sfârșiturile de linie în spații.) -Formularul este întotdeauna validat pe partea serverului, dar este generată și validarea JavaScript, care este rapidă, iar utilizatorul știe imediat de eroare, fără a fi nevoie să trimită formularul la server. Acest lucru este gestionat de scriptul `netteForms.js`. -Adăugați-l în pagină: +Formularul se validează întotdeauna pe partea de server, dar se generează și o validare JavaScript, care se execută instantaneu, iar utilizatorul află despre eroare imediat, fără a fi nevoie să trimită formularul la server. Acest lucru este gestionat de scriptul `netteForms.js`. Inserați-l în pagină: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Dacă vă uitați în codul sursă al paginii cu formular, puteți observa că Nette inserează câmpurile obligatorii în elemente cu o clasă CSS `required`. Încercați să adăugați următorul stil în șablon, iar eticheta "Name" va fi roșie. În mod elegant, marcăm câmpurile obligatorii pentru utilizatori: +Dacă vă uitați la codul sursă al paginii cu formularul, puteți observa că Nette inserează elementele obligatorii în elemente cu clasa CSS `required`. Încercați să adăugați următorul stil în șablon și eticheta „Nume” va fi roșie. Astfel, marcăm elegant elementele obligatorii pentru utilizatori: ```latte <style> @@ -118,96 +117,96 @@ Dacă vă uitați în codul sursă al paginii cu formular, puteți observa că N </style> ``` -Reguli de validare suplimentare vor fi adăugate prin metoda `addRule()`. Primul parametru este regula, al doilea este din nou textul mesajului de eroare, iar argumentul opțional al regulii de validare poate urma. Ce înseamnă acest lucru? +Alte reguli de validare le adăugăm cu metoda `addRule()`. Primul parametru este regula, al doilea este din nou textul mesajului de eroare și poate urma un argument al regulii de validare. Ce înseamnă asta? -Formularul va primi o altă intrare opțională *age* cu condiția ca aceasta să fie un număr (`addInteger()`) și să se încadreze în anumite limite (`$form::Range`). Și aici vom folosi al treilea argument din `addRule()`, intervalul propriu-zis: +Vom extinde formularul cu un nou câmp opțional „vârstă”, care trebuie să fie un număr întreg (`addInteger()`) și, în plus, într-un interval permis (`$form::Range`). Și aici vom folosi al treilea parametru al metodei `addRule()`, prin care transmitem validatorului intervalul dorit ca pereche `[de la, până la]`: ```php -$form->addInteger('age', 'Age:') - ->addRule($form::Range, 'You must be older 18 years and be under 120.', [18, 120]); +$form->addInteger('age', 'Vârstă:') + ->addRule($form::Range, 'Vârsta trebuie să fie între 18 și 120', [18, 120]); ``` .[tip] -În cazul în care utilizatorul nu completează câmpul, regulile de validare nu vor fi verificate, deoarece câmpul este opțional. +Dacă utilizatorul nu completează câmpul, regulile de validare nu vor fi verificate, deoarece elementul este opțional. -Evident, există loc pentru o mică refactorizare. În mesajul de eroare și în cel de-al treilea parametru, numerele sunt listate în dublu exemplar, ceea ce nu este ideal. Dacă am crea un [formular multilingv |rendering#translating], iar mesajul care conține numere ar trebui tradus în mai multe limbi, ar îngreuna modificarea valorilor. Din acest motiv, se pot utiliza caracterele de substituție `%d`: +Aici apare spațiu pentru o mică refactorizare. În mesajul de eroare și în al treilea parametru, numerele sunt menționate duplicat, ceea ce nu este ideal. Dacă am crea [formulare multilingve |rendering#Traducere] și mesajul care conține numere ar fi tradus în mai multe limbi, o eventuală modificare a valorilor ar fi dificilă. Din acest motiv, este posibil să folosim substituenții `%d`, iar Nette va completa valorile: ```php - ->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]); + ->addRule($form::Range, 'Vârsta trebuie să fie între %d și %d ani', [18, 120]); ``` -Să ne întoarcem la câmpul *parolă*, să îl facem *obligatoriu* și să verificăm lungimea minimă a parolei (`$form::MinLength`), folosind din nou caracterele de substituție din mesaj: +Să revenim la elementul `password`, pe care îl vom face, de asemenea, obligatoriu și vom verifica lungimea minimă a parolei (`$form::MinLength`), folosind din nou substituentul: ```php -$form->addPassword('password', 'Password:') - ->setRequired('Pick a password') - ->addRule($form::MinLength, 'Your password has to be at least %d long', 8); +$form->addPassword('password', 'Parolă:') + ->setRequired('Alegeți o parolă') + ->addRule($form::MinLength, 'Parola trebuie să aibă cel puțin %d caractere', 8); ``` -Vom adăuga un câmp `passwordVerify` la formular, în care utilizatorul introduce din nou parola, pentru verificare. Folosind reguli de validare, vom verifica dacă ambele parole sunt identice (`$form::Equal`). Iar ca argument vom da o referință la prima parolă folosind [paranteze pătrate |#Access to Controls]: +Vom adăuga în formular și câmpul `passwordVerify`, unde utilizatorul introduce parola încă o dată, pentru verificare. Folosind regulile de validare, vom verifica dacă ambele parole sunt identice (`$form::Equal`). Și ca parametru vom da o referință la prima parolă folosind [paranteze drepte |#Accesul la elemente]: ```php -$form->addPassword('passwordVerify', 'Password again:') - ->setRequired('Fill your password again to check for typo') - ->addRule($form::Equal, 'Password mismatch', $form['password']) +$form->addPassword('passwordVerify', 'Parola pentru verificare:') + ->setRequired('Vă rugăm să introduceți parola din nou pentru verificare') + ->addRule($form::Equal, 'Parolele nu se potrivesc', $form['password']) ->setOmitted(); ``` -Folosind `setOmitted()`, marcăm un element a cărui valoare nu ne interesează cu adevărat și care există doar pentru validare. Valoarea sa nu este transmisă la `$data`. +Folosind `setOmitted()`, am marcat elementul a cărui valoare nu ne interesează de fapt și care există doar în scopul validării. Valoarea nu se va transmite în `$data`. -Avem un formular complet funcțional cu validare în PHP și JavaScript. Capacitățile de validare ale Nette sunt mult mai largi, puteți crea condiții, afișa și ascunde părți ale unei pagini în funcție de acestea etc. Puteți afla totul în capitolul dedicat [validării formularelor |validation]. +Astfel, avem un formular complet funcțional cu validare în PHP și JavaScript. Capacitățile de validare ale Nette sunt mult mai extinse, se pot crea condiții, se pot afișa și ascunde părți ale paginii în funcție de acestea etc. Veți afla totul în capitolul despre [validarea formularelor|validation]. -Valori implicite .[#toc-default-values] -======================================= +Valori implicite +================ -Adesea se stabilesc valori implicite pentru controalele de formular: +Elementelor formularului le setăm în mod obișnuit valori implicite: ```php -$form->addEmail('email', 'Email') +$form->addEmail('email', 'E-mail') ->setDefaultValue($lastUsedEmail); ``` -Adesea este util să setați valorile implicite pentru toate controalele deodată. De exemplu, atunci când formularul este utilizat pentru a edita înregistrări. Citim înregistrarea din baza de date și o setăm ca valoare implicită: +Adesea este util să setăm valori implicite pentru toate elementele simultan. De exemplu, când formularul servește la editarea înregistrărilor. Citim înregistrarea din baza de date și setăm valorile implicite: ```php //$row = ['name' => 'John', 'age' => '33', /* ... */]; $form->setDefaults($row); ``` -Apelați `setDefaults()` după definirea controalelor. +Apelați `setDefaults()` după definirea elementelor. -Redarea formularului .[#toc-rendering-the-form] -=============================================== +Randarea formularului +===================== -În mod implicit, formularul este redat sub forma unui tabel. Controalele individuale respectă orientările de bază privind accesibilitatea web. Toate etichetele sunt generate ca `<label>` elemente și sunt asociate cu intrările lor, făcând clic pe etichetă se mută cursorul pe intrare. +Standard, formularul se randează ca un tabel. Elementele individuale respectă regula de bază a accesibilității - toate etichetele sunt scrise ca `<label>` și legate de elementul de formular corespunzător. La clic pe etichetă, cursorul apare automat în câmpul formularului. -Putem seta orice atribute HTML pentru fiecare element. De exemplu, adăugați un marcaj de loc: +Fiecărui element îi putem seta atribute HTML arbitrare. De exemplu, adăugăm un placeholder: ```php -$form->addInteger('age', 'Age:') - ->setHtmlAttribute('placeholder', 'Please fill in the age'); +$form->addInteger('age', 'Vârstă:') + ->setHtmlAttribute('placeholder', 'Vă rugăm să completați vârsta'); ``` -Există într-adevăr o mulțime de moduri de a reda un formular, așa că este un [capitol |rendering] dedicat [redării |rendering]. +Există într-adevăr o mare varietate de moduri de a randa un formular, așa că acestui subiect i se dedică un [capitol separat despre randare|rendering]. -Maparea în clase .[#toc-mapping-to-classes] -=========================================== +Maparea pe clase +================ -Să ne întoarcem la procesarea datelor din formulare. Metoda `getValues()` a returnat datele trimise sub forma unui obiect `ArrayHash`. Deoarece aceasta este o clasă generică, ceva de genul `stdClass`, ne vor lipsi unele facilități atunci când lucrăm cu ea, cum ar fi completarea codului pentru proprietăți în editori sau analiza statică a codului. Acest lucru ar putea fi rezolvat prin existența unei clase specifice pentru fiecare formular, ale cărei proprietăți să reprezinte controalele individuale. De ex: +Să revenim la procesarea datelor formularului. Metoda `getValues()` ne returna datele trimise ca obiect `ArrayHash`. Deoarece este o clasă generică, ceva de genul `stdClass`, ne va lipsi un anumit confort în lucrul cu ea, cum ar fi sugestiile de proprietăți în editori sau analiza statică a codului. Acest lucru ar putea fi rezolvat având o clasă specifică pentru fiecare formular, ale cărei proprietăți reprezintă elementele individuale. De ex.: ```php class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -Începând cu PHP 8.0, puteți utiliza această notație elegantă care folosește un constructor: +Alternativ, puteți utiliza constructorul: ```php class RegistrationFormData @@ -221,16 +220,18 @@ class RegistrationFormData } ``` -Cum să îi spunem lui Nette să ne returneze datele ca obiecte din această clasă? Mai ușor decât credeți. Tot ce trebuie să faceți este să specificați ca parametru numele clasei sau obiectul de hidratat: +Proprietățile clasei de date pot fi, de asemenea, enumuri și vor fi mapate automat. .{data-version:3.2.4} + +Cum să spunem Nette să ne returneze datele ca obiecte ale acestei clase? Mai ușor decât credeți. Este suficient să specificați numele clasei sau obiectul de hidratat ca parametru: ```php $data = $form->getValues(RegistrationFormData::class); $name = $data->name; ``` -Un `'array'` poate fi, de asemenea, specificat ca parametru, iar apoi datele se întorc sub forma unui array. +Ca parametru se poate specifica și `'array'`, iar datele vor fi returnate ca array. -În cazul în care formularele constau într-o structură pe mai multe niveluri compusă din containere, creați o clasă separată pentru fiecare dintre acestea: +Dacă formularele formează o structură multinivel compusă din containere, creați o clasă separată pentru fiecare: ```php $form = new Form; @@ -247,26 +248,28 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` -În acest caz, cartografierea știe din tipul de proprietate `$person` că trebuie să mapeze containerul la clasa `PersonFormData`. În cazul în care proprietatea ar trebui să conțină o matrice de containere, furnizați tipul `array` și treceți clasa care urmează să fie mapată direct la container: +Maparea va recunoaște apoi din tipul proprietății `$person` că trebuie să mapeze containerul la clasa `PersonFormData`. Dacă proprietatea ar conține un array de containere, specificați tipul `array` și transmiteți clasa pentru mapare direct containerului: ```php $person->setMappedType(PersonFormData::class); ``` +Puteți genera schița clasei de date a formularului folosind metoda `Nette\Forms\Blueprint::dataClass($form)`, care o va afișa în pagina browserului. Apoi, este suficient să selectați codul cu un clic și să îl copiați în proiect. .{data-version:3.1.15} + -Butoane de trimitere multiple .[#toc-multiple-submit-buttons] -============================================================= +Mai multe butoane +================= -În cazul în care formularul are mai multe butoane, de obicei trebuie să distingem care dintre ele a fost apăsat. Metoda `isSubmittedBy()` a butonului ne returnează această informație: +Dacă formularul are mai mult de un buton, de obicei trebuie să distingem care dintre ele a fost apăsat. Această informație ne este returnată de metoda `isSubmittedBy()` a butonului: ```php -$form->addSubmit('save', 'Save'); -$form->addSubmit('delete', 'Delete'); +$form->addSubmit('save', 'Salvare'); +$form->addSubmit('delete', 'Ștergere'); if ($form->isSuccess()) { if ($form['save']->isSubmittedBy()) { @@ -279,37 +282,36 @@ if ($form->isSuccess()) { } ``` -Nu omiteți `$form->isSuccess()` pentru a verifica validitatea datelor. +Nu omiteți interogarea `$form->isSuccess()`, aceasta verifică validitatea datelor. -Atunci când un formular este trimis cu tasta <kbd>Enter</kbd>, acesta este tratat ca și cum ar fi fost trimis cu primul buton. +Când formularul este trimis cu tasta <kbd>Enter</kbd>, se consideră ca și cum ar fi fost trimis cu primul buton. -Protecția împotriva vulnerabilităților .[#toc-vulnerability-protection] -======================================================================= +Protecția împotriva vulnerabilităților +====================================== -Nette Framework depune un mare efort pentru a fi sigur și, deoarece formularele sunt cele mai frecvente intrări ale utilizatorului, formularele Nette sunt ca și impenetrabile. +Nette Framework acordă o mare importanță securității și, prin urmare, are grijă deosebită de securizarea formularelor. -Pe lângă protejarea formularelor împotriva atacurilor vulnerabilităților bine cunoscute, cum ar fi Cross-Site [Scripting (XSS |nette:glossary#cross-site-scripting-xss] [) |nette:glossary#cross-site-request-forgery-csrf] și [Cross-Site Request Forgery (CSRF) |nette:glossary#cross-site-request-forgery-csrf], face o mulțime de mici sarcini de securitate la care nu mai trebuie să vă gândiți. +Pe lângă protejarea formularelor împotriva atacurilor [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] și [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF], realizează o mulțime de mici măsuri de securitate la care nu mai trebuie să vă gândiți. -De exemplu, filtrează toate caracterele de control din intrări și verifică validitatea codificării UTF-8, astfel încât datele din formular vor fi întotdeauna curate. Pentru căsuțele de selectare și listele radio, verifică dacă elementele selectate sunt într-adevăr dintre cele oferite și dacă nu a existat nicio falsificare. Am menționat deja că, pentru introducerea textului pe o singură linie, elimină caracterele de sfârșit de linie pe care un atacator le-ar putea trimite acolo. În cazul intrărilor de text pe mai multe linii, normalizează caracterele de sfârșit de linie. Și așa mai departe. +De exemplu, filtrează toate caracterele de control din intrări și verifică validitatea codificării UTF-8, astfel încât datele din formular vor fi întotdeauna curate. Pentru casetele de selecție și listele radio, verifică dacă elementele selectate au fost într-adevăr dintre cele oferite și nu a avut loc o falsificare. Am menționat deja că pentru intrările de text de o singură linie elimină caracterele de sfârșit de linie pe care un atacator le-ar fi putut trimite. Pentru intrările multilinie, normalizează caracterele de sfârșit de linie. Și așa mai departe. -Nette rezolvă pentru dumneavoastră vulnerabilitățile de securitate despre care majoritatea programatorilor habar nu au că există. +Nette rezolvă pentru dvs. riscurile de securitate despre care mulți programatori nici nu știu că există. -Atacul CSRF menționat constă în faptul că un atacator atrage victima să viziteze o pagină care execută în tăcere o cerere în browserul victimei către serverul unde victima este conectată în acel moment, iar serverul crede că cererea a fost făcută de către victimă în mod voit. Prin urmare, Nette împiedică trimiterea formularului prin POST de pe un alt domeniu. Dacă dintr-un motiv oarecare doriți să dezactivați protecția și să permiteți ca formularul să fie trimis de pe un alt domeniu, utilizați: +Atacul CSRF menționat constă în faptul că atacatorul atrage victima pe o pagină care execută discret în browserul victimei o cerere către serverul pe care victima este autentificată, iar serverul crede că cererea a fost executată de victimă din proprie inițiativă. De aceea, Nette previne trimiterea formularelor POST de pe un alt domeniu. Dacă, din anumite motive, doriți să dezactivați protecția și să permiteți trimiterea formularului de pe un alt domeniu, utilizați: ```php $form->allowCrossOrigin(); // ATENȚIE! Dezactivează protecția! ``` -Această protecție utilizează un cookie SameSite numit `_nss`. Prin urmare, creați un formular înainte de a trimite prima ieșire, astfel încât cookie-ul să poată fi trimis. +Această protecție utilizează un cookie SameSite numit `_nss`. Prin urmare, creați obiectul formularului înainte de a trimite prima ieșire, pentru a putea trimite cookie-ul. -Este posibil ca protecția cookie-urilor SameSite să nu fie 100% fiabilă, așa că este o idee bună să activați protecția cu token-uri: +Protecția prin cookie SameSite poate să nu fie 100% fiabilă, de aceea este recomandat să activați și protecția prin token: ```php $form->addProtection(); ``` -Se recomandă cu tărie să aplicați această protecție formularelor dintr-o parte administrativă a aplicației dvs. care modifică date sensibile. Cadrul protejează împotriva unui atac CSRF prin generarea și validarea token-ului de autentificare care este stocat într-o sesiune (argumentul este mesajul de eroare afișat în cazul în care token-ul a expirat). De aceea, este necesar să aveți o sesiune pornită înainte de a afișa formularul. În partea de administrare a site-ului web, de obicei, sesiunea este deja începută, datorită autentificării utilizatorului. -În caz contrar, începeți sesiunea cu metoda `Nette\Http\Session::start()`. +Recomandăm protejarea în acest mod a formularelor din partea de administrare a site-ului, care modifică date sensibile în aplicație. Framework-ul se apără împotriva atacului CSRF prin generarea și verificarea unui token de autorizare, care este stocat în sesiune. Prin urmare, este necesar să aveți sesiunea deschisă înainte de afișarea formularului. În partea de administrare a site-ului, sesiunea este de obicei deja pornită datorită autentificării utilizatorului. Altfel, porniți sesiunea cu metoda `Nette\Http\Session::start()`. -Așadar, avem o scurtă introducere în formulare în Nette. Încercați să căutați în directorul de [exemple |https://github.com/nette/forms/tree/master/examples] din distribuție pentru mai multă inspirație. +Așadar, am parcurs o introducere rapidă în formularele Nette. Încercați să consultați și directorul [examples|https://github.com/nette/forms/tree/master/examples] din distribuție, unde veți găsi mai multă inspirație. diff --git a/forms/ro/validation.texy b/forms/ro/validation.texy index 87d26482db..8bad54f2d4 100644 --- a/forms/ro/validation.texy +++ b/forms/ro/validation.texy @@ -2,167 +2,178 @@ Validarea formularelor ********************** -Controale obligatorii .[#toc-required-controls] -=============================================== +Elemente obligatorii +==================== -Controalele sunt marcate ca fiind obligatorii cu ajutorul metodei `setRequired()`, al cărei argument este textul [mesajului de eroare |#Error Messages] care va fi afișat în cazul în care utilizatorul nu îl completează. Dacă nu se furnizează niciun argument, se utilizează mesajul de eroare implicit. +Elementele obligatorii le marcăm cu metoda `setRequired()`, al cărei argument este textul [mesajului de eroare |#Mesaje de eroare], care se afișează dacă utilizatorul nu completează elementul. Dacă nu specificăm argumentul, se va utiliza mesajul de eroare implicit. ```php -$form->addText('name', 'Name:') - ->setRequired('Please fill your name.'); +$form->addText('name', 'Nume:') + ->setRequired('Vă rugăm să introduceți numele'); ``` -Reguli .[#toc-rules] -==================== +Reguli +====== -Adăugăm reguli de validare la controale cu ajutorul metodei `addRule()`. Primul parametru este regula, al doilea este [mesajul de eroare |#Error Messages], iar al treilea este argumentul regulii de validare. +Regulile de validare le adăugăm elementelor cu metoda `addRule()`. Primul parametru este regula, al doilea este textul [mesajului de eroare |#Mesaje de eroare] și al treilea este argumentul regulii de validare. ```php -$form->addPassword('password', 'Password:') - ->addRule($form::MinLength, 'Password must be at least %d characters', 8); +$form->addPassword('password', 'Parolă:') + ->addRule($form::MinLength, 'Parola trebuie să aibă cel puțin %d caractere', 8); ``` -Nette vine cu un număr de reguli încorporate ale căror nume sunt constante ale clasei `Nette\Forms\Form`: +**Regulile de validare se verifică doar în cazul în care utilizatorul a completat elementul.** -Putem utiliza următoarele reguli pentru toate controalele: +Nette vine cu o serie întreagă de reguli predefinite, ale căror nume sunt constante ale clasei `Nette\Forms\Form`. Pentru toate elementele putem folosi aceste reguli: -| constantă | descriere | argumente +| constantă | descriere | tip argument |------- -| `Required` | alias al `setRequired()` | - -| `Filled` | alias `setRequired()` | - -| `Blank` | nu trebuie să fie completat | - +| `Required` | element obligatoriu, alias pentru `setRequired()` | - +| `Filled` | element obligatoriu, alias pentru `setRequired()` | - +| `Blank` | elementul nu trebuie completat | - | `Equal` | valoarea este egală cu parametrul | `mixed` -| `NotEqual` | value is not be equal to parameter | `mixed` -| `IsIn` | valoarea este egală cu un element din matrice | - - - valoarea este egală cu un parametru `array` -| `IsNotIn` | valoarea nu este egală cu nici un element din matrice | `array` -| `Valid` | input passes validation (for [conditions |#conditions]) | - - -Pentru controalele `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` se pot utiliza, de asemenea, următoarele reguli: - -| `MinLength` | lungime minimă a șirului de caractere | `int` -| `MaxLength` | lungime maximă a șirului de caractere | `int` -| `Length` | lungime în interval sau lungime exactă | pereche `[int, int]` sau `int` -| `Email` | adresa de e-mail validă | - -| `URL` | URL valid | - -| `Pattern` | se potrivește cu un model regulat | `string` -| `PatternInsensitive` | ca și `Pattern`, dar nu ține cont de majuscule și minuscule | `string` -| `Integer` | întreg | - -| `Numeric` | pseudonimul lui `Integer` | - -| `Float` | întreg sau număr cu virgulă mobilă | - -| `Min` | minimul valorii întregi | `int\|float` -| `Max` | maximum of the integer value | `int\|float` -| `Range` | valoare în intervalul de valori | pereche `[int\|float, int\|float]` - -Regulile `Integer`, `Numeric` și `Float` convertesc automat valoarea în număr întreg (sau, respectiv, flotant). În plus, regula `URL` acceptă și o adresă fără o schemă (de exemplu, `nette.org`) și completează schema (`https://nette.org`). -Expresiile din `Pattern` și `PatternInsensitive` trebuie să fie valabile pentru întreaga valoare, adică ca și cum ar fi înfășurată în caracterele `^` and `$`. - -Pentru controalele `addUpload()`, `addMultiUpload()` se pot utiliza, de asemenea, următoarele reguli: - -| `MaxFileSize` | dimensiunea maximă a fișierului | `int` -| `MimeType` | tip MIME, acceptă wildcards (`'video/*'`) | `string\|string[]` -| `Image` | fișierul încărcat este JPEG, PNG, GIF, WebP | - -| `Pattern` | numele fișierului se potrivește cu o expresie regulată | `string` -| `PatternInsensitive` | ca `Pattern`, dar nu ține cont de majuscule și minuscule | `string` - - `MimeType` și `Image` necesită extensia PHP `fileinfo`. Dacă un fișier sau o imagine este de tipul cerut este detectat prin semnătura sa. Integritatea întregului fișier nu este verificată. Puteți afla dacă o imagine nu este coruptă, de exemplu, încercând să [o încărcați |http:request#toImage]. - -Pentru controalele `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()` se pot utiliza, de asemenea, următoarele reguli pentru a limita numărul de elemente selectate, respectiv de fișiere încărcate: +| `NotEqual` | valoarea nu este egală cu parametrul | `mixed` +| `IsIn` | valoarea este egală cu unul dintre elementele din array | `array` +| `IsNotIn` | valoarea nu este egală cu niciun element din array | `array` +| `Valid` | elementul este completat corect? (pentru [#condiții]) | - + + +Intrări de text +--------------- + +Pentru elementele `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`, `addFloat()` se pot utiliza și unele dintre următoarele reguli: + +| `MinLength` | lungimea minimă a textului | `int` +| `MaxLength` | lungimea maximă a textului | `int` +| `Length` | lungimea în interval sau lungimea exactă | pereche `[int, int]` sau `int` +| `Email` | adresă de e-mail validă | - +| `URL` | URL absolut | - +| `Pattern` | se potrivește expresiei regulate | `string` +| `PatternInsensitive` | ca `Pattern`, dar independent de majuscule/minuscule | `string` +| `Integer` | valoare întreagă | - +| `Numeric` | alias pentru `Integer` | - +| `Float` | număr | - +| `Min` | valoarea minimă a elementului numeric | `int\|float` +| `Max` | valoarea maximă a elementului numeric | `int\|float` +| `Range` | valoarea în interval | pereche `[int\|float, int\|float]` + +Regulile de validare `Integer`, `Numeric` și `Float` convertesc direct valoarea la integer, respectiv float. Mai mult, regula `URL` acceptă și o adresă fără schemă (de ex. `nette.org`) și completează schema (`https://nette.org`). Expresia din `Pattern` și `PatternIcase` trebuie să fie valabilă pentru întreaga valoare, adică ca și cum ar fi încadrată de caracterele `^` și `$`. + + +Numărul de elemente +------------------- + +Pentru elementele `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()` se pot utiliza și următoarele reguli pentru a limita numărul de elemente selectate, respectiv fișiere încărcate: | `MinLength` | număr minim | `int` | `MaxLength` | număr maxim | `int` -| `Length` | număr în interval sau număr exact | pereche `[int, int]` sau `int` +| `Length` | numărul în interval sau numărul exact | pereche `[int, int]` sau `int` + + +Încărcarea fișierelor +--------------------- +Pentru elementele `addUpload()`, `addMultiUpload()` se pot utiliza și următoarele reguli: -Mesaje de eroare .[#toc-error-messages] ---------------------------------------- +| `MaxFileSize` | dimensiunea maximă a fișierului în octeți | `int` +| `MimeType` | tip MIME, permise caractere wildcard (`'video/*'`) | `string\|string[]` +| `Image` | imagine JPEG, PNG, GIF, WebP, AVIF | - +| `Pattern` | numele fișierului se potrivește expresiei regulate | `string` +| `PatternInsensitive` | ca `Pattern`, dar independent de majuscule/minuscule | `string` -Toate regulile predefinite, cu excepția `Pattern` și `PatternInsensitive`, au un mesaj de eroare implicit, astfel încât acestea pot fi omise. Cu toate acestea, dacă treceți și formulați toate mesajele personalizate, veți face formularul mai ușor de utilizat. +`MimeType` și `Image` necesită extensia PHP `fileinfo`. Faptul că fișierul sau imaginea este de tipul dorit este detectat pe baza semnăturii sale și **nu verifică integritatea întregului fișier.** Dacă imaginea nu este deteriorată, se poate afla, de exemplu, încercând să o [încărcați |http:request#toImage]. -Puteți schimba mesajele implicite în [forms:configuration], modificând textele din matricea `Nette\Forms\Validator::$messages` sau utilizând [translator |rendering#translating]. -În textul mesajelor de eroare pot fi utilizate următoarele caractere wildcards: +Mesaje de eroare +================ -| `%d` | înlocuiește treptat regulile de după argumente -| `%n$d` | înlocuiește cu al n-lea argument al regulii -| `%label` | înlocuiește cu eticheta câmpului (fără două puncte) -| `%name` | înlocuiește cu numele câmpului (de exemplu, `name`) +Toate regulile predefinite, cu excepția `Pattern` și `PatternInsensitive`, au un mesaj de eroare implicit, deci acesta poate fi omis. Cu toate acestea, specificarea și formularea tuturor mesajelor personalizate va face formularul mai prietenos pentru utilizator. + +Puteți modifica mesajele implicite în [configurație|forms:configuration], editând textele din array-ul `Nette\Forms\Validator::$messages` sau folosind un [translator |rendering#Traducere]. + +În textul mesajelor de eroare se pot utiliza următorii substituenți: + +| `%d` | înlocuiește succesiv cu argumentele regulii +| `%n$d` | înlocuiește cu al n-lea argument al regulii +| `%label` | înlocuiește cu eticheta elementului (fără două puncte) +| `%name` | înlocuiește cu numele elementului (de ex. `name`) | `%value` | înlocuiește cu valoarea introdusă de utilizator ```php -$form->addText('name', 'Name:') - ->setRequired('Please fill in %label'); +$form->addText('name', 'Nume:') + ->setRequired('Vă rugăm să completați %label'); $form->addInteger('id', 'ID:') - ->addRule($form::Range, 'at least %d and no more than %d', [5, 10]); + ->addRule($form::Range, 'cel puțin %d și cel mult %d', [5, 10]); $form->addInteger('id', 'ID:') - ->addRule($form::Range, 'no more than %2$d and at least %1$d', [5, 10]); + ->addRule($form::Range, 'cel mult %2$d și cel puțin %1$d', [5, 10]); ``` -Condiții .[#toc-conditions] -=========================== +Condiții +======== -Pe lângă regulile de validare, pot fi stabilite și condiții. Acestea se stabilesc la fel ca regulile, însă folosim `addRule()` în loc de `addCondition()` și, bineînțeles, lăsăm fără un mesaj de eroare (condiția doar întreabă): +Pe lângă reguli, se pot adăuga și condiții. Acestea se scriu similar cu regulile, doar că în loc de `addRule()` folosim metoda `addCondition()` și, desigur, nu specificăm niciun mesaj de eroare (condiția doar întreabă): ```php -$form->addPassword('password', 'Password:') - // dacă parola nu are mai mult de 8 caractere ... +$form->addPassword('password', 'Parolă:') + // dacă parola nu este mai lungă de 8 caractere ->addCondition($form::MaxLength, 8) - // ... atunci trebuie să conțină un număr - ->addRule($form::Pattern, 'Must contain number', '.*[0-9].*'); + // atunci trebuie să conțină o cifră + ->addRule($form::Pattern, 'Trebuie să conțină o cifră', '.*[0-9].*'); ``` -Condiția poate fi legată de un alt element decât cel curent folosind `addConditionOn()`. Primul parametru este o referință la câmp. În cazul următor, e-mailul va fi necesar numai dacă caseta de selectare este bifată (adică dacă valoarea sa este `true`): +Condiția poate fi legată și de alt element decât cel curent folosind `addConditionOn()`. Ca prim parametru, specificăm referința la element. În acest exemplu, e-mailul va fi obligatoriu doar dacă se bifează checkbox-ul (valoarea sa va fi true): ```php -$form->addCheckbox('newsletters', 'send me newsletters'); +$form->addCheckbox('newsletters', 'trimiteți-mi newslettere'); -$form->addEmail('email', 'Email:') - // dacă caseta de selectare este bifată ... +$form->addEmail('email', 'E-mail:') + // dacă checkbox-ul este bifat ->addConditionOn($form['newsletters'], $form::Equal, true) - // ... necesită e-mail - ->setRequired('Fill your email address'); + // atunci solicită e-mail + ->setRequired('Introduceți adresa de e-mail'); ``` -Condițiile pot fi grupate în structuri complexe cu ajutorul metodelor `elseCondition()` și `endCondition()`. +Din condiții se pot crea structuri complexe folosind `elseCondition()` și `endCondition()`: ```php $form->addText(/* ... */) - ->addCondition(/* ... */) // dacă este îndeplinită prima condiție - ->addConditionOn(/* ... */) // și a doua condiție și la un alt element - ->addRule(/* ... */) // necesită această regulă + ->addCondition(/* ... */) // dacă prima condiție este îndeplinită + ->addConditionOn(/* ... */) // și a doua condiție pe un alt element + ->addRule(/* ... */) // solicită această regulă ->elseCondition() // dacă a doua condiție nu este îndeplinită - ->addRule(/* ... */) // necesită aceste reguli + ->addRule(/* ... */) // solicită aceste reguli ->addRule(/* ... */) - ->endCondition() // se revine la prima condiție + ->endCondition() // ne întoarcem la prima condiție ->addRule(/* ... */); ``` -În Nette, este foarte ușor să reacționați la îndeplinirea sau nu a unei condiții pe partea de JavaScript folosind metoda `toggle()`, consultați [Dynamic JavaScript |#Dynamic JavaScript]. +În Nette se poate reacționa foarte ușor la îndeplinirea sau neîndeplinirea condiției și pe partea de JavaScript folosind metoda `toggle()`, vezi [#javascript-dinamic]. -Referințe între controale .[#toc-references-between-controls] -============================================================= +Referință la alt element +======================== -Argumentul regulii sau al condiției poate fi o referință la un alt element. De exemplu, puteți valida în mod dinamic faptul că `text` are atâtea caractere cât este valoarea câmpului `length`: +Ca argument al regulii sau condiției se poate transmite și alt element al formularului. Regula va folosi atunci valoarea introdusă ulterior de utilizator în browser. Astfel se poate valida dinamic, de exemplu, că elementul `password` conține același șir ca elementul `password_confirm`: ```php -$form->addInteger('length'); -$form->addText('text') - ->addRule($form::Length, null, $form['length']); +$form->addPassword('password', 'Parolă'); +$form->addPassword('password_confirm', 'Confirmați parola') + ->addRule($form::Equal, 'Parolele introduse nu se potrivesc', $form['password']); ``` -Reguli și condiții personalizate .[#toc-custom-rules-and-conditions] -==================================================================== +Reguli și condiții personalizate +================================ -Uneori, ne aflăm într-o situație în care regulile de validare încorporate în Nette nu sunt suficiente și trebuie să validăm datele de la utilizator în felul nostru propriu. În Nette acest lucru este foarte ușor! +Uneori ajungem în situația în care regulile de validare încorporate în Nette nu sunt suficiente și trebuie să validăm datele de la utilizator în felul nostru. În Nette este foarte simplu! -Puteți trece orice callback ca prim parametru al metodelor `addRule()` sau `addCondition()`. Callback-ul acceptă elementul însuși ca prim parametru și returnează o valoare booleană care indică dacă validarea a avut succes sau nu. Atunci când se adaugă o regulă folosind `addRule()`, se pot trece argumente suplimentare, iar acestea sunt apoi trecute ca al doilea parametru. +Metodelor `addRule()` sau `addCondition()` li se poate transmite ca prim parametru orice callback. Acesta primește ca prim parametru elementul însuși și returnează o valoare booleană care indică dacă validarea a avut loc cu succes. La adăugarea unei reguli folosind `addRule()`, se pot specifica și alte argumente, acestea fiind apoi transmise ca al doilea parametru. -Setul personalizat de validatoare poate fi astfel creat ca o clasă cu metode statice: +Putem crea astfel propriul set de validatori ca o clasă cu metode statice: ```php class MyValidators @@ -175,7 +186,7 @@ class MyValidators public static function validateEmailDomain(BaseControl $input, $domain) { - // validatori suplimentari + // alți validatori } } ``` @@ -186,12 +197,12 @@ Utilizarea este apoi foarte simplă: $form->addInteger('num') ->addRule( [MyValidators::class, 'validateDivisibility'], - 'The value must be a multiple of %d', + 'Valoarea trebuie să fie un multiplu al numărului %d', 8, ); ``` -Regulile de validare personalizate pot fi, de asemenea, adăugate la JavaScript. Singura cerință este ca regula să fie o metodă statică. Numele acesteia pentru validatorul JavaScript este creat prin concatenarea numelui clasei fără backslash-uri `\`, the underscore `_`, și a numelui metodei. De exemplu, scrieți `App\MyValidators::validateDivisibility` ca `AppMyValidators_validateDivisibility` și adăugați-o la obiectul `Nette.validators`: +Regulile de validare personalizate pot fi adăugate și în JavaScript. Condiția este ca regula să fie o metodă statică. Numele său pentru validatorul JavaScript se formează prin concatenarea numelui clasei fără backslash-uri `\`, a unui underscore `_` și a numelui metodei. De ex. `App\MyValidators::validateDivisibility` se scrie ca `AppMyValidators_validateDivisibility` și se adaugă la obiectul `Nette.validators`: ```js Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => { @@ -200,12 +211,12 @@ Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => ``` -Eveniment onValidate .[#toc-event-onvalidate] -============================================= +Evenimentul onValidate +====================== -După trimiterea formularului, validarea este efectuată prin verificarea regulilor individuale adăugate de `addRule()` și apoi prin apelarea [evenimentului |nette:glossary#Events] `onValidate`. Gestionarul acestuia poate fi utilizat pentru validare suplimentară, de obicei pentru a verifica combinația corectă de valori în mai multe elemente ale formularului. +După trimiterea formularului, se efectuează validarea, în timpul căreia se verifică regulile individuale adăugate folosind `addRule()` și apoi se declanșează [evenimentul |nette:glossary#Evenimente] `onValidate`. Handler-ul său poate fi utilizat pentru validare suplimentară, de obicei verificarea combinației corecte de valori în mai multe elemente ale formularului. -În cazul în care se detectează o eroare, aceasta este transmisă formularului cu ajutorul metodei `addError()`. Aceasta poate fi apelată fie pe un anumit element, fie direct pe formular. +Dacă se descoperă o eroare, o transmitem formularului prin metoda `addError()`. Aceasta poate fi apelată fie pe un element specific, fie direct pe formular. ```php protected function createComponentSignInForm(): Form @@ -219,16 +230,16 @@ protected function createComponentSignInForm(): Form public function validateSignInForm(Form $form, \stdClass $data): void { if ($data->foo > 1 && $data->bar > 5) { - $form->addError('This combination is not possible.'); + $form->addError('Această combinație nu este posibilă.'); } } ``` -Erori de procesare .[#toc-processing-errors] -============================================ +Erori în timpul procesării +========================== -În multe cazuri, descoperim o eroare atunci când procesăm un formular valid, de exemplu, atunci când scriem o nouă intrare în baza de date și întâlnim o cheie duplicată. În acest caz, transmitem eroarea înapoi la formular folosind metoda `addError()`. Aceasta poate fi apelată fie pe un element specific, fie direct pe formular: +În multe cazuri, aflăm despre eroare abia în momentul în care procesăm formularul valid, de exemplu, scriem un nou element în baza de date și întâlnim o duplicitate a cheilor. În acest caz, transmitem din nou eroarea formularului prin metoda `addError()`. Aceasta poate fi apelată fie pe un element specific, fie direct pe formular: ```php try { @@ -238,65 +249,64 @@ try { } catch (Nette\Security\AuthenticationException $e) { if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) { - $form->addError('Invalid password.'); + $form->addError('Parolă invalidă.'); } } ``` -Dacă este posibil, vă recomandăm să adăugați eroarea direct la elementul de formular, deoarece va apărea lângă acesta atunci când se utilizează randarea implicită. +Dacă este posibil, recomandăm atașarea erorii direct la elementul formularului, deoarece aceasta va fi afișată lângă el la utilizarea renderer-ului implicit. ```php -$form['date']->addError('Sorry, this date is already taken.'); +$form['date']->addError('Ne pare rău, dar această dată este deja ocupată.'); ``` -Puteți apela `addError()` în mod repetat pentru a transmite mai multe mesaje de eroare unui formular sau element. Le obțineți cu `getErrors()`. +Puteți apela `addError()` în mod repetat pentru a transmite formularului sau elementului mai multe mesaje de eroare. Le puteți obține folosind `getErrors()`. -Rețineți că `$form->getErrors()` returnează un rezumat al tuturor mesajelor de eroare, chiar și al celor transmise direct către elemente individuale, nu doar direct către formular. Mesajele de eroare transmise doar la formular sunt recuperate prin `$form->getOwnErrors()`. +Atenție, `$form->getErrors()` returnează un sumar al tuturor mesajelor de eroare, inclusiv cele transmise direct elementelor individuale, nu doar direct formularului. Mesajele de eroare transmise doar formularului le puteți obține prin `$form->getOwnErrors()`. -Modificarea valorilor de intrare .[#toc-modifying-input-values] -=============================================================== +Modificarea intrării +==================== -Utilizând metoda `addFilter()`, putem modifica valoarea introdusă de utilizator. În acest exemplu, vom tolera și elimina spațiile din codul poștal: +Folosind metoda `addFilter()`, putem modifica valoarea introdusă de utilizator. În acest exemplu, vom tolera și elimina spațiile din codul poștal: ```php -$form->addText('zip', 'Postcode:') +$form->addText('zip', 'Cod poștal:') ->addFilter(function ($value) { - return str_replace(' ', '', $value); // eliminați spațiile din codul poștal + return str_replace(' ', '', $value); // eliminăm spațiile din codul poștal }) - ->addRule($form::Pattern, 'The postal code is not five digits', '\d{5}'); + ->addRule($form::Pattern, 'Codul poștal nu este în format de cinci cifre', '\d{5}'); ``` -Filtrul este inclus între regulile și condițiile de validare și, prin urmare, depinde de ordinea metodelor, adică filtrul și regula sunt apelate în aceeași ordine ca și ordinea metodelor `addFilter()` și `addRule()`. +Filtrul se integrează între regulile și condițiile de validare, deci ordinea metodelor contează, adică filtrul și regula se apelează în ordinea în care sunt metodele `addFilter()` și `addRule()`. -Validarea JavaScript .[#toc-javascript-validation] -================================================== +Validare JavaScript +=================== -Limbajul regulilor și condițiilor de validare este puternic. Chiar dacă toate construcțiile funcționează atât pe partea serverului, cât și pe partea clientului, în JavaScript. Regulile sunt transferate în atributele HTML `data-nette-rules` ca JSON. -Validarea propriu-zisă este gestionată de un alt script, care agață toate evenimentele `submit` ale formularului, itera peste toate intrările și execută validările respective. +Limbajul pentru formularea condițiilor și regulilor este foarte puternic. Toate construcțiile funcționează atât pe partea de server, cât și pe partea de JavaScript. Acestea sunt transmise în atributele HTML `data-nette-rules` ca JSON. Validarea propriu-zisă este apoi efectuată de un script care interceptează evenimentul `submit` al formularului, parcurge elementele individuale și efectuează validarea corespunzătoare. -Acest script este `netteForms.js`, care este disponibil din mai multe surse posibile: +Acest script este `netteForms.js` și este disponibil din mai multe surse posibile: -Puteți încorpora scriptul direct în pagina HTML din CDN: +Puteți include scriptul direct în pagina HTML de pe CDN: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Sau copiați local în folderul public al proiectului (de exemplu, de la `vendor/nette/forms/src/assets/netteForms.min.js`): +Sau copiați-l local în folderul public al proiectului (de ex. din `vendor/nette/forms/src/assets/netteForms.min.js`): ```latte <script src="/path/to/netteForms.min.js"></script> ``` -Sau instalați prin [npm |https://www.npmjs.com/package/nette-forms]: +Sau instalați-l prin [npm|https://www.npmjs.com/package/nette-forms]: ```shell npm install nette-forms ``` -Și apoi încărcați și rulați: +Și apoi încărcați-l și rulați-l: ```js import netteForms from 'nette-forms'; @@ -311,10 +321,10 @@ netteForms.initOnLoad(); ``` -JavaScript dinamic .[#toc-dynamic-javascript] -============================================= +JavaScript dinamic +================== -Doriți să afișați câmpurile de adresă doar dacă utilizatorul alege să trimită bunurile prin poștă? Nu este nicio problemă. Cheia este o pereche de metode `addCondition()` & `toggle()`: +Doriți să afișați câmpurile pentru introducerea adresei doar dacă utilizatorul alege să primească produsul prin poștă? Nicio problemă. Cheia este perechea de metode `addCondition()` & `toggle()`: ```php $form->addCheckbox('send_it') @@ -322,25 +332,25 @@ $form->addCheckbox('send_it') ->toggle('#address-container'); ``` -Acest cod spune că atunci când condiția este îndeplinită, adică atunci când caseta de selectare este bifată, elementul HTML `#address-container` va fi vizibil. Și viceversa. Așadar, plasăm elementele de formular cu adresa destinatarului într-un container cu acest ID, iar atunci când se face clic pe caseta de selectare, acestea sunt ascunse sau afișate. Acest lucru este gestionat de scriptul `netteForms.js`. +Acest cod spune că atunci când condiția este îndeplinită, adică atunci când checkbox-ul este bifat, elementul HTML `#address-container` va fi vizibil. Și invers. Elementele formularului cu adresa destinatarului le vom plasa astfel într-un container cu acest ID, iar la clic pe checkbox, acestea se vor ascunde sau afișa. Acest lucru este asigurat de scriptul `netteForms.js`. -Orice selector poate fi trecut ca argument pentru metoda `toggle()`. Din motive istorice, un șir de caractere alfanumerice fără alte caractere speciale este tratat ca un ID de element, la fel ca și cum ar fi precedat de `#` character. The second optional parameter allows us to reverse the behavior, i.e. if we used `toggle('#address-container', false)`, elementul ar fi afișat numai dacă caseta de selectare ar fi debifată. +Ca argument al metodei `toggle()` se poate transmite orice selector. Din motive istorice, un șir alfanumeric fără alte caractere speciale este înțeles ca ID-ul elementului, adică la fel ca și cum ar fi precedat de caracterul `#`. Al doilea parametru opțional permite inversarea comportamentului, adică dacă am folosi `toggle('#address-container', false)`, elementul s-ar afișa doar dacă checkbox-ul nu ar fi bifat. -Implementarea implicită a JavaScript modifică proprietatea `hidden` pentru elemente. Cu toate acestea, putem schimba cu ușurință comportamentul, de exemplu prin adăugarea unei animații. Trebuie doar să suprascrieți metoda `Nette.toggle` în JavaScript cu o soluție personalizată: +Implementarea implicită în JavaScript modifică proprietatea `hidden` a elementelor. Putem însă schimba ușor comportamentul, de exemplu, adăugând o animație. Este suficient să suprascriem în JavaScript metoda `Nette.toggle` cu propria soluție: ```js Nette.toggle = (selector, visible, srcElement, event) => { document.querySelectorAll(selector).forEach((el) => { - // hide or show 'el' according to the value of 'visible' + // ascundem sau afișăm 'el' în funcție de valoarea 'visible' }); }; ``` -Dezactivarea validării .[#toc-disabling-validation] -=================================================== +Dezactivarea validării +====================== -În anumite cazuri, este necesar să dezactivați validarea. Dacă un buton de trimitere nu trebuie să execute validarea după trimitere (de exemplu, butonul *Cancel* sau *Preview*), puteți dezactiva validarea prin apelarea `$submit->setValidationScope([])`. De asemenea, puteți valida formularul parțial, specificând elementele care trebuie validate. +Uneori poate fi util să dezactivăm validarea. Dacă apăsarea butonului de trimitere nu trebuie să efectueze validarea (potrivit pentru butoanele *Cancel* sau *Preview*), o dezactivăm cu metoda `$submit->setValidationScope([])`. Dacă trebuie să efectueze doar o validare parțială, putem specifica ce câmpuri sau containere de formular trebuie validate. ```php $form->addText('name') @@ -354,13 +364,13 @@ $details->addInteger('age2') $form->addSubmit('send1'); // Validează întregul formular $form->addSubmit('send2') - ->setValidationScope([]); // Nu validează nimic + ->setValidationScope([]); // Nu validează deloc $form->addSubmit('send3') - ->setValidationScope([$form['name']]); // Validează doar câmpul "name" (nume) + ->setValidationScope([$form['name']]); // Validează doar elementul name $form->addSubmit('send4') - ->setValidationScope([$form['details']['age']]); // Validează doar câmpul "vârstă". + ->setValidationScope([$form['details']['age']]); // Validează doar elementul age $form->addSubmit('send5') - ->setValidationScope([$form['details']]); // Validează recipientul "details" (detalii) + ->setValidationScope([$form['details']]); // Validează containerul details ``` -Evenimentul [onValidate |#Event onValidate] de pe formular este întotdeauna invocat și nu este afectat de `setValidationScope`. Evenimentul `onValidate` de pe container este invocat numai atunci când acest container este specificat pentru validare parțială. +`setValidationScope` nu afectează [#evenimentul onValidate] al formularului, care va fi apelat întotdeauna. Evenimentul `onValidate` al containerului va fi declanșat doar dacă acest container este marcat pentru validare parțială. diff --git a/forms/ru/@home.texy b/forms/ru/@home.texy index afbf9436e6..1351009e4c 100644 --- a/forms/ru/@home.texy +++ b/forms/ru/@home.texy @@ -1,31 +1,31 @@ -Формы -***** +Nette Forms +*********** <div class=perex> -Библиотека Nette Forms произвела революцию в создании веб-форм. Всё, что вам нужно было сделать, это написать несколько четких строк кода, и у вас была форма, включая рендеринг, JavaScript и проверку на сервере, плюс лучшая в отрасли безопасность. Давайте посмотрим, как +Nette Forms произвели революцию в создании веб-форм. Внезапно стало достаточно написать несколько понятных строк кода, и у вас была готовая форма, включая рендеринг, валидацию на стороне JavaScript и сервера, и к тому же превосходно защищенная. Мы покажем вам, как -- создавать дружественные формы -- проверять отправленные данные -- отображать элементы точно по необходимости +- создавать удобные формы +- валидировать отправленные данные +- рендерить элементы точно по необходимости </div> -С помощью Nette Forms вы можете сократить рутинные задачи, такие как написание валидации (как на стороне сервера, так и на стороне клиента), а также минимизировать вероятность ошибок и проблем безопасности. +Используя Nette Forms, вы избежите целого ряда рутинных задач, таких как написание валидации (причем двойной, на стороне сервера и клиента), минимизируете вероятность возникновения ошибок и уязвимостей безопасности. -Формы можно использовать как в составе приложения Nette (т. е. в презентерах), так и отдельно. Поскольку в обоих случаях использование немного отличается, мы подготовили для вас отдельные инструкции: +Формы можно использовать либо как часть Nette Application (то есть в презентерах), либо совершенно самостоятельно. Поскольку в обоих случаях использование немного отличается, мы подготовили для вас два руководства: <div class="wiki-buttons"> <div> "Формы в презентерах .[wiki-button]":in-presenter </div> -<div> "Автономные формы .[wiki-button]":standalone </div> +<div> "Формы отдельно .[wiki-button]":standalone </div> </div> Установка --------- -Загрузите и установите пакет с помощью [Composer|best-practices:composer]: +Скачать и установить библиотеку можно с помощью [Composer|best-practices:composer]: ```shell composer require nette/forms diff --git a/forms/ru/@left-menu.texy b/forms/ru/@left-menu.texy index 5b0064b423..a50f2a3f1c 100644 --- a/forms/ru/@left-menu.texy +++ b/forms/ru/@left-menu.texy @@ -1,14 +1,14 @@ -Формы -***** -- [Обзор |@home] +Nette Forms +*********** +- [Введение |@home] - [Формы в презентерах|in-presenter] -- [Автономные формы|standalone] -- [Элементы форм |controls] +- [Формы отдельно|standalone] +- [Элементы управления формы |controls] - [Валидация |validation] - [Рендеринг |rendering] -- [Настройка |configuration] +- [Конфигурация |configuration] -Дальнейшее чтение -***************** -- [Лучшие практики |best-practices:] +Дополнительное чтение +********************* +- [Руководства и лучшие практики |best-practices:] diff --git a/forms/ru/@meta.texy b/forms/ru/@meta.texy new file mode 100644 index 0000000000..7f329adfce --- /dev/null +++ b/forms/ru/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документация Nette}} diff --git a/forms/ru/configuration.texy b/forms/ru/configuration.texy index b6d4e6d7a4..e275b176e6 100644 --- a/forms/ru/configuration.texy +++ b/forms/ru/configuration.texy @@ -1,8 +1,8 @@ -Настройка форм -************** +Конфигурация форм +***************** .[perex] -Вы можете изменить стандартные [сообщения об ошибках формы|validation] в конфигурации. +В конфигурации можно изменить стандартные [сообщения об ошибках форм|validation]. ```neon forms: @@ -45,17 +45,17 @@ forms: Email: 'Пожалуйста, введите действительный адрес электронной почты.' URL: 'Пожалуйста, введите действительный URL.' Integer: 'Пожалуйста, введите действительное целое число.' - Float: 'Пожалуйста, введите действительное вещественное число.' - Min: 'Пожалуйста, введите значение, большее или равное %d.' - Max: 'Пожалуйста, введите значение, меньшее или равное %d.' + Float: 'Пожалуйста, введите действительное число.' + Min: 'Пожалуйста, введите значение больше или равное %d.' + Max: 'Пожалуйста, введите значение меньше или равное %d.' Range: 'Пожалуйста, введите значение между %d и %d.' - MaxFileSize: 'Размер загружаемого файла не может превышать %d байт.' + MaxFileSize: 'Размер загружаемого файла может быть не более %d байт.' MaxPostSize: 'Загруженные данные превышают лимит в %d байт.' MimeType: 'Загруженный файл не соответствует ожидаемому формату.' - Image: 'Загружаемый файл должен быть изображением в формате JPEG, GIF, PNG или WebP.' + Image: 'Загруженный файл должен быть изображением в формате JPEG, GIF, PNG, WebP или AVIF.' Nette\Forms\Controls\SelectBox::Valid: 'Пожалуйста, выберите действительный вариант.' - Nette\Forms\Controls\UploadControl::Valid: 'Во время загрузки файла произошла ошибка.' - Nette\Forms\Controls\CsrfProtection::Protection: 'Ваш сеанс истек. Пожалуйста, вернитесь на главную страницу и повторите попытку.' + Nette\Forms\Controls\UploadControl::Valid: 'Произошла ошибка при загрузке файла.' + Nette\Forms\Controls\CsrfProtection::Protection: 'Ваша сессия истекла. Пожалуйста, вернитесь на главную страницу и попробуйте снова.' ``` -Если вы не используете весь фреймворк и, следовательно, даже конфигурационные файлы, вы можете изменить сообщения об ошибках по умолчанию непосредственно в поле `Nette\Forms\Validator::$messages`. +Если вы не используете весь фреймворк и, следовательно, конфигурационные файлы, вы можете изменить стандартные сообщения об ошибках непосредственно в массиве `Nette\Forms\Validator::$messages`. diff --git a/forms/ru/controls.texy b/forms/ru/controls.texy index 00e30764f1..155cd41359 100644 --- a/forms/ru/controls.texy +++ b/forms/ru/controls.texy @@ -1,14 +1,14 @@ -Элементы управления форм -************************ +Элементы формы +************** .[perex] -Обзор встроенных элементов управления формой. +Обзор стандартных элементов формы. -addText(string|int $name, $label=null): TextInput .[method] -=========================================================== +addText(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +================================================================================================== -Добавляет однострочное текстовое поле (класс [TextInput |api:Nette\Forms\Controls\TextInput]). Если пользователь не заполнил поле, возвращается пустая строка `''`, или используйте `setNullable()` для возврата `null`. +Добавляет однострочное текстовое поле (класс [TextInput |api:Nette\Forms\Controls\TextInput]). Если пользователь не заполняет поле, возвращает пустую строку `''`, или с помощью `setNullable()` можно указать, чтобы возвращал `null`. ```php $form->addText('name', 'Имя:') @@ -16,238 +16,331 @@ $form->addText('name', 'Имя:') ->setNullable(); ``` -Этот метод автоматически проверяет UTF-8, обрезает левые и правые пробелы и удаляет переносы строк, которые могут быть отправлены злоумышленником. +Автоматически проверяет UTF-8, обрезает пробелы слева и справа и удаляет переводы строк, которые мог бы отправить злоумышленник. -Максимальная длина может быть ограничена с помощью `setMaxLength()`. Функция [addFilter()|validation#Modifying-Input-Values] позволяет изменить введенное пользователем значение. +Максимальную длину можно ограничить с помощью `setMaxLength()`. Изменить введенное пользователем значение позволяет [addFilter() |validation#Изменение ввода]. -Используйте `setHtmlType()` для изменения [типа|https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types] элемента ввода на `search`, `tel`, `url`, `range`, `date`, `datetime-local`, `month`, `time`, `week`, `color`. Вместо типов `number` и `email` мы рекомендуем использовать [#addInteger] и [#addEmail], которые обеспечивают валидацию на стороне сервера. +С помощью `setHtmlType()` можно изменить визуальный характер текстового поля на типы, такие как `search`, `tel` или `url`, см. [спецификацию|https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types]. Помните, что изменение типа является только визуальным и не заменяет функцию валидации. Для типа `url` рекомендуется добавить специфическое правило валидации [URL |validation#Текстовые поля ввода]. -```php -$form->addText('color', 'Выберите цвет:') - ->setHtmlType('color') - ->addRule($form::Pattern, 'недопустимое значение', '[0-9a-f]{6}'); -``` +.[note] +Для других типов ввода, таких как `number`, `range`, `email`, `date`, `datetime-local`, `time` и `color`, используйте специализированные методы, такие как [#addInteger], [#addFloat], [#addEmail] [#addDate], [#addTime], [#addDateTime] и [#addColor], которые обеспечивают серверную валидацию. Типы `month` и `week` пока не полностью поддерживаются во всех браузерах. -Для элемента может быть установлено так называемое «пустое значение», которое является чем-то вроде значения по умолчанию, но если пользователь не перезаписывает его, возвращает пустую строку или `null`. +Элементу можно установить так называемое empty-value, что-то вроде значения по умолчанию, но если пользователь его не изменит, элемент вернет пустую строку или `null`. ```php $form->addText('phone', 'Телефон:') ->setHtmlType('tel') - ->setEmptyValue('+420'); + ->setEmptyValue('+7'); ``` addTextArea(string|int $name, $label=null): TextArea .[method] ============================================================== -Добавляет многострочное текстовое поле (класс [TextArea |api:Nette\Forms\Controls\TextArea]). Если пользователь не заполнил поле, возвращается пустая строка `''`, или используйте `setNullable()` для возврата `null`. +Добавляет поле для ввода многострочного текста (класс [TextArea |api:Nette\Forms\Controls\TextArea]). Если пользователь не заполняет поле, возвращает пустую строку `''`, или с помощью `setNullable()` можно указать, чтобы возвращал `null`. ```php $form->addTextArea('note', 'Примечание:') - ->addRule($form::MaxLength, 'Ваша заметка слишком длинная', 10000); + ->addRule($form::MaxLength, 'Примечание слишком длинное', 10000); ``` -Автоматически проверяет UTF-8 и нормализует переносы строк до `\n`. В отличие от однострочного поля ввода, в нем не обрезаются пробельные символы. +Автоматически проверяет UTF-8 и нормализует разделители строк на `\n`. В отличие от однострочного поля ввода, обрезка пробелов не происходит. -Максимальная длина может быть ограничена с помощью `setMaxLength()`. Функция [addFilter()|validation#Modifying-Input-Values] позволяет изменить введенное пользователем значение. Вы можете установить так называемое «пустое значение», используя `setEmptyValue()`. +Максимальную длину можно ограничить с помощью `setMaxLength()`. Изменить введенное пользователем значение позволяет [addFilter() |validation#Изменение ввода]. Можно установить так называемое empty-value с помощью `setEmptyValue()`. addInteger(string|int $name, $label=null): TextInput .[method] ============================================================== -Добавляет поле ввода для целого числа (класс [TextInput |api:Nette\Forms\Controls\TextInput]). Возвращает либо целое число, либо `null`, если пользователь ничего не ввел. +Добавляет поле для ввода целого числа (класс [TextInput |api:Nette\Forms\Controls\TextInput]). Возвращает либо integer, либо `null`, если пользователь ничего не ввел. + +```php +$form->addInteger('year', 'Год:') + ->addRule($form::Range, 'Год должен быть в диапазоне от %d до %d.', [1900, 2023]); +``` + +Элемент отображается как `<input type="number">`. С помощью метода `setHtmlType()` можно изменить тип на `range` для отображения в виде ползунка, или на `text`, если вы предпочитаете стандартное текстовое поле без специального поведения типа `number`. + + +addFloat(string|int $name, $label=null): TextInput .[method]{data-version:3.1.12} +================================================================================= + +Добавляет поле для ввода десятичного числа (класс [TextInput |api:Nette\Forms\Controls\TextInput]). Возвращает либо float, либо `null`, если пользователь ничего не ввел. ```php -$form->addInteger('level', 'Уровень:') +$form->addFloat('level', 'Уровень:') ->setDefaultValue(0) - ->addRule($form::Range, 'Уровень должен быть между %d и %d.', [0, 100]); + ->addRule($form::Range, 'Уровень должен быть в диапазоне от %d до %d.', [0, 100]); ``` +Элемент отображается как `<input type="number">`. С помощью метода `setHtmlType()` можно изменить тип на `range` для отображения в виде ползунка, или на `text`, если вы предпочитаете стандартное текстовое поле без специального поведения типа `number`. + +Nette и браузер Chrome принимают в качестве разделителя десятичных знаков как запятую, так и точку. Чтобы эта функциональность была доступна и в Firefox, рекомендуется установить атрибут `lang` либо для данного элемента, либо для всей страницы, например `<html lang="ru">`. + -addEmail(string|int $name, $label=null): TextInput .[method] -============================================================ +addEmail(string|int $name, $label=null, int $maxLength=255): TextInput .[method] +================================================================================ -Добавляет поле адреса электронной почты с проверкой достоверности (класс [TextInput |api:Nette\Forms\Controls\TextInput]). Если пользователь не заполнил поле, возвращается пустая строка `''`, или используйте `setNullable()` для возврата `null`. +Добавляет поле для ввода адреса электронной почты (класс [TextInput |api:Nette\Forms\Controls\TextInput]). Если пользователь не заполняет поле, возвращает пустую строку `''`, или с помощью `setNullable()` можно указать, чтобы возвращал `null`. ```php -$form->addEmail('email', 'Имейл:'); +$form->addEmail('email', 'E-mail:'); ``` -Проверяет, что значение является действительным адресом электронной почты. Он не проверяет, существует ли домен на самом деле, проверяется только синтаксис. Автоматически проверяет UTF-8, обрезает левые и правые пробелы. +Проверяет, является ли значение действительным адресом электронной почты. Не проверяется, существует ли домен на самом деле, проверяется только синтаксис. Автоматически проверяет UTF-8, обрезает пробелы слева и справа. -Максимальная длина может быть ограничена с помощью `setMaxLength()`. Функция [addFilter()|validation#Modifying-Input-Values] позволяет изменить введенное пользователем значение. Вы можете установить так называемое «пустое значение», используя `setEmptyValue()`. +Максимальную длину можно ограничить с помощью `setMaxLength()`. Изменить введенное пользователем значение позволяет [addFilter() |validation#Изменение ввода]. Можно установить так называемое empty-value с помощью `setEmptyValue()`. -addPassword(string|int $name, $label=null): TextInput .[method] -=============================================================== +addPassword(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +====================================================================================================== -Добавляет поле пароля (класс [TextInput |api:Nette\Forms\Controls\TextInput]). +Добавляет поле для ввода пароля (класс [TextInput |api:Nette\Forms\Controls\TextInput]). ```php $form->addPassword('password', 'Пароль:') ->setRequired() - ->addRule($form::MinLength, 'Пароль должен быть длиной не менее %d символов', 8) - ->addRule($form::Pattern, 'Пароль должен содержать цифры', '.*[0-9].*'); + ->addRule($form::MinLength, 'Пароль должен содержать не менее %d символов', 8) + ->addRule($form::Pattern, 'Должен содержать цифру', '.*[0-9].*'); ``` -При повторной отправке формы ввод будет пустым. Он автоматически проверяет UTF-8, обрезает левые и правые пробелы и удаляет переносы строк, которые могут быть отправлены злоумышленником. +При повторном отображении формы поле будет пустым. Автоматически проверяет UTF-8, обрезает пробелы слева и справа и удаляет переводы строк, которые мог бы отправить злоумышленник. addCheckbox(string|int $name, $caption=null): Checkbox .[method] ================================================================ -Добавляет флажок (класс [Checkbox |api:Nette\Forms\Controls\Checkbox]). Поле возвращает либо `true`, либо `false`, в зависимости от того, проверено ли оно. +Добавляет флажок (чекбокс) (класс [Checkbox |api:Nette\Forms\Controls\Checkbox]). Возвращает значение `true` или `false`, в зависимости от того, установлен ли флажок. ```php $form->addCheckbox('agree', 'Я согласен с условиями') - ->setRequired('Вы должны согласиться с нашими условиями'); + ->setRequired('Необходимо согласиться с условиями'); ``` -addCheckboxList(string|int $name, $label=null, array $items=null): CheckboxList .[method] -========================================================================================= +addCheckboxList(string|int $name, $label=null, ?array $items=null): CheckboxList .[method] +========================================================================================== -Добавляет список флажков для выбора нескольких элементов (класс [CheckboxList |api:Nette\Forms\Controls\CheckboxList]). Возвращает массив ключей выбранных элементов. Метод `getSelectedItems()` возвращает значения вместо ключей. +Добавляет флажки для выбора нескольких элементов (класс [CheckboxList |api:Nette\Forms\Controls\CheckboxList]). Возвращает массив ключей выбранных элементов. Метод `getSelectedItems()` возвращает значения вместо ключей. ```php $form->addCheckboxList('colors', 'Цвета:', [ - 'r' => 'red', - 'g' => 'green', - 'b' => 'blue', + 'r' => 'красный', + 'g' => 'зеленый', + 'b' => 'синий', ]); ``` -Мы передаем массив элементов в качестве третьего параметра или методом `setItems()`. +Массив предлагаемых элементов передаем как третий параметр или методом `setItems()`. -Вы можете использовать `setDisabled(['r', 'g'])` для отключения отдельных элементов. +С помощью `setDisabled(['r', 'g'])` можно деактивировать отдельные элементы. -Элемент автоматически проверяет, что не было подделки и что выбранные элементы действительно являются одними из предложенных и не были отключены. Метод `getRawValue()` может быть использован для получения отправленных элементов без этой важной проверки. +Элемент автоматически проверяет, не произошла ли подделка и что выбранные элементы действительно являются одними из предлагаемых и не были деактивированы. Методом `getRawValue()` можно получить отправленные элементы без этой важной проверки. -При установке значений по умолчанию также проверяется, что они являются одним из предлагаемых элементов, в противном случае возникает исключение. Эта проверка может быть отключена с помощью `checkDefaultValue(false)`. +При установке выбранных по умолчанию элементов также проверяет, что они являются одними из предлагаемых, иначе выбрасывает исключение. Эту проверку можно отключить с помощью `checkDefaultValue(false)`. +Если вы отправляете форму методом `GET`, вы можете выбрать более компактный способ передачи данных, который экономит размер строки запроса. Он активируется установкой HTML-атрибута формы: + +```php +$form->setHtmlAttribute('data-nette-compact'); +``` -addRadioList(string|int $name, $label=null, array $items=null): RadioList .[method] -=================================================================================== -Добавляет радиокнопки (класс [RadioList |api:Nette\Forms\Controls\RadioList]). Возвращает ключ выбранного элемента или `null`, если пользователь ничего не выбрал. Метод `getSelectedItem()` возвращает значение вместо ключа. +addRadioList(string|int $name, $label=null, ?array $items=null): RadioList .[method] +==================================================================================== + +Добавляет переключатели (радиокнопки) (класс [RadioList |api:Nette\Forms\Controls\RadioList]). Возвращает ключ выбранного элемента или `null`, если пользователь ничего не выбрал. Метод `getSelectedItem()` возвращает значение вместо ключа. ```php $sex = [ - 'm' => 'male', - 'f' => 'female', + 'm' => 'мужчина', + 'f' => 'женщина', ]; $form->addRadioList('gender', 'Пол:', $sex); ``` -Мы передаем массив элементов в качестве третьего параметра или методом `setItems()`. +Массив предлагаемых элементов передаем как третий параметр или методом `setItems()`. -Вы можете использовать `setDisabled(['m'])` для отключения отдельных элементов. +С помощью `setDisabled(['m', 'f'])` можно деактивировать отдельные элементы. -Элемент автоматически проверяет, что не было подделки и что выбранный элемент действительно является одним из предложенных и не был отключен. Метод `getRawValue()` может быть использован для получения отправленного элемента без этой важной проверки. +Элемент автоматически проверяет, не произошла ли подделка и что выбранный элемент действительно является одним из предлагаемых и не был деактивирован. Методом `getRawValue()` можно получить отправленный элемент без этой важной проверки. -При установке значения по умолчанию проверяется, что оно является одним из предлагаемых элементов, в противном случае возникает исключение. Эта проверка может быть отключена с помощью `checkDefaultValue(false)`. +При установке выбранного по умолчанию элемента также проверяет, что он является одним из предлагаемых, иначе выбрасывает исключение. Эту проверку можно отключить с помощью `checkDefaultValue(false)`. -addSelect(string|int $name, $label=null, array $items=null): SelectBox .[method] -================================================================================ +addSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): SelectBox .[method] +================================================================================================== -Добавляет поле выбора (класс [SelectBox |api:Nette\Forms\Controls\SelectBox]). Возвращает ключ выбранного элемента или `null`, если пользователь ничего не выбрал. Метод `getSelectedItem()` возвращает значение вместо ключа. +Добавляет выпадающий список (select box) (класс [SelectBox |api:Nette\Forms\Controls\SelectBox]). Возвращает ключ выбранного элемента или `null`, если пользователь ничего не выбрал. Метод `getSelectedItem()` возвращает значение вместо ключа. ```php $countries = [ - 'CZ' => 'Чешская республика', + 'CZ' => 'Чешская Республика', 'SK' => 'Словакия', - 'GB' => 'Великобритания', + 'RU' => 'Россия', ]; $form->addSelect('country', 'Страна:', $countries) - ->setDefaultValue('SK'); + ->setDefaultValue('RU'); ``` -Мы передаем массив элементов в качестве третьего параметра или методом `setItems()`. Массив элементов также может быть двумерным: +Массив предлагаемых элементов передаем как третий параметр или методом `setItems()`. Элементы могут быть и двумерным массивом: ```php $countries = [ - 'Europe' => [ - 'CZ' => 'Чешская республика', + 'Европа' => [ + 'CZ' => 'Чешская Республика', 'SK' => 'Словакия', 'GB' => 'Великобритания', ], - 'CA' => 'Канада', + 'RU' => 'Россия', 'US' => 'США', '?' => 'другая', ]; ``` -В блоках выбора первый элемент часто имеет особое значение, он служит призывом к действию. Используйте метод `setPrompt()` для добавления такой записи. +У выпадающих списков часто первый элемент имеет особое значение, служит призывом к действию. Для добавления такого элемента служит метод `setPrompt()`. ```php $form->addSelect('country', 'Страна:', $countries) ->setPrompt('Выберите страну'); ``` -Вы можете использовать `setDisabled(['CZ', 'SK'])` для отключения отдельных элементов. +С помощью `setDisabled(['CZ', 'SK'])` можно деактивировать отдельные элементы. -Элемент автоматически проверяет, что не было подделки и что выбранный элемент действительно является одним из предложенных и не был отключен. Метод `getRawValue()` может быть использован для получения отправленного элемента без этой важной проверки. +Элемент автоматически проверяет, не произошла ли подделка и что выбранный элемент действительно является одним из предлагаемых и не был деактивирован. Методом `getRawValue()` можно получить отправленный элемент без этой важной проверки. -При установке значения по умолчанию проверяется, что оно является одним из предлагаемых элементов, в противном случае возникает исключение. Эта проверка может быть отключена с помощью `checkDefaultValue(false)`. +При установке выбранного по умолчанию элемента также проверяет, что он является одним из предлагаемых, иначе выбрасывает исключение. Эту проверку можно отключить с помощью `checkDefaultValue(false)`. -addMultiSelect(string|int $name, $label=null, array $items=null): MultiSelectBox .[method] -========================================================================================== +addMultiSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): MultiSelectBox .[method] +============================================================================================================ -Добавляет окно выбора нескольких вариантов (класс [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox]). Возвращает массив ключей выбранных элементов. Метод `getSelectedItems()` возвращает значения вместо ключей. +Добавляет выпадающий список для выбора нескольких элементов (класс [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox]). Возвращает массив ключей выбранных элементов. Метод `getSelectedItems()` возвращает значения вместо ключей. ```php $form->addMultiSelect('countries', 'Страны:', $countries); ``` -Мы передаем массив элементов в качестве третьего параметра или методом `setItems()`. Массив элементов также может быть двумерным. +Массив предлагаемых элементов передаем как третий параметр или методом `setItems()`. Элементы могут быть и двумерным массивом. -Вы можете использовать `setDisabled(['CZ', 'SK'])` для отключения отдельных элементов. +С помощью `setDisabled(['CZ', 'SK'])` можно деактивировать отдельные элементы. -Элемент автоматически проверяет, что не было подделки и что выбранные элементы действительно являются одними из предложенных и не были отключены. Метод `getRawValue()` может быть использован для получения отправленных элементов без этой важной проверки. +Элемент автоматически проверяет, не произошла ли подделка и что выбранные элементы действительно являются одними из предлагаемых и не были деактивированы. Методом `getRawValue()` можно получить отправленные элементы без этой важной проверки. -При установке значений по умолчанию также проверяется, что они являются одним из предлагаемых элементов, в противном случае возникает исключение. Эта проверка может быть отключена с помощью `checkDefaultValue(false)`. +При установке выбранных по умолчанию элементов также проверяет, что они являются одними из предлагаемых, иначе выбрасывает исключение. Эту проверку можно отключить с помощью `checkDefaultValue(false)`. addUpload(string|int $name, $label=null): UploadControl .[method] ================================================================= -Добавляет поле загрузки файлов (класс [UploadControl |api:Nette\Forms\Controls\UploadControl]). Возвращает объект [FileUpload |http:request#FileUpload], даже если пользователь не загрузил файл, что можно выяснить с помощью метода `FileUpload::hasFile()`. +Добавляет поле для загрузки файла (класс [UploadControl |api:Nette\Forms\Controls\UploadControl]). Возвращает объект [FileUpload |http:request#FileUpload], даже если пользователь не отправил ни одного файла, что можно проверить методом `FileUpload::hasFile()`. ```php $form->addUpload('avatar', 'Аватар:') - ->addRule($form::Image, 'Аватар должен быть в формате JPEG, PNG, GIF или WebP') - ->addRule($form::MaxFileSize, 'Максимальный размер - 1 МБ', 1024 * 1024); + ->addRule($form::Image, 'Аватар должен быть в формате JPEG, PNG, GIF, WebP или AVIF.') + ->addRule($form::MaxFileSize, 'Максимальный размер 1 МБ.', 1024 * 1024); ``` -Если файл загружен неправильно, форма не была отправлена успешно и отображается ошибка. Т. е. нет необходимости проверять метод `FileUpload::isOk()`. +Если файл не удается корректно загрузить, форма не отправляется успешно и отображается ошибка. То есть при успешной отправке нет необходимости проверять метод `FileUpload::isOk()`. -Не доверяйте оригинальному имени файла, возвращаемому методом `FileUpload::getName()`, клиент может отправить вредоносное имя файла с намерением испортить или взломать ваше приложение. +Никогда не доверяйте оригинальному имени файла, возвращаемому методом `FileUpload::getName()`, клиент мог отправить вредоносное имя файла с целью повредить или взломать ваше приложение. -Правила `MimeType` и `Image` определяют необходимый тип файла или изображения по его сигнатуре. Целостность всего файла не проверяется. Вы можете узнать, не повреждено ли изображение, например, попытавшись [загрузить его|http:request#toImage]. +Правила `MimeType` и `Image` определяют требуемый тип на основе сигнатуры файла и не проверяют его целостность. Не повреждено ли изображение, можно выяснить, например, попытавшись его [загрузить |http:request#toImage]. addMultiUpload(string|int $name, $label=null): UploadControl .[method] ====================================================================== -Добавляет поле загрузки нескольких файлов (класс [UploadControl |api:Nette\Forms\Controls\UploadControl]). Возвращает массив объектов [FileUpload |http:request#FileUpload]. Метод `FileUpload::hasFile()` вернет `true` для каждого из них. +Добавляет поле для загрузки нескольких файлов одновременно (класс [UploadControl |api:Nette\Forms\Controls\UploadControl]). Возвращает массив объектов [FileUpload |http:request#FileUpload]. Метод `FileUpload::hasFile()` у каждого из них будет возвращать `true`. ```php $form->addMultiUpload('files', 'Файлы:') - ->addRule($form::MaxLength, 'Может быть загружено не более %d файлов', 10); + ->addRule($form::MaxLength, 'Максимально можно загрузить %d файлов', 10); ``` -Если один из файлов не загрузится правильно, форма не будет отправлена успешно и отобразится ошибка. Т. е. нет необходимости проверять метод `FileUpload::isOk()`. +Если какой-либо файл не удается корректно загрузить, форма не отправляется успешно и отображается ошибка. То есть при успешной отправке нет необходимости проверять метод `FileUpload::isOk()`. + +Никогда не доверяйте оригинальным именам файлов, возвращаемым методом `FileUpload::getName()`, клиент мог отправить вредоносное имя файла с целью повредить или взломать ваше приложение. + +Правила `MimeType` и `Image` определяют требуемый тип на основе сигнатуры файла и не проверяют его целостность. Не повреждено ли изображение, можно выяснить, например, попытавшись его [загрузить |http:request#toImage]. + -Не доверяйте оригинальным именам файлов, возвращаемым методом `FileUpload::getName()`, клиент может отправить вредоносное имя файла с намерением испортить или взломать ваше приложение. +addDate(string|int $name, $label=null): DateTimeControl .[method]{data-version:3.1.14} +====================================================================================== -Правила `MimeType` и `Image` определяют необходимый тип файла или изображения по его сигнатуре. Целостность всего файла не проверяется. Вы можете узнать, не повреждено ли изображение, например, попытавшись [загрузить его|http:request#toImage]. +Добавляет поле, которое позволяет пользователю легко ввести дату, состоящую из года, месяца и дня (класс [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +В качестве значения по умолчанию принимает либо объекты, реализующие интерфейс `DateTimeInterface`, строку с временем, либо число, представляющее UNIX timestamp. То же самое относится к аргументам правил `Min`, `Max` или `Range`, которые определяют минимальную и максимальную допустимую дату. + +```php +$form->addDate('date', 'Дата:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'Дата должна быть не ранее месяца назад.', new DateTime('-1 month')); +``` + +По умолчанию возвращает объект `DateTimeImmutable`, методом `setFormat()` вы можете указать [текстовый формат|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] или timestamp: + +```php +$form->addDate('date', 'Дата:') + ->setFormat('Y-m-d'); +``` -addHidden(string|int $name, string $default=null): HiddenField .[method] -======================================================================== +addTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=============================================================================================================== + +Добавляет поле, которое позволяет пользователю легко ввести время, состоящее из часов, минут и, опционально, секунд (класс [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +В качестве значения по умолчанию принимает либо объекты, реализующие интерфейс `DateTimeInterface`, строку с временем, либо число, представляющее UNIX timestamp. Из этих входных данных используется только информация о времени, дата игнорируется. То же самое относится к аргументам правил `Min`, `Max` или `Range`, которые определяют минимальное и максимальное допустимое время. Если установленное минимальное значение выше максимального, создается временной диапазон, пересекающий полночь. + +```php +$form->addTime('time', 'Время:', withSeconds: true) + ->addRule($form::Range, 'Время должно быть в диапазоне от %s до %s.', ['12:30', '13:30']); +``` + +По умолчанию возвращает объект `DateTimeImmutable` (с датой 1 января 1 года), методом `setFormat()` вы можете указать [текстовый формат|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters]: + +```php +$form->addTime('time', 'Время:') + ->setFormat('H:i'); +``` + + +addDateTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=================================================================================================================== + +Добавляет поле, которое позволяет пользователю легко ввести дату и время, состоящие из года, месяца, дня, часов, минут и, опционально, секунд (класс [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +В качестве значения по умолчанию принимает либо объекты, реализующие интерфейс `DateTimeInterface`, строку с временем, либо число, представляющее UNIX timestamp. То же самое относится к аргументам правил `Min`, `Max` или `Range`, которые определяют минимальную и максимальную допустимую дату. + +```php +$form->addDateTime('datetime', 'Дата и время:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'Дата должна быть не ранее месяца назад.', new DateTime('-1 month')); +``` + +Standardně vrací objekt `DateTimeImmutable`, metodou `setFormat()` můžete specifikovat [textový formát|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] či timestamp: + +```php +$form->addDateTime('datetime') + ->setFormat(DateTimeControl::FormatTimestamp); +``` + + +addColor(string|int $name, $label=null): ColorPicker .[method]{data-version:3.1.14} +=================================================================================== + +Добавляет поле для выбора цвета (класс [ColorPicker |api:Nette\Forms\Controls\ColorPicker]). Цвет — это строка в формате `#rrggbb`. Если пользователь не сделал выбор, возвращается черный цвет `#000000`. + +```php +$form->addColor('color', 'Цвет:') + ->setDefaultValue('#3C8ED7'); +``` + + +addHidden(string|int $name, ?string $default=null): HiddenField .[method] +========================================================================= Добавляет скрытое поле (класс [HiddenField |api:Nette\Forms\Controls\HiddenField]). @@ -255,7 +348,9 @@ addHidden(string|int $name, string $default=null): HiddenField .[method] $form->addHidden('userid'); ``` -Используйте `setNullable()`, чтобы изменить его так, чтобы он возвращал `null` вместо пустой строки. Функция [addFilter()|validation#Modifying-Input-Values] позволяет изменить представленное значение. +С помощью `setNullable()` можно настроить, чтобы возвращался `null` вместо пустой строки. Изменить отправленное значение позволяет [addFilter() |validation#Изменение ввода]. + +Хотя элемент скрыт, **важно помнить**, что значение все еще может быть изменено или подделано злоумышленником. Всегда тщательно проверяйте и валидируйте все полученные значения на стороне сервера, чтобы предотвратить риски безопасности, связанные с манипуляцией данными. addSubmit(string|int $name, $caption=null): SubmitButton .[method] @@ -264,17 +359,17 @@ addSubmit(string|int $name, $caption=null): SubmitButton .[method] Добавляет кнопку отправки (класс [SubmitButton |api:Nette\Forms\Controls\SubmitButton]). ```php -$form->addSubmit('submit', 'Зарегистрироваться'); +$form->addSubmit('submit', 'Отправить'); ``` -В форме можно иметь более одной кнопки отправки: +В форме может быть несколько кнопок отправки: ```php $form->addSubmit('register', 'Зарегистрироваться'); $form->addSubmit('cancel', 'Отмена'); ``` -Чтобы узнать, какая из них была нажата, используйте: +Чтобы определить, какая из них была нажата, используйте: ```php if ($form['register']->isSubmittedBy()) { @@ -282,22 +377,22 @@ if ($form['register']->isSubmittedBy()) { } ``` -Если вы не хотите проверять форму при нажатии кнопки отправки (например, кнопки *Отмена* или *Предварительный просмотр*), вы можете отключить её с помощью [setValidationScope()|validation#Disabling-Validation]. +Если вы не хотите валидировать всю форму при нажатии кнопки (например, для кнопок *Отмена* или *Предпросмотр*), используйте [setValidationScope() |validation#Отключение валидации]. addButton(string|int $name, $caption): Button .[method] ======================================================= -Добавляет кнопку (класс [Button |api:Nette\Forms\Controls\Button]) без функции отправки. Это полезно для привязки другой функциональности к id, например, действия JavaScript. +Добавляет кнопку (класс [Button |api:Nette\Forms\Controls\Button]), которая не имеет функции отправки. Ее можно использовать для какой-либо другой функции, например, вызова JavaScript-функции при нажатии. ```php -$form->addButton('raise', 'Поднять зарплату') +$form->addButton('raise', 'Повысить зарплату') ->setHtmlAttribute('onclick', 'raiseSalary()'); ``` -addImageButton(string|int $name, string $src=null, string $alt=null): ImageButton .[method] -=========================================================================================== +addImageButton(string|int $name, ?string $src=null, ?string $alt=null): ImageButton .[method] +============================================================================================= Добавляет кнопку отправки в виде изображения (класс [ImageButton |api:Nette\Forms\Controls\ImageButton]). @@ -305,25 +400,25 @@ addImageButton(string|int $name, string $src=null, string $alt=null): ImageButto $form->addImageButton('submit', '/path/to/image'); ``` -При использовании нескольких кнопок отправки можно узнать, какая из них была нажата с помощью `$form['submit']->isSubmittedBy()`. +При использовании нескольких кнопок отправки можно определить, какая была нажата, с помощью `$form['submit']->isSubmittedBy()`. addContainer(string|int $name): Container .[method] =================================================== -Добавляет подформу (класс [Container|api:Nette\Forms\Container]), или контейнер, с которым можно обращаться так же, как и с формой. Это означает, что вы можете использовать такие методы, как `setDefaults()` или `getValues()`. +Добавляет подформу (класс [Container|api:Nette\Forms\Container]), или контейнер, в который можно добавлять другие элементы так же, как мы добавляем их в форму. Работают также методы `setDefaults()` или `getValues()`. ```php $sub1 = $form->addContainer('first'); $sub1->addText('name', 'Ваше имя:'); -$sub1->addEmail('email', 'Имейл:'); +$sub1->addEmail('email', 'Email:'); $sub2 = $form->addContainer('second'); $sub2->addText('name', 'Ваше имя:'); -$sub2->addEmail('email', 'Имейл:'); +$sub2->addEmail('email', 'Email:'); ``` -Затем отправленные данные возвращаются в виде многомерной структуры: +Отправленные данные затем возвращаются в виде многомерной структуры: ```php [ @@ -339,69 +434,69 @@ $sub2->addEmail('email', 'Имейл:'); ``` -Обзор настроек .[#toc-overview-of-settings] -=========================================== +Обзор настроек +============== -Для всех элементов мы можем вызывать следующие методы (полный обзор см. в [документации по API|https://api.nette.org/forms/master/Nette/Forms/Controls.html]): +Для всех элементов мы можем вызывать следующие методы (полный обзор в [документации API|https://api.nette.org/forms/master/Nette/Forms/Controls.html]): .[table-form-methods language-php] -| `setDefaultValue($value)` | устанавливает значение по умолчанию +| `setDefaultValue($value)` | устанавливает значение по умолчанию | `getValue()` | получить текущее значение -| `setOmitted()` | [#omitted values] -| `setDisabled()` | [#disabling inputs] +| `setOmitted()` | [#Исключение значения] +| `setDisabled()` | [#Деактивация элементов] -Рендеринг: +Отображение: .[table-form-methods language-php] -| `setCaption()` | изменить заголовка элемента -| `setTranslator()` | задает [транслятор |rendering#translating] -| `setHtmlAttribute()` | задает [HTML атрибут |rendering#HTML attributes] элемента -| `setHtmlId()` | задает HTML атрибут `id` -| `setHtmlType()` | задает HTML атрибут `type` -| `setHtmlName()` | задает HTML атрибут `name` -| `setOption()` | задает [рендеринг данных|rendering#Options] +| `setCaption($caption)` | изменяет метку элемента +| `setTranslator($translator)` | устанавливает [переводчик |rendering#Перевод] +| `setHtmlAttribute($name, $value)` | устанавливает [HTML атрибут |rendering#HTML-атрибуты] элемента +| `setHtmlId($id)` | устанавливает HTML атрибут `id` +| `setHtmlType($type)` | устанавливает HTML атрибут `type` +| `setHtmlName($name)` | устанавливает HTML атрибут `name` +| `setOption($key, $value)` | [настройки для отображения |rendering#Options] Валидация: .[table-form-methods language-php] -| `setRequired()` | [обязательное поле |validation] -| `addRule()` | установить [правило проверки |validation#Rules] -| `addCondition()`, `addConditionOn()` | установить [условие проверки|validation#Conditions] -| `addError()` | [передача сообщения об ошибке|validation#Processing-Errors] +| `setRequired()` | [обязательный элемент |validation] +| `addRule()` | установка [правила валидации |validation#Правила] +| `addCondition()`, `addConditionOn()` | установка [условия валидации |validation#Условия] +| `addError($message)` | [передача сообщения об ошибке |validation#Ошибки при обработке] -Следующие методы могут быть вызваны для элементов `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`: +Для элементов `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` можно вызывать следующие методы: .[table-form-methods language-php] -| `setNullable()` | устанавливает, возвращает ли getValue() `null` вместо пустой строки -| `setEmptyValue()` | устанавливает специальное значение, которое рассматривается как пустая строка -| `setMaxLength()` | устанавливает максимальное количество разрешенных символов -| `addFilter()` | [изменение входных значений |validation#Modifying-Input-Values] +| `setNullable()` | устанавливает, вернет ли getValue() `null` вместо пустой строки +| `setEmptyValue($value)` | устанавливает специальное значение, которое считается пустой строкой +| `setMaxLength($length)` | устанавливает максимальное количество разрешенных символов +| `addFilter($filter)` | [изменение ввода |validation#Изменение ввода] -Опущенные значения .[#toc-omitted-values] ------------------------------------------ +Исключение значения +=================== -Если вас не интересует значение, введенное пользователем, мы можем использовать `setOmitted()`, чтобы опустить его из результата, предоставляемого методом `$form->getValues()` или передаваемого обработчикам. Это подходит для различных паролей для проверки, антиспамовых полей и т. д. +Если нас не интересует значение, введенное пользователем, мы можем с помощью `setOmitted()` исключить его из результата метода `$form->getValues()` или из данных, передаваемых в обработчики. Это удобно для различных паролей для проверки, антиспам-элементов и т. д. ```php -$form->addPassword('passwordVerify', 'Повторите пароль:') - ->setRequired('Введите пароль ещё раз, чтобы проверить опечатку') - ->addRule($form::Equal, 'Несоответствие пароля', $form['password']) +$form->addPassword('passwordVerify', 'Пароль для проверки:') + ->setRequired('Пожалуйста, введите пароль еще раз для проверки') + ->addRule($form::Equal, 'Пароли не совпадают', $form['password']) ->setOmitted(); ``` -Отключение элементов ввода .[#toc-disabling-inputs] ---------------------------------------------------- +Деактивация элементов +===================== -Чтобы отключить вход, вы можете вызвать `setDisabled()`. Такое поле не может быть отредактировано пользователем. +Элементы можно деактивировать с помощью `setDisabled()`. Такой элемент пользователь не может редактировать. ```php $form->addText('username', 'Имя пользователя:') ->setDisabled(); ``` -Обратите внимание, что браузер вообще не отправляет отключенные поля на сервер, поэтому вы даже не найдете их в данных, возвращаемых функцией `$form->getValues()`. +Отключенные элементы браузер вообще не отправляет на сервер, поэтому их не найти в данных, возвращаемых функцией `$form->getValues()`. Однако, если вы установите `setOmitted(false)`, Nette включит в эти данные их значение по умолчанию. -Если вы устанавливаете значение по умолчанию для поля, вы должны сделать это только после его отключения: +При вызове `setDisabled()` из соображений безопасности **значение элемента удаляется**. Если вы устанавливаете значение по умолчанию, это необходимо делать после его деактивации: ```php $form->addText('username', 'Имя пользователя:') @@ -409,11 +504,13 @@ $form->addText('username', 'Имя пользователя:') ->setDefaultValue($userName); ``` +Альтернативой отключенным элементам являются элементы с HTML-атрибутом `readonly`, которые браузер отправляет на сервер. Хотя элемент предназначен только для чтения, **важно помнить**, что его значение все еще может быть изменено или подделано злоумышленником. + -Пользовательские элементы управления .[#toc-custom-controls] -============================================================ +Пользовательские элементы +========================= -Помимо широкого спектра встроенных элементов управления формой, вы можете добавить свои элементы следующим образом: +Помимо широкого спектра встроенных элементов формы, вы можете добавлять в форму пользовательские элементы следующим образом: ```php $form->addComponent(new DateInput('Дата:'), 'date'); @@ -421,28 +518,28 @@ $form->addComponent(new DateInput('Дата:'), 'date'); ``` .[note] -Форма является потомком класса [Container | component-model:#Container], а элементы являются потомками [Component | component-model:#Component]. +Форма является потомком класса [Container |component-model:#Container], а отдельные элементы — потомками [Component |component-model:#Component]. -Существует способ определения новых методов формы для добавления пользовательских элементов (например, '$form->addZip()'). Это так называемые методы расширения. Недостатком является то, что подсказки кода в редакторах не будут работать для них. +Существует способ определить новые методы формы, служащие для добавления пользовательских элементов (например, `$form->addZip()`). Это так называемые extension methods. Недостаток в том, что для них не будет работать автодополнение в редакторах. ```php use Nette\Forms\Container; -// добавляет метод addZip(string $name, string $label = null) -Container::extensionMethod('addZip', function (Container $form, string $name, string $label = null) { +// добавим метод addZip(string $name, ?string $label = null) +Container::extensionMethod('addZip', function (Container $form, string $name, ?string $label = null) { return $form->addText($name, $label) - ->addRule($form::Pattern, 'Не менее 5 номеров', '[0-9]{5}'); + ->addRule($form::Pattern, 'Не менее 5 цифр', '[0-9]{5}'); }); // использование -$form->addZip('zip', 'ZIP-код:'); +$form->addZip('zip', 'Почтовый индекс:'); ``` -Низкоуровневые поля .[#toc-low-level-fields] -============================================ +Низкоуровневые элементы +======================= -Чтобы добавить элемент в форму, не нужно вызывать '$form->addXyz()'. Вместо этого элементы формы могут быть введены исключительно в шаблоны. Это полезно, если вам, например, нужно создать динамические элементы: +Можно использовать и элементы, которые мы записываем только в шаблоне и не добавляем в форму каким-либо из методов `$form->addXyz()`. Например, когда мы выводим записи из базы данных и заранее не знаем, сколько их будет и какие у них будут ID, и хотим у каждой строки отобразить чекбокс или радиокнопку, достаточно закодировать это в шаблоне: ```latte {foreach $items as $item} @@ -450,13 +547,13 @@ $form->addZip('zip', 'ZIP-код:'); {/foreach} ``` -После отправки можно получить значения: +А после отправки узнать значение: ```php $data = $form->getHttpData($form::DataText, 'sel[]'); $data = $form->getHttpData($form::DataText | $form::DataKeys, 'sel[]'); ``` -В первом параметре вы указываете тип элемента (`DataFile` для `type=file`, `DataLine` для однострочных вводов типа `text`, `password` или `email` и `DataText` для остальных). Второй параметр соответствует HTML-атрибуту `name`. Если вам нужно сохранить ключи, вы можете объединить первый параметр с `DataKeys`. Это полезно для `select`, `radioList` или `checkboxList`. +где первый параметр — это тип элемента (`DataFile` для `type=file`, `DataLine` для однострочных вводов, таких как `text`, `password`, `email` и т. д., и `DataText` для всех остальных), а второй параметр `sel[]` соответствует HTML-атрибуту name. Тип элемента можно комбинировать со значением `DataKeys`, которое сохраняет ключи элементов. Это особенно полезно для `select`, `radioList` и `checkboxList`. -Функция `getHttpData()` возвращает обработанные данные. В этом случае это всегда будет массив допустимых строк UTF-8, независимо от того, что атакующий отправил через форму. Это альтернатива работе с `$_POST` или `$_GET` напрямую, если вы хотите получать безопасные данные. +Существенно то, что `getHttpData()` возвращает санитайзенное значение, в данном случае это всегда будет массив валидных UTF-8 строк, независимо от того, что попытался бы подсунуть серверу злоумышленник. Это аналог прямой работы с `$_POST` или `$_GET`, но с тем существенным отличием, что всегда возвращаются чистые данные, так, как вы привыкли у стандартных элементов форм Nette. diff --git a/forms/ru/in-presenter.texy b/forms/ru/in-presenter.texy index f8ba0bff66..8aec5e9975 100644 --- a/forms/ru/in-presenter.texy +++ b/forms/ru/in-presenter.texy @@ -2,15 +2,15 @@ ******************* .[perex] -Nette Forms значительно упрощает создание и обработку веб-форм. В этой главе вы узнаете, как использовать формы внутри презентеров. +Nette Forms значительно упрощают создание и обработку веб-форм. В этой главе вы узнаете, как использовать формы внутри презентеров. -Если вы хотите использовать их полностью автономно, без остальной части фреймворка, есть руководство по [автономным формам|standalone]. +Если вас интересует, как использовать их полностью самостоятельно, без остальной части фреймворка, для вас предназначено руководство по [самостоятельному использованию|standalone]. -Первая форма .[#toc-first-form] -=============================== +Первая форма +============ -Мы постараемся написать простую регистрационную форму. Её код будет выглядеть следующим образом: +Попробуем написать простую регистрационную форму. Ее код будет следующим: ```php use Nette\Application\UI\Form; @@ -22,15 +22,15 @@ $form->addSubmit('send', 'Зарегистрироваться'); $form->onSuccess[] = [$this, 'formSucceeded']; ``` -и в браузере результат должен выглядеть следующим образом: +а в браузере она отобразится так: -[* form-en.webp *] +[* form-cs.webp *] -Форма в презентере является объектом класса `Nette\Application\UI\Form`, его предшественник `Nette\Forms\Form` предназначен для автономного использования. Мы добавили в него поля имя, пароль и кнопку отправки. Наконец, в строке с `$form->onSuccess` говорится, что после отправки и успешной валидации должен быть вызван метод `$this->formSucceeded()`. +Форма в презентере — это объект класса `Nette\Application\UI\Form`, ее предшественник `Nette\Forms\Form` предназначен для самостоятельного использования. Мы добавили в нее так называемые элементы: имя, пароль и кнопку отправки. И, наконец, строка с `$form->onSuccess` говорит, что после отправки и успешной валидации должен быть вызван метод `$this->formSucceeded()`. -С точки зрения презентера форма является общим компонентом. Поэтому она рассматривается как компонент и включается в презентер с помощью [фабричного метода |application:components#Factory-Methods]. Это будет выглядеть следующим образом: +С точки зрения презентера форма является обычным компонентом. Поэтому с ней обращаются как с компонентом и включают ее в презентер с помощью [фабричных методов |application:components#Фабричные методы]. Это будет выглядеть так: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} use Nette; use Nette\Application\UI\Form; @@ -48,94 +48,91 @@ class HomePresenter extends Nette\Application\UI\Presenter public function formSucceeded(Form $form, $data): void { - // здесь мы будем обрабатывать данные, отправленные формой + // здесь мы обрабатываем данные, отправленные формой // $data->name содержит имя // $data->password содержит пароль - $this->flashMessage('Вы успешно зарегистрировались.'); + $this->flashMessage('Вы были успешно зарегистрированы.'); $this->redirect('Home:'); } } ``` -А рендеринг в шаблоне осуществляется с помощью тега `{control}`: +А в шаблоне форму отрисовываем тегом `{control}`: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} <h1>Регистрация</h1> {control registrationForm} ``` -И это всё :-) У нас есть функциональная и идеально [защищенная |#Защита от уязвимостей] форма. +И это, собственно, все :-) У нас есть рабочая и идеально [защищенная |#Защита от уязвимостей] форма. -Теперь вы, вероятно, думаете, что это было слишком быстро, задаваясь вопросом, как возможно, что вызывается метод `formSucceeded()` и какие параметры он получает. Конечно, вы правы, это заслуживает объяснения. +А теперь вы, вероятно, думаете, что это было слишком быстро, и размышляете, как возможно, что вызывается метод `formSucceeded()` и какие параметры он получает. Конечно, вы правы, это заслуживает объяснения. -Nette придумала классный механизм, который мы назвали [Голливудский стиль |application:components#Hollywood-Style]. Вместо того, чтобы постоянно спрашивать, произошло ли что-то («была ли форма отправлена?», «была ли она отправлена правильно?», или «не была ли она подделана?»), вы говорите фреймворку: «когда форма заполнена правильно, вызовите этот метод». Если вы программируете на JavaScript, вы знакомы с этим стилем программирования. Вы пишете функции, которые вызываются при наступлении определенного [события|nette:glossary#Events]. И язык передает им соответствующие аргументы. +Nette предлагает свежий механизм, который мы называем [Hollywood style |application:components#Стиль Голливуда]. Вместо того чтобы вам, как разработчику, постоянно спрашивать, произошло ли что-то («была ли форма отправлена?», «была ли она отправлена валидно?» и «не была ли она подделана?»), вы говорите фреймворку: «когда форма будет валидно заполнена, вызови этот метод» и оставляете дальнейшую работу ему. Если вы программируете на JavaScript, этот стиль программирования вам хорошо знаком. Вы пишете функции, которые вызываются, когда наступает определенное [событие |nette:glossary#События Events]. И язык передает им соответствующие аргументы. -Вот как построен приведенный выше код презентера. Массив `$form->onSuccess` представляет собой список обратных вызовов PHP, которые Nette будет вызывать, когда форма будет отправлена и правильно заполнена. -В рамках [жизненного цикла презентера |application:presenters#Life-Cycle-of-Presenter] это так называемый сигнал, поэтому они вызываются после метода `action*` и перед методом `render*`. -И он передает каждому обратному вызову саму форму в первом параметре и отправленные данные в виде объекта [ArrayHash |utils:arrays#ArrayHash] во втором. Вы можете опустить первый параметр, если вам не нужен объект формы. Второй параметр может быть ещё более удобным, но об этом [позже|#Mapping-to-Classes]. +Именно так построен и вышеприведенный код презентера. Массив `$form->onSuccess` представляет собой список PHP callback-ов, которые Nette вызовет в момент, когда форма отправлена и правильно заполнена (т. е. валидна). В рамках [жизненного цикла презентера |application:presenters#Жизненный цикл презентера] это так называемый сигнал, то есть они вызываются после метода `action*` и перед методом `render*`. И каждому callback-у он передает в качестве первого параметра саму форму, а в качестве второго — отправленные данные в виде объекта [ArrayHash |utils:arrays#ArrayHash]. Первый параметр можно опустить, если объект формы вам не нужен. А второй параметр может быть хитрее, но об этом [позже |#Маппинг на классы]. -Объект `$data` содержит свойства `name` и `password` с данными, введенными пользователем. Обычно мы отправляем данные непосредственно для дальнейшей обработки, которая может быть, например, вставкой в базу данных. Однако в процессе обработки может возникнуть ошибка, например, имя пользователя уже занято. В этом случае мы передаем ошибку обратно в форму с помощью `addError()` и позволяем ей перерисовываться заново, с сообщением об ошибке: +Объект `$data` содержит ключи `name` и `password` с данными, которые заполнил пользователь. Обычно данные сразу отправляются на дальнейшую обработку, что может быть, например, вставка в базу данных. Однако во время обработки может возникнуть ошибка, например, имя пользователя уже занято. В таком случае мы передаем ошибку обратно в форму с помощью `addError()` и позволяем ей отрисоваться снова, уже с сообщением об ошибке. ```php -$form->addError('Извините, имя пользователя уже используется.'); +$form->addError('Извините, это имя пользователя уже используется.'); ``` -В дополнение к `onSuccess`, существует также `onSubmit`: обратные вызовы всегда вызываются после отправки формы, даже если она заполнена неправильно. И, наконец, `onError`: обратные вызовы вызываются только в том случае, если отправка недействительна. Они вызываются, даже если мы аннулируем форму в `onSuccess` или `onSubmit` с помощью `addError()`. +Кроме `onSuccess` существует еще `onSubmit`: callback-и вызываются всегда после отправки формы, даже если она заполнена неправильно. А также `onError`: callback-и вызываются только если отправка невалидна. Они вызываются даже тогда, когда в `onSuccess` или `onSubmit` мы делаем форму невалидной с помощью `addError()`. -После обработки формы мы перенаправим вас на следующую страницу. Это предотвращает непреднамеренную повторную отправку формы при нажатии кнопки *обновить*, *назад* или перемещении истории браузера. +После обработки формы мы перенаправляем на следующую страницу. Это предотвратит нежелательную повторную отправку формы кнопкой *обновить*, *назад* или перемещением в истории браузера. -Попробуйте добавить больше [элементов управления формы|controls]. +Попробуйте добавить и другие [элементы формы|controls]. -Доступ к элементам управления .[#toc-access-to-controls] -======================================================== +Доступ к элементам +================== -Форма является компонентом презентера, в нашем случае с именем `registrationForm` (по имени фабричного метода `createComponentRegistrationForm`), поэтому в любом месте презентера вы можете получить доступ к форме, используя: +Форма является компонентом презентера, в нашем случае названным `registrationForm` (по имени фабричного метода `createComponentRegistrationForm`), поэтому где угодно в презентере к форме можно получить доступ с помощью: ```php $form = $this->getComponent('registrationForm'); // альтернативный синтаксис: $form = $this['registrationForm']; ``` -Также отдельные элементы управления формы являются компонентами, поэтому доступ к ним можно получить таким же образом: +Отдельные элементы формы также являются компонентами, поэтому к ним можно получить доступ таким же образом: ```php $input = $form->getComponent('name'); // или $input = $form['name']; $button = $form->getComponent('send'); // или $button = $form['send']; ``` -Элементы управления удаляются с помощью функции unset: +Элементы удаляются с помощью unset: ```php unset($form['name']); ``` -Правила валидации .[#toc-validation-rules] -========================================== +Правила валидации +================= -Слово *valid* было использовано несколько раз, но форма ещё не имеет правил валидации. Давайте исправим это. +Здесь прозвучало слово *валидная,* но у формы пока нет никаких правил валидации. Давайте это исправим. -Имя будет обязательным, поэтому мы пометим его методом `setRequired()`, аргументом которого является текст сообщения об ошибке, которое будет выведено, если пользователь не заполнит его. Если аргумент не указан, используется сообщение об ошибке по умолчанию. +Имя будет обязательным, поэтому мы отметим его методом `setRequired()`, аргументом которого является текст сообщения об ошибке, которое отобразится, если пользователь не заполнит имя. Если аргумент не указан, используется сообщение об ошибке по умолчанию. ```php $form->addText('name', 'Имя:') - ->setRequired('Пожалуйста, введите имя.'); + ->setRequired('Пожалуйста, введите имя'); ``` -Попробуйте отправить форму без заполненного имени, и вы увидите, что появится сообщение об ошибке, и браузер или сервер будет отклонять форму, пока вы не заполните её. +Попробуйте отправить форму без заполненного имени, и вы увидите, что отобразится сообщение об ошибке, и браузер или сервер будет отклонять ее до тех пор, пока вы не заполните поле. -В то же время вы не сможете обмануть систему, набрав в поле ввода, например, только пробелы. Ни за что. Nette автоматически обрезает левые и правые пробельные символы. Попробуйте. Это то, что вы всегда должны делать с каждым однострочным вводом, но об этом часто забывают. Nette делает это автоматически. (Вы можете попытаться обмануть форму и отправить многострочную строку в качестве имени. Даже здесь Nette не будет обманут, и переносы строк будут заменены на пробелы). +В то же время систему не обмануть, введя в поле, например, только пробелы. Ни в коем случае. Nette автоматически удаляет пробелы слева и справа. Попробуйте сами. Это то, что вы всегда должны делать с каждым однострочным инпутом, но об этом часто забывают. Nette делает это автоматически. (Можете попробовать обмануть форму и отправить в качестве имени многострочную строку. И здесь Nette не даст себя обмануть и заменит переносы строк на пробелы.) -Форма всегда проверяется на стороне сервера, но также генерируется проверка JavaScript, что происходит быстро, и пользователь сразу же узнает об ошибке, без необходимости отправлять форму на сервер. Этим занимается скрипт `netteForms.js`. -Вставьте его в шаблон макета: +Форма всегда валидируется на стороне сервера, но также генерируется JavaScript-валидация, которая выполняется мгновенно, и пользователь узнает об ошибке сразу, без необходимости отправлять форму на сервер. За это отвечает скрипт `netteForms.js`. Вставьте его в шаблон макета: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Если вы посмотрите в исходный код страницы с формой, вы можете заметить, что Nette вставляет обязательные поля в элементы с CSS-классом `required`. Попробуйте добавить следующий стиль в шаблон, и метка "Имя" будет красного цвета. Мы элегантно помечаем обязательные поля для пользователей: +Если вы посмотрите исходный код страницы с формой, вы можете заметить, что Nette вставляет обязательные элементы в элементы с CSS-классом `required`. Попробуйте добавить в шаблон следующую таблицу стилей, и надпись «Имя» станет красной. Таким образом, мы элегантно выделяем обязательные элементы для пользователей: ```latte <style> @@ -143,96 +140,96 @@ $form->addText('name', 'Имя:') </style> ``` -Дополнительные правила валидации будут добавлены методом `addRule()`. Первым параметром является правило, вторым — текст сообщения об ошибке, далее может следовать необязательный аргумент правила проверки. Что это значит? +Другие правила валидации добавляются методом `addRule()`. Первый параметр — это правило, второй — снова текст сообщения об ошибке, и может еще следовать аргумент правила валидации. Что это значит? -Форма получит ещё один необязательный элемент ввода *age* с условием, что он должен быть числом (`addInteger()`) и находиться в определенных границах (`$form::Range`). И здесь мы будем использовать третий аргумент `addRule()`, сам диапазон: +Расширим форму новым необязательным полем «возраст», которое должно быть целым числом (`addInteger()`) и, кроме того, находиться в допустимом диапазоне (`$form::Range`). И здесь мы как раз используем третий параметр метода `addRule()`, которым передадим валидатору требуемый диапазон в виде пары `[от, до]`: ```php $form->addInteger('age', 'Возраст:') - ->addRule($form::Range, 'Вы должны быть старше 18 лет и иметь возраст до 120 лет.', [18, 120]); + ->addRule($form::Range, 'Возраст должен быть от 18 до 120', [18, 120]); ``` .[tip] -Если пользователь не заполнит поле, правила валидации не будут проверены, поскольку поле является необязательным. +Если пользователь не заполнит поле, правила валидации проверяться не будут, так как элемент необязателен. -Очевидно, что здесь есть место для небольшого рефакторинга. В сообщении об ошибке и в третьем параметре числа перечислены в двух экземплярах, что не идеально. Если бы мы создавали [многоязычную форму|rendering#translating] и сообщение, содержащее числа, пришлось бы переводить на несколько языков, это усложнило бы изменение значений. По этой причине можно использовать символы-заменители `%d`: +Здесь возникает пространство для небольшого рефакторинга. В сообщении об ошибке и в третьем параметре числа указаны дублировано, что не идеально. Если бы мы создавали [многоязычные формы |rendering#Перевод] и сообщение, содержащее числа, было бы переведено на несколько языков, то возможное изменение значений усложнилось бы. По этой причине можно использовать плейсхолдеры `%d`, и Nette дополнит значения: ```php - ->addRule($form::Range, 'Вы должны быть старше %d лет и иметь возраст до %d лет.', [18, 120]); + ->addRule($form::Range, 'Возраст должен быть от %d до %d лет', [18, 120]); ``` -Вернемся к полю *пароль*, сделаем его *обязательным* и проверим минимальную длину пароля (`$form::MinLength`), снова используя символы-заменители в сообщении: +Вернемся к элементу `password`, который также сделаем обязательным и еще проверим минимальную длину пароля (`$form::MinLength`), снова с использованием плейсхолдера: ```php $form->addPassword('password', 'Пароль:') ->setRequired('Выберите пароль') - ->addRule($form::MinLength, 'Ваш пароль должен быть длиной не менее %d', 8); + ->addRule($form::MinLength, 'Пароль должен содержать не менее %d символов', 8); ``` -Мы добавим в форму поле `passwordVerify`, в котором пользователь вводит пароль ещё раз, для проверки. Используя правила валидации, мы проверяем, одинаковы ли оба пароля (`$form::Equal`). А в качестве аргумента мы даем ссылку на первый пароль, используя [квадратные скобки |#Access-to-Controls]: +Добавим в форму еще поле `passwordVerify`, где пользователь введет пароль еще раз, для проверки. С помощью правил валидации проверим, совпадают ли оба пароля (`$form::Equal`). А в качестве параметра дадим ссылку на первый пароль с помощью [квадратных скобок |#Доступ к элементам]: ```php -$form->addPassword('passwordVerify', 'Повторите пароль:') - ->setRequired('Введите пароль ещё раз, чтобы проверить опечатку') - ->addRule($form::Equal, 'Несоответствие пароля', $form['password']) +$form->addPassword('passwordVerify', 'Пароль для проверки:') + ->setRequired('Пожалуйста, введите пароль еще раз для проверки') + ->addRule($form::Equal, 'Пароли не совпадают', $form['password']) ->setOmitted(); ``` -Используя `setOmitted()`, мы пометили элемент, значение которого нас не особо волнует и который существует только для проверки. Его значение не передается в `$data`. +С помощью `setOmitted()` мы отметили элемент, значение которого нам на самом деле не важно и который существует только для валидации. Значение не передается в `$data`. -У нас есть полнофункциональная форма с валидацией на PHP и JavaScript. Возможности валидации в Nette гораздо шире, вы можете создавать условия, отображать и скрывать части страницы в соответствии с ними и т. д. Вы можете узнать обо всем в главе [Валидация форм|validation]. +Таким образом, у нас есть полностью рабочая форма с валидацией на PHP и JavaScript. Возможности валидации Nette гораздо шире, можно создавать условия, позволять по ним отображать и скрывать части страницы и т. д. Все это вы узнаете в главе о [валидации форм|validation]. -Значения по умолчанию .[#toc-default-values] -============================================ +Значения по умолчанию +===================== -Мы часто устанавливаем значения по умолчанию для элементов управления формы: +Элементам формы обычно устанавливают значения по умолчанию: ```php -$form->addEmail('email', 'Имейл') +$form->addEmail('email', 'E-mail') ->setDefaultValue($lastUsedEmail); ``` -Часто бывает полезно установить значения по умолчанию сразу для всех элементов управления. Например, когда форма используется для редактирования записей. Мы считываем запись из базы данных и устанавливаем её в качестве значения по умолчанию: +Часто бывает полезно установить значения по умолчанию для всех элементов одновременно. Например, когда форма служит для редактирования записей. Мы читаем запись из базы данных и устанавливаем значения по умолчанию: ```php //$row = ['name' => 'John', 'age' => '33', /* ... */]; $form->setDefaults($row); ``` -Вызовите `setDefaults()` после определения элементов управления. +Вызывайте `setDefaults()` после определения элементов. -Отображение формы .[#toc-rendering-the-form] -============================================ +Отрисовка формы +=============== -По умолчанию форма отображается в виде таблицы. Отдельные элементы управления следуют основным рекомендациям по обеспечению доступности веб-страниц. Все метки генерируются как элементы `<label>` и связаны со своими элементами, щелчок по метке перемещает курсор на соответствующий элемент. +По умолчанию форма отрисовывается в виде таблицы. Отдельные элементы соответствуют основному правилу доступности — все надписи записаны как `<label>` и связаны с соответствующим элементом формы. При клике на надпись курсор автоматически появляется в поле формы. -Мы можем установить любые атрибуты HTML для каждого элемента. Например, добавьте заполнитель: +Каждому элементу мы можем устанавливать любые HTML-атрибуты. Например, добавить placeholder: ```php $form->addInteger('age', 'Возраст:') - ->setHtmlAttribute('placeholder', 'Пожалуйста, заполните возраст'); + ->setHtmlAttribute('placeholder', 'Пожалуйста, укажите возраст'); ``` -На самом деле существует множество способов визуализации формы, подробнее в главе [Рендеринг|rendering]. +Способов отрисовки формы действительно много, поэтому этому посвящена [отдельная глава об отрисовке|rendering]. -Сопоставление с классами .[#toc-mapping-to-classes] -=================================================== +Маппинг на классы +================= -Давайте вернемся к обработке данных формы. Метод `getValues()` возвращает представленные данные в виде объекта `ArrayHash`. Поскольку это общий класс, что-то вроде `stdClass`, нам будет не хватать некоторых удобств при работе с ним, таких как завершение кода для свойств в редакторах или статический анализ кода. Эту проблему можно решить, создав для каждой формы отдельный класс, свойства которого представляют отдельные элементы управления. Например: +Вернемся к методу `formSucceeded()`, который во втором параметре `$data` получает отправленные данные как объект `ArrayHash`. Поскольку это генерический класс, что-то вроде `stdClass`, при работе с ним нам будет не хватать определенного комфорта, такого как автодополнение свойств в редакторах или статический анализ кода. Это можно было бы решить, имея для каждой формы конкретный класс, свойства которого представляют отдельные элементы. Например: ```php class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -Начиная с PHP 8.0, вы можете использовать эту элегантную нотацию, которая использует конструктор: +Альтернативно можно использовать конструктор: ```php class RegistrationFormData @@ -246,27 +243,29 @@ class RegistrationFormData } ``` -Как указать Nette возвращать данные в виде объектов этого класса? Легче, чем вы думаете. Все, что вам нужно сделать, это указать класс в качестве типа параметра `$data` в обработчике: +Свойства класса данных также могут быть перечислениями (enum), и они будут автоматически сопоставлены. .{data-version:3.2.4} + +Как сказать Nette, чтобы он возвращал нам данные в виде объектов этого класса? Проще, чем вы думаете. Достаточно просто указать класс как тип параметра `$data` в методе-обработчике: ```php public function formSucceeded(Form $form, RegistrationFormData $data): void { - // $name — экземпляр RegistrationFormData + // $data является экземпляром RegistrationFormData $name = $data->name; // ... } ``` -Вы также можете указать `array` в качестве типа, и тогда данные будут передаваться в виде массива. +В качестве типа можно также указать `array`, и тогда данные будут переданы в виде массива. -Аналогичным образом можно использовать метод `getValues()`, которому в качестве параметра мы передаем имя класса или объекта для гидратации: +Аналогичным образом можно использовать и функцию `getValues()`, которой мы передаем имя класса или объект для гидратации в качестве параметра: ```php $data = $form->getValues(RegistrationFormData::class); $name = $data->name; ``` -Если формы состоят из многоуровневой структуры, состоящей из контейнеров, создайте отдельный класс для каждого из них: +Если формы образуют многоуровневую структуру, состоящую из контейнеров, создайте для каждого отдельный класс: ```php $form = new Form; @@ -283,22 +282,24 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` -Из типа свойства `$person` отображение узнает, что оно должно отобразить контейнер на класс `PersonFormData`. Если свойство будет содержать массив контейнеров, укажите тип `array` и передайте класс, который должен быть сопоставлен непосредственно с контейнером: +Маппинг тогда по типу свойства `$person` поймет, что контейнер нужно сопоставить с классом `PersonFormData`. Если бы свойство содержало массив контейнеров, укажите тип `array` и передайте класс для маппинга непосредственно контейнеру: ```php $person->setMappedType(PersonFormData::class); ``` +Вы можете сгенерировать проект класса данных формы с помощью метода `Nette\Forms\Blueprint::dataClass($form)`, который выведет его на страницу браузера. Затем достаточно кликнуть, чтобы выделить код, и скопировать его в проект. .{data-version:3.1.15} + -Несколько кнопок отправки .[#toc-multiple-submit-buttons] -========================================================= +Несколько кнопок +================ -Если форма содержит более одной кнопки, нам обычно нужно различать, какая из них была нажата. Мы можем создать собственную функцию для каждой кнопки. Установите его в качестве обработчика для [события|nette:glossary#Events] `onClick`: +Если у формы больше одной кнопки, нам обычно нужно различать, какая из них была нажата. Мы можем создать для каждой кнопки свою функцию-обработчик. Установим ее как обработчик для [события |nette:glossary#События Events] `onClick`: ```php $form->addSubmit('save', 'Сохранить') @@ -308,7 +309,7 @@ $form->addSubmit('delete', 'Удалить') ->onClick[] = [$this, 'deleteButtonPressed']; ``` -Эти обработчики также вызываются только в том случае, если форма действительна, как в случае события `onSuccess`. Разница в том, что первым параметром может быть объект кнопки submit, а не форма, в зависимости от типа, который вы укажете: +Эти обработчики вызываются только в случае валидно заполненной формы, так же как и в случае события `onSuccess`. Разница в том, что в качестве первого параметра вместо формы может передаваться кнопка отправки, это зависит от типа, который вы укажете: ```php public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) @@ -318,62 +319,61 @@ public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) } ``` -Когда форма отправляется с помощью кнопки <kbd>Enter</kbd>, она обрабатывается так же, как если бы она была отправлена с помощью первой кнопки. +Когда форма отправляется нажатием клавиши <kbd>Enter</kbd>, это считается так, как если бы она была отправлена первой кнопкой. -Событие onAnchor .[#toc-event-onanchor] -======================================= +Событие onAnchor +================ -Когда вы создаете форму в фабричном методе (например, `createComponentRegistrationForm`), она ещё не знает, была ли она отправлена или с какими данными она была отправлена. Но бывают случаи, когда нам необходимо знать переданные значения, возможно, от них зависит, как будет выглядеть форма, или они используются для зависимых списков и т. д. +Когда в фабричном методе (например, `createComponentRegistrationForm`) мы собираем форму, она еще не знает, была ли она отправлена и с какими данными. Но есть случаи, когда нам нужно знать отправленные значения, например, от них зависит дальнейший вид формы, или они нужны для зависимых селектбоксов и т. д. -Поэтому можно сделать так, чтобы код, создающий форму, вызывался, когда она закреплена, т. е. он уже связан с презентером и знает его представленные данные. Мы поместим такой код в массив `$onAnchor`: +Поэтому часть кода, собирающего форму, можно вызвать только в момент, когда она так называемо «заякорена», то есть уже связана с презентером и знает свои отправленные данные. Такой код мы передадим в массив `$onAnchor`: ```php $country = $form->addSelect('country', 'Страна:', $this->model->getCountries()); $city = $form->addSelect('city', 'Город:'); $form->onAnchor[] = function () use ($country, $city) { - // эта функция будет вызвана, когда форма узнает данные, с которыми она была отправлена - // поэтому вы можете использовать метод getValue(). + // эта функция будет вызвана, когда форма будет знать, была ли она отправлена и с какими данными + // поэтому можно использовать метод getValue() $val = $country->getValue(); $city->setItems($val ? $this->model->getCities($val) : []); }; ``` -Vulnerability Protection -======================== +Защита от уязвимостей +===================== -Nette Framework прилагает большие усилия для обеспечения безопасности, а поскольку формы являются наиболее распространенным видом пользовательского ввода, формы Nette настолько же хороши, насколько непроницаемы. +Nette Framework уделяет большое внимание безопасности и поэтому тщательно следит за хорошей защитой форм. Он делает это совершенно прозрачно и не требует ручной настройки. -В дополнение к защите форм от атак известных уязвимостей, таких как [Cross-Site Scripting (XSS)|nette:glossary#Cross-Site-Scripting-XSS] и [Cross-Site Request Forgery (CSRF)|nette:glossary#Cross-Site-Request-Forgery-CSRF], он выполняет множество мелких задач по обеспечению безопасности, о которых вам больше не нужно думать. +Кроме того, что формы защищены от атаки [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] и [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF], он выполняет множество мелких мер безопасности, о которых вам уже не нужно думать. -Например, он отфильтровывает все управляющие символы из вводимых данных и проверяет правильность кодировки UTF-8, так что данные из формы всегда будут чистыми. Для полей выбора и радиосписков проверяется, что выбранные элементы действительно были из предложенных и не было подделки. Мы уже упоминали, что для ввода однострочного текста он удаляет символы конца строки, которые злоумышленник может туда отправить. Для многострочных вводов он нормализует символы конца строки. И так далее. +Например, он отфильтровывает из входных данных все управляющие символы и проверяет валидность кодировки UTF-8, так что данные из формы всегда будут чистыми. У селектбоксов и списков радиокнопок он проверяет, что выбранные элементы действительно были из предложенных и не было подделки. Мы уже упоминали, что у однострочных текстовых вводов он удаляет символы конца строки, которые мог отправить злоумышленник. У многострочных вводов он нормализует символы конца строки. И так далее. -Nette устраняет за вас уязвимости в системе безопасности, о существовании которых большинство программистов даже не подозревают. +Nette решает за вас риски безопасности, о существовании которых многие программисты даже не подозревают. -Упомянутая CSRF-атака заключается в том, что злоумышленник заманивает жертву посетить страницу, которая молча выполняет запрос в браузере жертвы к серверу, на котором жертва в данный момент зарегистрирована, и сервер считает, что запрос был сделан жертвой по собственному желанию. Таким образом, Nette предотвращает отправку формы через POST из другого домена. Если по какой-то причине вы хотите отключить защиту и разрешить отправку формы с другого домена, используйте: +Упомянутая CSRF-атака заключается в том, что злоумышленник заманивает жертву на страницу, которая незаметно в браузере жертвы выполняет запрос на сервер, на котором жертва авторизована, и сервер полагает, что запрос был выполнен жертвой по собственной воле. Поэтому Nette предотвращает отправку POST-формы с другого домена. Если по какой-то причине вы хотите отключить защиту и разрешить отправку формы с другого домена, используйте: ```php -$form->allowCrossOrigin(); // ВНИМАНИЕ! Выключает защиту! +$form->allowCrossOrigin(); // ВНИМАНИЕ! Отключает защиту! ``` -Эта защита использует файл cookie SameSite с именем `_nss`. Защита куки SameSite может быть не на 100% надежной, поэтому лучше включить защиту токенами: +Эта защита использует SameSite cookie с именем `_nss`. Защита с помощью SameSite cookie может быть не 100% надежной, поэтому рекомендуется включить еще и защиту с помощью токена: ```php $form->addProtection(); ``` -Настоятельно рекомендуется применять эту защиту к формам в административной части вашего приложения, которые изменяют конфиденциальные данные. Фреймворк защищает от атаки CSRF, генерируя и проверяя токен аутентификации, который хранится в сессии (аргументом является сообщение об ошибке, показываемое, если срок действия токена истек). Поэтому перед отображением формы необходимо, чтобы сессия была запущена. В административной части сайта сессия, как правило, уже началась, поскольку пользователь вошел в систему. -В противном случае, запустите сессию с помощью метода `Nette\Http\Session::start()`. +Рекомендуем таким образом защищать формы в административной части сайта, которые изменяют чувствительные данные в приложении. Фреймворк защищается от CSRF-атаки путем генерации и проверки авторизационного токена, который сохраняется в сессии. Поэтому перед отображением формы необходимо иметь открытую сессию. В административной части сайта сессия обычно уже запущена из-за входа пользователя. В противном случае запустите сессию методом `Nette\Http\Session::start()`. -Использование одной формы в нескольких презентерах .[#toc-using-one-form-in-multiple-presenters] -================================================================================================ +Одна и та же форма в нескольких презентерах +=========================================== -Если вам нужно использовать одну форму в нескольких презентерах, мы рекомендуем вам создать для нее фабрику, которую вы затем передадите презентеру. Подходящим местом для такого класса является, например, каталог `app/Forms`. +Если вам нужно использовать одну и ту же форму в нескольких презентерах, рекомендуем создать для нее фабрику, которую затем передать в презентер. Подходящее место для такого класса — например, каталог `app/Forms`. -Класс фабрики может выглядеть следующим образом: +Фабричный класс может выглядеть, например, так: ```php use Nette\Application\UI\Form; @@ -390,7 +390,7 @@ class SignInFormFactory } ``` -Мы просим класс изготовить форму в методе фабрики для компонентов в презентере: +Мы попросим класс создать форму в фабричном методе для компонентов в презентере: ```php public function __construct( @@ -401,14 +401,14 @@ public function __construct( protected function createComponentSignInForm(): Form { $form = $this->formFactory->create(); - // мы можем изменить форму, например, метку на кнопке - $form['login']->setCaption('Продолжить'); - $form->onSuccess[] = [$this, 'signInFormSubmitted']; // и добавить обработчик + // мы можем изменить форму, здесь, например, мы меняем надпись на кнопке + $form['send']->setCaption('Продолжить'); + $form->onSuccess[] = [$this, 'signInFormSuceeded']; // и добавляем обработчик return $form; } ``` -Обработчик формы также может быть получен с помощью фабрики: +Обработчик для обработки формы также может быть предоставлен уже из фабрики: ```php use Nette\Application\UI\Form; @@ -421,11 +421,11 @@ class SignInFormFactory $form->addText('name', 'Имя:'); $form->addSubmit('send', 'Войти'); $form->onSuccess[] = function (Form $form, $data): void { - // здесь мы обрабатываем отправленную форму + // здесь мы обрабатываем форму }; return $form; } } ``` -Итак, мы кратко познакомились с формами в Nette. Попробуйте поискать вдохновение в каталоге [examples |https://github.com/nette/forms/tree/master/examples] в дистрибутиве. +Итак, мы прошли быстрое введение в формы в Nette. Попробуйте еще заглянуть в каталог [examples|https://github.com/nette/forms/tree/master/examples] в дистрибутиве, где вы найдете дополнительное вдохновение. diff --git a/forms/ru/rendering.texy b/forms/ru/rendering.texy index 0d1a640600..0683fcfc7c 100644 --- a/forms/ru/rendering.texy +++ b/forms/ru/rendering.texy @@ -1,33 +1,35 @@ -Рендеринг форм +Отрисовка форм ************** -Внешний вид форм может быть очень разнообразным. На практике мы можем столкнуться с двумя крайностями. С одной стороны, в приложении необходимо отобразить ряд форм, визуально похожих друг на друга, и мы ценим простоту визуализации без шаблона с помощью `$form->render()`. Обычно это относится к административным интерфейсам. +Внешний вид форм может быть очень разным. На практике мы можем столкнуться с двумя крайностями. С одной стороны, существует необходимость отрисовывать в приложении множество форм, визуально похожих друг на друга как две капли воды, и мы оценим простую отрисовку без шаблона с помощью `$form->render()`. Обычно это относится к административным интерфейсам. -С другой стороны, существуют различные формы, каждая из которых уникальна. Их внешний вид лучше всего описывать с помощью языка HTML в шаблоне. И конечно, помимо обеих упомянутых крайностей, мы встретим множество форм, которые находятся где-то посередине. +С другой стороны, существуют разнообразные формы, где действует правило: каждая — уникальна. Их вид лучше всего описать языком HTML в шаблоне формы. И, конечно, помимо этих двух крайностей, мы столкнемся с множеством форм, находящихся где-то посередине. -Рендеринг с помощью Latte .[#toc-rendering-with-latte] -====================================================== +Отрисовка с помощью Latte +========================= + +[Система шаблонов Latte |latte:] существенно упрощает отрисовку форм и их элементов. Сначала мы покажем, как отрисовывать формы вручную по отдельным элементам и тем самым получить полный контроль над кодом. Позже мы покажем, как можно такую отрисовку [автоматизировать |#Автоматическая отрисовка]. -[Система шаблонов Latte |latte:] в корне облегчает отрисовку форм и их элементов. Сначала мы покажем, как отрисовывать формы вручную, элемент за элементом, чтобы получить полный контроль над кодом. Позже мы покажем, как [автоматизировать |#Automatic-Rendering] такой рендеринг. +Проект Latte-шаблона для формы можно сгенерировать с помощью метода `Nette\Forms\Blueprint::latte($form)`, который выведет его на страницу браузера. Затем достаточно кликнуть, чтобы выделить код, и скопировать его в проект. .{data-version:3.1.15} `{control}` ----------- -Самый простой способ отобразить форму - написать ее в шаблоне: +Самый простой способ отрисовать форму — написать в шаблоне: ```latte {control signInForm} ``` -Внешний вид отрисованной формы можно изменить, настроив [Renderer |#Renderer] и [отдельные элементы управления |#HTML-Attributes]. +Повлиять на вид отрисованной таким образом формы можно с помощью конфигурации [#Renderer] и [отдельных элементов |#HTML-атрибуты]. `n:name` -------- -Очень легко связать определение формы в PHP-коде с HTML-кодом. Просто добавьте атрибуты `n:name`. Вот как это просто! +Определение формы в PHP-коде можно очень легко связать с HTML-кодом. Достаточно лишь добавить атрибуты `n:name`. Это так просто! ```php protected function createComponentSignInForm(): Form @@ -54,10 +56,9 @@ protected function createComponentSignInForm(): Form </form> ``` -Внешний вид получившегося HTML-кода полностью в ваших руках. Если вы используете атрибут `n:name` с `<select>`, `<button>` или `<textarea>` элементами, их внутреннее содержимое заполняется автоматически. -Кроме того, тег `<form n:name>` тег создает локальную переменную `$form` с нарисованным объектом формы, а закрывающий тег `</form>` перерисовывает все недорисованные скрытые элементы (то же самое относится и к `{form} ... {/form}`). +Вид результирующего HTML-кода полностью в ваших руках. Если атрибут `n:name` использовать у элементов `<select>`, `<button>` или `<textarea>`, их внутреннее содержимое автоматически дополнится. Тег `<form n:name>` к тому же создает локальную переменную `$form` с объектом отрисовываемой формы, а закрывающий `</form>` отрисовывает все неотрисованные скрытые элементы (то же самое относится и к `{form} ... {/form}`). -Однако не стоит забывать о выводе возможных сообщений об ошибках. Как тех, которые были добавлены к отдельным элементам методом `addError()` (с помощью `{inputError}`), так и тех, которые были добавлены непосредственно к форме (возвращаются методом `$form->getOwnErrors()`): +Однако нельзя забывать об отрисовке возможных сообщений об ошибках. Как тех, которые были добавлены методом `addError()` к отдельным элементам (с помощью `{inputError}`), так и тех, что добавлены непосредственно к форме (их возвращает `$form->getOwnErrors()`): ```latte <form n:name=signInForm class=form> @@ -79,7 +80,7 @@ protected function createComponentSignInForm(): Form </form> ``` -Более сложные элементы формы, такие как RadioList или CheckboxList, могут быть отображены элемент за элементом: +Более сложные элементы формы, такие как RadioList или CheckboxList, можно таким образом отрисовывать по отдельным элементам: ```latte {foreach $form[gender]->getItems() as $key => $label} @@ -88,16 +89,10 @@ protected function createComponentSignInForm(): Form ``` -Предложение кода `{formPrint}` .[#toc-formprint] ------------------------------------------------- - -Вы можете сгенерировать подобный код Latte для формы, используя тег `{formPrint}`. Если вы поместите его в шаблон, вы увидите проект кода вместо обычного рендеринга. Затем просто выделите его и скопируйте в свой проект. - - `{label}` `{input}` ------------------- -Не хотите ли вы подумать для каждого элемента, какой HTML-элемент использовать для него в шаблоне? `<input>`, `<textarea>` и т.д.? Решением является универсальный тег `{input}`: +Не хотите для каждого элемента думать, какой HTML-элемент использовать для него в шаблоне, будь то `<input>`, `<textarea>` и т. д.? Решением является универсальный тег `{input}`: ```latte <form n:name=signInForm class=form> @@ -119,9 +114,9 @@ protected function createComponentSignInForm(): Form </form> ``` -Если форма использует переводчик, то текст внутри тегов `{label}` будет переведен. +Если форма использует переводчик (translator), текст внутри тегов `{label}` будет переведен. -Опять же, более сложные элементы формы, такие как RadioList или CheckboxList, могут выводиться поэлементно: +И в этом случае более сложные элементы формы, такие как RadioList или CheckboxList, можно отрисовывать по отдельным элементам: ```latte {foreach $form[gender]->items as $key => $label} @@ -129,20 +124,19 @@ protected function createComponentSignInForm(): Form {/foreach} ``` -Чтобы отобразить `<input>` в элементе Checkbox, используйте `{input myCheckbox:}`. Атрибуты HTML должны быть разделены запятой `{input myCheckbox:, class: required}`. +Для отрисовки самого `<input>` в элементе Checkbox используйте `{input myCheckbox:}`. HTML-атрибуты в этом случае всегда отделяйте запятой `{input myCheckbox:, class: required}`. `{inputError}` -------------- -Выводит сообщение об ошибке для элемента формы, если оно есть. Сообщение обычно оборачивается в HTML-элемент для стилизации. -Избежать вывода пустого элемента при отсутствии сообщения можно с помощью `n:ifcontent`: +Выводит сообщение об ошибке для элемента формы, если оно есть. Сообщение обычно оборачивают в HTML-элемент для стилизации. Предотвратить отрисовку пустого элемента, если сообщения нет, можно элегантно с помощью `n:ifcontent`: ```latte <span class=error n:ifcontent>{inputError $input}</span> ``` -Мы можем определить наличие ошибки с помощью метода `hasErrors()` и установить класс родительского элемента соответствующим образом: +Наличие ошибки можно проверить методом `hasErrors()` и в зависимости от этого установить класс родительскому элементу: ```latte <div n:class="$form[username]->hasErrors() ? 'error'"> @@ -158,11 +152,10 @@ protected function createComponentSignInForm(): Form Теги `{form signInForm}...{/form}` являются альтернативой `<form n:name="signInForm">...</form>`. -Автоматический рендеринг .[#toc-automatic-rendering] ----------------------------------------------------- +Автоматическая отрисовка +------------------------ -С помощью тегов `{input}` и `{label}` мы можем легко создать общий шаблон для любой формы. Он будет итерировать и последовательно выводить все элементы формы, за исключением скрытых элементов, которые выводятся автоматически при завершении формы с помощью тега `</form>` тега. -Он будет ожидать имя отрисованной формы в переменной `$form`. +Благодаря тегам `{input}` и `{label}` мы можем легко создать общий шаблон для любой формы. Он будет последовательно итерировать и отрисовывать все ее элементы, кроме скрытых элементов, которые отрисовываются автоматически при завершении формы тегом `</form>`. Имя отрисовываемой формы он будет ожидать в переменной `$form`. ```latte <form n:name=$form class=form> @@ -179,16 +172,15 @@ protected function createComponentSignInForm(): Form </form> ``` -Используемые парные самозакрывающиеся теги `{label .../}` отображают метки, поступающие из определения формы в PHP-коде. +Использованные самозакрывающиеся парные теги `{label .../}` отображают метки (labels), взятые из определения формы в PHP-коде. -Вы можете сохранить этот общий шаблон в файле `basic-form.latte` и для рендеринга формы просто включить его и передать имя формы (или экземпляр) в параметр `$form`: +Сохраните этот общий шаблон, например, в файле `basic-form.latte`, и для отрисовки формы достаточно его включить и передать имя (или экземпляр) формы в параметр `$form`: ```latte {include basic-form.latte, form: signInForm} ``` -Если вы хотите повлиять на внешний вид одной конкретной формы и отрисовать один элемент по-другому, то самый простой способ - это подготовить в шаблоне блоки, которые можно перезаписать позже. -Блоки также могут иметь [динамические имена |latte:template-inheritance#Dynamic-Block-Names], поэтому вы можете вставить в них имя элемента, который нужно нарисовать. Например: +Если при отрисовке определенной формы вы захотите изменить ее вид и, например, один элемент отрисовать иначе, то самый простой путь — подготовить в шаблоне блоки, которые можно будет впоследствии переопределить. Блоки могут также иметь [динамические имена |latte:template-inheritance#Динамические имена блоков], в них можно так вставить и имя отрисовываемого элемента. Например: ```latte ... @@ -197,7 +189,7 @@ protected function createComponentSignInForm(): Form ... ``` -Для элемента, например, `username` создается блок `input-username`, который можно легко переопределить с помощью тега [{embed} |latte:template-inheritance#Unit-Inheritance]: +Для элемента, например `username`, будет создан блок `input-username`, который можно легко переопределить с помощью тега [{embed} |latte:template-inheritance#Модульное наследование]: ```latte {embed basic-form.latte, form: signInForm} @@ -209,7 +201,7 @@ protected function createComponentSignInForm(): Form {/embed} ``` -Альтернативно, все содержимое шаблона `basic-form.latte` может быть [определено |latte:template-inheritance#Definitions] как блок, включая параметр `$form`: +Альтернативно, все содержимое шаблона `basic-form.latte` можно [определить |latte:template-inheritance#Определения] как блок, включая параметр `$form`: ```latte {define basic-form, $form} @@ -219,7 +211,7 @@ protected function createComponentSignInForm(): Form {/define} ``` -Это несколько упростит его использование: +Благодаря этому его вызов станет немного проще: ```latte {embed basic-form, signInForm} @@ -227,31 +219,31 @@ protected function createComponentSignInForm(): Form {/embed} ``` -Вам нужно будет импортировать блок только в одном месте, в начале шаблона макета: +При этом блок достаточно импортировать только в одном месте — в начале шаблона макета (layout): ```latte {import basic-form.latte} ``` -Особые случаи .[#toc-special-cases] ------------------------------------ +Особые случаи +------------- -Если вам нужно отобразить только внутреннее содержимое формы без `<form>` & `</form>` HTML тегов, например, в AJAX запросе, вы можете открывать и закрывать форму с помощью `{formContext} … {/formContext}`. Он работает аналогично `{form}` в логическом смысле, здесь он позволяет вам использовать другие теги для рисования элементов формы, но в то же время он ничего не рисует. +Если вам нужно отрисовать только внутреннюю часть формы без HTML-тегов `<form>`, например, при отправке сниппетов, скройте их с помощью атрибута `n:tag-if`: ```latte -{formContext signForm} +<form n:name=signInForm n:tag-if=false> <div> <label n:name=username>Имя пользователя: <input n:name=username></label> {inputError username} </div> -{/formContext} +</form> ``` -Тег `formContainer` помогает при отрисовке вводимых данных внутри контейнера формы. +С отрисовкой элементов внутри контейнера формы поможет тег `{formContainer}`. ```latte -<p>Which news you wish to receive:</p> +<p>Какие новости вы хотите получать:</p> {formContainer emailNews} <ul> @@ -262,27 +254,27 @@ protected function createComponentSignInForm(): Form ``` -Рендеринг без Latte .[#toc-rendering-without-latte] -=================================================== +Отрисовка без Latte +=================== -Самый простой способ отобразить форму - вызвать: +Самый простой способ отрисовать форму — вызвать: ```php $form->render(); ``` -Внешний вид отрисованной формы можно изменить, настроив [Renderer |#Renderer] и [отдельные элементы управления |#HTML-Attributes]. +Повлиять на вид отрисованной таким образом формы можно с помощью конфигурации [#Renderer] и [отдельных элементов |#HTML-атрибуты]. -Рендеринг вручную .[#toc-manual-rendering] ------------------------------------------- +Ручная отрисовка +---------------- -Каждый элемент формы имеет методы, которые генерируют HTML код для поля и метки формы. Они могут возвращать его в виде строки или объекта [Nette\Utils\Html |utils:html-elements]: +Каждый элемент формы располагает методами, которые генерируют HTML-код поля формы и метки (labels). Они могут возвращать его либо как строку, либо как объект [Nette\Utils\Html |utils:html-elements]: - `getControl(): Html|string` возвращает HTML-код элемента -- `getLabel($caption = null): Html|string|null` возвращает HTML-код метки, если таковая имеется +- `getLabel($caption = null): Html|string|null` возвращает HTML-код метки, если она существует -Это позволяет отображать форму элемент за элементом: +Таким образом, форму можно отрисовывать по отдельным элементам: ```php <?php $form->render('begin') ?> @@ -305,47 +297,46 @@ $form->render(); <?php $form->render('end') ?> ``` -В то время как для некоторых элементов `getControl()` возвращает один HTML-элемент (например. `<input>`, `<select>` и т.д.), для других он возвращает целый кусок HTML-кода (CheckboxList, RadioList). -В этом случае можно использовать методы, генерирующие отдельные входы и метки, для каждого элемента отдельно: +В то время как для некоторых элементов `getControl()` возвращает единственный HTML-элемент (например, `<input>`, `<select>` и т. д.), для других — целый кусок HTML-кода (CheckboxList, RadioList). В таком случае вы можете использовать методы, которые генерируют отдельные инпуты и метки (labels), для каждого элемента отдельно: -- `getControlPart($key = null): ?Html` возвращает HTML-код одного элемента. -- `getLabelPart($key = null): ?Html` возвращает HTML-код для метки отдельного элемента +- `getControlPart($key = null): ?Html` возвращает HTML-код одного элемента +- `getLabelPart($key = null): ?Html` возвращает HTML-код метки одного элемента .[note] -Эти методы имеют префикс `get` по историческим причинам, но `generate` было бы лучше, так как он создает и возвращает новый элемент `Html` при каждом вызове. +Эти методы по историческим причинам имеют префикс `get`, но лучше было бы `generate`, потому что при каждом вызове они создают и возвращают новый `Html` элемент. -Рендерер .[#toc-renderer] -========================= +Renderer +======== -Это объект, обеспечивающий рендеринг формы. Он может быть установлен методом `$form->setRenderer`. Ему передается управление при вызове метода `$form->render()`. +Это объект, обеспечивающий отрисовку формы. Его можно установить методом `$form->setRenderer`. Ему передается управление при вызове метода `$form->render()`. -Если мы не зададим пользовательский рендерер, будет использоваться рендерер по умолчанию [api:Nette\Forms\Rendering\DefaultFormRenderer]. В результате элементы формы будут отображены в виде HTML-таблицы. Вывод выглядит следующим образом: +Если мы не установим собственный рендерер, будет использован рендерер по умолчанию [api:Nette\Forms\Rendering\DefaultFormRenderer]. Он отрисует элементы формы в виде HTML-таблицы. Вывод выглядит так: ```latte <table> <tr class="required"> - <th><label class="required" for="frm-name">Name:</label></th> + <th><label class="required" for="frm-name">Имя:</label></th> <td><input type="text" class="text" name="name" id="frm-name" required value=""></td> </tr> <tr class="required"> - <th><label class="required" for="frm-age">Age:</label></th> + <th><label class="required" for="frm-age">Возраст:</label></th> <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> </tr> <tr> - <th><label>Gender:</label></th> + <th><label>Пол:</label></th> ... ``` -Использовать таблицу или нет - решать вам, многие веб-дизайнеры предпочитают другую разметку, например, список. Мы можем настроить `DefaultFormRenderer` так, чтобы он вообще не выводился в таблицу. Мы просто должны установить соответствующие [$wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers]. Первый индекс всегда представляет область, а второй - элемент. Все соответствующие области показаны на рисунке: +Использовать таблицу для каркаса формы или нет — спорный вопрос, и многие веб-дизайнеры предпочитают другую разметку. Например, список определений. Поэтому переконфигурируем `DefaultFormRenderer` так, чтобы он отрисовал форму в виде списка. Конфигурация выполняется путем редактирования массива [$wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers]. Первый индекс всегда представляет область, а второй — ее атрибут. Отдельные области показаны на рисунке: -[* form-areas-en.webp *] +[* defaultformrenderer.webp *] -По умолчанию группа `controls` обернута в. `<table>`и каждый `pair` представляет собой строку таблицы `<tr>` содержащая пару `label` и `control` (ячейки `<th>` и `<td>`). Давайте изменим все эти элементы обертки. Мы завернем `controls` в `<dl>`, оставим `pair` в одиночестве, поместим `label` в `<dt>` и обернем `control` в `<dd>`: +По умолчанию группа элементов `controls` обернута таблицей `<table>`, каждый `pair` представляет строку таблицы `<tr>`, а пара `label` и `control` — ячейки `<th>` и `<td>`. Теперь изменим оборачивающие элементы. Область `controls` вложим в контейнер `<dl>`, область `pair` оставим без контейнера, `label` вложим в `<dt>` и, наконец, `control` обернем тегами `<dd>`: ```php $renderer = $form->getRenderer(); @@ -357,75 +348,75 @@ $renderer->wrappers['control']['container'] = 'dd'; $form->render(); ``` -В результате получится следующий фрагмент: +Результатом является этот HTML-код: ```latte <dl> - <dt><label class="required" for="frm-name">Name:</label></dt> + <dt><label class="required" for="frm-name">Имя:</label></dt> <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> - <dt><label class="required" for="frm-age">Age:</label></dt> + <dt><label class="required" for="frm-age">Возраст:</label></dt> <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> - <dt><label>Gender:</label></dt> + <dt><label>Пол:</label></dt> ... </dl> ``` -Обертки могут влиять на многие атрибуты. Например: +В массиве wrappers можно повлиять на целый ряд других атрибутов: -- добавить специальные классы CSS к каждому вводу формы -- различать четные и нечетные строки -- по-разному рисовать обязательные и необязательные элементы -- установить, будут ли сообщения об ошибках показываться над формой или рядом с каждым элементом +- добавлять CSS-классы отдельным типам элементов формы +- различать CSS-классом четные и нечетные строки +- визуально отличать обязательные и необязательные элементы +- определять, будут ли сообщения об ошибках отображаться непосредственно у элементов или над формой -Опции .[#toc-options] ---------------------- +Options +------- -Поведением Renderer также можно управлять, устанавливая *опции* для отдельных элементов формы. Таким образом, вы можете установить всплывающую подсказку, которая будет отображаться рядом с полем ввода: +Поведением Renderer можно управлять также путем установки *options* для отдельных элементов формы. Так можно установить надпись, которая выведется рядом с полем ввода: ```php $form->addText('phone', 'Номер:') ->setOption('description', 'Этот номер останется скрытым'); ``` -Если мы хотим поместить в него HTML-контент, мы используем класс [Html |utils:html-elements]. +Если мы хотим поместить в него HTML-содержимое, используем класс [Html |utils:html-elements] ```php use Nette\Utils\Html; -$form->addText('phone', 'Телефон:') +$form->addText('phone', 'Номер:') ->setOption('description', Html::el('p') - ->setHtml('<a href="...">Условия предоставления услуг.</a>') + ->setHtml('<a href="...">Условия хранения вашего номера</a>') ); ``` .[tip] -Html-элемент также можно использовать вместо label: `$form->addCheckbox('conditions', $label)`. +Html-элемент можно использовать и вместо метки (label): `$form->addCheckbox('conditions', $label)`. -Группировка входов .[#toc-grouping-inputs] ------------------------------------------- +Группировка элементов +--------------------- -Поля ввода можно объединить в наборы визуальных полей, создав группу: +Renderer позволяет группировать элементы в визуальные группы (fieldset): ```php -$form->addGroup('Персональные данные'); +$form->addGroup('Личные данные'); ``` -Создание новой группы активирует её — все добавленные далее элементы добавляются в эту группу. Вы можете построить форму следующим образом: +После создания новой группы она становится активной, и каждый вновь добавленный элемент одновременно добавляется и в нее. Так что форму можно строить таким образом: ```php $form = new Form; -$form->addGroup('Персональные данные'); +$form->addGroup('Личные данные'); $form->addText('name', 'Ваше имя:'); $form->addInteger('age', 'Ваш возраст:'); -$form->addEmail('email', 'Имейл:'); +$form->addEmail('email', 'Email:'); $form->addGroup('Адрес доставки'); $form->addCheckbox('send', 'Отправить по адресу'); @@ -434,116 +425,115 @@ $form->addText('city', 'Город:'); $form->addSelect('country', 'Страна:', $countries); ``` +Renderer сначала отрисовывает группы, а затем элементы, которые не принадлежат ни к какой группе. -Поддержка Bootstrap .[#toc-bootstrap-support] ---------------------------------------------- -Вы можете найти [примеры |https://github.com/nette/forms/tree/master/examples] настройки рендерера для [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], [Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] и [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php] +Поддержка Bootstrap +------------------- + +[В примерах |https://github.com/nette/forms/tree/master/examples] вы найдете примеры, как сконфигурировать Renderer для [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], [Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] и [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php] -Атрибуты HTML .[#toc-html-attributes] -===================================== +HTML-атрибуты +============= -Вы можете установить любые атрибуты HTML для элементов управления формы с помощью `setHtmlAttribute(string $name, $value = true)`: +Для установки любых HTML-атрибутов элементов формы используем метод `setHtmlAttribute(string $name, $value = true)`: ```php -$form->addInteger('number', 'Количество:') - ->setHtmlAttribute('class', 'bigNumbers'); +$form->addInteger('number', 'Номер:') + ->setHtmlAttribute('class', 'big-number'); -$form->addSelect('rank', 'Заказать:', ['price', 'name']) - ->setHtmlAttribute('onchange', 'submit()'); // вызывает JS-функцию submit() при изменении +$form->addSelect('rank', 'Сортировать по:', ['цене', 'названию']) + ->setHtmlAttribute('onchange', 'submit()'); // отправить при изменении -// применяется на <form> +// Для установки атрибутов самой <form> $form->setHtmlAttribute('id', 'myForm'); ``` -Установка типа входа: +Спецификация типа элемента: ```php $form->addText('tel', 'Ваш телефон:') ->setHtmlType('tel') - ->setHtmlAttribute('placeholder', 'Пожалуйста, заполните ваш телефон'); + ->setHtmlAttribute('placeholder', 'введите телефон'); ``` -Мы можем установить HTML-атрибут для отдельных элементов в списках радио- или чекбоксов с разными значениями для каждого из них. -Обратите внимание на двоеточие после `style:`, чтобы убедиться, что значение выбирается по ключу: +.[warning] +Установка типа и других атрибутов служит только для визуальных целей. Проверка правильности ввода должна происходить на сервере, что вы обеспечите выбором подходящего [элемента формы |controls] и указанием [правил валидации |validation]. + +Отдельным элементам в списках radio или checkbox мы можем установить HTML-атрибут с различными значениями для каждого из них. Обратите внимание на двоеточие после `style:`, которое обеспечит выбор значения по ключу: ```php -$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue']; +$colors = ['r' => 'красный', 'g' => 'зеленый', 'b' => 'синий']; $styles = ['r' => 'background:red', 'g' => 'background:green']; $form->addCheckboxList('colors', 'Цвета:', $colors) ->setHtmlAttribute('style:', $styles); ``` -Отображается как: +Выведет: ```latte -<label><input type="checkbox" name="colors[]" style="background:red" value="r">red</label> -<label><input type="checkbox" name="colors[]" style="background:green" value="g">green</label> -<label><input type="checkbox" name="colors[]" value="b">blue</label> +<label><input type="checkbox" name="colors[]" style="background:red" value="r">красный</label> +<label><input type="checkbox" name="colors[]" style="background:green" value="g">зеленый</label> +<label><input type="checkbox" name="colors[]" value="b">синий</label> ``` -Для логического атрибута HTML (который не имеет значения, например, `readonly`) можно использовать вопросительный знак: +Для установки логических атрибутов, таких как `readonly`, мы можем использовать запись с вопросительным знаком: ```php -$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue']; $form->addCheckboxList('colors', 'Цвета:', $colors) - ->setHtmlAttribute('readonly?', 'r'); // использовать массив для нескольких ключей, например ['r', 'g'] + ->setHtmlAttribute('readonly?', 'r'); // для нескольких ключей используйте массив, например ['r', 'g'] ``` -Отображается как: +Выведет: ```latte -<label><input type="checkbox" name="colors[]" readonly value="r">red</label> -<label><input type="checkbox" name="colors[]" value="g">green</label> -<label><input type="checkbox" name="colors[]" value="b">blue</label> +<label><input type="checkbox" name="colors[]" readonly value="r">красный</label> +<label><input type="checkbox" name="colors[]" value="g">зеленый</label> +<label><input type="checkbox" name="colors[]" value="b">синий</label> ``` -Для селекбоксов метод `setHtmlAttribute()` устанавливает атрибуты элемента `<select>`. Если мы хотим установить атрибуты для каждого -`<option>`, мы будем использовать метод `setOptionAttribute()`. Кроме того, двоеточие и вопросительный знак, использованные выше, работают: +В случае селектбоксов метод `setHtmlAttribute()` устанавливает атрибуты элемента `<select>`. Если мы хотим установить атрибуты отдельным `<option>`, используем метод `setOptionAttribute()`. Работают и записи с двоеточием и вопросительным знаком, указанные выше: ```php $form->addSelect('colors', 'Цвета:', $colors) ->setOptionAttribute('style:', $styles); ``` -Отображается как: +Выведет: ```latte <select name="colors"> - <option value="r" style="background:red">red</option> - <option value="g" style="background:green">green</option> - <option value="b">blue</option> + <option value="r" style="background:red">красный</option> + <option value="g" style="background:green">зеленый</option> + <option value="b">синий</option> </select> ``` -Прототипы .[#toc-prototypes] ----------------------------- +Прототипы +--------- -Альтернативным способом установки атрибутов HTML является изменение шаблона, на основе которого генерируется элемент HTML. Шаблон является объектом `Html` и возвращается методом `getControlPrototype()`: +Альтернативный способ установки HTML-атрибутов заключается в изменении прототипа (prototype), из которого генерируется HTML-элемент. Прототипом является объект `Html`, и его возвращает метод `getControlPrototype()`: ```php -$input = $form->addInteger('number'); +$input = $form->addInteger('number', 'Номер:'); $html = $input->getControlPrototype(); // <input> $html->class('big-number'); // <input class="big-number"> ``` -Шаблон метки, возвращаемый методом `getLabelPrototype()`, также может быть изменен таким образом: +Этим способом можно модифицировать и прототип метки (label), который возвращает `getLabelPrototype()`: ```php $html = $input->getLabelPrototype(); // <label> $html->class('distinctive'); // <label class="distinctive"> ``` -Для элементов Checkbox, CheckboxList и RadioList вы можете повлиять на шаблон элемента, который оборачивает элемент. Его возвращает `getContainerPrototype()`. По умолчанию это "пустой" элемент, поэтому ничего не отображается, но если дать ему имя, он будет отображаться: +У элементов Checkbox, CheckboxList и RadioList вы можете влиять на прототип элемента, который оборачивает весь элемент. Его возвращает `getContainerPrototype()`. В исходном состоянии это «пустой» элемент, так что ничего не отрисовывается, но тем, что мы установим ему имя, он будет отрисовываться: ```php $input = $form->addCheckbox('send'); -echo $input->getControl(); -// <label><input type="checkbox" name="send"></label> - $html = $input->getContainerPrototype(); $html->setName('div'); // <div> $html->class('check'); // <div class="check"> @@ -551,50 +541,49 @@ echo $input->getControl(); // <div class="check"><label><input type="checkbox" name="send"></label></div> ``` -В случае CheckboxList и RadioList можно также повлиять на шаблон разделителя элементов, возвращаемый методом `getSeparatorPrototype()`. По умолчанию это элемент `<br>`. Если вы измените его на парный элемент, он будет обволакивать отдельные элементы вместо того, чтобы разделять их. -Также можно влиять на шаблон HTML-элемента меток элементов, который возвращает метод `getItemLabelPrototype()`. +В случае CheckboxList и RadioList можно влиять и на прототип разделителя отдельных элементов, который возвращает метод `getSeparatorPrototype()`. В исходном состоянии это элемент `<br>`. Если вы измените его на парный элемент, он будет оборачивать отдельные элементы вместо разделения. А далее можно влиять на прототип HTML-элемента метки у отдельных элементов, который возвращает `getItemLabelPrototype()`. -Перевод .[#toc-translating] -=========================== +Перевод +======= -Если вы программируете многоязычное приложение, то вам, вероятно, потребуется отображать форму на разных языках. В Nette Framework для этого определен интерфейс перевода [api:Nette\Localization\Translator]. В Nette нет реализации по умолчанию, вы можете выбрать в соответствии с вашими потребностями из нескольких готовых решений, которые можно найти на [Componette |https://componette.org/search/localization]. В их документации описано, как настроить переводчик. +Если вы программируете многоязычное приложение, вам, вероятно, потребуется отрисовать форму в различных языковых версиях. Nette Framework для этой цели определяет интерфейс для перевода [api:Nette\Localization\Translator]. В Nette нет реализации по умолчанию, вы можете выбрать в соответствии со своими потребностями из нескольких готовых решений, которые найдете на [Componette |https://componette.org/search/localization]. В их документации вы узнаете, как конфигурировать переводчик (translator). -Форма поддерживает вывод текста через переводчик. Мы передаем его с помощью метода `setTranslator()`: +Формы поддерживают вывод текстов через переводчик (translator). Передадим его им с помощью метода `setTranslator()`: ```php $form->setTranslator($translator); ``` -С этого момента не только все метки, но и все сообщения об ошибках или записи в поле выбора будут переведены на другой язык. +С этого момента не только все метки (labels), но и все сообщения об ошибках или элементы select box будут переведены на другой язык. -Можно установить другой переводчик для отдельных элементов формы или полностью отключить перевод с помощью `null`: +При этом для отдельных элементов формы можно установить другой переводчик или полностью отключить перевод, установив значение `null`: ```php -$form->addSelect('carModel', 'Model:', $cars) +$form->addSelect('carModel', 'Модель:', $cars) ->setTranslator(null); ``` -Для [правил валидации |validation] переводчику также передаются определенные параметры, например, для правила: +Для [правил валидации |validation] переводчику (translator) передаются также специфические параметры, например, у правила: ```php -$form->addPassword('password', 'Password:') - ->addRule($form::MinLength, 'Password has to be at least %d characters long', 8) +$form->addPassword('password', 'Пароль:') + ->addRule($form::MinLength, 'Пароль должен содержать не менее %d символов', 8); ``` -транслятор вызывается со следующими параметрами: +вызывается переводчик (translator) с этими параметрами: ```php -$translator->translate('Password has to be at least %d characters long', 8); +$translator->translate('Пароль должен содержать не менее %d символов', 8); ``` -и таким образом может выбрать правильную форму множественного числа для слова `characters` по счету. +и, следовательно, он может выбрать правильную форму множественного числа для слова `символов` в зависимости от числа. -Событие onRender .[#toc-event-onrender] -======================================= +Событие onRender +================ -Непосредственно перед отображением формы мы можем вызвать наш код. Это может, например, добавить HTML-классы к элементам формы для правильного отображения. Добавим код в массив 'onRender': +Непосредственно перед отрисовкой формы мы можем вызвать наш код. Он может, например, дополнить элементы формы HTML-классами для правильного отображения. Код добавим в массив `onRender`: ```php $form->onRender[] = function ($form) { diff --git a/forms/ru/standalone.texy b/forms/ru/standalone.texy index 65912b061e..a1dbc93c28 100644 --- a/forms/ru/standalone.texy +++ b/forms/ru/standalone.texy @@ -1,16 +1,16 @@ -Формы, используемые автономно -***************************** +Использование форм отдельно +*************************** .[perex] -Nette Forms значительно упрощает создание и обработку веб-форм. Вы можете использовать их в своих приложениях совершенно самостоятельно, без остального фреймворка, что мы и продемонстрируем в этой главе. +Nette Forms значительно упрощают создание и обработку веб-форм. Вы можете использовать их в своих приложениях совершенно отдельно от остальной части фреймворка, что мы и покажем в этой главе. -Однако если вы используете приложение Nette и презентеры, для вас есть руководство: [Формы в презентерах|in-presenter]. +Однако, если вы используете Nette Application и презентеры, для вас предназначено руководство по [использованию в презентерах |in-presenter]. -Первая форма .[#toc-first-form] -=============================== +Первая форма +============ -Мы постараемся написать простую регистрационную форму. Его код будет выглядеть следующим образом ("полный код":https://gist.github.com/dg/370a7e3094d9ba9a9e913b8e2a2dc851): +Попробуем написать простую регистрационную форму. Ее код будет следующим ("весь код":https://gist.github.com/dg/57878c1a413ae8ef0c1d83f02c43ef3f): ```php use Nette\Forms\Form; @@ -21,23 +21,23 @@ $form->addPassword('password', 'Пароль:'); $form->addSubmit('send', 'Зарегистрироваться'); ``` -И давайте сделаем рендеринг: +Мы можем очень легко ее отрисовать: ```php $form->render(); ``` -и результат должен выглядеть следующим образом: +и в браузере она отобразится так: -[* form-en.webp *] +[* form-cs.webp *] -Форма является объектом класса `Nette\Forms\Form` (класс `Nette\Application\UI\Form` используется в презентерах). Мы добавили в него имя элемента управления, пароль и кнопку отправки. +Форма — это объект класса `Nette\Forms\Form` (класс `Nette\Application\UI\Form` используется в презентерах). Мы добавили в нее так называемые элементы: имя, пароль и кнопку отправки. -Теперь мы оживим форму. Спросив `$form->isSuccess()`, мы узнаем, была ли форма отправлена и правильно ли она была заполнена. Если да, то мы сбросим данные. После определения формы добавим: +А теперь оживим форму. С помощью запроса `$form->isSuccess()` мы узнаем, была ли форма отправлена и была ли она заполнена валидно. Если да, то выведем данные. Дополним определение формы: ```php if ($form->isSuccess()) { - echo 'Форма была заполнена и отправлена правильно'; + echo 'Форма была правильно заполнена и отправлена'; $data = $form->getValues(); // $data->name содержит имя // $data->password содержит пароль @@ -45,32 +45,32 @@ if ($form->isSuccess()) { } ``` -Метод `getValues()` возвращает отправленные данные в виде объекта [ArrayHash |utils:arrays#ArrayHash]. Мы покажем, как это изменить [позже |#Mapping-to-Classes]. Переменная `$data` содержит ключи `name` и `password` с данными, введёнными пользователем. +Метод `getValues()` возвращает отправленные данные в виде объекта [ArrayHash |utils:arrays#ArrayHash]. Как это изменить, мы покажем [позже |#Маппинг на классы]. Объект `$data` содержит ключи `name` и `password` с данными, которые ввел пользователь. -Обычно мы отправляем данные непосредственно для дальнейшей обработки, которая может быть, например, вставкой в базу данных. Однако в процессе обработки может возникнуть ошибка, например, если имя пользователя уже занято. В этом случае мы передаем ошибку обратно в форму с помощью `addError()` и позволяем ей перерисовываться заново, с сообщением об ошибке: +Обычно мы сразу отправляем данные для дальнейшей обработки, например, для вставки в базу данных. Однако во время обработки может возникнуть ошибка, например, имя пользователя уже занято. В таком случае мы передаем ошибку обратно в форму с помощью `addError()` и позволяем ей отобразиться снова, уже с сообщением об ошибке. ```php -$form->addError('Извините, имя пользователя уже используется.'); +$form->addError('Извините, это имя пользователя уже используется.'); ``` -После обработки формы мы перенаправим вас на следующую страницу. Это предотвращает непреднамеренную повторную отправку формы при нажатии кнопки *обновить*, *назад* или перемещении по истории браузера. +После обработки формы мы перенаправляем на следующую страницу. Это предотвратит нежелательную повторную отправку формы кнопкой *обновить*, *назад* или перемещением по истории браузера. -По умолчанию форма отправляется методом POST на ту же страницу. И то, и другое можно изменить: +По умолчанию форма отправляется методом POST на ту же страницу. Оба параметра можно изменить: ```php $form->setAction('/submit.php'); $form->setMethod('GET'); ``` -И это всё :-) У нас есть функциональная и идеально [защищенная |#Защита от уязвимостей] форма. +И это, собственно, все :-) У нас есть рабочая и идеально [защищенная |#Защита от уязвимостей] форма. -Попробуйте добавить больше [элементов управления формой|controls]. +Попробуйте добавить и другие [элементы формы |controls]. -Доступ к элементам управления .[#toc-access-to-controls] -======================================================== +Доступ к элементам +================== -Форма и её отдельные элементы управления называются компонентами. Они создают дерево компонентов, корнем которого является форма. Доступ к отдельным элементам управления можно получить следующим образом: +Форму и ее отдельные элементы мы называем компонентами. Они образуют дерево компонентов, где корнем является сама форма. К отдельным элементам формы можно получить доступ следующим образом: ```php $input = $form->getComponent('name'); @@ -80,37 +80,36 @@ $button = $form->getComponent('send'); // альтернативный синтаксис: $button = $form['send']; ``` -Элементы управления удаляются с помощью функции unset: +Элементы удаляются с помощью `unset`: ```php unset($form['name']); ``` -Правила валидации .[#toc-validation-rules] -========================================== +Правила валидации +================= -Слово *valid* было использовано несколько раз, но форма ещё не имеет правил валидации. Давайте исправим это. +Здесь прозвучало слово *валидный*, но у формы пока нет никаких правил валидации. Давайте это исправим. -Имя будет обязательным, поэтому мы пометим его методом `setRequired()`, аргументом которого является текст сообщения об ошибке, которое будет выведено, если пользователь не заполнит его. Если аргумент не указан, используется сообщение об ошибке по умолчанию. +Имя будет обязательным, поэтому мы пометим его методом `setRequired()`, аргументом которого является текст сообщения об ошибке, которое отобразится, если пользователь не заполнит имя. Если аргумент не указан, будет использовано сообщение об ошибке по умолчанию. ```php $form->addText('name', 'Имя:') - ->setRequired('Пожалуйста, введите имя.'); + ->setRequired('Пожалуйста, введите имя'); ``` -Попробуйте отправить форму без заполненного имени, и вы увидите, что появится сообщение об ошибке, и браузер или сервер будет отклонять форму, пока вы не заполните её. +Попробуйте отправить форму без заполненного имени, и вы увидите, что отобразится сообщение об ошибке, и браузер или сервер будут отклонять ее до тех пор, пока вы не заполните поле. -В то же время вы не сможете обмануть систему, набрав в поле ввода, например, только пробелы. Ни за что. Nette автоматически обрезает левые и правые пробельные символы. Попробуйте. Это то, что вы всегда должны делать с каждым однострочным вводом, но об этом часто забывают. Nette делает это автоматически. (Вы можете попытаться обмануть форму и отправить многострочную строку в качестве имени. Даже здесь Nette не будет обманут, и переносы строк будут заменены на пробелы). +В то же время, вы не обманете систему, введя в поле, например, только пробелы. Ни в коем случае. Nette автоматически удаляет пробелы слева и справа. Попробуйте сами. Это то, что вы всегда должны делать с каждым однострочным вводом, но часто об этом забывают. Nette делает это автоматически. (Вы можете попробовать обмануть форму и отправить многострочную строку в качестве имени. И здесь Nette не даст себя обмануть и заменит переносы строк на пробелы.) -Форма всегда проверяется на стороне сервера, но также генерируется проверка JavaScript, что происходит быстро, и пользователь сразу же узнает об ошибке, без необходимости отправлять форму на сервер. Этим занимается скрипт `netteForms.js`. -Добавьте его на страницу: +Форма всегда валидируется на стороне сервера, но также генерируется JavaScript-валидация, которая выполняется мгновенно, и пользователь узнает об ошибке сразу, без необходимости отправлять форму на сервер. За это отвечает скрипт `netteForms.js`. Вставьте его на страницу: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Если вы посмотрите в исходный код страницы с формой, вы можете заметить, что Nette вставляет обязательные поля в элементы с CSS-классом `required`. Попробуйте добавить следующий стиль в шаблон, и метка "Имя" будет красного цвета. Мы элегантно помечаем обязательные поля для пользователей: +Если вы посмотрите исходный код страницы с формой, вы можете заметить, что Nette вставляет обязательные элементы в элементы с CSS-классом `required`. Попробуйте добавить в шаблон следующую таблицу стилей, и метка «Имя» станет красной. Таким образом, мы элегантно выделим обязательные элементы для пользователей: ```latte <style> @@ -118,96 +117,96 @@ $form->addText('name', 'Имя:') </style> ``` -Дополнительные правила валидации будут добавлены методом `addRule()`. Первым параметром является правило, вторым — текст сообщения об ошибке, далее может следовать необязательный аргумент правила проверки. Что это значит? +Другие правила валидации мы добавим методом `addRule()`. Первый параметр — это правило, второй — снова текст сообщения об ошибке, а затем может следовать аргумент правила валидации. Что под этим подразумевается? -Форма получит ещё один необязательный элемент ввода *age* с условием, что он должен быть числом (`addInteger()`) и находиться в определенных границах (`$form::Range`). И здесь мы будем использовать третий аргумент `addRule()`, сам диапазон: +Расширим форму новым необязательным полем «возраст», которое должно быть целым числом (`addInteger()`) и, кроме того, находиться в допустимом диапазоне (`$form::Range`). И здесь мы как раз используем третий параметр метода `addRule()`, которым передадим валидатору требуемый диапазон в виде пары `[от, до]`: ```php $form->addInteger('age', 'Возраст:') - ->addRule($form::Range, 'Вы должны быть старше 18 лет и иметь возраст до 120 лет.', [18, 120]); + ->addRule($form::Range, 'Возраст должен быть от 18 до 120', [18, 120]); ``` .[tip] -Если пользователь не заполнит поле, правила валидации не будут проверены, поскольку поле является необязательным. +Если пользователь не заполнит поле, правила валидации проверяться не будут, так как элемент необязательный. -Очевидно, что здесь есть место для небольшого рефакторинга. В сообщении об ошибке и в третьем параметре числа перечислены в двух экземплярах, что не идеально. Если бы мы создавали [многоязычную форму|rendering#translating] и сообщение, содержащее числа, пришлось бы переводить на несколько языков, это усложнило бы изменение значений. По этой причине можно использовать символы-заменители `%d`: +Здесь возникает возможность для небольшого рефакторинга. В сообщении об ошибке и в третьем параметре числа указаны дублировано, что не идеально. Если бы мы создавали [многоязычные формы |rendering#Перевод] и сообщение, содержащее числа, было бы переведено на несколько языков, это усложнило бы возможное изменение значений. По этой причине можно использовать заполнители (placeholders) `%d`, и Nette дополнит значения: ```php - ->addRule($form::Range, 'Вы должны быть старше %d лет и иметь возраст до %d лет.', [18, 120]); + ->addRule($form::Range, 'Возраст должен быть от %d до %d лет', [18, 120]); ``` -Вернемся к полю *пароль*, сделаем его *обязательным* и проверим минимальную длину пароля (`$form::MinLength`), снова используя символы-заменители в сообщении: +Вернемся к элементу `password`, который мы также сделаем обязательным и еще проверим минимальную длину пароля (`$form::MinLength`), снова используя заполнитель: ```php $form->addPassword('password', 'Пароль:') ->setRequired('Выберите пароль') - ->addRule($form::MinLength, 'Ваш пароль должен быть длиной не менее %d', 8); + ->addRule($form::MinLength, 'Пароль должен содержать не менее %d символов', 8); ``` -Мы добавим в форму поле `passwordVerify`, в котором пользователь вводит пароль ещё раз, для проверки. Используя правила валидации, мы проверяем, одинаковы ли оба пароля (`$form::Equal`). А в качестве аргумента мы даем ссылку на первый пароль, используя [квадратные скобки |#Access-to-Controls]: +Добавим в форму еще поле `passwordVerify`, где пользователь введет пароль еще раз, для проверки. С помощью правил валидации проверим, совпадают ли оба пароля (`$form::Equal`). А в качестве параметра дадим ссылку на первый пароль с помощью [квадратных скобок |#Доступ к элементам]: ```php -$form->addPassword('passwordVerify', 'Повторите пароль:') - ->setRequired('Введите пароль ещё раз, чтобы проверить опечатку') - ->addRule($form::Equal, 'Несоответствие пароля', $form['password']) +$form->addPassword('passwordVerify', 'Пароль для проверки:') + ->setRequired('Пожалуйста, введите пароль еще раз для проверки') + ->addRule($form::Equal, 'Пароли не совпадают', $form['password']) ->setOmitted(); ``` -Используя `setOmitted()`, мы пометили элемент, значение которого нас не особо волнует и который существует только для проверки. Его значение не передается в `$data`. +С помощью `setOmitted()` мы пометили элемент, значение которого нас на самом деле не интересует и который существует только для валидации. Значение не будет передано в `$data`. -У нас есть полнофункциональная форма с валидацией на PHP и JavaScript. Возможности валидации в Nette гораздо шире, вы можете создавать условия, отображать и скрывать части страницы в соответствии с ними и т. д. Вы можете узнать обо всем в главе [Валидация форм|validation]. +Таким образом, у нас готова полностью рабочая форма с валидацией на PHP и JavaScript. Возможности валидации Nette гораздо шире, можно создавать условия, отображать и скрывать части страницы в зависимости от них и т. д. Все это вы узнаете в главе о [валидации форм |validation]. -Значения по умолчанию .[#toc-default-values] -============================================ +Значения по умолчанию +===================== -Мы часто устанавливаем значения по умолчанию для элементов управления формы: +Элементам формы обычно устанавливают значения по умолчанию: ```php -$form->addEmail('email', 'Имейл') +$form->addEmail('email', 'E-mail') ->setDefaultValue($lastUsedEmail); ``` -Часто бывает полезно установить значения по умолчанию сразу для всех элементов управления. Например, когда форма используется для редактирования записей. Мы считываем запись из базы данных и устанавливаем её в качестве значения по умолчанию: +Часто бывает полезно установить значения по умолчанию для всех элементов одновременно. Например, когда форма используется для редактирования записей. Мы читаем запись из базы данных и устанавливаем значения по умолчанию: ```php //$row = ['name' => 'John', 'age' => '33', /* ... */]; $form->setDefaults($row); ``` -Вызовите `setDefaults()` после определения элементов управления. +Вызывайте `setDefaults()` после определения элементов. -Отображение формы .[#toc-rendering-the-form] -============================================ +Отрисовка формы +=============== -По умолчанию форма отображается в виде таблицы. Отдельные элементы управления следуют основным рекомендациям по обеспечению доступности веб-страниц. Все метки генерируются как элементы `<label>` и связаны со своими элементами, щелчок по метке перемещает курсор на соответствующий элемент. +По умолчанию форма отрисовывается в виде таблицы. Отдельные элементы соответствуют основному правилу доступности - все метки записаны как `<label>` и связаны с соответствующим элементом формы. При клике на метку курсор автоматически появляется в поле формы. -Мы можем установить любые атрибуты HTML для каждого элемента. Например, добавьте заполнитель: +Каждому элементу мы можем устанавливать любые HTML-атрибуты. Например, добавить плейсхолдер: ```php $form->addInteger('age', 'Возраст:') ->setHtmlAttribute('placeholder', 'Пожалуйста, заполните возраст'); ``` -На самом деле существует множество способов визуализации формы, подробнее в главе [Рендеринг|rendering]. +Способов отрисовки формы действительно очень много, поэтому этому посвящена [отдельная глава об отрисовке |rendering]. -Сопоставление с классами .[#toc-mapping-to-classes] -=================================================== +Маппинг на классы +================= -Давайте вернемся к обработке данных формы. Метод `getValues()` возвращает представленные данные в виде объекта `ArrayHash`. Поскольку это общий класс, что-то вроде `stdClass`, нам будет не хватать некоторых удобств при работе с ним, таких как завершение кода для свойств в редакторах или статический анализ кода. Эту проблему можно решить, создав для каждой формы отдельный класс, свойства которого представляют отдельные элементы управления. Например: +Вернемся к обработке данных формы. Метод `getValues()` возвращал нам отправленные данные как объект `ArrayHash`. Поскольку это обобщенный класс, что-то вроде `stdClass`, при работе с ним нам будет не хватать определенного комфорта, такого как автодополнение свойств в редакторах или статический анализ кода. Это можно было бы решить, создав для каждой формы конкретный класс, свойства которого представляют отдельные элементы. Например: ```php class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -Начиная с PHP 8.0, вы можете использовать эту элегантную нотацию, которая использует конструктор: +Альтернативно можно использовать конструктор: ```php class RegistrationFormData @@ -221,16 +220,18 @@ class RegistrationFormData } ``` -Как сказать Nette, чтобы он возвращал нам данные в виде объектов этого класса? Легче, чем вы думаете. Всё, что вам нужно сделать, это указать имя класса или объекта для гидратации в качестве параметра: +Свойства класса данных также могут быть перечислениями (enum), и они будут автоматически сопоставлены. .{data-version:3.2.4} + +Как сказать Nette, чтобы он возвращал нам данные в виде объектов этого класса? Проще, чем вы думаете. Достаточно указать имя класса или объект для гидратации в качестве параметра: ```php $data = $form->getValues(RegistrationFormData::class); $name = $data->name; ``` -В качестве параметра также можно указать `'array'`, и тогда данные возвращаются в виде массива. +В качестве параметра можно также указать `'array'`, и тогда данные будут возвращены в виде массива. -Если формы состоят из многоуровневой структуры, состоящей из контейнеров, создайте отдельный класс для каждого из них: +Если формы образуют многоуровневую структуру, состоящую из контейнеров, создайте для каждого отдельный класс: ```php $form = new Form; @@ -247,22 +248,24 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` -Из типа свойства `$person` отображение узнает, что оно должно отобразить контейнер на класс `PersonFormData`. Если свойство будет содержать массив контейнеров, укажите тип `array` и передайте класс, который должен быть отображен непосредственно на контейнер: +Маппинг затем по типу свойства `$person` поймет, что контейнер нужно сопоставить с классом `PersonFormData`. Если бы свойство содержало массив контейнеров, укажите тип `array` и передайте класс для маппинга непосредственно контейнеру: ```php $person->setMappedType(PersonFormData::class); ``` +Проект класса данных для формы можно сгенерировать с помощью метода `Nette\Forms\Blueprint::dataClass($form)`, который выведет его на страницу браузера. Затем достаточно кликнуть, чтобы выделить код, и скопировать его в проект. .{data-version:3.1.15} + -Несколько кнопок отправки .[#toc-multiple-submit-buttons] -========================================================= +Несколько кнопок +================ -Если форма содержит более одной кнопки, нам обычно нужно различать, какая из них была нажата. Метод `isSubmittedBy()` кнопки возвращает нам эту информацию: +Если у формы больше одной кнопки, нам обычно нужно различать, какая из них была нажата. Эту информацию нам вернет метод `isSubmittedBy()` кнопки: ```php $form->addSubmit('save', 'Сохранить'); @@ -279,37 +282,36 @@ if ($form->isSuccess()) { } ``` -Не опускайте `$form->isSuccess()` для проверки достоверности данных. +Не пропускайте запрос `$form->isSuccess()`, он проверит валидность данных. -Когда форма отправляется с помощью кнопки <kbd>Enter</kbd>, она обрабатывается так же, как если бы она была отправлена с помощью первой кнопки. +Когда форма отправляется нажатием клавиши <kbd>Enter</kbd>, это считается так, как если бы она была отправлена первой кнопкой. -Защита от уязвимостей .[#toc-vulnerability-protection] -====================================================== +Защита от уязвимостей +===================== -Nette Framework прилагает большие усилия для обеспечения безопасности, а поскольку формы являются наиболее распространенным видом пользовательского ввода, формы Nette настолько же хороши, насколько непроницаемы. +Nette Framework уделяет большое внимание безопасности и поэтому тщательно заботится о надежной защите форм. -В дополнение к защите форм от атак известных уязвимостей, таких как [Cross-Site Scripting (XSS)|nette:glossary#Cross-Site-Scripting-XSS] и [Cross-Site Request Forgery (CSRF)|nette:glossary#Cross-Site-Request-Forgery-CSRF], он выполняет множество мелких задач по обеспечению безопасности, о которых вам больше не нужно думать. +Помимо защиты форм от атак [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] и [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF], он выполняет множество мелких мер безопасности, о которых вам уже не нужно думать. -Например, он отфильтровывает все управляющие символы из вводимых данных и проверяет правильность кодировки UTF-8, так что данные из формы всегда будут чистыми. Для полей выбора и радиосписков проверяется, что выбранные элементы действительно были из предложенных и не было подделки. Мы уже упоминали, что для ввода однострочного текста он удаляет символы конца строки, которые злоумышленник может туда отправить. Для многострочных вводов он нормализует символы конца строки. И так далее. +Например, он отфильтровывает из входных данных все управляющие символы и проверяет валидность кодировки UTF-8, так что данные из формы всегда будут чистыми. У select box и radio list он проверяет, что выбранные элементы действительно были из предложенных и не произошло подделки. Мы уже упоминали, что у однострочных текстовых полей ввода он удаляет символы конца строки, которые мог отправить злоумышленник. У многострочных полей ввода он нормализует символы конца строки. И так далее. -Nette устраняет за вас уязвимости в системе безопасности, о существовании которых большинство программистов даже не подозревают. +Nette решает за вас риски безопасности, о существовании которых многие программисты даже не подозревают. -Упомянутая CSRF-атака заключается в том, что злоумышленник заманивает жертву посетить страницу, которая молча выполняет запрос в браузере жертвы к серверу, на котором жертва в данный момент зарегистрирована, и сервер считает, что запрос был сделан жертвой по собственному желанию. Таким образом, Nette предотвращает отправку формы через POST из другого домена. Если по какой-то причине вы хотите отключить защиту и разрешить отправку формы с другого домена, используйте: +Упомянутая CSRF-атака заключается в том, что злоумышленник заманивает жертву на страницу, которая незаметно в браузере жертвы выполняет запрос на сервер, на котором жертва авторизована, и сервер полагает, что запрос был выполнен жертвой по ее собственной воле. Поэтому Nette предотвращает отправку POST-формы с другого домена. Если по какой-то причине вы хотите отключить защиту и разрешить отправку формы с другого домена, используйте: ```php -$form->allowCrossOrigin(); // ВНИМАНИЕ! Выключает защиту! +$form->allowCrossOrigin(); // ВНИМАНИЕ! Отключает защиту! ``` -Эта защита использует файл куки SameSite с именем `_nss`. Поэтому создайте форму перед первым выводом, чтобы можно было отправить куки. +Эта защита использует SameSite cookie с именем `_nss`. Поэтому создавайте объект формы еще до отправки первого вывода, чтобы можно было отправить cookie. -Защита куки-файлов SameSite может быть не на 100% надежной, поэтому хорошей идеей будет включить защиту с помощью токена: +Защита с помощью SameSite cookie может быть не 100% надежной, поэтому рекомендуется включить еще защиту с помощью токена: ```php $form->addProtection(); ``` -Настоятельно рекомендуется применять эту защиту к формам в административной части вашего приложения, которые изменяют конфиденциальные данные. Фреймворк защищает от атаки CSRF, генерируя и проверяя токен аутентификации, который хранится в сессии (аргументом является сообщение об ошибке, показываемое, если срок действия токена истек). Поэтому перед отображением формы необходимо, чтобы сессия была запущена. В административной части сайта сессия, как правило, уже началась, поскольку пользователь вошел в систему. -В противном случае, запустите сессию с помощью метода `Nette\Http\Session::start()`. +Рекомендуем таким образом защищать формы в административной части сайта, которые изменяют чувствительные данные в приложении. Фреймворк защищается от CSRF-атаки путем генерации и проверки авторизационного токена, который сохраняется в сессии. Поэтому необходимо, чтобы перед отображением формы была открыта сессия. В административной части сайта сессия обычно уже запущена из-за входа пользователя. В противном случае запустите сессию методом `Nette\Http\Session::start()`. -Итак, мы кратко познакомились с формами в Nette. Попробуйте поискать вдохновение в каталоге [examples |https://github.com/nette/forms/tree/master/examples] в дистрибутиве. +Итак, мы рассмотрели быстрое введение в формы в Nette. Попробуйте еще заглянуть в каталог [examples |https://github.com/nette/forms/tree/master/examples] в дистрибутиве, где вы найдете дополнительное вдохновение. diff --git a/forms/ru/validation.texy b/forms/ru/validation.texy index b3f60e1a21..23ffa7de26 100644 --- a/forms/ru/validation.texy +++ b/forms/ru/validation.texy @@ -2,91 +2,102 @@ ************** -Обязательные для заполнения элементы .[#toc-required-controls] -============================================================== +Обязательные элементы +===================== -Элементы управления помечаются как обязательные с помощью метода `setRequired()`, аргументом которого является текст [сообщения об ошибке|#Сообщения об ошибке], отображаемый, если пользователь не заполнит его. Если аргумент не указан, используется сообщение об ошибке по умолчанию. +Обязательные элементы помечаются методом `setRequired()`, аргументом которого является текст [сообщения об ошибке |#Сообщения об ошибках], которое отобразится, если пользователь не заполнит элемент. Если аргумент не указан, будет использовано сообщение об ошибке по умолчанию. ```php $form->addText('name', 'Имя:') - ->setRequired('Пожалуйста, заполните ваше имя.'); + ->setRequired('Пожалуйста, введите имя'); ``` -Правила .[#toc-rules] -===================== +Правила +======= -Мы добавляем правила проверки к элементам управления с помощью метода `addRule()`. Первый параметр — это правило, второй — [сообщение об ошибке|#Сообщения об ошибке], а третий — аргумент правила проверки. +Правила валидации добавляются к элементам методом `addRule()`. Первый параметр — это правило, второй — текст [сообщения об ошибке |#Сообщения об ошибках], а третий — аргумент правила валидации. ```php $form->addPassword('password', 'Пароль:') - ->addRule($form::MinLength, 'Пароль должен состоять не менее чем из %d символов', 8); + ->addRule($form::MinLength, 'Пароль должен содержать не менее %d символов', 8); ``` -Nette поставляется с рядом встроенных правил, имена которых являются константами класса `Nette\Forms\Form`: +**Правила валидации проверяются только в том случае, если пользователь заполнил элемент.** -Мы можем использовать следующие правила для всех элементов управления: +Nette поставляется с целым рядом предопределенных правил, названия которых являются константами класса `Nette\Forms\Form`. Для всех элементов можно использовать следующие правила: -| константа | описание | аргументы +| константа | описание | тип аргумента |------- -| `Required` | псевдоним `setRequired()` | - -| `Filled` | псевдоним `setRequired()` | - -| `Blank` | не должно быть заполнены | - -| `Equal` | значение равно параметру | `mixed` -| `NotEqual` | значение не равно параметру | `mixed` -| `IsIn` | значение равно некоторому элементу массива | `array` -| `IsNotIn` | значение не равно ни одному элементу массива | `array` -| `Valid` | ввод проходит валидацию (для [#Условия]) | - - -Для элементов управления `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` также могут быть использованы следующие правила: - -| `MinLength` | минимальная длина строки | `int` -| `MaxLength` | максимальная длина строки | `int` +| `Required` | обязательный элемент, псевдоним для `setRequired()` | - +| `Filled` | обязательный элемент, псевдоним для `setRequired()` | - +| `Blank` | элемент не должен быть заполнен | - +| `Equal` | значение равно параметру | `mixed` +| `NotEqual` | значение не равно параметру | `mixed` +| `IsIn` | значение равно одному из элементов массива | `array` +| `IsNotIn` | значение не равно ни одному из элементов массива | `array` +| `Valid` | элемент заполнен правильно? (для [#Условия]) | - + + +Текстовые поля ввода +-------------------- + +Для элементов `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`, `addFloat()` можно также использовать некоторые из следующих правил: + +| `MinLength` | минимальная длина текста | `int` +| `MaxLength` | максимальная длина текста | `int` | `Length` | длина в диапазоне или точная длина | пара `[int, int]` или `int` -| `Email` | действительный адрес электронной почты | - -| `URL` | действительный URL | - -| `Pattern` | соответствует регулярному шаблону | `string` -| `PatternInsensitive` | как `Pattern`, но без учёта регистра. | `string` -| `Integer` | целое число | - -| `Numeric` | псевдоним `Integer` | - -| `Float` | целое число или число с плавающей точкой | - -| `Min` | минимум целочисленного значения | `int\|float` -| `Max` | максимум целочисленного значения | `int\|float` +| `Email` | валидный адрес электронной почты | - +| `URL` | абсолютный URL | - +| `Pattern` | соответствует регулярному выражению | `string` +| `PatternInsensitive` | как `Pattern`, но не зависит от регистра | `string` +| `Integer` | целочисленное значение | - +| `Numeric` | псевдоним для `Integer` | - +| `Float` | число | - +| `Min` | минимальное значение числового элемента | `int\|float` +| `Max` | максимальное значение числового элемента | `int\|float` | `Range` | значение в диапазоне | пара `[int\|float, int\|float]` -Правила `Integer`, `Numeric` и `Float` автоматически преобразуют значение в целое (или плавающее соответственно). Более того, правило `URL` также принимает адрес без схемы (например, `nette.org`) и дополняет схему (`https://nette.org`). -Выражения в `Pattern` и `PatternInsensitive` должны быть действительны для всего значения, т. е. как если бы оно было обернуто в символы `^` и `$`. - -Для элементов управления `addUpload()`, `addMultiUpload()` также могут быть использованы следующие правила: +Правила валидации `Integer`, `Numeric` и `Float` сразу преобразуют значение в integer или float соответственно. А правило `URL` также принимает адрес без схемы (например, `nette.org`) и дополняет схему (`https://nette.org`). Выражение в `Pattern` и `PatternIcase` должно соответствовать всему значению, т.е. как если бы оно было обернуто символами `^` и `$`. -| `MaxFileSize` | максимальный размер файла | `int` -| `MimeType` | Тип MIME, принимает подстановочные знаки (`'video/*'`) | `string\|string[]` -| `Image` | загруженный файл является JPEG, PNG, GIF, WebP | - -| `Pattern` | имя файла соответствует регулярному выражению | `string` -| `PatternInsensitive` | как `Pattern`, но без учета регистра. | `string` -Для `MimeType` и `Image` требуется расширение PHP `fileinfo`. Принадлежность файла или изображения к нужному типу определяется по его сигнатуре. Целостность всего файла не проверяется. Вы можете узнать, не повреждено ли изображение, например, попытавшись [загрузить его|http:request#toImage]. +Количество элементов +-------------------- -Для элементов управления `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()` следующие правила также могут быть использованы для ограничения количества выбранных элементов, соответственно загруженных файлов: +Для элементов `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()` можно также использовать следующие правила для ограничения количества выбранных элементов или загруженных файлов: | `MinLength` | минимальное количество | `int` | `MaxLength` | максимальное количество | `int` | `Length` | количество в диапазоне или точное количество | пара `[int, int]` или `int` -Сообщения об ошибках .[#toc-error-messages] -------------------------------------------- +Загрузка файлов +--------------- + +Для элементов `addUpload()`, `addMultiUpload()` можно также использовать следующие правила: + +| `MaxFileSize` | максимальный размер файла в байтах | `int` +| `MimeType` | MIME-тип, разрешены плейсхолдеры (`'video/*'`) | `string\|string[]` +| `Image` | изображение JPEG, PNG, GIF, WebP, AVIF | - +| `Pattern` | имя файла соответствует регулярному выражению | `string` +| `PatternInsensitive` | как `Pattern`, но не зависит от регистра | `string` + +`MimeType` и `Image` требуют PHP-расширения `fileinfo`. То, что файл или изображение имеет требуемый тип, определяется на основе его сигнатуры, и **не проверяется целостность всего файла.** Не повреждено ли изображение, можно узнать, например, попытавшись его [загрузить |http:request#toImage]. + + +Сообщения об ошибках +==================== -Все предопределенные правила, кроме `Pattern` и `PatternInsensitive`, имеют сообщение об ошибке по умолчанию, поэтому их можно опустить. Однако, передав и сформулировав все индивидуальные сообщения, вы сделаете форму более удобной для пользователя. +Все предопределенные правила, за исключением `Pattern` и `PatternInsensitive`, имеют сообщение об ошибке по умолчанию, поэтому его можно опустить. Однако, указав и сформулировав все сообщения индивидуально, вы сделаете форму более удобной для пользователя. -Вы можете изменить сообщения по умолчанию в [forms:configuration], изменяя тексты в массиве `Nette\Forms\Validator::$messages` или используя [translator|rendering#translating]. +Изменить сообщения по умолчанию можно в [конфигурации |forms:configuration], отредактировав тексты в массиве `Nette\Forms\Validator::$messages` или используя [переводчик |rendering#Перевод]. -В тексте сообщений об ошибках можно использовать следующие символы подстановки: +В тексте сообщений об ошибках можно использовать следующие заполнители (placeholders): -| `%d` | постепенно заменяет правила после аргументов -| `%n$d` | заменяется на n-й аргумент правила -| `%label` | заменяет на метку поля (без двоеточия) -| `%name` | заменяет имя поля (например, `name`) +| `%d` | заменяется последовательно аргументами правила +| `%n$d` | заменяется n-м аргументом правила +| `%label` | заменяется меткой (label) элемента (без двоеточия) +| `%name` | заменяется именем элемента (например, `name`) | `%value` | заменяется значением, введенным пользователем ```php @@ -101,68 +112,68 @@ $form->addInteger('id', 'ID:') ``` -Условия .[#toc-conditions] -========================== +Условия +======= -Помимо правил валидации, можно задать условия. Они устанавливаются так же, как и правила, но мы используем `addRule()` вместо `addCondition()` и, конечно, оставляем их без сообщения об ошибке (условие просто спрашивает): +Помимо правил, можно добавлять также условия. Они записываются аналогично правилам, только вместо `addRule()` используется метод `addCondition()`, и, разумеется, не указывается никакого сообщения об ошибке (условие только спрашивает): ```php $form->addPassword('password', 'Пароль:') - // если пароль не длиннее 8 символов ... + // если пароль не длиннее 8 символов ->addCondition($form::MaxLength, 8) - // ... тогда он должен содержать число - ->addRule($form::Pattern, 'Должен содержать номер', '.*[0-9].*'); + // то он должен содержать цифру + ->addRule($form::Pattern, 'Должен содержать цифру', '.*[0-9].*'); ``` -Условие может быть привязано к элементу, отличному от текущего, с помощью `addConditionOn()`. Первый параметр — это ссылка на поле. В следующем случае электронная почта потребуется только в том случае, если флажок установлен (т. е. его значение равно `true`): +Условие можно привязать и к другому элементу, отличному от текущего, с помощью `addConditionOn()`. В качестве первого параметра указывается ссылка на элемент. В этом примере e-mail будет обязательным только тогда, когда установлен флажок (его значение будет true): ```php -$form->addCheckbox('newsletters', 'отправлять мне информационные бюллетени'); +$form->addCheckbox('newsletters', 'присылайте мне рассылки'); -$form->addEmail('email', 'Имейл:') - // если флажок установлен ... +$form->addEmail('email', 'E-mail:') + // если флажок установлен ->addConditionOn($form['newsletters'], $form::Equal, true) - // ... требовать электронную почту - ->setRequired('Введите свой адрес электронной почты'); + // то требуй e-mail + ->setRequired('Введите адрес электронной почты'); ``` -Условия могут быть сгруппированы в сложные структуры с помощью методов `elseCondition()` и `endCondition()`. +Из условий можно создавать сложные структуры с помощью `elseCondition()` и `endCondition()`: ```php $form->addText(/* ... */) - ->addCondition(/* ... */) // если выполняется первое условие - ->addConditionOn(/* ... */) // и второе условие на другом элементе тоже - ->addRule(/* ... */) // требуют соблюдения этого правила - ->elseCondition() // если второе условие не выполняется - ->addRule(/* ... */) // требуют соблюдения этих правил + ->addCondition(/* ... */) // если выполнено первое условие + ->addConditionOn(/* ... */) // и второе условие на другом элементе + ->addRule(/* ... */) // требуй это правило + ->elseCondition() // если второе условие не выполнено + ->addRule(/* ... */) // требуй эти правила ->addRule(/* ... */) - ->endCondition() // мы возвращаемся к первому условию + ->endCondition() // возвращаемся к первому условию ->addRule(/* ... */); ``` -В Nette очень легко реагировать на выполнение или невыполнение условия на стороне JavaScript, используя метод `toggle()`, см. [#Динамический JavaScript]. +В Nette можно очень легко реагировать на выполнение или невыполнение условия и на стороне JavaScript с помощью метода `toggle()`, см. [#динамический JavaScript]. -Ссылки между элементами управления .[#toc-references-between-controls] -====================================================================== +Ссылка на другой элемент +======================== -Аргумент правила или условия может быть ссылкой на другой элемент. Например, вы можете динамически подтвердить, что `text` имеет столько символов, сколько указано в поле `length`: +В качестве аргумента правила или условия можно передать и другой элемент формы. Правило тогда будет использовать значение, введенное пользователем позже в браузере. Таким образом можно, например, динамически валидировать, что элемент `password` содержит ту же строку, что и элемент `password_confirm`: ```php -$form->addInteger('length'); -$form->addText('text') - ->addRule($form::Length, null, $form['length']); +$form->addPassword('password', 'Пароль'); +$form->addPassword('password_confirm', 'Подтвердите пароль') + ->addRule($form::Equal, 'Введенные пароли не совпадают', $form['password']); ``` -Пользовательские правила и условия .[#toc-custom-rules-and-conditions] -====================================================================== +Пользовательские правила и условия +================================== -Иногда мы сталкиваемся с ситуацией, когда встроенных правил валидации в Nette недостаточно, и нам нужно проверить данные от пользователя по-своему. В Nette это очень просто! +Иногда мы попадаем в ситуацию, когда встроенных правил валидации в Nette недостаточно, и нам нужно валидировать данные от пользователя по-своему. В Nette это очень просто! -Вы можете передать любой обратный вызов в качестве первого параметра в методы `addRule()` или `addCondition()`. Обратный вызов принимает сам элемент в качестве первого параметра и возвращает булево значение, указывающее на успешность проверки. При добавлении правила с помощью `addRule()` можно передать дополнительные аргументы, которые передаются в качестве второго параметра. +Методам `addRule()` или `addCondition()` можно в качестве первого параметра передать любой callback. Он принимает в качестве первого параметра сам элемент и возвращает булево значение, определяющее, прошла ли валидация успешно. При добавлении правила с помощью `addRule()` можно указать и другие аргументы, они затем передаются в качестве второго параметра. -Таким образом, пользовательский набор валидаторов может быть создан как класс со статическими методами: +Таким образом, мы можем создать собственный набор валидаторов как класс со статическими методами: ```php class MyValidators @@ -175,23 +186,23 @@ class MyValidators public static function validateEmailDomain(BaseControl $input, $domain) { - // дополнительные валидаторы + // другие валидаторы } } ``` -Далее использование очень простое: +Использование тогда очень простое: ```php $form->addInteger('num') ->addRule( [MyValidators::class, 'validateDivisibility'], - 'Значение должно быть кратно %d', + 'Значение должно быть кратно числу %d', 8, ); ``` -Пользовательские правила валидации также могут быть добавлены в JavaScript. Единственным требованием является то, что правило должно быть статическим методом. Его имя для валидатора JavaScript создается путем соединения имени класса без обратных косых черт `\`, подчеркивания `_` и имени метода. Например, запишите `App\MyValidators::validateDivisibility` как `AppMyValidators_validateDivisibility` и добавьте его в объект `Nette.validators`: +Пользовательские правила валидации можно добавлять и в JavaScript. Условием является то, что правило должно быть статическим методом. Его имя для JavaScript-валидатора формируется путем соединения имени класса без обратных слешей `\`, подчеркивания `_` и имени метода. Например, `App\MyValidators::validateDivisibility` запишем как `AppMyValidators_validateDivisibility` и добавим в объект `Nette.validators`: ```js Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => { @@ -200,12 +211,12 @@ Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => ``` -Событие onValidate .[#toc-event-onvalidate] -=========================================== +Событие onValidate +================== -После отправки формы проверка выполняется путем проверки отдельных правил, добавленных с помощью `addRule()`, и последующего вызова [события|nette:glossary#Events] `onValidate`. Его обработчик может быть использован для дополнительной проверки, обычно для проверки правильности комбинации значений в нескольких элементах формы. +После отправки формы выполняется валидация, во время которой проверяются отдельные правила, добавленные с помощью `addRule()`, и затем вызывается [событие |nette:glossary#События Events] `onValidate`. Его обработчик можно использовать для дополнительной валидации, обычно для проверки правильной комбинации значений в нескольких элементах формы. -Если обнаружена ошибка, она передается в форму с помощью метода `addError()`. Это может быть вызвано как на определенном элементе, так и непосредственно на форме. +Если обнаружена ошибка, мы передаем ее в форму методом `addError()`. Его можно вызывать либо на конкретном элементе, либо непосредственно на форме. ```php protected function createComponentSignInForm(): Form @@ -225,10 +236,10 @@ public function validateSignInForm(Form $form, \stdClass $data): void ``` -Ошибки обработки .[#toc-processing-errors] -========================================== +Ошибки при обработке +==================== -Во многих случаях мы обнаруживаем ошибку, когда обрабатываем действительную форму, например, когда мы записываем новую запись в базу данных и сталкиваемся с дублирующимся ключом. В этом случае мы передаем ошибку обратно в форму с помощью метода `addError()`. Это может быть вызвано как на определенном элементе, так и непосредственно на форме: +Во многих случаях об ошибке мы узнаем только в момент обработки валидной формы, например, при записи новой записи в базу данных и столкновении с дубликатом ключей. В таком случае ошибку снова передаем в форму методом `addError()`. Его можно вызывать либо на конкретном элементе, либо непосредственно на форме: ```php try { @@ -243,67 +254,66 @@ try { } ``` -Если возможно, мы рекомендуем добавить ошибку непосредственно в элемент формы, так как она будет отображаться рядом с ним при использовании рендеринга по умолчанию. +Если это возможно, рекомендуем прикреплять ошибку непосредственно к элементу формы, так как она будет отображаться рядом с ним при использовании рендерера по умолчанию. ```php -$form['date']->addError('Извините, эта дата уже занята.'); +$form['date']->addError('Извините, но эта дата уже занята.'); ``` -Вы можете вызывать `addError()` несколько раз, чтобы передать несколько сообщений об ошибках форме или элементу. Вы получаете их с помощью функции `getErrors()`. +Вы можете вызывать `addError()` повторно и таким образом передать форме или элементу несколько сообщений об ошибках. Их можно получить с помощью `getErrors()`. -Обратите внимание, что `$form->getErrors()` возвращает сводку всех сообщений об ошибках, даже тех, которые были переданы непосредственно отдельным элементам, а не только непосредственно форме. Сообщения об ошибках, переданные только форме, извлекаются через `$form->getOwnErrors()`. +Внимание, `$form->getErrors()` возвращает сводку всех сообщений об ошибках, включая те, что были переданы непосредственно отдельным элементам, а не только самой форме. Сообщения об ошибках, переданные только форме, можно получить через `$form->getOwnErrors()`. -Изменение входных значений .[#toc-modifying-input-values] -========================================================= +Изменение ввода +=============== -Используя метод `addFilter()`, мы можем изменить значение, введенное пользователем. В этом примере мы будем допускать и удалять пробелы в почтовом индексе: +С помощью метода `addFilter()` мы можем изменить значение, введенное пользователем. В этом примере мы будем допускать и удалять пробелы в почтовом индексе: ```php $form->addText('zip', 'Почтовый индекс:') ->addFilter(function ($value) { - return str_replace(' ', '', $value); // удалить пробелы из почтового индекса + return str_replace(' ', '', $value); // удалим пробелы из почтового индекса }) - ->addRule($form::Pattern, 'Почтовый индекс не состоит из пяти цифр', '\d{5}'); + ->addRule($form::Pattern, 'Почтовый индекс не в формате пяти цифр', '\d{5}'); ``` -Фильтр включен между правилами проверки и условиями и поэтому зависит от порядка следования методов, то есть фильтр и правило вызываются в том же порядке, что и порядок следования методов `addFilter()` и `addRule()`. +Фильтр встраивается между правилами валидации и условиями, поэтому порядок методов имеет значение, т.е. фильтр и правило вызываются в том порядке, в каком указаны методы `addFilter()` и `addRule()`. -Валидация JavaScript .[#toc-javascript-validation] -================================================== +JavaScript-валидация +==================== -Язык правил и условий проверки является мощным. Несмотря на то, что все конструкции работают как на стороне сервера, так и на стороне клиента, в JavaScript. Правила передаются в HTML-атрибутах `data-nette-rules` в виде JSON. -Сама валидация обрабатывается другим скриптом, который перехватывает все события формы `submit`, перебирает все вводимые данные и запускает соответствующие валидации. +Язык для формулирования условий и правил очень мощный. Все конструкции при этом работают как на стороне сервера, так и на стороне JavaScript. Они передаются в HTML-атрибутах `data-nette-rules` в формате JSON. Саму валидацию затем выполняет скрипт, который перехватывает событие формы `submit`, проходит по отдельным элементам и выполняет соответствующую валидацию. -Этот скрипт — `netteForms.js`, который доступен из нескольких возможных источников: +Этим скриптом является `netteForms.js`, и он доступен из нескольких возможных источников: -Вы можете встроить сценарий непосредственно в HTML-страницу из CDN: +Скрипт можно вставить непосредственно в HTML-страницу из CDN: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Или скопируйте локально в общую папку проекта (например, с сайта `vendor/nette/forms/src/assets/netteForms.min.js`): +Или скопировать локально в публичную папку проекта (например, из `vendor/nette/forms/src/assets/netteForms.min.js`): ```latte <script src="/path/to/netteForms.min.js"></script> ``` -Или установите через [npm|https://www.npmjs.com/package/nette-forms]: +Или установить через [npm |https://www.npmjs.com/package/nette-forms]: ```shell npm install nette-forms ``` -А затем загрузите и запустите: +А затем загрузить и запустить: ```js import netteForms from 'nette-forms'; netteForms.initOnLoad(); ``` -Кроме того, вы можете загрузить его непосредственно из папки `vendor`: +Альтернативно его можно загрузить прямо из папки `vendor`: ```js import netteForms from '../path/to/vendor/nette/forms/src/assets/netteForms.js'; @@ -311,10 +321,10 @@ netteForms.initOnLoad(); ``` -Динамический JavaScript .[#toc-dynamic-javascript] -================================================== +Динамический JavaScript +======================= -Вы хотите отображать поля адреса только в том случае, если пользователь решит отправить товар по почте? Нет проблем. Ключом является пара методов `addCondition()` и `toggle()`: +Хотите отображать поля для ввода адреса только если пользователь выберет доставку товара почтой? Нет проблем. Ключ — это пара методов `addCondition()` & `toggle()`: ```php $form->addCheckbox('send_it') @@ -322,25 +332,25 @@ $form->addCheckbox('send_it') ->toggle('#address-container'); ``` -Этот код говорит, что при выполнении условия, то есть при установке флажка, HTML-элемент `#address-container` станет видимым. И наоборот. Итак, мы помещаем элементы формы с адресом получателя в контейнер с этим ID, и при нажатии на флажок они скрываются или показываются. Этим занимается скрипт `netteForms.js`. +Этот код говорит, что когда условие выполнено, то есть когда флажок установлен, будет виден HTML-элемент `#address-container`. И наоборот. Элементы формы с адресом получателя мы разместим в контейнере с этим ID, и при клике на флажок они будут скрываться или отображаться. Это обеспечивает скрипт `netteForms.js`. -Любой селектор может быть передан в качестве аргумента методу `toggle()`. По историческим причинам буквенно-цифровая строка без других специальных символов рассматривается как идентификатор элемента, так же как если бы ей предшествовал символ `#`. Второй необязательный параметр позволяет нам изменить поведение, т. е. если бы мы использовали `toggle('#address-container', false)`, элемент отображался бы только при снятом флажке. +В качестве аргумента метода `toggle()` можно передать любой селектор. По историческим причинам буквенно-цифровая строка без других специальных символов понимается как ID элемента, то есть так же, как если бы ей предшествовал символ `#`. Второй необязательный параметр позволяет инвертировать поведение, т.е. если бы мы использовали `toggle('#address-container', false)`, элемент, наоборот, отображался бы только тогда, когда флажок не был бы установлен. -Реализация JavaScript по умолчанию изменяет свойство `hidden` для элементов. Однако мы можем легко изменить поведение, например, добавив анимацию. Просто переопределите метод `Nette.toggle` в JavaScript с помощью собственного решения: +Реализация по умолчанию в JavaScript изменяет свойство `hidden` элементов. Однако поведение можно легко изменить, например, добавить анимацию. Достаточно в JavaScript переопределить метод `Nette.toggle` собственным решением: ```js Nette.toggle = (selector, visible, srcElement, event) => { document.querySelectorAll(selector).forEach((el) => { - // скрыть или показать 'el' в зависимости от значения 'visible' + // скроем или покажем 'el' в зависимости от значения 'visible' }); }; ``` -Отключение валидации .[#toc-disabling-validation] -================================================= +Отключение валидации +==================== -В некоторых случаях необходимо отключить валидацию. Если кнопка submit не должна выполнять проверку после отправки (например, кнопка *Отмена* или *Предварительный просмотр*), вы можете отключить проверку, вызвав `$submit->setValidationScope([])`. Вы также можете проверить форму частично, указав элементы для проверки. +Иногда может потребоваться отключить валидацию. Если нажатие кнопки отправки не должно выполнять валидацию (подходит для кнопок *Cancel* или *Preview*), мы отключаем ее методом `$submit->setValidationScope([])`. Если она должна выполнять только частичную валидацию, мы можем указать, какие поля или контейнеры формы должны валидироваться. ```php $form->addText('name') @@ -352,15 +362,15 @@ $details->addInteger('age') $details->addInteger('age2') ->setRequired('age2'); -$form->addSubmit('send1'); // Проверяет всю форму +$form->addSubmit('send1'); // Валидирует всю форму $form->addSubmit('send2') - ->setValidationScope([]); // Ничего не подтверждает + ->setValidationScope([]); // Не валидирует вообще $form->addSubmit('send3') - ->setValidationScope([$form['name']]); // Проверяет только поле 'имя' + ->setValidationScope([$form['name']]); // Валидирует только элемент name $form->addSubmit('send4') - ->setValidationScope([$form['details']['age']]); // Проверяется только поле 'возраст' + ->setValidationScope([$form['details']['age']]); // Валидирует только элемент age $form->addSubmit('send5') - ->setValidationScope([$form['details']]); // Проверяет контейнер 'details' + ->setValidationScope([$form['details']]); // Валидирует контейнер details ``` -[#Событие onValidate] на форме вызывается всегда и не зависит от `setValidationScope`. Событие `onValidate` на контейнере вызывается только тогда, когда этот контейнер указан для частичной валидации. +`setValidationScope` не влияет на [#событие onValidate] у формы, которое будет вызвано всегда. Событие `onValidate` у контейнера будет вызвано только если этот контейнер помечен для частичной валидации. diff --git a/forms/sl/@home.texy b/forms/sl/@home.texy index d48e378f01..b71b6a213f 100644 --- a/forms/sl/@home.texy +++ b/forms/sl/@home.texy @@ -1,31 +1,31 @@ -Obrazci -******* +Nette Forms +*********** <div class=perex> -Nette Forms je revolucionarno spremenil ustvarjanje spletnih obrazcev. Vse, kar ste morali storiti, je bilo napisati nekaj jasnih vrstic kode in že ste imeli obrazec, vključno z upodabljanjem, JavaScriptom in potrjevanjem strežnika ter vrhunsko varnostjo. Oglejmo si, kako +Nette Forms so prinesli revolucijo v ustvarjanje spletnih obrazcev. Naenkrat je bilo dovolj napisati nekaj razumljivih vrstic kode in imeli ste pripravljen obrazec, vključno z izrisovanjem, JavaScript in strežniško validacijo ter poleg tega vrhunsko zaščiten. Pokazali bomo, kako -- ustvarite prijazne obrazce -- potrjevanje poslanih podatkov -- izrisati elemente točno tako, kot je potrebno +- ustvarjati prijazne obrazce +- validirati poslane podatke +- izrisovati elemente natančno po potrebi </div> -Z obrazci Nette Forms lahko zmanjšate rutinska opravila, kot je pisanje potrditev (na strani strežnika in odjemalca), ter zmanjšate verjetnost napak in varnostnih težav. +Z uporabo Nette Forms se izognete celi vrsti rutinskih nalog, kot je na primer pisanje validacije (poleg tega dvojne, na strani strežnika in odjemalca), minimizirate verjetnost nastanka napak in varnostnih lukenj. -Obrazce lahko uporabljate kot del aplikacije Nette (npr. v predstavitvah) ali samostojno. Ker se uporaba v obeh primerih nekoliko razlikuje, smo za vas pripravili ločena navodila: +Obrazce lahko uporabljate bodisi kot del Nette Aplikacije (torej v presenterjih), bodisi popolnoma samostojno. Ker se v obeh primerih uporaba nekoliko razlikuje, smo za vas pripravili dva navodila: <div class="wiki-buttons"> -<div> "Obrazci v predstavitvah .[wiki-button]":in-presenter </div> -<div> "Samostojni obrazci .[wiki-button]":standalone </div> +<div> "Obrazci v presenterjih .[wiki-button]":in-presenter </div> +<div> "Obrazci samostojno .[wiki-button]":standalone </div> </div> Namestitev ---------- -Prenesite in namestite paket s [programom Composer |best-practices:composer]: +Knjižnico prenesete in namestite z orodjem [Composer|best-practices:composer]: ```shell composer require nette/forms diff --git a/forms/sl/@left-menu.texy b/forms/sl/@left-menu.texy index 4e5426267f..10d9d69543 100644 --- a/forms/sl/@left-menu.texy +++ b/forms/sl/@left-menu.texy @@ -1,14 +1,14 @@ -Obrazci -******* -- [Pregled |@home] -- [Obrazci v programu Presenters |in-presenter] -- [Samostojni obrazci |standalone] -- [Krmilniki obrazcev |controls] -- [Preverjanje veljavnosti |validation] +Nette Forms +*********** +- [Uvod |@home] +- [Obrazci v presenterjih|in-presenter] +- [Obrazci samostojno|standalone] +- [Elementi obrazca |controls] +- [Validacija |validation] - [Izrisovanje |rendering] -- [Konfiguracija |Configuration] +- [Konfiguracija |configuration] Nadaljnje branje **************** -- [Najboljše prakse |best-practices:] +- [Navodila in postopki |best-practices:] diff --git a/forms/sl/@meta.texy b/forms/sl/@meta.texy new file mode 100644 index 0000000000..724324bee5 --- /dev/null +++ b/forms/sl/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokumentacija}} diff --git a/forms/sl/configuration.texy b/forms/sl/configuration.texy index af548a3052..6ca404d63c 100644 --- a/forms/sl/configuration.texy +++ b/forms/sl/configuration.texy @@ -1,8 +1,8 @@ -Konfiguriranje obrazcev -*********************** +Konfiguracija obrazcev +********************** .[perex] -V konfiguraciji lahko spremenite privzeta [sporočila o napakah obrazca |validation]. +V konfiguraciji lahko spremenite privzeta [sporočila o napakah obrazcev|validation]. ```neon forms: @@ -30,4 +30,32 @@ forms: Nette\Forms\Controls\CsrfProtection::Protection: 'Your session has expired. Please return to the home page and try again.' ``` -Če ne uporabljate celotnega ogrodja in torej niti konfiguracijskih datotek, lahko privzeta sporočila o napakah spremenite neposredno v polju `Nette\Forms\Validator::$messages`. +Tukaj je slovenski prevod: + +```neon +forms: + messages: + Equal: 'Vnesite %s.' + NotEqual: 'Ta vrednost ne sme biti %s.' + Filled: 'To polje je obvezno.' + Blank: 'To polje mora biti prazno.' + MinLength: 'Vnesite vsaj %d znakov.' + MaxLength: 'Vnesite največ %d znakov.' + Length: 'Vnesite vrednost dolžine med %d in %d znakov.' + Email: 'Vnesite veljaven e-poštni naslov.' + URL: 'Vnesite veljaven URL.' + Integer: 'Vnesite veljavno celo število.' + Float: 'Vnesite veljavno število.' + Min: 'Vnesite vrednost večjo ali enako %d.' + Max: 'Vnesite vrednost manjšo ali enako %d.' + Range: 'Vnesite vrednost med %d in %d.' + MaxFileSize: 'Velikost naložene datoteke je lahko največ %d bajtov.' + MaxPostSize: 'Naloženi podatki presegajo omejitev %d bajtov.' + MimeType: 'Naložena datoteka ni v pričakovanem formatu.' + Image: 'Naložena datoteka mora biti slika v formatu JPEG, GIF, PNG, WebP ali AVIF.' + Nette\Forms\Controls\SelectBox::Valid: 'Izberite veljavno možnost.' + Nette\Forms\Controls\UploadControl::Valid: 'Pri nalaganju datoteke je prišlo do napake.' + Nette\Forms\Controls\CsrfProtection::Protection: 'Vaša seja je potekla. Vrnite se na domačo stran in poskusite znova.' +``` + +Če ne uporabljate celotnega ogrodja in torej niti konfiguracijskih datotek, lahko spremenite privzeta sporočila o napakah neposredno v polju `Nette\Forms\Validator::$messages`. diff --git a/forms/sl/controls.texy b/forms/sl/controls.texy index 4caa6b7f5c..b03b9e0978 100644 --- a/forms/sl/controls.texy +++ b/forms/sl/controls.texy @@ -1,253 +1,346 @@ -Krmilniki obrazca -***************** +Elementi obrazca +**************** .[perex] -Pregled vgrajenih kontrolnih elementov obrazca. +Pregled standardnih elementov obrazca. -addText(string|int $name, $label=null): TextInput .[method] -=========================================================== +addText(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +================================================================================================== -Doda enovrstično besedilno polje (razred [TextInput |api:Nette\Forms\Controls\TextInput]). Če uporabnik ne izpolni polja, vrne prazen niz `''`, ali pa ga z uporabo `setNullable()` spremeni tako, da vrne `null`. +Doda enovrstično besedilno polje (razred [TextInput |api:Nette\Forms\Controls\TextInput]). Če uporabnik polja ne izpolni, vrne prazen niz `''`, ali pa s pomočjo `setNullable()` lahko določite, da vrne `null`. ```php -$form->addText('name', 'Name:') +$form->addText('name', 'Ime:') ->setRequired() ->setNullable(); ``` -Samodejno potrdi UTF-8, obreže leve in desne bele prostore ter odstrani prelome vrstic, ki bi jih lahko poslal napadalec. +Samodejno validira UTF-8, obreže leve in desne presledke ter odstrani prelome vrstic, ki bi jih lahko poslal napadalec. -Največjo dolžino lahko omejite z uporabo `setMaxLength()`. S funkcijo [addFilter() |validation#Modifying Input Values] lahko spremenite uporabniško vneseno vrednost. +Maksimalno dolžino lahko omejite s pomočjo `setMaxLength()`. Spreminjanje vrednosti, ki jo vnese uporabnik, omogoča [addFilter() |validation#Spreminjanje vnosa]. -Z uporabo `setHtmlType()` spremenite [znak |https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types] vhodnega elementa v `search`, `tel`, `url`, `range`, `date`, `datetime-local`, `month`, `time`, `week`, `color`. Namesto tipov `number` in `email` priporočamo uporabo tipov [addInteger |#addInteger] in [addEmail |#addEmail], ki zagotavljata preverjanje na strani strežnika. +S pomočjo `setHtmlType()` lahko spremenite vizualni značaj besedilnega polja na tipe, kot so `search`, `tel` ali `url`, glej [specifikacijo|https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types]. Ne pozabite, da je sprememba tipa le vizualna in ne nadomešča funkcije validacije. Za tip `url` je priporočljivo dodati specifično validacijsko [pravilo URL |validation#Besedilni vnosi]. -```php -$form->addText('color', 'Choose color:') - ->setHtmlType('color') - ->addRule($form::Pattern, 'invalid value', '[0-9a-f]{6}'); -``` +.[note] +Za druge tipe vnosov, kot so `number`, `range`, `email`, `date`, `datetime-local`, `time` in `color`, uporabite specializirane metode, kot so [#addInteger], [#addFloat], [#addEmail] [#addDate], [#addTime], [#addDateTime] in [#addColor], ki zagotavljajo strežniško validacijo. Tipi `month` in `week` še niso popolnoma podprti v vseh brskalnikih. -Za element lahko nastavite tako imenovano prazno vrednost, ki je nekaj podobnega kot privzeta vrednost, vendar če je uporabnik ne prepiše, vrne prazen niz ali `null`. +Elementu lahko nastavite t.i. empty-value, kar je nekaj podobnega privzeti vrednosti, a če je uporabnik ne spremeni, element vrne prazen niz ali `null`. ```php -$form->addText('phone', 'Phone:') +$form->addText('phone', 'Telefon:') ->setHtmlType('tel') - ->setEmptyValue('+420'); + ->setEmptyValue('+386'); ``` addTextArea(string|int $name, $label=null): TextArea .[method] ============================================================== -Doda večvrstično besedilno polje (razred [TextArea |api:Nette\Forms\Controls\TextArea]). Če uporabnik ne izpolni polja, vrne prazen niz `''`, ali pa ga z uporabo `setNullable()` spremeni tako, da vrne `null`. +Doda polje za vnos večvrstičnega besedila (razred [TextArea |api:Nette\Forms\Controls\TextArea]). Če uporabnik polja ne izpolni, vrne prazen niz `''`, ali pa s pomočjo `setNullable()` lahko določite, da vrne `null`. ```php -$form->addTextArea('note', 'Note:') - ->addRule($form::MaxLength, 'Your note is way too long', 10000); +$form->addTextArea('note', 'Opomba:') + ->addRule($form::MaxLength, 'Opomba je predolga', 10000); ``` -Samodejno preveri UTF-8 in normalizira prelome vrstic na `\n`. Za razliko od enovrstičnega vnosnega polja ne obrezuje belih pik. +Samodejno validira UTF-8 in normalizira ločila vrstic na `\n`. V nasprotju z enovrstičnim vnosnim poljem ne pride do obrezovanja presledkov. -Največjo dolžino lahko omejite z uporabo `setMaxLength()`. Funkcija [addFilter() |validation#Modifying Input Values] omogoča spreminjanje uporabniško vnesene vrednosti. Tako imenovano prazno vrednost lahko nastavite z uporabo `setEmptyValue()`. +Maksimalno dolžino lahko omejite s pomočjo `setMaxLength()`. Spreminjanje vrednosti, ki jo vnese uporabnik, omogoča [addFilter() |validation#Spreminjanje vnosa]. Lahko nastavite t.i. empty-value s pomočjo `setEmptyValue()`. addInteger(string|int $name, $label=null): TextInput .[method] ============================================================== -Doda vnosno polje za celo število (razred [TextInput |api:Nette\Forms\Controls\TextInput]). Vrne celo število ali `null`, če uporabnik ne vnese ničesar. +Doda polje za vnos celega števila (razred [TextInput |api:Nette\Forms\Controls\TextInput]). Vrne bodisi integer ali `null`, če uporabnik ničesar ne vnese. + +```php +$form->addInteger('year', 'Leto:') + ->addRule($form::Range, 'Leto mora biti v obsegu od %d do %d.', [1900, 2023]); +``` + +Element se izriše kot `<input type="number">`. Z uporabo metode `setHtmlType()` lahko spremenite tip na `range` za prikaz v obliki drsnika ali na `text`, če preferirate standardno besedilno polje brez posebnega obnašanja tipa `number`. + + +addFloat(string|int $name, $label=null): TextInput .[method]{data-version:3.1.12} +================================================================================= + +Doda polje za vnos decimalnega števila (razred [TextInput |api:Nette\Forms\Controls\TextInput]). Vrne bodisi float ali `null`, če uporabnik ničesar ne vnese. ```php -$form->addInteger('level', 'Level:') +$form->addFloat('level', 'Raven:') ->setDefaultValue(0) - ->addRule($form::Range, 'Level must be between %d and %d.', [0, 100]); + ->addRule($form::Range, 'Raven mora biti v obsegu od %d do %d.', [0, 100]); ``` +Element se izriše kot `<input type="number">`. Z uporabo metode `setHtmlType()` lahko spremenite tip na `range` za prikaz v obliki drsnika ali na `text`, če preferirate standardno besedilno polje brez posebnega obnašanja tipa `number`. + +Nette in brskalnik Chrome sprejemata kot ločilo decimalnih mest tako vejico kot piko. Da bi bila ta funkcionalnost na voljo tudi v Firefoxu, je priporočljivo nastaviti atribut `lang` bodisi za dani element ali za celotno stran, na primer `<html lang="sl">`. + -addEmail(string|int $name, $label=null): TextInput .[method] -============================================================ +addEmail(string|int $name, $label=null, int $maxLength=255): TextInput .[method] +================================================================================ -Doda polje e-poštnega naslova s preverjanjem veljavnosti (razred [TextInput |api:Nette\Forms\Controls\TextInput]). Če uporabnik ne izpolni polja, vrne prazen niz `''`, ali pa ga z uporabo `setNullable()` spremeni tako, da vrne `null`. +Doda polje za vnos e-poštnega naslova (razred [TextInput |api:Nette\Forms\Controls\TextInput]). Če uporabnik polja ne izpolni, vrne prazen niz `''`, ali pa s pomočjo `setNullable()` lahko določite, da vrne `null`. ```php -$form->addEmail('email', 'Email:'); +$form->addEmail('email', 'E-pošta:'); ``` -Preveri, ali je vrednost veljavni e-poštni naslov. Ne preveri, ali domena dejansko obstaja, preveri se le sintaksa. Samodejno preveri UTF-8, obreže leve in desne bele prostore. +Preveri, ali je vrednost veljaven e-poštni naslov. Ne preverja se, ali domena dejansko obstaja, preverja se le sintaksa. Samodejno validira UTF-8, obreže leve in desne presledke. -Največjo dolžino lahko omejite z uporabo `setMaxLength()`. S funkcijo [addFilter() |validation#Modifying Input Values] lahko spremenite uporabniško vneseno vrednost. Tako imenovano prazno vrednost lahko nastavite z uporabo `setEmptyValue()`. +Maksimalno dolžino lahko omejite s pomočjo `setMaxLength()`. Spreminjanje vrednosti, ki jo vnese uporabnik, omogoča [addFilter() |validation#Spreminjanje vnosa]. Lahko nastavite t.i. empty-value s pomočjo `setEmptyValue()`. -addPassword(string|int $name, $label=null): TextInput .[method] -=============================================================== +addPassword(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +====================================================================================================== -Doda polje za geslo (razred [TextInput |api:Nette\Forms\Controls\TextInput]). +Doda polje za vnos gesla (razred [TextInput |api:Nette\Forms\Controls\TextInput]). ```php -$form->addPassword('password', 'Password:') +$form->addPassword('password', 'Geslo:') ->setRequired() - ->addRule($form::MinLength, 'Password has to be at least %d characters long', 8) - ->addRule($form::Pattern, 'Password must contain a number', '.*[0-9].*'); + ->addRule($form::MinLength, 'Geslo mora imeti vsaj %d znakov', 8) + ->addRule($form::Pattern, 'Mora vsebovati števko', '.*[0-9].*'); ``` -Ob ponovnem pošiljanju obrazca bo vnos prazno polje. Samodejno potrdi UTF-8, obreže leve in desne bele prostore ter odstrani prelome vrstic, ki bi jih lahko poslal napadalec. +Pri ponovnem prikazu obrazca bo polje prazno. Samodejno validira UTF-8, obreže leve in desne presledke ter odstrani prelome vrstic, ki bi jih lahko poslal napadalec. addCheckbox(string|int $name, $caption=null): Checkbox .[method] ================================================================ -Doda potrditveno polje (razred [Checkbox |api:Nette\Forms\Controls\Checkbox]). Polje vrne `true` ali `false`, odvisno od tega, ali je označeno. +Doda potrditveno polje (razred [Checkbox |api:Nette\Forms\Controls\Checkbox]). Vrne vrednost bodisi `true` ali `false`, glede na to, ali je označeno. ```php -$form->addCheckbox('agree', 'I agree with terms') - ->setRequired('You must agree with our terms'); +$form->addCheckbox('agree', 'Strinjam se s pogoji') + ->setRequired('Potrebno je strinjanje s pogoji'); ``` -addCheckboxList(string|int $name, $label=null, array $items=null): CheckboxList .[method] -========================================================================================= +addCheckboxList(string|int $name, $label=null, ?array $items=null): CheckboxList .[method] +========================================================================================== -Doda seznam potrditvenih polj za izbiro več elementov (razred [CheckboxList |api:Nette\Forms\Controls\CheckboxList]). Vrne polje ključev izbranih elementov. Metoda `getSelectedItems()` namesto ključev vrne vrednosti. +Doda potrditvena polja za izbiro več postavk (razred [CheckboxList |api:Nette\Forms\Controls\CheckboxList]). Vrne polje ključev izbranih postavk. Metoda `getSelectedItems()` vrne vrednosti namesto ključev. ```php -$form->addCheckboxList('colors', 'Colors:', [ - 'r' => 'red', - 'g' => 'green', - 'b' => 'blue', +$form->addCheckboxList('colors', 'Barve:', [ + 'r' => 'rdeča', + 'g' => 'zelena', + 'b' => 'modra', ]); ``` -Polje elementov posredujemo kot tretji parameter ali z metodo `setItems()`. +Polje ponujenih postavk predamo kot tretji parameter ali z metodo `setItems()`. -Uporabite lahko `setDisabled(['r', 'g'])` za onemogočanje posameznih elementov. +S pomočjo `setDisabled(['r', 'g'])` lahko deaktivirate posamezne postavke. -Element samodejno preveri, da ni prišlo do ponarejanja in da so izbrani elementi dejansko eni od ponujenih ter da niso bili onemogočeni. Z metodo `getRawValue()` lahko pridobite predložene elemente brez tega pomembnega preverjanja. +Element samodejno preverja, da ni prišlo do ponarejanja in da so izbrane postavke dejansko ene izmed ponujenih in niso bile deaktivirane. Z metodo `getRawValue()` lahko pridobite poslane postavke brez tega pomembnega preverjanja. -Če so nastavljene privzete vrednosti, se prav tako preveri, ali gre za enega od ponujenih elementov, sicer se vrže izjema. To preverjanje je mogoče izklopiti z metodo `checkDefaultValue(false)`. +Pri nastavitvi privzetih izbranih postavk tudi preverja, da gre za ene izmed ponujenih, sicer vrže izjemo. To preverjanje lahko izklopite s pomočjo `checkDefaultValue(false)`. +Če pošiljate obrazec z metodo `GET`, lahko izberete kompaktnejši način prenosa podatkov, ki prihrani velikost poizvedbenega niza (query string). Aktivira se z nastavitvijo HTML atributa obrazca: + +```php +$form->setHtmlAttribute('data-nette-compact'); +``` -addRadioList(string|int $name, $label=null, array $items=null): RadioList .[method] -=================================================================================== -Doda radijske gumbe (razred [RadioList |api:Nette\Forms\Controls\RadioList]). Vrne ključ izbranega elementa ali `null`, če uporabnik ni izbral ničesar. Metoda `getSelectedItem()` namesto ključa vrne vrednost. +addRadioList(string|int $name, $label=null, ?array $items=null): RadioList .[method] +==================================================================================== + +Doda izbirne gumbe (radio buttons) (razred [RadioList |api:Nette\Forms\Controls\RadioList]). Vrne ključ izbrane postavke ali `null`, če uporabnik ničesar ni izbral. Metoda `getSelectedItem()` vrne vrednost namesto ključa. ```php $sex = [ - 'm' => 'male', - 'f' => 'female', + 'm' => 'moški', + 'f' => 'ženska', ]; -$form->addRadioList('gender', 'Gender:', $sex); +$form->addRadioList('gender', 'Spol:', $sex); ``` -Polje elementov posredujemo kot tretji parameter ali z metodo `setItems()`. +Polje ponujenih postavk predamo kot tretji parameter ali z metodo `setItems()`. -Uporabite lahko `setDisabled(['m'])` za onemogočanje posameznih elementov. +S pomočjo `setDisabled(['m', 'f'])` lahko deaktivirate posamezne postavke. -Element samodejno preveri, ali ni prišlo do ponarejanja in ali je izbrani element dejansko eden od ponujenih ter ni bil onemogočen. Z metodo `getRawValue()` lahko pridobite ponujeni element brez tega pomembnega preverjanja. +Element samodejno preverja, da ni prišlo do ponarejanja in da je izbrana postavka dejansko ena izmed ponujenih in ni bila deaktivirana. Z metodo `getRawValue()` lahko pridobite poslano postavko brez tega pomembnega preverjanja. -Če je nastavljena privzeta vrednost, preveri tudi, ali gre za enega od ponujenih elementov, sicer vrže izjemo. To preverjanje je mogoče izklopiti z metodo `checkDefaultValue(false)`. +Pri nastavitvi privzete izbrane postavke tudi preverja, da gre za eno izmed ponujenih, sicer vrže izjemo. To preverjanje lahko izklopite s pomočjo `checkDefaultValue(false)`. -addSelect(string|int $name, $label=null, array $items=null): SelectBox .[method] -================================================================================ +addSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): SelectBox .[method] +================================================================================================== -Doda izbirno polje (razred [SelectBox |api:Nette\Forms\Controls\SelectBox]). Vrne ključ izbranega elementa ali `null`, če uporabnik ni izbral ničesar. Metoda `getSelectedItem()` namesto ključa vrne vrednost. +Doda izbirno polje (select box) (razred [SelectBox |api:Nette\Forms\Controls\SelectBox]). Vrne ključ izbrane postavke ali `null`, če uporabnik ničesar ni izbral. Metoda `getSelectedItem()` vrne vrednost namesto ključa. ```php $countries = [ - 'CZ' => 'Czech republic', - 'SK' => 'Slovakia', - 'GB' => 'United Kingdom', + 'CZ' => 'Češka Republika', + 'SK' => 'Slovaška', + 'GB' => 'Velika Britanija', ]; -$form->addSelect('country', 'Country:', $countries) +$form->addSelect('country', 'Država:', $countries) ->setDefaultValue('SK'); ``` -Polje elementov posredujemo kot tretji parameter ali z metodo `setItems()`. Polje elementov je lahko tudi dvodimenzionalno: +Polje ponujenih postavk predamo kot tretji parameter ali z metodo `setItems()`. Postavke so lahko tudi dvodimenzionalno polje: ```php $countries = [ 'Europe' => [ - 'CZ' => 'Czech republic', - 'SK' => 'Slovakia', - 'GB' => 'United Kingdom', + 'CZ' => 'Češka Republika', + 'SK' => 'Slovaška', + 'GB' => 'Velika Britanija', ], - 'CA' => 'Canada', - 'US' => 'USA', - '?' => 'other', + 'CA' => 'Kanada', + 'US' => 'ZDA', + '?' => 'druga', ]; ``` -Pri izbirnih poljih ima prvi element pogosto poseben pomen, saj služi kot poziv k dejanju. Za dodajanje takega elementa uporabite metodo `setPrompt()`. +Pri izbirnih poljih ima pogosto prva postavka poseben pomen, služi kot poziv k akciji. Za dodajanje takšne postavke služi metoda `setPrompt()`. ```php -$form->addSelect('country', 'Country:', $countries) - ->setPrompt('Pick a country'); +$form->addSelect('country', 'Država:', $countries) + ->setPrompt('Izberite državo'); ``` -Uporabite lahko `setDisabled(['CZ', 'SK'])` za onemogočanje posameznih elementov. +S pomočjo `setDisabled(['CZ', 'SK'])` lahko deaktivirate posamezne postavke. -Element samodejno preveri, ali ni prišlo do ponarejanja in ali je izbrani element dejansko eden od ponujenih ter ni bil onemogočen. Z metodo `getRawValue()` lahko pridobite ponujeni element brez tega pomembnega preverjanja. +Element samodejno preverja, da ni prišlo do ponarejanja in da je izbrana postavka dejansko ena izmed ponujenih in ni bila deaktivirana. Z metodo `getRawValue()` lahko pridobite poslano postavko brez tega pomembnega preverjanja. -Če je nastavljena privzeta vrednost, preveri tudi, ali gre za enega od ponujenih elementov, sicer vrže izjemo. To preverjanje je mogoče izklopiti z metodo `checkDefaultValue(false)`. +Pri nastavitvi privzete izbrane postavke tudi preverja, da gre za eno izmed ponujenih, sicer vrže izjemo. To preverjanje lahko izklopite s pomočjo `checkDefaultValue(false)`. -addMultiSelect(string|int $name, $label=null, array $items=null): MultiSelectBox .[method] -========================================================================================== +addMultiSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): MultiSelectBox .[method] +============================================================================================================ -Doda izbirno polje za več možnosti (razred [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox]). Vrne polje ključev izbranih elementov. Metoda `getSelectedItems()` namesto ključev vrne vrednosti. +Doda izbirno polje za izbiro več postavk (razred [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox]). Vrne polje ključev izbranih postavk. Metoda `getSelectedItems()` vrne vrednosti namesto ključev. ```php -$form->addMultiSelect('countries', 'Countries:', $countries); +$form->addMultiSelect('countries', 'Države:', $countries); ``` -Polje elementov posredujemo kot tretji parameter ali z metodo `setItems()`. Polje elementov je lahko tudi dvodimenzionalno. +Polje ponujenih postavk predamo kot tretji parameter ali z metodo `setItems()`. Postavke so lahko tudi dvodimenzionalno polje. -Uporabite lahko `setDisabled(['CZ', 'SK'])` za onemogočanje posameznih elementov. +S pomočjo `setDisabled(['CZ', 'SK'])` lahko deaktivirate posamezne postavke. -Element samodejno preveri, da ni prišlo do ponarejanja in da so izbrani elementi dejansko eni od ponujenih ter da niso bili onemogočeni. Z metodo `getRawValue()` lahko pridobite predložene elemente brez tega pomembnega preverjanja. +Element samodejno preverja, da ni prišlo do ponarejanja in da so izbrane postavke dejansko ene izmed ponujenih in niso bile deaktivirane. Z metodo `getRawValue()` lahko pridobite poslane postavke brez tega pomembnega preverjanja. -Če so nastavljene privzete vrednosti, se prav tako preveri, ali gre za enega od ponujenih elementov, sicer se vrže izjema. To preverjanje je mogoče izklopiti z metodo `checkDefaultValue(false)`. +Pri nastavitvi privzetih izbranih postavk tudi preverja, da gre za ene izmed ponujenih, sicer vrže izjemo. To preverjanje lahko izklopite s pomočjo `checkDefaultValue(false)`. addUpload(string|int $name, $label=null): UploadControl .[method] ================================================================= -Doda polje za nalaganje datotek (razred [UploadControl |api:Nette\Forms\Controls\UploadControl]). Vrne objekt [FileUpload |http:request#FileUpload], tudi če uporabnik ni naložil datoteke, kar je mogoče ugotoviti z metodo `FileUpload::hasFile()`. +Doda polje za nalaganje datoteke (razred [UploadControl |api:Nette\Forms\Controls\UploadControl]). Vrne objekt [FileUpload |http:request#FileUpload], in to tudi v primeru, da uporabnik nobene datoteke ni poslal, kar lahko ugotovite z metodo `FileUpload::hasFile()`. ```php $form->addUpload('avatar', 'Avatar:') - ->addRule($form::Image, 'Avatar must be JPEG, PNG, GIF or WebP') - ->addRule($form::MaxFileSize, 'Maximum size is 1 MB', 1024 * 1024); + ->addRule($form::Image, 'Avatar mora biti JPEG, PNG, GIF, WebP ali AVIF.') + ->addRule($form::MaxFileSize, 'Maksimalna velikost je 1 MB.', 1024 * 1024); ``` -Če datoteka ni bila pravilno naložena, obrazec ni bil uspešno poslan in se prikaže napaka. To pomeni, da metode `FileUpload::isOk()` ni treba preverjati. +Če se datoteka ne uspe pravilno naložiti, obrazec ni uspešno poslan in prikaže se napaka. Tj. pri uspešni oddaji ni treba preverjati metode `FileUpload::isOk()`. -Ne zaupajte izvirnemu imenu datoteke, ki ga vrne metoda `FileUpload::getName()`, odjemalec lahko pošlje zlonamerno ime datoteke z namenom poškodovanja ali vdora v vašo aplikacijo. +Nikoli ne zaupajte originalnemu imenu datoteke, vrnjenemu z metodo `FileUpload::getName()`, klient bi lahko poslal škodljivo ime datoteke z namenom poškodovati ali vdreti v vašo aplikacijo. -Pravili `MimeType` in `Image` zahtevano vrsto datoteke ali slike odkrijeta na podlagi njenega podpisa. Celovitost celotne datoteke se ne preverja. Ali slika ni poškodovana, lahko ugotovite na primer tako, da [jo |http:request#toImage] poskusite [naložiti |http:request#toImage]. +Pravili `MimeType` in `Image` zaznata zahtevani tip na podlagi signature datoteke in ne preverjata njene integritete. Ali slika ni poškodovana, lahko ugotovite na primer s poskusom njenega [nalaganja |http:request#toImage]. addMultiUpload(string|int $name, $label=null): UploadControl .[method] ====================================================================== -Doda polje za nalaganje več datotek (razred [UploadControl |api:Nette\Forms\Controls\UploadControl]). Vrne polje predmetov [FileUpload |http:request#FileUpload]. Metoda `FileUpload::hasFile()` bo vrnila `true` za vsakega od njih. +Doda polje za nalaganje več datotek hkrati (razred [UploadControl |api:Nette\Forms\Controls\UploadControl]). Vrne polje objektov [FileUpload |http:request#FileUpload]. Metoda `FileUpload::hasFile()` pri vsakem od njih bo vračala `true`. ```php -$form->addMultiUpload('files', 'Files:') - ->addRule($form::MaxLength, 'A maximum of %d files can be uploaded', 10); +$form->addMultiUpload('files', 'Datoteke:') + ->addRule($form::MaxLength, 'Maksimalno lahko naložite %d datotek', 10); ``` -Če se ena od datotek ne naloži pravilno, obrazec ni bil uspešno poslan in se prikaže napaka. To pomeni, da metode `FileUpload::isOk()` ni treba preverjati. +Če se katera koli datoteka ne uspe pravilno naložiti, obrazec ni uspešno poslan in prikaže se napaka. Tj. pri uspešni oddaji ni treba preverjati metode `FileUpload::isOk()`. + +Nikoli ne zaupajte originalnim imenom datotek, vrnjenim z metodo `FileUpload::getName()`, klient bi lahko poslal škodljivo ime datoteke z namenom poškodovati ali vdreti v vašo aplikacijo. + +Pravili `MimeType` in `Image` zaznata zahtevani tip na podlagi signature datoteke in ne preverjata njene integritete. Ali slika ni poškodovana, lahko ugotovite na primer s poskusom njenega [nalaganja |http:request#toImage]. + -Ne zaupajte izvirnim imenom datotek, ki jih vrne metoda `FileUpload::getName()`, odjemalec lahko pošlje zlonamerno ime datoteke z namenom poškodovanja ali vdora v vašo aplikacijo. +addDate(string|int $name, $label=null): DateTimeControl .[method]{data-version:3.1.14} +====================================================================================== -Pravili `MimeType` in `Image` zahtevano vrsto datoteke ali slike odkrijeta na podlagi njenega podpisa. Celovitost celotne datoteke se ne preverja. Ali slika ni poškodovana, lahko ugotovite na primer tako, da [jo |http:request#toImage] poskusite [naložiti |http:request#toImage]. +Doda polje, ki uporabniku omogoča enostaven vnos datuma, sestavljenega iz leta, meseca in dneva (razred [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Kot privzeto vrednost sprejema bodisi objekte, ki implementirajo vmesnik `DateTimeInterface`, niz s časom ali število, ki predstavlja UNIX časovni žig. Enako velja za argumente pravil `Min`, `Max` ali `Range`, ki definirajo minimalni in maksimalni dovoljeni datum. + +```php +$form->addDate('date', 'Datum:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'Datum mora biti vsaj en mesec star.', new DateTime('-1 month')); +``` + +Standardno vrne objekt `DateTimeImmutable`, z metodo `setFormat()` lahko specificirate [besedilni format|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] ali časovni žig: + +```php +$form->addDate('date', 'Datum:') + ->setFormat('Y-m-d'); +``` -addHidden(string|int $name, string $default=null): HiddenField .[method] -======================================================================== +addTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=============================================================================================================== + +Doda polje, ki uporabniku omogoča enostaven vnos časa, sestavljenega iz ur, minut in opcijsko tudi sekund (razred [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Kot privzeto vrednost sprejema bodisi objekte, ki implementirajo vmesnik `DateTimeInterface`, niz s časom ali število, ki predstavlja UNIX časovni žig. Iz teh vnosov je uporabljena le časovna informacija, datum je ignoriran. Enako velja za argumente pravil `Min`, `Max` ali `Range`, ki definirajo minimalni in maksimalni dovoljeni čas. Če je nastavljena minimalna vrednost višja od maksimalne, se ustvari časovni obseg, ki presega polnoč. + +```php +$form->addTime('time', 'Čas:', withSeconds: true) + ->addRule($form::Range, 'Čas mora biti v obsegu od %d do %d.', ['12:30', '13:30']); +``` + +Standardno vrne objekt `DateTimeImmutable` (z datumom 1. januarja leta 1), z metodo `setFormat()` lahko specificirate [besedilni format|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters]: + +```php +$form->addTime('time', 'Čas:') + ->setFormat('H:i'); +``` + + +addDateTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=================================================================================================================== + +Doda polje, ki uporabniku omogoča enostaven vnos datuma in časa, sestavljenega iz leta, meseca, dneva, ur, minut in opcijsko tudi sekund (razred [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Kot privzeto vrednost sprejema bodisi objekte, ki implementirajo vmesnik `DateTimeInterface`, niz s časom ali število, ki predstavlja UNIX časovni žig. Enako velja za argumente pravil `Min`, `Max` ali `Range`, ki definirajo minimalni in maksimalni dovoljeni datum. + +```php +$form->addDateTime('datetime', 'Datum in čas:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'Datum mora biti vsaj en mesec star.', new DateTime('-1 month')); +``` + +Standardno vrne objekt `DateTimeImmutable`, z metodo `setFormat()` lahko specificirate [besedilni format|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] ali časovni žig: + +```php +$form->addDateTime('datetime') + ->setFormat(DateTimeControl::FormatTimestamp); +``` + + +addColor(string|int $name, $label=null): ColorPicker .[method]{data-version:3.1.14} +=================================================================================== + +Doda polje za izbiro barve (razred [ColorPicker |api:Nette\Forms\Controls\ColorPicker]). Barva je niz v obliki `#rrggbb`. Če uporabnik izbire ne opravi, se vrne črna barva `#000000`. + +```php +$form->addColor('color', 'Barva:') + ->setDefaultValue('#3C8ED7'); +``` + + +addHidden(string|int $name, ?string $default=null): HiddenField .[method] +========================================================================= Doda skrito polje (razred [HiddenField |api:Nette\Forms\Controls\HiddenField]). @@ -255,7 +348,9 @@ Doda skrito polje (razred [HiddenField |api:Nette\Forms\Controls\HiddenField]). $form->addHidden('userid'); ``` -Uporabite `setNullable()`, da ga spremenite tako, da vrne `null` namesto praznega niza. S funkcijo [addFilter() |validation#Modifying Input Values] lahko spremenite oddano vrednost. +S pomočjo `setNullable()` lahko nastavite, da vrne `null` namesto praznega niza. Spreminjanje poslane vrednosti omogoča [addFilter() |validation#Spreminjanje vnosa]. + +Čeprav je element skrit, je **pomembno se zavedati**, da lahko vrednost še vedno spremeni ali ponaredi napadalec. Vedno temeljito preverjajte in validirajte vse prejete vrednosti na strežniški strani, da preprečite varnostna tveganja, povezana z manipulacijo podatkov. addSubmit(string|int $name, $caption=null): SubmitButton .[method] @@ -264,17 +359,17 @@ addSubmit(string|int $name, $caption=null): SubmitButton .[method] Doda gumb za oddajo (razred [SubmitButton |api:Nette\Forms\Controls\SubmitButton]). ```php -$form->addSubmit('submit', 'Register'); +$form->addSubmit('submit', 'Pošlji'); ``` -V obrazcu je mogoče imeti več kot en gumb za oddajo: +V obrazcu je mogoče imeti tudi več gumbov za oddajo: ```php -$form->addSubmit('register', 'Register'); -$form->addSubmit('cancel', 'Cancel'); +$form->addSubmit('register', 'Registriraj'); +$form->addSubmit('cancel', 'Prekliči'); ``` -Če želite ugotoviti, kateri od njih je bil kliknjen, uporabite: +Za ugotovitev, na katerega od njih je bilo kliknjeno, uporabite: ```php if ($form['register']->isSubmittedBy()) { @@ -282,45 +377,45 @@ if ($form['register']->isSubmittedBy()) { } ``` -Če ne želite preveriti obrazca, ko je pritisnjen gumb za oddajo (kot sta gumba *Preklic* ali *Predogled*), lahko to izklopite s funkcijo [setValidationScope(). |validation#Disabling Validation] +Če ne želite validirati celotnega obrazca ob pritisku na gumb (na primer pri gumbih *Prekliči* ali *Predogled*), uporabite [setValidationScope() |validation#Izklop validacije]. addButton(string|int $name, $caption): Button .[method] ======================================================= -Doda gumb (razred [Button |api:Nette\Forms\Controls\Button]) brez funkcije submit. Uporaben je za vezavo drugih funkcij na id, na primer akcije JavaScript. +Doda gumb (razred [Button |api:Nette\Forms\Controls\Button]), ki nima funkcije oddaje. Lahko ga torej uporabite za kakšno drugo funkcijo, npr. klic JavaScript funkcije ob kliku. ```php -$form->addButton('raise', 'Raise salary') +$form->addButton('raise', 'Zvišaj plačo') ->setHtmlAttribute('onclick', 'raiseSalary()'); ``` -addImageButton(string|int $name, string $src=null, string $alt=null): ImageButton .[method] -=========================================================================================== +addImageButton(string|int $name, ?string $src=null, ?string $alt=null): ImageButton .[method] +============================================================================================= Doda gumb za oddajo v obliki slike (razred [ImageButton |api:Nette\Forms\Controls\ImageButton]). ```php -$form->addImageButton('submit', '/path/to/image'); +$form->addImageButton('submit', '/pot/do/slike'); ``` -Če uporabljate več gumbov za oddajo, lahko ugotovite, kateri od njih je bil kliknjen z `$form['submit']->isSubmittedBy()`. +Pri uporabi več gumbov za oddajo lahko ugotovite, na katerega je bilo kliknjeno, s pomočjo `$form['submit']->isSubmittedBy()`. addContainer(string|int $name): Container .[method] =================================================== -Doda podobrazec (razred [Container |api:Nette\Forms\Container]) ali vsebnik, ki ga je mogoče obravnavati enako kot obrazec. To pomeni, da lahko uporabite metode, kot sta `setDefaults()` ali `getValues()`. +Doda podobrazec (razred [Container|api:Nette\Forms\Container]), ali vsebnik, v katerega lahko dodajate druge elemente na enak način, kot jih dodajamo v obrazec. Delujejo tudi metode `setDefaults()` ali `getValues()`. ```php $sub1 = $form->addContainer('first'); -$sub1->addText('name', 'Your name:'); -$sub1->addEmail('email', 'Email:'); +$sub1->addText('name', 'Vaše ime:'); +$sub1->addEmail('email', 'E-pošta:'); $sub2 = $form->addContainer('second'); -$sub2->addText('name', 'Your name:'); -$sub2->addEmail('email', 'Email:'); +$sub2->addText('name', 'Vaše ime:'); +$sub2->addEmail('email', 'E-pošta:'); ``` Poslani podatki se nato vrnejo kot večdimenzionalna struktura: @@ -339,110 +434,112 @@ Poslani podatki se nato vrnejo kot večdimenzionalna struktura: ``` -Pregled nastavitev .[#toc-overview-of-settings] -=============================================== +Pregled nastavitev +================== -Za vse elemente lahko pokličemo naslednje metode (za popoln pregled glejte [dokumentacijo API |https://api.nette.org/forms/master/Nette/Forms/Controls.html] ): +Pri vseh elementih lahko kličemo naslednje metode (popoln pregled v [API dokumentaciji|https://api.nette.org/forms/master/Nette/Forms/Controls.html]): .[table-form-methods language-php] -| `setDefaultValue($value)` | nastavi privzeto vrednost -| `getValue()` | pridobitev trenutne vrednosti -| `setOmitted()` | [izpuščene vrednosti |#omitted values] -| `setDisabled()` | [onemogočanje vnosov |#disabling inputs] +| `setDefaultValue($value)` | nastavi privzeto vrednost +| `getValue()` | pridobi trenutno vrednost +| `setOmitted()` | [#Izpustitev vrednosti] +| `setDisabled()` | [#Deaktivacija elementov] Izrisovanje: .[table-form-methods language-php] -| `setCaption($caption)`| sprememba napisa elementa -| `setTranslator($translator)` | nastavi [prevajalnik |rendering#translating] -| `setHtmlAttribute($name, $value)` | nastavi [atribut HTML |rendering#HTML attributes] elementa -| `setHtmlId($id)` | nastavi atribut HTML `id` -| `setHtmlType($type)` | nastavi atribut HTML `type` -| `setHtmlName($name)`| nastavi atribut HTML `name` -| `setOption($key, $value)` | nastavi [podatke za upodabljanje |rendering#Options] - -Potrjevanje: +| `setCaption($caption)` | spremeni oznako elementa +| `setTranslator($translator)` | nastavi [prevajalnik |rendering#Prevajanje] +| `setHtmlAttribute($name, $value)` | nastavi [HTML atribut |rendering#HTML atributi] elementa +| `setHtmlId($id)` | nastavi HTML atribut `id` +| `setHtmlType($type)` | nastavi HTML atribut `type` +| `setHtmlName($name)` | nastavi HTML atribut `name` +| `setOption($key, $value)` | [nastavitve za izrisovanje |rendering#Možnosti Options] + +Validacija: .[table-form-methods language-php] -| `setRequired()` | [obvezno polje |validation] -| `addRule()` | določitev [pravila potrjevanja |validation#Rules] -| `addCondition()`, `addConditionOn()` | nastavitev [pogoja potrjevanja |validation#Conditions] -| `addError($message)`| [posredovanje sporočila o napaki |validation#processing-errors] +| `setRequired()` | [obvezni element |validation] +| `addRule()` | nastavitev [validacijsko pravilo |validation#Pravila] +| `addCondition()`, `addConditionOn()` | nastavi [validacijski pogoj |validation#Pogoji] +| `addError($message)` | [predaja sporočila o napaki |validation#Napake pri obdelavi] -Za elemente `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` se lahko kličejo naslednje metode: +Pri elementih `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` lahko kličemo naslednje metode: .[table-form-methods language-php] -| `setNullable()`| določa, ali funkcija getValue() vrne `null` namesto praznega niza -| `setEmptyValue($value)` | določa posebno vrednost, ki se obravnava kot prazen niz -| `setMaxLength($length)`| določa največje dovoljeno število znakov -| `addFilter($filter)`| [spreminjanje vhodnih vrednosti |validation#Modifying Input Values] +| `setNullable()` | nastavi, ali getValue() vrne `null` namesto praznega niza +| `setEmptyValue($value)` | nastavi posebno vrednost, ki se šteje za prazen niz +| `setMaxLength($length)` | nastavi maksimalno število dovoljenih znakov +| `addFilter($filter)` | [prilagoditev vnosa |validation#Spreminjanje vnosa] -Izpuščene vrednosti .[#toc-omitted-values] ------------------------------------------- +Izpustitev vrednosti +==================== -Če vas vrednost, ki jo je vnesel uporabnik, ne zanima, jo lahko z uporabo `setOmitted()` izpustimo iz rezultata, ki ga zagotovi metoda `$form->getValues​()` ali posreduje obdelovalcem. To je primerno za različna gesla za preverjanje, antispam polja itd. +Če nas vrednost, ki jo je vnesel uporabnik, ne zanima, jo lahko s pomočjo `setOmitted()` izpustimo iz rezultata metode `$form->getValues()` ali iz podatkov, predanih handlerjem. To je koristno za različna gesla za preverjanje, antispam elemente itd. ```php -$form->addPassword('passwordVerify', 'Password again:') - ->setRequired('Fill your password again to check for typo') - ->addRule($form::Equal, 'Password mismatch', $form['password']) +$form->addPassword('passwordVerify', 'Geslo za preverjanje:') + ->setRequired('Prosimo, vnesite geslo še enkrat za preverjanje') + ->addRule($form::Equal, 'Gesli se ne ujemata', $form['password']) ->setOmitted(); ``` -Onemogočanje vnosov .[#toc-disabling-inputs] --------------------------------------------- +Deaktivacija elementov +====================== -Če želite onemogočiti vhod, lahko pokličete `setDisabled()`. Takšnega polja uporabnik ne more urejati. +Elemente lahko deaktivirate s pomočjo `setDisabled()`. Takšnega elementa uporabnik ne more urejati. ```php -$form->addText('username', 'User name:') +$form->addText('username', 'Uporabniško ime:') ->setDisabled(); ``` -Upoštevajte, da brskalnik onemogočenih polj sploh ne pošlje strežniku, zato jih ne boste našli niti v podatkih, ki jih vrne funkcija `$form->getValues()`. +Onemogočeni elementi brskalnik sploh ne pošilja na strežnik, torej jih niti ne najdete v podatkih, vrnjenih s funkcijo `$form->getValues()`. Če pa nastavite `setOmitted(false)`, Nette v te podatke vključi njihovo privzeto vrednost. -Če polju določate privzeto vrednost, morate to storiti šele potem, ko ste ga onemogočili: +Pri klicu `setDisabled()` se iz varnostnih razlogov **izbriše vrednost elementa**. Če nastavljate privzeto vrednost, je to treba storiti šele po njegovi deaktivaciji: ```php -$form->addText('username', 'User name:') +$form->addText('username', 'Uporabniško ime:') ->setDisabled() ->setDefaultValue($userName); ``` +Alternativa onemogočenim elementom so elementi s HTML atributom `readonly`, ki jih brskalnik pošilja na strežnik. Čeprav je element samo za branje, je **pomembno se zavedati**, da lahko njegovo vrednost še vedno spremeni ali ponaredi napadalec. + -Prilagojeni gumbi .[#toc-custom-controls] -========================================= +Lastni elementi +=============== -Poleg širokega nabora vgrajenih kontrolnikov obrazca lahko v obrazec dodate kontrolnike po meri, kot sledi: +Poleg široke palete vgrajenih elementov obrazca lahko v obrazec dodajate lastne elemente na ta način: ```php -$form->addComponent(new DateInput('Date:'), 'date'); -// alternativna sintaksa: Datum:"); +$form->addComponent(new DateInput('Datum:'), 'date'); +// alternativna sintaksa: $form['date'] = new DateInput('Datum:'); ``` .[note] -Obrazec je potomec razreda [Container | component-model:#Container], elementi pa so potomci razreda [Component | component-model:#Component]. +Obrazec je potomec razreda [Container |component-model:#Container] in posamezni elementi so potomci [Component |component-model:#Component]. -Za dodajanje elementov po meri (npr. `$form->addZip()`) lahko določite nove metode obrazca. To so tako imenovane razširitvene metode. Slaba stran je, da namigi za kodo v urejevalnikih zanje ne bodo delovali. +Obstaja način, kako definirati nove metode obrazca, ki služijo za dodajanje lastnih elementov (npr. `$form->addZip()`). Gre za t.i. extension methods. Slabost je, da zanje ne bo delovalo predlaganje v urejevalnikih. ```php use Nette\Forms\Container; -// doda metodo addZip(string $name, string $label = null) -Container::extensionMethod('addZip', function (Container $form, string $name, string $label = null) { +// dodamo metodo addZip(string $name, ?string $label = null) +Container::extensionMethod('addZip', function (Container $form, string $name, ?string $label = null) { return $form->addText($name, $label) - ->addRule($form::Pattern, 'At least 5 numbers', '[0-9]{5}'); + ->addRule($form::Pattern, 'Vsaj 5 številk', '[0-9]{5}'); }); // uporaba -$form->addZip('zip', 'ZIP code:'); +$form->addZip('zip', 'Poštna številka:'); ``` -Polja nizke ravni .[#toc-low-level-fields] -========================================== +Nizkonivojski elementi +====================== -Če želite v obrazec dodati element, vam ni treba klicati `$form->addXyz()`. Namesto tega lahko elemente obrazca uvedete izključno v predlogah. To je koristno, če morate na primer ustvariti dinamične elemente: +Lahko uporabljamo tudi elemente, ki jih zapišemo samo v predlogi in jih ne dodamo v obrazec z nobeno od metod `$form->addXyz()`. Ko na primer izpisujemo zapise iz podatkovne baze in vnaprej ne vemo, koliko jih bo in kakšne ID-je bodo imeli, in želimo pri vsaki vrstici prikazati potrditveno polje ali izbirni gumb, ga zadostuje kodirati v predlogi: ```latte {foreach $items as $item} @@ -450,13 +547,13 @@ Polja nizke ravni .[#toc-low-level-fields] {/foreach} ``` -Po oddaji lahko pridobite vrednosti: +In po oddaji vrednost ugotovimo: ```php $data = $form->getHttpData($form::DataText, 'sel[]'); $data = $form->getHttpData($form::DataText | $form::DataKeys, 'sel[]'); ``` -V prvem parametru določite vrsto elementa (`DataFile` za `type=file`, `DataLine` za enovrstične vnose, kot so `text`, `password` ali `email` in `DataText` za ostale). Drugi parameter ustreza atributu HTML `name`. Če morate ohraniti ključe, lahko prvi parameter kombinirate s `DataKeys`. To je uporabno za `select`, `radioList` ali `checkboxList`. +kjer prvi parameter je tip elementa (`DataFile` za `type=file`, `DataLine` za enovrstične vnose kot `text`, `password`, `email` ipd. in `DataText` za vse ostale) in drugi parameter `sel[]` ustreza HTML atributu name. Tip elementa lahko kombiniramo z vrednostjo `DataKeys`, ki ohrani ključe elementov. To je koristno zlasti za `select`, `radioList` in `checkboxList`. -Parameter `getHttpData()` vrne prečiščene vhodne podatke. V tem primeru bo to vedno polje veljavnih nizov UTF-8, ne glede na to, kaj je napadalec poslal z obrazcem. To je alternativa neposrednemu delu s `$_POST` ali `$_GET`, če želite prejeti varne podatke. +Bistveno je, da `getHttpData()` vrne sanirano vrednost, v tem primeru bo to vedno polje veljavnih UTF-8 nizov, ne glede na to, kaj bi poskušal napadalec podtakniti strežniku. Gre za analogijo neposrednega dela z `$_POST` ali `$_GET`, vendar s to bistveno razliko, da vedno vrne čiste podatke, tako kot ste navajeni pri standardnih elementih Nette obrazcev. diff --git a/forms/sl/in-presenter.texy b/forms/sl/in-presenter.texy index bf919a5eb2..e09ea5be98 100644 --- a/forms/sl/in-presenter.texy +++ b/forms/sl/in-presenter.texy @@ -1,36 +1,36 @@ -Obrazci v programu Presenters -***************************** +Obrazci v presenterjih +********************** .[perex] -Nette Forms bistveno olajša ustvarjanje in obdelavo spletnih obrazcev. V tem poglavju se boste naučili, kako uporabljati obrazce v predstavitvah. +Nette Forms bistveno olajšajo ustvarjanje in obdelavo spletnih obrazcev. V tem poglavju se boste seznanili z uporabo obrazcev znotraj presenterjev. -Če jih želite uporabljati povsem samostojno brez preostalega ogrodja, je na voljo vodnik za [samostojne obrazce |standalone]. +Če vas zanima, kako jih uporabljati popolnoma samostojno brez preostalega ogrodja, je za vas namenjen vodič za [samostojno uporabo|standalone]. -Prvi obrazec .[#toc-first-form] -=============================== +Prvi obrazec +============ -Poskusili bomo napisati preprost obrazec za registracijo. Njegova koda bo videti takole: +Poskusimo napisati preprost registracijski obrazec. Njegova koda bo naslednja: ```php use Nette\Application\UI\Form; $form = new Form; -$form->addText('name', 'Name:'); -$form->addPassword('password', 'Password:'); -$form->addSubmit('send', 'Sign up'); +$form->addText('name', 'Ime:'); +$form->addPassword('password', 'Geslo:'); +$form->addSubmit('send', 'Registriraj'); $form->onSuccess[] = [$this, 'formSucceeded']; ``` -v brskalniku pa mora biti rezultat videti takole: +in v brskalniku se bo prikazal takole: -[* form-en.webp *] +[* form-cs.webp *] -Obrazec v predstavitvi je objekt razreda `Nette\Application\UI\Form`, njegov predhodnik `Nette\Forms\Form` pa je namenjen samostojni uporabi. Dodali smo mu polja ime, geslo in gumb za pošiljanje. Nazadnje je v vrstici s `$form->onSuccess` zapisano, da je treba po oddaji in uspešni potrditvi poklicati metodo `$this->formSucceeded()`. +Obrazec v presenterju je objekt razreda `Nette\Application\UI\Form`, njegov predhodnik `Nette\Forms\Form` je namenjen samostojni uporabi. Dodali smo mu t.i. elemente ime, geslo in gumb za oddajo. In na koncu vrstica z `$form->onSuccess` pove, da se mora po oddaji in uspešni validaciji poklicati metoda `$this->formSucceeded()`. -Z vidika predstavnika je obrazec skupna komponenta. Zato ga obravnavamo kot komponento in ga vključimo v predstavitveni program z uporabo [tovarniške metode |application:components#Factory Methods]. To bo videti takole: +Z vidika presenterja je obrazec običajna komponenta. Zato se z njim kot s komponento ravna in ga vključimo v presenter s pomočjo [tovarne metode |application:components#Tovarniške metode]. Izgledalo bo takole: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} use Nette; use Nette\Application\UI\Form; @@ -39,103 +39,100 @@ class HomePresenter extends Nette\Application\UI\Presenter protected function createComponentRegistrationForm(): Form { $form = new Form; - $form->addText('name', 'Name:'); - $form->addPassword('password', 'Password:'); - $form->addSubmit('send', 'Sign up'); + $form->addText('name', 'Ime:'); + $form->addPassword('password', 'Geslo:'); + $form->addSubmit('send', 'Registriraj'); $form->onSuccess[] = [$this, 'formSucceeded']; return $form; } public function formSucceeded(Form $form, $data): void { - // tukaj bomo obdelali podatke, ki jih je poslal obrazec + // tukaj obdelamo podatke, poslane z obrazcem // $data->name vsebuje ime // $data->password vsebuje geslo - $this->flashMessage('You have successfully signed up.'); + $this->flashMessage('Uspešno ste bili registrirani.'); $this->redirect('Home:'); } } ``` -Prikaz v predlogi pa se izvede z uporabo oznake `{control}`: +In v predlogi obrazec izrišemo z oznako `{control}`: -```latte .{file:app/Presenters/templates/Home/default.latte} -<h1>Registration</h1> +```latte .{file:app/Presentation/Home/default.latte} +<h1>Registracija</h1> {control registrationForm} ``` -In to je vse :-) Imamo funkcionalen in popolnoma [zaščiten |#Vulnerability Protection] obrazec. +In to je pravzaprav vse :-) Imamo delujoč in popolnoma [zavarovan |#Zaščita pred ranljivostmi] obrazec. -Zdaj verjetno razmišljate, da je bilo to prehitro, in se sprašujete, kako je mogoče, da se kliče metoda `formSucceeded()` in kakšne parametre dobi. Seveda imate prav, to si zasluži razlago. +In zdaj si verjetno mislite, da je bilo prehitro, razmišljate, kako je mogoče, da se pokliče metoda `formSucceeded()` in kaj so parametri, ki jih prejme. Seveda, imate prav, to si zasluži pojasnilo. -Nette se je domislil kul mehanizma, ki mu pravimo [hollywoodski slog |application:components#Hollywood style]. Namesto da bi nenehno spraševali, ali se je nekaj zgodilo ("je bil obrazec oddan?", "je bil veljavno oddan?" ali "ni bil ponarejen?"), ogrodju rečete "ko je obrazec veljavno izpolnjen, pokliči to metodo" in mu prepustite nadaljnje delo. Če programirate v javascriptu, ste seznanjeni s tem načinom programiranja. Napišete funkcije, ki se pokličejo, ko se zgodi določen [dogodek |nette:glossary#Events]. Jezik pa jim posreduje ustrezne argumente. +Nette namreč prihaja s svežim mehanizmom, ki mu pravimo [Hollywood style |application:components#Hollywood style]. Namesto da bi se kot razvijalec morali nenehno spraševati, ali se je nekaj zgodilo („ali je bil obrazec poslan?“, „ali je bil poslan veljavno?“ in „ali ni prišlo do njegovega ponarejanja?“), poveste ogrodju „ko bo obrazec veljavno izpolnjen, pokliči to metodo“ in prepustite nadaljnje delo njemu. Če programirate v JavaScriptu, ta slog programiranja dobro poznate. Pišete funkcije, ki se kličejo, ko nastopi določen [dogodek |nette:glossary#Dogodki eventi]. In jezik jim preda ustrezne argumente. -Na ta način je zgrajena zgornja koda predstavnika. Polje `$form->onSuccess` predstavlja seznam povratnih klicev PHP, ki jih bo Nette poklical, ko bo obrazec oddan in pravilno izpolnjen. -V [življenjskem ciklu predstavnika |application:presenters#Life Cycle of Presenter] gre za tako imenovani signal, zato se kličejo po metodi `action*` in pred metodo `render*`. -Vsakemu povratnemu klicu pa posreduje sam obrazec v prvem parametru in poslane podatke kot objekt [ArrayHash |utils:arrays#ArrayHash] v drugem. Prvi parameter lahko izpustite, če objekta obrazca ne potrebujete. Drugi parameter je lahko še bolj priročen, a o tem [kasneje |#Mapping to Classes]. +Prav tako je zgrajena tudi zgoraj navedena koda presenterja. Polje `$form->onSuccess` predstavlja seznam PHP povratnih klicev (callbackov), ki jih Nette pokliče v trenutku, ko je obrazec poslan in pravilno izpolnjen (tj. je veljaven). V okviru [življenjskega cikla presenterja |application:presenters#Življenjski cikel presenterja] gre za t.i. signal, kličejo se torej po `action*` metodi in pred `render*` metodo. In vsakemu povratnemu klicu preda kot prvi parameter sam obrazec in kot drugega poslane podatke v obliki objekta [ArrayHash |utils:arrays#ArrayHash]. Prvi parameter lahko izpustite, če objekta obrazca ne potrebujete. In drugi parameter zna biti bolj prebrisan, ampak o tem [kasneje |#Mapiranje na razrede]. -Objekt `$data` vsebuje lastnosti `name` in `password` s podatki, ki jih je vnesel uporabnik. Običajno podatke pošljemo neposredno v nadaljnjo obdelavo, ki je lahko na primer vstavljanje v zbirko podatkov. Vendar pa lahko med obdelavo pride do napake, na primer uporabniško ime je že zasedeno. V tem primeru posredujemo napako nazaj obrazcu s pomočjo `addError()` in ga pustimo ponovno narisati, s sporočilom o napaki: +Objekt `$data` vsebuje ključe `name` in `password` s podatki, ki jih je izpolnil uporabnik. Običajno podatke takoj pošljemo k nadaljnji obdelavi, kar je lahko na primer vstavljanje v podatkovno bazo. Med obdelavo pa se lahko pojavi napaka, na primer uporabniško ime je že zasedeno. V takem primeru napako predamo nazaj v obrazec s pomočjo `addError()` in pustimo, da se ponovno izriše, tudi s sporočilom o napaki. ```php -$form->addError('Sorry, username is already in use.'); +$form->addError('Oprostite, uporabniško ime že nekdo uporablja.'); ``` -Poleg `onSuccess` obstaja tudi `onSubmit`: povratni klici se vedno pokličejo po oddaji obrazca, tudi če ta ni pravilno izpolnjen. In končno `onError`: klicne vrstice se pokličejo samo, če oddaja ni pravilna. Pokličejo se celo, če z uporabo `addError()` razveljavimo obrazec v `onSuccess` ali `onSubmit`. +Poleg `onSuccess` obstaja še `onSubmit`: povratni klici se kličejo vedno po oddaji obrazca, tudi takrat, ko ni pravilno izpolnjen. In dalje `onError`: povratni klici se kličejo le, če oddaja ni veljavna. Pokličejo se celo takrat, če v `onSuccess` ali `onSubmit` znevalidiramo obrazec s pomočjo `addError()`. -Po obdelavi obrazca bomo preusmerili na naslednjo stran. S tem preprečimo, da bi obrazec nenamerno ponovno oddali s klikom na gumb *osvežitev*, *povratek* ali s premikanjem zgodovine brskalnika. +Po obdelavi obrazca preusmerimo na naslednjo stran. S tem se prepreči neželeno ponovno pošiljanje obrazca z gumbom *osveži*, *nazaj* ali premikanjem v zgodovini brskalnika. -Poskusite dodati več [kontrolnih elementov obrazca |controls]. +Poskusite dodati tudi druge [elemente obrazca|controls]. -Dostop do kontrolnih elementov .[#toc-access-to-controls] -========================================================= +Dostop do elementov +=================== -Obrazec je sestavni del predstavnika, v našem primeru poimenovanega `registrationForm` (po imenu tovarniške metode `createComponentRegistrationForm`), zato lahko kjer koli v predstavniku pridete do obrazca z uporabo: +Obrazec je komponenta presenterja, v našem primeru poimenovana `registrationForm` (po imenu tovarne metode `createComponentRegistrationForm`), tako da kjerkoli v presenterju do obrazca pridete s pomočjo: ```php $form = $this->getComponent('registrationForm'); // alternativna sintaksa: $form = $this['registrationForm']; ``` -Tudi posamezne kontrole obrazca so komponente, zato lahko do njih dostopate na enak način: +Komponente so tudi posamezni elementi obrazca, zato do njih pridete na enak način: ```php $input = $form->getComponent('name'); // ali $input = $form['name']; $button = $form->getComponent('send'); // ali $button = $form['send']; ``` -Kontrolne elemente odstranite z uporabo funkcije unset: +Elementi se odstranijo s pomočjo unset: ```php unset($form['name']); ``` -Pravila potrjevanja .[#toc-validation-rules] -============================================ +Validacijska pravila +==================== -Večkrat je bila uporabljena beseda *valid*, vendar obrazec še nima pravil potrjevanja. To popravimo. +Padla je beseda *veljaven,* ampak obrazec zaenkrat nima nobenih validacijskih pravil. Popravimo to. -Ime bo obvezno, zato ga bomo označili z metodo `setRequired()`, katere argument je besedilo sporočila o napaki, ki se prikaže, če ga uporabnik ne izpolni. Če argument ni naveden, se uporabi privzeto sporočilo o napaki. +Ime bo obvezno, zato ga označimo z metodo `setRequired()`, katere argument je besedilo sporočila o napaki, ki se prikaže, če uporabnik imena ne izpolni. Če argumenta ne navedemo, se uporabi privzeto sporočilo o napaki. ```php -$form->addText('name', 'Name:') - ->setRequired('Please fill your name.'); +$form->addText('name', 'Ime:') + ->setRequired('Prosimo, vnesite ime'); ``` -Poskusite oddati obrazec brez izpolnjenega imena in videli boste, da se bo prikazalo sporočilo o napaki, brskalnik ali strežnik pa ga bo zavrnil, dokler ga ne izpolnite. +Poskusite poslati obrazec brez izpolnjenega imena in videli boste, da se prikaže sporočilo o napaki in brskalnik ali strežnik ga bo zavračal, dokler polja ne izpolnite. -Hkrati sistema ne boste mogli prevarati tako, da boste v vnos vnesli samo presledke, na primer. Na noben način. Nette samodejno obreže leve in desne bele prostore. Preizkusite. To je nekaj, kar bi morali vedno narediti pri vsakem enovrstičnem vnosu, vendar se na to pogosto pozablja. Nette to stori samodejno. (Lahko poskusite prevarati obrazce in kot ime pošljete večvrstični niz. Tudi v tem primeru se Nette ne bo pustil preslepiti in prelomi vrstic se bodo spremenili v presledke.) +Hkrati sistema ne boste prelisičili s tem, da v polje napišete na primer le presledke. Kje pa. Nette leve in desne presledke samodejno odstranjuje. Preizkusite to. To je stvar, ki bi jo morali z vsakim enovrstičnim vnosom vedno narediti, a se nanjo pogosto pozabi. Nette to dela samodejno. (Lahko poskusite prelisičiti obrazec in kot ime poslati večvrstični niz. Tudi tukaj se Nette ne pusti zmesti in prelome vrstic spremeni v presledke.) -Obrazec se vedno potrdi na strani strežnika, vendar se ustvari tudi potrditev JavaScript, ki je hitra in uporabnik takoj izve za napako, ne da bi mu bilo treba obrazec poslati v strežnik. Za to poskrbi skripta `netteForms.js`. -Vstavite jo v predlogo postavitve: +Obrazec se vedno validira na strani strežnika, vendar se generira tudi JavaScript validacija, ki poteka bliskovito in uporabnik se o napaki seznani takoj, brez potrebe po pošiljanju obrazca na strežnik. Za to skrbi skript `netteForms.js`. Vstavite ga v predlogo postavitve: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Če pogledate izvorno kodo strani z obrazcem, lahko opazite, da Nette vstavi zahtevana polja v elemente z razredom CSS `required`. Poskusite v predlogo dodati naslednji slog in oznaka "Ime" bo rdeča. Na eleganten način označimo zahtevana polja za uporabnike: +Če pogledate v izvorno kodo strani z obrazcem, lahko opazite, da Nette obvezne elemente vstavlja v elemente s CSS razredom `required`. Poskusite dodati v predlogo naslednji slogovni list in oznaka „Ime“ bo rdeča. Elegantno tako uporabnikom označimo obvezne elemente: ```latte <style> @@ -143,96 +140,96 @@ Vstavite jo v predlogo postavitve: </style> ``` -Dodatna pravila potrjevanja bomo dodali z metodo `addRule()`. Prvi parameter je pravilo, drugi je spet besedilo sporočila o napaki, sledi pa lahko neobvezni argument pravila potrjevanja. Kaj to pomeni? +Druga validacijska pravila dodamo z metodo `addRule()`. Prvi parameter je pravilo, drugi je spet besedilo sporočila o napaki in lahko še sledi argument validacijskega pravila. Kaj se s tem misli? -Obrazec bo dobil še en izbirni vnos *starost* s pogojem, da mora biti številka (`addInteger()`) in v določenih mejah (`$form::Range`). In tu bomo uporabili tretji argument `addRule()`, tj. samo območje: +Obrazec razširimo z novim neobveznim poljem „starost“, ki mora biti celo število (`addInteger()`) in poleg tega v dovoljenem obsegu (`$form::Range`). In tukaj prav izkoristimo tretji parameter metode `addRule()`, s katerim validatorju predamo zahtevani obseg kot par `[od, do]`: ```php -$form->addInteger('age', 'Age:') - ->addRule($form::Range, 'You must be older 18 years and be under 120.', [18, 120]); +$form->addInteger('age', 'Starost:') + ->addRule($form::Range, 'Starost mora biti od 18 do 120', [18, 120]); ``` .[tip] -Če uporabnik polja ne izpolni, se pravila potrjevanja ne bodo preverila, saj je polje neobvezno. +Če uporabnik polja ne izpolni, se validacijska pravila ne bodo preverjala, saj je element neobvezen. -Očitno je na voljo prostor za majhno preoblikovanje. V sporočilu o napaki in v tretjem parametru so številke navedene podvojeno, kar ni idealno. Če bi ustvarjali [večjezični obrazec |rendering#translating] in bi bilo treba sporočilo s številkami prevesti v več jezikov, bi bilo spreminjanje vrednosti težje. Zato lahko uporabimo nadomestne znake `%d`: +Tukaj nastane prostor za drobno preoblikovanje (refactoring). V sporočilu o napaki in v tretjem parametru so števila navedena podvojeno, kar ni idealno. Če bi ustvarjali [večjezične obrazce |rendering#Prevajanje] in bi bilo sporočilo, ki vsebuje števila, prevedeno v več jezikov, bi se otežila morebitna sprememba vrednosti. Iz tega razloga je mogoče uporabiti nadomestne znake `%d` in Nette vrednosti dopolni: ```php - ->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]); + ->addRule($form::Range, 'Starost mora biti od %d do %d let', [18, 120]); ``` -Vrnimo se k polju *geslo*, naredimo ga *zahtevno* in preverimo najmanjšo dolžino gesla (`$form::MinLength`), pri čemer ponovno uporabimo nadomestne znake v sporočilu: +Vrnimo se k elementu `password`, ki ga prav tako naredimo obveznega in še preverimo minimalno dolžino gesla (`$form::MinLength`), spet z uporabo nadomestnega znaka: ```php -$form->addPassword('password', 'Password:') - ->setRequired('Pick a password') - ->addRule($form::MinLength, 'Your password has to be at least %d long', 8); +$form->addPassword('password', 'Geslo:') + ->setRequired('Izberite si geslo') + ->addRule($form::MinLength, 'Geslo mora imeti vsaj %d znakov', 8); ``` -Za preverjanje bomo obrazcu dodali polje `passwordVerify`, kamor uporabnik ponovno vnese geslo. Z uporabo pravil potrjevanja preverimo, ali sta obe gesli enaki (`$form::Equal`). Kot argument pa podamo sklic na prvo geslo z uporabo oglatih [oklepajev |#Access to Controls]: +Dodamo v obrazec še polje `passwordVerify`, kjer uporabnik vnese geslo še enkrat, za preverjanje. S pomočjo validacijskih pravil preverimo, ali sta obe gesli enaki (`$form::Equal`). In kot parameter damo sklic na prvo geslo s pomočjo [oglatih oklepajev |#Dostop do elementov]: ```php -$form->addPassword('passwordVerify', 'Password again:') - ->setRequired('Fill your password again to check for typo') - ->addRule($form::Equal, 'Password mismatch', $form['password']) +$form->addPassword('passwordVerify', 'Geslo za preverjanje:') + ->setRequired('Prosimo, vnesite geslo še enkrat za preverjanje') + ->addRule($form::Equal, 'Gesli se ne ujemata', $form['password']) ->setOmitted(); ``` -Z uporabo `setOmitted()` smo označili element, katerega vrednost nas v resnici ne zanima in ki obstaja le za potrjevanje. Njegove vrednosti ne posredujemo v `$data`. +S pomočjo `setOmitted()` smo označili element, katerega vrednost nas pravzaprav ne zanima in ki obstaja le zaradi validacije. Vrednost se ne preda v `$data`. -Imamo popolnoma funkcionalen obrazec s preverjanjem v PHP in JavaScript. Nettejeve možnosti potrjevanja so veliko širše, saj lahko ustvarite pogoje, v skladu z njimi prikažete in skrijete dele strani itd. Vse to si lahko preberete v poglavju o [potrjevanju obrazcev |validation]. +S tem imamo končan popolnoma delujoč obrazec z validacijo v PHP in JavaScriptu. Validacijske sposobnosti Nette so veliko širše, dajo se ustvarjati pogoji, puščati glede na njih prikazovati in skrivati dele strani itd. Vse se boste naučili v poglavju o [validaciji obrazcev|validation]. -Privzete vrednosti .[#toc-default-values] -========================================= +Privzete vrednosti +================== -Pogosto nastavljamo privzete vrednosti za kontrolne elemente obrazca: +Elementom obrazca običajno nastavljamo privzete vrednosti: ```php -$form->addEmail('email', 'Email') +$form->addEmail('email', 'E-pošta') ->setDefaultValue($lastUsedEmail); ``` -Pogosto je koristno nastaviti privzete vrednosti za vse kontrole naenkrat. Na primer, ko se obrazec uporablja za urejanje zapisov. Zapise preberemo iz podatkovne zbirke in jih nastavimo kot privzete vrednosti: +Pogosto je koristno nastaviti privzete vrednosti vsem elementom hkrati. Na primer, ko obrazec služi za urejanje zapisov. Preberemo zapis iz podatkovne baze in nastavimo privzete vrednosti: ```php //$row = ['name' => 'John', 'age' => '33', /* ... */]; $form->setDefaults($row); ``` -Po definiranju kontrolnih elementov pokličemo `setDefaults()`. +Kličite `setDefaults()` šele po definiciji elementov. -Prikazovanje obrazca .[#toc-rendering-the-form] -=============================================== +Izrisovanje obrazca +=================== -Obrazec je privzeto prikazan kot tabela. Posamezni kontrolni elementi upoštevajo osnovne smernice spletne dostopnosti. Vse oznake so ustvarjene kot `<label>` elementi in so povezani z njihovimi vhodi, klik na etiketo pa premakne kazalec na vhod. +Standardno se obrazec izriše kot tabela. Posamezni elementi izpolnjujejo osnovno pravilo dostopnosti - vse oznake so zapisane kot `<label>` in povezane z ustreznim elementom obrazca. Ob kliku na oznako se kazalec samodejno pojavi v polju obrazca. -Za vsak element lahko nastavimo poljubne atribute HTML. Dodajte na primer nadomestno mesto: +Vsakemu elementu lahko nastavljamo poljubne HTML atribute. Na primer dodati placeholder: ```php -$form->addInteger('age', 'Age:') - ->setHtmlAttribute('placeholder', 'Please fill in the age'); +$form->addInteger('age', 'Starost:') + ->setHtmlAttribute('placeholder', 'Prosimo, izpolnite starost'); ``` -V tem poglavju je veliko načinov upodabljanja obrazca, zato je namenjeno prav [upodabljanju |rendering]. +Načinov, kako izrisati obrazec, je res veliko, zato je temu namenjeno [samostojno poglavje o izrisovanju|rendering]. -Prikazovanje v razrede .[#toc-mapping-to-classes] -================================================= +Mapiranje na razrede +==================== -Vrnimo se k metodi `formSucceeded()`, ki v drugem parametru `$data` dobi poslane podatke kot objekt `ArrayHash`. Ker gre za splošen razred, nekaj podobnega kot `stdClass`, bomo pri delu z njim prikrajšani za nekaj udobja, na primer za dopolnjevanje kode za lastnosti v urejevalnikih ali statično analizo kode. To bi lahko rešili tako, da bi za vsak obrazec imeli poseben razred, katerega lastnosti bi predstavljale posamezne kontrole. Npr: +Vrnimo se k metodi `formSucceeded()`, ki v drugem parametru `$data` prejme poslane podatke kot objekt `ArrayHash`. Ker gre za generični razred, nekaj kot `stdClass`, nam bo pri delu z njim manjkalo določeno udobje, kot je na primer predlaganje lastnosti v urejevalnikih ali statična analiza kode. To bi lahko rešili tako, da bi za vsak obrazec imeli konkreten razred, katerega lastnosti predstavljajo posamezne elemente. Npr.: ```php class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -Od različice PHP 8.0 lahko uporabite eleganten zapis, ki uporablja konstruktor: +Alternativno lahko uporabite konstruktor: ```php class RegistrationFormData @@ -246,27 +243,29 @@ class RegistrationFormData } ``` -Kako reči Nette, naj vrne podatke kot predmete tega razreda? Lažje, kot si mislite. Vse, kar morate storiti, je določiti razred kot tip parametra `$data` v izvajalcu: +Lastnosti podatkovnega razreda so lahko tudi enumi in pride do njihovega samodejnega mapiranja. .{data-version:3.2.4} + +Kako povedati Nette, naj nam podatke vrača kot objekte tega razreda? Lažje, kot si mislite. Zadostuje le navesti razred kot tip parametra `$data` v obdelovalni metodi: ```php public function formSucceeded(Form $form, RegistrationFormData $data): void { - // $name je primerek RegistrationFormData + // $name je instanca RegistrationFormData $name = $data->name; // ... } ``` -Kot tip lahko določite tudi `array` in potem bo posredoval podatke kot polje. +Kot tip lahko navedete tudi `array` in potem podatke preda kot polje. -Na podoben način lahko uporabite metodo `getValues()`, ki ji kot parameter posredujemo ime razreda ali predmeta hidrata: +Podobno lahko uporabljate tudi funkcijo `getValues()`, ki ji ime razreda ali objekt za hidracijo predamo kot parameter: ```php $data = $form->getValues(RegistrationFormData::class); $name = $data->name; ``` -Če so obrazci sestavljeni iz večnivojske strukture, sestavljene iz vsebnikov, ustvarite ločen razred za vsakega od njih: +Če obrazci tvorijo večnivojsko strukturo, sestavljeno iz vsebnikov, ustvarite za vsakega samostojen razred: ```php $form = new Form; @@ -283,32 +282,34 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` -Prikazovanje nato na podlagi vrste lastnosti `$person` ugotovi, da mora vsebnik prikazati v razred `PersonFormData`. Če bi lastnost vsebovala polje vsebnikov, navedite tip `array` in razred, ki ga je treba preslikati neposredno na vsebnik: +Mapiranje nato iz tipa lastnosti `$person` prepozna, da mora vsebnik mapirati na razred `PersonFormData`. Če bi lastnost vsebovala polje vsebnikov, navedite tip `array` in razred za mapiranje predajte neposredno vsebniku: ```php $person->setMappedType(PersonFormData::class); ``` +Načrt podatkovnega razreda obrazca si lahko pustite generirati s pomočjo metode `Nette\Forms\Blueprint::dataClass($form)`, ki ga izpiše na stran brskalnika. Kodo nato zadostuje s klikom označiti in kopirati v projekt. .{data-version:3.1.15} -Več gumbov za pošiljanje .[#toc-multiple-submit-buttons] -======================================================== -Če ima obrazec več kot en gumb, moramo običajno razlikovati, kateri je bil pritisnjen. Za vsak gumb lahko ustvarimo svojo funkcijo. Nastavite jo kot izvajalca za [dogodek |nette:glossary#Events] `onClick`: +Več gumbov +========== + +Če ima obrazec več kot en gumb, moramo praviloma razlikovati, kateri od njih je bil pritisnjen. Lahko si za vsak gumb ustvarimo lastno obdelovalno funkcijo. Nastavimo jo kot handler za [dogodek |nette:glossary#Dogodki eventi] `onClick`: ```php -$form->addSubmit('save', 'Save') +$form->addSubmit('save', 'Shrani') ->onClick[] = [$this, 'saveButtonPressed']; -$form->addSubmit('delete', 'Delete') +$form->addSubmit('delete', 'Izbriši') ->onClick[] = [$this, 'deleteButtonPressed']; ``` -Tudi ti obdelavci se kličejo samo v primeru, da je obrazec veljaven, kot v primeru dogodka `onSuccess`. Razlika je v tem, da je lahko prvi parameter objekt gumba za pošiljanje namesto obrazca, odvisno od vrste, ki ste jo določili: +Ti handlerji se kličejo le v primeru veljavno izpolnjenega obrazca, enako kot v primeru dogodka `onSuccess`. Razlika je v tem, da se kot prvi parameter namesto obrazca lahko preda gumb za oddajo, odvisno od tipa, ki ga navedete: ```php public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) @@ -318,62 +319,61 @@ public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) } ``` -Ko je obrazec oddan s tipko <kbd>Enter</kbd>, se obravnava, kot da bi bil oddan s prvim gumbom. +Ko se obrazec odda z gumbom <kbd>Enter</kbd>, se šteje, kot da bi bil oddan s prvim gumbom. -Dogodek onAnchor .[#toc-event-onanchor] -======================================= +Dogodek onAnchor +================ -Ko obrazec sestavite s tovarniško metodo (kot je `createComponentRegistrationForm`), ta še ne ve, ali je bil oddan ali s kakšnimi podatki je bil oddan. Obstajajo pa primeri, ko moramo poznati oddane vrednosti, morda je od njih odvisno, kako bo obrazec videti, ali pa se uporabljajo za odvisna izbirna polja itd. +Ko v tovarni metodi (kot je npr. `createComponentRegistrationForm`) sestavljamo obrazec, ta še ne ve, ali je bil poslan, niti s kakšnimi podatki. So pa primeri, ko poslane vrednosti potrebujemo poznati, na primer se glede na njih odvija nadaljnja podoba obrazca, ali jih potrebujemo za odvisna izbirna polja itd. -Zato lahko kodo, ki zgradi obrazec, pokličete, ko je obrazec zasidran, tj. je že povezan s predstavnikom in pozna njegove oddane podatke. Takšno kodo bomo postavili v polje `$onAnchor`: +Del kode, ki sestavlja obrazec, lahko zato pustite poklicati šele v trenutku, ko je t.i. zasidran, torej je že povezan s presenterjem in pozna svoje poslane podatke. Takšno kodo predamo v polje `$onAnchor`: ```php -$country = $form->addSelect('country', 'Country:', $this->model->getCountries()); -$city = $form->addSelect('city', 'City:'); +$country = $form->addSelect('country', 'Država:', $this->model->getCountries()); +$city = $form->addSelect('city', 'Mesto:'); $form->onAnchor[] = function () use ($country, $city) { - // ta funkcija se pokliče, ko obrazec pozna podatke, s katerimi je bil poslan. - // zato lahko uporabite metodo getValue() + // ta funkcija se pokliče šele, ko bo obrazec vedel, ali je bil poslan in s kakšnimi podatki + // lahko torej uporabljamo metodo getValue() $val = $country->getValue(); - $city->setItems($val ? $this->model->getCities($val): []); + $city->setItems($val ? $this->model->getCities($val) : []); }; ``` -Zaščita pred ranljivostmi .[#toc-vulnerability-protection] -========================================================== +Zaščita pred ranljivostmi +========================= -Okvir Nette si zelo prizadeva biti varen, in ker so obrazci najpogostejši uporabniški vnos, so obrazci Nette tako dobro kot neprepustni. Vse se vzdržuje dinamično in pregledno, ničesar ni treba nastavljati ročno. +Nette Framework daje velik poudarek varnosti in zato skrbno pazi na dobro zavarovanje obrazcev. To počne popolnoma transparentno in ne zahteva ročnega nastavljanja ničesar. -Poleg zaščite obrazcev pred napadi, usmerjenimi v dobro znane ranljivosti, kot sta [Cross-Site Scripting (XSS |nette:glossary#cross-site-scripting-xss] ) in [Cross-Site Request Forgery (CSRF) |nette:glossary#cross-site-request-forgery-csrf], opravi tudi veliko manjših varnostnih opravil, o katerih vam ni treba več razmišljati. +Poleg tega, da obrazce zaščiti pred napadom [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] in [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF], opravlja veliko drobnih zavarovanj, na katere vam ni več treba misliti. -Na primer, iz vnosov filtrira vse kontrolne znake in preverja veljavnost kodiranja UTF-8, tako da bodo podatki iz obrazca vedno čisti. Pri izbirnih poljih in radijskih seznamih preveri, ali so bili izbrani elementi dejansko izmed ponujenih in ni prišlo do ponarejanja. Omenili smo že, da pri enovrstičnem vnosu besedila odstrani znake s konca vrstice, ki bi jih lahko tja poslal napadalec. Pri večvrstičnih vnosih normalizira znake na koncu vrstice. In tako naprej. +Tako na primer iz vnosov odfiltrira vse kontrolne znake in preveri veljavnost UTF-8 kodiranja, tako da bodo podatki iz obrazca vedno čisti. Pri izbirnih poljih in seznamih izbirnih gumbov preverja, da so bile izbrane postavke dejansko iz ponujenih in da ni prišlo do ponarejanja. Že smo omenili, da pri enovrstičnih besedilnih vnosih odstranjuje znake konca vrstic, ki bi jih tja lahko poslal napadalec. Pri večvrstičnih vnosih pa normalizira znake za konce vrstic. In tako naprej. -Nette za vas odpravi varnostne ranljivosti, za katere večina programerjev nima pojma, da obstajajo. +Nette za vas rešuje varnostna tveganja, za katera veliko programerjev niti ne ve, da obstajajo. -Pri omenjenem napadu CSRF gre za to, da napadalec žrtev zvabi k obisku strani, ki v brskalniku žrtve tiho izvrši zahtevo strežniku, v katerega je žrtev trenutno prijavljena, strežnik pa verjame, da je zahtevo po svoji volji izvršila žrtev. Zato Nette prepreči, da bi bil obrazec oddan prek protokola POST iz druge domene. Če iz nekega razloga želite izklopiti zaščito in dovoliti oddajo obrazca iz druge domene, uporabite: +Omenjeni CSRF napad temelji na tem, da napadalec žrtev zvabi na stran, ki neopazno v brskalniku žrtve izvede zahtevo na strežnik, na katerem je žrtev prijavljena, in strežnik domneva, da je zahtevo izvedla žrtev po svoji volji. Zato Nette preprečuje pošiljanje POST obrazca iz druge domene. Če iz kakršnega koli razloga želite zaščito izklopiti in dovoliti pošiljanje obrazca iz druge domene, uporabite: ```php $form->allowCrossOrigin(); // POZOR! Izklopi zaščito! ``` -Ta zaščita uporablja piškotek SameSite z imenom `_nss`. Zaščita s piškotki SameSite morda ni 100-odstotno zanesljiva, zato je dobro vklopiti zaščito s simboli: +Ta zaščita uporablja SameSite piškotek, poimenovan `_nss`. Zaščita s pomočjo SameSite piškotka morda ni 100% zanesljiva, zato je priporočljivo vklopiti še zaščito s pomočjo žetona (token): ```php $form->addProtection(); ``` -To zaščito je zelo priporočljivo uporabiti za obrazce v upravnem delu aplikacije, ki spreminjajo občutljive podatke. Okvir ščiti pred napadom CSRF z generiranjem in potrjevanjem avtentikacijskega žetona, ki je shranjen v seji (argument je sporočilo o napaki, ki se prikaže, če je žeton potekel). Zato je treba pred prikazom obrazca zagnati sejo. V upravljalnem delu spletnega mesta je seja običajno že začeta zaradi prijave uporabnika. -V nasprotnem primeru sejo zaženite z metodo `Nette\Http\Session::start()`. +Priporočamo, da tako zaščitite obrazce v administrativnem delu spletnega mesta, ki spreminjajo občutljive podatke v aplikaciji. Ogrodje se proti napadu CSRF brani z generiranjem in preverjanjem avtorizacijskega žetona, ki se shranjuje v sejo. Zato je treba pred prikazom obrazca imeti odprto sejo. V administrativnem delu spletnega mesta je običajno seja že zagnana zaradi prijave uporabnika. Sicer sejo zaženite z metodo `Nette\Http\Session::start()`. -Uporaba enega obrazca v več prikazovalnikih .[#toc-using-one-form-in-multiple-presenters] -========================================================================================= +Enak obrazec v več presenterjih +=============================== -Če morate en obrazec uporabiti v več kot enem predstavitvenem programu, priporočamo, da zanj ustvarite tovarno, ki jo nato posredujete predstavitvenemu programu. Primerno mesto za tak razred je na primer imenik `app/Forms`. +Če potrebujete en obrazec uporabiti v več presenterjih, priporočamo, da si zanj ustvarite tovarno, ki si jo nato predajte v presenter. Primerna lokacija za tak razred je npr. imenik `app/Forms`. -Tovarniški razred je lahko videti takole: +Tovarniški razred lahko izgleda na primer takole: ```php use Nette\Application\UI\Form; @@ -383,14 +383,14 @@ class SignInFormFactory public function create(): Form { $form = new Form; - $form->addText('name', 'Name:'); - $form->addSubmit('send', 'Log in'); + $form->addText('name', 'Ime:'); + $form->addSubmit('send', 'Prijavi se'); return $form; } } ``` -Razred prosimo, da v tovarniški metodi izdela obrazec za komponente v predstavitvenem programu: +Razred prosimo za izdelavo obrazca v tovarni metodi za komponente v presenterju: ```php public function __construct( @@ -401,14 +401,14 @@ public function __construct( protected function createComponentSignInForm(): Form { $form = $this->formFactory->create(); - // lahko spremenimo obrazec, tukaj na primer spremenimo etiketo na gumbu - $form['login']->setCaption('Continue'); - $form->onSuccess[] = [$this, 'signInFormSubmitted']; // in dodamo izvajalca + // lahko obrazec spremenimo, tukaj na primer spreminjamo napis na gumbu + $form['send']->setCaption('Nadaljuj'); + $form->onSuccess[] = [$this, 'signInFormSuceeded']; // in dodamo handler return $form; } ``` -Obdelovalec za obdelavo obrazca se lahko prav tako dostavi iz tovarne: +Handler za obdelavo obrazca je lahko tudi že dodan iz tovarne: ```php use Nette\Application\UI\Form; @@ -418,14 +418,14 @@ class SignInFormFactory public function create(): Form { $form = new Form; - $form->addText('name', 'Name:'); - $form->addSubmit('send', 'Log in'); + $form->addText('name', 'Ime:'); + $form->addSubmit('send', 'Prijavi se'); $form->onSuccess[] = function (Form $form, $data): void { - // tukaj obdelamo oddani obrazec + // tukaj izvedemo obdelavo obrazca }; return $form; } } ``` -Tako smo na hitro spoznali obrazce v Nette. Za več navdiha si oglejte imenik s [primeri |https://github.com/nette/forms/tree/master/examples] v distribuciji. +Tako, za nami je hiter uvod v obrazce v Nette. Poskusite še pogledati v imenik [examples|https://github.com/nette/forms/tree/master/examples] v distribuciji, kjer najdete dodatno inspiracijo. diff --git a/forms/sl/rendering.texy b/forms/sl/rendering.texy index 680af79ddd..e12a057593 100644 --- a/forms/sl/rendering.texy +++ b/forms/sl/rendering.texy @@ -1,33 +1,35 @@ -Oblikovanje obrazcev +Izrisovanje obrazcev ******************** -Videz oblik je lahko zelo raznolik. V praksi lahko naletimo na dve skrajnosti. Po eni strani je treba v aplikaciji prikazati vrsto obrazcev, ki so si vizualno podobni, pri čemer cenimo enostavno prikazovanje brez predloge z uporabo spletne strani `$form->render()`. To je običajno primer upravnih vmesnikov. +Videz obrazcev je lahko zelo raznolik. V praksi lahko naletimo na dva ekstrema. Na eni strani stoji potreba po izrisovanju številnih obrazcev v aplikaciji, ki so si vizualno podobni kot jajce jajcu, in cenimo enostavno izrisovanje brez predloge s pomočjo `$form->render()`. Gre običajno za primer administrativnih vmesnikov. -Po drugi strani pa obstajajo različni obrazci, pri katerih je vsak izmed njih edinstven. Njihov videz je najbolje opisati s pomočjo jezika HTML v predlogi. In seveda bomo poleg obeh omenjenih skrajnosti naleteli na številne obrazce, ki so nekje vmes. +Na drugi strani pa so raznoliki obrazci, kjer velja: vsak kos je original. Njihovo podobo najbolje opišemo z jezikom HTML v predlogi obrazca. In seveda poleg obeh omenjenih ekstremov naletimo na veliko obrazcev, ki se gibljejo nekje vmes. -Renderiranje z Latte .[#toc-rendering-with-latte] -================================================= +Izrisovanje s pomočjo Latte +=========================== - [Sistem predlog Latte |latte:] bistveno olajša upodabljanje obrazcev in njihovih elementov. Najprej bomo pokazali, kako ročno upodabljati obrazce, element za elementom, da bi pridobili popoln nadzor nad kodo. Kasneje bomo pokazali, kako takšno upodabljanje [avtomatizirati |#Automatic rendering]. +[Sistem predlog Latte|latte:] bistveno olajša izrisovanje obrazcev in njihovih elementov. Najprej si bomo pokazali, kako obrazce izrisovati ročno po posameznih elementih in s tem pridobiti popoln nadzor nad kodo. Kasneje si bomo pokazali, kako je mogoče takšno izrisovanje [avtomatizirati |#Samodejno izrisovanje]. + +Načrt Latte predloge obrazca si lahko pustite generirati s pomočjo metode `Nette\Forms\Blueprint::latte($form)`, ki ga izpiše na stran brskalnika. Kodo nato zadostuje s klikom označiti in kopirati v projekt. .{data-version:3.1.15} `{control}` ----------- -Obrazec najlažje prikažete tako, da ga zapišete v predlogo: +Najenostavnejši način, kako izrisati obrazec, je napisati v predlogi: ```latte {control signInForm} ``` -Videz izrisanega obrazca lahko spremenite z nastavitvijo [Rendererja |#Renderer] in [posameznih kontrolnih elementov |#HTML Attributes]. +Vplivati na podobo tako izrisanega obrazca je mogoče s konfiguracijo [Rendererja |#Renderer] in [posameznih elementov |#HTML atributi]. `n:name` -------- -Definicijo obrazca v kodi PHP je zelo enostavno povezati s kodo HTML. Preprosto dodajte atribute `n:name`. Tako enostavno je to! +Definicijo obrazca v PHP kodi je mogoče izjemno enostavno povezati s HTML kodo. Zadostuje le dopolniti atribute `n:name`. Tako je enostavno! ```php protected function createComponentSignInForm(): Form @@ -54,10 +56,9 @@ protected function createComponentSignInForm(): Form </form> ``` -Videz nastale kode HTML je povsem v vaših rokah. Če uporabite atribut `n:name` z `<select>`, `<button>` ali `<textarea>` elementi, se njihova notranja vsebina samodejno izpolni. -Poleg tega je `<form n:name>` oznaka ustvari lokalno spremenljivko `$form` z narisanim objektom obrazca in zaključnim `</form>` izriše vse neizrisane skrite elemente (enako velja za `{form} ... {/form}`). +Podobo končne HTML kode imate popolnoma v svojih rokah. Če atribut `n:name` uporabite pri elementih `<select>`, `<button>` ali `<textarea>`, se njihova notranja vsebina samodejno dopolni. Oznaka `<form n:name>` poleg tega ustvari lokalno spremenljivko `$form` z objektom risanega obrazca in zaključna `</form>` izriše vse neizrisane skrite elemente (enako velja tudi za `{form} ... {/form}`). -Ne smemo pa pozabiti na izris morebitnih sporočil o napakah. Tako tistih, ki so bila posameznim elementom dodana z metodo `addError()` (z uporabo `{inputError}`), kot tistih, ki so bila dodana neposredno v obrazec (vrnjena z `$form->getOwnErrors()`): +Ne smemo pa pozabiti na izrisovanje morebitnih sporočil o napakah. In to tako tistih, ki so se z metodo `addError()` dodala k posameznim elementom (s pomočjo `{inputError}`), kot tudi tistih, dodanih neposredno k obrazcu (vrača jih `$form->getOwnErrors()`): ```latte <form n:name=signInForm class=form> @@ -79,7 +80,7 @@ Ne smemo pa pozabiti na izris morebitnih sporočil o napakah. Tako tistih, ki so </form> ``` -Kompleksnejše elemente obrazca, kot sta RadioList ali CheckboxList, lahko prikažemo element za elementom: +Bolj zapletene elemente obrazca, kot sta RadioList ali CheckboxList, je mogoče tako izrisovati po posameznih postavkah: ```latte {foreach $form[gender]->getItems() as $key => $label} @@ -88,16 +89,10 @@ Kompleksnejše elemente obrazca, kot sta RadioList ali CheckboxList, lahko prika ``` -Predlog kode `{formPrint}` .[#toc-formprint] --------------------------------------------- - -Podobno kodo Latte lahko ustvarite za obrazec z uporabo oznake `{formPrint}`. Če jo vstavite v predlogo, se bo namesto običajnega izrisa prikazal predlog kode. Nato jo preprosto izberite in kopirajte v svoj projekt. - - `{label}` `{input}` ------------------- -Ali ne želite za vsak element razmisliti, kateri element HTML uporabiti zanj v predlogi, ali `<input>`, `<textarea>` itd. Rešitev je univerzalna oznaka `{input}`: +Ne želite pri vsakem elementu razmišljati, kateri HTML element zanj uporabiti v predlogi, ali `<input>`, `<textarea>` itd.? Rešitev je univerzalna oznaka `{input}`: ```latte <form n:name=signInForm class=form> @@ -119,9 +114,9 @@ Ali ne želite za vsak element razmisliti, kateri element HTML uporabiti zanj v </form> ``` -Če obrazec uporablja prevajalnik, bo besedilo znotraj značk `{label}` prevedeno. +Če obrazec uporablja prevajalnik|, bo besedilo znotraj oznak `{label}` prevedeno. -Tudi v tem primeru se lahko kompleksnejši elementi obrazca, kot sta RadioList ali CheckboxList, preslikajo po posameznih elementih: +Tudi v tem primeru je mogoče bolj zapletene elemente obrazca, kot sta RadioList ali CheckboxList, izrisovati po posameznih postavkah: ```latte {foreach $form[gender]->items as $key => $label} @@ -129,20 +124,19 @@ Tudi v tem primeru se lahko kompleksnejši elementi obrazca, kot sta RadioList a {/foreach} ``` -Če želite prikazati element `<input>` samega elementa Checkbox, uporabite `{input myCheckbox:}`. Atributi HTML morajo biti ločeni z vejico `{input myCheckbox:, class: required}`. +Za izrisovanje samega `<input>` v elementu Checkbox uporabite `{input myCheckbox:}`. HTML atribute v tem primeru vedno ločujte z vejico `{input myCheckbox:, class: required}`. `{inputError}` -------------- -Izpiše sporočilo o napaki za element obrazca, če ga vsebuje. Sporočilo je običajno zavito v element HTML za oblikovanje. -Izogibanje izrisu praznega elementa, če ni sporočila, je mogoče elegantno opraviti s `n:ifcontent`: +Izpiše sporočilo o napaki k elementu obrazca, če ga ima. Sporočilo običajno zavijemo v HTML element zaradi stiliranja. Preprečiti izrisovanje praznega elementa, če sporočila ni, je mogoče elegantno s pomočjo `n:ifcontent`: ```latte <span class=error n:ifcontent>{inputError $input}</span> ``` -Z metodo `hasErrors()` lahko zaznamo prisotnost napake in ustrezno nastavimo razred nadrejenega elementa: +Prisotnost napake lahko ugotovimo z metodo `hasErrors()` in glede na to nastavimo razred nadrejenemu elementu: ```latte <div n:class="$form[username]->hasErrors() ? 'error'"> @@ -155,14 +149,13 @@ Z metodo `hasErrors()` lahko zaznamo prisotnost napake in ustrezno nastavimo raz `{form}` -------- -Oznake `{form signInForm}...{/form}` so alternativa za `<form n:name="signInForm">...</form>`. +Oznake `{form signInForm}...{/form}` so alternativa k `<form n:name="signInForm">...</form>`. -Samodejno upodabljanje .[#toc-automatic-rendering] --------------------------------------------------- +Samodejno izrisovanje +--------------------- -Z oznakama `{input}` in `{label}` lahko preprosto ustvarimo splošno predlogo za kateri koli obrazec. Ta bo iterirala in zaporedno prikazala vse elemente, razen skritih elementov, ki se samodejno prikažejo, ko se obrazec zaključi z oznako `</form>` z oznako. -V spremenljivki `$form` bo pričakovala ime izrisanega obrazca. +Zahvaljujoč oznakam `{input}` in `{label}` lahko enostavno ustvarimo splošno predlogo za kateri koli obrazec. Postopoma bo iterirala in izrisovala vse njegove elemente, razen skritih elementov, ki se izrišejo samodejno ob zaključku obrazca z oznako `</form>`. Ime izrisovanega obrazca bo pričakovala v spremenljivki `$form`. ```latte <form n:name=$form class=form> @@ -179,16 +172,15 @@ V spremenljivki `$form` bo pričakovala ime izrisanega obrazca. </form> ``` -Uporabljene samozapiralne parne oznake `{label .../}` prikazujejo oznake, ki izhajajo iz definicije obrazca v kodi PHP. +Uporabljene samozaključujoče parne oznake `{label .../}` prikazujejo oznake, ki izhajajo iz definicije obrazca v PHP kodi. -To splošno predlogo lahko shranite v datoteko `basic-form.latte`, za prikaz obrazca pa jo samo vključite in v parameter `$form` posredujete ime (ali primerek) obrazca: +To splošno predlogo shranite na primer v datoteko `basic-form.latte` in za izrisovanje obrazca jo zadostuje vključiti ter predati ime (ali instanco) obrazca v parameter `$form`: ```latte {include basic-form.latte, form: signInForm} ``` -Če bi radi vplivali na videz posameznega obrazca in en element izrisali drugače, potem je najlažje, da v predlogi pripravite bloke, ki jih lahko pozneje prepišete. -Bloki imajo lahko tudi [dinamična imena |latte:template-inheritance#dynamic-block-names], zato lahko vanje vstavite ime elementa, ki ga želite narisati. Na primer: +Če bi pri izrisovanju enega določenega obrazca želeli poseči v njegovo podobo in na primer en element izrisati drugače, potem je najenostavnejša pot, da si v predlogi predpripravite bloke, ki jih bo mogoče kasneje prepisati. Bloki imajo lahko tudi [dinamična imena |latte:template-inheritance#Dinamična imena blokov], vanje je tako mogoče vstaviti tudi ime izrisovanega elementa. Na primer: ```latte ... @@ -197,7 +189,7 @@ Bloki imajo lahko tudi [dinamična imena |latte:template-inheritance#dynamic-blo ... ``` -Za element npr. `username` to ustvari blok `input-username`, ki ga lahko enostavno spremenite z uporabo oznake [{embed} |latte:template-inheritance#unit-inheritance]: +Za element npr. `username` tako nastane blok `input-username`, ki ga je mogoče enostavno prepisati z uporabo oznake [{embed} |latte:template-inheritance#Dedovanje enot]: ```latte {embed basic-form.latte, form: signInForm} @@ -209,7 +201,7 @@ Za element npr. `username` to ustvari blok `input-username`, ki ga lahko enostav {/embed} ``` -Druga možnost je, da se celotna vsebina predloge `basic-form.latte` [opredeli |latte:template-inheritance#definitions] kot blok, vključno s parametrom `$form`: +Alternativno je mogoče celotno vsebino predloge `basic-form.latte` [definirati |latte:template-inheritance#Definicije] kot blok, vključno s parametrom `$form`: ```latte {define basic-form, $form} @@ -219,7 +211,7 @@ Druga možnost je, da se celotna vsebina predloge `basic-form.latte` [opredeli | {/define} ``` -Tako bo uporaba nekoliko lažja: +Zahvaljujoč temu bo njegov klic nekoliko enostavnejši: ```latte {embed basic-form, signInForm} @@ -227,31 +219,31 @@ Tako bo uporaba nekoliko lažja: {/embed} ``` -Blok morate uvoziti le na enem mestu, na začetku predloge za postavitev: +Blok pri tem zadostuje uvoziti na enem samem mestu in to na začetku predloge postavitve: ```latte {import basic-form.latte} ``` -Posebni primeri .[#toc-special-cases] -------------------------------------- +Posebni primeri +--------------- -Če morate prikazati samo notranjo vsebino obrazca brez `<form>` & `</form>` HTML, na primer v zahtevi AJAX, lahko obrazec odprete in zaprete s `{formContext} … {/formContext}`. V logičnem smislu deluje podobno kot `{form}`, tu vam omogoča uporabo drugih oznak za izris elementov obrazca, vendar hkrati ne izrisuje ničesar. +Če potrebujete izrisati le notranji del obrazca brez HTML oznak `<form>`, na primer pri pošiljanju odrezkov (snippetov), jih skrijte s pomočjo atributa `n:tag-if`: ```latte -{formContext signForm} +<form n:name=signInForm n:tag-if=false> <div> <label n:name=username>Username: <input n:name=username></label> {inputError username} </div> -{/formContext} +</form> ``` -Oznaka `formContainer` pomaga pri izrisovanju vnosov znotraj vsebnika obrazca. +Z izrisovanjem elementov znotraj vsebnika obrazca pomaga oznaka `{formContainer}`. ```latte -<p>Which news you wish to receive:</p> +<p>Katere novice želite prejemati:</p> {formContainer emailNews} <ul> @@ -262,27 +254,27 @@ Oznaka `formContainer` pomaga pri izrisovanju vnosov znotraj vsebnika obrazca. ``` -Renderiranje brez Latte .[#toc-rendering-without-latte] -======================================================= +Izrisovanje brez Latte +====================== -Obrazec najlažje prikažete tako, da pokličete: +Najenostavnejši način, kako izrisati obrazec, je poklicati: ```php $form->render(); ``` -Videz izrisanega obrazca lahko spremenite z nastavitvijo [Rendererja |#Renderer] in [posameznih kontrolnih elementov |#HTML Attributes]. +Vplivati na podobo tako izrisanega obrazca je mogoče s konfiguracijo [Rendererja |#Renderer] in [posameznih elementov |#HTML atributi]. -Ročno upodabljanje .[#toc-manual-rendering] -------------------------------------------- +Ročno izrisovanje +----------------- -Vsak element obrazca ima metode, ki generirajo kodo HTML za polje in oznako obrazca. Vrnejo jo lahko kot niz ali objekt [Nette\Utils\Html |utils:html-elements]: +Vsak element obrazca ima metode, ki generirajo HTML kodo polja obrazca in oznake. Lahko jo vračajo bodisi kot niz ali objekt [Nette\Utils\Html|utils:html-elements]: -- `getControl(): Html|string` vrne kodo HTML elementa -- `getLabel($caption = null): Html|string|null` vrne kodo HTML oznake, če obstaja +- `getControl(): Html|string` vrne HTML kodo elementa +- `getLabel($caption = null): Html|string|null` vrne HTML kodo oznake, če obstaja -To omogoča prikaz obrazca po posameznih elementih: +Obrazec je tako mogoče izrisovati po posameznih elementih: ```php <?php $form->render('begin') ?> @@ -305,47 +297,46 @@ To omogoča prikaz obrazca po posameznih elementih: <?php $form->render('end') ?> ``` -Medtem ko za nekatere elemente `getControl()` vrne en sam element HTML (npr. `<input>`, `<select>` itd.), za druge vrne celoten del kode HTML (CheckboxList, RadioList). -V tem primeru lahko uporabite metode, ki generirajo posamezne vhode in oznake, za vsak element posebej: +Medtem ko pri nekaterih elementih `getControl()` vrne en sam HTML element (npr. `<input>`, `<select>` ipd.), pri drugih cel kos HTML kode (CheckboxList, RadioList). V takem primeru lahko uporabite metode, ki generirajo posamezne vnose in oznake, za vsako postavko posebej: -- `getControlPart($key = null): ?Html` vrne kodo HTML posameznega elementa -- `getLabelPart($key = null): ?Html` vrne kodo HTML za oznako posameznega elementa +- `getControlPart($key = null): ?Html` vrne HTML kodo ene postavke +- `getLabelPart($key = null): ?Html` vrne HTML kodo oznake ene postavke .[note] -Tem metodam je iz zgodovinskih razlogov dodana predpona `get`, vendar bi bila `generate` boljša, saj ob vsakem klicu ustvari in vrne nov element `Html`. +Te metode imajo iz zgodovinskih razlogov predpono `get`, vendar bi bil boljši `generate`, ker pri vsakem klicu ustvari in vrne nov element `Html`. -Renderer .[#toc-renderer] -========================= +Renderer +======== -To je objekt, ki zagotavlja upodabljanje obrazca. Nastavite ga lahko z metodo `$form->setRenderer`. Ob klicu metode `$form->render()` se mu posreduje nadzor. +Gre za objekt, ki zagotavlja izrisovanje obrazca. Tega je mogoče nastaviti z metodo `$form->setRenderer`. Njemu se preda nadzor ob klicu metode `$form->render()`. -Če ne nastavimo lastnega upodabljavca, bo uporabljen privzeti upodabljavec [api:Nette\Forms\Rendering\DefaultFormRenderer]. Ta bo elemente obrazca prikazal kot tabelo HTML. Izpis je videti takole: +Če ne nastavimo lastnega rendererja, bo uporabljen privzeti izrisovalnik [api:Nette\Forms\Rendering\DefaultFormRenderer]. Ta elemente obrazca izriše v obliki HTML tabele. Izhod izgleda takole: ```latte <table> <tr class="required"> - <th><label class="required" for="frm-name">Name:</label></th> + <th><label class="required" for="frm-name">Ime:</label></th> <td><input type="text" class="text" name="name" id="frm-name" required value=""></td> </tr> <tr class="required"> - <th><label class="required" for="frm-age">Age:</label></th> + <th><label class="required" for="frm-age">Starost:</label></th> <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> </tr> <tr> - <th><label>Gender:</label></th> + <th><label>Spol:</label></th> ... ``` -Mnogi spletni oblikovalci imajo raje drugačno označevanje, na primer seznam. Spletno mesto `DefaultFormRenderer` lahko konfiguriramo tako, da se sploh ne bo izrisalo v tabelo. Nastaviti moramo le ustrezne [ovitke $wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers]. Prvi indeks vedno predstavlja območje, drugi pa element. Vsa ustrezna območja so prikazana na sliki: +Ali uporabiti ali ne uporabiti za ogrodje obrazca tabelo je sporno in vrsta spletnih oblikovalcev preferira drugačen markup. Na primer definicijski seznam. Prekonfiguriramo zato `DefaultFormRenderer` tako, da obrazec izriše v obliki seznama. Konfiguracija se izvaja z urejanjem polja [$wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers]. Prvi indeks vedno predstavlja območje in drugi njegov atribut. Posamezna območja ponazarja slika: -[* form-areas-en.webp *] +[* defaultformrenderer.webp *] -Privzeto je skupina `controls` ovita v `<table>`, vsak naslov `pair` pa je vrstica tabele. `<tr>` ki vsebuje par `label` in `control` (celice `<th>` in `<td>`). Spremenimo vse te ovijalne elemente. `controls` bomo zavili v `<dl>`, `pair` bomo pustili samega, `label` bomo vstavili v `<dt>` in zavijemo `control` v `<dd>`: +Standardno je skupina elementov `controls` ovita s tabelo `<table>`, vsak `pair` predstavlja vrstico tabele `<tr>` in par `label` ter `control` sta celici `<th>` in `<td>`. Zdaj ovojne elemente spremenimo. Območje `controls` vstavimo v vsebnik `<dl>`, območje `pair` pustimo brez vsebnika, `label` vstavimo v `<dt>` in na koncu `control` ovijemo z oznakami `<dd>`: ```php $renderer = $form->getRenderer(); @@ -357,193 +348,192 @@ $renderer->wrappers['control']['container'] = 'dd'; $form->render(); ``` -Rezultat je naslednji delček: +Rezultat je ta HTML koda: ```latte <dl> - <dt><label class="required" for="frm-name">Name:</label></dt> + <dt><label class="required" for="frm-name">Ime:</label></dt> <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> - <dt><label class="required" for="frm-age">Age:</label></dt> + <dt><label class="required" for="frm-age">Starost:</label></dt> <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> - <dt><label>Gender:</label></dt> + <dt><label>Spol:</label></dt> ... </dl> ``` -Obalčki lahko vplivajo na številne atribute. Na primer: +V polju wrappers je mogoče vplivati na celo vrsto drugih atributov: -- dodajte posebne razrede CSS vsakemu vnosu obrazca -- razlikovanje med lihimi in sodimi vrsticami -- poskrbite, da se obvezni in neobvezni elementi rišejo drugače. -- določite, ali se sporočila o napakah prikazujejo nad obrazcem ali blizu vsakega elementa +- dodajati CSS razrede posameznim tipom elementov obrazca +- razlikovati CSS razred lihih in sodih vrstic +- vizualno ločiti obvezne in neobvezne postavke +- določati, ali se sporočila o napakah prikažejo neposredno pri elementih ali nad obrazcem -Možnosti .[#toc-options] ------------------------- +Možnosti (Options) +------------------ -Obnašanje programa Renderer lahko nadzorujete tudi z nastavitvijo *opcij* na posameznih elementih obrazca. Tako lahko nastavite namig, ki se prikaže ob vnosnem polju: +Obnašanje Rendererja je mogoče nadzorovati tudi z nastavljanjem *options* na posameznih elementih obrazca. Tako je mogoče nastaviti napis, ki se izpiše poleg vnosnega polja: ```php -$form->addText('phone', 'Number:') - ->setOption('description', 'This number will remain hidden'); +$form->addText('phone', 'Številka:') + ->setOption('description', 'Ta številka bo ostala skrita'); ``` -Če želimo vanj vstaviti vsebino HTML, uporabimo razred [Html |utils:html-elements]. +Če vanj želimo umestiti HTML vsebino, uporabimo razred [Html |utils:html-elements] ```php use Nette\Utils\Html; -$form->addText('phone', 'Phone:') +$form->addText('phone', 'Številka:') ->setOption('description', Html::el('p') - ->setHtml('<a href="...">Terms of service.</a>') + ->setHtml('<a href="...">Pogoji shranjevanja Vaše številke</a>') ); ``` .[tip] -Element Html lahko uporabimo tudi namesto etikete: `$form->addCheckbox('conditions', $label)`. +Html element je mogoče uporabiti tudi namesto oznake: `$form->addCheckbox('conditions', $label)`. -Združevanje vhodnih podatkov v skupine .[#toc-grouping-inputs] --------------------------------------------------------------- +Združevanje elementov +--------------------- -Renderer omogoča združevanje elementov v vizualne skupine (fieldsets): +Renderer omogoča združevanje elementov v vizualne skupine (fieldsete): ```php -$form->addGroup('Personal data'); +$form->addGroup('Osebni podatki'); ``` -Ustvarjanje nove skupine jo aktivira - vsi elementi, ki so dodani naprej, so dodani v to skupino. Obrazec lahko sestavite takole: +Po ustvarjanju nove skupine ta postane aktivna in vsak na novo dodan element je hkrati dodan tudi vanjo. Tako je mogoče obrazec graditi na ta način: ```php $form = new Form; -$form->addGroup('Personal data'); -$form->addText('name', 'Your name:'); -$form->addInteger('age', 'Your age:'); -$form->addEmail('email', 'Email:'); +$form->addGroup('Osebni podatki'); +$form->addText('name', 'Vaše ime:'); +$form->addInteger('age', 'Vaša starost:'); +$form->addEmail('email', 'E-pošta:'); -$form->addGroup('Shipping address'); -$form->addCheckbox('send', 'Ship to address'); -$form->addText('street', 'Street:'); -$form->addText('city', 'City:'); -$form->addSelect('country', 'Country:', $countries); +$form->addGroup('Naslov za dostavo'); +$form->addCheckbox('send', 'Pošlji na naslov'); +$form->addText('street', 'Ulica:'); +$form->addText('city', 'Mesto:'); +$form->addSelect('country', 'Država:', $countries); ``` +Renderer najprej izrisuje skupine in šele nato elemente, ki ne pripadajo nobeni skupini. -Podpora Bootstrap .[#toc-bootstrap-support] -------------------------------------------- -Najdete lahko [primere |https://github.com/nette/forms/tree/master/examples] konfiguracije Rendererja za [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], [Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] in [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php] +Podpora za Bootstrap +-------------------- +[V primerih |https://github.com/nette/forms/tree/master/examples] najdete primere, kako konfigurirati Renderer za [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], [Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] in [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php] -Atributi HTML .[#toc-html-attributes] -===================================== -Z uporabo spletne strani `setHtmlAttribute(string $name, $value = true)` lahko kontrolnim elementom obrazca nastavite poljubne atribute HTML: +HTML atributi +============= + +Za nastavitev poljubnih HTML atributov elementov obrazca uporabimo metodo `setHtmlAttribute(string $name, $value = true)`: ```php -$form->addInteger('number', 'Number:') +$form->addInteger('number', 'Število:') ->setHtmlAttribute('class', 'big-number'); -$form->addSelect('rank', 'Order by:', ['price', 'name']) - ->setHtmlAttribute('onchange', 'submit()'); // ob spremembi pokliče funkcijo JS submit(). +$form->addSelect('rank', 'Razvrsti po:', ['ceni', 'nazivu']) + ->setHtmlAttribute('onchange', 'submit()'); // ob spremembi pošlji -// uporaba na <form> +// Za nastavitev atributov samega <form> $form->setHtmlAttribute('id', 'myForm'); ``` -Nastavitev vrste vnosa: +Specifikacija tipa elementa: ```php -$form->addText('tel', 'Your telephone:') +$form->addText('tel', 'Vaš telefon:') ->setHtmlType('tel') - ->setHtmlAttribute('placeholder', 'Please, fill in your telephone'); + ->setHtmlAttribute('placeholder', 'napišite telefon'); ``` -V seznamih z radijskimi ali potrditvenimi polji lahko posameznim elementom nastavimo atribut HTML z različnimi vrednostmi za vsakega od njih. -Upoštevajte dvopičje za `style:`, ki zagotavlja, da je vrednost izbrana po ključu: +.[warning] +Nastavitev tipa in drugih atributov služi le za vizualne namene. Preverjanje pravilnosti vnosov mora potekati na strežniku, kar zagotovite z izbiro ustreznega [elementa obrazca|controls] in navedbo [validacijskih pravil|validation]. + +Posameznim postavkam v seznamih izbirnih gumbov ali potrditvenih polj lahko nastavimo HTML atribut z različnimi vrednostmi za vsako od njih. Opazite dvopičje za `style:`, ki zagotovi izbiro vrednosti glede na ključ: ```php -$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue']; +$colors = ['r' => 'rdeča', 'g' => 'zelena', 'b' => 'modra']; $styles = ['r' => 'background:red', 'g' => 'background:green']; -$form->addCheckboxList('colors', 'Colors:', $colors) +$form->addCheckboxList('colors', 'Barve:', $colors) ->setHtmlAttribute('style:', $styles); ``` -Renders: +Izpiše: ```latte -<label><input type="checkbox" name="colors[]" style="background:red" value="r">red</label> -<label><input type="checkbox" name="colors[]" style="background:green" value="g">green</label> -<label><input type="checkbox" name="colors[]" value="b">blue</label> +<label><input type="checkbox" name="colors[]" style="background:red" value="r">rdeča</label> +<label><input type="checkbox" name="colors[]" style="background:green" value="g">zelena</label> +<label><input type="checkbox" name="colors[]" value="b">modra</label> ``` -Za logični atribut HTML (ki nima vrednosti, na primer `readonly`), lahko uporabite vprašalnik: +Za nastavitev logičnih atributov, kot je `readonly`, lahko uporabimo zapis z vprašajem: ```php -$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue']; -$form->addCheckboxList('colors', 'Colors:', $colors) - ->setHtmlAttribute('readonly?', 'r'); // uporabite polje za več ključev, npr. ['r', 'g'] +$form->addCheckboxList('colors', 'Barve:', $colors) + ->setHtmlAttribute('readonly?', 'r'); // za več ključev uporabite polje, npr. ['r', 'g'] ``` -Renders: +Izpiše: ```latte -<label><input type="checkbox" name="colors[]" readonly value="r">red</label> -<label><input type="checkbox" name="colors[]" value="g">green</label> -<label><input type="checkbox" name="colors[]" value="b">blue</label> +<label><input type="checkbox" name="colors[]" readonly value="r">rdeča</label> +<label><input type="checkbox" name="colors[]" value="g">zelena</label> +<label><input type="checkbox" name="colors[]" value="b">modra</label> ``` -Pri izbirnih poljih metoda `setHtmlAttribute()` nastavi atribute `<select>` elementa. Če želimo nastaviti atribute za vsako -`<option>`, bomo uporabili metodo `setOptionAttribute()`. Tudi zgoraj uporabljena dvopičje in vprašalno znamenje delujeta: +V primeru izbirnih polj (selectbox) metoda `setHtmlAttribute()` nastavlja atribute elementa `<select>`. Če želimo nastaviti atribute posameznim `<option>`, uporabimo metodo `setOptionAttribute()`. Delujejo tudi zapisi z dvopičjem in vprašajem, navedeni zgoraj: ```php -$form->addSelect('colors', 'Colors:', $colors) +$form->addSelect('colors', 'Barve:', $colors) ->setOptionAttribute('style:', $styles); ``` -Renders: +Izpiše: ```latte <select name="colors"> - <option value="r" style="background:red">red</option> - <option value="g" style="background:green">green</option> - <option value="b">blue</option> + <option value="r" style="background:red">rdeča</option> + <option value="g" style="background:green">zelena</option> + <option value="b">modra</option> </select> ``` -Prototipi .[#toc-prototypes] ----------------------------- +Prototipi +--------- -Alternativni način določanja atributov HTML je spreminjanje predloge, iz katere se ustvari element HTML. Predloga je predmet `Html` in jo vrne metoda `getControlPrototype()`: +Alternativni način nastavljanja HTML atributov temelji na urejanju predloge, iz katere se HTML element generira. Predloga je objekt `Html` in jo vrača metoda `getControlPrototype()`: ```php -$input = $form->addInteger('number'); +$input = $form->addInteger('number', 'Število:'); $html = $input->getControlPrototype(); // <input> $html->class('big-number'); // <input class="big-number"> ``` -Na ta način je mogoče spremeniti tudi predlogo za etiketo, ki jo vrne metoda `getLabelPrototype()`: +Na ta način je mogoče modificirati tudi predlogo oznake, ki jo vrača `getLabelPrototype()`: ```php $html = $input->getLabelPrototype(); // <label> $html->class('distinctive'); // <label class="distinctive"> ``` -Za elemente Checkbox, CheckboxList in RadioList lahko vplivate na predlogo elementa, ki obdaja element. Vrne jo `getContainerPrototype()`. Privzeto je to "prazen" element, zato se ne prikaže nič, vendar se bo s podelitvijo imena prikazal: +Pri elementih Checkbox, CheckboxList in RadioList lahko vplivate na predlogo elementa, ki celoten element ovija. Vrača jo `getContainerPrototype()`. V privzetem stanju gre za „prazen“ element, tako da se nič ne izrisuje, a s tem, da mu nastavimo ime, se bo izrisoval: ```php $input = $form->addCheckbox('send'); -echo $input->getControl(); -// <label><input type="checkbox" name="send"></label> - $html = $input->getContainerPrototype(); $html->setName('div'); // <div> $html->class('check'); // <div class="check"> @@ -551,50 +541,49 @@ echo $input->getControl(); // <div class="check"><label><input type="checkbox" name="send"></label></div> ``` -V primeru CheckboxList in RadioList je mogoče vplivati tudi na vzorec ločevalnika elementov, ki ga vrne metoda `getSeparatorPrototype()`. Privzeto je to element `<br>`. Če ga spremenite v parni element, bo zavil posamezne elemente, namesto da bi jih ločil. -Prav tako je mogoče vplivati na predlogo elementa HTML za oznake elementov, ki jo vrne metoda `getItemLabelPrototype()`. +V primeru CheckboxList in RadioList lahko vplivate tudi na predlogo ločila posameznih postavk, ki ga vrača metoda `getSeparatorPrototype()`. V privzetem stanju je to element `<br>`. Če ga spremenite v parni element, bo posamezne postavke ovijal namesto ločeval. In dalje lahko vplivate na predlogo HTML elementa oznake pri posameznih postavkah, ki ga vrača `getItemLabelPrototype()`. -Prevajanje .[#toc-translating] -============================== +Prevajanje +========== -Če programirate večjezično aplikacijo, boste verjetno morali obrazec prikazati v različnih jezikih. Okvir Nette v ta namen opredeljuje vmesnik za prevajanje [api:Nette\Localization\Translator]. V Nette ni privzete implementacije, glede na svoje potrebe lahko izbirate med več pripravljenimi rešitvami, ki jih najdete na portalu [Componette |https://componette.org/search/localization]. V njihovi dokumentaciji je opisano, kako konfigurirati prevajalnik. +Če programirate večjezično aplikacijo, boste verjetno potrebovali obrazec izrisati v različnih jezikovnih mutacijah. Nette Framework za ta namen definira vmesnik za prevajanje [api:Nette\Localization\Translator]. V Nette ni nobene privzete implementacije, lahko si izberete glede na svoje potrebe iz več pripravljenih rešitev, ki jih najdete na [Componette |https://componette.org/search/localization]. V njihovi dokumentaciji boste izvedeli, kako prevajalnik konfigurirati. -Obrazec podpira izpis besedila prek prevajalnika. Predamo ga z uporabo metode `setTranslator()`: +Obrazci podpirajo izpisovanje besedil preko prevajalnika. Predamo jim ga s pomočjo metode `setTranslator()`: ```php $form->setTranslator($translator); ``` -Odslej bodo v drug jezik prevedene ne le vse oznake, temveč tudi vsa sporočila o napakah ali vnosi v izbirna polja. +Od te točke naprej se ne le vse oznake, ampak tudi vsa sporočila o napakah ali postavke izbirnih polj prevedejo v drug jezik. -Za posamezne elemente obrazca je mogoče nastaviti drugačen prevajalnik ali popolnoma onemogočiti prevajanje s `null`: +Pri posameznih elementih obrazca je pri tem mogoče nastaviti drug prevajalnik ali prevajanje popolnoma izklopiti z vrednostjo `null`: ```php $form->addSelect('carModel', 'Model:', $cars) ->setTranslator(null); ``` -Pri [pravilih potrjevanja |validation] se prevajalniku posredujejo tudi določeni parametri, na primer za pravilo: +Pri [validacijskih pravilih|validation] se prevajalniku predajajo tudi specifični parametri, na primer pri pravilu: ```php -$form->addPassword('password', 'Password:') - ->addRule($form::MinLength, 'Password has to be at least %d characters long', 8) +$form->addPassword('password', 'Geslo:') + ->addRule($form::MinLength, 'Geslo mora imeti vsaj %d znakov', 8); ``` -se prevajalnik pokliče z naslednjimi parametri: +se kliče prevajalnik s temi parametri: ```php -$translator->translate('Password has to be at least %d characters long', 8); +$translator->translate('Geslo mora imeti vsaj %d znakov', 8); ``` -in tako lahko izbere pravilno množinsko obliko za besedo `characters` po številu. +in torej lahko izbere pravilno obliko množine pri besedi `znakov` glede na število. -Dogodek onRender .[#toc-event-onrender] -======================================= +Dogodek onRender +================ -Tik preden se obrazec izriše, lahko sprožimo našo kodo. Ta lahko na primer elementom obrazca doda razrede HTML za pravilen prikaz. Kodo dodamo v polje `onRender`: +Tik preden se obrazec izriše, lahko pustimo poklicati našo kodo. Ta lahko na primer dopolni elementom obrazca HTML razrede za pravilno prikazovanje. Kodo dodamo v polje `onRender`: ```php $form->onRender[] = function ($form) { diff --git a/forms/sl/standalone.texy b/forms/sl/standalone.texy index a68ac0a15e..a242f18279 100644 --- a/forms/sl/standalone.texy +++ b/forms/sl/standalone.texy @@ -1,43 +1,43 @@ -Obrazci, ki se uporabljajo samostojno -************************************* +Obrazci, uporabljeni samostojno +******************************* .[perex] -Nette Forms bistveno olajša ustvarjanje in obdelavo spletnih obrazcev. V svojih aplikacijah jih lahko uporabljate povsem samostojno, brez preostalega ogrodja, kar bomo prikazali v tem poglavju. +Nette Forms bistveno olajšajo ustvarjanje in obdelavo spletnih obrazcev. Uporabljate jih lahko v svojih aplikacijah popolnoma samostojno brez preostalega ogrodja, kar bomo pokazali v tem poglavju. -Če pa uporabljate aplikacijo Nette in predstavnike, je za vas na voljo priročnik: [Obrazci v predstavnikih |in-presenter]. +Če pa uporabljate Nette Application in presenterje, je za vas namenjen vodnik za [uporabo v presenterjih|in-presenter]. -Prvi obrazec .[#toc-first-form] -=============================== +Prvi obrazec +============ -Poskusili bomo napisati preprost obrazec za registracijo. Njegova koda bo videti takole ("celotna koda":https://gist.github.com/dg/370a7e3094d9ba9a9e913b8e2a2dc851): +Poskusimo napisati preprost obrazec za registracijo. Njegova koda bo naslednja ("celotna koda":https://gist.github.com/dg/57878c1a413ae8ef0c1d83f02c43ef3f): ```php use Nette\Forms\Form; $form = new Form; -$form->addText('name', 'Name:'); -$form->addPassword('password', 'Password:'); -$form->addSubmit('send', 'Sign up'); +$form->addText('name', 'Ime:'); +$form->addPassword('password', 'Geslo:'); +$form->addSubmit('send', 'Registriraj'); ``` -Izpišimo ga: +Zelo enostavno ga izrišemo: ```php $form->render(); ``` -in rezultat naj bo videti takole: +in v brskalniku se prikaže takole: -[* form-en.webp *] +[* form-cs.webp *] -Obrazec je objekt razreda `Nette\Forms\Form` (razred `Nette\Application\UI\Form` se uporablja v predstavitvah). Dodali smo mu kontrolne elemente ime, geslo in gumb za pošiljanje. +Obrazec je objekt razreda `Nette\Forms\Form` (razred `Nette\Application\UI\Form` se uporablja v presenterjih). Dodali smo mu t.i. elemente ime, geslo in gumb za pošiljanje. -Zdaj bomo obrazec oživili. Z vprašanjem `$form->isSuccess()` bomo ugotovili, ali je bil obrazec poslan in ali je bil pravilno izpolnjen. Če je odgovor pritrdilen, bomo podatke izpisali. Za definicijo obrazca bomo dodali: +In zdaj obrazec oživimo. Z vprašanjem `$form->isSuccess()` ugotovimo, ali je bil obrazec poslan in ali je bil veljavno izpolnjen. Če je, podatke izpišemo. Za definicijo obrazca torej dodamo: ```php if ($form->isSuccess()) { - echo 'The form has been filled in and submitted correctly'; + echo 'Obrazec je bil pravilno izpolnjen in poslan'; $data = $form->getValues(); // $data->name vsebuje ime // $data->password vsebuje geslo @@ -45,32 +45,32 @@ if ($form->isSuccess()) { } ``` -Metoda `getValues()` vrne poslane podatke v obliki objekta [ArrayHash |utils:arrays#ArrayHash]. Kako to spremeniti, bomo pokazali [pozneje |#Mapping to Classes]. Spremenljivka `$data` vsebuje ključa `name` in `password` s podatki, ki jih je vnesel uporabnik. +Metoda `getValues()` vrača poslane podatke v obliki objekta [ArrayHash |utils:arrays#ArrayHash]. Kako to spremeniti, si bomo ogledali [kasneje |#Preslikava v razrede]. Objekt `$data` vsebuje ključa `name` in `password` s podatki, ki jih je izpolnil uporabnik. -Običajno podatke pošljemo neposredno v nadaljnjo obdelavo, ki je lahko na primer vstavljanje v podatkovno zbirko. Vendar pa lahko med obdelavo pride do napake, na primer uporabniško ime je že zasedeno. V tem primeru napako posredujemo nazaj v obrazec s pomočjo `addError()` in ga pustimo ponovno narisati, s sporočilom o napaki: +Običajno podatke takoj pošljemo v nadaljnjo obdelavo, kar je lahko na primer vstavljanje v bazo podatkov. Med obdelavo pa se lahko pojavi napaka, na primer uporabniško ime je že zasedeno. V takem primeru napako vrnemo nazaj v obrazec z `addError()` in ga pustimo ponovno izrisati, tudi s sporočilom o napaki. ```php -$form->addError('Sorry, username is already in use.'); +$form->addError('Oprostite, to uporabniško ime že nekdo uporablja.'); ``` -Po obdelavi obrazca bomo preusmerili na naslednjo stran. S tem preprečimo, da bi obrazec nenamerno ponovno poslali s klikom na gumb *osvežitev*, *povratek* ali s premikanjem zgodovine brskalnika. +Po obdelavi obrazca preusmerimo na naslednjo stran. S tem preprečimo neželeno ponovno pošiljanje obrazca z gumbom *obnovi*, *nazaj* ali premikanjem v zgodovini brskalnika. -Privzeto se obrazec pošlje z metodo POST na isto stran. Oboje lahko spremenite: +Obrazec se standardno pošilja z metodo POST in to na isto stran. Oboje se da spremeniti: ```php $form->setAction('/submit.php'); $form->setMethod('GET'); ``` -To je vse :-) Imamo funkcionalen in popolnoma [zavarovan |#Vulnerability Protection] obrazec. +In to je pravzaprav vse :-) Imamo delujoč in popolnoma [zaščiten |#Zaščita pred ranljivostmi] obrazec. -Poskusite dodati več [kontrolnih elementov obrazca |controls]. +Poskusite dodati tudi druge [elemente obrazca|controls]. -Dostop do kontrolnih elementov .[#toc-access-to-controls] -========================================================= +Dostop do elementov +=================== -Obrazec in njegove posamezne kontrole se imenujejo komponente. Ustvarjajo drevo komponent, katerega koren je obrazec. Do posameznih kontrolnih elementov lahko dostopate na naslednji način: +Obrazec in njegove posamezne elemente imenujemo komponente. Tvorijo drevo komponent, kjer je koren prav obrazec. Do posameznih elementov obrazca dostopamo na ta način: ```php $input = $form->getComponent('name'); @@ -80,37 +80,36 @@ $button = $form->getComponent('send'); // alternativna sintaksa: $button = $form['send']; ``` -Kontrolne elemente odstranite z uporabo funkcije unset: +Elementi se odstranijo z unset: ```php unset($form['name']); ``` -Pravila potrjevanja .[#toc-validation-rules] -============================================ +Validacijska pravila +==================== -Tu je bila uporabljena beseda *valid*, vendar obrazec še nima pravil potrjevanja. To popravimo. +Omenili smo besedo *veljaven,* vendar obrazec zaenkrat nima nobenih validacijskih pravil. Popravimo to. -Ime bo obvezno, zato ga bomo označili z metodo `setRequired()`, katere argument je besedilo sporočila o napaki, ki se prikaže, če ga uporabnik ne izpolni. Če argument ni naveden, se uporabi privzeto sporočilo o napaki. +Ime bo obvezno, zato ga označimo z metodo `setRequired()`, katere argument je besedilo sporočila o napaki, ki se prikaže, če uporabnik imena ne izpolni. Če argumenta ne navedemo, se uporabi privzeto sporočilo o napaki. ```php -$form->addText('name', 'Name:') - ->setRequired('Please enter a name.'); +$form->addText('name', 'Ime:') + ->setRequired('Prosimo, vnesite ime'); ``` -Poskusite oddati obrazec brez izpolnjenega imena in videli boste, da se bo prikazalo sporočilo o napaki, brskalnik ali strežnik pa ga bo zavrnil, dokler ga ne izpolnite. +Poskusite poslati obrazec brez izpolnjenega imena in videli boste, da se prikaže sporočilo o napaki in brskalnik ali strežnik ga bo zavračal, dokler polja ne izpolnite. -Hkrati sistema ne boste mogli prevarati tako, da boste v vnos vnesli samo presledke, na primer. Na noben način. Nette samodejno obreže leve in desne bele prostore. Preizkusite. To je nekaj, kar bi morali vedno narediti pri vsakem enovrstičnem vnosu, vendar se na to pogosto pozablja. Nette to stori samodejno. (Lahko poskusite prevarati obrazce in kot ime pošljete večvrstični niz. Tudi v tem primeru se Nette ne bo pustil preslepiti in prelomi vrstic se bodo spremenili v presledke.) +Hkrati sistema ne boste prelisičili s tem, da v polje vpišete samo presledke. Kje pa. Nette samodejno odstranjuje leve in desne presledke. Preizkusite. To je stvar, ki bi jo morali vedno narediti z vsakim enovrstičnim vnosom, vendar se nanjo pogosto pozablja. Nette to naredi samodejno. (Lahko poskusite prelisičiti obrazec in kot ime poslati večvrstični niz. Tudi tu se Nette ne pusti zmesti in prelome vrstic spremeni v presledke.) -Obrazec se vedno potrdi na strani strežnika, vendar se ustvari tudi potrditev JavaScript, ki je hitra in uporabnik takoj izve za napako, ne da bi mu bilo treba obrazec poslati v strežnik. Za to poskrbi skripta `netteForms.js`. -Dodajte jo na stran: +Obrazec se vedno validira na strani strežnika, vendar se generira tudi JavaScript validacija, ki poteka bliskovito in uporabnik se o napaki takoj obvesti, brez potrebe po pošiljanju obrazca na strežnik. Za to skrbi skript `netteForms.js`. Vstavite ga na stran: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Če pogledate izvorno kodo strani z obrazcem, lahko opazite, da Nette vstavi zahtevana polja v elemente z razredom CSS `required`. Poskusite v predlogo dodati naslednji slog in oznaka "Ime" bo rdeča. Na eleganten način označimo zahtevana polja za uporabnike: +Če pogledate izvorno kodo strani z obrazcem, lahko opazite, da Nette obvezne elemente vstavlja v elemente s CSS razredom `required`. Poskusite dodati v predlogo naslednji slog in oznaka "Ime" bo rdeča. Tako elegantno uporabnikom označimo obvezne elemente: ```latte <style> @@ -118,96 +117,96 @@ Dodajte jo na stran: </style> ``` -Dodatna pravila potrjevanja bomo dodali z metodo `addRule()`. Prvi parameter je pravilo, drugi je spet besedilo sporočila o napaki, sledi pa lahko neobvezni argument pravila potrjevanja. Kaj to pomeni? +Druga validacijska pravila dodamo z metodo `addRule()`. Prvi parameter je pravilo, drugi je spet besedilo sporočila o napaki in lahko še sledi argument validacijskega pravila. Kaj to pomeni? -Obrazec bo dobil še en izbirni vnos *starost* s pogojem, da mora biti številka (`addInteger()`) in v določenih mejah (`$form::Range`). In tu bomo uporabili tretji argument `addRule()`, tj. samo območje: +Obrazec razširimo z novim neobveznim poljem "starost", ki mora biti celo število (`addInteger()`) in poleg tega v dovoljenem obsegu (`$form::Range`). In tu prav izkoristimo tretji parameter metode `addRule()`, s katerim validatorju predamo zahtevani obseg kot par `[od, do]`: ```php -$form->addInteger('age', 'Age:') - ->addRule($form::Range, 'You must be older 18 years and be under 120.', [18, 120]); +$form->addInteger('age', 'Starost:') + ->addRule($form::Range, 'Starost mora biti od 18 do 120', [18, 120]); ``` .[tip] -Če uporabnik polja ne izpolni, se pravila potrjevanja ne bodo preverila, saj je polje neobvezno. +Če uporabnik polja ne izpolni, se validacijska pravila ne bodo preverjala, saj je element neobvezen. -Očitno je na voljo prostor za majhno preoblikovanje. V sporočilu o napaki in v tretjem parametru so številke navedene podvojeno, kar ni idealno. Če bi ustvarjali [večjezični obrazec |rendering#translating] in bi bilo treba sporočilo s številkami prevesti v več jezikov, bi bilo spreminjanje vrednosti težje. Zato lahko uporabimo nadomestne znake `%d`: +Tu nastane prostor za drobno preoblikovanje (refactoring). V sporočilu o napaki in v tretjem parametru so števila navedena dvakrat, kar ni idealno. Če bi ustvarjali [večjezične obrazce |rendering#Prevajanje] in bi bilo sporočilo, ki vsebuje števila, prevedeno v več jezikov, bi se morebitna sprememba vrednosti otežila. Zato je mogoče uporabiti nadomestne znake `%d` in Nette vrednosti dopolni: ```php - ->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]); + ->addRule($form::Range, 'Starost mora biti od %d do %d let', [18, 120]); ``` -Vrnimo se k polju *geslo*, naredimo ga *zahtevno* in preverimo najmanjšo dolžino gesla (`$form::MinLength`), pri čemer ponovno uporabimo nadomestne znake v sporočilu: +Vrnimo se k elementu `password`, ki ga prav tako naredimo obveznega in še preverimo minimalno dolžino gesla (`$form::MinLength`), spet z uporabo nadomestnega znaka: ```php -$form->addPassword('password', 'Password:') - ->setRequired('Pick a password') - ->addRule($form::MinLength, 'Your password has to be at least %d long', 8); +$form->addPassword('password', 'Geslo:') + ->setRequired('Izberite si geslo') + ->addRule($form::MinLength, 'Geslo mora imeti vsaj %d znakov', 8); ``` -Za preverjanje bomo obrazcu dodali polje `passwordVerify`, kamor uporabnik ponovno vnese geslo. Z uporabo pravil potrjevanja preverimo, ali sta obe gesli enaki (`$form::Equal`). Kot argument pa podamo sklic na prvo geslo z uporabo oglatih [oklepajev |#Access to Controls]: +Dodamo v obrazec še polje `passwordVerify`, kjer uporabnik vnese geslo še enkrat, za kontrolo. S pomočjo validacijskih pravil preverimo, ali sta obe gesli enaki (`$form::Equal`). In kot parameter damo sklic na prvo geslo z uporabo [oglatimi oklepaji |#Dostop do elementov]: ```php -$form->addPassword('passwordVerify', 'Password again:') - ->setRequired('Fill your password again to check for typo') - ->addRule($form::Equal, 'Password mismatch', $form['password']) +$form->addPassword('passwordVerify', 'Geslo za kontrolo:') + ->setRequired('Prosimo, vnesite geslo še enkrat za kontrolo') + ->addRule($form::Equal, 'Gesli se ne ujemata', $form['password']) ->setOmitted(); ``` -Z uporabo `setOmitted()` smo označili element, katerega vrednost nas v resnici ne zanima in ki obstaja le za potrjevanje. Njegove vrednosti ne posredujemo v `$data`. +Z `setOmitted()` smo označili element, katerega vrednost nas pravzaprav ne zanima in ki obstaja le zaradi validacije. Vrednost se ne preda v `$data`. -Imamo popolnoma funkcionalen obrazec s preverjanjem v PHP in JavaScript. Nettejeve zmožnosti potrjevanja so veliko širše, saj lahko ustvarite pogoje, v skladu z njimi prikažete in skrijete dele strani itd. Vse boste izvedeli v poglavju o [potrjevanju obrazca |validation]. +S tem imamo končan popolnoma delujoč obrazec z validacijo v PHP in JavaScriptu. Validacijske zmožnosti Nette so veliko širše, dajo se ustvarjati pogoji, puščati po njih prikazovati in skrivati dele strani itd. Vse boste izvedeli v poglavju o [validaciji obrazcev|validation]. -Privzete vrednosti .[#toc-default-values] -========================================= +Privzete vrednosti +================== -Pogosto nastavljamo privzete vrednosti za kontrolne elemente obrazca: +Elementom obrazca običajno nastavimo privzete vrednosti: ```php -$form->addEmail('email', 'Email') +$form->addEmail('email', 'E-pošta') ->setDefaultValue($lastUsedEmail); ``` -Pogosto je koristno nastaviti privzete vrednosti za vse kontrole naenkrat. Na primer, ko se obrazec uporablja za urejanje zapisov. Zapise preberemo iz podatkovne zbirke in jih nastavimo kot privzete vrednosti: +Pogosto je koristno nastaviti privzete vrednosti vsem elementom hkrati. Na primer, ko obrazec služi za urejanje zapisov. Preberemo zapis iz baze podatkov in nastavimo privzete vrednosti: ```php //$row = ['name' => 'John', 'age' => '33', /* ... */]; $form->setDefaults($row); ``` -Po definiranju kontrolnih elementov pokličemo `setDefaults()`. +Pokličite `setDefaults()` šele po definiciji elementov. -Prikazovanje obrazca .[#toc-rendering-the-form] -=============================================== +Izris obrazca +============= -Obrazec je privzeto prikazan kot tabela. Posamezni kontrolni elementi upoštevajo osnovne smernice spletne dostopnosti. Vse oznake so ustvarjene kot `<label>` elementi in so povezani z njihovimi vhodi, klik na etiketo pa premakne kazalec na vhod. +Standardno se obrazec izriše kot tabela. Posamezni elementi izpolnjujejo osnovno pravilo dostopnosti - vse oznake so zapisane kot `<label>` in povezane z ustreznim elementom obrazca. Pri kliku na oznako se kazalec samodejno pojavi v polju obrazca. -Za vsak element lahko nastavimo poljubne atribute HTML. Dodajte na primer nadomestno mesto: +Vsakemu elementu lahko nastavimo poljubne HTML atribute. Na primer, dodamo placeholder: ```php -$form->addInteger('age', 'Age:') - ->setHtmlAttribute('placeholder', 'Please fill in the age'); +$form->addInteger('age', 'Starost:') + ->setHtmlAttribute('placeholder', 'Prosimo, izpolnite starost'); ``` -V tem poglavju je veliko načinov upodabljanja obrazca, zato je namenjeno prav [upodabljanju |rendering]. +Načinov, kako izrisati obrazec, je res veliko, zato je temu namenjeno [ločeno poglavje o izrisu|rendering]. -Prikazovanje v razrede .[#toc-mapping-to-classes] -================================================= +Preslikava v razrede +==================== -Vrnimo se k obdelavi podatkov iz obrazca. Metoda `getValues()` je posredovane podatke vrnila kot predmet `ArrayHash`. Ker je to splošen razred, nekaj podobnega kot `stdClass`, bomo pri delu z njim prikrajšani za nekaj udobja, na primer za dopolnjevanje kode za lastnosti v urejevalnikih ali statično analizo kode. To bi lahko rešili tako, da bi za vsak obrazec imeli poseben razred, katerega lastnosti bi predstavljale posamezne kontrole. Npr: +Vrnimo se k obdelavi podatkov obrazca. Metoda `getValues()` nam je vračala poslane podatke kot objekt `ArrayHash`. Ker gre za generični razred, nekaj kot `stdClass`, nam bo pri delu z njim manjkalo določeno udobje, kot je na primer predlaganje lastnosti v urejevalnikih ali statična analiza kode. To bi lahko rešili tako, da bi za vsak obrazec imeli konkreten razred, katerega lastnosti predstavljajo posamezne elemente. Npr.: ```php class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -Od različice PHP 8.0 lahko uporabite eleganten zapis, ki uporablja konstruktor: +Alternativno lahko uporabite konstruktor: ```php class RegistrationFormData @@ -221,16 +220,18 @@ class RegistrationFormData } ``` -Kako reči Nettu, naj nam podatke vrne kot predmete tega razreda? Lažje, kot si mislite. Vse, kar morate storiti, je, da kot parameter navedete ime razreda ali objekta, ki ga želite hidrirati: +Lastnosti podatkovnega razreda so lahko tudi enumi in pride do njihove samodejne preslikave. .{data-version:3.2.4} + +Kako Nette sporočiti, naj nam podatke vrača kot objekte tega razreda? Lažje, kot si mislite. Dovolj je le ime razreda ali objekt za hidracijo navesti kot parameter: ```php $data = $form->getValues(RegistrationFormData::class); $name = $data->name; ``` -Kot parameter lahko navedete tudi `'array'`, nato pa se podatki vrnejo kot polje. +Kot parameter lahko navedete tudi `'array'` in potem podatke vrne kot polje. -Če so obrazci sestavljeni iz večnivojske strukture, sestavljene iz vsebnikov, ustvarite ločen razred za vsakega od njih: +Če obrazci tvorijo večnivojsko strukturo, sestavljeno iz vsebnikov, ustvarite za vsakega ločen razred: ```php $form = new Form; @@ -247,26 +248,28 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` -Prikazovanje nato na podlagi vrste lastnosti `$person` ugotovi, da mora vsebnik prikazati v razred `PersonFormData`. Če bi lastnost vsebovala polje vsebnikov, navedite tip `array` in razred, ki ga je treba preslikati neposredno na vsebnik: +Preslikava nato iz tipa lastnosti `$person` prepozna, da mora vsebnik preslikati v razred `PersonFormData`. Če bi lastnost vsebovala polje vsebnikov, navedite tip `array` in razred za preslikavo predajte neposredno vsebniku: ```php $person->setMappedType(PersonFormData::class); ``` +Načrt podatkovnega razreda obrazca si lahko pustite generirati s pomočjo metode `Nette\Forms\Blueprint::dataClass($form)`, ki ga izpiše na stran brskalnika. Kodo nato samo kliknite, označite in kopirajte v projekt. .{data-version:3.1.15} + -Več gumbov za pošiljanje .[#toc-multiple-submit-buttons] -======================================================== +Več gumbov +========== -Če ima obrazec več kot en gumb, moramo običajno razlikovati, kateri je bil pritisnjen. Metoda `isSubmittedBy()` gumba nam vrne to informacijo: +Če ima obrazec več kot en gumb, moramo praviloma razlikovati, kateri od njih je bil pritisnjen. To informacijo nam vrne metoda `isSubmittedBy()` gumba: ```php -$form->addSubmit('save', 'Save'); -$form->addSubmit('delete', 'Delete'); +$form->addSubmit('save', 'Shrani'); +$form->addSubmit('delete', 'Izbriši'); if ($form->isSuccess()) { if ($form['save']->isSubmittedBy()) { @@ -279,37 +282,36 @@ if ($form->isSuccess()) { } ``` -Ne izpustite `$form->isSuccess()`, da preverite veljavnost podatkov. +Vprašanja `$form->isSuccess()` ne izpustite, s tem preverite veljavnost podatkov. -Ko je obrazec oddan s tipko <kbd>Enter</kbd>, se obravnava, kot da bi bil oddan s prvo tipko. +Ko se obrazec pošlje z gumbom <kbd>Enter</kbd>, se šteje, kot da je bil poslan s prvim gumbom. -Zaščita pred ranljivostmi .[#toc-vulnerability-protection] -========================================================== +Zaščita pred ranljivostmi +========================= -Okvir Nette si zelo prizadeva biti varen, in ker so obrazci najpogostejši uporabniški vnos, so obrazci Nette tako dobro kot neprepustni. +Nette Framework daje velik poudarek varnosti in zato skrbno pazi na dobro zaščito obrazcev. -Poleg zaščite obrazcev pred napadi dobro znanih ranljivosti, kot sta [Cross-Site Scripting (XSS |nette:glossary#cross-site-scripting-xss] ) in [Cross-Site Request Forgery (CSRF) |nette:glossary#cross-site-request-forgery-csrf], opravi veliko manjših varnostnih opravil, o katerih vam ni treba več razmišljati. +Poleg tega, da obrazce zaščiti pred napadom [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] in [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF], izvaja veliko drobnih zaščit, na katere vam ni treba več misliti. -Na primer, iz vnosov filtrira vse kontrolne znake in preverja veljavnost kodiranja UTF-8, tako da bodo podatki iz obrazca vedno čisti. Pri izbirnih poljih in radijskih seznamih preveri, ali so bili izbrani elementi dejansko izmed ponujenih in ni prišlo do ponarejanja. Omenili smo že, da pri enovrstičnem vnosu besedila odstrani znake s konca vrstice, ki bi jih lahko tja poslal napadalec. Pri večvrstičnih vnosih normalizira znake na koncu vrstice. In tako naprej. +Tako na primer iz vhodov filtrira vse kontrolne znake in preveri veljavnost UTF-8 kodiranja, tako da bodo podatki iz obrazca vedno čisti. Pri izbirnih poljih in seznamih radijskih gumbov preverja, ali so bili izbrani elementi resnično iz ponujenih in ni prišlo do ponarejanja. Že smo omenili, da pri enovrstičnih besedilnih vnosih odstranjuje znake konca vrstice, ki jih je tja lahko poslal napadalec. Pri večvrstičnih vnosih pa normalizira znake za konce vrstic. In tako naprej. -Nette za vas odpravi varnostne ranljivosti, za katere večina programerjev nima pojma, da obstajajo. +Nette za vas rešuje varnostna tveganja, za katera veliko programerjev sploh ne ve, da obstajajo. -Pri omenjenem napadu CSRF gre za to, da napadalec žrtev zvabi k obisku strani, ki v brskalniku žrtve tiho izvrši zahtevo strežniku, v katerega je žrtev trenutno prijavljena, strežnik pa verjame, da je zahtevo po svoji volji izvršila žrtev. Zato Nette prepreči, da bi bil obrazec oddan prek protokola POST iz druge domene. Če iz nekega razloga želite izklopiti zaščito in dovoliti oddajo obrazca iz druge domene, uporabite: +Omenjeni napad CSRF temelji na tem, da napadalec žrtev zvabi na stran, ki neopazno v brskalniku žrtve izvede zahtevo na strežnik, na katerem je žrtev prijavljena, in strežnik domneva, da je zahtevo izvedla žrtev po svoji volji. Zato Nette preprečuje pošiljanje obrazca POST iz druge domene. Če iz kakršnega koli razloga želite zaščito izklopiti in dovoliti pošiljanje obrazca iz druge domene, uporabite: ```php $form->allowCrossOrigin(); // POZOR! Izklopi zaščito! ``` -Ta zaščita uporablja piškotek SameSite z imenom `_nss`. Zato ustvarite obrazec, preden izpraznite prvi izhod, da se lahko pošlje piškotek. +Ta zaščita uporablja SameSite piškotek z imenom `_nss`. Zato ustvarite objekt obrazca še pred pošiljanjem prvega izpisa, da bo mogoče piškotek poslati. -Zaščita piškotkov SameSite morda ni 100-odstotno zanesljiva, zato je dobro vklopiti zaščito s žetoni: +Zaščita s pomočjo SameSite piškotka morda ni 100% zanesljiva, zato je priporočljivo vklopiti še zaščito s pomočjo žetona: ```php $form->addProtection(); ``` -To zaščito je priporočljivo uporabiti za obrazce v upravnem delu aplikacije, ki spreminjajo občutljive podatke. Okvir ščiti pred napadom CSRF z generiranjem in potrjevanjem avtentikacijskega žetona, ki je shranjen v seji (argument je sporočilo o napaki, ki se prikaže, če je žeton potekel). Zato je treba pred prikazom obrazca zagnati sejo. V upravljalnem delu spletnega mesta je seja običajno že začeta zaradi prijave uporabnika. -V nasprotnem primeru sejo zaženite z metodo `Nette\Http\Session::start()`. +Priporočamo, da tako zaščitite obrazce v administrativnem delu spletnega mesta, ki spreminjajo občutljive podatke v aplikaciji. Ogrodje se proti napadu CSRF brani z generiranjem in preverjanjem avtorizacijskega žetona, ki se shranjuje v sejo. Zato je treba pred prikazom obrazca imeti odprto sejo. V administrativnem delu spletnega mesta je običajno seja že zagnana zaradi prijave uporabnika. Sicer sejo zaženite z metodo `Nette\Http\Session::start()`. -Tako smo na hitro spoznali obrazce v Nette. Za več navdiha poskusite poiskati v imeniku s [primeri |https://github.com/nette/forms/tree/master/examples] v distribuciji. +Tako, za nami je hiter uvod v obrazce v Nette. Poskusite si še pogledati v imenik [examples|https://github.com/nette/forms/tree/master/examples] v distribuciji, kjer boste našli dodatno inspiracijo. diff --git a/forms/sl/validation.texy b/forms/sl/validation.texy index a1f35438fb..da4379ff7c 100644 --- a/forms/sl/validation.texy +++ b/forms/sl/validation.texy @@ -1,173 +1,184 @@ -Potrjevanje obrazcev -******************** +Validacija obrazcev +******************* -Zahtevani kontrolni elementi .[#toc-required-controls] -====================================================== +Obvezni elementi +================ -Kontrole so označene kot zahtevane z metodo `setRequired()`, katere argument je besedilo [sporočila o napaki, ki |#Error Messages] se prikaže, če ga uporabnik ne izpolni. Če argument ni podan, se uporabi privzeto sporočilo o napaki. +Obvezne elemente označimo z metodo `setRequired()`, katere argument je besedilo [#sporočila o napakah], ki se prikaže, če uporabnik elementa ne izpolni. Če argumenta ne navedemo, se uporabi privzeto sporočilo o napaki. ```php -$form->addText('name', 'Name:') - ->setRequired('Please fill your name.'); +$form->addText('name', 'Ime:') + ->setRequired('Prosimo, vnesite ime'); ``` -Pravila .[#toc-rules] -===================== +Pravila +======= -Pravila potrjevanja dodajamo kontrolnim elementom z metodo `addRule()`. Prvi parameter je pravilo, drugi je [sporočilo o napaki |#Error Messages], tretji pa argument pravila potrjevanja. +Validacijska pravila dodajamo elementom z metodo `addRule()`. Prvi parameter je pravilo, drugi je besedilo [#sporočila o napakah] in tretji je argument validacijskega pravila. ```php -$form->addPassword('password', 'Password:') - ->addRule($form::MinLength, 'Password must be at least %d characters', 8); +$form->addPassword('password', 'Geslo:') + ->addRule($form::MinLength, 'Geslo mora imeti vsaj %d znakov', 8); ``` -Nette ima na voljo številna vgrajena pravila, katerih imena so konstante razreda `Nette\Forms\Form`: +**Validacijska pravila se preverjajo samo v primeru, da je uporabnik element izpolnil.** -Naslednja pravila lahko uporabimo za vse kontrole: +Nette prihaja s celo vrsto preddefiniranih pravil, katerih imena so konstante razreda `Nette\Forms\Form`. Pri vseh elementih lahko uporabimo ta pravila: -| konstanta | opis | argumenti +| konstanta | opis | tip argumenta |------- -| `Required` | vzdevek `setRequired()` | - -| `Filled` | vzdevek `setRequired()` | - -| `Blank` | ne sme biti izpolnjen | - +| `Required` | obvezen element, alias za `setRequired()` | - +| `Filled` | obvezen element, alias za `setRequired()` | - +| `Blank` | element ne sme biti izpolnjen | - | `Equal` | vrednost je enaka parametru | `mixed` | `NotEqual` | vrednost ni enaka parametru | `mixed` -| `IsIn` | vrednost je enaka nekemu elementu v polju | `array` +| `IsIn` | vrednost je enaka nekateremu elementu v polju | `array` | `IsNotIn` | vrednost ni enaka nobenemu elementu v polju | `array` -| `Valid` | vhodni podatki so uspešno preverjeni (za [pogoje |#conditions]) | - - -Za kontrole `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` se lahko uporabijo tudi naslednja pravila: - -| `MinLength` | minimalna dolžina niza | `int` -| `MaxLength` | največja dolžina niza | `int` -| `Length` | dolžina v območju ali natančna dolžina | par `[int, int]` ali `int` -| `Email` | veljavni e-poštni naslov | - -| `URL` | veljavni URL | - -| `Pattern` | ustreza regularnemu vzorcu | `string` -| `PatternInsensitive` | kot `Pattern`, vendar ne upošteva velikih in malih črk | `string` -| `Integer` | celo število | - -| `Numeric` | vzdevek `Integer` | - -| `Float` | celo število ali število s plavajočo vejico | - -| `Min` | najmanjša vrednost celega števila | `int\|float` -| `Max` | največja vrednost celega števila | `int\|float` -| `Range` | vrednost v območju | par `[int\|float, int\|float]` - -Pravila `Integer`, `Numeric` in `Float` samodejno pretvorijo vrednost v celo število (oziroma plavajoče število). Poleg tega pravilo `URL` sprejme tudi naslov brez sheme (npr. `nette.org`) in dopolni shemo (`https://nette.org`). -Izraza v `Pattern` in `PatternInsensitive` morata biti veljavna za celotno vrednost, tj. kot da bi bila zavita v znake `^` and `$`. - -Za kontrole `addUpload()`, `addMultiUpload()` se lahko uporabljajo tudi naslednja pravila: - -| `MaxFileSize` | največja velikost datoteke | `int` -| `MimeType` | vrsta MIME, sprejema nadomestne znake (`'video/*'`) | `string\|string[]` -| `Image` | naložena datoteka je JPEG, PNG, GIF, WebP | - -| `Pattern` | ime datoteke ustreza regularnemu izrazu | `string` -| `PatternInsensitive` | kot `Pattern`, vendar se pri tem ne upošteva velikih in malih črk | `string` +| `Valid` | je element pravilno izpolnjen? (za [pogoje |#Pogoji]) | - + + +Besedilni vnosi +--------------- + +Pri elementih `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`, `addFloat()` lahko uporabimo tudi nekatera naslednja pravila: + +| `MinLength` | minimalna dolžina besedila | `int` +| `MaxLength` | maksimalna dolžina besedila | `int` +| `Length` | dolžina v obsegu ali natančna dolžina | par `[int, int]` ali `int` +| `Email` | veljaven e-poštni naslov | - +| `URL` | absolutni URL | - +| `Pattern` | ustreza regularnemu izrazu | `string` +| `PatternInsensitive` | kot `Pattern`, vendar neodvisno od velikosti črk | `string` +| `Integer` | celoštevilska vrednost | - +| `Numeric` | alias za `Integer` | - +| `Float` | število | - +| `Min` | minimalna vrednost numeričnega elementa | `int\|float` +| `Max` | maksimalna vrednost numeričnega elementa | `int\|float` +| `Range` | vrednost v obsegu | par `[int\|float, int\|float]` + +Validacijska pravila `Integer`, `Numeric` in `Float` takoj pretvorijo vrednost v integer oz. float. In nadalje pravilo `URL` sprejme tudi naslov brez sheme (npr. `nette.org`) in shemo dopolni (`https://nette.org`). Izraz v `Pattern` in `PatternIcase` mora veljati za celotno vrednost, tj. kot da bi bil obdan z znakoma `^` in `$`. + + +Število elementov +----------------- -Za `MimeType` in `Image` je potrebna razširitev PHP `fileinfo`. Ali je datoteka ali slika zahtevane vrste, se ugotovi na podlagi njenega podpisa. Celovitost celotne datoteke se ne preverja. Ali slika ni poškodovana, lahko ugotovite na primer tako, da [jo |http:request#toImage] poskusite [naložiti |http:request#toImage]. +Pri elementih `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()` lahko uporabimo tudi naslednja pravila za omejitev števila izbranih elementov oz. naloženih datotek: -Za kontrolnike `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()` lahko za omejitev števila izbranih elementov oziroma naloženih datotek uporabite tudi naslednja pravila: +| `MinLength` | minimalno število | `int` +| `MaxLength` | maksimalno število | `int` +| `Length` | število v obsegu ali natančno število | par `[int, int]` ali `int` -| `MinLength` | minimalno število | `int` -| `MaxLength` | maksimalno število | `int` -| `Length` | število v razponu ali natančno število | par `[int, int]` ali `int` +Nalaganje datotek +----------------- -Sporočila o napakah .[#toc-error-messages] ------------------------------------------- +Pri elementih `addUpload()`, `addMultiUpload()` lahko uporabimo tudi naslednja pravila: -Vsa vnaprej določena pravila, razen `Pattern` in `PatternInsensitive`, imajo privzeto sporočilo o napaki, zato jih lahko izpustite. Vendar pa boste s posredovanjem in oblikovanjem vseh prilagojenih sporočil naredili obrazec prijaznejši do uporabnika. +| `MaxFileSize` | maksimalna velikost datoteke v bajtih | `int` +| `MimeType` | MIME tip, dovoljeni nadomestni znaki (`'video/*'`) | `string\|string[]` +| `Image` | slika JPEG, PNG, GIF, WebP, AVIF | - +| `Pattern` | ime datoteke ustreza regularnemu izrazu | `string` +| `PatternInsensitive` | kot `Pattern`, vendar neodvisno od velikosti črk | `string` + +`MimeType` in `Image` zahtevata PHP razširitev `fileinfo`. Da je datoteka ali slika zahtevanega tipa, zaznajo na podlagi njene signature in **ne preverjajo integritete celotne datoteke.** Ali slika ni poškodovana, lahko ugotovite na primer s poskusom njenega [nalaganjem |http:request#toImage]. + + +Sporočila o napakah +=================== + +Vsa preddefinirana pravila z izjemo `Pattern` in `PatternInsensitive` imajo privzeto sporočilo o napaki, zato ga lahko izpustite. Vendar z navedbo in oblikovanjem vseh sporočil po meri naredite obrazec uporabniku prijaznejši. -Privzeta sporočila lahko spremenite v obrazcu [forms:configuration], tako da spremenite besedila v polju `Nette\Forms\Validator::$messages` ali uporabite [prevajalnik |rendering#translating]. +Spremeniti privzeta sporočila lahko v [konfiguraciji|forms:configuration], s prilagoditvijo besedil v polju `Nette\Forms\Validator::$messages` ali z uporabo [prevajalniku |rendering#Prevajanje]. -V besedilu sporočil o napakah lahko uporabite naslednje nadomestne znake: +V besedilu sporočil o napakah lahko uporabljate te nadomestne nize: -| `%d` | postopoma nadomesti pravila za argumenti -| `%n$d` | nadomesti z n-tim argumentom pravila -| `%label` | nadomesti z oznako polja (brez dvopičja) -| `%name` | nadomesti z imenom polja (npr. `name`) -| `%value` | nadomesti z vrednostjo, ki jo vnese uporabnik +| `%d` | postopoma nadomesti z argumenti pravila +| `%n$d` | nadomesti z n-tim argumentom pravila +| `%label` | nadomesti z oznako elementa (brez dvopičja) +| `%name` | nadomesti z imenom elementa (npr. `name`) +| `%value` | nadomesti z vrednostjo, ki jo je vnesel uporabnik ```php -$form->addText('name', 'Name:') - ->setRequired('Please fill in %label'); +$form->addText('name', 'Ime:') + ->setRequired('Izpolnite prosim %label'); $form->addInteger('id', 'ID:') - ->addRule($form::Range, 'at least %d and no more than %d', [5, 10]); + ->addRule($form::Range, 'najmanj %d in največ %d', [5, 10]); $form->addInteger('id', 'ID:') - ->addRule($form::Range, 'no more than %2$d and at least %1$d', [5, 10]); + ->addRule($form::Range, 'največ %2$d in najmanj %1$d', [5, 10]); ``` -Pogoji .[#toc-conditions] -========================= +Pogoji +====== -Poleg pravil potrjevanja lahko določite tudi pogoje. Nastavljamo jih podobno kot pravila, le da namesto `addCondition()` uporabimo `addRule()` in seveda pustimo brez sporočila o napaki (pogoj samo vpraša): +Poleg pravil lahko dodajamo tudi pogoje. Ti se zapisujejo podobno kot pravila, le da namesto `addRule()` uporabimo metodo `addCondition()` in seveda ne navajamo nobenega sporočila o napaki (pogoj se samo sprašuje): ```php -$form->addPassword('password', 'Password:') - // če geslo ni daljše od 8 znakov ... +$form->addPassword('password', 'Geslo:') + // če geslo ni daljše od 8 znakov ->addCondition($form::MaxLength, 8) - // ... potem mora vsebovati številko - ->addRule($form::Pattern, 'Must contain number', '.*[0-9].*'); + // potem mora vsebovati števko + ->addRule($form::Pattern, 'Mora vsebovati števko', '.*[0-9].*'); ``` -Pogoj lahko povežemo z drugim elementom od trenutnega z uporabo `addConditionOn()`. Prvi parameter je referenca na polje. V naslednjem primeru bo elektronsko sporočilo zahtevano le, če je potrditveno polje označeno (tj. njegova vrednost je `true`): +Pogoj je mogoče vezati tudi na drug element kot trenutni s pomočjo `addConditionOn()`. Kot prvi parameter navedemo sklic na element. V tem primeru bo e-pošta obvezna le takrat, ko se označi potrditveno polje (njegova vrednost bo true): ```php -$form->addCheckbox('newsletters', 'send me newsletters'); +$form->addCheckbox('newsletters', 'pošiljajte mi novice'); -$form->addEmail('email', 'Email:') - // če je potrditveno polje označeno ... +$form->addEmail('email', 'E-pošta:') + // če je potrditveno polje označeno ->addConditionOn($form['newsletters'], $form::Equal, true) - // ... zahteva e-pošto - ->setRequired('Fill your email address'); + // potem zahtevaj e-pošto + ->setRequired('Vnesite e-poštni naslov'); ``` -Pogoje lahko združite v kompleksne strukture z metodama `elseCondition()` in `endCondition()`. +Iz pogojev je mogoče ustvarjati kompleksne strukture s pomočjo `elseCondition()` in `endCondition()`: ```php $form->addText(/* ... */) - ->addCondition(/* ... */) // če je izpolnjen prvi pogoj. - ->addConditionOn(/* ... */) // in drugi pogoj tudi za drug element - ->addRule(/* ... */) // zahteva to pravilo + ->addCondition(/* ... */) // če je izpolnjen prvi pogoj + ->addConditionOn(/* ... */) // in drugi pogoj na drugem elementu + ->addRule(/* ... */) // zahtevaj to pravilo ->elseCondition() // če drugi pogoj ni izpolnjen - ->addRule(/* ... */) // zahteva ta pravila + ->addRule(/* ... */) // zahtevaj ta pravila ->addRule(/* ... */) - ->endCondition() // se vrnemo k prvemu pogoju + ->endCondition() // vračamo se k prvemu pogoju ->addRule(/* ... */); ``` -V Nette je zelo enostavno reagirati na izpolnitev ali neizpolnitev pogoja na strani JavaScript z uporabo metode `toggle()`, glejte [Dinamični JavaScript |#Dynamic JavaScript]. +V Nette je mogoče zelo enostavno reagirati na izpolnitev ali neizpolnitev pogoja tudi na strani JavaScripta s pomočjo metode `toggle()`, glej [#Dinamični JavaScript]. -Sklicevanja med kontrolniki .[#toc-references-between-controls] -=============================================================== +Sklic na drug element +===================== -Argument pravila ali pogoja je lahko sklic na drug element. Na primer, dinamično lahko preverite, da ima `text` toliko znakov, kolikor je vrednost polja `length`: +Kot argument pravila ali pogoja lahko predamo tudi drug element obrazca. Pravilo potem uporabi vrednost, ki jo je kasneje vnesel uporabnik v brskalniku. Tako lahko npr. dinamično validiramo, da element `password` vsebuje enak niz kot element `password_confirm`: ```php -$form->addInteger('length'); -$form->addText('text') - ->addRule($form::Length, null, $form['length']); +$form->addPassword('password', 'Geslo'); +$form->addPassword('password_confirm', 'Potrdite geslo') + ->addRule($form::Equal, 'Vneseni gesli se ne ujemata', $form['password']); ``` -Pravila in pogoji po meri .[#toc-custom-rules-and-conditions] -============================================================= +Pravila in pogoji po meri +========================= -Včasih se znajdemo v situaciji, ko vgrajena pravila potrjevanja v Nette niso dovolj in moramo podatke od uporabnika potrditi na svoj način. V Nette je to zelo enostavno! +Včasih se znajdemo v situaciji, ko nam vgrajena validacijska pravila v Nette ne zadostujejo in moramo podatke od uporabnika validirati po svoje. V Nette je to zelo enostavno! -Metodam `addRule()` ali `addCondition()` lahko kot prvi parameter posredujete katerikoli povratni klic. Povratni klic sprejme sam element kot prvi parameter in vrne logično vrednost, ki označuje, ali je bilo preverjanje uspešno. Pri dodajanju pravila z uporabo metode `addRule()` lahko posredujete dodatne argumente, ki se nato posredujejo kot drugi parameter. +Metodam `addRule()` ali `addCondition()` lahko kot prvi parameter predamo poljuben povratni klic. Ta sprejme kot prvi parameter sam element in vrača boolean vrednost, ki določa, ali je validacija potekala v redu. Pri dodajanju pravila s pomočjo `addRule()` je mogoče vnesti tudi druge argumente, ti so nato predani kot drugi parameter. -Lastni nabor validatorjev je tako mogoče ustvariti kot razred s statičnimi metodami: +Lasten nabor validatorjev tako lahko ustvarimo kot razred s statičnimi metodami: ```php class MyValidators { - // preveri, ali je vrednost deljiva z argumentom + // testira, ali je vrednost deljiva z argumentom public static function validateDivisibility(BaseControl $input, $arg): bool { return $input->getValue() % $arg === 0; @@ -175,23 +186,23 @@ class MyValidators public static function validateEmailDomain(BaseControl $input, $domain) { - // dodatni validatorji + // drugi validatorji } } ``` -Uporaba je nato zelo preprosta: +Uporaba je nato zelo enostavna: ```php $form->addInteger('num') ->addRule( [MyValidators::class, 'validateDivisibility'], - 'The value must be a multiple of %d', + 'Vrednost mora biti večkratnik števila %d', 8, ); ``` -Pravila potrjevanja po meri lahko dodate tudi v JavaScript. Edina zahteva je, da mora biti pravilo statična metoda. Njegovo ime za validator JavaScript se ustvari z združitvijo imena razreda brez povratnih lomk `\`, the underscore `_`, in imena metode. Na primer, `App\MyValidators::validateDivisibility` zapišite kot `AppMyValidators_validateDivisibility` in ga dodajte predmetu `Nette.validators`: +Lastna validacijska pravila lahko dodajamo tudi v JavaScript. Pogoj je, da je pravilo statična metoda. Njeno ime za JavaScript validator nastane s spojitvijo imena razreda brez povratnih poševnic `\`, podčrtaja `_` in imena metode. Npr. `App\MyValidators::validateDivisibility` zapišemo kot `AppMyValidators_validateDivisibility` in dodamo v objekt `Nette.validators`: ```js Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => { @@ -200,12 +211,12 @@ Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => ``` -Dogodek onValidate .[#toc-event-onvalidate] -=========================================== +Dogodek onValidate +================== -Ko je obrazec poslan, se preverjanje veljavnosti izvede tako, da se preverijo posamezna pravila, dodana s strani `addRule()`, nato pa se pokliče [dogodek |nette:glossary#Events] `onValidate`. Njegovega izvajalca je mogoče uporabiti za dodatno potrjevanje, običajno za preverjanje pravilne kombinacije vrednosti v več elementih obrazca. +Po pošiljanju obrazca se izvede validacija, kjer se preverijo posamezna pravila, dodana s pomočjo `addRule()`, in nato se sproži [dogodek |nette:glossary#Dogodki eventi] `onValidate`. Njegov obravnavalnik lahko uporabimo za dodatno validacijo, tipično preverjanje pravilne kombinacije vrednosti v več elementih obrazca. -Če je odkrita napaka, se posreduje obrazcu z metodo `addError()`. Ta se lahko kliče na določen element ali neposredno na obrazec. +Če se odkrije napaka, jo predamo v obrazec z metodo `addError()`. To lahko pokličemo bodisi na konkretnem elementu ali neposredno na obrazcu. ```php protected function createComponentSignInForm(): Form @@ -219,16 +230,16 @@ protected function createComponentSignInForm(): Form public function validateSignInForm(Form $form, \stdClass $data): void { if ($data->foo > 1 && $data->bar > 5) { - $form->addError('This combination is not possible.'); + $form->addError('Ta kombinacija ni mogoča.'); } } ``` -Napake pri obdelavi .[#toc-processing-errors] -============================================= +Napake pri obdelavi +=================== -V številnih primerih odkrijemo napako med obdelavo veljavnega obrazca, npr. ko zapišemo nov vnos v zbirko podatkov in naletimo na podvojen ključ. V tem primeru napako posredujemo nazaj v obrazec s pomočjo metode `addError()`. To lahko kličemo na določen element ali neposredno na obrazec: +V mnogih primerih se o napaki zavemo šele v trenutku, ko obdelujemo veljaven obrazec, na primer zapisujemo novo postavko v bazo podatkov in naletimo na podvojitev ključev. V takem primeru napako spet predamo v obrazec z metodo `addError()`. To lahko pokličemo bodisi na konkretnem elementu ali neposredno na obrazcu: ```php try { @@ -238,72 +249,71 @@ try { } catch (Nette\Security\AuthenticationException $e) { if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) { - $form->addError('Invalid password.'); + $form->addError('Neveljavno geslo.'); } } ``` -Če je mogoče, priporočamo, da napako dodate neposredno na element obrazca, saj se bo ob uporabi privzetega prikazovalnika prikazala poleg njega. +Če je mogoče, priporočamo, da napako priključite neposredno elementu obrazca, ker se bo prikazala poleg njega pri uporabi privzetega rendererja. ```php -$form['date']->addError('Sorry, this date is already taken.'); +$form['date']->addError('Oprostite, ampak ta datum je že zaseden.'); ``` -Za posredovanje več sporočil o napakah obrazcu ali elementu lahko večkrat pokličete `addError()`. Pridobite jih s `getErrors()`. +Lahko `addError()` kličete večkrat in tako predaste obrazcu ali elementu več sporočil o napakah. Dobite jih s pomočjo `getErrors()`. -Upoštevajte, da `$form->getErrors()` vrne povzetek vseh sporočil o napakah, tudi tistih, ki so posredovana neposredno posameznim elementom, ne le neposredno obrazcu. Sporočila o napakah, ki so posredovana samo obrazcu, se pridobijo prek `$form->getOwnErrors()`. +Pozor, `$form->getErrors()` vrača povzetek vseh sporočil o napakah, tudi tistih, ki so bila predana neposredno posameznim elementom, ne le neposredno obrazcu. Sporočila o napakah, predana samo obrazcu, dobite preko `$form->getOwnErrors()`. -Spreminjanje vhodnih vrednosti .[#toc-modifying-input-values] -============================================================= +Spreminjanje vnosa +================== -Z metodo `addFilter()` lahko spremenimo vrednost, ki jo je vnesel uporabnik. V tem primeru bomo dopustili in odstranili presledke v poštni številki: +S pomočjo metode `addFilter()` lahko spremenimo vrednost, ki jo je vnesel uporabnik. V tem primeru bomo tolerirali in odstranjevali presledke v poštni številki: ```php -$form->addText('zip', 'Postcode:') +$form->addText('zip', 'Poštna št.:') ->addFilter(function ($value) { - return str_replace(' ', '', $value); // odstranite presledke iz poštne številke. + return str_replace(' ', '', $value); // odstranimo presledke iz poštne številke }) - ->addRule($form::Pattern, 'The postal code is not five digits', '\d{5}'); + ->addRule($form::Pattern, 'Poštna št. ni v obliki petih števk', '\d{5}'); ``` -Filter je vključen med pravila in pogoje potrjevanja in je zato odvisen od vrstnega reda metod, tj. filter in pravilo se kličeta v enakem vrstnem redu, kot je vrstni red metod `addFilter()` in `addRule()`. +Filter se vključi med validacijska pravila in pogoje, zato je vrstni red metod pomemben, tj. filter in pravilo se kličeta v takem vrstnem redu, kot je vrstni red metod `addFilter()` in `addRule()`. -Potrjevanje v javascriptu .[#toc-javascript-validation] -======================================================= +Validacija JavaScript +===================== -Jezik pravil in pogojev potrjevanja je zelo zmogljiv. Čeprav vse konstrukcije delujejo tako na strani strežnika kot na strani odjemalca, v jeziku JavaScript. Pravila se prenašajo v atributih HTML `data-nette-rules` kot JSON. -Za samo potrjevanje skrbi druga skripta, ki zasvoji vse dogodke obrazca `submit`, iterira čez vse vhode in izvede ustrezne validacije. +Jezik za oblikovanje pogojev in pravil je zelo močan. Vse konstrukcije pri tem delujejo tako na strani strežnika kot tudi na strani JavaScripta. Prenašajo se v HTML atributih `data-nette-rules` kot JSON. Samo validacijo nato izvaja skript, ki prestreže dogodek obrazca `submit`, pregleda posamezne elemente in izvede ustrezno validacijo. -Ta skripta je `netteForms.js`, ki je na voljo iz več možnih virov: +Ta skript je `netteForms.js` in je na voljo iz več možnih virov: -Skripto lahko vstavite neposredno v stran HTML iz CDN: +Skript lahko vstavite neposredno v HTML stran iz CDN: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Lahko pa jo kopirate lokalno v javno mapo projekta (npr. s spletne strani `vendor/nette/forms/src/assets/netteForms.min.js`): +Ali ga kopirate lokalno v javno mapo projekta (npr. iz `vendor/nette/forms/src/assets/netteForms.min.js`): ```latte <script src="/path/to/netteForms.min.js"></script> ``` -ali namestite prek [npm |https://www.npmjs.com/package/nette-forms]: +Ali namestite preko [npm|https://www.npmjs.com/package/nette-forms]: ```shell npm install nette-forms ``` -Nato naložite in zaženite: +In nato naložite in zaženete: ```js import netteForms from 'nette-forms'; netteForms.initOnLoad(); ``` -Lahko ga naložite tudi neposredno iz mape `vendor`: +Alternativno ga lahko naložite neposredno iz mape `vendor`: ```js import netteForms from '../path/to/vendor/nette/forms/src/assets/netteForms.js'; @@ -311,10 +321,10 @@ netteForms.initOnLoad(); ``` -Dinamični JavaScript .[#toc-dynamic-javascript] -=============================================== +Dinamični JavaScript +==================== -Ali želite prikazati polja z naslovi le, če se uporabnik odloči za pošiljanje blaga po pošti? Ni problema. Ključ je par metod `addCondition()` in `toggle()`: +Želite prikazati polja za vnos naslova samo, če uporabnik izbere pošiljanje blaga po pošti? Ni problema. Ključ je par metod `addCondition()` & `toggle()`: ```php $form->addCheckbox('send_it') @@ -322,25 +332,25 @@ $form->addCheckbox('send_it') ->toggle('#address-container'); ``` -Ta koda pravi, da bo ob izpolnitvi pogoja, tj. ko je potrditveno polje označeno, viden element HTML `#address-container`. In obratno. Elemente obrazca z naslovom prejemnika torej postavimo v vsebnik s tem ID, in ko je potrditveno polje kliknjeno, se skrijejo ali prikažejo. Za to poskrbi skripta `netteForms.js`. +Ta koda pravi, da ko je pogoj izpolnjen, torej ko je potrditveno polje označeno, bo viden HTML element `#address-container`. In obratno. Elemente obrazca z naslovom prejemnika tako postavimo v vsebnik s tem ID-jem in ob kliku na potrditveno polje se skrijejo ali prikažejo. To zagotavlja skript `netteForms.js`. -Metodi `toggle()` lahko kot argument posredujemo katerikoli selektor. Iz zgodovinskih razlogov se alfanumerični niz brez drugih posebnih znakov obravnava kot ID elementa, enako, kot če bi pred njim bil `#` character. The second optional parameter allows us to reverse the behavior, i.e. if we used `toggle('#address-container', false)`, element bi se prikazal le, če bi bilo potrditveno polje odkljukano. +Kot argument metode `toggle()` je mogoče predati poljuben selektor. Iz zgodovinskih razlogov se alfanumerični niz brez drugih posebnih znakov razume kot ID elementa, torej enako, kot če bi mu predhajal znak `#`. Drugi neobvezni parameter omogoča obrniti vedenje, tj. če bi uporabili `toggle('#address-container', false)`, bi se element nasprotno prikazal samo takrat, če potrditveno polje ne bi bilo označeno. -Privzeta implementacija JavaScript spreminja lastnost `hidden` za elemente. Vendar lahko obnašanje preprosto spremenimo, na primer z dodajanjem animacije. Preprosto nadomeščajte metodo `Nette.toggle` v jeziku JavaScript s prilagojeno rešitvijo: +Privzeta implementacija v JavaScriptu spreminja elementom lastnost `hidden`. Vedenje pa lahko enostavno spremenimo, na primer dodamo animacijo. Dovolj je, da v JavaScriptu prepišemo metodo `Nette.toggle` z lastno rešitvijo: ```js Nette.toggle = (selector, visible, srcElement, event) => { document.querySelectorAll(selector).forEach((el) => { - // hide or show 'el' according to the value of 'visible' + // skrijemo ali prikažemo 'el' glede na vrednost 'visible' }); }; ``` -Onemogočanje potrjevanja .[#toc-disabling-validation] -===================================================== +Izklop validacije +================= -V nekaterih primerih morate preverjanje onemogočiti. Če gumb za oddajo ne sme po oddaji zagnati preverjanja (na primer gumb *Preklic* ali *Preview*), lahko preverjanje onemogočite tako, da pokličete `$submit->setValidationScope([])`. Obrazec lahko delno potrdite tudi tako, da določite elemente, ki jih je treba potrditi. +Včasih se lahko zgodi, da je treba validacijo izklopiti. Če pritisk na gumb za pošiljanje ne sme izvajati validacije (primerno za gumbe *Prekliči* ali *Predogled*), jo izklopimo z metodo `$submit->setValidationScope([])`. Če naj izvaja le delno validacijo, lahko določimo, katera polja ali vsebnikov obrazca se naj validirajo. ```php $form->addText('name') @@ -352,15 +362,15 @@ $details->addInteger('age') $details->addInteger('age2') ->setRequired('age2'); -$form->addSubmit('send1'); // Potrdi celoten obrazec +$form->addSubmit('send1'); // Validira celoten obrazec $form->addSubmit('send2') - ->setValidationScope([]); // Ne potrdi ničesar + ->setValidationScope([]); // Sploh ne validira $form->addSubmit('send3') - ->setValidationScope([$form['name']]); // Potrdi samo polje 'ime' + ->setValidationScope([$form['name']]); // Validira samo element name $form->addSubmit('send4') - ->setValidationScope([$form['details']['age']]); // Potrdi samo polje "starost". + ->setValidationScope([$form['details']['age']]); // Validira samo element age $form->addSubmit('send5') - ->setValidationScope([$form['details']]); // Potrdi vsebnik "podrobnosti". + ->setValidationScope([$form['details']]); // Validira vsebnik details ``` -[Dogodek onValidate |#Event onValidate] na obrazcu se vedno sproži in nanj ne vpliva `setValidationScope`. Dogodek `onValidate` na vsebniku se sproži le, če je ta vsebnik določen za delno preverjanje. +`setValidationScope` ne vpliva na [#dogodek onValidate] pri obrazcu, ki bo vedno poklican. Dogodek `onValidate` pri vsebniku bo sprožen samo, če je ta vsebnik označen za delno validacijo. diff --git a/forms/tr/@home.texy b/forms/tr/@home.texy index 9c9767a43e..06ba1aeba8 100644 --- a/forms/tr/@home.texy +++ b/forms/tr/@home.texy @@ -1,31 +1,31 @@ -Formlar -******* +Nette Forms +*********** <div class=perex> -Nette Forms web formlarının oluşturulmasında devrim yarattı. Tek yapmanız gereken birkaç net kod satırı yazmaktı ve işleme, JavaScript ve sunucu doğrulamasının yanı sıra endüstri lideri güvenlik de dahil olmak üzere bir formunuz oldu. Nasıl olduğunu görelim +Nette Forms, web formlarının oluşturulmasında devrim yarattı. Aniden, birkaç anlaşılır kod satırı yazmak yeterli oldu ve oluşturma, JavaScript ve sunucu tarafı doğrulama dahil olmak üzere hazır bir formunuz oldu ve ayrıca en üst düzeyde güvenliydi. Nasıl yapılacağını göstereceğiz -- dost formlar oluşturun -- gönderilen verileri doğrulayın -- öğeleri tam olarak gerektiği gibi çizin +- kullanıcı dostu formlar oluşturma +- gönderilen verileri doğrulama +- öğeleri tam olarak gerektiği gibi oluşturma </div> -Nette Forms ile doğrulama yazmak (hem sunucu hem de istemci tarafında) gibi rutin görevleri azaltabilir, hata ve güvenlik sorunları olasılığını en aza indirebilirsiniz. +Nette Forms kullanarak, doğrulama yazma (ayrıca çift, sunucu ve istemci tarafında) gibi birçok rutin görevden kaçınırsınız, hata ve güvenlik açığı olasılığını en aza indirirsiniz. -Formları Nette Uygulamasının bir parçası olarak (yani sunumlarda) veya bağımsız olarak kullanabilirsiniz. Her iki durumda da kullanım biraz farklı olduğundan, sizin için ayrı talimatlar hazırladık: +Formları Nette Uygulamasının bir parçası olarak (yani presenter'larda) veya tamamen bağımsız olarak kullanabilirsiniz. Her iki durumda da kullanım biraz farklı olduğundan, sizin için iki kılavuz hazırladık: <div class="wiki-buttons"> -<div> "Sunuculardaki formlar .[wiki-button]":in-presenter </div> -<div> "Bağımsız formlar .[wiki-button]":standalone </div> +<div> "Presenter'lardaki Formlar .[wiki-button]":in-presenter </div> +<div> "Bağımsız Formlar .[wiki-button]":standalone </div> </div> Kurulum ------- -[Composer'ı |best-practices:composer] kullanarak paketi indirin ve yükleyin: +Kütüphaneyi [Composer|best-practices:composer] aracını kullanarak indirip kurabilirsiniz: ```shell composer require nette/forms diff --git a/forms/tr/@left-menu.texy b/forms/tr/@left-menu.texy index 8283edb10d..f9169bf428 100644 --- a/forms/tr/@left-menu.texy +++ b/forms/tr/@left-menu.texy @@ -1,14 +1,14 @@ -Formlar -******* -- [Genel Bakış |@home] -- [Sunuculardaki Formlar |in-presenter] -- [Bağımsız Formlar |standalone] -- [Form Kontrolleri |controls] +Nette Forms +*********** +- [Giriş |@home] +- [Presenter'lardaki Formlar|in-presenter] +- [Bağımsız Formlar|standalone] +- [Form Öğeleri |controls] - [Doğrulama |validation] -- [Rendering |rendering] -- [Konfigürasyon |Configuration] +- [Oluşturma |rendering] +- [Yapılandırma |configuration] -Daha fazla okuma +Daha Fazla Okuma **************** -- [En iyi uygulamalar |best-practices:] +- [Kılavuzlar ve yöntemler |best-practices:] diff --git a/forms/tr/@meta.texy b/forms/tr/@meta.texy new file mode 100644 index 0000000000..8dfe82f311 --- /dev/null +++ b/forms/tr/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokümantasyonu}} diff --git a/forms/tr/configuration.texy b/forms/tr/configuration.texy index eec53c669c..77fa3c84ba 100644 --- a/forms/tr/configuration.texy +++ b/forms/tr/configuration.texy @@ -1,8 +1,8 @@ -Formları Yapılandırma -********************* +Form Yapılandırması +******************* .[perex] -Yapılandırmada varsayılan [form hata mesajlarını |validation] değiştirebilirsiniz. +Yapılandırmada, varsayılan [form hata mesajları|validation] değiştirebilirsiniz. ```neon forms: @@ -30,4 +30,32 @@ forms: Nette\Forms\Controls\CsrfProtection::Protection: 'Your session has expired. Please return to the home page and try again.' ``` -Tüm çerçeveyi ve dolayısıyla yapılandırma dosyalarını bile kullanmıyorsanız, varsayılan hata mesajlarını doğrudan `Nette\Forms\Validator::$messages` alanından değiştirebilirsiniz. +İşte Türkçe çevirisi: + +```neon +forms: + messages: + Equal: '%s girin.' + NotEqual: 'Bu değer %s olmamalıdır.' + Filled: 'Bu alan zorunludur.' + Blank: 'Bu alan boş olmalıdır.' + MinLength: 'Lütfen en az %d karakter girin.' + MaxLength: 'Lütfen en fazla %d karakter girin.' + Length: 'Lütfen %d ile %d karakter uzunluğunda bir değer girin.' + Email: 'Geçerli bir e-posta adresi girin.' + URL: 'Lütfen geçerli bir URL girin.' + Integer: 'Geçerli bir tamsayı girin.' + Float: 'Geçerli bir sayı girin.' + Min: 'Lütfen %d veya daha büyük bir değer girin.' + Max: 'Lütfen %d veya daha küçük bir değer girin.' + Range: 'Lütfen %d ile %d arasında bir değer girin.' + MaxFileSize: 'Yüklenen dosyanın boyutu en fazla %d bayt olabilir.' + MaxPostSize: 'Yüklenen veriler %d bayt sınırını aşıyor.' + MimeType: 'Yüklenen dosya beklenen biçimde değil.' + Image: 'Yüklenen dosya JPEG, GIF, PNG, WebP veya AVIF biçiminde bir resim olmalıdır.' + Nette\Forms\Controls\SelectBox::Valid: 'Lütfen geçerli bir seçenek seçin.' + Nette\Forms\Controls\UploadControl::Valid: 'Dosya yükleme sırasında bir hata oluştu.' + Nette\Forms\Controls\CsrfProtection::Protection: 'Oturumunuzun süresi doldu. Lütfen ana sayfaya dönüp tekrar deneyin.' +``` + +Eğer tüm framework'ü ve dolayısıyla yapılandırma dosyalarını kullanmıyorsanız, varsayılan hata mesajlarını doğrudan `Nette\Forms\Validator::$messages` dizisinde değiştirebilirsiniz. diff --git a/forms/tr/controls.texy b/forms/tr/controls.texy index fe5264d994..b45be2ca83 100644 --- a/forms/tr/controls.texy +++ b/forms/tr/controls.texy @@ -1,280 +1,375 @@ -Form Kontrolleri -**************** +Form Elemanları +*************** .[perex] -Yerleşik form kontrollerine genel bakış. +Standart form elemanlarının özeti. -addText(string|int $name, $label=null): TextInput .[method] -=========================================================== +addText(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +================================================================================================== -Tek satırlık metin alanı ekler ( [TextInput |api:Nette\Forms\Controls\TextInput] sınıfı). Kullanıcı alanı doldurmazsa, boş bir dize döndürür `''` veya `null` döndürmek üzere değiştirmek için `setNullable()` kullanın. +Tek satırlık bir metin kutusu ekler (sınıf [TextInput |api:Nette\Forms\Controls\TextInput]). Kullanıcı alanı doldurmazsa, boş bir dize `''` döndürür veya `setNullable()` kullanarak `null` döndürmesini belirleyebilirsiniz. ```php -$form->addText('name', 'Name:') +$form->addText('name', 'İsim:') ->setRequired() ->setNullable(); ``` -UTF-8'i otomatik olarak doğrular, sol ve sağ boşlukları keser ve bir saldırgan tarafından gönderilebilecek satır sonlarını kaldırır. +UTF-8'i otomatik olarak doğrular, sol ve sağ boşlukları kırpar ve bir saldırganın gönderebileceği satır sonlarını kaldırır. -Maksimum uzunluk `setMaxLength()` kullanılarak sınırlandırılabilir. [addFilter() |validation#Modifying Input Values] kullanıcı tarafından girilen değeri değiştirmenize olanak tanır. +Maksimum uzunluk `setMaxLength()` ile sınırlandırılabilir. Kullanıcı tarafından girilen değeri değiştirmek [addFilter() |validation#Girişi Değiştirme] ile mümkündür. -Giriş öğesinin [karakterini |https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types] `search`, `tel`, `url`, `range`, `date`, `datetime-local`, `month`, `time`, `week`, `color` olarak değiştirmek için `setHtmlType()` kullanın. `number` ve `email` türleri yerine, sunucu tarafı doğrulama sağlayan [addInteger |#addInteger] ve [addEmail'i |#addEmail] kullanmanızı öneririz. +`setHtmlType()` kullanarak metin kutusunun görsel karakterini `search`, `tel` veya `url` gibi türlere değiştirebilirsiniz, bkz. [şartname|https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types]. Tür değişikliğinin yalnızca görsel olduğunu ve doğrulama işlevinin yerini tutmadığını unutmayın. `url` türü için belirli bir [URL kuralı |validation#Metin Girişleri] eklemek uygundur. -```php -$form->addText('color', 'Choose color:') - ->setHtmlType('color') - ->addRule($form::Pattern, 'invalid value', '[0-9a-f]{6}'); -``` +.[note] +`number`, `range`, `email`, `date`, `datetime-local`, `time` ve `color` gibi diğer giriş türleri için, sunucu tarafı doğrulaması sağlayan [#addInteger], [#addFloat], [#addEmail], [#addDate], [#addTime], [#addDateTime] ve [#addColor] gibi özel metotları kullanın. `month` ve `week` türleri henüz tüm tarayıcılarda tam olarak desteklenmemektedir. -Varsayılan değer gibi bir şey olan boş değer öğe için ayarlanabilir, ancak kullanıcı bunun üzerine yazmazsa boş dize veya `null` döndürür. +Elemana, varsayılan değere benzeyen ancak kullanıcı değiştirmezse elemanın boş bir dize veya `null` döndürdüğü sözde boş değer (empty-value) atanabilir. ```php -$form->addText('phone', 'Phone:') +$form->addText('phone', 'Telefon:') ->setHtmlType('tel') - ->setEmptyValue('+420'); + ->setEmptyValue('+90'); ``` addTextArea(string|int $name, $label=null): TextArea .[method] ============================================================== -Çok satırlı bir metin alanı ekler ( [TextArea |api:Nette\Forms\Controls\TextArea] sınıfı). Kullanıcı alanı doldurmazsa, boş bir dize döndürür `''` veya `null` döndürmek üzere değiştirmek için `setNullable()` kullanın. +Çok satırlı metin girmek için bir alan ekler (sınıf [TextArea |api:Nette\Forms\Controls\TextArea]). Kullanıcı alanı doldurmazsa, boş bir dize `''` döndürür veya `setNullable()` kullanarak `null` döndürmesini belirleyebilirsiniz. ```php -$form->addTextArea('note', 'Note:') - ->addRule($form::MaxLength, 'Your note is way too long', 10000); +$form->addTextArea('note', 'Not:') + ->addRule($form::MaxLength, 'Not çok uzun', 10000); ``` -UTF-8'i otomatik olarak doğrular ve satır sonlarını `\n` olarak normalleştirir. Tek satırlı bir giriş alanının aksine, boşlukları kırpmaz. +UTF-8'i otomatik olarak doğrular ve satır ayırıcılarını `\n` olarak normalleştirir. Tek satırlık giriş alanının aksine, boşluk kırpma işlemi yapılmaz. -Maksimum uzunluk `setMaxLength()` kullanılarak sınırlandırılabilir. [addFilter() |validation#Modifying Input Values] kullanıcı tarafından girilen değeri değiştirmenize olanak tanır. Boş değer olarak adlandırılan değeri `setEmptyValue()` adresini kullanarak ayarlayabilirsiniz. +Maksimum uzunluk `setMaxLength()` ile sınırlandırılabilir. Kullanıcı tarafından girilen değeri değiştirmek [addFilter() |validation#Girişi Değiştirme] ile mümkündür. `setEmptyValue()` kullanarak sözde boş değer (empty-value) ayarlanabilir. addInteger(string|int $name, $label=null): TextInput .[method] ============================================================== -Tamsayı için giriş alanı ekler ( [TextInput |api:Nette\Forms\Controls\TextInput] sınıfı). Kullanıcı hiçbir şey girmezse bir tamsayı veya `null` döndürür. +Tamsayı girmek için bir alan ekler (sınıf [TextInput |api:Nette\Forms\Controls\TextInput]). Kullanıcı hiçbir şey girmezse tamsayı veya `null` döndürür. + +```php +$form->addInteger('year', 'Yıl:') + ->addRule($form::Range, 'Yıl %d ile %d arasında olmalıdır.', [1900, 2023]); +``` + +Eleman `<input type="number">` olarak render edilir. `setHtmlType()` metodunu kullanarak türü, kaydırıcı şeklinde görüntülemek için `range` olarak veya `number` türünün özel davranışları olmayan standart bir metin alanı tercih ediyorsanız `text` olarak değiştirebilirsiniz. + + +addFloat(string|int $name, $label=null): TextInput .[method]{data-version:3.1.12} +================================================================================= + +Ondalık sayı girmek için bir alan ekler (sınıf [TextInput |api:Nette\Forms\Controls\TextInput]). Kullanıcı hiçbir şey girmezse float veya `null` döndürür. ```php -$form->addInteger('level', 'Level:') +$form->addFloat('level', 'Seviye:') ->setDefaultValue(0) - ->addRule($form::Range, 'Level must be between %d and %d.', [0, 100]); + ->addRule($form::Range, 'Seviye %d ile %d arasında olmalıdır.', [0, 100]); ``` +Eleman `<input type="number">` olarak render edilir. `setHtmlType()` metodunu kullanarak türü, kaydırıcı şeklinde görüntülemek için `range` olarak veya `number` türünün özel davranışları olmayan standart bir metin alanı tercih ediyorsanız `text` olarak değiştirebilirsiniz. + +Nette ve Chrome tarayıcısı, ondalık ayırıcı olarak hem virgülü hem de noktayı kabul eder. Bu işlevselliğin Firefox'ta da kullanılabilir olması için, ilgili eleman veya tüm sayfa için `lang` niteliğini ayarlamanız önerilir, örneğin `<html lang="tr">`. + -addEmail(string|int $name, $label=null): TextInput .[method] -============================================================ +addEmail(string|int $name, $label=null, int $maxLength=255): TextInput .[method] +================================================================================ -Geçerlilik kontrolü ile e-posta adresi alanı ekler (class [TextInput |api:Nette\Forms\Controls\TextInput]). Kullanıcı alanı doldurmazsa, boş bir dize döndürür `''` veya `null` döndürmek üzere değiştirmek için `setNullable()` kullanın. +E-posta adresi girmek için bir alan ekler (sınıf [TextInput |api:Nette\Forms\Controls\TextInput]). Kullanıcı alanı doldurmazsa, boş bir dize `''` döndürür veya `setNullable()` kullanarak `null` döndürmesini belirleyebilirsiniz. ```php -$form->addEmail('email', 'Email:'); +$form->addEmail('email', 'E-posta:'); ``` -Değerin geçerli bir e-posta adresi olduğunu doğrular. Etki alanının gerçekten var olduğunu doğrulamaz, yalnızca sözdizimi doğrulanır. UTF-8'i otomatik olarak doğrular, sol ve sağ boşlukları kırpar. +Değerin geçerli bir e-posta adresi olup olmadığını doğrular. Alan adının gerçekten var olup olmadığı kontrol edilmez, yalnızca sözdizimi doğrulanır. UTF-8'i otomatik olarak doğrular, sol ve sağ boşlukları kırpar. -Maksimum uzunluk `setMaxLength()` kullanılarak sınırlandırılabilir. [addFilter() |validation#Modifying Input Values] kullanıcı tarafından girilen değeri değiştirmenize olanak tanır. Boş değer olarak adlandırılan değeri `setEmptyValue()` adresini kullanarak ayarlayabilirsiniz. +Maksimum uzunluk `setMaxLength()` ile sınırlandırılabilir. Kullanıcı tarafından girilen değeri değiştirmek [addFilter() |validation#Girişi Değiştirme] ile mümkündür. `setEmptyValue()` kullanarak sözde boş değer (empty-value) ayarlanabilir. -addPassword(string|int $name, $label=null): TextInput .[method] -=============================================================== +addPassword(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +====================================================================================================== -Parola alanı ekler ( [TextInput |api:Nette\Forms\Controls\TextInput] sınıfı). +Şifre girmek için bir alan ekler (sınıf [TextInput |api:Nette\Forms\Controls\TextInput]). ```php -$form->addPassword('password', 'Password:') +$form->addPassword('password', 'Şifre:') ->setRequired() - ->addRule($form::MinLength, 'Password has to be at least %d characters long', 8) - ->addRule($form::Pattern, 'Password must contain a number', '.*[0-9].*'); + ->addRule($form::MinLength, 'Şifre en az %d karakter olmalıdır', 8) + ->addRule($form::Pattern, 'Bir rakam içermelidir', '.*[0-9].*'); ``` -Formu yeniden gönderdiğinizde, girdi boş olacaktır. UTF-8'i otomatik olarak doğrular, sol ve sağ boşlukları keser ve bir saldırgan tarafından gönderilebilecek satır sonlarını kaldırır. +Form yeniden görüntülendiğinde alan boş olacaktır. UTF-8'i otomatik olarak doğrular, sol ve sağ boşlukları kırpar ve bir saldırganın gönderebileceği satır sonlarını kaldırır. addCheckbox(string|int $name, $caption=null): Checkbox .[method] ================================================================ -Bir onay kutusu ekler ( [Checkbox |api:Nette\Forms\Controls\Checkbox] sınıfı). Alan, işaretli olup olmamasına bağlı olarak `true` veya `false` döndürür. +Bir onay kutusu ekler (sınıf [Checkbox |api:Nette\Forms\Controls\Checkbox]). İşaretli olup olmadığına bağlı olarak `true` veya `false` değerini döndürür. ```php -$form->addCheckbox('agree', 'I agree with terms') - ->setRequired('You must agree with our terms'); +$form->addCheckbox('agree', 'Şartları kabul ediyorum') + ->setRequired('Şartları kabul etmeniz gerekiyor'); ``` -addCheckboxList(string|int $name, $label=null, array $items=null): CheckboxList .[method] -========================================================================================= +addCheckboxList(string|int $name, $label=null, ?array $items=null): CheckboxList .[method] +========================================================================================== -Birden fazla öğe seçmek için onay kutuları listesi ekler (sınıf [CheckboxList |api:Nette\Forms\Controls\CheckboxList]). Seçilen öğelerin anahtar dizisini döndürür. `getSelectedItems()` yöntemi anahtarlar yerine değerler döndürür. +Birden çok öğe seçmek için onay kutuları ekler (sınıf [CheckboxList |api:Nette\Forms\Controls\CheckboxList]). Seçilen öğelerin anahtarlarından oluşan bir dizi döndürür. `getSelectedItems()` metodu anahtarlar yerine değerleri döndürür. ```php -$form->addCheckboxList('colors', 'Colors:', [ - 'r' => 'red', - 'g' => 'green', - 'b' => 'blue', +$form->addCheckboxList('colors', 'Renkler:', [ + 'r' => 'kırmızı', + 'g' => 'yeşil', + 'b' => 'mavi', ]); ``` -Öğe dizisini üçüncü parametre olarak veya `setItems()` yöntemiyle iletiriz. +Sunulan öğelerin dizisini üçüncü parametre olarak veya `setItems()` metoduyla iletiriz. -Kullanabilirsiniz `setDisabled(['r', 'g'])` tek tek öğeleri devre dışı bırakmak için. +`setDisabled(['r', 'g'])` kullanarak bireysel öğeleri devre dışı bırakabilirsiniz. -Öğe, sahtecilik yapılmadığını ve seçilen öğelerin gerçekten sunulanlardan biri olduğunu ve devre dışı bırakılmadığını otomatik olarak kontrol eder. Bu önemli kontrol olmadan sunulan öğeleri almak için `getRawValue()` yöntemi kullanılabilir. +Eleman, sahtecilik yapılmadığını ve seçilen öğelerin gerçekten sunulanlardan biri olduğunu ve devre dışı bırakılmadığını otomatik olarak kontrol eder. `getRawValue()` metoduyla bu önemli kontrol olmadan gönderilen öğeleri alabilirsiniz. -Varsayılan değerler ayarlandığında, bunların sunulan öğelerden biri olup olmadığını da kontrol eder, aksi takdirde bir istisna atar. Bu kontrol `checkDefaultValue(false)` ile kapatılabilir. +Varsayılan seçili öğeleri ayarlarken, bunların sunulanlardan biri olup olmadığını da kontrol eder, aksi takdirde bir istisna fırlatır. Bu kontrol `checkDefaultValue(false)` ile kapatılabilir. +Formu `GET` metoduyla gönderiyorsanız, sorgu dizesinin boyutunu azaltan daha kompakt bir veri aktarım yöntemi seçebilirsiniz. Formun HTML niteliğini ayarlayarak etkinleştirilir: + +```php +$form->setHtmlAttribute('data-nette-compact'); +``` -addRadioList(string|int $name, $label=null, array $items=null): RadioList .[method] -=================================================================================== -[Radyo |api:Nette\Forms\Controls\RadioList] düğmeleri ekler ( [RadioList |api:Nette\Forms\Controls\RadioList] sınıfı). Seçilen öğenin anahtarını veya kullanıcı herhangi bir şey seçmediyse `null` adresini döndürür. `getSelectedItem()` yöntemi, anahtar yerine bir değer döndürür. +addRadioList(string|int $name, $label=null, ?array $items=null): RadioList .[method] +==================================================================================== + +Radyo düğmeleri ekler (sınıf [RadioList |api:Nette\Forms\Controls\RadioList]). Seçilen öğenin anahtarını veya kullanıcı hiçbir şey seçmezse `null` döndürür. `getSelectedItem()` metodu anahtar yerine değeri döndürür. ```php $sex = [ - 'm' => 'male', - 'f' => 'female', + 'm' => 'erkek', + 'f' => 'kadın', ]; -$form->addRadioList('gender', 'Gender:', $sex); +$form->addRadioList('gender', 'Cinsiyet:', $sex); ``` -Öğe dizisini üçüncü parametre olarak veya `setItems()` yöntemiyle iletiriz. +Sunulan öğelerin dizisini üçüncü parametre olarak veya `setItems()` metoduyla iletiriz. -Kullanabilirsiniz `setDisabled(['m'])` tek tek öğeleri devre dışı bırakmak için. +`setDisabled(['m', 'f'])` kullanarak bireysel öğeleri devre dışı bırakabilirsiniz. -Öğe, sahtecilik yapılmadığını ve seçilen öğenin gerçekten sunulanlardan biri olduğunu ve devre dışı bırakılmadığını otomatik olarak kontrol eder. Bu önemli kontrol yapılmadan sunulan öğeyi almak için `getRawValue()` yöntemi kullanılabilir. +Eleman, sahtecilik yapılmadığını ve seçilen öğenin gerçekten sunulanlardan biri olduğunu ve devre dışı bırakılmadığını otomatik olarak kontrol eder. `getRawValue()` metoduyla bu önemli kontrol olmadan gönderilen öğeyi alabilirsiniz. -Varsayılan değer ayarlandığında, bunun sunulan öğelerden biri olup olmadığını da kontrol eder, aksi takdirde bir istisna atar. Bu kontrol `checkDefaultValue(false)` ile kapatılabilir. +Varsayılan seçili öğeyi ayarlarken, bunun sunulanlardan biri olup olmadığını da kontrol eder, aksi takdirde bir istisna fırlatır. Bu kontrol `checkDefaultValue(false)` ile kapatılabilir. -addSelect(string|int $name, $label=null, array $items=null): SelectBox .[method] -================================================================================ +addSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): SelectBox .[method] +================================================================================================== -Seçme kutusu ekler ( [SelectBox |api:Nette\Forms\Controls\SelectBox] sınıfı). Seçilen öğenin anahtarını veya kullanıcı herhangi bir şey seçmediyse `null` adresini döndürür. `getSelectedItem()` yöntemi, anahtar yerine bir değer döndürür. +Bir seçme kutusu (select box) ekler (sınıf [SelectBox |api:Nette\Forms\Controls\SelectBox]). Seçilen öğenin anahtarını veya kullanıcı hiçbir şey seçmezse `null` döndürür. `getSelectedItem()` metodu anahtar yerine değeri döndürür. ```php $countries = [ - 'CZ' => 'Czech republic', - 'SK' => 'Slovakia', - 'GB' => 'United Kingdom', + 'TR' => 'Türkiye', + 'DE' => 'Almanya', + 'GB' => 'Büyük Britanya', ]; -$form->addSelect('country', 'Country:', $countries) - ->setDefaultValue('SK'); +$form->addSelect('country', 'Ülke:', $countries) + ->setDefaultValue('TR'); ``` -Öğe dizisini üçüncü parametre olarak veya `setItems()` yöntemiyle aktarırız. Öğe dizisi iki boyutlu da olabilir: +Sunulan öğelerin dizisini üçüncü parametre olarak veya `setItems()` metoduyla iletiriz. Öğeler iki boyutlu bir dizi de olabilir: ```php $countries = [ - 'Europe' => [ - 'CZ' => 'Czech republic', - 'SK' => 'Slovakia', - 'GB' => 'United Kingdom', + 'Avrupa' => [ + 'CZ' => 'Çek Cumhuriyeti', + 'DE' => 'Almanya', + 'FR' => 'Fransa', ], - 'CA' => 'Canada', - 'US' => 'USA', - '?' => 'other', + 'TR' => 'Türkiye', + 'US' => 'ABD', + '?' => 'diğer', ]; ``` -Seçim kutuları için, ilk öğenin genellikle özel bir anlamı vardır, eylem çağrısı görevi görür. Böyle bir girdi eklemek için `setPrompt()` yöntemini kullanın. +Seçme kutularında genellikle ilk öğenin özel bir anlamı vardır, bir eylem çağrısı olarak hizmet eder. Böyle bir öğe eklemek için `setPrompt()` metodu kullanılır. ```php -$form->addSelect('country', 'Country:', $countries) - ->setPrompt('Pick a country'); +$form->addSelect('country', 'Ülke:', $countries) + ->setPrompt('Ülke seçin'); ``` -Kullanabilirsiniz `setDisabled(['CZ', 'SK'])` tek tek öğeleri devre dışı bırakmak için. +`setDisabled(['DE', 'FR'])` kullanarak bireysel öğeleri devre dışı bırakabilirsiniz. -Öğe, sahtecilik yapılmadığını ve seçilen öğenin gerçekten sunulanlardan biri olduğunu ve devre dışı bırakılmadığını otomatik olarak kontrol eder. Bu önemli kontrol yapılmadan sunulan öğeyi almak için `getRawValue()` yöntemi kullanılabilir. +Eleman, sahtecilik yapılmadığını ve seçilen öğenin gerçekten sunulanlardan biri olduğunu ve devre dışı bırakılmadığını otomatik olarak kontrol eder. `getRawValue()` metoduyla bu önemli kontrol olmadan gönderilen öğeyi alabilirsiniz. -Varsayılan değer ayarlandığında, bunun sunulan öğelerden biri olup olmadığını da kontrol eder, aksi takdirde bir istisna atar. Bu kontrol `checkDefaultValue(false)` ile kapatılabilir. +Varsayılan seçili öğeyi ayarlarken, bunun sunulanlardan biri olup olmadığını da kontrol eder, aksi takdirde bir istisna fırlatır. Bu kontrol `checkDefaultValue(false)` ile kapatılabilir. -addMultiSelect(string|int $name, $label=null, array $items=null): MultiSelectBox .[method] -========================================================================================== +addMultiSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): MultiSelectBox .[method] +============================================================================================================ -Çok seçenekli seçim kutusu ekler ( [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox] sınıfı). Seçilen öğelerin anahtar dizisini döndürür. `getSelectedItems()` yöntemi anahtarlar yerine değerler döndürür. +Birden çok öğe seçmek için bir seçme kutusu ekler (sınıf [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox]). Seçilen öğelerin anahtarlarından oluşan bir dizi döndürür. `getSelectedItems()` metodu anahtarlar yerine değerleri döndürür. ```php -$form->addMultiSelect('countries', 'Countries:', $countries); +$form->addMultiSelect('countries', 'Ülke:', $countries); ``` -Öğe dizisini üçüncü parametre olarak veya `setItems()` yöntemiyle aktarırız. Öğe dizisi iki boyutlu da olabilir. +Sunulan öğelerin dizisini üçüncü parametre olarak veya `setItems()` metoduyla iletiriz. Öğeler iki boyutlu bir dizi de olabilir. -Kullanabilirsiniz `setDisabled(['CZ', 'SK'])` tek tek öğeleri devre dışı bırakmak için. +`setDisabled(['DE', 'GB'])` kullanarak bireysel öğeleri devre dışı bırakabilirsiniz. -Öğe, sahtecilik yapılmadığını ve seçilen öğelerin gerçekten sunulanlardan biri olduğunu ve devre dışı bırakılmadığını otomatik olarak kontrol eder. Bu önemli kontrol olmadan sunulan öğeleri almak için `getRawValue()` yöntemi kullanılabilir. +Eleman, sahtecilik yapılmadığını ve seçilen öğelerin gerçekten sunulanlardan biri olduğunu ve devre dışı bırakılmadığını otomatik olarak kontrol eder. `getRawValue()` metoduyla bu önemli kontrol olmadan gönderilen öğeleri alabilirsiniz. -Varsayılan değerler ayarlandığında, bunların sunulan öğelerden biri olup olmadığını da kontrol eder, aksi takdirde bir istisna atar. Bu kontrol `checkDefaultValue(false)` ile kapatılabilir. +Varsayılan seçili öğeleri ayarlarken, bunların sunulanlardan biri olup olmadığını da kontrol eder, aksi takdirde bir istisna fırlatır. Bu kontrol `checkDefaultValue(false)` ile kapatılabilir. addUpload(string|int $name, $label=null): UploadControl .[method] ================================================================= -Dosya yükleme alanı ekler ( [UploadControl |api:Nette\Forms\Controls\UploadControl] sınıfı). Kullanıcı bir dosya yüklememiş olsa bile [FileUpload |http:request#FileUpload] nesnesini döndürür, bu `FileUpload::hasFile()` yöntemiyle öğrenilebilir. +Dosya yüklemek için bir alan ekler (sınıf [UploadControl |api:Nette\Forms\Controls\UploadControl]). Kullanıcı hiçbir dosya göndermese bile bir [FileUpload |http:request#FileUpload] nesnesi döndürür, bu durum `FileUpload::hasFile()` metoduyla kontrol edilebilir. ```php $form->addUpload('avatar', 'Avatar:') - ->addRule($form::Image, 'Avatar must be JPEG, PNG, GIF or WebP') - ->addRule($form::MaxFileSize, 'Maximum size is 1 MB', 1024 * 1024); + ->addRule($form::Image, 'Avatar JPEG, PNG, GIF, WebP veya AVIF olmalıdır.') + ->addRule($form::MaxFileSize, 'Maksimum boyut 1 MB.', 1024 * 1024); ``` -Dosya doğru şekilde yüklenmediyse, form başarıyla gönderilmemiştir ve bir hata görüntülenir. Yani `FileUpload::isOk()` yöntemini kontrol etmek gerekli değildir. +Dosya doğru şekilde yüklenemezse, form başarıyla gönderilmez ve bir hata görüntülenir. Yani, başarılı bir gönderimde `FileUpload::isOk()` metodunu doğrulamaya gerek yoktur. -`FileUpload::getName()` yöntemi tarafından döndürülen orijinal dosya adına güvenmeyin, bir istemci uygulamanızı bozmak veya hacklemek amacıyla kötü amaçlı bir dosya adı gönderebilir. +`FileUpload::getName()` metodu tarafından döndürülen orijinal dosya adına asla güvenmeyin, istemci uygulamanıza zarar vermek veya hacklemek amacıyla kötü niyetli bir dosya adı göndermiş olabilir. -Kurallar `MimeType` ve `Image` gerekli dosya veya görüntü türünü imzasına göre tespit eder. Tüm dosyanın bütünlüğü kontrol edilmez. Bir görüntünün bozuk olup olmadığını örneğin [yüklemeye |http:request#toImage] çalışarak öğrenebilirsiniz. +`MimeType` ve `Image` kuralları, istenen türü dosya imzasına göre algılar ve bütünlüğünü doğrulamaz. Bir resmin bozuk olup olmadığını, örneğin onu [yükleme |http:request#toImage] deneyerek öğrenebilirsiniz. addMultiUpload(string|int $name, $label=null): UploadControl .[method] ====================================================================== -Çoklu dosya yükleme alanı ekler ( [UploadControl |api:Nette\Forms\Controls\UploadControl] sınıfı). [FileUpload |http:request#FileUpload] nesnelerinden oluşan bir dizi döndürür. `FileUpload::hasFile()` yöntemi her biri için `true` döndürür. +Aynı anda birden çok dosya yüklemek için bir alan ekler (sınıf [UploadControl |api:Nette\Forms\Controls\UploadControl]). [FileUpload |http:request#FileUpload] nesnelerinden oluşan bir dizi döndürür. Her birinde `FileUpload::hasFile()` metodu `true` döndürür. ```php -$form->addMultiUpload('files', 'Files:') - ->addRule($form::MaxLength, 'A maximum of %d files can be uploaded', 10); +$form->addMultiUpload('files', 'Dosyalar:') + ->addRule($form::MaxLength, 'En fazla %d dosya yüklenebilir', 10); ``` -Dosyalardan biri doğru şekilde yüklenemezse, form başarıyla gönderilmemiştir ve bir hata görüntülenir. Yani `FileUpload::isOk()` yöntemini kontrol etmek gerekli değildir. +Dosyalardan herhangi biri doğru şekilde yüklenemezse, form başarıyla gönderilmez ve bir hata görüntülenir. Yani, başarılı bir gönderimde `FileUpload::isOk()` metodunu doğrulamaya gerek yoktur. + +`FileUpload::getName()` metodu tarafından döndürülen orijinal dosya adlarına asla güvenmeyin, istemci uygulamanıza zarar vermek veya hacklemek amacıyla kötü niyetli bir dosya adı göndermiş olabilir. + +`MimeType` ve `Image` kuralları, istenen türü dosya imzasına göre algılar ve bütünlüğünü doğrulamaz. Bir resmin bozuk olup olmadığını, örneğin onu [yükleme |http:request#toImage] deneyerek öğrenebilirsiniz. + -`FileUpload::getName()` yöntemi tarafından döndürülen orijinal dosya adlarına güvenmeyin, bir istemci uygulamanızı bozmak veya hacklemek amacıyla kötü amaçlı bir dosya adı gönderebilir. +addDate(string|int $name, $label=null): DateTimeControl .[method]{data-version:3.1.14} +====================================================================================== -Kurallar `MimeType` ve `Image` gerekli dosya veya görüntü türünü imzasına göre tespit eder. Tüm dosyanın bütünlüğü kontrol edilmez. Bir görüntünün bozuk olup olmadığını örneğin [yüklemeye |http:request#toImage] çalışarak öğrenebilirsiniz. +Kullanıcının yıl, ay ve günden oluşan bir tarihi kolayca girmesini sağlayan bir alan ekler (sınıf [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). +Varsayılan değer olarak `DateTimeInterface` arayüzünü uygulayan nesneleri, zaman içeren bir dizeyi veya UNIX zaman damgasını temsil eden bir sayıyı kabul eder. Aynı durum, izin verilen minimum ve maksimum tarihi tanımlayan `Min`, `Max` veya `Range` kurallarının argümanları için de geçerlidir. -addHidden(string|int $name, string $default=null): HiddenField .[method] -======================================================================== +```php +$form->addDate('date', 'Tarih:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'Tarih en az bir ay önce olmalıdır.', new DateTime('-1 month')); +``` -Gizli alan ekler ( [HiddenField |api:Nette\Forms\Controls\HiddenField] sınıfı). +Standart olarak `DateTimeImmutable` nesnesi döndürür, `setFormat()` metoduyla [metin biçimi|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] veya zaman damgası belirtebilirsiniz: + +```php +$form->addDate('date', 'Tarih:') + ->setFormat('Y-m-d'); +``` + + +addTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=============================================================================================================== + +Kullanıcının saat, dakika ve isteğe bağlı olarak saniyeden oluşan bir zamanı kolayca girmesini sağlayan bir alan ekler (sınıf [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Varsayılan değer olarak `DateTimeInterface` arayüzünü uygulayan nesneleri, zaman içeren bir dizeyi veya UNIX zaman damgasını temsil eden bir sayıyı kabul eder. Bu girdilerden yalnızca zaman bilgisi kullanılır, tarih göz ardı edilir. Aynı durum, izin verilen minimum ve maksimum zamanı tanımlayan `Min`, `Max` veya `Range` kurallarının argümanları için de geçerlidir. Ayarlanan minimum değer maksimum değerden yüksekse, gece yarısını aşan bir zaman aralığı oluşturulur. + +```php +$form->addTime('time', 'Saat:', withSeconds: true) + ->addRule($form::Range, 'Saat %d ile %d arasında olmalıdır.', ['12:30', '13:30']); +``` + +Standart olarak `DateTimeImmutable` nesnesi (1 Ocak yıl 1 tarihiyle) döndürür, `setFormat()` metoduyla [metin biçimi|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] belirtebilirsiniz: + +```php +$form->addTime('time', 'Saat:') + ->setFormat('H:i'); +``` + + +addDateTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=================================================================================================================== + +Kullanıcının yıl, ay, gün, saat, dakika ve isteğe bağlı olarak saniyeden oluşan bir tarih ve saati kolayca girmesini sağlayan bir alan ekler (sınıf [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Varsayılan değer olarak `DateTimeInterface` arayüzünü uygulayan nesneleri, zaman içeren bir dizeyi veya UNIX zaman damgasını temsil eden bir sayıyı kabul eder. Aynı durum, izin verilen minimum ve maksimum tarihi tanımlayan `Min`, `Max` veya `Range` kurallarının argümanları için de geçerlidir. + +```php +$form->addDateTime('datetime', 'Tarih ve Saat:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'Tarih en az bir ay önce olmalıdır.', new DateTime('-1 month')); +``` + +Standart olarak `DateTimeImmutable` nesnesi döndürür, `setFormat()` metoduyla [metin biçimi|https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] veya zaman damgası belirtebilirsiniz: + +```php +$form->addDateTime('datetime') + ->setFormat(DateTimeControl::FormatTimestamp); +``` + + +addColor(string|int $name, $label=null): ColorPicker .[method]{data-version:3.1.14} +=================================================================================== + +Renk seçmek için bir alan ekler (sınıf [ColorPicker |api:Nette\Forms\Controls\ColorPicker]). Renk `#rrggbb` biçiminde bir dizedir. Kullanıcı seçim yapmazsa, siyah renk `#000000` döndürülür. + +```php +$form->addColor('color', 'Renk:') + ->setDefaultValue('#3C8ED7'); +``` + + +addHidden(string|int $name, ?string $default=null): HiddenField .[method] +========================================================================= + +Gizli bir alan ekler (sınıf [HiddenField |api:Nette\Forms\Controls\HiddenField]). ```php $form->addHidden('userid'); ``` -Boş bir dize yerine `null` döndürmek üzere değiştirmek için `setNullable()` adresini kullanın. [addFilter() |validation#Modifying Input Values], gönderilen değeri değiştirmenize olanak tanır. +`setNullable()` kullanarak boş dize yerine `null` döndürmesini ayarlayabilirsiniz. Gönderilen değeri değiştirmek [addFilter() |validation#Girişi Değiştirme] ile mümkündür. + +Eleman gizli olsa da, değerin hala bir saldırgan tarafından değiştirilebileceğini veya sahtesinin yapılabileceğini **unutmamak önemlidir**. Veri manipülasyonuyla ilgili güvenlik risklerini önlemek için sunucu tarafında alınan tüm değerleri her zaman dikkatlice doğrulayın ve geçerleyin. addSubmit(string|int $name, $caption=null): SubmitButton .[method] ================================================================== -Gönder düğmesi ekler ( [SubmitButton |api:Nette\Forms\Controls\SubmitButton] sınıfı). +Bir gönderme düğmesi ekler (sınıf [SubmitButton |api:Nette\Forms\Controls\SubmitButton]). ```php -$form->addSubmit('submit', 'Register'); +$form->addSubmit('submit', 'Gönder'); ``` -Formda birden fazla gönder düğmesi olması mümkündür: +Formda birden fazla gönderme düğmesi olabilir: ```php -$form->addSubmit('register', 'Register'); -$form->addSubmit('cancel', 'Cancel'); +$form->addSubmit('register', 'Kaydol'); +$form->addSubmit('cancel', 'İptal'); ``` -Hangisinin tıklandığını bulmak için şunu kullanın: +Hangisine tıklandığını öğrenmek için şunu kullanın: ```php if ($form['register']->isSubmittedBy()) { @@ -282,48 +377,48 @@ if ($form['register']->isSubmittedBy()) { } ``` -Bir gönder düğmesine basıldığında (*İptal* veya *Önizleme* düğmeleri gibi) formu doğrulamak istemiyorsanız, [setValidationScope() |validation#Disabling Validation] ile bunu kapatabilirsiniz. +Düğmeye basıldığında tüm formu doğrulamak istemiyorsanız (örneğin *İptal* veya *Önizleme* düğmeleri için), [setValidationScope() |validation#Doğrulamayı Devre Dışı Bırakma] kullanın. addButton(string|int $name, $caption): Button .[method] ======================================================= -Gönderme işlevi olmayan düğme ( [Button |api:Nette\Forms\Controls\Button] sınıfı) ekler. Diğer işlevleri id'ye bağlamak için kullanışlıdır, örneğin bir JavaScript eylemi. +Gönderme işlevi olmayan bir düğme ekler (sınıf [Button |api:Nette\Forms\Controls\Button]). Bu nedenle, başka bir işlev için kullanılabilir, örneğin tıklandığında bir JavaScript fonksiyonunu çağırmak için. ```php -$form->addButton('raise', 'Raise salary') +$form->addButton('raise', 'Maaşı Artır') ->setHtmlAttribute('onclick', 'raiseSalary()'); ``` -addImageButton(string|int $name, string $src=null, string $alt=null): ImageButton .[method] -=========================================================================================== +addImageButton(string|int $name, ?string $src=null, ?string $alt=null): ImageButton .[method] +============================================================================================= -Resim biçiminde gönder düğmesi ekler ( [ImageButton |api:Nette\Forms\Controls\ImageButton] sınıfı). +Resim şeklinde bir gönderme düğmesi ekler (sınıf [ImageButton |api:Nette\Forms\Controls\ImageButton]). ```php $form->addImageButton('submit', '/path/to/image'); ``` -Birden fazla gönder düğmesi kullanırken, hangisinin tıklandığını şu şekilde öğrenebilirsiniz `$form['submit']->isSubmittedBy()`. +Birden fazla gönderme düğmesi kullanırken, hangisine tıklandığını `$form['submit']->isSubmittedBy()` kullanarak öğrenebilirsiniz. addContainer(string|int $name): Container .[method] =================================================== -Bir alt form ( [Container |api:Nette\Forms\Container] sınıfı) veya bir formla aynı şekilde ele alınabilen bir [konteyner |api:Nette\Forms\Container] ekler. Bu, `setDefaults()` veya `getValues()` gibi yöntemleri kullanabileceğiniz anlamına gelir. +Forma bir alt form (sınıf [Container|api:Nette\Forms\Container]), yani bir konteyner ekler, buna forma eklediğimiz gibi diğer elemanları ekleyebiliriz. `setDefaults()` veya `getValues()` metotları da çalışır. ```php $sub1 = $form->addContainer('first'); -$sub1->addText('name', 'Your name:'); -$sub1->addEmail('email', 'Email:'); +$sub1->addText('name', 'Adınız:'); +$sub1->addEmail('email', 'E-posta:'); $sub2 = $form->addContainer('second'); -$sub2->addText('name', 'Your name:'); -$sub2->addEmail('email', 'Email:'); +$sub2->addText('name', 'Adınız:'); +$sub2->addEmail('email', 'E-posta:'); ``` -Gönderilen veriler daha sonra çok boyutlu bir yapı olarak döndürülür: +Gönderilen verileri daha sonra çok boyutlu bir yapı olarak döndürür: ```php [ @@ -339,99 +434,101 @@ Gönderilen veriler daha sonra çok boyutlu bir yapı olarak döndürülür: ``` -Ayarlara Genel Bakış .[#toc-overview-of-settings] -================================================= +Ayarların Özeti +=============== -Tüm öğeler için aşağıdaki yöntemleri çağırabiliriz (tam bir genel bakış için [API belgelerine |https://api.nette.org/forms/master/Nette/Forms/Controls.html] bakın): +Tüm elemanlarda aşağıdaki metotları çağırabiliriz ([API dokümantasyonu|https://api.nette.org/forms/master/Nette/Forms/Controls.html] içinde tam liste): .[table-form-methods language-php] -| `setDefaultValue($value)` | varsayılan değeri ayarlar -| `getValue()` | mevcut değeri al -| `setOmitted()` | [atlanan değerler |#omitted values] -| `setDisabled()` | [girişleridevre dışı bırakma |#disabling inputs] +| `setDefaultValue($value)` | varsayılan değeri ayarlar +| `getValue()` | geçerli değeri alır +| `setOmitted()` | [##Değerin Atlanması] +| `setDisabled()` | [##Elemanların Devre Dışı Bırakılması] -Rendeleme: +Renderleme: .[table-form-methods language-php] -| `setCaption($caption)`| öğenin başlığını değiştir -| `setTranslator($translator)` | [çevirmeni ayarlar|rendering#translating] -| `setHtmlAttribute($name, $value)` | öğenin [HTML niteliğini ayarlar|rendering#HTML attributes] -| `setHtmlId($id)` | HTML niteliğini ayarlar `id` -| `setHtmlType($type)` | HTML özniteliğini ayarlar `type` -| `setHtmlName($name)`| HTML özniteliğini ayarlar `name` -| `setOption($key, $value)` | [render verilerini ayarlar|rendering#Options] +| `setCaption($caption)` | eleman etiketini değiştirir +| `setTranslator($translator)` | [çevirmeni |rendering#Çeviri] ayarlar +| `setHtmlAttribute($name, $value)` | elementin [HTML niteliğini |rendering#HTML Nitelikleri] ayarlar +| `setHtmlId($id)` | HTML `id` niteliğini ayarlar +| `setHtmlType($type)` | HTML `type` niteliğini ayarlar +| `setHtmlName($name)` | HTML `name` niteliğini ayarlar +| `setOption($key, $value)` | [renderleme ayarları |rendering#Options] Doğrulama: .[table-form-methods language-php] -| `setRequired()` | [zorunlu alan |validation] -| `addRule()` | [doğrulama kuralını ayarla|validation#Rules] -| `addCondition()`, `addConditionOn()` | [doğrulama koşulunu ayarla|validation#Conditions] -| `addError($message)`| [hata mesajı iletme |validation#processing-errors] +| `setRequired()` | [zorunlu eleman |validation] +| `addRule()` | [doğrulama kuralı |validation#Kurallar] ayarı +| `addCondition()`, `addConditionOn()` | [doğrulama koşulunu |validation#Koşullar] ayarlar +| `addError($message)` | [hata mesajı iletme |validation#İşleme Sırasındaki Hatalar] -Aşağıdaki yöntemler `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` öğeleri için çağrılabilir: +`addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` elemanlarında aşağıdaki metotları çağırabiliriz: .[table-form-methods language-php] -| `setNullable()`| getValue() işlevinin boş dize yerine `null` döndürüp döndürmeyeceğini ayarlar -| `setEmptyValue($value)` | boş dize olarak kabul edilen özel değeri ayarlar -| `setMaxLength($length)`| izin verilen maksimum karakter sayısını ayarlar -| `addFilter($filter)`| [Girdi Değerlerini Değiştirme |validation#Modifying Input Values] +| `setNullable()` | getValue()'nin boş dize yerine `null` döndürüp döndürmeyeceğini ayarlar +| `setEmptyValue($value)` | boş dize olarak kabul edilen özel bir değer ayarlar +| `setMaxLength($length)` | izin verilen maksimum karakter sayısını ayarlar +| `addFilter($filter)` | [giriş düzenleme |validation#Girişi Değiştirme] -Atlanan Değerler .[#toc-omitted-values] ---------------------------------------- +Değerin Atlanması +================= -Kullanıcı tarafından girilen değerle ilgilenmiyorsanız, `$form->getValues​()` yöntemi tarafından sağlanan veya işleyicilere aktarılan sonuçtan çıkarmak için `setOmitted()` adresini kullanabiliriz. Bu, doğrulama için çeşitli parolalar, antispam alanları vb. için uygundur. +Kullanıcı tarafından doldurulan değerle ilgilenmiyorsak, `setOmitted()` kullanarak onu `$form->getValues()` metodunun sonucundan veya işleyicilere iletilen verilerden çıkarabiliriz. Bu, çeşitli kontrol şifreleri, antispam elemanları vb. için kullanışlıdır. ```php -$form->addPassword('passwordVerify', 'Password again:') - ->setRequired('Fill your password again to check for typo') - ->addRule($form::Equal, 'Password mismatch', $form['password']) +$form->addPassword('passwordVerify', 'Kontrol için şifre:') + ->setRequired('Lütfen kontrol için şifreyi tekrar girin') + ->addRule($form::Equal, 'Şifreler eşleşmiyor', $form['password']) ->setOmitted(); ``` -Girişleri Devre Dışı Bırakma .[#toc-disabling-inputs] ------------------------------------------------------ +Elemanların Devre Dışı Bırakılması +================================== -Bir girişi devre dışı bırakmak için `setDisabled()` adresini çağırabilirsiniz. Böyle bir alan kullanıcı tarafından düzenlenemez. +Elemanlar `setDisabled()` ile devre dışı bırakılabilir. Böyle bir eleman kullanıcı tarafından düzenlenemez. ```php -$form->addText('username', 'User name:') +$form->addText('username', 'Kullanıcı adı:') ->setDisabled(); ``` -Tarayıcının devre dışı bırakılan alanları sunucuya göndermediğini unutmayın, bu nedenle bunları `$form->getValues()` işlevi tarafından döndürülen verilerde bile bulamazsınız. +Devre dışı bırakılmış elemanlar tarayıcı tarafından sunucuya hiç gönderilmez, bu nedenle onları `$form->getValues()` fonksiyonu tarafından döndürülen verilerde bulamazsınız. Ancak `setOmitted(false)` ayarlarsanız, Nette bu verilere varsayılan değerlerini dahil eder. -Bir alan için varsayılan bir değer ayarlıyorsanız, bunu yalnızca devre dışı bıraktıktan sonra yapmalısınız: +`setDisabled()` çağrıldığında, güvenlik nedeniyle elemanın değeri **silinir**. Varsayılan bir değer ayarlıyorsanız, bunu devre dışı bıraktıktan sonra yapmanız gerekir: ```php -$form->addText('username', 'User name:') +$form->addText('username', 'Kullanıcı adı:') ->setDisabled() ->setDefaultValue($userName); ``` +Devre dışı bırakılmış elemanlara alternatif olarak, tarayıcının sunucuya gönderdiği `readonly` HTML niteliğine sahip elemanlar vardır. Eleman yalnızca okunabilir olsa da, değerinin hala bir saldırgan tarafından değiştirilebileceğini veya sahtesinin yapılabileceğini **unutmamak önemlidir**. + -Özel Kontroller .[#toc-custom-controls] -======================================= +Özel Elemanlar +============== -Çok çeşitli yerleşik form kontrollerinin yanı sıra, forma aşağıdaki gibi özel kontroller ekleyebilirsiniz: +Geniş yerleşik form elemanları yelpazesinin yanı sıra, forma şu şekilde özel elemanlar ekleyebilirsiniz: ```php -$form->addComponent(new DateInput('Date:'), 'date'); -// alternatif sözdizimi: $form['date'] = new DateInput('Date:'); +$form->addComponent(new DateInput('Tarih:'), 'date'); +// alternatif sözdizimi: $form['date'] = new DateInput('Tarih:'); ``` .[note] -Form, [Container | component-model:#Container] sınıfının bir torunudur ve elemanlar [Component | component-model:#Component]'in torunlarıdır. +Form, [Container |component-model:#Container] sınıfının bir alt sınıfıdır ve bireysel elemanlar [Component |component-model:#Component] sınıfının alt sınıflarıdır. -Özel öğeler eklemek için yeni form yöntemleri tanımlamanın bir yolu vardır (örneğin `$form->addZip()`). Bunlar uzantı yöntemleri olarak adlandırılır. Dezavantajı, editörlerdeki kod ipuçlarının bunlar için çalışmayacağıdır. +Özel elemanlar eklemek için formun yeni metotlarını (örneğin `$form->addZip()`) tanımlamanın bir yolu vardır. Buna extension methods denir. Dezavantajı, editörlerde kod tamamlama özelliğinin onlar için çalışmamasıdır. ```php use Nette\Forms\Container; -// addZip(string $name, string $label = null) yöntemini ekler -Container::extensionMethod('addZip', function (Container $form, string $name, string $label = null) { +// addZip(string $name, ?string $label = null) metodunu ekliyoruz +Container::extensionMethod('addZip', function (Container $form, string $name, ?string $label = null) { return $form->addText($name, $label) - ->addRule($form::Pattern, 'At least 5 numbers', '[0-9]{5}'); + ->addRule($form::Pattern, 'En az 5 rakam', '[0-9]{5}'); }); // kullanım @@ -439,10 +536,10 @@ $form->addZip('zip', 'Posta kodu:'); ``` -Düşük Seviyeli Alanlar .[#toc-low-level-fields] -=============================================== +Düşük Seviyeli Elemanlar +======================== -Forma bir öğe eklemek için `$form->addXyz()` adresini çağırmak zorunda değilsiniz. Bunun yerine form öğeleri yalnızca şablonlarda tanıtılabilir. Bu, örneğin dinamik öğeler oluşturmanız gerekiyorsa kullanışlıdır: +Yalnızca şablonda yazdığımız ve `$form->addXyz()` metotlarından biriyle forma eklemediğimiz elemanları da kullanabilirsiniz. Örneğin, veritabanından kayıtları listelerken ve kaç tane olacağını ve ID'lerinin ne olacağını önceden bilmediğimizde ve her satırda bir onay kutusu veya radyo düğmesi görüntülemek istediğimizde, onu şablonda kodlamak yeterlidir: ```latte {foreach $items as $item} @@ -450,13 +547,13 @@ Forma bir öğe eklemek için `$form->addXyz()` adresini çağırmak zorunda de {/foreach} ``` -Gönderdikten sonra değerleri alabilirsiniz: +Ve gönderdikten sonra değeri öğreniriz: ```php $data = $form->getHttpData($form::DataText, 'sel[]'); $data = $form->getHttpData($form::DataText | $form::DataKeys, 'sel[]'); ``` -İlk parametrede, öğe türünü belirtirsiniz ( `type=file` için`DataFile`, `text`, `password` veya `email` gibi tek satırlı girdiler için `DataLine` ve diğerleri için `DataText` ). İkinci parametre `name` HTML özniteliğiyle eşleşir. Anahtarları korumanız gerekiyorsa, ilk parametreyi `DataKeys` ile birleştirebilirsiniz. Bu `select`, `radioList` veya `checkboxList` için kullanışlıdır. +burada ilk parametre eleman türüdür (`DataFile` `type=file` için, `DataLine` `text`, `password`, `email` vb. gibi tek satırlık girdiler için ve `DataText` diğer tümü için) ve ikinci parametre `sel[]` HTML name niteliğine karşılık gelir. Eleman türünü, elemanların anahtarlarını koruyan `DataKeys` değeriyle birleştirebiliriz. Bu özellikle `select`, `radioList` ve `checkboxList` için kullanışlıdır. -`getHttpData()` temizlenmiş girdi döndürür. Bu durumda, form tarafından gönderilen saldırgan ne olursa olsun, her zaman geçerli UTF-8 dizeleri dizisi olacaktır. Güvenli veri almak istiyorsanız doğrudan `$_POST` veya `$_GET` ile çalışmaya bir alternatiftir. +Önemli olan, `getHttpData()`'nın temizlenmiş bir değer döndürmesidir, bu durumda her zaman geçerli UTF-8 dizelerinden oluşan bir dizi olacaktır, saldırgan sunucuya ne göndermeye çalışırsa çalışsın. Bu, doğrudan `$_POST` veya `$_GET` ile çalışmaya benzer, ancak önemli farkla her zaman temiz veriler döndürmesidir, tıpkı standart Nette form elemanlarında alıştığınız gibi. diff --git a/forms/tr/in-presenter.texy b/forms/tr/in-presenter.texy index cef4cc8ee8..80bf4ba256 100644 --- a/forms/tr/in-presenter.texy +++ b/forms/tr/in-presenter.texy @@ -1,36 +1,36 @@ -Sunuculardaki Formlar -********************* +Presenter'larda Formlar +*********************** .[perex] -Nette Forms, web formları oluşturmayı ve işlemeyi önemli ölçüde kolaylaştırır. Bu bölümde, formları sunumların içinde nasıl kullanacağınızı öğreneceksiniz. +Nette Forms, web formlarının oluşturulmasını ve işlenmesini önemli ölçüde kolaylaştırır. Bu bölümde, presenter'lar içinde formların kullanımını öğreneceksiniz. -Çerçevenin geri kalanı olmadan bunları tamamen bağımsız olarak kullanmakla ilgileniyorsanız, [bağımsız formlar |standalone] için bir kılavuz vardır. +Framework'ün geri kalanı olmadan tamamen bağımsız olarak nasıl kullanılacağını merak ediyorsanız, sizin için [bağımsız kullanım|standalone] kılavuzu bulunmaktadır. -İlk Form .[#toc-first-form] -=========================== +İlk Form +======== -Basit bir kayıt formu yazmaya çalışacağız. Kodu şu şekilde görünecektir: +Basit bir kayıt formu yazmayı deneyelim. Kodu şöyle olacaktır: ```php use Nette\Application\UI\Form; $form = new Form; -$form->addText('name', 'Name:'); -$form->addPassword('password', 'Password:'); -$form->addSubmit('send', 'Sign up'); +$form->addText('name', 'İsim:'); +$form->addPassword('password', 'Şifre:'); +$form->addSubmit('send', 'Kaydol'); $form->onSuccess[] = [$this, 'formSucceeded']; ``` -ve tarayıcıda sonuç aşağıdaki gibi görünmelidir: +ve tarayıcıda şöyle görünecektir: -[* form-en.webp *] +[* form-cs.webp *] -Sunucudaki form `Nette\Application\UI\Form` sınıfının bir nesnesidir, selefi `Nette\Forms\Form` bağımsız kullanım için tasarlanmıştır. Biz buna isim, şifre ve gönderme butonu alanlarını ekledik. Son olarak, `$form->onSuccess` satırında form gönderildikten ve başarılı bir doğrulama yapıldıktan sonra `$this->formSucceeded()` metodunun çağrılması gerektiği yazıyor. +Presenter'daki form, `Nette\Application\UI\Form` sınıfının bir nesnesidir, öncülü `Nette\Forms\Form` bağımsız kullanım için tasarlanmıştır. Ona isim, şifre ve gönderme düğmesi olarak adlandırılan elemanları ekledik. Ve son olarak, `$form->onSuccess` satırı, gönderildikten ve başarılı bir şekilde doğrulandıktan sonra `$this->formSucceeded()` metodunun çağrılması gerektiğini söyler. -Sunucunun bakış açısından, form ortak bir bileşendir. Bu nedenle, bir bileşen olarak ele alınır ve [fabrika yöntemi |application:components#Factory Methods] kullanılarak sunucuya dahil edilir. Bu şekilde görünecektir: +Presenter açısından form, sıradan bir bileşendir. Bu nedenle, bir bileşen olarak ele alınır ve [fabrika metotları |application:components#Fabrika Metotları] kullanılarak presenter'a dahil edilir. Şöyle görünecektir: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} use Nette; use Nette\Application\UI\Form; @@ -39,9 +39,9 @@ class HomePresenter extends Nette\Application\UI\Presenter protected function createComponentRegistrationForm(): Form { $form = new Form; - $form->addText('name', 'Name:'); - $form->addPassword('password', 'Password:'); - $form->addSubmit('send', 'Sign up'); + $form->addText('name', 'İsim:'); + $form->addPassword('password', 'Şifre:'); + $form->addSubmit('send', 'Kaydol'); $form->onSuccess[] = [$this, 'formSucceeded']; return $form; } @@ -49,93 +49,90 @@ class HomePresenter extends Nette\Application\UI\Presenter public function formSucceeded(Form $form, $data): void { // burada form tarafından gönderilen verileri işleyeceğiz - // $data->name isim içeriyor - // $data->password parola içerir + // $data->name ismi içerir + // $data->password şifreyi içerir $this->flashMessage('Başarıyla kaydoldunuz.'); $this->redirect('Home:'); } } ``` -Ve şablonda render işlemi `{control}` etiketi kullanılarak yapılır: +Ve şablonda formu `{control}` etiketiyle render ederiz: -```latte .{file:app/Presenters/templates/Home/default.latte} -<h1>Registration</h1> +```latte .{file:app/Presentation/Home/default.latte} +<h1>Kayıt</h1> {control registrationForm} ``` -Ve hepsi bu kadar :-) İşlevsel ve mükemmel şekilde [güvence |#Vulnerability Protection] altına alınmış bir formumuz var. +Ve aslında hepsi bu :-) Çalışan ve mükemmel [güvenli |#Güvenlik Açıklarına Karşı Koruma] bir formumuz var. -Şimdi muhtemelen bunun çok hızlı olduğunu düşünüyor, `formSucceeded()` yönteminin nasıl çağrıldığını ve aldığı parametrelerin ne olduğunu merak ediyorsunuz. Elbette haklısınız, bu bir açıklamayı hak ediyor. +Ve şimdi muhtemelen bunun çok hızlı olduğunu düşünüyorsunuz, `formSucceeded()` metodunun nasıl çağrıldığını ve aldığı parametrelerin ne olduğunu merak ediyorsunuz. Evet, haklısınız, bu açıklama gerektiriyor. -Nette, [Hollywood tarzı |application:components#Hollywood style] olarak adlandırdığımız harika bir mekanizma ile geliyor. Sürekli olarak bir şey olup olmadığını sormak zorunda kalmak yerine ("form gönderildi mi?", "geçerli bir şekilde gönderildi mi?" veya "sahte değil mi?"), framework'e "form geçerli bir şekilde tamamlandığında, bu yöntemi çağır" diyorsunuz ve üzerinde daha fazla çalışmayı bırakıyorsunuz. JavaScript'te programlama yapıyorsanız, bu tarz programlamaya aşinasınızdır. Belirli bir [olay |nette:glossary#Events] gerçekleştiğinde çağrılan fonksiyonlar yazarsınız. Ve dil onlara uygun argümanları iletir. +Nette, [Hollywood tarzı |application:components#Hollywood Tarzı] dediğimiz taze bir mekanizma ile birlikte gelir. Bir geliştirici olarak sürekli bir şeylerin olup olmadığını sormak yerine ("form gönderildi mi?", "geçerli bir şekilde gönderildi mi?" ve "sahtesi yapılmadı mı?"), framework'e "form geçerli bir şekilde doldurulduğunda, bu metodu çağır" dersiniz ve geri kalan işi ona bırakırsınız. JavaScript'te programlama yapıyorsanız, bu programlama tarzını yakından tanırsınız. Belirli bir [olay |nette:glossary#Olaylar Events] gerçekleştiğinde çağrılan fonksiyonlar yazarsınız. Ve dil onlara ilgili argümanları iletir. -Yukarıdaki sunum kodu bu şekilde oluşturulmuştur. `$form->onSuccess` dizisi, form gönderildiğinde ve doğru şekilde doldurulduğunda Nette'in çağıracağı PHP geri aramalarının listesini temsil eder. -Sunucunun [yaşam döngüsü |application:presenters#Life Cycle of Presenter] içinde bu bir sinyal olarak adlandırılır, bu nedenle `action*` yönteminden sonra ve `render*` yönteminden önce çağrılırlar. -Ve her geri aramaya ilk parametre olarak formun kendisini ve ikinci parametre olarak da [ArrayHash |utils:arrays#ArrayHash] nesnesi olarak gönderilen verileri aktarır. Form nesnesine ihtiyacınız yoksa ilk parametreyi atlayabilirsiniz. İkinci parametre daha da kullanışlı olabilir, ancak bunu daha [sonra |#Mapping to Classes] anlatacağız. +Yukarıdaki presenter kodu tam olarak bu şekilde oluşturulmuştur. `$form->onSuccess` dizisi, form gönderildiğinde ve doğru bir şekilde doldurulduğunda (yani geçerli olduğunda) Nette'nin çağıracağı PHP geri aramalarının (callback) bir listesini temsil eder. [Presenter yaşam döngüsü |application:presenters#Presenter Yaşam Döngüsü] çerçevesinde, bu sözde bir sinyaldir, yani `action*` metodundan sonra ve `render*` metodundan önce çağrılırlar. Ve her geri aramaya ilk parametre olarak formun kendisini ve ikinci parametre olarak gönderilen verileri [ArrayHash |utils:arrays#ArrayHash] nesnesi şeklinde iletir. Form nesnesine ihtiyacınız yoksa ilk parametreyi atlayabilirsiniz. Ve ikinci parametre daha akıllı olabilir, ancak bunun hakkında [daha sonra |#Sınıflara Eşleme] konuşacağız. -`$data` nesnesi, kullanıcı tarafından girilen verilerle birlikte `name` ve `password` özelliklerini içerir. Genellikle verileri, örneğin veritabanına ekleme gibi daha ileri işlemler için doğrudan göndeririz. Ancak, işleme sırasında bir hata oluşabilir, örneğin kullanıcı adı zaten alınmış olabilir. Bu durumda, `addError()` adresini kullanarak hatayı forma geri iletiriz ve bir hata mesajıyla birlikte yeniden çizilmesine izin veririz: +`$data` nesnesi, kullanıcının doldurduğu verilerle `name` ve `password` anahtarlarını içerir. Genellikle verileri doğrudan daha fazla işleme göndeririz, bu örneğin veritabanına ekleme olabilir. Ancak işleme sırasında bir hata oluşabilir, örneğin kullanıcı adı zaten alınmış olabilir. Bu durumda, hatayı `addError()` kullanarak forma geri iletiriz ve hata mesajıyla birlikte yeniden render edilmesini sağlarız. ```php -$form->addError('Sorry, username is already in use.'); +$form->addError('Üzgünüz, bu kullanıcı adı zaten kullanılıyor.'); ``` -Buna ek olarak `onSuccess`, ayrıca `onSubmit`: form doğru doldurulmamış olsa bile form gönderildikten sonra geri aramalar her zaman çağrılır. Ve son olarak `onError`: geri aramalar yalnızca gönderim geçerli değilse çağrılır. Hatta `onSuccess` veya `onSubmit` adresindeki formu `addError()` adresini kullanarak geçersiz kılsak bile çağrılırlar. +`onSuccess` dışında bir de `onSubmit` vardır: geri aramalar, form doğru doldurulmamış olsa bile her zaman form gönderildikten sonra çağrılır. Ve ayrıca `onError`: geri aramalar yalnızca gönderim geçerli değilse çağrılır. `onSuccess` veya `onSubmit` içinde formu `addError()` ile geçersiz kılsak bile çağrılırlar. -Formu işledikten sonra bir sonraki sayfaya yönlendireceğiz. Bu, formun *yenile*, *geri* düğmesine tıklanarak veya tarayıcı geçmişi taşınarak istenmeden yeniden gönderilmesini önler. +Formu işledikten sonra bir sonraki sayfaya yönlendiririz. Bu, *yenile*, *geri* düğmesiyle veya tarayıcı geçmişinde gezinerek formun istenmeyen şekilde yeniden gönderilmesini önler. -Daha fazla [form denetimi |controls] eklemeyi deneyin. +Diğer [form elemanları|controls] eklemeyi deneyin. -Kontrollere Erişim .[#toc-access-to-controls] -============================================= +Elemanlara Erişim +================= -Form, sunucunun bir bileşenidir, bizim durumumuzda `registrationForm` olarak adlandırılmıştır (fabrika yönteminin adından sonra `createComponentRegistrationForm`), bu nedenle sunucunun herhangi bir yerinde formu kullanarak forma ulaşabilirsiniz: +Form, presenter'ın bir bileşenidir, bizim durumumuzda `registrationForm` olarak adlandırılmıştır (fabrika metodu `createComponentRegistrationForm` adına göre), bu nedenle presenter'ın herhangi bir yerinde forma şu şekilde erişebilirsiniz: ```php $form = $this->getComponent('registrationForm'); // alternatif sözdizimi: $form = $this['registrationForm']; ``` -Ayrıca bireysel form kontrolleri de bileşenlerdir, bu nedenle onlara da aynı şekilde erişebilirsiniz: +Bireysel form elemanları da bileşenlerdir, bu nedenle onlara aynı şekilde erişebilirsiniz: ```php $input = $form->getComponent('name'); // veya $input = $form['name']; $button = $form->getComponent('send'); // veya $button = $form['send']; ``` -Kontroller unset kullanılarak kaldırılır: +Elemanlar unset ile kaldırılır: ```php unset($form['name']); ``` -Doğrulama Kuralları .[#toc-validation-rules] -============================================ +Doğrulama Kuralları +=================== -Geçerli* kelimesi birkaç kez kullanıldı, ancak formun henüz doğrulama kuralları yok. Hadi düzeltelim. +*Geçerli* kelimesi geçti, ancak formun henüz herhangi bir doğrulama kuralı yok. Bunu düzeltelim. -Ad zorunlu olacaktır, bu nedenle onu, argümanı kullanıcı doldurmazsa görüntülenecek hata mesajının metni olan `setRequired()` yöntemiyle işaretleyeceğiz. Herhangi bir argüman verilmezse, varsayılan hata mesajı kullanılır. +İsim zorunlu olacak, bu yüzden onu `setRequired()` metoduyla işaretleyeceğiz, argümanı kullanıcı ismi doldurmazsa görüntülenecek hata mesajının metnidir. Argüman belirtmezsek, varsayılan hata mesajı kullanılır. ```php -$form->addText('name', 'Name:') - ->setRequired('Please fill your name.'); +$form->addText('name', 'İsim:') + ->setRequired('Lütfen ismi girin'); ``` -İsim doldurulmadan formu göndermeye çalıştığınızda bir hata mesajının görüntülendiğini ve siz doldurana kadar tarayıcı veya sunucunun formu reddettiğini göreceksiniz. +Formu doldurulmuş isim olmadan göndermeyi deneyin ve bir hata mesajının görüntülendiğini ve tarayıcının veya sunucunun alanı doldurana kadar reddedeceğini göreceksiniz. -Aynı zamanda, örneğin girişe sadece boşluk yazarak sistemi aldatamazsınız. Mümkün değil. Nette sol ve sağ boşlukları otomatik olarak keser. Bunu deneyin. Bu, her tek satırlık girişte her zaman yapmanız gereken bir şeydir, ancak genellikle unutulur. Nette bunu otomatik olarak yapar. (Formları kandırmayı deneyebilir ve isim olarak çok satırlı bir dize gönderebilirsiniz. Burada bile Nette aldanmayacak ve satır sonları boşluk olarak değişecektir). +Aynı zamanda, sisteme sadece boşluk yazarak hile yapamazsınız. Hayır. Nette sol ve sağ boşlukları otomatik olarak kaldırır. Deneyin. Bu, her tek satırlık girişle her zaman yapmanız gereken bir şeydir, ancak genellikle unutulur. Nette bunu otomatik olarak yapar. (Formu aldatmayı deneyebilir ve isim olarak çok satırlı bir dize gönderebilirsiniz. Nette burada da aldanmaz ve satır sonlarını boşluklara dönüştürür.) -Form her zaman sunucu tarafında doğrulanır, ancak JavaScript doğrulaması da oluşturulur, bu da hızlıdır ve kullanıcı formu sunucuya göndermek zorunda kalmadan hatayı hemen öğrenir. Bu işlem `netteForms.js` komut dosyası tarafından gerçekleştirilir. -Bunu düzen şablonuna ekleyin: +Form her zaman sunucu tarafında doğrulanır, ancak aynı zamanda anında gerçekleşen ve kullanıcının hatayı formu sunucuya göndermeye gerek kalmadan hemen öğrendiği JavaScript doğrulaması da üretilir. Bu, `netteForms.js` betiği tarafından yapılır. Bunu layout şablonuna ekleyin: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Formun bulunduğu sayfanın kaynak koduna bakarsanız, Nette'in gerekli alanları `required` CSS sınıfına sahip öğelere eklediğini fark edebilirsiniz. Aşağıdaki stili şablona eklemeyi deneyin, "Ad" etiketi kırmızı olacaktır. Kullanıcılar için gerekli alanları zarif bir şekilde işaretliyoruz: +Form içeren sayfanın kaynak koduna bakarsanız, Nette'nin zorunlu elemanları `required` CSS sınıfına sahip elemanlara eklediğini fark edebilirsiniz. Şablona aşağıdaki stil sayfasını eklemeyi deneyin ve "İsim" etiketi kırmızı olacaktır. Bu şekilde kullanıcılara zorunlu elemanları zarifçe işaretleriz: ```latte <style> @@ -143,130 +140,132 @@ Formun bulunduğu sayfanın kaynak koduna bakarsanız, Nette'in gerekli alanlar </style> ``` -Ek doğrulama kuralları `addRule()` yöntemi ile eklenecektir. İlk parametre kuraldır, ikincisi yine hata mesajının metnidir ve isteğe bağlı doğrulama kuralı argümanı takip edebilir. Bu ne anlama geliyor? +Diğer doğrulama kurallarını `addRule()` metoduyla ekleriz. İlk parametre kuraldır, ikincisi yine hata mesajının metnidir ve bunu doğrulama kuralının bir argümanı takip edebilir. Bununla ne kastediliyor? -Form, bir sayı olması (`addInteger()`) ve belirli sınırlar içinde olması (`$form::Range`) koşuluyla başka bir isteğe bağlı girdi *yaş* alacaktır. Ve burada `addRule()`'un üçüncü argümanı olan aralığın kendisini kullanacağız: +Formu, tamsayı olması gereken (`addInteger()`) ve ayrıca izin verilen bir aralıkta (`$form::Range`) olması gereken yeni isteğe bağlı "yaş" alanıyla genişleteceğiz. Ve burada tam olarak `addRule()` metodunun üçüncü parametresini kullanacağız, bununla doğrulayıcıya istenen aralığı `[başlangıç, bitiş]` çifti olarak ileteceğiz: ```php -$form->addInteger('age', 'Age:') - ->addRule($form::Range, 'You must be older 18 years and be under 120.', [18, 120]); +$form->addInteger('age', 'Yaş:') + ->addRule($form::Range, 'Yaş 18 ile 120 arasında olmalıdır', [18, 120]); ``` .[tip] -Kullanıcı alanı doldurmazsa, alan isteğe bağlı olduğu için doğrulama kuralları doğrulanmayacaktır. +Kullanıcı alanı doldurmazsa, eleman isteğe bağlı olduğu için doğrulama kuralları kontrol edilmeyecektir. -Açıkçası küçük bir yeniden düzenleme için yer mevcut. Hata mesajında ve üçüncü parametrede, sayılar çift olarak listelenmiştir ve bu ideal değildir. [Çok dilli |rendering#translating] bir [form |rendering#translating] oluşturuyor olsaydık ve sayıları içeren mesajın birden fazla dile çevrilmesi gerekseydi, değerleri değiştirmek daha zor olurdu. Bu nedenle, `%d` ikame karakterleri kullanılabilir: +Burada küçük bir yeniden düzenleme için yer var. Hata mesajında ve üçüncü parametrede sayılar yinelenmiştir, bu ideal değildir. Eğer [çok dilli formlar |rendering#Çeviri] oluşturuyor olsaydık ve sayıları içeren mesaj birden çok dile çevrilmiş olsaydı, değerlerin olası bir değişikliği zorlaşırdı. Bu nedenle, `%d` yer tutucularını kullanmak mümkündür ve Nette değerleri tamamlayacaktır: ```php - ->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]); + ->addRule($form::Range, 'Yaş %d ile %d arasında olmalıdır', [18, 120]); ``` -Parola* alanına geri dönelim, bunu *gerekli* yapalım ve yine mesajdaki yedek karakterleri kullanarak minimum parola uzunluğunu (`$form::MinLength`) doğrulayalım: +Aynı zamanda zorunlu hale getireceğimiz ve ayrıca şifrenin minimum uzunluğunu (`$form::MinLength`) doğrulayacağımız `password` elemanına geri dönelim, yine yer tutucu kullanarak: ```php -$form->addPassword('password', 'Password:') - ->setRequired('Pick a password') - ->addRule($form::MinLength, 'Your password has to be at least %d long', 8); +$form->addPassword('password', 'Şifre:') + ->setRequired('Bir şifre seçin') + ->addRule($form::MinLength, 'Şifre en az %d karakter olmalıdır', 8); ``` -Kontrol için kullanıcının şifreyi tekrar girdiği forma `passwordVerify` şeklinde bir alan ekleyeceğiz. Doğrulama kurallarını kullanarak, her iki şifrenin de aynı olup olmadığını kontrol ediyoruz (`$form::Equal`). Ve bir argüman olarak [köşeli |#Access to Controls] parantez kullanarak ilk şifreye bir referans veriyoruz: +Forma bir de `passwordVerify` alanı ekleyelim, burada kullanıcı kontrol için şifreyi tekrar girecektir. Doğrulama kurallarını kullanarak her iki şifrenin aynı olup olmadığını kontrol edeceğiz (`$form::Equal`). Ve parametre olarak ilk şifreye [köşeli parantezler |#Elemanlara Erişim] kullanarak bir referans vereceğiz: ```php -$form->addPassword('passwordVerify', 'Password again:') - ->setRequired('Fill your password again to check for typo') - ->addRule($form::Equal, 'Password mismatch', $form['password']) +$form->addPassword('passwordVerify', 'Kontrol için şifre:') + ->setRequired('Lütfen kontrol için şifreyi tekrar girin') + ->addRule($form::Equal, 'Şifreler eşleşmiyor', $form['password']) ->setOmitted(); ``` -`setOmitted()` adresini kullanarak, değerini gerçekten önemsemediğimiz ve yalnızca doğrulama için var olan bir öğeyi işaretledik. Değeri `$data` adresine aktarılmaz. +`setOmitted()` kullanarak, değerinin aslında bizim için önemli olmadığı ve yalnızca doğrulama amacıyla var olan elemanı işaretledik. Değer `$data`'ya iletilmez. -PHP ve JavaScript'te doğrulama ile tamamen işlevsel bir formumuz var. Nette'nin doğrulama yetenekleri çok daha geniştir, koşullar oluşturabilir, bunlara göre bir sayfanın bölümlerini görüntüleyebilir ve gizleyebilirsiniz, vb. Her şeyi [form doğrul |validation] ama bölümünde bulabilirsiniz. +Böylece PHP ve JavaScript'te doğrulaması olan tamamen işlevsel bir formumuz oldu. Nette'nin doğrulama yetenekleri çok daha geniştir, koşullar oluşturabilir, bunlara göre sayfanın bölümlerini gösterebilir ve gizleyebilirsiniz vb. Her şeyi [form doğrulaması|validation] bölümünde öğreneceksiniz. -Varsayılan Değerler .[#toc-default-values] -========================================== +Varsayılan Değerler +=================== -Form denetimleri için genellikle varsayılan değerler ayarlarız: +Form elemanlarına genellikle varsayılan değerler atarız: ```php -$form->addEmail('email', 'Email') +$form->addEmail('email', 'E-posta') ->setDefaultValue($lastUsedEmail); ``` -Genellikle tüm kontroller için varsayılan değerleri aynı anda ayarlamak yararlıdır. Örneğin, form kayıtları düzenlemek için kullanıldığında. Kaydı veritabanından okuruz ve varsayılan değerler olarak ayarlarız: +Genellikle tüm elemanlara aynı anda varsayılan değerler atamak kullanışlıdır. Örneğin, form kayıtları düzenlemek için kullanıldığında. Veritabanından kaydı okur ve varsayılan değerleri atarız: ```php //$row = ['name' => 'John', 'age' => '33', /* ... */]; $form->setDefaults($row); ``` -Kontrolleri tanımladıktan sonra `setDefaults()` adresini çağırın. +`setDefaults()`'u elemanları tanımladıktan sonra çağırın. -Formun Oluşturulması .[#toc-rendering-the-form] -=============================================== +Formun Render Edilmesi +====================== -Varsayılan olarak, form bir tablo olarak işlenir. Bireysel kontroller temel web erişilebilirlik yönergelerini takip eder. Tüm etiketler şu şekilde oluşturulur `<label>` elemanlarıdır ve girişleriyle ilişkilidir, etikete tıklamak imleci girişe taşır. +Standart olarak form bir tablo olarak render edilir. Bireysel elemanlar temel erişilebilirlik kuralını karşılar - tüm etiketler `<label>` olarak yazılır ve ilgili form elemanıyla ilişkilendirilir. Etikete tıklandığında imleç otomatik olarak form alanında görünür. -Her öğe için herhangi bir HTML niteliği ayarlayabiliriz. Örneğin, bir yer tutucu ekleyin: +Her elemana istediğimiz HTML niteliklerini atayabiliriz. Örneğin bir yer tutucu ekleyebiliriz: ```php -$form->addInteger('age', 'Age:') - ->setHtmlAttribute('placeholder', 'Please fill in the age'); +$form->addInteger('age', 'Yaş:') + ->setHtmlAttribute('placeholder', 'Lütfen yaşı girin'); ``` -Bir formu oluşturmanın gerçekten pek çok yolu vardır, bu yüzden bu [bölüm |rendering] oluşturma konusuna ayrılmıştır. +Formu render etmenin gerçekten çok sayıda yolu vardır, bu nedenle buna ayrılmış [renderleme hakkında ayrı bir bölüm|rendering] bulunmaktadır. -Sınıflarla Eşleme .[#toc-mapping-to-classes] -============================================ +Sınıflara Eşleme +================ -İkinci parametre olarak `$data` gönderilen veriyi bir `ArrayHash` nesnesi olarak alan `formSucceeded()` yöntemine geri dönelim. Bu `stdClass` gibi genel bir sınıf olduğundan, onunla çalışırken editörlerdeki özellikler için kod tamamlama veya statik kod analizi gibi bazı kolaylıklardan yoksun kalacağız. Bu, her form için, özellikleri ayrı kontrolleri temsil eden özel bir sınıfa sahip olarak çözülebilir. Örneğin: +İkinci parametre `$data`'da gönderilen verileri `ArrayHash` nesnesi olarak alan `formSucceeded()` metoduna geri dönelim. Bu, `stdClass` gibi genel bir sınıf olduğundan, onunla çalışırken belirli bir konfor eksikliği yaşayacağız, örneğin editörlerde özelliklerin önerilmesi veya statik kod analizi gibi. Bu, her form için özelliklerinin bireysel elemanları temsil ettiği belirli bir sınıfa sahip olarak çözülebilir. Örneğin: ```php class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -PHP 8.0'dan itibaren, bir kurucu kullanan bu zarif gösterimi kullanabilirsiniz: +Alternatif olarak, yapıcıyı kullanabilirsiniz: ```php class RegistrationFormData { public function __construct( public string $name, - public int $age, + public ?int $age, public string $password, ) { } } ``` -Nette'e verileri bu sınıfın nesneleri olarak döndürmesini nasıl söyleyebilirim? Düşündüğünüzden daha kolay. Tek yapmanız gereken, işleyicideki `$data` parametresinin türü olarak sınıfı belirtmektir: +Veri sınıfının özellikleri enum'lar da olabilir ve otomatik olarak eşlenirler. .{data-version:3.2.4} + +Nette'ye verileri bu sınıfın nesneleri olarak döndürmesini nasıl söyleriz? Düşündüğünüzden daha kolay. Sınıfı işleyici metodundaki `$data` parametresinin türü olarak belirtmek yeterlidir: ```php public function formSucceeded(Form $form, RegistrationFormData $data): void { - // $name is instance of RegistrationFormData + // $data, RegistrationFormData örneğidir $name = $data->name; // ... } ``` -Ayrıca tür olarak `array` belirtebilirsiniz ve ardından verileri bir dizi olarak iletir. +Tür olarak `array` de belirtebilirsiniz ve o zaman verileri bir dizi olarak iletir. -Benzer şekilde, parametre olarak hidrate edilecek bir sınıf adı veya nesne olarak ilettiğimiz `getValues()` yöntemini kullanabilirsiniz: +Benzer şekilde, sınıf adını veya hidratlanacak nesneyi parametre olarak ilettiğimiz `getValues()` fonksiyonunu da kullanabilirsiniz: ```php $data = $form->getValues(RegistrationFormData::class); $name = $data->name; ``` -Formlar konteynerlerden oluşan çok seviyeli bir yapıdan oluşuyorsa, her biri için ayrı bir sınıf oluşturun: +Formlar konteynerlerden oluşan çok seviyeli bir yapı oluşturuyorsa, her biri için ayrı bir sınıf oluşturun: ```php $form = new Form; @@ -283,32 +282,34 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` -Eşleme daha sonra `$person` özellik türünden konteyneri `PersonFormData` sınıfına eşlemesi gerektiğini bilir. Özellik bir kapsayıcı dizisi içerecekse, `array` türünü sağlayın ve doğrudan kapsayıcıyla eşlenecek sınıfı iletin: +Eşleme daha sonra `$person` özelliğinin türünden, konteyneri `PersonFormData` sınıfına eşlemesi gerektiğini anlar. Eğer özellik bir konteyner dizisi içeriyorsa, `array` türünü belirtin ve eşleme için sınıfı doğrudan konteynere iletin: ```php $person->setMappedType(PersonFormData::class); ``` +Formun veri sınıfının tasarımını `Nette\Forms\Blueprint::dataClass($form)` metodunu kullanarak oluşturabilirsiniz, bu da onu tarayıcı sayfasına yazdırır. Kodu daha sonra tıklayarak işaretleyip projeye kopyalamak yeterlidir. .{data-version:3.1.15} + -Çoklu Gönder Düğmeleri .[#toc-multiple-submit-buttons] -====================================================== +Birden Fazla Düğme +================== -Formda birden fazla düğme varsa, genellikle hangisine basıldığını ayırt etmemiz gerekir. Her düğme için kendi fonksiyonumuzu oluşturabiliriz. Bunu `onClick` [olayı |nette:glossary#Events] için bir işleyici olarak ayarlayın: +Formun birden fazla düğmesi varsa, genellikle hangisine basıldığını ayırt etmemiz gerekir. Her düğme için kendi işleyici fonksiyonumuzu oluşturabiliriz. Bunu [olay |nette:glossary#Olaylar Events] `onClick` için bir işleyici olarak ayarlayacağız: ```php -$form->addSubmit('save', 'Save') +$form->addSubmit('save', 'Kaydet') ->onClick[] = [$this, 'saveButtonPressed']; -$form->addSubmit('delete', 'Delete') +$form->addSubmit('delete', 'Sil') ->onClick[] = [$this, 'deleteButtonPressed']; ``` -Bu işleyiciler de `onSuccess` olayında olduğu gibi yalnızca formun geçerli olması durumunda çağrılır. Aradaki fark, belirttiğiniz türe bağlı olarak ilk parametrenin form yerine gönder düğmesi nesnesi olabilmesidir: +Bu işleyiciler, tıpkı `onSuccess` olayında olduğu gibi, yalnızca geçerli bir şekilde doldurulmuş form durumunda çağrılır. Fark, ilk parametre olarak form yerine gönderme düğmesinin iletilebilmesidir, belirttiğiniz türe bağlıdır: ```php public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) @@ -318,62 +319,61 @@ public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) } ``` -Bir form <kbd>Enter</kbd> tuşu ile gönderildiğinde, ilk düğme ile gönderilmiş gibi işlem görür. +Form <kbd>Enter</kbd> tuşuyla gönderildiğinde, ilk düğmeyle gönderilmiş gibi kabul edilir. -Olay onAnchor .[#toc-event-onanchor] -==================================== +onAnchor Olayı +============== -Bir fabrika yönteminde ( `createComponentRegistrationForm` gibi) bir form oluşturduğunuzda, henüz gönderilip gönderilmediğini veya hangi verilerle gönderildiğini bilmez. Ancak, gönderilen değerleri bilmemiz gereken durumlar vardır, belki de formun nasıl görüneceği onlara bağlıdır veya bağımlı seçim kutuları vb. için kullanılırlar. +Fabrika metodunda (örneğin `createComponentRegistrationForm` gibi) formu oluştururken, form henüz gönderilip gönderilmediğini veya hangi verilerle gönderildiğini bilmez. Ancak gönderilen değerleri bilmemiz gereken durumlar vardır, örneğin formun sonraki şekli bunlara bağlıdır veya bağımlı seçme kutuları (select box) için onlara ihtiyacımız vardır vb. -Bu nedenle, formu oluşturan kodun, form bağlandığında çağrılmasını sağlayabilirsiniz, yani form zaten sunum yapan kişiye bağlıdır ve gönderilen verileri bilir. Böyle bir kodu `$onAnchor` dizisine koyacağız: +Bu nedenle, formu oluşturan kodun bir kısmını, yalnızca sözde demirlendiğinde, yani presenter ile zaten bağlantılı olduğunda ve gönderilen verilerini bildiğinde çağrılmasını sağlayabilirsiniz. Böyle bir kodu `$onAnchor` dizisine iletiriz: ```php -$country = $form->addSelect('country', 'Country:', $this->model->getCountries()); -$city = $form->addSelect('city', 'City:'); +$country = $form->addSelect('country', 'Ülke:', $this->model->getCountries()); +$city = $form->addSelect('city', 'Şehir:'); $form->onAnchor[] = function () use ($country, $city) { - // form gönderildiği veriyi bildiğinde bu fonksiyon çağrılacaktır - // böylece getValue() yöntemini kullanabilirsiniz + // bu fonksiyon, formun gönderilip gönderilmediğini ve hangi verilerle gönderildiğini bildiğinde çağrılır + // bu nedenle getValue() metodu kullanılabilir $val = $country->getValue(); $city->setItems($val ? $this->model->getCities($val) : []); }; ``` -Güvenlik Açığı Koruması .[#toc-vulnerability-protection] -======================================================== +Güvenlik Açıklarına Karşı Koruma +================================ -Nette Framework güvenli olmak için büyük çaba sarf eder ve formlar en yaygın kullanıcı girdisi olduğundan, Nette formları aşılmaz kadar iyidir. Her şey dinamik ve şeffaf olarak korunur, hiçbir şeyin manuel olarak ayarlanması gerekmez. +Nette Framework güvenliğe büyük önem verir ve bu nedenle formların iyi bir şekilde korunmasına özen gösterir. Bunu tamamen şeffaf bir şekilde yapar ve manuel olarak hiçbir şey ayarlamayı gerektirmez. -Formları Siteler Arası Komut Dosyası [Yazma (XSS |nette:glossary#cross-site-scripting-xss] ) ve [Siteler Arası İstek Sahteciliği (CSRF) |nette:glossary#cross-site-request-forgery-csrf] gibi iyi bilinen güvenlik açıklarını hedef alan saldırılara karşı korumanın yanı sıra, artık düşünmek zorunda olmadığınız birçok küçük güvenlik görevini de yerine getirir. +Formları [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] ve [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF] saldırılarına karşı korumanın yanı sıra, sizin artık düşünmeniz gerekmeyen birçok küçük güvenlik önlemi alır. -Örneğin, girdilerden tüm kontrol karakterlerini filtreler ve UTF-8 kodlamasının geçerliliğini kontrol eder, böylece formdan gelen veriler her zaman temiz olur. Seçim kutuları ve radyo listeleri için, seçilen öğelerin gerçekten sunulanlardan olduğunu ve herhangi bir sahtecilik olmadığını doğrular. Tek satırlı metin girişi için, bir saldırganın oraya gönderebileceği satır sonu karakterlerini kaldırdığından daha önce bahsetmiştik. Çok satırlı girdiler için satır sonu karakterlerini normalleştirir. Ve böyle devam eder. +Örneğin, girdilerden tüm kontrol karakterlerini filtreler ve UTF-8 kodlamasının geçerliliğini kontrol eder, böylece formdan gelen veriler her zaman temiz olur. Seçme kutuları (select box) ve radyo listelerinde, seçilen öğelerin gerçekten sunulanlardan olduğunu ve sahtesinin yapılmadığını doğrular. Tek satırlık metin girişlerinde, saldırganın oraya göndermiş olabileceği satır sonu karakterlerini kaldırdığını zaten belirtmiştik. Çok satırlık girişlerde ise satır sonu karakterlerini normalleştirir. Ve böyle devam eder. -Nette, çoğu programcının varlığından bile haberdar olmadığı güvenlik açıklarını sizin için düzeltir. +Nette, birçok programcının varlığından bile haberdar olmadığı güvenlik risklerini sizin için çözer. -Bahsedilen CSRF saldırısı, bir saldırganın kurbanı, kurbanın tarayıcısında kurbanın o anda oturum açtığı sunucuya sessizce bir istek yürüten bir sayfayı ziyaret etmesi için kandırması ve sunucunun, isteğin kurban tarafından isteğe bağlı olarak yapıldığına inanmasıdır. Bu nedenle Nette, formun başka bir etki alanından POST yoluyla gönderilmesini engeller. Herhangi bir nedenle korumayı kapatmak ve formun başka bir etki alanından gönderilmesine izin vermek istiyorsanız, şunu kullanın: +Bahsedilen CSRF saldırısı, bir saldırganın kurbanı, kurbanın oturum açtığı sunucuya kurbanın tarayıcısında fark ettirmeden bir istek gerçekleştiren bir sayfaya çekmesinden oluşur ve sunucu, isteğin kurban tarafından kendi isteğiyle gerçekleştirildiğini varsayar. Bu nedenle Nette, POST formunun başka bir alan adından gönderilmesini engeller. Herhangi bir nedenle korumayı kapatmak ve formun başka bir alan adından gönderilmesine izin vermek isterseniz, şunu kullanın: ```php $form->allowCrossOrigin(); // DİKKAT! Korumayı kapatır! ``` -Bu koruma, `_nss` adlı bir SameSite çerezi kullanır. SameSite çerez koruması %100 güvenilir olmayabilir, bu nedenle token korumasını açmak iyi bir fikirdir: +Bu koruma, `_nss` adlı SameSite çerezini kullanır. SameSite çereziyle koruma %100 güvenilir olmayabilir, bu nedenle token ile korumayı da etkinleştirmek önerilir: ```php $form->addProtection(); ``` -Bu korumayı uygulamanızın hassas verileri değiştiren idari bölümündeki formlara uygulamanız şiddetle tavsiye edilir. Çerçeve, bir oturumda saklanan kimlik doğrulama belirtecini oluşturarak ve doğrulayarak CSRF saldırısına karşı koruma sağlar (argüman, belirtecin süresi dolmuşsa gösterilen hata mesajıdır). Bu nedenle formu görüntülemeden önce bir oturumun başlatılmış olması gerekir. Web sitesinin yönetim bölümünde, kullanıcının oturum açması nedeniyle oturum genellikle zaten başlatılmıştır. -Aksi takdirde, oturumu `Nette\Http\Session::start()` yöntemi ile başlatın. +Uygulamadaki hassas verileri değiştiren sitenin yönetim bölümündeki formları bu şekilde korumanızı öneririz. Framework, oturumda saklanan bir yetkilendirme token'ı üreterek ve doğrulayarak CSRF saldırısına karşı kendini savunur. Bu nedenle, formu görüntülemeden önce oturumun açık olması gerekir. Sitenin yönetim bölümünde, genellikle kullanıcı girişi nedeniyle oturum zaten başlatılmıştır. Aksi takdirde, oturumu `Nette\Http\Session::start()` metoduyla başlatın. -Bir Formu Birden Fazla Sunucuda Kullanma .[#toc-using-one-form-in-multiple-presenters] -====================================================================================== +Birden Fazla Presenter'da Aynı Form +=================================== -Bir formu birden fazla sunucuda kullanmanız gerekiyorsa, bunun için daha sonra sunucuya aktaracağınız bir fabrika oluşturmanızı öneririz. Böyle bir sınıf için uygun bir konum, örneğin, `app/Forms` dizinidir. +Bir formu birden fazla presenter'da kullanmanız gerekiyorsa, bunun için bir fabrika oluşturmanızı ve ardından bunu presenter'a iletmenizi öneririz. Böyle bir sınıf için uygun bir konum, örneğin `app/Forms` dizinidir. -Fabrika sınıfı şu şekilde görünebilir: +Fabrika sınıfı şöyle görünebilir: ```php use Nette\Application\UI\Form; @@ -383,14 +383,14 @@ class SignInFormFactory public function create(): Form { $form = new Form; - $form->addText('name', 'Name:'); - $form->addSubmit('send', 'Log in'); + $form->addText('name', 'İsim:'); + $form->addSubmit('send', 'Giriş yap'); return $form; } } ``` -Sunucu içerisindeki bileşenler için fabrika metodunda sınıfın formu üretmesini istiyoruz: +Sınıftan, presenter'daki bileşenler için fabrika metodunda formu üretmesini isteriz: ```php public function __construct( @@ -401,14 +401,14 @@ public function __construct( protected function createComponentSignInForm(): Form { $form = $this->formFactory->create(); - // formu değiştirebiliriz, burada örneğin düğmenin üzerindeki etiketi değiştiriyoruz - $form['login']->setCaption('Continue'); - $form->onSuccess[] = [$this, 'signInFormSubmitted']; // ve işleyici ekleyin + // formu değiştirebiliriz, burada örneğin düğme üzerindeki etiketi değiştiriyoruz + $form['send']->setCaption('Devam et'); + $form->onSuccess[] = [$this, 'signInFormSuceeded']; // ve bir işleyici ekliyoruz return $form; } ``` -Form işleme işleyicisi fabrikadan da teslim edilebilir: +Form işleme için işleyici, fabrikadan da sağlanabilir: ```php use Nette\Application\UI\Form; @@ -418,14 +418,14 @@ class SignInFormFactory public function create(): Form { $form = new Form; - $form->addText('name', 'Name:'); - $form->addSubmit('send', 'Log in'); + $form->addText('name', 'İsim:'); + $form->addSubmit('send', 'Giriş yap'); $form->onSuccess[] = function (Form $form, $data): void { - // gönderilen formumuzu burada işliyoruz + // burada form işlemeyi gerçekleştiriyoruz }; return $form; } } ``` -Böylece, Nette'deki formlara hızlı bir giriş yapmış olduk. Daha fazla ilham almak için dağıtımdaki [örnekler |https://github.com/nette/forms/tree/master/examples] dizinine bakmayı deneyin. +İşte, Nette'deki formlara hızlı bir giriş yaptık. Dağıtımdaki [examples|https://github.com/nette/forms/tree/master/examples] dizinine göz atmayı deneyin, burada daha fazla ilham bulacaksınız. diff --git a/forms/tr/rendering.texy b/forms/tr/rendering.texy index 2835653df8..2e5f03cddf 100644 --- a/forms/tr/rendering.texy +++ b/forms/tr/rendering.texy @@ -1,33 +1,35 @@ -Form Oluşturma -************** +Formların Render Edilmesi +************************* -Formların görünümü çok çeşitli olabilir. Uygulamada iki uç noktayla karşılaşabiliriz. Bir yandan, bir uygulamada görsel olarak birbirine benzeyen bir dizi form oluşturma ihtiyacı vardır ve `$form->render()` kullanarak şablon olmadan kolay oluşturmayı takdir ederiz. Bu durum genellikle yönetim arayüzleri için geçerlidir. +Formların görünümü çok çeşitli olabilir. Pratikte iki uç durumla karşılaşabiliriz. Bir yanda, uygulamada görsel olarak birbirine çok benzeyen bir dizi formu render etme ihtiyacı vardır ve `$form->render()` kullanarak şablon olmadan kolay render etmeyi takdir ederiz. Bu genellikle yönetim arayüzleri durumudur. -Öte yandan, her birinin benzersiz olduğu çeşitli formlar vardır. Bunların görünümü en iyi şablonda HTML dili kullanılarak tanımlanır. Ve elbette, bahsedilen her iki aşırı uca ek olarak, arada bir yere düşen birçok formla karşılaşacağız. +Diğer yanda, her birinin orijinal olduğu çeşitli formlar vardır. Görünümleri en iyi HTML diliyle form şablonunda tanımlanır. Ve elbette, bahsedilen her iki uç durumun yanı sıra, arada bir yerde bulunan birçok formla karşılaşacağız. -Latte ile Rendering .[#toc-rendering-with-latte] -================================================ +Latte ile Render Etme +===================== -[Latte şablonlama sistemi |latte:], formların ve öğelerinin oluşturulmasını temel olarak kolaylaştırır. İlk olarak, kod üzerinde tam kontrol elde etmek için formların öğe öğe manuel olarak nasıl oluşturulacağını göstereceğiz. Daha sonra bu işlemin nasıl [otomatikleştirileceğini |#Automatic rendering] göstereceğiz. +[Latte şablonlama sistemi|latte:] formların ve elemanlarının render edilmesini önemli ölçüde kolaylaştırır. Önce formları manuel olarak tek tek elemanlarla nasıl render edeceğimizi ve böylece kod üzerinde tam kontrol sahibi olacağımızı göstereceğiz. Daha sonra böyle bir render işlemini nasıl [otomatikleştirebileceğinizi |#Otomatik Render Etme] göstereceğiz. + +Formun Latte şablonu tasarımını `Nette\Forms\Blueprint::latte($form)` metodunu kullanarak oluşturabilirsiniz, bu da onu tarayıcı sayfasına yazdırır. Kodu daha sonra tıklayarak işaretleyip projeye kopyalamak yeterlidir. .{data-version:3.1.15} `{control}` ----------- -Bir formu oluşturmanın en kolay yolu bir şablon içine yazmaktır: +Formu render etmenin en basit yolu şablonda şunu yazmaktır: ```latte {control signInForm} ``` -Oluşturulan formun görünümü, [Renderer |#Renderer] ve [bireysel kontroller |#HTML Attributes] yapılandırılarak değiştirilebilir. +Bu şekilde render edilen formun görünümünü [#Renderer] ve [bireysel elemanlar |#HTML Nitelikleri] yapılandırarak etkileyebilirsiniz. `n:name` -------- -PHP kodundaki form tanımını HTML koduna bağlamak son derece kolaydır. Sadece `n:name` niteliklerini ekleyin. İşte bu kadar kolay! +PHP kodundaki form tanımını HTML koduyla ilişkilendirmek son derece kolaydır. Sadece `n:name` niteliklerini eklemek yeterlidir. Bu kadar kolay! ```php protected function createComponentSignInForm(): Form @@ -43,10 +45,10 @@ protected function createComponentSignInForm(): Form ```latte <form n:name=signInForm class=form> <div> - <label n:name=username>Username: <input n:name=username size=20 autofocus></label> + <label n:name=username>Kullanıcı adı: <input n:name=username size=20 autofocus></label> </div> <div> - <label n:name=password>Password: <input n:name=password></label> + <label n:name=password>Şifre: <input n:name=password></label> </div> <div> <input n:name=send class="btn btn-default"> @@ -54,10 +56,9 @@ protected function createComponentSignInForm(): Form </form> ``` -Ortaya çıkan HTML kodunun görünümü tamamen sizin elinizdedir. `n:name` özniteliğini şu şekilde kullanırsanız `<select>`, `<button>` veya `<textarea>` öğeleri varsa, iç içerikleri otomatik olarak doldurulur. -Buna ek olarak `<form n:name>` etiketi, çizilen form nesnesi ve kapanış ile `$form` yerel değişkenini oluşturur. `</form>` çizilmemiş tüm gizli öğeleri çizer (aynı durum `{form} ... {/form}` için de geçerlidir). +Sonuçtaki HTML kodunun görünümü tamamen sizin kontrolünüzdedir. `n:name` niteliğini `<select>`, `<button>` veya `<textarea>` elemanlarında kullanırsanız, iç içerikleri otomatik olarak tamamlanır. `<form n:name>` etiketi ayrıca render edilen formun nesnesiyle `$form` yerel değişkenini oluşturur ve kapanış `</form>` etiketi render edilmemiş tüm gizli elemanları render eder (aynısı `{form} ... {/form}` için de geçerlidir). -Ancak, olası hata mesajlarını oluşturmayı unutmamalıyız. Hem `addError()` yöntemi ( `{inputError}` kullanılarak) tarafından tek tek öğelere eklenenler hem de doğrudan forma eklenenler ( `$form->getOwnErrors()` tarafından döndürülenler): +Ancak olası hata mesajlarının render edilmesini unutmamalıyız. Hem `addError()` metoduyla bireysel elemanlara eklenenler ( `{inputError}` kullanarak) hem de doğrudan forma eklenenler ( `$form->getOwnErrors()` tarafından döndürülenler): ```latte <form n:name=signInForm class=form> @@ -66,11 +67,11 @@ Ancak, olası hata mesajlarını oluşturmayı unutmamalıyız. Hem `addError()` </ul> <div> - <label n:name=username>Username: <input n:name=username size=20 autofocus></label> + <label n:name=username>Kullanıcı adı: <input n:name=username size=20 autofocus></label> <span class=error n:ifcontent>{inputError username}</span> </div> <div> - <label n:name=password>Password: <input n:name=password></label> + <label n:name=password>Şifre: <input n:name=password></label> <span class=error n:ifcontent>{inputError password}</span> </div> <div> @@ -79,7 +80,7 @@ Ancak, olası hata mesajlarını oluşturmayı unutmamalıyız. Hem `addError()` </form> ``` -RadioList veya CheckboxList gibi daha karmaşık form öğeleri öğe öğe işlenebilir: +RadioList veya CheckboxList gibi daha karmaşık form elemanları bu şekilde tek tek öğelerle render edilebilir: ```latte {foreach $form[gender]->getItems() as $key => $label} @@ -88,16 +89,10 @@ RadioList veya CheckboxList gibi daha karmaşık form öğeleri öğe öğe işl ``` -Kod Önerisi `{formPrint}` .[#toc-formprint] -------------------------------------------- - -`{formPrint}` etiketini kullanarak bir form için benzer bir Latte kodu oluşturabilirsiniz. Bunu bir şablona yerleştirirseniz, normal oluşturma yerine taslak kodu görürsünüz. Sonra onu seçin ve projenize kopyalayın. - - `{label}` `{input}` ------------------- -Her bir öğe için şablonda hangi HTML öğesini kullanacağınızı düşünmek istemez misiniz? `<input>`, `<textarea>` vb. Çözüm evrensel `{input}` etiketidir: +Her eleman için şablonda hangi HTML elemanını kullanacağınızı düşünmek istemiyor musunuz, `<input>`, `<textarea>` vb. mi? Çözüm evrensel `{input}` etiketidir: ```latte <form n:name=signInForm class=form> @@ -106,11 +101,11 @@ Her bir öğe için şablonda hangi HTML öğesini kullanacağınızı düşünm </ul> <div> - {label username}Username: {input username, size: 20, autofocus: true}{/label} + {label username}Kullanıcı adı: {input username, size: 20, autofocus: true}{/label} {inputError username} </div> <div> - {label password}Password: {input password}{/label} + {label password}Şifre: {input password}{/label} {inputError password} </div> <div> @@ -119,9 +114,9 @@ Her bir öğe için şablonda hangi HTML öğesini kullanacağınızı düşünm </form> ``` -Form bir çevirmen kullanıyorsa, `{label}` etiketlerinin içindeki metin çevrilecektir. +Form bir çevirmen kullanıyorsa, `{label}` etiketleri içindeki metin çevrilecektir. -Yine, RadioList veya CheckboxList gibi daha karmaşık form öğeleri öğe öğe işlenebilir: +Bu durumda bile, RadioList veya CheckboxList gibi daha karmaşık form elemanları tek tek öğelerle render edilebilir: ```latte {foreach $form[gender]->items as $key => $label} @@ -129,20 +124,19 @@ Yine, RadioList veya CheckboxList gibi daha karmaşık form öğeleri öğe öğ {/foreach} ``` -İşlemek için `<input>` 'nin kendisini Onay Kutusu öğesinde kullanmak için `{input myCheckbox:}` adresini kullanın. HTML nitelikleri virgülle ayrılmalıdır `{input myCheckbox:, class: required}`. +Checkbox elemanındaki `<input>`'ın kendisini render etmek için `{input myCheckbox:}` kullanın. Bu durumda HTML niteliklerini her zaman virgülle ayırın `{input myCheckbox:, class: required}`. `{inputError}` -------------- -Form öğesi için, eğer varsa, bir hata mesajı yazdırır. Mesaj genellikle stil için bir HTML öğesine sarılır. -Mesaj yoksa boş bir öğe oluşturmaktan kaçınmak `n:ifcontent` ile zarif bir şekilde yapılabilir: +Bir form elemanı için varsa hata mesajını yazdırır. Mesajı genellikle stil vermek için bir HTML elemanına sararız. Mesaj yoksa boş bir elemanın render edilmesini önlemek, `n:ifcontent` kullanarak zarif bir şekilde yapılabilir: ```latte <span class=error n:ifcontent>{inputError $input}</span> ``` -`hasErrors()` yöntemini kullanarak bir hatanın varlığını tespit edebilir ve üst öğenin sınıfını buna göre ayarlayabiliriz: +Bir hatanın varlığını `hasErrors()` metoduyla kontrol edebilir ve buna göre üst elemana bir sınıf atayabiliriz: ```latte <div n:class="$form[username]->hasErrors() ? 'error'"> @@ -155,14 +149,13 @@ Mesaj yoksa boş bir öğe oluşturmaktan kaçınmak `n:ifcontent` ile zarif bir `{form}` -------- -Etiketler `{form signInForm}...{/form}` bir alternatiftir `<form n:name="signInForm">...</form>`. +`{form signInForm}...{/form}` etiketleri `<form n:name="signInForm">...</form>`'a bir alternatiftir. -Otomatik Rendering .[#toc-automatic-rendering] ----------------------------------------------- +Otomatik Render Etme +-------------------- -`{input}` ve `{label}` etiketleri ile herhangi bir form için kolayca genel bir şablon oluşturabiliriz. Form şu etiketle sonlandırıldığında otomatik olarak işlenen gizli öğeler hariç, tüm öğelerini sırayla yineleyecek ve işleyecektir `</form>` etiketini kullanır. -Oluşturulan formun adını `$form` değişkeninde bekleyecektir. +`{input}` ve `{label}` etiketleri sayesinde, herhangi bir form için kolayca genel bir şablon oluşturabiliriz. Sırayla tüm elemanlarını yineleyecek ve render edecektir, formun `</form>` etiketiyle sonlandırıldığında otomatik olarak render edilen gizli elemanlar hariç. Render edilen formun adı `$form` değişkeninde beklenecektir. ```latte <form n:name=$form class=form> @@ -179,16 +172,15 @@ Oluşturulan formun adını `$form` değişkeninde bekleyecektir. </form> ``` -Kullanılan kendi kendine kapanan çift etiketleri `{label .../}` PHP kodundaki form tanımından gelen etiketleri gösterir. +Kullanılan kendi kendine kapanan çift etiketler `{label .../}` PHP kodundaki form tanımından gelen etiketleri görüntüler. -Bu genel şablonu `basic-form.latte` dosyasına kaydedebilir ve formu oluşturmak için onu dahil edip form adını (veya örneğini) `$form` parametresine aktarabilirsiniz: +Bu genel şablonu örneğin `basic-form.latte` dosyasına kaydedin ve formu render etmek için onu dahil etmek ve formun adını (veya örneğini) `$form` parametresine iletmek yeterlidir: ```latte {include basic-form.latte, form: signInForm} ``` -Belirli bir formun görünümünü etkilemek ve bir öğeyi farklı şekilde çizmek istiyorsanız, bunun en kolay yolu şablonda daha sonra üzerine yazılabilecek bloklar hazırlamaktır. -Bloklar [dinamik isimlere |latte:template-inheritance#dynamic-block-names] de sahip olabilir, böylece içine çizilecek öğenin adını ekleyebilirsiniz. Örneğin: +Belirli bir formu render ederken görünümüne müdahale etmek ve örneğin bir elemanı farklı render etmek isterseniz, en kolay yol şablonda daha sonra üzerine yazılabilecek bloklar hazırlamaktır. Bloklar ayrıca [dinamik isimler |latte:template-inheritance#Dinamik Blok Adları] sahip olabilir, bu nedenle render edilen elemanın adını da içlerine ekleyebilirsiniz. Örneğin: ```latte ... @@ -197,7 +189,7 @@ Bloklar [dinamik isimlere |latte:template-inheritance#dynamic-block-names] de sa ... ``` -Örneğin `username` öğesi için bu, [{embed} |latte:template-inheritance#unit-inheritance] etiketi kullanılarak kolayca geçersiz kılınabilecek `input-username` bloğunu oluşturur: +Örneğin `username` elemanı için, [{embed} |latte:template-inheritance#Birim Kalıtımı] etiketi kullanılarak kolayca üzerine yazılabilecek `input-username` bloğu oluşturulur: ```latte {embed basic-form.latte, form: signInForm} @@ -209,7 +201,7 @@ Bloklar [dinamik isimlere |latte:template-inheritance#dynamic-block-names] de sa {/embed} ``` -Alternatif olarak, `basic-form.latte` şablonunun tüm içeriği `$form` parametresi de dahil olmak üzere bir blok olarak [tanımlanabilir |latte:template-inheritance#definitions]: +Alternatif olarak, `basic-form.latte` şablonunun tüm içeriği, `$form` parametresi dahil olmak üzere bir blok olarak [tanımlanabilir |latte:template-inheritance#Tanımlar]: ```latte {define basic-form, $form} @@ -219,7 +211,7 @@ Alternatif olarak, `basic-form.latte` şablonunun tüm içeriği `$form` paramet {/define} ``` -Bu, kullanımı biraz daha kolaylaştıracaktır: +Bu sayede çağrısı biraz daha basit olacaktır: ```latte {embed basic-form, signInForm} @@ -227,31 +219,31 @@ Bu, kullanımı biraz daha kolaylaştıracaktır: {/embed} ``` -Bloğu yalnızca bir yerde, düzen şablonunun başında içe aktarmanız gerekir: +Bloğu yalnızca tek bir yerde, layout şablonunun başında içe aktarmak yeterlidir: ```latte {import basic-form.latte} ``` -Özel Durumlar .[#toc-special-cases] ------------------------------------ +Özel Durumlar +------------- -Bir formun yalnızca iç içeriğini oluşturmanız gerekiyorsa `<form>` & `</form>` HTML etiketleri, örneğin bir AJAX isteğinde, formu `{formContext} … {/formContext}` ile açıp kapatabilirsiniz. Mantıksal anlamda `{form}` ile benzer şekilde çalışır, burada form öğelerini çizmek için diğer etiketleri kullanmanıza izin verir, ancak aynı zamanda hiçbir şey çizmez. +Formun yalnızca iç kısmını HTML `<form>` etiketleri olmadan render etmeniz gerekiyorsa, örneğin snippet gönderirken, bunları `n:tag-if` niteliğiyle gizleyin: ```latte -{formContext signForm} +<form n:name=signInForm n:tag-if=false> <div> - <label n:name=username>Username: <input n:name=username></label> + <label n:name=username>Kullanıcı adı: <input n:name=username></label> {inputError username} </div> -{/formContext} +</form> ``` -Etiket `formContainer` bir form konteyneri içindeki girdilerin oluşturulmasına yardımcı olur. +Form konteyneri içindeki elemanların render edilmesine `{formContainer}` etiketi yardımcı olur. ```latte -<p>Which news you wish to receive:</p> +<p>Hangi haberleri almak istersiniz:</p> {formContainer emailNews} <ul> @@ -262,27 +254,27 @@ Etiket `formContainer` bir form konteyneri içindeki girdilerin oluşturulmasın ``` -Latte Olmadan Rendering .[#toc-rendering-without-latte] -======================================================= +Latte Olmadan Render Etme +========================= -Bir formu oluşturmanın en kolay yolu çağırmaktır: +Formu render etmenin en basit yolu çağırmaktır: ```php $form->render(); ``` -Oluşturulan formun görünümü, [Renderer |#Renderer] ve [bireysel kontroller |#HTML Attributes] yapılandırılarak değiştirilebilir. +Bu şekilde render edilen formun görünümünü [#Renderer] ve [bireysel elemanlar |#HTML Nitelikleri] yapılandırarak etkileyebilirsiniz. -Manuel Rendering .[#toc-manual-rendering] ------------------------------------------ +Manuel Render Etme +------------------ -Her form öğesi, form alanı ve etiket için HTML kodu üreten yöntemlere sahiptir. Bu kodlar bir dize ya da [Nette\Utils\Html |utils:html-elements] nesnesi olarak döndürülebilir: +Her form elemanı, form alanının ve etiketin HTML kodunu üreten metotlara sahiptir. Bunu ya bir dize olarak ya da bir [Nette\Utils\Html|utils:html-elements] nesnesi olarak döndürebilirler: -- `getControl(): Html|string` öğenin HTML kodunu döndürür +- `getControl(): Html|string` elemanın HTML kodunu döndürür - `getLabel($caption = null): Html|string|null` varsa etiketin HTML kodunu döndürür -Bu, formun öğe öğe işlenmesini sağlar: +Form bu nedenle tek tek elemanlarla render edilebilir: ```php <?php $form->render('begin') ?> @@ -305,47 +297,46 @@ Bu, formun öğe öğe işlenmesini sağlar: <?php $form->render('end') ?> ``` -Bazı öğeler için `getControl()` tek bir HTML öğesi döndürürken (örn. `<input>`, `<select>` vb.), diğerleri için bütün bir HTML kodu parçası döndürür (CheckboxList, RadioList). -Bu durumda, her öğe için ayrı ayrı girdiler ve etiketler oluşturan yöntemler kullanabilirsiniz: +Bazı elemanlar için `getControl()` tek bir HTML elemanı (örneğin `<input>`, `<select>` vb.) döndürürken, diğerleri için tüm bir HTML kod parçasını (CheckboxList, RadioList) döndürür. Bu durumda, her öğe için ayrı ayrı tek tek girdileri ve etiketleri üreten metotları kullanabilirsiniz: -- `getControlPart($key = null): ?Html` tek bir öğenin HTML kodunu döndürür -- `getLabelPart($key = null): ?Html` tek bir öğenin etiketi için HTML kodunu döndürür +- `getControlPart($key = null): ?Html` bir öğenin HTML kodunu döndürür +- `getLabelPart($key = null): ?Html` bir öğenin etiketinin HTML kodunu döndürür .[note] -Bu yöntemlerin önüne tarihsel nedenlerden dolayı `get` eklenmiştir, ancak her çağrıda yeni bir `Html` öğesi oluşturup döndürdüğü için `generate` daha iyi olacaktır. +Bu metotların tarihsel nedenlerden dolayı `get` öneki vardır, ancak her çağrıda yeni bir `Html` elemanı oluşturup döndürdükleri için `generate` daha iyi olurdu. -Oluşturucu .[#toc-renderer] -=========================== +Renderer +======== -Formun işlenmesini sağlayan bir nesnedir. `$form->setRenderer` metodu tarafından ayarlanabilir. `$form->render()` metodu çağrıldığında kontrol aktarılır. +Formun render edilmesini sağlayan bir nesnedir. `$form->setRenderer` metoduyla ayarlanabilir. `$form->render()` metodu çağrıldığında kontrol ona devredilir. -Eğer özel bir renderer ayarlamazsak, varsayılan renderer [api:Nette\Forms\Rendering\DefaultFormRenderer] kullanılacaktır. Bu, form öğelerini bir HTML tablosu olarak oluşturacaktır. Çıktı şu şekilde görünür: +Kendi renderer'ımızı ayarlamazsak, varsayılan [api:Nette\Forms\Rendering\DefaultFormRenderer] render edicisi kullanılacaktır. Bu, form elemanlarını bir HTML tablosu şeklinde render eder. Çıktı şöyle görünür: ```latte <table> <tr class="required"> - <th><label class="required" for="frm-name">Name:</label></th> + <th><label class="required" for="frm-name">İsim:</label></th> <td><input type="text" class="text" name="name" id="frm-name" required value=""></td> </tr> <tr class="required"> - <th><label class="required" for="frm-age">Age:</label></th> + <th><label class="required" for="frm-age">Yaş:</label></th> <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> </tr> <tr> - <th><label>Gender:</label></th> + <th><label>Cinsiyet:</label></th> ... ``` -Tablo kullanıp kullanmamak size bağlıdır ve birçok web tasarımcısı farklı işaretlemeleri tercih eder, örneğin bir liste. `DefaultFormRenderer` adresini hiç tabloya dönüştürmeyecek şekilde yapılandırabiliriz. Sadece uygun [$wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers] ayarlamamız gerekiyor. İlk indeks her zaman bir alanı, ikincisi ise bir elementi temsil eder. İlgili tüm alanlar resimde gösterilmiştir: +Form iskeleti için tablo kullanıp kullanmamak tartışmalıdır ve birçok web tasarımcısı farklı bir işaretleme tercih eder. Örneğin, bir tanım listesi. Bu nedenle `DefaultFormRenderer`'ı formu bir liste şeklinde render edecek şekilde yeniden yapılandıracağız. Yapılandırma [$wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers] dizisini düzenleyerek yapılır. İlk dizin her zaman alanı ve ikincisi onun niteliğini temsil eder. Bireysel alanlar resimde gösterilmiştir: -[* form-areas-en.webp *] +[* defaultformrenderer.webp *] -Varsayılan olarak bir `controls` grubu `<table>`ve her `pair` bir tablo satırıdır `<tr>` bir çift `label` ve `control` içeren (hücreler `<th>` ve `<td>`). Tüm bu sarmalayıcı öğeleri değiştirelim. `controls` adresini `<dl>` pair adresini tek başına bırakın, `label` adresini `<dt>` ve `control` adresini `<dd>`: +Standart olarak, `controls` eleman grubu bir tablo `<table>` ile sarılır, her `pair` bir tablo satırını `<tr>` temsil eder ve `label` ve `control` çifti hücreler `<th>` ve `<td>`'dir. Şimdi saran elemanları değiştireceğiz. `controls` alanını `<dl>` konteynerine koyacağız, `pair` alanını konteynersiz bırakacağız, `label`'ı `<dt>`'ye koyacağız ve son olarak `control`'ü `<dd>` etiketleriyle saracağız: ```php $renderer = $form->getRenderer(); @@ -357,193 +348,192 @@ $renderer->wrappers['control']['container'] = 'dd'; $form->render(); ``` -Sonuçlar aşağıdaki kod parçacığına dönüşür: +Sonuç bu HTML kodudur: ```latte <dl> - <dt><label class="required" for="frm-name">Name:</label></dt> + <dt><label class="required" for="frm-name">İsim:</label></dt> <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> - <dt><label class="required" for="frm-age">Age:</label></dt> + <dt><label class="required" for="frm-age">Yaş:</label></dt> <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> - <dt><label>Gender:</label></dt> + <dt><label>Cinsiyet:</label></dt> ... </dl> ``` -Sarmalayıcılar birçok özelliği etkileyebilir. Örneğin: +Wrappers dizisinde diğer birçok niteliği etkileyebilirsiniz: -- her form girişine özel CSS sınıfları ekleyin -- tek ve çift çizgiler arasında ayrım yapabilme -- gerekli ve isteğe bağlı çizimleri farklı yapın -- hata mesajlarının formun üstünde mi yoksa her bir öğenin yakınında mı gösterileceğini belirler +- bireysel form elemanı türlerine CSS sınıfları eklemek +- tek ve çift satırları CSS sınıfıyla ayırt etmek +- zorunlu ve isteğe bağlı öğeleri görsel olarak ayırt etmek +- hata mesajlarının doğrudan elemanların yanında mı yoksa formun üzerinde mi görüntüleneceğini belirlemek -Seçenekler .[#toc-options] --------------------------- +Options +------- -Renderer'ın davranışı, tek tek form öğeleri üzerinde *seçenekler* ayarlanarak da kontrol edilebilir. Bu şekilde, giriş alanının yanında görüntülenen araç ipucunu ayarlayabilirsiniz: +Renderer'ın davranışı, bireysel form elemanlarında *options* ayarlayarak da kontrol edilebilir. Bu şekilde, giriş alanının yanında yazdırılacak açıklamayı ayarlayabilirsiniz: ```php -$form->addText('phone', 'Number:') - ->setOption('description', 'This number will remain hidden'); +$form->addText('phone', 'Numara:') + ->setOption('description', 'Bu numara gizli kalacaktır'); ``` -Eğer içine HTML içeriği yerleştirmek istiyorsak [Html |utils:html-elements] sınıfını kullanırız. +İçine HTML içeriği yerleştirmek istiyorsak, [Html |utils:html-elements] sınıfını kullanırız ```php use Nette\Utils\Html; -$form->addText('phone', 'Phone:') +$form->addText('phone', 'Numara:') ->setOption('description', Html::el('p') - ->setHtml('<a href="...">Terms of service.</a>') + ->setHtml('<a href="...">Numaranızın saklanma koşulları</a>') ); ``` .[tip] -Etiket yerine Html öğesi de kullanılabilir: `$form->addCheckbox('conditions', $label)`. +Html elemanı etiket yerine de kullanılabilir: `$form->addCheckbox('conditions', $label)`. -Gruplama Girişleri .[#toc-grouping-inputs] ------------------------------------------- +Elemanların Gruplandırılması +---------------------------- -Renderer, öğeleri görsel gruplar (alan kümeleri) halinde gruplandırmaya izin verir: +Renderer, elemanları görsel gruplara (fieldset) ayırmayı sağlar: ```php -$form->addGroup('Personal data'); +$form->addGroup('Kişisel Veriler'); ``` -Yeni grup oluşturmak onu etkinleştirir - daha sonra eklenen tüm öğeler bu gruba eklenir. Bunun gibi bir form oluşturabilirsiniz: +Yeni bir grup oluşturulduktan sonra, bu grup aktif hale gelir ve yeni eklenen her eleman aynı zamanda ona eklenir. Yani formu bu şekilde oluşturabilirsiniz: ```php $form = new Form; -$form->addGroup('Personal data'); -$form->addText('name', 'Your name:'); -$form->addInteger('age', 'Your age:'); -$form->addEmail('email', 'Email:'); +$form->addGroup('Kişisel Veriler'); +$form->addText('name', 'Adınız:'); +$form->addInteger('age', 'Yaşınız:'); +$form->addEmail('email', 'E-posta:'); -$form->addGroup('Shipping address'); -$form->addCheckbox('send', 'Ship to address'); -$form->addText('street', 'Street:'); -$form->addText('city', 'City:'); -$form->addSelect('country', 'Country:', $countries); +$form->addGroup('Teslimat Adresi'); +$form->addCheckbox('send', 'Adrese gönder'); +$form->addText('street', 'Sokak:'); +$form->addText('city', 'Şehir:'); +$form->addSelect('country', 'Ülke:', $countries); ``` +Renderer önce grupları ve ancak ondan sonra hiçbir gruba ait olmayan elemanları render eder. + -Bootstrap Desteği .[#toc-bootstrap-support] -------------------------------------------- +Bootstrap Desteği +----------------- -[Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], [Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] ve [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php] için Renderer yapılandırma [örneklerini |https://github.com/nette/forms/tree/master/examples] bulabilirsiniz +[Örneklerde |https://github.com/nette/forms/tree/master/examples] Renderer'ı [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], [Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] ve [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php] için nasıl yapılandıracağınıza dair örnekler bulacaksınız. -HTML Nitelikleri .[#toc-html-attributes] -======================================== +HTML Nitelikleri +================ -`setHtmlAttribute(string $name, $value = true)` adresini kullanarak form denetimlerine herhangi bir HTML niteliği atayabilirsiniz: +Form elemanlarının herhangi bir HTML niteliğini ayarlamak için `setHtmlAttribute(string $name, $value = true)` metodunu kullanırız: ```php -$form->addInteger('number', 'Number:') +$form->addInteger('number', 'Numara:') ->setHtmlAttribute('class', 'big-number'); -$form->addSelect('rank', 'Order by:', ['price', 'name']) - ->setHtmlAttribute('onchange', 'submit()'); // değişiklik üzerine JS fonksiyonu submit()'i çağırır +$form->addSelect('rank', 'Sıralama ölçütü:', ['fiyat', 'isim']) + ->setHtmlAttribute('onchange', 'submit()'); // değişiklikte gönder -// <form> üzerinde uygulama +// <form> öğesinin kendisinin niteliklerini ayarlamak için $form->setHtmlAttribute('id', 'myForm'); ``` -Giriş türünü ayarlama: +Eleman türünün belirtilmesi: ```php -$form->addText('tel', 'Your telephone:') +$form->addText('tel', 'Telefonunuz:') ->setHtmlType('tel') - ->setHtmlAttribute('placeholder', 'Please, fill in your telephone'); + ->setHtmlAttribute('placeholder', 'telefonu yazın'); ``` -HTML niteliğini radyo veya onay kutusu listelerindeki her bir öğe için farklı değerlerle ayarlayabiliriz. -Değerin anahtara göre seçildiğinden emin olmak için `style:` adresinden sonra iki nokta üst üste işaretine dikkat edin: +.[warning] +Türün ve diğer niteliklerin ayarlanması yalnızca görsel amaçlıdır. Girdilerin doğruluğunun doğrulanması sunucu tarafında yapılmalıdır, bu da uygun bir [form elemanı|controls] seçerek ve [doğrulama kuralları|validation] belirterek sağlanır. + +Radyo veya onay kutusu listelerindeki bireysel öğelere, her biri için farklı değerlere sahip HTML niteliği atayabiliriz. Anahtara göre değer seçimini sağlayan `style:` sonrasındaki iki noktaya dikkat edin: ```php -$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue']; +$colors = ['r' => 'kırmızı', 'g' => 'yeşil', 'b' => 'mavi']; $styles = ['r' => 'background:red', 'g' => 'background:green']; -$form->addCheckboxList('colors', 'Colors:', $colors) +$form->addCheckboxList('colors', 'Renkler:', $colors) ->setHtmlAttribute('style:', $styles); ``` -Render: +Yazdırır: ```latte -<label><input type="checkbox" name="colors[]" style="background:red" value="r">red</label> -<label><input type="checkbox" name="colors[]" style="background:green" value="g">green</label> -<label><input type="checkbox" name="colors[]" value="b">blue</label> +<label><input type="checkbox" name="colors[]" style="background:red" value="r">kırmızı</label> +<label><input type="checkbox" name="colors[]" style="background:green" value="g">yeşil</label> +<label><input type="checkbox" name="colors[]" value="b">mavi</label> ``` -Mantıksal bir HTML niteliği için (değeri olmayan, örneğin `readonly`), bir soru işareti kullanabilirsiniz: +`readonly` gibi mantıksal nitelikleri ayarlamak için soru işaretiyle yazımı kullanabiliriz: ```php -$colors = ['r' => 'kırmızı', 'g' => 'yeşil', 'b' => 'mavi']; -$form->addCheckboxList('colors', 'Colors:', $colors) - ->setHtmlAttribute('readonly?', 'r'); // birden fazla anahtar için dizi kullanın, örneğin ['r', 'g'] +$form->addCheckboxList('colors', 'Renkler:', $colors) + ->setHtmlAttribute('readonly?', 'r'); // daha fazla anahtar için bir dizi kullanın, örn. ['r', 'g'] ``` -Render: +Yazdırır: ```latte -<label><input type="checkbox" name="colors[]" readonly value="r">red</label> -<label><input type="checkbox" name="colors[]" value="g">green</label> -<label><input type="checkbox" name="colors[]" value="b">blue</label> +<label><input type="checkbox" name="colors[]" readonly value="r">kırmızı</label> +<label><input type="checkbox" name="colors[]" value="g">yeşil</label> +<label><input type="checkbox" name="colors[]" value="b">mavi</label> ``` -Selectbox'lar için `setHtmlAttribute()` yöntemi, selectbox'ın niteliklerini ayarlar. `<select>` öğesi. Her bir eleman için öznitelikleri ayarlamak istersek -`<option>`yöntemini kullanacağız `setOptionAttribute()`. Ayrıca, yukarıda kullanılan iki nokta üst üste ve soru işareti de çalışır: +Seçme kutuları (selectbox) durumunda, `setHtmlAttribute()` metodu `<select>` elemanının niteliklerini ayarlar. Bireysel `<option>`'ların niteliklerini ayarlamak istiyorsak, `setOptionAttribute()` metodunu kullanırız. Yukarıda belirtilen iki nokta ve soru işaretiyle yazımlar da çalışır: ```php -$form->addSelect('colors', 'Colors:', $colors) +$form->addSelect('colors', 'Renkler:', $colors) ->setOptionAttribute('style:', $styles); ``` -Render: +Yazdırır: ```latte <select name="colors"> - <option value="r" style="background:red">red</option> - <option value="g" style="background:green">green</option> - <option value="b">blue</option> + <option value="r" style="background:red">kırmızı</option> + <option value="g" style="background:green">yeşil</option> + <option value="b">mavi</option> </select> ``` -Prototipler .[#toc-prototypes] ------------------------------- +Prototypler +----------- -HTML niteliklerini ayarlamanın alternatif bir yolu, HTML öğesinin oluşturulduğu şablonu değiştirmektir. Şablon bir `Html` nesnesidir ve `getControlPrototype()` yöntemi tarafından döndürülür: +HTML niteliklerini ayarlamanın alternatif bir yolu, HTML elemanının üretildiği şablonu düzenlemektir. Şablon bir `Html` nesnesidir ve `getControlPrototype()` metodu tarafından döndürülür: ```php -$input = $form->addInteger('number'); +$input = $form->addInteger('number', 'Numara:'); $html = $input->getControlPrototype(); // <input> -$html->class('big-number'); // <input class="big-number"> +$html->class('big-number'); // <input class="big-number"> ``` -`getLabelPrototype()` tarafından döndürülen etiket şablonu da bu şekilde değiştirilebilir: +Bu şekilde, `getLabelPrototype()` tarafından döndürülen etiket şablonunu da değiştirebilirsiniz: ```php $html = $input->getLabelPrototype(); // <label> -$html->class('distinctive'); // <label class="distinctive"> +$html->class('distinctive'); // <label class="distinctive"> ``` -Checkbox, CheckboxList ve RadioList öğeleri için öğeyi saran öğe şablonunu etkileyebilirsiniz. `getContainerPrototype()` tarafından döndürülür. Varsayılan olarak "boş" bir öğedir, bu nedenle hiçbir şey işlenmez, ancak bir ad vererek işlenecektir: +Checkbox, CheckboxList ve RadioList elemanlarında, tüm elemanı saran elemanın şablonunu etkileyebilirsiniz. Bu, `getContainerPrototype()` tarafından döndürülür. Varsayılan durumda bu "boş" bir elemandır, bu yüzden hiçbir şey render edilmez, ancak ona bir ad atayarak render edilecektir: ```php $input = $form->addCheckbox('send'); -echo $input->getControl(); -// <label><input type="checkbox" name="send"></label> - $html = $input->getContainerPrototype(); $html->setName('div'); // <div> $html->class('check'); // <div class="check"> @@ -551,50 +541,49 @@ echo $input->getControl(); // <div class="check"><label><input type="checkbox" name="send"></label></div> ``` -CheckboxList ve RadioList söz konusu olduğunda, `getSeparatorPrototype()` yöntemi tarafından döndürülen öğe ayırıcı modelini etkilemek de mümkündür. Varsayılan olarak, bu bir öğedir `<br>`. Bunu bir çift öğe olarak değiştirirseniz, öğeleri ayırmak yerine tek tek saracaktır. -Ayrıca, öğe etiketlerinin HTML öğesi şablonunu etkilemek de mümkündür; bu şablon `getItemLabelPrototype()` döndürür. +CheckboxList ve RadioList durumunda, `getSeparatorPrototype()` tarafından döndürülen bireysel öğelerin ayırıcısının şablonunu da etkileyebilirsiniz. Varsayılan durumda bu `<br>` elemanıdır. Onu çiftli bir elemana değiştirirseniz, bireysel öğeleri ayırmak yerine saracaktır. Ayrıca, `getItemLabelPrototype()` tarafından döndürülen bireysel öğelerdeki etiket HTML elemanının şablonunu da etkileyebilirsiniz. -Çeviri .[#toc-translating] -========================== +Çeviri +====== -Çok dilli bir uygulama programlıyorsanız, muhtemelen formu farklı dillerde oluşturmanız gerekecektir. Nette Framework bu amaç için bir çeviri arayüzü tanımlar [api:Nette\Localization\Translator]. Nette'de varsayılan bir uygulama yoktur, [Componette |https://componette.org/search/localization]'de bulabileceğiniz birkaç hazır çözüm arasından ihtiyaçlarınıza göre seçim yapabilirsiniz. Belgeleri size çevirmeni nasıl yapılandıracağınızı anlatır. +Çok dilli bir uygulama programlıyorsanız, muhtemelen formu farklı dil mutasyonlarında render etmeniz gerekecektir. Nette Framework bu amaçla çeviri için [api:Nette\Localization\Translator] arayüzünü tanımlar. Nette'de varsayılan bir uygulama yoktur, ihtiyaçlarınıza göre [Componette |https://componette.org/search/localization] üzerinde bulabileceğiniz birkaç hazır çözüm arasından seçim yapabilirsiniz. Belgelerinde çevirmeni nasıl yapılandıracağınızı öğreneceksiniz. -Form, çevirmen aracılığıyla metin çıktısı almayı destekler. Bunu `setTranslator()` yöntemini kullanarak aktarıyoruz: +Formlar, metinlerin bir çevirmen aracılığıyla yazdırılmasını destekler. Onu `setTranslator()` metoduyla iletiriz: ```php $form->setTranslator($translator); ``` -Şu andan itibaren, sadece tüm etiketler değil, aynı zamanda tüm hata mesajları veya seçim kutusu girişleri de başka bir dile çevrilecektir. +Bu andan itibaren, yalnızca tüm etiketler değil, aynı zamanda tüm hata mesajları veya seçme kutusu (select box) öğeleri de başka bir dile çevrilecektir. -Tek tek form öğeleri için farklı bir çevirmen ayarlamak veya `null` ile çeviriyi tamamen devre dışı bırakmak mümkündür: +Bireysel form elemanları için farklı bir çevirmen ayarlamak veya `null` değeriyle çeviriyi tamamen kapatmak mümkündür: ```php $form->addSelect('carModel', 'Model:', $cars) ->setTranslator(null); ``` - [Doğrulama kuralları |validation] için, örneğin kural gibi belirli parametreler de çevirmene aktarılır: +[Doğrulama kuralları|validation] için çevirmene özel parametreler de iletilir, örneğin kural için: ```php -$form->addPassword('password', 'Password:') - ->addRule($form::MinLength, 'Password has to be at least %d characters long', 8) +$form->addPassword('password', 'Şifre:') + ->addRule($form::MinLength, 'Şifre en az %d karakter olmalıdır', 8); ``` -çevirmen aşağıdaki parametrelerle çağrılır: +çevirmen şu parametrelerle çağrılır: ```php -$translator->translate('Password has to be at least %d characters long', 8); +$translator->translate('Şifre en az %d karakter olmalıdır', 8); ``` -ve böylece `characters` sözcüğü için doğru çoğul biçimini sayarak seçebilir. +ve dolayısıyla sayıya göre `karakter` kelimesinin doğru çoğul biçimini seçebilir. -Olay onRender .[#toc-event-onrender] -==================================== +onRender Olayı +============== -Form işlenmeden hemen önce kodumuzu çağırabiliriz. Bu, örneğin, düzgün görüntüleme için form öğelerine HTML sınıfları ekleyebilir. Kodu `onRender` dizisine ekliyoruz: +Form render edilmeden hemen önce, kodumuzun çağrılmasını sağlayabiliriz. Bu kod, örneğin doğru görüntüleme için form elemanlarına HTML sınıfları ekleyebilir. Kodu `onRender` dizisine ekleriz: ```php $form->onRender[] = function ($form) { diff --git a/forms/tr/standalone.texy b/forms/tr/standalone.texy index ffbe8fbea5..d1b4f1b4c0 100644 --- a/forms/tr/standalone.texy +++ b/forms/tr/standalone.texy @@ -1,76 +1,76 @@ -Bağımsız Kullanılan Formlar -*************************** +Formları Tek Başına Kullanma +**************************** .[perex] -Nette Forms, web formlarının oluşturulmasını ve işlenmesini önemli ölçüde kolaylaştırır. Bu bölümde göstereceğimiz gibi, bunları uygulamalarınızda çerçevenin geri kalanı olmadan tamamen kendi başlarına kullanabilirsiniz. +Nette Forms, web formları oluşturmayı ve işlemeyi önemli ölçüde kolaylaştırır. Bunları, bu bölümde göstereceğimiz gibi, framework'ün geri kalanı olmadan uygulamalarınızda tamamen bağımsız olarak kullanabilirsiniz. -Ancak, Nette Application ve [presenters |in-presenter] kullanıyorsanız, sizin için bir kılavuz var: [presenters'daki formlar |in-presenter]. +Ancak Nette Application ve presenter'ları kullanıyorsanız, [presenter'larda kullanım |in-presenter] kılavuzu sizin içindir. -İlk Form .[#toc-first-form] -=========================== +İlk Form +======== -Basit bir kayıt formu yazmaya çalışacağız. Kodu şu şekilde görünecektir ("tam kod":https://gist.github.com/dg/370a7e3094d9ba9a9e913b8e2a2dc851): +Basit bir kayıt formu yazmaya çalışalım. Kodu aşağıdaki gibi olacaktır ("tüm kod":https://gist.github.com/dg/57878c1a413ae8ef0c1d83f02c43ef3f): ```php use Nette\Forms\Form; $form = new Form; -$form->addText('name', 'Name:'); -$form->addPassword('password', 'Password:'); -$form->addSubmit('send', 'Sign up'); +$form->addText('name', 'İsim:'); +$form->addPassword('password', 'Şifre:'); +$form->addSubmit('send', 'Kayıt Ol'); ``` -Ve onu işleyelim: +Çok kolay bir şekilde oluşturabiliriz: ```php $form->render(); ``` -ve sonuç aşağıdaki gibi görünmelidir: +ve tarayıcıda şöyle görünecektir: -[* form-en.webp *] +[* form-cs.webp *] -Form, `Nette\Forms\Form` sınıfının bir nesnesidir ( `Nette\Application\UI\Form` sınıfı sunumlarda kullanılır). İçine isim, şifre ve gönder butonu kontrollerini ekledik. +Form, `Nette\Forms\Form` sınıfının bir nesnesidir (`Nette\Application\UI\Form` sınıfı presenter'larda kullanılır). Buna isim, şifre ve gönderme düğmesi gibi öğeler ekledik. -Şimdi formu yeniden canlandıracağız. `$form->isSuccess()` adresine sorarak formun gönderilip gönderilmediğini ve geçerli bir şekilde doldurulup doldurulmadığını öğreneceğiz. Eğer öyleyse, verileri dökeceğiz. Formun tanımından sonra ekleyeceğiz: +Şimdi formu canlandıralım. `$form->isSuccess()` sorgusuyla formun gönderilip gönderilmediğini ve geçerli bir şekilde doldurulup doldurulmadığını öğreneceğiz. Eğer öyleyse, verileri yazdıracağız. Form tanımından sonra şunu ekleyeceğiz: ```php if ($form->isSuccess()) { - echo 'Form doğru şekilde dolduruldu ve gönderildi'; + echo 'Form doğru bir şekilde dolduruldu ve gönderildi'; $data = $form->getValues(); - // $data->name isim içeriyor - // $data->password parola içerir + // $data->name ismi içerir + // $data->password şifreyi içerir var_dump($data); } ``` -`getValues()` yöntemi, gönderilen verileri bir [ArrayHash |utils:arrays#ArrayHash] nesnesi biçiminde döndürür. Bunu nasıl değiştireceğimizi [daha sonra |#Mapping to Classes] göstereceğiz. `$data` değişkeni, kullanıcı tarafından girilen verilerle birlikte `name` ve `password` anahtarlarını içerir. +`getValues()` metodu, gönderilen verileri [ArrayHash |utils:arrays#ArrayHash] nesnesi biçiminde döndürür. Bunun nasıl değiştirileceğini [daha sonra |#Sınıflara Eşleme] göstereceğiz. `$data` nesnesi, kullanıcının doldurduğu verilerle birlikte `name` ve `password` anahtarlarını içerir. -Genellikle verileri, örneğin veritabanına ekleme gibi daha ileri işlemler için doğrudan göndeririz. Ancak, işleme sırasında bir hata oluşabilir, örneğin kullanıcı adı zaten alınmıştır. Bu durumda, `addError()` adresini kullanarak hatayı forma geri iletiriz ve bir hata mesajıyla birlikte yeniden çizilmesine izin veririz: +Genellikle verileri doğrudan daha fazla işleme göndeririz, bu da örneğin veritabanına ekleme olabilir. Ancak işleme sırasında bir hata oluşabilir, örneğin kullanıcı adı zaten alınmış olabilir. Bu durumda, hatayı `addError()` kullanarak forma geri iletiriz ve hata mesajıyla birlikte yeniden oluşturulmasını sağlarız. ```php -$form->addError('Sorry, username is already in use.'); +$form->addError('Üzgünüz, bu kullanıcı adı zaten kullanılıyor.'); ``` -Formu işledikten sonra bir sonraki sayfaya yönlendireceğiz. Bu, formun *yenile*, *geri* düğmesine tıklanarak veya tarayıcı geçmişi taşınarak istenmeden yeniden gönderilmesini önler. +Formu işledikten sonra bir sonraki sayfaya yönlendiririz. Bu, *yenile*, *geri* düğmesiyle veya tarayıcı geçmişinde gezinerek formun istenmeyen şekilde yeniden gönderilmesini önler. -Varsayılan olarak, form POST yöntemi kullanılarak aynı sayfaya gönderilir. Her ikisi de değiştirilebilir: +Form varsayılan olarak POST metoduyla aynı sayfaya gönderilir. Her ikisi de değiştirilebilir: ```php $form->setAction('/submit.php'); $form->setMethod('GET'); ``` -Ve hepsi bu kadar :-) İşlevsel ve mükemmel şekilde [güvence |#Vulnerability Protection] altına alınmış bir formumuz var. +Ve aslında hepsi bu :-) İşlevsel ve mükemmel bir şekilde [güvenli |#Güvenlik Açıklarına Karşı Koruma] bir formumuz var. -Daha fazla [form denetimi |controls] eklemeyi deneyin. +Diğer [form elemanları |controls] eklemeyi de deneyin. -Kontrollere Erişim .[#toc-access-to-controls] -============================================= +Elemanlara Erişim +================= -Form ve tek tek kontrolleri bileşen olarak adlandırılır. Bunlar, kökü form olan bir bileşen ağacı oluşturur. Tek tek kontrollere aşağıdaki şekilde erişebilirsiniz: +Formu ve tek tek elemanlarını bileşenler olarak adlandırırız. Kökü form olan bir bileşen ağacı oluştururlar. Formun tek tek elemanlarına şu şekilde erişebiliriz: ```php $input = $form->getComponent('name'); @@ -80,37 +80,36 @@ $button = $form->getComponent('send'); // alternatif sözdizimi: $button = $form['send']; ``` -Kontroller unset kullanılarak kaldırılır: +Elemanlar unset kullanılarak kaldırılır: ```php unset($form['name']); ``` -Doğrulama Kuralları .[#toc-validation-rules] -============================================ +Doğrulama Kuralları +=================== -Burada *geçerli* kelimesi kullanıldı, ancak formun henüz doğrulama kuralları yok. Hadi düzeltelim. +*Geçerli* kelimesini kullandık, ancak formun henüz herhangi bir doğrulama kuralı yok. Bunu düzeltelim. -Ad zorunlu olacaktır, bu nedenle onu, argümanı kullanıcı doldurmazsa görüntülenecek hata mesajının metni olan `setRequired()` yöntemiyle işaretleyeceğiz. Herhangi bir argüman verilmezse, varsayılan hata mesajı kullanılır. +İsim zorunlu olacak, bu yüzden onu `setRequired()` metoduyla işaretleyeceğiz. Argümanı, kullanıcı ismi doldurmazsa görüntülenecek hata mesajının metnidir. Argüman belirtmezsek, varsayılan hata mesajı kullanılır. ```php -$form->addText('name', 'Name:') - ->setRequired('Please enter a name.'); +$form->addText('name', 'İsim:') + ->setRequired('Lütfen bir isim girin'); ``` -İsim doldurulmadan formu göndermeye çalıştığınızda bir hata mesajının görüntülendiğini ve siz doldurana kadar tarayıcı veya sunucunun formu reddettiğini göreceksiniz. +Formu doldurulmuş bir isim olmadan göndermeyi deneyin ve bir hata mesajının görüntüleneceğini ve tarayıcının veya sunucunun alanı doldurana kadar reddedeceğini göreceksiniz. -Aynı zamanda, örneğin girişe sadece boşluk yazarak sistemi aldatamazsınız. Mümkün değil. Nette sol ve sağ boşlukları otomatik olarak keser. Bunu deneyin. Bu, her tek satırlık girişte her zaman yapmanız gereken bir şeydir, ancak genellikle unutulur. Nette bunu otomatik olarak yapar. (Formları kandırmayı deneyebilir ve isim olarak çok satırlı bir dize gönderebilirsiniz. Burada bile Nette aldanmayacak ve satır sonları boşluk olarak değişecektir). +Aynı zamanda, alana sadece boşluk yazarak sistemi aldatamazsınız. Hayır. Nette sol ve sağ boşlukları otomatik olarak kaldırır. Deneyin. Bu, her tek satırlık girişle her zaman yapmanız gereken bir şeydir, ancak genellikle unutulur. Nette bunu otomatik olarak yapar. (Formu aldatmayı deneyebilir ve isim olarak çok satırlı bir dize gönderebilirsiniz. Nette burada bile kafası karışmaz ve satır sonlarını boşluklara dönüştürür.) -Form her zaman sunucu tarafında doğrulanır, ancak JavaScript doğrulaması da oluşturulur, bu da hızlıdır ve kullanıcı formu sunucuya göndermek zorunda kalmadan hatayı hemen öğrenir. Bu işlem `netteForms.js` betiği tarafından gerçekleştirilir. -Bunu sayfaya ekleyin: +Form her zaman sunucu tarafında doğrulanır, ancak aynı zamanda anında çalışan ve kullanıcının hatayı formu sunucuya göndermeye gerek kalmadan hemen öğrenmesini sağlayan JavaScript doğrulaması da oluşturulur. Bu, `netteForms.js` betiği tarafından halledilir. Sayfaya ekleyin: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Formun bulunduğu sayfanın kaynak koduna bakarsanız, Nette'in gerekli alanları `required` CSS sınıfına sahip öğelere eklediğini fark edebilirsiniz. Aşağıdaki stili şablona eklemeyi deneyin, "Ad" etiketi kırmızı olacaktır. Kullanıcılar için gerekli alanları zarif bir şekilde işaretliyoruz: +Formu içeren sayfanın kaynak koduna bakarsanız, Nette'nin zorunlu elemanları `required` CSS sınıfına sahip elemanlara eklediğini fark edebilirsiniz. Şablona aşağıdaki stil sayfasını eklemeyi deneyin ve "İsim" etiketi kırmızı olacaktır. Bu şekilde, zorunlu elemanları kullanıcılara zarif bir şekilde işaretleriz: ```latte <style> @@ -118,119 +117,121 @@ Formun bulunduğu sayfanın kaynak koduna bakarsanız, Nette'in gerekli alanlar </style> ``` -Ek doğrulama kuralları `addRule()` yöntemi ile eklenecektir. İlk parametre kuraldır, ikincisi yine hata mesajının metnidir ve isteğe bağlı doğrulama kuralı argümanı takip edebilir. Bu ne anlama geliyor? +Diğer doğrulama kurallarını `addRule()` metoduyla ekleriz. İlk parametre kuraldır, ikincisi yine hata mesajının metnidir ve bunu doğrulama kuralının argümanı takip edebilir. Bu ne anlama geliyor? -Form, bir sayı olması (`addInteger()`) ve belirli sınırlar içinde olması (`$form::Range`) koşuluyla başka bir isteğe bağlı girdi *yaş* alacaktır. Ve burada `addRule()`'un üçüncü argümanı olan aralığın kendisini kullanacağız: +Formu, tamsayı olması gereken (`addInteger()`) ve ayrıca izin verilen bir aralıkta olması gereken (`$form::Range`) yeni bir isteğe bağlı "yaş" alanı ile genişleteceğiz. Ve burada, doğrulayıcıya istenen aralığı `[başlangıç, bitiş]` çifti olarak ilettiğimiz `addRule()` metodunun üçüncü parametresini kullanacağız: ```php -$form->addInteger('age', 'Age:') - ->addRule($form::Range, 'You must be older 18 years and be under 120.', [18, 120]); +$form->addInteger('age', 'Yaş:') + ->addRule($form::Range, 'Yaş 18 ile 120 arasında olmalıdır', [18, 120]); ``` .[tip] -Kullanıcı alanı doldurmazsa, alan isteğe bağlı olduğu için doğrulama kuralları doğrulanmayacaktır. +Kullanıcı alanı doldurmazsa, eleman isteğe bağlı olduğu için doğrulama kuralları kontrol edilmeyecektir. -Açıkçası küçük bir yeniden düzenleme için yer mevcut. Hata mesajında ve üçüncü parametrede, sayılar çift olarak listelenmiştir ve bu ideal değildir. [Çok dilli |rendering#translating] bir [form |rendering#translating] oluşturuyor olsaydık ve sayıları içeren mesajın birden fazla dile çevrilmesi gerekseydi, değerleri değiştirmek daha zor olurdu. Bu nedenle, `%d` ikame karakterleri kullanılabilir: +Burada küçük bir yeniden düzenleme için yer var. Hata mesajında ve üçüncü parametrede sayılar yineleniyor, bu ideal değil. Sayıları içeren bir mesaj [çok dilli formlar |rendering#Çeviri] oluşturursak ve birden fazla dile çevrilirse, değerleri değiştirmek zorlaşır. Bu nedenle, `%d` yer tutucularını kullanmak mümkündür ve Nette değerleri tamamlayacaktır: ```php - ->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]); + ->addRule($form::Range, 'Yaş %d ile %d arasında olmalıdır', [18, 120]); ``` -Parola* alanına geri dönelim, bunu *gerekli* yapalım ve yine mesajdaki yedek karakterleri kullanarak minimum parola uzunluğunu (`$form::MinLength`) doğrulayalım: +`password` elemanına geri dönelim, onu da zorunlu hale getireceğiz ve ayrıca şifrenin minimum uzunluğunu (`$form::MinLength`) doğrulayacağız, yine yer tutucu kullanarak: ```php -$form->addPassword('password', 'Password:') - ->setRequired('Pick a password') - ->addRule($form::MinLength, 'Your password has to be at least %d long', 8); +$form->addPassword('password', 'Şifre:') + ->setRequired('Bir şifre seçin') + ->addRule($form::MinLength, 'Şifre en az %d karakter uzunluğunda olmalıdır', 8); ``` -Kontrol için kullanıcının şifreyi tekrar girdiği forma `passwordVerify` şeklinde bir alan ekleyeceğiz. Doğrulama kurallarını kullanarak, her iki şifrenin de aynı olup olmadığını kontrol ediyoruz (`$form::Equal`). Ve bir argüman olarak [köşeli |#Access to Controls] parantez kullanarak ilk şifreye bir referans veriyoruz: +Forma bir `passwordVerify` alanı daha ekleyelim, burada kullanıcı kontrol için şifreyi tekrar girecektir. Doğrulama kurallarını kullanarak her iki şifrenin de aynı olup olmadığını kontrol edeceğiz (`$form::Equal`). Ve parametre olarak, [köşeli parantezler |#Elemanlara Erişim] kullanarak ilk şifreye bir referans vereceğiz: ```php -$form->addPassword('passwordVerify', 'Password again:') - ->setRequired('Fill your password again to check for typo') - ->addRule($form::Equal, 'Password mismatch', $form['password']) +$form->addPassword('passwordVerify', 'Kontrol için şifre:') + ->setRequired('Lütfen kontrol için şifreyi tekrar girin') + ->addRule($form::Equal, 'Şifreler eşleşmiyor', $form['password']) ->setOmitted(); ``` -`setOmitted()` adresini kullanarak, değerini gerçekten önemsemediğimiz ve yalnızca doğrulama için var olan bir öğeyi işaretledik. Değeri `$data` adresine aktarılmaz. +`setOmitted()` kullanarak, değerine aslında önem vermediğimiz ve yalnızca doğrulama amacıyla var olan bir elemanı işaretledik. Değer `$data`'ya iletilmeyecektir. -PHP ve JavaScript'te doğrulama ile tamamen işlevsel bir formumuz var. Nette'nin doğrulama yetenekleri çok daha geniştir, koşullar oluşturabilir, bunlara göre bir sayfanın bölümlerini görüntüleyebilir ve gizleyebilirsiniz, vb. Her şeyi [form doğrul |validation] ama bölümünde bulabilirsiniz. +Bununla, PHP ve JavaScript'te doğrulamaya sahip tamamen işlevsel bir formumuz var. Nette'nin doğrulama yetenekleri çok daha geniştir, koşullar oluşturabilir, bunlara göre sayfanın bölümlerini gösterebilir ve gizleyebilir vb. Her şeyi [form doğrulama |validation] bölümünde öğreneceksiniz. -Varsayılan Değerler .[#toc-default-values] -========================================== +Varsayılan Değerler +=================== -Form denetimleri için genellikle varsayılan değerler ayarlarız: +Form elemanlarına genellikle varsayılan değerler atarız: ```php -$form->addEmail('email', 'Email') +$form->addEmail('email', 'E-posta') ->setDefaultValue($lastUsedEmail); ``` -Genellikle tüm kontroller için varsayılan değerleri aynı anda ayarlamak yararlıdır. Örneğin, form kayıtları düzenlemek için kullanıldığında. Kaydı veritabanından okuruz ve varsayılan değerler olarak ayarlarız: +Genellikle tüm elemanlara aynı anda varsayılan değerler atamak kullanışlıdır. Örneğin, form kayıtları düzenlemek için kullanıldığında. Veritabanından kaydı okur ve varsayılan değerleri ayarlarız: ```php //$row = ['name' => 'John', 'age' => '33', /* ... */]; $form->setDefaults($row); ``` -Kontrolleri tanımladıktan sonra `setDefaults()` adresini çağırın. +Elemanları tanımladıktan sonra `setDefaults()` çağırın. -Formun Oluşturulması .[#toc-rendering-the-form] -=============================================== +Formu Oluşturma +=============== -Varsayılan olarak, form bir tablo olarak işlenir. Bireysel kontroller temel web erişilebilirlik yönergelerini takip eder. Tüm etiketler şu şekilde oluşturulur `<label>` elemanlarıdır ve girişleriyle ilişkilidir, etikete tıklamak imleci girişe taşır. +Varsayılan olarak, form bir tablo olarak oluşturulur. Tek tek elemanlar temel erişilebilirlik kuralını karşılar - tüm etiketler `<label>` olarak yazılır ve ilgili form elemanıyla ilişkilendirilir. Etikete tıklandığında, imleç otomatik olarak form alanında görünür. -Her öğe için herhangi bir HTML niteliği ayarlayabiliriz. Örneğin, bir yer tutucu ekleyin: +Her elemana herhangi bir HTML niteliği atayabiliriz. Örneğin, bir yer tutucu ekleyin: ```php -$form->addInteger('age', 'Age:') - ->setHtmlAttribute('placeholder', 'Please fill in the age'); +$form->addInteger('age', 'Yaş:') + ->setHtmlAttribute('placeholder', 'Lütfen yaşınızı girin'); ``` -Bir formu oluşturmanın gerçekten pek çok yolu vardır, bu yüzden bu [bölüm |rendering] oluşturma konusuna ayrılmıştır. +Bir formu oluşturmanın gerçekten çok sayıda yolu vardır, bu yüzden [rendering hakkında ayrı bölüm |rendering] buna ayrılmıştır. -Sınıflarla Eşleme .[#toc-mapping-to-classes] -============================================ +Sınıflara Eşleme +================ -Form verilerinin işlenmesine geri dönelim. `getValues()` yöntemi gönderilen verileri bir `ArrayHash` nesnesi olarak döndürür. Bu `stdClass` gibi genel bir sınıf olduğundan, onunla çalışırken editörlerdeki özellikler için kod tamamlama veya statik kod analizi gibi bazı kolaylıklardan yoksun kalacağız. Bu, her form için, özellikleri ayrı kontrolleri temsil eden özel bir sınıfa sahip olarak çözülebilir. Örneğin: +Form verilerinin işlenmesine geri dönelim. `getValues()` metodu bize gönderilen verileri `ArrayHash` nesnesi olarak döndürdü. Bu genel bir sınıf olduğundan, `stdClass` gibi bir şey olduğundan, onunla çalışırken belirli bir rahatlıktan yoksun olacağız, örneğin editörlerde özelliklerin otomatik tamamlanması veya statik kod analizi. Bu, her form için özelliklerinin tek tek elemanları temsil ettiği belirli bir sınıfa sahip olarak çözülebilir. Örneğin: ```php class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -PHP 8.0'dan itibaren, bir kurucu kullanan bu zarif gösterimi kullanabilirsiniz: +Alternatif olarak, bir kurucu kullanabilirsiniz: ```php class RegistrationFormData { public function __construct( public string $name, - public int $age, + public ?int $age, public string $password, ) { } } ``` -Nette'e verileri bize bu sınıfın nesneleri olarak döndürmesini nasıl söyleyebiliriz? Düşündüğünüzden daha kolay. Tek yapmanız gereken parametre olarak hidrate edilecek sınıf adını veya nesneyi belirtmek: +Veri sınıfının özellikleri enum'lar da olabilir ve bunlar otomatik olarak eşlenir. .{data-version:3.2.4} + +Nette'ye verileri bu sınıfın nesneleri olarak döndürmesini nasıl söyleyebiliriz? Düşündüğünüzden daha kolay. Sınıf adını veya hidratlanacak nesneyi parametre olarak belirtmeniz yeterlidir: ```php $data = $form->getValues(RegistrationFormData::class); $name = $data->name; ``` -Parametre olarak bir `'array'` adresi de belirtilebilir ve ardından veriler bir dizi olarak döndürülür. +Parametre olarak `'array'` de belirtilebilir ve ardından veriler dizi olarak döndürülür. -Formlar konteynerlerden oluşan çok seviyeli bir yapıdan oluşuyorsa, her biri için ayrı bir sınıf oluşturun: +Formlar konteynerlerden oluşan çok seviyeli bir yapı oluşturuyorsa, her biri için ayrı bir sınıf oluşturun: ```php $form = new Form; @@ -247,22 +248,24 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` -Eşleme daha sonra `$person` özellik türünden konteyneri `PersonFormData` sınıfına eşlemesi gerektiğini bilir. Özellik bir kapsayıcı dizisi içerecekse, `array` türünü sağlayın ve doğrudan kapsayıcıyla eşlenecek sınıfı iletin: +Eşleme daha sonra `$person` özelliğinin türünden konteyneri `PersonFormData` sınıfına eşlemesi gerektiğini anlar. Özellik bir konteyner dizisi içeriyorsa, `array` türünü belirtin ve eşlenecek sınıfı doğrudan konteynere iletin: ```php $person->setMappedType(PersonFormData::class); ``` +Form veri sınıfının tasarımını `Nette\Forms\Blueprint::dataClass($form)` metodunu kullanarak oluşturabilirsiniz, bu da onu tarayıcı sayfasına yazdırır. Ardından kodu tıklayarak işaretleyip projenize kopyalamanız yeterlidir. .{data-version:3.1.15} + -Çoklu Gönder Düğmeleri .[#toc-multiple-submit-buttons] -====================================================== +Çoklu Düğmeler +============== -Formda birden fazla düğme varsa, genellikle hangisine basıldığını ayırt etmemiz gerekir. Düğmenin `isSubmittedBy()` yöntemi bize bu bilgiyi döndürür: +Formun birden fazla düğmesi varsa, genellikle hangisinin basıldığını ayırt etmemiz gerekir. Düğmenin `isSubmittedBy()` metodu bize bu bilgiyi döndürür: ```php $form->addSubmit('save', 'Kaydet'); @@ -279,37 +282,36 @@ if ($form->isSuccess()) { } ``` -Verilerin geçerliliğini doğrulamak için `$form->isSuccess()` adresini atlamayın. +Verilerin geçerliliğini doğrulamak için `$form->isSuccess()` sorgusunu atlamayın. -Bir form <kbd>Enter</kbd> tuşu ile gönderildiğinde, ilk düğme ile gönderilmiş gibi işlem görür. +Form <kbd>Enter</kbd> tuşuyla gönderildiğinde, ilk düğmeyle gönderilmiş gibi kabul edilir. -Güvenlik Açığı Koruması .[#toc-vulnerability-protection] -======================================================== +Güvenlik Açıklarına Karşı Koruma +================================ -Nette Framework güvenli olmak için büyük çaba sarf eder ve formlar en yaygın kullanıcı girdisi olduğundan, Nette formları aşılmaz kadar iyidir. +Nette Framework güvenliğe büyük önem verir ve bu nedenle formların iyi bir şekilde güvence altına alınmasına özen gösterir. -Formları Siteler Arası Komut Dosyası [Yazma (XSS) |nette:glossary#cross-site-scripting-xss] ve [Siteler Arası İstek Sahteciliği (CSRF) |nette:glossary#cross-site-request-forgery-csrf] gibi iyi bilinen saldırı açıklarına karşı korumanın yanı sıra, artık düşünmek zorunda olmadığınız birçok küçük güvenlik görevini de yerine getirir. +Formları [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] ve [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF] saldırılarına karşı korumanın yanı sıra, artık düşünmeniz gerekmeyen birçok küçük güvenlik önlemi alır. -Örneğin, girdilerden tüm kontrol karakterlerini filtreler ve UTF-8 kodlamasının geçerliliğini kontrol eder, böylece formdan gelen veriler her zaman temiz olur. Seçim kutuları ve radyo listeleri için, seçilen öğelerin gerçekten sunulanlardan olduğunu ve herhangi bir sahtecilik olmadığını doğrular. Tek satırlı metin girişi için, bir saldırganın oraya gönderebileceği satır sonu karakterlerini kaldırdığından daha önce bahsetmiştik. Çok satırlı girdiler için satır sonu karakterlerini normalleştirir. Ve böyle devam eder. +Örneğin, girdilerden tüm kontrol karakterlerini filtreler ve UTF-8 kodlamasının geçerliliğini kontrol eder, böylece form verileri her zaman temiz olur. Seçim kutuları ve radyo listeleri için, seçilen öğelerin gerçekten sunulanlardan olduğunu ve sahtecilik yapılmadığını doğrular. Tek satırlık metin girişlerinde, bir saldırganın oraya göndermiş olabileceği satır sonu karakterlerini kaldırdığını zaten belirtmiştik. Çok satırlı girişler için ise satır sonu karakterlerini normalleştirir. Ve böyle devam eder. -Nette, çoğu programcının varlığından bile haberdar olmadığı güvenlik açıklarını sizin için düzeltir. +Nette, birçok programcının varlığından bile haberdar olmadığı güvenlik risklerini sizin için çözer. -Bahsedilen CSRF saldırısı, bir saldırganın kurbanı, kurbanın tarayıcısında kurbanın o anda oturum açtığı sunucuya sessizce bir istek yürüten bir sayfayı ziyaret etmesi için kandırması ve sunucunun, isteğin kurban tarafından isteğe bağlı olarak yapıldığına inanmasıdır. Bu nedenle Nette, formun başka bir etki alanından POST yoluyla gönderilmesini engeller. Herhangi bir nedenle korumayı kapatmak ve formun başka bir etki alanından gönderilmesine izin vermek istiyorsanız, şunu kullanın: +Bahsedilen CSRF saldırısı, bir saldırganın kurbanı, kurbanın oturum açtığı sunucuya fark ettirmeden bir istek gerçekleştiren bir sayfaya çekmesi ve sunucunun isteği kurbanın kendi isteğiyle gerçekleştirdiğini varsaymasıdır. Bu nedenle Nette, POST formunun başka bir alan adından gönderilmesini engeller. Herhangi bir nedenle korumayı kapatmak ve formun başka bir alan adından gönderilmesine izin vermek istiyorsanız, şunu kullanın: ```php $form->allowCrossOrigin(); // DİKKAT! Korumayı kapatır! ``` -Bu koruma `_nss` adında bir SameSite çerezi kullanır. Bu nedenle, çerezin gönderilebilmesi için ilk çıktıyı flushing yapmadan önce bir form oluşturun. +Bu koruma, `_nss` adlı SameSite çerezini kullanır. Bu nedenle, çerezin gönderilebilmesi için ilk çıktıyı göndermeden önce form nesnesini oluşturun. -SameSite çerez koruması %100 güvenilir olmayabilir, bu nedenle token korumasını açmak iyi bir fikirdir: +SameSite çerezi ile koruma %100 güvenilir olmayabilir, bu nedenle token ile korumayı da etkinleştirmek iyi bir fikirdir: ```php $form->addProtection(); ``` -Bu korumayı uygulamanızın hassas verileri değiştiren idari bölümündeki formlara uygulamanız şiddetle tavsiye edilir. Çerçeve, bir oturumda saklanan kimlik doğrulama belirtecini oluşturarak ve doğrulayarak CSRF saldırısına karşı koruma sağlar (argüman, belirtecin süresi dolmuşsa gösterilen hata mesajıdır). Bu nedenle formu görüntülemeden önce bir oturumun başlatılmış olması gerekir. Web sitesinin yönetim bölümünde, kullanıcının oturum açması nedeniyle oturum genellikle zaten başlatılmıştır. -Aksi takdirde, oturumu `Nette\Http\Session::start()` yöntemi ile başlatın. +Uygulamadaki hassas verileri değiştiren web sitesinin yönetim bölümündeki formları bu şekilde korumanızı öneririz. Framework, oturumda saklanan bir yetkilendirme token'ı oluşturup doğrulayarak CSRF saldırısına karşı kendini savunur. Bu nedenle, formu görüntülemeden önce oturumun açık olması gerekir. Web sitesinin yönetim bölümünde, genellikle kullanıcının oturum açması nedeniyle oturum zaten başlatılmıştır. Aksi takdirde, oturumu `Nette\Http\Session::start()` metoduyla başlatın. -Böylece, Nette'deki formlara hızlı bir giriş yapmış olduk. Daha fazla ilham almak için dağıtımdaki [örnekler |https://github.com/nette/forms/tree/master/examples] dizinine bakmayı deneyin. +İşte Nette'deki formlara hızlı bir giriş yaptık. Daha fazla ilham almak için dağıtımdaki [examples|https://github.com/nette/forms/tree/master/examples] dizinine göz atmayı deneyin. diff --git a/forms/tr/validation.texy b/forms/tr/validation.texy index 2d02efe8a3..545442c823 100644 --- a/forms/tr/validation.texy +++ b/forms/tr/validation.texy @@ -2,172 +2,183 @@ Form Doğrulama ************** -Gerekli Kontroller .[#toc-required-controls] -============================================ +Zorunlu Elemanlar +================= -Kontroller, argümanı kullanıcı doldurmazsa görüntülenecek [hata mesajının |#Error Messages] metni olan `setRequired()` yöntemiyle gerekli olarak işaretlenir. Herhangi bir bağımsız değişken verilmezse, varsayılan hata mesajı kullanılır. +Zorunlu elemanları `setRequired()` metoduyla işaretleriz. Argümanı, kullanıcı elemanı doldurmazsa görüntülenecek [#hata mesajları] metnidir. Argüman belirtmezsek, varsayılan hata mesajı kullanılır. ```php -$form->addText('name', 'Name:') - ->setRequired('Please fill your name.'); +$form->addText('name', 'İsim:') + ->setRequired('Lütfen bir isim girin'); ``` -Kurallar .[#toc-rules] -====================== +Kurallar +======== -`addRule()` metodu ile kontrollere doğrulama kuralları ekleriz. İlk parametre kural, ikincisi [hata mesajı, |#Error Messages] üçüncüsü ise doğrulama kuralı argümanıdır. +Doğrulama kurallarını elemanlara `addRule()` metoduyla ekleriz. İlk parametre kuraldır, ikincisi [#hata mesajları] metnidir ve üçüncüsü doğrulama kuralının argümanıdır. ```php -$form->addPassword('password', 'Password:') - ->addRule($form::MinLength, 'Password must be at least %d characters', 8); +$form->addPassword('password', 'Şifre:') + ->addRule($form::MinLength, 'Şifre en az %d karakter uzunluğunda olmalıdır', 8); ``` -Nette, adları `Nette\Forms\Form` sınıfının sabitleri olan bir dizi yerleşik kuralla birlikte gelir: +**Doğrulama kuralları yalnızca kullanıcı elemanı doldurduğunda kontrol edilir.** -Tüm kontroller için aşağıdaki kuralları kullanabiliriz: +Nette, adları `Nette\Forms\Form` sınıfının sabitleri olan bir dizi önceden tanımlanmış kuralla birlikte gelir. Tüm elemanlar için şu kuralları kullanabiliriz: -| sabit | açıklama | argümanlar +| sabit | açıklama | argüman türü |------- -| `Required` | `setRequired()` 'un takma adı | - -| `Filled` | `setRequired()` 'un takma adı | - -| `Blank` | doldurulmamalıdır | - +| `Required` | zorunlu eleman, `setRequired()` için takma ad | - +| `Filled` | zorunlu eleman, `setRequired()` için takma ad | - +| `Blank` | eleman doldurulmamalıdır | - | `Equal` | değer parametreye eşittir | `mixed` -| `NotEqual` | değer parametreye eşit değil | `mixed` -| `IsIn` | değer dizideki bazı elemanlara eşittir | `array` -| `IsNotIn` | değer dizideki herhangi bir elemana eşit değil | `array` -| `Valid` | girdi doğrulamayı geçer ( [koşullar |#conditions] için) | - +| `NotEqual` | değer parametreye eşit değildir | `mixed` +| `IsIn` | değer dizideki bazı öğelere eşittir | `array` +| `IsNotIn` | değer dizideki hiçbir öğeye eşit değildir | `array` +| `Valid` | eleman doğru doldurulmuş mu? ([#koşullar] için) | - + -`addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` denetimleri için aşağıdaki kurallar da kullanılabilir: +Metin Girişleri +--------------- -| `MinLength` | minimum dize uzunluğu | `int` -| `MaxLength` | maksimum dize uzunluğu | `int` -| `Length` | aralıktaki uzunluk veya tam uzunluk | çifti `[int, int]` veya `int` +`addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`, `addFloat()` elemanları için aşağıdaki kurallardan bazıları da kullanılabilir: + +| `MinLength` | minimum metin uzunluğu | `int` +| `MaxLength` | maksimum metin uzunluğu | `int` +| `Length` | aralıktaki uzunluk veya tam uzunluk | çift `[int, int]` veya `int` | `Email` | geçerli e-posta adresi | - -| `URL` | geçerli URL | - -| `Pattern` | düzenli kalıpla eşleşir | `string` -| `PatternInsensitive` | `Pattern` gibi, ancak büyük/küçük harf duyarsız | `string` -| `Integer` | tamsayı | - -| `Numeric` | `Integer` 'un takma adı | - -| `Float` | tamsayı veya kayan noktalı sayı | - -| `Min` | tamsayı değerinin minimum değeri | `int\|float` -| `Max` | tamsayı değerinin maksimum değeri | `int\|float` -| `Range` | aralıktaki değer | çifti `[int\|float, int\|float]` +| `URL` | mutlak URL | - +| `Pattern` | düzenli ifadeye uyar | `string` +| `PatternInsensitive` | `Pattern` gibi, ancak büyük/küçük harfe duyarsız | `string` +| `Integer` | tamsayı değeri | - +| `Numeric` | `Integer` için takma ad | - +| `Float` | sayı | - +| `Min` | sayısal elemanın minimum değeri | `int\|float` +| `Max` | sayısal elemanın maksimum değeri | `int\|float` +| `Range` | aralıktaki değer | çift `[int\|float, int\|float]` + +`Integer`, `Numeric` ve `Float` doğrulama kuralları değeri doğrudan tamsayıya resp. ondalık sayıya dönüştürür. Ve ayrıca `URL` kuralı şemasız bir adresi de kabul eder (ör. `nette.org`) ve şemayı ekler (`https://nette.org`). `Pattern` ve `PatternIcase` içindeki ifade tüm değer için geçerli olmalıdır, yani `^` ve `$` karakterleriyle çevrelenmiş gibi. -`Integer`, `Numeric` a `Float` kuralları değeri otomatik olarak tamsayıya (veya sırasıyla float) dönüştürür. Ayrıca, `URL` kuralı da şeması olmayan bir adresi kabul eder (örneğin `nette.org`) ve şemayı tamamlar (`https://nette.org`). -`Pattern` ve `PatternInsensitive` içindeki ifadeler tüm değer için geçerli olmalıdır, yani `^` and `$` karakterlerine sarılmış gibi olmalıdır. -`addUpload()`, `addMultiUpload()` kontrolleri için aşağıdaki kurallar da kullanılabilir: +Öğe Sayısı +---------- -| `MaxFileSize` | maksimum dosya boyutu | `int` -| `MimeType` | MIME türü, joker karakterleri kabul eder (`'video/*'`) | `string\|string[]` -| `Image` | yüklenen dosya JPEG, PNG, GIF, WebP | - -| `Pattern` | dosya adı düzenli ifadeyle eşleşiyor | `string` -| `PatternInsensitive` | `Pattern` gibi, ancak büyük/küçük harf duyarsız | `string` +`addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()` elemanları için, seçilen öğelerin resp. yüklenen dosyaların sayısını sınırlamak üzere aşağıdaki kurallar da kullanılabilir: -`MimeType` ve `Image` PHP uzantısı `fileinfo` gerektirir. Bir dosya veya resmin gerekli türde olup olmadığı imzasından anlaşılır. Tüm dosyanın bütünlüğü kontrol edilmez. Bir resmin bozuk olup olmadığını örneğin [yüklemeye |http:request#toImage] çalışarak öğrenebilirsiniz. +| `MinLength` | minimum sayı | `int` +| `MaxLength` | maksimum sayı | `int` +| `Length` | aralıktaki sayı veya tam sayı | çift `[int, int]` veya `int` -`addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()` kontrolleri için, sırasıyla yüklenen dosyalar olmak üzere seçilen öğelerin sayısını sınırlamak için aşağıdaki kurallar da kullanılabilir: -| `MinLength` | minimum sayı | `int` -| `MaxLength` | maksimum sayı | `int` -| `Length` | aralıktaki sayı veya tam sayı | çifti `[int, int]` veya `int` +Dosya Yüklemeleri +----------------- +`addUpload()`, `addMultiUpload()` elemanları için aşağıdaki kurallar da kullanılabilir: -Hata Mesajları .[#toc-error-messages] -------------------------------------- +| `MaxFileSize` | bayt cinsinden maksimum dosya boyutu | `int` +| `MimeType` | MIME türü, joker karakterlere izin verilir (`'video/*'`) | `string\|string[]` +| `Image` | JPEG, PNG, GIF, WebP, AVIF resmi | - +| `Pattern` | dosya adı düzenli ifadeye uyar | `string` +| `PatternInsensitive` | `Pattern` gibi, ancak büyük/küçük harfe duyarsız | `string` -`Pattern` ve `PatternInsensitive` dışındaki tüm önceden tanımlanmış kuralların varsayılan bir hata mesajı vardır, bu nedenle bunlar atlanabilir. Bununla birlikte, tüm özelleştirilmiş mesajları ileterek ve formüle ederek, formu daha kullanıcı dostu hale getireceksiniz. +`MimeType` ve `Image`, PHP `fileinfo` uzantısını gerektirir. Bir dosyanın veya resmin istenen türde olup olmadığını imzasına göre algılarlar ve **tüm dosyanın bütünlüğünü doğrulamazlar.** Bir resmin hasarlı olup olmadığını, örneğin onu [yüklemeye |http:request#toImage] çalışarak belirleyebilirsiniz. -Varsayılan mesajları [configuration'da |forms:configuration], `Nette\Forms\Validator::$messages` dizisindeki metinleri değiştirerek veya [çevirmen |rendering#translating] kullanarak değiştirebilirsiniz. -Hata mesajlarının metninde aşağıdaki joker karakterler kullanılabilir: +Hata Mesajları +============== -| `%d` | argümanlardan sonraki kuralları kademeli olarak değiştirir -| `%n$d` | n'inci kural argümanıyla yer değiştirir -| `%label` | alan etiketi ile değiştirir (iki nokta üst üste olmadan) -| `%name` | alan adı ile değiştirir (örneğin `name`) -| `%value` | kullanıcı tarafından girilen değerle değiştirir +`Pattern` ve `PatternInsensitive` hariç tüm önceden tanımlanmış kuralların varsayılan bir hata mesajı vardır, bu nedenle atlanabilir. Ancak, tüm mesajları özel olarak belirterek ve formüle ederek formu kullanıcı dostu hale getirebilirsiniz. + +Varsayılan mesajları [yapılandırmada |forms:configuration], `Nette\Forms\Validator::$messages` dizisindeki metinleri düzenleyerek veya [çevirmen |rendering#Çeviri] kullanarak değiştirebilirsiniz. + +Hata mesajlarının metninde şu yer tutucu dizeler kullanılabilir: + +| `%d` | kural argümanlarıyla sırayla değiştirilir +| `%n$d` | n'inci kural argümanıyla değiştirilir +| `%label` | eleman etiketiyle değiştirilir (iki nokta üst üste olmadan) +| `%name` | eleman adıyla değiştirilir (ör. `name`) +| `%value` | kullanıcı tarafından girilen değerle değiştirilir ```php -$form->addText('name', 'Name:') - ->setRequired('Please fill in %label'); +$form->addText('name', 'İsim:') + ->setRequired('Lütfen %label girin'); $form->addInteger('id', 'ID:') - ->addRule($form::Range, 'at least %d and no more than %d', [5, 10]); + ->addRule($form::Range, 'en az %d ve en fazla %d', [5, 10]); $form->addInteger('id', 'ID:') - ->addRule($form::Range, 'no more than %2$d and at least %1$d', [5, 10]); + ->addRule($form::Range, 'en fazla %2$d ve en az %1$d', [5, 10]); ``` -Koşullar .[#toc-conditions] -=========================== +Koşullar +======== -Doğrulama kurallarının yanı sıra koşullar da belirlenebilir. Bunlar kurallar gibi ayarlanır, ancak `addCondition()` yerine `addRule()` kullanırız ve elbette bir hata mesajı olmadan bırakırız (koşul sadece sorar): +Kurallara ek olarak koşullar da eklenebilir. Bunlar kurallara benzer şekilde yazılır, ancak `addRule()` yerine `addCondition()` metodunu kullanırız ve tabii ki herhangi bir hata mesajı belirtmeyiz (koşul sadece sorar): ```php -$form->addPassword('password', 'Password:') - // eğer şifre 8 karakterden uzun değilse ... +$form->addPassword('password', 'Şifre:') + // şifre 8 karakterden uzun değilse ->addCondition($form::MaxLength, 8) - // ... o zaman bir sayı içermelidir - ->addRule($form::Pattern, 'Must contain number', '.*[0-9].*'); + // o zaman bir rakam içermelidir + ->addRule($form::Pattern, 'Bir rakam içermelidir', '.*[0-9].*'); ``` -Koşul, `addConditionOn()` kullanılarak geçerli olandan farklı bir öğeye bağlanabilir. İlk parametre alana bir referanstır. Aşağıdaki durumda, e-posta yalnızca onay kutusu işaretliyse (yani değeri `true` ise) gerekli olacaktır: +Koşul, `addConditionOn()` kullanarak geçerli olandan başka bir elemana da bağlanabilir. İlk parametre olarak elemana bir referans belirtiriz. Bu örnekte, e-posta yalnızca onay kutusu işaretlendiğinde (değeri true olacaktır) zorunlu olacaktır: ```php -$form->addCheckbox('newsletters', 'send me newsletters'); +$form->addCheckbox('newsletters', 'bana bülten gönder'); -$form->addEmail('email', 'Email:') - // onay kutusu işaretliyse ... +$form->addEmail('email', 'E-posta:') + // onay kutusu işaretliyse ->addConditionOn($form['newsletters'], $form::Equal, true) - // ... e-posta gerektirir - ->setRequired('E-posta adresinizi doldurun'); + // o zaman e-posta iste + ->setRequired('Bir e-posta adresi girin'); ``` -Koşullar `elseCondition()` ve `endCondition()` yöntemleri ile karmaşık yapılar halinde gruplandırılabilir. +Koşullardan `elseCondition()` ve `endCondition()` kullanarak karmaşık yapılar oluşturulabilir: ```php $form->addText(/* ... */) ->addCondition(/* ... */) // ilk koşul karşılanırsa - ->addConditionOn(/* ... */) // ve ikinci koşulu da başka bir eleman üzerinde - ->addRule(/* ... */) // bu kuralı gerektirir + ->addConditionOn(/* ... */) // ve başka bir eleman üzerinde ikinci koşul + ->addRule(/* ... */) // bu kuralı iste ->elseCondition() // ikinci koşul karşılanmazsa - ->addRule(/* ... */) // bu kuralları gerektirir + ->addRule(/* ... */) // bu kuralları iste ->addRule(/* ... */) ->endCondition() // ilk koşula geri dönüyoruz ->addRule(/* ... */); ``` -Nette, `toggle()` yöntemini kullanarak [JavaScript |#Dynamic JavaScript] tarafında bir koşulun yerine getirilip getirilmemesine tepki vermek çok kolaydır, bkz. +Nette'de, `toggle()` metodunu kullanarak JavaScript tarafında bir koşulun karşılanmasına veya karşılanmamasına çok kolay bir şekilde yanıt verilebilir, bkz. [#dinamik-javascript]. -Kontroller Arasındaki Referanslar .[#toc-references-between-controls] -===================================================================== +Başka Bir Elemana Referans +========================== -Kural veya koşul bağımsız değişkeni başka bir öğeye referans olabilir. Örneğin, `text` adresinin `length` alanının değeri kadar karaktere sahip olduğunu dinamik olarak doğrulayabilirsiniz: +Kural veya koşul argümanı olarak formun başka bir elemanı da iletilebilir. Kural daha sonra tarayıcıda kullanıcı tarafından daha sonra girilen değeri kullanır. Bu şekilde, örneğin `password` elemanının `password_confirm` elemanıyla aynı dizeyi içerip içermediğini dinamik olarak doğrulayabilirsiniz: ```php -$form->addInteger('length'); -$form->addText('text') - ->addRule($form::Length, null, $form['length']); +$form->addPassword('password', 'Şifre'); +$form->addPassword('password_confirm', 'Şifreyi onayla') + ->addRule($form::Equal, 'Girilen şifreler eşleşmiyor', $form['password']); ``` -Özel Kurallar ve Koşullar .[#toc-custom-rules-and-conditions] -============================================================= +Özel Kurallar ve Koşullar +========================= -Bazen Nette'deki yerleşik doğrulama kurallarının yeterli olmadığı ve kullanıcıdan gelen verileri kendi yöntemimizle doğrulamamız gereken bir durumla karşılaşırız. Nette'de bu çok kolaydır! +Bazen Nette'deki yerleşik doğrulama kurallarının yeterli olmadığı ve kullanıcı verilerini kendi yöntemimizle doğrulamamız gereken bir duruma geliriz. Nette'de bu çok basittir! -`addRule()` veya `addCondition()` yöntemlerine ilk parametre olarak herhangi bir geri çağırma iletebilirsiniz. Geri arama, öğenin kendisini ilk parametre olarak kabul eder ve doğrulamanın başarılı olup olmadığını gösteren bir boolean değeri döndürür. `addRule()` kullanarak bir kural eklerken, ek argümanlar geçirilebilir ve bunlar daha sonra ikinci parametre olarak geçirilir. +`addRule()` veya `addCondition()` metotlarına ilk parametre olarak herhangi bir geri arama iletilebilir. Bu, ilk parametre olarak elemanın kendisini alır ve doğrulamanın düzgün bir şekilde yapılıp yapılmadığını belirten bir boole değeri döndürür. `addRule()` kullanarak bir kural eklerken, ek argümanlar da belirtilebilir, bunlar daha sonra ikinci parametre olarak iletilir. -Özel doğrulayıcı kümesi böylece statik yöntemlere sahip bir sınıf olarak oluşturulabilir: +Böylece statik metotlara sahip bir sınıf olarak kendi doğrulayıcı setimizi oluşturabiliriz: ```php class MyValidators { - // değerin argüman tarafından bölünebilir olup olmadığını test eder + // değerin argümana bölünüp bölünemediğini test eder public static function validateDivisibility(BaseControl $input, $arg): bool { return $input->getValue() % $arg === 0; @@ -175,23 +186,23 @@ class MyValidators public static function validateEmailDomain(BaseControl $input, $domain) { - // ek doğrulayıcılar + // diğer doğrulayıcılar } } ``` -Bu durumda kullanım çok basittir: +Kullanım daha sonra çok basittir: ```php $form->addInteger('num') ->addRule( [MyValidators::class, 'validateDivisibility'], - 'The value must be a multiple of %d', + 'Değer %d sayısının katı olmalıdır', 8, ); ``` -Özel doğrulama kuralları JavaScript'e de eklenebilir. Tek gereklilik, kuralın statik bir yöntem olmasıdır. JavaScript doğrulayıcısı için adı, ters eğik çizgiler olmadan sınıf adı `\`, the underscore `_`, ve yöntem adı birleştirilerek oluşturulur. Örneğin, `App\MyValidators::validateDivisibility` öğesini `AppMyValidators_validateDivisibility` olarak yazın ve `Nette.validators` nesnesine ekleyin: +Özel doğrulama kuralları JavaScript'e de eklenebilir. Koşul, kuralın statik bir metot olmasıdır. JavaScript doğrulayıcısı için adı, ters eğik çizgiler `\` olmadan sınıf adının, alt çizgi `_` ve metot adının birleştirilmesiyle oluşturulur. Örneğin, `App\MyValidators::validateDivisibility` `AppMyValidators_validateDivisibility` olarak yazılır ve `Nette.validators` nesnesine eklenir: ```js Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => { @@ -200,12 +211,12 @@ Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => ``` -Olay onValidate .[#toc-event-onvalidate] -======================================== +onValidate Olayı +================ -Form gönderildikten sonra, `addRule()` tarafından eklenen kurallar tek tek kontrol edilerek ve ardından `onValidate`[olayı |nette:glossary#Events] çağrılarak doğrulama gerçekleştirilir. İşleyicisi, tipik olarak birden fazla form öğesindeki değerlerin doğru kombinasyonunu doğrulamak üzere ek doğrulama için kullanılabilir. +Form gönderildikten sonra, `addRule()` kullanılarak eklenen tek tek kuralların kontrol edildiği doğrulama gerçekleştirilir ve ardından [olay |nette:glossary#Olaylar Events] `onValidate` tetiklenir. İşleyicisi, ek doğrulama için kullanılabilir, tipik olarak formun birden fazla elemanındaki değerlerin doğru kombinasyonunu doğrulamak için. -Bir hata tespit edilirse, bu hata `addError()` yöntemi kullanılarak forma iletilir. Bu, belirli bir öğe üzerinde veya doğrudan form üzerinde çağrılabilir. +Bir hata tespit edilirse, `addError()` metoduyla forma iletiriz. Bu, belirli bir eleman üzerinde veya doğrudan form üzerinde çağrılabilir. ```php protected function createComponentSignInForm(): Form @@ -225,10 +236,10 @@ public function validateSignInForm(Form $form, \stdClass $data): void ``` -İşleme Hataları .[#toc-processing-errors] -========================================= +İşleme Sırasındaki Hatalar +========================== -Çoğu durumda, geçerli bir formu işlerken bir hata keşfederiz, örneğin veritabanına yeni bir girdi yazdığımızda ve yinelenen bir anahtarla karşılaştığımızda. Bu durumda, `addError()` yöntemini kullanarak hatayı forma geri iletiriz. Bu, belirli bir öğe üzerinde ya da doğrudan form üzerinde çağrılabilir: +Birçok durumda, hatayı ancak geçerli formu işlerken, örneğin veritabanına yeni bir öğe yazarken ve yinelenen anahtarlarla karşılaştığımızda öğreniriz. Bu durumda, hatayı tekrar `addError()` metoduyla forma iletiriz. Bu, belirli bir eleman üzerinde veya doğrudan form üzerinde çağrılabilir: ```php try { @@ -238,72 +249,71 @@ try { } catch (Nette\Security\AuthenticationException $e) { if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) { - $form->addError('Invalid password.'); + $form->addError('Geçersiz şifre.'); } } ``` -Mümkünse, hatayı doğrudan form öğesine eklemenizi öneririz, çünkü varsayılan oluşturucu kullanılırken yanında görünecektir. +Mümkünse, varsayılan oluşturucuyu kullanırken yanında görüntüleneceği için hatayı doğrudan form elemanına eklemenizi öneririz. ```php -$form['date']->addError('Sorry, this date is already taken.'); +$form['date']->addError('Üzgünüz, ancak bu tarih zaten alınmış.'); ``` -Bir forma veya öğeye birden fazla hata mesajı iletmek için `addError()` adresini tekrar tekrar çağırabilirsiniz. Bunları `getErrors()` ile alırsınız. +Forma veya elemana birden fazla hata mesajı iletmek için `addError()`'ı tekrar tekrar çağırabilirsiniz. Bunları `getErrors()` kullanarak alırsınız. -`$form->getErrors()` adresinin, yalnızca doğrudan forma değil, doğrudan tek tek öğelere iletilenler de dahil olmak üzere tüm hata mesajlarının bir özetini döndürdüğünü unutmayın. Yalnızca forma iletilen hata mesajları `$form->getOwnErrors()` aracılığıyla alınır. +Dikkat, `$form->getErrors()` tüm hata mesajlarının bir özetini döndürür, doğrudan tek tek elemanlara iletilenler de dahil olmak üzere, yalnızca doğrudan forma iletilenleri değil. Yalnızca forma iletilen hata mesajlarını `$form->getOwnErrors()` aracılığıyla alırsınız. -Girdi Değerlerini Değiştirme .[#toc-modifying-input-values] -=========================================================== +Girişi Değiştirme +================= -`addFilter()` yöntemini kullanarak, kullanıcı tarafından girilen değeri değiştirebiliriz. Bu örnekte, posta kodundaki boşlukları tolere edeceğiz ve kaldıracağız: +`addFilter()` metodunu kullanarak kullanıcı tarafından girilen değeri değiştirebiliriz. Bu örnekte, posta kodlarındaki boşlukları tolere edip kaldıracağız: ```php -$form->addText('zip', 'Posta kodu:') +$form->addText('zip', 'Posta Kodu:') ->addFilter(function ($value) { - return str_replace(' ', '', $value); // posta kodundan boşlukları kaldır + return str_replace(' ', '', $value); // posta kodundaki boşlukları kaldıracağız }) - ->addRule($form::Pattern, 'The postal code is not five digits', '\d{5}'); + ->addRule($form::Pattern, 'Posta kodu beş basamaklı biçimde değil', '\d{5}'); ``` -Filtre, doğrulama kuralları ve koşulları arasında yer alır ve bu nedenle yöntemlerin sırasına bağlıdır; yani filtre ve kural, `addFilter()` ve `addRule()` yöntemlerinin sırası ile aynı sırada çağrılır. +Filtre, doğrulama kuralları ve koşulları arasına eklenir ve bu nedenle metotların sırası önemlidir, yani filtre ve kural, `addFilter()` ve `addRule()` metotlarının sırasıyla çağrılır. -JavaScript Doğrulama .[#toc-javascript-validation] -================================================== +JavaScript Doğrulaması +====================== -Doğrulama kuralları ve koşullarının dili güçlüdür. Tüm yapılar JavaScript'te hem sunucu tarafında hem de istemci tarafında çalışsa da. Kurallar HTML özelliklerinde `data-nette-rules` JSON olarak aktarılır. -Doğrulamanın kendisi, tüm formun `submit` olaylarını bağlayan, tüm girdileri yineleyen ve ilgili doğrulamaları çalıştıran başka bir komut dosyası tarafından gerçekleştirilir. +Koşulları ve kuralları formüle etme dili çok güçlüdür. Tüm yapılar hem sunucu tarafında hem de JavaScript tarafında çalışır. JSON olarak `data-nette-rules` HTML niteliklerinde iletilirler. Doğrulamanın kendisi daha sonra formun `submit` olayını yakalayan, tek tek elemanları gözden geçiren ve ilgili doğrulamayı gerçekleştiren betik tarafından yapılır. -Bu komut dosyası, birkaç olası kaynaktan temin edilebilen `netteForms.js` adresindedir: +Bu betik `netteForms.js`'dir ve birden fazla olası kaynaktan edinilebilir: -Komut dosyasını CDN'den doğrudan HTML sayfasına gömebilirsiniz: +Betiği doğrudan CDN'den HTML sayfasına ekleyebilirsiniz: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Ya da yerel olarak projenin ortak klasörüne kopyalayın (örneğin `vendor/nette/forms/src/assets/netteForms.min.js` adresinden): +Veya yerel olarak projenin genel klasörüne kopyalayın (ör. `vendor/nette/forms/src/assets/netteForms.min.js`'den): ```latte <script src="/path/to/netteForms.min.js"></script> ``` -Veya [npm |https://www.npmjs.com/package/nette-forms] aracılığıyla yükleyin: +Veya [npm|https://www.npmjs.com/package/nette-forms] aracılığıyla yükleyin: ```shell npm install nette-forms ``` -Ve sonra yükleyin ve çalıştırın: +Ve ardından yükleyip çalıştırın: ```js import netteForms from 'nette-forms'; netteForms.initOnLoad(); ``` -Alternatif olarak, doğrudan `vendor` klasöründen de yükleyebilirsiniz: +Alternatif olarak, doğrudan `vendor` klasöründen yükleyebilirsiniz: ```js import netteForms from '../path/to/vendor/nette/forms/src/assets/netteForms.js'; @@ -311,10 +321,10 @@ netteForms.initOnLoad(); ``` -Dinamik JavaScript .[#toc-dynamic-javascript] -============================================= +Dinamik JavaScript +================== -Adres alanlarını yalnızca kullanıcı ürünleri postayla göndermeyi seçerse mi göstermek istiyorsunuz? Hiç sorun değil. Anahtar bir çift yöntemdir `addCondition()` & `toggle()`: +Adres girme alanlarını yalnızca kullanıcı malların postayla gönderilmesini seçtiğinde mi görüntülemek istiyorsunuz? Sorun değil. Anahtar, `addCondition()` & `toggle()` metot çiftidir: ```php $form->addCheckbox('send_it') @@ -322,25 +332,25 @@ $form->addCheckbox('send_it') ->toggle('#address-container'); ``` -Bu kod, koşul karşılandığında, yani onay kutusu işaretlendiğinde, `#address-container` HTML öğesinin görünür olacağını söyler. Ve tam tersi. Böylece, alıcının adresini içeren form öğelerini bu kimliğe sahip bir kapsayıcıya yerleştiririz ve onay kutusu tıklandığında gizlenir veya gösterilir. Bu işlem `netteForms.js` komut dosyası tarafından gerçekleştirilir. +Bu kod, koşul karşılandığında, yani onay kutusu işaretlendiğinde, `#address-container` HTML öğesinin görünür olacağını söyler. Ve tersi. Alıcının adresini içeren form elemanlarını bu kimliğe sahip bir konteynere yerleştiririz ve onay kutusuna tıklandığında gizlenir veya gösterilirler. Bu, `netteForms.js` betiği tarafından sağlanır. -Herhangi bir seçici `toggle()` yöntemine argüman olarak aktarılabilir. Tarihsel nedenlerden dolayı, başka hiçbir özel karakter içermeyen alfanümerik bir dize, önünde `#` character. The second optional parameter allows us to reverse the behavior, i.e. if we used `toggle('#address-container', false)` olduğu gibi bir öğe kimliği olarak kabul edilir, öğe yalnızca onay kutusu işaretlenmemişse görüntülenir. +`toggle()` metodunun argümanı olarak herhangi bir seçici iletilebilir. Tarihsel nedenlerden dolayı, başka özel karakterler içermeyen alfanümerik bir dize, öğenin kimliği olarak anlaşılır, yani önünde `#` karakteri varmış gibi. İkinci isteğe bağlı parametre, davranışı tersine çevirmeyi sağlar, yani `toggle('#address-container', false)` kullanırsak, öğe yalnızca onay kutusu işaretli olmadığında görüntülenir. -Varsayılan JavaScript uygulaması, öğeler için `hidden` özelliğini değiştirir. Ancak, örneğin bir animasyon ekleyerek davranışı kolayca değiştirebiliriz. JavaScript'teki `Nette.toggle` yöntemini özel bir çözümle geçersiz kılmanız yeterlidir: +JavaScript'teki varsayılan uygulama, öğelerin `hidden` özelliğini değiştirir. Ancak, davranışı kolayca değiştirebiliriz, örneğin bir animasyon ekleyebiliriz. JavaScript'te `Nette.toggle` metodunu kendi çözümümüzle geçersiz kılmamız yeterlidir: ```js Nette.toggle = (selector, visible, srcElement, event) => { document.querySelectorAll(selector).forEach((el) => { - // hide or show 'el' according to the value of 'visible' + // 'el' öğesini 'visible' değerine göre gizleyeceğiz veya göstereceğiz }); }; ``` -Doğrulamayı Devre Dışı Bırakma .[#toc-disabling-validation] -=========================================================== +Doğrulamayı Devre Dışı Bırakma +============================== -Bazı durumlarda doğrulamayı devre dışı bırakmanız gerekir. Bir gönderme düğmesinin gönderme işleminden sonra doğrulama yapmaması gerekiyorsa (örneğin *İptal* veya *Önizleme* düğmesi), `$submit->setValidationScope([])` adresini çağırarak doğrulamayı devre dışı bırakabilirsiniz. Ayrıca, doğrulanacak öğeleri belirterek formu kısmen doğrulayabilirsiniz. +Bazen doğrulamayı devre dışı bırakmak gerekebilir. Gönderme düğmesine basmak doğrulamayı gerçekleştirmemesi gerekiyorsa ( *İptal* veya *Önizleme* düğmeleri için uygundur), `$submit->setValidationScope([])` metoduyla devre dışı bırakırız. Yalnızca kısmi doğrulama yapması gerekiyorsa, hangi alanların veya form konteynerlerinin doğrulanacağını belirleyebiliriz. ```php $form->addText('name') @@ -354,13 +364,13 @@ $details->addInteger('age2') $form->addSubmit('send1'); // Tüm formu doğrular $form->addSubmit('send2') - ->setValidationScope([]); // Hiçbir şeyi doğrulamaz + ->setValidationScope([]); // Hiç doğrulama yapmaz $form->addSubmit('send3') - ->setValidationScope([$form['name']]); // Yalnızca 'name' alanını doğrular + ->setValidationScope([$form['name']]); // Yalnızca name öğesini doğrular $form->addSubmit('send4') - ->setValidationScope([$form['details']['age']]); // Yalnızca 'age' alanını doğrular + ->setValidationScope([$form['details']['age']]); // Yalnızca age öğesini doğrular $form->addSubmit('send5') - ->setValidationScope([$form['details']]); // Validates 'details' container + ->setValidationScope([$form['details']]); // details konteynerini doğrular ``` -[Form üzerindeki onValidate |#Event onValidate] olayı her zaman çağrılır ve `setValidationScope` adresinden etkilenmez. `onValidate` kapsayıcı üzerindeki olay yalnızca bu kapsayıcı kısmi doğrulama için belirtildiğinde çağrılır. +`setValidationScope`, her zaman çağrılacak olan formdaki [##onValidate-olayı] etkilemez. Konteynerdeki `onValidate` olayı yalnızca bu konteyner kısmi doğrulama için işaretlenmişse tetiklenir. diff --git a/forms/uk/@home.texy b/forms/uk/@home.texy index f4115252ab..39bfd4af56 100644 --- a/forms/uk/@home.texy +++ b/forms/uk/@home.texy @@ -1,31 +1,31 @@ -Форми -***** +Nette Forms +*********** <div class=perex> -Бібліотека Nette Forms зробила революцію у створенні веб-форм. Усе, що вам потрібно було зробити, це написати кілька чітких рядків коду, і у вас була форма, включно з рендерингом, JavaScript і перевіркою на сервері, плюс найкраща в галузі безпека. Давайте подивимося, як +Nette Forms здійснили революцію у створенні веб-форм. Раптом стало достатньо написати кілька зрозумілих рядків коду, і ви мали готову форму, включаючи відображення, валідацію на стороні JavaScript та сервера, а також відмінно захищену. Ми покажемо, як -- створювати дружні форми -- перевіряти надіслані дані +- створювати зручні форми +- валідувати надіслані дані - відображати елементи точно за потребою </div> -За допомогою Nette Forms ви можете скоротити рутинні завдання, як-от написання валідації (як на стороні сервера, так і на стороні клієнта), а також мінімізувати ймовірність помилок і проблем безпеки. +Використовуючи Nette Forms, ви уникнете цілої низки рутинних завдань, таких як написання валідації (до того ж подвійної, на стороні сервера та клієнта), мінімізуєте ймовірність виникнення помилок та дірок у безпеці. -Форми можна використовувати як у складі застосунку Nette (тобто в презентерах), так і окремо. Оскільки в обох випадках використання трохи відрізняється, ми підготували для вас окремі інструкції: +Форми можна використовувати або як частину Nette Application (тобто в презентерах), або повністю самостійно. Оскільки в обох випадках використання трохи відрізняється, ми підготували для вас два посібники: <div class="wiki-buttons"> <div> "Форми в презентерах .[wiki-button]":in-presenter </div> -<div> "Автономні форми .[wiki-button]":standalone </div> +<div> "Форми самостійно .[wiki-button]":standalone </div> </div> Встановлення ------------ -Завантажте та встановіть пакет за допомогою [Composer |best-practices:composer]: +Бібліотеку можна завантажити та встановити за допомогою інструменту [Composer|best-practices:composer]: ```shell composer require nette/forms diff --git a/forms/uk/@left-menu.texy b/forms/uk/@left-menu.texy index a0c63c8157..b1497f49e6 100644 --- a/forms/uk/@left-menu.texy +++ b/forms/uk/@left-menu.texy @@ -1,14 +1,14 @@ -Форми -***** -- [Огляд |@home] -- [Форми в презентерах |in-presenter] -- [Автономні форми |standalone] +Nette Forms +*********** +- [Вступ |@home] +- [Форми в презентерах|in-presenter] +- [Форми самостійно|standalone] - [Елементи форм |controls] - [Валідація |validation] -- [Рендеринг |rendering] -- [Налаштування |configuration] +- [Відображення |rendering] +- [Конфігурація |configuration] -Подальше читання -**************** -- [Кращі практики |best-practices:] +Додаткове читання +***************** +- [Посібники та практики |best-practices:] diff --git a/forms/uk/@meta.texy b/forms/uk/@meta.texy new file mode 100644 index 0000000000..96e2d9752a --- /dev/null +++ b/forms/uk/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документація Nette}} diff --git a/forms/uk/configuration.texy b/forms/uk/configuration.texy index d3bdb181e0..c2e8ab9954 100644 --- a/forms/uk/configuration.texy +++ b/forms/uk/configuration.texy @@ -1,8 +1,8 @@ -Налаштування форм +Конфігурація форм ***************** .[perex] -Ви можете змінити стандартні [повідомлення про помилки форми |validation] в конфігурації. +У конфігурації можна змінити стандартні [повідомлення про помилки форм |validation]. ```neon forms: @@ -30,32 +30,32 @@ forms: Nette\Forms\Controls\CsrfProtection::Protection: 'Your session has expired. Please return to the home page and try again.' ``` -Ось російський переклад: +Ось український переклад: ```neon forms: messages: - Equal: 'Пожалуйста, введите %s.' - NotEqual: 'Это значение не должно быть %s.' - Filled: 'Это поле обязательно для заполнения.' - Blank: 'Это поле должно быть пустым.' - MinLength: 'Пожалуйста, введите не менее %d символов.' - MaxLength: 'Пожалуйста, введите не более %d символов.' - Length: 'Пожалуйста, введите значение длиной от %d до %d символов.' - Email: 'Пожалуйста, введите действительный адрес электронной почты.' - URL: 'Пожалуйста, введите действительный URL.' - Integer: 'Пожалуйста, введите действительное целое число.' - Float: 'Пожалуйста, введите действительное вещественное число.' - Min: 'Пожалуйста, введите значение, большее или равное %d.' - Max: 'Пожалуйста, введите значение, меньшее или равное %d.' - Range: 'Пожалуйста, введите значение между %d и %d.' - MaxFileSize: 'Размер загружаемого файла не может превышать %d байт.' - MaxPostSize: 'Загруженные данные превышают лимит в %d байт.' - MimeType: 'Загруженный файл не соответствует ожидаемому формату.' - Image: 'Загружаемый файл должен быть изображением в формате JPEG, GIF, PNG или WebP.' - Nette\Forms\Controls\SelectBox::Valid: 'Пожалуйста, выберите действительный вариант.' - Nette\Forms\Controls\UploadControl::Valid: 'Во время загрузки файла произошла ошибка.' - Nette\Forms\Controls\CsrfProtection::Protection: 'Ваш сеанс истек. Пожалуйста, вернитесь на главную страницу и повторите попытку.' + Equal: 'Введіть %s.' + NotEqual: 'Це значення не повинно бути %s.' + Filled: 'Це поле є обов’язковим.' + Blank: 'Це поле повинно бути порожнім.' + MinLength: 'Будь ласка, введіть щонайменше %d символів.' + MaxLength: 'Будь ласка, введіть не більше %d символів.' + Length: 'Будь ласка, введіть значення довжиною від %d до %d символів.' + Email: 'Введіть дійсну адресу електронної пошти.' + URL: 'Будь ласка, введіть дійсну URL-адресу.' + Integer: 'Введіть дійсне ціле число.' + Float: 'Введіть дійсне число.' + Min: 'Будь ласка, введіть значення, більше або рівне %d.' + Max: 'Будь ласка, введіть значення, менше або рівне %d.' + Range: 'Введіть значення між %d та %d.' + MaxFileSize: 'Розмір завантаженого файлу може бути не більше %d байт.' + MaxPostSize: 'Завантажені дані перевищують ліміт %d байт.' + MimeType: 'Завантажений файл не у очікуваному форматі.' + Image: 'Завантажений файл має бути зображенням у форматі JPEG, GIF, PNG, WebP або AVIF.' + Nette\Forms\Controls\SelectBox::Valid: 'Будь ласка, виберіть дійсний варіант.' + Nette\Forms\Controls\UploadControl::Valid: 'Під час завантаження файлу сталася помилка.' + Nette\Forms\Controls\CsrfProtection::Protection: 'Ваша сесія закінчилася. Поверніться на головну сторінку та спробуйте ще раз.' ``` -Якщо ви не використовуєте весь фреймворк і, отже, навіть конфігураційні файли, ви можете змінити повідомлення про помилки за замовчуванням безпосередньо в полі `Nette\Forms\Validator::$messages`. +Якщо ви не використовуєте весь фреймворк, а отже, і конфігураційні файли, ви можете змінити стандартні повідомлення про помилки безпосередньо в масиві `Nette\Forms\Validator::$messages`. diff --git a/forms/uk/controls.texy b/forms/uk/controls.texy index 26af421a00..afa847c3b1 100644 --- a/forms/uk/controls.texy +++ b/forms/uk/controls.texy @@ -1,34 +1,31 @@ -Елементи керування форм -*********************** +Елементи форми +************** .[perex] -Огляд вбудованих елементів керування формою. +Огляд стандартних елементів форми. -addText(string|int $name, $label=null): TextInput .[method] -=========================================================== +addText(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +================================================================================================== -Додає однорядкове текстове поле (клас [TextInput |api:Nette\Forms\Controls\TextInput]). Якщо користувач не заповнив поле, повертається порожній рядок `''`, або використовуйте `setNullable()` для повернення `null`. +Додає однорядкове текстове поле (клас [TextInput |api:Nette\Forms\Controls\TextInput]). Якщо користувач не заповнює поле, повертається порожній рядок `''`, або за допомогою `setNullable()` можна вказати, щоб повертався `null`. ```php -$form->addText('name', 'Имя:') +$form->addText('name', 'Ім\'я:') ->setRequired() ->setNullable(); ``` -Цей метод автоматично перевіряє UTF-8, обрізає ліві та праві пропуски й видаляє перенесення рядків, які можуть бути надіслані зловмисником. +Автоматично перевіряє UTF-8, обрізає пробіли зліва та справа та видаляє переноси рядків, які може надіслати зловмисник. -Максимальна довжина може бути обмежена за допомогою `setMaxLength()`. Функція [addFilter() |validation#Modifying-Input-Values] дозволяє змінити введене користувачем значення. +Максимальну довжину можна обмежити за допомогою `setMaxLength()`. Змінити введене користувачем значення дозволяє [addFilter() |validation#Зміна вводу]. -Використовуйте `setHtmlType()` для зміни [типу |https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types] елемента введення на `search`, `tel`, `url`, `range`, `date`, `datetime-local`, `month`, `time`, `week`, `color`. Замість типів `number` і `email` ми рекомендуємо використовувати [addInteger |#addInteger] і [addEmail |#addEmail], які забезпечують валідацію на стороні сервера. +За допомогою `setHtmlType()` можна змінити візуальний характер текстового поля на типи, такі як `search`, `tel` або `url`, див. [специфікацію |https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types]. Пам'ятайте, що зміна типу є лише візуальною і не замінює функцію валідації. Для типу `url` доцільно додати специфічне правило валідації [URL |validation#Текстові поля]. -```php -$form->addText('color', 'Выберите цвет:') - ->setHtmlType('color') - ->addRule($form::Pattern, 'недопустимое значение', '[0-9a-f]{6}'); -``` +.[note] +Для інших типів введення, таких як `number`, `range`, `email`, `date`, `datetime-local`, `time` та `color`, використовуйте спеціалізовані методи, такі як [#addInteger], [#addFloat], [#addEmail] [#addDate], [#addTime], [#addDateTime] та [#addColor], які забезпечують серверну валідацію. Типи `month` та `week` поки що не повністю підтримуються всіма браузерами. -Для елемента може бути встановлено так зване "порожнє значення", яке є чимось на зразок значення за замовчуванням, але якщо користувач не перезаписує його, повертає порожній рядок або `null`. +Елементу можна встановити так зване empty-value, щось на зразок значення за замовчуванням, але якщо користувач його не змінить, елемент поверне порожній рядок або `null`. ```php $form->addText('phone', 'Телефон:') @@ -40,214 +37,310 @@ $form->addText('phone', 'Телефон:') addTextArea(string|int $name, $label=null): TextArea .[method] ============================================================== -Додає багаторядкове текстове поле (клас [TextArea |api:Nette\Forms\Controls\TextArea]). Якщо користувач не заповнив поле, повертається порожній рядок `''`, або використовуйте `setNullable()` для повернення `null`. +Додає поле для введення багаторядкового тексту (клас [TextArea |api:Nette\Forms\Controls\TextArea]). Якщо користувач не заповнює поле, повертається порожній рядок `''`, або за допомогою `setNullable()` можна вказати, щоб повертався `null`. ```php -$form->addTextArea('note', 'Примечание:') - ->addRule($form::MaxLength, 'Ваша заметка слишком длинная', 10000); +$form->addTextArea('note', 'Примітка:') + ->addRule($form::MaxLength, 'Примітка занадто довга', 10000); ``` -Автоматично перевіряє UTF-8 і нормалізує переноси рядків до `\n`. На відміну від однорядкового поля введення, у ньому не обрізаються пробільні символи. +Автоматично перевіряє UTF-8 і нормалізує роздільники рядків до `\n`. На відміну від однорядкового поля введення, обрізання пробілів не відбувається. -Максимальна довжина може бути обмежена за допомогою `setMaxLength()`. Функція [addFilter() |validation#Modifying-Input-Values] дозволяє змінити введене користувачем значення. Ви можете встановити так зване "порожнє значення", використовуючи `setEmptyValue()`. +Максимальну довжину можна обмежити за допомогою `setMaxLength()`. Змінити введене користувачем значення дозволяє [addFilter() |validation#Зміна вводу]. Можна встановити так зване empty-value за допомогою `setEmptyValue()`. addInteger(string|int $name, $label=null): TextInput .[method] ============================================================== -Додає поле введення для цілого числа (клас [TextInput |api:Nette\Forms\Controls\TextInput]). Повертає або ціле число, або `null`, якщо користувач нічого не ввів. +Додає поле для введення цілого числа (клас [TextInput |api:Nette\Forms\Controls\TextInput]). Повертає або ціле число (integer), або `null`, якщо користувач нічого не ввів. + +```php +$form->addInteger('year', 'Рік:') + ->addRule($form::Range, 'Рік має бути в діапазоні від %d до %d.', [1900, 2023]); +``` + +Елемент відображається як `<input type="number">`. За допомогою методу `setHtmlType()` можна змінити тип на `range` для відображення у вигляді повзунка, або на `text`, якщо ви віддаєте перевагу стандартному текстовому полю без спеціальної поведінки типу `number`. + + +addFloat(string|int $name, $label=null): TextInput .[method]{data-version:3.1.12} +================================================================================= + +Додає поле для введення десяткового числа (клас [TextInput |api:Nette\Forms\Controls\TextInput]). Повертає або float, або `null`, якщо користувач нічого не ввів. ```php -$form->addInteger('level', 'Уровень:') +$form->addFloat('level', 'Рівень:') ->setDefaultValue(0) - ->addRule($form::Range, 'Уровень должен быть между %d и %d.', [0, 100]); + ->addRule($form::Range, 'Рівень має бути в діапазоні від %d до %d.', [0, 100]); ``` +Елемент відображається як `<input type="number">`. За допомогою методу `setHtmlType()` можна змінити тип на `range` для відображення у вигляді повзунка, або на `text`, якщо ви віддаєте перевагу стандартному текстовому полю без спеціальної поведінки типу `number`. + +Nette та браузер Chrome приймають як роздільник десяткових знаків як кому, так і крапку. Щоб ця функціональність була доступна і у Firefox, рекомендується встановити атрибут `lang` або для даного елемента, або для всієї сторінки, наприклад `<html lang="uk">`. + -addEmail(string|int $name, $label=null): TextInput .[method] -============================================================ +addEmail(string|int $name, $label=null, int $maxLength=255): TextInput .[method] +================================================================================ -Додає поле адреси електронної пошти з перевіркою достовірності (клас [TextInput |api:Nette\Forms\Controls\TextInput]). Якщо користувач не заповнив поле, повертається порожній рядок `''`, або використовуйте `setNullable()` для повернення `null`. +Додає поле для введення адреси електронної пошти (клас [TextInput |api:Nette\Forms\Controls\TextInput]). Якщо користувач не заповнює поле, повертається порожній рядок `''`, або за допомогою `setNullable()` можна вказати, щоб повертався `null`. ```php -$form->addEmail('email', 'Имейл:'); +$form->addEmail('email', 'E-mail:'); ``` -Перевіряє, що значення є дійсною адресою електронної пошти. Він не перевіряє, чи існує домен насправді, перевіряється тільки синтаксис. Автоматично перевіряє UTF-8, обрізає ліві та праві пробіли. +Перевіряє, чи є значення дійсною адресою електронної пошти. Не перевіряється, чи дійсно існує домен, перевіряється лише синтаксис. Автоматично перевіряє UTF-8, обрізає пробіли зліва та справа. -Максимальна довжина може бути обмежена за допомогою `setMaxLength()`. Функція [addFilter() |validation#Modifying-Input-Values] дозволяє змінити введене користувачем значення. Ви можете встановити так зване "порожнє значення", використовуючи `setEmptyValue()`. +Максимальну довжину можна обмежити за допомогою `setMaxLength()`. Змінити введене користувачем значення дозволяє [addFilter() |validation#Зміна вводу]. Можна встановити так зване empty-value за допомогою `setEmptyValue()`. -addPassword(string|int $name, $label=null): TextInput .[method] -=============================================================== +addPassword(string|int $name, $label=null, ?int $cols=null, ?int $maxLength=null): TextInput .[method] +====================================================================================================== -Додає поле пароля (клас [TextInput |api:Nette\Forms\Controls\TextInput]). +Додає поле для введення пароля (клас [TextInput |api:Nette\Forms\Controls\TextInput]). ```php $form->addPassword('password', 'Пароль:') ->setRequired() - ->addRule($form::MinLength, 'Пароль должен быть длиной не менее %d символов', 8) - ->addRule($form::Pattern, 'Пароль должен содержать цифры', '.*[0-9].*'); + ->addRule($form::MinLength, 'Пароль повинен містити щонайменше %d символів', 8) + ->addRule($form::Pattern, 'Повинен містити цифру', '.*[0-9].*'); ``` -Під час повторного надсилання форми введення буде порожнім. Він автоматично перевіряє UTF-8, обрізає ліві та праві пропуски й видаляє перенесення рядків, які можуть бути надіслані зловмисником. +При повторному відображенні форми поле буде порожнім. Автоматично перевіряє UTF-8, обрізає пробіли зліва та справа та видаляє переноси рядків, які може надіслати зловмисник. addCheckbox(string|int $name, $caption=null): Checkbox .[method] ================================================================ -Додає прапорець (клас [Checkbox |api:Nette\Forms\Controls\Checkbox]). Поле повертає або `true`, або `false`, залежно від того, чи перевірено воно. +Додає прапорець (клас [Checkbox |api:Nette\Forms\Controls\Checkbox]). Повертає значення `true` або `false`, залежно від того, чи встановлено прапорець. ```php -$form->addCheckbox('agree', 'Я согласен с условиями') - ->setRequired('Вы должны согласиться с нашими условиями'); +$form->addCheckbox('agree', 'Згоден з умовами') + ->setRequired('Необхідно погодитися з умовами'); ``` -addCheckboxList(string|int $name, $label=null, array $items=null): CheckboxList .[method] -========================================================================================= +addCheckboxList(string|int $name, $label=null, ?array $items=null): CheckboxList .[method] +========================================================================================== -Додає список прапорців для вибору кількох елементів (клас [CheckboxList |api:Nette\Forms\Controls\CheckboxList]). Повертає масив ключів обраних елементів. Метод `getSelectedItems()` повертає значення замість ключів. +Додає прапорці для вибору кількох елементів (клас [CheckboxList |api:Nette\Forms\Controls\CheckboxList]). Повертає масив ключів вибраних елементів. Метод `getSelectedItems()` повертає значення замість ключів. ```php -$form->addCheckboxList('colors', 'Цвета:', [ - 'r' => 'red', - 'g' => 'green', - 'b' => 'blue', +$form->addCheckboxList('colors', 'Кольори:', [ + 'r' => 'червоний', + 'g' => 'зелений', + 'b' => 'синій', ]); ``` -Ми передаємо масив елементів як третій параметр або методом `setItems()`. +Масив пропонованих елементів передаємо як третій параметр або методом `setItems()`. -Ви можете використовувати `setDisabled(['r', 'g'])` для відключення окремих елементів. +За допомогою `setDisabled(['r', 'g'])` можна деактивувати окремі елементи. -Елемент автоматично перевіряє, що не було підробки і що вибрані елементи дійсно є одними із запропонованих і не були відключені. Метод `getRawValue()` може бути використаний для отримання відправлених елементів без цієї важливої перевірки. +Елемент автоматично перевіряє, що не відбулося підробки і що вибрані елементи дійсно є одними з пропонованих і не були деактивовані. Методом `getRawValue()` можна отримати надіслані елементи без цієї важливої перевірки. -Під час встановлення значень за замовчуванням також перевіряється, що вони є одним із запропонованих елементів, інакше виникає виняток. Цю перевірку можна вимкнути за допомогою `checkDefaultValue(false)`. +При встановленні вибраних елементів за замовчуванням також перевіряє, що вони є одними з пропонованих, інакше викидає виняток. Цю перевірку можна вимкнути за допомогою `checkDefaultValue(false)`. +Якщо ви надсилаєте форму методом `GET`, ви можете вибрати компактніший спосіб передачі даних, який економить розмір рядка запиту. Він активується встановленням HTML-атрибута форми: + +```php +$form->setHtmlAttribute('data-nette-compact'); +``` -addRadioList(string|int $name, $label=null, array $items=null): RadioList .[method] -=================================================================================== -Додає радіокнопки (клас [RadioList |api:Nette\Forms\Controls\RadioList]). Повертає ключ обраного елемента або `null`, якщо користувач нічого не вибрав. Метод `getSelectedItem()` повертає значення замість ключа. +addRadioList(string|int $name, $label=null, ?array $items=null): RadioList .[method] +==================================================================================== + +Додає перемикачі (клас [RadioList |api:Nette\Forms\Controls\RadioList]). Повертає ключ вибраного елемента або `null`, якщо користувач нічого не вибрав. Метод `getSelectedItem()` повертає значення замість ключа. ```php $sex = [ - 'm' => 'male', - 'f' => 'female', + 'm' => 'чоловік', + 'f' => 'жінка', ]; -$form->addRadioList('gender', 'Пол:', $sex); +$form->addRadioList('gender', 'Стать:', $sex); ``` -Ми передаємо масив елементів як третій параметр або методом `setItems()`. +Масив пропонованих елементів передаємо як третій параметр або методом `setItems()`. -Ви можете використовувати `setDisabled(['m'])` для відключення окремих елементів. +За допомогою `setDisabled(['m', 'f'])` можна деактивувати окремі елементи. -Елемент автоматично перевіряє, що не було підробки і що обраний елемент дійсно є одним із запропонованих і не був відключений. Метод `getRawValue()` може бути використаний для отримання відправленого елемента без цієї важливої перевірки. +Елемент автоматично перевіряє, що не відбулося підробки і що вибраний елемент дійсно є одним із пропонованих і не був деактивований. Методом `getRawValue()` можна отримати надісланий елемент без цієї важливої перевірки. -Під час встановлення значення за замовчуванням перевіряється, що воно є одним із запропонованих елементів, інакше виникає виняток. Цю перевірку можна вимкнути за допомогою `checkDefaultValue(false)`. +При встановленні вибраного елемента за замовчуванням також перевіряє, що він є одним із пропонованих, інакше викидає виняток. Цю перевірку можна вимкнути за допомогою `checkDefaultValue(false)`. -addSelect(string|int $name, $label=null, array $items=null): SelectBox .[method] -================================================================================ +addSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): SelectBox .[method] +================================================================================================== -Додає поле вибору (клас [SelectBox |api:Nette\Forms\Controls\SelectBox]). Повертає ключ обраного елемента або `null`, якщо користувач нічого не вибрав. Метод `getSelectedItem()` повертає значення замість ключа. +Додає select box (клас [SelectBox |api:Nette\Forms\Controls\SelectBox]). Повертає ключ вибраного елемента або `null`, якщо користувач нічого не вибрав. Метод `getSelectedItem()` повертає значення замість ключа. ```php $countries = [ - 'CZ' => 'Чешская республика', - 'SK' => 'Словакия', - 'GB' => 'Великобритания', + 'CZ' => 'Чеська Республіка', + 'SK' => 'Словаччина', + 'GB' => 'Велика Британія', ]; -$form->addSelect('country', 'Страна:', $countries) +$form->addSelect('country', 'Країна:', $countries) ->setDefaultValue('SK'); ``` -Ми передаємо масив елементів як третій параметр або методом `setItems()`. Масив елементів також може бути двовимірним: +Масив пропонованих елементів передаємо як третій параметр або методом `setItems()`. Елементи також можуть бути двовимірним масивом: ```php $countries = [ - 'Europe' => [ - 'CZ' => 'Чешская республика', - 'SK' => 'Словакия', - 'GB' => 'Великобритания', + 'Європа' => [ + 'CZ' => 'Чеська Республіка', + 'SK' => 'Словаччина', + 'GB' => 'Велика Британія', ], 'CA' => 'Канада', 'US' => 'США', - '?' => 'другая', + '?' => 'інша', ]; ``` -У блоках вибору перший елемент часто має особливе значення, він служить закликом до дії. Використовуйте метод `setPrompt()` для додавання такого запису. +У select box-ах часто перший елемент має особливе значення, служить як заклик до дії. Для додавання такого елемента служить метод `setPrompt()`. ```php -$form->addSelect('country', 'Страна:', $countries) - ->setPrompt('Выберите страну'); +$form->addSelect('country', 'Країна:', $countries) + ->setPrompt('Виберіть країну'); ``` -Ви можете використовувати `setDisabled(['CZ', 'SK'])` для вимкнення окремих елементів. +За допомогою `setDisabled(['CZ', 'SK'])` можна деактивувати окремі елементи. -Елемент автоматично перевіряє, що не було підробки і що обраний елемент дійсно є одним із запропонованих і не був відключений. Метод `getRawValue()` може бути використаний для отримання відправленого елемента без цієї важливої перевірки. +Елемент автоматично перевіряє, що не відбулося підробки і що вибраний елемент дійсно є одним із пропонованих і не був деактивований. Методом `getRawValue()` можна отримати надісланий елемент без цієї важливої перевірки. -Під час встановлення значення за замовчуванням перевіряється, що воно є одним із запропонованих елементів, інакше виникає виняток. Цю перевірку можна вимкнути за допомогою `checkDefaultValue(false)`. +При встановленні вибраного елемента за замовчуванням також перевіряє, що він є одним із пропонованих, інакше викидає виняток. Цю перевірку можна вимкнути за допомогою `checkDefaultValue(false)`. -addMultiSelect(string|int $name, $label=null, array $items=null): MultiSelectBox .[method] -========================================================================================== +addMultiSelect(string|int $name, $label=null, ?array $items=null, ?int $size=null): MultiSelectBox .[method] +============================================================================================================ -Додає вікно вибору кількох варіантів (клас [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox]). Повертає масив ключів обраних елементів. Метод `getSelectedItems()` повертає значення замість ключів. +Додає select box для вибору кількох елементів (клас [MultiSelectBox |api:Nette\Forms\Controls\MultiSelectBox]). Повертає масив ключів вибраних елементів. Метод `getSelectedItems()` повертає значення замість ключів. ```php -$form->addMultiSelect('countries', 'Страны:', $countries); +$form->addMultiSelect('countries', 'Країна:', $countries); ``` -Ми передаємо масив елементів як третій параметр або методом `setItems()`. Масив елементів також може бути двовимірним. +Масив пропонованих елементів передаємо як третій параметр або методом `setItems()`. Елементи також можуть бути двовимірним масивом. -Ви можете використовувати `setDisabled(['CZ', 'SK'])` для відключення окремих елементів. +За допомогою `setDisabled(['CZ', 'SK'])` можна деактивувати окремі елементи. -Елемент автоматично перевіряє, що не було підробки і що вибрані елементи дійсно є одними із запропонованих і не були відключені. Метод `getRawValue()` може бути використаний для отримання відправлених елементів без цієї важливої перевірки. +Елемент автоматично перевіряє, що не відбулося підробки і що вибрані елементи дійсно є одними з пропонованих і не були деактивовані. Методом `getRawValue()` можна отримати надіслані елементи без цієї важливої перевірки. -Під час встановлення значень за замовчуванням також перевіряється, що вони є одним із запропонованих елементів, інакше виникає виняток. Цю перевірку можна вимкнути за допомогою `checkDefaultValue(false)`. +При встановленні вибраних елементів за замовчуванням також перевіряє, що вони є одними з пропонованих, інакше викидає виняток. Цю перевірку можна вимкнути за допомогою `checkDefaultValue(false)`. addUpload(string|int $name, $label=null): UploadControl .[method] ================================================================= -Додає поле завантаження файлів (клас [UploadControl |api:Nette\Forms\Controls\UploadControl]). Повертає об'єкт [FileUpload |http:request#FileUpload], навіть якщо користувач не завантажив файл, що можна з'ясувати за допомогою методу `FileUpload::hasFile()`. +Додає поле для завантаження файлу (клас [UploadControl |api:Nette\Forms\Controls\UploadControl]). Повертає об'єкт [FileUpload |http:request#FileUpload] навіть у випадку, якщо користувач не надіслав жодного файлу, що можна перевірити методом `FileUpload::hasFile()`. ```php $form->addUpload('avatar', 'Аватар:') - ->addRule($form::Image, 'Аватар должен быть в формате JPEG, PNG, GIF или WebP') - ->addRule($form::MaxFileSize, 'Максимальный размер - 1 МБ', 1024 * 1024); + ->addRule($form::Image, 'Аватар має бути у форматі JPEG, PNG, GIF, WebP або AVIF.') + ->addRule($form::MaxFileSize, 'Максимальний розмір 1 МБ.', 1024 * 1024); ``` -Якщо файл завантажений неправильно, форма не була відправлена успішно і відображається помилка. Тобто немає необхідності перевіряти метод `FileUpload::isOk()`. +Якщо файл не вдалося коректно завантажити, форма не надсилається успішно і відображається помилка. Тобто при успішному надсиланні не потрібно перевіряти метод `FileUpload::isOk()`. -Не довіряйте оригінальному імені файлу, що повертається методом `FileUpload::getName()`, клієнт може надіслати шкідливе ім'я файлу з наміром зіпсувати або зламати ваш додаток. +Ніколи не довіряйте оригінальній назві файлу, повернутій методом `FileUpload::getName()`, клієнт міг надіслати шкідливу назву файлу з наміром пошкодити або зламати ваш застосунок. -Правила `MimeType` і `Image` визначають необхідний тип файлу або зображення за його сигнатурою. Цілісність усього файлу не перевіряється. Ви можете дізнатися, чи не пошкоджено зображення, наприклад, спробувавши [завантажити його |http:request#toImage]. +Правила `MimeType` та `Image` визначають потрібний тип на основі сигнатури файлу і не перевіряють його цілісність. Чи не пошкоджене зображення, можна з'ясувати, наприклад, спробувавши його [завантажити |http:request#toImage]. addMultiUpload(string|int $name, $label=null): UploadControl .[method] ====================================================================== -Додає поле завантаження кількох файлів (клас [UploadControl |api:Nette\Forms\Controls\UploadControl]). Повертає масив об'єктів [FileUpload |http:request#FileUpload]. Метод `FileUpload::hasFile()` поверне `true` для кожного з них. +Додає поле для одночасного завантаження кількох файлів (клас [UploadControl |api:Nette\Forms\Controls\UploadControl]). Повертає масив об'єктів [FileUpload |http:request#FileUpload]. Метод `FileUpload::hasFile()` для кожного з них повертатиме `true`. ```php -$form->addMultiUpload('files', 'Файлы:') - ->addRule($form::MaxLength, 'Может быть загружено не более %d файлов', 10); +$form->addMultiUpload('files', 'Файли:') + ->addRule($form::MaxLength, 'Максимально можна завантажити %d файлів', 10); ``` -Якщо один із файлів не завантажиться правильно, форма не буде відправлена успішно і відобразиться помилка. Тобто немає необхідності перевіряти метод `FileUpload::isOk()`. +Якщо якийсь із файлів не вдалося коректно завантажити, форма не надсилається успішно і відображається помилка. Тобто при успішному надсиланні не потрібно перевіряти метод `FileUpload::isOk()`. + +Ніколи не довіряйте оригінальним назвам файлів, повернутим методом `FileUpload::getName()`, клієнт міг надіслати шкідливу назву файлу з наміром пошкодити або зламати ваш застосунок. + +Правила `MimeType` та `Image` визначають потрібний тип на основі сигнатури файлу і не перевіряють його цілісність. Чи не пошкоджене зображення, можна з'ясувати, наприклад, спробувавши його [завантажити |http:request#toImage]. + -Не довіряйте оригінальним іменам файлів, що повертаються методом `FileUpload::getName()`, клієнт може надіслати шкідливе ім'я файлу з наміром зіпсувати або зламати ваш додаток. +addDate(string|int $name, $label=null): DateTimeControl .[method]{data-version:3.1.14} +====================================================================================== -Правила `MimeType` і `Image` визначають необхідний тип файлу або зображення за його сигнатурою. Цілісність усього файлу не перевіряється. Ви можете дізнатися, чи не пошкоджено зображення, наприклад, спробувавши [завантажити його |http:request#toImage]. +Додає поле, яке дозволяє користувачеві легко ввести дату, що складається з року, місяця та дня (клас [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Як значення за замовчуванням приймає або об'єкти, що реалізують інтерфейс `DateTimeInterface`, рядок з часом, або число, що представляє UNIX timestamp. Те саме стосується аргументів правил `Min`, `Max` або `Range`, які визначають мінімальну та максимальну допустиму дату. + +```php +$form->addDate('date', 'Дата:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'Дата повинна бути щонайменше місячної давності.', new DateTime('-1 month')); +``` + +Стандартно повертає об'єкт `DateTimeImmutable`, методом `setFormat()` ви можете вказати [текстовий формат |https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] або timestamp: + +```php +$form->addDate('date', 'Дата:') + ->setFormat('Y-m-d'); +``` -addHidden(string|int $name, string $default=null): HiddenField .[method] -======================================================================== +addTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=============================================================================================================== + +Додає поле, яке дозволяє користувачеві легко ввести час, що складається з годин, хвилин та, за бажанням, секунд (клас [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Як значення за замовчуванням приймає або об'єкти, що реалізують інтерфейс `DateTimeInterface`, рядок з часом, або число, що представляє UNIX timestamp. З цих вхідних даних використовується лише інформація про час, дата ігнорується. Те саме стосується аргументів правил `Min`, `Max` або `Range`, які визначають мінімальний та максимальний допустимий час. Якщо встановлене мінімальне значення більше за максимальне, створюється часовий діапазон, що переходить через північ. + +```php +$form->addTime('time', 'Час:', withSeconds: true) + ->addRule($form::Range, 'Час має бути в діапазоні від %d до %d.', ['12:30', '13:30']); +``` + +Стандартно повертає об'єкт `DateTimeImmutable` (з датою 1 січня 1 року), методом `setFormat()` ви можете вказати [текстовий формат |https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters]: + +```php +$form->addTime('time', 'Час:') + ->setFormat('H:i'); +``` + + +addDateTime(string|int $name, $label=null, bool $withSeconds=false): DateTimeControl .[method]{data-version:3.1.14} +=================================================================================================================== + +Додає поле, яке дозволяє користувачеві легко ввести дату та час, що складаються з року, місяця, дня, годин, хвилин та, за бажанням, секунд (клас [DateTimeControl |api:Nette\Forms\Controls\DateTimeControl]). + +Як значення за замовчуванням приймає або об'єкти, що реалізують інтерфейс `DateTimeInterface`, рядок з часом, або число, що представляє UNIX timestamp. Те саме стосується аргументів правил `Min`, `Max` або `Range`, які визначають мінімальну та максимальну допустиму дату. + +```php +$form->addDateTime('datetime', 'Дата і час:') + ->setDefaultValue(new DateTime) + ->addRule($form::Min, 'Дата повинна бути щонайменше місячної давності.', new DateTime('-1 month')); +``` + +Стандартно повертає об'єкт `DateTimeImmutable`, методом `setFormat()` ви можете вказати [текстовий формат |https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters] або timestamp: + +```php +$form->addDateTime('datetime') + ->setFormat(DateTimeControl::FormatTimestamp); +``` + + +addColor(string|int $name, $label=null): ColorPicker .[method]{data-version:3.1.14} +=================================================================================== + +Додає поле для вибору кольору (клас [ColorPicker |api:Nette\Forms\Controls\ColorPicker]). Колір - це рядок у форматі `#rrggbb`. Якщо користувач не зробить вибір, повертається чорний колір `#000000`. + +```php +$form->addColor('color', 'Колір:') + ->setDefaultValue('#3C8ED7'); +``` + + +addHidden(string|int $name, ?string $default=null): HiddenField .[method] +========================================================================= Додає приховане поле (клас [HiddenField |api:Nette\Forms\Controls\HiddenField]). @@ -255,26 +348,28 @@ addHidden(string|int $name, string $default=null): HiddenField .[method] $form->addHidden('userid'); ``` -Використовуйте `setNullable()`, щоб змінити його так, щоб він повертав `null` замість порожнього рядка. Функція [addFilter() |validation#Modifying-Input-Values] дозволяє змінити представлене значення. +За допомогою `setNullable()` можна налаштувати, щоб повертався `null` замість порожнього рядка. Змінити надіслане значення дозволяє [addFilter() |validation#Зміна вводу]. + +Хоча елемент прихований, **важливо усвідомлювати**, що значення все ще може бути змінено або підроблено зловмисником. Завжди ретельно перевіряйте та валідуйте всі отримані значення на стороні сервера, щоб запобігти ризикам безпеки, пов'язаним з маніпуляцією даними. addSubmit(string|int $name, $caption=null): SubmitButton .[method] ================================================================== -Додає кнопку відправлення (клас [SubmitButton |api:Nette\Forms\Controls\SubmitButton]). +Додає кнопку надсилання (клас [SubmitButton |api:Nette\Forms\Controls\SubmitButton]). ```php -$form->addSubmit('submit', 'Зарегистрироваться'); +$form->addSubmit('submit', 'Надіслати'); ``` -У формі можна мати більше однієї кнопки відправлення: +У формі можна мати кілька кнопок надсилання: ```php -$form->addSubmit('register', 'Зарегистрироваться'); -$form->addSubmit('cancel', 'Отмена'); +$form->addSubmit('register', 'Зареєструватися'); +$form->addSubmit('cancel', 'Скасувати'); ``` -Щоб дізнатися, яку з них було натиснуто, використовуйте: +Щоб з'ясувати, на яку з них було натиснуто, використовуйте: ```php if ($form['register']->isSubmittedBy()) { @@ -282,48 +377,48 @@ if ($form['register']->isSubmittedBy()) { } ``` -Якщо ви не хочете перевіряти форму під час натискання кнопки відправлення (наприклад, кнопки *Скасування* або *Попередній перегляд*), ви можете відключити її за допомогою [setValidationScope() |validation#Disabling-Validation]. +Якщо ви не хочете валідувати всю форму при натисканні кнопки (наприклад, для кнопок *Скасувати* або *Попередній перегляд*), використовуйте [setValidationScope() |validation#Вимкнення валідації]. addButton(string|int $name, $caption): Button .[method] ======================================================= -Додає кнопку (клас [Button |api:Nette\Forms\Controls\Button]) без функції відправлення. Це корисно для прив'язки іншої функціональності до id, наприклад, дії JavaScript. +Додає кнопку (клас [Button |api:Nette\Forms\Controls\Button]), яка не має функції надсилання. Отже, її можна використовувати для іншої функції, наприклад, виклику функції JavaScript при натисканні. ```php -$form->addButton('raise', 'Поднять зарплату') +$form->addButton('raise', 'Підвищити зарплату') ->setHtmlAttribute('onclick', 'raiseSalary()'); ``` -addImageButton(string|int $name, string $src=null, string $alt=null): ImageButton .[method] -=========================================================================================== +addImageButton(string|int $name, ?string $src=null, ?string $alt=null): ImageButton .[method] +============================================================================================= -Додає кнопку відправлення у вигляді зображення (клас [ImageButton |api:Nette\Forms\Controls\ImageButton]). +Додає кнопку надсилання у вигляді зображення (клас [ImageButton |api:Nette\Forms\Controls\ImageButton]). ```php $form->addImageButton('submit', '/path/to/image'); ``` -У разі використання кількох кнопок відправлення можна дізнатися, яку з них було натиснуто за допомогою `$form['submit']->isSubmittedBy()`. +При використанні кількох кнопок надсилання можна з'ясувати, на яку було натиснуто, за допомогою `$form['submit']->isSubmittedBy()`. addContainer(string|int $name): Container .[method] =================================================== -Додає підформу (клас [Container |api:Nette\Forms\Container]), або контейнер, з яким можна поводитися так само, як і з формою. Це означає, що ви можете використовувати такі методи, як `setDefaults()` або `getValues()`. +Додає підформу (клас [Container |api:Nette\Forms\Container]), або контейнер, до якого можна додавати інші елементи так само, як ми додаємо їх до форми. Також працюють методи `setDefaults()` або `getValues()`. ```php $sub1 = $form->addContainer('first'); -$sub1->addText('name', 'Ваше имя:'); -$sub1->addEmail('email', 'Имейл:'); +$sub1->addText('name', 'Ваше ім\'я:'); +$sub1->addEmail('email', 'Email:'); $sub2 = $form->addContainer('second'); -$sub2->addText('name', 'Ваше имя:'); -$sub2->addEmail('email', 'Имейл:'); +$sub2->addText('name', 'Ваше ім\'я:'); +$sub2->addEmail('email', 'Email:'); ``` -Потім відправлені дані повертаються у вигляді багатовимірної структури: +Надіслані дані потім повертаються як багатовимірна структура: ```php [ @@ -339,81 +434,83 @@ $sub2->addEmail('email', 'Имейл:'); ``` -Огляд налаштувань .[#toc-overview-of-settings] -============================================== +Огляд налаштувань +================= -Для всіх елементів ми можемо викликати такі методи (повний огляд див. у [документації з API |https://api.nette.org/forms/master/Nette/Forms/Controls.html]): +Для всіх елементів ми можемо викликати наступні методи (повний огляд в [документації API |https://api.nette.org/forms/master/Nette/Forms/Controls.html]): .[table-form-methods language-php] -| `setDefaultValue($value)` | встановлює значення за замовчуванням -| `getValue()` | отримати поточне значення -| `setOmitted()` | [omitted values |#omitted values] -| `setDisabled()` | [відключення входів |#disabling inputs] +| `setDefaultValue($value)` | встановлює значення за замовчуванням +| `getValue()` | отримати поточне значення +| `setOmitted()` | [##пропуск значення] +| `setDisabled()` | [##деактивація елементів] -Рендеринг: +Відображення: .[table-form-methods language-php] -| `setCaption()` | змінити заголовок елемента -| `setTranslator()` | задає [транслятор |rendering#translating] -| `setHtmlAttribute()` | задає [HTML атрибут |rendering#HTML attributes] елемента -| `setHtmlId()` | задає HTML атрибут `id` -| `setHtmlType()` | задає HTML атрибут `type` -| `setHtmlName()` | задає HTML атрибут `name` -| `setOption()` | задає [рендеринг даних |rendering#Options] +| `setCaption($caption)` | змінює підпис елемента +| `setTranslator($translator)` | встановлює [перекладач |rendering#Переклад] +| `setHtmlAttribute($name, $value)` | встановлює [HTML-атрибут |rendering#HTML атрибути] елемента +| `setHtmlId($id)` | встановлює HTML-атрибут `id` +| `setHtmlType($type)` | встановлює HTML-атрибут `type` +| `setHtmlName($name)` | встановлює HTML-атрибут `name` +| `setOption($key, $value)` | [налаштування для відображення |rendering#Options] Валідація: .[table-form-methods language-php] -| `setRequired()` | [обов'язкове поле |validation] -| `addRule()` | встановити [правило перевірки |validation#Rules] -| `addCondition()`, `addConditionOn()` | встановити [умову перевірки |validation#Conditions] -| `addError()` | [передача повідомлення про помилку |validation#Processing-Errors] +| `setRequired()` | [обов'язковий елемент |validation] +| `addRule()` | встановлення [правила валідації |validation#Правила] +| `addCondition()`, `addConditionOn()` | встановлює [умову валідації |validation#Умови] +| `addError($message)` | [передача повідомлення про помилку |validation#Помилки під час обробки] -Наступні методи можуть бути викликані для елементів `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`: +Для елементів `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` можна викликати наступні методи: .[table-form-methods language-php] -| `setNullable()`| встановлює, чи повертає getValue() `null` замість порожнього рядка -| `setEmptyValue()` | встановлює спеціальне значення, яке розглядається як порожній рядок -| `setMaxLength()` | встановлює максимальну кількість дозволених символів -| `addFilter()` | [зміна вхідних значень |validation#Modifying-Input-Values] +| `setNullable()` | встановлює, чи поверне getValue() `null` замість порожнього рядка +| `setEmptyValue($value)` | встановлює спеціальне значення, яке вважається порожнім рядком +| `setMaxLength($length)` | встановлює максимальну кількість дозволених символів +| `addFilter($filter)` | [зміна вводу |validation#Зміна вводу] -Опущені значення .[#toc-omitted-values] ---------------------------------------- +Пропуск значення +================ -Якщо вас не цікавить значення, введене користувачем, ми можемо використовувати `setOmitted()`, щоб опустити його з результату, який надається методом `$form->getValues()` або передається обробникам. Це підходить для різних паролів для перевірки, антиспамових полів тощо. +Якщо нас не цікавить значення, введене користувачем, ми можемо пропустити його за допомогою `setOmitted()` з результату методу `$form->getValues()` або з даних, що передаються в обробники. Це корисно для різних паролів для перевірки, антиспам-елементів тощо. ```php -$form->addPassword('passwordVerify', 'Повторите пароль:') - ->setRequired('Введите пароль ещё раз, чтобы проверить опечатку') - ->addRule($form::Equal, 'Несоответствие пароля', $form['password']) +$form->addPassword('passwordVerify', 'Пароль для перевірки:') + ->setRequired('Будь ласка, введіть пароль ще раз для перевірки') + ->addRule($form::Equal, 'Паролі не співпадають', $form['password']) ->setOmitted(); ``` -Вимкнення елементів введення .[#toc-disabling-inputs] ------------------------------------------------------ +Деактивація елементів +===================== -Щоб вимкнути вхід, ви можете викликати `setDisabled()`. Таке поле не може бути відредаговане користувачем. +Елементи можна деактивувати за допомогою `setDisabled()`. Такий елемент користувач не може редагувати. ```php -$form->addText('username', 'Имя пользователя:') +$form->addText('username', 'Ім\'я користувача:') ->setDisabled(); ``` -Зверніть увагу, що браузер узагалі не надсилає відключені поля на сервер, тому ви навіть не знайдете їх у даних, які повертає функція `$form->getValues()`. +Вимкнені елементи браузер взагалі не надсилає на сервер, тому ви їх не знайдете в даних, повернутих функцією `$form->getValues()`. Однак, якщо ви встановите `setOmitted(false)`, Nette включить їхнє значення за замовчуванням у ці дані. -Якщо ви встановлюєте значення за замовчуванням для поля, ви повинні зробити це тільки після його вимкнення: +При виклику `setDisabled()` з міркувань безпеки **значення елемента видаляється**. Якщо ви встановлюєте значення за замовчуванням, це необхідно зробити після його деактивації: ```php -$form->addText('username', 'Имя пользователя:') +$form->addText('username', 'Ім\'я користувача:') ->setDisabled() ->setDefaultValue($userName); ``` +Альтернативою вимкненим елементам є елементи з HTML-атрибутом `readonly`, які браузер надсилає на сервер. Хоча елемент призначений лише для читання, **важливо усвідомлювати**, що його значення все ще може бути змінено або підроблено зловмисником. + -Користувацькі елементи керування .[#toc-custom-controls] -======================================================== +Власні елементи +=============== -Крім широкого спектра вбудованих елементів керування формою, ви можете додати свої елементи таким чином: +Поряд із широким спектром вбудованих елементів форми, ви можете додавати власні елементи до форми таким чином: ```php $form->addComponent(new DateInput('Дата:'), 'date'); @@ -421,28 +518,28 @@ $form->addComponent(new DateInput('Дата:'), 'date'); ``` .[note] -Форма є нащадком класу [Container | component-model:#Container], а елементи є нащадками класу [Component | component-model:#Component]. +Форма є нащадком класу [Container |component-model:#Container], а окремі елементи є нащадками [Component |component-model:#Component]. -Існує спосіб визначення нових методів форми для додавання користувацьких елементів (наприклад, '$form->addZip()'). Це так звані методи розширення. Недоліком є те, що підказки коду в редакторах не працюватимуть для них. +Існує спосіб визначення нових методів форми для додавання власних елементів (наприклад, `$form->addZip()`). Це так звані extension methods. Недоліком є те, що для них не працюватиме автодоповнення в редакторах. ```php use Nette\Forms\Container; -// додає метод addZip(string $name, string $label = null) -Container::extensionMethod('addZip', function (Container $form, string $name, string $label = null) { +// додамо метод addZip(string $name, ?string $label = null) +Container::extensionMethod('addZip', function (Container $form, string $name, ?string $label = null) { return $form->addText($name, $label) - ->addRule($form::Pattern, 'Не менше 5 номерів', '[0-9]{5}'); + ->addRule($form::Pattern, 'Щонайменше 5 цифр', '[0-9]{5}'); }); // використання -$form->addZip('zip', 'ZIP-код:'); +$form->addZip('zip', 'Поштовий індекс:'); ``` -Низькорівневі поля .[#toc-low-level-fields] -=========================================== +Низькорівневі елементи +====================== -Щоб додати елемент у форму, не потрібно викликати '$form->addXyz()'. Замість цього елементи форми можуть бути введені виключно в шаблони. Це корисно, якщо вам, наприклад, потрібно створити динамічні елементи: +Можна також використовувати елементи, які ми записуємо лише в шаблоні і не додаємо до форми жодним із методів `$form->addXyz()`. Наприклад, коли ми виводимо записи з бази даних і заздалегідь не знаємо, скільки їх буде і які у них будуть ID, і хочемо біля кожного рядка відобразити прапорець або перемикач, достатньо закодувати його в шаблоні: ```latte {foreach $items as $item} @@ -450,13 +547,13 @@ $form->addZip('zip', 'ZIP-код:'); {/foreach} ``` -Після надсилання можна отримати значення: +А після надсилання дізнаємося значення: ```php $data = $form->getHttpData($form::DataText, 'sel[]'); $data = $form->getHttpData($form::DataText | $form::DataKeys, 'sel[]'); ``` -У першому параметрі ви вказуєте тип елемента (`DataFile` для `type=file`, `DataLine` для однорядкових введень типу `text`, `password` або `email` і `DataText` для інших). Другий параметр відповідає HTML-атрибуту `name`. Якщо вам потрібно зберегти ключі, ви можете об'єднати перший параметр з `DataKeys`. Це корисно для `select`, `radioList` або `checkboxList`. +де перший параметр - це тип елемента (`DataFile` для `type=file`, `DataLine` для однорядкових полів введення, таких як `text`, `password`, `email` тощо, і `DataText` для всіх інших), а другий параметр `sel[]` відповідає HTML-атрибуту name. Тип елемента можна комбінувати зі значенням `DataKeys`, яке зберігає ключі елементів. Це особливо корисно для `select`, `radioList` та `checkboxList`. -Функція `getHttpData()` повертає оброблені дані. У цьому випадку це завжди буде масив допустимих рядків UTF-8, незалежно від того, що атакуючий відправив через форму. Це альтернатива роботі з `$_POST` або `$_GET` безпосередньо, якщо ви хочете отримувати безпечні дані. +Важливо те, що `getHttpData()` повертає санітизоване значення, у цьому випадку це завжди буде масив дійсних рядків UTF-8, незалежно від того, що зловмисник спробував би підсунути серверу. Це аналог прямої роботи з `$_POST` або `$_GET`, але з тією суттєвою різницею, що він завжди повертає чисті дані, так, як ви звикли зі стандартними елементами форм Nette. diff --git a/forms/uk/in-presenter.texy b/forms/uk/in-presenter.texy index ed89773929..4a23b0bfdb 100644 --- a/forms/uk/in-presenter.texy +++ b/forms/uk/in-presenter.texy @@ -2,35 +2,35 @@ ******************* .[perex] -Nette Forms значно спрощує створення та обробку веб-форм. У цьому розділі ви дізнаєтеся, як використовувати форми всередині презентерів. +Nette Forms значно полегшують створення та обробку веб-форм. У цьому розділі ви дізнаєтеся, як використовувати форми всередині презентерів. -Якщо ви хочете використовувати їх повністю автономно, без решти фреймворку, є посібник з [автономних форм |standalone]. +Якщо вас цікавить, як використовувати їх повністю окремо без решти фреймворку, для вас призначений посібник для [самостійного використання |standalone]. -Перша форма .[#toc-first-form] -============================== +Перша форма +=========== -Ми постараємося написати просту реєстраційну форму. Її код матиме такий вигляд: +Спробуємо написати просту форму реєстрації. Її код буде таким: ```php use Nette\Application\UI\Form; $form = new Form; -$form->addText('name', 'Имя:'); +$form->addText('name', 'Ім\'я:'); $form->addPassword('password', 'Пароль:'); -$form->addSubmit('send', 'Зарегистрироваться'); +$form->addSubmit('send', 'Зареєструватися'); $form->onSuccess[] = [$this, 'formSucceeded']; ``` -і в браузері результат має виглядати так: +і в браузері вона відобразиться так: -[* form-en.webp *] +[* form-cs.webp *] -Форма в презентері є об'єктом класу `Nette\Application\UI\Form`, його попередник `Nette\Forms\Form` призначений для автономного використання. Ми додали в нього поля ім'я, пароль і кнопку відправлення. Нарешті, у рядку з `$form->onSuccess` йдеться про те, що після відправлення та успішної валідації має бути викликаний метод `$this->formSucceeded()`. +Форма в presenter'і є об'єктом класу `Nette\Application\UI\Form`, її попередник `Nette\Forms\Form` призначений для самостійного використання. Ми додали до неї так звані елементи ім'я, пароль та кнопку відправки. І, нарешті, рядок з `$form->onSuccess` говорить, що після відправки та успішної валідації має бути викликаний метод `$this->formSucceeded()`. -З точки зору презентера форма є загальним компонентом. Тому вона розглядається як компонент і включається в презентер за допомогою [фабричного методу |application:components#Factory-Methods]. Це виглядатиме наступним чином: +З точки зору presenter'а форма є звичайним компонентом. Тому з нею поводяться як з компонентом і включають її до presenter'а за допомогою [фабричного методу |application:components#Фабричні методи]. Це виглядатиме так: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} use Nette; use Nette\Application\UI\Form; @@ -39,7 +39,7 @@ class HomePresenter extends Nette\Application\UI\Presenter protected function createComponentRegistrationForm(): Form { $form = new Form; - $form->addText('name', 'Ім'я:'); + $form->addText('name', 'Ім\'я:'); $form->addPassword('password', 'Пароль:'); $form->addSubmit('send', 'Зареєструватися'); $form->onSuccess[] = [$this, 'formSucceeded']; @@ -48,94 +48,91 @@ class HomePresenter extends Nette\Application\UI\Presenter public function formSucceeded(Form $form, $data): void { - // тут ми будемо обробляти дані, відправлені формою + // тут ми обробляємо дані, надіслані формою // $data->name містить ім'я // $data->password містить пароль - $this->flashMessage('Ви успішно зареєструвалися.'); + $this->flashMessage('Ви були успішно зареєстровані.'); $this->redirect('Home:'); } } ``` -А рендеринг у шаблоні здійснюється за допомогою тега `{control}`: +А в шаблоні форму відображаємо за допомогою тегу `{control}`: -```latte .{file:app/Presenters/templates/Home/default.latte} -<h1>Регистрация</h1> +```latte .{file:app/Presentation/Home/default.latte} +<h1>Реєстрація</h1> {control registrationForm} ``` -І це все :-) У нас є функціональна та ідеально [захищена |#Защита от уязвимостей] форма. +І це, власне, все :-) Ми маємо функціональну та ідеально [захищену |#Захист від вразливостей] форму. -Тепер ви, ймовірно, думаєте, що це було занадто швидко, задаючись питанням, як можливо, що викликається метод `formSucceeded()` і які параметри він отримує. Звичайно, ви маєте рацію, це заслуговує на пояснення. +А тепер ви, мабуть, думаєте, що це було занадто швидко, і розмірковуєте, як можливо, що викликається метод `formSucceeded()` і які параметри він отримує. Звичайно, ви маєте рацію, це заслуговує на пояснення. -Nette придумала класний механізм, який ми назвали [Голлівудський стиль |application:components#Hollywood-Style]. Замість того, щоб постійно запитувати, чи відбулося щось ("чи була форма надіслана?", "чи була вона надіслана правильно?", або "чи не була вона підроблена?"), ви кажете фреймворку: "коли форма заповнена правильно, викличте цей метод". Якщо ви програмуєте на JavaScript, ви знайомі з цим стилем програмування. Ви пишете функції, які викликаються при настанні певної [події |nette:glossary#Events]. І мова передає їм відповідні аргументи. +Nette пропонує свіжий механізм, який ми називаємо [Hollywood style |application:components#Голлівудський стиль]. Замість того, щоб ви як розробник постійно запитували, чи щось сталося («чи була форма відправлена?», «чи була вона відправлена валідно?» і «чи не була вона підроблена?»), ви говорите фреймворку «коли форма буде валідно заповнена, виклич цей метод» і залишаєте подальшу роботу йому. Якщо ви програмуєте на JavaScript, цей стиль програмування вам добре знайомий. Ви пишете функції, які викликаються, коли настає певна [подія |nette:glossary#Події události]. І мова передає їм відповідні аргументи. -Ось як побудований наведений вище код презентера. Масив `$form->onSuccess` являє собою список зворотних викликів PHP, які Nette буде викликати, коли форма буде відправлена і правильно заповнена. -У рамках [життєвого циклу презентера |application:presenters#Life-Cycle-of-Presenter] це так званий сигнал, тому вони викликаються після методу `action*` і перед методом `render*`. -І він передає кожному зворотному виклику саму форму в першому параметрі та відправлені дані у вигляді об'єкта [ArrayHash |utils:arrays#ArrayHash] у другому. Ви можете опустити перший параметр, якщо вам не потрібен об'єкт форми. Другий параметр може бути ще зручнішим, але про це [пізніше |#Mapping-to-Classes]. +Саме так побудований і вищезгаданий код presenter'а. Масив `$form->onSuccess` представляє список PHP callback'ів, які Nette викличе в момент, коли форма буде відправлена і правильно заповнена (тобто є валідною). У рамках [життєвого циклу presenter'а |application:presenters#Життєвий цикл презентера] це так званий сигнал, тому вони викликаються після методу `action*` і перед методом `render*`. І кожному callback'у передає як перший параметр саму форму, а як другий — надіслані дані у вигляді об'єкта [ArrayHash |utils:arrays#ArrayHash]. Перший параметр можна пропустити, якщо об'єкт форми вам не потрібен. А другий параметр може бути хитрішим, але про це [пізніше |#Мапування на класи]. -Об'єкт `$data` містить властивості `name` і `password` з даними, введеними користувачем. Зазвичай ми надсилаємо дані безпосередньо для подальшого опрацювання, яке може бути, наприклад, вставкою в базу даних. Однак у процесі обробки може виникнути помилка, наприклад, ім'я користувача вже зайнято. У цьому випадку ми передаємо помилку назад у форму за допомогою `addError()` і дозволяємо їй перемальовуватися заново, з повідомленням про помилку: +Об'єкт `$data` містить ключі `name` та `password` з даними, які заповнив користувач. Зазвичай дані відразу відправляються на подальшу обробку, що може бути, наприклад, вставкою в базу даних. Однак під час обробки може виникнути помилка, наприклад, ім'я користувача вже зайняте. У такому випадку ми передаємо помилку назад у форму за допомогою `addError()` і дозволяємо їй відобразитися знову, вже з повідомленням про помилку. ```php -$form->addError('Извините, имя пользователя уже используется.'); +$form->addError('Вибачте, це ім\'я користувача вже використовується.'); ``` -На додаток до `onSuccess`, існує також `onSubmit`: зворотні виклики завжди викликаються після надсилання форми, навіть якщо вона заповнена неправильно. І, нарешті, `onError`: зворотні виклики викликаються тільки в тому випадку, якщо відправка недійсна. Вони викликаються, навіть якщо ми анулюємо форму в `onSuccess` або `onSubmit` за допомогою `addError()`. +Крім `onSuccess`, існує ще `onSubmit`: callback'и викликаються завжди після відправлення форми, навіть якщо вона заповнена неправильно. А також `onError`: callback'и викликаються тільки якщо відправлення не є валідним. Вони викликаються навіть тоді, якщо в `onSuccess` або `onSubmit` ми зробимо форму невалідною за допомогою `addError()`. -Після обробки форми ми перенаправимо вас на наступну сторінку. Це запобігає ненавмисному повторному надсиланню форми під час натискання кнопки *оновити*, *назад* або переміщення історії браузера. +Після обробки форми ми перенаправляємо на наступну сторінку. Це запобігає небажаному повторному надсиланню форми кнопкою *оновити*, *назад* або рухом в історії браузера. -Спробуйте додати більше [елементів управління форми |controls]. +Спробуйте додати й інші [елементи форми |controls]. -Доступ до елементів керування .[#toc-access-to-controls] -======================================================== +Доступ до елементів +=================== -Форма є компонентом презентера, у нашому випадку з ім'ям `registrationForm` (за іменем фабричного методу `createComponentRegistrationForm`), тому в будь-якому місці презентера ви можете отримати доступ до форми, використовуючи: +Форма є компонентом presenter'а, у нашому випадку названим `registrationForm` (за назвою фабричного методу `createComponentRegistrationForm`), тому будь-де в presenter'і ви можете отримати доступ до форми за допомогою: ```php $form = $this->getComponent('registrationForm'); // альтернативний синтаксис: $form = $this['registrationForm']; ``` -Також окремі елементи керування форми є компонентами, тож доступ до них можна отримати в такий самий спосіб: +Окремі елементи форми також є компонентами, тому ви можете отримати доступ до них таким же чином: ```php $input = $form->getComponent('name'); // або $input = $form['name']; $button = $form->getComponent('send'); // або $button = $form['send']; ``` -Елементи керування видаляються за допомогою функції unset: +Елементи видаляються за допомогою unset: ```php unset($form['name']); ``` -Правила валідації .[#toc-validation-rules] -========================================== +Правила валідації +================= -Слово *valid* було використано кілька разів, але форма ще не має правил валідації. Давайте виправимо це. +Тут прозвучало слово *валідний,* але форма поки що не має жодних правил валідації. Давайте це виправимо. -Ім'я буде обов'язковим, тому ми позначимо його методом `setRequired()`, аргументом якого є текст повідомлення про помилку, яке буде виведено, якщо користувач не заповнить його. Якщо аргумент не вказано, використовується повідомлення про помилку за замовчуванням. +Ім'я буде обов'язковим, тому позначимо його методом `setRequired()`, аргументом якого є текст повідомлення про помилку, яке відобразиться, якщо користувач не заповнить ім'я. Якщо аргумент не вказано, буде використано стандартне повідомлення про помилку. ```php -$form->addText('name', 'Имя:') - ->setRequired('Пожалуйста, введите имя.'); +$form->addText('name', 'Ім\'я:') + ->setRequired('Будь ласка, введіть ім\'я'); ``` -Спробуйте надіслати форму без заповненого імені, і ви побачите, що з'явиться повідомлення про помилку, і браузер або сервер відхилятиме форму, поки ви не заповните її. +Спробуйте надіслати форму без заповненого імені, і ви побачите, що з'явиться повідомлення про помилку, і браузер або сервер відхилятимуть її доти, доки ви не заповните поле. -Водночас ви не зможете обдурити систему, набравши в полі введення, наприклад, тільки пробіли. Ні за що. Nette автоматично обрізає ліві та праві пробільні символи. Спробуйте. Це те, що ви завжди повинні робити з кожним однорядковим введенням, але про це часто забувають. Nette робить це автоматично. (Ви можете спробувати обдурити форму і відправити багаторядковий рядок як ім'я. Навіть тут Nette не обдурять, і переноси рядків будуть замінені на пробіли). +Водночас систему не обдуриш, написавши в полі, наприклад, лише пробіли. Ні. Nette автоматично видаляє пробіли зліва та справа. Спробуйте самі. Це те, що ви завжди повинні робити з кожним однорядковим полем введення, але про це часто забувають. Nette робить це автоматично. (Можете спробувати обдурити форму і надіслати як ім'я багаторядковий рядок. Навіть тут Nette не дасть себе обдурити і замінить переноси рядків на пробіли.) -Форма завжди перевіряється на стороні сервера, але також генерується перевірка JavaScript, що відбувається швидко, і користувач одразу ж дізнається про помилку, без необхідності відправляти форму на сервер. Цим займається скрипт `netteForms.js`. -Вставте його в шаблон макета: +Форма завжди валідується на стороні сервера, але також генерується JavaScript-валідація, яка відбувається миттєво, і користувач дізнається про помилку відразу, без необхідності надсилати форму на сервер. За це відповідає скрипт `netteForms.js`. Вставте його в шаблон макета: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Якщо ви подивитеся у вихідний код сторінки з формою, ви можете помітити, що Nette вставляє обов'язкові поля в елементи з CSS-класом `required`. Спробуйте додати наступний стиль у шаблон, і мітка "Ім'я" буде червоного кольору. Ми елегантно позначаємо обов'язкові поля для користувачів: +Якщо ви подивитеся на вихідний код сторінки з формою, то помітите, що Nette вставляє обов'язкові елементи в елементи з CSS-класом `required`. Спробуйте додати до шаблону наступний стиль, і напис "Ім'я" стане червоним. Таким чином, ми елегантно позначаємо обов'язкові елементи для користувачів: ```latte <style> @@ -143,130 +140,132 @@ $form->addText('name', 'Имя:') </style> ``` -Додаткові правила валідації будуть додані методом `addRule()`. Першим параметром є правило, другим - текст повідомлення про помилку, далі може йти необов'язковий аргумент правила перевірки. Що це означає? +Інші правила валідації додамо методом `addRule()`. Перший параметр — це правило, другий — знову текст повідомлення про помилку, а потім може йти аргумент правила валідації. Що це означає? -Форма отримає ще один необов'язковий елемент введення *age* з умовою, що він має бути числом (`addInteger()`) і перебувати в певних межах (`$form::Range`). І тут ми будемо використовувати третій аргумент `addRule()`, сам діапазон: +Розширимо форму новим необов'язковим полем "вік", яке має бути цілим числом (`addInteger()`) і, крім того, в дозволеному діапазоні (`$form::Range`). І тут ми використаємо третій параметр методу `addRule()`, яким передамо валідатору необхідний діапазон у вигляді пари `[від, до]`: ```php -$form->addInteger('age', 'Возраст:') - ->addRule($form::Range, 'Вы должны быть старше 18 лет и иметь возраст до 120 лет.', [18, 120]); +$form->addInteger('age', 'Вік:') + ->addRule($form::Range, 'Вік має бути від 18 до 120', [18, 120]); ``` .[tip] -Якщо користувач не заповнить поле, правила валідації не будуть перевірені, оскільки поле є необов'язковим. +Якщо користувач не заповнить поле, правила валідації не перевірятимуться, оскільки елемент є необов'язковим. -Очевидно, що тут є місце для невеликого рефакторингу. У повідомленні про помилку і в третьому параметрі числа перераховані у двох екземплярах, що не ідеально. Якби ми створювали [багатомовну форму |rendering#translating] і повідомлення, що містить числа, довелося б перекладати кількома мовами, це ускладнило б зміну значень. З цієї причини можна використовувати символи-замінники `%d`: +Тут виникає простір для невеликого рефакторингу. У повідомленні про помилку та в третьому параметрі числа вказані дубльовано, що не ідеально. Якби ми створювали [багатомовні форми |rendering#Переклад], і повідомлення, що містить числа, було б перекладено кількома мовами, це ускладнило б можливу зміну значень. З цієї причини можна використовувати плейсхолдери `%d`, і Nette доповнить значення: ```php - ->addRule($form::Range, 'Вы должны быть старше %d лет и иметь возраст до %d лет.', [18, 120]); + ->addRule($form::Range, 'Вік має бути від %d до %d років', [18, 120]); ``` -Повернемося до поля *пароль*, зробимо його *обов'язковим* і перевіримо мінімальну довжину пароля (`$form::MinLength`), знову використовуючи символи-замінники в повідомленні: +Повернемося до елемента `password`, який ми також зробимо обов'язковим і ще перевіримо мінімальну довжину пароля (`$form::MinLength`), знову ж таки, використовуючи плейсхолдер: ```php $form->addPassword('password', 'Пароль:') - ->setRequired('Выберите пароль') - ->addRule($form::MinLength, 'Ваш пароль должен быть длиной не менее %d', 8); + ->setRequired('Виберіть пароль') + ->addRule($form::MinLength, 'Пароль повинен містити щонайменше %d символів', 8); ``` -Ми додамо у форму поле `passwordVerify`, в якому користувач вводить пароль ще раз, для перевірки. Використовуючи правила валідації, ми перевіряємо, чи однакові обидва паролі (`$form::Equal`). А як аргумент ми даємо посилання на перший пароль, використовуючи [квадратні дужки |#Access-to-Controls]: +Додамо до форми ще поле `passwordVerify`, де користувач введе пароль ще раз для перевірки. За допомогою правил валідації перевіримо, чи обидва паролі однакові (`$form::Equal`). А як параметр дамо посилання на перший пароль за допомогою [квадратних дужок |#Доступ до елементів]: ```php -$form->addPassword('passwordVerify', 'Повторите пароль:') - ->setRequired('Введите пароль ещё раз, чтобы проверить опечатку') - ->addRule($form::Equal, 'Несоответствие пароля', $form['password']) +$form->addPassword('passwordVerify', 'Пароль для перевірки:') + ->setRequired('Будь ласка, введіть пароль ще раз для перевірки') + ->addRule($form::Equal, 'Паролі не співпадають', $form['password']) ->setOmitted(); ``` -Використовуючи `setOmitted()`, ми позначили елемент, значення якого нас не особливо хвилює і який існує тільки для перевірки. Його значення не передається в `$data`. +За допомогою `setOmitted()` ми позначили елемент, значення якого насправді не має значення і який існує лише для валідації. Значення не передається до `$data`. -У нас є повнофункціональна форма з валідацією на PHP і JavaScript. Можливості валідації в Nette набагато ширші, ви можете створювати умови, відображати і приховувати частини сторінки відповідно до них тощо. Ви можете дізнатися про все в розділі [Валідація форм |validation]. +Таким чином, ми маємо готову, повністю функціональну форму з валідацією в PHP та JavaScript. Можливості валідації Nette набагато ширші, можна створювати умови, за якими відображати та приховувати частини сторінки тощо. Все це ви дізнаєтеся в розділі про [валідацію форм |validation]. -Значення за замовчуванням .[#toc-default-values] -================================================ +Значення за замовчуванням +========================= -Ми часто встановлюємо значення за замовчуванням для елементів управління форми: +Елементам форми зазвичай встановлюють значення за замовчуванням: ```php -$form->addEmail('email', 'Имейл') +$form->addEmail('email', 'E-mail') ->setDefaultValue($lastUsedEmail); ``` -Часто буває корисно встановити значення за замовчуванням одразу для всіх елементів керування. Наприклад, коли форма використовується для редагування записів. Ми зчитуємо запис із бази даних і встановлюємо його як значення за замовчуванням: +Часто буває зручно встановити значення за замовчуванням для всіх елементів одночасно. Наприклад, коли форма використовується для редагування записів. Ми читаємо запис з бази даних і встановлюємо значення за замовчуванням: ```php //$row = ['name' => 'John', 'age' => '33', /* ... */]; $form->setDefaults($row); ``` -Викличте `setDefaults()` після визначення елементів керування. +Викликайте `setDefaults()` після визначення елементів. -Відображення форми .[#toc-rendering-the-form] -============================================= +Відображення форми +================== -За замовчуванням форма відображається у вигляді таблиці. Окремі елементи управління слідують основним рекомендаціям щодо забезпечення доступності веб-сторінок. Усі мітки генеруються як елементи `<label>` і пов'язані зі своїми елементами, клацання по мітці переміщує курсор на відповідний елемент. +Стандартно форма відображається як таблиця. Окремі елементи відповідають основному правилу доступності - всі написи записані як `<label>` і пов'язані з відповідним елементом форми. При натисканні на напис курсор автоматично з'являється в полі форми. -Ми можемо встановити будь-які атрибути HTML для кожного елемента. Наприклад, додайте заповнювач: +Кожному елементу ми можемо встановлювати будь-які HTML-атрибути. Наприклад, додати placeholder: ```php -$form->addInteger('age', 'Возраст:') - ->setHtmlAttribute('placeholder', 'Пожалуйста, заполните возраст'); +$form->addInteger('age', 'Вік:') + ->setHtmlAttribute('placeholder', 'Будь ласка, заповніть вік'); ``` -Насправді існує безліч способів візуалізації форми, докладніше в розділі [Рендеринг |rendering]. +Способів відображення форми дійсно багато, тому цьому присвячено [окремий розділ про відображення |rendering]. -Зіставлення з класами .[#toc-mapping-to-classes] -================================================ +Мапування на класи +================== -Давайте повернемося до обробки даних форми. Метод `getValues()` повертає представлені дані у вигляді об'єкта `ArrayHash`. Оскільки це загальний клас, щось на кшталт `stdClass`, нам не вистачатиме деяких зручностей під час роботи з ним, як-от завершення коду для властивостей у редакторах або статичний аналіз коду. Цю проблему можна вирішити, створивши для кожної форми окремий клас, властивості якого представляють окремі елементи керування. Наприклад: +Повернемося до методу `formSucceeded()`, який у другому параметрі `$data` отримує надіслані дані як об'єкт `ArrayHash`. Оскільки це загальний клас, щось на зразок `stdClass`, нам при роботі з ним бракуватиме певного комфорту, такого як підказка властивостей в редакторах або статичний аналіз коду. Це можна було б вирішити, маючи для кожної форми конкретний клас, властивості якого представляють окремі елементи. Наприклад: ```php class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -Починаючи з PHP 8.0, ви можете використовувати цю елегантну нотацію, яка використовує конструктор: +Альтернативно, ви можете використовувати конструктор: ```php class RegistrationFormData { public function __construct( public string $name, - public int $age, + public ?int $age, public string $password, ) { } } ``` -Як вказати Nette повертати дані у вигляді об'єктів цього класу? Легше, ніж ви думаєте. Все, що вам потрібно зробити, це вказати клас як тип параметра `$data` в обробнику: +Властивості класу даних також можуть бути enum'ами, і вони будуть автоматично зіставлені. .{data-version:3.2.4} + +Як сказати Nette, щоб він повертав нам дані як об'єкти цього класу? Легше, ніж ви думаєте. Достатньо лише вказати клас як тип параметра `$data` в обробному методі: ```php public function formSucceeded(Form $form, RegistrationFormData $data): void { - // $name - екземпляр RegistrationFormData + // $data є екземпляром RegistrationFormData $name = $data->name; // ... } ``` -Ви також можете вказати `array` як тип, і тоді дані будуть передаватися у вигляді масиву. +Як тип можна також вказати `array`, і тоді дані передадуться як масив. -Аналогічним чином можна використовувати метод `getValues()`, якому як параметр ми передаємо ім'я класу або об'єкта для гідратації: +Аналогічним чином можна використовувати і функцію `getValues()`, якій назву класу або об'єкт для гідратації передамо як параметр: ```php $data = $form->getValues(RegistrationFormData::class); $name = $data->name; ``` -Якщо форми складаються з багаторівневої структури, що складається з контейнерів, створіть окремий клас для кожного з них: +Якщо форми утворюють багаторівневу структуру, що складається з контейнерів, створіть для кожного окремий клас: ```php $form = new Form; @@ -283,32 +282,34 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` -З типу властивості `$person` відображення дізнається, що воно має відобразити контейнер на клас `PersonFormData`. Якщо властивість буде містити масив контейнерів, вкажіть тип `array` і передайте клас, який повинен бути зіставлений безпосередньо з контейнером: +Мапування потім з типу властивості `$person` дізнається, що контейнер потрібно мапувати на клас `PersonFormData`. Якщо властивість містила б масив контейнерів, вкажіть тип `array` і клас для мапування передайте безпосередньо контейнеру: ```php $person->setMappedType(PersonFormData::class); ``` +Проект класу даних форми можна згенерувати за допомогою методу `Nette\Forms\Blueprint::dataClass($form)`, який виведе його на сторінку браузера. Потім код достатньо клацнути, щоб виділити, і скопіювати до проекту. .{data-version:3.1.15} + -Кілька кнопок надсилання .[#toc-multiple-submit-buttons] -======================================================== +Кілька кнопок +============= -Якщо форма містить більше однієї кнопки, нам зазвичай потрібно розрізняти, яку з них було натиснуто. Ми можемо створити власну функцію для кожної кнопки. Встановіть її як обробник для [події |nette:glossary#Events] `onClick`: +Якщо форма має більше однієї кнопки, зазвичай потрібно розрізнити, яка з них була натиснута. Ми можемо створити для кожної кнопки власну функцію-обробник. Встановимо її як обробник для [події |nette:glossary#Події události] `onClick`: ```php -$form->addSubmit('save', 'Сохранить') +$form->addSubmit('save', 'Зберегти') ->onClick[] = [$this, 'saveButtonPressed']; -$form->addSubmit('delete', 'Удалить') +$form->addSubmit('delete', 'Видалити') ->onClick[] = [$this, 'deleteButtonPressed']; ``` -Ці обробники також викликаються тільки в тому випадку, якщо форма дійсна, як у випадку події `onSuccess`. Різниця в тому, що першим параметром може бути об'єкт кнопки submit, а не форма, залежно від типу, який ви вкажете: +Ці обробники викликаються лише у випадку валідно заповненої форми, так само як і у випадку події `onSuccess`. Різниця полягає в тому, що як перший параметр замість форми може передаватися кнопка відправки, залежно від типу, який ви вкажете: ```php public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) @@ -318,62 +319,61 @@ public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data) } ``` -Коли форма надсилається за допомогою кнопки <kbd>Enter</kbd>, вона обробляється так само, як якби вона була надіслана за допомогою першої кнопки. +Коли форма надсилається кнопкою <kbd>Enter</kbd>, це вважається так, ніби вона була надіслана першою кнопкою. -Подія onAnchor .[#toc-event-onanchor] -===================================== +Подія onAnchor +============== -Коли ви створюєте форму у фабричному методі (наприклад, `createComponentRegistrationForm`), вона ще не знає, чи була вона надіслана, чи з якими даними її було надіслано. Але бувають випадки, коли нам необхідно знати передані значення, можливо, від них залежить, який вигляд матиме форма, або вони використовуються для залежних списків тощо. +Коли у фабричному методі (наприклад, `createComponentRegistrationForm`) ми створюємо форму, вона ще не знає, чи була вона надіслана, і з якими даними. Але є випадки, коли нам потрібно знати надіслані значення, наприклад, від них залежить подальший вигляд форми, або вони потрібні для залежних селектбоксів тощо. -Тому можна зробити так, щоб код, який створює форму, викликався, коли вона закріплена, тобто він уже пов'язаний із презентером і знає його представлені дані. Ми помістимо такий код у масив `$onAnchor`: +Тому частину коду, що створює форму, можна викликати лише в момент, коли вона так звано "заякорена", тобто вже пов'язана з presenter'ом і знає свої надіслані дані. Такий код передаємо до масиву `$onAnchor`: ```php $country = $form->addSelect('country', 'Країна:', $this->model->getCountries()); $city = $form->addSelect('city', 'Місто:'); $form->onAnchor[] = function () use ($country, $city) { - // ця функція буде викликана, коли форма дізнається дані, з якими вона була відправлена - // тому ви можете використовувати метод getValue(). + // ця функція викликається, коли форма вже знає, чи була вона надіслана і з якими даними + // тому можна використовувати метод getValue() $val = $country->getValue(); $city->setItems($val ? $this->model->getCities($val) : []); }; ``` -Vulnerability Protection .[#toc-vulnerability-protection] -========================================================= +Захист від вразливостей +======================= -Nette Framework докладає великих зусиль для забезпечення безпеки, а оскільки форми є найпоширенішим видом користувацького введення, форми Nette настільки ж гарні, наскільки непроникні. +Nette Framework надає великого значення безпеці, тому ретельно дбає про надійний захист форм. Це робиться повністю прозоро і не вимагає ручного налаштування. -На додаток до захисту форм від атак відомих вразливостей, таких як [Cross-Site Scripting (XSS) |nette:glossary#Cross-Site-Scripting-XSS] і [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site-Request-Forgery-CSRF], він виконує безліч дрібних завдань із забезпечення безпеки, про які вам більше не потрібно думати. +Крім того, що форми захищають від атаки [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] та [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF], вони виконують багато дрібних заходів безпеки, про які вам вже не потрібно думати. -Наприклад, він відфільтровує всі керуючі символи з даних, що вводяться, і перевіряє правильність кодування UTF-8, так що дані з форми завжди будуть чистими. Для полів вибору і радіосписків перевіряється, що обрані елементи дійсно були із запропонованих і не було підробки. Ми вже згадували, що для введення однорядкового тексту він видаляє символи кінця рядка, які зловмисник може туди відправити. Для багаторядкових вводів він нормалізує символи кінця рядка. І так далі. +Наприклад, вони фільтрують з вхідних даних усі керуючі символи та перевіряють валідність кодування UTF-8, тому дані з форми завжди будуть чистими. У селектбоксах та радіо-списках перевіряється, що вибрані елементи були дійсно з запропонованих і не відбулося підробки. Ми вже згадували, що в однорядкових текстових полях видаляються символи кінця рядка, які міг надіслати зловмисник. У багаторядкових полях, навпаки, нормалізуються символи кінця рядка. І так далі. -Nette усуває за вас уразливості в системі безпеки, про існування яких більшість програмістів навіть не підозрюють. +Nette вирішує за вас ризики безпеки, про існування яких багато програмістів навіть не підозрюють. -Згадана CSRF-атака полягає в тому, що зловмисник заманює жертву відвідати сторінку, яка мовчки виконує запит у браузері жертви до сервера, на якому жертва на даний момент зареєстрована, і сервер вважає, що запит був зроблений жертвою за власним бажанням. Таким чином, Nette запобігає надсиланню форми через POST з іншого домену. Якщо з якоїсь причини ви хочете відключити захист і дозволити надсилання форми з іншого домену, використовуйте: +Згадана CSRF-атака полягає в тому, що зловмисник заманює жертву на сторінку, яка непомітно в браузері жертви виконує запит на сервер, на якому жертва авторизована, і сервер вважає, що запит виконала жертва за власною волею. Тому Nette запобігає надсиланню POST-форми з іншого домену. Якщо з якоїсь причини ви хочете вимкнути захист і дозволити надсилати форму з іншого домену, використовуйте: ```php $form->allowCrossOrigin(); // УВАГА! Вимикає захист! ``` -Цей захист використовує файл cookie SameSite з назвою `_nss`. Захист за допомогою файлів cookie SameSite може бути не на 100% надійним, тому варто ввімкнути захист за допомогою токенів: +Цей захист використовує SameSite cookie з назвою `_nss`. Захист за допомогою SameSite cookie може бути не 100% надійним, тому бажано увімкнути ще захист за допомогою токена: ```php $form->addProtection(); ``` -Настійно рекомендується застосовувати цей захист до форм в адміністративній частині вашого застосунку, які змінюють конфіденційні дані. Фреймворк захищає від атаки CSRF, генеруючи та перевіряючи токен автентифікації, що зберігається в сесії (аргументом є повідомлення про помилку, яке показується, якщо термін дії токена закінчився). Тому перед відображенням форми необхідно, щоб сесія була запущена. В адміністративній частині сайту сесія, як правило, вже почалася, оскільки користувач увійшов у систему. -В іншому випадку, запустіть сесію за допомогою методу `Nette\Http\Session::start()`. +Рекомендуємо таким чином захищати форми в адміністративній частині сайту, які змінюють чутливі дані в додатку. Фреймворк захищається від CSRF-атаки шляхом генерації та перевірки авторизаційного токена, який зберігається в сесії. Тому необхідно перед відображенням форми мати відкриту сесію. В адміністративній частині сайту зазвичай сесія вже запущена через авторизацію користувача. В іншому випадку запустіть сесію методом `Nette\Http\Session::start()`. -Використання однієї форми в декількох презентерах .[#toc-using-one-form-in-multiple-presenters] -=============================================================================================== +Однакова форма в кількох презентерах +==================================== -Якщо вам потрібно використовувати одну форму в декількох презентерах, ми рекомендуємо вам створити для неї фабрику, яку ви потім передасте презентерам. Відповідним місцем для такого класу є, наприклад, каталог `app/Forms`. +Якщо вам потрібно використовувати одну й ту ж форму в кількох презентерах, рекомендуємо створити для неї фабрику, яку потім передасте до презентера. Підходящим місцем для такого класу є, наприклад, директорія `app/Forms`. -Клас фабрики може виглядати таким чином: +Клас фабрики може виглядати приблизно так: ```php use Nette\Application\UI\Form; @@ -383,14 +383,14 @@ class SignInFormFactory public function create(): Form { $form = new Form; - $form->addText('name', 'Имя:'); - $form->addSubmit('send', 'Войти'); + $form->addText('name', 'Ім\'я:'); + $form->addSubmit('send', 'Увійти'); return $form; } } ``` -Ми просимо клас виготовити форму в методі фабрики для компонентів у презентері: +Ми просимо клас створити форму у фабричному методі для компонентів у презентері: ```php public function __construct( @@ -401,14 +401,14 @@ public function __construct( protected function createComponentSignInForm(): Form { $form = $this->formFactory->create(); - // ми можемо змінити форму, наприклад, мітку на кнопці - $form['login']->setCaption('Продовжити'); - $form->onSuccess[] = [$this, 'signInFormSubmitted']; // і додати обробник + // ми можемо змінити форму, тут, наприклад, змінюємо напис на кнопці + $form['send']->setCaption('Продовжити'); + $form->onSuccess[] = [$this, 'signInFormSuceeded']; // і додаємо обробник return $form; } ``` -Обробник форми також може бути отриманий за допомогою фабрики: +Обробник для обробки форми також може бути наданий вже з фабрики: ```php use Nette\Application\UI\Form; @@ -418,14 +418,14 @@ class SignInFormFactory public function create(): Form { $form = new Form; - $form->addText('name', 'Ім'я:'); + $form->addText('name', 'Ім\'я:'); $form->addSubmit('send', 'Увійти'); $form->onSuccess[] = function (Form $form, $data): void { - // тут ми обробляємо відправлену форму + // тут ми виконуємо обробку форми }; return $form; } } ``` -Отже, ми коротко познайомилися з формами в Nette. Спробуйте пошукати натхнення в каталозі [examples |https://github.com/nette/forms/tree/master/examples] у дистрибутиві. +Отже, ми пройшли швидкий вступ до форм у Nette. Спробуйте ще заглянути в директорію [examples |https://github.com/nette/forms/tree/master/examples] в дистрибутиві, де знайдете більше натхнення. diff --git a/forms/uk/rendering.texy b/forms/uk/rendering.texy index 95bf703c4d..d6c1287888 100644 --- a/forms/uk/rendering.texy +++ b/forms/uk/rendering.texy @@ -1,33 +1,35 @@ -Рендеринг форм -************** +Відображення форм +***************** -Зовнішній вигляд форм може бути дуже різноманітним. На практиці ми можемо зіткнутися з двома крайнощами. З одного боку, є потреба відрендерити серію форм у додатку, які візуально схожі одна на одну, і ми цінуємо простий рендеринг без шаблону за допомогою `$form->render()`. Зазвичай це стосується адміністративних інтерфейсів. +Зовнішній вигляд форм може бути дуже різноманітним. На практиці ми можемо зіткнутися з двома крайнощами. З одного боку, існує потреба відображати в додатку низку форм, які візуально схожі одна на одну, як дві краплі води, і ми оцінимо легкість відображення без шаблону за допомогою `$form->render()`. Зазвичай це стосується адміністративних інтерфейсів. -З іншого боку, існують різні форми, де кожна з них унікальна. Їх зовнішній вигляд найкраще описати за допомогою мови HTML в шаблоні. І, звичайно, крім обох згаданих крайнощів, ми зустрінемо багато форм, які знаходяться десь посередині. +З іншого боку, існують різноманітні форми, де кожна форма є оригінальною. Їхній вигляд найкраще описувати мовою HTML у шаблоні форми. І, звісно, крім обох згаданих крайнощів, ми зустрінемо безліч форм, які знаходяться десь посередині. -Візуалізація за допомогою Latte .[#toc-rendering-with-latte] -============================================================ +Відображення за допомогою Latte +=============================== -[Система шаблонів Latte |latte:] докорінно полегшує відтворення форм та їхніх елементів. Спочатку ми покажемо, як відтворювати форми вручну, елемент за елементом, щоб отримати повний контроль над кодом. Пізніше ми покажемо, як [автоматизувати |#Automatic-Rendering] такий рендеринг. +[Система шаблонів Latte|latte:] суттєво полегшує відображення форм та їхніх елементів. Спочатку ми покажемо, як відображати форми вручну по окремих елементах, щоб отримати повний контроль над кодом. Пізніше ми покажемо, як таке відображення можна [автоматизувати |#Автоматичне відображення]. + +Ви можете згенерувати дизайн шаблону форми Latte за допомогою методу `Nette\Forms\Blueprint::latte($form)`, який виведе його на сторінку браузера. Потім достатньо клацнути, щоб виділити код, і скопіювати його до вашого проєкту. .{data-version:3.1.15} `{control}` ----------- -Найпростіший спосіб відобразити форму - написати її в шаблоні: +Найпростіший спосіб відобразити форму — написати в шаблоні: ```latte {control signInForm} ``` -Зовнішній вигляд відмальованої форми можна змінити, налаштувавши [Renderer |#Renderer] і [окремі елементи керування |#HTML-Attributes]. +Вплинути на вигляд так відображеної форми можна за допомогою конфігурації [#Renderer] та [окремих елементів |#HTML атрибути]. `n:name` -------- -Дуже легко пов'язати визначення форми в PHP-коді з HTML-кодом. Просто додайте атрибути `n:name`. Ось як це просто! +Визначення форми в PHP-коді можна надзвичайно легко пов'язати з HTML-кодом. Достатньо лише додати атрибути `n:name`. Це так просто! ```php protected function createComponentSignInForm(): Form @@ -43,10 +45,10 @@ protected function createComponentSignInForm(): Form ```latte <form n:name=signInForm class=form> <div> - <label n:name=username>Имя пользователя: <input n:name=username size=20 autofocus></label> + <label n:name=username>Username: <input n:name=username size=20 autofocus></label> </div> <div> - <label n:name=password>Пароль: <input n:name=password></label> + <label n:name=password>Password: <input n:name=password></label> </div> <div> <input n:name=send class="btn btn-default"> @@ -54,10 +56,9 @@ protected function createComponentSignInForm(): Form </form> ``` -Зовнішній вигляд отриманого HTML-коду повністю у ваших руках. Якщо ви використовуєте атрибут `n:name` з `<select>`, `<button>` або `<textarea>` елементами, їхній внутрішній вміст заповнюється автоматично. -Крім того, тег `<form n:name>` тег створює локальну змінну `$form` з намальованим об'єктом форми, а закриваючий тег `</form>` перемальовує всі недомальовані приховані елементи (те саме стосується і `{form} ... {/form}`). +Вигляд кінцевого HTML-коду повністю у ваших руках. Якщо ви використовуєте атрибут `n:name` для елементів `<select>`, `<button>` або `<textarea>`, їхній внутрішній вміст автоматично заповнюється. Тег `<form n:name>` також створює локальну змінну `$form` з об'єктом відображуваної форми, а закриваючий тег `</form>` відображає всі невідображені приховані елементи (те саме стосується `{form} ... {/form}`). -Однак не варто забувати про виведення можливих повідомлень про помилки. Як тих, які були додані до окремих елементів методом `addError()` (за допомогою `{inputError}`), так і тих, які були додані безпосередньо до форми (повертаються методом `$form->getOwnErrors()`): +Однак не можна забувати про відображення можливих повідомлень про помилки. Як тих, що були додані до окремих елементів за допомогою методу `addError()` (за допомогою `{inputError}`), так і тих, що були додані безпосередньо до форми (повертаються методом `$form->getOwnErrors()`): ```latte <form n:name=signInForm class=form> @@ -66,11 +67,11 @@ protected function createComponentSignInForm(): Form </ul> <div> - <label n:name=username>Имя пользователя: <input n:name=username size=20 autofocus></label> + <label n:name=username>Username: <input n:name=username size=20 autofocus></label> <span class=error n:ifcontent>{inputError username}</span> </div> <div> - <label n:name=password>Пароль: <input n:name=password></label> + <label n:name=password>Password: <input n:name=password></label> <span class=error n:ifcontent>{inputError password}</span> </div> <div> @@ -79,7 +80,7 @@ protected function createComponentSignInForm(): Form </form> ``` -Більш складні елементи форми, такі як RadioList або CheckboxList, можуть бути відображені елемент за елементом: +Складніші елементи форми, такі як RadioList або CheckboxList, можна таким чином відображати по окремих пунктах: ```latte {foreach $form[gender]->getItems() as $key => $label} @@ -88,16 +89,10 @@ protected function createComponentSignInForm(): Form ``` -Пропозиція коду `{formPrint}` .[#toc-formprint] ------------------------------------------------ - -Ви можете згенерувати подібний код Latte для форми, використовуючи тег `{formPrint}`. Якщо ви помістите його в шаблон, ви побачите проєкт коду замість звичайного рендерингу. Потім просто виділіть його та скопіюйте у свій проект. - - `{label}` `{input}` ------------------- -Чи не хочете ви подумати для кожного елемента, який HTML-елемент використовувати для нього в шаблоні? `<input>`, `<textarea>` і т.д.? Рішенням є універсальний тег `{input}`: +Не хочете думати для кожного елемента, який HTML-елемент використовувати в шаблоні, чи то `<input>`, `<textarea>` тощо? Рішенням є універсальний тег `{input}`: ```latte <form n:name=signInForm class=form> @@ -106,11 +101,11 @@ protected function createComponentSignInForm(): Form </ul> <div> - {label username}Имя пользователя: {input username, size: 20, autofocus: true}{/label} + {label username}Username: {input username, size: 20, autofocus: true}{/label} {inputError username} </div> <div> - {label password}Пароль: {input password}{/label} + {label password}Password: {input password}{/label} {inputError password} </div> <div> @@ -119,9 +114,9 @@ protected function createComponentSignInForm(): Form </form> ``` -Якщо форма використовує перекладач, то текст усередині тегів `{label}` буде перекладено. +Якщо форма використовує перекладач, текст усередині тегів `{label}` буде перекладено. -Знову ж таки, складніші елементи форми, такі як RadioList або CheckboxList, можуть виводитися поелементно: +Навіть у цьому випадку складніші елементи форми, такі як RadioList або CheckboxList, можна відображати по окремих пунктах: ```latte {foreach $form[gender]->items as $key => $label} @@ -129,20 +124,19 @@ protected function createComponentSignInForm(): Form {/foreach} ``` -Щоб відобразити `<input>` в елементі Checkbox, використовуйте `{input myCheckbox:}`. Атрибути HTML повинні бути розділені комою `{input myCheckbox:, class: required}`. +Для відображення самого `<input>` в елементі Checkbox використовуйте `{input myCheckbox:}`. HTML-атрибути в цьому випадку завжди розділяйте комою `{input myCheckbox:, class: required}`. `{inputError}` -------------- -Виводить повідомлення про помилку для елемента форми, якщо воно є. Повідомлення зазвичай обертається в HTML-елемент для стилізації. -Уникнути виведення порожнього елемента за відсутності повідомлення можна за допомогою `n:ifcontent`: +Виводить повідомлення про помилку для елемента форми, якщо воно є. Повідомлення зазвичай загортають у HTML-елемент для стилізації. Запобігти відображенню порожнього елемента, якщо повідомлення немає, можна елегантно за допомогою `n:ifcontent`: ```latte <span class=error n:ifcontent>{inputError $input}</span> ``` -Ми можемо визначити наявність помилки за допомогою методу `hasErrors()` і встановити клас батьківського елемента відповідним чином: +Наявність помилки можна перевірити методом `hasErrors()` і відповідно встановити клас для батьківського елемента: ```latte <div n:class="$form[username]->hasErrors() ? 'error'"> @@ -155,14 +149,13 @@ protected function createComponentSignInForm(): Form `{form}` -------- -Мітки `{form signInForm}...{/form}` є альтернативою `<form n:name="signInForm">...</form>`. +Теги `{form signInForm}...{/form}` є альтернативою до `<form n:name="signInForm">...</form>`. -Автоматичний рендеринг .[#toc-automatic-rendering] --------------------------------------------------- +Автоматичне відображення +------------------------ -За допомогою тегів `{input}` і `{label}` ми можемо легко створити загальний шаблон для будь-якої форми. Він буде ітерувати і послідовно виводити всі елементи форми, за винятком прихованих елементів, які виводяться автоматично під час завершення форми за допомогою тега `</form>` тега. -Він буде очікувати ім'я намальованої форми у змінній `$form`. +Завдяки тегам `{input}` і `{label}` ми можемо легко створити загальний шаблон для будь-якої форми. Він буде послідовно ітерувати та відображати всі її елементи, крім прихованих елементів, які відображаються автоматично при закритті форми тегом `</form>`. Назва відображуваної форми очікується у змінній `$form`. ```latte <form n:name=$form class=form> @@ -179,16 +172,15 @@ protected function createComponentSignInForm(): Form </form> ``` -Використовувані парні самозакривні теги `{label .../}` відображають мітки, що надходять із визначення форми в PHP-коді. +Використані самозакривні парні теги `{label .../}` відображають мітки, що походять з визначення форми в PHP-коді. -Ви можете зберегти цей загальний шаблон у файлі `basic-form.latte` і для візуалізації форми просто увімкнути його та передати ім'я форми (або екземпляр) у параметр `$form`: +Цей загальний шаблон збережіть, наприклад, у файлі `basic-form.latte`, і для відображення форми достатньо його включити та передати назву (або екземпляр) форми в параметр `$form`: ```latte {include basic-form.latte, form: signInForm} ``` -Якщо ви хочете вплинути на зовнішній вигляд однієї конкретної форми і намалювати один елемент по-іншому, то найпростіший спосіб - це підготувати в шаблоні блоки, які можна перезаписати пізніше. -Блоки також можуть мати [динамічні імена |latte:template-inheritance#Dynamic-Block-Names], тому ви можете вставити в них ім'я елемента, який потрібно намалювати. Наприклад: +Якщо б ви хотіли під час відображення однієї конкретної форми втрутитися в її вигляд і, наприклад, один елемент відобразити інакше, то найпростішим шляхом є підготувати в шаблоні блоки, які можна буде потім перезаписати. Блоки можуть мати також [динамічні імена |latte:template-inheritance#Динамічні назви блоків], тому в них можна вставити й ім'я відображуваного елемента. Наприклад: ```latte ... @@ -197,7 +189,7 @@ protected function createComponentSignInForm(): Form ... ``` -Для елемента, наприклад, `username` створюється блок `input-username`, який можна легко перевизначити за допомогою тега [{embed} |latte:template-inheritance#Unit-Inheritance]: +Для елемента, наприклад, `username` таким чином виникне блок `input-username`, який можна легко перезаписати за допомогою тегу [{embed} |latte:template-inheritance#Успадкування одиниць embed]: ```latte {embed basic-form.latte, form: signInForm} @@ -209,7 +201,7 @@ protected function createComponentSignInForm(): Form {/embed} ``` -Альтернативно, весь вміст шаблону `basic-form.latte` може бути [визначено |latte:template-inheritance#Definitions] як блок, включаючи параметр `$form`: +Альтернативно, весь вміст шаблону `basic-form.latte` можна [визначити |latte:template-inheritance#Визначення] як блок, включно з параметром `$form`: ```latte {define basic-form, $form} @@ -219,7 +211,7 @@ protected function createComponentSignInForm(): Form {/define} ``` -Це дещо спростить його використання: +Завдяки цьому його виклик буде трохи простішим: ```latte {embed basic-form, signInForm} @@ -227,31 +219,31 @@ protected function createComponentSignInForm(): Form {/embed} ``` -Вам потрібно буде імпортувати блок тільки в одному місці, на початку шаблону макета: +При цьому блок достатньо імпортувати лише в одному місці, а саме на початку шаблону layout: ```latte {import basic-form.latte} ``` -Особливі випадки .[#toc-special-cases] --------------------------------------- +Спеціальні випадки +------------------ -Якщо вам потрібно відобразити тільки внутрішній вміст форми без `<form>` & `</form>` HTML тегів, наприклад, в AJAX запиті, ви можете відкривати і закривати форму за допомогою `{formContext} … {/formContext}`. Він працює аналогічно `{form}` в логічному сенсі, тут він дозволяє вам використовувати інші теги для малювання елементів форми, але в той же час він нічого не малює. +Якщо потрібно відобразити лише внутрішню частину форми без HTML-тегів `<form>`, наприклад, при надсиланні сніпетів, приховайте їх за допомогою атрибута `n:tag-if`: ```latte -{formContext signForm} +<form n:name=signInForm n:tag-if=false> <div> - <label n:name=username>Имя пользователя: <input n:name=username></label> + <label n:name=username>Username: <input n:name=username></label> {inputError username} </div> -{/formContext} +</form> ``` -Тег `formContainer` допомагає при відтворенні даних, що вводяться, всередині контейнера форми. +З відображенням елементів усередині контейнера форми допоможе тег `{formContainer}`. ```latte -<p>Which news you wish to receive:</p> +<p>Які новини ви бажаєте отримувати:</p> {formContainer emailNews} <ul> @@ -262,27 +254,27 @@ protected function createComponentSignInForm(): Form ``` -Рендеринг без латте .[#toc-rendering-without-latte] -=================================================== +Відображення без Latte +====================== -Найпростіший спосіб відобразити форму - викликати: +Найпростіший спосіб відобразити форму — викликати: ```php $form->render(); ``` -Зовнішній вигляд відмальованої форми можна змінити, налаштувавши [Renderer |#Renderer] і [окремі елементи керування |#HTML-Attributes]. +Вплинути на вигляд так відображеної форми можна за допомогою конфігурації [#Renderer] та [окремих елементів |#HTML атрибути]. -Рендеринг вручну .[#toc-manual-rendering] ------------------------------------------ +Ручне відображення +------------------ -Кожен елемент форми має методи, які генерують HTML код для поля і мітки форми. Вони можуть повертати його у вигляді рядка або об'єкта [Nette\Utils\Html |utils:html-elements]: +Кожен елемент форми має методи, які генерують HTML-код поля форми та мітки. Вони можуть повертати його або як рядок, або як об'єкт [Nette\Utils\Html|utils:html-elements]: - `getControl(): Html|string` повертає HTML-код елемента -- `getLabel($caption = null): Html|string|null` повертає HTML-код мітки, якщо така є +- `getLabel($caption = null): Html|string|null` повертає HTML-код мітки, якщо вона існує -Це дає змогу відображати форму елемент за елементом: +Таким чином, форму можна відображати по окремих елементах: ```php <?php $form->render('begin') ?> @@ -305,47 +297,46 @@ $form->render(); <?php $form->render('end') ?> ``` -Тоді як для деяких елементів `getControl()` повертає один HTML-елемент (наприклад. `<input>`, `<select>` тощо), для інших він повертає цілий шматок HTML-коду (CheckboxList, RadioList). -У цьому випадку можна використовувати методи, що генерують окремі входи і мітки, для кожного елемента окремо: +У той час як для деяких елементів `getControl()` повертає єдиний HTML-елемент (наприклад, `<input>`, `<select>` тощо), для інших — цілий шматок HTML-коду (CheckboxList, RadioList). У такому випадку ви можете використовувати методи, які генерують окремі інпути та мітки для кожного пункту окремо: -- `getControlPart($key = null): ?Html` повертає HTML-код одного елемента. -- `getLabelPart($key = null): ?Html` повертає HTML-код для мітки окремого елемента +- `getControlPart($key = null): ?Html` повертає HTML-код одного пункту +- `getLabelPart($key = null): ?Html` повертає HTML-код мітки одного пункту .[note] -Ці методи мають префікс `get` з історичних причин, але `generate` було б краще, тому що він створює і повертає новий елемент `Html` при кожному виклику. +Ці методи з історичних причин мають префікс `get`, але краще було б `generate`, оскільки при кожному виклику вони створюють і повертають новий елемент `Html`. -Рендерер .[#toc-renderer] -========================= +Renderer +======== -Це об'єкт, що забезпечує рендеринг форми. Він може бути встановлений методом `$form->setRenderer`. Йому передається керування при виклику методу `$form->render()`. +Це об'єкт, що забезпечує відображення форми. Його можна встановити за допомогою методу `$form->setRenderer`. Йому передається управління при виклику методу `$form->render()`. -Якщо ми не задамо користувацький рендерер, буде використовуватися рендерер за замовчуванням [api:Nette\Forms\Rendering\DefaultFormRenderer]. У результаті елементи форми будуть відображені у вигляді HTML-таблиці. Виведення має такий вигляд: +Якщо ми не встановимо власний рендерер, буде використано стандартний рендерер [api:Nette\Forms\Rendering\DefaultFormRenderer]. Він відображає елементи форми у вигляді HTML-таблиці. Вивід виглядає так: ```latte <table> <tr class="required"> - <th><label class="required" for="frm-name">Name:</label></th> + <th><label class="required" for="frm-name">Ім'я:</label></th> <td><input type="text" class="text" name="name" id="frm-name" required value=""></td> </tr> <tr class="required"> - <th><label class="required" for="frm-age">Age:</label></th> + <th><label class="required" for="frm-age">Вік:</label></th> <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> </tr> <tr> - <th><label>Gender:</label></th> + <th><label>Стать:</label></th> ... ``` -Використовувати таблицю чи ні - вирішувати вам, багато веб-дизайнерів віддають перевагу іншій розмітці, наприклад, списку. Ми можемо налаштувати `DefaultFormRenderer` так, щоб він взагалі не виводився в таблицю. Ми просто повинні встановити відповідні [$wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers]. Перший індекс завжди представляє область, а другий - елемент. Усі відповідні області показано на малюнку: +Використовувати чи не використовувати таблицю для каркасу форми — питання спірне, і багато вебдизайнерів віддають перевагу іншій розмітці. Наприклад, списку визначень. Тому ми переконфігуруємо `DefaultFormRenderer` так, щоб він відображав форму у вигляді списку. Конфігурація здійснюється редагуванням масиву [$wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers]. Перший індекс завжди представляє область, а другий — її атрибут. Окремі області зображені на малюнку: -[* form-areas-en.webp *] +[* defaultformrenderer.webp *] -За замовчуванням група `controls` обгорнута в. `<table>`і кожен `pair` являє собою рядок таблиці `<tr>` що містить пару `label` і `control` (комірки `<th>` и `<td>`). Давайте змінимо всі ці елементи обгортки. Ми загорнемо `controls` у `<dl>`, залишимо `pair` на самоті, помістимо `label` у `<dt>` і обернемо `control` в `<dd>`: +Стандартно група елементів `controls` обгортається таблицею `<table>`, кожен `pair` представляє рядок таблиці `<tr>`, а пара `label` і `control` є комірками `<th>` і `<td>`. Тепер ми змінимо обгортаючі елементи. Область `controls` вставимо в контейнер `<dl>`, область `pair` залишимо без контейнера, `label` вставимо в `<dt>` і, нарешті, `control` обгорнемо тегами `<dd>`: ```php $renderer = $form->getRenderer(); @@ -357,193 +348,192 @@ $renderer->wrappers['control']['container'] = 'dd'; $form->render(); ``` -У результаті вийде наступний фрагмент: +Результатом є такий HTML-код: ```latte <dl> - <dt><label class="required" for="frm-name">Name:</label></dt> + <dt><label class="required" for="frm-name">Ім'я:</label></dt> <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> - <dt><label class="required" for="frm-age">Age:</label></dt> + <dt><label class="required" for="frm-age">Вік:</label></dt> <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> - <dt><label>Gender:</label></dt> + <dt><label>Стать:</label></dt> ... </dl> ``` -Обгортки можуть впливати на багато атрибутів. Наприклад: +У масиві wrappers можна вплинути на цілу низку інших атрибутів: -- додати спеціальні класи CSS до кожного введення форми -- розрізняти парні та непарні рядки -- по-різному малювати обов'язкові та необов'язкові елементи -- встановити, чи будуть повідомлення про помилки показуватися над формою або поруч із кожним елементом +- додавати CSS-класи окремим типам елементів форми +- розрізняти CSS-класом парні та непарні рядки +- візуально розрізняти обов'язкові та необов'язкові елементи +- визначати, чи відображатимуться повідомлення про помилки безпосередньо біля елементів чи над формою -Опції .[#toc-options] ---------------------- +Options +------- -Поведінкою Renderer також можна керувати, встановлюючи *опції* для окремих елементів форми. Таким чином, ви можете встановити спливаючу підказку, яка відображатиметься поруч із полем введення: +Поведінку Renderer можна контролювати також встановленням *options* на окремих елементах форми. Таким чином можна встановити опис, який буде виведений поруч із полем введення: ```php $form->addText('phone', 'Номер:') - ->setOption('description', 'Этот номер останется скрытым'); + ->setOption('description', 'Цей номер залишиться прихованим'); ``` -Якщо ми хочемо помістити в нього HTML-контент, ми використовуємо клас [Html |utils:html-elements]. +Якщо ми хочемо розмістити в ньому HTML-вміст, використаємо клас [Html |utils:html-elements] ```php use Nette\Utils\Html; -$form->addText('phone', 'Телефон:') +$form->addText('phone', 'Номер:') ->setOption('description', Html::el('p') - ->setHtml('<a href="...">Условия предоставления услуг.</a>') + ->setHtml('<a href="...">Умови зберігання Вашого номера</a>') ); ``` .[tip] -Html-елемент також можна використовувати замість label: `$form->addCheckbox('conditions', $label)`. +Елемент Html можна використовувати також замість мітки: `$form->addCheckbox('conditions', $label)`. -Групування входів .[#toc-grouping-inputs] ------------------------------------------ +Групування елементів +-------------------- -Поля введення можна об'єднати в набори візуальних полів, створивши групу: +Renderer дозволяє групувати елементи у візуальні групи (fieldset): ```php -$form->addGroup('Персональные данные'); +$form->addGroup('Особисті дані'); ``` -Створення нової групи активує її - усі додані далі елементи додаються в цю групу. Ви можете побудувати форму таким чином: +Після створення нової групи вона стає активною, і кожен новододаний елемент одночасно додається і до неї. Тож форму можна будувати таким чином: ```php $form = new Form; -$form->addGroup('Персональные данные'); -$form->addText('name', 'Ваше имя:'); -$form->addInteger('age', 'Ваш возраст:'); -$form->addEmail('email', 'Имейл:'); +$form->addGroup('Особисті дані'); +$form->addText('name', 'Ваше ім\'я:'); +$form->addInteger('age', 'Ваш вік:'); +$form->addEmail('email', 'Email:'); -$form->addGroup('Адрес доставки'); -$form->addCheckbox('send', 'Отправить по адресу'); -$form->addText('street', 'Улица:'); -$form->addText('city', 'Город:'); -$form->addSelect('country', 'Страна:', $countries); +$form->addGroup('Адреса доставки'); +$form->addCheckbox('send', 'Надіслати на адресу'); +$form->addText('street', 'Вулиця:'); +$form->addText('city', 'Місто:'); +$form->addSelect('country', 'Країна:', $countries); ``` +Renderer спочатку відображає групи, а потім елементи, які не належать до жодної групи. -Підтримка Bootstrap .[#toc-bootstrap-support] ---------------------------------------------- -Ви можете знайти [приклади |https://github.com/nette/forms/tree/master/examples] налаштування рендерера для [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], Bootstrap [3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] і [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php] +Підтримка Bootstrap +------------------- + +[У прикладах |https://github.com/nette/forms/tree/master/examples] ви знайдете приклади, як налаштувати Renderer для [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], [Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] та [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php] -Атрибути HTML .[#toc-html-attributes] -===================================== +HTML атрибути +============= -Ви можете встановити будь-які атрибути HTML для елементів управління форми за допомогою `setHtmlAttribute(string $name, $value = true)`: +Для встановлення будь-яких HTML-атрибутів елементів форми використовуємо метод `setHtmlAttribute(string $name, $value = true)`: ```php -$form->addInteger('number', 'Кількість:') - ->setHtmlAttribute('class', 'bigNumbers'); +$form->addInteger('number', 'Число:') + ->setHtmlAttribute('class', 'big-number'); -$form->addSelect('rank', 'Замовити:', ['price', 'name']) - ->setHtmlAttribute('onchange', 'submit()'); // викликає JS-функцію submit() у разі зміни +$form->addSelect('rank', 'Сортувати за:', ['ціною', 'назвою']) + ->setHtmlAttribute('onchange', 'submit()'); // при зміні надіслати -// застосовується на <form> +// Для встановлення атрибутів самого <form> $form->setHtmlAttribute('id', 'myForm'); ``` -Встановлення типу входу: +Специфікація типу елемента: ```php $form->addText('tel', 'Ваш телефон:') ->setHtmlType('tel') - ->setHtmlAttribute('placeholder', 'Пожалуйста, заполните ваш телефон'); + ->setHtmlAttribute('placeholder', 'напишіть телефон'); ``` -Ми можемо встановити HTML-атрибут для окремих елементів у списках радіо- або чекбоксів із різними значеннями для кожного з них. -Зверніть увагу на двокрапку після `style:`, щоб переконатися, що значення вибирається за ключем: +.[warning] +Встановлення типу та інших атрибутів служить лише для візуальних цілей. Перевірка правильності введення має відбуватися на сервері, що забезпечується вибором відповідного [елемента форми|controls] та зазначенням [правил валідації|validation]. + +Окремим пунктам у списках radio або checkbox ми можемо встановити HTML-атрибут з різними значеннями для кожного з них. Зверніть увагу на двокрапку після `style:`, яка забезпечує вибір значення за ключем: ```php -$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue']; +$colors = ['r' => 'червоний', 'g' => 'зелений', 'b' => 'синій']; $styles = ['r' => 'background:red', 'g' => 'background:green']; -$form->addCheckboxList('colors', 'Цвета:', $colors) +$form->addCheckboxList('colors', 'Кольори:', $colors) ->setHtmlAttribute('style:', $styles); ``` -Відображається як: +Виведе: ```latte -<label><input type="checkbox" name="colors[]" style="background:red" value="r">red</label> -<label><input type="checkbox" name="colors[]" style="background:green" value="g">green</label> -<label><input type="checkbox" name="colors[]" value="b">blue</label> +<label><input type="checkbox" name="colors[]" style="background:red" value="r">червоний</label> +<label><input type="checkbox" name="colors[]" style="background:green" value="g">зелений</label> +<label><input type="checkbox" name="colors[]" value="b">синій</label> ``` -Для логічного атрибута HTML (який не має значення, наприклад, `readonly`) можна використовувати знак питання: +Для встановлення логічних атрибутів, таких як `readonly`, ми можемо використовувати запис зі знаком питання: ```php -$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue']; $form->addCheckboxList('colors', 'Кольори:', $colors) - ->setHtmlAttribute('readonly?', 'r'); // використовувати масив для кількох ключів, наприклад ['r', 'g'] + ->setHtmlAttribute('readonly?', 'r'); // для кількох ключів використовуйте масив, напр. ['r', 'g'] ``` -Відображається як: +Виведе: ```latte -<label><input type="checkbox" name="colors[]" readonly value="r">red</label> -<label><input type="checkbox" name="colors[]" value="g">green</label> -<label><input type="checkbox" name="colors[]" value="b">blue</label> +<label><input type="checkbox" name="colors[]" readonly value="r">червоний</label> +<label><input type="checkbox" name="colors[]" value="g">зелений</label> +<label><input type="checkbox" name="colors[]" value="b">синій</label> ``` -Для селекбоксів метод `setHtmlAttribute()` встановлює атрибути елемента `<select>`. Якщо ми хочемо встановити атрибути для кожного -`<option>`, ми будемо використовувати метод `setOptionAttribute()`. Крім того, двокрапка і знак питання, використані вище, працюють: +У випадку selectbox метод `setHtmlAttribute()` встановлює атрибути елемента `<select>`. Якщо ми хочемо встановити атрибути окремим `<option>`, використовуємо метод `setOptionAttribute()`. Також працюють записи з двокрапкою та знаком питання, зазначені вище: ```php -$form->addSelect('colors', 'Цвета:', $colors) +$form->addSelect('colors', 'Кольори:', $colors) ->setOptionAttribute('style:', $styles); ``` -Відображається як: +Виведе: ```latte <select name="colors"> - <option value="r" style="background:red">red</option> - <option value="g" style="background:green">green</option> - <option value="b">blue</option> + <option value="r" style="background:red">червоний</option> + <option value="g" style="background:green">зелений</option> + <option value="b">синій</option> </select> ``` -Прототипи .[#toc-prototypes] ----------------------------- +Прототипи +--------- -Альтернативним способом встановлення атрибутів HTML є зміна шаблону, на основі якого генерується елемент HTML. Шаблон є об'єктом `Html` і повертається методом `getControlPrototype()`: +Альтернативний спосіб встановлення HTML-атрибутів полягає в модифікації шаблону, з якого генерується HTML-елемент. Шаблоном є об'єкт `Html`, і його повертає метод `getControlPrototype()`: ```php -$input = $form->addInteger('number'); +$input = $form->addInteger('number', 'Число:'); $html = $input->getControlPrototype(); // <input> $html->class('big-number'); // <input class="big-number"> ``` -Шаблон мітки, що повертається методом `getLabelPrototype()`, також може бути змінений таким чином: +Таким чином можна модифікувати й шаблон мітки, який повертає `getLabelPrototype()`: ```php $html = $input->getLabelPrototype(); // <label> $html->class('distinctive'); // <label class="distinctive"> ``` -Для елементів Checkbox, CheckboxList і RadioList ви можете вплинути на шаблон елемента, який обертає елемент. Його повертає `getContainerPrototype()`. За замовчуванням це "порожній" елемент, тому нічого не відображається, але якщо дати йому ім'я, він буде відображатися: +Для елементів Checkbox, CheckboxList та RadioList ви можете вплинути на шаблон елемента, який обгортає весь елемент. Його повертає `getContainerPrototype()`. За замовчуванням це «порожній» елемент, тому нічого не відображається, але якщо ми встановимо йому назву, він буде відображатися: ```php $input = $form->addCheckbox('send'); -echo $input->getControl(); -// <label><input type="checkbox" name="send"></label> - $html = $input->getContainerPrototype(); $html->setName('div'); // <div> $html->class('check'); // <div class="check"> @@ -551,50 +541,49 @@ echo $input->getControl(); // <div class="check"><label><input type="checkbox" name="send"></label></div> ``` -У випадку CheckboxList і RadioList можна також вплинути на шаблон роздільника елементів, що повертається методом `getSeparatorPrototype()`. За замовчуванням це елемент `<br>`. Якщо ви зміните його на парний елемент, він буде обволікати окремі елементи замість того, щоб розділяти їх. -Також можна впливати на шаблон HTML-елемента міток елементів, який повертає метод `getItemLabelPrototype()`. +У випадку CheckboxList та RadioList можна також вплинути на шаблон роздільника окремих пунктів, який повертає метод `getSeparatorPrototype()`. За замовчуванням це елемент `<br>`. Якщо ви зміните його на парний елемент, він буде обгортати окремі пункти замість того, щоб розділяти їх. А також можна вплинути на шаблон HTML-елемента мітки біля окремих пунктів, який повертає `getItemLabelPrototype()`. -Переклад .[#toc-translating] -============================ +Переклад +======== -Якщо ви програмуєте багатомовний додаток, вам, ймовірно, знадобиться рендерити форму різними мовами. Для цього в Nette Framework передбачено інтерфейс перекладу [api:Nette\Localization\Translator]. У Nette немає реалізації за замовчуванням, ви можете вибрати відповідно до ваших потреб з декількох готових рішень, які ви можете знайти на [Componette |https://componette.org/search/localization]. Їх документація підкаже вам, як налаштувати перекладач. +Якщо ви програмуєте багатомовний додаток, вам, ймовірно, знадобиться відображати форму різними мовними версіями. Nette Framework для цієї мети визначає інтерфейс для перекладу [api:Nette\Localization\Translator]. У Nette немає стандартної реалізації, ви можете вибрати відповідно до своїх потреб з кількох готових рішень, які знайдете на [Componette |https://componette.org/search/localization]. У їхній документації ви дізнаєтеся, як конфігурувати перекладач. -Форма підтримує виведення тексту через перекладач. Ми передаємо його за допомогою методу `setTranslator()`: +Форми підтримують виведення текстів через перекладач. Ми передаємо його їм за допомогою методу `setTranslator()`: ```php $form->setTranslator($translator); ``` -Відтепер не тільки всі підписи, але й усі повідомлення про помилки або значення у вибраних полях будуть перекладені на іншу мову. +З цього моменту не тільки всі мітки, але й усі повідомлення про помилки або пункти select box перекладаються іншою мовою. -Ви можете встановити інший перекладач для окремих елементів форми або повністю вимкнути переклад за допомогою `null`: +Для окремих елементів форми при цьому можна встановити інший перекладач або повністю вимкнути переклад значенням `null`: ```php -$form->addSelect('carModel', 'Model:', $cars) +$form->addSelect('carModel', 'Модель:', $cars) ->setTranslator(null); ``` -Для [правил валідації |validation] перекладачеві також передаються певні параметри, наприклад, для правила: +Для [правил валідації|validation] перекладачу передаються також специфічні параметри, наприклад, для правила: ```php -$form->addPassword('password', 'Password:') - ->addRule($form::MinLength, 'Password has to be at least %d characters long', 8) +$form->addPassword('password', 'Пароль:') + ->addRule($form::MinLength, 'Пароль повинен мати щонайменше %d символів', 8); ``` -викликається транслятор з наступними параметрами: +викликається перекладач з такими параметрами: ```php -$translator->translate('Password has to be at least %d characters long', 8); +$translator->translate('Пароль повинен мати щонайменше %d символів', 8); ``` -і таким чином може вибрати правильну форму множини для слова `characters` за підрахунком. +і таким чином може вибрати правильну форму множини для слова `символів` залежно від кількості. -Подія onRender .[#toc-event-onrender] -===================================== +Подія onRender +============== -Безпосередньо перед відображенням форми ми можемо викликати наш код. Це може, наприклад, додати HTML-класи до елементів форми для правильного відображення. Додамо код у масив 'onRender': +Безпосередньо перед тим, як форма буде відображена, ми можемо викликати наш код. Він може, наприклад, додати елементам форми HTML-класи для правильного відображення. Код додаємо до масиву `onRender`: ```php $form->onRender[] = function ($form) { diff --git a/forms/uk/standalone.texy b/forms/uk/standalone.texy index ccd1872f46..2c3f33bcf5 100644 --- a/forms/uk/standalone.texy +++ b/forms/uk/standalone.texy @@ -1,43 +1,43 @@ -Форми, що використовуються автономно -************************************ +Форми, що використовуються окремо +********************************* .[perex] -Nette Forms значно спрощує створення та обробку веб-форм. Ви можете використовувати їх у своїх додатках абсолютно самостійно, без решти фреймворку, що ми й продемонструємо в цьому розділі. +Nette Forms значно полегшують створення та обробку веб-форм. Ви можете використовувати їх у своїх програмах абсолютно окремо від решти фреймворку, що ми покажемо в цьому розділі. -Однак якщо ви використовуєте додаток Nette і презентери, для вас є посібник: [Форми в презентерах |in-presenter]. +Однак, якщо ви використовуєте Nette Application та презентери, для вас призначений посібник для [використання в презентерах|in-presenter]. -Перша форма .[#toc-first-form] -============================== +Перша форма +=========== -Ми постараємося написати просту реєстраційну форму. Його код матиме такий вигляд ("повний код":https://gist.github.com/dg/370a7e3094d9ba9a9e913b8e2a2dc851): +Спробуємо написати просту реєстраційну форму. Її код буде таким ("повний код":https://gist.github.com/dg/57878c1a413ae8ef0c1d83f02c43ef3f): ```php use Nette\Forms\Form; $form = new Form; -$form->addText('name', 'Имя:'); +$form->addText('name', 'Ім\'я:'); $form->addPassword('password', 'Пароль:'); -$form->addSubmit('send', 'Зарегистрироваться'); +$form->addSubmit('send', 'Зареєструватися'); ``` -І давайте зробимо рендеринг: +Дуже легко її відобразимо: ```php $form->render(); ``` -і результат має виглядати наступним чином: +і в браузері вона зобразиться так: -[* form-en.webp *] +[* form-cs.webp *] -Форма є об'єктом класу `Nette\Forms\Form` (клас `Nette\Application\UI\Form` використовується в презентерах). Ми додали в нього ім'я елемента управління, пароль і кнопку відправлення. +Форма — це об'єкт класу `Nette\Forms\Form` (клас `Nette\Application\UI\Form` використовується в презентерах). Ми додали до неї так звані елементи: ім'я, пароль та кнопку відправки. -Тепер ми оживимо форму. Запитавши `$form->isSuccess()`, ми дізнаємося, чи була форма надіслана і чи правильно вона була заповнена. Якщо так, то ми скинемо дані. Після визначення форми додамо: +А тепер оживимо форму. За допомогою запиту `$form->isSuccess()` ми дізнаємося, чи була форма відправлена і чи була вона заповнена валідно. Якщо так, виведемо дані. Отже, після визначення форми доповнимо: ```php if ($form->isSuccess()) { - echo 'Форма була заповнена і відправлена правильно'; + echo 'Форма була правильно заповнена та відправлена'; $data = $form->getValues(); // $data->name містить ім'я // $data->password містить пароль @@ -45,32 +45,32 @@ if ($form->isSuccess()) { } ``` -Метод `getValues()` повертає відправлені дані у вигляді об'єкта [ArrayHash |utils:arrays#ArrayHash]. Ми покажемо, як це змінити [пізніше |#Mapping-to-Classes]. Змінна `$data` містить ключі `name` і `password` з даними, введеними користувачем. +Метод `getValues()` повертає відправлені дані у вигляді об'єкта [ArrayHash |utils:arrays#ArrayHash]. Як це змінити, ми покажемо [пізніше |#Мапінг на класи]. Об'єкт `$data` містить ключі `name` та `password` з даними, які ввів користувач. -Зазвичай ми надсилаємо дані безпосередньо для подальшого опрацювання, яке може бути, наприклад, вставкою в базу даних. Однак у процесі обробки може виникнути помилка, наприклад, якщо ім'я користувача вже зайнято. У цьому випадку ми передаємо помилку назад у форму за допомогою `addError()` і дозволяємо їй перемальовуватися заново, з повідомленням про помилку: +Зазвичай дані одразу надсилаються для подальшої обробки, наприклад, вставки в базу даних. Однак під час обробки може виникнути помилка, наприклад, ім'я користувача вже зайняте. У такому випадку ми передаємо помилку назад у форму за допомогою `addError()` і дозволяємо їй відобразитися знову, вже з повідомленням про помилку. ```php -$form->addError('Извините, имя пользователя уже используется.'); +$form->addError('Вибачте, це ім\'я користувача вже використовується.'); ``` -Після обробки форми ми перенаправимо вас на наступну сторінку. Це запобігає ненавмисному повторному надсиланню форми під час натискання кнопки *оновити*, *назад* або переміщення по історії браузера. +Після обробки форми перенаправимо на наступну сторінку. Це запобігає небажаному повторному відправленню форми кнопкою *оновити*, *назад* або рухом в історії браузера. -За замовчуванням форма надсилається методом POST на ту саму сторінку. І те, і інше можна змінити: +Форма стандартно надсилається методом POST на ту саму сторінку. Обидва параметри можна змінити: ```php $form->setAction('/submit.php'); $form->setMethod('GET'); ``` -І це все :-) У нас є функціональна та ідеально [захищена |#Защита от уязвимостей] форма. +І це, власне, все :-) Ми маємо функціональну та ідеально [захищену |#Захист від вразливостей] форму. -Спробуйте додати більше [елементів керування формою |controls]. +Спробуйте додати й інші [елементи форми|controls]. -Доступ до елементів керування .[#toc-access-to-controls] -======================================================== +Доступ до елементів +=================== -Форма та її окремі елементи керування називаються компонентами. Вони створюють дерево компонентів, коренем якого є форма. Доступ до окремих елементів керування можна отримати в такий спосіб: +Форму та її окремі елементи ми називаємо компонентами. Вони утворюють дерево компонентів, де коренем є саме форма. До окремих елементів форми можна отримати доступ таким чином: ```php $input = $form->getComponent('name'); @@ -80,37 +80,36 @@ $button = $form->getComponent('send'); // альтернативний синтаксис: $button = $form['send']; ``` -Елементи керування видаляються за допомогою функції unset: +Елементи видаляються за допомогою unset: ```php unset($form['name']); ``` -Правила валідації .[#toc-validation-rules] -========================================== +Правила валідації +================= -Слово *valid* було використано кілька разів, але форма ще не має правил валідації. Давайте виправимо це. +Тут прозвучало слово *валідна*, але форма поки що не має жодних правил валідації. Давайте це виправимо. -Ім'я буде обов'язковим, тому ми позначимо його методом `setRequired()`, аргументом якого є текст повідомлення про помилку, яке буде виведено, якщо користувач не заповнить його. Якщо аргумент не вказано, використовується повідомлення про помилку за замовчуванням. +Ім'я буде обов'язковим, тому позначимо його методом `setRequired()`, аргументом якого є текст повідомлення про помилку, яке відобразиться, якщо користувач не введе ім'я. Якщо аргумент не вказано, використовується стандартне повідомлення про помилку. ```php -$form->addText('name', 'Имя:') - ->setRequired('Пожалуйста, введите имя.'); +$form->addText('name', 'Ім\'я:') + ->setRequired('Будь ласка, введіть ім\'я'); ``` -Спробуйте надіслати форму без заповненого імені, і ви побачите, що з'явиться повідомлення про помилку, і браузер або сервер відхилятиме форму, поки ви не заповните її. +Спробуйте відправити форму без заповненого імені, і ви побачите, що з'явиться повідомлення про помилку, а браузер чи сервер відхилятимуть її доти, доки ви не заповните поле. -Водночас ви не зможете обдурити систему, набравши в полі введення, наприклад, тільки пробіли. Ні за що. Nette автоматично обрізає ліві та праві пробільні символи. Спробуйте. Це те, що ви завжди повинні робити з кожним однорядковим введенням, але про це часто забувають. Nette робить це автоматично. (Ви можете спробувати обдурити форму і відправити багаторядковий рядок як ім'я. Навіть тут Nette не обдурять, і переноси рядків будуть замінені на пробіли). +Водночас ви не обдурите систему, написавши в полі, наприклад, лише пробіли. Ні. Nette автоматично видаляє пробіли зліва та справа. Спробуйте самі. Це те, що ви завжди повинні робити з кожним однорядковим полем введення, але часто про це забувають. Nette робить це автоматично. (Можете спробувати обдурити форму і надіслати як ім'я багаторядковий рядок. Навіть тут Nette не дасть себе обдурити і змінить переноси рядків на пробіли.) -Форма завжди перевіряється на стороні сервера, але також генерується перевірка JavaScript, що відбувається швидко, і користувач одразу ж дізнається про помилку, без необхідності відправляти форму на сервер. Цим займається скрипт `netteForms.js`. -Додайте його на сторінку: +Форма завжди валідується на стороні сервера, але також генерується JavaScript валідація, яка відбувається миттєво, і користувач дізнається про помилку одразу, без необхідності надсилати форму на сервер. За це відповідає скрипт `netteForms.js`. Вставте його на сторінку: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Якщо ви подивитеся у вихідний код сторінки з формою, ви можете помітити, що Nette вставляє обов'язкові поля в елементи з CSS-класом `required`. Спробуйте додати наступний стиль у шаблон, і мітка "Ім'я" буде червоного кольору. Ми елегантно позначаємо обов'язкові поля для користувачів: +Якщо ви подивитеся на вихідний код сторінки з формою, ви помітите, що Nette вставляє обов'язкові елементи в елементи з CSS-класом `required`. Спробуйте додати до шаблону наступну таблицю стилів, і мітка «Ім'я» стане червоною. Таким чином, ми елегантно позначаємо для користувачів обов'язкові елементи: ```latte <style> @@ -118,119 +117,121 @@ $form->addText('name', 'Имя:') </style> ``` -Додаткові правила валідації будуть додані методом `addRule()`. Першим параметром є правило, другим - текст повідомлення про помилку, далі може йти необов'язковий аргумент правила перевірки. Що це означає? +Інші правила валідації додамо методом `addRule()`. Перший параметр — це правило, другий — знову текст повідомлення про помилку, а третім може бути аргумент правила валідації. Що це означає? -Форма отримає ще один необов'язковий елемент введення *age* з умовою, що він має бути числом (`addInteger()`) і перебувати в певних межах (`$form::Range`). І тут ми будемо використовувати третій аргумент `addRule()`, сам діапазон: +Розширимо форму новим необов'язковим полем «вік», яке має бути цілим числом (`addInteger()`) і, крім того, у допустимому діапазоні (`$form::Range`). І саме тут ми використаємо третій параметр методу `addRule()`, яким передамо валідатору потрібний діапазон як пару `[від, до]`: ```php -$form->addInteger('age', 'Возраст:') - ->addRule($form::Range, 'Вы должны быть старше 18 лет и иметь возраст до 120 лет.', [18, 120]); +$form->addInteger('age', 'Вік:') + ->addRule($form::Range, 'Вік має бути від 18 до 120', [18, 120]); ``` .[tip] -Якщо користувач не заповнить поле, правила валідації не будуть перевірені, оскільки поле є необов'язковим. +Якщо користувач не заповнить поле, правила валідації не перевірятимуться, оскільки елемент є необов'язковим. -Очевидно, що тут є місце для невеликого рефакторингу. У повідомленні про помилку і в третьому параметрі числа перераховані у двох екземплярах, що не ідеально. Якби ми створювали [багатомовну форму |rendering#translating] і повідомлення, що містить числа, довелося б перекладати кількома мовами, це ускладнило б зміну значень. З цієї причини можна використовувати символи-замінники `%d`: +Тут виникає простір для невеликого рефакторингу. У повідомленні про помилку та в третьому параметрі числа вказані дубльовано, що не ідеально. Якби ми створювали [багатомовні форми |rendering#Переклад] і повідомлення, що містить числа, було б перекладено кількома мовами, це ускладнило б можливу зміну значень. З цієї причини можна використовувати плейсхолдери `%d`, і Nette доповнить значення: ```php - ->addRule($form::Range, 'Вы должны быть старше %d лет и иметь возраст до %d лет.', [18, 120]); + ->addRule($form::Range, 'Вік має бути від %d до %d років', [18, 120]); ``` -Повернемося до поля *пароль*, зробимо його *обов'язковим* і перевіримо мінімальну довжину пароля (`$form::MinLength`), знову використовуючи символи-замінники в повідомленні: +Повернемося до елемента `password`, який також зробимо обов'язковим і ще перевіримо мінімальну довжину пароля (`$form::MinLength`), знову ж таки з використанням плейсхолдера: ```php $form->addPassword('password', 'Пароль:') - ->setRequired('Выберите пароль') - ->addRule($form::MinLength, 'Ваш пароль должен быть длиной не менее %d', 8); + ->setRequired('Виберіть пароль') + ->addRule($form::MinLength, 'Пароль повинен мати щонайменше %d символів', 8); ``` -Ми додамо у форму поле `passwordVerify`, в якому користувач вводить пароль ще раз, для перевірки. Використовуючи правила валідації, ми перевіряємо, чи однакові обидва паролі (`$form::Equal`). А як аргумент ми даємо посилання на перший пароль, використовуючи [квадратні дужки |#Access-to-Controls]: +Додамо до форми ще поле `passwordVerify`, де користувач введе пароль ще раз для перевірки. За допомогою правил валідації перевіримо, чи обидва паролі однакові (`$form::Equal`). А як параметр дамо посилання на перший пароль за допомогою [квадратних дужок |#Доступ до елементів]: ```php -$form->addPassword('passwordVerify', 'Повторите пароль:') - ->setRequired('Введите пароль ещё раз, чтобы проверить опечатку') - ->addRule($form::Equal, 'Несоответствие пароля', $form['password']) +$form->addPassword('passwordVerify', 'Пароль для перевірки:') + ->setRequired('Будь ласка, введіть пароль ще раз для перевірки') + ->addRule($form::Equal, 'Паролі не співпадають', $form['password']) ->setOmitted(); ``` -Використовуючи `setOmitted()`, ми позначили елемент, значення якого нас не особливо хвилює і який існує тільки для перевірки. Його значення не передається в `$data`. +За допомогою `setOmitted()` ми позначили елемент, значення якого насправді не має значення і який існує лише для валідації. Значення не передається до `$data`. -У нас є повнофункціональна форма з валідацією на PHP і JavaScript. Можливості валідації в Nette набагато ширші, ви можете створювати умови, відображати і приховувати частини сторінки відповідно до них тощо. Ви можете дізнатися про все в розділі [Валідація форм |validation]. +Таким чином, ми маємо готову повнофункціональну форму з валідацією в PHP та JavaScript. Можливості валідації Nette набагато ширші, можна створювати умови, дозволяти на їх основі показувати та приховувати частини сторінки тощо. Все це ви дізнаєтеся в розділі про [валідацію форм|validation]. -Значення за замовчуванням .[#toc-default-values] -================================================ +Значення за замовчуванням +========================= -Ми часто встановлюємо значення за замовчуванням для елементів керування форми: +Елементам форми зазвичай встановлюємо значення за замовчуванням: ```php -$form->addEmail('email', 'Имейл') +$form->addEmail('email', 'E-mail') ->setDefaultValue($lastUsedEmail); ``` -Часто буває корисно встановити значення за замовчуванням одразу для всіх елементів керування. Наприклад, коли форма використовується для редагування записів. Ми зчитуємо запис із бази даних і встановлюємо його як значення за замовчуванням: +Часто буває зручно встановити значення за замовчуванням для всіх елементів одночасно. Наприклад, коли форма служить для редагування записів. Читаємо запис з бази даних і встановлюємо значення за замовчуванням: ```php //$row = ['name' => 'John', 'age' => '33', /* ... */]; $form->setDefaults($row); ``` -Викличте `setDefaults()` після визначення елементів керування. +Викликайте `setDefaults()` після визначення елементів. -Відображення форми .[#toc-rendering-the-form] -============================================= +Відображення форми +================== -За замовчуванням форма відображається у вигляді таблиці. Окремі елементи керування дотримуються основних рекомендацій щодо забезпечення доступності веб-сторінок. Усі мітки генеруються як елементи `<label>` і пов'язані зі своїми елементами, клацання по мітці переміщує курсор на відповідний елемент. +Стандартно форма відображається як таблиця. Окремі елементи відповідають основному правилу доступності — всі мітки записані як `<label>` і пов'язані з відповідним елементом форми. При кліку на мітку курсор автоматично з'являється у полі форми. -Ми можемо встановити будь-які атрибути HTML для кожного елемента. Наприклад, додайте заповнювач: +Кожному елементу ми можемо встановлювати будь-які HTML-атрибути. Наприклад, додати placeholder: ```php -$form->addInteger('age', 'Возраст:') - ->setHtmlAttribute('placeholder', 'Пожалуйста, заполните возраст'); +$form->addInteger('age', 'Вік:') + ->setHtmlAttribute('placeholder', 'Будь ласка, заповніть вік'); ``` -Насправді існує безліч способів візуалізації форми, докладніше в розділі [Рендеринг |rendering]. +Способів відображення форми є справді багато, тому цьому присвячено [окремий розділ про відображення|rendering]. -Зіставлення з класами .[#toc-mapping-to-classes] -================================================ +Мапінг на класи +=============== -Давайте повернемося до обробки даних форми. Метод `getValues()` повертає представлені дані у вигляді об'єкта `ArrayHash`. Оскільки це загальний клас, щось на кшталт `stdClass`, нам не вистачатиме деяких зручностей під час роботи з ним, як-от завершення коду для властивостей у редакторах або статичний аналіз коду. Цю проблему можна вирішити, створивши для кожної форми окремий клас, властивості якого представляють окремі елементи керування. Наприклад: +Повернемося до обробки даних форми. Метод `getValues()` повертав нам відправлені дані як об'єкт `ArrayHash`. Оскільки це загальний клас, щось на зразок `stdClass`, при роботі з ним нам бракуватиме певного комфорту, наприклад, автодоповнення властивостей у редакторах або статичного аналізу коду. Це можна було б вирішити, маючи для кожної форми конкретний клас, властивості якого представляють окремі елементи. Наприклад: ```php class RegistrationFormData { public string $name; - public int $age; + public ?int $age; public string $password; } ``` -Починаючи з PHP 8.0, ви можете використовувати цю елегантну нотацію, яка використовує конструктор: +Альтернативно, ви можете використовувати конструктор: ```php class RegistrationFormData { public function __construct( public string $name, - public int $age, + public ?int $age, public string $password, ) { } } ``` -Як сказати Nette, щоб він повертав нам дані у вигляді об'єктів цього класу? Легше, ніж ви думаєте. Все, що вам потрібно зробити, це вказати ім'я класу або об'єкта для гідратації як параметр: +Властивості класу даних також можуть бути enum-ами і будуть автоматично зіставлені. .{data-version:3.2.4} + +Як сказати Nette, щоб він повертав нам дані як об'єкти цього класу? Легше, ніж ви думаєте. Достатньо лише вказати назву класу або об'єкт для гідратації як параметр: ```php $data = $form->getValues(RegistrationFormData::class); $name = $data->name; ``` -Як параметр також можна вказати `'array'`, і тоді дані повертаються у вигляді масиву. +Як параметр можна також вказати `'array'`, і тоді дані повернуться як масив. -Якщо форми складаються з багаторівневої структури, що складається з контейнерів, створіть окремий клас для кожного з них: +Якщо форми утворюють багаторівневу структуру, що складається з контейнерів, створіть для кожного окремий клас: ```php $form = new Form; @@ -247,22 +248,24 @@ class PersonFormData class RegistrationFormData { public PersonFormData $person; - public int $age; + public ?int $age; public string $password; } ``` -З типу властивості `$person` відображення дізнається, що воно має відобразити контейнер на клас `PersonFormData`. Якщо властивість буде містити масив контейнерів, вкажіть тип `array` і передайте клас, який повинен бути відображений безпосередньо на контейнер: +Мапінг потім з типу властивості `$person` дізнається, що контейнер потрібно зіставити з класом `PersonFormData`. Якщо властивість містить масив контейнерів, вкажіть тип `array` і передайте клас для мапінгу безпосередньо контейнеру: ```php $person->setMappedType(PersonFormData::class); ``` +Проект класу даних форми можна згенерувати за допомогою методу `Nette\Forms\Blueprint::dataClass($form)`, який виведе його на сторінку браузера. Потім код достатньо виділити кліком і скопіювати в проект. .{data-version:3.1.15} + -Кілька кнопок надсилання .[#toc-multiple-submit-buttons] -======================================================== +Кілька кнопок +============= -Якщо форма містить більше однієї кнопки, нам зазвичай потрібно розрізняти, яку з них було натиснуто. Метод `isSubmittedBy()` кнопки повертає нам цю інформацію: +Якщо форма має більше однієї кнопки, зазвичай потрібно розрізнити, яка з них була натиснута. Цю інформацію нам поверне метод `isSubmittedBy()` кнопки: ```php $form->addSubmit('save', 'Зберегти'); @@ -279,37 +282,36 @@ if ($form->isSuccess()) { } ``` -Не опускайте `$form->isSuccess()` для перевірки достовірності даних. +Не пропускайте запит `$form->isSuccess()`, він перевірить валідність даних. -Коли форму відправляють за допомогою кнопки <kbd>Enter</kbd>, її обробляють так само, як якби її було відправлено за допомогою першої кнопки. +Коли форма надсилається кнопкою <kbd>Enter</kbd>, це вважається так, ніби вона була надіслана першою кнопкою. -Захист від уразливостей .[#toc-vulnerability-protection] -======================================================== +Захист від вразливостей +======================= -Nette Framework докладає великих зусиль для забезпечення безпеки, а оскільки форми є найпоширенішим видом користувацького введення, форми Nette настільки ж гарні, наскільки непроникні. +Nette Framework приділяє велику увагу безпеці, тому ретельно дбає про надійний захист форм. -На додаток до захисту форм від атак відомих вразливостей, таких як [Cross-Site Scripting (XSS) |nette:glossary#Cross-Site-Scripting-XSS] і [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site-Request-Forgery-CSRF], він виконує безліч дрібних завдань із забезпечення безпеки, про які вам більше не потрібно думати. +Крім того, що форми захищають від атак [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] та [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery CSRF], він виконує багато дрібних заходів безпеки, про які вам вже не потрібно думати. -Наприклад, він відфільтровує всі керуючі символи з даних, що вводяться, і перевіряє правильність кодування UTF-8, так що дані з форми завжди будуть чистими. Для полів вибору і радіосписків перевіряється, що обрані елементи дійсно були із запропонованих і не було підробки. Ми вже згадували, що для введення однорядкового тексту він видаляє символи кінця рядка, які зловмисник може туди відправити. Для багаторядкових вводів він нормалізує символи кінця рядка. І так далі. +Наприклад, він фільтрує всі керуючі символи з вхідних даних і перевіряє валідність кодування UTF-8, тому дані з форми завжди будуть чистими. У select box-ах та radio list-ах він перевіряє, чи вибрані елементи дійсно були з запропонованих і чи не відбулася підробка. Ми вже згадували, що для однорядкових текстових полів він видаляє символи кінця рядка, які міг надіслати зловмисник. Для багаторядкових полів він нормалізує символи кінця рядка. І так далі. -Nette усуває за вас уразливості в системі безпеки, про існування яких більшість програмістів навіть не підозрюють. +Nette вирішує за вас ризики безпеки, про існування яких багато програмістів навіть не здогадуються. -Згадана CSRF-атака полягає в тому, що зловмисник заманює жертву відвідати сторінку, яка мовчки виконує запит у браузері жертви до сервера, на якому жертва на даний момент зареєстрована, і сервер вважає, що запит був зроблений жертвою за власним бажанням. Таким чином, Nette запобігає відправленню форми через POST з іншого домену. Якщо з якоїсь причини ви хочете відключити захист і дозволити надсилання форми з іншого домену, використовуйте: +Згадана атака CSRF полягає в тому, що зловмисник заманює жертву на сторінку, яка непомітно в браузері жертви виконує запит на сервер, на якому жертва залогінена, і сервер вважає, що запит виконала жертва за власним бажанням. Тому Nette запобігає надсиланню POST-форми з іншого домену. Якщо з якоїсь причини ви хочете вимкнути захист і дозволити надсилати форму з іншого домену, використовуйте: ```php $form->allowCrossOrigin(); // УВАГА! Вимикає захист! ``` -Цей захист використовує файл куки SameSite з ім'ям `_nss`. Тому створіть форму перед першим виведенням, щоб можна було надіслати куки. +Цей захист використовує SameSite cookie з назвою `_nss`. Тому створюйте об'єкт форми ще до надсилання першого виводу, щоб можна було надіслати cookie. -Захист кукі-файлів SameSite може бути не на 100% надійним, тому гарною ідеєю буде ввімкнути захист за допомогою токена: +Захист за допомогою SameSite cookie може бути не 100% надійним, тому рекомендується увімкнути ще захист за допомогою токена: ```php $form->addProtection(); ``` -Настійно рекомендується застосовувати цей захист до форм в адміністративній частині вашого застосунку, які змінюють конфіденційні дані. Фреймворк захищає від атаки CSRF, генеруючи та перевіряючи токен автентифікації, що зберігається в сесії (аргументом є повідомлення про помилку, яке показується, якщо термін дії токена закінчився). Тому перед відображенням форми необхідно, щоб сесія була запущена. В адміністративній частині сайту сесія, як правило, вже почалася, оскільки користувач увійшов у систему. -В іншому випадку, запустіть сесію за допомогою методу `Nette\Http\Session::start()`. +Рекомендуємо так захищати форми в адміністративній частині сайту, які змінюють чутливі дані в програмі. Фреймворк захищається від атаки CSRF шляхом генерації та перевірки авторизаційного токена, який зберігається в сесії. Тому необхідно перед відображенням форми мати відкриту сесію. В адміністративній частині сайту зазвичай сесія вже запущена через вхід користувача. В іншому випадку запустіть сесію методом `Nette\Http\Session::start()`. -Отже, ми коротко познайомилися з формами в Nette. Спробуйте пошукати натхнення в каталозі [examples |https://github.com/nette/forms/tree/master/examples] у дистрибутиві. +Отже, ми пройшли швидкий вступ до форм у Nette. Спробуйте ще заглянути в каталог [examples|https://github.com/nette/forms/tree/master/examples] у дистрибутиві, де ви знайдете більше натхнення. diff --git a/forms/uk/validation.texy b/forms/uk/validation.texy index a9ca94d241..1c2bc94484 100644 --- a/forms/uk/validation.texy +++ b/forms/uk/validation.texy @@ -2,172 +2,183 @@ ************** -Обов'язкові для заповнення елементи .[#toc-required-controls] -============================================================= +Обов'язкові елементи +==================== -Елементи керування позначаються як обов'язкові за допомогою методу `setRequired()`, аргументом якого є текст [повідомлення про помилку |#Сообщения об ошибке], що відображається, якщо користувач не заповнить його. Якщо аргумент не вказано, використовується повідомлення про помилку за замовчуванням. +Обов'язкові елементи позначаємо методом `setRequired()`, аргументом якого є текст [#Повідомлення про помилки], який відобразиться, якщо користувач не заповнить елемент. Якщо аргумент не вказано, використовується стандартне повідомлення про помилку. ```php -$form->addText('name', 'Имя:') - ->setRequired('Пожалуйста, заполните ваше имя.'); +$form->addText('name', 'Ім\'я:') + ->setRequired('Будь ласка, введіть ім\'я'); ``` -Правила .[#toc-rules] -===================== +Правила +======= -Ми додаємо правила перевірки до елементів управління за допомогою методу `addRule()`. Перший параметр - це правило, другий - [повідомлення про помилку |#Сообщения об ошибке], а третій - аргумент правила перевірки. +Правила валідації додаємо до елементів методом `addRule()`. Перший параметр — це правило, другий — текст [#Повідомлення про помилки], а третій — аргумент правила валідації. ```php $form->addPassword('password', 'Пароль:') - ->addRule($form::MinLength, 'Пароль должен состоять не менее чем из %d символов', 8); + ->addRule($form::MinLength, 'Пароль повинен мати щонайменше %d символів', 8); ``` -Nette постачається з низкою вбудованих правил, імена яких є константами класу `Nette\Forms\Form`: +**Правила валідації перевіряються лише в тому випадку, якщо користувач заповнив елемент.** -Ми можемо використовувати такі правила для всіх елементів керування: +Nette постачається з цілою низкою передбачених правил, назви яких є константами класу `Nette\Forms\Form`. Для всіх елементів ми можемо використовувати ці правила: -| константа | опис | аргументи +| константа | опис | тип аргументу |------- -| `Required` | псевдонім `setRequired()` | - -| `Filled` | псевдонім `setRequired()` | - -| `Blank` | не повинно бути заповнено | - +| `Required` | обов'язковий елемент, псевдонім для `setRequired()` | - +| `Filled` | обов'язковий елемент, псевдонім для `setRequired()` | - +| `Blank` | елемент не повинен бути заповнений | - | `Equal` | значення дорівнює параметру | `mixed` | `NotEqual` | значення не дорівнює параметру | `mixed` -| `IsIn` | значення дорівнює деякому елементу масиву | `array` -| `IsNotIn` | значення не дорівнює жодному елементу масиву | `array` -| `Valid` | введення проходить валідацію (для [Умови |#Условия])| - +| `IsIn` | значення дорівнює одному з елементів у масиві | `array` +| `IsNotIn` | значення не дорівнює жодному з елементів у масиві | `array` +| `Valid` | чи елемент заповнений правильно? (для [#Умови]) | - + -Для елементів керування `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` також можуть бути використані такі правила: +Текстові поля +------------- -| `MinLength` | мінімальна довжина рядка | `int` -| `MaxLength` | максимальна довжина рядка | `int` +Для елементів `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`, `addFloat()` можна також використовувати деякі з наступних правил: + +| `MinLength` | мінімальна довжина тексту | `int` +| `MaxLength` | максимальна довжина тексту | `int` | `Length` | довжина в діапазоні або точна довжина | пара `[int, int]` або `int` -| `Email` | дійсна адреса електронної пошти | - -| `URL` | дійсний URL | - -| `Pattern` | відповідає регулярному шаблону | `string` -| `PatternInsensitive` | як `Pattern`, але без урахування регістру. | `string` -| `Integer` | ціле число | - -| `Numeric` | псевдонім `Integer` | - - -| `Float` | ціле число або число з плаваючою крапкою | - -| `Min` | мінімум цілочисельного значення | `int\|float` -| `Max` | максимум цілочисельного значення | `int\|float` +| `Email` | дійсна електронна адреса | - +| `URL` | абсолютний URL | - +| `Pattern` | відповідає регулярному виразу | `string` +| `PatternInsensitive` | як `Pattern`, але нечутливий до регістру | `string` +| `Integer` | цілочисельне значення | - +| `Numeric` | псевдонім для `Integer` | - +| `Float` | число | - +| `Min` | мінімальне значення числового елемента | `int\|float` +| `Max` | максимальне значення числового елемента | `int\|float` | `Range` | значення в діапазоні | пара `[int\|float, int\|float]` -Правила `Integer`, `Numeric` і `Float` автоматично перетворюють значення в ціле (або плаваюче відповідно). Більше того, правило `URL` також приймає адресу без схеми (наприклад, `nette.org`) і доповнює схему (`https://nette.org`). -Вирази в `Pattern` і `PatternInsensitive` мають бути дійсними для всього значення, тобто так, ніби воно було обгорнуте в символи `^` и `$`. +Правила валідації `Integer`, `Numeric` та `Float` одразу перетворюють значення на integer відповідно float. Крім того, правило `URL` приймає також адресу без схеми (наприклад, `nette.org`) і доповнює схему (`https://nette.org`). Вираз у `Pattern` та `PatternIcase` повинен відповідати всьому значенню, тобто ніби він був обгорнутий символами `^` та `$`. -Для елементів керування `addUpload()`, `addMultiUpload()` також можуть бути використані такі правила: -| `MaxFileSize` | максимальний розмір файлу | `int` -| `MimeType` | Тип MIME, приймає підстановні знаки (`'video/*'`) | `string\|string[]` -| `Image` | завантажений файл є JPEG, PNG, GIF, WebP | - -| `Pattern` | ім'я файлу відповідає регулярному виразу | `string` -| `PatternInsensitive` | як `Pattern`, але без урахування регістру. | `string` +Кількість елементів +------------------- -Для `MimeType` і `Image` потрібне розширення PHP `fileinfo`. Належність файлу або зображення до потрібного типу визначається за його сигнатурою. Цілісність усього файлу не перевіряється. Ви можете дізнатися, чи не пошкоджено зображення, наприклад, спробувавши [завантажити його |http:request#toImage]. - -Для елементів керування `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()` такі правила також можуть бути використані для обмеження кількості обраних елементів, відповідно завантажених файлів: +Для елементів `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()` можна також використовувати наступні правила для обмеження кількості вибраних елементів або завантажених файлів: | `MinLength` | мінімальна кількість | `int` | `MaxLength` | максимальна кількість | `int` | `Length` | кількість у діапазоні або точна кількість | пара `[int, int]` або `int` -Повідомлення про помилки .[#toc-error-messages] ------------------------------------------------ +Завантаження файлів +------------------- + +Для елементів `addUpload()`, `addMultiUpload()` можна також використовувати наступні правила: + +| `MaxFileSize` | максимальний розмір файлу в байтах | `int` +| `MimeType` | MIME-тип, дозволені плейсхолдери (`'video/*'`) | `string\|string[]` +| `Image` | зображення JPEG, PNG, GIF, WebP, AVIF | - +| `Pattern` | ім'я файлу відповідає регулярному виразу | `string` +| `PatternInsensitive` | як `Pattern`, але нечутливий до регістру | `string` + +`MimeType` та `Image` вимагають PHP-розширення `fileinfo`. Те, що файл чи зображення є потрібного типу, визначається на основі його сигнатури, і **не перевіряється цілісність усього файлу.** Чи не пошкоджене зображення, можна з'ясувати, наприклад, спробувавши його [завантажити |http:request#toImage]. -Усі зумовлені правила, крім `Pattern` і `PatternInsensitive`, мають повідомлення про помилку за замовчуванням, тому їх можна опустити. Однак, передавши і сформулювавши всі індивідуальні повідомлення, ви зробите форму зручнішою для користувача. -Ви можете змінити повідомлення за замовчуванням у [configuration |forms:configuration], змінюючи тексти в масиві `Nette\Forms\Validator::$messages` або використовуючи [translator |rendering#translating]. +Повідомлення про помилки +======================== + +Усі передбачені правила, за винятком `Pattern` та `PatternInsensitive`, мають стандартне повідомлення про помилку, тому його можна пропустити. Однак, вказавши та сформулювавши всі повідомлення індивідуально, ви зробите форму більш зручною для користувача. -У тексті повідомлень про помилки можна використовувати такі символи підстановки: +Змінити стандартні повідомлення можна в [конфігурації|forms:configuration], змінивши тексти в масиві `Nette\Forms\Validator::$messages` або використовуючи [перекладач |rendering#Переклад]. -| `%d` | поступово замінює правила після аргументів -| `%n$d` | замінюється на n-й аргумент правила -| `%label` | замінює на мітку поля (без двокрапки) -| `%name` | замінює ім'я поля (наприклад, `name`) -| `%value` | замінюється значенням, введеним користувачем +У тексті повідомлень про помилки можна використовувати ці рядки-заповнювачі: + +| `%d` | замінюється послідовно на аргументи правила +| `%n$d` | замінюється на n-й аргумент правила +| `%label` | замінюється на мітку елемента (без двокрапки) +| `%name` | замінюється на ім'я елемента (наприклад, `name`) +| `%value` | замінюється на значення, введене користувачем ```php -$form->addText('name', 'Имя:') - ->setRequired('Пожалуйста, заполните %label'); +$form->addText('name', 'Ім\'я:') + ->setRequired('Заповніть, будь ласка, %label'); $form->addInteger('id', 'ID:') - ->addRule($form::Range, 'не менее %d и не более %d', [5, 10]); + ->addRule($form::Range, 'щонайменше %d і щонайбільше %d', [5, 10]); $form->addInteger('id', 'ID:') - ->addRule($form::Range, 'не более %2$d и не менее %1$d', [5, 10]); + ->addRule($form::Range, 'щонайбільше %2$d і щонайменше %1$d', [5, 10]); ``` -Умови .[#toc-conditions] -======================== +Умови +===== -Крім правил валідації, можна задати умови. Вони встановлюються так само, як і правила, але ми використовуємо `addRule()` замість `addCondition()` і, звісно, залишаємо їх без повідомлення про помилку (умова просто запитує): +Крім правил, можна додавати також умови. Вони записуються подібно до правил, тільки замість `addRule()` використовуємо метод `addCondition()` і, зрозуміло, не вказуємо жодного повідомлення про помилку (умова лише запитує): ```php $form->addPassword('password', 'Пароль:') - // якщо пароль не довший за 8 символів ... + // якщо пароль не довший за 8 символів ->addCondition($form::MaxLength, 8) - // ... тоді він має містити число - ->addRule($form::Pattern, 'Повинен містити номер', '.*[0-9].*'); + // тоді він повинен містити цифру + ->addRule($form::Pattern, 'Повинен містити цифру', '.*[0-9].*'); ``` -Умова може бути прив'язана до елемента, відмінного від поточного, за допомогою `addConditionOn()`. Перший параметр - це посилання на поле. У наступному випадку електронна пошта знадобиться тільки в тому випадку, якщо прапорець установлено (тобто його значення дорівнює `true`): +Умову можна прив'язати і до іншого елемента, ніж поточний, за допомогою `addConditionOn()`. Як перший параметр вкажемо посилання на елемент. У цьому прикладі e-mail буде обов'язковим лише тоді, коли буде відмічено checkbox (його значення буде true): ```php -$form->addCheckbox('newsletters', 'надсилати мені інформаційні бюлетені'); +$form->addCheckbox('newsletters', 'надсилайте мені розсилки'); -$form->addEmail('email', 'Імейл:') - // якщо прапорець встановлено ... +$form->addEmail('email', 'E-mail:') + // якщо checkbox відмічено ->addConditionOn($form['newsletters'], $form::Equal, true) - // ... вимагати електронну пошту - ->setRequired('Введіть свою адресу електронної пошти'); + // тоді вимагай e-mail + ->setRequired('Введіть адресу електронної пошти'); ``` -Умови можуть бути згруповані в складні структури за допомогою методів `elseCondition()` і `endCondition()`. +З умов можна створювати складні структури за допомогою `elseCondition()` та `endCondition()`: ```php $form->addText(/* ... */) - ->addCondition(/* ... */) // якщо виконується перша умова - ->addConditionOn(/* ... */) // і друга умова на іншому елементі теж - ->addRule(/* ... */) // вимагають дотримання цього правила - ->elseCondition() // якщо друга умова не виконується - ->addRule(/* ... */) // вимагають дотримання цих правил + ->addCondition(/* ... */) // якщо виконана перша умова + ->addConditionOn(/* ... */) // і друга умова на іншому елементі + ->addRule(/* ... */) // вимагай це правило + ->elseCondition() // якщо друга умова не виконана + ->addRule(/* ... */) // вимагай ці правила ->addRule(/* ... */) - ->endCondition() // ми повертаємося до першої умови + ->endCondition() // повертаємося до першої умови ->addRule(/* ... */); ``` -У Nette дуже легко реагувати на виконання або невиконання умови на стороні JavaScript, використовуючи метод `toggle()`, див. [Динамічний JavaScript |#Динамический JavaScript]. +У Nette можна дуже легко реагувати на виконання чи невиконання умови також на стороні JavaScript за допомогою методу `toggle()`, див. [#Динамічний JavaScript]. -Посилання між елементами керування .[#toc-references-between-controls] -====================================================================== +Посилання на інший елемент +========================== -Аргумент правила або умови може бути посиланням на інший елемент. Наприклад, ви можете динамічно підтвердити, що `text` має стільки символів, скільки вказано в полі `length`: +Як аргумент правила чи умови можна передати й інший елемент форми. Правило тоді використає значення, введене пізніше користувачем у браузері. Таким чином можна, наприклад, динамічно валідувати, що елемент `password` містить той самий рядок, що й елемент `password_confirm`: ```php -$form->addInteger('length'); -$form->addText('text') - ->addRule($form::Length, null, $form['length']); +$form->addPassword('password', 'Пароль'); +$form->addPassword('password_confirm', 'Підтвердіть пароль') + ->addRule($form::Equal, 'Введені паролі не співпадають', $form['password']); ``` -Користувацькі правила та умови .[#toc-custom-rules-and-conditions] -================================================================== +Власні правила та умови +======================= -Іноді ми стикаємося з ситуацією, коли вбудованих правил валідації в Nette недостатньо, і нам потрібно перевірити дані від користувача по-своєму. У Nette це дуже просто! +Іноді ми потрапляємо в ситуацію, коли вбудованих правил валідації в Nette недостатньо, і нам потрібно валідувати дані від користувача по-своєму. У Nette це дуже просто! -Ви можете передати будь-який зворотний виклик як перший параметр у методи `addRule()` або `addCondition()`. Зворотний виклик приймає сам елемент як перший параметр і повертає булеве значення, що вказує на успішність перевірки. Під час додавання правила за допомогою `addRule()` можна передати додаткові аргументи, які передаються як другий параметр. +Методам `addRule()` чи `addCondition()` можна як перший параметр передати будь-який callback. Він приймає як перший параметр сам елемент і повертає булеве значення, що визначає, чи валідація пройшла успішно. При додаванні правила за допомогою `addRule()` можна вказати й інші аргументи, які потім передаються як другий параметр. -Таким чином, користувацький набір валідаторів може бути створений як клас зі статичними методами: +Власний набір валідаторів ми можемо створити як клас зі статичними методами: ```php class MyValidators { - // перевіряє, чи ділиться значення на аргумент + // перевіряє, чи значення ділиться на аргумент public static function validateDivisibility(BaseControl $input, $arg): bool { return $input->getValue() % $arg === 0; @@ -175,23 +186,23 @@ class MyValidators public static function validateEmailDomain(BaseControl $input, $domain) { - // додаткові валідатори + // інші валідатори } } ``` -Далі використання дуже просте: +Використання тоді дуже просте: ```php $form->addInteger('num') ->addRule( [MyValidators::class, 'validateDivisibility'], - 'Значение должно быть кратно %d', + 'Значення має бути кратним числу %d', 8, ); ``` -Користувацькі правила валідації також можуть бути додані в JavaScript. Єдиною вимогою є те, що правило має бути статичним методом. Його ім'я для валідатора JavaScript створюється шляхом з'єднання імені класу без зворотних косих рисок `\`, подчеркивания `_` та імені методу. Наприклад, запишіть `App\MyValidators::validateDivisibility` як `AppMyValidators_validateDivisibility` і додайте його в об'єкт `Nette.validators`: +Власні правила валідації можна додавати і до JavaScript. Умовою є те, що правило має бути статичним методом. Його назва для JavaScript-валідатора утворюється шляхом об'єднання назви класу без зворотних слешів `\`, підкреслення `_` та назви методу. Наприклад, `App\MyValidators::validateDivisibility` запишемо як `AppMyValidators_validateDivisibility` і додамо до об'єкта `Nette.validators`: ```js Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => { @@ -200,12 +211,12 @@ Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => ``` -Подія onValidate .[#toc-event-onvalidate] -========================================= +Подія onValidate +================ -Після відправлення форми перевірка виконується шляхом перевірки окремих правил, доданих за допомогою `addRule()`, і подальшого виклику [події |nette:glossary#Events] `onValidate`. Її обробник може бути використаний для додаткової перевірки, зазвичай для перевірки правильності комбінації значень у кількох елементах форми. +Після надсилання форми проводиться валідація, під час якої перевіряються окремі правила, додані за допомогою `addRule()`, а потім викликається [подія |nette:glossary#Події události] `onValidate`. Її обробник можна використовувати для додаткової валідації, зазвичай для перевірки правильної комбінації значень у кількох елементах форми. -Якщо виявлено помилку, вона передається у форму за допомогою методу `addError()`. Це може бути викликано як на певному елементі, так і безпосередньо на формі. +Якщо виявлено помилку, передаємо її до форми методом `addError()`. Його можна викликати або на конкретному елементі, або безпосередньо на формі. ```php protected function createComponentSignInForm(): Form @@ -225,10 +236,10 @@ public function validateSignInForm(Form $form, \stdClass $data): void ``` -Помилки обробки .[#toc-processing-errors] -========================================= +Помилки під час обробки +======================= -У багатьох випадках ми виявляємо помилку, коли обробляємо дійсну форму, наприклад, коли ми записуємо новий запис у базу даних і стикаємося з дублюючим ключем. У цьому випадку ми передаємо помилку назад у форму за допомогою методу `addError()`. Це може бути викликано як на певному елементі, так і безпосередньо на формі: +У багатьох випадках про помилку ми дізнаємося лише тоді, коли обробляємо валідну форму, наприклад, записуємо новий елемент у базу даних і натрапляємо на дублювання ключів. У такому випадку помилку знову передаємо до форми методом `addError()`. Його можна викликати або на конкретному елементі, або безпосередньо на формі: ```php try { @@ -238,72 +249,71 @@ try { } catch (Nette\Security\AuthenticationException $e) { if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) { - $form->addError('Неверный пароль.'); + $form->addError('Неправильний пароль.'); } } ``` -Якщо можливо, ми рекомендуємо додати помилку безпосередньо до елемента форми, оскільки вона відображатиметься поруч із ним під час використання рендерингу за замовчуванням. +Якщо можливо, рекомендуємо прикріпити помилку безпосередньо до елемента форми, оскільки вона відобразиться поруч із ним при використанні стандартного візуалізатора. ```php -$form['date']->addError('Извините, эта дата уже занята.'); +$form['date']->addError('Вибачте, але ця дата вже зайнята.'); ``` -Ви можете викликати `addError()` кілька разів, щоб передати кілька повідомлень про помилки формі або елементу. Ви отримуєте їх за допомогою функції `getErrors()`. +Ви можете викликати `addError()` повторно і таким чином передати формі або елементу кілька повідомлень про помилки. Отримати їх можна за допомогою `getErrors()`. -Зверніть увагу, що `$form->getErrors()` повертає зведення всіх повідомлень про помилки, навіть тих, що були передані безпосередньо окремим елементам, а не тільки безпосередньо формі. Повідомлення про помилки, передані тільки формі, витягуються через `$form->getOwnErrors()`. +Увага, `$form->getErrors()` повертає зведення всіх повідомлень про помилки, включаючи ті, що були передані безпосередньо окремим елементам, а не лише безпосередньо формі. Повідомлення про помилки, передані лише формі, можна отримати через `$form->getOwnErrors()`. -Зміна вхідних значень .[#toc-modifying-input-values] -==================================================== +Зміна вводу +=========== -Використовуючи метод `addFilter()`, ми можемо змінити значення, введене користувачем. У цьому прикладі ми будемо допускати і видаляти пробіли в поштовому індексі: +За допомогою методу `addFilter()` ми можемо змінити значення, введене користувачем. У цьому прикладі ми будемо толерувати та видаляти пробіли в поштовому індексі: ```php $form->addText('zip', 'Поштовий індекс:') ->addFilter(function ($value) { - return str_replace(' ', '', $value); // видалити пробіли з поштового індексу + return str_replace(' ', '', $value); // видалимо пробіли з поштового індексу }) - ->addRule($form::Pattern, 'Поштовий індекс не складається з п'яти цифр', '\d{5}'); + ->addRule($form::Pattern, 'Поштовий індекс не у форматі п\'яти цифр', '\d{5}'); ``` -Фільтр увімкнено між правилами перевірки й умовами і тому він залежить від порядку проходження методів, тобто фільтр і правило викликаються в тому самому порядку, що й порядок проходження методів `addFilter()` і `addRule()`. +Фільтр включається між правилами валідації та умовами, тому порядок методів має значення, тобто фільтр і правило викликаються в тому порядку, в якому вказані методи `addFilter()` та `addRule()`. -Валідація JavaScript .[#toc-javascript-validation] -================================================== +JavaScript валідація +==================== -Мова правил та умов перевірки є потужною. Незважаючи на те, що всі конструкції працюють як на стороні сервера, так і на стороні клієнта, у JavaScript. Правила передаються в HTML-атрибутах `data-nette-rules` у вигляді JSON. -Сама валідація обробляється іншим скриптом, який перехоплює всі події форми `submit`, перебирає всі дані, що вводяться, і запускає відповідні валідації. +Мова для формулювання умов і правил дуже потужна. Усі конструкції при цьому працюють як на стороні сервера, так і на стороні JavaScript. Вони передаються в HTML-атрибутах `data-nette-rules` як JSON. Саму валідацію потім виконує скрипт, який перехоплює подію форми `submit`, проходить по окремих елементах і виконує відповідну валідацію. -Цей скрипт - `netteForms.js`, який доступний з декількох можливих джерел: +Цим скриптом є `netteForms.js`, і він доступний з кількох можливих джерел: -Ви можете вбудувати сценарій безпосередньо в HTML-сторінку з CDN: +Скрипт можна вставити безпосередньо в HTML-сторінку з CDN: ```latte -<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script> +<script src="https://unpkg.com/nette-forms@3"></script> ``` -Або скопіюйте локально в загальну папку проєкту (наприклад, із сайту `vendor/nette/forms/src/assets/netteForms.min.js`): +Або скопіювати локально в публічний каталог проекту (наприклад, з `vendor/nette/forms/src/assets/netteForms.min.js`): ```latte <script src="/path/to/netteForms.min.js"></script> ``` -Або встановіть через [npm |https://www.npmjs.com/package/nette-forms]: +Або встановити через [npm|https://www.npmjs.com/package/nette-forms]: ```shell npm install nette-forms ``` -А потім завантажте та запустіть: +А потім завантажити та запустити: ```js import netteForms from 'nette-forms'; netteForms.initOnLoad(); ``` -Крім того, ви можете завантажити його безпосередньо з папки `vendor`: +Альтернативно, його можна завантажити безпосередньо з каталогу `vendor`: ```js import netteForms from '../path/to/vendor/nette/forms/src/assets/netteForms.js'; @@ -311,10 +321,10 @@ netteForms.initOnLoad(); ``` -Динамічний JavaScript .[#toc-dynamic-javascript] -================================================ +Динамічний JavaScript +===================== -Ви хочете відображати поля адреси тільки в тому випадку, якщо користувач вирішить відправити товар поштою? Немає проблем. Ключем є пара методів `addCondition()` і `toggle()`: +Хочете відображати поля для введення адреси лише якщо користувач вибере доставку товару поштою? Без проблем. Ключем є пара методів `addCondition()` & `toggle()`: ```php $form->addCheckbox('send_it') @@ -322,25 +332,25 @@ $form->addCheckbox('send_it') ->toggle('#address-container'); ``` -Цей код говорить, що при виконанні умови, тобто при встановленні прапорця, HTML-елемент `#address-container` стане видимим. І навпаки. Отже, ми поміщаємо елементи форми з адресою одержувача в контейнер із цим ID, і під час натискання на прапорець вони приховуються або показуються. Цим займається скрипт `netteForms.js`. +Цей код говорить, що коли умова виконана, тобто коли відмічено checkbox, буде видимим HTML-елемент `#address-container`. І навпаки. Елементи форми з адресою одержувача ми розмістимо в контейнері з цим ID, і при кліку на checkbox вони будуть приховані або показані. Це забезпечує скрипт `netteForms.js`. -Будь-який селектор може бути переданий як аргумент методу `toggle()`. З історичних причин буквено-цифровий рядок без інших спеціальних символів розглядають як ідентифікатор елемента, так само як якби йому передував символ `#`. Второй необязательный параметр позволяет нам изменить поведение, т. е. если бы мы использовали `toggle('#address-container', false)`, елемент відображали б тільки в разі знятого прапорця. +Як аргумент методу `toggle()` можна передати будь-який селектор. З історичних причин буквено-цифровий рядок без інших спеціальних символів розуміється як ID елемента, тобто так само, якби йому передував символ `#`. Другий необов'язковий параметр дозволяє інвертувати поведінку, тобто якби ми використали `toggle('#address-container', false)`, елемент би, навпаки, відображався лише тоді, коли checkbox не був би відмічений. -Реалізація JavaScript за замовчуванням змінює властивість `hidden` для елементів. Однак ми можемо легко змінити поведінку, наприклад, додавши анімацію. Просто перевизначте метод `Nette.toggle` у JavaScript за допомогою власного рішення: +Стандартна реалізація в JavaScript змінює властивість `hidden` елементів. Однак поведінку можна легко змінити, наприклад, додати анімацію. Достатньо в JavaScript перезаписати метод `Nette.toggle` власним рішенням: ```js Nette.toggle = (selector, visible, srcElement, event) => { document.querySelectorAll(selector).forEach((el) => { - // скрыть или показать 'el' в зависимости от значения 'visible' + // приховаємо або покажемо 'el' залежно від значення 'visible' }); }; ``` -Вимкнення валідації .[#toc-disabling-validation] -================================================ +Вимкнення валідації +=================== -У деяких випадках необхідно відключити валідацію. Якщо кнопка submit не повинна виконувати перевірку після відправлення (наприклад, кнопка *Скасування* або *Предварительный просмотр*), ви можете відключити перевірку, викликавши `$submit->setValidationScope([])`. Ви також можете перевірити форму частково, вказавши елементи для перевірки. +Іноді може знадобитися вимкнути валідацію. Якщо натискання кнопки відправки не повинно виконувати валідацію (підходить для кнопок *Cancel* або *Preview*), вимкнемо її методом `$submit->setValidationScope([])`. Якщо вона повинна виконувати лише часткову валідацію, ми можемо вказати, які поля або контейнери форми мають валідуватися. ```php $form->addText('name') @@ -352,15 +362,15 @@ $details->addInteger('age') $details->addInteger('age2') ->setRequired('age2'); -$form->addSubmit('send1'); // Перевіряє всю форму +$form->addSubmit('send1'); // Валідує всю форму $form->addSubmit('send2') - ->setValidationScope([]); // Нічого не підтверджує + ->setValidationScope([]); // Не валідує взагалі $form->addSubmit('send3') - ->setValidationScope([$form['name']]); // Перевіряє тільки поле 'ім'я' + ->setValidationScope([$form['name']]); // Валідує лише елемент name $form->addSubmit('send4') - ->setValidationScope([$form['details']['age']]); // Перевіряється тільки поле 'вік' + ->setValidationScope([$form['details']['age']]); // Валідує лише елемент age $form->addSubmit('send5') - ->setValidationScope([$form['details']]); // Перевіряє контейнер 'details' + ->setValidationScope([$form['details']]); // Валідує контейнер details ``` -[Подія onValidate |#Событие onValidate] на формі викликається завжди і не залежить від `setValidationScope`. Подія `onValidate` на контейнері викликається тільки тоді, коли цей контейнер вказано для часткової валідації. +`setValidationScope` не впливає на [#подія onValidate] у формі, яка буде викликана завжди. Подія `onValidate` у контейнері буде викликана лише якщо цей контейнер позначений для часткової валідації. diff --git a/http/bg/@home.texy b/http/bg/@home.texy index a825b12f96..5dc37c2679 100644 --- a/http/bg/@home.texy +++ b/http/bg/@home.texy @@ -2,13 +2,13 @@ Nette HTTP ********** .[perex] -Пакетът `nette/http` включва [HTTP заявки |request] и [отговори |response], обработка на [сесии |sessions], [анализ и конструиране на URL адреси |urls]. +Пакетът `nette/http` капсулира [HTTP request|request] & [response], работа със [сесии|sessions] и [парсване и съставяне на URL |urls]. -Инсталиране на .[#toc-installation] ------------------------------------ +Инсталация +---------- -Изтеглете и инсталирайте пакета с помощта на [Composer |best-practices:composer]: +Изтеглете и инсталирайте библиотеката с помощта на [Composer|best-practices:composer]: ```shell composer require nette/http diff --git a/http/bg/@left-menu.texy b/http/bg/@left-menu.texy index f59990edf1..1f9b67ea4c 100644 --- a/http/bg/@left-menu.texy +++ b/http/bg/@left-menu.texy @@ -1,8 +1,8 @@ -Мрежи HTTP +Nette HTTP ********** -- [Преглед |@home] -- [HTTP заявка |request] -- [HTTP отговор |response] +- [Въведение |@home] +- [HTTP request|request] +- [HTTP response|response] - [Сесии |Sessions] -- [URL Utility |urls] -- [Конфигурация |Configuration] +- [URL utilities |urls] +- [Конфигурация |configuration] diff --git a/http/bg/@meta.texy b/http/bg/@meta.texy new file mode 100644 index 0000000000..57804a1127 --- /dev/null +++ b/http/bg/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документация на Nette}} diff --git a/http/bg/configuration.texy b/http/bg/configuration.texy index e79dc8e58c..56fe18000c 100644 --- a/http/bg/configuration.texy +++ b/http/bg/configuration.texy @@ -1,40 +1,40 @@ -Настройка на HTTP +HTTP конфигурация ***************** .[perex] -Преглед на опциите за конфигуриране на Nette HTTP. +Преглед на опциите за конфигурация за Nette HTTP. -Ако не използвате цялата рамка, а само тази библиотека, прочетете [как да изтеглите конфигурацията |bootstrap:]. +Ако не използвате целия framework, а само тази библиотека, прочетете [как да заредите конфигурацията|bootstrap:]. -HTTP хедъри .[#toc-http-headers] -================================ +HTTP хедъри +=========== ```neon http: - # заглавия, които се изпращат с всяка заявка + # хедъри, които се изпращат с всяка заявка headers: X-Powered-By: MyCMS X-Content-Type-Options: nosniff X-XSS-Protection: '1; mode=block' - # header X-Frame-Options + # влияе на хедъра X-Frame-Options frames: ... # (string|bool) по подразбиране е 'SAMEORIGIN' ``` -От съображения за сигурност рамката изпраща заглавието `X-Frame-Options: SAMEORIGIN`, което гласи, че дадена страница може да бъде визуализирана само вътре в друга страница (в елемента `<iframe>`) само ако е в същия домейн. Това може да не е желателно в някои ситуации (например ако разработвате приложение за Facebook), затова поведението може да се промени чрез задаване на рамки `frames: http://allowed-host.com`. +Framework-ът по съображения за сигурност изпраща хедъра `X-Frame-Options: SAMEORIGIN`, който казва, че страницата може да бъде показана вътре в друга страница (в елемента `<iframe>`) само ако се намира на същия домейн. Това може да бъде нежелателно в някои ситуации (например, ако разработвате приложение за Facebook), затова поведението може да бъде променено чрез настройка `frames: http://allowed-host.com` или `frames: true`. -Политика за сигурност на съдържанието .[#toc-content-security-policy] ---------------------------------------------------------------------- +Content Security Policy +----------------------- -Заглавията на `Content-Security-Policy` (наричани по-нататък CSP) могат лесно да бъдат сглобени, като тяхното описание може да бъде намерено в [описанието на CSP |https://content-security-policy.com]. Директивите на CSP (като например `script-src`) могат да бъдат записани или като низове съгласно спецификацията, или като масиви от стойности за по-добра четимост. Тогава не е необходимо да пишете кавички около ключови думи като `'self'`. Nette също така автоматично генерира стойността `nonce`, така че `'nonce-y4PopTLM=='` ще бъде изпратен в заглавието. +Лесно могат да се съставят хедъри `Content-Security-Policy` (по-нататък CSP), тяхното описание ще намерите в [описанието на CSP |https://content-security-policy.com]. CSP директивите (като напр. `script-src`) могат да бъдат записани или като низове според спецификацията, или като масив от стойности за по-добра четимост. Тогава не е необходимо около ключовите думи, като например `'self'`, да се пишат кавички. Nette също автоматично генерира стойност `nonce`, така че в хедъра ще има например `'nonce-y4PopTLM=='`. ```neon http: - # Политика за сигурност на съдържанието + # Content Security Policy csp: - # низ според спецификацията на CSP + # низ във формат според спецификацията на CSP default-src: "'self' https://example.com" # масив от стойности @@ -49,18 +49,18 @@ http: block-all-mixed-content: false ``` -Използвайте `<script n:nonce>...</script>` в шаблоните, а стойността nonce ще бъде попълнена автоматично. Създаването на сигурни уебсайтове в Nette е лесно. +В шаблоните използвайте `<script n:nonce>...</script>` и стойността nonce ще се допълни автоматично. Създаването на безопасни сайтове в Nette е наистина лесно. -По същия начин можете да добавите заглавия `Content-Security-Policy-Report-Only` (които можете да използвате паралелно с CSP) и [Feature Policy |https://developers.google.com/web/updates/2018/06/feature-policy]: +Подобно могат да се съставят и хедъри `Content-Security-Policy-Report-Only` (които могат да се използват паралелно с CSP) и [Feature Policy|https://developers.google.com/web/updates/2018/06/feature-policy]: ```neon http: - # Доклад за политиката за сигурност на съдържанието + # Content Security Policy Report-Only cspReportOnly: default-src: self report-uri: 'https://my-report-uri-endpoint' - # Политика за характеристиките + # Feature Policy featurePolicy: unsized-media: none geolocation: @@ -69,91 +69,103 @@ http: ``` -Бисквитка HTTP .[#toc-http-cookie] ----------------------------------- +HTTP бисквитки +-------------- -Можете да промените стойностите по подразбиране на някои параметри на методите [Nette\Http\Response::setCookie( |response#setCookie] ) и session. +Могат да се променят стойностите по подразбиране на някои параметри на метода [Nette\Http\Response::setCookie() |response#setCookie] и сесията. ```neon http: - # обхвата на "бисквитката" по пътя - cookiePath: ... # (string) по подразбиране е '/' + # обхват на бисквитката според пътя + cookiePath: ... # (string) по подразбиране е '/' - # на кои хостове е разрешено да получават бисквитки - cookieDomain: 'example.com' # (string|домейн) по подразбиране не е зададен + # домейни, които приемат бисквитката + cookieDomain: 'example.com' # (string|domain) по подразбиране не е зададено - # изпращате бисквитки само чрез HTTPS? - cookieSecure: ... # (bool|auto) по подразбиране е auto + # изпращане на бисквитка само през HTTPS? + cookieSecure: ... # (bool|auto) по подразбиране е auto - # деактивира изпращането на бисквитки, които Nette използва за защита срещу CSRF - disableNetteCookie: ... # (bool) по подразбиране е false + # изключва изпращането на бисквитка, която Nette използва за защита срещу CSRF + disableNetteCookie: ... # (bool) по подразбиране е false ``` -Параметърът `cookieDomain` определя кои домейни (произход) могат да приемат "бисквитката". Ако не е посочено, "бисквитката" се приема от същия (под)домейн, за който е зададена, *изключвайки* техните поддомейни. Ако е посочен `cookieDomain`, поддомейните също ще бъдат включени. Така че посочването на `cookieDomain` е по-малко ограничаващо, отколкото пропускането му. +Атрибутът `cookieDomain` определя кои домейни могат да приемат бисквитката. Ако не е посочен, бисквитката се приема от същия (под)домейн, който я е задал, *но не* и от неговите поддомейни. Ако `cookieDomain` е зададен, са включени и поддомейните. Затова посочването на `cookieDomain` е по-малко ограничаващо от пропускането му. -Например, ако е посочен `cookieDomain: nette.org`, "бисквитката" е достъпна и за всички поддомейни, например `doc.nette.org`. Това може да се постигне и чрез посочване на `domain`, т.е. `cookieDomain: domain`. +Например при `cookieDomain: nette.org` бисквитките са достъпни и на всички поддомейни като `doc.nette.org`. Същото може да се постигне и с помощта на специалната стойност `domain`, т.е. `cookieDomain: domain`. -Стойността по подразбиране на `cookieSecure` е `auto`, което означава, че ако сайтът работи на HTTPS, бисквитката ще бъде изпратена с флаг `Secure` и следователно ще бъде достъпна само през HTTPS. +Стойността по подразбиране `auto` при атрибута `cookieSecure` означава, че ако сайтът работи на HTTPS, бисквитките ще се изпращат с флаг `Secure` и следователно ще бъдат достъпни само през HTTPS. -HTTP проксито е .[#toc-http-proxy] ----------------------------------- +HTTP прокси +----------- -Ако сайтът работи зад HTTP прокси сървър, въведете IP адреса на прокси сървъра, за да работи правилно откриването на HTTPS връзката, както и IP адреса на клиента. Това означава, че [Nette\Http\Request::getRemoteAddress() |request#getRemoteAddress] и [isSecured() |request#isSecured] връщат правилните стойности и връзките с протокол `https:` се генерират в шаблоните. +Ако сайтът работи зад HTTP прокси, въведете неговия IP адрес, за да работи правилно откриването на връзка през HTTPS и също IP адресите на клиента. Тоест, за да функциите [Nette\Http\Request::getRemoteAddress() |request#getRemoteAddress] и [isSecured() |request#isSecured] връщат правилните стойности и в шаблоните да се генерират връзки с `https:` протокол. ```neon http: # IP адрес, обхват (напр. 127.0.0.1/8) или масив от тези стойности - proxy: 127.0.0.1 # (string|string[]) по подразбиране е none + proxy: 127.0.0.1 # (string|string[]) по подразбиране не е зададено ``` -Сесия .[#toc-session] -===================== +Сесия +===== -Основни настройки на [сесията |sessions]: +Основни настройки на [сесиите|sessions]: ```neon session: - # показва панела на сесията в панела на Tracey? - debugger: ... # (bool) по подразбиране е false + # показване на панела за сесии в Tracy Bar? + debugger: ... # (bool) по подразбиране е false - # време на неактивност, след което сесията се прекратява - expiration: 14 days # (string) по подразбиране е '3 часа' + # период на неактивност, след който сесията изтича + expiration: 14 days # (string) по подразбиране е '3 hours' - # кога да започнем? - autoStart: ... # (smart|always|never) по подразбиране е 'smart' + # кога да се стартира сесията? + autoStart: ... # (smart|always|never) по подразбиране е 'smart' - # манипулатор, услуга, реализираща интерфейса SessionHandlerInterface + # handler, сървис, имплементиращ интерфейса SessionHandlerInterface handler: @handlerService ``` -Параметърът `autoStart` определя кога да започне сесията. Стойността `always` означава, че сесията ще се стартира винаги при стартиране на приложението. Стойността `smart` означава, че сесията ще се стартира само при стартиране на приложението, ако вече съществува, или когато искаме да четем от нея или да записваме в нея. И накрая, `never` деактивира автоматичното стартиране на сесията. +Опцията `autoStart` контролира кога да се стартира сесията. Стойността `always` означава, че сесията ще се стартира винаги при стартиране на приложението. Стойността `smart` означава, че сесията ще се стартира при стартиране на приложението само тогава, когато вече съществува, или в момента, в който искаме да четем от нея или да записваме в нея. И накрая стойността `never` забранява автоматичното стартиране на сесията. -Можете също така да посочите всички [директиви на |https://www.php.net/manual/en/session.configuration.php] PHP [сесията |https://www.php.net/manual/en/session.configuration.php] (във формат camelCase), както и [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. Пример: +Освен това могат да се настройват всички PHP [session директиви |https://www.php.net/manual/en/session.configuration.php] (във формат camelCase) и също [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. Пример: ```neon session: - # 'session.name' се записва като 'name' + # 'session.name' записваме като 'name' name: MYID - # 'session.save_path' се записва като 'savePath' + # 'session.save_path' записваме като 'savePath' savePath: "%tempDir%/sessions" ``` -Бисквитка за сесия .[#toc-session-cookie] ------------------------------------------ +Бисквитка за сесия +------------------ -Сесийната "бисквитка" се изпраща със същите параметри като [другите "бисквитки" |#HTTP-Cookie], но можете да ги промените за нея: +Бисквитката за сесия се изпраща със същите параметри като [други бисквитки |#HTTP бисквитки], но тези можете да промените за нея: ```neon session: - # на кои хостове е разрешено да получават "бисквитката - cookieDomain: 'example.com' # (string|домейн) + # домейни, които приемат бисквитката + cookieDomain: 'example.com' # (string|domain) - # ограничения на кръстосания достъп до заявките - cookieSamesite: None # (Strict|Lax|None) по подразбиране е Lax + # ограничение при достъп от друг домейн + cookieSamesite: None # (Strict|Lax|None) по подразбиране е Lax ``` -Опцията `cookieSamesite` влияе върху това дали се изпраща бисквитка при [заявки от различни сайтове |nette:glossary#SameSite-Cookie], което осигурява известна защита срещу атаки [Cross-Site Request Forgery |nette:glossary#Cross-Site-Request-Forgery-CSRF]. +Атрибутът `cookieSamesite` влияе дали бисквитката ще бъде изпратена при [достъп от друг домейн |nette:glossary#SameSite cookie], което предоставя известна защита срещу атаки [Cross-Site Request Forgery |nette:glossary#Cross-Site Request Forgery CSRF] (CSRF). + + +DI сървиси +========== + +Тези сървиси се добавят към DI контейнера: + +| Име | Тип | Описание +|----------------------------------------------------- +| `http.request` | [api:Nette\Http\Request] | [HTTP заявка| request] +| `http.response` | [api:Nette\Http\Response] | [HTTP отговор| response] +| `session.session` | [api:Nette\Http\Session] | [управление на сесии| sessions] diff --git a/http/bg/request.texy b/http/bg/request.texy index 44fd5475f7..a807ef95ff 100644 --- a/http/bg/request.texy +++ b/http/bg/request.texy @@ -2,84 +2,84 @@ HTTP заявка *********** .[perex] -Nette капсулира HTTP заявката в обекти с ясен API, като същевременно осигурява филтър за обработка. +Nette капсулира HTTP заявката в обекти с разбираем API и същевременно предоставя саниращ филтър. -HTTP заявката е обект [api:Nette\Http\Request], който получавате, като го предавате с помощта на [инжектиране на зависимости |dependency-injection:passing-dependencies]. В презентаторите просто извикайте `$httpRequest = $this->getHttpRequest()`. +HTTP заявката представлява обект [api:Nette\Http\Request]. Ако работите с Nette, този обект се създава автоматично от framework-а и можете да го получите чрез [dependency injection |dependency-injection:passing-dependencies]. В презентерите е достатъчно само да извикате метода `$this->getHttpRequest()`. Ако работите извън Nette Framework, можете да създадете обект с помощта на [#RequestFactory]. -Важното е, че при [създаването на |#RequestFactory] този обект Nette ще изчисти всички входни параметри GET, POST и COOKIE, както и URL адреса от контролни символи и невалидни UTF-8 последователности. Следователно можете спокойно да продължите да работите с данните. След това почистените данни се използват в презентатори и формуляри. +Голямо предимство на Nette е, че при създаването на обекта автоматично почиства всички входни параметри GET, POST, COOKIE, както и URL от контролни знаци и невалидни UTF-8 последователности. С тези данни след това можете безопасно да работите по-нататък. Почистените данни след това се използват в презентерите и формите. -→ [Монтаж и изисквания |@home#Installation] +→ [Инсталация и изисквания |@home#Инсталация] -Nette\Http\Запитване .[#toc-nette-http-request] -=============================================== +Nette\Http\Request +================== -Този обект е неизменен. Той няма setters, има само един т.нар. wither `withUrl()`, който не променя обекта, а връща нова инстанция с променената стойност. +Този обект е immutable (непроменлив). Няма никакви сетъри, има само един т.нар. wither `withUrl()`, който не променя обекта, а връща нов екземпляр с променена стойност. withUrl(Nette\Http\UrlScript $url): Nette\Http\Request .[method] ---------------------------------------------------------------- -Връща клонинг с различен URL адрес. +Връща клонинг с различен URL. getUrl(): Nette\Http\UrlScript .[method] ---------------------------------------- -Връща URL адреса на заявката като обект [UrlScript |urls#UrlScript]. +Връща URL на заявката като обект [UrlScript |urls#UrlScript]. ```php $url = $httpRequest->getUrl(); -echo $url; // https://nette.org/en/documentation?action=edit +echo $url; // https://doc.nette.org/cs/?action=edit echo $url->getHost(); // nette.org ``` -Браузърите не изпращат откъса към сървъра, така че `$url->getFragment()` ще върне празен низ. +Предупреждение: браузърите не изпращат фрагмент на сървъра, така че `$url->getFragment()` ще връща празен низ. -getQuery(string $key=null): string|array|null .[method] -------------------------------------------------------- -Връща параметрите на заявката GET: +getQuery(?string $key=null): string|array|null .[method] +-------------------------------------------------------- +Връща параметрите на GET заявката. ```php -$all = $httpRequest->getQuery(); // масив от всички параметри на URL +$all = $httpRequest->getQuery(); // връща масив с всички параметри от URL $id = $httpRequest->getQuery('id'); // връща GET параметър 'id' (или null) ``` -getPost(string $key=null): string|array|null .[method] ------------------------------------------------------- -Връща параметрите на заявката POST: +getPost(?string $key=null): string|array|null .[method] +------------------------------------------------------- +Връща параметрите на POST заявката. ```php -$all = $httpRequest->getPost(); // масив от всички POST параметри +$all = $httpRequest->getPost(); // връща масив с всички параметри от POST $id = $httpRequest->getPost('id'); // връща POST параметър 'id' (или null) ``` getFile(string|string[] $key): Nette\Http\FileUpload|array|null .[method] ------------------------------------------------------------------------- -Връща [качването |#Uploaded-Files] като обект [api:Nette\Http\FileUpload]: +Връща [качване |#Качени файлове] като обект [api:Nette\Http\FileUpload]: ```php $file = $httpRequest->getFile('avatar'); -if ($file->hasFile()) { // качен ли е някой файл? - $file->getUntrustedName(); // име на файла, изпратен от потребителя - $file->getSanitizedName(); //име без опасни символи +if ($file?->hasFile()) { // качен ли е файл? + $file->getUntrustedName(); // име на файла, изпратено от потребителя + $file->getSanitizedName(); // име без опасни символи } ``` -Посочете масив от ключове за достъп до структурата на поддървото. +За достъп до вложена структура посочете масив от ключове. ```php //<input type="file" name="my-form[details][avatar]" multiple> $file = $request->getFile(['my-form', 'details', 'avatar']); ``` -Тъй като не можете да се доверите на данните отвън и следователно не разчитате на формата на структурата, този метод е по-безопасен от `$request->getFiles()['my-form']['details']['avatar']`които могат да се провалят. +Тъй като не може да се вярва на данни отвън и следователно не може да се разчита на структурата на файловете, този начин е по-безопасен от например `$request->getFiles()['my-form']['details']['avatar']`, който може да се провали. getFiles(): array .[method] --------------------------- -Връща дърво на [файловете за разтоварване |#Uploaded-Files] в нормализирана структура, като всеки лист е инстанция на [api:Nette\Http\FileUpload]: +Връща дърво на [всички качвания |#Качени файлове] в нормализирана структура, чиито листа са обекти [api:Nette\Http\FileUpload]: ```php $files = $httpRequest->getFiles(); @@ -88,7 +88,7 @@ $files = $httpRequest->getFiles(); getCookie(string $key): string|array|null .[method] --------------------------------------------------- -Връща "бисквитка" или `null`, ако тя не съществува. +Връща бисквитка или `null`, когато не съществува. ```php $sessId = $httpRequest->getCookie('sess_id'); @@ -97,7 +97,7 @@ $sessId = $httpRequest->getCookie('sess_id'); getCookies(): array .[method] ----------------------------- -Връща всички бисквитки: +Връща всички бисквитки. ```php $cookies = $httpRequest->getCookies(); @@ -109,13 +109,13 @@ getMethod(): string .[method] Връща HTTP метода, с който е направена заявката. ```php -echo $httpRequest->getMethod(); // GET, POST, HEAD, PUT +$httpRequest->getMethod(); // GET, POST, HEAD, PUT ``` isMethod(string $method): bool .[method] ---------------------------------------- -Проверява HTTP метода, с който е направена заявката. Параметърът не е чувствителен към големи и малки букви. +Тества HTTP метода, с който е направена заявката. Параметърът е case-insensitive. ```php if ($httpRequest->isMethod('GET')) // ... @@ -124,7 +124,7 @@ if ($httpRequest->isMethod('GET')) // ... getHeader(string $header): ?string .[method] -------------------------------------------- -Връща HTTP заглавието или `null`, ако то не съществува. Параметърът не е чувствителен към големи и малки букви: +Връща HTTP хедър или `null`, ако не съществува. Параметърът е case-insensitive. ```php $userAgent = $httpRequest->getHeader('User-Agent'); @@ -133,7 +133,7 @@ $userAgent = $httpRequest->getHeader('User-Agent'); getHeaders(): array .[method] ----------------------------- -Връща всички HTTP заглавия като асоциативен масив: +Връща всички HTTP хедъри като асоциативен масив. ```php $headers = $httpRequest->getHeaders(); @@ -141,39 +141,34 @@ echo $headers['Content-Type']; ``` -getReferer(): ?Nette\Http\UrlImmutable .[method] ------------------------------------------------- -От кой URL адрес е дошъл потребителят? Внимавайте, това изобщо не е надеждно. - - isSecured(): bool .[method] --------------------------- -Криптирана ли е връзката (HTTPS)? Може да се наложи да конфигурирате [прокси сървър |configuration#HTTP-Proxy], за да работи правилно. +Връзката шифрована ли е (HTTPS)? За правилната функционалност може да е необходимо [да се настрои прокси |configuration#HTTP прокси]. isSameSite(): bool .[method] ---------------------------- -Заявката идва от същия (под)домейн и се инициира от кликване върху връзка? Nette използва бисквитката `_nss` (преди `nette-samesite`), за да определи това. +Заявката идва ли от същия (под)домейн и е инициирана чрез кликване върху връзка? Nette използва бисквитката `_nss` (преди `nette-samesite`) за откриване. isAjax(): bool .[method] ------------------------ -Това заявка AJAX ли е? +Това AJAX заявка ли е? getRemoteAddress(): ?string .[method] ------------------------------------- -Връща IP адреса на потребителя. Може да се наложи да настроите [прокси сървър |configuration#HTTP-Proxy], за да работи правилно. +Връща IP адреса на потребителя. За правилната функционалност може да е необходимо [да се настрои прокси |configuration#HTTP прокси]. getRemoteHost(): ?string .[method deprecated] --------------------------------------------- -Връща DNS превода на IP адреса на потребителя. Може да се наложи да настроите [прокси сървър |configuration#HTTP-Proxy], за да работи правилно. +Връща DNS превода на IP адреса на потребителя. За правилната функционалност може да е необходимо [да се настрои прокси |configuration#HTTP прокси]. -getBasicCredentials(): ?string .[method] ----------------------------------------- -Връща [основните идентифика |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication] ционни данни за [удостоверяване по HTTP |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication]. +getBasicCredentials(): ?array .[method] +--------------------------------------- +Връща данните за удостоверяване за [Basic HTTP authentication |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication]. ```php [$user, $password] = $httpRequest->getBasicCredentials(); @@ -182,7 +177,7 @@ getBasicCredentials(): ?string .[method] getRawBody(): ?string .[method] ------------------------------- -Връща тялото на HTTP заявката: +Връща тялото на HTTP заявката. ```php $body = $httpRequest->getRawBody(); @@ -191,48 +186,55 @@ $body = $httpRequest->getRawBody(); detectLanguage(array $langs): ?string .[method] ----------------------------------------------- -Определя езика. Предаваме масив от езици, които приложението поддържа, като параметър на `$lang` и той връща предпочитания от браузъра език. Това не е магия, методът просто използва заглавието `Accept-Language`. Ако не бъде намерено съвпадение, се връща `null`. +Открива езика. Като параметър `$lang` предаваме масив с езиците, които приложението поддържа, и тя връща този, който браузърът на посетителя би предпочел най-много. Това не са никакви магии, просто се използва хедърът `Accept-Language`. Ако не се намери съвпадение, връща `null`. ```php -// Заглавие, изпратено от браузъра: Accept-Language: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3 +// браузърът изпраща напр. Accept-Language: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3 $langs = ['hu', 'pl', 'en']; // езици, поддържани от приложението echo $httpRequest->detectLanguage($langs); // en ``` -RequestFactory .[#toc-requestfactory] -===================================== +RequestFactory +============== -Обектът на текущата HTTP заявка се създава с помощта на [api:Nette\Http\RequestFactory]. Ако пишете приложение, което не използва DI-контейнер, създавате заявката по следния начин: +Класът [api:Nette\Http\RequestFactory] служи за създаване на екземпляр на `Nette\Http\Request`, който представлява текущата HTTP заявка. (Ако работите с Nette, обектът на HTTP заявката се създава автоматично от framework-а.) ```php $factory = new Nette\Http\RequestFactory; $httpRequest = $factory->fromGlobals(); ``` -RequestFactory може да бъде конфигурирана преди извикването на `fromGlobals()`. Можем да деактивираме цялата обработка на входните параметри от невалидни UTF-8 последователности, като използваме `$factory->setBinary()`. Също така конфигурирайте прокси сървъра, което е важно за правилното идентифициране на IP адреса на потребителя с помощта на `$factory->setProxy(...)`. +Методът `fromGlobals()` създава обект на заявката въз основа на текущите глобални променливи на PHP (`$_GET`, `$_POST`, `$_COOKIE`, `$_FILES` и `$_SERVER`). При създаването на обекта автоматично почиства всички входни параметри GET, POST, COOKIE, както и URL от контролни знаци и невалидни UTF-8 последователности, което осигурява безопасност при по-нататъшна работа с тези данни. + +RequestFactory може да се конфигурира преди извикването на `fromGlobals()`: -Можем да използваме филтри, за да изчистим URL адресите от знаци, които могат да попаднат в тях поради лошо реализирани системи за коментари в различни други сайтове: +- с метода `$factory->setBinary()` изключвате автоматичното почистване на входните параметри от контролни знаци и невалидни UTF-8 последователности. +- с метода `$factory->setProxy(...)` посочвате IP адреса на [прокси сървъра |configuration#HTTP прокси], което е необходимо за правилното откриване на IP адреса на потребителя. + +RequestFactory позволява да се дефинират филтри, които автоматично трансформират части от URL на заявката. Тези филтри премахват нежелани знаци от URL, които могат да бъдат вмъкнати там например поради неправилна имплементация на системи за коментари на различни сайтове: ```php -// премахване на интервалите от пътя +// премахване на интервали от пътя $requestFactory->urlFilters['path']['%20'] = ''; -// премахване на точката, запетаята или дясната скоба в края на URL адреса -$requestFactory->urlFilters['url']['['[.,)]$'] = ''; +// премахване на точка, запетая или дясна скоба от края на URI +$requestFactory->urlFilters['url']['[.,)]$'] = ''; -// изчистване на пътя от дублиращи се наклонени черти (филтър по подразбиране) +// почистване на пътя от двойни наклонени черти (филтър по подразбиране) $requestFactory->urlFilters['path']['/{2,}'] = '/'; ``` +Първият ключ `'path'` или `'url'` определя към коя част на URL ще се приложи филтърът. Вторият ключ е регулярен израз, който трябва да се търси, а стойността е заместителят, който ще се използва вместо намерения текст. + -Качени файлове .[#toc-uploaded-files] -===================================== +Качени файлове +============== -Методът `Nette\Http\Request::getFiles()` връща дърво от изтеглени файлове в нормализирана структура, като всеки лист е инстанция на [api:Nette\Http\FileUpload]. Тези обекти капсулират данните, представени от `<input type=file>` елемент на формата. +Методът `Nette\Http\Request::getFiles()` връща масив от всички качвания в нормализирана структура, чиито листа са обекти [api:Nette\Http\FileUpload]. Те капсулират данните, изпратени от елемента на формата `<input type=file>`. -Структурата отразява наименованието на елементите в HTML. В най-простия пример това може да бъде един именуван елемент на формуляра, представен като: +Структурата отразява именуването на елементите в HTML. В най-простия случай това може да бъде единствен именуван елемент на формата, изпратен като: ```latte <input type="file" name="avatar"> @@ -246,19 +248,19 @@ $requestFactory->urlFilters['path']['/{2,}'] = '/'; ] ``` -Обектът `FileUpload` се създава, дори ако потребителят не е качил никакъв файл или качването е неуспешно. Методът `hasFile()` връща true, ако файлът е бил качен: +Обектът `FileUpload` се създава и в случай, че потребителят не е изпратил никакъв файл или изпращането е неуспешно. Дали файлът е бил изпратен връща методът `hasFile()`: ```php -$request->getFile('avatar')->hasFile(); +$request->getFile('avatar')?->hasFile(); ``` -В случай на въвеждане, при което се използва масивна нотация за името: +В случай на име на елемент, използващо нотация за масив: ```latte <input type="file" name="my-form[details][avatar]"> ``` -дървото, което трябва да се върне, е следното: +върнатото дърво изглежда така: ```php [ @@ -270,13 +272,13 @@ $request->getFile('avatar')->hasFile(); ] ``` -Можете също така да създавате масиви от файлове: +Може да се създаде и масив от файлове: ```latte -<input type="file" name="my-form[details][avatars][] multiple"> +<input type="file" name="my-form[details][avatars][]" multiple> ``` -В този случай структурата изглежда по следния начин +В такъв случай структурата изглежда така: ```php [ @@ -292,7 +294,7 @@ $request->getFile('avatar')->hasFile(); ] ``` -Най-добрият начин за достъп до индекс 1 на вложен масив е следният: +Достъпът до индекс 1 на вложения масив се осъществява най-добре така: ```php $file = $request->getFile(['my-form', 'details', 'avatars', 1]); @@ -301,31 +303,31 @@ if ($file instanceof FileUpload) { } ``` -Тъй като не можете да се доверите на външните данни и следователно не разчитате на формата на структурата, този метод е по-безопасен от `$request->getFiles()['my-form']['details']['avatars'][1]`което може да не работи. +Тъй като не може да се вярва на данни отвън и следователно не може да се разчита на структурата на файловете, този начин е по-безопасен от например `$request->getFiles()['my-form']['details']['avatars'][1]`, който може да се провали. -Преглед на методите `FileUpload` .{toc: FileUpload} ---------------------------------------------------- +Преглед на методите на `FileUpload` .{toc: FileUpload} +------------------------------------------------------ hasFile(): bool .[method] ------------------------- -Връща `true`, ако потребителят е качил файл. +Връща `true`, ако потребителят е качил някакъв файл. isOk(): bool .[method] ---------------------- -Връща `true`, ако файлът е изтеглен успешно. +Връща `true`, ако файлът е бил качен успешно. getError(): int .[method] ------------------------- -Връща кода за грешка, свързан с качения файл. Това може да бъде една от константите [UPLOAD_ERR_XXX |http://php.net/manual/en/features.file-upload.errors.php]. Ако файлът е бил качен успешно, се връща `UPLOAD_ERR_OK`. +Връща кода на грешката при качване на файла. Това е една от константите [UPLOAD_ERR_XXX|http://php.net/manual/en/features.file-upload.errors.php]. В случай, че качването е преминало успешно, връща `UPLOAD_ERR_OK`. move(string $dest) .[method] ---------------------------- -Премества качения файл на ново място. Ако файлът на дестинацията вече съществува, той ще бъде презаписан. +Премества качения файл на ново място. Ако целевият файл вече съществува, той ще бъде презаписан. ```php $file->move('/path/to/files/name.ext'); @@ -334,61 +336,72 @@ $file->move('/path/to/files/name.ext'); getContents(): ?string .[method] -------------------------------- -Връща съдържанието на качения файл. Ако качването не е било успешно, се връща `null`. +Връща съдържанието на качения файл. В случай, че качването не е било успешно, връща `null`. getContentType(): ?string .[method] ----------------------------------- -Определя типа на съдържанието MIME на изтегления файл въз основа на неговата сигнатура. Ако качването не е било успешно или откриването е било неуспешно, се връща `null`. +Открива MIME content type на качения файл въз основа на неговата сигнатура. В случай, че качването не е било успешно или откриването не е успяло, връща `null`. .[caution] -Изисква разширението на PHP `fileinfo`. +Изисква PHP разширението `fileinfo`. getUntrustedName(): string .[method] ------------------------------------ -Връща оригиналното име на файла, както е предадено от браузъра. +Връща оригиналното име на файла, както го е изпратил браузърът. .[caution] -Не се доверявайте на стойността, върната от този метод. Клиентът може да изпрати злонамерено име на файл с намерението да повреди или хакне вашето приложение. +Не вярвайте на стойността, върната от този метод. Клиентът може да е изпратил злонамерено име на файл с намерение да повреди или хакне вашето приложение. getSanitizedName(): string .[method] ------------------------------------ -Връща обработено име на файл. Той съдържа само ASCII символи. `[a-zA-Z0-9.-]`. Ако името не съдържа такива знаци, се връща символът 'unknown'. Ако файлът е JPEG, PNG, GIF или WebP изображение, се връща правилното разширение на файла. +Връща санираното име на файла. Съдържа само ASCII знаци `[a-zA-Z0-9.-]`. Ако името не съдържа такива знаци, връща `'unknown'`. Ако файлът е изображение във формат JPEG, PNG, GIF, WebP или AVIF, връща и правилното разширение. + +.[caution] +Изисква PHP разширението `fileinfo`. + + +getSuggestedExtension(): ?string .[method]{data-version:3.2.4} +-------------------------------------------------------------- +Връща подходящо разширение на файла (без точка), съответстващо на открития MIME тип. + +.[caution] +Изисква PHP разширението `fileinfo`. getUntrustedFullPath(): string .[method] ---------------------------------------- -Връща оригиналния пълен път, зададен от браузъра при зареждането на директорията. Пълният път е наличен само в PHP 8.1 и по-нови версии. В предишните версии този метод връща ненадеждно име на файл. +Връща оригиналния път до файла, както го е изпратил браузърът при качване на папка. Целият път е достъпен само в PHP 8.1 и по-нови версии. В предишни версии този метод връща оригиналното име на файла. .[caution] -Не се доверявайте на стойността, върната от този метод. Клиентът може да изпрати злонамерено име на файл с намерението да повреди или отвлече вашето приложение. +Не вярвайте на стойността, върната от този метод. Клиентът може да е изпратил злонамерено име на файл с намерение да повреди или хакне вашето приложение. getSize(): int .[method] ------------------------ -Връща размера на качения файл. Ако качването не е било успешно, се връща `0`. +Връща размера на качения файл. В случай, че качването не е било успешно, връща `0`. getTemporaryFile(): string .[method] ------------------------------------ -Връща пътя до временното местоположение на изтегления файл. Ако качването не е било успешно, се връща `''`. +Връща пътя до временната локация на качения файл. В случай, че качването не е било успешно, връща `''`. isImage(): bool .[method] ------------------------- -Връща `true`, ако каченият файл е изображение JPEG, PNG, GIF или WebP. Откриването се основава на неговия подпис. Целостта на целия файл не се проверява. Можете да разберете дали дадено изображение е повредено, например като се опитате да [го изтеглите |#toImage]. +Връща `true`, ако каченият файл е изображение във формат JPEG, PNG, GIF, WebP или AVIF. Откриването се извършва въз основа на неговата сигнатура и не се проверява целостта на целия файл. Дали изображението не е повредено може да се установи например чрез опит за неговото [зареждане |#toImage]. .[caution] -Изисква разширението на PHP `fileinfo`. +Изисква PHP разширението `fileinfo`. getImageSize(): ?array .[method] -------------------------------- -Връща двойка от `[width, height]` с размерите на каченото изображение. Ако качването не е било успешно или изображението не е валидно, се връща `null`. +Връща двойка `[ширина, височина]` с размерите на каченото изображение. В случай, че качването не е било успешно или не е валидно изображение, връща `null`. toImage(): Nette\Utils\Image .[method] -------------------------------------- -Зарежда изображението като обект [Image |utils:images]. Ако качването е неуспешно или изображението не е валидно, се изхвърля изключение на адрес `Nette\Utils\ImageException`. +Зарежда изображението като обект [Image|utils:images]. В случай, че качването не е било успешно или не е валидно изображение, хвърля изключение `Nette\Utils\ImageException`. diff --git a/http/bg/response.texy b/http/bg/response.texy index 53743a6bd4..461e66a71a 100644 --- a/http/bg/response.texy +++ b/http/bg/response.texy @@ -2,22 +2,22 @@ HTTP отговор ************ .[perex] -Nette капсулира HTTP отговора в обекти с ясен API, като същевременно осигурява филтър за обработка. +Nette капсулира HTTP отговора в обекти с разбираем API. -Отговорът на HTTP е обект [api:Nette\Http\Response], който получавате, като го предавате с помощта на [инжектиране на зависимости |dependency-injection:passing-dependencies]. В презентаторите просто извикайте `$httpResponse = $this->getHttpResponse()`. +HTTP отговорът представлява обект [api:Nette\Http\Response]. Ако работите с Nette, този обект се създава автоматично от framework-а и можете да го получите чрез [dependency injection |dependency-injection:passing-dependencies]. В презентерите е достатъчно само да извикате метода `$this->getHttpResponse()`. -→ [Монтаж и изисквания |@home#Installation] +→ [Инсталация и изисквания |@home#Инсталация] -Нетте\Http\Response .[#toc-nette-http-response] -=============================================== +Nette\Http\Response +=================== -За разлика от [Nette\Http\Request |request], този обект е променлив, така че можете да използвате сетъри, за да промените състоянието, т.е. да изпратите заглавия. Не забравяйте, че всички задаващи устройства **трябва да бъдат извикани преди да бъде изпратен какъвто и да е действителен изход.** Методът `isSent()` показва дали изходът е изпратен. Ако той връща `true`, всеки опит за изпращане на заглавие хвърля изключение `Nette\InvalidStateException`. +Обектът, за разлика от [Nette\Http\Request|request], е mutable, т.е. с помощта на сетъри можете да променяте състоянието, например да изпращате хедъри. Не забравяйте, че всички сетъри трябва да бъдат извикани **преди изпращането на какъвто и да е изход.** Дали вече е бил изпратен изход показва методът `isSent()`. Ако връща `true`, всеки опит за изпращане на хедър ще предизвика изключение `Nette\InvalidStateException`. -setCode(int $code, string $reason=null) .[method] -------------------------------------------------- -Променя [кода на отговора на |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10] състоянието. За по-добра четливост на изходния код се препоръчва да се използват [предварително дефинирани константи |api:Nette\Http\IResponse] вместо реални числа. +setCode(int $code, ?string $reason=null) .[method] +-------------------------------------------------- +Променя [кода на състоянието на отговора |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10]. За по-добра разбираемост на изходния код препоръчваме за кода да се използват вместо числа [предварително дефинирани константи |api:Nette\Http\IResponse]. ```php $httpResponse->setCode(Nette\Http\Response::S404_NotFound); @@ -31,12 +31,12 @@ getCode(): int .[method] isSent(): bool .[method] ------------------------ -Връща информация дали заглавията вече са били изпратени от сървъра към браузъра, така че вече не е възможно да се изпращат заглавия или да се променя кодът на състоянието. +Връща дали вече са били изпратени хедъри от сървъра към браузъра и следователно вече не е възможно да се изпращат хедъри или да се променя кодът на състоянието. setHeader(string $name, string $value) .[method] ------------------------------------------------ -Изпраща HTTP хедър и **заменя** изпратения преди това хедър със същото име. +Изпраща HTTP хедър и **презаписва** предишно изпратен хедър със същото име. ```php $httpResponse->setHeader('Pragma', 'no-cache'); @@ -45,7 +45,7 @@ $httpResponse->setHeader('Pragma', 'no-cache'); addHeader(string $name, string $value) .[method] ------------------------------------------------ -Изпраща HTTP заглавие и **не презаписва** изпратеното преди това заглавие със същото име. +Изпраща HTTP хедър и **не презаписва** предишно изпратен хедър със същото име. ```php $httpResponse->addHeader('Accept', 'application/json'); @@ -55,12 +55,12 @@ $httpResponse->addHeader('Accept', 'application/xml'); deleteHeader(string $name) .[method] ------------------------------------ -Изтрива изпратен преди това HTTP хедър. +Изтрива предишно изпратен HTTP хедър. getHeader(string $header): ?string .[method] -------------------------------------------- -Връща изпратения HTTP хедър или `null`, ако той не съществува. Параметърът не е чувствителен към големи и малки букви. +Връща изпратен HTTP хедър или `null`, ако такъв не съществува. Параметърът е case-insensitive. ```php $pragma = $httpResponse->getHeader('Pragma'); @@ -69,7 +69,7 @@ $pragma = $httpResponse->getHeader('Pragma'); getHeaders(): array .[method] ----------------------------- -Връща всички изпратени HTTP заглавия като асоциативен масив. +Връща всички изпратени HTTP хедъри като асоциативен масив. ```php $headers = $httpResponse->getHeaders(); @@ -77,9 +77,9 @@ echo $headers['Pragma']; ``` -setContentType(string $type, string $charset=null) .[method] ------------------------------------------------------------- -Изпраща заглавието `Content-Type`. +setContentType(string $type, ?string $charset=null) .[method] +------------------------------------------------------------- +Променя хедъра `Content-Type`. ```php $httpResponse->setContentType('text/plain', 'UTF-8'); @@ -88,7 +88,7 @@ $httpResponse->setContentType('text/plain', 'UTF-8'); redirect(string $url, int $code=self::S302_Found): void .[method] ----------------------------------------------------------------- -Пренасочва към друг URL адрес. Не забравяйте да излезете от скрипта след това. +Пренасочва към друг URL. Не забравяйте след това да прекратите скрипта. ```php $httpResponse->redirect('http://example.com'); @@ -98,52 +98,52 @@ exit; setExpiration(?string $time) .[method] -------------------------------------- -Задава времето на изтичане на HTTP документ, като използва заглавията `Cache-Control` и `Expires`. Параметърът е или интервал от време (като текст), или `null`, който деактивира кеширането. +Задава изтичането на HTTP документа с помощта на хедърите `Cache-Control` и `Expires`. Параметърът е или времеви интервал (като текст), или `null`, което забранява кеширането. ```php -// кешът на браузъра изтича след един час +// кешът в браузъра изтича след час $httpResponse->setExpiration('1 hour'); ``` sendAsFile(string $fileName) .[method] -------------------------------------- -Отговорът трябва да бъде качен с помощта на диалоговия прозорец *Саѕе As* с посоченото име. Самият файл не се изпраща за извеждане. +Отговорът ще бъде изтеглен с помощта на диалоговия прозорец *Запиши като* под посоченото име. Самият файл при това не се изпраща. ```php -$httpResponse->sendAsFile('invoice.pdf'); +$httpResponse->sendAsFile('faktura.pdf'); ``` -setCookie(string $name, string $value, $time, string $path=null, string $domain=null, bool $secure=null, bool $httpOnly=null, string $sameSite=null) .[method] --------------------------------------------------------------------------------------------------------------------------------------------------------------- -Изпраща бисквитка. Стойности на параметрите по подразбиране: +setCookie(string $name, string $value, $time, ?string $path=null, ?string $domain=null, ?bool $secure=null, ?bool $httpOnly=null, ?string $sameSite=null) .[method] +------------------------------------------------------------------------------------------------------------------------------------------------------------------- +Изпраща бисквитка. Стойностите по подразбиране на параметрите: -| `$path` | `'/'` | с покритие на всички пътища в (под)домейна *(може да се конфигурира)*. -| `$domain` | `null` | с покритие на текущия (под)домейн, но не и на неговите поддомейни *(може да се конфигурира)*. -| `$secure` | `true` | ако сайтът работи с HTTPS, в противен случай `false` *(може да се конфигурира)*. -| `$httpOnly` | `true` | бисквитките не са налични за JavaScript -| `$sameSite` | `'Lax'` | не е необходимо да се изпраща бисквитка при [достъп от друг източник |nette:glossary#SameSite-Cookie] +| `$path` | `'/'` | бисквитката има обхват за всички пътища в (под)домейна *(конфигурируемо)* +| `$domain` | `null` | което означава с обхват за текущия (под)домейн, но не и неговите поддомейни *(конфигурируемо)* +| `$secure` | `true` | ако сайтът работи на HTTPS, иначе `false` *(конфигурируемо)* +| `$httpOnly` | `true` | бисквитката е недостъпна за JavaScript +| `$sameSite` | `'Lax'` | бисквитката може да не бъде изпратена при [достъп от друг домейн |nette:glossary#SameSite cookie] -Можете да промените стойностите по подразбиране за `$path`, `$domain` и `$secure` в [конфигурацията |configuration#HTTP-Cookie]. +Стойностите по подразбиране на параметрите `$path`, `$domain` и `$secure` можете да промените в [конфигурацията |configuration#HTTP бисквитки]. -Времето може да бъде зададено като брой секунди или като низ: +Времето може да се посочва като брой секунди или низ: ```php -$httpResponse->setCookie('lang', 'en', '100 days'); +$httpResponse->setCookie('lang', 'bg', '100 days'); ``` -Параметърът `$domain` определя кои домейни (на произход) могат да приемат "бисквитката". Ако параметърът не е посочен, "бисквитката" се приема от същия (под)домейн, който е посочен, с изключение на техните поддомейни. Ако е посочен `$domain`, поддомейните също се включват. Така че посочването на `$domain` е по-малко ограничително, отколкото пропускането му. Например, ако `$domain = 'nette.org'`, "бисквитката" е достъпна и на всички поддомейни като `doc.nette.org`. +Параметърът `$domain` определя кои домейни могат да приемат бисквитката. Ако не е посочен, бисквитката се приема от същия (под)домейн, който я е задал, но не и от неговите поддомейни. Ако `$domain` е зададен, са включени и поддомейните. Затова посочването на `$domain` е по-малко ограничаващо от пропускането му. Например при `$domain = 'nette.org'` бисквитките са достъпни и на всички поддомейни като `doc.nette.org`. -Константите `Response::SameSiteLax`, `SameSiteStrict` и `SameSiteNone` могат да се използват за стойността `$sameSite`. +За стойността `$sameSite` можете да използвате константите `Response::SameSiteLax`, `SameSiteStrict` и `SameSiteNone`. -deleteCookie(string $name, string $path=null, string $domain=null, bool $secure=null): void .[method] ------------------------------------------------------------------------------------------------------ -Изтрива "бисквитката". Настройките по подразбиране имат следните стойности: -- `$path` с обхват върху всички директории (`'/'`) -- `$domain` с обхват в текущия (под)домейн, но не и в неговите поддомейни. -- `$secure` зависи от настройките в [конфигурацията |configuration#HTTP-Cookie]. +deleteCookie(string $name, ?string $path=null, ?string $domain=null, ?bool $secure=null): void .[method] +-------------------------------------------------------------------------------------------------------- +Изтрива бисквитка. Стойностите по подразбиране на параметрите са: +- `$path` с обхват за всички директории (`'/'`) +- `$domain` с обхват за текущия (под)домейн, но не и неговите поддомейни +- `$secure` се управлява според настройките в [конфигурацията |configuration#HTTP бисквитки] ```php $httpResponse->deleteCookie('lang'); diff --git a/http/bg/sessions.texy b/http/bg/sessions.texy index 03fb06e43e..73b3cb8c28 100644 --- a/http/bg/sessions.texy +++ b/http/bg/sessions.texy @@ -3,69 +3,69 @@ <div class=perex> -HTTP е протокол без статични данни, но почти всяко приложение трябва да съхранява състояние между заявките, напр. съдържанието на количка за пазаруване. Именно за това е сесията. Нека разгледаме +HTTP е протокол без състояние, но почти всяко приложение трябва да съхранява състояние между заявките, например съдържанието на количката за пазаруване. Именно за това служат сесиите. Ще покажем, -- как да използвате сесиите -- как да избегнете конфликти на имена -- как да зададете дата на изтичане на срока на валидност +- как да използваме сесии +- как да предотвратим конфликти на имена +- как да настроим изтичане </div> -Когато се използват сесии, на всеки потребител се дава уникален идентификатор, наречен идентификатор на сесия, който се предава на "бисквитката". Той служи като ключ към данните за сесията. За разлика от "бисквитките", които се съхраняват от страна на браузъра, данните за сесията се съхраняват от страна на сървъра. +При използване на сесии всеки потребител получава уникален идентификатор, наречен session ID, който се предава в бисквитка. Той служи като ключ към данните на сесията. За разлика от бисквитките, които се съхраняват от страна на браузъра, данните в сесията се съхраняват от страна на сървъра. -Настройваме сесията в [конфигурацията |configuration#Session], като изборът на време на изтичане е важен. +Сесията се настройва в [конфигурацията |configuration#Сесия], важен е особено изборът на времето за изтичане. -Сесията се управлява от обекта [api:Nette\Http\Session], който получавате, като го предавате чрез [инжектиране на зависимости |dependency-injection:passing-dependencies]. В презентаторите наричаме `$session = $this->getSession()`. +Управлението на сесията се осъществява от обекта [api:Nette\Http\Session], до който можете да стигнете, като го получите чрез [dependency injection |dependency-injection:passing-dependencies]. В презентерите е достатъчно само да извикате `$session = $this->getSession()`. -→ [Монтаж и изисквания |@home#Installation] +→ [Инсталация и изисквания |@home#Инсталация] -Стартираща сесия .[#toc-starting-session] -========================================= +Стартиране на сесия +=================== -По подразбиране Nette автоматично стартира сесия в момента, в който започнем да четем от нея или да записваме данни в нея. За да стартирате сесия ръчно, използвайте `$session->start()`. +Nette по подразбиране автоматично стартира сесията в момента, когато започнем да четем от нея или да записваме данни в нея. Ръчно сесията се стартира с `$session->start()`. -PHP изпраща HTTP хедъри, които влияят на кеширането, когато сесията започне, вж. [php:session_cache_limiter] и евентуално бисквитка с идентификатор на сесията. Затова винаги трябва да стартирате сесията, преди да изпратите какъвто и да е изход към браузъра, в противен случай ще бъде хвърлено изключение. Затова, ако знаете, че сесията ще се използва по време на визуализирането на страницата, стартирайте я ръчно, например в презентатора. +PHP изпраща при стартиране на сесията HTTP хедъри, влияещи на кеширането, виж [php:session_cache_limiter], и евентуално и бисквитка със session ID. Затова е необходимо винаги да стартирате сесията преди изпращането на какъвто и да е изход към браузъра, иначе ще бъде хвърлено изключение. Ако знаете, че по време на рендирането на страницата ще се използва сесия, стартирайте я ръчно преди това, например в презентера. -В режим за разработчици Tracy стартира сесията, тъй като я използва за показване на ленти за пренасочване и AJAX заявки в панела Tracy. +В режим на разработка сесията се стартира от Tracy, тъй като я използва за показване на ленти с пренасочвания и AJAX заявки в Tracy Bar. -Раздел .[#toc-section] -====================== +Секции +====== -В чистия език PHP съхранението на данни за сесията се реализира като масив, достъпен чрез глобалната променлива `$_SESSION`. Проблемът се състои в това, че приложенията обикновено се състоят от няколко независими части и ако всички разполагат само с един и същ масив, рано или късно ще се стигне до колизии на имена. +В чист PHP хранилището на данни на сесията се реализира като масив, достъпен чрез глобалната променлива `$_SESSION`. Проблемът е, че приложенията обикновено се състоят от цяла редица взаимно независими части и ако всички имат на разположение само един масив, рано или късно ще възникне конфликт на имена. -Рамката на Nette решава този проблем, като разделя цялото пространство на секции (обекти [api:Nette\Http\SessionSection]). В този случай всяка част използва свой собствен раздел с уникално име и не се получават колизии. +Nette Framework решава проблема, като разделя цялото пространство на секции (обекти [api:Nette\Http\SessionSection]). Всяка единица след това използва своя собствена секция с уникално име и вече не може да възникне никакъв конфликт. -Получаваме дяла от мениджъра на сесии: +Секцията получаваме от сесията: ```php -$section = $session->getSection('unique name'); +$section = $session->getSection('уникално_име'); ``` -Всичко, което трябва да направите в презентатора, е да извикате `getSession()` с параметъра: +В презентера е достатъчно да използвате `getSession()` с параметър: ```php -// $this - Представящ -$section = $this->getSession('unique name'); +// $this е Presenter +$section = $this->getSession('уникално_име'); ``` -Съществуването на дяла може да се провери по метода `$session->hasSection('unique name')`. +Проверката за съществуване на секция може да се направи с метода `$session->hasSection('уникално_име')`. -Самият дял е много лесен за работа с помощта на методите `set()`, `get()` и `remove()`: +Със самата секция след това се работи много лесно с помощта на методите `set()`, `get()` и `remove()`: ```php -// писане на променлива +// запис на променлива $section->set('userName', 'franta'); -// четене на променливата, връща null, ако не съществува +// четене на променлива, връща null, ако не съществува echo $section->get('userName'); -//премахване на променлива +// изтриване на променлива $section->remove('userName'); ``` -Можете да използвате цикъла `foreach`, за да извлечете всички променливи от секцията: +За получаване на всички променливи от секцията може да се използва цикъл `foreach`: ```php foreach ($section as $key => $val) { @@ -74,17 +74,17 @@ foreach ($section as $key => $val) { ``` -Как да зададете дата на изтичане .[#toc-how-to-set-expiration] --------------------------------------------------------------- +Настройка на изтичане +--------------------- -Срокът на валидност може да бъде зададен за отделни раздели или дори за отделни променливи. Можем да позволим на потребителския вход да изтече след 20 минути, но да запазим съдържанието на кошницата. +За отделни секции или дори отделни променливи е възможно да се настрои изтичане. Можем така да оставим изтичането на влизането на потребителя след 20 минути, но същевременно да продължим да помним съдържанието на количката. ```php -// срокът на действие на раздела изтича след 20 минути +// секцията изтича след 20 минути $section->setExpiration('20 minutes'); ``` -Третият параметър на метода `set()` се използва за задаване на датата на изтичане на срока на валидност на отделните променливи: +За настройка на изтичането на отделни променливи служи третият параметър на метода `set()`: ```php // променливата 'flash' изтича след 30 секунди @@ -92,26 +92,26 @@ $section->set('flash', $message, '30 seconds'); ``` .[note] -Не забравяйте, че времето на изтичане на цялата сесия (вж. [Конфигурация на сесията |configuration#Session]) трябва да е равно или по-голямо от времето, зададено за отделните секции или променливи. +Не забравяйте, че времето за изтичане на цялата сесия (виж [конфигурация на сесията |configuration#Сесия]) трябва да бъде същото или по-голямо от времето, зададено за отделните секции или променливи. -Отмяна на предварително зададено време на изтичане може да се осъществи чрез метода `removeExpiration()`. Незабавното изтриване на цял раздел се осигурява от метода `remove()`. +Отмяната на предишно зададено изтичане се постига с метода `removeExpiration()`. Незабавното отменяне на цялата секция осигурява методът `remove()`. -Събития $onStart, $onBeforeWrite .[#toc-events-onstart-onbeforewrite] ---------------------------------------------------------------------- +Събития $onStart, $onBeforeWrite +-------------------------------- -Обектът `Nette\Http\Session` има [събития |nette:glossary#Events] `$onStart` и `$onBeforeWrite`, така че можете да добавите обратни извиквания, които ще бъдат извикани след стартирането на сесията или преди тя да бъде записана на диска и след това завършена. +Обектът `Nette\Http\Session` има [събития |nette:glossary#Събития events] `$onStart` и `$onBeforeWrite`, така че можете да добавите callback-ове, които се извикват след стартиране на сесията или преди нейното записване на диска и последващо прекратяване. ```php $session->onBeforeWrite[] = function () { - // запис на данни в сесията + // записваме данни в сесията $this->section->set('basket', $this->basket); }; ``` -Управление на сесиите .[#toc-session-management] -================================================ +Управление на сесии +=================== Преглед на методите на класа `Nette\Http\Session` за управление на сесии: @@ -125,86 +125,87 @@ start(): void .[method] isStarted(): bool .[method] --------------------------- -Работи ли сесията? +Сесията стартирана ли е? close(): void .[method] ----------------------- -Приключва сесията. Сесията приключва автоматично в края на скрипта. +Прекратява сесията. Сесията автоматично се прекратява в края на изпълнението на скрипта. destroy(): void .[method] ------------------------- -Приключва и изтрива сесията. +Прекратява и изтрива сесията. exists(): bool .[method] ------------------------ -Съдържа ли HTTP заявката "бисквитка" с идентификатор на сесията? +HTTP заявката съдържа ли бисквитка със session ID? regenerateId(): void .[method] ------------------------------ -Генерира нов случаен идентификатор на сесия. Данните остават непроменени. +Генерира нов случаен session ID. Данните остават запазени. getId(): string .[method] ------------------------- -Връща идентификатора на сесията. +Връща session ID. </div> -Конфигурация .[#toc-configuration] ----------------------------------- +Конфигурация +------------ -Конфигурираме сесията в [конфигурацията |configuration#Session]. Ако пишете приложение, което не използва DI-контейнер, използвайте тези методи, за да го конфигурирате. Те трябва да бъдат извикани преди стартирането на сесията. +Сесията се настройва в [конфигурацията |configuration#Сесия]. Ако пишете приложение, което не използва DI контейнер, за конфигурация служат тези методи. Трябва да бъдат извикани преди стартирането на сесията. <div class=wiki-methods-brief> setName(string $name): static .[method] --------------------------------------- -Задава името на "бисквитката", използвана за предаване на идентификатора на сесията. Името по подразбиране е `PHPSESSID`. Това е полезно, ако използвате няколко различни приложения в един и същи сайт. +Задава името на бисквитката, в която се пренася session ID. Стандартното име е `PHPSESSID`. Полезно е в случай, че в рамките на един сайт поддържате няколко различни приложения. getName(): string .[method] --------------------------- -Връща името на "бисквитката" на сесията. +Връща името на бисквитката, в която се пренася session ID. setOptions(array $options): static .[method] -------------------------------------------- -Конфигурира сесията. Могат да се задават всички [директиви на |https://www.php.net/manual/en/session.configuration.php] PHP [сесията |https://www.php.net/manual/en/session.configuration.php] (във формат camelCase, напр. запишете `savePath` вместо `session.save_path`), както и [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. +Конфигурира сесията. Могат да се настройват всички PHP [session директиви |https://www.php.net/manual/en/session.configuration.php] (във формат camelCase, напр. вместо `session.save_path` записваме `savePath`) и също [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. setExpiration(?string $time): static .[method] ---------------------------------------------- -Задава времето за неактивност, след което сесията се затваря. +Задава времето на неактивност, след което сесията изтича. -setCookieParameters(string $path, string $domain=null, bool $secure=null, string $samesite=null): static .[method] ------------------------------------------------------------------------------------------------------------------- -Задава параметрите за бисквитките. Настройките по подразбиране могат да бъдат променени в раздела за [конфигурация |configuration#Session-Cookie]. +setCookieParameters(string $path, ?string $domain=null, ?bool $secure=null, ?string $samesite=null): static .[method] +--------------------------------------------------------------------------------------------------------------------- +Настройка на параметрите за бисквитката. Стойностите по подразбиране на параметрите можете да промените в [конфигурацията |configuration#Бисквитка за сесия]. setSavePath(string $path): static .[method] ------------------------------------------- -Задава директорията, в която се съхраняват файловете на сесиите. +Задава директорията, където се съхраняват файловете със сесиите. setHandler(\SessionHandlerInterface $handler): static .[method] --------------------------------------------------------------- -Задава персонализирания манипулатор, вижте [документацията на PHP |https://www.php.net/manual/en/class.sessionhandlerinterface.php]. +Настройка на собствен handler, виж [документацията на PHP|https://www.php.net/manual/en/class.sessionhandlerinterface.php]. </div> -Сигурността на първо място .[#toc-safety-first] -=============================================== +Сигурността преди всичко +======================== -Сървърът приема, че комуникира с един и същ потребител, докато заявките съдържат един и същ идентификатор на сесията. Предизвикателството пред механизмите за сигурност е да се гарантира, че това поведение действително работи и че няма начин да се подмени или открадне идентификаторът. +Сървърът предполага, че комуникира постоянно със същия потребител, докато заявките са придружени от същия session ID. Задачата на механизмите за сигурност е да гарантират, че това наистина е така и не е възможно идентификаторът да бъде откраднат или подменен. -Ето защо Nette Framework правилно конфигурира директивите на PHP да предават идентификаторите на сесията само на бисквитките, да предотвратяват достъпа от JavaScript и да игнорират идентификаторите в URL адресите. Освен това в критични моменти, като например влизане на потребител, той генерира нов идентификатор на сесията. +Nette Framework затова правилно конфигурира PHP директивите, така че session ID да се пренася само в бисквитка, да го направи недостъпен за JavaScript и да игнорира евентуални идентификатори в URL. Освен това в критични моменти, като например влизане на потребителя, генерира нов session ID. -Функцията ini_set се използва за конфигуриране на PHP, но за съжаление някои уеб хостове не я разрешават. Ако вашият случай е такъв, опитайте се да помолите доставчика си на хостинг да разреши тази функция или поне да конфигурира правилно своя сървър. .[note] +.[note] +За конфигурация на PHP се използва функцията ini_set, която за съжаление някои хостинги забраняват. Ако това е случаят и с вашия хостинг, опитайте да се договорите с него да ви разреши функцията или поне да конфигурира сървъра. diff --git a/http/bg/urls.texy b/http/bg/urls.texy index 677dbec0f0..cdbf111e0a 100644 --- a/http/bg/urls.texy +++ b/http/bg/urls.texy @@ -1,19 +1,19 @@ -Парсер и конструктор на URL адреси -********************************** +Работа с URL адреси +******************* .[perex] -Класовете [Url |#Url], [UrlImmutable |#UrlImmutable] и [UrlScript |#UrlScript] позволяват лесно управление, анализиране и манипулиране на URL адреси. +Класовете [#Url], [#UrlImmutable] и [#UrlScript] позволяват лесно генериране, парсиране и манипулиране на URL адреси. -→ [Монтаж и изисквания |@home#Installation] +→ [Инсталация и изисквания |@home#Инсталация] -Урл +Url === -Класът [api:Nette\Http\Url] позволява лесна работа с URL адреса и отделните му компоненти, които са показани на тази диаграма: +Класът [api:Nette\Http\Url] позволява лесно да се работи с URL и неговите отделни компоненти, които са показани на тази схема: /--pre - scheme user password host port path query fragment + схема потребител парола хост порт път заявка фрагмент | | | | | | | | /--\ /--\ /------\ /-------\ /--\/----------\ /--------\ /----\ <b>http://john:xyz%2A12@nette.org:8080/en/download?name=param#footer</b> @@ -22,7 +22,7 @@ hostUrl authority \-- -Генерирането на URL адреси е интуитивно: +Генерирането на URL е интуитивно: ```php use Nette\Http\Url; @@ -36,7 +36,7 @@ $url->setScheme('https') echo $url; // 'https://localhost/edit?foo=bar' ``` -Можете също така да анализирате URL адреса и след това да го манипулирате: +Може също да се парсира URL и да се манипулира по-нататък: ```php $url = new Url( @@ -44,62 +44,94 @@ $url = new Url( ); ``` -Следните методи са достъпни за извличане или промяна на отделни компоненти на URL: +Класът `Url` имплементира интерфейса `JsonSerializable` и има метод `__toString()`, така че обектът може да бъде изведен или използван в данни, предавани на `json_encode()`. + +```php +echo $url; +echo json_encode([$url]); +``` + + +URL компоненти .[method] +------------------------ + +За връщане или промяна на отделните компоненти на URL са ви на разположение тези методи: .[language-php] -| Setter | Getter | Върната стойност +| Setter | Getter | Върната стойност |-------------------------------------------------------------------------------------------- -| `setScheme(string $scheme)`| `getScheme(): string`| `'http'` -| `setUser(string $user)`| `getUser(): string`| `'john'` -| `setPassword(string $password)`| `getPassword(): string`| `'xyz*12'` -| `setHost(string $host)`| `getHost(): string`| `'nette.org'` -| `setPort(int $port)`| `getPort(): ?int`| `8080` -| | `getDefaultPort(): ?int`| `80` -| `setPath(string $path)`| `getPath(): string`| `'/en/download'` -| `setQuery(string\|array $query)`| `getQuery(): string`| `'name=param'` -| `setFragment(string $fragment)`| `getFragment(): string`| `'footer'` -| | `getAuthority(): string`| `'nette.org:8080'` -| | `getHostUrl(): string`| `'http://nette.org:8080' -| | `getAbsoluteUrl(): string` | пълен URL адрес - -Можем да работим и с отделни параметри на заявката, като използваме: +| `setScheme(string $scheme)` | `getScheme(): string` | `'http'` +| `setUser(string $user)` | `getUser(): string` | `'john'` +| `setPassword(string $password)` | `getPassword(): string` | `'xyz*12'` +| `setHost(string $host)` | `getHost(): string` | `'nette.org'` +| `setPort(int $port)` | `getPort(): ?int` | `8080` +| | `getDefaultPort(): ?int` | `80` +| `setPath(string $path)` | `getPath(): string` | `'/en/download'` +| `setQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` +| `setFragment(string $fragment)` | `getFragment(): string` | `'footer'` +| | `getAuthority(): string` | `'john:xyz%2A12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | цял URL + +Предупреждение: Когато работите с URL, който е получен от [HTTP заявка|request], имайте предвид, че той няма да съдържа фрагмент, тъй като браузърът не го изпраща на сървъра. + +Можем да работим и с отделните query параметри с помощта на: .[language-php] -| Установяване | Получаване +| Setter | Getter |--------------------------------------------------- -| `setQuery(string\|array $query)` | `getQueryParameters(): array` -| `setQueryParameter(string $name, $val)`| `getQueryParameter(string $name)` +| `setQuery(string\|array $query)` | `getQueryParameters(): array` +| `setQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` -Методът `getDomain(int $level = 2)` връща дясната или лявата страна на хоста. Ето как става това, ако хостът е `www.nette.org`: + +getDomain(int $level = 2): string .[method] +------------------------------------------- +Връща дясната или лявата част на хоста. Така работи, ако хостът е `www.nette.org`: .[language-php] -| `getDomain(1)` | `'org'` -| `getDomain(2)` | `'nette.org'` -| `getDomain(3)` | `'www.nette.org'` -| `getDomain(0)` | `'www.nette.org'` -| `getDomain(-1)` | `'www.nette'` -| `getDomain(-2)` | `'www'` -| `getDomain(-3)` | `''` +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `''` -Класът `Url` имплементира интерфейса `JsonSerializable` и има метод `__toString()`, така че обектът може да бъде отпечатан или използван в данните, предадени на `json_encode()`. +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +Проверява дали два URL адреса са идентични. ```php -echo $url; -echo json_encode([$url]); +$url->isEqual('https://nette.org'); ``` -Методът `isEqual(string|Url $anotherUrl): bool` проверява дали двата URL адреса са идентични. + +Url::isAbsolute(string $url): bool .[method]{data-version:3.3.2} +---------------------------------------------------------------- +Проверява дали URL адресът е абсолютен. URL се счита за абсолютен, ако започва със схема (напр. http, https, ftp), последвана от двоеточие. ```php -$url->isEqual('https://nette.org'); +Url::isAbsolute('https://nette.org'); // true +Url::isAbsolute('//nette.org'); // false +``` + + +Url::removeDotSegments(string $path): string .[method]{data-version:3.3.2} +-------------------------------------------------------------------------- +Нормализира пътя в URL чрез премахване на специалните сегменти `.` и `..`. Методът премахва излишните елементи на пътя по същия начин, както го правят уеб браузърите. + +```php +Url::removeDotSegments('/path/../subtree/./file.txt'); // '/subtree/file.txt' +Url::removeDotSegments('/../foo/./bar'); // '/foo/bar' +Url::removeDotSegments('./today/../file.txt'); // 'file.txt' ``` -UrlImmutable .[#toc-urlimmutable] -================================= +UrlImmutable +============ -Класът [api:Nette\Http\UrlImmutable] е неизменна алтернатива на класа `Url` (точно както в PHP `DateTimeImmutable` е неизменна алтернатива на `DateTime`). Вместо сетъри има т.нар. withers, които не променят обекта, а връщат нови екземпляри с променената стойност: +Класът [api:Nette\Http\UrlImmutable] е immutable (непроменлива) алтернатива на класа [#Url] (подобно на това как в PHP `DateTimeImmutable` е непроменлива алтернатива на `DateTime`). Вместо сетъри има т.нар. withery, които не променят обекта, а връщат нови екземпляри с променена стойност: ```php use Nette\Http\UrlImmutable; @@ -111,53 +143,96 @@ $url = new UrlImmutable( $newUrl = $url ->withUser('') ->withPassword('') - ->withPath('/en/'); + ->withPath('/cs/'); + +echo $newUrl; // 'http://john:xyz%2A12@nette.org:8080/cs/?name=param#footer' +``` + +Класът `UrlImmutable` имплементира интерфейса `JsonSerializable` и има метод `__toString()`, така че обектът може да бъде изведен или използван в данни, предавани на `json_encode()`. -echo $newUrl; // 'http://nette.org:8080/en/?name=param#footer' +```php +echo $url; +echo json_encode([$url]); ``` -Следните методи са достъпни за извличане или промяна на отделни компоненти на URL: + +URL компоненти .[method] +------------------------ + +За връщане или промяна на отделните компоненти на URL служат методите: .[language-php] -| Wither | Getter | Върната стойност +| Wither | Getter | Върната стойност |-------------------------------------------------------------------------------------------- -| `withScheme(string $scheme)`| `getScheme(): string`| `'http'` -| `withUser(string $user)`| `getUser(): string`| `'john'` -| `withPassword(string $password)`| `getPassword(): string`| `'xyz*12'` -| `withHost(string $host)`| `getHost(): string`| `'nette.org'` -| `withPort(int $port)`| `getPort(): ?int`| `8080` -| | `getDefaultPort(): ?int`| `80` -| `withPath(string $path)`| `getPath(): string`| `'/en/download'` -| `withQuery(string\|array $query)`| `getQuery(): string`| `'name=param'` -| `withFragment(string $fragment)`| `getFragment(): string`| `'footer'` -| | `getAuthority(): string`| `'nette.org:8080'` -| | `getHostUrl(): string`| `'http://nette.org:8080'` -| | `getAbsoluteUrl(): string` | пълен URL адрес - -Можем да работим и с отделни параметри на заявката, като използваме: +| `withScheme(string $scheme)` | `getScheme(): string` | `'http'` +| `withUser(string $user)` | `getUser(): string` | `'john'` +| `withPassword(string $password)` | `getPassword(): string` | `'xyz*12'` +| `withHost(string $host)` | `getHost(): string` | `'nette.org'` +| `withPort(int $port)` | `getPort(): ?int` | `8080` +| | `getDefaultPort(): ?int` | `80` +| `withPath(string $path)` | `getPath(): string` | `'/en/download'` +| `withQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` +| `withFragment(string $fragment)` | `getFragment(): string` | `'footer'` +| | `getAuthority(): string` | `'john:xyz%2A12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | цял URL + +Методът `withoutUserInfo()` премахва `user` и `password`. + +Можем да работим и с отделните query параметри с помощта на: .[language-php] -| Увяхване | Получаване +| Wither | Getter |----------------------------------------------- -| `withQuery(string\|array $query)` | `getQueryParameters(): array` -| `withQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` +| `withQuery(string\|array $query)` | `getQueryParameters(): array` +| `withQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` -Методът `getDomain(int $level = 2)` работи по същия начин като метода в `Url`. Методът `withoutUserInfo()` премахва `user` и `password`. -Класът `UrlImmutable` имплементира интерфейса `JsonSerializable` и има метод `__toString()`, така че обектът може да бъде отпечатан или използван в данните, предадени на `json_encode()`. +getDomain(int $level = 2): string .[method] +------------------------------------------- +Връща дясната или лявата част на хоста. Така работи, ако хостът е `www.nette.org`: + +.[language-php] +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `''` + + +resolve(string $reference): UrlImmutable .[method]{data-version:3.3.2} +---------------------------------------------------------------------- +Извежда абсолютен URL по същия начин, по който браузърът обработва връзките на HTML страница: +- ако връзката е абсолютен URL (съдържа схема), тя се използва без промяна +- ако връзката започва с `//`, се приема само схемата от текущия URL +- ако връзката започва с `/`, се създава абсолютен път от корена на домейна +- в останалите случаи URL се съставя относително спрямо текущия път ```php -echo $url; -echo json_encode([$url]); +$url = new UrlImmutable('https://example.com/path/page'); +echo $url->resolve('../foo'); // 'https://example.com/foo' +echo $url->resolve('/bar'); // 'https://example.com/bar' +echo $url->resolve('sub/page.html'); // 'https://example.com/path/sub/page.html' +``` + + +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +Проверява дали два URL адреса са идентични. + +```php +$url->isEqual('https://nette.org'); ``` -Методът `isEqual(string|Url $anotherUrl): bool` проверява дали двата URL адреса са идентични. +UrlScript +========= -UrlScript .[#toc-urlscript] -=========================== +Класът [api:Nette\Http\UrlScript] е наследник на [#UrlImmutable] и го разширява с допълнителни виртуални компоненти на URL, като например коренната директория на проекта и др. Подобно на родителския клас, той е immutable (непроменлив) обект. -Класът [api:Nette\Http\UrlScript] е потомък на класа `UrlImmutable` и допълнително разграничава тези логически части на URL: +Следващата диаграма показва компонентите, които UrlScript разпознава: /--pre baseUrl basePath relativePath relativeUrl @@ -169,17 +244,23 @@ UrlScript .[#toc-urlscript] scriptPath pathInfo \-- -За извличането на тези части са налични следните методи: +- `baseUrl` е основният URL адрес на приложението, включително домейна и частта от пътя до коренната директория на приложението +- `basePath` е частта от пътя до коренната директория на приложението +- `scriptPath` е пътят до текущия скрипт +- `relativePath` е името на скрипта (евентуално и други сегменти от пътя) относително спрямо basePath +- `relativeUrl` е цялата част от URL след baseUrl, включително query string и фрагмент. +- `pathInfo` днес вече малко използвана част от URL след името на скрипта + +За връщане на части от URL са на разположение методите: .[language-php] -| Getter | Върната стойност +| Getter | Върната стойност |------------------------------------------------ -| `getScriptPath(): string`| `'/admin/script.php'` -| `getBasePath(): string`| `'/admin/'` -| `getBaseUrl(): string`| `'http://nette.org/admin/'` -| `getRelativePath(): string`| `'script.php'` -| `getRelativeUrl(): string`| `'script.php/pathinfo/?name=param#footer'` -| `getPathInfo(): string`| `'/pathinfo/'` - - -Не създаваме директно обекти `UrlScript`, но методът [Nette\Http\Request::getUrl() |request] ги връща. +| `getScriptPath(): string` | `'/admin/script.php'` +| `getBasePath(): string` | `'/admin/'` +| `getBaseUrl(): string` | `'http://nette.org/admin/'` +| `getRelativePath(): string` | `'script.php'` +| `getRelativeUrl(): string` | `'script.php/pathinfo/?name=param#footer'` +| `getPathInfo(): string` | `'/pathinfo/'` + +Обектите `UrlScript` обикновено не ги създаваме директно, а ги връща методът [Nette\Http\Request::getUrl()|request] с вече правилно зададени компоненти за текущата HTTP заявка. diff --git a/http/cs/@left-menu.texy b/http/cs/@left-menu.texy index 7016e6379b..8753fc54a5 100644 --- a/http/cs/@left-menu.texy +++ b/http/cs/@left-menu.texy @@ -1,4 +1,4 @@ -Nette Http +Nette HTTP ********** - [Úvod |@home] - [HTTP request|request] diff --git a/http/cs/@meta.texy b/http/cs/@meta.texy new file mode 100644 index 0000000000..462d9add80 --- /dev/null +++ b/http/cs/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokumentace}} diff --git a/http/cs/configuration.texy b/http/cs/configuration.texy index 37314ea001..4d9bc137d9 100644 --- a/http/cs/configuration.texy +++ b/http/cs/configuration.texy @@ -72,7 +72,7 @@ http: HTTP cookie ----------- -Lze změnit vychozí hodnoty některých parametrů metody [Nette\Http\Response::setCookie()|response#setCookie] a session. +Lze změnit vychozí hodnoty některých parametrů metody [Nette\Http\Response::setCookie() |response#setCookie] a session. ```neon http: @@ -99,7 +99,7 @@ Výchozí hodnota `auto` u atributu `cookieSecure` znamená, že pokud web běž HTTP proxy ---------- -Pokud web běží za HTTP proxy, zadejte její IP adresu, aby správně fungovala detekce spojení přes HTTPS a také IP adresy klienta. Tedy aby funkce [Nette\Http\Request::getRemoteAddress()|request#getRemoteAddress] a [isSecured()|request#isSecured] vracely správné hodnoty a v šablonách se generovaly odkazy s `https:` protokolem. +Pokud web běží za HTTP proxy, zadejte její IP adresu, aby správně fungovala detekce spojení přes HTTPS a také IP adresy klienta. Tedy aby funkce [Nette\Http\Request::getRemoteAddress() |request#getRemoteAddress] a [isSecured() |request#isSecured] vracely správné hodnoty a v šablonách se generovaly odkazy s `https:` protokolem. ```neon http: @@ -145,7 +145,7 @@ session: Session cookie -------------- -Session cookie se odesílá se stejnými parametry jako [jiné cookie|#HTTP cookie], ale tyto můžete pro ni změnit: +Session cookie se odesílá se stejnými parametry jako [jiné cookie |#HTTP cookie], ale tyto můžete pro ni změnit: ```neon session: @@ -156,4 +156,16 @@ session: cookieSamesite: None # (Strict|Lax|None) výchozí je Lax ``` -Atribut `cookieSamesite` ovlivňuje, zda bude cookie odeslaná při [přístupu z jiné domény|nette:glossary#SameSite cookie], což poskytuje určitou ochranu před útoky [Cross-Site Request Forgery |nette:glossary#cross-site-request-forgery-csrf] (CSRF). +Atribut `cookieSamesite` ovlivňuje, zda bude cookie odeslaná při [přístupu z jiné domény |nette:glossary#SameSite cookie], což poskytuje určitou ochranu před útoky [Cross-Site Request Forgery |nette:glossary#Cross-Site Request Forgery CSRF] (CSRF). + + +Služby DI +========= + +Tyto služby se přidávají do DI kontejneru: + +| Název | Typ | Popis +|----------------------------------------------------- +| `http.request` | [api:Nette\Http\Request] | [HTTP request| request] +| `http.response` | [api:Nette\Http\Response] | [HTTP response| response] +| `session.session` | [api:Nette\Http\Session] | [správa session| sessions] diff --git a/http/cs/request.texy b/http/cs/request.texy index f4f76bc948..1827f489e0 100644 --- a/http/cs/request.texy +++ b/http/cs/request.texy @@ -4,9 +4,9 @@ HTTP request .[perex] Nette zapouzdřuje HTTP požadavek do objektů se srozumitelným API a zároveň poskytuje sanitizační filtr. -HTTP požadavek představuje objekt [api:Nette\Http\Request], ke kterému se dostanete tak, že si jej necháte předat pomocí [dependency injection |dependency-injection:passing-dependencies]. V presenterech stačí jen zavolat `$httpRequest = $this->getHttpRequest()`. +HTTP požadavek představuje objekt [api:Nette\Http\Request]. Pokud pracujete s Nette, tento objekt je automaticky vytvořen frameworkem a můžete si jej nechat předat pomocí [dependency injection |dependency-injection:passing-dependencies]. V presenterech stačí jen zavolat metodu `$this->getHttpRequest()`. Pokud pracujete mimo Nette Framework, můžete si vytvořit objekt pomocí [#RequestFactory]. -Co je důležité, tak že Nette když [vytváří|#RequestFactory] tento objekt, všechny vstupní parametry GET, POST, COOKIE a také URL pročistí od kontrolních znaků a neplatných UTF-8 sekvencí. Takže s daty pak můžete bezpečně dále pracovat. Očištěná data se následně používají v presenterech a formulářích. +Velkou předností Nette je, že při vytváření objektu automaticky pročišťuje všechny vstupní parametry GET, POST, COOKIE a také URL od kontrolních znaků a neplatných UTF-8 sekvencí. S těmito daty pak můžete bezpečně dále pracovat. Očištěná data se následně používají v presenterech a formulářích. → [Instalace a požadavky |@home#Instalace] @@ -24,7 +24,7 @@ Vrací klon s jinou URL. getUrl(): Nette\Http\UrlScript .[method] ---------------------------------------- -Vrací URL požadavku jako objekt [UrlScript|urls#UrlScript]. +Vrací URL požadavku jako objekt [UrlScript |urls#UrlScript]. ```php $url = $httpRequest->getUrl(); @@ -32,11 +32,11 @@ echo $url; // https://doc.nette.org/cs/?action=edit echo $url->getHost(); // nette.org ``` -Prohlížeče neodesílají na server fragment, takže `$url->getFragment()` bude vracet prázdný řetězec. +Upozornění: prohlížeče neodesílají na server fragment, takže `$url->getFragment()` bude vracet prázdný řetězec. -getQuery(string $key=null): string|array|null .[method] -------------------------------------------------------- +getQuery(?string $key=null): string|array|null .[method] +-------------------------------------------------------- Vrací parametry GET požadavku. ```php @@ -45,8 +45,8 @@ $id = $httpRequest->getQuery('id'); // vrací GET parametr 'id' (nebo null) ``` -getPost(string $key=null): string|array|null .[method] ------------------------------------------------------- +getPost(?string $key=null): string|array|null .[method] +------------------------------------------------------- Vrací parametry POST požadavku. ```php @@ -57,11 +57,11 @@ $id = $httpRequest->getPost('id'); // vrací POST parametr 'id' (nebo null) getFile(string|string[] $key): Nette\Http\FileUpload|array|null .[method] ------------------------------------------------------------------------- -Vrací [upload|#Uploadované soubory] jako objekt [api:Nette\Http\FileUpload]: +Vrací [upload |#Uploadované soubory] jako objekt [api:Nette\Http\FileUpload]: ```php $file = $httpRequest->getFile('avatar'); -if ($file->hasFile()) { // byl nějaký soubor nahraný? +if ($file?->hasFile()) { // byl nějaký soubor nahraný? $file->getUntrustedName(); // jméno souboru odeslané uživatelem $file->getSanitizedName(); // jméno bez nebezpečných znaků } @@ -79,7 +79,7 @@ Protože nelze důvěřovat datům zvenčí a tedy ani spoléhat na podobu struk getFiles(): array .[method] --------------------------- -Vrátí strom [všech uploadů|#Uploadované soubory] v normalizované struktuře, jejíž listy jsou objekty [api:Nette\Http\FileUpload]: +Vrátí strom [všech uploadů |#Uploadované soubory] v normalizované struktuře, jejíž listy jsou objekty [api:Nette\Http\FileUpload]: ```php $files = $httpRequest->getFiles(); @@ -141,14 +141,9 @@ echo $headers['Content-Type']; ``` -getReferer(): ?Nette\Http\UrlImmutable .[method] ------------------------------------------------- -Z jaké URL uživatel přišel? Pozor, není vůbec spolehlivé. - - isSecured(): bool .[method] --------------------------- -Je spojení šifrované (HTTPS)? Pro správnou funkčnost může být potřeba [nastavit proxy|configuration#HTTP proxy]. +Je spojení šifrované (HTTPS)? Pro správnou funkčnost může být potřeba [nastavit proxy |configuration#HTTP proxy]. isSameSite(): bool .[method] @@ -163,16 +158,16 @@ Jde o AJAXový požadavek? getRemoteAddress(): ?string .[method] ------------------------------------- -Vrací IP adresu uživatele. Pro správnou funkčnost může být potřeba [nastavit proxy|configuration#HTTP proxy]. +Vrací IP adresu uživatele. Pro správnou funkčnost může být potřeba [nastavit proxy |configuration#HTTP proxy]. getRemoteHost(): ?string .[method deprecated] --------------------------------------------- -Vrací DNS překlad IP adresy uživatele. Pro správnou funkčnost může být potřeba [nastavit proxy|configuration#HTTP proxy]. +Vrací DNS překlad IP adresy uživatele. Pro správnou funkčnost může být potřeba [nastavit proxy |configuration#HTTP proxy]. -getBasicCredentials(): ?string .[method] ----------------------------------------- +getBasicCredentials(): ?array .[method] +--------------------------------------- Vrací ověřovací údaje pro [Basic HTTP authentication |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication]. ```php @@ -189,6 +184,30 @@ $body = $httpRequest->getRawBody(); ``` +getOrigin(): ?UrlImmutable .[method] +------------------------------------ +Vrací origin, ze kterého požadavek přišel. Origin se skládá z protokolu, hostname a portu - například `https://example.com:8080`. Vrací `null`, pokud hlavička origin není přítomna nebo je nastavena na `'null'`. + +```php +$origin = $httpRequest->getOrigin(); +echo $origin; // https://example.com:8080 +echo $origin?->getHost(); // example.com +``` + +Prohlížeč posílá hlavičku `Origin` v následujících případech: +- Požadavky mezi doménami (AJAX volání na jinou doménu) +- POST, PUT, DELETE a další modifikující požadavky +- Požadavky provedené pomocí Fetch API + +Prohlížeč NEPOSÍLÁ hlavičku `Origin` při: +- Běžných GET požadavcích na stejnou doménu (navigace v rámci téže domény) +- Přímé navigaci zadáním URL do adresního řádku +- Požadavcích z jiných klientů než prohlížeče (pokud není ručně přidána) + +.[note] +Na rozdíl od hlavičky `Referer` obsahuje `Origin` pouze schéma, host a port - nikoli celou cestu URL. To ji činí vhodnější pro bezpečnostní kontroly při zachování soukromí uživatele. Hlavička `Origin` se primárně používá pro validaci [CORS |nette:glossary#Cross-Origin Resource Sharing (CORS)] (Cross-Origin Resource Sharing). + + detectLanguage(array $langs): ?string .[method] ----------------------------------------------- Detekuje jazyk. Jako parametr `$lang` předáme pole s jazyky, které aplikace podporuje, a ona vrátí ten, který by viděl návštěvníkův prohlížeč nejraději. Nejsou to žádná kouzla, jen se využívá hlavičky `Accept-Language`. Pokud nedojde k žádné shodě, vrací `null`. @@ -204,28 +223,35 @@ echo $httpRequest->detectLanguage($langs); // en RequestFactory ============== -Objekt aktuálního HTTP requestu vyrobí [api:Nette\Http\RequestFactory]. Pokud píšete aplikaci, která nepoužívá DI kontejner, vyrobíte request takto: +Třída [api:Nette\Http\RequestFactory] slouží k vytvoření instance `Nette\Http\Request`, která reprezentuje aktuální HTTP požadavek. (Pokud pracujete s Nette, objekt HTTP požadavku je automaticky vytvořen frameworkem.) ```php $factory = new Nette\Http\RequestFactory; $httpRequest = $factory->fromGlobals(); ``` -RequestFactory lze před zavoláním `fromGlobals()` konfigurovat. Můžeme vypnout sanitizaci vstupních parametrů od kontrolních znaků a neplatných UTF-8 sekvencí pomocí `$factory->setBinary()`. A také nastavit proxy server pomocí `$factory->setProxy(...)`, což je důležité pro správnou detekci IP adresy uživatele. +Metoda `fromGlobals()` vytvoří objekt požadavku na základě aktuálních globálních proměnných PHP (`$_GET`, `$_POST`, `$_COOKIE`, `$_FILES` a `$_SERVER`). Při vytváření objektu automaticky pročišťuje všechny vstupní parametry GET, POST, COOKIE a také URL od kontrolních znaků a neplatných UTF-8 sekvencí, což zajišťuje bezpečnost při další práci s těmito daty. -Pomocí tzv. filtrů lze URL vyčistit od znaků, které se do něj mohou dostat např. kvůli špatně implementovaným komentářovým systémům na různých cizích webech: +RequestFactory lze před zavoláním `fromGlobals()` konfigurovat: + +- metodou `$factory->setBinary()` vypnete automatické čištění vstupních parametrů od kontrolních znaků a neplatných UTF-8 sekvencí. +- metodou `$factory->setProxy(...)` uvedete IP adresu [proxy serveru |configuration#HTTP proxy], což je nezbytné pro správnou detekci IP adresy uživatele. + +RequestFactory umožňuje definovat filtry, které automaticky transformují části URL požadavku. Tyto filtry odstraňují nežádoucí znaky z URL, které tam mohou být vloženy například nesprávnou implementací komentářových systémů na různých webech: ```php -// odstraníme mezery z cesty +// odstranění mezer z cesty $requestFactory->urlFilters['path']['%20'] = ''; -// odstraníme tečku, čárku nebo pravou závorku z konce URI +// odstranění tečky, čárky nebo pravé závorky z konce URI $requestFactory->urlFilters['url']['[.,)]$'] = ''; -// vyčistíme cestu od zdvojených lomítek (výchozí filtr) +// vyčištění cesty od zdvojených lomítek (výchozí filtr) $requestFactory->urlFilters['path']['/{2,}'] = '/'; ``` +První klíč `'path'` nebo `'url'` určuje, na kterou část URL se filtr použije. Druhý klíč je regulární výraz, který se má vyhledat, a hodnota je náhrada, která se použije místo nalezeného textu. + Uploadované soubory =================== @@ -249,7 +275,7 @@ V tomto případě `$request->getFiles()` vrací pole: Objekt `FileUpload` se vytvoří i v případě, že uživatel žádný soubor neodeslal nebo odeslání selhalo. Jestli byl soubor odeslán vrací metoda `hasFile()`: ```php -$request->getFile('avatar')->hasFile(); +$request->getFile('avatar')?->hasFile(); ``` V případě názvu elementu používajícího notaci pro pole: @@ -355,7 +381,18 @@ Nevěřte hodnotě vrácené touto metodou. Klient mohl odeslat škodlivý náze getSanitizedName(): string .[method] ------------------------------------ -Vrací sanitizovaný název souboru. Obsahuje pouze ASCII znaky `[a-zA-Z0-9.-]`. Pokud název takové znaky neobsahuje, vrátí `'unknown'`. Pokud je soubor obrázek ve formátu JPEG, PNG, GIF, nebo WebP, vrátí i správnou příponu. +Vrací sanitizovaný název souboru. Obsahuje pouze ASCII znaky `[a-zA-Z0-9.-]`. Pokud název takové znaky neobsahuje, vrátí `'unknown'`. Pokud je soubor obrázek ve formátu JPEG, PNG, GIF, WebP nebo AVIF, vrátí i správnou příponu. + +.[caution] +Vyžaduje PHP rozšíření `fileinfo`. + + +getSuggestedExtension(): ?string .[method]{data-version:3.2.4} +-------------------------------------------------------------- +Vrací vhodnou příponu souboru (bez tečky) odpovídající zjištěnému MIME typu. + +.[caution] +Vyžaduje PHP rozšíření `fileinfo`. getUntrustedFullPath(): string .[method] @@ -376,9 +413,14 @@ getTemporaryFile(): string .[method] Vrací cestu k dočasné lokaci uploadovaného souboru. V případě, že upload nebyl úspěšný, vrací `''`. +__toString(): string .[method] +------------------------------ +Vrací cestu k dočasnému umístění nahraného souboru. To umožňuje objekt `FileUpload` použít přímo jako řetězec. + + isImage(): bool .[method] ------------------------- -Vrací `true`, pokud nahraný soubor je obrázek ve formátu JPEG, PNG, GIF, nebo WebP. Detekce probíhá na základě jeho signatury a neověřuje se integrita celého souboru. Zda není obrázek poškozený lze zjistit například pokusem o jeho [načtení|#toImage]. +Vrací `true`, pokud nahraný soubor je obrázek ve formátu JPEG, PNG, GIF, WebP nebo AVIF. Detekce probíhá na základě jeho signatury a neověřuje se integrita celého souboru. Zda není obrázek poškozený lze zjistit například pokusem o jeho [načtení |#toImage]. .[caution] Vyžaduje PHP rozšíření `fileinfo`. diff --git a/http/cs/response.texy b/http/cs/response.texy index ef4f008eca..4732bc7a76 100644 --- a/http/cs/response.texy +++ b/http/cs/response.texy @@ -4,7 +4,7 @@ HTTP response .[perex] Nette zapouzdřuje HTTP odpověď do objektů se srozumitelným API. -HTTP odpověď představuje objekt [api:Nette\Http\Response], ke kterému se dostanete tak, že si jej necháte předat pomocí [dependency injection |dependency-injection:passing-dependencies]. V presenterech stačí jen zavolat `$httpResponse = $this->getHttpResponse()`. +HTTP odpověď představuje objekt [api:Nette\Http\Response]. Pokud pracujete s Nette, tento objekt je automaticky vytvořen frameworkem a můžete si jej nechat předat pomocí [dependency injection |dependency-injection:passing-dependencies]. V presenterech stačí jen zavolat metodu `$this->getHttpResponse()`. → [Instalace a požadavky |@home#Instalace] @@ -15,8 +15,8 @@ Nette\Http\Response Objekt je na rozdíl od [Nette\Http\Request|request] mutable, tedy pomocí setterů můžete měnit stav, tedy např. odesílat hlavičky. Nezapomeňte, že všechny settery musí být volány **před odesláním jakéhokoli výstupu.** Jestli už byl výstup odeslán prozradí metoda `isSent()`. Pokud vrací `true`, každý pokus o odeslání hlavičky vyvolá výjimku `Nette\InvalidStateException`. -setCode(int $code, string $reason=null) .[method] -------------------------------------------------- +setCode(int $code, ?string $reason=null) .[method] +-------------------------------------------------- Změní stavový [kód odpovědi |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10]. Kvůli lepší srozumitelnosti zdrojového kódu doporučujeme pro kód používat místo čísel [předdefinované konstanty |api:Nette\Http\IResponse]. ```php @@ -34,9 +34,9 @@ isSent(): bool .[method] Vrací, zda už došlo k odeslání hlaviček ze serveru do prohlížeče, a tedy již není možné odesílat hlavičky či měnit stavový kód. -setHeader(string $name, string $value) .[method] ------------------------------------------------- -Odešle HTTP hlavičku a **přepíše** dříve odeslanou hlavičkou stejného jména. +setHeader(string $name, ?string $value) .[method] +------------------------------------------------- +Odešle HTTP hlavičku a **přepíše** dříve odeslanou hlavičkou stejného jména. Pokud je `$value` `null`, bude záhlaví odstraněno. ```php $httpResponse->setHeader('Pragma', 'no-cache'); @@ -77,8 +77,8 @@ echo $headers['Pragma']; ``` -setContentType(string $type, string $charset=null) .[method] ------------------------------------------------------------- +setContentType(string $type, ?string $charset=null) .[method] +------------------------------------------------------------- Mění hlavičku `Content-Type`. ```php @@ -115,17 +115,17 @@ $httpResponse->sendAsFile('faktura.pdf'); ``` -setCookie(string $name, string $value, $time, string $path=null, string $domain=null, bool $secure=null, bool $httpOnly=null, string $sameSite=null) .[method] --------------------------------------------------------------------------------------------------------------------------------------------------------------- +setCookie(string $name, string $value, $time, ?string $path=null, ?string $domain=null, ?bool $secure=null, ?bool $httpOnly=null, ?string $sameSite='Lax') .[method] +-------------------------------------------------------------------------------------------------------------------------------------------------------------------- Odešle cookie. Výchozí hodnoty parametrů: | `$path` | `'/'` | cookie má dosah na všechny cesty v (sub)doméně *(konfigurovatelné)* | `$domain` | `null` | což znamená s dosahem na aktuální (sub)doménu, ale nikoliv její subdomény *(konfigurovatelné)* | `$secure` | `true` | pokud web běží na HTTPS, jinak `false` *(konfigurovatelné)* | `$httpOnly` | `true` | cookie je pro JavaScript nepřístupná -| `$sameSite` | `'Lax'` | cookie nemusí být odeslána při [přístupu z jiné domény|nette:glossary#SameSite cookie] +| `$sameSite` | `'Lax'` | cookie nemusí být odeslána při [přístupu z jiné domény |nette:glossary#SameSite cookie] -Výchozí hodnoty parametrů `$path`, `$domain` a `$secure` můžete změnit v [konfiguraci|configuration#HTTP cookie]. +Výchozí hodnoty parametrů `$path`, `$domain` a `$secure` můžete změnit v [konfiguraci |configuration#HTTP cookie]. Čas lze uvádět jako počet sekund nebo řetězec: @@ -138,12 +138,12 @@ Parametr `$domain` určuje, které domény mohou cookie přijímat. Není-li uve Pro hodnotu `$sameSite` můžete použít konstanty `Response::SameSiteLax`, `SameSiteStrict` a `SameSiteNone`. -deleteCookie(string $name, string $path=null, string $domain=null, bool $secure=null): void .[method] ------------------------------------------------------------------------------------------------------ +deleteCookie(string $name, ?string $path=null, ?string $domain=null, ?bool $secure=null): void .[method] +-------------------------------------------------------------------------------------------------------- Smaže cookie. Výchozí hodnoty parametrů jsou: - `$path` s dosahem na všechny adresáře (`'/'`) - `$domain` s dosahem na aktuální (sub)doménu, ale nikoliv její subdomény -- `$secure` se řídí podle nastavení v [konfiguraci|configuration#HTTP cookie] +- `$secure` se řídí podle nastavení v [konfiguraci |configuration#HTTP cookie] ```php $httpResponse->deleteCookie('lang'); diff --git a/http/cs/sessions.texy b/http/cs/sessions.texy index 2580cca56e..de10e7715f 100644 --- a/http/cs/sessions.texy +++ b/http/cs/sessions.texy @@ -13,7 +13,7 @@ HTTP je bezestavový protokol, nicméně takřka každá aplikace potřebuje sta Při použití sessions každý uživatel obdrží jedinečný identifikátor nazývaný session ID, který se předává v cookie. Ten slouží jako klíč k session datům. Na rozdíl od cookies, které se uchovávají na straně prohlížeče, jsou data v session uchovávána na straně serveru. -Session nastavujeme v [konfiguraci |configuration#session], důležitá je zejména volba doby exipirace. +Session nastavujeme v [konfiguraci |configuration#Session], důležitá je zejména volba doby exipirace. Správu session má na starosti objekt [api:Nette\Http\Session], ke kterému se dostanete tak, že si jej necháte předat pomocí [dependency injection |dependency-injection:passing-dependencies]. V presenterech stačí jen zavolat `$session = $this->getSession()`. @@ -23,7 +23,7 @@ Správu session má na starosti objekt [api:Nette\Http\Session], ke kterému se Start session ============= -Nette ve výchozím nastavení automaticky zahájí session automaticky ve chvíli, když z ní začneme číst nebo do ní zapisovat data. Ručně se session zahájí pomocí `$session->start()`. +Nette ve výchozím nastavení automaticky zahájí session ve chvíli, když z ní začneme číst nebo do ní zapisovat data. Ručně se session zahájí pomocí `$session->start()`. PHP odešle při spuštění session HTTP hlavičky ovlivňující kešování, viz [php:session_cache_limiter], a případně i cookie se session ID. Proto je nutné vždy session nastartovat ještě před odesláním jakéhokoliv výstupu do prohlížeče, jinak dojde k vyhození výjimky. Pokud tedy víte, že v průběhu vykreslování stránky se bude používat session, nastartujte ji ručně předtím, třeba v presenteru. @@ -92,7 +92,7 @@ $section->set('flash', $message, '30 seconds'); ``` .[note] -Nezapomeňte, že doba expirace celé session (viz [konfigurace session|configuration#session]) musí být stejná nebo vyšší než doba nastavená u jednotlivých sekcí či proměnných. +Nezapomeňte, že doba expirace celé session (viz [konfigurace session |configuration#Session]) musí být stejná nebo vyšší než doba nastavená u jednotlivých sekcí či proměnných. Zrušení dříve nastavené expirace docílíme metodou `removeExpiration()`. Okamžité zrušení celé sekce zajistí metoda `remove()`. @@ -100,7 +100,7 @@ Zrušení dříve nastavené expirace docílíme metodou `removeExpiration()`. O Události $onStart, $onBeforeWrite --------------------------------- -Objekt `Nette\Http\Session` má [události|nette:glossary#Události] `$onStart` a `$onBeforeWrite`, můžete tedy přidat callbacky, které se vyvolají po startu session nebo před jejím zápisem na disk a následným ukončením. +Objekt `Nette\Http\Session` má [události |nette:glossary#události] `$onStart` a `$onBeforeWrite`, můžete tedy přidat callbacky, které se vyvolají po startu session nebo před jejím zápisem na disk a následným ukončením. ```php $session->onBeforeWrite[] = function () { @@ -158,7 +158,7 @@ Vrátí session ID. Konfigurace ----------- -Session nastavujeme v [konfiguraci |configuration#session]. Pokud píšete aplikaci, která nepoužívá DI kontejner, slouží ke konfiguraci tyto metody. Musí být volány ještě před spuštěním session. +Session nastavujeme v [konfiguraci |configuration#Session]. Pokud píšete aplikaci, která nepoužívá DI kontejner, slouží ke konfiguraci tyto metody. Musí být volány ještě před spuštěním session. <div class=wiki-methods-brief> @@ -183,9 +183,9 @@ setExpiration(?string $time): static .[method] Nastaví dobu neaktivity po které session vyexpiruje. -setCookieParameters(string $path, string $domain=null, bool $secure=null, string $samesite=null): static .[method] ------------------------------------------------------------------------------------------------------------------- -Nastavení parametrů pro cookie. Výchozí hodnoty parametrů můžete změnit v [konfiguraci|configuration#Session cookie]. +setCookieParameters(string $path, ?string $domain=null, ?bool $secure=null, ?string $samesite=null): static .[method] +--------------------------------------------------------------------------------------------------------------------- +Nastavení parametrů pro cookie. Výchozí hodnoty parametrů můžete změnit v [konfiguraci |configuration#Session cookie]. setSavePath(string $path): static .[method] @@ -207,4 +207,5 @@ Server předpokládá, že komunikuje stále s tímtéž uživatelem, dokud pož Nette Framework proto správně nakonfiguruje PHP direktivy, aby session ID přenášel pouze v cookie, znepřístupnil jej JavaScriptu a případné identifikátory v URL ignoroval. Navíc v kritických chvílích, jako je třeba přihlášení uživatele, vygeneruje session ID nové. -Pro konfiguraci PHP se používá funkce ini_set, kterou bohužel některé hostingy zakazují. Pokud je to případ i vašeho hostéra, pokuste se s ním domluvit, aby vám funkci povolil nebo alespoň server nakonfiguroval. .[note] +.[note] +Pro konfiguraci PHP se používá funkce ini_set, kterou bohužel některé hostingy zakazují. Pokud je to případ i vašeho hostéra, pokuste se s ním domluvit, aby vám funkci povolil nebo alespoň server nakonfiguroval. diff --git a/http/cs/urls.texy b/http/cs/urls.texy index 4f1095f896..e40a157e18 100644 --- a/http/cs/urls.texy +++ b/http/cs/urls.texy @@ -1,5 +1,5 @@ -Parsování a skládání URL -************************ +Práce s URL +*********** .[perex] Třídy [#Url], [#UrlImmutable] a [#UrlScript] umožňují snadné generování, parsování a manipulaci s URL. @@ -44,6 +44,17 @@ $url = new Url( ); ``` +Třída `Url` implementuje rozhraní `JsonSerializable` a má metodu `__toString()`, takže objekt lze vypsat nebo použít v datech předávaných do `json_encode()`. + +```php +echo $url; +echo json_encode([$url]); +``` + + +Komponenty URL .[method] +------------------------ + Pro vrácení nebo změnu jednotlivých komponent URL jsou vám k dispozici tyto metody: .[language-php] @@ -58,10 +69,12 @@ Pro vrácení nebo změnu jednotlivých komponent URL jsou vám k dispozici tyto | `setPath(string $path)` | `getPath(): string` | `'/en/download'` | `setQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` | `setFragment(string $fragment)` | `getFragment(): string` | `'footer'` -| | `getAuthority(): string` | `'nette.org:8080'` -| | `getHostUrl(): string` | `'http://nette.org:8080'` +| | `getAuthority(): string` | `'john:xyz%2A12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` | | `getAbsoluteUrl(): string` | celá URL +Upozornění: Když pracujete s URL, které je získáno z [HTTP requestu|request], mějte na paměti, že nebude obsahovat fragment, protože prohlížeč jej neodesílá na server. + Můžeme pracovat i s jednotlivými query parametry pomocí: .[language-php] @@ -69,8 +82,12 @@ Můžeme pracovat i s jednotlivými query parametry pomocí: |--------------------------------------------------- | `setQuery(string\|array $query)` | `getQueryParameters(): array` | `setQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` +| `appendQuery(string|array $query)` | + -Metoda `getDomain(int $level = 2)` vrací pravou či levou část hostitele. Takto funguje, pokud host je `www.nette.org`: +getDomain(int $level = 2): string .[method] +------------------------------------------- +Vrací pravou či levou část hostitele. Takto funguje, pokud host je `www.nette.org`: .[language-php] | `getDomain(1)` | `'org'` @@ -82,24 +99,45 @@ Metoda `getDomain(int $level = 2)` vrací pravou či levou část hostitele. Tak | `getDomain(-3)` | `''` -Třída `Url` implementuje rozhraní `JsonSerializable` a má metodu `__toString()`, takže objekt lze vypsat nebo použít v datech předávaných do `json_encode()`. +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +Ověří, zda jsou dvě URL shodné. ```php -echo $url; -echo json_encode([$url]); +$url->isEqual('https://nette.org'); ``` -Metoda `isEqual(string|Url $anotherUrl): bool` ověří, zda jsou dvě URL shodné. + +canonicalize() .[method] +------------------------ +Převede URL do kanonického tvaru. To zahrnuje například seřazení parametrů v query stringu podle abecedy, převod hostname na malá písmena a odstranění nadbytečných znaků. + + +Url::isAbsolute(string $url): bool .[method]{data-version:3.3.2} +---------------------------------------------------------------- +Ověřuje, zda je URL absolutní. URL je považována za absolutní, pokud začíná schématem (např. http, https, ftp) následovaným dvojtečkou. ```php -$url->isEqual('https://nette.org'); +Url::isAbsolute('https://nette.org'); // true +Url::isAbsolute('//nette.org'); // false +``` + + +Url::removeDotSegments(string $path): string .[method]{data-version:3.3.2} +-------------------------------------------------------------------------- +Normalizuje cestu v URL odstraněním speciálních segmentů `.` a `..`. Metoda odstraňuje nadbytečné prvky cesty stejným způsobem, jako to dělají webové prohlížeče. + +```php +Url::removeDotSegments('/path/../subtree/./file.txt'); // '/subtree/file.txt' +Url::removeDotSegments('/../foo/./bar'); // '/foo/bar' +Url::removeDotSegments('./today/../file.txt'); // 'file.txt' ``` UrlImmutable ============ -Třída [api:Nette\Http\UrlImmutable] je immutable (neměnnou) alternativou třídy `Url` (podobně jako je v PHP `DateTimeImmutable` neměnnou alternativou `DateTime`). Místo setterů má tzv. withery, které objekt nemění, ale vracejí nové instance s upravenou hodnotou: +Třída [api:Nette\Http\UrlImmutable] je immutable (neměnnou) alternativou třídy [#Url] (podobně jako je v PHP `DateTimeImmutable` neměnnou alternativou `DateTime`). Místo setterů má tzv. withery, které objekt nemění, ale vracejí nové instance s upravenou hodnotou: ```php use Nette\Http\UrlImmutable; @@ -113,9 +151,20 @@ $newUrl = $url ->withPassword('') ->withPath('/cs/'); -echo $newUrl; // 'http://nette.org:8080/cs/?name=param#footer' +echo $newUrl; // 'http://john:xyz%2A12@nette.org:8080/cs/?name=param#footer' ``` +Třída `UrlImmutable` implementuje rozhraní `JsonSerializable` a má metodu `__toString()`, takže objekt lze vypsat nebo použít v datech předávaných do `json_encode()`. + +```php +echo $url; +echo json_encode([$url]); +``` + + +Komponenty URL .[method] +------------------------ + Pro vrácení nebo změnu jednotlivých komponent URL slouží metody: .[language-php] @@ -130,10 +179,12 @@ Pro vrácení nebo změnu jednotlivých komponent URL slouží metody: | `withPath(string $path)` | `getPath(): string` | `'/en/download'` | `withQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` | `withFragment(string $fragment)` | `getFragment(): string` | `'footer'` -| | `getAuthority(): string` | `'nette.org:8080'` -| | `getHostUrl(): string` | `'http://nette.org:8080'` +| | `getAuthority(): string` | `'john:xyz%2A12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` | | `getAbsoluteUrl(): string` | celá URL +Metoda `withoutUserInfo()` odstraňuje `user` a `password`. + Můžeme pracovat i s jednotlivými query parametry pomocí: .[language-php] @@ -142,22 +193,52 @@ Můžeme pracovat i s jednotlivými query parametry pomocí: | `withQuery(string\|array $query)` | `getQueryParameters(): array` | `withQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` -Metoda `getDomain(int $level = 2)` funguje stejně, jako její jmenovkyně ze třídy `Url`. Metoda `withoutUserInfo()` odstraňuje `user` a `password`. -Třída `UrlImmutable` implementuje rozhraní `JsonSerializable` a má metodu `__toString()`, takže objekt lze vypsat nebo použít v datech předávaných do `json_encode()`. +getDomain(int $level = 2): string .[method] +------------------------------------------- +Vrací pravou či levou část hostitele. Takto funguje, pokud host je `www.nette.org`: + +.[language-php] +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `''` + + +resolve(string $reference): UrlImmutable .[method]{data-version:3.3.2} +---------------------------------------------------------------------- +Odvozuje absolutní URL stejným způsobem, jakým prohlížeč zpracovává odkazy na HTML stránce: +- pokud je odkaz absolutní URL (obsahuje schéma), použije se beze změny +- pokud odkaz začíná `//`, převezme se pouze schéma z aktuální URL +- pokud odkaz začíná `/`, vytvoří se absolutní cesta od kořene domény +- v ostatních případech se URL sestaví relativně vůči aktuální cestě ```php -echo $url; -echo json_encode([$url]); +$url = new UrlImmutable('https://example.com/path/page'); +echo $url->resolve('../foo'); // 'https://example.com/foo' +echo $url->resolve('/bar'); // 'https://example.com/bar' +echo $url->resolve('sub/page.html'); // 'https://example.com/path/sub/page.html' ``` -Metoda `isEqual(string|Url $anotherUrl): bool` ověří, zda jsou dvě URL shodné. + +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +Ověří, zda jsou dvě URL shodné. + +```php +$url->isEqual('https://nette.org'); +``` UrlScript ========= -Třída [api:Nette\Http\UrlScript] je potomkem `UrlImmutable` a navíc rozlišuje tyto další logické části URL: +Třída [api:Nette\Http\UrlScript] je potomkem [#UrlImmutable] a rozšiřuje jej o další virtuální komponenty URL, jako je kořenový adresáři projektu apod. Stejně jako rodičovská třída je immutable (neměnným) objektem. + +Následující diagram zobrazuje komponenty, které UrlScript rozpoznává: /--pre baseUrl basePath relativePath relativeUrl @@ -169,6 +250,13 @@ Třída [api:Nette\Http\UrlScript] je potomkem `UrlImmutable` a navíc rozlišuj scriptPath pathInfo \-- +- `baseUrl` je základní URL adresa aplikace včetně domény a části cesty ke kořenovému adresáři aplikace +- `basePath` je část cesty ke kořenovému adresáři aplikace +- `scriptPath` je cesta k aktuálnímu skriptu +- `relativePath` je název skriptu (případně další segmenty cesty) relativní k basePath +- `relativeUrl` je celá část URL za baseUrl, včetně query string a fragmentu. +- `pathInfo` dnes už málo využívaná část URL za názvem skriptu + Pro vrácení částí URL jsou k dispozici metody: .[language-php] @@ -181,4 +269,4 @@ Pro vrácení částí URL jsou k dispozici metody: | `getRelativeUrl(): string` | `'script.php/pathinfo/?name=param#footer'` | `getPathInfo(): string` | `'/pathinfo/'` -Objekty `UrlScript` přímo nevytváříme, ale vrací jej metoda [Nette\Http\Request::getUrl()|request]. +Objekty `UrlScript` obvykle přímo nevytváříme, ale vrací jej metoda [Nette\Http\Request::getUrl()|request] s již správně nastavenými komponentami pro aktuální HTTP požadavek. diff --git a/http/de/@home.texy b/http/de/@home.texy index 1a4a5496e6..17f55bae4f 100644 --- a/http/de/@home.texy +++ b/http/de/@home.texy @@ -2,13 +2,13 @@ Nette HTTP ********** .[perex] -Das Paket `nette/http` kapselt [HTTP-Anfrage |request] und [-Antwort |response], arbeitet mit [Sessionen |sessions] und [URL-Parsing und -Erstellung |urls]. +Das Paket `nette/http` kapselt den [HTTP-Request|request] & die [Response|response], die Arbeit mit [Sessions] und das [Parsen und Zusammensetzen von URLs |urls]. -Installation .[#toc-installation] ---------------------------------- +Installation +------------ -Laden Sie das Paket herunter und installieren Sie es mit [Composer |best-practices:composer]: +Sie können die Bibliothek mit dem Werkzeug [Composer|best-practices:composer] herunterladen und installieren: ```shell composer require nette/http diff --git a/http/de/@left-menu.texy b/http/de/@left-menu.texy index 8e690324a9..28a6ce308f 100644 --- a/http/de/@left-menu.texy +++ b/http/de/@left-menu.texy @@ -1,8 +1,8 @@ -HTTP-Netze +Nette HTTP ********** -- [Überblick |@home] -- [HTTP-Anfrage |request] -- [HTTP-Antwort |response] -- [Sessionen |Sessions] -- [URL-Dienstprogramm |urls] -- [Konfiguration |Configuration] +- [Einführung |@home] +- [HTTP-Request |request] +- [HTTP-Response |response] +- [Sessions |Sessions] +- [URL-Dienstprogramme |urls] +- [Konfiguration |configuration] diff --git a/http/de/@meta.texy b/http/de/@meta.texy new file mode 100644 index 0000000000..b3b806b2ca --- /dev/null +++ b/http/de/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokumentation}} diff --git a/http/de/configuration.texy b/http/de/configuration.texy index ea3a315093..352519fa2f 100644 --- a/http/de/configuration.texy +++ b/http/de/configuration.texy @@ -1,14 +1,14 @@ -HTTP konfigurieren +HTTP-Konfiguration ****************** .[perex] -Überblick über die Konfigurationsmöglichkeiten für Nette HTTP. +Übersicht der Konfigurationsoptionen für Nette HTTP. -Wenn Sie nicht das gesamte Framework, sondern nur diese Bibliothek verwenden, lesen Sie [, wie Sie die Konfiguration laden |bootstrap:]. +Wenn Sie nicht das gesamte Framework, sondern nur diese Bibliothek verwenden, lesen Sie, [wie die Konfiguration geladen wird|bootstrap:]. -HTTP-Kopfzeilen .[#toc-http-headers] -==================================== +HTTP-Header +=========== ```neon http: @@ -18,23 +18,23 @@ http: X-Content-Type-Options: nosniff X-XSS-Protection: '1; mode=block' - # betrifft den Header X-Frame-Options - frames: ... # (string|bool) Standardwert ist 'SAMEORIGIN' + # beeinflusst den X-Frame-Options Header + frames: ... # (string|bool) Standard ist 'SAMEORIGIN' ``` -Aus Sicherheitsgründen sendet das Framework einen Header `X-Frame-Options: SAMEORIGIN`, der besagt, dass eine Seite nur dann innerhalb einer anderen Seite angezeigt werden kann (im Element `<iframe>`) nur angezeigt werden kann, wenn sie sich auf derselben Domäne befindet. Dies kann in bestimmten Situationen unerwünscht sein (z. B. wenn Sie eine Facebook-Anwendung entwickeln), daher kann das Verhalten durch die Einstellung von `frames: http://allowed-host.com`. +Das Framework sendet aus Sicherheitsgründen den Header `X-Frame-Options: SAMEORIGIN`, der besagt, dass die Seite nur dann innerhalb einer anderen Seite (im `<iframe>`-Element) angezeigt werden kann, wenn sie sich auf derselben Domain befindet. Dies kann in einigen Situationen unerwünscht sein (z. B. wenn Sie eine Anwendung für Facebook entwickeln), das Verhalten kann daher durch Setzen von `frames: http://allowed-host.com` oder `frames: true` geändert werden. -Sicherheitsrichtlinien für Inhalte .[#toc-content-security-policy] ------------------------------------------------------------------- +Content Security Policy +----------------------- -Header `Content-Security-Policy` (im Folgenden als CSP bezeichnet) lassen sich leicht zusammenstellen, ihre Beschreibung ist in der [CSP-Beschreibung |https://content-security-policy.com] zu finden. CSP-Direktiven (wie z. B. `script-src`) können zur besseren Lesbarkeit entweder als Zeichenketten gemäß der Spezifikation oder als Arrays von Werten geschrieben werden. Dann ist es nicht notwendig, Anführungszeichen um Schlüsselwörter wie `'self'` zu setzen. Nette generiert auch automatisch einen Wert von `nonce`, so dass `'nonce-y4PopTLM=='` im Header gesendet wird. +Die Header `Content-Security-Policy` (im Folgenden CSP) können einfach zusammengestellt werden, ihre Beschreibung finden Sie in der [CSP-Beschreibung |https://content-security-policy.com]. CSP-Direktiven (wie z. B. `script-src`) können entweder als Strings gemäß der Spezifikation oder als Arrays von Werten zur besseren Lesbarkeit geschrieben werden. Dann ist es nicht notwendig, Schlüsselwörter wie `'self'` in Anführungszeichen zu setzen. Nette generiert auch automatisch den `nonce`-Wert, sodass im Header zum Beispiel `'nonce-y4PopTLM=='` steht. ```neon http: # Content Security Policy csp: - # String gemäß CSP-Spezifikation + # String im Format gemäß der CSP-Spezifikation default-src: "'self' https://example.com" # Array von Werten @@ -44,14 +44,14 @@ http: - self - https://example.com - # bool für den Fall von Schaltern + # bool im Falle von Schaltern upgrade-insecure-requests: true block-all-mixed-content: false ``` -Verwenden Sie `<script n:nonce>...</script>` in den Vorlagen und der Nonce-Wert wird automatisch ausgefüllt. Die Erstellung sicherer Websites in Nette ist wirklich einfach. +Verwenden Sie in Templates `<script n:nonce>...</script>` und der Nonce-Wert wird automatisch ergänzt. Sichere Websites in Nette zu erstellen ist wirklich einfach. -In ähnlicher Weise können die Header `Content-Security-Policy-Report-Only` (die parallel zu CSP verwendet werden können) und [Feature Policy |https://developers.google.com/web/updates/2018/06/feature-policy] hinzugefügt werden: +Ähnlich können auch die Header `Content-Security-Policy-Report-Only` (die parallel zu CSP verwendet werden können) und [Feature Policy|https://developers.google.com/web/updates/2018/06/feature-policy] zusammengestellt werden: ```neon http: @@ -69,91 +69,103 @@ http: ``` -HTTP-Cookie .[#toc-http-cookie] -------------------------------- +HTTP-Cookie +----------- -Sie können die Standardwerte einiger Parameter der Methoden [Nette\Http\Response::setCookie() |response#setCookie] und session ändern. +Die Standardwerte einiger Parameter der Methode [Nette\Http\Response::setCookie() |response#setCookie] und der Session können geändert werden. ```neon http: - # Cookie-Bereich nach Pfad - cookiePath: ... # (String) standardmäßig '/' + # Cookie-Gültigkeitsbereich nach Pfad + cookiePath: ... # (string) Standard ist '/' - # welche Hosts das Cookie empfangen dürfen - cookieDomain: 'example.com' # (string|domain) ist standardmäßig nicht gesetzt + # Domains, die Cookies akzeptieren + cookieDomain: 'example.com' # (string|domain) Standard ist nicht gesetzt # Cookies nur über HTTPS senden? - cookieSecure: ... # (bool|auto) ist standardmäßig auf auto eingestellt + cookieSecure: ... # (bool|auto) Standard ist auto - # deaktiviert das Senden des Cookies, das Nette als Schutz gegen CSRF verwendet - disableNetteCookie: ... # (bool) Standardwert ist false + # Deaktiviert das Senden des Cookies, das Nette als Schutz vor CSRF verwendet + disableNetteCookie: ... # (bool) Standard ist false ``` -Die Option `cookieDomain` bestimmt, welche Domänen (Ursprünge) Cookies akzeptieren können. Ist sie nicht angegeben, wird das Cookie von derselben (Sub-)Domain akzeptiert, die es gesetzt hat, *ausgenommen* deren Subdomains. Wenn `cookieDomain` angegeben ist, werden auch Subdomains einbezogen. Daher ist die Angabe von `cookieDomain` weniger restriktiv als das Weglassen. +Das Attribut `cookieDomain` bestimmt, welche Domains Cookies akzeptieren können. Wenn es nicht angegeben ist, akzeptiert die gleiche (Sub-)Domain, die das Cookie gesetzt hat, *aber nicht* ihre Subdomains, das Cookie. Wenn `cookieDomain` angegeben ist, sind auch Subdomains enthalten. Daher ist die Angabe von `cookieDomain` weniger restriktiv als das Weglassen. -Wenn z. B. `cookieDomain: nette.org` angegeben wird, ist das Cookie auch auf allen Subdomains wie `doc.nette.org` verfügbar. Dies kann auch mit dem speziellen Wert `domain`, also `cookieDomain: domain` erreicht werden. +Zum Beispiel sind bei `cookieDomain: nette.org` Cookies auch auf allen Subdomains wie `doc.nette.org` verfügbar. Dasselbe kann auch mit dem speziellen Wert `domain` erreicht werden, also `cookieDomain: domain`. -Der Standardwert von `cookieSecure` ist `auto`, d. h. wenn die Website über HTTPS läuft, werden Cookies mit dem Flag `Secure` gesendet und sind daher nur über HTTPS verfügbar. +Der Standardwert `auto` für das Attribut `cookieSecure` bedeutet, dass, wenn die Website über HTTPS läuft, Cookies mit dem `Secure`-Flag gesendet werden und somit nur über HTTPS verfügbar sind. -HTTP-Proxy .[#toc-http-proxy] ------------------------------ +HTTP-Proxy +---------- -Wenn die Website hinter einem HTTP-Proxy läuft, geben Sie die IP-Adresse des Proxys ein, damit die Erkennung von HTTPS-Verbindungen korrekt funktioniert, sowie die IP-Adresse des Clients. Das heißt, damit [Nette\Http\Request::getRemoteAddress() |request#getRemoteAddress] und [isSecured() |request#isSecured] die richtigen Werte zurückgeben und Links mit dem Protokoll `https:` in den Vorlagen erzeugt werden. +Wenn die Website hinter einem HTTP-Proxy läuft, geben Sie dessen IP-Adresse an, damit die Erkennung der Verbindung über HTTPS und auch die IP-Adresse des Clients korrekt funktionieren. Das heißt, damit die Funktionen [Nette\Http\Request::getRemoteAddress() |request#getRemoteAddress] und [isSecured() |request#isSecured] die korrekten Werte zurückgeben und in Templates Links mit dem `https:`-Protokoll generiert werden. ```neon http: - # IP-Adresse, Bereich (z. B. 127.0.0.1/8) oder Array dieser Werte - proxy: 127.0.0.1 # (string|string[]) ist standardmäßig none + # IP-Adresse, Bereich (z.B. 127.0.0.1/8) oder Array dieser Werte + proxy: 127.0.0.1 # (string|string[]) Standard ist nicht gesetzt ``` -Session .[#toc-session] -======================= +Session +======= -Grundeinstellungen für [Sessionen |sessions]: +Grundlegende Einstellungen für [Sitzungen|sessions]: ```neon session: - # zeigt das Sessionsfenster in der Tracy Bar? - debugger: ... # (bool) ist standardmäßig auf false eingestellt + # Session-Panel in der Tracy Bar anzeigen? + debugger: ... # (bool) Standard ist false - # Inaktivitätszeit, nach der die Session abläuft - expiration: 14 days # (string) Standardwert ist '3 Stunden' + # Inaktivitätsdauer, nach der die Session abläuft + expiration: 14 days # (string) Standard ist '3 hours' - # wann wird die Session gestartet? - autoStart: ... # (smart|always|never) Standardwert ist 'smart' + # Wann soll die Session gestartet werden? + autoStart: ... # (smart|always|never) Standard ist 'smart' - # handler, Dienst, der die Schnittstelle SessionHandlerInterface implementiert + # Handler, Dienst, der das SessionHandlerInterface implementiert handler: @handlerService ``` -Mit der Option `autoStart` wird festgelegt, wann die Session gestartet werden soll. Der Wert `always` bedeutet, dass die Session immer gestartet wird, wenn die Anwendung startet. Der Wert `smart` bedeutet, dass die Session beim Starten der Anwendung nur dann gestartet wird, wenn sie bereits existiert, oder in dem Moment, in dem wir aus ihr lesen oder in sie schreiben wollen. Mit dem Wert `never` schließlich wird der automatische Start der Session deaktiviert. +Die Option `autoStart` steuert, wann die Session gestartet werden soll. Der Wert `always` bedeutet, dass die Session immer beim Start der Anwendung gestartet wird. Der Wert `smart` bedeutet, dass die Session beim Start der Anwendung nur dann gestartet wird, wenn sie bereits existiert, oder in dem Moment, in dem wir daraus lesen oder hineinschreiben möchten. Und schließlich verbietet der Wert `never` den automatischen Start der Session. -Sie können auch alle [PHP-Session-Direktiven |https://www.php.net/manual/en/session.configuration.php] (im camelCase-Format) und auch [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters] setzen. Beispiel: +Weiterhin können alle PHP [Session-Direktiven |https://www.php.net/manual/en/session.configuration.php] (im CamelCase-Format) sowie [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters] eingestellt werden. Beispiel: ```neon session: - # 'session.name' geschrieben als 'name' + # 'session.name' schreiben wir als 'name' name: MYID - # 'session.save_path' geschrieben als 'savePath' + # 'session.save_path' schreiben wir als 'savePath' savePath: "%tempDir%/sessions" ``` -Sessions-Cookie .[#toc-session-cookie] --------------------------------------- +Session-Cookie +-------------- -Das Sessions-Cookie wird mit denselben Parametern wie [andere Cookies |#HTTP cookie] gesendet, aber Sie können diese Parameter ändern: +Das Session-Cookie wird mit denselben Parametern wie [andere Cookies |#HTTP-Cookie] gesendet, aber diese können für es geändert werden: ```neon session: - # welche Hosts dürfen das Cookie erhalten - cookieDomain: 'example.com' # (string|domain) + # Domains, die Cookies akzeptieren + cookieDomain: 'example.com' # (string|domain) - # Einschränkungen beim Zugriff auf herkunftsübergreifende Anfragen - cookieSamesite: None # (Strict|Lax|None) Standardwert ist Lax + # Einschränkung beim Zugriff von einer anderen Domain + cookieSamesite: None # (Strict|Lax|None) Standard ist Lax ``` -Die Option `cookieSamesite` bestimmt, ob das Cookie bei [herkunftsübergreifenden Anfragen |nette:glossary#SameSite cookie] gesendet wird, was einen gewissen Schutz gegen [Cross-Site Request Forgery-Angriffe |nette:glossary#cross-site-request-forgery-csrf] bietet. +Das Attribut `cookieSamesite` beeinflusst, ob das Cookie beim [Zugriff von einer anderen Domain |nette:glossary#SameSite-Cookie] gesendet wird, was einen gewissen Schutz vor Angriffen durch [Cross-Site Request Forgery |nette:glossary#Cross-Site Request Forgery CSRF] (CSRF) bietet. + + +DI-Dienste +========== + +Diese Dienste werden dem DI-Container hinzugefügt: + +| Name | Typ | Beschreibung +|----------------------------------------------------- +| `http.request` | [api:Nette\Http\Request] | [HTTP-Anfrage| request] +| `http.response` | [api:Nette\Http\Response] | [HTTP-Antwort| response] +| `session.session` | [api:Nette\Http\Session] | [Session-Verwaltung| sessions] diff --git a/http/de/request.texy b/http/de/request.texy index b93dbdde68..6e8ba74c57 100644 --- a/http/de/request.texy +++ b/http/de/request.texy @@ -4,17 +4,17 @@ HTTP-Anfrage .[perex] Nette kapselt die HTTP-Anfrage in Objekte mit einer verständlichen API und bietet gleichzeitig einen Bereinigungsfilter. -Eine HTTP-Anfrage ist ein [api:Nette\Http\Request] -Objekt, das Sie erhalten, indem Sie es mit Hilfe von [Dependency Injection |dependency-injection:passing-dependencies] übergeben. In Moderatoren rufen Sie einfach `$httpRequest = $this->getHttpRequest()` auf. +Die HTTP-Anfrage wird durch das Objekt [api:Nette\Http\Request] repräsentiert. Wenn Sie mit Nette arbeiten, wird dieses Objekt automatisch vom Framework erstellt und Sie können es sich mittels [Dependency Injection |dependency-injection:passing-dependencies] übergeben lassen. In Presentern reicht es aus, die Methode `$this->getHttpRequest()` aufzurufen. Wenn Sie außerhalb des Nette Frameworks arbeiten, können Sie das Objekt mithilfe von [#RequestFactory] erstellen. -Wichtig ist, dass Nette bei der [Erstellung |#RequestFactory] dieses Objekts alle GET-, POST- und COOKIE-Eingabeparameter sowie URLs von Steuerzeichen und ungültigen UTF-8-Sequenzen befreit. Sie können also gefahrlos mit den Daten weiterarbeiten. Die bereinigten Daten werden dann in Presentern und Formularen verwendet. +Ein großer Vorteil von Nette ist, dass beim Erstellen des Objekts alle Eingabeparameter GET, POST, COOKIE sowie die URL automatisch von Steuerzeichen und ungültigen UTF-8-Sequenzen bereinigt werden. Mit diesen Daten können Sie dann sicher weiterarbeiten. Die bereinigten Daten werden anschließend in Presentern und Formularen verwendet. -→ [Installation und Voraussetzungen |@home#Installation] +→ [Installation und Anforderungen |@home#Installation] -Nette\Http\Request .[#toc-nette-http-request] -============================================= +Nette\Http\Request +================== -Dieses Objekt ist unveränderlich. Es hat keine Setter, sondern nur einen sogenannten Wither `withUrl()`, der das Objekt nicht verändert, sondern eine neue Instanz mit einem geänderten Wert zurückgibt. +Dieses Objekt ist immutable (unveränderlich). Es hat keine Setter, sondern nur einen sogenannten Wither `withUrl()`, der das Objekt nicht ändert, sondern eine neue Instanz mit dem geänderten Wert zurückgibt. withUrl(Nette\Http\UrlScript $url): Nette\Http\Request .[method] @@ -24,62 +24,62 @@ Gibt einen Klon mit einer anderen URL zurück. getUrl(): Nette\Http\UrlScript .[method] ---------------------------------------- -Gibt die URL der Anfrage als Objekt [UrlScript |urls#UrlScript] zurück. +Gibt die URL der Anfrage als [UrlScript |urls#UrlScript]-Objekt zurück. ```php $url = $httpRequest->getUrl(); -echo $url; // https://nette.org/en/documentation?action=edit +echo $url; // https://doc.nette.org/cs/?action=edit echo $url->getHost(); // nette.org ``` -Browser senden kein Fragment an den Server, daher gibt `$url->getFragment()` einen leeren String zurück. +Warnung: Browser senden kein Fragment an den Server, daher gibt `$url->getFragment()` einen leeren String zurück. -getQuery(string $key=null): string|array|null .[method] -------------------------------------------------------- -Gibt GET-Anfrageparameter zurück: +getQuery(?string $key=null): string|array|null .[method] +-------------------------------------------------------- +Gibt die GET-Parameter der Anfrage zurück. ```php -$all = $httpRequest->getQuery(); // Array mit allen URL-Parametern -$id = $httpRequest->getQuery('id'); // liefert den GET-Parameter 'id' (oder null) +$all = $httpRequest->getQuery(); // Gibt ein Array aller Parameter aus der URL zurück +$id = $httpRequest->getQuery('id'); // Gibt den GET-Parameter 'id' zurück (oder null) ``` -getPost(string $key=null): string|array|null .[method] ------------------------------------------------------- -Gibt die Parameter der POST-Anforderung zurück: +getPost(?string $key=null): string|array|null .[method] +------------------------------------------------------- +Gibt die POST-Parameter der Anfrage zurück. ```php -$all = $httpRequest->getPost(); // Array mit allen POST-Parametern -$id = $httpRequest->getPost('id'); // gibt den POST-Parameter 'id' (oder null) zurück +$all = $httpRequest->getPost(); // Gibt ein Array aller Parameter aus POST zurück +$id = $httpRequest->getPost('id'); // Gibt den POST-Parameter 'id' zurück (oder null) ``` getFile(string|string[] $key): Nette\Http\FileUpload|array|null .[method] ------------------------------------------------------------------------- -Gibt den [Upload |#Uploaded Files] als Objekt [api:Nette\Http\FileUpload] zurück: +Gibt den [Upload |#Hochgeladene Dateien] als [api:Nette\Http\FileUpload]-Objekt zurück: ```php $file = $httpRequest->getFile('avatar'); -if ($file->hasFile()) { // Wurde eine Datei hochgeladen? - $file->getUntrustedName(); // Name der vom Benutzer gesendeten Datei - $file->getSanitizedName(); // der Name ohne gefährliche Zeichen +if ($file?->hasFile()) { // Wurde eine Datei hochgeladen? + $file->getUntrustedName(); // Vom Benutzer gesendeter Dateiname + $file->getSanitizedName(); // Name ohne gefährliche Zeichen } ``` -Geben Sie ein Array von Schlüsseln für den Zugriff auf die Teilbaumstruktur an. +Für den Zugriff auf eine verschachtelte Struktur geben Sie ein Array von Schlüsseln an. ```php //<input type="file" name="my-form[details][avatar]" multiple> $file = $request->getFile(['my-form', 'details', 'avatar']); ``` -Da man den Daten von außen nicht trauen kann und sich daher nicht auf die Form der Struktur verlassen kann, ist diese Methode sicherer als `$request->getFiles()['my-form']['details']['avatar']`, die fehlschlagen kann. +Da man externen Daten nicht vertrauen kann und sich daher auch nicht auf die Struktur der Dateien verlassen kann, ist dieser Weg sicherer als z.B. `$request->getFiles()['my-form']['details']['avatar']`, was fehlschlagen kann. getFiles(): array .[method] --------------------------- -Gibt einen Baum von [Upload-Dateien |#Uploaded Files] in einer normalisierten Struktur zurück, wobei jedes Blatt eine Instanz von [api:Nette\Http\FileUpload] ist: +Gibt einen Baum [aller Uploads |#Hochgeladene Dateien] in einer normalisierten Struktur zurück, deren Blätter [api:Nette\Http\FileUpload]-Objekte sind: ```php $files = $httpRequest->getFiles(); @@ -97,7 +97,7 @@ $sessId = $httpRequest->getCookie('sess_id'); getCookies(): array .[method] ----------------------------- -Gibt alle Cookies zurück: +Gibt alle Cookies zurück. ```php $cookies = $httpRequest->getCookies(); @@ -106,16 +106,16 @@ $cookies = $httpRequest->getCookies(); getMethod(): string .[method] ----------------------------- -Gibt die HTTP-Methode zurück, mit der die Anfrage gestellt wurde. +Gibt die HTTP-Methode zurück, mit der die Anfrage gemacht wurde. ```php -echo $httpRequest->getMethod(); // GET, POST, HEAD, PUT +$httpRequest->getMethod(); // GET, POST, HEAD, PUT ``` isMethod(string $method): bool .[method] ---------------------------------------- -Prüft die HTTP-Methode, mit der die Anfrage gestellt wurde. Der Parameter unterscheidet nicht zwischen Groß- und Kleinschreibung. +Testet die HTTP-Methode, mit der die Anfrage gemacht wurde. Der Parameter ist case-insensitive. ```php if ($httpRequest->isMethod('GET')) // ... @@ -124,7 +124,7 @@ if ($httpRequest->isMethod('GET')) // ... getHeader(string $header): ?string .[method] -------------------------------------------- -Gibt einen HTTP-Header zurück oder `null`, wenn er nicht existiert. Der Parameter unterscheidet nicht zwischen Groß- und Kleinschreibung: +Gibt einen HTTP-Header zurück oder `null`, wenn er nicht existiert. Der Parameter ist case-insensitive. ```php $userAgent = $httpRequest->getHeader('User-Agent'); @@ -133,7 +133,7 @@ $userAgent = $httpRequest->getHeader('User-Agent'); getHeaders(): array .[method] ----------------------------- -Gibt alle HTTP-Header als assoziatives Array zurück: +Gibt alle HTTP-Header als assoziatives Array zurück. ```php $headers = $httpRequest->getHeaders(); @@ -141,39 +141,34 @@ echo $headers['Content-Type']; ``` -getReferer(): ?Nette\Http\UrlImmutable .[method] ------------------------------------------------- -Von welcher URL kam der Benutzer? Vorsicht, diese Angabe ist nicht zuverlässig. - - isSecured(): bool .[method] --------------------------- -Ist die Verbindung verschlüsselt (HTTPS)? Möglicherweise müssen Sie [einen Proxy ein |configuration#HTTP proxy] richten, damit die Verbindung funktioniert. +Ist die Verbindung verschlüsselt (HTTPS)? Für die korrekte Funktion kann es notwendig sein, einen [Proxy einzurichten |configuration#HTTP-Proxy]. isSameSite(): bool .[method] ---------------------------- -Kommt die Anfrage von der gleichen (Sub-)Domain und wird durch Anklicken eines Links ausgelöst? Nette verwendet das Cookie `_nss` (früher `nette-samesite`), um dies zu erkennen. +Kommt die Anfrage von derselben (Sub-)Domain und wird sie durch Klicken auf einen Link initiiert? Nette verwendet zur Erkennung das Cookie `_nss` (früher `nette-samesite`). isAjax(): bool .[method] ------------------------ -Handelt es sich um eine AJAX-Anfrage? +Ist es eine AJAX-Anfrage? getRemoteAddress(): ?string .[method] ------------------------------------- -Gibt die IP-Adresse des Benutzers zurück. Für eine ordnungsgemäße Funktion müssen Sie möglicherweise [einen Proxy ein |configuration#HTTP proxy] richten. +Gibt die IP-Adresse des Benutzers zurück. Für die korrekte Funktion kann es notwendig sein, einen [Proxy einzurichten |configuration#HTTP-Proxy]. getRemoteHost(): ?string .[method deprecated] --------------------------------------------- -Gibt die DNS-Übersetzung der IP-Adresse des Benutzers zurück. Für eine ordnungsgemäße Funktion müssen Sie möglicherweise [einen Proxy ein |configuration#HTTP proxy] richten. +Gibt die DNS-Auflösung der IP-Adresse des Benutzers zurück. Für die korrekte Funktion kann es notwendig sein, einen [Proxy einzurichten |configuration#HTTP-Proxy]. -getBasicCredentials(): ?string .[method] ----------------------------------------- -Gibt die Anmeldedaten für [die Basic-HTTP-Authentifizierung |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication] zurück. +getBasicCredentials(): ?array .[method] +--------------------------------------- +Gibt die Anmeldeinformationen für die [Basic HTTP-Authentifizierung |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication] zurück. ```php [$user, $password] = $httpRequest->getBasicCredentials(); @@ -182,7 +177,7 @@ Gibt die Anmeldedaten für [die Basic-HTTP-Authentifizierung |https://developer. getRawBody(): ?string .[method] ------------------------------- -Gibt den Body der HTTP-Anfrage zurück: +Gibt den Körper der HTTP-Anfrage zurück. ```php $body = $httpRequest->getRawBody(); @@ -191,54 +186,61 @@ $body = $httpRequest->getRawBody(); detectLanguage(array $langs): ?string .[method] ----------------------------------------------- -Ermittelt die Sprache. Als Parameter `$lang` wird eine Reihe von Sprachen übergeben, die die Anwendung unterstützt, und es wird die vom Browser bevorzugte Sprache zurückgegeben. Es handelt sich nicht um Magie, die Methode verwendet einfach die Kopfzeile `Accept-Language`. Wird keine Übereinstimmung erzielt, gibt sie `null` zurück. +Erkennt die Sprache. Als Parameter `$lang` übergeben wir ein Array mit den von der Anwendung unterstützten Sprachen, und sie gibt diejenige zurück, die der Browser des Besuchers am liebsten sehen würde. Das ist keine Magie, es wird nur der `Accept-Language`-Header verwendet. Wenn keine Übereinstimmung gefunden wird, gibt sie `null` zurück. ```php -// Vom Browser gesendeter Header: Accept-Language: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3 +// Der Browser sendet z.B. Accept-Language: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3 -$langs = ['hu', 'pl', 'en']; // In der Anwendung unterstützte Sprachen +$langs = ['hu', 'pl', 'en']; // Von der Anwendung unterstützte Sprachen echo $httpRequest->detectLanguage($langs); // en ``` -RequestFactory .[#toc-requestfactory] -===================================== +RequestFactory +============== -Das Objekt der aktuellen HTTP-Anfrage wird von [api:Nette\Http\RequestFactory] erstellt. Wenn Sie eine Anwendung schreiben, die keinen DI-Container verwendet, erstellen Sie einen Request wie folgt: +Die Klasse [api:Nette\Http\RequestFactory] dient zur Erstellung einer Instanz von `Nette\Http\Request`, die die aktuelle HTTP-Anfrage repräsentiert. (Wenn Sie mit Nette arbeiten, wird das HTTP-Anfrageobjekt automatisch vom Framework erstellt.) ```php $factory = new Nette\Http\RequestFactory; $httpRequest = $factory->fromGlobals(); ``` -Die RequestFactory kann vor dem Aufruf von `fromGlobals()` konfiguriert werden. Wir können alle Sanitization von Eingabeparametern aus ungültigen UTF-8-Sequenzen mit `$factory->setBinary()` deaktivieren. Und auch einen Proxy-Server einrichten, der für die korrekte Erkennung der IP-Adresse des Benutzers mit `$factory->setProxy(...)` wichtig ist. +Die Methode `fromGlobals()` erstellt das Anfrageobjekt basierend auf den aktuellen globalen PHP-Variablen (`$_GET`, `$_POST`, `$_COOKIE`, `$_FILES` und `$_SERVER`). Beim Erstellen des Objekts bereinigt sie automatisch alle Eingabeparameter GET, POST, COOKIE sowie die URL von Steuerzeichen und ungültigen UTF-8-Sequenzen, was die Sicherheit bei der weiteren Arbeit mit diesen Daten gewährleistet. + +RequestFactory kann vor dem Aufruf von `fromGlobals()` konfiguriert werden: -Mit Hilfe von Filtern können URLs von Zeichen bereinigt werden, die aufgrund schlecht implementierter Kommentarsysteme auf verschiedenen anderen Websites in sie gelangen können: +- Mit der Methode `$factory->setBinary()` deaktivieren Sie die automatische Bereinigung der Eingabeparameter von Steuerzeichen und ungültigen UTF-8-Sequenzen. +- Mit der Methode `$factory->setProxy(...)` geben Sie die IP-Adresse des [Proxy-Servers |configuration#HTTP-Proxy] an, was für die korrekte Erkennung der IP-Adresse des Benutzers notwendig ist. + +RequestFactory ermöglicht die Definition von Filtern, die Teile der Anfrage-URL automatisch transformieren. Diese Filter entfernen unerwünschte Zeichen aus der URL, die dort beispielsweise durch eine falsche Implementierung von Kommentarsystemen auf verschiedenen Websites eingefügt werden können: ```php -// Leerzeichen aus dem Pfad entfernen +// Entfernung von Leerzeichen aus dem Pfad $requestFactory->urlFilters['path']['%20'] = ''; -// Punkt, Komma oder rechte Klammer am Ende des URLs entfernen +// Entfernung von Punkt, Komma oder rechter Klammer am Ende der URI $requestFactory->urlFilters['url']['[.,)]$'] = ''; -// Bereinigung des Pfads von doppelten Schrägstrichen (Standardfilter) +// Bereinigung des Pfades von doppelten Schrägstrichen (Standardfilter) $requestFactory->urlFilters['path']['/{2,}'] = '/'; ``` +Der erste Schlüssel `'path'` oder `'url'` bestimmt, auf welchen Teil der URL der Filter angewendet wird. Der zweite Schlüssel ist der reguläre Ausdruck, der gesucht werden soll, und der Wert ist der Ersatz, der anstelle des gefundenen Textes verwendet wird. + -Hochgeladene Dateien .[#toc-uploaded-files] -=========================================== +Hochgeladene Dateien +==================== -Die Methode `Nette\Http\Request::getFiles()` gibt einen Baum von Upload-Dateien in einer normalisierten Struktur zurück, wobei jedes Blatt eine Instanz von [api:Nette\Http\FileUpload] ist. Diese Objekte kapseln die Daten, die vom `<input type=file>` Formular-Element übermittelt wurden. +Die Methode `Nette\Http\Request::getFiles()` gibt ein Array aller Uploads in einer normalisierten Struktur zurück, deren Blätter [api:Nette\Http\FileUpload]-Objekte sind. Diese kapseln die Daten, die von einem Formularelement `<input type=file>` gesendet wurden. -Die Struktur spiegelt die Benennung von Elementen in HTML wider. Im einfachsten Beispiel könnte dies ein einzelnes benanntes Formularelement sein, das wie folgt übermittelt wird: +Die Struktur spiegelt die Benennung der Elemente in HTML wider. Im einfachsten Fall kann dies ein einzelnes benanntes Formularelement sein, das wie folgt gesendet wird: ```latte <input type="file" name="avatar"> ``` -In diesem Fall gibt die `$request->getFiles()` ein Array zurück: +In diesem Fall gibt `$request->getFiles()` ein Array zurück: ```php [ @@ -246,19 +248,19 @@ In diesem Fall gibt die `$request->getFiles()` ein Array zurück: ] ``` -Das Objekt `FileUpload` wird auch dann erstellt, wenn der Benutzer keine Datei hochgeladen hat oder der Upload fehlgeschlagen ist. Die Methode `hasFile()` gibt true zurück, wenn eine Datei gesendet wurde: +Das `FileUpload`-Objekt wird auch dann erstellt, wenn der Benutzer keine Datei gesendet hat oder das Senden fehlgeschlagen ist. Ob eine Datei gesendet wurde, gibt die Methode `hasFile()` zurück: ```php -$request->getFile('avatar')->hasFile(); +$request->getFile('avatar')?->hasFile(); ``` -Im Falle einer Eingabe mit Array-Notation für den Namen: +Im Falle eines Elementnamens, der die Array-Notation verwendet: ```latte <input type="file" name="my-form[details][avatar]"> ``` -Der zurückgegebene Baum sieht dann wie folgt aus: +sieht der zurückgegebene Baum so aus: ```php [ @@ -270,13 +272,13 @@ Der zurückgegebene Baum sieht dann wie folgt aus: ] ``` -Sie können auch Arrays von Dateien erstellen: +Es kann auch ein Array von Dateien erstellt werden: ```latte -<input type="file" name="my-form[details][avatars][] multiple"> +<input type="file" name="my-form[details][avatars][]" multiple> ``` -In einem solchen Fall sieht die Struktur wie folgt aus: +In einem solchen Fall sieht die Struktur so aus: ```php [ @@ -292,7 +294,7 @@ In einem solchen Fall sieht die Struktur wie folgt aus: ] ``` -Der beste Weg, um auf den Index 1 eines verschachtelten Arrays zuzugreifen, ist wie folgt: +Der Zugriff auf den Index 1 des verschachtelten Arrays erfolgt am besten so: ```php $file = $request->getFile(['my-form', 'details', 'avatars', 1]); @@ -301,11 +303,11 @@ if ($file instanceof FileUpload) { } ``` -Da man den Daten von außen nicht trauen kann und daher nicht auf die Form der Struktur angewiesen ist, ist diese Methode sicherer als `$request->getFiles()['my-form']['details']['avatars'][1]`, die fehlschlagen kann. +Da man externen Daten nicht vertrauen kann und sich daher auch nicht auf die Struktur der Dateien verlassen kann, ist dieser Weg sicherer als z.B. `$request->getFiles()['my-form']['details']['avatars'][1]`, was fehlschlagen kann. -Überblick über die `FileUpload` Methoden .{toc: FileUpload} ------------------------------------------------------------ +Übersicht der `FileUpload`-Methoden .{toc: FileUpload} +------------------------------------------------------ hasFile(): bool .[method] @@ -320,12 +322,12 @@ Gibt `true` zurück, wenn die Datei erfolgreich hochgeladen wurde. getError(): int .[method] ------------------------- -Gibt den mit der hochgeladenen Datei verbundenen Fehlercode zurück. Es handelt sich um eine der Konstanten [UPLOAD_ERR_XXX |http://php.net/manual/en/features.file-upload.errors.php]. Wurde die Datei erfolgreich hochgeladen, wird `UPLOAD_ERR_OK` zurückgegeben. +Gibt den Fehlercode beim Hochladen der Datei zurück. Es handelt sich um eine der Konstanten [UPLOAD_ERR_XXX |http://php.net/manual/en/features.file-upload.errors.php]. Wenn der Upload erfolgreich war, gibt sie `UPLOAD_ERR_OK` zurück. move(string $dest) .[method] ---------------------------- -Verschiebt eine hochgeladene Datei an einen neuen Speicherort. Wenn die Zieldatei bereits existiert, wird sie überschrieben. +Verschiebt die hochgeladene Datei an einen neuen Speicherort. Wenn die Zieldatei bereits existiert, wird sie überschrieben. ```php $file->move('/path/to/files/name.ext'); @@ -334,12 +336,12 @@ $file->move('/path/to/files/name.ext'); getContents(): ?string .[method] -------------------------------- -Gibt den Inhalt der hochgeladenen Datei zurück. Wenn der Upload nicht erfolgreich war, wird `null` zurückgegeben. +Gibt den Inhalt der hochgeladenen Datei zurück. Wenn der Upload nicht erfolgreich war, gibt sie `null` zurück. getContentType(): ?string .[method] ----------------------------------- -Ermittelt den MIME-Inhaltstyp der hochgeladenen Datei anhand ihrer Signatur. Wenn der Upload nicht erfolgreich war oder die Erkennung fehlgeschlagen ist, wird `null` zurückgegeben. +Erkennt den MIME-Inhaltstyp der hochgeladenen Datei anhand ihrer Signatur. Wenn der Upload nicht erfolgreich war oder die Erkennung fehlschlug, gibt sie `null` zurück. .[caution] Erfordert die PHP-Erweiterung `fileinfo`. @@ -347,38 +349,49 @@ Erfordert die PHP-Erweiterung `fileinfo`. getUntrustedName(): string .[method] ------------------------------------ -Gibt den ursprünglichen Dateinamen zurück, wie er vom Browser übermittelt wurde. +Gibt den ursprünglichen Dateinamen zurück, wie er vom Browser gesendet wurde. .[caution] -Vertrauen Sie nicht auf den von dieser Methode zurückgegebenen Wert. Ein Client könnte einen bösartigen Dateinamen mit der Absicht senden, Ihre Anwendung zu beschädigen oder zu hacken. +Vertrauen Sie nicht dem von dieser Methode zurückgegebenen Wert. Ein Client könnte einen bösartigen Dateinamen gesendet haben, um Ihre Anwendung zu beschädigen oder zu hacken. getSanitizedName(): string .[method] ------------------------------------ -Gibt den bereinigten Dateinamen zurück. Er enthält nur ASCII-Zeichen `[a-zA-Z0-9.-]`. Enthält der Name keine solchen Zeichen, wird "unbekannt" zurückgegeben. Wenn die Datei ein JPEG-, PNG-, GIF- oder WebP-Bild ist, wird die korrekte Dateierweiterung zurückgegeben. +Gibt den bereinigten Dateinamen zurück. Enthält nur ASCII-Zeichen `[a-zA-Z0-9.-]`. Wenn der Name keine solchen Zeichen enthält, gibt sie `'unknown'` zurück. Wenn die Datei ein Bild im Format JPEG, PNG, GIF, WebP oder AVIF ist, gibt sie auch die korrekte Erweiterung zurück. + +.[caution] +Erfordert die PHP-Erweiterung `fileinfo`. + + +getSuggestedExtension(): ?string .[method]{data-version:3.2.4} +-------------------------------------------------------------- +Gibt die passende Dateierweiterung (ohne Punkt) zurück, die dem erkannten MIME-Typ entspricht. + +.[caution] +Erfordert die PHP-Erweiterung `fileinfo`. getUntrustedFullPath(): string .[method] ---------------------------------------- -Gibt den vollständigen Originalpfad zurück, den der Browser beim Hochladen des Verzeichnisses angegeben hat. Der vollständige Pfad ist nur in PHP 8.1 und höher verfügbar. In früheren Versionen gibt diese Methode den nicht vertrauenswürdigen Dateinamen zurück. +Gibt den ursprünglichen Dateipfad zurück, wie er vom Browser beim Hochladen eines Ordners gesendet wurde. Der vollständige Pfad ist nur in PHP 8.1 und höher verfügbar. In früheren Versionen gibt diese Methode den ursprünglichen Dateinamen zurück. .[caution] -Vertrauen Sie dem von dieser Methode zurückgegebenen Wert nicht. Ein Client könnte einen bösartigen Dateinamen mit der Absicht senden, Ihre Anwendung zu beschädigen oder zu hacken. +Vertrauen Sie nicht dem von dieser Methode zurückgegebenen Wert. Ein Client könnte einen bösartigen Dateinamen gesendet haben, um Ihre Anwendung zu beschädigen oder zu hacken. getSize(): int .[method] ------------------------ -Gibt die Größe der hochgeladenen Datei zurück. Wenn der Upload nicht erfolgreich war, wird `0` zurückgegeben. +Gibt die Größe der hochgeladenen Datei zurück. Wenn der Upload nicht erfolgreich war, gibt sie `0` zurück. getTemporaryFile(): string .[method] ------------------------------------ -Gibt den Pfad zum temporären Speicherort der hochgeladenen Datei zurück. Wenn der Upload nicht erfolgreich war, wird `''` zurückgegeben. +Gibt den Pfad zum temporären Speicherort der hochgeladenen Datei zurück. Wenn der Upload nicht erfolgreich war, gibt sie `''` zurück. isImage(): bool .[method] ------------------------- -Gibt `true` zurück, wenn die hochgeladene Datei ein JPEG-, PNG-, GIF- oder WebP-Bild ist. Die Erkennung basiert auf ihrer Signatur. Die Integrität der gesamten Datei wird nicht geprüft. Sie können herausfinden, ob ein Bild nicht beschädigt ist, indem Sie beispielsweise versuchen, [es zu laden |#toImage]. +Gibt `true` zurück, wenn die hochgeladene Datei ein Bild im Format JPEG, PNG, GIF, WebP oder AVIF ist. Die Erkennung erfolgt anhand ihrer Signatur und die Integrität der gesamten Datei wird nicht überprüft. Ob ein Bild beschädigt ist, kann beispielsweise durch den Versuch, es zu [Laden |#toImage], festgestellt werden. .[caution] Erfordert die PHP-Erweiterung `fileinfo`. @@ -386,9 +399,9 @@ Erfordert die PHP-Erweiterung `fileinfo`. getImageSize(): ?array .[method] -------------------------------- -Gibt ein Paar von `[width, height]` mit den Abmessungen des hochgeladenen Bildes zurück. Wenn der Upload nicht erfolgreich war oder kein gültiges Bild ist, wird `null` zurückgegeben. +Gibt ein Paar `[Breite, Höhe]` mit den Abmessungen des hochgeladenen Bildes zurück. Wenn der Upload nicht erfolgreich war oder es sich nicht um ein gültiges Bild handelt, gibt sie `null` zurück. toImage(): Nette\Utils\Image .[method] -------------------------------------- -Lädt ein Bild als [Image-Objekt |utils:images]. Wenn das Hochladen nicht erfolgreich war oder es sich nicht um ein gültiges Bild handelt, wird eine `Nette\Utils\ImageException` exception ausgelöst. +Lädt das Bild als [Image |utils:images]-Objekt. Wenn der Upload nicht erfolgreich war oder es sich nicht um ein gültiges Bild handelt, wird eine Ausnahme `Nette\Utils\ImageException` ausgelöst. diff --git a/http/de/response.texy b/http/de/response.texy index 4dff8b879d..0665ad2a4f 100644 --- a/http/de/response.texy +++ b/http/de/response.texy @@ -2,22 +2,22 @@ HTTP-Antwort ************ .[perex] -Nette kapselt die HTTP-Antwort in Objekte mit einer verständlichen API und bietet gleichzeitig einen Bereinigungsfilter. +Nette kapselt die HTTP-Antwort in Objekte mit einer verständlichen API. -Eine HTTP-Antwort ist ein [api:Nette\Http\Response] -Objekt, das Sie erhalten, indem Sie es mittels [Dependency Injection |dependency-injection:passing-dependencies] übergeben. In Moderatoren rufen Sie einfach `$httpResponse = $this->getHttpResponse()` auf. +Die HTTP-Antwort wird durch das Objekt [api:Nette\Http\Response] repräsentiert. Wenn Sie mit Nette arbeiten, wird dieses Objekt automatisch vom Framework erstellt und Sie können es sich mittels [Dependency Injection |dependency-injection:passing-dependencies] übergeben lassen. In Presentern reicht es aus, die Methode `$this->getHttpResponse()` aufzurufen. → [Installation und Anforderungen |@home#Installation] -Nette\Http\Response .[#toc-nette-http-response] -=============================================== +Nette\Http\Response +=================== -Im Gegensatz zu [Nette\Http\Request |request] ist dieses Objekt veränderbar, so dass Sie Setter verwenden können, um den Status zu ändern, d.h. um Header zu senden. Denken Sie daran, dass alle Setter **aufgerufen werden müssen, bevor die eigentliche Ausgabe gesendet wird.** Die Methode `isSent()` zeigt an, ob die Ausgabe gesendet wurde. Wenn sie `true` zurückgibt, löst jeder Versuch, eine Kopfzeile zu senden, eine `Nette\InvalidStateException` Ausnahme aus. +Das Objekt ist im Gegensatz zu [Nette\Http\Request |request] mutable, d.h. Sie können den Zustand mithilfe von Settern ändern, z. B. Header senden. Vergessen Sie nicht, dass alle Setter **vor dem Senden jeglicher Ausgabe** aufgerufen werden müssen. Ob bereits eine Ausgabe gesendet wurde, verrät die Methode `isSent()`. Wenn sie `true` zurückgibt, löst jeder Versuch, einen Header zu senden, eine Ausnahme `Nette\InvalidStateException` aus. -setCode(int $code, string $reason=null) .[method] -------------------------------------------------- -Ändert einen [Status-Antwort-Code |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10]. Zur besseren Lesbarkeit des Quellcodes wird empfohlen, [vordefinierte Konstanten |api:Nette\Http\IResponse] anstelle von tatsächlichen Zahlen zu verwenden. +setCode(int $code, ?string $reason=null) .[method] +-------------------------------------------------- +Ändert den [Antwort-Statuscode |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10]. Zur besseren Lesbarkeit des Quellcodes empfehlen wir, für den Code statt Zahlen [vordefinierte Konstanten |api:Nette\Http\IResponse] zu verwenden. ```php $httpResponse->setCode(Nette\Http\Response::S404_NotFound); @@ -31,12 +31,12 @@ Gibt den Statuscode der Antwort zurück. isSent(): bool .[method] ------------------------ -Gibt zurück, ob bereits Header vom Server an den Browser gesendet wurden, so dass es nicht mehr möglich ist, Header zu senden oder den Statuscode zu ändern. +Gibt zurück, ob bereits Header vom Server an den Browser gesendet wurden und es daher nicht mehr möglich ist, Header zu senden oder den Statuscode zu ändern. setHeader(string $name, string $value) .[method] ------------------------------------------------ -Sendet einen HTTP-Header und **überschreibt** den zuvor gesendeten Header mit demselben Namen. +Sendet einen HTTP-Header und **überschreibt** einen zuvor gesendeten Header mit demselben Namen. ```php $httpResponse->setHeader('Pragma', 'no-cache'); @@ -45,7 +45,7 @@ $httpResponse->setHeader('Pragma', 'no-cache'); addHeader(string $name, string $value) .[method] ------------------------------------------------ -Sendet einen HTTP-Header und **überschreibt** nicht den zuvor gesendeten Header gleichen Namens. +Sendet einen HTTP-Header und **überschreibt nicht** einen zuvor gesendeten Header mit demselben Namen. ```php $httpResponse->addHeader('Accept', 'application/json'); @@ -60,7 +60,7 @@ Löscht einen zuvor gesendeten HTTP-Header. getHeader(string $header): ?string .[method] -------------------------------------------- -Gibt den gesendeten HTTP-Header zurück, oder `null`, wenn er nicht existiert. Der Parameter unterscheidet nicht zwischen Groß- und Kleinschreibung. +Gibt den gesendeten HTTP-Header zurück oder `null`, wenn keiner existiert. Der Parameter ist case-insensitive. ```php $pragma = $httpResponse->getHeader('Pragma'); @@ -77,9 +77,9 @@ echo $headers['Pragma']; ``` -setContentType(string $type, string $charset=null) .[method] ------------------------------------------------------------- -Sendet den Header `Content-Type`. +setContentType(string $type, ?string $charset=null) .[method] +------------------------------------------------------------- +Ändert den `Content-Type`-Header. ```php $httpResponse->setContentType('text/plain', 'UTF-8'); @@ -88,7 +88,7 @@ $httpResponse->setContentType('text/plain', 'UTF-8'); redirect(string $url, int $code=self::S302_Found): void .[method] ----------------------------------------------------------------- -Leitet zu einer anderen URL um. Vergessen Sie nicht, das Skript dann zu beenden. +Leitet zu einer anderen URL weiter. Vergessen Sie nicht, das Skript danach zu beenden. ```php $httpResponse->redirect('http://example.com'); @@ -98,52 +98,52 @@ exit; setExpiration(?string $time) .[method] -------------------------------------- -Legt das Verfallsdatum des HTTP-Dokuments unter Verwendung der Header `Cache-Control` und `Expires` fest. Der Parameter ist entweder ein Zeitintervall (als Text) oder `null`, was das Caching deaktiviert. +Legt die Ablaufzeit des HTTP-Dokuments mithilfe der Header `Cache-Control` und `Expires` fest. Der Parameter ist entweder ein Zeitintervall (als Text) oder `null`, was das Caching deaktiviert. ```php -// Browser-Cache läuft in einer Stunde ab +// Cache im Browser läuft in einer Stunde ab $httpResponse->setExpiration('1 hour'); ``` sendAsFile(string $fileName) .[method] -------------------------------------- -Die Antwort sollte mit dem Dialog *Speichern unter* mit dem angegebenen Namen heruntergeladen werden. Es wird keine Datei selbst zur Ausgabe gesendet. +Die Antwort wird über das Dialogfeld *Speichern unter* unter dem angegebenen Namen heruntergeladen. Die Datei selbst wird dabei nicht gesendet. ```php -$httpResponse->sendAsFile('invoice.pdf'); +$httpResponse->sendAsFile('rechnung.pdf'); ``` -setCookie(string $name, string $value, $time, string $path=null, string $domain=null, bool $secure=null, bool $httpOnly=null, string $sameSite=null) .[method] --------------------------------------------------------------------------------------------------------------------------------------------------------------- -Sendet ein Cookie. Standard-Parameterwerte: +setCookie(string $name, string $value, $time, ?string $path=null, ?string $domain=null, ?bool $secure=null, ?bool $httpOnly=null, ?string $sameSite=null) .[method] +------------------------------------------------------------------------------------------------------------------------------------------------------------------- +Sendet ein Cookie. Standardwerte der Parameter: -| `$path` | `'/'` | mit Geltungsbereich auf alle Pfade der (Sub-)Domain *(konfigurierbar)* -| `$domain` | `null` | mit Geltungsbereich der aktuellen (Sub)domain, aber nicht deren Subdomains *(konfigurierbar)* -| `$secure` | `true` | wenn die Seite über HTTPS läuft, sonst `false` *(konfigurierbar)* -| `$httpOnly` | `true` | Cookie ist für JavaScript unzugänglich -| `$sameSite` | `'Lax'` | Cookie muss nicht gesendet werden, wenn [der Zugriff von einem anderen Ursprung erfolgt |nette:glossary#SameSite cookie] +| `$path` | `'/'` | Cookie ist für alle Pfade in der (Sub-)Domain gültig *(konfigurierbar)* +| `$domain` | `null` | bedeutet Gültigkeit für die aktuelle (Sub-)Domain, aber nicht deren Subdomains *(konfigurierbar)* +| `$secure` | `true` | wenn die Website über HTTPS läuft, sonst `false` *(konfigurierbar)* +| `$httpOnly` | `true` | Cookie ist für JavaScript unzugänglich +| `$sameSite` | `'Lax'` | Cookie muss möglicherweise nicht beim [Zugriff von einer anderen Domain |nette:glossary#SameSite-Cookie] gesendet werden -Sie können die Standardwerte der Parameter `$path`, `$domain` und `$secure` in [configuration |configuration#HTTP cookie] ändern. +Die Standardwerte der Parameter `$path`, `$domain` und `$secure` können Sie in der [Konfiguration |configuration#HTTP-Cookie] ändern. Die Zeit kann als Anzahl von Sekunden oder als String angegeben werden: ```php -$httpResponse->setCookie('lang', 'en', '100 days'); +$httpResponse->setCookie('lang', 'cs', '100 days'); ``` -Die Option `$domain` bestimmt, welche Domänen (Ursprünge) Cookies akzeptieren können. Wird sie nicht angegeben, wird das Cookie von derselben (Sub-)Domain akzeptiert, die es gesetzt hat, mit Ausnahme ihrer Subdomains. Wenn `$domain` angegeben ist, werden auch Subdomains einbezogen. Daher ist die Angabe von `$domain` weniger restriktiv als das Weglassen. Wenn zum Beispiel `$domain = 'nette.org'` angegeben wird, ist das Cookie auch auf allen Subdomains wie `doc.nette.org` verfügbar. +Der Parameter `$domain` bestimmt, welche Domains Cookies akzeptieren können. Wenn er nicht angegeben ist, akzeptiert die gleiche (Sub-)Domain, die das Cookie gesetzt hat, aber nicht deren Subdomains, das Cookie. Wenn `$domain` angegeben ist, sind auch Subdomains enthalten. Daher ist die Angabe von `$domain` weniger restriktiv als das Weglassen. Zum Beispiel sind bei `$domain = 'nette.org'` Cookies auch auf allen Subdomains wie `doc.nette.org` verfügbar. -Sie können die Konstanten `Response::SameSiteLax`, `SameSiteStrict` und `SameSiteNone` für den Wert `$sameSite` verwenden. +Für den Wert `$sameSite` können Sie die Konstanten `Response::SameSiteLax`, `SameSiteStrict` und `SameSiteNone` verwenden. -deleteCookie(string $name, string $path=null, string $domain=null, bool $secure=null): void .[method] ------------------------------------------------------------------------------------------------------ -Löscht ein Cookie. Die Standardwerte der Parameter sind: -- `$path` mit Geltungsbereich für alle Verzeichnisse (`'/'`) -- `$domain` mit Geltungsbereich der aktuellen (Sub-)Domain, aber nicht deren Subdomains -- `$secure` wird von den Einstellungen in [configuration |configuration#HTTP cookie] beeinflusst +deleteCookie(string $name, ?string $path=null, ?string $domain=null, ?bool $secure=null): void .[method] +-------------------------------------------------------------------------------------------------------- +Löscht ein Cookie. Standardwerte der Parameter sind: +- `$path` mit Gültigkeit für alle Verzeichnisse (`'/'`) +- `$domain` mit Gültigkeit für die aktuelle (Sub-)Domain, aber nicht deren Subdomains +- `$secure` richtet sich nach den Einstellungen in der [Konfiguration |configuration#HTTP-Cookie] ```php $httpResponse->deleteCookie('lang'); diff --git a/http/de/sessions.texy b/http/de/sessions.texy index 30f15791ec..4d0552c62a 100644 --- a/http/de/sessions.texy +++ b/http/de/sessions.texy @@ -1,71 +1,71 @@ -Sessionen -********* +Sessions +******** <div class=perex> -HTTP ist ein zustandsloses Protokoll, aber fast jede Anwendung muss zwischen den Anfragen einen Zustand beibehalten, z. B. den Inhalt eines Warenkorbs. Hierfür wird eine Session verwendet. Schauen wir mal +HTTP ist ein zustandsloses Protokoll, jedoch muss fast jede Anwendung den Zustand zwischen Anfragen aufrechterhalten, beispielsweise den Inhalt eines Warenkorbs. Genau dafür dienen Sessions oder Sitzungen. Wir zeigen Ihnen, - wie man Sessions verwendet -- wie man Benennungskonflikte vermeiden kann -- wie man eine Ablaufzeit festlegt +- wie man Namenskonflikte vermeidet +- wie man die Ablaufzeit einstellt </div> -Bei der Verwendung von Sessionen erhält jeder Benutzer eine eindeutige Kennung, die sogenannte Sessions-ID, die in einem Cookie übergeben wird. Dieser dient als Schlüssel zu den Sessionsdaten. Im Gegensatz zu Cookies, die auf der Browserseite gespeichert werden, werden Sessionsdaten auf der Serverseite gespeichert. +Bei der Verwendung von Sessions erhält jeder Benutzer eine eindeutige Kennung, die sogenannte Session-ID, die in einem Cookie übermittelt wird. Diese dient als Schlüssel zu den Session-Daten. Im Gegensatz zu Cookies, die auf der Browserseite gespeichert werden, werden die Daten in der Session auf der Serverseite gespeichert. -Wir konfigurieren die Session in der [Konfiguration |configuration#session], die Wahl der Ablaufzeit ist wichtig. +Die Session wird in der [Konfiguration |configuration#Session] eingestellt, wichtig ist insbesondere die Wahl der Ablaufzeit. -Die Session wird durch das [api:Nette\Http\Session] Objekt verwaltet, das Sie durch Übergabe mittels [Dependency Injection |dependency-injection:passing-dependencies] erhalten. In Presentern rufen Sie einfach `$session = $this->getSession()` auf. +Die Verwaltung der Session übernimmt das Objekt [api:Nette\Http\Session], auf das Sie zugreifen können, indem Sie es sich mittels [Dependency Injection |dependency-injection:passing-dependencies] übergeben lassen. In Presentern reicht es aus, `$session = $this->getSession()` aufzurufen. → [Installation und Anforderungen |@home#Installation] -Session starten .[#toc-starting-session] -======================================== +Session starten +=============== -Standardmäßig startet Nette eine Session automatisch, sobald wir beginnen, Daten aus ihr zu lesen oder in sie zu schreiben. Um eine Session manuell zu starten, verwenden Sie `$session->start()`. +Nette startet die Session standardmäßig automatisch in dem Moment, in dem wir beginnen, Daten daraus zu lesen oder hineinzuschreiben. Manuell wird die Session mit `$session->start()` gestartet. -PHP sendet beim Starten der Session HTTP-Header, die die Zwischenspeicherung beeinflussen, siehe [php:session_cache_limiter], und möglicherweise ein Cookie mit der Sessions-ID. Daher ist es immer notwendig, die Session zu starten, bevor irgendeine Ausgabe an den Browser gesendet wird, da sonst eine Ausnahme ausgelöst wird. Wenn Sie also wissen, dass eine Session während des Renderns der Seite verwendet wird, starten Sie sie vorher manuell, zum Beispiel im Präsentator. +PHP sendet beim Starten der Session HTTP-Header, die das Caching beeinflussen, siehe [php:session_cache_limiter], und gegebenenfalls auch ein Cookie mit der Session-ID. Daher ist es notwendig, die Session immer zu starten, bevor irgendeine Ausgabe an den Browser gesendet wird, andernfalls wird eine Ausnahme ausgelöst. Wenn Sie also wissen, dass während des Renderns der Seite die Session verwendet wird, starten Sie sie manuell vorher, zum Beispiel im Presenter. -Im Entwicklermodus startet Tracy die Session, weil es sie zur Anzeige von Umleitungs- und AJAX-Anforderungsleisten in der Tracy-Leiste verwendet. +Im Entwicklermodus startet Tracy die Session, da sie diese zur Anzeige von Balken mit Weiterleitungen und AJAX-Anfragen in der Tracy Bar verwendet. -Abschnitt .[#toc-section] -========================= +Abschnitte +========== -In reinem PHP ist der Sessionsdatenspeicher als Array implementiert, das über eine globale Variable `$_SESSION` zugänglich ist. Das Problem ist, dass Anwendungen normalerweise aus einer Reihe unabhängiger Teile bestehen, und wenn alle nur ein einziges Array zur Verfügung haben, kommt es früher oder später zu einer Namenskollision. +In reinem PHP wird der Datenspeicher der Session als Array realisiert, das über die globale Variable `$_SESSION` zugänglich ist. Das Problem dabei ist, dass Anwendungen üblicherweise aus einer Reihe voneinander unabhängiger Teile bestehen, und wenn alle nur ein Array zur Verfügung haben, kommt es früher oder später zu Namenskollisionen. -Nette Framework löst das Problem, indem es den gesamten Raum in Abschnitte (Objekte [api:Nette\Http\SessionSection]) unterteilt. Jede Einheit verwendet dann ihren eigenen Abschnitt mit einem eindeutigen Namen, so dass keine Kollisionen auftreten können. +Das Nette Framework löst dieses Problem, indem es den gesamten Raum in Abschnitte (Objekte [api:Nette\Http\SessionSection]) unterteilt. Jede Einheit verwendet dann ihren eigenen Abschnitt mit einem eindeutigen Namen, und es kann zu keiner Kollision mehr kommen. -Wir erhalten den Abschnitt vom Sessionsmanager: +Einen Abschnitt erhalten wir aus der Session: ```php -$section = $session->getSection('unique name'); +$section = $session->getSection('eindeutigerName'); ``` -Im Präsentator genügt es, `getSession()` mit dem Parameter aufzurufen: +Im Presenter genügt es, `getSession()` mit einem Parameter zu verwenden: ```php // $this ist Presenter -$section = $this->getSession('eindeutiger Name'); +$section = $this->getSession('eindeutigerName'); ``` -Das Vorhandensein des Abschnitts kann mit der Methode `$session->hasSection('unique name')` überprüft werden. +Die Existenz eines Abschnitts kann mit der Methode `$session->hasSection('eindeutigerName')` überprüft werden. -Der Abschnitt selbst ist mit den Methoden `set()`, `get()` und `remove()` sehr einfach zu bearbeiten: +Mit dem Abschnitt selbst arbeitet man dann sehr einfach mithilfe der Methoden `set()`, `get()` und `remove()`: ```php -// Schreiben von Variablen -$section->set('benutzername', 'franta'); +// Variable schreiben +$section->set('userName', 'franta'); -// Lesen einer Variable, gibt null zurück, wenn sie nicht existiert -echo $section->get('benutzername'); +// Variable lesen, gibt null zurück, wenn sie nicht existiert +echo $section->get('userName'); -// Entfernen von Variablen -$section->remove('benutzername'); +// Variable löschen +$section->remove('userName'); ``` -Es ist möglich, den Zyklus `foreach` zu verwenden, um alle Variablen von section zu erhalten: +Um alle Variablen aus einem Abschnitt zu erhalten, kann eine `foreach`-Schleife verwendet werden: ```php foreach ($section as $key => $val) { @@ -74,53 +74,53 @@ foreach ($section as $key => $val) { ``` -Wie man den Verfall einstellt .[#toc-how-to-set-expiration] ------------------------------------------------------------ +Ablaufzeit einstellen +--------------------- -Die Gültigkeitsdauer kann für einzelne Bereiche oder sogar einzelne Variablen festgelegt werden. Wir können die Anmeldung des Benutzers in 20 Minuten ablaufen lassen, aber den Inhalt eines Warenkorbs trotzdem speichern. +Für einzelne Abschnitte oder sogar einzelne Variablen kann eine Ablaufzeit eingestellt werden. So können wir die Anmeldung eines Benutzers nach 20 Minuten ablaufen lassen, aber den Inhalt des Warenkorbs weiterhin speichern. ```php -// Der Abschnitt läuft nach 20 Minuten ab +// Abschnitt läuft nach 20 Minuten ab $section->setExpiration('20 minutes'); ``` -Der dritte Parameter der Methode `set()` wird verwendet, um das Verfallsdatum für einzelne Variablen festzulegen: +Zur Einstellung der Ablaufzeit einzelner Variablen dient der dritte Parameter der Methode `set()`: ```php -// Die Variable "flash" läuft nach 30 Sekunden ab +// Variable 'flash' läuft bereits nach 30 Sekunden ab $section->set('flash', $message, '30 seconds'); ``` .[note] -Beachten Sie, dass die Verfallszeit der gesamten Session (siehe [Sessionskonfiguration |configuration#session]) gleich oder höher sein muss als die für einzelne Abschnitte oder Variablen eingestellte Zeit. +Vergessen Sie nicht, dass die Ablaufzeit der gesamten Session (siehe [Session-Konfiguration |configuration#Session]) gleich oder länger sein muss als die bei einzelnen Abschnitten oder Variablen eingestellte Zeit. -Die Aufhebung des zuvor eingestellten Verfalls kann mit der Methode `removeExpiration()` erreicht werden. Die sofortige Löschung des gesamten Abschnitts wird durch die Methode `remove()` gewährleistet. +Das Löschen einer zuvor eingestellten Ablaufzeit erreichen wir mit der Methode `removeExpiration()`. Das sofortige Löschen des gesamten Abschnitts stellt die Methode `remove()` sicher. -Ereignisse $onStart, $onBeforeWrite .[#toc-events-onstart-onbeforewrite] ------------------------------------------------------------------------- +Ereignisse $onStart, $onBeforeWrite +----------------------------------- -Das Objekt `Nette\Http\Session` hat die [Ereignisse |nette:glossary#Events] `$onStart` und `$onBeforeWrite`, so dass Sie Rückrufe hinzufügen können, die nach dem Start der Session oder vor dem Schreiben auf die Festplatte und dem anschließenden Beenden der Session aufgerufen werden. +Das Objekt `Nette\Http\Session` hat die [Ereignisse |nette:glossary#Events Ereignisse] `$onStart` und `$onBeforeWrite`, Sie können also Callbacks hinzufügen, die nach dem Start der Session oder vor ihrem Schreiben auf die Festplatte und dem anschließenden Beenden aufgerufen werden. ```php $session->onBeforeWrite[] = function () { - // Daten in die Session schreiben + // Wir schreiben Daten in die Session $this->section->set('basket', $this->basket); }; ``` -Sessionsverwaltung .[#toc-session-management] -============================================= +Session-Verwaltung +================== -Überblick über die Methoden der Klasse `Nette\Http\Session` zur Sessionsverwaltung: +Übersicht der Methoden der Klasse `Nette\Http\Session` zur Session-Verwaltung: <div class=wiki-methods-brief> start(): void .[method] ----------------------- -Startet eine Session. +Startet die Session. isStarted(): bool .[method] @@ -130,7 +130,7 @@ Ist die Session gestartet? close(): void .[method] ----------------------- -Beendet die Session. Die Session wird automatisch am Ende des Skripts beendet. +Beendet die Session. Die Session wird automatisch am Ende des Skriptlaufs beendet. destroy(): void .[method] @@ -140,71 +140,72 @@ Beendet und löscht die Session. exists(): bool .[method] ------------------------ -Enthält die HTTP-Anfrage ein Cookie mit einer Sessions-ID? +Enthält die HTTP-Anfrage ein Cookie mit einer Session-ID? regenerateId(): void .[method] ------------------------------ -Erzeugt eine neue zufällige Sessions-ID. Die Daten bleiben unverändert. +Generiert eine neue zufällige Session-ID. Die Daten bleiben erhalten. getId(): string .[method] ------------------------- -Gibt die Sessions-ID zurück. +Gibt die Session-ID zurück. </div> -Konfiguration .[#toc-configuration] ------------------------------------ +Konfiguration +------------- -Wir konfigurieren die Session in der [Konfiguration |configuration#session]. Wenn Sie eine Anwendung schreiben, die keinen DI-Container verwendet, verwenden Sie diese Methoden, um sie zu konfigurieren. Sie müssen vor dem Start der Session aufgerufen werden. +Die Session wird in der [Konfiguration |configuration#Session] eingestellt. Wenn Sie eine Anwendung schreiben, die keinen DI-Container verwendet, dienen diese Methoden zur Konfiguration. Sie müssen aufgerufen werden, bevor die Session gestartet wird. <div class=wiki-methods-brief> setName(string $name): static .[method] --------------------------------------- -Legt den Namen des Cookies fest, der zur Übertragung der Sessions-ID verwendet wird. Der Standardname ist `PHPSESSID`. Dies ist nützlich, wenn Sie mehrere verschiedene Anwendungen auf derselben Website ausführen. +Legt den Namen des Cookies fest, in dem die Session-ID übertragen wird. Der Standardname ist `PHPSESSID`. Dies ist nützlich, wenn Sie mehrere unterschiedliche Anwendungen innerhalb einer Website betreiben. getName(): string .[method] --------------------------- -Gibt den Namen des Session-Cookies zurück. +Gibt den Namen des Cookies zurück, in dem die Session-ID übertragen wird. setOptions(array $options): static .[method] -------------------------------------------- -Konfiguriert die Session. Es ist möglich, alle [PHP-Session-Direktiven |https://www.php.net/manual/en/session.configuration.php] zu setzen (im camelCase-Format, z.B. `savePath` statt `session.save_path` zu schreiben) und auch [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. +Konfiguriert die Session. Es können alle PHP [Session-Direktiven |https://www.php.net/manual/en/session.configuration.php] (im CamelCase-Format, z.B. statt `session.save_path` schreiben wir `savePath`) sowie [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters] eingestellt werden. setExpiration(?string $time): static .[method] ---------------------------------------------- -Legt die Zeit der Inaktivität fest, nach der die Session abläuft. +Legt die Inaktivitätsdauer fest, nach der die Session abläuft. -setCookieParameters(string $path, string $domain=null, bool $secure=null, string $samesite=null): static .[method] ------------------------------------------------------------------------------------------------------------------- -Legt Parameter für Cookies fest. Sie können die Standardparameterwerte in [configuration |configuration#Session cookie] ändern. +setCookieParameters(string $path, ?string $domain=null, ?bool $secure=null, ?string $samesite=null): static .[method] +--------------------------------------------------------------------------------------------------------------------- +Einstellung der Parameter für das Cookie. Die Standardwerte der Parameter können Sie in der [Konfiguration |configuration#Session-Cookie] ändern. setSavePath(string $path): static .[method] ------------------------------------------- -Legt das Verzeichnis fest, in dem Sessionsdateien gespeichert werden. +Legt das Verzeichnis fest, in dem die Session-Dateien gespeichert werden. setHandler(\SessionHandlerInterface $handler): static .[method] --------------------------------------------------------------- -Legt einen eigenen Handler fest, siehe [PHP-Dokumentation |https://www.php.net/manual/en/class.sessionhandlerinterface.php]. +Einstellung eines eigenen Handlers, siehe [PHP-Dokumentation |https://www.php.net/manual/en/class.sessionhandlerinterface.php]. </div> -Sicherheit zuerst .[#toc-safety-first] -====================================== +Sicherheit geht vor +=================== -Der Server geht davon aus, dass er mit demselben Benutzer kommuniziert, solange die Anfragen dieselbe Sessionskennung enthalten. Die Aufgabe von Sicherheitsmechanismen ist es, sicherzustellen, dass dieses Verhalten auch wirklich funktioniert und dass es keine Möglichkeit gibt, eine Kennung zu ersetzen oder zu stehlen. +Der Server geht davon aus, dass er immer mit demselben Benutzer kommuniziert, solange die Anfragen von derselben Session-ID begleitet werden. Die Aufgabe der Sicherheitsmechanismen besteht darin, sicherzustellen, dass dies tatsächlich der Fall ist und es nicht möglich ist, die Kennung zu stehlen oder unterzuschieben. -Aus diesem Grund konfiguriert Nette Framework die PHP-Direktiven so, dass die Sessions-ID nur in Cookies übertragen wird, der Zugriff von JavaScript vermieden wird und die Identifikatoren in der URL ignoriert werden. Außerdem wird in kritischen Momenten, z. B. bei der Anmeldung eines Benutzers, eine neue Sessions-ID generiert. +Das Nette Framework konfiguriert daher die PHP-Direktiven korrekt, um die Session-ID nur im Cookie zu übertragen, sie für JavaScript unzugänglich zu machen und eventuelle Kennungen in der URL zu ignorieren. Darüber hinaus generiert es in kritischen Momenten, wie z. B. bei der Benutzeranmeldung, eine neue Session-ID. -Die Funktion ini_set wird für die Konfiguration von PHP verwendet, aber leider ist ihre Verwendung bei einigen Webhosting-Diensten verboten. Wenn dies bei Ihnen der Fall ist, versuchen Sie, Ihren Hosting-Anbieter zu bitten, diese Funktion für Sie zuzulassen oder zumindest seinen Server richtig zu konfigurieren. .[note] +.[note] +Zur Konfiguration von PHP wird die Funktion ini_set verwendet, die leider von einigen Hostern verboten wird. Wenn dies auch bei Ihrem Hoster der Fall ist, versuchen Sie, mit ihm zu vereinbaren, dass er Ihnen die Funktion erlaubt oder zumindest den Server konfiguriert. diff --git a/http/de/urls.texy b/http/de/urls.texy index 6a6647d450..9f36fb4411 100644 --- a/http/de/urls.texy +++ b/http/de/urls.texy @@ -1,8 +1,8 @@ -URL-Parser und -Ersteller -************************* +Arbeiten mit URLs +***************** .[perex] -Die Klassen [Url |#Url], [UrlImmutable |#UrlImmutable] und [UrlScript |#UrlScript] erleichtern das Verwalten, Parsen und Manipulieren von URLs. +Die Klassen [#Url], [#UrlImmutable] und [#UrlScript] ermöglichen das einfache Generieren, Parsen und Manipulieren von URLs. → [Installation und Anforderungen |@home#Installation] @@ -10,7 +10,7 @@ Die Klassen [Url |#Url], [UrlImmutable |#UrlImmutable] und [UrlScript |#UrlScrip Url === -Die Klasse [api:Nette\Http\Url] erleichtert die Arbeit mit der URL und ihren einzelnen Komponenten, die in diesem Diagramm skizziert werden: +Die Klasse [api:Nette\Http\Url] ermöglicht das einfache Arbeiten mit URLs und ihren einzelnen Komponenten, die diese Skizze erfasst: /--pre scheme user password host port path query fragment @@ -22,7 +22,7 @@ Die Klasse [api:Nette\Http\Url] erleichtert die Arbeit mit der URL und ihren ein hostUrl authority \-- -Die URL-Generierung ist intuitiv: +Das Generieren von URLs ist intuitiv: ```php use Nette\Http\Url; @@ -36,7 +36,7 @@ $url->setScheme('https') echo $url; // 'https://localhost/edit?foo=bar' ``` -Sie können die URL auch parsen und dann manipulieren: +Es ist auch möglich, eine URL zu parsen und weiter zu manipulieren: ```php $url = new Url( @@ -44,62 +44,94 @@ $url = new Url( ); ``` -Die folgenden Methoden stehen zur Verfügung, um einzelne URL-Komponenten zu erhalten oder zu ändern: +Die Klasse `Url` implementiert die Schnittstelle `JsonSerializable` und hat die Methode `__toString()`, sodass das Objekt ausgegeben oder in Daten verwendet werden kann, die an `json_encode()` übergeben werden. + +```php +echo $url; +echo json_encode([$url]); +``` + + +URL-Komponenten .[method] +------------------------- + +Zum Abrufen oder Ändern einzelner URL-Komponenten stehen Ihnen diese Methoden zur Verfügung: .[language-php] -| Setter | Getter | Rückgabewert +| Setter | Getter | Zurückgegebener Wert |-------------------------------------------------------------------------------------------- -| `setScheme(string $scheme)`| `getScheme(): string`| `'http'` -| `setUser(string $user)`| `getUser(): string`| `'john'` -| `setPassword(string $password)`| `getPassword(): string`| `'xyz*12'` -| `setHost(string $host)`| `getHost(): string`| `'nette.org'` -| `setPort(int $port)`| `getPort(): ?int`| `8080` -| | `getDefaultPort(): ?int`| `80` -| `setPath(string $path)`| `getPath(): string`| `'/en/download'` -| `setQuery(string\|array $query)`| `getQuery(): string`| `'name=param'` -| `setFragment(string $fragment)`| `getFragment(): string`| `'footer'` -| | `getAuthority(): string`| `'nette.org:8080'` -| | `getHostUrl(): string`| `'http://nette.org:8080'` -| | `getAbsoluteUrl(): string` | vollständige URL - -Wir können auch mit einzelnen Abfrageparametern arbeiten: +| `setScheme(string $scheme)` | `getScheme(): string` | `'http'` +| `setUser(string $user)` | `getUser(): string` | `'john'` +| `setPassword(string $password)` | `getPassword(): string` | `'xyz*12'` +| `setHost(string $host)` | `getHost(): string` | `'nette.org'` +| `setPort(int $port)` | `getPort(): ?int` | `8080` +| | `getDefaultPort(): ?int` | `80` +| `setPath(string $path)` | `getPath(): string` | `'/en/download'` +| `setQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` +| `setFragment(string $fragment)` | `getFragment(): string` | `'footer'` +| | `getAuthority(): string` | `'john:xyz%2A12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | ganze URL + +Warnung: Wenn Sie mit einer URL arbeiten, die aus einer [HTTP-Anfrage |request] stammt, beachten Sie, dass sie kein Fragment enthalten wird, da der Browser es nicht an den Server sendet. + +Wir können auch mit einzelnen Query-Parametern arbeiten mittels: .[language-php] -| Setter | Getter +| Setter | Getter |--------------------------------------------------- -| `setQuery(string\|array $query)` | `getQueryParameters(): array` -| `setQueryParameter(string $name, $val)`| `getQueryParameter(string $name)` +| `setQuery(string\|array $query)` | `getQueryParameters(): array` +| `setQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` -Die Methode `getDomain(int $level = 2)` gibt den rechten oder linken Teil des Hosts zurück. So funktioniert es, wenn der Host `www.nette.org` ist: + +getDomain(int $level = 2): string .[method] +------------------------------------------- +Gibt den rechten oder linken Teil des Hosts zurück. So funktioniert es, wenn der Host `www.nette.org` ist: .[language-php] -| `getDomain(1)` | `'org'` -| `getDomain(2)` | `'nette.org'` -| `getDomain(3)` | `'www.nette.org'` -| `getDomain(0)` | `'www.nette.org'` -| `getDomain(-1)` | `'www.nette'` -| `getDomain(-2)` | `'www'` -| `getDomain(-3)` | `''` +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `''` -Die Klasse `Url` implementiert die Schnittstelle `JsonSerializable` und verfügt über eine Methode `__toString()`, damit das Objekt gedruckt oder in Daten verwendet werden kann, die an `json_encode()` übergeben werden. +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +Überprüft, ob zwei URLs identisch sind. ```php -echo $url; -echo json_encode([$url]); +$url->isEqual('https://nette.org'); ``` -Die Methode `isEqual(string|Url $anotherUrl): bool` prüft, ob die beiden URLs identisch sind. + +Url::isAbsolute(string $url): bool .[method]{data-version:3.3.2} +---------------------------------------------------------------- +Überprüft, ob die URL absolut ist. Eine URL wird als absolut betrachtet, wenn sie mit einem Schema (z. B. http, https, ftp) gefolgt von einem Doppelpunkt beginnt. ```php -$url->isEqual('https://nette.org'); +Url::isAbsolute('https://nette.org'); // true +Url::isAbsolute('//nette.org'); // false +``` + + +Url::removeDotSegments(string $path): string .[method]{data-version:3.3.2} +-------------------------------------------------------------------------- +Normalisiert den Pfad in einer URL durch Entfernen der speziellen Segmente `.` und `..`. Die Methode entfernt überflüssige Pfadelemente auf die gleiche Weise, wie es Webbrowser tun. + +```php +Url::removeDotSegments('/path/../subtree/./file.txt'); // '/subtree/file.txt' +Url::removeDotSegments('/../foo/./bar'); // '/foo/bar' +Url::removeDotSegments('./today/../file.txt'); // 'file.txt' ``` -UrlImmutable .[#toc-urlimmutable] -================================= +UrlImmutable +============ -Die Klasse [api:Nette\Http\UrlImmutable] ist eine unveränderbare Alternative zur Klasse `Url` (so wie in PHP `DateTimeImmutable` eine unveränderbare Alternative zu `DateTime` ist). Anstelle von Settern hat sie sogenannte Wither, die das Objekt nicht verändern, sondern neue Instanzen mit einem geänderten Wert zurückgeben: +Die Klasse [api:Nette\Http\UrlImmutable] ist eine immutable (unveränderliche) Alternative zur Klasse [#Url] (ähnlich wie in PHP `DateTimeImmutable` eine unveränderliche Alternative zu `DateTime` ist). Anstelle von Settern hat sie sogenannte Wither, die das Objekt nicht ändern, sondern neue Instanzen mit dem geänderten Wert zurückgeben: ```php use Nette\Http\UrlImmutable; @@ -111,53 +143,96 @@ $url = new UrlImmutable( $newUrl = $url ->withUser('') ->withPassword('') - ->withPath('/en/'); + ->withPath('/cs/'); + +echo $newUrl; // 'http://john:xyz%2A12@nette.org:8080/cs/?name=param#footer' +``` + +Die Klasse `UrlImmutable` implementiert die Schnittstelle `JsonSerializable` und hat die Methode `__toString()`, sodass das Objekt ausgegeben oder in Daten verwendet werden kann, die an `json_encode()` übergeben werden. -echo $newUrl; // 'http://nette.org:8080/en/?name=param#footer' +```php +echo $url; +echo json_encode([$url]); ``` -Die folgenden Methoden stehen zur Verfügung, um einzelne URL-Komponenten zu erhalten oder zu ändern: + +URL-Komponenten .[method] +------------------------- + +Zum Abrufen oder Ändern einzelner URL-Komponenten dienen Methoden: .[language-php] -| Wither | Getter | Rückgabewert +| Wither | Getter | Zurückgegebener Wert |-------------------------------------------------------------------------------------------- -| `withScheme(string $scheme)`| `getScheme(): string`| `'http'` -| `withUser(string $user)`| `getUser(): string`| `'john'` -| `withPassword(string $password)`| `getPassword(): string`| `'xyz*12'` -| `withHost(string $host)`| `getHost(): string`| `'nette.org'` -| `withPort(int $port)`| `getPort(): ?int`| `8080` -| | `getDefaultPort(): ?int`| `80` -| `withPath(string $path)`| `getPath(): string`| `'/en/download'` -| `withQuery(string\|array $query)`| `getQuery(): string`| `'name=param'` -| `withFragment(string $fragment)`| `getFragment(): string`| `'footer'` -| | `getAuthority(): string`| `'nette.org:8080'` -| | `getHostUrl(): string`| `'http://nette.org:8080'` -| | `getAbsoluteUrl(): string` | vollständige URL - -Wir können auch mit einzelnen Abfrageparametern arbeiten: +| `withScheme(string $scheme)` | `getScheme(): string` | `'http'` +| `withUser(string $user)` | `getUser(): string` | `'john'` +| `withPassword(string $password)` | `getPassword(): string` | `'xyz*12'` +| `withHost(string $host)` | `getHost(): string` | `'nette.org'` +| `withPort(int $port)` | `getPort(): ?int` | `8080` +| | `getDefaultPort(): ?int` | `80` +| `withPath(string $path)` | `getPath(): string` | `'/en/download'` +| `withQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` +| `withFragment(string $fragment)` | `getFragment(): string` | `'footer'` +| | `getAuthority(): string` | `'john:xyz%2A12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | ganze URL + +Die Methode `withoutUserInfo()` entfernt `user` und `password`. + +Wir können auch mit einzelnen Query-Parametern arbeiten mittels: .[language-php] -| Wither | Getter +| Wither | Getter |----------------------------------------------- -| `withQuery(string\|array $query)` | `getQueryParameters(): array` -| `withQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` +| `withQuery(string\|array $query)` | `getQueryParameters(): array` +| `withQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` -Die Methode `getDomain(int $level = 2)` funktioniert genauso wie die Methode in `Url`. Mit der Methode `withoutUserInfo()` werden `user` und `password` entfernt. -Die Klasse `UrlImmutable` implementiert die Schnittstelle `JsonSerializable` und verfügt über eine Methode `__toString()`, damit das Objekt gedruckt oder in Daten verwendet werden kann, die an `json_encode()` übergeben werden. +getDomain(int $level = 2): string .[method] +------------------------------------------- +Gibt den rechten oder linken Teil des Hosts zurück. So funktioniert es, wenn der Host `www.nette.org` ist: + +.[language-php] +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `''` + + +resolve(string $reference): UrlImmutable .[method]{data-version:3.3.2} +---------------------------------------------------------------------- +Leitet eine absolute URL auf die gleiche Weise ab, wie ein Browser Links auf einer HTML-Seite verarbeitet: +- wenn der Link eine absolute URL ist (Schema enthält), wird er unverändert verwendet +- wenn der Link mit `//` beginnt, wird nur das Schema aus der aktuellen URL übernommen +- wenn der Link mit `/` beginnt, wird ein absoluter Pfad vom Domain-Stamm erstellt +- in anderen Fällen wird die URL relativ zum aktuellen Pfad zusammengestellt ```php -echo $url; -echo json_encode([$url]); +$url = new UrlImmutable('https://example.com/path/page'); +echo $url->resolve('../foo'); // 'https://example.com/foo' +echo $url->resolve('/bar'); // 'https://example.com/bar' +echo $url->resolve('sub/page.html'); // 'https://example.com/path/sub/page.html' +``` + + +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +Überprüft, ob zwei URLs identisch sind. + +```php +$url->isEqual('https://nette.org'); ``` -Die Methode `isEqual(string|Url $anotherUrl): bool` prüft, ob die beiden URLs identisch sind. +UrlScript +========= -UrlScript .[#toc-urlscript] -=========================== +Die Klasse [api:Nette\Http\UrlScript] ist ein Nachkomme von [#UrlImmutable] und erweitert diese um weitere virtuelle URL-Komponenten, wie das Stammverzeichnis des Projekts usw. Wie die übergeordnete Klasse ist sie ein immutables (unveränderliches) Objekt. -Die Klasse [api:Nette\Http\UrlScript] ist ein Abkömmling von `UrlImmutable` und unterscheidet zusätzlich diese logischen Teile der URL: +Das folgende Diagramm zeigt die Komponenten, die UrlScript erkennt: /--pre baseUrl basePath relativePath relativeUrl @@ -169,17 +244,23 @@ Die Klasse [api:Nette\Http\UrlScript] ist ein Abkömmling von `UrlImmutable` und scriptPath pathInfo \-- -Die folgenden Methoden sind verfügbar, um diese Teile zu erhalten: +- `baseUrl` ist die Basis-URL der Anwendung einschließlich Domain und Pfadteil zum Stammverzeichnis der Anwendung +- `basePath` ist der Pfadteil zum Stammverzeichnis der Anwendung +- `scriptPath` ist der Pfad zum aktuellen Skript +- `relativePath` ist der Name des Skripts (ggf. weitere Pfadsegmente) relativ zum basePath +- `relativeUrl` ist der gesamte Teil der URL nach baseUrl, einschließlich Query-String und Fragment. +- `pathInfo` ist ein heute selten genutzter Teil der URL nach dem Skriptnamen + +Zum Abrufen von URL-Teilen stehen Methoden zur Verfügung: .[language-php] -| Getter | Rückgabewert +| Getter | Zurückgegebener Wert |------------------------------------------------ -| `getScriptPath(): string`| `'/admin/script.php'` -| `getBasePath(): string`| `'/admin/'` -| `getBaseUrl(): string`| `'http://nette.org/admin/'` -| `getRelativePath(): string`| `'script.php'` -| `getRelativeUrl(): string`| `'script.php/pathinfo/?name=param#footer'` -| `getPathInfo(): string`| `'/pathinfo/'` - - -Wir erstellen die Objekte `UrlScript` nicht direkt, aber die Methode [Nette\Http\Request::getUrl() |request] gibt sie zurück. +| `getScriptPath(): string` | `'/admin/script.php'` +| `getBasePath(): string` | `'/admin/'` +| `getBaseUrl(): string` | `'http://nette.org/admin/'` +| `getRelativePath(): string` | `'script.php'` +| `getRelativeUrl(): string` | `'script.php/pathinfo/?name=param#footer'` +| `getPathInfo(): string` | `'/pathinfo/'` + +Objekte `UrlScript` erstellen wir normalerweise nicht direkt, sondern sie werden von der Methode [Nette\Http\Request::getUrl() |request] mit bereits korrekt eingestellten Komponenten für die aktuelle HTTP-Anfrage zurückgegeben. diff --git a/http/el/@home.texy b/http/el/@home.texy index e185acdbb4..7f99e07aa7 100644 --- a/http/el/@home.texy +++ b/http/el/@home.texy @@ -2,13 +2,13 @@ Nette HTTP ********** .[perex] -Το πακέτο `nette/http` ενθυλακώνει το [αίτημα |request] και την [απόκριση |response] [HTTP |request], δουλεύει με [συνεδρίες |sessions] και [αναλύει και κατασκευάζει διευθύνσεις URL |urls]. +Το πακέτο `nette/http` ενσωματώνει το [HTTP request |request] & [response], την εργασία με [sessions] και την [ανάλυση και σύνθεση URL |urls]. -Εγκατάσταση .[#toc-installation] --------------------------------- +Εγκατάσταση +----------- -Κατεβάστε και εγκαταστήστε το πακέτο χρησιμοποιώντας το [Composer |best-practices:composer]: +Κατεβάστε και εγκαταστήστε τη βιβλιοθήκη χρησιμοποιώντας το εργαλείο [Composer|best-practices:composer]: ```shell composer require nette/http diff --git a/http/el/@left-menu.texy b/http/el/@left-menu.texy index 783cd16fa4..a8ca399134 100644 --- a/http/el/@left-menu.texy +++ b/http/el/@left-menu.texy @@ -1,8 +1,8 @@ -Net HTTP -******** -- [Επισκόπηση |@home] -- [Αίτημα HTTP |request] -- [Απόκριση HTTP |response] -- [Συνεδρίες |Sessions] -- [Βοηθητικό πρόγραμμα URL |urls] -- [Διαμόρφωση |Configuration] +Nette HTTP +********** +- [Εισαγωγή |@home] +- [HTTP request|request] +- [HTTP response|response] +- [Sessions] +- [Βοηθητικά προγράμματα URL |urls] +- [Διαμόρφωση |configuration] diff --git a/http/el/@meta.texy b/http/el/@meta.texy new file mode 100644 index 0000000000..88e29852c7 --- /dev/null +++ b/http/el/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Τεκμηρίωση}} diff --git a/http/el/configuration.texy b/http/el/configuration.texy index 83c5cf6207..828c56c70c 100644 --- a/http/el/configuration.texy +++ b/http/el/configuration.texy @@ -1,40 +1,40 @@ -Ρύθμιση του HTTP -**************** +Διαμόρφωση HTTP +*************** .[perex] Επισκόπηση των επιλογών διαμόρφωσης για το Nette HTTP. -Αν δεν χρησιμοποιείτε ολόκληρο το πλαίσιο, αλλά μόνο αυτή τη βιβλιοθήκη, διαβάστε [πώς να φορτώσετε τη διαμόρφωση |bootstrap:]. +Εάν δεν χρησιμοποιείτε ολόκληρο το framework, αλλά μόνο αυτή τη βιβλιοθήκη, διαβάστε [πώς να φορτώσετε τη διαμόρφωση|bootstrap:]. -Κεφαλίδες HTTP .[#toc-http-headers] -=================================== +Κεφαλίδες HTTP +============== ```neon http: - # επικεφαλίδες που αποστέλλονται με κάθε αίτηση + # κεφαλίδες που αποστέλλονται με κάθε αίτημα headers: X-Powered-By: MyCMS X-Content-Type-Options: nosniff X-XSS-Protection: '1; mode=block' - # επηρεάζει την επικεφαλίδα X-Frame-Options - frames: ... # (string|bool) προεπιλογή 'SAMEORIGIN' + # επηρεάζει την κεφαλίδα X-Frame-Options + frames: ... # (string|bool) προεπιλογή είναι 'SAMEORIGIN' ``` -Για λόγους ασφαλείας, το πλαίσιο στέλνει μια επικεφαλίδα `X-Frame-Options: SAMEORIGIN`, η οποία λέει ότι μια σελίδα μπορεί να εμφανιστεί μέσα σε μια άλλη σελίδα (στο στοιχείο `<iframe>`) μόνο εάν αυτή βρίσκεται στον ίδιο τομέα. Αυτό μπορεί να είναι ανεπιθύμητο σε ορισμένες περιπτώσεις (για παράδειγμα, αν αναπτύσσετε μια εφαρμογή στο Facebook), οπότε η συμπεριφορά μπορεί να αλλάξει με τον καθορισμό των πλαισίων `frames: http://allowed-host.com`. +Για λόγους ασφαλείας, το framework στέλνει την κεφαλίδα `X-Frame-Options: SAMEORIGIN`, η οποία δηλώνει ότι η σελίδα μπορεί να εμφανιστεί μέσα σε άλλη σελίδα (στο στοιχείο `<iframe>`) μόνο εάν βρίσκεται στο ίδιο domain. Αυτό μπορεί να είναι ανεπιθύμητο σε ορισμένες περιπτώσεις (για παράδειγμα, εάν αναπτύσσετε μια εφαρμογή για το Facebook), οπότε η συμπεριφορά μπορεί να αλλάξει ορίζοντας `frames: http://allowed-host.com` ή `frames: true`. -Πολιτική ασφάλειας περιεχομένου .[#toc-content-security-policy] ---------------------------------------------------------------- +Πολιτική Ασφάλειας Περιεχομένου +------------------------------- -Οι επικεφαλίδες `Content-Security-Policy` (εφεξής CSP) μπορούν να συναρμολογηθούν εύκολα, η περιγραφή τους μπορεί να βρεθεί στην [περιγραφή CSP |https://content-security-policy.com]. Οι οδηγίες CSP (όπως `script-src`) μπορούν να γραφούν είτε ως συμβολοσειρές σύμφωνα με τις προδιαγραφές είτε ως πίνακες τιμών για καλύτερη αναγνωσιμότητα. Τότε δεν χρειάζεται να γράφονται εισαγωγικά γύρω από λέξεις-κλειδιά όπως το `'self'`. Η Nette θα παράγει επίσης αυτόματα μια τιμή του `nonce`, έτσι ώστε το `'nonce-y4PopTLM=='` να αποστέλλεται στην επικεφαλίδα. +Μπορείτε εύκολα να δημιουργήσετε κεφαλίδες `Content-Security-Policy` (εφεξής CSP), η περιγραφή τους βρίσκεται στην [περιγραφή CSP |https://content-security-policy.com]. Οι οδηγίες CSP (όπως `script-src`) μπορούν να γραφτούν είτε ως συμβολοσειρές σύμφωνα με την προδιαγραφή, είτε ως πίνακες τιμών για καλύτερη αναγνωσιμότητα. Τότε δεν χρειάζεται να γράψετε εισαγωγικά γύρω από λέξεις-κλειδιά όπως `'self'`. Το Nette δημιουργεί επίσης αυτόματα μια τιμή `nonce`, οπότε η κεφαλίδα θα περιέχει κάτι σαν `'nonce-y4PopTLM=='`. ```neon http: - # Πολιτική ασφάλειας περιεχομένου + # Content Security Policy csp: - # συμβολοσειρά σύμφωνα με την προδιαγραφή CSP + # συμβολοσειρά στη μορφή σύμφωνα με την προδιαγραφή CSP default-src: "'self' https://example.com" # πίνακας τιμών @@ -44,23 +44,23 @@ http: - self - https://example.com - # bool στην περίπτωση των διακοπτών + # bool στην περίπτωση διακοπτών upgrade-insecure-requests: true block-all-mixed-content: false ``` -Χρήση `<script n:nonce>...</script>` στα πρότυπα και η τιμή nonce θα συμπληρωθεί αυτόματα. Η δημιουργία ασφαλών ιστότοπων στη Nette είναι πραγματικά εύκολη. +Στα templates, χρησιμοποιήστε `<script n:nonce>...</script>` και η τιμή nonce θα συμπληρωθεί αυτόματα. Η δημιουργία ασφαλών ιστότοπων στο Nette είναι πραγματικά εύκολη. -Ομοίως, μπορούν να προστεθούν οι επικεφαλίδες `Content-Security-Policy-Report-Only` (οι οποίες μπορούν να χρησιμοποιηθούν παράλληλα με το CSP) και η [Πολιτική χαρακτηριστικών |https://developers.google.com/web/updates/2018/06/feature-policy]: +Ομοίως, μπορείτε να δημιουργήσετε κεφαλίδες `Content-Security-Policy-Report-Only` (οι οποίες μπορούν να χρησιμοποιηθούν παράλληλα με το CSP) και [Feature Policy|https://developers.google.com/web/updates/2018/06/feature-policy]: ```neon http: - # Έκθεση πολιτικής ασφάλειας περιεχομένου Μόνο + # Content Security Policy Report-Only cspReportOnly: default-src: self report-uri: 'https://my-report-uri-endpoint' - # Πολιτική χαρακτηριστικών + # Feature Policy featurePolicy: unsized-media: none geolocation: @@ -69,91 +69,103 @@ http: ``` -HTTP Cookie .[#toc-http-cookie] -------------------------------- +HTTP cookie +----------- -Μπορείτε να αλλάξετε τις προεπιλεγμένες τιμές ορισμένων παραμέτρων των μεθόδων [Nette\Http\Response::setCookie() |response#setCookie] και session. +Μπορείτε να αλλάξετε τις προεπιλεγμένες τιμές ορισμένων παραμέτρων της μεθόδου [Nette\Http\Response::setCookie() |response#setCookie] και του session. ```neon http: - # Πεδίο εφαρμογής cookie ανά διαδρομή - cookiePath: ... # (συμβολοσειρά) προεπιλογή '/' + # εμβέλεια cookie ανά διαδρομή + cookiePath: ... # (string) προεπιλογή είναι '/' - # ποιοι υπολογιστές επιτρέπεται να λάβουν το cookie - cookieDomain: 'example.com' # (string|domain) με προεπιλογή unset + # domains που δέχονται cookies + cookieDomain: 'example.com' # (string|domain) προεπιλογή είναι μη ορισμένο - # να αποστέλλονται cookies μόνο μέσω HTTPS; - cookieSecure: ... # (bool|auto) προεπιλογή σε auto + # αποστολή cookies μόνο μέσω HTTPS; + cookieSecure: ... # (bool|auto) προεπιλογή είναι auto - # απενεργοποιεί την αποστολή του cookie που χρησιμοποιεί η Nette ως προστασία κατά του CSRF - disableNetteCookie: ... # (bool) προεπιλογή σε false + # απενεργοποιεί την αποστολή του cookie που χρησιμοποιείται από το Nette για προστασία από CSRF + disableNetteCookie: ... # (bool) προεπιλογή είναι false ``` -Η επιλογή `cookieDomain` καθορίζει ποιοι τομείς (origins) μπορούν να δέχονται cookies. Αν δεν καθοριστεί, το cookie γίνεται αποδεκτό από το ίδιο (υπο)domain που έχει οριστεί από αυτό, *εξαιρώντας* τα υποτομείς τους. Αν καθοριστεί η επιλογή `cookieDomain`, τότε συμπεριλαμβάνονται και τα subdomains. Επομένως, ο προσδιορισμός του `cookieDomain` είναι λιγότερο περιοριστικός από την παράλειψη. +Το attribute `cookieDomain` καθορίζει ποια domains μπορούν να δέχονται cookies. Εάν δεν καθοριστεί, το cookie γίνεται αποδεκτό από το ίδιο (υπο)domain που το όρισε, *αλλά όχι* από τα υποdomains του. Εάν το `cookieDomain` καθοριστεί, περιλαμβάνονται και τα υποdomains. Επομένως, ο καθορισμός του `cookieDomain` είναι λιγότερο περιοριστικός από την παράλειψή του. -Για παράδειγμα, αν οριστεί το `cookieDomain: nette.org`, το cookie είναι επίσης διαθέσιμο σε όλα τα subdomains όπως το `doc.nette.org`. Αυτό μπορεί επίσης να επιτευχθεί με την ειδική τιμή `domain`, δηλαδή `cookieDomain: domain`. +Για παράδειγμα, με `cookieDomain: nette.org`, τα cookies είναι επίσης διαθέσιμα σε όλα τα υποdomains όπως το `doc.nette.org`. Αυτό μπορεί επίσης να επιτευχθεί χρησιμοποιώντας την ειδική τιμή `domain`, δηλαδή `cookieDomain: domain`. -Η προεπιλεγμένη τιμή του `cookieSecure` είναι `auto`, πράγμα που σημαίνει ότι εάν ο ιστότοπος εκτελείται σε HTTPS, τα cookies θα αποστέλλονται με τη σημαία `Secure` και επομένως θα είναι διαθέσιμα μόνο μέσω HTTPS. +Η προεπιλεγμένη τιμή `auto` για το attribute `cookieSecure` σημαίνει ότι εάν ο ιστότοπος εκτελείται σε HTTPS, τα cookies θα αποστέλλονται με τη σημαία `Secure` και επομένως θα είναι διαθέσιμα μόνο μέσω HTTPS. -Μεσολάβηση HTTP .[#toc-http-proxy] ----------------------------------- +HTTP proxy +---------- -Εάν ο ιστότοπος λειτουργεί πίσω από έναν διακομιστή μεσολάβησης HTTP, εισαγάγετε τη διεύθυνση IP του διακομιστή μεσολάβησης, ώστε να λειτουργεί σωστά η ανίχνευση των συνδέσεων HTTPS, καθώς και τη διεύθυνση IP του πελάτη. Δηλαδή, έτσι ώστε οι [Nette\Http\Request::getRemoteAddress() |request#getRemoteAddress] και [isSecured() |request#isSecured] να επιστρέφουν τις σωστές τιμές και να δημιουργούνται σύνδεσμοι με το πρωτόκολλο `https:` στα πρότυπα. +Εάν ο ιστότοπος εκτελείται πίσω από ένα HTTP proxy, καθορίστε τη διεύθυνση IP του, ώστε η ανίχνευση σύνδεσης μέσω HTTPS και η διεύθυνση IP του client να λειτουργούν σωστά. Δηλαδή, ώστε οι συναρτήσεις [Nette\Http\Request::getRemoteAddress() |request#getRemoteAddress] και [isSecured() |request#isSecured] να επιστρέφουν τις σωστές τιμές και οι σύνδεσμοι με το πρωτόκολλο `https:` να δημιουργούνται στα templates. ```neon http: - # Διεύθυνση IP, περιοχή (π.χ. 127.0.0.1/8) ή συστοιχία αυτών των τιμών - proxy: 127.0.0.1 # (string|string[]) προεπιλογή σε none + # Διεύθυνση IP, εύρος (π.χ., 127.0.0.1/8), ή πίνακας αυτών των τιμών + proxy: 127.0.0.1 # (string|string[]) προεπιλογή είναι μη ορισμένο ``` -Σύνοδος .[#toc-session] -======================= +Session +======= -Βασικές ρυθμίσεις [συνεδριών |sessions]: +Βασικές ρυθμίσεις [sessions |sessions]: ```neon session: - # εμφανίζει πάνελ συνεδρίας στο Tracy Bar; - debugger: ... # (bool) προεπιλογή false + # εμφάνιση του πίνακα session στο Tracy Bar; + debugger: ... # (bool) προεπιλογή είναι false - # χρόνος αδράνειας μετά τον οποίο λήγει η σύνοδος - expiration: 14 days # (string) προεπιλογή '3 ώρες' + # χρόνος αδράνειας μετά τον οποίο λήγει το session + expiration: 14 days # (string) προεπιλογή είναι '3 hours' - # πότε θα ξεκινήσει η συνεδρία; - autoStart: ... # (smart|always|never) προεπιλογή 'smart' + # πότε πρέπει να ξεκινήσει το session; + autoStart: ... # (smart|always|never) προεπιλογή είναι 'smart' - # handler, υπηρεσία που υλοποιεί τη διεπαφή SessionHandlerInterface + # handler, μια υπηρεσία που υλοποιεί το interface SessionHandlerInterface handler: @handlerService ``` -Η επιλογή `autoStart` ελέγχει πότε θα ξεκινήσει η σύνοδος. Η τιμή `always` σημαίνει ότι η σύνοδος ξεκινά πάντα κατά την εκκίνηση της εφαρμογής. Η τιμή `smart` σημαίνει ότι η σύνοδος θα ξεκινήσει κατά την εκκίνηση της εφαρμογής μόνο αν υπάρχει ήδη ή τη στιγμή που θέλουμε να διαβάσουμε από αυτήν ή να γράψουμε σε αυτήν. Τέλος, η τιμή `never` απενεργοποιεί την αυτόματη εκκίνηση της συνόδου. +Η επιλογή `autoStart` ελέγχει πότε πρέπει να ξεκινήσει το session. Η τιμή `always` σημαίνει ότι το session θα ξεκινά πάντα με την εκκίνηση της εφαρμογής. Η τιμή `smart` σημαίνει ότι το session θα ξεκινά κατά την εκκίνηση της εφαρμογής μόνο εάν υπάρχει ήδη, ή τη στιγμή που θέλουμε να διαβάσουμε ή να γράψουμε σε αυτό. Τέλος, η τιμή `never` απενεργοποιεί την αυτόματη έναρξη του session. -Μπορείτε επίσης να ορίσετε όλες τις [οδηγίες συνόδου |https://www.php.net/manual/en/session.configuration.php] της PHP (σε μορφή camelCase) και επίσης το [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. Παράδειγμα: +Επιπλέον, μπορείτε να ορίσετε όλες τις PHP [session directives |https://www.php.net/manual/en/session.configuration.php] (σε μορφή camelCase) καθώς και το [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. Παράδειγμα: ```neon session: - # 'session.name' γραμμένο ως 'όνομα' + # γράψτε το 'session.name' ως 'name' name: MYID - # 'session.save_path' γραμμένο ως 'savePath' + # γράψτε το 'session.save_path' ως 'savePath' savePath: "%tempDir%/sessions" ``` -Cookie συνόδου .[#toc-session-cookie] -------------------------------------- +Session cookie +-------------- -Το cookie συνεδρίας αποστέλλεται με τις ίδιες παραμέτρους όπως και τα [άλλα cookies |#HTTP cookie], αλλά μπορείτε να τις αλλάξετε: +Το session cookie αποστέλλεται με τις ίδιες παραμέτρους όπως [άλλα cookie |#HTTP cookie], αλλά μπορείτε να τις αλλάξετε για αυτό: ```neon session: - # ποιοι κεντρικοί υπολογιστές επιτρέπεται να λάβουν το cookie + # domains που δέχονται cookies cookieDomain: 'example.com' # (string|domain) - # περιορισμοί κατά την πρόσβαση σε cross-origin αίτημα - cookieSamesite: None # (Strict|Lax|None) προεπιλογή σε Lax + # περιορισμός κατά την πρόσβαση από άλλο domain + cookieSamesite: None # (Strict|Lax|None) προεπιλογή είναι Lax ``` -Η επιλογή `cookieSamesite` επηρεάζει το αν το cookie αποστέλλεται με [αιτήσεις cross-origin |nette:glossary#SameSite cookie], γεγονός που παρέχει κάποια προστασία από επιθέσεις [Cross-Site Request Forgery |nette:glossary#cross-site-request-forgery-csrf]. +Το attribute `cookieSamesite` επηρεάζει εάν το cookie θα αποσταλεί κατά την [πρόσβαση από άλλο domain |nette:glossary#SameSite cookie], το οποίο παρέχει κάποια προστασία έναντι επιθέσεων [Cross-Site Request Forgery |nette:glossary#Cross-Site Request Forgery CSRF] (CSRF). + + +Υπηρεσίες DI +============ + +Αυτές οι υπηρεσίες προστίθενται στο DI container: + +| Όνομα | Τύπος | Περιγραφή +|----------------------------------------------------- +| `http.request` | [api:Nette\Http\Request] | [HTTP request| request] +| `http.response` | [api:Nette\Http\Response] | [HTTP response| response] +| `session.session` | [api:Nette\Http\Session] | [διαχείριση session| sessions] diff --git a/http/el/request.texy b/http/el/request.texy index 5b215f84c1..9171ba5cd2 100644 --- a/http/el/request.texy +++ b/http/el/request.texy @@ -2,84 +2,84 @@ *********** .[perex] -Η Nette ενθυλακώνει το αίτημα HTTP σε αντικείμενα με κατανοητό API παρέχοντας παράλληλα ένα φίλτρο εξυγίανσης. +Το Nette ενσωματώνει το αίτημα HTTP σε αντικείμενα με ένα κατανοητό API και ταυτόχρονα παρέχει ένα φίλτρο εξυγίανσης. -Ένα αίτημα HTTP είναι ένα αντικείμενο [api:Nette\Http\Request], το οποίο λαμβάνετε περνώντας το με τη χρήση [dependency injection |dependency-injection:passing-dependencies]. Σε παρουσιαστές απλά καλείτε το `$httpRequest = $this->getHttpRequest()`. +Το αίτημα HTTP αντιπροσωπεύεται από το αντικείμενο [api:Nette\Http\Request]. Εάν εργάζεστε με το Nette, αυτό το αντικείμενο δημιουργείται αυτόματα από το framework και μπορείτε να το λάβετε μέσω [έγχυσης εξάρτησης |dependency-injection:passing-dependencies]. Στους presenters, απλά καλέστε τη μέθοδο `$this->getHttpRequest()`. Εάν εργάζεστε εκτός του Nette Framework, μπορείτε να δημιουργήσετε το αντικείμενο χρησιμοποιώντας το [#RequestFactory]. -Αυτό που είναι σημαντικό είναι ότι η Nette κατά τη [δημιουργία |#RequestFactory] αυτού του αντικειμένου, καθαρίζει όλες τις παραμέτρους εισόδου GET, POST και COOKIE καθώς και τα URLs από χαρακτήρες ελέγχου και άκυρες ακολουθίες UTF-8. Έτσι μπορείτε να συνεχίσετε με ασφάλεια να εργάζεστε με τα δεδομένα. Τα καθαρισμένα δεδομένα χρησιμοποιούνται στη συνέχεια σε παρουσιαστές και φόρμες. +Ένα μεγάλο πλεονέκτημα του Nette είναι ότι κατά τη δημιουργία του αντικειμένου, καθαρίζει αυτόματα όλες τις παραμέτρους εισόδου GET, POST, COOKIE, καθώς και το URL από χαρακτήρες ελέγχου και μη έγκυρες ακολουθίες UTF-8. Στη συνέχεια, μπορείτε να εργαστείτε με ασφάλεια με αυτά τα δεδομένα. Τα καθαρισμένα δεδομένα χρησιμοποιούνται στη συνέχεια σε presenters και φόρμες. -→ [Εγκατάσταση και απαιτήσεις |@home#Installation] +→ [Εγκατάσταση και απαιτήσεις |@home#Εγκατάσταση] -Nette\Http\Request .[#toc-nette-http-request] -============================================= +Nette\Http\Request +================== -Αυτό το αντικείμενο είναι αμετάβλητο. Δεν έχει setters, έχει μόνο ένα λεγόμενο wither `withUrl()`, το οποίο δεν αλλάζει το αντικείμενο, αλλά επιστρέφει ένα νέο instance με τροποποιημένη τιμή. +Αυτό το αντικείμενο είναι αμετάβλητο (immutable). Δεν έχει setters, έχει μόνο έναν λεγόμενο wither `withUrl()`, ο οποίος δεν αλλάζει το αντικείμενο, αλλά επιστρέφει μια νέα παρουσία με την αλλαγμένη τιμή. withUrl(Nette\Http\UrlScript $url): Nette\Http\Request .[method] ---------------------------------------------------------------- -Επιστρέφει έναν κλώνο με διαφορετική διεύθυνση URL. +Επιστρέφει έναν κλώνο με διαφορετικό URL. getUrl(): Nette\Http\UrlScript .[method] ---------------------------------------- -Επιστρέφει τη διεύθυνση URL της αίτησης ως αντικείμενο [UrlScript |urls#UrlScript]. +Επιστρέφει το URL του αιτήματος ως αντικείμενο [UrlScript |urls#UrlScript]. ```php $url = $httpRequest->getUrl(); -echo $url; // https://nette.org/en/documentation?action=edit +echo $url; // https://doc.nette.org/cs/?action=edit echo $url->getHost(); // nette.org ``` -Οι φυλλομετρητές δεν στέλνουν ένα τμήμα στον διακομιστή, οπότε το `$url->getFragment()` θα επιστρέψει ένα κενό αλφαριθμητικό. +Προειδοποίηση: οι περιηγητές δεν στέλνουν το fragment στον διακομιστή, οπότε το `$url->getFragment()` θα επιστρέψει μια κενή συμβολοσειρά. -getQuery(string $key=null): string|array|null .[method] -------------------------------------------------------- -Επιστρέφει τις παραμέτρους της αίτησης GET: +getQuery(?string $key=null): string|array|null .[method] +-------------------------------------------------------- +Επιστρέφει τις παραμέτρους GET του αιτήματος. ```php -$all = $httpRequest->getQuery(); // συστοιχία όλων των παραμέτρων URL +$all = $httpRequest->getQuery(); // επιστρέφει έναν πίνακα όλων των παραμέτρων από το URL $id = $httpRequest->getQuery('id'); // επιστρέφει την παράμετρο GET 'id' (ή null) ``` -getPost(string $key=null): string|array|null .[method] ------------------------------------------------------- -Επιστρέφει παραμέτρους αίτησης POST: +getPost(?string $key=null): string|array|null .[method] +------------------------------------------------------- +Επιστρέφει τις παραμέτρους POST του αιτήματος. ```php -$all = $httpRequest->getPost(); // πίνακας όλων των παραμέτρων POST -$id = $httpRequest->getPost('id'); // επιστρέφει την παράμετρο POST 'id' (ή null) +$all = $httpRequest->getPost(); // επιστρέφει έναν πίνακα όλων των παραμέτρων από το POST +$id = $httpRequest->getPost('id'); // επιστρέφει την παράμετρο POST 'id' (ή null) ``` getFile(string|string[] $key): Nette\Http\FileUpload|array|null .[method] ------------------------------------------------------------------------- -Επιστρέφει το [upload |#Uploaded Files] ως αντικείμενο [api:Nette\Http\FileUpload]: +Επιστρέφει το [ανέβασμα |#Ανεβασμένα Αρχεία] ως αντικείμενο [api:Nette\Http\FileUpload]: ```php $file = $httpRequest->getFile('avatar'); -if ($file->hasFile()) { // Ανέβηκε κάποιο αρχείο; - $file->getUntrustedName(); // όνομα του αρχείου που έστειλε ο χρήστης - $file->getSanitizedName(); // το όνομα χωρίς επικίνδυνους χαρακτήρες +if ($file?->hasFile()) { // ανέβηκε κάποιο αρχείο; + $file->getUntrustedName(); // όνομα αρχείου που στάλθηκε από τον χρήστη + $file->getSanitizedName(); // όνομα χωρίς επικίνδυνους χαρακτήρες } ``` -Καθορίστε έναν πίνακα κλειδιών για την πρόσβαση στη δομή του υποδέντρου. +Για πρόσβαση σε μια ένθετη δομή, καθορίστε έναν πίνακα κλειδιών. ```php //<input type="file" name="my-form[details][avatar]" multiple> $file = $request->getFile(['my-form', 'details', 'avatar']); ``` -Επειδή δεν μπορείτε να εμπιστευτείτε δεδομένα από το εξωτερικό και επομένως δεν βασίζεστε στη μορφή της δομής, αυτή η μέθοδος είναι ασφαλέστερη από την `$request->getFiles()['my-form']['details']['avatar']`, η οποία μπορεί να αποτύχει. +Επειδή δεν μπορείτε να εμπιστευτείτε δεδομένα από έξω και επομένως ούτε να βασιστείτε στη μορφή της δομής των αρχείων, αυτή η μέθοδος είναι ασφαλέστερη από, για παράδειγμα, `$request->getFiles()['my-form']['details']['avatar']`, η οποία μπορεί να αποτύχει. getFiles(): array .[method] --------------------------- -Επιστρέφει δέντρο [αρχείων μεταφόρτωσης |#Uploaded Files] σε κανονικοποιημένη δομή, με κάθε φύλλο μια περίπτωση του [api:Nette\Http\FileUpload]: +Επιστρέφει ένα δέντρο [όλων των ανεβασμάτων |#Ανεβασμένα Αρχεία] σε μια κανονικοποιημένη δομή, της οποίας τα φύλλα είναι αντικείμενα [api:Nette\Http\FileUpload]: ```php $files = $httpRequest->getFiles(); @@ -88,7 +88,7 @@ $files = $httpRequest->getFiles(); getCookie(string $key): string|array|null .[method] --------------------------------------------------- -Επιστρέφει ένα cookie ή το `null` εάν δεν υπάρχει. +Επιστρέφει ένα cookie ή `null` εάν δεν υπάρχει. ```php $sessId = $httpRequest->getCookie('sess_id'); @@ -97,7 +97,7 @@ $sessId = $httpRequest->getCookie('sess_id'); getCookies(): array .[method] ----------------------------- -Επιστρέφει όλα τα cookies: +Επιστρέφει όλα τα cookies. ```php $cookies = $httpRequest->getCookies(); @@ -106,16 +106,16 @@ $cookies = $httpRequest->getCookies(); getMethod(): string .[method] ----------------------------- -Επιστρέφει τη μέθοδο HTTP με την οποία έγινε η αίτηση. +Επιστρέφει τη μέθοδο HTTP με την οποία έγινε το αίτημα. ```php -echo $httpRequest->getMethod(); // GET, POST, HEAD, PUT +$httpRequest->getMethod(); // GET, POST, HEAD, PUT ``` isMethod(string $method): bool .[method] ---------------------------------------- -Ελέγχει τη μέθοδο HTTP με την οποία έγινε η αίτηση. Η παράμετρος δεν λαμβάνει υπόψη την πεζότητα. +Ελέγχει τη μέθοδο HTTP με την οποία έγινε το αίτημα. Η παράμετρος δεν κάνει διάκριση πεζών-κεφαλαίων. ```php if ($httpRequest->isMethod('GET')) // ... @@ -124,7 +124,7 @@ if ($httpRequest->isMethod('GET')) // ... getHeader(string $header): ?string .[method] -------------------------------------------- -Επιστρέφει μια επικεφαλίδα HTTP ή το `null` εάν δεν υπάρχει. Η παράμετρος δεν λαμβάνει υπόψη την πεζότητα: +Επιστρέφει μια κεφαλίδα HTTP ή `null` εάν δεν υπάρχει. Η παράμετρος δεν κάνει διάκριση πεζών-κεφαλαίων. ```php $userAgent = $httpRequest->getHeader('User-Agent'); @@ -133,7 +133,7 @@ $userAgent = $httpRequest->getHeader('User-Agent'); getHeaders(): array .[method] ----------------------------- -Επιστρέφει όλες τις επικεφαλίδες HTTP ως συσχετιστικό πίνακα: +Επιστρέφει όλες τις κεφαλίδες HTTP ως συσχετιστικό πίνακα. ```php $headers = $httpRequest->getHeaders(); @@ -141,39 +141,34 @@ echo $headers['Content-Type']; ``` -getReferer(): ?Nette\Http\UrlImmutable .[method] ------------------------------------------------- -Από ποια διεύθυνση URL ήρθε ο χρήστης; Προσοχή, δεν είναι καθόλου αξιόπιστο. - - isSecured(): bool .[method] --------------------------- -Είναι η σύνδεση κρυπτογραφημένη (HTTPS); Ενδέχεται να χρειαστεί να [ρυθμίσετε έναν διακομιστή μεσολάβησης |configuration#HTTP proxy] για τη σωστή λειτουργία. +Είναι η σύνδεση κρυπτογραφημένη (HTTPS); Μπορεί να χρειαστεί να [ρυθμίσετε έναν proxy |configuration#HTTP proxy] για σωστή λειτουργία. isSameSite(): bool .[method] ---------------------------- -Το αίτημα προέρχεται από τον ίδιο (υπο)τομέα και ξεκινάει με κλικ σε έναν σύνδεσμο; Η Nette χρησιμοποιεί το cookie `_nss` (πρώην `nette-samesite`) για να το εντοπίσει. +Προέρχεται το αίτημα από το ίδιο (υπο)domain και ξεκίνησε κάνοντας κλικ σε έναν σύνδεσμο; Το Nette χρησιμοποιεί το cookie `_nss` (παλαιότερα `nette-samesite`) για ανίχνευση. isAjax(): bool .[method] ------------------------ -Πρόκειται για αίτηση AJAX; +Είναι αυτό ένα αίτημα AJAX; getRemoteAddress(): ?string .[method] ------------------------------------- -Επιστρέφει τη διεύθυνση IP του χρήστη. Ίσως χρειαστεί να [ρυθμίσετε έναν διακομιστή μεσολάβησης |configuration#HTTP proxy] για σωστή λειτουργία. +Επιστρέφει τη διεύθυνση IP του χρήστη. Μπορεί να χρειαστεί να [ρυθμίσετε έναν proxy |configuration#HTTP proxy] για σωστή λειτουργία. getRemoteHost(): ?string .[method deprecated] --------------------------------------------- -Επιστρέφει τη μετάφραση DNS της διεύθυνσης IP του χρήστη. Μπορεί να χρειαστεί να [ρυθμίσετε έναν μεσάζοντα |configuration#HTTP proxy] για τη σωστή λειτουργία. +Επιστρέφει τη μετάφραση DNS της διεύθυνσης IP του χρήστη. Μπορεί να χρειαστεί να [ρυθμίσετε έναν proxy |configuration#HTTP proxy] για σωστή λειτουργία. -getBasicCredentials(): ?string .[method] ----------------------------------------- -Επιστρέφει τα διαπιστευτήρια [ελέγχου ταυτότητας Basic HTTP |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication]. +getBasicCredentials(): ?array .[method] +--------------------------------------- +Επιστρέφει τα διαπιστευτήρια ελέγχου ταυτότητας για [Basic HTTP authentication |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication]. ```php [$user, $password] = $httpRequest->getBasicCredentials(); @@ -182,7 +177,7 @@ getBasicCredentials(): ?string .[method] getRawBody(): ?string .[method] ------------------------------- -Επιστρέφει το σώμα της αίτησης HTTP: +Επιστρέφει το σώμα του αιτήματος HTTP. ```php $body = $httpRequest->getRawBody(); @@ -191,108 +186,115 @@ $body = $httpRequest->getRawBody(); detectLanguage(array $langs): ?string .[method] ----------------------------------------------- -Ανιχνεύει τη γλώσσα. Ως παράμετρος `$lang`, περνάμε έναν πίνακα με τις γλώσσες που υποστηρίζει η εφαρμογή και επιστρέφει αυτή που προτιμά ο φυλλομετρητής. Δεν είναι μαγικό, η μέθοδος απλώς χρησιμοποιεί την επικεφαλίδα `Accept-Language`. Εάν δεν επιτευχθεί καμία αντιστοιχία, επιστρέφει `null`. +Ανιχνεύει τη γλώσσα. Ως παράμετρο `$lang`, περνάμε έναν πίνακα με τις γλώσσες που υποστηρίζει η εφαρμογή, και επιστρέφει αυτή που θα προτιμούσε να δει ο περιηγητής του επισκέπτη. Δεν είναι μαγεία, απλά χρησιμοποιεί την κεφαλίδα `Accept-Language`. Εάν δεν βρεθεί αντιστοιχία, επιστρέφει `null`. ```php -// Κεφαλίδα που αποστέλλεται από το πρόγραμμα περιήγησης: el-us;q=0.8,en;q=0.5,sl;q=0.3 +// ο περιηγητής στέλνει π.χ. Accept-Language: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3 -$langs = ['hu', 'pl', 'en']; // Γλώσσες που υποστηρίζονται στην εφαρμογή +$langs = ['hu', 'pl', 'en']; // γλώσσες που υποστηρίζονται από την εφαρμογή echo $httpRequest->detectLanguage($langs); // en ``` -RequestFactory .[#toc-requestfactory] -===================================== +RequestFactory +============== -Το αντικείμενο της τρέχουσας αίτησης HTTP δημιουργείται από το [api:Nette\Http\RequestFactory]. Εάν γράφετε μια εφαρμογή που δεν χρησιμοποιεί DI container, δημιουργείτε ένα αίτημα ως εξής: +Η κλάση [api:Nette\Http\RequestFactory] χρησιμοποιείται για τη δημιουργία μιας παρουσίας του `Nette\Http\Request`, η οποία αντιπροσωπεύει το τρέχον αίτημα HTTP. (Εάν εργάζεστε με το Nette, το αντικείμενο αιτήματος HTTP δημιουργείται αυτόματα από το framework.) ```php $factory = new Nette\Http\RequestFactory; $httpRequest = $factory->fromGlobals(); ``` -Το RequestFactory μπορεί να ρυθμιστεί πριν από την κλήση του `fromGlobals()`. Μπορούμε να απενεργοποιήσουμε όλη την εξυγίανση των παραμέτρων εισόδου από μη έγκυρες ακολουθίες UTF-8 χρησιμοποιώντας το `$factory->setBinary()`. Και επίσης να ρυθμίσουμε έναν διακομιστή μεσολάβησης, ο οποίος είναι σημαντικός για τη σωστή ανίχνευση της διεύθυνσης IP του χρήστη χρησιμοποιώντας το `$factory->setProxy(...)`. +Η μέθοδος `fromGlobals()` δημιουργεί το αντικείμενο αιτήματος με βάση τις τρέχουσες καθολικές μεταβλητές της PHP (`$_GET`, `$_POST`, `$_COOKIE`, `$_FILES` και `$_SERVER`). Κατά τη δημιουργία του αντικειμένου, καθαρίζει αυτόματα όλες τις παραμέτρους εισόδου GET, POST, COOKIE, καθώς και το URL από χαρακτήρες ελέγχου και μη έγκυρες ακολουθίες UTF-8, γεγονός που διασφαλίζει την ασφάλεια κατά την περαιτέρω εργασία με αυτά τα δεδομένα. + +Το RequestFactory μπορεί να διαμορφωθεί πριν από την κλήση του `fromGlobals()`: -Είναι δυνατό να καθαρίσουμε τις διευθύνσεις URL από χαρακτήρες που μπορεί να εισέλθουν σε αυτές λόγω κακώς υλοποιημένων συστημάτων σχολιασμού σε διάφορους άλλους ιστότοπους χρησιμοποιώντας φίλτρα: +- με τη μέθοδο `$factory->setBinary()`, απενεργοποιείτε τον αυτόματο καθαρισμό των παραμέτρων εισόδου από χαρακτήρες ελέγχου και μη έγκυρες ακολουθίες UTF-8. +- με τη μέθοδο `$factory->setProxy(...)`, καθορίζετε τη διεύθυνση IP του [proxy server |configuration#HTTP proxy], η οποία είναι απαραίτητη για τη σωστή ανίχνευση της διεύθυνσης IP του χρήστη. + +Το RequestFactory επιτρέπει τον ορισμό φίλτρων που μετασχηματίζουν αυτόματα τμήματα του URL του αιτήματος. Αυτά τα φίλτρα αφαιρούν ανεπιθύμητους χαρακτήρες από το URL, οι οποίοι μπορεί να έχουν εισαχθεί εκεί, για παράδειγμα, από λανθασμένη υλοποίηση συστημάτων σχολιασμού σε διάφορους ιστότοπους: ```php // αφαίρεση κενών από τη διαδρομή $requestFactory->urlFilters['path']['%20'] = ''; -// αφαίρεση τελείας, κόμματος ή δεξιάς παρένθεσης από το τέλος της διεύθυνσης URL +// αφαίρεση τελείας, κόμματος ή δεξιάς παρένθεσης από το τέλος του URI $requestFactory->urlFilters['url']['[.,)]$'] = ''; -// καθαρίζει τη διαδρομή από διπλές κάθετες (προεπιλεγμένο φίλτρο) +// καθαρισμός της διαδρομής από διπλές καθέτους (προεπιλεγμένο φίλτρο) $requestFactory->urlFilters['path']['/{2,}'] = '/'; ``` +Το πρώτο κλειδί `'path'` ή `'url'` καθορίζει σε ποιο τμήμα του URL θα εφαρμοστεί το φίλτρο. Το δεύτερο κλειδί είναι η κανονική έκφραση που πρέπει να βρεθεί, και η τιμή είναι η αντικατάσταση που θα χρησιμοποιηθεί αντί για το κείμενο που βρέθηκε. + -Uploaded Files .[#toc-uploaded-files] -===================================== +Ανεβασμένα Αρχεία +================= -Η μέθοδος `Nette\Http\Request::getFiles()` επιστρέφει ένα δέντρο από αρχεία upload σε μια κανονικοποιημένη δομή, με κάθε φύλλο μια περίπτωση του [api:Nette\Http\FileUpload]. Αυτά τα αντικείμενα ενθυλακώνουν τα δεδομένα που υποβάλλονται από τον `<input type=file>` στοιχείο της φόρμας. +Η μέθοδος `Nette\Http\Request::getFiles()` επιστρέφει έναν πίνακα όλων των ανεβασμάτων σε μια κανονικοποιημένη δομή, της οποίας τα φύλλα είναι αντικείμενα [api:Nette\Http\FileUpload]. Αυτά ενσωματώνουν τα δεδομένα που αποστέλλονται από το στοιχείο φόρμας `<input type=file>`. -Η δομή αντικατοπτρίζει την ονομασία των στοιχείων στην HTML. Στο απλούστερο παράδειγμα, αυτό μπορεί να είναι ένα μόνο ονομασμένο στοιχείο φόρμας που υποβάλλεται ως: +Η δομή αντικατοπτρίζει την ονομασία των στοιχείων στο HTML. Στην απλούστερη περίπτωση, μπορεί να είναι ένα μόνο ονομασμένο στοιχείο φόρμας που αποστέλλεται ως: ```latte <input type="file" name="avatar"> ``` -Σε αυτή την περίπτωση, το `$request->getFiles()` επιστρέφει πίνακα: +Σε αυτή την περίπτωση, το `$request->getFiles()` επιστρέφει έναν πίνακα: ```php [ - 'avatar' => /* FileUpload instance */ + 'avatar' => /* Παράδειγμα FileUpload */ ] ``` -Το αντικείμενο `FileUpload` δημιουργείται ακόμη και αν ο χρήστης δεν ανέβασε κανένα αρχείο ή αν το ανέβασμα απέτυχε. Η μέθοδος `hasFile()` επιστρέφει true αν έχει σταλεί ένα αρχείο: +Το αντικείμενο `FileUpload` δημιουργείται ακόμη και αν ο χρήστης δεν ανέβασε κανένα αρχείο ή το ανέβασμα απέτυχε. Η μέθοδος `hasFile()` επιστρέφει εάν ένα αρχείο ανέβηκε: ```php -$request->getFile('avatar')->hasFile(); +$request->getFile('avatar')?->hasFile(); ``` -Στην περίπτωση μιας εισόδου που χρησιμοποιεί σημειογραφία πίνακα για το όνομα: +Στην περίπτωση ενός ονόματος στοιχείου που χρησιμοποιεί σημειογραφία πίνακα: ```latte <input type="file" name="my-form[details][avatar]"> ``` -Το επιστρεφόμενο δέντρο καταλήγει να μοιάζει ως εξής: +το επιστρεφόμενο δέντρο μοιάζει με αυτό: ```php [ 'my-form' => [ 'details' => [ - 'avatar' => /* FileUpload instance */ + 'avatar' => /* Παράδειγμα FileUpload */ ], ], ] ``` -Μπορείτε επίσης να δημιουργήσετε πίνακες αρχείων: +Μπορείτε επίσης να δημιουργήσετε έναν πίνακα αρχείων: ```latte -<input type="file" name="my-form[details][avatars][] multiple"> +<input type="file" name="my-form[details][avatars][]" multiple> ``` -Σε μια τέτοια περίπτωση η δομή μοιάζει με: +Σε αυτή την περίπτωση, η δομή μοιάζει με αυτό: ```php [ 'my-form' => [ 'details' => [ 'avatars' => [ - 0 => /* FileUpload instance */, - 1 => /* FileUpload instance */, - 2 => /* FileUpload instance */, + 0 => /* Παράδειγμα FileUpload */, + 1 => /* Παράδειγμα FileUpload */, + 2 => /* Παράδειγμα FileUpload */, ], ], ], ] ``` -Ο καλύτερος τρόπος πρόσβασης στο δείκτη 1 ενός ένθετου πίνακα είναι ο εξής: +Η πρόσβαση στο ευρετήριο 1 του ένθετου πίνακα γίνεται καλύτερα ως εξής: ```php $file = $request->getFile(['my-form', 'details', 'avatars', 1]); @@ -301,7 +303,7 @@ if ($file instanceof FileUpload) { } ``` -Επειδή δεν μπορείτε να εμπιστευτείτε δεδομένα από το εξωτερικό και επομένως δεν βασίζεστε στη μορφή της δομής, αυτή η μέθοδος είναι ασφαλέστερη από την `$request->getFiles()['my-form']['details']['avatars'][1]`, η οποία μπορεί να αποτύχει. +Επειδή δεν μπορείτε να εμπιστευτείτε δεδομένα από έξω και επομένως ούτε να βασιστείτε στη μορφή της δομής των αρχείων, αυτή η μέθοδος είναι ασφαλέστερη από, για παράδειγμα, `$request->getFiles()['my-form']['details']['avatars'][1]`, η οποία μπορεί να αποτύχει. Επισκόπηση των μεθόδων `FileUpload` .{toc: FileUpload} @@ -310,22 +312,22 @@ if ($file instanceof FileUpload) { hasFile(): bool .[method] ------------------------- -Επιστρέφει `true` αν ο χρήστης έχει ανεβάσει ένα αρχείο. +Επιστρέφει `true` εάν ο χρήστης ανέβασε κάποιο αρχείο. isOk(): bool .[method] ---------------------- -Επιστρέφει `true` αν το αρχείο μεταφορτώθηκε με επιτυχία. +Επιστρέφει `true` εάν το αρχείο ανέβηκε με επιτυχία. getError(): int .[method] ------------------------- -Επιστρέφει τον κωδικό σφάλματος που σχετίζεται με το αρχείο που μεταφορτώθηκε. Είναι μία από τις σταθερές [UPLOAD_ERR_XXX |http://php.net/manual/en/features.file-upload.errors.php]. Εάν το αρχείο μεταφορτώθηκε με επιτυχία, επιστρέφει `UPLOAD_ERR_OK`. +Επιστρέφει τον κωδικό σφάλματος κατά το ανέβασμα του αρχείου. Είναι μία από τις σταθερές [UPLOAD_ERR_XXX|http://php.net/manual/en/features.file-upload.errors.php]. Εάν το ανέβασμα ήταν επιτυχές, επιστρέφει `UPLOAD_ERR_OK`. move(string $dest) .[method] ---------------------------- -Μετακινεί ένα αρχείο που έχει μεταφορτωθεί σε μια νέα θέση. Εάν το αρχείο προορισμού υπάρχει ήδη, θα αντικατασταθεί. +Μετακινεί το ανεβασμένο αρχείο σε νέα τοποθεσία. Εάν το αρχείο προορισμού υπάρχει ήδη, θα αντικατασταθεί. ```php $file->move('/path/to/files/name.ext'); @@ -334,12 +336,12 @@ $file->move('/path/to/files/name.ext'); getContents(): ?string .[method] -------------------------------- -Επιστρέφει τα περιεχόμενα του αρχείου που μεταφορτώθηκε. Εάν η μεταφόρτωση δεν ήταν επιτυχής, επιστρέφει `null`. +Επιστρέφει τα περιεχόμενα του ανεβασμένου αρχείου. Εάν το ανέβασμα δεν ήταν επιτυχές, επιστρέφει `null`. getContentType(): ?string .[method] ----------------------------------- -Ανιχνεύει τον τύπο περιεχομένου MIME του αρχείου που μεταφορτώθηκε με βάση την υπογραφή του. Εάν η μεταφόρτωση δεν ήταν επιτυχής ή η ανίχνευση απέτυχε, επιστρέφει `null`. +Ανιχνεύει τον τύπο περιεχομένου MIME του ανεβασμένου αρχείου με βάση την υπογραφή του. Εάν το ανέβασμα δεν ήταν επιτυχές ή η ανίχνευση απέτυχε, επιστρέφει `null`. .[caution] Απαιτεί την επέκταση PHP `fileinfo`. @@ -347,38 +349,49 @@ getContentType(): ?string .[method] getUntrustedName(): string .[method] ------------------------------------ -Επιστρέφει το αρχικό όνομα του αρχείου, όπως αυτό υποβλήθηκε από το πρόγραμμα περιήγησης. +Επιστρέφει το αρχικό όνομα του αρχείου, όπως στάλθηκε από τον περιηγητή. .[caution] -Μην εμπιστεύεστε την τιμή που επιστρέφει αυτή η μέθοδος. Ένας πελάτης θα μπορούσε να στείλει ένα κακόβουλο όνομα αρχείου με σκοπό να καταστρέψει ή να παραβιάσει την εφαρμογή σας. +Μην εμπιστεύεστε την τιμή που επιστρέφεται από αυτή τη μέθοδο. Ο πελάτης θα μπορούσε να έχει στείλει ένα κακόβουλο όνομα αρχείου με σκοπό να βλάψει ή να παραβιάσει την εφαρμογή σας. getSanitizedName(): string .[method] ------------------------------------ -Επιστρέφει το καθαρισμένο όνομα αρχείου. Περιέχει μόνο χαρακτήρες ASCII `[a-zA-Z0-9.-]`. Εάν το όνομα δεν περιέχει τέτοιους χαρακτήρες, επιστρέφει 'unknown'. Εάν το αρχείο είναι εικόνα JPEG, PNG, GIF ή WebP, επιστρέφει τη σωστή επέκταση αρχείου. +Επιστρέφει το εξυγιασμένο όνομα αρχείου. Περιέχει μόνο χαρακτήρες ASCII `[a-zA-Z0-9.-]`. Εάν το όνομα δεν περιέχει τέτοιους χαρακτήρες, επιστρέφει `'unknown'`. Εάν το αρχείο είναι εικόνα σε μορφή JPEG, PNG, GIF, WebP ή AVIF, επιστρέφει επίσης τη σωστή επέκταση. + +.[caution] +Απαιτεί την επέκταση PHP `fileinfo`. + + +getSuggestedExtension(): ?string .[method]{data-version:3.2.4} +-------------------------------------------------------------- +Επιστρέφει την κατάλληλη επέκταση αρχείου (χωρίς την τελεία) που αντιστοιχεί στον ανιχνευμένο τύπο MIME. + +.[caution] +Απαιτεί την επέκταση PHP `fileinfo`. getUntrustedFullPath(): string .[method] ---------------------------------------- -Επιστρέφει την αρχική πλήρη διαδρομή όπως υποβλήθηκε από το πρόγραμμα περιήγησης κατά τη μεταφόρτωση καταλόγου. Η πλήρης διαδρομή είναι διαθέσιμη μόνο στην PHP 8.1 και άνω. Σε προηγούμενες εκδόσεις, αυτή η μέθοδος επιστρέφει το μη αξιόπιστο όνομα αρχείου. +Επιστρέφει την αρχική διαδρομή του αρχείου, όπως στάλθηκε από τον περιηγητή κατά το ανέβασμα ενός φακέλου. Η πλήρης διαδρομή είναι διαθέσιμη μόνο σε PHP 8.1 και νεότερες εκδόσεις. Σε προηγούμενες εκδόσεις, αυτή η μέθοδος επιστρέφει το αρχικό όνομα αρχείου. .[caution] -Μην εμπιστεύεστε την τιμή που επιστρέφει αυτή η μέθοδος. Ένας πελάτης θα μπορούσε να στείλει ένα κακόβουλο όνομα αρχείου με σκοπό να καταστρέψει ή να παραβιάσει την εφαρμογή σας. +Μην εμπιστεύεστε την τιμή που επιστρέφεται από αυτή τη μέθοδο. Ο πελάτης θα μπορούσε να έχει στείλει ένα κακόβουλο όνομα αρχείου με σκοπό να βλάψει ή να παραβιάσει την εφαρμογή σας. getSize(): int .[method] ------------------------ -Επιστρέφει το μέγεθος του ανεβασμένου αρχείου. Εάν η μεταφόρτωση δεν ήταν επιτυχής, επιστρέφει `0`. +Επιστρέφει το μέγεθος του ανεβασμένου αρχείου. Εάν το ανέβασμα δεν ήταν επιτυχές, επιστρέφει `0`. getTemporaryFile(): string .[method] ------------------------------------ -Επιστρέφει τη διαδρομή της προσωρινής τοποθεσίας του αρχείου που μεταφορτώθηκε. Εάν η μεταφόρτωση δεν ήταν επιτυχής, επιστρέφει `''`. +Επιστρέφει τη διαδρομή προς την προσωρινή τοποθεσία του ανεβασμένου αρχείου. Εάν το ανέβασμα δεν ήταν επιτυχές, επιστρέφει `''`. isImage(): bool .[method] ------------------------- -Επιστρέφει `true` εάν το αρχείο που μεταφορτώθηκε είναι εικόνα JPEG, PNG, GIF ή WebP. Η ανίχνευση βασίζεται στην υπογραφή της. Η ακεραιότητα ολόκληρου του αρχείου δεν ελέγχεται. Μπορείτε να διαπιστώσετε αν μια εικόνα δεν είναι κατεστραμμένη, για παράδειγμα, προσπαθώντας να [τη φορτώσετε |#toImage]. +Επιστρέφει `true` εάν το ανεβασμένο αρχείο είναι εικόνα σε μορφή JPEG, PNG, GIF, WebP ή AVIF. Η ανίχνευση βασίζεται στην υπογραφή του και δεν επαληθεύει την ακεραιότητα ολόκληρου του αρχείου. Το αν μια εικόνα είναι κατεστραμμένη μπορεί να προσδιοριστεί, για παράδειγμα, προσπαθώντας να την [φορτώσετε |#toImage]. .[caution] Απαιτεί την επέκταση PHP `fileinfo`. @@ -386,9 +399,9 @@ isImage(): bool .[method] getImageSize(): ?array .[method] -------------------------------- -Επιστρέφει ένα ζεύγος `[width, height]` με τις διαστάσεις της μεταφορτωμένης εικόνας. Εάν η μεταφόρτωση δεν ήταν επιτυχής ή δεν είναι έγκυρη εικόνα, επιστρέφει `null`. +Επιστρέφει ένα ζεύγος `[πλάτος, ύψος]` με τις διαστάσεις της ανεβασμένης εικόνας. Εάν το ανέβασμα δεν ήταν επιτυχές ή δεν είναι έγκυρη εικόνα, επιστρέφει `null`. toImage(): Nette\Utils\Image .[method] -------------------------------------- -Φορτώνει μια εικόνα ως αντικείμενο [Image |utils:images]. Εάν η φόρτωση δεν ήταν επιτυχής ή δεν είναι έγκυρη εικόνα, πετάει μια εξαίρεση `Nette\Utils\ImageException`. +Φορτώνει την εικόνα ως αντικείμενο [Image|utils:images]. Εάν το ανέβασμα δεν ήταν επιτυχές ή δεν είναι έγκυρη εικόνα, δημιουργεί μια εξαίρεση `Nette\Utils\ImageException`. diff --git a/http/el/response.texy b/http/el/response.texy index dfd85f5a6c..0d3eea73ab 100644 --- a/http/el/response.texy +++ b/http/el/response.texy @@ -2,22 +2,22 @@ ************* .[perex] -Η Nette ενθυλακώνει την απόκριση HTTP σε αντικείμενα με κατανοητό API παρέχοντας παράλληλα ένα φίλτρο εξυγίανσης. +Το Nette ενσωματώνει την απόκριση HTTP σε αντικείμενα με ένα κατανοητό API. -Μια απόκριση HTTP είναι ένα αντικείμενο [api:Nette\Http\Response], το οποίο λαμβάνετε περνώντας το με τη χρήση [dependency injection |dependency-injection:passing-dependencies]. Σε παρουσιαστές απλά καλείτε το `$httpResponse = $this->getHttpResponse()`. +Η απόκριση HTTP αντιπροσωπεύεται από το αντικείμενο [api:Nette\Http\Response]. Εάν εργάζεστε με το Nette, αυτό το αντικείμενο δημιουργείται αυτόματα από το framework και μπορείτε να το λάβετε μέσω [έγχυσης εξάρτησης |dependency-injection:passing-dependencies]. Στους presenters, απλά καλέστε τη μέθοδο `$this->getHttpResponse()`. -→ [Εγκατάσταση και απαιτήσεις |@home#Installation] +→ [Εγκατάσταση και απαιτήσεις |@home#Εγκατάσταση] -Nette\Http\Response .[#toc-nette-http-response] -=============================================== +Nette\Http\Response +=================== -Σε αντίθεση με το [Nette\Http\Request |request], αυτό το αντικείμενο είναι μεταβλητό, οπότε μπορείτε να χρησιμοποιήσετε ρυθμιστές για να αλλάξετε την κατάσταση, δηλαδή να στείλετε κεφαλίδες. Να θυμάστε ότι όλοι οι ρυθμιστές **πρέπει να κληθούν πριν από την αποστολή της πραγματικής εξόδου.** Η μέθοδος `isSent()` δηλώνει αν η έξοδος έχει σταλεί. Εάν επιστρέφει `true`, κάθε προσπάθεια αποστολής κεφαλίδας προκαλεί μια εξαίρεση `Nette\InvalidStateException`. +Το αντικείμενο, σε αντίθεση με το [Nette\Http\Request|request], είναι μεταβλητό (mutable), οπότε μπορείτε να αλλάξετε την κατάσταση χρησιμοποιώντας setters, π.χ. να στείλετε κεφαλίδες. Θυμηθείτε ότι όλοι οι setters πρέπει να κληθούν **πριν από την αποστολή οποιασδήποτε εξόδου.** Η μέθοδος `isSent()` υποδεικνύει εάν η έξοδος έχει ήδη σταλεί. Εάν επιστρέφει `true`, κάθε προσπάθεια αποστολής κεφαλίδας θα προκαλέσει μια εξαίρεση `Nette\InvalidStateException`. -setCode(int $code, string $reason=null) .[method] -------------------------------------------------- -Αλλάζει έναν [κωδικό απόκρισης |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10] κατάστασης. Για καλύτερη αναγνωσιμότητα του πηγαίου κώδικα συνιστάται η χρήση [προκαθορισμένων σταθερών |api:Nette\Http\IResponse] αντί πραγματικών αριθμών. +setCode(int $code, ?string $reason=null) .[method] +-------------------------------------------------- +Αλλάζει τον [κωδικό κατάστασης της απόκρισης |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10]. Για καλύτερη κατανόηση του πηγαίου κώδικα, συνιστούμε τη χρήση [προκαθορισμένων σταθερών |api:Nette\Http\IResponse] αντί για αριθμούς για τον κωδικό. ```php $httpResponse->setCode(Nette\Http\Response::S404_NotFound); @@ -26,17 +26,17 @@ $httpResponse->setCode(Nette\Http\Response::S404_NotFound); getCode(): int .[method] ------------------------ -Επιστρέφει τον κωδικό κατάστασης της απάντησης. +Επιστρέφει τον κωδικό κατάστασης της απόκρισης. isSent(): bool .[method] ------------------------ -Επιστρέφει εάν οι επικεφαλίδες έχουν ήδη σταλεί από τον διακομιστή στο πρόγραμμα περιήγησης, οπότε δεν είναι πλέον δυνατή η αποστολή επικεφαλίδων ή η αλλαγή του κωδικού κατάστασης. +Επιστρέφει εάν οι κεφαλίδες έχουν ήδη σταλεί από τον διακομιστή στον περιηγητή, και επομένως δεν είναι πλέον δυνατό να σταλούν κεφαλίδες ή να αλλάξει ο κωδικός κατάστασης. setHeader(string $name, string $value) .[method] ------------------------------------------------ -Αποστέλλει μια επικεφαλίδα HTTP και **αντικαθιστά** την προηγουμένως αποσταλείσα επικεφαλίδα με το ίδιο όνομα. +Στέλνει μια κεφαλίδα HTTP και **αντικαθιστά** μια προηγουμένως σταλμένη κεφαλίδα με το ίδιο όνομα. ```php $httpResponse->setHeader('Pragma', 'no-cache'); @@ -45,7 +45,7 @@ $httpResponse->setHeader('Pragma', 'no-cache'); addHeader(string $name, string $value) .[method] ------------------------------------------------ -Αποστέλλει μια επικεφαλίδα HTTP και **δεν αντικαθιστά** την προηγουμένως αποσταλείσα επικεφαλίδα με το ίδιο όνομα. +Στέλνει μια κεφαλίδα HTTP και **δεν αντικαθιστά** μια προηγουμένως σταλμένη κεφαλίδα με το ίδιο όνομα. ```php $httpResponse->addHeader('Accept', 'application/json'); @@ -55,12 +55,12 @@ $httpResponse->addHeader('Accept', 'application/xml'); deleteHeader(string $name) .[method] ------------------------------------ -Διαγράφει μια κεφαλίδα HTTP που έχει αποσταλεί προηγουμένως. +Διαγράφει μια προηγουμένως σταλμένη κεφαλίδα HTTP. getHeader(string $header): ?string .[method] -------------------------------------------- -Επιστρέφει την αποσταλείσα επικεφαλίδα HTTP ή `null` εάν δεν υπάρχει. Η παράμετρος δεν λαμβάνει υπόψη την πεζότητα. +Επιστρέφει μια σταλμένη κεφαλίδα HTTP ή `null` εάν δεν υπάρχει. Η παράμετρος δεν κάνει διάκριση πεζών-κεφαλαίων. ```php $pragma = $httpResponse->getHeader('Pragma'); @@ -69,7 +69,7 @@ $pragma = $httpResponse->getHeader('Pragma'); getHeaders(): array .[method] ----------------------------- -Επιστρέφει όλες τις αποσταλμένες επικεφαλίδες HTTP ως συσχετιστικό πίνακα. +Επιστρέφει όλες τις σταλμένες κεφαλίδες HTTP ως συσχετιστικό πίνακα. ```php $headers = $httpResponse->getHeaders(); @@ -77,9 +77,9 @@ echo $headers['Pragma']; ``` -setContentType(string $type, string $charset=null) .[method] ------------------------------------------------------------- -Αποστέλλει την επικεφαλίδα `Content-Type`. +setContentType(string $type, ?string $charset=null) .[method] +------------------------------------------------------------- +Αλλάζει την κεφαλίδα `Content-Type`. ```php $httpResponse->setContentType('text/plain', 'UTF-8'); @@ -88,7 +88,7 @@ $httpResponse->setContentType('text/plain', 'UTF-8'); redirect(string $url, int $code=self::S302_Found): void .[method] ----------------------------------------------------------------- -Ανακατευθύνει σε άλλη διεύθυνση URL. Μην ξεχάσετε να τερματίσετε το σενάριο στη συνέχεια. +Ανακατευθύνει σε άλλο URL. Μην ξεχάσετε να τερματίσετε το σενάριο μετά. ```php $httpResponse->redirect('http://example.com'); @@ -98,52 +98,52 @@ exit; setExpiration(?string $time) .[method] -------------------------------------- -Ορίζει τη λήξη του εγγράφου HTTP χρησιμοποιώντας τις επικεφαλίδες `Cache-Control` και `Expires`. Η παράμετρος είναι είτε ένα χρονικό διάστημα (ως κείμενο) είτε το `null`, το οποίο απενεργοποιεί την προσωρινή αποθήκευση. +Ορίζει τη λήξη του εγγράφου HTTP χρησιμοποιώντας τις κεφαλίδες `Cache-Control` και `Expires`. Η παράμετρος είναι είτε ένα χρονικό διάστημα (ως κείμενο) είτε `null`, το οποίο απενεργοποιεί την προσωρινή αποθήκευση. ```php -// η προσωρινή μνήμη cache του προγράμματος περιήγησης λήγει σε μία ώρα +// η cache στον περιηγητή θα λήξει σε μία ώρα $httpResponse->setExpiration('1 hour'); ``` sendAsFile(string $fileName) .[method] -------------------------------------- -Η απάντηση θα πρέπει να μεταφορτωθεί με το παράθυρο διαλόγου *Save as* με το καθορισμένο όνομα. Δεν στέλνει κανένα αρχείο στην έξοδο. +Η απόκριση θα ληφθεί μέσω του διαλόγου *Αποθήκευση ως* με το καθορισμένο όνομα. Δεν στέλνει το ίδιο το αρχείο. ```php $httpResponse->sendAsFile('invoice.pdf'); ``` -setCookie(string $name, string $value, $time, string $path=null, string $domain=null, bool $secure=null, bool $httpOnly=null, string $sameSite=null) .[method] --------------------------------------------------------------------------------------------------------------------------------------------------------------- -Αποστέλλει ένα cookie. Προεπιλεγμένες τιμές παραμέτρων: +setCookie(string $name, string $value, $time, ?string $path=null, ?string $domain=null, ?bool $secure=null, ?bool $httpOnly=null, ?string $sameSite=null) .[method] +------------------------------------------------------------------------------------------------------------------------------------------------------------------- +Στέλνει ένα cookie. Οι προεπιλεγμένες τιμές των παραμέτρων είναι: -| `$path` | `'/'` | με εμβέλεια σε όλες τις διαδρομές στον (υπο)τομέα *(διαμορφώσιμο)* -| `$domain` | `null` | με πεδίο εφαρμογής τον τρέχοντα (υπο)τομέα, αλλά όχι τους υποτομείς του *(διαμορφώσιμο)* -| `$secure` | `true` | αν ο ιστότοπος λειτουργεί σε HTTPS, διαφορετικά `false` *(διαμορφώσιμο)* -| `$httpOnly` | `true` | το cookie δεν είναι προσβάσιμο σε JavaScript -| `$sameSite` | `'Lax'` | το cookie δεν χρειάζεται να αποστέλλεται όταν η [πρόσβαση γίνεται από άλλη προέλευση |nette:glossary#SameSite cookie] +| `$path` | `'/'` | το cookie έχει εμβέλεια σε όλες τις διαδρομές στο (υπο)domain *(διαμορφώσιμο)* +| `$domain` | `null` | που σημαίνει με εμβέλεια στο τρέχον (υπο)domain, αλλά όχι στα υποdomains του *(διαμορφώσιμο)* +| `$secure` | `true` | εάν ο ιστότοπος εκτελείται σε HTTPS, διαφορετικά `false` *(διαμορφώσιμο)* +| `$httpOnly` | `true` | το cookie δεν είναι προσβάσιμο από JavaScript +| `$sameSite` | `'Lax'` | το cookie μπορεί να μην αποσταλεί κατά την [πρόσβαση από άλλο domain |nette:glossary#SameSite cookie] -Μπορείτε να αλλάξετε τις προεπιλεγμένες τιμές των παραμέτρων `$path`, `$domain` και `$secure` στο [configuration |configuration#HTTP cookie]. +Μπορείτε να αλλάξετε τις προεπιλεγμένες τιμές των παραμέτρων `$path`, `$domain` και `$secure` στην [διαμόρφωση |configuration#HTTP cookie]. Ο χρόνος μπορεί να καθοριστεί ως αριθμός δευτερολέπτων ή ως συμβολοσειρά: ```php -$httpResponse->setCookie('lang', 'en', '100 days'); +$httpResponse->setCookie('lang', 'el', '100 days'); ``` -Η επιλογή `$domain` καθορίζει ποιοι τομείς (προέλευση) μπορούν να δέχονται cookies. Αν δεν καθοριστεί, το cookie γίνεται αποδεκτό από τον ίδιο (υπο)τομέα που έχει οριστεί από αυτό, εξαιρουμένων των υποτομέων τους. Εάν καθοριστεί η επιλογή `$domain`, τότε συμπεριλαμβάνονται και τα subdomains. Επομένως, ο προσδιορισμός του `$domain` είναι λιγότερο περιοριστικός από την παράλειψη. Για παράδειγμα, εάν `$domain = 'nette.org'`, το cookie είναι επίσης διαθέσιμο σε όλα τα subdomains όπως το `doc.nette.org`. +Η παράμετρος `$domain` καθορίζει ποια domains μπορούν να δέχονται cookies. Εάν δεν καθοριστεί, το cookie γίνεται αποδεκτό από το ίδιο (υπο)domain που το όρισε, αλλά όχι από τα υποdomains του. Εάν το `$domain` καθοριστεί, περιλαμβάνονται και τα υποdomains. Επομένως, ο καθορισμός του `$domain` είναι λιγότερο περιοριστικός από την παράλειψή του. Για παράδειγμα, με `$domain = 'nette.org'`, τα cookies είναι επίσης διαθέσιμα σε όλα τα υποdomains όπως το `doc.nette.org`. -Μπορείτε να χρησιμοποιήσετε τις σταθερές `Response::SameSiteLax`, `SameSiteStrict` και `SameSiteNone` για την τιμή `$sameSite`. +Για την τιμή `$sameSite`, μπορείτε να χρησιμοποιήσετε τις σταθερές `Response::SameSiteLax`, `SameSiteStrict` και `SameSiteNone`. -deleteCookie(string $name, string $path=null, string $domain=null, bool $secure=null): void .[method] ------------------------------------------------------------------------------------------------------ +deleteCookie(string $name, ?string $path=null, ?string $domain=null, ?bool $secure=null): void .[method] +-------------------------------------------------------------------------------------------------------- Διαγράφει ένα cookie. Οι προεπιλεγμένες τιμές των παραμέτρων είναι: - `$path` με εμβέλεια σε όλους τους καταλόγους (`'/'`) -- `$domain` με πεδίο εφαρμογής τον τρέχοντα (υπο)τομέα, αλλά όχι τους υποτομείς του -- Το `$secure` επηρεάζεται από τις ρυθμίσεις στο [configuration#HTTP cookie |configuration#HTTP cookie] +- `$domain` με εμβέλεια στο τρέχον (υπο)domain, αλλά όχι στα υποdomains του +- `$secure` καθορίζεται από τις ρυθμίσεις στην [διαμόρφωση |configuration#HTTP cookie] ```php $httpResponse->deleteCookie('lang'); diff --git a/http/el/sessions.texy b/http/el/sessions.texy index c3d21ff3b6..d5d76fc14e 100644 --- a/http/el/sessions.texy +++ b/http/el/sessions.texy @@ -1,71 +1,71 @@ -Συνεδρίες -********* +Sessions +******** <div class=perex> -Το HTTP είναι ένα πρωτόκολλο χωρίς κατάσταση, αλλά σχεδόν κάθε εφαρμογή χρειάζεται να διατηρεί κατάσταση μεταξύ των αιτήσεων, π.χ. το περιεχόμενο ενός καλαθιού αγορών. Γι' αυτό το σκοπό χρησιμοποιείται η σύνοδος. Ας δούμε +Το HTTP είναι ένα πρωτόκολλο χωρίς κατάσταση, αλλά σχεδόν κάθε εφαρμογή χρειάζεται να διατηρεί την κατάσταση μεταξύ των αιτημάτων, για παράδειγμα, το περιεχόμενο ενός καλαθιού αγορών. Αυτός είναι ακριβώς ο σκοπός των sessions. Θα δείξουμε: -- πώς να χρησιμοποιήσετε συνεδρίες -- πώς να αποφύγετε συγκρούσεις ονομάτων +- πώς να χρησιμοποιείτε τα sessions +- πώς να αποφύγετε τις συγκρούσεις ονομάτων - πώς να ορίσετε τη λήξη </div> -Όταν χρησιμοποιείτε συνεδρίες, κάθε χρήστης λαμβάνει ένα μοναδικό αναγνωριστικό που ονομάζεται αναγνωριστικό συνεδρίας, το οποίο περνάει σε ένα cookie. Αυτό χρησιμεύει ως κλειδί για τα δεδομένα της συνεδρίας. Σε αντίθεση με τα cookies, τα οποία αποθηκεύονται στην πλευρά του προγράμματος περιήγησης, τα δεδομένα συνόδου αποθηκεύονται στην πλευρά του διακομιστή. +Όταν χρησιμοποιείτε sessions, κάθε χρήστης λαμβάνει ένα μοναδικό αναγνωριστικό που ονομάζεται session ID, το οποίο μεταδίδεται σε ένα cookie. Αυτό χρησιμεύει ως κλειδί για τα δεδομένα του session. Σε αντίθεση με τα cookies, τα οποία αποθηκεύονται στην πλευρά του προγράμματος περιήγησης, τα δεδομένα του session αποθηκεύονται στην πλευρά του διακομιστή. -Διαμορφώνουμε τη σύνοδο στη [διαμόρφωση |configuration#session], η επιλογή του χρόνου λήξης είναι σημαντική. +Ρυθμίζουμε το session στην [διαμόρφωση |configuration#Session], η επιλογή του χρόνου λήξης είναι ιδιαίτερα σημαντική. -Η διαχείριση της συνεδρίας γίνεται από το αντικείμενο [api:Nette\Http\Session], το οποίο λαμβάνετε περνώντας το με τη χρήση [dependency injection |dependency-injection:passing-dependencies]. Στους παρουσιαστές απλά καλούμε το `$session = $this->getSession()`. +Η διαχείριση του session γίνεται από το αντικείμενο [api:Nette\Http\Session], στο οποίο μπορείτε να αποκτήσετε πρόσβαση ζητώντας το μέσω [έγχυσης εξάρτησης |dependency-injection:passing-dependencies]. Στους presenters, απλά καλέστε `$session = $this->getSession()`. -→ [Εγκατάσταση και απαιτήσεις |@home#Installation] +→ [Εγκατάσταση και απαιτήσεις |@home#Εγκατάσταση] -Έναρξη συνεδρίας .[#toc-starting-session] -========================================= +Έναρξη Session +============== -Από προεπιλογή, η Nette θα ξεκινήσει αυτόματα μια συνεδρία τη στιγμή που θα αρχίσουμε να διαβάζουμε από αυτήν ή να γράφουμε δεδομένα σε αυτήν. Για να ξεκινήσετε χειροκίνητα μια σύνοδο, χρησιμοποιήστε το `$session->start()`. +Από προεπιλογή, το Nette ξεκινά αυτόματα το session τη στιγμή που αρχίζουμε να διαβάζουμε ή να γράφουμε δεδομένα σε αυτό. Μπορείτε να ξεκινήσετε το session χειροκίνητα χρησιμοποιώντας το `$session->start()`. -Η PHP στέλνει επικεφαλίδες HTTP που επηρεάζουν την προσωρινή αποθήκευση κατά την έναρξη της συνεδρίας, βλέπε [php:session_cache_limiter], και ενδεχομένως ένα cookie με το αναγνωριστικό συνεδρίας. Επομένως, είναι πάντα απαραίτητο να ξεκινάτε τη σύνοδο πριν στείλετε οποιαδήποτε έξοδο στο πρόγραμμα περιήγησης, διαφορετικά θα εκπέμπεται μια εξαίρεση. Επομένως, αν γνωρίζετε ότι μια σύνοδος θα χρησιμοποιηθεί κατά την απόδοση της σελίδας, ξεκινήστε την χειροκίνητα πριν, για παράδειγμα στον παρουσιαστή. +Όταν ξεκινά ένα session, η PHP στέλνει κεφαλίδες HTTP που επηρεάζουν την προσωρινή αποθήκευση, δείτε [php:session_cache_limiter], και ενδεχομένως ένα cookie με το session ID. Επομένως, είναι απαραίτητο να ξεκινάτε πάντα το session πριν στείλετε οποιαδήποτε έξοδο στο πρόγραμμα περιήγησης, διαφορετικά θα προκληθεί εξαίρεση. Εάν γνωρίζετε ότι το session θα χρησιμοποιηθεί κατά την απόδοση της σελίδας, ξεκινήστε το χειροκίνητα εκ των προτέρων, για παράδειγμα, στον presenter. -Στη λειτουργία προγραμματιστή, το Tracy εκκινεί τη σύνοδο επειδή τη χρησιμοποιεί για να εμφανίζει τις μπάρες ανακατεύθυνσης και αιτήσεων AJAX στη γραμμή Tracy. +Στη λειτουργία ανάπτυξης, το Tracy ξεκινά το session επειδή το χρησιμοποιεί για την εμφάνιση των γραμμών με ανακατευθύνσεις και αιτήματα AJAX στο Tracy Bar. -Τμήμα .[#toc-section] -===================== +Ενότητες +======== -Στην καθαρή PHP, η αποθήκευση δεδομένων συνόδου υλοποιείται ως πίνακας προσβάσιμος μέσω μιας παγκόσμιας μεταβλητής `$_SESSION`. Το πρόβλημα είναι ότι οι εφαρμογές συνήθως αποτελούνται από έναν αριθμό ανεξάρτητων τμημάτων, και αν όλα έχουν μόνο έναν ίδιο πίνακα διαθέσιμο, αργά ή γρήγορα θα συμβεί σύγκρουση ονομάτων. +Στην καθαρή PHP, ο χώρος αποθήκευσης δεδομένων του session υλοποιείται ως ένας πίνακας προσβάσιμος μέσω της καθολικής μεταβλητής `$_SESSION`. Το πρόβλημα είναι ότι οι εφαρμογές συνήθως αποτελούνται από έναν αριθμό ανεξάρτητων τμημάτων, και εάν όλα έχουν πρόσβαση μόνο σε έναν πίνακα, αργά ή γρήγορα θα προκύψει σύγκρουση ονομάτων. -Το Nette Framework λύνει το πρόβλημα χωρίζοντας ολόκληρο το χώρο σε τμήματα (αντικείμενα [api:Nette\Http\SessionSection]). Κάθε μονάδα χρησιμοποιεί τότε το δικό της τμήμα με ένα μοναδικό όνομα και δεν μπορούν να προκύψουν συγκρούσεις. +Το Nette Framework λύνει αυτό το πρόβλημα διαιρώντας ολόκληρο τον χώρο σε ενότητες (αντικείμενα [api:Nette\Http\SessionSection]). Κάθε μονάδα χρησιμοποιεί τότε τη δική της ενότητα με ένα μοναδικό όνομα, και δεν μπορεί να προκύψει σύγκρουση. -Παίρνουμε το τμήμα από τον διαχειριστή συνόδου: +Λαμβάνουμε μια ενότητα από το session: ```php -$section = $session->getSection('unique name'); +$section = $session->getSection('μοναδικό όνομα'); ``` -Στον παρουσιαστή αρκεί να καλέσουμε το `getSession()` με την παράμετρο: +Στον presenter, απλά χρησιμοποιήστε το `getSession()` με μια παράμετρο: ```php -// $this is Presenter -$section = $this->getSession('unique name'); +// $this είναι ένας Presenter +$section = $this->getSession('μοναδικό όνομα'); ``` -Η ύπαρξη του τμήματος μπορεί να ελεγχθεί με τη μέθοδο `$session->hasSection('unique name')`. +Η ύπαρξη μιας ενότητας μπορεί να ελεγχθεί με τη μέθοδο `$session->hasSection('μοναδικό όνομα')`. -Το ίδιο το τμήμα είναι πολύ εύκολο να δουλέψει κανείς με τις μεθόδους `set()`, `get()` και `remove()`: +Η εργασία με την ίδια την ενότητα είναι τότε πολύ εύκολη χρησιμοποιώντας τις μεθόδους `set()`, `get()` και `remove()`: ```php -// γράψιμο μεταβλητών +// εγγραφή μεταβλητής $section->set('userName', 'franta'); -// ανάγνωση μιας μεταβλητής, επιστρέφει null αν δεν υπάρχει +// ανάγνωση μεταβλητής, επιστρέφει null εάν δεν υπάρχει echo $section->get('userName'); -// αφαίρεση μεταβλητής +// διαγραφή μεταβλητής $section->remove('userName'); ``` -Είναι δυνατόν να χρησιμοποιήσετε τον κύκλο `foreach` για να λάβετε όλες τις μεταβλητές από το τμήμα: +Για να λάβετε όλες τις μεταβλητές από την ενότητα, μπορείτε να χρησιμοποιήσετε έναν βρόχο `foreach`: ```php foreach ($section as $key => $val) { @@ -74,17 +74,17 @@ foreach ($section as $key => $val) { ``` -Πώς να ορίσετε τη λήξη .[#toc-how-to-set-expiration] ----------------------------------------------------- +Ρύθμιση Λήξης +------------- -Η λήξη μπορεί να οριστεί για μεμονωμένα τμήματα ή ακόμη και για μεμονωμένες μεταβλητές. Μπορούμε να αφήσουμε τη σύνδεση του χρήστη να λήξει σε 20 λεπτά, αλλά να θυμόμαστε ακόμα τα περιεχόμενα ενός καλαθιού αγορών. +Είναι δυνατό να οριστεί η λήξη για μεμονωμένες ενότητες ή ακόμη και για μεμονωμένες μεταβλητές. Μπορούμε να αφήσουμε τη σύνδεση του χρήστη να λήξει σε 20 λεπτά, αλλά ταυτόχρονα να θυμόμαστε το περιεχόμενο του καλαθιού αγορών. ```php -// η ενότητα θα λήξει μετά από 20 λεπτά +// η ενότητα λήγει μετά από 20 λεπτά $section->setExpiration('20 minutes'); ``` -Η τρίτη παράμετρος της μεθόδου `set()` χρησιμοποιείται για τον ορισμό της λήξης μεμονωμένων μεταβλητών: +Για να ορίσετε τη λήξη μεμονωμένων μεταβλητών, χρησιμοποιήστε την τρίτη παράμετρο της μεθόδου `set()`: ```php // η μεταβλητή 'flash' λήγει μετά από 30 δευτερόλεπτα @@ -92,119 +92,120 @@ $section->set('flash', $message, '30 seconds'); ``` .[note] -Θυμηθείτε ότι ο χρόνος λήξης ολόκληρης της συνεδρίας (βλ. [διαμόρφωση συνεδρίας |configuration#session]) πρέπει να είναι ίσος ή μεγαλύτερος από τον χρόνο που έχει οριστεί για μεμονωμένα τμήματα ή μεταβλητές. +Μην ξεχνάτε ότι ο χρόνος λήξης ολόκληρου του session (δείτε [διαμόρφωση session |configuration#Session]) πρέπει να είναι ίσος ή μεγαλύτερος από τον χρόνο που ορίζεται για μεμονωμένες ενότητες ή μεταβλητές. -Η ακύρωση της λήξης που έχει οριστεί προηγουμένως μπορεί να επιτευχθεί με τη μέθοδο `removeExpiration()`. Η άμεση διαγραφή ολόκληρου του τμήματος θα εξασφαλιστεί με τη μέθοδο `remove()`. +Η ακύρωση μιας προηγουμένως ορισμένης λήξης επιτυγχάνεται με τη μέθοδο `removeExpiration()`. Η άμεση ακύρωση ολόκληρης της ενότητας διασφαλίζεται από τη μέθοδο `remove()`. -Γεγονότα $onStart, $onBeforeWrite .[#toc-events-onstart-onbeforewrite] ----------------------------------------------------------------------- +Συμβάντα $onStart, $onBeforeWrite +--------------------------------- -Το αντικείμενο `Nette\Http\Session` έχει [γεγονότα |nette:glossary#Events] `$onStart` a `$onBeforeWrite`, έτσι ώστε να μπορείτε να προσθέσετε callbacks που καλούνται μετά την έναρξη της συνεδρίας ή πριν αυτή εγγραφεί στο δίσκο και στη συνέχεια τερματιστεί. +Το αντικείμενο `Nette\Http\Session` έχει [συμβάντα |nette:glossary#Events] `$onStart` και `$onBeforeWrite`, οπότε μπορείτε να προσθέσετε επανακλήσεις που καλούνται μετά την έναρξη του session ή πριν από την εγγραφή του στον δίσκο και τον επακόλουθο τερματισμό του. ```php $session->onBeforeWrite[] = function () { - // εγγραφή δεδομένων στη σύνοδο + // γράφουμε δεδομένα στο session $this->section->set('basket', $this->basket); }; ``` -Διαχείριση συνόδου .[#toc-session-management] -============================================= +Διαχείριση Session +================== -Επισκόπηση των μεθόδων της κλάσης `Nette\Http\Session` για τη διαχείριση συνόδου: +Επισκόπηση των μεθόδων της κλάσης `Nette\Http\Session` για τη διαχείριση του session: <div class=wiki-methods-brief> start(): void .[method] ----------------------- -Ξεκινά μια συνεδρία. +Ξεκινά το session. isStarted(): bool .[method] --------------------------- -Ξεκίνησε η συνεδρία; +Έχει ξεκινήσει το session; close(): void .[method] ----------------------- -Τερματίζει τη συνεδρία. Η συνεδρία τερματίζεται αυτόματα με το τέλος της δέσμης ενεργειών. +Τερματίζει το session. Το session τερματίζεται αυτόματα στο τέλος της εκτέλεσης του σεναρίου. destroy(): void .[method] ------------------------- -Τερματίζει και διαγράφει τη συνεδρία. +Τερματίζει και διαγράφει το session. exists(): bool .[method] ------------------------ -Περιέχει η αίτηση HTTP ένα cookie με αναγνωριστικό συνεδρίας; +Περιέχει το αίτημα HTTP ένα cookie με το session ID; regenerateId(): void .[method] ------------------------------ -Δημιουργεί ένα νέο τυχαίο αναγνωριστικό συνεδρίας. Τα δεδομένα παραμένουν αμετάβλητα. +Δημιουργεί ένα νέο τυχαίο session ID. Τα δεδομένα διατηρούνται. getId(): string .[method] ------------------------- -Επιστρέφει το αναγνωριστικό συνεδρίας. +Επιστρέφει το session ID. </div> -Διαμόρφωση .[#toc-configuration] --------------------------------- +Διαμόρφωση +---------- -Διαμορφώνουμε τη σύνοδο στη [διαμόρφωση |configuration#session]. Αν γράφετε μια εφαρμογή που δεν χρησιμοποιεί ένα DI container, χρησιμοποιήστε αυτές τις μεθόδους για να τη διαμορφώσετε. Πρέπει να κληθούν πριν από την έναρξη της συνεδρίας. +Ρυθμίζουμε το session στην [διαμόρφωση |configuration#Session]. Εάν γράφετε μια εφαρμογή που δεν χρησιμοποιεί DI container, αυτές οι μέθοδοι χρησιμοποιούνται για τη διαμόρφωση. Πρέπει να κληθούν πριν από την έναρξη του session. <div class=wiki-methods-brief> setName(string $name): static .[method] --------------------------------------- -Ορίζει το όνομα του cookie που χρησιμοποιείται για τη μετάδοση του αναγνωριστικού συνεδρίας. Το προεπιλεγμένο όνομα είναι `PHPSESSID`. Αυτό είναι χρήσιμο εάν εκτελείτε πολλές διαφορετικές εφαρμογές στον ίδιο ιστότοπο. +Ορίζει το όνομα του cookie στο οποίο μεταδίδεται το session ID. Το προεπιλεγμένο όνομα είναι `PHPSESSID`. Είναι χρήσιμο εάν εκτελείτε πολλές διαφορετικές εφαρμογές στον ίδιο ιστότοπο. getName(): string .[method] --------------------------- -Επιστρέφει το όνομα του cookie συνεδρίας. +Επιστρέφει το όνομα του cookie στο οποίο μεταδίδεται το session ID. setOptions(array $options): static .[method] -------------------------------------------- -Διαμορφώνει τη σύνοδο. Είναι δυνατόν να οριστούν όλες οι [οδηγίες συνόδου |https://www.php.net/manual/en/session.configuration.php] PHP (σε μορφή camelCase, π.χ. γράψτε `savePath` αντί για `session.save_path`) και επίσης [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. +Διαμορφώνει το session. Μπορείτε να ορίσετε όλες τις PHP [οδηγίες session |https://www.php.net/manual/en/session.configuration.php] (σε μορφή camelCase, π.χ. αντί για `session.save_path` γράφουμε `savePath`) καθώς και το [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. setExpiration(?string $time): static .[method] ---------------------------------------------- -Ορίζει το χρόνο αδράνειας μετά τον οποίο λήγει η σύνοδος. +Ορίζει τον χρόνο αδράνειας μετά τον οποίο λήγει το session. -setCookieParameters(string $path, string $domain=null, bool $secure=null, string $samesite=null): static .[method] ------------------------------------------------------------------------------------------------------------------- -Ορίζει παραμέτρους για τα cookies. Μπορείτε να αλλάξετε τις προεπιλεγμένες τιμές των παραμέτρων στο [configuration |configuration#Session cookie]. +setCookieParameters(string $path, ?string $domain=null, ?bool $secure=null, ?string $samesite=null): static .[method] +--------------------------------------------------------------------------------------------------------------------- +Ρύθμιση παραμέτρων για το cookie. Μπορείτε να αλλάξετε τις προεπιλεγμένες τιμές των παραμέτρων στην [διαμόρφωση |configuration#Session cookie]. setSavePath(string $path): static .[method] ------------------------------------------- -Ορίζει τον κατάλογο όπου αποθηκεύονται τα αρχεία συνεδρίας. +Ορίζει τον κατάλογο όπου αποθηκεύονται τα αρχεία session. setHandler(\SessionHandlerInterface $handler): static .[method] --------------------------------------------------------------- -Ορίζει προσαρμοσμένο χειριστή, βλ. [τεκμηρίωση PHP |https://www.php.net/manual/en/class.sessionhandlerinterface.php]. +Ορίζει έναν προσαρμοσμένο χειριστή, δείτε την [τεκμηρίωση της PHP|https://www.php.net/manual/en/class.sessionhandlerinterface.php]. </div> -Πρώτα η ασφάλεια .[#toc-safety-first] -===================================== +Ασφάλεια Πρώτα +============== -Ο διακομιστής θεωρεί ότι επικοινωνεί με τον ίδιο χρήστη εφόσον οι αιτήσεις περιέχουν το ίδιο αναγνωριστικό συνεδρίας. Το καθήκον των μηχανισμών ασφαλείας είναι να διασφαλίσουν ότι αυτή η συμπεριφορά λειτουργεί πραγματικά και ότι δεν υπάρχει δυνατότητα αντικατάστασης ή κλοπής ενός αναγνωριστικού. +Ο διακομιστής υποθέτει ότι επικοινωνεί πάντα με τον ίδιο χρήστη, εφόσον τα αιτήματα συνοδεύονται από το ίδιο session ID. Ο ρόλος των μηχανισμών ασφαλείας είναι να διασφαλίσουν ότι αυτό συμβαίνει πραγματικά και ότι δεν είναι δυνατό να κλαπεί ή να πλαστογραφηθεί το αναγνωριστικό. -Γι' αυτό το λόγο το Nette Framework ρυθμίζει κατάλληλα τις οδηγίες της PHP ώστε να μεταφέρει το αναγνωριστικό συνόδου μόνο σε cookies, να αποφεύγεται η πρόσβαση από JavaScript και να αγνοούνται τα αναγνωριστικά στη διεύθυνση URL. Επιπλέον, σε κρίσιμες στιγμές, όπως η είσοδος του χρήστη, δημιουργεί ένα νέο αναγνωριστικό συνεδρίας. +Επομένως, το Nette Framework διαμορφώνει σωστά τις οδηγίες PHP έτσι ώστε το session ID να μεταδίδεται μόνο σε cookies, να το καθιστά μη προσβάσιμο από JavaScript και να αγνοεί τυχόν αναγνωριστικά στο URL. Επιπλέον, σε κρίσιμες στιγμές, όπως η σύνδεση του χρήστη, δημιουργεί ένα νέο session ID. -Η συνάρτηση ini_set χρησιμοποιείται για τη διαμόρφωση της PHP, αλλά δυστυχώς η χρήση της απαγορεύεται σε ορισμένες υπηρεσίες φιλοξενίας ιστοσελίδων. Αν είναι η περίπτωσή σας, προσπαθήστε να ζητήσετε από τον πάροχο φιλοξενίας σας να σας επιτρέψει αυτή τη λειτουργία ή τουλάχιστον να ρυθμίσει σωστά τον διακομιστή του. .[note] +.[note] +Η συνάρτηση ini_set χρησιμοποιείται για τη διαμόρφωση της PHP, την οποία δυστυχώς ορισμένοι πάροχοι φιλοξενίας απαγορεύουν. Εάν αυτό συμβαίνει και με τον δικό σας πάροχο, προσπαθήστε να διαπραγματευτείτε μαζί του για να σας επιτρέψει τη συνάρτηση ή τουλάχιστον να διαμορφώσει τον διακομιστή. diff --git a/http/el/urls.texy b/http/el/urls.texy index eff9550e24..129f400099 100644 --- a/http/el/urls.texy +++ b/http/el/urls.texy @@ -1,16 +1,16 @@ -Αναλυτής και κατασκευαστής URL -****************************** +Εργασία με URLs +*************** .[perex] -Οι κλάσεις [Url |#Url], [UrlImmutable |#UrlImmutable] και [UrlScript |#UrlScript] διευκολύνουν τη διαχείριση, την ανάλυση και τον χειρισμό των URL. +Οι κλάσεις [#Url], [#UrlImmutable] και [#UrlScript] επιτρέπουν την εύκολη δημιουργία, ανάλυση και χειρισμό URLs. -→ [Εγκατάσταση και απαιτήσεις |@home#Installation] +→ [Εγκατάσταση και απαιτήσεις |@home#Εγκατάσταση] Url === -Η κλάση [api:Nette\Http\Url] διευκολύνει την εργασία με τη διεύθυνση URL και τα επιμέρους στοιχεία της, τα οποία περιγράφονται σε αυτό το διάγραμμα: +Η κλάση [api:Nette\Http\Url] επιτρέπει την εύκολη εργασία με URLs και τα μεμονωμένα συστατικά τους, τα οποία αποτυπώνονται σε αυτό το διάγραμμα: /--pre scheme user password host port path query fragment @@ -22,7 +22,7 @@ Url hostUrl authority \-- -Η δημιουργία URL είναι διαισθητική: +Η δημιουργία URLs είναι διαισθητική: ```php use Nette\Http\Url; @@ -36,7 +36,7 @@ $url->setScheme('https') echo $url; // 'https://localhost/edit?foo=bar' ``` -Μπορείτε επίσης να αναλύσετε τη διεύθυνση URL και στη συνέχεια να την επεξεργαστείτε: +Μπορείτε επίσης να αναλύσετε ένα URL και να το χειριστείτε περαιτέρω: ```php $url = new Url( @@ -44,62 +44,94 @@ $url = new Url( ); ``` -Οι ακόλουθες μέθοδοι είναι διαθέσιμες για να λάβετε ή να αλλάξετε μεμονωμένα στοιχεία URL: +Η κλάση `Url` υλοποιεί τη διεπαφή `JsonSerializable` και έχει μια μέθοδο `__toString()`, οπότε το αντικείμενο μπορεί να εκτυπωθεί ή να χρησιμοποιηθεί σε δεδομένα που περνούν στο `json_encode()`. + +```php +echo $url; +echo json_encode([$url]); +``` + + +Συστατικά URL .[method] +----------------------- + +Για την επιστροφή ή την αλλαγή μεμονωμένων συστατικών του URL, είναι διαθέσιμες οι ακόλουθες μέθοδοι: .[language-php] -| Setter | Getter | Επιστρεφόμενη τιμή +| Setter | Getter | Επιστρεφόμενη τιμή |-------------------------------------------------------------------------------------------- -| `setScheme(string $scheme)`| `getScheme(): string`| `'http'` -| `setUser(string $user)`| `getUser(): string`| `'john'` -| `setPassword(string $password)`| `getPassword(): string`| `'xyz*12'` -| `setHost(string $host)`| `getHost(): string`| `'nette.org'` -| `setPort(int $port)`| `getPort(): ?int`| `8080` -| | `getDefaultPort(): ?int`| `80` -| `setPath(string $path)`| `getPath(): string`| `'/en/download'` -| `setQuery(string\|array $query)`| `getQuery(): string`| `'name=param'` -| `setFragment(string $fragment)`| `getFragment(): string`| `'footer'` -| | `getAuthority(): string`| `'nette.org:8080'` -| | `getHostUrl(): string`| `'http://nette.org:8080'` -| | `getAbsoluteUrl(): string` | πλήρες URL - -Μπορούμε επίσης να λειτουργήσουμε με μεμονωμένες παραμέτρους ερωτήματος χρησιμοποιώντας: +| `setScheme(string $scheme)` | `getScheme(): string` | `'http'` +| `setUser(string $user)` | `getUser(): string` | `'john'` +| `setPassword(string $password)` | `getPassword(): string` | `'xyz*12'` +| `setHost(string $host)` | `getHost(): string` | `'nette.org'` +| `setPort(int $port)` | `getPort(): ?int` | `8080` +| | `getDefaultPort(): ?int` | `80` +| `setPath(string $path)` | `getPath(): string` | `'/en/download'` +| `setQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` +| `setFragment(string $fragment)` | `getFragment(): string` | `'footer'` +| | `getAuthority(): string` | `'john:xyz%2A12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | ολόκληρο το URL + +Προειδοποίηση: Όταν εργάζεστε με ένα URL που λαμβάνεται από ένα [αίτημα HTTP|request], λάβετε υπόψη ότι δεν θα περιέχει το fragment, καθώς ο περιηγητής δεν το στέλνει στον διακομιστή. + +Μπορούμε επίσης να εργαστούμε με μεμονωμένες παραμέτρους query χρησιμοποιώντας: .[language-php] -| Setter | Getter +| Setter | Getter |--------------------------------------------------- -| `setQuery(string\|array $query)` | `getQueryParameters(): array` -| `setQueryParameter(string $name, $val)`| `getQueryParameter(string $name)` +| `setQuery(string\|array $query)` | `getQueryParameters(): array` +| `setQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` -Η μέθοδος `getDomain(int $level = 2)` επιστρέφει το δεξί ή το αριστερό μέρος του κεντρικού υπολογιστή. Έτσι λειτουργεί αν ο κεντρικός υπολογιστής είναι `www.nette.org`: + +getDomain(int $level = 2): string .[method] +------------------------------------------- +Επιστρέφει το δεξί ή το αριστερό τμήμα του host. Λειτουργεί ως εξής εάν ο host είναι `www.nette.org`: .[language-php] -| `getDomain(1)` | `'org'` -| `getDomain(2)` | `'nette.org'` -| `getDomain(3)` | `'www.nette.org'` -| `getDomain(0)` | `'www.nette.org'` -| `getDomain(-1)` | `'www.nette'` -| `getDomain(-2)` | `'www'` -| `getDomain(-3)` | `''` +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `''` -Η κλάση `Url` υλοποιεί τη διεπαφή `JsonSerializable` και διαθέτει μια μέθοδο `__toString()` ώστε το αντικείμενο να μπορεί να εκτυπωθεί ή να χρησιμοποιηθεί σε δεδομένα που περνούν στο `json_encode()`. +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +Επαληθεύει εάν δύο URLs είναι πανομοιότυπα. ```php -echo $url; -echo json_encode([$url]); +$url->isEqual('https://nette.org'); ``` -Η μέθοδος `isEqual(string|Url $anotherUrl): bool` ελέγχει αν οι δύο διευθύνσεις URL είναι πανομοιότυπες. + +Url::isAbsolute(string $url): bool .[method]{data-version:3.3.2} +---------------------------------------------------------------- +Επαληθεύει εάν ένα URL είναι απόλυτο. Ένα URL θεωρείται απόλυτο εάν ξεκινά με ένα σχήμα (π.χ. http, https, ftp) ακολουθούμενο από άνω και κάτω τελεία. ```php -$url->isEqual('https://nette.org'); +Url::isAbsolute('https://nette.org'); // true +Url::isAbsolute('//nette.org'); // false +``` + + +Url::removeDotSegments(string $path): string .[method]{data-version:3.3.2} +-------------------------------------------------------------------------- +Κανονικοποιεί τη διαδρομή σε ένα URL αφαιρώντας τα ειδικά τμήματα `.` και `..`. Η μέθοδος αφαιρεί τα περιττά στοιχεία διαδρομής με τον ίδιο τρόπο που το κάνουν οι περιηγητές ιστού. + +```php +Url::removeDotSegments('/path/../subtree/./file.txt'); // '/subtree/file.txt' +Url::removeDotSegments('/../foo/./bar'); // '/foo/bar' +Url::removeDotSegments('./today/../file.txt'); // 'file.txt' ``` -UrlImmutable .[#toc-urlimmutable] -================================= +UrlImmutable +============ -Η κλάση [api:Nette\Http\UrlImmutable] είναι μια αμετάβλητη εναλλακτική της κλάσης `Url` (όπως ακριβώς στην PHP `DateTimeImmutable` είναι αμετάβλητη εναλλακτική της `DateTime`). Αντί για setters, έχει τους λεγόμενους withers, οι οποίοι δεν αλλάζουν το αντικείμενο, αλλά επιστρέφουν νέες περιπτώσεις με τροποποιημένη τιμή: +Η κλάση [api:Nette\Http\UrlImmutable] είναι μια αμετάβλητη (immutable) εναλλακτική της κλάσης [#Url] (παρόμοια με το πώς το `DateTimeImmutable` είναι η αμετάβλητη εναλλακτική του `DateTime` στην PHP). Αντί για setters, έχει τους λεγόμενους withers, οι οποίοι δεν αλλάζουν το αντικείμενο, αλλά επιστρέφουν νέες παρουσίες με την τροποποιημένη τιμή: ```php use Nette\Http\UrlImmutable; @@ -111,53 +143,96 @@ $url = new UrlImmutable( $newUrl = $url ->withUser('') ->withPassword('') - ->withPath('/en/'); + ->withPath('/cs/'); + +echo $newUrl; // 'http://john:xyz%2A12@nette.org:8080/cs/?name=param#footer' +``` + +Η κλάση `UrlImmutable` υλοποιεί τη διεπαφή `JsonSerializable` και έχει μια μέθοδο `__toString()`, οπότε το αντικείμενο μπορεί να εκτυπωθεί ή να χρησιμοποιηθεί σε δεδομένα που περνούν στο `json_encode()`. -echo $newUrl; // 'http://nette.org:8080/en/?name=param#footer' +```php +echo $url; +echo json_encode([$url]); ``` -Οι ακόλουθες μέθοδοι είναι διαθέσιμες για να λάβετε ή να αλλάξετε μεμονωμένα στοιχεία URL: + +Συστατικά URL .[method] +----------------------- + +Για την επιστροφή ή την αλλαγή μεμονωμένων συστατικών του URL, χρησιμοποιούνται οι ακόλουθες μέθοδοι: .[language-php] -| Wither | Getter | Επιστρεφόμενη τιμή +| Wither | Getter | Επιστρεφόμενη τιμή |-------------------------------------------------------------------------------------------- -| `withScheme(string $scheme)`| `getScheme(): string`| `'http'` -| `withUser(string $user)`| `getUser(): string`| `'john'` -| `withPassword(string $password)`| `getPassword(): string`| `'xyz*12'` -| `withHost(string $host)`| `getHost(): string`| `'nette.org'` -| `withPort(int $port)`| `getPort(): ?int`| `8080` -| | `getDefaultPort(): ?int`| `80` -| `withPath(string $path)`| `getPath(): string`| `'/en/download'` -| `withQuery(string\|array $query)`| `getQuery(): string`| `'name=param'` -| `withFragment(string $fragment)`| `getFragment(): string`| `'footer'` -| | `getAuthority(): string`| `'nette.org:8080'` -| | `getHostUrl(): string`| `'http://nette.org:8080'` -| | `getAbsoluteUrl(): string` | πλήρες URL - -Μπορούμε επίσης να λειτουργήσουμε με μεμονωμένες παραμέτρους ερωτήματος χρησιμοποιώντας: +| `withScheme(string $scheme)` | `getScheme(): string` | `'http'` +| `withUser(string $user)` | `getUser(): string` | `'john'` +| `withPassword(string $password)` | `getPassword(): string` | `'xyz*12'` +| `withHost(string $host)` | `getHost(): string` | `'nette.org'` +| `withPort(int $port)` | `getPort(): ?int` | `8080` +| | `getDefaultPort(): ?int` | `80` +| `withPath(string $path)` | `getPath(): string` | `'/en/download'` +| `withQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` +| `withFragment(string $fragment)` | `getFragment(): string` | `'footer'` +| | `getAuthority(): string` | `'john:xyz%2A12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | ολόκληρο το URL + +Η μέθοδος `withoutUserInfo()` αφαιρεί τα `user` και `password`. + +Μπορούμε επίσης να εργαστούμε με μεμονωμένες παραμέτρους query χρησιμοποιώντας: .[language-php] -| Wither | Getter +| Wither | Getter |----------------------------------------------- -| `withQuery(string\|array $query)` | `getQueryParameters(): array` -| `withQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` +| `withQuery(string\|array $query)` | `getQueryParameters(): array` +| `withQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` -Η μέθοδος `getDomain(int $level = 2)` λειτουργεί όπως και η μέθοδος στο `Url`. Η μέθοδος `withoutUserInfo()` αφαιρεί τα `user` και `password`. -Η κλάση `UrlImmutable` υλοποιεί τη διεπαφή `JsonSerializable` και διαθέτει μια μέθοδο `__toString()` ώστε το αντικείμενο να μπορεί να εκτυπωθεί ή να χρησιμοποιηθεί σε δεδομένα που μεταβιβάζονται στο `json_encode()`. +getDomain(int $level = 2): string .[method] +------------------------------------------- +Επιστρέφει το δεξί ή το αριστερό τμήμα του host. Λειτουργεί ως εξής εάν ο host είναι `www.nette.org`: + +.[language-php] +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `''` + + +resolve(string $reference): UrlImmutable .[method]{data-version:3.3.2} +---------------------------------------------------------------------- +Παράγει ένα απόλυτο URL με τον ίδιο τρόπο που ένας περιηγητής επεξεργάζεται συνδέσμους σε μια σελίδα HTML: +- εάν ο σύνδεσμος είναι ένα απόλυτο URL (περιέχει σχήμα), χρησιμοποιείται αμετάβλητος +- εάν ο σύνδεσμος ξεκινά με `//`, λαμβάνεται μόνο το σχήμα από το τρέχον URL +- εάν ο σύνδεσμος ξεκινά με `/`, δημιουργείται μια απόλυτη διαδρομή από τη ρίζα του domain +- σε άλλες περιπτώσεις, το URL δημιουργείται σχετικά με την τρέχουσα διαδρομή ```php -echo $url; -echo json_encode([$url]); +$url = new UrlImmutable('https://example.com/path/page'); +echo $url->resolve('../foo'); // 'https://example.com/foo' +echo $url->resolve('/bar'); // 'https://example.com/bar' +echo $url->resolve('sub/page.html'); // 'https://example.com/path/sub/page.html' +``` + + +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +Επαληθεύει εάν δύο URLs είναι πανομοιότυπα. + +```php +$url->isEqual('https://nette.org'); ``` -Η μέθοδος `isEqual(string|Url $anotherUrl): bool` ελέγχει αν οι δύο διευθύνσεις URL είναι πανομοιότυπες. +UrlScript +========= -UrlScript .[#toc-urlscript] -=========================== +Η κλάση [api:Nette\Http\UrlScript] είναι απόγονος του [#UrlImmutable] και το επεκτείνει με πρόσθετα εικονικά συστατικά URL, όπως ο ριζικός κατάλογος του έργου κ.λπ. Όπως και η γονική κλάση, είναι ένα αμετάβλητο (immutable) αντικείμενο. -Η κλάση [api:Nette\Http\UrlScript] είναι απόγονος της `UrlImmutable` και διακρίνει επιπλέον αυτά τα λογικά μέρη της διεύθυνσης URL: +Το ακόλουθο διάγραμμα δείχνει τα συστατικά που αναγνωρίζει το UrlScript: /--pre baseUrl basePath relativePath relativeUrl @@ -169,17 +244,23 @@ UrlScript .[#toc-urlscript] scriptPath pathInfo \-- -Οι ακόλουθες μέθοδοι είναι διαθέσιμες για την απόκτηση αυτών των τμημάτων: +- `baseUrl` είναι η βασική διεύθυνση URL της εφαρμογής, συμπεριλαμβανομένου του domain και του τμήματος της διαδρομής προς τον ριζικό κατάλογο της εφαρμογής +- `basePath` είναι το τμήμα της διαδρομής προς τον ριζικό κατάλογο της εφαρμογής +- `scriptPath` είναι η διαδρομή προς το τρέχον σενάριο +- `relativePath` είναι το όνομα του σεναρίου (και ενδεχομένως περαιτέρω τμήματα διαδρομής) σχετικά με το basePath +- `relativeUrl` είναι ολόκληρο το τμήμα του URL μετά το baseUrl, συμπεριλαμβανομένης της συμβολοσειράς query και του fragment. +- `pathInfo` είναι ένα τμήμα του URL που χρησιμοποιείται σπάνια σήμερα, μετά το όνομα του σεναρίου + +Για την επιστροφή τμημάτων του URL, είναι διαθέσιμες οι ακόλουθες μέθοδοι: .[language-php] -| Getter | Επιστρεφόμενη τιμή +| Getter | Επιστρεφόμενη τιμή |------------------------------------------------ -| `getScriptPath(): string`| `'/admin/script.php'` -| `getBasePath(): string`| `'/admin/'` -| `getBaseUrl(): string`| `'http://nette.org/admin/'` -| `getRelativePath(): string`| `'script.php'` -| `getRelativeUrl(): string`| `'script.php/pathinfo/?name=param#footer'` -| `getPathInfo(): string`| `'/pathinfo/'` - - -Δεν δημιουργούμε άμεσα αντικείμενα `UrlScript`, αλλά η μέθοδος [Nette\Http\Request::getUrl() |request] το επιστρέφει. +| `getScriptPath(): string` | `'/admin/script.php'` +| `getBasePath(): string` | `'/admin/'` +| `getBaseUrl(): string` | `'http://nette.org/admin/'` +| `getRelativePath(): string` | `'script.php'` +| `getRelativeUrl(): string` | `'script.php/pathinfo/?name=param#footer'` +| `getPathInfo(): string` | `'/pathinfo/'` + +Συνήθως δεν δημιουργούμε απευθείας αντικείμενα `UrlScript`, αλλά η μέθοδος [Nette\Http\Request::getUrl()|request] τα επιστρέφει με τα συστατικά ήδη σωστά ρυθμισμένα για το τρέχον αίτημα HTTP. diff --git a/http/en/@left-menu.texy b/http/en/@left-menu.texy index 5f72f7dd2e..652706062b 100644 --- a/http/en/@left-menu.texy +++ b/http/en/@left-menu.texy @@ -1,8 +1,8 @@ Nette HTTP ********** - [Overview |@home] -- [HTTP request |request] -- [HTTP response |response] +- [HTTP Request |request] +- [HTTP Response |response] - [Sessions] -- [URL utility |urls] +- [URL Utility |urls] - [Configuration] diff --git a/http/en/@meta.texy b/http/en/@meta.texy new file mode 100644 index 0000000000..42471908b0 --- /dev/null +++ b/http/en/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Documentation}} diff --git a/http/en/configuration.texy b/http/en/configuration.texy index 52fb269168..64ce01eb2d 100644 --- a/http/en/configuration.texy +++ b/http/en/configuration.texy @@ -1,10 +1,10 @@ -Configuring HTTP -**************** +HTTP Configuration +****************** .[perex] -Overview of configuration options for the Nette HTTP. +Overview of configuration options for Nette HTTP. -If you are not using the whole framework, but only this library, read [how to load the configuration|bootstrap:]. +If you are not using the entire framework, but only this library, read [how to load the configuration|bootstrap:]. HTTP Headers @@ -18,23 +18,23 @@ http: X-Content-Type-Options: nosniff X-XSS-Protection: '1; mode=block' - # affects header X-Frame-Options + # affects the X-Frame-Options header frames: ... # (string|bool) defaults to 'SAMEORIGIN' ``` -For security reasons, the framework sends a header `X-Frame-Options: SAMEORIGIN`, which says that a page can be displayed inside another page (in element `<iframe>`) only if it is on the same domain. This can be unwanted in certain situations (for example, if you are developing a Facebook application), so the behavior can be changed by setting `frames: http://allowed-host.com` or `frames: true`. +For security reasons, the framework sends the `X-Frame-Options: SAMEORIGIN` header, which indicates that a page can be displayed inside another page (in an `<iframe>` element) only if it is on the same domain. This might be undesirable in certain situations (e.g., if you are developing a Facebook application), so the behavior can be changed by setting `frames: http://allowed-host.com` or `frames: true`. Content Security Policy ----------------------- -Headers `Content-Security-Policy` (hereinafter referred to as CSP) can be easily assembled, their description can be found in [CSP description |https://content-security-policy.com]. CSP directives (such as `script-src`) can be written either as strings according to specification or as arrays of values ​​for better readability. Then there is no need to write quotation marks around keywords such as `'self'`. Nette will also automatically generate a value of `nonce`, so `'nonce-y4PopTLM=='` will be send in the header. +Headers `Content-Security-Policy` (CSP) can be easily configured; their description can be found in the [CSP specification |https://content-security-policy.com]. CSP directives (such as `script-src`) can be written either as strings according to the specification or as arrays of values for better readability. Then there is no need to use quotation marks around keywords like `'self'`. Nette will also automatically generate a `nonce` value, so something like `'nonce-y4PopTLM=='` will be sent in the header. ```neon http: # Content Security Policy csp: - # string according to CSP specification + # string according to the CSP specification default-src: "'self' https://example.com" # array of values @@ -49,9 +49,9 @@ http: block-all-mixed-content: false ``` -Use `<script n:nonce>...</script>` in the templates and the nonce value will be filled in automatically. Making secure websites in Nette is really easy. +Use `<script n:nonce>...</script>` in templates, and the nonce value will be filled in automatically. Making secure websites in Nette is really easy. -Similarly, headers `Content-Security-Policy-Report-Only` (which can be used in parallel with CSP) and [Feature Policy |https://developers.google.com/web/updates/2018/06/feature-policy] can be added: +Similarly, `Content-Security-Policy-Report-Only` headers (which can be used concurrently with CSP) and [Feature Policy|https://developers.google.com/web/updates/2018/06/feature-policy] can be configured: ```neon http: @@ -72,39 +72,39 @@ http: HTTP Cookie ----------- -You can change the default values of some parameters of the [Nette\Http\Response::setCookie() |response#setCookie] and session methods. +You can change the default values of some parameters of the [Nette\Http\Response::setCookie() |response#setCookie] method and session handling. ```neon http: # cookie scope by path cookiePath: ... # (string) defaults to '/' - # which hosts are allowed to receive the cookie + # domains that can receive the cookie cookieDomain: 'example.com' # (string|domain) defaults to unset - # to send cookies only via HTTPS? + # send cookies only via HTTPS? cookieSecure: ... # (bool|auto) defaults to auto - # disables the sending of the cookie that Nette uses as protection against CSRF + # disables sending the cookie that Nette uses for CSRF protection disableNetteCookie: ... # (bool) defaults to false ``` -The `cookieDomain` option determines which domains (origins) can accept cookies. If not specified, the cookie is accepted by the same (sub)domain as is set by it, *excluding* their subdomains. If `cookieDomain` is specified, then subdomains are also included. Therefore, specifying `cookieDomain` is less restrictive than omitting. +The `cookieDomain` attribute determines which domains (origins) can accept cookies. If not specified, the cookie is accepted by the same (sub)domain that set it, *excluding* its subdomains. If `cookieDomain` is specified, subdomains are also included. Therefore, specifying `cookieDomain` is less restrictive than omitting it. -For example, if `cookieDomain: nette.org` is set, cookie is also available on all subdomains like `doc.nette.org`. This can also be achieved with the special value `domain`, ie `cookieDomain: domain`. +For example, if `cookieDomain: nette.org` is set, cookies are also available on all subdomains like `doc.nette.org`. This can also be achieved with the special value `domain`, i.e., `cookieDomain: domain`. -The default value of `cookieSecure` is `auto` which means that if the website is running on HTTPS, cookies will be sent with the `Secure` flag and will therefore only be available via HTTPS. +The default value `auto` for the `cookieSecure` attribute means that if the website runs on HTTPS, cookies will be sent with the `Secure` flag and will therefore only be available via HTTPS. HTTP Proxy ---------- -If the site is running behind an HTTP proxy, enter the IP address of the proxy so that detection of HTTPS connections works correctly, as well as the client IP address. That is, so that [Nette\Http\Request::getRemoteAddress()|request#getRemoteAddress] and [isSecured()|request#isSecured] return the correct values and links are generated with the `https:` protocol in the templates. +If the site runs behind an HTTP proxy, enter the proxy's IP address so that HTTPS connection detection and the client's IP address work correctly. That is, so that [Nette\Http\Request::getRemoteAddress() |request#getRemoteAddress] and [isSecured() |request#isSecured] return the correct values, and links are generated with the `https:` protocol in templates. ```neon http: - # IP address, range (ie. 127.0.0.1/8) or array of these values - proxy: 127.0.0.1 # (string|string[]) defaults to none + # IP address, range (e.g., 127.0.0.1/8), or an array of these values + proxy: 127.0.0.1 # (string|string[]) defaults to not set ``` @@ -115,22 +115,22 @@ Basic [sessions] settings: ```neon session: - # shows session panel in Tracy Bar? + # show the session panel in Tracy Bar? debugger: ... # (bool) defaults to false # inactivity time after which the session expires expiration: 14 days # (string) defaults to '3 hours' - # when to start the session? + # when should the session start? autoStart: ... # (smart|always|never) defaults to 'smart' - # handler, service that implements the SessionHandlerInterface interface + # handler, a service implementing SessionHandlerInterface handler: @handlerService ``` -The `autoStart` option controls when to start the session. The value `always` means that the session is always started when the application starts. The `smart` value means that the session will be started when the application starts only if it already exists, or at the moment we want to read from or write to it. Finally, the value `never` disables the automatic start of the session. +The `autoStart` option controls when the session should start. The value `always` means the session starts whenever the application starts. The value `smart` means the session starts with the application only if it already exists, or at the moment we want to read from or write to it. Finally, the value `never` disables the automatic start of the session. -You can also set all PHP [session directives |https://www.php.net/manual/en/session.configuration.php] (in camelCase format) and also [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. Example: +Furthermore, you can set all PHP [session directives |https://www.php.net/manual/en/session.configuration.php] (in camelCase format) and also [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. Example: ```neon session: @@ -145,15 +145,27 @@ session: Session Cookie -------------- -The session cookie is sent with the same parameters as [other cookie|#HTTP cookie], but you can change these for it: +The session cookie is sent with the same parameters as [other cookies |#HTTP Cookie], but you can change these specifically for it: ```neon session: - # which hosts are allowed to receive the cookie + # domains that can receive the cookie cookieDomain: 'example.com' # (string|domain) - # restrictions when accessing cross-origin request + # restriction for cross-origin access cookieSamesite: None # (Strict|Lax|None) defaults to Lax ``` -The `cookieSamesite` option affects whether the cookie is sent with [cross-origin requests |nette:glossary#SameSite cookie], which provides some protection against [Cross-Site Request Forgery|nette:glossary#cross-site-request-forgery-csrf] attecks. +The `cookieSamesite` attribute affects whether the cookie is sent with [cross-origin requests |nette:glossary#SameSite cookie], which provides some protection against [Cross-Site Request Forgery |nette:glossary#Cross-Site Request Forgery CSRF] (CSRF) attacks. + + +DI Services +=========== + +These services are added to the DI container: + +| Name | Type | Description +|-----------------|----------------------------|--------------------------- +| `http.request` | [api:Nette\Http\Request] | [HTTP request| request] +| `http.response` | [api:Nette\Http\Response] | [HTTP response| response] +| `session.session`| [api:Nette\Http\Session] | [session management| sessions] diff --git a/http/en/request.texy b/http/en/request.texy index edf7a57531..47e0232f54 100644 --- a/http/en/request.texy +++ b/http/en/request.texy @@ -2,11 +2,11 @@ HTTP Request ************ .[perex] -Nette encapsulates the HTTP request into objects with an understandable API while providing a sanitization filter. +Nette encapsulates the HTTP request into objects with a clear API while providing a sanitization filter. -An HTTP request is an [api:Nette\Http\Request] object, which you get by passing it using [dependency injection |dependency-injection:passing-dependencies]. In presenters simply call `$httpRequest = $this->getHttpRequest()`. +The HTTP request is represented by the [api:Nette\Http\Request] object. If you are working with Nette, this object is automatically created by the framework, and you can have it passed to you using [dependency injection |dependency-injection:passing-dependencies]. In presenters, simply call the `$this->getHttpRequest()` method. If you are working outside the Nette Framework, you can create the object using [#RequestFactory]. -What is important is that Nette when [creating|#RequestFactory] this object, it clears all GET, POST and COOKIE input parameters as well as URLs of control characters and invalid UTF-8 sequences. So you can safely continue working with the data. The cleaned data is then used in presenters and forms. +A major advantage of Nette is that when creating the object, it automatically sanitizes all input parameters (GET, POST, COOKIE) as well as the URL, removing control characters and invalid UTF-8 sequences. You can then safely work with this data. The sanitized data is subsequently used in presenters and forms. → [Installation and requirements |@home#Installation] @@ -14,7 +14,7 @@ What is important is that Nette when [creating|#RequestFactory] this object, it Nette\Http\Request ================== -This object is immutable. It has no setters, it has only one so-called wither `withUrl()`, which does not change the object, but returns a new instance with a modified value. +This object is immutable. It has no setters; it has only one so-called wither, `withUrl()`, which does not change the object but returns a new instance with the modified value. withUrl(Nette\Http\UrlScript $url): Nette\Http\Request .[method] @@ -24,7 +24,7 @@ Returns a clone with a different URL. getUrl(): Nette\Http\UrlScript .[method] ---------------------------------------- -Returns the URL of the request as object [UrlScript|urls#UrlScript]. +Returns the URL of the request as a [UrlScript |urls#UrlScript] object. ```php $url = $httpRequest->getUrl(); @@ -32,12 +32,12 @@ echo $url; // https://nette.org/en/documentation?action=edit echo $url->getHost(); // nette.org ``` -Browsers do not send a fragment to the server, so `$url->getFragment()` will return an empty string. +Warning: Browsers do not send the fragment to the server, so `$url->getFragment()` will return an empty string. -getQuery(string $key=null): string|array|null .[method] -------------------------------------------------------- -Returns GET request parameters: +getQuery(?string $key=null): string|array|null .[method] +-------------------------------------------------------- +Returns GET request parameters. ```php $all = $httpRequest->getQuery(); // array of all URL parameters @@ -45,9 +45,9 @@ $id = $httpRequest->getQuery('id'); // returns GET parameter 'id' (or null) ``` -getPost(string $key=null): string|array|null .[method] ------------------------------------------------------- -Returns POST request parameters: +getPost(?string $key=null): string|array|null .[method] +------------------------------------------------------- +Returns POST request parameters. ```php $all = $httpRequest->getPost(); // array of all POST parameters @@ -57,29 +57,29 @@ $id = $httpRequest->getPost('id'); // returns POST parameter 'id' (or null) getFile(string|string[] $key): Nette\Http\FileUpload|array|null .[method] ------------------------------------------------------------------------- -Returns [upload|#Uploaded Files] as object [api:Nette\Http\FileUpload]: +Returns an [upload |#Uploaded Files] as a [api:Nette\Http\FileUpload] object: ```php $file = $httpRequest->getFile('avatar'); -if ($file->hasFile()) { // was any file uploaded? - $file->getUntrustedName(); // name of the file sent by user - $file->getSanitizedName(); // the name without dangerous characters +if ($file?->hasFile()) { // was any file uploaded? + $file->getUntrustedName(); // filename sent by the user + $file->getSanitizedName(); // name without dangerous characters } ``` -Specify an array of keys to access the subtree structure. +To access a nested structure, provide an array of keys. ```php //<input type="file" name="my-form[details][avatar]" multiple> $file = $request->getFile(['my-form', 'details', 'avatar']); ``` -Because you can't trust data from the outside and therefore don't rely on the form of the structure, this method is safer than `$request->getFiles()['my-form']['details']['avatar']`, which can fail. +Since you cannot trust external data and therefore rely on the structure of the files, this approach is safer than, for example, `$request->getFiles()['my-form']['details']['avatar']`, which might fail. getFiles(): array .[method] --------------------------- -Returns tree of [upload files|#Uploaded Files] in a normalized structure, with each leaf an instance of [api:Nette\Http\FileUpload]: +Returns a tree of [all uploads |#Uploaded Files] in a normalized structure, where the leaves are [api:Nette\Http\FileUpload] objects: ```php $files = $httpRequest->getFiles(); @@ -88,7 +88,7 @@ $files = $httpRequest->getFiles(); getCookie(string $key): string|array|null .[method] --------------------------------------------------- -Returns a cookie or `null` if it does not exist. +Returns a cookie or `null` if it doesn't exist. ```php $sessId = $httpRequest->getCookie('sess_id'); @@ -97,7 +97,7 @@ $sessId = $httpRequest->getCookie('sess_id'); getCookies(): array .[method] ----------------------------- -Returns all cookies: +Returns all cookies. ```php $cookies = $httpRequest->getCookies(); @@ -106,16 +106,16 @@ $cookies = $httpRequest->getCookies(); getMethod(): string .[method] ----------------------------- -Returns the HTTP method with which the request was made. +Returns the HTTP method used for the request. ```php -echo $httpRequest->getMethod(); // GET, POST, HEAD, PUT +$httpRequest->getMethod(); // GET, POST, HEAD, PUT ``` isMethod(string $method): bool .[method] ---------------------------------------- -Checks the HTTP method with which the request was made. The parameter is case-insensitive. +Tests the HTTP method used for the request. The parameter is case-insensitive. ```php if ($httpRequest->isMethod('GET')) // ... @@ -124,7 +124,7 @@ if ($httpRequest->isMethod('GET')) // ... getHeader(string $header): ?string .[method] -------------------------------------------- -Returns an HTTP header or `null` if it does not exist. The parameter is case-insensitive: +Returns an HTTP header or `null` if it doesn't exist. The parameter is case-insensitive. ```php $userAgent = $httpRequest->getHeader('User-Agent'); @@ -133,7 +133,7 @@ $userAgent = $httpRequest->getHeader('User-Agent'); getHeaders(): array .[method] ----------------------------- -Returns all HTTP headers as associative array: +Returns all HTTP headers as an associative array. ```php $headers = $httpRequest->getHeaders(); @@ -141,19 +141,14 @@ echo $headers['Content-Type']; ``` -getReferer(): ?Nette\Http\UrlImmutable .[method] ------------------------------------------------- -What URL did the user come from? Beware, it is not reliable at all. - - isSecured(): bool .[method] --------------------------- -Is the connection encrypted (HTTPS)? You may need to [set up a proxy|configuration#HTTP proxy] for proper functionality. +Is the connection encrypted (HTTPS)? Proper functionality might require [setting up a proxy |configuration#HTTP Proxy]. isSameSite(): bool .[method] ---------------------------- -Is the request coming from the same (sub) domain and is initiated by clicking on a link? Nette uses the `_nss` cookie (formerly `nette-samesite`) to detect it. +Is the request coming from the same (sub)domain and initiated by clicking a link? Nette uses the `_nss` cookie (formerly `nette-samesite`) for detection. isAjax(): bool .[method] @@ -163,17 +158,17 @@ Is it an AJAX request? getRemoteAddress(): ?string .[method] ------------------------------------- -Returns the user's IP address. You may need to [set up a proxy|configuration#HTTP proxy] for proper functionality. +Returns the user's IP address. Proper functionality might require [setting up a proxy |configuration#HTTP Proxy]. getRemoteHost(): ?string .[method deprecated] --------------------------------------------- -Returns DNS translation of the user's IP address. You may need to [set up a proxy|configuration#HTTP proxy] for proper functionality. +Returns the DNS translation of the user's IP address. Proper functionality might require [setting up a proxy |configuration#HTTP Proxy]. -getBasicCredentials(): ?string .[method] ----------------------------------------- -Returns [Basic HTTP authentication |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication] credentials. +getBasicCredentials(): ?array .[method] +--------------------------------------- +Returns authentication credentials for [Basic HTTP authentication |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication]. ```php [$user, $password] = $httpRequest->getBasicCredentials(); @@ -182,21 +177,45 @@ Returns [Basic HTTP authentication |https://developer.mozilla.org/en-US/docs/Web getRawBody(): ?string .[method] ------------------------------- -Returns the body of the HTTP request: +Returns the body of the HTTP request. ```php $body = $httpRequest->getRawBody(); ``` +getOrigin(): ?UrlImmutable .[method] +------------------------------------ +Returns the origin from which the request came. An origin consists of the scheme (protocol), hostname, and port - for example, `https://example.com:8080`. Returns `null` if the origin header is not present or is set to `'null'`. + +```php +$origin = $httpRequest->getOrigin(); +echo $origin; // https://example.com:8080 +echo $origin?->getHost(); // example.com +``` + +The browser sends the `Origin` header in the following cases: +- Cross-origin requests (AJAX calls to a different domain) +- POST, PUT, DELETE, and other modifying requests +- Requests made using the Fetch API + +The browser does NOT send the `Origin` header for: +- Regular GET requests to the same domain (same-origin navigation) +- Direct navigation by typing a URL into the address bar +- Requests from non-browser clients + +.[note] +Unlike the `Referer` header, `Origin` contains only the scheme, host, and port - not the full URL path. This makes it more suitable for security checks while preserving user privacy. The `Origin` header is primarily used for [CORS |nette:glossary#Cross-Origin Resource Sharing (CORS)] (Cross-Origin Resource Sharing) validation. + + detectLanguage(array $langs): ?string .[method] ----------------------------------------------- -Detects language. As a parameter `$lang`, we pass an array of languages ​​that the application supports, and it returns the one preferred by browser. It is not magic, the method just uses the `Accept-Language` header. If no match is reached, it returns `null`. +Detects the language. Pass an array of languages supported by the application as the `$langs` parameter, and it will return the one preferred by the visitor's browser. It's not magic; it just uses the `Accept-Language` header. If no match is found, it returns `null`. ```php -// Header sent by browser: Accept-Language: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3 +// Browser sends e.g., Accept-Language: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3 -$langs = ['hu', 'pl', 'en']; // languages supported in application +$langs = ['hu', 'pl', 'en']; // languages supported by the application echo $httpRequest->detectLanguage($langs); // en ``` @@ -204,41 +223,48 @@ echo $httpRequest->detectLanguage($langs); // en RequestFactory ============== -The object of the current HTTP request is created by [api:Nette\Http\RequestFactory]. If you are writing an application that does not use a DI container, you create a request as follows: +The [api:Nette\Http\RequestFactory] class is used to create an instance of `Nette\Http\Request`, which represents the current HTTP request. (If you are working with Nette, the HTTP request object is automatically created by the framework.) ```php $factory = new Nette\Http\RequestFactory; $httpRequest = $factory->fromGlobals(); ``` -RequestFactory can be configured before calling `fromGlobals()`. We can disable all sanitization of input parameters from invalid UTF-8 sequences using `$factory->setBinary()`. And also set up a proxy server, which is important for the correct detection of the user's IP address using `$factory->setProxy(...)`. +The `fromGlobals()` method creates the request object based on the current PHP global variables (`$_GET`, `$_POST`, `$_COOKIE`, `$_FILES`, and `$_SERVER`). When creating the object, it automatically cleanses all input parameters (GET, POST, COOKIE) as well as the URL from control characters and invalid UTF-8 sequences, ensuring security when working with this data later. + +RequestFactory can be configured before calling `fromGlobals()`: + +- using the `$factory->setBinary()` method disables automatic cleansing of input parameters from control characters and invalid UTF-8 sequences. +- using the `$factory->setProxy(...)` method specifies the IP address of the [proxy server |configuration#HTTP Proxy], which is necessary for correct detection of the user's IP address. -It's possible to clean up URLs from characters that can get into them because of poorly implemented comment systems on various other websites by using filters: +RequestFactory allows defining filters that automatically transform parts of the URL request. These filters remove unwanted characters from URLs that might have been inserted, for example, by incorrect implementations of comment systems on various websites: ```php -// remove spaces from path +// remove spaces from the path $requestFactory->urlFilters['path']['%20'] = ''; -// remove dot, comma or right parenthesis form the end of the URL +// remove dot, comma, or right parenthesis from the end of the URI $requestFactory->urlFilters['url']['[.,)]$'] = ''; -// clean the path from duplicated slashes (default filter) +// clean the path from double slashes (default filter) $requestFactory->urlFilters['path']['/{2,}'] = '/'; ``` +The first key, `'path'` or `'url'`, determines which part of the URL the filter will be applied to. The second key is the regular expression to search for, and the value is the replacement to be used instead of the found text. + Uploaded Files ============== -Method `Nette\Http\Request::getFiles()` return a tree of upload files in a normalized structure, with each leaf an instance of [api:Nette\Http\FileUpload]. These objects encapsulate the data submitted by the `<input type=file>` form element. +The `Nette\Http\Request::getFiles()` method returns an array of all uploads in a normalized structure, where the leaves are [api:Nette\Http\FileUpload] objects. These encapsulate the data submitted by the `<input type=file>` form element. -The structure reflects the naming of elements in HTML. In the simplest example, this might be a single named form element submitted as: +The structure reflects the naming of elements in HTML. In the simplest case, it might be a single named form element submitted as: ```latte <input type="file" name="avatar"> ``` -In this case, the `$request->getFiles()` returns array: +In this case, `$request->getFiles()` returns an array: ```php [ @@ -246,19 +272,19 @@ In this case, the `$request->getFiles()` returns array: ] ``` -The `FileUpload` object is created even if the user did not upload any file or the upload failed. Method `hasFile()` returns true if a file has been sent: +The `FileUpload` object is created even if the user did not upload any file or the upload failed. The `hasFile()` method returns true if a file was sent: ```php -$request->getFile('avatar')->hasFile(); +$request->getFile('avatar')?->hasFile(); ``` -In the case of an input using array notation for the name: +In the case of an element name using array notation: ```latte <input type="file" name="my-form[details][avatar]"> ``` -returned tree ends up looking like this: +the returned tree looks like this: ```php [ @@ -273,10 +299,10 @@ returned tree ends up looking like this: You can also create arrays of files: ```latte -<input type="file" name="my-form[details][avatars][] multiple"> +<input type="file" name="my-form[details][avatars][]" multiple> ``` -In such a case structure looks like: +In such a case, the structure looks like this: ```php [ @@ -292,16 +318,16 @@ In such a case structure looks like: ] ``` -The best way to access index 1 of a nested array is as follows: +The best way to access index 1 of the nested array is as follows: ```php $file = $request->getFile(['my-form', 'details', 'avatars', 1]); -if ($file instanceof FileUpload) { +if ($file instanceof Nette\Http\FileUpload) { // ... } ``` -Because you can't trust data from the outside and therefore don't rely on the form of the structure, this method is safer than `$request->getFiles()['my-form']['details']['avatars'][1]`, which can fail. +Since you cannot trust external data and therefore rely on the structure of the files, this approach is safer than, for example, `$request->getFiles()['my-form']['details']['avatars'][1]`, which might fail. Overview of `FileUpload` Methods .{toc: FileUpload} @@ -310,7 +336,7 @@ Overview of `FileUpload` Methods .{toc: FileUpload} hasFile(): bool .[method] ------------------------- -Returns `true` if the user has uploaded a file. +Returns `true` if the user uploaded a file. isOk(): bool .[method] @@ -320,7 +346,7 @@ Returns `true` if the file was uploaded successfully. getError(): int .[method] ------------------------- -Returns the error code associated with the uploaded file. It is be one of [UPLOAD_ERR_XXX|http://php.net/manual/en/features.file-upload.errors.php] constants. If the file was uploaded successfully, it returns `UPLOAD_ERR_OK`. +Returns the error code associated with the uploaded file. It is one of the [UPLOAD_ERR_XXX |https://php.net/manual/en/features.file-upload.errors.php] constants. If the file was uploaded successfully, it returns `UPLOAD_ERR_OK`. move(string $dest) .[method] @@ -342,7 +368,7 @@ getContentType(): ?string .[method] Detects the MIME content type of the uploaded file based on its signature. If the upload was not successful or the detection failed, it returns `null`. .[caution] -Requires PHP extension `fileinfo`. +Requires the PHP extension `fileinfo`. getUntrustedName(): string .[method] @@ -355,12 +381,23 @@ Do not trust the value returned by this method. A client could send a malicious getSanitizedName(): string .[method] ------------------------------------ -Returns the sanitized file name. It contains only ASCII characters `[a-zA-Z0-9.-]`. If the name does not contain such characters, it returns 'unknown'. If the file is JPEG, PNG, GIF, or WebP image, it returns the correct file extension. +Returns the sanitized file name. It contains only ASCII characters `[a-zA-Z0-9.-]`. If the name does not contain such characters, it returns `'unknown'`. If the file is a JPEG, PNG, GIF, WebP, or AVIF image, it also returns the correct file extension. + +.[caution] +Requires the PHP extension `fileinfo`. + + +getSuggestedExtension(): ?string .[method]{data-version:3.2.4} +-------------------------------------------------------------- +Returns the appropriate file extension (without the dot) corresponding to the detected MIME type. + +.[caution] +Requires the PHP extension `fileinfo`. getUntrustedFullPath(): string .[method] ---------------------------------------- -Returns the original full path as submitted by the browser during directory upload. The full path is only available in PHP 8.1 and above. In previous versions, this method returns the untrusted file name. +Returns the original file path as submitted by the browser during directory upload. The full path is only available in PHP 8.1 and later. In previous versions, this method returns the original file name. .[caution] Do not trust the value returned by this method. A client could send a malicious filename with the intention to corrupt or hack your application. @@ -373,22 +410,27 @@ Returns the size of the uploaded file. If the upload was not successful, it retu getTemporaryFile(): string .[method] ------------------------------------ -Returns the path of the temporary location of the uploaded file. If the upload was not successful, it returns `''`. +Returns the path to the temporary location of the uploaded file. If the upload was not successful, it returns `''`. + + +__toString(): string .[method] +------------------------------ +Returns the path to the temporary location of the uploaded file. This allows the `FileUpload` object to be used directly as a string. isImage(): bool .[method] ------------------------- -Returns `true` if the uploaded file is a JPEG, PNG, GIF, or WebP image. Detection is based on its signature. The integrity of the entire file is not checked. You can find out if an image is not corrupted for example by trying to [load it|#toImage]. +Returns `true` if the uploaded file is a JPEG, PNG, GIF, WebP, or AVIF image. Detection is based on its signature and does not verify the integrity of the entire file. Whether an image is corrupted can be determined, for example, by trying to [load it |#toImage]. .[caution] -Requires PHP extension `fileinfo`. +Requires the PHP extension `fileinfo`. getImageSize(): ?array .[method] -------------------------------- -Returns a pair of `[width, height]` with dimensions of the uploaded image. If the upload was not successful or is not a valid image, it returns `null`. +Returns a pair `[width, height]` with the dimensions of the uploaded image. If the upload was not successful or it is not a valid image, it returns `null`. toImage(): Nette\Utils\Image .[method] -------------------------------------- -Loads an image as an [Image|utils:images] object. If the upload was not successful or is not a valid image, it throws an `Nette\Utils\ImageException` exception. +Loads the image as an [Image |utils:images] object. If the upload was not successful or it is not a valid image, it throws an `Nette\Utils\ImageException`. diff --git a/http/en/response.texy b/http/en/response.texy index 58d61af63e..7dbd4dca00 100644 --- a/http/en/response.texy +++ b/http/en/response.texy @@ -2,9 +2,9 @@ HTTP Response ************* .[perex] -Nette encapsulates the HTTP response into objects with an understandable API while providing a sanitization filter. +Nette encapsulates the HTTP response into objects with a clear API. -An HTTP response is an [api:Nette\Http\Response] object, which you get by passing it using [dependency injection |dependency-injection:passing-dependencies]. In presenters simply call `$httpResponse = $this->getHttpResponse()`. +The HTTP response is represented by the [api:Nette\Http\Response] object. If you are working with Nette, this object is automatically created by the framework, and you can have it passed to you using [dependency injection |dependency-injection:passing-dependencies]. In presenters, simply call the `$this->getHttpResponse()` method. → [Installation and requirements |@home#Installation] @@ -12,12 +12,12 @@ An HTTP response is an [api:Nette\Http\Response] object, which you get by passin Nette\Http\Response =================== -Unlike [Nette\Http\Request|request], this object is mutable, so you can use setters to change the state, ie to send headers. Remember that all setters **must be called before any actual output is sent.** The `isSent()` method tells if output have been sent. If it returns `true`, each attempt to send a header throws an `Nette\InvalidStateException` exception. +Unlike [Nette\Http\Request |request], this object is mutable, so you can use setters to change the state, e.g., to send headers. Remember that all setters **must be called before any actual output is sent.** The `isSent()` method indicates if the output has already been sent. If it returns `true`, any attempt to send a header will throw an `Nette\InvalidStateException`. -setCode(int $code, string $reason=null) .[method] -------------------------------------------------- -Changes a status [response code |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10]. For better source code readability it is recommended to use [predefined constants |api:Nette\Http\IResponse] instead of actual numbers. +setCode(int $code, ?string $reason=null) .[method] +-------------------------------------------------- +Changes the status [response code |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10]. For better source code readability, it is recommended to use [predefined constants |api:Nette\Http\IResponse] instead of actual numbers. ```php $httpResponse->setCode(Nette\Http\Response::S404_NotFound); @@ -31,12 +31,12 @@ Returns the status code of the response. isSent(): bool .[method] ------------------------ -Returns whether headers have already been sent from the server to the browser, so it is no longer possible to send headers or change the status code. +Returns whether headers have already been sent from the server to the browser, meaning it is no longer possible to send headers or change the status code. -setHeader(string $name, string $value) .[method] ------------------------------------------------- -Sends an HTTP header and **overwrites** previously sent header of the same name. +setHeader(string $name, ?string $value) .[method] +------------------------------------------------- +Sends an HTTP header and **overwrites** a previously sent header of the same name. If `$value` is `null`, the header will be removed. ```php $httpResponse->setHeader('Pragma', 'no-cache'); @@ -45,7 +45,7 @@ $httpResponse->setHeader('Pragma', 'no-cache'); addHeader(string $name, string $value) .[method] ------------------------------------------------ -Sends an HTTP header and **doesn't overwrite** previously sent header of the same name. +Sends an HTTP header and **does not overwrite** a previously sent header of the same name. ```php $httpResponse->addHeader('Accept', 'application/json'); @@ -60,7 +60,7 @@ Deletes a previously sent HTTP header. getHeader(string $header): ?string .[method] -------------------------------------------- -Returns the sent HTTP header, or `null` if it does not exist. The parameter is case-insensitive. +Returns the sent HTTP header or `null` if it doesn't exist. The parameter is case-insensitive. ```php $pragma = $httpResponse->getHeader('Pragma'); @@ -69,7 +69,7 @@ $pragma = $httpResponse->getHeader('Pragma'); getHeaders(): array .[method] ----------------------------- -Returns all sent HTTP headers as associative array. +Returns all sent HTTP headers as an associative array. ```php $headers = $httpResponse->getHeaders(); @@ -77,9 +77,9 @@ echo $headers['Pragma']; ``` -setContentType(string $type, string $charset=null) .[method] ------------------------------------------------------------- -Sends the header `Content-Type`. +setContentType(string $type, ?string $charset=null) .[method] +------------------------------------------------------------- +Changes the `Content-Type` header. ```php $httpResponse->setContentType('text/plain', 'UTF-8'); @@ -88,7 +88,7 @@ $httpResponse->setContentType('text/plain', 'UTF-8'); redirect(string $url, int $code=self::S302_Found): void .[method] ----------------------------------------------------------------- -Redirects to another URL. Don't forget to quit the script then. +Redirects to another URL. Remember to terminate the script afterwards. ```php $httpResponse->redirect('http://example.com'); @@ -108,42 +108,42 @@ $httpResponse->setExpiration('1 hour'); sendAsFile(string $fileName) .[method] -------------------------------------- -Response should be downloaded with *Save as* dialog with specified name. It does not send any file itself to output. +The response will be downloaded via a *Save as* dialog box with the specified name. It does not send the file itself. ```php $httpResponse->sendAsFile('invoice.pdf'); ``` -setCookie(string $name, string $value, $time, string $path=null, string $domain=null, bool $secure=null, bool $httpOnly=null, string $sameSite=null) .[method] --------------------------------------------------------------------------------------------------------------------------------------------------------------- +setCookie(string $name, string $value, $time, ?string $path=null, ?string $domain=null, ?bool $secure=null, ?bool $httpOnly=null, ?string $sameSite='Lax') .[method] +-------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sends a cookie. Default parameter values: -| `$path` | `'/'` | with scope to all paths on (sub)domain *(configurable)* -| `$domain` | `null` | with scope of the current (sub)domain, but not its subdomains *(configurable)* +| `$path` | `'/'` | cookie is available for all paths within the (sub)domain *(configurable)* +| `$domain` | `null` | meaning available for the current (sub)domain, but not its subdomains *(configurable)* | `$secure` | `true` | if the site is running on HTTPS, otherwise `false` *(configurable)* | `$httpOnly` | `true` | cookie is inaccessible to JavaScript -| `$sameSite` | `'Lax'` | cookie does not have to be sent when [accessed from another origin|nette:glossary#SameSite cookie] +| `$sameSite` | `'Lax'` | cookie might not be sent during [cross-origin access |nette:glossary#SameSite cookie] -You can change the default values of the `$path`, `$domain` and `$secure` parameters in [configuration#HTTP cookie]. +You can change the default values of the `$path`, `$domain`, and `$secure` parameters in the [configuration |configuration#HTTP Cookie]. -The time can be specified as number of seconds or a string: +The time can be specified as a number of seconds or a string: ```php $httpResponse->setCookie('lang', 'en', '100 days'); ``` -The `$domain` option determines which domains (origins) can accept cookies. If not specified, the cookie is accepted by the same (sub)domain as is set by it, excluding their subdomains. If `$domain` is specified, then subdomains are also included. Therefore, specifying `$domain` is less restrictive than omitting. For example, if `$domain = 'nette.org'`, cookie is also available on all subdomains like `doc.nette.org`. +The `$domain` parameter determines which domains can accept the cookie. If not specified, the cookie is accepted by the same (sub)domain that set it, but not its subdomains. If `$domain` is specified, subdomains are also included. Therefore, specifying `$domain` is less restrictive than omitting it. For example, with `$domain = 'nette.org'`, cookies are also available on all subdomains like `doc.nette.org`. -You can use the `Response::SameSiteLax`, `SameSiteStrict` and `SameSiteNone` constants for the `$sameSite` value. +You can use the constants `Response::SameSiteLax`, `Response::SameSiteStrict`, and `Response::SameSiteNone` for the `$sameSite` value. -deleteCookie(string $name, string $path=null, string $domain=null, bool $secure=null): void .[method] ------------------------------------------------------------------------------------------------------ -Deletes a cookie. The default values ​​of the parameters are: +deleteCookie(string $name, ?string $path=null, ?string $domain=null, ?bool $secure=null): void .[method] +-------------------------------------------------------------------------------------------------------- +Deletes a cookie. The default values of the parameters are: - `$path` with scope to all directories (`'/'`) -- `$domain` with scope of the current (sub)domain, but not its subdomains -- `$secure` is affected by the settings in [configuration#HTTP cookie] +- `$domain` with scope to the current (sub)domain, but not its subdomains +- `$secure` depends on the settings in the [configuration |configuration#HTTP Cookie] ```php $httpResponse->deleteCookie('lang'); diff --git a/http/en/sessions.texy b/http/en/sessions.texy index 697105fc43..f0831458d5 100644 --- a/http/en/sessions.texy +++ b/http/en/sessions.texy @@ -3,19 +3,19 @@ Sessions <div class=perex> -HTTP is a stateless protocol, but almost every application needs to keep state between requests, e.g. content of a shopping cart. This is what a session is used for. Let's see +HTTP is a stateless protocol; however, almost every application needs to maintain state between requests, such as the content of a shopping cart. This is precisely what sessions are used for. We will show: - how to use sessions -- how to avoid naming conflicts +- how to prevent naming conflicts - how to set expiration </div> -When using sessions, each user receives a unique identifier called session ID, which is passed in a cookie. This serves as the key to the session data. Unlike cookies, which are stored on the browser side, session data is stored on the server side. +When using sessions, each user receives a unique identifier called a session ID, which is passed in a cookie. This serves as a key to the session data. Unlike cookies, which are stored on the browser side, session data is stored on the server side. -We configure session in [configuration |configuration#session], the choice of expiration time is important. +We configure sessions in the [configuration |configuration#Session]; the choice of expiration time is particularly important. -The session is managed by the [api:Nette\Http\Session] object, which you get by passing it using [dependency injection |dependency-injection:passing-dependencies]. In presenters simply call `$session = $this->getSession()`. +Session management is handled by the [api:Nette\Http\Session] object, which you can access by having it passed via [dependency injection |dependency-injection:passing-dependencies]. In presenters, simply call `$session = $this->getSession()`. → [Installation and requirements |@home#Installation] @@ -23,49 +23,49 @@ The session is managed by the [api:Nette\Http\Session] object, which you get by Starting Session ================ -By default, Nette will start a session automatically the moment we start reading from it or writing data to it. To manually start a session, use `$session->start()`. +By default, Nette automatically starts a session the moment we begin reading from or writing data to it. To start a session manually, use `$session->start()`. -PHP sends HTTP headers affecting caching when starting the session, see [php:session_cache_limiter], and possibly a cookie with the session ID. Therefore, it is always necessary to start the session before sending any output to the browser, otherwise an exception will be thrown. So if you know that a session will be used during page rendering, start it manually before, for example in the presenter. +PHP sends HTTP headers affecting caching when starting the session (see [php:session_cache_limiter]), and possibly a cookie with the session ID. Therefore, it is always necessary to start the session before sending any output to the browser; otherwise, an exception will be thrown. So, if you know that a session will be used during page rendering, start it manually beforehand, for example, in the presenter. -In developer mode, Tracy starts the session because it uses it to display redirection and AJAX requests bars in the Tracy Bar. +In developer mode, Tracy starts the session because it uses it to display bars for redirects and AJAX requests in the Tracy Bar. -Section -======= +Sections +======== -In pure PHP, the session data store is implemented as an array accessible via a global variable `$_SESSION`. The problem is that applications normally consist of a number of independent parts, and if all have only one same array available, sooner or later a name collision will occur. +In pure PHP, the session data storage is implemented as an array accessible via the global variable `$_SESSION`. The problem is that applications typically consist of many independent parts, and if all of them have only one array available, sooner or later a name collision will occur. -Nette Framework solves the problem by dividing the entire space into sections (objects [api:Nette\Http\SessionSection]). Each unit then uses its own section with a unique name and no collisions can occur. +Nette Framework solves this problem by dividing the entire space into sections (objects of [api:Nette\Http\SessionSection]). Each unit then uses its own section with a unique name, and no collision can occur. -We get the section from the session manager: +We obtain a section from the session: ```php $section = $session->getSection('unique name'); ``` -In the presenter it is enough to call `getSession()` with the parameter: +In the presenter, just use `getSession()` with a parameter: ```php -// $this is Presenter +// $this is a Presenter $section = $this->getSession('unique name'); ``` -The existence of the section can be checked by the method `$session->hasSection('unique name')`. +The existence of a section can be checked using the `$session->hasSection('unique name')` method. -The section itself is very easy to work with using the `set()`, `get()` and `remove()` methods: +Working with the section itself is then very easy using the `set()`, `get()`, and `remove()` methods: ```php -// variable writing -$section->set('userName', 'franta'); +// writing a variable +$section->set('userName', 'john'); -// reading a variable, returns null if it does not exist +// reading a variable, returns null if it doesn't exist echo $section->get('userName'); -// variable removing +// removing a variable $section->remove('userName'); ``` -It's possible to use `foreach` cycle to obtain all variables from section: +To get all variables from a section, you can use a `foreach` loop: ```php foreach ($section as $key => $val) { @@ -77,14 +77,14 @@ foreach ($section as $key => $val) { How to Set Expiration --------------------- -Expiration can be set for individual sections or even individual variables. We can let the user's login expire in 20 minutes, but still remember the contents of a shopping cart. +Expiration can be set for individual sections or even individual variables. We can let a user's login expire after 20 minutes, while still remembering the contents of the shopping cart. ```php -// section will expire after 20 minutes +// section expires after 20 minutes $section->setExpiration('20 minutes'); ``` -The third parameter of the `set()` method is used to set the expiration of individual variables: +To set the expiration for individual variables, use the third parameter of the `set()` method: ```php // variable 'flash' expires after 30 seconds @@ -92,15 +92,15 @@ $section->set('flash', $message, '30 seconds'); ``` .[note] -Remember that the expiration time of the whole session (see [session configuration |configuration#session]) must be equal to or higher than the time set for individual sections or variables. +Remember that the expiration time of the entire session (see [session configuration |configuration#Session]) must be equal to or greater than the time set for individual sections or variables. -The cancellation of the previously set expiration can be achieved by the method `removeExpiration()`. Immediate deletion of the whole section will be ensured by the method `remove()`. +To cancel a previously set expiration, use the `removeExpiration()` method. To immediately remove the entire section, use the `remove()` method. Events $onStart, $onBeforeWrite ------------------------------- -Object `Nette\Http\Session` has [events|nette:glossary#Events] `$onStart` a `$onBeforeWrite`, so you can add callbacks that are called after the session starts or before it is written to disk and then terminated. +The `Nette\Http\Session` object has [events |nette:glossary#Events] `$onStart` and `$onBeforeWrite`, so you can add callbacks that are invoked after the session starts or before it is written to disk and subsequently terminated. ```php $session->onBeforeWrite[] = function () { @@ -120,7 +120,7 @@ Overview of methods of the `Nette\Http\Session` class for session management: start(): void .[method] ----------------------- -Starts a session. +Starts the session. isStarted(): bool .[method] @@ -130,7 +130,7 @@ Is the session started? close(): void .[method] ----------------------- -Ends the session. The session ends automatically at the end of the script. +Ends the session. The session automatically ends at the end of the script execution. destroy(): void .[method] @@ -145,7 +145,7 @@ Does the HTTP request contain a cookie with a session ID? regenerateId(): void .[method] ------------------------------ -Generates a new random session ID. Data remain unchanged. +Generates a new random session ID. Data remains preserved. getId(): string .[method] @@ -158,34 +158,34 @@ Returns the session ID. Configuration ------------- -We configure session in [configuration |configuration#session]. If you are writing an application that does not use a DI container, use these methods to configure it. They must be called before starting the session. +We configure the session in the [configuration |configuration#Session]. If you are writing an application that does not use a DI container, use these methods for configuration. They must be called before starting the session. <div class=wiki-methods-brief> setName(string $name): static .[method] --------------------------------------- -Sets the name of the cookie which is used to transmit the session ID. The default name is `PHPSESSID`. This is useful if you run several different applications on the same site. +Sets the name of the cookie in which the session ID is transmitted. The standard name is `PHPSESSID`. This is useful if you run several different applications on the same website. getName(): string .[method] --------------------------- -Returns the name of session cookie. +Returns the name of the cookie in which the session ID is transmitted. setOptions(array $options): static .[method] -------------------------------------------- -Configures the session. It is possible to set all PHP [session directives |https://www.php.net/manual/en/session.configuration.php] (in camelCase format, eg write `savePath` instead of `session.save_path`) and also [readAndClose|https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. +Configures the session. It is possible to set all PHP [session directives |https://www.php.net/manual/en/session.configuration.php] (in camelCase format, e.g., write `savePath` instead of `session.save_path`) and also [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. setExpiration(?string $time): static .[method] ---------------------------------------------- -Sets the time of inactivity after which the session expires. +Sets the inactivity time after which the session expires. -setCookieParameters(string $path, string $domain=null, bool $secure=null, string $samesite=null): static .[method] ------------------------------------------------------------------------------------------------------------------- -Sets parameters for cookies. You can change the default parameter values in [configuration#Session cookie]. +setCookieParameters(string $path, ?string $domain=null, ?bool $secure=null, ?string $samesite=null): static .[method] +--------------------------------------------------------------------------------------------------------------------- +Sets parameters for cookies. You can change the default parameter values in the [configuration |configuration#Session Cookie]. setSavePath(string $path): static .[method] @@ -195,7 +195,7 @@ Sets the directory where session files are stored. setHandler(\SessionHandlerInterface $handler): static .[method] --------------------------------------------------------------- -Sets custom handler, see [PHP documentation |https://www.php.net/manual/en/class.sessionhandlerinterface.php]. +Sets a custom handler, see the [PHP documentation |https://www.php.net/manual/en/class.sessionhandlerinterface.php]. </div> @@ -203,8 +203,9 @@ Sets custom handler, see [PHP documentation |https://www.php.net/manual/en/class Safety First ============ -The server assumes that it communicates with the same user as long as requests contain the same session ID. The task of security mechanisms is to ensure that this behavior really works and that there is no possibility to substitute or steal an identifier. +The server assumes that it communicates with the same user as long as requests are accompanied by the same session ID. The task of security mechanisms is to ensure that this is indeed the case and that the identifier cannot be stolen or substituted. -That's why Nette Framework properly configures PHP directives to transfer session ID only in cookies, to avoid access from JavaScript and to ignore the identifiers in the URL. Moreover in critical moments, such as user login, it generates a new Session ID. +Nette Framework therefore correctly configures PHP directives to transfer the session ID only in cookies, make it inaccessible to JavaScript, and ignore any identifiers in the URL. Moreover, at critical moments, such as user login, it generates a new session ID. -Function ini_set is used for configuring PHP, but unfortunately, its use is prohibited at some web hosting services. If it's your case, try to ask your hosting provider to allow this function for you, or at least to configure his server properly. .[note] +.[note] +The `ini_set` function is used for configuring PHP, but unfortunately, some hosting providers prohibit its use. If this is the case with your host, try to arrange with them to allow this function for you or at least configure the server properly. diff --git a/http/en/urls.texy b/http/en/urls.texy index 71385836a8..608fa0064f 100644 --- a/http/en/urls.texy +++ b/http/en/urls.texy @@ -1,8 +1,8 @@ -URL Parser and Builder -********************** +Working with URLs +***************** .[perex] -The [#Url], [#UrlImmutable], and [#UrlScript] classes make it easy to manage, parse, and manipulate URLs. +The [#Url], [#UrlImmutable], and [#UrlScript] classes make it easy to generate, parse, and manipulate URLs. → [Installation and requirements |@home#Installation] @@ -10,7 +10,7 @@ The [#Url], [#UrlImmutable], and [#UrlScript] classes make it easy to manage, pa Url === -The [api:Nette\Http\Url] class makes it easy to work with the URL and its individual components, which are outlined in this diagram: +The [api:Nette\Http\Url] class allows easy manipulation of URLs and their individual components, as depicted in this diagram: /--pre scheme user password host port path query fragment @@ -22,7 +22,7 @@ The [api:Nette\Http\Url] class makes it easy to work with the URL and its indivi hostUrl authority \-- -URL generation is intuitive: +Generating URLs is intuitive: ```php use Nette\Http\Url; @@ -36,7 +36,7 @@ $url->setScheme('https') echo $url; // 'https://localhost/edit?foo=bar' ``` -You can also parse the URL and then manipulate it: +You can also parse a URL and then manipulate it: ```php $url = new Url( @@ -44,7 +44,18 @@ $url = new Url( ); ``` -The following methods are available to get or change individual URL components: +The `Url` class implements the `JsonSerializable` interface and has a `__toString()` method, so the object can be printed or used in data passed to `json_encode()`. + +```php +echo $url; +echo json_encode([$url]); +``` + + +URL Components .[method] +------------------------ + +The following methods are available to retrieve or modify individual URL components: .[language-php] | Setter | Getter | Returned value @@ -58,19 +69,25 @@ The following methods are available to get or change individual URL components: | `setPath(string $path)` | `getPath(): string` | `'/en/download'` | `setQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` | `setFragment(string $fragment)` | `getFragment(): string` | `'footer'` -| | `getAuthority(): string` | `'nette.org:8080'` -| | `getHostUrl(): string` | `'http://nette.org:8080'` -| | `getAbsoluteUrl(): string` | full URL +| | `getAuthority(): string` | `'john:xyz*12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | entire URL + +Warning: When working with a URL obtained from an [HTTP request |request], keep in mind that it will not contain the fragment, as the browser does not send it to the server. -We can also operate with individual query parameters using: +We can also work with individual query parameters using: .[language-php] | Setter | Getter |--------------------------------------------------- | `setQuery(string\|array $query)` | `getQueryParameters(): array` | `setQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` +| `appendQuery(string|array $query)` | + -Method `getDomain(int $level = 2)` returns the right or left part of the host. This is how it works if the host is `www.nette.org`: +getDomain(int $level = 2): string .[method] +------------------------------------------- +Returns the right or left part of the host. Here's how it works if the host is `www.nette.org`: .[language-php] | `getDomain(1)` | `'org'` @@ -82,24 +99,45 @@ Method `getDomain(int $level = 2)` returns the right or left part of the host. T | `getDomain(-3)` | `''` -The `Url` class implements the `JsonSerializable` interface and has a `__toString()` method so that the object can be printed or used in data passed to `json_encode()`. +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +Checks if two URLs are identical. ```php -echo $url; -echo json_encode([$url]); +$url->isEqual('https://nette.org'); ``` -Method `isEqual(string|Url $anotherUrl): bool` tests whether the two URLs are identical. + +canonicalize() .[method] +------------------------ +Converts the URL to canonical form. This includes, for example, sorting the parameters in the query string alphabetically, converting the hostname to lowercase, and removing redundant characters. + + +Url::isAbsolute(string $url): bool .[method]{data-version:3.3.2} +---------------------------------------------------------------- +Checks if a URL is absolute. A URL is considered absolute if it begins with a scheme (e.g., http, https, ftp) followed by a colon. ```php -$url->isEqual('https://nette.org'); +Url::isAbsolute('https://nette.org'); // true +Url::isAbsolute('//nette.org'); // false +``` + + +Url::removeDotSegments(string $path): string .[method]{data-version:3.3.2} +-------------------------------------------------------------------------- +Normalizes a URL path by removing special segments `.` and `..`. This method removes redundant path elements in the same way web browsers do. + +```php +Url::removeDotSegments('/path/../subtree/./file.txt'); // '/subtree/file.txt' +Url::removeDotSegments('/../foo/./bar'); // '/foo/bar' +Url::removeDotSegments('./today/../file.txt'); // 'file.txt' ``` UrlImmutable ============ -The class [api:Nette\Http\UrlImmutable] is an immutable alternative to class `Url` (just as in PHP `DateTimeImmutable` is immutable alternative to `DateTime`). Instead of setters, it has so-called withers, which do not change the object, but return new instances with a modified value: +The [api:Nette\Http\UrlImmutable] class is an immutable alternative to the [#Url] class (similar to how `DateTimeImmutable` is an immutable alternative to `DateTime` in PHP). Instead of setters, it has "withers," which do not change the object but return new instances with the modified value: ```php use Nette\Http\UrlImmutable; @@ -111,12 +149,23 @@ $url = new UrlImmutable( $newUrl = $url ->withUser('') ->withPassword('') - ->withPath('/cs/'); + ->withPath('/en/'); + +echo $newUrl; // 'http://john:xyz%2A12@nette.org:8080/en/?name=param#footer' +``` + +The `UrlImmutable` class implements the `JsonSerializable` interface and has a `__toString()` method, so the object can be printed or used in data passed to `json_encode()`. -echo $newUrl; // 'http://nette.org:8080/cs/?name=param#footer' +```php +echo $url; +echo json_encode([$url]); ``` -The following methods are available to get or change individual URL components: + +URL Components .[method] +------------------------ + +The following methods are available to retrieve or change individual URL components: .[language-php] | Wither | Getter | Returned value @@ -130,11 +179,13 @@ The following methods are available to get or change individual URL components: | `withPath(string $path)` | `getPath(): string` | `'/en/download'` | `withQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` | `withFragment(string $fragment)` | `getFragment(): string` | `'footer'` -| | `getAuthority(): string` | `'nette.org:8080'` -| | `getHostUrl(): string` | `'http://nette.org:8080'` -| | `getAbsoluteUrl(): string` | full URL +| | `getAuthority(): string` | `'john:xyz*12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | entire URL -We can also operate with individual query parameters using: +The `withoutUserInfo()` method removes `user` and `password`. + +We can also work with individual query parameters using: .[language-php] | Wither | Getter @@ -142,22 +193,52 @@ We can also operate with individual query parameters using: | `withQuery(string\|array $query)` | `getQueryParameters(): array` | `withQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` -The `getDomain(int $level = 2)` method works the same as the method in `Url`. Method `withoutUserInfo()` removes `user` and `password`. -The `UrlImmutable` class implements the `JsonSerializable` interface and has a `__toString()` method so that the object can be printed or used in data passed to `json_encode()`. +getDomain(int $level = 2): string .[method] +------------------------------------------- +Returns the right or left part of the host. Here's how it works if the host is `www.nette.org`: + +.[language-php] +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `''` + + +resolve(string $reference): UrlImmutable .[method]{data-version:3.3.2} +---------------------------------------------------------------------- +Resolves an absolute URL in the same way a browser processes links on an HTML page: +- if the link is an absolute URL (contains a scheme), it is used unchanged +- if the link begins with `//`, only the scheme from the current URL is adopted +- if the link begins with `/`, an absolute path from the domain root is created +- in other cases, the URL is constructed relative to the current path ```php -echo $url; -echo json_encode([$url]); +$url = new UrlImmutable('https://example.com/path/page'); +echo $url->resolve('../foo'); // 'https://example.com/foo' +echo $url->resolve('/bar'); // 'https://example.com/bar' +echo $url->resolve('sub/page.html'); // 'https://example.com/path/sub/page.html' ``` -Method `isEqual(string|Url $anotherUrl): bool` tests whether the two URLs are identical. + +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +Checks if two URLs are identical. + +```php +$url->isEqual('https://nette.org'); +``` UrlScript ========= -The [api:Nette\Http\UrlScript] class is a descendant of `UrlImmutable` and additionally distinguishes these logical parts of the URL: +The [api:Nette\Http\UrlScript] class is a descendant of [#UrlImmutable] and extends it with additional virtual URL components, such as the project's root directory, etc. Like its parent class, it is an immutable object. + +The following diagram shows the components that UrlScript recognizes: /--pre baseUrl basePath relativePath relativeUrl @@ -169,7 +250,14 @@ The [api:Nette\Http\UrlScript] class is a descendant of `UrlImmutable` and addit scriptPath pathInfo \-- -The following methods are available to get these parts: +- `baseUrl` is the base URL of the application, including the domain and the path part to the application's root directory +- `basePath` is the path part to the application's root directory +- `scriptPath` is the path to the current script +- `relativePath` is the script name (and possibly additional path segments) relative to `basePath` +- `relativeUrl` is the entire part of the URL after `baseUrl`, including the query string and fragment +- `pathInfo` is a now rarely used part of the URL after the script name + +The following methods are available to retrieve these parts of the URL: .[language-php] | Getter | Returned value @@ -181,5 +269,4 @@ The following methods are available to get these parts: | `getRelativeUrl(): string` | `'script.php/pathinfo/?name=param#footer'` | `getPathInfo(): string` | `'/pathinfo/'` - -We do not create objects `UrlScript` directly, but the method [Nette\Http\Request::getUrl() |request] returns it. +We usually do not create `UrlScript` objects directly; instead, the [Nette\Http\Request::getUrl() |request] method returns it with the components already correctly set for the current HTTP request. diff --git a/http/es/@home.texy b/http/es/@home.texy index b959950092..99ecbd441a 100644 --- a/http/es/@home.texy +++ b/http/es/@home.texy @@ -2,13 +2,13 @@ Nette HTTP ********** .[perex] -El paquete `nette/http` encapsula [peticiones |request] y [respuestas |response] [HTTP |request], trabaja con [sesiones |sessions] y [analiza y construye URLs |urls]. +El paquete `nette/http` encapsula la [petición HTTP|request] y la [respuesta HTTP|response], el trabajo con [sesiones|sessions] y el [análisis y composición de URL |urls]. -Instalación .[#toc-installation] --------------------------------- +Instalación +----------- -Descargue e instale el paquete utilizando [Composer |best-practices:composer]: +Puede descargar e instalar la librería usando [Composer|best-practices:composer]: ```shell composer require nette/http diff --git a/http/es/@left-menu.texy b/http/es/@left-menu.texy index 3ac366f9c8..4daffef2a0 100644 --- a/http/es/@left-menu.texy +++ b/http/es/@left-menu.texy @@ -1,8 +1,8 @@ -Redes HTTP +Nette HTTP ********** -- [Panorama general |@home] -- [Solicitud HTTP|request] +- [Introducción |@home] +- [Petición HTTP|request] - [Respuesta HTTP|response] -- [Sesiones |Sessions] -- [Utilidad URL |urls] -- [Configuración |Configuration] +- [Sesiones|Sessions] +- [Utilidades de URL |urls] +- [Configuración |configuration] diff --git a/http/es/@meta.texy b/http/es/@meta.texy new file mode 100644 index 0000000000..1670b124ad --- /dev/null +++ b/http/es/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Documentación}} diff --git a/http/es/configuration.texy b/http/es/configuration.texy index 3d0ff41520..c283068e79 100644 --- a/http/es/configuration.texy +++ b/http/es/configuration.texy @@ -1,57 +1,57 @@ -Configuración de HTTP -********************* +Configuración HTTP +****************** .[perex] -Resumen de las opciones de configuración del HTTP de Nette. +Resumen de las opciones de configuración para Nette HTTP. -Si no está utilizando todo el framework, sino sólo esta librería, lea [cómo cargar la configuración |bootstrap:]. +Si no utiliza todo el framework, sino solo esta librería, lea [cómo cargar la configuración|bootstrap:]. -Cabeceras HTTP .[#toc-http-headers] -=================================== +Cabeceras HTTP +============== ```neon http: - # cabeceras que se envían con cada solicitud + # cabeceras que se envían con cada petición headers: X-Powered-By: MyCMS X-Content-Type-Options: nosniff X-XSS-Protection: '1; mode=block' # afecta a la cabecera X-Frame-Options - frames: ... # (string|bool) defaults to 'SAMEORIGIN' + frames: ... # (string|bool) el valor por defecto es 'SAMEORIGIN' ``` -Por razones de seguridad, el framework envía una cabecera `X-Frame-Options: SAMEORIGIN`, que dice que una página puede mostrarse dentro de otra (en el elemento `<iframe>`) sólo si está en el mismo dominio. Esto puede resultar indeseado en determinadas situaciones (por ejemplo, si se está desarrollando una aplicación para Facebook), por lo que el comportamiento puede modificarse configurando los marcos `frames: http://allowed-host.com`. +El framework, por razones de seguridad, envía la cabecera `X-Frame-Options: SAMEORIGIN`, que indica que la página solo se puede mostrar dentro de otra página (en un elemento `<iframe>`) si se encuentra en el mismo dominio. Esto puede ser no deseado en algunas situaciones (por ejemplo, si está desarrollando una aplicación para Facebook), por lo que el comportamiento se puede cambiar estableciendo `frames: http://allowed-host.com` o `frames: true`. -Política de seguridad de contenidos .[#toc-content-security-policy] -------------------------------------------------------------------- +Content Security Policy +----------------------- -Las cabeceras `Content-Security-Policy` (en lo sucesivo CSP) pueden ensamblarse fácilmente, su descripción puede encontrarse en la [descripción CSP |https://content-security-policy.com]. Las directivas CSP (como `script-src`) pueden escribirse como cadenas de acuerdo con la especificación o como matrices de valores para una mejor legibilidad. Entonces no es necesario escribir comillas alrededor de palabras clave como `'self'`. Nette también generará automáticamente un valor de `nonce`, por lo que `'nonce-y4PopTLM=='` se enviará en la cabecera. +Se pueden construir fácilmente las cabeceras `Content-Security-Policy` (en adelante CSP), cuya descripción encontrará en la [descripción de CSP |https://content-security-policy.com]. Las directivas CSP (como `script-src`) pueden escribirse como cadenas según la especificación, o como un array de valores para una mejor legibilidad. Entonces no es necesario escribir comillas alrededor de palabras clave como `'self'`. Nette también genera automáticamente el valor `nonce`, por lo que en la cabecera habrá, por ejemplo, `'nonce-y4PopTLM=='`. ```neon http: # Content Security Policy csp: - # cadena según especificación CSP + # cadena en formato según la especificación CSP default-src: "'self' https://example.com" - # array of values + # array de valores script-src: - nonce - strict-dynamic - self - https://example.com - # bool en el caso de los interruptores + # bool en caso de interruptores upgrade-insecure-requests: true block-all-mixed-content: false ``` -Utilice `<script n:nonce>...</script>` en las plantillas y el valor nonce se rellenará automáticamente. Hacer sitios web seguros en Nette es realmente fácil. +En las plantillas, use `<script n:nonce>...</script>` y el valor nonce se completará automáticamente. Hacer sitios web seguros en Nette es realmente fácil. -Del mismo modo, se pueden añadir las cabeceras `Content-Security-Policy-Report-Only` (que se puede utilizar en paralelo con CSP) y [Feature Policy |https://developers.google.com/web/updates/2018/06/feature-policy]: +De manera similar, se pueden construir las cabeceras `Content-Security-Policy-Report-Only` (que se pueden usar simultáneamente con CSP) y [Feature Policy|https://developers.google.com/web/updates/2018/06/feature-policy]: ```neon http: @@ -69,91 +69,103 @@ http: ``` -Cookie HTTP .[#toc-http-cookie] -------------------------------- +Cookie HTTP +----------- -Puede cambiar los valores por defecto de algunos parámetros de los métodos [Nette\Http\Response::setCookie() |response#setCookie] y session. +Se pueden cambiar los valores predeterminados de algunos parámetros del método [Nette\Http\Response::setCookie() |response#setCookie] y de la sesión. ```neon http: - # cookie scope by path - cookiePath: ... # (string) por defecto '/' + # alcance de la cookie por ruta + cookiePath: ... # (string) el valor por defecto es '/' - # qué hosts pueden recibir la cookie - cookieDomain: 'example.com' # (string|dominio) por defecto unset + # dominios que aceptan la cookie + cookieDomain: 'example.com' # (string|domain) el valor por defecto es no establecido - # ¿enviar cookies sólo a través de HTTPS? - cookieSecure: ... # (bool|auto) por defecto auto + # ¿enviar cookie solo a través de HTTPS? + cookieSecure: ... # (bool|auto) el valor por defecto es auto - # deshabilita el envío de la cookie que Nette usa como protección contra CSRF - disableNetteCookie: ... # (bool) por defecto false + # desactiva el envío de la cookie que Nette usa como protección contra CSRF + disableNetteCookie: ... # (bool) el valor por defecto es false ``` -La opción `cookieDomain` determina qué dominios (orígenes) pueden aceptar cookies. Si no se especifica, la cookie es aceptada por el mismo (sub)dominio que la establece, *excluyendo* sus subdominios. Si se especifica `cookieDomain`, también se incluyen los subdominios. Por lo tanto, especificar `cookieDomain` es menos restrictivo que omitirlo. +El atributo `cookieDomain` determina qué dominios pueden aceptar la cookie. Si no se especifica, la cookie es aceptada por el mismo (sub)dominio que la estableció, *pero no* por sus subdominios. Si se especifica `cookieDomain`, también se incluyen los subdominios. Por lo tanto, especificar `cookieDomain` es menos restrictivo que omitirlo. -Por ejemplo, si se establece `cookieDomain: nette.org`, la cookie también está disponible en todos los subdominios como `doc.nette.org`. Esto también se puede conseguir con el valor especial `domain`, es decir, `cookieDomain: domain`. +Por ejemplo, con `cookieDomain: nette.org`, las cookies también están disponibles en todos los subdominios como `doc.nette.org`. Lo mismo se puede lograr también con el valor especial `domain`, es decir, `cookieDomain: domain`. -El valor por defecto de `cookieSecure` es `auto`, lo que significa que si el sitio web funciona con HTTPS, las cookies se enviarán con la bandera `Secure` y, por lo tanto, sólo estarán disponibles a través de HTTPS. +El valor predeterminado `auto` para el atributo `cookieSecure` significa que si el sitio web se ejecuta en HTTPS, las cookies se enviarán con el indicador `Secure` y, por lo tanto, solo estarán disponibles a través de HTTPS. -Proxy HTTP .[#toc-http-proxy] ------------------------------ +Proxy HTTP +---------- -Si el sitio se ejecuta detrás de un proxy HTTP, introduzca la dirección IP del proxy para que la detección de conexiones HTTPS funcione correctamente, así como la dirección IP del cliente. Es decir, para que [Nette\Http\Request::getRemoteAddress() |request#getRemoteAddress] y [isSecured() |request#isSecured] devuelvan los valores correctos y se generen enlaces con el protocolo `https:` en las plantillas. +Si el sitio web se ejecuta detrás de un proxy HTTP, especifique su dirección IP para que la detección de la conexión a través de HTTPS y también la dirección IP del cliente funcionen correctamente. Es decir, para que las funciones [Nette\Http\Request::getRemoteAddress() |request#getRemoteAddress] y [isSecured() |request#isSecured] devuelvan los valores correctos y en las plantillas se generen enlaces con el protocolo `https:`. ```neon http: - # dirección IP, rango (ej. 127.0.0.1/8) o array de estos valores - proxy: 127.0.0.1 # (string|string[]) por defecto ninguno + # Dirección IP, rango (p. ej. 127.0.0.1/8) o array de estos valores + proxy: 127.0.0.1 # (string|string[]) el valor por defecto es no establecido ``` -Sesión .[#toc-session] -====================== +Sesión +====== -Configuración básica de [las sesiones |sessions]: +Configuración básica de [sesiones|sessions]: ```neon session: - # ¿muestra el panel de sesión en Tracy Bar? - depurador: ... # (bool) por defecto false + # ¿mostrar el panel de sesión en Tracy Bar? + debugger: ... # (bool) el valor por defecto es false - # tiempo de inactividad tras el cual expira la sesión - expiración: 14 días # (string) por defecto '3 horas' + # tiempo de inactividad después del cual la sesión expira + expiration: 14 days # (string) el valor por defecto es '3 hours' - # ¿cuándo iniciar la sesión? - autoStart: ... # (smart|always|never) por defecto 'smart + # ¿cuándo debe iniciarse la sesión? + autoStart: ... # (smart|always|never) el valor por defecto es 'smart' - # handler, servicio que implementa la interfaz SessionHandlerInterface + # manejador, servicio que implementa la interfaz SessionHandlerInterface handler: @handlerService ``` -La opción `autoStart` controla cuándo iniciar la sesión. El valor `always` significa que la sesión se inicia siempre al arrancar la aplicación. El valor `smart` significa que la sesión se iniciará al arrancar la aplicación sólo si ya existe, o en el momento en que queramos leer de ella o escribir en ella. Por último, el valor `never` desactiva el inicio automático de la sesión. +La opción `autoStart` controla cuándo debe iniciarse la sesión. El valor `always` significa que la sesión siempre se iniciará cuando se inicie la aplicación. El valor `smart` significa que la sesión se iniciará al inicio de la aplicación solo si ya existe, o en el momento en que queramos leer o escribir en ella. Y finalmente, el valor `never` prohíbe el inicio automático de la sesión. -También se pueden establecer todas las [directivas de sesión |https://www.php.net/manual/en/session.configuration.php] de PHP (en formato camelCase) y también [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. Ejemplo: +Además, se pueden configurar todas las [directivas de sesión |https://www.php.net/manual/en/session.configuration.php] de PHP (en formato camelCase) y también [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. Ejemplo: ```neon session: - # 'session.name' written as 'name' + # 'session.name' se escribe como 'name' name: MYID - # 'session.save_path' written as 'savePath' + # 'session.save_path' se escribe como 'savePath' savePath: "%tempDir%/sessions" ``` -Cookie de sesión .[#toc-session-cookie] ---------------------------------------- +Cookie de sesión +---------------- -La cookie de sesión se envía con los mismos parámetros que [otras cookies |#HTTP cookie], pero puede cambiarlos: +La cookie de sesión se envía con los mismos parámetros que [otras cookies |#Cookie HTTP], pero puede cambiarlos para ella: ```neon session: - # qué hosts pueden recibir la cookie - cookieDomain: 'example.com' # (string|dominio) + # dominios que aceptan la cookie + cookieDomain: 'example.com' # (string|domain) - # restricciones al acceder a la solicitud de origen cruzado - cookieSamesite: None # (Strict|Lax|None) por defecto Lax + # restricción al acceder desde otro dominio + cookieSamesite: None # (Strict|Lax|None) el valor por defecto es Lax ``` -La opción `cookieSamesite` afecta a si la cookie se envía con [solicitudes de origen cruzado |nette:glossary#SameSite cookie], lo que proporciona cierta protección contra ataques de [falsificación de solicitud de sitios cruz |nette:glossary#cross-site-request-forgery-csrf] ados. +El atributo `cookieSamesite` afecta si la cookie se enviará al [acceder desde otro dominio |nette:glossary#Cookie SameSite], lo que proporciona cierta protección contra ataques [Cross-Site Request Forgery |nette:glossary#Cross-Site Request Forgery CSRF] (CSRF). + + +Servicios DI +============ + +Estos servicios se añaden al contenedor DI: + +| Nombre | Tipo | Descripción +|----------------------------------------------------- +| `http.request` | [api:Nette\Http\Request] | [Petición HTTP| request] +| `http.response` | [api:Nette\Http\Response] | [Respuesta HTTP| response] +| `session.session` | [api:Nette\Http\Session] | [gestión de sesiones| sessions] diff --git a/http/es/request.texy b/http/es/request.texy index 99f1351f48..49b9cdf1c6 100644 --- a/http/es/request.texy +++ b/http/es/request.texy @@ -1,20 +1,20 @@ -Solicitud HTTP -************** +Petición HTTP +************* .[perex] -Nette encapsula la petición HTTP en objetos con una API comprensible a la vez que proporciona un filtro de desinfección. +Nette encapsula la petición HTTP en objetos con una API comprensible y, al mismo tiempo, proporciona un filtro de sanitización. -Una petición HTTP es un objeto [api:Nette\Http\Request], que se obtiene pasándolo mediante [inyección de dependencia |dependency-injection:passing-dependencies]. En los presentadores basta con llamar a `$httpRequest = $this->getHttpRequest()`. +La petición HTTP está representada por el objeto [api:Nette\Http\Request]. Si trabaja con Nette, este objeto es creado automáticamente por el framework y puede solicitar que se le pase mediante [inyección de dependencias |dependency-injection:passing-dependencies]. En los presenters, basta con llamar al método `$this->getHttpRequest()`. Si trabaja fuera del Nette Framework, puede crear el objeto usando [#RequestFactory]. -Lo importante es que Nette al [crear |#RequestFactory] este objeto, limpia todos los parámetros de entrada GET, POST y COOKIE así como las URLs de caracteres de control y secuencias UTF-8 inválidas. Así puede seguir trabajando con los datos de forma segura. Los datos limpiados se utilizan entonces en presentadores y formularios. +Una gran ventaja de Nette es que al crear el objeto, limpia automáticamente todos los parámetros de entrada GET, POST, COOKIE y también la URL de caracteres de control y secuencias UTF-8 inválidas. Con estos datos, puede trabajar de forma segura. Los datos limpios se utilizan posteriormente en presenters y formularios. -→ [Instalación y requisitos |@home#Installation] +→ [Instalación y requisitos |@home#Instalación] -Nette\Http\Request .[#toc-nette-http-request] -============================================= +Nette\Http\Request +================== -Este objeto es inmutable. No tiene setters, sólo tiene uno llamado wither `withUrl()`, que no cambia el objeto, pero devuelve una nueva instancia con un valor modificado. +Este objeto es inmutable. No tiene setters, solo tiene un llamado wither `withUrl()`, que no modifica el objeto, sino que devuelve una nueva instancia con el valor cambiado. withUrl(Nette\Http\UrlScript $url): Nette\Http\Request .[method] @@ -24,62 +24,62 @@ Devuelve un clon con una URL diferente. getUrl(): Nette\Http\UrlScript .[method] ---------------------------------------- -Devuelve la URL de la petición como objeto [UrlScript |urls#UrlScript]. +Devuelve la URL de la petición como un objeto [UrlScript |urls#UrlScript]. ```php $url = $httpRequest->getUrl(); -echo $url; // https://nette.org/en/documentation?action=edit +echo $url; // https://doc.nette.org/es/?action=edit echo $url->getHost(); // nette.org ``` -Los navegadores no envían un fragmento al servidor, por lo que `$url->getFragment()` devolverá una cadena vacía. +Advertencia: los navegadores no envían el fragmento al servidor, por lo que `$url->getFragment()` devolverá una cadena vacía. -getQuery(string $key=null): string|array|null .[method] -------------------------------------------------------- -Devuelve los parámetros de la petición GET: +getQuery(?string $key=null): string|array|null .[method] +-------------------------------------------------------- +Devuelve los parámetros GET de la petición. ```php -$all = $httpRequest->getQuery(); // array de todos los parámetros URL +$all = $httpRequest->getQuery(); // devuelve un array de todos los parámetros de la URL $id = $httpRequest->getQuery('id'); // devuelve el parámetro GET 'id' (o null) ``` -getPost(string $key=null): string|array|null .[method] ------------------------------------------------------- -Devuelve los parámetros de la solicitud POST: +getPost(?string $key=null): string|array|null .[method] +------------------------------------------------------- +Devuelve los parámetros POST de la petición. ```php -$all = $httpRequest->getPost(); // array de todos los parámetros POST +$all = $httpRequest->getPost(); // devuelve un array de todos los parámetros de POST $id = $httpRequest->getPost('id'); // devuelve el parámetro POST 'id' (o null) ``` getFile(string|string[] $key): Nette\Http\FileUpload|array|null .[method] ------------------------------------------------------------------------- -Devuelve la [carga |#Uploaded Files] como objeto [api:Nette\Http\FileUpload]: +Devuelve la [carga |#Archivos cargados] como un objeto [api:Nette\Http\FileUpload]: ```php $file = $httpRequest->getFile('avatar'); -if ($archivo->tieneArchivo()) { // ¿se ha subido algún archivo? - $file->getUntrustedName(); // nombre del fichero enviado por el usuario - $file->getSanitizedName(); // el nombre sin caracteres peligrosos +if ($file?->hasFile()) { // ¿se ha cargado algún archivo? + $file->getUntrustedName(); // nombre del archivo enviado por el usuario + $file->getSanitizedName(); // nombre sin caracteres peligrosos } ``` -Especifica un array de claves para acceder a la estructura del subárbol. +Para acceder a una estructura anidada, proporcione un array de claves. ```php //<input type="file" name="my-form[details][avatar]" multiple> $file = $request->getFile(['my-form', 'details', 'avatar']); ``` -Debido a que no se puede confiar en los datos desde el exterior y por lo tanto no depende de la forma de la estructura, este método es más seguro que `$request->getFiles()['my-form']['details']['avatar']`que puede fallar. +Dado que no se puede confiar en los datos externos y, por lo tanto, tampoco en la forma de la estructura de archivos, este método es más seguro que, por ejemplo, `$request->getFiles()['my-form']['details']['avatar']`, que podría fallar. getFiles(): array .[method] --------------------------- -Devuelve un árbol de [archivos de carga |#Uploaded Files] en una estructura normalizada, con cada hoja como una instancia de [api:Nette\Http\FileUpload]: +Devuelve el árbol de [todas las cargas |#Archivos cargados] en una estructura normalizada, cuyas hojas son objetos [api:Nette\Http\FileUpload]: ```php $files = $httpRequest->getFiles(); @@ -88,7 +88,7 @@ $files = $httpRequest->getFiles(); getCookie(string $key): string|array|null .[method] --------------------------------------------------- -Devuelve una cookie o `null` si no existe. +Devuelve la cookie o `null` si no existe. ```php $sessId = $httpRequest->getCookie('sess_id'); @@ -97,7 +97,7 @@ $sessId = $httpRequest->getCookie('sess_id'); getCookies(): array .[method] ----------------------------- -Devuelve todas las cookies: +Devuelve todas las cookies. ```php $cookies = $httpRequest->getCookies(); @@ -109,13 +109,13 @@ getMethod(): string .[method] Devuelve el método HTTP con el que se realizó la petición. ```php -echo $httpRequest->getMethod(); // GET, POST, HEAD, PUT +$httpRequest->getMethod(); // GET, POST, HEAD, PUT ``` isMethod(string $method): bool .[method] ---------------------------------------- -Comprueba el método HTTP con el que se realizó la solicitud. El parámetro no distingue entre mayúsculas y minúsculas. +Comprueba el método HTTP con el que se realizó la petición. El parámetro es insensible a mayúsculas/minúsculas. ```php if ($httpRequest->isMethod('GET')) // ... @@ -124,7 +124,7 @@ if ($httpRequest->isMethod('GET')) // ... getHeader(string $header): ?string .[method] -------------------------------------------- -Devuelve una cabecera HTTP o `null` si no existe. El parámetro no distingue entre mayúsculas y minúsculas: +Devuelve una cabecera HTTP o `null` si no existe. El parámetro es insensible a mayúsculas/minúsculas. ```php $userAgent = $httpRequest->getHeader('User-Agent'); @@ -133,7 +133,7 @@ $userAgent = $httpRequest->getHeader('User-Agent'); getHeaders(): array .[method] ----------------------------- -Devuelve todas las cabeceras HTTP como matriz asociativa: +Devuelve todas las cabeceras HTTP como un array asociativo. ```php $headers = $httpRequest->getHeaders(); @@ -141,19 +141,14 @@ echo $headers['Content-Type']; ``` -getReferer(): ?Nette\Http\UrlImmutable .[method] ------------------------------------------------- -¿De qué URL procede el usuario? Cuidado, no es nada fiable. - - isSecured(): bool .[method] --------------------------- -¿Está cifrada la conexión (HTTPS)? Es posible que tenga que [configurar un proxy |configuration#HTTP proxy] para que funcione correctamente. +¿Está la conexión cifrada (HTTPS)? Para un funcionamiento correcto, puede ser necesario [configurar un proxy |configuration#Proxy HTTP]. isSameSite(): bool .[method] ---------------------------- -¿La solicitud procede del mismo (sub)dominio y se inicia al hacer clic en un enlace? Nette utiliza la cookie `_nss` (antes `nette-samesite`) para detectarlo. +¿Proviene la petición del mismo (sub)dominio y se inicia haciendo clic en un enlace? Nette utiliza la cookie `_nss` (anteriormente `nette-samesite`) para la detección. isAjax(): bool .[method] @@ -163,26 +158,26 @@ isAjax(): bool .[method] getRemoteAddress(): ?string .[method] ------------------------------------- -Devuelve la dirección IP del usuario. Es posible que tenga que [configurar un proxy |configuration#HTTP proxy] para que funcione correctamente. +Devuelve la dirección IP del usuario. Para un funcionamiento correcto, puede ser necesario [configurar un proxy |configuration#Proxy HTTP]. getRemoteHost(): ?string .[method deprecated] --------------------------------------------- -Devuelve la traducción DNS de la dirección IP del usuario. Es posible que tenga que [configurar un proxy |configuration#HTTP proxy] para que funcione correctamente. +Devuelve la resolución DNS de la dirección IP del usuario. Para un funcionamiento correcto, puede ser necesario [configurar un proxy |configuration#Proxy HTTP]. -getBasicCredentials(): ?string .[method] ----------------------------------------- -Devuelve las credenciales [básicas de autenticación HTTP |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication]. +getBasicCredentials(): ?array .[method] +--------------------------------------- +Devuelve las credenciales de autenticación para [Basic HTTP authentication |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication]. Devuelve un array `[nombre de usuario, contraseña]` o `null`. ```php -[$user, $password] = $httpRequest->getBasicCredentials(); +[$user, $password] = $httpRequest->getBasicCredentials() ?? [null, null]; ``` getRawBody(): ?string .[method] ------------------------------- -Devuelve el cuerpo de la petición HTTP: +Devuelve el cuerpo de la petición HTTP. ```php $body = $httpRequest->getRawBody(); @@ -191,108 +186,115 @@ $body = $httpRequest->getRawBody(); detectLanguage(array $langs): ?string .[method] ----------------------------------------------- -Detecta el idioma. Como parámetro `$lang`, pasamos un array de idiomas que soporta la aplicación, y devuelve el preferido por navegador. No es magia, el método sólo utiliza la cabecera `Accept-Language`. Si no hay coincidencias, devuelve `null`. +Detecta el idioma. Como parámetro `$langs`, pasamos un array con los idiomas que soporta la aplicación, y devuelve el que el navegador del visitante preferiría. No es magia, simplemente utiliza la cabecera `Accept-Language`. Si no hay coincidencia, devuelve `null`. ```php -// Cabecera enviada por el navegador: Accept-Language: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3 +// el navegador envía p. ej. Accept-Language: es-ES,es;q=0.9,en;q=0.8 -$langs = ['hu', 'pl', 'en']; // idiomas soportados en la aplicación +$langs = ['hu', 'pl', 'en']; // idiomas soportados por la aplicación echo $httpRequest->detectLanguage($langs); // en ``` -RequestFactory .[#toc-requestfactory] -===================================== +RequestFactory +============== -El objeto de la petición HTTP actual es creado por [api:Nette\Http\RequestFactory]. Si está escribiendo una aplicación que no utiliza un contenedor DI, cree una petición de la siguiente manera: +La clase [api:Nette\Http\RequestFactory] sirve para crear una instancia de `Nette\Http\Request`, que representa la petición HTTP actual. (Si trabaja con Nette, el objeto de la petición HTTP es creado automáticamente por el framework). ```php $factory = new Nette\Http\RequestFactory; $httpRequest = $factory->fromGlobals(); ``` -RequestFactory puede configurarse antes de llamar a `fromGlobals()`. Podemos deshabilitar toda la sanitización de parámetros de entrada de secuencias UTF-8 inválidas usando `$factory->setBinary()`. Y también configurar un servidor proxy, que es importante para la correcta detección de la dirección IP del usuario utilizando `$factory->setProxy(...)`. +El método `fromGlobals()` crea el objeto de la petición basándose en las variables globales actuales de PHP (`$_GET`, `$_POST`, `$_COOKIE`, `$_FILES` y `$_SERVER`). Al crear el objeto, limpia automáticamente todos los parámetros de entrada GET, POST, COOKIE y también la URL de caracteres de control y secuencias UTF-8 inválidas, lo que garantiza la seguridad al trabajar posteriormente con estos datos. + +RequestFactory se puede configurar antes de llamar a `fromGlobals()`: -Es posible limpiar las URL de caracteres que puedan introducirse en ellas debido a sistemas de comentarios mal implementados en otros sitios web mediante el uso de filtros: +- con el método `$factory->setBinary()` desactiva la limpieza automática de los parámetros de entrada de caracteres de control y secuencias UTF-8 inválidas. +- con el método `$factory->setProxy(...)` indica la dirección IP del [servidor proxy |configuration#Proxy HTTP], lo cual es necesario para la correcta detección de la dirección IP del usuario. + +RequestFactory permite definir filtros que transforman automáticamente partes de la URL de la petición. Estos filtros eliminan caracteres no deseados de la URL, que pueden haber sido insertados allí, por ejemplo, por una implementación incorrecta de sistemas de comentarios en varios sitios web: ```php -// eliminar espacios de la ruta -$requestFactory->urlFilters['ruta']['%20'] = ''; +// eliminación de espacios de la ruta +$requestFactory->urlFilters['path']['%20'] = ''; -// eliminar punto, coma o paréntesis derecho del final de la URL +// eliminación de punto, coma o paréntesis derecho del final de la URI $requestFactory->urlFilters['url']['[.,)]$'] = ''; -// limpiar la ruta de barras duplicadas (filtro por defecto) +// limpieza de la ruta de barras dobles (filtro por defecto) $requestFactory->urlFilters['path']['/{2,}'] = '/'; ``` +La primera clave `'path'` o `'url'` determina a qué parte de la URL se aplica el filtro. La segunda clave es la expresión regular que se debe buscar, y el valor es el reemplazo que se utilizará en lugar del texto encontrado. + -Archivos subidos .[#toc-uploaded-files] -======================================= +Archivos cargados +================= -El método `Nette\Http\Request::getFiles()` devuelve un árbol de archivos cargados en una estructura normalizada, en la que cada hoja es una instancia de [api:Nette\Http\FileUpload]. Estos objetos encapsulan los datos enviados por el elemento de formulario `<input type=file>` elemento formulario. +El método `Nette\Http\Request::getFiles()` devuelve un array de todas las cargas en una estructura normalizada, cuyas hojas son objetos [api:Nette\Http\FileUpload]. Estos encapsulan los datos enviados por el elemento de formulario `<input type=file>`. -La estructura refleja la nomenclatura de los elementos en HTML. En el ejemplo más simple, esto podría ser un único elemento de formulario con nombre enviado como: +La estructura refleja la nomenclatura de los elementos en HTML. En el caso más simple, puede ser un único elemento de formulario con nombre enviado como: ```latte <input type="file" name="avatar"> ``` -En este caso, `$request->getFiles()` devuelve una matriz: +En este caso, `$request->getFiles()` devuelve un array: ```php [ - 'avatar' => /* FileUpload instance */ + 'avatar' => /* instancia de FileUpload */ ] ``` -El objeto `FileUpload` se crea aunque el usuario no haya subido ningún archivo o la subida haya fallado. El método `hasFile()` devuelve true si se ha enviado un archivo: +El objeto `FileUpload` se crea incluso si el usuario no envió ningún archivo o el envío falló. Si se envió un archivo lo devuelve el método `hasFile()`: ```php -$request->getFile('avatar')->hasFile(); +$request->getFile('avatar')?->hasFile(); ``` -En el caso de una entrada que utilice la notación array para el nombre: +En el caso de un nombre de elemento que utiliza la notación de array: ```latte <input type="file" name="my-form[details][avatar]"> ``` -el árbol devuelto acaba teniendo este aspecto: +el árbol devuelto se ve así: ```php [ 'my-form' => [ 'details' => [ - 'avatar' => /* FileUpload instance */ + 'avatar' => /* instancia de FileUpload */ ], ], ] ``` -También puede crear matrices de archivos: +También se puede crear un array de archivos: ```latte -<input type="file" name="my-form[details][avatars][] multiple"> +<input type="file" name="my-form[details][avatars][]" multiple> ``` -En tal caso la estructura se parece: +En tal caso, la estructura se ve así: ```php [ 'my-form' => [ 'details' => [ 'avatars' => [ - 0 => /* FileUpload instance */, - 1 => /* FileUpload instance */, - 2 => /* FileUpload instance */, + 0 => /* instancia de FileUpload */, + 1 => /* instancia de FileUpload */, + 2 => /* instancia de FileUpload */, ], ], ], ] ``` -La mejor forma de acceder al índice 1 de una matriz anidada es la siguiente: +La mejor manera de acceder al índice 1 del array anidado es así: ```php $file = $request->getFile(['my-form', 'details', 'avatars', 1]); @@ -301,31 +303,31 @@ if ($file instanceof FileUpload) { } ``` -Debido a que no se puede confiar en los datos desde el exterior y por lo tanto no se confía en la forma de la estructura, este método es más seguro que `$request->getFiles()['my-form']['details']['avatars'][1]`que puede fallar. +Dado que no se puede confiar en los datos externos y, por lo tanto, tampoco en la forma de la estructura de archivos, este método es más seguro que, por ejemplo, `$request->getFiles()['my-form']['details']['avatars'][1]`, que podría fallar. -Visión general de los métodos `FileUpload` .{toc: FileUpload} -------------------------------------------------------------- +Resumen de métodos `FileUpload` .{toc: FileUpload} +-------------------------------------------------- hasFile(): bool .[method] ------------------------- -Devuelve `true` si el usuario ha subido un archivo. +Devuelve `true` si el usuario ha cargado algún archivo. isOk(): bool .[method] ---------------------- -Devuelve `true` si el archivo se ha cargado correctamente. +Devuelve `true` si el archivo se cargó correctamente. getError(): int .[method] ------------------------- -Devuelve el código de error asociado al archivo cargado. Es una de las constantes [UPLOAD_ERR_XXX |http://php.net/manual/en/features.file-upload.errors.php]. Si el archivo se ha cargado correctamente, devuelve `UPLOAD_ERR_OK`. +Devuelve el código de error de la carga del archivo. Es una de las constantes [UPLOAD_ERR_XXX|http://php.net/manual/en/features.file-upload.errors.php]. Si la carga fue exitosa, devuelve `UPLOAD_ERR_OK`. move(string $dest) .[method] ---------------------------- -Mueve un archivo cargado a una nueva ubicación. Si el archivo de destino ya existe, se sobrescribirá. +Mueve el archivo cargado a una nueva ubicación. Si el archivo de destino ya existe, será sobrescrito. ```php $file->move('/path/to/files/name.ext'); @@ -334,12 +336,12 @@ $file->move('/path/to/files/name.ext'); getContents(): ?string .[method] -------------------------------- -Devuelve el contenido del archivo cargado. Si la carga no se ha realizado correctamente, devuelve `null`. +Devuelve el contenido del archivo cargado. Si la carga no fue exitosa, devuelve `null`. getContentType(): ?string .[method] ----------------------------------- -Detecta el tipo de contenido MIME del archivo cargado basándose en su firma. Si la carga no se ha realizado correctamente o la detección ha fallado, devuelve `null`. +Detecta el tipo de contenido MIME del archivo cargado basándose en su firma. Si la carga no fue exitosa o la detección falló, devuelve `null`. .[caution] Requiere la extensión PHP `fileinfo`. @@ -347,38 +349,49 @@ Requiere la extensión PHP `fileinfo`. getUntrustedName(): string .[method] ------------------------------------ -Devuelve el nombre original del archivo tal y como fue enviado por el navegador. +Devuelve el nombre original del archivo, tal como lo envió el navegador. .[caution] -No confíe en el valor devuelto por este método. Un cliente podría enviar un nombre de archivo malicioso con la intención de corromper o hackear su aplicación. +No confíe en el valor devuelto por este método. Un cliente podría haber enviado un nombre de archivo malicioso con la intención de dañar o hackear su aplicación. getSanitizedName(): string .[method] ------------------------------------ -Devuelve el nombre de archivo desinfectado. Sólo contiene caracteres ASCII `[a-zA-Z0-9.-]`. Si el nombre no contiene tales caracteres, devuelve 'desconocido'. Si el archivo es una imagen JPEG, PNG, GIF o WebP, devuelve la extensión de archivo correcta. +Devuelve el nombre del archivo sanitizado. Contiene solo caracteres ASCII `[a-zA-Z0-9.-]`. Si el nombre no contiene tales caracteres, devuelve `'unknown'`. Si el archivo es una imagen en formato JPEG, PNG, GIF, WebP o AVIF, también devuelve la extensión correcta. + +.[caution] +Requiere la extensión PHP `fileinfo`. + + +getSuggestedExtension(): ?string .[method]{data-version:3.2.4} +-------------------------------------------------------------- +Devuelve la extensión de archivo apropiada (sin el punto) correspondiente al tipo MIME detectado. + +.[caution] +Requiere la extensión PHP `fileinfo`. getUntrustedFullPath(): string .[method] ---------------------------------------- -Devuelve la ruta completa original tal y como fue enviada por el navegador durante la carga del directorio. La ruta completa sólo está disponible en PHP 8.1 y superiores. En versiones anteriores, este método devuelve el nombre de archivo no confiable. +Devuelve la ruta original del archivo, tal como la envió el navegador al cargar una carpeta. La ruta completa solo está disponible en PHP 8.1 y superior. En versiones anteriores, este método devuelve el nombre original del archivo. .[caution] -No confíe en el valor devuelto por este método. Un cliente podría enviar un nombre de archivo malicioso con la intención de corromper o hackear su aplicación. +No confíe en el valor devuelto por este método. Un cliente podría haber enviado un nombre de archivo malicioso con la intención de dañar o hackear su aplicación. getSize(): int .[method] ------------------------ -Devuelve el tamaño del archivo subido. Si la subida no tuvo éxito, devuelve `0`. +Devuelve el tamaño del archivo cargado. Si la carga no fue exitosa, devuelve `0`. getTemporaryFile(): string .[method] ------------------------------------ -Devuelve la ruta de la ubicación temporal del archivo subido. Si la carga no se ha realizado correctamente, devuelve `''`. +Devuelve la ruta a la ubicación temporal del archivo cargado. Si la carga no fue exitosa, devuelve `''`. isImage(): bool .[method] ------------------------- -Devuelve `true` si el archivo cargado es una imagen JPEG, PNG, GIF o WebP. La detección se basa en su firma. No se comprueba la integridad de todo el archivo. Puede averiguar si una imagen no está dañada, por ejemplo, intentando [cargarla |#toImage]. +Devuelve `true` si el archivo cargado es una imagen en formato JPEG, PNG, GIF, WebP o AVIF. La detección se basa en su firma y no verifica la integridad de todo el archivo. Si una imagen está dañada se puede detectar, por ejemplo, intentando [cargarla |#toImage]. .[caution] Requiere la extensión PHP `fileinfo`. @@ -386,9 +399,9 @@ Requiere la extensión PHP `fileinfo`. getImageSize(): ?array .[method] -------------------------------- -Devuelve un par de `[width, height]` con las dimensiones de la imagen subida. Si la carga no se ha realizado correctamente o no es una imagen válida, devuelve `null`. +Devuelve un par `[ancho, alto]` con las dimensiones de la imagen cargada. Si la carga no fue exitosa o no es una imagen válida, devuelve `null`. toImage(): Nette\Utils\Image .[method] -------------------------------------- -Carga una imagen como objeto [Image |utils:images]. Si la carga no se ha realizado correctamente o no es una imagen válida, lanza una excepción `Nette\Utils\ImageException`. +Carga la imagen como un objeto [Image|utils:images]. Si la carga no fue exitosa o no es una imagen válida, lanza una excepción `Nette\Utils\ImageException`. diff --git a/http/es/response.texy b/http/es/response.texy index 8ae2418680..bdbc10e076 100644 --- a/http/es/response.texy +++ b/http/es/response.texy @@ -2,22 +2,22 @@ Respuesta HTTP ************** .[perex] -Nette encapsula la respuesta HTTP en objetos con una API comprensible a la vez que proporciona un filtro de desinfección. +Nette encapsula la respuesta HTTP en objetos con una API comprensible. -Una respuesta HTTP es un objeto [api:Nette\Http\Response], que se obtiene pasándolo mediante [inyección de dependencia |dependency-injection:passing-dependencies]. En los presentadores basta con llamar a `$httpResponse = $this->getHttpResponse()`. +La respuesta HTTP está representada por el objeto [api:Nette\Http\Response]. Si trabaja con Nette, este objeto es creado automáticamente por el framework y puede solicitar que se le pase mediante [inyección de dependencias |dependency-injection:passing-dependencies]. En los presenters, basta con llamar al método `$this->getHttpResponse()`. -→ [Instalación y requisitos |@home#Installation] +→ [Instalación y requisitos |@home#Instalación] -Nette\Http\Response .[#toc-nette-http-response] -=============================================== +Nette\Http\Response +=================== -A diferencia de [Nette\Http\Request |request], este objeto es mutable, por lo que puede utilizar setters para cambiar el estado, es decir, para enviar cabeceras. Recuerde que todos los setters **deben ser llamados antes de que cualquier salida real sea enviada.** El método `isSent()` dice si la salida ha sido enviada. Si devuelve `true`, cada intento de enviar una cabecera lanza una excepción `Nette\InvalidStateException`. +El objeto, a diferencia de [Nette\Http\Request|request], es mutable, es decir, puede cambiar el estado mediante setters, por ejemplo, enviar cabeceras. No olvide que todos los setters deben ser llamados **antes de enviar cualquier salida.** Si ya se ha enviado la salida lo indica el método `isSent()`. Si devuelve `true`, cualquier intento de enviar una cabecera lanzará una excepción `Nette\InvalidStateException`. -setCode(int $code, string $reason=null) .[method] -------------------------------------------------- -Cambia un [código de |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10] estado de [respuesta |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10]. Para una mejor legibilidad del código fuente se recomienda utilizar [constantes predefinidas |api:Nette\Http\IResponse] en lugar de números reales. +setCode(int $code, ?string $reason=null) .[method] +-------------------------------------------------- +Cambia el [código de estado de la respuesta |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10]. Para una mejor legibilidad del código fuente, se recomienda utilizar [constantes predefinidas |api:Nette\Http\IResponse] en lugar de números para el código. ```php $httpResponse->setCode(Nette\Http\Response::S404_NotFound); @@ -31,12 +31,12 @@ Devuelve el código de estado de la respuesta. isSent(): bool .[method] ------------------------ -Devuelve si las cabeceras ya han sido enviadas desde el servidor al navegador, por lo que ya no es posible enviar cabeceras o cambiar el código de estado. +Devuelve si las cabeceras ya han sido enviadas desde el servidor al navegador, por lo que ya no es posible enviar cabeceras ni cambiar el código de estado. setHeader(string $name, string $value) .[method] ------------------------------------------------ -Envía una cabecera HTTP y **sobrescribe** la cabecera previamente enviada con el mismo nombre. +Envía una cabecera HTTP y **sobrescribe** una cabecera enviada previamente con el mismo nombre. ```php $httpResponse->setHeader('Pragma', 'no-cache'); @@ -45,7 +45,7 @@ $httpResponse->setHeader('Pragma', 'no-cache'); addHeader(string $name, string $value) .[method] ------------------------------------------------ -Envía una cabecera HTTP y **no sobrescribe** la cabecera del mismo nombre enviada anteriormente. +Envía una cabecera HTTP y **no sobrescribe** una cabecera enviada previamente con el mismo nombre. ```php $httpResponse->addHeader('Accept', 'application/json'); @@ -60,7 +60,7 @@ Elimina una cabecera HTTP enviada previamente. getHeader(string $header): ?string .[method] -------------------------------------------- -Devuelve la cabecera HTTP enviada, o `null` si no existe. El parámetro no distingue entre mayúsculas y minúsculas. +Devuelve la cabecera HTTP enviada o `null` si no existe. El parámetro es insensible a mayúsculas/minúsculas. ```php $pragma = $httpResponse->getHeader('Pragma'); @@ -69,7 +69,7 @@ $pragma = $httpResponse->getHeader('Pragma'); getHeaders(): array .[method] ----------------------------- -Devuelve todas las cabeceras HTTP enviadas como matriz asociativa. +Devuelve todas las cabeceras HTTP enviadas como un array asociativo. ```php $headers = $httpResponse->getHeaders(); @@ -77,9 +77,9 @@ echo $headers['Pragma']; ``` -setContentType(string $type, string $charset=null) .[method] ------------------------------------------------------------- -Envía la cabecera `Content-Type`. +setContentType(string $type, ?string $charset=null) .[method] +------------------------------------------------------------- +Cambia la cabecera `Content-Type`. ```php $httpResponse->setContentType('text/plain', 'UTF-8'); @@ -88,7 +88,7 @@ $httpResponse->setContentType('text/plain', 'UTF-8'); redirect(string $url, int $code=self::S302_Found): void .[method] ----------------------------------------------------------------- -Redirige a otra URL. No olvides salir del script entonces. +Redirige a otra URL. No olvide terminar el script después. ```php $httpResponse->redirect('http://example.com'); @@ -98,52 +98,52 @@ exit; setExpiration(?string $time) .[method] -------------------------------------- -Establece la caducidad del documento HTTP utilizando las cabeceras `Cache-Control` y `Expires`. El parámetro es un intervalo de tiempo (como texto) o `null`, que desactiva el almacenamiento en caché. +Establece la expiración del documento HTTP utilizando las cabeceras `Cache-Control` y `Expires`. El parámetro es un intervalo de tiempo (como texto) o `null`, lo que deshabilita el almacenamiento en caché. ```php -// la caché del navegador caduca en una hora +// la caché del navegador expirará en una hora $httpResponse->setExpiration('1 hour'); ``` sendAsFile(string $fileName) .[method] -------------------------------------- -La respuesta debe descargarse con el diálogo *Guardar como* con el nombre especificado. No envía ningún archivo en sí a la salida. +La respuesta se descargará mediante el cuadro de diálogo *Guardar como* con el nombre especificado. El archivo en sí no se envía. ```php -$httpResponse->sendAsFile('invoice.pdf'); +$httpResponse->sendAsFile('factura.pdf'); ``` -setCookie(string $name, string $value, $time, string $path=null, string $domain=null, bool $secure=null, bool $httpOnly=null, string $sameSite=null) .[method] --------------------------------------------------------------------------------------------------------------------------------------------------------------- +setCookie(string $name, string $value, $time, ?string $path=null, ?string $domain=null, ?bool $secure=null, ?bool $httpOnly=null, ?string $sameSite=null) .[method] +------------------------------------------------------------------------------------------------------------------------------------------------------------------- Envía una cookie. Valores por defecto de los parámetros: -| `$path` | `'/'` | con alcance a todas las rutas del (sub)dominio *(configurable)* -| `$domain` | `null` | con alcance al (sub)dominio actual, pero no a sus subdominios *(configurable)* -| `$secure` | `true` | si el sitio funciona con HTTPS; en caso contrario, `false` *(configurable)*. -| `$httpOnly` | `true` | cookie es inaccesible para JavaScript -| `$sameSite` | `'Lax'` | cookie no tiene que ser enviada cuando se [accede desde otro origen |nette:glossary#SameSite cookie] +| `$path` | `'/'` | la cookie tiene alcance a todas las rutas en el (sub)dominio *(configurable)* +| `$domain` | `null` | lo que significa con alcance al (sub)dominio actual, pero no a sus subdominios *(configurable)* +| `$secure` | `true` | si el sitio web se ejecuta en HTTPS, de lo contrario `false` *(configurable)* +| `$httpOnly` | `true` | la cookie no es accesible para JavaScript +| `$sameSite` | `'Lax'` | la cookie puede no ser enviada al [acceder desde otro dominio |nette:glossary#Cookie SameSite] -Puede cambiar los valores por defecto de los parámetros `$path`, `$domain` y `$secure` en [configuration |configuration#HTTP cookie]. +Puede cambiar los valores predeterminados de los parámetros `$path`, `$domain` y `$secure` en la [configuración |configuration#Cookie HTTP]. -El tiempo puede especificarse como número de segundos o como una cadena: +El tiempo se puede especificar como un número de segundos o una cadena: ```php -$httpResponse->setCookie('lang', 'en', '100 days'); +$httpResponse->setCookie('lang', 'es', '100 days'); ``` -La opción `$domain` determina qué dominios (orígenes) pueden aceptar cookies. Si no se especifica, la cookie es aceptada por el mismo (sub)dominio que la establece, excluyendo sus subdominios. Si se especifica `$domain`, también se incluyen los subdominios. Por lo tanto, especificar `$domain` es menos restrictivo que omitirlo. Por ejemplo, si `$domain = 'nette.org'`, cookie también está disponible en todos los subdominios como `doc.nette.org`. +El parámetro `$domain` determina qué dominios pueden aceptar la cookie. Si no se especifica, la cookie es aceptada por el mismo (sub)dominio que la estableció, pero no por sus subdominios. Si se especifica `$domain`, también se incluyen los subdominios. Por lo tanto, especificar `$domain` es menos restrictivo que omitirlo. Por ejemplo, con `$domain = 'nette.org'`, las cookies también están disponibles en todos los subdominios como `doc.nette.org`. -Puede utilizar las constantes `Response::SameSiteLax`, `SameSiteStrict` y `SameSiteNone` para el valor `$sameSite`. +Para el valor `$sameSite`, puede usar las constantes `Response::SameSiteLax`, `SameSiteStrict` y `SameSiteNone`. -deleteCookie(string $name, string $path=null, string $domain=null, bool $secure=null): void .[method] ------------------------------------------------------------------------------------------------------ -Elimina una cookie. Los valores por defecto de los parámetros son +deleteCookie(string $name, ?string $path=null, ?string $domain=null, ?bool $secure=null): void .[method] +-------------------------------------------------------------------------------------------------------- +Elimina una cookie. Los valores por defecto de los parámetros son: - `$path` con alcance a todos los directorios (`'/'`) -- `$domain` con alcance al (sub)dominio actual, pero no a sus subdominios -- `$secure` se ve afectado por los ajustes en [configuration |configuration#HTTP cookie] +- `$domain` con alcance al (sub)dominio actual, pero nikoliv sus subdominios +- `$secure` se rige por la configuración en [configuración |configuration#Cookie HTTP] ```php $httpResponse->deleteCookie('lang'); diff --git a/http/es/sessions.texy b/http/es/sessions.texy index 74e7998749..db607e8d98 100644 --- a/http/es/sessions.texy +++ b/http/es/sessions.texy @@ -3,69 +3,69 @@ Sesiones <div class=perex> -HTTP es un protocolo sin estado, pero casi todas las aplicaciones necesitan mantener un estado entre peticiones, por ejemplo, el contenido de un carrito de la compra. Para esto se utiliza una sesión. Veamos +HTTP es un protocolo sin estado, sin embargo, casi todas las aplicaciones necesitan mantener el estado entre peticiones, por ejemplo, el contenido de un carrito de compras. Precisamente para eso sirven las sesiones. Mostraremos: - cómo usar las sesiones - cómo evitar conflictos de nombres -- cómo fijar la caducidad +- cómo configurar la expiración </div> -Cuando se utilizan sesiones, cada usuario recibe un identificador único llamado ID de sesión, que se pasa en una cookie. Esto sirve como clave para los datos de sesión. A diferencia de las cookies, que se almacenan en el navegador, los datos de sesión se almacenan en el servidor. +Al usar sesiones, cada usuario recibe un identificador único llamado ID de sesión, que se pasa en una cookie. Este sirve como clave para los datos de la sesión. A diferencia de las cookies, que se almacenan en el lado del navegador, los datos de la sesión se almacenan en el lado del servidor. -Configuramos la sesión en la [configuración |configuration#session], la elección del tiempo de expiración es importante. +Configuramos la sesión en la [configuración |configuration#Sesión], la opción de tiempo de expiración es especialmente importante. -La sesión es gestionada por el objeto [api:Nette\Http\Session], que se obtiene pasándolo mediante [inyección de dependencia |dependency-injection:passing-dependencies]. En presentadores simplemente llame a `$session = $this->getSession()`. +La gestión de la sesión está a cargo del objeto [api:Nette\Http\Session], al que puede acceder solicitando que se le pase mediante [inyección de dependencias |dependency-injection:passing-dependencies]. En los presenters, basta con llamar a `$session = $this->getSession()`. -→ [Instalación y requisitos |@home#Installation] +→ [Instalación y requisitos |@home#Instalación] -Inicio de la sesión .[#toc-starting-session] -============================================ +Iniciar sesión +============== -Por defecto, Nette iniciará una sesión automáticamente en el momento en que empecemos a leer de ella o a escribir datos en ella. Para iniciar una sesión manualmente, utilice `$session->start()`. +Nette, en su configuración predeterminada, inicia automáticamente la sesión en el momento en que comenzamos a leer o escribir datos en ella. La sesión se inicia manualmente usando `$session->start()`. -PHP envía cabeceras HTTP que afectan a la caché cuando se inicia la sesión, ver [php:session_cache_limiter], y posiblemente una cookie con el ID de sesión. Por lo tanto, siempre es necesario iniciar la sesión antes de enviar cualquier salida al navegador, de lo contrario se lanzará una excepción. Por lo tanto, si sabe que se va a utilizar una sesión durante la representación de la página, iníciela manualmente antes, por ejemplo en el presentador. +PHP envía al iniciar la sesión cabeceras HTTP que afectan al almacenamiento en caché, consulte [php:session_cache_limiter], y posiblemente también una cookie con el ID de sesión. Por lo tanto, es necesario iniciar siempre la sesión antes de enviar cualquier salida al navegador, de lo contrario se lanzará una excepción. Si sabe que se utilizará la sesión durante la renderización de la página, iníciela manualmente antes, por ejemplo, en el presenter. -En modo desarrollador, Tracy inicia la sesión porque la utiliza para mostrar barras de redirección y peticiones AJAX en la Tracy Bar. +En modo de desarrollo, Tracy inicia la sesión porque la utiliza para mostrar barras con redirecciones y peticiones AJAX en la Tracy Bar. -Sección .[#toc-section] -======================= +Secciones +========= -En PHP puro, el almacén de datos de sesión se implementa como un array accesible a través de una variable global `$_SESSION`. El problema es que las aplicaciones normalmente consisten en un número de partes independientes, y si todas tienen un mismo array disponible, tarde o temprano ocurrirá una colisión de nombres. +En PHP puro, el almacenamiento de datos de la sesión se realiza como un array accesible a través de la variable global `$_SESSION`. El problema es que las aplicaciones suelen constar de varias partes independientes entre sí y si todas tienen acceso a un solo array, tarde o temprano se producirá una colisión de nombres. -Nette Framework resuelve el problema dividiendo todo el espacio en secciones (objetos [api:Nette\Http\SessionSection]). Cada unidad utiliza entonces su propia sección con un nombre único y no pueden producirse colisiones. +Nette Framework resuelve el problema dividiendo todo el espacio en secciones (objetos [api:Nette\Http\SessionSection]). Cada unidad utiliza entonces su propia sección con un nombre único y ya no puede producirse ninguna colisión. -Obtenemos la sección del gestor de sesiones: +Obtenemos la sección de la sesión: ```php -$section = $session->getSection('unique name'); +$section = $session->getSection('nombreUnico'); ``` -En el presentador basta con llamar a `getSession()` con el parámetro: +En el presenter, basta con usar `getSession()` con un parámetro: ```php -// $this es Presentador -$section = $this->getSession('unique name'); +// $this es Presenter +$section = $this->getSession('nombreUnico'); ``` -La existencia de la sección puede comprobarse mediante el método `$session->hasSection('unique name')`. +La existencia de la sección se puede verificar con el método `$session->hasSection('nombreUnico')`. -La sección en sí es muy fácil de trabajar con los métodos `set()`, `get()` y `remove()`: +Trabajar con la sección en sí es muy fácil usando los métodos `set()`, `get()` y `remove()`: ```php -// escritura de variables -$section->set('nombreusuario', 'franta'); +// escribir variable +$section->set('userName', 'juan'); -// lectura de una variable, devuelve null si no existe -echo $section->get('nombredeusuario'); +// leer variable, devuelve null si no existe +echo $section->get('userName'); // eliminar variable $section->remove('userName'); ``` -Es posible utilizar el ciclo `foreach` para obtener todas las variables de la sección: +Para obtener todas las variables de la sección, se puede usar el bucle `foreach`: ```php foreach ($section as $key => $val) { @@ -74,78 +74,78 @@ foreach ($section as $key => $val) { ``` -Cómo fijar la caducidad .[#toc-how-to-set-expiration] ------------------------------------------------------ +Configuración de la expiración +------------------------------ -La caducidad puede establecerse para secciones individuales o incluso variables individuales. Podemos dejar que el login del usuario caduque en 20 minutos, pero seguir recordando el contenido de un carrito de la compra. +Es posible configurar la expiración para secciones individuales o incluso variables individuales. Podemos así dejar que el inicio de sesión del usuario expire en 20 minutos, pero seguir recordando el contenido del carrito. ```php -// la sección caducará a los 20 minutos +// la sección expirará después de 20 minutos $section->setExpiration('20 minutes'); ``` -El tercer parámetro del método `set()` se utiliza para establecer la caducidad de variables individuales: +Para configurar la expiración de variables individuales, se utiliza el tercer parámetro del método `set()`: ```php -// la variable 'flash' caduca a los 30 segundos +// la variable 'flash' expirará después de 30 segundos $section->set('flash', $message, '30 seconds'); ``` .[note] -Recuerda que el tiempo de expiración de toda la sesión (ver [configuración de la sesión |configuration#session]) debe ser igual o superior al tiempo establecido para secciones o variables individuales. +No olvide que el tiempo de expiración de toda la sesión (consulte [configuración de la sesión |configuration#Sesión]) debe ser igual o mayor que el tiempo establecido para secciones o variables individuales. -La anulación de la caducidad previamente fijada puede conseguirse mediante el método `removeExpiration()`. La eliminación inmediata de toda la sección se asegurará mediante el método `remove()`. +La cancelación de una expiración previamente establecida se logra con el método `removeExpiration()`. La cancelación inmediata de toda la sección la asegura el método `remove()`. -Eventos $onStart, $onBeforeWrite .[#toc-events-onstart-onbeforewrite] ---------------------------------------------------------------------- +Eventos $onStart, $onBeforeWrite +-------------------------------- -El objeto `Nette\Http\Session` tiene los [eventos |nette:glossary#Events] `$onStart` y `$onBeforeWrite`, por lo que puedes añadir callbacks que sean llamados después de que la sesión se inicie o antes de que se escriba en disco y luego se termine. +El objeto `Nette\Http\Session` tiene [eventos |nette:glossary#Eventos] `$onStart` y `$onBeforeWrite`, por lo que puede añadir callbacks que se invocarán después de iniciar la sesión o antes de escribirla en el disco y su posterior finalización. ```php $session->onBeforeWrite[] = function () { - // escribir datos en la sesión + // escribimos datos en la sesión $this->section->set('basket', $this->basket); }; ``` -Gestión de sesiones .[#toc-session-management] -============================================== +Gestión de sesiones +=================== -Visión general de los métodos de la clase `Nette\Http\Session` para la gestión de sesiones: +Resumen de los métodos de la clase `Nette\Http\Session` para la gestión de sesiones: <div class=wiki-methods-brief> start(): void .[method] ----------------------- -Inicia una sesión. +Inicia la sesión. isStarted(): bool .[method] --------------------------- -¿Se ha iniciado la sesión? +¿Está iniciada la sesión? close(): void .[method] ----------------------- -Finaliza la sesión. La sesión termina automáticamente al final del script. +Termina la sesión. La sesión termina automáticamente al final de la ejecución del script. destroy(): void .[method] ------------------------- -Finaliza y borra la sesión. +Termina y elimina la sesión. exists(): bool .[method] ------------------------ -¿Contiene la petición HTTP una cookie con un ID de sesión? +¿Contiene la petición HTTP una cookie con el ID de sesión? regenerateId(): void .[method] ------------------------------ -Genera un nuevo ID de sesión aleatorio. Los datos permanecen inalterados. +Genera un nuevo ID de sesión aleatorio. Los datos se conservan. getId(): string .[method] @@ -155,56 +155,57 @@ Devuelve el ID de sesión. </div> -Configuración .[#toc-configuration] ------------------------------------ +Configuración +------------- -Configuramos la sesión en [configuración |configuration#session]. Si está escribiendo una aplicación que no utiliza un contenedor DI, utilice estos métodos para configurarla. Deben ser llamados antes de iniciar la sesión. +Configuramos la sesión en la [configuración |configuration#Sesión]. Si escribe una aplicación que no utiliza un contenedor DI, estos métodos sirven para la configuración. Deben llamarse antes de iniciar la sesión. <div class=wiki-methods-brief> setName(string $name): static .[method] --------------------------------------- -Establece el nombre de la cookie que se utiliza para transmitir el ID de sesión. El nombre por defecto es `PHPSESSID`. Esto es útil si ejecuta varias aplicaciones diferentes en el mismo sitio. +Establece el nombre de la cookie en la que se transmite el ID de sesión. El nombre estándar es `PHPSESSID`. Es útil si ejecuta varias aplicaciones diferentes en el mismo sitio web. getName(): string .[method] --------------------------- -Devuelve el nombre de la cookie de sesión. +Devuelve el nombre de la cookie en la que se transmite el ID de sesión. setOptions(array $options): static .[method] -------------------------------------------- -Configura la sesión. Es posible establecer todas las [directivas de sesión |https://www.php.net/manual/en/session.configuration.php] de PHP (en formato camelCase, por ejemplo, escribir `savePath` en lugar de `session.save_path`) y también [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. +Configura la sesión. Se pueden establecer todas las [directivas de sesión |https://www.php.net/manual/en/session.configuration.php] de PHP (en formato camelCase, p. ej., en lugar de `session.save_path` escribimos `savePath`) y también [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. setExpiration(?string $time): static .[method] ---------------------------------------------- -Establece el tiempo de inactividad tras el cual expira la sesión. +Establece el período de inactividad después del cual la sesión expira. -setCookieParameters(string $path, string $domain=null, bool $secure=null, string $samesite=null): static .[method] ------------------------------------------------------------------------------------------------------------------- -Establece los parámetros para las cookies. Puede cambiar los valores predeterminados de los parámetros en [configuration |configuration#Session cookie]. +setCookieParameters(string $path, ?string $domain=null, ?bool $secure=null, ?string $samesite=null): static .[method] +--------------------------------------------------------------------------------------------------------------------- +Configuración de los parámetros de la cookie. Puede cambiar los valores predeterminados de los parámetros en la [configuración |configuration#Cookie de sesión]. setSavePath(string $path): static .[method] ------------------------------------------- -Establece el directorio donde se almacenan los archivos de sesión. +Establece el directorio donde se guardan los archivos de sesión. setHandler(\SessionHandlerInterface $handler): static .[method] --------------------------------------------------------------- -Establece el manejador personalizado, vea [la documentación de PHP |https://www.php.net/manual/en/class.sessionhandlerinterface.php]. +Configuración de un manejador personalizado, consulte la [documentación de PHP|https://www.php.net/manual/en/class.sessionhandlerinterface.php]. </div> -Seguridad .[#toc-safety-first] -============================== +La seguridad es lo primero +========================== -El servidor asume que se comunica con el mismo usuario mientras las peticiones contengan el mismo identificador de sesión. La tarea de los mecanismos de seguridad es garantizar que este comportamiento funciona realmente y que no hay posibilidad de sustituir o robar un identificador. +El servidor asume que se comunica siempre con el mismo usuario mientras las peticiones vayan acompañadas del mismo ID de sesión. La tarea de los mecanismos de seguridad es asegurar que esto sea realmente así y que no sea posible robar o suplantar el identificador. -Por eso Nette Framework configura adecuadamente las directivas PHP para transferir el identificador de sesión sólo en las cookies, evitar el acceso desde JavaScript e ignorar los identificadores en la URL. Además en momentos críticos, como el login del usuario, genera un nuevo identificador de sesión. +Por lo tanto, Nette Framework configura correctamente las directivas PHP para que el ID de sesión se transmita únicamente en la cookie, lo haga inaccesible para JavaScript e ignore los posibles identificadores en la URL. Además, en momentos críticos, como el inicio de sesión del usuario, genera un nuevo ID de sesión. -La función ini_set se utiliza para configurar PHP, pero desafortunadamente, su uso está prohibido en algunos servicios de alojamiento web. Si es tu caso, intenta pedir a tu proveedor de hosting que te permita esta función, o al menos que configure su servidor adecuadamente. .[note] +.[note] +Para la configuración de PHP se utiliza la función `ini_set`, que lamentablemente algunos hostings prohíben. Si es el caso de su proveedor de hosting, intente negociar con él para que le permita la función o al menos configure el servidor. diff --git a/http/es/urls.texy b/http/es/urls.texy index 818bd9c551..3a124786f6 100644 --- a/http/es/urls.texy +++ b/http/es/urls.texy @@ -1,16 +1,16 @@ -Parser y constructor de URL -*************************** +Trabajar con URLs +***************** .[perex] -Las clases [Url |#Url], [UrlImmutable |#UrlImmutable] y [UrlScript |#UrlScript] facilitan el manejo, análisis y manipulación de URLs. +Las clases [#Url], [#UrlImmutable] y [#UrlScript] permiten generar, analizar y manipular URLs fácilmente. -→ [Instalación y requisitos |@home#Installation] +→ [Instalación y requisitos |@home#Instalación] Url === -La clase [api:Nette\Http\Url] facilita el trabajo con la URL y sus componentes individuales, que se esbozan en este diagrama: +La clase [api:Nette\Http\Url] permite trabajar fácilmente con URLs y sus componentes individuales, que captura este diagrama: /--pre scheme user password host port path query fragment @@ -22,7 +22,7 @@ La clase [api:Nette\Http\Url] facilita el trabajo con la URL y sus componentes i hostUrl authority \-- -La generación de URL es intuitiva: +La generación de URLs es intuitiva: ```php use Nette\Http\Url; @@ -36,7 +36,7 @@ $url->setScheme('https') echo $url; // 'https://localhost/edit?foo=bar' ``` -También puede analizar la URL y luego manipularla: +También se puede analizar una URL y manipularla posteriormente: ```php $url = new Url( @@ -44,62 +44,94 @@ $url = new Url( ); ``` -Los siguientes métodos están disponibles para obtener o cambiar componentes individuales de la URL: +La clase `Url` implementa la interfaz `JsonSerializable` y tiene un método `__toString()`, por lo que el objeto se puede imprimir o usar en datos pasados a `json_encode()`. + +```php +echo $url; +echo json_encode([$url]); +``` + + +Componentes de URL .[method] +---------------------------- + +Para devolver o cambiar los componentes individuales de la URL, tiene a su disposición estos métodos: .[language-php] -| Setter | Getter | Valor devuelto +| Setter | Getter | Valor devuelto |-------------------------------------------------------------------------------------------- -| `setScheme(string $scheme)` | `getScheme(): string` | `'http'` -| `setUser(string $user)` | `getUser(): string` | `'john'` -| `setPassword(string $password)` | `getPassword(): string` | `'xyz*12'` -| `setHost(string $host)` | `getHost(): string` | `'nette.org'` -| `setPort(int $port)` | `getPort(): ?int` | `8080` -| | `getDefaultPort(): ?int` | `80` -| `setPath(string $path)` | `getPath(): string` | `'/en/download'` -| `setQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` -| `setFragment(string $fragment)` | `getFragment(): string` | `'footer'` -| | `getAuthority(): string`| `'nette.org:8080'` -| | `getHostUrl(): string`| `'http://nette.org:8080'` -| | `getAbsoluteUrl(): string` | URL completa - -También podemos operar con parámetros de consulta individuales utilizando: +| `setScheme(string $scheme)` | `getScheme(): string` | `'http'` +| `setUser(string $user)` | `getUser(): string` | `'john'` +| `setPassword(string $password)` | `getPassword(): string` | `'xyz*12'` +| `setHost(string $host)` | `getHost(): string` | `'nette.org'` +| `setPort(int $port)` | `getPort(): ?int` | `8080` +| | `getDefaultPort(): ?int` | `80` +| `setPath(string $path)` | `getPath(): string` | `'/en/download'` +| `setQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` +| `setFragment(string $fragment)` | `getFragment(): string` | `'footer'` +| | `getAuthority(): string` | `'john:xyz*12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | URL completa + +Advertencia: Cuando trabaje con una URL obtenida de una [petición HTTP|request], tenga en cuenta que no contendrá el fragmento, ya que el navegador no lo envía al servidor. + +También podemos trabajar con parámetros de consulta individuales usando: .[language-php] -| Setter | Getter +| Setter | Getter |--------------------------------------------------- -| `setQuery(string\|array $query)` | `getQueryParameters(): array` -| `setQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` +| `setQuery(string\|array $query)` | `getQueryParameters(): array` +| `setQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` -El método `getDomain(int $level = 2)` devuelve la parte derecha o izquierda del host. Así funciona si el host es `www.nette.org`: + +getDomain(int $level = 2): string .[method] +------------------------------------------- +Devuelve la parte derecha o izquierda del host. Así funciona si el host es `www.nette.org`: .[language-php] -| `getDomain(1)` | `'org'` -| `getDomain(2)` | `'nette.org'` -| `getDomain(3)` | `'www.nette.org'` -| `getDomain(0)` | `'www.nette.org'` -| `getDomain(-1)` | `'www.nette'` -| `getDomain(-2)` | `'www'` -| `getDomain(-3)` | `''` +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `''` -La clase `Url` implementa la interfaz `JsonSerializable` y tiene un método `__toString()` para que el objeto pueda imprimirse o utilizarse en datos pasados a `json_encode()`. +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +Comprueba si dos URLs son idénticas. ```php -echo $url; -echo json_encode([$url]); +$url->isEqual('https://nette.org'); ``` -El método `isEqual(string|Url $anotherUrl): bool` comprueba si las dos URL son idénticas. + +Url::isAbsolute(string $url): bool .[method]{data-version:3.3.2} +---------------------------------------------------------------- +Comprueba si la URL es absoluta. Una URL se considera absoluta si comienza con un esquema (p. ej., http, https, ftp) seguido de dos puntos. ```php -$url->isEqual('https://nette.org'); +Url::isAbsolute('https://nette.org'); // true +Url::isAbsolute('//nette.org'); // false +``` + + +Url::removeDotSegments(string $path): string .[method]{data-version:3.3.2} +-------------------------------------------------------------------------- +Normaliza la ruta en la URL eliminando los segmentos especiales `.` y `..`. El método elimina los elementos redundantes de la ruta de la misma manera que lo hacen los navegadores web. + +```php +Url::removeDotSegments('/path/../subtree/./file.txt'); // '/subtree/file.txt' +Url::removeDotSegments('/../foo/./bar'); // '/foo/bar' +Url::removeDotSegments('./today/../file.txt'); // 'file.txt' ``` -UrlImmutable .[#toc-urlimmutable] -================================= +UrlImmutable +============ -La clase [api:Nette\Http\UrlImmutable] es una alternativa inmutable a la clase `Url` (al igual que en PHP `DateTimeImmutable` es una alternativa inmutable a `DateTime`). En lugar de setters, tiene los llamados withers, que no cambian el objeto, sino que devuelven nuevas instancias con un valor modificado: +La clase [api:Nette\Http\UrlImmutable] es una alternativa inmutable a la clase [#Url] (similar a como `DateTimeImmutable` en PHP es la alternativa inmutable a `DateTime`). En lugar de setters, tiene los llamados withers, que no modifican el objeto, sino que devuelven nuevas instancias con el valor modificado: ```php use Nette\Http\UrlImmutable; @@ -111,53 +143,96 @@ $url = new UrlImmutable( $newUrl = $url ->withUser('') ->withPassword('') - ->withPath('/en/'); + ->withPath('/es/'); + +echo $newUrl; // 'http://john:xyz%2A12@nette.org:8080/es/?name=param#footer' +``` + +La clase `UrlImmutable` implementa la interfaz `JsonSerializable` y tiene un método `__toString()`, por lo que el objeto se puede imprimir o usar en datos pasados a `json_encode()`. -echo $newUrl; // 'http://nette.org:8080/en/?name=param#footer' +```php +echo $url; +echo json_encode([$url]); ``` -Los siguientes métodos están disponibles para obtener o cambiar componentes URL individuales: + +Componentes de URL .[method] +---------------------------- + +Para devolver o cambiar los componentes individuales de la URL sirven los métodos: .[language-php] -| Obtener | Valor devuelto +| Wither | Getter | Valor devuelto |-------------------------------------------------------------------------------------------- -| `withScheme(string $scheme)` | `getScheme(): string` | `'http'` -| `withUser(string $user)` | `getUser(): string` | `'john'` -| `withPassword(string $password)` | `getPassword(): string` | `'xyz*12'` -| `withHost(string $host)` | `getHost(): string` | `'nette.org'` -| `withPort(int $port)` | `getPort(): ?int` | `8080` -| | `getDefaultPort(): ?int` | `80` -| `withPath(string $path)` | `getPath(): string` | `'/en/download'` -| `withQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` -| `withFragment(string $fragment)` | `getFragment(): string` | `'footer'` -| | `getAuthority(): string` | `'nette.org:8080'` -| | `getHostUrl(): string` | `'http://nette.org:8080'` -| | `getAbsoluteUrl(): string` | URL completa - -También podemos operar con parámetros de consulta individuales utilizando: +| `withScheme(string $scheme)` | `getScheme(): string` | `'http'` +| `withUser(string $user)` | `getUser(): string` | `'john'` +| `withPassword(string $password)` | `getPassword(): string` | `'xyz*12'` +| `withHost(string $host)` | `getHost(): string` | `'nette.org'` +| `withPort(int $port)` | `getPort(): ?int` | `8080` +| | `getDefaultPort(): ?int` | `80` +| `withPath(string $path)` | `getPath(): string` | `'/en/download'` +| `withQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` +| `withFragment(string $fragment)` | `getFragment(): string` | `'footer'` +| | `getAuthority(): string` | `'john:xyz*12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | URL completa + +El método `withoutUserInfo()` elimina `user` y `password`. + +También podemos trabajar con parámetros de consulta individuales usando: .[language-php] -| Wither | Getter +| Wither | Getter |----------------------------------------------- -| `withQuery(string\|array $query)` | `getQueryParameters(): array` -| `withQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` +| `withQuery(string\|array $query)` | `getQueryParameters(): array` +| `withQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` -El método `getDomain(int $level = 2)` funciona igual que el método en `Url`. El método `withoutUserInfo()` elimina `user` y `password`. -La clase `UrlImmutable` implementa la interfaz `JsonSerializable` y tiene un método `__toString()` para que el objeto pueda imprimirse o utilizarse en datos pasados a `json_encode()`. +getDomain(int $level = 2): string .[method] +------------------------------------------- +Devuelve la parte derecha o izquierda del host. Así funciona si el host es `www.nette.org`: + +.[language-php] +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `''` + + +resolve(string $reference): UrlImmutable .[method]{data-version:3.3.2} +---------------------------------------------------------------------- +Deriva una URL absoluta de la misma manera que un navegador procesa los enlaces en una página HTML: +- si el enlace es una URL absoluta (contiene un esquema), se utiliza sin cambios +- si el enlace comienza con `//`, solo se toma el esquema de la URL actual +- si el enlace comienza con `/`, se crea una ruta absoluta desde la raíz del dominio +- en otros casos, la URL se construye relativamente a la ruta actual ```php -echo $url; -echo json_encode([$url]); +$url = new UrlImmutable('https://example.com/path/page'); +echo $url->resolve('../foo'); // 'https://example.com/foo' +echo $url->resolve('/bar'); // 'https://example.com/bar' +echo $url->resolve('sub/page.html'); // 'https://example.com/path/sub/page.html' +``` + + +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +Comprueba si dos URLs son idénticas. + +```php +$url->isEqual('https://nette.org'); ``` -El método `isEqual(string|Url $anotherUrl): bool` comprueba si las dos URL son idénticas. +UrlScript +========= -UrlScript .[#toc-urlscript] -=========================== +La clase [api:Nette\Http\UrlScript] es descendiente de [#UrlImmutable] y la extiende con componentes virtuales adicionales de la URL, como el directorio raíz del proyecto, etc. Al igual que la clase padre, es un objeto inmutable. -La clase [api:Nette\Http\UrlScript] es descendiente de `UrlImmutable` y distingue además estas partes lógicas de la URL: +El siguiente diagrama muestra los componentes que UrlScript reconoce: /--pre baseUrl basePath relativePath relativeUrl @@ -169,17 +244,23 @@ La clase [api:Nette\Http\UrlScript] es descendiente de `UrlImmutable` y distingu scriptPath pathInfo \-- -Los siguientes métodos están disponibles para obtener estas partes: +- `baseUrl` es la dirección URL base de la aplicación, incluido el dominio y la parte de la ruta al directorio raíz de la aplicación +- `basePath` es la parte de la ruta al directorio raíz de la aplicación +- `scriptPath` es la ruta al script actual +- `relativePath` es el nombre del script (posiblemente segmentos de ruta adicionales) relativo a basePath +- `relativeUrl` es toda la parte de la URL después de baseUrl, incluida la cadena de consulta y el fragmento. +- `pathInfo` hoy en día una parte de la URL poco utilizada después del nombre del script + +Para devolver partes de la URL están disponibles los métodos: .[language-php] -| Getter | Valor devuelto +| Getter | Valor devuelto |------------------------------------------------ -| `getScriptPath(): string` | `'/admin/script.php'` -| `getBasePath(): string` | `'/admin/'` -| `getBaseUrl(): string` | `'http://nette.org/admin/'` -| `getRelativePath(): string` | `'script.php'` -| `getRelativeUrl(): string` | `'script.php/pathinfo/?name=param#footer'` -| `getPathInfo(): string` | `'/pathinfo/'` - - -No creamos el objeto `UrlScript` directamente, sino que el método [Nette\Http\Request::getUrl() |request] lo devuelve. +| `getScriptPath(): string` | `'/admin/script.php'` +| `getBasePath(): string` | `'/admin/'` +| `getBaseUrl(): string` | `'http://nette.org/admin/'` +| `getRelativePath(): string` | `'script.php'` +| `getRelativeUrl(): string` | `'script.php/pathinfo/?name=param#footer'` +| `getPathInfo(): string` | `'/pathinfo/'` + +Los objetos `UrlScript` normalmente no los creamos directamente, sino que los devuelve el método [Nette\Http\Request::getUrl()|request] con los componentes ya correctamente configurados para la petición HTTP actual. diff --git a/http/fr/@home.texy b/http/fr/@home.texy index a6075160e5..dbef36f4e8 100644 --- a/http/fr/@home.texy +++ b/http/fr/@home.texy @@ -2,13 +2,13 @@ Nette HTTP ********** .[perex] -Le paquet `nette/http` encapsule la [demande |request] et la [réponse |response] [HTTP |request], le travail avec les [sessions] et l'[analyse et la construction des URL |urls]. +Le paquet `nette/http` encapsule la [requête HTTP |request] & la [réponse |response], le travail avec les [sessions |sessions] et [l'analyse et la composition d'URL |urls]. -Installation .[#toc-installation] ---------------------------------- +Installation +------------ -Téléchargez et installez le paquet en utilisant [Composer |best-practices:composer]: +La bibliothèque peut être téléchargée et installée en utilisant l'outil [Composer|best-practices:composer] : ```shell composer require nette/http diff --git a/http/fr/@left-menu.texy b/http/fr/@left-menu.texy index f4c54828f6..57940fc1f4 100644 --- a/http/fr/@left-menu.texy +++ b/http/fr/@left-menu.texy @@ -1,8 +1,8 @@ -Filets HTTP -*********** -- [Vue d'ensemble |@home] -- [Demande HTTP|request] -- [Réponse HTTP|response] -- [Sessions] -- [Utilitaire URL |urls] -- [Configuration] +Nette HTTP +********** +- [Introduction |@home] +- [Requête HTTP |request] +- [Réponse HTTP |response] +- [Sessions |Sessions] +- [Utilitaires URL |urls] +- [Configuration |configuration] diff --git a/http/fr/@meta.texy b/http/fr/@meta.texy new file mode 100644 index 0000000000..72ae4b8db8 --- /dev/null +++ b/http/fr/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentation Nette}} diff --git a/http/fr/configuration.texy b/http/fr/configuration.texy index 421bb438e1..8c6b5c7944 100644 --- a/http/fr/configuration.texy +++ b/http/fr/configuration.texy @@ -1,40 +1,40 @@ -Configuration de HTTP -********************* +Configuration HTTP +****************** .[perex] -Aperçu des options de configuration de la Nette HTTP. +Aperçu des options de configuration pour Nette HTTP. Si vous n'utilisez pas l'ensemble du framework, mais seulement cette bibliothèque, lisez [comment charger la configuration |bootstrap:]. -En-têtes HTTP .[#toc-http-headers] -================================== +En-têtes HTTP +============= ```neon http: - # les en-têtes qui sont envoyés avec chaque requête + # en-têtes qui seront envoyés avec chaque requête headers: X-Powered-By: MyCMS X-Content-Type-Options: nosniff - X-XSS-Protection: '1; mode=block'. + X-XSS-Protection: '1; mode=block' # affecte l'en-tête X-Frame-Options - frames: ... # (string|bool) a pour valeur par défaut 'SAMEORIGIN'. + frames: ... # (string|bool) la valeur par défaut est 'SAMEORIGIN' ``` -Pour des raisons de sécurité, le framework envoie un en-tête `X-Frame-Options: SAMEORIGIN`, qui indique qu'une page peut être affichée à l'intérieur d'une autre page (dans l'élément `<iframe>`) uniquement si celle-ci se trouve sur le même domaine. Cela peut être indésirable dans certaines situations (par exemple, si vous développez une application Facebook), le comportement peut donc être modifié en définissant les cadres `frames: http://allowed-host.com`. +Pour des raisons de sécurité, le framework envoie l'en-tête `X-Frame-Options: SAMEORIGIN`, qui indique que la page ne peut être affichée à l'intérieur d'une autre page (dans un élément `<iframe>`) que si elle se trouve sur le même domaine. Cela peut être indésirable dans certaines situations (par exemple, si vous développez une application pour Facebook), le comportement peut donc être modifié en définissant `frames: http://allowed-host.com` ou `frames: true`. -Politique de sécurité du contenu .[#toc-content-security-policy] ----------------------------------------------------------------- +Content Security Policy +----------------------- -Les en-têtes `Content-Security-Policy` (ci-après dénommés CSP) peuvent être facilement assemblés, leur description se trouve dans la [description CSP |https://content-security-policy.com]. Les directives CSP (telles que `script-src`) peuvent être écrites soit sous forme de chaînes de caractères conformément aux spécifications, soit sous forme de tableaux de valeurs pour une meilleure lisibilité. Il n'est alors pas nécessaire d'écrire des guillemets autour de mots-clés tels que `'self'`. Nette générera aussi automatiquement une valeur de `nonce`, donc `'nonce-y4PopTLM=='` sera envoyé dans l'en-tête. +Il est facile de construire des en-têtes `Content-Security-Policy` (ci-après CSP), dont la description se trouve dans la [description de CSP |https://content-security-policy.com]. Les directives CSP (comme par exemple `script-src`) peuvent être écrites soit comme des chaînes de caractères selon la spécification, soit comme des tableaux de valeurs pour une meilleure lisibilité. Il n'est alors pas nécessaire d'écrire des guillemets autour des mots-clés, comme par exemple `'self'`. Nette génère également automatiquement la valeur `nonce`, de sorte que l'en-tête contiendra par exemple `'nonce-y4PopTLM=='`. ```neon http: # Content Security Policy csp: - # chaîne selon la spécification CSP + # chaîne de caractères au format selon la spécification CSP default-src: "'self' https://example.com" # tableau de valeurs @@ -44,14 +44,14 @@ http: - self - https://example.com - # bool dans le cas de commutations + # booléen dans le cas des commutateurs upgrade-insecure-requests: true block-all-mixed-content: false ``` -Utilisez `<script n:nonce>...</script>` dans les modèles et la valeur nonce sera remplie automatiquement. Il est très facile de créer des sites Web sécurisés avec Nette. +Dans les templates, utilisez `<script n:nonce>...</script>` et la valeur nonce sera complétée automatiquement. Créer des sites web sécurisés dans Nette est vraiment facile. -De même, les en-têtes `Content-Security-Policy-Report-Only` (qui peuvent être utilisés en parallèle avec CSP) et [Feature Policy |https://developers.google.com/web/updates/2018/06/feature-policy] peuvent être ajoutés : +De même, il est possible de construire les en-têtes `Content-Security-Policy-Report-Only` (qui peuvent être utilisés en parallèle avec CSP) et [Feature Policy|https://developers.google.com/web/updates/2018/06/feature-policy] : ```neon http: @@ -69,91 +69,103 @@ http: ``` -Cookie HTTP .[#toc-http-cookie] -------------------------------- +Cookie HTTP +----------- -Vous pouvez modifier les valeurs par défaut de certains paramètres des méthodes [Nette\Http\Response::setCookie() |response#setCookie] et session. +Il est possible de modifier les valeurs par défaut de certains paramètres de la méthode [Nette\Http\Response::setCookie() |response#setCookie] et de la session. ```neon http: - # cookie scope by path - cookiePath: ... # (chaîne), la valeur par défaut est '/'. + # portée du cookie selon le chemin + cookiePath: ... # (string) la valeur par défaut est '/' - # quels hôtes sont autorisés à recevoir le cookie - cookieDomain: 'example.com' # (string|domain) a pour valeur par défaut unset + # domaines qui acceptent le cookie + cookieDomain: 'example.com' # (string|domain) la valeur par défaut n'est pas définie - # pour envoyer les cookies uniquement via HTTPS ? - cookieSecure: ... # (bool|auto) valeur par défaut: auto + # envoyer le cookie uniquement via HTTPS ? + cookieSecure: ... # (bool|auto) la valeur par défaut est auto - # désactive l'envoi du cookie que Nette utilise comme protection contre CSRF - disableNetteCookie: ... # (bool) valeur par défaut: false + # désactive l'envoi du cookie utilisé par Nette comme protection contre CSRF + disableNetteCookie: ... # (bool) la valeur par défaut est false ``` -L'option `cookieDomain` détermine quels domaines (origines) peuvent accepter les cookies. Si elle n'est pas spécifiée, le cookie est accepté par le même (sous-)domaine que celui qu'il définit, *à l'exclusion* de leurs sous-domaines. Si `cookieDomain` est spécifié, les sous-domaines sont également inclus. Par conséquent, spécifier `cookieDomain` est moins restrictif que de l'omettre. +L'attribut `cookieDomain` détermine quels domaines peuvent accepter le cookie. S'il n'est pas spécifié, le cookie est accepté par le même (sous-)domaine qui l'a défini, *mais pas* par ses sous-domaines. Si `cookieDomain` est spécifié, les sous-domaines sont également inclus. Par conséquent, spécifier `cookieDomain` est moins restrictif que de l'omettre. -Par exemple, si `cookieDomain: nette.org` est défini, le cookie est également disponible sur tous les sous-domaines comme `doc.nette.org`. Ceci peut également être réalisé avec la valeur spéciale `domain`, c'est-à-dire `cookieDomain: domain`. +Par exemple, avec `cookieDomain: nette.org`, les cookies sont également disponibles sur tous les sous-domaines comme `doc.nette.org`. Le même résultat peut également être obtenu en utilisant la valeur spéciale `domain`, c'est-à-dire `cookieDomain: domain`. -La valeur par défaut de `cookieSecure` est `auto`, ce qui signifie que si le site Web fonctionne en HTTPS, les cookies seront envoyés avec le drapeau `Secure` et ne seront donc disponibles que via HTTPS. +La valeur par défaut `auto` pour l'attribut `cookieSecure` signifie que si le site fonctionne en HTTPS, les cookies seront envoyés avec l'indicateur `Secure` et ne seront donc disponibles que via HTTPS. -Proxy HTTP .[#toc-http-proxy] ------------------------------ +Proxy HTTP +---------- -Si le site fonctionne derrière un proxy HTTP, entrez l'adresse IP du proxy pour que la détection des connexions HTTPS fonctionne correctement, ainsi que l'adresse IP du client. C'est-à-dire pour que [Nette\Http\Request::getRemoteAddress() |request#getRemoteAddress] et [isSecured() |request#isSecured] renvoient les bonnes valeurs et que les liens soient générés avec le protocole `https:` dans les modèles. +Si le site fonctionne derrière un proxy HTTP, spécifiez son adresse IP pour que la détection de la connexion via HTTPS et l'adresse IP du client fonctionnent correctement. C'est-à-dire pour que les fonctions [Nette\Http\Request::getRemoteAddress() |request#getRemoteAddress] et [isSecured() |request#isSecured] retournent les bonnes valeurs et que les liens avec le protocole `https:` soient générés dans les templates. ```neon http: - # adresse IP, plage (ex. 127.0.0.1/8) ou tableau de ces valeurs - proxy: 127.0.0.1 # (string|string[]) La valeur par défaut est none. + # Adresse IP, plage (par ex. 127.0.0.1/8) ou tableau de ces valeurs + proxy: 127.0.0.1 # (string|string[]) la valeur par défaut n'est pas définie ``` -Session .[#toc-session] -======================= +Session +======= -Paramètres de base des [sessions]: +Configuration de base des [sessions |sessions] : ```neon session: - # affiche le panneau de session dans Tracy Bar ? - debugger: ... # (bool) par défaut à false + # afficher le panneau de session dans la barre Tracy ? + debugger: ... # (bool) la valeur par défaut est false - # temps d'inactivité après lequel la session expire - expiration: 14 jours # (string) a pour valeur par défaut '3 hours' (3 heures) + # durée d'inactivité après laquelle la session expire + expiration: 14 days # (string) la valeur par défaut est '3 hours' - # quand démarrer la session ? - autoStart: ... # (smart|always|never) Valeur par défaut: 'smart'. + # quand la session doit-elle démarrer ? + autoStart: ... # (smart|always|never) la valeur par défaut est 'smart' - # handler, service qui implémente l'interface SessionHandlerInterface + # gestionnaire, service implémentant l'interface SessionHandlerInterface handler: @handlerService ``` -L'option `autoStart` contrôle quand démarrer la session. La valeur `always` signifie que la session est toujours lancée au démarrage de l'application. La valeur `smart` signifie que la session sera démarrée au démarrage de l'application uniquement si elle existe déjà, ou au moment où nous voulons lire ou écrire dans celle-ci. Enfin, la valeur `never` désactive le démarrage automatique de la session. +L'option `autoStart` contrôle quand la session doit démarrer. La valeur `always` signifie que la session démarrera toujours au lancement de l'application. La valeur `smart` signifie que la session ne démarrera au lancement de l'application que si elle existe déjà, ou au moment où nous voulons lire ou écrire dedans. Enfin, la valeur `never` interdit le démarrage automatique de la session. -Vous pouvez également définir toutes les [directives de session |https://www.php.net/manual/en/session.configuration.php] PHP (au format camelCase) ainsi que [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. Exemple : +De plus, il est possible de définir toutes les [directives de session PHP |https://www.php.net/manual/en/session.configuration.php] (au format camelCase) ainsi que [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. Exemple : ```neon session: - # 'session.name' est écrit comme 'name'. + # 'session.name' s'écrit 'name' name: MYID - # 'session.save_path' écrit en tant que 'savePath'. + # 'session.save_path' s'écrit 'savePath' savePath: "%tempDir%/sessions" ``` -Cookie de session .[#toc-session-cookie] ----------------------------------------- +Cookie de session +----------------- -Le cookie de session est envoyé avec les mêmes paramètres que les [autres cookies |#HTTP cookie], mais vous pouvez les modifier : +Le cookie de session est envoyé avec les mêmes paramètres que les [autres cookies |#Cookie HTTP], mais vous pouvez modifier ceux-ci pour lui : ```neon session: - # quels hôtes sont autorisés à recevoir le cookie - cookieDomain: 'exemple.com' # (string|domain) + # domaines qui acceptent le cookie + cookieDomain: 'example.com' # (string|domain) - # restrictions lors de l'accès à une demande d'origine croisée - cookieSamesite: None # (Strict|Lax|None) valeur par défaut: Lax + # restriction lors de l'accès depuis un autre domaine + cookieSamesite: None # (Strict|Lax|None) la valeur par défaut est Lax ``` -L'option `cookieSamesite` détermine si le cookie est envoyé avec les [demandes d'origine croisée |nette:glossary#SameSite cookie], ce qui offre une certaine protection contre les attaques de [type Cross-Site Request Forgery |nette:glossary#cross-site-request-forgery-csrf]. +L'attribut `cookieSamesite` influence si le cookie sera envoyé lors d'un [accès depuis un domaine différent |nette:glossary#Cookie SameSite], ce qui offre une certaine protection contre les attaques [Cross-Site Request Forgery |nette:glossary#Cross-Site Request Forgery CSRF] (CSRF). + + +Services DI +=========== + +Ces services sont ajoutés au conteneur DI : + +| Nom | Type | Description +|-----------------------------------------------------| +| `http.request` | [api:Nette\Http\Request] | [Requête HTTP | request] +| `http.response` | [api:Nette\Http\Response] | [Réponse HTTP | response] +| `session.session` | [api:Nette\Http\Session] | [Gestion de session | sessions] diff --git a/http/fr/request.texy b/http/fr/request.texy index 8f6dc7b848..984a96e9df 100644 --- a/http/fr/request.texy +++ b/http/fr/request.texy @@ -1,85 +1,85 @@ -Demande HTTP +Requête HTTP ************ .[perex] -Nette encapsule la requête HTTP dans des objets avec une API compréhensible tout en fournissant un filtre de désinfection. +Nette encapsule la requête HTTP dans des objets avec une API compréhensible et fournit en même temps un filtre d'assainissement. -Une requête HTTP est un objet [api:Nette\Http\Request], que vous obtenez en le passant en utilisant l'[injection de dépendances |dependency-injection:passing-dependencies]. Dans les présentateurs, il suffit d'appeler `$httpRequest = $this->getHttpRequest()`. +La requête HTTP est représentée par l'objet [api:Nette\Http\Request]. Si vous travaillez avec Nette, cet objet est automatiquement créé par le framework et vous pouvez vous le faire passer via l'[injection de dépendances |dependency-injection:passing-dependencies]. Dans les presenters, il suffit d'appeler la méthode `$this->getHttpRequest()`. Si vous travaillez en dehors du Nette Framework, vous pouvez créer l'objet à l'aide de [#RequestFactory]. -Ce qui est important, c'est que Nette, en [créant |#RequestFactory] cet objet, efface tous les paramètres d'entrée GET, POST et COOKIE ainsi que les URL des caractères de contrôle et des séquences UTF-8 invalides. Vous pouvez donc continuer à travailler avec les données en toute sécurité. Les données nettoyées sont ensuite utilisées dans les présentateurs et les formulaires. +Un grand avantage de Nette est que lors de la création de l'objet, il nettoie automatiquement tous les paramètres d'entrée GET, POST, COOKIE ainsi que l'URL des caractères de contrôle et des séquences UTF-8 invalides. Vous pouvez ensuite travailler en toute sécurité avec ces données. Les données nettoyées sont ensuite utilisées dans les presenters et les formulaires. -→ [Installation et exigences |@home#Installation] +→ [Installation et prérequis |@home#Installation] -Nette\Http\Request .[#toc-nette-http-request] -============================================= +Nette\Http\Request +================== -Cet objet est immuable. Il n'a pas de setters, il n'a qu'un seul appelé wither `withUrl()`, qui ne change pas l'objet, mais renvoie une nouvelle instance avec une valeur modifiée. +Cet objet est immutable (immuable). Il n'a pas de setters, il n'a qu'un seul "wither" `withUrl()`, qui ne modifie pas l'objet, mais retourne une nouvelle instance avec la valeur modifiée. withUrl(Nette\Http\UrlScript $url): Nette\Http\Request .[method] ---------------------------------------------------------------- -Renvoie un clone avec une URL différente. +Retourne un clone avec une URL différente. getUrl(): Nette\Http\UrlScript .[method] ---------------------------------------- -Renvoie l'URL de la demande sous forme d'objet [UrlScript |urls#UrlScript]. +Retourne l'URL de la requête sous forme d'objet [UrlScript |urls#UrlScript]. ```php $url = $httpRequest->getUrl(); -echo $url; // https://nette.org/en/documentation?action=edit +echo $url; // https://doc.nette.org/cs/?action=edit echo $url->getHost(); // nette.org ``` -Les navigateurs n'envoient pas de fragment au serveur, donc `$url->getFragment()` retournera une chaîne vide. +Attention : les navigateurs n'envoient pas le fragment au serveur, donc `$url->getFragment()` retournera une chaîne vide. -getQuery(string $key=null): string|array|null .[method] -------------------------------------------------------- -Renvoie les paramètres de la requête GET : +getQuery(?string $key=null): string|array|null .[method] +-------------------------------------------------------- +Retourne les paramètres GET de la requête. ```php -$all = $httpRequest->getQuery(); // tableau de tous les paramètres URL -$id = $httpRequest->getQuery('id'); // renvoie le paramètre GET 'id' (ou null) +$all = $httpRequest->getQuery(); // retourne un tableau de tous les paramètres de l'URL +$id = $httpRequest->getQuery('id'); // retourne le paramètre GET 'id' (ou null) ``` -getPost(string $key=null): string|array|null .[method] ------------------------------------------------------- -Renvoie les paramètres de la demande POST : +getPost(?string $key=null): string|array|null .[method] +------------------------------------------------------- +Retourne les paramètres POST de la requête. ```php -$all = $httpRequest->getPost(); // tableau de tous les paramètres POST -$id = $httpRequest->getPost('id'); // renvoie le paramètre POST 'id' (ou null) +$all = $httpRequest->getPost(); // retourne un tableau de tous les paramètres de POST +$id = $httpRequest->getPost('id'); // retourne le paramètre POST 'id' (ou null) ``` getFile(string|string[] $key): Nette\Http\FileUpload|array|null .[method] ------------------------------------------------------------------------- -Renvoie le [téléchargement |#Uploaded Files] en tant qu'objet [api:Nette\Http\FileUpload]: +Retourne l'[upload |#Fichiers uploadés] sous forme d'objet [api:Nette\Http\FileUpload] : ```php $file = $httpRequest->getFile('avatar'); -if ($file->hasFile()) { // un fichier a-t-il été téléchargé ? +if ($file?->hasFile()) { // un fichier a-t-il été uploadé ? $file->getUntrustedName(); // nom du fichier envoyé par l'utilisateur - $file->getSanitizedName(); // le nom sans les caractères dangereux + $file->getSanitizedName(); // nom sans caractères dangereux } ``` -Spécifier un tableau de clés pour accéder à la structure du sous-arbre. +Pour accéder à une structure imbriquée, spécifiez un tableau de clés. ```php //<input type="file" name="my-form[details][avatar]" multiple> $file = $request->getFile(['my-form', 'details', 'avatar']); ``` -Étant donné que vous ne pouvez pas faire confiance aux données de l'extérieur et que vous ne vous fiez donc pas à la forme de la structure, cette méthode est plus sûre que la méthode `$request->getFiles()['my-form']['details']['avatar']`qui peut échouer. +Comme on ne peut pas faire confiance aux données externes et donc pas non plus à la forme de la structure des fichiers, cette méthode est plus sûre que par exemple `$request->getFiles()['my-form']['details']['avatar']`, qui peut échouer. getFiles(): array .[method] --------------------------- -Renvoie l'arbre des [fichiers de téléchargement |#Uploaded Files] dans une structure normalisée, avec chaque feuille une instance de [api:Nette\Http\FileUpload]: +Retourne l'arbre de [tous les uploads |#Fichiers uploadés] dans une structure normalisée, dont les feuilles sont des objets [api:Nette\Http\FileUpload] : ```php $files = $httpRequest->getFiles(); @@ -88,7 +88,7 @@ $files = $httpRequest->getFiles(); getCookie(string $key): string|array|null .[method] --------------------------------------------------- -Renvoie un cookie ou `null` s'il n'existe pas. +Retourne un cookie ou `null` s'il n'existe pas. ```php $sessId = $httpRequest->getCookie('sess_id'); @@ -97,7 +97,7 @@ $sessId = $httpRequest->getCookie('sess_id'); getCookies(): array .[method] ----------------------------- -Renvoie tous les cookies : +Retourne tous les cookies. ```php $cookies = $httpRequest->getCookies(); @@ -106,16 +106,16 @@ $cookies = $httpRequest->getCookies(); getMethod(): string .[method] ----------------------------- -Renvoie la méthode HTTP avec laquelle la demande a été effectuée. +Retourne la méthode HTTP avec laquelle la requête a été effectuée. ```php -echo $httpRequest->getMethod(); // GET, POST, HEAD, PUT +$httpRequest->getMethod(); // GET, POST, HEAD, PUT ``` isMethod(string $method): bool .[method] ---------------------------------------- -Vérifie la méthode HTTP avec laquelle la demande a été faite. Le paramètre n'est pas sensible à la casse. +Teste la méthode HTTP avec laquelle la requête a été effectuée. Le paramètre est insensible à la casse. ```php if ($httpRequest->isMethod('GET')) // ... @@ -124,7 +124,7 @@ if ($httpRequest->isMethod('GET')) // ... getHeader(string $header): ?string .[method] -------------------------------------------- -Renvoie un en-tête HTTP ou `null` s'il n'existe pas. Le paramètre est insensible à la casse : +Retourne un en-tête HTTP ou `null` s'il n'existe pas. Le paramètre est insensible à la casse. ```php $userAgent = $httpRequest->getHeader('User-Agent'); @@ -133,7 +133,7 @@ $userAgent = $httpRequest->getHeader('User-Agent'); getHeaders(): array .[method] ----------------------------- -Renvoie tous les en-têtes HTTP sous forme de tableau associatif : +Retourne tous les en-têtes HTTP sous forme de tableau associatif. ```php $headers = $httpRequest->getHeaders(); @@ -141,19 +141,14 @@ echo $headers['Content-Type']; ``` -getReferer(): ?Nette\Http\UrlImmutable .[method] ------------------------------------------------- -De quelle URL l'utilisateur provient-il ? Attention, ce n'est pas du tout fiable. - - isSecured(): bool .[method] --------------------------- -La connexion est-elle cryptée (HTTPS) ? Vous devrez peut-être configurer [un proxy |configuration#HTTP proxy] pour une bonne fonctionnalité. +La connexion est-elle chiffrée (HTTPS) ? Pour un fonctionnement correct, il peut être nécessaire de [configurer le proxy |configuration#Proxy HTTP]. isSameSite(): bool .[method] ---------------------------- -La demande provient-elle du même (sous) domaine et est-elle initiée par un clic sur un lien ? Nette utilise le cookie `_nss` (anciennement `nette-samesite`) pour le détecter. +La requête provient-elle du même (sous-)domaine et est-elle initiée par un clic sur un lien ? Nette utilise le cookie `_nss` (auparavant `nette-samesite`) pour la détection. isAjax(): bool .[method] @@ -163,17 +158,17 @@ S'agit-il d'une requête AJAX ? getRemoteAddress(): ?string .[method] ------------------------------------- -Renvoie l'adresse IP de l'utilisateur. Il se peut que vous deviez configurer [un proxy |configuration#HTTP proxy] pour une bonne fonctionnalité. +Retourne l'adresse IP de l'utilisateur. Pour un fonctionnement correct, il peut être nécessaire de [configurer le proxy |configuration#Proxy HTTP]. getRemoteHost(): ?string .[method deprecated] --------------------------------------------- -Renvoie la traduction DNS de l'adresse IP de l'utilisateur. Il se peut que vous deviez configurer [un proxy |configuration#HTTP proxy] pour une bonne fonctionnalité. +Retourne la résolution DNS de l'adresse IP de l'utilisateur. Pour un fonctionnement correct, il peut être nécessaire de [configurer le proxy |configuration#Proxy HTTP]. -getBasicCredentials(): ?string .[method] ----------------------------------------- -Renvoie les informations d'[authentification HTTP de base |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication]. +getBasicCredentials(): ?array .[method] +--------------------------------------- +Retourne les informations d'identification pour l'[authentification HTTP de base |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication]. ```php [$user, $password] = $httpRequest->getBasicCredentials(); @@ -182,7 +177,7 @@ Renvoie les informations d'[authentification HTTP de base |https://developer.moz getRawBody(): ?string .[method] ------------------------------- -Renvoie le corps de la requête HTTP : +Retourne le corps de la requête HTTP. ```php $body = $httpRequest->getRawBody(); @@ -191,141 +186,148 @@ $body = $httpRequest->getRawBody(); detectLanguage(array $langs): ?string .[method] ----------------------------------------------- -Détection de la langue. En tant que paramètre `$lang`, nous passons un tableau des langues que l'application prend en charge, et la méthode renvoie la langue préférée du navigateur. Ce n'est pas de la magie, la méthode utilise simplement l'en-tête `Accept-Language`. Si aucune correspondance n'est trouvée, elle renvoie `null`. +Détecte la langue. Comme paramètre `$lang`, nous passons un tableau des langues que l'application supporte, et elle retourne celle que le navigateur du visiteur préférerait voir. Ce n'est pas de la magie, on utilise simplement l'en-tête `Accept-Language`. S'il n'y a pas de correspondance, retourne `null`. ```php -// En-tête envoyé par le navigateur: Accept-Language: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3 +// le navigateur envoie par ex. Accept-Language: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3 -$langs = ['hu', 'pl', 'en']; // Langues supportées par l'application +$langs = ['hu', 'pl', 'en']; // langues supportées par l'application echo $httpRequest->detectLanguage($langs); // en ``` -RequestFactory .[#toc-requestfactory] -===================================== +RequestFactory +============== -L'objet de la requête HTTP courante est créé par [api:Nette\Http\RequestFactory]. Si vous écrivez une application qui n'utilise pas de conteneur DI, vous créez une requête comme suit : +La classe [api:Nette\Http\RequestFactory] sert à créer une instance de `Nette\Http\Request`, qui représente la requête HTTP actuelle. (Si vous travaillez avec Nette, l'objet de requête HTTP est automatiquement créé par le framework.) ```php $factory = new Nette\Http\RequestFactory; $httpRequest = $factory->fromGlobals(); ``` -RequestFactory peut être configuré avant d'appeler `fromGlobals()`. Nous pouvons désactiver toute la désinfection des paramètres d'entrée à partir de séquences UTF-8 invalides en utilisant `$factory->setBinary()`. Et aussi configurer un serveur proxy, ce qui est important pour la détection correcte de l'adresse IP de l'utilisateur en utilisant `$factory->setProxy(...)`. +La méthode `fromGlobals()` crée l'objet de requête sur la base des variables globales PHP actuelles (`$_GET`, `$_POST`, `$_COOKIE`, `$_FILES` et `$_SERVER`). Lors de la création de l'objet, elle nettoie automatiquement tous les paramètres d'entrée GET, POST, COOKIE ainsi que l'URL des caractères de contrôle et des séquences UTF-8 invalides, ce qui garantit la sécurité lors du travail ultérieur avec ces données. -Il est possible de nettoyer les URL des caractères qui peuvent s'y introduire en raison de systèmes de commentaires mal implémentés sur divers autres sites Web en utilisant des filtres : +RequestFactory peut être configuré avant d'appeler `fromGlobals()` : + +- avec la méthode `$factory->setBinary()` , vous désactivez le nettoyage automatique des paramètres d'entrée des caractères de contrôle et des séquences UTF-8 invalides. +- avec la méthode `$factory->setProxy(...)`, vous indiquez l'adresse IP du [serveur proxy |configuration#Proxy HTTP], ce qui est nécessaire pour une détection correcte de l'adresse IP de l'utilisateur. + +RequestFactory permet de définir des filtres qui transforment automatiquement des parties de l'URL de la requête. Ces filtres suppriment les caractères indésirables de l'URL, qui peuvent y être insérés par exemple par une implémentation incorrecte des systèmes de commentaires sur différents sites web : ```php -// supprimer les espaces du chemin +// suppression des espaces du chemin $requestFactory->urlFilters['path']['%20'] = ''; -// supprime le point, la virgule ou la parenthèse droite de la fin de l'URL +// suppression du point, de la virgule ou de la parenthèse droite de la fin de l'URI $requestFactory->urlFilters['url']['[.,)]$'] = ''; -// nettoyer le chemin des barres obliques dupliquées (filtre par défaut) +// nettoyage du chemin des doubles barres obliques (filtre par défaut) $requestFactory->urlFilters['path']['/{2,}'] = '/'; ``` +La première clé `'path'` ou `'url'` détermine à quelle partie de l'URL le filtre s'applique. La deuxième clé est une expression régulière à rechercher, et la valeur est le remplacement qui sera utilisé à la place du texte trouvé. -Fichiers téléchargés .[#toc-uploaded-files] -=========================================== -La méthode `Nette\Http\Request::getFiles()` renvoie un arbre de fichiers téléchargés dans une structure normalisée, chaque feuille étant une instance de [api:Nette\Http\FileUpload]. Ces objets encapsulent les données soumises par l'élément de formulaire `<input type=file>` élément de formulaire. +Fichiers uploadés +================= -La structure reflète la dénomination des éléments en HTML. Dans l'exemple le plus simple, il pourrait s'agir d'un seul élément de formulaire nommé soumis comme suit : +La méthode `Nette\Http\Request::getFiles()` retourne un tableau de tous les uploads dans une structure normalisée, dont les feuilles sont des objets [api:Nette\Http\FileUpload]. Ceux-ci encapsulent les données envoyées par l'élément de formulaire `<input type=file>`. + +La structure reflète la dénomination des éléments en HTML. Dans le cas le plus simple, il peut s'agir d'un seul élément de formulaire nommé envoyé comme : ```latte <input type="file" name="avatar"> ``` -Dans ce cas, le site `$request->getFiles()` renvoie un tableau : +Dans ce cas, `$request->getFiles()` retourne un tableau : ```php [ - 'avatar' => /* FileUpload instance */ + 'avatar' => /* Instance FileUpload */ ] ``` -L'objet `FileUpload` est créé même si l'utilisateur n'a pas envoyé de fichier ou si l'envoi a échoué. La méthode `hasFile()` renvoie vrai si un fichier a été envoyé : +L'objet `FileUpload` est créé même si l'utilisateur n'a envoyé aucun fichier ou si l'envoi a échoué. La méthode `hasFile()` retourne si un fichier a été envoyé : ```php -$request->getFile('avatar')->hasFile(); +$request->getFile('avatar')?->hasFile(); ``` -Dans le cas d'une entrée utilisant la notation tableau pour le nom : +Dans le cas d'un nom d'élément utilisant la notation pour les tableaux : ```latte <input type="file" name="my-form[details][avatar]"> ``` -l'arbre retourné finit par ressembler à ceci : +l'arbre retourné ressemble à ceci : ```php [ 'my-form' => [ 'details' => [ - 'avatar' => /* FileUpload instance */ + 'avatar' => /* Instance FileUpload */ ], ], ] ``` -Vous pouvez également créer des tableaux de fichiers : +Il est également possible de créer un tableau de fichiers : ```latte -<input type="file" name="my-form[details][avatars][] multiple"> +<input type="file" name="my-form[details][avatars][]" multiple> ``` -Dans ce cas, la structure ressemble à : +Dans ce cas, la structure ressemble à ceci : ```php [ 'my-form' => [ 'details' => [ 'avatars' => [ - 0 => /* FileUpload instance */, - 1 => /* FileUpload instance */, - 2 => /* FileUpload instance */, + 0 => /* Instance FileUpload */, + 1 => /* Instance FileUpload */, + 2 => /* Instance FileUpload */, ], ], ], ] ``` -La meilleure façon d'accéder à l'indice 1 d'un tableau imbriqué est la suivante : +Accéder à l'index 1 du tableau imbriqué se fait de préférence comme ceci : ```php $file = $request->getFile(['my-form', 'details', 'avatars', 1]); -if ($file instanceof FileUpload) { +if ($file instanceof Nette\Http\FileUpload) { // ... } ``` -Parce que vous ne pouvez pas faire confiance aux données de l'extérieur et donc ne pas vous fier à la forme de la structure, cette méthode est plus sûre que `$request->getFiles()['my-form']['details']['avatars'][1]`qui peut échouer. +Comme on ne peut pas faire confiance aux données externes et donc pas non plus à la forme de la structure des fichiers, cette méthode est plus sûre que par exemple `$request->getFiles()['my-form']['details']['avatars'][1]`, qui peut échouer. -Aperçu des méthodes de `FileUpload` .{toc: FileUpload} ------------------------------------------------------- +Aperçu des méthodes `FileUpload` .{toc: FileUpload} +--------------------------------------------------- hasFile(): bool .[method] ------------------------- -Renvoie `true` si l'utilisateur a téléchargé un fichier. +Retourne `true` si l'utilisateur a uploadé un fichier. isOk(): bool .[method] ---------------------- -Renvoie `true` si le fichier a été téléchargé avec succès. +Retourne `true` si le fichier a été uploadé avec succès. getError(): int .[method] ------------------------- -Renvoie le code d'erreur associé au fichier téléchargé. Il s'agit de l'une des constantes [UPLOAD_ERR_XXX |http://php.net/manual/en/features.file-upload.errors.php]. Si le fichier a été téléchargé avec succès, il renvoie `UPLOAD_ERR_OK`. +Retourne le code d'erreur lors de l'upload du fichier. Il s'agit de l'une des constantes [UPLOAD_ERR_XXX|https://www.php.net/manual/en/features.file-upload.errors.php]. Si l'upload s'est déroulé correctement, retourne `UPLOAD_ERR_OK`. move(string $dest) .[method] ---------------------------- -Déplace un fichier téléchargé vers un nouvel emplacement. Si le fichier de destination existe déjà, il sera écrasé. +Déplace le fichier uploadé vers un nouvel emplacement. Si le fichier de destination existe déjà, il sera écrasé. ```php $file->move('/path/to/files/name.ext'); @@ -334,12 +336,12 @@ $file->move('/path/to/files/name.ext'); getContents(): ?string .[method] -------------------------------- -Renvoie le contenu du fichier téléchargé. Si le téléchargement n'a pas réussi, il renvoie `null`. +Retourne le contenu du fichier uploadé. Si l'upload n'a pas réussi, retourne `null`. getContentType(): ?string .[method] ----------------------------------- -Détecte le type de contenu MIME du fichier téléchargé en se basant sur sa signature. Si le téléchargement n'a pas réussi ou si la détection a échoué, il renvoie `null`. +Détecte le type de contenu MIME du fichier uploadé sur la base de sa signature. Si l'upload n'a pas réussi ou si la détection a échoué, retourne `null`. .[caution] Nécessite l'extension PHP `fileinfo`. @@ -347,38 +349,49 @@ Nécessite l'extension PHP `fileinfo`. getUntrustedName(): string .[method] ------------------------------------ -Renvoie le nom du fichier original tel que soumis par le navigateur. +Retourne le nom original du fichier, tel qu'envoyé par le navigateur. .[caution] -Ne vous fiez pas à la valeur renvoyée par cette méthode. Un client pourrait envoyer un nom de fichier malveillant dans le but de corrompre ou de pirater votre application. +Ne faites pas confiance à la valeur retournée par cette méthode. Le client aurait pu envoyer un nom de fichier malveillant dans l'intention d'endommager ou de pirater votre application. getSanitizedName(): string .[method] ------------------------------------ -Renvoie le nom de fichier aseptisé. Il ne contient que des caractères ASCII `[a-zA-Z0-9.-]`. Si le nom ne contient pas de tels caractères, il renvoie "inconnu". Si le fichier est une image JPEG, PNG, GIF ou WebP, il renvoie l'extension de fichier correcte. +Retourne le nom de fichier assaini. Il ne contient que des caractères ASCII `[a-zA-Z0-9.-]`. Si le nom ne contient pas de tels caractères, retourne `'unknown'`. Si le fichier est une image au format JPEG, PNG, GIF, WebP ou AVIF, retourne également la bonne extension. + +.[caution] +Nécessite l'extension PHP `fileinfo`. + + +getSuggestedExtension(): ?string .[method]{data-version:3.2.4} +-------------------------------------------------------------- +Retourne l'extension de fichier appropriée (sans le point) correspondant au type MIME détecté. + +.[caution] +Nécessite l'extension PHP `fileinfo`. getUntrustedFullPath(): string .[method] ---------------------------------------- -Renvoie le chemin complet d'origine tel qu'il a été soumis par le navigateur lors du téléchargement du répertoire. Le chemin complet n'est disponible qu'à partir de la version 8.1 de PHP. Dans les versions précédentes, cette méthode renvoie le nom du fichier non fiable. +Retourne le chemin d'accès original du fichier, tel qu'envoyé par le navigateur lors de l'upload d'un dossier. Le chemin complet n'est disponible qu'en PHP 8.1 et supérieur. Dans les versions précédentes, cette méthode retourne le nom de fichier original. .[caution] -Ne faites pas confiance à la valeur renvoyée par cette méthode. Un client pourrait envoyer un nom de fichier malveillant dans le but de corrompre ou de pirater votre application. +Ne faites pas confiance à la valeur retournée par cette méthode. Le client aurait pu envoyer un nom de fichier malveillant dans l'intention d'endommager ou de pirater votre application. getSize(): int .[method] ------------------------ -Renvoie la taille du fichier téléchargé. Si le téléchargement n'a pas réussi, il renvoie `0`. +Retourne la taille du fichier uploadé. Si l'upload n'a pas réussi, retourne `0`. getTemporaryFile(): string .[method] ------------------------------------ -Renvoie le chemin de l'emplacement temporaire du fichier téléchargé. Si le téléchargement n'a pas réussi, il renvoie `''`. +Retourne le chemin d'accès à l'emplacement temporaire du fichier uploadé. Si l'upload n'a pas réussi, retourne `''`. isImage(): bool .[method] ------------------------- -Renvoie `true` si le fichier téléchargé est une image JPEG, PNG, GIF ou WebP. La détection est basée sur sa signature. L'intégrité de l'ensemble du fichier n'est pas vérifiée. Vous pouvez savoir si une image n'est pas corrompue, par exemple en essayant de [la charger |#toImage]. +Retourne `true` si le fichier uploadé est une image au format JPEG, PNG, GIF, WebP ou AVIF. La détection est basée sur sa signature et ne vérifie pas l'intégrité de l'ensemble du fichier. On peut vérifier si une image n'est pas endommagée, par exemple, en essayant de la [charger |#toImage]. .[caution] Nécessite l'extension PHP `fileinfo`. @@ -386,9 +399,9 @@ Nécessite l'extension PHP `fileinfo`. getImageSize(): ?array .[method] -------------------------------- -Retourne une paire de `[width, height]` avec les dimensions de l'image téléchargée. Si le téléchargement n'a pas réussi ou si l'image n'est pas valide, il renvoie `null`. +Retourne une paire `[largeur, hauteur]` avec les dimensions de l'image uploadée. Si l'upload n'a pas réussi ou s'il ne s'agit pas d'une image valide, retourne `null`. toImage(): Nette\Utils\Image .[method] -------------------------------------- -Charge une image sous la forme d'un objet [Image |utils:images]. Si le chargement n'a pas réussi ou s'il ne s'agit pas d'une image valide, il lève une exception `Nette\Utils\ImageException`. +Charge l'image comme un objet [Image|utils:images]. Si l'upload n'a pas réussi ou s'il ne s'agit pas d'une image valide, lève une exception `Nette\Utils\ImageException`. diff --git a/http/fr/response.texy b/http/fr/response.texy index c42a4ac920..0c9483cb66 100644 --- a/http/fr/response.texy +++ b/http/fr/response.texy @@ -2,22 +2,22 @@ Réponse HTTP ************ .[perex] -Nette encapsule la réponse HTTP dans des objets avec une API compréhensible tout en fournissant un filtre de désinfection. +Nette encapsule la réponse HTTP dans des objets avec une API compréhensible. -Une réponse HTTP est un objet [api:Nette\Http\Response], que vous obtenez en le passant en utilisant l'[injection de dépendances |dependency-injection:passing-dependencies]. Dans les présentateurs, il suffit d'appeler `$httpResponse = $this->getHttpResponse()`. +La réponse HTTP est représentée par l'objet [api:Nette\Http\Response]. Si vous travaillez avec Nette, cet objet est automatiquement créé par le framework et vous pouvez vous le faire passer via l'[injection de dépendances |dependency-injection:passing-dependencies]. Dans les presenters, il suffit d'appeler la méthode `$this->getHttpResponse()`. -→ [Installation et exigences |@home#Installation] +→ [Installation et prérequis |@home#Installation] -Nette\Http\Réponse .[#toc-nette-http-response] -============================================== +Nette\Http\Response +=================== -Contrairement à [Nette\Http\Request |request], cet objet est mutable, donc vous pouvez utiliser des setters pour changer l'état, c'est-à-dire pour envoyer des en-têtes. Rappelez-vous que tous les setters **doivent être appelés avant l'envoi de toute sortie réelle.** La méthode `isSent()` indique si la sortie a été envoyée. Si elle renvoie `true`, chaque tentative d'envoyer un en-tête lève une exception `Nette\InvalidStateException`. +L'objet, contrairement à [Nette\Http\Request|request], est mutable, c'est-à-dire qu'à l'aide de setters, vous pouvez modifier l'état, par exemple envoyer des en-têtes. N'oubliez pas que tous les setters doivent être appelés **avant l'envoi de toute sortie.** La méthode `isSent()` indique si la sortie a déjà été envoyée. Si elle retourne `true`, toute tentative d'envoi d'un en-tête lèvera une exception `Nette\InvalidStateException`. -setCode(int $code, string $reason=null) .[method] -------------------------------------------------- -Modifie un [code de réponse |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10] d'état. Pour une meilleure lisibilité du code source, il est recommandé d'utiliser des [constantes prédéfinies |api:Nette\Http\IResponse] plutôt que des nombres réels. +setCode(int $code, ?string $reason=null) .[method] +-------------------------------------------------- +Modifie le [code de statut de la réponse |https://developer.mozilla.org/fr/docs/Web/HTTP/Status]. Pour une meilleure lisibilité du code source, nous recommandons d'utiliser des [constantes prédéfinies |api:Nette\Http\IResponse] pour le code au lieu de chiffres. ```php $httpResponse->setCode(Nette\Http\Response::S404_NotFound); @@ -26,17 +26,17 @@ $httpResponse->setCode(Nette\Http\Response::S404_NotFound); getCode(): int .[method] ------------------------ -Renvoie le code d'état de la réponse. +Retourne le code de statut de la réponse. isSent(): bool .[method] ------------------------ -Indique si les en-têtes ont déjà été envoyés du serveur au navigateur, de sorte qu'il n'est plus possible d'envoyer des en-têtes ou de modifier le code d'état. +Retourne si les en-têtes ont déjà été envoyés du serveur au navigateur, et donc s'il n'est plus possible d'envoyer des en-têtes ou de modifier le code de statut. setHeader(string $name, string $value) .[method] ------------------------------------------------ -Envoie un en-tête HTTP et **supprime** l'en-tête du même nom envoyé précédemment. +Envoie un en-tête HTTP et **écrase** un en-tête précédemment envoyé du même nom. ```php $httpResponse->setHeader('Pragma', 'no-cache'); @@ -45,7 +45,7 @@ $httpResponse->setHeader('Pragma', 'no-cache'); addHeader(string $name, string $value) .[method] ------------------------------------------------ -Envoie un en-tête HTTP et **n'écrase pas** l'en-tête du même nom envoyé précédemment. +Envoie un en-tête HTTP et **n'écrase pas** un en-tête précédemment envoyé du même nom. ```php $httpResponse->addHeader('Accept', 'application/json'); @@ -60,7 +60,7 @@ Supprime un en-tête HTTP précédemment envoyé. getHeader(string $header): ?string .[method] -------------------------------------------- -Renvoie l'en-tête HTTP envoyé, ou `null` s'il n'existe pas. Le paramètre est insensible à la casse. +Retourne un en-tête HTTP envoyé ou `null` s'il n'existe pas. Le paramètre est insensible à la casse. ```php $pragma = $httpResponse->getHeader('Pragma'); @@ -69,7 +69,7 @@ $pragma = $httpResponse->getHeader('Pragma'); getHeaders(): array .[method] ----------------------------- -Renvoie tous les en-têtes HTTP envoyés sous forme de tableau associatif. +Retourne tous les en-têtes HTTP envoyés sous forme de tableau associatif. ```php $headers = $httpResponse->getHeaders(); @@ -77,9 +77,9 @@ echo $headers['Pragma']; ``` -setContentType(string $type, string $charset=null) .[method] ------------------------------------------------------------- -Envoie l'en-tête `Content-Type`. +setContentType(string $type, ?string $charset=null) .[method] +------------------------------------------------------------- +Modifie l'en-tête `Content-Type`. ```php $httpResponse->setContentType('text/plain', 'UTF-8'); @@ -88,7 +88,7 @@ $httpResponse->setContentType('text/plain', 'UTF-8'); redirect(string $url, int $code=self::S302_Found): void .[method] ----------------------------------------------------------------- -Redirige vers une autre URL. N'oubliez pas de quitter le script à ce moment-là. +Redirige vers une autre URL. N'oubliez pas de terminer ensuite le script. ```php $httpResponse->redirect('http://example.com'); @@ -98,52 +98,52 @@ exit; setExpiration(?string $time) .[method] -------------------------------------- -Définit l'expiration du document HTTP utilisant les en-têtes `Cache-Control` et `Expires`. Le paramètre est soit un intervalle de temps (sous forme de texte), soit `null`, qui désactive la mise en cache. +Définit l'expiration du document HTTP à l'aide des en-têtes `Cache-Control` et `Expires`. Le paramètre est soit un intervalle de temps (sous forme de texte), soit `null`, ce qui désactive la mise en cache. ```php -// le cache du navigateur expire dans une heure +// le cache du navigateur expirera dans une heure $httpResponse->setExpiration('1 hour'); ``` sendAsFile(string $fileName) .[method] -------------------------------------- -La réponse doit être téléchargée avec la boîte de dialogue *Enregistrer sous* avec le nom spécifié. Elle n'envoie pas de fichier à la sortie. +La réponse sera téléchargée via la boîte de dialogue *Enregistrer sous* sous le nom spécifié. Le fichier lui-même n'est pas envoyé. ```php -$httpResponse->sendAsFile('invoice.pdf'); +$httpResponse->sendAsFile('facture.pdf'); ``` -setCookie(string $name, string $value, $time, string $path=null, string $domain=null, bool $secure=null, bool $httpOnly=null, string $sameSite=null) .[method] --------------------------------------------------------------------------------------------------------------------------------------------------------------- +setCookie(string $name, string $value, $time, ?string $path=null, ?string $domain=null, ?bool $secure=null, ?bool $httpOnly=null, ?string $sameSite=null) .[method] +------------------------------------------------------------------------------------------------------------------------------------------------------------------- Envoie un cookie. Valeurs par défaut des paramètres : -| `$path` | `'/'` | avec une portée à tous les chemins sur le (sous-)domaine *(configurable)* -| `$domain` | `null` | avec la portée du (sous-)domaine actuel, mais pas de ses sous-domaines *(configurable)*. -| `$secure` | `true` | si le site fonctionne en HTTPS, sinon `false` *(configurable)* -| `$httpOnly` | `true` | le cookie est inaccessible à JavaScript -| `$sameSite` | `'Lax'` | le cookie ne doit pas être envoyé en cas d'[accès depuis une autre origine |nette:glossary#SameSite cookie] +| `$path` | `'/'` | le cookie a une portée sur tous les chemins du (sous-)domaine *(configurable)* +| `$domain` | `null` | ce qui signifie avec une portée sur le (sous-)domaine actuel, mais pas ses sous-domaines *(configurable)* +| `$secure` | `true` | si le site fonctionne en HTTPS, sinon `false` *(configurable)* +| `$httpOnly` | `true` | le cookie est inaccessible à JavaScript +| `$sameSite` | `'Lax'` | le cookie peut ne pas être envoyé lors d'un [accès depuis un domaine différent |nette:glossary#Cookie SameSite] -Vous pouvez modifier les valeurs par défaut des paramètres `$path`, `$domain` et `$secure` dans [configuration |configuration#HTTP cookie]. +Vous pouvez modifier les valeurs par défaut des paramètres `$path`, `$domain` et `$secure` dans la [configuration |configuration#Cookie HTTP]. -Le temps peut être spécifié comme un nombre de secondes ou une chaîne de caractères : +Le temps peut être spécifié en secondes ou sous forme de chaîne de caractères : ```php -$httpResponse->setCookie('lang', 'en', '100 days'); +$httpResponse->setCookie('lang', 'fr', '100 days'); ``` -L'option `$domain` détermine quels domaines (origines) peuvent accepter les cookies. Si elle n'est pas spécifiée, le cookie est accepté par le même (sous-)domaine que celui qu'il a défini, à l'exclusion de leurs sous-domaines. Si `$domain` est spécifié, les sous-domaines sont également inclus. Par conséquent, spécifier `$domain` est moins restrictif que de l'omettre. Par exemple, si `$domain = 'nette.org'`, le cookie est également disponible sur tous les sous-domaines comme `doc.nette.org`. +Le paramètre `$domain` détermine quels domaines peuvent accepter le cookie. S'il n'est pas spécifié, le cookie est accepté par le même (sous-)domaine qui l'a défini, mais pas par ses sous-domaines. Si `$domain` est spécifié, les sous-domaines sont également inclus. Par conséquent, spécifier `$domain` est moins restrictif que de l'omettre. Par exemple, avec `$domain = 'nette.org'`, les cookies sont également disponibles sur tous les sous-domaines comme `doc.nette.org`. -Vous pouvez utiliser les constantes `Response::SameSiteLax`, `SameSiteStrict` et `SameSiteNone` pour la valeur `$sameSite`. +Pour la valeur `$sameSite`, vous pouvez utiliser les constantes `Response::SameSiteLax`, `Response::SameSiteStrict` et `Response::SameSiteNone`. -deleteCookie(string $name, string $path=null, string $domain=null, bool $secure=null): void .[method] ------------------------------------------------------------------------------------------------------ +deleteCookie(string $name, ?string $path=null, ?string $domain=null, ?bool $secure=null): void .[method] +-------------------------------------------------------------------------------------------------------- Supprime un cookie. Les valeurs par défaut des paramètres sont : -- `$path` avec une portée à tous les répertoires (`'/'`) -- `$domain` avec la portée du (sous-)domaine actuel, mais pas de ses sous-domaines -- `$secure` est affecté par les paramètres de [configuration |configuration#HTTP cookie] +- `$path` avec une portée sur tous les répertoires (`'/'`) +- `$domain` avec une portée sur le (sous-)domaine actuel, mais pas ses sous-domaines +- `$secure` est régi par les paramètres de la [configuration |configuration#Cookie HTTP] ```php $httpResponse->deleteCookie('lang'); diff --git a/http/fr/sessions.texy b/http/fr/sessions.texy index ab9a8c8144..d95463fa3e 100644 --- a/http/fr/sessions.texy +++ b/http/fr/sessions.texy @@ -3,7 +3,7 @@ Sessions <div class=perex> -HTTP est un protocole sans état, mais presque toutes les applications ont besoin de conserver un état entre les requêtes, par exemple le contenu d'un panier d'achat. C'est à cela que sert une session. Voyons maintenant +HTTP est un protocole sans état, mais presque toutes les applications ont besoin de conserver un état entre les requêtes, par exemple le contenu d'un panier d'achat. C'est précisément à cela que servent les sessions. Nous allons montrer : - comment utiliser les sessions - comment éviter les conflits de noms @@ -11,61 +11,61 @@ HTTP est un protocole sans état, mais presque toutes les applications ont besoi </div> -Lors de l'utilisation de sessions, chaque utilisateur reçoit un identifiant unique appelé ID de session, qui est transmis dans un cookie. Cet identifiant sert de clé pour les données de session. Contrairement aux cookies, qui sont stockés du côté du navigateur, les données de session sont stockées du côté du serveur. +Lors de l'utilisation des sessions, chaque utilisateur reçoit un identifiant unique appelé ID de session, qui est transmis dans un cookie. Celui-ci sert de clé pour les données de session. Contrairement aux cookies, qui sont stockés côté navigateur, les données de session sont stockées côté serveur. -On configure la session dans la [configuration |configuration#session], le choix du temps d'expiration est important. +Nous configurons la session dans la [configuration |configuration#Session], le choix de la durée d'expiration est particulièrement important. -La session est gérée par l'objet [api:Nette\Http\Session], que vous obtenez en le passant en utilisant l'[injection de dépendances |dependency-injection:passing-dependencies]. Dans les présentateurs, il suffit d'appeler `$session = $this->getSession()`. +La gestion de la session est assurée par l'objet [api:Nette\Http\Session], auquel vous accédez en vous le faisant passer via l'[injection de dépendances |dependency-injection:passing-dependencies]. Dans les presenters, il suffit d'appeler `$session = $this->getSession()`. -→ [Installation et configuration requise |@home#Installation] +→ [Installation et prérequis |@home#Installation] -Démarrage de la session .[#toc-starting-session] -================================================ +Démarrage de la session +======================= -Par défaut, Nette démarre automatiquement une session au moment où nous commençons à lire ou à écrire des données dans celle-ci. Pour démarrer manuellement une session, utilisez `$session->start()`. +Par défaut, Nette démarre automatiquement la session au moment où nous commençons à lire ou à écrire des données dedans. Manuellement, la session est démarrée à l'aide de `$session->start()`. -PHP envoie des en-têtes HTTP affectant la mise en cache lors du démarrage de la session, voir [php:session_cache_limiter], et éventuellement un cookie avec l'ID de la session. Par conséquent, il est toujours nécessaire de démarrer la session avant d'envoyer des données au navigateur, sinon une exception sera levée. Donc, si vous savez qu'une session sera utilisée pendant le rendu de la page, lancez-la manuellement avant, par exemple dans le présentateur. +PHP envoie lors du démarrage de la session des en-têtes HTTP affectant la mise en cache, voir [php:session_cache_limiter], et éventuellement aussi un cookie avec l'ID de session. Il est donc nécessaire de toujours démarrer la session avant d'envoyer toute sortie au navigateur, sinon une exception sera levée. Si vous savez donc que la session sera utilisée pendant le rendu de la page, démarrez-la manuellement avant, par exemple dans le presenter. -En mode développeur, Tracy démarre la session car il l'utilise pour afficher les barres de redirection et de demandes AJAX dans la barre Tracy. +En mode développeur, Tracy démarre la session car elle l'utilise pour afficher les barres avec les redirections et les requêtes AJAX dans la barre Tracy. -Section .[#toc-section] -======================= +Sections +======== -En PHP pur, le stockage des données de session est implémenté comme un tableau accessible via une variable globale `$_SESSION`. Le problème est que les applications sont normalement constituées d'un certain nombre de parties indépendantes, et si toutes n'ont qu'un seul et même tableau disponible, tôt ou tard, une collision de noms se produira. +En PHP pur, le stockage des données de session est réalisé sous forme de tableau accessible via la variable globale `$_SESSION`. Le problème est que les applications sont généralement composées de nombreuses parties indépendantes les unes des autres, et si toutes n'ont qu'un seul tableau à leur disposition, tôt ou tard un conflit de noms se produira. -Nette Framework résout le problème en divisant l'espace entier en sections (objets [api:Nette\Http\SessionSection]). Chaque unité utilise alors sa propre section avec un nom unique et aucune collision ne peut se produire. +Nette Framework résout le problème en divisant tout l'espace en sections (objets [api:Nette\Http\SessionSection]). Chaque unité utilise alors sa propre section avec un nom unique et aucune collision ne peut plus se produire. -Nous obtenons la section à partir du gestionnaire de session : +Nous obtenons la section de la session : ```php -$section = $session->getSection('unique name'); +$section = $session->getSection('nom unique'); ``` -Dans le présentateur il suffit d'appeler `getSession()` avec le paramètre : +Dans le presenter, il suffit d'utiliser `getSession()` avec un paramètre : ```php -// $this est le présentateur -$section = $this->getSession('unique name'); +// $this est un Presenter +$section = $this->getSession('nom unique'); ``` -L'existence de la section peut être vérifiée par la méthode `$session->hasSection('unique name')`. +On peut vérifier l'existence de la section avec la méthode `$session->hasSection('nom unique')`. -La section elle-même est très facile à utiliser avec les méthodes `set()`, `get()` et `remove()`: +Travailler avec la section elle-même est ensuite très facile à l'aide des méthodes `set()`, `get()` et `remove()` : ```php -// écriture de variables +// écriture d'une variable $section->set('userName', 'franta'); -// lecture d'une variable, renvoie null si elle n'existe pas +// lecture d'une variable, retourne null si elle n'existe pas echo $section->get('userName'); // suppression d'une variable $section->remove('userName'); ``` -Il est possible d'utiliser le cycle `foreach` pour obtenir toutes les variables de la section : +Pour obtenir toutes les variables de la section, il est possible d'utiliser une boucle `foreach` : ```php foreach ($section as $key => $val) { @@ -74,63 +74,63 @@ foreach ($section as $key => $val) { ``` -Comment définir l'expiration .[#toc-how-to-set-expiration] ----------------------------------------------------------- +Définition de l'expiration +-------------------------- -L'expiration peut être définie pour des sections individuelles ou même des variables individuelles. Nous pouvons laisser la connexion de l'utilisateur expirer au bout de 20 minutes, tout en conservant en mémoire le contenu d'un panier d'achat. +Il est possible de définir une expiration pour des sections individuelles ou même des variables individuelles. Nous pouvons ainsi faire expirer la connexion de l'utilisateur après 20 minutes, tout en continuant à mémoriser le contenu du panier. ```php // la section expirera après 20 minutes $section->setExpiration('20 minutes'); ``` -Le troisième paramètre de la méthode `set()` est utilisé pour définir l'expiration des variables individuelles : +Pour définir l'expiration de variables individuelles, le troisième paramètre de la méthode `set()` est utilisé : ```php -// la variable 'flash' expire après 30 secondes +// la variable 'flash' expirera déjà après 30 secondes $section->set('flash', $message, '30 seconds'); ``` .[note] -N'oubliez pas que le temps d'expiration de l'ensemble de la session (voir la [configuration de la session |configuration#session]) doit être égal ou supérieur au temps défini pour les sections ou les variables individuelles. +N'oubliez pas que la durée d'expiration de toute la session (voir [configuration de session |configuration#Session]) doit être égale ou supérieure à la durée définie pour les sections ou variables individuelles. -L'annulation de l'expiration précédemment définie peut être obtenue par la méthode `removeExpiration()`. La suppression immédiate de la section entière sera assurée par la méthode `remove()`. +L'annulation d'une expiration précédemment définie est réalisée avec la méthode `removeExpiration()`. La suppression immédiate de toute la section est assurée par la méthode `remove()`. -Événements $onStart, $onBeforeWrite .[#toc-events-onstart-onbeforewrite] ------------------------------------------------------------------------- +Événements $onStart, $onBeforeWrite +----------------------------------- -L'objet `Nette\Http\Session` possède les [événements |nette:glossary#Events] `$onStart` et `$onBeforeWrite`, ce qui vous permet d'ajouter des rappels qui sont appelés après le démarrage de la session ou avant qu'elle ne soit écrite sur le disque puis terminée. +L'objet `Nette\Http\Session` a des [événements |nette:glossary#Événements events] `$onStart` et `$onBeforeWrite`, vous pouvez donc ajouter des callbacks qui seront appelés après le démarrage de la session ou avant son écriture sur le disque et sa fermeture ultérieure. ```php $session->onBeforeWrite[] = function () { - // écrit les données dans la session + // nous écrivons les données dans la session $this->section->set('basket', $this->basket); }; ``` -Gestion des sessions .[#toc-session-management] -=============================================== +Gestion de la session +===================== -Aperçu des méthodes de la classe `Nette\Http\Session` pour la gestion des sessions : +Aperçu des méthodes de la classe `Nette\Http\Session` pour la gestion de la session : <div class=wiki-methods-brief> start(): void .[method] ----------------------- -Démarre une session. +Démarre la session. isStarted(): bool .[method] --------------------------- -La session est-elle lancée ? +La session est-elle démarrée ? close(): void .[method] ----------------------- -Termine la session. La session se termine automatiquement à la fin du script. +Termine la session. La session se termine automatiquement à la fin de l'exécution du script. destroy(): void .[method] @@ -140,71 +140,72 @@ Termine et supprime la session. exists(): bool .[method] ------------------------ -La requête HTTP contient-elle un cookie avec un ID de session ? +La requête HTTP contient-elle un cookie avec l'ID de session ? regenerateId(): void .[method] ------------------------------ -Génère un nouvel ID de session aléatoire. Les données restent inchangées. +Génère un nouvel ID de session aléatoire. Les données restent conservées. getId(): string .[method] ------------------------- -Renvoie l'ID de la session. +Retourne l'ID de session. </div> -Configuration .[#toc-configuration] ------------------------------------ +Configuration +------------- -Nous configurons la session dans la [configuration |configuration#session]. Si vous écrivez une application qui n'utilise pas de conteneur DI, utilisez ces méthodes pour la configurer. Elles doivent être appelées avant le démarrage de la session. +Nous configurons la session dans la [configuration |configuration#Session]. Si vous écrivez une application qui n'utilise pas de conteneur DI, ces méthodes servent à la configuration. Elles doivent être appelées avant le démarrage de la session. <div class=wiki-methods-brief> setName(string $name): static .[method] --------------------------------------- -Définit le nom du cookie qui est utilisé pour transmettre l'ID de session. Le nom par défaut est `PHPSESSID`. Ceci est utile si vous exécutez plusieurs applications différentes sur le même site. +Définit le nom du cookie dans lequel l'ID de session est transmis. Le nom standard est `PHPSESSID`. Utile si vous exécutez plusieurs applications différentes sur le même site web. getName(): string .[method] --------------------------- -Renvoie le nom du cookie de session. +Retourne le nom du cookie dans lequel l'ID de session est transmis. setOptions(array $options): static .[method] -------------------------------------------- -Configure la session. Il est possible de définir toutes les [directives de session |https://www.php.net/manual/en/session.configuration.php] PHP (en format camelCase, par exemple écrire `savePath` au lieu de `session.save_path`) et aussi [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. +Configure la session. Il est possible de définir toutes les [directives de session PHP |https://www.php.net/manual/en/session.configuration.php] (au format camelCase, par ex. au lieu de `session.save_path`, nous écrivons `savePath`) ainsi que [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. setExpiration(?string $time): static .[method] ---------------------------------------------- -Définit le temps d'inactivité après lequel la session expire. +Définit la durée d'inactivité après laquelle la session expire. -setCookieParameters(string $path, string $domain=null, bool $secure=null, string $samesite=null): static .[method] ------------------------------------------------------------------------------------------------------------------- -Définit les paramètres des cookies. Vous pouvez modifier les valeurs par défaut des paramètres dans [configuration |configuration#Session cookie]. +setCookieParameters(string $path, ?string $domain=null, ?bool $secure=null, ?string $samesite=null): static .[method] +--------------------------------------------------------------------------------------------------------------------- +Définition des paramètres pour le cookie. Vous pouvez modifier les valeurs par défaut des paramètres dans la [configuration |configuration#Cookie de session]. setSavePath(string $path): static .[method] ------------------------------------------- -Définit le répertoire dans lequel les fichiers de session sont stockés. +Définit le répertoire où sont stockés les fichiers de session. setHandler(\SessionHandlerInterface $handler): static .[method] --------------------------------------------------------------- -Définit le gestionnaire personnalisé, voir la [documentation PHP |https://www.php.net/manual/en/class.sessionhandlerinterface.php]. +Définition d'un gestionnaire personnalisé, voir la [documentation PHP|https://www.php.net/manual/en/class.sessionhandlerinterface.php]. </div> -Sécurité d'abord .[#toc-safety-first] -===================================== +Sécurité avant tout +=================== -Le serveur suppose qu'il communique avec le même utilisateur tant que les demandes contiennent le même identifiant de session. La tâche des mécanismes de sécurité est de garantir que ce comportement fonctionne réellement et qu'il n'existe aucune possibilité de substituer ou de voler un identifiant. +Le serveur suppose qu'il communique toujours avec le même utilisateur tant que les requêtes sont accompagnées du même ID de session. La tâche des mécanismes de sécurité est de garantir que ce soit réellement le cas et qu'il ne soit pas possible de voler ou de substituer l'identifiant. -C'est pourquoi Nette Framework configure correctement les directives PHP pour transférer l'identifiant de session uniquement dans les cookies, pour éviter l'accès depuis JavaScript et pour ignorer les identifiants dans l'URL. De plus, dans les moments critiques, comme la connexion de l'utilisateur, il génère un nouvel identifiant de session. +Nette Framework configure donc correctement les directives PHP pour que l'ID de session soit transmis uniquement dans le cookie, le rende inaccessible à JavaScript et ignore les éventuels identifiants dans l'URL. De plus, dans les moments critiques, comme la connexion de l'utilisateur, il génère un nouvel ID de session. -La fonction ini_set est utilisée pour configurer PHP, mais malheureusement, son utilisation est interdite chez certains hébergeurs. Si c'est votre cas, essayez de demander à votre hébergeur d'autoriser cette fonction pour vous, ou au moins de configurer son serveur correctement. .[note] +.[note] +Pour la configuration de PHP, la fonction ini_set est utilisée, que certains hébergeurs interdisent malheureusement. Si c'est le cas de votre hébergeur, essayez de négocier avec lui pour qu'il vous autorise la fonction ou au moins configure le serveur. diff --git a/http/fr/urls.texy b/http/fr/urls.texy index 2d5a0c35fa..dcf73b8d0e 100644 --- a/http/fr/urls.texy +++ b/http/fr/urls.texy @@ -1,16 +1,16 @@ -Analyseur et constructeur d'URL -******************************* +Travailler avec les URL +*********************** .[perex] -Les classes [Url |#Url], [UrlImmutable |#UrlImmutable] et [UrlScript |#UrlScript] permettent de gérer, d'analyser et de manipuler facilement les URL. +Les classes [#Url], [#UrlImmutable] et [#UrlScript] permettent de générer, parser et manipuler facilement les URL. -→ [Installation et exigences |@home#Installation] +→ [Installation et prérequis |@home#Installation] Url === -La classe [api:Nette\Http\Url] permet de travailler facilement avec l'URL et ses composants individuels, qui sont décrits dans ce diagramme : +La classe [api:Nette\Http\Url] permet de travailler facilement avec les URL et leurs différentes composantes, illustrées par ce schéma : /--pre scheme user password host port path query fragment @@ -36,7 +36,7 @@ $url->setScheme('https') echo $url; // 'https://localhost/edit?foo=bar' ``` -Vous pouvez également analyser l'URL, puis la manipuler : +Il est également possible de parser une URL et de la manipuler ensuite : ```php $url = new Url( @@ -44,62 +44,94 @@ $url = new Url( ); ``` -Les méthodes suivantes sont disponibles pour obtenir ou modifier des composants individuels de l'URL : +La classe `Url` implémente l'interface `JsonSerializable` et a une méthode `__toString()`, de sorte que l'objet peut être affiché ou utilisé dans des données passées à `json_encode()`. + +```php +echo $url; +echo json_encode([$url]); +``` + + +Composantes de l'URL .[method] +------------------------------ + +Pour retourner ou modifier les différentes composantes de l'URL, les méthodes suivantes sont à votre disposition : .[language-php] -| Setter | Getter | Valeur retournée +| Setter | Getter | Valeur retournée |-------------------------------------------------------------------------------------------- -| `setScheme(string $scheme)`| `getScheme(): string`| `'http'` -| `setUser(string $user)`| `getUser(): string`| `'john'` -| `setPassword(string $password)`| `getPassword(): string`| `'xyz*12'` -| `setHost(string $host)`| `getHost(): string`| `'nette.org'` -| `setPort(int $port)`| `getPort(): ?int`| `8080` -| | `getDefaultPort(): ?int`| `80` -| `setPath(string $path)`| `getPath(): string`| `'/en/download'` -| `setQuery(string\|array $query)`| `getQuery(): string`| `'name=param'` -| `setFragment(string $fragment)`| `getFragment(): string`| `'footer'` -| | `getAuthority(): string`| `'nette.org:8080'` -| | `getHostUrl(): string`| `'http://nette.org:8080'` -| | `getAbsoluteUrl(): string` | URL complète - -Nous pouvons également opérer avec des paramètres de requête individuels en utilisant : +| `setScheme(string $scheme)` | `getScheme(): string` | `'http'` +| `setUser(string $user)` | `getUser(): string` | `'john'` +| `setPassword(string $password)` | `getPassword(): string` | `'xyz*12'` +| `setHost(string $host)` | `getHost(): string` | `'nette.org'` +| `setPort(int $port)` | `getPort(): ?int` | `8080` +| | `getDefaultPort(): ?int` | `80` +| `setPath(string $path)` | `getPath(): string` | `'/en/download'` +| `setQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` +| `setFragment(string $fragment)` | `getFragment(): string` | `'footer'` +| | `getAuthority(): string` | `'john:xyz%2A12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | URL complète + +Attention : Lorsque vous travaillez avec une URL obtenue à partir d'une [requête HTTP |request], gardez à l'esprit qu'elle ne contiendra pas le fragment, car le navigateur ne l'envoie pas au serveur. + +Nous pouvons également travailler avec les paramètres de requête individuels en utilisant : .[language-php] -| Setter | Getter +| Setter | Getter |--------------------------------------------------- -| `setQuery(string\|array $query)` | `getQueryParameters(): array` -| `setQueryParameter(string $name, $val)`| `getQueryParameter(string $name)` +| `setQuery(string\|array $query)` | `getQueryParameters(): array` +| `setQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` -La méthode `getDomain(int $level = 2)` renvoie la partie droite ou gauche de l'hôte. Voici comment cela fonctionne si l'hôte est `www.nette.org`: + +getDomain(int $level = 2): string .[method] +------------------------------------------- +Retourne la partie droite ou gauche de l'hôte. Voici comment cela fonctionne si l'hôte est `www.nette.org` : .[language-php] -| `getDomain(1)` | `'org'` -| `getDomain(2)` | `'nette.org'` -| `getDomain(3)` | `'www.nette.org'` -| `getDomain(0)` | `'www.nette.org'` -| `getDomain(-1)` | `'www.nette'` -| `getDomain(-2)` | `'www'` -| `getDomain(-3)` | `''` +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `''` -La classe `Url` implémente l'interface `JsonSerializable` et possède une méthode `__toString()` afin que l'objet puisse être imprimé ou utilisé dans les données transmises à `json_encode()`. +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +Vérifie si deux URL sont identiques. ```php -echo $url; -echo json_encode([$url]); +$url->isEqual('https://nette.org'); ``` -La méthode `isEqual(string|Url $anotherUrl): bool` teste si les deux URL sont identiques. + +Url::isAbsolute(string $url): bool .[method]{data-version:3.3.2} +---------------------------------------------------------------- +Vérifie si l'URL est absolue. Une URL est considérée comme absolue si elle commence par un schéma (par ex. http, https, ftp) suivi de deux points. ```php -$url->isEqual('https://nette.org'); +Url::isAbsolute('https://nette.org'); // true +Url::isAbsolute('//nette.org'); // false +``` + + +Url::removeDotSegments(string $path): string .[method]{data-version:3.3.2} +-------------------------------------------------------------------------- +Normalise le chemin dans l'URL en supprimant les segments spéciaux `.` et `..`. La méthode supprime les éléments de chemin superflus de la même manière que le font les navigateurs web. + +```php +Url::removeDotSegments('/path/../subtree/./file.txt'); // '/subtree/file.txt' +Url::removeDotSegments('/../foo/./bar'); // '/foo/bar' +Url::removeDotSegments('./today/../file.txt'); // 'file.txt' ``` -UrlImmutable .[#toc-urlimmutable] -================================= +UrlImmutable +============ -La classe [api:Nette\Http\UrlImmutable] est une alternative immuable à la classe `Url` (tout comme en PHP `DateTimeImmutable` est une alternative immuable à `DateTime`). Au lieu de setters, elle a ce qu'on appelle des garrots, qui ne changent pas l'objet, mais retournent de nouvelles instances avec une valeur modifiée : +La classe [api:Nette\Http\UrlImmutable] est une alternative immutable (immuable) à la classe [#Url] (similaire à la façon dont `DateTimeImmutable` en PHP est une alternative immuable à `DateTime`). Au lieu de setters, elle a des withers, qui ne modifient pas l'objet, mais retournent de nouvelles instances avec la valeur modifiée : ```php use Nette\Http\UrlImmutable; @@ -113,51 +145,94 @@ $newUrl = $url ->withPassword('') ->withPath('/fr/'); -echo $newUrl; // 'http://nette.org:8080/fr/?name=param#footer' +echo $newUrl; // 'http://john:xyz%2A12@nette.org:8080/fr/?name=param#footer' +``` + +La classe `UrlImmutable` implémente l'interface `JsonSerializable` et a une méthode `__toString()`, de sorte que l'objet peut être affiché ou utilisé dans des données passées à `json_encode()`. + +```php +echo $url; +echo json_encode([$url]); ``` -Les méthodes suivantes sont disponibles pour obtenir ou modifier des composants individuels d'URL : + +Composantes de l'URL .[method] +------------------------------ + +Pour retourner ou modifier les différentes composantes de l'URL, les méthodes suivantes sont utilisées : .[language-php] -| Wither | Getter | Valeur retournée +| Wither | Getter | Valeur retournée |-------------------------------------------------------------------------------------------- -| `withScheme(string $scheme)`| `getScheme(): string`| `'http'` -| `withUser(string $user)`| `getUser(): string`| `'john'` -| `withPassword(string $password)`| `getPassword(): string`| `'xyz*12'` -| `withHost(string $host)`| `getHost(): string`| `'nette.org'` -| `withPort(int $port)`| `getPort(): ?int`| `8080` -| | `getDefaultPort(): ?int`| `80` -| `withPath(string $path)`| `getPath(): string`| `'/en/download'` -| `withQuery(string\|array $query)`| `getQuery(): string`| `'name=param'` -| `withFragment(string $fragment)`| `getFragment(): string`| `'footer'` -| | `getAuthority(): string`| `'nette.org:8080'` -| | `getHostUrl(): string`| `'http://nette.org:8080'` -| | `getAbsoluteUrl(): string` | URL complète - -Nous pouvons également opérer avec des paramètres de requête individuels en utilisant : +| `withScheme(string $scheme)` | `getScheme(): string` | `'http'` +| `withUser(string $user)` | `getUser(): string` | `'john'` +| `withPassword(string $password)` | `getPassword(): string` | `'xyz*12'` +| `withHost(string $host)` | `getHost(): string` | `'nette.org'` +| `withPort(int $port)` | `getPort(): ?int` | `8080` +| | `getDefaultPort(): ?int` | `80` +| `withPath(string $path)` | `getPath(): string` | `'/en/download'` +| `withQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` +| `withFragment(string $fragment)` | `getFragment(): string` | `'footer'` +| | `getAuthority(): string` | `'john:xyz%2A12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | URL complète + +La méthode `withoutUserInfo()` supprime `user` et `password`. + +Nous pouvons également travailler avec les paramètres de requête individuels en utilisant : .[language-php] -| Wither | Getter +| Wither | Getter |----------------------------------------------- -| `withQuery(string\|array $query)` | `getQueryParameters(): array` -| `withQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` +| `withQuery(string\|array $query)` | `getQueryParameters(): array` +| `withQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` -La méthode `getDomain(int $level = 2)` fonctionne de la même manière que la méthode `Url`. La méthode `withoutUserInfo()` supprime `user` et `password`. -La classe `UrlImmutable` implémente l'interface `JsonSerializable` et possède une méthode `__toString()` afin que l'objet puisse être imprimé ou utilisé dans les données transmises à `json_encode()`. +getDomain(int $level = 2): string .[method] +------------------------------------------- +Retourne la partie droite ou gauche de l'hôte. Voici comment cela fonctionne si l'hôte est `www.nette.org` : + +.[language-php] +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `''` + + +resolve(string $reference): UrlImmutable .[method]{data-version:3.3.2} +---------------------------------------------------------------------- +Dérive une URL absolue de la même manière qu'un navigateur traite les liens sur une page HTML : +- si le lien est une URL absolue (contient un schéma), il est utilisé tel quel +- si le lien commence par `//`, seul le schéma de l'URL actuelle est repris +- si le lien commence par `/`, un chemin absolu est créé à partir de la racine du domaine +- dans les autres cas, l'URL est construite relativement au chemin actuel ```php -echo $url; -echo json_encode([$url]); +$url = new UrlImmutable('https://example.com/path/page'); +echo $url->resolve('../foo'); // 'https://example.com/foo' +echo $url->resolve('/bar'); // 'https://example.com/bar' +echo $url->resolve('sub/page.html'); // 'https://example.com/path/sub/page.html' +``` + + +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +Vérifie si deux URL sont identiques. + +```php +$url->isEqual('https://nette.org'); ``` -La méthode `isEqual(string|Url $anotherUrl): bool` teste si les deux URL sont identiques. +UrlScript +========= -UrlScript .[#toc-urlscript] -=========================== +La classe [api:Nette\Http\UrlScript] est un descendant de [#UrlImmutable] et l'étend avec d'autres composantes URL virtuelles, telles que le répertoire racine du projet, etc. Tout comme la classe parente, c'est un objet immutable (immuable). -La classe [api:Nette\Http\UrlScript] est un descendant de `UrlImmutable` et distingue en plus ces parties logiques de l'URL : +Le diagramme suivant illustre les composantes que UrlScript reconnaît : /--pre baseUrl basePath relativePath relativeUrl @@ -169,17 +244,23 @@ La classe [api:Nette\Http\UrlScript] est un descendant de `UrlImmutable` et dist scriptPath pathInfo \-- -Les méthodes suivantes sont disponibles pour obtenir ces parties : +- `baseUrl` est l'URL de base de l'application, y compris le domaine et la partie du chemin vers le répertoire racine de l'application +- `basePath` est la partie du chemin vers le répertoire racine de l'application +- `scriptPath` est le chemin vers le script actuel +- `relativePath` est le nom du script (éventuellement d'autres segments de chemin) relatif à basePath +- `relativeUrl` est toute la partie de l'URL après baseUrl, y compris la chaîne de requête et le fragment. +- `pathInfo` est une partie de l'URL peu utilisée aujourd'hui après le nom du script + +Pour retourner les parties de l'URL, les méthodes suivantes sont disponibles : .[language-php] -| Getter | Valeur retournée +| Getter | Valeur retournée |------------------------------------------------ -| `getScriptPath(): string`| `'/admin/script.php'` -| `getBasePath(): string`| `'/admin/'` -| `getBaseUrl(): string`| `'http://nette.org/admin/'` -| `getRelativePath(): string`| `'script.php'` -| `getRelativeUrl(): string`| `'script.php/pathinfo/?name=param#footer'` -| `getPathInfo(): string`| `'/pathinfo/'` - - -Nous ne créons pas directement l'objet `UrlScript`, mais la méthode [Nette\Http\Request::getUrl() |request] le renvoie. +| `getScriptPath(): string` | `'/admin/script.php'` +| `getBasePath(): string` | `'/admin/'` +| `getBaseUrl(): string` | `'http://nette.org/admin/'` +| `getRelativePath(): string` | `'script.php'` +| `getRelativeUrl(): string` | `'script.php/pathinfo/?name=param#footer'` +| `getPathInfo(): string` | `'/pathinfo/'` + +Les objets `UrlScript` ne sont généralement pas créés directement, mais sont retournés par la méthode [Nette\Http\Request::getUrl()|request] avec les composantes déjà correctement définies pour la requête HTTP actuelle. diff --git a/http/hu/@home.texy b/http/hu/@home.texy index a9936972bb..3539e4b800 100644 --- a/http/hu/@home.texy +++ b/http/hu/@home.texy @@ -2,13 +2,13 @@ Nette HTTP ********** .[perex] -A `nette/http` csomag a [HTTP kérés |request] és [válasz |response], a [munkamenetekkel |sessions] való munka, valamint az [URL elemzése és felépítése |urls]. +A `nette/http` csomag magába foglalja a [HTTP kérést|request] & [választ|response], a [sessionok |sessions] kezelését és az [URL-ek feldolgozását és összeállítását |urls]. -Telepítés .[#toc-installation] ------------------------------- +Telepítés +--------- -Töltse le és telepítse a csomagot a [Composer |best-practices:composer] segítségével: +A könyvtárat a [Composer|best-practices:composer] eszközzel töltheti le és telepítheti: ```shell composer require nette/http diff --git a/http/hu/@left-menu.texy b/http/hu/@left-menu.texy index 9c504bdad5..09f9a24d93 100644 --- a/http/hu/@left-menu.texy +++ b/http/hu/@left-menu.texy @@ -1,8 +1,8 @@ -Net HTTP -******** -- [Áttekintés |@home] -- [HTTP-kérelem |request] -- [HTTP-válasz |response] -- [Ülések |Sessions] -- [URL segédprogram |urls] -- [Konfiguráció |Configuration] +Nette HTTP +********** +- [Bevezetés |@home] +- [HTTP kérés|request] +- [HTTP válasz|response] +- [Sessions |sessions] +- [URL utilities |urls] +- [Konfiguráció |configuration] diff --git a/http/hu/@meta.texy b/http/hu/@meta.texy new file mode 100644 index 0000000000..c172d1cda5 --- /dev/null +++ b/http/hu/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette dokumentáció}} diff --git a/http/hu/configuration.texy b/http/hu/configuration.texy index 9c022d587f..9c8242f099 100644 --- a/http/hu/configuration.texy +++ b/http/hu/configuration.texy @@ -1,40 +1,40 @@ -HTTP konfigurálása -****************** +HTTP konfiguráció +***************** .[perex] -A Nette HTTP konfigurációs lehetőségeinek áttekintése. +A Nette HTTP konfigurációs opcióinak áttekintése. -Ha nem a teljes keretrendszert, hanem csak ezt a könyvtárat használja, olvassa el [, hogyan töltse be a konfigurációt |bootstrap:]. +Ha nem a teljes keretrendszert használja, csak ezt a könyvtárat, olvassa el, [hogyan kell betölteni a konfigurációt|bootstrap:]. -HTTP fejlécek .[#toc-http-headers] -================================== +HTTP fejlécek +============= ```neon http: - # headerek, amelyeket minden egyes kéréssel elküldenek + # fejlécek, amelyek minden kéréssel elküldésre kerülnek headers: X-Powered-By: MyCMS X-Content-Type-Options: nosniff X-XSS-Protection: '1; mode=block' # befolyásolja az X-Frame-Options fejlécet - frames: ... # (string|bool) alapértelmezett értéke 'SAMEORIGIN' + frames: ... # (string|bool) alapértelmezett 'SAMEORIGIN' ``` -Biztonsági okokból a keretrendszer egy `X-Frame-Options: SAMEORIGIN` fejlécet küld, amely azt mondja, hogy egy oldal egy másik oldalon belül jeleníthető meg (az elemben `<iframe>`) csak akkor, ha az ugyanabban a tartományban van. Ez bizonyos helyzetekben nem kívánatos lehet (például, ha Facebook-alkalmazást fejlesztünk), ezért a viselkedés megváltoztatható a `frames: http://allowed-host.com`. +A keretrendszer biztonsági okokból elküldi az `X-Frame-Options: SAMEORIGIN` fejlécet, amely azt mondja, hogy az oldalt csak akkor lehet megjeleníteni egy másik oldalon belül (az `<iframe>` elemben), ha ugyanazon a domainen található. Ez bizonyos helyzetekben nem kívánatos lehet (például ha Facebook alkalmazást fejleszt), a viselkedés ezért megváltoztatható a `frames: http://allowed-host.com` vagy `frames: true` beállítással. -Tartalombiztonsági politika .[#toc-content-security-policy] ------------------------------------------------------------ +Content Security Policy +----------------------- -Fejlécek `Content-Security-Policy` (a továbbiakban CSP) könnyen összeállítható, leírásuk a [CSP leírásában |https://content-security-policy.com] található. A CSP irányelvek (például `script-src`) a specifikációnak megfelelően karakterláncokként vagy a jobb olvashatóság érdekében értékekből álló tömbként írhatók. Ekkor nem szükséges idézőjeleket írni az olyan kulcsszavak köré, mint a `'self'`. A Nette automatikusan generálja a `nonce` értékét is, így a `'nonce-y4PopTLM=='` a fejlécben kerül elküldésre. +Könnyen összeállíthatók a `Content-Security-Policy` (továbbiakban CSP) fejlécek, leírásukat a [CSP leírásában |https://content-security-policy.com] találja. A CSP direktívák (mint pl. `script-src`) megadhatók akár stringként a specifikáció szerint, akár értékek tömbjeként a jobb olvashatóság érdekében. Ekkor nincs szükség idézőjelek írására a kulcsszavak, mint például a `'self'`, köré. A Nette automatikusan generál egy `nonce` értéket is, így a fejlécben például `'nonce-y4PopTLM=='` lesz. ```neon http: # Content Security Policy csp: - # a CSP specifikáció szerinti karakterlánc + # string a CSP specifikáció szerinti formátumban default-src: "'self' https://example.com" # értékek tömbje @@ -44,18 +44,18 @@ http: - self - https://example.com - # bool a kapcsolók esetében + # bool kapcsolók esetén upgrade-insecure-requests: true block-all-mixed-content: false ``` -Használja a `<script n:nonce>...</script>` a sablonokban, és a nonce érték automatikusan kitöltődik. Biztonságos weboldalak készítése a Nette-ben nagyon egyszerű. +A sablonokban használja a `<script n:nonce>...</script>`-et, és a nonce érték automatikusan kiegészül. Biztonságos webhelyek készítése a Nette-ben valóban egyszerű. -Hasonlóképpen hozzáadható a `Content-Security-Policy-Report-Only` fejléc (amely a CSP-vel párhuzamosan használható) és a [Feature Policy |https://developers.google.com/web/updates/2018/06/feature-policy]: +Hasonlóan összeállíthatók a `Content-Security-Policy-Report-Only` (amelyek a CSP-vel párhuzamosan használhatók) és a [Feature Policy|https://developers.google.com/web/updates/2018/06/feature-policy] fejlécek is: ```neon http: - # Csak a tartalombiztonsági szabályzat jelentése + # Content Security Policy Report-Only cspReportOnly: default-src: self report-uri: 'https://my-report-uri-endpoint' @@ -69,91 +69,103 @@ http: ``` -HTTP süti .[#toc-http-cookie] ------------------------------ +HTTP cookie +----------- -A [Nette\Http\Response::setCookie() |response#setCookie] és a session metódusok néhány paraméterének alapértelmezett értékét megváltoztathatja. +Megváltoztathatók a [Nette\Http\Response::setCookie() |response#setCookie] metódus és a session egyes paramétereinek alapértelmezett értékei. ```neon http: - # cookie hatókör az útvonal szerint - cookiePath: ... # (string) alapértelmezett értéke '/' + # cookie hatóköre útvonal szerint + cookiePath: ... # (string) alapértelmezett '/' - # mely hosztok kaphatják meg a cookie-t - cookieDomain: 'example.com' # (string|domain) alapértelmezés szerint nem beállított + # domainek, amelyek elfogadják a cookie-t + cookieDomain: 'example.com' # (string|domain) alapértelmezett nincs beállítva - # a cookie-kat csak HTTPS-en keresztül küldjük? - cookieSecure: ... # (bool|auto) alapértelmezés szerint auto + # csak HTTPS-en keresztül küldeni a cookie-t? + cookieSecure: ... # (bool|auto) alapértelmezett auto - # letiltja a cookie küldését, amelyet a Nette a CSRF elleni védelemként használ. - disableNetteCookie: ... # (bool) alapértelmezés szerint false + # kikapcsolja a Nette által CSRF védelemként használt cookie küldését + disableNetteCookie: ... # (bool) alapértelmezett false ``` -A `cookieDomain` opció határozza meg, hogy mely tartományok (eredet) fogadhatják el a cookie-kat. Ha nincs megadva, akkor a cookie-t ugyanaz a (al)tartomány fogadja el, amelyik az általa beállított, *kizárva* az aldomainjeiket. Ha a `cookieDomain` van megadva, akkor az aldomainek is bevonásra kerülnek. Ezért a `cookieDomain` megadása kevésbé korlátozó, mint az elhagyása. +A `cookieDomain` attribútum meghatározza, mely domainek fogadhatják el a cookie-t. Ha nincs megadva, a cookie-t ugyanaz a (sub)domain fogadja el, amelyik beállította, *de nem* annak aldomainjei. Ha a `cookieDomain` meg van adva, az aldomainek is beletartoznak. Ezért a `cookieDomain` megadása kevésbé korlátozó, mint annak elhagyása. -Például, ha a `cookieDomain: nette.org` van megadva, a cookie az összes aldomainen is elérhető, mint a `doc.nette.org`. Ez a `domain`, azaz `cookieDomain: domain` speciális értékével is elérhető. +Például a `cookieDomain: nette.org` esetén a cookie-k minden aldomainen, mint például a `doc.nette.org`, is elérhetők. Ugyanezt elérhetjük a speciális `domain` értékkel is, tehát `cookieDomain: domain`. -A `cookieSecure` alapértelmezett értéke a `auto`, ami azt jelenti, hogy ha a weboldal HTTPS-en fut, a cookie-kat a `Secure` jelzővel küldi el, és így csak HTTPS-en keresztül lesznek elérhetőek. +A `cookieSecure` attribútum `auto` alapértelmezett értéke azt jelenti, hogy ha a webhely HTTPS-en fut, a cookie-k a `Secure` jelzővel kerülnek elküldésre, és így csak HTTPS-en keresztül lesznek elérhetők. -HTTP proxy .[#toc-http-proxy] ------------------------------ +HTTP proxy +---------- -Ha a webhely HTTP-proxy mögött fut, adja meg a proxy IP-címét, hogy a HTTPS-kapcsolatok felismerése megfelelően működjön, valamint az ügyfél IP-címét. Azaz, hogy a [Nette\Http\Request::getRemoteAddress() |request#getRemoteAddress] és az [isSecured() |request#isSecured] a megfelelő értékeket adja vissza, és a sablonokban a `https:` protokollal generálódjanak linkek. +Ha a webhely HTTP proxy mögött fut, adja meg annak IP címét, hogy a HTTPS-en keresztüli kapcsolat és a kliens IP címének észlelése megfelelően működjön. Tehát hogy a [Nette\Http\Request::getRemoteAddress() |request#getRemoteAddress] és [isSecured() |request#isSecured] függvények helyes értékeket adjanak vissza, és a sablonokban a linkek `https:` protokollal generálódjanak. ```neon http: - # IP-cím, tartomány (pl. 127.0.0.0.1/8) vagy ezek tömbje. - proxy: 127.0.0.1 # (string|string[]) alapértelmezett értéke none + # IP cím, tartomány (pl. 127.0.0.1/8) vagy ezen értékek tömbje + proxy: 127.0.0.1 # (string|string[]) alapértelmezett nincs beállítva ``` -Munkamenet .[#toc-session] -========================== +Session +======= -Alapvető [munkamenetek |sessions] beállításai: +Alapvető [session |sessions] beállítások: ```neon session: - # megjeleníti a munkamenet panelt a Tracy Barban? - debugger: ... # (bool) alapértelmezett értéke false + # session panel megjelenítése a Tracy Bar-ban? + debugger: ... # (bool) alapértelmezett false - # inaktivitási idő, amely után a munkamenet lejár. - expiration: # (string) alapértelmezett értéke + # inaktivitási idő, amely után a session lejár + expiration: 14 days # (string) alapértelmezett '3 hours' - # mikor kezdjük el a munkamenetet? - autoStart: ... # (smart|always|never) alapértelmezés szerint 'smart' + # mikor kell elindítani a sessiont? + autoStart: ... # (smart|always|never) alapértelmezett 'smart' - # handler, a SessionHandlerInterface interfészt megvalósító szolgáltatás. + # handler, a SessionHandlerInterface interfészt implementáló szolgáltatás handler: @handlerService ``` -A `autoStart` opció szabályozza a munkamenet indításának időpontját. A `always` érték azt jelenti, hogy a munkamenet mindig az alkalmazás indításakor indul. A `smart` érték azt jelenti, hogy a munkamenet csak akkor indul el az alkalmazás indításakor, ha már létezik, vagy abban a pillanatban, amikor olvasni akarunk róla vagy írni akarunk rá. Végül a `never` érték kikapcsolja a munkamenet automatikus indítását. +Az `autoStart` opció vezérli, hogy mikor kell elindítani a sessiont. Az `always` érték azt jelenti, hogy a session mindig elindul az alkalmazás indításakor. A `smart` érték azt jelenti, hogy a session csak akkor indul el az alkalmazás indításakor, ha már létezik, vagy abban a pillanatban, amikor olvasni vagy írni akarunk belőle. Végül a `never` érték letiltja a session automatikus indítását. -Beállíthatjuk továbbá az összes PHP [munkamenet direktívát |https://www.php.net/manual/en/session.configuration.php] (camelCase formátumban), valamint a [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters] értéket is. Példa: +Továbbá beállíthatók az összes PHP [session direktíva |https://www.php.net/manual/en/session.configuration.php] (camelCase formátumban) és a [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters] is. Példa: ```neon session: - # 'session.name' írva mint 'name' + # 'session.name' írjuk 'name'-ként name: MYID - # 'session.save_path' írva mint 'savePath' + # 'session.save_path' írjuk 'savePath'-ként savePath: "%tempDir%/sessions" ``` -Munkamenet süti .[#toc-session-cookie] --------------------------------------- +Session cookie +-------------- -A munkamenet süti ugyanazokkal a paraméterekkel kerül elküldésre, mint a [többi süti |#HTTP cookie], de ezeket megváltoztathatja: +A session cookie ugyanazokkal a paraméterekkel kerül elküldésre, mint a [más cookie-k |#HTTP cookie], de ezeket megváltoztathatja számára: ```neon session: - # mely hosztok kaphatják meg a cookie-t - cookieDomain: 'example.com' # (string|domain) + # domainek, amelyek elfogadják a cookie-t + cookieDomain: 'example.com' # (string|domain) - # korlátozások a cross-origin kérés elérésekor - cookieSamesite: None # (Strict|Lax|None) alapértelmezés szerint Lax + # korlátozások más domainről való hozzáférés esetén + cookieSamesite: None # (Strict|Lax|None) alapértelmezett Lax ``` -A `cookieSamesite` opció befolyásolja, hogy a cookie-t a [cross-origin kérésekkel |nette:glossary#SameSite cookie] együtt küldi-e el, ami némi védelmet nyújt a [Cross-Site Request Forgery |nette:glossary#cross-site-request-forgery-csrf] támadások ellen. +A `cookieSamesite` attribútum befolyásolja, hogy a cookie elküldésre kerül-e [más domainről való hozzáférés |nette:glossary#SameSite cookie] esetén, ami bizonyos védelmet nyújt a [Cross-Site Request Forgery |nette:glossary#Cross-Site Request Forgery CSRF] (CSRF) támadások ellen. + + +DI szolgáltatások +================= + +Ezek a szolgáltatások kerülnek hozzáadásra a DI konténerhez: + +| Név | Típus | Leírás +|----------------------------------------------------- +| `http.request` | [api:Nette\Http\Request] | [HTTP kérés| request] +| `http.response` | [api:Nette\Http\Response] | [HTTP válasz| response] +| `session.session`| [api:Nette\Http\Session] | [session kezelés| sessions] diff --git a/http/hu/request.texy b/http/hu/request.texy index e8e9a3931c..c3bac10ef1 100644 --- a/http/hu/request.texy +++ b/http/hu/request.texy @@ -2,84 +2,84 @@ HTTP kérés ********** .[perex] -A Nette a HTTP-kérést érthető API-val rendelkező objektumokba kapszulázza, miközben szanálási szűrőt biztosít. +A Nette a HTTP kérést érthető API-val rendelkező objektumokba zárja, és egyúttal szanitizáló szűrőt is biztosít. -A HTTP-kérés egy [api:Nette\Http\Request] objektum, amelyet [függőségi injektálással |dependency-injection:passing-dependencies] átadva kapunk meg. Az előadókban egyszerűen hívja meg a `$httpRequest = $this->getHttpRequest()`. +A HTTP kérést a [api:Nette\Http\Request] objektum képviseli. Ha a Nette-tel dolgozik, ezt az objektumot a keretrendszer automatikusan létrehozza, és [dependency injection |dependency-injection:passing-dependencies] segítségével átadhatja magának. A presenterekben elég csak a `$this->getHttpRequest()` metódust meghívni. Ha a Nette Frameworkön kívül dolgozik, létrehozhatja az objektumot a [#RequestFactory] segítségével. -Ami fontos, hogy a Nette ennek az objektumnak a [létrehozásakor |#RequestFactory] megtisztítja az összes GET, POST és COOKIE bemeneti paramétert, valamint az URL-eket a vezérlő karakterektől és az érvénytelen UTF-8 szekvenciáktól. Így nyugodtan folytathatja a munkát az adatokkal. A megtisztított adatok ezután a prezenterekben és űrlapokban használhatók. +A Nette nagy előnye, hogy az objektum létrehozásakor automatikusan megtisztítja az összes GET, POST, COOKIE bemeneti paramétert, valamint az URL-t a vezérlőkarakterektől és az érvénytelen UTF-8 szekvenciáktól. Ezekkel az adatokkal ezután biztonságosan dolgozhat tovább. A megtisztított adatokat ezután a presenterekben és az űrlapokban használják. -→ [Telepítés és követelmények |@home#Installation] +→ [Telepítés és követelmények |@home#Telepítés] -Nette\Http\Request .[#toc-nette-http-request] -============================================= +Nette\Http\Request +================== -Ez az objektum megváltoztathatatlan. Nincsenek setterei, csak egy úgynevezett wither `withUrl()`, amely nem változtatja meg az objektumot, hanem egy új példányt ad vissza a módosított értékkel. +Ez az objektum immutable (megváltoztathatatlan). Nincsenek setterei, csak egy ún. wither `withUrl()` metódusa van, amely nem változtatja meg az objektumot, hanem egy új példányt ad vissza megváltozott értékkel. withUrl(Nette\Http\UrlScript $url): Nette\Http\Request .[method] ---------------------------------------------------------------- -Egy klónt ad vissza egy másik URL-címmel. +Egy klónt ad vissza más URL-lel. getUrl(): Nette\Http\UrlScript .[method] ---------------------------------------- -Visszaadja a kérés URL-címét [UrlScript |urls#UrlScript] objektumként. +Visszaadja a kérés URL-jét [UrlScript |urls#UrlScript] objektumként. ```php $url = $httpRequest->getUrl(); -echo $url; // https://nette.org/en/documentation?action=edit +echo $url; // https://doc.nette.org/cs/?action=edit echo $url->getHost(); // nette.org ``` -A böngészők nem küldenek töredéket a kiszolgálónak, ezért a `$url->getFragment()` üres karakterláncot ad vissza. +Figyelmeztetés: a böngészők nem küldik el a fragmentet a szerverre, így a `$url->getFragment()` üres stringet fog visszaadni. -getQuery(string $key=null): string|array|null .[method] -------------------------------------------------------- -Visszaadja a GET-kérelem paramétereit: +getQuery(?string $key=null): string|array|null .[method] +-------------------------------------------------------- +Visszaadja a GET kérés paramétereit. ```php -$all = $httpRequest->getQuery(); // az összes URL paraméter tömbje -$id = $httpRequest->getQuery('id'); // visszaadja a GET paramétert 'id' (vagy null) +$all = $httpRequest->getQuery(); // visszaadja az összes paraméter tömbjét az URL-ből +$id = $httpRequest->getQuery('id'); // visszaadja a 'id' GET paramétert (vagy null-t) ``` -getPost(string $key=null): string|array|null .[method] ------------------------------------------------------- -POST kérés paramétereinek visszaadása: +getPost(?string $key=null): string|array|null .[method] +------------------------------------------------------- +Visszaadja a POST kérés paramétereit. ```php -$all = $httpRequest->getPost(); // az összes POST paraméter tömbje -$id = $httpRequest->getPost('id'); // visszaadja az 'id' POST paramétert (vagy null) +$all = $httpRequest->getPost(); // visszaadja az összes paraméter tömbjét a POST-ból +$id = $httpRequest->getPost('id'); // visszaadja a 'id' POST paramétert (vagy null-t) ``` getFile(string|string[] $key): Nette\Http\FileUpload|array|null .[method] ------------------------------------------------------------------------- -Visszaadja a [feltöltést |#Uploaded Files] objektumként: [api:Nette\Http\FileUpload]: +Visszaadja a [feltöltést |#Feltöltött fájlok] [api:Nette\Http\FileUpload] objektumként: ```php $file = $httpRequest->getFile('avatar'); -if ($file->hasFile()) { // feltöltöttünk bármilyen fájlt? - $file->getUntrustedName(); // a felhasználó által küldött fájl neve - $file->getSanitizedName(); // a veszélyes karakterek nélküli név +if ($file?->hasFile()) { // feltöltöttek valamilyen fájlt? + $file->getUntrustedName(); // a felhasználó által küldött fájlnév + $file->getSanitizedName(); // név veszélyes karakterek nélkül } ``` -Adja meg a kulcsok tömbjét a részfa struktúrájának eléréséhez. +A beágyazott struktúrához való hozzáféréshez adjon meg egy kulcsokból álló tömböt. ```php //<input type="file" name="my-form[details][avatar]" multiple> $file = $request->getFile(['my-form', 'details', 'avatar']); ``` -Mivel a kívülről érkező adatokat nem bízhatja meg, és ezért nem támaszkodik a struktúra formájára, ez a módszer biztonságosabb, mint a `$request->getFiles()['my-form']['details']['avatar']`, amely hibásan működhet. +Mivel nem lehet megbízni a kívülről érkező adatokban, és így a fájlok struktúrájának formájában sem, ez a módszer biztonságosabb, mint például a `$request->getFiles()['my-form']['details']['avatar']`, amely meghiúsulhat. getFiles(): array .[method] --------------------------- -Visszaadja a [feltöltési fájlok |#Uploaded Files] fáját egy normalizált struktúrában, amelynek minden egyes levele a [api:Nette\Http\FileUpload] egy példánya: +Visszaadja az [összes feltöltés |#Feltöltött fájlok] fáját normalizált struktúrában, amelynek levelei [api:Nette\Http\FileUpload] objektumok: ```php $files = $httpRequest->getFiles(); @@ -88,7 +88,7 @@ $files = $httpRequest->getFiles(); getCookie(string $key): string|array|null .[method] --------------------------------------------------- -Visszaad egy cookie-t vagy `null`, ha nem létezik. +Visszaadja a cookie-t vagy `null`-t, ha nem létezik. ```php $sessId = $httpRequest->getCookie('sess_id'); @@ -97,7 +97,7 @@ $sessId = $httpRequest->getCookie('sess_id'); getCookies(): array .[method] ----------------------------- -Visszaadja az összes sütit: +Visszaadja az összes cookie-t. ```php $cookies = $httpRequest->getCookies(); @@ -106,16 +106,16 @@ $cookies = $httpRequest->getCookies(); getMethod(): string .[method] ----------------------------- -Visszaadja a HTTP-módszert, amellyel a kérés történt. +Visszaadja a HTTP metódust, amellyel a kérés történt. ```php -echo $httpRequest->getMethod(); // GET, POST, PUT, HEAD, PUT +$httpRequest->getMethod(); // GET, POST, HEAD, PUT ``` isMethod(string $method): bool .[method] ---------------------------------------- -Ellenőrzi a HTTP-módszert, amellyel a kérés érkezett. A paraméter nem érzékeny a nagy- és kisbetűkre. +Teszteli a HTTP metódust, amellyel a kérés történt. A paraméter kis- és nagybetű érzéketlen. ```php if ($httpRequest->isMethod('GET')) // ... @@ -124,7 +124,7 @@ if ($httpRequest->isMethod('GET')) // ... getHeader(string $header): ?string .[method] -------------------------------------------- -Visszaad egy HTTP-fejlécet vagy a `null` címet, ha az nem létezik. A paraméter nem érzékeny a nagy- és kisbetűkre: +Visszaadja a HTTP fejlécet vagy `null`-t, ha nem létezik. A paraméter kis- és nagybetű érzéketlen. ```php $userAgent = $httpRequest->getHeader('User-Agent'); @@ -133,7 +133,7 @@ $userAgent = $httpRequest->getHeader('User-Agent'); getHeaders(): array .[method] ----------------------------- -Az összes HTTP-fejlécet asszociatív tömbként adja vissza: +Visszaadja az összes HTTP fejlécet asszociatív tömbként. ```php $headers = $httpRequest->getHeaders(); @@ -141,39 +141,34 @@ echo $headers['Content-Type']; ``` -getReferer(): ?Nette\Http\UrlImmutable .[method] ------------------------------------------------- -Milyen URL-ről érkezett a felhasználó? Vigyázat, egyáltalán nem megbízható. - - isSecured(): bool .[method] --------------------------- -A kapcsolat titkosított (HTTPS)? Lehet, hogy a megfelelő működéshez [proxy-t |configuration#HTTP proxy] kell beállítania. +Titkosított a kapcsolat (HTTPS)? A megfelelő működéshez szükség lehet a [proxy beállítására |configuration#HTTP proxy]. isSameSite(): bool .[method] ---------------------------- -A kérés ugyanarról az (al)tartományról érkezik, és egy linkre kattintva indult? A Nette a `_nss` cookie-t (korábban `nette-samesite`) használja ennek felismerésére. +Ugyanarról a (sub)domainről érkezik a kérés, és egy linkre kattintással indították? A Nette a `_nss` cookie-t (korábban `nette-samesite`) használja az észleléshez. isAjax(): bool .[method] ------------------------ -Ez egy AJAX-kérés? +AJAX kérésről van szó? getRemoteAddress(): ?string .[method] ------------------------------------- -Visszaadja a felhasználó IP-címét. A megfelelő működéshez szükség lehet [egy proxy beállítás |configuration#HTTP proxy] ára. +Visszaadja a felhasználó IP címét. A megfelelő működéshez szükség lehet a [proxy beállítására |configuration#HTTP proxy]. getRemoteHost(): ?string .[method deprecated] --------------------------------------------- -Visszaadja a felhasználó IP-címének DNS-fordítását. A megfelelő működéshez szükség lehet [egy proxy beállítás |configuration#HTTP proxy] ára. +Visszaadja a felhasználó IP címének DNS fordítását. A megfelelő működéshez szükség lehet a [proxy beállítására |configuration#HTTP proxy]. -getBasicCredentials(): ?string .[method] ----------------------------------------- -Visszaadja a [Basic HTTP hitelesítési |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication] hitelesítő adatokat. +getBasicCredentials(): ?array .[method] +--------------------------------------- +Visszaadja a [Basic HTTP authentication |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication] hitelesítési adatait. ```php [$user, $password] = $httpRequest->getBasicCredentials(); @@ -182,7 +177,7 @@ Visszaadja a [Basic HTTP hitelesítési |https://developer.mozilla.org/en-US/doc getRawBody(): ?string .[method] ------------------------------- -Visszaadja a HTTP-kérelem testét: +Visszaadja a HTTP kérés törzsét. ```php $body = $httpRequest->getRawBody(); @@ -191,54 +186,61 @@ $body = $httpRequest->getRawBody(); detectLanguage(array $langs): ?string .[method] ----------------------------------------------- -Nyelv észlelése. A `$lang` paramétereként átadjuk az alkalmazás által támogatott nyelvek tömbjét, és a böngésző által preferált nyelvet adja vissza. Ez nem varázslat, a módszer csak a `Accept-Language` fejlécet használja. Ha nem talál egyezést, akkor a `null` értéket adja vissza. +Észleli a nyelvet. Paraméterként `$lang` átadjuk az alkalmazás által támogatott nyelvek tömbjét, és visszaadja azt, amelyet a látogató böngészője legszívesebben látna. Ez nem varázslat, csak az `Accept-Language` fejlécet használja. Ha nincs egyezés, `null`-t ad vissza. ```php -// A böngésző által küldött fejléc: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3 +// a böngésző pl. Accept-Language: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3 küld -$langs = ['hu', 'pl', 'en']; // az alkalmazásban támogatott nyelvek +$langs = ['hu', 'pl', 'en']; // az alkalmazás által támogatott nyelvek echo $httpRequest->detectLanguage($langs); // en ``` -RequestFactory .[#toc-requestfactory] -===================================== +RequestFactory +============== -Az aktuális HTTP-kérelem objektumát a [api:Nette\Http\RequestFactory] hozza létre. Ha olyan alkalmazást ír, amely nem használ DI konténert, akkor a következőképpen hoz létre egy kérést: +A [api:Nette\Http\RequestFactory] osztály egy `Nette\Http\Request` példány létrehozására szolgál, amely az aktuális HTTP kérést reprezentálja. (Ha a Nette-tel dolgozik, a HTTP kérés objektumot a keretrendszer automatikusan létrehozza.) ```php $factory = new Nette\Http\RequestFactory; $httpRequest = $factory->fromGlobals(); ``` -A RequestFactory a `fromGlobals()` meghívása előtt konfigurálható. A `$factory->setBinary()` segítségével kikapcsolhatjuk az érvénytelen UTF-8 szekvenciákból származó bemeneti paraméterek minden szanálását. És beállíthatunk egy proxy szervert is, ami fontos a felhasználó IP-címének helyes felismeréséhez a `$factory->setProxy(...)` segítségével. +A `fromGlobals()` metódus létrehozza a kérés objektumot az aktuális PHP globális változók (`$_GET`, `$_POST`, `$_COOKIE`, `$_FILES` és `$_SERVER`) alapján. Az objektum létrehozásakor automatikusan megtisztítja az összes GET, POST, COOKIE bemeneti paramétert, valamint az URL-t a vezérlőkarakterektől és az érvénytelen UTF-8 szekvenciáktól, ami biztosítja a biztonságot ezen adatok további feldolgozása során. + +A RequestFactory konfigurálható a `fromGlobals()` meghívása előtt: -Az URL-eket szűrők segítségével megtisztíthatjuk azoktól a karakterektől, amelyek a különböző más weboldalakon rosszul megvalósított kommentrendszerek miatt kerülhetnek bele: +- a `$factory->setBinary()` metódussal kikapcsolhatja a bemeneti paraméterek automatikus tisztítását a vezérlőkarakterektől és az érvénytelen UTF-8 szekvenciáktól. +- a `$factory->setProxy(...)` metódussal megadhatja a [proxy szerver |configuration#HTTP proxy] IP címét, ami szükséges a felhasználó IP címének helyes észleléséhez. + +A RequestFactory lehetővé teszi szűrők definiálását, amelyek automatikusan átalakítják a kérés URL-jének részeit. Ezek a szűrők eltávolítják a nem kívánt karaktereket az URL-ből, amelyeket például a különböző webhelyeken lévő kommentrendszerek helytelen implementációja miatt helyezhettek oda: ```php // szóközök eltávolítása az útvonalból $requestFactory->urlFilters['path']['%20'] = ''; -// pont, vessző vagy jobb oldali zárójel eltávolítása az URL végéről +// pont, vessző vagy jobb zárójel eltávolítása az URI végéről $requestFactory->urlFilters['url']['[.,)]$'] = ''; -// megtisztítja az elérési utat a duplázott írásjelektől (alapértelmezett szűrő). +// az útvonal tisztítása a dupla perjelektől (alapértelmezett szűrő) $requestFactory->urlFilters['path']['/{2,}'] = '/'; ``` +Az első kulcs, a `'path'` vagy `'url'`, meghatározza, hogy a szűrő az URL melyik részére vonatkozik. A második kulcs a keresendő reguláris kifejezés, az érték pedig a helyettesítés, amelyet a talált szöveg helyett használnak. + -Feltöltött fájlok .[#toc-uploaded-files] -======================================== +Feltöltött fájlok +================= -A `Nette\Http\Request::getFiles()` módszer egy normalizált struktúrájú, feltöltött fájlokat tartalmazó fát ad vissza, amelynek minden egyes levele a [api:Nette\Http\FileUpload] példánya. Ezek az objektumok tartalmazzák a feltöltött fájlokat, amelyeket a `<input type=file>` form elem által megadott adatokat. +A `Nette\Http\Request::getFiles()` metódus visszaadja az összes feltöltés tömbjét normalizált struktúrában, amelynek levelei [api:Nette\Http\FileUpload] objektumok. Ezek az `<input type=file>` űrlap elem által küldött adatokat zárják magukba. -A struktúra tükrözi a HTML elemeinek elnevezését. A legegyszerűbb példában ez lehet egy egyetlen elnevezett űrlapelem, amelyet a következőképpen küldtek be: +A struktúra tükrözi az elemek elnevezését a HTML-ben. A legegyszerűbb esetben ez egyetlen elnevezett űrlap elem lehet, amelyet így küldtek: ```latte <input type="file" name="avatar"> ``` -Ebben az esetben a `$request->getFiles()` tömböt ad vissza: +Ebben az esetben a `$request->getFiles()` a következő tömböt adja vissza: ```php [ @@ -246,19 +248,19 @@ Ebben az esetben a `$request->getFiles()` tömböt ad vissza: ] ``` -A `FileUpload` objektum akkor is létrejön, ha a felhasználó nem töltött fel semmilyen fájlt, vagy a feltöltés sikertelen volt. A `hasFile()` módszer true-t ad vissza, ha egy fájl elküldésre került: +A `FileUpload` objektum akkor is létrejön, ha a felhasználó nem küldött fájlt, vagy a küldés sikertelen volt. Azt, hogy a fájl elküldésre került-e, a `hasFile()` metódus adja vissza: ```php -$request->getFile('avatar')->hasFile(); +$request->getFile('avatar')?->hasFile(); ``` -Tömbös jelölést használó bemenet esetén a név: +Ha az elem neve tömb jelölést használ: ```latte <input type="file" name="my-form[details][avatar]"> ``` -A visszaadott fa végül így néz ki: +a visszaadott fa így néz ki: ```php [ @@ -270,10 +272,10 @@ A visszaadott fa végül így néz ki: ] ``` -A fájlok tömbjeit is létrehozhatja: +Létrehozhatunk fájlok tömbjét is: ```latte -<input type="file" name="my-form[details][avatars][] multiple"> +<input type="file" name="my-form[details][avatars][]" multiple> ``` Ebben az esetben a struktúra így néz ki: @@ -292,7 +294,7 @@ Ebben az esetben a struktúra így néz ki: ] ``` -A beágyazott tömb 1. indexének elérésének legjobb módja a következő: +A beágyazott tömb 1-es indexéhez való hozzáférés a legjobb módja a következő: ```php $file = $request->getFile(['my-form', 'details', 'avatars', 1]); @@ -301,31 +303,31 @@ if ($file instanceof FileUpload) { } ``` -Mivel kívülről nem bízhatunk az adatokban, és ezért nem támaszkodunk a struktúra formájára, ez a módszer biztonságosabb, mint a `$request->getFiles()['my-form']['details']['avatars'][1]`, amely meghibásodhat. +Mivel nem lehet megbízni a kívülről érkező adatokban, és így a fájlok struktúrájának formájában sem, ez a módszer biztonságosabb, mint például a `$request->getFiles()['my-form']['details']['avatars'][1]`, amely meghiúsulhat. -A `FileUpload` módszerek áttekintése .{toc: FileUpload} -------------------------------------------------------- +A `FileUpload` metódusainak áttekintése .{toc: FileUpload} +---------------------------------------------------------- hasFile(): bool .[method] ------------------------- -Visszaadja a `true` értéket, ha a felhasználó feltöltött egy fájlt. +Visszaadja a `true` értéket, ha a felhasználó feltöltött valamilyen fájlt. isOk(): bool .[method] ---------------------- -Visszaadja a `true` értéket, ha a fájl feltöltése sikeres volt. +Visszaadja a `true` értéket, ha a fájl sikeresen feltöltésre került. getError(): int .[method] ------------------------- -Visszaadja a feltöltött fájlhoz tartozó hibakódot. Ez az [UPLOAD_ERR_XXX |http://php.net/manual/en/features.file-upload.errors.php] konstansok egyike. Ha a fájl feltöltése sikeres volt, akkor a `UPLOAD_ERR_OK` értéket adja vissza. +Visszaadja a fájlfeltöltés hibakódját. Ez az egyik [UPLOAD_ERR_XXX|http://php.net/manual/en/features.file-upload.errors.php] konstans. Ha a feltöltés rendben lezajlott, `UPLOAD_ERR_OK`-t ad vissza. move(string $dest) .[method] ---------------------------- -Egy feltöltött fájl áthelyezése egy új helyre. Ha a célfájl már létezik, a rendszer felülírja azt. +Áthelyezi a feltöltött fájlt egy új helyre. Ha a célfájl már létezik, felülíródik. ```php $file->move('/path/to/files/name.ext'); @@ -334,61 +336,72 @@ $file->move('/path/to/files/name.ext'); getContents(): ?string .[method] -------------------------------- -Visszaadja a feltöltött fájl tartalmát. Ha a feltöltés nem volt sikeres, akkor a `null` értéket adja vissza. +Visszaadja a feltöltött fájl tartalmát. Ha a feltöltés sikertelen volt, `null`-t ad vissza. getContentType(): ?string .[method] ----------------------------------- -A feltöltött fájl MIME-tartalomtípusát az aláírása alapján állapítja meg. Ha a feltöltés nem volt sikeres, vagy a felismerés sikertelen, akkor a `null` címet adja vissza. +Észleli a feltöltött fájl MIME content type-ját az aláírása alapján. Ha a feltöltés sikertelen volt, vagy az észlelés nem sikerült, `null`-t ad vissza. .[caution] -PHP kiterjesztést igényel `fileinfo`. +Szükséges a `fileinfo` PHP kiterjesztés. getUntrustedName(): string .[method] ------------------------------------ -Visszaadja a böngésző által megadott eredeti fájlnevet. +Visszaadja a fájl eredeti nevét, ahogy a böngésző küldte. .[caution] -Ne bízzon az e módszer által visszaadott értékben. Egy ügyfél rosszindulatú fájlnevet küldhet azzal a szándékkal, hogy megrongálja vagy feltörje az alkalmazást. +Ne bízzon a metódus által visszaadott értékben. A kliens rosszindulatú fájlnevet küldhetett azzal a szándékkal, hogy károsítsa vagy feltörje az alkalmazását. getSanitizedName(): string .[method] ------------------------------------ -Visszaadja a szanált fájlnevet. Csak ASCII karaktereket tartalmaz. `[a-zA-Z0-9.-]`. Ha a név nem tartalmaz ilyen karaktereket, akkor az 'unknown' értéket adja vissza. Ha a fájl JPEG, PNG, GIF vagy WebP kép, akkor a megfelelő fájlkiterjesztést adja vissza. +Visszaadja a szanitizált fájlnevet. Csak ASCII karaktereket `[a-zA-Z0-9.-]` tartalmaz. Ha a név nem tartalmaz ilyen karaktereket, `'unknown'`-t ad vissza. Ha a fájl JPEG, PNG, GIF, WebP vagy AVIF formátumú kép, akkor a helyes kiterjesztést is visszaadja. + +.[caution] +Szükséges a `fileinfo` PHP kiterjesztés. + + +getSuggestedExtension(): ?string .[method]{data-version:3.2.4} +-------------------------------------------------------------- +Visszaadja a fájl megfelelő kiterjesztését (pont nélkül), amely megfelel az észlelt MIME típusnak. + +.[caution] +Szükséges a `fileinfo` PHP kiterjesztés. getUntrustedFullPath(): string .[method] ---------------------------------------- -Visszaadja a böngésző által a könyvtár feltöltése során megadott eredeti teljes elérési utat. A teljes elérési útvonal csak a PHP 8.1 és újabb verziókban érhető el. A korábbi verziókban ez a módszer a nem megbízható fájlnevet adja vissza. +Visszaadja a fájl eredeti elérési útját, ahogy a böngésző küldte a mappa feltöltésekor. A teljes elérési út csak PHP 8.1 és újabb verziókban érhető el. Korábbi verziókban ez a metódus az eredeti fájlnevet adja vissza. .[caution] -Ne bízzon az e metódus által visszaadott értékben. Egy ügyfél rosszindulatú fájlnevet küldhet azzal a szándékkal, hogy megrongálja vagy feltörje az alkalmazást. +Ne bízzon a metódus által visszaadott értékben. A kliens rosszindulatú fájlnevet küldhetett azzal a szándékkal, hogy károsítsa vagy feltörje az alkalmazását. getSize(): int .[method] ------------------------ -Visszaadja a feltöltött fájl méretét. Ha a feltöltés nem volt sikeres, akkor `0`-t ad vissza. +Visszaadja a feltöltött fájl méretét. Ha a feltöltés sikertelen volt, `0`-t ad vissza. getTemporaryFile(): string .[method] ------------------------------------ -Visszaadja a feltöltött fájl ideiglenes helyének elérési útvonalát. Ha a feltöltés nem volt sikeres, akkor a `''` értéket adja vissza. +Visszaadja a feltöltött fájl ideiglenes helyének elérési útját. Ha a feltöltés sikertelen volt, `''`-t ad vissza. isImage(): bool .[method] ------------------------- -Visszaadja a `true` értéket, ha a feltöltött fájl JPEG, PNG, GIF vagy WebP kép. A felismerés az aláírás alapján történik. A teljes fájl sértetlenségét nem ellenőrzi. Azt, hogy egy kép nem sérült-e, például a [betöltési |#toImage] próbálkozással állapíthatja meg. +Visszaadja a `true` értéket, ha a feltöltött fájl JPEG, PNG, GIF, WebP vagy AVIF formátumú kép. Az észlelés az aláírása alapján történik, és nem ellenőrzi az egész fájl integritását. Azt, hogy a kép nem sérült-e, például a [betöltésével |#toImage] lehet megállapítani. .[caution] -PHP bővítményt igényel: `fileinfo`. +Szükséges a `fileinfo` PHP kiterjesztés. getImageSize(): ?array .[method] -------------------------------- -Visszaad egy pár `[width, height]` a feltöltött kép méreteivel. Ha a feltöltés nem volt sikeres, vagy nem érvényes kép, akkor a `null` visszatér. +Visszaadja a `[szélesség, magasság]` párt a feltöltött kép méreteivel. Ha a feltöltés sikertelen volt, vagy nem érvényes képről van szó, `null`-t ad vissza. toImage(): Nette\Utils\Image .[method] -------------------------------------- -Képet tölt be [Image |utils:images] objektumként. Ha a feltöltés nem volt sikeres vagy nem érvényes kép, akkor a `Nette\Utils\ImageException` kivételt dob. +Betölti a képet [Image|utils:images] objektumként. Ha a feltöltés sikertelen volt, vagy nem érvényes képről van szó, `Nette\Utils\ImageException` kivételt dob. diff --git a/http/hu/response.texy b/http/hu/response.texy index b01f95c733..96f9da71cb 100644 --- a/http/hu/response.texy +++ b/http/hu/response.texy @@ -1,23 +1,23 @@ -HTTP-válasz +HTTP válasz *********** .[perex] -A Nette a HTTP-választ érthető API-val rendelkező objektumokba csomagolja, miközben szanálási szűrőt biztosít. +A Nette a HTTP választ érthető API-val rendelkező objektumokba zárja. -A HTTP-válasz egy [api:Nette\Http\Response] objektum, amelyet [függőségi injektálással |dependency-injection:passing-dependencies] történő átadással kapunk meg. Az előadókban egyszerűen hívja meg a `$httpResponse = $this->getHttpResponse()`. +A HTTP választ a [api:Nette\Http\Response] objektum képviseli. Ha a Nette-tel dolgozik, ezt az objektumot a keretrendszer automatikusan létrehozza, és [dependency injection |dependency-injection:passing-dependencies] segítségével átadhatja magának. A presenterekben elég csak a `$this->getHttpResponse()` metódust meghívni. -→ [Telepítés és követelmények |@home#Installation] +→ [Telepítés és követelmények |@home#Telepítés] -Nette\Http\Response .[#toc-nette-http-response] -=============================================== +Nette\Http\Response +=================== -A [Nette\Http\Request-tel |request] ellentétben ez az objektum változtatható, így az állapot megváltoztatásához, azaz a fejlécek elküldéséhez használhatsz állítót. Ne feledje, hogy minden beállítót **a tényleges kimenet elküldése előtt kell meghívni.** A `isSent()` metódus megmondja, hogy a kimenet elküldésre került-e. Ha a `true` visszatér, akkor minden egyes fejléc küldési kísérlet `Nette\InvalidStateException` kivételt dob. +Az objektum, ellentétben a [Nette\Http\Request|request]-tel, mutable (megváltoztatható), tehát setterek segítségével megváltoztathatja az állapotot, például fejléceket küldhet. Ne felejtse el, hogy minden settert **bármilyen kimenet elküldése előtt** kell meghívni. Azt, hogy a kimenet már elküldésre került-e, az `isSent()` metódus árulja el. Ha `true`-t ad vissza, minden fejléc küldési kísérlet `Nette\InvalidStateException` kivételt vált ki. -setCode(int $code, string $reason=null) .[method] -------------------------------------------------- -Megváltoztatja a státusz [válaszkódot |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10]. A forráskód jobb olvashatósága érdekében ajánlott a tényleges számok helyett [előre definiált konstansokat |api:Nette\Http\IResponse] használni. +setCode(int $code, ?string $reason=null) .[method] +-------------------------------------------------- +Megváltoztatja a [válasz állapotkódját |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10]. A forráskód jobb érthetősége érdekében javasoljuk, hogy a kódhoz számok helyett [előre definiált konstansokat |api:Nette\Http\IResponse] használjon. ```php $httpResponse->setCode(Nette\Http\Response::S404_NotFound); @@ -31,12 +31,12 @@ Visszaadja a válasz állapotkódját. isSent(): bool .[method] ------------------------ -Visszaadja, hogy a fejlécek már el lettek-e küldve a szerverről a böngészőnek, így már nem lehet fejléceket küldeni vagy az állapotkódot megváltoztatni. +Visszaadja, hogy a fejlécek már elküldésre kerültek-e a szerverről a böngészőbe, és így már nem lehet fejléceket küldeni vagy az állapotkódot megváltoztatni. setHeader(string $name, string $value) .[method] ------------------------------------------------ -Elküld egy HTTP fejlécet, és **felülírja** a korábban elküldött azonos nevű fejlécet. +Elküld egy HTTP fejlécet és **felülírja** a korábban elküldött, azonos nevű fejlécet. ```php $httpResponse->setHeader('Pragma', 'no-cache'); @@ -45,7 +45,7 @@ $httpResponse->setHeader('Pragma', 'no-cache'); addHeader(string $name, string $value) .[method] ------------------------------------------------ -HTTP fejlécet küld, és **nem írja felül** a korábban küldött azonos nevű fejlécet. +Elküld egy HTTP fejlécet és **nem írja felül** a korábban elküldött, azonos nevű fejlécet. ```php $httpResponse->addHeader('Accept', 'application/json'); @@ -55,12 +55,12 @@ $httpResponse->addHeader('Accept', 'application/xml'); deleteHeader(string $name) .[method] ------------------------------------ -Töröl egy korábban elküldött HTTP-fejlécet. +Törli a korábban elküldött HTTP fejlécet. getHeader(string $header): ?string .[method] -------------------------------------------- -Visszaadja az elküldött HTTP-fejlécet, vagy a `null`, ha nem létezik. A paraméter nem érzékeny a nagy- és kisbetűkre. +Visszaadja az elküldött HTTP fejlécet vagy `null`-t, ha ilyen nem létezik. A paraméter kis- és nagybetű érzéketlen. ```php $pragma = $httpResponse->getHeader('Pragma'); @@ -69,7 +69,7 @@ $pragma = $httpResponse->getHeader('Pragma'); getHeaders(): array .[method] ----------------------------- -Az összes elküldött HTTP-fejlécet asszociatív tömbként adja vissza. +Visszaadja az összes elküldött HTTP fejlécet asszociatív tömbként. ```php $headers = $httpResponse->getHeaders(); @@ -77,9 +77,9 @@ echo $headers['Pragma']; ``` -setContentType(string $type, string $charset=null) .[method] ------------------------------------------------------------- -Elküldi a `Content-Type` fejlécet. +setContentType(string $type, ?string $charset=null) .[method] +------------------------------------------------------------- +Megváltoztatja a `Content-Type` fejlécet. ```php $httpResponse->setContentType('text/plain', 'UTF-8'); @@ -88,7 +88,7 @@ $httpResponse->setContentType('text/plain', 'UTF-8'); redirect(string $url, int $code=self::S302_Found): void .[method] ----------------------------------------------------------------- -Átirányít egy másik URL-re. Ne felejtsd el ekkor kilépni a szkriptből. +Átirányít egy másik URL-re. Ne felejtse el utána leállítani a szkriptet. ```php $httpResponse->redirect('http://example.com'); @@ -98,52 +98,52 @@ exit; setExpiration(?string $time) .[method] -------------------------------------- -A HTTP-dokumentum lejárati idejének beállítása a `Cache-Control` és a `Expires` fejlécek segítségével. A paraméter vagy egy időintervallum (szövegként) vagy a `null`, amely kikapcsolja a gyorsítótárazást. +Beállítja a HTTP dokumentum lejáratát a `Cache-Control` és `Expires` fejlécek segítségével. A paraméter vagy egy időintervallum (szövegként), vagy `null`, ami letiltja a gyorsítótárazást. ```php -// a böngésző gyorsítótár egy óra múlva lejár +// a böngésző gyorsítótára egy óra múlva lejár $httpResponse->setExpiration('1 hour'); ``` sendAsFile(string $fileName) .[method] -------------------------------------- -A választ a megadott nevű *Save as* párbeszédpanel segítségével kell letölteni. Magát a fájlt nem küldi el a kimenetre. +A választ a *Mentés másként* párbeszédablak segítségével tölti le a megadott néven. Magát a fájlt nem küldi el. ```php -$httpResponse->sendAsFile('invoice.pdf'); +$httpResponse->sendAsFile('faktura.pdf'); ``` -setCookie(string $name, string $value, $time, string $path=null, string $domain=null, bool $secure=null, bool $httpOnly=null, string $sameSite=null) .[method] --------------------------------------------------------------------------------------------------------------------------------------------------------------- -Cookie-t küld. Alapértelmezett paraméterértékek: +setCookie(string $name, string $value, $time, ?string $path=null, ?string $domain=null, ?bool $secure=null, ?bool $httpOnly=null, ?string $sameSite=null) .[method] +------------------------------------------------------------------------------------------------------------------------------------------------------------------- +Elküld egy cookie-t. A paraméterek alapértelmezett értékei: -| `$path` | `'/'` | az (al)tartomány összes elérési útvonalára kiterjedő hatállyal *(konfigurálható)*. -| `$domain` | `null` | az aktuális (al)tartomány hatókörével, de az aldomainek kivételével *(konfigurálható)* -| `$secure` | `true` | ha a webhely HTTPS-en fut, egyébként `false` *(konfigurálható)* -| `$httpOnly` | `true` | a cookie nem hozzáférhető a JavaScript számára. -| `$sameSite` | `'Lax'` | a cookie-t nem kell elküldeni, ha [más eredetű hozzáférésről történik|nette:glossary#SameSite cookie]. +| `$path` | `'/'` | a cookie hatóköre az összes útvonalra kiterjed a (sub)domainen *(konfigurálható)* +| `$domain` | `null` | ami azt jelenti, hogy a hatókör az aktuális (sub)domainre terjed ki, de nem annak aldomainjeire *(konfigurálható)* +| `$secure` | `true` | ha a webhely HTTPS-en fut, egyébként `false` *(konfigurálható)* +| `$httpOnly` | `true` | a cookie JavaScript számára nem hozzáférhető +| `$sameSite` | `'Lax'` | a cookie nem feltétlenül kerül elküldésre [más domainről való hozzáférés |nette:glossary#SameSite cookie] esetén -A `$path`, `$domain` és `$secure` paraméterek alapértelmezett értékeit a [configuration#HTTP cookie |configuration#HTTP cookie] menüpontban módosíthatja. +A `$path`, `$domain` és `$secure` paraméterek alapértelmezett értékeit megváltoztathatja a [konfigurációban |configuration#HTTP cookie]. -Az idő megadható másodpercek számaként vagy karakterláncként: +Az időt megadhatja másodpercek számaként vagy stringként: ```php -$httpResponse->setCookie('lang', 'en', '100 days'); +$httpResponse->setCookie('lang', 'cs', '100 days'); ``` -A `$domain` opció határozza meg, hogy mely tartományok (származási helyek) fogadhatják el a cookie-kat. Ha nincs megadva, akkor a cookie-t ugyanaz a (al)tartomány fogadja el, amelyik beállítja, kivéve azok aldomainjeit. Ha a `$domain` van megadva, akkor az aldomainek is bevonásra kerülnek. Ezért a `$domain` megadása kevésbé korlátozó, mint az elhagyása. Például, ha a `$domain = 'nette.org'`, cookie az összes aldomainen is elérhető, mint a `doc.nette.org`. +A `$domain` paraméter meghatározza, mely domainek fogadhatják el a cookie-t. Ha nincs megadva, a cookie-t ugyanaz a (sub)domain fogadja el, amelyik beállította, de nem annak aldomainjei. Ha a `$domain` meg van adva, az aldomainek is beletartoznak. Ezért a `$domain` megadása kevésbé korlátozó, mint annak elhagyása. Például a `$domain = 'nette.org'` esetén a cookie-k minden aldomainen, mint például a `doc.nette.org`, is elérhetők. -A `$sameSite` értékhez használhatja a `Response::SameSiteLax`, `SameSiteStrict` és `SameSiteNone` konstansokat. +A `$sameSite` értékhez használhatja a `Response::SameSiteLax`, `Response::SameSiteStrict` és `Response::SameSiteNone` konstansokat. -deleteCookie(string $name, string $path=null, string $domain=null, bool $secure=null): void .[method] ------------------------------------------------------------------------------------------------------ -Töröl egy sütit. A paraméterek alapértelmezett értékei a következők: -- `$path` minden könyvtárra kiterjedő hatállyal (`'/'`). -- `$domain` az aktuális (al)tartomány hatókörével, de nem az aldomainek hatókörével. -- A `$secure` értéket a [konfiguráció#HTTP cookie |configuration#HTTP cookie] beállításai befolyásolják. +deleteCookie(string $name, ?string $path=null, ?string $domain=null, ?bool $secure=null): void .[method] +-------------------------------------------------------------------------------------------------------- +Törli a cookie-t. A paraméterek alapértelmezett értékei: +- `$path` hatókörrel az összes könyvtárra (`'/'`) +- `$domain` hatókörrel az aktuális (sub)domainre, de nem annak aldomainjeire +- `$secure` a [konfigurációban |configuration#HTTP cookie] beállítottak szerint ```php $httpResponse->deleteCookie('lang'); diff --git a/http/hu/sessions.texy b/http/hu/sessions.texy index 65dcc2393f..e6719c82d3 100644 --- a/http/hu/sessions.texy +++ b/http/hu/sessions.texy @@ -1,71 +1,71 @@ -Ülések -****** +Sessionök +********* <div class=perex> -A HTTP egy állapotmentes protokoll, de szinte minden alkalmazásnak szüksége van állapot megőrzésére a kérések között, pl. egy bevásárlókosár tartalma. Erre szolgál a munkamenet. Lássuk +A HTTP egy állapot nélküli protokoll, azonban szinte minden alkalmazásnak szüksége van az állapot megőrzésére a kérések között, például a bevásárlókosár tartalmának megőrzésére. Pontosan erre szolgál a session vagy munkamenet. Megmutatjuk, -- hogyan használjuk a munkameneteket -- hogyan kerüljük el a névkonfliktusokat -- hogyan állítsuk be a lejáratot +- hogyan használjuk a sessionöket +- hogyan kerüljük el a névütközéseket +- hogyan állítsuk be a lejárati időt </div> -Munkamenetek használatakor minden felhasználó kap egy egyedi azonosítót, a munkamenet-azonosítót, amelyet egy cookie-ban adunk át. Ez szolgál a munkamenetadatok kulcsaként. A sütikkel ellentétben, amelyek a böngésző oldalán tárolódnak, a munkamenetadatokat a kiszolgáló oldalán tárolják. +Sessionök használatakor minden felhasználó egyedi azonosítót kap, az úgynevezett session ID-t, amelyet cookie-ban továbbítanak. Ez kulcsként szolgál a session adatokhoz. Ellentétben a cookie-kkal, amelyek a böngésző oldalán tárolódnak, a session adatok a szerver oldalán tárolódnak. -A munkamenetet a [konfigurációban |configuration#session] állítjuk be, fontos a lejárati idő megválasztása. +A sessiont a [konfigurációban |configuration#Session] állítjuk be, különösen fontos a lejárati idő megválasztása. -A munkamenetet a [api:Nette\Http\Session] objektum kezeli, amelyet [függőségi injektálással |dependency-injection:passing-dependencies] átadva kapunk meg. A prezenterekben egyszerűen hívjuk meg a `$session = $this->getSession()`. +A session kezeléséért a [api:Nette\Http\Session] objektum felelős, amelyhez úgy juthat hozzá, hogy [dependency injection |dependency-injection:passing-dependencies] segítségével átadja magának. A presenterekben elég csak a `$session = $this->getSession()` metódust meghívni. -→ [Telepítés és követelmények |@home#Installation] +→ [Telepítés és követelmények |@home#Telepítés] -A munkamenet indítása .[#toc-starting-session] -============================================== +Session indítása +================ -Alapértelmezés szerint a Nette automatikusan elindít egy munkamenetet abban a pillanatban, amikor elkezdünk olvasni belőle vagy adatokat írni rá. A munkamenet manuális indításához használja a `$session->start()` parancsot. +A Nette alapértelmezés szerint automatikusan elindítja a sessiont abban a pillanatban, amikor elkezdünk olvasni belőle vagy adatokat írni bele. Manuálisan a session a `$session->start()` segítségével indítható el. -A PHP a munkamenet indításakor a gyorsítótárazást befolyásoló HTTP fejléceket küld, lásd [php:session_cache_limiter], és esetleg egy cookie-t a munkamenet azonosítójával. Ezért mindig el kell indítani a munkamenetet, mielőtt bármilyen kimenetet küldenénk a böngészőnek, különben kivételt dob. Ha tehát tudja, hogy az oldal megjelenítése során munkamenetet fog használni, indítsa el azt manuálisan előtte, például a prezenterben. +A PHP a session indításakor HTTP fejléceket küld, amelyek befolyásolják a gyorsítótárazást, lásd [php:session_cache_limiter], és adott esetben a session ID-t tartalmazó cookie-t is. Ezért mindig el kell indítani a sessiont még azelőtt, hogy bármilyen kimenetet küldenénk a böngészőbe, különben kivétel váltódik ki. Ha tehát tudja, hogy az oldal megjelenítése során sessiont fog használni, indítsa el manuálisan előtte, például a presenterben. -Fejlesztői módban a Tracy elindítja a munkamenetet, mert azt használja az átirányítás és az AJAX-kérések sávjainak megjelenítéséhez a Tracy Barban. +Fejlesztői módban a Tracy indítja el a sessiont, mert azt használja az átirányítási és AJAX kérések sávjainak megjelenítésére a Tracy Barban. -A szakasz. .[#toc-section] -=========================== +Szekciók +======== -Tiszta PHP-ben a munkamenet-adattároló egy tömbként van implementálva, amely egy globális változóval érhető el: `$_SESSION`. A probléma az, hogy az alkalmazások általában több független részből állnak, és ha mindegyiknek csak egyazon tömb áll rendelkezésre, előbb-utóbb névütközés következik be. +Tiszta PHP-ban a session adattárolója egy tömbként valósul meg, amely a `$_SESSION` globális változón keresztül érhető el. A probléma az, hogy az alkalmazások általában számos egymástól független részből állnak, és ha mindegyiknek csak egy tömb áll rendelkezésére, előbb-utóbb névütközés következik be. -A Nette Framework úgy oldja meg a problémát, hogy a teljes teret részekre (objektumokra [api:Nette\Http\SessionSection]) osztja. Ilyenkor minden egység a saját szekcióját használja egyedi névvel, és nem fordulhat elő ütközés. +A Nette Framework ezt a problémát úgy oldja meg, hogy az egész teret szekciókra ( [api:Nette\Http\SessionSection] objektumokra) osztja. Minden egység ezután saját, egyedi nevű szekciót használ, és így már nem fordulhat elő ütközés. -A szekciót a munkamenetkezelőtől kapjuk: +A szekciót a sessionből kapjuk meg: ```php -$section = $session->getSection('unique name'); +$section = $session->getSection('unikatni nazev'); ``` -A prezenterben elég a `getSession()` paraméterrel meghívni: +A presenterben elég a `getSession()`-t használni paraméterrel: ```php -// $this a bemutató -$section = $this->getSession('unique name'); +// $this egy Presenter +$section = $this->getSession('unikatni nazev'); ``` -A szakasz meglétét a `$session->hasSection('unique name')` metódussal lehet ellenőrizni. +A szekció létezését a `$session->hasSection('unikatni nazev')` metódussal ellenőrizhetjük. -Magával a szakasszal nagyon egyszerűen lehet dolgozni a `set()`, `get()` és `remove()` metódusok segítségével: +Magával a szekcióval ezután nagyon egyszerűen dolgozhatunk a `set()`, `get()` és `remove()` metódusokkal: ```php // változó írása $section->set('userName', 'franta'); -// változó kiolvasása, nullát ad vissza, ha nem létezik +// változó olvasása, null-t ad vissza, ha nem létezik echo $section->get('userName'); -// változó eltávolítása +// változó törlése $section->remove('userName'); ``` -A `foreach` ciklussal a szakasz összes változóját megkaphatjuk: +Az összes változó megszerzéséhez a szekcióból használhatjuk a `foreach` ciklust: ```php foreach ($section as $key => $val) { @@ -74,137 +74,138 @@ foreach ($section as $key => $val) { ``` -Hogyan állítsuk be a lejáratot .[#toc-how-to-set-expiration] ------------------------------------------------------------- +Lejárati idő beállítása +----------------------- -A lejárat beállítható egyes szakaszokhoz vagy akár egyes változókhoz is. Engedhetjük, hogy a felhasználó bejelentkezése 20 perc múlva lejárjon, de a bevásárlókosár tartalmára továbbra is emlékezhetünk. +Az egyes szekciókhoz vagy akár egyes változókhoz is beállítható lejárati idő. Így például hagyhatjuk, hogy a felhasználó bejelentkezése 20 perc múlva lejárjon, miközben továbbra is megjegyezzük a kosár tartalmát. ```php -// a szakasz 20 perc múlva lejár +// a szekció 20 perc múlva lejár $section->setExpiration('20 minutes'); ``` -A `set()` metódus harmadik paramétere az egyes változók lejáratának beállítására szolgál: +Az egyes változók lejárati idejének beállítására a `set()` metódus harmadik paramétere szolgál: ```php -// a 'flash' változó 30 másodperc múlva jár le +// a 'flash' változó már 30 másodperc múlva lejár $section->set('flash', $message, '30 seconds'); ``` .[note] -Ne feledje, hogy a teljes munkamenet lejárati idejének (lásd a [munkamenet konfigurációját |configuration#session]) egyenlőnek vagy magasabbnak kell lennie, mint az egyes szakaszokhoz vagy változókhoz beállított idő. +Ne felejtse el, hogy az egész session lejárati ideje (lásd [session konfiguráció |configuration#Session]) meg kell hogy egyezzen vagy magasabb legyen, mint az egyes szekciókhoz vagy változókhoz beállított idő. -A korábban beállított lejárati idő törlése a `removeExpiration()` módszerrel érhető el. A teljes szakasz azonnali törlését a `remove()` módszer biztosítja. +A korábban beállított lejárati idő törlését a `removeExpiration()` metódussal érhetjük el. Az egész szekció azonnali törlését a `remove()` metódus biztosítja. -$onStart, $onBeforeWrite események .[#toc-events-onstart-onbeforewrite] ------------------------------------------------------------------------ +$onStart, $onBeforeWrite események +---------------------------------- -A `Nette\Http\Session` objektum rendelkezik `$onStart` a `$onBeforeWrite`[eseményekkel |nette:glossary#Events], így olyan visszahívásokat adhat hozzá, amelyek a munkamenet elindulása után vagy a munkamenet lemezre írása, majd befejezése előtt hívódnak meg. +A `Nette\Http\Session` objektumnak vannak [$onStart és $onBeforeWrite eseményei |nette:glossary#Eventek események], így hozzáadhat callbackeket, amelyek a session indítása után vagy a lemezre írása és az azt követő befejezése előtt hívódnak meg. ```php $session->onBeforeWrite[] = function () { - // adatok írása a munkamenetbe + // adatokat írunk a sessionbe $this->section->set('basket', $this->basket); }; ``` -Munkamenet-kezelés .[#toc-session-management] -============================================= +Session kezelés +=============== -A `Nette\Http\Session` osztály munkamenet-kezelési metódusainak áttekintése: +A `Nette\Http\Session` osztály metódusainak áttekintése a session kezeléséhez: <div class=wiki-methods-brief> start(): void .[method] ----------------------- -Munkamenetet indít. +Elindítja a sessiont. isStarted(): bool .[method] --------------------------- -Elindult a munkamenet? +El van indítva a session? close(): void .[method] ----------------------- -A munkamenet befejeződik. A munkamenet automatikusan véget ér a parancsfájl végén. +Befejezi a sessiont. A session automatikusan befejeződik a szkript futásának végén. destroy(): void .[method] ------------------------- -Befejezi és törli a munkamenetet. +Befejezi és törli a sessiont. exists(): bool .[method] ------------------------ -A HTTP-kérelem tartalmaz egy munkamenet-azonosítót tartalmazó sütit? +Tartalmaz a HTTP kérés cookie-t session ID-vel? regenerateId(): void .[method] ------------------------------ -Új véletlenszerű munkamenet-azonosítót generál. Az adatok változatlanok maradnak. +Új, véletlenszerű session ID-t generál. Az adatok megmaradnak. getId(): string .[method] ------------------------- -Visszaadja a munkamenet azonosítóját. +Visszaadja a session ID-t. </div> -Konfiguráció .[#toc-configuration] ----------------------------------- +Konfiguráció +------------ -A [konfigurációban |configuration#session] konfiguráljuk a munkamenetet. Ha olyan alkalmazást írsz, amely nem használ DI konténert, használd ezeket a módszereket a konfiguráláshoz. Ezeket a munkamenet elindítása előtt kell meghívni. +A sessiont a [konfigurációban |configuration#Session] állítjuk be. Ha olyan alkalmazást ír, amely nem használ DI konténert, ezek a metódusok szolgálnak a konfigurációhoz. Ezeket még a session elindítása előtt kell meghívni. <div class=wiki-methods-brief> setName(string $name): static .[method] --------------------------------------- -Beállítja a munkamenet azonosítójának továbbítására használt süti nevét. Az alapértelmezett név `PHPSESSID`. Ez akkor hasznos, ha több különböző alkalmazást futtat ugyanazon a webhelyen. +Beállítja annak a cookie-nak a nevét, amelyben a session ID-t továbbítják. A standard név `PHPSESSID`. Hasznos abban az esetben, ha egy webhelyen belül több különböző alkalmazást üzemeltet. getName(): string .[method] --------------------------- -Visszaadja a munkamenet süti nevét. +Visszaadja annak a cookie-nak a nevét, amelyben a session ID-t továbbítják. setOptions(array $options): static .[method] -------------------------------------------- -A munkamenet konfigurálása. Lehetőség van az összes PHP [munkamenet direktíva |https://www.php.net/manual/en/session.configuration.php] beállítására (camelCase formátumban, pl. `session.save_path` helyett `savePath` írása), valamint [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. +Konfigurálja a sessiont. Beállíthatók az összes PHP [session direktíva |https://www.php.net/manual/en/session.configuration.php] (camelCase formátumban, pl. `session.save_path` helyett `savePath`-t írunk) és a [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters] is. setExpiration(?string $time): static .[method] ---------------------------------------------- -Beállítja az inaktivitás idejét, amely után a munkamenet lejár. +Beállítja az inaktivitási időt, amely után a session lejár. -setCookieParameters(string $path, string $domain=null, bool $secure=null, string $samesite=null): static .[method] ------------------------------------------------------------------------------------------------------------------- -A sütik paramétereinek beállítása. Az alapértelmezett paraméterértékeket a [configuration#Session cookie |configuration#Session cookie] menüpontban módosíthatja. +setCookieParameters(string $path, ?string $domain=null, ?bool $secure=null, ?string $samesite=null): static .[method] +--------------------------------------------------------------------------------------------------------------------- +Cookie paraméterek beállítása. A paraméterek alapértelmezett értékeit megváltoztathatja a [konfigurációban |configuration#Session cookie]. setSavePath(string $path): static .[method] ------------------------------------------- -Beállítja a munkamenetfájlok tárolási könyvtárát. +Beállítja a könyvtárat, ahová a session fájlok mentésre kerülnek. setHandler(\SessionHandlerInterface $handler): static .[method] --------------------------------------------------------------- -Beállítja az egyéni kezelőt, lásd a [PHP dokumentációt |https://www.php.net/manual/en/class.sessionhandlerinterface.php]. +Saját handler beállítása, lásd [PHP dokumentáció|https://www.php.net/manual/en/class.sessionhandlerinterface.php]. </div> -Első biztonság .[#toc-safety-first] -=================================== +Biztonság mindenekelőtt +======================= -A kiszolgáló feltételezi, hogy ugyanazzal a felhasználóval kommunikál, amíg a kérések ugyanazt a munkamenet-azonosítót tartalmazzák. A biztonsági mechanizmusok feladata annak biztosítása, hogy ez a viselkedés valóban működjön, és ne legyen lehetőség az azonosító kicserélésére vagy ellopására. +A szerver feltételezi, hogy ugyanazzal a felhasználóval kommunikál, amíg a kéréseket ugyanaz a session ID kíséri. A biztonsági mechanizmusok feladata annak biztosítása, hogy ez valóban így legyen, és ne lehessen az azonosítót ellopni vagy meghamisítani. -Ezért a Nette Framework megfelelően konfigurálja a PHP direktívákat, hogy a munkamenet-azonosítót csak sütikben továbbítsa, hogy elkerülje a JavaScriptből való hozzáférést, és hogy figyelmen kívül hagyja az URL-ben szereplő azonosítókat. Sőt, kritikus pillanatokban, például a felhasználó bejelentkezésekor új munkamenet-azonosítót generál. +A Nette Framework ezért helyesen konfigurálja a PHP direktívákat, hogy a session ID-t csak cookie-ban továbbítsa, JavaScript számára hozzáférhetetlenné tegye, és az URL-ben lévő esetleges azonosítókat figyelmen kívül hagyja. Ezenkívül kritikus pillanatokban, mint például a felhasználó bejelentkezésekor, új session ID-t generál. -Az ini_set függvényt a PHP konfigurálására használják, de sajnos egyes webtárhely-szolgáltatóknál tilos a használata. Ha ez a te eseted, próbáld meg kérni a tárhelyszolgáltatódat, hogy engedélyezze neked ezt a funkciót, vagy legalábbis megfelelően konfigurálja a szerverét. .[note] +.[note] +A PHP konfigurálásához az ini_set függvényt használják, amelyet sajnos néhány hosting szolgáltató letilt. Ha ez az Ön szolgáltatójának esete is, próbáljon meg velük megegyezni, hogy engedélyezzék a függvényt, vagy legalább konfigurálják a szervert. diff --git a/http/hu/urls.texy b/http/hu/urls.texy index fe7f6cf7c8..d53b089012 100644 --- a/http/hu/urls.texy +++ b/http/hu/urls.texy @@ -1,16 +1,16 @@ -URL elemző és építő -******************* +URL-ekkel való munka +******************** .[perex] -Az [Url |#Url], [UrlImmutable |#UrlImmutable] és [UrlScript |#UrlScript] osztályok megkönnyítik az URL-ek kezelését, elemzését és manipulálását. +Az [#Url], [#UrlImmutable] és [#UrlScript] osztályok lehetővé teszik az URL-ek egyszerű generálását, elemzését és manipulálását. -→ [Telepítés és követelmények |@home#Installation] +→ [Telepítés és követelmények |@home#Telepítés] Url === -A [api:Nette\Http\Url] osztály megkönnyíti az URL-rel és annak egyes összetevőivel való munkát, amelyeket ez az ábra vázol fel: +A [api:Nette\Http\Url] osztály lehetővé teszi az URL-ekkel és azok egyes komponenseivel való egyszerű munkát, amelyeket ez a rajz ábrázol: /--pre scheme user password host port path query fragment @@ -36,7 +36,7 @@ $url->setScheme('https') echo $url; // 'https://localhost/edit?foo=bar' ``` -Az URL-t is elemezheti, majd manipulálhatja: +Lehetőség van az URL elemzésére és további manipulálására is: ```php $url = new Url( @@ -44,62 +44,94 @@ $url = new Url( ); ``` -A következő módszerek állnak rendelkezésre az egyes URL-komponensek lekérdezéséhez vagy módosításához: +A `Url` osztály implementálja a `JsonSerializable` interfészt, és rendelkezik a `__toString()` metódussal, így az objektumot ki lehet írni, vagy fel lehet használni a `json_encode()`-nak átadott adatokban. + +```php +echo $url; +echo json_encode([$url]); +``` + + +URL komponensek .[method] +------------------------- + +Az URL egyes komponenseinek visszaadására vagy megváltoztatására a következő metódusok állnak rendelkezésre: .[language-php] -| Setter | Getter | Visszatérő érték +| Setter | Getter | Visszaadott érték |-------------------------------------------------------------------------------------------- -| `setScheme(string $scheme)`| `getScheme(): string`| `'http'` -| `setUser(string $user)`| `getUser(): string`| `'john'` -| `setPassword(string $password)`| `getPassword(): string`| `'xyz*12'` -| `setHost(string $host)`| `getHost(): string`| `'nette.org'` -| `setPort(int $port)`| `getPort(): ?int`| `8080` -| | `getDefaultPort(): ?int`| `80` -| `setPath(string $path)`| `getPath(): string`| `'/en/download'` -| `setQuery(string\|array $query)`| `getQuery(): string`| `'name=param'` -| `setFragment(string $fragment)`| `getFragment(): string`| `'footer'` -| | `getAuthority(): string`| `'nette.org:8080'` -| | `getHostUrl(): string`| `'http://nette.org:8080'` -| | `getAbsoluteUrl(): string` | teljes URL - -Az egyes lekérdezési paraméterekkel is operálhatunk a következőkkel: +| `setScheme(string $scheme)` | `getScheme(): string` | `'http'` +| `setUser(string $user)` | `getUser(): string` | `'john'` +| `setPassword(string $password)` | `getPassword(): string` | `'xyz*12'` +| `setHost(string $host)` | `getHost(): string` | `'nette.org'` +| `setPort(int $port)` | `getPort(): ?int` | `8080` +| | `getDefaultPort(): ?int` | `80` +| `setPath(string $path)` | `getPath(): string` | `'/en/download'` +| `setQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` +| `setFragment(string $fragment)` | `getFragment(): string` | `'footer'` +| | `getAuthority(): string` | `'john:xyz%2A12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | teljes URL + +Figyelmeztetés: Amikor olyan URL-lel dolgozik, amelyet a [HTTP kérésből|request] szereztek be, vegye figyelembe, hogy nem fogja tartalmazni a fragmentet, mivel a böngésző nem küldi el azt a szerverre. + +Az egyes query paraméterekkel is dolgozhatunk a következők segítségével: .[language-php] -| Setter | Getter +| Setter | Getter |--------------------------------------------------- -| `setQuery(string\|array $query)` | `getQueryParameters(): array` -| `setQueryParameter(string $name, $val)`| `getQueryParameter(string $name)` +| `setQuery(string\|array $query)` | `getQueryParameters(): array` +| `setQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` -A `getDomain(int $level = 2)` metódus a gazdatest jobb vagy bal oldalát adja vissza. Így működik, ha az állomás a `www.nette.org`: + +getDomain(int $level = 2): string .[method] +------------------------------------------- +Visszaadja a hoszt jobb vagy bal részét. Így működik, ha a hoszt `www.nette.org`: .[language-php] -| `getDomain(1)` | `'org'` -| `getDomain(2)` | `'nette.org'` -| `getDomain(3)` | `'www.nette.org'` -| `getDomain(0)` | `'www.nette.org'` -| `getDomain(-1)` | `'www.nette'` -| `getDomain(-2)` | `'www'` -| `getDomain(-3)` | `''` +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `''` -A `Url` osztály megvalósítja a `JsonSerializable` interfészt, és rendelkezik egy `__toString()` metódussal, hogy az objektumot ki lehessen nyomtatni, vagy felhasználni a `json_encode()`-nak átadott adatokban. +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +Ellenőrzi, hogy két URL megegyezik-e. ```php -echo $url; -echo json_encode([$url]); +$url->isEqual('https://nette.org'); ``` -A `isEqual(string|Url $anotherUrl): bool` metódus azt vizsgálja, hogy a két URL azonos-e. + +Url::isAbsolute(string $url): bool .[method]{data-version:3.3.2} +---------------------------------------------------------------- +Ellenőrzi, hogy az URL abszolút-e. Az URL abszolútnak tekintendő, ha sémával kezdődik (pl. http, https, ftp), amelyet kettőspont követ. ```php -$url->isEqual('https://nette.org'); +Url::isAbsolute('https://nette.org'); // true +Url::isAbsolute('//nette.org'); // false +``` + + +Url::removeDotSegments(string $path): string .[method]{data-version:3.3.2} +-------------------------------------------------------------------------- +Normalizálja az URL elérési útját a speciális `.` és `..` szegmensek eltávolításával. A metódus eltávolítja a felesleges elérési út elemeket ugyanúgy, ahogy a webböngészők teszik. + +```php +Url::removeDotSegments('/path/../subtree/./file.txt'); // '/subtree/file.txt' +Url::removeDotSegments('/../foo/./bar'); // '/foo/bar' +Url::removeDotSegments('./today/../file.txt'); // 'file.txt' ``` -UrlImmutable .[#toc-urlimmutable] -================================= +UrlImmutable +============ -A [api:Nette\Http\UrlImmutable] osztály a `Url` osztály megváltoztathatatlan alternatívája (ahogyan a PHP-ben a `DateTimeImmutable` a `DateTime` megváltoztathatatlan alternatívája). Setterek helyett úgynevezett witherekkel rendelkezik, amelyek nem változtatják meg az objektumot, hanem új példányokat adnak vissza a módosított értékkel: +A [api:Nette\Http\UrlImmutable] osztály az [#Url] osztály immutable (megváltoztathatatlan) alternatívája (hasonlóan ahhoz, ahogy a PHP-ban a `DateTimeImmutable` a `DateTime` megváltoztathatatlan alternatívája). Setterek helyett ún. withereket használ, amelyek nem változtatják meg az objektumot, hanem új példányokat adnak vissza módosított értékkel: ```php use Nette\Http\UrlImmutable; @@ -111,53 +143,96 @@ $url = new UrlImmutable( $newUrl = $url ->withUser('') ->withPassword('') - ->withPath('/en/'); + ->withPath('/cs/'); + +echo $newUrl; // 'http://john:xyz%2A12@nette.org:8080/cs/?name=param#footer' +``` + +A `UrlImmutable` osztály implementálja a `JsonSerializable` interfészt, és rendelkezik a `__toString()` metódussal, így az objektumot ki lehet írni, vagy fel lehet használni a `json_encode()`-nak átadott adatokban. -echo $newUrl; // 'http://nette.org:8080/en/?name=param#footer' +```php +echo $url; +echo json_encode([$url]); ``` -A következő metódusok állnak rendelkezésre az egyes URL-összetevők kinyerésére vagy megváltoztatására: + +URL komponensek .[method] +------------------------- + +Az URL egyes komponenseinek visszaadására vagy megváltoztatására a következő metódusok szolgálnak: .[language-php] -| Wither | Getter | Visszaadott érték +| Wither | Getter | Visszaadott érték |-------------------------------------------------------------------------------------------- -| `withScheme(string $scheme)`| `getScheme(): string`| `'http'` -| `withUser(string $user)`| `getUser(): string`| `'john'` -| `withPassword(string $password)`| `getPassword(): string`| `'xyz*12'` -| `withHost(string $host)`| `getHost(): string`| `'nette.org'` -| `withPort(int $port)`| `getPort(): ?int`| `8080` -| | `getDefaultPort(): ?int`| `80` -| `withPath(string $path)`| `getPath(): string`| `'/en/download'` -| `withQuery(string\|array $query)`| `getQuery(): string`| `'name=param'` -| `withFragment(string $fragment)`| `getFragment(): string`| `'footer'` -| | `getAuthority(): string`| `'nette.org:8080'` -| | `getHostUrl(): string`| `'http://nette.org:8080'` -| | `getAbsoluteUrl(): string` | teljes URL - -Az egyes lekérdezési paraméterekkel is operálhatunk a következőkkel: +| `withScheme(string $scheme)` | `getScheme(): string` | `'http'` +| `withUser(string $user)` | `getUser(): string` | `'john'` +| `withPassword(string $password)` | `getPassword(): string` | `'xyz*12'` +| `withHost(string $host)` | `getHost(): string` | `'nette.org'` +| `withPort(int $port)` | `getPort(): ?int` | `8080` +| | `getDefaultPort(): ?int` | `80` +| `withPath(string $path)` | `getPath(): string` | `'/en/download'` +| `withQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` +| `withFragment(string $fragment)` | `getFragment(): string` | `'footer'` +| | `getAuthority(): string` | `'john:xyz%2A12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | teljes URL + +A `withoutUserInfo()` metódus eltávolítja a `user`-t és a `password`-öt. + +Az egyes query paraméterekkel is dolgozhatunk a következők segítségével: .[language-php] -| Wither | Getter +| Wither | Getter |----------------------------------------------- -| `withQuery(string\|array $query)` | `getQueryParameters(): array` -| `withQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` +| `withQuery(string\|array $query)` | `getQueryParameters(): array` +| `withQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` -A `getDomain(int $level = 2)` módszer ugyanúgy működik, mint a `Url` módszer. A `withoutUserInfo()` módszer eltávolítja a `user` és a `password` címet. -A `UrlImmutable` osztály megvalósítja a `JsonSerializable` interfészt, és rendelkezik egy `__toString()` metódussal, hogy az objektumot ki lehessen nyomtatni, vagy fel lehessen használni a `json_encode()`-nak átadott adatokban. +getDomain(int $level = 2): string .[method] +------------------------------------------- +Visszaadja a hoszt jobb vagy bal részét. Így működik, ha a hoszt `www.nette.org`: + +.[language-php] +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `''` + + +resolve(string $reference): UrlImmutable .[method]{data-version:3.3.2} +---------------------------------------------------------------------- +Abszolút URL-t vezet le ugyanúgy, ahogy a böngésző feldolgozza a HTML oldalon lévő linkeket: +- ha a link abszolút URL (sémát tartalmaz), változatlanul használja +- ha a link `//`-vel kezdődik, csak a sémát veszi át az aktuális URL-ből +- ha a link `/`-vel kezdődik, abszolút elérési utat hoz létre a domain gyökerétől +- egyéb esetekben az URL-t relatívan állítja össze az aktuális elérési úthoz képest ```php -echo $url; -echo json_encode([$url]); +$url = new UrlImmutable('https://example.com/path/page'); +echo $url->resolve('../foo'); // 'https://example.com/foo' +echo $url->resolve('/bar'); // 'https://example.com/bar' +echo $url->resolve('sub/page.html'); // 'https://example.com/path/sub/page.html' +``` + + +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +Ellenőrzi, hogy két URL megegyezik-e. + +```php +$url->isEqual('https://nette.org'); ``` -A `isEqual(string|Url $anotherUrl): bool` metódus azt vizsgálja, hogy a két URL azonos-e. +UrlScript +========= -UrlScript .[#toc-urlscript] -=========================== +A [api:Nette\Http\UrlScript] osztály az [#UrlImmutable] leszármazottja, és további virtuális URL komponensekkel bővíti ki, mint például a projekt gyökérkönyvtára stb. Ugyanúgy, mint a szülő osztálya, immutable (megváltoztathatatlan) objektum. -A [api:Nette\Http\UrlScript] osztály a `UrlImmutable` leszármazottja, és ezen felül megkülönbözteti az URL ezen logikai részeit: +A következő diagram azokat a komponenseket ábrázolja, amelyeket az UrlScript felismer: /--pre baseUrl basePath relativePath relativeUrl @@ -169,17 +244,23 @@ A [api:Nette\Http\UrlScript] osztály a `UrlImmutable` leszármazottja, és ezen scriptPath pathInfo \-- -A következő metódusok állnak rendelkezésre ezen részek lekérdezéséhez: +- `baseUrl` az alkalmazás alap URL címe, beleértve a domaint és az alkalmazás gyökérkönyvtárához vezető útvonalrészt +- `basePath` az alkalmazás gyökérkönyvtárához vezető útvonalrész +- `scriptPath` az aktuális szkripthez vezető útvonal +- `relativePath` a szkript neve (esetleg további útvonalszegmensek) a basePath-hoz képest relatívan +- `relativeUrl` az URL teljes része a baseUrl után, beleértve a query stringet és a fragmentet. +- `pathInfo` ma már ritkán használt URL rész a szkript neve után + +Az URL részeinek visszaadására a következő metódusok állnak rendelkezésre: .[language-php] -| Getter | Visszaadott érték +| Getter | Visszaadott érték |------------------------------------------------ -| `getScriptPath(): string`| `'/admin/script.php'` -| `getBasePath(): string`| `'/admin/'` -| `getBaseUrl(): string`| `'http://nette.org/admin/'` -| `getRelativePath(): string`| `'script.php'` -| `getRelativeUrl(): string`| `'script.php/pathinfo/?name=param#footer'` -| `getPathInfo(): string`| `'/pathinfo/'` - - -A `UrlScript` objektumokat nem közvetlenül hozzuk létre, hanem a [Nette\Http\Request::getUrl() |request] metódus adja vissza. +| `getScriptPath(): string` | `'/admin/script.php'` +| `getBasePath(): string` | `'/admin/'` +| `getBaseUrl(): string` | `'http://nette.org/admin/'` +| `getRelativePath(): string` | `'script.php'` +| `getRelativeUrl(): string` | `'script.php/pathinfo/?name=param#footer'` +| `getPathInfo(): string` | `'/pathinfo/'` + +Az `UrlScript` objektumokat általában nem közvetlenül hozzuk létre, hanem a [Nette\Http\Request::getUrl()|request] metódus adja vissza őket már helyesen beállított komponensekkel az aktuális HTTP kéréshez. diff --git a/http/it/@home.texy b/http/it/@home.texy index 0ebc1f9eb7..6435ce6d19 100644 --- a/http/it/@home.texy +++ b/http/it/@home.texy @@ -2,13 +2,13 @@ Nette HTTP ********** .[perex] -Il pacchetto `nette/http` incapsula le [richieste |request] e le [risposte |response] [HTTP |request], lavora con le [sessioni |sessions] e [analizza e costruisce gli URL |urls]. +Il pacchetto `nette/http` incapsula la [HTTP request|request] & [response], il lavoro con le [sessions] e il [parsing e la composizione degli URL |urls]. -Installazione .[#toc-installation] ----------------------------------- +Installazione +------------- -Scaricare e installare il pacchetto utilizzando [Composer |best-practices:composer]: +È possibile scaricare e installare la libreria utilizzando lo strumento [Composer|best-practices:composer]: ```shell composer require nette/http diff --git a/http/it/@left-menu.texy b/http/it/@left-menu.texy index 8081046f19..38e13b9ee6 100644 --- a/http/it/@left-menu.texy +++ b/http/it/@left-menu.texy @@ -1,8 +1,8 @@ -Net HTTP -******** -- [Panoramica |@home] -- [Richiesta HTTP |request] -- [Risposta HTTP |response] -- [Sessioni |Sessions] +Nette HTTP +********** +- [Introduzione |@home] +- [HTTP request|request] +- [HTTP response|response] +- [Sessioni |sessions] - [Utilità URL |urls] -- [Configurazione |Configuration] +- [Configurazione |configuration] diff --git a/http/it/@meta.texy b/http/it/@meta.texy new file mode 100644 index 0000000000..4647d0c8a2 --- /dev/null +++ b/http/it/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentazione Nette}} diff --git a/http/it/configuration.texy b/http/it/configuration.texy index 37f99d4019..eba6f97a55 100644 --- a/http/it/configuration.texy +++ b/http/it/configuration.texy @@ -1,40 +1,40 @@ -Configurazione di HTTP -********************** +Configurazione HTTP +******************* .[perex] -Panoramica delle opzioni di configurazione di Nette HTTP. +Panoramica delle opzioni di configurazione per Nette HTTP. -Se non si utilizza l'intero framework, ma solo questa libreria, leggere [come caricare la configurazione |bootstrap:]. +Se non utilizzate l'intero framework, ma solo questa libreria, leggete [come caricare la configurazione|bootstrap:]. -Intestazioni HTTP .[#toc-http-headers] -====================================== +Header HTTP +=========== ```neon http: - # intestazioni che vengono inviate con ogni richiesta + # header che vengono inviati con ogni request headers: X-Powered-By: MyCMS X-Content-Type-Options: nosniff X-XSS-Protection: '1; mode=block' - # influisce sull'intestazione X-Frame-Options - frames: ... # (string|bool) predefinito a "SAMEORIGIN". + # influisce sull'header X-Frame-Options + frames: ... # (string|bool) il default è 'SAMEORIGIN' ``` -Per motivi di sicurezza, il framework invia un'intestazione `X-Frame-Options: SAMEORIGIN`, che dice che una pagina può essere visualizzata all'interno di un'altra pagina (nell'elemento `<iframe>`) solo se si trova sullo stesso dominio. Questo può essere indesiderato in alcune situazioni (per esempio, se si sta sviluppando un'applicazione Facebook), quindi il comportamento può essere modificato impostando i frame `frames: http://allowed-host.com`. +Per motivi di sicurezza, il framework invia l'header `X-Frame-Options: SAMEORIGIN`, che indica che la pagina può essere visualizzata all'interno di un'altra pagina (nell'elemento `<iframe>`) solo se si trova sullo stesso dominio. Questo può essere indesiderato in alcune situazioni (ad esempio, se state sviluppando un'applicazione per Facebook), quindi il comportamento può essere modificato impostando `frames: http://allowed-host.com` o `frames: true`. -Politica di sicurezza dei contenuti .[#toc-content-security-policy] -------------------------------------------------------------------- +Content Security Policy +----------------------- -Le intestazioni `Content-Security-Policy` (d'ora in poi denominate CSP) possono essere facilmente assemblate; la loro descrizione si trova nella [descrizione delle CSP |https://content-security-policy.com]. Le direttive CSP (come `script-src`) possono essere scritte come stringhe secondo le specifiche o come array di valori per una migliore leggibilità. Non è quindi necessario scrivere le virgolette intorno a parole chiave come `'self'`. Nette genererà automaticamente anche un valore di `nonce`, quindi `'nonce-y4PopTLM=='` sarà inviato nell'intestazione. +È facile costruire gli header `Content-Security-Policy` (di seguito CSP), la cui descrizione si trova nella [descrizione CSP |https://content-security-policy.com]. Le direttive CSP (come `script-src`) possono essere scritte sia come stringhe secondo la specifica, sia come array di valori per una migliore leggibilità. In tal caso, non è necessario racchiudere tra virgolette le parole chiave come `'self'`. Nette genera anche automaticamente il valore `nonce`, quindi nell'header ci sarà ad esempio `'nonce-y4PopTLM=='`. ```neon http: - # Politica di sicurezza dei contenuti + # Content Security Policy csp: - # stringa secondo le specifiche CSP + # stringa nel formato secondo la specifica CSP default-src: "'self' https://example.com" # array di valori @@ -49,18 +49,18 @@ http: block-all-mixed-content: false ``` -Utilizzare `<script n:nonce>...</script>` nei template e il valore nonce verrà compilato automaticamente. Creare siti web sicuri in Nette è davvero facile. +Nei template, usate `<script n:nonce>...</script>` e il valore nonce verrà aggiunto automaticamente. Creare siti web sicuri in Nette è davvero facile. -Allo stesso modo, è possibile aggiungere le intestazioni `Content-Security-Policy-Report-Only` (che può essere utilizzata in parallelo con CSP) e [Feature Policy |https://developers.google.com/web/updates/2018/06/feature-policy]: +Allo stesso modo, è possibile costruire gli header `Content-Security-Policy-Report-Only` (che possono essere utilizzati contemporaneamente a CSP) e [Feature Policy|https://developers.google.com/web/updates/2018/06/feature-policy]: ```neon http: - # Rapporto sulla politica di sicurezza dei contenuti + # Content Security Policy Report-Only cspReportOnly: default-src: self report-uri: 'https://my-report-uri-endpoint' - # Politica sulle caratteristiche + # Feature Policy featurePolicy: unsized-media: none geolocation: @@ -69,91 +69,103 @@ http: ``` -Cookie HTTP .[#toc-http-cookie] -------------------------------- +Cookie HTTP +----------- -È possibile modificare i valori predefiniti di alcuni parametri dei metodi [Nette\Http\Response::setCookie() |response#setCookie] e session. +È possibile modificare i valori predefiniti di alcuni parametri del metodo [Nette\Http\Response::setCookie() |response#setCookie] e della sessione. ```neon http: - # cookie scope per percorso - cookiePath: ... # (string) predefinito a "/". + # scope del cookie per percorso + cookiePath: ... # (string) il default è '/' - # quali host sono autorizzati a ricevere il cookie - cookieDomain: 'example.com' # (string|dominio) predefinito a non impostato + # domini che accettano i cookie + cookieDomain: 'example.com' # (string|domain) il default è non impostato # inviare i cookie solo tramite HTTPS? - cookieSecure: ... # (bool|auto) predefinito a auto + cookieSecure: ... # (bool|auto) il default è auto - # disabilita l'invio del cookie che Nette usa come protezione contro il CSRF - disableNetteCookie: ... # (bool) predefinito a false + # disabilita l'invio del cookie usato da Nette per la protezione CSRF + disableNetteCookie: ... # (bool) il default è false ``` -L'opzione `cookieDomain` determina quali domini (origini) possono accettare i cookie. Se non è specificata, il cookie viene accettato dallo stesso (sotto)dominio in cui è impostato, *escludendo* i suoi sottodomini. Se viene specificato `cookieDomain`, sono inclusi anche i sottodomini. Pertanto, specificare `cookieDomain` è meno restrittivo che ometterlo. +L'attributo `cookieDomain` specifica quali domini possono accettare il cookie. Se non specificato, il cookie viene accettato dallo stesso (sotto)dominio che lo ha impostato, *ma non* dai suoi sottodomini. Se `cookieDomain` è specificato, vengono inclusi anche i sottodomini. Pertanto, specificare `cookieDomain` è meno restrittivo che ometterlo. -Ad esempio, se viene impostato `cookieDomain: nette.org`, il cookie è disponibile anche su tutti i sottodomini come `doc.nette.org`. Questo può essere ottenuto anche con il valore speciale `domain`, cioè `cookieDomain: domain`. +Ad esempio, con `cookieDomain: nette.org`, i cookie sono disponibili anche su tutti i sottodomini come `doc.nette.org`. Lo stesso si può ottenere anche con il valore speciale `domain`, cioè `cookieDomain: domain`. -Il valore predefinito di `cookieSecure` è `auto`, il che significa che se il sito web funziona su HTTPS, i cookie saranno inviati con il flag `Secure` e saranno quindi disponibili solo tramite HTTPS. +Il valore predefinito `auto` per l'attributo `cookieSecure` significa che se il sito web viene eseguito su HTTPS, i cookie verranno inviati con il flag `Secure` e quindi saranno disponibili solo tramite HTTPS. -Proxy HTTP .[#toc-http-proxy] ------------------------------ +Proxy HTTP +---------- -Se il sito funziona dietro un proxy HTTP, inserire l'indirizzo IP del proxy in modo che il rilevamento delle connessioni HTTPS funzioni correttamente, oltre all'indirizzo IP del client. In altre parole, affinché [Nette\Http\Request::getRemoteAddress() |request#getRemoteAddress] e [isSecured() |request#isSecured] restituiscano i valori corretti e i link siano generati con il protocollo `https:` nei template. +Se il sito web viene eseguito dietro un proxy HTTP, specificare il suo indirizzo IP affinché il rilevamento della connessione tramite HTTPS e l'indirizzo IP del client funzionino correttamente. Cioè, affinché le funzioni [Nette\Http\Request::getRemoteAddress() |request#getRemoteAddress] e [isSecured() |request#isSecured] restituiscano i valori corretti e nei template vengano generati link con il protocollo `https:`. ```neon http: - # indirizzo IP, intervallo (es. 127.0.0.1/8) o array di questi valori - proxy: 127.0.0.1 # (string|stringa[]) predefinito a nessuno + # Indirizzo IP, intervallo (es. 127.0.0.1/8) o array di questi valori + proxy: 127.0.0.1 # (string|string[]) il default è non impostato ``` -Sessione .[#toc-session] -======================== +Sessione +======== -Impostazioni di base [delle sessioni |sessions]: +Impostazioni di base delle [sessioni |sessions]: ```neon session: - # mostra il pannello della sessione nella barra Tracy? - debugger: ... # (bool) predefinito a false + # visualizzare il pannello della sessione nella Tracy Bar? + debugger: ... # (bool) il default è false - # tempo di inattività dopo il quale la sessione scade - expiration: 14 days # (string) predefinito a "3 ore". + # periodo di inattività dopo il quale la sessione scade + expiration: 14 days # (string) il default è '3 hours' - # quando avviare la sessione? - autoStart: ... # (smart|true|false) predefinito a "smart". + # quando deve iniziare la sessione? + autoStart: ... # (smart|always|never) il default è 'smart' # handler, servizio che implementa l'interfaccia SessionHandlerInterface handler: @handlerService ``` -L'opzione `autoStart` controlla quando avviare la sessione. Il valore `always` significa che la sessione viene sempre avviata all'avvio dell'applicazione. Il valore `smart` significa che la sessione verrà avviata all'avvio dell'applicazione solo se esiste già, oppure nel momento in cui si vuole leggere o scrivere su di essa. Infine, il valore `never` disabilita l'avvio automatico della sessione. +L'opzione `autoStart` controlla quando deve iniziare la sessione. Il valore `always` significa che la sessione si avvierà sempre all'avvio dell'applicazione. Il valore `smart` significa che la sessione si avvierà all'avvio dell'applicazione solo se esiste già, o nel momento in cui vogliamo leggere o scrivere dati in essa. Infine, il valore `never` disabilita l'avvio automatico della sessione. -È inoltre possibile impostare tutte le [direttive di sessione |https://www.php.net/manual/en/session.configuration.php] di PHP (in formato camelCase) e anche [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. Esempio: +Inoltre, è possibile impostare tutte le [direttive di sessione |https://www.php.net/manual/en/session.configuration.php] PHP (in formato camelCase) e anche [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. Esempio: ```neon session: - # 'session.name' scritto come 'name'. + # 'session.name' lo scriviamo come 'name' name: MYID - # 'session.save_path' scritto come 'savePath' - savePath: "%tempDir%/sessioni" + # 'session.save_path' lo scriviamo come 'savePath' + savePath: "%tempDir%/sessions" ``` -Cookie di sessione .[#toc-session-cookie] ------------------------------------------ +Cookie di Sessione +------------------ -Il cookie di sessione viene inviato con gli stessi parametri degli [altri cookie |#HTTP cookie], ma è possibile modificarli: +Il cookie di sessione viene inviato con gli stessi parametri degli [altri cookie |#Cookie HTTP], ma è possibile modificarli per esso: ```neon session: - # quali host sono autorizzati a ricevere il cookie - cookieDomain: 'example.com' # (string|dominio) + # domini che accettano i cookie + cookieDomain: 'example.com' # (string|domain) - # restrizioni per l'accesso a richieste di origine incrociata - cookieSamesite: ... # (Strict|Lax|None) predefinito a Lax + # restrizione sull'accesso da un altro dominio + cookieSamesite: None # (Strict|Lax|None) il default è Lax ``` -L'opzione `cookieSamesite` influisce sull'invio del cookie con le [richieste di origine incrociata |nette:glossary#SameSite cookie], il che fornisce una certa protezione contro gli attacchi di [Cross-Site Request Forgery |nette:glossary#cross-site-request-forgery-csrf]. +L'attributo `cookieSamesite` influisce sul fatto che il cookie venga inviato durante l'[accesso da un altro dominio |nette:glossary#Cookie SameSite], il che fornisce una certa protezione contro gli attacchi [Cross-Site Request Forgery |nette:glossary#Cross-Site Request Forgery CSRF] (CSRF). + + +Servizi DI +========== + +Questi servizi vengono aggiunti al container DI: + +| Nome | Tipo | Descrizione +|----------------------------------------------------- +| `http.request` | [api:Nette\Http\Request] | [HTTP request| request] +| `http.response` | [api:Nette\Http\Response] | [HTTP response| response] +| `session.session`| [api:Nette\Http\Session] | [gestione delle sessioni| sessions] diff --git a/http/it/request.texy b/http/it/request.texy index c76f283b0f..c5f31907f7 100644 --- a/http/it/request.texy +++ b/http/it/request.texy @@ -1,20 +1,20 @@ -Richiesta HTTP -************** +HTTP Request +************ .[perex] -Nette incapsula la richiesta HTTP in oggetti con un'API comprensibile e fornisce un filtro di sanificazione. +Nette incapsula la richiesta HTTP in oggetti con un'API comprensibile e fornisce allo stesso tempo un filtro di sanificazione. -Una richiesta HTTP è un oggetto [api:Nette\Http\Request], che si ottiene passandoglielo usando la [dependency injection |dependency-injection:passing-dependencies]. Nei presentatori è sufficiente chiamare `$httpRequest = $this->getHttpRequest()`. +La richiesta HTTP è rappresentata dall'oggetto [api:Nette\Http\Request]. Se lavorate con Nette, questo oggetto viene creato automaticamente dal framework e potete riceverlo tramite [dependency injection |dependency-injection:passing-dependencies]. Nei presenter, basta chiamare il metodo `$this->getHttpRequest()`. Se lavorate al di fuori del Nette Framework, potete creare l'oggetto usando [#RequestFactory]. -L'importante è che Nette, quando [crea |#RequestFactory] questo oggetto, cancelli tutti i parametri di input GET, POST e COOKIE, nonché gli URL con caratteri di controllo e sequenze UTF-8 non valide. In questo modo si può continuare a lavorare con i dati in tutta sicurezza. I dati puliti vengono poi utilizzati nei presentatori e nei moduli. +Un grande vantaggio di Nette è che durante la creazione dell'oggetto, pulisce automaticamente tutti i parametri di input GET, POST, COOKIE e anche l'URL dai caratteri di controllo e dalle sequenze UTF-8 non valide. Potete quindi lavorare in sicurezza con questi dati. I dati puliti vengono successivamente utilizzati nei presenter e nei form. -→ [Installazione e requisiti |@home#Installation] +→ [Installazione e requisiti |@home#Installazione] -Richiesta Nette Http .[#toc-nette-http-request] -=============================================== +Nette\Http\Request +================== -Questo oggetto è immutabile. Non ha setter, ha solo un cosiddetto wither `withUrl()`, che non modifica l'oggetto, ma restituisce una nuova istanza con un valore modificato. +Questo oggetto è immutabile. Non ha setter, ha solo un cosiddetto wither `withUrl()`, che non modifica l'oggetto, ma restituisce una nuova istanza con il valore modificato. withUrl(Nette\Http\UrlScript $url): Nette\Http\Request .[method] @@ -28,58 +28,58 @@ Restituisce l'URL della richiesta come oggetto [UrlScript |urls#UrlScript]. ```php $url = $httpRequest->getUrl(); -echo $url; // https://nette.org/en/documentation?action=edit +echo $url; // https://doc.nette.org/cs/?action=edit echo $url->getHost(); // nette.org ``` -I browser non inviano un frammento al server, quindi `$url->getFragment()` restituirà una stringa vuota. +Attenzione: i browser non inviano il frammento al server, quindi `$url->getFragment()` restituirà una stringa vuota. -getQuery(string $key=null): string|array|null .[method] -------------------------------------------------------- -Restituisce i parametri della richiesta GET: +getQuery(?string $key=null): string|array|null .[method] +-------------------------------------------------------- +Restituisce i parametri GET della richiesta. ```php -$all = $httpRequest->getQuery(); // array di tutti i parametri URL +$all = $httpRequest->getQuery(); // restituisce un array di tutti i parametri dall'URL $id = $httpRequest->getQuery('id'); // restituisce il parametro GET 'id' (o null) ``` -getPost(string $key=null): string|array|null .[method] ------------------------------------------------------- -Restituisce i parametri della richiesta POST: +getPost(?string $key=null): string|array|null .[method] +------------------------------------------------------- +Restituisce i parametri POST della richiesta. ```php -$all = $httpRequest->getPost(); // array di tutti i parametri POST +$all = $httpRequest->getPost(); // restituisce un array di tutti i parametri da POST $id = $httpRequest->getPost('id'); // restituisce il parametro POST 'id' (o null) ``` getFile(string|string[] $key): Nette\Http\FileUpload|array|null .[method] ------------------------------------------------------------------------- -Restituisce il [caricamento |#Uploaded Files] come oggetto [api:Nette\Http\FileUpload]: +Restituisce l'[upload |#File Caricati] come oggetto [api:Nette\Http\FileUpload]: ```php $file = $httpRequest->getFile('avatar'); -if ($file->hasFile()) { // è stato caricato un file? +if ($file?->hasFile()) { // è stato caricato qualche file? $file->getUntrustedName(); // nome del file inviato dall'utente - $file->getSanitizedName(); // il nome senza caratteri pericolosi + $file->getSanitizedName(); // nome senza caratteri pericolosi } ``` -Specificare un array di chiavi per accedere alla struttura del sottoalbero. +Per accedere alla struttura nidificata, specificare un array di chiavi. ```php //<input type="file" name="my-form[details][avatar]" multiple> $file = $request->getFile(['my-form', 'details', 'avatar']); ``` -Poiché non ci si può fidare dei dati provenienti dall'esterno e quindi non si fa affidamento sulla forma della struttura, questo metodo è più sicuro rispetto a `$request->getFiles()['my-form']['details']['avatar']`che può fallire. +Poiché non ci si può fidare dei dati provenienti dall'esterno e quindi nemmeno fare affidamento sulla forma della struttura dei file, questo metodo è più sicuro rispetto, ad esempio, a `$request->getFiles()['my-form']['details']['avatar']`, che potrebbe fallire. getFiles(): array .[method] --------------------------- -Restituisce un albero di [file di upload |#Uploaded Files] in una struttura normalizzata, con ogni foglia un'istanza di [api:Nette\Http\FileUpload]: +Restituisce l'albero di [tutti gli upload |#File Caricati] in una struttura normalizzata, le cui foglie sono oggetti [api:Nette\Http\FileUpload]: ```php $files = $httpRequest->getFiles(); @@ -97,7 +97,7 @@ $sessId = $httpRequest->getCookie('sess_id'); getCookies(): array .[method] ----------------------------- -Restituisce tutti i cookie: +Restituisce tutti i cookie. ```php $cookies = $httpRequest->getCookies(); @@ -109,13 +109,13 @@ getMethod(): string .[method] Restituisce il metodo HTTP con cui è stata effettuata la richiesta. ```php -echo $httpRequest->getMethod(); // GET, POST, HEAD, PUT +$httpRequest->getMethod(); // GET, POST, HEAD, PUT ``` isMethod(string $method): bool .[method] ---------------------------------------- -Verifica il metodo HTTP con cui è stata effettuata la richiesta. Il parametro non fa distinzione tra maiuscole e minuscole. +Verifica il metodo HTTP con cui è stata effettuata la richiesta. Il parametro è case-insensitive. ```php if ($httpRequest->isMethod('GET')) // ... @@ -124,7 +124,7 @@ if ($httpRequest->isMethod('GET')) // ... getHeader(string $header): ?string .[method] -------------------------------------------- -Restituisce un'intestazione HTTP o `null` se non esiste. Il parametro non fa distinzione tra maiuscole e minuscole: +Restituisce un header HTTP o `null` se non esiste. Il parametro è case-insensitive. ```php $userAgent = $httpRequest->getHeader('User-Agent'); @@ -133,7 +133,7 @@ $userAgent = $httpRequest->getHeader('User-Agent'); getHeaders(): array .[method] ----------------------------- -Restituisce tutte le intestazioni HTTP come array associativo: +Restituisce tutti gli header HTTP come array associativo. ```php $headers = $httpRequest->getHeaders(); @@ -141,19 +141,14 @@ echo $headers['Content-Type']; ``` -getReferer(): ?Nette\Http\UrlImmutable .[method] ------------------------------------------------- -Da quale URL proviene l'utente? Attenzione, non è affatto affidabile. - - isSecured(): bool .[method] --------------------------- -La connessione è criptata (HTTPS)? Potrebbe essere necessario [impostare un proxy |configuration#HTTP proxy] per una corretta funzionalità. +La connessione è crittografata (HTTPS)? Potrebbe essere necessario [impostare il proxy |configuration#Proxy HTTP] per un corretto funzionamento. isSameSite(): bool .[method] ---------------------------- -La richiesta proviene dallo stesso (sotto)dominio ed è iniziata facendo clic su un link? Nette utilizza il cookie `_nss` (ex `nette-samesite`) per rilevarlo. +La richiesta proviene dallo stesso (sotto)dominio ed è stata avviata facendo clic su un link? Nette utilizza il cookie `_nss` (precedentemente `nette-samesite`) per il rilevamento. isAjax(): bool .[method] @@ -163,17 +158,17 @@ isAjax(): bool .[method] getRemoteAddress(): ?string .[method] ------------------------------------- -Restituisce l'indirizzo IP dell'utente. Potrebbe essere necessario [impostare un proxy |configuration#HTTP proxy] per una corretta funzionalità. +Restituisce l'indirizzo IP dell'utente. Potrebbe essere necessario [impostare il proxy |configuration#Proxy HTTP] per un corretto funzionamento. getRemoteHost(): ?string .[method deprecated] --------------------------------------------- -Restituisce la traduzione DNS dell'indirizzo IP dell'utente. Potrebbe essere necessario [impostare un proxy |configuration#HTTP proxy] per una corretta funzionalità. +Restituisce la traduzione DNS dell'indirizzo IP dell'utente. Potrebbe essere necessario [impostare il proxy |configuration#Proxy HTTP] per un corretto funzionamento. -getBasicCredentials(): ?string .[method] ----------------------------------------- -Restituisce le credenziali di [autenticazione HTTP di base |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication]. +getBasicCredentials(): ?array .[method] +--------------------------------------- +Restituisce le credenziali di autenticazione per [Basic HTTP authentication |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication]. ```php [$user, $password] = $httpRequest->getBasicCredentials(); @@ -182,7 +177,7 @@ Restituisce le credenziali di [autenticazione HTTP di base |https://developer.mo getRawBody(): ?string .[method] ------------------------------- -Restituisce il corpo della richiesta HTTP: +Restituisce il corpo della richiesta HTTP. ```php $body = $httpRequest->getRawBody(); @@ -191,48 +186,55 @@ $body = $httpRequest->getRawBody(); detectLanguage(array $langs): ?string .[method] ----------------------------------------------- -Rileva la lingua. Come parametro `$lang`, si passa un array di lingue supportate dall'applicazione e viene restituita quella preferita dal browser. Non è una magia, il metodo utilizza semplicemente l'intestazione `Accept-Language`. Se non c'è corrispondenza, restituisce `null`. +Rileva la lingua. Come parametro `$lang` passiamo un array con le lingue supportate dall'applicazione, e restituirà quella che il browser del visitatore preferirebbe vedere. Non c'è magia, si utilizza semplicemente l'header `Accept-Language`. Se non c'è corrispondenza, restituisce `null`. ```php -// Intestazione inviata dal browser: Accept-Language: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3 +// il browser invia ad es. Accept-Language: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3 -$langs = ['hu', 'pl', 'en']; // lingue supportate nell'applicazione +$langs = ['hu', 'pl', 'en']; // lingue supportate dall'applicazione echo $httpRequest->detectLanguage($langs); // en ``` -Fabbrica di richieste .[#toc-requestfactory] -============================================ +RequestFactory +============== -L'oggetto della richiesta HTTP corrente viene creato da [api:Nette\Http\RequestFactory]. Se si sta scrivendo un'applicazione che non utilizza un contenitore DI, si crea una richiesta come segue: +La classe [api:Nette\Http\RequestFactory] serve per creare un'istanza di `Nette\Http\Request`, che rappresenta la richiesta HTTP corrente. (Se lavorate con Nette, l'oggetto della richiesta HTTP viene creato automaticamente dal framework.) ```php $factory = new Nette\Http\RequestFactory; $httpRequest = $factory->fromGlobals(); ``` -La RequestFactory può essere configurata prima di chiamare `fromGlobals()`. Possiamo disabilitare tutta la sanitizzazione dei parametri di input da sequenze UTF-8 non valide, usando `$factory->setBinary()`. E anche impostare un server proxy, importante per il corretto rilevamento dell'indirizzo IP dell'utente, utilizzando `$factory->setProxy(...)`. +Il metodo `fromGlobals()` crea l'oggetto della richiesta in base alle variabili globali PHP correnti (`$_GET`, `$_POST`, `$_COOKIE`, `$_FILES` e `$_SERVER`). Durante la creazione dell'oggetto, pulisce automaticamente tutti i parametri di input GET, POST, COOKIE e anche l'URL dai caratteri di controllo e dalle sequenze UTF-8 non valide, garantendo la sicurezza nel successivo lavoro con questi dati. + +RequestFactory può essere configurata prima di chiamare `fromGlobals()`: -È possibile ripulire gli URL dai caratteri che possono entrare in essi a causa di sistemi di commento mal implementati su vari altri siti web, utilizzando dei filtri: +- con il metodo `$factory->setBinary()` disabilitate la pulizia automatica dei parametri di input dai caratteri di controllo e dalle sequenze UTF-8 non valide. +- con il metodo `$factory->setProxy(...)` specificate l'indirizzo IP del [server proxy |configuration#Proxy HTTP], necessario per il corretto rilevamento dell'indirizzo IP dell'utente. + +RequestFactory consente di definire filtri che trasformano automaticamente parti dell'URL della richiesta. Questi filtri rimuovono caratteri indesiderati dall'URL, che possono essere inseriti lì, ad esempio, da un'implementazione errata dei sistemi di commento su vari siti web: ```php -// rimuovere gli spazi dal percorso +// rimozione degli spazi dal percorso $requestFactory->urlFilters['path']['%20'] = ''; -// rimuove il punto, la virgola o la parentesi destra alla fine dell'URL +// rimozione di punto, virgola o parentesi destra dalla fine dell'URI $requestFactory->urlFilters['url']['[.,)]$'] = ''; -// pulisce il percorso dagli slash duplicati (filtro predefinito) +// pulizia del percorso dalle doppie barre (filtro predefinito) $requestFactory->urlFilters['path']['/{2,}'] = '/'; ``` +La prima chiave `'path'` o `'url'` specifica a quale parte dell'URL applicare il filtro. La seconda chiave è l'espressione regolare da cercare e il valore è la sostituzione da utilizzare al posto del testo trovato. + -File caricati .[#toc-uploaded-files] -==================================== +File Caricati +============= -Il metodo `Nette\Http\Request::getFiles()` restituisce un albero di file caricati in una struttura normalizzata, con ogni foglia un'istanza di [api:Nette\Http\FileUpload]. Questi oggetti incapsulano i dati inviati dall'elemento del form. `<input type=file>` elemento del modulo. +Il metodo `Nette\Http\Request::getFiles()` restituisce un array di tutti gli upload in una struttura normalizzata, le cui foglie sono oggetti [api:Nette\Http\FileUpload]. Questi incapsulano i dati inviati dall'elemento del form `<input type=file>`. -La struttura riflette la denominazione degli elementi in HTML. Nell'esempio più semplice, potrebbe trattarsi di un singolo elemento del modulo con nome, inviato come: +La struttura riflette la denominazione degli elementi in HTML. Nel caso più semplice, può essere un singolo elemento del form nominato inviato come: ```latte <input type="file" name="avatar"> @@ -246,19 +248,19 @@ In questo caso, `$request->getFiles()` restituisce un array: ] ``` -L'oggetto `FileUpload` viene creato anche se l'utente non ha caricato alcun file o il caricamento è fallito. Il metodo `hasFile()` restituisce true se è stato inviato un file: +L'oggetto `FileUpload` viene creato anche nel caso in cui l'utente non abbia inviato alcun file o l'invio sia fallito. Il metodo `hasFile()` restituisce se il file è stato inviato: ```php -$request->getFile('avatar')->hasFile(); +$request->getFile('avatar')?->hasFile(); ``` -Nel caso di un input che utilizza la notazione array per il nome: +Nel caso di un nome di elemento che utilizza la notazione per array: ```latte <input type="file" name="my-form[details][avatar]"> ``` -l'albero restituito finisce per assomigliare a questo: +l'albero restituito appare così: ```php [ @@ -270,13 +272,13 @@ l'albero restituito finisce per assomigliare a questo: ] ``` -È anche possibile creare array di file: +È possibile creare anche un array di file: ```latte -<input type="file" name="my-form[details][avatars][] multiple"> +<input type="file" name="my-form[details][avatars][]" multiple> ``` -In questo caso la struttura appare come: +In tal caso, la struttura appare così: ```php [ @@ -292,7 +294,7 @@ In questo caso la struttura appare come: ] ``` -Il modo migliore per accedere all'indice 1 di un array annidato è il seguente: +Il modo migliore per accedere all'indice 1 dell'array nidificato è il seguente: ```php $file = $request->getFile(['my-form', 'details', 'avatars', 1]); @@ -301,11 +303,11 @@ if ($file instanceof FileUpload) { } ``` -Poiché non ci si può fidare dei dati provenienti dall'esterno e quindi non si fa affidamento sulla forma della struttura, questo metodo è più sicuro rispetto a `$request->getFiles()['my-form']['details']['avatars'][1]`che può fallire. +Poiché non ci si può fidare dei dati provenienti dall'esterno e quindi nemmeno fare affidamento sulla forma della struttura dei file, questo metodo è più sicuro rispetto, ad esempio, a `$request->getFiles()['my-form']['details']['avatars'][1]`, che potrebbe fallire. -Panoramica dei metodi di `FileUpload` .{toc: FileUpload} --------------------------------------------------------- +Panoramica dei metodi `FileUpload` .{toc: FileUpload} +----------------------------------------------------- hasFile(): bool .[method] @@ -320,12 +322,12 @@ Restituisce `true` se il file è stato caricato con successo. getError(): int .[method] ------------------------- -Restituisce il codice di errore associato al file caricato. È una delle costanti [UPLOAD_ERR_XXX |http://php.net/manual/en/features.file-upload.errors.php]. Se il file è stato caricato con successo, restituisce `UPLOAD_ERR_OK`. +Restituisce il codice di errore durante il caricamento del file. È una delle costanti [UPLOAD_ERR_XXX|http://php.net/manual/en/features.file-upload.errors.php]. Se il caricamento è avvenuto correttamente, restituisce `UPLOAD_ERR_OK`. move(string $dest) .[method] ---------------------------- -Sposta un file caricato in una nuova posizione. Se il file di destinazione esiste già, verrà sovrascritto. +Sposta il file caricato in una nuova posizione. Se il file di destinazione esiste già, verrà sovrascritto. ```php $file->move('/path/to/files/name.ext'); @@ -334,12 +336,12 @@ $file->move('/path/to/files/name.ext'); getContents(): ?string .[method] -------------------------------- -Restituisce il contenuto del file caricato. Se il caricamento non è riuscito, restituisce `null`. +Restituisce il contenuto del file caricato. Se il caricamento non è andato a buon fine, restituisce `null`. getContentType(): ?string .[method] ----------------------------------- -Rileva il tipo di contenuto MIME del file caricato in base alla sua firma. Se il caricamento non è riuscito o il rilevamento non è andato a buon fine, restituisce `null`. +Rileva il tipo di contenuto MIME del file caricato in base alla sua firma. Se il caricamento non è andato a buon fine o il rilevamento fallisce, restituisce `null`. .[caution] Richiede l'estensione PHP `fileinfo`. @@ -347,38 +349,49 @@ Richiede l'estensione PHP `fileinfo`. getUntrustedName(): string .[method] ------------------------------------ -Restituisce il nome originale del file come inviato dal browser. +Restituisce il nome originale del file, come inviato dal browser. .[caution] -Non fidarsi del valore restituito da questo metodo. Un client potrebbe inviare un nome di file dannoso con l'intento di corrompere o hackerare l'applicazione. +Non fidatevi del valore restituito da questo metodo. Il client potrebbe aver inviato un nome di file dannoso con l'intenzione di danneggiare o hackerare la vostra applicazione. getSanitizedName(): string .[method] ------------------------------------ -Restituisce il nome del file sanificato. Contiene solo caratteri ASCII `[a-zA-Z0-9.-]`. Se il nome non contiene tali caratteri, restituisce "unknown". Se il file è un'immagine JPEG, PNG, GIF o WebP, restituisce l'estensione corretta. +Restituisce il nome del file sanificato. Contiene solo caratteri ASCII `[a-zA-Z0-9.-]`. Se il nome non contiene tali caratteri, restituisce `'unknown'`. Se il file è un'immagine in formato JPEG, PNG, GIF, WebP o AVIF, restituisce anche l'estensione corretta. + +.[caution] +Richiede l'estensione PHP `fileinfo`. + + +getSuggestedExtension(): ?string .[method]{data-version:3.2.4} +-------------------------------------------------------------- +Restituisce un'estensione di file appropriata (senza punto) corrispondente al tipo MIME rilevato. + +.[caution] +Richiede l'estensione PHP `fileinfo`. getUntrustedFullPath(): string .[method] ---------------------------------------- -Restituisce il percorso completo originale inviato dal browser durante il caricamento della directory. Il percorso completo è disponibile solo in PHP 8.1 e versioni successive. Nelle versioni precedenti, questo metodo restituisce il nome del file non attendibile. +Restituisce il percorso originale del file, come inviato dal browser durante il caricamento di una cartella. Il percorso completo è disponibile solo in PHP 8.1 e versioni successive. Nelle versioni precedenti, questo metodo restituisce il nome originale del file. .[caution] -Non fidarsi del valore restituito da questo metodo. Un client potrebbe inviare un nome di file dannoso con l'intenzione di corrompere o hackerare l'applicazione. +Non fidatevi del valore restituito da questo metodo. Il client potrebbe aver inviato un nome di file dannoso con l'intenzione di danneggiare o hackerare la vostra applicazione. getSize(): int .[method] ------------------------ -Restituisce la dimensione del file caricato. Se il caricamento non è riuscito, restituisce `0`. +Restituisce la dimensione del file caricato. Se il caricamento non è andato a buon fine, restituisce `0`. getTemporaryFile(): string .[method] ------------------------------------ -Restituisce il percorso della posizione temporanea del file caricato. Se il caricamento non è riuscito, restituisce `''`. +Restituisce il percorso della posizione temporanea del file caricato. Se il caricamento non è andato a buon fine, restituisce `''`. isImage(): bool .[method] ------------------------- -Restituisce `true` se il file caricato è un'immagine JPEG, PNG, GIF o WebP. Il rilevamento si basa sulla sua firma. L'integrità dell'intero file non viene controllata. È possibile scoprire se un'immagine non è danneggiata, ad esempio provando a [caricarla |#toImage]. +Restituisce `true` se il file caricato è un'immagine in formato JPEG, PNG, GIF, WebP o AVIF. Il rilevamento avviene in base alla sua firma e non viene verificata l'integrità dell'intero file. È possibile verificare se un'immagine è danneggiata, ad esempio, provando a [caricarla |#toImage]. .[caution] Richiede l'estensione PHP `fileinfo`. @@ -386,9 +399,9 @@ Richiede l'estensione PHP `fileinfo`. getImageSize(): ?array .[method] -------------------------------- -Restituisce una coppia di elementi `[width, height]` con le dimensioni dell'immagine caricata. Se il caricamento non è riuscito o non è un'immagine valida, restituisce `null`. +Restituisce la coppia `[larghezza, altezza]` con le dimensioni dell'immagine caricata. Se il caricamento non è andato a buon fine o non si tratta di un'immagine valida, restituisce `null`. toImage(): Nette\Utils\Image .[method] -------------------------------------- -Carica un'immagine come oggetto [Image |utils:images]. Se il caricamento non è avvenuto con successo o non è un'immagine valida, viene lanciata un'eccezione `Nette\Utils\ImageException`. +Carica l'immagine come oggetto [Image|utils:images]. Se il caricamento non è andato a buon fine o non si tratta di un'immagine valida, lancia un'eccezione `Nette\Utils\ImageException`. diff --git a/http/it/response.texy b/http/it/response.texy index 8b1f115827..86f7e99187 100644 --- a/http/it/response.texy +++ b/http/it/response.texy @@ -1,23 +1,23 @@ -Risposta HTTP +HTTP Response ************* .[perex] -Nette incapsula la risposta HTTP in oggetti con un'API comprensibile e fornisce un filtro di sanificazione. +Nette incapsula la risposta HTTP in oggetti con un'API comprensibile. -Una risposta HTTP è un oggetto [api:Nette\Http\Response], che si ottiene passandoglielo tramite [dependency injection |dependency-injection:passing-dependencies]. Nei presentatori è sufficiente chiamare `$httpResponse = $this->getHttpResponse()`. +La risposta HTTP è rappresentata dall'oggetto [api:Nette\Http\Response]. Se lavorate con Nette, questo oggetto viene creato automaticamente dal framework e potete riceverlo tramite [dependency injection |dependency-injection:passing-dependencies]. Nei presenter, basta chiamare il metodo `$this->getHttpResponse()`. -→ [Installazione e requisiti |@home#Installation] +→ [Installazione e requisiti |@home#Installazione] -Nettezza Http Risposta .[#toc-nette-http-response] -================================================== +Nette\Http\Response +=================== -A differenza di [Nette\Http\Request |request], questo oggetto è mutabile, quindi si possono usare i setter per cambiare lo stato, cioè per inviare le intestazioni. Ricordare che tutti i setter **devono essere chiamati prima dell'invio di qualsiasi output effettivo.** Il metodo `isSent()` dice se l'output è stato inviato. Se restituisce `true`, ogni tentativo di inviare un'intestazione lancia un'eccezione `Nette\InvalidStateException`. +L'oggetto, a differenza di [Nette\Http\Request|request], è mutabile, quindi potete modificare lo stato usando i setter, ad esempio inviare header. Ricordate che tutti i setter devono essere chiamati **prima di inviare qualsiasi output.** Il metodo `isSent()` indica se l'output è già stato inviato. Se restituisce `true`, ogni tentativo di inviare un header lancerà un'eccezione `Nette\InvalidStateException`. -setCode(int $code, string $reason=null) .[method] -------------------------------------------------- -Cambia un [codice di risposta |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10] di stato. Per una migliore leggibilità del codice sorgente, si consiglia di utilizzare [costanti predefinite |api:Nette\Http\IResponse] anziché numeri reali. +setCode(int $code, ?string $reason=null) .[method] +-------------------------------------------------- +Modifica il [codice di stato della risposta |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10]. Per una migliore comprensibilità del codice sorgente, si consiglia di utilizzare le [costanti predefinite |api:Nette\Http\IResponse] per il codice invece dei numeri. ```php $httpResponse->setCode(Nette\Http\Response::S404_NotFound); @@ -31,12 +31,12 @@ Restituisce il codice di stato della risposta. isSent(): bool .[method] ------------------------ -Restituisce se le intestazioni sono già state inviate dal server al browser, quindi non è più possibile inviare intestazioni o modificare il codice di stato. +Restituisce se gli header sono già stati inviati dal server al browser, e quindi non è più possibile inviare header o modificare il codice di stato. setHeader(string $name, string $value) .[method] ------------------------------------------------ -Invia un'intestazione HTTP e **sovrascrive** l'intestazione dello stesso nome inviata in precedenza. +Invia un header HTTP e **sovrascrive** un header precedentemente inviato con lo stesso nome. ```php $httpResponse->setHeader('Pragma', 'no-cache'); @@ -45,7 +45,7 @@ $httpResponse->setHeader('Pragma', 'no-cache'); addHeader(string $name, string $value) .[method] ------------------------------------------------ -Invia un'intestazione HTTP e **non sovrascrive** l'intestazione dello stesso nome inviata in precedenza. +Invia un header HTTP e **non sovrascrive** un header precedentemente inviato con lo stesso nome. ```php $httpResponse->addHeader('Accept', 'application/json'); @@ -55,12 +55,12 @@ $httpResponse->addHeader('Accept', 'application/xml'); deleteHeader(string $name) .[method] ------------------------------------ -Elimina un'intestazione HTTP precedentemente inviata. +Elimina un header HTTP precedentemente inviato. getHeader(string $header): ?string .[method] -------------------------------------------- -Restituisce l'intestazione HTTP inviata o `null` se non esiste. Il parametro non fa distinzione tra maiuscole e minuscole. +Restituisce l'header HTTP inviato o `null` se non esiste. Il parametro è case-insensitive. ```php $pragma = $httpResponse->getHeader('Pragma'); @@ -69,7 +69,7 @@ $pragma = $httpResponse->getHeader('Pragma'); getHeaders(): array .[method] ----------------------------- -Restituisce tutte le intestazioni HTTP inviate come array associativo. +Restituisce tutti gli header HTTP inviati come array associativo. ```php $headers = $httpResponse->getHeaders(); @@ -77,9 +77,9 @@ echo $headers['Pragma']; ``` -setContentType(string $type, string $charset=null) .[method] ------------------------------------------------------------- -Invia l'intestazione `Content-Type`. +setContentType(string $type, ?string $charset=null) .[method] +------------------------------------------------------------- +Modifica l'header `Content-Type`. ```php $httpResponse->setContentType('text/plain', 'UTF-8'); @@ -88,7 +88,7 @@ $httpResponse->setContentType('text/plain', 'UTF-8'); redirect(string $url, int $code=self::S302_Found): void .[method] ----------------------------------------------------------------- -Reindirizza a un altro URL. Non dimenticare di chiudere lo script. +Reindirizza a un altro URL. Non dimenticate di terminare lo script successivamente. ```php $httpResponse->redirect('http://example.com'); @@ -98,52 +98,52 @@ exit; setExpiration(?string $time) .[method] -------------------------------------- -Imposta la scadenza del documento HTTP utilizzando le intestazioni `Cache-Control` e `Expires`. Il parametro è un intervallo di tempo (come testo) o `null`, che disabilita la cache. +Imposta la scadenza del documento HTTP usando gli header `Cache-Control` e `Expires`. Il parametro è un intervallo di tempo (come testo) o `null`, che disabilita la cache. ```php -// la cache del browser scade tra un'ora +// la cache nel browser scadrà tra un'ora $httpResponse->setExpiration('1 hour'); ``` sendAsFile(string $fileName) .[method] -------------------------------------- -La risposta deve essere scaricata con la finestra di dialogo *Salva con nome* con il nome specificato. Non invia alcun file in uscita. +La risposta verrà scaricata tramite la finestra di dialogo *Salva con nome* con il nome specificato. Il file stesso non viene inviato. ```php -$httpResponse->sendAsFile('invoice.pdf'); +$httpResponse->sendAsFile('faktura.pdf'); ``` -setCookie(string $name, string $value, $time, string $path=null, string $domain=null, bool $secure=null, bool $httpOnly=null, string $sameSite=null) .[method] --------------------------------------------------------------------------------------------------------------------------------------------------------------- -Invia un cookie. Valori predefiniti dei parametri: +setCookie(string $name, string $value, $time, ?string $path=null, ?string $domain=null, ?bool $secure=null, ?bool $httpOnly=null, ?string $sameSite=null) .[method] +------------------------------------------------------------------------------------------------------------------------------------------------------------------- +Invia un cookie. I valori predefiniti dei parametri sono: -| `$path` | `'/'` | con estensione a tutti i percorsi del (sotto)dominio *(configurabile)* -| `$domain` | `null` | con l'ambito del (sotto)dominio corrente, ma non dei suoi sottodomini *(configurabile)* -| `$secure` | `true` | se il sito funziona su HTTPS, altrimenti `false` *(configurabile)* -| `$httpOnly` | `true` | il cookie è inaccessibile a JavaScript -| `$sameSite` | `'Lax'` | il cookie non deve essere inviato quando si [accede da un'altra origine |nette:glossary#SameSite cookie] +| `$path` | `'/'` | il cookie ha scope su tutti i percorsi nel (sotto)dominio *(configurabile)* +| `$domain` | `null` | che significa con scope sul (sotto)dominio corrente, ma non sui suoi sottodomini *(configurabile)* +| `$secure` | `true` | se il sito web viene eseguito su HTTPS, altrimenti `false` *(configurabile)* +| `$httpOnly` | `true` | il cookie non è accessibile da JavaScript +| `$sameSite` | `'Lax'` | il cookie potrebbe non essere inviato durante l'[accesso da un altro dominio |nette:glossary#Cookie SameSite] -È possibile modificare i valori predefiniti dei parametri `$path`, `$domain` e `$secure` in [configuration#HTTP cookie |configuration#HTTP cookie]. +I valori predefiniti dei parametri `$path`, `$domain` e `$secure` possono essere modificati nella [configurazione |configuration#Cookie HTTP]. -Il tempo può essere specificato come numero di secondi o come stringa: +Il tempo può essere specificato come numero di secondi o stringa: ```php -$httpResponse->setCookie('lang', 'en', '100 days'); +$httpResponse->setCookie('lang', 'cs', '100 days'); ``` -L'opzione `$domain` determina quali domini (origini) possono accettare i cookie. Se non viene specificata, il cookie viene accettato dallo stesso (sotto)dominio impostato, esclusi i sottodomini. Se viene specificato `$domain`, sono inclusi anche i sottodomini. Pertanto, specificare `$domain` è meno restrittivo che ometterlo. Ad esempio, se `$domain = 'nette.org'`, il cookie è disponibile anche su tutti i sottodomini come `doc.nette.org`. +Il parametro `$domain` specifica quali domini possono accettare il cookie. Se non specificato, il cookie viene accettato dallo stesso (sotto)dominio che lo ha impostato, ma non dai suoi sottodomini. Se `$domain` è specificato, vengono inclusi anche i sottodomini. Pertanto, specificare `$domain` è meno restrittivo che ometterlo. Ad esempio, con `$domain = 'nette.org'`, i cookie sono disponibili anche su tutti i sottodomini come `doc.nette.org`. -È possibile utilizzare le costanti `Response::SameSiteLax`, `SameSiteStrict` e `SameSiteNone` per il valore `$sameSite`. +Per il valore `$sameSite` potete usare le costanti `Response::SameSiteLax`, `Response::SameSiteStrict` e `Response::SameSiteNone`. -deleteCookie(string $name, string $path=null, string $domain=null, bool $secure=null): void .[method] ------------------------------------------------------------------------------------------------------ +deleteCookie(string $name, ?string $path=null, ?string $domain=null, ?bool $secure=null): void .[method] +-------------------------------------------------------------------------------------------------------- Elimina un cookie. I valori predefiniti dei parametri sono: -- `$path` con estensione a tutte le directory (`'/'`) -- `$domain` con ambito del (sotto)dominio corrente, ma non dei suoi sottodomini -- `$secure` è influenzato dalle impostazioni in [configurazione#HTTP cookie |configuration#HTTP cookie] +- `$path` con scope su tutte le directory (`'/'`) +- `$domain` con scope sul (sotto)dominio corrente, ma non sui suoi sottodomini +- `$secure` è determinato dalle impostazioni nella [configurazione |configuration#Cookie HTTP] ```php $httpResponse->deleteCookie('lang'); diff --git a/http/it/sessions.texy b/http/it/sessions.texy index d3b8637bab..136fd0a282 100644 --- a/http/it/sessions.texy +++ b/http/it/sessions.texy @@ -3,69 +3,69 @@ Sessioni <div class=perex> -HTTP è un protocollo stateless, ma quasi tutte le applicazioni hanno bisogno di mantenere uno stato tra una richiesta e l'altra, ad esempio il contenuto di un carrello della spesa. A questo serve una sessione. Vediamo +HTTP è un protocollo stateless, tuttavia quasi ogni applicazione ha bisogno di mantenere lo stato tra le richieste, ad esempio il contenuto del carrello degli acquisti. A questo servono le sessioni. Vedremo: -- come utilizzare le sessioni -- come evitare conflitti di denominazione +- come usare le sessioni +- come prevenire conflitti di nomi - come impostare la scadenza </div> -Quando si utilizzano le sessioni, ogni utente riceve un identificatore unico, chiamato ID di sessione, che viene passato in un cookie. Questo serve come chiave per i dati di sessione. A differenza dei cookie, che sono memorizzati sul lato browser, i dati di sessione sono memorizzati sul lato server. +Quando si usano le sessioni, ogni utente riceve un identificatore univoco chiamato ID di sessione, che viene passato in un cookie. Questo funge da chiave per i dati della sessione. A differenza dei cookie, che vengono memorizzati lato browser, i dati della sessione vengono memorizzati lato server. -La sessione viene configurata nella [configurazione |configuration#session]; la scelta del tempo di scadenza è importante. +Impostiamo la sessione nella [configurazione |configuration#Sessione], l'opzione del tempo di scadenza è particolarmente importante. -La sessione è gestita dall'oggetto [api:Nette\Http\Session], che si ottiene passandoglielo tramite [dependency injection |dependency-injection:passing-dependencies]. Nei presentatori è sufficiente chiamare `$session = $this->getSession()`. +La gestione della sessione è affidata all'oggetto [api:Nette\Http\Session], a cui potete accedere facendovelo passare tramite [dependency injection |dependency-injection:passing-dependencies]. Nei presenter, basta chiamare `$session = $this->getSession()`. -→ [Installazione e requisiti |@home#Installation] +→ [Installazione e requisiti |@home#Installazione] -Avvio della sessione .[#toc-starting-session] -============================================= +Avvio della Sessione +==================== -Per impostazione predefinita, Nette avvia automaticamente una sessione nel momento in cui si inizia a leggere da essa o a scrivere dati su di essa. Per avviare manualmente una sessione, utilizzare `$session->start()`. +Per default, Nette avvia automaticamente la sessione nel momento in cui iniziamo a leggere o scrivere dati in essa. Manualmente, la sessione si avvia con `$session->start()`. -All'avvio della sessione, PHP invia le intestazioni HTTP che influiscono sulla cache, vedere [php:session_cache_limiter], ed eventualmente un cookie con l'ID della sessione. Pertanto, è sempre necessario avviare la sessione prima di inviare qualsiasi output al browser, altrimenti verrà lanciata un'eccezione. Pertanto, se si sa che una sessione verrà utilizzata durante il rendering della pagina, è bene avviarla manualmente prima, ad esempio nel presentatore. +PHP invia header HTTP che influenzano la cache all'avvio della sessione, vedi [php:session_cache_limiter], e potenzialmente anche un cookie con l'ID di sessione. Pertanto, è necessario avviare sempre la sessione prima di inviare qualsiasi output al browser, altrimenti verrà lanciata un'eccezione. Quindi, se sapete che la sessione verrà utilizzata durante il rendering della pagina, avviatela manualmente prima, ad esempio nel presenter. -In modalità sviluppatore, Tracy avvia la sessione perché la utilizza per visualizzare le barre di reindirizzamento e di richiesta AJAX nella barra Tracy. +In modalità sviluppatore, Tracy avvia la sessione perché la utilizza per visualizzare le barre con i reindirizzamenti e le richieste AJAX nella Tracy Bar. -Sezione .[#toc-section] -======================= +Sezioni +======= -In PHP puro, l'archivio dei dati di sessione è implementato come un array accessibile tramite una variabile globale `$_SESSION`. Il problema è che le applicazioni sono normalmente costituite da un certo numero di parti indipendenti e se tutte hanno a disposizione solo uno stesso array, prima o poi si verificherà una collisione di nomi. +In PHP puro, l'archivio dati della sessione è implementato come un array accessibile tramite la variabile globale `$_SESSION`. Il problema è che le applicazioni sono comunemente composte da molte parti indipendenti e se tutte hanno accesso a un solo array, prima o poi si verificherà una collisione di nomi. -Nette Framework risolve il problema dividendo l'intero spazio in sezioni (oggetti [api:Nette\Http\SessionSection]). Ogni unità utilizza quindi la propria sezione con un nome unico e non si verificano collisioni. +Nette Framework risolve il problema dividendo l'intero spazio in sezioni (oggetti [api:Nette\Http\SessionSection]). Ogni unità utilizza quindi la propria sezione con un nome univoco e non possono più verificarsi collisioni. -La sezione viene ottenuta dal gestore di sessione: +Otteniamo la sezione dalla sessione: ```php -$section = $session->getSection('unique name'); +$section = $session->getSection('nome univoco'); ``` -Nel presentatore è sufficiente chiamare `getSession()` con il parametro: +Nel presenter, basta usare `getSession()` con un parametro: ```php -// $this è il presentatore -$section = $this->getSession('unique name'); +// $this è Presenter +$section = $this->getSession('nome univoco'); ``` -L'esistenza della sezione può essere verificata con il metodo `$session->hasSection('unique name')`. +È possibile verificare l'esistenza di una sezione con il metodo `$session->hasSection('nome univoco')`. -È molto facile lavorare con la sezione stessa usando i metodi `set()`, `get()` e `remove()`: +Lavorare con la sezione stessa è quindi molto facile usando i metodi `set()`, `get()` e `remove()`: ```php -// scrittura di una variabile +// scrittura della variabile $section->set('userName', 'franta'); -// lettura di una variabile, restituisce null se non esiste +// lettura della variabile, restituisce null se non esiste echo $section->get('userName'); -// rimozione di una variabile +// cancellazione della variabile $section->remove('userName'); ``` -È possibile utilizzare il ciclo `foreach` per ottenere tutte le variabili della sezione: +Per ottenere tutte le variabili da una sezione, è possibile utilizzare un ciclo `foreach`: ```php foreach ($section as $key => $val) { @@ -74,137 +74,138 @@ foreach ($section as $key => $val) { ``` -Come impostare la scadenza .[#toc-how-to-set-expiration] --------------------------------------------------------- +Impostazione della Scadenza +--------------------------- -La scadenza può essere impostata per singole sezioni o anche per singole variabili. Possiamo lasciare che il login dell'utente scada tra 20 minuti, ma ricordare il contenuto di un carrello. +È possibile impostare una scadenza per singole sezioni o addirittura per singole variabili. Possiamo quindi far scadere il login dell'utente dopo 20 minuti, ma continuare a ricordare il contenuto del carrello. ```php // la sezione scadrà dopo 20 minuti $section->setExpiration('20 minutes'); ``` -Il terzo parametro del metodo `set()` è usato per impostare la scadenza delle singole variabili: +Per impostare la scadenza delle singole variabili, si usa il terzo parametro del metodo `set()`: ```php -// La variabile 'flash' scade dopo 30 secondi +// la variabile 'flash' scadrà dopo soli 30 secondi $section->set('flash', $message, '30 seconds'); ``` .[note] -Ricordare che il tempo di scadenza dell'intera sessione (vedere la [configurazione della sessione |configuration#session]) deve essere uguale o superiore al tempo impostato per le singole sezioni o variabili. +Non dimenticate che il tempo di scadenza dell'intera sessione (vedi [configurazione della sessione |configuration#Sessione]) deve essere uguale o superiore al tempo impostato per le singole sezioni o variabili. -La cancellazione della scadenza precedentemente impostata può essere ottenuta con il metodo `removeExpiration()`. La cancellazione immediata dell'intera sezione sarà assicurata dal metodo `remove()`. +L'annullamento di una scadenza precedentemente impostata si ottiene con il metodo `removeExpiration()`. La cancellazione immediata dell'intera sezione è garantita dal metodo `remove()`. -Eventi $onStart, $onBeforeWrite .[#toc-events-onstart-onbeforewrite] --------------------------------------------------------------------- +Eventi $onStart, $onBeforeWrite +------------------------------- -L'oggetto `Nette\Http\Session` ha gli [eventi |nette:glossary#Events] `$onStart` e `$onBeforeWrite`, quindi si possono aggiungere callback che vengono richiamati dopo l'avvio della sessione o prima che questa venga scritta su disco e poi terminata. +L'oggetto `Nette\Http\Session` ha [eventi |nette:glossary#Eventi] `$onStart` e `$onBeforeWrite`, quindi potete aggiungere callback che vengono invocati dopo l'avvio della sessione o prima della sua scrittura su disco e successiva chiusura. ```php $session->onBeforeWrite[] = function () { - // scrivere i dati nella sessione + // scriviamo i dati nella sessione $this->section->set('basket', $this->basket); }; ``` -Gestione della sessione .[#toc-session-management] -================================================== +Gestione della Sessione +======================= -Panoramica dei metodi della classe `Nette\Http\Session` per la gestione delle sessioni: +Panoramica dei metodi della classe `Nette\Http\Session` per la gestione della sessione: <div class=wiki-methods-brief> start(): void .[method] ----------------------- -Avvia una sessione. +Avvia la sessione. isStarted(): bool .[method] --------------------------- -La sessione è stata avviata? +La sessione è avviata? close(): void .[method] ----------------------- -Termina la sessione. La sessione termina automaticamente al termine dello script. +Termina la sessione. La sessione termina automaticamente alla fine dell'esecuzione dello script. destroy(): void .[method] ------------------------- -Termina e cancella la sessione. +Termina ed elimina la sessione. exists(): bool .[method] ------------------------ -La richiesta HTTP contiene un cookie con un ID di sessione? +La richiesta HTTP contiene un cookie con l'ID di sessione? regenerateId(): void .[method] ------------------------------ -Genera un nuovo ID di sessione casuale. I dati rimangono invariati. +Genera un nuovo ID di sessione casuale. I dati rimangono conservati. getId(): string .[method] ------------------------- -Restituisce l'ID della sessione. +Restituisce l'ID di sessione. </div> -Configurazione .[#toc-configuration] ------------------------------------- +Configurazione +-------------- -Configuriamo la sessione nella [configurazione |configuration#session]. Se si sta scrivendo un'applicazione che non usa un contenitore DI, usare questi metodi per configurarla. Devono essere chiamati prima di avviare la sessione. +Impostiamo la sessione nella [configurazione |configuration#Sessione]. Se state scrivendo un'applicazione che non utilizza un container DI, questi metodi vengono utilizzati per la configurazione. Devono essere chiamati prima dell'avvio della sessione. <div class=wiki-methods-brief> setName(string $name): static .[method] --------------------------------------- -Imposta il nome del cookie utilizzato per trasmettere l'ID di sessione. Il nome predefinito è `PHPSESSID`. È utile se si eseguono diverse applicazioni sullo stesso sito. +Imposta il nome del cookie in cui viene trasmesso l'ID di sessione. Il nome standard è `PHPSESSID`. È utile nel caso in cui si eseguano diverse applicazioni distinte all'interno dello stesso sito web. getName(): string .[method] --------------------------- -Restituisce il nome del cookie di sessione. +Restituisce il nome del cookie in cui viene trasmesso l'ID di sessione. setOptions(array $options): static .[method] -------------------------------------------- -Configura la sessione. È possibile impostare tutte le [direttive di sessione |https://www.php.net/manual/en/session.configuration.php] PHP (in formato camelCase, ad esempio scrivere `savePath` invece di `session.save_path`) e anche [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. +Configura la sessione. È possibile impostare tutte le [direttive di sessione |https://www.php.net/manual/en/session.configuration.php] PHP (in formato camelCase, ad esempio, invece di `session.save_path` scriviamo `savePath`) e anche [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. setExpiration(?string $time): static .[method] ---------------------------------------------- -Imposta il tempo di inattività dopo il quale la sessione scade. +Imposta il periodo di inattività dopo il quale la sessione scade. -setCookieParameters(string $path, string $domain=null, bool $secure=null, string $samesite=null): static .[method] ------------------------------------------------------------------------------------------------------------------- -Imposta i parametri per i cookie. È possibile modificare i valori dei parametri predefiniti in [configuration#Session cookie |configuration#Session cookie]. +setCookieParameters(string $path, ?string $domain=null, ?bool $secure=null, ?string $samesite=null): static .[method] +--------------------------------------------------------------------------------------------------------------------- +Impostazione dei parametri per i cookie. I valori predefiniti dei parametri possono essere modificati nella [configurazione |configuration#Cookie di Sessione]. setSavePath(string $path): static .[method] ------------------------------------------- -Imposta la directory in cui sono memorizzati i file di sessione. +Imposta la directory in cui vengono salvati i file di sessione. setHandler(\SessionHandlerInterface $handler): static .[method] --------------------------------------------------------------- -Imposta un gestore personalizzato, vedere la [documentazione di PHP |https://www.php.net/manual/en/class.sessionhandlerinterface.php]. +Impostazione di un handler personalizzato, vedi [documentazione PHP|https://www.php.net/manual/en/class.sessionhandlerinterface.php]. </div> -Sicurezza prima di tutto .[#toc-safety-first] -============================================= +Sicurezza Prima di Tutto +======================== -Il server presume di comunicare con lo stesso utente finché le richieste contengono lo stesso ID di sessione. Il compito dei meccanismi di sicurezza è quello di garantire che questo comportamento funzioni davvero e che non ci sia la possibilità di sostituire o rubare un identificatore. +Il server presume di comunicare sempre con lo stesso utente finché le richieste sono accompagnate dallo stesso ID di sessione. Il compito dei meccanismi di sicurezza è garantire che ciò avvenga effettivamente e che non sia possibile rubare o falsificare l'identificatore. -Ecco perché Nette Framework configura correttamente le direttive PHP per trasferire l'ID di sessione solo nei cookie, per evitare l'accesso da JavaScript e per ignorare gli identificatori nell'URL. Inoltre, nei momenti critici, come il login dell'utente, genera un nuovo ID di sessione. +Nette Framework configura quindi correttamente le direttive PHP in modo che l'ID di sessione venga trasmesso solo nei cookie, lo renda inaccessibile a JavaScript e ignori eventuali identificatori nell'URL. Inoltre, nei momenti critici, come il login dell'utente, genera un nuovo ID di sessione. -La funzione ini_set è utilizzata per la configurazione di PHP, ma sfortunatamente il suo uso è proibito da alcuni servizi di web hosting. Se è il vostro caso, provate a chiedere al vostro fornitore di hosting di autorizzare questa funzione o almeno di configurare correttamente il suo server. .[note] +.[note] +Per la configurazione di PHP si utilizza la funzione ini_set, che purtroppo alcuni hosting vietano. Se questo è il caso del vostro hoster, cercate di concordare con lui l'abilitazione della funzione o almeno la configurazione del server. diff --git a/http/it/urls.texy b/http/it/urls.texy index 01319b8c8b..16565823c8 100644 --- a/http/it/urls.texy +++ b/http/it/urls.texy @@ -1,16 +1,16 @@ -Parser e costruttore di URL -*************************** +Lavorare con gli URL +******************** .[perex] -Le classi [Url |#Url], [UrlImmutable |#UrlImmutable] e [UrlScript |#UrlScript] semplificano la gestione, l'analisi e la manipolazione degli URL. +Le classi [#Url], [#UrlImmutable] e [#UrlScript] consentono di generare, analizzare e manipolare facilmente gli URL. -→ [Installazione e requisiti |@home#Installation] +→ [Installazione e requisiti |@home#Installazione] Url === -La classe [api:Nette\Http\Url] semplifica il lavoro con l'URL e i suoi singoli componenti, che sono illustrati in questo diagramma: +La classe [api:Nette\Http\Url] consente di lavorare facilmente con gli URL e i suoi singoli componenti, catturati in questo diagramma: /--pre scheme user password host port path query fragment @@ -36,7 +36,7 @@ $url->setScheme('https') echo $url; // 'https://localhost/edit?foo=bar' ``` -È anche possibile analizzare l'URL e poi manipolarlo: +È anche possibile analizzare un URL e manipolarlo ulteriormente: ```php $url = new Url( @@ -44,62 +44,94 @@ $url = new Url( ); ``` -Per ottenere o modificare singoli componenti dell'URL sono disponibili i seguenti metodi: +La classe `Url` implementa l'interfaccia `JsonSerializable` e ha un metodo `__toString()`, quindi l'oggetto può essere stampato o utilizzato nei dati passati a `json_encode()`. + +```php +echo $url; +echo json_encode([$url]); +``` + + +Componenti URL .[method] +------------------------ + +Per restituire o modificare i singoli componenti dell'URL, sono disponibili i seguenti metodi: .[language-php] -| Setter | Getter | Valore restituito +| Setter | Getter | Valore restituito |-------------------------------------------------------------------------------------------- -| `setScheme(string $scheme)`| `getScheme(): string`| `'http'` -| `setUser(string $user)`| `getUser(): string`| `'john'` -| `setPassword(string $password)`| `getPassword(): string`| `'xyz*12'` -| `setHost(string $host)`| `getHost(): string`| `'nette.org'` -| `setPort(int $port)`| `getPort(): ?int`| `8080` -| | `getDefaultPort(): ?int`| `80` -| `setPath(string $path)`| `getPath(): string`| `'/en/download'` -| `setQuery(string\|array $query)`| `getQuery(): string`| `'name=param'` -| `setFragment(string $fragment)`| `getFragment(): string`| `'footer'` -| | `getAuthority(): string`| `'nette.org:8080'` -| | `getHostUrl(): string`| `'http://nette.org:8080'` -| | `getAbsoluteUrl(): string` | URL completo - -Possiamo anche operare con singoli parametri di query utilizzando: +| `setScheme(string $scheme)` | `getScheme(): string` | `'http'` +| `setUser(string $user)` | `getUser(): string` | `'john'` +| `setPassword(string $password)` | `getPassword(): string` | `'xyz*12'` +| `setHost(string $host)` | `getHost(): string` | `'nette.org'` +| `setPort(int $port)` | `getPort(): ?int` | `8080` +| | `getDefaultPort(): ?int` | `80` +| `setPath(string $path)` | `getPath(): string` | `'/en/download'` +| `setQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` +| `setFragment(string $fragment)` | `getFragment(): string` | `'footer'` +| | `getAuthority(): string` | `'john:xyz%2A12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | intero URL + +Attenzione: Quando si lavora con un URL ottenuto da una [HTTP request|request], tenere presente che non conterrà il frammento, poiché il browser non lo invia al server. + +Possiamo anche lavorare con i singoli parametri query usando: .[language-php] -| Setter | Getter +| Setter | Getter |--------------------------------------------------- -| `setQuery(string\|array $query)` | `getQueryParameters(): array` -| `setQueryParameter(string $name, $val)`| `getQueryParameter(string $name)` +| `setQuery(string\|array $query)` | `getQueryParameters(): array` +| `setQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` -Il metodo `getDomain(int $level = 2)` restituisce la parte destra o sinistra dell'host. Funziona così se l'host è `www.nette.org`: + +getDomain(int $level = 2): string .[method] +------------------------------------------- +Restituisce la parte destra o sinistra dell'host. Funziona così se l'host è `www.nette.org`: .[language-php] -| `getDomain(1)` | `'org'` -| `getDomain(2)` | `'nette.org'` -| `getDomain(3)` | `'www.nette.org'` -| `getDomain(0)` | `'www.nette.org'` -| `getDomain(-1)` | `'www.nette'` -| `getDomain(-2)` | `'www'` -| `getDomain(-3)` | `''` +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `''` -La classe `Url` implementa l'interfaccia `JsonSerializable` e ha un metodo `__toString()` che consente di stampare l'oggetto o di utilizzarlo nei dati passati a `json_encode()`. +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +Verifica se due URL sono identici. ```php -echo $url; -echo json_encode([$url]); +$url->isEqual('https://nette.org'); ``` -Il metodo `isEqual(string|Url $anotherUrl): bool` verifica se i due URL sono identici. + +Url::isAbsolute(string $url): bool .[method]{data-version:3.3.2} +---------------------------------------------------------------- +Verifica se l'URL è assoluto. Un URL è considerato assoluto se inizia con uno schema (es. http, https, ftp) seguito da due punti. ```php -$url->isEqual('https://nette.org'); +Url::isAbsolute('https://nette.org'); // true +Url::isAbsolute('//nette.org'); // false +``` + + +Url::removeDotSegments(string $path): string .[method]{data-version:3.3.2} +-------------------------------------------------------------------------- +Normalizza il percorso nell'URL rimuovendo i segmenti speciali `.` e `..`. Il metodo rimuove gli elementi superflui del percorso nello stesso modo in cui lo fanno i browser web. + +```php +Url::removeDotSegments('/path/../subtree/./file.txt'); // '/subtree/file.txt' +Url::removeDotSegments('/../foo/./bar'); // '/foo/bar' +Url::removeDotSegments('./today/../file.txt'); // 'file.txt' ``` -UrlImmutabile .[#toc-urlimmutable] -================================== +UrlImmutable +============ -La classe [api:Nette\Http\UrlImmutable] è un'alternativa immutabile alla classe `Url` (così come in PHP `DateTimeImmutable` è un'alternativa immutabile a `DateTime`). Al posto dei setter, ha i cosiddetti wither, che non modificano l'oggetto, ma restituiscono nuove istanze con un valore modificato: +La classe [api:Nette\Http\UrlImmutable] è un'alternativa immutabile alla classe [#Url] (simile a come `DateTimeImmutable` è l'alternativa immutabile a `DateTime` in PHP). Invece dei setter, ha i cosiddetti wither, che non modificano l'oggetto, ma restituiscono nuove istanze con il valore modificato: ```php use Nette\Http\UrlImmutable; @@ -111,53 +143,96 @@ $url = new UrlImmutable( $newUrl = $url ->withUser('') ->withPassword('') - ->withPath('/en/'); + ->withPath('/cs/'); + +echo $newUrl; // 'http://john:xyz%2A12@nette.org:8080/cs/?name=param#footer' +``` + +La classe `UrlImmutable` implementa l'interfaccia `JsonSerializable` e ha un metodo `__toString()`, quindi l'oggetto può essere stampato o utilizzato nei dati passati a `json_encode()`. -echo $newUrl; // 'http://nette.org:8080/en/?name=param#footer' +```php +echo $url; +echo json_encode([$url]); ``` -I seguenti metodi sono disponibili per ottenere o modificare singoli componenti dell'URL: + +Componenti URL .[method] +------------------------ + +Per restituire o modificare i singoli componenti dell'URL, servono i seguenti metodi: .[language-php] -| Contiene | Getter | Valore restituito +| Wither | Getter | Valore restituito |-------------------------------------------------------------------------------------------- -| `withScheme(string $scheme)`| `getScheme(): string`| `'http'` -| `withUser(string $user)`| `getUser(): string`| `'john'` -| `withPassword(string $password)`| `getPassword(): string`| `'xyz*12'` -| `withHost(string $host)`| `getHost(): string`| `'nette.org'` -| `withPort(int $port)`| `getPort(): ?int`| `8080` -| | `getDefaultPort(): ?int`| `80` -| `withPath(string $path)`| `getPath(): string`| `'/en/download'` -| `withQuery(string\|array $query)`| `getQuery(): string`| `'name=param'` -| `withFragment(string $fragment)`| `getFragment(): string`| `'footer'` -| | `getAuthority(): string`| `'nette.org:8080'` -| | `getHostUrl(): string`| `'http://nette.org:8080'` -| | `getAbsoluteUrl(): string` | URL completo - -Possiamo anche operare con singoli parametri di query utilizzando: +| `withScheme(string $scheme)` | `getScheme(): string` | `'http'` +| `withUser(string $user)` | `getUser(): string` | `'john'` +| `withPassword(string $password)` | `getPassword(): string` | `'xyz*12'` +| `withHost(string $host)` | `getHost(): string` | `'nette.org'` +| `withPort(int $port)` | `getPort(): ?int` | `8080` +| | `getDefaultPort(): ?int` | `80` +| `withPath(string $path)` | `getPath(): string` | `'/en/download'` +| `withQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` +| `withFragment(string $fragment)` | `getFragment(): string` | `'footer'` +| | `getAuthority(): string` | `'john:xyz%2A12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | intero URL + +Il metodo `withoutUserInfo()` rimuove `user` e `password`. + +Possiamo anche lavorare con i singoli parametri query usando: .[language-php] -| Wither | Getter +| Wither | Getter |----------------------------------------------- -| `withQuery(string\|array $query)` | `getQueryParameters(): array` -| `withQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` +| `withQuery(string\|array $query)` | `getQueryParameters(): array` +| `withQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` -Il metodo `getDomain(int $level = 2)` funziona come il metodo in `Url`. Il metodo `withoutUserInfo()` rimuove `user` e `password`. -La classe `UrlImmutable` implementa l'interfaccia `JsonSerializable` e ha un metodo `__toString()` che consente di stampare l'oggetto o di utilizzarlo nei dati passati a `json_encode()`. +getDomain(int $level = 2): string .[method] +------------------------------------------- +Restituisce la parte destra o sinistra dell'host. Funziona così se l'host è `www.nette.org`: + +.[language-php] +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `''` + + +resolve(string $reference): UrlImmutable .[method]{data-version:3.3.2} +---------------------------------------------------------------------- +Deriva un URL assoluto nello stesso modo in cui il browser elabora i link su una pagina HTML: +- se il link è un URL assoluto (contiene uno schema), viene utilizzato senza modifiche +- se il link inizia con `//`, viene preso solo lo schema dall'URL corrente +- se il link inizia con `/`, viene creato un percorso assoluto dalla radice del dominio +- negli altri casi, l'URL viene costruito relativamente al percorso corrente ```php -echo $url; -echo json_encode([$url]); +$url = new UrlImmutable('https://example.com/path/page'); +echo $url->resolve('../foo'); // 'https://example.com/foo' +echo $url->resolve('/bar'); // 'https://example.com/bar' +echo $url->resolve('sub/page.html'); // 'https://example.com/path/sub/page.html' +``` + + +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +Verifica se due URL sono identici. + +```php +$url->isEqual('https://nette.org'); ``` -Il metodo `isEqual(string|Url $anotherUrl): bool` verifica se i due URL sono identici. +UrlScript +========= -UrlScript .[#toc-urlscript] -=========================== +La classe [api:Nette\Http\UrlScript] è un discendente di [#UrlImmutable] e lo estende con ulteriori componenti URL virtuali, come la directory radice del progetto, ecc. Come la classe genitore, è un oggetto immutabile. -La classe [api:Nette\Http\UrlScript] è un discendente di `UrlImmutable` e distingue inoltre queste parti logiche dell'URL: +Il seguente diagramma mostra i componenti che UrlScript riconosce: /--pre baseUrl basePath relativePath relativeUrl @@ -169,17 +244,23 @@ La classe [api:Nette\Http\UrlScript] è un discendente di `UrlImmutable` e disti scriptPath pathInfo \-- -Per ottenere queste parti sono disponibili i seguenti metodi: +- `baseUrl` è l'URL di base dell'applicazione, inclusi il dominio e la parte del percorso alla directory radice dell'applicazione +- `basePath` è la parte del percorso alla directory radice dell'applicazione +- `scriptPath` è il percorso allo script corrente +- `relativePath` è il nome dello script (eventualmente altri segmenti del percorso) relativo a basePath +- `relativeUrl` è l'intera parte dell'URL dopo baseUrl, inclusi query string e frammento. +- `pathInfo` è una parte dell'URL, oggi poco utilizzata, dopo il nome dello script + +Per restituire parti dell'URL, sono disponibili i seguenti metodi: .[language-php] -| Getter | Valore restituito +| Getter | Valore restituito |------------------------------------------------ -| `getScriptPath(): string`| `'/admin/script.php'` -| `getBasePath(): string`| `'/admin/'` -| `getBaseUrl(): string`| `'http://nette.org/admin/'` -| `getRelativePath(): string`| `'script.php'` -| `getRelativeUrl(): string`| `'script.php/pathinfo/?name=param#footer'` -| `getPathInfo(): string`| `'/pathinfo/'` - - -Non creiamo direttamente l'oggetto `UrlScript`, ma il metodo [Nette\Http\Request::getUrl() |request] lo restituisce. +| `getScriptPath(): string` | `'/admin/script.php'` +| `getBasePath(): string` | `'/admin/'` +| `getBaseUrl(): string` | `'http://nette.org/admin/'` +| `getRelativePath(): string` | `'script.php'` +| `getRelativeUrl(): string` | `'script.php/pathinfo/?name=param#footer'` +| `getPathInfo(): string` | `'/pathinfo/'` + +Gli oggetti `UrlScript` di solito non li creiamo direttamente, ma vengono restituiti dal metodo [Nette\Http\Request::getUrl()|request] con i componenti già correttamente impostati per la richiesta HTTP corrente. diff --git a/http/ja/@home.texy b/http/ja/@home.texy new file mode 100644 index 0000000000..289124a99d --- /dev/null +++ b/http/ja/@home.texy @@ -0,0 +1,15 @@ +Nette HTTP +********** + +.[perex] +`nette/http` パッケージは、[HTTP リクエスト|request]と[レスポンス |response]、[セッション |sessions]の操作、および[URL の解析と構築 |urls]をカプセル化します。 + + +インストール +------ + +[Composer|best-practices:composer]を使用してライブラリをダウンロードし、インストールします: + +```shell +composer require nette/http +``` diff --git a/http/ja/@left-menu.texy b/http/ja/@left-menu.texy new file mode 100644 index 0000000000..f05c3fa47c --- /dev/null +++ b/http/ja/@left-menu.texy @@ -0,0 +1,8 @@ +Nette HTTP +********** +- [はじめに |@home] +- [HTTP リクエスト|request] +- [HTTP レスポンス|response] +- [セッション |sessions] +- [URL ユーティリティ |urls] +- [設定 |configuration] diff --git a/http/ja/@meta.texy b/http/ja/@meta.texy new file mode 100644 index 0000000000..d3c41dc3d7 --- /dev/null +++ b/http/ja/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette ドキュメンテーション}} diff --git a/http/ja/configuration.texy b/http/ja/configuration.texy new file mode 100644 index 0000000000..aee3772453 --- /dev/null +++ b/http/ja/configuration.texy @@ -0,0 +1,171 @@ +HTTP 設定 +******* + +.[perex] +Nette HTTP の設定オプションの概要です。 + +フレームワーク全体を使用せず、このライブラリのみを使用する場合は、[設定の読み込み方法|bootstrap:]をお読みください。 + + +HTTP ヘッダー +========= + +```neon +http: + # 各リクエストで送信されるヘッダー + headers: + X-Powered-By: MyCMS + X-Content-Type-Options: nosniff + X-XSS-Protection: '1; mode=block' + + # X-Frame-Options ヘッダーに影響します + frames: ... # (string|bool) デフォルトは 'SAMEORIGIN' +``` + +フレームワークはセキュリティ上の理由から、ヘッダー `X-Frame-Options: SAMEORIGIN` を送信します。これは、ページが同じドメインにある場合にのみ、別のページ内(`<iframe>` 要素内)に表示できることを示します。これは特定の状況(例えば、Facebook アプリケーションを開発している場合)では望ましくない場合があるため、`frames: http://allowed-host.com` または `frames: true` を設定することで動作を変更できます。 + + +Content Security Policy +----------------------- + +`Content-Security-Policy`(以下 CSP)ヘッダーを簡単に作成できます。その説明は[CSP の説明|https://content-security-policy.com]にあります。CSP ディレクティブ(例:`script-src`)は、仕様に従って文字列として記述するか、読みやすさのために値の配列として記述できます。その場合、`'self'` のようなキーワードの周りに引用符を書く必要はありません。Nette は `nonce` 値も自動的に生成するため、ヘッダーには例えば `'nonce-y4PopTLM=='` が含まれます。 + +```neon +http: + # Content Security Policy + csp: + # CSP 仕様に従った形式の文字列 + default-src: "'self' https://example.com" + + # 値の配列 + script-src: + - nonce + - strict-dynamic + - self + - https://example.com + + # スイッチの場合は bool + upgrade-insecure-requests: true + block-all-mixed-content: false +``` + +テンプレートでは `<script n:nonce>...</script>` を使用し、nonce 値は自動的に補完されます。Nette で安全なウェブサイトを作成するのは本当に簡単です。 + +同様に、`Content-Security-Policy-Report-Only` ヘッダー(CSP と並行して使用可能)と [Feature Policy|https://developers.google.com/web/updates/2018/06/feature-policy] を作成できます: + +```neon +http: + # Content Security Policy Report-Only + cspReportOnly: + default-src: self + report-uri: 'https://my-report-uri-endpoint' + + # Feature Policy + featurePolicy: + unsized-media: none + geolocation: + - self + - https://example.com +``` + + +HTTP クッキー +--------- + +[Nette\Http\Response::setCookie() |response#setCookie] メソッドとセッションの一部のパラメータのデフォルト値を変更できます。 + +```neon +http: + # パスによるクッキーの到達範囲 + cookiePath: ... # (string) デフォルトは '/' + + # クッキーを受け入れるドメイン + cookieDomain: 'example.com' # (string|domain) デフォルトは未設定 + + # HTTPS 経由でのみクッキーを送信しますか? + cookieSecure: ... # (bool|auto) デフォルトは auto + + # Nette が CSRF 保護として使用するクッキーの送信を無効にします + disableNetteCookie: ... # (bool) デフォルトは false +``` + +`cookieDomain` 属性は、どのドメインがクッキーを受け入れることができるかを指定します。指定されていない場合、クッキーは設定したのと同じ(サブ)ドメインを受け入れますが、そのサブドメインは*受け入れません*。`cookieDomain` が指定されている場合、サブドメインも含まれます。したがって、`cookieDomain` を指定する方が、省略するよりも制限が緩くなります。 + +例えば、`cookieDomain: nette.org` の場合、クッキーは `doc.nette.org` のようなすべてのサブドメインでも利用可能です。これは特別な値 `domain`、つまり `cookieDomain: domain` を使用しても達成できます。 + +`cookieSecure` 属性のデフォルト値 `auto` は、ウェブサイトが HTTPS で実行されている場合、クッキーは `Secure` フラグ付きで送信され、したがって HTTPS 経由でのみ利用可能になることを意味します。 + + +HTTP プロキシ +--------- + +ウェブサイトが HTTP プロキシの背後で実行されている場合は、HTTPS 経由の接続検出とクライアントの IP アドレスが正しく機能するように、その IP アドレスを指定します。つまり、[Nette\Http\Request::getRemoteAddress() |request#getRemoteAddress] 関数と [isSecured() |request#isSecured] 関数が正しい値を返し、テンプレートで `https:` プロトコルを持つリンクが生成されるようにします。 + +```neon +http: + # IP アドレス、範囲(例:127.0.0.1/8)、またはこれらの値の配列 + proxy: 127.0.0.1 # (string|string[]) デフォルトは未設定 +``` + + +セッション +===== + +[セッション |sessions]の基本設定: + +```neon +session: + # Tracy Bar にセッションパネルを表示しますか? + debugger: ... # (bool) デフォルトは false + + # セッションが期限切れになるまでの非アクティブ期間 + expiration: 14 days # (string) デフォルトは '3 hours' + + # セッションはいつ開始されるべきですか? + autoStart: ... # (smart|always|never) デフォルトは 'smart' + + # ハンドラ、SessionHandlerInterface インターフェースを実装するサービス + handler: @handlerService +``` + +`autoStart` オプションは、セッションをいつ開始するかを制御します。値 `always` は、アプリケーションの起動時に常にセッションが開始されることを意味します。値 `smart` は、セッションが既に存在する場合、または読み取りまたは書き込みを行いたい場合にのみ、アプリケーションの起動時にセッションが開始されることを意味します。そして最後に、値 `never` はセッションの自動開始を禁止します。 + +さらに、すべての PHP [セッションディレクティブ|https://www.php.net/manual/en/session.configuration.php](camelCase 形式)と [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters] を設定できます。例: + +```neon +session: + # 'session.name' は 'name' として記述します + name: MYID + + # 'session.save_path' は 'savePath' として記述します + savePath: "%tempDir%/sessions" +``` + + +セッションクッキー +--------- + +セッションクッキーは[他のクッキー |#HTTP クッキー]と同じパラメータで送信されますが、これらを変更できます: + +```neon +session: + # クッキーを受け入れるドメイン + cookieDomain: 'example.com' # (string|domain) + + # 他のドメインからのアクセス時の制限 + cookieSamesite: None # (Strict|Lax|None) デフォルトは Lax +``` + +`cookieSamesite` 属性は、[他のドメインからのアクセス |nette:glossary#SameSite cookie]時にクッキーが送信されるかどうかに影響します。これは、[クロスサイトリクエストフォージェリ |nette:glossary#Cross-Site Request Forgery CSRF](CSRF)攻撃に対するある程度の保護を提供します。 + + +DI サービス +======= + +これらのサービスは DI コンテナに追加されます: + +| 名前 | 型 | 説明 +|----------------------------------------------------- +| `http.request` | [api:Nette\Http\Request] | [HTTP リクエスト| request] +| `http.response` | [api:Nette\Http\Response] | [HTTP レスポンス| response] +| `session.session` | [api:Nette\Http\Session] | [セッション管理| sessions] diff --git a/http/ja/request.texy b/http/ja/request.texy new file mode 100644 index 0000000000..e72003b226 --- /dev/null +++ b/http/ja/request.texy @@ -0,0 +1,407 @@ +HTTP リクエスト +********** + +.[perex] +Nette は HTTP リクエストを分かりやすい API を持つオブジェクトにカプセル化し、同時にサニタイズフィルタを提供します。 + +HTTP リクエストは [api:Nette\Http\Request] オブジェクトによって表されます。Nette を使用している場合、このオブジェクトはフレームワークによって自動的に作成され、[依存性注入|dependency-injection:passing-dependencies] を使用して渡すことができます。Presenter では、単に `$this->getHttpRequest()` メソッドを呼び出すだけです。Nette Framework の外部で作業している場合は、[#RequestFactory] を使用してオブジェクトを作成できます。 + +Nette の大きな利点は、オブジェクトを作成する際に、すべての入力パラメータ GET、POST、COOKIE、および URL から制御文字と無効な UTF-8 シーケンスを自動的にクリーンアップすることです。その後、これらのデータを安全にさらに処理できます。クリーンアップされたデータは、Presenter とフォームで使用されます。 + +→ [インストールと要件 |@home#インストール] + + +Nette\Http\Request +================== + +このオブジェクトはイミュータブル(不変)です。セッターはなく、`withUrl()` というウィザーが 1 つだけあり、これはオブジェクトを変更せず、変更された値を持つ新しいインスタンスを返します。 + + +withUrl(Nette\Http\UrlScript $url): Nette\Http\Request .[method] +---------------------------------------------------------------- +異なる URL を持つクローンを返します。 + + +getUrl(): Nette\Http\UrlScript .[method] +---------------------------------------- +リクエストの URL を [UrlScript |urls#UrlScript] オブジェクトとして返します。 + +```php +$url = $httpRequest->getUrl(); +echo $url; // https://doc.nette.org/cs/?action=edit +echo $url->getHost(); // nette.org +``` + +注意:ブラウザはサーバーにフラグメントを送信しないため、`$url->getFragment()` は空の文字列を返します。 + + +getQuery(?string $key=null): string|array|null .[method] +-------------------------------------------------------- +GET リクエストのパラメータを返します。 + +```php +$all = $httpRequest->getQuery(); // URL からのすべてのパラメータの配列を返します +$id = $httpRequest->getQuery('id'); // GET パラメータ 'id' を返します(または null) +``` + + +getPost(?string $key=null): string|array|null .[method] +------------------------------------------------------- +POST リクエストのパラメータを返します。 + +```php +$all = $httpRequest->getPost(); // POST からのすべてのパラメータの配列を返します +$id = $httpRequest->getPost('id'); // POST パラメータ 'id' を返します(または null) +``` + + +getFile(string|string[] $key): Nette\Http\FileUpload|array|null .[method] +------------------------------------------------------------------------- +[アップロード |#アップロードされたファイル]を [api:Nette\Http\FileUpload] オブジェクトとして返します: + +```php +$file = $httpRequest->getFile('avatar'); +if ($file?->hasFile()) { // 何かファイルがアップロードされましたか? + $file->getUntrustedName(); // ユーザーによって送信されたファイル名 + $file->getSanitizedName(); // 危険な文字を含まない名前 +} +``` + +ネストされた構造にアクセスするには、キーの配列を指定します。 + +```php +//<input type="file" name="my-form[details][avatar]" multiple> +$file = $request->getFile(['my-form', 'details', 'avatar']); +``` + +外部からのデータを信頼できず、したがってファイル構造の形式に依存できないため、例えば `$request->getFiles()['my-form']['details']['avatar']` のように失敗する可能性がある方法よりも、この方法の方が安全です。 + + +getFiles(): array .[method] +--------------------------- +正規化された構造で[すべてのアップロード |#アップロードされたファイル]のツリーを返します。そのリーフは [api:Nette\Http\FileUpload] オブジェクトです: + +```php +$files = $httpRequest->getFiles(); +``` + + +getCookie(string $key): string|array|null .[method] +--------------------------------------------------- +クッキーを返すか、存在しない場合は `null` を返します。 + +```php +$sessId = $httpRequest->getCookie('sess_id'); +``` + + +getCookies(): array .[method] +----------------------------- +すべてのクッキーを返します。 + +```php +$cookies = $httpRequest->getCookies(); +``` + + +getMethod(): string .[method] +----------------------------- +リクエストが行われた HTTP メソッドを返します。 + +```php +$httpRequest->getMethod(); // GET, POST, HEAD, PUT +``` + + +isMethod(string $method): bool .[method] +---------------------------------------- +リクエストが行われた HTTP メソッドをテストします。パラメータは大文字と小文字を区別しません。 + +```php +if ($httpRequest->isMethod('GET')) // ... +``` + + +getHeader(string $header): ?string .[method] +-------------------------------------------- +HTTP ヘッダーを返すか、存在しない場合は `null` を返します。パラメータは大文字と小文字を区別しません。 + +```php +$userAgent = $httpRequest->getHeader('User-Agent'); +``` + + +getHeaders(): array .[method] +----------------------------- +すべての HTTP ヘッダーを連想配列として返します。 + +```php +$headers = $httpRequest->getHeaders(); +echo $headers['Content-Type']; +``` + + +isSecured(): bool .[method] +--------------------------- +接続は暗号化されていますか(HTTPS)?正しく機能させるためには、[プロキシの設定 |configuration#HTTP プロキシ]が必要になる場合があります。 + + +isSameSite(): bool .[method] +---------------------------- +リクエストは同じ(サブ)ドメインから来ており、リンクのクリックによって開始されましたか?Nette は検出にクッキー `_nss`(以前は `nette-samesite`)を使用します。 + + +isAjax(): bool .[method] +------------------------ +AJAX リクエストですか? + + +getRemoteAddress(): ?string .[method] +------------------------------------- +ユーザーの IP アドレスを返します。正しく機能させるためには、[プロキシの設定 |configuration#HTTP プロキシ]が必要になる場合があります。 + + +getRemoteHost(): ?string .[method deprecated] +--------------------------------------------- +ユーザーの IP アドレスの DNS 解決を返します。正しく機能させるためには、[プロキシの設定 |configuration#HTTP プロキシ]が必要になる場合があります。 + + +getBasicCredentials(): ?array .[method] +--------------------------------------- +[Basic HTTP authentication |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication] の認証情報を返します。 + +```php +[$user, $password] = $httpRequest->getBasicCredentials(); +``` + + +getRawBody(): ?string .[method] +------------------------------- +HTTP リクエストの本文を返します。 + +```php +$body = $httpRequest->getRawBody(); +``` + + +detectLanguage(array $langs): ?string .[method] +----------------------------------------------- +言語を検出します。パラメータ `$lang` として、アプリケーションがサポートする言語の配列を渡し、訪問者のブラウザが最も好む言語を返します。これは魔法ではなく、単に `Accept-Language` ヘッダーを使用します。一致するものがない場合は `null` を返します。 + +```php +// ブラウザは例えば Accept-Language: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3 を送信します + +$langs = ['hu', 'pl', 'en']; // アプリケーションがサポートする言語 +echo $httpRequest->detectLanguage($langs); // en +``` + + +RequestFactory +============== + +[api:Nette\Http\RequestFactory] クラスは、現在の HTTP リクエストを表す `Nette\Http\Request` インスタンスを作成するために使用されます。(Nette を使用している場合、HTTP リクエストオブジェクトはフレームワークによって自動的に作成されます。) + +```php +$factory = new Nette\Http\RequestFactory; +$httpRequest = $factory->fromGlobals(); +``` + +`fromGlobals()` メソッドは、現在の PHP グローバル変数(`$_GET`、`$_POST`、`$_COOKIE`、`$_FILES`、`$_SERVER`)に基づいてリクエストオブジェクトを作成します。オブジェクトを作成する際に、すべての入力パラメータ GET、POST、COOKIE、および URL から制御文字と無効な UTF-8 シーケンスを自動的にクリーンアップし、これらのデータをさらに処理する際の安全性を確保します。 + +RequestFactory は `fromGlobals()` を呼び出す前に設定できます: + +- `$factory->setBinary()` メソッドで、入力パラメータから制御文字と無効な UTF-8 シーケンスの自動クリーニングを無効にします。 +- `$factory->setProxy(...)` メソッドで、[プロキシサーバー |configuration#HTTP プロキシ]の IP アドレスを指定します。これは、ユーザーの IP アドレスを正しく検出するために必要です。 + +RequestFactory を使用すると、リクエスト URL の一部を自動的に変換するフィルタを定義できます。これらのフィルタは、例えば、さまざまなウェブサイト上のコメントシステムの不適切な実装によって挿入される可能性のある、URL からの不要な文字を削除します: + +```php +// パスからスペースを削除 +$requestFactory->urlFilters['path']['%20'] = ''; + +// URI の末尾からドット、カンマ、または右括弧を削除 +$requestFactory->urlFilters['url']['[.,)]$'] = ''; + +// パスから重複したスラッシュをクリーンアップ(デフォルトフィルタ) +$requestFactory->urlFilters['path']['/{2,}'] = '/'; +``` + +最初のキー `'path'` または `'url'` は、フィルタが適用される URL の部分を指定します。2番目のキーは検索する正規表現であり、値は見つかったテキストの代わりに使用される置換です。 + + +アップロードされたファイル +============= + +`Nette\Http\Request::getFiles()` メソッドは、正規化された構造ですべてのアップロードの配列を返します。そのリーフは [api:Nette\Http\FileUpload] オブジェクトです。これらは、フォームコントロール `<input type=file>` によって送信されたデータをカプセル化します。 + +構造は HTML のコントロールの命名を反映しています。最も単純なケースでは、次のように送信された単一の名前付きフォームコントロールである可能性があります: + +```latte +<input type="file" name="avatar"> +``` + +この場合、`$request->getFiles()` は配列を返します: + +```php +[ + 'avatar' => /* FileUpload インスタンス */ +] +``` + +`FileUpload` オブジェクトは、ユーザーがファイルを送信しなかった場合や送信が失敗した場合でも作成されます。ファイルが送信されたかどうかは `hasFile()` メソッドが返します: + +```php +$request->getFile('avatar')?->hasFile(); +``` + +配列表記を使用するコントロール名の場合: + +```latte +<input type="file" name="my-form[details][avatar]"> +``` + +返されるツリーは次のようになります: + +```php +[ + 'my-form' => [ + 'details' => [ + 'avatar' => /* FileUpload インスタンス */ + ], + ], +] +``` + +ファイルの配列を作成することもできます: + +```latte +<input type="file" name="my-form[details][avatars][]" multiple> +``` + +この場合、構造は次のようになります: + +```php +[ + 'my-form' => [ + 'details' => [ + 'avatars' => [ + 0 => /* FileUpload インスタンス */, + 1 => /* FileUpload インスタンス */, + 2 => /* FileUpload インスタンス */, + ], + ], + ], +] +``` + +ネストされた配列のインデックス 1 にアクセスする最良の方法は次のとおりです: + +```php +$file = $request->getFile(['my-form', 'details', 'avatars', 1]); +if ($file instanceof FileUpload) { + // ... +} +``` + +外部からのデータを信頼できず、したがってファイル構造の形式に依存できないため、例えば `$request->getFiles()['my-form']['details']['avatars'][1]` のように失敗する可能性がある方法よりも、この方法の方が安全です。 + + +`FileUpload` メソッドの概要 .{toc: FileUpload} +--------------------------------------- + + +hasFile(): bool .[method] +------------------------- +ユーザーがファイルをアップロードした場合に `true` を返します。 + + +isOk(): bool .[method] +---------------------- +ファイルが正常にアップロードされた場合に `true` を返します。 + + +getError(): int .[method] +------------------------- +ファイルアップロード時のエラーコードを返します。これは [UPLOAD_ERR_XXX|http://php.net/manual/en/features.file-upload.errors.php] 定数の 1 つです。アップロードが正常に行われた場合、`UPLOAD_ERR_OK` を返します。 + + +move(string $dest) .[method] +---------------------------- +アップロードされたファイルを新しい場所に移動します。宛先ファイルが既に存在する場合、上書きされます。 + +```php +$file->move('/path/to/files/name.ext'); +``` + + +getContents(): ?string .[method] +-------------------------------- +アップロードされたファイルの内容を返します。アップロードが成功しなかった場合、`null` を返します。 + + +getContentType(): ?string .[method] +----------------------------------- +アップロードされたファイルの MIME コンテンツタイプを、その署名に基づいて検出します。アップロードが成功しなかった場合、または検出が失敗した場合、`null` を返します。 + +.[caution] +PHP 拡張機能 `fileinfo` が必要です。 + + +getUntrustedName(): string .[method] +------------------------------------ +ブラウザが送信した元のファイル名を返します。 + +.[caution] +このメソッドによって返される値を信頼しないでください。クライアントは、アプリケーションを破損またはハッキングする意図で悪意のあるファイル名を送信した可能性があります。 + + +getSanitizedName(): string .[method] +------------------------------------ +サニタイズされたファイル名を返します。ASCII 文字 `[a-zA-Z0-9.-]` のみを含みます。名前にそのような文字が含まれていない場合、`'unknown'` を返します。ファイルが JPEG、PNG、GIF、WebP、または AVIF 形式の画像である場合、正しい拡張子も返します。 + +.[caution] +PHP 拡張機能 `fileinfo` が必要です。 + + +getSuggestedExtension(): ?string .[method]{data-version:3.2.4} +-------------------------------------------------------------- +検出された MIME タイプに対応する適切なファイル拡張子(ドットなし)を返します。 + +.[caution] +PHP 拡張機能 `fileinfo` が必要です。 + + +getUntrustedFullPath(): string .[method] +---------------------------------------- +フォルダのアップロード時にブラウザが送信した元のファイルパスを返します。完全なパスは PHP 8.1 以降でのみ利用可能です。以前のバージョンでは、このメソッドは元のファイル名を返します。 + +.[caution] +このメソッドによって返される値を信頼しないでください。クライアントは、アプリケーションを破損またはハッキングする意図で悪意のあるファイル名を送信した可能性があります。 + + +getSize(): int .[method] +------------------------ +アップロードされたファイルのサイズを返します。アップロードが成功しなかった場合、`0` を返します。 + + +getTemporaryFile(): string .[method] +------------------------------------ +アップロードされたファイルの一時的な場所へのパスを返します。アップロードが成功しなかった場合、`''` を返します。 + + +isImage(): bool .[method] +------------------------- +アップロードされたファイルが JPEG、PNG、GIF、WebP、または AVIF 形式の画像である場合に `true` を返します。検出はその署名に基づいて行われ、ファイル全体の整合性は検証されません。画像が破損していないかどうかは、例えば[読み込み |#toImage]を試みることで確認できます。 + +.[caution] +PHP 拡張機能 `fileinfo` が必要です。 + + +getImageSize(): ?array .[method] +-------------------------------- +アップロードされた画像の寸法を含むペア `[幅, 高さ]` を返します。アップロードが成功しなかった場合、または有効な画像でない場合、`null` を返します。 + + +toImage(): Nette\Utils\Image .[method] +-------------------------------------- +画像を [Image|utils:images] オブジェクトとして読み込みます。アップロードが成功しなかった場合、または有効な画像でない場合、`Nette\Utils\ImageException` 例外をスローします。 diff --git a/http/ja/response.texy b/http/ja/response.texy new file mode 100644 index 0000000000..1bc9d82c00 --- /dev/null +++ b/http/ja/response.texy @@ -0,0 +1,150 @@ +HTTP レスポンス +********** + +.[perex] +Nette は HTTP レスポンスを分かりやすい API を持つオブジェクトにカプセル化します。 + +HTTP レスポンスは [api:Nette\Http\Response] オブジェクトによって表されます。Nette を使用している場合、このオブジェクトはフレームワークによって自動的に作成され、[依存性注入|dependency-injection:passing-dependencies] を使用して渡すことができます。Presenter では、単に `$this->getHttpResponse()` メソッドを呼び出すだけです。 + +→ [インストールと要件 |@home#インストール] + + +Nette\Http\Response +=================== + +このオブジェクトは [Nette\Http\Request|request] とは異なり、ミュータブル(可変)です。つまり、セッターを使用して状態を変更できます。例えば、ヘッダーを送信するなどです。すべてのセッターは、**任意の出力が送信される前に**呼び出す必要があることを忘れないでください。出力が既に送信されたかどうかは `isSent()` メソッドが示します。`true` を返す場合、ヘッダーを送信しようとするたびに `Nette\InvalidStateException` 例外がスローされます。 + + +setCode(int $code, ?string $reason=null) .[method] +-------------------------------------------------- +[レスポンスステータスコード|https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10]を変更します。ソースコードの可読性を高めるために、コードには数値の代わりに[事前定義された定数|api:Nette\Http\IResponse]を使用することをお勧めします。 + +```php +$httpResponse->setCode(Nette\Http\Response::S404_NotFound); +``` + + +getCode(): int .[method] +------------------------ +レスポンスのステータスコードを返します。 + + +isSent(): bool .[method] +------------------------ +ヘッダーがサーバーからブラウザに既に送信されたかどうかを返します。したがって、ヘッダーを送信したり、ステータスコードを変更したりすることはできなくなります。 + + +setHeader(string $name, string $value) .[method] +------------------------------------------------ +HTTP ヘッダーを送信し、以前に送信された同じ名前のヘッダーを**上書き**します。 + +```php +$httpResponse->setHeader('Pragma', 'no-cache'); +``` + + +addHeader(string $name, string $value) .[method] +------------------------------------------------ +HTTP ヘッダーを送信し、以前に送信された同じ名前のヘッダーを**上書きしません**。 + +```php +$httpResponse->addHeader('Accept', 'application/json'); +$httpResponse->addHeader('Accept', 'application/xml'); +``` + + +deleteHeader(string $name) .[method] +------------------------------------ +以前に送信された HTTP ヘッダーを削除します。 + + +getHeader(string $header): ?string .[method] +-------------------------------------------- +送信された HTTP ヘッダーを返すか、存在しない場合は `null` を返します。パラメータは大文字と小文字を区別しません。 + +```php +$pragma = $httpResponse->getHeader('Pragma'); +``` + + +getHeaders(): array .[method] +----------------------------- +送信されたすべての HTTP ヘッダーを連想配列として返します。 + +```php +$headers = $httpResponse->getHeaders(); +echo $headers['Pragma']; +``` + + +setContentType(string $type, ?string $charset=null) .[method] +------------------------------------------------------------- +`Content-Type` ヘッダーを変更します。 + +```php +$httpResponse->setContentType('text/plain', 'UTF-8'); +``` + + +redirect(string $url, int $code=self::S302_Found): void .[method] +----------------------------------------------------------------- +別の URL にリダイレクトします。その後、スクリプトを終了することを忘れないでください。 + +```php +$httpResponse->redirect('http://example.com'); +exit; +``` + + +setExpiration(?string $time) .[method] +-------------------------------------- +`Cache-Control` および `Expires` ヘッダーを使用して HTTP ドキュメントの有効期限を設定します。パラメータは時間間隔(テキストとして)または `null` で、キャッシュを無効にします。 + +```php +// ブラウザのキャッシュは1時間後に期限切れになります +$httpResponse->setExpiration('1 hour'); +``` + + +sendAsFile(string $fileName) .[method] +-------------------------------------- +レスポンスは、指定された名前で *名前を付けて保存* ダイアログボックスを使用してダウンロードされます。ファイル自体は送信しません。 + +```php +$httpResponse->sendAsFile('invoice.pdf'); +``` + + +setCookie(string $name, string $value, $time, ?string $path=null, ?string $domain=null, ?bool $secure=null, ?bool $httpOnly=null, ?string $sameSite=null) .[method] +------------------------------------------------------------------------------------------------------------------------------------------------------------------- +クッキーを送信します。パラメータのデフォルト値: + +| `$path` | `'/'` | クッキーは(サブ)ドメイン内のすべてのパスに適用されます *(設定可能)* +| `$domain` | `null` | これは現在の(サブ)ドメインに適用されますが、そのサブドメインには適用されません *(設定可能)* +| `$secure` | `true` | ウェブサイトが HTTPS で実行されている場合、それ以外は `false` *(設定可能)* +| `$httpOnly` | `true` | クッキーは JavaScript からアクセスできません +| `$sameSite` | `'Lax'` | クッキーは[他のドメインからのアクセス |nette:glossary#SameSite cookie]時に送信されない場合があります + +パラメータ `$path`、`$domain`、`$secure` のデフォルト値は[設定 |configuration#HTTP クッキー]で変更できます。 + +時間は秒数または文字列として指定できます: + +```php +$httpResponse->setCookie('lang', 'ja', '100 days'); +``` + +`$domain` パラメータは、どのドメインがクッキーを受け入れることができるかを指定します。指定されていない場合、クッキーは設定したのと同じ(サブ)ドメインを受け入れますが、そのサブドメインは受け入れません。`$domain` が指定されている場合、サブドメインも含まれます。したがって、`$domain` を指定する方が、省略するよりも制限が緩くなります。例えば、`$domain = 'nette.org'` の場合、クッキーは `doc.nette.org` のようなすべてのサブドメインでも利用可能です。 + +`$sameSite` の値には、定数 `Response::SameSiteLax`、`SameSiteStrict`、`SameSiteNone` を使用できます。 + + +deleteCookie(string $name, ?string $path=null, ?string $domain=null, ?bool $secure=null): void .[method] +-------------------------------------------------------------------------------------------------------- +クッキーを削除します。パラメータのデフォルト値は次のとおりです: +- `$path` すべてのディレクトリに適用されます(`'/'`) +- `$domain` 現在の(サブ)ドメインに適用されますが、そのサブドメインには適用されません +- `$secure` は[設定 |configuration#HTTP クッキー]の設定に従います + +```php +$httpResponse->deleteCookie('lang'); +``` diff --git a/http/ja/sessions.texy b/http/ja/sessions.texy new file mode 100644 index 0000000000..c1c8169880 --- /dev/null +++ b/http/ja/sessions.texy @@ -0,0 +1,211 @@ +セッション +***** + +<div class=perex> + +HTTP はステートレスプロトコルですが、ほぼすべてのアプリケーションはリクエスト間で状態を維持する必要があります。例えば、ショッピングカートの内容などです。セッションはまさにこの目的のために使用されます。ここでは、 + +- セッションの使用方法 +- 名前の衝突を回避する方法 +- 有効期限の設定方法 + +</div> + +セッションを使用する場合、各ユーザーはセッション ID と呼ばれる一意の識別子を受け取り、これはクッキーで渡されます。これはセッションデータのキーとして機能します。ブラウザ側に保存されるクッキーとは異なり、セッションデータはサーバー側に保存されます。 + +セッションは[設定 |configuration#セッション]で設定します。特に有効期限の選択が重要です。 + +セッション管理は [api:Nette\Http\Session] オブジェクトが担当します。これには、[依存性注入|dependency-injection:passing-dependencies] を使用して渡すことでアクセスできます。Presenter では、単に `$session = $this->getSession()` を呼び出すだけです。 + +→ [インストールと要件 |@home#インストール] + + +セッションの開始 +======== + +Nette はデフォルト設定では、データの読み取りまたは書き込みを開始したときに自動的にセッションを開始します。手動でセッションを開始するには `$session->start()` を使用します。 + +PHP はセッションの開始時にキャッシュに影響を与える HTTP ヘッダー([php:session_cache_limiter]参照)と、場合によってはセッション ID を含むクッキーを送信します。そのため、ブラウザに何らかの出力が送信される前に常にセッションを開始する必要があります。そうしないと例外がスローされます。したがって、ページのレンダリング中にセッションが使用されることがわかっている場合は、事前に手動で開始してください。例えば Presenter で。 + +開発モードでは、Tracy がセッションを開始します。これは、Tracy Bar でのリダイレクトや AJAX リクエストのバーを表示するために使用するためです。 + + +セクション +===== + +純粋な PHP では、セッションデータストレージはグローバル変数 `$_SESSION` を介してアクセス可能な配列として実装されます。問題は、アプリケーションが通常、相互に独立した多数の部分で構成されており、すべてが 1 つの配列しか利用できない場合、遅かれ早かれ名前の衝突が発生することです。 + +Nette Framework は、スペース全体をセクション([api:Nette\Http\SessionSection] オブジェクト)に分割することでこの問題を解決します。各ユニットは一意の名前を持つ独自のセクションを使用するため、衝突は発生しません。 + +セッションからセクションを取得します: + +```php +$section = $session->getSection('unique_name'); +``` + +Presenter では、パラメータ付きで `getSession()` を使用するだけです: + +```php +// $this は Presenter です +$section = $this->getSession('unique_name'); +``` + +セクションの存在は `$session->hasSection('unique_name')` メソッドで確認できます。 + +セクション自体は、`set()`、`get()`、`remove()` メソッドを使用して非常に簡単に操作できます: + +```php +// 変数の書き込み +$section->set('userName', 'john'); + +// 変数の読み取り、存在しない場合は null を返します +echo $section->get('userName'); + +// 変数の削除 +$section->remove('userName'); +``` + +セクションからすべての変数を取得するには、`foreach` ループを使用できます: + +```php +foreach ($section as $key => $val) { + echo "$key = $val"; +} +``` + + +有効期限の設定 +------- + +個々のセクション、または個々の変数に対して有効期限を設定できます。これにより、ユーザーのログインを 20 分後に期限切れにすることができますが、カートの内容は引き続き記憶されます。 + +```php +// セクションは 20 分後に期限切れになります +$section->setExpiration('20 minutes'); +``` + +個々の変数の有効期限を設定するには、`set()` メソッドの 3 番目のパラメータを使用します: + +```php +// 変数 'flash' は 30 秒後に期限切れになります +$section->set('flash', $message, '30 seconds'); +``` + +.[note] +セッション全体の有効期限([セッション設定 |configuration#セッション]参照)は、個々のセクションまたは変数に設定された時間と同じかそれ以上でなければならないことを忘れないでください。 + +以前に設定された有効期限をキャンセルするには `removeExpiration()` メソッドを使用します。セクション全体を即座にキャンセルするには `remove()` メソッドを使用します。 + + +イベント $onStart, $onBeforeWrite +----------------------------- + +`Nette\Http\Session` オブジェクトには[イベント |nette:glossary#イベント] `$onStart` と `$onBeforeWrite` があります。したがって、セッションの開始後またはディスクへの書き込みとそれに続く終了前に呼び出されるコールバックを追加できます。 + +```php +$session->onBeforeWrite[] = function () { + // セッションにデータを書き込みます + $this->section->set('basket', $this->basket); +}; +``` + + +セッション管理 +======= + +セッション管理のための `Nette\Http\Session` クラスのメソッドの概要: + +<div class=wiki-methods-brief> + + +start(): void .[method] +----------------------- +セッションを開始します。 + + +isStarted(): bool .[method] +--------------------------- +セッションは開始されていますか? + + +close(): void .[method] +----------------------- +セッションを終了します。セッションはスクリプトの実行終了時に自動的に終了します。 + + +destroy(): void .[method] +------------------------- +セッションを終了して削除します。 + + +exists(): bool .[method] +------------------------ +HTTP リクエストにセッション ID を含むクッキーが含まれていますか? + + +regenerateId(): void .[method] +------------------------------ +新しいランダムなセッション ID を生成します。データは保持されます。 + + +getId(): string .[method] +------------------------- +セッション ID を返します。 + +</div> + + +設定 +----------- + +セッションは[設定 |configuration#セッション]で設定します。DI コンテナを使用しないアプリケーションを作成している場合は、これらのメソッドを使用して設定します。これらはセッションを開始する前に呼び出す必要があります。 + +<div class=wiki-methods-brief> + + +setName(string $name): static .[method] +--------------------------------------- +セッション ID が転送されるクッキーの名前を設定します。標準の名前は `PHPSESSID` です。これは、1 つのウェブサイト内で複数の異なるアプリケーションを運用する場合に便利です。 + + +getName(): string .[method] +--------------------------- +セッション ID が転送されるクッキーの名前を返します。 + + +setOptions(array $options): static .[method] +-------------------------------------------- +セッションを設定します。すべての PHP [セッションディレクティブ|https://www.php.net/manual/en/session.configuration.php](camelCase 形式、例:`session.save_path` の代わりに `savePath` と記述)と [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters] を設定できます。 + + +setExpiration(?string $time): static .[method] +---------------------------------------------- +セッションが期限切れになるまでの非アクティブ期間を設定します。 + + +setCookieParameters(string $path, ?string $domain=null, ?bool $secure=null, ?string $samesite=null): static .[method] +--------------------------------------------------------------------------------------------------------------------- +クッキーのパラメータを設定します。パラメータのデフォルト値は[設定 |configuration#セッションクッキー]で変更できます。 + + +setSavePath(string $path): static .[method] +------------------------------------------- +セッションファイルが保存されるディレクトリを設定します。 + + +setHandler(\SessionHandlerInterface $handler): static .[method] +--------------------------------------------------------------- +カスタムハンドラを設定します。[PHP ドキュメント|https://www.php.net/manual/en/class.sessionhandlerinterface.php]を参照してください。 + +</div> + + +セキュリティ第一 +======== + +サーバーは、リクエストが同じセッション ID を伴う限り、常に同じユーザーと通信していると想定します。セキュリティメカニズムのタスクは、これが実際にそうであり、識別子を盗んだり偽装したりできないことを保証することです。 + +したがって、Nette Framework は PHP ディレクティブを正しく設定し、セッション ID をクッキーでのみ転送し、JavaScript からアクセスできないようにし、URL 内の識別子を無視します。さらに、ユーザーのログインなどの重要な瞬間には、新しいセッション ID を生成します。 + +.[note] +PHP の設定には ini_set 関数が使用されますが、残念ながら一部のホスティングプロバイダーはこの関数を禁止しています。これがあなたのホスティングプロバイダーの場合、関数を有効にするか、少なくともサーバーを設定するように交渉してみてください。 diff --git a/http/ja/urls.texy b/http/ja/urls.texy new file mode 100644 index 0000000000..0aa3600224 --- /dev/null +++ b/http/ja/urls.texy @@ -0,0 +1,266 @@ +URL の操作 +******* + +.[perex] +クラス [#Url]、[#UrlImmutable]、[#UrlScript] を使用すると、URL の生成、解析、操作が簡単になります。 + +→ [インストールと要件 |@home#インストール] + + +Url +=== + +[api:Nette\Http\Url] クラスを使用すると、URL とその個々のコンポーネントを簡単に操作できます。これらは次の図で示されています: + +/--pre + scheme user password host port path query fragment + | | | | | | | | + /--\ /--\ /------\ /-------\ /--\/----------\ /--------\ /----\ + <b>http://john:xyz%2A12@nette.org:8080/en/download?name=param#footer</b> + \______\__________________________/ + | | + hostUrl authority +\-- + +URL の生成は直感的です: + +```php +use Nette\Http\Url; + +$url = new Url; +$url->setScheme('https') + ->setHost('localhost') + ->setPath('/edit') + ->setQueryParameter('foo', 'bar'); + +echo $url; // 'https://localhost/edit?foo=bar' +``` + +URL を解析してさらに操作することもできます: + +```php +$url = new Url( + 'http://john:xyz%2A12@nette.org:8080/en/download?name=param#footer', +); +``` + +`Url` クラスは `JsonSerializable` インターフェースを実装し、`__toString()` メソッドを持っているため、オブジェクトを出力したり、`json_encode()` に渡されるデータで使用したりできます。 + +```php +echo $url; +echo json_encode([$url]); +``` + + +URL コンポーネント .[method] +--------------------- + +URL の個々のコンポーネントを返すか変更するには、次のメソッドを使用できます: + +.[language-php] +| セッター | ゲッター | 返される値 +|-------------------------------------------------------------------------------------------- +| `setScheme(string $scheme)` | `getScheme(): string` | `'http'` +| `setUser(string $user)` | `getUser(): string` | `'john'` +| `setPassword(string $password)` | `getPassword(): string` | `'xyz*12'` +| `setHost(string $host)` | `getHost(): string` | `'nette.org'` +| `setPort(int $port)` | `getPort(): ?int` | `8080` +| | `getDefaultPort(): ?int` | `80` +| `setPath(string $path)` | `getPath(): string` | `'/en/download'` +| `setQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` +| `setFragment(string $fragment)` | `getFragment(): string` | `'footer'` +| | `getAuthority(): string` | `'john:xyz%2A12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | 完全な URL + +注意:[HTTP リクエスト|request]から取得した URL を操作する場合、ブラウザはフラグメントをサーバーに送信しないため、フラグメントは含まれないことに注意してください。 + +個々のクエリパラメータも次のように操作できます: + +.[language-php] +| セッター | ゲッター +|--------------------------------------------------- +| `setQuery(string\|array $query)` | `getQueryParameters(): array` +| `setQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` + + +getDomain(int $level = 2): string .[method] +------------------------------------------- +ホストの右側または左側の部分を返します。ホストが `www.nette.org` の場合、次のように機能します: + +.[language-php] +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `''` + + +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +2 つの URL が同じかどうかを確認します。 + +```php +$url->isEqual('https://nette.org'); +``` + + +Url::isAbsolute(string $url): bool .[method]{data-version:3.3.2} +---------------------------------------------------------------- +URL が絶対 URL かどうかを確認します。URL は、スキーム(例:http、https、ftp)で始まり、その後にコロンが続く場合に絶対 URL と見なされます。 + +```php +Url::isAbsolute('https://nette.org'); // true +Url::isAbsolute('//nette.org'); // false +``` + + +Url::removeDotSegments(string $path): string .[method]{data-version:3.3.2} +-------------------------------------------------------------------------- +特別なセグメント `.` と `..` を削除して URL のパスを正規化します。このメソッドは、Web ブラウザと同じ方法で余分なパス要素を削除します。 + +```php +Url::removeDotSegments('/path/../subtree/./file.txt'); // '/subtree/file.txt' +Url::removeDotSegments('/../foo/./bar'); // '/foo/bar' +Url::removeDotSegments('./today/../file.txt'); // 'file.txt' +``` + + +UrlImmutable +============ + +[api:Nette\Http\UrlImmutable] クラスは、[#Url] クラスのイミュータブル(不変)な代替です(PHP の `DateTimeImmutable` が `DateTime` の不変な代替であるのと同様です)。セッターの代わりに、オブジェクトを変更せず、変更された値を持つ新しいインスタンスを返すウィザーがあります: + +```php +use Nette\Http\UrlImmutable; + +$url = new UrlImmutable( + 'http://john:xyz%2A12@nette.org:8080/en/download?name=param#footer', +); + +$newUrl = $url + ->withUser('') + ->withPassword('') + ->withPath('/cs/'); + +echo $newUrl; // 'http://john:xyz%2A12@nette.org:8080/cs/?name=param#footer' +``` + +`UrlImmutable` クラスは `JsonSerializable` インターフェースを実装し、`__toString()` メソッドを持っているため、オブジェクトを出力したり、`json_encode()` に渡されるデータで使用したりできます。 + +```php +echo $url; +echo json_encode([$url]); +``` + + +URL コンポーネント .[method] +--------------------- + +URL の個々のコンポーネントを返すか変更するには、次のメソッドを使用します: + +.[language-php] +| ウィザー | ゲッター | 返される値 +|-------------------------------------------------------------------------------------------- +| `withScheme(string $scheme)` | `getScheme(): string` | `'http'` +| `withUser(string $user)` | `getUser(): string` | `'john'` +| `withPassword(string $password)` | `getPassword(): string` | `'xyz*12'` +| `withHost(string $host)` | `getHost(): string` | `'nette.org'` +| `withPort(int $port)` | `getPort(): ?int` | `8080` +| | `getDefaultPort(): ?int` | `80` +| `withPath(string $path)` | `getPath(): string` | `'/en/download'` +| `withQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` +| `withFragment(string $fragment)` | `getFragment(): string` | `'footer'` +| | `getAuthority(): string` | `'john:xyz%2A12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | 完全な URL + +`withoutUserInfo()` メソッドは `user` と `password` を削除します。 + +個々のクエリパラメータも次のように操作できます: + +.[language-php] +| ウィザー | ゲッター +|----------------------------------------------- +| `withQuery(string\|array $query)` | `getQueryParameters(): array` +| `withQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` + + +getDomain(int $level = 2): string .[method] +------------------------------------------- +ホストの右側または左側の部分を返します。ホストが `www.nette.org` の場合、次のように機能します: + +.[language-php] +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `''` + + +resolve(string $reference): UrlImmutable .[method]{data-version:3.3.2} +---------------------------------------------------------------------- +ブラウザが HTML ページ上のリンクを処理するのと同じ方法で絶対 URL を導出します: +- リンクが絶対 URL(スキームを含む)の場合、変更せずに使用されます +- リンクが `//` で始まる場合、現在の URL からスキームのみが引き継がれます +- リンクが `/` で始まる場合、ドメインのルートからの絶対パスが作成されます +- その他の場合、URL は現在のパスに対して相対的に構築されます + +```php +$url = new UrlImmutable('https://example.com/path/page'); +echo $url->resolve('../foo'); // 'https://example.com/foo' +echo $url->resolve('/bar'); // 'https://example.com/bar' +echo $url->resolve('sub/page.html'); // 'https://example.com/path/sub/page.html' +``` + + +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +2 つの URL が同じかどうかを確認します。 + +```php +$url->isEqual('https://nette.org'); +``` + + +UrlScript +========= + +[api:Nette\Http\UrlScript] クラスは [#UrlImmutable] の子孫であり、プロジェクトのルートディレクトリなどの追加の仮想 URL コンポーネントで拡張します。親クラスと同様に、イミュータブル(不変)なオブジェクトです。 + +次の図は、UrlScript が認識するコンポーネントを示しています: + +/--pre + baseUrl basePath relativePath relativeUrl + | | | | + /---------------/-----\/--------\---------------------------\ + <b>http://nette.org/admin/script.php/pathinfo/?name=param#footer</b> + \_______________/\________/ + | | + scriptPath pathInfo +\-- + +- `baseUrl` は、ドメインとアプリケーションのルートディレクトリへのパスの一部を含む、アプリケーションのベース URL アドレスです +- `basePath` は、アプリケーションのルートディレクトリへのパスの一部です +- `scriptPath` は、現在のスクリプトへのパスです +- `relativePath` は、basePath に対して相対的なスクリプトの名前(および場合によっては追加のパスセグメント)です +- `relativeUrl` は、クエリ文字列とフラグメントを含む、baseUrl の後の URL の全体の部分です。 +- `pathInfo` は、今日ではあまり使用されない、スクリプト名の後の URL の部分です + +URL の部分を返すには、次のメソッドを使用できます: + +.[language-php] +| ゲッター | 返される値 +|------------------------------------------------ +| `getScriptPath(): string` | `'/admin/script.php'` +| `getBasePath(): string` | `'/admin/'` +| `getBaseUrl(): string` | `'http://nette.org/admin/'` +| `getRelativePath(): string` | `'script.php'` +| `getRelativeUrl(): string` | `'script.php/pathinfo/?name=param#footer'` +| `getPathInfo(): string` | `'/pathinfo/'` + +`UrlScript` オブジェクトは通常、直接作成しませんが、現在の HTTP リクエストに対して正しく設定されたコンポーネントを持つ [Nette\Http\Request::getUrl()|request] メソッドによって返されます。 diff --git a/http/pl/@home.texy b/http/pl/@home.texy index a8e6174f55..bb86d6830c 100644 --- a/http/pl/@home.texy +++ b/http/pl/@home.texy @@ -2,13 +2,13 @@ Nette HTTP ********** .[perex] -Pakiet `nette/http` enkapsuluje [żądania |request] i [odpowiedzi |response] [HTTP |request], obsługę [sesji |sessions] oraz [parsowanie i składanie adresów URL |urls]. +Pakiet `nette/http` enkapsuluje [Żądanie HTTP|request] & [Odpowiedź HTTP|response], pracę z [sesjami |sessions] oraz [parsowanie i składanie URL |urls]. -Instalacja .[#toc-installation] -------------------------------- +Instalacja +---------- -Pobierz i zainstaluj bibliotekę za pomocą [Composera |best-practices:composer]: +Bibliotekę pobierzesz i zainstalujesz za pomocą narzędzia [Composer|best-practices:composer]: ```shell composer require nette/http diff --git a/http/pl/@left-menu.texy b/http/pl/@left-menu.texy index c460935c97..6022278eab 100644 --- a/http/pl/@left-menu.texy +++ b/http/pl/@left-menu.texy @@ -1,7 +1,7 @@ -Net Http -******** -- [Úvod |@home] -- [Żądanie HTTP |request] +Nette HTTP +********** +- [Wprowadzenie |@home] +- [Żądanie HTTP|request] - [Odpowiedź HTTP|response] - [Sesje |Sessions] - [Narzędzia URL |urls] diff --git a/http/pl/@meta.texy b/http/pl/@meta.texy new file mode 100644 index 0000000000..61ac92d1af --- /dev/null +++ b/http/pl/@meta.texy @@ -0,0 +1 @@ +{{sitename: Dokumentacja Nette}} diff --git a/http/pl/configuration.texy b/http/pl/configuration.texy index 009a58cbf1..5ef5f5a7e8 100644 --- a/http/pl/configuration.texy +++ b/http/pl/configuration.texy @@ -4,11 +4,11 @@ Konfiguracja HTTP .[perex] Przegląd opcji konfiguracyjnych dla Nette HTTP. -Jeśli nie używasz całego frameworka, a jedynie tej biblioteki, przeczytaj [jak załadować konfigurację |bootstrap:]. +Jeśli nie używasz całego frameworka, ale tylko tej biblioteki, przeczytaj, [jak wczytać konfigurację|bootstrap:]. -Nagłówki HTTP .[#toc-http-headers] -================================== +Nagłówki HTTP +============= ```neon http: @@ -22,22 +22,22 @@ http: frames: ... # (string|bool) domyślnie 'SAMEORIGIN' ``` -Ze względów bezpieczeństwa framework wysyła nagłówek `X-Frame-Options: SAMEORIGIN`, który mówi, że strona może być wyświetlana wewnątrz innej strony (w elemencie `<iframe>`) tylko wtedy, gdy znajduje się w tej samej domenie. Może to być niepożądane w niektórych sytuacjach (na przykład, jeśli tworzysz aplikację dla Facebooka), więc możesz zmienić zachowanie, ustawiając `frames: http://allowed-host.com`. +Framework ze względów bezpieczeństwa wysyła nagłówek `X-Frame-Options: SAMEORIGIN`, który mówi, że stronę można wyświetlić wewnątrz innej strony (w elemencie `<iframe>`) tylko wtedy, gdy znajduje się ona na tej samej domenie. Może to być w niektórych sytuacjach niepożądane (na przykład jeśli tworzysz aplikację dla Facebooka), zachowanie można zatem zmienić, ustawiając `frames: http://allowed-host.com` lub `frames: true`. -Polityka bezpieczeństwa treści .[#toc-content-security-policy] --------------------------------------------------------------- +Content Security Policy +----------------------- -Łatwo jest zbudować nagłówki `Content-Security-Policy` (dalej CSP), zobacz opis [CSP |https://content-security-policy.com] dla opisu. Dyrektywy CSP (takie jak `script-src`) mogą być zapisane jako łańcuchy zgodnie ze specyfikacją lub jako tablica wartości dla lepszej czytelności. Wtedy nie ma potrzeby pisania cudzysłowów wokół słów kluczowych, takich jak na przykład `'self'`,. Nette automatycznie wygeneruje również wartość `nonce`, więc w nagłówku będzie napisane `'nonce-y4PopTLM=='`. +Łatwo można tworzyć nagłówki `Content-Security-Policy` (dalej CSP), ich opis znajdziesz w [opisie CSP |https://content-security-policy.com]. Dyrektywy CSP (jak np. `script-src`) mogą być zapisane albo jako ciągi znaków zgodnie ze specyfikacją, albo jako tablica wartości dla lepszej czytelności. Wtedy nie trzeba wokół słów kluczowych, jak na przykład `'self'`, pisać cudzysłowów. Nette również automatycznie wygeneruje wartość `nonce`, więc w nagłówku będzie na przykład `'nonce-y4PopTLM=='`. ```neon http: - # Polityka bezpieczeństwa treści + # Content Security Policy csp: - # ciąg znaków w postaci specyfikacji CSP + # ciąg znaków zgodny ze specyfikacją CSP default-src: "'self' https://example.com" - # array of values + # tablica wartości script-src: - nonce - strict-dynamic @@ -49,9 +49,9 @@ http: block-all-mixed-content: false ``` -W szablonach należy używać `<script n:nonce>...</script>` a wartość nonce zostanie dodana automatycznie. Tworzenie bezpiecznych stron w Nette jest naprawdę proste. +W szablonach używaj `<script n:nonce>...</script>`, a wartość nonce zostanie uzupełniona automatycznie. Tworzenie bezpiecznych stron w Nette jest naprawdę łatwe. -Podobnie można zbudować nagłówki `Content-Security-Policy-Report-Only` (które mogą być używane równolegle z CSP) i [Feature Policy |https://developers.google.com/web/updates/2018/06/feature-policy]: +Podobnie można tworzyć nagłówki `Content-Security-Policy-Report-Only` (które można używać równolegle z CSP) i [Feature Policy|https://developers.google.com/web/updates/2018/06/feature-policy]: ```neon http: @@ -69,91 +69,103 @@ http: ``` -Ciasteczko HTTP .[#toc-http-cookie] ------------------------------------ +Ciasteczka HTTP +--------------- -Możesz zmienić domyślne wartości niektórych parametrów metod [Nette\Http\Response::setCookie() |response#setCookie] i session. +Można zmienić domyślne wartości niektórych parametrów metody [Nette\Http\Response::setCookie() |response#setCookie] i sesji. ```neon http: - # cookie reach by path + # zasięg ciasteczka według ścieżki cookiePath: ... # (string) domyślnie '/' # domeny, które akceptują ciasteczka - cookieDomain: 'example.com' # (string|domain) domyślnie nie jest ustawiony + cookieDomain: 'example.com' # (string|domain) domyślnie nieustawione - # wysyłać cookie tylko przez HTTPS? - cookieSecure: ... # (bool|auto) domyślnie jest auto + # wysyłać ciasteczka tylko przez HTTPS? + cookieSecure: ... # (bool|auto) domyślnie auto - # wyłącza wysyłanie ciasteczka, którego Nette używa jako zabezpieczenia przed CSRF - disableNetteCookie: ... # (bool) domyślnie jest false + # wyłącza wysyłanie ciasteczka używanego przez Nette jako ochronę przed CSRF + disableNetteCookie: ... # (bool) domyślnie false ``` -Atrybut `cookieDomain` określa, które domeny mogą akceptować pliki cookie. Jeśli nie jest określone, plik cookie jest akceptowany przez tę samą (pod)domenę, która go ustawiła, *ale nie* jej subdomeny. Jeśli określono `cookieDomain`, uwzględniane są subdomeny. Dlatego określenie `cookieDomain` jest mniej restrykcyjne niż pominięcie go. +Atrybut `cookieDomain` określa, które domeny mogą akceptować ciasteczka. Jeśli nie jest podany, ciasteczko akceptuje ta sama (sub)domena, która je ustawiła, *ale nie* jej subdomeny. Jeśli `cookieDomain` jest podany, uwzględniane są również subdomeny. Dlatego podanie `cookieDomain` jest mniej ograniczające niż jego pominięcie. -Na przykład, w przypadku `cookieDomain: nette.org`, pliki cookie są również dostępne na wszystkich subdomenach jako `doc.nette.org`. To samo można również osiągnąć za pomocą wartości specjalnej `domain`, czyli `cookieDomain: domain`. +Na przykład przy `cookieDomain: nette.org` ciasteczka są dostępne również na wszystkich subdomenach, takich jak `doc.nette.org`. Tego samego można dokonać również za pomocą specjalnej wartości `domain`, czyli `cookieDomain: domain`. -Domyślna wartość `auto` dla atrybutu `cookieSecure` oznacza, że jeśli strona działa na HTTPS, pliki cookie będą wysyłane z flagą `Secure`, a więc będą dostępne tylko przez HTTPS. +Domyślna wartość `auto` dla atrybutu `cookieSecure` oznacza, że jeśli strona działa na HTTPS, ciasteczka będą wysyłane z flagą `Secure` i będą dostępne tylko przez HTTPS. -Proxy HTTP .[#toc-http-proxy] ------------------------------ +Proxy HTTP +---------- -Jeśli witryna działa za proxy HTTP, wpisz jego adres IP, aby wykrywanie połączenia HTTPS działało poprawnie, a także adres IP klienta. Oznacza to, że funkcje [Nette:getRemoteAddress() |request#getRemoteAddress] i [isSecured() |request#isSecured] zwracają poprawne wartości i generują w szablonach linki z protokołem `https:`. +Jeśli strona działa za proxy HTTP, podaj jej adres IP, aby poprawnie działało wykrywanie połączenia przez HTTPS oraz adres IP klienta. Czyli aby funkcje [Nette\Http\Request::getRemoteAddress() |request#getRemoteAddress] i [isSecured() |request#isSecured] zwracały poprawne wartości, a w szablonach generowały się linki z protokołem `https:`. ```neon http: - # adres IP, zakres (np. 127.0.0.1/8) lub tablica tych wartości - proxy: 127.0.0.1 # (string|string[]) domyślnie nie jest ustawiony + # Adres IP, zakres (np. 127.0.0.1/8) lub tablica tych wartości + proxy: 127.0.0.1 # (string|string[]) domyślnie nieustawione ``` -Sesja .[#toc-session] -===================== +Sesja +===== -Podstawowe ustawienia [sesji |sessions]: +Podstawowe ustawienia [sesji|sessions]: ```neon session: - # pokazowy panel sesyjny w Tracy Bar? - debugger: ... # (bool) domyślnie jest false + # pokazać panel sesji w Tracy Bar? + debugger: ... # (bool) domyślnie false - # okres bezczynności, po którym sesja wygasa - expiration: 14 days # (string) defaults to '3 hours' + # czas nieaktywności, po którym sesja wygaśnie + expiration: 14 days # (string) domyślnie '3 hours' - # kiedy sesja powinna się rozpocząć? + # kiedy uruchomić sesję? autoStart: ... # (smart|always|never) domyślnie 'smart' - # handler, usługa implementująca interfejs SessionHandler + # handler, usługa implementująca interfejs SessionHandlerInterface handler: @handlerService ``` -Opcja `autoStart` kontroluje, kiedy rozpocząć sesję. Wartość `always` oznacza, że sesja będzie zawsze uruchamiana w momencie startu aplikacji. Wartość `smart` oznacza, że sesja będzie uruchamiana tylko w momencie startu aplikacji, jeśli już istnieje, lub gdy będziemy chcieli z niej czytać lub do niej pisać. Wreszcie, wartość `never` wyłącza automatyczne rozpoczynanie sesji. +Opcja `autoStart` kontroluje, kiedy ma się uruchamiać sesja. Wartość `always` oznacza, że sesja uruchomi się zawsze wraz z uruchomieniem aplikacji. Wartość `smart` oznacza, że sesja uruchomi się przy starcie aplikacji tylko wtedy, gdy już istnieje, lub w chwili, gdy chcemy z niej czytać lub do niej zapisywać. A na koniec wartość `never` zabrania automatycznego startu sesji. -Możesz również ustawić wszystkie [dyrektywy sesji |https://www.php.net/manual/en/session.configuration.php] PHP (w formacie camelCase), a także [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. Przykład: +Dalej można ustawiać wszystkie [dyrektywy sesji |https://www.php.net/manual/en/session.configuration.php] PHP (w formacie camelCase) oraz [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. Przykład: ```neon session: - # write 'session.name' as 'name' + # 'session.name' zapisujemy jako 'name' name: MYID - # zapisz 'session.save_path' jako 'savePath' + # 'session.save_path' zapisujemy jako 'savePath' savePath: "%tempDir%/sessions" ``` -Plik cookie sesji .[#toc-session-cookie] ----------------------------------------- +Ciasteczko sesji +---------------- -Sesyjny plik cookie jest wysyłany z takimi samymi parametrami jak [inne |#HTTP-Cookie] pliki cookie, ale możesz je dla niego zmienić: +Ciasteczko sesji jest wysyłane z tymi samymi parametrami co [inne ciasteczka |#Ciasteczka HTTP], ale te możesz dla niego zmienić: ```neon session: # domeny, które akceptują ciasteczka - cookieDomain: 'example.com' # (string|domain) + cookieDomain: 'example.com' # (string|domain) - # ograniczenia w przypadku dostępu z innej domeny - cookieSamesite: None # (Strict|Lax|None) domyślnie Lax + # ograniczenie przy dostępie z innej domeny + cookieSamesite: None # (Strict|Lax|None) domyślnie Lax ``` -Atrybut `cookieSamesite` wpływa na to, czy plik cookie jest wysyłany przy [dostępie z innej domeny |nette:glossary#SameSite-Cookie], co zapewnia pewną ochronę przed atakami [Cross-Site Request Forgery |nette:glossary#Cross-Site-Request-Forgery-CSRF] (CSRF). +Atrybut `cookieSamesite` wpływa na to, czy ciasteczko zostanie wysłane podczas [dostępu z innej domeny |nette:glossary#SameSite cookie], co zapewnia pewną ochronę przed atakami [Cross-Site Request Forgery |nette:glossary#Cross-Site Request Forgery CSRF] (CSRF). + + +Usługi DI +========= + +Te usługi są dodawane do kontenera DI: + +| Nazwa | Typ | Opis +|----------------------------------------------------- +| `http.request` | [api:Nette\Http\Request] | [Żądanie HTTP| request] +| `http.response` | [api:Nette\Http\Response] | [Odpowiedź HTTP| response] +| `session.session` | [api:Nette\Http\Session] | [zarządzanie sesją| sessions] diff --git a/http/pl/request.texy b/http/pl/request.texy index c8d899a45f..09a2e581df 100644 --- a/http/pl/request.texy +++ b/http/pl/request.texy @@ -2,24 +2,24 @@ ************ .[perex] -Nette enkapsuluje żądanie HTTP w obiekty o zrozumiałym API, zapewniając jednocześnie filtr sanityzujący. +Nette enkapsuluje żądanie HTTP w obiekty z zrozumiałym API i jednocześnie zapewnia filtr sanityzujący. -Żądanie HTTP jest reprezentowane przez obiekt [api:Nette\Http\Request], do którego uzyskujesz dostęp poprzez przekazanie go za pomocą [zastrzyku zależności |dependency-injection:passing-dependencies]. W presenterech wystarczy zadzwonić na `$httpRequest = $this->getHttpRequest()`. +Żądanie HTTP reprezentuje obiekt [api:Nette\Http\Request]. Jeśli pracujesz z Nette, ten obiekt jest automatycznie tworzony przez framework i możesz go otrzymać za pomocą [wstrzykiwania zależności |dependency-injection:passing-dependencies]. W prezenterach wystarczy tylko wywołać metodę `$this->getHttpRequest()`. Jeśli pracujesz poza Nette Framework, możesz utworzyć obiekt za pomocą [#RequestFactory]. -Co ważne, gdy Nette [tworzy |#RequestFactory] ten obiekt, sanityzuje wszystkie parametry wejściowe GET, POST, COOKIE, a także URL ze znaków sterujących i nieprawidłowych sekwencji UTF-8. Można więc wtedy bezpiecznie kontynuować pracę z danymi. Oczyszczone dane są następnie wykorzystywane w prezenterach i formularzach. +Dużą zaletą Nette jest to, że podczas tworzenia obiektu automatycznie oczyszcza wszystkie parametry wejściowe GET, POST, COOKIE oraz URL z znaków kontrolnych i nieprawidłowych sekwencji UTF-8. Z tymi danymi możesz następnie bezpiecznie dalej pracować. Oczyszczone dane są następnie używane w prezenterach i formularzach. -→ [Instalacja i wymagania |@home#Installation] +→ [Instalacja i wymagania |@home#Instalacja] -Nette\NRequest .[#toc-nette-http-request] -========================================= +Nette\Http\Request +================== -Ten obiekt jest niezmienny. Nie posiada żadnych seterów, ma tylko jeden tzw. wither `withUrl()`, który nie modyfikuje obiektu, ale zwraca nową instancję ze zmienioną wartością. +Ten obiekt jest immutable (niezmienny). Nie ma żadnych setterów, ma tylko jeden tzw. wither `withUrl()`, który nie zmienia obiektu, ale zwraca nową instancję ze zmienioną wartością. withUrl(Nette\Http\UrlScript $url): Nette\Http\Request .[method] ---------------------------------------------------------------- -Zwraca klon z innym adresem URL. +Zwraca klona z innym adresem URL. getUrl(): Nette\Http\UrlScript .[method] @@ -28,15 +28,15 @@ Zwraca adres URL żądania jako obiekt [UrlScript |urls#UrlScript]. ```php $url = $httpRequest->getUrl(); -echo $url; // https://doc.nette.org/en/?action=edit +echo $url; // https://doc.nette.org/cs/?action=edit echo $url->getHost(); // nette.org ``` -Przeglądarki nie wysyłają fragmentu do serwera, więc `$url->getFragment()` zwróci pusty ciąg. +Uwaga: przeglądarki nie wysyłają fragmentu na serwer, więc `$url->getFragment()` będzie zwracać pusty ciąg znaków. -getQuery(string $key=null): string|array|null .[method] -------------------------------------------------------- +getQuery(?string $key=null): string|array|null .[method] +-------------------------------------------------------- Zwraca parametry żądania GET. ```php @@ -45,41 +45,41 @@ $id = $httpRequest->getQuery('id'); // zwraca parametr GET 'id' (lub null) ``` -getPost(string $key=null): string|array|null .[method] ------------------------------------------------------- +getPost(?string $key=null): string|array|null .[method] +------------------------------------------------------- Zwraca parametry żądania POST. ```php -$all = $httpRequest->getPost(); // zwraca tablicę wszystkich parametrów z POSTa +$all = $httpRequest->getPost(); // zwraca tablicę wszystkich parametrów z POST $id = $httpRequest->getPost('id'); // zwraca parametr POST 'id' (lub null) ``` getFile(string|string[] $key): Nette\Http\FileUpload|array|null .[method] ------------------------------------------------------------------------- -Zwraca [przesłanie |#Uploaded-Files] jako obiekt [api:Nette\Http\FileUpload]: +Zwraca [przesłany plik |#Przesłane pliki] jako obiekt [api:Nette\Http\FileUpload]: ```php $file = $httpRequest->getFile('avatar'); -if ($file->hasFile()) { // czy jakiś plik został przesłany? - $file->getUntrustedName(); // nazwa pliku przesłanego przez użytkownika +if ($file?->hasFile()) { // czy jakiś plik został przesłany? + $file->getUntrustedName(); // nazwa pliku wysłana przez użytkownika $file->getSanitizedName(); // nazwa bez niebezpiecznych znaków } ``` -Określenie pola kluczowego dla dostępu do struktury zagnieżdżonej. +Aby uzyskać dostęp do zagnieżdżonej struktury, podaj tablicę kluczy. ```php //<input type="file" name="my-form[details][avatar]" multiple> $file = $request->getFile(['my-form', 'details', 'avatar']); ``` -Ponieważ nie można ufać zewnętrznym danym, a zatem nie można polegać na formie struktury plików, jest to bezpieczniejsza metoda niż np. `$request->getFiles()['my-form']['details']['avatar']`co może się nie udać. +Ponieważ nie można ufać danym z zewnątrz, a zatem polegać na strukturze plików, ten sposób jest bezpieczniejszy niż na przykład `$request->getFiles()['my-form']['details']['avatar']`, który może zawieść. getFiles(): array .[method] --------------------------- -Zwraca drzewo [wszystkich uploadów |#Uploaded-Files] w znormalizowanej strukturze, której liście są obiektami [api:Nette\Http\FileUpload]: +Zwraca drzewo [wszystkich przesłanych plików |#Przesłane pliki] w znormalizowanej strukturze, której liśćmi są obiekty [api:Nette\Http\FileUpload]: ```php $files = $httpRequest->getFiles(); @@ -88,7 +88,7 @@ $files = $httpRequest->getFiles(); getCookie(string $key): string|array|null .[method] --------------------------------------------------- -Zwraca cookie lub `null`, jeśli nie istnieje. +Zwraca ciasteczko lub `null`, jeśli nie istnieje. ```php $sessId = $httpRequest->getCookie('sess_id'); @@ -115,7 +115,7 @@ $httpRequest->getMethod(); // GET, POST, HEAD, PUT isMethod(string $method): bool .[method] ---------------------------------------- -Testuje metodę HTTP, za pomocą której zostało wykonane żądanie. Wielkość liter w parametrze nie ma znaczenia. +Testuje metodę HTTP, za pomocą której zostało wykonane żądanie. Parametr jest niewrażliwy na wielkość liter. ```php if ($httpRequest->isMethod('GET')) // ... @@ -124,7 +124,7 @@ if ($httpRequest->isMethod('GET')) // ... getHeader(string $header): ?string .[method] -------------------------------------------- -Zwraca nagłówek HTTP lub `null`, jeśli nie istnieje. Wielkość liter w parametrze nie ma znaczenia. +Zwraca nagłówek HTTP lub `null`, jeśli nie istnieje. Parametr jest niewrażliwy na wielkość liter. ```php $userAgent = $httpRequest->getHeader('User-Agent'); @@ -141,19 +141,14 @@ echo $headers['Content-Type']; ``` -getReferer(): ?Nette\Http\UrlImmutable .[method] ------------------------------------------------- -Z jakiego adresu URL przyszedł użytkownik? Uważaj, w ogóle nie jest niezawodny. - - isSecured(): bool .[method] --------------------------- -Czy połączenie jest szyfrowane (HTTPS)? Może być konieczne skonfigurowanie [proxy |configuration#HTTP-Proxy] dla prawidłowej funkcjonalności. +Czy połączenie jest szyfrowane (HTTPS)? Aby zapewnić prawidłowe działanie, może być konieczne [skonfigurowanie proxy |configuration#Proxy HTTP]. isSameSite(): bool .[method] ---------------------------- -Czy żądanie pochodzi z tej samej (pod)domeny i jest inicjowane przez kliknięcie na link? Nette używa pliku cookie `_nss` (dawniej `nette-samesite`), aby to wykryć. +Czy żądanie pochodzi z tej samej (sub)domeny i jest inicjowane przez kliknięcie linku? Nette używa ciasteczka `_nss` (wcześniej `nette-samesite`) do detekcji. isAjax(): bool .[method] @@ -163,17 +158,17 @@ Czy to jest żądanie AJAX? getRemoteAddress(): ?string .[method] ------------------------------------- -Zwraca adres IP użytkownika. Może być konieczne skonfigurowanie [proxy |configuration#HTTP-Proxy] dla prawidłowej funkcjonalności. +Zwraca adres IP użytkownika. Aby zapewnić prawidłowe działanie, może być konieczne [skonfigurowanie proxy |configuration#Proxy HTTP]. getRemoteHost(): ?string .[method deprecated] --------------------------------------------- -Zwraca rozdzielczość DNS adresu IP użytkownika. Może być konieczne skonfigurowanie [proxy |configuration#HTTP-Proxy] dla prawidłowej funkcjonalności. +Zwraca tłumaczenie DNS adresu IP użytkownika. Aby zapewnić prawidłowe działanie, może być konieczne [skonfigurowanie proxy |configuration#Proxy HTTP]. -getBasicCredentials(): ?string .[method] ----------------------------------------- -Zwraca dane uwierzytelniające dla uwierzytelniania [Basic HTTP |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication]. +getBasicCredentials(): ?array .[method] +--------------------------------------- +Zwraca dane uwierzytelniające dla [Basic HTTP authentication |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication]. ```php [$user, $password] = $httpRequest->getBasicCredentials(); @@ -182,7 +177,7 @@ Zwraca dane uwierzytelniające dla uwierzytelniania [Basic HTTP |https://develop getRawBody(): ?string .[method] ------------------------------- -Zwraca treść żądania HTTP. +Zwraca ciało żądania HTTP. ```php $body = $httpRequest->getRawBody(); @@ -191,48 +186,55 @@ $body = $httpRequest->getRawBody(); detectLanguage(array $langs): ?string .[method] ----------------------------------------------- -Wykrywa język. Przekaż tablicę języków obsługiwanych przez aplikację jako parametr `$lang`, a zwróci ona ten, który przeglądarka odwiedzającego wolałaby zobaczyć. Nie ma w tym żadnej magii, po prostu używa nagłówka `Accept-Language`. Jeśli nie ma dopasowania, zwraca `null`. +Wykrywa język. Jako parametr `$lang` przekazujemy tablicę z językami obsługiwanymi przez aplikację, a ona zwraca ten, który przeglądarka odwiedzającego preferuje najbardziej. To nie są żadne czary, po prostu wykorzystuje nagłówek `Accept-Language`. Jeśli nie ma dopasowania, zwraca `null`. ```php -// przeglądarka wysyła np. Accept-Language: en-us;q=0.8,en;q=0.5,en;q=0.3 +// przeglądarka wysyła np. Accept-Language: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3 $langs = ['hu', 'pl', 'en']; // języki obsługiwane przez aplikację -echo $httpRequest->detectLanguage($langs); // pl +echo $httpRequest->detectLanguage($langs); // en ``` -RequestFactory .[#toc-requestfactory] -===================================== +RequestFactory +============== -Bieżący obiekt żądania HTTP wytworzy [api:Nette\Http\RequestFactory]. Jeśli piszesz aplikację, która nie używa kontenera DI, wytworzysz żądanie w następujący sposób: +Klasa [api:Nette\Http\RequestFactory] służy do tworzenia instancji `Nette\Http\Request`, która reprezentuje bieżące żądanie HTTP. (Jeśli pracujesz z Nette, obiekt żądania HTTP jest automatycznie tworzony przez framework.) ```php $factory = new Nette\Http\RequestFactory; $httpRequest = $factory->fromGlobals(); ``` -RequestFactory może być skonfigurowany przed wywołaniem `fromGlobals()`. Możemy wyłączyć sanityzację parametrów wejściowych ze znaków sterujących i nieprawidłowych sekwencji UTF-8 za pomocą `$factory->setBinary()`. A także skonfigurować serwer proxy za pomocą `$factory->setProxy(...)`, co jest ważne dla prawidłowego wykrywania adresu IP użytkownika. +Metoda `fromGlobals()` tworzy obiekt żądania na podstawie bieżących globalnych zmiennych PHP (`$_GET`, `$_POST`, `$_COOKIE`, `$_FILES` i `$_SERVER`). Podczas tworzenia obiektu automatycznie oczyszcza wszystkie parametry wejściowe GET, POST, COOKIE oraz URL z znaków kontrolnych i nieprawidłowych sekwencji UTF-8, co zapewnia bezpieczeństwo podczas dalszej pracy z tymi danymi. + +RequestFactory można skonfigurować przed wywołaniem `fromGlobals()`: -Za pomocą tzw. filtrów można oczyścić adres URL ze znaków, które mogą się do niego dostać, np. z powodu źle wdrożonych systemów komentarzy na różnych zagranicznych stronach: +- metodą `$factory->setBinary()` wyłączysz automatyczne czyszczenie parametrów wejściowych z znaków kontrolnych i nieprawidłowych sekwencji UTF-8. +- metodą `$factory->setProxy(...)` podasz adres IP [serwera proxy |configuration#Proxy HTTP], co jest niezbędne do poprawnego wykrywania adresu IP użytkownika. + +RequestFactory umożliwia definiowanie filtrów, które automatycznie transformują części adresu URL żądania. Filtry te usuwają niepożądane znaki z adresu URL, które mogą się tam znaleźć na przykład z powodu nieprawidłowej implementacji systemów komentarzy na różnych stronach: ```php -// usuń spacje z drogi +// usunięcie spacji ze ścieżki $requestFactory->urlFilters['path']['%20'] = ''; -// usuń kropkę, przecinek lub prawy nawias z końca URI +// usunięcie kropki, przecinka lub prawego nawiasu z końca URI $requestFactory->urlFilters['url']['[.,)]$'] = ''; -// czyszczenie ścieżki z podwójnych ukośników (domyślny filtr) +// oczyszczenie ścieżki z podwójnych ukośników (filtr domyślny) $requestFactory->urlFilters['path']['/{2,}'] = '/'; ``` +Pierwszy klucz `'path'` lub `'url'` określa, do której części adresu URL filtr zostanie zastosowany. Drugi klucz to wyrażenie regularne, które ma zostać wyszukane, a wartość to zamiennik, który zostanie użyty zamiast znalezionego tekstu. + -Przesłane pliki .[#toc-uploaded-files] -====================================== +Przesłane pliki +=============== -Metoda `Nette\Http\Request::getFiles()` zwraca tablicę wszystkich uploadów w znormalizowanej strukturze, której listki są obiektami [api:Nette\Http\FileUpload]. Zawierają one dane przesłane przez element formularza `<input type=file>`. +Metoda `Nette\Http\Request::getFiles()` zwraca tablicę wszystkich przesłanych plików w znormalizowanej strukturze, której liśćmi są obiekty [api:Nette\Http\FileUpload]. Enkapsulują one dane wysłane przez element formularza `<input type=file>`. -Struktura odzwierciedla nazewnictwo elementów w HTML. W najprostszym przypadku może to być pojedynczy nazwany element formularza złożony jako: +Struktura odzwierciedla nazewnictwo elementów w HTML. W najprostszym przypadku może to być pojedynczy nazwany element formularza wysłany jako: ```latte <input type="file" name="avatar"> @@ -242,17 +244,17 @@ W tym przypadku `$request->getFiles()` zwraca tablicę: ```php [ - 'avatar' => /* FileUpload instance */ + 'avatar' => /* Instancja FileUpload */ ] ``` -Obiekt `FileUpload` jest tworzony nawet jeśli użytkownik nie przesłał żadnego pliku lub przesłanie nie powiodło się. Jeśli plik został załadowany, to zwraca się metoda `hasFile()`: +Obiekt `FileUpload` jest tworzony nawet wtedy, gdy użytkownik nie wysłał żadnego pliku lub wysyłanie nie powiodło się. Czy plik został wysłany, zwraca metoda `hasFile()`: ```php -$request->getFile('avatar')->hasFile(); +$request->getFile('avatar')?->hasFile(); ``` -W przypadku nazwy elementu z wykorzystaniem notacji tablicowej: +W przypadku nazwy elementu używającej notacji dla tablic: ```latte <input type="file" name="my-form[details][avatar]"> @@ -264,7 +266,7 @@ zwrócone drzewo wygląda tak: [ 'my-form' => [ 'details' => [ - 'avatar' => /* FileUpload instance */ + 'avatar' => /* Instancja FileUpload */ ], ], ] @@ -276,23 +278,23 @@ Można również utworzyć tablicę plików: <input type="file" name="my-form[details][avatars][]" multiple> ``` -W tym przypadku struktura wygląda tak: +W takim przypadku struktura wygląda tak: ```php [ 'my-form' => [ 'details' => [ 'avatars' => [ - 0 => /* FileUpload instance */, - 1 => /* FileUpload instance */, - 2 => /* FileUpload instance */, + 0 => /* Instancja FileUpload */, + 1 => /* Instancja FileUpload */, + 2 => /* Instancja FileUpload */, ], ], ], ] ``` -Najlepszym sposobem podejścia do indeksu 1 zagnieżdżonej tablicy jest następujący sposób: +Dostęp do indeksu 1 zagnieżdżonej tablicy najlepiej uzyskać w ten sposób: ```php $file = $request->getFile(['my-form', 'details', 'avatars', 1]); @@ -301,7 +303,7 @@ if ($file instanceof FileUpload) { } ``` -Ponieważ nie można ufać zewnętrznym danym, a zatem nie można polegać na formie struktury plików, jest to bezpieczniejszy sposób niż np. `$request->getFiles()['my-form']['details']['avatars'][1]`co może się nie udać. +Ponieważ nie można ufać danym z zewnątrz, a zatem polegać na strukturze plików, ten sposób jest bezpieczniejszy niż na przykład `$request->getFiles()['my-form']['details']['avatars'][1]`, który może zawieść. Przegląd metod `FileUpload` .{toc: FileUpload} @@ -310,22 +312,22 @@ Przegląd metod `FileUpload` .{toc: FileUpload} hasFile(): bool .[method] ------------------------- -Zwraca `true`, jeśli użytkownik przesłał plik. +Zwraca `true`, jeśli użytkownik przesłał jakiś plik. isOk(): bool .[method] ---------------------- -Zwraca `true`, jeśli plik został przesłany pomyślnie. +Zwraca `true`, jeśli plik został pomyślnie przesłany. getError(): int .[method] ------------------------- -Zwraca kod błędu podczas przesyłania pliku. Jest to jedna ze stałych [UPLOAD_ERR_XXX |http://php.net/manual/en/features.file-upload.errors.php]. Jeśli przesyłanie zakończyło się sukcesem, zwraca `UPLOAD_ERR_OK`. +Zwraca kod błędu podczas przesyłania pliku. Jest to jedna ze stałych [UPLOAD_ERR_XXX|http://php.net/manual/en/features.file-upload.errors.php]. Jeśli przesyłanie przebiegło pomyślnie, zwraca `UPLOAD_ERR_OK`. move(string $dest) .[method] ---------------------------- -Przenosi przesłany plik do nowej lokalizacji. Jeśli plik docelowy już istnieje, zostanie on nadpisany. +Przenosi przesłany plik do nowej lokalizacji. Jeśli plik docelowy już istnieje, zostanie nadpisany. ```php $file->move('/path/to/files/name.ext'); @@ -334,12 +336,12 @@ $file->move('/path/to/files/name.ext'); getContents(): ?string .[method] -------------------------------- -Zwraca zawartość przesłanego pliku. Jeśli przesłanie nie powiodło się, zwraca `null`. +Zwraca zawartość przesłanego pliku. Jeśli przesyłanie nie powiodło się, zwraca `null`. getContentType(): ?string .[method] ----------------------------------- -Wykrywa typ zawartości MIME przesłanego pliku na podstawie jego sygnatury. Jeśli przesłanie nie powiodło się lub wykrycie nie powiodło się, zwraca `null`. +Wykrywa typ zawartości MIME przesłanego pliku na podstawie jego sygnatury. Jeśli przesyłanie nie powiodło się lub wykrywanie nie powiodło się, zwraca `null`. .[caution] Wymaga rozszerzenia PHP `fileinfo`. @@ -347,38 +349,49 @@ Wymaga rozszerzenia PHP `fileinfo`. getUntrustedName(): string .[method] ------------------------------------ -Zwraca oryginalną nazwę pliku przesłaną przez przeglądarkę. +Zwraca oryginalną nazwę pliku wysłaną przez przeglądarkę. .[caution] -Nie należy ufać wartości zwracanej przez tę metodę. Klient mógł wysłać złośliwą nazwę pliku z zamiarem uszkodzenia lub zhakowania Twojej aplikacji. +Nie ufaj wartości zwracanej przez tę metodę. Klient mógł wysłać złośliwą nazwę pliku w celu uszkodzenia lub zhakowania aplikacji. getSanitizedName(): string .[method] ------------------------------------ -Zwraca oczyszczoną nazwę pliku. Zawiera tylko znaki ASCII `[a-zA-Z0-9.-]`. Jeśli nazwa nie zawiera takich znaków, zwraca `'unknown'`. Jeśli plik jest obrazem JPEG, PNG, GIF lub WebP, zwraca również prawidłowe rozszerzenie. +Zwraca oczyszczoną nazwę pliku. Zawiera tylko znaki ASCII `[a-zA-Z0-9.-]`. Jeśli nazwa nie zawiera takich znaków, zwraca `'unknown'`. Jeśli plik jest obrazem w formacie JPEG, PNG, GIF, WebP lub AVIF, zwraca również poprawne rozszerzenie. + +.[caution] +Wymaga rozszerzenia PHP `fileinfo`. + + +getSuggestedExtension(): ?string .[method]{data-version:3.2.4} +-------------------------------------------------------------- +Zwraca odpowiednie rozszerzenie pliku (bez kropki) odpowiadające wykrytemu typowi MIME. + +.[caution] +Wymaga rozszerzenia PHP `fileinfo`. getUntrustedFullPath(): string .[method] ---------------------------------------- -Zwraca oryginalną ścieżkę pliku, jak wysłane przez przeglądarkę, gdy folder został załadowany. Pełna ścieżka jest dostępna tylko w PHP 8.1 i wyższych. W poprzednich wersjach metoda ta zwracała oryginalną nazwę pliku. +Zwraca oryginalną ścieżkę do pliku, tak jak została wysłana przez przeglądarkę podczas przesyłania folderu. Pełna ścieżka jest dostępna tylko w PHP 8.1 i nowszych. W poprzednich wersjach ta metoda zwraca oryginalną nazwę pliku. .[caution] -Nie należy ufać wartości zwracanej przez tę metodę. Klient mógł wysłać złośliwą nazwę pliku z zamiarem uszkodzenia lub zhakowania Twojej aplikacji. +Nie ufaj wartości zwracanej przez tę metodę. Klient mógł wysłać złośliwą nazwę pliku w celu uszkodzenia lub zhakowania aplikacji. getSize(): int .[method] ------------------------ -Zwraca rozmiar przesłanego pliku. Zwraca `0` jeśli załadowanie nie powiodło się. +Zwraca rozmiar przesłanego pliku. Jeśli przesyłanie nie powiodło się, zwraca `0`. getTemporaryFile(): string .[method] ------------------------------------ -Zwraca ścieżkę do tymczasowej lokalizacji przesłanego pliku. Jeśli przesłanie nie powiodło się, zwraca `''`. +Zwraca ścieżkę do tymczasowej lokalizacji przesłanego pliku. Jeśli przesyłanie nie powiodło się, zwraca `''`. isImage(): bool .[method] ------------------------- -Zwraca `true`, jeśli przesłany plik jest obrazem JPEG, PNG, GIF lub WebP. Wykrywanie oparte jest na jego sygnaturze i nie sprawdza integralności całego pliku. Możliwe jest określenie, czy obraz jest uszkodzony, na przykład próbując go [załadować |#toImage]. +Zwraca `true`, jeśli przesłany plik jest obrazem w formacie JPEG, PNG, GIF, WebP lub AVIF. Wykrywanie odbywa się na podstawie jego sygnatury i nie weryfikuje integralności całego pliku. Czy obraz nie jest uszkodzony, można sprawdzić na przykład próbując go [załadować |#toImage]. .[caution] Wymaga rozszerzenia PHP `fileinfo`. @@ -386,9 +399,9 @@ Wymaga rozszerzenia PHP `fileinfo`. getImageSize(): ?array .[method] -------------------------------- -Zwraca parę `[šířka, výška]` z wymiarami przesłanego obrazu. Jeśli przesłanie nie powiodło się lub nie jest to prawidłowy obraz, zwraca `null`. +Zwraca parę `[szerokość, wysokość]` z wymiarami przesłanego obrazu. Jeśli przesyłanie nie powiodło się lub nie jest to prawidłowy obraz, zwraca `null`. toImage(): Nette\Utils\Image .[method] -------------------------------------- -Wczytuje obraz jako obiekt [Image |utils:images]. Jeśli przesłanie nie powiodło się lub nie jest prawidłowym obrazem, rzuca wyjątek `Nette\Utils\ImageException`. +Ładuje obraz jako obiekt [Image|utils:images]. Jeśli przesyłanie nie powiodło się lub nie jest to prawidłowy obraz, zgłasza wyjątek `Nette\Utils\ImageException`. diff --git a/http/pl/response.texy b/http/pl/response.texy index 363f21bd36..d7468ec9d1 100644 --- a/http/pl/response.texy +++ b/http/pl/response.texy @@ -2,22 +2,22 @@ Odpowiedź HTTP ************** .[perex] -Nette enkapsuluje odpowiedź HTTP w obiekty o zrozumiałym API. +Nette enkapsuluje odpowiedź HTTP w obiekty z zrozumiałym API. -Odpowiedź HTTP jest obiektem [api:Nette\Http\Response], do którego możesz uzyskać dostęp, mając go przekazanego do ciebie za pomocą [wtrysku zależności |dependency-injection:passing-dependencies]. W presenterech wystarczy zadzwonić na `$httpResponse = $this->getHttpResponse()`. +Odpowiedź HTTP reprezentuje obiekt [api:Nette\Http\Response]. Jeśli pracujesz z Nette, ten obiekt jest automatycznie tworzony przez framework i możesz go otrzymać za pomocą [wstrzykiwania zależności |dependency-injection:passing-dependencies]. W prezenterach wystarczy tylko wywołać metodę `$this->getHttpResponse()`. -→ [Instalacja i wymagania |@home#Installation] +→ [Instalacja i wymagania |@home#Instalacja] -Odpowiedź sieci .[#toc-nette-http-response] -=========================================== +Nette\Http\Response +=================== -W przeciwieństwie do Nette: [HttpRequest |request], ten obiekt jest zmienny, więc możesz użyć seterów do zmiany stanu, tj. do wysłania nagłówków. Pamiętaj, że wszystkie setery **muszą być wywołane przed wysłaniem jakichkolwiek danych wyjściowych.** Metoda `isSent()` mówi, czy dane wyjściowe zostały wysłane. Jeśli zwraca `true`, to każda próba wysłania nagłówka rzuca wyjątek `Nette\InvalidStateException`. +Obiekt jest w przeciwieństwie do [Nette\Http\Request|request] mutable, czyli za pomocą setterów możesz zmieniać stan, czyli np. wysyłać nagłówki. Pamiętaj, że wszystkie settery muszą być wywołane **przed wysłaniem jakiegokolwiek wyjścia.** Czy wyjście zostało już wysłane, powie metoda `isSent()`. Jeśli zwraca `true`, każda próba wysłania nagłówka wywoła wyjątek `Nette\InvalidStateException`. -setCode(int $code, string $reason=null) .[method] -------------------------------------------------- -Zmienia [kod |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10] statusu [odpowiedzi |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10]. Dla lepszej czytelności kodu źródłowego zalecamy używanie [predefiniowanych stałych |api:Nette\Http\IResponse] zamiast liczb. +setCode(int $code, ?string $reason=null) .[method] +-------------------------------------------------- +Zmienia [kod stanu odpowiedzi |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10]. Dla lepszej czytelności kodu źródłowego zalecamy używanie [predefiniowanych stałych |api:Nette\Http\IResponse] zamiast liczb. ```php $httpResponse->setCode(Nette\Http\Response::S404_NotFound); @@ -26,12 +26,12 @@ $httpResponse->setCode(Nette\Http\Response::S404_NotFound); getCode(): int .[method] ------------------------ -Zwraca kod statusu odpowiedzi. +Zwraca kod stanu odpowiedzi. isSent(): bool .[method] ------------------------ -Zwraca, czy nagłówki zostały już wysłane z serwera do przeglądarki i dlatego nie można już wysyłać nagłówków ani zmieniać kodu statusu. +Zwraca informację, czy nagłówki zostały już wysłane z serwera do przeglądarki, a zatem nie jest już możliwe wysyłanie nagłówków ani zmiana kodu stanu. setHeader(string $name, string $value) .[method] @@ -60,7 +60,7 @@ Usuwa wcześniej wysłany nagłówek HTTP. getHeader(string $header): ?string .[method] -------------------------------------------- -Zwraca wysłany nagłówek HTTP lub `null`, jeśli nie istnieje. Wielkość liter w parametrze nie ma znaczenia. +Zwraca wysłany nagłówek HTTP lub `null`, jeśli taki nie istnieje. Parametr jest niewrażliwy na wielkość liter. ```php $pragma = $httpResponse->getHeader('Pragma'); @@ -77,9 +77,9 @@ echo $headers['Pragma']; ``` -setContentType(string $type, string $charset=null) .[method] ------------------------------------------------------------- -Zmienia nagłówek na `Content-Type`. +setContentType(string $type, ?string $charset=null) .[method] +------------------------------------------------------------- +Zmienia nagłówek `Content-Type`. ```php $httpResponse->setContentType('text/plain', 'UTF-8'); @@ -88,7 +88,7 @@ $httpResponse->setContentType('text/plain', 'UTF-8'); redirect(string $url, int $code=self::S302_Found): void .[method] ----------------------------------------------------------------- -Przekierowuje na inny adres URL. Pamiętaj, aby po zakończeniu skryptu wyjść z niego. +Przekierowuje na inny adres URL. Pamiętaj, aby zakończyć skrypt po tym. ```php $httpResponse->redirect('http://example.com'); @@ -98,52 +98,52 @@ exit; setExpiration(?string $time) .[method] -------------------------------------- -Ustawia wygasanie dokumentu HTTP przy użyciu nagłówków `Cache-Control` i `Expires`. Parametrem jest albo przedział czasowy (jako tekst) albo `null`, który wyłącza buforowanie. +Ustawia wygaśnięcie dokumentu HTTP za pomocą nagłówków `Cache-Control` i `Expires`. Parametrem jest interwał czasowy (jako tekst) lub `null`, co wyłącza buforowanie. ```php -// cache przeglądarki wygasa za godzinę +// cache w przeglądarce wygaśnie za godzinę $httpResponse->setExpiration('1 hour'); ``` sendAsFile(string $fileName) .[method] -------------------------------------- -Odpowiedź zostanie pobrana za pomocą okna dialogowego *Zapisz jako* pod określoną nazwą. Nie pobiera samego pliku. +Odpowiedź zostanie pobrana za pomocą okna dialogowego *Zapisz jako* pod podaną nazwą. Sam plik nie jest wysyłany. ```php $httpResponse->sendAsFile('faktura.pdf'); ``` -setCookie(string $name, string $value, $time, string $path=null, string $domain=null, bool $secure=null, bool $httpOnly=null, string $sameSite=null) .[method] --------------------------------------------------------------------------------------------------------------------------------------------------------------- +setCookie(string $name, string $value, $time, ?string $path=null, ?string $domain=null, ?bool $secure=null, ?bool $httpOnly=null, ?string $sameSite=null) .[method] +------------------------------------------------------------------------------------------------------------------------------------------------------------------- Wysyła ciasteczko. Domyślne wartości parametrów: -| `$path` | `'/'` | cookie dociera do wszystkich ścieżek w (pod)domenie *(konfigurowalne)* -| `$domain` | `null` | co oznacza, że ma zasięg do bieżącej (pod)domeny, ale nie do jej poddomeny *(konfigurowalne)*. -| `$secure` | `true` | jeśli strona działa na HTTPS, w przeciwnym razie `false` *(konfigurowalne)* -| `$httpOnly` | `true` | cookie jest niedostępne dla JavaScript -| `$sameSite` | `'Lax'` | cookie może nie być wysyłane, gdy [dostęp do niego odbywa się z innej domeny |nette:glossary#SameSite-Cookie] +| `$path` | `'/'` | ciasteczko ma zasięg na wszystkie ścieżki w (sub)domenie *(konfigurowalne)* +| `$domain` | `null` | co oznacza zasięg na bieżącą (sub)domenę, ale nie jej subdomeny *(konfigurowalne)* +| `$secure` | `true` | jeśli strona działa na HTTPS, w przeciwnym razie `false` *(konfigurowalne)* +| `$httpOnly` | `true` | ciasteczko jest niedostępne dla JavaScript +| `$sameSite` | `'Lax'` | ciasteczko może nie być wysyłane podczas [dostępu z innej domeny |nette:glossary#SameSite cookie] -Możesz zmienić domyślne wartości `$path`, `$domain` i `$secure` w [konfiguracji |configuration#HTTP-Cookie]. +Domyślne wartości parametrów `$path`, `$domain` i `$secure` możesz zmienić w [konfiguracji |configuration#Ciasteczka HTTP]. -Czas może być określony jako liczba sekund lub ciąg znaków: +Czas można podawać jako liczbę sekund lub ciąg znaków: ```php -$httpResponse->setCookie('lang', 'cs', '100 days'); +$httpResponse->setCookie('lang', 'pl', '100 days'); ``` -Parametr `$domain` określa, które domeny mogą akceptować pliki cookie. Jeśli nie zostanie określone, plik cookie jest akceptowany przez tę samą (pod)domenę, która go ustawiła, ale nie przez jej subdomeny. Jeśli określono `$domain`, uwzględniane są subdomeny. Dlatego określenie `$domain` jest mniej restrykcyjne niż pominięcie go. Na przykład, w przypadku `$domain = 'nette.org'`, pliki cookie są również dostępne na wszystkich subdomenach, takich jak `doc.nette.org`. +Parametr `$domain` określa, które domeny mogą akceptować ciasteczka. Jeśli nie jest podany, ciasteczko akceptuje ta sama (sub)domena, która je ustawiła, ale nie jej subdomeny. Jeśli `$domain` jest podany, uwzględniane są również subdomeny. Dlatego podanie `$domain` jest mniej ograniczające niż jego pominięcie. Na przykład przy `$domain = 'nette.org'` ciasteczka są dostępne również na wszystkich subdomenach, takich jak `doc.nette.org`. -Dla wartości `$sameSite`, można użyć stałych `Response::SameSiteLax`, `SameSiteStrict`, oraz `SameSiteNone`. +Dla wartości `$sameSite` możesz użyć stałych `Response::SameSiteLax`, `SameSiteStrict` i `SameSiteNone`. -deleteCookie(string $name, string $path=null, string $domain=null, bool $secure=null): void .[method] ------------------------------------------------------------------------------------------------------ -Usuwa plik cookie. Domyślne wartości parametrów to: -- `$path` z zasięgiem do wszystkich katalogów (`'/'`) -- `$domain` z zasięgiem do bieżącej (sub)domeny, ale nie do jej subdomen -- `$secure` jest regulowany przez ustawienia w [konfiguracji |configuration#HTTP-Cookie] +deleteCookie(string $name, ?string $path=null, ?string $domain=null, ?bool $secure=null): void .[method] +-------------------------------------------------------------------------------------------------------- +Usuwa ciasteczko. Domyślne wartości parametrów to: +- `$path` z zasięgiem na wszystkie katalogi (`'/'`) +- `$domain` z zasięgiem na bieżącą (sub)domenę, ale nie jej subdomeny +- `$secure` jest zgodny z ustawieniami w [konfiguracji |configuration#Ciasteczka HTTP] ```php $httpResponse->deleteCookie('lang'); diff --git a/http/pl/sessions.texy b/http/pl/sessions.texy index 79f3b2a4e6..e95ca6aa73 100644 --- a/http/pl/sessions.texy +++ b/http/pl/sessions.texy @@ -3,69 +3,69 @@ Sesje <div class=perex> -HTTP jest protokołem bezstanowym, ale prawie każda aplikacja musi przechowywać stan między żądaniami, takimi jak zawartość koszyka na zakupy. Po to właśnie jest sesja. Zobaczmy, +HTTP jest protokołem bezstanowym, jednak niemal każda aplikacja potrzebuje przechowywać stan między żądaniami, na przykład zawartość koszyka. Właśnie do tego służy sesja. Pokażemy, -- jak korzystać z sesji -- jak uniknąć konfliktów nazw +- jak używać sesji +- jak unikać konfliktów nazw - jak ustawić wygaśnięcie </div> -W przypadku korzystania z sesji, każdy użytkownik otrzymuje unikalny identyfikator zwany identyfikatorem sesji, który jest przekazywany w pliku cookie. Służy to jako klucz do danych sesji. W przeciwieństwie do plików cookie, które są przechowywane po stronie przeglądarki, dane sesyjne są przechowywane po stronie serwera. +Podczas używania sesji każdy użytkownik otrzymuje unikalny identyfikator zwany ID sesji, który jest przekazywany w ciasteczku. Służy on jako klucz do danych sesji. W przeciwieństwie do ciasteczek, które są przechowywane po stronie przeglądarki, dane w sesji są przechowywane po stronie serwera. -Sesję ustawiamy w [konfiguracji |configuration#Session], szczególnie ważny jest wybór czasu wygaśnięcia. +Sesję ustawiamy w [konfiguracji |configuration#Sesja], ważny jest zwłaszcza wybór czasu wygaśnięcia. -Zarządzanie sesją jest obsługiwane przez obiekt [api:Nette\Http\Session], do którego uzyskujesz dostęp poprzez przekazanie go za pomocą [zastrzyku zależności |dependency-injection:passing-dependencies]. W presenterech wystarczy zadzwonić na `$session = $this->getSession()`. +Zarządzaniem sesją zajmuje się obiekt [api:Nette\Http\Session], do którego dostaniesz się, prosząc o jego przekazanie za pomocą [wstrzykiwania zależności |dependency-injection:passing-dependencies]. W prezenterach wystarczy tylko wywołać `$session = $this->getSession()`. -→ [Instalacja i wymagania |@home#Installation] +→ [Instalacja i wymagania |@home#Instalacja] -Rozpoczęcie sesji .[#toc-starting-session] -========================================== +Uruchomienie sesji +================== -Domyślnie Nette rozpoczyna sesję automatycznie w momencie, gdy zaczniemy z niej czytać lub zapisywać do niej dane. Aby ręcznie rozpocząć sesję, użyj `$session->start()`. +Nette w domyślnych ustawieniach automatycznie rozpoczyna sesję w momencie, gdy zaczynamy z niej czytać lub do niej zapisywać dane. Ręcznie sesję rozpoczyna się za pomocą `$session->start()`. -PHP wysyła nagłówki HTTP wpływające na buforowanie, gdy sesja jest uruchamiana, zobacz [php:session_cache_limiter], i ewentualnie plik cookie z identyfikatorem sesji. Dlatego zawsze musisz rozpocząć sesję przed wysłaniem jakiegokolwiek wyjścia do przeglądarki, w przeciwnym razie zostanie rzucony wyjątek. Więc jeśli wiesz, że sesja będzie używana podczas renderowania strony, uruchom ją ręcznie wcześniej, być może w prezenterze. +PHP wysyła przy uruchomieniu sesji nagłówki HTTP wpływające na buforowanie, zobacz [php:session_cache_limiter], a ewentualnie również ciasteczko z ID sesji. Dlatego konieczne jest zawsze uruchomienie sesji jeszcze przed wysłaniem jakiegokolwiek wyjścia do przeglądarki, w przeciwnym razie zostanie zgłoszony wyjątek. Jeśli więc wiesz, że w trakcie renderowania strony będzie używana sesja, uruchom ją ręcznie wcześniej, na przykład w prezenterze. -W trybie deweloperskim Tracy uruchamia sesję, ponieważ używa jej do wyświetlania pasków przekierowań i żądań AJAX w pasku Tracy. +W trybie deweloperskim sesję uruchamia Tracy, ponieważ używa jej do wyświetlania pasków z przekierowaniami i żądaniami AJAX w Tracy Bar. -Sekcja .[#toc-section] -====================== +Sekcje +====== -W czystym PHP, magazyn danych sesji jest zaimplementowany jako tablica dostępna poprzez zmienną globalną `$_SESSION`. Problem polega na tym, że aplikacje zwykle składają się z wielu niezależnych części, a jeśli wszystkie z nich mają tylko jedno dostępne pole, prędzej czy później nazwy będą kolidować. +W czystym PHP magazyn danych sesji jest realizowany jako tablica dostępna przez zmienną globalną `$_SESSION`. Problem polega na tym, że aplikacje zwykle składają się z wielu wzajemnie niezależnych części i jeśli wszystkie mają do dyspozycji tylko jedną tablicę, wcześniej czy później dojdzie do kolizji nazw. -Nette Framework rozwiązuje ten problem dzieląc całą przestrzeń na sekcje ( [api:Nette\Http\SessionSection] obiekty ). Każda jednostka korzysta wtedy z własnej sekcji o unikalnej nazwie i nie może już dojść do żadnych kolizji. +Nette Framework rozwiązuje problem, dzieląc całą przestrzeń na sekcje (obiekty [api:Nette\Http\SessionSection]). Każda jednostka używa następnie swojej sekcji z unikalną nazwą i do żadnej kolizji już dojść nie może. -Odcinek jest pozyskiwany z sesji: +Sekcję uzyskujemy z sesji: ```php -$section = $session->getSection('unikatni nazev'); +$section = $session->getSection('unikalna nazwa'); ``` W prezenterze wystarczy użyć `getSession()` z parametrem: ```php -// $this jest prezenterem -$section = $this->getSession('unikatni nazev'); +// $this to Presenter +$section = $this->getSession('unikalna nazwa'); ``` -Aby sprawdzić istnienie odcinka, należy użyć metody `$session->hasSection('unikatni nazev')`. +Sprawdzić istnienie sekcji można metodą `$session->hasSection('unikalna nazwa')`. -Sama sekcja jest następnie bardzo łatwa do pracy z wykorzystaniem metod `set()`, `get()` i `remove()`: +Z samą sekcją pracuje się następnie bardzo łatwo za pomocą metod `set()`, `get()` i `remove()`: ```php -// pisanie zmiennych +// zapis zmiennej $section->set('userName', 'franta'); -// odczytuje zmienną, zwraca null jeśli nie istnieje +// odczyt zmiennej, zwraca null, jeśli nie istnieje echo $section->get('userName'); -// anulowanie zmiennej +// usunięcie zmiennej $section->remove('userName'); ``` -Możesz użyć pętli `foreach`, aby uzyskać wszystkie zmienne z sekcji: +Aby uzyskać wszystkie zmienne z sekcji, można użyć pętli `foreach`: ```php foreach ($section as $key => $val) { @@ -74,63 +74,63 @@ foreach ($section as $key => $val) { ``` -Ustawienia wygaśnięcia .[#toc-how-to-set-expiration] ----------------------------------------------------- +Ustawienie wygaśnięcia +---------------------- -Możliwe jest ustawienie wygasania dla poszczególnych sekcji lub nawet poszczególnych zmiennych. W ten sposób możemy pozwolić, aby login użytkownika wygasł za 20 minut, ale nadal pamiętał zawartość koszyka. +Dla poszczególnych sekcji lub nawet poszczególnych zmiennych można ustawić wygaśnięcie. Możemy w ten sposób pozwolić na wygaśnięcie logowania użytkownika po 20 minutach, ale jednocześnie nadal pamiętać zawartość koszyka. ```php -// sekcja wygasa po 20 minutach +// sekcja wygaśnie po 20 minutach $section->setExpiration('20 minutes'); ``` -Trzeci parametr metody `set()` służy do ustawienia wygasania poszczególnych zmiennych: +Do ustawienia wygaśnięcia poszczególnych zmiennych służy trzeci parametr metody `set()`: ```php -// zmienna 'flash' wygasa po 30 sekundach -$section->set('flash', $message, '30 sekund'); +// zmienna 'flash' wygaśnie już po 30 sekundach +$section->set('flash', $message, '30 seconds'); ``` .[note] -Pamiętaj, że czas wygaśnięcia całej sesji (patrz [konfiguracja |configuration#Session] sesji) musi być równy lub wyższy niż czas wygaśnięcia ustawiony dla poszczególnych sekcji lub zmiennych. +Pamiętaj, że czas wygaśnięcia całej sesji (zobacz [konfiguracja sesji |configuration#Sesja]) musi być taki sam lub dłuższy niż czas ustawiony dla poszczególnych sekcji lub zmiennych. -Anulowanie wcześniej ustawionego wygaśnięcia uzyskuje się za pomocą metody `removeExpiration()` Metoda `remove()` zapewnia natychmiastowe anulowanie całego odcinka. +Anulowanie wcześniej ustawionego wygaśnięcia uzyskamy metodą `removeExpiration()`. Natychmiastowe anulowanie całej sekcji zapewni metoda `remove()`. -Zdarzenia $onStart, $onBeforeWrite .[#toc-events-onstart-onbeforewrite] ------------------------------------------------------------------------ +Zdarzenia $onStart, $onBeforeWrite +---------------------------------- -Obiekt `Nette\Http\Session` posiada [zdarzenia |nette:glossary#Events] `$onStart` i `$onBeforeWrite`, więc można dodać wywołania zwrotne, które odpalają się po rozpoczęciu sesji lub przed jej zapisaniem na dysk, a następnie zakończeniem. +Obiekt `Nette\Http\Session` ma [zdarzenia |nette:glossary#Eventy zdarzenia] `$onStart` i `$onBeforeWrite`, możesz więc dodać callbacki, które zostaną wywołane po starcie sesji lub przed jej zapisem na dysk i późniejszym zakończeniem. ```php $session->onBeforeWrite[] = function () { - // zapiszemy dane do sesji + // zapisujemy dane do sesji $this->section->set('basket', $this->basket); }; ``` -Zarządzanie sesją .[#toc-session-management] -============================================ +Zarządzanie sesją +================= -Przegląd metod klasy `Nette\Http\Session` służących do zarządzania sesjami: +Przegląd metod klasy `Nette\Http\Session` do zarządzania sesją: <div class=wiki-methods-brief> start(): void .[method] ----------------------- -Otworzy on sesję. +Rozpoczyna sesję. isStarted(): bool .[method] --------------------------- -Czy sesja została rozpoczęta? +Czy sesja jest rozpoczęta? close(): void .[method] ----------------------- -Kończy sesję. Sesja jest automatycznie kończona po zakończeniu działania skryptu. +Kończy sesję. Sesja jest automatycznie kończona na końcu działania skryptu. destroy(): void .[method] @@ -140,71 +140,72 @@ Kończy i usuwa sesję. exists(): bool .[method] ------------------------ -Czy żądanie HTTP zawiera plik cookie z identyfikatorem sesji? +Czy żądanie HTTP zawiera ciasteczko z ID sesji? regenerateId(): void .[method] ------------------------------ -Generuje nowy losowy identyfikator sesji. Dane są zachowane. +Generuje nowy losowy ID sesji. Dane pozostają zachowane. getId(): string .[method] ------------------------- -Zwraca identyfikator sesji. +Zwraca ID sesji. </div> -Konfiguracja .[#toc-configuration] ----------------------------------- +Konfiguracja +------------ -Sesja jest ustawiona w [konfiguracji |configuration#Session]. Jeśli piszesz aplikację, która nie korzysta z kontenera DI, do jej konfiguracji służą następujące metody. Muszą one zostać wywołane przed rozpoczęciem sesji. +Sesję ustawiamy w [konfiguracji |configuration#Sesja]. Jeśli piszesz aplikację, która nie używa kontenera DI, do konfiguracji służą te metody. Muszą być wywołane jeszcze przed uruchomieniem sesji. <div class=wiki-methods-brief> setName(string $name): static .[method] --------------------------------------- -Ustawia nazwę pliku cookie, w którym przekazywany jest identyfikator sesji. Domyślna nazwa to `PHPSESSID`. Jest to przydatne, jeśli w ramach tej samej witryny uruchamiasz kilka różnych aplikacji. +Ustawia nazwę ciasteczka, w którym przekazywane jest ID sesji. Standardowa nazwa to `PHPSESSID`. Przydatne, gdy w ramach jednej strony internetowej działa kilka różnych aplikacji. getName(): string .[method] --------------------------- -Zwraca nazwę pliku cookie, w którym przekazywany jest identyfikator sesji. +Zwraca nazwę ciasteczka, w którym przekazywane jest ID sesji. setOptions(array $options): static .[method] -------------------------------------------- -Konfiguruje sesję. Można ustawić wszystkie [dyrektywy |https://www.php.net/manual/en/session.configuration.php] sesji PHP (w formacie camelCase, np. zamiast `session.save_path` wpisz `savePath`), a także [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. +Konfiguruje sesję. Można ustawiać wszystkie [dyrektywy sesji |https://www.php.net/manual/en/session.configuration.php] PHP (w formacie camelCase, np. zamiast `session.save_path` zapisujemy `savePath`) oraz [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. setExpiration(?string $time): static .[method] ---------------------------------------------- -Ustawia okres bezczynności, po którym sesja wygasa. +Ustawia czas nieaktywności, po którym sesja wygaśnie. -setCookieParameters(string $path, string $domain=null, bool $secure=null, string $samesite=null): static .[method] ------------------------------------------------------------------------------------------------------------------- -Ustawia parametry dla pliku cookie. Można zmienić domyślne wartości parametrów w [konfiguracji |configuration#Session-Cookie]. +setCookieParameters(string $path, ?string $domain=null, ?bool $secure=null, ?string $samesite=null): static .[method] +--------------------------------------------------------------------------------------------------------------------- +Ustawienie parametrów dla ciasteczka. Domyślne wartości parametrów można zmienić w [konfiguracji |configuration#Ciasteczko sesji]. setSavePath(string $path): static .[method] ------------------------------------------- -Ustawia katalog, w którym przechowywane są pliki sesji. +Ustawia katalog, w którym zapisywane są pliki sesji. setHandler(\SessionHandlerInterface $handler): static .[method] --------------------------------------------------------------- -Ustaw niestandardowy handler, zobacz [dokumentację PHP |https://www.php.net/manual/en/class.sessionhandlerinterface.php]. +Ustawienie własnego handlera, zobacz [dokumentację PHP|https://www.php.net/manual/en/class.sessionhandlerinterface.php]. </div> -W szczególności bezpieczeństwo .[#toc-safety-first] -=================================================== +Bezpieczeństwo przede wszystkim +=============================== -Serwer zakłada, że wciąż komunikuje się z tym samym użytkownikiem, dopóki żądaniom towarzyszy ten sam identyfikator sesji. Zadaniem mechanizmów bezpieczeństwa jest zapewnienie, że tak rzeczywiście jest i że identyfikator sesji nie może zostać skradziony lub sfałszowany. +Serwer zakłada, że komunikuje się ciągle z tym samym użytkownikiem, dopóki żądania towarzyszy ten sam ID sesji. Zadaniem mechanizmów bezpieczeństwa jest zapewnienie, aby tak rzeczywiście było i nie było możliwe kradzież lub podstawienie identyfikatora. -Dlatego Nette Framework odpowiednio konfiguruje dyrektywy PHP, aby przekazywały identyfikator sesji tylko w ciasteczku, czyniły go niedostępnym dla JavaScript i ignorowały wszelkie identyfikatory w URL. Ponadto w krytycznych momentach, takich jak logowanie się użytkownika, wygeneruje nowy identyfikator sesji. +Nette Framework dlatego poprawnie konfiguruje dyrektywy PHP, aby ID sesji przekazywał tylko w ciasteczku, uniemożliwił dostęp do niego JavaScriptowi i ignorował ewentualne identyfikatory w URL. Ponadto w krytycznych momentach, jak na przykład logowanie użytkownika, generuje nowy ID sesji. -Do konfiguracji PHP służy funkcja ini_set, która niestety jest wyłączona przez niektóre hosty. Jeśli tak jest również w przypadku twojego hosta, spróbuj porozmawiać z nimi, aby włączyć tę funkcję lub przynajmniej skonfigurować serwer dla ciebie. .[note] +.[note] +Do konfiguracji PHP używana jest funkcja ini_set, której niestety niektóre hostingi zabraniają. Jeśli jest to przypadek również Twojego hostingu, spróbuj się z nim dogadać, aby pozwolił Ci na użycie funkcji lub przynajmniej skonfigurował serwer. diff --git a/http/pl/urls.texy b/http/pl/urls.texy index 595a64b01b..c2d29b6fcd 100644 --- a/http/pl/urls.texy +++ b/http/pl/urls.texy @@ -1,16 +1,16 @@ -Parsowanie i składanie adresów URL -********************************** +Praca z adresami URL +******************** .[perex] -Klasy [Url |#Url], [UrlImmutable |#UrlImmutable] i [UrlScript |#UrlScript] ułatwiają generowanie, przetwarzanie i manipulowanie adresami URL. +Klasy [#Url], [#UrlImmutable] i [#UrlScript] umożliwiają łatwe generowanie, parsowanie i manipulowanie adresami URL. -→ [Instalacja i wymagania |@home#Installation] +→ [Instalacja i wymagania |@home#Instalacja] Url === -Klasa [api:Nette\Http\Url] ułatwia pracę z adresem URL i jego poszczególnymi składnikami, jak pokazano na tym schemacie: +Klasa [api:Nette\Http\Url] umożliwia łatwą pracę z adresami URL i ich poszczególnymi komponentami, które przedstawia ten schemat: /--pre scheme user password host port path query fragment @@ -22,7 +22,7 @@ Klasa [api:Nette\Http\Url] ułatwia pracę z adresem URL i jego poszczególnymi hostUrl authority \-- -Generowanie adresów URL jest intuicyjne: +Generowanie URL jest intuicyjne: ```php use Nette\Http\Url; @@ -36,7 +36,7 @@ $url->setScheme('https') echo $url; // 'https://localhost/edit?foo=bar' ``` -Możesz również parsować adres URL i manipulować nim dalej: +Można również sparsować URL i dalej nim manipulować: ```php $url = new Url( @@ -44,62 +44,94 @@ $url = new Url( ); ``` -Aby zwrócić lub zmienić poszczególne składniki adresu URL, dostępne są następujące metody: +Klasa `Url` implementuje interfejs `JsonSerializable` i ma metodę `__toString()`, więc obiekt można wypisać lub użyć w danych przekazywanych do `json_encode()`. + +```php +echo $url; +echo json_encode([$url]); +``` + + +Komponenty URL .[method] +------------------------ + +Do zwracania lub zmiany poszczególnych komponentów URL dostępne są następujące metody: .[language-php] -| Setter | Getter | Wartość zwrotna +| Setter | Getter | Zwracana wartość |-------------------------------------------------------------------------------------------- -| `setScheme(string $scheme)`| `getScheme(): string`| `'http'` -| `setUser(string $user)`| `getUser(): string`| `'john'` -| `setPassword(string $password)`| `getPassword(): string`| `'xyz*12'` -| `setHost(string $host)`| `getHost(): string`| `'nette.org'` -| `setPort(int $port)`| `getPort(): ?int`| `8080` -| | `getDefaultPort(): ?int`| `80` -| `setPath(string $path)`| `getPath(): string`| `'/en/download'` -| `setQuery(string\|array $query)`| `getQuery(): string`| `'name=param'` -| `setFragment(string $fragment)`| `getFragment(): string`| `'footer'` -| | `getAuthority(): string`| `'nette.org:8080'` -| | `getHostUrl(): string`| `'http://nette.org:8080'` -| | `getAbsoluteUrl(): string` | pełny adres URL +| `setScheme(string $scheme)` | `getScheme(): string` | `'http'` +| `setUser(string $user)` | `getUser(): string` | `'john'` +| `setPassword(string $password)` | `getPassword(): string` | `'xyz*12'` +| `setHost(string $host)` | `getHost(): string` | `'nette.org'` +| `setPort(int $port)` | `getPort(): ?int` | `8080` +| | `getDefaultPort(): ?int` | `80` +| `setPath(string $path)` | `getPath(): string` | `'/en/download'` +| `setQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` +| `setFragment(string $fragment)` | `getFragment(): string` | `'footer'` +| | `getAuthority(): string` | `'john:xyz%2A12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | cały URL + +Uwaga: Pracując z adresem URL uzyskanym z [żądania HTTP|request], pamiętaj, że nie będzie on zawierał fragmentu, ponieważ przeglądarka go nie wysyła na serwer. Możemy również pracować z poszczególnymi parametrami zapytania za pomocą: .[language-php] -| Setter | Getter +| Setter | Getter |--------------------------------------------------- -| `setQuery(string\|array $query)` | `getQueryParameters(): array` -| `setQueryParameter(string $name, $val)`| `getQueryParameter(string $name)` +| `setQuery(string\|array $query)` | `getQueryParameters(): array` +| `setQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` -Metoda `getDomain(int $level = 2)` zwraca prawą lub lewą część hosta. Działa to, jeśli host jest `www.nette.org`: + +getDomain(int $level = 2): string .[method] +------------------------------------------- +Zwraca prawą lub lewą część hosta. Działa w ten sposób, jeśli host to `www.nette.org`: .[language-php] -| `getDomain(1)` | `'org'` -| `getDomain(2)` | `'nette.org'` -| `getDomain(3)` | `'www.nette.org'` -| `getDomain(0)` | `'www.nette.org'` -| `getDomain(-1)` | `'www.nette'` -| `getDomain(-2)` | `'www'` -| `getDomain(-3)` | `''` +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `''` -Klasa `Url` implementuje interfejs `JsonSerializable` i posiada metodę `__toString()`, dzięki czemu obiekt może zostać zrzucony lub wykorzystany w danych przekazywanych do `json_encode()`. +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +Sprawdza, czy dwa adresy URL są identyczne. ```php -echo $url; -echo json_encode([$url]); +$url->isEqual('https://nette.org'); ``` -Metoda `isEqual(string|Url $anotherUrl): bool` sprawdza, czy dwa adresy URL są takie same. + +Url::isAbsolute(string $url): bool .[method]{data-version:3.3.2} +---------------------------------------------------------------- +Sprawdza, czy adres URL jest absolutny. Adres URL jest uważany za absolutny, jeśli zaczyna się od schematu (np. http, https, ftp) poprzedzonego dwukropkiem. ```php -$url->isEqual('https://nette.org'); +Url::isAbsolute('https://nette.org'); // true +Url::isAbsolute('//nette.org'); // false +``` + + +Url::removeDotSegments(string $path): string .[method]{data-version:3.3.2} +-------------------------------------------------------------------------- +Normalizuje ścieżkę w adresie URL, usuwając specjalne segmenty `.` i `..`. Metoda usuwa zbędne elementy ścieżki w taki sam sposób, jak robią to przeglądarki internetowe. + +```php +Url::removeDotSegments('/path/../subtree/./file.txt'); // '/subtree/file.txt' +Url::removeDotSegments('/../foo/./bar'); // '/foo/bar' +Url::removeDotSegments('./today/../file.txt'); // 'file.txt' ``` -UrlImmutable .[#toc-urlimmutable] -================================= +UrlImmutable +============ -Klasa [api:Nette\Http\UrlImmutable] jest niezmienną (immutowalną) alternatywą dla klasy `Url` (podobnie jak `DateTimeImmutable` jest niezmienną alternatywą dla `DateTime` w PHP ). Zamiast setterów posiada withery, które nie modyfikują obiektu, ale zwracają nowe instancje ze zmodyfikowaną wartością: +Klasa [api:Nette\Http\UrlImmutable] jest immutable (niezmienną) alternatywą klasy [#Url] (podobnie jak w PHP `DateTimeImmutable` jest niezmienną alternatywą `DateTime`). Zamiast setterów ma tzw. withery, które nie zmieniają obiektu, ale zwracają nowe instancje ze zmodyfikowaną wartością: ```php use Nette\Http\UrlImmutable; @@ -111,53 +143,96 @@ $url = new UrlImmutable( $newUrl = $url ->withUser('') ->withPassword('') - ->withPath('/en/'); + ->withPath('/cs/'); -echo $newUrl; // 'http://nette.org:8080/en/?name=param#footer' +echo $newUrl; // 'http://john:xyz%2A12@nette.org:8080/cs/?name=param#footer' ``` -Metody służą do zwracania lub zmiany poszczególnych elementów adresu URL: +Klasa `UrlImmutable` implementuje interfejs `JsonSerializable` i ma metodę `__toString()`, więc obiekt można wypisać lub użyć w danych przekazywanych do `json_encode()`. + +```php +echo $url; +echo json_encode([$url]); +``` + + +Komponenty URL .[method] +------------------------ + +Do zwracania lub zmiany poszczególnych komponentów URL służą metody: .[language-php] -| Wither | Getter | Wartość zwrotna +| Wither | Getter | Zwracana wartość |-------------------------------------------------------------------------------------------- -| `withScheme(string $scheme)`| `getScheme(): string`| `'http'` -| `withUser(string $user)`| `getUser(): string`| `'john'` -| `withPassword(string $password)`| `getPassword(): string`| `'xyz*12'` -| `withHost(string $host)`| `getHost(): string`| `'nette.org'` -| `withPort(int $port)`| `getPort(): ?int`| `8080` -| | `getDefaultPort(): ?int`| `80` -| `withPath(string $path)`| `getPath(): string`| `'/en/download'` -| `withQuery(string\|array $query)`| `getQuery(): string`| `'name=param'` -| `withFragment(string $fragment)`| `getFragment(): string`| `'footer'` -| | `getAuthority(): string`| `'nette.org:8080'` -| | `getHostUrl(): string`| `'http://nette.org:8080'` -| | `getAbsoluteUrl(): string` | pełny adres URL +| `withScheme(string $scheme)` | `getScheme(): string` | `'http'` +| `withUser(string $user)` | `getUser(): string` | `'john'` +| `withPassword(string $password)` | `getPassword(): string` | `'xyz*12'` +| `withHost(string $host)` | `getHost(): string` | `'nette.org'` +| `withPort(int $port)` | `getPort(): ?int` | `8080` +| | `getDefaultPort(): ?int` | `80` +| `withPath(string $path)` | `getPath(): string` | `'/en/download'` +| `withQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` +| `withFragment(string $fragment)` | `getFragment(): string` | `'footer'` +| | `getAuthority(): string` | `'john:xyz%2A12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | cały URL + +Metoda `withoutUserInfo()` usuwa `user` i `password`. Możemy również pracować z poszczególnymi parametrami zapytania za pomocą: .[language-php] -| Wither | Getter +| Wither | Getter |----------------------------------------------- -| `withQuery(string\|array $query)` | `getQueryParameters(): array` -| `withQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` +| `withQuery(string\|array $query)` | `getQueryParameters(): array` +| `withQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` -Metoda `getDomain(int $level = 2)` działa tak samo jak jej imienniczka klasa `Url`. Metoda `withoutUserInfo()` usuwa `user` i `password`. -Klasa `UrlImmutable` implementuje interfejs `JsonSerializable` i posiada metodę `__toString()`, dzięki czemu obiekt może zostać zrzucony lub wykorzystany w danych przekazywanych do `json_encode()`. +getDomain(int $level = 2): string .[method] +------------------------------------------- +Zwraca prawą lub lewą część hosta. Działa w ten sposób, jeśli host to `www.nette.org`: + +.[language-php] +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `''` + + +resolve(string $reference): UrlImmutable .[method]{data-version:3.3.2} +---------------------------------------------------------------------- +Wyprowadza absolutny adres URL w taki sam sposób, w jaki przeglądarka przetwarza linki na stronie HTML: +- jeśli link jest absolutnym adresem URL (zawiera schemat), jest używany bez zmian +- jeśli link zaczyna się od `//`, pobierany jest tylko schemat z bieżącego adresu URL +- jeśli link zaczyna się od `/`, tworzona jest ścieżka absolutna od korzenia domeny +- w pozostałych przypadkach adres URL jest budowany względnie do bieżącej ścieżki ```php -echo $url; -echo json_encode([$url]); +$url = new UrlImmutable('https://example.com/path/page'); +echo $url->resolve('../foo'); // 'https://example.com/foo' +echo $url->resolve('/bar'); // 'https://example.com/bar' +echo $url->resolve('sub/page.html'); // 'https://example.com/path/sub/page.html' ``` -Metoda `isEqual(string|Url $anotherUrl): bool` sprawdza, czy dwa adresy URL są takie same. +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +Sprawdza, czy dwa adresy URL są identyczne. -UrlScript .[#toc-urlscript] -=========================== +```php +$url->isEqual('https://nette.org'); +``` -Klasa [api:Nette\Http\UrlScript] jest potomkiem klasy `UrlImmutable` i dodatkowo wyróżnia następujące inne logiczne części adresu URL: + +UrlScript +========= + +Klasa [api:Nette\Http\UrlScript] jest potomkiem [#UrlImmutable] i rozszerza go o dodatkowe wirtualne komponenty URL, takie jak katalog główny projektu itp. Podobnie jak klasa macierzysta, jest obiektem immutable (niezmiennym). + +Poniższy diagram przedstawia komponenty, które UrlScript rozpoznaje: /--pre baseUrl basePath relativePath relativeUrl @@ -169,16 +244,23 @@ Klasa [api:Nette\Http\UrlScript] jest potomkiem klasy `UrlImmutable` i dodatkowo scriptPath pathInfo \-- -Udostępniane są metody pozwalające na zwrócenie części adresu URL: +- `baseUrl` to podstawowy adres URL aplikacji, w tym domena i część ścieżki do katalogu głównego aplikacji +- `basePath` to część ścieżki do katalogu głównego aplikacji +- `scriptPath` to ścieżka do bieżącego skryptu +- `relativePath` to nazwa skryptu (ewentualnie dodatkowe segmenty ścieżki) względna do `basePath` +- `relativeUrl` to cała część adresu URL za `baseUrl`, w tym query string i fragment. +- `pathInfo` to dziś rzadko używana część adresu URL za nazwą skryptu + +Do zwracania części URL dostępne są metody: .[language-php] -| Getter | Wartość zwrotna +| Getter | Zwracana wartość |------------------------------------------------ -| `getScriptPath(): string`| `'/admin/script.php'` -| `getBasePath(): string`| `'/admin/'` -| `getBaseUrl(): string`| `'http://nette.org/admin/'` -| `getRelativePath(): string`| `'script.php'` -| `getRelativeUrl(): string`| `'script.php/pathinfo/?name=param#footer'` -| `getPathInfo(): string`| `'/pathinfo/'` - -Nie tworzymy bezpośrednio obiektów `UrlScript`, ale jest on zwracany przez metodę [Nette\Http\Request::getUrl() |request]. +| `getScriptPath(): string` | `'/admin/script.php'` +| `getBasePath(): string` | `'/admin/'` +| `getBaseUrl(): string` | `'http://nette.org/admin/'` +| `getRelativePath(): string` | `'script.php'` +| `getRelativeUrl(): string` | `'script.php/pathinfo/?name=param#footer'` +| `getPathInfo(): string` | `'/pathinfo/'` + +Obiekty `UrlScript` zwykle nie tworzymy bezpośrednio, ale zwraca je metoda [Nette\Http\Request::getUrl()|request] z już poprawnie ustawionymi komponentami dla bieżącego żądania HTTP. diff --git a/http/pt/@home.texy b/http/pt/@home.texy index b3fbe99568..6921df5499 100644 --- a/http/pt/@home.texy +++ b/http/pt/@home.texy @@ -2,13 +2,13 @@ Nette HTTP ********** .[perex] -O pacote `nette/http` encapsula o [pedido |request] e [resposta |response] [HTTP |request], trabalhando com [sessões |sessions] e [análise e construção de URLs |urls]. +O pacote `nette/http` encapsula a [requisição HTTP|request] & [resposta HTTP|response], trabalho com [sessões|sessions] e [análise e composição de URLs |urls]. -Instalação .[#toc-installation] -------------------------------- +Instalação +---------- -Baixe e instale o pacote usando [o Composer |best-practices:composer]: +Faça o download e instale a biblioteca usando a ferramenta [Composer|best-practices:composer]: ```shell composer require nette/http diff --git a/http/pt/@left-menu.texy b/http/pt/@left-menu.texy index 4add07dd9c..f20249494e 100644 --- a/http/pt/@left-menu.texy +++ b/http/pt/@left-menu.texy @@ -1,8 +1,8 @@ -Rede HTTP -********* -- [Visão geral |@home] -- [Solicitação HTTP |request] -- [Resposta HTTP |response] -- [Sessões |Sessions] -- [Utilitário URL |urls] -- [Configuração |Configuration] +Nette HTTP +********** +- [Introdução |@home] +- [Requisição HTTP|request] +- [Resposta HTTP|response] +- [Sessões|Sessions] +- [Utilitários de URL |urls] +- [Configuração |configuration] diff --git a/http/pt/@meta.texy b/http/pt/@meta.texy new file mode 100644 index 0000000000..41a853b6aa --- /dev/null +++ b/http/pt/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentação Nette}} diff --git a/http/pt/configuration.texy b/http/pt/configuration.texy index 11b8f5efbf..e13f7301c1 100644 --- a/http/pt/configuration.texy +++ b/http/pt/configuration.texy @@ -1,61 +1,61 @@ -Configurando o HTTP -******************* +Configuração HTTP +***************** .[perex] -Visão geral das opções de configuração para o Nette HTTP. +Visão geral das opções de configuração para Nette HTTP. -Se você não estiver usando toda a estrutura, mas apenas esta biblioteca, leia [como carregar a configuração |bootstrap:]. +Se você não usa o framework inteiro, mas apenas esta biblioteca, leia [como carregar a configuração|bootstrap:]. -Cabeçalhos HTTP .[#toc-http-headers] -==================================== +Cabeçalhos HTTP +=============== ```neon http: - # cabeçalhos que são enviados com cada pedido + # cabeçalhos que são enviados com cada requisição headers: X-Powered-By: MyCMS X-Content-Type-Options: nosniff X-XSS-Protection: '1; mode=block' # afeta o cabeçalho X-Frame-Options - frames: ... # (string|bool) padrão de 'SAMEORIGIN' + frames: ... # (string|bool) padrão é 'SAMEORIGIN' ``` -Por razões de segurança, a estrutura envia um cabeçalho `X-Frame-Options: SAMEORIGIN`, que diz que uma página pode ser exibida dentro de outra página (no elemento `<iframe>`) somente se estiver no mesmo domínio. Isto pode ser indesejado em certas situações (por exemplo, se você estiver desenvolvendo um aplicativo do Facebook), portanto, o comportamento pode ser alterado através da definição de quadros `frames: http://allowed-host.com`. +O framework, por razões de segurança, envia o cabeçalho `X-Frame-Options: SAMEORIGIN`, que diz que a página só pode ser exibida dentro de outra página (no elemento `<iframe>`) se estiver no mesmo domínio. Isso pode ser indesejável em algumas situações (por exemplo, se você estiver desenvolvendo uma aplicação para o Facebook), o comportamento pode, portanto, ser alterado definindo `frames: http://allowed-host.com` ou `frames: true`. -Política de Segurança de Conteúdo .[#toc-content-security-policy] ------------------------------------------------------------------ +Content Security Policy +----------------------- -Os cabeçalhos `Content-Security-Policy` (doravante denominados CSP) podem ser facilmente montados, sua descrição pode ser encontrada na [descrição do CSP |https://content-security-policy.com]. As diretrizes CSP (tais como `script-src`) podem ser escritas como cordas de acordo com a especificação ou como matrizes de valores para melhor legibilidade. Então não há necessidade de escrever aspas em torno de palavras-chave, como `'self'`. A Nette também gerará automaticamente um valor de `nonce`, portanto `'nonce-y4PopTLM=='` será enviado no cabeçalho. +É fácil construir cabeçalhos `Content-Security-Policy` (doravante CSP), sua descrição pode ser encontrada na [descrição do CSP |https://content-security-policy.com]. As diretivas CSP (como `script-src`) podem ser escritas como strings de acordo com a especificação, ou como um array de valores para melhor legibilidade. Então não é necessário colocar aspas em torno de palavras-chave, como `'self'`. Nette também gera automaticamente o valor `nonce`, então no cabeçalho haverá, por exemplo, `'nonce-y4PopTLM=='`. ```neon http: - # Política de Segurança de Conteúdo + # Content Security Policy csp: - # string de acordo com a especificação CSP + # string no formato de acordo com a especificação CSP default-src: "'self' https://example.com" - # matriz de valores + # array de valores script-src: - nonce - strict-dynamic - self - https://example.com - # bool no caso de interruptores + # bool no caso de flags upgrade-insecure-requests: true block-all-mixed-content: false ``` -Use `<script n:nonce>...</script>` nos gabaritos e o valor nonce será preenchido automaticamente. Fazer sites seguros em Nette é realmente fácil. +Nos templates, use `<script n:nonce>...</script>` e o valor nonce será preenchido automaticamente. Criar sites seguros em Nette é realmente fácil. -Da mesma forma, os cabeçalhos `Content-Security-Policy-Report-Only` (que podem ser usados em paralelo com o CSP) e a [Política de Recursos |https://developers.google.com/web/updates/2018/06/feature-policy] podem ser acrescentados: +Da mesma forma, é possível construir os cabeçalhos `Content-Security-Policy-Report-Only` (que podem ser usados ​​simultaneamente com CSP) e [Feature Policy|https://developers.google.com/web/updates/2018/06/feature-policy]: ```neon http: - # Relatório de Política de Segurança de Conteúdo - Somente + # Content Security Policy Report-Only cspReportOnly: default-src: self report-uri: 'https://my-report-uri-endpoint' @@ -69,91 +69,103 @@ http: ``` -Cookie HTTP .[#toc-http-cookie] -------------------------------- +Cookie HTTP +----------- -Você pode alterar os valores padrão de alguns parâmetros do [Nette\HttpResposta::setCookie() |response#setCookie] e métodos de sessão. +É possível alterar os valores padrão de alguns parâmetros do método [Nette\Http\Response::setCookie() |response#setCookie] e da sessão. ```neon http: - # escopo do cookie por caminho - cookiePath: ... # (cordão) padrão de '/' + # escopo do cookie pelo caminho + cookiePath: ... # (string) padrão é '/' - # que hospeda é permitido receber o cookie - cookieDomain: 'example.com' # (string|domain) default to unset + # domínios que aceitam o cookie + cookieDomain: 'example.com' # (string|domain) padrão é não definido - # para enviar cookies somente via HTTPS? - cookieSecure: ... # (bool|auto) padrão para auto + # enviar cookie apenas via HTTPS? + cookieSecure: ... # (bool|auto) padrão é auto - # desativa o envio do cookie que a Nette utiliza como proteção contra o CSRF - disableNetteCookie: ... # (bool) falha em false + # desativa o envio do cookie que o Nette usa como proteção contra CSRF + disableNetteCookie: ... # (bool) padrão é false ``` -A opção `cookieDomain` determina quais domínios (origens) podem aceitar cookies. Se não especificado, o cookie é aceito pelo mesmo (sub)domínio que é definido por ele, *excluindo* seus subdomínios. Se `cookieDomain` for especificado, então os subdomínios também serão incluídos. Portanto, a especificação do `cookieDomain` é menos restritiva do que a omissão. +O atributo `cookieDomain` determina quais domínios podem aceitar o cookie. Se não for especificado, o cookie é aceito pelo mesmo (sub)domínio que o definiu, *mas não* por seus subdomínios. Se `cookieDomain` for especificado, os subdomínios também são incluídos. Portanto, especificar `cookieDomain` é menos restritivo do que omiti-lo. -Por exemplo, se `cookieDomain: nette.org` estiver definido, o cookie também está disponível em todos os subdomínios como `doc.nette.org`. Isto também pode ser conseguido com o valor especial `domain`, ou seja, `cookieDomain: domain`. +Por exemplo, com `cookieDomain: nette.org`, os cookies também estão disponíveis em todos os subdomínios como `doc.nette.org`. O mesmo pode ser alcançado usando o valor especial `domain`, ou seja, `cookieDomain: domain`. -O valor padrão de `cookieSecure` é `auto` o que significa que se o site estiver rodando em HTTPS, os cookies serão enviados com a bandeira `Secure` e, portanto, só estarão disponíveis via HTTPS. +O valor padrão `auto` para o atributo `cookieSecure` significa que, se o site estiver rodando em HTTPS, os cookies serão enviados com o sinalizador `Secure` e, portanto, estarão disponíveis apenas via HTTPS. -Proxy HTTP .[#toc-http-proxy] ------------------------------ +Proxy HTTP +---------- -Se o site estiver rodando atrás de um proxy HTTP, digite o endereço IP do proxy para que a detecção de conexões HTTPS funcione corretamente, assim como o endereço IP do cliente. Ou seja, para que [Nette\Http\Request::getRemoteAddress() |request#getRemoteAddress] e [isSecured() |request#isSecured] retornem os valores corretos e os links sejam gerados com o protocolo `https:` nos templates. +Se o site estiver rodando atrás de um proxy HTTP, forneça seu endereço IP para que a detecção de conexão via HTTPS e também o endereço IP do cliente funcionem corretamente. Ou seja, para que as funções [Nette\Http\Request::getRemoteAddress() |request#getRemoteAddress] e [isSecured() |request#isSecured] retornem os valores corretos e nos templates sejam gerados links com o protocolo `https:`. ```neon http: - # endereço IP, faixa (ou seja, 127.0.0.1/8) ou matriz destes valores - proxy: 127.0.0.1 # (string|string[]) não tem padrão + # Endereço IP, intervalo (por exemplo, 127.0.0.1/8) ou array desses valores + proxy: 127.0.0.1 # (string|string[]) padrão é não definido ``` -Sessão .[#toc-session] -====================== +Sessão +====== -Configurações básicas das [sessões |sessions]: +Configurações básicas de [sessões|sessions]: ```neon session: - # mostra painel da sessão em Tracy Bar? - debugger: ... # (bool) falha em falso + # exibir painel de sessão na Tracy Bar? + debugger: ... # (bool) padrão é false # tempo de inatividade após o qual a sessão expira - expiration: 14 days # (string) por defeito a '3 horas'. + expiration: 14 days # (string) padrão é '3 hours' - # quando começar a sessão? - autoStart: ... # (smart|always|never) por padrão para 'smart'... + # quando a sessão deve ser iniciada? + autoStart: ... # (smart|always|never) padrão é 'smart' - # handler, serviço que implementa a interface SessionHandlerInterface + # handler, serviço implementando a interface SessionHandlerInterface handler: @handlerService ``` -A opção `autoStart` controla quando iniciar a sessão. O valor `always` significa que a sessão é sempre iniciada quando a aplicação é iniciada. O valor `smart` significa que a sessão será iniciada quando a aplicação começar somente se ela já existir, ou no momento em que quisermos ler ou escrever para ela. Finalmente, o valor `never` desabilita o início automático da sessão. +A opção `autoStart` controla quando a sessão deve ser iniciada. O valor `always` significa que a sessão será iniciada sempre com o início da aplicação. O valor `smart` significa que a sessão será iniciada no início da aplicação apenas se já existir, ou no momento em que quisermos ler ou escrever nela. E, finalmente, o valor `never` proíbe o início automático da sessão. -Você também pode definir todas as [diretrizes de sessão |https://www.php.net/manual/en/session.configuration.php] PHP (em formato camelCase) e também [lerAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. Exemplo: +Além disso, é possível definir todas as [diretivas de sessão |https://www.php.net/manual/en/session.configuration.php] do PHP (no formato camelCase) e também [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. Exemplo: ```neon session: - # 'session.name' escrito como 'name + # 'session.name' escrevemos como 'name' name: MYID - # 'session.save_path' escrito como 'savePath'. - savePath: "%tempDir%/sessões" + # 'session.save_path' escrevemos como 'savePath' + savePath: "%tempDir%/sessions" ``` -Sessão Cookie .[#toc-session-cookie] ------------------------------------- +Cookie de sessão +---------------- -O cookie da sessão é enviado com os mesmos parâmetros que os [outros cookies |#HTTP cookie], mas você pode alterá-los para ele: +O cookie de sessão é enviado com os mesmos parâmetros que [outros cookies |#Cookie HTTP], mas você pode alterá-los para ele: ```neon session: - # que hospeda é permitido receber o cookie - cookieDomain: 'example.com' # (string|domain) + # domínios que aceitam o cookie + cookieDomain: 'example.com' # (string|domain) - # restrições ao acessar pedidos de origem cruzada - cookieSamesite: ... # (Strict|Lax|None) tem como padrão Lax + # restrição ao acessar de outro domínio + cookieSamesite: None # (Strict|Lax|None) padrão é Lax ``` -A opção `cookieSamesite` afeta se o cookie é enviado com [pedidos de origem cruzada |nette:glossary#SameSite cookie], o que proporciona alguma proteção contra [falsificações em locais cruzados |nette:glossary#cross-site-request-forgery-csrf]. +O atributo `cookieSamesite` afeta se o cookie será enviado durante o [acesso de outro domínio |nette:glossary#SameSite cookie], o que fornece alguma proteção contra ataques [Cross-Site Request Forgery |nette:glossary#Cross-Site Request Forgery CSRF] (CSRF). + + +Serviços DI +=========== + +Estes serviços são adicionados ao contêiner de DI: + +| Nome | Tipo | Descrição +|----------------------------------------------------- +| `http.request` | [api:Nette\Http\Request] | [Requisição HTTP| request] +| `http.response` | [api:Nette\Http\Response] | [Resposta HTTP| response] +| `session.session` | [api:Nette\Http\Session] | [gerenciamento de sessão| sessions] diff --git a/http/pt/request.texy b/http/pt/request.texy index 7e36306737..e9b9fe6086 100644 --- a/http/pt/request.texy +++ b/http/pt/request.texy @@ -1,85 +1,85 @@ -Solicitação HTTP -**************** +Requisição HTTP +*************** .[perex] -Nette encapsula a solicitação HTTP em objetos com uma API compreensível enquanto fornece um filtro de higienização. +Nette encapsula a requisição HTTP em objetos com uma API compreensível e, ao mesmo tempo, fornece um filtro de sanitização. -Um pedido HTTP é um objeto [api:Nette\Http\Request], que você recebe passando-o usando a [injeção de dependência |dependency-injection:passing-dependencies]. Nos apresentadores, basta ligar para `$httpRequest = $this->getHttpRequest()`. +A requisição HTTP é representada pelo objeto [api:Nette\Http\Request]. Se trabalha com Nette, este objeto é criado automaticamente pelo framework e pode recebê-lo por meio de [injeção de dependência |dependency-injection:passing-dependencies]. Nos presenters, basta chamar o método `$this->getHttpRequest()`. Se trabalha fora do Nette Framework, pode criar o objeto usando [#RequestFactory]. -O importante é que Nette, ao [criar |#RequestFactory] este objeto, limpa todos os parâmetros de entrada GET, POST e COOKIE, assim como URLs de caracteres de controle e seqüências UTF-8 inválidas. Assim, você pode continuar trabalhando com os dados com segurança. Os dados limpos são então utilizados em apresentadores e formulários. +Uma grande vantagem de Nette é que, ao criar o objeto, ele limpa automaticamente todos os parâmetros de entrada GET, POST, COOKIE e também a URL de caracteres de controlo e sequências UTF-8 inválidas. Com esses dados, pode trabalhar com segurança. Os dados limpos são então usados em presenters e formulários. -→ [Instalação e requisitos |@home#Installation] +→ [Instalação e requisitos |@home#Instalação] -Nette\Http Solicitação .[#toc-nette-http-request] -================================================= +Nette\Http\Request +================== -Este objeto é imutável. Não tem setters, tem apenas um chamado wither `withUrl()`, que não muda o objeto, mas retorna uma nova instância com um valor modificado. +Este objeto é imutável. Não possui setters, tem apenas um chamado wither `withUrl()`, que não altera o objeto, mas retorna uma nova instância com o valor alterado. withUrl(Nette\Http\UrlScript $url): Nette\Http\Request .[method] ---------------------------------------------------------------- -Devolve um clone com uma URL diferente. +Retorna um clone com uma URL diferente. getUrl(): Nette\Http\UrlScript .[method] ---------------------------------------- -Retorna o URL do pedido como [UrlScript |urls#UrlScript] do objeto. +Retorna a URL da requisição como um objeto [UrlScript |urls#UrlScript]. ```php $url = $httpRequest->getUrl(); -echo $url; // https://nette.org/en/documentation?action=edit +echo $url; // https://doc.nette.org/cs/?action=edit echo $url->getHost(); // nette.org ``` -Os navegadores não enviam um fragmento para o servidor, portanto `$url->getFragment()` retornará uma cadeia vazia. +Aviso: os navegadores não enviam o fragmento para o servidor, então `$url->getFragment()` retornará uma string vazia. -getQuery(string $key=null): string|array|null .[method] -------------------------------------------------------- -Retorna os parâmetros de solicitação GET: +getQuery(?string $key=null): string|array|null .[method] +-------------------------------------------------------- +Retorna os parâmetros GET da requisição. ```php -$all = $httpRequest->getQuery(); // matriz de todos os parâmetros de URL -$id = $httpRequest->getQuery('id'); // retorna o parâmetro GET 'id' (ou nulo) +$all = $httpRequest->getQuery(); // retorna um array de todos os parâmetros da URL +$id = $httpRequest->getQuery('id'); // retorna o parâmetro GET 'id' (ou null) ``` -getPost(string $key=null): string|array|null .[method] ------------------------------------------------------- -Retorna os parâmetros de solicitação de PÓS-PST: +getPost(?string $key=null): string|array|null .[method] +------------------------------------------------------- +Retorna os parâmetros POST da requisição. ```php -$all = $httpRequest->getPost(); // matriz de todos os parâmetros POST -$id = $httpRequest->getPost('id'); // devolve o parâmetro 'id' do POST (ou nulo) +$all = $httpRequest->getPost(); // retorna um array de todos os parâmetros do POST +$id = $httpRequest->getPost('id'); // retorna o parâmetro POST 'id' (ou null) ``` getFile(string|string[] $key): Nette\Http\FileUpload|array|null .[method] ------------------------------------------------------------------------- -Devolve o [upload |#Uploaded Files] como objeto [api:Nette\Http\FileUpload]: +Retorna o [upload |#Upload de ficheiros] como um objeto [api:Nette\Http\FileUpload]: ```php $file = $httpRequest->getFile('avatar'); -if ($file->hasFile()) { // foi feito o upload de algum arquivo? - $file->getUntrustedName(); // nome do arquivo enviado pelo usuário - $file->getSanitizedName(); // o nome sem caracteres perigosos +if ($file?->hasFile()) { // algum ficheiro foi enviado? + $file->getUntrustedName(); // nome do ficheiro enviado pelo utilizador + $file->getSanitizedName(); // nome sem caracteres perigosos } ``` -Especifique um conjunto de chaves para acessar a estrutura da sub-árvore. +Para aceder à estrutura aninhada, forneça um array de chaves. ```php //<input type="file" name="my-form[details][avatar]" multiple> $file = $request->getFile(['my-form', 'details', 'avatar']); ``` -Como você não pode confiar nos dados do exterior e, portanto, não confia na forma da estrutura, este método é mais seguro do que `$request->getFiles()['my-form']['details']['avatar']`o que pode falhar. +Como não se pode confiar nos dados externos e, portanto, nem na forma da estrutura dos ficheiros, este método é mais seguro do que, por exemplo, `$request->getFiles()['my-form']['details']['avatar']`, que pode falhar. getFiles(): array .[method] --------------------------- -Retorna árvore de [arquivos de upload |#Uploaded Files] em uma estrutura normalizada, com cada folha uma instância de [api:Nette\Http\FileUpload]: +Retorna a árvore de [todos os uploads |#Upload de ficheiros] numa estrutura normalizada, cujas folhas são objetos [api:Nette\Http\FileUpload]: ```php $files = $httpRequest->getFiles(); @@ -88,7 +88,7 @@ $files = $httpRequest->getFiles(); getCookie(string $key): string|array|null .[method] --------------------------------------------------- -Devolve um cookie ou `null` se ele não existir. +Retorna um cookie ou `null` se não existir. ```php $sessId = $httpRequest->getCookie('sess_id'); @@ -97,7 +97,7 @@ $sessId = $httpRequest->getCookie('sess_id'); getCookies(): array .[method] ----------------------------- -Devolve todos os cookies: +Retorna todos os cookies. ```php $cookies = $httpRequest->getCookies(); @@ -106,16 +106,16 @@ $cookies = $httpRequest->getCookies(); getMethod(): string .[method] ----------------------------- -Retorna o método HTTP com o qual a solicitação foi feita. +Retorna o método HTTP com o qual a requisição foi feita. ```php -echo $httpRequest->getMethod(); // GET, POST, HEAD, PUT +$httpRequest->getMethod(); // GET, POST, HEAD, PUT ``` isMethod(string $method): bool .[method] ---------------------------------------- -Verifica o método HTTP com o qual a solicitação foi feita. O parâmetro não diferencia maiúsculas e minúsculas de minúsculas. +Testa o método HTTP com o qual a requisição foi feita. O parâmetro é insensível a maiúsculas/minúsculas. ```php if ($httpRequest->isMethod('GET')) // ... @@ -124,7 +124,7 @@ if ($httpRequest->isMethod('GET')) // ... getHeader(string $header): ?string .[method] -------------------------------------------- -Devolve um cabeçalho HTTP ou `null` se ele não existir. O parâmetro não diferencia maiúsculas e minúsculas de minúsculas: +Retorna um cabeçalho HTTP ou `null` se não existir. O parâmetro é insensível a maiúsculas/minúsculas. ```php $userAgent = $httpRequest->getHeader('User-Agent'); @@ -133,7 +133,7 @@ $userAgent = $httpRequest->getHeader('User-Agent'); getHeaders(): array .[method] ----------------------------- -Devolve todos os cabeçalhos HTTP como matriz associativa: +Retorna todos os cabeçalhos HTTP como um array associativo. ```php $headers = $httpRequest->getHeaders(); @@ -141,39 +141,34 @@ echo $headers['Content-Type']; ``` -getReferer(): ?Nette\Http\UrlImmutable .[method] ------------------------------------------------- -De que URL o usuário veio? Cuidado, ela não é confiável em absoluto. - - isSecured(): bool .[method] --------------------------- -A conexão é criptografada (HTTPS)? Talvez seja necessário [configurar um proxy |configuration#HTTP proxy] para uma funcionalidade adequada. +A conexão é criptografada (HTTPS)? Para o funcionamento correto, pode ser necessário [configurar o proxy |configuration#Proxy HTTP]. isSameSite(): bool .[method] ---------------------------- -A solicitação vem do mesmo (sub) domínio e é iniciada clicando em um link? Nette utiliza o cookie `_nss` (anteriormente `nette-samesite`) para detectá-lo. +A requisição vem do mesmo (sub)domínio e é iniciada clicando num link? Nette usa o cookie `_nss` (anteriormente `nette-samesite`) para deteção. isAjax(): bool .[method] ------------------------ -Trata-se de um pedido AJAX? +É uma requisição AJAX? getRemoteAddress(): ?string .[method] ------------------------------------- -Devolve o endereço IP do usuário. Pode ser necessário [configurar um proxy |configuration#HTTP proxy] para uma funcionalidade adequada. +Retorna o endereço IP do utilizador. Para o funcionamento correto, pode ser necessário [configurar o proxy |configuration#Proxy HTTP]. getRemoteHost(): ?string .[method deprecated] --------------------------------------------- -Retorna a tradução DNS do endereço IP do usuário. Pode ser necessário [configurar um proxy |configuration#HTTP proxy] para uma funcionalidade adequada. +Retorna a resolução DNS do endereço IP do utilizador. Para o funcionamento correto, pode ser necessário [configurar o proxy |configuration#Proxy HTTP]. -getBasicCredentials(): ?string .[method] ----------------------------------------- -Retorna as credenciais [básicas de autenticação HTTP |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication]. +getBasicCredentials(): ?array .[method] +--------------------------------------- +Retorna as credenciais de autenticação para [Basic HTTP authentication |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication]. ```php [$user, $password] = $httpRequest->getBasicCredentials(); @@ -182,7 +177,7 @@ Retorna as credenciais [básicas de autenticação HTTP |https://developer.mozil getRawBody(): ?string .[method] ------------------------------- -Devolve o corpo do pedido HTTP: +Retorna o corpo da requisição HTTP. ```php $body = $httpRequest->getRawBody(); @@ -191,108 +186,115 @@ $body = $httpRequest->getRawBody(); detectLanguage(array $langs): ?string .[method] ----------------------------------------------- -Detecta a linguagem. Como parâmetro `$lang`, passamos um conjunto de idiomas que o aplicativo suporta, e ele retorna o preferido pelo navegador. Não é mágico, o método utiliza apenas o cabeçalho `Accept-Language`. Se não for alcançado, ele retorna `null`. +Deteta o idioma. Como parâmetro `$lang`, passamos um array com os idiomas que a aplicação suporta, e ela retorna aquele que o navegador do visitante preferiria ver. Não há mágica, apenas o cabeçalho `Accept-Language` é usado. Se não houver correspondência, retorna `null`. ```php -// Cabeçalho enviado pelo navegador: Aceitar-Língua: cs,en-us;q=0,8,en;q=0,5,sl;q=0,3 +// o navegador envia, por exemplo, Accept-Language: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3 -$langs = ['hu', 'pl', 'en']; // idiomas suportados na aplicação -echo $httpRequest->detectLanguage($langs); // pt +$langs = ['hu', 'pl', 'en']; // idiomas suportados pela aplicação +echo $httpRequest->detectLanguage($langs); // en ``` -RequestFactory .[#toc-requestfactory] -===================================== +RequestFactory +============== -O objeto da solicitação HTTP atual é criado por [api:Nette\Http\RequestFactory]. Se você está escrevendo um pedido que não utiliza um recipiente DI, você cria um pedido da seguinte forma: +A classe [api:Nette\Http\RequestFactory] serve para criar uma instância de `Nette\Http\Request`, que representa a requisição HTTP atual. (Se trabalha com Nette, o objeto da requisição HTTP é criado automaticamente pelo framework.) ```php $factory = new Nette\Http\RequestFactory; $httpRequest = $factory->fromGlobals(); ``` -RequestFactory pode ser configurado antes de ligar para `fromGlobals()`. Podemos desativar toda a higienização dos parâmetros de entrada das seqüências UTF-8 inválidas usando `$factory->setBinary()`. E também configurar um servidor proxy, o que é importante para a detecção correta do endereço IP do usuário usando `$factory->setProxy(...)`. +O método `fromGlobals()` cria o objeto da requisição com base nas variáveis globais atuais do PHP (`$_GET`, `$_POST`, `$_COOKIE`, `$_FILES` e `$_SERVER`). Ao criar o objeto, ele limpa automaticamente todos os parâmetros de entrada GET, POST, COOKIE e também a URL de caracteres de controlo e sequências UTF-8 inválidas, o que garante a segurança ao trabalhar posteriormente com esses dados. + +A RequestFactory pode ser configurada antes de chamar `fromGlobals()`: -É possível limpar URLs de caracteres que podem entrar neles devido a sistemas de comentários mal implementados em vários outros sites, utilizando filtros: +- com o método `$factory->setBinary()`, desativa a limpeza automática dos parâmetros de entrada de caracteres de controlo e sequências UTF-8 inválidas. +- com o método `$factory->setProxy(...)`, indica o endereço IP do [servidor proxy |configuration#Proxy HTTP], o que é necessário para a deteção correta do endereço IP do utilizador. + +A RequestFactory permite definir filtros que transformam automaticamente partes da URL da requisição. Esses filtros removem caracteres indesejados da URL, que podem ser inseridos lá, por exemplo, por implementações incorretas de sistemas de comentários em vários sites: ```php -// remover espaços do caminho +// remoção de espaços do caminho $requestFactory->urlFilters['path']['%20'] = ''; -// remover ponto, vírgula ou parêntese direita do final da URL +// remoção de ponto, vírgula ou parêntese direito do final da URI $requestFactory->urlFilters['url']['[.,)]$'] = ''; -// limpar o caminho a partir de cortes duplicados (filtro padrão) +// limpeza do caminho de barras duplicadas (filtro padrão) $requestFactory->urlFilters['path']['/{2,}'] = '/'; ``` +A primeira chave `'path'` ou `'url'` determina a qual parte da URL o filtro se aplica. A segunda chave é a expressão regular a ser pesquisada, e o valor é a substituição a ser usada no lugar do texto encontrado. + -Arquivos carregados .[#toc-uploaded-files] -========================================== +Upload de ficheiros +=================== -Método `Nette\Http\Request::getFiles()` devolve uma árvore de arquivos de upload em uma estrutura normalizada, com cada folha uma instância de [api:Nette\Http\FileUpload]. Estes objetos encapsulam os dados enviados pelo `<input type=file>` elemento forma. +O método `Nette\Http\Request::getFiles()` retorna um array de todos os uploads numa estrutura normalizada, cujas folhas são objetos [api:Nette\Http\FileUpload]. Eles encapsulam os dados enviados pelo controlo de formulário `<input type=file>`. -A estrutura reflete a nomenclatura dos elementos em HTML. No exemplo mais simples, este pode ser um único elemento de formulário nomeado apresentado como: +A estrutura reflete a nomenclatura dos controlos em HTML. No caso mais simples, pode ser um único elemento de formulário nomeado enviado como: ```latte <input type="file" name="avatar"> ``` -Neste caso, o `$request->getFiles()` retorna a matriz: +Neste caso, `$request->getFiles()` retorna um array: ```php [ - 'avatar' => /* FileUpload instance */ + 'avatar' => /* Instância FileUpload */ ] ``` -O objeto `FileUpload` é criado mesmo que o usuário não tenha carregado nenhum arquivo ou que o carregamento tenha falhado. O método `hasFile()` retorna verdadeiro se um arquivo tiver sido enviado: +O objeto `FileUpload` é criado mesmo que o utilizador não tenha enviado nenhum ficheiro ou o envio tenha falhado. Se o ficheiro foi enviado é retornado pelo método `hasFile()`: ```php -$request->getFile('avatar')->hasFile(); +$request->getFile('avatar')?->hasFile(); ``` -No caso de uma entrada usando notação de array para o nome: +No caso do nome do elemento usando a notação de array: ```latte <input type="file" name="my-form[details][avatar]"> ``` -árvore devolvida acaba ficando com este aspecto: +a árvore retornada parece-se com isto: ```php [ 'my-form' => [ 'details' => [ - 'avatar' => /* FileUpload instance */ + 'avatar' => /* Instância FileUpload */ ], ], ] ``` -Você também pode criar matrizes de arquivos: +Também é possível criar um array de ficheiros: ```latte -<input type="file" name="my-form[details][avatars][] multiple"> +<input type="file" name="my-form[details][avatars][]" multiple> ``` -Em tal caso, a estrutura parece ser a mesma: +Nesse caso, a estrutura parece-se com isto: ```php [ 'my-form' => [ 'details' => [ 'avatars' => [ - 0 => /* FileUpload instance */, - 1 => /* FileUpload instance */, - 2 => /* FileUpload instance */, + 0 => /* Instância FileUpload */, + 1 => /* Instância FileUpload */, + 2 => /* Instância FileUpload */, ], ], ], ] ``` -A melhor maneira de acessar o índice 1 de uma matriz aninhada é a seguinte: +A melhor maneira de aceder ao índice 1 do array aninhado é assim: ```php $file = $request->getFile(['my-form', 'details', 'avatars', 1]); @@ -301,31 +303,31 @@ if ($file instanceof FileUpload) { } ``` -Como você não pode confiar nos dados do exterior e, portanto, não confia na forma da estrutura, este método é mais seguro do que `$request->getFiles()['my-form']['details']['avatars'][1]`o que pode falhar. +Como não se pode confiar nos dados externos e, portanto, nem na forma da estrutura dos ficheiros, este método é mais seguro do que, por exemplo, `$request->getFiles()['my-form']['details']['avatars'][1]`, que pode falhar. -Visão geral de `FileUpload` Métodos .{toc: FileUpload} ------------------------------------------------------- +Visão geral dos métodos `FileUpload` .{toc: FileUpload} +------------------------------------------------------- hasFile(): bool .[method] ------------------------- -Retorna `true` se o usuário tiver feito o upload de um arquivo. +Retorna `true` se o utilizador enviou algum ficheiro. isOk(): bool .[method] ---------------------- -Retorna `true` se o arquivo foi carregado com sucesso. +Retorna `true` se o ficheiro foi carregado com sucesso. getError(): int .[method] ------------------------- -Devolve o código de erro associado com o arquivo carregado. É uma das constantes do [UPLOAD_ERR_XXX |http://php.net/manual/en/features.file-upload.errors.php]. Se o arquivo foi carregado com sucesso, ele retorna `UPLOAD_ERR_OK`. +Retorna o código de erro durante o upload do ficheiro. É uma das constantes [UPLOAD_ERR_XXX|http://php.net/manual/en/features.file-upload.errors.php]. Caso o upload tenha ocorrido corretamente, retorna `UPLOAD_ERR_OK`. move(string $dest) .[method] ---------------------------- -Move um arquivo carregado para um novo local. Se o arquivo de destino já existir, ele será sobregravado. +Move o ficheiro carregado para um novo local. Se o ficheiro de destino já existir, ele será sobrescrito. ```php $file->move('/path/to/files/name.ext'); @@ -334,61 +336,72 @@ $file->move('/path/to/files/name.ext'); getContents(): ?string .[method] -------------------------------- -Devolve o conteúdo do arquivo carregado. Se o upload não foi bem sucedido, ele retorna `null`. +Retorna o conteúdo do ficheiro carregado. Caso o upload não tenha sido bem-sucedido, retorna `null`. getContentType(): ?string .[method] ----------------------------------- -Detecta o tipo de conteúdo MIME do arquivo carregado com base em sua assinatura. Se o upload não foi bem sucedido ou a detecção falhou, ele retorna `null`. +Deteta o tipo de conteúdo MIME do ficheiro carregado com base na sua assinatura. Caso o upload não tenha sido bem-sucedido ou a deteção falhe, retorna `null`. .[caution] -Requer extensão PHP `fileinfo`. +Requer a extensão PHP `fileinfo`. getUntrustedName(): string .[method] ------------------------------------ -Devolve o nome original do arquivo, conforme apresentado pelo navegador. +Retorna o nome original do ficheiro, como enviado pelo navegador. .[caution] -Não confie no valor retornado por este método. Um cliente pode enviar um nome de arquivo malicioso com a intenção de corromper ou invadir sua aplicação. +Não confie no valor retornado por este método. O cliente pode ter enviado um nome de ficheiro malicioso com a intenção de danificar ou hackear a sua aplicação. getSanitizedName(): string .[method] ------------------------------------ -Devolve o nome do arquivo higienizado. Contém apenas caracteres ASCII `[a-zA-Z0-9.-]`. Se o nome não contiver tais caracteres, ele retorna "desconhecido". Se o arquivo for JPEG, PNG, GIF, ou imagem WebP, ele retorna a extensão correta do arquivo. +Retorna o nome do ficheiro sanitizado. Contém apenas caracteres ASCII `[a-zA-Z0-9.-]`. Se o nome não contiver tais caracteres, retorna `'unknown'`. Se o ficheiro for uma imagem no formato JPEG, PNG, GIF, WebP ou AVIF, retorna também a extensão correta. + +.[caution] +Requer a extensão PHP `fileinfo`. + + +getSuggestedExtension(): ?string .[method]{data-version:3.2.4} +-------------------------------------------------------------- +Retorna a extensão de ficheiro apropriada (sem o ponto) correspondente ao tipo MIME detetado. + +.[caution] +Requer a extensão PHP `fileinfo`. getUntrustedFullPath(): string .[method] ---------------------------------------- -Retorna o caminho completo original, conforme apresentado pelo navegador durante o upload do diretório. O caminho completo só está disponível no PHP 8.1 e superior. Nas versões anteriores, este método retorna o nome do arquivo não confiável. +Retorna o caminho original do ficheiro, como enviado pelo navegador ao fazer upload de uma pasta. O caminho completo está disponível apenas no PHP 8.1 e superior. Em versões anteriores, este método retorna o nome original do ficheiro. .[caution] -Não confie no valor retornado por este método. Um cliente pode enviar um nome de arquivo malicioso com a intenção de corromper ou invadir sua aplicação. +Não confie no valor retornado por este método. O cliente pode ter enviado um nome de ficheiro malicioso com a intenção de danificar ou hackear a sua aplicação. getSize(): int .[method] ------------------------ -Devolve o tamanho do arquivo carregado. Se o upload não foi bem sucedido, ele retorna `0`. +Retorna o tamanho do ficheiro carregado. Caso o upload não tenha sido bem-sucedido, retorna `0`. getTemporaryFile(): string .[method] ------------------------------------ -Retorna o caminho da localização temporária do arquivo carregado. Se o upload não foi bem sucedido, ele retorna `''`. +Retorna o caminho para o local temporário do ficheiro carregado. Caso o upload não tenha sido bem-sucedido, retorna `''`. isImage(): bool .[method] ------------------------- -Retorna `true` se o arquivo carregado for uma imagem JPEG, PNG, GIF, ou WebP. A detecção é baseada em sua assinatura. A integridade do arquivo inteiro não é verificada. Você pode descobrir se uma imagem não está corrompida, por exemplo, ao tentar [carregá-la |#toImage]. +Retorna `true` se o ficheiro carregado for uma imagem no formato JPEG, PNG, GIF, WebP ou AVIF. A deteção ocorre com base na sua assinatura e não verifica a integridade de todo o ficheiro. Se a imagem não está danificada pode ser verificado, por exemplo, tentando [carregá-la |#toImage]. .[caution] -Requer extensão PHP `fileinfo`. +Requer a extensão PHP `fileinfo`. getImageSize(): ?array .[method] -------------------------------- -Devolve um par de `[width, height]` com as dimensões da imagem carregada. Se o upload não foi bem sucedido ou não é uma imagem válida, ele retorna `null`. +Retorna o par `[largura, altura]` com as dimensões da imagem carregada. Caso o upload não tenha sido bem-sucedido ou não seja uma imagem válida, retorna `null`. toImage(): Nette\Utils\Image .[method] -------------------------------------- -Carrega uma imagem como um objeto de [imagem |utils:images]. Se o upload não foi bem sucedido ou não é uma imagem válida, ele lança uma exceção `Nette\Utils\ImageException`. +Carrega a imagem como um objeto [Image|utils:images]. Caso o upload não tenha sido bem-sucedido ou não seja uma imagem válida, lança a exceção `Nette\Utils\ImageException`. diff --git a/http/pt/response.texy b/http/pt/response.texy index 828fe5722d..2e9692d2be 100644 --- a/http/pt/response.texy +++ b/http/pt/response.texy @@ -2,22 +2,22 @@ Resposta HTTP ************* .[perex] -Nette encapsula a resposta HTTP em objetos com uma API compreensível enquanto fornece um filtro de sanitização. +Nette encapsula a resposta HTTP em objetos com uma API compreensível. -Uma resposta HTTP é um objeto [api:Nette\Http\Response], que você obtém passando-o usando a [injeção de dependência |dependency-injection:passing-dependencies]. Nos apresentadores, basta ligar para `$httpResponse = $this->getHttpResponse()`. +A resposta HTTP é representada pelo objeto [api:Nette\Http\Response]. Se trabalha com Nette, este objeto é criado automaticamente pelo framework e pode recebê-lo por meio de [injeção de dependência |dependency-injection:passing-dependencies]. Nos presenters, basta chamar o método `$this->getHttpResponse()`. -→ [Instalação e requisitos |@home#Installation] +→ [Instalação e requisitos |@home#Instalação] -Nette\Http\Resposta .[#toc-nette-http-response] -=============================================== +Nette\Http\Response +=================== -Unlike [Nette\Http\Request |request], this object is mutable, so you can use setters to change the state, ie to send headers. Lembre-se que todos os setters **devem ser chamados antes que qualquer saída real seja enviada.** O método `isSent()` informa se a saída foi enviada. Se ele retornar `true`, cada tentativa de enviar um cabeçalho lançará uma exceção `Nette\InvalidStateException`. +O objeto, ao contrário de [Nette\Http\Request|request], é mutável, ou seja, usando setters pode alterar o estado, por exemplo, enviar cabeçalhos. Lembre-se de que todos os setters devem ser chamados **antes de enviar qualquer saída.** Se a saída já foi enviada é indicado pelo método `isSent()`. Se retornar `true`, qualquer tentativa de enviar um cabeçalho lançará a exceção `Nette\InvalidStateException`. -setCode(int $code, string $reason=null) .[method] -------------------------------------------------- -Altera um [código de resposta de |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10] status. Para melhor legibilidade do código fonte, é recomendado o uso de [constantes pré-definidas |api:Nette\Http\IResponse] em vez de números reais. +setCode(int $code, ?string $reason=null) .[method] +-------------------------------------------------- +Altera o [código de status da resposta |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10]. Para melhor clareza do código-fonte, recomendamos usar [constantes predefinidas |api:Nette\Http\IResponse] em vez de números para o código. ```php $httpResponse->setCode(Nette\Http\Response::S404_NotFound); @@ -31,12 +31,12 @@ Retorna o código de status da resposta. isSent(): bool .[method] ------------------------ -Retorna se os cabeçalhos já foram enviados do servidor para o navegador, portanto não é mais possível enviar cabeçalhos ou alterar o código de status. +Retorna se os cabeçalhos já foram enviados do servidor para o navegador e, portanto, não é mais possível enviar cabeçalhos ou alterar o código de status. setHeader(string $name, string $value) .[method] ------------------------------------------------ -Envia um cabeçalho HTTP e **sobreescritas*** cabeçalho previamente enviado com o mesmo nome. +Envia um cabeçalho HTTP e **sobrescreve** um cabeçalho enviado anteriormente com o mesmo nome. ```php $httpResponse->setHeader('Pragma', 'no-cache'); @@ -45,7 +45,7 @@ $httpResponse->setHeader('Pragma', 'no-cache'); addHeader(string $name, string $value) .[method] ------------------------------------------------ -Envia um cabeçalho HTTP e **não sobrescreve** cabeçalho previamente enviado com o mesmo nome. +Envia um cabeçalho HTTP e **não sobrescreve** um cabeçalho enviado anteriormente com o mesmo nome. ```php $httpResponse->addHeader('Accept', 'application/json'); @@ -55,12 +55,12 @@ $httpResponse->addHeader('Accept', 'application/xml'); deleteHeader(string $name) .[method] ------------------------------------ -Elimina um cabeçalho HTTP previamente enviado. +Exclui um cabeçalho HTTP enviado anteriormente. getHeader(string $header): ?string .[method] -------------------------------------------- -Devolve o cabeçalho HTTP enviado, ou `null` se ele não existir. O parâmetro não diferencia maiúsculas e minúsculas de minúsculas. +Retorna o cabeçalho HTTP enviado ou `null` se não existir. O parâmetro é insensível a maiúsculas/minúsculas. ```php $pragma = $httpResponse->getHeader('Pragma'); @@ -69,7 +69,7 @@ $pragma = $httpResponse->getHeader('Pragma'); getHeaders(): array .[method] ----------------------------- -Devolve todos os cabeçalhos HTTP enviados como matriz associativa. +Retorna todos os cabeçalhos HTTP enviados como um array associativo. ```php $headers = $httpResponse->getHeaders(); @@ -77,9 +77,9 @@ echo $headers['Pragma']; ``` -setContentType(string $type, string $charset=null) .[method] ------------------------------------------------------------- -Envia o cabeçalho `Content-Type`. +setContentType(string $type, ?string $charset=null) .[method] +------------------------------------------------------------- +Altera o cabeçalho `Content-Type`. ```php $httpResponse->setContentType('text/plain', 'UTF-8'); @@ -88,7 +88,7 @@ $httpResponse->setContentType('text/plain', 'UTF-8'); redirect(string $url, int $code=self::S302_Found): void .[method] ----------------------------------------------------------------- -Redireciona para outra URL. Não se esqueça de desistir do roteiro então. +Redireciona para outra URL. Lembre-se de encerrar o script depois. ```php $httpResponse->redirect('http://example.com'); @@ -98,52 +98,52 @@ exit; setExpiration(?string $time) .[method] -------------------------------------- -Define a expiração do documento HTTP usando os cabeçalhos `Cache-Control` e `Expires`. O parâmetro é um intervalo de tempo (como texto) ou `null`, o que desativa o cache. +Define a expiração do documento HTTP usando os cabeçalhos `Cache-Control` e `Expires`. O parâmetro é um intervalo de tempo (como texto) ou `null`, que desativa o cache. ```php -// o cache do navegador expira em uma hora +// o cache no navegador expirará em uma hora $httpResponse->setExpiration('1 hour'); ``` sendAsFile(string $fileName) .[method] -------------------------------------- -A resposta deve ser baixada com *Salvar como* diálogo com nome especificado. Ele próprio não envia nenhum arquivo para a saída. +A resposta será baixada usando a caixa de diálogo *Salvar como* com o nome fornecido. O ficheiro em si não é enviado. ```php -$httpResponse->sendAsFile('invoice.pdf'); +$httpResponse->sendAsFile('fatura.pdf'); ``` -setCookie(string $name, string $value, $time, string $path=null, string $domain=null, bool $secure=null, bool $httpOnly=null, string $sameSite=null) .[method] --------------------------------------------------------------------------------------------------------------------------------------------------------------- -Envia um cookie. Valores de parâmetros padrão: +setCookie(string $name, string $value, $time, ?string $path=null, ?string $domain=null, ?bool $secure=null, ?bool $httpOnly=null, ?string $sameSite=null) .[method] +------------------------------------------------------------------------------------------------------------------------------------------------------------------- +Envia um cookie. Os valores padrão dos parâmetros são: -| `$path` | `'/'` | com escopo para todos os caminhos no (sub)domínio *(configurável)* -| `$domain` | `null` | com escopo do (sub)domínio atual, mas não de seus subdomínios *(configurável)* -| `$secure` | `true` | se o site estiver rodando em HTTPS, caso contrário `false` *(configurável)* -| `$httpOnly` | `true` | cookie é inacessível ao JavaScript -| `$sameSite` | `'Lax'` | cookie não tem que ser enviado quando [acessado de outra origem |nette:glossary#SameSite cookie] +| `$path` | `'/'` | o cookie tem alcance para todos os caminhos no (sub)domínio *(configurável)* +| `$domain` | `null` | o que significa com alcance para o (sub)domínio atual, mas não seus subdomínios *(configurável)* +| `$secure` | `true` | se o site estiver rodando em HTTPS, caso contrário `false` *(configurável)* +| `$httpOnly` | `true` | o cookie é inacessível para JavaScript +| `$sameSite` | `'Lax'` | o cookie pode não ser enviado durante o [acesso de outro domínio |nette:glossary#SameSite cookie] -Você pode alterar os valores padrão dos parâmetros `$path`, `$domain` e `$secure` na [configuração#HTTP cookie |configuration#HTTP cookie]. +Os valores padrão dos parâmetros `$path`, `$domain` e `$secure` podem ser alterados na [configuração |configuration#Cookie HTTP]. -O tempo pode ser especificado como número de segundos ou um fio: +O tempo pode ser especificado como um número de segundos ou uma string: ```php -$httpResponse->setCookie('lang', 'en', '100 days'); +$httpResponse->setCookie('lang', 'pt', '100 days'); // Traduzido 'cs' para 'pt' como exemplo ``` -A opção `$domain` determina quais domínios (origens) podem aceitar cookies. Se não especificado, o cookie é aceito pelo mesmo (sub)domínio que é definido por ele, excluindo seus subdomínios. Se `$domain` for especificado, então os subdomínios também serão incluídos. Portanto, a especificação do `$domain` é menos restritiva do que a omissão. Por exemplo, se `$domain = 'nette.org'`, o cookie também está disponível em todos os subdomínios como `doc.nette.org`. +O parâmetro `$domain` determina quais domínios podem aceitar o cookie. Se não for especificado, o cookie é aceito pelo mesmo (sub)domínio que o definiu, mas não por seus subdomínios. Se `$domain` for especificado, os subdomínios também são incluídos. Portanto, especificar `$domain` é menos restritivo do que omiti-lo. Por exemplo, com `$domain = 'nette.org'`, os cookies também estão disponíveis em todos os subdomínios como `doc.nette.org`. -Você pode usar as constantes `Response::SameSiteLax`, `SameSiteStrict` e `SameSiteNone` para o valor `$sameSite`. +Para o valor `$sameSite`, pode usar as constantes `Response::SameSiteLax`, `SameSiteStrict` e `SameSiteNone`. -deleteCookie(string $name, string $path=null, string $domain=null, bool $secure=null): void .[method] ------------------------------------------------------------------------------------------------------ -Elimina um cookie. Os valores padrão dos parâmetros são: -- `$path` com escopo para todos os diretórios (`'/'`) -- `$domain` com escopo do (sub)domínio atual, mas não de seus subdomínios -- `$secure` é afetado pelos ajustes na [configuração#HTTP cookie |configuration#HTTP cookie] +deleteCookie(string $name, ?string $path=null, ?string $domain=null, ?bool $secure=null): void .[method] +-------------------------------------------------------------------------------------------------------- +Exclui um cookie. Os valores padrão dos parâmetros são: +- `$path` com alcance para todos os diretórios (`'/'`) +- `$domain` com alcance para o (sub)domínio atual, mas não seus subdomínios +- `$secure` é regido pelas configurações na [configuração |configuration#Cookie HTTP] ```php $httpResponse->deleteCookie('lang'); diff --git a/http/pt/sessions.texy b/http/pt/sessions.texy index c073b1c7fe..96c4aeb392 100644 --- a/http/pt/sessions.texy +++ b/http/pt/sessions.texy @@ -3,69 +3,69 @@ Sessões <div class=perex> -HTTP é um protocolo sem estado, mas quase todas as aplicações precisam manter o estado entre as solicitações, por exemplo, o conteúdo de um carrinho de compras. É para isso que uma sessão é usada. Vamos ver +HTTP é um protocolo sem estado, no entanto, quase toda aplicação precisa manter o estado entre as requisições, por exemplo, o conteúdo de um carrinho de compras. É exatamente para isso que servem as sessões. Vamos mostrar: -- como usar as sessões +- como usar sessões - como evitar conflitos de nomes - como definir a expiração </div> -Ao utilizar as sessões, cada usuário recebe um identificador único chamado ID da sessão, que é passado em um cookie. Isto serve como a chave para os dados da sessão. Ao contrário dos cookies, que são armazenados no lado do navegador, os dados da sessão são armazenados no lado do servidor. +Ao usar sessões, cada utilizador recebe um identificador único chamado ID de sessão, que é passado num cookie. Ele serve como chave para os dados da sessão. Ao contrário dos cookies, que são armazenados no lado do navegador, os dados da sessão são armazenados no lado do servidor. -Nós configuramos a sessão em [configuração |configuration#session], a escolha do tempo de expiração é importante. +Configuramos a sessão na [configuração |configuration#Sessão], a escolha do tempo de expiração é especialmente importante. -A sessão é gerenciada pelo objeto [api:Nette\Http\Session], que você obtém passando por [injeção de dependência |dependency-injection:passing-dependencies]. Nos apresentadores, basta ligar para `$session = $this->getSession()`. +O gerenciamento da sessão é feito pelo objeto [api:Nette\Http\Session], ao qual pode aceder solicitando-o por meio de [injeção de dependência |dependency-injection:passing-dependencies]. Nos presenters, basta chamar `$session = $this->getSession()`. -→ [Instalação e requisitos |@home#Installation] +→ [Instalação e requisitos |@home#Instalação] -Sessão inicial .[#toc-starting-session] -======================================= +Iniciar sessão +============== -Por padrão, a Nette iniciará uma sessão automaticamente no momento em que começarmos a ler a partir dela ou a escrever dados para ela. Para iniciar manualmente uma sessão, use `$session->start()`. +Nette, por padrão, inicia automaticamente a sessão no momento em que começamos a ler ou escrever dados nela. Manualmente, a sessão é iniciada usando `$session->start()`. -PHP envia cabeçalhos HTTP que afetam o cache ao iniciar a sessão, veja [php:session_cache_limiter], e possivelmente um cookie com o ID da sessão. Portanto, é sempre necessário iniciar a sessão antes de enviar qualquer saída para o navegador, caso contrário, será lançada uma exceção. Portanto, se você souber que uma sessão será usada durante a renderização da página, inicie-a manualmente antes, por exemplo, no apresentador. +O PHP envia cabeçalhos HTTP que afetam o cache ao iniciar a sessão, veja [php:session_cache_limiter], e possivelmente também um cookie com o ID da sessão. Portanto, é sempre necessário iniciar a sessão antes de enviar qualquer saída para o navegador, caso contrário, uma exceção será lançada. Se sabe que a sessão será usada durante a renderização da página, inicie-a manualmente antes, por exemplo, no presenter. -No modo desenvolvedor, Tracy inicia a sessão porque a utiliza para exibir o redirecionamento e AJAX solicita barras na barra Tracy. +No modo de desenvolvimento, o Tracy inicia a sessão porque a usa para exibir barras com redirecionamentos e requisições AJAX na Tracy Bar. -Seção .[#toc-section] -===================== +Seções +====== -Em PHP puro, o armazenamento de dados da sessão é implementado como uma matriz acessível através de uma variável global `$_SESSION`. O problema é que as aplicações normalmente consistem em várias partes independentes, e se todas tiverem apenas uma mesma matriz disponível, mais cedo ou mais tarde ocorrerá uma colisão de nome. +Em PHP puro, o armazenamento de dados da sessão é implementado como um array acessível através da variável global `$_SESSION`. O problema é que as aplicações geralmente consistem em várias partes independentes e, se todas tiverem acesso a apenas um array, mais cedo ou mais tarde ocorrerá uma colisão de nomes. -Nette Framework resolve o problema dividindo o espaço inteiro em seções (objetos [api:Nette\Http\SessionSection]). Cada unidade então usa sua própria seção com um nome único e não podem ocorrer colisões. +O Nette Framework resolve o problema dividindo todo o espaço em seções (objetos [api:Nette\Http\SessionSection]). Cada unidade então usa a sua própria seção com um nome exclusivo e nenhuma colisão pode mais ocorrer. -Recebemos a seção do gerente da sessão: +Obtemos a seção da sessão: ```php -$section = $session->getSection('unique name'); +$section = $session->getSection('nome unico'); ``` -No apresentador, basta ligar para `getSession()` com o parâmetro: +No presenter, basta usar `getSession()` com um parâmetro: ```php -// $this é Apresentador -$section = $this->getSession('unique name'); +// $this é Presenter +$section = $this->getSession('nome unico'); ``` -A existência da seção pode ser verificada pelo método `$session->hasSection('unique name')`. +A existência da seção pode ser verificada com o método `$session->hasSection('nomeUnico')`. -A seção em si é muito fácil de trabalhar com o uso dos métodos `set()`, `get()` e `remove()`: +Trabalhar com a própria seção é então muito fácil usando os métodos `set()`, `get()` e `remove()`: ```php -// escrita variável +// escrever variável $section->set('userName', 'franta'); -// lendo uma variável, retorna nula se ela não existir +// ler variável, retorna null se não existir echo $section->get('userName'); -// remoção variável +// cancelar variável $section->remove('userName'); ``` -É possível utilizar o ciclo `foreach` para obter todas as variáveis da seção: +Para obter todas as variáveis da seção, é possível usar o ciclo `foreach`: ```php foreach ($section as $key => $val) { @@ -74,108 +74,108 @@ foreach ($section as $key => $val) { ``` -Como definir a expiração .[#toc-how-to-set-expiration] ------------------------------------------------------- +Definir expiração +----------------- -A expiração pode ser definida para seções individuais ou mesmo variáveis individuais. Podemos deixar o login do usuário expirar em 20 minutos, mas ainda assim lembrar o conteúdo de um carrinho de compras. +É possível definir a expiração para seções individuais ou até mesmo variáveis individuais. Podemos, assim, deixar a sessão do utilizador expirar em 20 minutos, mas ainda lembrar o conteúdo do carrinho. ```php -// seção expirará após 20 minutos +// a seção expirará após 20 minutos $section->setExpiration('20 minutes'); ``` -O terceiro parâmetro do método `set()` é usado para definir a expiração de variáveis individuais: +Para definir a expiração de variáveis individuais, serve o terceiro parâmetro do método `set()`: ```php -// o 'flash' variável expira após 30 segundos +// a variável 'flash' expirará em 30 segundos $section->set('flash', $message, '30 seconds'); ``` .[note] -Lembre-se que o tempo de expiração de toda a sessão (ver [configuração da sessão |configuration#session]) deve ser igual ou superior ao tempo definido para seções ou variáveis individuais. +Não se esqueça que o tempo de expiração de toda a sessão (veja [configuração da sessão |configuration#Sessão]) deve ser igual ou maior que o tempo definido para seções ou variáveis individuais. -O cancelamento da expiração previamente estabelecida pode ser conseguido pelo método `removeExpiration()`. O cancelamento imediato de toda a seção será assegurado pelo método `remove()`. +A remoção da expiração definida anteriormente é feita pelo método `removeExpiration()`. A remoção imediata de toda a seção é garantida pelo método `remove()`. -Eventos $onStart, $onBeforeWrite .[#toc-events-onstart-onbeforewrite] ---------------------------------------------------------------------- +Eventos $onStart, $onBeforeWrite +-------------------------------- -O objeto `Nette\Http\Session` tem [eventos |nette:glossary#Events] `$onStart` a `$onBeforeWrite`, assim você pode adicionar callbacks que são chamados após o início da sessão ou antes de ser gravado em disco e depois terminado. +O objeto `Nette\Http\Session` possui os [eventos |nette:glossary#Eventos] `$onStart` e `$onBeforeWrite`, então pode adicionar callbacks que serão chamados após o início da sessão ou antes da sua escrita no disco e subsequente encerramento. ```php $session->onBeforeWrite[] = function () { - // escrever dados para a sessão + // escrevemos dados na sessão $this->section->set('basket', $this->basket); }; ``` -Gerenciamento da sessão .[#toc-session-management] -================================================== +Gerenciamento de sessão +======================= -Visão geral dos métodos da classe `Nette\Http\Session` para gerenciamento de sessões: +Visão geral dos métodos da classe `Nette\Http\Session` para gerenciamento de sessão: <div class=wiki-methods-brief> start(): void .[method] ----------------------- -Inicia uma sessão. +Inicia a sessão. isStarted(): bool .[method] --------------------------- -A sessão foi iniciada? +A sessão está iniciada? close(): void .[method] ----------------------- -Encerra a sessão. A sessão termina automaticamente no final do roteiro. +Encerra a sessão. A sessão é encerrada automaticamente no final da execução do script. destroy(): void .[method] ------------------------- -Encerra e apaga a sessão. +Encerra e exclui a sessão. exists(): bool .[method] ------------------------ -A solicitação HTTP contém um cookie com um ID de sessão? +A requisição HTTP contém um cookie com o ID da sessão? regenerateId(): void .[method] ------------------------------ -Gera uma nova identificação de sessão aleatória. Os dados permanecem inalterados. +Gera um novo ID de sessão aleatório. Os dados permanecem preservados. getId(): string .[method] ------------------------- -Devolve a identificação da sessão. +Retorna o ID da sessão. </div> -Configuração .[#toc-configuration] ----------------------------------- +Configuração +------------ -Configuramos a sessão em [configuração |configuration#session]. Se você estiver escrevendo uma aplicação que não utilize um recipiente DI, use estes métodos para configurá-la. Eles devem ser chamados antes de iniciar a sessão. +Configuramos a sessão na [configuração |configuration#Sessão]. Se está a escrever uma aplicação que não usa um contêiner DI, estes métodos são usados para configuração. Devem ser chamados antes de iniciar a sessão. <div class=wiki-methods-brief> setName(string $name): static .[method] --------------------------------------- -Define o nome do cookie que é usado para transmitir o ID da sessão. O nome padrão é `PHPSESSID`. Isto é útil se você executar várias aplicações diferentes no mesmo site. +Define o nome do cookie no qual o ID da sessão é transmitido. O nome padrão é `PHPSESSID`. É útil caso execute várias aplicações diferentes no mesmo site. getName(): string .[method] --------------------------- -Retorna o nome do cookie da sessão. +Retorna o nome do cookie no qual o ID da sessão é transmitido. setOptions(array $options): static .[method] -------------------------------------------- -Configura a sessão. É possível configurar todas as [diretrizes da sessão |https://www.php.net/manual/en/session.configuration.php] PHP (em formato camelCase, por exemplo, escrever `savePath` ao invés de `session.save_path`) e também [lerAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. +Configura a sessão. É possível definir todas as [diretivas de sessão |https://www.php.net/manual/en/session.configuration.php] do PHP (no formato camelCase, por exemplo, em vez de `session.save_path` escrevemos `savePath`) e também [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. setExpiration(?string $time): static .[method] @@ -183,28 +183,29 @@ setExpiration(?string $time): static .[method] Define o tempo de inatividade após o qual a sessão expira. -setCookieParameters(string $path, string $domain=null, bool $secure=null, string $samesite=null): static .[method] ------------------------------------------------------------------------------------------------------------------- -Estabelece parâmetros para cookies. Você pode alterar os valores padrão dos parâmetros na [configuração#Bookie de sessão |configuration#Session cookie]. +setCookieParameters(string $path, ?string $domain=null, ?bool $secure=null, ?string $samesite=null): static .[method] +--------------------------------------------------------------------------------------------------------------------- +Define os parâmetros para o cookie. Os valores padrão dos parâmetros podem ser alterados na [configuração |configuration#Cookie de sessão]. setSavePath(string $path): static .[method] ------------------------------------------- -Define o diretório onde os arquivos das sessões são armazenados. +Define o diretório onde os ficheiros de sessão são armazenados. setHandler(\SessionHandlerInterface $handler): static .[method] --------------------------------------------------------------- -Define manipulador personalizado, veja a [documentação PHP |https://www.php.net/manual/en/class.sessionhandlerinterface.php]. +Define um manipulador personalizado, veja a [documentação do PHP|https://www.php.net/manual/en/class.sessionhandlerinterface.php]. </div> -Segurança em primeiro lugar .[#toc-safety-first] -================================================ +Segurança em primeiro lugar +=========================== -O servidor assume que ele se comunica com o mesmo usuário desde que as solicitações contenham o mesmo ID de sessão. A tarefa dos mecanismos de segurança é garantir que este comportamento realmente funcione e que não haja possibilidade de substituir ou roubar um identificador. +O servidor assume que está a comunicar sempre com o mesmo utilizador, desde que as requisições sejam acompanhadas pelo mesmo ID de sessão. A tarefa dos mecanismos de segurança é garantir que isso realmente aconteça e que não seja possível roubar ou falsificar o identificador. -É por isso que a Nette Framework configura corretamente as diretrizes PHP para transferir o ID da sessão somente em cookies, para evitar o acesso a partir do JavaScript e para ignorar os identificadores na URL. Além disso, em momentos críticos, como o login do usuário, ele gera um novo ID de sessão. +O Nette Framework, portanto, configura corretamente as diretivas PHP para que o ID da sessão seja transmitido apenas em cookies, o torne inacessível ao JavaScript e ignore quaisquer identificadores na URL. Além disso, em momentos críticos, como o login do utilizador, ele gera um novo ID de sessão. -A função ini_set é usada para configurar PHP, mas infelizmente, seu uso é proibido em alguns serviços de hospedagem web. Se for seu caso, tente pedir ao seu provedor de hospedagem que permita esta função para você, ou pelo menos que configure seu servidor adequadamente. .[note] +.[note] +Para configurar o PHP, usa-se a função ini_set, que infelizmente alguns provedores de hospedagem proíbem. Se este for o caso do seu provedor, tente negociar com ele para permitir a função ou pelo menos configurar o servidor. diff --git a/http/pt/urls.texy b/http/pt/urls.texy index 10ee166de8..ba39376048 100644 --- a/http/pt/urls.texy +++ b/http/pt/urls.texy @@ -1,19 +1,19 @@ -URL Parser e Builder +Trabalhando com URLs ******************** .[perex] -As classes [Url |#Url], [UrlImmutable |#UrlImmutable] e [UrlScript |#UrlScript] facilitam o gerenciamento, a análise e a manipulação de URLs. +As classes [#Url], [#UrlImmutable] e [#UrlScript] permitem gerar, analisar e manipular URLs facilmente. -→ [Instalação e requisitos |@home#Installation] +→ [Instalação e requisitos |@home#Instalação] Url === -A classe [api:Nette\Http\Url] facilita o trabalho com a URL e seus componentes individuais, que estão delineados neste diagrama: +A classe [api:Nette\Http\Url] permite trabalhar facilmente com URLs e os seus componentes individuais, que são capturados neste diagrama: /--pre - scheme user password host port path query fragment + schema user password host port path query fragment | | | | | | | | /--\ /--\ /------\ /-------\ /--\/----------\ /--------\ /----\ <b>http://john:xyz%2A12@nette.org:8080/en/download?name=param#footer</b> @@ -22,7 +22,7 @@ A classe [api:Nette\Http\Url] facilita o trabalho com a URL e seus componentes i hostUrl authority \-- -A geração de URL é intuitiva: +A geração de URLs é intuitiva: ```php use Nette\Http\Url; @@ -36,7 +36,7 @@ $url->setScheme('https') echo $url; // 'https://localhost/edit?foo=bar' ``` -Você também pode analisar a URL e depois manipulá-la: +Também é possível analisar uma URL e manipulá-la posteriormente: ```php $url = new Url( @@ -44,62 +44,94 @@ $url = new Url( ); ``` -Os seguintes métodos estão disponíveis para obter ou alterar os componentes individuais da URL: +A classe `Url` implementa a interface `JsonSerializable` e possui o método `__toString()`, então o objeto pode ser impresso ou usado em dados passados para `json_encode()`. + +```php +echo $url; +echo json_encode([$url]); +``` + + +Componentes da URL .[method] +---------------------------- + +Para retornar ou alterar os componentes individuais da URL, estes métodos estão disponíveis: .[language-php] -| Setter| Getter| Valor devolvido +| Setter | Getter | Valor retornado |-------------------------------------------------------------------------------------------- -| `setScheme(string $scheme)`| `getScheme(): string`| `'http'` -| `setUser(string $user)`| `getUser(): string`| `'john'` -| `setPassword(string $password)`| `getPassword(): string`| `'xyz*12'` -| `setHost(string $host)`| `getHost(): string`| `'nette.org'` -| `setPort(int $port)`| `getPort(): ?int`| `8080` -| | `getDefaultPort(): ?int`| `80` -| `setPath(string $path)`| `getPath(): string`| `'/en/download'` -| `setQuery(string\|array $query)`| `getQuery(): string`| `'name=param'` -| `setFragment(string $fragment)`| `getFragment(): string`| `'footer'` -| | `getAuthority(): string`| `'nette.org:8080'` -| | `getHostUrl(): string`| `'http://nette.org:8080'` -| | `getAbsoluteUrl(): string` | URL completa - -Também podemos operar com parâmetros de consulta individuais usando: +| `setScheme(string $scheme)` | `getScheme(): string` | `'http'` +| `setUser(string $user)` | `getUser(): string` | `'john'` +| `setPassword(string $password)` | `getPassword(): string` | `'xyz*12'` +| `setHost(string $host)` | `getHost(): string` | `'nette.org'` +| `setPort(int $port)` | `getPort(): ?int` | `8080` +| | `getDefaultPort(): ?int` | `80` +| `setPath(string $path)` | `getPath(): string` | `'/en/download'` +| `setQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` +| `setFragment(string $fragment)` | `getFragment(): string` | `'footer'` +| | `getAuthority(): string` | `'john:xyz%2A12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | URL completa + +Aviso: Ao trabalhar com uma URL obtida de uma [requisição HTTP|request], lembre-se de que ela não conterá o fragmento, pois o navegador não o envia para o servidor. + +Também podemos trabalhar com parâmetros de consulta individuais usando: .[language-php] -| Setter| Getter +| Setter | Getter |--------------------------------------------------- -| `setQuery(string\|array $query)` | `getQueryParameters(): array` -| `setQueryParameter(string $name, $val)`| `getQueryParameter(string $name)` +| `setQuery(string\|array $query)` | `getQueryParameters(): array` +| `setQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` -O método `getDomain(int $level = 2)` retorna a parte direita ou esquerda do anfitrião. É assim que funciona se o anfitrião for `www.nette.org`: + +getDomain(int $level = 2): string .[method] +------------------------------------------- +Retorna a parte direita ou esquerda do host. Funciona assim se o host for `www.nette.org`: .[language-php] -| `getDomain(1)` | `'org'` -| `getDomain(2)` | `'nette.org'` -| `getDomain(3)` | `'www.nette.org'` -| `getDomain(0)` | `'www.nette.org'` -| `getDomain(-1)` | `'www.nette'` -| `getDomain(-2)` | `'www'` -| `getDomain(-3)` | `''` +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `''` -A classe `Url` implementa a interface `JsonSerializable` e tem um método `__toString()` para que o objeto possa ser impresso ou usado em dados passados para `json_encode()`. +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +Verifica se duas URLs são idênticas. ```php -echo $url; -echo json_encode([$url]); +$url->isEqual('https://nette.org'); ``` -O método `isEqual(string|Url $anotherUrl): bool` testa se as duas URLs são idênticas. + +Url::isAbsolute(string $url): bool .[method]{data-version:3.3.2} +---------------------------------------------------------------- +Verifica se a URL é absoluta. Uma URL é considerada absoluta se começa com um esquema (por exemplo, http, https, ftp) seguido por dois pontos. ```php -$url->isEqual('https://nette.org'); +Url::isAbsolute('https://nette.org'); // true +Url::isAbsolute('//nette.org'); // false +``` + + +Url::removeDotSegments(string $path): string .[method]{data-version:3.3.2} +-------------------------------------------------------------------------- +Normaliza o caminho na URL removendo os segmentos especiais `.` e `..`. O método remove elementos de caminho redundantes da mesma forma que os navegadores web fazem. + +```php +Url::removeDotSegments('/path/../subtree/./file.txt'); // '/subtree/file.txt' +Url::removeDotSegments('/../foo/./bar'); // '/foo/bar' +Url::removeDotSegments('./today/../file.txt'); // 'file.txt' ``` -UrlImmutável .[#toc-urlimmutable] -================================= +UrlImmutable +============ -A classe [api:Nette\Http\UrlImmutable] é uma alternativa imutável à classe `Url` (assim como no PHP `DateTimeImmutable` é uma alternativa imutável ao `DateTime`). Ao invés de setters, ela tem os chamados murchadores, que não mudam o objeto, mas retornam novas instâncias com um valor modificado: +A classe [api:Nette\Http\UrlImmutable] é uma alternativa imutável à classe [#Url] (semelhante a como `DateTimeImmutable` do PHP é a alternativa imutável a `DateTime`). Em vez de setters, ela possui os chamados withers, que não alteram o objeto, mas retornam novas instâncias com o valor modificado: ```php use Nette\Http\UrlImmutable; @@ -111,53 +143,96 @@ $url = new UrlImmutable( $newUrl = $url ->withUser('') ->withPassword('') - ->withPath('/en/'); + ->withPath('/cs/'); + +echo $newUrl; // 'http://john:xyz%2A12@nette.org:8080/cs/?name=param#footer' +``` + +A classe `UrlImmutable` implementa a interface `JsonSerializable` e possui o método `__toString()`, então o objeto pode ser impresso ou usado em dados passados para `json_encode()`. -echo $newUrl; // 'http://nette.org:8080/en/?name=param#footer' +```php +echo $url; +echo json_encode([$url]); ``` -Os seguintes métodos estão disponíveis para obter ou alterar os componentes individuais da URL: + +Componentes da URL .[method] +---------------------------- + +Para retornar ou alterar os componentes individuais da URL, servem os métodos: .[language-php] -| Wither| Getter| Valor devolvido +| Wither | Getter | Valor retornado |-------------------------------------------------------------------------------------------- -| `withScheme(string $scheme)`| `getScheme(): string`| `'http'` -| `withUser(string $user)`| `getUser(): string`| `'john'` -| `withPassword(string $password)`| `getPassword(): string`| `'xyz*12'` -| `withHost(string $host)`| `getHost(): string`| `'nette.org'` -| `withPort(int $port)`| `getPort(): ?int`| `8080` -| | `getDefaultPort(): ?int`| `80` -| `withPath(string $path)`| `getPath(): string`| `'/en/download'` -| `withQuery(string\|array $query)`| `getQuery(): string`| `'name=param'` -| `withFragment(string $fragment)`| `getFragment(): string`| `'footer'` -| | `getAuthority(): string`| `'nette.org:8080'` -| | `getHostUrl(): string`| `'http://nette.org:8080'` -| | `getAbsoluteUrl(): string` | URL completa - -Também podemos operar com parâmetros de consulta individuais usando: +| `withScheme(string $scheme)` | `getScheme(): string` | `'http'` +| `withUser(string $user)` | `getUser(): string` | `'john'` +| `withPassword(string $password)` | `getPassword(): string` | `'xyz*12'` +| `withHost(string $host)` | `getHost(): string` | `'nette.org'` +| `withPort(int $port)` | `getPort(): ?int` | `8080` +| | `getDefaultPort(): ?int` | `80` +| `withPath(string $path)` | `getPath(): string` | `'/en/download'` +| `withQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` +| `withFragment(string $fragment)` | `getFragment(): string` | `'footer'` +| | `getAuthority(): string` | `'john:xyz%2A12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | URL completa + +O método `withoutUserInfo()` remove `user` e `password`. + +Também podemos trabalhar com parâmetros de consulta individuais usando: .[language-php] -| Wither| Getter +| Wither | Getter |----------------------------------------------- -| `withQuery(string\|array $query)` | `getQueryParameters(): array` -| `withQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` +| `withQuery(string\|array $query)` | `getQueryParameters(): array` +| `withQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` -O método `getDomain(int $level = 2)` funciona da mesma forma que o método em `Url`. O método `withoutUserInfo()` remove `user` e `password`. -A classe `UrlImmutable` implementa a interface `JsonSerializable` e tem um método `__toString()` para que o objeto possa ser impresso ou usado em dados passados para `json_encode()`. +getDomain(int $level = 2): string .[method] +------------------------------------------- +Retorna a parte direita ou esquerda do host. Funciona assim se o host for `www.nette.org`: + +.[language-php] +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `''` + + +resolve(string $reference): UrlImmutable .[method]{data-version:3.3.2} +---------------------------------------------------------------------- +Deriva uma URL absoluta da mesma forma que um navegador processa links numa página HTML: +- se o link for uma URL absoluta (contém esquema), ele é usado sem alterações +- se o link começar com `//`, apenas o esquema da URL atual é adotado +- se o link começar com `/`, um caminho absoluto da raiz do domínio é criado +- em outros casos, a URL é construída relativamente ao caminho atual ```php -echo $url; -echo json_encode([$url]); +$url = new UrlImmutable('https://example.com/path/page'); +echo $url->resolve('../foo'); // 'https://example.com/foo' +echo $url->resolve('/bar'); // 'https://example.com/bar' +echo $url->resolve('sub/page.html'); // 'https://example.com/path/sub/page.html' +``` + + +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +Verifica se duas URLs são idênticas. + +```php +$url->isEqual('https://nette.org'); ``` -O método `isEqual(string|Url $anotherUrl): bool` testa se as duas URLs são idênticas. +UrlScript +========= -UrlScript .[#toc-urlscript] -=========================== +A classe [api:Nette\Http\UrlScript] é descendente de [#UrlImmutable] e a estende com outros componentes virtuais da URL, como o diretório raiz do projeto, etc. Assim como a classe pai, é um objeto imutável. -A classe [api:Nette\Http\UrlScript] é descendente de `UrlImmutable` e, além disso, distingue estas partes lógicas da URL: +O diagrama a seguir mostra os componentes que UrlScript reconhece: /--pre baseUrl basePath relativePath relativeUrl @@ -169,17 +244,23 @@ A classe [api:Nette\Http\UrlScript] é descendente de `UrlImmutable` e, além di scriptPath pathInfo \-- -Os seguintes métodos estão disponíveis para obter estas peças: +- `baseUrl` é o endereço URL base da aplicação, incluindo o domínio e a parte do caminho para o diretório raiz da aplicação +- `basePath` é a parte do caminho para o diretório raiz da aplicação +- `scriptPath` é o caminho para o script atual +- `relativePath` é o nome do script (eventualmente outros segmentos do caminho) relativo a basePath +- `relativeUrl` é toda a parte da URL após baseUrl, incluindo a query string e o fragmento. +- `pathInfo` hoje em dia é uma parte da URL pouco utilizada após o nome do script + +Para retornar partes da URL, estão disponíveis os métodos: .[language-php] -| Getter| Valor devolvido +| Getter | Valor retornado |------------------------------------------------ -| `getScriptPath(): string`| `'/admin/script.php'` -| `getBasePath(): string`| `'/admin/'` -| `getBaseUrl(): string`| `'http://nette.org/admin/'` -| `getRelativePath(): string`| `'script.php'` -| `getRelativeUrl(): string`| `'script.php/pathinfo/?name=param#footer'` -| `getPathInfo(): string`| `'/pathinfo/'` - - -Não criamos objetos `UrlScript` diretamente, mas o método [Nette\Http\Request::getUrl() |request] o devolve. +| `getScriptPath(): string` | `'/admin/script.php'` +| `getBasePath(): string` | `'/admin/'` +| `getBaseUrl(): string` | `'http://nette.org/admin/'` +| `getRelativePath(): string` | `'script.php'` +| `getRelativeUrl(): string` | `'script.php/pathinfo/?name=param#footer'` +| `getPathInfo(): string` | `'/pathinfo/'` + +Objetos `UrlScript` geralmente não são criados diretamente, mas são retornados pelo método [Nette\Http\Request::getUrl()|request] com os componentes já configurados corretamente para a requisição HTTP atual. diff --git a/http/ro/@home.texy b/http/ro/@home.texy index e47d78b398..77caa66684 100644 --- a/http/ro/@home.texy +++ b/http/ro/@home.texy @@ -2,13 +2,13 @@ Nette HTTP ********** .[perex] -Pachetul `nette/http` încapsulează [cererile |request] și [răspunsurile |response] [HTTP |request], lucrează cu [sesiuni |sessions], precum și cu [analiza și construirea de URL-uri |urls]. +Pachetul `nette/http` încapsulează [cererea HTTP|request] & [răspunsul|response], lucrul cu [sesiunile|sessions] și [parsarea și compunerea URL-urilor |urls]. -Instalare .[#toc-installation] ------------------------------- +Instalare +--------- -Descărcați și instalați pachetul folosind [Composer |best-practices:composer]: +Descărcați și instalați biblioteca folosind [Composer|best-practices:composer]: ```shell composer require nette/http diff --git a/http/ro/@left-menu.texy b/http/ro/@left-menu.texy index fbeef02dd7..df87435c9c 100644 --- a/http/ro/@left-menu.texy +++ b/http/ro/@left-menu.texy @@ -1,8 +1,8 @@ -Net HTTP -******** -- [Prezentare generală |@home] -- [Cerere HTTP |request] -- [Răspuns HTTP |response] +Nette HTTP +********** +- [Introducere |@home] +- [Cerere HTTP|request] +- [Răspuns HTTP|response] - [Sesiuni |Sessions] -- [Utilitar URL |urls] -- [Configurație |Configuration] +- [Utilități URL |urls] +- [Configurație |configuration] diff --git a/http/ro/@meta.texy b/http/ro/@meta.texy new file mode 100644 index 0000000000..9c744b37d6 --- /dev/null +++ b/http/ro/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentație Nette}} diff --git a/http/ro/configuration.texy b/http/ro/configuration.texy index daa66f397b..74ded7164e 100644 --- a/http/ro/configuration.texy +++ b/http/ro/configuration.texy @@ -1,43 +1,43 @@ -Configurarea HTTP -***************** +Configurare HTTP +**************** .[perex] Prezentare generală a opțiunilor de configurare pentru Nette HTTP. -Dacă nu utilizați întregul cadru, ci doar această bibliotecă, citiți [cum se încarcă configurația |bootstrap:]. +Dacă nu utilizați întregul framework, ci doar această bibliotecă, citiți [cum se încarcă configurația|bootstrap:]. -Antetele HTTP .[#toc-http-headers] -================================== +Antete HTTP +=========== ```neon http: - # antetele care sunt trimise cu fiecare cerere + # antete care sunt trimise cu fiecare cerere headers: X-Powered-By: MyCMS X-Content-Type-Options: nosniff X-XSS-Protection: '1; mode=block' # afectează antetul X-Frame-Options - frames: ... # (string|bool) valoarea implicită este "SAMEORIGIN". + frames: ... # (string|bool) implicit este 'SAMEORIGIN' ``` -Din motive de securitate, framework-ul trimite un antet `X-Frame-Options: SAMEORIGIN`, care spune că o pagină poate fi afișată în interiorul altei pagini (în elementul `<iframe>`) numai dacă aceasta se află pe același domeniu. Acest lucru poate fi nedorit în anumite situații (de exemplu, dacă dezvoltați o aplicație Facebook), astfel încât comportamentul poate fi modificat prin setarea cadrelor `frames: http://allowed-host.com`. +Framework-ul, din motive de securitate, trimite antetul `X-Frame-Options: SAMEORIGIN`, care specifică faptul că pagina poate fi afișată în interiorul altei pagini (în elementul `<iframe>`) doar dacă se află pe același domeniu. Acest lucru poate fi nedorit în anumite situații (de exemplu, dacă dezvoltați o aplicație pentru Facebook), comportamentul putând fi modificat prin setarea `frames: http://allowed-host.com` sau `frames: true`. -Politica de securitate a conținutului .[#toc-content-security-policy] ---------------------------------------------------------------------- +Content Security Policy +----------------------- -Headers `Content-Security-Policy` (denumită în continuare CSP) poate fi asamblată cu ușurință, iar descrierea acestora poate fi găsită în [descrierea CSP |https://content-security-policy.com]. Directivele CSP (cum ar fi `script-src`) pot fi scrise fie sub formă de șiruri de caractere, în conformitate cu specificațiile, fie sub formă de matrice de valori pentru o mai bună lizibilitate. În acest caz, nu este nevoie să se scrie ghilimele în jurul cuvintelor cheie, cum ar fi `'self'`. De asemenea, Nette va genera automat o valoare de `nonce`, astfel încât `'nonce-y4PopTLM=='` va fi trimis în antet. +Se pot construi ușor antetele `Content-Security-Policy` (în continuare CSP), descrierea lor o găsiți în [descrierea CSP |https://content-security-policy.com]. Directivele CSP (cum ar fi `script-src`) pot fi scrise fie ca șiruri conform specificației, fie ca array-uri de valori pentru o mai bună lizibilitate. Atunci nu este nevoie să puneți ghilimele în jurul cuvintelor cheie, cum ar fi `'self'`. Nette generează, de asemenea, automat valoarea `nonce`, astfel încât antetul va conține, de exemplu, `'nonce-y4PopTLM=='`. ```neon http: - # Politica de securitate a conținutului + # Content Security Policy csp: - # șir de caractere în conformitate cu specificația CSP + # șir în format conform specificației CSP default-src: "'self' https://example.com" - # matrice de valori + # array de valori script-src: - nonce - strict-dynamic @@ -49,18 +49,18 @@ http: block-all-mixed-content: false ``` -Utilizați `<script n:nonce>...</script>` în șabloane, iar valoarea nonce va fi completată automat. Realizarea de site-uri web securizate în Nette este foarte ușoară. +În șabloane utilizați `<script n:nonce>...</script>` și valoarea nonce se va completa automat. Crearea site-urilor web sigure în Nette este într-adevăr ușoară. -În mod similar, pot fi adăugate antetele `Content-Security-Policy-Report-Only` (care poate fi utilizat în paralel cu CSP) și [Feature Policy |https://developers.google.com/web/updates/2018/06/feature-policy]: +Similar se pot construi și antetele `Content-Security-Policy-Report-Only` (care pot fi utilizate concomitent cu CSP) și [Feature Policy|https://developers.google.com/web/updates/2018/06/feature-policy]: ```neon http: - # Raport privind politica de securitate a conținutului + # Content Security Policy Report-Only cspReportOnly: default-src: self report-uri: 'https://my-report-uri-endpoint' - # Politica privind caracteristicile + # Feature Policy featurePolicy: unsized-media: none geolocation: @@ -69,91 +69,103 @@ http: ``` -Cookie HTTP .[#toc-http-cookie] -------------------------------- +Cookie HTTP +----------- -Puteți modifica valorile implicite ale unor parametri din metodele [Nette\Http\Response::setCookie() |response#setCookie] și session. +Se pot modifica valorile implicite ale unor parametri ai metodei [Nette\Http\Response::setCookie() |response#setCookie] și ale sesiunii. ```neon http: - # domeniul de aplicare al cookie-urilor în funcție de cale - cookiePath: ... # (șir de caractere) valoarea implicită este "/ + # domeniul cookie-ului în funcție de cale + cookiePath: ... # (string) implicit este '/' - # ce gazde sunt autorizate să primească cookie-ul - cookieDomain: 'example.com' # (șir|domeniu) implicit la unset + # domenii care acceptă cookie-uri + cookieDomain: 'example.com' # (string|domain) implicit este nesetat - # pentru a trimite cookie-uri numai prin HTTPS? - cookieSecure: ... # (bool|auto) valoarea implicită este auto + # trimite cookie-uri doar prin HTTPS? + cookieSecure: ... # (bool|auto) implicit este auto - # dezactivează trimiterea cookie-ului pe care Nette îl folosește ca protecție împotriva CSRF - disableNetteCookie: ... # (bool) are valoarea implicită false + # dezactivează trimiterea cookie-ului utilizat de Nette pentru protecția CSRF + disableNetteCookie: ... # (bool) implicit este false ``` -Opțiunea `cookieDomain` determină domeniile (originile) care pot accepta cookie-uri. Dacă nu este specificată, cookie-ul este acceptat de același (sub)domeniu ca și cel stabilit de acesta, *excluzând* subdomeniile acestora. Dacă se specifică `cookieDomain`, atunci sunt incluse și subdomeniile. Prin urmare, specificarea `cookieDomain` este mai puțin restrictivă decât omiterea. +Atributul `cookieDomain` specifică ce domenii pot accepta cookie-uri. Dacă nu este specificat, cookie-ul este acceptat de același (sub)domeniu care l-a setat, *dar nu* și de subdomeniile sale. Dacă `cookieDomain` este specificat, sunt incluse și subdomeniile. Prin urmare, specificarea `cookieDomain` este mai puțin restrictivă decât omiterea sa. -De exemplu, dacă este setat `cookieDomain: nette.org`, cookie-ul este, de asemenea, disponibil pe toate subdomeniile ca `doc.nette.org`. Acest lucru poate fi realizat și cu valoarea specială `domain`, adică `cookieDomain: domain`. +De exemplu, cu `cookieDomain: nette.org`, cookie-urile sunt disponibile și pe toate subdomeniile precum `doc.nette.org`. Același lucru se poate realiza și cu valoarea specială `domain`, adică `cookieDomain: domain`. -Valoarea implicită a `cookieSecure` este `auto`, ceea ce înseamnă că, dacă site-ul web rulează pe HTTPS, cookie-urile vor fi trimise cu steagul `Secure` și, prin urmare, vor fi disponibile numai prin HTTPS. +Valoarea implicită `auto` pentru atributul `cookieSecure` înseamnă că, dacă site-ul rulează pe HTTPS, cookie-urile vor fi trimise cu flag-ul `Secure` și, prin urmare, vor fi disponibile doar prin HTTPS. -Proxy HTTP .[#toc-http-proxy] ------------------------------ +Proxy HTTP +---------- -Dacă site-ul rulează în spatele unui proxy HTTP, introduceți adresa IP a proxy-ului pentru ca detectarea conexiunilor HTTPS să funcționeze corect, precum și adresa IP a clientului. Adică, pentru ca [Nette\Http\Request::getRemoteAddress() |request#getRemoteAddress] și [isSecured() |request#isSecured] să returneze valorile corecte și pentru ca legăturile să fie generate cu protocolul `https:` în șabloane. +Dacă site-ul rulează în spatele unui proxy HTTP, specificați adresa sa IP pentru ca detectarea conexiunii prin HTTPS și a adresei IP a clientului să funcționeze corect. Adică, pentru ca funcțiile [Nette\Http\Request::getRemoteAddress() |request#getRemoteAddress] și [isSecured() |request#isSecured] să returneze valorile corecte și în șabloane să se genereze linkuri cu protocolul `https:`. ```neon http: - # Adresa IP, interval (de exemplu, 127.0.0.1/8) sau o serie de aceste valori - proxy: 127.0.0.1 # (string|string[]) valoarea implicită este niciuna + # Adresă IP, interval (ex. 127.0.0.1/8) sau array cu aceste valori + proxy: 127.0.0.1 # (string|string[]) implicit este nesetat ``` -Sesiunea .[#toc-session] -======================== +Sesiune +======= - [Setări |sessions] de bază pentru [sesiuni |sessions]: +Setări de bază pentru [sesiuni|sessions]: ```neon session: - # arată panoul de sesiune în Tracy Bar? - debugger: ... # (bool) valoarea implicită este false + # afișează panoul de sesiune în Tracy Bar? + debugger: ... # (bool) implicit este false - # timpul de inactivitate după care expiră sesiunea - expiration: 14 days # (string) valoarea implicită este "3 ore + # perioada de inactivitate după care sesiunea expiră + expiration: 14 days # (string) implicit este '3 hours' - # când să înceapă sesiunea? - autoStart: ... # (smart|always|never) valoarea implicită este "smart". + # când ar trebui să pornească sesiunea? + autoStart: ... # (smart|always|never) implicit este 'smart' # handler, serviciu care implementează interfața SessionHandlerInterface handler: @handlerService ``` -Opțiunea `autoStart` controlează momentul în care începe sesiunea. Valoarea `always` înseamnă că sesiunea este întotdeauna inițiată la pornirea aplicației. Valoarea `smart` înseamnă că sesiunea va fi pornită la pornirea aplicației numai dacă există deja sau în momentul în care dorim să citim sau să scriem în ea. În cele din urmă, valoarea `never` dezactivează pornirea automată a sesiunii. +Opțiunea `autoStart` controlează când trebuie să pornească sesiunea. Valoarea `always` înseamnă că sesiunea va porni întotdeauna la pornirea aplicației. Valoarea `smart` înseamnă că sesiunea va porni la începutul aplicației doar dacă există deja, sau în momentul în care dorim să citim sau să scriem în ea. Și, în final, valoarea `never` interzice pornirea automată a sesiunii. -De asemenea, puteți seta toate [directivele de sesiune |https://www.php.net/manual/en/session.configuration.php] PHP (în format camelCase) și, de asemenea, [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. Exemplu: +În plus, se pot seta toate [directivele de sesiune |https://www.php.net/manual/en/session.configuration.php] PHP (în format camelCase) și, de asemenea, [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. Exemplu: ```neon session: - # 'session.name' scris ca 'name' + # 'session.name' se scrie ca 'name' name: MYID - # 'session.save_path' scris ca 'savePath' + # 'session.save_path' se scrie ca 'savePath' savePath: "%tempDir%/sessions" ``` -Cookie de sesiune .[#toc-session-cookie] ----------------------------------------- +Cookie de sesiune +----------------- -Cookie-ul de sesiune este trimis cu aceiași parametri ca și [celelalte cookie-uri |#HTTP cookie], dar îi puteți modifica: +Cookie-ul de sesiune este trimis cu aceiași parametri ca [alte cookie-uri |#Cookie HTTP], dar îi puteți modifica pentru acesta: ```neon session: - # ce gazde sunt autorizate să primească cookie-ul - cookieDomain: 'example.com' # (șir|domeniu) + # domenii care acceptă cookie-uri + cookieDomain: 'example.com' # (string|domain) - # restricții la accesarea unei cereri de origine încrucișată - cookieSamesite: None # (Strict|Lax|None) valoarea implicită este Lax + # restricții la accesul de pe alt domeniu + cookieSamesite: None # (Strict|Lax|None) implicit este Lax ``` -Opțiunea `cookieSamesite` influențează dacă cookie-ul este trimis în cazul [cererilor cu origine înc |nette:glossary#SameSite cookie] rucișată, ceea ce oferă o anumită protecție împotriva atacurilor de falsificare a cererilor încrucișate pe site ([Cross-Site Request Forgery |nette:glossary#cross-site-request-forgery-csrf]). +Atributul `cookieSamesite` afectează dacă cookie-ul va fi trimis la [accesul de pe alt domeniu |nette:glossary#Cookie SameSite], ceea ce oferă o anumită protecție împotriva atacurilor [Cross-Site Request Forgery |nette:glossary#Cross-Site Request Forgery CSRF] (CSRF). + + +Servicii DI +=========== + +Aceste servicii sunt adăugate în containerul DI: + +| Nume | Tip | Descriere +|----------------------------------------------------- +| `http.request` | [api:Nette\Http\Request] | [Cerere HTTP| request] +| `http.response` | [api:Nette\Http\Response] | [Răspuns HTTP| response] +| `session.session` | [api:Nette\Http\Session] | [Gestionarea sesiunii| sessions] diff --git a/http/ro/request.texy b/http/ro/request.texy index 9579a906de..87c68f188b 100644 --- a/http/ro/request.texy +++ b/http/ro/request.texy @@ -2,84 +2,84 @@ Cerere HTTP *********** .[perex] -Nette încapsulează cererea HTTP în obiecte cu un API ușor de înțeles, oferind în același timp un filtru de curățare. +Nette încapsulează cererea HTTP în obiecte cu o API inteligibilă și, în același timp, oferă un filtru de igienizare. -O solicitare HTTP este un obiect [api:Nette\Http\Request], pe care îl obțineți trecându-l folosind [injecția de dependență |dependency-injection:passing-dependencies]. În prezentatori, pur și simplu apelați `$httpRequest = $this->getHttpRequest()`. +Cererea HTTP este reprezentată de obiectul [api:Nette\Http\Request]. Dacă lucrați cu Nette, acest obiect este creat automat de framework și îl puteți primi prin [injecție de dependențe |dependency-injection:passing-dependencies]. În presentere, este suficient să apelați metoda `$this->getHttpRequest()`. Dacă lucrați în afara Nette Framework, puteți crea obiectul folosind [#RequestFactory]. -Ceea ce este important este că Nette, atunci când [creează |#RequestFactory] acest obiect, curăță toți parametrii de intrare GET, POST și COOKIE, precum și URL-urile de caractere de control și secvențe UTF-8 invalide. Astfel, puteți continua să lucrați în siguranță cu datele. Datele curățate sunt apoi utilizate în prezentatori și formulare. +Un mare avantaj al Nette este că, la crearea obiectului, curăță automat toți parametrii de intrare GET, POST, COOKIE și, de asemenea, URL-ul de caractere de control și secvențe UTF-8 invalide. Cu aceste date puteți lucra în siguranță în continuare. Datele curățate sunt apoi utilizate în presentere și formulare. -→ [Instalare și cerințe |@home#Installation] +→ [Instalare și cerințe |@home#Instalare] -Nette\Http\Request .[#toc-nette-http-request] -============================================= +Nette\Http\Request +================== -Acest obiect este imuabil. Nu are setori, are doar un singur așa-numit wither `withUrl()`, care nu modifică obiectul, ci returnează o nouă instanță cu o valoare modificată. +Acest obiect este imuabil (nu poate fi modificat). Nu are setteri, are doar un așa-numit wither `withUrl()`, care nu modifică obiectul, ci returnează o nouă instanță cu valoarea modificată. withUrl(Nette\Http\UrlScript $url): Nette\Http\Request .[method] ---------------------------------------------------------------- -Returnează o clonă cu o adresă URL diferită. +Returnează o clonă cu o altă adresă URL. getUrl(): Nette\Http\UrlScript .[method] ---------------------------------------- -Returnează adresa URL a cererii ca obiect [UrlScript |urls#UrlScript]. +Returnează URL-ul cererii ca obiect [UrlScript |urls#UrlScript]. ```php $url = $httpRequest->getUrl(); -echo $url; // https://nette.org/en/documentation?action=edit +echo $url; // https://doc.nette.org/cs/?action=edit echo $url->getHost(); // nette.org ``` -Browserele nu trimit un fragment către server, astfel încât `$url->getFragment()` va returna un șir gol. +Atenție: browserele nu trimit fragmentul către server, așa că `$url->getFragment()` va returna un șir gol. -getQuery(string $key=null): string|array|null .[method] -------------------------------------------------------- -Returnează parametrii cererii GET: +getQuery(?string $key=null): string|array|null .[method] +-------------------------------------------------------- +Returnează parametrii GET ai cererii. ```php -$all = $httpRequest->getQuery(); // matrice cu toți parametrii URL -$id = $httpRequest->getQuery('id'); // returnează parametrul GET "id" (sau nul) +$all = $httpRequest->getQuery(); // returnează un array cu toți parametrii din URL +$id = $httpRequest->getQuery('id'); // returnează parametrul GET 'id' (sau null) ``` -getPost(string $key=null): string|array|null .[method] ------------------------------------------------------- -Returnează parametrii cererii POST: +getPost(?string $key=null): string|array|null .[method] +------------------------------------------------------- +Returnează parametrii POST ai cererii. ```php -$all = $httpRequest->getPost(); // matrice cu toți parametrii POST -$id = $httpRequest->getPost('id'); // returnează parametrul POST "id" (sau null) +$all = $httpRequest->getPost(); // returnează un array cu toți parametrii din POST +$id = $httpRequest->getPost('id'); // returnează parametrul POST 'id' (sau null) ``` getFile(string|string[] $key): Nette\Http\FileUpload|array|null .[method] ------------------------------------------------------------------------- -Returnează [încărcarea |#Uploaded Files] ca obiect [api:Nette\Http\FileUpload]: +Returnează [încărcarea |#Fișiere încărcate] ca obiect [api:Nette\Http\FileUpload]: ```php $file = $httpRequest->getFile('avatar'); -if ($file->hasFile()) { // a fost încărcat vreun fișier? +if ($file?->hasFile()) { // a fost încărcat vreun fișier? $file->getUntrustedName(); // numele fișierului trimis de utilizator - $file->getSanitizedName(); // numele fără caractere periculoase + $file->getSanitizedName(); // nume fără caractere periculoase } ``` -Specificați un tablou de chei pentru a accesa structura subarborelui. +Pentru a accesa structura imbricată, specificați un array de chei. ```php //<input type="file" name="my-form[details][avatar]" multiple> $file = $request->getFile(['my-form', 'details', 'avatar']); ``` -Deoarece nu puteți avea încredere în datele din exterior și, prin urmare, nu vă bazați pe forma structurii, această metodă este mai sigură decât `$request->getFiles()['my-form']['details']['avatar']`, care poate da greș. +Deoarece nu se poate avea încredere în datele din exterior și, prin urmare, nici în structura fișierelor, această metodă este mai sigură decât, de exemplu, `$request->getFiles()['my-form']['details']['avatar']`, care poate eșua. getFiles(): array .[method] --------------------------- -Returnează arborele de [fișiere de încărcare |#Uploaded Files] într-o structură normalizată, fiecare frunză fiind o instanță a [api:Nette\Http\FileUpload]: +Returnează arborele [tuturor încărcărilor |#Fișiere încărcate] într-o structură normalizată, ale cărei frunze sunt obiecte [api:Nette\Http\FileUpload]: ```php $files = $httpRequest->getFiles(); @@ -88,7 +88,7 @@ $files = $httpRequest->getFiles(); getCookie(string $key): string|array|null .[method] --------------------------------------------------- -Returnează un cookie sau `null` dacă acesta nu există. +Returnează cookie-ul sau `null` dacă nu există. ```php $sessId = $httpRequest->getCookie('sess_id'); @@ -97,7 +97,7 @@ $sessId = $httpRequest->getCookie('sess_id'); getCookies(): array .[method] ----------------------------- -Returnează toate cookie-urile: +Returnează toate cookie-urile. ```php $cookies = $httpRequest->getCookies(); @@ -109,13 +109,13 @@ getMethod(): string .[method] Returnează metoda HTTP cu care a fost făcută cererea. ```php -echo $httpRequest->getMethod(); // GET, POST, HEAD, PUT +$httpRequest->getMethod(); // GET, POST, HEAD, PUT ``` isMethod(string $method): bool .[method] ---------------------------------------- -Verifică metoda HTTP cu care a fost efectuată cererea. Parametrul nu ține cont de majuscule și minuscule. +Testează metoda HTTP cu care a fost făcută cererea. Parametrul este case-insensitive. ```php if ($httpRequest->isMethod('GET')) // ... @@ -124,7 +124,7 @@ if ($httpRequest->isMethod('GET')) // ... getHeader(string $header): ?string .[method] -------------------------------------------- -Returnează un antet HTTP sau `null` dacă acesta nu există. Parametrul nu ține cont de majuscule și minuscule: +Returnează antetul HTTP sau `null` dacă nu există. Parametrul este case-insensitive. ```php $userAgent = $httpRequest->getHeader('User-Agent'); @@ -133,7 +133,7 @@ $userAgent = $httpRequest->getHeader('User-Agent'); getHeaders(): array .[method] ----------------------------- -Returnează toate antetele HTTP ca matrice asociativă: +Returnează toate antetele HTTP ca un array asociativ. ```php $headers = $httpRequest->getHeaders(); @@ -141,19 +141,14 @@ echo $headers['Content-Type']; ``` -getReferer(): ?Nette\Http\UrlImmutable .[method] ------------------------------------------------- -De la ce URL a venit utilizatorul? Atenție, nu este deloc de încredere. - - isSecured(): bool .[method] --------------------------- -Conexiunea este criptată (HTTPS)? Este posibil să fie necesar să configurați [un proxy |configuration#HTTP proxy] pentru o funcționalitate corespunzătoare. +Este conexiunea criptată (HTTPS)? Pentru o funcționare corectă, poate fi necesar să [configurați proxy-ul |configuration#Proxy HTTP]. isSameSite(): bool .[method] ---------------------------- -Solicitarea provine de la același (sub)domeniu și este inițiată prin apăsarea unui link? Nette utilizează cookie-ul `_nss` (fost `nette-samesite`) pentru a detecta acest lucru. +Cererea provine de pe același (sub)domeniu și este inițiată printr-un clic pe un link? Nette utilizează cookie-ul `_nss` (anterior `nette-samesite`) pentru detectare. isAjax(): bool .[method] @@ -163,17 +158,17 @@ Este o cerere AJAX? getRemoteAddress(): ?string .[method] ------------------------------------- -Returnează adresa IP a utilizatorului. Este posibil să fie necesar să configurați [un proxy |configuration#HTTP proxy] pentru o funcționalitate corespunzătoare. +Returnează adresa IP a utilizatorului. Pentru o funcționare corectă, poate fi necesar să [configurați proxy-ul |configuration#Proxy HTTP]. getRemoteHost(): ?string .[method deprecated] --------------------------------------------- -Returnează traducerea DNS a adresei IP a utilizatorului. Este posibil să fie necesar să configurați [un proxy |configuration#HTTP proxy] pentru o funcționalitate corespunzătoare. +Returnează rezoluția DNS a adresei IP a utilizatorului. Pentru o funcționare corectă, poate fi necesar să [configurați proxy-ul |configuration#Proxy HTTP]. -getBasicCredentials(): ?string .[method] ----------------------------------------- -Returnează acreditările de [autentificare HTTP de bază |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication]. +getBasicCredentials(): ?array .[method] +--------------------------------------- +Returnează datele de autentificare pentru [Basic HTTP authentication |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication]. ```php [$user, $password] = $httpRequest->getBasicCredentials(); @@ -182,7 +177,7 @@ Returnează acreditările de [autentificare HTTP de bază |https://developer.moz getRawBody(): ?string .[method] ------------------------------- -Returnează corpul solicitării HTTP: +Returnează corpul cererii HTTP. ```php $body = $httpRequest->getRawBody(); @@ -191,68 +186,75 @@ $body = $httpRequest->getRawBody(); detectLanguage(array $langs): ?string .[method] ----------------------------------------------- -Detectează limba. Ca parametru `$lang`, se trece o matrice de limbi pe care aplicația le acceptă, iar acesta o returnează pe cea preferată de browser. Nu este vorba de magie, metoda folosește doar antetul `Accept-Language`. Dacă nu se găsește nicio potrivire, se returnează `null`. +Detectează limba. Ca parametru `$lang`, transmitem un array cu limbile suportate de aplicație, iar aceasta va returna limba preferată de browserul vizitatorului. Nu este magie, ci doar utilizează antetul `Accept-Language`. Dacă nu există nicio potrivire, returnează `null`. ```php -// Antetul trimis de browser: Accept-Language: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3 +// browserul trimite, de ex., Accept-Language: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3 -$langs = ['hu', 'pl', 'en']; // Limbi acceptate în aplicație +$langs = ['hu', 'pl', 'en']; // limbi suportate de aplicație echo $httpRequest->detectLanguage($langs); // en ``` -RequestFactory .[#toc-requestfactory] -===================================== +RequestFactory +============== -Obiectul cererii HTTP curente este creat de [api:Nette\Http\RequestFactory]. Dacă scrieți o aplicație care nu utilizează un container DI, creați o cerere după cum urmează: +Clasa [api:Nette\Http\RequestFactory] servește la crearea unei instanțe `Nette\Http\Request`, care reprezintă cererea HTTP curentă. (Dacă lucrați cu Nette, obiectul cererii HTTP este creat automat de framework.) ```php $factory = new Nette\Http\RequestFactory; $httpRequest = $factory->fromGlobals(); ``` -RequestFactory poate fi configurat înainte de a apela `fromGlobals()`. Putem dezactiva toate măsurile de igienizare a parametrilor de intrare din secvențe UTF-8 invalide folosind `$factory->setBinary()`. Și, de asemenea, să configurăm un server proxy, care este important pentru detectarea corectă a adresei IP a utilizatorului folosind `$factory->setProxy(...)`. +Metoda `fromGlobals()` creează obiectul cererii pe baza variabilelor globale PHP curente (`$_GET`, `$_POST`, `$_COOKIE`, `$_FILES` și `$_SERVER`). La crearea obiectului, curăță automat toți parametrii de intrare GET, POST, COOKIE și, de asemenea, URL-ul de caractere de control și secvențe UTF-8 invalide, ceea ce asigură siguranța în lucrul ulterior cu aceste date. + +RequestFactory poate fi configurat înainte de a apela `fromGlobals()`: -Este posibil să curățăm URL-urile de caracterele care pot intra în ele din cauza sistemelor de comentarii prost implementate pe diverse alte site-uri web prin utilizarea filtrelor: +- prin metoda `$factory->setBinary()` dezactivați curățarea automată a parametrilor de intrare de caractere de control și secvențe UTF-8 invalide. +- prin metoda `$factory->setProxy(...)` specificați adresa IP a [serverului proxy |configuration#Proxy HTTP], ceea ce este necesar pentru detectarea corectă a adresei IP a utilizatorului. + +RequestFactory permite definirea filtrelor care transformă automat părți ale URL-ului cererii. Aceste filtre elimină caracterele nedorite din URL, care pot fi introduse acolo, de exemplu, printr-o implementare incorectă a sistemelor de comentarii pe diverse site-uri web: ```php -// eliminați spațiile din cale +// eliminarea spațiilor din cale $requestFactory->urlFilters['path']['%20'] = ''; -// elimină punctul, virgula sau paranteza dreaptă de la sfârșitul URL-ului +// eliminarea punctului, virgulei sau parantezei drepte de la sfârșitul URI-ului $requestFactory->urlFilters['url']['[.,)]$'] = ''; -// curăță calea de slash-uri duplicate (filtru implicit) +// curățarea căii de slash-uri duplicate (filtru implicit) $requestFactory->urlFilters['path']['/{2,}'] = '/'; ``` +Prima cheie `'path'` sau `'url'` specifică la ce parte a URL-ului se aplică filtrul. A doua cheie este expresia regulată care trebuie căutată, iar valoarea este înlocuirea care se utilizează în locul textului găsit. + -Fișiere încărcate .[#toc-uploaded-files] -======================================== +Fișiere încărcate +================= -Metoda `Nette\Http\Request::getFiles()` returnează un arbore de fișiere încărcate într-o structură normalizată, fiecare frunză fiind o instanță de [api:Nette\Http\FileUpload]. Aceste obiecte încapsulează datele trimise de către `<input type=file>` element de formular. +Metoda `Nette\Http\Request::getFiles()` returnează un array cu toate încărcările într-o structură normalizată, ale cărei frunze sunt obiecte [api:Nette\Http\FileUpload]. Acestea încapsulează datele trimise de elementul de formular `<input type=file>`. -Structura reflectă denumirea elementelor din HTML. În cel mai simplu exemplu, acesta ar putea fi un singur element de formular cu nume, trimis ca: +Structura reflectă denumirea elementelor în HTML. În cel mai simplu caz, poate fi un singur element de formular numit, trimis ca: ```latte <input type="file" name="avatar"> ``` -În acest caz, `$request->getFiles()` returnează o matrice: +În acest caz, `$request->getFiles()` returnează un array: ```php [ - 'avatar' => /* FileUpload instance */ + 'avatar' => /* Instanță FileUpload */ ] ``` -Obiectul `FileUpload` este creat chiar dacă utilizatorul nu a încărcat niciun fișier sau dacă încărcarea a eșuat. Metoda `hasFile()` returnează true în cazul în care a fost trimis un fișier: +Obiectul `FileUpload` este creat chiar și în cazul în care utilizatorul nu a trimis niciun fișier sau trimiterea a eșuat. Dacă fișierul a fost trimis, returnează metoda `hasFile()`: ```php -$request->getFile('avatar')->hasFile(); +$request->getFile('avatar')?->hasFile(); ``` -În cazul unei intrări care utilizează notația de matrice pentru nume: +În cazul numelui elementului care utilizează notația pentru array-uri: ```latte <input type="file" name="my-form[details][avatar]"> @@ -264,16 +266,16 @@ arborele returnat arată astfel: [ 'my-form' => [ 'details' => [ - 'avatar' => /* FileUpload instance */ + 'avatar' => /* Instanță FileUpload */ ], ], ] ``` -De asemenea, puteți crea array-uri de fișiere: +Se poate crea și un array de fișiere: ```latte -<input type="file" name="my-form[details][avatars][] multiple"> +<input type="file" name="my-form[details][avatars][]" multiple> ``` În acest caz, structura arată astfel: @@ -283,16 +285,16 @@ De asemenea, puteți crea array-uri de fișiere: 'my-form' => [ 'details' => [ 'avatars' => [ - 0 => /* FileUpload instance */, - 1 => /* FileUpload instance */, - 2 => /* FileUpload instance */, + 0 => /* Instanță FileUpload */, + 1 => /* Instanță FileUpload */, + 2 => /* Instanță FileUpload */, ], ], ], ] ``` -Cel mai bun mod de a accesa indexul 1 al unei matrice imbricate este după cum urmează: +Accesarea indexului 1 al array-ului imbricat se face cel mai bine astfel: ```php $file = $request->getFile(['my-form', 'details', 'avatars', 1]); @@ -301,7 +303,7 @@ if ($file instanceof FileUpload) { } ``` -Deoarece nu puteți avea încredere în datele din exterior și, prin urmare, nu vă bazați pe forma structurii, această metodă este mai sigură decât `$request->getFiles()['my-form']['details']['avatars'][1]`, care poate da greș. +Deoarece nu se poate avea încredere în datele din exterior și, prin urmare, nici în structura fișierelor, această metodă este mai sigură decât, de exemplu, `$request->getFiles()['my-form']['details']['avatars'][1]`, care poate eșua. Prezentare generală a metodelor `FileUpload` .{toc: FileUpload} @@ -320,12 +322,12 @@ Returnează `true` dacă fișierul a fost încărcat cu succes. getError(): int .[method] ------------------------- -Returnează codul de eroare asociat cu fișierul încărcat. Acesta este una dintre constantele [UPLOAD_ERR_XXX |http://php.net/manual/en/features.file-upload.errors.php]. În cazul în care fișierul a fost încărcat cu succes, se returnează `UPLOAD_ERR_OK`. +Returnează codul de eroare la încărcarea fișierului. Este una dintre constantele [UPLOAD_ERR_XXX|http://php.net/manual/en/features.file-upload.errors.php]. Dacă încărcarea a avut succes, returnează `UPLOAD_ERR_OK`. move(string $dest) .[method] ---------------------------- -Mută un fișier încărcat într-o nouă locație. Dacă fișierul de destinație există deja, acesta va fi suprascris. +Mută fișierul încărcat într-o nouă locație. Dacă fișierul țintă există deja, acesta va fi suprascris. ```php $file->move('/path/to/files/name.ext'); @@ -334,12 +336,12 @@ $file->move('/path/to/files/name.ext'); getContents(): ?string .[method] -------------------------------- -Returnează conținutul fișierului încărcat. În cazul în care încărcarea nu a avut succes, se returnează `null`. +Returnează conținutul fișierului încărcat. Dacă încărcarea nu a avut succes, returnează `null`. getContentType(): ?string .[method] ----------------------------------- -Detectează tipul de conținut MIME al fișierului încărcat pe baza semnăturii acestuia. În cazul în care încărcarea nu a avut succes sau detectarea a eșuat, se returnează `null`. +Detectează tipul de conținut MIME al fișierului încărcat pe baza semnăturii sale. Dacă încărcarea nu a avut succes sau detectarea a eșuat, returnează `null`. .[caution] Necesită extensia PHP `fileinfo`. @@ -347,38 +349,49 @@ Necesită extensia PHP `fileinfo`. getUntrustedName(): string .[method] ------------------------------------ -Returnează numele original al fișierului, așa cum a fost transmis de către browser. +Returnează numele original al fișierului, așa cum a fost trimis de browser. .[caution] -Nu aveți încredere în valoarea returnată de această metodă. Un client ar putea trimite un nume de fișier malițios cu intenția de a vă corupe sau de a vă sparge aplicația. +Nu aveți încredere în valoarea returnată de această metodă. Clientul ar fi putut trimite un nume de fișier dăunător cu intenția de a deteriora sau de a pirata aplicația dvs. getSanitizedName(): string .[method] ------------------------------------ -Returnează numele de fișier curățat. Acesta conține numai caractere ASCII `[a-zA-Z0-9.-]`. Dacă numele nu conține astfel de caractere, se returnează "necunoscut". Dacă fișierul este o imagine JPEG, PNG, GIF sau WebP, se returnează extensia corectă a fișierului. +Returnează numele de fișier igienizat. Conține doar caractere ASCII `[a-zA-Z0-9.-]`. Dacă numele nu conține astfel de caractere, returnează `'unknown'`. Dacă fișierul este o imagine în format JPEG, PNG, GIF, WebP sau AVIF, returnează și extensia corectă. + +.[caution] +Necesită extensia PHP `fileinfo`. + + +getSuggestedExtension(): ?string .[method]{data-version:3.2.4} +-------------------------------------------------------------- +Returnează extensia de fișier potrivită (fără punct) corespunzătoare tipului MIME detectat. + +.[caution] +Necesită extensia PHP `fileinfo`. getUntrustedFullPath(): string .[method] ---------------------------------------- -Returnează calea completă originală, așa cum a fost transmisă de browser în timpul încărcării directorului. Calea completă este disponibilă numai în PHP 8.1 și versiunile ulterioare. În versiunile anterioare, această metodă returnează numele fișierului care nu este de încredere. +Returnează calea originală a fișierului, așa cum a fost trimisă de browser la încărcarea unui folder. Calea completă este disponibilă numai în PHP 8.1 și versiunile ulterioare. În versiunile anterioare, această metodă returnează numele original al fișierului. .[caution] -Nu aveți încredere în valoarea returnată de această metodă. Un client ar putea trimite un nume de fișier malițios cu intenția de a vă corupe sau de a vă pirata aplicația. +Nu aveți încredere în valoarea returnată de această metodă. Clientul ar fi putut trimite un nume de fișier dăunător cu intenția de a deteriora sau de a pirata aplicația dvs. getSize(): int .[method] ------------------------ -Returnează dimensiunea fișierului încărcat. În cazul în care încărcarea nu a avut succes, se returnează `0`. +Returnează dimensiunea fișierului încărcat. Dacă încărcarea nu a avut succes, returnează `0`. getTemporaryFile(): string .[method] ------------------------------------ -Returnează calea de acces la locația temporară a fișierului încărcat. În cazul în care încărcarea nu a avut succes, se returnează `''`. +Returnează calea către locația temporară a fișierului încărcat. Dacă încărcarea nu a avut succes, returnează `''`. isImage(): bool .[method] ------------------------- -Returnează `true` în cazul în care fișierul încărcat este o imagine JPEG, PNG, GIF sau WebP. Detectarea se bazează pe semnătura acestuia. Integritatea întregului fișier nu este verificată. Puteți afla dacă o imagine nu este coruptă, de exemplu, încercând să [o încărcați |#toImage]. +Returnează `true` dacă fișierul încărcat este o imagine în format JPEG, PNG, GIF, WebP sau AVIF. Detectarea se bazează pe semnătura sa și nu verifică integritatea întregului fișier. Dacă imaginea este deteriorată poate fi determinat, de exemplu, încercând să o [încărcați |#toImage]. .[caution] Necesită extensia PHP `fileinfo`. @@ -386,9 +399,9 @@ Necesită extensia PHP `fileinfo`. getImageSize(): ?array .[method] -------------------------------- -Returnează o pereche de `[width, height]` cu dimensiunile imaginii încărcate. În cazul în care încărcarea nu a avut succes sau nu este o imagine validă, se returnează `null`. +Returnează o pereche `[lățime, înălțime]` cu dimensiunile imaginii încărcate. Dacă încărcarea nu a avut succes sau nu este o imagine validă, returnează `null`. toImage(): Nette\Utils\Image .[method] -------------------------------------- -Încarcă o imagine sub forma unui obiect [Image |utils:images]. În cazul în care încărcarea nu a avut succes sau nu este o imagine validă, se aruncă o excepție `Nette\Utils\ImageException`. +Încarcă imaginea ca obiect [Image|utils:images]. Dacă încărcarea nu a avut succes sau nu este o imagine validă, aruncă o excepție `Nette\Utils\ImageException`. diff --git a/http/ro/response.texy b/http/ro/response.texy index 6f315787f7..e5dcb254f3 100644 --- a/http/ro/response.texy +++ b/http/ro/response.texy @@ -2,22 +2,22 @@ Răspuns HTTP ************ .[perex] -Nette încapsulează răspunsul HTTP în obiecte cu un API ușor de înțeles, oferind în același timp un filtru de curățare. +Nette încapsulează răspunsul HTTP în obiecte cu o API inteligibilă. -Un răspuns HTTP este un obiect [api:Nette\Http\Response], pe care îl obțineți trecându-l cu ajutorul [injecției de dependență |dependency-injection:passing-dependencies]. În prezentatori, pur și simplu apelați `$httpResponse = $this->getHttpResponse()`. +Răspunsul HTTP este reprezentat de obiectul [api:Nette\Http\Response]. Dacă lucrați cu Nette, acest obiect este creat automat de framework și îl puteți primi prin [injecție de dependențe |dependency-injection:passing-dependencies]. În presentere, este suficient să apelați metoda `$this->getHttpResponse()`. -→ [Instalare și cerințe |@home#Installation] +→ [Instalare și cerințe |@home#Instalare] -Nette\Http\Răspuns .[#toc-nette-http-response] -============================================== +Nette\Http\Response +=================== -Spre deosebire de [Nette\Http\Request |request], acest obiect este mutabil, astfel încât puteți utiliza setori pentru a schimba starea, adică pentru a trimite antetele. Nu uitați că toți cei care setează **trebuie să fie apelați înainte de a trimite orice ieșire efectivă.** Metoda `isSent()` indică dacă au fost trimise ieșirile. Dacă returnează `true`, fiecare încercare de a trimite un antet aruncă o excepție `Nette\InvalidStateException`. +Obiectul, spre deosebire de [Nette\Http\Request|request], este mutabil, adică puteți modifica starea folosind setteri, de exemplu, trimițând antete. Nu uitați că toți setterii trebuie apelați **înainte de a trimite orice ieșire.** Dacă ieșirea a fost deja trimisă, indică metoda `isSent()`. Dacă returnează `true`, orice încercare de a trimite un antet va arunca o excepție `Nette\InvalidStateException`. -setCode(int $code, string $reason=null) .[method] -------------------------------------------------- -Modifică un [cod de răspuns de |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10] stare. Pentru o mai bună lizibilitate a codului sursă, se recomandă utilizarea unor [constante predefinite |api:Nette\Http\IResponse] în locul numerelor reale. +setCode(int $code, ?string $reason=null) .[method] +-------------------------------------------------- +Modifică [codul de stare al răspunsului |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10]. Pentru o mai bună lizibilitate a codului sursă, se recomandă utilizarea [constantelor predefinite |api:Nette\Http\IResponse] în loc de numere pentru cod. ```php $httpResponse->setCode(Nette\Http\Response::S404_NotFound); @@ -31,12 +31,12 @@ Returnează codul de stare al răspunsului. isSent(): bool .[method] ------------------------ -Returnează dacă antetele au fost deja trimise de la server către browser, astfel încât nu mai este posibil să se trimită antetele sau să se modifice codul de stare. +Returnează dacă antetele au fost deja trimise de la server la browser și, prin urmare, nu mai este posibil să se trimită antete sau să se modifice codul de stare. setHeader(string $name, string $value) .[method] ------------------------------------------------ -Trimite un antet HTTP și **subscrie** antetul cu același nume trimis anterior. +Trimite un antet HTTP și **suprascrie** antetul trimis anterior cu același nume. ```php $httpResponse->setHeader('Pragma', 'no-cache'); @@ -45,7 +45,7 @@ $httpResponse->setHeader('Pragma', 'no-cache'); addHeader(string $name, string $value) .[method] ------------------------------------------------ -Trimite un antet HTTP și **nu suprascrie** antetul cu același nume trimis anterior. +Trimite un antet HTTP și **nu suprascrie** antetul trimis anterior cu același nume. ```php $httpResponse->addHeader('Accept', 'application/json'); @@ -60,7 +60,7 @@ deleteHeader(string $name) .[method] getHeader(string $header): ?string .[method] -------------------------------------------- -Returnează antetul HTTP trimis, sau `null` dacă nu există. Parametrul nu ține cont de majuscule și minuscule. +Returnează antetul HTTP trimis sau `null` dacă nu există. Parametrul este case-insensitive. ```php $pragma = $httpResponse->getHeader('Pragma'); @@ -69,7 +69,7 @@ $pragma = $httpResponse->getHeader('Pragma'); getHeaders(): array .[method] ----------------------------- -Returnează toate antetele HTTP trimise ca matrice asociativă. +Returnează toate antetele HTTP trimise ca un array asociativ. ```php $headers = $httpResponse->getHeaders(); @@ -77,9 +77,9 @@ echo $headers['Pragma']; ``` -setContentType(string $type, string $charset=null) .[method] ------------------------------------------------------------- -Trimite antetul `Content-Type`. +setContentType(string $type, ?string $charset=null) .[method] +------------------------------------------------------------- +Modifică antetul `Content-Type`. ```php $httpResponse->setContentType('text/plain', 'UTF-8'); @@ -88,7 +88,7 @@ $httpResponse->setContentType('text/plain', 'UTF-8'); redirect(string $url, int $code=self::S302_Found): void .[method] ----------------------------------------------------------------- -Redirecționează către un alt URL. Nu uitați să ieșiți apoi din script. +Redirecționează către o altă adresă URL. Nu uitați să terminați scriptul după aceea. ```php $httpResponse->redirect('http://example.com'); @@ -98,52 +98,52 @@ exit; setExpiration(?string $time) .[method] -------------------------------------- -Stabilește data de expirare a documentului HTTP folosind anteturile `Cache-Control` și `Expires`. Parametrul este fie un interval de timp (ca text), fie `null`, care dezactivează memoria cache. +Setează expirarea documentului HTTP folosind antetele `Cache-Control` și `Expires`. Parametrul este fie un interval de timp (ca text), fie `null`, ceea ce dezactivează stocarea în cache. ```php -// cache-ul browserului expiră într-o oră +// cache-ul din browser va expira într-o oră $httpResponse->setExpiration('1 hour'); ``` sendAsFile(string $fileName) .[method] -------------------------------------- -Răspunsul trebuie descărcat cu dialogul *Save as* cu numele specificat. Nu trimite niciun fișier propriu-zis la ieșire. +Răspunsul va fi descărcat folosind caseta de dialog *Salvare ca* sub numele specificat. Fișierul în sine nu este trimis. ```php -$httpResponse->sendAsFile('invoice.pdf'); +$httpResponse->sendAsFile('factura.pdf'); ``` -setCookie(string $name, string $value, $time, string $path=null, string $domain=null, bool $secure=null, bool $httpOnly=null, string $sameSite=null) .[method] --------------------------------------------------------------------------------------------------------------------------------------------------------------- -Trimite un cookie. Valori implicite ale parametrilor: +setCookie(string $name, string $value, $time, ?string $path=null, ?string $domain=null, ?bool $secure=null, ?bool $httpOnly=null, ?string $sameSite=null) .[method] +------------------------------------------------------------------------------------------------------------------------------------------------------------------- +Trimite un cookie. Valorile implicite ale parametrilor: -| `$path` | `'/'` | cu domeniul de aplicare pentru toate căile de acces pe (sub)domeniu *(configurabil)*. -| `$domain` | `null` | cu domeniul de aplicare al (sub)domeniului curent, dar nu și al subdomeniilor acestuia *(configurabil)* -| `$secure` | `true` | dacă site-ul funcționează pe HTTPS, în caz contrar `false` *(configurabil)* -| `$httpOnly` | `true` | cookie-ul este inaccesibil pentru JavaScript -| `$sameSite` | `'Lax'` | cookie-ul nu trebuie să fie trimis atunci când este [accesat de la o altă origine |nette:glossary#SameSite cookie] +| `$path` | `'/'` | cookie-ul are acoperire pentru toate căile din (sub)domeniu *(configurabil)* +| `$domain` | `null` | ceea ce înseamnă cu acoperire pentru (sub)domeniul curent, dar nu și subdomeniile sale *(configurabil)* +| `$secure` | `true` | dacă site-ul rulează pe HTTPS, altfel `false` *(configurabil)* +| `$httpOnly` | `true` | cookie-ul este inaccesibil pentru JavaScript +| `$sameSite` | `'Lax'` | cookie-ul poate să nu fie trimis la [accesul de pe alt domeniu |nette:glossary#Cookie SameSite] -Puteți modifica valorile implicite ale parametrilor `$path`, `$domain` și `$secure` în [configuration |configuration#HTTP cookie]. +Valorile implicite ale parametrilor `$path`, `$domain` și `$secure` le puteți modifica în [configurație |configuration#Cookie HTTP]. -Timpul poate fi specificat ca număr de secunde sau ca un șir de caractere: +Timpul poate fi specificat ca număr de secunde sau șir: ```php -$httpResponse->setCookie('lang', 'en', '100 days'); +$httpResponse->setCookie('lang', 'ro', '100 days'); ``` -Opțiunea `$domain` determină domeniile (originile) care pot accepta cookie-uri. Dacă nu este specificată, cookie-ul este acceptat de același (sub)domeniu ca și cel stabilit de acesta, cu excepția subdomeniilor acestora. Dacă este specificată `$domain`, atunci sunt incluse și subdomeniile. Prin urmare, specificarea `$domain` este mai puțin restrictivă decât omiterea. De exemplu, dacă `$domain = 'nette.org'`, cookie-ul este, de asemenea, acceptat pe toate subdomeniile ca `doc.nette.org`. +Parametrul `$domain` specifică ce domenii pot accepta cookie-uri. Dacă nu este specificat, cookie-ul este acceptat de același (sub)domeniu care l-a setat, dar nu și de subdomeniile sale. Dacă `$domain` este specificat, sunt incluse și subdomeniile. Prin urmare, specificarea `$domain` este mai puțin restrictivă decât omiterea sa. De exemplu, cu `$domain = 'nette.org'`, cookie-urile sunt disponibile și pe toate subdomeniile precum `doc.nette.org`. -Puteți utiliza constantele `Response::SameSiteLax`, `SameSiteStrict` și `SameSiteNone` pentru valoarea `$sameSite`. +Pentru valoarea `$sameSite` puteți utiliza constantele `Response::SameSiteLax`, `SameSiteStrict` și `SameSiteNone`. -deleteCookie(string $name, string $path=null, string $domain=null, bool $secure=null): void .[method] ------------------------------------------------------------------------------------------------------ -Șterge un modul cookie. Valorile implicite ale parametrilor sunt: -- `$path` cu domeniul de aplicare la toate directoarele (`'/'`) -- `$domain` cu domeniul de aplicare al (sub)domeniului curent, dar nu și al subdomeniilor sale -- `$secure` este afectat de setările din [configurația#HTTP cookie |configuration#HTTP cookie] +deleteCookie(string $name, ?string $path=null, ?string $domain=null, ?bool $secure=null): void .[method] +-------------------------------------------------------------------------------------------------------- +Șterge un cookie. Valorile implicite ale parametrilor sunt: +- `$path` cu acoperire pentru toate directoarele (`'/'`) +- `$domain` cu acoperire pentru (sub)domeniul curent, dar nu și subdomeniile sale +- `$secure` se ghidează după setările din [configurație |configuration#Cookie HTTP] ```php $httpResponse->deleteCookie('lang'); diff --git a/http/ro/sessions.texy b/http/ro/sessions.texy index 60dbff9906..02af57148e 100644 --- a/http/ro/sessions.texy +++ b/http/ro/sessions.texy @@ -3,69 +3,69 @@ Sesiuni <div class=perex> -HTTP este un protocol fără stare, dar aproape toate aplicațiile trebuie să păstreze o stare între cereri, de exemplu, conținutul unui coș de cumpărături. Pentru aceasta se folosește o sesiune. Să vedem +HTTP este un protocol fără stare, însă aproape orice aplicație are nevoie să păstreze starea între cereri, de exemplu conținutul coșului de cumpărături. Tocmai pentru aceasta servesc sesiunile. Vom arăta, -- cum se utilizează sesiunile -- cum să evităm conflictele de denumire -- cum se stabilește expirarea +- cum să utilizați sesiunile +- cum să preveniți conflictele de nume +- cum să setați expirarea </div> -Atunci când se utilizează sesiuni, fiecare utilizator primește un identificator unic numit ID de sesiune, care este transmis într-un modul cookie. Acesta servește drept cheie pentru datele sesiunii. Spre deosebire de modulele cookie, care sunt stocate pe partea browserului, datele de sesiune sunt stocate pe partea serverului. +La utilizarea sesiunilor, fiecare utilizator primește un identificator unic numit ID de sesiune, care este transmis într-un cookie. Acesta servește drept cheie pentru datele sesiunii. Spre deosebire de cookie-uri, care sunt stocate pe partea browserului, datele din sesiune sunt stocate pe partea serverului. -Noi [configurăm |configuration#session] sesiunea în [configurare |configuration#session], alegerea timpului de expirare este importantă. +Sesiunea o setăm în [configurație |configuration#Sesiune], importantă fiind în special alegerea timpului de expirare. -Sesiunea este gestionată de obiectul [api:Nette\Http\Session], pe care îl obțineți trecându-l folosind [injecția de dependență |dependency-injection:passing-dependencies]. În prezentatori, pur și simplu apelați `$session = $this->getSession()`. +Gestionarea sesiunii este responsabilitatea obiectului [api:Nette\Http\Session], la care ajungeți solicitându-l prin [injecție de dependențe |dependency-injection:passing-dependencies]. În presentere, este suficient să apelați `$session = $this->getSession()`. -→ [Instalare și cerințe |@home#Installation] +→ [Instalare și cerințe |@home#Instalare] -Pornirea sesiunii .[#toc-starting-session] -========================================== +Pornirea sesiunii +================= -În mod implicit, Nette va porni automat o sesiune în momentul în care începem să citim din ea sau să scriem date în ea. Pentru a porni manual o sesiune, utilizați `$session->start()`. +Nette, în setarea implicită, pornește automat sesiunea în momentul în care începem să citim sau să scriem date în ea. Manual, sesiunea se pornește folosind `$session->start()`. -PHP trimite antetele HTTP care afectează memoria cache atunci când începe sesiunea, a se vedea [php:session_cache_limiter], și, eventual, un cookie cu ID-ul sesiunii. Prin urmare, este întotdeauna necesar să se pornească sesiunea înainte de a trimite orice ieșire către browser, în caz contrar va fi lansată o excepție. Prin urmare, dacă știți că o sesiune va fi utilizată în timpul redării paginii, porniți-o manual înainte, de exemplu în prezentator. +PHP trimite la pornirea sesiunii antete HTTP care afectează stocarea în cache, vezi [php:session_cache_limiter], și eventual și un cookie cu ID-ul sesiunii. De aceea, este necesar să porniți întotdeauna sesiunea înainte de a trimite orice ieșire către browser, altfel se va arunca o excepție. Deci, dacă știți că în timpul randării paginii se va utiliza sesiunea, porniți-o manual înainte, de exemplu în presenter. -În modul dezvoltator, Tracy pornește sesiunea, deoarece o folosește pentru a afișa barele de redirecționare și de cereri AJAX în bara Tracy. +În modul de dezvoltare, Tracy pornește sesiunea, deoarece o utilizează pentru afișarea barelor cu redirecționări și cereri AJAX în Tracy Bar. -Secțiunea .[#toc-section] -========================= +Secțiuni +======== -În PHP pur, stocarea datelor de sesiune este implementată ca o matrice accesibilă prin intermediul unei variabile globale `$_SESSION`. Problema este că, în mod normal, aplicațiile sunt formate din mai multe părți independente și, dacă toate au la dispoziție doar același array, mai devreme sau mai târziu va avea loc o coliziune de nume. +În PHP pur, stocarea datelor sesiunii este realizată ca un array accesibil prin variabila globală `$_SESSION`. Problema este că aplicațiile sunt compuse în mod obișnuit dintr-o serie de părți independente reciproc și dacă toate au la dispoziție doar un singur array, mai devreme sau mai târziu va apărea o coliziune de nume. -Nette Framework rezolvă problema prin împărțirea întregului spațiu în secțiuni (obiecte [api:Nette\Http\SessionSection]). Fiecare unitate utilizează apoi propria secțiune cu un nume unic și nu mai pot apărea coliziuni. +Nette Framework rezolvă problema împărțind întregul spațiu în secțiuni (obiecte [api:Nette\Http\SessionSection]). Fiecare unitate utilizează apoi propria secțiune cu un nume unic și nicio coliziune nu mai poate avea loc. -Obținem secțiunea de la managerul de sesiune: +Obținem secțiunea din sesiune: ```php -$section = $session->getSection('unique name'); +$section = $session->getSection('nume-unic'); ``` -În prezentator este suficient să se apeleze `getSession()` cu parametrul: +În presenter este suficient să folosim `getSession()` cu parametru: ```php -// $this este prezentator -$section = $this->getSession('unique name'); +// $this este Presenter +$section = $this->getSession('nume-unic'); ``` -Existența secțiunii poate fi verificată prin metoda `$session->hasSection('unique name')`. +Existența secțiunii poate fi verificată cu metoda `$session->hasSection('nume-unic')`. -Secțiunea în sine este foarte ușor de lucrat cu ajutorul metodelor `set()`, `get()` și `remove()`: +Cu secțiunea însăși se lucrează apoi foarte ușor folosind metodele `set()`, `get()` și `remove()`: ```php // scriere variabilă $section->set('userName', 'franta'); -// citirea unei variabile, returnează null dacă aceasta nu există +// citire variabilă, returnează null dacă nu există echo $section->get('userName'); -// eliminarea variabilei +// anulare variabilă $section->remove('userName'); ``` -Este posibil să se utilizeze ciclul `foreach` pentru a obține toate variabilele din secțiune: +Pentru a obține toate variabilele dintr-o secțiune, se poate utiliza bucla `foreach`: ```php foreach ($section as $key => $val) { @@ -74,78 +74,78 @@ foreach ($section as $key => $val) { ``` -Cum se stabilește expirarea .[#toc-how-to-set-expiration] ---------------------------------------------------------- +Setarea expirării +----------------- -Expirarea poate fi setată pentru secțiuni individuale sau chiar pentru variabile individuale. Putem permite ca autentificarea utilizatorului să expire în 20 de minute, dar să ne amintim în continuare conținutul unui coș de cumpărături. +Pentru secțiuni individuale sau chiar variabile individuale se poate seta expirarea. Putem astfel lăsa autentificarea utilizatorului să expire după 20 de minute, dar în același timp să păstrăm conținutul coșului. ```php -// secțiunea va expira după 20 de minute +// secțiunea expiră după 20 de minute $section->setExpiration('20 minutes'); ``` -Al treilea parametru al metodei `set()` este utilizat pentru a seta expirarea variabilelor individuale: +Pentru setarea expirării variabilelor individuale servește al treilea parametru al metodei `set()`: ```php -// variabila "flash" expiră după 30 de secunde +// variabila 'flash' va expira după 30 de secunde $section->set('flash', $message, '30 seconds'); ``` .[note] -Nu uitați că timpul de expirare al întregii sesiuni (a se vedea [configurația sesiunii |configuration#session]) trebuie să fie egal sau mai mare decât timpul stabilit pentru secțiunile sau variabilele individuale. +Nu uitați că timpul de expirare al întregii sesiuni (vezi [configurarea sesiunii |configuration#Sesiune]) trebuie să fie egal sau mai mare decât timpul setat pentru secțiunile sau variabilele individuale. -Anularea expirării setate anterior poate fi realizată prin metoda `removeExpiration()`. Ștergerea imediată a întregii secțiuni va fi asigurată prin metoda `remove()`. +Anularea expirării setate anterior se realizează cu metoda `removeExpiration()`. Anularea imediată a întregii secțiuni este asigurată de metoda `remove()`. -Evenimentele $onStart, $onBeforeWrite .[#toc-events-onstart-onbeforewrite] --------------------------------------------------------------------------- +Evenimentele $onStart, $onBeforeWrite +------------------------------------- -Obiectul `Nette\Http\Session` are [evenimentele |nette:glossary#Events] `$onStart` a `$onBeforeWrite`, astfel încât puteți adăuga callback-uri care sunt apelate după ce sesiunea începe sau înainte ca aceasta să fie scrisă pe disc și apoi terminată. +Obiectul `Nette\Http\Session` are [evenimente |nette:glossary#Evenimente] `$onStart` și `$onBeforeWrite`, deci puteți adăuga callback-uri care se declanșează după pornirea sesiunii sau înainte de scrierea ei pe disc și închiderea ulterioară. ```php $session->onBeforeWrite[] = function () { - // scrierea datelor în sesiune + // scriem datele în sesiune $this->section->set('basket', $this->basket); }; ``` -Gestionarea sesiunilor .[#toc-session-management] -================================================= +Gestionarea sesiunii +==================== -Prezentare generală a metodelor clasei `Nette\Http\Session` pentru gestionarea sesiunilor: +Prezentare generală a metodelor clasei `Nette\Http\Session` pentru gestionarea sesiunii: <div class=wiki-methods-brief> start(): void .[method] ----------------------- -Pornește o sesiune. +Pornește sesiunea. isStarted(): bool .[method] --------------------------- -A început sesiunea? +Sesiunea este pornită? close(): void .[method] ----------------------- -Încheie sesiunea. Sesiunea se încheie automat la sfârșitul scriptului. +Închide sesiunea. Sesiunea se închide automat la sfârșitul rulării scriptului. destroy(): void .[method] ------------------------- -Încheie și șterge sesiunea. +Închide și șterge sesiunea. exists(): bool .[method] ------------------------ -Cererea HTTP conține un modul cookie cu un ID de sesiune? +Cererea HTTP conține un cookie cu ID-ul sesiunii? regenerateId(): void .[method] ------------------------------ -Generează un nou ID de sesiune aleatoriu. Datele rămân neschimbate. +Generează un nou ID de sesiune aleatoriu. Datele rămân păstrate. getId(): string .[method] @@ -155,56 +155,57 @@ Returnează ID-ul sesiunii. </div> -Configurație .[#toc-configuration] ----------------------------------- +Configurație +------------ -Configuram sesiunea în [configurare |configuration#session]. Dacă scrieți o aplicație care nu utilizează un container DI, utilizați aceste metode pentru a o configura. Ele trebuie apelate înainte de a porni sesiunea. +Sesiunea o setăm în [configurație |configuration#Sesiune]. Dacă scrieți o aplicație care nu utilizează containerul DI, pentru configurare servesc aceste metode. Trebuie apelate înainte de pornirea sesiunii. <div class=wiki-methods-brief> setName(string $name): static .[method] --------------------------------------- -Definește numele cookie-ului care este utilizat pentru a transmite ID-ul sesiunii. Numele implicit este `PHPSESSID`. Acest lucru este util în cazul în care executați mai multe aplicații diferite pe același site. +Setează numele cookie-ului în care se transmite ID-ul sesiunii. Numele standard este `PHPSESSID`. Este util în cazul în care pe același site web rulați mai multe aplicații diferite. getName(): string .[method] --------------------------- -Returnează numele cookie-ului de sesiune. +Returnează numele cookie-ului în care se transmite ID-ul sesiunii. setOptions(array $options): static .[method] -------------------------------------------- -Configurează sesiunea. Este posibil să setați toate [directivele sesiunii |https://www.php.net/manual/en/session.configuration.php] PHP (în format camelCase, de exemplu, scrieți `savePath` în loc de `session.save_path`) și,</div>de<div class=wiki-methods-brief>asemenea, [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. +Configurează sesiunea. Se pot seta toate [directivele de sesiune |https://www.php.net/manual/en/session.configuration.php] PHP (în format camelCase, de ex. în loc de `session.save_path` scriem `savePath`) și, de asemenea, [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. setExpiration(?string $time): static .[method] ---------------------------------------------- -Stabilește timpul de inactivitate după care expiră sesiunea. +Setează perioada de inactivitate după care sesiunea expiră. -setCookieParameters(string $path, string $domain=null, bool $secure=null, string $samesite=null): static .[method] ------------------------------------------------------------------------------------------------------------------- -Stabilește parametrii pentru cookie-uri. Puteți modifica valorile implicite ale parametrilor în [configuration |configuration#Session cookie]. +setCookieParameters(string $path, ?string $domain=null, ?bool $secure=null, ?string $samesite=null): static .[method] +--------------------------------------------------------------------------------------------------------------------- +Setarea parametrilor pentru cookie. Valorile implicite ale parametrilor le puteți modifica în [configurație |configuration#Cookie de sesiune]. setSavePath(string $path): static .[method] ------------------------------------------- -Stabilește directorul în care sunt stocate fișierele de sesiune. +Setează directorul unde se salvează fișierele cu sesiuni. setHandler(\SessionHandlerInterface $handler): static .[method] --------------------------------------------------------------- -Definește gestionarul personalizat, consultați [documentația PHP |https://www.php.net/manual/en/class.sessionhandlerinterface.php]. +Setarea unui handler personalizat, vezi [documentația PHP|https://www.php.net/manual/en/class.sessionhandlerinterface.php]. </div> -Siguranța mai întâi .[#toc-safety-first] -======================================== +Securitatea înainte de toate +============================ -Serverul presupune că comunică cu același utilizator atâta timp cât cererile conțin același ID de sesiune. Sarcina mecanismelor de securitate este de a se asigura că acest comportament funcționează cu adevărat și că nu există nicio posibilitate de a înlocui sau fura un identificator. +Serverul presupune că comunică în continuare cu același utilizator, atâta timp cât cererile sunt însoțite de același ID de sesiune. Sarcina mecanismelor de securitate este să asigure că acest lucru se întâmplă într-adevăr și că nu este posibilă furtul sau substituirea identificatorului. -De aceea, Nette Framework configurează în mod corespunzător directivele PHP pentru a transfera ID-ul de sesiune numai în cookie-uri, pentru a evita accesul din JavaScript și pentru a ignora identificatorii din URL. Mai mult, în momentele critice, cum ar fi autentificarea utilizatorului, generează un nou ID de sesiune. +Nette Framework configurează, prin urmare, corect directivele PHP, astfel încât ID-ul sesiunii să fie transmis doar în cookie, să fie inaccesibil pentru JavaScript și să ignore eventualii identificatori din URL. În plus, în momente critice, cum ar fi autentificarea utilizatorului, generează un nou ID de sesiune. -Funcția ini_set este utilizată pentru configurarea PHP, dar, din păcate, utilizarea sa este interzisă la unele servicii de găzduire web. Dacă este cazul dumneavoastră, încercați să cereți furnizorului dumneavoastră de găzduire să vă permită această funcție sau cel puțin să configureze serverul său în mod corespunzător. .[note] +.[note] +Pentru configurarea PHP se utilizează funcția ini_set, pe care, din păcate, unele hostinguri o interzic. Dacă este și cazul hosterului dvs., încercați să discutați cu el pentru a vă permite funcția sau cel puțin pentru a configura serverul. diff --git a/http/ro/urls.texy b/http/ro/urls.texy index 5984e73fa7..a15fa0903b 100644 --- a/http/ro/urls.texy +++ b/http/ro/urls.texy @@ -1,16 +1,16 @@ -URL Parser și Builder -********************* +Lucrul cu URL-uri +***************** .[perex] -Clasele [Url |#Url], [UrlImmutable |#UrlImmutable] și [UrlScript |#UrlScript] facilitează gestionarea, analiza și manipularea URL-urilor. +Clasele [#Url], [#UrlImmutable] și [#UrlScript] permit generarea, parsarea și manipularea ușoară a URL-urilor. -→ [Instalare și cerințe |@home#Installation] +→ [Instalare și cerințe |@home#Instalare] Url === -Clasa [api:Nette\Http\Url] facilitează lucrul cu URL-ul și cu componentele sale individuale, care sunt prezentate în această diagramă: +Clasa [api:Nette\Http\Url] permite lucrul ușor cu URL-uri și componentele sale individuale, pe care le surprinde această schiță: /--pre scheme user password host port path query fragment @@ -36,7 +36,7 @@ $url->setScheme('https') echo $url; // 'https://localhost/edit?foo=bar' ``` -Puteți, de asemenea, să analizați URL-ul și apoi să îl manipulați: +Se poate, de asemenea, parsa un URL și manipula ulterior: ```php $url = new Url( @@ -44,62 +44,94 @@ $url = new Url( ); ``` -Următoarele metode sunt disponibile pentru a obține sau a modifica componente URL individuale: +Clasa `Url` implementează interfața `JsonSerializable` și are metoda `__toString()`, astfel încât obiectul poate fi afișat sau utilizat în datele transmise către `json_encode()`. + +```php +echo $url; +echo json_encode([$url]); +``` + + +Componentele URL .[method] +-------------------------- + +Pentru returnarea sau modificarea componentelor individuale ale URL-ului, aveți la dispoziție aceste metode: .[language-php] -Setter | Getter | Getter | Valoare returnată +| Setter | Getter | Valoare returnată |-------------------------------------------------------------------------------------------- -| `setScheme(string $scheme)`| `getScheme(): string`| `'http'` -| `setUser(string $user)`| `getUser(): string`| `'john'` -| `setPassword(string $password)`| `getPassword(): string`| `'xyz*12'` -| `setHost(string $host)`| `getHost(): string`| `'nette.org'` -| `setPort(int $port)`| `getPort(): ?int`| `8080` -| | `getDefaultPort(): ?int`| `80` -| `setPath(string $path)`| `getPath(): string`| `'/en/download'` -| `setQuery(string\|array $query)`| `getQuery(): string`| `'name=param'` -| `setFragment(string $fragment)`| `getFragment(): string`| `'footer'` -| | `getAuthority(): string`| `'nette.org:8080'` -| | `getHostUrl(): string`| `'http://nette.org:8080'` -| | `getAbsoluteUrl(): string` | URL complet - -De asemenea, putem opera cu parametri de interogare individuali folosind: +| `setScheme(string $scheme)` | `getScheme(): string` | `'http'` +| `setUser(string $user)` | `getUser(): string` | `'john'` +| `setPassword(string $password)` | `getPassword(): string` | `'xyz*12'` +| `setHost(string $host)` | `getHost(): string` | `'nette.org'` +| `setPort(int $port)` | `getPort(): ?int` | `8080` +| | `getDefaultPort(): ?int` | `80` +| `setPath(string $path)` | `getPath(): string` | `'/en/download'` +| `setQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` +| `setFragment(string $fragment)` | `getFragment(): string` | `'footer'` +| | `getAuthority(): string` | `'john:xyz*12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | URL complet + +Atenție: Când lucrați cu un URL obținut dintr-o [cerere HTTP|request], rețineți că nu va conține fragmentul, deoarece browserul nu îl trimite către server. + +Putem lucra și cu parametrii query individuali folosind: .[language-php] -| Setter | Getter +| Setter | Getter |--------------------------------------------------- -| `setQuery(string\|array $query)` | `getQueryParameters(): array` -| `setQueryParameter(string $name, $val)`| `getQueryParameter(string $name)` +| `setQuery(string\|array $query)` | `getQueryParameters(): array` +| `setQueryParameter(string $name, $val)` | `getQueryParameter(string $name): ?string` -Metoda `getDomain(int $level = 2)` returnează partea dreaptă sau stângă a gazdei. Iată cum funcționează dacă gazda este `www.nette.org`: + +getDomain(int $level = 2): ?string .[method] +-------------------------------------------- +Returnează partea dreaptă sau stângă a gazdei. Funcționează astfel dacă gazda este `www.nette.org`: .[language-php] -| `getDomain(1)` | `'org'` -| `getDomain(2)` | `'nette.org'` -| `getDomain(3)` | `'www.nette.org'` -| `getDomain(0)` | `'www.nette.org'` -| `getDomain(-1)` | `'www.nette'` -| `getDomain(-2)` | `'www'` -| `getDomain(-3)` | `''` +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `null` -Clasa `Url` implementează interfața `JsonSerializable` și are o metodă `__toString()`, astfel încât obiectul poate fi tipărit sau utilizat în datele transmise către `json_encode()`. +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +Verifică dacă două URL-uri sunt identice. ```php -echo $url; -echo json_encode([$url]); +$url->isEqual('https://nette.org'); ``` -Metoda `isEqual(string|Url $anotherUrl): bool` testează dacă cele două URL-uri sunt identice. + +Url::isAbsolute(string $url): bool .[method]{data-version:3.3.2} +---------------------------------------------------------------- +Verifică dacă URL-ul este absolut. Un URL este considerat absolut dacă începe cu o schemă (de ex., http, https, ftp) urmată de două puncte. ```php -$url->isEqual('https://nette.org'); +Url::isAbsolute('https://nette.org'); // true +Url::isAbsolute('//nette.org'); // false +``` + + +Url::removeDotSegments(string $path): string .[method]{data-version:3.3.2} +-------------------------------------------------------------------------- +Normalizează calea în URL prin eliminarea segmentelor speciale `.` și `..`. Metoda elimină elementele redundante ale căii în același mod în care o fac browserele web. + +```php +Url::removeDotSegments('/path/../subtree/./file.txt'); // '/subtree/file.txt' +Url::removeDotSegments('/../foo/./bar'); // '/foo/bar' +Url::removeDotSegments('./today/../file.txt'); // 'file.txt' ``` -UrlImmutable .[#toc-urlimmutable] -================================= +UrlImmutable +============ -Clasa [api:Nette\Http\UrlImmutable] este o alternativă imuabilă la clasa `Url` (la fel cum în PHP `DateTimeImmutable` este o alternativă imuabilă la `DateTime`). În loc de setters, aceasta are așa-numitele withers, care nu modifică obiectul, ci returnează noi instanțe cu o valoare modificată: +Clasa [api:Nette\Http\UrlImmutable] este o alternativă imuabilă (nu poate fi modificată) a clasei [#Url] (similar cu modul în care în PHP `DateTimeImmutable` este alternativa imuabilă a `DateTime`). În loc de setteri, are așa-numiți witheri, care nu modifică obiectul, ci returnează noi instanțe cu valoarea modificată: ```php use Nette\Http\UrlImmutable; @@ -111,53 +143,96 @@ $url = new UrlImmutable( $newUrl = $url ->withUser('') ->withPassword('') - ->withPath('/en/'); + ->withPath('/cs/'); + +echo $newUrl; // 'http://john:xyz%2A12@nette.org:8080/cs/?name=param#footer' +``` + +Clasa `UrlImmutable` implementează interfața `JsonSerializable` și are metoda `__toString()`, astfel încât obiectul poate fi afișat sau utilizat în datele transmise către `json_encode()`. -echo $newUrl; // 'http://nette.org:8080/en/?name=param#footer' +```php +echo $url; +echo json_encode([$url]); ``` -Următoarele metode sunt disponibile pentru a obține sau a modifica componente URL individuale: + +Componentele URL .[method] +-------------------------- + +Pentru returnarea sau modificarea componentelor individuale ale URL-ului servesc metodele: .[language-php] -Wither | Getter | Getter | Valoare returnată +| Wither | Getter | Valoare returnată |-------------------------------------------------------------------------------------------- -| `withScheme(string $scheme)`| `getScheme(): string`| `'http'` -| `withUser(string $user)`| `getUser(): string`| `'john'` -| `withPassword(string $password)`| `getPassword(): string`| `'xyz*12'` -| `withHost(string $host)`| `getHost(): string`| `'nette.org'` -| `withPort(int $port)`| `getPort(): ?int`| `8080` -| | `getDefaultPort(): ?int`| `80` -| `withPath(string $path)`| `getPath(): string`| `'/en/download'` -| `withQuery(string\|array $query)`| `getQuery(): string`| `'name=param'` -| `withFragment(string $fragment)`| `getFragment(): string`| `'footer'` -| | `getAuthority(): string`| `'nette.org:8080'` -| | `getHostUrl(): string`| `'http://nette.org:8080'` -| | `getAbsoluteUrl(): string` | URL complet - -De asemenea, putem opera cu parametri de interogare individuali folosind: +| `withScheme(string $scheme)` | `getScheme(): string` | `'http'` +| `withUser(string $user)` | `getUser(): string` | `'john'` +| `withPassword(string $password)` | `getPassword(): string` | `'xyz*12'` +| `withHost(string $host)` | `getHost(): string` | `'nette.org'` +| `withPort(int $port)` | `getPort(): ?int` | `8080` +| | `getDefaultPort(): ?int` | `80` +| `withPath(string $path)` | `getPath(): string` | `'/en/download'` +| `withQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` +| `withFragment(string $fragment)` | `getFragment(): string` | `'footer'` +| | `getAuthority(): string` | `'john:xyz*12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | URL complet + +Metoda `withoutUserInfo()` elimină `user` și `password`. + +Putem lucra și cu parametrii query individuali folosind: .[language-php] -| Wither | Getter +| Wither | Getter |----------------------------------------------- -| `withQuery(string\|array $query)` | `getQueryParameters(): array` -| `withQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` +| `withQuery(string\|array $query)` | `getQueryParameters(): array` +| `withQueryParameter(string $name, $val)` | `getQueryParameter(string $name): ?string` -Metoda `getDomain(int $level = 2)` funcționează la fel ca și metoda din `Url`. Metoda `withoutUserInfo()` elimină `user` și `password`. -Clasa `UrlImmutable` implementează interfața `JsonSerializable` și are o metodă `__toString()` pentru ca obiectul să poată fi tipărit sau utilizat în datele transmise către `json_encode()`. +getDomain(int $level = 2): ?string .[method] +-------------------------------------------- +Returnează partea dreaptă sau stângă a gazdei. Funcționează astfel dacă gazda este `www.nette.org`: + +.[language-php] +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `null` + + +resolve(string $reference): UrlImmutable .[method]{data-version:3.3.2} +---------------------------------------------------------------------- +Derivă un URL absolut în același mod în care un browser procesează linkurile pe o pagină HTML: +- dacă linkul este un URL absolut (conține o schemă), este utilizat neschimbat +- dacă linkul începe cu `//`, se preia doar schema din URL-ul curent +- dacă linkul începe cu `/`, se creează o cale absolută de la rădăcina domeniului +- în celelalte cazuri, URL-ul este construit relativ la calea curentă ```php -echo $url; -echo json_encode([$url]); +$url = new UrlImmutable('https://example.com/path/page'); +echo $url->resolve('../foo'); // 'https://example.com/foo' +echo $url->resolve('/bar'); // 'https://example.com/bar' +echo $url->resolve('sub/page.html'); // 'https://example.com/path/sub/page.html' +``` + + +isEqual(string|UrlImmutable $anotherUrl): bool .[method] +-------------------------------------------------------- +Verifică dacă două URL-uri sunt identice. + +```php +$url->isEqual('https://nette.org'); ``` -Metoda `isEqual(string|Url $anotherUrl): bool` testează dacă cele două URL-uri sunt identice. +UrlScript +========= -UrlScript .[#toc-urlscript] -=========================== +Clasa [api:Nette\Http\UrlScript] este un descendent al [#UrlImmutable] și îl extinde cu alte componente virtuale ale URL-ului, cum ar fi directorul rădăcină al proiectului etc. La fel ca clasa părinte, este un obiect imuabil (nu poate fi modificat). -Clasa [api:Nette\Http\UrlScript] este un descendent al `UrlImmutable` și distinge în plus aceste părți logice ale URL-ului: +Următoarea diagramă afișează componentele pe care UrlScript le recunoaște: /--pre baseUrl basePath relativePath relativeUrl @@ -169,17 +244,23 @@ Clasa [api:Nette\Http\UrlScript] este un descendent al `UrlImmutable` și distin scriptPath pathInfo \-- -Următoarele metode sunt disponibile pentru a obține aceste părți: +- `baseUrl` este adresa URL de bază a aplicației, inclusiv domeniul și partea căii către directorul rădăcină al aplicației +- `basePath` este partea căii către directorul rădăcină al aplicației +- `scriptPath` este calea către scriptul curent +- `relativePath` este numele scriptului (eventual alte segmente ale căii) relativ la basePath +- `relativeUrl` este întreaga parte a URL-ului după baseUrl, inclusiv query string și fragment. +- `pathInfo` este o parte a URL-ului, astăzi puțin utilizată, după numele scriptului + +Pentru returnarea părților URL-ului sunt disponibile metodele: .[language-php] -| Getter | Valoare returnată +| Getter | Valoare returnată |------------------------------------------------ -| `getScriptPath(): string`| `'/admin/script.php'` -| `getBasePath(): string`| `'/admin/'` -| `getBaseUrl(): string`| `'http://nette.org/admin/'` -| `getRelativePath(): string`| `'script.php'` -| `getRelativeUrl(): string`| `'script.php/pathinfo/?name=param#footer'` -| `getPathInfo(): string`| `'/pathinfo/'` - - -Nu creăm obiecte `UrlScript` direct, dar metoda [Nette\Http\Request::getUrl() |request] îl returnează. +| `getScriptPath(): string` | `'/admin/script.php'` +| `getBasePath(): string` | `'/admin/'` +| `getBaseUrl(): string` | `'http://nette.org/admin/'` +| `getRelativePath(): string` | `'script.php'` +| `getRelativeUrl(): string` | `'script.php/pathinfo/?name=param#footer'` +| `getPathInfo(): string` | `'/pathinfo/'` + +Obiectele `UrlScript` de obicei nu le creăm direct, ci le returnează metoda [Nette\Http\Request::getUrl()|request] cu componentele deja setate corect pentru cererea HTTP curentă. diff --git a/http/ru/@home.texy b/http/ru/@home.texy index c2fe6825a6..afeb7a8938 100644 --- a/http/ru/@home.texy +++ b/http/ru/@home.texy @@ -2,13 +2,13 @@ Nette HTTP ********** .[perex] -Пакет `nette/http` включает в себя [HTTP-запросы |request] и [ответы |response], работу с [сессиями |sessions], [разбор и построение URL |urls]. +Пакет `nette/http` инкапсулирует [HTTP-запрос|request] и [ответ|response], работу с [сессиями|sessions] и [парсинг и составление URL |urls]. -Установка .[#toc-installation] ------------------------------- +Установка +--------- -Загрузите и установите пакет с помощью [Composer |best-practices:composer]: +Скачать и установить библиотеку можно с помощью [Composer|best-practices:composer]: ```shell composer require nette/http diff --git a/http/ru/@left-menu.texy b/http/ru/@left-menu.texy index 79045a07fa..37b139824b 100644 --- a/http/ru/@left-menu.texy +++ b/http/ru/@left-menu.texy @@ -1,8 +1,8 @@ -Сети HTTP -********* -- [Обзор |@home] -- [HTTP-запрос |request] -- [HTTP-ответ |response] -- [Сессии |Sessions] -- [Утилита URL |urls] -- [Конфигурация |Configuration] +Nette HTTP +********** +- [Введение |@home] +- [HTTP-запрос|request] +- [HTTP-ответ|response] +- [Сессии |sessions] +- [Утилиты URL |urls] +- [Конфигурация |configuration] diff --git a/http/ru/@meta.texy b/http/ru/@meta.texy new file mode 100644 index 0000000000..7f329adfce --- /dev/null +++ b/http/ru/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документация Nette}} diff --git a/http/ru/configuration.texy b/http/ru/configuration.texy index 20642c40c1..2000b80e7e 100644 --- a/http/ru/configuration.texy +++ b/http/ru/configuration.texy @@ -1,14 +1,14 @@ -Настройка HTTP -************** +Конфигурация HTTP +***************** .[perex] Обзор опций конфигурации для Nette HTTP. -Если вы используете не весь фреймворк, а только эту библиотеку, прочитайте, [как загрузить конфигурацию |bootstrap:]. +Если вы не используете весь фреймворк, а только эту библиотеку, прочитайте, [как загрузить конфигурацию|bootstrap:]. -HTTP-заголовки .[#toc-http-headers] -=================================== +HTTP-заголовки +============== ```neon http: @@ -18,23 +18,23 @@ http: X-Content-Type-Options: nosniff X-XSS-Protection: '1; mode=block' - # affects header X-Frame-Options - frames: ... # (string|bool) defaults to 'SAMEORIGIN' + # влияет на заголовок X-Frame-Options + frames: ... # (string|bool) по умолчанию 'SAMEORIGIN' ``` -В целях безопасности фреймворк отправляет заголовок `X-Frame-Options: SAMEORIGIN`, в котором говорится, что страница может быть отображена внутри другой страницы (в элементе `<iframe>`) только в том случае, если она находится на том же домене. Это может быть нежелательно в некоторых ситуациях (например, если вы разрабатываете приложение для Facebook), поэтому поведение можно изменить, установив фреймы `frames: http://allowed-host.com`. +Фреймворк из соображений безопасности отправляет заголовок `X-Frame-Options: SAMEORIGIN`, который говорит, что страницу можно отображать внутри другой страницы (в элементе `<iframe>`) только если она находится на том же домене. Это может быть нежелательно в некоторых ситуациях (например, если вы разрабатываете приложение для Facebook), поэтому поведение можно изменить, установив `frames: http://allowed-host.com` или `frames: true`. -Политика безопасности контента .[#toc-content-security-policy] --------------------------------------------------------------- +Content Security Policy +----------------------- -Заголовки `Content-Security-Policy` (далее CSP) могут быть легко собраны, их описание можно найти в [описании CSP |https://content-security-policy.com]. Директивы CSP (такие как `script-src`) могут быть записаны либо как строки в соответствии со спецификацией, либо как массивы значений для лучшей читабельности. Тогда нет необходимости писать кавычки вокруг ключевых слов, таких как `'self'`. Nette также автоматически генерирует значение `nonce`, поэтому `'nonce-y4PopTLM=='` будет отправлен в заголовке. +Легко можно составить заголовки `Content-Security-Policy` (далее CSP), их описание вы найдете в [описании CSP |https://content-security-policy.com]. Директивы CSP (например, `script-src`) могут быть записаны либо как строки согласно спецификации, либо как массив значений для лучшей читаемости. Тогда не нужно вокруг ключевых слов, таких как `'self'`, писать кавычки. Nette также автоматически генерирует значение `nonce`, так что в заголовке будет, например, `'nonce-y4PopTLM=='`. ```neon http: # Content Security Policy csp: - # строка в соответствии со спецификацией CSP + # строка в формате согласно спецификации CSP default-src: "'self' https://example.com" # массив значений @@ -49,9 +49,9 @@ http: block-all-mixed-content: false ``` -Используйте `<script n:nonce>...</script>` в шаблонах, и значение nonce будет заполнено автоматически. Создавать защищенные веб-сайты в Nette очень просто. +В шаблонах используйте `<script n:nonce>...</script>`, и значение nonce будет добавлено автоматически. Делать безопасные сайты в Nette действительно легко. -Аналогичным образом можно добавить заголовки `Content-Security-Policy-Report-Only` (который можно использовать параллельно с CSP) и [Feature Policy |https://developers.google.com/web/updates/2018/06/feature-policy]: +Аналогично можно составить и заголовки `Content-Security-Policy-Report-Only` (которые можно использовать параллельно с CSP) и [Feature Policy|https://developers.google.com/web/updates/2018/06/feature-policy]: ```neon http: @@ -69,91 +69,103 @@ http: ``` -HTTP Cookie .[#toc-http-cookie] -------------------------------- +HTTP cookie +----------- -Вы можете изменить значения по умолчанию некоторых параметров методов [Nette\Http\Response::setCookie() |response#setCookie] и session. +Можно изменить значения по умолчанию некоторых параметров метода [Nette\Http\Response::setCookie() |response#setCookie] и сессии. ```neon http: - # область применения cookie по пути - cookiePath: ... # (строка) по умолчанию '/' + # область действия cookie по пути + cookiePath: ... # (string) по умолчанию '/' - # каким хостам разрешено получать куки - cookieDomain: 'example.com' # (строка|домен) по умолчанию unset + # домены, которые принимают cookie + cookieDomain: 'example.com' # (string|domain) по умолчанию не установлено - # отправлять куки только через HTTPS? + # отправлять cookie только через HTTPS? cookieSecure: ... # (bool|auto) по умолчанию auto - # отключает отправку куки, которые Nette использует в качестве защиты от CSRF + # отключает отправку cookie, которую использует Nette как защиту от CSRF disableNetteCookie: ... # (bool) по умолчанию false ``` -Параметр `cookieDomain` определяет, какие домены (origin) могут принимать куки. Если он не указан, то cookie принимается тем же (под)доменом, который им задан, *исключая* их поддомены. Если указан `cookieDomain`, то субдомены также будут включены. Поэтому указание `cookieDomain` является менее ограничительным, чем опущение. +Атрибут `cookieDomain` определяет, какие домены могут принимать cookie. Если он не указан, cookie принимает тот же (суб)домен, который его установил, *но не* его субдомены. Если `cookieDomain` указан, субдомены также включаются. Поэтому указание `cookieDomain` менее ограничивающее, чем его отсутствие. -Например, если задан `cookieDomain: nette.org`, то cookie также доступен на всех поддоменах, таких как `doc.nette.org`. Этого также можно достичь с помощью специального значения `domain`, т.е. `cookieDomain: domain`. +Например, при `cookieDomain: nette.org` cookie доступны и на всех субдоменах, таких как `doc.nette.org`. Того же можно достичь также с помощью специального значения `domain`, то есть `cookieDomain: domain`. -Значение по умолчанию `cookieSecure` равно `auto`, что означает, что если сайт работает на HTTPS, cookie будут отправляться с флагом `Secure` и, следовательно, будут доступны только через HTTPS. +Значение по умолчанию `auto` у атрибута `cookieSecure` означает, что если сайт работает по HTTPS, cookie будут отправляться с флагом `Secure` и, следовательно, будут доступны только через HTTPS. -HTTP-прокси .[#toc-http-proxy] ------------------------------- +HTTP-прокси +----------- -Если сайт работает за HTTP-прокси, введите IP-адрес прокси, чтобы обнаружение HTTPS-соединений работало правильно, а также IP-адрес клиента. То есть, чтобы [Nette\Http\Request::getRemoteAddress() |request#getRemoteAddress] и [isSecured() |request#isSecured] возвращали правильные значения и в шаблонах генерировались ссылки с протоколом `https:`. +Если сайт работает за HTTP-прокси, укажите его IP-адрес, чтобы правильно работало обнаружение соединения через HTTPS, а также IP-адреса клиента. То есть, чтобы функции [Nette\Http\Request::getRemoteAddress() |request#getRemoteAddress] и [isSecured() |request#isSecured] возвращали правильные значения, и в шаблонах генерировались ссылки с протоколом `https:`. ```neon http: - # IP-адрес, диапазон (т.е. 127.0.0.1/8) или массив этих значений - proxy: 127.0.0.1 # (string|string[]) по умолчанию none + # IP-адрес, диапазон (например, 127.0.0.1/8) или массив этих значений + proxy: 127.0.0.1 # (string|string[]) по умолчанию не установлено ``` -Сессия .[#toc-session] -====================== +Сессия +====== -Основные настройки [сеансов |sessions]: +Базовые настройки [сессий|sessions]: ```neon session: - # показывает панель сеанса в панели трейси? + # отображать панель сессии в Tracy Bar? debugger: ... # (bool) по умолчанию false - # время бездействия, по истечении которого сессия завершается - expiration: 14 days # (string) по умолчанию '3 часа' + # время неактивности, после которого сессия истечет + expiration: 14 days # (string) по умолчанию '3 hours' - # когда начинать сессию? + # когда должна запускаться сессия? autoStart: ... # (smart|always|never) по умолчанию 'smart' - # обработчик, служба, реализующая интерфейс SessionHandlerInterface + # обработчик, сервис, реализующий интерфейс SessionHandlerInterface handler: @handlerService ``` -Параметр `autoStart` определяет, когда начинать сеанс. Значение `always` означает, что сессия всегда запускается при запуске приложения. Значение `smart` означает, что сессия будет запускаться при запуске приложения, только если она уже существует, или в тот момент, когда мы хотим читать из нее или писать в нее. Наконец, значение `never` отключает автоматический запуск сессии. +Опция `autoStart` управляет тем, когда должна запускаться сессия. Значение `always` означает, что сессия запустится всегда при запуске приложения. Значение `smart` означает, что сессия запустится при старте приложения только тогда, когда она уже существует, или в момент, когда мы хотим из нее читать или в нее записывать. И наконец, значение `never` запрещает автоматический запуск сессии. -Вы также можете задать все [директивы |https://www.php.net/manual/en/session.configuration.php] PHP [сессии |https://www.php.net/manual/en/session.configuration.php] (в формате camelCase), а также [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. Пример: +Далее можно настраивать все PHP [директивы сессии |https://www.php.net/manual/en/session.configuration.php] (в формате camelCase) и также [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. Пример: ```neon session: - # 'session.name' written as 'name' + # 'session.name' запишем как 'name' name: MYID - # 'session.save_path' written as 'savePath' + # 'session.save_path' запишем как 'savePath' savePath: "%tempDir%/sessions" ``` -Сессионный файл cookie .[#toc-session-cookie] ---------------------------------------------- +Session cookie +-------------- -Сессионный cookie отправляется с теми же параметрами, что и [другие cookie |#HTTP-Cookie], но вы можете изменить их для него: +Session cookie отправляется с теми же параметрами, что и [другие cookie |#HTTP cookie], но эти вы можете для нее изменить: ```neon session: - # каким хостам разрешено получать cookie-файл + # домены, которые принимают cookie cookieDomain: 'example.com' # (string|domain) - # ограничения при доступе к кросс-оригинальному запросу - cookieSamesite: None # (Strict|Lax|None) defaults to Lax + # ограничение при доступе с другого домена + cookieSamesite: None # (Strict|Lax|None) по умолчанию Lax ``` -Параметр `cookieSamesite` влияет на то, отправляется ли cookie при [межсайтовых запросах |nette:glossary#SameSite-Cookie], что обеспечивает некоторую защиту от атак [Cross-Site Request Forgery |nette:glossary#Cross-Site-Request-Forgery-CSRF]. +Атрибут `cookieSamesite` влияет на то, будет ли cookie отправлена при [доступе с другого домена |nette:glossary#SameSite cookie], что обеспечивает определенную защиту от атак [Cross-Site Request Forgery |nette:glossary#Cross-Site Request Forgery CSRF] (CSRF). + + +Сервисы DI +========== + +Эти сервисы добавляются в DI-контейнер: + +| Имя | Тип | Описание +|----------------------------------------------------- +| `http.request` | [api:Nette\Http\Request] | [HTTP-запрос| request] +| `http.response` | [api:Nette\Http\Response] | [HTTP-ответ| response] +| `session.session` | [api:Nette\Http\Session] | [управление сессией| sessions] diff --git a/http/ru/request.texy b/http/ru/request.texy index ed5afba54d..1d49bdd389 100644 --- a/http/ru/request.texy +++ b/http/ru/request.texy @@ -2,19 +2,19 @@ HTTP-запрос *********** .[perex] -Nette инкапсулирует HTTP-запрос в объекты с понятным API, обеспечивая при этом фильтр санации. +Nette инкапсулирует HTTP-запрос в объекты с понятным API и одновременно предоставляет санитайзирующий фильтр. -HTTP-запрос - это объект [api:Nette\Http\Request], который вы получаете, передавая его с помощью [инъекции зависимостей |dependency-injection:passing-dependencies]. В презентаторах просто вызывайте `$httpRequest = $this->getHttpRequest()`. +HTTP-запрос представляет собой объект [api:Nette\Http\Request]. Если вы работаете с Nette, этот объект автоматически создается фреймворком, и вы можете получить его с помощью [внедрения зависимостей |dependency-injection:passing-dependencies]. В презентерах достаточно просто вызвать метод `$this->getHttpRequest()`. Если вы работаете вне Nette Framework, вы можете создать объект с помощью [#RequestFactory]. -Важно то, что Nette при [создании |#RequestFactory] этого объекта очищает все входные параметры GET, POST и COOKIE, а также URL от управляющих символов и недопустимых последовательностей UTF-8. Поэтому вы можете спокойно продолжать работу с данными. Очищенные данные затем используются в презентаторах и формах. +Большим преимуществом Nette является то, что при создании объекта он автоматически очищает все входные параметры GET, POST, COOKIE, а также URL от управляющих символов и невалидных UTF-8 последовательностей. С этими данными затем можно безопасно работать дальше. Очищенные данные впоследствии используются в презентерах и формах. -→ [Установка и требования |@home#Installation] +→ [Установка и требования |@home#Установка] -Nette\Http\Request .[#toc-nette-http-request] -============================================= +Nette\Http\Request +================== -Этот объект является неизменяемым. У него нет сеттеров, есть только один так называемый wither `withUrl()`, который не изменяет объект, а возвращает новый экземпляр с измененным значением. +Этот объект является immutable (неизменяемым). У него нет сеттеров, есть только один так называемый wither `withUrl()`, который не изменяет объект, а возвращает новый экземпляр с измененным значением. withUrl(Nette\Http\UrlScript $url): Nette\Http\Request .[method] @@ -24,62 +24,62 @@ withUrl(Nette\Http\UrlScript $url): Nette\Http\Request .[method] getUrl(): Nette\Http\UrlScript .[method] ---------------------------------------- -Возвращает URL запроса в виде объекта [UrlScript |urls#UrlScript]. +Возвращает URL запроса как объект [UrlScript |urls#UrlScript]. ```php $url = $httpRequest->getUrl(); -echo $url; // https://nette.org/en/documentation?action=edit +echo $url; // https://doc.nette.org/cs/?action=edit echo $url->getHost(); // nette.org ``` -Браузеры не отправляют фрагмент на сервер, поэтому `$url->getFragment()` вернет пустую строку. +Внимание: браузеры не отправляют фрагмент на сервер, поэтому `$url->getFragment()` будет возвращать пустую строку. -getQuery(string $key=null): string|array|null .[method] -------------------------------------------------------- -Возвращает параметры GET-запроса: +getQuery(?string $key=null): string|array|null .[method] +-------------------------------------------------------- +Возвращает параметры GET-запроса. ```php -$all = $httpRequest->getQuery(); // массив всех URL параметров +$all = $httpRequest->getQuery(); // возвращает массив всех параметров из URL $id = $httpRequest->getQuery('id'); // возвращает GET-параметр 'id' (или null) ``` -getPost(string $key=null): string|array|null .[method] ------------------------------------------------------- -Возвращает параметры POST-запроса: +getPost(?string $key=null): string|array|null .[method] +------------------------------------------------------- +Возвращает параметры POST-запроса. ```php -$all = $httpRequest->getPost(); // массив всех POST параметров +$all = $httpRequest->getPost(); // возвращает массив всех параметров из POST $id = $httpRequest->getPost('id'); // возвращает POST-параметр 'id' (или null) ``` getFile(string|string[] $key): Nette\Http\FileUpload|array|null .[method] ------------------------------------------------------------------------- -Возвращает [выгрузку |#Uploaded-Files] как объект [api:Nette\Http\FileUpload]: +Возвращает [загруженный файл |#Загруженные файлы] как объект [api:Nette\Http\FileUpload]: ```php $file = $httpRequest->getFile('avatar'); -if ($file->hasFile()) { // был ли загружен какой-либо файл? - $file->getUntrustedName(); // имя файла, отправленного пользователем +if ($file?->hasFile()) { // был ли загружен какой-либо файл? + $file->getUntrustedName(); // имя файла, отправленное пользователем $file->getSanitizedName(); // имя без опасных символов } ``` -Укажите массив ключей для доступа к структуре поддерева. +Для доступа к вложенной структуре укажите массив ключей. ```php //<input type="file" name="my-form[details][avatar]" multiple> $file = $request->getFile(['my-form', 'details', 'avatar']); ``` -Поскольку вы не можете доверять данным извне и поэтому не полагаетесь на форму структуры, этот метод безопаснее, чем `$request->getFiles()['my-form']['details']['avatar']`который может потерпеть неудачу. +Поскольку нельзя доверять данным извне и, следовательно, полагаться на структуру файлов, этот способ безопаснее, чем, например, `$request->getFiles()['my-form']['details']['avatar']`, который может не сработать. getFiles(): array .[method] --------------------------- -Возвращает дерево [файлов выгрузки |#Uploaded-Files] в нормализованной структуре, каждый лист которого является экземпляром [api:Nette\Http\FileUpload]: +Возвращает дерево [всех загруженных файлов |#Загруженные файлы] в нормализованной структуре, листьями которой являются объекты [api:Nette\Http\FileUpload]: ```php $files = $httpRequest->getFiles(); @@ -88,7 +88,7 @@ $files = $httpRequest->getFiles(); getCookie(string $key): string|array|null .[method] --------------------------------------------------- -Возвращает cookie или `null`, если он не существует. +Возвращает cookie или `null`, если она не существует. ```php $sessId = $httpRequest->getCookie('sess_id'); @@ -97,7 +97,7 @@ $sessId = $httpRequest->getCookie('sess_id'); getCookies(): array .[method] ----------------------------- -Возвращает все файлы cookie: +Возвращает все cookie. ```php $cookies = $httpRequest->getCookies(); @@ -106,16 +106,16 @@ $cookies = $httpRequest->getCookies(); getMethod(): string .[method] ----------------------------- -Возвращает метод HTTP, с помощью которого был сделан запрос. +Возвращает HTTP-метод, с которым был сделан запрос. ```php -echo $httpRequest->getMethod(); // GET, POST, HEAD, PUT +$httpRequest->getMethod(); // GET, POST, HEAD, PUT ``` isMethod(string $method): bool .[method] ---------------------------------------- -Проверяет метод HTTP, с помощью которого был сделан запрос. Параметр не чувствителен к регистру. +Проверяет HTTP-метод, с которым был сделан запрос. Параметр нечувствителен к регистру. ```php if ($httpRequest->isMethod('GET')) // ... @@ -124,7 +124,7 @@ if ($httpRequest->isMethod('GET')) // ... getHeader(string $header): ?string .[method] -------------------------------------------- -Возвращает HTTP-заголовок или `null`, если он не существует. Параметр не чувствителен к регистру: +Возвращает HTTP-заголовок или `null`, если он не существует. Параметр нечувствителен к регистру. ```php $userAgent = $httpRequest->getHeader('User-Agent'); @@ -133,7 +133,7 @@ $userAgent = $httpRequest->getHeader('User-Agent'); getHeaders(): array .[method] ----------------------------- -Возвращает все HTTP-заголовки в виде ассоциативного массива: +Возвращает все HTTP-заголовки как ассоциативный массив. ```php $headers = $httpRequest->getHeaders(); @@ -141,39 +141,34 @@ echo $headers['Content-Type']; ``` -getReferer(): ?Nette\Http\UrlImmutable .[method] ------------------------------------------------- -С какого URL пришел пользователь? Остерегайтесь, это совсем не надежно. - - isSecured(): bool .[method] --------------------------- -Зашифровано ли соединение (HTTPS)? Возможно, вам потребуется настроить [прокси-сервер |configuration#HTTP-Proxy] для правильной работы. +Является ли соединение шифрованным (HTTPS)? Для правильной работы может потребоваться [настроить прокси |configuration#HTTP-прокси]. isSameSite(): bool .[method] ---------------------------- -Запрос исходит из того же (под)домена и инициирован нажатием на ссылку? Для определения этого Nette использует cookie `_nss` (ранее `nette-samesite`). +Приходит ли запрос с того же (суб)домена и инициирован ли он кликом по ссылке? Nette для обнаружения использует cookie `_nss` (ранее `nette-samesite`). isAjax(): bool .[method] ------------------------ -Это AJAX-запрос? +Является ли это AJAX-запросом? getRemoteAddress(): ?string .[method] ------------------------------------- -Возвращает IP-адрес пользователя. Для правильной работы может потребоваться настройка [прокси-сервера |configuration#HTTP-Proxy]. +Возвращает IP-адрес пользователя. Для правильной работы может потребоваться [настроить прокси |configuration#HTTP-прокси]. getRemoteHost(): ?string .[method deprecated] --------------------------------------------- -Возвращает DNS-трансляцию IP-адреса пользователя. Для правильной работы может потребоваться настройка [прокси-сервера |configuration#HTTP-Proxy]. +Возвращает DNS-преобразование IP-адреса пользователя. Для правильной работы может потребоваться [настроить прокси |configuration#HTTP-прокси]. -getBasicCredentials(): ?string .[method] ----------------------------------------- -Возвращает учетные данные [базовой HTTP-аутентификации |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication]. +getBasicCredentials(): ?array .[method] +--------------------------------------- +Возвращает учетные данные для [Basic HTTP authentication |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication]. ```php [$user, $password] = $httpRequest->getBasicCredentials(); @@ -182,7 +177,7 @@ getBasicCredentials(): ?string .[method] getRawBody(): ?string .[method] ------------------------------- -Возвращает тело HTTP-запроса: +Возвращает тело HTTP-запроса. ```php $body = $httpRequest->getRawBody(); @@ -191,48 +186,55 @@ $body = $httpRequest->getRawBody(); detectLanguage(array $langs): ?string .[method] ----------------------------------------------- -Определяет язык. В качестве параметра `$lang` мы передаем массив языков, которые поддерживает приложение, и он возвращает тот, который предпочитает браузер. Это не магия, метод просто использует заголовок `Accept-Language`. Если совпадение не достигнуто, возвращается `null`. +Определяет язык. В качестве параметра `$langs` передаем массив языков, которые поддерживает приложение, и она возвращает тот, который предпочел бы видеть браузер посетителя. Это не магия, просто используется заголовок `Accept-Language`. Если совпадений нет, возвращает `null`. ```php -// Заголовок, отправляемый браузером: Accept-Language: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3 +// браузер отправляет, например, Accept-Language: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3 -$langs = ['hu', 'pl', 'en']; // языки, поддерживаемые в приложении +$langs = ['hu', 'pl', 'en']; // языки, поддерживаемые приложением echo $httpRequest->detectLanguage($langs); // en ``` -RequestFactory .[#toc-requestfactory] -===================================== +RequestFactory +============== -Объект текущего HTTP-запроса создается с помощью [api:Nette\Http\RequestFactory]. Если вы пишете приложение, которое не использует DI-контейнер, вы создаете запрос следующим образом: +Класс [api:Nette\Http\RequestFactory] служит для создания экземпляра `Nette\Http\Request`, который представляет текущий HTTP-запрос. (Если вы работаете с Nette, объект HTTP-запроса автоматически создается фреймворком.) ```php $factory = new Nette\Http\RequestFactory; $httpRequest = $factory->fromGlobals(); ``` -RequestFactory может быть сконфигурирован перед вызовом `fromGlobals()`. Мы можем отключить всю санацию входных параметров от недопустимых последовательностей UTF-8 с помощью `$factory->setBinary()`. А также настроить прокси-сервер, что важно для правильного определения IP-адреса пользователя с помощью `$factory->setProxy(...)`. +Метод `fromGlobals()` создает объект запроса на основе текущих глобальных переменных PHP (`$_GET`, `$_POST`, `$_COOKIE`, `$_FILES` и `$_SERVER`). При создании объекта он автоматически очищает все входные параметры GET, POST, COOKIE, а также URL от управляющих символов и невалидных UTF-8 последовательностей, что обеспечивает безопасность при дальнейшей работе с этими данными. + +RequestFactory можно сконфигурировать перед вызовом `fromGlobals()`: -Очистить URL от символов, которые могут попасть в них из-за плохо реализованных систем комментариев на различных других сайтах, можно с помощью фильтров: +- методом `$factory->setBinary()` отключите автоматическую очистку входных параметров от управляющих символов и невалидных UTF-8 последовательностей. +- методом `$factory->setProxy(...)` укажите IP-адрес [прокси-сервера |configuration#HTTP-прокси], что необходимо для правильного определения IP-адреса пользователя. + +RequestFactory позволяет определить фильтры, которые автоматически трансформируют части URL запроса. Эти фильтры удаляют нежелательные символы из URL, которые могут быть туда вставлены, например, из-за неправильной реализации систем комментариев на различных веб-сайтах: ```php -// удалите пробелы из пути +// удаление пробелов из пути $requestFactory->urlFilters['path']['%20'] = ''; -// удалите точку, запятую или правую круглую скобку в конце URL-адреса -$requestFactory->urlFilters['url']['['[.,)]$'] = ''; +// удаление точки, запятой или правой скобки с конца URI +$requestFactory->urlFilters['url']['[.,)]$'] = ''; -// очистить путь от дублирующихся слешей (фильтр по умолчанию) +// очистка пути от удвоенных слешей (фильтр по умолчанию) $requestFactory->urlFilters['path']['/{2,}'] = '/'; ``` +Первый ключ `'path'` или `'url'` определяет, к какой части URL применяется фильтр. Второй ключ — это регулярное выражение, которое нужно найти, а значение — это замена, которая будет использована вместо найденного текста. + -Загруженные файлы .[#toc-uploaded-files] -======================================== +Загруженные файлы +================= -Метод `Nette\Http\Request::getFiles()` возвращает дерево загруженных файлов в нормализованной структуре, каждый лист которого является экземпляром [api:Nette\Http\FileUpload]. Эти объекты инкапсулируют данные, представленные элементом `<input type=file>` элементом формы. +Метод `Nette\Http\Request::getFiles()` возвращает массив всех загруженных файлов в нормализованной структуре, листьями которой являются объекты [api:Nette\Http\FileUpload]. Они инкапсулируют данные, отправленные элементом формы `<input type=file>`. -Структура отражает именование элементов в HTML. В простейшем примере это может быть один именованный элемент формы, представленный как: +Структура отражает именование элементов в HTML. В простейшем случае это может быть единственный именованный элемент формы, отправленный как: ```latte <input type="file" name="avatar"> @@ -246,19 +248,19 @@ $requestFactory->urlFilters['path']['/{2,}'] = '/'; ] ``` -Объект `FileUpload` создается, даже если пользователь не загрузил ни одного файла или загрузка не удалась. Метод `hasFile()` возвращает true, если файл был отправлен: +Объект `FileUpload` создается и в случае, если пользователь не отправил ни одного файла или отправка не удалась. Был ли файл отправлен, возвращает метод `hasFile()`: ```php -$request->getFile('avatar')->hasFile(); +$request->getFile('avatar')?->hasFile(); ``` -В случае ввода с использованием нотации массива для имени: +В случае имени элемента, использующего нотацию для массива: ```latte <input type="file" name="my-form[details][avatar]"> ``` -возвращаемое дерево выглядит следующим образом: +возвращаемое дерево выглядит так: ```php [ @@ -270,13 +272,13 @@ $request->getFile('avatar')->hasFile(); ] ``` -Вы также можете создавать массивы файлов: +Можно создать и массив файлов: ```latte -<input type="file" name="my-form[details][avatars][] multiple"> +<input type="file" name="my-form[details][avatars][]" multiple> ``` -В таком случае структура выглядит следующим образом: +В таком случае структура выглядит так: ```php [ @@ -292,7 +294,7 @@ $request->getFile('avatar')->hasFile(); ] ``` -Лучший способ доступа к индексу 1 вложенного массива следующий: +Доступ к индексу 1 вложенного массива лучше всего получить так: ```php $file = $request->getFile(['my-form', 'details', 'avatars', 1]); @@ -301,7 +303,7 @@ if ($file instanceof FileUpload) { } ``` -Поскольку вы не можете доверять данным извне и поэтому не полагаетесь на форму структуры, этот метод безопаснее, чем `$request->getFiles()['my-form']['details']['avatars'][1]`который может не сработать. +Поскольку нельзя доверять данным извне и, следовательно, полагаться на структуру файлов, этот способ безопаснее, чем, например, `$request->getFiles()['my-form']['details']['avatars'][1]`, который может не сработать. Обзор методов `FileUpload` .{toc: FileUpload} @@ -310,7 +312,7 @@ if ($file instanceof FileUpload) { hasFile(): bool .[method] ------------------------- -Возвращает `true`, если пользователь загрузил файл. +Возвращает `true`, если пользователь загрузил какой-либо файл. isOk(): bool .[method] @@ -320,12 +322,12 @@ isOk(): bool .[method] getError(): int .[method] ------------------------- -Возвращает код ошибки, связанный с загруженным файлом. Это может быть одна из констант [UPLOAD_ERR_XXX |http://php.net/manual/en/features.file-upload.errors.php]. Если файл был загружен успешно, возвращается `UPLOAD_ERR_OK`. +Возвращает код ошибки при загрузке файла. Это одна из констант [UPLOAD_ERR_XXX |http://php.net/manual/en/features.file-upload.errors.php]. В случае, если загрузка прошла успешно, возвращает `UPLOAD_ERR_OK`. -move(string $dest) .[method] ----------------------------- -Перемещает загруженный файл в новое место. Если файл назначения уже существует, он будет перезаписан. +move(string $dest): void .[method] +---------------------------------- +Перемещает загруженный файл в новое местоположение. Если целевой файл уже существует, он будет перезаписан. ```php $file->move('/path/to/files/name.ext'); @@ -334,61 +336,72 @@ $file->move('/path/to/files/name.ext'); getContents(): ?string .[method] -------------------------------- -Возвращает содержимое загруженного файла. Если загрузка не была успешной, возвращается `null`. +Возвращает содержимое загруженного файла. В случае, если загрузка не была успешной, возвращает `null`. getContentType(): ?string .[method] ----------------------------------- -Определяет MIME-тип содержимого загружаемого файла на основе его сигнатуры. Если загрузка не была успешной или определение не удалось, возвращается `null`. +Определяет MIME content type загруженного файла на основе его сигнатуры. В случае, если загрузка не была успешной или определение не удалось, возвращает `null`. .[caution] -Требуется расширение PHP `fileinfo`. +Требует PHP-расширение `fileinfo`. getUntrustedName(): string .[method] ------------------------------------ -Возвращает исходное имя файла, переданное браузером. +Возвращает оригинальное имя файла, как его отправил браузер. .[caution] -Не доверяйте значению, возвращаемому этим методом. Клиент может отправить вредоносное имя файла с намерением испортить или взломать ваше приложение. +Не доверяйте значению, возвращаемому этим методом. Клиент мог отправить вредоносное имя файла с намерением повредить или взломать ваше приложение. getSanitizedName(): string .[method] ------------------------------------ -Возвращает санированное имя файла. Оно содержит только символы ASCII `[a-zA-Z0-9.-]`. Если имя не содержит таких символов, возвращается 'unknown'. Если файл является изображением JPEG, PNG, GIF или WebP, возвращается правильное расширение файла. +Возвращает очищенное (sanitized) имя файла. Содержит только ASCII-символы `[a-zA-Z0-9.-]`. Если имя не содержит таких символов, возвращает `'unknown'`. Если файл является изображением в формате JPEG, PNG, GIF, WebP или AVIF, возвращает и правильное расширение. + +.[caution] +Требует PHP-расширение `fileinfo`. + + +getSuggestedExtension(): ?string .[method]{data-version:3.2.4} +-------------------------------------------------------------- +Возвращает подходящее расширение файла (без точки), соответствующее определенному MIME-типу. + +.[caution] +Требует PHP-расширение `fileinfo`. getUntrustedFullPath(): string .[method] ---------------------------------------- -Возвращает исходный полный путь, указанный браузером во время загрузки каталога. Полный путь доступен только в PHP 8.1 и выше. В предыдущих версиях этот метод возвращает недоверенное имя файла. +Возвращает оригинальный путь к файлу, как его отправил браузер при загрузке папки. Полный путь доступен только в PHP 8.1 и выше. В предыдущих версиях этот метод возвращает оригинальное имя файла. .[caution] -Не доверяйте значению, возвращаемому этим методом. Клиент может отправить вредоносное имя файла с намерением испортить или взломать ваше приложение. +Не доверяйте значению, возвращаемому этим методом. Клиент мог отправить вредоносное имя файла с намерением повредить или взломать ваше приложение. getSize(): int .[method] ------------------------ -Возвращает размер загруженного файла. Если загрузка не была успешной, возвращается `0`. +Возвращает размер загруженного файла. В случае, если загрузка не была успешной, возвращает `0`. getTemporaryFile(): string .[method] ------------------------------------ -Возвращает путь к временному местоположению загруженного файла. Если загрузка не была успешной, возвращается `''`. +Возвращает путь к временному местоположению загруженного файла. В случае, если загрузка не была успешной, возвращает `''`. isImage(): bool .[method] ------------------------- -Возвращает `true`, если загруженный файл является изображением JPEG, PNG, GIF или WebP. Обнаружение основано на его сигнатуре. Целостность всего файла не проверяется. Вы можете узнать, не повреждено ли изображение, например, попытавшись [загрузить его |#toImage]. +Возвращает `true`, если загруженный файл является изображением в формате JPEG, PNG, GIF, WebP или AVIF. Определение происходит на основе его сигнатуры и не проверяется целостность всего файла. Не повреждено ли изображение, можно узнать, например, попытавшись его [загрузить |#toImage]. .[caution] -Требуется расширение PHP `fileinfo`. +Требует PHP-расширение `fileinfo`. getImageSize(): ?array .[method] -------------------------------- -Возвращает пару `[width, height]` с размерами загруженного изображения. Если загрузка не была успешной или это не действительное изображение, возвращается `null`. +Возвращает пару `[ширина, высота]` с размерами загруженного изображения. В случае, если загрузка не была успешной или это не валидное изображение, возвращает `null`. toImage(): Nette\Utils\Image .[method] -------------------------------------- -Загружает изображение как объект [Image |utils:images]. Если загрузка не была успешной или изображение не является действительным, выбрасывается исключение `Nette\Utils\ImageException`. +Загружает изображение как объект [Image |utils:images]. В случае, если загрузка не была успешной или это не валидное изображение, выбрасывает исключение `Nette\Utils\ImageException`. diff --git a/http/ru/response.texy b/http/ru/response.texy index 1a69ea4ab9..f9ba4307f1 100644 --- a/http/ru/response.texy +++ b/http/ru/response.texy @@ -2,22 +2,22 @@ HTTP-ответ ********** .[perex] -Nette инкапсулирует HTTP-ответ в объекты с понятным API, обеспечивая при этом фильтр санации. +Nette инкапсулирует HTTP-ответ в объекты с понятным API. -HTTP-ответ - это объект [api:Nette\Http\Response], который вы получаете, передавая его с помощью [инъекции зависимостей |dependency-injection:passing-dependencies]. В презентаторах просто вызывайте `$httpResponse = $this->getHttpResponse()`. +HTTP-ответ представляет собой объект [api:Nette\Http\Response]. Если вы работаете с Nette, этот объект автоматически создается фреймворком, и вы можете получить его с помощью [внедрения зависимостей |dependency-injection:passing-dependencies]. В презентерах достаточно просто вызвать метод `$this->getHttpResponse()`. -→ [Установка и требования |@home#Installation] +→ [Установка и требования |@home#Установка] -Nette\Http\Response .[#toc-nette-http-response] -=============================================== +Nette\Http\Response +=================== -В отличие от [Nette\Http\Request |request], этот объект является изменяемым, поэтому вы можете использовать сеттеры для изменения состояния, т.е. для отправки заголовков. Помните, что все сеттеры **должны быть вызваны до отправки фактического вывода.** Метод `isSent()` показывает, был ли отправлен вывод. Если он возвращает `true`, то каждая попытка отправить заголовок вызывает исключение `Nette\InvalidStateException`. +Объект, в отличие от [Nette\Http\Request |request], является mutable (изменяемым), то есть с помощью сеттеров вы можете изменять состояние, например, отправлять заголовки. Не забывайте, что все сеттеры должны быть вызваны **перед отправкой любого вывода.** Был ли уже отправлен вывод, показывает метод `isSent()`. Если он возвращает `true`, любая попытка отправить заголовок вызовет исключение `Nette\InvalidStateException`. -setCode(int $code, string $reason=null) .[method] -------------------------------------------------- -Изменяет [код ответа |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10] статуса. Для лучшей читаемости исходного кода рекомендуется использовать [предопределенные константы |api:Nette\Http\IResponse] вместо реальных чисел. +setCode(int $code, ?string $reason=null): static .[method] +---------------------------------------------------------- +Изменяет [код состояния ответа |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10]. Для лучшей понятности исходного кода рекомендуем для кода использовать вместо чисел [предопределенные константы |api:Nette\Http\IResponse]. ```php $httpResponse->setCode(Nette\Http\Response::S404_NotFound); @@ -26,26 +26,26 @@ $httpResponse->setCode(Nette\Http\Response::S404_NotFound); getCode(): int .[method] ------------------------ -Возвращает код статуса ответа. +Возвращает код состояния ответа. isSent(): bool .[method] ------------------------ -Возвращает, были ли заголовки уже отправлены с сервера в браузер, поэтому уже невозможно отправить заголовки или изменить код статуса. +Возвращает, были ли уже отправлены заголовки с сервера в браузер, и, следовательно, уже невозможно отправлять заголовки или изменять код состояния. -setHeader(string $name, string $value) .[method] ------------------------------------------------- -Отправляет HTTP-заголовок и **перезаписывает** ранее отправленный одноименный заголовок. +setHeader(string $name, string $value): static .[method] +-------------------------------------------------------- +Отправляет HTTP-заголовок и **перезаписывает** ранее отправленный заголовок с тем же именем. ```php $httpResponse->setHeader('Pragma', 'no-cache'); ``` -addHeader(string $name, string $value) .[method] ------------------------------------------------- -Отправляет HTTP-заголовок и **не перезаписывает** ранее отправленный одноименный заголовок. +addHeader(string $name, string $value): static .[method] +-------------------------------------------------------- +Отправляет HTTP-заголовок и **не перезаписывает** ранее отправленный заголовок с тем же именем. ```php $httpResponse->addHeader('Accept', 'application/json'); @@ -53,14 +53,14 @@ $httpResponse->addHeader('Accept', 'application/xml'); ``` -deleteHeader(string $name) .[method] ------------------------------------- +deleteHeader(string $name): static .[method] +-------------------------------------------- Удаляет ранее отправленный HTTP-заголовок. getHeader(string $header): ?string .[method] -------------------------------------------- -Возвращает отправленный HTTP-заголовок, или `null`, если он не существует. Параметр не чувствителен к регистру. +Возвращает отправленный HTTP-заголовок или `null`, если такой не существует. Параметр нечувствителен к регистру. ```php $pragma = $httpResponse->getHeader('Pragma'); @@ -69,7 +69,7 @@ $pragma = $httpResponse->getHeader('Pragma'); getHeaders(): array .[method] ----------------------------- -Возвращает все отправленные HTTP-заголовки в виде ассоциативного массива. +Возвращает все отправленные HTTP-заголовки как ассоциативный массив. ```php $headers = $httpResponse->getHeaders(); @@ -77,18 +77,18 @@ echo $headers['Pragma']; ``` -setContentType(string $type, string $charset=null) .[method] ------------------------------------------------------------- -Отправляет заголовок `Content-Type`. +setContentType(string $type, ?string $charset=null): static .[method] +--------------------------------------------------------------------- +Изменяет заголовок `Content-Type`. ```php $httpResponse->setContentType('text/plain', 'UTF-8'); ``` -redirect(string $url, int $code=self::S302_Found): void .[method] ------------------------------------------------------------------ -Перенаправляет на другой URL. Не забудьте после этого выйти из скрипта. +redirect(string $url, int $code = self::S302_Found): void .[method] +------------------------------------------------------------------- +Перенаправляет на другой URL. Не забудьте после этого завершить скрипт. ```php $httpResponse->redirect('http://example.com'); @@ -96,54 +96,54 @@ exit; ``` -setExpiration(?string $time) .[method] --------------------------------------- -Устанавливает срок действия HTTP-документа, использующего заголовки `Cache-Control` и `Expires`. Параметром является либо временной интервал (в виде текста), либо `null`, который отключает кэширование. +setExpiration(?string $time): static .[method] +---------------------------------------------- +Устанавливает срок действия (expiration) HTTP-документа с помощью заголовков `Cache-Control` и `Expires`. Параметром является либо временной интервал (как текст), либо `null`, что запрещает кеширование. ```php -// срок действия кэша браузера истекает через час +// кеш в браузере истечет через час $httpResponse->setExpiration('1 hour'); ``` -sendAsFile(string $fileName) .[method] --------------------------------------- -Ответ должен быть загружен с помощью диалога *Сохранить как* с указанным именем. Сам файл на вывод не отправляется. +sendAsFile(string $fileName): static .[method] +---------------------------------------------- +Ответ будет скачан с помощью диалогового окна *Сохранить как* под указанным именем. Сам файл при этом не отправляется. ```php -$httpResponse->sendAsFile('invoice.pdf'); +$httpResponse->sendAsFile('faktura.pdf'); ``` -setCookie(string $name, string $value, $time, string $path=null, string $domain=null, bool $secure=null, bool $httpOnly=null, string $sameSite=null) .[method] --------------------------------------------------------------------------------------------------------------------------------------------------------------- -Отправляет cookie. Значения параметров по умолчанию: +setCookie(string $name, string $value, $time, ?string $path=null, ?string $domain=null, ?bool $secure=null, ?bool $httpOnly=null, ?string $sameSite=null) .[method] +------------------------------------------------------------------------------------------------------------------------------------------------------------------- +Отправляет cookie. Значения по умолчанию параметров: -| `$path` | `'/'` | с охватом всех путей на (под)домене *(настраивается)*. -| `$domain` | `null` | с областью действия текущего (под)домена, но не его поддоменов *(настраивается)*. -| `$secure` | `true` | если сайт работает на HTTPS, иначе `false` *(настраивается)*. -| `$httpOnly` | `true` | cookie недоступна для JavaScript -| `$sameSite` | `'Lax'` | cookie не нужно отправлять при [доступе из другого источника |nette:glossary#SameSite-Cookie] +| `$path` | `'/'` | cookie имеет область действия на все пути в (суб)домене *(настраиваемый)* +| `$domain` | `null` | что означает с областью действия на текущий (суб)домен, но не его субдомены *(настраиваемый)* +| `$secure` | `true` | если сайт работает по HTTPS, иначе `false` *(настраиваемый)* +| `$httpOnly` | `true` | cookie недоступен для JavaScript +| `$sameSite` | `'Lax'` | cookie может не отправляться при [доступе с другого домена |nette:glossary#SameSite cookie] -Вы можете изменить значения по умолчанию параметров `$path`, `$domain` и `$secure` в [конфигурация |configuration#HTTP-Cookie]. +Значения по умолчанию параметров `$path`, `$domain` и `$secure` вы можете изменить в [конфигурации |configuration#HTTP cookie]. -Время может быть указано в виде количества секунд или строки: +Время можно указывать как количество секунд или строку: ```php -$httpResponse->setCookie('lang', 'en', '100 days'); +$httpResponse->setCookie('lang', 'cs', '100 days'); ``` -Параметр `$domain` определяет, какие домены (origin) могут принимать cookie. Если параметр не указан, cookie принимается тем же (под)доменом, который задан, исключая их поддомены. Если указан `$domain`, то поддомены также включаются. Поэтому указание `$domain` является менее ограничительным, чем опущение. Например, если `$domain = 'nette.org'`, cookie также доступен на всех поддоменах, как `doc.nette.org`. +Параметр `$domain` определяет, какие домены могут принимать cookie. Если он не указан, cookie принимает тот же (суб)домен, который его установил, но не его субдомены. Если `$domain` указан, субдомены также включаются. Поэтому указание `$domain` менее ограничивающее, чем его отсутствие. Например, при `$domain = 'nette.org'` cookie доступны и на всех субдоменах, таких как `doc.nette.org`. -Для значения `$sameSite` можно использовать константы `Response::SameSiteLax`, `SameSiteStrict` и `SameSiteNone`. +Для значения `$sameSite` вы можете использовать константы `Response::SameSiteLax`, `Response::SameSiteStrict` и `Response::SameSiteNone`. -deleteCookie(string $name, string $path=null, string $domain=null, bool $secure=null): void .[method] ------------------------------------------------------------------------------------------------------ -Удаляет файл cookie. По умолчанию параметры имеют следующие значения: +deleteCookie(string $name, ?string $path=null, ?string $domain=null, ?bool $secure=null): void .[method] +-------------------------------------------------------------------------------------------------------- +Удаляет cookie. Значения по умолчанию параметров: - `$path` с областью действия на все каталоги (`'/'`) -- `$domain` с областью действия на текущий (под)домен, но не на его поддомены. -- `$secure` зависит от настроек в [конфигурации |configuration#HTTP-Cookie] +- `$domain` с областью действия на текущий (суб)домен, но не его субдомены +- `$secure` определяется настройками в [конфигурации |configuration#HTTP cookie] ```php $httpResponse->deleteCookie('lang'); diff --git a/http/ru/sessions.texy b/http/ru/sessions.texy index 78719bdc8b..45fd7c9517 100644 --- a/http/ru/sessions.texy +++ b/http/ru/sessions.texy @@ -1,9 +1,9 @@ -Сеансы +Сессии ****** <div class=perex> -HTTP - это протокол без статических данных, но почти каждому приложению необходимо сохранять состояние между запросами, например, содержимое корзины. Для этого и используется сессия. Давайте посмотрим +HTTP — это протокол без состояния, однако почти каждое приложение нуждается в сохранении состояния между запросами, например, содержимого корзины покупок. Именно для этого служат сессии. Мы покажем, - как использовать сессии - как избежать конфликтов имен @@ -11,61 +11,61 @@ HTTP - это протокол без статических данных, но </div> -При использовании сессий каждый пользователь получает уникальный идентификатор, называемый ID сессии, который передается в cookie. Он служит ключом к данным сеанса. В отличие от cookie, которые хранятся на стороне браузера, данные сеанса хранятся на стороне сервера. +При использовании сессий каждый пользователь получает уникальный идентификатор, называемый ID сессии, который передается в cookie. Он служит ключом к данным сессии. В отличие от cookie, которые хранятся на стороне браузера, данные в сессии хранятся на стороне сервера. -Мы настраиваем сессию в [конфигурации |configuration#Session], при этом важен выбор времени истечения срока действия. +Сессию мы настраиваем в [конфигурации |configuration#Сессия], особенно важен выбор срока действия (expiration). -Сессией управляет объект [api:Nette\Http\Session], который вы получаете, передавая его с помощью [инъекции зависимостей |dependency-injection:passing-dependencies]. В презентаторах просто вызываем `$session = $this->getSession()`. +Управление сессией осуществляет объект [api:Nette\Http\Session], к которому вы можете получить доступ, запросив его с помощью [внедрения зависимостей |dependency-injection:passing-dependencies]. В презентерах достаточно просто вызвать `$session = $this->getSession()`. -→ [Установка и требования |@home#Installation] +→ [Установка и требования |@home#Установка] -Запуск сеанса .[#toc-starting-session] -====================================== +Запуск сессии +============= -По умолчанию Nette автоматически запускает сессию в тот момент, когда мы начинаем читать из нее или записывать в нее данные. Чтобы запустить сессию вручную, используйте `$session->start()`. +Nette по умолчанию автоматически запускает сессию в тот момент, когда мы начинаем читать из нее или записывать в нее данные. Вручную сессия запускается с помощью `$session->start()`. -PHP отправляет HTTP-заголовки, влияющие на кэширование, при запуске сессии, см. [php:session_cache_limiter], и, возможно, cookie с идентификатором сессии. Поэтому всегда необходимо запускать сессию перед отправкой любого вывода в браузер, иначе будет выброшено исключение. Поэтому, если вы знаете, что сессия будет использоваться во время рендеринга страницы, запустите ее вручную, например, в презентаторе. +PHP при запуске сессии отправляет HTTP-заголовки, влияющие на кеширование, см. [php:session_cache_limiter], и, возможно, cookie с ID сессии. Поэтому необходимо всегда запускать сессию еще до отправки любого вывода в браузер, иначе будет выброшено исключение. Если вы знаете, что в процессе рендеринга страницы будет использоваться сессия, запустите ее вручную заранее, например, в презентере. -В режиме разработчика Tracy запускает сессию, поскольку использует ее для отображения полос перенаправления и запросов AJAX в панели Tracy. +В режиме разработки сессию запускает Tracy, так как он использует ее для отображения полос с перенаправлениями и AJAX-запросами в Tracy Bar. -Раздел .[#toc-section] -====================== +Секции +====== -В чистом PHP хранилище данных сессии реализовано в виде массива, доступного через глобальную переменную `$_SESSION`. Проблема заключается в том, что приложения обычно состоят из нескольких независимых частей, и если всем доступен только один и тот же массив, рано или поздно произойдет столкновение имен. +В чистом PHP хранилище данных сессии реализовано как массив, доступный через глобальную переменную `$_SESSION`. Проблема в том, что приложения обычно состоят из целого ряда взаимно независимых частей, и если все они имеют доступ только к одному массиву, рано или поздно произойдет конфликт имен. -Nette Framework решает эту проблему путем разделения всего пространства на секции (объекты [api:Nette\Http\SessionSection]). При этом каждая часть использует свой собственный раздел с уникальным именем, и коллизии не возникают. +Nette Framework решает эту проблему, разделяя все пространство на секции (объекты [api:Nette\Http\SessionSection |api:Nette\Http\SessionSection]). Каждая единица затем использует свою секцию с уникальным именем, и никакой коллизии уже произойти не может. -Мы получаем раздел от менеджера сессий: +Секцию получаем из сессии: ```php -$section = $session->getSection('unique name'); +$section = $session->getSection('уникальное имя'); ``` -В презентере достаточно вызвать `getSession()` с параметром: +В презентере достаточно использовать `getSession()` с параметром: ```php -// $this - Presenter -$section = $this->getSession('unique name'); +// $this - это Presenter +$section = $this->getSession('уникальное имя'); ``` -Существование раздела можно проверить методом `$session->hasSection('unique name')`. +Проверить существование секции можно методом `$session->hasSection('уникальное имя')`. -С самим разделом очень легко работать, используя методы `set()`, `get()` и `remove()`: +С самой секцией затем работать очень легко с помощью методов `set()`, `get()` и `remove()`: ```php // запись переменной $section->set('userName', 'franta'); -// чтение переменной, возвращает null, если она не существует +// чтение переменной, вернет null, если не существует echo $section->get('userName'); // удаление переменной $section->remove('userName'); ``` -Можно использовать цикл `foreach` для получения всех переменных из секции: +Для получения всех переменных из секции можно использовать цикл `foreach`: ```php foreach ($section as $key => $val) { @@ -74,63 +74,63 @@ foreach ($section as $key => $val) { ``` -Как установить срок действия .[#toc-how-to-set-expiration] ----------------------------------------------------------- +Установка срока действия +------------------------ -Срок действия может быть установлен для отдельных разделов или даже отдельных переменных. Мы можем позволить логину пользователя истечь через 20 минут, но при этом сохранить содержимое корзины. +Для отдельных секций или даже отдельных переменных можно установить срок действия. Мы можем, например, установить истечение срока действия входа пользователя через 20 минут, но при этом продолжать помнить содержимое корзины. ```php -// срок действия раздела истекает через 20 минут +// секция истечет через 20 минут $section->setExpiration('20 minutes'); ``` -Третий параметр метода `set()` используется для установки срока действия отдельных переменных: +Для установки срока действия отдельных переменных служит третий параметр метода `set()`: ```php -// переменная 'flash' истекает через 30 секунд +// переменная 'flash' истечет уже через 30 секунд $section->set('flash', $message, '30 seconds'); ``` .[note] -Помните, что время истечения срока действия всей сессии (см. [конфигурацию сессии |configuration#Session]) должно быть равно или больше времени, установленного для отдельных секций или переменных. +Не забывайте, что срок экспирации всей сессии (см. [конфигурацию сессии |configuration#Сессия]) должен быть равен или больше срока, установленного для отдельных секций или переменных. -Отмена ранее установленного срока действия может быть достигнута с помощью метода `removeExpiration()`. Немедленное удаление всего раздела обеспечивается методом `remove()`. +Отмену ранее установленного срока действия обеспечивает метод `removeExpiration()`. Немедленное удаление всей секции обеспечивает метод `remove()`. -События $onStart, $onBeforeWrite .[#toc-events-onstart-onbeforewrite] ---------------------------------------------------------------------- +События $onStart, $onBeforeWrite +-------------------------------- -Объект `Nette\Http\Session` имеет [события |nette:glossary#Events] `$onStart` и `$onBeforeWrite`, поэтому вы можете добавить обратные вызовы, которые будут вызываться после начала сессии или перед тем, как она будет записана на диск и затем завершена. +Объект `Nette\Http\Session` имеет [события |nette:glossary#События Events] `$onStart` и `$onBeforeWrite`, поэтому вы можете добавить колбэки, которые будут вызваны после запуска сессии или перед ее записью на диск и последующим завершением. ```php $session->onBeforeWrite[] = function () { - // записываем данные в сессию + // запишем данные в сессию $this->section->set('basket', $this->basket); }; ``` -Управление сессиями .[#toc-session-management] -============================================== +Управление сессией +================== -Обзор методов класса `Nette\Http\Session` для управления сеансами: +Обзор методов класса `Nette\Http\Session` для управления сессией: <div class=wiki-methods-brief> start(): void .[method] ----------------------- -Запускает сеанс. +Запускает сессию. isStarted(): bool .[method] --------------------------- -Запущен ли сеанс? +Запущена ли сессия? close(): void .[method] ----------------------- -Завершает сеанс. Сеанс завершается автоматически в конце сценария. +Завершает сессию. Сессия автоматически завершается в конце выполнения скрипта. destroy(): void .[method] @@ -140,71 +140,72 @@ destroy(): void .[method] exists(): bool .[method] ------------------------ -Содержит ли HTTP-запрос файл cookie с идентификатором сессии? +Содержит ли HTTP-запрос cookie с ID сессии? regenerateId(): void .[method] ------------------------------ -Генерирует новый случайный идентификатор сессии. Данные остаются неизменными. +Генерирует новый случайный ID сессии. Данные остаются сохраненными. getId(): string .[method] ------------------------- -Возвращает идентификатор сессии. +Возвращает ID сессии. </div> -Конфигурация .[#toc-configuration] ----------------------------------- +Конфигурация +------------ -Мы настраиваем сессию в [конфигурации |configuration#Session]. Если вы пишете приложение, которое не использует DI-контейнер, используйте эти методы для конфигурирования. Они должны быть вызваны до запуска сессии. +Сессию настраиваем в [конфигурации |configuration#Сессия]. Если вы пишете приложение, которое не использует DI-контейнер, для конфигурации служат следующие методы. Они должны быть вызваны еще до запуска сессии. <div class=wiki-methods-brief> setName(string $name): static .[method] --------------------------------------- -Устанавливает имя cookie, которое используется для передачи идентификатора сессии. По умолчанию используется имя `PHPSESSID`. Это полезно, если вы запускаете несколько разных приложений на одном сайте. +Устанавливает имя cookie, в котором передается ID сессии. Стандартное имя — `PHPSESSID`. Пригодится в случае, когда в рамках одного веб-сайта вы запускаете несколько различных приложений. getName(): string .[method] --------------------------- -Возвращает имя сеансового файла cookie. +Возвращает имя cookie, в котором передается ID сессии. setOptions(array $options): static .[method] -------------------------------------------- -Настраивает сессию. Можно установить все [директивы сессии |https://www.php.net/manual/en/session.configuration.php] PHP (в формате camelCase, например, написать `savePath` вместо `session.save_path`), а также [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. +Конфигурирует сессию. Можно устанавливать все PHP [директивы сессии |https://www.php.net/manual/en/session.configuration.php] (в формате camelCase, например, вместо `session.save_path` запишем `savePath`) и также [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. setExpiration(?string $time): static .[method] ---------------------------------------------- -Устанавливает время бездействия, после которого сессия завершается. +Устанавливает время неактивности, после которого сессия истечет. -setCookieParameters(string $path, string $domain=null, bool $secure=null, string $samesite=null): static .[method] ------------------------------------------------------------------------------------------------------------------- -Устанавливает параметры для куки. Значения параметров по умолчанию можно изменить в разделе [configuration|configuration#Session-Cookie]. +setCookieParameters(string $path, ?string $domain=null, ?bool $secure=null, ?string $samesite=null): static .[method] +--------------------------------------------------------------------------------------------------------------------- +Настройка параметров для cookie. Значения по умолчанию параметров вы можете изменить в [конфигурации |configuration#Session cookie]. setSavePath(string $path): static .[method] ------------------------------------------- -Устанавливает каталог, в котором хранятся файлы сессий. +Устанавливает каталог, куда сохраняются файлы сессий. setHandler(\SessionHandlerInterface $handler): static .[method] --------------------------------------------------------------- -Устанавливает пользовательский обработчик, см. [документацию PHP |https://www.php.net/manual/en/class.sessionhandlerinterface.php]. +Установка собственного обработчика, см. [документацию PHP|https://www.php.net/manual/en/class.sessionhandlerinterface.php]. </div> -Безопасность прежде всего .[#toc-safety-first] -============================================== +Безопасность прежде всего +========================= -Сервер предполагает, что он общается с одним и тем же пользователем до тех пор, пока запросы содержат один и тот же идентификатор сессии. Задача механизмов безопасности - гарантировать, что такое поведение действительно работает и что нет возможности подменить или украсть идентификатор. +Сервер предполагает, что он постоянно взаимодействует с одним и тем же пользователем, пока запросы сопровождаются одним и тем же ID сессии. Задачей механизмов безопасности является обеспечение того, чтобы это действительно было так, и чтобы идентификатор нельзя было украсть или подменить. -Именно поэтому Nette Framework правильно настраивает директивы PHP для передачи идентификатора сессии только в cookies, для предотвращения доступа из JavaScript и для игнорирования идентификаторов в URL. Более того, в критические моменты, такие как вход пользователя в систему, он генерирует новый идентификатор сессии. +Поэтому Nette Framework правильно конфигурирует PHP-директивы, чтобы ID сессии передавался только в cookie, делал его недоступным для JavaScript и игнорировал возможные идентификаторы в URL. Кроме того, в критические моменты, такие как вход пользователя, он генерирует новый ID сессии. -Функция ini_set используется для настройки PHP, но, к сожалению, ее использование запрещено на некоторых хостингах. Если это ваш случай, попробуйте попросить хостинг-провайдера разрешить эту функцию для вас или хотя бы правильно настроить его сервер. .[note] +.[note] +Для конфигурации PHP используется функция ini_set, которую, к сожалению, некоторые хостинги запрещают. Если это ваш случай, попробуйте договориться с хостером, чтобы он разрешил вам эту функцию или хотя бы настроил сервер. diff --git a/http/ru/urls.texy b/http/ru/urls.texy index 3b3f370eea..57caebed27 100644 --- a/http/ru/urls.texy +++ b/http/ru/urls.texy @@ -1,19 +1,19 @@ -Парсер и конструктор URL -************************ +Работа с URL +************ .[perex] -Классы [Url |#Url], [UrlImmutable |#UrlImmutable] и [UrlScript |#UrlScript] позволяют легко управлять, разбирать и манипулировать URL-адресами. +Классы [#Url], [#UrlImmutable] и [#UrlScript] позволяют легко генерировать, парсить и манипулировать URL. -→ [Установка и требования |@home#Installation] +→ [Установка и требования |@home#Установка] Url === -Класс [api:Nette\Http\Url] позволяет легко работать с URL и его отдельными компонентами, которые показаны на этой диаграмме: +Класс [api:Nette\Http\Url |api:Nette\Http\Url] позволяет легко работать с URL и его отдельными компонентами, которые отражены на этой схеме: /--pre - scheme user password host port path query fragment + схема пользователь пароль хост порт путь запрос фрагмент | | | | | | | | /--\ /--\ /------\ /-------\ /--\/----------\ /--------\ /----\ <b>http://john:xyz%2A12@nette.org:8080/en/download?name=param#footer</b> @@ -36,7 +36,7 @@ $url->setScheme('https') echo $url; // 'https://localhost/edit?foo=bar' ``` -Вы также можете разобрать URL и затем манипулировать им: +Также можно распарсить URL и далее манипулировать им: ```php $url = new Url( @@ -44,62 +44,94 @@ $url = new Url( ); ``` -Для получения или изменения отдельных компонентов URL доступны следующие методы: +Класс `Url` реализует интерфейс `JsonSerializable` и имеет метод `__toString()`, так что объект можно вывести или использовать в данных, передаваемых в `json_encode()`. + +```php +echo $url; +echo json_encode([$url]); +``` + + +Компоненты URL .[method] +------------------------ + +Для возврата или изменения отдельных компонентов URL вам доступны следующие методы: .[language-php] -| Setter | Getter | Возвращаемое значение +| Сеттер | Геттер | Возвращаемое значение |-------------------------------------------------------------------------------------------- -| `setScheme(string $scheme)`| `getScheme(): string`| `'http'` -| `setUser(string $user)`| `getUser(): string`| `'john'` -| `setPassword(string $password)`| `getPassword(): string`| `'xyz*12'` -| `setHost(string $host)`| `getHost(): string`| `'nette.org'` -| `setPort(int $port)`| `getPort(): ?int`| `8080` -| | `getDefaultPort(): ?int`| `80` -| `setPath(string $path)`| `getPath(): string`| `'/en/download'` -| `setQuery(string\|array $query)`| `getQuery(): string`| `'name=param'` -| `setFragment(string $fragment)`| `getFragment(): string`| `'footer'` -| | `getAuthority(): string`| `'nette.org:8080'` -| | `getHostUrl(): string`| `'http://nette.org:8080'` -| | `getAbsoluteUrl(): string` | полный URL - -Мы также можем работать с отдельными параметрами запроса, используя: +| `setScheme(string $scheme)` | `getScheme(): string` | `'http'` +| `setUser(string $user)` | `getUser(): string` | `'john'` +| `setPassword(string $password)` | `getPassword(): string` | `'xyz*12'` +| `setHost(string $host)` | `getHost(): string` | `'nette.org'` +| `setPort(int $port)` | `getPort(): ?int` | `8080` +| | `getDefaultPort(): ?int` | `80` +| `setPath(string $path)` | `getPath(): string` | `'/en/download'` +| `setQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` +| `setFragment(string $fragment)` | `getFragment(): string` | `'footer'` +| | `getAuthority(): string` | `'john:xyz%2A12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | весь URL + +Предупреждение: Когда вы работаете с URL, полученным из [HTTP-запроса|request], имейте в виду, что он не будет содержать фрагмент, так как браузер не отправляет его на сервер. + +Мы можем работать и с отдельными query-параметрами с помощью: .[language-php] -| Setter | Getter +| Сеттер | Геттер |--------------------------------------------------- -| `setQuery(string\|array $query)` | `getQueryParameters(): array` -| `setQueryParameter(string $name, $val)`| `getQueryParameter(string $name)` +| `setQuery(string\|array $query)` | `getQueryParameters(): array` +| `setQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` -Метод `getDomain(int $level = 2)` возвращает правую или левую часть хоста. Вот как это работает, если хост - `www.nette.org`: + +getDomain(int $level = 2): string .[method] +------------------------------------------- +Возвращает правую или левую часть хоста. Так это работает, если хост `www.nette.org`: .[language-php] -| `getDomain(1)` | `'org'` -| `getDomain(2)` | `'nette.org'` -| `getDomain(3)` | `'www.nette.org'` -| `getDomain(0)` | `'www.nette.org'` -| `getDomain(-1)` | `'www.nette'` -| `getDomain(-2)` | `'www'` -| `getDomain(-3)` | `''` +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `''` -Класс `Url` реализует интерфейс `JsonSerializable` и имеет метод `__toString()`, чтобы объект можно было распечатать или использовать в данных, передаваемых в `json_encode()`. +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +Проверяет, идентичны ли два URL. ```php -echo $url; -echo json_encode([$url]); +$url->isEqual('https://nette.org'); ``` -Метод `isEqual(string|Url $anotherUrl): bool` проверяет, идентичны ли два URL. + +Url::isAbsolute(string $url): bool .[method]{data-version:3.3.2} +---------------------------------------------------------------- +Проверяет, является ли URL абсолютным. URL считается абсолютным, если он начинается со схемы (например, http, https, ftp), за которой следует двоеточие. ```php -$url->isEqual('https://nette.org'); +Url::isAbsolute('https://nette.org'); // true +Url::isAbsolute('//nette.org'); // false +``` + + +Url::removeDotSegments(string $path): string .[method]{data-version:3.3.2} +-------------------------------------------------------------------------- +Нормализует путь в URL, удаляя специальные сегменты `.` и `..`. Метод удаляет избыточные элементы пути тем же способом, как это делают веб-браузеры. + +```php +Url::removeDotSegments('/path/../subtree/./file.txt'); // '/subtree/file.txt' +Url::removeDotSegments('/../foo/./bar'); // '/foo/bar' +Url::removeDotSegments('./today/../file.txt'); // 'file.txt' ``` -UrlImmutable .[#toc-urlimmutable] -================================= +UrlImmutable +============ -Класс [api:Nette\Http\UrlImmutable] является неизменяемой альтернативой классу `Url` (так же, как в PHP `DateTimeImmutable` является неизменяемой альтернативой `DateTime`). Вместо сеттеров он имеет так называемые withers, которые не изменяют объект, а возвращают новые экземпляры с измененным значением: +Класс [api:Nette\Http\UrlImmutable |api:Nette\Http\UrlImmutable] является immutable (неизменяемой) альтернативой классу [#Url] (подобно тому, как в PHP `DateTimeImmutable` является неизменяемой альтернативой `DateTime`). Вместо сеттеров у него есть так называемые withers, которые не изменяют объект, а возвращают новые экземпляры с измененным значением: ```php use Nette\Http\UrlImmutable; @@ -111,53 +143,96 @@ $url = new UrlImmutable( $newUrl = $url ->withUser('') ->withPassword('') - ->withPath('/en/'); + ->withPath('/cs/'); + +echo $newUrl; // 'http://john:xyz%2A12@nette.org:8080/cs/?name=param#footer' +``` + +Класс `UrlImmutable` реализует интерфейс `JsonSerializable` и имеет метод `__toString()`, так что объект можно вывести или использовать в данных, передаваемых в `json_encode()`. -echo $newUrl; // 'http://nette.org:8080/en/?name=param#footer' +```php +echo $url; +echo json_encode([$url]); ``` -Для получения или изменения отдельных компонентов URL доступны следующие методы: + +Компоненты URL .[method] +------------------------ + +Для возврата или изменения отдельных компонентов URL служат методы: .[language-php] -| Wither | Getter | Возвращаемое значение +| Wither | Геттер | Возвращаемое значение |-------------------------------------------------------------------------------------------- -| `withScheme(string $scheme)`| `getScheme(): string`| `'http'` -| `withUser(string $user)`| `getUser(): string`| `'john'` -| `withPassword(string $password)`| `getPassword(): string`| `'xyz*12'` -| `withHost(string $host)`| `getHost(): string`| `'nette.org'` -| `withPort(int $port)`| `getPort(): ?int`| `8080` -| | `getDefaultPort(): ?int`| `80` -| `withPath(string $path)`| `getPath(): string`| `'/en/download'` -| `withQuery(string\|array $query)`| `getQuery(): string`| `'name=param'` -| `withFragment(string $fragment)`| `getFragment(): string`| `'footer'` -| | `getAuthority(): string`| `'nette.org:8080'` -| | `getHostUrl(): string`| `'http://nette.org:8080'` -| | `getAbsoluteUrl(): string` | полный URL - -Мы также можем работать с отдельными параметрами запроса, используя: +| `withScheme(string $scheme)` | `getScheme(): string` | `'http'` +| `withUser(string $user)` | `getUser(): string` | `'john'` +| `withPassword(string $password)` | `getPassword(): string` | `'xyz*12'` +| `withHost(string $host)` | `getHost(): string` | `'nette.org'` +| `withPort(int $port)` | `getPort(): ?int` | `8080` +| | `getDefaultPort(): ?int` | `80` +| `withPath(string $path)` | `getPath(): string` | `'/en/download'` +| `withQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` +| `withFragment(string $fragment)` | `getFragment(): string` | `'footer'` +| | `getAuthority(): string` | `'john:xyz%2A12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | весь URL + +Метод `withoutUserInfo()` удаляет `user` и `password`. + +Мы можем работать и с отдельными query-параметрами с помощью: .[language-php] -| Wither | Getter +| Wither | Геттер |----------------------------------------------- -| `withQuery(string\|array $query)` | `getQueryParameters(): array` -| `withQueryParameter(string $name, $val)` | | `getQueryParameter(string $name)` +| `withQuery(string\|array $query)` | `getQueryParameters(): array` +| `withQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` -Метод `getDomain(int $level = 2)` работает так же, как и метод в `Url`. Метод `withoutUserInfo()` удаляет `user` и `password`. -Класс `UrlImmutable` реализует интерфейс `JsonSerializable` и имеет метод `__toString()`, чтобы объект можно было распечатать или использовать в данных, передаваемых в `json_encode()`. +getDomain(int $level = 2): string .[method] +------------------------------------------- +Возвращает правую или левую часть хоста. Так это работает, если хост `www.nette.org`: + +.[language-php] +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `''` + + +resolve(string $reference): UrlImmutable .[method]{data-version:3.3.2} +---------------------------------------------------------------------- +Выводит абсолютный URL тем же способом, каким браузер обрабатывает ссылки на HTML-странице: +- если ссылка является абсолютным URL (содержит схему), она используется без изменений +- если ссылка начинается с `//`, берется только схема из текущего URL +- если ссылка начинается с `/`, создается абсолютный путь от корня домена +- в остальных случаях URL составляется относительно текущего пути ```php -echo $url; -echo json_encode([$url]); +$url = new UrlImmutable('https://example.com/path/page'); +echo $url->resolve('../foo'); // 'https://example.com/foo' +echo $url->resolve('/bar'); // 'https://example.com/bar' +echo $url->resolve('sub/page.html'); // 'https://example.com/path/sub/page.html' +``` + + +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +Проверяет, идентичны ли два URL. + +```php +$url->isEqual('https://nette.org'); ``` -Метод `isEqual(string|Url $anotherUrl): bool` проверяет, идентичны ли два URL. +UrlScript +========= -UrlScript .[#toc-urlscript] -=========================== +Класс [api:Nette\Http\UrlScript |api:Nette\Http\UrlScript] является потомком [#UrlImmutable] и расширяет его дополнительными виртуальными компонентами URL, такими как корневой каталог проекта и т.д. Как и родительский класс, это immutable (неизменяемый) объект. -Класс [api:Nette\Http\UrlScript] является потомком класса `UrlImmutable` и дополнительно различает эти логические части URL: +Следующая диаграмма отображает компоненты, которые распознает UrlScript: /--pre baseUrl basePath relativePath relativeUrl @@ -169,17 +244,23 @@ UrlScript .[#toc-urlscript] scriptPath pathInfo \-- -Для получения этих частей доступны следующие методы: +- `baseUrl` — это базовый URL-адрес приложения, включая домен и часть пути к корневому каталогу приложения +- `basePath` — это часть пути к корневому каталогу приложения +- `scriptPath` — это путь к текущему скрипту +- `relativePath` — это имя скрипта (возможно, с дополнительными сегментами пути) относительно basePath +- `relativeUrl` — это вся часть URL после baseUrl, включая строку запроса и фрагмент. +- `pathInfo` — сегодня уже малоиспользуемая часть URL после имени скрипта + +Для возврата частей URL доступны методы: .[language-php] -| Getter | Возвращаемое значение +| Геттер | Возвращаемое значение |------------------------------------------------ -| `getScriptPath(): string`| `'/admin/script.php'` -| `getBasePath(): string`| `'/admin/'` -| `getBaseUrl(): string`| `'http://nette.org/admin/'` -| `getRelativePath(): string`| `'script.php'` -| `getRelativeUrl(): string`| `'script.php/pathinfo/?name=param#footer'` -| `getPathInfo(): string`| `'/pathinfo/'` - - -Мы не создаем объекты `UrlScript` напрямую, но метод [Nette\Http\Request::getUrl() |request] возвращает его. +| `getScriptPath(): string` | `'/admin/script.php'` +| `getBasePath(): string` | `'/admin/'` +| `getBaseUrl(): string` | `'http://nette.org/admin/'` +| `getRelativePath(): string` | `'script.php'` +| `getRelativeUrl(): string` | `'script.php/pathinfo/?name=param#footer'` +| `getPathInfo(): string` | `'/pathinfo/'` + +Объекты `UrlScript` обычно не создаются напрямую, но их возвращает метод [Nette\Http\Request::getUrl()|request] с уже правильно настроенными компонентами для текущего HTTP-запроса. diff --git a/http/sl/@home.texy b/http/sl/@home.texy index 2cc0deb22b..aa5ad2508b 100644 --- a/http/sl/@home.texy +++ b/http/sl/@home.texy @@ -2,13 +2,13 @@ Nette HTTP ********** .[perex] -Paket `nette/http` vsebuje [zahteve |request] in [odzive |response] [HTTP |request], delo s [sejami |sessions] ter [razčlenjevanje in gradnjo URL-jev |urls]. +Paket `nette/http` zaobjema [HTTP zahtevo|request] & [odgovor|response], delo s [sejami|sessions] ter [razčlenjevanje in sestavljanje URL-jev |urls]. -Namestitev .[#toc-installation] -------------------------------- +Namestitev +---------- -Prenesite in namestite paket s [programom Composer |best-practices:composer]: +Knjižnico prenesete in namestite z orodjem [Composer|best-practices:composer]: ```shell composer require nette/http diff --git a/http/sl/@left-menu.texy b/http/sl/@left-menu.texy index e0bf901c97..e024b050e0 100644 --- a/http/sl/@left-menu.texy +++ b/http/sl/@left-menu.texy @@ -1,8 +1,8 @@ -Net HTTP -******** -- [Pregled |@home] -- [Zahteva HTTP |request] -- [Odziv HTTP |response] +Nette HTTP +********** +- [Uvod |@home] +- [HTTP zahteva|request] +- [HTTP odgovor|response] - [Seje |Sessions] -- [Pripomoček URL |urls] -- [Konfiguracija |Configuration] +- [Pripomočki za URL |urls] +- [Konfiguracija |configuration] diff --git a/http/sl/@meta.texy b/http/sl/@meta.texy new file mode 100644 index 0000000000..724324bee5 --- /dev/null +++ b/http/sl/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokumentacija}} diff --git a/http/sl/configuration.texy b/http/sl/configuration.texy index c5132e9076..1e57c793c9 100644 --- a/http/sl/configuration.texy +++ b/http/sl/configuration.texy @@ -1,40 +1,40 @@ -Konfiguriranje protokola HTTP -***************************** +Konfiguracija HTTP +****************** .[perex] -Pregled možnosti konfiguracije za Nette HTTP. +Pregled konfiguracijskih možnosti za Nette HTTP. -Če ne uporabljate celotnega ogrodja, temveč samo to knjižnico, preberite, [kako naložiti konfiguracijo |bootstrap:]. +Če ne uporabljate celotnega ogrodja, ampak samo to knjižnico, preberite, [kako naložiti konfiguracijo|bootstrap:]. -Glave HTTP .[#toc-http-headers] -=============================== +Glave HTTP +========== ```neon http: - # glave, ki se pošljejo z vsako zahtevo. + # glave, ki se pošljejo z vsako zahtevo headers: X-Powered-By: MyCMS X-Content-Type-Options: nosniff X-XSS-Protection: '1; mode=block' # vpliva na glavo X-Frame-Options - frames: ... # (niz|bool) privzeto je 'SAMEORIGIN' + frames: ... # (string|bool) privzeto je 'SAMEORIGIN' ``` -Iz varnostnih razlogov ogrodje pošlje glavo `X-Frame-Options: SAMEORIGIN`, ki pove, da se stran lahko prikaže znotraj druge strani (v elementu `<iframe>`) le, če je ta v isti domeni. To je lahko v določenih situacijah nezaželeno (na primer, če razvijate aplikacijo za Facebook), zato lahko obnašanje spremenite z nastavitvijo okvirjev `frames: http://allowed-host.com`. +Ogrodje iz varnostnih razlogov pošilja glavo `X-Frame-Options: SAMEORIGIN`, ki pravi, da se stran lahko prikaže znotraj druge strani (v elementu `<iframe>`) samo, če se nahaja na isti domeni. To je lahko v nekaterih situacijah nezaželeno (na primer, če razvijate aplikacijo za Facebook), vedenje lahko zato spremenite z nastavitvijo `frames: http://allowed-host.com` ali `frames: true`. -Varnostna politika vsebine .[#toc-content-security-policy] ----------------------------------------------------------- +Content Security Policy +----------------------- -Naslovnice `Content-Security-Policy` (v nadaljevanju CSP) je mogoče preprosto sestaviti, njihov opis pa je na voljo v [opisu CSP |https://content-security-policy.com]. Smernice CSP (kot je `script-src`) so lahko zapisane kot nizi v skladu s specifikacijo ali kot polja vrednosti zaradi boljše berljivosti. Potem ni treba pisati narekovajev okoli ključnih besed, kot je `'self'`. Nette bo samodejno ustvaril tudi vrednost `nonce`, zato bo `'nonce-y4PopTLM=='` poslana v glavi. +Enostavno je mogoče sestaviti glave `Content-Security-Policy` (v nadaljevanju CSP), njihov opis najdete v [opisu CSP |https://content-security-policy.com]. CSP direktive (kot npr. `script-src`) so lahko zapisane bodisi kot nizi po specifikaciji ali kot polja vrednosti zaradi boljše čitljivosti. Potem ni treba okoli ključnih besed, kot na primer `'self'`, pisati narekovajev. Nette tudi samodejno generira vrednost `nonce`, tako da bo v glavi na primer `'nonce-y4PopTLM=='`. ```neon http: - # Pravilnik o varnosti vsebine + # Content Security Policy csp: - # niz v skladu s specifikacijo CSP + # niz v obliki po specifikaciji CSP default-src: "'self' https://example.com" # polje vrednosti @@ -49,18 +49,18 @@ http: block-all-mixed-content: false ``` -Uporabite `<script n:nonce>...</script>` v predlogah in vrednost nonce bo samodejno izpolnjena. Ustvarjanje varnih spletnih mest v Nette je zelo enostavno. +V predlogah uporabljajte `<script n:nonce>...</script>` in vrednost nonce se dopolni samodejno. Delati varne spletne strani v Nette je res enostavno. -Podobno je mogoče dodati glave `Content-Security-Policy-Report-Only` (ki se lahko uporabljajo vzporedno s CSP) in [politiko funkcij |https://developers.google.com/web/updates/2018/06/feature-policy]: +Podobno je mogoče sestaviti tudi glave `Content-Security-Policy-Report-Only` (ki jih je mogoče uporabljati sočasno s CSP) in [Feature Policy|https://developers.google.com/web/updates/2018/06/feature-policy]: ```neon http: - # Samo poročilo o politiki varnosti vsebine + # Content Security Policy Report-Only cspReportOnly: default-src: self report-uri: 'https://my-report-uri-endpoint' - # Politika funkcij + # Feature Policy featurePolicy: unsized-media: none geolocation: @@ -69,91 +69,103 @@ http: ``` -Piškotki HTTP .[#toc-http-cookie] ---------------------------------- +Piškotki HTTP +------------- -Spremenite lahko privzete vrednosti nekaterih parametrov metod [Nette\Http\Response::setCookie( |response#setCookie] ) in session. +Lahko spremenite privzete vrednosti nekaterih parametrov metode [Nette\Http\Response::setCookie() |response#setCookie] in seje. ```neon http: - # obseg piškotkov po poti - cookiePath: ... # (niz) privzeto je '/' + # doseg piškotka glede na pot + cookiePath: ... # (string) privzeto je '/' - # kateri gostitelji lahko prejmejo piškotek - cookieDomain: 'example.com' # (niz|domena) privzeta vrednost je ne-nastavljeno + # domene, ki sprejemajo piškotek + cookieDomain: 'example.com' # (string|domain) privzeto je nenastavljeno - # za pošiljanje piškotkov samo prek HTTPS? - cookieSecure: ... # (bool|auto) privzeto je samodejno + # pošiljati piškotek samo preko HTTPS? + cookieSecure: ... # (bool|auto) privzeto je auto - # onemogoči pošiljanje piškotka, ki ga Nette uporablja kot zaščito pred CSRF + # izklopi pošiljanje piškotka, ki ga uporablja Nette kot zaščito pred CSRF disableNetteCookie: ... # (bool) privzeto je false ``` -Možnost `cookieDomain` določa, katere domene (izvori) lahko sprejmejo piškotke. Če ni določena, piškotek sprejme ista (pod)domena, kot je nastavljena, *izključujoč* njihove poddomene. Če je določena možnost `cookieDomain`, so vključene tudi poddomene. Zato je navedba `cookieDomain` manj omejevalna kot opustitev. +Atribut `cookieDomain` določa, katere domene lahko sprejemajo piškotek. Če ni naveden, piškotek sprejema ista (pod)domena, kot ga je nastavila, *vendar ne* njenih poddomen. Če je `cookieDomain` določen, so vključene tudi poddomene. Zato je navedba `cookieDomain` manj omejujoča kot izpustitev. -Če je na primer nastavljena `cookieDomain: nette.org`, je piškotek na voljo tudi na vseh poddomenah, kot je `doc.nette.org`. To je mogoče doseči tudi s posebno vrednostjo `domain`, tj. `cookieDomain: domain`. +Na primer, pri `cookieDomain: nette.org` so piškotki dostopni tudi na vseh poddomenah kot `doc.nette.org`. Istega lahko dosežemo tudi s pomočjo posebne vrednosti `domain`, torej `cookieDomain: domain`. -Privzeta vrednost `cookieSecure` je `auto`, kar pomeni, da bodo piškotki, če spletna stran deluje v protokolu HTTPS, poslani z zastavico `Secure` in bodo zato na voljo le prek HTTPS. +Privzeta vrednost `auto` pri atributu `cookieSecure` pomeni, da če spletno mesto teče na HTTPS, se bodo piškotki pošiljali z zastavico `Secure` in bodo torej dostopni samo preko HTTPS. -Proxy strežnik HTTP .[#toc-http-proxy] --------------------------------------- +HTTP proxy +---------- -Če spletno mesto deluje za posredniškim strežnikom HTTP, vnesite naslov IP posredniškega strežnika, da bo zaznavanje povezav HTTPS delovalo pravilno, in naslov IP odjemalca. To pomeni, da bosta funkciji [Nette\Http\Request::getRemoteAddress() |request#getRemoteAddress] in [isSecured( |request#isSecured] ) vrnili pravilne vrednosti in da bodo v predlogah ustvarjene povezave s protokolom `https:`. +Če spletno mesto teče za HTTP proxyjem, vnesite njegov IP naslov, da bo pravilno delovalo zaznavanje povezave preko HTTPS in tudi IP naslova odjemalca. Torej, da bosta funkciji [Nette\Http\Request::getRemoteAddress() |request#getRemoteAddress] in [isSecured() |request#isSecured] vračali pravilne vrednosti in se bodo v predlogah generirale povezave s `https:` protokolom. ```neon http: - # naslov IP, območje (npr. 127.0.0.1/8) ali niz teh vrednosti - proxy: 127.0.0.1 # (string|string[]) privzeta vrednost je none + # IP naslov, obseg (npr. 127.0.0.1/8) ali polje teh vrednosti + proxy: 127.0.0.1 # (string|string[]) privzeto je nenastavljeno ``` -Seja .[#toc-session] -==================== +Seja +==== -Osnovne nastavitve [seje |sessions]: +Osnovne nastavitve [sej|sessions]: ```neon session: - # prikazuje ploščo seje v Tracy Baru? - debugger: ... # (bool) privzeto false + # prikazati ploščo seje v Tracy Bar? + debugger: ... # (bool) privzeto je false # čas neaktivnosti, po katerem seja poteče - expiration: 14 days # (niz) privzeto je '3 ure' + expiration: 14 days # (string) privzeto je '3 hours' - # kdaj začeti sejo? + # kdaj naj se zažene seja? autoStart: ... # (smart|always|never) privzeto je 'smart' - # handler, storitev, ki izvaja vmesnik SessionHandlerInterface + # handler, storitev, ki implementira vmesnik SessionHandlerInterface handler: @handlerService ``` -Možnost `autoStart` določa, kdaj se seja začne. Vrednost `always` pomeni, da se seja vedno zažene ob zagonu aplikacije. Vrednost `smart` pomeni, da se bo seja začela ob zagonu aplikacije le, če že obstaja, ali v trenutku, ko želimo iz nje brati ali vanjo pisati. Nazadnje, vrednost `never` onemogoča samodejni zagon seje. +Možnost `autoStart` nadzoruje, kdaj naj se zažene seja. Vrednost `always` pomeni, da se seja zažene vedno ob zagonu aplikacije. Vrednost `smart` pomeni, da se seja zažene ob zagonu aplikacije samo takrat, ko že obstaja, ali v trenutku, ko želimo iz nje brati ali vanjo pisati. In končno vrednost `never` prepoveduje samodejni zagon seje. -Nastavite lahko tudi vse [direktive seje |https://www.php.net/manual/en/session.configuration.php] PHP (v obliki camelCase) in tudi [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. Primer: +Nadalje je mogoče nastavljati vse PHP [direktive seje |https://www.php.net/manual/en/session.configuration.php] (v formatu camelCase) in tudi [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. Primer: ```neon session: - # 'session.name', zapisano kot 'name' + # 'session.name' zapišemo kot 'name' name: MYID - # "session.save_path" zapisano kot "savePath + # 'session.save_path' zapišemo kot 'savePath' savePath: "%tempDir%/sessions" ``` -Piškotek seje .[#toc-session-cookie] ------------------------------------- +Piškotek seje +------------- -Sejni piškotek se pošlje z enakimi parametri kot [drugi piškotki |#HTTP cookie], vendar jih lahko spremenite: +Piškotek seje se pošilja z enakimi parametri kot [drugi piškotki |#Piškotki HTTP], vendar te lahko zanj spremenite: ```neon session: - # kateri gostitelji lahko prejmejo piškotek. - cookieDomain: 'example.com' # (niz|domena) + # domene, ki sprejemajo piškotek + cookieDomain: 'example.com' # (string|domain) - # omejitve pri dostopu do zahtevka z različnim izvorom - cookieSamesite: None # (Strict|Lax|None) privzeta vrednost je Lax + # omejitve pri dostopu iz druge domene + cookieSamesite: None # (Strict|Lax|None) privzeto je Lax ``` -Možnost `cookieSamesite` vpliva na to, ali se piškotek pošilja z [zahtevki z navzkrižnim izvorom |nette:glossary#SameSite cookie], kar zagotavlja določeno zaščito pred napadi [Cross-Site Request Forgery |nette:glossary#cross-site-request-forgery-csrf]. +Atribut `cookieSamesite` vpliva na to, ali bo piškotek poslan pri [dostopu iz druge domene |nette:glossary#SameSite cookie], kar zagotavlja določeno zaščito pred napadi [Cross-Site Request Forgery |nette:glossary#Cross-Site Request Forgery CSRF] (CSRF). + + +Storitve DI +=========== + +Te storitve se dodajo v DI vsebnik: + +| Ime | Tip | Opis +|----------------------------------------------------- +| `http.request` | [api:Nette\Http\Request] | [zahteva HTTP| request] +| `http.response` | [api:Nette\Http\Response] | [odgovor HTTP| response] +| `session.session` | [api:Nette\Http\Session] | [upravljanje sej| sessions] diff --git a/http/sl/request.texy b/http/sl/request.texy index 6198c956f8..8c991d201f 100644 --- a/http/sl/request.texy +++ b/http/sl/request.texy @@ -2,84 +2,84 @@ Zahteva HTTP ************ .[perex] -Nette zapakira zahtevo HTTP v predmete z razumljivim API-jem, hkrati pa zagotavlja filter za čiščenje. +Nette inkapsulira HTTP zahtevo v objekte z razumljivim API-jem in hkrati zagotavlja sanacijski filter. -Zahteva HTTP je objekt [api:Nette\Http\Request], ki ga dobite tako, da ga posredujete z uporabo [vbrizgavanja odvisnosti |dependency-injection:passing-dependencies]. V predvajalnikih preprosto pokličite `$httpRequest = $this->getHttpRequest()`. +HTTP zahtevo predstavlja objekt [api:Nette\Http\Request]. Če delate z Nette, ta objekt samodejno ustvari ogrodje in si ga lahko pustite predati s pomočjo [dependency injection |dependency-injection:passing-dependencies]. V presenterjih je dovolj le poklicati metodo `$this->getHttpRequest()`. Če delate izven Nette Frameworka, si lahko ustvarite objekt s pomočjo [#RequestFactory]. -Pomembno je, da Nette pri [ustvarjanju |#RequestFactory] tega objekta očisti vse vhodne parametre GET, POST in COOKIE ter naslove URL kontrolnih znakov in neveljavnih zaporedij UTF-8. Tako lahko varno nadaljujete delo s podatki. Očiščeni podatki se nato uporabljajo v predstavitvenih programih in obrazcih. +Velika prednost Nette je, da pri ustvarjanju objekta samodejno očisti vse vhodne parametre GET, POST, COOKIE in tudi URL kontrolnih znakov in neveljavnih UTF-8 sekvenc. S temi podatki lahko nato varno nadalje delate. Očiščeni podatki se nato uporabljajo v presenterjih in obrazcih. -→ [Namestitev in zahteve |@home#Installation] +→ [Namestitev in zahteve |@home#Namestitev] -Nette\Http\Praševanje .[#toc-nette-http-request] -================================================ +Nette\Http\Request +================== -Ta predmet je nespremenljiv. Nima nastavljalcev, ima le en tako imenovani wither `withUrl()`, ki ne spremeni objekta, temveč vrne novo instanco s spremenjeno vrednostjo. +Ta objekt je nespremenljiv (immutable). Nima nobenih setterjev, ima le en t.i. wither `withUrl()`, ki objekta ne spreminja, ampak vrača novo instanco s spremenjeno vrednostjo. withUrl(Nette\Http\UrlScript $url): Nette\Http\Request .[method] ---------------------------------------------------------------- -Vrne klon z drugačnim naslovom URL. +Vrača klon z drugim URL-jem. getUrl(): Nette\Http\UrlScript .[method] ---------------------------------------- -Vrne URL zahteve kot predmet [UrlScript |urls#UrlScript]. +Vrača URL zahteve kot objekt [UrlScript |urls#UrlScript]. ```php $url = $httpRequest->getUrl(); -echo $url; // https://nette.org/en/documentation?action=edit +echo $url; // https://doc.nette.org/cs/?action=edit echo $url->getHost(); // nette.org ``` -Brskalniki strežniku ne pošljejo fragmenta, zato `$url->getFragment()` vrne prazen niz. +Opozorilo: brskalniki ne pošiljajo fragmenta na strežnik, zato bo `$url->getFragment()` vračal prazen niz. -getQuery(string $key=null): string|array|null .[method] -------------------------------------------------------- -Vrne parametre zahteve GET: +getQuery(?string $key=null): string|array|null .[method] +-------------------------------------------------------- +Vrača parametre GET zahteve. ```php -$all = $httpRequest->getQuery(); // polje vseh parametrov URL -$id = $httpRequest->getQuery('id'); // vrne parameter GET 'id' (ali null) +$all = $httpRequest->getQuery(); // vrača polje vseh parametrov iz URL-ja +$id = $httpRequest->getQuery('id'); // vrača GET parameter 'id' (ali null) ``` -getPost(string $key=null): string|array|null .[method] ------------------------------------------------------- -Vrne parametre zahteve POST: +getPost(?string $key=null): string|array|null .[method] +------------------------------------------------------- +Vrača parametre POST zahteve. ```php -$all = $httpRequest->getPost(); // polje vseh parametrov POST -$id = $httpRequest->getPost('id'); // vrne parameter POST 'id' (ali null) +$all = $httpRequest->getPost(); // vrača polje vseh parametrov iz POST-a +$id = $httpRequest->getPost('id'); // vrača POST parameter 'id' (ali null) ``` getFile(string|string[] $key): Nette\Http\FileUpload|array|null .[method] ------------------------------------------------------------------------- -Vrne [prenos |#Uploaded Files] kot predmet [api:Nette\Http\FileUpload]: +Vrača [naloženo datoteko |#Naložene datoteke] kot objekt [api:Nette\Http\FileUpload]: ```php $file = $httpRequest->getFile('avatar'); -if ($file->hasFile()) { // je bila naložena katera koli datoteka? - $file->getUntrustedName(); // ime datoteke, ki jo je poslal uporabnik +if ($file?->hasFile()) { // je bila kakšna datoteka naložena? + $file->getUntrustedName(); // ime datoteke, ki ga je poslal uporabnik $file->getSanitizedName(); // ime brez nevarnih znakov } ``` -Navedite polje ključev za dostop do strukture poddrevesa. +Za dostop do ugnezdene strukture navedite polje ključev. ```php //<input type="file" name="my-form[details][avatar]" multiple> $file = $request->getFile(['my-form', 'details', 'avatar']); ``` -Ker ne morete zaupati podatkom od zunaj in se zato ne zanašate na obliko strukture, je ta metoda varnejša kot `$request->getFiles()['my-form']['details']['avatar']`ki je lahko neuspešna. +Ker ni mogoče zaupati podatkom od zunaj in se torej tudi ne zanašati na obliko strukture datotek, je ta način varnejši kot na primer `$request->getFiles()['my-form']['details']['avatar']`, ki lahko odpove. getFiles(): array .[method] --------------------------- -Vrne drevo [datotek za prenos v |#Uploaded Files] normalizirani strukturi, pri čemer je vsak list primerek [api:Nette\Http\FileUpload]: +Vrne drevo [vseh naloženih datotek |#Naložene datoteke] v normalizirani strukturi, katere listi so objekti [api:Nette\Http\FileUpload]: ```php $files = $httpRequest->getFiles(); @@ -88,7 +88,7 @@ $files = $httpRequest->getFiles(); getCookie(string $key): string|array|null .[method] --------------------------------------------------- -Vrne piškotek ali `null`, če ne obstaja. +Vrača piškotek ali `null`, če ne obstaja. ```php $sessId = $httpRequest->getCookie('sess_id'); @@ -97,7 +97,7 @@ $sessId = $httpRequest->getCookie('sess_id'); getCookies(): array .[method] ----------------------------- -Vrne vse piškotke: +Vrača vse piškotke. ```php $cookies = $httpRequest->getCookies(); @@ -106,16 +106,16 @@ $cookies = $httpRequest->getCookies(); getMethod(): string .[method] ----------------------------- -Vrne metodo HTTP, s katero je bila zahteva izvedena. +Vrača HTTP metodo, s katero je bila narejena zahteva. ```php -echo $httpRequest->getMethod(); // GET, POST, HEAD, PUT +$httpRequest->getMethod(); // GET, POST, HEAD, PUT ``` isMethod(string $method): bool .[method] ---------------------------------------- -Preveri metodo HTTP, s katero je bila izvedena zahteva. Pri parametru se ne razlikujejo velike in male črke. +Testira HTTP metodo, s katero je bila narejena zahteva. Parameter je neobčutljiv na velikost črk. ```php if ($httpRequest->isMethod('GET')) // ... @@ -124,7 +124,7 @@ if ($httpRequest->isMethod('GET')) // ... getHeader(string $header): ?string .[method] -------------------------------------------- -Vrne glavo HTTP ali `null`, če ta ne obstaja. Pri parametru se ne razlikujejo velike in male črke: +Vrača HTTP glavo ali `null`, če ne obstaja. Parameter je neobčutljiv na velikost črk. ```php $userAgent = $httpRequest->getHeader('User-Agent'); @@ -133,7 +133,7 @@ $userAgent = $httpRequest->getHeader('User-Agent'); getHeaders(): array .[method] ----------------------------- -Vrne vse glave HTTP kot asociativno polje: +Vrača vse HTTP glave kot asociativno polje. ```php $headers = $httpRequest->getHeaders(); @@ -141,39 +141,34 @@ echo $headers['Content-Type']; ``` -getReferer(): ?Nette\Http\UrlImmutable .[method] ------------------------------------------------- -S katerega naslova URL je prišel uporabnik? Pazite, ni zanesljivo. - - isSecured(): bool .[method] --------------------------- -Ali je povezava šifrirana (HTTPS)? Za pravilno delovanje boste morda morali nastaviti [posrednika |configuration#HTTP proxy]. +Je povezava šifrirana (HTTPS)? Za pravilno delovanje je morda treba [nastaviti proxy |configuration#HTTP proxy]. isSameSite(): bool .[method] ---------------------------- -Ali zahteva prihaja iz iste (pod)domene in se sproži s klikom na povezavo? Nette to zazna s piškotkom `_nss` (prej `nette-samesite`). +Ali zahteva prihaja iz iste (pod)domene in je sprožena s klikom na povezavo? Nette za zaznavanje uporablja piškotek `_nss` (prej `nette-samesite`). isAjax(): bool .[method] ------------------------ -Ali gre za zahtevo AJAX? +Gre za AJAX zahtevo? getRemoteAddress(): ?string .[method] ------------------------------------- -Vrne uporabnikov naslov IP. Za pravilno delovanje boste morda morali nastaviti [proxy strežnik |configuration#HTTP proxy]. +Vrača IP naslov uporabnika. Za pravilno delovanje je morda treba [nastaviti proxy |configuration#HTTP proxy]. getRemoteHost(): ?string .[method deprecated] --------------------------------------------- -Vrne prevod DNS uporabnikovega naslova IP. Za pravilno delovanje boste morda morali nastaviti [posrednika |configuration#HTTP proxy]. +Vrača DNS prevod IP naslova uporabnika. Za pravilno delovanje je morda treba [nastaviti proxy |configuration#HTTP proxy]. -getBasicCredentials(): ?string .[method] ----------------------------------------- -Vrne [osnovne |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication] poverilnice za [avtentikacijo HTTP |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication]. +getBasicCredentials(): ?array .[method] +--------------------------------------- +Vrača podatke za preverjanje pristnosti za [Basic HTTP authentication |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication]. ```php [$user, $password] = $httpRequest->getBasicCredentials(); @@ -182,7 +177,7 @@ Vrne [osnovne |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication] getRawBody(): ?string .[method] ------------------------------- -Vrne telo zahteve HTTP: +Vrača telo HTTP zahteve. ```php $body = $httpRequest->getRawBody(); @@ -191,54 +186,61 @@ $body = $httpRequest->getRawBody(); detectLanguage(array $langs): ?string .[method] ----------------------------------------------- -zazna jezik. Kot parameter `$lang` posredujemo polje jezikov, ki jih aplikacija podpira, in vrne tistega, ki ga ima brskalnik najraje. Ne gre za čarovnijo, metoda samo uporabi glavo `Accept-Language`. Če ni nobenega ujemanja, vrne `null`. +Zazna jezik. Kot parameter `$lang` predamo polje z jeziki, ki jih aplikacija podpira, in ona vrne tistega, ki bi ga brskalnik obiskovalca najraje videl. To niso nobene čarovnije, le uporablja se glava `Accept-Language`. Če ne pride do nobenega ujemanja, vrača `null`. ```php -// Naslovnica, ki jo pošlje brskalnik: Jezik: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3 +// brskalnik pošilja npr. Accept-Language: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3 -$langs = ['hu', 'pl', 'en']; // jeziki, podprti v aplikaciji +$langs = ['hu', 'pl', 'en']; // jeziki, ki jih podpira aplikacija echo $httpRequest->detectLanguage($langs); // en ``` -RequestFactory .[#toc-requestfactory] -===================================== +RequestFactory +============== -Predmet trenutne zahteve HTTP ustvari [api:Nette\Http\RequestFactory]. Če pišete aplikacijo, ki ne uporablja vsebnika DI, ustvarite zahtevo na naslednji način: +Razred [api:Nette\Http\RequestFactory] služi za ustvarjanje instance `Nette\Http\Request`, ki predstavlja trenutno HTTP zahtevo. (Če delate z Nette, objekt HTTP zahteve samodejno ustvari ogrodje.) ```php $factory = new Nette\Http\RequestFactory; $httpRequest = $factory->fromGlobals(); ``` -RequestFactory lahko konfigurirate pred klicem `fromGlobals()`. Z uporabo spletne strani `$factory->setBinary()` lahko onemogočimo vso sanitizacijo vhodnih parametrov iz neveljavnih zaporedij UTF-8. Prav tako lahko nastavimo strežnik proxy, kar je pomembno za pravilno zaznavanje uporabnikovega naslova IP z uporabo `$factory->setProxy(...)`. +Metoda `fromGlobals()` ustvari objekt zahteve na podlagi trenutnih globalnih spremenljivk PHP (`$_GET`, `$_POST`, `$_COOKIE`, `$_FILES` in `$_SERVER`). Pri ustvarjanju objekta samodejno očisti vse vhodne parametre GET, POST, COOKIE in tudi URL kontrolnih znakov in neveljavnih UTF-8 sekvenc, kar zagotavlja varnost pri nadaljnjem delu s temi podatki. + +RequestFactory lahko pred klicem `fromGlobals()` konfigurirate: + +- z metodo `$factory->setBinary()` izklopite samodejno čiščenje vhodnih parametrov kontrolnih znakov in neveljavnih UTF-8 sekvenc. +- z metodo `$factory->setProxy(...)` navedete IP naslov [proxy strežniku |configuration#HTTP proxy], kar je nujno za pravilno zaznavanje IP naslova uporabnika. -Z uporabo filtrov je mogoče naslove URL očistiti znakov, ki lahko vanje pridejo zaradi slabo izvedenih sistemov komentarjev na različnih drugih spletnih mestih: +RequestFactory omogoča definiranje filtrov, ki samodejno transformirajo dele URL zahteve. Ti filtri odstranjujejo nezaželene znake iz URL-ja, ki so tja lahko vstavljeni na primer z nepravilno implementacijo sistemov za komentarje na različnih spletnih mestih: ```php -// odstranite presledke s poti +// odstranitev presledkov iz poti $requestFactory->urlFilters['path']['%20'] = ''; -// odstranite piko, vejico ali desni oklepaj na koncu naslova URL. +// odstranitev pike, vejice ali desnega oklepaja s konca URI $requestFactory->urlFilters['url']['[.,)]$'] = ''; -// očisti pot podvojenih poševnic (privzeti filter) +// čiščenje poti od podvojenih poševnic (privzeti filter) $requestFactory->urlFilters['path']['/{2,}'] = '/'; ``` +Prvi ključ `'path'` ali `'url'` določa, na kateri del URL-ja se filter uporabi. Drugi ključ je regularni izraz, ki se najde, in vrednost je nadomestilo, ki se uporabi namesto najdenega besedila. -Naložene datoteke .[#toc-uploaded-files] -======================================== -Metoda `Nette\Http\Request::getFiles()` vrne drevo naloženih datotek v normalizirani strukturi, pri čemer je vsak list primerek objekta [api:Nette\Http\FileUpload]. Ti objekti vsebujejo podatke, ki jih je predložil `<input type=file>` element obrazca. +Naložene datoteke +================= -Struktura odraža poimenovanje elementov v jeziku HTML. V najpreprostejšem primeru je to lahko en poimenovan element obrazca, ki se odda kot: +Metoda `Nette\Http\Request::getFiles()` vrača polje vseh naloženih datotek v normalizirani strukturi, katere listi so objekti [api:Nette\Http\FileUpload]. Ti inkapsulirajo podatke, poslane z elementom obrazca `<input type=file>`. + +Struktura odraža poimenovanje elementov v HTML. V najpreprostejšem primeru je to lahko en sam poimenovan element obrazca, poslan kot: ```latte <input type="file" name="avatar"> ``` -V tem primeru `$request->getFiles()` vrne polje: +V tem primeru `$request->getFiles()` vrača polje: ```php [ @@ -246,19 +248,19 @@ V tem primeru `$request->getFiles()` vrne polje: ] ``` -Objekt `FileUpload` se ustvari, tudi če uporabnik ni naložil nobene datoteke ali če nalaganje ni bilo uspešno. Metoda `hasFile()` vrne true, če je bila datoteka poslana: +Objekt `FileUpload` se ustvari tudi v primeru, da uporabnik ni poslal nobene datoteke ali je pošiljanje spodletelo. Ali je bila datoteka poslana, vrača metoda `hasFile()`: ```php -$request->getFile('avatar')->hasFile(); +$request->getFile('avatar')?->hasFile(); ``` -V primeru vnosa, ki za ime uporablja zapis v obliki polja: +V primeru imena elementa, ki uporablja notacijo za polja: ```latte <input type="file" name="my-form[details][avatar]"> ``` -vrnjeno drevo je videti takole: +izgleda vrnjeno drevo takole: ```php [ @@ -270,13 +272,13 @@ vrnjeno drevo je videti takole: ] ``` -Ustvarite lahko tudi polja datotek: +Lahko ustvarite tudi polje datotek: ```latte -<input type="file" name="my-form[details][avatars][] multiple"> +<input type="file" name="my-form[details][avatars][]" multiple> ``` -V takem primeru je struktura videti kot: +V takem primeru izgleda struktura takole: ```php [ @@ -292,7 +294,7 @@ V takem primeru je struktura videti kot: ] ``` -Najboljši način za dostop do indeksa 1 ugnezdenega polja je naslednji: +Dostop do indeksa 1 ugnezdenega polja je najbolje izvesti tako: ```php $file = $request->getFile(['my-form', 'details', 'avatars', 1]); @@ -301,7 +303,7 @@ if ($file instanceof FileUpload) { } ``` -Ker ne morete zaupati podatkom od zunaj in se zato ne zanašate na obliko strukture, je ta metoda varnejša od `$request->getFiles()['my-form']['details']['avatars'][1]`ki je lahko neuspešna. +Ker ni mogoče zaupati podatkom od zunaj in se torej tudi ne zanašati na obliko strukture datotek, je ta način varnejši kot na primer `$request->getFiles()['my-form']['details']['avatars'][1]`, ki lahko odpove. Pregled metod `FileUpload` .{toc: FileUpload} @@ -310,22 +312,22 @@ Pregled metod `FileUpload` .{toc: FileUpload} hasFile(): bool .[method] ------------------------- -Vrne `true`, če je uporabnik naložil datoteko. +Vrača `true`, če je uporabnik naložil kakšno datoteko. isOk(): bool .[method] ---------------------- -Vrne `true`, če je bila datoteka uspešno naložena. +Vrača `true`, če je bila datoteka uspešno naložena. getError(): int .[method] ------------------------- -Vrne kodo napake, povezano z naloženo datoteko. To je ena od konstant [UPLOAD_ERR_XXX |http://php.net/manual/en/features.file-upload.errors.php]. Če je bila datoteka uspešno naložena, se vrne `UPLOAD_ERR_OK`. +Vrača kodo napake pri nalaganju datoteke. Gre za eno od konstant [UPLOAD_ERR_XXX|http://php.net/manual/en/features.file-upload.errors.php]. V primeru, da je nalaganje potekalo v redu, vrača `UPLOAD_ERR_OK`. move(string $dest) .[method] ---------------------------- -Premakne naloženo datoteko na novo lokacijo. Če ciljna datoteka že obstaja, se prepiše. +Premakne naloženo datoteko na novo lokacijo. Če ciljna datoteka že obstaja, bo prepisana. ```php $file->move('/path/to/files/name.ext'); @@ -334,61 +336,72 @@ $file->move('/path/to/files/name.ext'); getContents(): ?string .[method] -------------------------------- -Vrne vsebino naložene datoteke. Če nalaganje ni bilo uspešno, vrne `null`. +Vrača vsebino naložene datoteke. V primeru, da nalaganje ni bilo uspešno, vrača `null`. getContentType(): ?string .[method] ----------------------------------- -Določi vrsto vsebine MIME naložene datoteke na podlagi njenega podpisa. Če nalaganje ni bilo uspešno ali pa je bilo zaznavanje neuspešno, vrne `null`. +Zazna MIME content type naložene datoteke na podlagi njene signature. V primeru, da nalaganje ni bilo uspešno ali zaznavanje ni uspelo, vrača `null`. .[caution] -Zahteva razširitev PHP `fileinfo`. +Zahteva PHP razširitev `fileinfo`. getUntrustedName(): string .[method] ------------------------------------ -Vrne izvirno ime datoteke, kot ga je posredoval brskalnik. +Vrača originalno ime datoteke, kot ga je poslal brskalnik. .[caution] -Ne zaupajte vrednosti, ki jo vrne ta metoda. Odjemalec lahko pošlje zlonamerno ime datoteke z namenom, da bi poškodoval ali vdrl v vašo aplikacijo. +Ne zaupajte vrednosti, ki jo vrne ta metoda. Odjemalec je lahko poslal škodljivo ime datoteke z namenom poškodovati ali vdreti v vašo aplikacijo. getSanitizedName(): string .[method] ------------------------------------ -Vrne prečiščeno ime datoteke. Vsebuje samo znake ASCII `[a-zA-Z0-9.-]`. Če ime ne vsebuje takih znakov, vrne 'unknown'. Če je datoteka slika JPEG, PNG, GIF ali WebP, vrne pravilno končnico datoteke. +Vrača sanirano ime datoteke. Vsebuje samo ASCII znake `[a-zA-Z0-9.-]`. Če ime takih znakov ne vsebuje, vrne `'unknown'`. Če je datoteka slika v formatu JPEG, PNG, GIF, WebP ali AVIF, vrne tudi pravilno končnico. + +.[caution] +Zahteva PHP razširitev `fileinfo`. + + +getSuggestedExtension(): ?string .[method]{data-version:3.2.4} +-------------------------------------------------------------- +Vrača primerno končnico datoteke (brez pike), ki ustreza zaznanemu MIME tipu. + +.[caution] +Zahteva PHP razširitev `fileinfo`. getUntrustedFullPath(): string .[method] ---------------------------------------- -Vrne izvirno celotno pot, kot jo je posredoval brskalnik med nalaganjem imenika. Polna pot je na voljo samo v PHP 8.1 in novejših različicah. V prejšnjih različicah ta metoda vrne ime nezaupljive datoteke. +Vrača originalno pot do datoteke, kot jo je poslal brskalnik pri nalaganju mape. Celotna pot je na voljo samo v PHP 8.1 in višjih. V prejšnjih različicah ta metoda vrača originalno ime datoteke. .[caution] -Ne zaupajte vrednosti, ki jo vrne ta metoda. Odjemalec lahko pošlje zlonamerno ime datoteke z namenom, da bi poškodoval ali vdrl v vašo aplikacijo. +Ne zaupajte vrednosti, ki jo vrne ta metoda. Odjemalec je lahko poslal škodljivo ime datoteke z namenom poškodovati ali vdreti v vašo aplikacijo. getSize(): int .[method] ------------------------ -Vrne velikost naložene datoteke. Če nalaganje ni bilo uspešno, vrne `0`. +Vrača velikost naložene datoteke. V primeru, da nalaganje ni bilo uspešno, vrača `0`. getTemporaryFile(): string .[method] ------------------------------------ -Vrne pot do začasne lokacije naložene datoteke. Če nalaganje ni bilo uspešno, vrne `''`. +Vrača pot do začasne lokacije naložene datoteke. V primeru, da nalaganje ni bilo uspešno, vrača `''`. isImage(): bool .[method] ------------------------- -Vrne `true`, če je naložena datoteka slika JPEG, PNG, GIF ali WebP. Odkrivanje temelji na njenem podpisu. Celovitost celotne datoteke se ne preverja. Ali slika ni poškodovana, lahko ugotovite na primer tako, da jo poskusite [naložiti |#toImage]. +Vrača `true`, če je naložena datoteka slika v formatu JPEG, PNG, GIF, WebP ali AVIF. Zaznavanje poteka na podlagi njene signature in se ne preverja integriteta celotne datoteke. Ali slika ni poškodovana, lahko ugotovite na primer s poskusom njenega [nalaganjem |#toImage]. .[caution] -Zahteva razširitev PHP `fileinfo`. +Zahteva PHP razširitev `fileinfo`. getImageSize(): ?array .[method] -------------------------------- -Vrne par `[width, height]` z merami naložene slike. Če nalaganje ni bilo uspešno ali slika ni veljavna, vrne `null`. +Vrača par `[širina, višina]` z dimenzijami naložene slike. V primeru, da nalaganje ni bilo uspešno ali ne gre za veljavno sliko, vrača `null`. toImage(): Nette\Utils\Image .[method] -------------------------------------- -Naloži sliko kot objekt [Image |utils:images]. Če nalaganje ni bilo uspešno ali slika ni veljavna, se vrže izjema `Nette\Utils\ImageException`. +Naloži sliko kot objekt [Image|utils:images]. V primeru, da nalaganje ni bilo uspešno ali ne gre za veljavno sliko, vrže izjemo `Nette\Utils\ImageException`. diff --git a/http/sl/response.texy b/http/sl/response.texy index 53c9ba207e..e93fd0e234 100644 --- a/http/sl/response.texy +++ b/http/sl/response.texy @@ -1,23 +1,23 @@ -Odziv HTTP -********** +Odgovor HTTP +************ .[perex] -Nette zapakira odziv HTTP v predmete z razumljivim API-jem, hkrati pa zagotavlja filter za čiščenje. +Nette inkapsulira HTTP odgovor v objekte z razumljivim API-jem. -Odziv HTTP je objekt [api:Nette\Http\Response], ki ga dobite tako, da ga posredujete z uporabo [vbrizgavanja odvisnosti |dependency-injection:passing-dependencies]. V predstavitvah preprosto pokličite `$httpResponse = $this->getHttpResponse()`. +HTTP odgovor predstavlja objekt [api:Nette\Http\Response]. Če delate z Nette, ta objekt samodejno ustvari ogrodje in si ga lahko pustite predati s pomočjo [dependency injection |dependency-injection:passing-dependencies]. V presenterjih je dovolj le poklicati metodo `$this->getHttpResponse()`. -→ [Namestitev in zahteve |@home#Installation] +→ [Namestitev in zahteve |@home#Namestitev] -Nette\Http\Response .[#toc-nette-http-response] -=============================================== +Nette\Http\Response +=================== -Za razliko od [Nette\Http\Request |request] je ta objekt spremenljiv, zato lahko za spremembo stanja, tj. za pošiljanje glave, uporabite nastavitve. Ne pozabite, da je treba vse nastavljalnike **poklicati, preden se pošlje dejanski izhod.** Metoda `isSent()` pove, ali je bil izhod poslan. Če vrne `true`, vsak poskus pošiljanja glave vrže izjemo `Nette\InvalidStateException`. +Objekt je za razliko od [Nette\Http\Request|request] spremenljiv (mutable), torej s pomočjo nastavitvenih metod lahko spreminjate stanje, torej npr. pošiljate glave. Ne pozabite, da morajo biti vse nastavitvene metode poklicane **pred pošiljanjem kakršnega koli izpisa.** Ali je bil izpis že poslan, pove metoda `isSent()`. Če vrača `true`, vsak poskus pošiljanja glave sproži izjemo `Nette\InvalidStateException`. -setCode(int $code, string $reason=null) .[method] -------------------------------------------------- -Spremeni [odzivno kodo |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10] stanja. Za boljšo berljivost izvorne kode je priporočljivo uporabljati [vnaprej določene konstante |api:Nette\Http\IResponse] namesto dejanskih številk. +setCode(int $code, ?string $reason=null) .[method] +-------------------------------------------------- +Spremeni [statusno kodo odgovora |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10]. Zaradi boljše razumljivosti izvorne kode priporočamo, da za kodo namesto številk uporabljate [preddefinirane konstante |api:Nette\Http\IResponse]. ```php $httpResponse->setCode(Nette\Http\Response::S404_NotFound); @@ -26,17 +26,17 @@ $httpResponse->setCode(Nette\Http\Response::S404_NotFound); getCode(): int .[method] ------------------------ -Vrne kodo stanja odgovora. +Vrača statusno kodo odgovora. isSent(): bool .[method] ------------------------ -Vrne, ali so bile glave že poslane iz strežnika v brskalnik, zato ni več mogoče pošiljati glav ali spreminjati kode stanja. +Vrača, ali so bile glave že poslane s strežnika v brskalnik, in torej ni več mogoče pošiljati glav ali spreminjati statusne kode. setHeader(string $name, string $value) .[method] ------------------------------------------------ -Pošlje glavo HTTP in **prepiše** prej poslano glavo z istim imenom. +Pošlje HTTP glavo in **prepiše** prej poslano glavo istega imena. ```php $httpResponse->setHeader('Pragma', 'no-cache'); @@ -45,7 +45,7 @@ $httpResponse->setHeader('Pragma', 'no-cache'); addHeader(string $name, string $value) .[method] ------------------------------------------------ -Pošlje glavo HTTP in **ne prepiše** prej poslane glave z istim imenom. +Pošlje HTTP glavo in **ne prepiše** prej poslane glave istega imena. ```php $httpResponse->addHeader('Accept', 'application/json'); @@ -55,12 +55,12 @@ $httpResponse->addHeader('Accept', 'application/xml'); deleteHeader(string $name) .[method] ------------------------------------ -Izbriše predhodno poslano glavo HTTP. +Izbriše prej poslano HTTP glavo. getHeader(string $header): ?string .[method] -------------------------------------------- -Vrne poslano glavo HTTP ali `null`, če ne obstaja. Pri parametru se ne razlikujejo velike in male črke. +Vrača poslano HTTP glavo ali `null`, če takšna ne obstaja. Parameter je neobčutljiv na velikost črk. ```php $pragma = $httpResponse->getHeader('Pragma'); @@ -69,7 +69,7 @@ $pragma = $httpResponse->getHeader('Pragma'); getHeaders(): array .[method] ----------------------------- -Vrne vse poslane glave HTTP kot asociativno polje. +Vrača vse poslane HTTP glave kot asociativno polje. ```php $headers = $httpResponse->getHeaders(); @@ -77,9 +77,9 @@ echo $headers['Pragma']; ``` -setContentType(string $type, string $charset=null) .[method] ------------------------------------------------------------- -Pošlje glavo `Content-Type`. +setContentType(string $type, ?string $charset=null) .[method] +------------------------------------------------------------- +Spremeni glavo `Content-Type`. ```php $httpResponse->setContentType('text/plain', 'UTF-8'); @@ -88,7 +88,7 @@ $httpResponse->setContentType('text/plain', 'UTF-8'); redirect(string $url, int $code=self::S302_Found): void .[method] ----------------------------------------------------------------- -preusmeri na drug naslov URL. Nato ne pozabite zaključiti skripte. +Preusmeri na drug URL. Ne pozabite nato končati skripta. ```php $httpResponse->redirect('http://example.com'); @@ -98,52 +98,52 @@ exit; setExpiration(?string $time) .[method] -------------------------------------- -Določi potek veljavnosti dokumenta HTTP z uporabo glave `Cache-Control` in `Expires`. Parameter je bodisi časovni interval (kot besedilo) bodisi `null`, ki onemogoči predpomnjenje. +Nastavi potek HTTP dokumenta s pomočjo glav `Cache-Control` in `Expires`. Parameter je bodisi časovni interval (kot besedilo) ali `null`, kar onemogoči predpomnjenje. ```php -// predpomnilnik brskalnika poteče čez eno uro +// predpomnilnik v brskalniku poteče čez eno uro $httpResponse->setExpiration('1 hour'); ``` sendAsFile(string $fileName) .[method] -------------------------------------- -Odgovor je treba prenesti s pogovornim oknom *Shrani kot* z določenim imenom. Sam ne pošilja nobene datoteke v izhod. +Odgovor bo prenesen s pomočjo pogovornega okna *Shrani kot* pod navedenim imenom. Same datoteke pri tem ne pošilja. ```php -$httpResponse->sendAsFile('invoice.pdf'); +$httpResponse->sendAsFile('faktura.pdf'); ``` -setCookie(string $name, string $value, $time, string $path=null, string $domain=null, bool $secure=null, bool $httpOnly=null, string $sameSite=null) .[method] --------------------------------------------------------------------------------------------------------------------------------------------------------------- +setCookie(string $name, string $value, $time, ?string $path=null, ?string $domain=null, ?bool $secure=null, ?bool $httpOnly=null, ?string $sameSite=null) .[method] +------------------------------------------------------------------------------------------------------------------------------------------------------------------- Pošlje piškotek. Privzete vrednosti parametrov: -| `$path` | `'/'` | s področjem uporabe za vse poti v (pod)domeni *(nastavljivo)* -| `$domain` | `null` | s področjem uporabe trenutne (pod)domene, vendar ne njenih poddomen *(nastavljivo)* -| `$secure` | `true` | če spletno mesto deluje v HTTPS, sicer `false` *(nastavljivo)* -| `$httpOnly` | `true` | piškotek ni dostopen za JavaScript -| `$sameSite` | `'Lax'` | piškotka ni treba poslati, če do njega [dostopate iz drugega izvora |nette:glossary#SameSite cookie] +| `$path` | `'/'` | piškotek ima doseg na vse poti v (pod)domeni *(nastavljivo)* +| `$domain` | `null` | kar pomeni z dosegom na trenutno (pod)domeno, vendar ne njenih poddomen *(nastavljivo)* +| `$secure` | `true` | če spletno mesto teče na HTTPS, sicer `false` *(nastavljivo)* +| `$httpOnly` | `true` | piškotek je za JavaScript nedostopen +| `$sameSite` | `'Lax'` | piškotek ni nujno poslan pri [dostopu iz druge domene |nette:glossary#SameSite cookie] -Privzete vrednosti parametrov `$path`, `$domain` in `$secure` lahko spremenite v [konfiguraciji |configuration#HTTP cookie]. +Privzete vrednosti parametrov `$path`, `$domain` in `$secure` lahko spremenite v [konfiguraciji |configuration#Piškotki HTTP]. -Čas lahko določite kot število sekund ali niz: +Čas lahko navajate kot število sekund ali niz: ```php -$httpResponse->setCookie('lang', 'en', '100 days'); +$httpResponse->setCookie('lang', 'cs', '100 days'); ``` -Možnost `$domain` določa, katere domene (izvori) lahko sprejmejo piškotke. Če ni določena, piškotek sprejme ista (pod)domena, kot je določena z njo, razen njihovih poddomen. Če je določena možnost `$domain`, so vključene tudi poddomene. Zato je navedba `$domain` manj omejujoča kot opustitev. Če je na primer `$domain = 'nette.org'`, je piškotek na voljo tudi na vseh poddomenah, kot je `doc.nette.org`. +Parameter `$domain` določa, katere domene lahko sprejemajo piškotek. Če ni naveden, piškotek sprejema ista (pod)domena, kot ga je nastavila, vendar ne njenih poddomen. Če je `$domain` določen, so vključene tudi poddomene. Zato je navedba `$domain` manj omejujoča kot izpustitev. Na primer, pri `$domain = 'nette.org'` so piškotki dostopni tudi na vseh poddomenah kot `doc.nette.org`. Za vrednost `$sameSite` lahko uporabite konstante `Response::SameSiteLax`, `SameSiteStrict` in `SameSiteNone`. -deleteCookie(string $name, string $path=null, string $domain=null, bool $secure=null): void .[method] ------------------------------------------------------------------------------------------------------ +deleteCookie(string $name, ?string $path=null, ?string $domain=null, ?bool $secure=null): void .[method] +-------------------------------------------------------------------------------------------------------- Izbriše piškotek. Privzete vrednosti parametrov so: -- `$path` s področjem uporabe za vse imenike (`'/'`) -- `$domain` s področjem uporabe trenutne (pod)domene, vendar ne njenih poddomen -- Na `$secure` vplivajo nastavitve v [konfiguraciji#HTTP piškotek |configuration#HTTP cookie] +- `$path` z dosegom na vse imenike (`'/'`) +- `$domain` z dosegom na trenutno (pod)domeno, vendar ne njenih poddomen +- `$secure` se ravna po nastavitvah v [konfiguraciji |configuration#Piškotki HTTP] ```php $httpResponse->deleteCookie('lang'); diff --git a/http/sl/sessions.texy b/http/sl/sessions.texy index 5ed29e881a..dd1bec2462 100644 --- a/http/sl/sessions.texy +++ b/http/sl/sessions.texy @@ -3,69 +3,69 @@ Seje <div class=perex> -HTTP je protokol brez stanja, vendar mora skoraj vsaka aplikacija ohranjati stanje med zahtevami, npr. vsebino nakupovalne košarice. Za to se uporablja seja. Oglejmo si +HTTP je brezstanje protokol, vendar skoraj vsaka aplikacija potrebuje ohranjati stanje med zahtevami, na primer vsebino nakupovalne košarice. Prav temu služijo seje ali relacije. Pokazali si bomo, - kako uporabljati seje -- kako se izogniti navzkrižju imen -- kako nastaviti potek veljavnosti +- kako preprečiti konflikte imen +- kako nastaviti potek </div> -Pri uporabi sej vsak uporabnik prejme edinstven identifikator, imenovan ID seje, ki se posreduje v piškotku. Ta služi kot ključ do podatkov seje. Za razliko od piškotkov, ki so shranjeni na strani brskalnika, so podatki o seji shranjeni na strani strežnika. +Pri uporabi sej vsak uporabnik prejme edinstven identifikator, imenovan ID seje, ki se prenaša v piškotku. Ta služi kot ključ do podatkov seje. Za razliko od piškotkov, ki se shranjujejo na strani brskalnika, se podatki v seji shranjujejo na strani strežnika. -Sejo konfiguriramo v [konfiguraciji |configuration#session], pri čemer je pomembna izbira časa izteka veljavnosti. +Sejo nastavljamo v [konfiguraciji |configuration#Seja], pomembna je zlasti izbira časa poteka. -Sejo upravlja objekt [api:Nette\Http\Session], ki ga dobimo s posredovanjem z uporabo [vbrizgavanja odvisnosti |dependency-injection:passing-dependencies]. V predstavitvah preprosto pokličemo `$session = $this->getSession()`. +Upravljanje sej ima na skrbi objekt [api:Nette\Http\Session], do katerega pridete tako, da si ga pustite predati s pomočjo [dependency injection |dependency-injection:passing-dependencies]. V presenterjih je dovolj le poklicati `$session = $this->getSession()`. -→ [Namestitev in zahteve |@home#Installation] +→ [Namestitev in zahteve |@home#Namestitev] -Začetek seje .[#toc-starting-session] -===================================== +Zagon seje +========== -Privzeto bo Nette samodejno zagnal sejo v trenutku, ko bomo začeli brati iz nje ali vanjo pisati podatke. Če želite sejo zagnati ročno, uporabite `$session->start()`. +Nette v privzeti nastavitvi samodejno zažene sejo samodejno v trenutku, ko iz nje začnemo brati ali vanjo zapisovati podatke. Ročno se seja zažene s pomočjo `$session->start()`. -PHP ob začetku seje pošlje glave HTTP, ki vplivajo na predpomnjenje, glejte [php:session_cache_limiter], in po možnosti piškotek z ID seje. Zato je treba sejo vedno začeti, preden brskalniku pošljemo kakršen koli izpis, sicer se vrže izjema. Če torej veste, da bo seja uporabljena med prikazovanjem strani, jo pred tem ročno zaženite, na primer v predstavitvenem programu. +PHP ob zagonu seje pošlje HTTP glave, ki vplivajo na predpomnjenje, glej [php:session_cache_limiter], in po potrebi tudi piškotek z ID-jem seje. Zato je treba vedno sejo zagnati še pred pošiljanjem kakršnega koli izpisa v brskalnik, sicer pride do sprožitve izjeme. Če torej veste, da se bo med izrisovanjem strani uporabljala seja, jo zaženite ročno prej, na primer v presenterju. -V načinu za razvijalce Tracy zažene sejo, ker jo uporablja za prikaz preusmeritev in vrstic z zahtevami AJAX v vrstici Tracy. +V razvijalskem načinu sejo zažene Tracy, ker jo uporablja za prikazovanje trakov s preusmeritvami in AJAX zahtevami v Tracy Baru. -Razdelek .[#toc-section] -======================== +Sekcije +======= -V čistem jeziku PHP je shramba podatkov seje izvedena kot polje, ki je dostopno prek globalne spremenljivke `$_SESSION`. Težava je v tem, da so aplikacije običajno sestavljene iz več neodvisnih delov, in če imajo vsi na voljo le eno isto polje, bo prej ali slej prišlo do kolizije imen. +V čistem PHP je podatkovno skladišče seje realizirano kot polje, dostopno preko globalne spremenljivke `$_SESSION`. Problem je v tem, da se aplikacije običajno sestojijo iz cele vrste medsebojno neodvisnih delov in če imajo vsi na voljo le eno polje, prej ali slej pride do kolizije imen. -Nette Framework rešuje težavo tako, da celoten prostor razdeli na dele (predmete [api:Nette\Http\SessionSection]). Vsaka enota nato uporablja svoj odsek z edinstvenim imenom in do trkov ne more priti. +Nette Framework problem rešuje tako, da celoten prostor razdeli na sekcije (objekte [api:Nette\Http\SessionSection]). Vsaka enota nato uporablja svojo sekcijo z edinstvenim imenom in do nobene kolizije več ne more priti. -Odsek dobimo od upravitelja sej: +Sekcijo dobimo iz seje: ```php -$section = $session->getSection('unique name'); +$section = $session->getSection('unikatno ime'); ``` -V predstavitvenem programu je dovolj, da pokličemo `getSession()` s parametrom: +V presenterju je dovolj uporabiti `getSession()` s parametrom: ```php -// $this je Predavatelj -$section = $this->getSession('unique name'); +// $this je Presenter +$section = $this->getSession('unikatno ime'); ``` -Obstoj odseka lahko preverimo z metodo `$session->hasSection('unique name')`. +Preveriti obstoj sekcije je mogoče z metodo `$session->hasSection('unikatno ime')`. -S samim odsekom je zelo enostavno delati z metodami `set()`, `get()` in `remove()`: +S samo sekcijo se nato dela zelo enostavno s pomočjo metod `set()`, `get()` in `remove()`: ```php -// pisanje spremenljivk +// zapis spremenljivke $section->set('userName', 'franta'); -// branje spremenljivke, vrne nič, če ne obstaja +// branje spremenljivke, vrne null če ne obstaja echo $section->get('userName'); -// odstranjevanje spremenljivke +// preklic spremenljivke $section->remove('userName'); ``` -Za pridobitev vseh spremenljivk iz razdelka je mogoče uporabiti cikel `foreach`: +Za pridobitev vseh spremenljivk iz sekcije je mogoče uporabiti zanko `foreach`: ```php foreach ($section as $key => $val) { @@ -74,44 +74,44 @@ foreach ($section as $key => $val) { ``` -Kako nastaviti potek veljavnosti .[#toc-how-to-set-expiration] --------------------------------------------------------------- +Nastavitev poteka +----------------- -Iztek veljavnosti lahko nastavite za posamezne odseke ali celo posamezne spremenljivke. Uporabnikova prijava lahko poteče čez 20 minut, vendar si lahko še vedno zapomnimo vsebino nakupovalne košarice. +Za posamezne sekcije ali celo posamezne spremenljivke je mogoče nastaviti potek. Lahko tako pustimo poteči prijavo uporabnika čez 20 minut, vendar si pri tem še naprej zapomnimo vsebino košarice. ```php -// razdelek poteče po 20 minutah. +// sekcija poteče po 20 minutah $section->setExpiration('20 minutes'); ``` -Tretji parameter metode `set()` se uporablja za nastavitev izteka veljavnosti posameznih spremenljivk: +Za nastavitev poteka posameznih spremenljivk služi tretji parameter metode `set()`: ```php -// spremenljivka 'flash' poteče po 30 sekundah +// spremenljivka 'flash' poteče že po 30 sekundah $section->set('flash', $message, '30 seconds'); ``` .[note] -Ne pozabite, da mora biti čas poteka celotne seje (glejte [konfiguracijo seje |configuration#session]) enak ali višji od časa, nastavljenega za posamezne odseke ali spremenljivke. +Ne pozabite, da mora biti čas poteka celotne seje (glej [konfiguracija seje |configuration#Seja]) enak ali daljši od časa, nastavljenega pri posameznih sekcijah ali spremenljivkah. -Preklic predhodno nastavljenega izteka je mogoče doseči z metodo `removeExpiration()`. Takojšen izbris celotnega odseka bo zagotovljen z metodo `remove()`. +Preklic prej nastavljenega poteka dosežemo z metodo `removeExpiration()`. Takojšen preklic celotne sekcije zagotovi metoda `remove()`. -Dogodki $onStart, $onBeforeWrite .[#toc-events-onstart-onbeforewrite] ---------------------------------------------------------------------- +Dogodka $onStart, $onBeforeWrite +-------------------------------- -Objekt `Nette\Http\Session` ima [dogodka |nette:glossary#Events] `$onStart` in `$onBeforeWrite`, zato lahko dodate povratne klice, ki se kličejo po začetku seje ali preden se seja zapiše na disk in nato zaključi. +Objekt `Nette\Http\Session` ima [dogodke |nette:glossary#Dogodki eventi] `$onStart` in `$onBeforeWrite`, lahko torej dodate povratne klice, ki se sprožijo po zagonu seje ali pred njenim zapisom na disk in posledičnim zaključkom. ```php $session->onBeforeWrite[] = function () { - // zapisovanje podatkov v sejo + // zapišemo podatke v sejo $this->section->set('basket', $this->basket); }; ``` -Upravljanje seje .[#toc-session-management] -=========================================== +Upravljanje sej +=============== Pregled metod razreda `Nette\Http\Session` za upravljanje sej: @@ -120,32 +120,32 @@ Pregled metod razreda `Nette\Http\Session` za upravljanje sej: start(): void .[method] ----------------------- -Začne sejo. +Zažene sejo. isStarted(): bool .[method] --------------------------- -Ali se je seja začela? +Je seja zagnana? close(): void .[method] ----------------------- -Konča sejo. Seja se samodejno konča ob koncu scenarija. +Zaključi sejo. Seja se samodejno zaključi na koncu izvajanja skripta. destroy(): void .[method] ------------------------- -Konča in izbriše sejo. +Zaključi in izbriše sejo. exists(): bool .[method] ------------------------ -Ali zahteva HTTP vsebuje piškotek z ID seje? +Ali HTTP zahteva vsebuje piškotek z ID-jem seje? regenerateId(): void .[method] ------------------------------ -Ustvari nov naključni ID seje. Podatki ostanejo nespremenjeni. +Generira nov naključni ID seje. Podatki ostanejo ohranjeni. getId(): string .[method] @@ -155,56 +155,57 @@ Vrne ID seje. </div> -Konfiguracija .[#toc-configuration] ------------------------------------ +Konfiguracija +------------- -Seanso konfiguriramo v [konfiguraciji |configuration#session]. Če pišete aplikacijo, ki ne uporablja vsebnika DI, jo konfigurirajte s temi metodami. Poklicati jih je treba pred začetkom seje. +Sejo nastavljamo v [konfiguraciji |configuration#Seja]. Če pišete aplikacijo, ki ne uporablja DI vsebnika, služijo za konfiguracijo te metode. Morajo biti poklicane še pred zagonom seje. <div class=wiki-methods-brief> setName(string $name): static .[method] --------------------------------------- -Nastavi ime piškotka, ki se uporablja za prenos ID seje. Privzeto ime je `PHPSESSID`. To je uporabno, če na istem spletnem mestu uporabljate več različnih aplikacij. +Nastavi ime piškotka, v katerem se prenaša ID seje. Standardno ime je `PHPSESSID`. Koristno je v primeru, ko v okviru enega spletnega mesta poganjate več različnih aplikacij. getName(): string .[method] --------------------------- -Vrne ime sejnega piškotka. +Vrača ime piškotka, v katerem se prenaša ID seje. setOptions(array $options): static .[method] -------------------------------------------- -Konfigurira sejo. Mogoče je nastaviti vse [direktive seje |https://www.php.net/manual/en/session.configuration.php] PHP (v obliki camelCase, npr. namesto `session.save_path` napišite `savePath` ) in tudi [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. +Konfigurira sejo. Lahko nastavljate vse PHP [direktive seje |https://www.php.net/manual/en/session.configuration.php] (v formatu camelCase, npr. namesto `session.save_path` zapišemo `savePath`) in tudi [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. setExpiration(?string $time): static .[method] ---------------------------------------------- -Nastavi čas neaktivnosti, po katerem se seja izteče. +Nastavi čas neaktivnosti, po katerem seja poteče. -setCookieParameters(string $path, string $domain=null, bool $secure=null, string $samesite=null): static .[method] ------------------------------------------------------------------------------------------------------------------- -Nastavi parametre za piškotke. Privzete vrednosti parametrov lahko spremenite v [konfiguraciji |configuration#Session cookie]. +setCookieParameters(string $path, ?string $domain=null, ?bool $secure=null, ?string $samesite=null): static .[method] +--------------------------------------------------------------------------------------------------------------------- +Nastavitev parametrov za piškotek. Privzete vrednosti parametrov lahko spremenite v [konfiguraciji |configuration#Piškotek seje]. setSavePath(string $path): static .[method] ------------------------------------------- -Nastavi imenik, v katerem so shranjene datoteke sej. +Nastavi imenik, kamor se shranjujejo datoteke s sejo. setHandler(\SessionHandlerInterface $handler): static .[method] --------------------------------------------------------------- -Nastavi obdelava po meri, glejte [dokumentacijo PHP |https://www.php.net/manual/en/class.sessionhandlerinterface.php]. +Nastavitev lastnega obravnavalnika, glej [dokumentacija PHP|https://www.php.net/manual/en/class.sessionhandlerinterface.php]. </div> -Najprej varnost .[#toc-safety-first] -==================================== +Varnost na prvem mestu +====================== -Strežnik domneva, da komunicira z istim uporabnikom, dokler zahteve vsebujejo isti ID seje. Naloga varnostnih mehanizmov je zagotoviti, da to vedenje res deluje in da ni možnosti zamenjave ali kraje identifikatorja. +Strežnik predpostavlja, da komunicira vedno z istim uporabnikom, dokler zahteve spremlja isti ID seje. Naloga varnostnih mehanizmov je zagotoviti, da je temu res tako in da ni mogoče identifikatorja ukrasti ali podtakniti. -Zato ogrodje Nette Framework ustrezno konfigurira direktive PHP za prenos identifikatorja seje samo v piškotkih, preprečevanje dostopa iz JavaScripta in ignoriranje identifikatorjev v naslovu URL. Poleg tega v kritičnih trenutkih, kot je prijava uporabnika, ustvari nov identifikator seje. +Nette Framework zato pravilno konfigurira PHP direktive, da ID seje prenaša samo v piškotku, ga onemogoči JavaScriptu in morebitne identifikatorje v URL-ju ignorira. Poleg tega v kritičnih trenutkih, kot je na primer prijava uporabnika, generira nov ID seje. -Funkcija ini_set se uporablja za konfiguracijo PHP, vendar je njena uporaba pri nekaterih storitvah spletnega gostovanja žal prepovedana. Če je to vaš primer, poskusite prositi ponudnika gostovanja, da vam to funkcijo dovoli ali vsaj ustrezno konfigurira svoj strežnik. .[note] +.[note] +Za konfiguracijo PHP se uporablja funkcija ini_set, ki jo na žalost nekateri gostitelji prepovedujejo. Če je to primer tudi vašega gostitelja, se poskusite z njim dogovoriti, da vam funkcijo dovoli ali vsaj strežnik konfigurira. diff --git a/http/sl/urls.texy b/http/sl/urls.texy index 03f088eed4..64f5164a46 100644 --- a/http/sl/urls.texy +++ b/http/sl/urls.texy @@ -1,28 +1,28 @@ -Razhroščevalnik in graditelj URL-jev -************************************ +Delo z URL-ji +************* .[perex] -Razredi [Url |#Url], [UrlImmutable |#UrlImmutable] in [UrlScript |#UrlScript] omogočajo enostavno upravljanje, razčlenjevanje in manipuliranje z naslovi URL. +Razreda [#Url], [#UrlImmutable] in [#UrlScript] omogočata enostavno generiranje, razčlenjevanje in manipulacijo z URL-ji. -→ [Namestitev in zahteve |@home#Installation] +→ [Namestitev in zahteve |@home#Namestitev] Url === -Razred [api:Nette\Http\Url] omogoča enostavno delo z naslovom URL in njegovimi posameznimi sestavnimi deli, ki so opisani v tem diagramu: +Razred [api:Nette\Http\Url] omogoča enostavno delo z URL-ji in njihovimi posameznimi komponentami, ki jih zajema ta skica: /--pre - scheme user password host port path query fragment + shema uporabnik geslo gostitelj vrata pot poizvedba fragment | | | | | | | | /--\ /--\ /------\ /-------\ /--\/----------\ /--------\ /----\ <b>http://john:xyz%2A12@nette.org:8080/en/download?name=param#footer</b> \______\__________________________/ | | - hostUrl authority + hostUrl avtoriteta \-- -Ustvarjanje URL je intuitivno: +Generiranje URL-jev je intuitivno: ```php use Nette\Http\Url; @@ -36,7 +36,7 @@ $url->setScheme('https') echo $url; // 'https://localhost/edit?foo=bar' ``` -URL lahko tudi razčlenite in ga nato obdelate: +Lahko tudi URL razčlenite in ga nadalje manipulirate: ```php $url = new Url( @@ -44,62 +44,94 @@ $url = new Url( ); ``` -Za pridobivanje ali spreminjanje posameznih komponent URL so na voljo naslednje metode: +Razred `Url` implementira vmesnik `JsonSerializable` in ima metodo `__toString()`, tako da lahko objekt izpišete ali uporabite v podatkih, predanih v `json_encode()`. + +```php +echo $url; +echo json_encode([$url]); +``` + + +Komponente URL .[method] +------------------------ + +Za vračanje ali spreminjanje posameznih komponent URL-ja so vam na voljo te metode: .[language-php] -| Setter | Getter | Vrnjena vrednost +| Setter | Getter | Vrnjena vrednost |-------------------------------------------------------------------------------------------- -| `setScheme(string $scheme)`| `getScheme(): string`| `'http'` -| `setUser(string $user)`| `getUser(): string`| `'john'` -| `setPassword(string $password)`| `getPassword(): string`| `'xyz*12'` -| `setHost(string $host)`| `getHost(): string`| `'nette.org'` -| `setPort(int $port)`| `getPort(): ?int`| `8080` -| | `getDefaultPort(): ?int`| `80` -| `setPath(string $path)`| `getPath(): string`| `'/en/download'` -| `setQuery(string\|array $query)`| `getQuery(): string`| `'name=param'` -| `setFragment(string $fragment)`| `getFragment(): string`| `'footer'` -| | `getAuthority(): string`| `'nette.org:8080'` -| | `getHostUrl(): string`| `http://nette.org:8080'` -| | `getAbsoluteUrl(): string` | polni URL - -S posameznimi parametri poizvedbe lahko operiramo tudi z uporabo: +| `setScheme(string $scheme)` | `getScheme(): string` | `'http'` +| `setUser(string $user)` | `getUser(): string` | `'john'` +| `setPassword(string $password)` | `getPassword(): string` | `'xyz*12'` +| `setHost(string $host)` | `getHost(): string` | `'nette.org'` +| `setPort(int $port)` | `getPort(): ?int` | `8080` +| | `getDefaultPort(): ?int` | `80` +| `setPath(string $path)` | `getPath(): string` | `'/en/download'` +| `setQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` +| `setFragment(string $fragment)` | `getFragment(): string` | `'footer'` +| | `getAuthority(): string` | `'john:xyz%2A12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | celoten URL + +Opozorilo: Ko delate z URL-jem, ki je pridobljen iz [zahteve HTTP|request], imejte v mislih, da ne bo vseboval fragmenta, ker ga brskalnik ne pošilja na strežnik. + +Lahko delamo tudi s posameznimi query parametri s pomočjo: .[language-php] -| Setter | Getter +| Setter | Getter |--------------------------------------------------- -| `setQuery(string\|array $query)` | `getQueryParameters(): array` -| `setQueryParameter(string $name, $val)`| `getQueryParameter(string $name)` +| `setQuery(string\|array $query)` | `getQueryParameters(): array` +| `setQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` -Metoda `getDomain(int $level = 2)` vrne desni ali levi del gostitelja. Tako deluje, če je gostitelj `www.nette.org`: + +getDomain(int $level = 2): string .[method] +------------------------------------------- +Vrača desni ali levi del gostitelja. Tako deluje, če je gostitelj `www.nette.org`: .[language-php] -| `getDomain(1)` | `'org'` -| `getDomain(2)` | `'nette.org'` -| `getDomain(3)` | `'www.nette.org'` -| `getDomain(0)` | `'www.nette.org'` -| `getDomain(-1)` | `'www.nette'` -| `getDomain(-2)` | `'www'` -| `getDomain(-3)` | `''` +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `''` -Razred `Url` implementira vmesnik `JsonSerializable` in ima metodo `__toString()`, tako da se lahko objekt natisne ali uporabi v podatkih, posredovanih `json_encode()`. +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +Preveri, ali sta dva URL-ja enaka. ```php -echo $url; -echo json_encode([$url]); +$url->isEqual('https://nette.org'); ``` -Metoda `isEqual(string|Url $anotherUrl): bool` preveri, ali sta naslova URL enaka. + +Url::isAbsolute(string $url): bool .[method]{data-version:3.3.2} +---------------------------------------------------------------- +Preverja, ali je URL absoluten. URL se šteje za absoluten, če se začne s shemo (npr. http, https, ftp), ki ji sledi dvopičje. ```php -$url->isEqual('https://nette.org'); +Url::isAbsolute('https://nette.org'); // true +Url::isAbsolute('//nette.org'); // false +``` + + +Url::removeDotSegments(string $path): string .[method]{data-version:3.3.2} +-------------------------------------------------------------------------- +Normalizira pot v URL-ju z odstranitvijo posebnih segmentov `.` in `..`. Metoda odstranjuje odvečne elemente poti na enak način, kot to počnejo spletni brskalniki. + +```php +Url::removeDotSegments('/path/../subtree/./file.txt'); // '/subtree/file.txt' +Url::removeDotSegments('/../foo/./bar'); // '/foo/bar' +Url::removeDotSegments('./today/../file.txt'); // 'file.txt' ``` -UrlImmutable .[#toc-urlimmutable] -================================= +UrlImmutable +============ -Razred [api:Nette\Http\UrlImmutable] je nespremenljiva alternativa razredu `Url` (tako kot je v PHP `DateTimeImmutable` nespremenljiva alternativa `DateTime`). Namesto setterjev ima tako imenovane witherje, ki ne spreminjajo objekta, temveč vračajo nove instance s spremenjeno vrednostjo: +Razred [api:Nette\Http\UrlImmutable] je nespremenljiva (immutable) alternativa razredu [#Url] (podobno kot je v PHP `DateTimeImmutable` nespremenljiva alternativa `DateTime`). Namesto nastavitvenih metod ima t.i. wither metode, ki objekta ne spreminjajo, ampak vračajo nove instance s prilagojeno vrednostjo: ```php use Nette\Http\UrlImmutable; @@ -111,53 +143,96 @@ $url = new UrlImmutable( $newUrl = $url ->withUser('') ->withPassword('') - ->withPath('/en/'); + ->withPath('/cs/'); + +echo $newUrl; // 'http://john:xyz%2A12@nette.org:8080/cs/?name=param#footer' +``` + +Razred `UrlImmutable` implementira vmesnik `JsonSerializable` in ima metodo `__toString()`, tako da lahko objekt izpišete ali uporabite v podatkih, predanih v `json_encode()`. -echo $newUrl; // 'http://nette.org:8080/en/?name=param#footer' +```php +echo $url; +echo json_encode([$url]); ``` -Za pridobivanje ali spreminjanje posameznih komponent URL so na voljo naslednje metode: + +Komponente URL .[method] +------------------------ + +Za vračanje ali spreminjanje posameznih komponent URL-ja služijo metode: .[language-php] -| Wither | Getter | Vrnjena vrednost +| Wither | Getter | Vrnjena vrednost |-------------------------------------------------------------------------------------------- -| `withScheme(string $scheme)`| `getScheme(): string`| `'http'` -| `withUser(string $user)`| `getUser(): string`| `'john'` -| `withPassword(string $password)`| `getPassword(): string`| `'xyz*12'` -| `withHost(string $host)`| `getHost(): string`| `'nette.org'` -| `withPort(int $port)`| `getPort(): ?int`| `8080` -| | `getDefaultPort(): ?int`| `80` -| `withPath(string $path)`| `getPath(): string`| `'/en/download'` -| `withQuery(string\|array $query)`| `getQuery(): string`| `'name=param'` -| `withFragment(string $fragment)`| `getFragment(): string`| `'footer'` -| | `getAuthority(): string`| `'nette.org:8080'` -| | `getHostUrl(): string`| `http://nette.org:8080'` -| | `getAbsoluteUrl(): string` | polni URL - -S posameznimi parametri poizvedbe lahko operiramo tudi z uporabo: +| `withScheme(string $scheme)` | `getScheme(): string` | `'http'` +| `withUser(string $user)` | `getUser(): string` | `'john'` +| `withPassword(string $password)` | `getPassword(): string` | `'xyz*12'` +| `withHost(string $host)` | `getHost(): string` | `'nette.org'` +| `withPort(int $port)` | `getPort(): ?int` | `8080` +| | `getDefaultPort(): ?int` | `80` +| `withPath(string $path)` | `getPath(): string` | `'/en/download'` +| `withQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` +| `withFragment(string $fragment)` | `getFragment(): string` | `'footer'` +| | `getAuthority(): string` | `'john:xyz%2A12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | celoten URL + +Metoda `withoutUserInfo()` odstranjuje `user` in `password`. + +Lahko delamo tudi s posameznimi query parametri s pomočjo: .[language-php] -| Wither | Getter +| Wither | Getter |----------------------------------------------- -| `withQuery(string\|array $query)` | `getQueryParameters(): array` -| `withQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` +| `withQuery(string\|array $query)` | `getQueryParameters(): array` +| `withQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` -Metoda `getDomain(int $level = 2)` deluje enako kot metoda v `Url`. Metoda `withoutUserInfo()` odstrani `user` in `password`. -Razred `UrlImmutable` implementira vmesnik `JsonSerializable` in ima metodo `__toString()`, tako da se lahko objekt natisne ali uporabi v podatkih, posredovanih v `json_encode()`. +getDomain(int $level = 2): string .[method] +------------------------------------------- +Vrača desni ali levi del gostitelja. Tako deluje, če je gostitelj `www.nette.org`: + +.[language-php] +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `''` + + +resolve(string $reference): UrlImmutable .[method]{data-version:3.3.2} +---------------------------------------------------------------------- +Izpelje absolutni URL na enak način, kot brskalnik obdeluje povezave na HTML strani: +- če je povezava absolutni URL (vsebuje shemo), se uporabi nespremenjena +- če se povezava začne z `//`, se prevzame samo shema iz trenutnega URL-ja +- če se povezava začne z `/`, se ustvari absolutna pot od korena domene +- v ostalih primerih se URL sestavi relativno glede na trenutno pot ```php -echo $url; -echo json_encode([$url]); +$url = new UrlImmutable('https://example.com/path/page'); +echo $url->resolve('../foo'); // 'https://example.com/foo' +echo $url->resolve('/bar'); // 'https://example.com/bar' +echo $url->resolve('sub/page.html'); // 'https://example.com/path/sub/page.html' +``` + + +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +Preveri, ali sta dva URL-ja enaka. + +```php +$url->isEqual('https://nette.org'); ``` -Metoda `isEqual(string|Url $anotherUrl): bool` preveri, ali sta naslova URL enaka. +UrlScript +========= -UrlScript .[#toc-urlscript] -=========================== +Razred [api:Nette\Http\UrlScript] je potomec [#UrlImmutable] in ga razširja z dodatnimi virtualnimi komponentami URL-ja, kot je korenski imenik projekta ipd. Tako kot starševski razred je nespremenljiv (immutable) objekt. -Razred [api:Nette\Http\UrlScript] je potomec razreda `UrlImmutable` in dodatno razlikuje te logične dele URL-ja: +Naslednji diagram prikazuje komponente, ki jih UrlScript prepoznava: /--pre baseUrl basePath relativePath relativeUrl @@ -169,17 +244,23 @@ Razred [api:Nette\Http\UrlScript] je potomec razreda `UrlImmutable` in dodatno r scriptPath pathInfo \-- -Za pridobitev teh delov so na voljo naslednje metode: +- `baseUrl` je osnovni URL naslov aplikacije, vključno z domeno in delom poti do korenskega imenika aplikacije +- `basePath` je del poti do korenskega imenika aplikacije +- `scriptPath` je pot do trenutnega skripta +- `relativePath` je ime skripta (po potrebi dodatni segmenti poti) relativno glede na basePath +- `relativeUrl` je celoten del URL-ja za baseUrl, vključno s query stringom in fragmentom. +- `pathInfo` danes že malo uporabljen del URL-ja za imenom skripta + +Za vračanje delov URL-ja so na voljo metode: .[language-php] -| Getter | Vrnjena vrednost +| Getter | Vrnjena vrednost |------------------------------------------------ -| `getScriptPath(): string`| `'/admin/script.php'` -| `getBasePath(): string`| `'/admin/'` -| `getBaseUrl(): string`| `http://nette.org/admin/'` -| `getRelativePath(): string`| `'script.php'` -| `getRelativeUrl(): string`| `'script.php/pathinfo/?name=param#footer'` -| `getPathInfo(): string`| `'/pathinfo/'` - - -Ne ustvarjamo predmetov `UrlScript` neposredno, ampak jih vrne metoda [Nette\Http\Request::getUrl() |request]. +| `getScriptPath(): string` | `'/admin/script.php'` +| `getBasePath(): string` | `'/admin/'` +| `getBaseUrl(): string` | `'http://nette.org/admin/'` +| `getRelativePath(): string` | `'script.php'` +| `getRelativeUrl(): string` | `'script.php/pathinfo/?name=param#footer'` +| `getPathInfo(): string` | `'/pathinfo/'` + +Objektov `UrlScript` običajno ne ustvarjamo neposredno, ampak jih vrača metoda [Nette\Http\Request::getUrl()|request] z že pravilno nastavljenimi komponentami za trenutno HTTP zahtevo. diff --git a/http/tr/@home.texy b/http/tr/@home.texy index 77ec4571a0..316b44dbb5 100644 --- a/http/tr/@home.texy +++ b/http/tr/@home.texy @@ -2,13 +2,13 @@ Nette HTTP ********** .[perex] -`nette/http` paketi [HTTP istek |request] ve [yanıtını |response] kapsüller, [oturumlarla |sessions] ve [URL ayrıştırma ve oluşturma |urls] ile çalışır. +`nette/http` paketi [HTTP isteğini|request] & [yanıtını|response], [oturumlarla|sessions] çalışmayı ve [URL'lerin ayrıştırılmasını ve birleştirilmesini |urls] kapsar. -Kurulum .[#toc-installation] ----------------------------- +Kurulum +------- -[Composer'ı |best-practices:composer] kullanarak paketi indirin ve yükleyin: +Kütüphaneyi [Composer|best-practices:composer] aracını kullanarak indirip kurabilirsiniz: ```shell composer require nette/http diff --git a/http/tr/@left-menu.texy b/http/tr/@left-menu.texy index c229373914..d575519204 100644 --- a/http/tr/@left-menu.texy +++ b/http/tr/@left-menu.texy @@ -1,8 +1,8 @@ -Net HTTP -******** -- [Genel Bakış |@home] -- [HTTP isteği |request] -- [HTTP yanıtı |response] +Nette HTTP +********** +- [Giriş |@home] +- [HTTP isteği|request] +- [HTTP yanıtı|response] - [Oturumlar |Sessions] -- [URL yardımcı programı |urls] -- [Konfigürasyon |Configuration] +- [URL yardımcı programları |urls] +- [Yapılandırma |configuration] diff --git a/http/tr/@meta.texy b/http/tr/@meta.texy new file mode 100644 index 0000000000..8dfe82f311 --- /dev/null +++ b/http/tr/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokümantasyonu}} diff --git a/http/tr/configuration.texy b/http/tr/configuration.texy index 8f1533fd52..7b545523f4 100644 --- a/http/tr/configuration.texy +++ b/http/tr/configuration.texy @@ -1,43 +1,43 @@ -HTTP'yi Yapılandırma -******************** +HTTP Yapılandırması +******************* .[perex] Nette HTTP için yapılandırma seçeneklerine genel bakış. -Tüm çerçeveyi değil, yalnızca bu kütüphaneyi kullanıyorsanız, [yapılandırmayı nasıl yükleyeceğinizi |bootstrap:] okuyun. +Tüm framework'ü değil de yalnızca bu kütüphaneyi kullanıyorsanız, [yapılandırmayı nasıl yükleyeceğiniz |bootstrap:] hakkında bilgi edinin. -HTTP Üstbilgileri .[#toc-http-headers] -====================================== +HTTP Başlıkları +=============== ```neon http: - # her istekle birlikte gönderilen başlıklar + # her istekle gönderilecek başlıklar headers: X-Powered-By: MyCMS X-Content-Type-Options: nosniff X-XSS-Protection: '1; mode=block' # X-Frame-Options başlığını etkiler - frames: ... # (string|bool) varsayılan olarak 'SAMEORIGIN' + frames: ... # (string|bool) varsayılan 'SAMEORIGIN' ``` -Güvenlik nedeniyle, çerçeve bir sayfanın başka bir sayfanın içinde görüntülenebileceğini belirten bir `X-Frame-Options: SAMEORIGIN` başlığı gönderir (öğe `<iframe>`) yalnızca aynı etki alanındaysa. Bu bazı durumlarda istenmeyen bir durum olabilir (örneğin, bir Facebook uygulaması geliştiriyorsanız), bu nedenle davranış `frames: http://allowed-host.com`. +Framework, güvenlik nedeniyle, sayfanın başka bir sayfa içinde ( `<iframe>` öğesinde) yalnızca aynı alan adında bulunuyorsa görüntülenebileceğini söyleyen `X-Frame-Options: SAMEORIGIN` başlığını gönderir. Bu bazı durumlarda istenmeyebilir (örneğin, Facebook için bir uygulama geliştiriyorsanız), davranış bu nedenle `frames: http://allowed-host.com` veya `frames: true` ayarlanarak değiştirilebilir. -İçerik Güvenliği Politikası .[#toc-content-security-policy] ------------------------------------------------------------ +Content Security Policy +----------------------- -Başlıklar `Content-Security-Policy` (bundan sonra CSP olarak anılacaktır) kolayca bir araya getirilebilir, açıklamaları [CSP açıklam |https://content-security-policy.com] asında bulunabilir. CSP direktifleri ( `script-src` gibi) spesifikasyona göre dizeler olarak ya da daha iyi okunabilirlik için değer dizileri olarak yazılabilir. Bu durumda `'self'` gibi anahtar kelimelerin etrafına tırnak işareti koymaya gerek yoktur. Nette ayrıca otomatik olarak `nonce` değerini üretecektir, böylece `'nonce-y4PopTLM=='` başlıkta gönderilecektir. +`Content-Security-Policy` (bundan sonra CSP) başlıklarını kolayca oluşturabilirsiniz, açıklamaları [CSP açıklaması |https://content-security-policy.com] içinde bulunabilir. CSP yönergeleri (ör. `script-src`) ya belirtimlere göre dizeler olarak ya da daha iyi okunabilirlik için değer dizileri olarak yazılabilir. O zaman `'self'` gibi anahtar kelimelerin etrafına tırnak işareti koymaya gerek yoktur. Nette ayrıca otomatik olarak bir `nonce` değeri oluşturur, böylece başlıkta örneğin `'nonce-y4PopTLM=='` olacaktır. ```neon http: - # İçerik Güvenliği Politikası + # Content Security Policy csp: - # CSP spesifikasyonuna göre dize + # CSP belirtimine göre dize biçimi default-src: "'self' https://example.com" - # değerler dizisi + # değer dizisi script-src: - nonce - strict-dynamic @@ -49,18 +49,18 @@ http: block-all-mixed-content: false ``` -Kullanım `<script n:nonce>...</script>` ve nonce değeri otomatik olarak doldurulacaktır. Nette'de güvenli web siteleri yapmak gerçekten çok kolay. +Şablonlarda `<script n:nonce>...</script>` kullanın ve nonce değeri otomatik olarak eklenecektir. Nette'de güvenli web siteleri yapmak gerçekten kolaydır. -Benzer şekilde, `Content-Security-Policy-Report-Only` (CSP ile paralel olarak kullanılabilir) ve [Özellik Politikası |https://developers.google.com/web/updates/2018/06/feature-policy] başlıkları da eklenebilir: +Benzer şekilde, `Content-Security-Policy-Report-Only` (CSP ile eş zamanlı olarak kullanılabilir) ve [Feature Policy|https://developers.google.com/web/updates/2018/06/feature-policy] başlıkları da oluşturulabilir: ```neon http: - # Yalnızca İçerik Güvenliği Politikası Raporu + # Content Security Policy Report-Only cspReportOnly: default-src: self report-uri: 'https://my-report-uri-endpoint' - # Özellik Politikası + # Feature Policy featurePolicy: unsized-media: none geolocation: @@ -69,91 +69,103 @@ http: ``` -HTTP Çerezi .[#toc-http-cookie] -------------------------------- +HTTP çerezi +----------- -[Nette\Http\Response::setCookie() |response#setCookie] ve session yöntemlerinin bazı parametrelerinin varsayılan değerlerini değiştirebilirsiniz. +[Nette\Http\Response::setCookie() |response#setCookie] metodunun ve oturumun bazı parametrelerinin varsayılan değerlerini değiştirebilirsiniz. ```neon http: # yola göre çerez kapsamı - cookiePath: ... # (string) varsayılan olarak '/' + cookiePath: ... # (string) varsayılan '/' - # hangi ana bilgisayarların çerezi almasına izin verilir - cookieDomain: 'example.com' # (string|domain) varsayılan olarak unset + # çerezleri kabul eden alan adları + cookieDomain: 'example.com' # (string|domain) varsayılan ayarlanmamış - # çerezleri yalnızca HTTPS üzerinden göndermek için? - cookieSecure: ... # (bool|auto) varsayılan olarak auto + # çerezi yalnızca HTTPS üzerinden gönder? + cookieSecure: ... # (bool|auto) varsayılan auto - # Nette'in CSRF'ye karşı koruma olarak kullandığı çerezin gönderilmesini devre dışı bırakır - disableNetteCookie: ... # (bool) varsayılan değer false + # Nette tarafından CSRF koruması olarak kullanılan çerezin gönderilmesini devre dışı bırakır + disableNetteCookie: ... # (bool) varsayılan false ``` -`cookieDomain` seçeneği hangi etki alanlarının (kökenlerin) çerezleri kabul edebileceğini belirler. Belirtilmezse, çerez, alt alan adları hariç tutularak *aynı (alt) alan adı tarafından kabul edilir. Eğer `cookieDomain` belirtilirse, alt alan adları da dahil edilir. Bu nedenle, `cookieDomain` belirtmek, belirtmemekten daha az kısıtlayıcıdır. +`cookieDomain` niteliği, hangi alan adlarının çerezi kabul edebileceğini belirtir. Belirtilmezse, çerez onu ayarlayan aynı (alt) alan adı tarafından kabul edilir, *ancak* alt alan adları tarafından değil. `cookieDomain` belirtilirse, alt alan adları da dahil edilir. Bu nedenle, `cookieDomain` belirtmek, atlamaktan daha az kısıtlayıcıdır. -Örneğin, `cookieDomain: nette.org` ayarlanırsa, çerez `doc.nette.org` gibi tüm alt alan adlarında da kullanılabilir. Bu, `domain` yani `cookieDomain: domain` özel değeri ile de elde edilebilir. +Örneğin, `cookieDomain: nette.org` ile çerezler `doc.nette.org` gibi tüm alt alan adlarında da kullanılabilir. Aynı şey özel `domain` değeriyle, yani `cookieDomain: domain` ile de elde edilebilir. -`cookieSecure` 'un varsayılan değeri `auto` 'dir, bu da web sitesi HTTPS üzerinde çalışıyorsa çerezlerin `Secure` bayrağıyla gönderileceği ve bu nedenle yalnızca HTTPS üzerinden kullanılabileceği anlamına gelir. +`cookieSecure` niteliğindeki varsayılan `auto` değeri, web sitesi HTTPS üzerinde çalışıyorsa, çerezlerin `Secure` bayrağıyla gönderileceği ve dolayısıyla yalnızca HTTPS üzerinden erişilebilir olacağı anlamına gelir. -HTTP Proxy .[#toc-http-proxy] ------------------------------ +HTTP proxy +---------- -Site bir HTTP proxy'sinin arkasında çalışıyorsa, HTTPS bağlantılarının algılanmasının doğru çalışması için proxy'nin IP adresini ve istemci IP adresini girin. Böylece [Nette\Http\Request::getRemoteAddress( |request#getRemoteAddress] ) ve [isSecured() |request#isSecured] doğru değerleri döndürür ve şablonlarda `https:` protokolü ile bağlantılar oluşturulur. +Web sitesi bir HTTP proxy arkasında çalışıyorsa, HTTPS üzerinden bağlantı algılamasının ve ayrıca istemcinin IP adresinin doğru çalışması için IP adresini belirtin. Yani [Nette\Http\Request::getRemoteAddress() |request#getRemoteAddress] ve [isSecured() |request#isSecured] fonksiyonlarının doğru değerleri döndürmesi ve şablonlarda `https:` protokolü ile bağlantıların oluşturulması için. ```neon http: - # IP adresi, aralık (örn. 127.0.0.1/8) veya bu değerlerin dizisi - proxy: 127.0.0.1 # (string|string[]) varsayılan değeri none + # IP adresi, aralık (ör. 127.0.0.1/8) veya bu değerlerin dizisi + proxy: 127.0.0.1 # (string|string[]) varsayılan ayarlanmamış ``` -Oturum .[#toc-session] -====================== +Oturum (Session) +================ Temel [oturum |sessions] ayarları: ```neon session: - # Tracy Bar'da oturum panelini gösterir mi? - debugger: ... # (bool) varsayılan değer false + # Tracy Bar'da oturum panelini göster? + debugger: ... # (bool) varsayılan false - # oturumun sona erdiği hareketsizlik süresi - expiration: 14 days # (string) varsayılan değer '3 saat' + # oturumun sona ereceği etkinlik dışı kalma süresi + expiration: 14 days # (string) varsayılan '3 hours' # oturum ne zaman başlatılmalı? - autoStart: ... # (smart|always|never) varsayılan olarak 'smart' + autoStart: ... # (smart|always|never) varsayılan 'smart' - # işleyici, SessionHandlerInterface arayüzünü uygulayan hizmet + # handler, SessionHandlerInterface arayüzünü uygulayan servis handler: @handlerService ``` -`autoStart` seçeneği oturumun ne zaman başlatılacağını kontrol eder. `always` değeri, oturumun her zaman uygulama başladığında başlatılacağı anlamına gelir. `smart` değeri, oturumun uygulama başladığında yalnızca zaten mevcutsa veya ondan okumak ya da ona yazmak istediğimiz anda başlatılacağı anlamına gelir. Son olarak, `never` değeri oturumun otomatik olarak başlatılmasını devre dışı bırakır. +`autoStart` seçeneği, oturumun ne zaman başlatılacağını kontrol eder. `always` değeri, oturumun her zaman uygulamanın başlatılmasıyla birlikte başlatılacağı anlamına gelir. `smart` değeri, oturumun yalnızca zaten varsa veya ondan okumak veya ona yazmak istediğimiz anda uygulamanın başlangıcında başlatılacağı anlamına gelir. Ve son olarak, `never` değeri oturumun otomatik olarak başlatılmasını yasaklar. -Ayrıca, tüm PHP [oturum yönergelerini |https://www.php.net/manual/en/session.configuration.php] (camelCase biçiminde) ve ayrıca [readAndClose'u |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters] da ayarlayabilirsiniz. Örnek +Ayrıca, tüm PHP [oturum yönergeleri |https://www.php.net/manual/en/session.configuration.php] (camelCase biçiminde) ve ayrıca [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters] ayarlanabilir. Örnek: ```neon session: - # 'session.name' 'name' olarak yazıldı + # 'session.name' 'name' olarak yazılır name: MYID - # 'session.save_path', 'savePath' olarak yazılır + # 'session.save_path' 'savePath' olarak yazılır savePath: "%tempDir%/sessions" ``` -Oturum Çerezi .[#toc-session-cookie] ------------------------------------- +Oturum çerezi +------------- -Oturum çerezi [diğer çerez |#HTTP cookie] lerle aynı parametrelerle gönderilir, ancak bunları onun için değiştirebilirsiniz: +Oturum çerezi [diğer çerezler |#HTTP çerezi] ile aynı parametrelerle gönderilir, ancak bunlar sizin için değiştirilebilir: ```neon session: - # hangi ana bilgisayarların çerezi almasına izin verilir - cookieDomain: 'example.com' # (string|domain) + # çerezleri kabul eden alan adları + cookieDomain: 'example.com' # (string|domain) - # çapraz kökenli isteklere erişirken kısıtlamalar - cookieSamesite: ... # (Strict|Lax|None) varsayılan olarak Lax + # başka bir alan adından erişimde kısıtlama + cookieSamesite: None # (Strict|Lax|None) varsayılan Lax ``` -`cookieSamesite` seçeneği, çerezin [Siteler Arası İstek Sahteciliği |nette:glossary#cross-site-request-forgery-csrf] saldırılarına karşı bir miktar koruma sağlayan [çapraz kökenli istek |nette:glossary#SameSite cookie]lerle gönderilip gönderilmeyeceğini etkiler. +`cookieSamesite` niteliği, çerezin [başka bir alan adından erişim |nette:glossary#SameSite Çerezi] sırasında gönderilip gönderilmeyeceğini etkiler, bu da [Cross-Site Request Forgery |nette:glossary#Cross-Site Request Forgery CSRF] (CSRF) saldırılarına karşı bir miktar koruma sağlar. + + +DI Servisleri +============= + +Bu servisler DI konteynerine eklenir: + +| İsim | Tip | Açıklama +|----------------------------------------------------- +| `http.request` | [api:Nette\Http\Request] | [HTTP isteği| request] +| `http.response` | [api:Nette\Http\Response] | [HTTP yanıtı| response] +| `session.session` | [api:Nette\Http\Session] | [oturum yönetimi| sessions] diff --git a/http/tr/request.texy b/http/tr/request.texy index 75192ba3e4..ba5ec9c1bc 100644 --- a/http/tr/request.texy +++ b/http/tr/request.texy @@ -2,19 +2,19 @@ HTTP İsteği *********** .[perex] -Nette, HTTP isteğini anlaşılabilir bir API ile nesneler halinde kapsüllerken bir sanitizasyon filtresi sağlar. +Nette, HTTP isteğini anlaşılır bir API'ye sahip nesneler içinde kapsüller ve aynı zamanda bir temizleme filtresi sağlar. -Bir HTTP isteği, [bağımlılık enjeksiyonu |dependency-injection:passing-dependencies] kullanarak geçirdiğiniz bir [api:Nette\Http\Request] nesnesidir. Sunucularda basitçe `$httpRequest = $this->getHttpRequest()` adresini çağırın. +HTTP isteği [api:Nette\Http\Request] nesnesi tarafından temsil edilir. Nette ile çalışıyorsanız, bu nesne framework tarafından otomatik olarak oluşturulur ve [bağımlılık enjeksiyonu |dependency-injection:passing-dependencies] aracılığıyla size iletilmesini sağlayabilirsiniz. Presenter'larda sadece `$this->getHttpRequest()` metodunu çağırmanız yeterlidir. Nette Framework dışında çalışıyorsanız, [#RequestFactory] kullanarak bir nesne oluşturabilirsiniz. -Önemli olan, Nette'in bu nesneyi [oluştururken |#RequestFactory] tüm GET, POST ve COOKIE giriş parametrelerinin yanı sıra kontrol karakterlerinin ve geçersiz UTF-8 dizilerinin URL'lerini de temizlemesidir. Böylece verilerle güvenle çalışmaya devam edebilirsiniz. Temizlenen veriler daha sonra sunumlarda ve formlarda kullanılır. +Nette'nin büyük bir avantajı, nesneyi oluştururken tüm GET, POST, COOKIE giriş parametrelerini ve ayrıca URL'yi kontrol karakterlerinden ve geçersiz UTF-8 dizilerinden otomatik olarak temizlemesidir. Daha sonra bu verilerle güvenle çalışabilirsiniz. Temizlenmiş veriler daha sonra presenter'larda ve formlarda kullanılır. -→ [Kurulum ve gereksinimler |@home#Installation] +→ [Kurulum ve gereksinimler |@home#Kurulum] -Nette\Http\Request .[#toc-nette-http-request] -============================================= +Nette\Http\Request +================== -Bu nesne değişmezdir. Ayarlayıcıları yoktur, yalnızca bir tane wither `withUrl()` vardır, bu da nesneyi değiştirmez, ancak değiştirilmiş bir değere sahip yeni bir örnek döndürür. +Bu nesne değişmezdir (immutable). Hiçbir ayarlayıcısı yoktur, yalnızca nesneyi değiştirmeyen ancak değiştirilmiş bir değere sahip yeni bir örnek döndüren `withUrl()` adlı bir wither'ı vardır. withUrl(Nette\Http\UrlScript $url): Nette\Http\Request .[method] @@ -28,58 +28,58 @@ getUrl(): Nette\Http\UrlScript .[method] ```php $url = $httpRequest->getUrl(); -echo $url; // https://nette.org/en/documentation?action=edit +echo $url; // https://doc.nette.org/cs/?action=edit echo $url->getHost(); // nette.org ``` -Tarayıcılar sunucuya bir parça göndermez, bu nedenle `$url->getFragment()` boş bir dize döndürür. +Uyarı: tarayıcılar sunucuya fragment göndermez, bu nedenle `$url->getFragment()` boş bir dize döndürür. -getQuery(string $key=null): string|array|null .[method] -------------------------------------------------------- -GET istek parametrelerini döndürür: +getQuery(?string $key=null): string|array|null .[method] +-------------------------------------------------------- +GET isteğinin parametrelerini döndürür. ```php -$all = $httpRequest->getQuery(); // tüm URL parametrelerinin dizisi -$id = $httpRequest->getQuery('id'); // GET parametresi 'id' (veya null) döndürür +$all = $httpRequest->getQuery(); // URL'deki tüm parametrelerin dizisini döndürür +$id = $httpRequest->getQuery('id'); // 'id' GET parametresini döndürür (veya null) ``` -getPost(string $key=null): string|array|null .[method] ------------------------------------------------------- -POST istek parametrelerini döndürür: +getPost(?string $key=null): string|array|null .[method] +------------------------------------------------------- +POST isteğinin parametrelerini döndürür. ```php -$all = $httpRequest->getPost(); // tüm POST parametrelerinin dizisi -$id = $httpRequest->getPost('id'); // POST parametresi 'id' (veya null) döndürür +$all = $httpRequest->getPost(); // POST'taki tüm parametrelerin dizisini döndürür +$id = $httpRequest->getPost('id'); // 'id' POST parametresini döndürür (veya null) ``` getFile(string|string[] $key): Nette\Http\FileUpload|array|null .[method] ------------------------------------------------------------------------- -[Yüklemeyi |#Uploaded Files] nesne olarak döndürür [api:Nette\Http\FileUpload]: +[Yüklemeyi |#Yüklenen Dosyalar] [api:Nette\Http\FileUpload] nesnesi olarak döndürür: ```php $file = $httpRequest->getFile('avatar'); -if ($file->hasFile()) { // herhangi bir dosya yüklendi mi? - $file->getUntrustedName(); // kullanıcı tarafından gönderilen dosyanın adı +if ($file?->hasFile()) { // herhangi bir dosya yüklendi mi? + $file->getUntrustedName(); // kullanıcı tarafından gönderilen dosya adı $file->getSanitizedName(); // tehlikeli karakterler içermeyen ad } ``` -Alt ağaç yapısına erişmek için bir anahtar dizisi belirtin. +İç içe geçmiş yapıya erişmek için anahtar dizisi belirtin. ```php //<input type="file" name="my-form[details][avatar]" multiple> $file = $request->getFile(['my-form', 'details', 'avatar']); ``` -Dışarıdan gelen verilere güvenemediğiniz ve bu nedenle yapının biçimine güvenmediğiniz için, bu yöntem `$request->getFiles()['my-form']['details']['avatar']`bu da başarısız olabilir. +Dışarıdan gelen verilere güvenilemediği ve dolayısıyla dosya yapısının biçimine güvenilemediği için, bu yöntem, örneğin başarısız olabilecek `$request->getFiles()['my-form']['details']['avatar']`'dan daha güvenlidir. getFiles(): array .[method] --------------------------- -Normalleştirilmiş bir yapıda [yükleme dosyalarının |#Uploaded Files] ağacını döndürür, her yaprak [api:Nette\Http\FileUpload]'un bir örneğidir: +[Tüm yüklemeler |#Yüklenen Dosyalar] ağacını, yaprakları [api:Nette\Http\FileUpload] nesneleri olan normalleştirilmiş bir yapıda döndürür: ```php $files = $httpRequest->getFiles(); @@ -88,7 +88,7 @@ $files = $httpRequest->getFiles(); getCookie(string $key): string|array|null .[method] --------------------------------------------------- -Bir çerezi veya yoksa `null` adresini döndürür. +Çerezi veya mevcut değilse `null` döndürür. ```php $sessId = $httpRequest->getCookie('sess_id'); @@ -97,7 +97,7 @@ $sessId = $httpRequest->getCookie('sess_id'); getCookies(): array .[method] ----------------------------- -Tüm çerezleri döndürür: +Tüm çerezleri döndürür. ```php $cookies = $httpRequest->getCookies(); @@ -106,16 +106,16 @@ $cookies = $httpRequest->getCookies(); getMethod(): string .[method] ----------------------------- -İsteğin yapıldığı HTTP yöntemini döndürür. +İsteğin yapıldığı HTTP metodunu döndürür. ```php -echo $httpRequest->getMethod(); // GET, POST, HEAD, PUT +$httpRequest->getMethod(); // GET, POST, HEAD, PUT ``` isMethod(string $method): bool .[method] ---------------------------------------- -İsteğin hangi HTTP yöntemiyle yapıldığını kontrol eder. Parametre büyük/küçük harfe duyarlı değildir. +İsteğin yapıldığı HTTP metodunu test eder. Parametre büyük/küçük harfe duyarsızdır. ```php if ($httpRequest->isMethod('GET')) // ... @@ -124,7 +124,7 @@ if ($httpRequest->isMethod('GET')) // ... getHeader(string $header): ?string .[method] -------------------------------------------- -Bir HTTP başlığını veya yoksa `null` adresini döndürür. Parametre büyük/küçük harfe duyarlı değildir: +HTTP başlığını veya mevcut değilse `null` döndürür. Parametre büyük/küçük harfe duyarsızdır. ```php $userAgent = $httpRequest->getHeader('User-Agent'); @@ -133,7 +133,7 @@ $userAgent = $httpRequest->getHeader('User-Agent'); getHeaders(): array .[method] ----------------------------- -Tüm HTTP başlıklarını ilişkisel dizi olarak döndürür: +Tüm HTTP başlıklarını ilişkisel bir dizi olarak döndürür. ```php $headers = $httpRequest->getHeaders(); @@ -141,19 +141,14 @@ echo $headers['Content-Type']; ``` -getReferer(): ?Nette\Http\UrlImmutable .[method] ------------------------------------------------- -Kullanıcı hangi URL'den geldi? Dikkat edin, hiç güvenilir değil. - - isSecured(): bool .[method] --------------------------- -Bağlantı şifreli mi (HTTPS)? Düzgün işlevsellik için [bir proxy ayarlamanız |configuration#HTTP proxy] gerekebilir. +Bağlantı şifreli mi (HTTPS)? Doğru işlevsellik için [proxy ayarlanması |configuration#HTTP proxy] gerekebilir. isSameSite(): bool .[method] ---------------------------- -İstek aynı (alt) etki alanından mı geliyor ve bir bağlantıya tıklanarak mı başlatılıyor? Nette bunu tespit etmek için `_nss` çerezini (eski adıyla `nette-samesite`) kullanır. +İstek aynı (alt) alan adından mı geliyor ve bir bağlantıya tıklanarak mı başlatıldı? Nette algılama için `_nss` (önceden `nette-samesite`) çerezini kullanır. isAjax(): bool .[method] @@ -163,17 +158,17 @@ Bu bir AJAX isteği mi? getRemoteAddress(): ?string .[method] ------------------------------------- -Kullanıcının IP adresini döndürür. Düzgün işlevsellik için [bir proxy ayarlamanız |configuration#HTTP proxy] gerekebilir. +Kullanıcının IP adresini döndürür. Doğru işlevsellik için [proxy ayarlanması |configuration#HTTP proxy] gerekebilir. getRemoteHost(): ?string .[method deprecated] --------------------------------------------- -Kullanıcının IP adresinin DNS çevirisini döndürür. Düzgün işlevsellik için [bir proxy ayarlamanız |configuration#HTTP proxy] gerekebilir. +Kullanıcının IP adresinin DNS çözümlemesini döndürür. Doğru işlevsellik için [proxy ayarlanması |configuration#HTTP proxy] gerekebilir. -getBasicCredentials(): ?string .[method] ----------------------------------------- -[Temel HTTP kimlik doğrulama |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication] bilgilerini döndürür. +getBasicCredentials(): ?array .[method] +--------------------------------------- +[Basic HTTP authentication |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication] için kimlik doğrulama bilgilerini döndürür. ```php [$user, $password] = $httpRequest->getBasicCredentials(); @@ -182,7 +177,7 @@ getBasicCredentials(): ?string .[method] getRawBody(): ?string .[method] ------------------------------- -HTTP isteğinin gövdesini döndürür: +HTTP isteğinin gövdesini döndürür. ```php $body = $httpRequest->getRawBody(); @@ -191,54 +186,61 @@ $body = $httpRequest->getRawBody(); detectLanguage(array $langs): ?string .[method] ----------------------------------------------- -Dili algılar. Parametre olarak `$lang`, uygulamanın desteklediği bir dizi dili iletiriz ve tarayıcı tarafından tercih edileni döndürür. Bu sihirli değildir, yöntem sadece `Accept-Language` başlığını kullanır. Eşleşme bulunamazsa, `null` döndürür. +Dili algılar. Parametre olarak `$lang`, uygulamanın desteklediği dilleri içeren bir dizi iletiriz ve ziyaretçinin tarayıcısının en çok görmek isteyeceği dili döndürür. Bu sihir değil, sadece `Accept-Language` başlığı kullanılır. Eşleşme olmazsa `null` döndürür. ```php -// Tarayıcı tarafından gönderilen başlık: Accept-Language: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3 +// tarayıcı örneğin Accept-Language gönderir: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3 -$langs = ['hu', 'pl', 'en']; // uygulamada desteklenen diller +$langs = ['hu', 'pl', 'en']; // uygulama tarafından desteklenen diller echo $httpRequest->detectLanguage($langs); // en ``` -RequestFactory .[#toc-requestfactory] -===================================== +RequestFactory +============== -Geçerli HTTP isteğinin nesnesi [api:Nette\Http\RequestFactory] tarafından oluşturulur. DI konteyneri kullanmayan bir uygulama yazıyorsanız, aşağıdaki gibi bir istek oluşturursunuz: +[api:Nette\Http\RequestFactory] sınıfı, mevcut HTTP isteğini temsil eden bir `Nette\Http\Request` örneği oluşturmak için kullanılır. (Nette ile çalışıyorsanız, HTTP isteği nesnesi framework tarafından otomatik olarak oluşturulur.) ```php $factory = new Nette\Http\RequestFactory; $httpRequest = $factory->fromGlobals(); ``` -RequestFactory, `fromGlobals()` adresini çağırmadan önce yapılandırılabilir. `$factory->setBinary()` adresini kullanarak geçersiz UTF-8 dizilerinden gelen girdi parametrelerinin tüm sanitizasyonunu devre dışı bırakabiliriz. Ayrıca `$factory->setProxy(...)` adresini kullanarak kullanıcının IP adresinin doğru algılanması için önemli olan bir proxy sunucusu da kurabiliriz. +`fromGlobals()` metodu, mevcut PHP genel değişkenlerine (`$_GET`, `$_POST`, `$_COOKIE`, `$_FILES` ve `$_SERVER`) dayalı olarak bir istek nesnesi oluşturur. Nesneyi oluştururken, tüm GET, POST, COOKIE giriş parametrelerini ve ayrıca URL'yi kontrol karakterlerinden ve geçersiz UTF-8 dizilerinden otomatik olarak temizler, bu da bu verilerle daha fazla çalışırken güvenliği sağlar. + +RequestFactory, `fromGlobals()` çağrılmadan önce yapılandırılabilir: -Filtreler kullanarak URL'leri, diğer çeşitli web sitelerindeki kötü uygulanan yorum sistemleri nedeniyle içlerine girebilecek karakterlerden temizlemek mümkündür: +- `$factory->setBinary()` metoduyla, giriş parametrelerinin kontrol karakterlerinden ve geçersiz UTF-8 dizilerinden otomatik olarak temizlenmesini devre dışı bırakırsınız. +- `$factory->setProxy(...)` metoduyla, kullanıcının IP adresinin doğru algılanması için gerekli olan [proxy sunucusu |configuration#HTTP proxy] IP adresini belirtirsiniz. + +RequestFactory, URL isteğinin bölümlerini otomatik olarak dönüştüren filtreler tanımlamanıza olanak tanır. Bu filtreler, URL'den, örneğin çeşitli web sitelerindeki yorum sistemlerinin yanlış uygulanmasıyla oraya eklenebilecek istenmeyen karakterleri kaldırır: ```php -// yoldan boşlukları kaldırın +// yoldan boşlukların kaldırılması $requestFactory->urlFilters['path']['%20'] = ''; -// URL'nin sonundaki nokta, virgül veya sağ parantezi kaldırın +// URI sonundan nokta, virgül veya sağ parantezin kaldırılması $requestFactory->urlFilters['url']['[.,)]$'] = ''; -// yolu yinelenen bölü çizgilerinden temizle (varsayılan filtre) +// yolun çift eğik çizgilerden temizlenmesi (varsayılan filtre) $requestFactory->urlFilters['path']['/{2,}'] = '/'; ``` +İlk anahtar `'path'` veya `'url'`, filtrenin URL'nin hangi bölümüne uygulanacağını belirtir. İkinci anahtar, aranacak düzenli ifadedir ve değer, bulunan metnin yerine kullanılacak olan değiştirmedir. + -Yüklenen Dosyalar .[#toc-uploaded-files] -======================================== +Yüklenen Dosyalar +================= -`Nette\Http\Request::getFiles()` yöntemi normalleştirilmiş bir yapıda yükleme dosyalarından oluşan bir ağaç döndürür ve her yaprak [api:Nette\Http\FileUpload] öğesinin bir örneğidir. `<input type=file>` form öğesi. +`Nette\Http\Request::getFiles()` metodu, tüm yüklemelerin normalleştirilmiş bir yapıda bir dizisini döndürür, yaprakları [api:Nette\Http\FileUpload] nesneleridir. Bunlar, `<input type=file>` form elemanı tarafından gönderilen verileri kapsüller. -Yapı, HTML'deki öğelerin adlandırılmasını yansıtır. En basit örnekte bu, şu şekilde gönderilen tek bir adlandırılmış form öğesi olabilir: +Yapı, HTML'deki elemanların adlandırılmasını yansıtır. En basit durumda, bu, şu şekilde gönderilen tek bir adlandırılmış form elemanı olabilir: ```latte <input type="file" name="avatar"> ``` -Bu durumda, `$request->getFiles()` adresi dizi döndürür: +Bu durumda `$request->getFiles()` bir dizi döndürür: ```php [ @@ -246,19 +248,19 @@ Bu durumda, `$request->getFiles()` adresi dizi döndürür: ] ``` -Kullanıcı herhangi bir dosya yüklememiş veya yükleme başarısız olmuş olsa bile `FileUpload` nesnesi oluşturulur. Bir dosya gönderilmişse `hasFile()` yöntemi true değerini döndürür: +`FileUpload` nesnesi, kullanıcı hiçbir dosya göndermese veya gönderme başarısız olsa bile oluşturulur. Dosyanın gönderilip gönderilmediğini `hasFile()` metodu döndürür: ```php -$request->getFile('avatar')->hasFile(); +$request->getFile('avatar')?->hasFile(); ``` -Ad için dizi gösterimi kullanan bir girdi olması durumunda: +Dizi gösterimini kullanan eleman adı durumunda: ```latte <input type="file" name="my-form[details][avatar]"> ``` -Geri dönen ağaç bu şekilde görünür: +döndürülen ağaç şöyle görünür: ```php [ @@ -270,13 +272,13 @@ Geri dönen ağaç bu şekilde görünür: ] ``` -Ayrıca dosya dizileri de oluşturabilirsiniz: +Dosya dizileri de oluşturulabilir: ```latte -<input type="file" name="my-form[details][avatars][] multiple"> +<input type="file" name="my-form[details][avatars][]" multiple> ``` -Böyle bir durumda yapı şöyle görünür: +Bu durumda yapı şöyle görünür: ```php [ @@ -292,7 +294,7 @@ Böyle bir durumda yapı şöyle görünür: ] ``` -İç içe geçmiş bir dizinin 1. dizinine erişmenin en iyi yolu aşağıdaki gibidir: +İç içe geçmiş dizinin 1 indeksine erişmenin en iyi yolu şudur: ```php $file = $request->getFile(['my-form', 'details', 'avatars', 1]); @@ -301,31 +303,31 @@ if ($file instanceof FileUpload) { } ``` -Dışarıdan gelen verilere güvenemediğiniz ve bu nedenle yapının biçimine güvenmediğiniz için, bu yöntem `$request->getFiles()['my-form']['details']['avatars'][1]`bu da başarısız olabilir. +Dışarıdan gelen verilere güvenilemediği ve dolayısıyla dosya yapısının biçimine güvenilemediği için, bu yöntem, örneğin başarısız olabilecek `$request->getFiles()['my-form']['details']['avatars'][1]`'dan daha güvenlidir. -`FileUpload` Yöntemlerine Genel Bakış .{toc: FileUpload} --------------------------------------------------------- +`FileUpload` Metotlarına Genel Bakış .{toc: FileUpload} +------------------------------------------------------- hasFile(): bool .[method] ------------------------- -Kullanıcı bir dosya yüklediyse `true` döndürür. +Kullanıcı herhangi bir dosya yüklediyse `true` döndürür. isOk(): bool .[method] ---------------------- -Dosya başarıyla yüklenmişse `true` döndürür. +Dosya başarıyla yüklendiyse `true` döndürür. getError(): int .[method] ------------------------- -Yüklenen dosya ile ilişkili hata kodunu döndürür. [UPLOAD_ERR_XXX |http://php.net/manual/en/features.file-upload.errors.php] sabitlerinden biri olabilir. Dosya başarıyla yüklenmişse, `UPLOAD_ERR_OK` döndürür. +Dosya yükleme sırasındaki hata kodunu döndürür. Bu, [UPLOAD_ERR_XXX|http://php.net/manual/en/features.file-upload.errors.php] sabitlerinden biridir. Yükleme başarılı olursa `UPLOAD_ERR_OK` döndürür. move(string $dest) .[method] ---------------------------- -Yüklenen bir dosyayı yeni bir konuma taşır. Hedef dosya zaten mevcutsa, üzerine yazılır. +Yüklenen dosyayı yeni bir konuma taşır. Hedef dosya zaten varsa, üzerine yazılır. ```php $file->move('/path/to/files/name.ext'); @@ -334,36 +336,47 @@ $file->move('/path/to/files/name.ext'); getContents(): ?string .[method] -------------------------------- -Karşıya yüklenen dosyanın içeriğini döndürür. Yükleme başarılı olmadıysa, `null` döndürür. +Yüklenen dosyanın içeriğini döndürür. Yükleme başarılı olmadıysa `null` döndürür. getContentType(): ?string .[method] ----------------------------------- -İmzasına dayalı olarak yüklenen dosyanın MIME içerik türünü algılar. Yükleme başarılı olmadıysa veya algılama başarısız olduysa, `null` döndürür. +Yüklenen dosyanın MIME içerik türünü imzasına göre algılar. Yükleme başarılı olmadıysa veya algılama başarısız olursa `null` döndürür. .[caution] -PHP uzantısı gerektirir `fileinfo`. +PHP `fileinfo` uzantısını gerektirir. getUntrustedName(): string .[method] ------------------------------------ -Tarayıcı tarafından gönderilen orijinal dosya adını döndürür. +Tarayıcı tarafından gönderildiği şekliyle dosyanın orijinal adını döndürür. .[caution] -Bu yöntem tarafından döndürülen değere güvenmeyin. Bir istemci, uygulamanızı bozmak veya hacklemek amacıyla kötü amaçlı bir dosya adı gönderebilir. +Bu metodun döndürdüğü değere güvenmeyin. İstemci, uygulamanıza zarar vermek veya hacklemek amacıyla kötü niyetli bir dosya adı göndermiş olabilir. getSanitizedName(): string .[method] ------------------------------------ -Temizlenmiş dosya adını döndürür. Yalnızca ASCII karakterleri içerir `[a-zA-Z0-9.-]`. Ad bu tür karakterler içermiyorsa, 'bilinmeyen' döndürür. Dosya JPEG, PNG, GIF veya WebP görüntüsüyse, doğru dosya uzantısını döndürür. +Temizlenmiş dosya adını döndürür. Yalnızca ASCII karakterleri `[a-zA-Z0-9.-]` içerir. Ad bu tür karakterleri içermiyorsa `'unknown'` döndürür. Dosya JPEG, PNG, GIF, WebP veya AVIF biçiminde bir resimse, doğru uzantıyı da döndürür. + +.[caution] +PHP `fileinfo` uzantısını gerektirir. + + +getSuggestedExtension(): ?string .[method]{data-version:3.2.4} +-------------------------------------------------------------- +Algılanan MIME türüne karşılık gelen uygun dosya uzantısını (nokta olmadan) döndürür. + +.[caution] +PHP `fileinfo` uzantısını gerektirir. getUntrustedFullPath(): string .[method] ---------------------------------------- -Dizin yüklemesi sırasında tarayıcı tarafından gönderilen özgün tam yolu döndürür. Tam yol sadece PHP 8.1 ve üzeri sürümlerde mevcuttur. Önceki sürümlerde bu yöntem güvenilmeyen dosya ismini döndürür. +Klasör yüklenirken tarayıcı tarafından gönderildiği şekliyle dosyanın orijinal yolunu döndürür. Tam yol yalnızca PHP 8.1 ve sonraki sürümlerde kullanılabilir. Önceki sürümlerde bu metot dosyanın orijinal adını döndürür. .[caution] -Bu yöntem tarafından döndürülen değere güvenmeyin. Bir istemci, uygulamanızı bozmak veya hacklemek amacıyla kötü amaçlı bir dosya adı gönderebilir. +Bu metodun döndürdüğü değere güvenmeyin. İstemci, uygulamanıza zarar vermek veya hacklemek amacıyla kötü niyetli bir dosya adı göndermiş olabilir. getSize(): int .[method] @@ -373,22 +386,22 @@ Yüklenen dosyanın boyutunu döndürür. Yükleme başarılı olmadıysa `0` d getTemporaryFile(): string .[method] ------------------------------------ -Karşıya yüklenen dosyanın geçici konumunun yolunu döndürür. Yükleme başarılı olmadıysa, `''` döndürür. +Yüklenen dosyanın geçici konumunun yolunu döndürür. Yükleme başarılı olmadıysa `''` döndürür. isImage(): bool .[method] ------------------------- -Yüklenen dosya bir JPEG, PNG, GIF veya WebP görüntüsü ise `true` döndürür. Algılama, imzasına dayanır. Tüm dosyanın bütünlüğü kontrol edilmez. Bir görüntünün bozuk olup olmadığını örneğin [yüklemeyi |#toImage] deneyerek öğrenebilirsiniz. +Yüklenen dosya JPEG, PNG, GIF, WebP veya AVIF biçiminde bir resimse `true` döndürür. Algılama imzasına göre yapılır ve tüm dosyanın bütünlüğü doğrulanmaz. Bir resmin hasarlı olup olmadığını, örneğin onu [yüklemeye |#toImage] çalışarak belirleyebilirsiniz. .[caution] -PHP uzantısı gerektirir `fileinfo`. +PHP `fileinfo` uzantısını gerektirir. getImageSize(): ?array .[method] -------------------------------- -Bir çift döndürür `[width, height]` yüklenen resmin boyutlarıyla birlikte. Yükleme başarılı olmadıysa veya geçerli bir görüntü değilse, `null` döndürür. +Yüklenen resmin boyutlarını içeren `[genişlik, yükseklik]` çiftini döndürür. Yükleme başarılı olmadıysa veya geçerli bir resim değilse `null` döndürür. toImage(): Nette\Utils\Image .[method] -------------------------------------- -Bir resmi [Image |utils:images] nesnesi olarak yükler. Yükleme başarılı değilse veya geçerli bir resim değilse, bir `Nette\Utils\ImageException` istisnası atar. +Resmi [Image|utils:images] nesnesi olarak yükler. Yükleme başarılı olmadıysa veya geçerli bir resim değilse `Nette\Utils\ImageException` istisnası atar. diff --git a/http/tr/response.texy b/http/tr/response.texy index e0811471fc..a3d20eba24 100644 --- a/http/tr/response.texy +++ b/http/tr/response.texy @@ -2,22 +2,22 @@ HTTP Yanıtı *********** .[perex] -Nette, HTTP yanıtını anlaşılabilir bir API ile nesneler halinde kapsüllerken bir sanitizasyon filtresi sağlar. +Nette, HTTP yanıtını anlaşılır bir API'ye sahip nesneler içinde kapsüller. -HTTP yanıtı, [bağımlılık enjeksiyonu |dependency-injection:passing-dependencies] kullanarak geçirerek elde ettiğiniz bir [api:Nette\Http\Response] nesnesidir. Sunucularda basitçe `$httpResponse = $this->getHttpResponse()` adresini çağırın. +HTTP yanıtı [api:Nette\Http\Response] nesnesi tarafından temsil edilir. Nette ile çalışıyorsanız, bu nesne framework tarafından otomatik olarak oluşturulur ve [bağımlılık enjeksiyonu |dependency-injection:passing-dependencies] aracılığıyla size iletilmesini sağlayabilirsiniz. Presenter'larda sadece `$this->getHttpResponse()` metodunu çağırmanız yeterlidir. -→ [Kurulum ve gereksinimler |@home#Installation] +→ [Kurulum ve gereksinimler |@home#Kurulum] -Nette\Http\Yanıt .[#toc-nette-http-response] -============================================ +Nette\Http\Response +=================== - [Nette\Http\Request |request]'in aksine, bu nesne değişkendir, bu nedenle durumu değiştirmek, yani başlıkları göndermek için ayarlayıcıları kullanabilirsiniz. Tüm ayarlayıcıların **gerçek çıktı gönderilmeden önce çağrılması gerektiğini unutmayın.** `isSent()` yöntemi çıktının gönderilip gönderilmediğini söyler. Eğer `true` döndürürse, her başlık gönderme girişimi bir `Nette\InvalidStateException` istisnası fırlatır. +Nesne, [Nette\Http\Request|request] aksine değiştirilebilirdir (mutable), yani ayarlayıcılar kullanarak durumu değiştirebilirsiniz, örneğin başlıkları gönderebilirsiniz. Tüm ayarlayıcıların **herhangi bir çıktı gönderilmeden önce** çağrılması gerektiğini unutmayın. Çıktının zaten gönderilip gönderilmediğini `isSent()` metodu söyler. `true` döndürürse, başlık göndermeye yönelik her girişim `Nette\InvalidStateException` istisnası atar. -setCode(int $code, string $reason=null) .[method] -------------------------------------------------- -Bir durum [yanıt kodunu |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10] değiştirir. Daha iyi kaynak kodu okunabilirliği için gerçek sayılar yerine [önceden tanımlanmış sabitlerin |api:Nette\Http\IResponse] kullanılması önerilir. +setCode(int $code, ?string $reason=null) .[method] +-------------------------------------------------- +[Yanıt kodu |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10] durumunu değiştirir. Kaynak kodunun daha iyi anlaşılması için, kod için sayılar yerine [önceden tanımlanmış sabitler |api:Nette\Http\IResponse] kullanmanızı öneririz. ```php $httpResponse->setCode(Nette\Http\Response::S404_NotFound); @@ -31,12 +31,12 @@ Yanıtın durum kodunu döndürür. isSent(): bool .[method] ------------------------ -Başlıkların sunucudan tarayıcıya zaten gönderilmiş olup olmadığını döndürür, bu nedenle artık başlık göndermek veya durum kodunu değiştirmek mümkün değildir. +Başlıkların sunucudan tarayıcıya zaten gönderilip gönderilmediğini ve dolayısıyla artık başlık göndermenin veya durum kodunu değiştirmenin mümkün olup olmadığını döndürür. setHeader(string $name, string $value) .[method] ------------------------------------------------ -Bir HTTP başlığı gönderir ve daha önce gönderilen aynı adlı başlığın **üzerine yazar**. +Bir HTTP başlığı gönderir ve daha önce gönderilen aynı addaki başlığın **üzerine yazar**. ```php $httpResponse->setHeader('Pragma', 'no-cache'); @@ -45,7 +45,7 @@ $httpResponse->setHeader('Pragma', 'no-cache'); addHeader(string $name, string $value) .[method] ------------------------------------------------ -Bir HTTP başlığı gönderir ve **daha önce gönderilen aynı adlı başlığın** üzerine yazmaz. +Bir HTTP başlığı gönderir ve daha önce gönderilen aynı addaki başlığın **üzerine yazmaz**. ```php $httpResponse->addHeader('Accept', 'application/json'); @@ -55,12 +55,12 @@ $httpResponse->addHeader('Accept', 'application/xml'); deleteHeader(string $name) .[method] ------------------------------------ -Önceden gönderilmiş bir HTTP başlığını siler. +Daha önce gönderilen bir HTTP başlığını siler. getHeader(string $header): ?string .[method] -------------------------------------------- -Gönderilen HTTP başlığını veya yoksa `null` adresini döndürür. Parametre büyük/küçük harfe duyarlı değildir. +Gönderilen HTTP başlığını veya böyle bir başlık yoksa `null` döndürür. Parametre büyük/küçük harfe duyarsızdır. ```php $pragma = $httpResponse->getHeader('Pragma'); @@ -69,7 +69,7 @@ $pragma = $httpResponse->getHeader('Pragma'); getHeaders(): array .[method] ----------------------------- -Gönderilen tüm HTTP başlıklarını ilişkisel dizi olarak döndürür. +Gönderilen tüm HTTP başlıklarını ilişkisel bir dizi olarak döndürür. ```php $headers = $httpResponse->getHeaders(); @@ -77,9 +77,9 @@ echo $headers['Pragma']; ``` -setContentType(string $type, string $charset=null) .[method] ------------------------------------------------------------- -`Content-Type` başlığını gönderir. +setContentType(string $type, ?string $charset=null) .[method] +------------------------------------------------------------- +`Content-Type` başlığını değiştirir. ```php $httpResponse->setContentType('text/plain', 'UTF-8'); @@ -88,7 +88,7 @@ $httpResponse->setContentType('text/plain', 'UTF-8'); redirect(string $url, int $code=self::S302_Found): void .[method] ----------------------------------------------------------------- -Başka bir URL'ye yönlendirir. Daha sonra komut dosyasından çıkmayı unutmayın. +Başka bir URL'ye yönlendirir. Ardından betiği sonlandırmayı unutmayın. ```php $httpResponse->redirect('http://example.com'); @@ -98,52 +98,52 @@ exit; setExpiration(?string $time) .[method] -------------------------------------- -`Cache-Control` ve `Expires` başlıklarını kullanarak HTTP belgesinin sona erme süresini ayarlar. Parametre ya bir zaman aralığı (metin olarak) ya da önbelleğe almayı devre dışı bırakan `null` şeklindedir. +`Cache-Control` ve `Expires` başlıklarını kullanarak HTTP belgesinin sona erme süresini ayarlar. Parametre ya bir zaman aralığıdır (metin olarak) ya da `null`'dır, bu da önbelleğe almayı devre dışı bırakır. ```php -// tarayıcı önbelleğinin süresi bir saat içinde dolar +// tarayıcı önbelleği bir saat içinde sona erecek $httpResponse->setExpiration('1 hour'); ``` sendAsFile(string $fileName) .[method] -------------------------------------- -Yanıt, belirtilen adla *Farklı kaydet* iletişim kutusu ile indirilmelidir. Çıkışa herhangi bir dosya göndermez. +Yanıt, belirtilen ad altında *Farklı Kaydet* iletişim kutusu kullanılarak indirilecektir. Dosyanın kendisini göndermez. ```php -$httpResponse->sendAsFile('invoice.pdf'); +$httpResponse->sendAsFile('faktura.pdf'); ``` -setCookie(string $name, string $value, $time, string $path=null, string $domain=null, bool $secure=null, bool $httpOnly=null, string $sameSite=null) .[method] --------------------------------------------------------------------------------------------------------------------------------------------------------------- -Bir çerez gönderir. Varsayılan parametre değerleri: +setCookie(string $name, string $value, $time, ?string $path=null, ?string $domain=null, ?bool $secure=null, ?bool $httpOnly=null, ?string $sameSite=null) .[method] +------------------------------------------------------------------------------------------------------------------------------------------------------------------- +Bir çerez gönderir. Parametrelerin varsayılan değerleri: -| `$path` | `'/'` | (alt)etki alanı *(yapılandırılabilir)* üzerindeki tüm yolların kapsamı ile -| `$domain` | `null` | geçerli (alt) etki alanının kapsamı ile, ancak alt etki alanları ile değil *(yapılandırılabilir)* -| `$secure` | `true` | site HTTPS üzerinde çalışıyorsa, aksi takdirde `false` *(yapılandırılabilir)* -| `$httpOnly` | `true` | çerez JavaScript için erişilemez -| `$sameSite` | `'Lax'` | çerez [başka bir kaynaktan erişildiğinde |nette:glossary#SameSite cookie] gönderilmek zorunda değildir +| `$path` | `'/'` | çerez, (alt) alan adındaki tüm yollarda kapsama sahiptir *(yapılandırılabilir)* +| `$domain` | `null` | bu, geçerli (alt) alan adında kapsama sahip olduğu, ancak alt alan adlarında olmadığı anlamına gelir *(yapılandırılabilir)* +| `$secure` | `true` | web sitesi HTTPS üzerinde çalışıyorsa, aksi takdirde `false` *(yapılandırılabilir)* +| `$httpOnly` | `true` | çerez JavaScript için erişilemez +| `$sameSite` | `'Lax'` | çerez [başka bir alan adından erişim |nette:glossary#SameSite Çerezi] sırasında gönderilmeyebilir -`$path`, `$domain` ve `$secure` parametrelerinin varsayılan değerlerini [yapılandırma#HTTP çerezi |configuration#HTTP cookie] bölümünden değiştirebilirsiniz. +`$path`, `$domain` ve `$secure` parametrelerinin varsayılan değerlerini [yapılandırma |configuration#HTTP çerezi] içinde değiştirebilirsiniz. Zaman, saniye sayısı veya bir dize olarak belirtilebilir: ```php -$httpResponse->setCookie('lang', 'en', '100 days'); +$httpResponse->setCookie('lang', 'tr', '100 days'); // 'cs' changed to 'tr' as an example ``` -`$domain` seçeneği, hangi etki alanlarının (kökenlerin) çerezleri kabul edebileceğini belirler. Belirtilmezse, çerez, alt alan adları hariç olmak üzere, kendisi tarafından belirlenen aynı (alt) alan adı tarafından kabul edilir. Eğer `$domain` belirtilirse, alt alan adları da dahil edilir. Bu nedenle, `$domain` belirtmek, belirtmemekten daha az kısıtlayıcıdır. Örneğin, `$domain = 'nette.org'`, çerezi `doc.nette.org` gibi tüm alt alan adlarında da kullanılabilir. +`$domain` parametresi, hangi alan adlarının çerezi kabul edebileceğini belirtir. Belirtilmezse, çerez onu ayarlayan aynı (alt) alan adı tarafından kabul edilir, ancak alt alan adları tarafından değil. `$domain` belirtilirse, alt alan adları da dahil edilir. Bu nedenle, `$domain` belirtmek, atlamaktan daha az kısıtlayıcıdır. Örneğin, `$domain = 'nette.org'` ile çerezler `doc.nette.org` gibi tüm alt alan adlarında da kullanılabilir. -`$sameSite` değeri için `Response::SameSiteLax`, `SameSiteStrict` ve `SameSiteNone` sabitlerini kullanabilirsiniz. +`$sameSite` değeri için `Response::SameSiteLax`, `Response::SameSiteStrict` ve `Response::SameSiteNone` sabitlerini kullanabilirsiniz. -deleteCookie(string $name, string $path=null, string $domain=null, bool $secure=null): void .[method] ------------------------------------------------------------------------------------------------------ +deleteCookie(string $name, ?string $path=null, ?string $domain=null, ?bool $secure=null): void .[method] +-------------------------------------------------------------------------------------------------------- Bir çerezi siler. Parametrelerin varsayılan değerleri şunlardır: -- `$path` tüm dizinleri kapsayacak şekilde (`'/'`) -- `$domain` geçerli (alt) etki alanının kapsamı ile, ancak alt etki alanları ile değil -- `$secure` [yapılandırma#HTTP çerezi |configuration#HTTP cookie] içindeki ayarlardan etkilenir +- `$path` tüm dizinlerde kapsama sahip (`'/'`) +- `$domain` geçerli (alt) alan adında kapsama sahip, ancak alt alan adlarında değil +- `$secure`, [yapılandırma |configuration#HTTP çerezi] içindeki ayarlara göre yönetilir ```php $httpResponse->deleteCookie('lang'); diff --git a/http/tr/sessions.texy b/http/tr/sessions.texy index f7e7a1450c..2f3e3cbb2f 100644 --- a/http/tr/sessions.texy +++ b/http/tr/sessions.texy @@ -1,71 +1,71 @@ -Oturumlar -********* +Oturumlar (Sessions) +******************** <div class=perex> -HTTP durumsuz bir protokoldür, ancak neredeyse her uygulamanın istekler arasında durum tutması gerekir, örneğin bir alışveriş sepetinin içeriği. Oturum bunun için kullanılır. Bakalım +HTTP durumsuz bir protokoldür, ancak neredeyse her uygulamanın istekler arasında durumu koruması gerekir, örneğin alışveriş sepetinin içeriği. İşte bu noktada oturumlar devreye girer. Göstereceğimiz konular: -- oturumlar nasil kullanilir -- adlandirma çatişmalarindan nasil kaçinilir -- son kullanma tari̇hi̇ nasil ayarlanir +- oturumları nasıl kullanacağınız +- isim çakışmalarını nasıl önleyeceğiniz +- sona erme süresini nasıl ayarlayacağınız </div> -Oturumları kullanırken, her kullanıcı oturum kimliği adı verilen ve bir çerezde aktarılan benzersiz bir tanımlayıcı alır. Bu, oturum verilerinin anahtarı olarak işlev görür. Tarayıcı tarafında saklanan çerezlerin aksine, oturum verileri sunucu tarafında saklanır. +Oturumları kullanırken, her kullanıcıya oturum kimliği adı verilen benzersiz bir tanımlayıcı verilir ve bu tanımlayıcı bir çerezde iletilir. Bu, oturum verileri için bir anahtar görevi görür. Tarayıcı tarafında saklanan çerezlerin aksine, oturum verileri sunucu tarafında saklanır. -[Yapılandırmada |configuration#session] oturumu [yapılandırırız |configuration#session], sona erme süresinin seçimi önemlidir. +Oturumu [yapılandırma |configuration#Oturum Session] içinde ayarlarız, özellikle sona erme süresi seçeneği önemlidir. -Oturum, [bağımlılık enjeksiyonu |dependency-injection:passing-dependencies] kullanarak aktardığınız [api:Nette\Http\Session] nesnesi tarafından yönetilir. Sunucularda `$session = $this->getSession()` adresini çağırmanız yeterlidir. +Oturum yönetimi [api:Nette\Http\Session] nesnesi tarafından gerçekleştirilir. Bu nesneye [bağımlılık enjeksiyonu |dependency-injection:passing-dependencies] aracılığıyla erişebilirsiniz. Presenter'larda sadece `$session = $this->getSession()` çağırmanız yeterlidir. -→ [Kurulum ve gereksinimler |@home#Installation] +→ [Kurulum ve gereksinimler |@home#Kurulum] -Başlangıç Oturumu .[#toc-starting-session] -========================================== +Oturumu Başlatma +================ -Varsayılan olarak, Nette bir oturumdan okumaya veya ona veri yazmaya başladığımız anda otomatik olarak bir oturum başlatacaktır. Bir oturumu manuel olarak başlatmak için `$session->start()` adresini kullanın. +Nette, varsayılan olarak oturumu okumaya veya veri yazmaya başladığımızda otomatik olarak başlatır. Oturum manuel olarak `$session->start()` ile başlatılır. -PHP, oturumu başlatırken önbelleğe almayı etkileyen HTTP başlıkları gönderir, bkz. [php:session_cache_limiter], ve muhtemelen oturum kimliğini içeren bir çerez. Bu nedenle, tarayıcıya herhangi bir çıktı göndermeden önce oturumu başlatmak her zaman gereklidir, aksi takdirde bir istisna fırlatılır. Bu nedenle, sayfa oluşturma sırasında bir oturumun kullanılacağını biliyorsanız, oturumu daha önce manuel olarak, örneğin sunucuda başlatın. +PHP, oturum başlatıldığında önbelleğe almayı etkileyen HTTP başlıklarını gönderir, bkz. [php:session_cache_limiter], ve muhtemelen oturum kimliği içeren bir çerez de gönderir. Bu nedenle, herhangi bir çıktıyı tarayıcıya göndermeden önce oturumu her zaman başlatmak gerekir, aksi takdirde bir istisna atılır. Bu nedenle, sayfa oluşturma sırasında oturumun kullanılacağını biliyorsanız, önceden manuel olarak başlatın, örneğin presenter'da. -Geliştirici modunda, Tracy oturumu başlatır çünkü Tracy Bar'da yeniden yönlendirme ve AJAX istekleri çubuklarını görüntülemek için kullanır. +Geliştirme modunda, Tracy oturumu başlatır çünkü onu Tracy Bar'daki yönlendirme ve AJAX istekleri çubuklarını görüntülemek için kullanır. -Bölüm .[#toc-section] -===================== +Bölümler +======== -Saf PHP'de oturum veri deposu, `$_SESSION` global değişkeni aracılığıyla erişilebilen bir dizi olarak uygulanır. Sorun şu ki, uygulamalar normalde bir dizi bağımsız parçadan oluşur ve hepsinde yalnızca aynı dizi mevcutsa, er ya da geç bir isim çakışması meydana gelecektir. +Saf PHP'de, oturum veri deposu `$_SESSION` genel değişkeni aracılığıyla erişilebilen bir dizi olarak gerçekleştirilir. Sorun şu ki, uygulamalar genellikle birbirine bağlı olmayan bir dizi parçadan oluşur ve hepsi yalnızca bir diziye erişebiliyorsa, er ya da geç bir isim çakışması meydana gelir. -Nette Framework bu sorunu tüm alanı bölümlere ayırarak çözer (nesneler [api:Nette\Http\SessionSection]). Her birim daha sonra benzersiz bir adla kendi bölümünü kullanır ve hiçbir çarpışma meydana gelmez. +Nette Framework, tüm alanı bölümlere ( [api:Nette\Http\SessionSection] nesneleri) ayırarak sorunu çözer. Her birim daha sonra benzersiz bir ada sahip kendi bölümünü kullanır ve artık çakışma olamaz. -Bölümü oturum yöneticisinden alıyoruz: +Bölümü oturumdan alırız: ```php -$section = $session->getSection('unique name'); +$section = $session->getSection('benzersiz_isim'); // 'unikatni nazev' translated ``` -Sunucuda `getSession()` adresini parametre ile çağırmak yeterlidir: +Presenter'da sadece parametre ile `getSession()` kullanın: ```php -// $this Sunucu -$section = $this->getSession('unique name'); +// $this bir Presenter'dır +$section = $this->getSession('benzersiz_isim'); // 'unikatni nazev' translated ``` -Kesitin varlığı `$session->hasSection('unique name')` yöntemi ile kontrol edilebilir. +Bir bölümün varlığı `$session->hasSection('benzersiz_isim')` metoduyla kontrol edilebilir. -Bölümün kendisiyle `set()`, `get()` ve `remove()` yöntemlerini kullanarak çalışmak çok kolaydır: +Bölümün kendisiyle çalışmak daha sonra `set()`, `get()` ve `remove()` metotlarıyla çok kolaydır: ```php -// değişken yazımı +// değişken yazma $section->set('userName', 'franta'); -// bir değişkeni okuma, mevcut değilse null döndürür +// değişken okuma, yoksa null döndürür echo $section->get('userName'); -// değişken kaldırma +// değişkeni kaldırma $section->remove('userName'); ``` -Tüm değişkenleri bölümden elde etmek için `foreach` döngüsünü kullanmak mümkündür: +Bölümdeki tüm değişkenleri almak için `foreach` döngüsü kullanılabilir: ```php foreach ($section as $key => $val) { @@ -74,63 +74,63 @@ foreach ($section as $key => $val) { ``` -Son Kullanma Tarihi Nasıl Ayarlanır .[#toc-how-to-set-expiration] ------------------------------------------------------------------ +Sona Erme Süresini Ayarlama +--------------------------- -Süre sonu, tek tek bölümler ve hatta tek tek değişkenler için ayarlanabilir. Kullanıcının oturum açma süresinin 20 dakika içinde sona ermesine izin verebiliriz, ancak yine de bir alışveriş sepetinin içeriğini hatırlayabiliriz. +Tek tek bölümler veya hatta tek tek değişkenler için sona erme süresi ayarlamak mümkündür. Böylece kullanıcının oturum açma süresini 20 dakika sonra sona erdirebilir, ancak sepetin içeriğini hatırlamaya devam edebiliriz. ```php -// bölüm 20 dakika sonra sona erecektir +// bölüm 20 dakika sonra sona erecek $section->setExpiration('20 minutes'); ``` -`set()` yönteminin üçüncü parametresi, bireysel değişkenlerin sona erme süresini ayarlamak için kullanılır: +Tek tek değişkenler için sona erme süresini ayarlamak için `set()` metodunun üçüncü parametresi kullanılır: ```php -// 'flash' değişkeninin süresi 30 saniye sonra dolar +// 'flash' değişkeni 30 saniye sonra sona erecek $section->set('flash', $message, '30 seconds'); ``` .[note] -Tüm oturumun sona erme süresinin (bkz. oturum [yapılandırması |configuration#session]) tek tek bölümler veya değişkenler için ayarlanan süreye eşit veya daha yüksek olması gerektiğini unutmayın. +Tüm oturumun sona erme süresinin (bkz. [oturum yapılandırması |configuration#Oturum Session]) tek tek bölümler veya değişkenler için ayarlanan süreye eşit veya daha uzun olması gerektiğini unutmayın. -Daha önce ayarlanmış olan sürenin iptali `removeExpiration()` yöntemi ile gerçekleştirilebilir. Tüm bölümün derhal silinmesi `remove()` yöntemi ile sağlanacaktır. +Daha önce ayarlanan sona erme süresinin iptali `removeExpiration()` metoduyla sağlanır. Tüm bölümün anında iptali `remove()` metoduyla sağlanır. -Olaylar $onStart, $onBeforeWrite .[#toc-events-onstart-onbeforewrite] ---------------------------------------------------------------------- +$onStart, $onBeforeWrite Olayları +--------------------------------- -Object `Nette\Http\Session` `$onStart` a `$onBeforeWrite`[olaylarına |nette:glossary#Events] sahiptir, böylece oturum başladıktan sonra veya diske yazılıp sonlandırılmadan önce çağrılan geri aramalar ekleyebilirsiniz. +`Nette\Http\Session` nesnesinin [olaylar |nette:glossary#Olaylar Events] `$onStart` ve `$onBeforeWrite` vardır, bu nedenle oturum başlatıldıktan sonra veya diske yazılmadan ve ardından sonlandırılmadan önce çağrılacak geri aramalar ekleyebilirsiniz. ```php $session->onBeforeWrite[] = function () { - // oturuma veri yazma + // oturum verilerini yazacağız $this->section->set('basket', $this->basket); }; ``` -Oturum Yönetimi .[#toc-session-management] -========================================== +Oturum Yönetimi +=============== -Oturum yönetimi için `Nette\Http\Session` sınıfının yöntemlerine genel bakış: +Oturum yönetimi için `Nette\Http\Session` sınıfının metotlarına genel bakış: <div class=wiki-methods-brief> start(): void .[method] ----------------------- -Bir oturum başlatır. +Oturumu başlatır. isStarted(): bool .[method] --------------------------- -Oturum başladı mı? +Oturum başlatıldı mı? close(): void .[method] ----------------------- -Oturumu sonlandırır. Oturum, komut dosyasının sonunda otomatik olarak sona erer. +Oturumu sonlandırır. Oturum, betiğin çalışması sonunda otomatik olarak sonlandırılır. destroy(): void .[method] @@ -140,12 +140,12 @@ Oturumu sonlandırır ve siler. exists(): bool .[method] ------------------------ -HTTP isteği oturum kimliğine sahip bir çerez içeriyor mu? +HTTP isteği oturum kimliği içeren bir çerez içeriyor mu? regenerateId(): void .[method] ------------------------------ -Yeni bir rastgele oturum kimliği oluşturur. Veriler değişmeden kalır. +Yeni rastgele bir oturum kimliği oluşturur. Veriler korunur. getId(): string .[method] @@ -155,56 +155,57 @@ Oturum kimliğini döndürür. </div> -Konfigürasyon .[#toc-configuration] ------------------------------------ +Yapılandırma +------------ -Oturumu [yapılandırmada yapılandırıyoruz |configuration#session]. DI konteyneri kullanmayan bir uygulama yazıyorsanız, yapılandırmak için bu yöntemleri kullanın. Oturum başlatılmadan önce çağrılmalıdırlar. +Oturumu [yapılandırmada |configuration#Oturum Session] ayarlarız. DI konteyneri kullanmayan bir uygulama yazıyorsanız, yapılandırma için şu metotlar kullanılır. Oturum başlatılmadan önce çağrılmalıdırlar. <div class=wiki-methods-brief> setName(string $name): static .[method] --------------------------------------- -Oturum kimliğini iletmek için kullanılan çerezin adını ayarlar. Varsayılan ad `PHPSESSID` şeklin</div>dedir.<div class=wiki-methods-brief>Bu, aynı sitede birkaç farklı uygulama çalıştırıyorsanız kullanışlıdır. +Oturum kimliğinin iletildiği çerezin adını ayarlar. Standart ad `PHPSESSID`'dir. Aynı web sitesi içinde birkaç farklı uygulama çalıştırıyorsanız kullanışlıdır. getName(): string .[method] --------------------------- -Oturum çerezinin adını döndürür. +Oturum kimliğinin iletildiği çerezin adını döndürür. setOptions(array $options): static .[method] -------------------------------------------- -Oturumu yapılandırır. Tüm PHP [oturum yönergelerini |https://www.php.net/manual/en/session.configuration.php] ayarlamak (camelCase biçiminde, örneğin `session.save_path` yerine `savePath` yazmak) ve ayrıca [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters] yapm</div>ak<div class=wiki-methods-brief>mümkündür. +Oturumu yapılandırır. Tüm PHP [oturum yönergeleri |https://www.php.net/manual/en/session.configuration.php] (camelCase biçiminde, ör. `session.save_path` yerine `savePath` yazılır) ve ayrıca [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters] ayarlanabilir. setExpiration(?string $time): static .[method] ---------------------------------------------- -Oturumun sona ereceği hareketsizlik süresini ayarlar. +Oturumun sona ereceği etkinlik dışı kalma süresini ayarlar. -setCookieParameters(string $path, string $domain=null, bool $secure=null, string $samesite=null): static .[method] ------------------------------------------------------------------------------------------------------------------- -Çerezler için parametreleri ayarlar. Varsayılan parametre değerlerini [configuration#Session cookie |configuration#Session cookie] bölümünden değiştirebilirsiniz. +setCookieParameters(string $path, ?string $domain=null, ?bool $secure=null, ?string $samesite=null): static .[method] +--------------------------------------------------------------------------------------------------------------------- +Çerez parametrelerini ayarlar. Parametrelerin varsayılan değerlerini [yapılandırmada |configuration#Oturum çerezi] değiştirebilirsiniz. setSavePath(string $path): static .[method] ------------------------------------------- -Oturum dosyalarının depolandığı dizini ayarlar. +Oturum dosyalarının saklandığı dizini ayarlar. setHandler(\SessionHandlerInterface $handler): static .[method] --------------------------------------------------------------- -Özel işleyici ayarlar, [PHP belgelerine |https://www.php.net/manual/en/class.sessionhandlerinterface.php] bakınız. +Özel bir işleyici ayarlar, bkz. [PHP belgeleri |https://www.php.net/manual/en/class.sessionhandlerinterface.php]. </div> -Önce Güvenlik .[#toc-safety-first] -================================== +Önce Güvenlik +============= -Sunucu, istekler aynı oturum kimliğini içerdiği sürece aynı kullanıcıyla iletişim kurduğunu varsayar. Güvenlik mekanizmalarının görevi, bu davranışın gerçekten çalıştığından ve bir tanımlayıcıyı değiştirme veya çalma olasılığının olmadığından emin olmaktır. +Sunucu, istekler aynı oturum kimliğiyle eşlik ettiği sürece sürekli olarak aynı kullanıcıyla iletişim kurduğunu varsayar. Güvenlik mekanizmalarının görevi, bunun gerçekten böyle olmasını ve tanımlayıcının çalınmasının veya sahtesinin yapılmasının mümkün olmamasını sağlamaktır. -Bu nedenle Nette Framework, oturum kimliğini yalnızca çerezlerde aktarmak, JavaScript'ten erişimi önlemek ve URL'deki tanımlayıcıları yok saymak için PHP yönergelerini uygun şekilde yapılandırır. Dahası, kullanıcı girişi gibi kritik anlarda yeni bir Oturum Kimliği oluşturur. +Nette Framework bu nedenle PHP yönergelerini, oturum kimliğini yalnızca çerezde iletecek, JavaScript'e erişilemez hale getirecek ve URL'deki olası tanımlayıcıları yok sayacak şekilde doğru bir şekilde yapılandırır. Ayrıca, kullanıcının oturum açması gibi kritik anlarda yeni bir oturum kimliği oluşturur. -PHP'yi yapılandırmak için ini_set işlevi kullanılır, ancak ne yazık ki bazı web barındırma hizmetlerinde kullanımı yasaktır. Eğer sizin durumunuz buysa, barındırma sağlayıcınızdan bu işleve izin vermesini ya da en azından sunucusunu düzgün bir şekilde yapılandırmasını isteyin. .[note] +.[note] +PHP yapılandırması için ini_set fonksiyonu kullanılır, ancak maalesef bazı hostingler bunu yasaklar. Bu sizin hostinginiz için de geçerliyse, onlarla fonksiyonu etkinleştirmelerini veya en azından sunucuyu yapılandırmalarını istemeyi deneyin. diff --git a/http/tr/urls.texy b/http/tr/urls.texy index 2cf4fe5d8b..232879609c 100644 --- a/http/tr/urls.texy +++ b/http/tr/urls.texy @@ -1,19 +1,19 @@ -URL Ayrıştırıcı ve Oluşturucu -***************************** +URL'lerle Çalışma +***************** .[perex] -[Url |#Url], [UrlImmutable |#UrlImmutable] ve [UrlScript |#UrlScript] sınıfları URL'leri yönetmeyi, ayrıştırmayı ve değiştirmeyi kolaylaştırır. +[#Url], [#UrlImmutable] ve [#UrlScript] sınıfları, URL'leri kolayca oluşturmayı, ayrıştırmayı ve işlemeyi sağlar. -→ [Kurulum ve gereksinimler |@home#Installation] +→ [Kurulum ve gereksinimler |@home#Kurulum] Url === -[api:Nette\Http\Url] sınıfı, URL ve bu diyagramda ana hatlarıyla belirtilen bileşenleriyle çalışmayı kolaylaştırır: +[api:Nette\Http\Url] sınıfı, URL'ler ve bu çizimde yakalanan tek tek bileşenleriyle kolayca çalışmanıza olanak tanır: /--pre - scheme user password host port path query fragment + şema kullanıcı şifre ana bilgisayar port yol sorgu fragment | | | | | | | | /--\ /--\ /------\ /-------\ /--\/----------\ /--------\ /----\ <b>http://john:xyz%2A12@nette.org:8080/en/download?name=param#footer</b> @@ -36,7 +36,7 @@ $url->setScheme('https') echo $url; // 'https://localhost/edit?foo=bar' ``` -Ayrıca URL'yi ayrıştırabilir ve ardından değiştirebilirsiniz: +Ayrıca bir URL'yi ayrıştırabilir ve daha fazla işleyebilirsiniz: ```php $url = new Url( @@ -44,62 +44,94 @@ $url = new Url( ); ``` -Tek tek URL bileşenlerini almak veya değiştirmek için aşağıdaki yöntemler kullanılabilir: +`Url` sınıfı `JsonSerializable` arayüzünü uygular ve `__toString()` metoduna sahiptir, böylece nesne yazdırılabilir veya `json_encode()`'a iletilen verilerde kullanılabilir. + +```php +echo $url; +echo json_encode([$url]); +``` + + +URL Bileşenleri .[method] +------------------------- + +Tek tek URL bileşenlerini döndürmek veya değiştirmek için şu metotlar kullanılabilir: .[language-php] -| Ayarlayıcı | Getirici | Dönen değer +| Ayarlayıcı | Alıcı | Döndürülen değer |-------------------------------------------------------------------------------------------- -| `setScheme(string $scheme)`| `getScheme(): string`| `'http'` -| `setUser(string $user)`| `getUser(): string`| `'john'` -| `setPassword(string $password)`| `getPassword(): string`| `'xyz*12'` -| `setHost(string $host)`| `getHost(): string`| `'nette.org'` -| `setPort(int $port)`| `getPort(): ?int`| `8080` -| | `getDefaultPort(): ?int`| `80` -| `setPath(string $path)`| `getPath(): string`| `'/en/download'` -| `setQuery(string\|array $query)`| `getQuery(): string`| `'name=param'` -| `setFragment(string $fragment)`| `getFragment(): string`| `'footer'` -| | `getAuthority(): string`| `'nette.org:8080'` -| | `getHostUrl(): string`| `'http://nette.org:8080'` -| | `getAbsoluteUrl(): string` | tam URL - -Ayrıca tek tek sorgu parametreleri ile de işlem yapabiliriz: +| `setScheme(string $scheme)` | `getScheme(): string` | `'http'` +| `setUser(string $user)` | `getUser(): string` | `'john'` +| `setPassword(string $password)` | `getPassword(): string` | `'xyz*12'` +| `setHost(string $host)` | `getHost(): string` | `'nette.org'` +| `setPort(int $port)` | `getPort(): ?int` | `8080` +| | `getDefaultPort(): ?int` | `80` +| `setPath(string $path)` | `getPath(): string` | `'/en/download'` +| `setQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` +| `setFragment(string $fragment)` | `getFragment(): string` | `'footer'` +| | `getAuthority(): string` | `'john:xyz%2A12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | tüm URL + +Uyarı: [HTTP isteğinden |request] alınan bir URL ile çalışırken, tarayıcı sunucuya göndermediği için fragment içermeyeceğini unutmayın. + +Ayrıca tek tek sorgu parametreleriyle de çalışabiliriz: .[language-php] -| Ayarlayıcı | Getirici +| Ayarlayıcı | Alıcı |--------------------------------------------------- -| `setQuery(string\|array $query)` | `getQueryParameters(): array` -| `setQueryParameter(string $name, $val)`| `getQueryParameter(string $name)` +| `setQuery(string\|array $query)` | `getQueryParameters(): array` +| `setQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` -`getDomain(int $level = 2)` yöntemi, ana bilgisayarın sağ veya sol kısmını döndürür. Ana bilgisayar `www.nette.org` ise bu şekilde çalışır: + +getDomain(int $level = 2): string .[method] +------------------------------------------- +Ana bilgisayarın sağ veya sol kısmını döndürür. Ana bilgisayar `www.nette.org` ise şu şekilde çalışır: .[language-php] -| `getDomain(1)` | `'org'` -| `getDomain(2)` | `'nette.org'` -| `getDomain(3)` | `'www.nette.org'` -| `getDomain(0)` | `'www.nette.org'` -| `getDomain(-1)` | `'www.nette'` -| `getDomain(-2)` | `'www'` -| `getDomain(-3)` | `''` +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `''` -`Url` sınıfı, `JsonSerializable` arayüzünü uygular ve nesnenin yazdırılabilmesi veya `json_encode()` adresine aktarılan verilerde kullanılabilmesi için bir `__toString()` yöntemine sahiptir. +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +İki URL'nin aynı olup olmadığını doğrular. ```php -echo $url; -echo json_encode([$url]); +$url->isEqual('https://nette.org'); ``` -Yöntem `isEqual(string|Url $anotherUrl): bool` iki URL'nin aynı olup olmadığını test eder. + +Url::isAbsolute(string $url): bool .[method]{data-version:3.3.2} +---------------------------------------------------------------- +Bir URL'nin mutlak olup olmadığını doğrular. Bir URL, bir şema (ör. http, https, ftp) ve ardından iki nokta üst üste ile başlıyorsa mutlak kabul edilir. ```php -$url->isEqual('https://nette.org'); +Url::isAbsolute('https://nette.org'); // true +Url::isAbsolute('//nette.org'); // false +``` + + +Url::removeDotSegments(string $path): string .[method]{data-version:3.3.2} +-------------------------------------------------------------------------- +Özel `.` ve `..` segmentlerini kaldırarak URL'deki yolu normalleştirir. Metot, gereksiz yol öğelerini web tarayıcılarının yaptığı gibi kaldırır. + +```php +Url::removeDotSegments('/path/../subtree/./file.txt'); // '/subtree/file.txt' +Url::removeDotSegments('/../foo/./bar'); // '/foo/bar' +Url::removeDotSegments('./today/../file.txt'); // 'file.txt' ``` -UrlImmutable .[#toc-urlimmutable] -================================= +UrlImmutable +============ -[api:Nette\Http\UrlImmutable] sınıfı `Url` sınıfına değişmez bir alternatiftir (tıpkı PHP'de `DateTimeImmutable` sınıfının `DateTime` sınıfına değişmez bir alternatif olması gibi). Ayarlayıcılar yerine, nesneyi değiştirmeyen, ancak değiştirilmiş bir değerle yeni örnekler döndüren sözde solduruculara sahiptir: +[api:Nette\Http\UrlImmutable] sınıfı, [#Url] sınıfının değişmez (immutable) bir alternatifidir (PHP'deki `DateTimeImmutable`'ın `DateTime`'ın değişmez alternatifi olması gibi). Ayarlayıcılar yerine, nesneyi değiştirmeyen ancak değiştirilmiş bir değere sahip yeni örnekler döndüren wither'lara sahiptir: ```php use Nette\Http\UrlImmutable; @@ -111,53 +143,96 @@ $url = new UrlImmutable( $newUrl = $url ->withUser('') ->withPassword('') - ->withPath('/en/'); + ->withPath('/cs/'); + +echo $newUrl; // 'http://john:xyz%2A12@nette.org:8080/cs/?name=param#footer' +``` + +`UrlImmutable` sınıfı `JsonSerializable` arayüzünü uygular ve `__toString()` metoduna sahiptir, böylece nesne yazdırılabilir veya `json_encode()`'a iletilen verilerde kullanılabilir. -echo $newUrl; // 'http://nette.org:8080/en/?name=param#footer' +```php +echo $url; +echo json_encode([$url]); ``` -Tek tek URL bileşenlerini almak veya değiştirmek için aşağıdaki yöntemler kullanılabilir: + +URL Bileşenleri .[method] +------------------------- + +Tek tek URL bileşenlerini döndürmek veya değiştirmek için şu metotlar kullanılır: .[language-php] -| Wither | Getter | Dönen değer +| Wither | Alıcı | Döndürülen değer |-------------------------------------------------------------------------------------------- -| `withScheme(string $scheme)`| `getScheme(): string`| `'http'` -| `withUser(string $user)`| `getUser(): string`| `'john'` -| `withPassword(string $password)`| `getPassword(): string`| `'xyz*12'` -| `withHost(string $host)`| `getHost(): string`| `'nette.org'` -| `withPort(int $port)`| `getPort(): ?int`| `8080` -| | `getDefaultPort(): ?int`| `80` -| `withPath(string $path)`| `getPath(): string`| `'/en/download'` -| `withQuery(string\|array $query)`| `getQuery(): string`| `'name=param'` -| `withFragment(string $fragment)`| `getFragment(): string`| `'footer'` -| | `getAuthority(): string`| `'nette.org:8080'` -| | `getHostUrl(): string`| `'http://nette.org:8080'` -| | `getAbsoluteUrl(): string` | tam URL - -Ayrıca tek tek sorgu parametreleri ile de işlem yapabiliriz: +| `withScheme(string $scheme)` | `getScheme(): string` | `'http'` +| `withUser(string $user)` | `getUser(): string` | `'john'` +| `withPassword(string $password)` | `getPassword(): string` | `'xyz*12'` +| `withHost(string $host)` | `getHost(): string` | `'nette.org'` +| `withPort(int $port)` | `getPort(): ?int` | `8080` +| | `getDefaultPort(): ?int` | `80` +| `withPath(string $path)` | `getPath(): string` | `'/en/download'` +| `withQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` +| `withFragment(string $fragment)` | `getFragment(): string` | `'footer'` +| | `getAuthority(): string` | `'john:xyz%2A12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | tüm URL + +`withoutUserInfo()` metodu `user` ve `password`'ü kaldırır. + +Ayrıca tek tek sorgu parametreleriyle de çalışabiliriz: .[language-php] -| Solan | Getter +| Wither | Alıcı |----------------------------------------------- -| `withQuery(string\|array $query)` | `getQueryParameters(): array` -| `withQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` +| `withQuery(string\|array $query)` | `getQueryParameters(): array` +| `withQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` -`getDomain(int $level = 2)` yöntemi `Url`'daki yöntemle aynı şekilde çalışır. `withoutUserInfo()` yöntemi `user` ve `password` adreslerini kaldırır. -`UrlImmutable` sınıfı, `JsonSerializable` arayüzünü uygular ve nesnenin yazdırılabilmesi veya `json_encode()` adresine aktarılan verilerde kullanılabilmesi için bir `__toString()` yöntemine sahiptir. +getDomain(int $level = 2): string .[method] +------------------------------------------- +Ana bilgisayarın sağ veya sol kısmını döndürür. Ana bilgisayar `www.nette.org` ise şu şekilde çalışır: + +.[language-php] +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `''` + + +resolve(string $reference): UrlImmutable .[method]{data-version:3.3.2} +---------------------------------------------------------------------- +Mutlak URL'yi tarayıcının HTML sayfasındaki bağlantıları işlediği gibi türetir: +- bağlantı mutlak bir URL ise (şema içeriyorsa), değişiklik yapılmadan kullanılır +- bağlantı `//` ile başlıyorsa, yalnızca geçerli URL'den şema alınır +- bağlantı `/` ile başlıyorsa, alan adının kökünden mutlak bir yol oluşturulur +- diğer durumlarda, URL geçerli yola göre göreceli olarak oluşturulur ```php -echo $url; -echo json_encode([$url]); +$url = new UrlImmutable('https://example.com/path/page'); +echo $url->resolve('../foo'); // 'https://example.com/foo' +echo $url->resolve('/bar'); // 'https://example.com/bar' +echo $url->resolve('sub/page.html'); // 'https://example.com/path/sub/page.html' +``` + + +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +İki URL'nin aynı olup olmadığını doğrular. + +```php +$url->isEqual('https://nette.org'); ``` -`isEqual(string|Url $anotherUrl): bool` yöntemi iki URL'nin aynı olup olmadığını test eder. +UrlScript +========= -UrlScript .[#toc-urlscript] -=========================== +[api:Nette\Http\UrlScript] sınıfı, [#UrlImmutable] sınıfının bir alt sınıfıdır ve onu projenin kök dizini vb. gibi ek sanal URL bileşenleriyle genişletir. Üst sınıfı gibi, değişmez (immutable) bir nesnedir. -[api:Nette\Http\UrlScript] sınıfı, `UrlImmutable` sınıfının soyundan gelir ve ek olarak URL'nin bu mantıksal bölümlerini ayırt eder: +Aşağıdaki diyagram, UrlScript'in tanıdığı bileşenleri gösterir: /--pre baseUrl basePath relativePath relativeUrl @@ -169,17 +244,23 @@ UrlScript .[#toc-urlscript] scriptPath pathInfo \-- -Bu parçaları almak için aşağıdaki yöntemler kullanılabilir: +- `baseUrl`, alan adı ve uygulamanın kök dizinine giden yolun bir bölümü dahil olmak üzere uygulamanın temel URL adresidir +- `basePath`, uygulamanın kök dizinine giden yolun bir bölümüdür +- `scriptPath`, geçerli betiğe giden yoldur +- `relativePath`, basePath'e göre betiğin adıdır (ve muhtemelen ek yol segmentleri) +- `relativeUrl`, sorgu dizesi ve fragment dahil olmak üzere baseUrl'den sonraki URL'nin tüm bölümüdür. +- `pathInfo`, günümüzde betik adından sonraki URL'nin nadiren kullanılan bir bölümüdür + +URL'nin bölümlerini döndürmek için şu metotlar kullanılabilir: .[language-php] -| Getter | Dönen değer +| Alıcı | Döndürülen değer |------------------------------------------------ -| `getScriptPath(): string`| `'/admin/script.php'` -| `getBasePath(): string`| `'/admin/'` -| `getBaseUrl(): string`| `'http://nette.org/admin/'` -| `getRelativePath(): string`| `'script.php'` -| `getRelativeUrl(): string`| `'script.php/pathinfo/?name=param#footer'` -| `getPathInfo(): string`| `'/pathinfo/'` - - -Doğrudan `UrlScript` nesnesi oluşturmuyoruz, ancak [Nette\Http\Request::getUrl() |request] yöntemi bunu döndürüyor. +| `getScriptPath(): string` | `'/admin/script.php'` +| `getBasePath(): string` | `'/admin/'` +| `getBaseUrl(): string` | `'http://nette.org/admin/'` +| `getRelativePath(): string` | `'script.php'` +| `getRelativeUrl(): string` | `'script.php/pathinfo/?name=param#footer'` +| `getPathInfo(): string` | `'/pathinfo/'` + +`UrlScript` nesneleri genellikle doğrudan oluşturulmaz, ancak geçerli HTTP isteği için zaten doğru şekilde ayarlanmış bileşenlerle [Nette\Http\Request::getUrl()|request] metodu tarafından döndürülür. diff --git a/http/uk/@home.texy b/http/uk/@home.texy index a9fddf984c..dfc6127791 100644 --- a/http/uk/@home.texy +++ b/http/uk/@home.texy @@ -2,13 +2,13 @@ Nette HTTP ********** .[perex] -Пакет `nette/http` охоплює [HTTP-запити |request] і [відповіді |response], роботу із [сесіями |sessions], [розбір і побудову URL |urls]. +Пакет `nette/http` інкапсулює [HTTP request|request] та [response |response], роботу з [sessions |sessions] та [парсинг і складання URL |urls]. -Встановлення .[#toc-installation] ---------------------------------- +Встановлення +------------ -Завантажте та встановіть пакет за допомогою [Composer |best-practices:composer]: +Бібліотеку можна завантажити та встановити за допомогою інструменту [Composer|best-practices:composer]: ```shell composer require nette/http diff --git a/http/uk/@left-menu.texy b/http/uk/@left-menu.texy index 176cc6ba03..fb27b98ab9 100644 --- a/http/uk/@left-menu.texy +++ b/http/uk/@left-menu.texy @@ -1,8 +1,8 @@ -Мережі HTTP -*********** -- [Огляд |@home] -- [HTTP-запит |request] -- [HTTP-відповідь |response] +Nette HTTP +********** +- [Вступ |@home] +- [HTTP запит|request] +- [HTTP відповідь|response] - [Сесії |Sessions] -- [Утиліта URL |urls] -- [Конфігурація |Configuration] +- [Утиліти URL |urls] +- [Конфігурація |configuration] diff --git a/http/uk/@meta.texy b/http/uk/@meta.texy new file mode 100644 index 0000000000..96e2d9752a --- /dev/null +++ b/http/uk/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документація Nette}} diff --git a/http/uk/configuration.texy b/http/uk/configuration.texy index 9257d11f8b..1fddd1b3be 100644 --- a/http/uk/configuration.texy +++ b/http/uk/configuration.texy @@ -1,14 +1,14 @@ -Налаштування HTTP +Конфігурація HTTP ***************** .[perex] -Огляд опцій конфігурації для Nette HTTP. +Огляд параметрів конфігурації для Nette HTTP. -Якщо ви використовуєте не весь фреймворк, а тільки цю бібліотеку, прочитайте, [як завантажити конфігурацію |bootstrap:]. +Якщо ви не використовуєте весь фреймворк, а лише цю бібліотеку, прочитайте, [як завантажити конфігурацію|bootstrap:]. -HTTP-заголовки .[#toc-http-headers] -=================================== +HTTP-заголовки +============== ```neon http: @@ -22,19 +22,19 @@ http: frames: ... # (string|bool) за замовчуванням 'SAMEORIGIN' ``` -З метою безпеки фреймворк надсилає заголовок `X-Frame-Options: SAMEORIGIN`, в якому йдеться про те, що сторінка може бути відображена всередині іншої сторінки (в елементі `<iframe>`) тільки в тому випадку, якщо вона знаходиться на тому ж домені. Це може бути небажано в деяких ситуаціях (наприклад, якщо ви розробляєте додаток для Facebook), тому поведінку можна змінити, встановивши фрейми `frames: http://allowed-host.com`. +Фреймворк з міркувань безпеки надсилає заголовок `X-Frame-Options: SAMEORIGIN`, який вказує, що сторінку можна відображати всередині іншої сторінки (в елементі `<iframe>`) лише якщо вона знаходиться на тому ж домені. Це може бути небажаним у деяких ситуаціях (наприклад, якщо ви розробляєте програму для Facebook), тому поведінку можна змінити, встановивши `frames: http://allowed-host.com` або `frames: true`. -Політика безпеки контенту .[#toc-content-security-policy] ---------------------------------------------------------- +Content Security Policy +----------------------- -Заголовки `Content-Security-Policy` (далі CSP) можуть бути легко зібрані, їх опис можна знайти в [описі CSP |https://content-security-policy.com]. Директиви CSP (такі як `script-src`) можуть бути записані або як рядки відповідно до специфікації, або як масиви значень для кращої читабельності. Тоді немає необхідності писати лапки навколо ключових слів, таких як `'self'`. Nette також автоматично генерує значення `nonce`, тому `'nonce-y4PopTLM=='` буде надіслано в заголовку. +Легко можна створювати заголовки `Content-Security-Policy` (далі CSP), їх опис ви знайдете в [опису CSP |https://content-security-policy.com]. Директиви CSP (наприклад, `script-src`) можуть бути записані або як рядки відповідно до специфікації, або як масив значень для кращої читабельності. Тоді не потрібно навколо ключових слів, як-от `'self'`, ставити лапки. Nette також автоматично генерує значення `nonce`, тому в заголовку буде, наприклад, `'nonce-y4PopTLM=='`. ```neon http: - # Політика безпеки контенту + # Content Security Policy csp: - # рядок відповідно до специфікації CSP + # рядок у форматі відповідно до специфікації CSP default-src: "'self' https://example.com" # масив значень @@ -44,14 +44,14 @@ http: - self - https://example.com - # bool у разі перемикачів + # bool у випадку перемикачів upgrade-insecure-requests: true block-all-mixed-content: false ``` -Використовуйте `<script n:nonce>...</script>` у шаблонах, і значення nonce буде заповнено автоматично. Створювати захищені веб-сайти в Nette дуже просто. +У шаблонах використовуйте `<script n:nonce>...</script>`, і значення nonce доповниться автоматично. Робити безпечні сайти в Nette справді легко. -Аналогічним чином можна додати заголовки `Content-Security-Policy-Report-Only` (який можна використовувати паралельно з CSP) і [Feature Policy |https://developers.google.com/web/updates/2018/06/feature-policy]: +Подібно можна створити й заголовки `Content-Security-Policy-Report-Only` (які можна використовувати одночасно з CSP) та [Feature Policy|https://developers.google.com/web/updates/2018/06/feature-policy]: ```neon http: @@ -60,7 +60,7 @@ http: default-src: self report-uri: 'https://my-report-uri-endpoint' - # Політика можливостей + # Feature Policy featurePolicy: unsized-media: none geolocation: @@ -69,91 +69,103 @@ http: ``` -HTTP Cookie .[#toc-http-cookie] -------------------------------- +HTTP cookie +----------- -Ви можете змінити значення за замовчуванням деяких параметрів методів [Nette\Http\Response::setCookie() |response#setCookie] і session. +Можна змінити стандартні значення деяких параметрів методу [Nette\Http\Response::setCookie() |response#setCookie] та сесії. ```neon http: - # область застосування cookie на шляху - cookiePath: ... # (рядок) за замовчуванням '/' + # область дії cookie за шляхом + cookiePath: ... # (string) за замовчуванням '/' - # яким хостам дозволено отримувати куки - cookieDomain: 'example.com' # (рядок|домен) за замовчуванням unset + # домени, які приймають cookie + cookieDomain: 'example.com' # (string|domain) за замовчуванням не встановлено - # відправляти куки тільки через HTTPS? + # надсилати cookie лише через HTTPS? cookieSecure: ... # (bool|auto) за замовчуванням auto - # відключає надсилання кукі, які Nette використовує як захист від CSRF + # вимкне надсилання cookie, яку Nette використовує як захист від CSRF disableNetteCookie: ... # (bool) за замовчуванням false ``` -Параметр `cookieDomain` визначає, які домени (origin) можуть приймати куки. Якщо його не вказано, то cookie приймається тим самим (під)доменом, який ним задано, *виключаючи* їхні піддомени. Якщо вказано `cookieDomain`, то субдомени також будуть включені. Тому вказівка `cookieDomain` є менш обмежувальною, ніж опущення. +Атрибут `cookieDomain` визначає, які домени можуть приймати cookie. Якщо він не вказаний, cookie приймає той самий (під)домен, що й встановив його, *але не* його піддомени. Якщо `cookieDomain` вказаний, піддомени також включаються. Тому вказання `cookieDomain` є менш обмежувальним, ніж його відсутність. -Наприклад, якщо задано `cookieDomain: nette.org`, то cookie також доступний на всіх піддоменах, таких як `doc.nette.org`. Цього також можна досягти за допомогою спеціального значення `domain`, тобто `cookieDomain: domain`. +Наприклад, при `cookieDomain: nette.org` cookies доступні також на всіх піддоменах, таких як `doc.nette.org`. Того ж можна досягти також за допомогою спеціального значення `domain`, тобто `cookieDomain: domain`. -Значення за замовчуванням `cookieSecure` дорівнює `auto`, що означає, що якщо сайт працює на HTTPS, cookie надсилатимуться з прапором `Secure` і, отже, будуть доступні тільки через HTTPS. +Стандартне значення `auto` для атрибута `cookieSecure` означає, що якщо сайт працює на HTTPS, cookies будуть надсилатися з прапором `Secure` і, отже, будуть доступні лише через HTTPS. -HTTP-проксі .[#toc-http-proxy] ------------------------------- +HTTP-проксі +----------- -Якщо сайт працює за HTTP-проксі, введіть IP-адресу проксі, щоб виявлення HTTPS-з'єднань працювало правильно, а також IP-адресу клієнта. Тобто, щоб [Nette\Http\Request::getRemoteAddress() |request#getRemoteAddress] та [isSecured() |request#isSecured] повертали правильні значення і в шаблонах генерувалися посилання з протоколом `https:`. +Якщо сайт працює за HTTP-проксі, вкажіть його IP-адресу, щоб правильно працювало визначення з'єднання через HTTPS, а також IP-адреси клієнта. Тобто, щоб функції [Nette\Http\Request::getRemoteAddress() |request#getRemoteAddress] та [isSecured() |request#isSecured] повертали правильні значення, а в шаблонах генерувалися посилання з протоколом `https:`. ```neon http: - # IP-адреса, діапазон (тобто 127.0.0.1/8) або масив цих значень - proxy: 127.0.0.1.1 # (string|string[]) за замовчуванням none + # IP-адреса, діапазон (напр. 127.0.0.1/8) або масив цих значень + proxy: 127.0.0.1 # (string|string[]) за замовчуванням не встановлено ``` -Сесія .[#toc-session] -===================== +Сесія +===== -Основні налаштування [сеансів |sessions]: +Базові налаштування [сесій |sessions]: ```neon session: - # показує панель сеансу в панелі трейсі? + # показувати панель сесії в Tracy Bar? debugger: ... # (bool) за замовчуванням false - # час бездіяльності, після закінчення якого сесія завершується - expiration: 14 days # (string) за замовчуванням '3 години' + # час неактивності, після якого сесія закінчиться + expiration: 14 days # (string) за замовчуванням '3 hours' - # коли починати сесію? + # коли має запускатися сесія? autoStart: ... # (smart|always|never) за замовчуванням 'smart' - # обробник, служба, що реалізує інтерфейс SessionHandlerInterface + # обробник, сервіс, що реалізує інтерфейс SessionHandlerInterface handler: @handlerService ``` -Параметр `autoStart` визначає, коли починати сеанс. Значення `always` означає, що сесія завжди запускається під час запуску програми. Значення `smart` означає, що сесія буде запускатися під час запуску програми, тільки якщо вона вже існує, або в той момент, коли ми хочемо читати з неї або писати в неї. Нарешті, значення `never` відключає автоматичний запуск сесії. +Опція `autoStart` керує тим, коли має запускатися сесія. Значення `always` означає, що сесія запуститься завжди при запуску програми. Значення `smart` означає, що сесія запуститься при старті програми лише тоді, коли вона вже існує, або в момент, коли ми хочемо з неї читати або в неї записувати. І нарешті, значення `never` забороняє автоматичний запуск сесії. -Ви також можете задати всі [директиви |https://www.php.net/manual/en/session.configuration.php] PHP [сесії |https://www.php.net/manual/en/session.configuration.php] (у форматі camelCase), а також [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. Приклад: +Далі можна налаштовувати всі PHP [директиви сесії |https://www.php.net/manual/en/session.configuration.php] (у форматі camelCase) та також [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. Приклад: ```neon session: - # 'session.name' записано як 'name' + # 'session.name' запишемо як 'name' name: MYID - # 'session.save_path' записано як 'savePath' + # 'session.save_path' запишемо як 'savePath' savePath: "%tempDir%/sessions" ``` -Сесійний файл cookie .[#toc-session-cookie] -------------------------------------------- +Session cookie +-------------- -Сесійний cookie надсилається з тими самими параметрами, що й [інші cookie |#HTTP-Cookie], але ви можете змінити їх для нього: +Session cookie надсилається з тими ж параметрами, що й [інші cookie |#HTTP cookie], але ці ви можете для неї змінити: ```neon session: - # яким хостам дозволено отримувати cookie-файл - cookieDomain: 'example.com' # (string|domain) + # домени, які приймають cookie + cookieDomain: 'example.com' # (string|domain) - # обмеження при доступі до крос-оригінального запиту - cookieSamesite: None # (Strict|Lax|None) за замовчуванням Lax + # обмеження при доступі з іншого домену + cookieSamesite: None # (Strict|Lax|None) за замовчуванням Lax ``` -Параметр `cookieSamesite` впливає на те, чи надсилається cookie під час [міжсайтових запитів |nette:glossary#SameSite-Cookie], що забезпечує деякий захист від атак [Cross-Site Request Forgery |nette:glossary#Cross-Site-Request-Forgery-CSRF]. +Атрибут `cookieSamesite` впливає на те, чи буде cookie надіслано при [доступі з іншого домену |nette:glossary#SameSite cookie], що забезпечує певний захист від атак [Cross-Site Request Forgery |nette:glossary#Cross-Site Request Forgery CSRF] (CSRF). + + +Сервіси DI +========== + +Ці сервіси додаються до DI-контейнера: + +| Назва | Тип | Опис +|----------------------------------------------------- +| `http.request` | [api:Nette\Http\Request] | [HTTP-запит| request] +| `http.response` | [api:Nette\Http\Response] | [HTTP-відповідь| response] +| `session.session` | [api:Nette\Http\Session] | [керування сесіями| sessions] diff --git a/http/uk/request.texy b/http/uk/request.texy index f35c32a2fe..b3537c1f0d 100644 --- a/http/uk/request.texy +++ b/http/uk/request.texy @@ -2,19 +2,19 @@ HTTP-запит ********** .[perex] -Nette інкапсулює HTTP-запит в об'єкти зі зрозумілим API, забезпечуючи при цьому фільтр санації. +Nette інкапсулює HTTP-запит в об'єкти зі зрозумілим API і водночас надає фільтр санітизації. -HTTP-запит - це об'єкт [api:Nette\Http\Request], який ви отримуєте, передаючи його за допомогою [ін'єкції залежностей |dependency-injection:passing-dependencies]. У презентаторах просто викликайте `$httpRequest = $this->getHttpRequest()`. +HTTP-запит представляє об'єкт [api:Nette\Http\Request]. Якщо ви працюєте з Nette, цей об'єкт автоматично створюється фреймворком, і ви можете отримати його за допомогою [впровадження залежностей |dependency-injection:passing-dependencies]. У презентерах достатньо лише викликати метод `$this->getHttpRequest()`. Якщо ви працюєте поза Nette Framework, ви можете створити об'єкт за допомогою [#RequestFactory]. -Важливим є те, що Nette при [створенні |#RequestFactory] цього об'єкта очищає всі вхідні параметри GET, POST і COOKIE, а також URL від керуючих символів і неприпустимих послідовностей UTF-8. Тому ви можете спокійно продовжувати роботу з даними. Очищені дані потім використовуються в презентаторах і формах. +Великою перевагою Nette є те, що при створенні об'єкта він автоматично очищає всі вхідні параметри GET, POST, COOKIE, а також URL від керуючих символів та недійсних UTF-8 послідовностей. З цими даними потім можна безпечно працювати далі. Очищені дані потім використовуються в презентерах та формах. -→ [Встановлення та вимоги |@home#Installation] +→ [Встановлення та вимоги |@home#Встановлення] -Nette\Http\Request .[#toc-nette-http-request] -============================================= +Nette\Http\Request +================== -Цей об'єкт є незмінним. У нього немає сеттерів, є тільки один так званий wither `withUrl()`, який не змінює об'єкт, а повертає новий екземпляр зі зміненим значенням. +Цей об'єкт є immutable (незмінним). Він не має жодних сеттерів, має лише один так званий wither `withUrl()`, який не змінює об'єкт, а повертає новий екземпляр зі зміненим значенням. withUrl(Nette\Http\UrlScript $url): Nette\Http\Request .[method] @@ -24,62 +24,62 @@ withUrl(Nette\Http\UrlScript $url): Nette\Http\Request .[method] getUrl(): Nette\Http\UrlScript .[method] ---------------------------------------- -Повертає URL запиту у вигляді об'єкта [UrlScript |urls#UrlScript]. +Повертає URL запиту як об'єкт [UrlScript |urls#UrlScript]. ```php $url = $httpRequest->getUrl(); -echo $url; // https://nette.org/en/documentation?action=edit +echo $url; // https://doc.nette.org/uk/?action=edit echo $url->getHost(); // nette.org ``` -Браузери не надсилають фрагмент на сервер, тому `$url->getFragment()` поверне порожній рядок. +Попередження: браузери не надсилають на сервер фрагмент, тому `$url->getFragment()` повертатиме порожній рядок. -getQuery(string $key=null): string|array|null .[method] -------------------------------------------------------- -Повертає параметри GET-запиту: +getQuery(?string $key=null): string|array|null .[method] +-------------------------------------------------------- +Повертає параметри GET-запиту. ```php -$all = $httpRequest->getQuery(); // масив усіх URL параметрів +$all = $httpRequest->getQuery(); // повертає масив усіх параметрів з URL $id = $httpRequest->getQuery('id'); // повертає GET-параметр 'id' (або null) ``` -getPost(string $key=null): string|array|null .[method] ------------------------------------------------------- -Повертає параметри POST-запиту: +getPost(?string $key=null): string|array|null .[method] +------------------------------------------------------- +Повертає параметри POST-запиту. ```php -$all = $httpRequest->getPost(); // масив усіх POST-параметрів +$all = $httpRequest->getPost(); // повертає масив усіх параметрів з POST $id = $httpRequest->getPost('id'); // повертає POST-параметр 'id' (або null) ``` getFile(string|string[] $key): Nette\Http\FileUpload|array|null .[method] ------------------------------------------------------------------------- -Повертає [вивантаження |#Uploaded-Files] як об'єкт [api:Nette\Http\FileUpload]: +Повертає [завантаження |#Завантажені файли] як об'єкт [api:Nette\Http\FileUpload]: ```php $file = $httpRequest->getFile('avatar'); -if ($file->hasFile()) { // чи був завантажений якийсь файл? - $file->getUntrustedName(); // ім'я файлу, відправленого користувачем +if ($file?->hasFile()) { // чи був якийсь файл завантажений? + $file->getUntrustedName(); // ім'я файлу, надіслане користувачем $file->getSanitizedName(); // ім'я без небезпечних символів } ``` -Вкажіть масив ключів для доступу до структури піддерева. +Для доступу до вкладеної структури вкажіть масив ключів. ```php //<input type="file" name="my-form[details][avatar]" multiple> $file = $request->getFile(['my-form', 'details', 'avatar']); ``` -Оскільки ви не можете довіряти даним ззовні і тому не покладаєтеся на форму структури, цей метод безпечніший, ніж `$request->getFiles()['my-form']['details']['avatar']`який може зазнати невдачі. +Оскільки не можна довіряти даним ззовні і, отже, покладатися на структуру файлів, цей спосіб є безпечнішим, ніж, наприклад, `$request->getFiles()['my-form']['details']['avatar']`, який може зазнати невдачі. getFiles(): array .[method] --------------------------- -Повертає дерево [файлів вивантаження |#Uploaded-Files] в нормалізованій структурі, кожен аркуш якого є екземпляром [api:Nette\Http\FileUpload]: +Повертає дерево [всіх завантажень |#Завантажені файли] у нормалізованій структурі, листками якої є об'єкти [api:Nette\Http\FileUpload]: ```php $files = $httpRequest->getFiles(); @@ -88,7 +88,7 @@ $files = $httpRequest->getFiles(); getCookie(string $key): string|array|null .[method] --------------------------------------------------- -Повертає cookie або `null`, якщо він не існує. +Повертає cookie або `null`, якщо вона не існує. ```php $sessId = $httpRequest->getCookie('sess_id'); @@ -97,7 +97,7 @@ $sessId = $httpRequest->getCookie('sess_id'); getCookies(): array .[method] ----------------------------- -Повертає всі файли cookie: +Повертає всі cookies. ```php $cookies = $httpRequest->getCookies(); @@ -106,16 +106,16 @@ $cookies = $httpRequest->getCookies(); getMethod(): string .[method] ----------------------------- -Повертає метод HTTP, за допомогою якого було зроблено запит. +Повертає HTTP-метод, яким був зроблений запит. ```php -echo $httpRequest->getMethod(); // GET, POST, HEAD, PUT +$httpRequest->getMethod(); // GET, POST, HEAD, PUT ``` isMethod(string $method): bool .[method] ---------------------------------------- -Перевіряє метод HTTP, за допомогою якого було зроблено запит. Параметр не чутливий до регістру. +Перевіряє HTTP-метод, яким був зроблений запит. Параметр нечутливий до регістру. ```php if ($httpRequest->isMethod('GET')) // ... @@ -124,7 +124,7 @@ if ($httpRequest->isMethod('GET')) // ... getHeader(string $header): ?string .[method] -------------------------------------------- -Повертає HTTP-заголовок або `null`, якщо він не існує. Параметр не чутливий до регістру: +Повертає HTTP-заголовок або `null`, якщо він не існує. Параметр нечутливий до регістру. ```php $userAgent = $httpRequest->getHeader('User-Agent'); @@ -133,7 +133,7 @@ $userAgent = $httpRequest->getHeader('User-Agent'); getHeaders(): array .[method] ----------------------------- -Повертає всі HTTP-заголовки у вигляді асоціативного масиву: +Повертає всі HTTP-заголовки як асоціативний масив. ```php $headers = $httpRequest->getHeaders(); @@ -141,39 +141,34 @@ echo $headers['Content-Type']; ``` -getReferer(): ?Nette\Http\UrlImmutable .[method] ------------------------------------------------- -З якого URL прийшов користувач? Остерігайтеся, це зовсім не надійно. - - isSecured(): bool .[method] --------------------------- -Чи зашифровано з'єднання (HTTPS)? Можливо, вам буде потрібно налаштувати [проксі-сервер |configuration#HTTP-Proxy] для правильної роботи. +Чи є з'єднання зашифрованим (HTTPS)? Для правильної роботи може знадобитися [налаштувати проксі |configuration#HTTP-проксі]. isSameSite(): bool .[method] ---------------------------- -Запит виходить із того самого (під)домену та ініційований натисканням на посилання? Для визначення цього Nette використовує cookie `_nss` (раніше `nette-samesite`). +Чи надходить запит з того самого (під)домену і чи ініційований він кліком на посилання? Nette для визначення використовує cookie `_nss` (раніше `nette-samesite`). isAjax(): bool .[method] ------------------------ -Це AJAX-запит? +Чи це AJAX-запит? getRemoteAddress(): ?string .[method] ------------------------------------- -Повертає IP-адресу користувача. Для правильної роботи може знадобитися налаштування [проксі-сервера |configuration#HTTP-Proxy]. +Повертає IP-адресу користувача. Для правильної роботи може знадобитися [налаштувати проксі |configuration#HTTP-проксі]. getRemoteHost(): ?string .[method deprecated] --------------------------------------------- -Повертає DNS-трансляцію IP-адреси користувача. Для правильної роботи може знадобитися налаштування [проксі-сервера |configuration#HTTP-Proxy]. +Повертає DNS-перетворення IP-адреси користувача. Для правильної роботи може знадобитися [налаштувати проксі |configuration#HTTP-проксі]. -getBasicCredentials(): ?string .[method] ----------------------------------------- -Повертає облікові дані [базової HTTP-аутентифікації |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication]. +getBasicCredentials(): ?array .[method] +--------------------------------------- +Повертає облікові дані для [Basic HTTP authentication |https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication]. ```php [$user, $password] = $httpRequest->getBasicCredentials(); @@ -182,7 +177,7 @@ getBasicCredentials(): ?string .[method] getRawBody(): ?string .[method] ------------------------------- -Повертає тіло HTTP-запиту: +Повертає тіло HTTP-запиту. ```php $body = $httpRequest->getRawBody(); @@ -191,48 +186,55 @@ $body = $httpRequest->getRawBody(); detectLanguage(array $langs): ?string .[method] ----------------------------------------------- -Визначає мову. Як параметр `$lang` ми передаємо масив мов, які підтримує додаток, і він повертає ту, якій віддає перевагу браузер. Це не магія, метод просто використовує заголовок `Accept-Language`. Якщо збіг не досягнуто, повертається `null`. +Визначає мову. Як параметр `$langs` передаємо масив мов, які підтримує програма, і вона поверне ту, яку браузер відвідувача хотів би бачити найбільше. Це не магія, просто використовується заголовок `Accept-Language`. Якщо збігу не знайдено, повертає `null`. ```php -// Заголовок, що надсилається браузером: Accept-Language: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3 +// браузер надсилає, напр., Accept-Language: uk,en-us;q=0.8,en;q=0.5,sl;q=0.3 -$langs = ['hu', 'pl', 'en']; // мови, що підтримуються в застосунку -echo $httpRequest->detectLanguage($langs); // en +$langs = ['hu', 'pl', 'uk']; // мови, підтримувані програмою +echo $httpRequest->detectLanguage($langs); // uk ``` -RequestFactory .[#toc-requestfactory] -===================================== +RequestFactory +============== -Об'єкт поточного HTTP-запиту створюється за допомогою [api:Nette\Http\RequestFactory]. Якщо ви пишете додаток, який не використовує DI-контейнер, ви створюєте запит таким чином: +Клас [api:Nette\Http\RequestFactory] служить для створення екземпляра `Nette\Http\Request`, який представляє поточний HTTP-запит. (Якщо ви працюєте з Nette, об'єкт HTTP-запиту автоматично створюється фреймворком.) ```php $factory = new Nette\Http\RequestFactory; $httpRequest = $factory->fromGlobals(); ``` -RequestFactory може бути налаштований перед викликом `fromGlobals()`. Ми можемо відключити всю санацію вхідних параметрів від неприпустимих послідовностей UTF-8 за допомогою `$factory->setBinary()`. А також налаштувати проксі-сервер, що важливо для правильного визначення IP-адреси користувача за допомогою `$factory->setProxy(...)`. +Метод `fromGlobals()` створює об'єкт запиту на основі поточних глобальних змінних PHP (`$_GET`, `$_POST`, `$_COOKIE`, `$_FILES` та `$_SERVER`). При створенні об'єкта він автоматично очищає всі вхідні параметри GET, POST, COOKIE, а також URL від керуючих символів та недійсних UTF-8 послідовностей, що забезпечує безпеку при подальшій роботі з цими даними. + +RequestFactory можна конфігурувати перед викликом `fromGlobals()`: + +- методом `$factory->setBinary()` вимкнете автоматичне очищення вхідних параметрів від керуючих символів та недійсних UTF-8 послідовностей. +- методом `$factory->setProxy(...)` вкажете IP-адресу [проксі-сервера |configuration#HTTP-проксі], що необхідно для правильного визначення IP-адреси користувача. -Очистити URL від символів, які можуть потрапити в них через погано реалізовані системи коментарів на різних інших сайтах, можна за допомогою фільтрів: +RequestFactory дозволяє визначати фільтри, які автоматично трансформують частини URL запиту. Ці фільтри видаляють небажані символи з URL, які там можуть бути вставлені, наприклад, неправильною реалізацією систем коментарів на різних сайтах: ```php -// видаліть пробіли зі шляху +// видалення пробілів зі шляху $requestFactory->urlFilters['path']['%20'] = ''; -// видаліть крапку, кому або праву круглу дужку в кінці URL-адреси -$requestFactory->urlFilters['url']['['[.,)]$'] = ''; +// видалення крапки, коми або правої дужки з кінця URI +$requestFactory->urlFilters['url']['[.,)]$'] = ''; -// очистити шлях від дубльованих слешів (фільтр за замовчуванням) +// очищення шляху від подвійних слешів (стандартний фільтр) $requestFactory->urlFilters['path']['/{2,}'] = '/'; ``` +Перший ключ `'path'` або `'url'` визначає, до якої частини URL застосовується фільтр. Другий ключ — це регулярний вираз, який потрібно знайти, а значення — це заміна, яка використовується замість знайденого тексту. -Завантажені файли .[#toc-uploaded-files] -======================================== -Метод `Nette\Http\Request::getFiles()` повертає дерево завантажених файлів у нормалізованій структурі, кожен аркуш якого є екземпляром [api:Nette\Http\FileUpload]. Ці об'єкти інкапсулюють дані, представлені елементом `<input type=file>` елементом форми. +Завантажені файли +================= -Структура відображає іменування елементів у HTML. У найпростішому прикладі це може бути один іменований елемент форми, представлений як: +Метод `Nette\Http\Request::getFiles()` повертає масив усіх завантажень у нормалізованій структурі, листками якої є об'єкти [api:Nette\Http\FileUpload]. Вони інкапсулюють дані, надіслані елементом форми `<input type=file>`. + +Структура відображає іменування елементів у HTML. У найпростішому випадку це може бути єдиний іменований елемент форми, надісланий як: ```latte <input type="file" name="avatar"> @@ -246,19 +248,19 @@ $requestFactory->urlFilters['path']['/{2,}'] = '/'; ] ``` -Об'єкт `FileUpload` створюється, навіть якщо користувач не завантажив жодного файлу або завантаження не вдалося. Метод `hasFile()` повертає true, якщо файл було надіслано: +Об'єкт `FileUpload` створюється навіть у випадку, якщо користувач не надіслав жодного файлу або надсилання не вдалося. Чи був файл надісланий, повертає метод `hasFile()`: ```php -$request->getFile('avatar')->hasFile(); +$request->getFile('avatar')?->hasFile(); ``` -У разі введення з використанням нотації масиву для імені: +У випадку назви елемента, що використовує нотацію для масиву: ```latte <input type="file" name="my-form[details][avatar]"> ``` -дерево, що повертається, має такий вигляд: +повернене дерево виглядає так: ```php [ @@ -270,13 +272,13 @@ $request->getFile('avatar')->hasFile(); ] ``` -Ви також можете створювати масиви файлів: +Можна створити і масив файлів: ```latte -<input type="file" name="my-form[details][avatars][] multiple"> +<input type="file" name="my-form[details][avatars][]" multiple> ``` -У такому разі структура має такий вигляд: +У такому випадку структура виглядає так: ```php [ @@ -292,7 +294,7 @@ $request->getFile('avatar')->hasFile(); ] ``` -Найкращий спосіб доступу до індексу 1 вкладеного масиву такий: +Доступ до індексу 1 вкладеного масиву найкраще отримати так: ```php $file = $request->getFile(['my-form', 'details', 'avatars', 1]); @@ -301,7 +303,7 @@ if ($file instanceof FileUpload) { } ``` -Оскільки ви не можете довіряти даним ззовні і тому не покладаєтеся на форму структури, цей метод є безпечнішим, ніж `$request->getFiles()['my-form']['details']['avatars'][1]`який може не спрацювати. +Оскільки не можна довіряти даним ззовні і, отже, покладатися на структуру файлів, цей спосіб є безпечнішим, ніж, наприклад, `$request->getFiles()['my-form']['details']['avatars'][1]`, який може зазнати невдачі. Огляд методів `FileUpload` .{toc: FileUpload} @@ -310,22 +312,22 @@ if ($file instanceof FileUpload) { hasFile(): bool .[method] ------------------------- -Повертає `true`, якщо користувач завантажив файл. +Повертає `true`, якщо користувач завантажив якийсь файл. isOk(): bool .[method] ---------------------- -Повертає `true`, якщо файл було завантажено успішно. +Повертає `true`, якщо файл був завантажений успішно. getError(): int .[method] ------------------------- -Повертає код помилки, пов'язаний із завантаженим файлом. Це може бути одна з констант [UPLOAD_ERR_XXX |http://php.net/manual/en/features.file-upload.errors.php]. Якщо файл було завантажено успішно, повертається `UPLOAD_ERR_OK`. +Повертає код помилки при завантаженні файлу. Це одна з констант [UPLOAD_ERR_XXX|http://php.net/manual/en/features.file-upload.errors.php]. У випадку, якщо завантаження пройшло успішно, повертає `UPLOAD_ERR_OK`. move(string $dest) .[method] ---------------------------- -Переміщує завантажений файл у нове місце. Якщо файл призначення вже існує, його буде перезаписано. +Переміщує завантажений файл у нове місце. Якщо цільовий файл вже існує, він буде перезаписаний. ```php $file->move('/path/to/files/name.ext'); @@ -334,61 +336,72 @@ $file->move('/path/to/files/name.ext'); getContents(): ?string .[method] -------------------------------- -Повертає вміст завантаженого файлу. Якщо завантаження не було успішним, повертається `null`. +Повертає вміст завантаженого файлу. У випадку, якщо завантаження не було успішним, повертає `null`. getContentType(): ?string .[method] ----------------------------------- -Визначає MIME-тип вмісту завантажуваного файлу на основі його сигнатури. Якщо завантаження не було успішним або визначення не вдалося, повертається `null`. +Визначає MIME content type завантаженого файлу на основі його сигнатури. У випадку, якщо завантаження не було успішним або визначення не вдалося, повертає `null`. .[caution] -Потрібне розширення PHP `fileinfo`. +Вимагає PHP-розширення `fileinfo`. getUntrustedName(): string .[method] ------------------------------------ -Повертає вихідне ім'я файлу, передане браузером. +Повертає оригінальну назву файлу, як її надіслав браузер. .[caution] -Не довіряйте значенню, що повертається цим методом. Клієнт може надіслати шкідливе ім'я файлу з наміром зіпсувати або зламати ваш додаток. +Не довіряйте значенню, повернутому цим методом. Клієнт міг надіслати шкідливу назву файлу з наміром пошкодити або зламати вашу програму. getSanitizedName(): string .[method] ------------------------------------ -Повертає саніроване ім'я файлу. Воно містить тільки символи ASCII `[a-zA-Z0-9.-]`. Якщо ім'я не містить таких символів, повертається 'unknown'. Якщо файл є зображенням JPEG, PNG, GIF або WebP, повертається правильне розширення файлу. +Повертає санітизовану назву файлу. Містить лише ASCII-символи `[a-zA-Z0-9.-]`. Якщо назва не містить таких символів, поверне `'unknown'`. Якщо файл є зображенням у форматі JPEG, PNG, GIF, WebP або AVIF, поверне також правильне розширення. + +.[caution] +Вимагає PHP-розширення `fileinfo`. + + +getSuggestedExtension(): ?string .[method]{data-version:3.2.4} +-------------------------------------------------------------- +Повертає відповідне розширення файлу (без крапки), що відповідає виявленому MIME-типу. + +.[caution] +Вимагає PHP-розширення `fileinfo`. getUntrustedFullPath(): string .[method] ---------------------------------------- -Повертає вихідний повний шлях, вказаний браузером під час завантаження каталогу. Повний шлях доступний тільки в PHP 8.1 і вище. У попередніх версіях цей метод повертає недовірене ім'я файлу. +Повертає оригінальний шлях до файлу, як його надіслав браузер при завантаженні папки. Повний шлях доступний лише в PHP 8.1 та вище. У попередніх версіях цей метод повертає оригінальну назву файлу. .[caution] -Не довіряйте значенню, що повертається цим методом. Клієнт може надіслати шкідливе ім'я файлу з наміром зіпсувати або зламати ваш додаток. +Не довіряйте значенню, повернутому цим методом. Клієнт міг надіслати шкідливу назву файлу з наміром пошкодити або зламати вашу програму. getSize(): int .[method] ------------------------ -Повертає розмір завантаженого файлу. Якщо завантаження не було успішним, повертається `0`. +Повертає розмір завантаженого файлу. У випадку, якщо завантаження не було успішним, повертає `0`. getTemporaryFile(): string .[method] ------------------------------------ -Повертає шлях до тимчасового місця розташування завантаженого файлу. Якщо завантаження не було успішним, повертається `''`. +Повертає шлях до тимчасового розташування завантаженого файлу. У випадку, якщо завантаження не було успішним, повертає `''`. isImage(): bool .[method] ------------------------- -Повертає `true`, якщо завантажений файл є зображенням JPEG, PNG, GIF або WebP. Виявлення засноване на його сигнатурі. Цілісність усього файлу не перевіряється. Ви можете дізнатися, чи не пошкоджено зображення, наприклад, спробувавши [завантажити його |#toImage]. +Повертає `true`, якщо завантажений файл є зображенням у форматі JPEG, PNG, GIF, WebP або AVIF. Визначення відбувається на основі його сигнатури і не перевіряється цілісність усього файлу. Чи не пошкоджене зображення, можна з'ясувати, наприклад, спробувавши його [завантажити |#toImage]. .[caution] -Потрібне розширення PHP `fileinfo`. +Вимагає PHP-розширення `fileinfo`. getImageSize(): ?array .[method] -------------------------------- -Повертає пару `[width, height]` з розмірами завантаженого зображення. Якщо завантаження не було успішним або це не дійсне зображення, повертається `null`. +Повертає пару `[ширина, висота]` з розмірами завантаженого зображення. У випадку, якщо завантаження не було успішним або це не дійсне зображення, повертає `null`. toImage(): Nette\Utils\Image .[method] -------------------------------------- -Завантажує зображення як об'єкт [Image |utils:images]. Якщо завантаження не було успішним або зображення не є дійсним, викидається виняток `Nette\Utils\ImageException`. +Завантажує зображення як об'єкт [Image|utils:images]. У випадку, якщо завантаження не було успішним або це не дійсне зображення, викине виняток `Nette\Utils\ImageException`. diff --git a/http/uk/response.texy b/http/uk/response.texy index b0bbc815ff..d9dcc7e50f 100644 --- a/http/uk/response.texy +++ b/http/uk/response.texy @@ -2,22 +2,22 @@ HTTP-відповідь ************** .[perex] -Nette інкапсулює HTTP-відповідь в об'єкти зі зрозумілим API, забезпечуючи при цьому фільтр санації. +Nette інкапсулює HTTP-відповідь в об'єкти зі зрозумілим API. -HTTP-відповідь - це об'єкт [api:Nette\Http\Response], який ви отримуєте, передаючи його за допомогою [ін'єкції залежностей |dependency-injection:passing-dependencies]. У презентаторах просто викликайте `$httpResponse = $this->getHttpResponse()`. +HTTP-відповідь представляє об'єкт [api:Nette\Http\Response]. Якщо ви працюєте з Nette, цей об'єкт автоматично створюється фреймворком, і ви можете отримати його за допомогою [впровадження залежностей |dependency-injection:passing-dependencies]. У презентерах достатньо лише викликати метод `$this->getHttpResponse()`. -→ [Встановлення та вимоги |@home#Installation] +→ [Встановлення та вимоги |@home#Встановлення] -Nette\Http\Response .[#toc-nette-http-response] -=============================================== +Nette\Http\Response +=================== -На відміну від [Nette\Http\Request |request], цей об'єкт є змінюваним, тому ви можете використовувати сеттери для зміни стану, тобто для надсилання заголовків. Пам'ятайте, що всі сеттери **повинні бути викликані до того, як буде надіслано фактичні дані.** Метод `isSent()` визначає, чи було надіслано дані. Якщо він повертає `true`, кожна спроба надіслати заголовок спричиняє виключення `Nette\InvalidStateException`. +Об'єкт, на відміну від [Nette\Http\Request|request], є mutable (змінним), тобто за допомогою сеттерів ви можете змінювати стан, наприклад, надсилати заголовки. Не забувайте, що всі сеттери повинні бути викликані **перед надсиланням будь-якого виводу.** Чи був вже надісланий вивід, покаже метод `isSent()`. Якщо він повертає `true`, кожна спроба надіслати заголовок викличе виняток `Nette\InvalidStateException`. -setCode(int $code, string $reason=null) .[method] -------------------------------------------------- -Змінює [код відповіді |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10] статусу. Для кращої читабельності вихідного коду рекомендується використовувати [зумовлені константи |api:Nette\Http\IResponse] замість реальних чисел. +setCode(int $code, ?string $reason=null) .[method] +-------------------------------------------------- +Змінює [код стану відповіді |https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10]. Для кращої зрозумілості вихідного коду рекомендуємо для коду використовувати замість чисел [передбачені константи |api:Nette\Http\IResponse]. ```php $httpResponse->setCode(Nette\Http\Response::S404_NotFound); @@ -26,17 +26,17 @@ $httpResponse->setCode(Nette\Http\Response::S404_NotFound); getCode(): int .[method] ------------------------ -Повертає код статусу відповіді. +Повертає код стану відповіді. isSent(): bool .[method] ------------------------ -Повертає, чи були заголовки вже надіслані з сервера в браузер, тому вже неможливо надіслати заголовки або змінити код статусу. +Повертає, чи вже відбулося надсилання заголовків з сервера до браузера, і отже, вже неможливо надсилати заголовки чи змінювати код стану. setHeader(string $name, string $value) .[method] ------------------------------------------------ -Надсилає HTTP-заголовок і **перезаписує** раніше надісланий однойменний заголовок. +Надсилає HTTP-заголовок і **перезаписує** раніше надісланий заголовок з тією ж назвою. ```php $httpResponse->setHeader('Pragma', 'no-cache'); @@ -45,7 +45,7 @@ $httpResponse->setHeader('Pragma', 'no-cache'); addHeader(string $name, string $value) .[method] ------------------------------------------------ -Надсилає HTTP-заголовок і **не перезаписує** раніше надісланий однойменний заголовок. +Надсилає HTTP-заголовок і **не перезаписує** раніше надісланий заголовок з тією ж назвою. ```php $httpResponse->addHeader('Accept', 'application/json'); @@ -60,7 +60,7 @@ deleteHeader(string $name) .[method] getHeader(string $header): ?string .[method] -------------------------------------------- -Повертає відправлений HTTP-заголовок, або `null`, якщо він не існує. Параметр не чутливий до регістру. +Повертає надісланий HTTP-заголовок або `null`, якщо такий не існує. Параметр нечутливий до регістру. ```php $pragma = $httpResponse->getHeader('Pragma'); @@ -69,7 +69,7 @@ $pragma = $httpResponse->getHeader('Pragma'); getHeaders(): array .[method] ----------------------------- -Повертає всі відправлені HTTP-заголовки у вигляді асоціативного масиву. +Повертає всі надіслані HTTP-заголовки як асоціативний масив. ```php $headers = $httpResponse->getHeaders(); @@ -77,9 +77,9 @@ echo $headers['Pragma']; ``` -setContentType(string $type, string $charset=null) .[method] ------------------------------------------------------------- -Надсилає заголовок `Content-Type`. +setContentType(string $type, ?string $charset=null) .[method] +------------------------------------------------------------- +Змінює заголовок `Content-Type`. ```php $httpResponse->setContentType('text/plain', 'UTF-8'); @@ -88,7 +88,7 @@ $httpResponse->setContentType('text/plain', 'UTF-8'); redirect(string $url, int $code=self::S302_Found): void .[method] ----------------------------------------------------------------- -Перенаправляє на інший URL. Не забудьте після цього вийти зі скрипта. +Перенаправляє на інший URL. Не забудьте потім завершити скрипт. ```php $httpResponse->redirect('http://example.com'); @@ -98,52 +98,52 @@ exit; setExpiration(?string $time) .[method] -------------------------------------- -Встановлює термін дії HTTP-документа, що використовує заголовки `Cache-Control` і `Expires`. Параметром є або часовий інтервал (у вигляді тексту), або `null`, який вимикає кешування. +Встановлює термін дії HTTP-документа за допомогою заголовків `Cache-Control` та `Expires`. Параметром є або часовий інтервал (як текст), або `null`, що заборонить кешування. ```php -// кеш браузера закінчується через одну годину +// кеш у браузері закінчиться через годину $httpResponse->setExpiration('1 hour'); ``` sendAsFile(string $fileName) .[method] -------------------------------------- -Відповідь має бути завантажена за допомогою діалогу *Зберегти як* із зазначеним ім'ям. Сам файл на виведення не надсилається. +Відповідь буде завантажена за допомогою діалогового вікна *Зберегти як* під вказаною назвою. Сам файл при цьому не надсилається. ```php -$httpResponse->sendAsFile('invoice.pdf'); +$httpResponse->sendAsFile('invoice.pdf'); // Змінено назву файлу на англійську для прикладу ``` -setCookie(string $name, string $value, $time, string $path=null, string $domain=null, bool $secure=null, bool $httpOnly=null, string $sameSite=null) .[method] --------------------------------------------------------------------------------------------------------------------------------------------------------------- +setCookie(string $name, string $value, $time, ?string $path=null, ?string $domain=null, ?bool $secure=null, ?bool $httpOnly=null, ?string $sameSite=null) .[method] +------------------------------------------------------------------------------------------------------------------------------------------------------------------- Надсилає cookie. Значення параметрів за замовчуванням: -| `$path` | `'/'` | з охопленням усіх шляхів на (під)домені *(налаштовується)*. -| `$domain` | `null` | з областю дії поточного (під)домену, але не його піддоменів *(налаштовується)*. -| `$secure` | `true` | якщо сайт працює на HTTPS, інакше `false` *(налаштовується)*. -| `$httpOnly` | `true` | cookie недоступна для JavaScript -| `$sameSite` | `'Lax'` | cookie не потрібно відправляти при [доступі з іншого джерела |nette:glossary#SameSite-Cookie] +| `$path` | `'/'` | cookie має область дії на всі шляхи в (під)домені *(конфігурується)* +| `$domain` | `null` | що означає з областю дії на поточний (під)домен, але не на його піддомени *(конфігурується)* +| `$secure` | `true` | якщо сайт працює на HTTPS, інакше `false` *(конфігурується)* +| `$httpOnly` | `true` | cookie недоступна для JavaScript +| `$sameSite` | `'Lax'` | cookie може не надсилатися при [доступі з іншого домену |nette:glossary#SameSite cookie] -Ви можете змінити значення за замовчуванням параметрів `$path`, `$domain` і `$secure` в [конфігурація |configuration#HTTP-Cookie]. +Значення параметрів `$path`, `$domain` та `$secure` за замовчуванням можна змінити в [конфігурації |configuration#HTTP cookie]. -Час може бути вказано у вигляді кількості секунд або рядка: +Час можна вказувати як кількість секунд або рядок: ```php -$httpResponse->setCookie('lang', 'en', '100 days'); +$httpResponse->setCookie('lang', 'uk', '100 days'); // Змінено мову на 'uk' ``` -Параметр `$domain` визначає, які домени (origin) можуть приймати cookie. Якщо параметр не вказано, cookie приймається тим самим (під)доменом, який задано, виключаючи їхні піддомени. Якщо вказано `$domain`, то піддомени також включаються. Тому зазначення `$domain` є менш обмежувальним, ніж опущення. Наприклад, якщо `$domain = 'nette.org'`, cookie також доступний на всіх піддоменах, як `doc.nette.org`. +Параметр `$domain` визначає, які домени можуть приймати cookie. Якщо він не вказаний, cookie приймає той самий (під)домен, що й встановив його, але не його піддомени. Якщо `$domain` вказаний, піддомени також включаються. Тому вказання `$domain` є менш обмежувальним, ніж його відсутність. Наприклад, при `$domain = 'nette.org'` cookies доступні також на всіх піддоменах, таких як `doc.nette.org`. -Для значення `$sameSite` можна використовувати константи `Response::SameSiteLax`, `SameSiteStrict` і `SameSiteNone`. +Для значення `$sameSite` ви можете використовувати константи `Response::SameSiteLax`, `Response::SameSiteStrict` та `Response::SameSiteNone`. -deleteCookie(string $name, string $path=null, string $domain=null, bool $secure=null): void .[method] ------------------------------------------------------------------------------------------------------ -Видаляє файл cookie. За замовчуванням параметри мають такі значення: +deleteCookie(string $name, ?string $path=null, ?string $domain=null, ?bool $secure=null): void .[method] +-------------------------------------------------------------------------------------------------------- +Видаляє cookie. Значення параметрів за замовчуванням: - `$path` з областю дії на всі каталоги (`'/'`) -- `$domain` з областю дії на поточний (під)домен, але не на його піддомени. -- `$secure` залежить від налаштувань у [конфігурації |configuration#HTTP-Cookie] +- `$domain` з областю дії на поточний (під)домен, але не на його піддомени +- `$secure` керується налаштуваннями в [конфігурації |configuration#HTTP cookie] ```php $httpResponse->deleteCookie('lang'); diff --git a/http/uk/sessions.texy b/http/uk/sessions.texy index e7419dbbec..e69a4d6490 100644 --- a/http/uk/sessions.texy +++ b/http/uk/sessions.texy @@ -1,71 +1,71 @@ -Сеанси -****** +Сесії +***** <div class=perex> -HTTP - це протокол без статичних даних, але майже кожному додатку необхідно зберігати стан між запитами, наприклад, вміст кошика. Для цього і використовується сесія. Давайте подивимося +HTTP — це протокол без стану, однак майже кожна програма потребує зберігати стан між запитами, наприклад, вміст кошика покупок. Саме для цього служать сесії або сеанси. Покажемо, - як використовувати сесії - як уникнути конфліктів імен -- як встановити термін дії +- як налаштувати термін дії </div> -Під час використання сесій кожен користувач отримує унікальний ідентифікатор, який називається ID сесії, що передається в cookie. Він слугує ключем до даних сеансу. На відміну від cookie, які зберігаються на стороні браузера, дані сеансу зберігаються на стороні сервера. +При використанні сесій кожен користувач отримує унікальний ідентифікатор, який називається ID сесії, що передається в cookie. Він служить ключем до даних сесії. На відміну від cookies, які зберігаються на стороні браузера, дані в сесії зберігаються на стороні сервера. -Ми налаштовуємо сесію в [конфігурації |configuration#Session], при цьому важливим є вибір часу закінчення терміну дії. +Сесію налаштовуємо в [конфігурації |configuration#Сесія], особливо важливим є вибір терміну дії. -Сесією керує об'єкт [api:Nette\Http\Session], який ви отримуєте, передаючи його за допомогою [ін'єкції залежностей |dependency-injection:passing-dependencies]. У презентаторах просто викликаємо `$session = $this->getSession()`. +Керування сесіями здійснює об'єкт [api:Nette\Http\Session], до якого ви можете отримати доступ, попросивши передати його за допомогою [впровадження залежностей |dependency-injection:passing-dependencies]. У презентерах достатньо лише викликати `$session = $this->getSession()`. -→ Встановлення [та вимоги |@home#Installation] +→ [Встановлення та вимоги |@home#Встановлення] -Запуск сеансу .[#toc-starting-session] -====================================== +Запуск сесії +============ -За замовчуванням Nette автоматично запускає сесію в той момент, коли ми починаємо читати з неї або записувати в неї дані. Щоб запустити сесію вручну, використовуйте `$session->start()`. +Nette за замовчуванням автоматично розпочинає сесію в момент, коли ми починаємо з неї читати або в неї записувати дані. Ручний запуск сесії здійснюється за допомогою `$session->start()`. -PHP надсилає HTTP-заголовки, що впливають на кешування, під час запуску сесії, див. [php:session_cache_limiter], і, можливо, cookie з ідентифікатором сесії. Тому завжди необхідно запускати сесію перед надсиланням будь-якого виводу в браузер, інакше буде викинуто виняток. Тому, якщо ви знаєте, що сесія буде використовуватися під час рендерингу сторінки, запустіть її вручну, наприклад, у презентаторі. +PHP надсилає при запуску сесії HTTP-заголовки, що впливають на кешування, див. [php:session_cache_limiter], і, можливо, cookie з ID сесії. Тому необхідно завжди запускати сесію ще до надсилання будь-якого виводу в браузер, інакше буде викинуто виняток. Якщо ви знаєте, що під час відображення сторінки буде використовуватися сесія, запустіть її вручну заздалегідь, наприклад, у презентері. -У режимі розробника Tracy запускає сесію, оскільки використовує її для відображення смуг перенаправлення і запитів AJAX у панелі Tracy. +У режимі розробки сесію запускає Tracy, оскільки вона використовує її для відображення смуг з перенаправленнями та AJAX-запитами в Tracy Bar. -Розділ .[#toc-section] -====================== +Секції +====== -У чистому PHP сховище даних сесії реалізовано у вигляді масиву, доступного через глобальну змінну `$_SESSION`. Проблема полягає в тому, що додатки зазвичай складаються з декількох незалежних частин, і якщо всім доступний тільки один і той самий масив, рано чи пізно відбудеться зіткнення імен. +У чистому PHP сховище даних сесії реалізовано як масив, доступний через глобальну змінну `$_SESSION`. Проблема полягає в тому, що програми зазвичай складаються з цілої низки взаємно незалежних частин, і якщо всі вони мають доступ лише до одного масиву, рано чи пізно виникне колізія імен. -Nette Framework вирішує цю проблему шляхом поділу всього простору на секції (об'єкти [api:Nette\Http\SessionSection]). При цьому кожна частина використовує свій власний розділ з унікальним ім'ям, і колізії не виникають. +Nette Framework вирішує цю проблему, розділяючи весь простір на секції (об'єкти [api:Nette\Http\SessionSection]). Кожна одиниця потім використовує свою секцію з унікальною назвою, і жодної колізії вже виникнути не може. -Ми отримуємо розділ від менеджера сесій: +Секцію отримуємо з сесії: ```php -$section = $session->getSection('unique name'); +$section = $session->getSection('unique_name'); // Змінено на англійську для прикладу ``` -У презентері достатньо викликати `getSession()` з параметром: +У презентері достатньо використати `getSession()` з параметром: ```php -// $this - доповідач -$section = $this->getSession('unique name'); +// $this є Presenter +$section = $this->getSession('unique_name'); // Змінено на англійську для прикладу ``` -Існування розділу можна перевірити методом `$session->hasSection('unique name')`. +Перевірити існування секції можна методом `$session->hasSection('unique_name')`. -Із самим розділом дуже легко працювати, використовуючи методи `set()`, `get()` і `remove()`: +З самою секцією потім працювати дуже легко за допомогою методів `set()`, `get()` та `remove()`: ```php // запис змінної -$section->set('userName', 'franta'); +$section->set('userName', 'frank'); // Змінено на англійську для прикладу -// читання змінної, повертає null, якщо вона не існує +// читання змінної, поверне null, якщо не існує echo $section->get('userName'); // видалення змінної $section->remove('userName'); ``` -Можна використовувати цикл `foreach` для отримання всіх змінних із секції: +Для отримання всіх змінних із секції можна використовувати цикл `foreach`: ```php foreach ($section as $key => $val) { @@ -74,63 +74,63 @@ foreach ($section as $key => $val) { ``` -Як встановити термін дії .[#toc-how-to-set-expiration] ------------------------------------------------------- +Налаштування терміну дії +------------------------ -Термін дії може бути встановлений для окремих розділів або навіть окремих змінних. Ми можемо дозволити логіну користувача закінчитися через 20 хвилин, але при цьому зберегти вміст кошика. +Для окремих секцій або навіть окремих змінних можна встановити термін дії. Ми можемо, наприклад, дозволити закінчитися терміну дії входу користувача через 20 хвилин, але при цьому продовжувати пам'ятати вміст кошика. ```php -// термін дії розділу закінчується через 20 хвилин +// секція закінчиться через 20 хвилин $section->setExpiration('20 minutes'); ``` -Третій параметр методу `set()` використовується для встановлення терміну дії окремих змінних: +Для налаштування терміну дії окремих змінних служить третій параметр методу `set()`: ```php -// змінна 'flash' закінчується через 30 секунд +// змінна 'flash' закінчиться вже через 30 секунд $section->set('flash', $message, '30 seconds'); ``` .[note] -Пам'ятайте, що час закінчення терміну дії всієї сесії (див. [конфігурацію сесії |configuration#Session]) має дорівнювати або бути більшим за час, встановлений для окремих секцій або змінних. +Не забувайте, що термін дії всієї сесії (див. [конфігурацію сесії |configuration#Сесія]) повинен бути таким самим або більшим, ніж термін, встановлений для окремих секцій чи змінних. -Скасування раніше встановленого терміну дії може бути досягнуто за допомогою методу `removeExpiration()`. Негайне видалення всього розділу забезпечується методом `remove()`. +Скасування раніше встановленого терміну дії досягається методом `removeExpiration()`. Негайне скасування всієї секції забезпечує метод `remove()`. -Події $onStart, $onBeforeWrite .[#toc-events-onstart-onbeforewrite] -------------------------------------------------------------------- +Події $onStart, $onBeforeWrite +------------------------------ -Об'єкт `Nette\Http\Session` має [події |nette:glossary#Events] `$onStart` і `$onBeforeWrite`, тому ви можете додати зворотні виклики, які будуть викликатися після початку сеансу або перед тим, як вона буде записана на диск і потім завершена. +Об'єкт `Nette\Http\Session` має [події |nette:glossary#Події události] `$onStart` та `$onBeforeWrite`, тому ви можете додати callback-и, які будуть викликані після запуску сесії або перед її записом на диск та подальшим завершенням. ```php $session->onBeforeWrite[] = function () { - // записуємо дані в сесію + // запишемо дані в сесію $this->section->set('basket', $this->basket); }; ``` -Управління сесіями .[#toc-session-management] -============================================= +Керування сесіями +================= -Огляд методів класу `Nette\Http\Session` для керування сеансами: +Огляд методів класу `Nette\Http\Session` для керування сесіями: <div class=wiki-methods-brief> start(): void .[method] ----------------------- -Запускає сеанс. +Розпочинає сесію. isStarted(): bool .[method] --------------------------- -Чи запущено сеанс? +Чи розпочата сесія? close(): void .[method] ----------------------- -Завершує сеанс. Сеанс завершується автоматично наприкінці сценарію. +Завершує сесію. Сесія автоматично завершується в кінці виконання скрипта. destroy(): void .[method] @@ -140,71 +140,72 @@ destroy(): void .[method] exists(): bool .[method] ------------------------ -Чи містить HTTP-запит файл cookie з ідентифікатором сесії? +Чи містить HTTP-запит cookie з ID сесії? regenerateId(): void .[method] ------------------------------ -Генерує новий випадковий ідентифікатор сесії. Дані залишаються незмінними. +Генерує нове випадкове ID сесії. Дані залишаються збереженими. getId(): string .[method] ------------------------- -Повертає ідентифікатор сесії. +Повертає ID сесії. </div> -Конфігурація .[#toc-configuration] ----------------------------------- +Конфігурація +------------ -Ми налаштовуємо сесію в [конфігурації |configuration#Session]. Якщо ви пишете додаток, який не використовує DI-контейнер, використовуйте ці методи для конфігурації. Вони повинні бути викликані до запуску сесії. +Сесію налаштовуємо в [конфігурації |configuration#Сесія]. Якщо ви пишете програму, яка не використовує DI-контейнер, для конфігурації служать ці методи. Вони повинні бути викликані ще до запуску сесії. <div class=wiki-methods-brief> setName(string $name): static .[method] --------------------------------------- -Встановлює ім'я cookie, яке використовується для передачі ідентифікатора сесії. За замовчуванням використовується ім'я `PHPSESSID`. Це корисно, якщо ви запускаєте кілька різних додатків на одному сайті. +Встановлює назву cookie, в якій передається ID сесії. Стандартна назва — `PHPSESSID`. Це корисно у випадку, коли в рамках одного сайту ви запускаєте кілька різних програм. getName(): string .[method] --------------------------- -Повертає ім'я сеансового файлу cookie. +Повертає назву cookie, в якій передається ID сесії. setOptions(array $options): static .[method] -------------------------------------------- -Налаштовує сесію. Можна встановити всі [директиви сесії |https://www.php.net/manual/en/session.configuration.php] PHP (у форматі camelCase, наприклад, написати `savePath` замість `session.save_path`), а також [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. +Конфігурує сесію. Можна налаштовувати всі PHP [директиви сесії |https://www.php.net/manual/en/session.configuration.php] (у форматі camelCase, наприклад, замість `session.save_path` запишемо `savePath`), а також [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters]. setExpiration(?string $time): static .[method] ---------------------------------------------- -Встановлює час бездіяльності, після якого сесія завершується. +Встановлює час неактивності, після якого сесія закінчиться. -setCookieParameters(string $path, string $domain=null, bool $secure=null, string $samesite=null): static .[method] ------------------------------------------------------------------------------------------------------------------- -Встановлює параметри для кукі. Значення параметрів за замовчуванням можна змінити в розділі [configuration |configuration#Session-Cookie]. +setCookieParameters(string $path, ?string $domain=null, ?bool $secure=null, ?string $samesite=null): static .[method] +--------------------------------------------------------------------------------------------------------------------- +Налаштування параметрів для cookie. Значення параметрів за замовчуванням можна змінити в [конфігурації |configuration#Session cookie]. setSavePath(string $path): static .[method] ------------------------------------------- -Встановлює каталог, у якому зберігаються файли сесій. +Встановлює каталог, куди зберігаються файли сесій. setHandler(\SessionHandlerInterface $handler): static .[method] --------------------------------------------------------------- -Встановлює користувацький обробник, див. [документацію PHP |https://www.php.net/manual/en/class.sessionhandlerinterface.php]. +Налаштування власного обробника, див. [документацію PHP|https://www.php.net/manual/en/class.sessionhandlerinterface.php]. </div> -Безпека насамперед .[#toc-safety-first] -======================================= +Безпека перш за все +=================== -Сервер припускає, що він спілкується з одним і тим самим користувачем доти, доки запити містять один і той самий ідентифікатор сесії. Завдання механізмів безпеки - гарантувати, що така поведінка дійсно працює і що немає можливості підмінити або вкрасти ідентифікатор. +Сервер припускає, що він спілкується постійно з тим самим користувачем, доки запити супроводжуються тим самим ID сесії. Завданням механізмів безпеки є забезпечення того, щоб це справді було так, і щоб неможливо було ідентифікатор вкрасти або підсунути. -Саме тому Nette Framework правильно налаштовує директиви PHP для передавання ідентифікатора сесії тільки в cookies, для запобігання доступу з JavaScript і для ігнорування ідентифікаторів в URL. Ба більше, у критичні моменти, такі як вхід користувача в систему, він генерує новий ідентифікатор сесії. +Тому Nette Framework правильно конфігурує PHP-директиви, щоб ID сесії передавався лише в cookie, зробив його недоступним для JavaScript та ігнорував можливі ідентифікатори в URL. Крім того, у критичні моменти, наприклад, при вході користувача, він генерує нове ID сесії. -Функція ini_set використовується для налаштування PHP, але, на жаль, її використання заборонено на деяких хостингах. Якщо це ваш випадок, спробуйте попросити хостинг-провайдера дозволити цю функцію для вас або хоча б правильно налаштувати його сервер. .[note] +.[note] +Для конфігурації PHP використовується функція ini_set, яку, на жаль, деякі хостинги забороняють. Якщо це стосується і вашого хостера, спробуйте домовитися з ним, щоб він дозволив вам використовувати цю функцію або хоча б налаштував сервер. diff --git a/http/uk/urls.texy b/http/uk/urls.texy index c3d2f12605..72448ef8be 100644 --- a/http/uk/urls.texy +++ b/http/uk/urls.texy @@ -1,16 +1,16 @@ -Парсер і конструктор URL -************************ +Робота з URL +************ .[perex] -Класи [Url |#Url], [UrlImmutable |#UrlImmutable] і [UrlScript |#UrlScript] дають змогу легко керувати, розбирати і маніпулювати URL-адресами. +Класи [#Url], [#UrlImmutable] та [#UrlScript] дозволяють легко генерувати, парсити та маніпулювати URL. -→ [Встановлення та вимоги |@home#Installation] +→ [Встановлення та вимоги |@home#Встановлення] Url === -Клас [api:Nette\Http\Url] дає змогу легко працювати з URL та його окремими компонентами, які показані на цій діаграмі: +Клас [api:Nette\Http\Url] дозволяє легко працювати з URL та його окремими компонентами, які відображає ця схема: /--pre scheme user password host port path query fragment @@ -22,7 +22,7 @@ Url hostUrl authority \-- -Генерація URL інтуїтивно зрозуміла: +Генерування URL є інтуїтивним: ```php use Nette\Http\Url; @@ -36,7 +36,7 @@ $url->setScheme('https') echo $url; // 'https://localhost/edit?foo=bar' ``` -Ви також можете розібрати URL і потім маніпулювати ним: +Можна також розпарсити URL і далі з ним маніпулювати: ```php $url = new Url( @@ -44,62 +44,94 @@ $url = new Url( ); ``` -Для отримання або зміни окремих компонентів URL доступні такі методи: +Клас `Url` реалізує інтерфейс `JsonSerializable` і має метод `__toString()`, тому об'єкт можна вивести або використовувати в даних, переданих до `json_encode()`. + +```php +echo $url; +echo json_encode([$url]); +``` + + +Компоненти URL .[method] +------------------------ + +Для повернення або зміни окремих компонентів URL вам доступні ці методи: .[language-php] -| Setter | Getter | Повернуте значення +| Setter | Getter | Значення, що повертається |-------------------------------------------------------------------------------------------- -| `setScheme(string $scheme)`| `getScheme(): string`| `'http'` -| `setUser(string $user)`| `getUser(): string`| `'john'` -| `setPassword(string $password)`| `getPassword(): string`| `'xyz*12'` -| `setHost(string $host)`| `getHost(): string`| `'nette.org'` -| `setPort(int $port)`| `getPort(): ?int`| `8080` -| | `getDefaultPort(): ?int`| `80` -| `setPath(string $path)`| `getPath(): string`| `'/en/download'` -| `setQuery(string\|array $query)`| `getQuery(): string`| `'name=param'` -| `setFragment(string $fragment)`| `getFragment(): string`| `'footer'` -| | `getAuthority(): string`| `'nette.org:8080'` -| | `getHostUrl(): string`| `'http://nette.org:8080'` -| | `getAbsoluteUrl(): string` | повний URL - -Ми також можемо працювати з окремими параметрами запиту, використовуючи: +| `setScheme(string $scheme)` | `getScheme(): string` | `'http'` +| `setUser(string $user)` | `getUser(): string` | `'john'` +| `setPassword(string $password)` | `getPassword(): string` | `'xyz*12'` +| `setHost(string $host)` | `getHost(): string` | `'nette.org'` +| `setPort(int $port)` | `getPort(): ?int` | `8080` +| | `getDefaultPort(): ?int` | `80` +| `setPath(string $path)` | `getPath(): string` | `'/en/download'` +| `setQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` +| `setFragment(string $fragment)` | `getFragment(): string` | `'footer'` +| | `getAuthority(): string` | `'john:xyz%2A12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | цілий URL + +Попередження: Коли ви працюєте з URL, отриманим з [HTTP-запиту|request], майте на увазі, що він не міститиме фрагмент, оскільки браузер його не надсилає на сервер. + +Ми можемо працювати і з окремими query-параметрами за допомогою: .[language-php] -| Setter | Getter +| Setter | Getter |--------------------------------------------------- -| `setQuery(string\|array $query)` | `getQueryParameters(): array` -| `setQueryParameter(string $name, $val)`| `getQueryParameter(string $name)` +| `setQuery(string\|array $query)` | `getQueryParameters(): array` +| `setQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` -Метод `getDomain(int $level = 2)` повертає праву або ліву частину хоста. Ось як це працює, якщо хост - `www.nette.org`: + +getDomain(int $level = 2): string .[method] +------------------------------------------- +Повертає праву чи ліву частину хоста. Так це працює, якщо хост — `www.nette.org`: .[language-php] -| `getDomain(1)` | `'org'` -| `getDomain(2)` | `'nette.org'` -| `getDomain(3)` | `'www.nette.org'` -| `getDomain(0)` | `'www.nette.org'` -| `getDomain(-1)` | `'www.nette'` -| `getDomain(-2)` | `'www'` -| `getDomain(-3)` | `''` +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `''` -Клас `Url` реалізує інтерфейс `JsonSerializable` і має метод `__toString()`, щоб об'єкт можна було роздрукувати або використати в даних, що передаються в `json_encode()`. +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +Перевіряє, чи два URL однакові. ```php -echo $url; -echo json_encode([$url]); +$url->isEqual('https://nette.org'); ``` -Метод `isEqual(string|Url $anotherUrl): bool` перевіряє, чи ідентичні два URL. + +Url::isAbsolute(string $url): bool .[method]{data-version:3.3.2} +---------------------------------------------------------------- +Перевіряє, чи є URL абсолютним. URL вважається абсолютним, якщо він починається зі схеми (наприклад, http, https, ftp), за якою слідує двокрапка. ```php -$url->isEqual('https://nette.org'); +Url::isAbsolute('https://nette.org'); // true +Url::isAbsolute('//nette.org'); // false +``` + + +Url::removeDotSegments(string $path): string .[method]{data-version:3.3.2} +-------------------------------------------------------------------------- +Нормалізує шлях в URL, видаляючи спеціальні сегменти `.` та `..`. Метод видаляє надлишкові елементи шляху так само, як це роблять веб-браузери. + +```php +Url::removeDotSegments('/path/../subtree/./file.txt'); // '/subtree/file.txt' +Url::removeDotSegments('/../foo/./bar'); // '/foo/bar' +Url::removeDotSegments('./today/../file.txt'); // 'file.txt' ``` -UrlImmutable .[#toc-urlimmutable] -================================= +UrlImmutable +============ -Клас [api:Nette\Http\UrlImmutable] є незмінною альтернативою класу `Url` (так само, як у PHP `DateTimeImmutable` є незмінною альтернативою `DateTime`). Замість сеттерів він має так звані withers, які не змінюють об'єкт, а повертають нові екземпляри зі зміненим значенням: +Клас [api:Nette\Http\UrlImmutable] є immutable (незмінною) альтернативою класу [#Url] (подібно до того, як у PHP `DateTimeImmutable` є незмінною альтернативою `DateTime`). Замість сеттерів він має так звані wither-и, які не змінюють об'єкт, а повертають нові екземпляри зі зміненим значенням: ```php use Nette\Http\UrlImmutable; @@ -111,53 +143,96 @@ $url = new UrlImmutable( $newUrl = $url ->withUser('') ->withPassword('') - ->withPath('/en/'); + ->withPath('/uk/'); + +echo $newUrl; // 'http://john:xyz%2A12@nette.org:8080/uk/?name=param#footer' +``` + +Клас `UrlImmutable` реалізує інтерфейс `JsonSerializable` і має метод `__toString()`, тому об'єкт можна вивести або використовувати в даних, переданих до `json_encode()`. -echo $newUrl; // 'http://nette.org:8080/en/?name=param#footer' +```php +echo $url; +echo json_encode([$url]); ``` -Для отримання або зміни окремих компонентів URL доступні такі методи: + +Компоненти URL .[method] +------------------------ + +Для повернення або зміни окремих компонентів URL служать методи: .[language-php] -| Wither | Getter | Повернуте значення +| Wither | Getter | Значення, що повертається |-------------------------------------------------------------------------------------------- -| `withScheme(string $scheme)`| `getScheme(): string`| `'http'` -| `withUser(string $user)`| `getUser(): string`| `'john'` -| `withPassword(string $password)`| `getPassword(): string`| `'xyz*12'` -| `withHost(string $host)`| `getHost(): string`| `'nette.org'` -| `withPort(int $port)`| `getPort(): ?int`| `8080` -| | `getDefaultPort(): ?int`| `80` -| `withPath(string $path)`| `getPath(): string`| `'/en/download'` -| `withQuery(string\|array $query)`| `getQuery(): string`| `'name=param'` -| `withFragment(string $fragment)`| `getFragment(): string`| `'footer'` -| | `getAuthority(): string`| `'nette.org:8080'` -| | `getHostUrl(): string`| `'http://nette.org:8080'` -| | `getAbsoluteUrl(): string` | повний URL - -Ми також можемо працювати з окремими параметрами запиту, використовуючи: +| `withScheme(string $scheme)` | `getScheme(): string` | `'http'` +| `withUser(string $user)` | `getUser(): string` | `'john'` +| `withPassword(string $password)` | `getPassword(): string` | `'xyz*12'` +| `withHost(string $host)` | `getHost(): string` | `'nette.org'` +| `withPort(int $port)` | `getPort(): ?int` | `8080` +| | `getDefaultPort(): ?int` | `80` +| `withPath(string $path)` | `getPath(): string` | `'/en/download'` +| `withQuery(string\|array $query)` | `getQuery(): string` | `'name=param'` +| `withFragment(string $fragment)` | `getFragment(): string` | `'footer'` +| | `getAuthority(): string` | `'john:xyz%2A12@nette.org:8080'` +| | `getHostUrl(): string` | `'http://john:xyz%2A12@nette.org:8080'` +| | `getAbsoluteUrl(): string` | цілий URL + +Метод `withoutUserInfo()` видаляє `user` та `password`. + +Ми можемо працювати і з окремими query-параметрами за допомогою: .[language-php] -| Wither | Getter +| Wither | Getter |----------------------------------------------- -| `withQuery(string\|array $query)` | `getQueryParameters(): array` -| `withQueryParameter(string $name, $val)` | | `getQueryParameter(string $name)` +| `withQuery(string\|array $query)` | `getQueryParameters(): array` +| `withQueryParameter(string $name, $val)` | `getQueryParameter(string $name)` -Метод `getDomain(int $level = 2)` працює так само, як і метод у `Url`. Метод `withoutUserInfo()` видаляє `user` і `password`. -Клас `UrlImmutable` реалізує інтерфейс `JsonSerializable` і має метод `__toString()`, щоб об'єкт можна було роздрукувати або використати в даних, що передаються в `json_encode()`. +getDomain(int $level = 2): string .[method] +------------------------------------------- +Повертає праву чи ліву частину хоста. Так це працює, якщо хост — `www.nette.org`: + +.[language-php] +| `getDomain(1)` | `'org'` +| `getDomain(2)` | `'nette.org'` +| `getDomain(3)` | `'www.nette.org'` +| `getDomain(0)` | `'www.nette.org'` +| `getDomain(-1)` | `'www.nette'` +| `getDomain(-2)` | `'www'` +| `getDomain(-3)` | `''` + + +resolve(string $reference): UrlImmutable .[method]{data-version:3.3.2} +---------------------------------------------------------------------- +Виводить абсолютний URL так само, як браузер обробляє посилання на HTML-сторінці: +- якщо посилання є абсолютним URL (містить схему), воно використовується без змін +- якщо посилання починається з `//`, переймається лише схема з поточного URL +- якщо посилання починається з `/`, створюється абсолютний шлях від кореня домену +- в інших випадках URL складається відносно поточного шляху ```php -echo $url; -echo json_encode([$url]); +$url = new UrlImmutable('https://example.com/path/page'); +echo $url->resolve('../foo'); // 'https://example.com/foo' +echo $url->resolve('/bar'); // 'https://example.com/bar' +echo $url->resolve('sub/page.html'); // 'https://example.com/path/sub/page.html' +``` + + +isEqual(string|Url $anotherUrl): bool .[method] +----------------------------------------------- +Перевіряє, чи два URL однакові. + +```php +$url->isEqual('https://nette.org'); ``` -Метод `isEqual(string|Url $anotherUrl): bool` перевіряє, чи ідентичні два URL. +UrlScript +========= -UrlScript .[#toc-urlscript] -=========================== +Клас [api:Nette\Http\UrlScript] є нащадком [#UrlImmutable] і розширює його додатковими віртуальними компонентами URL, такими як кореневий каталог проєкту тощо. Так само, як і батьківський клас, він є immutable (незмінним) об'єктом. -Клас [api:Nette\Http\UrlScript] є нащадком класу `UrlImmutable` і додатково розрізняє ці логічні частини URL: +Наступна діаграма відображає компоненти, які розпізнає UrlScript: /--pre baseUrl basePath relativePath relativeUrl @@ -169,17 +244,23 @@ UrlScript .[#toc-urlscript] scriptPath pathInfo \-- -Для отримання цих частин доступні такі методи: +- `baseUrl` — це базова URL-адреса програми, включаючи домен та частину шляху до кореневого каталогу програми +- `basePath` — це частина шляху до кореневого каталогу програми +- `scriptPath` — це шлях до поточного скрипта +- `relativePath` — це назва скрипта (можливо, з додатковими сегментами шляху) відносно basePath +- `relativeUrl` — це вся частина URL після baseUrl, включаючи query string та фрагмент. +- `pathInfo` — сьогодні вже мало використовувана частина URL після назви скрипта + +Для повернення частин URL доступні методи: .[language-php] -| Getter | Повернуте значення +| Getter | Значення, що повертається |------------------------------------------------ -| `getScriptPath(): string`| `'/admin/script.php'` -| `getBasePath(): string`| `'/admin/'` -| `getBaseUrl(): string`| `'http://nette.org/admin/'` -| `getRelativePath(): string`| `'script.php'` -| `getRelativeUrl(): string`| `'script.php/pathinfo/?name=param#footer'` -| `getPathInfo(): string`| `'/pathinfo/'` - - -Ми не створюємо об'єкти `UrlScript` безпосередньо, але метод [Nette\Http\Request::getUrl() |request] повертає його. +| `getScriptPath(): string` | `'/admin/script.php'` +| `getBasePath(): string` | `'/admin/'` +| `getBaseUrl(): string` | `'http://nette.org/admin/'` +| `getRelativePath(): string` | `'script.php'` +| `getRelativeUrl(): string` | `'script.php/pathinfo/?name=param#footer'` +| `getPathInfo(): string` | `'/pathinfo/'` + +Об'єкти `UrlScript` зазвичай безпосередньо не створюємо, але їх повертає метод [Nette\Http\Request::getUrl()|request] з уже правильно налаштованими компонентами для поточного HTTP-запиту. diff --git a/latte/bg/@home.texy b/latte/bg/@home.texy index e2f335602d..a9fdedf8d8 100644 --- a/latte/bg/@home.texy +++ b/latte/bg/@home.texy @@ -1 +1,2 @@ -{{maintitle: Latte - Най-сигурните и наистина интуитивни шаблони за PHP}} +{{maintitle: Latte – най-сигурните & наистина интуитивни шаблони за PHP}} +{{description: Latte е най-сигурната система за шаблони за PHP. Предотвратява много уязвимости в сигурността. Ще оцените неговия интуитивен синтаксис и много полезни функции.}} diff --git a/latte/bg/@left-menu.texy b/latte/bg/@left-menu.texy index 2edb276425..d1c5d99428 100644 --- a/latte/bg/@left-menu.texy +++ b/latte/bg/@left-menu.texy @@ -1,24 +1,24 @@ -- [Започване на работа |Guide] -- Концепции - - [Безопасността на първо място |Safety First] - - [Наследяване на шаблона |Template Inheritance] - - [Тип система |Type System] - - [Пясъчник |Sandbox] +- [Да започнем с Latte |guide] +- [Защо да използваме шаблони? |why-use] +- Концепции ⚗️ + - [Безопасността преди всичко |safety-first] + - [Наследяване на шаблони |Template Inheritance] + - [Типова система |type-system] + - [Sandbox |Sandbox] -- За дизайнери - - [Синтаксис |Syntax] - - [Етикети |Tags] - - [Филтри |Filters] - - [Функции |Functions] +- За дизайнери 🎨 + - [Синтаксис |syntax] + - [Тагове |tags] + - [Филтри |filters] + - [Функции |functions] - [Съвети и трикове |recipes] -- За разработчици - - [Практически съвети за разработчици |develop] - - [Удължаване на Latte |Extending Latte] - - [Създаване на разширение |creating-extension] +- За разработчици 🧮 + - [Процедури за разработчици |develop] + - [Разширяване на Latte |extending-latte] -- [Готварска книга |cookbook/@home] +- [Ръководства и процедури 💡|cookbook/@home] - [Миграция от Twig |cookbook/migration-from-twig] - - ... [повече |cookbook/@home] + - [… други |cookbook/@home] -- "Детска площадка .[link-external]":https://fiddle.nette.org/latte/ .{padding-top:1em} +- "Плейграунд .[link-external]":https://fiddle.nette.org/latte/ .{padding-top:1em} diff --git a/latte/bg/@menu.texy b/latte/bg/@menu.texy index bef0aab194..c6324fc01c 100644 --- a/latte/bg/@menu.texy +++ b/latte/bg/@menu.texy @@ -1,12 +1,12 @@ <ul> -- [Главная |@home] -- [Документация |Guide] +- [Въведение |@home] +- [Документация |guide] - "GitHub .[link-external]":https://github.com/nette/latte -<li class="dropdown"><a class="dropdown-toggle" href="#">Инструменты</a> +<li class="dropdown"><a class="dropdown-toggle" href="#">Инструменти</a> <ul class="dropdown-flyout"> -- [скрипка |https://fiddle.nette.org] -- [php2Latte |https://php2latte.nette.org] -- [twig2Latte |https://twig2latte.nette.org] +- [fiddle |https://fiddle.nette.org] +- [php2Latte |https://fiddle.nette.org/php2latte/] +- [twig2Latte|https://fiddle.nette.org/twig2latte/] </ul> </li> </ul> diff --git a/latte/bg/@meta.texy b/latte/bg/@meta.texy new file mode 100644 index 0000000000..4297aeff19 --- /dev/null +++ b/latte/bg/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документация на Latte}} diff --git a/latte/bg/compiler-passes.texy b/latte/bg/compiler-passes.texy new file mode 100644 index 0000000000..198667999f --- /dev/null +++ b/latte/bg/compiler-passes.texy @@ -0,0 +1,555 @@ +Компилационни проходи +********************* + +.[perex] +Компилационните проходи предоставят мощен механизъм за анализ и модификация на Latte шаблони *след* тяхното парсване в абстрактно синтактично дърво (AST) и *преди* генерирането на финалния PHP код. Това позволява напреднала манипулация на шаблони, оптимизации, проверки за сигурност (като Sandbox) и събиране на информация за шаблоните. Това ръководство ще ви преведе през създаването на собствени компилационни проходи. + + +Какво е компилационен проход? +============================= + +За да разберете ролята на компилационните проходи, погледнете [процеса на компилация на Latte |custom-tags#Разбиране на процеса на компилация]. Както можете да видите, компилационните проходи оперират в ключова фаза, позволявайки дълбока намеса между първоначалното парсване и финалния изход на кода. + +По същество, компилационният проход е просто PHP callable обект (като функция, статичен метод или метод на инстанция), който приема един аргумент: коренния възел на AST на шаблона, който винаги е инстанция на `Latte\Compiler\Nodes\TemplateNode`. + +Основната цел на компилационния проход обикновено е една или и двете от следните: + +- Анализ: Обхождане на AST и събиране на информация за шаблона (напр. намиране на всички дефинирани блокове, проверка на използването на специфични тагове, осигуряване на спазването на определени ограничения за сигурност). +- Модификация: Промяна на структурата на AST или атрибутите на възлите (напр. автоматично добавяне на HTML атрибути, оптимизиране на определени комбинации от тагове, замяна на остарели тагове с нови, прилагане на правила на sandbox). + + +Регистрация +=========== + +Компилационните проходи се регистрират с помощта на метода [`getPasses()` на разширението |extending-latte#getPasses]. Този метод връща асоциативен масив, където ключовете са уникални имена на проходите (използвани вътрешно и за сортиране), а стойностите са PHP callable обекти, имплементиращи логиката на прохода. + +```php +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Extension; + +class MyExtension extends Extension +{ + public function getPasses(): array + { + return [ + 'modificationPass' => $this->modifyTemplateAst(...), + // ... други проходи ... + ]; + } + + public function modifyTemplateAst(TemplateNode $templateNode): void + { + // Имплементация... + } +} +``` + +Проходите, регистрирани от основните разширения на Latte и вашите собствени разширения, се изпълняват последователно. Редът може да бъде важен, особено ако един проход зависи от резултатите или модификациите на друг. Latte предоставя помощен механизъм за контрол на този ред, ако е необходимо; вижте документацията за [`Extension::getPasses()` |extending-latte#getPasses] за подробности. + + +Пример за AST +============= + +За по-добра представа за AST, добавяме пример. Това е изходният шаблон: + +```latte +{foreach $category->getItems() as $item} + <li>{$item->name|upper}</li> + {else} + no items found +{/foreach} +``` + +А това е неговото представяне под формата на AST: + +/--pre +Latte\Compiler\Nodes\<b>TemplateNode</b>( + Latte\Compiler\Nodes\<b>FragmentNode</b>( + - Latte\Essential\Nodes\<b>ForeachNode</b>( + expression: Latte\Compiler\Nodes\Php\Expression\<b>MethodCallNode</b>( + object: Latte\Compiler\Nodes\Php\Expression\<b>VariableNode</b>('$category') + name: Latte\Compiler\Nodes\Php\<b>IdentifierNode</b>('getItems') + ) + value: Latte\Compiler\Nodes\Php\Expression\<b>VariableNode</b>('$item') + content: Latte\Compiler\Nodes\<b>FragmentNode</b>( + - Latte\Compiler\Nodes\<b>TextNode</b>(' ') + - Latte\Compiler\Nodes\<b>Html\ElementNode</b>('li')( + content: Latte\Essential\Nodes\<b>PrintNode</b>( + expression: Latte\Compiler\Nodes\Php\Expression\<b>PropertyFetchNode</b>( + object: Latte\Compiler\Nodes\Php\Expression\<b>VariableNode</b>('$item') + name: Latte\Compiler\Nodes\Php\<b>IdentifierNode</b>('name') + ) + modifier: Latte\Compiler\Nodes\Php\<b>ModifierNode</b>( + filters: + - Latte\Compiler\Nodes\Php\<b>FilterNode</b>('upper') + ) + ) + ) + ) + else: Latte\Compiler\Nodes\<b>FragmentNode</b>( + - Latte\Compiler\Nodes\<b>TextNode</b>('no items found') + ) + ) + ) +) +\-- + + +Обхождане на AST с помощта на `NodeTraverser` +============================================= + +Ръчното писане на рекурсивни функции за обхождане на сложната структура на AST е уморително и податливо на грешки. Latte предоставя специален инструмент за тази цел: [api:Latte\Compiler\NodeTraverser]. Този клас имплементира [дизайн патърна Visitor |https://en.wikipedia.org/wiki/Visitor_pattern], благодарение на който обхождането на AST става систематично и лесно управляемо. + +Основното използване включва създаване на инстанция на `NodeTraverser` и извикване на нейния метод `traverse()`, като се предаде коренният възел на AST и един или два "visitor" callable обекта: + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; + +(new NodeTraverser)->traverse( + $templateNode, + + // 'enter' visitor: Извиква се при влизане във възел (преди неговите деца) + enter: function (Node $node) { + echo "Влизане във възел от тип: " . $node::class . "\n"; + // Тук можете да изследвате възела + if ($node instanceof Nodes\TextNode) { + // echo "Намерен текст: " . $node->content . "\n"; + } + }, + + // 'leave' visitor: Извиква се при напускане на възел (след неговите деца) + leave: function (Node $node) { + echo "Напускане на възел от тип: " . $node::class . "\n"; + // Тук можете да извършвате действия след обработка на децата + }, +); +``` + +Можете да предоставите само `enter` visitor, само `leave` visitor, или и двата, в зависимост от вашите нужди. + +**`enter(Node $node)`:** Тази функция се изпълнява за всеки възел **преди** обхождащият да посети което и да е от децата на този възел. Полезна е за: + +- Събиране на информация при обхождане на дървото надолу. +- Вземане на решения *преди* обработката на децата (като решение за тяхното пропускане, вижте [#Оптимизиране на обхождането]). +- Потенциални корекции на възела преди посещение на децата (по-рядко). + +**`leave(Node $node)`:** Тази функция се изпълнява за всеки възел **след** като всички негови деца (и техните цели поддървета) са напълно посетени (както влизане, така и напускане). Това е най-честото място за: + +И двата визитора `enter` и `leave` могат по избор да връщат стойност, за да повлияят на процеса на обхождане. Връщането на `null` (или нищо) продължава обхождането нормално, връщането на инстанция на `Node` замества текущия възел, а връщането на специални константи като `NodeTraverser::RemoveNode` или `NodeTraverser::StopTraversal` модифицира потока, както е обяснено в следващите секции. + + +Как работи обхождането +---------------------- + +`NodeTraverser` вътрешно използва метода `getIterator()`, който трябва да бъде имплементиран от всеки клас `Node` (както беше обсъдено в [Създаване на собствени тагове |custom-tags#Имплементиране на getIterator за подвъзли]). Итерира през децата, получени с помощта на `getIterator()`, рекурсивно извиква `traverse()` върху тях и гарантира, че `enter` и `leave` визиторите се извикват в правилния ред „първо в дълбочина“ за всеки възел в дървото, достъпен чрез итератори. Това отново подчертава защо правилно имплементираният `getIterator()` във вашите собствени тагови възли е абсолютно необходим за правилното функциониране на компилационните проходи. + +Нека напишем прост проход, който брои колко пъти в шаблона е използван тагът `{do}` (представен от `Latte\Essential\Nodes\DoNode`). + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Essential\Nodes\DoNode; + +function countDoTags(TemplateNode $templateNode): void +{ + $count = 0; + (new NodeTraverser)->traverse( + $templateNode, + enter: function (Node $node) use (&$count): void { + if ($node instanceof DoNode) { + $count++; + } + }, + // 'leave' visitor не е необходим за тази задача + ); + + echo "Намерен таг {do} $count пъти.\n"; +} + +$latte = new Latte\Engine; +$ast = $latte->parse($templateSource); +countDoTags($ast); +``` + +В този пример ни беше необходим само visitor `enter`, за да проверим типа на всеки посетен възел. + +След това ще разгледаме как тези визитори действително модифицират AST. + + +Модификация на AST +================== + +Една от основните цели на компилационните проходи е модификацията на абстрактното синтактично дърво. Това позволява мощни трансформации, оптимизации или налагане на правила директно върху структурата на шаблона преди генерирането на PHP код. `NodeTraverser` предоставя няколко начина за постигане на това в рамките на визиторите `enter` и `leave`. + +**Важна забележка:** Модификацията на AST изисква внимание. Неправилните промени – като премахване на основни възли или замяна на възел с несъвместим тип – могат да доведат до грешки по време на генерирането на код или да причинят неочаквано поведение по време на изпълнение на програмата. Винаги тествайте обстойно вашите модификационни проходи. + + +Промяна на свойствата на възлите +-------------------------------- + +Най-простият начин за модифициране на дървото е директната промяна на **публичните свойства** на възлите, посетени по време на обхождането. Всички възли съхраняват своите парснати аргументи, съдържание или атрибути в публични свойства. + +**Пример:** Нека създадем проход, който намира всички статични текстови възли (`TextNode`, представляващи обикновен HTML или текст извън Latte тагове) и преобразува тяхното съдържание на главни букви *директно в AST*. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\TextNode; + +function uppercaseStaticText(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // Можем да използваме 'enter', тъй като TextNode няма деца за обработка + enter: function (Node $node) { + // Този възел статичен текстов блок ли е? + if ($node instanceof TextNode) { + // Да! Директно ще променим неговото публично свойство 'content'. + $node->content = mb_strtoupper(html_entity_decode($node->content)); + } + // Не е необходимо нищо да се връща; промяната е приложена директно. + }, + ); +} +``` + +В този пример visitor `enter` проверява дали текущият `$node` е от тип `TextNode`. Ако е така, директно актуализираме неговото публично свойство `$content` с помощта на `mb_strtoupper()`. Това директно променя съдържанието на статичния текст, съхранен в AST *преди* генерирането на PHP код. Тъй като модифицираме обекта директно, не е необходимо да връщаме нищо от визитора. + +Ефект: Ако шаблонът съдържаше `<p>Hello</p>{= $var }<span>World</span>`, след този проход AST ще представя нещо като: `<p>HELLO</p>{= $var }<span>WORLD</span>`. Това НЕ ВЛИЯЕ на съдържанието на $var. + + +Замяна на възли +--------------- + +По-мощна техника за модификация е пълната замяна на възел с друг. Това се извършва чрез **връщане на нова инстанция на `Node`** от визитора `enter` или `leave`. `NodeTraverser` след това замества оригиналния възел с върнатия в структурата на родителския възел. + +**Пример:** Нека създадем проход, който намира всички употреби на константата `PHP_VERSION` (представена от `ConstantFetchNode`) и ги заменя директно с низов литерал (`StringNode`), съдържащ *действителната* версия на PHP, открита *по време на компилация*. Това е форма на оптимизация по време на компилация. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\Php\Expression\ConstantFetchNode; +use Latte\Compiler\Nodes\Php\Scalar\StringNode; + +function inlinePhpVersion(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // 'leave' често се използва за замяна, като гарантира, че децата (ако има такива) + // се обработват първо, въпреки че 'enter' също би работил тук. + leave: function (Node $node) { + // Този възел достъп до константа ли е и името на константата 'PHP_VERSION' ли е? + if ($node instanceof ConstantFetchNode && (string) $node->name === 'PHP_VERSION') { + // Създаваме нов StringNode, съдържащ текущата версия на PHP + $newNode = new StringNode(PHP_VERSION); + + // Незадължително, но добра практика: копираме информацията за позицията + $newNode->position = $node->position; + + // Връщаме новия StringNode. Traverser ще замени + // оригиналния ConstantFetchNode с този $newNode. + return $newNode; + } + // Ако не върнем Node, оригиналният $node се запазва. + }, + ); +} +``` + +Тук visitor `leave` идентифицира специфичния `ConstantFetchNode` за `PHP_VERSION`. След това създава изцяло нов `StringNode`, съдържащ стойността на константата `PHP_VERSION` *по време на компилация*. Връщайки този `$newNode`, той казва на обхождащия да замени оригиналния `ConstantFetchNode` в AST. + +Ефект: Ако шаблонът съдържаше `{= PHP_VERSION }` и компилацията се изпълнява на PHP 8.2.1, AST след този проход ефективно ще представя `{= '8.2.1' }`. + +**Избор на `enter` срещу `leave` за замяна:** + +- Използвайте `leave`, ако създаването на новия възел зависи от резултатите от обработката на децата на стария възел, или ако просто искате да гарантирате, че децата са посетени преди замяната (често срещана практика). +- Използвайте `enter`, ако искате да замените възел *преди* неговите деца изобщо да бъдат посетени. + + +Премахване на възли +------------------- + +Можете напълно да премахнете възел от AST, като върнете специалната константа `NodeTraverser::RemoveNode` от визитора. + +**Пример:** Нека премахнем всички коментари на шаблона (`{* ... *}`), които са представени от `CommentNode` в AST, генериран от ядрото на Latte (въпреки че обикновено се обработват по-рано, това служи като пример). + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\CommentNode; + +function removeCommentNodes(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // 'enter' тук е добре, тъй като не се нуждаем от информация за децата, за да премахнем коментара + enter: function (Node $node) { + if ($node instanceof CommentNode) { + // Сигнализираме на обхождащия да премахне този възел от AST + return NodeTraverser::RemoveNode; + } + }, + ); +} +``` + +**Внимание:** Използвайте `RemoveNode` внимателно. Премахването на възел, който съдържа основно съдържание или влияе на структурата (като премахване на съдържателния възел на цикъл), може да доведе до повредени шаблони или невалиден генериран код. Най-безопасно е за възли, които са наистина незадължителни или самостоятелни (като коментари или дебъгващи тагове) или за празни структурни възли (напр. празен `FragmentNode` може да бъде безопасно премахнат в някои контексти чрез проход за почистване). + +Тези три метода - промяна на свойства, замяна на възли и премахване на възли - предоставят основните инструменти за манипулиране на AST в рамките на вашите компилационни проходи. + + +Оптимизиране на обхождането +=========================== + +AST на шаблоните може да бъде доста голям, потенциално съдържащ хиляди възли. Обхождането на всеки отделен възел може да бъде ненужно и да повлияе на производителността на компилацията, ако вашият проход се интересува само от специфични части на дървото. `NodeTraverser` предлага начини за оптимизиране на обхождането: + + +Пропускане на деца +------------------ + +Ако знаете, че щом срещнете определен тип възел, нито един от неговите потомци не може да съдържа възли, които търсите, можете да кажете на обхождащия да пропусне посещението на неговите деца. Това се извършва чрез връщане на константата `NodeTraverser::DontTraverseChildren` от визитора **`enter`**. По този начин пропускате цели клонове при обхождането, което потенциално спестява значително време, особено в шаблони със сложни PHP изрази вътре в тагове. + + +Спиране на обхождането +---------------------- + +Ако вашият проход трябва да намери само *първото* срещане на нещо (специфичен тип възел, изпълнение на условие), можете напълно да спрете целия процес на обхождане, щом го намерите. Това се постига чрез връщане на константата `NodeTraverser::StopTraversal` от визитора `enter` или `leave`. Методът `traverse()` спира да посещава всякакви други възли. Това е изключително ефективно, ако се нуждаете само от първото съвпадение в потенциално много голямо дърво. + + +Полезен помощник `NodeHelpers` +============================== + +Въпреки че `NodeTraverser` предлага фин контрол, Latte също предоставя практичен помощен клас, [api:Latte\Compiler\NodeHelpers], който капсулира `NodeTraverser` за няколко често срещани задачи за търсене и анализ, често изискващи по-малко подготвителен код. + + +find(Node $startNode, callable $filter): array .[method] +-------------------------------------------------------- + +Този статичен метод намира **всички** възли в поддървото, започващо от `$startNode` (включително), които отговарят на callback `$filter`. Връща масив от съответстващи възли. + +**Пример:** Намиране на всички възли на променливи (`VariableNode`) в целия шаблон. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Php\Expression\VariableNode; +use Latte\Compiler\Nodes\TemplateNode; + +function findAllVariables(TemplateNode $templateNode): array +{ + return NodeHelpers::find( + $templateNode, + fn($node) => $node instanceof VariableNode, + ); +} +``` + + +findFirst(Node $startNode, callable $filter): ?Node .[method] +-------------------------------------------------------------- + +Подобно на `find`, но спира обхождането незабавно след намиране на **първия** възел, който отговаря на callback `$filter`. Връща намерения обект `Node` или `null`, ако не е намерен съответстващ възел. Това е по същество практична обвивка около `NodeTraverser::StopTraversal`. + +**Пример:** Намиране на възела `{parameters}` (същото като ръчния пример преди, но по-кратко). + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Essential\Nodes\ParametersNode; + +function findParametersNodeHelper(TemplateNode $templateNode): ?ParametersNode +{ + return NodeHelpers::findFirst( + $templateNode->head, // Търсене само в главната секция за ефективност + fn($node) => $node instanceof ParametersNode, + ); +} +``` + + +toValue(ExpressionNode $node, bool $constants = false): mixed .[method] +----------------------------------------------------------------------- + +Този статичен метод се опитва да *изчисли стойността* на `ExpressionNode` **по време на компилация** и да върне неговата съответстваща PHP стойност. Работи надеждно само за прости литерални възли (`StringNode`, `IntegerNode`, `FloatNode`, `BooleanNode`, `NullNode`) и инстанции на `ArrayNode`, съдържащи само такива изчислими елементи. + +Ако `$constants` е зададено на `true`, той също ще се опита да разреши `ConstantFetchNode` и `ClassConstantFetchNode` чрез проверка с `defined()` и използване на `constant()`. + +Ако възелът съдържа променливи, извиквания на функции или други динамични елементи, той не може да бъде изчислен по време на компилация и методът ще хвърли `InvalidArgumentException`. + +**Случай на употреба:** Получаване на статичната стойност на аргумент на таг по време на компилация за вземане на решения по време на компилация. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Php\ExpressionNode; + +function getStaticStringArgument(ExpressionNode $argumentNode): ?string +{ + try { + $value = NodeHelpers::toValue($argumentNode); + return is_string($value) ? $value : null; + } catch (\InvalidArgumentException $e) { + // Аргументът не беше статичен литерален низ + return null; + } +} +``` + + +toText(?Node $node): ?string .[method] +-------------------------------------- + +Този статичен метод е полезен за извличане на обикновено текстово съдържание от прости възли. Работи предимно с: +- `TextNode`: Връща неговото `$content`. +- `FragmentNode`: Конкатенира резултата от `toText()` за всички негови деца. Ако някое дете не може да се преобразува в текст (напр. съдържа `PrintNode`), връща `null`. +- `NopNode`: Връща празен низ. +- Други типове възли: Връща `null`. + +**Случай на употреба:** Получаване на статичното текстово съдържание на стойността на HTML атрибут или прост HTML елемент за анализ по време на компилационен проход. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Html\AttributeNode; + +function getStaticAttributeValue(AttributeNode $attr): ?string +{ + // $attr->value обикновено е AreaNode (като FragmentNode или TextNode) + return NodeHelpers::toText($attr->value); +} + +// Пример за използване в проход: +// if ($node instanceof Html\ElementNode && $node->name === 'meta') { +// $nameAttrValue = getStaticAttributeValue($node->getAttributeNode('name')); +// if ($nameAttrValue === 'description') { ... } +// } +``` + +`NodeHelpers` може да опрости вашите компилационни проходи, като предостави готови решения за често срещани задачи за обхождане и анализ на AST. + + +Практически примери +=================== + +Нека приложим концепциите за обхождане и модификация на AST за решаване на някои практически проблеми. Тези примери демонстрират често срещани модели, използвани в компилационните проходи. + + +Автоматично добавяне на `loading="lazy"` към `<img>` +---------------------------------------------------- + +Съвременните браузъри поддържат вградено мързеливо зареждане за изображения с помощта на атрибута `loading="lazy"`. Нека създадем проход, който автоматично добавя този атрибут към всички тагове `<img>`, които все още нямат атрибут `loading`. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; +use Latte\Compiler\Nodes\Html; + +function addLazyLoading(Nodes\TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // Можем да използваме 'enter', тъй като модифицираме възела директно + // и не зависим от децата за това решение. + enter: function (Node $node) { + // Това HTML елемент с име 'img' ли е? + if ($node instanceof Html\ElementNode && $node->name === 'img') { + // Гарантираме, че възелът на атрибутите съществува + $node->attributes ??= new Nodes\FragmentNode; + + // Проверяваме дали вече съществува атрибут 'loading' (без значение от регистъра) + foreach ($node->attributes->children as $attrNode) { + if ($attrNode instanceof Html\AttributeNode + && $attrNode->name instanceof Nodes\TextNode // Статично име на атрибут + && strtolower($attrNode->name->content) === 'loading' + ) { + return; // Атрибутът 'loading' вече съществува, не правим нищо + } + } + + // Добавяме интервал, ако атрибутите не са празни и последният не е интервал + if ($node->attributes->children) { + $node->attributes->children[] = new Nodes\TextNode(' '); + } + + // Създаваме нов възел на атрибута: loading="lazy" + $node->attributes->children[] = new Html\AttributeNode( + name: new Nodes\TextNode('loading'), + value: new Nodes\TextNode('lazy'), + quote: '"', + ); + // Промяната се прилага директно в обекта, не е необходимо нищо да се връща. + } + }, + ); +} +``` + +Обяснение: +- Visitor `enter` търси възли `Html\ElementNode` с име `img`. +- Итерира през съществуващите атрибути (`$node->attributes->children`) и проверява дали атрибутът `loading` вече присъства. +- Ако не е намерен, създава нов `Html\AttributeNode`, представляващ `loading="lazy"`. + + +Проверка на извиквания на функции +--------------------------------- + +Компилационните проходи са основата на Latte Sandbox. Въпреки че истинският Sandbox е сложен, можем да демонстрираме основния принцип на проверка за забранени извиквания на функции. + +**Цел:** Предотвратяване на използването на потенциално опасната функция `shell_exec` в рамките на изрази в шаблона. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; +use Latte\Compiler\Nodes\Php; +use Latte\SecurityViolationException; + +function checkForbiddenFunctions(Nodes\TemplateNode $templateNode): void +{ + $forbiddenFunctions = ['shell_exec' => true, 'exec' => true]; // Прост списък + + $traverser = new NodeTraverser; + (new NodeTraverser)->traverse( + $templateNode, + enter: function (Node $node) use ($forbiddenFunctions) { + // Това възел на директно извикване на функция ли е? + if ($node instanceof Php\Expression\FunctionCallNode + && $node->name instanceof Php\NameNode + && isset($forbiddenFunctions[strtolower((string) $node->name)]) + ) { + throw new SecurityViolationException( + "Функцията {$node->name}() не е разрешена.", + $node->position, + ); + } + }, + ); +} +``` + +Обяснение: +- Дефинираме списък със забранени имена на функции. +- Visitor `enter` проверява `FunctionCallNode`. +- Ако името на функцията (`$node->name`) е статичен `NameNode`, проверяваме неговото представяне като низ с малки букви спрямо нашия забранен списък. +- Ако е намерена забранена функция, хвърляме `Latte\SecurityViolationException`, която ясно показва нарушение на правилото за сигурност и спира компилацията. + +Тези примери показват как компилационните проходи с използването на `NodeTraverser` могат да бъдат използвани за анализ, автоматични модификации и налагане на ограничения за сигурност чрез директно взаимодействие със структурата на AST на шаблона. + + +Добри практики +============== + +При писане на компилационни проходи имайте предвид тези насоки за създаване на стабилни, поддържаеми и ефективни разширения: + +- **Редът на изпълнение е важен:** Бъдете наясно с реда, в който се изпълняват проходите. Ако вашият проход зависи от структурата на AST, създадена от друг проход (напр. основни проходи на Latte или друг персонализиран проход), или ако други проходи могат да зависят от вашите модификации, използвайте механизма за сортиране, предоставен от `Extension::getPasses()`, за да дефинирате зависимости (`before`/`after`). Вижте документацията за [`Extension::getPasses()` |extending-latte#getPasses] за подробности. +- **Една отговорност:** Стремете се към проходи, които изпълняват една добре дефинирана задача. За сложни трансформации обмислете разделянето на логиката на няколко прохода – може би един за анализ и друг за модификация, базирана на резултатите от анализа. Това подобрява прегледността и тестваемостта. +- **Производителност:** Помнете, че компилационните проходи добавят време към компилацията на шаблона (въпреки че това обикновено се случва само веднъж, докато шаблонът не се промени). Избягвайте изчислително скъпи операции във вашите проходи, ако е възможно. Използвайте оптимизации на обхождането като `NodeTraverser::DontTraverseChildren` и `NodeTraverser::StopTraversal` винаги, когато знаете, че не е необходимо да посещавате определени части от AST. +- **Използвайте `NodeHelpers`:** За често срещани задачи като търсене на специфични възли или статично изчисляване на прости изрази, проверете дали `Latte\Compiler\NodeHelpers` не предлага подходящ метод, преди да пишете собствена логика с `NodeTraverser`. Това може да спести време и да намали количеството подготвителен код. +- **Обработка на грешки:** Ако вашият проход открие грешка или невалидно състояние в AST на шаблона, хвърлете `Latte\CompileException` (или `Latte\SecurityViolationException` за проблеми със сигурността) с ясно съобщение и релевантен обект `Position` (обикновено `$node->position`). Това предоставя полезна обратна връзка на разработчика на шаблона. +- **Идемпотентност (ако е възможно):** В идеалния случай, изпълнението на вашия проход няколко пъти върху същия AST трябва да произведе същия резултат като еднократното му изпълнение. Това не винаги е изпълнимо, но опростява отстраняването на грешки и разсъжденията за взаимодействията на проходите, ако бъде постигнато. Например, уверете се, че вашият модификационен проход проверява дали модификацията вече е приложена, преди да я приложи отново. + +Следвайки тези практики, можете ефективно да използвате компилационните проходи, за да разширите възможностите на Latte по мощен и надежден начин, допринасяйки за по-безопасна, по-оптимизирана или функционално по-богата обработка на шаблони. diff --git a/latte/bg/cookbook/@home.texy b/latte/bg/cookbook/@home.texy index 8d6307deee..019aa6cd32 100644 --- a/latte/bg/cookbook/@home.texy +++ b/latte/bg/cookbook/@home.texy @@ -1,13 +1,13 @@ -Готварска книга -*************** +Ръководства и процедури +*********************** .[perex] -Примерни кодове и рецепти за изпълнение на често срещани задачи с Latte. +Примери за кодове и рецепти за изпълнение на често срещани задачи с помощта на Latte. -- [Всичко, което винаги сте искали да знаете за {iterateWhile} |iteratewhile] -- [Как се пишат SQL заявки в Latte |how-to-write-sql-queries-in-latte]? +- [Процедури за разработчици |/develop] +- [Предаване на променливи между шаблони |passing-variables] +- [Всичко, което някога сте искали да знаете за групирането |grouping] +- [Как да пишем SQL заявки в Latte? |how-to-write-sql-queries-in-latte] - [Миграция от PHP |migration-from-php] -- [Миграция с Twig |migration-from-twig] -- [Използване на Latte с Slim 4 |slim-framework] - -{{leftbar: /@left-menu}} +- [Миграция от Twig |migration-from-twig] +- [Използване на Latte със Slim 4 |slim-framework] diff --git a/latte/bg/cookbook/@meta.texy b/latte/bg/cookbook/@meta.texy new file mode 100644 index 0000000000..64e87d1168 --- /dev/null +++ b/latte/bg/cookbook/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Документация на Latte}} +{{leftbar: /@left-menu}} diff --git a/latte/bg/cookbook/grouping.texy b/latte/bg/cookbook/grouping.texy new file mode 100644 index 0000000000..fe6ac48fe5 --- /dev/null +++ b/latte/bg/cookbook/grouping.texy @@ -0,0 +1,251 @@ +Всичко, което някога сте искали да знаете за групирането +******************************************************** + +.[perex] +При работа с данни в шаблони често можете да срещнете нуждата от тяхното групиране или специфично показване според определени критерии. Latte за тази цел предлага няколко силни инструмента. + +Филтърът и функцията `|group` позволяват ефективно групиране на данни според зададен критерий, филтърът `|batch` пък улеснява разделянето на данни на предварително зададени партиди, а тагът `{iterateWhile}` предоставя възможност за по-сложно управление на протичането на цикли с условия. Всеки от тези тагове предлага специфични възможности за работа с данни, което ги прави незаменими инструменти за динамично и структурирано показване на информация в Latte шаблони. + + +Филтър и функция `group` .{data-version:3.0.16} +=============================================== + +Представете си таблица в база данни `items` с елементи, разделени на категории: + +| id | categoryId | name +|------------------ +| 1 | 1 | Apple +| 2 | 1 | Banana +| 3 | 2 | PHP +| 4 | 3 | Green +| 5 | 3 | Red +| 6 | 3 | Blue + +Прост списък на всички елементи с помощта на Latte шаблон би изглеждал така: + +```latte +<ul> +{foreach $items as $item} + <li>{$item->name}</li> +{/foreach} +</ul> +``` + +Ако обаче искахме елементите да бъдат подредени в групи според категорията, трябва да ги разделим така, че всяка категория да има свой собствен списък. Резултатът тогава трябва да изглежда по следния начин: + +```latte +<ul> + <li>Apple</li> + <li>Banana</li> +</ul> + +<ul> + <li>PHP</li> +</ul> + +<ul> + <li>Green</li> + <li>Red</li> + <li>Blue</li> +</ul> +``` + +Задачата може лесно и елегантно да се реши с помощта на `|group`. Като параметър посочваме `categoryId`, което означава, че елементите ще се разделят на по-малки масиви според стойността на `$item->categoryId` (ако `$item` беше масив, ще се използва `$item['categoryId']`): + +```latte +{foreach ($items|group: categoryId) as $categoryId => $categoryItems} + <ul> + {foreach $categoryItems as $item} + <li>{$item->name}</li> + {/foreach} + </ul> +{/foreach} +``` + +Филтърът може в Latte да се използва и като функция, което ни дава алтернативен синтаксис: `{foreach group($items, categoryId) ...}`. + +Ако искате да групирате елементи според по-сложни критерии, можете в параметъра на филтъра да използвате функция. Например, групиране на елементи според дължината на името би изглеждало така: + +```latte +{foreach ($items|group: fn($item) => strlen($item->name)) as $items} + ... +{/foreach} +``` + +Важно е да се осъзнае, че `$categoryItems` не е обикновен масив, а обект, който се държи като итератор. За достъп до първия елемент на групата можете да използвате функцията [`first()` |latte:functions#first]. + +Тази гъвкавост в групирането на данни прави `group` изключително полезен инструмент за представяне на данни в шаблони Latte. + + +Вложени цикли +------------- + +Представете си, че имаме база данни с допълнителна колона `subcategoryId`, която дефинира подкатегориите на отделните елементи. Искаме да покажем всяка основна категория в отделен списък `<ul>` и всяка подкатегория в отделен вложен списък `<ol>`: + +```latte +{foreach ($items|group: categoryId) as $categoryItems} + <ul> + {foreach ($categoryItems|group: subcategoryId) as $subcategoryItems} + <ol> + {foreach $subcategoryItems as $item} + <li>{$item->name} + {/foreach} + </ol> + {/foreach} + </ul> +{/foreach} +``` + + +Връзка с Nette Database +----------------------- + +Нека покажем как ефективно да използваме групирането на данни в комбинация с Nette Database. Да предположим, че работим с таблицата `items` от уводния пример, която чрез колоната `categoryId` е свързана с тази таблица `categories`: + +| categoryId | name | +|------------|------------| +| 1 | Fruits | +| 2 | Languages | +| 3 | Colors | + +Данните от таблицата `items` зареждаме с помощта на Nette Database Explorer с командата `$items = $db->table('items')`. По време на итерацията над тези данни имаме възможност да достъпваме не само атрибути като `$item->name` и `$item->categoryId`, но благодарение на връзката с таблицата `categories` също и свързания ред в нея чрез `$item->category`. На тази връзка може да се демонстрира интересно използване: + +```latte +{foreach ($items|group: category) as $category => $categoryItems} + <h1>{$category->name}</h1> + <ul> + {foreach $categoryItems as $item} + <li>{$item->name}</li> + {/foreach} + </ul> +{/foreach} +``` + +В този случай използваме филтъра `|group` за групиране според свързания ред `$item->category`, а не само според колоната `categoryId`. Благодарение на това в променливата ключ имаме директно `ActiveRow` на дадената категория, което ни позволява директно да изписваме нейното име с `{$category->name}`. Това е практичен пример за това как групирането може да изясни шаблоните и да улесни работата с данни. + + +Филтър `|batch` +=============== + +Филтърът позволява да се раздели списък от елементи на групи с предварително определен брой елементи. Този филтър е идеален за ситуации, когато искате да представите данните в няколко по-малки групи, например за по-добра прегледност или визуално подреждане на страницата. + +Представете си, че имаме списък с елементи и искаме да ги покажем в списъци, където всеки съдържа максимум три елемента. Използването на филтъра `|batch` в такъв случай е много практично: + +```latte +<ul> +{foreach ($items|batch: 3) as $batch} + {foreach $batch as $item} + <li>{$item->name}</li> + {/foreach} +{/foreach} +</ul> +``` + +В този пример списъкът `$items` е разделен на по-малки групи, като всяка група (`$batch`) съдържа до три елемента. Всяка група след това се показва в отделен `<ul>` списък. + +Ако последната група не съдържа достатъчно елементи за достигане на желания брой, вторият параметър на филтъра позволява да се дефинира с какво ще бъде допълнена тази група. Това е идеално за естетическо подравняване на елементите там, където непълният ред би могъл да изглежда неподреден. + +```latte +{foreach ($items|batch: 3, '—') as $batch} + ... +{/foreach} +``` + + +Таг `{iterateWhile}` +==================== + +Същите задачи, които решавахме с филтъра `|group`, ще покажем с използването на тага `{iterateWhile}`. Основната разлика между двата подхода е в това, че `group` първо обработва и групира всички входни данни, докато `{iterateWhile}` управлява протичането на цикли с условия, така че итерацията протича постепенно. + +Първо ще рендираме таблицата с категориите с помощта на iterateWhile: + +```latte +{foreach $items as $item} + <ul> + {iterateWhile} + <li>{$item->name}</li> + {/iterateWhile $item->categoryId === $iterator->nextValue->categoryId} + </ul> +{/foreach} +``` + +Докато `{foreach}` обозначава външната част на цикъла, т.е. рендирането на списъци за всяка категория, тагът `{iterateWhile}` обозначава вътрешната част, т.е. отделните елементи. Условието в крайния таг казва, че повторението ще продължи дотогава, докато текущият и следващият елемент принадлежат към същата категория (`$iterator->nextValue` е [следващият елемент |/tags#iterator]). + +Ако условието беше изпълнено винаги, тогава във вътрешния цикъл ще се рендират всички елементи: + +```latte +{foreach $items as $item} + <ul> + {iterateWhile} + <li>{$item->name} + {/iterateWhile true} + </ul> +{/foreach} +``` + +Резултатът ще изглежда така: + +```latte +<ul> + <li>Apple</li> + <li>Banana</li> + <li>PHP</li> + <li>Green</li> + <li>Red</li> + <li>Blue</li> +</ul> +``` + +За какво е полезно такова използване на iterateWhile? Когато таблицата е празна и не съдържа никакви елементи, няма да се изпише празно `<ul></ul>`. + +Ако посочим условие в отварящия таг `{iterateWhile}`, тогава поведението се променя: условието (и преходът към следващия елемент) се изпълнява още в началото на вътрешния цикъл, а не в края. Тоест, докато в `{iterateWhile}` без условие се влиза винаги, в `{iterateWhile $cond}` само при изпълнение на условието `$cond`. И същевременно с това в `$item` се записва следващият елемент. + +Което е полезно например в ситуация, когато искаме първия елемент във всяка категория да рендираме по различен начин, например така: + +```latte +<h1>Apple</h1> +<ul> + <li>Banana</li> +</ul> + +<h1>PHP</h1> +<ul> +</ul> + +<h1>Green</h1> +<ul> + <li>Red</li> + <li>Blue</li> +</ul> +``` + +Ще променим оригиналния код така, че първо да рендираме първия елемент и след това във вътрешния цикъл `{iterateWhile}` да рендираме другите елементи от същата категория: + +```latte +{foreach $items as $item} + <h1>{$item->name}</h1> + <ul> + {iterateWhile $item->categoryId === $iterator->nextValue->categoryId} + <li>{$item->name}</li> + {/iterateWhile} + </ul> +{/foreach} +``` + +В рамките на един цикъл можем да създаваме повече вътрешни цикли и дори да ги влагаме. Така биха могли да се групират например подкатегории и т.н. + +Да предположим, че в таблицата има още една колона `subcategoryId` и освен това, че всяка категория ще бъде в отделен `<ul>`, всяка подкатегория в отделен `<ol>`: + +```latte +{foreach $items as $item} + <ul> + {iterateWhile} + <ol> + {iterateWhile} + <li>{$item->name} + {/iterateWhile $item->subcategoryId === $iterator->nextValue->subcategoryId} + </ol> + {/iterateWhile $item->categoryId === $iterator->nextValue->categoryId} + </ul> +{/foreach} +``` diff --git a/latte/bg/cookbook/how-to-write-sql-queries-in-latte.texy b/latte/bg/cookbook/how-to-write-sql-queries-in-latte.texy index 576045f9aa..b6a002275e 100644 --- a/latte/bg/cookbook/how-to-write-sql-queries-in-latte.texy +++ b/latte/bg/cookbook/how-to-write-sql-queries-in-latte.texy @@ -1,10 +1,10 @@ -Как се пишат SQL заявки в Latte? +Как да пишем SQL заявки в Latte? ******************************** .[perex] -Latte може да бъде полезен и за създаване на наистина сложни SQL заявки. +Latte може да бъде полезен и за генериране на наистина сложни SQL заявки. -Ако SQL изявлението, което трябва да бъде създадено, съдържа много условия и променливи, тогава записването му в Latte може да бъде наистина по-лесно за разбиране. Много прост пример: +Ако създаването на SQL заявка съдържа редица условия и променливи, може да бъде наистина по-прегледно да я напишете в Latte. Много прост пример: ```latte SELECT users.* FROM users @@ -14,8 +14,7 @@ SELECT users.* FROM users WHERE groups.name = 'Admins' {ifset $country} AND country.name = {$country} {/ifset} ``` -Използвайки `$latte->setContentType()`, казваме на Latte да третира съдържанието като обикновен текст (а не като HTML) и -след това подготвяме функция за ескейпване, която ескейпва низовете директно от драйвера на базата данни: +С помощта на `$latte->setContentType()` казваме на Latte да третира съдържанието като обикновен текст (а не като HTML) и след това подготвяме функция за екраниране, която ще екранира низовете директно с драйвера на базата данни: ```php $db = new PDO(/* ... */); @@ -31,13 +30,11 @@ $latte->addFilter('escape', fn($val) => match (true) { }); ``` -Използването ще изглежда по следния начин: +Използването би изглеждало така: ```php $sql = $latte->renderToString('query.sql.latte', ['country' => $country]); $result = $db->query($sql); ``` -*Този пример изисква Latte версия 3.0.5 или по-висока. - -{{leftbar: /@left-menu}} +*Посоченият пример изисква Latte v3.0.5 или по-нова версия.* diff --git a/latte/bg/cookbook/iteratewhile.texy b/latte/bg/cookbook/iteratewhile.texy deleted file mode 100644 index fb75a5acf3..0000000000 --- a/latte/bg/cookbook/iteratewhile.texy +++ /dev/null @@ -1,214 +0,0 @@ -Всичко, което винаги сте искали да знаете за {iterateWhile} -*********************************************************** - -.[perex] -Тагът `{iterateWhile}` е подходящ за различни трикове в цикли foreach. - -Да предположим, че имаме следната таблица в базата данни, в която елементите са разделени на категории: - -| id | catId | name -|------------------ -| 1 | 1 | Apple -| 2 | 1 | Banana -| 3 | 2 | PHP -| 4 | 3 | Green -| 5 | 3 | Red -| 6 | 3 | Blue - -Разбира се, много е лесно да изведете елементите в цикъла foreach като списък: - -```latte -<ul> -{foreach $items as $item} - <li>{$item->name}</li> -{/foreach} -</ul> -``` - -Но какво да направите, ако искате да изведете всяка категория като отделен списък? С други думи, как се решава проблемът с групирането на елементи от линеен списък в цикъл foreach. Резултатът трябва да изглежда така: - -```latte -<ul> - <li>Apple</li> - <li>Banana</li> -</ul> - -<ul> - <li>PHP</li> -</ul> - -<ul> - <li>Green</li> - <li>Red</li> - <li>Blue</li> -</ul> -``` - -Ще ви покажем как да решите този проблем лесно и елегантно с помощта на iterateWhile: - -```latte -{foreach $items as $item} - <ul> - {iterateWhile} - <li>{$item->name}</li> - {/iterateWhile $item->catId === $iterator->nextValue->catId} - </ul> -{/foreach} -``` - -Докато `{foreach}` се отнася до външната част на цикъла, т.е. изготвянето на списъци за всяка категория, таговете `{iterateWhile}` се отнасят до вътрешната част, т.е. отделните елементи. -Условието в крайния таг гласи, че повторението ще продължи, докато текущият и следващият елемент принадлежат към една и съща категория (`$iterator->nextValue` - [следващ елемент |/tags#iterator]). - -Ако условието е винаги изпълнено, всички елементи се изтеглят във вътрешния цикъл: - -```latte -{foreach $items as $item} - <ul> - {iterateWhile} - <li>{$item->name} - {/iterateWhile true} - </ul> -{/foreach} -``` - -Резултатът ще изглежда по следния начин: - -```latte -<ul> - <li>Apple</li> - <li>Banana</li> - <li>PHP</li> - <li>Green</li> - <li>Red</li> - <li>Blue</li> -</ul> -``` - -Как е полезно да се използва iterateWhile по този начин? По какво се различава от решението, което показахме в началото на това ръководство? Разликата е, че ако таблицата е празна и не съдържа елементи, тя няма да се изведе празна `<ul></ul>`. - - -Решение без `{iterateWhile}` .[#toc-solution-without-iteratewhile] ------------------------------------------------------------------- - -Ако трябва да решим същия проблем, като използваме напълно елементарни шаблони, например в Twig, Blade или чист PHP, решението би изглеждало по следния начин - -```latte -{var $prevCatId = null} -{foreach $items as $item} - {if $item->catId !== $prevCatId} - {* the category has changed *} - - {* we close the previous <ul>, if it is not the first item *} - {if $prevCatId !== null} - </ul> - {/if} - - {* we will open a new list *} - <ul> - - {do $prevCatId = $item->catId} - {/if} - - <li>{$item->name}</li> -{/foreach} - -{if $prevCatId !== null} - {* we close the last list *} - </ul> -{/if} -``` - -Този код обаче е неразбираем и неинтуитивен. Връзката между началния и крайния HTML таг не е съвсем ясна. От пръв поглед не е ясно дали има грешка. А това изисква помощни променливи като `$prevCatId`. - -За разлика от това решението с `{iterateWhile}` е чисто, ясно, не изисква помощни променливи и е надеждно. - - -Условие в затварящия таг .[#toc-condition-in-the-closing-tag] -------------------------------------------------------------- - -Ако посочите условие в отварящия таг `{iterateWhile}`, поведението се променя: условието (и преходът към следващия елемент) се изпълнява в началото на вътрешния цикъл, а не в края. -Така, докато `{iterateWhile}` без условие се въвежда винаги, `{iterateWhile $cond}` се въвежда само когато е изпълнено условието `$cond`. В същото време следващият елемент се записва в `$item`. - -Това е полезно например в ситуация, в която първият елемент във всяка категория трябва да се показва по различен начин, напр: - -```latte -<h1>Apple</h1> -<ul> - <li>Banana</li> -</ul> - -<h1>PHP</h1> -<ul> -</ul> - -<h1>Green</h1> -<ul> - <li>Red</li> - <li>Blue</li> -</ul> -``` - -Променете изходния код, като визуализираме първия елемент и след това допълнителни елементи от същата категория във вътрешния цикъл `{iterateWhile}`: - -```latte -{foreach $items as $item} - <h1>{$item->name}</h1> - <ul> - {iterateWhile $item->catId === $iterator->nextValue->catId} - <li>{$item->name}</li> - {/iterateWhile} - </ul> -{/foreach} -``` - - -Вложени цикли .[#toc-nested-loops] ----------------------------------- - -Можем да създадем няколко вътрешни цикъла в един цикъл и дори да ги вложим един в друг. По този начин например подкатегориите могат да бъдат групирани заедно. - -Да предположим, че в таблицата има още една колона `subCatId` и освен че всяка категория е в отделна колона, всяка подкатегория ще бъде в отделна колона. `<ul>`, всяка подкатегория ще бъде в отделна колона. `<ol>`: - -```latte -{foreach $items as $item} - <ul> - {iterateWhile} - <ol> - {iterateWhile} - <li>{$item->name} - {/iterateWhile $item->subCatId === $iterator->nextValue->subCatId} - </ol> - {/iterateWhile $item->catId === $iterator->nextValue->catId} - </ul> -{/foreach} -``` - - -Филтриране | партида .[#toc-filter-batch] ------------------------------------------ - -Групирането на елементите на реда се осигурява и от филтъра `batch`, в партида с фиксиран брой елементи: - -```latte -<ul> -{foreach ($items|batch:3) as $batch} - {foreach $batch as $item} - <li>{$item->name}</li> - {/foreach} -{/foreach} -</ul> -``` - -Тя може да бъде заменена с iterateWhile, както следва: - -```latte -<ul> -{foreach $items as $item} - {iterateWhile} - <li>{$item->name}</li> - {/iterateWhile $iterator->counter0 % 3} -{/foreach} -</ul> -``` - -{{leftbar: /@left-menu}} diff --git a/latte/bg/cookbook/migration-from-php.texy b/latte/bg/cookbook/migration-from-php.texy index 180f7c8da9..73be04cf53 100644 --- a/latte/bg/cookbook/migration-from-php.texy +++ b/latte/bg/cookbook/migration-from-php.texy @@ -1,28 +1,28 @@ -Преминаване от PHP към Latte -**************************** +Миграция от PHP към Latte +************************* .[perex] -Мигрирате стар проект, написан на чист PHP, към Latte? Разполагаме с инструмент за улесняване на миграцията. [Изпробвайте го онлайн |https://php2latte.nette.org]. +Преобразувате стар проект, написан на чист PHP, към Latte? Имаме за вас инструмент, който ще ви улесни миграцията. [Изпробвайте го онлайн |https://fiddle.nette.org/php2latte/]. -Можете да изтеглите инструмента от [GitHub |https://github.com/nette/latte-tools] или да го инсталирате с помощта на Composer: +Можете да изтеглите инструмента от [GitHub|https://github.com/nette/latte-tools] или да го инсталирате с помощта на Composer: ```shell composer create-project latte/tools ``` -Конверторът не използва просто заместване на регулярни изрази, а използва директно PHP анализатора, така че може да обработва всеки сложен синтаксис. +Преобразувателят не използва прости замени с помощта на регулярни изрази, а напротив, използва директно PHP парсера, така че може да се справи с всякакъв сложен синтаксис. -За да конвертирате от PHP в Latte, скриптът `php-to-latte.php`: +За преобразуване от PHP към Latte служи скриптът `php-to-latte.php`: ```shell -php-to-latte.php input.php [output.latte] +php php-to-latte.php input.php [output.latte] ``` -Пример: .[#toc-example] ------------------------ +Пример +------ -Входният файл може да изглежда по следния начин (това е част от кода на форума PunBB): +Входният файл може да изглежда например така (това е част от кода на форума PunBB): ```php <h1><span><?= $lang_common['User list'] ?></span></h1> @@ -48,7 +48,7 @@ foreach ($result as $cur_group) { </div> ``` -Генерира този шаблон: +Ще генерира този шаблон: ```latte <h1><span>{$lang_common['User list']}</span></h1> @@ -68,5 +68,3 @@ foreach ($result as $cur_group) { </form> </div> ``` - -{{leftbar: /@left-menu}} diff --git a/latte/bg/cookbook/migration-from-twig.texy b/latte/bg/cookbook/migration-from-twig.texy index 67834218a4..04e356b464 100644 --- a/latte/bg/cookbook/migration-from-twig.texy +++ b/latte/bg/cookbook/migration-from-twig.texy @@ -2,37 +2,37 @@ ************************** .[perex] -Мигрирате проект, написан на Twig, към по-модерен Latte? Разполагаме с инструмент за улесняване на миграцията. [Изпробвайте го онлайн |https://twig2latte.nette.org]. +Преобразувате проект, написан на Twig, към по-модерния Latte? Имаме за вас инструмент, който ще ви улесни миграцията. [Изпробвайте го онлайн |https://fiddle.nette.org/twig2latte/]. -Можете да изтеглите инструмента от [GitHub |https://github.com/nette/latte-tools] или да го инсталирате с помощта на Composer: +Можете да изтеглите инструмента от [GitHub|https://github.com/nette/latte-tools] или да го инсталирате с помощта на Composer: ```shell composer create-project latte/tools ``` -Конверторът не използва просто заместване на регулярни изрази, а използва директно парсера на Twig, така че може да обработва всеки сложен синтаксис. +Преобразувателят не използва прости замени с помощта на регулярни изрази, а напротив, използва директно Twig парсера, така че може да се справи с всякакъв сложен синтаксис. -За да конвертирате от Twig в Latte, използвайте скрипта `twig-to-latte.php`: +За преобразуване от Twig към Latte служи скриптът `twig-to-latte.php`: ```shell -twig-to-latte.php input.twig.html [output.latte] +php twig-to-latte.php input.twig.html [output.latte] ``` -Преобразуване .[#toc-conversion] --------------------------------- +Конверсия +--------- -Преобразуването изисква ръчно редактиране на резултата, тъй като преобразуването не може да се извърши еднозначно. Twig използва точков синтаксис, където `{{ a.b }}` може да означава `$a->b`, `$a['b']` или `$a->getB()`, които не могат да бъдат разграничени по време на компилация. Затова конверторът преобразува всичко в `$a->b`. +Преобразуването предполага ръчна корекция на резултата, тъй като конверсията не може да се извърши еднозначно. Twig използва точков синтаксис, където `{{ a.b }}` може да означава `$a->b`, `$a['b']` или `$a->getB()`, което не може да се разграничи при компилация. Преобразувателят затова преобразува всичко на `$a->b`. -Някои функции, филтри или тагове нямат еквивалент в Latte или могат да се държат по малко по-различен начин. +Някои функции, филтри или тагове нямат аналог в Latte, или могат да се държат леко по-различно. -Пример: .[#toc-example] ------------------------ +Пример +------ -Входният файл може да изглежда по следния начин +Входният файл може да изглежда например така: -```latte +```twig {% use "blocks.twig" %} <!DOCTYPE html> <html> @@ -54,7 +54,7 @@ twig-to-latte.php input.twig.html [output.latte] </html> ``` -След конвертиране в Latte ще получим този шаблон: +След конверсията към Latte получаваме този шаблон: ```latte {import 'blocks.latte'} @@ -77,5 +77,3 @@ twig-to-latte.php input.twig.html [output.latte] </body> </html> ``` - -{{leftbar: /@left-menu}} diff --git a/latte/bg/cookbook/passing-variables.texy b/latte/bg/cookbook/passing-variables.texy new file mode 100644 index 0000000000..4e040e9217 --- /dev/null +++ b/latte/bg/cookbook/passing-variables.texy @@ -0,0 +1,158 @@ +Предаване на променливи между шаблони +************************************* + +Това ръководство ще ви обясни как се предават променливи между шаблони в Latte с помощта на различни тагове като `{include}`, `{import}`, `{embed}`, `{layout}`, `{sandbox}` и други. Ще научите също как да работите с променливи в тага `{block}` и `{define}`, и за какво служи тагът `{parameters}`. + + +Типове променливи +----------------- +Променливите в Latte можем да разделим на три категории според това как и къде са дефинирани: + +**Входни променливи** са тези, които се предават на шаблона отвън, например от PHP скрипт или с помощта на таг като `{include}`. + +```php +$latte->render('template.latte', ['userName' => 'Jan', 'userAge' => 30]); +``` + +**Околни променливи** са променливи, съществуващи на мястото на определен таг. Включват всички входни променливи и други променливи, създадени с помощта на тагове като `{var}`, `{default}` или в рамките на цикъл `{foreach}`. + +```latte +{foreach $users as $user} + {include 'userBox.latte', user: $user} +{/foreach} +``` + +**Експлицитни променливи** са тези, които са директно специфицирани вътре в тага и се изпращат към целевия шаблон. + +```latte +{include 'userBox.latte', name: $user->name, age: $user->age} +``` + + +`{block}` +--------- +Тагът `{block}` се използва за дефиниране на повторно използваеми блокове код, които могат да бъдат персонализирани или разширени в наследяващи шаблони. Околните променливи, дефинирани преди блока, са достъпни вътре в блока, но всякакви промени на променливите се отразяват само в рамките на този блок. + +```latte +{var $foo = 'оригинален'} +{block example} + {var $foo = 'променен'} +{/block} + +{$foo} // извежда: оригинален +``` + + +`{define}` +---------- +Тагът `{define}` служи за създаване на блокове, които се рендират едва след тяхното извикване с `{include}`. Променливите, достъпни вътре в тези блокове, зависят от това дали в дефиницията са посочени параметри. Ако да, достъп имат само до тези параметри. Ако не, достъп имат до всички входни променливи на шаблона, в който са дефинирани блоковете. + +```latte +{define hello} + {* има достъп до всички входни променливи на шаблона *} +{/define} + +{define hello $name} + {* има достъп само до параметъра $name *} +{/define} +``` + + +`{parameters}` +-------------- +Тагът `{parameters}` служи за експлицитно деклариране на очакваните входни променливи в началото на шаблона. По този начин може лесно да се документират очакваните променливи и техните типове данни. Също така е възможно да се дефинират стойности по подразбиране. + +```latte +{parameters int $age, string $name = 'неизвестно'} +<p>Възраст: {$age}, Име: {$name}</p> +``` + + +`{include file}` +---------------- +Тагът `{include file}` служи за вмъкване на цял шаблон. На този шаблон се предават както входните променливи на шаблона, в който е използван тагът, така и променливите, експлицитно дефинирани в него. Целевият шаблон обаче може да ограничи обхвата с помощта на `{parameters}`. + +```latte +{include 'profile.latte', userId: $user->id} +``` + + +`{include block}` +----------------- +Когато вмъквате блок, дефиниран в същия шаблон, към него се предават всички околни и експлицитно дефинирани променливи: + +```latte +{define blockName} + <p>Име: {$name}, Възраст: {$age}</p> +{/define} + +{var $name = 'Jan', $age = 30} +{include blockName} +``` + +В този пример променливите `$name` и `$age` се предават към блока `blockName`. По същия начин се държи и `{include parent}`. + +При вмъкване на блок от друг шаблон се предават само входните променливи и експлицитно дефинираните. Околните променливи не са автоматично достъпни. + +```latte +{include blockInOtherTemplate, name: $name, age: $age} +``` + + +`{layout}` или `{extends}` +-------------------------- +Тези тагове дефинират лейаут, към който се предават входните променливи на дъщерния шаблон и по-нататък променливите, създадени в кода преди блоковете: + +```latte +{layout 'layout.latte'} +{var $seo = 'index, follow'} +``` + +Шаблон `layout.latte`: + +```latte +<head> + <meta name="robots" content="{$seo}"> +</head> +``` + + +`{embed}` +--------- +Тагът `{embed}` е подобен на тага `{include}`, но позволява вмъкване на блокове в шаблона. За разлика от `{include}`, се предават само експлицитно декларираните променливи: + +```latte +{embed 'menu.latte', items: $menuItems} +{/embed} +``` + +В този пример шаблонът `menu.latte` има достъп само до променливата `$items`. + +Напротив, в блоковете вътре в `{embed}` има достъп до всички околни променливи: + +```latte +{var $name = 'Jan'} +{embed 'menu.latte', items: $menuItems} + {block foo} + {$name} + {/block} +{/embed} +``` + + +`{import}` +---------- +Тагът `{import}` се използва за зареждане на блокове от други шаблони. Пренасят се както входните, така и експлицитно декларираните променливи към импортираните блокове. + +```latte +{import 'buttons.latte'} +``` + + +`{sandbox}` +----------- +Тагът `{sandbox}` изолира шаблона за безопасна обработка. Променливите се предават изключително експлицитно. + +```latte +{sandbox 'secure.latte', data: $secureData} +``` diff --git a/latte/bg/cookbook/slim-framework.texy b/latte/bg/cookbook/slim-framework.texy index abe678fb51..3f72e4bcb4 100644 --- a/latte/bg/cookbook/slim-framework.texy +++ b/latte/bg/cookbook/slim-framework.texy @@ -1,43 +1,43 @@ -Използване на Latte с Slim 4 -**************************** +Използване на Latte със Slim 4 +****************************** .[perex] -Тази статия, написана от "Daniel Opitz:https://odan.github.io/2022/04/06/slim4-latte.html", описва използването на Latte с Slim Framework. +Тази статия, чийто автор е "Daniel Opitz":https://odan.github.io/2022/04/06/slim4-latte.html, описва използването на Latte със Slim Framework. -Първо "инсталирайте Slim Framework":https://odan.github.io/2019/11/05/slim4-tutorial.html и след това Latte с Composer: +Първо "инсталирайте Slim Framework":https://odan.github.io/2019/11/05/slim4-tutorial.html и след това Latte с помощта на Composer: ```shell composer require latte/latte ``` -Конфигурация .[#toc-configuration] ----------------------------------- +Конфигурация +------------ -Създайте нова директория `templates` в главната директория на проекта. Всички шаблони ще бъдат поставени там по-късно. +В коренната директория на проекта създайте нова директория `templates`. Всички шаблони ще бъдат поставени в нея по-късно. -Добавете нов конфигурационен ключ `template` към вашия файл `config/defaults.php`: +В файла `config/defaults.php` добавете нов конфигурационен ключ `template`: ```php $settings['template'] = __DIR__ . '/../templates'; ``` -Latte компилира шаблоните в собствен PHP код и ги съхранява в кеш на диска. По този начин те работят толкова бързо, колкото ако бяха написани на родния език PHP. +Latte компилира шаблоните в нативен PHP код и ги съхранява в кеш памет на диска. Те са толкова бързи, колкото ако бяха написани на нативен PHP език. -Добавете нов конфигурационен ключ `template_temp` към файла `config/defaults.php`: Уверете се, че директорията `{project}/tmp/templates` съществува и има права за четене и запис. +В файла `config/defaults.php` добавете нов конфигурационен ключ `template_temp`: Уверете се, че директорията `{project}/tmp/templates` съществува и има права за четене и запис. ```php $settings['template_temp'] = __DIR__ . '/../tmp/templates'; ``` -Latte автоматично регенерира кеша при всяка промяна на шаблона, което може да бъде деактивирано в производствена среда, за да се спести част от производителността: +Latte автоматично регенерира кеша при всяка промяна на шаблона, което може да бъде изключено в продукционна среда, за да се спести малко производителност: ```php -// променете на false в производствената среда +// в продукционна среда променете на false $settings['template_auto_refresh'] = true; ``` -След това добавете дефиниции на DI контейнера за класа `Latte\Engine`. +След това добавете дефиниция на DI контейнера за класа `Latte\Engine`. ```php <?php @@ -63,11 +63,11 @@ return [ ]; ``` -Това само по себе си технически ще работи за визуализиране на шаблона Latte, но трябва да го направим работещ и с обекта за отговор PSR-7. +Самото рендиране на шаблона Latte технически би работило, но трябва също да осигурим, че работи с обекта response PSR-7. -За тази цел създаваме специален клас `TemplateRenderer`, който върши тази работа вместо нас. +За тази цел ще създадем специален клас `TemplateRenderer`, който ще свърши тази работа вместо нас. -Затова създайте файл в `src/Renderer/TemplateRenderer.php` и копирайте/вмъкнете този код: +След това създайте файл `src/Renderer/TemplateRenderer.php` и копирайте/поставете този код: ```php <?php @@ -99,12 +99,12 @@ final class TemplateRenderer ``` -Използване на .[#toc-usage] ---------------------------- +Използване +---------- -Вместо да използваме директно обекта Latte Engine, използваме обекта `TemplateRenderer`, за да визуализираме шаблона в обект, съвместим с PSR-7. +Вместо директно да използваме обекта Latte Engine, ще използваме за рендиране на шаблона в обект, съвместим с PSR-7, обекта `TemplateRenderer`. -Типичен клас за обработка на действия може да изглежда по следния начин за визуализиране на шаблон с име `home.latte`: +Типичен клас за обработка на действие може да изглежда така: Рендира шаблон с име `home.latte`: ```php <?php @@ -136,17 +136,17 @@ final class HomeAction } ``` -За да направите това, създайте файл-шаблон в `templates/home.latte` с това съдържание: +За да работи това, създайте файл на шаблона в `templates/home.latte` с това съдържание: ```latte -<ul n:if="$items"> +<ul n:if=$items> {foreach $items as $item} <li id="item-{$iterator->counter}">{$item|capitalize}</li> {/foreach} </ul> ``` -Ако всичко е настроено правилно, трябва да видите следния резултат: +Ако всичко е правилно конфигурирано, трябва да се покаже следният изход: ```latte One @@ -155,4 +155,3 @@ Three ``` {{priority: -1}} -{{leftbar: /@left-menu}} diff --git a/latte/bg/creating-extension.texy b/latte/bg/creating-extension.texy deleted file mode 100644 index b685ba2c80..0000000000 --- a/latte/bg/creating-extension.texy +++ /dev/null @@ -1,579 +0,0 @@ -Създаване на разширение -*********************** - -.[perex]{data-version:3.0} -Разширението е клас за многократна употреба, който може да дефинира персонализирани тагове, филтри, функции, доставчици и т.н. - -Създаваме разширения, когато искаме да използваме повторно настройките на Latte в различни проекти или да ги споделим с други хора. -Полезно е също така да създадете разширение за всеки уеб проект, което да съдържа всички специфични тагове и филтри, които искате да използвате в шаблоните на проектите си. - - -Клас за разширение .[#toc-extension-class] -========================================== - -Extension е клас, наследен от [api:Latte\Extension]. Регистрира се в Latte с помощта на `addExtension()` (или чрез [конфигурационен файл |application:configuration#Latte]): - -```php -$latte = new Latte\Engine; -$latte->addExtension(new MyLatteExtension); -``` - -Ако регистрирате няколко разширения и те дефинират идентични по име тагове, филтри или функции, печели последното добавено разширение. Това означава също, че разширенията ви могат да отменят собствените си тагове/филтри/функции. - -Когато направите промени в клас и автоматичното обновяване не е изключено, Latte автоматично ще прекомпилира вашите шаблони. - -Класът може да реализира някой от следните методи: - -```php -abstract class Extension -{ - /** - * Initializes before template is compiler. - */ - public function beforeCompile(Engine $engine): void; - - /** - * Returns a list of parsers for Latte tags. - * @return array<string, callable> - */ - public function getTags(): array; - - /** - * Returns a list of compiler passes. - * @return array<string, callable> - */ - public function getPasses(): array; - - /** - * Returns a list of |filters. - * @return array<string, callable> - */ - public function getFilters(): array; - - /** - * Returns a list of functions used in templates. - * @return array<string, callable> - */ - public function getFunctions(): array; - - /** - * Returns a list of providers. - * @return array<mixed> - */ - public function getProviders(): array; - - /** - * Returns a value to distinguish multiple versions of the template. - */ - public function getCacheKey(Engine $engine): mixed; - - /** - * Initializes before template is rendered. - */ - public function beforeRender(Template $template): void; -} -``` - -За да получите представа как изглежда разширението, разгледайте вграденото разширение "CoreExtension:https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php". - - -beforeCompile(Latte\Engine $engine): void .[method] ---------------------------------------------------- - -Извиква се преди компилирането на шаблона. Методът може да се използва например за инициализация, свързана с компилирането. - - -getTags(): array .[method] --------------------------- - -Извиква се при компилиране на шаблон. Връща асоциативен масив *име на таг => callable*, които са [функции за парсване на тагове |#Tag-Parsing-Function]. - -```php -public function getTags(): array -{ - return [ - 'foo' => [FooNode::class, 'create'], - 'bar' => [BarNode::class, 'create'], - 'n:baz' => [NBazNode::class, 'create'], - // ... - ]; -} -``` - -Тагът `n:baz` е чист n:атрибут, т.е. това е таг, който може да бъде записан само като атрибут. - -В случая с таговете `foo` и `bar` Latte автоматично разпознава дали са двойки и ако е така, те могат да бъдат автоматично записани с помощта на n:attributes, включително варианти, предхождани от `n:inner-foo` и `n:tag-foo`. - -Редът, по който се изпълняват тези n:атрибути, се определя от реда им в масива, върнат от `getTags()`. По този начин `n:foo` се изпълнява винаги преди `n:bar`, дори ако атрибутите са изброени в обратен ред в HTML тага като `<div n:bar="..." n:foo="...">`. - -Ако трябва да определите реда на изпълнение на n:атрибути за няколко разширения, използвайте помощния метод `order()`, където параметърът `before` xor `after` определя кои тагове ще бъдат подредени преди или след тага . - -```php -public function getTags(): array -{ - return [ - 'foo' => self::order([FooNode::class, 'create'], before: 'bar')] - 'bar' => self::order([BarNode::class, 'create'], after: ['block', 'snippet'])] - ]; -} -``` - - -getPasses(): array .[method] ----------------------------- - -Извиква се при компилиране на шаблона. Връща асоциативен масив *name pass => callable*, които са функции, представляващи така наречените [passes на компилатора |#Compiler-Passes], които заобикалят и модифицират AST. - -Отново може да се използва спомагателен метод `order()`. Стойността на параметъра `before` или `after` може да бъде `*` със стойност преди/след всички. - -```php -public function getPasses(): array -{ - return [ - 'optimize' => [Passes::class, 'optimizePass'], - 'sandbox' => self::order([$this, 'sandboxPass'], before: '*'), - // ... - ]; -} -``` - - -beforeRender(Latte\Engine $engine): void .[method] --------------------------------------------------- - -Извиква се преди всяко визуализиране на шаблона. Методът може да се използва например за инициализиране на променливи, използвани по време на рендирането. - - -getFilters(): array .[method] ------------------------------ - -Извиква се преди шаблонът да бъде визуализиран. Връща [филтрите |extending-latte#Filters] като асоциативен масив *име на филтър => извикващ се*. - -```php -public function getFilters(): array -{ - return [ - 'batch' => [$this, 'batchFilter'], - 'trim' => [$this, 'trimFilter'], - // ... - ]; -} -``` - - -getFunctions(): array .[method] -------------------------------- - -Извиква се преди шаблонът да бъде визуализиран. Връща [функциите |extending-latte#Functions] като асоциативен масив *име на функция => callable*. - -```php -public function getFunctions(): array -{ - return [ - 'clamp' => [$this, 'clampFunction'], - 'divisibleBy' => [$this, 'divisibleByFunction'], - // ... - ]; -} -``` - - -getProviders(): array .[method] -------------------------------- - -Извиква се преди шаблонът да бъде визуализиран. Връща масив от доставчици, които обикновено са маркирани по време на изпълнение обекти. Достъпът до тях се осъществява чрез `$this->global->...`. - -```php -public function getProviders(): array -{ - return [ - 'myFoo' => $this->foo, - 'myBar' => $this->bar, - // ... - ]; -} -``` - - -getCacheKey(Latte\Engine $engine): mixed .[method] --------------------------------------------------- - -Извиква се преди шаблонът да бъде визуализиран. Върнатата стойност става част от ключа, чийто хеш се съдържа в името на файла на компилирания шаблон. По този начин Latte ще генерира различни кеш файлове за различните върнати стойности. - - -Как работи Latte? .[#toc-how-does-latte-work] -============================================= - -За да разберете как да дефинирате потребителски тагове или пропуски на компилатора, трябва да разберете как работи Latte под капака. - -Съставянето на модели в Latte опростено работи по следния начин: - -- Първо, **parser** разбива изходния код на шаблона на малки фрагменти (токени) за по-лесна обработка. -- След това **парсерът** преобразува потока от символи в смислено дърво от възли (Abstract Syntax Tree, AST). -- Накрая компилаторът **генерира** PHP клас от AST, който съпоставя шаблона, и го съхранява в кеша си. - -Всъщност съставянето е малко по-сложно. Latte **има два** лексикатора и анализатора: един за HTML шаблона и един за PHP-подобния код вътре в таговете. Освен това парсингът не се извършва след токенизацията, а лексикаторът и парсерът работят паралелно в две "нишки" и са координирани. Това е ракетна наука :-) - -Освен това всички тагове имат свои собствени процедури за парсване. Когато парсерът срещне таг, той извиква своята функция за парсиране (тя връща [Extension::getTags() |#getTags]). -Задачата му е да анализира аргументите на тага и, в случай на сдвоени тагове, вътрешното съдържание. Той връща *възел*, който става част от AST. За повече информация вижте раздел [Разработване на етикети |#Tag-Parsing-Function]. - -Когато анализаторът завърши работата си, получаваме пълния AST, представящ модела. Коренният възел е `Latte\Compiler\Nodes\TemplateNode`. Отделните възли в дървото представляват не само таговете, но и HTML елементите, техните атрибути, всички изрази, използвани в таговете, и т.н. - -След това се появяват т.нар. [passes на компилатора |#Compiler-Passes], които представляват функции (връщани от [Extension::getPasses() |#getPasses]), които модифицират AST. - -Целият процес - от зареждането на съдържанието на шаблона, през парсирането, до генерирането на получения файл - може да бъде оптимизиран с този код, с който можете да експериментирате и да изхвърляте междинни резултати: - -```php -$latte = new Latte\Engine; -$source = $latte->getLoader()->getContent($file); -$ast = $latte->parse($source); -$latte->applyPasses($ast); -$code = $latte->generate($ast, $file); -``` - - -Пример за AST .[#toc-example-of-ast] ------------------------------------- - -За да получите по-добра представа за AST, ще добавим пример. Това е оригиналният шаблон: - -```latte -{foreach $category->getItems() as $item} - <li>{$item->name|upper}</li> - {else} - no items found -{/foreach} -``` - -И това е нейното представяне като AST: - -/--pre -Latte\Compiler\Nodes\<b>TemplateNode</b>( - Latte\Compiler\Nodes\<b>FragmentNode</b>( - - Latte\Essential\Nodes\<b>ForeachNode</b>( - expression: Latte\Compiler\Nodes\Php\Expression\<b>MethodCallNode</b>( - object: Latte\Compiler\Nodes\Php\Expression\<b>VariableNode</b>('$category') - name: Latte\Compiler\Nodes\Php\<b>IdentifierNode</b>('getItems') - ) - value: Latte\Compiler\Nodes\Php\Expression\<b>VariableNode</b>('$item') - content: Latte\Compiler\Nodes\<b>FragmentNode</b>( - - Latte\Compiler\Nodes\<b>TextNode</b>(' ') - - Latte\Compiler\Nodes\<b>Html\ElementNode</b>('li')( - content: Latte\Essential\Nodes\<b>PrintNode</b>( - expression: Latte\Compiler\Nodes\Php\Expression\<b>PropertyFetchNode</b>( - object: Latte\Compiler\Nodes\Php\Expression\<b>VariableNode</b>('$item') - name: Latte\Compiler\Nodes\Php\<b>IdentifierNode</b>('name') - ) - modifier: Latte\Compiler\Nodes\Php\<b>ModifierNode</b>( - filters: - - Latte\Compiler\Nodes\Php\<b>FilterNode</b>('upper') - ) - ) - ) - ) - else: Latte\Compiler\Nodes\<b>FragmentNode</b>( - - Latte\Compiler\Nodes\<b>TextNode</b>('no items found') - ) - ) - ) -) -\-- - - -Потребителски етикети .[#toc-custom-tags] -========================================= - -За дефинирането на нов етикет са необходими три стъпки: - -- дефиниране на [функция за разбор на таг |#Tag-Parsing-Function] (отговорна за разбора на тага във възел) -- създаване на клас за възли (отговарящ за [генерирането на PHP код |#Generating-PHP-Code] и [обхождането на AST |#AST-Traversing]) -- регистриране на тага с [Extension::getTags() |#getTags] - - -Функция за парсване на етикети .[#toc-tag-parsing-function] ------------------------------------------------------------ - -Таговете се анализират от функцията за анализ (тази, която се връща от [Extension::getTags() |#getTags]). Неговата задача е да анализира и проверява всички аргументи в даден таг (за целта използва TagParser). -Също така, ако тагът е двойка, той ще поиска от TemplateParser да анализира и върне вътрешното съдържание. -Функцията създава и връща възел, който обикновено е дъщерен възел `Latte\Compiler\Nodes\StatementNode`, и той става част от AST. - -Създаваме клас за всеки възел, което ще направим сега, и елегантно поставяме функцията за парсинг в него като статична фабрика. Като пример, нека се опитаме да създадем познатия таг `{foreach}`: - -```php -use Latte\Compiler\Nodes\StatementNode; - -class ForeachNode extends StatementNode -{ - // функция за парсиране, която засега просто създава възел - public static function create(Latte\Compiler\Tag $tag): self - { - $node = new self; - return $node; - } - - public function print(Latte\Compiler\PrintContext $context): string - { - // кодът ще бъде добавен по-късно - } - - public function &getIterator(): \Generator - { - // кодът ще бъде добавен по-късно - } -} -``` - -На функцията за парсинг `create()` се предава обект [api:Latte\Compiler\Tag], който носи основна информация за тага (дали е класически таг или n:атрибут, на кой ред се намира и т.н.) и основно се отнася до обекта [api:Latte\Compiler\TagParser] в `$tag->parser`. - -Ако даден таг трябва да има аргументи, проверете за тях, като извикате `$tag->expectArguments()`. Методите на обекта `$tag->parser` са на разположение за анализирането им : - -- `parseExpression(): ExpressionNode` за израз, подобен на PHP (например `10 + 3`). -- `parseUnquotedStringOrExpression(): ExpressionNode` за израз или низ без кавички -- Съдържание на масива `parseArguments(): ArrayNode` (напр. `10, true, foo => bar`) -- `parseModifier(): ModifierNode` за модификатор (напр. `|upper|truncate:10`) -- `parseType(): expressionNode` за подсказка за типа (например `int|string` или `Foo\Bar[]`) - -и на ниско ниво [api:Latte\Compiler\TokenStream], работещи директно с токени: - -- `$tag->parser->stream->consume(...): Token` -- `$tag->parser->stream->tryConsume(...): ?Token` - -Latte разширява синтаксиса на PHP по малки начини, например чрез добавяне на модификатори, съкращаване на тернарни оператори или позволяване на запис на прости буквено-цифрови низове без кавички. Ето защо използваме термина *PHP-like* вместо PHP. Например, методът `parseExpression()` анализира `foo` като `'foo'`. -Също така *нецитиран низ* е специален случай на низ, който също не се нуждае от кавички, но в същото време не е задължително да бъде буквено-цифров. Например, това е пътят до файла в тага `{include ../file.latte}`. За анализирането му се използва методът `parseUnquotedStringOrExpression()`. - -.[note] -Изучаването на класовете възли, които съставляват Latte, е най-добрият начин да научите тънкостите на процеса на разбор. - -Нека се върнем към етикета `{foreach}`. В него очакваме аргументи с формата `expression + 'as' + second expression`, които анализираме по следния начин: - -```php -use Latte\Compiler\Nodes\StatementNode; -use Latte\Compiler\Nodes\Php\ExpressionNode; -use Latte\Compiler\Nodes\AreaNode; - -class ForeachNode extends StatementNode -{ - public ExpressionNode $expression; - public ExpressionNode $value; - - public static function create(Latte\Compiler\Tag $tag): self - { - $tag->expectArguments(); - $node = new self; - $node->expression = $tag->parser->parseExpression(); - $tag->parser->stream->consume('as'); - $node->value = $parser->parseExpression(); - return $node; - } -} -``` - -Изразите, които сме записали в променливите `$expression` и `$value`, са вложени възли. - -.[tip] -Дефинирайте променливите на подвъзела като **публични**, за да могат да бъдат променяни на [по-късни етапи от обработката |#Compiler-Passes], ако е необходимо. Също така трябва да ги направите **достъпни** за [обхождане |#AST-Traversing]. - -За сдвоени тагове като нашия методът трябва да позволява на TemplateParser да анализира вътрешното съдържание на тага. Това се обработва от `yield`, който връща двойката ''вътрешно съдържание, краен таг]''. Съхраняваме вътрешното съдържание в променлива `$node->content`. - -```php -public AreaNode $content; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $endTag] = yield; - return $node; -} -``` - -Ключовата дума `yield` води до прекратяване на метода `create()`, като връща управлението обратно към TemplateParser, който продължава да анализира съдържанието, докато достигне тага end. След това той предава управлението обратно на метода `create()`, който продължава оттам, където е спрял. Използването на метода `yield`, автоматично връща `Generator`. - -Можете също така да подадете масив от имена на тагове на `yield`, за които искате да спрете парсирането, ако се появят преди крайния таг. Това ни помага да прилагаме `{foreach}...{else}...{/foreach}` конструкция. Ако срещнем `{else}`, анализираме съдържанието след него до `$node->elseContent`: - -```php -public AreaNode $content; -public ?AreaNode $elseContent = null; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $nextTag] = yield ['else']; - if ($nextTag?->name === 'else') { - [$node->elseContent] = yield; - } - - return $node; -} -``` - -Връщането на възела завършва разбора на тага. - - -Генериране на PHP код .[#toc-generating-php-code] -------------------------------------------------- - -Всеки възел трябва да прилага метода `print()`. Връща код на PHP, който визуализира дадената част от шаблона (код за изпълнение). Като параметър той се предава на обекта [api:Latte\Compiler\PrintContext], който има полезен метод `format()`, улесняващ сглобяването на получения код. - -Методът `format(string $mask, ...$args)` приема следните заместители в маска: -- `%node` отпечатва възела -- `%dump` експортира стойността в PHP -- `%raw` вмъква текст директно, без преобразувания -- `%args` отпечатва ArrayNode като аргументи за извикване на функция -- `%line` отпечатва коментар с номер на реда -- `%escape(...)` избягва съдържанието -- `%modify(...)` прилага модификатор -- `%modifyContent(...)` прилага модификатор към блокове - - -Нашата функция `print()` може да изглежда по следния начин (за улеснение пренебрегваме клона `else` ) - -```php -public function print(Latte\Compiler\PrintContext $context): string -{ - return $context->format( - <<<'XX' - foreach (%node as %node) %line { - %node - } - - XX, - $this->expression, - $this->value, - $this->position, - $this->content, - ); -} -``` - -Променливата `$this->position` вече е дефинирана от класа [api:Latte\Compiler\Node] и се задава от анализатора. Той съдържа обект [api:Latte\Compiler\Position] с позицията на тага в изходния код като номер на ред и колона. - -Кодът по време на изпълнение може да използва спомагателни променливи. За да се избегне сблъсък с променливи, използвани от самия шаблон, е прието те да се предхождат от `$ʟ__`. - -Времето за изпълнение може също така да използва произволни стойности, които се предават на шаблона като доставчици чрез метода [Extension::getProviders() |#getProviders]. Достъпът до тях се осъществява чрез `$this->global->...`. - - -Байпас AST .[#toc-ast-traversing] ---------------------------------- - -За да може да се прегледа дървото AST в дълбочина, трябва да се приложи методът `getIterator()`. Това ще осигури достъп до вложените възли: - -```php -public function &getIterator(): \Generator -{ - yield $this->expression; - yield $this->value; - yield $this->content; - if ($this->elseContent) { - yield $this->elseContent; - } -} -``` - -Обърнете внимание, че `getIterator()` връща връзка. Това позволява на посетителите на възела да заменят отделни възли с други възли. - -.[warning] -Ако даден възел има подвъзли, е необходимо да се приложи този метод и да се осигури достъп до всички подвъзли. В противен случай може да се стигне до пробив в сигурността. Например режимът "пясъчник" няма да може да контролира подвъзлите и да гарантира, че в тях няма да бъдат извикани неразрешени проекти. - -Тъй като ключовата дума `yield` трябва да присъства в тялото на метода, дори ако той няма подчинени възли, запишете я по следния начин: - -```php -public function &getIterator(): \Generator -{ - if (false) { - yield; - } -} -``` - - -Компилаторът предава .[#toc-compiler-passes] -============================================ - -Пропусканията на компилатора са функции, които модифицират AST или събират информация в тях. Те се връщат чрез метода [Extension::getPasses() |#getPasses]. - - -Обхождане на възли .[#toc-node-traverser] ------------------------------------------ - -Най-разпространеният начин за работа с AST е да се използва [api:Latte\Compiler\NodeTraverser]: - -```php -use Latte\Compiler\Node; -use Latte\Compiler\NodeTraverser; - -$ast = (new NodeTraverser)->traverse( - $ast, - enter: fn(Node $node) => ..., - leave: fn(Node $node) => ..., -); -``` - -Функцията *вход* (т.е. посетител) се извиква, когато даден възел се срещне за първи път, преди да бъдат обработени неговите подвъзели. Функцията *leave* се извиква, след като са посетени всички възли. -Често срещан модел е, че *enter* се използва за събиране на някаква информация, а след това *leave* извършва модификации въз основа на тази информация. В момента, в който се извика *leave*, всички кодове във възела ще са посетени и ще е събрана необходимата информация. - -Как да променя AST? Най-лесният начин е просто да промените свойствата на възела. Вторият начин е да замените изцяло възела, като върнете нов възел. Пример: Следният код ще промени всички цели числа в AST в низове (например 42 ще бъде заменено с `'42'`). - -```php -use Latte\Compiler\Nodes\Php; - -$ast = (new NodeTraverser)->traverse( - $ast, - leave: function (Node $node) { - if ($node instanceof Php\Scalar\IntegerNode) { - return new Php\Scalar\StringNode((string) $node->value); - } - }, -); -``` - -Един AST може да съдържа хиляди възли и преминаването през всички възли може да бъде бавно. В някои случаи може да се избегне цялостно обхождане. - -Ако търсите всички `Html\ElementNode` в дървото, знаете, че след като разгледате `Php\ExpressionNode`, няма смисъл да проверявате всички негови дъщерни възли, защото HTML не може да бъде вътре в изразите. В този случай можете да кажете на байпасния модул да не отива във възела на класа: - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Php\ExpressionNode) { - return NodeTraverser::DontTraverseChildren; - } - // ... - }, -); -``` - -Ако търсите само един конкретен възел, можете също така напълно да прекъснете обхождането, след като го намерите. - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Nodes\ParametersNode) { - return NodeTraverser::StopTraversal; - } - // ... - }, -); -``` - - -Асистенти на възли .[#toc-node-helpers] ---------------------------------------- - -Класът [api:Latte\Compiler\NodeHelpers] предоставя няколко метода, чрез които могат да се намерят AST възли, които отговарят на определено обратно повикване, и т.н. Показани са няколко примера: - -```php -use Latte\Compiler\NodeHelpers; - -// намира всички възли на HTML елементи -$elements = NodeHelpers::find($ast, fn(Node $node) => $node instanceof Nodes\Html\ElementNode); - -// намира първия текстов възел -$text = NodeHelpers::findFirst($ast, fn(Node $node) => $node instanceof Nodes\TextNode); - -// преобразува възел със стойност PHP в реална стойност -$value = NodeHelpers::toValue($node); - -// преобразува статичен текстов възел в низ -$text = NodeHelpers::toText($node); -``` diff --git a/latte/bg/custom-filters.texy b/latte/bg/custom-filters.texy new file mode 100644 index 0000000000..db7041068c --- /dev/null +++ b/latte/bg/custom-filters.texy @@ -0,0 +1,231 @@ +Създаване на персонализирани филтри +*********************************** + +.[perex] +Филтрите са мощни инструменти за форматиране и промяна на данни директно в шаблоните на Latte. Те предлагат чист синтаксис с помощта на символа за тръба (`|`) за трансформиране на променливи или резултати от изрази в желания изходен формат. + + +Какво са филтрите? +================== + +Филтрите в Latte по същество са **PHP функции, проектирани специално за трансформиране на входна стойност в изходна стойност**. Те се прилагат с помощта на запис с тръба (`|`) вътре в изразите на шаблона (`{...}`). + +**Удобство:** Филтрите ви позволяват да капсулирате често срещани задачи за форматиране (като форматиране на дати, промяна на регистъра на буквите, съкращаване) или манипулиране на данни в повторно използваеми единици. Вместо да повтаряте сложен PHP код във вашите шаблони, можете просто да приложите филтър: +```latte +{* Вместо сложен PHP за съкращаване: *} +{$article->text|truncate:100} + +{* Вместо код за форматиране на дати: *} +{$event->startTime|date:'Y-m-d H:i'} + +{* Прилагане на множество трансформации: *} +{$product->name|lower|capitalize} +``` + +**Четливост:** Използването на филтри прави шаблоните по-прегледни и по-фокусирани върху презентацията, тъй като трансформационната логика се премества в дефиницията на филтъра. + +**Контекстна чувствителност:** Ключово предимство на филтрите в Latte е тяхната способност да бъдат [контекстно чувствителни |#Контекстни филтри]. Това означава, че филтърът може да разпознае типа на съдържанието, с което работи (HTML, JavaScript, обикновен текст и т.н.), и да приложи съответната логика или екраниране, което е от съществено значение за сигурността и коректността, особено при генериране на HTML. + +**Интеграция с логиката на приложението:** Подобно на персонализираните функции, PHP callable зад филтъра може да бъде затваряне (closure), статичен метод или метод на инстанция. Това позволява на филтрите да достъпват услуги или данни на приложението, ако е необходимо, въпреки че основната им цел остава *трансформация на входната стойност*. + +Latte по подразбиране предоставя богат набор от [стандартни филтри |filters]. Персонализираните филтри ви позволяват да разширите този набор с форматиране и трансформации, специфични за вашия проект. + +Ако трябва да извършвате логика, базирана на *множество* входове или нямате основна стойност за трансформиране, вероятно е по-подходящо да използвате [персонализирана функция |custom-functions]. Ако трябва да генерирате сложен маркъп или да контролирате потока на шаблона, обмислете [персонализиран таг |custom-tags]. + + +Създаване и регистриране на филтри +================================== + +Има няколко начина за дефиниране и регистриране на персонализирани филтри в Latte. + + +Директна регистрация чрез `addFilter()` +--------------------------------------- + +Най-простият начин за добавяне на филтър е използването на метода `addFilter()` директно върху обекта `Latte\Engine`. Посочвате името на филтъра (както ще бъде използван в шаблона) и съответния PHP callable. + +```php +$latte = new Latte\Engine; + +// Прост филтър без аргументи +$latte->addFilter('initial', fn(string $s): string => mb_substr($s, 0, 1) . '.'); + +// Филтър с незадължителен аргумент +$latte->addFilter('shortify', function (string $s, int $len = 10): string { + return mb_substr($s, 0, $len); +}); + +// Филтър, обработващ масив +$latte->addFilter('sum', fn(array $numbers): int|float => array_sum($numbers)); +``` + +**Използване в шаблона:** + +```latte +{$name|initial} {* Изписва 'J.' ако $name е 'John' *} +{$description|shortify} {* Използва дължина по подразбиране 10 *} +{$description|shortify:50} {* Използва дължина 50 *} +{$prices|sum} {* Изписва сумата на елементите в масива $prices *} +``` + +**Предаване на аргументи:** + +Стойността отляво на тръбата (`|`) винаги се предава като *първи* аргумент на функцията на филтъра. Всички параметри, посочени след двоеточието (`:`) в шаблона, се предават като следващи аргументи. + +```latte +{$text|shortify:30} +// Извиква PHP функцията shortify($text, 30) +``` + + +Регистрация чрез разширение +--------------------------- + +За по-добра организация, особено при създаване на повторно използваеми набори от филтри или тяхното споделяне като пакети, препоръчителният начин е да ги регистрирате в рамките на [разширение на Latte |extending-latte#Latte Extension]: + +```php +namespace App\Latte; + +use Latte\Extension; + +class MyLatteExtension extends Extension +{ + public function getFilters(): array + { + return [ + 'initial' => $this->initial(...), + 'shortify' => $this->shortify(...), + ]; + } + + public function initial(string $s): string + { + return mb_substr($s, 0, 1) . '.'; + } + + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// Регистрация +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +Този подход поддържа логиката на вашия филтър капсулирана и регистрацията проста. + + +Използване на зареждач на филтри +-------------------------------- + +Latte позволява да се регистрира зареждач на филтри с помощта на `addFilterLoader()`. Това е единствено callable, което Latte ще поиска за всяко непознато име на филтър по време на компилация. Зареждачът връща PHP callable на филтъра или `null`. + +```php +$latte = new Latte\Engine; + +// Зареждачът може динамично да създава/получава callable филтри +$latte->addFilterLoader(function (string $name): ?callable { + if ($name === 'myLazyFilter') { + // Представете си тук тежка инициализация... + $service = get_some_expensive_service(); + return fn($value) => $service->process($value); + } + return null; +}); +``` + +Този метод беше първоначално предназначен за мързеливо зареждане на филтри с много **тежка инициализация**. Въпреки това, съвременните практики за вмъкване на зависимости (dependency injection) обикновено се справят с мързеливите услуги по-ефективно. + +Зареждачите на филтри добавят сложност и като цяло не се препоръчват в полза на директната регистрация с `addFilter()` или в рамките на разширение с `getFilters()`. Използвайте зареждачи само ако имате сериозна, специфична причина, свързана с проблеми с производителността при инициализацията на филтри, които не могат да бъдат решени по друг начин. + + +Филтри, използващи клас с атрибути +---------------------------------- + +Друг елегантен начин за дефиниране на филтри е използването на методи във вашия [клас на параметри на шаблона |develop#Параметри като клас]. Достатъчно е да добавите атрибут `#[Latte\Attributes\TemplateFilter]` към метода. + +```php +use Latte\Attributes\TemplateFilter; + +class TemplateParameters +{ + public function __construct( + public string $description, + // други параметри... + ) {} + + #[TemplateFilter] + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// Предаване на обекта в шаблона +$params = new TemplateParameters(description: '...'); +$latte->render('template.latte', $params); +``` + +Latte автоматично разпознава и регистрира методи, маркирани с този атрибут, когато обектът `TemplateParameters` е предаден в шаблона. Името на филтъра в шаблона ще бъде същото като името на метода (`shortify` в този случай). + +```latte +{* Използване на филтър, дефиниран в класа на параметрите *} +{$description|shortify:50} +``` + + +Контекстни филтри +================= + +Понякога филтърът се нуждае от повече информация отколкото само входната стойност. Може да се наложи да знае **типа на съдържанието** на низа, с който работи (напр. HTML, JavaScript, обикновен текст) или дори да го промени. Това е ситуация за контекстни филтри. + +Контекстният филтър се дефинира по същия начин като обикновен филтър, но неговият **първи параметър трябва да бъде** типово означен като `Latte\Runtime\FilterInfo`. Latte автоматично разпознава този подпис и при извикване на филтъра предава обект `FilterInfo`. Следващите параметри получават аргументите на филтъра както обикновено. + +```php +use Latte\Runtime\FilterInfo; +use Latte\ContentType; + +$latte->addFilter('money', function (FilterInfo $info, float $amount): string { + // 1. Проверете входния тип на съдържанието (незадължително, но препоръчително) + // Разрешете null (променлив вход) или обикновен текст. Отхвърлете, ако се прилага върху HTML и др. + if (!in_array($info->contentType, [null, ContentType::Text], true)) { + $actualType = $info->contentType ?? 'mixed'; + throw new \RuntimeException( + "Филтърът |money е използван в несъвместим тип съдържание $actualType. Очакван текст или null." + ); + } + + // 2. Извършете трансформацията + $formatted = number_format($amount, 2, '.', ',') . ' EUR'; + $htmlOutput = '<i>' . htmlspecialchars($formatted) . '</i>'; // Гарантирайте правилно екраниране! + + // 3. Декларирайте изходния тип на съдържанието + $info->contentType = ContentType::Html; + + // 4. Върнете резултата + return $htmlOutput; +}); +``` + +`$info->contentType` е низова константа от `Latte\ContentType` (напр. `ContentType::Html`, `ContentType::Text`, `ContentType::JavaScript` и др.) или `null`, ако филтърът се прилага върху променлива (`{$var|filter}`). Можете да **четете** тази стойност, за да проверите входния контекст, и да **записвате** в нея, за да декларирате типа на изходния контекст. + +Настройвайки типа на съдържанието на HTML, съобщавате на Latte, че низът, върнат от вашия филтър, е безопасен HTML. Latte тогава **няма** да приложи върху този резултат своето подразбиращо се автоматично екраниране. Това е от съществено значение, ако вашият филтър генерира HTML маркъп. + +.[warning] +Ако вашият филтър генерира HTML, **вие сте отговорни за правилното екраниране на всякакви входни данни**, използвани в този HTML (както в случая с извикването на `htmlspecialchars($formatted)` по-горе). Пропускането може да създаде XSS уязвимости. Ако вашият филтър връща само обикновен текст, не е необходимо да задавате `$info->contentType`. + + +Филтри върху блокове +-------------------- + +Всички филтри, приложени върху [блокове |tags#block], *трябва* да бъдат контекстни. Това е така, защото съдържанието на блока има дефиниран тип на съдържанието (обикновено HTML), за който филтърът трябва да е наясно. + +```latte +{block heading|money}1000{/block} +{* Филтърът 'money' ще получи '1000' като втори аргумент + а $info->contentType ще бъде ContentType::Html *} +``` + +Контекстните филтри предоставят силен контрол върху това как данните се обработват въз основа на техния контекст, позволяват напреднали функции и гарантират правилно поведение на екранирането, особено при генериране на HTML съдържание. diff --git a/latte/bg/custom-functions.texy b/latte/bg/custom-functions.texy new file mode 100644 index 0000000000..4f958508db --- /dev/null +++ b/latte/bg/custom-functions.texy @@ -0,0 +1,144 @@ +Създаване на персонализирани функции +************************************ + +.[perex] +Лесно добавяйте персонализирани помощни функции към шаблоните на Latte. Извиквайте PHP логика директно в изразите за изчисления, достъп до услуги или генериране на динамично съдържание, което поддържа вашите шаблони чисти и мощни. + + +Какво са функциите? +=================== + +Функциите в Latte ви позволяват да разширите набора от функции, които могат да бъдат извиквани в рамките на изрази в шаблоните (`{...}`). Можете да си ги представите като **персонализирани PHP функции, достъпни само вътре във вашите Latte шаблони**. Това носи няколко предимства: + +**Удобство:** Можете да дефинирате помощна логика (като изчисления, форматиране или достъп до данни на приложението) и да я извиквате с помощта на прост, познат синтаксис на функции директно в шаблона, точно както бихте извикали `strlen()` или `date()` в PHP. + +```latte +{var $userInitials = initials($userName)} {* напр. 'J. D.' *} + +{if hasPermission('article', 'edit')} + <a href="...">Редактиране</a> +{/if} +``` + +**Без замърсяване на глобалното именно пространство:** За разлика от дефинирането на истинска глобална функция в PHP, функциите на Latte съществуват само в контекста на рендиране на шаблона. Не е необходимо да натоварвате глобалното именно пространство на PHP с помощници, които са специфични само за шаблоните. + +**Интеграция с логиката на приложението:** PHP callable обектът, стоящ зад функцията на Latte, може да бъде всичко – анонимна функция, статичен метод или метод на инстанция. Това означава, че вашите функции в шаблоните могат лесно да достъпват услуги на приложението, бази данни, конфигурация или всякаква друга необходима логика чрез улавяне на променливи (в случай на анонимни функции) или с помощта на dependency injection (в случай на обекти). Горният пример `hasPermission` ясно демонстрира това, като вероятно извиква на заден план услуга за авторизация. + +**Предефиниране на вградени функции (по избор):** Можете дори да дефинирате функция на Latte със същото име като вградена PHP функция. В шаблона ще бъде извикана вашата собствена версия вместо оригиналната функция. Това може да бъде полезно за предоставяне на поведение, специфично за шаблона, или за осигуряване на последователна обработка (напр. гарантиране, че `strlen` винаги ще бъде многобайтово безопасна). Използвайте тази функция внимателно, за да избегнете недоразумения. + +По подразбиране Latte позволява извикването на *всички* вградени PHP функции (ако не са ограничени от [Sandbox |sandbox]). Персонализираните функции разширяват тази вградена библиотека със специфичните нужди на вашия проект. + +Ако само трансформирате единична стойност, може да е по-подходящо да използвате [персонализиран филтър |custom-filters]. + + +Създаване и регистриране на функции +=================================== + +Подобно на филтрите, има няколко начина за дефиниране и регистриране на персонализирани функции. + + +Директна регистрация с `addFunction()` +-------------------------------------- + +Най-простият метод е използването на `addFunction()` върху обекта `Latte\Engine`. Посочвате името на функцията (както ще се показва в шаблона) и съответния PHP callable обект. + +```php +$latte = new Latte\Engine; + +// Проста помощна функция +$latte->addFunction('initials', function (string $name): string { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; +}); +``` + +**Използване в шаблона:** + +```latte +{var $userInitials = initials($userName)} +``` + +Аргументите на функцията в шаблона се предават директно на PHP callable обекта в същия ред. PHP функционалности като типови подсказки, стойности по подразбиране и вариативни параметри (`...`) работят според очакванията. + + +Регистрация чрез разширение +--------------------------- + +За по-добра организация и повторна използваемост, регистрирайте функции в рамките на [Latte разширение |extending-latte#Latte Extension]. Този подход е препоръчителен за по-сложни приложения или споделени библиотеки. + +```php +namespace App\Latte; + +use Latte\Extension; +use Nette\Security\Authorizator; + +class MyLatteExtension extends Extension +{ + public function __construct( + // Предполагаме, че услугата Authorizator се инжектира + private Authorizator $authorizator, + ) { + } + + public function getFunctions(): array + { + // Регистрация на методи като Latte функции + return [ + 'hasPermission' => $this->hasPermission(...), + ]; + } + + public function hasPermission(string $resource, string $action): bool + { + return $this->authorizator->isAllowed($resource, $action); + } +} + +// Регистрация (предполагаме, че $container съдържа DI контейнер) +$extension = $container->getByType(App\Latte\MyLatteExtension::class); +$latte = new Latte\Engine; +$latte->addExtension($extension); +``` + +Този подход ясно показва как функциите, дефинирани в Latte, могат да бъдат подкрепени от методи на обекти, които могат да имат свои собствени зависимости, управлявани от контейнера за dependency injection на вашето приложение или фабрика. Това поддържа логиката на вашите шаблони свързана с ядрото на приложението, като същевременно запазва ясна организация. + + +Функции, използващи клас с атрибути +----------------------------------- + +Подобно на филтрите, функциите могат да бъдат дефинирани като методи във вашия [клас на параметри на шаблона |develop#Параметри като клас] с помощта на атрибута `#[Latte\Attributes\TemplateFunction]`. + +```php +use Latte\Attributes\TemplateFunction; + +class TemplateParameters +{ + public function __construct( + public string $userName, + // други параметри... + ) {} + + // Този метод ще бъде достъпен като {initials(...)} в шаблона + #[TemplateFunction] + public function initials(string $name): string + { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; + } +} + +// Предаване на обекта в шаблона +$params = new TemplateParameters(userName: 'John Doe', /* ... */); +$latte->render('template.latte', $params); +``` + +Latte автоматично открива и регистрира методи, маркирани с този атрибут, когато обектът на параметрите е предаден в шаблона. Името на функцията в шаблона съответства на името на метода. + +```latte +{* Използване на функция, дефинирана в класа на параметрите *} +{var $inits = initials($userName)} +``` + +**Контекстни функции?** + +За разлика от филтрите, не съществува директна концепция за "контекстни функции", които биха получили обект, подобен на `FilterInfo`. Функциите работят в рамките на изрази и обикновено не се нуждаят от директен достъп до контекста на рендиране или информация за типа на съдържанието по същия начин, както филтрите, приложени върху блокове. diff --git a/latte/bg/custom-tags.texy b/latte/bg/custom-tags.texy new file mode 100644 index 0000000000..46bbf08171 --- /dev/null +++ b/latte/bg/custom-tags.texy @@ -0,0 +1,1135 @@ +Създаване на персонализирани тагове +*********************************** + +.[perex] +Тази страница предоставя изчерпателно ръководство за създаване на персонализирани тагове в Latte. Ще обсъдим всичко - от прости тагове до по-сложни сценарии с вложено съдържание и специфични нужди от парсване, като надграждаме разбирането ви за това как Latte компилира шаблони. + +Персонализираните тагове осигуряват най-високо ниво на контрол върху синтаксиса на шаблона и логиката на рендиране, но са и най-сложната точка за разширяване. Преди да решите да създадете персонализиран таг, винаги обмисляйте дали [не съществува по-просто решение |extending-latte#Начини за разширяване на Latte] или дали вече не съществува подходящ таг в [стандартния набор |tags]. Използвайте персонализирани тагове само когато по-простите алтернативи не са достатъчни за вашите нужди. + + +Разбиране на процеса на компилация +================================== + +За ефективно създаване на персонализирани тагове е полезно да се обясни как Latte обработва шаблони. Разбирането на този процес изяснява защо таговете са структурирани по този начин и как се вписват в по-широкия контекст. + +Компилацията на шаблон в Latte, опростено, включва следните ключови стъпки: + +1. **Лексикален анализ:** Лексерът чете изходния код на шаблона (файл `.latte`) и го разделя на последователност от малки, отделни части, наречени **токени** (напр. `{`, `foreach`, `$variable`, `}`, HTML текст и т.н.). +2. **Парсване:** Парсерът взема този поток от токени и изгражда от него смислена дървовидна структура, представяща логиката и съдържанието на шаблона. Това дърво се нарича **абстрактно синтактично дърво (AST)**. +3. **Компилационни проходи:** Преди генерирането на PHP код, Latte изпълнява [компилационни проходи |compiler passes]. Това са функции, които обхождат цялото AST и могат да го модифицират или да събират информация. Тази стъпка е ключова за функции като сигурност ([Sandbox |sandbox]) или оптимизация. +4. **Генериране на код:** Накрая компилаторът обхожда (потенциално модифицираното) AST и генерира съответния код на PHP клас. Този PHP код е това, което всъщност рендира шаблона при изпълнение. +5. **Кеширане:** Генерираният PHP код се съхранява на диск, което прави последващите рендирания много бързи, тъй като стъпки 1-4 се пропускат. + +В действителност компилацията е малко по-сложна. Latte **има два** лексера и парсера: един за HTML шаблона и втори за PHP-подобния код вътре в таговете. Също така парсването не се извършва след токенизацията, а лексерът и парсерът работят паралелно в две "нишки" и се координират. Повярвайте ми, програмирането на това беше ракетна наука :-) + +Целият процес, от зареждането на съдържанието на шаблона, през парсването, до генерирането на крайния файл, може да бъде изпълнен последователно с този код, с който можете да експериментирате и да извеждате междинни резултати: + +```php +$latte = new Latte\Engine; +$source = $latte->getLoader()->getContent($file); +$ast = $latte->parse($source); +$latte->applyPasses($ast); +$code = $latte->generate($ast, $file); +``` + + +Анатомия на таг +=============== + +Създаването на напълно функционален персонализиран таг в Latte включва няколко взаимосвързани части. Преди да се заемем с имплементацията, нека разберем основните концепции и терминология, използвайки аналогия с HTML и Document Object Model (DOM). + + +Тагове срещу Възли (Аналогия с HTML) +------------------------------------ + +В HTML пишем **тагове** като `<p>` или `<div>...</div>`. Тези тагове са синтаксис в изходния код. Когато браузърът парсва този HTML, той създава представяне в паметта, наречено **Document Object Model (DOM)**. В DOM HTML таговете са представени от **възли** (конкретно възли `Element` в терминологията на JavaScript DOM). С тези *възли* работим програмно (напр. с помощта на JavaScript `document.getElementById(...)` се връща възел Element). Тагът е само текстово представяне в изходния файл; възелът е обектно представяне в логическото дърво. + +Latte работи по подобен начин: + +- Във файла `.latte` на шаблона пишете **Latte тагове**, като `{foreach ...}` и `{/foreach}`. Това е синтаксисът, с който вие като автор на шаблона работите. +- Когато Latte **парсва** шаблона, той изгражда **Abstract Syntax Tree (AST)**. Това дърво е съставено от **възли**. Всеки Latte таг, HTML елемент, част от текст или израз в шаблона се превръща в един или повече възли в това дърво. +- Основният клас за всички възли в AST е `Latte\Compiler\Node`. Точно както DOM има различни типове възли (Element, Text, Comment), AST на Latte има различни типове възли. Ще се сблъскате с `Latte\Compiler\Nodes\TextNode` за статичен текст, `Latte\Compiler\Nodes\Html\ElementNode` за HTML елементи, `Latte\Compiler\Nodes\Php\ExpressionNode` за изрази вътре в таговете и ключово за персонализирани тагове, възли, наследяващи от `Latte\Compiler\Nodes\StatementNode`. + + +Защо `StatementNode`? +--------------------- + +HTML елементите (`Html\ElementNode`) основно представят структура и съдържание. PHP изразите (`Php\ExpressionNode`) представят стойности или изчисления. Но какво да кажем за Latte тагове като `{if}`, `{foreach}` или нашия собствен `{datetime}`? Тези тагове *изпълняват действия*, управляват потока на програмата или генерират изход въз основа на логика. Те са функционални единици, които правят Latte мощен шаблонен *engine*, а не просто маркиращ език. + +В програмирането такива единици, изпълняващи действия, често се наричат "statements" (инструкции). Затова възлите, представящи тези функционални Latte тагове, обикновено наследяват от `Latte\Compiler\Nodes\StatementNode`. Това ги отличава от чисто структурните възли (като HTML елементи) или възлите, представящи стойности (като изрази). + + +Ключови компоненти +================== + +Нека разгледаме основните компоненти, необходими за създаване на персонализиран таг: + + +Функция за парсване на таг +-------------------------- + +- Тази PHP callable функция парсва синтаксиса на Latte тага (`{...}`) в изходния шаблон. +- Получава информация за тага (като неговото име, позиция и дали е n:атрибут) чрез обекта [api:Latte\Compiler\Tag]. +- Нейният основен инструмент за парсване на аргументи и изрази вътре в ограничителите на тага е обектът [api:Latte\Compiler\TagParser], достъпен чрез `$tag->parser` (това е различен парсер от този, който парсва целия шаблон). +- За сдвоени тагове използва `yield`, за да сигнализира на Latte да парсва вътрешното съдържание между началния и крайния таг. +- Крайната цел на парсващата функция е да създаде и върне инстанция на **класа на възела**, която се добавя към AST. +- Практика е (макар и да не е задължително) да се имплементира парсващата функция като статичен метод (често наречен `create`) директно в съответния клас на възела. Това поддържа парсващата логика и представянето на възела спретнато в един пакет, позволява достъп до private/protected елементи на класа, ако е необходимо, и подобрява организацията. + + +Клас на възела +-------------- + +- Представлява *логическата функция* на вашия таг в **Abstract Syntax Tree (AST)**. +- Съдържа парсваната информация (като аргументи или съдържание) като публични свойства. Тези свойства често съдържат други инстанции на `Node` (напр. `ExpressionNode` за парсвани аргументи, `AreaNode` за парсвано съдържание). +- Методът `print(PrintContext $context): string` генерира *PHP код* (инструкция или серия от инструкции), който изпълнява действието на тага по време на рендиране на шаблона. +- Методът `getIterator(): \Generator` предоставя достъп до дъщерните възли (аргументи, съдържание) за обхождане от **компилационните проходи**. Трябва да предоставя референции (`&`), за да позволи на проходите потенциално да модифицират или заменят подвъзли. +- След като целият шаблон е парсван в AST, Latte изпълнява серия от [компилационни проходи |compiler-passes]. Тези проходи обхождат *цялото* AST, използвайки метода `getIterator()`, предоставен от всеки възел. Те могат да инспектират възли, да събират информация и дори да *модифицират* дървото (напр. чрез промяна на публичните свойства на възлите или пълна замяна на възли). Този дизайн, изискващ комплексен `getIterator()`, е фундаментален. Той позволява на мощни функции като [Sandbox |sandbox] да анализират и потенциално да променят поведението на *всяка* част от шаблона, включително вашите персонализирани тагове, осигурявайки сигурност и консистентност. + + +Регистрация чрез разширение +--------------------------- + +- Трябва да информирате Latte за вашия нов таг и коя парсваща функция трябва да се използва за него. Това се случва в рамките на [Latte разширение |extending-latte#Latte Extension]. +- Вътре във вашия клас на разширението имплементирате метода `getTags(): array`. Този метод връща асоциативен масив, където ключовете са имената на таговете (напр. `'mytag'`, `'n:myattribute'`), а стойностите са PHP callable функции, представляващи техните съответни парсващи функции (напр. `MyNamespace\DatetimeNode::create(...)`). + +Резюме: **Функцията за парсване на таг** преобразува *изходния код на шаблона* на вашия таг във **възел на AST**. **Класът на възела** след това може да преобразува *себе си* в изпълним *PHP код* за компилирания шаблон и предоставя достъп до своите подвъзли за **компилационните проходи** чрез `getIterator()`. **Регистрацията чрез разширение** свързва името на тага с парсващата функция и уведомява Latte за него. + +Сега ще разгледаме как да имплементираме тези компоненти стъпка по стъпка. + + +Създаване на прост таг +====================== + +Нека се заемем със създаването на вашия първи персонализиран Latte таг. Ще започнем с много прост пример: таг с име `{datetime}`, който извежда текущата дата и час. **Първоначално този таг няма да приема никакви аргументи**, но ще го подобрим по-късно в секцията [#"Парсване на аргументи на таг"]. Той също така няма вътрешно съдържание. + +Този пример ще ви преведе през основните стъпки: дефиниране на класа на възела, имплементиране на неговите методи `print()` и `getIterator()`, създаване на парсваща функция и накрая регистриране на тага. + +**Цел:** Имплементиране на `{datetime}` за извеждане на текущата дата и час с помощта на PHP функцията `date()`. + + +Създаване на класа на възела +---------------------------- + +Първо, имаме нужда от клас, който ще представлява нашия таг в Abstract Syntax Tree (AST). Както беше обсъдено по-горе, наследяваме от `Latte\Compiler\Nodes\StatementNode`. + +Създайте файл (напр. `DatetimeNode.php`) и дефинирайте класа: + +```php +<?php + +namespace App\Latte; + +use Latte\Compiler\Nodes\StatementNode; +use Latte\Compiler\PrintContext; +use Latte\Compiler\Tag; + +class DatetimeNode extends StatementNode +{ + /** + * Функцията за парсване на таг, извиквана, когато е намерен {datetime}. + */ + public static function create(Tag $tag): self + { + // Нашият прост таг в момента не приема никакви аргументи, така че не трябва да парсваме нищо + $node = $tag->node = new self; + return $node; + } + + /** + * Генерира PHP код, който ще бъде изпълнен при рендиране на шаблона. + */ + public function print(PrintContext $context): string + { + return $context->format( + 'echo date(\'Y-m-d H:i:s\') %line;', + $this->position, + ); + } + + /** + * Предоставя достъп до дъщерните възли за компилационните проходи на Latte. + */ + public function &getIterator(): \Generator + { + false && yield; + } +} +``` + +Когато Latte срещне `{datetime}` в шаблона, той извиква парсващата функция `create()`. Нейната задача е да върне инстанция на `DatetimeNode`. + +Методът `print()` генерира PHP код, който ще бъде изпълнен при рендиране на шаблона. Извикваме метода `$context->format()`, който съставя крайния низ от PHP код за компилирания шаблон. Първият аргумент, `'echo date('Y-m-d H:i:s') %line;'`, е маска, в която се попълват следващите параметри. Placeholder-ът `%line` казва на метода `format()` да използва втория аргумент, който е `$this->position`, и да вмъкне коментар като `/* line 15 */`, който свързва генерирания PHP код обратно към оригиналния ред на шаблона, което е ключово за дебъгване. + +Свойството `$this->position` се наследява от базовия клас `Node` и се задава автоматично от парсера на Latte. То съдържа обект [api:Latte\Compiler\Position], който показва къде е намерен тагът в изходния файл `.latte`. + +Методът `getIterator()` е от съществено значение за компилационните проходи. Той трябва да предоставя всички дъщерни възли, но нашият прост `DatetimeNode` в момента няма никакви аргументи или съдържание, следователно няма дъщерни възли. Въпреки това, методът все още трябва да съществува и да бъде генератор, т.е. ключовата дума `yield` трябва да присъства по някакъв начин в тялото на метода. + + +Регистрация чрез разширение +--------------------------- + +Накрая, нека информираме Latte за новия таг. Създайте [клас на разширение |extending-latte#Latte Extension] (напр. `MyLatteExtension.php`) и регистрирайте тага в неговия метод `getTags()`. + +```php +<?php + +namespace App\Latte; + +use Latte\Extension; + +class MyLatteExtension extends Extension +{ + /** + * Връща списък с тагове, предоставени от това разширение. + * @return array<string, callable> Карта: 'име-на-таг' => парсваща-функция + */ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + // По-късно регистрирайте повече тагове тук + ]; + } +} +``` + +След това регистрирайте това разширение в Latte Engine: + +```php +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +Създайте шаблон: + +```latte +<p>Страницата е генерирана: {datetime}</p> +``` + +Очакван изход: `<p>Страницата е генерирана: 2023-10-27 11:00:00</p>` + + +Резюме на тази фаза +------------------- + +Успешно създадохме основен персонализиран таг `{datetime}`. Дефинирахме неговото представяне в AST (`DatetimeNode`), обработихме неговото парсване (`create()`), специфицирахме как трябва да генерира PHP код (`print()`), осигурихме достъп до неговите деца за обхождане (`getIterator()`) и го регистрирахме в Latte. + +В следващата секция ще подобрим този таг, така че да приема аргументи, и ще покажем как да парсваме изрази и да управляваме дъщерни възли. + + +Парсване на аргументи на таг +============================ + +Нашият прост таг `{datetime}` работи, но не е много гъвкав. Нека го подобрим, така че да приема незадължителен аргумент: форматиращ низ за функцията `date()`. Изискваният синтаксис ще бъде `{datetime $format}`. + +**Цел:** Да се модифицира `{datetime}`, така че да приема незадължителен PHP израз като аргумент, който ще бъде използван като форматиращ низ за `date()`. + + +Представяне на `TagParser` +-------------------------- + +Преди да модифицираме кода, е важно да разберем инструмента, който ще използваме [api:Latte\Compiler\TagParser]. Когато основният парсер на Latte (`TemplateParser`) срещне Latte таг като `{datetime ...}` или n:атрибут, той делегира парсването на съдържанието *вътре* в тага (частта между `{` и `}` или стойността на атрибута) на специализиран `TagParser`. + +Този `TagParser` работи изключително с **аргументите на тага**. Неговата задача е да обработва токените, представляващи тези аргументи. Ключово е, че **трябва да обработи цялото съдържание**, което му е предоставено. Ако вашата парсваща функция приключи, но `TagParser` не е достигнал края на аргументите (проверява се чрез `$tag->parser->isEnd()`), Latte ще хвърли изключение, тъй като това показва, че вътре в тага са останали неочаквани токени. Обратно, ако тагът *изисква* аргументи, трябва да извикате `$tag->expectArguments()` в началото на вашата парсваща функция. Този метод проверява дали има аргументи и хвърля полезно изключение, ако тагът е бил използван без никакви аргументи. + +`TagParser` предлага полезни методи за парсване на различни видове аргументи: + +- `parseExpression(): ExpressionNode`: Парсва PHP-подобен израз (променливи, литерали, оператори, извиквания на функции/методи и т.н.). Обработва синтактичната захар на Latte, като например третирането на прости буквено-цифрови низове като низове в кавички (напр. `foo` се парсва, сякаш е `'foo'`). +- `parseUnquotedStringOrExpression(): ExpressionNode`: Парсва или стандартен израз, или *низ без кавички*. Низовете без кавички са последователности, позволени от Latte без кавички, често използвани за неща като пътища до файлове (напр. `{include ../file.latte}`). Ако парсва низ без кавички, връща `StringNode`. +- `parseArguments(): ArrayNode`: Парсва аргументи, разделени със запетаи, потенциално с ключове, като `10, name: 'John', true`. +- `parseModifier(): ModifierNode`: Парсва филтри като `|upper|truncate:10`. +- `parseType(): ?SuperiorTypeNode`: Парсва PHP указания за тип като `int`, `?string`, `array|Foo`. + +За по-сложни или ниско ниво нужди от парсване, можете директно да взаимодействате с [потока от токени |api:Latte\Compiler\TokenStream] чрез `$tag->parser->stream`. Този обект предоставя методи за проверка и обработка на отделни токени: + +- `$tag->parser->stream->is(...): bool`: Проверява дали *текущият* токен съответства на някой от указаните типове (напр. `Token::Php_Variable`) или литерални стойности (напр. `'as'`) без да го консумира. Полезно за поглед напред. +- `$tag->parser->stream->consume(...): Token`: Консумира *текущия* токен и премества позицията на потока напред. Ако са предоставени очаквани типове/стойности на токени като аргументи и текущият токен не съответства, хвърля `CompileException`. Използвайте това, когато *очаквате* определен токен. +- `$tag->parser->stream->tryConsume(...): ?Token`: Опитва се да консумира *текущия* токен *само ако* съответства на един от указаните типове/стойности. Ако съответства, консумира токена и го връща. Ако не съответства, оставя позицията на потока непроменена и връща `null`. Използвайте това за незадължителни токени или когато избирате между различни синтактични пътища. + + +Актуализиране на парсващата функция `create()` +---------------------------------------------- + +С това разбиране, нека модифицираме метода `create()` в `DatetimeNode`, така че да парсва незадължителния форматиращ аргумент с помощта на `$tag->parser`. + +```php +<?php + +namespace App\Latte; + +use Latte\Compiler\Nodes\Php\ExpressionNode; +use Latte\Compiler\Nodes\Php\Scalar\StringNode; +use Latte\Compiler\Nodes\StatementNode; +use Latte\Compiler\PrintContext; +use Latte\Compiler\Tag; + +class DatetimeNode extends StatementNode +{ + // Добавяме публично свойство за съхраняване на парсвания възел на израза за формат + public ?ExpressionNode $format = null; + + public static function create(Tag $tag): self + { + $node = $tag->node = new self; + + // Проверяваме дали съществуват някакви токени + if (!$tag->parser->isEnd()) { + // Парсваме аргумента като PHP-подобен израз с помощта на TagParser. + $node->format = $tag->parser->parseExpression(); + } + + return $node; + } + + // ... методите print() и getIterator() ще бъдат актуализирани по-нататък ... +} +``` + +Добавихме публично свойство `$format`. В `create()` сега използваме `$tag->parser->isEnd()`, за да проверим дали *съществуват* аргументи. Ако да, `$tag->parser->parseExpression()` обработва токените за израза. Тъй като `TagParser` трябва да обработи всички входни токени, Latte автоматично ще хвърли грешка, ако потребителят напише нещо неочаквано след израза за формат (напр. `{datetime 'Y-m-d', unexpected}`). + + +Актуализиране на метода `print()` +--------------------------------- + +Сега нека модифицираме метода `print()`, така че да използва парсвания израз за формат, съхранен в `$this->format`. Ако не е предоставен формат (`$this->format` е `null`), трябва да използваме форматиращ низ по подразбиране, например `'Y-m-d H:i:s'`. + +```php + public function print(PrintContext $context): string + { + $formatNode = $this->format ?? new StringNode('Y-m-d H:i:s'); + + // %node отпечатва PHP кодовото представяне на $formatNode. + return $context->format( + 'echo date(%node) %line;', + $formatNode, + $this->position + ); + } +``` + +В променливата `$formatNode` съхраняваме възела на AST, представляващ форматиращия низ за PHP функцията `date()`. Използваме тук оператора за нулево сливане (`??`). Ако потребителят е предоставил аргумент в шаблона (напр. `{datetime 'd.m.Y'}`), тогава свойството `$this->format` съдържа съответния възел (в този случай `StringNode` със стойност `'d.m.Y'`) и този възел се използва. Ако потребителят не е предоставил аргумент (написал е само `{datetime}`), свойството `$this->format` е `null` и вместо това създаваме нов `StringNode` с формат по подразбиране `'Y-m-d H:i:s'`. Това гарантира, че `$formatNode` винаги съдържа валиден възел на AST за формата. + +В маската `'echo date(%node) %line;'` се използва нов placeholder `%node`, който казва на метода `format()` да вземе първия следващ аргумент (който е нашият `$formatNode`), да извика неговия метод `print()` (който ще върне неговото PHP кодово представяне) и да вмъкне резултата на позицията на placeholder-а. + + +Имплементиране на `getIterator()` за подвъзли +--------------------------------------------- + +Нашият `DatetimeNode` сега има дъщерен възел: изразът `$format`. **Трябва** да направим този дъщерен възел достъпен за компилационните проходи, като го предоставим в метода `getIterator()`. Не забравяйте да предоставите *референция* (`&`), за да позволите на проходите потенциално да заменят възела. + +```php + public function &getIterator(): \Generator + { + if ($this->format) { + yield $this->format; + } + } +``` + +Защо е толкова важно? Представете си Sandbox проход, който трябва да провери дали аргументът `$format` не съдържа забранено извикване на функция (напр. `{datetime dangerousFunction()}`). Ако `getIterator()` не предостави `$this->format`, Sandbox проходът никога няма да види извикването на `dangerousFunction()` вътре в аргумента на нашия таг, което би създало потенциална дупка в сигурността. Като му го предоставяме, позволяваме на Sandbox (и други проходи) да проверяват и потенциално да модифицират възела на израза `$format`. + + +Използване на подобрения таг +---------------------------- + +Тагът сега правилно обработва незадължителния аргумент: + +```latte +Формат по подразбиране: {datetime} +Персонализиран формат: {datetime 'd.m.Y'} +Използване на променлива: {datetime $userDateFormatPreference} + +{* Това би причинило грешка след парсването на 'd.m.Y', тъй като ", foo" е неочаквано *} +{* {datetime 'd.m.Y', foo} *} +``` + +След това ще разгледаме създаването на сдвоени тагове, които обработват съдържанието между тях. + + +Обработка на сдвоени тагове +=========================== + +Досега нашият таг `{datetime}` беше *самозатварящ се* (концептуално). Той няма съдържание между началния и крайния таг. Много полезни тагове обаче работят с блок от съдържание на шаблона. Те се наричат **сдвоени тагове**. Примерите включват `{if}...{/if}`, `{block}...{/block}` или персонализиран таг, който сега ще създадем: `{debug}...{/debug}`. + +Този таг ще ни позволи да включим в нашите шаблони дебъг информация, която трябва да бъде видима само по време на разработка. + +**Цел:** Да се създаде сдвоен таг `{debug}`, чието съдържание се рендира само когато е активен специфичен флаг "режим на разработка". + + +Представяне на Providers +------------------------ + +Понякога вашите тагове се нуждаят от достъп до данни или услуги, които не се предават директно като параметри на шаблона. Например, определяне дали приложението е в режим на разработка, достъп до обект на потребител или получаване на конфигурационни стойности. Latte предоставя механизъм, наречен **Providers** за тази цел. + +Providers се регистрират във вашето [разширение |extending-latte#Latte Extension] с помощта на метода `getProviders()`. Този метод връща асоциативен масив, където ключовете са имената, под които providers ще бъдат достъпни в кода на шаблона по време на изпълнение, а стойностите са действителните данни или обекти. + +Вътре в PHP кода, генериран от метода `print()` на вашия таг, можете да получите достъп до тези providers чрез специалното свойство на обекта `$this->global`. Тъй като това свойство се споделя между всички разширения, добра практика е **да добавяте префикс към имената на вашите providers**, за да предотвратите потенциални конфликти на имена с ключови providers на Latte или providers от други разширения на трети страни. Често срещана конвенция е да се използва кратък, уникален префикс, свързан с вашия производител или име на разширение. За нашия пример ще използваме префикс `app` и флагът за режим на разработка ще бъде достъпен като `$this->global->appDevMode`. + + +Ключовата дума `yield` за парсване на съдържание +------------------------------------------------ + +Как казваме на парсера на Latte да обработи съдържанието *между* `{debug}` и `{/debug}`? Тук влиза в игра ключовата дума `yield`. + +Когато `yield` се използва във функцията `create()`, функцията се превръща в [PHP генератор |https://www.php.net/manual/en/language.generators.overview.php]. Нейното изпълнение се спира и контролът се връща към основния `TemplateParser`. След това `TemplateParser` продължава да парсва съдържанието на шаблона, *докато* не срещне съответния затварящ таг (`{/debug}` в нашия случай). + +След като бъде намерен затварящият таг, `TemplateParser` възобновява изпълнението на нашата функция `create()` точно след инструкцията `yield`. Стойността, *върната* от инструкцията `yield`, е масив, съдържащ два елемента: + +1. `AreaNode`, представляващ парсваното съдържание между началния и крайния таг. +2. Обект `Tag`, представляващ затварящия таг (напр. `{/debug}`). + +Нека създадем клас `DebugNode` и неговия метод `create`, използващ `yield`. + +```php +<?php + +namespace App\Latte; + +use Latte\Compiler\Nodes\AreaNode; +use Latte\Compiler\Nodes\StatementNode; +use Latte\Compiler\PrintContext; +use Latte\Compiler\Tag; + +class DebugNode extends StatementNode +{ + // Публично свойство за съхраняване на парсваното вътрешно съдържание + public AreaNode $content; + + /** + * Парсваща функция за сдвоен таг {debug} ... {/debug}. + */ + public static function create(Tag $tag): \Generator // забележете типа на връщане + { + $node = $tag->node = new self; + + // Спиране на парсването, получаване на вътрешното съдържание и крайния таг, когато е намерен {/debug} + [$node->content, $endTag] = yield; + + return $node; + } + + // ... print() и getIterator() ще бъдат имплементирани по-нататък ... +} +``` + +Забележка: `$endTag` е `null`, ако тагът се използва като n:атрибут, т.е. `<div n:debug>...</div>`. + + +Имплементиране на `print()` за условно рендиране +------------------------------------------------ + +Методът `print()` сега трябва да генерира PHP код, който по време на изпълнение проверява provider-а `appDevMode` и изпълнява кода за вътрешното съдържание само ако флагът е true. + +```php + public function print(PrintContext $context): string + { + // Генерира PHP инструкция 'if', която по време на изпълнение проверява provider-а + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + // Ако е в режим на разработка, извежда вътрешното съдържание + %node + } + + XX, + $this->position, // За %line коментар + $this->content, // Възел, съдържащ AST на вътрешното съдържание + ); + } +``` + +Това е просто. Използваме `PrintContext::format()`, за да създадем стандартна PHP инструкция `if`. Вътре в `if` поставяме placeholder `%node` за `$this->content`. Latte рекурсивно ще извика `$this->content->print($context)`, за да генерира PHP код за вътрешната част на тага, но само ако `$this->global->appDevMode` се оцени като true по време на изпълнение. + + +Имплементиране на `getIterator()` за съдържание +----------------------------------------------- + +Точно както при възела на аргумента в предишния пример, нашият `DebugNode` сега има дъщерен възел: `AreaNode $content`. Трябва да го направим достъпен, като го предоставим в `getIterator()`: + +```php + public function &getIterator(): \Generator + { + // Предоставя референция към възела на съдържанието + yield $this->content; + } +``` + +Това позволява на компилационните проходи да слязат в съдържанието на нашия таг `{debug}`, което е важно, дори ако съдържанието се рендира условно. Например, Sandbox трябва да анализира съдържанието, независимо дали `appDevMode` е true или false. + + +Регистрация и използване +------------------------ + +Регистрирайте тага и provider-а във вашето разширение: + +```php +class MyLatteExtension extends Extension +{ + // Предполагаме, че $isDevelopmentMode се определя някъде (напр. от конфигурацията) + public function __construct( + private bool $isDevelopmentMode, + ) { + } + + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), // Регистрация на новия таг + ]; + } + + public function getProviders(): array + { + return [ + 'appDevMode' => $this->isDevelopmentMode, // Регистрация на provider-а + ]; + } +} + +// При регистрация на разширението: +$isDev = true; // Определете това въз основа на средата на вашето приложение +$latte->addExtension(new App\Latte\MyLatteExtension($isDev)); +``` + +И неговото използване в шаблона: + +```latte +<p>Обикновено съдържание, видимо винаги.</p> + +{debug} + <div class="debug-panel"> + ID на текущия потребител: {$user->id} + Време на заявката: {=time()} + </div> +{/debug} + +<p>Друго обикновено съдържание.</p> +``` + + +Интеграция на n:атрибути +------------------------ + +Latte предлага удобен съкратен запис за много сдвоени тагове: [n:атрибути |syntax#n:атрибути]. Ако имате сдвоен таг като `{tag}...{/tag}` и искате неговият ефект да се приложи директно към един HTML елемент, често можете да го запишете по-икономично като атрибут `n:tag` на този елемент. + +За повечето стандартни сдвоени тагове, които дефинирате (като нашия `{debug}`), Latte автоматично ще позволи съответната версия на `n:` атрибута. По време на регистрацията не е необходимо да правите нищо допълнително: + +```latte +{* Стандартно използване на сдвоен таг *} +{debug}<div>Информация за дебъгване</div>{/debug} + +{* Еквивалентно използване с n:атрибут *} +<div n:debug>Информация за дебъгване</div> +``` + +И двете версии ще рендират `<div>` само ако `$this->global->appDevMode` е true. Префиксите `inner-` и `tag-` също работят според очакванията. + +Понякога логиката на вашия таг може да се нуждае от леко различно поведение в зависимост от това дали се използва като стандартен сдвоен таг или като n:атрибут, или дали се използва префикс като `n:inner-tag` или `n:tag-tag`. Обектът `Latte\Compiler\Tag`, предаден на вашата парсваща функция `create()`, предоставя тази информация: + +- `$tag->isNAttribute(): bool`: Връща `true`, ако тагът се парсва като n:атрибут +- `$tag->prefix: ?string`: Връща префикса, използван с n:атрибута, който може да бъде `null` (не е n:атрибут), `Tag::PrefixNone`, `Tag::PrefixInner` или `Tag::PrefixTag` + +Сега, когато разбираме простите тагове, парсването на аргументи, сдвоените тагове, providers и n:атрибутите, нека се заемем с по-сложен сценарий, включващ тагове, вложени в други тагове, използвайки нашия таг `{debug}` като отправна точка. + + +Междинни тагове +=============== + +Някои сдвоени тагове позволяват или дори изискват други тагове да се появят *вътре* в тях преди крайния затварящ таг. Те се наричат **междинни тагове**. Класически примери включват `{if}...{elseif}...{else}...{/if}` или `{switch}...{case}...{default}...{/switch}`. + +Нека разширим нашия таг `{debug}` с поддръжка на незадължителна клауза `{else}`, която ще бъде рендирана, когато приложението *не е* в режим на разработка. + +**Цел:** Да се модифицира `{debug}`, така че да поддържа незадължителен междинен таг `{else}`. Крайният синтаксис трябва да бъде `{debug} ... {else} ... {/debug}`. + + +Парсване на междинни тагове с помощта на `yield` +------------------------------------------------ + +Вече знаем, че `yield` спира парсващата функция `create()` и връща парсваното съдържание заедно с крайния таг. `yield` обаче предлага повече контрол: можете да му предоставите масив от *имена на междинни тагове*. Когато парсерът срещне някой от тези указани тагове **на същото ниво на влагане** (т.е. като преки деца на родителския таг, не вътре в други блокове или тагове вътре в него), той също спира парсването. + +Когато парсването спре поради междинен таг, то спира парсването на съдържанието, възобновява генератора `create()` и предава обратно частично парсваното съдържание и **междинния таг** сам по себе си (вместо крайния затварящ таг). Нашата функция `create()` след това може да обработи този междинен таг (напр. да парсва неговите аргументи, ако има такива) и отново да използва `yield`, за да парсва *следващата* част от съдържанието до *крайния* затварящ таг или друг очакван междинен таг. + +Нека модифицираме `DebugNode::create()`, така че да очаква `{else}`: + +```php +<?php + +namespace App\Latte; + +use Latte\Compiler\Nodes\AreaNode; +use Latte\Compiler\Nodes\NopNode; +use Latte\Compiler\Nodes\StatementNode; +use Latte\Compiler\PrintContext; +use Latte\Compiler\Tag; + +class DebugNode extends StatementNode +{ + // Съдържание за частта {debug} + public AreaNode $thenContent; + // Незадължително съдържание за частта {else} + public ?AreaNode $elseContent = null; + + public static function create(Tag $tag): \Generator + { + $node = $tag->node = new self; + + // yield и очакваме или {/debug} или {else} + [$node->thenContent, $nextTag] = yield ['else']; + + // Проверяваме дали тагът, при който сме спрели, е бил {else} + if ($nextTag?->name === 'else') { + // Yield отново за парсване на съдържанието между {else} и {/debug} + [$node->elseContent, $endTag] = yield; + } + + return $node; + } + + // ... print() и getIterator() ще бъдат актуализирани по-нататък ... +} +``` + +Сега `yield ['else']` казва на Latte да спре парсването не само за `{/debug}`, но и за `{else}`. Ако `{else}` бъде намерен, `$nextTag` ще съдържа обект `Tag` за `{else}`. След това отново използваме `yield` без аргументи, което означава, че сега очакваме само крайния таг `{/debug}`, и съхраняваме резултата в `$node->elseContent`. Ако `{else}` не е бил намерен, `$nextTag` би бил `Tag` за `{/debug}` (или `null`, ако се използва като n:атрибут) и `$node->elseContent` би останал `null`. + + +Имплементиране на `print()` с `{else}` +-------------------------------------- + +Методът `print()` трябва да отразява новата структура. Той трябва да генерира PHP инструкция `if/else`, базирана на provider-а `appDevMode`. + +```php + public function print(PrintContext $context): string + { + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + %node // Код за клона 'then' (съдържание на {debug}) + } else { + %node // Код за клона 'else' (съдържание на {else}) + } + + XX, + $this->position, // Номер на ред за условието 'if' + $this->thenContent, // Първи placeholder %node + $this->elseContent ?? new NopNode, // Втори placeholder %node + ); + } +``` + +Това е стандартна PHP структура `if/else`. Използваме `%node` два пъти; `format()` замества предоставените възли последователно. Използваме `?? new NopNode`, за да избегнем грешки, ако `$this->elseContent` е `null` – `NopNode` просто не отпечатва нищо. + + +Имплементиране на `getIterator()` за двете съдържания +----------------------------------------------------- + +Сега имаме потенциално два дъщерни възела на съдържание (`$thenContent` и `$elseContent`). Трябва да предоставим и двата, ако съществуват: + +```php + public function &getIterator(): \Generator + { + yield $this->thenContent; + if ($this->elseContent) { + yield $this->elseContent; + } + } +``` + + +Използване на подобрения таг +---------------------------- + +Тагът сега може да бъде използван с незадължителна клауза `{else}`: + +```latte +{debug} + <p>Показване на дебъг информация, защото devMode е ВКЛЮЧЕНО.</p> +{else} + <p>Дебъг информацията е скрита, защото devMode е ИЗКЛЮЧЕНО.</p> +{/debug} +``` + + +Обработка на състояние и влагане +================================ + +Нашите предишни примери (`{datetime}`, `{debug}`) бяха относително без състояние в рамките на своите методи `print()`. Те или директно извеждаха съдържание, или извършваха проста условна проверка, базирана на глобален provider. Много тагове обаче трябва да управляват някаква форма на **състояние** по време на рендиране или включват оценка на потребителски изрази, които трябва да бъдат изпълнени само веднъж поради производителност или коректност. Освен това трябва да обмислим какво се случва, когато нашите персонализирани тагове са **вложени**. + +Нека илюстрираме тези концепции, като създадем таг `{repeat $count}...{/repeat}`. Този таг ще повтори своето вътрешно съдържание `$count` пъти. + +**Цел:** Имплементиране на `{repeat $count}`, който повтаря своето съдържание указан брой пъти. + + +Нуждата от временни и уникални променливи +----------------------------------------- + +Представете си, че потребителят напише: + +```latte +{repeat rand(1, 5)} Съдържание {/repeat} +``` + +Ако наивно генерираме PHP `for` цикъл по този начин в нашия метод `print()`: + +```php +// Опростен, НЕПРАВИЛЕН генериран код +for ($i = 0; $i < rand(1, 5); $i++) { + // извеждане на съдържание +} +``` +Това би било грешно! Изразът `rand(1, 5)` би бил **преизчислен при всяка итерация на цикъла**, което би довело до непредсказуем брой повторения. Трябва да оценим израза `$count` *веднъж* преди началото на цикъла и да съхраним резултата му. + +Ще генерираме PHP код, който първо оценява израза за броя и го съхранява във **временна променлива по време на изпълнение**. За да предотвратим конфликти с променливи, дефинирани от потребителя на шаблона, *и* вътрешни променливи на Latte (като `$ʟ_...`), ще използваме конвенцията за префикс **`$__` (двойно долно тире)** за нашите временни променливи. + +Генерираният код тогава би изглеждал така: + +```php +$__count = rand(1, 5); +for ($__i = 0; $__i < $__count; $__i++) { + // извеждане на съдържание +} +``` + +Сега да разгледаме влагането: + +```latte +{repeat $countA} {* Външен цикъл *} + {repeat $countB} {* Вътрешен цикъл *} + ... + {/repeat} +{/repeat} +``` + +Ако както външният, така и вътрешният таг `{repeat}` генерират код, използващ *едни и същи* имена на временни променливи (напр. `$__count` и `$__i`), вътрешният цикъл би презаписал променливите на външния цикъл, което би нарушило логиката. + +Трябва да гарантираме, че временните променливи, генерирани за всяка инстанция на тага `{repeat}`, са **уникални**. Постигаме това с помощта на `PrintContext::generateId()`. Този метод връща уникално цяло число по време на фазата на компилация. Можем да добавим това ID към имената на нашите временни променливи. + +Така че вместо `$__count`, ще генерираме `$__count_1` за първия таг repeat, `$__count_2` за втория и т.н. Подобно за брояча на цикъла ще използваме `$__i_1`, `$__i_2` и т.н. + + +Имплементиране на `RepeatNode` +------------------------------ + +Нека създадем класа на възела. + +```php +<?php + +namespace App\Latte; + +use Latte\CompileException; +use Latte\Compiler\Nodes\AreaNode; +use Latte\Compiler\Nodes\Php\ExpressionNode; +use Latte\Compiler\Nodes\StatementNode; +use Latte\Compiler\PrintContext; +use Latte\Compiler\Tag; + +class RepeatNode extends StatementNode +{ + public ExpressionNode $count; + public AreaNode $content; + + /** + * Парсваща функция за {repeat $count} ... {/repeat} + */ + public static function create(Tag $tag): \Generator + { + $tag->expectArguments(); // уверява се, че $count е предоставен + $node = $tag->node = new self; + // Парсва израза за броя + $node->count = $tag->parser->parseExpression(); + // Получаване на вътрешното съдържание + [$node->content] = yield; + return $node; + } + + /** + * Генерира PHP 'for' цикъл с уникални имена на променливи. + */ + public function print(PrintContext $context): string + { + // Генериране на уникални имена на променливи + $id = $context->generateId(); + $countVar = '$__count_' . $id; // напр. $__count_1, $__count_2, и т.н. + $iteratorVar = '$__i_' . $id; // напр. $__i_1, $__i_2, и т.н. + + return $context->format( + <<<'XX' + // Оценка на израза за броя *веднъж* и съхраняване + %raw = (int) (%node); + // Цикъл с използване на съхранения брой и уникална итерационна променлива + for (%raw = 0; %2.raw < %0.raw; %2.raw++) %line { + %node // Рендиране на вътрешното съдържание + } + + XX, + $countVar, // %0 - Променлива за съхраняване на броя + $this->count, // %1 - Възел на израза за броя + $iteratorVar, // %2 - Име на итерационната променлива на цикъла + $this->position, // %3 - Коментар с номер на ред за самия цикъл + $this->content // %4 - Възел на вътрешното съдържание + ); + } + + /** + * Предоставя дъщерните възли (израз за броя и съдържание). + */ + public function &getIterator(): \Generator + { + yield $this->count; + yield $this->content; + } +} +``` + +Методът `create()` парсва изисквания израз `$count` с помощта на `parseExpression()`. Първо се извиква `$tag->expectArguments()`. Това гарантира, че потребителят е предоставил *нещо* след `{repeat}`. Докато `$tag->parser->parseExpression()` би се провалило, ако нищо не е предоставено, съобщението за грешка може да бъде за неочакван синтаксис. Използването на `expectArguments()` предоставя много по-ясна грешка, конкретно посочваща, че липсват аргументи за тага `{repeat}`. + +Методът `print()` генерира PHP код, отговорен за изпълнението на логиката на повторение по време на изпълнение. Започва с генериране на уникални имена за временните PHP променливи, които ще са му нужни. + +Методът `$context->format()` се извиква с нов placeholder `%raw`, който вмъква *суровия низ*, предоставен като съответен аргумент. Тук той вмъква уникалното име на променлива, съхранено в `$countVar` (напр. `$__count_1`). А какво да кажем за `%0.raw` и `%2.raw`? Това демонстрира **позиционни placeholders**. Вместо просто `%raw`, който взема *следващия* наличен суров аргумент, `%2.raw` изрично взема аргумента на индекс 2 (който е `$iteratorVar`) и вмъква неговата сурова низова стойност. Това ни позволява да използваме повторно низа `$iteratorVar`, без да го предаваме многократно в списъка с аргументи за `format()`. + +Това внимателно конструирано извикване на `format()` генерира ефективен и безопасен PHP цикъл, който правилно обработва израза за броя и избягва конфликти на имена на променливи, дори когато таговете `{repeat}` са вложени. + + +Регистрация и използване +------------------------ + +Регистрирайте тага във вашето разширение: + +```php +use App\Latte\RepeatNode; + +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), // Регистрация на тага repeat + ]; + } +} +``` + +Използвайте го в шаблона, включително влагане: + +```latte +{var $rows = rand(5, 7)} +{var $cols = rand(3, 5)} + +{repeat $rows} + <tr> + {repeat $cols} + <td>Вътрешен цикъл</td> + {/repeat} + </tr> +{/repeat} +``` + +Този пример демонстрира как да се обработва състояние (броячи на цикли) и потенциални проблеми с влагането с помощта на временни променливи с префикс `$__` и уникални с ID от `PrintContext::generateId()`. + + +Чисти n:атрибути +---------------- + +Докато много `n:атрибути` като `n:if` или `n:foreach` служат като удобни съкращения за техните двойници в сдвоени тагове (`{if}...{/if}`, `{foreach}...{/foreach}`), Latte също позволява дефинирането на тагове, които *съществуват само* под формата на n:атрибут. Те често се използват за модифициране на атрибути или поведение на HTML елемента, към който са прикрепени. + +Стандартните примери, вградени в Latte, включват [`n:class` |tags#n:class], който помага за динамичното изграждане на атрибута `class`, и [`n:attr` |tags#n:attr], който може да зададе множество произволни атрибути. + +Нека създадем наш собствен чист n:атрибут: `n:confirm`, който добавя JavaScript диалогов прозорец за потвърждение преди извършване на действие (като следване на връзка или изпращане на формуляр). + +**Цел:** Имплементиране на `n:confirm="'Сигурни ли сте?'"`, който добавя обработчик `onclick` за предотвратяване на действието по подразбиране, ако потребителят отмени диалоговия прозорец за потвърждение. + + +Имплементиране на `ConfirmNode` +------------------------------- + +Нуждаем се от клас Node и парсваща функция. + +```php +<?php + +namespace App\Latte; + +use Latte\Compiler\Nodes\StatementNode; +use Latte\Compiler\PrintContext; +use Latte\Compiler\Tag; +use Latte\Compiler\Nodes\Php\ExpressionNode; +use Latte\Compiler\Nodes\Php\Scalar\StringNode; + +class ConfirmNode extends StatementNode +{ + public ExpressionNode $message; + + public static function create(Tag $tag): self + { + $tag->expectArguments(); + $node = $tag->node = new self; + $node->message = $tag->parser->parseExpression(); + return $node; + } + + /** + * Генерира код на атрибута 'onclick' с правилно екраниране. + */ + public function print(PrintContext $context): string + { + // Гарантира правилно екраниране за контекстите на JavaScript и HTML атрибут. + return $context->format( + <<<'XX' + echo ' onclick="', LR\Filters::escapeHtmlAttr('return confirm(' . LR\Filters::escapeJs(%node) . ')'), '"' %line; + XX, + $this->message, + $this->position, + ); + } + + public function &getIterator(): \Generator + { + yield $this->message; + } +} +``` + +Методът `print()` генерира PHP код, който в крайна сметка по време на рендиране на шаблона извежда HTML атрибута `onclick="..."`. Обработката на вложени контексти (JavaScript вътре в HTML атрибут) изисква внимателно екраниране. Филтърът `LR\Filters::escapeJs(%node)` се извиква по време на изпълнение и екранира съобщението правилно за използване вътре в JavaScript (изходът би бил като `"Sure?"`). След това филтърът `LR\Filters::escapeHtmlAttr(...)` екранира знаците, които са специални в HTML атрибутите, така че това би променило изхода на `return confirm("Sure?")`. Това двустепенно екраниране по време на изпълнение гарантира, че съобщението е безопасно за JavaScript и резултатният JavaScript код е безопасен за вмъкване в HTML атрибута `onclick`. + + +Регистрация и използване +------------------------ + +Регистрирайте n:атрибута във вашето разширение. Не забравяйте префикса `n:` в ключа: + +```php +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), + 'n:confirm' => ConfirmNode::create(...), // Регистрация на n:confirm + ]; + } +} +``` + +Сега можете да използвате `n:confirm` върху връзки, бутони или елементи на формуляр: + +```latte +<a href="delete.php?id=123" n:confirm='"Наистина ли искате да изтриете елемент {$id}?"'>Изтриване</a> +``` + +Генериран HTML: + +```html +<a href="delete.php?id=123" onclick="return confirm("Наистина ли искате да изтриете елемент 123?")">Изтриване</a> +``` + +Когато потребителят кликне върху връзката, браузърът изпълнява кода `onclick`, показва диалоговия прозорец за потвърждение и преминава към `delete.php` само ако потребителят кликне върху "OK". + +Този пример демонстрира как може да се създаде чист n:атрибут за модифициране на поведението или атрибутите на своя хост HTML елемент чрез генериране на подходящ PHP код в неговия метод `print()`. Не забравяйте за двойното екраниране, което често се изисква: веднъж за целевия контекст (JavaScript в този случай) и отново за контекста на HTML атрибута. + + +Напреднали теми +=============== + +Докато предишните секции покриват основните концепции, тук са няколко по-напреднали теми, на които може да попаднете при създаването на персонализирани Latte тагове. + + +Режими на изход на тагове +------------------------- + +Обектът `Tag`, предаден на вашата функция `create()`, има свойство `outputMode`. Това свойство влияе върху това как Latte третира околните празни пространства и индентация, особено когато тагът се използва на собствен ред. Можете да модифицирате това свойство във вашата функция `create()`. + +- `Tag::OutputKeepIndentation` (По подразбиране за повечето тагове като `{=...}`): Latte се опитва да запази индентацията преди тага. Новите редове *след* тага обикновено се запазват. Това е подходящо за тагове, които извеждат съдържание в реда. +- `Tag::OutputRemoveIndentation` (По подразбиране за блокови тагове като `{if}`, `{foreach}`): Latte премахва водещата индентация и потенциално един следващ нов ред. Това помага да се поддържа генерираният PHP код по-чист и предотвратява допълнителни празни редове в HTML изхода, причинени от самия таг. Използвайте това за тагове, които представляват контролни структури или блокове, които сами по себе си не трябва да добавят празни пространства. +- `Tag::OutputNone` (Използва се от тагове като `{var}`, `{default}`): Подобно на `RemoveIndentation`, но сигнализира по-силно, че самият таг не произвежда директен изход, потенциално влияейки върху обработката на празни пространства около него още по-агресивно. Подходящо за декларативни или задаващи тагове. + +Изберете режима, който най-добре отговаря на целта на вашия таг. За повечето структурни или контролни тагове обикновено е подходящ `OutputRemoveIndentation`. + + +Достъп до родителски/най-близки тагове +-------------------------------------- + +Понякога поведението на тага трябва да зависи от контекста, в който се използва, конкретно в кой родителски таг(ове) се намира. Обектът `Tag`, предаден на вашата функция `create()`, предоставя метода `closestTag(array $classes, ?callable $condition = null): ?Tag` точно за тази цел. + +Този метод търси нагоре в йерархията на текущо отворените тагове (включително HTML елементи, представени вътрешно по време на парсване) и връща обекта `Tag` на най-близкия предшественик, който отговаря на специфични критерии. Ако не бъде намерен съответстващ предшественик, връща `null`. + +Масивът `$classes` указва какъв вид предшествени тагове търсите. Проверява дали асоциираният възел на предшествения таг (`$ancestorTag->node`) е инстанция на този клас. + +```php +function create(Tag $tag) +{ + // Търсене на най-близкия предшествен таг, чийто възел е инстанция на ForeachNode + $foreachTag = $tag->closestTag([ForeachNode::class]); + if ($foreachTag) { + // Можем да получим достъп до самата инстанция на ForeachNode: + $foreachNode = $foreachTag->node; + } +} +``` + +Забележете `$foreachTag->node`: Това работи само защото е конвенция в разработката на Latte тагове незабавно да се присвои създаденият възел на `$tag->node` в рамките на метода `create()`, както винаги сме правили. + +Понякога само сравнението на типа на възела не е достатъчно. Може да се наложи да проверите специфично свойство на потенциалния предшествен таг или неговия възел. Незадължителният втори аргумент за `closestTag()` е callable, който приема потенциалния предшествен обект `Tag` и трябва да връща дали е валидно съвпадение. + +```php +function create(Tag $tag) +{ + $dynamicBlockTag = $tag->closestTag( + [BlockNode::class], + // Условие: блокът трябва да е динамичен + fn(Tag $blockTag) => $blockTag->node->block->isDynamic(), + ); +} +``` + +Използването на `closestTag()` позволява създаването на тагове, които са контекстно осъзнати и налагат правилно използване в рамките на структурата на вашия шаблон, което води до по-здрави и разбираеми шаблони. + + +Placeholders на `PrintContext::format()` +---------------------------------------- + +Често сме използвали `PrintContext::format()`, за да генерираме PHP код в методите `print()` на нашите възли. Той приема низ-маска и следващи аргументи, които заместват placeholders в маската. Ето резюме на наличните placeholders: + +- **`%node`**: Аргументът трябва да бъде инстанция на `Node`. Извиква метода `print()` на възела и вмъква резултантния низ от PHP код. +- **`%dump`**: Аргументът е всяка PHP стойност. Експортира стойността в валиден PHP код. Подходящо за скалари, масиви, null. + - `$context->format('echo %dump;', 'Hello')` -> `echo 'Hello';` + - `$context->format('$arr = %dump;', [1, 2])` -> `$arr = [1, 2];` +- **`%raw`**: Вмъква аргумента директно в изходния PHP код без никакво екраниране или модификация. **Използвайте с повишено внимание**, предимно за вмъкване на предварително генерирани фрагменти от PHP код или имена на променливи. + - `$context->format('%raw = 1;', '$variableName')` -> `$variableName = 1;` +- **`%args`**: Аргументът трябва да бъде `Expression\ArrayNode`. Извежда елементите на масива, форматирани като аргументи за извикване на функция или метод (разделени със запетаи, обработва именувани аргументи, ако присъстват). + - `$argsNode = new ArrayNode([...]);` + - `$context->format('myFunc(%args);', $argsNode)` -> `myFunc(1, name: 'Joe');` +- **`%line`**: Аргументът трябва да бъде обект `Position` (обикновено `$this->position`). Вмъква PHP коментар `/* line X */`, указващ номера на реда на източника. + - `$context->format('echo "Hi" %line;', $this->position)` -> `echo "Hi" /* line 42 */;` +- **`%escape(...)`**: Генерира PHP код, който *по време на изпълнение* екранира вътрешния израз, използвайки текущите контекстно осъзнати правила за екраниране. + - `$context->format('echo %escape(%node);', $variableNode)` +- **`%modify(...)`**: Аргументът трябва да бъде `ModifierNode`. Генерира PHP код, който прилага филтрите, указани в `ModifierNode`, към вътрешното съдържание, включително контекстно осъзнато екраниране, ако не е забранено с `|noescape`. + - `$context->format('%modify(%node);', $modifierNode, $variableNode)` +- **`%modifyContent(...)`**: Подобно на `%modify`, но предназначено за модифициране на блокове от уловено съдържание (често HTML). + +Можете изрично да се позовавате на аргументи по техния индекс (от нула): `%0.node`, `%1.dump`, `%2.raw` и т.н. Това позволява повторното използване на аргумент няколко пъти в маската, без да го предавате многократно на `format()`. Вижте примера с тага `{repeat}`, където бяха използвани `%0.raw` и `%2.raw`. + + +Пример за комплексно парсване на аргументи +------------------------------------------ + +Докато `parseExpression()`, `parseArguments()` и т.н., покриват много случаи, понякога се нуждаете от по-сложна логика за парсване, използваща по-ниско ниво `TokenStream`, достъпно чрез `$tag->parser->stream`. + +**Цел:** Да се създаде таг `{embedYoutube $videoID, width: 640, height: 480}`. Искаме да парсваме изискваното ID на видеото (низ или променлива), последвано от незадължителни двойки ключ-стойност за размерите. + +```php +<?php +namespace App\Latte; + +class YoutubeNode extends StatementNode +{ + public ExpressionNode $videoId; + public ?ExpressionNode $width = null; + public ?ExpressionNode $height = null; + + public static function create(Tag $tag): self + { + $tag->expectArguments(); + $node = $tag->node = new self; + // Парсване на изискваното ID на видеото + $node->videoId = $tag->parser->parseExpression(); + + // Парсване на незадължителни двойки ключ-стойност + $stream = $tag->parser->stream; // Получаване на потока от токени + while ($stream->tryConsume(',')) { // Изисква разделяне със запетая + // Очакване на идентификатор 'width' или 'height' + $keyToken = $stream->consume(Token::Php_Identifier); + $key = strtolower($keyToken->text); + + $stream->consume(':'); // Очакване на разделител двоеточие + + $value = $tag->parser->parseExpression(); // Парсване на израза за стойност + + if ($key === 'width') { + $node->width = $value; + } elseif ($key === 'height') { + $node->height = $value; + } else { + throw new CompileException("Неизвестен аргумент '$key'. Очаквано 'width' или 'height'.", $keyToken->position); + } + } + + return $node; + } +} +``` + +Това ниво на контрол ви позволява да дефинирате много специфични и комплексни синтаксиси за вашите персонализирани тагове чрез директно взаимодействие с потока от токени. + + +Използване на `AuxiliaryNode` +----------------------------- + +Latte предоставя общи "спомагателни" възли за специални ситуации по време на генериране на код или в рамките на компилационни проходи. Това са `AuxiliaryNode` и `Php\Expression\AuxiliaryNode`. + +Считайте `AuxiliaryNode` за гъвкав контейнерен възел, който делегира своите основни функционалности - генериране на код и излагане на дъщерни възли - на аргументите, предоставени в неговия конструктор: + +- Делегиране на `print()`: Първият аргумент на конструктора е PHP **closure**. Когато Latte извиква метода `print()` на `AuxiliaryNode`, той изпълнява тази предоставена closure. Closure приема `PrintContext` и всички възли, предадени във втория аргумент на конструктора, което ви позволява да дефинирате напълно персонализирана логика за генериране на PHP код по време на изпълнение. +- Делегиране на `getIterator()`: Вторият аргумент на конструктора е **масив от обекти `Node`**. Когато Latte трябва да обходи децата на `AuxiliaryNode` (напр. по време на компилационни проходи), неговият метод `getIterator()` просто предоставя възлите, изброени в този масив. + +Пример: + +```php +$node = new AuxiliaryNode( + // 1. Тази closure става тялото на print() + fn(PrintContext $context, $arg1, $arg2) => $context->format('...%node...%node...', $arg1, $arg2), + + // 2. Тези възли се предоставят от метода getIterator() и се предават на closure по-горе + [$argumentNode1, $argumentNode2] +); +``` + +Latte предоставя два различни типа въз основа на това къде трябва да вмъкнете генерирания код: + +- `Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode`: Използвайте това, когато трябва да генерирате част от PHP код, която представлява **израз** +- `Latte\Compiler\Nodes\AuxiliaryNode`: Използвайте това за по-общи цели, когато трябва да вмъкнете блок от PHP код, представляващ една или повече **инструкции** + +Важна причина да използвате `AuxiliaryNode` вместо стандартни възли (като `StaticMethodCallNode`) в рамките на вашия метод `print()` или компилационен проход е **контролът на видимостта за последващи компилационни проходи**, особено тези, свързани със сигурността, като Sandbox. + +Разгледайте сценарий: Вашият компилационен проход трябва да обвие предоставен от потребителя израз (`$userExpr`) с извикване на специфична, доверена помощна функция `myInternalSanitize($userExpr)`. Ако създадете стандартен възел `new FunctionCallNode('myInternalSanitize', [$userExpr])`, той ще бъде напълно видим за обхождането на AST. Ако Sandbox проходът се изпълни по-късно и `myInternalSanitize` *не е* в неговия списък с разрешени, Sandbox може да *блокира* или модифицира това извикване, потенциално нарушавайки вътрешната логика на вашия таг, дори ако *вие*, авторът на тага, знаете, че това специфично извикване е безопасно и необходимо. Можете следователно да генерирате извикването директно в рамките на closure на `AuxiliaryNode`. + +```php +use Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode; + +// ... вътре в print() или компилационен проход ... +$wrappedNode = new AuxiliaryNode( + fn(PrintContext $context, $userExpr) => $context->format( + 'myInternalSanitize(%node)', // Директно генериране на PHP код + $userExpr, + ), + // ВАЖНО: Все още предайте оригиналния възел на потребителския израз тук! + [$userExpr], +); +``` + +В този случай Sandbox проходът вижда `AuxiliaryNode`, но **не анализира PHP кода, генериран от неговата closure**. Той не може директно да блокира извикването на `myInternalSanitize`, генерирано *вътре* в closure. + +Докато самият генериран PHP код е скрит от проходите, *входовете* към този код (възлите, представляващи потребителски данни или изрази) **трябва все още да бъдат обходими**. Затова вторият аргумент на конструктора на `AuxiliaryNode` е от съществено значение. **Трябва** да предадете масив, съдържащ всички оригинални възли (като `$userExpr` в примера по-горе), които вашата closure използва. `getIterator()` на `AuxiliaryNode` **ще предостави тези възли**, позволявайки на компилационни проходи като Sandbox да ги анализират за потенциални проблеми. + + +Добри практики +============== + +- **Ясна цел:** Уверете се, че вашият таг има ясна и необходима цел. Не създавайте тагове за задачи, които могат лесно да бъдат решени с помощта на [филтри |custom-filters] или [функции |custom-functions]. +- **Правилно имплементирайте `getIterator()`:** Винаги имплементирайте `getIterator()` и предоставяйте *референции* (`&`) към *всички* дъщерни възли (аргументи, съдържание), които са били парсвани от шаблона. Това е необходимо за компилационните проходи, сигурността (Sandbox) и потенциални бъдещи оптимизации. +- **Публични свойства за възли:** Направете свойствата, съдържащи дъщерни възли, публични, за да могат компилационните проходи да ги модифицират при необходимост. +- **Използвайте `PrintContext::format()`:** Използвайте метода `format()` за генериране на PHP код. Той обработва кавички, правилно екранира placeholders и добавя коментари с номер на ред автоматично. +- **Временни променливи (`$__`):** При генериране на PHP код по време на изпълнение, който се нуждае от временни променливи (напр. за съхраняване на междинни суми, броячи на цикли), използвайте конвенцията за префикс `$__`, за да избегнете конфликти с потребителски променливи и вътрешни променливи на Latte `$ʟ_`. +- **Влагане и уникални ID:** Ако вашият таг може да бъде вложен или се нуждае от състояние, специфично за инстанцията по време на изпълнение, използвайте `$context->generateId()` в рамките на вашия метод `print()`, за да създадете уникални суфикси за вашите временни променливи `$__`. +- **Providers за външни данни:** Използвайте providers (регистрирани чрез `Extension::getProviders()`) за достъп до данни или услуги по време на изпълнение ($this->global->...) вместо твърдо кодиране на стойности или разчитане на глобално състояние. Използвайте префикси на производителя за имената на providers. +- **Обмислете n:атрибути:** Ако вашият сдвоен таг логически оперира върху един HTML елемент, Latte вероятно предоставя автоматична поддръжка на `n:атрибут`. Имайте това предвид за удобство на потребителя. Ако създавате таг, модифициращ атрибут, обмислете дали чист `n:атрибут` е най-подходящата форма. +- **Тестване:** Пишете тестове за вашите тагове, покриващи както парсването на различни синтактични входове, така и коректността на изхода на генерирания **PHP код**. + +Като следвате тези насоки, можете да създавате мощни, здрави и поддържаеми персонализирани тагове, които се интегрират безпроблемно с шаблонния engine на Latte. + +.[note] +Изучаването на класовете на възлите, които са част от Latte, е най-добрият начин да научите всички подробности за процеса на парсване. diff --git a/latte/bg/develop.texy b/latte/bg/develop.texy index 1797132a55..038fb280d0 100644 --- a/latte/bg/develop.texy +++ b/latte/bg/develop.texy @@ -1,70 +1,66 @@ -Практики за разработчици -************************ +Практики за разработка +********************** -Инсталиране .[#toc-installation] -================================ +Инсталация +========== -Най-добрият начин за инсталиране на Latte е да се използва Composer: +Най-добрият начин да инсталирате Latte е с помощта на Composer: ```shell composer require latte/latte ``` -Поддържани версии на PHP (важи за последните версии на Latte): +Поддържани версии на PHP (важи за последните минорни версии на Latte): -| версия | съвместима с PHP +| версия | съвместима с PHP |-----------------|------------------- -| Latte 3.0 | PHP 8.0 - 8.2 -| Latte 2.11 | PHP 7.1 - 8.2 -| Latte 2.8 - 2.10 | PHP 7.1 - 8.1 +| Latte 3.0 | PHP 8.0 – 8.2 -Как да визуализирате шаблон .[#toc-how-to-render-a-template] -============================================================ +Как да рендираме шаблон +======================= -Как да визуализираме шаблон? Просто използвайте този прост код: +Как да рендираме шаблон? Достатъчен е този прост код: ```php $latte = new Latte\Engine; -// директория за кеш +// директория за кеша $latte->setTempDirectory('/path/to/tempdir'); -$params = [ /* template variables */ ]; +$params = [ /* променливи на шаблона */ ]; // или $params = new TemplateParameters(/* ... */); -// визуализиране на изхода +// рендиране към изхода $latte->render('template.latte', $params); -// или извеждане на променлива +// рендиране в променлива $output = $latte->renderToString('template.latte', $params); ``` -Параметрите могат да бъдат масиви или още по-добре [обекти |#Parameters as a class], което ще осигури проверка на типа и предлагане в редактора. +Параметрите могат да бъдат масив или още по-добре [обект |#Параметри като клас], който ще осигури проверка на типовете и подсказване в редакторите. .[note] -Можете да намерите и примери за използване в хранилището [Latte examples |https://github.com/nette-examples/latte]. +Примери за употреба ще намерите също в хранилището [Latte examples |https://github.com/nette-examples/latte]. -Производителност и кеширане .[#toc-performance-and-caching] -=========================================================== +Производителност и кеш +====================== -Шаблоните на Latte са изключително бързи, тъй като Latte ги компилира директно в PHP код и ги кешира на диска. По този начин те нямат допълнителни разходи в сравнение с шаблоните, написани на чист PHP. +Шаблоните в Latte са изключително бързи, Latte ги компилира директно в PHP код и ги съхранява в кеш на диска. По този начин те нямат никакви допълнителни разходи в сравнение с шаблони, написани на чист PHP. -Кешът се възстановява автоматично при всяка промяна на изходния файл. Така че можете удобно да редактирате своите Latte шаблони по време на разработката и да виждате промените веднага в браузъра. Можете да деактивирате тази функция в производствена среда и да спестите малко производителност: +Кешът се регенерира автоматично всеки път, когато промените изходния файл. По време на разработката можете удобно да редактирате шаблоните в Latte и веднага да виждате промените в браузъра. Тази функция може да бъде изключена в продукционна среда, за да се спести малко производителност: ```php $latte->setAutoRefresh(false); ``` -Когато се внедрява в производствен сървър, първоначалното генериране на кеш, особено за по-големи приложения, може да отнеме известно време. Latte има вградена превенция срещу "препускане на кеша:https://en.wikipedia.org/wiki/Cache_stampede". -Това е ситуация, при която сървърът получава голям брой едновременни заявки и тъй като кешът на Latte все още не съществува, всички те ще го генерират едновременно. Което води до скок на процесора. -Latte е умен и когато има няколко едновременни заявки, само първата нишка генерира кеша, а останалите изчакват и след това го използват. +При разгръщане на продукционен сървър първоначалното генериране на кеша, особено при по-големи приложения, може разбира се да отнеме малко време. Latte има вградена превенция срещу "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Това е ситуация, при която се събират по-голям брой едновременни заявки, които стартират Latte, и тъй като кешът все още не съществува, всички биха започнали да го генерират едновременно. Което би натоварило неимоверно сървъра. Latte е умен и при повече едновременни заявки генерира кеша само първата нишка, останалите чакат и след това го използват. -Параметри като клас .[#toc-parameters-as-a-class] -================================================= +Параметри като клас +=================== -По-добре от подаването на променливи към шаблона като масиви е да се създаде клас. Получавате [безопасен за типа запис |type-system], [хубаво предложение в IDE |recipes#Editors and IDE] и начин да [регистрирате филтри |extending-latte#Filters Using the Class] и [функции |extending-latte#Functions Using the Class]. +По-добре от предаването на променливи към шаблона като масив е да създадете клас. Така ще получите [типово безопасен запис|type-system], [приятно подсказване в IDE |recipes#Редактори и IDE] и път за [регистрация на филтри |custom-filters#Филтри използващи клас с атрибути] и [функции |custom-functions#Функции използващи клас с атрибути]. ```php class MailTemplateParameters @@ -88,10 +84,10 @@ $latte->render('mail.latte', new MailTemplateParameters( ``` -Деактивиране на автоматичното избягване на променлива .[#toc-disabling-auto-escaping-of-variable] -================================================================================================= +Изключване на автоматичното екраниране на променлива +==================================================== -Ако променливата съдържа HTML низ, можете да я маркирате така, че Latte да не я извежда автоматично (и следователно двойно). По този начин се избягва необходимостта от посочване на `|noescape` в шаблона. +Ако променливата съдържа низ в HTML, можете да я маркирате така, че Latte да не я екранира автоматично (и следователно двойно). Така ще избегнете нуждата да посочвате в шаблона `|noescape`. Най-лесният начин е да обвиете низа в обект `Latte\Runtime\Html`: @@ -101,7 +97,7 @@ $params = [ ]; ``` -Latte също така не избягва всички обекти, които имплементират интерфейса `Latte\HtmlStringable`. Така че можете да създадете свой собствен клас, чийто метод `__toString()` ще връща HTML код, който няма да бъде ескапиран автоматично: +Latte освен това не екранира всички обекти, които имплементират интерфейса `Latte\HtmlStringable`. Можете така да създадете собствен клас, чийто метод `__toString()` ще връща HTML код, който няма да се екранира автоматично: ```php class Emphasis extends Latte\HtmlStringable @@ -123,32 +119,85 @@ $params = [ ``` .[warning] -Методът `__toString` трябва да връща коректен HTML код и да осигурява ескапиране на параметрите, в противен случай може да възникне XSS уязвимост! +Методът `__toString` трябва да връща коректен HTML и да осигури екраниране на параметрите, иначе може да възникне уязвимост XSS! -Как да разширим Latte с помощта на филтри, етикети и др. .[#toc-how-to-extend-latte-with-filters-tags-etc] -========================================================================================================== +Как да разширим Latte с филтри, тагове и т.н. +============================================= -Как да добавите потребителски филтър, функция, таг и т.н. в Latte? Научете това в главата [Разширяване на Latte |extending Latte]. -Ако искате да използвате повторно промените си в различни проекти или ако искате да ги споделите с други хора, тогава трябва да [създадете разширение |creating-extension]. +Как да добавим към Latte собствен филтър, функция, таг и т.н.? За това се говори в главата [разширяваме Latte |extending-latte]. Ако искате да използвате повторно своите модификации в различни проекти или да ги споделите с други, трябва да [създадете разширение |extending-latte#Latte Extension]. -Всеки код в шаблона `{php ...}` .{data-version:3.0}{toc: RawPhpExtension} -========================================================================= +Произволен код в шаблона `{php ...}` .{toc: RawPhpExtension} +============================================================ -Във вътрешността на символа могат да се записват само PHP изрази [`{do}` |tags#do] таг, така че не можете например да вмъквате конструкции като `if ... else` или изречения, завършващи с точка и запетая. +Вътре в тага [`{do}` |tags#do] могат да се записват само PHP изрази, не можете например да вмъкнете конструкции като `if ... else` или стейтмънти, завършващи с точка и запетая. -Можете обаче да регистрирате разширението `RawPhpExtension`, което добавя тага `{php ...}`, който може да се използва за вмъкване на всякакъв PHP код на риск на автора на шаблона. +Можете обаче да регистрирате разширението `RawPhpExtension`, което добавя тага `{php ...}`. С помощта на него може да се вмъква всякакъв PHP код. За него не важат никакви правила на sandbox режима, използването му е отговорност на автора на шаблона. ```php $latte->addExtension(new Latte\Essential\RawPhpExtension); ``` -Превод в шаблони .{data-version:3.0}{toc: TranslatorExtension} -============================================================== +Проверка на генерирания код .{data-version:3.0.7} +================================================= + +Latte компилира шаблоните в PHP код. Разбира се, той се грижи генерираният код да бъде синтактично валиден. Въпреки това, при използване на разширения от трети страни или `RawPhpExtension`, Latte не може да гарантира коректността на генерирания файл. Също така в PHP може да се запише код, който макар и синтактично правилен, е забранен (например присвояване на стойност на променливата `$this`) и причинява PHP Compile Error. Ако запишете такава операция в шаблона, тя ще попадне и в генерирания PHP код. Тъй като в PHP съществуват около двеста различни забранени операции, Latte няма амбицията да ги открива. За тях ще предупреди едва самият PHP при рендиране, което обикновено не пречи на нищо. + +Има обаче ситуации, когато искате да знаете още по време на компилацията на шаблона, че той не съдържа никакъв PHP Compile Error. Особено тогава, когато шаблоните могат да бъдат редактирани от потребители, или използвате [Sandbox]. В такъв случай оставете шаблоните да се проверяват още по време на компилацията. Тази функционалност се включва с метода `Engine::enablePhpLint()`. Тъй като за проверката е необходимо да се извика бинарният файл на PHP, предайте пътя до него като параметър: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); + +try { + $latte->compile('home.latte'); +} catch (Latte\CompileException $e) { + // улавя грешки в Latte, както и Compile Error в PHP + echo 'Error: ' . $e->getMessage(); +} +``` + + +Национална среда .{data-version:3.0.18}{toc: Locale} +==================================================== + +Latte позволява да се настрои национална среда, която влияе на форматирането на числа, дати и сортирането. Настройва се с помощта на метода `setLocale()`. Идентификаторът на средата се ръководи от стандарта IETF language tag, който използва разширението на PHP `intl`. Състои се от кода на езика и евентуално кода на страната, напр. `en_US` за английски в Съединените щати, `de_DE` за немски в Германия и т.н. + +```php +$latte = new Latte\Engine; +$latte->setLocale('bg_BG'); +``` + +Настройката на средата влияе на филтрите [localDate |filters#localDate], [sort |filters#sort], [number |filters#number] и [bytes |filters#bytes]. + +.[note] +Изисква PHP разширението `intl`. Настройката в Latte не влияе на глобалната настройка на locale в PHP. + + +Стриктен режим .{data-version:3.0.8} +==================================== + +В стриктен режим на парсиране Latte контролира дали не липсват затварящи HTML тагове и също забранява използването на променливата `$this`. Включвате го така: + +```php +$latte = new Latte\Engine; +$latte->setStrictParsing(); +``` + +Генерирането на шаблони с хедър `declare(strict_types=1)` включвате така: + +```php +$latte = new Latte\Engine; +$latte->setStrictTypes(); +``` + + +Превод в шаблони .{toc: TranslatorExtension} +============================================ -Използвайте разширението `TranslatorExtension`, за да добавите [`{_...}` |tags#_], [`{translate}` |tags#translate] и да филтрирате [`translate` |filters#translate] към шаблона. Те се използват за превод на стойности или части от шаблона на други езици. Параметърът е методът (извикващ се на PHP), който извършва превода: +С помощта на разширението `TranslatorExtension` добавяте към шаблона тагове [`{_...}` |tags#], [`{translate}` |tags#translate] и филтър [`translate` |filters#translate]. Служат за превод на стойности или части от шаблона на други езици. Като параметър посочваме метод (PHP callable), извършващ превода: ```php class MyTranslator @@ -158,7 +207,7 @@ class MyTranslator public function translate(string $original): string { - // създаване на $translated от $original в съответствие с $this->lang + // от $original създаваме $translated според $this->lang return $translated; } } @@ -170,7 +219,7 @@ $extension = new Latte\Essential\TranslatorExtension( $latte->addExtension($extension); ``` -Преводачът се извиква по време на изпълнение, когато шаблонът се визуализира. Въпреки това Latte може да превежда всички статични текстове по време на компилирането на шаблона. Това спестява производителност, тъй като всеки низ се превежда само веднъж и полученият превод се записва в компилирания файл. По този начин се създават няколко компилирани версии на шаблона в кеш директорията, по една за всеки език. За да направите това, трябва само да посочите езика като втори параметър: +Преводачът се извиква по време на изпълнение при рендиране на шаблона. Latte обаче може да превежда всички статични текстове още по време на компилацията на шаблона. Така се пести производителност, тъй като всеки низ се превежда само веднъж и резултатният превод се записва в компилираната форма. В директорията с кеша така възникват повече компилирани версии на шаблона, по една за всеки език. За това е достатъчно само да се посочи езикът като втори параметър: ```php $extension = new Latte\Essential\TranslatorExtension( @@ -179,9 +228,9 @@ $extension = new Latte\Essential\TranslatorExtension( ); ``` -Под статичен текст разбираме например `{_'hello'}` или `{translate}hello{/translate}`. Нестатичният текст, като например `{_$foo}`, ще продължи да се превежда по време на изпълнение. +Статичен текст означава например `{_'hello'}` или `{translate}hello{/translate}`. Нестатичните текстове, като например `{_$foo}`, ще продължат да се превеждат по време на изпълнение. -Шаблонът може също така да предава допълнителни параметри на преводача чрез `{_$original, foo: bar}` или `{translate foo: bar}`, които той получава като масив `$params`: +На преводача могат от шаблона да се предават и допълнителни параметри с помощта на `{_$original, foo: bar}` или `{translate foo: bar}`, които той получава като масив `$params`: ```php public function translate(string $original, ...$params): string @@ -191,66 +240,73 @@ public function translate(string $original, ...$params): string ``` -Отстраняване на грешки и Tracy .[#toc-debugging-and-tracy] -========================================================== +Дебъгване и Tracy +================= -Latte се опитва да направи разработката възможно най-приятна. За целите на отстраняването на грешки има три тага [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak] и [`{trace}` |tags#trace]. +Latte се опитва да ви улесни разработката колкото е възможно повече. Директно за целите на дебъгването съществуват три тага [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak] и [`{trace}` |tags#trace]. -Най-голямо удобство ще получите, ако инсталирате чудесния [инструмент за отстраняване на грешки Tracy |tracy:] и активирате приставката Latte: +Най-голям комфорт ще получите, ако още си инсталирате страхотния [инструмент за отстраняване на грешки Tracy|tracy:] и активирате добавката за Latte: ```php -// дава възможност на Tracy +// включва Tracy Tracy\Debugger::enable(); $latte = new Latte\Engine; -// активира разширението на Трейси +// активира разширението за Tracy $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension); ``` -Сега ще виждате всички грешки в чист червен екран, включително грешки в шаблони с подчертаване на редове и колони[(видео) |https://github.com/nette/tracy/releases/tag/v2.9.0]. -В същото време в долния десен ъгъл в така наречения Tracy Bar се появява таб за Latte, в който можете ясно да видите всички визуализирани шаблони и техните връзки (включително възможност за щракване в шаблона или компилирания код), както и променливите: +Сега всички грешки ще се показват в прегледен червен екран, включително грешките в шаблоните с подчертаване на ред и колона ([видео|https://github.com/nette/tracy/releases/tag/v2.9.0]). Същевременно в долния десен ъгъл в т.нар. Tracy Bar ще се появи раздел за Latte, където са прегледно показани всички рендирани шаблони и техните взаимни връзки (включително възможността да се кликне към шаблона или компилирания код) и също променливите: [* latte-debugging.webp *] -Тъй като Latte компилира шаблоните в четлив PHP код, можете удобно да ги преглеждате във вашата IDE. +Тъй като Latte компилира шаблоните в прегледен PHP код, можете удобно да ги стъпвате във вашето IDE. -Linter: Проверяване на синтаксиса на шаблона .{data-version:2.11}{toc: Linter} -============================================================================== +Linter: валидиране на синтаксиса на шаблони .{toc: Linter} +========================================================== -Инструментът Linter ще ви помогне да преминете през всички шаблони и да проверите за грешки в синтаксиса. Той се стартира от конзолата: +Да преминете през всички шаблони и да проверите дали не съдържат синтактични грешки, ще ви помогне инструментът Linter. Стартира се от конзолата: ```shell -vendor/bin/latte-lint <path> +vendor/bin/latte-lint <път> ``` -Ако използвате потребителски тагове, създайте и свой персонализиран Linter, например `custom-latte-lint`: +С параметъра `--strict` активирате [#стриктен режим]. + +Ако използвате собствени тагове, създайте си и собствена версия на Linter, напр. `custom-latte-lint`: ```php #!/usr/bin/env php <?php -// въведете действителния път до файла autoload.php +// въведете реалния път до файла autoload.php require __DIR__ . '/vendor/autoload.php'; -$linter = new Latte\Tools\Linter($engine); -$linter->scanDirectory($path); +$path = $argv[1] ?? '.'; -$engine = new Latte\Engine; -// регистрира отделни разширения тук -$engine->addExtension(/* ... */); +$linter = new Latte\Tools\Linter; +$latte = $linter->getEngine(); +// тук добавете вашите индивидуални разширения +$latte->addExtension(/* ... */); -$path = $argv[1]; -$linter = new Latte\Tools\Linter(engine: $engine); $ok = $linter->scanDirectory($path); exit($ok ? 0 : 1); ``` +Алтернативно можете да предадете собствен обект `Latte\Engine` на Linter: + +```php +$latte = new Latte\Engine; +// тук конфигурираме обекта $latte +$linter = new Latte\Tools\Linter(engine: $latte); +``` + -Зареждане на шаблони от низ .[#toc-loading-templates-from-a-string] -=================================================================== +Зареждане на шаблони от низ +=========================== -Трябва да зареждате шаблони от низове вместо от файлове, може би за целите на тестването? [StringLoader |extending-latte#stringloader] ще ви помогне: +Трябва ли ви да зареждате шаблони от низове вместо от файлове, например за целите на тестване? Ще ви помогне [StringLoader |loaders#StringLoader]: ```php $latte->setLoader(new Latte\Loaders\StringLoader([ @@ -262,10 +318,10 @@ $latte->render('main.file', $params); ``` -Обработчик на изключения .[#toc-exception-handler] -================================================== +Exception handler +================= -Можете да дефинирате свой собствен манипулатор за очакваните изключения. Изключенията, повдигнати вътре [`{try}` |tags#try] и в [пясъчника |sandbox] се предават към него. +Можете да дефинирате собствен обслужващ handler за очаквани изключения. Ще му бъдат предадени изключенията, възникнали вътре в [`{try}` |tags#try] и в [sandbox|sandbox]. ```php $loggingHandler = function (Throwable $e, Latte\Runtime\Template $template) use ($logger) { @@ -277,17 +333,17 @@ $latte->setExceptionHandler($loggingHandler); ``` -Автоматично търсене на оформление .[#toc-automatic-layout-lookup] -================================================================= +Автоматично намиране на лейаут +============================== -Използване на тага [`{layout}` |template-inheritance#layout-inheritance], шаблонът определя своя родителски шаблон. Възможно е също така макетът да се търси автоматично, което ще опрости писането на шаблони, тъй като няма да е необходимо те да включват тага `{layout}`. +С помощта на тага [`{layout}` |template-inheritance#Наследяване на лейаут] шаблонът определя своя родителски шаблон. Възможно е също да се остави автоматичното намиране на лейаута, което ще опрости писането на шаблони, тъй като в тях няма да е необходимо да се посочва тагът `{layout}`. Това се постига по следния начин: ```php $finder = function (Latte\Runtime\Template $template) { if (!$template->getReferenceType()) { - // връща пътя до родителския файл на шаблона + // връща пътя до файла с лейаута return 'automatic.layout.latte'; } }; @@ -296,4 +352,4 @@ $latte = new Latte\Engine; $latte->addProvider('coreParentFinder', $finder); ``` -Ако шаблонът не трябва да има оформление, той ще посочи това с тага `{layout none}`. +Ако шаблонът не трябва да има лейаут, той го обявява с тага `{layout none}`. diff --git a/latte/bg/extending-latte.texy b/latte/bg/extending-latte.texy index 5cd36fbb27..4e1766f892 100644 --- a/latte/bg/extending-latte.texy +++ b/latte/bg/extending-latte.texy @@ -2,284 +2,226 @@ ******************** .[perex] -Latte е много гъвкав и може да бъде разширяван по много начини: можете да добавяте персонализирани филтри, функции, тагове, зареждащи устройства и др. Ще ви покажем как да го направите. +Latte е проектиран с мисъл за разширяемост. Въпреки че стандартният му набор от тагове, филтри и функции покрива много случаи на употреба, често се налага да добавяте собствена специфична логика или помощни инструменти. Тази страница предоставя преглед на начините за разширяване на Latte, така че да отговаря перфектно на изискванията на вашия проект - от прости помощници до сложен нов синтаксис. -Тази глава описва различни начини за разширяване на Latte. Ако искате да използвате промените си в различни проекти или да ги споделите с други хора, трябва да [създадете така нареченото разширение |creating-extension]. +Начини за разширяване на Latte +============================== -Колко пътя водят до Рим? .[#toc-how-many-roads-lead-to-rome] -============================================================ +Ето бърз преглед на основните начини, по които можете да персонализирате и разширите Latte: -Тъй като някои от методите за удължаване на Latte могат да бъдат смесени, нека първо се опитаме да обясним разликите между тях. Като пример, нека се опитаме да реализираме генератор *Lorem ipsum*, на който се подава броят на думите, които трябва да генерира. +- **[Потребителски филтри |Custom Filters]:** За форматиране или трансформиране на данни директно в изхода на шаблона (напр. `{$var|myFilter}`). Идеални за задачи като форматиране на дати, редактиране на текст или прилагане на специфично екраниране. Можете също да ги използвате за модифициране на по-големи блокове HTML съдържание, като обвиете съдържанието в анонимен [`{block}` |tags#block] и приложите към него потребителски филтър. +- **[Потребителски функции |Custom Functions]:** За добавяне на преизползваема логика, която може да бъде извикана в рамките на изрази в шаблона (напр. `{myFunction($arg1, $arg2)}`). Полезни за изчисления, достъп до помощни функции на приложението или генериране на малки части от съдържанието. +- **[Потребителски тагове |Custom Tags]:** За създаване на напълно нови езикови конструкции (`{mytag}...{/mytag}` или `n:mytag`). Таговете предлагат най-много възможности, позволяват дефиниране на собствени структури, контрол върху парсването на шаблона и имплементиране на сложна логика за рендиране. +- **[Компилационни преминавания |Compiler Passes]:** Функции, които модифицират абстрактното синтактично дърво (AST) на шаблона след парсване, но преди генериране на PHP код. Използват се за напреднали оптимизации, проверки за сигурност (като Sandbox) или автоматични модификации на кода. +- **[Потребителски зареждащи устройства |loaders]:** За промяна на начина, по който Latte търси и зарежда файлове с шаблони (напр. зареждане от база данни, криптирано хранилище и т.н.). -Основната конструкция на езика Latte е тагът. Можем да реализираме генератора, като разширим Latte с нов таг: +Изборът на правилния метод за разширение е ключов. Преди да създадете сложен таг, помислете дали по-прост филтър или функция не биха били достатъчни. Нека го илюстрираме с пример: имплементиране на генератор *Lorem ipsum*, който приема като аргумент броя на думите за генериране. -```latte -{lipsum 40} -``` - -Етикетът ще работи добре. Въпреки това генераторът като таг може да не е достатъчно гъвкав, тъй като не може да се използва в израз. Между другото, на практика рядко се налага да генерирате тагове; и това е добра новина, тъй като таговете са по-сложен начин за разширяване. - -Добре, нека опитаме да създадем филтър вместо таг: - -```latte -{=40|lipsum} -``` - -Отново приемлив вариант. Но филтърът трябва да преобразува подадената стойност в нещо друго. Тук като аргумент на филтъра се използва стойността `40`, която показва броя на създадените думи, а не стойността, която искаме да преобразуваме. +- **Като таг?** `{lipsum 40}` - Възможно е, но таговете са по-подходящи за контролни структури или генериране на сложни тагове. Таговете не могат да се използват директно в изрази. +- **Като филтър?** `{=40|lipsum}` - Технически работи, но филтрите са предназначени за *трансформиране* на входната стойност. Тук `40` е *аргумент*, а не стойност, която се трансформира. Това изглежда семантично неправилно. +- **Като функция?** `{lipsum(40)}` - Това е най-естественото решение! Функциите приемат аргументи и връщат стойности, което е идеално за използване във всеки израз: `{var $text = lipsum(40)}`. -Затова нека се опитаме да използваме функция: - -```latte -{lipsum(40)} -``` +**Обща препоръка:** Използвайте функции за изчисления/генериране, филтри за трансформация и тагове за нови езикови конструкции или сложни тагове. Използвайте преминавания за манипулиране на AST и зареждащи устройства за извличане на шаблони. -Това е всичко! За този конкретен пример създаването на функция е идеалната точка за разширяване. Можете да го извикате навсякъде, където се получава израз, например: - -```latte -{var $text = lipsum(40)} -``` +Директна регистрация +==================== -Филтри .[#toc-filters] -====================== +За помощни инструменти, специфични за проекта, или бързи разширения, Latte позволява директна регистрация на филтри и функции в обекта `Latte\Engine`. -Създайте филтър, като регистрирате името му и всеки елемент, който може да бъде извикан от PHP, например функция: +За да регистрирате филтър, използвайте метода `addFilter()`. Първият аргумент на вашата филтърна функция ще бъде стойността преди знака `|`, а следващите аргументи са тези, които се предават след двоеточието `:`. ```php $latte = new Latte\Engine; -$latte->addFilter('shortify', fn(string $s) => mb_substr($s, 0, 10)); // съкращава текста до 10 символа -``` -В този случай би било по-добре филтърът да получи допълнителен параметър: - -```php -$latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); -``` +// Дефиниция на филтъра (извикващ се обект: функция, статичен метод и т.н.) +$myTruncate = fn(string $s, int $length = 50) => mb_substr($s, 0, $length); -Използваме го в шаблона по следния начин: +// Регистрация +$latte->addFilter('truncate', $myTruncate); -```latte -<p>{$text|shortify}</p> -<p>{$text|shortify:100}</p> +// Използване в шаблона: {$text|truncate} или {$text|truncate:100} ``` -Както можете да видите, функцията получава като аргументи лявата страна на филтъра пред тръбата `|` as the first argument and the arguments passed to the filter after `:`. - -Разбира се, функцията, представляваща филтъра, може да приема произволен брой параметри, като се поддържат и променливи параметри. - - -Филтри, които използват класа .[#toc-filters-using-the-class] -------------------------------------------------------------- - -Вторият начин за дефиниране на филтър е да [се използва клас |develop#Parameters-as-a-Class]. Създаваме метод с атрибут `TemplateFilter`: +Можете също да регистрирате **Filter Loader**, функция, която динамично предоставя извикващи се обекти на филтри според изискваното име: ```php -class TemplateParameters -{ - public function __construct( - // parameters - ) {} - - #[Latte\Attributes\TemplateFilter] - public function shortify(string $s, int $len = 10): string - { - return mb_substr($s, 0, $len); - } -} - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); +$latte->addFilterLoader(fn(string $name) => /* връща извикващ се обект или null */); ``` -Ако използвате PHP 7.x и Latte 2.x, използвайте анотацията `/** @filter */` вместо атрибута. - -Зареждане на филтри .{data-version:2.10}[#toc-filter-loader] ------------------------------------------------------------- - -Вместо да регистрирате отделни филтри, можете да създадете така наречения loader, който представлява функция, извикана с името на филтъра като аргумент и връщаща обратната връзка на PHP или null. +За да регистрирате функция, използваема в изрази на шаблона, използвайте `addFunction()`. ```php -$latte->addFilterLoader([new Filters, 'load']); +$latte = new Latte\Engine; +// Дефиниция на функцията +$isWeekend = fn(DateTimeInterface $date) => $date->format('N') >= 6; -class Filters -{ - public function load(string $filter): ?callable - { - if (in_array($filter, get_class_methods($this))) { - return [$this, $filter]; - } - return null; - } - - public function shortify($s, $len = 10) - { - return mb_substr($s, 0, $len); - } - - // ... -} +// Регистрация +$latte->addFunction('isWeekend', $isWeekend); + +// Използване в шаблона: {if isWeekend($myDate)}Уикенд!{/if} ``` +Повече информация ще намерите в секциите [Създаване на потребителски филтри |custom-filters] и [Функции |custom-functions]. -Контекстни филтри .[#toc-contextual-filters] --------------------------------------------- -Контекстният филтър е филтър, който приема обект [api:Latte\Runtime\FilterInfo] като първи параметър, последван от други параметри, както при класическите филтри. Той е регистриран по същия начин, а самият Latte признава, че филтърът е контекстуален: +Надежден начин: Latte Extension .{toc: Latte Extension} +======================================================= -```php -use Latte\Runtime\FilterInfo; +Докато директната регистрация е проста, стандартният и препоръчителен начин за пакетиране и разпространение на разширения на Latte е чрез класове **Extension**. Extension служи като централна конфигурационна точка за регистрация на множество тагове, филтри, функции, компилационни преминавания и други елементи. -$latte->addFilter('foo', function (FilterInfo $info, string $str): string { - // ... -}); -``` +Защо да използвате Extensions? -Контекстните филтри могат да определят и променят типа на съдържанието, което получават, в променливата `$info->contentType`. Ако филтърът се извиква класически чрез променлива (напр. `{$var|foo}`), тогава `$info->contentType` ще съдържа нула. +- **Организация:** Поддържа свързаните разширения (тагове, филтри и т.н. за конкретна функция) заедно в един клас. +- **Преизползваемост и споделяне:** Лесно пакетирайте вашите разширения за използване в други проекти или за споделяне с общността (напр. чрез Composer). +- **Пълна мощ:** Потребителските тагове и компилационните преминавания *могат да се регистрират само* чрез Extensions. -Филтърът трябва първо да провери дали се поддържа типът на съдържанието на входния низ. Може също така да го промени. Пример за филтър, който приема текст (или null) и връща HTML: + +Регистрация на Extension +------------------------ + +Extension се регистрира в Latte с помощта на метода `addExtension()` (или чрез [конфигурационен файл |application:configuration#Шаблони Latte]): ```php -use Latte\Runtime\FilterInfo; - -$latte->addFilter('money', function (FilterInfo $info, float $amount): string { - // Първо проверяваме дали типът на съдържанието на входа е текст - if (!in_array($info->contentType, [null, ContentType::Text])) { - throw new Exception("Filter |money used in incompatible content type $info->contentType."); - } - - // променете типа на съдържанието на HTML - $info->contentType = ContentType::Html; - return "<i>$num Kč</i>"; -}); +$latte = new Latte\Engine; +$latte->addExtension(new MyProjectExtension); ``` -.[note] -В този случай филтърът трябва да гарантира, че данните са правилно изведени. +Ако регистрирате множество разширения и те дефинират тагове, филтри или функции с еднакви имена, предимство има последно добавеното разширение. Това също означава, че вашите разширения могат да презапишат нативните тагове/филтри/функции. -Всички филтри, които се използват върху [блокове |tags#block] (като `{block|foo}...{/block}`) трябва да бъдат контекстуални. +Всеки път, когато направите промяна в класа и автоматичното обновяване не е изключено, Latte автоматично ще прекомпилира вашите шаблони. -Функции .{data-version:2.6}[#toc-functions] -=========================================== +Създаване на Extension +---------------------- -По подразбиране всички функции на PHP могат да се използват в Latte, освен ако това не е забранено в пясъчника. Но можете да дефинирате и свои собствени функции. Те могат да отменят собствените си функции. +За да създадете собствено разширение, трябва да създадете клас, който наследява от [api:Latte\Extension]. За да добиете представа как изглежда такова разширение, разгледайте вграденото "CoreExtension":https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php. -Създайте функция, като регистрирате нейното име и която и да е функция на PHP, която да извикате: +Нека разгледаме методите, които можете да имплементирате: -```php -$latte = new Latte\Engine; -$latte->addFunction('random', function (...$args) { - return $args[array_rand($args)]; -}); -``` -Използването ще бъде същото като при извикване на функция в PHP: +beforeCompile(Latte\Engine $engine): void .[method] +--------------------------------------------------- -```latte -{random(apple, orange, lemon)} // prints for example: apple -``` +Извиква се преди компилацията на шаблона. Методът може да се използва например за инициализации, свързани с компилацията. -Функции, използващи класа .[#toc-functions-using-the-class] ------------------------------------------------------------ +getTags(): array .[method] +-------------------------- -Вторият начин за дефиниране на функция е да [се използва клас |develop#Parameters-as-a-Class]. Създаваме метод с атрибут `TemplateFunction`: +Извиква се при компилация на шаблона. Връща асоциативен масив *име на таг => извикващ се обект*, което са функции за парсване на тагове. [Повече информация |custom-tags]. ```php -class TemplateParameters +public function getTags(): array { - public function __construct( - // parameters - ) {} - - #[Latte\Attributes\TemplateFunction] - public function random(...$args) - { - return $args[array_rand($args)]; - } + return [ + 'foo' => FooNode::create(...), + 'bar' => BarNode::create(...), + 'n:baz' => NBazNode::create(...), + // ... + ]; } - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); ``` -Ако използвате PHP 7.x и Latte 2.x, използвайте анотацията `/** @function */` вместо атрибута. +Тагът `n:baz` представлява чист [n:атрибут |syntax#n:атрибути], т.е. таг, който може да бъде записан само като атрибут. +При таговете `foo` и `bar`, Latte автоматично разпознава дали са двойни тагове и ако да, могат автоматично да се записват с помощта на n:атрибути, включително варианти с префикси `n:inner-foo` и `n:tag-foo`. -товарачи .[#toc-loaders] -======================== +Редът на изпълнение на такива n:атрибути се определя от реда им в масива, върнат от метода `getTags()`. Така `n:foo` винаги се изпълнява преди `n:bar`, дори ако атрибутите в HTML тага са изброени в обратен ред като `<div n:bar="..." n:foo="...">`. -Зареждащите устройства отговарят за зареждането на шаблони от източник, например файлова система. Те се монтират по метода `setLoader()`: +Ако трябва да определите реда на n:атрибутите между няколко разширения, използвайте помощния метод `order()`, където параметърът `before` xor `after` определя кои тагове се сортират преди или след тага. ```php -$latte->setLoader(new MyLoader); +public function getTags(): array +{ + return [ + 'foo' => self::order(FooNode::create(...), before: 'bar')] + 'bar' => self::order(BarNode::create(...), after: ['block', 'snippet'])] + ]; +} ``` -Вградените програми за зареждане са: +getPasses(): array .[method] +---------------------------- -FileLoader .[#toc-fileloader] ------------------------------ - -Зареждащо устройство по подразбиране. Зареждане на модели от файловата система. +Извиква се при компилация на шаблона. Връща асоциативен масив *име на преминаване => извикващ се обект*, което са функции, представляващи т.нар. [компилационни преминавания |compiler-passes], които преминават и модифицират AST. -Достъпът до файловете може да бъде ограничен чрез посочване на базова директория: +Тук също може да се използва помощният метод `order()`. Стойността на параметрите `before` или `after` може да бъде `*` със значение преди/след всички. ```php -$latte->setLoader(new Latte\Loaders\FileLoader($templateDir)); -$latte->render('test.latte'); +public function getPasses(): array +{ + return [ + 'optimize' => Passes::optimizePass(...), + 'sandbox' => self::order($this->sandboxPass(...), before: '*'), + // ... + ]; +} ``` -StringLoader .[#toc-stringloader] ---------------------------------- +beforeRender(Latte\Engine $engine): void .[method] +-------------------------------------------------- -Зареждане на модели от низове. Този модул за зареждане е много полезен за тестване на единици. Той може да се използва и за малки проекти, при които е целесъобразно всички шаблони да се съхраняват в един PHP файл. +Извиква се преди всяко рендиране на шаблона. Методът може да се използва например за инициализиране на променливи, използвани по време на рендирането. -```php -$latte->setLoader(new Latte\Loaders\StringLoader([ - 'main.file' => '{include other.file}', - 'other.file' => '{if true} {$var} {/if}', -])); -$latte->render('main.file'); -``` +getFilters(): array .[method] +----------------------------- -Опростена употреба: +Извиква се преди рендиране на шаблона. Връща филтри като асоциативен масив *име на филтър => извикващ се обект*. [Повече информация |custom-filters]. ```php -$template = '{if true} {$var} {/if}'; -$latte->setLoader(new Latte\Loaders\StringLoader); -$latte->render($template); +public function getFilters(): array +{ + return [ + 'batch' => $this->batchFilter(...), + 'trim' => $this->trimFilter(...), + // ... + ]; +} ``` -Създаване на персонализиран буутлоудър .[#toc-creating-a-custom-loader] ------------------------------------------------------------------------ +getFunctions(): array .[method] +------------------------------- -Loader е клас, който имплементира интерфейса [api:Latte\Loader]. +Извиква се преди рендиране на шаблона. Връща функции като асоциативен масив *име на функция => извикващ се обект*. [Повече информация |custom-functions]. +```php +public function getFunctions(): array +{ + return [ + 'clamp' => $this->clampFunction(...), + 'divisibleBy' => $this->divisibleByFunction(...), + // ... + ]; +} +``` -Етикети .[#toc-tags] -==================== - -Една от най-интересните характеристики на механизма за шаблониране е възможността за дефиниране на нови езикови конструкции с помощта на тагове. Това също е по-сложна функционалност и трябва да разберете как работи Latte вътрешно. -В повечето случаи обаче не е необходимо да се поставя етикет: -- ако трябва да генерира някакъв изход, използвайте [функция |#Functions] вместо това. -- ако искате да промените входа и да го върнете, използвайте [филтър |#Filters] -- ако искате да редактирате текстова секция, обвийте я с таг [`{block}` |tags#block] и използвайте [филтър |#Contextual-Filters] -- ако не трябва да извежда нищо, а само да извика функция, извикайте я с [`{do}` |tags#do] +getProviders(): array .[method] +------------------------------- -Ако все още искате да създадете етикет, чудесно! Всичко необходимо можете да намерите в раздел [Създаване на разширение |creating-extension]. +Извиква се преди рендиране на шаблона. Връща масив от providers, които обикновено са обекти, използвани от таговете по време на изпълнение. Достъпват се чрез `$this->global->...`. [Повече информация |custom-tags#Представяне на Providers]. +```php +public function getProviders(): array +{ + return [ + 'myFoo' => $this->foo, + 'myBar' => $this->bar, + // ... + ]; +} +``` -Предавания на компилатора .{data-version:3.0}[#toc-compiler-passes] -=================================================================== -Пропусканията на компилатора са функции, които модифицират AST или събират информация в тях. В Latte например пясъчникът е реализиран по следния начин: той обхожда всички AST възли, намира извиквания на функции и методи и ги заменя с контролирани извиквания. +getCacheKey(Latte\Engine $engine): mixed .[method] +-------------------------------------------------- -Както и при таговете, това е по-сложна функционалност и трябва да разберете как работи Latte под капака. Основните принципи са описани в глава [Създаване на разширения |creating-extension]. +Извиква се преди рендиране на шаблона. Върнатата стойност става част от ключа, чийто хеш се съдържа в името на файла на компилирания шаблон. Следователно за различни върнати стойности Latte ще генерира различни кеш файлове. diff --git a/latte/bg/filters.texy b/latte/bg/filters.texy index 6cb60393fd..00971a7e45 100644 --- a/latte/bg/filters.texy +++ b/latte/bg/filters.texy @@ -1,112 +1,114 @@ -Филтри за Latte -*************** +Latte филтри +************ .[perex] -Филтрите са функции, които променят или форматират данните в желаната от нас форма. Това е кратко описание на наличните вградени филтри. +В шаблоните можем да използваме функции, които помагат за модифициране или преформатиране на данните в окончателния им вид. Наричаме ги *филтри*. .[table-latte-filters] -|## Конвертиране на редове/масиви -| `batch` | [линейно изброяване на данни в таблица |#batch] -| `breakLines` | [вмъква прекъсвания на редове в HTML преди всички нови редове |#breakLines] -| `bytes` | [форматира размера в байтове |#bytes] -| `clamp` | [ограничава стойността до определен диапазон |#clamp] -| `dataStream` | [Конвертиране на протокола за данни URI |#dataStream] -| `date` | [форматиране на датата |#date] -| `explode` | [разделя низа с даден разделител |#explode] -| `first` [връща първия елемент на масив или символен низ |#first] -| `implode` | [свързва масива с низ |#implode] -| `indent` | [отстъпи на текста наляво с броя на табулаторите |#indent] -| `join` | [обединяване на масив в низ |#implode] -| `last` | [връща последния елемент на масив или символен низ |#last] -| `length` | [връща дължината на низ или масив |#length] -| `number` | [форматира число |#number] -| `padLeft` | [допълва низа вляво |#padLeft] -| `padRight` | [допълва низа до зададена дължина надясно |#padRight] -| `random` | [връща произволен елемент от масив или символен низ |#random] -| `repeat` | [повтаря символния низ |#repeat] -| `replace` | [замества всички срещания на търсения низ със заместител |#replace] -| `replaceRE` | [замества всички срещания в съответствие с регулярен израз |#replaceRE] -| `reverse` | [инвертира низ или масив във формат UTF-8 |#reverse] -| `slice` | [извлича масив или фрагмент от низ |#slice] -| `sort` | [сортира масива |#sort] -| `spaceless` | [изтрива символите за бял интервал |#spaceless], като тага [без интервал |tags] -| `split` | [разделя низ по зададен разделител |#explode] -| `strip` | [изтриване на бели интервали |#spaceless] -| `stripHtml` | [изтрива HTML тагове и преобразува HTML обекти в текст |#stripHtml] -| `substr` | [връща част от низ |#substr] -| `trim` | [изтрива интервалите от символния низ |#trim] -| `translate` | [превод на други езици |#translate] -| `truncate` | [съкращава дължината, като запазва цели думи |#truncate] -| `webalize` | [прави така, че низът UTF-8 |#webalize] да [съответства на формата, използвана в URL адреса |#webalize] +|## Трансформация +| `batch` | [извеждане на линейни данни в таблица |#batch] +| `breakLines` | [Добавя HTML нов ред преди края на реда |#breakLines] +| `bytes` | [форматира размер в байтове |#bytes] +| `clamp` | [ограничава стойността в даден диапазон |#clamp] +| `dataStream` | [конверсия за Data URI протокол |#dataStream] +| `date` | [форматира дата и час |#date] +| `explode` | [разделя низ на масив по разделител |#explode] +| `first` | [връща първия елемент на масив или знак от низ |#first] +| `group` | [групира данни по различни критерии |#group] +| `implode` | [свързва масив в низ |#implode] +| `indent` | [индентира текст отляво с даден брой табулации |#indent] +| `join` | [свързва масив в низ |#implode] +| `last` | [връща последния елемент на масив или знак от низ |#last] +| `length` | [връща дължината на низ в знаци или масив |#length] +| `localDate` | [форматира дата и час според локализацията |#localDate] +| `number` | [форматира число |#number] +| `padLeft` | [допълва низ отляво до желаната дължина |#padLeft] +| `padRight` | [допълва низ отдясно до желаната дължина |#padRight] +| `random` | [връща случаен елемент от масив или знак от низ |#random] +| `repeat` | [повторение на низ |#repeat] +| `replace` | [заменя срещанията на търсения низ |#replace] +| `replaceRE` | [заменя срещанията според регулярен израз |#replaceRE] +| `reverse` | [обръща UTF‑8 низ или масив |#reverse] +| `slice` | [извлича част от масив или низ |#slice] +| `sort` | [сортира масив |#sort] +| `spaceless` | [премахва празно пространство |#spaceless], подобно на тага [spaceless |tags] +| `split` | [разделя низ на масив по разделител |#explode] +| `strip` | [премахва празно пространство |#spaceless] +| `stripHtml` | [премахва HTML тагове и преобразува HTML ентити в знаци |#stripHtml] +| `substr` | [връща част от низ |#substr] +| `trim` | [премахва начални и крайни интервали или други знаци |#trim] +| `translate` | [превод на други езици |#translate] +| `truncate` | [скъсява дължината със запазване на думи |#truncate] +| `webalize` | [модифицира UTF‑8 низ във форма, използвана в URL |#webalize] .[table-latte-filters] -|## Азбучен регистър -| `capitalize` | [малки букви, първата буква на всяка дума е главна |#capitalize] -| `firstUpper` | [прави първата буква главна |#firstUpper] -| `lower` | [прави реда малък |#lower] -| `upper` | [прави реда главен |#upper] +|## Регистър на буквите +| `capitalize` | [малки букви, първата буква на думите е главна |#capitalize] +| `firstUpper` | [преобразува първата буква в главна |#firstUpper] +| `lower` | [преобразува в малки букви |#lower] +| `upper` | [преобразува в главни букви |#upper] .[table-latte-filters] -|## Закръгляне на числата -| `ceil` | [закръгляне на число до определена точност |#ceil] -| `floor` | [закръгляне на числото до определена точност|#floor] -| `round` | [закръгляне на число с определена точност|#round] +|## Закръгляване +| `ceil` | [закръгля число нагоре до дадена точност |#ceil] +| `floor` | [закръгля число надолу до дадена точност |#floor] +| `round` | [закръгля число до дадена точност |#round] .[table-latte-filters] -|## ескейпинг -| `escapeUrl` | [изстъргва параметъра в URL адреса |#escapeUrl] -| `noescape` |00 | [отпечатва променливата без ескейпване |#noescape] -| `query` | [формира низ за заявка в URL адреса |#query] +|## Екраниране +| `escapeUrl` | [екранира параметър в URL |#escapeUrl] +| `noescape` | [извежда променлива без екраниране |#noescape] +| `query` | [генерира query string в URL |#query] -Съществуват и филтри за ескапиране за HTML (`escapeHtml` и `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) и iCalendar (`escapeICal`), които Latte използва сам благодарение на [контекстно-осъзнатото ескапиране |safety-first#Context-aware escaping] и не е необходимо да ги пишете. +Освен това съществуват филтри за екраниране за HTML (`escapeHtml` и `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) и iCalendar (`escapeICal`), които Latte използва самостоятелно благодарение на [контекстно-чувствително екраниране |safety-first#Контекстно-чувствително екраниране] и не е необходимо да ги записвате. .[table-latte-filters] |## Сигурност -| `checkUrl` | [дезинфекцира символния низ, който ще се използва в атрибута href |#checkUrl] -| `nocheck` | [предотвратява автоматичната обработка на URL |#nocheck] +| `checkUrl` | [обработва URL адрес срещу опасни входове |#checkUrl] +| `nocheck` | [предотвратява автоматичната обработка на URL адреса |#nocheck] -Latte [проверява |safety-first#Link-Checking] атрибутите `src` и `href` [автоматично |safety-first#Link-Checking], така че почти не е необходимо да използвате филтъра `checkUrl`. +Latte атрибутите `src` и `href` [проверява автоматично |safety-first#Проверка на връзки], така че филтърът `checkUrl` почти не е необходимо да се използва. .[note] -Всички вградени филтри работят с низове, кодирани в UTF-8. +Всички филтри по подразбиране са предназначени за низове в кодировка UTF‑8. -Употреба .[#toc-usage] -====================== +Използване +========== -Latte позволява извикване на филтри чрез използване на знака за тръба (позволен е предхождащ интервал): +Филтрите се записват след вертикална черта (може да има интервал преди нея): ```latte <h1>{$heading|upper}</h1> ``` -Филтрите могат да бъдат верижно свързани, като в този случай се прилагат в ред от ляво на дясно: +Филтрите (в по-стари версии помощници) могат да бъдат верижно свързани и след това се прилагат в реда отляво надясно: ```latte <h1>{$heading|lower|capitalize}</h1> ``` -Параметрите се поставят след името на филтъра, разделени с двоеточие или запетая: +Параметрите се задават след името на филтъра, разделени с двоеточия или запетаи: ```latte <h1>{$heading|truncate:20,''}</h1> ``` -Филтрите могат да се прилагат върху изрази: +Филтрите могат да се прилагат и към израз: ```latte {var $name = ($title|upper) . ($subtitle|lower)}</h1> ``` -[Потребителски филтри |extending-latte#filters] могат да бъдат регистрирани по този начин: +[Потребителски филтри|custom-filters] могат да се регистрират по следния начин: ```php $latte = new Latte\Engine; $latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -Използваме го в шаблон по следния начин: +В шаблона след това се извиква така: ```latte <p>{$text|shortify}</p> @@ -114,13 +116,13 @@ $latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $ ``` -Филтри .[#toc-filters] -====================== +Филтри +====== -batch(int length, mixed item): array .[filter]{data-version:2.7} ----------------------------------------------------------------- -Филтър, който опростява изписването на линейни данни под формата на таблица. Връща масив от масиви със зададения брой елементи. Ако предоставите втори параметър, той се използва за попълване на липсващите елементи на последния ред. +batch(int $length, mixed $item): array .[filter] +------------------------------------------------ +Филтър, който опростява извеждането на линейни данни във вид на таблица. Връща масив от масиви със зададения брой елементи. Ако зададете втори параметър, той ще се използва за допълване на липсващите елементи на последния ред. ```latte {var $items = ['a', 'b', 'c', 'd', 'e']} @@ -135,7 +137,7 @@ batch(int length, mixed item): array .[filter]{data-version:2.7} </table> ``` -Отпечатва: +Извежда: ```latte <table> @@ -152,93 +154,95 @@ batch(int length, mixed item): array .[filter]{data-version:2.7} </table> ``` +Вижте също [#group] и тага [iterateWhile |tags#iterateWhile]. + breakLines .[filter] -------------------- -Вмъква прекъсванията на HTML редове преди всички нови редове. +Добавя HTML таг `<br>` преди всеки знак за нов ред. ```latte {var $s = "Text & with \n newline"} -{$s|breakLines} {* списъци "Text & with <br>\n newline" *} +{$s|breakLines} {* извежда "Text & with <br>\n newline" *} ``` -bytes(int precision = 2) .[filter] ----------------------------------- -Форматира размера в байтове в удобна за четене от човека форма. +bytes(int $precision=2) .[filter] +--------------------------------- +Форматира размера в байтове в четим за човека вид. Ако е зададена [локализация |develop#Locale], ще се използват съответните разделители за десетични знаци и хиляди. ```latte -{$size|bytes} 0 B, 1.25 GB, … -{$size|bytes:0} 10 B, 1 GB, … +{$size|bytes} {* 0 B, 1.25 GB, … *} +{$size|bytes:0} {* 10 B, 1 GB, … *} ``` -ceil(int precision = 0) .[filter] ---------------------------------- -Закръгляне на число до зададена точност. +ceil(int $precision=0) .[filter] +-------------------------------- +Закръгля число нагоре до дадена точност. ```latte -{=3.4|ceil} {* изходи 4 *} -{=135.22|ceil:1} {* изходи 135.3 *} -{=135.22|ceil:3} {* изходи 135.22 *} +{=3.4|ceil} {* извежда 4 *} +{=135.22|ceil:1} {* извежда 135.3 *} +{=135.22|ceil:3} {* извежда 135.22 *} ``` -Вижте също [floor |#floor], [round |#round]. +Вижте също [#floor], [#round]. capitalize .[filter] -------------------- -Връща версия на стойността, изписана със заглавие. Думите започват с главни букви, а всички останали символи са малки. Изисква PHP разширение `mbstring`. +Думите ще започват с главни букви, всички останали знаци ще бъдат малки. Изисква PHP разширението `mbstring`. ```latte -{='i like LATTE'|capitalize} {* списъци 'I Like Latte' *} +{='i like LATTE'|capitalize} {* извежда 'I Like Latte' *} ``` -Вижте също [firstUpper |#firstUpper], [lower |#lower], [upper |#upper]. +Вижте също [#firstUpper], [#lower], [#upper]. checkUrl .[filter] ------------------ -Принуждава URL адреса да се почиства. Той проверява дали променливата съдържа уеб URL адрес (т.е. протокол HTTP/HTTPS) и предотвратява записването на връзки, които могат да представляват риск за сигурността. +Принуждава обработката на URL адреса. Проверява дали променливата съдържа уеб URL (т.е. протокол HTTP/HTTPS) и предотвратява извеждането на връзки, които могат да представляват риск за сигурността. ```latte {var $link = 'javascript:window.close()'} -<a data-href="{$link|checkUrl}">checked</a> -<a data-href="{$link}">unchecked</a> +<a data-href={$link|checkUrl}>контролирано</a> +<a data-href={$link}>неконтролирано</a> ``` -Отпечатва: +Извежда: ```latte -<a data-href="">checked</a> -<a data-href="javascript:window.close()">unchecked</a> +<a data-href="">контролирано</a> +<a data-href="javascript:window.close()">неконтролирано</a> ``` -Вижте също [nocheck |#nocheck]. +Вижте също [#nocheck]. -clamp(int|float min, int|float max) .[filter]{data-version:2.9} ---------------------------------------------------------------- -Връща стойността, която е вкарана в обхват от min и max. +clamp(int|float $min, int|float $max) .[filter] +----------------------------------------------- +Ограничава стойността в дадения инклузивен диапазон min и max. ```latte {$level|clamp: 0, 255} ``` -Съществува и като [clamp |functions#clamp]. +Съществува и като [функция |functions#clamp]. -dataStream(string mimetype = detect) .[filter] ----------------------------------------------- -Конвертира съдържанието в схема URI за данни. Може да се използва за вмъкване на изображения в HTML или CSS, без да е необходимо да се свързват външни файлове. +dataStream(string $mimetype=detect) .[filter] +--------------------------------------------- +Конвертира съдържанието в data URI scheme. С негова помощ могат да се вмъкват изображения в HTML или CSS без необходимост от свързване на външни файлове. -Нека имаме изображение в променлива `$img = Image::fromFile('obrazek.gif')`, тогава +Нека имаме изображение в променливата `$img = Image::fromFile('obrazek.gif')`, тогава ```latte -<img src="{$img|dataStream}"> +<img src={$img|dataStream}> ``` -Отпечатва например: +Извежда например: ```latte <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA @@ -247,108 +251,128 @@ AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO ``` .[caution] -Изисква PHP разширение `fileinfo`. +Изисква PHP разширението `fileinfo`. -date(string format) .[filter] ------------------------------ -Връща дата в зададен формат, като използва опциите на функциите [php:strftime] или [php:date] на PHP. Филтърът получава датата като времеви печат на UNIX, низ или обект от тип `DateTime`. +date(string $format) .[filter] +------------------------------ +Форматира дата и час според маската, използвана от PHP функцията [php:date]. Филтърът приема дата във формат UNIX timestamp, като низ или обект от тип `DateTimeInterface`. ```latte -{$today|date:'%d.%m.%Y'} {$today|date:'j. n. Y'} ``` +Вижте също [#localDate]. + escapeUrl .[filter] ------------------- -Избягва променлива, която да се използва като параметър в URL. +Екранира променлива за използване като параметър в URL. ```latte <a href="http://example.com/{$name|escapeUrl}">{$name}</a> ``` -Вижте също [query |#query]. +Вижте също [#query]. -explode(string separator = '') .[filter]{data-version:2.10.2} -------------------------------------------------------------- -Разделя низ по зададения разделител и връща масив от низове. Псевдоним за `split`. +explode(string $separator='') .[filter] +--------------------------------------- +Разделя низ на масив по разделител. Псевдоним за `split`. ```latte -{='one,two,three'|explode:','} {* returns ['one', 'two', 'three'] *} +{='one,two,three'|explode:','} {* връща ['one', 'two', 'three'] *} ``` -Ако разделителят е празен низ (стойност по подразбиране), входът ще бъде разделен на отделни символи: +Ако разделителят е празен низ (стойност по подразбиране), входът ще бъде разделен на отделни знаци: ```latte -{='123'|explode} {* returns ['1', '2', '3'] *} +{='123'|explode} {* връща ['1', '2', '3'] *} ``` -Можете да използвате и псевдонима `split`: +Можете също да използвате псевдонима `split`: ```latte -{='1,2,3'|split:','} {* returns ['1', '2', '3'] *} +{='1,2,3'|split:','} {* връща ['1', '2', '3'] *} ``` -Вижте също [implode |#implode]. +Вижте също [#implode]. -first .[filter]{data-version:2.10.2} ------------------------------------- -Връща първия елемент от масив или символ от низ: +first .[filter] +--------------- +Връща първия елемент на масив или знак от низ: ```latte -{=[1, 2, 3, 4]|first} {* outputs 1 *} -{='abcd'|first} {* outputs 'a' *} +{=[1, 2, 3, 4]|first} {* извежда 1 *} +{='abcd'|first} {* извежда 'a' *} ``` -Вижте също [last |#last], [random |#random]. +Вижте също [#last], [#random]. -floor(int precision = 0) .[filter] ----------------------------------- -Закръгляне на число до зададена точност. +floor(int $precision=0) .[filter] +--------------------------------- +Закръгля число надолу до дадена точност. ```latte -{=3.5|floor} {* outputs 3 *} -{=135.79|floor:1} {* outputs 135.7 *} -{=135.79|floor:3} {* outputs 135.79 *} +{=3.5|floor} {* извежда 3 *} +{=135.79|floor:1} {* извежда 135.7 *} +{=135.79|floor:3} {* извежда 135.79 *} ``` -Вижте също [ceil |#ceil], [round |#round]. +Вижте също [#ceil], [#round]. firstUpper .[filter] -------------------- -Преобразува първата буква на стойността в главна буква. Изисква PHP разширение `mbstring`. +Преобразува първата буква в главна. Изисква PHP разширението `mbstring`. ```latte -{='the latte'|firstUpper} {* outputs 'The latte' *} +{='the latte'|firstUpper} {* извежда 'The latte' *} ``` -Вижте също [capitalize |#capitalize], [lower |#lower], [upper |#upper]. +Вижте също [#capitalize], [#lower], [#upper]. -implode(string glue = '') .[filter] ------------------------------------ -Връща низ, който е конкатенация на низовете в масива. Псевдоним за `join`. +group(string|int|\Closure $by): array .[filter]{data-version:3.0.16} +-------------------------------------------------------------------- +Филтърът групира данни по различни критерии. + +В този пример редовете в таблицата се групират по колона `categoryId`. Изходът е масив от масиви, където ключът е стойността в колоната `categoryId`. [Прочетете подробно ръководство|cookbook/grouping]. ```latte -{=[1, 2, 3]|implode} {* outputs '123' *} -{=[1, 2, 3]|implode:'|'} {* outputs '1|2|3' *} +{foreach ($items|group: categoryId) as $categoryId => $categoryItems} + <ul> + {foreach $categoryItems as $item} + <li>{$item->name}</li> + {/foreach} + </ul> +{/foreach} ``` -Можете също така да използвате псевдонима `join`: .{data-version:2.10.2} +Вижте също [#batch], функцията [group |functions#group] и тага [iterateWhile |tags#iterateWhile]. + + +implode(string $glue='') .[filter] +---------------------------------- +Връща низ, който е конкатенация на елементите на последователност. Псевдоним за `join`. ```latte -{=[1, 2, 3]|join} {* outputs '123' *} +{=[1, 2, 3]|implode} {* извежда '123' *} +{=[1, 2, 3]|implode:'|'} {* извежда '1|2|3' *} ``` +Можете също да използвате псевдонима `join`: + +```latte +{=[1, 2, 3]|join} {* извежда '123' *} +``` -indent(int level = 1, string char = "\t") .[filter] ---------------------------------------------------- -Отстъпва текста отляво с даден брой табулации или други символи, които посочваме във втория незадължителен аргумент. Празните редове не се отдръпват. + +indent(int $level=1, string $char="\t") .[filter] +------------------------------------------------- +Индентира текст отляво с даден брой табулации или други знаци, които можем да посочим във втория аргумент. Празните редове не се индентират. ```latte <div> @@ -358,7 +382,7 @@ indent(int level = 1, string char = "\t") .[filter] </div> ``` -Отпечатва: +Извежда: ```latte <div> @@ -367,26 +391,26 @@ indent(int level = 1, string char = "\t") .[filter] ``` -last .[filter]{data-version:2.10.2} ------------------------------------ -Връща последния елемент от масив или символ от низ: +last .[filter] +-------------- +Връща последния елемент на масив или знак от низ: ```latte -{=[1, 2, 3, 4]|last} {* outputs 4 *} -{='abcd'|last} {* outputs 'd' *} +{=[1, 2, 3, 4]|last} {* извежда 4 *} +{='abcd'|last} {* извежда 'd' *} ``` -Вижте също [first |#first], [random |#random]. +Вижте също [#first], [#random]. length .[filter] ---------------- Връща дължината на низ или масив. -- За низове връща дължина в UTF-8 символи. -- за масиви ще върне броя на елементите. -- за обекти, които имплементират интерфейса Countable, ще се използва върнатата стойност на функцията count() -- за обекти, които реализират интерфейса IteratorAggregate, ще се използва върнатата стойност на iterator_count() +- за низове връща дължината в UTF‑8 знаци +- за масиви връща броя на елементите +- за обекти, които имплементират интерфейса `Countable`, използва върнатата стойност на метода `count()` +- за обекти, които имплементират интерфейса `IteratorAggregate`, използва върнатата стойност на функцията `iterator_count()` ```latte @@ -396,204 +420,314 @@ length .[filter] ``` +localDate(?string $format=null, ?string $date=null, ?string $time=null) .[filter] +--------------------------------------------------------------------------------- +Форматира дата и час според [локализация |develop#Locale], което осигурява последователно и локализирано показване на времеви данни в различни езици и региони. Филтърът приема дата като UNIX timestamp, низ или обект от тип `DateTimeInterface`. + +```latte +{$date|localDate} {* 15 април 2024 *} +{$date|localDate: format: yM} {* 4/2024 *} +{$date|localDate: date: medium} {* 15.4.2024 *} +``` + +Ако използвате филтъра без параметри, датата ще се изведе на ниво `long`, вижте по-нататък. + +**а) използване на формат** + +Параметърът `format` описва кои времеви компоненти да се покажат. За тях се използват буквени кодове, чийто брой повторения влияе на ширината на изхода: + +| година | `y` / `yy` / `yyyy` | `2024` / `24` / `2024` +| месец | `M` / `MM` / `MMM` / `MMMM` | `8` / `08` / `авг` / `август` +| ден | `d` / `dd` / `E` / `EEEE` | `1` / `01` / `нд` / `неделя` +| час | `j` / `H` / `h` | предпочитан / 24-часов / 12-часов +| минута | `m` / `mm` | `5` / `05` <small>(2 цифри в комбинация със секунди)</small> +| секунда | `s` / `ss` | `8` / `08` <small>(2 цифри в комбинация с минути)</small> + +Редът на кодовете във формата няма значение, тъй като редът на компонентите се извежда според обичаите на локализацията. Следователно форматът е независим от нея. Например форматът `yyyyMMMMd` в среда `en_US` ще изведе `April 15, 2024`, докато в среда `bg_BG` ще изведе `15 април 2024`: + +| locale: | bg_BG | en_US +|--- +| `format: 'dMy'` | 10.8.2024 г. | 8/10/2024 +| `format: 'yM'` | 8.2024 г. | 8/2024 +| `format: 'yyyyMMMM'` | август 2024 г. | August 2024 +| `format: 'MMMM'` | август | August +| `format: 'jm'` | 17:22 | 5:22 PM +| `format: 'Hm'` | 17:22 | 17:22 +| `format: 'hm'` | 5:22 сл. об. | 5:22 PM + + +**б) използване на предварително зададени стилове** + +Параметрите `date` и `time` определят колко подробно да се изведат датата и часът. Можете да избирате от няколко нива: `full`, `long`, `medium`, `short`. Може да се изведе само датата, само часът или и двете: + +| locale: | bg_BG | en_US +|--- +| `date: short` | 23.01.78 г. | 1/23/78 +| `date: medium` | 23.01.1978 г. | Jan 23, 1978 +| `date: long` | 23 януари 1978 г. | January 23, 1978 +| `date: full` | понеделник, 23 януари 1978 г. | Monday, January 23, 1978 +| `time: short` | 8:30 | 8:30 AM +| `time: medium` | 8:30:59 | 8:30:59 AM +| `time: long` | 8:30:59 Гринуич+1 | 8:30:59 AM GMT+1 +| `date: short, time: short` | 23.01.78 г., 8:30 | 1/23/78, 8:30 AM +| `date: medium, time: short` | 23.01.1978 г., 8:30 | Jan 23, 1978, 8:30 AM +| `date: long, time: short` | 23 януари 1978 г. в 8:30 | January 23, 1978 at 8:30 AM + +При датата може допълнително да се използва префикс `relative-` (напр. `relative-short`), който за дати, близки до настоящия момент, ще покаже `вчера`, `днес` или `утре`, иначе ще се изведе по стандартния начин. + +```latte +{$date|localDate: date: relative-short} {* вчера *} +``` + +Вижте също [#date]. + + lower .[filter] --------------- -Преобразува стойността в малки букви. Изисква PHP разширение `mbstring`. +Преобразува низ в малки букви. Изисква PHP разширението `mbstring`. ```latte -{='LATTE'|lower} {* outputs 'latte' *} +{='LATTE'|lower} {* извежда 'latte' *} ``` -Вижте също [capitalize |#capitalize], [firstUpper |#firstUpper], [upper |#upper]. +Вижте също [#capitalize], [#firstUpper], [#upper]. nocheck .[filter] ----------------- -Предотвратява автоматичното почистване на URL адреси. Latte [автоматично проверява |safety-first#Link checking] дали променливата съдържа уеб URL адрес (т.е. протокол HTTP/HTTPS) и предотвратява записването на връзки, които могат да представляват риск за сигурността. +Предотвратява автоматичната обработка на URL адреса. Latte [автоматично проверява |safety-first#Проверка на връзки], дали променливата съдържа уеб URL (т.е. протокол HTTP/HTTPS) и предотвратява извеждането на връзки, които могат да представляват риск за сигурността. -Ако връзката използва друга схема, например `javascript:` или `data:`, и сте сигурни в нейното съдържание, можете да деактивирате проверката чрез `|nocheck`. +Ако връзката използва друга схема, напр. `javascript:` или `data:`, и сте сигурни в съдържанието й, можете да изключите проверката с помощта на `|nocheck`. ```latte {var $link = 'javascript:window.close()'} -<a href="{$link}">checked</a> -<a href="{$link|nocheck}">unchecked</a> +<a href={$link}>контролирано</a> +<a href={$link|nocheck}>неконтролирано</a> ``` -Отпечатъци: +Извежда: ```latte -<a href="">checked</a> -<a href="javascript:window.close()">unchecked</a> +<a href="">контролирано</a> +<a href="javascript:window.close()">неконтролирано</a> ``` -Вижте също [checkUrl |#checkUrl]. +Вижте също [#checkUrl]. noescape .[filter] ------------------ -Деактивира автоматичното ескапиране. +Забранява автоматичното екраниране. ```latte {var $trustedHtmlString = '<b>hello</b>'} -Escaped: {$trustedHtmlString} -Unescaped: {$trustedHtmlString|noescape} +Екранирано: {$trustedHtmlString} +Неекранирано: {$trustedHtmlString|noescape} ``` -Отпечатва: +Извежда: ```latte -Escaped: <b>hello</b> -Unescaped: <b>hello</b> +Екранирано: <b>hello</b> +Неекранирано: <b>hello</b> ``` .[warning] -Злоупотребата с филтъра `noescape` може да доведе до уязвимост XSS! Никога не го използвайте, освен ако не сте **напълно сигурни** какво правите и че низът, който отпечатвате, идва от надежден източник. +Неправилното използване на филтъра `noescape` може да доведе до уязвимост XSS! Никога не го използвайте, ако не сте **напълно сигурни** какво правите и че извежданият низ идва от надежден източник. -number(int decimals = 0, string decPoint = '.', string thousandsSep = ',') .[filter] ------------------------------------------------------------------------------------- -Форматира число до зададен брой знаци след десетичната запетая. Можете също така да зададете символ на десетичната запетая и разделителя за хиляди. +number(int $decimals=0, string $decPoint='.', string $thousandsSep=',') .[filter] +--------------------------------------------------------------------------------- +Форматира число до определен брой десетични знаци. Ако е зададена [локализация |develop#Locale], ще се използват съответните разделители за десетични знаци и хиляди. ```latte -{1234.20 |number} 1,234 -{1234.20 |number:1} 1,234.2 -{1234.20 |number:2} 1,234.20 -{1234.20 |number:2, ',', ' '} 1 234,20 +{1234.20|number} {* 1,234 *} +{1234.20|number:1} {* 1,234.2 *} +{1234.20|number:2} {* 1,234.20 *} +{1234.20|number:2, ',', ' '} {* 1 234,20 *} ``` -padLeft(int length, string pad = ' ') .[filter] +number(string $format) .[filter] +-------------------------------- +Параметърът `format` позволява да дефинирате вида на числата точно според вашите нужди. За това е необходимо да имате настроена [локализация |develop#Locale]. Форматът се състои от няколко специални знака, чието пълно описание ще намерите в документацията "DecimalFormat":https://unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns: + +- `0` задължителна цифра, винаги се показва, дори ако е нула +- `#` незадължителна цифра, показва се само ако на това място числото действително съществува +- `@` значеща цифра, помага да се покаже число с определен брой значещи цифри +- `.` показва къде трябва да бъде десетичната запетая (или точка, според държавата) +- `,` служи за разделяне на групи цифри, най-често хиляди +- `%` числото се умножава по 100× и се добавя знак за процент + +Нека разгледаме примери. В първия пример два десетични знака са задължителни, във втория - незадължителни. Третият пример показва допълване с нули отляво и отдясно, четвъртият показва само съществуващите цифри: + +```latte +{1234.5|number: '#,##0.00'} {* 1,234.50 *} +{1234.5|number: '#,##0.##'} {* 1,234.5 *} +{1.23 |number: '000.000'} {* 001.230 *} +{1.2 |number: '##.##'} {* 1.2 *} +``` + +Значещите цифри определят колко цифри, независимо от десетичната запетая, трябва да бъдат показани, като се закръгля: + +```latte +{1234|number: '@@'} {* 1200 *} +{1234|number: '@@@'} {* 1230 *} +{1234|number: '@@@#'} {* 1234 *} +{1.2345|number: '@@@'} {* 1.23 *} +{0.00123|number: '@@'} {* 0.0012 *} +``` + +Лесен начин да покажете число като процент. Числото се умножава по 100× и се добавя знак `%`: + +```latte +{0.1234|number: '#.##%'} {* 12.34% *} +``` + +Можем да дефинираме различен формат за положителни и отрицателни числа, разделени със знака `;`. По този начин може например да се настрои положителните числа да се показват със знак `+`: + +```latte +{42|number: '#.##;(#.##)'} {* 42 *} +{-42|number: '#.##;(#.##)'} {* (42) *} +{42|number: '+#.##;-#.##'} {* +42 *} +{-42|number: '+#.##;-#.##'} {* -42 *} +``` + +Помнете, че действителният вид на числата може да се различава според настройките на държавата. Например в някои държави се използва запетая вместо точка като разделител на десетичните знаци. Този филтър автоматично взема това предвид и не е нужно да се притеснявате за нищо. + + +padLeft(int $length, string $pad=' ') .[filter] ----------------------------------------------- -Попълва низ с определена дължина с друг низ отляво. +Допълва низ до определена дължина с друг низ отляво. ```latte -{='hello'|padLeft: 10, '123'} {* outputs '12312hello' *} +{='hello'|padLeft: 10, '123'} {* извежда '12312hello' *} ``` -padRight(int length, string pad = ' ') .[filter] +padRight(int $length, string $pad=' ') .[filter] ------------------------------------------------ -Подвежда низ до определена дължина с друг низ отдясно. +Допълва низ до определена дължина с друг низ отдясно. ```latte -{='hello'|padRight: 10, '123'} {* outputs 'hello12312' *} +{='hello'|padRight: 10, '123'} {* извежда 'hello12312' *} ``` -query .[filter]{data-version:2.10} ------------------------------------ -Динамично генерира низ за заявка в URL адреса: +query .[filter] +--------------- +Динамично генерира query string в URL: ```latte <a href="http://example.com/?{[name: 'John Doe', age: 43]|query}">click</a> <a href="http://example.com/?search={$search|query}">search</a> ``` -Отпечатва: +Извежда: ```latte <a href="http://example.com/?name=John+Doe&age=43">click</a> <a href="http://example.com/?search=Foo+Bar">search</a> ``` -Ключовете със стойност `null` се изпускат. +Ключове със стойност `null` се пропускат. -Вижте също [escapeUrl |#escapeUrl]. +Вижте също [#escapeUrl]. -random .[filter]{data-version:2.10.2} -------------------------------------- -Връща произволен елемент от масив или символ от низ: +random .[filter] +---------------- +Връща случаен елемент от масив или знак от низ: ```latte -{=[1, 2, 3, 4]|random} {* example output: 3 *} -{='abcd'|random} {* example output: 'b' *} +{=[1, 2, 3, 4]|random} {* извежда напр.: 3 *} +{='abcd'|random} {* извежда напр.: 'b' *} ``` -Вижте също [first |#first], [last |#last]. +Вижте също [#first], [#last]. -repeat(int count) .[filter] ---------------------------- -Повтаря символния низ х пъти. +repeat(int $count) .[filter] +---------------------------- +Повтаря низ x пъти. ```latte -{='hello'|repeat: 3} {* outputs 'hellohellohello' *} +{='hello'|repeat: 3} {* извежда 'hellohellohello' *} ``` -replace(string|array search, string replace = '') .[filter] +replace(string|array $search, string $replace='') .[filter] ----------------------------------------------------------- -Заменя всички срещания на търсения низ със заместващия низ. +Заменя всички срещания на търсения низ със заместващ низ. ```latte -{='hello world'|replace: 'world', 'friend'} {* outputs 'hello friend' *} +{='hello world'|replace: 'world', 'friend'} {* извежда 'hello friend' *} ``` -Могат да бъдат извършени няколко замени едновременно: .{data-version:2.10.2} +Могат да се извършат и няколко замени едновременно: ```latte -{='hello world'|replace: [h => l, l => h]} {* outputs 'lehho worhd' *} +{='hello world'|replace: [h => l, l => h]} {* извежда 'lehho worhd' *} ``` -replaceRE(string pattern, string replace = '') .[filter] +replaceRE(string $pattern, string $replace='') .[filter] -------------------------------------------------------- -Заменя всички срещания според регулярен израз. +Извършва търсене с регулярни изрази със замяна. ```latte -{='hello world'|replaceRE: '/l.*/', 'l'} {* outputs 'hel' *} +{='hello world'|replaceRE: '/l.*/', 'l'} {* извежда 'hel' *} ``` reverse .[filter] ----------------- -Обръща даден низ или масив. +Обръща дадения низ или масив. ```latte {var $s = 'Nette'} -{$s|reverse} {* outputs 'etteN' *} +{$s|reverse} {* извежда 'etteN' *} {var $a = ['N', 'e', 't', 't', 'e']} -{$a|reverse} {* returns ['e', 't', 't', 'e', 'N'] *} +{$a|reverse} {* връща ['e', 't', 't', 'e', 'N'] *} ``` -round(int precision = 0) .[filter] ----------------------------------- -Закръгляне на число до зададена точност. +round(int $precision=0) .[filter] +--------------------------------- +Закръгля число до дадена точност. ```latte -{=3.4|round} {* outputs 3 *} -{=3.5|round} {* outputs 4 *} -{=135.79|round:1} {* outputs 135.8 *} -{=135.79|round:3} {* outputs 135.79 *} +{=3.4|round} {* извежда 3 *} +{=3.5|round} {* извежда 4 *} +{=135.79|round:1} {* извежда 135.8 *} +{=135.79|round:3} {* извежда 135.79 *} ``` -Вижте също [таван, |#ceil] [етаж |#floor]. +Вижте също [#ceil], [#floor]. -slice(int start, int length = null, bool preserveKeys = false) .[filter]{data-version:2.10.2} ---------------------------------------------------------------------------------------------- +slice(int $start, ?int $length=null, bool $preserveKeys=false) .[filter] +------------------------------------------------------------------------ Извлича част от масив или низ. ```latte -{='hello'|slice: 1, 2} {* outputs 'el' *} -{=['a', 'b', 'c']|slice: 1, 2} {* outputs ['b', 'c'] *} +{='hello'|slice: 1, 2} {* извежда 'el' *} +{=['a', 'b', 'c']|slice: 1, 2} {* извежда ['b', 'c'] *} ``` -Филтърът за парчета работи като функцията на PHP `array_slice` за масиви и `mb_substr` за низове с отпадане на `iconv_substr` в режим UTF-8. +Филтърът работи като PHP функцията `array_slice` за масиви или `mb_substr` за низове с резервен вариант към функцията `iconv_substr` в режим UTF‑8. -Ако началото е неотрицателно, последователността ще започне от това начало в променливата. Ако стартът е отрицателен, последователността ще започне на това разстояние от края на променливата. +Ако `$start` е положителен, последователността ще започне изместена с този брой от началото на масива/низа. Ако е отрицателен, последователността ще започне изместена с толкова от края. -Ако е зададена дължина и тя е положителна, последователността ще има до толкова елемента в нея. Ако променливата е по-къса от дължината, тогава ще има само наличните елементи на променливата. Ако е зададена дължина и тя е отрицателна, последователността ще спре на толкова елемента от края на променливата. Ако тя е пропусната, последователността ще съдържа всичко от отместването до края на променливата. +Ако е зададен параметър `$length` и е положителен, последователността ще съдържа толкова елементи. Ако в тази функция се предаде отрицателен параметър `$length`, последователността ще съдържа всички елементи на оригиналния масив, започвайки от позиция `$start` и завършвайки на позиция, по-малка с `$length` елементи от края на масива. Ако не зададете този параметър, последователността ще съдържа всички елементи на оригиналния масив, започвайки от позиция `$start`. -По подразбиране Filter ще пренареди и нулира ключовете на целочислените масиви. Това поведение може да бъде променено чрез задаване на стойност preserveKeys на true. Ключовете на низове винаги се запазват, независимо от този параметър. +По подразбиране филтърът променя реда и нулира целочислените ключове на масива. Това поведение може да се промени, като се зададе `$preserveKeys` на `true`. Низовите ключове винаги се запазват, независимо от този параметър. -sort .[filter]{data-version:2.9} ---------------------------------- -Филтър, който сортира масив и поддържа асоциация на индексите. +sort(?Closure $comparison, string|int|\Closure|null $by=null, string|int|\Closure|bool $byKey=false) .[filter] +-------------------------------------------------------------------------------------------------------------- +Филтърът сортира елементите на масив или итератор и запазва техните асоциативни ключове. При зададена [локализация |develop#Locale] сортирането се ръководи от нейните правила, освен ако не е специфицирана собствена функция за сравнение. ```latte {foreach ($names|sort) as $name} @@ -601,7 +735,7 @@ sort .[filter]{data-version:2.9} {/foreach} ``` -Масивът се подрежда в обратен ред. +Сортиран масив в обратен ред: ```latte {foreach ($names|sort|reverse) as $name} @@ -609,16 +743,42 @@ sort .[filter]{data-version:2.9} {/foreach} ``` -Можете да подадете своя собствена функция за сравнение като параметър: .{data-version:2.10.2} +Можете да специфицирате собствена функция за сравнение за сортиране (примерът показва как да обърнете сортирането от най-голямо към най-малко): ```latte -{var $sorted = ($names|sort: fn($a, $b) => $b <=> $a)} +{var $reverted = ($names|sort: fn($a, $b) => $b <=> $a)} ``` +Филтърът `|sort` също позволява сортиране на елементи по ключове: -spaceless .[filter]{data-version:2.10.2} ------------------------------------------ -Премахва ненужните бели полета от изхода. Можете също така да използвате псевдонима `strip`. +```latte +{foreach ($names|sort: byKey: true) as $name} + ... +{/foreach} +``` + +Ако трябва да сортирате таблица по конкретна колона, можете да използвате параметъра `by`. Стойността `'name'` в примера указва, че ще се сортира по `$item->name` или `$item['name']`, в зависимост от това дали `$item` е масив или обект: + +```latte +{foreach ($items|sort: by: 'name') as $item} + {$item->name} +{/foreach} +``` + +Можете също да дефинирате callback функция, която да определи стойността, по която да се сортира: + +```latte +{foreach ($items|sort: by: fn($items) => $items->category->name) as $item} + {$item->name} +{/foreach} +``` + +По същия начин може да се използва и параметърът `byKey`. + + +spaceless .[filter] +------------------- +Премахва излишното празно пространство (интервали) от изхода. Можете също да използвате псевдонима `strip`. ```latte {block |spaceless} @@ -628,7 +788,7 @@ spaceless .[filter]{data-version:2.10.2} {/block} ``` -Отпечатва: +Извежда: ```latte <ul> <li>Hello</li> </ul> @@ -637,47 +797,47 @@ spaceless .[filter]{data-version:2.10.2} stripHtml .[filter] ------------------- -Конвертира HTML в обикновен текст. Това означава, че премахва HTML таговете и преобразува HTML единиците в текст. +Преобразува HTML в чист текст. Тоест премахва от него HTML таговете и преобразува HTML ентитите в текст. ```latte -{='<p>one < two</p>'|stripHtml} {* outputs 'one < two' *} +{='<p>one < two</p>'|stripHtml} {* извежда 'one < two' *} ``` -Полученият обикновен текст естествено може да съдържа символи, които представляват HTML тагове, например `'<p>'|stripHtml` се преобразува в `<p>`. Никога не извеждайте получения текст с `|noescape`, тъй като това може да доведе до уязвимост на сигурността. +Полученият чист текст може естествено да съдържа знаци, които представляват HTML тагове, например `'<p>'|stripHtml` се преобразува в `<p>`. В никакъв случай не извеждайте така получения текст с `|noescape`, защото това може да доведе до създаване на дупка в сигурността. -substr(int offset, int length = null) .[filter] ------------------------------------------------ -Извлича част от низ. Този филтър е заменен с филтър за [парчета |#slice]. +substr(int $offset, ?int $length=null) .[filter] +------------------------------------------------ +Извлича част от низ. Този филтър е заменен с филтъра [#slice]. ```latte {$string|substr: 1, 2} ``` -translate(string message, ...args) .[filter]{data-version:3.0} --------------------------------------------------------------- -Той превежда изрази на други езици. За да направите филтъра достъпен, трябва да настроите [преводач |develop#TranslatorExtension]. Можете също така да използвате [таговете за превод |tags#Translation]. +translate(...$args) .[filter] +----------------------------- +Превежда изрази на други езици. За да бъде филтърът наличен, е необходимо да [настроите преводач |develop#TranslatorExtension]. Можете също да използвате [тагове за превод |tags#Преводи]. ```latte -<a href="basket">{='Baskter'|translate}</a> +<a href="basket">{='Кошница'|translate}</a> <span>{$item|translate}</span> ``` -trim(string charlist = " \t\n\r\0\x0B\u{A0}") .[filter] -------------------------------------------------------- -Премахване на водещи и завършващи символи, по подразбиране бяло пространство. +trim(string $charlist=" \t\n\r\0\x0B\u{A0}") .[filter] +------------------------------------------------------ +Премахва празни знаци (или други знаци) от началото и края на низа. ```latte -{=' I like Latte. '|trim} {* outputs 'I like Latte.' *} -{=' I like Latte.'|trim: '.'} {* outputs ' I like Latte' *} +{=' I like Latte. '|trim} {* извежда 'I like Latte.' *} +{=' I like Latte.'|trim: '.'} {* извежда ' I like Latte' *} ``` -truncate(int length, string append = '…') .[filter] +truncate(int $length, string $append='…') .[filter] --------------------------------------------------- -Съкращава даден низ до максимално зададената дължина, но се опитва да запази цели думи. Ако низът е съкратен, той добавя елипса в края (това може да се промени с втория параметър). +Подрязва низ до посочената максимална дължина, като се опитва да запази цели думи. Ако низът бъде скъсен, накрая добавя три точки (може да се промени с втория параметър). ```latte {var $title = 'Hello, how are you?'} @@ -689,25 +849,25 @@ truncate(int length, string append = '…') .[filter] upper .[filter] --------------- -Преобразува стойността в главни букви. Изисква PHP разширение `mbstring`. +Преобразува низ в главни букви. Изисква PHP разширението `mbstring`. ```latte -{='latte'|upper} {* outputs 'LATTE' *} +{='latte'|upper} {* извежда 'LATTE' *} ``` -Вижте също [capitalize |#capitalize], [firstUpper |#firstUpper], [lower |#lower]. +Вижте също [#capitalize], [#firstUpper], [#lower]. webalize .[filter] ------------------ -Преобразува в ASCII. +Модифицира UTF‑8 низ във форма, използвана в URL. -Превръща интервалите в тирета. Премахва знаци, които не са буквено-цифрови, подчертаващи или дефисни. Преобразува в малки букви. Също така премахва началните и крайните бели полета. +Преобразува се в ASCII. Преобразува интервалите в тирета. Премахва знаци, които не са буквено-цифрови, долни черти или тирета. Преобразува в малки букви. Също така премахва начални и крайни интервали. ```latte -{var $s = 'Our 10. product'} -{$s|webalize} {* outputs 'our-10-product' *} +{var $s = 'Нашият 10-ти продукт'} +{$s|webalize} {* извежда 'nashiyat-10-ti-produkt' *} ``` .[caution] -Изисква пакет [nette/utils |utils:]. +Изисква библиотеката [nette/utils|utils:]. diff --git a/latte/bg/functions.texy b/latte/bg/functions.texy index faa74ba3df..aff39d32f7 100644 --- a/latte/bg/functions.texy +++ b/latte/bg/functions.texy @@ -1,23 +1,25 @@ -Функции на Latte -**************** +Latte функции +************* .[perex] -В допълнение към обичайните функции на PHP можете да ги използвате и в шаблони. +В шаблоните, освен обикновените PHP функции, можем да използваме и следните допълнителни функции. .[table-latte-filters] -| `clamp` | [притискане на стойност в диапазон |#clamp] -| `divisibleBy`| [проверява дали дадена променлива се дели на дадено число |#divisibleBy] -| `even` | [проверява дали дадено число е четно число|#even] -| `first` | [връща първия елемент на масив или символен низ |#first] -| `last` | [връща последния елемент на масив или символен низ|#last] -| `odd` | [проверява дали числото е нечетно |#odd] -| `slice` | [извлича фрагмент от масив или низ |#slice] +| `clamp` | [ограничава стойността в даден диапазон |#clamp] +| `divisibleBy`| [проверява дали променливата се дели на число |#divisibleBy] +| `even` | [проверява дали даденото число е четно |#even] +| `first` | [връща първия елемент на масив или знак от низ |#first] +| `group` | [групира данни по различни критерии |#group] +| `hasBlock` | [проверява съществуването на блок |#hasBlock] +| `last` | [връща последния елемент на масив или знак от низ |#last] +| `odd` | [проверява дали даденото число е нечетно |#odd] +| `slice` | [извлича част от масив или низ |#slice] -използване на .[#toc-usage] -=========================== +Използване +========== -Функциите се използват по същия начин, както обикновените функции на PHP, и могат да се използват във всички изрази: +Функциите се използват по същия начин като обикновените PHP функции и могат да се използват във всички изрази: ```latte <p>{clamp($num, 1, 100)}</p> @@ -25,14 +27,14 @@ {if odd($num)} ... {/if} ``` -По този начин могат да се регистрират [потребителски функции |extending-latte#Functions]: +[Потребителски функции|custom-functions] могат да се регистрират по следния начин: ```php $latte = new Latte\Engine; $latte->addFunction('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -Използваме го в шаблона по следния начин +В шаблона след това се извиква така: ```latte <p>{shortify($text)}</p> @@ -40,85 +42,115 @@ $latte->addFunction('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, ``` -Функции .[#toc-functions] -========================= +Функции +======= -clamp(int|float $value, int|float $min, int|float $max): int|float .[method]{data-version:2.9} ----------------------------------------------------------------------------------------------- -Връща стойността, притисната в обхвата min и max. +clamp(int|float $value, int|float $min, int|float $max): int|float .[method] +---------------------------------------------------------------------------- +Ограничава стойността в дадения инклузивен диапазон min и max. ```latte {=clamp($level, 0, 255)} ``` -Вижте също [филтърна скоба |filters#clamp]: +Вижте също [филтър clamp |filters#clamp]. -divisibleBy(int $value, int $by): bool .[method]{data-version:2.10.2} ---------------------------------------------------------------------- -Проверява дали дадена променлива е делима на число. +divisibleBy(int $value, int $by): bool .[method] +------------------------------------------------ +Проверява дали променливата се дели на число. ```latte {if divisibleBy($num, 5)} ... {/if} ``` -even(int $value): bool .[method]{data-version:2.10.2} ------------------------------------------------------ -Проверява дали дадено число е четно число. +even(int $value): bool .[method] +-------------------------------- +Проверява дали даденото число е четно. ```latte {if even($num)} ... {/if} ``` -first(string|array $value): mixed .[method]{data-version:2.10.2} ----------------------------------------------------------------- -Връща първия елемент на масив или символен низ: +first(string|iterable $value): mixed .[method] +---------------------------------------------- +Връща първия елемент на масив или знак от низ: ```latte -{=first([1, 2, 3, 4])} {* списъци 1 *} -{=first('abcd')} {* списъци 'a' *} +{=first([1, 2, 3, 4])} {* извежда 1 *} +{=first('abcd')} {* извежда 'a' *} ``` -Вижте също [последно |#last], [първо филтриране |filters#first]. +Вижте също [#last], [филтър first |filters#first]. -last(string|array $value): mixed .[method]{data-version:2.10.2} ---------------------------------------------------------------- -Връща последния елемент на масив или символен низ: +group(iterable $data, string|int|\Closure $by): array .[method]{data-version:3.0.16} +------------------------------------------------------------------------------------ +Функцията групира данни по различни критерии. + +В този пример редовете в таблицата се групират по колона `categoryId`. Изходът е масив от масиви, където ключът е стойността в колоната `categoryId`. [Прочетете подробно ръководство|cookbook/grouping]. + +```latte +{foreach group($items, categoryId) as $categoryId => $categoryItems} + <ul> + {foreach $categoryItems as $item} + <li>{$item->name}</li> + {/foreach} + </ul> +{/foreach} +``` + +Вижте също филтъра [group |filters#group]. + + +hasBlock(string $name): bool .[method]{data-version:3.0.10} +----------------------------------------------------------- +Проверява дали блок с посоченото име съществува: + +```latte +{if hasBlock(header)} ... {/if} +``` + +Вижте също [проверка за съществуване на блокове |template-inheritance#Проверка за съществуване на блокове]. + + +last(string|array $value): mixed .[method] +------------------------------------------ +Връща последния елемент на масив или знак от низ: ```latte -{=last([1, 2, 3, 4])} {* списъци 4 *} -{=last('abcd')} {* списъци 'd' *} +{=last([1, 2, 3, 4])} {* извежда 4 *} +{=last('abcd')} {* извежда 'd' *} ``` -Вижте също [първи |#first], [последен филтър |filters#last]. +Вижте също [#first], [филтър last |filters#last]. -odd(int $value): bool .[method]{data-version:2.10.2} ----------------------------------------------------- -Проверява дали зададеното число е нечетно. +odd(int $value): bool .[method] +------------------------------- +Проверява дали даденото число е нечетно. ```latte {if odd($num)} ... {/if} ``` -slice(string|array $value, int $start, int $length=null, bool $preserveKeys=false): string|array .[method]{data-version:2.10.2} -------------------------------------------------------------------------------------------------------------------------------- -Извлича фрагмент от масив или низ. +slice(string|array $value, int $start, ?int $length=null, bool $preserveKeys=false): string|array .[method] +----------------------------------------------------------------------------------------------------------- +Извлича част от масив или низ. ```latte -{=slice('hello', 1, 2)} {* списъци 'el' *} -{=slice(['a', 'b', 'c'], 1, 2)} {* списъци ['b', 'c'] *} +{=slice('hello', 1, 2)} {* извежда 'el' *} +{=slice(['a', 'b', 'c'], 1, 2)} {* извежда ['b', 'c'] *} ``` -Филтърът за парчета работи като функцията на PHP `array_slice` за масиви и `mb_substr` за низове, като се връща към `iconv_substr` в режим UTF-8. +Функцията работи като PHP функцията `array_slice` за масиви или `mb_substr` за низове с резервен вариант към функцията `iconv_substr` в режим UTF‑8. -Ако start е неотрицателно число, последователността ще започне от това начало в променлива. Ако началната стойност е отрицателна, последователността ще започне на това разстояние от края на променливата. +Ако `$start` е положителен, последователността ще започне изместена с този брой от началото на масива/низа. Ако е отрицателен, последователността ще започне изместена с толкова от края. -Ако е зададена дължина и тя е положителна, последователността ще съдържа до този брой елементи. Ако променливата е по-къса от дължината, ще бъдат представени само наличните елементи на променливата. Ако е зададена дължина и тя е отрицателна, последователността ще спре на толкова елемента от края на променливата. Ако не е зададена дължина, последователността ще съдържа всички елементи от отместването до края на променливата. +Ако е зададен параметър `$length` и е положителен, последователността ще съдържа толкова елементи. Ако в тази функция се предаде отрицателен параметър `$length`, последователността ще съдържа всички елементи на оригиналния масив, започвайки от позиция `$start` и завършвайки на позиция, по-малка с `$length` елементи от края на масива. Ако не зададете този параметър, последователността ще съдържа всички елементи на оригиналния масив, започвайки от позиция `$start`. -Филтърът по подразбиране пренарежда и нулира ключовете на масив от цели числа. Това поведение може да бъде променено чрез задаване на preserveKeys на true. Ключовете на низове винаги се запазват, независимо от този параметър. +По подразбиране функцията променя реда и нулира целочислените ключове на масива. Това поведение може да се промени, като се зададе `$preserveKeys` на `true`. Низовите ключове винаги се запазват, независимо от този параметър. diff --git a/latte/bg/guide.texy b/latte/bg/guide.texy index eac362a6b9..29de4ffdcd 100644 --- a/latte/bg/guide.texy +++ b/latte/bg/guide.texy @@ -1,46 +1,45 @@ -Започване на работа с Latte -*************************** +Първи стъпки с Latte +******************** <div class=perex> -Шаблоните подобряват организацията на кода, отделят логиката на приложението от представянето и повишават сигурността. Те предлагат много по-добри функции и изразни възможности за генериране на HTML от самия PHP. +Шаблоните подобряват организацията на кода, разделят логиката на приложението от представянето и повишават сигурността. Те предлагат много по-добри функции и изразни средства за генериране на HTML от самото PHP. -Latte е най-сигурната система за шаблониране за PHP. Ще харесате нейния интуитивен синтаксис. Широкият набор от полезни функции значително ще опрости работата ви. -Тя осигурява първокласна защита срещу [критични уязвимости |safety-first] и ви позволява да се съсредоточите върху създаването на висококачествени приложения, без да се притеснявате за тяхната сигурност. +Latte е най-сигурната система за шаблони за PHP. Ще се влюбите в интуитивния й синтаксис. Широката гама от полезни функции значително ще улесни работата ви. Предоставя върхова защита срещу [критични уязвимости|safety-first] и ви позволява да се съсредоточите върху създаването на качествени приложения без притеснения за тяхната сигурност. -Как се пишат шаблони с помощта на Latte? .[#toc-how-to-write-templates-using-latte] ------------------------------------------------------------------------------------ +Как да пишем шаблони с Latte? +----------------------------- -Latte е умело проектиран и лесен за усвояване от запознатите с PHP, тъй като те могат бързо да усвоят основните му тагове. +Latte е умно проектиран и лесен за научаване от тези, които познават PHP и усвоят основните тагове. -- Първо се запознайте със [синтаксиса на Latte |syntax] и [опитайте всичко онлайн |https://fiddle.nette.org/latte/] -- Разгледайте основния набор от [тагове |tags] и [филтри |filters] -- Напишете шаблони в [редактор с поддръжка на Latte |recipes#Editors and IDE] +- Първо се запознайте със [синтаксиса на Latte|syntax] и [ИЗПРОБВАЙТЕ ГО ОНЛАЙН |https://fiddle.nette.org/latte/#9cc0cf6d89#9cc0cf6d89] +- Разгледайте основния набор от [тагове|tags] и [филтри|filters] +- Пишете шаблони в [редактор с поддръжка на Latte |recipes#Редактори и IDE] -Как да използваме Latte в PHP? .[#toc-how-to-use-latte-in-php] --------------------------------------------------------------- +Как да използваме Latte в PHP? +------------------------------ -Внедряването на Latte в новото ви приложение е въпрос на минути: +Внедряването на Latte във вашето ново приложение е въпрос на няколко минути: -- Първо, [инсталирайте и стартирайте Latte |develop#Installation] -- Поглезете се [с инструмента за отстраняване на грешки на Трейси |develop#Debugging-and-Tracy] -- Разширяване на Latte с [персонализирана функционалност |extending-latte] +- Първо [инсталирайте и стартирайте Latte |develop#Инсталация] +- Позволете си да бъдете поглезени от [инструмента за дебъгване Tracy |develop#Дебъгване и Tracy] +- Разширете Latte със [собствена функционалност |extending-latte] -Ако конвертирате стар проект, написан на обикновен PHP, в Latte, [инструментът за конвертиране на PHP код в Latte |cookbook/migration-from-php] ще улесни миграцията. Или планирате да преминете към Latte от Twig? Имаме за вас инструмент за [конвертиране на шаблони от Twig в Latte |cookbook/migration-from-twig]. +Ако преобразувате стар проект, написан на чист PHP, в Latte, миграцията ще ви улесни [инструмент за преобразуване на PHP код в Latte |cookbook/migration-from-php]. Или се готвите да преминете към Latte от Twig? Имаме за вас [конвертор на шаблони от Twig към Latte |cookbook/migration-from-twig]. -Какво още може да направи Latte? .[#toc-what-else-can-latte-do] ---------------------------------------------------------------- +Какво още може Latte? +--------------------- -Latte е напълно оборудван с всичко необходимо. +Получавате Latte в пълна окомплектовка, с всичко важно в основата. -- Производителността ви се повишава благодарение на [механизмите за наследяване |template-inheritance], които позволяват повторно използване на повтарящи се елементи и структури. -- Защитата на [пясъчната кутия |Sandbox] изолира шаблоните от ненадеждни източници, като например редактирани от потребителя -- За допълнително вдъхновение, ето [съвети и трикове |recipes] +- Вашата продуктивност ще бъде подсилена от [механизми за наследяване |template-inheritance], благодарение на които повтарящите се елементи и структури се използват повторно +- Бронираният бункер [sandbox] изолира шаблони от ненадеждни източници, които например се редактират от самите потребители +- За допълнително вдъхновение са тук [съвети и трикове |recipes] </div> -{{description: Latte е най-сигурната система за шаблониране за PHP. Тя предотвратява много уязвимости в сигурността. Ще оцените интуитивния му синтаксис и ще оцените много полезни настройки.}} +{{description: Latte е най-сигурната система за шаблони за PHP. Предотвратява много уязвимости в сигурността. Ще оцените интуитивния му синтаксис и ще оцените много полезни функции.}} diff --git a/latte/bg/loaders.texy b/latte/bg/loaders.texy new file mode 100644 index 0000000000..01ac7a7666 --- /dev/null +++ b/latte/bg/loaders.texy @@ -0,0 +1,198 @@ +Loaders +******* + +.[perex] +Loaders са механизмът, който Latte използва за получаване на изходния код на вашите шаблони. Най-често шаблоните се съхраняват като файлове на диска, но благодарение на гъвкавата система на loaders, можете да ги зареждате практически отвсякъде или дори да ги генерирате динамично. + + +Какво е Loader? +=============== + +Когато работите с шаблони, обикновено си представяте файлове `.latte`, разположени в структурата на директориите на вашия проект. За това се грижи [#FileLoader] по подразбиране в Latte. Връзката между името на шаблона (като `'main.latte'` или `'components/card.latte'`) и неговия действителен изходен код обаче *не е задължително* да бъде директно съпоставяне с път до файл. + +Точно тук влизат в игра loaders. Loader е обект, който има за задача да вземе името на шаблона (идентифициращ низ) и да предостави на Latte неговия изходен код. Latte напълно разчита на конфигурирания loader за тази задача. Това важи не само за първоначалния шаблон, изискан с помощта на `$latte->render('main.latte')`, но и за **всеки шаблон, рефериран вътре** с помощта на тагове като `{include ...}`, `{layout ...}`, `{embed ...}` или `{import ...}`. + +Защо да използвате персонализиран loader? + +- **Зареждане от алтернативни източници:** Получаване на шаблони, съхранени в база данни, в кеш (като Redis или Memcached), в система за управление на версии (като Git, въз основа на конкретен commit) или динамично генерирани. +- **Имплементиране на персонализирани конвенции за именуване:** Може да искате да използвате по-кратки псевдоними за шаблони или да имплементирате специфична логика за пътища за търсене (напр. първо търсене в директорията на темата, след това връщане към директорията по подразбиране). +- **Добавяне на сигурност или контрол на достъпа:** Персонализиран loader може да провери потребителските права преди зареждане на определени шаблони. +- **Предварителна обработка:** Въпреки че това обикновено не се препоръчва ([компилационните проходи |compiler-passes] са по-добри), loader *би* могъл теоретично да извърши предварителна обработка на съдържанието на шаблона, преди да го предаде на Latte. + +Loader за инстанция на `Latte\Engine` се задава с помощта на метода `setLoader()`: + +```php +$latte = new Latte\Engine; + +// Използване на FileLoader по подразбиране за файлове в '/path/to/templates' +$loader = new Latte\Loaders\FileLoader('/path/to/templates'); +$latte->setLoader($loader); +``` + +Loader трябва да имплементира интерфейса `Latte\Loader`. + + +Вградени Loaders +================ + +Latte предлага няколко стандартни loaders: + + +FileLoader +---------- + +Това е **loader-ът по подразбиране**, използван от класа `Latte\Engine`, ако не е указан друг. Той зарежда шаблони директно от файловата система. + +По желание можете да зададете коренна директория за ограничаване на достъпа: + +```php +use Latte\Loaders\FileLoader; + +// Следното ще позволи зареждане на шаблони само от директорията /var/www/html/templates +$loader = new FileLoader('/var/www/html/templates'); +$latte->setLoader($loader); + +// $latte->render('../../../etc/passwd'); // Това би хвърлило изключение + +// Рендиране на шаблон, разположен на /var/www/html/templates/pages/contact.latte +$latte->render('pages/contact.latte'); +``` + +При използване на тагове като `{include}` или `{layout}` решава имената на шаблоните относително спрямо текущия шаблон, ако не е зададен абсолютен път. + + +StringLoader +------------ + +Този loader получава съдържанието на шаблона от асоциативен масив, където ключовете са имената на шаблоните (идентификатори), а стойностите са низове с изходния код на шаблона. Той е особено полезен за тестване или малки приложения, където шаблоните могат да бъдат съхранени директно в PHP кода. + +```php +use Latte\Loaders\StringLoader; + +$loader = new StringLoader([ + 'main.latte' => 'Hello {$name}, include is below:{include helper.latte}', + 'helper.latte' => '{var $x = 10}Included content: {$x}', + // Добавете още шаблони според нуждите +]); + +$latte->setLoader($loader); + +$latte->render('main.latte', ['name' => 'World']); +// Изход: Hello World, include is below:Included content: 10 +``` + +Ако трябва да рендирате само един шаблон директно от низ, без нужда от включване или наследяване, рефериращи към други именувани низови шаблони, можете да предадете низа директно на метода `render()` или `renderToString()`, когато използвате `StringLoader` без масив: + +```php +$loader = new StringLoader; +$latte->setLoader($loader); + +$templateString = 'Hello {$name}!'; +$output = $latte->renderToString($templateString, ['name' => 'Alice']); +// $output съдържа 'Hello Alice!' +``` + + +Създаване на персонализиран Loader +================================== + +За да създадете персонализиран loader (напр. за зареждане на шаблони от база данни, кеш, система за управление на версии или друг източник), трябва да създадете клас, който имплементира интерфейса [api:Latte\Loader]. + +Нека разгледаме какво трябва да прави всеки метод. + + +getContent(string $name): string .[method] +------------------------------------------ +Това е основният метод на loader-а. Неговата задача е да получи и върне пълния изходен код на шаблона, идентифициран чрез `$name` (както е предадено на метода `$latte->render()` или върнато от метода [#getReferredName()]). + +Ако шаблонът не може да бъде намерен или достъпен, този метод **трябва да хвърли изключение `Latte\RuntimeException`**. + +```php +public function getContent(string $name): string +{ + // Пример: Зареждане от хипотетично вътрешно хранилище + $content = $this->storage->read($name); + if ($content === null) { + throw new Latte\RuntimeException("Template '$name' cannot be loaded."); + } + return $content; +} +``` + + +getReferredName(string $name, string $referringName): string .[method] +---------------------------------------------------------------------- +Този метод решава превода на имената на шаблоните, използвани в рамките на тагове като `{include}`, `{layout}` и т.н. Когато Latte срещне например `{include 'partial.latte'}` вътре в `main.latte`, той извиква този метод с `$name = 'partial.latte'` и `$referringName = 'main.latte'`. + +Задачата на метода е да преведе `$name` на каноничен идентификатор (напр. абсолютен път, уникален ключ на база данни), който ще бъде използван при извикване на други методи на loader-а, въз основа на контекста, предоставен в `$referringName`. + +```php +public function getReferredName(string $name, string $referringName): string +{ + return ...; +} +``` + + +getUniqueId(string $name): string .[method] +------------------------------------------- +Latte използва кеш на компилирани шаблони за подобряване на производителността. Всеки компилиран файл на шаблон се нуждае от уникално име, получено от идентификатора на изходния шаблон. Този метод предоставя низ, който **еднозначно идентифицира** шаблона `$name`. + +За шаблони, базирани на файлове, може да послужи абсолютният път. За шаблони в база данни е обичайна комбинация от префикс и ID на базата данни. + +```php +public function getUniqueId(string $name): string +{ + return ...; +} +``` + + +Пример: Прост Loader за база данни +---------------------------------- + +Този пример показва основната структура на loader, който зарежда шаблони, съхранени в таблица на база данни, наречена `templates` с колони `name` (уникален идентификатор), `content` и `updated_at`. + +```php +use Latte; + +class DatabaseLoader implements Latte\Loader +{ + public function __construct( + private \PDO $db, + ) { + } + + public function getContent(string $name): string + { + $stmt = $this->db->prepare('SELECT content FROM templates WHERE name = ?'); + $stmt->execute([$name]); + $content = $stmt->fetchColumn(); + if ($content === false) { + throw new Latte\RuntimeException("Шаблон '$name' не е намерен в базата данни."); + } + return $content; + } + + // Този прост пример предполага, че имената на шаблоните ('homepage', 'article', и т.н.) + // са уникални ID и шаблоните не се реферират един към друг относително. + public function getReferredName(string $name, string $referringName): string + { + return $name; + } + + public function getUniqueId(string $name): string + { + // Използването на префикс и самото име тук е уникално и достатъчно + return 'db_' . $name; + } +} + +// Използване: +$pdo = new \PDO(/* детайли за връзка */); +$loader = new DatabaseLoader($pdo); +$latte->setLoader($loader); +$latte->render('homepage'); // Зарежда шаблон с име 'homepage' от БД +``` + +Персонализираните loaders ви дават пълен контрол върху това откъде идват вашите Latte шаблони, което позволява интеграция с различни системи за съхранение и работни процеси. diff --git a/latte/bg/recipes.texy b/latte/bg/recipes.texy index e0f1445d8c..2c843a386d 100644 --- a/latte/bg/recipes.texy +++ b/latte/bg/recipes.texy @@ -2,44 +2,44 @@ **************** -Редактори и IDE .[#toc-editors-and-ide] -======================================= +Редактори и IDE +=============== -Пишете шаблони в редактор или IDE, който поддържа Latte. Това ще бъде много по-приятно. +Пишете шаблони в редактор или IDE, който има поддръжка за Latte. Ще бъде много по-приятно. -- NetBeans IDE има вградена поддръжка за -- PhpStorm: инсталирайте [приставката Latte |https://plugins.jetbrains.com/plugin/7457-latte] в `Settings > Plugins > Marketplace` -- VS Code: намерете плъгина "Nette Latte + Neon" в маркерплей -- Sublime Text 3: в Управление на пакетите намерете и инсталирайте пакета `Nette` и изберете Latte в `View > Syntax` -- в по-стари редактори използвайте Smarty за подчертаване на .latte файлове +- PhpStorm: инсталирайте в `Settings > Plugins > Marketplace` [плъгин Latte|https://plugins.jetbrains.com/plugin/7457-latte] +- VS Code: инсталирайте [Nette Latte + Neon|https://marketplace.visualstudio.com/items?itemName=Kasik96.latte], [Nette Latte templates|https://marketplace.visualstudio.com/items?itemName=smuuf.latte-lang] или най-новия [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode] плъгин +- NetBeans IDE: нативната поддръжка на Latte е част от инсталацията +- Sublime Text 3: в Package Control намерете и инсталирайте пакета `Nette` и изберете Latte в `View > Syntax` +- в стари редактори използвайте за файлове .latte подчертаване на Smarty -Плъгинът за PhpStorm е много усъвършенстван и може перфектно да предлага PHP код. Използвайте [типизирани шаблони |type-system] за оптимална производителност. +Плъгинът за PhpStorm е много напреднал и може отлично да подсказва PHP код. За да работи оптимално, използвайте [типизирани шаблони|type-system]. [* latte-phpstorm-plugin.webp *] -Поддръжката на Latte може да бъде намерена и в разделителя на уеб код [Prism.js |https://prismjs.com/#supported-languages] и редактора [Ace |https://ace.c9.io]. +Поддръжка за Latte ще намерите също и в уеб подчертавача на код [Prism.js|https://prismjs.com/#supported-languages] и редактора [Ace|https://ace.c9.io]. -Latte в JavaScript или CSS .[#toc-latte-inside-javascript-or-css] -================================================================= +Latte в JavaScript или CSS +========================== -Latte може да се използва много удобно в JavaScript или CSS. Но как можете да избегнете погрешното третиране на кода на JavaScript или стиловете на CSS от Latte като тагове на Latte? +Latte може много удобно да се използва и в JavaScript или CSS. Но как да избегнем ситуация, в която Latte погрешно би счело JavaScript код или CSS стил за Latte таг? ```latte <style> - /* ERROR: interprets as tag {color} */ + /* ГРЕШКА: интерпретира се като таг {color} */ body {color: blue} </style> <script> - // ERROR: interprets as tag {id} + // ГРЕШКА: интерпретира се като таг {id} var obj = {id: 123}; </script> ``` **Вариант 1** -Избягвайте ситуации, в които буква следва непосредствено след `{`, като поставите интервал, прекъсване на реда или обърната запетая между тях: +Избягвайте ситуация, в която след `{` веднага следва буква, например като вмъкнете интервал, нов ред или кавичка преди нея: ```latte <style> @@ -55,7 +55,7 @@ Latte може да се използва много удобно в JavaScript **Вариант 2** -Напълно деактивирайте обработката на маркери Latte в рамките на елемент, като използвате [n:syntax |tags#Syntax]: +Напълно изключете обработката на Latte тагове вътре в елемента с помощта на [n:syntax |tags#syntax]: ```latte <script n:syntax="off"> @@ -65,23 +65,23 @@ Latte може да се използва много удобно в JavaScript **Вариант 3** -Превключете синтаксиса на тага Latte към двойни къдрави скоби вътре в елемента: +Превключете синтаксиса на Latte таговете вътре в елемента на двойни къдрави скоби: ```latte <script n:syntax="double"> - var obj = {id: 123}; // this is JavaScript + var obj = {id: 123}; // това е JavaScript - {{if $cond}} alert(); {{/if}} // this is Latte tag + {{if $cond}} alert(); {{/if}} // това е Latte </script> ``` -В JavaScript [не ограждайте променливата в кавички |tags#Filters]. +В JavaScript [не се пишат кавички около променливата |tags#Извеждане в JavaScript]. -Заместител на `use` .[#toc-replacement-for-use-clause] -====================================================== +Замяна на `use` клауза в Latte +============================== -Как да заменя клаузата `use`, използвана в PHP, така че да не се налага да изписвам пространството от имена, когато се позовавам на класа? Пример за PHP: +Как в Latte да заменим клаузите `use`, които се използват в PHP, за да не се налага да пишем namespace при достъп до клас? Пример в PHP: ```php use Pets\Model\Dog; @@ -93,7 +93,7 @@ if ($dog->status === Dog::StatusHungry) { **Вариант 1** -Вместо `use`, съхранете името на класа в променлива и след това използвайте `$Dog` вместо `Dog`: +Вместо клауза `use`, ще запазим името на класа в променлива и след това вместо `Dog` ще използваме `$Dog`: ```latte {var $Dog = Pets\Model\Dog::class} @@ -107,13 +107,13 @@ if ($dog->status === Dog::StatusHungry) { **Вариант 2** -Ако обектът `$dog` е инстанция на `Pets\Model\Dog`, можете да използвате `{if $dog->status === $dog::StatusHungry}`. +Ако обектът `$dog` е инстанция на `Pets\Model\Dog`, тогава може да се използва `{if $dog->status === $dog::StatusHungry}`. -Генериране на XML в Latte .[#toc-generating-xml-in-latte] -========================================================= +Генериране на XML в Latte +========================= -Latte може да генерира всякакъв текстов формат (HTML, XML, CSV, iCal и др.), но за да изведе правилно показаните данни, трябва да му кажем кой формат генерираме. За тази цел етикетът [`{contentType}` |tags#contentType] се използва за тази цел. +Latte може да генерира всякакъв текстов формат (HTML, XML, CSV, iCal и т.н.), но за да екранира правилно извежданите данни, трябва да му кажем какъв формат генерираме. За това служи тагът [`{contentType}` |tags#contentType]. ```latte {contentType application/xml} @@ -121,7 +121,7 @@ Latte може да генерира всякакъв текстов форма ... ``` -След това например можем да генерираме карта на сайта по подобен начин: +След това можем например да генерираме sitemap по подобен начин: ```latte {contentType application/xml} @@ -137,11 +137,10 @@ Latte може да генерира всякакъв текстов форма ``` -Прехвърляне на данни от включения шаблон .[#toc-passing-data-from-an-included-template] -======================================================================================= +Предаване на данни от включен шаблон +==================================== -Променливите, които създаваме с `{var}` или `{default}` в шаблона за разрешаване, съществуват само в шаблона за разрешаване и не са достъпни в шаблона за разрешаване. -Ако искаме да предадем някакви данни от активирания шаблон обратно към активирания шаблон, една от възможностите е да предадем обект на шаблона и да зададем данните към него. +Променливите, които създаваме с помощта на `{var}` или `{default}` във включения шаблон, съществуват само в него и не са достъпни във включващия шаблон. Ако искаме да предадем данни от включения шаблон обратно към включващия, една от възможностите е да предадем обект на шаблона и да вмъкнем данните в него. Основен шаблон: @@ -158,6 +157,6 @@ Latte може да генерира всякакъв текстов форма Включен шаблон `included.latte`: ```latte -{* запис на данни в имота foo *} +{* записваме данни в свойството foo *} {var $vars->foo = 123} ``` diff --git a/latte/bg/safety-first.texy b/latte/bg/safety-first.texy index 2693ba01f9..56a3b9fd91 100644 --- a/latte/bg/safety-first.texy +++ b/latte/bg/safety-first.texy @@ -1,250 +1,266 @@ -Latte - синоним на сигурност +Latte е синоним на сигурност **************************** <div class=perex> -Latte е единствената система за шаблони на PHP с ефективна защита срещу критичната уязвимост Cross-site Scripting (XSS). Това става благодарение на т.нар. контекстно чувствително ескапиране. Да поговорим, +Latte е единствената система за шаблони за PHP с ефективна защита срещу критичната уязвимост Cross-site Scripting (XSS). И това е благодарение на т.нар. контекстно-чувствително екраниране. Ще си поговорим за: -- как работи уязвимостта XSS и защо е толкова опасна -- какво прави Latte толкова ефективен в защитата срещу XSS -- защо Twig, Blade и други шаблони могат лесно да бъдат компрометирани +- какъв е принципът на уязвимостта XSS и защо е толкова опасна +- защо Latte е толкова ефективен в защитата срещу XSS +- как в шаблоните на Twig, Blade и други подобни може лесно да се направи дупка в сигурността </div> -Пресичане на сайтове (Cross-Site Scripting - XSS) .[#toc-cross-site-scripting-xss] -================================================================================== +Cross-site Scripting (XSS) +========================== -Cross-Site Scripting (съкратено XSS) е една от най-често срещаните уязвимости на уебсайтовете, при това много опасна. Той позволява на нападателя да вмъкне зловреден скрипт (известен още като зловреден софтуер) в чужд сайт, който се стартира в браузъра на нищо неподозиращ потребител. +Cross-site Scripting (съкратено XSS) е една от най-често срещаните уязвимости на уеб страниците и същевременно много опасна. Тя позволява на нападателя да вмъкне в чужда страница зловреден скрипт (т.нар. malware), който се стартира в браузъра на нищо неподозиращия потребител. -Какво може да прави такъв скрипт? Например може да изпрати на нападателя произволно съдържание от компрометиран сайт, включително чувствителни данни, които се показват след влизане в системата. Той може да променя страницата или да прави други заявки от името на потребителя. -Например, ако става въпрос за уебмейл, той може да прочете чувствителни съобщения, да промени показваното съдържание или да промени настройките, като например да позволи препращането на копия на всички съобщения на адреса на нападателя, за да получи достъп до бъдещи имейли. +Какво всичко може да направи такъв скрипт? Може например да изпрати на нападателя всякакво съдържание от нападнатата страница, включително чувствителни данни, показани след влизане. Може да промени страницата или да извършва други заявки от името на потребителя. Ако например става въпрос за уебмейл, може да прочете чувствителни съобщения, да промени показваното съдържание или да пренастрои конфигурацията, напр. да включи препращане на копия на всички съобщения към адреса на нападателя, за да получи достъп и до бъдещи имейли. -Ето защо XSS е на първо място в списъка на най-опасните уязвимости. Ако в даден уебсайт бъде открита уязвимост, тя трябва да бъде отстранена възможно най-скоро, за да се предотврати използването ѝ. +Затова XSS фигурира на водещи места в класациите на най-опасните уязвимости. Ако на уеб страница се появи уязвимост, е необходимо тя да бъде отстранена възможно най-скоро, за да се предотврати злоупотреба. -Как възниква уязвимостта? .[#toc-how-does-the-vulnerability-arise] ------------------------------------------------------------------- +Как възниква уязвимостта? +------------------------- -Грешката се появява, когато се генерира уеб страницата и се отпечатват променливи. Представете си, че генерирате страница за търсене и в началото има параграф с термин за търсене във формата: +Грешката възниква на мястото, където се генерира уеб страницата и се извеждат променливи. Представете си, че създавате страница с търсене, и в началото ще има параграф с търсения израз във вида: ```php -echo '<p>Search results for <em>' . $search . '</em></p>'; +echo '<p>Резултати от търсенето за <em>' . $search . '</em></p>'; ``` -Атакуващият може да напише произволен низ, включително HTML код като `<script>alert("Hacked!")</script>`, в полето за търсене и по този начин в променливата `$search`. Тъй като изходните данни не са обработени по никакъв начин, те стават част от показаната страница: +Нападателят може в полето за търсене и съответно в променливата `$search` да запише произволен низ, т.е. и HTML код като `<script>alert("Hacked!")</script>`. Тъй като изходът не е обработен по никакъв начин, той става част от показаната страница: ```html -<p>Search results for <em><script>alert("Hacked!")</script></em></p> +<p>Резултати от търсенето за <em><script>alert("Hacked!")</script></em></p> ``` -Вместо да показва низ за търсене, браузърът изпълнява JavaScript. По този начин атакуващият превзема страницата. +Браузърът, вместо да изпише търсения низ, стартира JavaScript. И така нападателят поема контрола над страницата. -Някой може да възрази, че поставянето на код в променлива наистина ще изпълни JavaScript, но само в браузъра на нападателя. И така, как се стига до жертвата? От тази гледна точка можем да разграничим няколко вида XSS. В примера с нашата страница за търсене става дума за *отразяващ XSS*. -В този случай жертвата трябва да бъде подмамена да кликне върху връзка, съдържаща зловреден код в параметъра: +Можете да възразите, че вмъкването на код в променлива наистина ще доведе до стартиране на JavaScript, но само в браузъра на нападателя. Как ще стигне до жертвата? От тази гледна точка разграничаваме няколко типа XSS. В нашия пример с търсенето говорим за *reflected XSS*. Тук е необходимо още да се насочи жертвата да кликне върху връзка, която ще съдържа зловреден код в параметъра: ``` https://example.com/?search=<script>alert("Hacked!")</script> ``` -Въпреки че е необходимо известно социално инженерство, за да накарате потребителя да кликне върху връзката, това не е трудно. Потребителите кликват върху линкове, независимо дали в имейли или в социални медии, без да се замислят. А фактът, че в адреса има нещо подозрително, може да бъде прикрит от съкратител на URL адреси, така че потребителят да вижда само `bit.ly/xxx`. +Насочването на потребителя към връзката наистина изисква известно социално инженерство, но не е нищо сложно. Потребителите кликват върху връзки, било то в имейли или в социалните мрежи, без много да мислят. А това, че в адреса има нещо подозрително, може да се маскира с помощта на съкратител на URL, потребителят тогава вижда само `bit.ly/xxx`. -Съществува обаче и втора, много по-опасна форма на атака, известна като *съхранен XSS* или *постоянно действащ XSS*, при която нападателят успява да запази зловреден код на сървъра, така че той да се вмъква автоматично в определени страници. +Въпреки това съществува и втора, много по-опасна форма на атака, наречена *stored XSS* или *persistent XSS*, при която нападателят успява да съхрани зловреден код на сървъра така, че той автоматично да се вмъква в някои страници. -Пример за това са сайтове, в които потребителите оставят коментари. Атакуващият изпраща съобщение, съдържащо кода, и то се съхранява на сървъра. Ако сайтът не е достатъчно сигурен, той ще се стартира в браузъра на всеки посетител. +Пример за това са страниците, където потребителите пишат коментари. Нападателят изпраща публикация, съдържаща код, и той се съхранява на сървъра. Ако страниците не са достатъчно защитени, той ще се стартира в браузъра на всеки посетител. -Изглежда, че целта на атаката е да се `<script>` линия към страницата. Всъщност "има много начини за вграждане на JavaScript:https://cheatsheetseries.owasp.org/cheatsheets/XSS_Filter_Evasion_Cheat_Sheet.html. -Нека разгледаме пример за вграждане с помощта на HTML атрибут. Да предположим, че имаме фотогалерия, в която можем да вмъкнем надпис към изображенията, който се показва в атрибута `alt`: +Може да изглежда, че ядрото на атаката се състои в това да се вкара в страницата низът `<script>`. В действителност "начините за вмъкване на JavaScript са много":https://cheatsheetseries.owasp.org/cheatsheets/XSS_Filter_Evasion_Cheat_Sheet.html. Ще покажем например пример за вмъкване с помощта на HTML атрибут. Нека имаме фотогалерия, където към изображенията може да се добавя описание, което се изписва в атрибута `alt`: ```php echo '<img src="' . $imageFile . '" alt="' . $imageAlt . '">'; ``` -Атакуващият просто трябва да вмъкне хитро конструирания низ `" onload="alert('Hacked!')` като етикет и ако изходът не е обработен, полученият код ще изглежда така +На нападателя е достатъчно като описание да вмъкне умело съставен низ `" onload="alert('Hacked!')` и ако изписването не е обработено, резултатният код ще изглежда така: ```html <img src="photo0145.webp" alt="" onload="alert('Hacked!')"> ``` -Сега подправеният атрибут `onload` става част от страницата. Браузърът ще изпълни съдържащия се в него код веднага след зареждането на изображението. Хакнат! +Част от страницата сега става подправеният атрибут `onload`. Браузърът ще стартира кода, съдържащ се в него, веднага след изтеглянето на изображението. Hacked! -Как да се предпазите от XSS? .[#toc-how-to-defend-against-xss] --------------------------------------------------------------- +Как да се защитим от XSS? +------------------------- -Всеки опит за откриване на атака с помощта на черен списък, като например блокиране на `<script>` низ и т.н. са недостатъчни. Основата на ефективната защита е **последователното отстраняване на всички данни, които се визуализират в рамките на страницата**. +Всякакви опити за откриване на атака с помощта на черен списък, като например блокиране на низа `<script>` и др., са недостатъчни. Основата на функционалната защита е **последователната санитация на всички данни, извеждани вътре в страницата.** -На първо място, това е заместване на всички символи със специално значение с други съответстващи последователности, което на жаргон се нарича **escaping** (първият символ в последователността се нарича escape символ, откъдето идва и името). -Например в текста на HTML се използва символът `<` has a special meaning, which, if it is not to be interpreted as the beginning of a tag, must be replaced by a visually corresponding sequence, the so-called HTML entity `<`. -И браузърът отпечатва символа. +Преди всичко става въпрос за замяна на всички знаци със специално значение с други съответстващи последователности, което разговорно се нарича **екраниране** (първият знак на последователността се нарича екраниращ, оттук и името). Например в HTML текст специално значение има знакът `<`, който, ако не трябва да бъде интерпретиран като начало на таг, трябва да го заменим с визуално съответстваща последователност, т.нар. HTML ентичност `<`. И браузърът ще изпише знак за по-малко. -**Изключително важно е да се разграничи контекстът, в който се отпечатват данните**. Тъй като в различните контексти низовете се почистват по различен начин. Различните знаци имат специално значение в различни контексти. -Например ескапирането на текст в HTML, на атрибути в HTML, на някои специални елементи и т.н. е различно. Скоро ще обсъдим това подробно. +**Много е важно да се разграничава контекстът, в който извеждаме данните**. Защото в различни контексти низовете се санират по различен начин. В различни контексти специално значение имат различни знаци. Например екранирането в HTML текст се различава от това в HTML атрибути, вътре в някои специални елементи и т.н. След малко ще го разгледаме подробно. -Най-добре е да извършвате бягството директно при писане на ред на страницата, за да сте сигурни, че то наистина се случва и че се случва само веднъж. Най-добре е обработката да се извършва **автоматично** директно от системата за шаблони. -Защото ако обработката не се извършва автоматично, програмистът може да забрави за нея. А един пропуск означава, че сайтът е уязвим. +Обработката е най-добре да се извършва директно при изписването на низа в страницата, с което се гарантира, че наистина ще се извърши и ще се извърши точно веднъж. Най-добре е, ако обработката се осигурява **автоматично** директно от системата за шаблони. Защото ако обработката не се извършва автоматично, програмистът може да я забрави. А едно пропускане означава, че уебсайтът е уязвим. -XSS обаче засяга не само изхода на шаблоните, но и други части на приложението, които трябва правилно да обработват ненадеждни данни. Например JavaScript в приложението ви не трябва да използва `innerHTML` в комбинация с тях, а само `innerText` или `textContent`. -Особено внимание трябва да се обърне на функциите, които оценяват низове, като например JavaScript, който е `eval()`, но и `setTimeout()`, или използването на `setAttribute()` с атрибути на събития, като `onload`, и т.н. Но това е извън обхвата на шаблоните. +Въпреки това XSS не се отнася само до извеждането на данни в шаблони, но и до други части на приложението, които трябва правилно да боравят с ненадеждни данни. Например е необходимо JavaScript във вашето приложение да не използва във връзка с тях `innerHTML`, а само `innerText` или `textContent`. Специално внимание трябва да се обръща на функциите, които оценяват низове като JavaScript, което е `eval()`, но също и `setTimeout()`, или използването на функцията `setAttribute()` с атрибути за събития като `onload` и др. Това обаче вече излиза извън областта, която покриват шаблоните. -**идеална защита в 3 точки:** +**Идеалната защита в 3 точки:** 1) разпознава контекста, в който се извеждат данните -2) обработва данните в съответствие с правилата на този контекст (т.е. "ориентиран към контекста"). -3) прави това автоматично +2) санира данните според правилата на дадения контекст (т.е. „контекстно-чувствително“) +3) прави го автоматично -Проверка с отчитане на контекста .[#toc-context-aware-escaping] -=============================================================== +Контекстно-чувствително екраниране +================================== -Какво точно означава думата "контекст"? Това е място в документа със собствени правила за обработка на изхода. Тя зависи от вида на документа (HTML, XML, CSS, JavaScript, обикновен текст, ...) и може да варира в определени части на документа. -Например в един HTML документ има много места (контексти), където се прилагат напълно различни правила. Може би ще се изненадате от броя им. Ето първите четири: +Какво точно се разбира под думата контекст? Това е място в документа със собствени правила за обработка на извежданите данни. Зависи от типа на документа (HTML, XML, CSS, JavaScript, plain text, ...) и може да се различава в конкретните му части. Например в HTML документ има цяла редица такива места (контексти), където важат много различни правила. Може би ще се изненадате колко са. Ето първите четири: ```html -<p>#text</p> -<img src="#attribute"> +<p>#текст</p> +<img src="#атрибут"> <textarea>#rawtext</textarea> -<!-- #comment --> +<!-- #коментар --> ``` -Първоначалният и основен контекст на една HTML страница е HTML текстът. Какви са правилата тук? Специалните знаци `<` and `&` представляват началото на таг или същност, така че те трябва да бъдат премахнати и заменени със същност на HTML (`<` with `<`, `&` with `&`). +Изходният и основен контекст на HTML страницата е HTML текстът. Какви правила важат тук? Специално значение имат знаците `<` и `&`, които представляват начало на таг или ентичност, така че трябва да ги екранираме, като ги заменим с HTML ентичност (`<` с `<` `&` с `&`). -Вторият най-често срещан контекст е стойността на HTML атрибута. Той се различава от текста по това, че тук кавичките `"` or `'`, които отделят атрибута, имат специално значение. Тя трябва да бъде написана като цяло, за да не се възприема като край на атрибута. -От друга страна, символът `<` може спокойно да се използва в атрибута, тъй като няма специално значение; той не може да се възприеме като начало на таг или коментар. -Но имайте предвид, че в HTML можете да записвате стойностите на атрибутите без кавички, като в този случай цял набор от знаци имат специално значение, така че това е друг отделен контекст. +Вторият най-често срещан контекст е стойността на HTML атрибут. Различава се от текста по това, че специално значение тук има кавичката `"` или `'`, която огражда атрибута. Тя трябва да се запише като ентичност, за да не бъде разбирана като край на атрибута. Обратно, в атрибута може безопасно да се използва знакът `<`, защото тук няма никакво специално значение, тук не може да бъде разбиран като начало на таг или коментар. Но внимавайте, в HTML може да се пишат стойности на атрибути и без кавички, в такъв случай специално значение има цяла редица знаци, следователно става въпрос за друг самостоятелен контекст. -Може би ще се изненадате, но вътре в героите `<textarea>` и `<title>` елементи, където `<` character need not (but can) be escaped unless followed by `/`. Но това е по-скоро любопитство. +Може би ще ви изненада, но специални правила важат вътре в елементите `<textarea>` и `<title>`, където знакът `<` не е необходимо (но може) да се екранира, ако след него не следва `/`. Но това е по-скоро любопитен факт. -По-интересно е в HTML коментарите. Тук HTML същностите не се използват за ескапиране. Дори няма спецификация, която да определя как да се изписват коментари. -Просто трябва да спазвате няколко "правила на любопитството":https://html.spec.whatwg.org/multipage/syntax.html#comments и да избягвате определени комбинации от символи в тях. +Интересно е вътре в HTML коментарите. Тук за екраниране не се използват HTML ентичности. Дори никоя спецификация не посочва как трябва да се екранира в коментарите. Само е необходимо да се спазват донякъде "любопитни правила":https://html.spec.whatwg.org/multipage/syntax.html#comments и да се избягват в тях определени комбинации от знаци. -Контекстите могат да бъдат и многопластови, което се случва, когато вграждаме JavaScript или CSS в HTML. Това може да се направи по два различни начина: с елемент или атрибут: +Контекстите също могат да се наслояват, което се случва, когато вмъкнем JavaScript или CSS в HTML. Това може да се направи по два различни начина, с елемент и с атрибут: ```html <script>#js-element</script> -<img onclick="#js-attribute"> +<img onclick="#js-атрибут"> <style>#css-element</style> -<p style="#css-attribute"></p> +<p style="#css-атрибут"></p> ``` -Два начина и два различни вида избягване на данни. В рамките на `<script>` и `<style>` както при HTML коментарите, при HTML същностите няма ескейпване. При ескапирането на данни в тези елементи се прилага само едно правило: текстът не трябва да съдържа последователностите `</script` и `</style`. +Два пътя и два различни начина за екраниране на данни. Вътре в елементите `<script>` и `<style>` точно както в случая с HTML коментарите, екранирането с помощта на HTML ентичности не се извършва. При извеждане на данни вътре в тези елементи е необходимо да се спазва единственото правило: текстът не трябва да съдържа последователността `</script` съответно `</style`. -От друга страна, атрибутите `style` и `on***` се ескапират с HTML същности. +Обратно, в атрибутите `style` и `on***` се екранира с помощта на HTML ентичности. -Разбира се, в рамките на вграден JavaScript или CSS се прилагат правилата за ескапиране на тези езици. Така че низ в атрибут като `onload` първо се ескапира според правилата на JS, а след това според правилата за атрибути на HTML. +И разбира се, вътре във вложения JavaScript или CSS важат правилата за екраниране на тези езици. Така че низ в атрибут напр. `onload` първо се екранира според правилата на JS и след това според правилата на HTML атрибута. -Пфу... Както виждате, HTML е много сложен документ с много контексти и без да знам къде точно показвам данните (т.е. в какъв контекст), е невъзможно да кажа как да го направя правилно. +Уф... Както виждате, HTML е много сложен документ, където се наслояват контексти, и без да осъзнаваме къде точно извеждаме данните (т.е. в какъв контекст), не може да се каже как да го направим правилно. -Искате ли пример? .[#toc-do-you-want-an-example] ------------------------------------------------- +Искате ли пример? +----------------- -Да предположим, че имаме низ `Rock'n'Roll`. +Нека имаме низ `Rock'n'Roll`. -Ако го изведете като HTML текст, няма да се налага да правите никакви замени, тъй като низът не съдържа символи със специално значение. Ситуацията се променя, ако го напишете в атрибут на HTML, заграден в единични кавички. В този случай трябва да избягате от кавичките в единицата HTML: +Ако го извеждате в HTML текст, точно в този случай не е необходимо да правите никакви замени, защото низът не съдържа нито един знак със специално значение. Друга ситуация възниква, ако го изведете вътре в HTML атрибут, ограден с единични кавички. В такъв случай е необходимо да екранирате кавичките в HTML ентичности: ```html <div title='Rock'n'Roll'></div> ``` -Това беше лесно. Много по-интересна ситуация възниква, когато контекстът е многопластов, например ако низът е част от JavaScript. +Това беше просто. Много по-интересна ситуация възниква при наслояване на контексти, например ако низът е част от JavaScript. -Затова първо го записваме в самия JavaScript. Това означава, че го затваряме в кавички и същевременно избягваме кавичките, които съдържа, като използваме символа `\`: +Първо ще го изведем в самия JavaScript. Т.е. ще го обвием в кавички и същевременно ще екранираме с помощта на знака `\` кавичките, съдържащи се в него: ```js 'Rock\'n\'Roll' ``` -Можем да добавим извикване на функция, за да накараме кода да направи нещо: +Можем още да допълним извикването на някаква функция, за да прави кодът нещо: ```js alert('Rock\'n\'Roll'); ``` -Ако вмъкнем този код в HTML документ с помощта на `<script>`тогава не е необходимо да променяме нищо друго, защото няма забранена последователност `</script`: +Ако този код вмъкнем в HTML документ с помощта на `<script>`, не е необходимо да се променя нищо друго, защото в него не се среща забранената последователност `</script`: ```html <script> alert('Rock\'n\'Roll'); </script> ``` -Ако обаче искаме да го вмъкнем в атрибут на HTML, все пак е необходимо да избягаме от кавичките в единицата на HTML: +Ако обаче искахме да го вмъкнем в HTML атрибут, трябва още да екранираме кавичките в HTML ентичности: ```html <div onclick='alert('Rock\'n\'Roll')'></div> ``` -Не е задължително обаче вложеният контекст да бъде само JS или CSS. Обикновено това е и URL адрес. Параметрите в URL адресите се избягват, като специалните символи се преобразуват в последователности, започващи с `%`. Пример: +Вложеният контекст обаче не е задължително да бъде само JS или CSS. Често това е и URL. Параметрите в URL се екранират така, че знаците със специално значение се преобразуват в последователности, започващи с `%`. Пример: ``` https://example.org/?a=Jazz&b=Rock%27n%27Roll ``` -Когато извеждаме този низ в атрибута, все още прилагаме escape в съответствие с този контекст и го заменяме с `&` with `&`: +И когато този низ изведем в атрибут, ще приложим още екраниране според този контекст и ще заменим `&` с `&`: ```html <a href="https://example.org/?a=Jazz&b=Rock%27n%27Roll"> ``` -Ако сте прочели дотук, поздравления, беше досадно. Вече сте разбрали добре какво представляват контекстът и бягството. И не е нужно да се притеснявате, че е сложно. Latte го прави автоматично за вас. +Ако сте прочели дотук, поздравления, беше изчерпателно. Сега вече имате добра представа какво са контексти и екраниране. И не трябва да се притеснявате, че е сложно. Latte прави това за вас автоматично. -Latte срещу наивни системи .[#toc-latte-vs-naive-systems] -========================================================= +Latte срещу наивни системи +========================== -Показахме как правилно да изпълнявате ескапирането в HTML документ и колко е важно да познавате контекста, т.е. мястото, където показвате данните. С други думи, как работи контекстно-чувствителното ескапиране. -Въпреки че това е предпоставка за функционална защита срещу XSS, **Latte е единствената система за шаблони за PHP, която прави това. +Показахме си как правилно се екранира в HTML документ и колко е важно познаването на контекста, т.е. мястото, където извеждаме данните. С други думи, как работи контекстно-чувствителното екраниране. Въпреки че това е необходима предпоставка за функционална защита срещу XSS, **Latte е единствената система за шаблони за PHP, която може това.** -Как е възможно това, след като всички днешни системи твърдят, че имат автоматичен ескейп? -Автоматичното бягство, без да се знае контекстът, е пълна глупост, която **създава фалшиво чувство за сигурност**. +Как е възможно това, когато всички системи днес твърдят, че имат автоматично екраниране? Автоматичното екраниране без познаване на контекста е малко глупост, която **създава фалшиво усещане за сигурност**. -Системите за шаблониране като Twig, Laravel Blade и други не виждат HTML структурата в шаблона. Затова и те не виждат контекста. В сравнение с Latte те са слепи и наивни. Те работят само със собствената си маркировка, а всичко останало за тях е поток от несъществени символи: +Системи за шаблони като Twig, Laravel Blade и други не виждат в шаблона никаква HTML структура. Следователно не виждат и контексти. В сравнение с Latte те са слепи и наивни. Обработват само собствените си тагове, всичко останало за тях е незначителен поток от знаци: <div class="juxtapose juxtapose--dark-handle" data-startingposition="80" data-animation="juxtapose-wiper"> -```twig .{file:Twig template as seen by Twig himself} -░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░ -░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░{{ text }}░░░░ +```twig .{file:Twig шаблон, както го вижда самият Twig} +░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░ +░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░ ``` -```twig .{file:Twig template as the designer sees it} -- in text: <span>{{ text }}</span> -- in tag: <span {{ text }} ></span> -- in attribute: <span title='{{ text }}'></span> -- in unquoted attribute: <span title={{ text }}></span> -- in attribute containing URL: <a href="{{ text }}"></a> -- in attribute containing JavaScript: <img onload="{{ text }}"> -- in attribute containing CSS: <span style="{{ text }}"></span> -- in JavaScriptu: <script>var = {{ text }}</script> -- in CSS: <style>body { content: {{ text }}; }</style> -- in comment: <!-- {{ text }} --> +```twig .{file:Twig шаблон, както го вижда дизайнерът} +- в текст: <span>{{ foo }}</span> +- в таг: <span {{ foo }} ></span> +- в атрибут: <span title='{{ foo }}'></span> +- в атрибут без кавички: <span title={{ foo }}></span> +- в атрибут, съдържащ URL: <a href="{{ foo }}"></a> +- в атрибут, съдържащ JavaScript: <img onload="{{ foo }}"> +- в атрибут, съдържащ CSS: <span style="{{ foo }}"></span> +- в JavaScript: <script>var = {{ foo }}</script> +- в CSS: <style>body { content: {{ foo }}; }</style> +- в коментар: <!-- {{ foo }} --> ``` </div> -Наивните системи просто механично преобразуват символите `< > & ' "` в HTML единици, което е правилният начин за ескейпване в повечето случаи, но далеч не винаги. По този начин те не могат да откриват или предотвратяват различни дупки в сигурността, както ще покажем по-долу. +Наивните системи само механично преобразуват знаците `< > & ' "` в HTML ентичности, което, макар и в повечето случаи на употреба да е валиден начин за екраниране, далеч не винаги е така. Те не могат да открият или предотвратят възникването на различни дупки в сигурността, както ще покажем по-нататък. -Latte вижда шаблона по същия начин като вас. Той разбира HTML, XML, разпознава тагове, атрибути и др. Поради това той разпознава контекстите и обработва данните по съответния начин. Следователно тя предлага наистина ефективна защита срещу критичната уязвимост Cross-site Scripting. +Latte вижда шаблона по същия начин като вас. Разбира HTML, XML, разпознава тагове, атрибути и т.н. И благодарение на това разграничава отделните контексти и според тях обработва данните. Предлага така наистина ефективна защита срещу критичната уязвимост Cross-site Scripting. + +<div class="juxtapose juxtapose--dark-handle" data-startingposition="80" data-animation="juxtapose-wiper"> + +```latte .{file:Latte шаблон, както го вижда Latte} +░░░░░░░░░░░<span>{$foo}</span> +░░░░░░░░░░<span {$foo} ></span> +░░░░░░░░░░░░░░<span title='{$foo}'></span> +░░░░░░░░░░░░░░░░░░░░░░░░░░░<span title={$foo}></span> +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░<a href="{$foo}"></a> +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░<img onload="{$foo}"> +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░<span style="{$foo}"></span> +░░░░░░░░░░░░░░░░░<script>░░░░░░{$foo}</script> +░░░░░░░░░<style>░░░░░░░░░░░░░░░░{$foo}░░░</style> +░░░░░░░░░░░░░░░<!--░{$foo}░--> +``` + +```latte .{file:Latte шаблон, както го вижда дизайнерът} +- в текст: <span>{$foo}</span> +- в таг: <span {$foo} ></span> +- в атрибут: <span title='{$foo}'></span> +- в атрибут без кавички: <span title={$foo}></span> +- в атрибут, съдържащ URL: <a href="{$foo}"></a> +- в атрибут, съдържащ JavaScript: <img onload="{$foo}"> +- в атрибут, съдържащ CSS: <span style="{$foo}"></span> +- в JavaScript: <script>var = {$foo}</script> +- в CSS: <style>body { content: {$foo}; }</style> +- в коментар: <!-- {$foo} --> +``` + +</div> -Демонстрация на живо .[#toc-live-demonstration] -=============================================== +Жив пример +========== -Вляво виждате шаблона в Latte, а вдясно - генерирания HTML код. Променливата `$text` се показва няколко пъти, всеки път в малко по-различен контекст. Поради това той се изобразява малко по-различно. Можете сами да редактирате кода на шаблона, например да промените съдържанието на променливата и т.н. Опитайте: +Вляво виждате шаблон в Latte, вдясно е генерираният HTML код. Няколко пъти тук се извежда променливата `$text` и всеки път в малко по-различен контекст. И следователно и малко по-различно екранирана. Можете сами да редактирате кода на шаблона, например да промените съдържанието на променливата и т.н. Опитайте: <div class="grid fiddle wiki-expanded"> <div class="contains-pre"> ``` .{file:template.latte; min-height: 14em}[fiddle-source] -{* TRY TO EDIT THIS TEMPLATE *} +{* ОПИТАЙТЕ ДА РЕДАКТИРАТЕ ТОЗИ ШАБЛОН *} {var $text = "Rock'n'Roll"} - <span>{$text}</span> - <span title='{$text}'></span> @@ -270,63 +286,61 @@ Latte вижда шаблона по същия начин като вас. То </div> </div> -Не е ли страхотно! Latte извършва автоматично контекстно чувствително екраниране, така че програмистът: +Не е ли страхотно! Latte прави контекстно-чувствително екраниране автоматично, така че програмистът: -- не трябва да мисли или да знае как да избяга от данните. +- не трябва да мисли или да знае как се екранира къде - не може да сгреши -- не може да забрави за него +- не може да забрави за екранирането -Това дори не са всички контексти, които Latte разграничава при извеждане и за които конфигурира обработката на данни. Сега ще разгледаме по-интересните случаи. +Това дори не са всички контексти, които Latte разграничава при извеждане и за които адаптира обработката на данни. Ще разгледаме сега други интересни случаи. -Как се хакват наивни системи .[#toc-how-to-hack-naive-systems] -============================================================== +Как да хакнем наивни системи +============================ -Чрез няколко практически примера ще покажем колко важно е контекстуалното разграничаване и защо наивните системи с шаблони не осигуряват достатъчна защита срещу XSS, за разлика от Latte. -В примерите ще използваме Twig като представител на наивна система, но същото важи и за други системи. +На няколко практически примера ще покажем колко е важно разграничаването на контексти и защо наивните системи за шаблони не предоставят достатъчна защита срещу XSS, за разлика от Latte. Като представител на наивна система ще използваме в примерите Twig, но същото важи и за други системи. -Уязвимост на атрибута .[#toc-attribute-vulnerability] ------------------------------------------------------ +Уязвимост чрез атрибут +---------------------- -Нека се опитаме да инжектираме зловредния код в страницата, като използваме атрибута HTML, както [показахме по-горе |#How-Does-the-Vulnerability-Arise]. Да предположим, че имаме шаблон в Twig, който показва изображение: +Ще се опитаме да инжектираме в страницата зловреден код с помощта на HTML атрибут, както [показахме по-горе |#Как възниква уязвимостта]. Нека имаме шаблон в Twig, изобразяващ изображение: ```twig .{file:Twig} <img src={{ imageFile }} alt={{ imageAlt }}> ``` -Обърнете внимание, че около стойностите на атрибутите няма кавички. Може би програмистът ги е забравил, което се случва често. Например в React кодът се пише така, без кавички, и програмист, който преминава от един език на друг, лесно може да забрави за кавичките. +Забележете, че около стойностите на атрибутите няма кавички. Кодерът може да ги е забравил, което просто се случва. Например в React кодът се пише така, без кавички, и кодер, който сменя езици, след това лесно може да забрави кавичките. -Атакуващият вмъква умело конструиран низ `foo onload=alert('Hacked!')` като надпис към изображение. Вече знаем, че Twig не може да определи дали дадена променлива е изобразена в HTML текстов поток, в атрибут, в HTML коментар и т.н.; накратко, той не прави разлика между контекстите. Той просто механично преобразува символите `< > & ' "` в HTML единици. -Така полученият код ще изглежда по следния начин +Нападателят като описание на изображението вмъква умело съставен низ `foo onload=alert('Hacked!')`. Вече знаем, че Twig не може да разпознае дали променливата се извежда в потока на HTML текста, вътре в атрибут, HTML коментар и т.н., накратко не разграничава контексти. И само механично преобразува знаците `< > & ' "` в HTML ентичности. Така резултатният код ще изглежда така: ```html <img src=photo0145.webp alt=foo onload=alert('Hacked!')> ``` -**Създаден е пробив в сигурността!** +**И възникна дупка в сигурността!** -Подправеният атрибут `onload` е станал част от страницата и браузърът го изпълнява веднага след зареждането на изображението. +Част от страницата стана подправеният атрибут `onload` и браузърът веднага след изтеглянето на изображението го стартира. -Сега нека видим как Latte се справя със същия модел: +Сега ще видим как със същия шаблон ще се справи Latte: ```latte .{file:Latte} <img src={$imageFile} alt={$imageAlt}> ``` -Latte вижда шаблона по същия начин като вас. За разлика от Twig, той разбира HTML и знае, че променливата се изобразява като стойност на атрибут, който не е затворен в кавички. Затова той ги добавя. Когато атакуващият вмъкне същата кавичка, полученият код ще изглежда по следния начин: +Latte вижда шаблона по същия начин като вас. За разлика от Twig, разбира HTML и знае, че променливата се извежда като стойност на атрибут, който не е в кавички. Затова ги допълва. Когато нападателят вмъкне същото описание, резултатният код ще изглежда така: ```html <img src="photo0145.webp" alt="foo onload=alert('Hacked!')"> ``` -**Latt успешно предотврати XSS.**. +**Latte успешно предотврати XSS.** -Отпечатване на променлива в JavaScript .[#toc-printing-a-variable-in-javascript] --------------------------------------------------------------------------------- +Извеждане на променлива в JavaScript +------------------------------------ -Благодарение на контекстно чувствителното ескапиране можете да използвате променливи на PHP в JavaScript. +Благодарение на контекстно-чувствителното екраниране е напълно нативно възможно да се използват PHP променливи вътре в JavaScript. ```latte <p onclick="alert({$movie})">{$movie}</p> @@ -334,7 +348,7 @@ Latte вижда шаблона по същия начин като вас. За <script>var movie = {$movie};</script> ``` -Ако в променливата `$movie` се съхранява низът `'Amarcord & 8 1/2'`, се генерира следният изход. Обърнете внимание на различното ескапиране, използвано в HTML и JavaScript, както и на атрибута `onclick`: +Ако променливата `$movie` съдържа низ `'Amarcord & 8 1/2'`, ще се генерира следният изход. Забележете, че вътре в HTML се използва различно екраниране от това вътре в JavaScript и още по-различно в атрибута `onclick`: ```latte <p onclick="alert("Amarcord & 8 1\/2")">Amarcord & 8 1/2</p> @@ -343,29 +357,27 @@ Latte вижда шаблона по същия начин като вас. За ``` -Валидиране на връзката .[#toc-link-checking] --------------------------------------------- +Проверка на връзки +------------------ -Latte автоматично проверява дали променливата, използвана в атрибутите `src` или `href`, съдържа уеб адрес (т.е. HTTP протокол) и предотвратява записването на връзки, които могат да представляват риск за сигурността. +Latte автоматично проверява дали променливата, използвана в атрибутите `src` или `href`, съдържа уеб URL (т.е. протокол HTTP) и предотвратява извеждането на връзки, които могат да представляват риск за сигурността. ```latte {var $link = 'javascript:attack()'} -<a href="{$link}">click here</a> +<a href={$link}>кликни</a> ``` -Пише: +Извежда: ```latte -<a href="">click here</a> +<a href="">кликни</a> ``` -Проверката може да бъде забранена с помощта на филтъра [nocheck |filters#nocheck]. +Проверката може да се изключи с помощта на филтъра [nocheck |filters#nocheck]. -Ограничения на Latte .[#toc-limits-of-latte] -============================================ +Ограничения на Latte +==================== -Latte не е пълна защита от XSS за цялото приложение. Бихме били щастливи, ако спрете да мислите за сигурността, когато използвате Latte. -Целта на Latte е да гарантира, че атакуващият не може да променя структурата на страницата, да подправя елементи или атрибути на HTML. Но тя не проверява дали съдържанието на изхода е правилно. Или че JavaScript се държи правилно. -Това е извън обхвата на системата за шаблони. Проверката на коректността на данните, особено на въведените от потребителя и следователно недостоверни данни, е важна задача за програмиста. +Latte не е напълно цялостна защита срещу XSS за цялото приложение. Не бихме искали, ако използвате Latte, да спрете да мислите за сигурността. Целта на Latte е да гарантира, че нападателят не може да промени структурата на страницата, да подправи HTML елементи или атрибути. Но не контролира коректността на съдържанието на извежданите данни. Или коректността на поведението на JavaScript. Това вече излиза извън компетенциите на системата за шаблони. Проверката на коректността на данните, особено тези, въведени от потребителя и следователно ненадеждни, е важна задача на програмиста. diff --git a/latte/bg/sandbox.texy b/latte/bg/sandbox.texy index 5d603d374f..a9586d1a28 100644 --- a/latte/bg/sandbox.texy +++ b/latte/bg/sandbox.texy @@ -1,12 +1,10 @@ -Пясъчник -******** +Sandbox +******* -.[perex]{data-version:2.8} -Sandbox осигурява ниво на сигурност, което ви дава възможност да контролирате кои тагове, PHP функции, методи и т.н. могат да се използват в шаблоните. Благодарение на режима Sandbox можете спокойно да си сътрудничите с клиент или външен програмист при създаването на шаблони, без да се притеснявате, че ще компрометирате приложението или ще извършите нежелани операции. +.[perex] +Sandbox предоставя слой за сигурност, който ви дава контрол върху това кои тагове, PHP функции, методи и т.н. могат да бъдат използвани в шаблоните. Благодарение на sandbox режима можете безопасно да си сътрудничите с клиента или външен кодер при създаването на шаблони, без да се налага да се притеснявате, че ще настъпи нарушаване на приложението или нежелани операции. -Как работи? Просто определяме какво искаме да разрешим в шаблона. В началото всичко е забранено и постепенно предоставяме разрешения: - -Следният код позволява на шаблона да използва таговете `{block}`, `{if}`, `{else}` и `{=}` (последният е таг за [печат на променливи или изрази |tags#Printing]) и всички филтри: +Как работи? Просто дефинираме какво всичко ще позволим на шаблона. При което по подразбиране всичко е забранено и ние постепенно разрешаваме. Със следния код ще позволим на автора на шаблона да използва таговете `{block}`, `{if}`, `{else}` и `{=}`, което е таг за [извеждане на променлива или израз |tags#Извеждане] и всички филтри: ```php $policy = new Latte\Sandbox\SecurityPolicy; @@ -16,7 +14,7 @@ $policy->allowFilters($policy::All); $latte->setPolicy($policy); ``` -Можем също така да разрешим достъп до глобални функции, методи или свойства на обекти: +Освен това можем да разрешим отделни функции, методи или свойства на обекти: ```php $policy->allowFunctions(['trim', 'strlen']); @@ -24,30 +22,35 @@ $policy->allowMethods(Nette\Security\User::class, ['isLoggedIn', 'isAllowed']); $policy->allowProperties(Nette\Database\Row::class, $policy::All); ``` -Не е ли невероятно? Можете да контролирате всичко на много ниско ниво. Ако даден шаблон се опита да извика неоторизирана функция или да получи достъп до неоторизиран метод или свойство, той хвърля изключение `Latte\SecurityViolationException`. +Не е ли страхотно? Можете на много ниско ниво да контролирате абсолютно всичко. Ако шаблонът се опита да извика непозволена функция или да достъпи непозволен метод или свойство, това ще завърши с изключение `Latte\SecurityViolationException`. -Създаването на политики от нулата, когато всичко е забранено, може да бъде неудобно, така че може да искате да започнете от сигурна основа: +Създаването на политика от нулата, когато всичко е забранено, може да не е удобно, затова можете да започнете от безопасна основа: ```php $policy = Latte\Sandbox\SecurityPolicy::createSafePolicy(); ``` -Това означава, че са разрешени всички стандартни тагове с изключение на `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget`. -Разрешени са и всички стандартни филтри, с изключение на `datastream`, `noescape` и `nocheck`. И накрая, разрешен е и достъпът до методите и свойствата на обекта `$iterator`. +Безопасна основа означава, че са разрешени всички стандартни тагове с изключение на `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget`. Разрешени са стандартните филтри с изключение на `datastream`, `noescape` и `nocheck`. И накрая е разрешен достъпът до методите и свойствата на обекта `$iterator`. -Тези правила важат за шаблона, който вмъкваме с новия [`{sandbox}` |tags#Including-Templates] етикет. Това е нещо подобно на `{include}`, но в него е активиран режим на пясъчник и не се предават външни променливи: +Правилата се прилагат за шаблон, който вмъкваме с тага [`{sandbox}` |tags#Вмъкване на шаблон]. Което е нещо като аналог на `{include}`, но включва безопасен режим и също така не предава никакви променливи: ```latte {sandbox 'untrusted.latte'} ``` -Така че оформлението и отделните страници могат да използват всички тагове и променливи, както преди, като ограниченията ще бъдат наложени само на шаблона `untrusted.latte`. +Така лейаутът и отделните страници могат необезпокоявано да използват всички тагове и променливи, само върху шаблона `untrusted.latte` ще бъдат приложени ограничения. -Някои нарушения, като например използването на забранен таг или филтър, се откриват по време на компилиране. Други, като например извиквания на неразрешени методи на обекти, се откриват по време на изпълнение. -Шаблонът може да съдържа и всякакви други грешки. За да предотвратите изхвърлянето на изключение от шаблона в пясъчната кутия, което прекъсва цялото визуализиране, можете да дефинирате [свой собствен обработчик на изключение |develop#Exception-Handler], който например просто да го регистрира. +Някои нарушения, като използване на забранен таг или филтър, се откриват по време на компилация. Други, като например извикване на непозволени методи на обект, чак по време на изпълнение. Шаблонът също може да съдържа всякакви други грешки. За да не може от sandbox шаблона да изскочи изключение, което да наруши цялото рендиране, може да се дефинира собствен [персонализиран обработчик на изключения |develop#Exception handler], който например да го логва. -Ако искаме да активираме режима sandbox директно за всички шаблони, това е лесно: +Ако искаме да включим sandbox режим директно за всички шаблони, това става лесно: ```php $latte->setSandboxMode(); ``` + +За да сте сигурни, че потребителят няма да вмъкне в страницата PHP код, който макар и синтактично правилен, е забранен и ще причини PHP Compile Error, препоръчваме [шаблоните да се проверяват с PHP linter |develop#Проверка на генерирания код]. Тази функционалност се включва с метода `Engine::enablePhpLint()`. Тъй като за проверката е необходимо да се извика PHP бинарният файл, предайте пътя до него като параметър: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); +``` diff --git a/latte/bg/syntax.texy b/latte/bg/syntax.texy index 8f2de35682..1cbeba309b 100644 --- a/latte/bg/syntax.texy +++ b/latte/bg/syntax.texy @@ -2,62 +2,60 @@ ********* .[perex] -Синтаксисът Latte е създаден от практическите нужди на уеб дизайнерите. Търсехме най-удобния синтаксис, с който можете елегантно да записвате конструкции, които иначе са истински проблем. -В същото време всички изрази са написани по същия начин, както в PHP, така че не е необходимо да учите нов език. Просто използвайте това, което вече знаете. +Синтаксисът на Latte произлиза от практическите изисквания на уеб дизайнерите. Търсихме най-удобния синтаксис, с който елегантно да запишете дори конструкции, които иначе представляват истинско предизвикателство. Същевременно всички изрази се пишат по абсолютно същия начин като в PHP, така че не е необходимо да учите нов език. Просто използвате това, което вече знаете. По-долу е представен минимален шаблон, който илюстрира няколко основни елемента: тагове, n:атрибути, коментари и филтри. ```latte {* това е коментар *} -<ul n:if="$items"> {* n:if е n:atribut *} -{foreach $items as $item} {* таг, представляващ цикъла foreach *} - <li>{$item|capitalize}</li> {* таг, който отпечатва променлива с филтър *} +<ul n:if=$items> {* n:if е n:атрибут *} +{foreach $items as $item} {* таг, представляващ цикъл foreach *} + <li>{$item|capitalize}</li> {* таг, извеждащ променлива с филтър *} {/foreach} {* край на цикъла *} </ul> ``` -Нека разгледаме по-отблизо тези важни елементи и как те могат да ви помогнат да създадете невероятен шаблон. +Нека разгледаме по-отблизо тези важни елементи и как те могат да ви помогнат да създадете страхотен шаблон. -Етикети .[#toc-tags] -==================== +Тагове +====== -Шаблонът съдържа тагове, които управляват логиката на шаблона (напр. цикли *foreach*) или изходните изрази. Един и същ сепаратор се използва и в двата случая `{ ... }`, така че не е необходимо да мислите кой сепаратор да използвате в дадена ситуация, както е при други системи. -Ако `{` е последвано от обърната запетая или интервал, Latte не го счита за начало на таг, така че можете да използвате JavaScript, JSON или CSS правила в шаблоните си без проблеми. +Шаблонът съдържа тагове, които управляват логиката на шаблона (например цикли *foreach*) или извеждат изрази. И за двете се използва единствен разделител `{ ... }`, така че не е необходимо да мислите кой разделител в коя ситуация да използвате, както е при други системи. Ако след знака `{` следва кавичка или интервал, Latte не го счита за начало на таг, благодарение на което можете в шаблоните безпроблемно да използвате и JavaScript конструкции, JSON или правила в CSS. -Вижте [преглед на всички етикети |tags]. Можете също така да създавате [персонализирани тагове |extending-latte#Tags]. +Разгледайте [преглед на всички тагове|tags]. Освен това можете да създавате и [собствени тагове|custom tags]. -Latte разбира PHP .[#toc-latte-understands-php] -=============================================== +Latte разбира PHP +================= -Вътре в таговете можете да използвате добре познати изрази на PHP: +Вътре в таговете можете да използвате PHP изрази, които добре познавате: - променливи - низове (включително HEREDOC и NOWDOC), масиви, числа и др. - [оператори |https://www.php.net/manual/en/language.operators.php] -- извиквания на функции и методи (които могат да бъдат ограничени от [пясъчната кутия |sandbox]). -- [кореспонденция |https://www.php.net/manual/en/control-structures.match.php] +- извиквания на функции и методи (които могат да бъдат ограничени чрез [sandbox|sandbox]) +- [match |https://www.php.net/manual/en/control-structures.match.php] - [анонимни функции |https://www.php.net/manual/en/functions.arrow.php] -- [обратни повиквания |https://www.php.net/manual/en/functions.first_class_callable_syntax.php] +- [callback функции |https://www.php.net/manual/en/functions.first_class_callable_syntax.php] - многоредови коментари `/* ... */` -- и т.н. +- и т.н.… -Latte добавя и някои [хубави разширения на |#Syntactic-Sugar] синтаксиса на PHP. +Освен това Latte допълва синтаксиса на PHP с няколко [приятни разширения |#Синтактичен захар]. -n:атрибути .[#toc-n-attributes] -=============================== +n:атрибути +========== -Всеки сдвоен таг, например `{if} … {/if}`, който работи с един HTML елемент, може да бъде записан в нотация [n:attribute |#n:attribute]. Например `{foreach}` в горния пример може да се запише като +Всички двойни тагове, например `{if} … {/if}`, опериращи над един HTML елемент, могат да бъдат преписани във вид на n:атрибути. Така би могло да се запише например и `{foreach}` в началния пример: ```latte -<ul n:if="$items"> +<ul n:if=$items> <li n:foreach="$items as $item">{$item|capitalize}</li> </ul> ``` -След това функционалността съответства на HTML елемента, в който е записана: +Функционалността тогава се отнася към HTML елемента, в който е поставена: ```latte {var $items = ['I', '♥', 'Latte']} @@ -65,7 +63,7 @@ n:атрибути .[#toc-n-attributes] <p n:foreach="$items as $item">{$item}</p> ``` -Отпечатъци: +извежда: ```latte <p>I</p> @@ -73,7 +71,7 @@ n:атрибути .[#toc-n-attributes] <p>Latte</p> ``` -Използвайки префикса `inner-`, можем да променим поведението, така че функционалността да се прилага само за тялото на елемента: +С помощта на префикса `inner-` можем да променим поведението така, че да се отнася само към вътрешната част на елемента: ```latte <div n:inner-foreach="$items as $item"> @@ -82,7 +80,7 @@ n:атрибути .[#toc-n-attributes] </div> ``` -Отпечатъци: +Ще се изведе: ```latte <div> @@ -95,178 +93,184 @@ n:атрибути .[#toc-n-attributes] </div> ``` -Или с префикс `tag-` функционалността се прилага само за HTML тагове: +Или с помощта на префикса `tag-` прилагаме функционалността само към самите HTML тагове: ```latte -<p><a href="{$url}" n:tag-if="$url">Title</a></p> +<p><a href={$url} n:tag-if="$url">Title</a></p> ``` -В зависимост от стойността на променливата `$url` ще бъде изведено следното: +Което ще изведе в зависимост от променливата `$url`: ```latte -// when $url is empty +{* когато $url е празно *} <p>Title</p> -// when $url equals 'https://nette.org' +{* когато $url съдържа 'https://nette.org' *} <p><a href="https://nette.org">Title</a></p> ``` -Въпреки това n:attributes не е само съкращение за сдвоени тагове, но има и някои чисти n:attributes, като например най-добрият приятел на програмистите [n:class |tags#n-class]. +Въпреки това, n:атрибутите не са само съкращение за двойни тагове. Съществуват и чисти n:атрибути, като например [n:href |application:creating-links#В шаблона на презентера] или изключително удобния помощник за кодера [n:class |tags#n:class]. -Филтри .[#toc-filters] -====================== +Филтри +====== -Вижте кратко описание на [стандартните филтри |filters]. +Разгледайте прегледа на [стандартни филтри |filters]. -Latte ви позволява да извиквате филтри със знака pipe (позволен е интервал преди филтъра): +Филтрите се записват след вертикална черта (може да има интервал преди нея): ```latte <h1>{$heading|upper}</h1> ``` -Филтрите могат да бъдат свързани във верига, като в този случай се прилагат в последователност отляво надясно: +Филтрите могат да бъдат верижно свързани и след това се прилагат в реда отляво надясно: ```latte <h1>{$heading|lower|capitalize}</h1> ``` -Параметрите се поставят след името на филтъра, разделени с двоеточие или запетая: +Параметрите се задават след името на филтъра, разделени с двоеточия или запетаи: ```latte <h1>{$heading|truncate:20,''}</h1> ``` -Към даден израз могат да се прилагат филтри: +Филтрите могат да се прилагат и към израз: ```latte {var $name = ($title|upper) . ($subtitle|lower)} ``` -В блока: +Към блок: ```latte <h1>{block |lower}{$heading}{/block}</h1> ``` -Или директно върху стойността (в комбинация с [`{=expr}` | https://latte.nette.org/bg/tags#printing] таг): +Или директно към стойност (в комбинация с тага [`{=expr}` |tags#Извеждане]): ```latte <h1>{=' Hello world '|trim}<h1> ``` -Коментарите са изключени за .[#toc-comments] -============================================ +Динамични HTML тагове .{data-version:3.0.9} +=========================================== -Коментарите се записват по този начин и не влизат в изхода: +Latte поддържа динамични HTML тагове, които са полезни, когато се нуждаете от гъвкавост в имената на таговете: + +```latte +<h{$level}>Heading</h{$level}> +``` + +Горният код може например да генерира `<h1>Heading</h1>` или `<h2>Heading</h2>` в зависимост от стойността на променливата `$level`. Динамичните HTML тагове в Latte трябва винаги да бъдат двойни. Тяхната алтернатива е [n:tag |tags#n:tag]. + +Тъй като Latte е сигурна система за шаблони, тя проверява дали резултатното име на тага е валидно и не съдържа никакви нежелани или вредни стойности. Освен това гарантира, че името на затварящия таг винаги ще бъде същото като името на отварящия таг. + + +Коментари +========= + +Коментарите се записват по този начин и не попадат в изхода: ```latte {* това е коментар в Latte *} ``` -Коментарите на PHP работят в рамките на тагове: +Вътре в таговете работят PHP коментари: ```latte {include 'file.info', /* value: 123 */} ``` -Синтактична захар .[#toc-syntactic-sugar] -========================================= +Синтактичен захар +================= -Некотирани низове .[#toc-strings-without-quotation-marks] ---------------------------------------------------------- +Низове без кавички +------------------ -За прости низове не могат да се използват кавички: +При прости низове могат да се пропуснат кавичките: ```latte -as in PHP: {var $arr = ['hello', 'btn--default', '€']} +като в PHP: {var $arr = ['hello', 'btn--default', '€']} -abbreviated: {var $arr = [hello, btn--default, €]} +съкратено: {var $arr = [hello, btn--default, €]} ``` -Прости низове са тези, които се състоят само от букви, цифри, подчертавания, тирета и точки. Те не трябва да започват с цифра и не трябва да започват или завършват с дефис. -То не трябва да се състои само от главни букви и подчертавания, защото тогава се счита за константа (например `PHP_VERSION`). -И не трябва да се припокрива с ключовите думи `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor`. +Прости низове са тези, които са съставени само от букви, цифри, долни черти, тирета и точки. Не трябва да започват с цифра и не трябва да започват или завършват с тире. Не трябва да са съставени само от главни букви и долни черти, защото тогава се считат за константа (напр. `PHP_VERSION`). И не трябва да колидират с ключови думи: `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor`. -Кратък троен оператор .[#toc-short-ternary-operator] ----------------------------------------------------- +Константи +--------- -Ако третата стойност на троичния оператор е празна, тя може да бъде пропусната: +Тъй като при прости низове могат да се пропускат кавичките, препоръчваме за разграничение да се записват глобални константи с наклонена черта в началото: ```latte -as in PHP: {$stock ? 'In stock' : ''} - -abbreviated: {$stock ? 'In stock'} +{if \PROJECT_ID === 1} ... {/if} ``` +Този запис е напълно валиден в самото PHP, наклонената черта казва, че константата е в глобалното пространство от имена. -Съвременна нотация на ключови масиви .[#toc-modern-key-notation-in-the-array] ------------------------------------------------------------------------------ -Ключовете на масива могат да се записват по същия начин като именуваните параметри при извикване на функции: +Съкратен тернарен оператор +-------------------------- + +Ако третата стойност на тернарния оператор е празна, може да се пропусне: ```latte -as in PHP: {var $arr = ['one' => 'item 1', 'two' => 'item 2']} +като в PHP: {$stock ? 'Налично' : ''} -modern: {var $arr = [one: 'item 1', two: 'item 2']} +съкратено: {$stock ? 'Налично'} ``` -Филтри .[#toc-filters] ----------------------- +Модерен запис на ключове в масив +-------------------------------- -Филтрите могат да се използват за всякакви изрази, просто поставете всичко в скоби: +Ключовете в масив могат да се записват подобно на именуваните параметри при извикване на функции: ```latte -{var $content = ($text|truncate: 30|upper)} +като в PHP: {var $arr = ['one' => 'item 1', 'two' => 'item 2']} + +модерно: {var $arr = [one: 'item 1', two: 'item 2']} ``` -Оператор `in` .[#toc-operator-in] ---------------------------------- +Филтри +------ -Операторът `in` може да се използва за замяна на функцията `in_array()`. Сравнението винаги е строго: +Филтрите могат да се използват за всякакви изрази, достатъчно е цялото да се затвори в скоби: ```latte -{* като in_array($item, $items, true) *} -{if $item in $items} - ... -{/if} +{var $content = ($text|truncate: 30|upper)} ``` -.{data-version:2.9} -Незадължителна верига с неопределен оператор .[#toc-optional-chaining-with-undefined-safe-operator] ---------------------------------------------------------------------------------------------------- +Оператор `in` +------------- -Операторът undefined-safe `??->` е подобен на оператора nullsafe `?->`, но не предизвиква грешка, ако променливата, свойството или индексът изобщо не съществуват. +С оператора `in` може да се замени функцията `in_array()`. Сравнението винаги е стриктно: ```latte -{$order??->id} +{* аналог на in_array($item, $items, true) *} +{if $item in $items} + ... +{/if} ``` -Това е начин да се каже, че когато `$order` е дефиниран и не е null, `$order->id` ще бъде оценен, но когато `$order` е null или не съществува, спираме действието си и просто връщаме null. - -```latte -{$user??->address??->street} -// roughly means isset($user) && isset($user->address) ? $user->address->street : null -``` +Исторически преглед +------------------- -Прозорец към историята .[#toc-a-window-into-history] ----------------------------------------------------- +Latte през своята история е въвеждал цяла редица синтактични захари, които след няколко години са се появявали в самото PHP. Например в Latte беше възможно да се пишат масиви като `[1, 2, 3]` вместо `array(1, 2, 3)` или да се използва nullsafe операторът `$obj?->foo` много преди това да стане възможно в самото PHP. Latte също въведе оператор за разгръщане на масив `(expand) $arr`, който е еквивалент на днешния оператор `...$arr` от PHP. -В своята история Latte предлага няколко синтаксиса, които се появяват в самия PHP няколко години по-късно. Например в Latte беше възможно да се записват масиви като `[1, 2, 3]` вместо `array(1, 2, 3)`, или да използвате оператора nullsafe `$obj?->foo` много преди това да е възможно в самия PHP. Latte въведе и оператора за разширяване на масива `(expand) $arr`, който е еквивалентен на днешния оператор `...$arr` от PHP. +Undefined-safe операторът `??->`, който е аналог на nullsafe оператора `?->`, но не предизвиква грешка, ако променливата не съществува, възникна по исторически причини и днес препоръчваме да се използва стандартният PHP оператор `?->`. -Ограничения на PHP в Latte .[#toc-php-limitations-in-latte] -=========================================================== +Ограничения на PHP в Latte +========================== -В Latte могат да се записват само PHP изрази. Това означава, че не можете да декларирате класове или да използвате [структури за управление |https://www.php.net/manual/en/language.control-structures.php], като например `if`, `foreach`, `switch`, `return`, `try`, `throw` и други, вместо които Latte предлага свои собствени [тагове |tags]. -Също така не можете да използвате [атрибути |https://www.php.net/manual/en/language.attributes.php], [контразнаци |https://www.php.net/manual/en/language.operators.execution.php] или [магически константи |https://www.php.net/manual/en/language.constants.magic.php], защото това няма смисъл. -Не можете дори да използвате `unset`, `echo`, `include`, `require`, `exit`, `eval`, защото това не са функции, а специални конструкции на езика PHP и следователно не са изрази. +В Latte могат да се записват само PHP изрази. Тоест не могат да се използват стейтмънти, завършващи с точка и запетая. Не могат да се декларират класове или да се използват [контролни структури |https://www.php.net/manual/en/language.control-structures.php], напр. `if`, `foreach`, `switch`, `return`, `try`, `throw` и други, вместо които Latte предлага свои [тагове|tags]. Също така не могат да се използват [атрибути |https://www.php.net/manual/en/language.attributes.php], [обратни кавички |https://www.php.net/manual/en/language.operators.execution.php] или някои [магически константи |https://www.php.net/manual/en/language.constants.magic.php]. Не могат да се използват и `unset`, `echo`, `include`, `require`, `exit`, `eval`, защото това не са функции, а специални езикови конструкции на PHP и следователно не са изрази. Коментарите се поддържат само многоредови `/* ... */`. -Въпреки това можете да заобиколите тези ограничения, като активирате [разширението RawPhpExtension |develop#RawPhpExtension], което ви позволява да използвате всякакъв PHP код в тага `{php ...}` под отговорността на автора на шаблона. +Тези ограничения обаче могат да бъдат заобиколени, като активирате разширението [RawPhpExtension |develop#RawPhpExtension], благодарение на което след това може да се използва в тага `{php ...}` всякакъв PHP код на отговорност на автора на шаблона. diff --git a/latte/bg/tags.texy b/latte/bg/tags.texy index 450882bd9a..05257823ee 100644 --- a/latte/bg/tags.texy +++ b/latte/bg/tags.texy @@ -1,168 +1,174 @@ -Тагове Latte +Latte тагове ************ .[perex] -Обобщение и описание на всички вградени тагове на Latte. +Преглед и описание на всички тагове на системата за шаблони Latte, които са ви стандартно достъпни. .[table-latte-tags language-latte] -|## Отпечатване -| `{$var}`, `{...}` или `{=...}` | [отпечатва скринирана променлива или израз |#Printing] -| `{$var\|filter}` | [отпечатва с филтри |#Filters] -| `{l}` или `{r}` | отпечатва символ `{` or `}` +|## Извеждане +| `{$var}`, `{...}` или `{=...}` | [извежда екранирана променлива или израз |#Извеждане] +| `{$var\|filter}` | [извежда с използване на филтри |#Филтри] +| `{l}` или `{r}` | извежда знак `{` или `}` .[table-latte-tags language-latte] -|## условия -| `{if}`... `{elseif}`... `{else}`... `{/if}` |# [условие ако |#if-elseif-else] -| `{ifset}`... `{elseifset}`... `{/ifset}` | [условие ifset |#ifset-elseifset] -| `{ifchanged}`... `{/ifchanged}` | [проверка за промени |#ifchanged] -| `{switch}` `{case}` `{default}` `{/switch}` | [състояние на превключване |#switch-case-default] +|## Условия +| `{if}` … `{elseif}` … `{else}` … `{/if}` | [условие if |#if elseif else] +| `{ifset}` … `{elseifset}` … `{/ifset}` | [условие ifset |#ifset elseifset] +| `{ifchanged}` … `{/ifchanged}` | [проверка дали е настъпила промяна |#ifchanged] +| `{switch}` `{case}` `{default}` `{/switch}` | [условие switch |#switch case default] +| `n:else` | [алтернативно съдържание за условия |#n:else] .[table-latte-tags language-latte] |## Цикли -| `{foreach}`... `{/foreach}` | [foreach |#foreach] -|# `{for}`... `{/for}` | [за |#for] -|# `{while}`... `{/while}` | [while |#while] -| `{continueIf $cond}` | [преминаване към следващата итерация |#continueIf-skipIf-breakIf] -| `{skipIf $cond}` | [прескачане на текущата итерация на цикъла |#continueIf-skipIf-breakIf] -| `{breakIf $cond}` | [прекъсване на цикъла |#continueIf-skipIf-breakIf] -| `{exitIf $cond}` | [ранен изход |#exitIf] -| `{first}`... `{/first}` | [Това ли е първата итерация |#first-last-sep]? -| `{last}`... `{/last}` | [Това ли е последната итерация |#first-last-sep]? -| `{sep}`... `{/sep}` | [Ще последва ли следващата итерация |#first-last-sep]? -| `{iterateWhile}`... `{/iterateWhile}` | [структуриран foreach |#iterateWhile] -| `$iterator` | [специална променлива в цикъла foreach |#iterator] +| `{foreach}` … `{/foreach}` | [#foreach] +| `{for}` … `{/for}` | [#for] +| `{while}` … `{/while}` | [#while] +| `{continueIf $cond}` | [продължаване със следващата итерация |#continueIf skipIf breakIf] +| `{skipIf $cond}` | [пропускане на итерация |#continueIf skipIf breakIf] +| `{breakIf $cond}` | [прекъсване на цикъл |#continueIf skipIf breakIf] +| `{exitIf $cond}` | [ранно прекратяване |#exitIf] +| `{first}` … `{/first}` | [това първото преминаване ли е? |#first last sep] +| `{last}` … `{/last}` | [това последното преминаване ли е? |#first last sep] +| `{sep}` … `{/sep}` | [ще последва ли още преминаване? |#first last sep] +| `{iterateWhile}` … `{/iterateWhile}` | [структуриран foreach |#iterateWhile] +| `$iterator` | [специална променлива вътре в foreach |#iterator] .[table-latte-tags language-latte] -|## Включване на други шаблони -| `{include 'file.latte'}` | [включване на шаблон от друг файл |#include] -| `{sandbox 'file.latte'}` |00 | [включва шаблона в режим на пясъчник |#sandbox] +|## Вмъкване на други шаблони +| `{include 'file.latte'}` | [зарежда шаблон от друг файл |#include] +| `{sandbox 'file.latte'}` | [зарежда шаблон в sandbox режим |#sandbox] .[table-latte-tags language-latte] -|## Блокове, оформления, наследяване на шаблони -| `{block}` | [анонимен блок |#block] -| `{block blockname}` |00 | [дефиниция на блок |template-inheritance#Blocks] -| `{define blockname}` | [блок за дефиниции |template-inheritance#Definitions] за [бъдеща употреба |template-inheritance#Definitions] -| `{include blockname}` | [принтира блок |template-inheritance#Printing-Blocks] -| `{include blockname from 'file.latte'}` отпечатва [блок от файл |template-inheritance#Printing-Blocks] -| `{import 'file.latte'}` | [зарежда блокове от друг шаблон |template-inheritance#Horizontal-Reuse] -| `{layout 'file.latte'}` / `{extends}` | [посочва файл за оформление |template-inheritance#Layout-Inheritance] -| `{embed}`... `{/embed}` | [зарежда шаблон или блок и позволява презаписване на блокове |template-inheritance#Unit-Inheritance] -| `{ifset blockname}`... `{/ifset}` | [условие, ако е дефиниран блок |template-inheritance#Checking-Block-Existence] +|## Блокове, лейаути, наследяване на шаблони +| `{block}` | [анонимен блок |#block] +| `{block blockname}` | [дефинира блок |template-inheritance#Блокове] +| `{define blockname}` | [дефинира блок за по-късна употреба |template-inheritance#Дефиниции] +| `{include blockname}` | [рендиране на блок |template-inheritance#Рендиране на блокове] +| `{include blockname from 'file.latte'}` | [рендира блок от файл |template-inheritance#Рендиране на блокове] +| `{import 'file.latte'}` | [зарежда блокове от шаблон |template-inheritance#Хоризонтално повторно използване] +| `{layout 'file.latte'}` / `{extends}` | [определя файла с лейаута |template-inheritance#Наследяване на лейаут] +| `{embed}` … `{/embed}` | [зарежда шаблон или блок и позволява презаписване на блокове |template-inheritance#Единично наследяване] +| `{ifset blockname}` … `{/ifset}` | [условие дали съществува блок |template-inheritance#Проверка за съществуване на блокове] .[table-latte-tags language-latte] -|## Обработка на изключения -| `{try}`... `{else}`... `{/try}` |# [улавяне на изключение |#try] -| `{rollback}` | [опит за отхвърляне на блок |#rollback] +|## Управление на изключения +| `{try}` … `{else}` … `{/try}` | [прихващане на изключения |#try] +| `{rollback}` | [отхвърляне на try блок |#rollback] .[table-latte-tags language-latte] |## Променливи -| `{var $foo = value}` | [създаване на променливи |#var-default] -| `{default $foo = value}` | [стойност по подразбиране, когато не е декларирана променлива |#var-default] -| `{parameters}` | [деклариране на променливи, типове стойности по подразбиране |#parameters] -| `{capture}`... `{/capture}` | [улавя секцията за променливата |#capture] +| `{var $foo = value}` | [създава променлива |#var default] +| `{default $foo = value}` | [създава променлива, ако не съществува |#var default] +| `{parameters}` | [декларира променливи, типове и стойности по подразбиране |#parameters] +| `{capture}` … `{/capture}` | [улавя блок в променлива |#capture] .[table-latte-tags language-latte] |## Типове -| `{varType}` | [декларира тип променлива |type-system#varType] -| `{varPrint}` |00 | [предлага видове променливи |type-system#varPrint] -| `{templateType}` | [декларира типове променливи с клас |type-system#templateType] -| `{templatePrint}` | [генерира клас със свойства |type-system#templatePrint] +| `{varType}` | [декларира типа на променливата |type-system#varType] +| `{varPrint}` | [предлага типове променливи |type-system#varPrint] +| `{templateType}` | [декларира типове променливи според класа |type-system#templateType] +| `{templatePrint}` | [предлага клас с типове променливи |type-system#templatePrint] .[table-latte-tags language-latte] -|## Превод -| `{_string}` | [отпечатва превода |#Translation] -| `{translate}`... `{/translate}` |# превежда [съдържанието |#Translation] +|## Преводи +| `{_...}` | [извежда превод |#Преводи] +| `{translate}` … `{/translate}` | [превежда съдържание |#Преводи] .[table-latte-tags language-latte] |## Други -| `{contentType}` | [превключва режима на избягване и изпраща HTTP заглавие |#contentType] -| `{debugbreak}` | [задава точка на прекъсване в кода |#debugbreak] -| `{do}` | [оценява израза, без да го отпечатва |#do] -| `{dump}` | [нулиране на променливите в Tracy Bar |#dump] -| `{spaceless}`... `{/spaceless}` | [изтрива нежеланите символи за бял интервал |#spaceless] -| `{syntax}` | [смяна на синтаксиса по време на изпълнение|#Syntax] -| `{trace}` | [показва проследяването на стека |#trace] +| `{contentType}` | [превключва екранирането и изпраща HTTP хедър |#contentType] +| `{debugbreak}` | [поставя breakpoint в кода |#debugbreak] +| `{do}` | [изпълнява код, но не извежда нищо |#do] +| `{dump}` | [дъмпва променливи в Tracy Bar |#dump] +| `{php}` | [изпълнява всякакъв PHP код |#php] +| `{spaceless}` … `{/spaceless}` | [премахва излишните интервали |#spaceless] +| `{syntax}` | [промяна на синтаксиса по време на изпълнение |#syntax] +| `{trace}` | [показва stack trace |#trace] .[table-latte-tags language-latte] -|## Помощни средства за HTML тагове -| `n:class` | [атрибут на интелигентен клас |#n-class] -| `n:attr` | [интелигентни атрибути на HTML |#n-attr] -| `n:tag` | [име на динамичен HTML елемент |#n-tag] -| `n:ifcontent` | [пропускане на празен HTML таг |#n-ifcontent] +|## Помощници за HTML кодера +| `n:class` | [динамичен запис на HTML атрибут class |#n:class] +| `n:attr` | [динамичен запис на всякакви HTML атрибути |#n:attr] +| `n:tag` | [динамичен запис на името на HTML елемент |#n:tag] +| `n:ifcontent` | [пропуска празен HTML таг |#n:ifcontent] .[table-latte-tags language-latte] -|## Налично само в Nette Framework -| `n:href` | [връзка в елементи на HTML |application:creating-links#In-the-Presenter-Template] -| `{link}` |00 | [отпечатва връзка |application:creating-links#In-the-Presenter-Template] -| `{plink}` | [отпечатва връзката към главния модул |application:creating-links#In-the-Presenter-Template] -| `{control}` | [отпечатва компонент |application:components#Rendering] -| `{snippet}`... `{/snippet}` | [откъс от шаблон, който може да бъде изпратен чрез AJAX |application:ajax#Tag-snippet] -| `{snippetArea}` | плик за фрагмент -| `{cache}`... `{/cache}` | [кеширане на секцията с шаблони |caching:#Caching-in-Latte] +|## Достъпни само в Nette Framework +| `n:href` | [връзка, използвана в HTML елементи `<a>` |application:creating-links#В шаблона на презентера] +| `{link}` | [извежда връзка |application:creating-links#В шаблона на презентера] +| `{plink}` | [извежда връзка към presenter |application:creating-links#В шаблона на презентера] +| `{control}` | [рендира компонент |application:components#Рендиране] +| `{snippet}` … `{/snippet}` | [фрагмент, който може да бъде изпратен чрез AJAX |application:ajax#Снипети в Latte] +| `{snippetArea}` | [обвивка за фрагменти |application:ajax#Области на снипети] +| `{cache}` … `{/cache}` | [кешира част от шаблона |caching:#Кеширане в Latte] .[table-latte-tags language-latte] -|## Налично само в Nette Forms -| `{form}`... `{/form}` |# [Отпечатва елемента на формата |forms:rendering#form] -| `{label}`|00 ... `{/label}` | [отпечатва тага за въвеждане на формуляра |forms:rendering#label-input] -| `{input}` | [отпечатва елемента за влизане във формата|forms:rendering#label-input] -| `{inputError}` | [отпечатва съобщението за грешка за елемента за въвеждане на форма |forms:rendering#inputError] -| `n:name` [активира входния елемент на HTML |forms:rendering#n:name] -| `{formPrint}` [генерира чертеж на формата Latte |forms:rendering#formPrint] -| `{formPrintClass}` [отпечатва PHP клас за данните от формата |forms:in-presenter#Mapping-to-Classes] -| `{formContext}`... `{/formContext}` | [Частично визуализиране на формата |forms:rendering#special-cases] +|## Достъпни само с Nette Forms +| `{form}` … `{/form}` | [рендира тагове на формата |forms:rendering#form] +| `{label}` … `{/label}` | [рендира етикет на елемент от формата |forms:rendering#label input] +| `{input}` | [рендира елемент от формата |forms:rendering#label input] +| `{inputError}` | [извежда съобщение за грешка на елемент от формата |forms:rendering#inputError] +| `n:name` | [активира елемент от формата |forms:rendering#n:name] +| `{formContainer}` … `{/formContainer}` | [рендиране на контейнер на формата |forms:rendering#Специални случаи] + +.[table-latte-tags language-latte] +|## Налично само с Nette Assets +| `{asset}` | [визуализира актив като HTML елемент или URL адрес |assets:#asset] +| `{preload}` | [генерира подсказки за предварително зареждане за оптимизиране на производителността |assets:#preload] +| `n:asset` | [добавя атрибути на активи към HTML елементи |assets:#n:asset] -Отпечатъци .[#toc-printing] -=========================== +Извеждане +========= `{$var}` `{...}` `{=...}` ------------------------- -Latte използва етикета `{=...}`, за да отпечата на изхода всеки израз. Ако изразът започва с променлива или с извикване на функция, не е необходимо да се пише знак за равенство. Това на практика означава, че почти никога не е необходимо да се пише: +В Latte се използва тагът `{=...}` за извеждане на всякакъв израз на изхода. Latte се грижи за вашето удобство, така че ако изразът започва с променлива или извикване на функция, не е необходимо да пишете знака за равенство. Което на практика означава, че почти никога не е необходимо да го пишете: ```latte -Name: {$name} {$surname}<br> -Age: {date('Y') - $birth}<br> +Име: {$name} {$surname}<br> +Възраст: {date('Y') - $birth}<br> ``` -Можете да запишете всичко, което знаете от PHP, като израз. Не е нужно да учите нов език. Например: +Като израз можете да запишете всичко, което познавате от PHP. Просто не е нужно да учите нов език. Така например: ```latte {='0' . ($num ?? $num * 3) . ', ' . PHP_VERSION} ``` -Моля, не търсете смисъл в предишния пример, но ако го откриете там, пишете ни :-) +Моля, не търсете никакъв смисъл в предишния пример, но ако намерите такъв, пишете ни :-) -Избягване на изхода .[#toc-escaping-output] -------------------------------------------- +Екраниране на изхода +-------------------- -Коя е най-важната задача на системата за шаблони? Избягване на дупки в сигурността. Точно това прави Latte, когато отпечатвате нещо за печат. Той автоматично защитава всичко: +Коя е най-важната задача на системата за шаблони? Да предотврати дупки в сигурността. И точно това прави Latte винаги, когато извеждате нещо. Автоматично го екранира: ```latte -<p>{='one < two'}</p> {* prints: '<p>one < two</p>' *} +<p>{='one < two'}</p> {* извежда: '<p>one < two</p>' *} ``` -За да бъдем точни, Latte използва контекстно чувствително ескапиране, което е толкова важна и уникална функция. че сме [му |safety-first#Context-Aware-Escaping] посветили [отделна глава |safety-first#Context-Aware-Escaping]. +За да бъдем точни, Latte използва контекстно-чувствително екраниране, което е толкова важно и уникално нещо, че му посветихме [отделна глава |safety-first#Контекстно-чувствително екраниране]. -А ако отпечатвате кодирано в HTML съдържание от надежден източник? След това можете лесно да деактивирате ескапирането: +А какво ако извеждате съдържание, кодирано в HTML от надежден източник? Тогава лесно може да се изключи екранирането: ```latte {$trustedHtmlString|noescape} ``` .[warning] -Неправилното използване на филтъра `noescape` може да доведе до уязвимост XSS! Никога не го използвайте, освен ако не сте абсолютно сигурни** в това, което правите, и че низът, който отпечатвате, е от надежден източник. +Неправилното използване на филтъра `noescape` може да доведе до уязвимост XSS! Никога не го използвайте, ако не сте **напълно сигурни** какво правите и че извежданият низ идва от надежден източник. -Отпечатване в JavaScript .[#toc-printing-in-javascript] -------------------------------------------------------- +Извеждане в JavaScript +---------------------- -Благодарение на контекстно чувствителното ескапиране е много лесно да отпечатвате променливи в JavaScript и Latte ще ги ескапира правилно. +Благодарение на контекстно-чувствителното екраниране е изключително лесно да се извеждат променливи вътре в JavaScript, а правилното екраниране се осигурява от Latte. -Не е задължително променливата да бъде низ, поддържа се всякакъв тип данни, които след това се кодират като JSON: +Променливата не е задължително да бъде низ, поддържа се всеки тип данни, който след това се кодира като JSON: ```latte {var $foo = ['hello', true, 1]} @@ -179,7 +185,7 @@ Age: {date('Y') - $birth}<br> </script> ``` -Това е и причината, поради която **не ограждайте променливата в кавички**: Latte ги добавя около струните. А ако искате да поставите символна променлива в друг символен низ, просто ги конкатенирайте: +Това е и причината, поради която около променливата **не се пишат кавички**: Latte ги добавя само при низове. А ако искате да вмъкнете низова променлива в друг низ, просто ги свържете: ```latte <script> @@ -187,74 +193,88 @@ Age: {date('Y') - $birth}<br> alert({="Hello $name!"}); // OK - alert('Hello {$name} !'); // ERROR! + alert('Hello {$name} !'); // ГРЕШКА! </script> ``` -Филтри .[#toc-filters] ----------------------- +Филтри +------ -Въведеният израз може да бъде променен с помощта на [филтри |syntax#Filters]. Например в този пример низът се преобразува в главни букви и се свежда до максимум 30 символа: +Извежданият израз може да бъде модифициран с [филтър |syntax#Филтри]. Така например низът се преобразува в главни букви и се скъсява до максимум 30 знака: ```latte {$string|upper|truncate:30} ``` -Можете също така да прилагате филтри към части от израз, както следва: +Филтрите могат да се използват и върху части от израза по следния начин: ```latte {$left . ($middle|upper) . $right} ``` -Условия .[#toc-conditions] -========================== +Условия +======= `{if}` `{elseif}` `{else}` -------------------------- -Условията се държат точно както техните аналози в PHP. Можете да използвате същите изрази, които познавате от PHP, и не е необходимо да учите нов език. +Условията се държат по същия начин като техните аналози в PHP. Можете да използвате в тях същите изрази, които познавате от PHP, не е необходимо да учите нов език. ```latte {if $product->inStock > Stock::Minimum} - In stock + Налично {elseif $product->isOnWay()} - On the way + На път {else} - Not available + Не е налично {/if} ``` -Както всеки сдвоен таг, двойката `{if} ... {/ if}` може да се запише като [n:attribute |syntax#n-attributes], например: +Както всеки двоен таг, така и двойката `{if} ... {/if}` може да се записва и във вид на [n:атрибут |syntax#n:атрибути], например: ```latte -<p n:if="$count > 0">In stock {$count} items</p> +<p n:if="$count > 0">Налични {$count} броя</p> ``` -Знаете ли, че можете да използвате префикс n:attributes с `tag-`? Тогава условието ще засяга само HTML таговете, а съдържанието между тях винаги ще бъде визуализирано: +Знаете ли, че към n:атрибутите можете да добавите префикс `tag-`? Тогава условието ще се отнася само до извеждането на HTML таговете, а съдържанието между тях ще се изведе винаги: ```latte <a href="..." n:tag-if="$clickable">Hello</a> -{* prints 'Hello' when $clickable is falsey *} -{* prints '<a href="...">Hello</a>' when $clickable is truthy *} +{* извежда 'Hello', когато $clickable е невярно *} +{* извежда '<a href="...">Hello</a>', когато $clickable е вярно *} ``` Страхотно. +`n:else` .{data-version:3.0.11} +------------------------------- + +Ако условието `{if} ... {/if}` запишете във вид на [n:атрибут |syntax#n:атрибути], имате възможност да посочите и алтернативен клон с помощта на `n:else`: + +```latte +<strong n:if="$count > 0">Налични {$count} броя</strong> + +<em n:else>не е налично</em> +``` + +Атрибутът `n:else` може да се използва също и в двойка с [`n:ifset` |#ifset elseifset], [`n:foreach` |#foreach], [`n:try` |#try], [#`n:ifcontent`] и [`n:ifchanged` |#ifchanged]. + + `{/if $cond}` ------------- -Може би ще се изненадате да научите, че изразът в условието `{if}` може да бъде посочен и в тага за край. Това е полезно в ситуации, в които все още не знаем стойността на условието в момента на отваряне на тага. Нека го наречем забавено решение. +Може би ще ви изненада, че изразът в условието `{if}` може да се посочи и в затварящия таг. Това е полезно в ситуации, когато при отваряне на условието все още не знаем стойността му. Нека го наречем отложено решение. -Например, започваме да извеждаме таблица със записи от база данни и едва след като отчетът е завършен, разбираме, че в базата данни няма записи. Затова поставяме условието в крайния таг `{/if}`, и ако няма запис, няма да бъде отпечатан нито един от тях: +Например започваме да извеждаме таблица със записи от база данни и едва след завършване на извеждането осъзнаваме, че в базата данни не е имало нито един запис. Тогава поставяме условие за това в затварящия таг `{/if}` и ако няма нито един запис, нищо от това няма да се изведе: ```latte {if} - <h1>Printing rows from the database</h1> + <h1>Извеждане на редове от базата данни</h1> <table> {foreach $resultSet as $row} @@ -264,30 +284,30 @@ Age: {date('Y') - $birth}<br> {/if isset($row)} ``` -Удобно е, нали? +Умно, нали? -Можете също така да използвате `{else}` в условие за забавяне, но не и `{elseif}`. +В отложеното условие може да се използва и `{else}`, но не и `{elseif}`. `{ifset}` `{elseifset}` ----------------------- .[note] -Вижте също [`{ifset block}` |template-inheritance#Checking-Block-Existence] +Вижте също [`{ifset block}` |template-inheritance#Проверка за съществуване на блокове] -Използвайте условието `{ifset $var}`, за да определите дали дадена променлива (или няколко променливи) съществува и има ненулева стойност. Всъщност това е същото като `if (isset($var))` в PHP. Както всеки сдвоен таг, и този може да бъде записан като [n:attribute |syntax#n-attributes], затова нека го покажем с пример: +С помощта на условието `{ifset $var}` установяваме дали променливата (или няколко променливи) съществува и има стойност, различна от *null*. Всъщност това е същото като `if (isset($var))` в PHP. Както всеки двоен таг, тя може да се записва и във вид на [n:атрибут |syntax#n:атрибути], така че нека го покажем като пример: ```latte -<meta name="robots" content="{$robots}" n:ifset="$robots"> +<meta name="robots" content={$robots} n:ifset="$robots"> ``` -`{ifchanged}` .{data-version:2.9} ---------------------------------- +`{ifchanged}` +------------- -`{ifchanged}` проверява дали стойността на променливата се е променила след последната итерация в цикъла (foreach, for или while). +`{ifchanged}` проверява дали стойността на променливата се е променила от последната итерация в цикъла (foreach, for или while). -Ако в тага посочим една или повече променливи, той ще провери дали стойността на някоя от тях се е променила и ще отпечата съдържанието им по съответния начин. Например в следния пример при изписване на имена първата буква от името се отпечатва като заглавие при всяка промяна: +Ако в тага посочим една или повече променливи, ще се проверява дали някоя от тях се е променила и според това ще се изведе съдържанието. Например следващият пример ще изведе първата буква на името като заглавие всеки път, когато при извеждане на имената тя се промени: ```latte {foreach ($names|sort) as $name} @@ -297,7 +317,7 @@ Age: {date('Y') - $birth}<br> {/foreach} ``` -Ако обаче не е посочен аргумент, самото съдържание за визуализиране ще бъде проверено според предишното му състояние. Това означава, че в предишния пример можем спокойно да пропуснем аргумента в тага. Разбира се, можем да използваме и [n:attribute |syntax#n-attributes]: +Ако обаче не посочим никакъв аргумент, ще се проверява рендираното съдържание спрямо предишното му състояние. Това означава, че в предишния пример можем спокойно да пропуснем аргумента в тага. И разбира се, можем да използваме и [n:атрибут |syntax#n:атрибути]: ```latte {foreach ($names|sort) as $name} @@ -307,49 +327,49 @@ Age: {date('Y') - $birth}<br> {/foreach} ``` -Можете също така да включите клаузата `{else}` в `{ifchanged}`. +Вътре в `{ifchanged}` може също да се посочи клауза `{else}`. `{switch}` `{case}` `{default}` ------------------------------- -Сравнява стойността с няколко опции. Това е подобно на структурата `switch`, позната от PHP. Latte обаче го подобрява: +Сравнява стойност с няколко възможности. Това е аналог на условния оператор `switch`, който познавате от PHP. Въпреки това Latte го подобрява: -- Използва стриктно сравнение (`===`) -- не изисква `break` +- използва стриктно сравнение (`===`) +- не се нуждае от `break` -По този начин тя е точен еквивалент на структурата `match`, с която се доставя PHP 8.0. +Това е точен еквивалент на структурата `match`, която идва с PHP 8.0. ```latte {switch $transport} {case train} - By train + С влак {case plane} - By plane + Със самолет {default} - Differently + Друго {/switch} ``` -.{data-version:2.9} + Клаузата `{case}` може да съдържа няколко стойности, разделени със запетаи: ```latte {switch $status} -{case $status::New}<b>new item</b> -{case $status::Sold, $status::Unknown}<i>not available</i> +{case $status::New}<b>нова позиция</b> +{case $status::Sold, $status::Unknown}<i>не е налична</i> {/switch} ``` -Цикли .[#toc-loops] -=================== +Цикли +===== -В Latte са налични всички цикли, познати от PHP: foreach, for и while. +В Latte ще намерите всички цикли, които познавате от PHP: foreach, for и while. `{foreach}` ----------- -Записвате цикъла по същия начин, както в PHP: +Цикълът се записва по абсолютно същия начин като в PHP: ```latte {foreach $langs as $code => $lang} @@ -357,11 +377,11 @@ Age: {date('Y') - $birth}<br> {/foreach} ``` -Той има и някои удобни настройки, за които ще говорим след малко. +Освен това има няколко удобни трика, за които ще поговорим сега. -Например, Latte проверява дали създадените променливи не презаписват случайно глобални променливи със същото име. Това ще ви спаси, когато приемете, че `$lang` е текущият език на страницата и не осъзнаете, че `foreach $langs as $lang` е презаписала тази променлива. +Например Latte проверява дали създадените променливи случайно не презаписват глобални променливи със същото име. Това спасява ситуации, когато разчитате, че в `$lang` е текущият език на страницата, и не осъзнавате, че `foreach $langs as $lang` ви е презаписало тази променлива. -Цикълът foreach също може да бъде написан много елегантно и икономично с помощта на [n:attribute |syntax#n-attributes]: +Цикълът foreach може също много елегантно и икономично да се запише с помощта на [n:атрибут |syntax#n:атрибути]: ```latte <ul> @@ -369,7 +389,7 @@ Age: {date('Y') - $birth}<br> </ul> ``` -Знаете ли, че можете да използвате префикс n:attributes с `inner-`? Тогава в цикъла ще се повтаря само вътрешността на елемента: +Знаете ли, че към n:атрибутите можете да добавите префикс `inner-`? Тогава в цикъла ще се повтаря само вътрешността на елемента: ```latte <div n:inner-foreach="$items as $item"> @@ -378,7 +398,7 @@ Age: {date('Y') - $birth}<br> </div> ``` -Така ще се получи нещо подобно на: +Така ще се изведе нещо като: ```latte <div> @@ -390,17 +410,17 @@ Age: {date('Y') - $birth}<br> ``` -`{else}` .{data-version:2.9}{toc: foreach-else} ------------------------------------------------ +`{else}` .{toc: foreach-else} +----------------------------- -Цикълът `foreach` може да приеме незадължително изречение `{else}`, чийто текст се извежда, ако даденият масив е празен: +Вътре в цикъла `foreach` може да се посочи клауза `{else}`, чието съдържание се показва, ако цикълът е празен: ```latte <ul> {foreach $people as $person} <li>{$person->name}</li> {else} - <li><em>Sorry, no users in this list</em></li> + <li><em>Съжаляваме, в този списък няма потребители</em></li> {/foreach} </ul> ``` @@ -409,17 +429,17 @@ Age: {date('Y') - $birth}<br> `$iterator` ----------- -Променливата `$iterator` се инициализира в цикъла `foreach`. В него се съхранява важна информация за текущия цикъл. +Вътре в цикъла `foreach` Latte създава променливата `$iterator`, с помощта на която можем да установяваме полезна информация за протичащия цикъл: -- `$iterator->first` ли е първата итерация? -- `$iterator->last` е последната итерация? -- `$iterator->counter` - брой итерации, започва от 1 -- `$iterator->counter0` - брой итерации, започва от 0 .{data-version:2.9} -- `$iterator->odd` - странна ли е тази итерация? -- `$iterator->even` - тази итерация изобщо ли е? -- `$iterator->parent` - итератор около текущия итератор. .{data-version:2.9} +- `$iterator->first` - това първото преминаване през цикъла ли е? +- `$iterator->last` - това последното преминаване ли е? +- `$iterator->counter` - кое по ред е това преминаване, броейки от едно? +- `$iterator->counter0` - кое по ред е това преминаване, броейки от нула? +- `$iterator->odd` - това нечетно преминаване ли е? +- `$iterator->even` - това четно преминаване ли е? +- `$iterator->parent` - итераторът, обгръщащ текущия - `$iterator->nextValue` - следващият елемент в цикъла -- `$iterator->nextKey` - ключ на следващия елемент в цикъла +- `$iterator->nextKey` - ключът на следващия елемент в цикъла ```latte @@ -435,20 +455,19 @@ Age: {date('Y') - $birth}<br> {/foreach} ``` -Lata е умен и `$iterator->last` работи не само за масиви, но и когато цикълът работи върху общ итератор, при който броят на елементите не е предварително известен. +Latte е умно и `$iterator->last` работи не само при масиви, но и когато цикълът преминава през общ итератор, където броят на елементите не е известен предварително. `{first}` `{last}` `{sep}` -------------------------- -Тези тагове могат да се използват в рамките на цикъла `{foreach}`. Съдържанието на `{first}` се показва при първото преминаване. -Съдържанието на `{last}` се показва ... Можете ли да познаете? Да, за последното преминаване. Това всъщност са етикети за `{if $iterator->first}` и `{if $iterator->last}`. +Тези тагове могат да се използват вътре в цикъла `{foreach}`. Съдържанието на `{first}` се рендира, ако това е първото преминаване. Съдържанието на `{last}` се рендира… дали ще познаете? Да, ако това е последното преминаване. Всъщност това са съкращения за `{if $iterator->first}` и `{if $iterator->last}`. -Таговете могат да бъдат записани и като [n:attributes |syntax#n-attributes]: +Таговете могат също елегантно да се използват като [n:атрибут |syntax#n:атрибути]: ```latte {foreach $rows as $row} - {first}<h1>List of names</h1>{/first} + {first}<h1>Списък с имена</h1>{/first} <p>{$row->name}</p> @@ -456,7 +475,7 @@ Lata е умен и `$iterator->last` работи не само за масив {/foreach} ``` -Съдържанието на `{sep}` се извежда, ако итерацията не е последна, така че е подходящ за отпечатване на разделители, например запетаи между елементите на списъка: +Съдържанието на тага `{sep}` се рендира, ако преминаването не е последно, така че е подходящо за рендиране на разделители, например запетаи между извежданите елементи: ```latte {foreach $items as $item} {$item} {sep}, {/sep} {/foreach} @@ -465,12 +484,12 @@ Lata е умен и `$iterator->last` работи не само за масив Това е доста практично, нали? -`{iterateWhile}` .{data-version:2.10} -------------------------------------- +`{iterateWhile}` +---------------- -Опростява групирането на линейни данни по време на итерация в цикъл foreach чрез итерация във вложен цикъл, докато условието е изпълнено. [Прочетете инструкциите в книгата с рецепти |cookbook/iteratewhile]. +Опростява групирането на линейни данни по време на итерация в цикъл foreach, като извършва итерацията във вложен цикъл, докато условието е изпълнено. [Прочетете подробно ръководство|cookbook/grouping]. -Той може също така елегантно да замени `{first}` и `{last}` в примера по-горе: +Може също елегантно да замени `{first}` и `{last}` в примера по-горе: ```latte {foreach $rows as $row} @@ -487,19 +506,21 @@ Lata е умен и `$iterator->last` работи не само за масив {/foreach} ``` +Вижте също филтрите [batch |filters#batch] и [group |filters#group]. + `{for}` ------- -Записваме цикъла по същия начин, както в PHP: +Цикълът се записва по абсолютно същия начин като в PHP: ```latte {for $i = 0; $i < 10; $i++} - <span>Item #{$i}</span> + <span>Елемент {$i}</span> {/for} ``` -Тагът може да се запише и като [n:attribute |syntax#n-attributes]: +Тагът може също да се използва като [n:атрибут |syntax#n:атрибути]: ```latte <h1 n:for="$i = 0; $i < 10; $i++">{$i}</h1> @@ -509,7 +530,7 @@ Lata е умен и `$iterator->last` работи не само за масив `{while}` --------- -Отново записваме цикъла по същия начин, както в PHP: +Цикълът отново се записва по абсолютно същия начин като в PHP: ```latte {while $row = $result->fetch()} @@ -517,7 +538,7 @@ Lata е умен и `$iterator->last` работи не само за масив {/while} ``` -Или като [n:attribute |syntax#n-attributes]: +Или като [n:атрибут |syntax#n:атрибути]: ```latte <span n:while="$row = $result->fetch()"> @@ -525,7 +546,7 @@ Lata е умен и `$iterator->last` работи не само за масив </span> ``` -Условният вариант в края на тага съответства на цикъла do-while в PHP: +Възможен е и вариант с условие в затварящия таг, който съответства на PHP цикъла do-while: ```latte {while} @@ -537,7 +558,7 @@ Lata е умен и `$iterator->last` работи не само за масив `{continueIf}` `{skipIf}` `{breakIf}` ------------------------------------- -Съществуват специални маркери, които могат да се използват за управление на всеки цикъл - `{continueIf ?}` и `{breakIf ?}`, които преминават към следващата итерация и съответно прекратяват цикъла, когато са изпълнени условията: +За управление на всеки цикъл могат да се използват таговете `{continueIf ?}` и `{breakIf ?}`, които преминават към следващия елемент съответно прекратяват цикъла при изпълнение на условието: ```latte {foreach $rows as $row} @@ -547,8 +568,8 @@ Lata е умен и `$iterator->last` работи не само за масив {/foreach} ``` -.{data-version:2.9} -Тагът `{skipIf}` е много подобен на `{continueIf}`, но не увеличава брояча. По този начин няма да има дупки в номерацията при отпечатване на `$iterator->counter` и прескачане на някои елементи. Също така изречението {else} ще бъде изведено, ако всички елементи са пропуснати. + +Тагът `{skipIf}` е много подобен на `{continueIf}`, но не увеличава брояча `$iterator->counter`, така че ако го извеждаме и същевременно пропуснем някои елементи, няма да има дупки в номерирането. Също така клаузата `{else}` се рендира, когато пропуснем всички елементи. ```latte <ul> @@ -556,7 +577,7 @@ Lata е умен и `$iterator->last` работи не само за масив {skipIf $person->age < 18} <li>{$iterator->counter}. {$person->name}</li> {else} - <li><em>Sorry, no adult users in this list</em></li> + <li><em>Съжаляваме, в този списък няма възрастни</em></li> {/foreach} </ul> ``` @@ -565,72 +586,68 @@ Lata е умен и `$iterator->last` работи не само за масив `{exitIf}` .{data-version:3.0.5} -------------------------------- -Завършва визуализирането на шаблона или блока, когато условието е изпълнено. +Прекратява рендирането на шаблона или блока при изпълнение на условието (т.нар. "early exit"). ```latte {exitIf !$messages} -<h1>Messages</h1> +<h1>Съобщения</h1> <div n:foreach="$messages as $message"> {$message} </div> ``` -Разрешаване на шаблони .[#toc-including-templates] -================================================== +Вмъкване на шаблон +================== `{include 'file.latte'}` .{toc: include} ---------------------------------------- .[note] -Вижте също [`{include block}` |template-inheritance#Printing-Blocks] +Вижте също [`{include block}` |template-inheritance#Рендиране на блокове] -Тагът `{include}` зарежда и показва посочения шаблон. На любимия ни език PHP тя изглежда по следния начин +Тагът `{include}` зарежда и рендира посочения шаблон. Ако говорим на езика на нашия любим език PHP, това е нещо като: ```php <?php include 'header.phtml'; ?> ``` -Разрешените шаблони нямат достъп до променливите на активния контекст, но имат достъп до глобалните променливи. +Вмъкнатите шаблони нямат достъп до променливите на активния контекст, имат достъп само до глобалните променливи. -Можете да предавате променливи по този начин: +Можете да предавате променливи към вмъкнатия шаблон по следния начин: ```latte -{* от Latte 2.9 *} {include 'template.latte', foo: 'bar', id: 123} - -{* преди Latte 2.9 *} -{include 'template.latte', foo => 'bar', id => 123} ``` -Името на шаблона може да бъде произволен израз на PHP: +Името на шаблона може да бъде всякакъв израз в PHP: ```latte {include $someVar} {include $ajax ? 'ajax.latte' : 'not-ajax.latte'} ``` -Вмъкнатото съдържание може да се променя с помощта на [филтри |syntax#Filters]. Следващият пример премахва целия HTML и коригира регистрите: +Вмъкнатото съдържание може да бъде модифицирано с помощта на [филтри |syntax#Филтри]. Следващият пример премахва целия HTML и променя регистъра на буквите: ```latte <title>{include 'heading.latte' |stripHtml|capitalize} ``` -[Наследяването на шаблона **шаблонът |template inheritance] по подразбиране не участва** в това. Въпреки че можете да добавяте блокови тагове към шаблони за включване, те няма да заменят съответните блокове в шаблона, в който са включени. Разглеждайте блоковете за включване като независими и защитени части на страници или модули. Това поведение може да бъде променено с помощта на модификатора `with blocks` (от Latte 2.9.1): +По подразбиране [наследяването на шаблони|template-inheritance] в този случай не играе никаква роля. Въпреки че във включения шаблон можем да използваме блокове, не се извършва замяна на съответните блокове в шаблона, в който се включва. Мислете за включените шаблони като за самостоятелни изолирани части от страници или модули. Това поведение може да се промени с помощта на модификатора `with blocks`: ```latte {include 'template.latte' with blocks} ``` -Връзката между името на файла, посочено в тага, и файла на диска зависи от [програмата за зареждане |extending-latte#Loaders]. +Връзката между името на файла, посочено в тага, и файла на диска е въпрос на [loader|loaders]. -`{sandbox}` .{data-version:2.8} -------------------------------- +`{sandbox}` +----------- -Когато разрешавате генериран от крайния потребител шаблон, помислете за неговото изолиране (за повече информация вижте [документацията за изолиране |sandbox]): +При вмъкване на шаблон, създаден от краен потребител, трябва да обмислите sandbox режим (повече информация в [документация за sandbox |sandbox]): ```latte {sandbox 'untrusted.latte', level: 3, data: $menu} @@ -641,9 +658,9 @@ Lata е умен и `$iterator->last` работи не само за масив ========= .[note] -Вижте също [`{block name}` |template-inheritance#Blocks] +Вижте също [`{block name}` |template-inheritance#Блокове] -Блоковете без заглавие се използват, за да позволят прилагането на [филтри |syntax#Filters] към част от шаблона. Например можете да приложите филтър за [ленти |filters#strip], за да премахнете ненужните интервали: +Блоковете без име служат като начин за прилагане на [филтри |syntax#Филтри] към част от шаблона. Например така може да се приложи филтърът [strip |filters#spaceless], който премахва излишните интервали: ```latte {block|strip} @@ -654,16 +671,16 @@ Lata е умен и `$iterator->last` работи не само за масив ``` -Обработка на изключения .[#toc-exception-handling] -================================================== +Управление на изключения +======================== -`{try}` .{data-version:2.9} ---------------------------- +`{try}` +------- -Тези етикети улесняват създаването на надеждни шаблони. +Благодарение на този таг е изключително лесно да се създават здрави шаблони. -Ако при визуализиране на блок възникне изключение `{try}`, целият блок се изхвърля и визуализирането ще продължи след него: +Ако при рендиране на блока `{try}` възникне изключение, целият блок се отхвърля и рендирането ще продължи след него: ```latte {try} @@ -675,7 +692,7 @@ Lata е умен и `$iterator->last` работи не само за масив {/try} ``` -Съдържанието на незадължителната клауза `{else}` се визуализира само при възникване на изключение: +Съдържанието в незадължителната клауза `{else}` се рендира само когато възникне изключение: ```latte {try} @@ -685,11 +702,11 @@ Lata е умен и `$iterator->last` работи не само за масив {/foreach} {else} -

    Sorry, the tweets could not be loaded.

    +

    Съжаляваме, не успяхме да заредим туитовете.

    {/try} ``` -Тагът може да се запише и като [n:attribute |syntax#n-attributes]: +Тагът може също да се използва като [n:атрибут |syntax#n:атрибути]: ```latte
      @@ -697,13 +714,13 @@ Lata е умен и `$iterator->last` работи не само за масив
    ``` -Можете също така да дефинирате [свой собствен обработчик на изключения за |develop#Exception-Handler] регистриране: +Възможно е също да се дефинира собствен [персонализиран обработчик на изключения |develop#Exception handler], например за логване. -`{rollback}` .{data-version:2.9} --------------------------------- +`{rollback}` +------------ -Блокът `{try}` може също така да бъде спрян и ръчно пропуснат с помощта на `{rollback}`. По този начин не е необходимо да проверявате всички входни данни предварително, а само по време на рендирането можете да решите дали има смисъл да рендирате обекта. +Блокът `{try}` може да бъде спрян и пропуснат също и ръчно с помощта на `{rollback}`. Благодарение на това не е необходимо предварително да проверявате всички входни данни и чак по време на рендирането можете да решите, че изобщо не искате да рендирате обекта: ```latte {try} @@ -719,30 +736,30 @@ Lata е умен и `$iterator->last` работи не само за масив ``` -Променливи .[#toc-variables] -============================ +Променливи +========== `{var}` `{default}` ------------------- -Ще създадем нови променливи в шаблона, като използваме тага `{var}`: +Нови променливи създаваме в шаблона с тага `{var}`: ```latte {var $name = 'John Smith'} {var $age = 27} -{* Multiple declaration *} +{* Множествена декларация *} {var $name = 'John Smith', $age = 27} ``` -Тагът `{default}` работи по подобен начин, с изключение на това, че създава променливи само ако те не съществуват: +Тагът `{default}` работи подобно, но създава променливи само тогава, когато те не съществуват. Ако променливата вече съществува и съдържа стойност `null`, тя няма да бъде презаписана: ```latte -{default $lang = 'cs'} +{default $lang = 'bg'} ``` -От версия 2.7 на Latte можете да задавате и [типове променливи |type-system]. Засега те са информативни и Latte не ги проверява. +Можете да посочвате и [типове променливи|type-system]. Засега те са информативни и Latte не ги проверява. ```latte {var string $name = $article->getTitle()} @@ -750,10 +767,10 @@ Lata е умен и `$iterator->last` работи не само за масив ``` -`{parameters}` .{data-version:2.9} ----------------------------------- +`{parameters}` +-------------- -Точно както функцията декларира своите параметри, шаблонът може да декларира своите променливи в началото: +Точно както функцията декларира своите параметри, така и шаблонът може в началото да декларира своите променливи: ```latte {parameters @@ -763,15 +780,15 @@ Lata е умен и `$iterator->last` работи не само за масив } ``` -Променливите `$a` и `$b` без стойност по подразбиране автоматично се подразбират на `null`. Декларираните типове остават информативни и Latte не ги проверява. +Променливите `$a` и `$b` без посочена стойност по подразбиране автоматично имат стойност по подразбиране `null`. Декларираните типове засега са информативни и Latte не ги проверява. -В противен случай декларираните променливи не се предават на шаблона. Това е различно от тага `{default}`. +Други променливи освен декларираните не се пренасят в шаблона. С това се различава от тага `{default}`. `{capture}` ----------- -С помощта на маркера `{capture}`, можете да уловите изхода в променлива: +Улавя изхода в променлива: ```latte {capture $var} @@ -780,10 +797,10 @@ Lata е умен и `$iterator->last` работи не само за масив {/capture} -

    Captured: {$var}

    +

    Уловено: {$var}

    ``` -Тагът може да се запише и като [n:attribute |syntax#n-attributes]: +Тагът може, подобно на всеки двоен таг, да се запише и като [n:атрибут |syntax#n:атрибути]: ```latte
      @@ -791,15 +808,17 @@ Lata е умен и `$iterator->last` работи не само за масив
    ``` +HTML изходът се записва в променливата `$var` във вид на обект `Latte\Runtime\Html`, за да [не се стигне до нежелано екраниране |develop#Изключване на автоматичното екраниране на променлива] при извеждане. + -Други .[#toc-others] -==================== +Други +===== `{contentType}` --------------- -Използвайте таг, за да определите какъв тип съдържание представлява шаблонът. Възможни са следните опции: +С тага указвате какъв тип съдържание представлява шаблонът. Възможностите са: - `html` (тип по подразбиране) - `xml` @@ -808,9 +827,9 @@ Lata е умен и `$iterator->last` работи не само за масив - `calendar` (iCal) - `text` -Използването му е важно, тъй като той задава [контекстно ескапиране |safety-first#Context-Aware-Escaping] и само тогава Latte може да ескапира правилно. Например `{contentType xml}` превключва на режим XML, а `{contentType text}` деактивира напълно ескапирането. +Неговото използване е важно, защото настройва [контекстно-чувствително екраниране |safety-first#Контекстно-чувствително екраниране] и само така може да екранира правилно. Например `{contentType xml}` превключва в режим XML, `{contentType text}` напълно изключва екранирането. -Ако параметърът е пълен MIME тип, като например `application/xml`, той също така изпраща HTTP заглавие към браузъра `Content-Type`: +Ако параметърът е пълноценен MIME тип, като например `application/xml`, тогава още допълнително изпраща HTTP хедър `Content-Type` към браузъра: ```latte {contentType application/xml} @@ -829,46 +848,50 @@ Lata е умен и `$iterator->last` работи не само за масив `{debugbreak}` -------------- -Посочва мястото, където изпълнението на кода се прекъсва. Използва се за отстраняване на грешки, за да може програмистът да провери средата на изпълнение и да се увери, че кодът се изпълнява според очакванията. Поддържа се от [Xdebug |https://xdebug.org]. Възможно е също така да се посочи условието, при което кодът трябва да се прекъсне. +Означава място, където изпълнението на програмата ще бъде спряно и ще се стартира дебъгерът, за да може програмистът да извърши инспекция на средата на изпълнение и да установи дали програмата работи според очакванията. Поддържа [Xdebug |https://xdebug.org/]. Може да се добави условие, което определя кога програмата трябва да бъде спряна. ```latte -{debugbreak} {* прекъсва програмата *} +{debugbreak} {* спира програмата *} -{debugbreak $counter == 1} {* прекъсва програмата, ако условието е изпълнено *} +{debugbreak $counter == 1} {* спира програмата при изпълнение на условието *} ``` `{do}` ------ -Изпълнява кода и не отпечатва нищо. +Изпълнява PHP код и нищо не извежда. Както при всички други тагове, под PHP код се разбира един израз, вижте [ограничения на PHP |syntax#Ограничения на PHP в Latte]. ```latte {do $num++} ``` -Latte 2.7 и по-ранните версии използват `{php}`. - `{dump}` -------- -Разтоварване на променлива или на текущия контекст. +Извежда променлива или текущия контекст. ```latte -{dump $name} {* изхвърля променливата $name *} +{dump $name} {* Извежда променливата $name *} -{dump} {* изхвърля всички дефинирани променливи *} +{dump} {* Извежда всички текущо дефинирани променливи *} ``` .[caution] -Изисква се пакет [Tracy |tracy:]. +Изисква библиотеката [Tracy|tracy:]. + + +`{php}` +------- + +Позволява изпълнението на всякакъв PHP код. Тагът трябва да бъде активиран с помощта на разширението [RawPhpExtension |develop#RawPhpExtension]. `{spaceless}` ------------- -Премахва ненужните бели символи. Подобно на филтъра [без бели полета |filters#spaceless]. +Премахва излишното празно пространство от изхода. Работи подобно на филтъра [spaceless |filters#spaceless]. ```latte {spaceless} @@ -878,52 +901,52 @@ Latte 2.7 и по-ранните версии използват `{php}`. {/spaceless} ``` -Изход: +Генерира ```latte
    • Hello
    ``` -Тагът може да се запише и като [n:attribute |syntax#n-attributes]: +Тагът може също да се запише като [n:атрибут |syntax#n:атрибути]. `{syntax}` ---------- -Не е необходимо таговете Latte да бъдат затворени само в единични къдрави скоби. Можете да изберете различен разделител, дори по време на изпълнение. Това се прави с помощта на `{syntax…}`, където параметърът може да бъде: +Latte таговете не е задължително да бъдат оградени само с единични къдрави скоби. Можем да изберем и друг разделител, и то дори по време на изпълнение. За това служи `{syntax …}`, където като параметър може да се посочи: -- двойно: `{{...}}` -- off: деактивира напълно маркерите Latte +- double: `{{...}}` +- off: напълно изключва обработката на Latte тагове -Като използваме означението n:attribute, можем да забраним Latte само за блок на JavaScript: +С използването на n:атрибути може да се изключи Latte например само за един блок JavaScript: ```latte ``` -Latte може да се използва много удобно в JavaScript, само избягвайте конструкции като в този пример, където буквата следва непосредствено след `{`, вижте [Latte в JavaScript или CSS |recipes#Latte-Inside-JavaScript-or-CSS]. +Latte може много удобно да се използва и вътре в JavaScript, достатъчно е да се избягват конструкции като в този пример, когато след `{` веднага следва буква, вижте [Latte в JavaScript или CSS |recipes#Latte в JavaScript или CSS]. -Ако деактивирате Latte с `{syntax off}` (т.е. тага, а не атрибута n:), той ще игнорира стриктно всички тагове до `{/syntax}`. +Ако изключите Latte с помощта на `{syntax off}` (т.е. с таг, а не с n:атрибут), той ще игнорира стриктно всички тагове до `{/syntax}` -{trace} .{data-version:2.10} ----------------------------- +{trace} +------- -Хвърля изключение `Latte\RuntimeException`, чието проследяване на стека е направено в духа на шаблоните. Така че вместо извикване на функции и методи се използват извикване на блокове и вмъкване на шаблони. Ако използвате инструмент за визуално показване на хвърлени изключения, като например [Tracy |tracy:], ще видите ясно стека на извикванията, включително всички подадени аргументи. +Хвърля изключение `Latte\RuntimeException`, чийто stack trace е в духа на шаблоните. Тоест вместо извиквания на функции и методи съдържа извиквания на блокове и вмъквания на шаблони. Ако използвате инструмент за прегледно показване на хвърлените изключения, като например [Tracy|tracy:], прегледно ще ви се покаже call stack, включително всички предавани аргументи. -Помощни средства за HTML тагове .[#toc-html-tag-helpers] -======================================================== +Помощници за HTML кодера +======================== -n:class .[#toc-n-class] ------------------------ +n:class +------- -С помощта на `n:class` е много лесно да генерирате HTML атрибута `class` точно по желания от вас начин. +Благодарение на `n:class` много лесно генерирате HTML атрибута `class` точно според представите. -Пример: Искам активният елемент да има клас `active`: +Пример: трябва активният елемент да има клас `active`: ```latte {foreach $items as $item} @@ -931,7 +954,7 @@ n:class .[#toc-n-class] {/foreach} ``` -Освен това ми е необходимо първият елемент да има класовете `first` и `main`: +И освен това, първият елемент да има класове `first` и `main`: ```latte {foreach $items as $item} @@ -939,7 +962,7 @@ n:class .[#toc-n-class] {/foreach} ``` -И всички елементи трябва да имат клас `list-item`: +И всички елементи да имат клас `list-item`: ```latte {foreach $items as $item} @@ -950,10 +973,10 @@ n:class .[#toc-n-class] Удивително просто, нали? -n:attr .[#toc-n-attr] ---------------------- +n:attr +------ -Атрибутът `n:attr` може да генерира произволни HTML атрибути със същата елегантност като [n:class |#n-class]. +Атрибутът `n:attr` може със същата елегантност като [#n:class] да генерира всякакви HTML атрибути. ```latte {foreach $data as $item} @@ -961,7 +984,7 @@ n:attr .[#toc-n-attr] {/foreach} ``` -В зависимост от върнатите стойности се показват например: +В зависимост от върнатите стойности ще изведе напр.: ```latte @@ -972,26 +995,28 @@ n:attr .[#toc-n-attr] ``` -n:tag .[#toc-n-tag] -------------------- +n:tag +----- -Атрибутът `n:tag` може динамично да променя името на HTML елемент. +Атрибутът `n:tag` може динамично да променя името на HTML елемента. ```latte

    {$title}

    ``` -Ако `$heading === null`, то `

    ` се визуализира без промяна. В противен случай името на елемента се променя в стойност на променлива, така че за `$heading === 'h3'` се записва: +Ако `$heading === null`, ще се изведе без промяна тагът `

    `. Иначе името на елемента ще се промени на стойността на променливата, така че за `$heading === 'h3'` ще се изведе: ```latte

    ...

    ``` +Тъй като Latte е сигурна система за шаблони, тя проверява дали новото име на тага е валидно и не съдържа никакви нежелани или вредни стойности. -n:ifcontent .[#toc-n-ifcontent] -------------------------------- -Предотвратява отпечатването на празен HTML елемент, т.е. елемент, съдържащ само интервали. +n:ifcontent +----------- + +Предотвратява извеждането на празен HTML елемент, т.е. елемент, който не съдържа нищо освен интервали. ```latte
    @@ -999,7 +1024,7 @@ n:ifcontent .[#toc-n-ifcontent]
    ``` -В зависимост от стойностите на променливата ще бъде отпечатан `$error`: +Извежда в зависимост от стойността на променливата `$error`: ```latte {* $error = '' *} @@ -1013,10 +1038,10 @@ n:ifcontent .[#toc-n-ifcontent] ``` -Превод .{data-version:3.0}[#toc-translation] -============================================ +Преводи +======= -За да работят етикетите за превод, трябва да настроите [преводач |develop#TranslatorExtension]. Можете също така да използвате [`translate` |filters#translate] филтър за превод. +За да работят таговете за превод, е необходимо да [активирате преводача |develop#TranslatorExtension]. За превод можете да използвате и филтъра [`translate` |filters#translate]. `{_...}` @@ -1025,30 +1050,30 @@ n:ifcontent .[#toc-n-ifcontent] Превежда стойности на други езици. ```latte -{_'Basket'} +{_'Кошница'} {_$item} ``` -Други параметри също могат да бъдат прехвърлени към транслатора: +На преводача могат да се предават и други параметри: ```latte -{_'Basket', domain: order} +{_'Кошница', domain: order} ``` `{translate}` ------------- -Překládá části šablony: +Превежда части от шаблона: ```latte -

    {translate}Order{/translate}

    +

    {translate}Поръчка{/translate}

    {translate domain: order}Lorem ipsum ...{/translate} ``` -Тагът може да се запише и като [n:attribute |syntax#n-attributes], за да се преведе вътрешната част на елемента: +Тагът може също да се запише като [n:атрибут |syntax#n:атрибути], за превод на вътрешността на елемента: ```latte -

    Order

    +

    Поръчка

    ``` diff --git a/latte/bg/template-inheritance.texy b/latte/bg/template-inheritance.texy index 921d9ad67e..b3a576ed80 100644 --- a/latte/bg/template-inheritance.texy +++ b/latte/bg/template-inheritance.texy @@ -1,16 +1,16 @@ -Наследяване на шаблони и възможност за повторно използване -********************************************************** +Наследяване и повторно използване на шаблони +******************************************** .[perex] -Механизмите за повторна употреба и наследяване на шаблони увеличават производителността ви, тъй като всеки шаблон съдържа само уникално съдържание, а повтарящите се елементи и структури се използват повторно. Въвеждаме три концепции: [наследяване на оформлението |#Layout-Inheritance], [хоризонтално повторно използване |#Horizontal-Reuse] и [наследяване на единици |#Unit-Inheritance]. +Механизмите за повторно използване и наследяване на шаблони ще повишат вашата производителност, тъй като всеки шаблон съдържа само своето уникално съдържание, а повтарящите се елементи и структури се използват повторно. Представяме три концепции: [#Наследяване на лейаут], [#Хоризонтално повторно използване] и [#Единично наследяване]. -Концепцията за наследяване на шаблони Latte е подобна на тази за наследяване на класове в PHP. Дефинирате **шаблон-родител**, от който други **наследени шаблони** могат да надграждат и да отменят части от шаблона-родител. Това работи чудесно, когато елементите имат обща структура. Звучи сложно? Не се притеснявайте, не е така. +Концепцията за наследяване на шаблони в Latte е подобна на наследяването на класове в PHP. Вие дефинирате **родителски шаблон**, от който други **дъщерни шаблони** могат да наследят и да презапишат части от родителския шаблон. Това работи чудесно, когато елементите споделят обща структура. Звучи ли сложно? Не се притеснявайте, много е лесно. -Наследяване на оформлението `{layout}` .{toc: Layout Inheritance} -================================================================= +Наследяване на лейаут `{layout}` .{toc: Наследяване на лейаут} +============================================================== -Нека разгледаме наследяването на шаблона на оформлението с пример. Това е родителският шаблон, който в примера ще наречем `layout.latte`, и той определя HTML скелета на документа. +Нека разгледаме наследяването на шаблон за оформление, т.е. лейаут, директно с пример. Това е родителски шаблон, който ще наречем например `layout.latte` и който дефинира скелета на HTML документ: ```latte @@ -30,9 +30,9 @@ ``` -Тагът `{block}` дефинира три блока, които подчинените шаблони могат да попълват. Единственото, което тагът за блок прави, е да укаже на шаблониращата машина, че подчиненият шаблон може да замени тези части на шаблона, като дефинира свой собствен блок със същото име. +Таговете `{block}` дефинират три блока, които дъщерните шаблони могат да запълнят. Тагът block прави само това, че обявява, че това място може да бъде презаписано от дъщерен шаблон чрез дефиниране на собствен блок със същото име. -Един шаблон на дете може да изглежда по следния начин: +Дъщерният шаблон може да изглежда така: ```latte {layout 'layout.latte'} @@ -44,11 +44,11 @@ {/block} ``` -Ключовият етикет тук е `{layout}`. Той указва на шаблониращата машина, че този шаблон "разширява" друг шаблон. Когато Latte визуализира този шаблон, той първо намира родителя - в случая `layout.latte`. +Ключът тук е тагът `{layout}`. Той казва на Latte, че този шаблон "разширява" друг шаблон. Когато Latte рендира този шаблон, първо намира родителския шаблон - в този случай `layout.latte`. -В този момент механизмът за шаблониране ще забележи три блокови тага в `layout.latte` и ще замени тези блокове със съдържанието на подчинения шаблон. Имайте предвид, че тъй като шаблонът на детето не е дефинирал блок *footer*, вместо него се използва съдържанието на шаблона на родителя. Съдържанието в тага `{block}` в родителския шаблон винаги се използва като резервен вариант. +В този момент Latte забелязва трите блокови тага в `layout.latte` и заменя тези блокове със съдържанието на дъщерния шаблон. Тъй като дъщерният шаблон не е дефинирал блока *footer*, вместо това се използва съдържанието от родителския шаблон. Съдържанието в тага `{block}` в родителския шаблон винаги се използва като резервно. -Изходът може да изглежда по следния начин: +Изходът може да изглежда така: ```latte @@ -68,7 +68,7 @@ ``` -В подчинен шаблон блоковете могат да се поставят само на горното ниво или в друг блок, т.е: +В дъщерния шаблон блоковете могат да бъдат поставени само на най-високо ниво или вътре в друг блок, т.е.: ```latte {block content} @@ -76,7 +76,7 @@ {/block} ``` -Освен това блокът винаги ще бъде създаден, независимо дали заобикалящото го условие `{if}` е оценено като вярно или невярно. Противно на това, което си мислите, този шаблон дефинира блок. +Също така, блок винаги ще бъде създаден, независимо дали заобикалящото го условие `{if}` се оценява като вярно или невярно. Така че, дори да не изглежда така, този шаблон ще дефинира блока. ```latte {if false} @@ -86,7 +86,7 @@ {/if} ``` -Ако искате изходът в блока да се показва условно, използвайте следното: +Ако искате изходът вътре в блока да се показва условно, използвайте следното вместо това: ```latte {block head} @@ -96,7 +96,7 @@ {/block} ``` -Извънблоковите данни в подчинения шаблон се изпълняват преди шаблонът за оформление да бъде визуализиран, така че можете да го използвате, за да дефинирате променливи от тип `{var $foo = bar}` и да разпространявате данните по цялата верига на наследяване: +Пространството извън блоковете в дъщерния шаблон се изпълнява преди рендирането на шаблона на лейаута, така че можете да го използвате за дефиниране на променливи като `{var $foo = bar}` и за разпространение на данни по цялата верига на наследяване: ```latte {layout 'layout.latte'} @@ -106,51 +106,50 @@ ``` -Наследяване на няколко нива .[#toc-multilevel-inheritance] ----------------------------------------------------------- -Можете да използвате толкова нива на наследяване, колкото ви е необходимо. Един от разпространените начини за използване на наследяване на оформлението е следният подход на три нива: +Многостепенно наследяване +------------------------- +Можете да използвате толкова нива на наследяване, колкото са ви необходими. Обичайният начин за използване на наследяването на лейаути е следният тристепенен подход: -1) Създайте шаблон `layout.latte`, в който се съхранява основният външен вид на сайта ви. -2) Създайте шаблон `layout-SECTIONNAME.latte` за всеки раздел на сайта си. Например `layout-news.latte`, `layout-blog.latte` и т.н. Всички тези шаблони разширяват `layout.latte` и включват стилове/дизайни за всеки раздел. -3) Създайте отделни шаблони за всеки тип страница, например новинарска статия или публикация в блог. Тези шаблони разширяват съответния шаблон на раздела. +1) Създайте шаблон `layout.latte`, който съдържа основния скелет на външния вид на сайта. +2) Създайте шаблон `layout-SECTIONNAME.latte` за всяка секция на вашия сайт. Например `layout-news.latte`, `layout-blog.latte` и т.н. Всички тези шаблони разширяват `layout.latte` и включват стилове и дизайн, специфични за всяка секция. +3) Създайте индивидуални шаблони за всеки тип страница, например новинарска статия или публикация в блог. Тези шаблони разширяват съответния шаблон на секцията. -Динамично наследяване на оформлението .[#toc-dynamic-layout-inheritance] ------------------------------------------------------------------------- -Можете да използвате променлива или какъвто и да е израз на PHP като име на родителския шаблон, така че наследяването да се извършва динамично: +Динамично наследяване +--------------------- +Като име на родителския шаблон може да се използва променлива или произволен PHP израз, така че наследяването може да се държи динамично: ```latte {layout $standalone ? 'minimum.latte' : 'layout.latte'} ``` -Можете също така да използвате приложния програмен интерфейс Latte API за [автоматично |develop#Automatic-Layout-Lookup] избиране на шаблона за оформление. +Можете също да използвате Latte API за [автоматично |develop#Автоматично намиране на лейаут] избиране на шаблон за лейаут. -Съвети .[#toc-tips] -------------------- -Ето няколко съвета за работа с наследяване на оформлението: +Съвети +------ +Ето няколко съвета за работа с наследяване на лейаути: - Ако използвате `{layout}` в шаблон, той трябва да бъде първият таг на шаблона в този шаблон. -- Макетът може да се [търси автоматично |develop#automatic-layout-lookup] (както в [презентаторите |application:templates#search-for-templates]). В този случай, ако шаблонът не трябва да има оформление, той ще посочи това с тага `{layout none}`. +- Лейаутът може да се [търси автоматично |develop#Автоматично намиране на лейаут] (както например в [презентери |application:templates#Търсене на шаблони]). В такъв случай, ако шаблонът не трябва да има лейаут, той го обявява с тага `{layout none}`. - Тагът `{layout}` има псевдоним `{extends}`. -- Името на разширения файл на шаблона зависи от [програмата за зареждане на шаблони |extending-latte#Loaders]. +- Името на файла на лейаута зависи от [loader |loaders]. -- Можете да имате толкова блокове, колкото искате. Не забравяйте, че не е задължително подчинените шаблони да дефинират всички родителски блокове, така че можете да въведете разумни стойности по подразбиране в няколко блока и след това да дефинирате само тези, които са ви необходими по-късно. +- Можете да имате толкова блокове, колкото искате. Помнете, че дъщерните шаблони не е необходимо да дефинират всички родителски блокове, така че можете да попълните разумни стойности по подразбиране в няколко блока и след това да дефинирате само тези, от които се нуждаете по-късно. -Блокове `{block}` .{toc: Blocks} -================================ +Блокове `{block}` .{toc: Блокове} +================================= .[note] -Вижте също анонимни [`{block}` |tags#block] +Вижте също анонимен [`{block}` |tags#block] -Блокът ви позволява да променяте показването на определена част от шаблона, но не се намесва по никакъв начин в заобикалящата го логика. Нека разгледаме следния пример, за да илюстрираме как работи блокът и, което е по-важно, как не работи: +Блокът представлява начин за промяна на начина, по който се рендира определена част от шаблона, но по никакъв начин не се намесва в логиката около него. В следващия пример ще покажем как работи блокът, но също и как не работи: -```latte -{* parent.Latte *} +```latte .{file: parent.latte} {foreach $posts as $post} {block post}

    {$post->title}

    @@ -159,10 +158,9 @@ {/foreach} ``` -Ако покажете този модел, резултатът е абсолютно същият със или без блокови тагове. Блоковете имат достъп до променливи от външни диапазони. Това е просто начин да се направи така, че да може да се пренастройва за шаблона на детето: +Ако рендирате този шаблон, резултатът ще бъде абсолютно същият със или без таговете `{block}`. Блоковете имат достъп до променливи от външни обхвати. Те просто дават възможност да бъдат презаписани от дъщерен шаблон: -```latte -{* child.Latte *} +```latte .{file: child.latte} {layout 'parent.Latte'} {block post} @@ -173,7 +171,7 @@ {/block} ``` -Сега при визуализиране на шаблона на детето цикълът ще използва блока, дефиниран в шаблона на детето `child.Latte`, вместо блока, дефиниран в базовия шаблон `parent.Latte`; изпълненият шаблон ще бъде еквивалентен на следния: +Сега, при рендиране на дъщерния шаблон, цикълът ще използва блока, дефиниран в дъщерния шаблон `child.Latte`, вместо блока, дефиниран в `parent.Latte`; изпълненият шаблон тогава е еквивалентен на следното: ```latte {foreach $posts as $post} @@ -184,7 +182,7 @@ {/foreach} ``` -Ако обаче създадем нова променлива вътре в именуван блок или заменим стойността на съществуваща променлива, промяната ще бъде видима само вътре в блока: +Ако обаче създадем нова променлива вътре в именуван блок или заменим стойността на съществуваща, промяната ще бъде видима само вътре в блока: ```latte {var $foo = 'foo'} @@ -193,17 +191,17 @@ {var $bar = 'bar'} {/block} -foo: {$foo} // prints: foo -bar: {$bar ?? 'not defined'} // prints: not defined +foo: {$foo} // отпечатва: foo +bar: {$bar ?? 'not defined'} // отпечатва: not defined ``` -Съдържанието на блока може да се променя с помощта на [филтри |syntax#Filters]. В следващия пример целият HTML е премахнат и е дадено заглавието: +Съдържанието на блока може да бъде модифицирано с помощта на [филтри |syntax#Филтри]. Следният пример премахва целия HTML и променя регистъра на буквите: ```latte {block title|stripHtml|capitalize}...{/block} ``` -Тагът може да се запише и като [n:attribute |syntax#n-attributes]: +Тагът може да бъде записан и като [n:атрибут |syntax#n:атрибути]: ```latte
    @@ -212,10 +210,10 @@ bar: {$bar ?? 'not defined'} // prints: not defined ``` -Местни блокове .{data-version:2.9}[#toc-local-blocks] ------------------------------------------------------ +Локални блокове +--------------- -Всеки блок отменя съдържанието на родителския блок със същото име. С изключение на местните блокове. Те донякъде приличат на частните методи в класа. Можете да създадете шаблон, без да се притеснявате, че той ще бъде презаписан от втори шаблон поради съвпадащите имена на блоковете. +Всеки блок презаписва съдържанието на родителския блок със същото име – с изключение на локалните блокове. В класовете това би било нещо като частни методи. По този начин можете да създавате шаблон, без да се притеснявате, че поради съвпадение на имената на блоковете, те ще бъдат презаписани от друг шаблон. ```latte {block local helper} @@ -224,13 +222,13 @@ bar: {$bar ?? 'not defined'} // prints: not defined ``` -Блокове за печат `{include}` .{toc: Printing Blocks} ----------------------------------------------------- +Рендиране на блокове `{include}` .{toc: Рендиране на блокове} +------------------------------------------------------------- .[note] Вижте също [`{include file}` |tags#include] -За да отпечатате блок на определено място, използвайте маркера `{include blockname}`: +За да изведете блок на определено място, използвайте тага `{include blockname}`: ```latte {block title}{/block} @@ -238,32 +236,28 @@ bar: {$bar ?? 'not defined'} // prints: not defined

    {include title}

    ``` -Можете също така да изведете блок от друг шаблон: +Можете също да изведете блок от друг шаблон: ```latte {include footer from 'main.latte'} ``` -Производните блокове нямат достъп до променливите на активния контекст, освен ако блокът не е дефиниран в същия файл, в който е включен. Те обаче имат достъп до глобални променливи. +Рендираният блок няма достъп до променливите на активния контекст, освен ако блокът не е дефиниран в същия файл, в който е вмъкнат. Въпреки това, той има достъп до глобалните променливи. -Можете да предавате променливи по този начин: +Можете да предавате променливи към блока по този начин: ```latte -{* от Latte 2.9 *} {include footer, foo: bar, id: 123} - -{* преди Latte 2.9 *} -{include footer, foo => bar, id => 123} ``` -За име на блока можете да използвате променлива или друг израз в PHP. В този случай добавете ключовата дума `block` преди променливата, така че по време на компилиране да се знае, че това е блок, а не [шаблон за вмъкване |tags#include], чието име също може да бъде в променливата: +Като име на блока може да се използва променлива или произволен израз в PHP. В такъв случай, преди променливата добавяме ключовата дума `block`, за да може Latte още по време на компилация да знае, че става въпрос за блок, а не за [вмъкване на шаблон |tags#include], чието име също може да бъде в променлива: ```latte {var $name = footer} {include block $name} ``` -Блокът може да бъде отпечатан и вътре в себе си, което е полезно например при визуализиране на дървовидна структура: +Блокът може да бъде рендиран и вътре в себе си, което е полезно например при рендиране на дървовидна структура: ```latte {define menu, $items} @@ -281,19 +275,19 @@ bar: {$bar ?? 'not defined'} // prints: not defined {/define} ``` -Вместо `{include menu, ...}` можете да напишете и `{include this, ...}`, където `this` означава текущия блок. +Вместо `{include menu, ...}` можем да напишем `{include this, ...}`, където `this` означава текущия блок. -Изходното съдържание може да се променя с помощта на [филтри |syntax#Filters]. В следващия пример целият HTML е премахнат и е вмъкнато заглавието: +Рендираният блок може да бъде модифициран с помощта на [филтри |syntax#Филтри]. Следният пример премахва целия HTML и променя регистъра на буквите: ```latte {include heading|stripHtml|capitalize} ``` -Родителски блок .[#toc-parent-block] ------------------------------------- +Родителски блок +--------------- -Ако трябва да визуализирате съдържанието на родителски блок от родителски шаблон, операторът `{include parent}` е полезен. Това е полезно, ако искате да разширите съдържанието на родителския блок, а не да го замените изцяло. +Ако трябва да изведете съдържанието на блок от родителския шаблон, използвайте `{include parent}`. Това е полезно, ако искате само да допълните съдържанието на родителския блок, вместо да го презапишете напълно. ```latte {block footer} @@ -304,32 +298,31 @@ bar: {$bar ?? 'not defined'} // prints: not defined ``` -Определения `{define}` .{toc: Definitions} ------------------------------------------- +Дефиниции `{define}` .{toc: Дефиниции} +-------------------------------------- -Освен блокове в Latte има и "дефиниции". Те могат да се сравнят с функциите в традиционните езици за програмиране. Те са полезни за повторно използване на фрагменти от шаблони, така че да не се повтарят. +Освен блокове, в Latte съществуват и "дефиниции". В обикновените езици за програмиране бихме ги сравнили с функции. Те са полезни за повторно използване на фрагменти от шаблони, за да не се повтаряте. -Latte се опитва да опрости нещата, така че основно дефинициите са същите като блоковете и **всичко, казано за блоковете, се отнася и за дефинициите**. Той се различава от блоковете само по три начина: +Latte се стреми да прави нещата прости, така че по същество дефинициите са същите като блоковете и **всичко, което е казано за блоковете, важи и за дефинициите**. Те се различават от блоковете по това, че: -1) те могат да приемат аргументи -2) те не могат да имат [филтри |syntax#Filters] -3) те са затворени в тагове `{define}`, а съдържанието в тези тагове не се изпраща на изхода, докато не ги активирате. Това ви позволява да ги създавате, където пожелаете: +1) са затворени в тагове `{define}` +2) рендират се едва когато ги вмъкнете чрез `{include}` +3) могат да им се дефинират параметри, подобно на функциите в PHP ```latte {block foo}

    Hello

    {/block} -{* prints:

    Hello

    *} +{* отпечатва:

    Hello

    *} {define bar}

    World

    {/define} -{* prints nothing *} +{* не отпечатва нищо *} {include bar} -{* prints:

    World

    *} +{* отпечатва:

    World

    *} ``` -Представете си, че разполагате с общ шаблон за помощ, който определя как да се визуализират HTML формуляри с помощта на дефиниции: +Представете си, че имате помощен шаблон с колекция от дефиниции за това как да рисувате HTML форми. -```latte -{* forms.latte *} +```latte .{file: forms.latte} {define input, $name, $value, $type = 'text'} {/define} @@ -339,34 +332,32 @@ Latte се опитва да опрости нещата, така че осно {/define} ``` -Аргументите за дефиниция са винаги незадължителни със стойност по подразбиране `null`, освен ако не е посочена стойност по подразбиране (тук `text` е стойността по подразбиране за `$type`, възможна от Latte 2.9.1). От Latte 2.7 могат да се декларират и типове параметри: `{define input, string $name, ...}`. +Аргументите винаги са незадължителни със стойност по подразбиране `null`, освен ако не е посочена стойност по подразбиране (тук `'text'` е стойността по подразбиране за `$type`). Могат да се декларират и типове параметри: `{define input, string $name, ...}`. -Дефинициите нямат достъп до променливите на активния контекст, но имат достъп до глобалните променливи. - -Те се включват по същия начин като [блоковете |#Printing-Blocks]: +Шаблонът с дефиниции се зарежда с помощта на [`{import}` |#Хоризонтално повторно използване]. Самите дефиниции се рендират [по същия начин като блоковете |#Рендиране на блокове]: ```latte

    {include input, 'password', null, 'password'}

    {include textarea, 'comment'}

    ``` +Дефинициите нямат достъп до променливите на активния контекст, но имат достъп до глобалните променливи. -Динамични имена на блокове .[#toc-dynamic-block-names] ------------------------------------------------------- -Latte позволява много гъвкаво именуване на блокове, тъй като името на блока може да бъде всеки израз на PHP. В този пример са дефинирани три блока с имена `hi-Peter`, `hi-John` и `hi-Mary`: +Динамични имена на блокове +-------------------------- -```latte -{* parent.latte *} +Latte позволява голяма гъвкавост при дефинирането на блокове, тъй като името на блока може да бъде произволен PHP израз. Този пример дефинира три блока с имена `hi-Peter`, `hi-John` и `hi-Mary`: + +```latte .{file: parent.latte} {foreach [Peter, John, Mary] as $name} {block "hi-$name"}Hi, I am {$name}.{/block} {/foreach} ``` -Например, можем да заменим само един блок в шаблона на детето: +В дъщерния шаблон тогава можем да предефинираме например само един блок: -```latte -{* child.latte *} +```latte .{file: child.latte} {block hi-John}Hello. I am {$name}.{/block} ``` @@ -379,13 +370,13 @@ Hi, I am Mary. ``` -Проверка на съществуването на блока `{ifset}` .{toc: Checking Block Existence} ------------------------------------------------------------------------------- +Проверка за съществуване на блокове `{ifset}` .{toc: Проверка за съществуване на блокове} +----------------------------------------------------------------------------------------- .[note] -Вижте също [`{ifset $var}` |tags#ifset-elseifset] +Вижте също [`{ifset $var}` |tags#ifset elseifset] -Използвайте теста `{ifset blockname}`, за да проверите дали даден блок (или няколко блока) съществува в текущия контекст: +С помощта на теста `{ifset blockname}` проверяваме дали в текущия контекст съществува блок (или повече блокове): ```latte {ifset footer} @@ -397,7 +388,7 @@ Hi, I am Mary. {/ifset} ``` -За име на блока можете да използвате променлива или друг израз в PHP. В този случай добавете ключовата дума `block` пред променливата, за да стане ясно, че [тя |tags#ifset-elseifset] не е [тази, която |tags#ifset-elseifset] се тества: +Като име на блока може да се използва променлива или произволен израз в PHP. В такъв случай, преди променливата добавяме ключовата дума `block`, за да е ясно, че не става въпрос за тест за съществуване на [променливи |tags#ifset elseifset]: ```latte {ifset block $name} @@ -405,71 +396,69 @@ Hi, I am Mary. {/ifset} ``` +Съществуването на блокове се проверява и от функцията [`hasBlock()` |functions#hasBlock]: -Съвети .[#toc-tips] -------------------- -Ето няколко съвета за работа с блокове: - -- Последният блок от най-високо ниво не трябва да има затварящ таг (блокът завършва с края на документа). Това улеснява писането на шаблони за деца с един основен блок. +```latte +{if hasBlock(header) || hasBlock(footer)} + ... +{/if} +``` -- За по-голяма прегледност можете по желание да дадете име на тага `{/block}`, например `{/block footer}`. Името обаче трябва да съвпада с името на блока. В по-големи шаблони тази техника помага да се види кои блокови тагове са затворени. -- Не можете да дефинирате директно няколко блокови тага с едно и също име в един шаблон. Но това може да се постигне чрез използване на [динамични имена на блокове |#Dynamic-Block-Names]. +Съвети +------ +Няколко съвета за работа с блокове: -- Можете да използвате [n:attributes, |syntax#n-attributes] за да дефинирате блокове като `

    Welcome to my awesome homepage

    ` +- Последният блок на най-високо ниво не е необходимо да има затварящ таг (блокът завършва с края на документа). Това опростява писането на дъщерни шаблони, които съдържат един основен блок. -- Блоковете могат да се използват и без имена, само за да се прилагат [филтри |syntax#Filters] към изхода: `{block|strip} hello {/block}` +- За по-добра четимост можете да посочите името на блока в тага `{/block}`, например `{/block footer}`. Името обаче трябва да съвпада с името на блока. В по-големи шаблони тази техника ще ви помогне да видите кои тагове на блокове се затварят. +- В един и същ шаблон не можете директно да дефинирате няколко тага на блокове със същото име. Това обаче може да се постигне с помощта на [#динамични имена на блокове]. -Хоризонтална повторна употреба `{import}` .{toc: Horizontal Reuse} -================================================================== +- Можете да използвате [n:атрибути |syntax#n:атрибути] за дефиниране на блокове като `

    Welcome to my awesome homepage

    ` -Хоризонталната повторна употреба е третият механизъм за повторна употреба и наследяване в Latte. Той позволява зареждане на блокове от други шаблони. Това е подобно на създаването на PHP файл с помощни функции или черти. +- Блоковете могат да се използват и без имена, само за прилагане на [филтри |syntax#Филтри]: `{block|strip} hello {/block}` -Въпреки че наследяването на шаблони за оформление е една от най-мощните функции на Latte, то е ограничено до единично наследяване; даден шаблон може да разшири само един друг шаблон. Това ограничение прави наследяването на шаблони лесно за разбиране и лесно за отстраняване на грешки: -```latte -{layout 'layout.latte'} +Хоризонтално повторно използване `{import}` .{toc: Хоризонтално повторно използване} +==================================================================================== -{block title}...{/block} -{block content}...{/block} -``` +Хоризонталното повторно използване е третият механизъм в Latte за повторно използване и наследяване. Той позволява зареждане на блокове от други шаблони. Това е подобно на създаването на файл с помощни функции в PHP, който след това зареждаме с помощта на `require`. -Хоризонталната повторна употреба е начин за постигане на същата цел като многократното наследяване, но без съпътстващите я усложнения: +Въпреки че наследяването на лейаута на шаблона е една от най-мощните функции на Latte, то е ограничено до просто наследяване - шаблонът може да разшири само един друг шаблон. Хоризонталното повторно използване е начин за постигане на множествено наследяване. -```latte -{layout 'layout.latte'} +Нека имаме файл с дефиниции на блокове: -{import 'blocks.latte'} +```latte .{file: blocks.latte} +{block sidebar}...{/block} -{block title}...{/block} -{block content}...{/block} +{block menu}...{/block} ``` -Операторът `{import}` казва на Latte да импортира всички блокове и [дефиниции |#Definitions], дефинирани в `blocks.latte`, в текущия шаблон. +С помощта на командата `{import}` импортираме всички блокове и [#Дефиниции], дефинирани в `blocks.latte`, в друг шаблон: -```latte -{* blocks.latte *} +```latte .{file: child.latte} +{import 'blocks.latte'} -{block sidebar}...{/block} +{* сега могат да се използват блоковете sidebar и menu *} ``` -В този пример операторът `{import}` импортира блока `sidebar` в главния шаблон. +Ако импортирате блокове в родителския шаблон (т.е. използвате `{import}` в `layout.latte`), блоковете ще бъдат достъпни и във всички дъщерни шаблони, което е много практично. -Импортираният шаблон не трябва да [разширява |#Layout-Inheritance] друг шаблон и тялото му трябва да е празно. Въпреки това импортираният шаблон може да импортира други шаблони. +Шаблонът, предназначен за импортиране (напр. `blocks.latte`), не трябва да [разширява |#Наследяване на лейаут] друг шаблон, т.е. да използва `{layout}`. Въпреки това, той може да импортира други шаблони. -Тагът `{import}` трябва да е първият таг на шаблона след `{layout}`. Името на шаблона може да бъде произволен израз на PHP: +Тагът `{import}` трябва да бъде първият таг на шаблона след `{layout}`. Името на шаблона може да бъде произволен PHP израз: ```latte {import $ajax ? 'ajax.latte' : 'not-ajax.latte'} ``` -Можете да използвате толкова изрази `{import}`, колкото искате в даден шаблон. Ако два импортирани шаблона дефинират един и същ блок, печели първият. Най-голям приоритет обаче има основният шаблон, който може да презапише всеки импортиран блок. +В шаблона можете да използвате толкова команди `{import}`, колкото искате. Ако два импортирани шаблона дефинират един и същ блок, печели първият. Най-висок приоритет обаче има основният шаблон, който може да презапише всеки импортиран блок. -Всички надписани блокове могат да бъдат включени постепенно, като се вмъкнат като [родителски блок |#Parent-Block]: +Съдържанието на презаписаните блокове може да бъде запазено, като вмъкнем блока по същия начин, по който се вмъква [#родителски блок]: ```latte -{layout 'base.latte'} +{layout 'layout.latte'} {import 'blocks.latte'} @@ -481,17 +470,17 @@ Hi, I am Mary. {block content}...{/block} ``` -В този пример `{include parent}` правилно ще извика блока `sidebar` от шаблона `blocks.latte`. +В този пример `{include parent}` извиква блока `sidebar` от шаблона `blocks.latte`. -Наследяване на блокове `{embed}` .{toc: Unit Inheritance}{data-version:2.9} -=========================================================================== +Единично наследяване `{embed}` .{toc: Единично наследяване} +=========================================================== -Наследяването на единици пренася идеята за наследяване на оформлението на нивото на части от съдържанието. Докато наследяването на оформлението работи със "скелети на документи", които се анимират от подчинени шаблони, наследяването на единици ви позволява да създавате скелети за по-малки части от съдържанието и да ги използвате повторно навсякъде. +Единичното наследяване разширява идеята за наследяване на лейаути до нивото на фрагменти от съдържание. Докато наследяването на лейаути работи със "скелета на документа", който се оживява от дъщерни шаблони, единичното наследяване ви позволява да създавате скелети за по-малки единици съдържание и да ги използвате повторно навсякъде, където искате. -При наследяването на единици ключовият етикет е `{embed}`. Той съчетава поведението на `{include}` и `{layout}`. Той ви позволява да включвате съдържание от друг шаблон или блок и по желание да предавате променливи, както и `{include}`. Той също така ви позволява да замените всеки блок, дефиниран в рамките на включен шаблон, както и `{layout}`. +При единичното наследяване ключът е тагът `{embed}`. Той комбинира поведението на `{include}` и `{layout}`. Позволява ви да вмъкнете съдържанието на друг шаблон или блок и по избор да предавате променливи, точно както при `{include}`. Също така ви позволява да презапишете всеки блок, дефиниран вътре във вмъкнатия шаблон, както при използване на `{layout}`. -В примера ще използваме сгъваемия елемент акордеон. Нека разгледаме скелета на елемента в шаблона `collapsible.latte`: +Например, нека използваме елемент акордеон. Да разгледаме скелета на елемента, съхранен в шаблона `collapsible.latte`: ```latte
    @@ -505,9 +494,9 @@ Hi, I am Mary.
    ``` -Таговете `{block}` дефинират два блока, които могат да попълват подчинени шаблони. Да, както в случая с шаблона-родител в шаблона за наследяване на оформлението. Можете да видите и променливата `$modifierClass`. +Таговете `{block}` дефинират два блока, които дъщерните шаблони могат да запълнят. Да, както в случая с родителския шаблон при наследяването на лейаути. Виждате също променливата `$modifierClass`. -Нека използваме нашия елемент в шаблона. Тук се намира `{embed}`. Това е изключително мощен набор, който ни позволява да правим всичко: да включваме съдържанието на шаблона на елемента, да добавяме променливи към него и да добавяме персонализирани HTML блокове към него: +Нека използваме нашия елемент в шаблона. Тук идва ред на `{embed}`. Това е изключително мощен таг, който ни позволява да правим всичко: да вмъкваме съдържанието на шаблона на елемента, да добавяме променливи към него и да добавяме блокове със собствен HTML: ```latte {embed 'collapsible.latte', modifierClass: my-style} @@ -522,7 +511,7 @@ Hi, I am Mary. {/embed} ``` -Резултатът може да изглежда така: +Изходът може да изглежда така: ```latte
    @@ -537,7 +526,7 @@ Hi, I am Mary.
    ``` -Блоковете в таговете за вграждане образуват отделен слой, независим от другите блокове. Затова те могат да имат същото име като блока извън вграждането и не се влияят от него. С помощта на тага [include |#Printing-Blocks] в таговете `{embed}`, можете да вмъкнете блокове, създадени тук, блокове от шаблона за вграждане (които *не са* [локални |#Local-Blocks]), както и блокове от основния шаблон, които *са* локални. Можете също така да [импортирате блокове |#Horizontal-Reuse] от други файлове: +Блоковете вътре във вмъкнатите тагове образуват отделен слой, независим от другите блокове. Следователно те могат да имат същото име като блок извън вмъкването и не се влияят по никакъв начин. С помощта на тага [include |#Рендиране на блокове] вътре в таговете `{embed}` можете да вмъквате блокове, създадени тук, блокове от вмъкнатия шаблон (които *не са* [локални |#Локални блокове]), както и блокове от основния шаблон, които от своя страна *са* локални. Можете също да [импортирате блокове |#Хоризонтално повторно използване] от други файлове: ```latte {block outer}…{/block} @@ -551,16 +540,16 @@ Hi, I am Mary. {block title} {include inner} {* работи, блокът е дефиниран вътре в embed *} {include hello} {* работи, блокът е локален в този шаблон *} - {include content} {* работи, блокът е дефиниран във вградения шаблон *} + {include content} {* работи, блокът е дефиниран във вмъкнатия шаблон *} {include aBlockDefinedInImportedTemplate} {* работи *} - {include outer} {* не работи! - блокът е във външен слой *} + {include outer} {* не работи! - блокът е във външния слой *} {/block} {/embed} ``` -Вградените шаблони нямат достъп до активни контекстни променливи, но имат достъп до глобални променливи. +Вмъкнатите шаблони нямат достъп до променливите на активния контекст, но имат достъп до глобалните променливи. -Не само шаблони, но и други блокове могат да бъдат вмъкнати с `{embed}`, така че предишният пример може да бъде написан по следния начин .{data-version:2.10} +С помощта на `{embed}` могат да се вмъкват не само шаблони, но и други блокове, така че предишният пример може да бъде записан по следния начин: ```latte {define collapsible} @@ -581,23 +570,23 @@ Hi, I am Mary. {/embed} ``` -Ако подаваме израз на `{embed}` и не е ясно дали става въпрос за блок или име на файл, добавете ключовата дума `block` или `file`: +Ако предадем израз на `{embed}` и не е ясно дали това е име на блок или файл, добавяме ключовата дума `block` или `file`: ```latte {embed block $name} ... {/embed} ``` -Примери за употреба .[#toc-use-cases] -===================================== +Случаи на употреба +================== -В Latte има различни видове наследяване и повторно използване на кода. Нека обобщим основните понятия за по-голяма яснота: +В Latte съществуват различни типове наследяване и повторно използване на код. Нека обобщим основните концепции за по-голяма яснота: `{include template}` -------------------- -**Случай на употреба:** Използване на `header.latte` и `footer.latte` вътре в `layout.latte`. +**Случай на употреба**: Използване на `header.latte` и `footer.latte` вътре в `layout.latte`. `header.latte` @@ -630,7 +619,7 @@ Hi, I am Mary. `{layout}` ---------- -**Пример за използване**: Разширение `layout.latte` вътре в `homepage.latte` и `about.latte`. +**Случай на употреба**: Разширяване на `layout.latte` вътре в `homepage.latte` и `about.latte`. `layout.latte` @@ -666,7 +655,7 @@ Hi, I am Mary. `{import}` ---------- -**Приложение за потребителя**: `sidebar.latte` към `single.product.latte` и `single.service.latte`. +**Случай на употреба**: `sidebar.latte` в `single.product.latte` и `single.service.latte`. `sidebar.latte` @@ -698,7 +687,7 @@ Hi, I am Mary. `{define}` ---------- -**Пример на потребителя**: Функция, която получава някои променливи и извежда някои маркировки. +**Случай на употреба**: Функции, на които предаваме променливи и те рендират нещо. `form.latte` @@ -724,7 +713,7 @@ Hi, I am Mary. `{embed}` --------- -**Пример на потребителя**: Вграждане на `pagination.latte` в `product.table.latte` и `service.table.latte`. +**Случай на употреба**: Вмъкване на `pagination.latte` в `product.table.latte` и `service.table.latte`. `pagination.latte` diff --git a/latte/bg/type-system.texy b/latte/bg/type-system.texy index e8db5a63a0..aa1be010a5 100644 --- a/latte/bg/type-system.texy +++ b/latte/bg/type-system.texy @@ -1,27 +1,27 @@ -Система от типове -***************** +Типова система +************** -
    +
    -Системата от типове е от съществено значение за разработването на надеждни приложения. Latte въвежда поддръжка на типове в шаблоните. Познаването на типа данни или обект, към който принадлежи всяка променлива, позволява +Типовата система е ключова за разработването на стабилни приложения. Latte въвежда поддръжка на типове и в шаблоните. Благодарение на това, че знаем какъв тип данни или обект има във всяка променлива, може -- IDE правилно изпълнява функцията за автоматично допълване (вж. [интеграция и плъгини |recipes#Editors-and-IDE]) -- статичен анализ за откриване на грешки +- IDE да подсказва правилно (вижте [интеграция |recipes#Редактори и IDE]) +- статичният анализ да открива грешки -Две неща, които значително подобряват качеството и използваемостта на разработката. +И двете значително повишават качеството и удобството на разработката.
    .[note] -Декларираните типове са информативни и в момента Latte не ги тества. +Декларираните типове са информативни и Latte в момента не ги проверява. -Как да започнете да използвате типовете? Създайте клас-шаблон, например `CatalogTemplateParameters`, представящ параметрите, които трябва да бъдат предадени: +Как да започнете да използвате типове? Създайте клас на шаблона, напр. `CatalogTemplateParameters`, представляващ предаваните параметри, техните типове и евентуално стойности по подразбиране: ```php class CatalogTemplateParameters { public function __construct( - public string $langs, + public string $lang, /** @var ProductEntity[] */ public array $products, public Address $address, @@ -35,19 +35,16 @@ $latte->render('template.latte', new CatalogTemplateParameters( )); ``` -След това поставете тага `{templateType}` с пълното име на класа (включително пространството от имена) в началото на шаблона. Това определя, че шаблонът ще съдържа променливите `$langs` и `$products`, включително съответните типове. -Можете също така да зададете типове локални променливи с таговете [`{var}` |tags#var-default], `{varType}` и [`{define}` |template-inheritance#Definitions]. +След това в началото на шаблона поставете тага `{templateType}` с пълното име на класа (включително namespace). Това дефинира, че в шаблона има променливи `$lang` и `$products`, включително съответните типове. Типовете на локалните променливи можете да посочите с помощта на таговете [`{var}` |tags#var default], `{varType}`, [`{define}` |template-inheritance#Дефиниции]. -IDE вече може да извършва правилно автоматично попълване. +От този момент IDE може да ви подсказва правилно. -Как да го поддържаме в работно състояние? Как да напишете клас на шаблона или тагове `{varType}` възможно най-просто? Генерирайте ги. -Точно това прави двойката тагове `{templatePrint}` и `{varPrint}`. -Ако поставите един от тези тагове в шаблон, кодът на класа или шаблона ще бъде показан вместо обичайното визуализиране. След това просто изберете и копирайте кода в проекта си. +Как да си спестите работа? Как най-лесно да напишете клас с параметри на шаблон или тагове `{varType}`? Нека бъдат генерирани за вас. За това съществуват двойка тагове `{templatePrint}` и `{varPrint}`. Ако ги поставите в шаблона, вместо нормалното рендиране ще се покаже предложение за код на клас или съответно списък с тагове `{varType}`. След това е достатъчно да маркирате кода с едно кликване и да го копирате в проекта. `{templateType}` ---------------- -Видовете параметри, предавани на шаблона, се декларират с помощта на клас: +Типовете на параметрите, предавани към шаблона, се декларират с помощта на клас: ```latte {templateType MyApp\CatalogTemplateParameters} @@ -56,7 +53,7 @@ IDE вече може да извършва правилно автоматич `{varType}` ----------- -Как да декларираме типовете променливи? За да направите това, използвайте тага `{varType}` за съществуваща променлива или [`{var}` |tags#var-default]: +Как да декларираме типовете на променливите? За това служат таговете `{varType}` за съществуващи променливи или [`{var}` |tags#var default]: ```latte {varType Nette\Security\User $user} @@ -66,11 +63,11 @@ IDE вече може да извършва правилно автоматич `{templatePrint}` ----------------- -Можете също така да генерирате този клас с помощта на тага `{templatePrint}`. Ако го поставите в началото на шаблона, кодът на класа ще бъде показан вместо нормалния шаблон. След това просто изберете и копирайте кода в проекта си. +Можете също така да генерирате класа с помощта на тага `{templatePrint}`. Ако го поставите в началото на шаблона, вместо нормалното рендиране ще се покаже предложение за клас. След това е достатъчно да маркирате кода с едно кликване и да го копирате в проекта. `{varPrint}` ------------ -Етикетът `{varPrint}` ви спестява време. Ако го поставите в шаблон, той ще показва списъка с тагове `{varType}` вместо нормалното визуализиране. След това просто изберете и копирайте кода в шаблона си. +Тагът `{varPrint}` ще ви спести време за писане. Ако го поставите в шаблона, вместо нормалното рендиране ще се покаже предложение за тагове `{varType}` за локални променливи. След това е достатъчно да маркирате кода с едно кликване и да го копирате в шаблона. -В `{varPrint}` са изброени локалните променливи, които не са параметри на шаблона. Ако искате да изброите всички променливи, използвайте `{varPrint all}`. +Самото `{varPrint}` извежда само локални променливи, които не са параметри на шаблона. Ако искате да изведете всички променливи, използвайте `{varPrint all}`. diff --git a/latte/bg/why-use.texy b/latte/bg/why-use.texy new file mode 100644 index 0000000000..5f87c55670 --- /dev/null +++ b/latte/bg/why-use.texy @@ -0,0 +1,80 @@ +Защо да използваме шаблони? +*************************** + + +Защо трябва да използвам система за шаблони в PHP? +-------------------------------------------------- + +Защо да използваме система за шаблони в PHP, когато самият PHP е език за шаблони? + +Нека първо накратко да обобщим историята на този език, която е пълна с интересни обрати. Един от първите езици за програмиране, използвани за генериране на HTML страници, беше езикът C. Скоро обаче се оказа, че използването му за тази цел е непрактично. Затова Расмус Лердорф създаде PHP, което улесни генерирането на динамичен HTML с езика C в бекенда. Така PHP първоначално е проектиран като език за шаблони, но с течение на времето придобива допълнителни функции и се превръща в пълноценен език за програмиране. + +Въпреки това той все още функционира и като език за шаблони. В PHP файл може да бъде записана HTML страница, в която с помощта на `` се извеждат променливи и т.н. + +Още в началото на историята на PHP възникна системата за шаблони Smarty, чиято цел беше стриктно да отдели външния вид (HTML/CSS) от логиката на приложението. Тоест, тя умишлено предоставяше по-ограничен език от самия PHP, така че разработчикът да не може например да изпълни заявка към базата данни от шаблона и т.н. От друга страна, тя представляваше допълнителна зависимост в проектите, увеличаваше тяхната сложност и програмистите трябваше да учат новия език Smarty. Такава полза беше спорна и за шаблони продължи да се използва обикновен PHP. + +С течение на времето системите за шаблони започнаха да стават полезни. Те въведоха концепцията за [наследяване |template-inheritance], [режим sandbox|sandbox] и редица други функции, които значително опростиха създаването на шаблони в сравнение с чистия PHP. На преден план излезе темата за сигурността, съществуването на [уязвимости като XSS|safety-first] и необходимостта от [екраниране |#Какво е екраниране]. Системите за шаблони въведоха автоматично екраниране, за да изчезне рискът програмистът да забрави за това и да възникне сериозна дупка в сигурността (след малко ще покажем, че това има известни клопки). + +Ползите от системите за шаблони днес значително надвишават разходите, свързани с тяхното внедряване. Затова има смисъл да се използват. + + +Защо Latte е по-добър от Twig или Blade? +---------------------------------------- + +Причините са няколко – някои са приятни, а други са изключително полезни. Latte е комбинация от приятното с полезното. + +*Първо приятната причина:* Latte има същия [синтаксис като PHP |syntax#Latte разбира PHP]. Различава се само записът на таговете, вместо `` предпочита по-кратките `{` и `}`. Това означава, че не е нужно да учите нов език. Разходите за обучение са минимални. И най-важното, по време на разработката не е нужно постоянно да "превключвате" между езика PHP и езика на шаблона, тъй като и двата са еднакви. За разлика от шаблоните на Twig, които използват езика Python, и програмистът трябва да превключва между два различни езика. + +*А сега изключително полезната причина*: Всички системи за шаблони, като Twig, Blade или Smarty, в хода на еволюцията си въведоха защита срещу XSS под формата на автоматично [екраниране |#Какво е екраниране]. По-точно, автоматично извикване на функцията `htmlspecialchars()`. Създателите на Latte обаче осъзнаха, че това изобщо не е правилното решение. Защото на различни места в документа екранирането се извършва по различни начини. Наивното автоматично екраниране е опасна функция, защото създава фалшиво усещане за сигурност. + +За да бъде автоматичното екраниране функционално и надеждно, то трябва да разпознава къде в документа се извеждат данните (наричаме ги контексти) и според това да избира функцията за екраниране. Тоест, трябва да бъде [контекстно-чувствително |safety-first#Контекстно-чувствително екраниране]. И точно това умее Latte. То разбира HTML. Не възприема шаблона само като низ от знаци, а разбира какво са тагове, атрибути и т.н. И затова екранира по различен начин в HTML текст, по различен начин вътре в HTML таг, по различен начин вътре в JavaScript и т.н. + +Latte е първата и единствена система за шаблони в PHP, която има контекстно-чувствително екраниране. По този начин тя представлява единствената наистина сигурна система за шаблони. + +*И още една приятна причина*: Благодарение на това, че Latte разбира HTML, той предлага други много приятни функции. Например [n:атрибути |syntax#n:атрибути]. Или способността да [проверява връзки |safety-first#Проверка на връзки]. И много други. + + +Какво е екраниране? +------------------- + +Екранирането е процес, който се състои в замяна на знаци със специално значение със съответните им последователности при вмъкване на един низ в друг, за да се предотвратят нежелани явления или грешки. Например, когато вмъкваме низ в HTML текст, в който знакът `<` има специално значение, тъй като обозначава началото на таг, го заменяме със съответната последователност, която е HTML ентитетът `<`. Благодарение на това браузърът правилно ще покаже символа `<`. + +Прост пример за екраниране директно при писане на код в PHP е вмъкването на кавичка в низ, като преди нея напишем обратна наклонена черта. + +Разглеждаме екранирането по-подробно в главата [Как да се защитим от XSS |safety-first#Как да се защитим от XSS]. + + +Възможно ли е да се изпълни заявка към базата данни от шаблон в Latte? +---------------------------------------------------------------------- + +В шаблоните може да се работи с обекти, които програмистът им предава. Следователно, ако програмистът иска, той може да предаде обект на база данни към шаблона и да изпълни заявка върху него. Ако има такова намерение, няма причина да му се пречи. + +Друга ситуация възниква, ако искате да дадете възможност на клиенти или външни кодери да редактират шаблоните. В такъв случай определено не искате те да имат достъп до базата данни. Разбира се, няма да предадете обект на база данни на шаблона, но какво ще стане, ако до нея може да се стигне чрез друг обект? Решението е [режим sandbox|sandbox], който позволява да се дефинира кои методи могат да се извикват в шаблоните. Благодарение на това не е нужно да се притеснявате за нарушаване на сигурността. + + +Какви са основните разлики между системите за шаблони като Latte, Twig и Blade? +------------------------------------------------------------------------------- + +Разликите между системите за шаблони Latte, Twig и Blade се състоят главно в синтаксиса, сигурността и начина на интеграция във фреймуърците + +- Latte: използва синтаксиса на езика PHP, което улеснява ученето и използването. Предоставя върхова защита срещу XSS атаки. +- Twig: използва синтаксиса на езика Python, който доста се различава от PHP. Екранира без разграничаване на контекста. Добре е интегриран в Symfony framework. +- Blade: използва смес от PHP и собствен синтаксис. Екранира без разграничаване на контекста. Тясно е интегриран с функциите и екосистемата на Laravel. + + +Изгодно ли е за фирмите да използват система за шаблони? +-------------------------------------------------------- + +Първо, разходите, свързани с обучението, използването и общата полза, се различават значително в зависимост от системата. Системата за шаблони Latte, благодарение на това, че използва синтаксиса на PHP, значително улеснява ученето за програмисти, които вече са запознати с този език. Обикновено отнема няколко часа, докато програмистът се запознае достатъчно с Latte. По този начин намалява разходите за обучение. Същевременно ускорява усвояването на технологията и преди всичко ефективността при ежедневна употреба. + +Освен това Latte предоставя високо ниво на защита срещу уязвимостта XSS благодарение на уникалната технология за контекстно-чувствително екраниране. Тази защита е ключова за гарантиране на сигурността на уеб приложенията и минимизиране на риска от атаки, които биха могли да застрашат потребителите или фирмените данни. Защитата на сигурността на уеб приложенията е важна и за поддържането на добрата репутация на фирмата. Проблемите със сигурността могат да доведат до загуба на доверие от страна на клиентите и да навредят на репутацията на фирмата на пазара. + +Използването на Latte също така намалява общите разходи за разработка и поддръжка на приложението, като улеснява и двете. Следователно използването на система за шаблони определено си заслужава. + + +Влияе ли Latte на производителността на уеб приложенията? +--------------------------------------------------------- + +Въпреки че шаблоните на Latte се обработват бързо, този аспект всъщност няма значение. Причината е, че парсирането на файловете се извършва само веднъж при първото показване. След това те се компилират в PHP код, съхраняват се на диска и се изпълняват при всяка следваща заявка, без да е необходимо повторно компилиране. + +Това е начинът на работа в продукционна среда. По време на разработката шаблоните на Latte се прекомпилират всеки път, когато съдържанието им се промени, така че разработчикът винаги да вижда актуалната версия. diff --git a/latte/cs/@left-menu.texy b/latte/cs/@left-menu.texy index 9c119e9632..4556b26913 100644 --- a/latte/cs/@left-menu.texy +++ b/latte/cs/@left-menu.texy @@ -1,25 +1,25 @@ - [Začínáme s Latte |guide] -- Koncepty +- [Proč používat šablony? |why-use] +- Koncepty ⚗️ - [Bezpečnost především |safety-first] - [Dědičnost šablon |Template Inheritance] - [Typový systém |type-system] - [Sandbox] + - [HTML attributy |html-attributes] -- Pro designéry +- Pro designéry 🎨 - [Syntaxe |syntax] - [Tagy |tags] - [Filtry |filters] - [Funkce |functions] - [Tipy a triky |recipes] -- Pro vývojáře +- Pro vývojáře 🧮 - [Vývojářské postupy |develop] - [Rozšiřujeme Latte |extending-latte] - - [Vytváříme Extension |creating-extension] -- [Návody a postupy |cookbook/@home] +- [Návody a postupy 💡|cookbook/@home] - [Migrace z Twigu |cookbook/migration-from-twig] - - [Migrace z Latte 2 |cookbook/migration-from-latte2] - [… další |cookbook/@home] - "Hřiště .[link-external]":https://fiddle.nette.org/latte/ .{padding-top:1em} diff --git a/latte/cs/@menu.texy b/latte/cs/@menu.texy index bf9ac4aa30..0901dda7ed 100644 --- a/latte/cs/@menu.texy +++ b/latte/cs/@menu.texy @@ -5,8 +5,8 @@ diff --git a/latte/cs/@meta.texy b/latte/cs/@meta.texy new file mode 100644 index 0000000000..4e9560c408 --- /dev/null +++ b/latte/cs/@meta.texy @@ -0,0 +1 @@ +{{sitename: Latte Dokumentace}} diff --git a/latte/cs/compiler-passes.texy b/latte/cs/compiler-passes.texy new file mode 100644 index 0000000000..8100b2416e --- /dev/null +++ b/latte/cs/compiler-passes.texy @@ -0,0 +1,555 @@ +Kompilační průchody +******************* + +.[perex] +Kompilační průchody poskytují výkonný mechanismus pro analýzu a modifikaci Latte šablon *po* jejich parsování do abstraktního syntaktického stromu (AST) a *před* vygenerováním finálního PHP kódu. To umožňuje pokročilou manipulaci s šablonami, optimalizace, bezpečnostní kontroly (jako je Sandbox) a sběr informací o šablonách. Tento průvodce vás provede vytvořením vlastních kompilačních průchodů. + + +Co je kompilační průchod? +========================= + +Pro pochopení role kompilačních průchodů se podívejte na [kompilační proces Latte |custom-tags#Pochopení procesu kompilace]. Jak můžete vidět, kompilační průchody operují v klíčové fázi, umožňující hluboký zásah mezi počátečním parsováním a finálním výstupem kódu. + +V jádru je kompilační průchod jednoduše PHP volatelný objekt (jako funkce, statická metoda nebo metoda instance), který přijímá jeden argument: kořenový uzel AST šablony, což je vždy instance `Latte\Compiler\Nodes\TemplateNode`. + +Primárním cílem kompilačního průchodu je obvykle jeden nebo oba z následujících: + +- Analýza: Procházet AST a shromažďovat informace o šabloně (např. najít všechny definované bloky, zkontrolovat použití specifických tagů, zajistit splnění určitých bezpečnostních omezení). +- Modifikace: Změnit strukturu AST nebo atributy uzlů (např. automaticky přidat HTML atributy, optimalizovat určité kombinace tagů, nahradit zastaralé tagy novými, implementovat pravidla sandboxu). + + +Registrace +========== + +Kompilační průchody jsou registrovány pomocí metody [rozšíření |extending-latte#getPasses] `getPasses()`. Tato metoda vrací asociativní pole, kde klíče jsou jedinečné názvy průchodů (používané interně a pro řazení) a hodnoty jsou PHP volatelné objekty implementující logiku průchodu. + +```php +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Extension; + +class MyExtension extends Extension +{ + public function getPasses(): array + { + return [ + 'modificationPass' => $this->modifyTemplateAst(...), + // ... další průchody ... + ]; + } + + public function modifyTemplateAst(TemplateNode $templateNode): void + { + // Implementace... + } +} +``` + +Průchody registrované základními rozšířeními Latte a vašimi vlastními rozšířeními běží sekvenčně. Pořadí může být důležité, zejména pokud jeden průchod závisí na výsledcích nebo modifikacích jiného. Latte poskytuje pomocný mechanismus pro kontrolu tohoto pořadí, pokud je potřeba; viz dokumentaci k [`Extension::getPasses()` |extending-latte#getPasses] pro podrobnosti. + + +Příklad AST +=========== + +Pro lepší představu o AST, přidáváme ukázku. Toto je zdrojová šablona: + +```latte +{foreach $category->getItems() as $item} +
  • {$item->name|upper}
  • + {else} + no items found +{/foreach} +``` + +A toto je její reprezentace ve formě AST: + +/--pre +Latte\Compiler\Nodes\TemplateNode( + Latte\Compiler\Nodes\FragmentNode( + - Latte\Essential\Nodes\ForeachNode( + expression: Latte\Compiler\Nodes\Php\Expression\MethodCallNode( + object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$category') + name: Latte\Compiler\Nodes\Php\IdentifierNode('getItems') + ) + value: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') + content: Latte\Compiler\Nodes\FragmentNode( + - Latte\Compiler\Nodes\TextNode(' ') + - Latte\Compiler\Nodes\Html\ElementNode('li')( + content: Latte\Essential\Nodes\PrintNode( + expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode( + object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') + name: Latte\Compiler\Nodes\Php\IdentifierNode('name') + ) + modifier: Latte\Compiler\Nodes\Php\ModifierNode( + filters: + - Latte\Compiler\Nodes\Php\FilterNode('upper') + ) + ) + ) + ) + else: Latte\Compiler\Nodes\FragmentNode( + - Latte\Compiler\Nodes\TextNode('no items found') + ) + ) + ) +) +\-- + + +Procházení AST pomocí `NodeTraverser` +===================================== + +Ruční psaní rekurzivních funkcí pro procházení komplexní struktury AST je únavné a náchylné k chybám. Latte poskytuje speciální nástroj pro tento účel: [api:Latte\Compiler\NodeTraverser]. Tato třída implementuje [návrhový vzor Visitor |https://en.wikipedia.org/wiki/Visitor_pattern], díky kterému je procházení AST systematické a snadno zvládnutelné. + +Základní použití zahrnuje vytvoření instance `NodeTraverser` a volání její metody `traverse()`, předání kořenového uzlu AST a jednoho nebo dvou "visitor" volatelných objektů: + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; + +(new NodeTraverser)->traverse( + $templateNode, + + // 'enter' visitor: Volán při vstupu do uzlu (před jeho dětmi) + enter: function (Node $node) { + echo "Vstup do uzlu typu: " . $node::class . "\n"; + // Zde můžete zkoumat uzel + if ($node instanceof Nodes\TextNode) { + // echo "Nalezen text: " . $node->content . "\n"; + } + }, + + // 'leave' visitor: Volán při opuštění uzlu (po jeho dětech) + leave: function (Node $node) { + echo "Opuštění uzlu typu: " . $node::class . "\n"; + // Zde můžete provádět akce po zpracování dětí + }, +); +``` + +Můžete poskytnout pouze `enter` visitor, pouze `leave` visitor, nebo oba, v závislosti na vašich potřebách. + +**`enter(Node $node)`:** Tato funkce je provedena pro každý uzel **před** tím, než procházející navštíví kterékoliv z dětí tohoto uzlu. Je užitečná pro: + +- Sbírání informací při průchodu stromem směrem dolů. +- Rozhodování *před* zpracováním dětí (jako rozhodnutí je přeskočit, viz [#Optimalizace procházení]). +- Potenciální úpravy uzlu před návštěvou dětí (méně časté). + +**`leave(Node $node)`:** Tato funkce je provedena pro každý uzel **po** tom, co všechny jeho děti (a jejich celé podstromy) byly plně navštíveny (jak vstup tak opuštění). Je nejčastějším místem pro: + +Oba visitoři `enter` a `leave` mohou volitelně vracet hodnotu pro ovlivnění procesu procházení. Vrácení `null` (nebo nic) pokračuje v procházení normálně, vrácení instance `Node` nahradí aktuální uzel, a vrácení speciálních konstant jako `NodeTraverser::RemoveNode` nebo `NodeTraverser::StopTraversal` modifikuje tok, jak je vysvětleno v následujících sekcích. + + +Jak procházení funguje +---------------------- + +`NodeTraverser` interně používá metodu `getIterator()`, kterou musí implementovat každá třída `Node` (jak bylo diskutováno v [Vytváření vlastních tagů |custom-tags#Implementace getIterator pro poduzly]). Iteruje přes děti získané pomocí `getIterator()`, rekurzivně volá `traverse()` na nich a zajišťuje, že `enter` a `leave` visitoři jsou voláni ve správném hloubkově-prvním pořadí pro každý uzel ve stromě dostupný přes iterátory. To znovu zdůrazňuje, proč správně implementovaný `getIterator()` ve vašich vlastních tagových uzlech je naprosto nezbytný pro správné fungování kompilačních průchodů. + +Pojďme napsat jednoduchý průchod, který počítá, kolikrát je v šabloně použit tag `{do}` (reprezentovaný `Latte\Essential\Nodes\DoNode`). + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Essential\Nodes\DoNode; + +function countDoTags(TemplateNode $templateNode): void +{ + $count = 0; + (new NodeTraverser)->traverse( + $templateNode, + enter: function (Node $node) use (&$count): void { + if ($node instanceof DoNode) { + $count++; + } + }, + // 'leave' visitor není pro tento úkol potřeba + ); + + echo "Nalezen tag {do} $count krát.\n"; +} + +$latte = new Latte\Engine; +$ast = $latte->parse($templateSource); +countDoTags($ast); +``` + +V tomto příkladu jsme potřebovali pouze visitor `enter` ke kontrole typu každého navštíveného uzlu. + +Dále prozkoumáme, jak tyto visitoři skutečně modifikují AST. + + +Modifikace AST +============== + +Jedním z hlavních účelů kompilačních průchodů je modifikace abstraktního syntaktického stromu. To umožňuje výkonné transformace, optimalizace nebo vynucení pravidel přímo na struktuře šablony před generováním PHP kódu. `NodeTraverser` poskytuje několik způsobů, jak toho dosáhnout v rámci visitorů `enter` a `leave`. + +**Důležitá poznámka:** Modifikace AST vyžaduje opatrnost. Nesprávné změny – jako odstranění základních uzlů nebo nahrazení uzlu nekompatibilním typem – mohou vést k chybám během generování kódu nebo způsobit neočekávané chování během běhu programu. Vždy důkladně testujte své modifikační průchody. + + +Úprava atributů uzlů +-------------------- + +Nejjednodušší způsob, jak modifikovat strom, je přímá změna **veřejných vlastností** uzlů navštívených během procházení. Všechny uzly ukládají své parsované argumenty, obsah nebo atributy ve veřejných vlastnostech. + +**Příklad:** Vytvořme průchod, který najde všechny statické textové uzly (`TextNode`, reprezentující běžné HTML nebo text mimo Latte tagy) a převede jejich obsah na velká písmena *přímo v AST*. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\TextNode; + +function uppercaseStaticText(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // Můžeme použít 'enter', protože TextNode nemá žádné děti ke zpracování + enter: function (Node $node) { + // Je tento uzel statickým textovým blokem? + if ($node instanceof TextNode) { + // Ano! Přímo upravíme jeho veřejnou vlastnost 'content'. + $node->content = mb_strtoupper(html_entity_decode($node->content)); + } + // Není potřeba nic vracet; změna je aplikována přímo. + }, + ); +} +``` + +V tomto příkladu visitor `enter` kontroluje, zda je aktuální `$node` typu `TextNode`. Pokud ano, přímo aktualizujeme jeho veřejnou vlastnost `$content` pomocí `mb_strtoupper()`. To přímo mění obsah statického textu uloženého v AST *před* generováním PHP kódu. Protože modifikujeme objekt přímo, nemusíme nic vracet z visitoru. + +Efekt: Pokud šablona obsahovala `

    Hello

    {= $var }World`, po tomto průchodu bude AST reprezentovat něco jako: `

    HELLO

    {= $var }WORLD`. To NEOVLIVNÍ obsah $var. + + +Nahrazování uzlů +---------------- + +Výkonnější technikou modifikace je kompletní nahrazení uzlu jiným. To se provádí **vrácením nové instance `Node`** z visitoru `enter` nebo `leave`. `NodeTraverser` pak nahradí původní uzel vráceným ve struktuře rodičovského uzlu. + +**Příklad:** Vytvořme průchod, který najde všechna použití konstanty `PHP_VERSION` (reprezentované `ConstantFetchNode`) a nahradí je přímo řetězcovým literálem (`StringNode`) obsahujícím *skutečnou* verzi PHP detekovanou *během kompilace*. Toto je forma optimalizace v době kompilace. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\Php\Expression\ConstantFetchNode; +use Latte\Compiler\Nodes\Php\Scalar\StringNode; + +function inlinePhpVersion(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // 'leave' je často používán pro nahrazení, zajišťuje, že děti (pokud existují) + // jsou zpracovány nejdříve, i když by zde fungoval i 'enter'. + leave: function (Node $node) { + // Je tento uzel přístupem ke konstantě a jméno konstanty 'PHP_VERSION'? + if ($node instanceof ConstantFetchNode && (string) $node->name === 'PHP_VERSION') { + // Vytvoříme nový StringNode obsahující aktuální verzi PHP + $newNode = new StringNode(PHP_VERSION); + + // Volitelné, ale dobrá praxe: zkopírujeme informace o pozici + $newNode->position = $node->position; + + // Vrátíme nový StringNode. Traverser nahradí + // původní ConstantFetchNode tímto $newNode. + return $newNode; + } + // Pokud nevrátíme Node, původní $node je zachován. + }, + ); +} +``` + +Zde visitor `leave` identifikuje specifický `ConstantFetchNode` pro `PHP_VERSION`. Poté vytvoří zcela nový `StringNode` obsahující hodnotu konstanty `PHP_VERSION` *v době kompilace*. Vrácením tohoto `$newNode` říká traverseru, aby nahradil původní `ConstantFetchNode` v AST. + +Efekt: Pokud šablona obsahovala `{= PHP_VERSION }` a kompilace běží na PHP 8.2.1, AST po tomto průchodu bude efektivně reprezentovat `{= '8.2.1' }`. + +**Volba `enter` vs. `leave` pro nahrazení:** + +- Použijte `leave`, pokud vytvoření nového uzlu závisí na výsledcích zpracování dětí starého uzlu, nebo pokud chcete jednoduše zajistit, aby děti byly navštíveny před nahrazením (běžná praxe). +- Použijte `enter`, pokud chcete nahradit uzel *před* tím, než jsou jeho děti vůbec navštíveny. + + +Odstraňování uzlů +----------------- + +Můžete zcela odstranit uzel z AST vrácením speciální konstanty `NodeTraverser::RemoveNode` z visitoru. + +**Příklad:** Odstraňme všechny komentáře šablony (`{* ... *}`), které jsou reprezentovány `CommentNode` v AST generovaném jádrem Latte (ačkoli typicky zpracovány dříve, toto slouží jako příklad). + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\CommentNode; + +function removeCommentNodes(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // 'enter' je zde v pořádku, protože nepotřebujeme informace o dětech k odstranění komentáře + enter: function (Node $node) { + if ($node instanceof CommentNode) { + // Signalizujeme traverseru, aby odstranil tento uzel z AST + return NodeTraverser::RemoveNode; + } + }, + ); +} +``` + +**Upozornění:** Používejte `RemoveNode` opatrně. Odstranění uzlu, který obsahuje základní obsah nebo ovlivňuje strukturu (jako odstranění obsahového uzlu cyklu), může vést k poškozeným šablonám nebo neplatnému generovanému kódu. Nejbezpečnější je pro uzly, které jsou skutečně volitelné nebo samostatné (jako komentáře nebo ladící tagy) nebo pro prázdné strukturální uzly (např. prázdný `FragmentNode` může být v některých kontextech bezpečně odstraněn průchodem pro vyčištění). + +Tyto tři metody - úprava vlastností, nahrazování uzlů a odstraňování uzlů - poskytují základní nástroje pro manipulaci s AST v rámci vašich kompilačních průchodů. + + +Optimalizace procházení +======================= + +AST šablon může být poměrně velký, potenciálně obsahující tisíce uzlů. Procházení každého jednotlivého uzlu může být zbytečné a ovlivnit výkon kompilace, pokud váš průchod má zájem pouze o specifické části stromu. `NodeTraverser` nabízí způsoby optimalizace procházení: + + +Přeskakování dětí +----------------- + +Pokud víte, že jakmile narazíte na určitý typ uzlu, žádný z jeho potomků nemůže obsahovat uzly, které hledáte, můžete traverseru říct, aby přeskočil návštěvu jeho dětí. To se provádí vrácením konstanty `NodeTraverser::DontTraverseChildren` z visitoru **`enter`**. Tím vynecháte celé větve při procházení, což potenciálně ušetří značný čas, zejména v šablonách s komplexními PHP výrazy uvnitř tagů. + + +Zastavení procházení +-------------------- + +Pokud váš průchod potřebuje najít pouze *první* výskyt něčeho (specifický typ uzlu, splnění podmínky), můžete úplně zastavit celý proces procházení, jakmile to najdete. Toho je dosaženo vrácením konstanty `NodeTraverser::StopTraversal` z visitoru `enter` nebo `leave`. Metoda `traverse()` přestane navštěvovat jakékoliv další uzly. To je vysoce efektivní, pokud potřebujete pouze první shodu v potenciálně velmi velkém stromě. + + +Užitečný pomocník `NodeHelpers` +=============================== + +Zatímco `NodeTraverser` nabízí jemně odstupňovanou kontrolu, Latte také poskytuje praktickou pomocnou třídu, [api:Latte\Compiler\NodeHelpers], která zapouzdřuje `NodeTraverser` pro několik běžných úloh vyhledávání a analýzy, často vyžadující méně přípravného kódu. + + +find(Node $startNode, callable $filter): array .[method] +-------------------------------------------------------- + +Tato statická metoda nachází **všechny** uzly v podstromu začínajícím na `$startNode` (včetně), které splňují callback `$filter`. Vrací pole odpovídajících uzlů. + +**Příklad:** Najít všechny uzly proměnných (`VariableNode`) v celé šabloně. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Php\Expression\VariableNode; +use Latte\Compiler\Nodes\TemplateNode; + +function findAllVariables(TemplateNode $templateNode): array +{ + return NodeHelpers::find( + $templateNode, + fn($node) => $node instanceof VariableNode, + ); +} +``` + + +findFirst(Node $startNode, callable $filter): ?Node .[method] +-------------------------------------------------------------- + +Podobné jako `find`, ale zastaví procházení okamžitě po nalezení **prvního** uzlu, který splňuje callback `$filter`. Vrací nalezený objekt `Node` nebo `null`, pokud není nalezen žádný odpovídající uzel. Toto je v podstatě praktický obal kolem `NodeTraverser::StopTraversal`. + +**Příklad:** Najít uzel `{parameters}` (stejné jako manuální příklad předtím, ale kratší). + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Essential\Nodes\ParametersNode; + +function findParametersNodeHelper(TemplateNode $templateNode): ?ParametersNode +{ + return NodeHelpers::findFirst( + $templateNode->head, // Hledat pouze v hlavní sekci pro efektivitu + fn($node) => $node instanceof ParametersNode, + ); +} +``` + + +toValue(ExpressionNode $node, bool $constants = false): mixed .[method] +----------------------------------------------------------------------- + +Tato statická metoda se pokouší vyhodnotit `ExpressionNode` **v době kompilace** a vrátit jeho odpovídající PHP hodnotu. Funguje spolehlivě pouze pro jednoduché literální uzly (`StringNode`, `IntegerNode`, `FloatNode`, `BooleanNode`, `NullNode`) a instance `ArrayNode` obsahující pouze takové vyhodnotitelné položky. + +Pokud je `$constants` nastaveno na `true`, bude se také pokoušet vyřešit `ConstantFetchNode` a `ClassConstantFetchNode` kontrolou `defined()` a použitím `constant()`. + +Pokud uzel obsahuje proměnné, volání funkcí nebo jiné dynamické prvky, nemůže být vyhodnocen v době kompilace a metoda vyhodí `InvalidArgumentException`. + +**Případ použití:** Získání statické hodnoty argumentu tagu během kompilace pro rozhodování v době kompilace. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Php\ExpressionNode; + +function getStaticStringArgument(ExpressionNode $argumentNode): ?string +{ + try { + $value = NodeHelpers::toValue($argumentNode); + return is_string($value) ? $value : null; + } catch (\InvalidArgumentException $e) { + // Argument nebyl statický literální řetězec + return null; + } +} +``` + + +toText(?Node $node): ?string .[method] +-------------------------------------- + +Tato statická metoda je užitečná pro extrakci prostého textového obsahu z jednoduchých uzlů. Funguje primárně s: +- `TextNode`: Vrací jeho `$content`. +- `FragmentNode`: Zřetězí výsledek `toText()` pro všechny jeho děti. Pokud některé dítě není převoditelné na text (např. obsahuje `PrintNode`), vrací `null`. +- `NopNode`: Vrací prázdný řetězec. +- Ostatní typy uzlů: Vrací `null`. + +**Případ použití:** Získání statického textového obsahu hodnoty HTML atributu nebo jednoduchého HTML elementu pro analýzu během kompilačního průchodu. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Html\AttributeNode; + +function getStaticAttributeValue(AttributeNode $attr): ?string +{ + // $attr->value je typicky AreaNode (jako FragmentNode nebo TextNode) + return NodeHelpers::toText($attr->value); +} + +// Příklad použití v průchodu: +// if ($node instanceof Html\ElementNode && $node->name === 'meta') { +// $nameAttrValue = getStaticAttributeValue($node->getAttributeNode('name')); +// if ($nameAttrValue === 'description') { ... } +// } +``` + +`NodeHelpers` může zjednodušit vaše kompilační průchody poskytnutím hotových řešení pro běžné úlohy procházení a analýzy AST. + + +Praktické příklady +================== + +Pojďme aplikovat koncepty procházení a modifikace AST k řešení některých praktických problémů. Tyto příklady demonstrují běžné vzory používané v kompilačních průchodech. + + +Automatické přidání `loading="lazy"` k `` +---------------------------------------------- + +Moderní prohlížeče podporují nativní líné načítání pro obrázky pomocí atributu `loading="lazy"`. Vytvořme průchod, který automaticky přidá tento atribut ke všem tagům ``, které ještě nemají atribut `loading`. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; +use Latte\Compiler\Nodes\Html; + +function addLazyLoading(Nodes\TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // Můžeme použít 'enter', protože modifikujeme uzel přímo + // a nezávisíme na dětech pro toto rozhodnutí. + enter: function (Node $node) { + // Je to HTML element s názvem 'img'? + if ($node instanceof Html\ElementNode && $node->name === 'img') { + // Zajistíme, že uzel atributů existuje + $node->attributes ??= new Nodes\FragmentNode; + + // Zkontrolujeme, zda již existuje atribut 'loading' (bez ohledu na velikost písmen) + foreach ($node->attributes->children as $attrNode) { + if ($attrNode instanceof Html\AttributeNode + && $attrNode->name instanceof Nodes\TextNode // Statický název atributu + && strtolower($attrNode->name->content) === 'loading' + ) { + return; + } + } + + // Připojíme mezeru, pokud atributy nejsou prázdné + if ($node->attributes->children) { + $node->attributes->children[] = new Nodes\TextNode(' '); + } + + // Vytvoříme nový uzel atributu: loading="lazy" + $node->attributes->children[] = new Html\AttributeNode( + name: new Nodes\TextNode('loading'), + value: new Nodes\TextNode('lazy'), + quote: '"', + ); + // Změna je aplikována přímo v objektu, není potřeba nic vracet. + } + }, + ); +} +``` + +Vysvětlení: +- Visitor `enter` hledá uzly `Html\ElementNode` s názvem `img`. +- Iteruje přes existující atributy (`$node->attributes->children`) a kontroluje, zda je atribut `loading` již přítomen. +- Pokud není nalezen, vytvoří nový `Html\AttributeNode` reprezentující `loading="lazy"`. + + +Kontrola volání funkcí +---------------------- + +Kompilační průchody jsou základem Latte Sandboxu. I když je skutečný Sandbox sofistikovaný, můžeme demonstrovat základní princip kontroly zakázaných volání funkcí. + +**Cíl:** Zabránit použití potenciálně nebezpečné funkce `shell_exec` v rámci výrazů šablony. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; +use Latte\Compiler\Nodes\Php; +use Latte\SecurityViolationException; + +function checkForbiddenFunctions(Nodes\TemplateNode $templateNode): void +{ + $forbiddenFunctions = ['shell_exec' => true, 'exec' => true]; // Jednoduchý seznam + + $traverser = new NodeTraverser; + (new NodeTraverser)->traverse( + $templateNode, + enter: function (Node $node) use ($forbiddenFunctions) { + // Je to uzel přímého volání funkce? + if ($node instanceof Php\Expression\FunctionCallNode + && $node->name instanceof Php\NameNode + && isset($forbiddenFunctions[strtolower((string) $node->name)]) + ) { + throw new SecurityViolationException( + "Funkce {$node->name}() není povolena.", + $node->position, + ); + } + }, + ); +} +``` + +Vysvětlení: +- Definujeme seznam zakázaných názvů funkcí. +- Visitor `enter` kontroluje `FunctionCallNode`. +- Pokud je název funkce (`$node->name`) statický `NameNode`, kontrolujeme jeho řetězcovou reprezentaci v malých písmenech proti našemu zakázanému seznamu. +- Pokud je nalezena zakázaná funkce, vyhodíme `Latte\SecurityViolationException`, která jasně indikuje porušení bezpečnostního pravidla a zastaví kompilaci. + +Tyto příklady ukazují, jak mohou být kompilační průchody s použitím `NodeTraverser` využity pro analýzu, automatické modifikace a vynucení bezpečnostních omezení interakcí přímo se strukturou AST šablony. + + +Osvědčené postupy +================= + +Při psaní kompilačních průchodů mějte na paměti tyto směrnice pro vytváření robustních, udržovatelných a efektivních rozšíření: + +- **Pořadí je důležité:** Buďte si vědomi pořadí, v jakém průchody běží. Pokud váš průchod závisí na struktuře AST vytvořené jiným průchodem (např. základní průchody Latte nebo jiný vlastní průchod), nebo pokud jiné průchody mohou záviset na vašich modifikacích, použijte mechanismus řazení poskytovaný `Extension::getPasses()` k definování závislostí (`before`/`after`). Viz dokumentaci k [`Extension::getPasses()` |extending-latte#getPasses] pro podrobnosti. +- **Jedna odpovědnost:** Snažte se o průchody, které provádějí jednu dobře definovanou úlohu. Pro komplexní transformace zvažte rozdělení logiky do více průchodů – možná jeden pro analýzu a další pro modifikaci založenou na výsledcích analýzy. To zlepšuje přehlednost a testovatelnost. +- **Výkon:** Pamatujte, že kompilační průchody přidávají čas kompilace šablony (i když to obvykle nastává pouze jednou, dokud se šablona nezmění). Vyhněte se výpočetně náročným operacím ve vašich průchodech, pokud je to možné. Využívejte optimalizace procházení jako `NodeTraverser::DontTraverseChildren` a `NodeTraverser::StopTraversal` kdykoliv víte, že nepotřebujete navštívit určité části AST. +- **Používejte `NodeHelpers`:** Pro běžné úlohy jako hledání specifických uzlů nebo statické vyhodnocování jednoduchých výrazů, zkontrolujte, zda `Latte\Compiler\NodeHelpers` nenabízí vhodnou metodu před psaním vlastní logiky `NodeTraverser`. Může to ušetřit čas a snížit množství přípravného kódu. +- **Zpracování chyb:** Pokud váš průchod detekuje chybu nebo neplatný stav v AST šablony, vyhoďte `Latte\CompileException` (nebo `Latte\SecurityViolationException` pro bezpečnostní problémy) s jasnou zprávou a relevantním objektem `Position` (obvykle `$node->position`). To poskytuje užitečnou zpětnou vazbu vývojáři šablony. +- **Idempotence (pokud možno):** Ideálně by spuštění vašeho průchodu vícekrát na stejném AST mělo produkovat stejný výsledek jako jeho jednorázové spuštění. To není vždy proveditelné, ale zjednodušuje ladění a uvažování o interakcích průchodů, pokud je toho dosaženo. Například zajistěte, aby váš modifikační průchod kontroloval, zda modifikace již byla aplikována, než ji aplikuje znovu. + +Dodržováním těchto postupů můžete efektivně využít kompilační průchody k rozšíření schopností Latte výkonným a spolehlivým způsobem, což přispívá k bezpečnějšímu, optimalizovanějšímu nebo funkčně bohatšímu zpracování šablon. diff --git a/latte/cs/cookbook/@home.texy b/latte/cs/cookbook/@home.texy index 1cdf0a908b..b6af15e673 100644 --- a/latte/cs/cookbook/@home.texy +++ b/latte/cs/cookbook/@home.texy @@ -4,11 +4,12 @@ Návody a postupy .[perex] Příklady kódů a receptů pro provádění běžných úkolů pomocí Latte. -- [Všechno, co jste kdy chtěli vědět o {iterateWhile} |iteratewhile] +- [Postupy pro vývojáře |/develop] +- [Předávání proměnných napříč šablonami |passing-variables] +- [Všechno, co jste kdy chtěli vědět o seskupování |grouping] - [Jak psát SQL queries v Latte? |how-to-write-sql-queries-in-latte] +- [Migrace z Latte 3.0 |migration-from-latte-30] - [Migrace z Latte 2 |migration-from-latte2] - [Migrace z PHP |migration-from-php] - [Migrace z Twigu |migration-from-twig] - [Použití Latte se Slim 4 |slim-framework] - -{{leftbar: /@left-menu}} diff --git a/latte/cs/cookbook/@meta.texy b/latte/cs/cookbook/@meta.texy new file mode 100644 index 0000000000..021702282f --- /dev/null +++ b/latte/cs/cookbook/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Latte Dokumentace}} +{{leftbar: /@left-menu}} diff --git a/latte/cs/cookbook/grouping.texy b/latte/cs/cookbook/grouping.texy new file mode 100644 index 0000000000..bdb563efb5 --- /dev/null +++ b/latte/cs/cookbook/grouping.texy @@ -0,0 +1,251 @@ +Všechno, co jste kdy chtěli vědět o seskupování +*********************************************** + +.[perex] +Při práci s daty ve šablonách můžete často narazit na potřebu jejich seskupování nebo specifického zobrazení podle určitých kritérií. Latte pro tento účel nabízí hned několik silných nástrojů. + +Filtr a funkce `|group` umožňují efektivní seskupení dat podle zadaného kritéria, filtr `|batch` zase usnadňuje rozdělení dat do pevně daných dávek a značka `{iterateWhile}` poskytuje možnost složitějšího řízení průběhu cyklů s podmínkami. Každá z těchto značek nabízí specifické možnosti pro práci s daty, čímž se stávají nepostradatelnými nástroji pro dynamické a strukturované zobrazení informací v Latte šablonách. + + +Filtr a funkce `group` .{data-version:3.0.16} +============================================= + +Představte si databázovou tabulku `items` s položkami rozdělenou do kategorií: + +| id | categoryId | name +|------------------ +| 1 | 1 | Apple +| 2 | 1 | Banana +| 3 | 2 | PHP +| 4 | 3 | Green +| 5 | 3 | Red +| 6 | 3 | Blue + +Jednoduchý seznam všech položek pomocí Latte šablony by vypadal takto: + +```latte +
      +{foreach $items as $item} +
    • {$item->name}
    • +{/foreach} +
    +``` + +Pokud bychom ale chtěli, aby položky byly uspořádány do skupin podle kategorie, potřebujeme je rozdělit tak, že každá kategorie bude mít svůj vlastní seznam. Výsledek by pak měl vypadat následovně: + +```latte +
      +
    • Apple
    • +
    • Banana
    • +
    + +
      +
    • PHP
    • +
    + +
      +
    • Green
    • +
    • Red
    • +
    • Blue
    • +
    +``` + +Úkol se dá snadno a elegantně vyřešit pomocí `|group`. Jako parametr uvedeme `categoryId`, což znamená, že se položky rozdělí do menších polí podle hodnoty `$item->categoryId` (pokud by `$item` bylo pole, použije se `$item['categoryId']`): + +```latte +{foreach ($items|group: categoryId) as $categoryId => $categoryItems} +
      + {foreach $categoryItems as $item} +
    • {$item->name}
    • + {/foreach} +
    +{/foreach} +``` + +Filtr lze v Latte použít i jako funkci, což nám dává alternativní syntaxi: `{foreach group($items, categoryId) ...}`. + +Chcete-li seskupovat položky podle složitějších kritérií, můžete v parametru filtru použít funkci. Například, seskupení položek podle délky názvu by vypadalo takto: + +```latte +{foreach ($items|group: fn($item) => strlen($item->name)) as $items} + ... +{/foreach} +``` + +Je důležité si uvědomit, že `$categoryItems` není běžné pole, ale objekt, který se chová jako iterátor. Pro přístup k první položce skupiny můžete použít funkci [`first()` |latte:functions#first]. + +Tato flexibilita v seskupování dat činí `group` výjimečně užitečným nástrojem pro prezentaci dat v šablonách Latte. + + +Vnořené smyčky +-------------- + +Představme si, že máme databázovou tabulku s dalším sloupcem `subcategoryId`, který definuje podkategorie jednotlivých položek. Chceme zobrazit každou hlavní kategorii v samostatném seznamu `
      ` a každou podkategorii v samostatném vnořeném seznamu `
        `: + +```latte +{foreach ($items|group: categoryId) as $categoryItems} +
          + {foreach ($categoryItems|group: subcategoryId) as $subcategoryItems} +
            + {foreach $subcategoryItems as $item} +
          1. {$item->name} + {/foreach} +
          + {/foreach} +
        +{/foreach} +``` + + +Spojení s Nette Database +------------------------ + +Pojďme si ukázat, jak efektivně využít seskupování dat v kombinaci s Nette Database. Předpokládejme, že pracujeme s tabulkou `items` z úvodního příkladu, která je prostřednictvím sloupce `categoryId` spojená s touto tabulkou `categories`: + +| categoryId | name | +|------------|------------| +| 1 | Fruits | +| 2 | Languages | +| 3 | Colors | + +Data z tabulky `items` načteme pomocí Nette Database Explorer příkazem `$items = $db->table('items')`. Během iterace nad těmito daty máme možnost přistupovat nejen k atributům jako `$item->name` a `$item->categoryId`, ale díky propojení s tabulkou `categories` také k souvisejícímu řádku v ní přes `$item->category`. Na tomto propojení lze demonstrovat zajímavé využití: + +```latte +{foreach ($items|group: category) as $category => $categoryItems} +

        {$category->name}

        +
          + {foreach $categoryItems as $item} +
        • {$item->name}
        • + {/foreach} +
        +{/foreach} +``` + +V tomto případě používáme filtr `|group` k seskupení podle propojeného řádku `$item->category`, nikoliv jen dle sloupce `categoryId`. Díky tomu v proměnné klíči přímo `ActiveRow` dané kategorie, což nám umožňuje přímo vypisovat její název pomocí `{$category->name}`. Toto je praktický příklad, jak může seskupování zpřehlednit šablony a usnadnit práci s daty. + + +Filtr `|batch` +============== + +Filtr umožňuje rozdělit seznam prvků do skupin s předem určeným počtem prvků. Tento filtr je ideální pro situace, kdy chcete data prezentovat ve více menších skupinách, například pro lepší přehlednost nebo vizuální uspořádání na stránce. + +Představme si, že máme seznam položek a chceme je zobrazit v seznamech, kde každý obsahuje maximálně tři položky. Použití filtru `|batch` je v takovém případě velmi praktické: + +```latte +
          +{foreach ($items|batch: 3) as $batch} + {foreach $batch as $item} +
        • {$item->name}
        • + {/foreach} +{/foreach} +
        +``` + +V tomto příkladu je seznam `$items` rozdělen do menších skupin, přičemž každá skupina (`$batch`) obsahuje až tři položky. Každá skupina je poté zobrazena v samostatném `
          ` seznamu. + +Pokud poslední skupina neobsahuje dostatek prvků k dosažení požadovaného počtu, druhý parametr filtru umožňuje definovat, čím bude tato skupina doplněna. To je ideální pro estetické zarovnání prvků tam, kde by neúplná řada mohla působit neuspořádaně. + +```latte +{foreach ($items|batch: 3, '—') as $batch} + ... +{/foreach} +``` + + +Značka `{iterateWhile}` +======================= + +Stejné úkoly, jako jsme řešili s filtrem `|group`, si ukážeme s použitím značky `{iterateWhile}`. Hlavní rozdíl mezi oběma přístupy je v tom, že `group` nejprve zpracuje a seskupí všechna vstupní data, zatímco `{iterateWhile}` řídí průběhu cyklů s podmínkami, takže iterace probíhá postupně. + +Nejprve vykreslíme tabulku s kategoriemi pomocí iterateWhile: + +```latte +{foreach $items as $item} +
            + {iterateWhile} +
          • {$item->name}
          • + {/iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
          +{/foreach} +``` + +Zatímco `{foreach}` označuje vnější část cyklu, tedy vykreslování seznamů pro každou kategorii, tak značka `{iterateWhile}` označuje vnitřní část, tedy jednotlivé položky. Podmínka v koncové značce říká, že opakování bude probíhat do té doby, dokud aktuální i následující prvek patří do stejné kategorie (`$iterator->nextValue` je [následující položka |/tags#iterator]). + +Kdyby podmínka byla splněná vždy, tak se ve vnitřním cyklu vykreslí všechny prvky: + +```latte +{foreach $items as $item} +
            + {iterateWhile} +
          • {$item->name} + {/iterateWhile true} +
          +{/foreach} +``` + +Výsledek bude vypadat takto: + +```latte +
            +
          • Apple
          • +
          • Banana
          • +
          • PHP
          • +
          • Green
          • +
          • Red
          • +
          • Blue
          • +
          +``` + +K čemu je takové použití iterateWhile dobré? Když bude tabulka prázdná a nebude obsahovat žádné prvky, nevypíše se prázdné `
            `. + +Pokud uvedeme podmínku v otevírací značce `{iterateWhile}`, tak se chování změní: podmínka (a přechod na další prvek) se vykoná už na začátku vnitřního cyklu, nikoliv na konci. Tedy zatímco do `{iterateWhile}` bez podmínky se vstoupí vždy, do `{iterateWhile $cond}` jen při splnění podmínky `$cond`. A zároveň se s tím do `$item` zapíše následující prvek. + +Což se hodí například v situaci, kdy budeme chtít první prvek v každé kategorii vykreslit jiným způsobem, například takto: + +```latte +

            Apple

            +
              +
            • Banana
            • +
            + +

            PHP

            +
              +
            + +

            Green

            +
              +
            • Red
            • +
            • Blue
            • +
            +``` + +Původní kód upravíme tak, že nejprve vykreslíme první položku a poté ve vnitřním cyklu `{iterateWhile}` vykreslíme další položky ze stejné kategorie: + +```latte +{foreach $items as $item} +

            {$item->name}

            +
              + {iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
            • {$item->name}
            • + {/iterateWhile} +
            +{/foreach} +``` + +V rámci jednoho cyklu můžeme vytvářet více vnitřních smyček a dokonce je zanořovat. Takto by se daly seskupovat třeba podkategorie atd. + +Dejme tomu, že v tabulce bude ještě další sloupec `subcategoryId` a kromě toho, že každá kategorie bude v samostatném `
              `, každá každý podkategorie samostatném `
                `: + +```latte +{foreach $items as $item} +
                  + {iterateWhile} +
                    + {iterateWhile} +
                  1. {$item->name} + {/iterateWhile $item->subcategoryId === $iterator->nextValue->subcategoryId} +
                  + {/iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                +{/foreach} +``` diff --git a/latte/cs/cookbook/how-to-write-sql-queries-in-latte.texy b/latte/cs/cookbook/how-to-write-sql-queries-in-latte.texy index bdfc0feedc..8607366729 100644 --- a/latte/cs/cookbook/how-to-write-sql-queries-in-latte.texy +++ b/latte/cs/cookbook/how-to-write-sql-queries-in-latte.texy @@ -14,8 +14,7 @@ SELECT users.* FROM users WHERE groups.name = 'Admins' {ifset $country} AND country.name = {$country} {/ifset} ``` -Pomocí `$latte->setContentType()` řekneme Latte, aby k obsahu přistupovalo jako k prostému textu (nikoliv jako k HTML) a dále -připravíme escapovací funkci, která bude řetězce escapovat přímo databázovým driverem: +Pomocí `$latte->setContentType()` řekneme Latte, aby k obsahu přistupovalo jako k prostému textu (nikoliv jako k HTML) a dále připravíme escapovací funkci, která bude řetězce escapovat přímo databázovým driverem: ```php $db = new PDO(/* ... */); @@ -39,5 +38,3 @@ $result = $db->query($sql); ``` *Uvedený příklad vyžaduje Latte v3.0.5 nebo vyšší.* - -{{leftbar: /@left-menu}} diff --git a/latte/cs/cookbook/iteratewhile.texy b/latte/cs/cookbook/iteratewhile.texy deleted file mode 100644 index d224273408..0000000000 --- a/latte/cs/cookbook/iteratewhile.texy +++ /dev/null @@ -1,214 +0,0 @@ -Všechno, co jste kdy chtěli vědět o {iterateWhile} -************************************************** - -.[perex] -Značka `{iterateWhile}` se hodí na nejrůznější kejkle ve foreach cyklech. - -Dejme tomu, že máme následující databázovou tabulku, kde jsou položky rozdělené do kategorií: - -| id | catId | name -|------------------ -| 1 | 1 | Apple -| 2 | 1 | Banana -| 3 | 2 | PHP -| 4 | 3 | Green -| 5 | 3 | Red -| 6 | 3 | Blue - -Vykreslit položky ve foreach cyklu jako seznam je samozřejmě snadné: - -```latte -
                  -{foreach $items as $item} -
                • {$item->name}
                • -{/foreach} -
                -``` - -Ale co kdybychom chtěli, aby každá kategorie byla v samostatném seznamu? Jinými slovy, řešíme úkol, jak seskupit položky v lineárním seznamu ve foreach cyklu do skupin. Výstup by měl vypadat takto: - -```latte -
                  -
                • Apple
                • -
                • Banana
                • -
                - -
                  -
                • PHP
                • -
                - -
                  -
                • Green
                • -
                • Red
                • -
                • Blue
                • -
                -``` - -Rovnou si ukážeme, jak snadno a elegantně se dá úkol vyřešit pomocí iterateWhile: - -```latte -{foreach $items as $item} -
                  - {iterateWhile} -
                • {$item->name}
                • - {/iterateWhile $item->catId === $iterator->nextValue->catId} -
                -{/foreach} -``` - -Zatímco `{foreach}` označuje vnější část cyklu, tedy vykreslování seznamů pro každou kategorii, tak značka `{iterateWhile}` označuje vnitřní část, tedy jednotlivé položky. -Podmínka v koncové značce říká, že opakování bude probíhat do té doby, dokud aktuální i následující prvek patří do stejné kategorie (`$iterator->nextValue` je [následující položka|/tags#$iterator]). - -Kdyby podmínka byla splněná vždy, tak se ve vnitřním cyklu vykreslí všechny prvky: - -```latte -{foreach $items as $item} -
                  - {iterateWhile} -
                • {$item->name} - {/iterateWhile true} -
                -{/foreach} -``` - -Výsledek bude vypadat takto: - -```latte -
                  -
                • Apple
                • -
                • Banana
                • -
                • PHP
                • -
                • Green
                • -
                • Red
                • -
                • Blue
                • -
                -``` - -K čemu je takové použití iterateWhile dobré? Jak se liší od řešení, které jsme si ukázali úplně na začátku tohoto návodu? Rozdíl je v tom, že když bude tabulka prázdná a nebude obsahovat žádné prvky, nevypíše se prázdné `
                  `. - - -Řešení bez `{iterateWhile}` ---------------------------- - -Pokud bychom stejný úkol řešili zcela základními prostředky šablonovacích systému, například v Twig, Blade, nebo čistém PHP, vypadalo by řešení cca takto: - -```latte -{var $prevCatId = null} -{foreach $items as $item} - {if $item->catId !== $prevCatId} - {* změnila se kategorie *} - - {* uzavřeme předchozí
                    , pokud nejde o první položku *} - {if $prevCatId !== null} -
                  - {/if} - - {* otevřeme nový seznam *} -
                    - - {do $prevCatId = $item->catId} - {/if} - -
                  • {$item->name}
                  • -{/foreach} - -{if $prevCatId !== null} - {* uzavřeme poslední seznam *} -
                  -{/if} -``` - -Tento kód je však nesrozumitelný a neintuitivní. Není vůbec jasná vazba mezi otevíracími a zavíracími HTML značkami. Není na první pohled vidět, jestli tam není nějaká chyba. A vyžaduje pomocné proměnné jako `$prevCatId`. - -Oproti tomu řešení s `{iterateWhile}` je čisté, přehledné, nepotřebujeme pomocné proměnné a je blbuvzdorné. - - -Podmínka v otevírací značce ---------------------------- - -Pokud uvedeme podmínku v otevírací značce `{iterateWhile}`, tak se chování změní: podmínka (a přechod na další prvek) se vykoná už na začátku vnitřního cyklu, nikoliv na konci. -Tedy zatímco do `{iterateWhile}` bez podmínky se vstoupí vždy, do `{iterateWhile $cond}` jen při splnění podmínky `$cond`. A zároveň se s tím do `$item` zapíše následující prvek. - -Což se hodí například v situaci, kdy budeme chtít první prvek v každé kategorii vykreslit jiným způsobem, například takto: - -```latte -

                  Apple

                  -
                    -
                  • Banana
                  • -
                  - -

                  PHP

                  -
                    -
                  - -

                  Green

                  -
                    -
                  • Red
                  • -
                  • Blue
                  • -
                  -``` - -Původní kód upravíme tak, že nejprve vykreslíme první položku a poté ve vnitřním cyklu `{iterateWhile}` vykreslíme další položky ze stejné kategorie: - -```latte -{foreach $items as $item} -

                  {$item->name}

                  -
                    - {iterateWhile $item->catId === $iterator->nextValue->catId} -
                  • {$item->name}
                  • - {/iterateWhile} -
                  -{/foreach} -``` - - -Vnořené smyčky --------------- - -V rámci jednoho cyklu můžeme vytvářet více vnitřních smyček a dokonce je zanořovat. Takto by se daly seskupovat třeba podkategorie atd. - -Dejme tomu, že v tabulce bude ještě další sloupec `subCatId` a kromě toho, že každá kategorie bude v samostatném `
                    `, každá každý podkategorie samostatném `
                      `: - -```latte -{foreach $items as $item} -
                        - {iterateWhile} -
                          - {iterateWhile} -
                        1. {$item->name} - {/iterateWhile $item->subCatId === $iterator->nextValue->subCatId} -
                        - {/iterateWhile $item->catId === $iterator->nextValue->catId} -
                      -{/foreach} -``` - - -Filtr |batch ------------- - -Seskupování lineárních položek obstarává také filtr `batch`, a to do dávek s pevným počtem prvků: - -```latte -
                        -{foreach ($items|batch:3) as $batch} - {foreach $batch as $item} -
                      • {$item->name}
                      • - {/foreach} -{/foreach} -
                      -``` - -Lze jej nahradit za iterateWhile tímto způsobem: - -```latte -
                        -{foreach $items as $item} - {iterateWhile} -
                      • {$item->name}
                      • - {/iterateWhile $iterator->counter0 % 3} -{/foreach} -
                      -``` - -{{leftbar: /@left-menu}} diff --git a/latte/cs/cookbook/migration-from-latte-30.texy b/latte/cs/cookbook/migration-from-latte-30.texy new file mode 100644 index 0000000000..03211a226d --- /dev/null +++ b/latte/cs/cookbook/migration-from-latte-30.texy @@ -0,0 +1,109 @@ +Migrace z Latte 3.0 +******************* + +.[perex] +Latte 3.1 přináší několik vylepšení a změn, díky kterým je psaní šablon bezpečnější a pohodlnější. Většina změn je zpětně kompatibilní, ale některé vyžadují pozornost při přechodu. Tento průvodce shrnuje BC breaky a jak je řešit. + +Latte 3.1 vyžaduje **PHP 8.2** nebo novější. + + +Chytré atributy a migrace +========================= + +Nejvýznamnější změnou v Latte 3.1 je nové chování [chytrých atributů |/html-attributes]. To ovlivňuje, jak se vykreslují hodnoty `null` a logické hodnoty v `data-` atributech. + +1. **Hodnoty `null`:** Dříve se `title={$null}` vykresloval jako `title=""`. Nyní se atribut zcela vynechá. +2. **`data-` atributy:** Dříve se `data-foo={=true}` / `data-foo={=false}` vykreslovaly jako `data-foo="1"` / `data-foo=""`. Nyní se vykreslují jako `data-foo="true"` / `data-foo="false"`. + +Abychom vám pomohli identifikovat místa, kde se výstup ve vaší aplikaci změnil, Latte poskytuje migrační nástroj. + + +Migrační varování +----------------- + +Můžete zapnout [migrační varování |/develop#Migrační varování], která vás během vykreslování upozorní, pokud se výstup liší od Latte 3.0. + +```php +$latte = new Latte\Engine; +$latte->setFeature(Latte\Feature::MigrationWarnings); +``` + +Pokud jsou povolena, sledujte logy aplikace nebo Tracy bar pro `E_USER_WARNING`. Každé varování bude ukazovat na konkrétní řádek a sloupec v šabloně. + +**Jak varování vyřešit:** + +Pokud je nové chování správné (např. chcete, aby prázdný atribut zmizel), potvrďte jej použitím filtru `|accept` pro potlačení varování: + +```latte +
                      +``` + +Pokud chcete atribut zachovat jako prázdný (např. `title=""`) místo jeho vynechání, použijte null coalescing operátor: + +```latte +
                      +``` + +Nebo, pokud striktně vyžadujete staré chování (např. `"1"` pro `true`), explicitně přetypujte hodnotu na string: + +```latte +
                      +``` + +**Poté, co vyřešíte všechna varování:** + +Jakmile vyřešíte všechna varování, vypněte migrační varování a **odstraňte všechny** filtry `|accept` ze svých šablon, protože již nejsou potřeba. + + +Strict Types +============ + +Latte 3.1 zapíná `declare(strict_types=1)` ve výchozím nastavení pro všechny kompilované šablony. To zlepšuje typovou bezpečnost, ale může způsobit typové chyby v PHP výrazech uvnitř šablon, pokud jste spoléhali na volné typování. + +Pokud typy nemůžete opravit okamžitě, můžete toto chování vypnout: + +```php +$latte->setFeature(Latte\Feature::StrictTypes, false); +``` + + +Globální konstanty +================== + +Parser šablon byl vylepšen, aby lépe rozlišoval mezi jednoduchými řetězci a konstantami. V důsledku toho musí být globální konstanty nyní prefixovány zpětným lomítkem `\`. + +```latte +{* Starý způsob (vyhodí varování, v budoucnu bude interpretováno jako string 'PHP_VERSION') *} +{if PHP_VERSION > ...} + +{* Nový způsob (správně interpretováno jako konstanta) *} +{if \PHP_VERSION > ...} +``` + +Tato změna předchází nejednoznačnostem a umožňuje volnější používání neuvodzovkovaných řetězců. + + +Odstraněné funkce +================= + +**Rezervované proměnné:** Proměnné začínající na `$__` (dvou podtržítko) a proměnná `$this` jsou nyní vyhrazeny pro vnitřní použití Latte. Nemůžete je používat v šablonách. + +**Undefined-safe operátor:** Operátor `??->`, což byla specifická funkce Latte vytvořená před PHP 8, byl odstraněn. Jde o historický relikt. Používejte prosím standardní PHP nullsafe operátor `?->`. + +**Filter Loader** +Metoda `Engine::addFilterLoader()` byla označena jako zastaralá a odstraněna. Šlo o nekonzistentní koncept, který se jinde v Latte nevyskytoval. + +**Date Format** +Statická vlastnost `Latte\Runtime\Filters::$dateFormat` byla odstraněna, aby se předešlo globálnímu stavu. + + +Nové funkce +=========== + +Během migrace si můžete začít užívat nové funkce: + +- **Chytré HTML atributy:** Předávání polí do `class` a `style`, automatické vynechání `null` atributů. +- **Nullsafe filtry:** Použijte `{$var?|filter}` pro přeskočení filtrování null hodnot. +- **`n:elseif`:** Nyní můžete používat `n:elseif` společně s `n:if` a `n:else`. +- **Zjednodušená syntaxe:** Pište `
                      ` bez uvozovek. +- **Toggle filtr:** Použijte `|toggle` pro ruční ovládání boolean atributů. diff --git a/latte/cs/cookbook/migration-from-latte2.texy b/latte/cs/cookbook/migration-from-latte2.texy index 522d6d3070..3425b44f2c 100644 --- a/latte/cs/cookbook/migration-from-latte2.texy +++ b/latte/cs/cookbook/migration-from-latte2.texy @@ -1,5 +1,5 @@ -Migrace z Latte v2 na v3 -************************ +Migrace z Latte 2 na 3 +********************** .[perex] Latte 3 má kompletně přepsaným kompilátor a formálně přesně definovanou gramatiku. Ta by měla co nejvíce odpovídat Latte 2, ale existují konstrukce, které je třeba drobně upravit. @@ -8,7 +8,7 @@ V praxi se ukazuje, že naprostou většinu šablon není potřeba nijak upravov **Nejprve si nainstalujte přechodovou verzi Latte 2.11.** -Tato verze nepřináší žádné novinky, jen pomocí E_USER_DEPRECATED upozorňuje na případy, u kterých ví, že je nové Latte nebude podporovat, a hlavně poradí, jak je upravit. Projít všechny šablony a otestovat, jestli jsou kompatibilní, vám pomůže nástroj [Linter|/develop#linter], který spustíte z konzole: +Tato verze nepřináší žádné novinky, jen pomocí E_USER_DEPRECATED upozorňuje na případy, u kterých ví, že je nové Latte nebude podporovat, a hlavně poradí, jak je upravit. Projít všechny šablony a otestovat, jestli jsou kompatibilní, vám pomůže nástroj [Linter |/develop#Linter], který spustíte z konzole: ```shell vendor/bin/latte-lint @@ -22,7 +22,7 @@ Změny v API Změny v API se týkají jen přidávaní vlastních značek. Zbytek API zůstává stejný jako u verze 2, tj. stejným způsobem se vykreslují šablony, předávají parametry, registrují filtry. -Výjimkou je nahrazení tzv. dynamického filtru `Engine::addFilter(null, ...)` za [zavaděč filtrů |/extending-latte#Zavaděč filtrů], který si liší tím, že vrací vždy callable a registruje se metodou `Engine::addFilterLoader()`. +Výjimkou je nahrazení tzv. dynamického filtru `Engine::addFilter(null, ...)` za [zavaděč filtrů |/custom-filters#Filtry používající třídu s atributy], který si liší tím, že vrací vždy callable a registruje se metodou `Engine::addFilterLoader()`. API pro přidávání vlastních značek je úplně jiné, takže doplňky určené pro Latte 2 s ním nebudou fungovat. Dále viz [#aktualizace doplňků]. @@ -46,14 +46,14 @@ A ještě okrajové případy: - atribut `n:inner-snippet` musí být psán bez inner- - musí být ukončené značky `` a `` - "odstranění magické proměnné `$iterations`":https://forum.nette.org/cs/35217-latte-3-magicka-promenna-iterations (neplést s `$iterator`!) -- značku `{includeblock file.latte}` nahrazuje [`{include file.latte with blocks}`|/tags#include] nebo [`{import}`|/template-inheritance#horizontální znovupoužití] +- značku `{includeblock file.latte}` nahrazuje [`{include file.latte with blocks}` |/tags#include] nebo [`{import}` |/template-inheritance#Horizontální znovupoužití] - `{include "abc"}` by mělo být psáno jako `{include file "abc"}`, pokud `"abc"` neobsahuje tečku a není tak jasné, že jde o soubor Aktualizace doplňků =================== -S kompletním přepsáním parseru se zcela změnil i způsob psaní vlastních značek. Pokud máte pro Latte vytvořené vlastní značky, bude třeba je napsat znovu pro verzi 3, viz [dokumentace|/creating-extension]. +S kompletním přepsáním parseru se zcela změnil i způsob psaní vlastních značek. Pokud máte pro Latte vytvořené vlastní značky, bude třeba je napsat znovu pro verzi 3, viz [dokumentace|/custom-tags]. Pokud používáte cizí doplněk, který přidává značky, je potřeba počkat, až autor vydá verzi pro Latte 3. Knihovny `nette/application`, `nette/caching` a `nette/forms` ve verzi 3.1 a také Texy již aktualizovány jsou a fungují jak s Latte 2, tak i 3. @@ -128,6 +128,26 @@ $latte->addExtension(new Nette\Bridges\CacheLatte\CacheExtension($cacheStorage)) ``` +Tracy +----- + +Panel pro Tracy se nyní aktivuje také jako rozšíření. + +Starý kód pro Latte 2: + +```php +$latte = new Latte\Engine; +Latte\Bridges\Tracy\LattePanel::initialize($latte); +``` + +Nový kód pro Latte 3: + +```php +$latte = new Latte\Engine; +$latte->addExtension(new Latte\Bridges\Tracy\TracyExtension); +``` + + Překlady -------- @@ -145,19 +165,19 @@ Nový kód pro Latte 3: $latte->addExtension(new Latte\Essential\TranslatorExtension($translator)); ``` -V presenterech se automaticky aktivuje tím, že nastavíte šabloně translator metodou `$template->setTranslator($translator)`. Bez toho značky pro překlad nebudou k dispozici a je potřeba rozšíření zaregistrovat ručně, nebo pomocí konfiguračního souboru. +V presenterech se automaticky aktivuje tím, že nastavíte šabloně translator metodou `$template->setTranslator($translator)`. Bez toho značky pro překlad nebudou k dispozici a je potřeba rozšíření zaregistrovat ručně, nebo pomocí konfiguračního souboru, viz dále. Konfigurační soubor =================== -V Latte 2 bylo možné registrovat nové tagy pomocí [konfiguračního souboru |application:configuration#Šablony Latte] v sekci `latte › macros`. Ve verzi 3 se přidávají celé rozšíření tímto způsobem: +V Latte 2 bylo možné registrovat nové tagy pomocí [konfiguračního souboru |application:configuration#Šablony Latte] v sekci `latte › macros`. Ve verzi 3 se přidávají rozšíření tímto způsobem: ```neon latte: extensions: - App\Templating\LatteExtension - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` @@ -188,7 +208,7 @@ $this->latte->onCompile[] = function (Latte\Engine $latte) { }; ``` -Latte 3 se rozšiřuje pomocí tzv. [extensions|/creating-extension]. Triviální extension přidávající značku `foo` by vypadalo takto: +Latte 3 se rozšiřuje pomocí tzv. [extensions|/extending-latte]. Triviální extension přidávající značku `foo` by vypadalo takto: ```php // nový kód pro Latte 3 @@ -234,7 +254,7 @@ class FooNode extends Latte\Compiler\Nodes\StatementNode } ``` -Dále maska v `$context->format()` už nemá zkratky `%node.***`, předpokládá se, že obsah značky [nejprve naparsujete |/creating-extension#Parsovací funkce tagu]. Takže využijeme parser a naparsujeme obsah do proměnných (poduzlů), a poté vypíšeme: +Dále maska v `$context->format()` už nemá zkratky `%node.***`, předpokládá se, že obsah značky [nejprve naparsujete |/custom-tags#Funkce pro parsování tagu]. Takže využijeme parser a naparsujeme obsah do proměnných (poduzlů), a poté vypíšeme: ```php use Latte\Compiler\Nodes\Php\Expression\ArrayNode; @@ -266,7 +286,7 @@ class FooNode extends Latte\Compiler\Nodes\StatementNode } ``` -A nakonec doplníme metodu `getIterator()`, aby bylo možné poduzly procházet při tzv. [traversování |/creating-extension#Node Traverser]: +A nakonec doplníme metodu `getIterator()`, aby bylo možné poduzly procházet při tzv. [traversování |/custom-tags#Implementace getIterator pro poduzly]: ```php class FooNode extends Latte\Compiler\Nodes\StatementNode @@ -282,4 +302,3 @@ class FooNode extends Latte\Compiler\Nodes\StatementNode ``` {{priority: -1}} -{{leftbar: /@left-menu}} diff --git a/latte/cs/cookbook/migration-from-php.texy b/latte/cs/cookbook/migration-from-php.texy index f17181af91..76404b348f 100644 --- a/latte/cs/cookbook/migration-from-php.texy +++ b/latte/cs/cookbook/migration-from-php.texy @@ -2,7 +2,7 @@ Migrace z PHP do Latte ********************** .[perex] -Převádíte starý projekt napsaný v čistém PHP do Latte? Máme pro vás nástroj, které vám migraci usnadní. [Vyzkoušejte jej online |https://php2latte.nette.org]. +Převádíte starý projekt napsaný v čistém PHP do Latte? Máme pro vás nástroj, které vám migraci usnadní. [Vyzkoušejte jej online |https://fiddle.nette.org/php2latte/]. Nástroj si můžete stáhnout z [GitHubu|https://github.com/nette/latte-tools] nebo nainstalovat pomocí Composeru: @@ -68,5 +68,3 @@ Vygeneruje tuto šablonu:
                      ``` - -{{leftbar: /@left-menu}} diff --git a/latte/cs/cookbook/migration-from-twig.texy b/latte/cs/cookbook/migration-from-twig.texy index 4ded46f341..8bc716d5e8 100644 --- a/latte/cs/cookbook/migration-from-twig.texy +++ b/latte/cs/cookbook/migration-from-twig.texy @@ -2,7 +2,7 @@ Migrace z Twigu do Latte ************************ .[perex] -Převádíte projekt napsaný v Twigu do modernějšího Latte? Máme pro vás nástroj, které vám migraci usnadní. [Vyzkoušejte jej online |https://twig2latte.nette.org]. +Převádíte projekt napsaný v Twigu do modernějšího Latte? Máme pro vás nástroj, které vám migraci usnadní. [Vyzkoušejte jej online |https://fiddle.nette.org/twig2latte/]. Nástroj si můžete stáhnout z [GitHubu|https://github.com/nette/latte-tools] nebo nainstalovat pomocí Composeru: @@ -32,7 +32,7 @@ Příklad Vstupní soubor může vypadat třeba takto: -```latte +```twig {% use "blocks.twig" %} @@ -77,5 +77,3 @@ Po konverzi do Latte získáme tuto šablonu: ``` - -{{leftbar: /@left-menu}} diff --git a/latte/cs/cookbook/passing-variables.texy b/latte/cs/cookbook/passing-variables.texy new file mode 100644 index 0000000000..fefbe704ee --- /dev/null +++ b/latte/cs/cookbook/passing-variables.texy @@ -0,0 +1,158 @@ +Předávání proměnných napříč šablonami +************************************* + +Tento průvodce vám vysvětlí, jak se proměnné předávají mezi šablonami v Latte pomocí různých tagů jako `{include}`, `{import}`, `{embed}`, `{layout}`, `{sandbox}` a dalších. Dozvíte se také, jak pracovat s proměnnými v tagu `{block}` a `{define}`, a k čemu slouží značka `{parameters}`. + + +Typy proměnných +--------------- +Proměnné v Latte můžeme rozdělit do tří kategorií podle toho, jak a kde jsou definovány: + +**Vstupní proměnné** jsou ty, které jsou do šablony předávány zvenčí, například z PHP skriptu nebo pomocí tagu jako `{include}`. + +```php +$latte->render('template.latte', ['userName' => 'Jan', 'userAge' => 30]); +``` + +**Okolní proměnné** jsou proměnné existující v místě určité značky. Zahrnují všechny vstupní proměnné a další proměnné vytvořené pomocí tagů jako `{var}`, `{default}` nebo v rámci smyčky `{foreach}`. + +```latte +{foreach $users as $user} + {include 'userBox.latte', user: $user} +{/foreach} +``` + +**Explicitní proměnné** jsou ty, které jsou přímo specifikovány uvnitř tagu a jsou odeslány do cílové šablony. + +```latte +{include 'userBox.latte', name: $user->name, age: $user->age} +``` + + +`{block}` +--------- +Tag `{block}` se používá k definování opakovaně použitelných bloků kódu, které lze v dědičných šablonách přizpůsobit nebo rozšířit. Okolní proměnné definované před blokem jsou dostupné uvnitř bloku, ale jakékoli změny proměnných se projeví jen v rámci toho bloku. + +```latte +{var $foo = 'původní'} +{block example} + {var $foo = 'změněný'} +{/block} + +{$foo} // vypíše: původní +``` + + +`{define}` +---------- +Tag `{define}` slouží k vytváření bloků, které se renderují až po jejich zavolání pomocí `{include}`. Proměnné dostupné uvnitř těchto bloků závisí na tom, zda jsou v definici uvedeny parametry. Pokud ano, přístup mají jen k těmto parametrům. Pokud ne, přístup mají ke všem vstupním proměnným šablony, ve které jsou bloky definovány. + +```latte +{define hello} + {* má přístup ke všem vstupním proměnným šablony *} +{/define} + +{define hello $name} + {* má přístup jen k parametru $name *} +{/define} +``` + + +`{parameters}` +-------------- +Tag `{parameters}` slouží k explicitní deklaraci očekávaných vstupních proměnných na začátku šablony. Tímto způsobem lze snadno dokumentovat očekávané proměnné a jejich datové typy. Také je možné definovat výchozí hodnoty. + +```latte +{parameters int $age, string $name = 'neznámé'} +

                      Věk: {$age}, Jméno: {$name}

                      +``` + + +`{include file}` +---------------- +Tag `{include file}` slouží k vložení celé šablony. Této šabloně se předávají jak vstupní proměnné šablony, ve které je značka použita, tak proměnné v ní explicitně definované. Cílová šablona ale může rozsah omezit pomocí `{parameters}`. + +```latte +{include 'profile.latte', userId: $user->id} +``` + + +`{include block}` +----------------- +Když vkládáte blok definovaný ve stejné šabloně, předávají se do něj všechny okolní a explicitně definované proměnné: + +```latte +{define blockName} +

                      Jméno: {$name}, Věk: {$age}

                      +{/define} + +{var $name = 'Jan', $age = 30} +{include blockName} +``` + +V tomto příkladu se proměnné `$name` a `$age` předají do bloku `blockName`. Stejným způsobem se chová i `{include parent}`. + +Při vkládání bloku z jiné šablony jsou předávány pouze vstupní proměnné a explicitně definované. Okolní proměnné nejsou automaticky dostupné. + +```latte +{include blockInOtherTemplate, name: $name, age: $age} +``` + + +`{layout}` nebo `{extends}` +--------------------------- +Tyto tagy definují layout, do kterého se předávají vstupní proměnné podřízené šablony a dále proměnné vytvořené v kódu před bloky: + +```latte +{layout 'layout.latte'} +{var $seo = 'index, follow'} +``` + +Šablona `layout.latte`: + +```latte + + + +``` + + +`{embed}` +--------- +Tag `{embed}` je podobný tagu `{include}`, ale umožňuje vkládání bloků do šablony. Na rozdíl od `{include}` se předávají pouze explicitně deklarované proměnné: + +```latte +{embed 'menu.latte', items: $menuItems} +{/embed} +``` + +V tomto příkladu má šablona `menu.latte` přístup pouze k proměnné `$items`. + +Naopak v blocích uvnitř `{embed}` je přístup ke všem okolním proměnným: + +```latte +{var $name = 'Jan'} +{embed 'menu.latte', items: $menuItems} + {block foo} + {$name} + {/block} +{/embed} +``` + + +`{import}` +---------- +Tag `{import}` se využívá pro načítání bloků z jiných šablon. Přenáší se jak vstupní, tak explicitně deklarované proměnné do importovaných bloků. + +```latte +{import 'buttons.latte'} +``` + + +`{sandbox}` +----------- +Tag `{sandbox}` izoluje šablonu pro bezpečné zpracování. Proměnné jsou předávány výhradně explicitně. + +```latte +{sandbox 'secure.latte', data: $secureData} +``` diff --git a/latte/cs/cookbook/slim-framework.texy b/latte/cs/cookbook/slim-framework.texy index 0a5158e90f..f319d69112 100644 --- a/latte/cs/cookbook/slim-framework.texy +++ b/latte/cs/cookbook/slim-framework.texy @@ -139,7 +139,7 @@ final class HomeAction Aby to fungovalo, vytvořte soubor šablony v `templates/home.latte` s tímto obsahem: ```latte -
                        +
                          {foreach $items as $item}
                        • {$item|capitalize}
                        • {/foreach} @@ -155,4 +155,3 @@ Three ``` {{priority: -1}} -{{leftbar: /@left-menu}} diff --git a/latte/cs/creating-extension.texy b/latte/cs/creating-extension.texy deleted file mode 100644 index 365a656e46..0000000000 --- a/latte/cs/creating-extension.texy +++ /dev/null @@ -1,579 +0,0 @@ -Vytváříme Extension -******************* - -.[perex]{data-version:3.0} -Tzv. rozšíření je opakovatelně použitelná třída, která může definovat vlastní značky, filtry, funkce, providery, atd. - -Rozšíření vytváříme tehdy, pokud chceme své úpravy Latte znovu použít v různých projektech nebo je sdílet s ostatními. -Je užitečné vytvářet rozšíření i pro každý webový projekt, které bude obsahovat všechny specifické značky a filtry, které chcete v šablonách projektu využívat. - - -Třída rozšíření -=============== - -Rozšíření je třída dědící od [api:Latte\Extension]. Do Latte se registruje pomocí `addExtension()` (případně [konfiguračním souborem |application:configuration#Šablony Latte]): - -```php -$latte = new Latte\Engine; -$latte->addExtension(new MyLatteExtension); -``` - -Pokud zaregistrujete vícero rozšíření a ty definují stejně pojmenované tagy, filtry nebo funkce, vyhrává poslední přidané rozšíření. Z toho také plyne, že vaše rozšíření mohou přepisovat nativní značky/filtry/funkce. - -Kdykoliv v třídě provedete změnu a není vypnutý auto-refresh, Latte automaticky překompiluje vaše šablony. - -Třída může implementovat kteroukoliv z následujících metod: - -```php -abstract class Extension -{ - /** - * Inicializace před kompilací šablony. - */ - public function beforeCompile(Engine $engine): void; - - /** - * Vrací seznam parserů pro značky Latte. - * @return array - */ - public function getTags(): array; - - /** - * Vrací seznam průchodů kompilátoru. - * @return array - */ - public function getPasses(): array; - - /** - * Vrací seznam |filtrů. - * @return array - */ - public function getFilters(): array; - - /** - * Vrací seznam funkcí použitých v šablonách. - * @return array - */ - public function getFunctions(): array; - - /** - * Vrací seznam providerů. - * @return array - */ - public function getProviders(): array; - - /** - * Vrací hodnotu pro rozlišení více verzí šablony. - */ - public function getCacheKey(Engine $engine): mixed; - - /** - * Inicializace před vykreslením šablony. - */ - public function beforeRender(Template $template): void; -} -``` - -Pro představu, jak rozšíření vypadá, se podívejte na vestavěné "CoreExtension":https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php. - - -beforeCompile(Latte\Engine $engine): void .[method] ---------------------------------------------------- - -Volá se před kompilací šablony. Metodu lze využít např. pro inicializace související s kompilací. - - -getTags(): array .[method] --------------------------- - -Volá se při kompilaci šablony. Vrací asociativní pole *název tagu => callable*, což jsou [#parsovací funkce tagu]. - -```php -public function getTags(): array -{ - return [ - 'foo' => [FooNode::class, 'create'], - 'bar' => [BarNode::class, 'create'], - 'n:baz' => [NBazNode::class, 'create'], - // ... - ]; -} -``` - -Značka `n:baz` představuje ryzí n:atribut, tj. jde o značku, kterou lze zapisovat pouze jako atribut. - -V případě značek `foo` a `bar` Latte samo rozezná, zda jsou párové, a pokud ano, bude je možné automaticky zapisovat i pomocí n:atributů, včetně variant s prefixy `n:inner-foo` a `n:tag-foo`. - -Pořadí provádění takovýchto n:atributů je dáno jejich pořadím v poli vráceném `getTags()`. Tedy `n:foo` se provede vždy před `n:bar`, i kdyby byly atributy v HTML značce uvedeny v opačném pořadí jako `
                          `. - -Pokud potřebujete stanovit pořadí n:atributů napříč více rozšířeními, použijte pomocnou metodu `order()`, kde parametr `before` a nebo `after` určuje, před nebo za kterými značkami se daná značka zařadí. - -```php -public function getTags(): array -{ - return [ - 'foo' => self::order([FooNode::class, 'create'], before: 'bar')] - 'bar' => self::order([BarNode::class, 'create'], after: ['block', 'snippet'])] - ]; -} -``` - - -getPasses(): array .[method] ----------------------------- - -Volá se při kompilaci šablony. Vrací asociativní pole *název pass => callable*, což jsou funkce představující tzv. [#průchody kompilátoru], které procházejí a modifikujou AST. - -Opět je možné využít pomocnou metodu `order()`. Hodnotou parametrů `before` nebo `after` může být `'*'` s významem před/za všemi. - -```php -public function getPasses(): array -{ - return [ - 'optimize' => [Passes::class, 'optimizePass'], - 'sandbox' => self::order([$this, 'sandboxPass'], before: '*'), - // ... - ]; -} -``` - - -beforeRender(Latte\Engine $engine): void .[method] --------------------------------------------------- - -Volá se před každým vykreslením šablony. Metodu lze využít např. pro inicializaci proměnných používaných při vykreslování. - - -getFilters(): array .[method] ------------------------------ - -Volá se před vykreslením šablony. Vrací [filtry|extending-latte#filtry] jako asociativní pole *název filtru => callable*. - -```php -public function getFilters(): array -{ - return [ - 'batch' => [$this, 'batchFilter'], - 'trim' => [$this, 'trimFilter'], - // ... - ]; -} -``` - - -getFunctions(): array .[method] -------------------------------- - -Volá se před vykreslením šablony. Vrací [funkce|extending-latte#funkce] jako asociativní pole *název funkce => callable*. - -```php -public function getFunctions(): array -{ - return [ - 'clamp' => [$this, 'clampFunction'], - 'divisibleBy' => [$this, 'divisibleByFunction'], - // ... - ]; -} -``` - - -getProviders(): array .[method] -------------------------------- - -Volá se před vykreslením šablony. Vrací pole tzv. providers, což jsou zpravidla objekty, které za běhu využívají tagy. Přistupují k nim přes `$this->global->...`. - -```php -public function getProviders(): array -{ - return [ - 'myFoo' => $this->foo, - 'myBar' => $this->bar, - // ... - ]; -} -``` - - -getCacheKey(Latte\Engine $engine): mixed .[method] --------------------------------------------------- - -Volá se před vykreslením šablony. Vrácená hodnota se stane součástí klíče, jehož hash je obsažen v názvu souboru se zkompilovanou šablonou. Tedy pro různé vrácené hodnoty Latte vygeneruje různé soubory v cache. - - -Jak Latte funguje? -================== - -Pro pochopení toho, jak definovat vlastní tagy nebo compiler passes, je nezbytné porozumět, jak funguje Latte pod kapotou. - -Kompilace šablon v Latte probíhá zjednodušeně takto: - -- Nejprve **lexer** tokenizuje zdrojový kód šablony na malé části (tokeny) pro snadnější zpracování. -- Poté **parser** převede proud tokenů na smysluplný strom uzlů (abstraktní syntaktický strom, AST). -- Nakonec překladač **vygeneruje** z AST třídu PHP, která vykresluje šablonu, a uloží ji do cache. - -Ve skutečnosti je kompilace o něco složitější. Latte **má dva** lexery a parsery: jeden pro HTML šablonu a druhý pro PHP-like kód uvnitř tagů. A také parsování neprobíhá až po tokenizaci, ale lexer i parser běží paralelně ve dvou "vláknech" a koordinují se. Je to raketová věda :-) - -Dále své parsovací rutiny mají i všechny tagy. Když parser narazí na tag, zavolá jeho parsovací funkci (vrací je [Extension::getTags()|#getTags]). -Jejich úkolem je naparsovat argumenty značky a v případě párových značek i vnitřní obsah. Vrací *uzel*, který se stane součástí AST. Podrobně v části [#Parsovací funkce tagu]. - -Když parser dokončí práci, máme kompletní AST reprezentující šablonu. Kořenovým uzlem je `Latte\Compiler\Nodes\TemplateNode`. Jednotlivé uzly uvnitř stromu pak reprezentují nejen tagy, ale i HTML elementy, jejich atributy, všechny výrazy použité uvnitř značek atd. - -Poté přicházejí na řadu tzv. [#Průchody kompilátoru], což jsou funkce (vrací je [Extension::getPasses()|#getPasses]), které modifikují AST. - -Celý proces od načtení obsahu šablony přes parsování až po vygenerování výsledného souboru se dá sekvenčně vykonat tímto kódem, se kterým můžete experimentovat a dumpovat si jednotlivé mezikroky: - -```php -$latte = new Latte\Engine; -$source = $latte->getLoader()->getContent($file); -$ast = $latte->parse($source); -$latte->applyPasses($ast); -$code = $latte->generate($ast, $file); -``` - - -Příklad AST ------------ - -Pro lepší představu o podobě AST přidáváme ukázku. Toto je zdrojová šablona: - -```latte -{foreach $category->getItems() as $item} -
                        • {$item->name|upper}
                        • - {else} - no items found -{/foreach} -``` - -A toto její reprezentace v podobě AST: - -/--pre -Latte\Compiler\Nodes\TemplateNode( - Latte\Compiler\Nodes\FragmentNode( - - Latte\Essential\Nodes\ForeachNode( - expression: Latte\Compiler\Nodes\Php\Expression\MethodCallNode( - object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$category') - name: Latte\Compiler\Nodes\Php\IdentifierNode('getItems') - ) - value: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') - content: Latte\Compiler\Nodes\FragmentNode( - - Latte\Compiler\Nodes\TextNode(' ') - - Latte\Compiler\Nodes\Html\ElementNode('li')( - content: Latte\Essential\Nodes\PrintNode( - expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode( - object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') - name: Latte\Compiler\Nodes\Php\IdentifierNode('name') - ) - modifier: Latte\Compiler\Nodes\Php\ModifierNode( - filters: - - Latte\Compiler\Nodes\Php\FilterNode('upper') - ) - ) - ) - ) - else: Latte\Compiler\Nodes\FragmentNode( - - Latte\Compiler\Nodes\TextNode('no items found') - ) - ) - ) -) -\-- - - -Vlastní tagy -============ - -K definování nové značky jsou zapotřebí tři kroky: - -- definování [#parsovací funkce tagu] (zodpovědná za parsování tagu do uzlu) -- vytvoření třídy uzlu (zodpovědné za [#generování PHP kódu] a [#procházení AST]) -- registrace tagu pomocí [Extension::getTags()|#getTags] - - -Parsovací funkce tagu ---------------------- - -Parsování tagů ma na starosti jeho parsovací funkce (ta, kterou vrací [Extension::getTags()|#getTags]). Jejím úkolem je naparsovat a zkontrolovat případné argumenty uvnitř značky (k tomu využívá TagParser). -A dále, pokud je značka párová, požádá TemplateParser o naparsování a vrácení vnitřního obsahu. -Funkce vytvoří a vrátí uzel, který je zpravidla potomkem `Latte\Compiler\Nodes\StatementNode`, a ten se stane součástí AST. - -Pro každý uzel si vytváříme třídu, což uděláme teď hned a parsovací funkci do ní elegantně umístíme jako statickou továrnu. Jako příklad si zkusíme vytvořit známý tag `{foreach}`: - -```php -use Latte\Compiler\Nodes\StatementNode; - -class ForeachNode extends StatementNode -{ - // parsovací funkce, která zatím pouze vytváří uzel - public static function create(Latte\Compiler\Tag $tag): self - { - $node = new self; - return $node; - } - - public function print(Latte\Compiler\PrintContext $context): string - { - // kód doplníme později - } - - public function &getIterator(): \Generator - { - // kód doplníme později - } -} -``` - -Parsovací funkci `create()` se předává objekt [api:Latte\Compiler\Tag], který nese základní informace o tagu (jestli jde o klasický tag nebo n:attribut, na jakém řádku se nachází, apod.) a hlavně zpřístupňuje [api:Latte\Compiler\TagParser] v `$tag->parser`. - -Pokud značka musí mít argumenty, zkontrolujeme jejich existenci zavoláním `$tag->expectArguments()`. Pro jejich parsování jsou k dispozici metody objektu `$tag->parser`: - -- `parseExpression(): ExpressionNode` pro PHP-like výraz (např. `10 + 3`) -- `parseUnquotedStringOrExpression(): ExpressionNode` pro výraz nebo *unquoted-řetězec* -- `parseArguments(): ArrayNode` obsah pole (např. `10, true, foo => bar`) -- `parseModifier(): ModifierNode` pro modifikátor (např `|upper|truncate:10`) -- `parseType(): ExpressionNode` pro typehint (např. `int|string` nebo `Foo\Bar[]`) - -a dále nízkoúrovňový [api:Latte\Compiler\TokenStream] operující přímo s tokeny: - -- `$tag->parser->stream->consume(...): Token` -- `$tag->parser->stream->tryConsume(...): ?Token` - -Latte drobnými způsoby rozšiřuje syntaxi PHP, například o modifikátory, zkrácené ternání operátory, nebo umožňuje jednoduché alfanumerické řetězce psát bez uvozovek. Proto používáme termím *PHP-like* místo PHP. Tudíž metoda `parseExpression()` naparsuje např. `foo` jako `'foo'`. -Vedle toho *unquoted-řetězec* je speciálním případem řetězce, který také nemusí být v uvozovkách, ale zároveň nemusí být ani alfanumerický. Jde třeba o cestu k souboru ve značce `{include ../file.latte}`. K jeho naparsování slouží metoda `parseUnquotedStringOrExpression()`. - -.[note] -Studium tříd uzlů, které jsou součástí Latte, je nejlepší způsob, jak se naučit všechny podrobnosti o procesu parsování. - -Vraťmě se ke značce `{foreach}`. V ní očekáváme argumenty ve tvaru `výraz + 'as' + druhý výraz` a naparsujeme je následujícícm způsobem: - -```php -use Latte\Compiler\Nodes\StatementNode; -use Latte\Compiler\Nodes\Php\ExpressionNode; -use Latte\Compiler\Nodes\AreaNode; - -class ForeachNode extends StatementNode -{ - public ExpressionNode $expression; - public ExpressionNode $value; - - public static function create(Latte\Compiler\Tag $tag): self - { - $tag->expectArguments(); - $node = new self; - $node->expression = $tag->parser->parseExpression(); - $tag->parser->stream->consume('as'); - $node->value = $parser->parseExpression(); - return $node; - } -} -``` - -Výrazy, které jsme zapsali do proměnných `$expression` a `$value`, představují poduzly. - -.[tip] -Proměnné s poduzly definujte jako **public**, aby je bylo možné případně modifikovat v [dalších krocích zpracování |#Průchody kompilátoru]. Zároveň je nutné je **zpřístupnit** pro [procházení |#Procházení AST]. - -U párových značek, jako je ta naše, musí metoda ještě nechat TemplateParser naparsovat její vnitřek. Tohle obstará `yield`, který vrací dvojici ''[vnitří obsah, koncová značka]''. Vnitřní obsah uložíme do proměnné `$node->content`. - -```php -public AreaNode $content; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $endTag] = yield; - return $node; -} -``` - -Klíčové slovo `yield` způsobí, že se metoda `create()` přeruší, řízení se vrátí zpátky k TemplateParser, který pokračuje v parsování obsahu dokud nenarazí na koncovou značku. Poté předá řízení zpět do `create()`, která pokračuje od místa, kde skončila. Užitím `yield` metoda automaticky vrací `Generator`. - -Do `yield` lze také předat pole názvů značek, u kterých chceme parsování zastavit, pokud se vyskytnou dříve než koncová značka. To nám pomůže implemenotovat konstrukci `{foreach}...{else}...{/foreach}`. Pokud se objeví `{else}`, obsah za ní naparsujeme do `$node->elseContent`: - -```php -public AreaNode $content; -public ?AreaNode $elseContent = null; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $nextTag] = yield ['else']; - if ($nextTag?->name === 'else') { - [$node->elseContent] = yield; - } - - return $node; -} -``` - -Vrácením uzlu je parsování tagu dokončeno. - - -Generování PHP kódu -------------------- - -Každý uzel musí implementovat metodu `print()`. Vrací PHP kód vykreslující danou část šablony (runtime kód). Jako parametr se jí předává objekt [api:Latte\Compiler\PrintContext], který má užitečnou metodu `format()` zjednodušující sestavení výsledného kódu. - -Metoda `format(string $mask, ...$args)` akceptuje v masce tyto placeholdery: -- `%node` vypisuje Node -- `%dump` vyexporuje hodnotu do PHP -- `%raw` vloží přímo text bez jakékoliv transformace -- `%args` vypíše ArrayNode jako argumenty volání funkce -- `%line` vypíše komentář s číslem řádku -- `%escape(...)` escapuje obsah -- `%modify(...)` aplikuje modifikátor -- `%modifyContent(...)` aplikuje modifikátor pro bloky - - -Naše funkce `print()` by mohla vypadat takto (pro jednoduchost zanedbáváme `else` větev): - -```php -public function print(Latte\Compiler\PrintContext $context): string -{ - return $context->format( - <<<'XX' - foreach (%node as %node) %line { - %node - } - - XX, - $this->expression, - $this->value, - $this->position, - $this->content, - ); -} -``` - -Proměnnou `$this->position` definuje už třída [api:Latte\Compiler\Node] a nastavuje ji parser. Obsahuje objekt [api:Latte\Compiler\Position] s pozicí tagu ve zdrojovém kódu v podobě čísla řádku a sloupce. - -Runtime kód může využívat pomocné proměnné. Aby nedošlo ke kolizi s proměnnými, které používá samotná šablona, je zvykem je prefixovat znaky `$ʟ__`. - -Může také za běhu využívat libovolné hodnoty, které si do šablony předá v podobě tzv. providerů metodou [Extension::getProviders()|#getProviders]. K nim přistupuje pomocí `$this->global->...`. - - -Procházení AST --------------- - -Aby bylo možné AST strom procházet do hloubky, je nutné implementovat metodu `getIterator()`. Ta zpřístupní poduzly: - -```php -public function &getIterator(): \Generator -{ - yield $this->expression; - yield $this->value; - yield $this->content; - if ($this->elseContent) { - yield $this->elseContent; - } -} -``` - -Všimněte si, že `getIterator()` vrací reference. Právě díky tomu mohou *node visitors* jednotlivé uzly měnit za jiné. - -.[warning] -Pokud má uzel poduzly, je nezbytné tuto metodu implementovat a všechny poduzly zpřístupnit. Jinak by mohla vzniknout bezpečnostní díra. Například režim sandboxu by nebyl schopen kontrolovat poduzly a zajistit, aby v nich nebyly volány nepovolené konstrukce. - -Protože klíčové slovo `yield` musí být přítomno v těle metody i pokud nemá žádné podřízené uzly, zapište ji takto: - -```php -public function &getIterator(): \Generator -{ - if (false) { - yield; - } -} -``` - - -Průchody kompilátoru -==================== - -Průchody kompilátoru jsou funkce, které modifikují AST nebo sbírají v nich informace. Vrací je metoda [Extension::getPasses()|#getPasses]. - - -Node Traverser --------------- - -Nejběžnějším způsobem práce s AST je použití [api:Latte\Compiler\NodeTraverser]: - -```php -use Latte\Compiler\Node; -use Latte\Compiler\NodeTraverser; - -$ast = (new NodeTraverser)->traverse( - $ast, - enter: fn(Node $node) => ..., - leave: fn(Node $node) => ..., -); -``` - -Funkce *enter* (tj. node visitor) je volána při prvním setkání s uzlem, ještě před zpracováním jeho poduzlů. Funkce *leave* je volána po návštěvě všech poduzlů. -Běžným postupem je, že funkce *enter* se používá ke shromáždění některých informací a poté funkce *leave* na jejich základě provede úpravy. V době, kdy je volána funkce *leave*, bude již veškerý kód uvnitř uzlu navštíven a potřebné informace shromážděny. - -Jak AST modifikovat? Nejjednodušším způsobem je jednoduše měnit vlastnosti uzlů. Druhým způsobem je uzel zcela nahradit vrácením uzlu nového. Příklad: následující kód změní všechna celá čísla v AST na řetězce (např. 42 se změní na `'42'`). - -```php -use Latte\Compiler\Nodes\Php; - -$ast = (new NodeTraverser)->traverse( - $ast, - leave: function (Node $node) { - if ($node instanceof Php\Scalar\IntegerNode) { - return new Php\Scalar\StringNode((string) $node->value); - } - }, -); -``` - -Modul AST může snadno obsahovat tisíce uzlů a procházení všech uzlů může být pomalé. V některých případech je možné se úplnému procházení vyhnout. - -Pokud ve stromu hledáte všechny uzly `Html\ElementNode`, pak víte, že jakmile jednou uvidíte uzel `Php\ExpressionNode`, nemá smysl kontrolovat také všechny jeho podřízené uzly, protože HTML nemůže být uvnitř ve výrazech. V takovém případě můžete traverseru přikázat, aby do uzlu třídy neprováděl rekurzi: - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Php\ExpressionNode) { - return NodeTraverser::DontTraverseChildren; - } - // ... - }, -); -``` - -Pokud hledáte pouze jeden konkrétní uzel, je také možné po jeho nalezení procházení zcela přerušit. - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Nodes\ParametersNode) { - return NodeTraverser::StopTraversal; - } - // ... - }, -); -``` - - -Pomocníci pro uzly ------------------- - -Třída [api:Latte\Compiler\NodeHelpers] poskytuje některé metody, které mohou najít uzly AST, které buď splňují určitou podmínku atd. Několik příkladů: - -```php -use Latte\Compiler\NodeHelpers; - -// najde všechny uzly prvků HTML -$elements = NodeHelpers::find($ast, fn(Node $node) => $node instanceof Nodes\Html\ElementNode); - -// najde první textový uzel -$text = NodeHelpers::findFirst($ast, fn(Node $node) => $node instanceof Nodes\TextNode); - -// převede uzel PHP na skutečnou hodnotu -$value = NodeHelpers::toValue($node); - -// převede statický textový uzel na řetězec -$text = NodeHelpers::toText($node); -``` diff --git a/latte/cs/custom-filters.texy b/latte/cs/custom-filters.texy new file mode 100644 index 0000000000..7f7cab0c0c --- /dev/null +++ b/latte/cs/custom-filters.texy @@ -0,0 +1,207 @@ +Vytváření vlastních filtrů +************************** + +.[perex] +Filtry jsou výkonné nástroje pro formátování a úpravu dat přímo v šablonách Latte. Nabízejí čistou syntaxi pomocí symbolu roury (`|`) pro transformaci proměnných nebo výsledků výrazů do požadovaného výstupního formátu. + + +Co jsou filtry? +=============== + +Filtry v Latte jsou v podstatě **PHP funkce navržené speciálně pro transformaci vstupní hodnoty na výstupní hodnotu**. Aplikují se pomocí zápisu s rourou (`|`) uvnitř výrazů šablony (`{...}`). + +**Pohodlnost:** Filtry vám umožňují zapouzdřit běžné úlohy formátování (jako formátování datumů, změna velikosti písmen, zkracování) nebo manipulace s daty do znovupoužitelných jednotek. Místo opakování složitého PHP kódu ve vašich šablonách můžete jednoduše aplikovat filtr: +```latte +{* Místo složitého PHP pro zkrácení: *} +{$article->text|truncate:100} + +{* Místo kódu pro formátování datumu: *} +{$event->startTime|date:'Y-m-d H:i'} + +{* Aplikace více transformací: *} +{$product->name|lower|capitalize} +``` + +**Čitelnost:** Používání filtrů činí šablony přehlednějšími a více zaměřenými na prezentaci, protože transformační logika je přesunuta do definice filtru. + +**Kontextová citlivost:** Klíčovou předností filtrů v Latte je jejich schopnost být [kontextově citlivé |#Kontextové filtry]. To znamená, že filtr může rozpoznat typ obsahu, se kterým pracuje (HTML, JavaScript, prostý text atd.), a aplikovat odpovídající logiku nebo escapování, což je zásadní pro bezpečnost a správnost, zejména při generování HTML. + +**Integrace s aplikační logikou:** Stejně jako vlastní funkce může být PHP callable za filtrem uzávěr (closure), statická metoda nebo metoda instance. To umožňuje filtrům přistupovat k aplikačním službám nebo datům, pokud je to potřeba, i když jejich hlavním účelem zůstává *transformace vstupní hodnoty*. + +Latte ve výchozím nastavení poskytuje bohatou sadu [standardních filtrů |filters]. Vlastní filtry vám umožňují rozšířit tuto sadu o formátování a transformace specifické pro váš projekt. + +Pokud potřebujete provádět logiku založenou na *více* vstupech nebo nemáte primární hodnotu k transformaci, je pravděpodobně vhodnější použít [vlastní funkci |custom-functions]. Pokud potřebujete generovat složitý markup nebo řídit tok šablony, zvažte [vlastní tag |custom-tags]. + + +Vytváření a registrace filtrů +============================= + +Existuje několik způsobů, jak definovat a registrovat vlastní filtry v Latte. + + +Přímá registrace pomocí `addFilter()` +------------------------------------- + +Nejjednodušší způsob, jak přidat filtr, je použití metody `addFilter()` přímo na objektu `Latte\Engine`. Zadáte název filtru (jak bude použit v šabloně) a odpovídající PHP callable. + +```php +$latte = new Latte\Engine; + +// Jednoduchý filtr bez argumentů +$latte->addFilter('initial', fn(string $s): string => mb_substr($s, 0, 1) . '.'); + +// Filtr s volitelným argumentem +$latte->addFilter('shortify', function (string $s, int $len = 10): string { + return mb_substr($s, 0, $len); +}); + +// Filtr zpracovávající pole +$latte->addFilter('sum', fn(array $numbers): int|float => array_sum($numbers)); +``` + +**Použití v šabloně:** + +```latte +{$name|initial} {* Vypíše 'J.' pokud je $name 'John' *} +{$description|shortify} {* Použije výchozí délku 10 *} +{$description|shortify:50} {* Použije délku 50 *} +{$prices|sum} {* Vypíše součet položek v poli $prices *} +``` + +**Předávání argumentů:** + +Hodnota nalevo od roury (`|`) je vždy předána jako *první* argument funkci filtru. Jakékoliv parametry uvedené za dvojtečkou (`:`) v šabloně jsou předány jako následující argumenty. + +```latte +{$text|shortify:30} +// Volá PHP funkci shortify($text, 30) +``` + + +Registrace pomocí rozšíření +--------------------------- + +Pro lepší organizaci, zejména při vytváření znovupoužitelných sad filtrů nebo jejich sdílení jako balíčky, je doporučeným způsobem registrovat je v rámci [rozšíření Latte |extending-latte#Latte Extension]: + +```php +namespace App\Latte; + +use Latte\Extension; + +class MyLatteExtension extends Extension +{ + public function getFilters(): array + { + return [ + 'initial' => $this->initial(...), + 'shortify' => $this->shortify(...), + ]; + } + + public function initial(string $s): string + { + return mb_substr($s, 0, 1) . '.'; + } + + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// Registrace +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +Tento přístup udrží logiku vašeho filtru zapouzdřenou a registraci jednoduchou. + + +Filtry používající třídu s atributy +----------------------------------- + +Další elegantní způsob, jak definovat filtry, je použití metod ve vaší [třídě parametrů šablony |develop#Parametry jako třída]. Stačí přidat atribut `#[Latte\Attributes\TemplateFilter]` k metodě. + +```php +use Latte\Attributes\TemplateFilter; + +class TemplateParameters +{ + public function __construct( + public string $description, + // další parametry... + ) {} + + #[TemplateFilter] + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// Předání objektu do šablony +$params = new TemplateParameters(description: '...'); +$latte->render('template.latte', $params); +``` + +Latte automaticky rozpozná a zaregistruje metody označené tímto atributem, když je objekt `TemplateParameters` předán do šablony. Název filtru v šabloně bude stejný jako název metody (`shortify` v tomto případě). + +```latte +{* Použití filtru definovaného ve třídě parametrů *} +{$description|shortify:50} +``` + + +Kontextové filtry +================= + +Někdy filtr potřebuje více informací než jen vstupní hodnotu. Může potřebovat znát **typ obsahu** řetězce, se kterým pracuje (např. HTML, JavaScript, prostý text) nebo ho dokonce upravit. To je situace pro kontextové filtry. + +Kontextový filtr je definován stejně jako běžný filtr, ale jeho **první parametr musí být** typově označen jako `Latte\Runtime\FilterInfo`. Latte automaticky rozpozná tento podpis a při volání filtru předá objekt `FilterInfo`. Následující parametry obdrží argumenty filtru jako obvykle. + +```php +use Latte\Runtime\FilterInfo; +use Latte\ContentType; + +$latte->addFilter('money', function (FilterInfo $info, float $amount): string { + // 1. Zkontrolujte vstupní typ obsahu (volitelné, ale doporučené) + // Povolte null (proměnný vstup) nebo prostý text. Odmítněte pokud je aplikován na HTML atd. + if (!in_array($info->contentType, [null, ContentType::Text], true)) { + $actualType = $info->contentType ?? 'mixed'; + throw new \RuntimeException( + "Filter |money used in incompatible content type $actualType. Expected text or null." + ); + } + + // 2. Proveďte transformaci + $formatted = number_format($amount, 2, '.', ',') . ' EUR'; + $htmlOutput = '' . htmlspecialchars($formatted) . ''; // Zajistěte správné escapování! + + // 3. Deklarujte výstupní typ obsahu + $info->contentType = ContentType::Html; + + // 4. Vraťte výsledek + return $htmlOutput; +}); +``` + +`$info->contentType` je řetězcová konstanta z `Latte\ContentType` (např. `ContentType::Html`, `ContentType::Text`, `ContentType::JavaScript`, atd.) nebo `null`, pokud je filtr aplikován na proměnnou (`{$var|filter}`). Můžete tuto hodnotu **číst**, abyste zkontrolovali vstupní kontext, a **zapisovat** do ní, abyste deklarovali typ výstupního kontextu. + +Nastavením typu obsahu na HTML sdělujete Latte, že řetězec vrácený vaším filtrem je bezpečné HTML. Latte pak na tento výsledek **nebude** aplikovat své výchozí automatické escapování. To je zásadní, pokud váš filtr generuje HTML markup. + +.[warning] +Pokud váš filtr generuje HTML, **jste zodpovědní za správné escapování jakýchkoliv vstupních dat** použitých v tomto HTML (jako v případě volání `htmlspecialchars($formatted)` výše). Opomenutí může vytvořit XSS zranitelnosti. Pokud váš filtr vrací pouze prostý text, nemusíte nastavovat `$info->contentType`. + + +Filtry na blocích +----------------- + +Všechny filtry aplikované na [bloky |tags#block] *musí* být kontextové. Je to proto, že obsah bloku má definovaný typ obsahu (obvykle HTML), kterého si filtr musí být vědom. + +```latte +{block heading|money}1000{/block} +{* Filtr 'money' obdrží '1000' jako druhý argument + a $info->contentType bude ContentType::Html *} +``` + +Kontextové filtry poskytují silnou kontrolu nad tím, jak jsou data zpracovávána na základě jejich kontextu, umožňují pokročilé funkce a zajišťují správné chování escapování, zejména při generování HTML obsahu. diff --git a/latte/cs/custom-functions.texy b/latte/cs/custom-functions.texy new file mode 100644 index 0000000000..8a1e70f188 --- /dev/null +++ b/latte/cs/custom-functions.texy @@ -0,0 +1,144 @@ +Vytváření vlastních funkcí +************************** + +.[perex] +Snadno přidejte do šablon Latte vlastní pomocné funkce. Volejte PHP logiku přímo ve výrazech pro výpočty, přístup ke službám nebo generování dynamického obsahu, což udrží vaše šablony čisté a výkonné. + + +Co jsou funkce? +=============== + +Funkce v Latte vám umožňují rozšířit sadu funkcí, které lze volat v rámci výrazů v šablonách (`{...}`). Můžete si je představit jako **vlastní PHP funkce dostupné pouze uvnitř vašich Latte šablon**. To přináší několik výhod: + +**Pohodlí:** Můžete definovat pomocnou logiku (jako výpočty, formátování nebo přístup k datům aplikace) a volat ji pomocí jednoduché, známé syntaxe funkcí přímo v šabloně, stejně jako byste volali `strlen()` nebo `date()` v PHP. + +```latte +{var $userInitials = initials($userName)} {* např. 'J. D.' *} + +{if hasPermission('article', 'edit')} + Edit +{/if} +``` + +**Bez znečištění globálního prostoru:** Na rozdíl od definování skutečné globální funkce v PHP existují funkce Latte pouze v kontextu vykreslování šablony. Nemusíte zatěžovat globální jmenný prostor PHP pomocníky, které jsou specifické jen pro šablony. + +**Integrace s logikou aplikace:** PHP volatelný objekt stojící za funkcí Latte může být cokoli – anonymní funkce, statická metoda nebo instanční metoda. To znamená, že vaše funkce v šablonách mohou snadno přistupovat ke službám aplikace, databázím, konfiguraci nebo jakékoli jiné potřebné logice zachycením proměnných (v případě anonymních funkcí) nebo pomocí dependency injection (v případě objektů). Výše uvedený příklad `hasPermission` to jasně demonstruje, když pravděpodobně volá na pozadí autorizační službu. + +**Přepsání nativních funkcí (volitelně):** Můžete dokonce definovat funkci Latte se stejným názvem jako nativní PHP funkce. V šabloně bude místo původní funkce volána vaše vlastní verze. To může být užitečné pro poskytnutí chování specifického pro šablonu nebo zajištění konzistentního zpracování (např. zajištění, že `strlen` bude vždy vícebytově bezpečná). Tuto funkci používejte opatrně, abyste předešli nedorozuměním. + +Ve výchozím nastavení Latte umožňuje volání *všech* nativních PHP funkcí (pokud nejsou omezeny [Sandboxem |sandbox]). Vlastní funkce rozšiřují tuto vestavěnou knihovnu o specifické potřeby vašeho projektu. + +Pokud pouze transformujete jedinou hodnotu, může být vhodnější použít [vlastní filtr |custom-filters]. + + +Vytváření a registrace funkcí +============================= + +Podobně jako u filtrů existuje několik způsobů, jak definovat a registrovat vlastní funkce. + + +Přímá registrace pomocí `addFunction()` +--------------------------------------- + +Nejjednodušší metodou je použití `addFunction()` na objektu `Latte\Engine`. Zadáte název funkce (jak se bude zobrazovat v šabloně) a odpovídající PHP volatelný objekt. + +```php +$latte = new Latte\Engine; + +// Jednoduchá pomocná funkce +$latte->addFunction('initials', function (string $name): string { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; +}); +``` + +**Použití v šabloně:** + +```latte +{var $userInitials = initials($userName)} +``` + +Argumenty funkce v šabloně jsou předávány přímo PHP volatelnému objektu ve stejném pořadí. PHP funkcionality jako typové nápovědy, výchozí hodnoty a variabilní parametry (`...`) fungují podle očekávání. + + +Registrace pomocí rozšíření +--------------------------- + +Pro lepší organizaci a znovupoužitelnost registrujte funkce v rámci [Latte rozšíření |extending-latte#Latte Extension]. Tento přístup je doporučen pro složitější aplikace nebo sdílené knihovny. + +```php +namespace App\Latte; + +use Latte\Extension; +use Nette\Security\Authorizator; + +class MyLatteExtension extends Extension +{ + public function __construct( + // Předpokládáme, že služba Authorizator existuje + private Authorizator $authorizator, + ) { + } + + public function getFunctions(): array + { + // Registrace metod jako Latte funkcí + return [ + 'hasPermission' => $this->hasPermission(...), + ]; + } + + public function hasPermission(string $resource, string $action): bool + { + return $this->authorizator->isAllowed($resource, $action); + } +} + +// Registrace (předpokládáme, že $container obsahuje DIC) +$extension = $container->getByType(App\Latte\MyLatteExtension::class); +$latte = new Latte\Engine; +$latte->addExtension($extension); +``` + +Tento přístup názorně ukazuje, jak mohou být funkce definované v Latte podpořeny metodami objektů, které mohou mít své vlastní závislosti spravované kontejnerem pro dependency injection vaší aplikace nebo továrnou. To udržuje logiku vašich šablon propojenou s jádrem aplikace a zároveň zachovává přehlednou organizaci. + + +Funkce používající třídu s atributy +----------------------------------- + +Stejně jako filtry, funkce mohou být definovány jako metody ve vaší [třídě parametrů šablony |develop#Parametry jako třída] pomocí atributu `#[Latte\Attributes\TemplateFunction]`. + +```php +use Latte\Attributes\TemplateFunction; + +class TemplateParameters +{ + public function __construct( + public string $userName, + // další parametry... + ) {} + + // Tato metoda bude dostupná jako {initials(...)} v šabloně + #[TemplateFunction] + public function initials(string $name): string + { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; + } +} + +// Předání objektu do šablony +$params = new TemplateParameters(userName: 'John Doe', /* ... */); +$latte->render('template.latte', $params); +``` + +Latte automaticky objeví a zaregistruje metody označené tímto atributem, když je objekt parametrů předán do šablony. Název funkce v šabloně odpovídá názvu metody. + +```latte +{* Použití funkce definované ve třídě parametrů *} +{var $inits = initials($userName)} +``` + +**Kontextové funkce?** + +Na rozdíl od filtrů neexistuje přímý koncept "kontextových funkcí", které by obdržely objekt podobný `FilterInfo`. Funkce pracují v rámci výrazů a typicky nepotřebují přímý přístup k kontextu vykreslování nebo informacím o typu obsahu stejným způsobem jako filtry aplikované na bloky. diff --git a/latte/cs/custom-tags.texy b/latte/cs/custom-tags.texy new file mode 100644 index 0000000000..7d41e7799d --- /dev/null +++ b/latte/cs/custom-tags.texy @@ -0,0 +1,1135 @@ +Vytváření vlastních tagů +************************ + +.[perex] +Tato stránka poskytuje komplexní návod pro vytváření vlastních tagů v Latte. Probereme vše od jednoduchých tagů až po složitější scénáře s vnořeným obsahem a specifickými potřebami parsování, přičemž budeme stavět na vašem pochopení toho, jak Latte kompiluje šablony. + +Vlastní tagy poskytují nejvyšší úroveň kontroly nad syntaxí šablony a logikou vykreslování, ale jsou také nejsložitějším bodem rozšíření. Než se rozhodnete vytvořit vlastní tag, vždy zvažte, zda [neexistuje jednodušší řešení |extending-latte#Způsoby rozšíření Latte] nebo zda již vhodný tag neexistuje ve [standardní sadě |tags]. Vlastní tagy používejte pouze tehdy, když pro vaše potřeby nejsou jednodušší alternativy dostatečné. + + +Pochopení procesu kompilace +=========================== + +Pro efektivní vytváření vlastních tagů je užitečné vysvětlit, jak Latte zpracovává šablony. Pochopení tohoto procesu objasňuje, proč jsou tagy strukturovány právě takto a jak zapadají do širšího kontextu. + +Kompilace šablony v Latte, zjednodušeně, zahrnuje tyto klíčové kroky: + +1. **Lexikální analýza:** Lexer čte zdrojový kód šablony (soubor `.latte`) a rozděluje ho na posloupnost malých, odlišných částí zvaných **tokeny** (např. `{`, `foreach`, `$variable`, `}`, HTML text, atd.). +2. **Parsování:** Parser bere tento proud tokenů a konstruuje z něj smysluplnou stromovou strukturu reprezentující logiku a obsah šablony. Tento strom se nazývá **abstraktní syntaktický strom (AST)**. +3. **Kompilační průchody:** Před generováním PHP kódu Latte spouští [kompilační průchody |compiler passes]. Jsou to funkce, které procházejí celý AST a mohou jej upravovat nebo sbírat informace. Tento krok je klíčový pro funkce jako zabezpečení ([Sandbox |sandbox]) nebo optimalizace. +4. **Generování kódu:** Nakonec kompilátor prochází (potenciálně upravený) AST a generuje odpovídající kód PHP třídy. Tento PHP kód je to, co skutečně vykresluje šablonu při spuštění. +5. **Caching:** Vygenerovaný PHP kód je uložen na disk, což činí následná vykreslení velmi rychlými, protože kroky 1-4 jsou přeskočeny. + +Ve skutečnosti je kompilace o něco složitější. Latte **má dva** lexery a parsery: jeden pro HTML šablonu a druhý pro PHP-like kód uvnitř tagů. A také parsování neprobíhá až po tokenizaci, ale lexer i parser běží paralelně ve dvou "vláknech" a koordinují se. Věřte mi, naprogramovat to byla raketová věda :-) + +Celý proces, od načtení obsahu šablony, přes parsování, až po generování výsledného souboru, lze sekvencovat tímto kódem, se kterým můžete experimentovat a vypisovat mezivýsledky: + +```php +$latte = new Latte\Engine; +$source = $latte->getLoader()->getContent($file); +$ast = $latte->parse($source); +$latte->applyPasses($ast); +$code = $latte->generate($ast, $file); +``` + + +Anatomie tagu +============= + +Vytvoření plně funkčního vlastního tagu v Latte zahrnuje několik propojených částí. Než se pustíme do implementace, pojďme pochopit základní koncepty a terminologii, s využitím analogie k HTML a Document Object Model (DOM). + + +Tagy vs. Uzly (Analogie s HTML) +------------------------------- + +V HTML píšeme **tagy** jako `

                          ` nebo `

                          ...
                          `. Tyto tagy jsou syntaxí ve zdrojovém kódu. Když prohlížeč parsuje toto HTML, vytváří paměťovou reprezentaci nazvanou **Document Object Model (DOM)**. V DOM jsou HTML tagy reprezentovány **uzly** (konkrétně uzly `Element` v terminologii JavaScriptového DOM). S těmito *uzly* programově pracujeme (např. pomocí JavaScriptového `document.getElementById(...)` se vrací uzel Element). Tag je pouze textová reprezentace ve zdrojovém souboru; uzel je objektová reprezentace v logickém stromu. + +Latte funguje podobně: + +- V souboru `.latte` šablony píšete **Latte tagy**, jako `{foreach ...}` a `{/foreach}`. Toto je syntaxe, se kterou vy jako autor šablony pracujete. +- Když Latte **parsuje** šablonu, buduje **Abstract Syntax Tree (AST)**. Tento strom je složen z **uzlů**. Každý Latte tag, HTML element, kus textu nebo výraz v šabloně se stává jedním nebo více uzly v tomto stromu. +- Základní třída pro všechny uzly v AST je `Latte\Compiler\Node`. Stejně jako DOM má různé typy uzlů (Element, Text, Comment), AST Latte má různé typy uzlů. Setkáte se s `Latte\Compiler\Nodes\TextNode` pro statický text, `Latte\Compiler\Nodes\Html\ElementNode` pro HTML elementy, `Latte\Compiler\Nodes\Php\ExpressionNode` pro výrazy uvnitř tagů a klíčově pro vlastní tagy, uzly dědící z `Latte\Compiler\Nodes\StatementNode`. + + +Proč `StatementNode`? +--------------------- + +HTML elementy (`Html\ElementNode`) primárně reprezentují strukturu a obsah. PHP výrazy (`Php\ExpressionNode`) reprezentují hodnoty nebo výpočty. Ale co Latte tagy jako `{if}`, `{foreach}` nebo náš vlastní `{datetime}`? Tyto tagy *provádějí akce*, řídí tok programu nebo generují výstup na základě logiky. Jsou to funkční jednotky, které dělají z Latte mocný šablonovací *engine*, nikoli jen značkovací jazyk. + +V programování se takovéto jednotky provádějící akce často nazývají "statements" (příkazy). Proto uzly reprezentující tyto funkční Latte tagy typicky dědí z `Latte\Compiler\Nodes\StatementNode`. To je odlišuje od čistě strukturálních uzlů (jako HTML elementy) nebo uzlů reprezentujících hodnoty (jako výrazy). + + +Klíčové komponenty +================== + +Projděme si hlavní komponenty potřebné k vytvoření vlastního tagu: + + +Funkce pro parsování tagu +------------------------- + +- Tato PHP callable funkce parsuje syntaxi Latte tagu (`{...}`) ve zdrojové šabloně. +- Dostává informace o tagu (jako jeho název, pozici a zda jde o n:atribut) prostřednictvím objektu [api:Latte\Compiler\Tag]. +- Jejím primárním nástrojem pro parsování argumentů a výrazů uvnitř oddělovačů tagu je objekt [api:Latte\Compiler\TagParser], přístupný přes `$tag->parser` (toto je jiný parser než ten, který parsuje celou šablonu). +- Pro párové tagy používá `yield` k signalizaci Latte, aby parsovalo vnitřní obsah mezi počátečním a koncovým tagem. +- Konečným cílem parsovací funkce je vytvořit a vrátit instanci **třídy uzlu**, která je přidána do AST. +- Je zvykem (i když to není vyžadováno) implementovat parsovací funkci jako statickou metodu (často nazvanou `create`) přímo v odpovídající třídě uzlu. To udržuje parsovací logiku a reprezentaci uzlu úhledně v jednom balíčku, umožňuje přístup k privátním/chráněným prvkům třídy, je-li třeba, a zlepšuje organizaci. + + +Třída uzlu +---------- + +- Reprezentuje *logickou funkci* vašeho tagu v **Abstract Syntax Tree (AST)**. +- Obsahuje parsované informace (jako argumenty nebo obsah) jako veřejné vlastnosti. Tyto vlastnosti často obsahují jiné instance `Node` (např. `ExpressionNode` pro parsované argumenty, `AreaNode` pro parsovaný obsah). +- Metoda `print(PrintContext $context): string` generuje *PHP kód* (příkaz nebo sérii příkazů), který provádí akci tagu během vykreslování šablony. +- Metoda `getIterator(): \Generator` zpřístupňuje dětské uzly (argumenty, obsah) pro průchod **kompilačními průchody**. Musí poskytovat reference (`&`), aby umožnila průchodům potenciálně modifikovat nebo nahrazovat poduzly. +- Poté, co je celá šablona zparsována do AST, Latte spouští řadu [kompilačních průchodů |compiler-passes]. Tyto průchody procházejí *celý* AST pomocí metody `getIterator()` poskytované každým uzlem. Mohou uzly kontrolovat, sbírat informace a dokonce *upravovat* strom (např. změnou veřejných vlastností uzlů nebo úplným nahrazením uzlů). Tento design, vyžadující komplexní `getIterator()`, je zásadní. Umožňuje mocným funkcím jako [Sandbox |sandbox] analyzovat a potenciálně měnit chování *jakékoli* části šablony, včetně vašich vlastních tagů, zajišťujíc bezpečnost a konzistenci. + + +Registrace přes rozšíření +------------------------- + +- Potřebujete informovat Latte o vašem novém tagu a která parsovací funkce má být pro něj použita. To se děje v rámci [Latte rozšíření |extending-latte#Latte Extension]. +- Uvnitř vaší třídy rozšíření implementujete metodu `getTags(): array`. Tato metoda vrací asociativní pole, kde klíče jsou názvy tagů (např. `'mytag'`, `'n:myattribute'`) a hodnoty jsou PHP callable funkce reprezentující jejich příslušné parsovací funkce (např. `MyNamespace\DatetimeNode::create(...)`). + +Shrnutí: **Funkce parsování tagu** přeměňuje *zdrojový kód šablony* vašeho tagu na **uzel AST**. **Třída uzlu** pak umí přeměnit *sama sebe* na spustitelný *PHP kód* pro kompilovanou šablonu a zpřístupňuje své poduzly pro **kompilační průchody** přes `getIterator()`. **Registrace přes rozšíření** propojuje název tagu s parsovací funkcí a dává o něm vědět Latte. + +Nyní prozkoumáme, jak implementovat tyto komponenty krok za krokem. + + +Vytvoření jednoduchého tagu +=========================== + +Pojďme se pustit do vytvoření vašeho prvního vlastního Latte tagu. Začneme s velmi jednoduchým příkladem: tag s názvem `{datetime}`, který vypisuje aktuální datum a čas. **Zpočátku tento tag nebude přijímat žádné argumenty**, ale vylepšíme ho později v sekci [#Parsování argumentů tagu]. Nemá také žádný vnitřní obsah. + +Tento příklad vás provede základními kroky: definování třídy uzlu, implementace jejích metod `print()` a `getIterator()`, vytvoření parsovací funkce a nakonec registrace tagu. + +**Cíl:** Implementovat `{datetime}` pro výstup aktuálního data a času pomocí PHP funkce `date()`. + + +Vytvoření třídy uzlu +-------------------- + +Nejprve potřebujeme třídu, která bude reprezentovat náš tag v Abstract Syntax Tree (AST). Jak bylo diskutováno výše, dědíme z `Latte\Compiler\Nodes\StatementNode`. + +Vytvořte soubor (např. `DatetimeNode.php`) a definujte třídu: + +```php +node = new self; + return $node; + } + + /** + * Generuje PHP kód, který bude spuštěn při vykreslování šablony. + */ + public function print(PrintContext $context): string + { + return $context->format( + 'echo date(\'Y-m-d H:i:s\') %line;', + $this->position, + ); + } + + /** + * Poskytuje přístup k dětským uzlům pro kompilační průchody Latte. + */ + public function &getIterator(): \Generator + { + false && yield; + } +} +``` + +Když Latte narazí na `{datetime}` v šabloně, zavolá parsovací funkci `create()`. Jejím úkolem je vrátit instanci `DatetimeNode`. + +Metoda `print()` generuje PHP kód, který bude spuštěn při vykreslování šablony. Voláme metodu `$context->format()`, která sestavuje výsledný řetězec PHP kódu pro kompilovanou šablonu. První argument, `'echo date('Y-m-d H:i:s') %line;'`, je maska, do které jsou doplněny následující parametry. Zástupný symbol `%line` říká metodě `format()`, aby použila druhý argument, kterým je `$this->position`, a vložila komentář jako `/* line 15 */`, který propojuje vygenerovaný PHP kód zpět na původní řádek šablony, což je klíčové pro ladění. + +Vlastnost `$this->position` je zděděna ze základní třídy `Node` a je automaticky nastavena parserem Latte. Obsahuje objekt [api:Latte\Compiler\Position], který indikuje, kde byl tag nalezen ve zdrojovém souboru `.latte`. + +Metoda `getIterator()` je zásadní pro kompilační průchody. Musí poskytovat všechny dětské uzly, ale náš jednoduchý `DatetimeNode` aktuálně nemá žádné argumenty ani obsah, tedy žádné dětské uzly. Nicméně metoda musí stále existovat a být generátorem, tj. klíčové slovo `yield` musí být nějakým způsobem přítomno v těle metody. + + +Registrace přes rozšíření +------------------------- + +Nakonec informujme Latte o novém tagu. Vytvořte [třídu rozšíření |extending-latte#Latte Extension] (např. `MyLatteExtension.php`) a zaregistrujte tag v její metodě `getTags()`. + +```php + Mapa: 'nazev-tagu' => parsovaci-funkce + */ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + // Později zde zaregistrujte více tagů + ]; + } +} +``` + +Poté zaregistrujte toto rozšíření v Latte Engine: + +```php +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +Vytvořte šablonu: + +```latte +

                          Stránka vygenerována: {datetime}

                          +``` + +Očekávaný výstup: `

                          Stránka vygenerována: 2023-10-27 11:00:00

                          ` + + +Shrnutí této fáze +----------------- + +Úspěšně jsme vytvořili základní vlastní tag `{datetime}`. Definovali jsme jeho reprezentaci v AST (`DatetimeNode`), zpracovali jeho parsování (`create()`), specifikovali, jak by měl generovat PHP kód (`print()`), zajistili, že jeho děti jsou přístupné pro průchod (`getIterator()`), a zaregistrovali ho v Latte. + +V další sekci vylepšíme tento tag tak, aby přijímal argumenty, a ukážeme, jak parsovat výrazy a spravovat dětské uzly. + + +Parsování argumentů tagu +======================== + +Náš jednoduchý tag `{datetime}` funguje, ale není příliš flexibilní. Vylepšeme ho, aby přijímal volitelný argument: formátovací řetězec pro funkci `date()`. Požadovaná syntaxe bude `{datetime $format}`. + +**Cíl:** Upravit `{datetime}` tak, aby přijímal volitelný PHP výraz jako argument, který bude použit jako formátovací řetězec pro `date()`. + + +Představení `TagParser` +----------------------- + +Než upravíme kód, je důležité pochopit nástroj, který budeme používat [api:Latte\Compiler\TagParser]. Když hlavní parser Latte (`TemplateParser`) narazí na Latte tag jako `{datetime ...}` nebo n:atribut, deleguje parsování obsahu *uvnitř* tagu (část mezi `{` a `}` nebo hodnota atributu) na specializovaný `TagParser`. + +Tento `TagParser` pracuje výhradně s **argumenty tagu**. Jeho úkolem je zpracovávat tokeny reprezentující tyto argumenty. Klíčové je, že **musí zpracovat celý obsah**, který je mu poskytnut. Pokud vaše parsovací funkce skončí, ale `TagParser` nedosáhl konce argumentů (kontrolováno přes `$tag->parser->isEnd()`), Latte vyhodí výjimku, protože to indikuje, že uvnitř tagu zbyly neočekávané tokeny. Naopak, pokud tag *vyžaduje* argumenty, měli byste na začátku vaší parsovací funkce zavolat `$tag->expectArguments()`. Tato metoda kontroluje, zda jsou argumenty přítomny, a vyhodí nápomocnou výjimku, pokud byl tag použit bez jakýchkoliv argumentů. + +`TagParser` nabízí užitečné metody pro parsování různých druhů argumentů: + +- `parseExpression(): ExpressionNode`: Parsuje PHP-podobný výraz (proměnné, literály, operátory, volání funkcí/metod, atd.). Zpracovává syntaktický cukr Latte, jako je například zacházení s jednoduchými alfanumerickými řetězci jako s řetězci v uvozovkách (např. `foo` je parsováno, jako by to bylo `'foo'`). +- `parseUnquotedStringOrExpression(): ExpressionNode`: Parsuje buď standardní výraz, nebo *neuvozený řetězec*. Neuvozené řetězce jsou sekvence povolené Latte bez uvozovek, často používané pro věci jako cesty k souborům (např. `{include ../file.latte}`). Pokud parsuje neuvozený řetězec, vrátí `StringNode`. +- `parseArguments(): ArrayNode`: Parsuje argumenty oddělené čárkami, potenciálně s klíči, jako `10, name: 'John', true`. +- `parseModifier(): ModifierNode`: Parsuje filtry jako `|upper|truncate:10`. +- `parseType(): ?SuperiorTypeNode`: Parsuje PHP typové nápovědy jako `int`, `?string`, `array|Foo`. + +Pro složitější nebo nižší úrovně parsovacích potřeb můžete přímo interagovat s [tokovým proudem |api:Latte\Compiler\TokenStream] přes `$tag->parser->stream`. Tento objekt poskytuje metody pro kontrolu a zpracování jednotlivých tokenů: + +- `$tag->parser->stream->is(...): bool`: Kontroluje, zda *aktuální* token odpovídá některému ze specifikovaných typů (např. `Token::Php_Variable`) nebo literálních hodnot (např. `'as'`) bez jeho konzumace. Užitečné pro pohled dopředu. +- `$tag->parser->stream->consume(...): Token`: Konzumuje *aktuální* token a posouvá pozici proudu vpřed. Pokud jsou poskytnuty očekávané typy/hodnoty tokenů jako argumenty a aktuální token neodpovídá, vyhodí `CompileException`. Použijte toto, když *očekáváte* určitý token. +- `$tag->parser->stream->tryConsume(...): ?Token`: Pokusí se konzumovat *aktuální* token *pouze pokud* odpovídá jednomu ze specifikovaných typů/hodnot. Pokud odpovídá, konzumuje token a vrací jej. Pokud neodpovídá, nechává pozici proudu nezměněnou a vrací `null`. Použijte toto pro volitelné tokeny nebo když volíte mezi různými syntaktickými cestami. + + +Aktualizace parsovací funkce `create()` +--------------------------------------- + +S tímto pochopením upravme metodu `create()` v `DatetimeNode` tak, aby parsovala volitelný formátovací argument pomocí `$tag->parser`. + +```php +node = new self; + + // Zkontrolujeme, zda existují nějaké tokeny + if (!$tag->parser->isEnd()) { + // Parsujeme argument jako PHP-podobný výraz pomocí TagParser. + $node->format = $tag->parser->parseExpression(); + } + + return $node; + } + + // ... metody print() a getIterator() budou aktualizovány dále ... +} +``` + +Přidali jsme veřejnou vlastnost `$format`. V `create()` nyní používáme `$tag->parser->isEnd()` ke kontrole, zda *existují* argumenty. Pokud ano, `$tag->parser->parseExpression()` zpracovává tokeny pro výraz. Protože `TagParser` musí zpracovat všechny vstupní tokeny, Latte automaticky vyhodí chybu, pokud uživatel napíše něco neočekávaného po výrazu formátu (např. `{datetime 'Y-m-d', unexpected}`). + + +Aktualizace metody `print()` +---------------------------- + +Nyní upravme metodu `print()` tak, aby používala parsovaný výraz formátu uložený v `$this->format`. Pokud nebyl poskytnut žádný formát (`$this->format` je `null`), měli bychom použít výchozí formátovací řetězec, například `'Y-m-d H:i:s'`. + +```php + public function print(PrintContext $context): string + { + $formatNode = $this->format ?? new StringNode('Y-m-d H:i:s'); + + // %node vytiskne PHP kódovou reprezentaci $formatNode. + return $context->format( + 'echo date(%node) %line;', + $formatNode, + $this->position + ); + } +``` + +Do proměnné `$formatNode` ukládáme uzel AST reprezentující formátovací řetězec pro PHP funkci `date()`. Používáme zde operátor nulového sloučení (`??`). Pokud uživatel poskytl argument v šabloně (např. `{datetime 'd.m.Y'}`), pak vlastnost `$this->format` obsahuje odpovídající uzel (v tomto případě `StringNode` s hodnotou `'d.m.Y'`), a tento uzel je použit. Pokud uživatel neposkytl argument (napsal jen `{datetime}`), vlastnost `$this->format` je `null`, a místo toho vytvoříme nový `StringNode` s výchozím formátem `'Y-m-d H:i:s'`. To zajišťuje, že `$formatNode` vždy obsahuje platný uzel AST pro formát. + +V masce `'echo date(%node) %line;'` je použit nový zástupný symbol `%node`, který říká metodě `format()`, aby vzala první následující argument (což je náš `$formatNode`), zavolala jeho metodu `print()` (která vrátí jeho PHP kódovou reprezentaci) a vložila výsledek na pozici zástupného symbolu. + + +Implementace `getIterator()` pro poduzly +---------------------------------------- + +Náš `DatetimeNode` nyní má dětský uzel: výraz `$format`. **Musíme** tento dětský uzel zpřístupnit kompilačním průchodům poskytnutím v metodě `getIterator()`. Nezapomeňte poskytnout *referenci* (`&`), abyste umožnili průchodům potenciálně nahradit uzel. + +```php + public function &getIterator(): \Generator + { + if ($this->format) { + yield $this->format; + } + } +``` + +Proč je to zásadní? Představte si průchod Sandbox, který potřebuje zkontrolovat, zda argument `$format` neobsahuje zakázané volání funkce (např. `{datetime dangerousFunction()}`). Pokud `getIterator()` neposkytne `$this->format`, průchod Sandbox by nikdy neuviděl volání `dangerousFunction()` uvnitř argumentu našeho tagu, což by vytvořilo potenciální bezpečnostní díru. Poskytnutím mu umožňujeme Sandboxu (a dalším průchodům) kontrolovat a potenciálně modifikovat uzel výrazu `$format`. + + +Použití vylepšeného tagu +------------------------ + +Tag nyní správně zpracovává volitelný argument: + +```latte +Výchozí formát: {datetime} +Vlastní formát: {datetime 'd.m.Y'} +Použití proměnné: {datetime $userDateFormatPreference} + +{* Toto by způsobilo chybu po parsování 'd.m.Y', protože ", foo" je neočekávané *} +{* {datetime 'd.m.Y', foo} *} +``` + +Dále se podíváme na vytváření párových tagů, které zpracovávají obsah mezi nimi. + + +Zpracování párových tagů +======================== + +Dosud byl náš tag `{datetime}` *samouzavírací* (koncepčně). Nemá žádný obsah mezi počátečním a koncovým tagem. Mnoho užitečných tagů však pracuje s blokem obsahu šablony. Tyto se nazývají **párové tagy**. Příklady zahrnují `{if}...{/if}`, `{block}...{/block}` nebo vlastní tag, který nyní vytvoříme: `{debug}...{/debug}`. + +Tento tag nám umožní zahrnout do našich šablon ladící informace, které by měly být viditelné pouze během vývoje. + +**Cíl:** Vytvořit párový tag `{debug}`, jehož obsah je vykreslen pouze tehdy, když je aktivní specifický příznak "vývojového režimu". + + +Představení poskytovatelů +------------------------- + +Někdy vaše tagy potřebují přístup k datům nebo službám, které nejsou předávány přímo jako parametry šablony. Například určení, zda je aplikace ve vývojovém režimu, přístup k objektu uživatele nebo získání konfiguračních hodnot. Latte poskytuje mechanismus nazvaný **poskytovatelé** (Providers) pro tento účel. + +Poskytovatelé jsou registrováni ve vašem [rozšíření |extending-latte#Latte Extension] pomocí metody `getProviders()`. Tato metoda vrací asociativní pole, kde klíče jsou názvy, pod kterými budou poskytovatelé přístupní v běhovém kódu šablony, a hodnoty jsou skutečná data nebo objekty. + +Uvnitř PHP kódu generovaného metodou `print()` vašeho tagu můžete k těmto poskytovatelům přistupovat prostřednictvím speciální vlastnosti objektu `$this->global`. Protože tato vlastnost je sdílena napříč všemi rozšířeními, je dobrou praxí **předpony názvy vašich poskytovatelů** pro zabránění potenciálních kolizí jmen s klíčovými poskytovateli Latte nebo poskytovateli z jiných rozšíření třetích stran. Běžnou konvencí je používat krátkou, jedinečnou předponu související s vaším výrobcem nebo názvem rozšíření. Pro náš příklad použijeme předponu `app` a příznak vývojového režimu bude dostupný jako `$this->global->appDevMode`. + + +Klíčové slovo `yield` pro parsování obsahu +------------------------------------------ + +Jak říkáme parseru Latte, aby zpracoval obsah *mezi* `{debug}` a `{/debug}`? Zde přichází ke slovu klíčové slovo `yield`. + +Když je `yield` použito ve funkci `create()`, funkce se stává [PHP generátorem |https://www.php.net/manual/en/language.generators.overview.php]. Jeho vykonávání se pozastaví a řízení se vrátí k hlavnímu `TemplateParser`. `TemplateParser` pak pokračuje v parsování obsahu šablony *dokud* nenarazí na odpovídající uzavírací tag (`{/debug}` v našem případě). + +Jakmile je nalezen uzavírací tag, `TemplateParser` obnoví vykonávání naší funkce `create()` přímo za příkazem `yield`. Hodnota *vracená* příkazem `yield` je pole obsahující dva prvky: + +1. `AreaNode` reprezentující zparsovaný obsah mezi počátečním a koncovým tagem. +2. Objekt `Tag` reprezentující uzavírací tag (např. `{/debug}`). + +Vytvořme třídu `DebugNode` a její metodu `create` využívající `yield`. + +```php +node = new self; + + // Pozastavit parsování, získat vnitřní obsah a koncový tag, když je nalezen {/debug} + [$node->content, $endTag] = yield; + + return $node; + } + + // ... print() a getIterator() budou implementovány dále ... +} +``` + +Poznámka: `$endTag` je `null`, pokud je tag použit jako n:atribut, tj. `
                          ...
                          `. + + +Implementace `print()` pro podmíněné vykreslování +------------------------------------------------- + +Metoda `print()` nyní potřebuje generovat PHP kód, který za běhu zkontroluje poskytovatele `appDevMode` a pouze vykoná kód pro vnitřní obsah, pokud je příznak true. + +```php + public function print(PrintContext $context): string + { + // Vygeneruje PHP příkaz 'if', který za běhu zkontroluje poskytovatele + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + // Pokud je ve vývojovém režimu, vypíše vnitřní obsah + %node + } + + XX, + $this->position, // Pro %line komentář + $this->content, // Uzel obsahující AST vnitřního obsahu + ); + } +``` + +To je jednoduché. Používáme `PrintContext::format()` k vytvoření standardního PHP příkazu `if`. Uvnitř `if` umisťujeme zástupný symbol `%node` pro `$this->content`. Latte rekurzivně zavolá `$this->content->print($context)` pro vygenerování PHP kódu pro vnitřní část tagu, ale pouze pokud `$this->global->appDevMode` vyhodnotí za běhu jako true. + + +Implementace `getIterator()` pro obsah +-------------------------------------- + +Stejně jako u argumentového uzlu v předchozím příkladu, náš `DebugNode` nyní má dětský uzel: `AreaNode $content`. Musíme ho zpřístupnit poskytnutím v `getIterator()`: + +```php + public function &getIterator(): \Generator + { + // Poskytuje referenci na uzel obsahu + yield $this->content; + } +``` + +To umožňuje kompilačním průchodům sestoupit do obsahu našeho tagu `{debug}`, což je důležité i když je obsah podmíněně vykreslen. Například Sandbox potřebuje analyzovat obsah bez ohledu na to, zda je `appDevMode` true nebo false. + + +Registrace a použití +-------------------- + +Zaregistrujte tag a poskytovatele ve vašem rozšíření: + +```php +class MyLatteExtension extends Extension +{ + // Předpokládáme, že $isDevelopmentMode je určeno někde (např. z konfigurace) + public function __construct( + private bool $isDevelopmentMode, + ) { + } + + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), // Registrace nového tagu + ]; + } + + public function getProviders(): array + { + return [ + 'appDevMode' => $this->isDevelopmentMode, // Registrace poskytovatele + ]; + } +} + +// Při registraci rozšíření: +$isDev = true; // Určete toto na základě prostředí vaší aplikace +$latte->addExtension(new App\Latte\MyLatteExtension($isDev)); +``` + +A jeho použití v šabloně: + +```latte +

                          Běžný obsah viditelný vždy.

                          + +{debug} +
                          + ID aktuálního uživatele: {$user->id} + Čas požadavku: {=time()} +
                          +{/debug} + +

                          Další běžný obsah.

                          +``` + + +Integrace n:atributů +-------------------- + +Latte nabízí pohodlný zkrácený zápis pro mnoho párových tagů: [n:atributy |syntax#n:atributy]. Pokud máte párový tag jako `{tag}...{/tag}` a chcete, aby se jeho efekt aplikoval přímo na jediný HTML element, můžete ho často zapsat úsporněji jako atribut `n:tag` na tomto elementu. + +Pro většinu standardních párových tagů, které definujete (jako náš `{debug}`), Latte automaticky povolí odpovídající verzi `n:` atributu. Během registrace nemusíte dělat nic navíc: + +```latte +{* Standardní použití párového tagu *} +{debug}
                          Informace pro ladění
                          {/debug} + +{* Ekvivalentní použití s n:atributem *} +
                          Informace pro ladění
                          +``` + +Obě verze vykreslí `
                          ` pouze pokud je `$this->global->appDevMode` true. Předpony `inner-` a `tag-` také fungují podle očekávání. + +Někdy může logika vašeho tagu potřebovat chovat se mírně odlišně v závislosti na tom, zda je použit jako standardní párový tag nebo jako n:atribut, nebo zda je použita předpona jako `n:inner-tag` nebo `n:tag-tag`. Objekt `Latte\Compiler\Tag`, předaný vaší parsovací funkci `create()`, poskytuje tyto informace: + +- `$tag->isNAttribute(): bool`: Vrací `true`, pokud je tag parsován jako n:atribut +- `$tag->prefix: ?string`: Vrací předponu použitou s n:atributem, což může být `null` (není n:atribut), `Tag::PrefixNone`, `Tag::PrefixInner` nebo `Tag::PrefixTag` + +Nyní, když rozumíme jednoduchým tagům, parsování argumentů, párovým tagům, poskytovatelům a n:atributům, pojďme se zabývat složitějším scénářem zahrnujícím tagy vnořené v jiných tazích, s využitím našeho tagu `{debug}` jako výchozího bodu. + + +Mezilehlé tagy +============== + +Některé párové tagy umožňují nebo dokonce vyžadují, aby se jiné tagy objevily *uvnitř* nich před konečným uzavíracím tagem. Tyto se nazývají **mezilehlé tagy**. Klasické příklady zahrnují `{if}...{elseif}...{else}...{/if}` nebo `{switch}...{case}...{default}...{/switch}`. + +Rozšiřme náš tag `{debug}` o podporu volitelné klauzule `{else}`, která bude vykreslena, když aplikace *není* ve vývojovém režimu. + +**Cíl:** Upravit `{debug}` tak, aby podporoval volitelný mezilehlý tag `{else}`. Konečná syntaxe by měla být `{debug} ... {else} ... {/debug}`. + + +Parsování mezilehlých tagů pomocí `yield` +----------------------------------------- + +Již víme, že `yield` pozastavuje parsovací funkci `create()` a vrací zparsovaný obsah spolu s koncovým tagem. `yield` však nabízí více kontroly: můžete mu poskytnout pole *názvů mezilehlých tagů*. Když parser narazí na kterýkoli z těchto specifikovaných tagů **na stejné úrovni vnoření** (tj. jako přímé děti rodičovského tagu, ne uvnitř jiných bloků nebo tagů uvnitř něj), také zastaví parsování. + +Když se parsování zastaví kvůli mezilehlému tagu, zastaví parsování obsahu, obnoví generátor `create()` a předá zpět částečně zparsovaný obsah a **mezilehlý tag** samotný (místo konečného koncového tagu). Naše funkce `create()` pak může zpracovat tento mezilehlý tag (např. parsovat jeho argumenty, pokud nějaké měl) a znovu použít `yield` pro parsování *další* části obsahu až do *konečného* koncového tagu nebo jiného očekávaného mezilehlého tagu. + +Upravme `DebugNode::create()` tak, aby očekával `{else}`: + +```php +node = new self; + + // yield a očekávat buď {/debug} nebo {else} + [$node->thenContent, $nextTag] = yield ['else']; + + // Zkontrolovat, zda tag, u kterého jsme se zastavili, byl {else} + if ($nextTag?->name === 'else') { + // Yield znovu pro parsování obsahu mezi {else} a {/debug} + [$node->elseContent, $endTag] = yield; + } + + return $node; + } + + // ... print() a getIterator() budou aktualizovány dále ... +} +``` + +Nyní `yield ['else']` říká Latte, aby zastavilo parsování nejen pro `{/debug}`, ale také pro `{else}`. Pokud je `{else}` nalezen, `$nextTag` bude obsahovat objekt `Tag` pro `{else}`. Pak znovu použijeme `yield` bez argumentů, což znamená, že nyní očekáváme pouze konečný tag `{/debug}`, a uložíme výsledek do `$node->elseContent`. Pokud `{else}` nebyl nalezen, `$nextTag` by byl `Tag` pro `{/debug}` (nebo `null`, pokud je použit jako n:atribut) a `$node->elseContent` by zůstal `null`. + + +Implementace `print()` s `{else}` +--------------------------------- + +Metoda `print()` potřebuje odrážet novou strukturu. Měla by generovat PHP příkaz `if/else` založený na poskytovateli `devMode`. + +```php + public function print(PrintContext $context): string + { + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + %node // Kód pro větev 'then' (obsah {debug}) + } else { + %node // Kód pro větev 'else' (obsah {else}) + } + + XX, + $this->position, // Číslo řádku pro podmínku 'if' + $this->thenContent, // První zástupný symbol %node + $this->elseContent ?? new NopNode, // Druhý zástupný symbol %node + ); + } +``` + +Toto je standardní PHP struktura `if/else`. Používáme `%node` dvakrát; `format()` nahrazuje poskytnuté uzly postupně. Používáme `?? new NopNode` pro vyhnutí se chybám, pokud je `$this->elseContent` `null` – `NopNode` jednoduše nevytiskne nic. + + +Implementace `getIterator()` pro oba obsahy +------------------------------------------- + +Nyní máme potenciálně dva dětské uzly obsahu (`$thenContent` a `$elseContent`). Musíme poskytnout oba, pokud existují: + +```php + public function &getIterator(): \Generator + { + yield $this->thenContent; + if ($this->elseContent) { + yield $this->elseContent; + } + } +``` + + +Použití vylepšeného tagu +------------------------ + +Tag nyní může být použit s volitelnou klauzulí `{else}`: + +```latte +{debug} +

                          Zobrazování ladících informací, protože devMode je ZAPNUTO.

                          +{else} +

                          Ladící informace jsou skryty, protože devMode je VYPNUTO.

                          +{/debug} +``` + + +Zpracování stavu a vnoření +========================== + +Naše předchozí příklady (`{datetime}`, `{debug}`) byly relativně bezstavové v rámci svých metod `print()`. Buď přímo vypisovaly obsah, nebo prováděly jednoduchou podmíněnou kontrolu založenou na globálním poskytovateli. Mnoho tagů však potřebuje spravovat nějakou formu **stavu** během vykreslování nebo zahrnuje vyhodnocení uživatelských výrazů, které by měly být spuštěny pouze jednou kvůli výkonu nebo správnosti. Dále musíme zvážit, co se stane, když jsou naše vlastní tagy **vnořeny**. + +Ilustrujme tyto koncepty vytvořením tagu `{repeat $count}...{/repeat}`. Tento tag bude opakovat svůj vnitřní obsah `$count`-krát. + +**Cíl:** Implementovat `{repeat $count}`, který opakuje svůj obsah specifikovaný počet krát. + + +Potřeba dočasných & jedinečných proměnných +------------------------------------------ + +Představte si, že uživatel napíše: + +```latte +{repeat rand(1, 5)} Obsah {/repeat} +``` + +Pokud bychom naivně vygenerovali PHP `for` cyklus tímto způsobem v naší metodě `print()`: + +```php +// Zjednodušený, NESPRÁVNÝ generovaný kód +for ($i = 0; $i < rand(1, 5); $i++) { + // výpis obsahu +} +``` +To by bylo špatně! Výraz `rand(1, 5)` by byl **znovu vyhodnocen při každé iteraci cyklu**, což by vedlo k nepředvídatelnému počtu opakování. Potřebujeme vyhodnotit výraz `$count` *jednou* před začátkem cyklu a uložit jeho výsledek. + +Vygenerujeme PHP kód, který nejprve vyhodnotí výraz počtu a uloží ho do **dočasné běhové proměnné**. Abychom zabránili kolizím s proměnnými definovanými uživatelem šablony *a* interními proměnnými Latte (jako `$ʟ_...`), použijeme konvenci předpony **`$__` (dvojité podtržítko)** pro naše dočasné proměnné. + +Vygenerovaný kód by pak vypadal takto: + +```php +$__count = rand(1, 5); +for ($__i = 0; $__i < $__count; $__i++) { + // výpis obsahu +} +``` + +Nyní zvažme vnoření: + +```latte +{repeat $countA} {* Vnější cyklus *} + {repeat $countB} {* Vnitřní cyklus *} + ... + {/repeat} +{/repeat} +``` + +Pokud by vnější i vnitřní tag `{repeat}` generoval kód používající *stejné* názvy dočasných proměnných (např. `$__count` a `$__i`), vnitřní cyklus by přepsal proměnné vnějšího cyklu, což by narušilo logiku. + +Potřebujeme zajistit, aby dočasné proměnné generované pro každou instanci tagu `{repeat}` byly **jedinečné**. Toho dosáhneme pomocí `PrintContext::generateId()`. Tato metoda vrací jedinečné celé číslo během kompilační fáze. Můžeme připojit toto ID k názvům našich dočasných proměnných. + +Takže místo `$__count` budeme generovat `$__count_1` pro první tag repeat, `$__count_2` pro druhý atd. Podobně pro počítadlo cyklu použijeme `$__i_1`, `$__i_2` atd. + + +Implementace `RepeatNode` +------------------------- + +Pojďme vytvořit třídu uzlu. + +```php +expectArguments(); // ujistí se, že $count je poskytnut + $node = $tag->node = new self; + // Parsuje výraz počtu + $node->count = $tag->parser->parseExpression(); + // Získání vnitřního obsahu + [$node->content] = yield; + return $node; + } + + /** + * Generuje PHP 'for' cyklus s jedinečnými názvy proměnných. + */ + public function print(PrintContext $context): string + { + // Generování jedinečných názvů proměnných + $id = $context->generateId(); + $countVar = '$__count_' . $id; // např. $__count_1, $__count_2, atd. + $iteratorVar = '$__i_' . $id; // např. $__i_1, $__i_2, atd. + + return $context->format( + <<<'XX' + // Vyhodnocení výrazu počtu *jednou* a uložení + %raw = (int) (%node); + // Cyklus s použitím uloženého počtu a jedinečné iterační proměnné + for (%raw = 0; %2.raw < %0.raw; %2.raw++) %line { + %node // Vykreslení vnitřního obsahu + } + + XX, + $countVar, // %0 - Proměnná pro uložení počtu + $this->count, // %1 - Uzel výrazu pro počet + $iteratorVar, // %2 - Název iterační proměnné cyklu + $this->position, // %3 - Komentář s číslem řádku pro cyklus samotný + $this->content // %4 - Uzel vnitřního obsahu + ); + } + + /** + * Poskytuje dětské uzly (výraz počtu a obsah). + */ + public function &getIterator(): \Generator + { + yield $this->count; + yield $this->content; + } +} +``` + +Metoda `create()` parsuje požadovaný výraz `$count` pomocí `parseExpression()`. Nejprve je voláno `$tag->expectArguments()`. To zajišťuje, že uživatel poskytl *něco* po `{repeat}`. Zatímco `$tag->parser->parseExpression()` by selhalo, pokud by nic nebylo poskytnuto, chybová zpráva by mohla být o neočekávané syntaxi. Použití `expectArguments()` poskytuje mnohem jasnější chybu, konkrétně uvádějící, že argumenty chybí pro tag `{repeat}`. + +Metoda `print()` generuje PHP kód zodpovědný za provádění logiky opakování za běhu. Začíná generováním jedinečných názvů pro dočasné PHP proměnné, které bude potřebovat. + +Metoda `$context->format()` je volána s novým zástupným symbolem `%raw`, který vkládá *surový řetězec* poskytnutý jako odpovídající argument. Zde vkládá jedinečný název proměnné uložený v `$countVar` (např. `$__count_1`). A co `%0.raw` a `%2.raw`? To demonstruje **poziční zástupné symboly**. Místo pouhého `%raw`, který bere *další* dostupný surový argument, `%2.raw` explicitně bere argument na indexu 2 (což je `$iteratorVar`) a vkládá jeho surovou řetězcovou hodnotu. To nám umožňuje znovu použít řetězec `$iteratorVar` bez jeho vícenásobného předávání v seznamu argumentů pro `format()`. + +Toto pečlivě konstruované volání `format()` generuje efektivní a bezpečný PHP cyklus, který správně zpracovává výraz počtu a vyhýbá se kolizím názvů proměnných i když jsou tagy `{repeat}` vnořeny. + + +Registrace a použití +-------------------- + +Zaregistrujte tag ve vašem rozšíření: + +```php +use App\Latte\RepeatNode; + +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), // Registrace tagu repeat + ]; + } +} +``` + +Použijte ho v šabloně, včetně vnoření: + +```latte +{var $rows = rand(5, 7)} +{var $cols = rand(3, 5)} + +{repeat $rows} + + {repeat $cols} + Vnitřní cyklus + {/repeat} + +{/repeat} +``` + +Tento příklad demonstruje, jak zpracovat stav (počítadla cyklů) a potenciální problémy s vnořením pomocí dočasných proměnných s předponou `$__` a jedinečných s ID od `PrintContext::generateId()`. + + +Čisté n:atributy +---------------- + +Zatímco mnoho `n:atributů` jako `n:if` nebo `n:foreach` slouží jako pohodlné zkratky pro jejich protějšky v párových tazích (`{if}...{/if}`, `{foreach}...{/foreach}`), Latte také umožňuje definovat tagy, které *existují pouze* ve formě n:atributu. Ty se často používají k úpravě atributů nebo chování HTML elementu, ke kterému jsou připojeny. + +Standardní příklady vestavěné v Latte zahrnují [`n:class` |tags#n:class], který pomáhá dynamicky sestavit atribut `class`, a [`n:attr` |tags#n:attr], který může nastavit více libovolných atributů. + +Vytvořme si vlastní čistý n:atribut: `n:confirm`, který přidá JavaScript potvrzovací dialog před provedením akce (jako je následování odkazu nebo odeslání formuláře). + +**Cíl:** Implementovat `n:confirm="'Jste si jisti?'"`, který přidá obslužnou rutinu `onclick` pro zabránění výchozí akci, pokud uživatel zruší potvrzovací dialog. + + +Implementace `ConfirmNode` +-------------------------- + +Potřebujeme třídu Node a parsovací funkci. + +```php +expectArguments(); + $node = $tag->node = new self; + $node->message = $tag->parser->parseExpression(); + return $node; + } + + /** + * Generuje kód atributu 'onclick' se správným escapováním. + */ + public function print(PrintContext $context): string + { + // Zajišťuje správné escapování pro kontexty JavaScript i HTML atributu. + return $context->format( + <<<'XX' + echo ' onclick="', LR\Filters::escapeHtmlAttr('return confirm(' . LR\Filters::escapeJs(%node) . ')'), '"' %line; + XX, + $this->message, + $this->position, + ); + } + + public function &getIterator(): \Generator + { + yield $this->message; + } +} +``` + +Metoda `print()` generuje PHP kód, který nakonec během vykreslování šablony vypíše HTML atribut `onclick="..."`. Zpracování vnořených kontextů (JavaScript uvnitř HTML atributu) vyžaduje pečlivé escapování. Filtr `LR\Filters::escapeJs(%node)` je volán za běhu a escapuje zprávu správně pro použití uvnitř JavaScriptu (výstup by byl jako `"Sure?"`). Poté filtr `LR\Filters::escapeHtmlAttr(...)` escapuje znaky, které jsou speciální v HTML atributech, takže by to změnilo výstup na `return confirm("Sure?")`. Toto dvoustupňové běhové escapování zajišťuje, že zpráva je bezpečná pro JavaScript a výsledný JavaScript kód je bezpečný pro vložení do HTML atributu `onclick`. + + +Registrace a použití +-------------------- + +Zaregistrujte n:atribut ve vašem rozšíření. Nezapomeňte na předponu `n:` v klíči: + +```php +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), + 'n:confirm' => ConfirmNode::create(...), // Registrace n:confirm + ]; + } +} +``` + +Nyní můžete použít `n:confirm` na odkazech, tlačítkách nebo prvcích formuláře: + +```latte +Smazat +``` + +Vygenerované HTML: + +```html +Smazat +``` + +Když uživatel klikne na odkaz, prohlížeč provede kód `onclick`, zobrazí potvrzovací dialog a pouze přejde na `delete.php`, pokud uživatel klikne na "OK". + +Tento příklad demonstruje, jak lze vytvořit čistý n:atribut k úpravě chování nebo atributů svého hostitelského HTML elementu generováním vhodného PHP kódu v jeho metodě `print()`. Nezapomeňte na dvojité escapování, které je často vyžadováno: jednou pro cílový kontext (JavaScript v tomto případě) a znovu pro kontext HTML atributu. + + +Pokročilá témata +================ + +Zatímco předchozí sekce pokrývají základní koncepty, zde je několik pokročilejších témat, na která můžete narazit při vytváření vlastních Latte tagů. + + +Režimy výstupu tagů +------------------- + +Objekt `Tag` předaný vaší funkci `create()` má vlastnost `outputMode`. Tato vlastnost ovlivňuje, jak Latte zachází s okolními mezerami a odsazením, zejména když je tag použit na vlastním řádku. Tuto vlastnost můžete upravit ve vaší funkci `create()`. + +- `Tag::OutputKeepIndentation` (Výchozí pro většinu tagů jako `{=...}`): Latte se snaží zachovat odsazení před tagem. Nové řádky *po* tagu jsou obecně zachovány. To je vhodné pro tagy, které vypisují obsah v řádku. +- `Tag::OutputRemoveIndentation` (Výchozí pro blokové tagy jako `{if}`, `{foreach}`): Latte odstraňuje úvodní odsazení a potenciálně jeden následující nový řádek. To pomáhá udržet generovaný PHP kód čistší a zabraňuje dalším prázdným řádkům v HTML výstupu způsobeným samotným tagem. Použijte toto pro tagy, které reprezentují řídicí struktury nebo bloky, které by samy neměly přidávat mezery. +- `Tag::OutputNone` (Používá se tagy jako `{var}`, `{default}`): Podobné jako `RemoveIndentation`, ale signalizuje silněji, že tag samotný neprodukuje přímý výstup, potenciálně ovlivňuje zpracování mezer kolem něj ještě agresivněji. Vhodné pro deklarační nebo nastavovací tagy. + +Vyberte režim, který nejlépe vyhovuje účelu vašeho tagu. Pro většinu strukturálních nebo řídicích tagů je obvykle vhodný `OutputRemoveIndentation`. + + +Přístup k rodičovským/nejbližším tagům +-------------------------------------- + +Někdy chování tagu potřebuje záviset na kontextu, ve kterém je použit, konkrétně ve kterém rodičovském tagu(tazích) se nachází. Objekt `Tag` předaný vaší funkci `create()` poskytuje metodu `closestTag(array $classes, ?callable $condition = null): ?Tag` přesně pro tento účel. + +Tato metoda prohledává směrem nahoru hierarchii aktuálně otevřených tagů (včetně HTML elementů reprezentovaných interně během parsování) a vrací objekt `Tag` nejbližšího předka, který odpovídá specifickým kritériím. Pokud není nalezen žádný odpovídající předek, vrátí `null`. + +Pole `$classes` specifikuje, jaký druh předkových tagů hledáte. Kontroluje, zda je přidružený uzel předkového tagu (`$ancestorTag->node`) instancí této třídy. + +```php +function create(Tag $tag) +{ + // Hledání nejbližšího předkového tagu, jehož uzel je instancí ForeachNode + $foreachTag = $tag->closestTag([ForeachNode::class]); + if ($foreachTag) { + // Můžeme přistupovat k instanci ForeachNode samotné: + $foreachNode = $foreachTag->node; + } +} +``` + +Všimněte si `$foreachTag->node`: Toto funguje pouze proto, že je konvencí ve vývoji Latte tagů okamžitě přiřadit vytvořený uzel k `$tag->node` v rámci metody `create()`, jak jsme vždy dělali. + +Někdy pouhé porovnání typu uzlu nestačí. Můžete potřebovat zkontrolovat specifickou vlastnost potenciálního předkového tagu nebo jeho uzlu. Volitelný druhý argument pro `closestTag()` je callable, který přijímá potenciální předkový objekt `Tag` a měl by vracet, zda je platnou shodou. + +```php +function create(Tag $tag) +{ + $dynamicBlockTag = $tag->closestTag( + [BlockNode::class], + // Podmínka: blok musí být dynamický + fn(Tag $blockTag) => $blockTag->node->block->isDynamic(), + ); +} +``` + +Použití `closestTag()` umožňuje vytvářet tagy, které jsou kontextově uvědomělé a vynucují správné použití v rámci struktury vaší šablony, což vede k robustnějším a srozumitelnějším šablonám. + + +Zástupné symboly `PrintContext::format()` +----------------------------------------- + +Často jsme používali `PrintContext::format()` ke generování PHP kódu v metodách `print()` našich uzlů. Přijímá řetězec masky a následující argumenty, které nahrazují zástupné symboly v masce. Zde je shrnutí dostupných zástupných symbolů: + +- **`%node`**: Argument musí být instance `Node`. Volá metodu `print()` uzlu a vkládá výsledný řetězec PHP kódu. +- **`%dump`**: Argument je jakákoli PHP hodnota. Exportuje hodnotu do platného PHP kódu. Vhodné pro skaláry, pole, null. + - `$context->format('echo %dump;', 'Hello')` -> `echo 'Hello';` + - `$context->format('$arr = %dump;', [1, 2])` -> `$arr = [1, 2];` +- **`%raw`**: Vkládá argument přímo do výstupního PHP kódu bez jakéhokoli escapování nebo úprav. **Používejte s opatrností**, primárně pro vkládání předgenerovaných PHP kódových fragmentů nebo názvů proměnných. + - `$context->format('%raw = 1;', '$variableName')` -> `$variableName = 1;` +- **`%args`**: Argument musí být `Expression\ArrayNode`. Vypíše položky pole formátované jako argumenty pro volání funkce nebo metody (oddělené čárkami, zpracovává pojmenované argumenty, pokud jsou přítomny). + - `$argsNode = new ArrayNode([...]);` + - `$context->format('myFunc(%args);', $argsNode)` -> `myFunc(1, name: 'Joe');` +- **`%line`**: Argument musí být objekt `Position` (obvykle `$this->position`). Vkládá PHP komentář `/* line X */` indikující číslo řádku zdroje. + - `$context->format('echo "Hi" %line;', $this->position)` -> `echo "Hi" /* line 42:1 */;` +- **`%escape(...)`**: Generuje PHP kód, který *za běhu* escapuje vnitřní výraz pomocí aktuálních kontextově uvědomělých pravidel escapování. + - `$context->format('echo %escape(%node);', $variableNode)` +- **`%modify(...)`**: Argument musí být `ModifierNode`. Generuje PHP kód, který aplikuje filtry specifikované v `ModifierNode` na vnitřní obsah, včetně kontextově uvědomělého escapování, pokud není zakázáno pomocí `|noescape`. + - `$context->format('%modify(%node);', $modifierNode, $variableNode)` +- **`%modifyContent(...)`**: Podobné jako `%modify`, ale určené pro úpravu bloků zachyceného obsahu (často HTML). + +Můžete explicitně odkazovat na argumenty podle jejich indexu (od nuly): `%0.node`, `%1.dump`, `%2.raw`, atd. To umožňuje znovu použít argument několikrát v masce bez jeho opakovaného předávání do `format()`. Viz příklad tagu `{repeat}`, kde byly použity `%0.raw` a `%2.raw`. + + +Příklad komplexního parsování argumentů +--------------------------------------- + +Zatímco `parseExpression()`, `parseArguments()`, atd., pokrývají mnoho případů, někdy potřebujete složitější parsovací logiku používající nižší úroveň `TokenStream` dostupnou přes `$tag->parser->stream`. + +**Cíl:** Vytvořit tag `{embedYoutube $videoID, width: 640, height: 480}`. Chceme parsovat požadované ID videa (řetězec nebo proměnnou) následované volitelnými páry klíč-hodnota pro rozměry. + +```php +expectArguments(); + $node = $tag->node = new self; + // Parsování požadovaného ID videa + $node->videoId = $tag->parser->parseExpression(); + + // Parsování volitelných párů klíč-hodnota + $stream = $tag->parser->stream; // Získání tokového proudu + while ($stream->tryConsume(',')) { // Vyžaduje oddělení čárkou + // Očekávání identifikátoru 'width' nebo 'height' + $keyToken = $stream->consume(Token::Php_Identifier); + $key = strtolower($keyToken->text); + + $stream->consume(':'); // Očekávání oddělovače dvojtečky + + $value = $tag->parser->parseExpression(); // Parsování výrazu hodnoty + + if ($key === 'width') { + $node->width = $value; + } elseif ($key === 'height') { + $node->height = $value; + } else { + throw new CompileException("Neznámý argument '$key'. Očekáváno 'width' nebo 'height'.", $keyToken->position); + } + } + + return $node; + } +} +``` + +Tato úroveň kontroly vám umožňuje definovat velmi specifické a komplexní syntaxe pro vaše vlastní tagy přímou interakcí s tokovým proudem. + + +Použití `AuxiliaryNode` +----------------------- + +Latte poskytuje obecné "pomocné" uzly pro speciální situace během generování kódu nebo v rámci kompilačních průchodů. Jsou to `AuxiliaryNode` a `Php\Expression\AuxiliaryNode`. + +Považujte `AuxiliaryNode` za flexibilní kontejnerový uzel, který deleguje své základní funkcionality - generování kódu a vystavení dětských uzlů - argumentům poskytnutým v jeho konstruktoru: + +- Delegace `print()`: První argument konstruktoru je PHP **closure**. Když Latte volá metodu `print()` na `AuxiliaryNode`, spustí tuto poskytnutou closure. Closure přijímá `PrintContext` a jakékoli uzly předané v druhém argumentu konstruktoru, což vám umožňuje definovat zcela vlastní logiku generování PHP kódu za běhu. +- Delegace `getIterator()`: Druhý argument konstruktoru je **pole objektů `Node`**. Když Latte potřebuje projít děti `AuxiliaryNode` (např. během kompilačních průchodů), jeho metoda `getIterator()` jednoduše poskytuje uzly uvedené v tomto poli. + +Příklad: + +```php +$node = new AuxiliaryNode( + // 1. Tato closure se stává tělem print() + fn(PrintContext $context, $arg1, $arg2) => $context->format('...%node...%node...', $arg1, $arg2), + + // 2. Tyto uzly jsou poskytovány metodou getIterator() a předány closure výše + [$argumentNode1, $argumentNode2] +); +``` + +Latte poskytuje dva odlišné typy založené na tom, kde potřebujete vložit generovaný kód: + +- `Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode`: Použijte toto, když potřebujete generovat kus PHP kódu, který reprezentuje **výraz** +- `Latte\Compiler\Nodes\AuxiliaryNode`: Použijte toto pro obecnější účely, když potřebujete vložit blok PHP kódu reprezentující jeden nebo více **příkazů** + +Důležitým důvodem k použití `AuxiliaryNode` namísto standardních uzlů (jako `StaticMethodCallNode`) v rámci vaší metody `print()` nebo kompilačního průchodu je **kontrola viditelnosti pro následující kompilační průchody**, zejména ty související s bezpečností, jako je Sandbox. + +Uvažte scénář: Váš kompilační průchod potřebuje obalit uživatelem poskytnutý výraz (`$userExpr`) voláním specifické, důvěryhodné pomocné funkce `myInternalSanitize($userExpr)`. Pokud vytvoříte standardní uzel `new FunctionCallNode('myInternalSanitize', [$userExpr])`, bude plně viditelný pro průchod AST. Pokud Sandbox průchod běží později a `myInternalSanitize` *není* na jeho seznamu povolených, Sandbox může toto volání *blokovat* nebo upravit, potenciálně narušující vnitřní logiku vašeho tagu, i když *vy*, autor tagu, víte, že toto specifické volání je bezpečné a nezbytné. Můžete tedy generovat volání přímo v rámci closure `AuxiliaryNode`. + +```php +use Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode; + +// ... uvnitř print() nebo kompilačního průchodu ... +$wrappedNode = new AuxiliaryNode( + fn(PrintContext $context, $userExpr) => $context->format( + 'myInternalSanitize(%node)', // Přímé generování PHP kódu + $userExpr, + ), + // DŮLEŽITÉ: Stále předejte původní uzel uživatelského výrazu zde! + [$userExpr], +); +``` + +V tomto případě průchod Sandbox vidí `AuxiliaryNode`, ale **neanalyzuje PHP kód generovaný jeho closure**. Nemůže přímo blokovat volání `myInternalSanitize` generované *uvnitř* closure. + +Zatímco generovaný PHP kód samotný je skryt před průchody, *vstupy* do tohoto kódu (uzly reprezentující uživatelská data nebo výrazy) **musí být stále průchodné**. Proto je druhý argument konstruktoru `AuxiliaryNode` zásadní. **Musíte** předat pole obsahující všechny původní uzly (jako `$userExpr` v příkladu výše), které vaše closure používá. `getIterator()` `AuxiliaryNode` **poskytne tyto uzly**, umožňující kompilačním průchodům jako Sandbox analyzovat je pro potenciální problémy. + + +Osvědčené postupy +================= + +- **Jasný účel:** Ujistěte se, že váš tag má jasný a nezbytný účel. Nevytvářejte tagy pro úkoly, které lze snadno řešit pomocí [filtrů |custom-filters] nebo [funkcí |custom-functions]. +- **Správně implementujte `getIterator()`:** Vždy implementujte `getIterator()` a poskytujte *reference* (`&`) na *všechny* dětské uzly (argumenty, obsah), které byly zparsovány ze šablony. To je nezbytné pro kompilační průchody, bezpečnost (Sandbox) a potenciální budoucí optimalizace. +- **Veřejné vlastnosti pro uzly:** Vlastnosti obsahující dětské uzly dělejte veřejnými, aby je kompilační průchody mohly v případě potřeby upravovat. +- **Používejte `PrintContext::format()`:** Využívejte metodu `format()` pro generování PHP kódu. Zpracovává uvozovky, správně escapuje zástupné symboly a přidává komentáře s číslem řádku automaticky. +- **Dočasné proměnné (`$__`):** Při generování běhového PHP kódu, který potřebuje dočasné proměnné (např. pro ukládání mezisoučtů, počítadla cyklů), používejte konvenci předpony `$__` pro vyhnutí se kolizím s uživatelskými proměnnými a interními proměnnými Latte `$ʟ_`. +- **Vnoření a jedinečná ID:** Pokud váš tag může být vnořený nebo potřebuje stav specifický pro instanci za běhu, použijte `$context->generateId()` v rámci vaší metody `print()` pro vytvoření jedinečných přípon pro vaše dočasné proměnné `$__`. +- **Poskytovatelé pro externí data:** Používejte poskytovatele (registrované přes `Extension::getProviders()`) pro přístup k běhovým datům nebo službám ($this->global->...) místo hardcodování hodnot nebo spoléhání se na globální stav. Používejte předpony výrobce pro názvy poskytovatelů. +- **Zvažte n:atributy:** Pokud váš párový tag logicky operuje na jednom HTML elementu, Latte pravděpodobně poskytuje automatickou podporu `n:atributu`. Mějte to na paměti pro pohodlí uživatele. Pokud vytváříte tag modifikující atribut, zvažte, zda je čistý `n:atribut` nejvhodnější formou. +- **Testování:** Pište testy pro vaše tagy, pokrývající jak parsování různých syntaktických vstupů, tak správnost výstupu generovaného **PHP kódu**. + +Dodržováním těchto pokynů můžete vytvářet mocné, robustní a udržitelné vlastní tagy, které se bezproblémově integrují s šablonovacím enginem Latte. + +.[note] +Studium tříd uzlů, které jsou součástí Latte, je nejlepší způsob, jak se naučit všechny podrobnosti o procesu parsování. diff --git a/latte/cs/develop.texy b/latte/cs/develop.texy index 38f0f45207..0377d96485 100644 --- a/latte/cs/develop.texy +++ b/latte/cs/develop.texy @@ -15,9 +15,8 @@ Podporované verze PHP (platí pro poslední setinkové verze Latte): | verze | kompatibilní s PHP |-----------------|------------------- -| Latte 3.0 | PHP 8.0 – 8.2 -| Latte 2.11 | PHP 7.1 – 8.2 -| Latte 2.8 – 2.10| PHP 7.1 – 8.1 +| Latte 3.1 | PHP 8.2 – 8.5 +| Latte 3.0 | PHP 8.0 – 8.5 Jak vykreslit šablonu @@ -28,7 +27,7 @@ Jak vykreslit šablonu? Stačí k tomu tento jednoduchý kód: ```php $latte = new Latte\Engine; // adresář pro cache -$latte->setTempDirectory('/path/to/tempdir'); +$latte->setCacheDirectory('/path/to/tempdir'); $params = [ /* proměnné šablony */ ]; // or $params = new TemplateParameters(/* ... */); @@ -39,7 +38,7 @@ $latte->render('template.latte', $params); $output = $latte->renderToString('template.latte', $params); ``` -Parametry mohou být pole nebo ještě lépe [objekt|#Parametry jako třída], který zajistí typovou kontrolu a napovídání v editorech. +Parametry mohou být pole nebo ještě lépe [objekt |#Parametry jako třída], který zajistí typovou kontrolu a napovídání v editorech. .[note] Ukázky použití najdete také v repozitáři [Latte examples |https://github.com/nette-examples/latte]. @@ -56,15 +55,13 @@ Cache se automaticky regeneruje pokaždé, když změníte zdrojový soubor. Bě $latte->setAutoRefresh(false); ``` -Při nasazení na produkčním serveru může prvotní vygenerování cache, zejména u rozsáhlejších aplikací, pochopitelně chviličku trvat. Latte má vestavěnou prevenci před "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. -Jde o situaci, kdy se sejde větší počet souběžných požadavků, které spustí Latte, a protože cache ještě neexistuje, začaly by ji všechny generovat současně. Což by neúměrně zatížilo server. -Latte je chytré a při více souběžných požadavcích generuje cache pouze první vlákno, ostatní čekají a následně ji využíjí. +Při nasazení na produkčním serveru může prvotní vygenerování cache, zejména u rozsáhlejších aplikací, pochopitelně chviličku trvat. Latte má vestavěnou prevenci před "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Jde o situaci, kdy se sejde větší počet souběžných požadavků, které spustí Latte, a protože cache ještě neexistuje, začaly by ji všechny generovat současně. Což by neúměrně zatížilo server. Latte je chytré a při více souběžných požadavcích generuje cache pouze první vlákno, ostatní čekají a následně ji využíjí. Parametry jako třída ==================== -Lepší než předávat proměnné do šablony jako pole je vytvořit si třídu. Získáte tak [typově bezpečný zápis|type-system], [příjemné napovídání v IDE|recipes#Editory a IDE] a cestu pro [registraci filtrů|extending-latte#Filtry pomocí třídy] a [funkcí|extending-latte#Funkce pomocí třídy]. +Lepší než předávat proměnné do šablony jako pole je vytvořit si třídu. Získáte tak [typově bezpečný zápis|type-system], [příjemné napovídání v IDE |recipes#Editory a IDE] a cestu pro [registraci filtrů |custom-filters#Filtry používající třídu s atributy] a [funkcí |custom-functions#Funkce používající třídu s atributy]. ```php class MailTemplateParameters @@ -129,26 +126,129 @@ Metoda `__toString` musí vracet korektní HTML a zajistit escapování parametr Jak rozšířit Latte o filtry, značky atd. ======================================== -Jak do Latte přidat vlastní filtr, funkci, značku atd? O tom pojednává kapitola [rozšiřujeme Latte |extending-latte]. -Pokud chcete své úpravy znovu použít v různých projektech nebo je sdílet s ostatními, měli byste [vytvořit rozšíření |creating-extension]. +Jak do Latte přidat vlastní filtr, funkci, značku atd? O tom pojednává kapitola [rozšiřujeme Latte |extending-latte]. Pokud chcete své úpravy znovu použít v různých projektech nebo je sdílet s ostatními, měli byste [vytvořit rozšíření |extending-latte#Latte Extension]. -Libovolný kód v šabloně `{php ...}` .{data-version:3.0}{toc: RawPhpExtension} -============================================================================= +Libovolný kód v šabloně `{php ...}` .{toc: RawPhpExtension} +=========================================================== -Uvnitř značky [`{do}`|tags#do] lze zapisovat pouze PHP výrazy, nemůžete tak třeba vložit konstrukce jako `if ... else` nebo statementy ukončené středníkem. +Uvnitř značky [`{do}` |tags#do] lze zapisovat pouze PHP výrazy, nemůžete tak třeba vložit konstrukce jako `if ... else` nebo statementy ukončené středníkem. -Můžete si však zaregistrovat rozšíření `RawPhpExtension`, které přidá značku `{php ...}`, kterou lze vkládat jakýkoliv PHP kód na zodpovědnost autora šablony. +Můžete si však zaregistrovat rozšíření `RawPhpExtension`, které přidává značku `{php ...}`. Pomocí té lze vkládat jakýkoliv PHP kód. Nevztahují se na něj žádná pravidla sandbox režimu, použití je tedy na zodpovědnost autora šablony. ```php $latte->addExtension(new Latte\Essential\RawPhpExtension); ``` -Překládání v šablonách .{data-version:3.0}{toc: TranslatorExtension} -==================================================================== +Kontrola vygenerovaného kódu .{data-version:3.0.7} +================================================== -Pomocí rozšíření `TranslatorExtension` přidáte do šablony značky [`{_...}`|tags#_], [`{translate}`|tags#translate] a filtr [`translate`|filters#translate]. Slouží k překládání hodnot nebo částí šablony do jiných jazyků. Jako parametr uvedeme metodu (PHP callable) provádějící překlad: +Latte kompiluje šablony do PHP kódu. Samozřejmě dbá na to, aby vygenerovaný kód byl syntakticky validní. Nicméně při použítí rozšíření třetích stran nebo `RawPhpExtension` nemůže Latte zaručit správnost vygenerovaného souboru. Také lze v PHP zapsat kód, který je sice syntakticky správný, ale je zakázaný (například přiřazení hodnoty do proměnné `$this`) a způsobí PHP Compile Error. Pokud takovou operaci zapíšete v šabloně, dostane se i do vygenerovaného PHP kódu. Jelikož v PHP existují na dvě stovky různých zakázaných operací, nemá Latte ambici je odhalovat. Upozorní na ně až samotné PHP při vykreslení, což obvykle ničemu nevadí. + +Jsou ale situace, kdy chcete vědět už v době kompilace šablony, že žádný PHP Compile Error neobsahuje. Zejména tehdy, pokud šablony mohou editovat uživatelé, nebo používáte [Sandbox]. V takovém případě si nechte šablony kontrolovat už v době kompilace. Tuto funkčnost zapnete metodou `Engine::enablePhpLint()`. Jelikož ke kontrole potřebuje volat binárku PHP, cestu k ní předejte jako parametr: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); + +try { + $latte->compile('home.latte'); +} catch (Latte\CompileException $e) { + // zachytí chyby v Latte a také Compile Error v PHP + echo 'Error: ' . $e->getMessage(); +} +``` + + +Národní prostředí .{data-version:3.0.18}{toc: Locale} +===================================================== + +Latte umožňuje nastavit národní prostředí, které ovlivňuje formátování čísel, datumů a řazení. Nastavuje se pomocí metody `setLocale()`. Identifikátor prostředí se řídí standardem IETF language tag, který používá rozšíření PHP `intl`. Skládá se z kódu jazyka a případně kódu země, např. `en_US` pro angličtinu ve Spojených státech, `de_DE` pro němčinu v Německu atd. + +```php +$latte = new Latte\Engine; +$latte->setLocale('cs'); +``` + +Nastavení prostředí ovlivňuje filtry [localDate |filters#localDate], [sort |filters#sort], [number |filters#number] a [bytes |filters#bytes]. + +.[note] +Vyžaduje PHP rozšíření `intl`. Nastavení v Latte neovlivňuje globální nastavení locale v PHP. + + +Striktní režim .{data-version:3.0.8} +==================================== + +Ve striktním režimu parsování Latte kontroluje, zda nechybí uzavírací HTML značky a také zakazuje používání proměnné `$this`. Zapnete jej takto: + +```php +$latte = new Latte\Engine; +$latte->setFeature(Latte\Feature::StrictParsing); +``` + +Generování šablon s hlavičkou `declare(strict_types=1)` zapnete takto: + +```php +$latte = new Latte\Engine; +$latte->setFeature(Latte\Feature::StrictTypes); +``` + +.[note] +Od verze Latte 3.1 jsou strict types povoleny ve výchozím nastavení. Můžete je deaktivovat pomocí `$latte->setFeature(Latte\Feature::StrictTypes, false)`. + + +Migrační varování .{data-version:3.1} +===================================== + +Latte 3.1 mění chování některých [HTML atributů|html-attributes]. Například hodnoty `null` nyní odstraní atribut namísto vypsání prázdného řetězce. Abyste snadno našli místa, kde tato změna ovlivňuje vaše šablony, můžete zapnout varování o migraci: + +```php +$latte->setFeature(Latte\Feature::MigrationWarnings); +``` + +Pokud je toto zapnuto, Latte kontroluje vykreslované atributy a vyvolá uživatelské varování (`E_USER_WARNING`), pokud se výstup liší od toho, co by Latte 3.0 vygenerovalo. Když narazíte na varování, použijte jedno z těchto řešení: + +1. Pokud je nový výstup pro váš případ použití správný (např. preferujete, aby atribut zmizel při `null`), potlačte varování přidáním filtru `|accept` +2. Pokud chcete, aby byl atribut vykreslen jako prázdný (např. `title=""`) namísto odstranění, když je proměnná `null`, poskytněte prázdný řetězec jako zálohu: `title={$val ?? ''}` +3. Pokud striktně vyžadujete staré chování (např. vypsání `"1"` pro `true` namísto `"true"`), explicitně přetypujte hodnotu na řetězec: `data-foo={(string) $val}` + +Jakmile jsou všechna varování vyřešena, vypněte varování o migraci a **odstraňte všechny** filtry `|accept` ze svých šablon, protože již nejsou potřeba. + + +Scopované proměnné cyklu .{data-version:3.1.2} +============================================== + +Ve výchozím nastavení proměnné definované v cyklech `{foreach}` (jako `$key` a `$value`) zůstávají dostupné i po skončení cyklu, což odpovídá nativnímu chování PHP. To může vést k neočekávanému přepsání proměnných, pokud má proměnná cyklu stejný název jako existující proměnná šablony. + +S funkcí `ScopedLoopVariables` existují proměnné cyklu pouze uvnitř cyklu. Po jeho skončení se obnoví původní hodnota proměnné (pokud existovala), nebo se proměnná odstraní: + +```php +$latte = new Latte\Engine; +$latte->setFeature(Latte\Feature::ScopedLoopVariables); +``` + +Příklad rozdílu: + +```latte +{var $item = 'original'} +{foreach [1, 2] as $item}{$item}, {/foreach} +{$item} +``` + +Bez `ScopedLoopVariables`: vypíše `1, 2, 2` (proměnná je přepsána) +Se `ScopedLoopVariables`: vypíše `1, 2, original` (proměnná je obnovena) + +Funguje to také s destrukturováním jako `{foreach $array as [$a, $b]}`. + +.[note] +Proměnné cyklu používající reference (`{foreach $array as &$value}`) nebo přiřazení do vlastností (`{foreach $array as $obj->prop}`) nejsou scopovány, protože by to narušilo jejich zamýšlenou funkčnost. + + +Překládání v šablonách .{toc: TranslatorExtension} +================================================== + +Pomocí rozšíření `TranslatorExtension` přidáte do šablony značky [`{_...}` |tags#], [`{translate}` |tags#translate] a filtr [`translate` |filters#translate]. Slouží k překládání hodnot nebo částí šablony do jiných jazyků. Jako parametr uvedeme metodu (PHP callable) provádějící překlad: ```php class MyTranslator @@ -194,7 +294,7 @@ public function translate(string $original, ...$params): string Debuggování a Tracy =================== -Latte se vám snaží vývoj co nejvíce zpříjemnit. Přímo pro účely debugování existuje trojice značek [`{dump}`|tags#dump], [`{debugbreak}`|tags#debugbreak] a [`{trace}`|tags#trace]. +Latte se vám snaží vývoj co nejvíce zpříjemnit. Přímo pro účely debugování existuje trojice značek [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak] a [`{trace}` |tags#trace]. Největší komfort získáte, když ještě si nainstalujete skvělý [ladicí nástroj Tracy|tracy:] a aktivujete doplněk pro Latte: @@ -207,24 +307,27 @@ $latte = new Latte\Engine; $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension); ``` -Nyní se vám budou všechny chyby zobrazovat v přehledné červené obrazovce, včetně chyb v šablonách se zvýrazněním řádku a sloupce ([video|https://github.com/nette/tracy/releases/tag/v2.9.0]). -Zároveň v pravém dolním rohu v tzv. Tracy Baru se objeví záložka pro Latte, kde jsou přehledně vidět všechny vykreslované šablony a jejich vzájemné vztahy (včetně možnosti se do šablony nebo zkompilovaného kódu prokliknout) a také proměnné: +Nyní se vám budou všechny chyby zobrazovat v přehledné červené obrazovce, včetně chyb v šablonách se zvýrazněním řádku a sloupce ([video|https://github.com/nette/tracy/releases/tag/v2.9.0]). Zároveň v pravém dolním rohu v tzv. Tracy Baru se objeví záložka pro Latte, kde jsou přehledně vidět všechny vykreslované šablony a jejich vzájemné vztahy (včetně možnosti se do šablony nebo zkompilovaného kódu prokliknout) a také proměnné: [* latte-debugging.webp *] Jelikož Latte kompiluje šablony do přehledného PHP kódu, můžete je pohodlně ve svém IDE krokovat. -Linter: validace syntaxe šablon .{data-version:2.11}{toc: Linter} -================================================================= +Linter: validace syntaxe šablon .{toc: Linter} +============================================== -Projít všechny šablony a zkontrolovat, zda neobsahují syntaktické chyby, vám pomůže nástroj Linter. Spouští se z konzole: +Ke kontrole všech šablon slouží nástroj **Linter**. Jeho úkolem je projít zadané soubory a ověřit, že neobsahují syntaktické chyby ani odkazy na neexistující značky, filtry, funkce, třídy apod. + +Linter se spouští z příkazové řádky: ```shell vendor/bin/latte-lint ``` -Pokud používáte vlastní značky, vytvořte si také vlastní verzi Linteru, např. `custom-latte-lint`: +Parametrem `--strict` aktivujete [#striktní režim]. + +Pokud používáte vlastní značky, filtry nebo další rozšíření Latte, je potřeba vytvořit si vlastní variantu Linteru, například `custom-latte-lint`. V té zaregistrujete všechna potřebná rozšíření ještě před samotnou validací šablon: ```php #!/usr/bin/env php @@ -233,24 +336,32 @@ Pokud používáte vlastní značky, vytvořte si také vlastní verzi Linteru, // zadejte skutečnou cestu k soubor autoload.php require __DIR__ . '/vendor/autoload.php'; -$linter = new Latte\Tools\Linter($engine); -$linter->scanDirectory($path); +$path = $argv[1] ?? '.'; -$engine = new Latte\Engine; -// tady zaregistruje jednotlivá rozšíření -$engine->addExtension(/* ... */); +$linter = new Latte\Tools\Linter; +$latte = $linter->getEngine(); +// tady přidejte jednotlivá svá rozšíření +$latte->addExtension(/* ... */); -$path = $argv[1]; -$linter = new Latte\Tools\Linter(engine: $engine); $ok = $linter->scanDirectory($path); exit($ok ? 0 : 1); ``` +Alternativně můžete vlastní objekt `Latte\Engine` předat do Linteru: + +```php +$latte = new Latte\Engine; +// tady nakonfigurujeme objekt $latte +$linter = new Latte\Tools\Linter(engine: $latte); +``` + +Takto přizpůsobený linter pak můžete používat stejným způsobem jako standardní nástroj, ale s plnou znalostí vašich vlastních rozšíření. + Načítání šablon z řetězce ========================= -Potřebujete načítat šablony z řetězců místo souborů, třeba pro účely testování? Pomůže vám [StringLoader|extending-latte#stringloader]: +Potřebujete načítat šablony z řetězců místo souborů, třeba pro účely testování? Pomůže vám [StringLoader |loaders#StringLoader]: ```php $latte->setLoader(new Latte\Loaders\StringLoader([ @@ -265,7 +376,7 @@ $latte->render('main.file', $params); Exception handler ================= -Můžete si definovat vlastní obslužný handler pro očekávané výjimky. Předají se mu výjimky vzniklé uvnitř [`{try}`|tags#try] a v [sandboxu|sandbox]. +Můžete si definovat vlastní obslužný handler pro očekávané výjimky. Předají se mu výjimky vzniklé uvnitř [`{try}` |tags#try] a v [sandboxu|sandbox]. ```php $loggingHandler = function (Throwable $e, Latte\Runtime\Template $template) use ($logger) { @@ -280,7 +391,7 @@ $latte->setExceptionHandler($loggingHandler); Automatické dohledávání layoutu =============================== -Pomocí značky [`{layout}`|template-inheritance#layoutova-dedicnost] šablona určuje svou rodičovskou šablonu. Je taky možné nechat dohledávat layout automaticky, což zjednoduší psaní šablon, neboť v nich nebude nutné značku `{layout}` uvádět. +Pomocí značky [`{layout}` |template-inheritance#Layoutová dědičnost] šablona určuje svou rodičovskou šablonu. Je taky možné nechat dohledávat layout automaticky, což zjednoduší psaní šablon, neboť v nich nebude nutné značku `{layout}` uvádět. Dosáhne se toho následujícím způsobem: diff --git a/latte/cs/extending-latte.texy b/latte/cs/extending-latte.texy index 30dbb08f9e..a378ff8cf0 100644 --- a/latte/cs/extending-latte.texy +++ b/latte/cs/extending-latte.texy @@ -1,285 +1,220 @@ -Rozšiřujeme Latte +Rozšiřování Latte ***************** .[perex] -Latte je velmi flexibilní a lze jej rozšířit mnoha způsoby: můžete přidat vlastní filtry, funkce, značky, loadery atd. Ukážeme si jak na to. +Latte je navrženo s ohledem na rozšiřitelnost. Přestože jeho standardní sada tagů, filtrů a funkcí pokrývá mnoho případů použití, často potřebujete přidat vlastní specifickou logiku nebo pomocné nástroje. Tato stránka poskytuje přehled způsobů, jak rozšířit Latte tak, aby dokonale odpovídalo požadavkům vašeho projektu - od jednoduchých pomocníků až po komplexní novou syntaxi. -Tato kapitola popisuje jednotlivé cesty rozšiřování Latte. Pokud chcete své úpravy znovu použít v různých projektech nebo je sdílet s ostatními, měli byste [vytvořit tzv. rozšíření |creating-extension]. +Způsoby rozšíření Latte +======================= -Kolik cest vede do Říma? -======================== +Zde je rychlý přehled hlavních způsobů, jak můžete přizpůsobit a rozšířit Latte: -Protože některé způsoby rozšíření Latte mohou splývat, zkusíme si nejprve vysvětlit rozdíly mezi nimi. Jako příklad se pokusíme implementovat generátor *Lorem ipsum*, kterému předáme počet slov, jenž má vygenerovat. +- **[Vlastní filtry |Custom Filters]:** Pro formátování nebo transformaci dat přímo ve výstupu šablony (např. `{$var|myFilter}`). Ideální pro úlohy jako formátování datumů, úpravy textu nebo aplikování specifického escapování. Můžete je také použít k úpravě větších bloků HTML obsahu tak, že obsah obalíte anonymním [`{block}` |tags#block] a aplikujete na něj vlastní filtr. +- **[Vlastní funkce |Custom Functions]:** Pro přidání znovupoužitelné logiky, kterou lze volat v rámci výrazů v šabloně (např. `{myFunction($arg1, $arg2)}`). Užitečné pro výpočty, přístup k pomocným funkcím aplikace nebo generování malých částí obsahu. +- **[Vlastní tagy |Custom Tags]:** Pro vytváření zcela nových jazykových konstrukcí (`{mytag}...{/mytag}` nebo `n:mytag`). Tagy nabízejí nejvíce možností, umožňují definovat vlastní struktury, řídit parsování šablony a implementovat komplexní vykreslovací logiku. +- **[Kompilační průchody |Compiler Passes]:** Funkce, které upravují abstraktní syntaktický strom (AST) šablony po parsování, ale před generováním PHP kódu. Používají se pro pokročilé optimalizace, bezpečnostní kontroly (jako je Sandbox) nebo automatické úpravy kódu. +- **[Vlastní loadery |loaders]:** Pro změnu způsobu, jakým Latte vyhledává a načítá soubory šablon (např. načítání z databáze, šifrovaného úložiště atd.). -Hlavní konstrukcí jazyka Latte je značka (tag). Generátor můžeme implementovat rozšířením jazyka Latte o nový tag: +Výběr správné metody rozšíření je klíčový. Než vytvoříte složitý tag, zvažte, zda by nestačil jednodušší filtr nebo funkce. Ukažme si to na příkladu: implementace generátoru *Lorem ipsum*, který jako argument přijímá počet slov k vygenerování. -```latte -{lipsum 40} -``` - -Tag bude skvěle fungovat. Nicméně generátor v podobě tagu nemusí být dostatečně flexibilní, protože jej nelze použít ve výrazu. Mimochodem v praxi potřebujete tagy vytvářet jen zřídka; a to je dobrá zpráva, protože tagy jsou složitějším způsobem rozšíření. - -Dobrá, zkusme místo tagu vytvořit filtr: - -```latte -{=40|lipsum} -``` - -Opět validní možnost. Ale filtr by měl předanou hodnotu transformovat na něco jiného. Zde hodnotu `40`, která udává počet vygenerovaných slov, používáme jako argument filtru, nikoli jako hodnotu, kterou chceme transformovat. +- **Jako tag?** `{lipsum 40}` - Možné, ale tagy jsou vhodnější pro řídicí struktury nebo generování složitých značek. Tagy nelze použít přímo ve výrazech. +- **Jako filtr?** `{=40|lipsum}` - Technicky to funguje, ale filtry jsou určeny k *transformaci* vstupní hodnoty. Zde je `40` *argument*, nikoli hodnota, která se transformuje. To působí sémanticky nesprávně. +- **Jako funkce?** `{lipsum(40)}` - Toto je nejpřirozenější řešení! Funkce přijímají argumenty a vracejí hodnoty, což je ideální pro použití v libovolném výrazu: `{var $text = lipsum(40)}`. -Tak zkusíme použít funkci: +**Obecné doporučení:** Používejte funkce pro výpočty/generování, filtry pro transformaci a tagy pro nové jazykové konstrukce nebo složité značky. Průchody používejte pro manipulaci s AST a loadery pro získávání šablon. -```latte -{lipsum(40)} -``` -To je ono! Pro tento konkrétní příklad je vytvoření funkce ideálním způsobem rozšíření. Můžete ji volat kdekoli, kde je akceptován výraz, například: +Přímá registrace +================ -```latte -{var $text = lipsum(40)} -``` +Pro pomocné nástroje specifické pro projekt nebo rychlá rozšíření umožňuje Latte přímou registraci filtrů a funkcí do objektu `Latte\Engine`. - -Filtry -====== - -Filtr vytvoříme zaregistrováním jeho názvu a libovolného PHP callable, třeba funkce: +Pro registraci filtru použijte metodu `addFilter()`. Prvním argumentem vaší filtrační funkce bude hodnota před znakem `|` a následující argumenty jsou ty, které se předávají za dvojtečkou `:`. ```php $latte = new Latte\Engine; -$latte->addFilter('shortify', fn(string $s) => mb_substr($s, 0, 10)); // zkrátí text na 10 písmen -``` -V tomto případě by bylo šikovnější, kdyby filtr přijímal další parametr: +// Definice filtru (volatelný objekt: funkce, statická metoda atd.) +$myTruncate = fn(string $s, int $length = 50) => mb_substr($s, 0, $length); -```php -$latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); -``` - -V šabloně se potom volá takto: +// Registrace +$latte->addFilter('truncate', $myTruncate); -```latte -

                          {$text|shortify}

                          -

                          {$text|shortify:100}

                          +// Použití v šabloně: {$text|truncate} nebo {$text|truncate:100} ``` -Jak vidíte, funkce obdrží levou stranu filtru před pipe `|` jako první argument a argumenty předané filtru za `:` jako další argumenty. - -Funkce představující filtr může samozřejmě přijímat libovolný počet parametrů, podporovány jsou i variadic parametry. +Pro registraci funkce použitelné ve výrazech šablony použijte `addFunction()`. +```php +$latte = new Latte\Engine; -Filtry pomocí třídy -------------------- - -Druhým způsobem definice filtru je [využití třídy|develop#Parametry jako třída]. Vytvoříme metodu s atributem `TemplateFilter`: +// Definice funkce +$isWeekend = fn(DateTimeInterface $date) => $date->format('N') >= 6; -```php -class TemplateParameters -{ - public function __construct( - // parameters - ) {} - - #[Latte\Attributes\TemplateFilter] - public function shortify(string $s, int $len = 10): string - { - return mb_substr($s, 0, $len); - } -} +// Registrace +$latte->addFunction('isWeekend', $isWeekend); -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); +// Použití v šabloně: {if isWeekend($myDate)}Víkend!{/if} ``` -Pokud používáte PHP 7.x a Latte 2.x, místo atributu uveďte anotaci `/** @filter */`. +Více informací najdete v části [Vytváření vlastních filtrů |custom-filters] a [Funkcí |custom-functions]. -Zavaděč filtrů .{data-version:2.10} ------------------------------------ +Robustní způsob: Latte Extension .{toc: Latte Extension} +======================================================== -Místo registrace jednotlivých filtrů lze vytvořit tzv. zavaděč, což je funkce, které se zavolá s názvem filtru jako argumentem a vrátí jeho PHP callable, nebo null. +Zatímco přímá registrace je jednoduchá, standardním a doporučeným způsobem, jak zabalit a distribuovat rozšíření Latte, je prostřednictvím tříd **Extension**. Extension slouží jako centrální konfigurační bod pro registraci více tagů, filtrů, funkcí, kompilačních průchodů a dalších prvků. -```php -$latte->addFilterLoader([new Filters, 'load']); +Proč používat Extensions? - -class Filters -{ - public function load(string $filter): ?callable - { - if (in_array($filter, get_class_methods($this))) { - return [$this, $filter]; - } - return null; - } - - public function shortify($s, $len = 10) - { - return mb_substr($s, 0, $len); - } - - // ... -} -``` +- **Organizace:** Udržuje související rozšíření (tagy, filtry atd. pro konkrétní funkci) pohromadě v jedné třídě. +- **Znovupoužitelnost a sdílení:** Snadno zabalíte svá rozšíření pro použití v jiných projektech nebo pro sdílení s komunitou (např. přes Composer). +- **Plná síla:** Vlastní tagy a kompilační průchody *lze registrovat pouze* prostřednictvím Extensions. -Kontextové filtry ------------------ +Registrace Extension +-------------------- -Kontextový filtr je takový, který v prvním parametru přijímá objekt [api:Latte\Runtime\FilterInfo] a teprve po něm následují další parametry jako u klasických filtrů. Registruje se stejný způsobem, Latte samo rozpozná, že filtr je kontextový: +Extension se registruje v Latte pomocí metody `addExtension()` (nebo prostřednictvím [konfiguračního souboru |application:configuration#Šablony Latte]): ```php -use Latte\Runtime\FilterInfo; - -$latte->addFilter('foo', function (FilterInfo $info, string $str): string { - // ... -}); -``` - -Kontextové filtry mohou zjišťovat a měnit content-type, který obdrží v proměnné `$info->contentType`. Pokud se filtr volá klasicky nad proměnnou (např. `{$var|foo}`), bude `$info->contentType` obsahovat null. - -Filtr by měl nejprve ověřit, zda content-type vstupního řetězce podporuje. A může ho také změnit. Příklad filtru, který přijímá text (nebo null) a vrací HTML: - -```php -use Latte\Runtime\FilterInfo; - -$latte->addFilter('money', function (FilterInfo $info, float $amount): string { - // nejprve oveříme, zda je vstupem content-type text - if (!in_array($info->contentType, [null, ContentType::Text])) { - throw new Exception("Filter |money used in incompatible content type $info->contentType."); - } - - // změníme content-type na HTML - $info->contentType = ContentType::Html; - return "$num Kč"; -}); +$latte = new Latte\Engine; +$latte->addExtension(new MyProjectExtension); ``` -.[note] -Filtr musí v takovém případě zajistit správné escapování dat. +Pokud zaregistrujete více rozšíření a ta definují stejně pojmenované tagy, filtry nebo funkce, má přednost naposledy přidané rozšíření. To také znamená, že vaše rozšíření mohou přepsat nativní tagy/filtry/funkce. -Všechny filtry, které se používají nad [bloky|tags#block] (např. jako `{block|foo}...{/block}`), musí být kontextové. +Kdykoli provedete změnu ve třídě a není vypnuté automatické obnovení, Latte automaticky překompiluje vaše šablony. -Funkce .{data-version:2.6} -========================== +Vytvoření Extension +------------------- -V Latte lze standardně používat všechny nativní funkce z PHP, pokud to nezakáže sandbox. Ale zároveň si můžete definovat funkce vlastní. Mohou přepsat funkce nativní. +Pro vytvoření vlastního rozšíření potřebujete vytvořit třídu, která dědí z [api:Latte\Extension]. Pro představu, jak takové rozšíření vypadá, podívejte se na vestavěné "CoreExtension":https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php. -Funkci vytvoříme zaregistrováním jejího názvu a libovolného PHP callable: +Podívejme se na metody, které můžete implementovat: -```php -$latte = new Latte\Engine; -$latte->addFunction('random', function (...$args) { - return $args[array_rand($args)]; -}); -``` -Použití je pak stejné, jako když voláte PHP funkci: +beforeCompile(Latte\Engine $engine): void .[method] +--------------------------------------------------- -```latte -{random(jablko, pomeranč, citron)} // vypíše například: jablko -``` +Volá se před kompilací šablony. Metodu lze použít například pro inicializace související s kompilací. -Funkce pomocí třídy -------------------- +getTags(): array .[method] +-------------------------- -Druhým způsobem definice funkce je [využití třídy|develop#Parametry jako třída]. Vytvoříme metodu s atributem `TemplateFunction`: +Volá se při kompilaci šablony. Vrací asociativní pole *název tagu => volatelný objekt*, což jsou funkce pro parsování tagů. [Více informací |custom-tags]. ```php -class TemplateParameters +public function getTags(): array { - public function __construct( - // parameters - ) {} - - #[Latte\Attributes\TemplateFunction] - public function random(...$args) - { - return $args[array_rand($args)]; - } + return [ + 'foo' => FooNode::create(...), + 'bar' => BarNode::create(...), + 'n:baz' => NBazNode::create(...), + // ... + ]; } - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); ``` -Pokud používáte PHP 7.x a Latte 2.x, místo atributu uveďte anotaci `/** @function */`. +Tag `n:baz` představuje čistý [n:atribut |syntax#n:atributy], tedy tag, který lze zapsat pouze jako atribut. +U tagů `foo` a `bar` Latte automaticky rozpozná, zda jde o párové tagy, a pokud ano, lze je automaticky zapisovat pomocí n:atributů, včetně variant s předponami `n:inner-foo` a `n:tag-foo`. -Loadery -======= +Pořadí vykonávání takových n:atributů je určeno jejich pořadím v poli vráceném metodou `getTags()`. Takže `n:foo` je vždy proveden před `n:bar`, i když jsou atributy v HTML tagu uvedeny v opačném pořadí jako `
                          `. -Loadery jsou zodpovědné za načítání šablon ze zdroje, například ze souborového systému. Nastaví se metodou `setLoader()`: +Pokud potřebujete určit pořadí n:atributů napříč více rozšířeními, použijte pomocnou metodu `order()`, kde parametr `before` xor `after` určuje, které tagy jsou řazeny před nebo za tagem. ```php -$latte->setLoader(new MyLoader); +public function getTags(): array +{ + return [ + 'foo' => self::order(FooNode::create(...), before: 'bar'), + 'bar' => self::order(BarNode::create(...), after: ['block', 'snippet']), + ]; +} ``` -Vestavěné loadery jsou tyto: - -FileLoader ----------- +getPasses(): array .[method] +---------------------------- -Výchozí loader. Načítá šablony ze souborového systému. +Volá se při kompilaci šablony. Vrací asociativní pole *název průchodu => volatelný objekt*, což jsou funkce představující tzv. [kompilační průchody |compiler-passes], které procházejí a upravují AST. -Přístup k souborům je možné omezit nastavením základního adresáře: +I zde lze použít pomocnou metodu `order()`. Hodnota parametrů `before` nebo `after` může být `*` s významem před/po všech. ```php -$latte->setLoader(new Latte\Loaders\FileLoader($templateDir)); -$latte->render('test.latte'); +public function getPasses(): array +{ + return [ + 'optimize' => Passes::optimizePass(...), + 'sandbox' => self::order($this->sandboxPass(...), before: '*'), + // ... + ]; +} ``` -StringLoader ------------- +beforeRender(Latte\Engine $engine): void .[method] +-------------------------------------------------- -Načítá šablony z řetězců. Tento loader je velmi užitečný pro testování. Lze jej také použít pro malé projekty, kde může mít smysl ukládat všechny šablony do jediného souboru PHP. +Volá se před každým vykreslením šablony. Metoda může být použita například k inicializaci proměnných používaných během vykreslování. -```php -$latte->setLoader(new Latte\Loaders\StringLoader([ - 'main.file' => '{include other.file}', - 'other.file' => '{if true} {$var} {/if}', -])); -$latte->render('main.file'); -``` +getFilters(): array .[method] +----------------------------- -Zjednodušené použití: +Volá se před vykreslením šablony. Vrací filtry jako asociativní pole *název filtru => volatelný objekt*. [Více informací |custom-filters]. ```php -$template = '{if true} {$var} {/if}'; -$latte->setLoader(new Latte\Loaders\StringLoader); -$latte->render($template); +public function getFilters(): array +{ + return [ + 'batch' => $this->batchFilter(...), + 'trim' => $this->trimFilter(...), + // ... + ]; +} ``` -Vytvoření vlastního loaderu ---------------------------- - -Loader je třída, která implementuje rozhraní [api:Latte\Loader]. +getFunctions(): array .[method] +------------------------------- +Volá se před vykreslením šablony. Vrací funkce jako asociativní pole *název funkce => volatelný objekt*. [Více informací |custom-functions]. -Tagy (makra) -============ +```php +public function getFunctions(): array +{ + return [ + 'clamp' => $this->clampFunction(...), + 'divisibleBy' => $this->divisibleByFunction(...), + // ... + ]; +} +``` -Jednou z nejzajímavějších funkcí šablonovacího jádra je možnost definovat nové jazykové konstrukce pomocí značek. Je to také složitější funkčnost a je třeba pochopit, jak Latte interně funguje. -Většinou však značka není potřeba: -- pokud by měla generovat nějaký výstup, použijte místo ní [funkci|#funkce] -- pokud měla upravovat nějaký vstup a vracet ho, použijte místo toho [filtr|#filtry] -- pokud měla upravovat oblast textu, obalte jej značkou [`{block}`|tags#block] a použijte [filtr|#Kontextové filtry] -- pokud neměla nic vypisovat, ale pouze volat funkci, zavolejte ji pomocí [`{do}`|tags#do] +getProviders(): array .[method] +------------------------------- -Pokud přesto chcete vytvořit tag, skvěle! Vše podstatné najdete v kapitole [Vytváříme Extension|creating-extension] (týká se Latte 3.x; viz také [podrobný návod pro Latte 2.x|http://css.nothrem.cz/latte-makra/]). +Volá se před vykreslením šablony. Vrací pole poskytovatelů, což jsou obvykle objekty, které používají tagy za běhu. Přistupuje se k nim přes `$this->global->...`. [Více informací |custom-tags#Představení poskytovatelů]. +```php +public function getProviders(): array +{ + return [ + 'myFoo' => $this->foo, + 'myBar' => $this->bar, + // ... + ]; +} +``` -Průchody kompilátoru .{data-version:3.0} -======================================== -Průchody kompilátoru jsou funkce, které modifikují AST nebo sbírají v nich informace. V Latte je takto implementován například sandbox: projde všechny uzly AST, najde volání funkcí a metod a nahradí je za jím kontrolované volání. +getCacheKey(Latte\Engine $engine): mixed .[method] +-------------------------------------------------- -Stejně jako v případě značek se jedná o složitější funkcionalitu a je potřeba pochopit, jak funguje Latte pod kapotou. Vše podstatné najdete v kapitole [Vytváříme Extension|creating-extension]. +Volá se před vykreslením šablony. Návratová hodnota se stává součástí klíče, jehož hash je obsažen v názvu souboru kompilované šablony. Pro různé návratové hodnoty tedy Latte vygeneruje různé cache soubory. diff --git a/latte/cs/filters.texy b/latte/cs/filters.texy index 06db84b6c6..2662e38000 100644 --- a/latte/cs/filters.texy +++ b/latte/cs/filters.texy @@ -10,15 +10,19 @@ V šablonách můžeme používat funkce, které pomáhají upravit nebo přefor | `breakLines` | [Před konce řádku přidá HTML odřádkování |#breakLines] | `bytes` | [formátuje velikost v bajtech |#bytes] | `clamp` | [ohraničí hodnotu do daného rozsahu |#clamp] -| `dataStream` | [konverze pro Data URI protokol |#datastream] -| `date` | [formátuje datum |#date] +| `column` | [extrahuje jeden sloupec z pole |#column] +| `commas` | [spojí pole čárkami |#commas] +| `dataStream` | [konverze pro Data URI protokol |#dataStream] +| `date` | [formátuje datum a čas |#date] | `explode` | [rozdělí řetězec na pole podle oddělovače |#explode] | `first` | [vrací první prvek pole nebo znak řetězce |#first] +| `group` | [seskupí data podle různých kritérií |#group] | `implode` | [spojí pole do řetězce |#implode] | `indent` | [odsadí text zleva o daný počet tabulátorů |#indent] | `join` | [spojí pole do řetězce |#implode] | `last` | [vrací poslední prvek pole nebo znak řetězce |#last] | `length` | [vrací délku řetězce ve znacích nebo pole |#length] +| `localDate` | [formátuje datum a čas podle národního prostředí |#localDate] | `number` | [formátuje číslo |#number] | `padLeft` | [doplní řetězec zleva na požadovanou délku |#padLeft] | `padRight` | [doplní řetězec zprava na požadovanou délku |#padRight] @@ -42,6 +46,7 @@ V šablonách můžeme používat funkce, které pomáhají upravit nebo přefor .[table-latte-filters] |## Velikost písmen | `capitalize` | [malá písmena, první písmeno ve slovech velké |#capitalize] +| `firstLower` | [převede první písmeno na malé |#firstLower] | `firstUpper` | [převede první písmeno na velké |#firstUpper] | `lower` | [převede na malá písmena |#lower] | `upper` | [převede na velká písmena |#upper] @@ -52,13 +57,18 @@ V šablonách můžeme používat funkce, které pomáhají upravit nebo přefor | `floor` | [zaokrouhlí číslo dolů na danou přesnost |#floor] | `round` | [zaokrouhlí číslo na danou přesnost |#round] +.[table-latte-filters] +|## HTML atributy +| `accept` | [potvrzuje nové chování chytrých atributů |#accept] +| `toggle` | [přepíná přítomnost HTML atributu |#toggle] + .[table-latte-filters] |## Escapování | `escapeUrl` | [escapuje parametr v URL |#escapeUrl] | `noescape` | [vypíše proměnnou bez escapování |#noescape] | `query` | [generuje query string v URL |#query] -Dále existují escapovací filtry pro HTML (`escapeHtml` a `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) a iCalendar (`escapeICal`), které Latte používá samo díky [kontextově sensitivnímu escapování|safety-first#Kontextově sensitivní escapování] a nemusíte je zapisovat. +Dále existují escapovací filtry pro HTML (`escapeHtml` a `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) a iCalendar (`escapeICal`), které Latte používá samo díky [kontextově sensitivnímu escapování |safety-first#Kontextově sensitivní escapování] a nemusíte je zapisovat. .[table-latte-filters] |## Bezpečnost @@ -99,7 +109,7 @@ Filtry lze aplikovat i na výraz: {var $name = ($title|upper) . ($subtitle|lower)} ``` -[Vlastní filtry|extending-latte#filtry] lze registrovat tímto způsobem: +[Vlastní filtry|custom-filters] lze registrovat tímto způsobem: ```php $latte = new Latte\Engine; @@ -114,12 +124,33 @@ V šabloně se potom volá takto: ``` +Nullsafe filtry .{data-version:3.1} +----------------------------------- + +Jakýkoliv filtr lze učinit nullsafe použitím `?|` místo `|`. Pokud je hodnota `null`, filtr se nevykoná a vrátí se `null`. Filtry následující v řetězci jsou také přeskočeny. + +To je užitečné v kombinaci s HTML atributy, které jsou vynechány, pokud je hodnota `null`. + +```latte +
                          +{* Pokud je $title null:
                          *} +{* Pokud je $title 'hello':
                          *} +``` + + Filtry ====== -batch(int length, mixed item): array .[filter]{data-version:2.7} ----------------------------------------------------------------- +accept .[filter]{data-version:3.1} +---------------------------------- +Filtr se používá při [migraci z Latte 3.0|cookbook/migration-from-latte-30] k potvrzení, že jste zkontrolovali změnu chování atributu a akceptujete ji. Nemění hodnotu. + +Jde o dočasný nástroj. Jakmile je migrace dokončena a varování při migraci jsou vypnuta, měli byste tento filtr ze svých šablon odstranit. + + +batch(int $length, mixed $item): array .[filter] +------------------------------------------------ Filtr, který zjednodušuje výpis lineárních dat do podoby tabulky. Vrací pole polí se zadaným počtem položek. Pokud zadáte druhý parametr, použije se k doplnění chybějících položek na posledním řádku. ```latte @@ -152,6 +183,8 @@ Vypíše: ``` +Viz také [#group] a značka [iterateWhile |tags#iterateWhile]. + breakLines .[filter] -------------------- @@ -163,18 +196,18 @@ Přidává před každý znak nového řádku HTML značku `
                          ` ``` -bytes(int precision = 2) .[filter] ----------------------------------- -Formátuje velikost v bajtech do lidsky čitelné podoby. +bytes(int $precision=2) .[filter] +--------------------------------- +Formátuje velikost v bajtech do lidsky čitelné podoby. Pokud je nastavené [národní prostředí |develop#Locale], použijí se odpovídající oddělovače desetinných míst a tisíců. ```latte -{$size|bytes} 0 B, 10 B, … -{$size|bytes:2} 1.25 GB, … +{$size|bytes} 0 B, 1.25 GB, … +{$size|bytes:0} 10 B, 1 GB, … ``` -ceil(int precision = 0) .[filter] ---------------------------------- +ceil(int $precision=0) .[filter] +-------------------------------- Zaokrouhlí číslo nahoru na danou přesnost. ```latte @@ -194,7 +227,7 @@ Slova budou začínat velkými písmeny, všechny zbývající znaky budou malá {='i like LATTE'|capitalize} {* vypíše 'I Like Latte' *} ``` -Viz také [#firstUpper], [#lower], [#upper]. +Viz také [#firstLower], [#firstUpper], [#lower], [#upper]. checkUrl .[filter] @@ -203,8 +236,8 @@ Vynutí ošetření URL adresy. Kontroluje, zda proměnná obsahuje webovou URL ```latte {var $link = 'javascript:window.close()'} -kontrolované -nekontrolované +kontrolované +nekontrolované ``` Vypíše: @@ -217,25 +250,69 @@ Vypíše: Viz také [#nocheck]. -clamp(int|float min, int|float max) .[filter]{data-version:2.9} ---------------------------------------------------------------- +clamp(int|float $min, int|float $max) .[filter] +----------------------------------------------- Ohraničí hodnotu do daného inkluzivního rozsahu min a max. ```latte {$level|clamp: 0, 255} ``` -Existuje také jako [funkce|functions#clamp]. +Existuje také jako [funkce |functions#clamp]. + + +column(string|int|null $columnKey, string|int|null $indexKey=null) .[filter]{data-version:3.1.2} +------------------------------------------------------------------------------------------------ +Vrátí z vícerozměrného pole všechny hodnoty konkrétního sloupce `$columnKey` a vrátí je jako nové jednoduché pole. Stejně tak lze filtr použít na pole objektů, ze kterých získá hodnoty properties. + +```latte +{var $users = [ + [id: 30, name: 'John', age: 30], + [id: 32, name: 'Jane', age: 25], + [id: 33, age: 35], +]} + +{$users|column: 'name'} +{* vrátí ['John', 'Jane'] *} + +{$users|column: 'name', 'id'} +{* vrátí [30 => 'John', 32 => 'Jane'] *} +``` + +Pokud předáte `null` jako klíč sloupce, přeindexuje pole podle `$indexKey`. + +commas(?string $lastGlue=null) .[filter]{data-version:3.1.2} +------------------------------------------------------------ +Spojí prvky pole čárkou a mezerou (`', '`). Jedná se o pohodlnou zkratku pro běžný případ výpisu položek v čitelné podobě. + +```latte +{var $items = ['jablka', 'pomeranče', 'banány']} +{$items|commas} +{* vypíše 'jablka, pomeranče, banány' *} +``` + +Volitelně můžete zadat vlastní oddělovač pro poslední dvojici položek: + +```latte +{$items|commas: ' a '} +{* vypíše 'jablka, pomeranče a banány' *} -dataStream(string mimetype = detect) .[filter] ----------------------------------------------- +{=['PHP', 'JavaScript', 'Python']|commas: ', nebo '} +{* vypíše 'PHP, JavaScript, nebo Python' *} +``` + +Viz také [#implode]. + + +dataStream(string $mimetype=detect) .[filter] +--------------------------------------------- Konvertuje obsah do data URI scheme. Pomocí něj lze do HTML nebo CSS vkládat obrázky bez nutnosti linkovat externí soubory. Mějme v proměnné obrázek `$img = Image::fromFile('obrazek.gif')`, poté ```latte - + ``` Vypíše například: @@ -250,15 +327,16 @@ AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO Vyžaduje PHP rozšíření `fileinfo`. -date(string format) .[filter] ------------------------------ -Formátuje datum podle masky buď ve tvaru používaném PHP funkcí [php:strftime] nebo [php:date]. Filtr přijímá datum buď ve formátu UNIX timestamp, v podobě řetězce nebo jako objekt `DateTime`. +date(string $format) .[filter] +------------------------------ +Formátuje datum a čas podle masky používané PHP funkcí [php:date]. Filtr přijímá datum ve formátu UNIX timestamp, jako řetězec nebo objekt typu `DateTimeInterface`. ```latte -{$today|date:'%d.%m.%Y'} {$today|date:'j. n. Y'} ``` +Viz také [#localDate]. + escapeUrl .[filter] ------------------- @@ -271,8 +349,8 @@ Escapuje proměnnou pro použití jakožto parametru v URL. Viz také [#query]. -explode(string separator = '') .[filter]{data-version:2.10.2} -------------------------------------------------------------- +explode(string $separator='') .[filter] +--------------------------------------- Rozdělí řetězec na pole podle oddělovače. Alias pro `split`. ```latte @@ -294,8 +372,8 @@ Můžete také použít alias `split`: Viz také [#implode]. -first .[filter]{data-version:2.10.2} ------------------------------------- +first .[filter] +--------------- Vrací první prvek pole nebo znak řetězce: ```latte @@ -306,8 +384,8 @@ Vrací první prvek pole nebo znak řetězce: Viz také [#last], [#random]. -floor(int precision = 0) .[filter] ----------------------------------- +floor(int $precision=0) .[filter] +--------------------------------- Zaokrouhlí číslo dolů na danou přesnost. ```latte @@ -319,6 +397,17 @@ Zaokrouhlí číslo dolů na danou přesnost. Viz také [#ceil], [#round]. +firstLower .[filter]{data-version:3.0.22} +----------------------------------------- +Převede první písmeno na malé. Vyžaduje PHP rozšíření `mbstring`. + +```latte +{='The Latte'|firstLower} {* vypíše 'the Latte' *} +``` + +Viz také [#capitalize], [#firstUpper], [#lower], [#upper]. + + firstUpper .[filter] -------------------- Převede první písmeno na velká. Vyžaduje PHP rozšíření `mbstring`. @@ -327,11 +416,30 @@ Převede první písmeno na velká. Vyžaduje PHP rozšíření `mbstring`. {='the latte'|firstUpper} {* vypíše 'The latte' *} ``` -Viz také [#capitalize], [#lower], [#upper]. +Viz také [#capitalize], [#firstLower], [#lower], [#upper]. -implode(string glue = '') .[filter] ------------------------------------ +group(string|int|\Closure $by): array .[filter]{data-version:3.0.16} +-------------------------------------------------------------------- +Filtr seskupí data podle různých kritérií. + +V tomto příkladu se řádky v tabulce seskupují podle sloupce `categoryId`. Výstupem je pole polí, kde klíčem je hodnota ve sloupci `categoryId`. [Přečtěte si podrobný návod|cookbook/grouping]. + +```latte +{foreach ($items|group: categoryId) as $categoryId => $categoryItems} +
                            + {foreach $categoryItems as $item} +
                          • {$item->name}
                          • + {/foreach} +
                          +{/foreach} +``` + +Viz také [#batch], funkce [group |functions#group] a značka [iterateWhile |tags#iterateWhile]. + + +implode(string $glue='') .[filter] +---------------------------------- Vrátí řetězec, který je zřetězením položek sekvence. Alias pro `join`. ```latte @@ -339,15 +447,17 @@ Vrátí řetězec, který je zřetězením položek sekvence. Alias pro `join`. {=[1, 2, 3]|implode:'|'} {* vypíše '1|2|3' *} ``` -Můžete také použít alias `join`: .{data-version:2.10.2} +Můžete také použít alias `join`: ```latte {=[1, 2, 3]|join} {* vypíše '123' *} ``` +Viz také [#commas], [#explode]. -indent(int level = 1, string char = "\t") .[filter] ---------------------------------------------------- + +indent(int $level=1, string $char="\t") .[filter] +------------------------------------------------- Odsadí text zleva o daný počet tabulátorů nebo jiných znaků, které můžeme uvést ve druhém argumentu. Prázdné řádky nejsou odsazeny. ```latte @@ -367,8 +477,8 @@ Vypíše: ``` -last .[filter]{data-version:2.10.2} ------------------------------------ +last .[filter] +-------------- Vrací poslední prvek pole nebo znak řetězce: ```latte @@ -396,6 +506,68 @@ Vrátí délku řetězce nebo pole. ``` +localDate(?string $format=null, ?string $date=null, ?string $time=null) .[filter] +--------------------------------------------------------------------------------- +Formátuje datum a čas podle [národního prostředí |develop#Locale], což zajišťuje konzistentní a lokalizované zobrazení časových údajů napříč různými jazyky a regiony. Filtr přijímá datum jako UNIX timestamp, řetězec nebo objekt typu `DateTimeInterface`. + +```latte +{$date|localDate} {* 15. dubna 2024 *} +{$date|localDate: format: yM} {* 4/2024 *} +{$date|localDate: date: medium} {* 15. 4. 2024 *} +``` + +Pokud použijete filtr bez parametrů, vypíše se datum v úrovní `long`, viz dále. + +**a) použití formátu** + +Parametr `format` popisuje, které časové složky se mají zobrazit. Používá pro ně písmenné kódy, jejichž počet opakování ovlivňuje šířku výstupu: + +| rok | `y` / `yy` / `yyyy` | `2024` / `24` / `2024` +| měsíc | `M` / `MM` / `MMM` / `MMMM` | `8` / `08` / `srp` / `srpen` +| den | `d` / `dd` / `E` / `EEEE` | `1` / `01` / `ne` / `neděle` +| hodina | `j` / `H` / `h` | preferovaný / 24hodinový / 12hodinový +| minuta | `m` / `mm` | `5` / `05` (2 číslice v kombinaci se sekundami) +| sekunda | `s` / `ss` | `8` / `08` (2 číslice v kombinaci s minutami) + +Na pořadí kódů ve formátu nezáleží, protože pořadí složek se vypíše podle zvyklostí národního prostředí. Formát je tedy na něm nezávislý. Například formát `yyyyMMMMd` v postředí `en_US` vypíše `April 15, 2024`, zatímco v prostředí `cs_CZ` vypíše `15. dubna 2024`: + +| locale: | cs_CZ | en_US +|--- +| `format: 'dMy'` | 10. 8. 2024 | 8/10/2024 +| `format: 'yM'` | 8/2024 | 8/2024 +| `format: 'yyyyMMMM'` | srpen 2024 | August 2024 +| `format: 'MMMM'` | srpen | August +| `format: 'jm'` | 17:22 | 5:22 PM +| `format: 'Hm'` | 17:22 | 17:22 +| `format: 'hm'` | 5:22 odp. | 5:22 PM + + +**b) použití přednastavených stylů** + +Parametry `date` a `time` určují, jak podrobně se má datum a čas vypsat. Můžete si vybrat z několika úrovní: `full`, `long`, `medium`, `short`. Lze nechat vypsat jen datum, jen čas, nebo obojí: + +| locale: | cs_CZ | en_US +|--- +| `date: short` | 23.01.78 | 1/23/78 +| `date: medium` | 23. 1. 1978 | Jan 23, 1978 +| `date: long` | 23. ledna 1978 | January 23, 1978 +| `date: full` | pondělí 23. ledna 1978 | Monday, January 23, 1978 +| `time: short` | 8:30 | 8:30 AM +| `time: medium` | 8:30:59 | 8:30:59 AM +| `time: long` | 8:30:59 SEČ | 8:30:59 AM GMT+1 +| `date: short, time: short` | 23.01.78 8:30 | 1/23/78, 8:30 AM +| `date: medium, time: short` | 23. 1. 1978 8:30 | Jan 23, 1978, 8:30 AM +| `date: long, time: short` | 23. ledna 1978 v 8:30 | January 23, 1978 at 8:30 AM + +U data lze navíc použít prefix `relative-` (např. `relative-short`), který pro data blízká současnosti zobrazí `včera`, `dnes` nebo `zítra`, jinak se vypíše standardním způsobem. + +```latte +{$date|localDate: date: relative-short} {* včera *} +``` + +Viz také [#date]. + + lower .[filter] --------------- Převede řetězec na malá písmena. Vyžaduje PHP rozšíření `mbstring`. @@ -404,20 +576,20 @@ Převede řetězec na malá písmena. Vyžaduje PHP rozšíření `mbstring`. {='LATTE'|lower} {* vypíše 'latte' *} ``` -Viz také [#capitalize], [#firstUpper], [#upper]. +Viz také [#capitalize], [#firstLower], [#firstUpper], [#upper]. nocheck .[filter] ----------------- -Předejde automatickému ošetření URL adresy. Latte [automaticky kontroluje|safety-first#Kontrola odkazů], zda proměnná obsahuje webovou URL (tj. protokol HTTP/HTTPS) a předchází vypsání odkazů, které mohou představovat bezpečnostní riziko. +Předejde automatickému ošetření URL adresy. Latte [automaticky kontroluje |safety-first#Kontrola odkazů], zda proměnná obsahuje webovou URL (tj. protokol HTTP/HTTPS) a předchází vypsání odkazů, které mohou představovat bezpečnostní riziko. Pokud odkaz používá jiné schéma, např. `javascript:` nebo `data:`, a jste si jistí jeho obsahem, můžete kontrolu vypnout pomoci `|nocheck`. ```latte {var $link = 'javascript:window.close()'} -kontrolované -nekontrolované +kontrolované +nekontrolované ``` Vypíše: @@ -451,19 +623,67 @@ Neescapovaný: hello Špatné použití filtru `noescape` může vést ke vzniku zranitelnosti XSS! Nikdy jej nepoužívejte, pokud si nejste **zcela jisti** co děláte, a že vypisovaný řetězec pochází z důvěryhodného zdroje. -number(int decimals = 0, string decPoint = '.', string thousandsSep = ',') .[filter] ------------------------------------------------------------------------------------- -Formátuje číslo na určitý počet desetinných míst. Lze určit znak pro desetinnou čárku a oddělovač tisíců. +number(int $decimals=0, string $decPoint='.', string $thousandsSep=',') .[filter] +--------------------------------------------------------------------------------- +Formátuje číslo na určitý počet desetinných míst. Pokud je nastavené [národní prostředí |develop#Locale], použijí se odpovídající oddělovače desetinných míst a tisíců. ```latte -{1234.20 |number} 1,234 -{1234.20 |number:1} 1,234.2 -{1234.20 |number:2} 1,234.20 -{1234.20 |number:2, ',', ' '} 1 234,20 +{1234.20|number} 1,234 +{1234.20|number:1} 1,234.2 +{1234.20|number:2} 1,234.20 +{1234.20|number:2, ',', ' '} 1 234,20 ``` -padLeft(int length, string pad = ' ') .[filter] +number(string $format) .[filter] +-------------------------------- +Parametr `format` umožňuje definovat vzhled čísel přesně podle vašich potřeb. K tomu je potřeba mít nastavené [národní prostředí |develop#Locale]. Formát se skládá z několika speciálních znaků, jejichž kompletní popis najdete v dokumentaci "DecimalFormat":https://unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns: + +- `0` povinná číslice, vždy se zobrazí, i kdyby to byla nula +- `#` volitelná číslice, zobrazí se jen tehdy, pokud na tomto místě číslo skutečně je +- `@` významná číslice, pomáhá zobrazit číslo s určitým počtem platných číslic +- `.` označuje, kde má být desetinná čárka (nebo tečka, podle země) +- `,` slouží k oddělení skupin číslic, nejčastěji tisíců +- `%` číslo vynásobí 100× a přidá znak procenta + +Pojďme se podívat na příklady. V prvním příkladu jsou dvě desetinná místa povinná, ve druhém volitelná. Třetí příklad ukazuje doplnění nulami zleva i zprava, čtvrtý zobrazuje jen existující číslice: + +```latte +{1234.5|number: '#,##0.00'} {* 1,234.50 *} +{1234.5|number: '#,##0.##'} {* 1,234.5 *} +{1.23 |number: '000.000'} {* 001.230 *} +{1.2 |number: '##.##'} {* 1.2 *} +``` + +Významné číslice určují, kolik číslic bez ohledu na desetinou čárku má být zobrazeno, přičemž se zaokrouhluje: + +```latte +{1234|number: '@@'} {* 1200 *} +{1234|number: '@@@'} {* 1230 *} +{1234|number: '@@@#'} {* 1234 *} +{1.2345|number: '@@@'} {* 1.23 *} +{0.00123|number: '@@'} {* 0.0012 *} +``` + +Snadný způsob, jak zobrazit číslo jako procenta. Číslo se vynásobí 100× a přidá se znak `%`: + +```latte +{0.1234|number: '#.##%'} {* 12.34% *} +``` + +Můžeme definovat odlišný formát pro kladná a záporná čísla, odděluje je znak `;`. Tímto způsobem lze například nastavit, že kladná čísla se mají zobrazovat se znaménkem `+`: + +```latte +{42|number: '#.##;(#.##)'} {* 42 *} +{-42|number: '#.##;(#.##)'} {* (42) *} +{42|number: '+#.##;-#.##'} {* +42 *} +{-42|number: '+#.##;-#.##'} {* -42 *} +``` + +Pamatujte, že skutečný vzhled čísel se může lišit podle nastavení země. Například v některých zemích se používá čárka místo tečky jako oddělovač desetinných míst. Tento filtr to automaticky zohlední a nemusíte se o nic starat. + + +padLeft(int $length, string $pad=' ') .[filter] ----------------------------------------------- Doplní řetězec do určité délky jiným řetězcem zleva. @@ -472,7 +692,7 @@ Doplní řetězec do určité délky jiným řetězcem zleva. ``` -padRight(int length, string pad = ' ') .[filter] +padRight(int $length, string $pad=' ') .[filter] ------------------------------------------------ Doplní řetězec do určité délky jiným řetězcem zprava. @@ -481,8 +701,8 @@ Doplní řetězec do určité délky jiným řetězcem zprava. ``` -query .[filter]{data-version:2.10} ----------------------------------- +query .[filter] +--------------- Dynamicky generuje query string v URL: ```latte @@ -502,8 +722,8 @@ Klíče s hodnotou `null` se vynechají. Viz také [#escapeUrl]. -random .[filter]{data-version:2.10.2} -------------------------------------- +random .[filter] +---------------- Vrací náhodný prvek pole nebo znak řetězce: ```latte @@ -514,8 +734,8 @@ Vrací náhodný prvek pole nebo znak řetězce: Viz také [#first], [#last]. -repeat(int count) .[filter] ---------------------------- +repeat(int $count) .[filter] +---------------------------- Opakuje řetězec x-krát. ```latte @@ -523,7 +743,7 @@ Opakuje řetězec x-krát. ``` -replace(string|array search, string replace = '') .[filter] +replace(string|array $search, string $replace='') .[filter] ----------------------------------------------------------- Nahradí všechny výskyty vyhledávacího řetězce náhradním řetězcem. @@ -531,14 +751,14 @@ Nahradí všechny výskyty vyhledávacího řetězce náhradním řetězcem. {='hello world'|replace: 'world', 'friend'} {* vypíše 'hello friend' *} ``` -Lze provést i více záměn najednou: .{data-version:2.10.2} +Lze provést i více záměn najednou: ```latte {='hello world'|replace: [h => l, l => h]} {* vypíše 'lehho worhd' *} ``` -replaceRE(string pattern, string replace = '') .[filter] +replaceRE(string $pattern, string $replace='') .[filter] -------------------------------------------------------- Provede vyhledávání regulárních výrazů s nahrazením. @@ -559,8 +779,8 @@ Obrátí daný řetězec nebo pole. ``` -round(int precision = 0) .[filter] ----------------------------------- +round(int $precision=0) .[filter] +--------------------------------- Zaokrouhlí číslo na danou přesnost. ```latte @@ -573,8 +793,8 @@ Zaokrouhlí číslo na danou přesnost. Viz také [#ceil], [#floor]. -slice(int start, int length = null, bool preserveKeys = false) .[filter]{data-version:2.10.2} ---------------------------------------------------------------------------------------------- +slice(int $start, ?int $length=null, bool $preserveKeys=false) .[filter] +------------------------------------------------------------------------ Extrahuje část pole nebo řetězce. ```latte @@ -591,9 +811,9 @@ Pokud je zadaný parametr length a je kladný, posloupnost bude obsahovat tolik Ve výchozím nastavení filtr změní pořadí a resetuje celočíselného klíče pole. Toto chování lze změnit nastavením preserveKeys na true. Řetězcové klíče jsou vždy zachovány, bez ohledu na tento parametr. -sort .[filter]{data-version:2.9} --------------------------------- -Filtr, který seřadí pole. Zachovává asociaci s klíčí. +sort(?Closure $comparison, string|int|\Closure|null $by=null, string|int|\Closure|bool $byKey=false) .[filter] +-------------------------------------------------------------------------------------------------------------- +Filtr seřadí prvky pole nebo iterátoru a zachová jejich asociační klíče. Při nastaveném [národním prostředí |develop#Locale] se řazení řídí jeho pravidly, pokud není specifikována vlastní porovnávací funkce. ```latte {foreach ($names|sort) as $name} @@ -609,15 +829,41 @@ Filtr, který seřadí pole. Zachovává asociaci s klíčí. {/foreach} ``` -Jako parametr lze předat vlastní porovnávací funkci: .{data-version:2.10.2} +Můžete specifikovat vlastní porovnávací funkci pro řazení (příklad ukazuje, jak obrátit řazení od největší po nejmenší): + +```latte +{var $reverted = ($names|sort: fn($a, $b) => $b <=> $a)} +``` + +Filtr `|sort` také umožňuje řadit prvky podle klíčů: + +```latte +{foreach ($names|sort: byKey: true) as $name} + ... +{/foreach} +``` + +Pokud potřebujete seřadit tabulku podle konkrétního sloupce, můžete použít parametr `by`. Hodnota `'name'` v ukázce určuje, že se bude řadit podle `$item->name` nebo `$item['name']`, v závislosti na tom, zda je `$item` pole nebo objekt: ```latte -{var $sorted = ($names|sort: fn($a, $b) => $b <=> $a)} +{foreach ($items|sort: by: 'name') as $item} + {$item->name} +{/foreach} ``` +Můžete také definovat callback funkci, která určí hodnotu, podle které se má řadit: -spaceless .[filter]{data-version:2.10.2} ----------------------------------------- +```latte +{foreach ($items|sort: by: fn($items) => $items->category->name) as $item} + {$item->name} +{/foreach} +``` + +Stejným způsobem lze využít i parametr `byKey`. + + +spaceless .[filter] +------------------- Odstraní zbytečné bílé místo (mezery) z výstupu. Můžete také použít alias `strip`. ```latte @@ -646,8 +892,8 @@ Převádí HTML na čistý text. Tedy odstraní z něj HTML značky a HTML entit Výsledný čistý text může přirozeně obsahovat znaky, které představují HTML značky, například `'<p>'|stripHtml` se převede na `

                          `. V žádném případě nevypisujte takto vzniklý text s `|noescape`, protože to může vést ke vzniku bezpečnostní díry. -substr(int offset, int length = null) .[filter] ------------------------------------------------ +substr(int $offset, ?int $length=null) .[filter] +------------------------------------------------ Extrahuje část řetězce. Tento filtr byl nahrazen filtrem [#slice]. ```latte @@ -655,9 +901,24 @@ Extrahuje část řetězce. Tento filtr byl nahrazen filtrem [#slice]. ``` -translate(string message, ...args) .[filter]{data-version:3.0} --------------------------------------------------------------- -Překládá výrazy do jiných jazyků. Aby byl filtr k dispozici, je potřeba [nastavit překladač|develop#TranslatorExtension]. Můžete také použít [tagy pro překlad|tags#Překlady]. +toggle .[filter]{data-version:3.1} +---------------------------------- +Filtr `toggle` ovládá přítomnost atributu na základě boolean hodnoty. Pokud je hodnota truthy, atribut je přítomen; pokud je falsy, atribut je zcela vynechán: + +```latte +

                          +{* Pokud je $isGrid truthy:
                          *} +{* Pokud je $isGrid falsy:
                          *} +``` + +Tento filtr je užitečný pro vlastní atributy nebo atributy JavaScriptových knihoven, které vyžadují kontrolu přítomnosti/nepřítomnosti podobně jako HTML boolean atributy. + +Filtr lze použít pouze uvnitř HTML atributů. + + +translate(...$args) .[filter] +----------------------------- +Překládá výrazy do jiných jazyků. Aby byl filtr k dispozici, je potřeba [nastavit překladač |develop#TranslatorExtension]. Můžete také použít [tagy pro překlad |tags#Překlady]. ```latte {='Košík'|translate} @@ -665,8 +926,8 @@ Překládá výrazy do jiných jazyků. Aby byl filtr k dispozici, je potřeba [ ``` -trim(string charlist = " \t\n\r\0\x0B\u{A0}") .[filter] -------------------------------------------------------- +trim(string $charlist=" \t\n\r\0\x0B\u{A0}") .[filter] +------------------------------------------------------ Odstraní prázdné znaky (nebo jiné znaky) od začátku a konce řetězce. ```latte @@ -675,7 +936,7 @@ Odstraní prázdné znaky (nebo jiné znaky) od začátku a konce řetězce. ``` -truncate(int length, string append = '…') .[filter] +truncate(int $length, string $append='…') .[filter] --------------------------------------------------- Ořízne řetězec na uvedenou maximální délku, přičemž se snaží zachovávat celá slova. Pokud dojde ke zkrácení řetězce, přidá nakonec trojtečku (lze změnit druhým parametrem). @@ -695,7 +956,7 @@ Převede řetězec na velká písmena. Vyžaduje PHP rozšíření `mbstring`. {='latte'|upper} {* vypíše 'LATTE' *} ``` -Viz také [#capitalize], [#firstUpper], [#lower]. +Viz také [#capitalize], [#firstLower], [#firstUpper], [#lower]. webalize .[filter] diff --git a/latte/cs/functions.texy b/latte/cs/functions.texy index 57853a8bde..2931e5583c 100644 --- a/latte/cs/functions.texy +++ b/latte/cs/functions.texy @@ -9,6 +9,9 @@ V šablonách můžeme kromě běžných PHP funkcí používat i tyto další. | `divisibleBy`| [zkontroluje, zda je proměnná dělitelná číslem |#divisibleBy] | `even` | [zkontroluje, zda je dané číslo sudé |#even] | `first` | [vrací první prvek pole nebo znak řetězce |#first] +| `group` | [seskupí data podle různých kritérií |#group] +| `hasBlock` | [zjistí existenci bloku |#hasBlock] +| `hasTemplate`| [zjistí existenci šablony |#hasTemplate] | `last` | [vrací poslední prvek pole nebo znak řetězce |#last] | `odd` | [zkontroluje, zda je dané číslo liché |#odd] | `slice` | [extrahuje část pole nebo řetězce |#slice] @@ -25,7 +28,7 @@ Funkce se používají strejně jaké běžné PHP funkce a lze je použít ve v {if odd($num)} ... {/if} ``` -[Vlastní funkce|extending-latte#funkce] lze registrovat tímto způsobem: +[Vlastní funkce|custom-functions] lze registrovat tímto způsobem: ```php $latte = new Latte\Engine; @@ -44,19 +47,19 @@ Funkce ====== -clamp(int|float $value, int|float $min, int|float $max): int|float .[method]{data-version:2.9} ----------------------------------------------------------------------------------------------- +clamp(int|float $value, int|float $min, int|float $max): int|float .[method] +---------------------------------------------------------------------------- Ohraničí hodnotu do daného inkluzivního rozsahu min a max. ```latte {=clamp($level, 0, 255)} ``` -Viz také [filtr clamp|filters#clamp]. +Viz také [filtr clamp |filters#clamp]. -divisibleBy(int $value, int $by): bool .[method]{data-version:2.10.2} ---------------------------------------------------------------------- +divisibleBy(int $value, int $by): bool .[method] +------------------------------------------------ Zkontroluje, zda je proměnná dělitelná číslem. ```latte @@ -64,8 +67,8 @@ Zkontroluje, zda je proměnná dělitelná číslem. ``` -even(int $value): bool .[method]{data-version:2.10.2} ------------------------------------------------------ +even(int $value): bool .[method] +-------------------------------- Zkontroluje, zda je dané číslo sudé. ```latte @@ -73,8 +76,8 @@ Zkontroluje, zda je dané číslo sudé. ``` -first(string|array $value): mixed .[method]{data-version:2.10.2} ----------------------------------------------------------------- +first(string|iterable $value): mixed .[method] +---------------------------------------------- Vrací první prvek pole nebo znak řetězce: ```latte @@ -82,11 +85,50 @@ Vrací první prvek pole nebo znak řetězce: {=first('abcd')} {* vypíše 'a' *} ``` -Viz také [#last], [filtr first|filters#first]. +Viz také [#last], [filtr first |filters#first]. -last(string|array $value): mixed .[method]{data-version:2.10.2} ---------------------------------------------------------------- +group(iterable $data, string|int|\Closure $by): array .[method]{data-version:3.0.16} +------------------------------------------------------------------------------------ +Funkce seskupí data podle různých kritérií. + +V tomto příkladu se řádky v tabulce seskupují podle sloupce `categoryId`. Výstupem je pole polí, kde klíčem je hodnota ve sloupci `categoryId`. [Přečtěte si podrobný návod|cookbook/grouping]. + +```latte +{foreach group($items, categoryId) as $categoryId => $categoryItems} +
                            + {foreach $categoryItems as $item} +
                          • {$item->name}
                          • + {/foreach} +
                          +{/foreach} +``` + +Viz také filtr [group |filters#group]. + + +hasBlock(string $name): bool .[method]{data-version:3.0.10} +----------------------------------------------------------- +Zjistí, zda blok uvedeného jména existuje: + +```latte +{if hasBlock(header)} ... {/if} +``` + +Viz také [kontrola existence bloků |template-inheritance#Kontrola existence bloků]. + + +hasTemplate(string $name): bool .[method]{data-version:3.0.22} +-------------------------------------------------------------- +Zjistí, zda existuje šablona uvedeného jména: + +```latte +{if hasTemplate('foo.latte')} ... {/if} +``` + + +last(string|array $value): mixed .[method] +------------------------------------------ Vrací poslední prvek pole nebo znak řetězce: ```latte @@ -94,11 +136,11 @@ Vrací poslední prvek pole nebo znak řetězce: {=last('abcd')} {* vypíše 'd' *} ``` -Viz také [#first], [filtr last|filters#last]. +Viz také [#first], [filtr last |filters#last]. -odd(int $value): bool .[method]{data-version:2.10.2} ----------------------------------------------------- +odd(int $value): bool .[method] +------------------------------- Zkontroluje, zda je dané číslo liché. ```latte @@ -106,8 +148,8 @@ Zkontroluje, zda je dané číslo liché. ``` -slice(string|array $value, int $start, int $length=null, bool $preserveKeys=false): string|array .[method]{data-version:2.10.2} -------------------------------------------------------------------------------------------------------------------------------- +slice(string|array $value, int $start, ?int $length=null, bool $preserveKeys=false): string|array .[method] +----------------------------------------------------------------------------------------------------------- Extrahuje část pole nebo řetězce. ```latte diff --git a/latte/cs/guide.texy b/latte/cs/guide.texy index b3ab6f7e87..bbadff4677 100644 --- a/latte/cs/guide.texy +++ b/latte/cs/guide.texy @@ -5,8 +5,7 @@ Začínáme s Latte Šablony zlepšují organizaci kódu, oddělují logiku aplikace od prezentace a zvyšují bezpečnost. Nabízejí daleko lepší funkce a vyjadřovací prostředky pro generování HTML než samotné PHP. -Latte je nejbezpečnějším šablonovacím systémem pro PHP. Jeho intuitivní syntaxi si zamilujete. Široká škála užitečných funkcí vám výrazně usnadní práci. -Poskytuje špičkovou ochranu proti [kritickým zranitelnostem|safety-first] a umožní vám soustředit se na vytváření kvalitních aplikací bez obav o jejich bezpečnost. +Latte je nejbezpečnějším šablonovacím systémem pro PHP. Jeho intuitivní syntaxi si zamilujete. Široká škála užitečných funkcí vám výrazně usnadní práci. Poskytuje špičkovou ochranu proti [kritickým zranitelnostem|safety-first] a umožní vám soustředit se na vytváření kvalitních aplikací bez obav o jejich bezpečnost. Jak psát šablony pomocí Latte? @@ -14,8 +13,8 @@ Jak psát šablony pomocí Latte? Latte je chytře navržen a snadno se ho naučí ti, kteří znají PHP a osvojí si základní značky. -- Začněte tím, že si vyzkoušíte [syntaxi |syntax] Latte [online |https://fiddle.nette.org/latte/] -- Prozkoumejte základní dostupné [značky|tags] a [filtry|filters] +- Nejprve se seznamte se [syntaxí Latte|syntax] a [VYZKOUŠEJTE JI ONLINE |https://fiddle.nette.org/latte/#9cc0cf6d89#9cc0cf6d89] +- Podívejte se na základní sadu [značek|tags] a [filtrů|filters] - Pište šablony v [editoru s podporu Latte |recipes#Editory a IDE] diff --git a/latte/cs/html-attributes.texy b/latte/cs/html-attributes.texy new file mode 100644 index 0000000000..e9632b2586 --- /dev/null +++ b/latte/cs/html-attributes.texy @@ -0,0 +1,151 @@ +Chytré HTML atributy +******************** + +.[perex] +Latte 3.1 přichází se sadou vylepšení, která se zaměřuje na jednu z nejčastějších činností v šablonách – vypisování HTML atributů. Přináší více pohodlí, flexibility a bezpečnosti. + + +Boolean atributy +================ + +HTML používá speciální atributy jako `checked`, `disabled`, `selected` nebo `hidden`, u kterých nezáleží na konkrétní hodnotě – pouze na jejich přítomnosti. Fungují jako jednoduché příznaky. + +Latte je zpracovává automaticky. Atributu můžete předat jakýkoliv výraz. Pokud je pravdivý (truthy), atribut se vykreslí. Pokud je nepravdivý (falsey - např. `false`, `null`, `0` nebo prázdný řetězec), atribut se zcela vynechá. + +To znamená, že se můžete rozloučit se složitými podmínkami nebo `n:attr` a jednoduše použít: + +```latte + +``` + +Pokud `$isDisabled` je `false` a `$isReadOnly` je `true`, vykreslí se: + +```latte + +``` + +Pokud potřebujete přepínací chování pro standardní atributy, které nemají toto automatické zpracování (tedy např. atributy `data-` nebo `aria-`), použijte filtr [toggle |filters#toggle]. + + +Hodnoty null +============ + +Toto je jedna z nejpříjemnějších změn. Dříve, pokud byla proměnná `null`, vypsala se jako prázdný řetězec `""`. To často vedlo k prázdným atributům v HTML jako `class=""` nebo `title=""`. + +V Latte 3.1 platí nové univerzální pravidlo: **Hodnota `null` znamená, že atribut neexistuje.** + +```latte +
                          +``` + +Pokud `$title` je `null`, výstupem je `
                          `. Pokud obsahuje řetězec, např. "Ahoj", výstupem je `
                          `. Díky tomu nemusíte obalovat atributy do podmínek. + +Pokud používáte filtry, mějte na paměti, že obvykle převádějí `null` na řetězec (např. prázdný řetězec). Abyste tomu zabránili, použijte [nullsafe filtr |filters#Nullsafe filtry] `?|`: + +```latte +
                          +``` + + +Třídy (Classes) +=============== + +Atributu `class` můžete předat pole. To je ideální pro podmíněné třídy: pokud je pole asociativní, klíče se použijí jako názvy tříd a hodnoty jako podmínky. Třída se vykreslí pouze v případě, že je podmínka splněna. + +```latte + +``` + +Pokud je `$isActive` true, vykreslí se: + +```latte + +``` + +Toto chování není omezeno pouze na `class`. Funguje pro jakýkoliv HTML atribut, který očekává seznam hodnot oddělených mezerou, jako jsou `itemprop`, `rel`, `sandbox` atd. + +```latte + $isExternal]}>odkaz +``` + + +Styly (Styles) +============== + +Atribut `style` také podporuje pole. Je to obzvláště užitečné pro podmíněné styly. Pokud položka pole obsahuje klíč (CSS vlastnost) a hodnotu, vlastnost se vykreslí pouze v případě, že hodnota není `null`. + +```latte +
                          lightblue, + display => $isVisible ? block : null, + font-size => '16px', +]}>
                          +``` + +Pokud je `$isVisible` false, vykreslí se: + +```latte +
                          +``` + + +Data atributy +============= + +Často potřebujeme do HTML předat konfiguraci pro JavaScript. Dříve se to dělalo přes `json_encode`. Nyní můžete atributu `data-` jednoduše předat pole nebo objekt stdClass a Latte jej serializuje do JSONu: + +```latte +
                          +``` + +Vypíše: + +```latte +
                          +``` + +Také `true` a `false` se vykreslují jako řetězce `"true"` a `"false"` (tj. validní JSON). + + +Aria atributy +============= + +Specifikace WAI-ARIA vyžaduje textové hodnoty `"true"` a `"false"` pro logické hodnoty. Latte to pro atributy `aria-` řeší automaticky: + +```latte + +``` + +Vypíše: + +```latte + +``` + + +Typová kontrola +=============== + +Už jste někdy viděli `` ve svém vygenerovaném HTML? Je to klasická chyba, která často projde bez povšimnutí. Latte zavádí přísnou typovou kontrolu pro HTML atributy, aby byly vaše šablony vůči takovým přehlédnutím odolnější. + +Latte ví, které atributy jsou které a jaké hodnoty očekávají: + +- **Standardní atributy** (jako `href`, `id`, `value`, `placeholder`...) očekávají hodnotu, kterou lze vykreslit jako text. To zahrnuje řetězce, čísla nebo stringable objekty. Také je akceptováno `null` (atribut vynechá). Pokud však omylem předáte pole, boolean nebo obecný objekt, Latte vyvolá varování a neplatnou hodnotu inteligentně ignoruje. +- **Boolean atributy** (jako `checked`, `disabled`...) akceptují jakýkoliv typ, protože jejich přítomnost je určena logikou pravdivý/nepravdivý. +- **Chytré atributy** (jako `class`, `style`, `data-`...) specificky zpracovávají pole jako validní vstupy. + +Tato kontrola zajišťuje, že vaše aplikace nebude produkovat neočekávané HTML. + + +Migrace z Latte 3.0 +=================== + +Protože se změnilo chování `null` (dříve vypisovalo `""`, nyní atribut vynechá) a atributů `data-` (boolean hodnoty vypisovaly `"1"`/`""`, nyní `"true"`/`"false"`), možná budete muset aktualizovat své šablony. + +Pro hladký přechod poskytuje Latte migrační režim, který upozorňuje na rozdíly. Přečtěte si podrobného průvodce [Migrace z Latte 3.0 na 3.1 |cookbook/migration-from-latte-30]. + +[* html-attributes.webp *] diff --git a/latte/cs/loaders.texy b/latte/cs/loaders.texy new file mode 100644 index 0000000000..17b0f2356e --- /dev/null +++ b/latte/cs/loaders.texy @@ -0,0 +1,198 @@ +Loadery +******* + +.[perex] +Loadery jsou mechanismus, který Latte používá k získání zdrojového kódu vašich šablon. Nejčastěji jsou šablony uloženy jako soubory na disku, ale díky flexibilnímu systému loaderů je můžete načítat prakticky odkudkoliv nebo je dokonce dynamicky generovat. + + +Co je to Loader? +================ + +Když pracujete se šablonami, obvykle si představíte soubory `.latte` umístěné ve struktuře adresářů vašeho projektu. O to se stará výchozí [#FileLoader] v Latte. Spojení mezi názvem šablony (jako `'main.latte'` nebo `'components/card.latte'`) a jejím skutečným zdrojovým kódem však *nemusí* být přímé mapování na cestu k souboru. + +Právě tady přicházejí na řadu loadery. Loader je objekt, který má za úkol vzít název šablony (identifikační řetězec) a poskytnout Latte její zdrojový kód. Latte se při tomto úkolu zcela spoléhá na nakonfigurovaný loader. To platí nejen pro počáteční šablonu vyžádanou pomocí `$latte->render('main.latte')`, ale také pro **každou šablonu odkazovanou uvnitř** pomocí tagů jako `{include ...}`, `{layout ...}`, `{embed ...}` nebo `{import ...}`. + +Proč používat vlastní loader? + +- **Načítání z alternativních zdrojů:** Získávání šablon uložených v databázi, v cache (jako Redis nebo Memcached), v systému správy verzí (jako Git, na základě konkrétního commitu) nebo dynamicky generovaných. +- **Implementace vlastních konvencí pojmenování:** Můžete chtít používat kratší aliasy pro šablony nebo implementovat specifickou logiku vyhledávacích cest (např. nejprve hledat v adresáři tématu, pak se vrátit k výchozímu adresáři). +- **Přidání zabezpečení nebo řízení přístupu:** Vlastní loader může před načtením určitých šablon ověřit uživatelská oprávnění. +- **Předzpracování:** I když se to obecně nedoporučuje ([kompilační průchody |compiler-passes] jsou lepší), loader *by* teoreticky mohl předzpracovat obsah šablony, než ho předá do Latte. + +Loader pro instanci `Latte\Engine` nastavíte pomocí metody `setLoader()`: + +```php +$latte = new Latte\Engine; + +// Použití výchozího FileLoaderu pro soubory v '/path/to/templates' +$loader = new Latte\Loaders\FileLoader('/path/to/templates'); +$latte->setLoader($loader); +``` + +Loader musí implementovat rozhraní `Latte\Loader`. + + +Vestavěné Loadery +================= + +Latte nabízí několik standardních loaderů: + + +FileLoader +---------- + +Toto je **výchozí loader** používaný třídou `Latte\Engine`, pokud není určen žádný jiný. Načítá šablony přímo ze souborového systému. + +Volitelně můžete nastavit kořenový adresář pro omezení přístupu: + +```php +use Latte\Loaders\FileLoader; + +// Následující umožní načítání šablon pouze z adresáře /var/www/html/templates +$loader = new FileLoader('/var/www/html/templates'); +$latte->setLoader($loader); + +// $latte->render('../../../etc/passwd'); // Toto by vyhodilo výjimku + +// Vykreslení šablony umístěné na /var/www/html/templates/pages/contact.latte +$latte->render('pages/contact.latte'); +``` + +Při použití tagů jako `{include}` nebo `{layout}` řeší názvy šablon relativně k aktuální šabloně, pokud není zadána absolutní cesta. + + +StringLoader +------------ + +Tento loader získává obsah šablony z asociativního pole, kde klíče jsou názvy šablon (identifikátory) a hodnoty jsou řetězce zdrojového kódu šablony. Je zvláště užitečný pro testování nebo malé aplikace, kde mohou být šablony uloženy přímo v PHP kódu. + +```php +use Latte\Loaders\StringLoader; + +$loader = new StringLoader([ + 'main.latte' => 'Hello {$name}, include is below:{include helper.latte}', + 'helper.latte' => '{var $x = 10}Included content: {$x}', + // Přidejte další šablony podle potřeby +]); + +$latte->setLoader($loader); + +$latte->render('main.latte', ['name' => 'World']); +// Výstup: Hello World, include is below:Included content: 10 +``` + +Pokud potřebujete vykreslit pouze jednu šablonu přímo z řetězce bez potřeby vkládání nebo dědičnosti odkazující na další pojmenované řetězcové šablony, můžete předat řetězec přímo metodě `render()` nebo `renderToString()` při použití `StringLoader` bez pole: + +```php +$loader = new StringLoader; +$latte->setLoader($loader); + +$templateString = 'Hello {$name}!'; +$output = $latte->renderToString($templateString, ['name' => 'Alice']); +// $output obsahuje 'Hello Alice!' +``` + + +Vytvoření vlastního Loaderu +=========================== + +Pro vytvoření vlastního loaderu (např. pro načítání šablon z databáze, cache, systému správy verzí nebo jiného zdroje) musíte vytvořit třídu, která implementuje rozhraní [api:Latte\Loader]. + +Podívejme se, co musí každá metoda dělat. + + +getContent(string $name): string .[method] +------------------------------------------ +Toto je základní metoda loaderu. Jejím úkolem je získat a vrátit úplný zdrojový kód šablony identifikované pomocí `$name` (jak je předáno metodě `$latte->render()` nebo vráceno metodou [#getReferredName()]). + +Pokud šablonu nelze najít nebo k ní přistupovat, tato metoda **musí vyhodit výjimku `Latte\TemplateNotFoundException`**. + +```php +public function getContent(string $name): string +{ + // Příklad: Načtení z hypotetického interního úložiště + $content = $this->storage->read($name); + if ($content === null) { + throw new Latte\RuntimeException("Template '$name' cannot be loaded."); + } + return $content; +} +``` + + +getReferredName(string $name, string $referringName): string .[method] +---------------------------------------------------------------------- +Tato metoda řeší překlad názvů šablon používaných v rámci tagů jako `{include}`, `{layout}` atd. Když Latte narazí například na `{include 'partial.latte'}` uvnitř `main.latte`, volá tuto metodu s `$name = 'partial.latte'` a `$referringName = 'main.latte'`. + +Úkolem metody je přeložit `$name` na kanonický identifikátor (např. absolutní cestu, jedinečný klíč databáze), který bude použit při volání dalších metod loaderu, na základě kontextu poskytnutého v `$referringName`. + +```php +public function getReferredName(string $name, string $referringName): string +{ + return ...; +} +``` + + +getUniqueId(string $name): string .[method] +------------------------------------------- +Latte používá pro zlepšení výkonu mezipaměť kompilovaných šablon. Každý kompilovaný soubor šablony potřebuje jedinečný název odvozený od identifikátoru zdrojové šablony. Tato metoda poskytuje řetězec, který **jednoznačně identifikuje** šablonu `$name`. + +Pro šablony založené na souborech může posloužit absolutní cesta. Pro šablony v databázi je běžná kombinace prefixu a ID databáze. + +```php +public function getUniqueId(string $name): string +{ + return ...; +} +``` + + +Příklad: Jednoduchý databázový Loader +------------------------------------- + +Tento příklad ukazuje základní strukturu loaderu, který načítá šablony uložené v databázové tabulce nazvané `templates` se sloupci `name` (jedinečný identifikátor), `content` a `updated_at`. + +```php +use Latte; + +class DatabaseLoader implements Latte\Loader +{ + public function __construct( + private \PDO $db, + ) { + } + + public function getContent(string $name): string + { + $stmt = $this->db->prepare('SELECT content FROM templates WHERE name = ?'); + $stmt->execute([$name]); + $content = $stmt->fetchColumn(); + if ($content === false) { + throw new Latte\TemplateNotFoundException("Template '$name' not found in database."); + } + return $content; + } + + // Tento jednoduchý příklad předpokládá, že názvy šablon ('homepage', 'article', atd.) + // jsou jedinečnými ID a šablony na sebe neodkazují relativně. + public function getReferredName(string $name, string $referringName): string + { + return $name; + } + + public function getUniqueId(string $name): string + { + // Použití prefixu a samotného názvu je zde jedinečné a dostatečné + return 'db_' . $name; + } +} + +// Použití: +$pdo = new \PDO(/* detaily připojení */); +$loader = new DatabaseLoader($pdo); +$latte->setLoader($loader); +$latte->render('homepage'); // Načte šablonu s názvem 'homepage' z DB +``` + +Vlastní loadery vám dávají úplnou kontrolu nad tím, odkud vaše Latte šablony pocházejí, což umožňuje integraci s různými systémy úložišť a pracovními postupy. diff --git a/latte/cs/recipes.texy b/latte/cs/recipes.texy index b071f62371..a11d147a0e 100644 --- a/latte/cs/recipes.texy +++ b/latte/cs/recipes.texy @@ -7,9 +7,9 @@ Editory a IDE Pište šablony v editoru nebo IDE, který má podporu pro Latte. Bude to mnohem příjemnější. -- NetBeans IDE má podporu vestavěnou - PhpStorm: nainstalujte v `Settings > Plugins > Marketplace` [plugin Latte|https://plugins.jetbrains.com/plugin/7457-latte] -- VS Code: hledejte v markerplace "Nette Latte + Neon" plugin +- VS Code: nainstalujte [Nette Latte + Neon|https://marketplace.visualstudio.com/items?itemName=Kasik96.latte], [Nette Latte templates|https://marketplace.visualstudio.com/items?itemName=smuuf.latte-lang] nebo nejnovější [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode] plugin +- NetBeans IDE: nativní podpora Latte je součástí instalace - Sublime Text 3: v Package Control najděte a nainstalujte balíček `Nette` a zvolte Latte ve `View > Syntax` - ve starých editorech použijte pro soubory .latte zvýrazňování Smarty @@ -113,7 +113,7 @@ Pokud je objekt `$dog` instancí `Pets\Model\Dog`, pak lze použít `{if $dog->s Generování XML v Latte ====================== -Latte může generovat jakýkoli textový formát (HTML, XML, CSV, iCal atd.), nicméně aby správě escapovalo vypisované data, musíme mu říct, jaký formát generujeme. K tomu slouží značka [`{contentType}`|tags#contentType]. +Latte může generovat jakýkoli textový formát (HTML, XML, CSV, iCal atd.), nicméně aby správě escapovalo vypisované data, musíme mu říct, jaký formát generujeme. K tomu slouží značka [`{contentType}` |tags#contentType]. ```latte {contentType application/xml} @@ -140,8 +140,7 @@ Poté můžeme například vygenerovat sitemapu podobným způsobem: Předání dat z includované šablony ================================= -Proměnné, které vytvoříme pomocí `{var}` či `{default}` v inkludované šabloně, existují jen v ní a nejsou v inkludující šabloně k dispozici. -Pokud bychom si chtěli z inkludované šablony předat zpátky do inkludující nějaká data, jednou z možností je předat do šablony objekt a do něj data vložit. +Proměnné, které vytvoříme pomocí `{var}` či `{default}` v inkludované šabloně, existují jen v ní a nejsou v inkludující šabloně k dispozici. Pokud bychom si chtěli z inkludované šablony předat zpátky do inkludující nějaká data, jednou z možností je předat do šablony objekt a do něj data vložit. Hlavní šablona: diff --git a/latte/cs/safety-first.texy b/latte/cs/safety-first.texy index 44bd9f4edf..e76142ec39 100644 --- a/latte/cs/safety-first.texy +++ b/latte/cs/safety-first.texy @@ -17,8 +17,7 @@ Cross-site Scripting (XSS) Cross-site Scripting (zkráceně XSS) je jednou z nejčastějších zranitelností webových stránek a přitom velmi nebezpečnou. Umožní útočníkovi vložit do cizí stránky škodlivý skript (tzv. malware), který se spustí v prohlížeči nic netušícího uživatele. -Co všechno může takový skript napáchat? Může například odeslat útočníkovi libovolný obsah z napadené stránky, včetně citlivých údajů zobrazených po přihlášení. Může stránku pozměnit nebo provádět další požadavky jménem uživatele. -Pokud by se například jednalo o webmail, může si přečíst citlivé zprávy, pozměnit zobrazovaný obsah nebo přenastavit konfiguraci, např. zapnout přeposílání kopií všech zpráv na útočníkovu adresu, aby získal přístup i k budoucím emailům. +Co všechno může takový skript napáchat? Může například odeslat útočníkovi libovolný obsah z napadené stránky, včetně citlivých údajů zobrazených po přihlášení. Může stránku pozměnit nebo provádět další požadavky jménem uživatele. Pokud by se například jednalo o webmail, může si přečíst citlivé zprávy, pozměnit zobrazovaný obsah nebo přenastavit konfiguraci, např. zapnout přeposílání kopií všech zpráv na útočníkovu adresu, aby získal přístup i k budoucím emailům. Proto také XSS figuruje na předních místech žebříčků nejnebezpečnějších zranitelností. Pokud se na webové stránce zranitelnost objeví, je nutné ji co nejdříve odstranit, aby se zabránilo zneužití. @@ -40,8 +39,7 @@ echo '

                          Výsledky vyhledávání pro ' . $search . '

                          '; Prohlížeč místo toho, aby vypsal hledaný řetězec, spustí JavaScript. A tím přebírá vládu nad stránkou útočník. -Můžete namítnout, že vložením kódu do proměnné sice dojde ke spuštění JavaScriptu, ale jen v útočníkově prohlížeči. Jak se dostane k oběti? Z tohoto pohledu rozlišujeme několik typů XSS. V našem příkladu s vyhledáváním hovoříme o *reflected XSS*. -Zde je ještě potřeba navést oběť, aby klikla na odkaz, který bude obsahovat škodlivý kód v parametru: +Můžete namítnout, že vložením kódu do proměnné sice dojde ke spuštění JavaScriptu, ale jen v útočníkově prohlížeči. Jak se dostane k oběti? Z tohoto pohledu rozlišujeme několik typů XSS. V našem příkladu s vyhledáváním hovoříme o *reflected XSS*. Zde je ještě potřeba navést oběť, aby klikla na odkaz, který bude obsahovat škodlivý kód v parametru: ``` https://example.com/?search= @@ -53,8 +51,7 @@ Nicméně existuje i druhá a mnohem nebezpečnější forma útoku označovaná Příkladem jsou stránky, kam uživatelé píší komentáře. Útočník pošle příspěvek obsahující kód a ten se uloží na server. Pokud stránky nejsou dostatečně zabezpečené, bude se pak spouštět v prohlížeči každého návštěvníka. -Mohlo by se zdát, že jádro útoku spočívá v tom dostat do stránky řetězec ` -- v CSS: -- v komentáři: +- v textu: {{ foo }} +- v tagu: +- v atributu: +- v atributu bez uvozovek: +- v atributu obsahujícím URL: +- v atributu obsahujícím JavaScript: +- v atributu obsahujícím CSS: +- v JavaScriptu: +- v CSS: +- v komentáři: ```
                          @@ -234,6 +220,36 @@ Naivní systémy jen mechanicky převádějí znaky `< > & ' "` na HTML entity, Latte šablonu vidí stejně jako vy. Chápe HTML, XML, rozeznává značky, atributy atd. A díky tomu rozlišuje jednotlivé kontexty a podle nich ošetřuje data. Nabízí tak opravdu efektivní ochranu proti kritické zranitelnosti Cross-site Scripting. +
                          + +```latte .{file:Latte šablona, jak ji vidí Latte} +░░░░░░░░░░░{$foo} +░░░░░░░░░░ +░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░ +░░░░░░░░░ +░░░░░░░░░░░░░░░ +``` + +```latte .{file:Latte šablona, jak ji vidí designer} +- v textu: {$foo} +- v tagu: +- v atributu: +- v atributu bez uvozovek: +- v atributu obsahujícím URL: +- v atributu obsahujícím JavaScript: +- v atributu obsahujícím CSS: +- v JavaScriptu: +- v CSS: +- v komentáři: +``` + +
                          + Živá ukázka =========== @@ -282,14 +298,13 @@ Tohle dokonce nejsou všechny kontexty, které Latte při vypisování rozlišuj Jak hacknout naivní systémy =========================== -Na několika praktických příkladech si ukážeme, jak je rozlišování kontextů důležité a proč naivní šablonovací systémy neposkytují dostatečnou ochranu před XSS, na rozdíl od Latte. -Jako zástupce naivního systému použijeme v ukázkách Twig, ale totéž platí i pro ostatní systémy. +Na několika praktických příkladech si ukážeme, jak je rozlišování kontextů důležité a proč naivní šablonovací systémy neposkytují dostatečnou ochranu před XSS, na rozdíl od Latte. Jako zástupce naivního systému použijeme v ukázkách Twig, ale totéž platí i pro ostatní systémy. Zranitelnost atributem ---------------------- -Pokusíme se do stránky injektovat škodlivý kód pomocí HTML atributu, jak jsme si [ukazovali výše|#Jak zranitelnost vzniká]. Mějme šablonu v Twigu vykreslující obrázek: +Pokusíme se do stránky injektovat škodlivý kód pomocí HTML atributu, jak jsme si [ukazovali výše |#Jak zranitelnost vzniká]. Mějme šablonu v Twigu vykreslující obrázek: ```twig .{file:Twig} {{ @@ -297,8 +312,7 @@ Pokusíme se do stránky injektovat škodlivý kód pomocí HTML atributu, jak j Všimněte si, že okolo hodnot atributů nejsou uvozovky. Kodér na ně mohl zapomenout, což se prostě stává. Například v Reactu se kód píše takto, bez uvozovek, a kodér, který střídá jazyky, pak na uvozovky může snadno zapomenout. -Útočník jako popisek obrázku vloží šikovně sestavený řetězec `foo onload=alert('Hacked!')`. Už víme, že Twig nemůže poznat, jestli se proměnná vypisuje v toku HTML textu, uvnitř atributu, HTML komentáře, atd., zkrátka nerozlišuje kontexty. A jen mechanicky převádí znaky `< > & ' "` na HTML entity. -Takže výsledný kód bude vypadat takto: +Útočník jako popisek obrázku vloží šikovně sestavený řetězec `foo onload=alert('Hacked!')`. Už víme, že Twig nemůže poznat, jestli se proměnná vypisuje v toku HTML textu, uvnitř atributu, HTML komentáře, atd., zkrátka nerozlišuje kontexty. A jen mechanicky převádí znaky `< > & ' "` na HTML entity. Takže výsledný kód bude vypadat takto: ```html foo @@ -351,7 +365,7 @@ Latte automaticky kontroluje, zda proměnná použitá v atributech `src` nebo ` ```latte {var $link = 'javascript:attack()'} -klikni +klikni ``` Vypíše: @@ -360,12 +374,10 @@ Vypíše: klikni ``` -Kontrola se dá vypnout pomocí filtru [nocheck|filters#nocheck]. +Kontrola se dá vypnout pomocí filtru [nocheck |filters#nocheck]. Limity Latte ============ -Latte není zcela kompletní ochranou před XSS pro celou aplikaci. Byli bychom neradi, kdybyste při použití Latte přestali nad bezpečností přemýšlet. -Cílem Latte je zajistit, aby útočník nemohl pozměnit strukturu stránky, podvrhnout HTML elementy nebo atributy. Ale nekontroluje obsahovou správnost vypisovaných dat. Nebo správnost chování JavaScriptu. -To už jde mimo kompetence šablonovacího systému. Ověřování správnosti dat, zejména těch vložených uživatelem a tedy nedůvěryhodných, je důležitým úkolem programátora. +Latte není zcela kompletní ochranou před XSS pro celou aplikaci. Byli bychom neradi, kdybyste při použití Latte přestali nad bezpečností přemýšlet. Cílem Latte je zajistit, aby útočník nemohl pozměnit strukturu stránky, podvrhnout HTML elementy nebo atributy. Ale nekontroluje obsahovou správnost vypisovaných dat. Nebo správnost chování JavaScriptu. To už jde mimo kompetence šablonovacího systému. Ověřování správnosti dat, zejména těch vložených uživatelem a tedy nedůvěryhodných, je důležitým úkolem programátora. diff --git a/latte/cs/sandbox.texy b/latte/cs/sandbox.texy index 48023666f8..48bf49c8cd 100644 --- a/latte/cs/sandbox.texy +++ b/latte/cs/sandbox.texy @@ -1,12 +1,10 @@ Sandbox ******* -.[perex]{data-version:2.8} +.[perex] Sandbox poskytuje bezpečnostní vrstvu, která vám dává kontrolu nad tím, jaké značky, PHP funkce, metody apod. mohou být v šablonách použity. Díky sandbox režimu můžete bezpečně spolupracovat s klientem nebo externím kodérem na tvorbě šablon, aniž byste se museli obávat, že dojde k narušení aplikace nebo nežádoucím operacím. -Jak to funguje? Jednoduše nadefinujeme, co všechno šabloně dovolíme. Přičemž v základu je všechno zakázané a my postupně povolujeme: - -Následujícím kódem umožníme autorovi šablony používat značky `{block}`, `{if}`, `{else}` a `{=}`, což je značka pro [vypsání proměnné nebo výrazu|tags#Vypisování] a všechny filtry: +Jak to funguje? Jednoduše nadefinujeme, co všechno šabloně dovolíme. Přičemž v základu je všechno zakázané a my postupně povolujeme. Následujícím kódem umožníme autorovi šablony používat značky `{block}`, `{if}`, `{else}` a `{=}`, což je značka pro [vypsání proměnné nebo výrazu |tags#Vypisování] a všechny filtry: ```php $policy = new Latte\Sandbox\SecurityPolicy; @@ -32,10 +30,9 @@ Tvořit policy od bodu nula, kdy je zakázáno úplně vše, nemusí být pohodl $policy = Latte\Sandbox\SecurityPolicy::createSafePolicy(); ``` -Bezpečný základ znamená, že jsou povoleny všechny standardní tagy kromě `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget`. -Jsou povoleny standardní filtry kromě `datastream`, `noescape` a `nocheck`. A nakonec je povolený přístup k metodám a properites objektu `$iterator`. +Bezpečný základ znamená, že jsou povoleny všechny standardní tagy kromě `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget`. Jsou povoleny standardní filtry kromě `datastream`, `noescape` a `nocheck`. A nakonec je povolený přístup k metodám a properites objektu `$iterator`. -Pravidla se aplikují pro šablonu, kterou vložíme značkou [`{sandbox}` |tags#Vložení šablon]. Což je jakási obdoba `{include}`, která však zapíná bezpečný režim a také nepředává žádné proměnné: +Pravidla se aplikují pro šablonu, kterou vložíme značkou [`{sandbox}` |tags#Vložení šablony]. Což je jakási obdoba `{include}`, která však zapíná bezpečný režim a také nepředává žádné proměnné: ```latte {sandbox 'untrusted.latte'} @@ -43,11 +40,17 @@ Pravidla se aplikují pro šablonu, kterou vložíme značkou [`{sandbox}` |tags Tedy layout a jednotlivé stránky mohou nerušeně využívat všechny tagy a proměnné, pouze na šablonu `untrusted.latte` budou uplatněny restrikce. -Některé prohřešky, jako použití zakázaného tagu nebo filtru, se odhalí v době kompilace. Jiné, jako třeba volání nepovolených metod objektu, až za běhu. -Šablona také může obsahovat jakékoliv jiné chyby. Aby vám ze sandboxované šablony nemohla vyskočit výjimka, která naruší celé vykreslování, lze definovat vlastní [obslužný handler pro výjimky|develop#exception handler], který ji třeba zaloguje. +Některé prohřešky, jako použití zakázaného tagu nebo filtru, se odhalí v době kompilace. Jiné, jako třeba volání nepovolených metod objektu, až za běhu. Šablona také může obsahovat jakékoliv jiné chyby. Aby vám ze sandboxované šablony nemohla vyskočit výjimka, která naruší celé vykreslování, lze definovat vlastní [obslužný handler pro výjimky |develop#Exception handler], který ji třeba zaloguje. Pokud bychom chtěli sandbox režim zapnout přímo pro všechny šablony, jde to snadno: ```php $latte->setSandboxMode(); ``` + +Abyste měli jistotu, že uživatel do stránky nevloží PHP kód, který je sice syntakticky správný, ale zakázaný a způsobí PHP Compile Error, doporučujeme nechávat [šablony kontrolovat PHP linterem |develop#Kontrola vygenerovaného kódu]. Tuto funkčnost zapnete metodou `Engine::enablePhpLint()`. Jelikož ke kontrole potřebuje volat binárku PHP, cestu k ní předejte jako parametr: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); +``` diff --git a/latte/cs/syntax.texy b/latte/cs/syntax.texy index ea12253584..1e21ab6dcf 100644 --- a/latte/cs/syntax.texy +++ b/latte/cs/syntax.texy @@ -2,14 +2,13 @@ Syntaxe ******* .[perex] -Syntax Latte vzešla z praktických požadavků webdesignerů. Hledali jsme tu nejpřívětivější syntax, se kterou elegantně zapíšete i konstrukce, které jinak představují skutečný oříšek. -Zároveň všechny výrazy se píší úplně stejně jako v PHP, takže se nemusíte učit nový jazyk. Prostě zúročíte co už dávno umíte. +Syntax Latte vzešla z praktických požadavků webdesignerů. Hledali jsme tu nejpřívětivější syntax, se kterou elegantně zapíšete i konstrukce, které jinak představují skutečný oříšek. Zároveň všechny výrazy se píší úplně stejně jako v PHP, takže se nemusíte učit nový jazyk. Prostě zúročíte, co už dávno umíte. Níže je uvedena minimální šablona, která ilustruje několik základních prvků: tagy, n:atributy, komentáře a filtry. ```latte {* toto je komentář *} -
                            {* n:if je n:atribut *} +
                              {* n:if je n:atribut *} {foreach $items as $item} {* tag představující cyklus foreach *}
                            • {$item|capitalize}
                            • {* tag vypisující proměnnou s filtrem *} {/foreach} {* konec cyklu *} @@ -22,10 +21,9 @@ Podívejme se blíže na tyto důležité prvky a na to, jak vám mohou pomoci v Tagy ==== -Šablona obsahuje tagy (neboli značky či makra), které řídí logiku šablony (například smyčky *foreach*) nebo vypisují výrazy. Pro obojí se používá jediný delimiter `{ ... }`, takže nemusíte přemýšlet, jaký delimiter v jaké situaci použít, jako je tomu u jiných systémů. -Pokud za znakem `{` následuje uvozovka nebo mezera, Latte jej nepovažuje za začátek značky, díky čemuž můžete v šablonách bez problémů používat i JavaScriptové konstrukce, JSON nebo pravidla v CSS. +Šablona obsahuje tagy (neboli značky či makra), které řídí logiku šablony (například smyčky *foreach*) nebo vypisují výrazy. Pro obojí se používá jediný delimiter `{ ... }`, takže nemusíte přemýšlet, jaký delimiter v jaké situaci použít, jako je tomu u jiných systémů. Pokud za znakem `{` následuje uvozovka nebo mezera, Latte jej nepovažuje za začátek značky, díky čemuž můžete v šablonách bez problémů používat i JavaScriptové konstrukce, JSON nebo pravidla v CSS. -Podívejte se na [přehled všech tagů|tags]. Krom toho si můžete vytvářet i [vlastní tagy|extending-latte#tagy]. +Podívejte se na [přehled všech tagů|tags]. Krom toho si můžete vytvářet i [vlastní tagy|custom tags]. Latte rozumí PHP @@ -52,7 +50,7 @@ n:atributy Všechny párové značky, například `{if} … {/if}`, operující nad jedním HTML elementem, se dají přepsat do podoby n:atributů. Takto by bylo možné zapsat například i `{foreach}` v úvodní ukázce: ```latte -
                                +
                                • {$item|capitalize}
                                ``` @@ -98,7 +96,7 @@ Vypíše se: Nebo pomocí prefixu `tag-` aplikujeme funkcionalitu jen na samotné HTML značky: ```latte -

                                Title

                                +

                                Title

                                ``` Což vypíše v závislosti na proměnné `$url`: @@ -113,6 +111,34 @@ Což vypíše v závislosti na proměnné `$url`: Avšak n:atributy nejsou jen zkratkou pro párové značky. Existují i ryzí n:atributy, jako třeba [n:href |application:creating-links#V šabloně presenteru] nebo velešikovný pomocník kodéra [n:class |tags#n:class]. +Kromě syntaxe s uvozovkami `
                                ` můžete použít alternativní syntaxi se složenými závorkami `
                                `. Hlavní výhodou je, že uvnitř `{...}` můžete volně používat jednoduché i dvojité uvozovky: + +```latte +
                                ...
                                +``` + + +Chytré HTML atributy .{data-version:3.1} +======================================== + +Latte dělá práci se standardními HTML atributy neuvěřitelně snadnou. Za vás řeší boolean atributy jako `checked`, odstraňuje atributy obsahující `null` a umožňuje vám skládat hodnoty `class` a `style` pomocí polí. Dokonce automaticky serializuje data pro `data-` atributy do JSON. + +```latte +{* null odstraní atribut *} +
                                + +{* boolean ovládá přítomnost boolean atributů *} + + +{* pole fungují v class *} +
                                $isActive]}> + +{* pole jsou JSON-enkódována v data- atributech *} +
                                +``` + +Více informací v samostatné kapitole [Chytré HTML atributy|html-attributes]. + Filtry ====== @@ -149,11 +175,32 @@ Na blok:

                                {block |lower}{$heading}{/block}

                                ``` -Nebo přímo na hodnotu (v kombinaci s tagem [`{=expr}`| https://latte.nette.org/cs/tags#Vypisování]): +Nebo přímo na hodnotu (v kombinaci s tagem [`{=expr}` |tags#Vypisování]): + ```latte

                                {=' Hello world '|trim}

                                ``` +Pokud může být hodnota `null` a chcete v takovém případě zabránit použití filtru, použijte [nullsafe filter |filters#Nullsafe Filters] `?|`: + +```latte +

                                {$heading?|upper}

                                +``` + + +Dynamické HTML značky .{data-version:3.0.9} +=========================================== + +Latte podporuje dynamické HTML značky, které jsou užitečné, když potřebujete flexibilitu v názvech značek: + +```latte +Heading +``` + +Výše uvedený kód může například generovat `

                                Heading

                                ` nebo `

                                Heading

                                ` v závislosti na hodnotě proměnné `$level`. Dynamické HTML značky v Latte musí být vždy párové. Jejich alternativou je [n:tag |tags#n:tag]. + +Protože Latte je bezpečný šablonovací systém, kontroluje, zda je výsledný název značky platný a neobsahuje žádné nežádoucí nebo škodlivé hodnoty. Dále zajistí, že název koncové značky bude vždy stejný jako název otevírací značky. + Komentáře ========= @@ -186,9 +233,19 @@ jako v PHP: {var $arr = ['hello', 'btn--default', '€']} zkráceně: {var $arr = [hello, btn--default, €]} ``` -Jednoduché řetězce jsou ty, které jsou tvořeny čistě z písmen, číslic, podtržítek, pomlček a teček. Nesmí začínat číslicí a nesmí začínat nebo končit pomlčkou. -Nesmí být složený jen z velkých písmen a podtržítek, protože pak se považuje za konstantu (např. `PHP_VERSION`). -A nesmí kolidovat s klíčovými slovy: `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor`. +Jednoduché řetězce jsou ty, které jsou tvořeny čistě z písmen, číslic, podtržítek, pomlček a teček. Nesmí začínat číslicí a nesmí začínat nebo končit pomlčkou. Nesmí být složený jen z velkých písmen a podtržítek, protože pak se považuje za konstantu (např. `PHP_VERSION`). A nesmí kolidovat s klíčovými slovy: `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor`. + + +Konstanty +--------- + +K rozlišení globálních konstant od jednoduchých řetězců použijte oddělovač globálního jmenného prostoru: + +```latte +{if \PROJECT_ID === 1} ... {/if} +``` + +Tento zápis je zcela validní v samotném PHP, lomítko říká, že konstanta je v globálním namespace. Zkrácený ternární operátor @@ -238,24 +295,6 @@ Operátorem `in` lze nahradit funkci `in_array()`. Porovnání je vždy striktn ``` -.{data-version:2.9} -Volitelné řetězení s undefined-safe operátorem ----------------------------------------------- - -Undefined-safe operator `??->` je obdoba nullsafe operatoru `?->`, avšak nevyvolá chybu, pokud proměnná, property nebo index v poli vůbec neexistuje. - -```latte -{$order??->id} -``` - -říkáme tím, že když existuje `$order` a není null, bude vypsán `$order->id`, ale když je `$order` null nebo neexistuje, zastaví se vyhodnocování a prostě se vrátí null. - -```latte -{$user??->address??->street} -// znamená cca isset($user) && isset($user->address) ? $user->address->street : null -``` - - Historické okénko ----------------- @@ -265,8 +304,6 @@ Latte přišlo v průběhu své historie s celou řadou syntaktických cukřík Omezení PHP v Latte =================== -V Latte lze zapisovat jen PHP výrazy. Tedy nelze deklarovat třídy nebo používat [řídící struktury |https://www.php.net/manual/en/language.control-structures.php], např. `if`, `foreach`, `switch`, `return`, `try`, `throw` a další, místo kterých Latte nabízí své [značky|tags]. -Také nelze používat [atributy |https://www.php.net/manual/en/language.attributes.php], [backticks |https://www.php.net/manual/en/language.operators.execution.php] či [magické konstanty |https://www.php.net/manual/en/language.constants.magic.php], protože by to nedávalo smysl. -Nelze používat ani `unset`, `echo`, `include`, `require`, `exit`, `eval`, protože nejde o funkce, ale speciální jazykové konstrukce PHP, a nejsou to tedy výrazy. +V Latte lze zapisovat jen PHP výrazy. Tedy nelze používat statementy ukončené středníkem. Nelze deklarovat třídy nebo používat [řídící struktury |https://www.php.net/manual/en/language.control-structures.php], např. `if`, `foreach`, `switch`, `return`, `try`, `throw` a další, místo kterých Latte nabízí své [značky|tags]. Také nelze používat [atributy |https://www.php.net/manual/en/language.attributes.php], [backticks |https://www.php.net/manual/en/language.operators.execution.php] či některé [magické konstanty |https://www.php.net/manual/en/language.constants.magic.php]. Nelze používat ani `unset`, `echo`, `include`, `require`, `exit`, `eval`, protože nejde o funkce, ale speciální jazykové konstrukce PHP, a nejsou to tedy výrazy. Komentáře jsou podporované jen víceřádkové `/* ... */`. Tyto omezení lze nicméně obejít tím, že si aktivujete rozšíření [RawPhpExtension |develop#RawPhpExtension], díky kterému lze pak používat ve značce `{php ...}` jakýkoliv PHP kód na zodpovědnost autora šablony. diff --git a/latte/cs/tags.texy b/latte/cs/tags.texy index 5b5329fd59..598b2b73dd 100644 --- a/latte/cs/tags.texy +++ b/latte/cs/tags.texy @@ -12,25 +12,26 @@ Přehled a popis všech tagů (neboli značek či maker) šablonovacího systém .[table-latte-tags language-latte] |## Podmínky -| `{if}` … `{elseif}` … `{else}` … `{/if}` | [podmínka if|#if-elseif-else] -| `{ifset}` … `{elseifset}` … `{/ifset}` | [podmínka ifset|#ifset-elseifset] +| `{if}` … `{elseif}` … `{else}` … `{/if}` | [podmínka if |#if elseif else] +| `{ifset}` … `{elseifset}` … `{/ifset}` | [podmínka ifset |#ifset elseifset] | `{ifchanged}` … `{/ifchanged}` | [test jestli došlo ke změně |#ifchanged] -| `{switch}` `{case}` `{default}` `{/switch}` | [podmínka switch|#switch-case-default] +| `{switch}` `{case}` `{default}` `{/switch}` | [podmínka switch |#switch case default] +| `n:else`, `n:elseif` | [alternativní obsah pro podmínky |#n:else] .[table-latte-tags language-latte] |## Cykly | `{foreach}` … `{/foreach}` | [#foreach] | `{for}` … `{/for}` | [#for] | `{while}` … `{/while}` | [#while] -| `{continueIf $cond}` | [pokračovat další iterací |#continueif-skipif-breakif] -| `{skipIf $cond}` | [přeskočit iteraci |#continueif-skipif-breakif] -| `{breakIf $cond}` | [přerušení cyklu |#continueif-skipif-breakif] -| `{exitIf $cond}` | [včasné ukončení |#exitif] -| `{first}` … `{/first}` | [jde o první průchod? |#first-last-sep] -| `{last}` … `{/last}` | [jde o poslední průchod? |#first-last-sep] -| `{sep}` … `{/sep}` | [bude ještě následovat průchod? |#first-last-sep] -| `{iterateWhile}` … `{/iterateWhile}` | [strukturovaný foreach|#iterateWhile] -| `$iterator` | [speciální proměnná uvnitř foreach |#$iterator] +| `{continueIf $cond}` | [pokračovat další iterací |#continueIf skipIf breakIf] +| `{skipIf $cond}` | [přeskočit iteraci |#continueIf skipIf breakIf] +| `{breakIf $cond}` | [přerušení cyklu |#continueIf skipIf breakIf] +| `{exitIf $cond}` | [včasné ukončení |#exitIf] +| `{first}` … `{/first}` | [jde o první průchod? |#first last sep] +| `{last}` … `{/last}` | [jde o poslední průchod? |#first last sep] +| `{sep}` … `{/sep}` | [bude ještě následovat průchod? |#first last sep] +| `{iterateWhile}` … `{/iterateWhile}` | [strukturovaný foreach |#iterateWhile] +| `$iterator` | [speciální proměnná uvnitř foreach |#iterator] .[table-latte-tags language-latte] |## Vložení dalších šablon @@ -39,14 +40,14 @@ Přehled a popis všech tagů (neboli značek či maker) šablonovacího systém .[table-latte-tags language-latte] |## Bloky, layouty, dědičnost šablon -| `{block}` | [anonymní blok|#block] -| `{block blockname}` | [definuje blok |template-inheritance#bloky] -| `{define blockname}` | [definuje blok pro pozdější použití |template-inheritance#definice] -| `{include blockname}` | [vykreslení bloku|template-inheritance#Vykreslení bloků] +| `{block}` | [anonymní blok |#block] +| `{block blockname}` | [definuje blok |template-inheritance#Bloky] +| `{define blockname}` | [definuje blok pro pozdější použití |template-inheritance#Definice] +| `{include blockname}` | [vykreslení bloku |template-inheritance#Vykreslení bloků] | `{include blockname from 'file.latte'}` | [vykreslí blok ze souboru |template-inheritance#Vykreslení bloků] -| `{import 'file.latte'}` | [načte bloky ze šablony |template-inheritance#horizontální znovupoužití] +| `{import 'file.latte'}` | [načte bloky ze šablony |template-inheritance#Horizontální znovupoužití] | `{layout 'file.latte'}` / `{extends}` | [určuje soubor s layoutem |template-inheritance#Layoutová dědičnost] -| `{embed}` … `{/embed}` | [načte šablonu či blok a umožní přepsat bloky |template-inheritance#jednotková dědičnost] +| `{embed}` … `{/embed}` | [načte šablonu či blok a umožní přepsat bloky |template-inheritance#Jednotková dědičnost] | `{ifset blockname}` … `{/ifset}` | [podmínka, zda existuje blok |template-inheritance#Kontrola existence bloků] .[table-latte-tags language-latte] @@ -56,8 +57,8 @@ Přehled a popis všech tagů (neboli značek či maker) šablonovacího systém .[table-latte-tags language-latte] |## Proměnné -| `{var $foo = value}` | [vytvoří proměnnou |#var-default] -| `{default $foo = value}` | [vytvoří proměnnou, pokud neexistuje |#var-default] +| `{var $foo = value}` | [vytvoří proměnnou |#var default] +| `{default $foo = value}` | [vytvoří proměnnou, pokud neexistuje |#var default] | `{parameters}` | [deklaruje proměnné, typy a výchozí hodnoty |#parameters] | `{capture}` … `{/capture}` | [zachytí blok do proměnné |#capture] @@ -70,15 +71,16 @@ Přehled a popis všech tagů (neboli značek či maker) šablonovacího systém .[table-latte-tags language-latte] |## Překlady -| `{_...}` | [vypíše překlad |#překlady] -| `{translate}` … `{/translate}` | [přeloží obsah |#překlady] +| `{_...}` | [vypíše překlad |#Překlady] +| `{translate}` … `{/translate}` | [přeloží obsah |#Překlady] .[table-latte-tags language-latte] |## Ostatní -| `{contentType}` | [přepne escapování a pošle HTTP hlavičku |#contenttype] +| `{contentType}` | [přepne escapování a pošle HTTP hlavičku |#contentType] | `{debugbreak}` | [umístí do kódu breakpoint |#debugbreak] | `{do}` | [vykoná kód, ale nic nevypíše |#do] | `{dump}` | [dumpuje proměnné do Tracy Bar |#dump] +| `{php}` | [vykoná jakýkoliv PHP kód |#php] | `{spaceless}` … `{/spaceless}` | [odstraní nadbytečné mezery |#spaceless] | `{syntax}` | [změna syntaxe za běhu |#syntax] | `{trace}` | [zobrazí stack trace |#trace] @@ -88,28 +90,33 @@ Přehled a popis všech tagů (neboli značek či maker) šablonovacího systém | `n:class` | [dynamický zápis HTML atributu class |#n:class] | `n:attr` | [dynamický zápis jakýchkoliv HTML atributů |#n:attr] | `n:tag` | [dynamický zápis jména HTML elementu |#n:tag] -| `n:ifcontent` | [Vynechá prázdný HTML tag |#n:ifcontent] +| `n:ifcontent` | [vynechá prázdný HTML tag |#n:ifcontent] .[table-latte-tags language-latte] |## Dostupné pouze v Nette Frameworku | `n:href` | [odkaz používaný v HTML elementech `` |application:creating-links#V šabloně presenteru] | `{link}` | [vypíše odkaz |application:creating-links#V šabloně presenteru] | `{plink}` | [vypíše odkaz na presenter |application:creating-links#V šabloně presenteru] +| `{linkBase}` | [změna základu pro odkazy |application:creating-links#Změna základu pro odkazy] | `{control}` | [vykreslí komponentu |application:components#Vykreslení] -| `{snippet}` … `{/snippet}` | [výstřižek, který lze odeslat AJAXem |application:ajax#tag-snippet] -| `{snippetArea}` | obálka pro výstřižky -| `{cache}` … `{/cache}` | [cachuje část šablony |caching:#cachovani-v-latte] +| `{snippet}` … `{/snippet}` | [výstřižek, který lze odeslat AJAXem |application:ajax#Snippety v Latte] +| `{snippetArea}` | [obálka pro výstřižky |application:ajax#Oblasti snippetů] +| `{cache}` … `{/cache}` | [cachuje část šablony |caching:#Cachování v Latte] .[table-latte-tags language-latte] |## Dostupné pouze s Nette Forms | `{form}` … `{/form}` | [vykreslí značky formuláře |forms:rendering#form] -| `{label}` … `{/label}` | [vykreslí popisku formulářového prvku |forms:rendering#label-input] -| `{input}` | [vykreslí formulářový prvek |forms:rendering#label-input] +| `{label}` … `{/label}` | [vykreslí popisku formulářového prvku |forms:rendering#label input] +| `{input}` | [vykreslí formulářový prvek |forms:rendering#label input] | `{inputError}` | [vypíše chybovou hlášku formulářového prvku |forms:rendering#inputError] | `n:name` | [oživí formulářový prvek |forms:rendering#n:name] -| `{formPrint}` | [navrhne Latte kód pro formulář |forms:rendering#formPrint] -| `{formPrintClass}` | [navrhne PHP kód třídy s daty formuláře |forms:in-presenter#mapovani-na-tridy] -| `{formContext}` … `{/formContext}` | [částečné kreslení formuláře |forms:rendering#specialni-pripady] +| `{formContainer}` … `{/formContainer}` | [kreslení formulářového kontejneru |forms:rendering#Speciální případy] + +.[table-latte-tags language-latte] +|## Dostupné pouze s Nette Assets +| `{asset}` | [vykreslí asset jako HTML element nebo URL |assets:#asset] +| `{preload}` | [generuje preload hints pro optimalizaci výkonu |assets:#preload] +| `n:asset` | [přidá asset atributy do HTML elementů |assets:#n:asset] Vypisování @@ -145,7 +152,7 @@ Jaký je nejdůležitější úkol šablonovacího systému? Zamezit bezpečnost

                                {='one < two'}

                                {* vypíše: '

                                one < two

                                ' *} ``` -Abychom byli přesní, Latte používá kontextově sensitivně escapování, což je tak důležitá a unikátní věc, že jsme tomu věnovali [samostatnou kapitolu|safety-first#Kontextově sensitivní escapování]. +Abychom byli přesní, Latte používá kontextově sensitivně escapování, což je tak důležitá a unikátní věc, že jsme tomu věnovali [samostatnou kapitolu |safety-first#Kontextově sensitivní escapování]. A co když vypisujte obsah kódovaný v HTML z důvěryhodného zdroje? Pak lze snadno escapování vypnout: @@ -195,7 +202,7 @@ To je také důvod, proč se kolem proměnné **nepíší uvozovky**: Latte je u Filtry ------ -Vypsaný výraz může být upraven [filtrem|syntax#filtry]. Takto třeba řetězec převedeme na velká písmena a zkrátíme na maximálně 30 znaků: +Vypsaný výraz může být upraven [filtrem |syntax#Filtry]. Takto třeba řetězec převedeme na velká písmena a zkrátíme na maximálně 30 znaků: ```latte {$string|upper|truncate:30} @@ -227,7 +234,7 @@ Podmínky se chovají stejně, jako jejich protějšky v PHP. Můžete v nich po {/if} ``` -Jako každou párovou značku, tak i dvojici `{if} ... {/if}` lze zapisovat i v podobě [n:attributu|syntax#n:atributy], například: +Jako každou párovou značku, tak i dvojici `{if} ... {/if}` lze zapisovat i v podobě [n:attributu |syntax#n:atributy], například: ```latte

                                Skladem {$count} kusů

                                @@ -245,6 +252,22 @@ Víte, že k n:atributům můžete připojit prefix `tag-`? Pak se bude podmínk Boží. +`n:else` `n:elseif` .{data-version:3.0.11} +------------------------------------------ + +Pokud podmínku `{if} ... {/if}` zapíšete v podobě [n:attributu |syntax#n:atributy], máte možnost uvést i alternativní větev pomocí `n:else` a `n:elseif` (od Latte 3.1): + +```latte +Skladem {$count} kusů + +Neplatný počet + +není dostupné +``` + +Atribut `n:else` lze použít také ve dvojici s [`n:ifset` |#ifset elseifset], [`n:foreach` |#foreach], [`n:try` |#try], [`n:ifcontent` |#n-ifcontent] a [`n:ifchanged` |#ifchanged]. + + `{/if $cond}` ------------- @@ -275,15 +298,15 @@ V odložené podmínce lze použít i `{else}`, ale nikoliv `{elseif}`. .[note] Viz také [`{ifset block}` |template-inheritance#Kontrola existence bloků] -Pomocí podmínky `{ifset $var}` zjistíme, zda proměnná (nebo více proměnných) existuje a má ne*null*ovou hodnotu. Vlastně jde o totéž, jako `if (isset($var))` v PHP. Jako každou párovou značku ji lze zapisovat i v podobě [n:attributu|syntax#n:atributy], tak si to ukažme jako příklad: +Pomocí podmínky `{ifset $var}` zjistíme, zda proměnná (nebo více proměnných) existuje a má ne*null*ovou hodnotu. Vlastně jde o totéž, jako `if (isset($var))` v PHP. Jako každou párovou značku ji lze zapisovat i v podobě [n:attributu |syntax#n:atributy], tak si to ukažme jako příklad: ```latte - + ``` -`{ifchanged}` .{data-version:2.9} ---------------------------------- +`{ifchanged}` +------------- `{ifchanged}` zkontroluje, zda se hodnota proměnné změnila od poslední iterace ve smyčce (foreach, for nebo while). @@ -297,7 +320,7 @@ Pokud ve značce uvedeme jednu či více proměnných, bude kontrolovat, zda se {/foreach} ``` -Pokud však neuvedeme žádný argument, bude se kontrolovat vykreslený obsah oproti jeho předchozímu stavu. To znamená, že v předchozím příkladě můžeme klidně argument ve značce vynechat. A samozřejmě také můžeme použít [n:attribut|syntax#n:atributy]: +Pokud však neuvedeme žádný argument, bude se kontrolovat vykreslený obsah oproti jeho předchozímu stavu. To znamená, že v předchozím příkladě můžeme klidně argument ve značce vynechat. A samozřejmě také můžeme použít [n:attribut |syntax#n:atributy]: ```latte {foreach ($names|sort) as $name} @@ -329,7 +352,7 @@ Je to tedy přesný ekvivalent struktury `match` se kterou přichází PHP 8.0. Jinak {/switch} ``` -.{data-version:2.9} + Klauzule `{case}` může obsahovat více hodnot oddělených čárkami: ```latte @@ -361,7 +384,7 @@ Navíc má několik šikovných vychytávek, o kterých si nyní povíme. Latte třeba kontroluje, zda vytvořené proměnné omylem nepřepisují globální proměnné téhož jména. Zachrání to situace, kdy počítáte s tím, že v `$lang` je aktuální jazyk stránky, a neuvědomíte si, že `foreach $langs as $lang` vám tu proměnnou přepsalo. -Cyklus foreach lze také velmi elegantně a úsporně zapsat pomocí [n:attributu|syntax#n:atributy]: +Cyklus foreach lze také velmi elegantně a úsporně zapsat pomocí [n:attributu |syntax#n:atributy]: ```latte
                                  @@ -390,8 +413,8 @@ Takže se vypíše něco jako: ``` -`{else}` .{data-version:2.9}{toc: foreach-else} ------------------------------------------------ +`{else}` .{toc: foreach-else} +----------------------------- Uvnitř cyklu `foreach` může uvést klauzuli `{else}`, jejíž obsah se zobrazí, pokud je cyklus prázdný: @@ -414,10 +437,10 @@ Uvnitř cyklu `foreach` vytvoří Latte proměnnou `$iterator`, pomocí které m - `$iterator->first` - prochází se cyklem poprvé? - `$iterator->last` - jde o poslední průchod? - `$iterator->counter` - kolikátý je to průchod počítáno od jedné? -- `$iterator->counter0` - kolikátý je to průchod počítáno od nuly? .{data-version:2.9} +- `$iterator->counter0` - kolikátý je to průchod počítáno od nuly? - `$iterator->odd` - jde o lichý průchod? - `$iterator->even` - jde o sudý průchod? -- `$iterator->parent` - iterátor obklopující ten aktuální .{data-version:2.9} +- `$iterator->parent` - iterátor obklopující ten aktuální - `$iterator->nextValue` - následující položka v cyklu - `$iterator->nextKey` - klíč následující položky v cyklu @@ -441,10 +464,9 @@ Latte je mazané a `$iterator->last` funguje nejen u polí, ale i když cyklus p `{first}` `{last}` `{sep}` -------------------------- -Tyto značky lze používat uvnitř cyklu `{foreach}`. Obsah `{first}` se vykreslí, pokud jde o první průchod. -Obsah `{last}` se vykreslí … jestlipak to uhádnete? Ano, pokud jde o poslední průchod. Jde vlastně o zkratky pro `{if $iterator->first}` a `{if $iterator->last}`. +Tyto značky lze používat uvnitř cyklu `{foreach}`. Obsah `{first}` se vykreslí, pokud jde o první průchod. Obsah `{last}` se vykreslí … jestlipak to uhádnete? Ano, pokud jde o poslední průchod. Jde vlastně o zkratky pro `{if $iterator->first}` a `{if $iterator->last}`. -Značky lze také elegantně použít jako [n:attribut|syntax#n:atributy]: +Značky lze také elegantně použít jako [n:attribut |syntax#n:atributy]: ```latte {foreach $rows as $row} @@ -465,10 +487,10 @@ Obsah značky `{sep}` se vykreslí, pokud průchod není poslední, hodí se ted To je docela praktické, že? -`{iterateWhile}` .{data-version:2.10} -------------------------------------- +`{iterateWhile}` +---------------- -Zjednodušuje seskupování lineárních dat během iterování v cyklu foreach tím, že iteraci provádí ve vnořené smyčce, dokud je splněná podmínka. [Přečtěte si návod|cookbook/iteratewhile]. +Zjednodušuje seskupování lineárních dat během iterování v cyklu foreach tím, že iteraci provádí ve vnořené smyčce, dokud je splněná podmínka. [Přečtěte si podrobný návod|cookbook/grouping]. Může také elegantně nahradit `{first}` a `{last}` v příkladu výše: @@ -487,6 +509,8 @@ Může také elegantně nahradit `{first}` a `{last}` v příkladu výše: {/foreach} ``` +Viz také filtry [batch |filters#batch] a [group |filters#group]. + `{for}` ------- @@ -499,7 +523,7 @@ Cyklus zapisujeme úplně stejně jako v PHP: {/for} ``` -Značku lze také použít jako [n:attribut|syntax#n:atributy]: +Značku lze také použít jako [n:attribut |syntax#n:atributy]: ```latte

                                  {$i}

                                  @@ -517,7 +541,7 @@ Cyklus opět zapisujeme úplně stejně jako v PHP: {/while} ``` -Nebo jako [n:attribut|syntax#n:atributy]: +Nebo jako [n:attribut |syntax#n:atributy]: ```latte @@ -547,7 +571,7 @@ Pro řízení jakéhokoliv cyklu lze používat značky `{continueIf ?}` a `{bre {/foreach} ``` -.{data-version:2.9} + Značka `{skipIf}` je velmi podobná jako `{continueIf}`, ale nezvyšuje počítadlo `$iterator->counter`, takže pokud jej vypisujeme a zároveň přeskočíme některé položky, nebudou v číslování díry. A také klauzule `{else}` se vykreslí, když přeskočíme všechny položky. ```latte @@ -585,7 +609,7 @@ Vložení šablony ---------------------------------------- .[note] -Viz také [`{include block}` |template-inheritance#Vykreslení bloků] +Viz také [`{include block}` |template-inheritance#Vykreslení bloků] a [`{embed}` |template-inheritance#Jednotková dědičnost] Značka `{include}` načte a vykreslí uvedenou šablonu. Pokud bychom se bavili v řeči našeho oblíbeného jazyka PHP, je to něco jako: @@ -595,14 +619,10 @@ Značka `{include}` načte a vykreslí uvedenou šablonu. Pokud bychom se bavili Vložené šablony nemají přístup k proměnným aktivního kontextu, mají přístup jen ke globálním proměnným. -Další proměnné můžete předávat tímto způsobem: +Proměnné do vložené šablony můžete předávat tímto způsobem: ```latte -{* od Latte 2.9 *} {include 'template.latte', foo: 'bar', id: 123} - -{* před Latte 2.9 *} -{include 'template.latte', foo => 'bar', id => 123} ``` Název šablony může být jakákoliv výraz v PHP: @@ -612,23 +632,25 @@ Název šablony může být jakákoliv výraz v PHP: {include $ajax ? 'ajax.latte' : 'not-ajax.latte'} ``` -Vložený obsah lze upravit pomocí [filtrů|syntax#filtry]. Následující příklad odebere všechno HTML a upraví velikost písmen: +Jestli šablona existuje lze ověřit funkcí [`hasTemplate()`|functions#hasTemplate]. + +Vložený obsah lze upravit pomocí [filtrů |syntax#Filtry]. Následující příklad odebere všechno HTML a upraví velikost písmen: ```latte {include 'heading.latte' |stripHtml|capitalize} ``` -Defaultně [dědičnost šablon|template-inheritance] v tomto případě nijak nefiguruje. I když v inkludované šabloně můžeme používat bloky, nedojde k nahrazení odpovídajících bloků v šabloně, do které se inkluduje. Přemýšlejte o inkludovaných šabloných jako samostatných odstíněných částech stránek nebo modulů. Toto chování se dá změnit pomocí modifikátoru `with blocks` (od Latte 2.9.1): +Defaultně [dědičnost šablon|template-inheritance] v tomto případě nijak nefiguruje. I když v inkludované šabloně můžeme používat bloky, nedojde k nahrazení odpovídajících bloků v šabloně, do které se inkluduje. Přemýšlejte o inkludovaných šabloných jako samostatných odstíněných částech stránek nebo modulů. Toto chování se dá změnit pomocí modifikátoru `with blocks`: ```latte {include 'template.latte' with blocks} ``` -Vztah mezi názvem souboru uvedeným ve značce a souborem na disku je věcí [loaderu|extending-latte#Loadery]. +Vztah mezi názvem souboru uvedeným ve značce a souborem na disku je věcí [loaderu|loaders]. -`{sandbox}` .{data-version:2.8} -------------------------------- +`{sandbox}` +----------- Při vkládání šablony vytvořené koncovým uživatelem byste měli zvážit sandbox režim (více informací v [dokumentaci sandboxu |sandbox]): @@ -641,9 +663,9 @@ Při vkládání šablony vytvořené koncovým uživatelem byste měli zvážit ========= .[note] -Viz také [`{block name}` |template-inheritance#bloky] +Viz také [`{block name}` |template-inheritance#Bloky] -Bloky bez jména slouží jako způsob jak aplikovat [filtry|syntax#filtry] na část šablony. Například takto lze aplikovat filtr [strip|filters#strip], který odstraní zbytečné mezery: +Bloky bez jména slouží jako způsob jak aplikovat [filtry |syntax#Filtry] na část šablony. Například takto lze aplikovat filtr [strip |filters#spaceless], který odstraní zbytečné mezery: ```latte {block|strip} @@ -658,8 +680,8 @@ Bloky bez jména slouží jako způsob jak aplikovat [filtry|syntax#filtry] na ============== -`{try}` .{data-version:2.9} ---------------------------- +`{try}` +------- Díky této značce je extrémně snadné vytvářet robustní šablony. @@ -689,7 +711,7 @@ Obsah ve volitelné klauzuli `{else}` se vykreslí jen když nastane výjimka: {/try} ``` -Značku lze také použít jako [n:attribut|syntax#n:atributy]: +Značku lze také použít jako [n:attribut |syntax#n:atributy]: ```latte
                                    @@ -697,11 +719,11 @@ Značku lze také použít jako [n:attribut|syntax#n:atributy]:
                                  ``` -Je také možné definovat vlastní [obslužný handler pro výjimky|develop#exception handler], například kvůli logování. +Je také možné definovat vlastní [obslužný handler pro výjimky |develop#Exception handler], například kvůli logování. -`{rollback}` .{data-version:2.9} --------------------------------- +`{rollback}` +------------ Blok `{try}` lze zastavit a přeskočit také ručně pomocí `{rollback}`. Díky tomu nemusíte předem kontrolovat všechna vstupní data a až během vykreslování se můžete rozhodnout, že objekt nechcete vůbec vykreslit: @@ -736,13 +758,13 @@ Nové proměnné vytvoříme v šabloně značkou `{var}`: {var $name = 'John Smith', $age = 27} ``` -Značka `{default}` funguje podobně s tím rozdílem, že vytváří proměnné jen tehdy, pokud neexistují: +Značka `{default}` funguje podobně, ale vytváří proměnné jen tehdy, pokud neexistují. Pokud proměnná již existuje a obsahuje hodnotu `null`, nebude přepsána: ```latte {default $lang = 'cs'} ``` -Od Latte 2.7 můžete uvádět i [typy proměnných|type-system]. Zatím jsou informativní a Latte je nekontroluje. +Můžete uvádět i [typy proměnných|type-system]. Zatím jsou informativní a Latte je nekontroluje. ```latte {var string $name = $article->getTitle()} @@ -750,8 +772,8 @@ Od Latte 2.7 můžete uvádět i [typy proměnných|type-system]. Zatím jsou in ``` -`{parameters}` .{data-version:2.9} ----------------------------------- +`{parameters}` +-------------- Tak jako funkce deklaruje své parametry, může i šablona na začátku deklarovat své proměnné: @@ -783,7 +805,7 @@ Zachytí výstup do proměnné:

                                  Captured: {$var}

                                  ``` -Značku lze také zapsat jako [n:attribut|syntax#n:atributy]: +Značku lze, podobně jako každou párovou značku, zapsat také jako [n:attribut |syntax#n:atributy]: ```latte
                                    @@ -791,6 +813,8 @@ Značku lze také zapsat jako [n:attribut|syntax#n:atributy]:
                                  ``` +HTML výstup se do proměnné `$var` uloží v podobě objektu `Latte\Runtime\Html`, aby [nedošlo k nežádoucímu escapování |develop#Vypnutí auto-escapování proměnné] při vypsání. + Ostatní ======= @@ -841,14 +865,12 @@ Označuje místo, kde dojde k pozastavení běhu programu a spuštění debugger `{do}` ------ -Vykoná kód a nic nevypisuje. +Vykoná PHP kód a nic nevypisuje. Stejně jako u všech dalších značek se PHP kódem rozumí jeden výraz, viz [omezení PHP |syntax#Omezení PHP v Latte]. ```latte {do $num++} ``` -V Latte 2.7 a starších se používalo `{php}`. - `{dump}` -------- @@ -865,10 +887,16 @@ Vypíše proměnnou nebo aktuální kontext. Vyžaduje knihovnu [Tracy|tracy:]. +`{php}` +------- + +Umožňuje vykonat jakýkoliv PHP kód. Značku je nutné aktivovat pomocí rozšíření [RawPhpExtension |develop#RawPhpExtension]. + + `{spaceless}` ------------- -Odstraní zbytečné bílé místo z výstupu. Funguje podobně jako filtr [spaceless|filters#spaceless]. +Odstraní zbytečné bílé místo z výstupu. Funguje podobně jako filtr [spaceless |filters#spaceless]. ```latte {spaceless} @@ -884,7 +912,7 @@ Vygeneruje
                                  • Hello
                                  ``` -Značku lze také zapsat jako [n:attribut|syntax#n:atributy]. +Značku lze také zapsat jako [n:attribut |syntax#n:atributy]. `{syntax}` @@ -903,13 +931,13 @@ S využitím n:atributů lze vypnout Latte třeba jen jednomu bloku JavaScriptu: ``` -Latte lze velmi pohodlně používat i uvnitř JavaScriptu, jen se stačí vynout konstrukcím jako v tomto příkladě, kdy následuje písmeno hned za `{`, viz [Latte uvnitř JavaScriptu nebo CSS|recipes#Latte uvnitř JavaScriptu nebo CSS]. +Latte lze velmi pohodlně používat i uvnitř JavaScriptu, jen se stačí vynout konstrukcím jako v tomto příkladě, kdy následuje písmeno hned za `{`, viz [Latte uvnitř JavaScriptu nebo CSS |recipes#Latte uvnitř JavaScriptu nebo CSS]. Pokud Latte vypnete pomocí `{syntax off}` (tj. značkou, nikoliv n:atributem), bude důsledně ignorovat všechny značky až do `{/syntax}` -{trace} .{data-version:2.10} ----------------------------- +{trace} +------- Vyhodí výjimku `Latte\RuntimeException`, jejíž stack trace se nese v duchu šablon. Tedy místo volání funkcí a metod obsahuje volání bloků a vkládání šablon. Pokud používáte nástroj pro přehledné zobrazení vyhozených výjimek, jako je například [Tracy|tracy:], přehledně se vám zobrazí call stack včetně všech předávaných argumentů. @@ -921,6 +949,9 @@ Pomocníci HTML kodéra n:class ------- +.[note] +Od verze Latte 3.1 získal standardní HTML atribut `class` [stejnou funkcionalitu |html-attributes#třídy-classes]. Není tedy již nutné používat n:class. + Díky `n:class` velice snadno vygenerujete HTML atribut `class` přesně podle představ. Příklad: potřebuji, aby aktivní prvek měl třídu `active`: @@ -953,7 +984,7 @@ A všechny prvky mají mít třídu `list-item`: n:attr ------ -Atribut `n:attr` umí se stejnou elegancí jako má [n:class|#n:class] generovat libovolné HTML atributy. +Atribut `n:attr` umí se stejnou elegancí jako má [#n:class] generovat libovolné HTML atributy. ```latte {foreach $data as $item} @@ -971,9 +1002,15 @@ V závislosti na vrácených hodnotách vypíše např.: ``` +Funkce inteligentních atributů v Latte 3.1, jako je vynechání hodnot `null` nebo předávání polí do `class` nebo `style`, fungují také v rámci `n:attr`: -n:tag .{data-version:2.10} --------------------------- +```latte +
                                  +``` + + +n:tag +----- Atribut `n:tag` umí dynamicky měnit název HTML elementu. @@ -987,6 +1024,8 @@ Pokud je `$heading === null`, vypíše se beze změny tag `

                                  `. Jinak se změn

                                  ...

                                  ``` +Protože Latte je bezpečný šablonovací systém, kontroluje, zda je nový název značky platný a neobsahuje žádné nežádoucí nebo škodlivé hodnoty. + n:ifcontent ----------- @@ -1013,10 +1052,10 @@ Vypíše v závislosti na hodnotě proměnné `$error`: ``` -Překlady .{data-version:3.0} -============================ +Překlady +======== -Aby značky pro překlad fungovaly, je potřeba [aktivovat překladač|develop#TranslatorExtension]. Pro překlad můžete také použít filtr [`translate`|filters#translate]. +Aby značky pro překlad fungovaly, je potřeba [aktivovat překladač |develop#TranslatorExtension]. Pro překlad můžete také použít filtr [`translate` |filters#translate]. `{_...}` @@ -1047,7 +1086,7 @@ Překládá části šablony: {translate domain: order}Lorem ipsum ...{/translate} ``` -Značku lze také zapsat jako [n:attribut|syntax#n:atributy], pro překlad vnitřku elementu: +Značku lze také zapsat jako [n:attribut |syntax#n:atributy], pro překlad vnitřku elementu: ```latte

                                  Objednávka

                                  diff --git a/latte/cs/template-inheritance.texy b/latte/cs/template-inheritance.texy index 92e78495b2..33bb1fd99f 100644 --- a/latte/cs/template-inheritance.texy +++ b/latte/cs/template-inheritance.texy @@ -44,7 +44,7 @@ Podřízená šablona může vypadat takto: {/block} ``` -Klíčem je zde značka `{layout}`. Říká Latte, že tato šablona „rozšiřuje“ další šablonu. Když Latte vykresluje tuto šablonu, nejprve najde rodiče - v tomto případě `layout.latte`. +Klíčem je zde značka `{layout}`. Říká Latte, že tato šablona „rozšiřuje“ další šablonu. Když Latte vykresluje tuto šablonu, nejprve najde nadřazenou šablonu - v tomto případě `layout.latte`. V tomto okamžiku si Latte všimne tří blokových značek v `layout.latte` a nahradí tyto bloky obsahem podřízené šablony. Vzhledem k tomu, že podřízená šablona nedefinovala blok *footer*, použije se místo toho obsah z nadřazené šablony. Obsah ve značce `{block}` v nadřazené šabloně se vždy používá jako záložní. @@ -123,7 +123,7 @@ Jako název nadřazené šablony lze použít proměnnou nebo jakýkoli výraz P {layout $standalone ? 'minimum.latte' : 'layout.latte'} ``` -Můžete také použít Latte API k [automatickému |develop#automaticke-dohledavani-layoutu] výběru šablony layoutu. +Můžete také použít Latte API k [automatickému |develop#Automatické dohledávání layoutu] výběru šablony layoutu. Tipy @@ -132,11 +132,11 @@ Zde je několik tipů pro práci s layoutovou dědičností: - Pokud v šabloně použijete `{layout}`, musí to být první značka šablony v této šabloně. -- Layout se může [dohledávat automaticky |develop#automaticke-dohledavani-layoutu] (jako třeba v [presenterech |application:templates#hledani-sablon]). V takovém případě pokud šablona nemá mít layout, oznámí to značkou `{layout none}`. +- Layout se může [dohledávat automaticky |develop#Automatické dohledávání layoutu] (jako třeba v [presenterech |application:templates#Hledání šablon]). V takovém případě pokud šablona nemá mít layout, oznámí to značkou `{layout none}`. - Značka `{layout}` má alias `{extends}`. -- Název souboru layoutu závisí na [loaderu |extending-latte#Loadery]. +- Název souboru layoutu závisí na [loaderu |loaders]. - Můžete mít tolik bloků, kolik chcete. Pamatujte, že podřízené šablony nemusí definovat všechny nadřazené bloky, takže můžete vyplnit přiměřené výchozí hodnoty v několika blocích a poté definovat pouze ty, které potřebujete později. @@ -149,8 +149,7 @@ Viz také anonymní [`{block}` |tags#block] Blok představuje způsob, jak změnit způsob vykreslování určité části šablony, ale nijak nezasahuje do logiky kolem něj. V následujícím příkladu si ukážeme, jak blok funguje, ale také jak nefunguje: -```latte -{* parent.Latte *} +```latte .{file: parent.latte} {foreach $posts as $post} {block post}

                                  {$post->title}

                                  @@ -161,8 +160,7 @@ Blok představuje způsob, jak změnit způsob vykreslování určité části Pokud tuto šablonu vykreslíte, bude výsledek přesně stejný se značkami `{block}` i bez nich. Bloky mají přístup k proměnným z vnějších oborů. Jen dávají možnost se nechat přepsat podřízenou šablonou: -```latte -{* child.Latte *} +```latte .{file: child.latte} {layout 'parent.Latte'} {block post} @@ -197,13 +195,13 @@ foo: {$foo} // prints: foo bar: {$bar ?? 'not defined'} // prints: not defined ``` -Obsah bloku lze upravit pomocí [filtrů |syntax#filtry]. Následující příklad odebere všechny HTML a změní velikost písmen: +Obsah bloku lze upravit pomocí [filtrů |syntax#Filtry]. Následující příklad odebere všechny HTML a změní velikost písmen: ```latte {block title|stripHtml|capitalize}...{/block} ``` -Značku lze také zapsat jako [n:attribut|syntax#n:atributy]: +Značku lze také zapsat jako [n:attribut |syntax#n:atributy]: ```latte
                                  @@ -212,10 +210,10 @@ Značku lze také zapsat jako [n:attribut|syntax#n:atributy]: ``` -Lokální bloky .{data-version:2.9} ---------------------------------- +Lokální bloky +------------- -Každý blok přepisuje obsah nadřazeného bloku se stejným názvem – kromě lokálních bloků. Ve třídách by šlo o něco jako privátní metody. Šablonu tak můžete tvořit bez obav, že kvůli shodě jmen bloků by byly přepsány z jiné šablonoy. +Každý blok přepisuje obsah nadřazeného bloku se stejným názvem – kromě lokálních bloků. Ve třídách by šlo o něco jako privátní metody. Šablonu tak můžete tvořit bez obav, že kvůli shodě jmen bloků by byly přepsány z jiné šablony. ```latte {block local helper} @@ -246,17 +244,13 @@ Lze také vypsat blok z jiné šablony: Vykreslovaný blok nemá přístup k proměnným aktivního kontextu, kromě případů, kdy je blok definován ve stejném souboru, kde je i vložen. Má však přístup ke globálním proměnným. -Proměnné můžete předávat tímto způsobem: +Proměnné můžete do bloku předávat tímto způsobem: ```latte -{* od Latte 2.9 *} {include footer, foo: bar, id: 123} - -{* před Latte 2.9 *} -{include footer, foo => bar, id => 123} ``` -Jako název bloku lze použít proměnnou nebo jakýkoli výraz v PHP. V takovém případě před proměnnou ještě doplníme klíčové slovo `block`, aby už v době kompilace Latte vědělo, že jde blok, a nikoliv o [vkládání šablony|tags#include], jejíž název by také mohl být v proměnné: +Jako název bloku lze použít proměnnou nebo jakýkoli výraz v PHP. V takovém případě před proměnnou ještě doplníme klíčové slovo `block`, aby už v době kompilace Latte vědělo, že jde blok, a nikoliv o [vkládání šablony |tags#include], jejíž název by také mohl být v proměnné: ```latte {var $name = footer} @@ -283,7 +277,7 @@ Blok lze vykreslit i uvnitř sebe samého, což je například užitečné při Místo `{include menu, ...}` pak můžeme napsat `{include this, ...}`, kde `this` znamená aktuální blok. -Vykreslovaný blok lze upravit pomocí [filtrů |syntax#filtry]. Následující příklad odebere všechna HTML a změní velikost písmen: +Vykreslovaný blok lze upravit pomocí [filtrů |syntax#Filtry]. Následující příklad odebere všechna HTML a změní velikost písmen: ```latte {include heading|stripHtml|capitalize} @@ -309,11 +303,11 @@ Definice `{define}` .{toc: Definice} Kromě bloků existují v Latte také „definice“. V běžných programovacích jazycích bychom je přirovnali k funkcím. Jsou užitečné k opětovnému použití fragmentů šablony, abyste se neopakovali. -Latte se snaží dělat věci jednoduše, takže v zásadě jsou definice stejné jako bloky a **všechno, co je o blocích řečeno, platí také pro definice**. Liší se od bloků pouze třemi způsoby: +Latte se snaží dělat věci jednoduše, takže v zásadě jsou definice stejné jako bloky a **všechno, co je o blocích řečeno, platí také pro definice**. Liší se od bloků tím, že: -1) mohou přijímat argumenty -2) nemohou mít [filtry |syntax#filtry] -3) jsou uzavřeny ve značkách `{define}` a obsah uvnitř těchto značek se neodesílá na výstup, dokud je nevložíte. Díky tomu je můžete vytvořit kdekoli: +1) jsou uzavřeny ve značkách `{define}` +2) vykreslí se teprve, až když je vložíte přes `{include}` +3) lze jim definovat parametry podobně jako funkcím v PHP ```latte {block foo}

                                  Hello

                                  {/block} @@ -326,10 +320,9 @@ Latte se snaží dělat věci jednoduše, takže v zásadě jsou definice stejn {* prints:

                                  World

                                  *} ``` -Představte si, že máte obecnou pomocnou šablonu, která definuje, jak vykreslit formuláře HTML pomocí definic: +Představte si, že máte pomocnou šablonu s kolekcí definic, jak kreslit formuláře HTML. -```latte -{* forms.latte *} +```latte .{file: forms.latte} {define input, $name, $value, $type = 'text'} {/define} @@ -339,25 +332,24 @@ Představte si, že máte obecnou pomocnou šablonu, která definuje, jak vykres {/define} ``` -Argumenty jsou vždy volitelné s výchozí hodnotou `null`, pokud není uvedena výchozí hodnota (zde `text` je výchozí hodnota pro `$type`, funguje od Latte 2.9.1). Od Latte 2.7 lze deklarovat také typy parametrů: `{define input, string $name, ...}`. +Argumenty jsou vždy volitelné s výchozí hodnotou `null`, pokud není uvedena výchozí hodnota (zde `'text'` je výchozí hodnota pro `$type`). Lze deklarovat také typy parametrů: `{define input, string $name, ...}`. -Definice nemají přístup k proměnným aktivního kontextu, ale mají přístup k globálním proměnným. - -Vkládají se [stejným způsobem jako blok |#Vykreslení bloků]: +Šablonu s definicemi nahrajeme pomocí [`{import}` |#Horizontální znovupoužití]. Samotné definice se vykreslují [stejným způsobem jako bloky |#Vykreslení bloků]: ```latte

                                  {include input, 'password', null, 'password'}

                                  {include textarea, 'comment'}

                                  ``` +Definice nemají přístup k proměnným aktivního kontextu, ale mají přístup ke globálním proměnným. + Dynamické názvy bloků --------------------- Latte dovoluje velkou flexibilitu při definování bloků, protože název bloku může být jakýkoli výraz PHP. Tento příklad definuje tři bloky s názvy `hi-Peter`, `hi-John` a `hi-Mary`: -```latte -{* parent.latte *} +```latte .{file: parent.latte} {foreach [Peter, John, Mary] as $name} {block "hi-$name"}Hi, I am {$name}.{/block} {/foreach} @@ -365,8 +357,7 @@ Latte dovoluje velkou flexibilitu při definování bloků, protože název blok V podřízené šabloně pak můžeme předefinovat například jen jeden blok: -```latte -{* child.latte *} +```latte .{file: child.latte} {block hi-John}Hello. I am {$name}.{/block} ``` @@ -383,7 +374,7 @@ Kontrola existence bloků `{ifset}` .{toc: Kontrola existence bloků} ------------------------------------------------------------------- .[note] -Viz také [`{ifset $var}` |tags#ifset-elseifset] +Viz také [`{ifset $var}` |tags#ifset elseifset] Pomocí testu `{ifset blockname}` zkontrolujeme, zda v aktuálním kontextu blok (nebo více bloků) existuje: @@ -397,7 +388,7 @@ Pomocí testu `{ifset blockname}` zkontrolujeme, zda v aktuálním kontextu blok {/ifset} ``` -Jako název bloku lze použít proměnnou nebo jakýkoli výraz v PHP. V takovém případě před proměnnou ještě doplníme klíčové slovo `block`, aby bylo jasné, že nejde o test existence [proměnných|tags#ifset-elseifset]: +Jako název bloku lze použít proměnnou nebo jakýkoli výraz v PHP. V takovém případě před proměnnou ještě doplníme klíčové slovo `block`, aby bylo jasné, že nejde o test existence [proměnných |tags#ifset elseifset]: ```latte {ifset block $name} @@ -405,6 +396,14 @@ Jako název bloku lze použít proměnnou nebo jakýkoli výraz v PHP. V takové {/ifset} ``` +Existenci bloků ověřuje také funkce [`hasBlock()` |functions#hasBlock]: + +```latte +{if hasBlock(header) || hasBlock(footer)} + ... +{/if} +``` + Tipy ---- @@ -414,49 +413,39 @@ Několik tipů pro práci s bloky: - Pro lepší čitelnost můžete název bloku uvést ve značce `{/block}`, například `{/block footer}`. Název se však musí shodovat s názvem bloku. Ve větších šablonách vám tato technika pomůže zjistit, které značky bloku se zavírají. -- Ve stejné šabloně nemůžete přímo definovat více značek bloků se stejným názvem. Toho však lze dosáhnout pomocí [dynamických názvů bloků|#dynamické názvy bloků]. +- Ve stejné šabloně nemůžete přímo definovat více značek bloků se stejným názvem. Toho však lze dosáhnout pomocí [dynamických názvů bloků |#Dynamické názvy bloků]. - Můžete použít [n:atributy |syntax#n:atributy] k definování bloků jako `

                                  Welcome to my awesome homepage

                                  ` -- Bloky lze také použít bez názvů pouze k použití [filtrů|syntax#filtry]: `{block|strip} hello {/block}` +- Bloky lze také použít bez názvů pouze k použití [filtrů |syntax#Filtry]: `{block|strip} hello {/block}` Horizontální znovupoužití `{import}` .{toc: Horizontální znovupoužití} ====================================================================== -Horizontální znovupoužití je v Latte třetím mechanismem opětovné použitelnosti a dědičnosti. Umožňuje načíst bloky z jiných šablon. Je to podobné jako vytvoření souboru PHP s pomocnými funkcemi. - -I když je layoutová dědičnost šablony jednou z nejmocnějších funkcí Latte, je omezena na jednoduchou dědičnost; Šablona může rozšířit pouze jednu další šablonu. Díky tomuto omezení je layoutová dědičnost snadno srozumitelná a snadno laditelná: - -```latte -{layout 'layout.latte'} - -{block title}...{/block} -{block content}...{/block} -``` +Horizontální znovupoužití je v Latte třetím mechanismem pro opětovné použití a dědičnost. Umožňuje načítat bloky z jiných šablon. Je to podobné, jako když si v PHP vytvoříme soubor s pomocnými funkcemi, který potom načítáme pomocí `require`. -Horizontální znovupoužití je způsob, jak dosáhnout vícenásobné dědičnosti, ale bez složitosti: +I když je layoutová dědičnost šablony jednou z nejmocnějších funkcí Latte, je omezena na jednoduchou dědičnost - šablona může rozšířit pouze jednu další šablonu. Horizontální znovupoužití je způsob, jak dosáhnout vícenásobné dědičnosti. -```latte -{layout 'layout.latte'} +Mějme soubor s definicemi bloků: -{import 'blocks.latte'} +```latte .{file: blocks.latte} +{block sidebar}...{/block} -{block title}...{/block} -{block content}...{/block} +{block menu}...{/block} ``` -Příkaz `{import}` říká Latte, aby importoval všechny bloky a [#definice] definované v `blocks.latte` do aktuální šablony. +Pomocí příkazu `{import}` naimportujeme všechny bloky a [#definice] definované v `blocks.latte` do jiné šablony: -```latte -{* blocks.latte *} +```latte .{file: child.latte} +{import 'blocks.latte'} -{block sidebar}...{/block} +{* nyní lze použít bloky sidebar a menu *} ``` -V tomto příkladu příkaz `{import}` importuje blok `sidebar` do hlavní šablony. +Pokud bloky importujete v nadřazené šabloně (tj. použijete `{import}` v `layout.latte`), budou bloky k dispozici i ve všech podřízených šablonách, což je velmi praktické. -Importovaná šablona nesmí [rozšiřovat|#Layoutová dědičnost] další šablonu a její tělo by mělo být prázdné. Importovaná šablona však může importovat další šablony. +Šablona, která je určená k importování (např. `blocks.latte`), nesmí [rozšiřovat |#Layoutová dědičnost] další šablonu, tj. používat `{layout}`. Může však importovat další šablony. Značka `{import}` by měla být první značkou šablony po `{layout}`. Název šablony může být jakýkoli výraz PHP: @@ -466,10 +455,10 @@ Značka `{import}` by měla být první značkou šablony po `{layout}`. Název V šabloně můžete použít tolik `{import}` příkazů, kolik chcete. Pokud dvě importované šablony definují stejný blok, vyhrává první z nich. Nejvyšší prioritu má ale hlavní šablona, která může přepsat jakýkoli importovaný blok. -Ke všem přepsaným blokům se dá postupně dostat tím že je vložíme jako vložen jak [#rodičovský blok]: +Obsah přepsaných bloků se dá zachovat tak, že blok vložíme stejně, jako se vkládá [#rodičovský blok]: ```latte -{layout 'base.latte'} +{layout 'layout.latte'} {import 'blocks.latte'} @@ -484,8 +473,8 @@ Ke všem přepsaným blokům se dá postupně dostat tím že je vložíme jako V tomto příkladu `{include parent}` zavolá blok `sidebar` ze šablony `blocks.latte`. -Jednotková dědičnost `{embed}` .{toc: Jednotková dědičnost}{data-version:2.9} -============================================================================= +Jednotková dědičnost `{embed}` .{toc: Jednotková dědičnost} +=========================================================== Jednotková dědičnost rozšiřuje myšlenku layoutové dědičnosti na úroveň fragmentů obsahu. Zatímco layoutová dědičnost pracuje s „kostrou dokumentu“, kterou oživují podřízené šablony, jednotková dědičnost vám umožňuje vytvářet kostry pro menší jednotky obsahu a znovu je používat kdekoli chcete. @@ -537,7 +526,7 @@ Výstup může vypadat takto: ``` -Bloky uvnitř vložených značek tvoří samostatnou vrstvu nezávislou na ostatních blocích. Proto mohou mít stejný název jako blok mimo vložení a nejsou nijak ovlivněny. Pomocí značky [include |#Vykreslení bloků] uvnitř značek `{embed}` můžete vložit bloky zde vytvořené, bloky z vložené šablony (které *nejsou* [lokální |#lokální bloky]) a také bloky z hlavní šablony, které naopak *jsou* lokální. Můžete také [importovat bloky |#horizontální znovupoužití] z jiných souborů: +Bloky uvnitř vložených značek tvoří samostatnou vrstvu nezávislou na ostatních blocích. Proto mohou mít stejný název jako blok mimo vložení a nejsou nijak ovlivněny. Pomocí značky [include |#Vykreslení bloků] uvnitř značek `{embed}` můžete vložit bloky zde vytvořené, bloky z vložené šablony (které *nejsou* [lokální |#Lokální bloky]) a také bloky z hlavní šablony, které naopak *jsou* lokální. Můžete také [importovat bloky |#Horizontální znovupoužití] z jiných souborů: ```latte {block outer}…{/block} @@ -560,7 +549,7 @@ Bloky uvnitř vložených značek tvoří samostatnou vrstvu nezávislou na osta Vložené šablony nemají přístup k proměnným aktivního kontextu, ale mají přístup k globálním proměnným. -Pomocí `{embed}` lze vkládat nejen šablony, ale i jiné bloky, a tedy předchozí příklad by se dal zapsat tímto způsobem: .{data-version:2.10} +Pomocí `{embed}` lze vkládat nejen šablony, ale i jiné bloky, a tedy předchozí příklad by se dal zapsat tímto způsobem: ```latte {define collapsible} diff --git a/latte/cs/type-system.texy b/latte/cs/type-system.texy index b80ce38c88..7dcf6e8dce 100644 --- a/latte/cs/type-system.texy +++ b/latte/cs/type-system.texy @@ -1,11 +1,11 @@ Typový systém ************* -
                                  +
                                  Typový systém je klíčový pro vývoj robustních aplikací. Latte přináší podporou typů i do šablon. Díky tomu, že víme, jaký datový či objektový typ je v každé proměnné, může -- IDE správně našeptávat (viz [integrace|recipes#Editory a IDE]) +- IDE správně našeptávat (viz [integrace |recipes#Editory a IDE]) - statická analýza odhalit chyby Obojí zásadním způsobem zvyšuje kvalitu a pohodlí vývoje. @@ -21,7 +21,7 @@ Jak začít používat typy? Vytvořte si třídu šablony, např. `CatalogTempl class CatalogTemplateParameters { public function __construct( - public string $langs, + public string $lang, /** @var ProductEntity[] */ public array $products, public Address $address, @@ -35,14 +35,11 @@ $latte->render('template.latte', new CatalogTemplateParameters( )); ``` -A dále na začátek šablony vložte značku `{templateType}` s plným názvem třídy (včetně namespace). To definuje, že v šabloně jsou proměnné `$langs` a `$products` včetně příslušných typů. -Typy lokálních proměnných můžete uvést pomocí značek [`{var}` |tags#var-default], `{varType}`, [`{define}` |template-inheritance#definice]. +A dále na začátek šablony vložte značku `{templateType}` s plným názvem třídy (včetně namespace). To definuje, že v šabloně jsou proměnné `$lang` a `$products` včetně příslušných typů. Typy lokálních proměnných můžete uvést pomocí značek [`{var}` |tags#var default], `{varType}`, [`{define}` |template-inheritance#Definice]. Od té chvíle vám může IDE správně našeptávat. -Jak si ušetřit práci? Jak co nejsnáze napsat třídu s parametry šablony nebo značky `{varType}`? Nechte si je vygenerovat. -Od toho existuje dvojice značek `{templatePrint}` a `{varPrint}`. -Pokud je umístíte do šablony, místo běžného vykreslení se zobrazí návrh kódu třídy resp. seznam značek `{varType}`. Kód pak stačí jedním kliknutím označit a zkopírovat do projektu. +Jak si ušetřit práci? Jak co nejsnáze napsat třídu s parametry šablony nebo značky `{varType}`? Nechte si je vygenerovat. Od toho existuje dvojice značek `{templatePrint}` a `{varPrint}`. Pokud je umístíte do šablony, místo běžného vykreslení se zobrazí návrh kódu třídy resp. seznam značek `{varType}`. Kód pak stačí jedním kliknutím označit a zkopírovat do projektu. `{templateType}` @@ -56,7 +53,7 @@ Typy parametrů předávaných do šablony deklarujeme pomocí třídy: `{varType}` ----------- -Jak deklarovat typy proměnných? K tomu slouží značky `{varType}` pro existující proměnné, nebo [`{var}` |tags#var-default]: +Jak deklarovat typy proměnných? K tomu slouží značky `{varType}` pro existující proměnné, nebo [`{var}` |tags#var default]: ```latte {varType Nette\Security\User $user} diff --git a/latte/cs/why-use.texy b/latte/cs/why-use.texy new file mode 100644 index 0000000000..331ecdcd60 --- /dev/null +++ b/latte/cs/why-use.texy @@ -0,0 +1,80 @@ +Proč používat šablony? +********************** + + +Proč bych měl použít šablonovací systém v PHP? +---------------------------------------------- + +Proč používat šablonovací systém v PHP, když je PHP samo o sobě šablonovací jazyk? + +Pojďme si nejprve stručně zrekapitulovat historii tohoto jazyka, která je plná zajímavých zvratů. Jeden z prvních programovacích jazyků používaných pro generování HTML stránek byl jazyk C. Brzy se však ukázalo, že jeho použití pro tento účel je nepraktické. Rasmus Lerdorf proto vytvořil PHP, které usnadnilo generování dynamického HTML s jazykem C na backendu. PHP bylo tedy původně navrženo jako šablonovací jazyk, ale postupem času získalo další funkce a stalo se plnohodnotným programovacím jazykem. + +Přesto stále funguje i jako šablonovací jazyk. V souboru PHP může být zapsaná HTML stránka, ve které se pomocí `` vypisují proměnné atd. + +Již v počátcích historie PHP vznikl šablonovací systém Smarty, jehož účelem bylo striktně oddělit vzhled (HTML/CSS) od aplikační logiky. Tedy záměrně poskytoval omezenější jazyk, než samotné PHP, aby vývojář nemohl například provést dotaz do databáze ze šablony apod. Na druhou stranu představoval další závislost v projektech, zvyšoval jejich složitost a programátoři se museli učit nový jazyk Smarty. Takový přínos byl sporný a nadále se pro šablony používalo prosté PHP. + +V průběhu času se začaly šablonovací systémy stávat užitečnými. Přišly s konceptem [dědičnosti |template-inheritance], [sandbox režimem|sandbox] a řadou dalších funkcí, který významně zjednodušily tvorbu šablon oproti čistému PHP. Do popředí se dostalo téma bezpečnosti, existence [zranitelností jako XSS|safety-first] a nutnost [escapování |#Co je to escapování]. Šablonovací systémy přišly s autoescapováním, aby zmizelo riziko, že programátor na to zapomene a vznikne vážná bezpečnostní díra (za chvíli si ukážeme, že to má jistá úskalí). + +Přínosy šablonovacích systému dnes výrazně převyšují náklady spojené s jejich nasazením. Proto má smysl je používat. + + +Proč je lepší Latte než třeba Twig nebo Blade? +---------------------------------------------- + +Důvodů je hned několik – některé jsou příjemné a jiné zásadně užitečné. Latte je kombinací příjemného s užitečným. + +*Nejprve ten příjemný:* Latte má stejnou [syntaxi jako PHP |syntax#Latte rozumí PHP]. Liší se jen zápis značek, místo `` preferuje kratší `{` a `}`. To znamená, že se nemusíte učit nový jazyk. Náklady na zaškolení jsou minimální. A hlavně se při vývoji nemusíte neustále "přepínat" mezi jazykem PHP a jazykem šablony, jelikož jsou oba stejné. Na rozdíl od šablon Twig, které používají jazyk Python, a programátor se tak musí přepínat mezi dvěma odlišnými jazyky. + +*A nyní důvod nesmírně užitečný*: Všechny šablonovací systémy, jako Twig, Blade nebo Smarty, přišly v průběhu evoluce s ochranou proti XSS v podobě automatického [escapování |#Co je to escapování]. Přesněji řečeno automatického volání funkce `htmlspecialchars()`. Tvůrci Latte si ale uvědomili, že tohle vůbec není správné řešení. Protože na různých místech dokumentu se escapuje různými způsoby. Naivní autoescapování je nebezpečná funkce, protože vytváří falešný pocit bezpečí. + +Aby bylo autoescapování funkční a spolehlivé, musí rozeznávat, ve kterém místě dokumentu se data vypisují (říkáme jim kontexty) a podle něj volit escapovací funkci. Tedy musí být [kontextově-sensitivní |safety-first#Kontextově sensitivní escapování]. A tohle právě Latte umí. Rozumí HTML. Nevnímá šablonu jen jako řetězec znaků, ale chápe, co jsou značky, atributy atd. A proto jinak escapuje v HTML textu, jinak uvnitř HTML značky, jinak uvnitř JavaScriptu atd. + +Latte je prvním a jediným šablonovacím systémem v PHP, který má kontextově citlivé escapování. Představuje tak jediný opravdu bezpečný šablonovací systém. + +*A ještě jeden příjemný důvod*: Díky tomu, že Latte chápe HTML, nabízí další velmi příjemné vychytávky. Například [n:atributy |syntax#n:atributy]. Nebo schopnost [kontrolovat odkazy |safety-first#Kontrola odkazů]. A mnoho dalších. + + +Co je to escapování? +-------------------- + +Escapování je proces, která spočívá v nahrazování znaků se speciálním významem odpovídajícími sekvencemi při vkládání jednoho řetězce do druhého, aby se zabránilo nežádoucím jevům či chybám. Například když vkládáme řetězec do HTML textu, ve kterém má znak `<` zvláštní význam, jelikož označuje začátek značky, nahradíme jej odpovídající sekvencí, což je HTML entita `<`. Díky tomu prohlížeč správně zobrazí symbol `<`. + +Jednoduchým příkladem escapování přímo při psaní kódu v PHP je vložení uvozovky do řetězce, kdy před ni napíšeme zpětné lomítko. + +Podrobněji escapování rozebíráme v kapitole [Jak se bránit XSS |safety-first#Jak se bránit XSS]. + + +Lze v Latte provést dotaz do databáze ze šablony? +------------------------------------------------- + +V šablonách lze pracovat s objekty, které do nich programátor předá. Pokud tedy programátor chce, může do šablony předat objekt databáze a nad ním provést dotaz. Pokud má takový záměr, není důvod mu v tom bránit. + +Jiná situace nastává, pokud chcete dát možnost editovat šablony klientům nebo externím kodérům. V takovém případě rozhodně nechcete, aby měli přístup k databázi. Samozřejmě šabloně objekt databáze nepředáte, ale co když se k ní lze dostat skrze jiný objekt? Řešením je [sandbox režim|sandbox], který umožňuje definovat, které metody lze v šablonách volat. Díky tomu se nemusíte obávat narušení bezpečnosti. + + +Jaké jsou hlavní rozdíly mezi šablonovacími systémy jako Latte, Twig a Blade? +----------------------------------------------------------------------------- + +Rozdíly mezi šablonovacími systémy Latte, Twig a Blade spočívají hlavně v syntaxi, zabezpečení a způsobu integrace do frameworků + +- Latte: používá syntaxi jazyka PHP, což usnadňuje učení a používání. Poskytuje špičkovou ochranu proti XSS útokům. +- Twig: používá syntax jazyka Python, která se od PHP dosti liší. Escapuje bez rozlišení kontextu. Je dobře integrován do Symfony frameworku. +- Blade: používá směs PHP a vlastní syntaxe. Escapuje bez rozlišení kontextu. Je těsně integrován s Laravel funkcemi a ekosystémem. + + +Vyplatí se firmám používat šablonovací systém? +---------------------------------------------- + +Předně, náklady spojené se zaškolením, používáním a celkovým přínosem, se významně liší podle systému. Šablonovací systém Latte díky tomu, že používá syntaxi PHP, velice zjednodušuje učení pro programátory již obeznámené s tímto jazykem. Obvykle trvá několik hodin, než se programátor s Latte dostatečně seznámí. Snižuje tedy náklady na školení. Zároveň zrychluje osvojení technologie a především efektivitu při každodenním používání. + +Dále Latte poskytuje vysokou úroveň ochrany proti zranitelnosti XSS díky unikátní technologii kontextově citlivého escapování. Tato ochrana je klíčová pro zajištění bezpečnosti webových aplikací a minimalizaci rizika útoků, které by mohly ohrozit uživatele či firemní data. Ochrana bezpečnosti webových aplikací je důležitá také pro udržení dobré pověsti firmy. Bezpečnostní problémy mohou způsobit ztrátu důvěry ze strany zákazníků a poškodit reputaci firmy na trhu. + +Použití Latte také snižuje celkové náklady na vývoj a údržbu aplikace tím, že obojí usnadňuje. Použití šablonovacího systému se tedy jednoznačně vyplatí. + + +Ovlivňuje Latte výkon webových aplikací? +---------------------------------------- + +Ačkoliv šablony Latte jsou zpracovávány rychle, na tomto aspektu vlastně nezáleží. Důvodem je, že parsování souborů proběhne pouze jednou při prvním zobrazení. Následně jsou zkompilovány do PHP kódu, uloženy na disk a spouštěny při každém dalším požadavku, aniž by bylo nutné provádět opětovnou kompilaci. + +Toto je způsob fungování v produkčním prostředí. Během vývoje se Latte šablony překompilují pokaždé, když dojde ke změně jejich obsahu, aby vývojář viděl vždy aktuální podobu. diff --git a/latte/de/@home.texy b/latte/de/@home.texy index 7cf90f25ee..0559bd7b9b 100644 --- a/latte/de/@home.texy +++ b/latte/de/@home.texy @@ -1 +1,2 @@ -{{maintitle: Latte - Die sichersten und wirklich intuitiven Templates für PHP}} +{{maintitle: Latte – die sichersten & wirklich intuitiven Templates für PHP}} +{{description: Latte ist das sicherste Templating-System für PHP. Es verhindert eine Vielzahl von Sicherheitslücken. Sie werden seine intuitive Syntax und die vielen nützlichen Features zu schätzen wissen.}} diff --git a/latte/de/@left-menu.texy b/latte/de/@left-menu.texy index c2d380373e..0883376a93 100644 --- a/latte/de/@left-menu.texy +++ b/latte/de/@left-menu.texy @@ -1,24 +1,24 @@ -- [Erste Schritte |Guide] -- Konzepte - - [Sicherheit zuerst |Safety First] - - [Vererbung von Vorlagen |Template Inheritance] - - [Typ-System |Type System] - - [Sandkasten |Sandbox] +- [Einstieg in Latte |guide] +- [Warum Templates verwenden? |why-use] +- Konzepte ⚗️ + - [Sicherheit zuerst |safety-first] + - [Template-Vererbung |Template Inheritance] + - [Typsystem |type-system] + - [Sandbox |Sandbox] -- Für Konstrukteure - - [Syntax] - - [Tags] - - [Filtern |Filters] - - [Funktionen |Functions] +- Für Designer 🎨 + - [Syntax |syntax] + - [Tags |tags] + - [Filter |filters] + - [Funktionen |functions] - [Tipps und Tricks |recipes] -- Für Entwickler - - [Praktiken für Entwickler |develop] - - [Erweitern von Latte |Extending Latte] - - [Erstellen einer Erweiterung |creating-extension] +- Für Entwickler 🧮 + - [Entwicklerpraktiken |develop] + - [Latte erweitern |extending-latte] -- [Kochbuch |cookbook/@home] +- [Anleitungen und Verfahren 💡|cookbook/@home] - [Migration von Twig |cookbook/migration-from-twig] - - [... mehr |cookbook/@home] + - [… weitere |cookbook/@home] - "Spielplatz .[link-external]":https://fiddle.nette.org/latte/ .{padding-top:1em} diff --git a/latte/de/@menu.texy b/latte/de/@menu.texy index da42739427..b86a1d28cf 100644 --- a/latte/de/@menu.texy +++ b/latte/de/@menu.texy @@ -1,12 +1,12 @@ diff --git a/latte/de/@meta.texy b/latte/de/@meta.texy new file mode 100644 index 0000000000..7b0706e371 --- /dev/null +++ b/latte/de/@meta.texy @@ -0,0 +1 @@ +{{sitename: Latte Dokumentation}} diff --git a/latte/de/compiler-passes.texy b/latte/de/compiler-passes.texy new file mode 100644 index 0000000000..ae957faec8 --- /dev/null +++ b/latte/de/compiler-passes.texy @@ -0,0 +1,555 @@ +Kompilierungsdurchläufe +*********************** + +.[perex] +Kompilierungsdurchläufe bieten einen leistungsstarken Mechanismus zur Analyse und Modifikation von Latte-Templates *nach* deren Parsen in einen abstrakten Syntaxbaum (AST) und *vor* der Generierung des finalen PHP-Codes. Dies ermöglicht fortgeschrittene Template-Manipulation, Optimierungen, Sicherheitsprüfungen (wie die Sandbox) und das Sammeln von Informationen über Templates. Diese Anleitung führt Sie durch die Erstellung Ihrer eigenen Kompilierungsdurchläufe. + + +Was ist ein Kompilierungsdurchlauf? +=================================== + +Um die Rolle der Kompilierungsdurchläufe zu verstehen, sehen Sie sich den [Latte-Kompilierungsprozess |custom-tags#Verständnis des Kompilierungsprozesses] an. Wie Sie sehen können, operieren Kompilierungsdurchläufe in einer entscheidenden Phase und ermöglichen einen tiefen Eingriff zwischen dem anfänglichen Parsen und der finalen Code-Ausgabe. + +Im Kern ist ein Kompilierungsdurchlauf einfach ein PHP Callable (wie eine Funktion, statische Methode oder Instanzmethode), das ein Argument akzeptiert: den Wurzelknoten des AST des Templates, der immer eine Instanz von `Latte\Compiler\Nodes\TemplateNode` ist. + +Das primäre Ziel eines Kompilierungsdurchlaufs ist normalerweise eines oder beide der folgenden: + +- Analyse: Den AST durchlaufen und Informationen über das Template sammeln (z. B. alle definierten Blöcke finden, die Verwendung spezifischer Tags prüfen, sicherstellen, dass bestimmte Sicherheitsbeschränkungen erfüllt sind). +- Modifikation: Die Struktur des AST oder die Attribute der Knoten ändern (z. B. automatisch HTML-Attribute hinzufügen, bestimmte Tag-Kombinationen optimieren, veraltete Tags durch neue ersetzen, Sandbox-Regeln implementieren). + + +Registrierung +============= + +Kompilierungsdurchläufe werden mit der Methode [`getPasses()` der Erweiterung |extending-latte#getPasses] registriert. Diese Methode gibt ein assoziatives Array zurück, bei dem die Schlüssel eindeutige Namen der Durchläufe sind (intern und zur Sortierung verwendet) und die Werte PHP Callables sind, die die Logik des Durchlaufs implementieren. + +```php +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Extension; + +class MyExtension extends Extension +{ + public function getPasses(): array + { + return [ + 'modificationPass' => $this->modifyTemplateAst(...), + // ... weitere Durchläufe ... + ]; + } + + public function modifyTemplateAst(TemplateNode $templateNode): void + { + // Implementierung... + } +} +``` + +Die von den Basis-Latte-Erweiterungen und Ihren eigenen Erweiterungen registrierten Durchläufe laufen sequentiell ab. Die Reihenfolge kann wichtig sein, insbesondere wenn ein Durchlauf von den Ergebnissen oder Modifikationen eines anderen abhängt. Latte bietet einen Hilfsmechanismus zur Steuerung dieser Reihenfolge, falls erforderlich; siehe Dokumentation zu [`Extension::getPasses()` |extending-latte#getPasses] für Details. + + +AST-Beispiel +============ + +Um eine bessere Vorstellung vom AST zu bekommen, fügen wir ein Beispiel hinzu. Dies ist das Quell-Template: + +```latte +{foreach $category->getItems() as $item} +
                                • {$item->name|upper}
                                • + {else} + no items found +{/foreach} +``` + +Und dies ist seine Repräsentation in Form eines AST: + +/--pre +Latte\Compiler\Nodes\TemplateNode( + Latte\Compiler\Nodes\FragmentNode( + - Latte\Essential\Nodes\ForeachNode( + expression: Latte\Compiler\Nodes\Php\Expression\MethodCallNode( + object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$category') + name: Latte\Compiler\Nodes\Php\IdentifierNode('getItems') + ) + value: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') + content: Latte\Compiler\Nodes\FragmentNode( + - Latte\Compiler\Nodes\TextNode(' ') + - Latte\Compiler\Nodes\Html\ElementNode('li')( + content: Latte\Essential\Nodes\PrintNode( + expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode( + object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') + name: Latte\Compiler\Nodes\Php\IdentifierNode('name') + ) + modifier: Latte\Compiler\Nodes\Php\ModifierNode( + filters: + - Latte\Compiler\Nodes\Php\FilterNode('upper') + ) + ) + ) + ) + else: Latte\Compiler\Nodes\FragmentNode( + - Latte\Compiler\Nodes\TextNode('no items found') + ) + ) + ) +) +\-- + + +Durchlaufen des AST mit `NodeTraverser` +======================================= + +Das manuelle Schreiben rekursiver Funktionen zum Durchlaufen der komplexen AST-Struktur ist mühsam und fehleranfällig. Latte bietet ein spezielles Werkzeug für diesen Zweck: [api:Latte\Compiler\NodeTraverser]. Diese Klasse implementiert das [Visitor Design Pattern |https://en.wikipedia.org/wiki/Visitor_pattern], dank dem das Durchlaufen des AST systematisch und einfach zu handhaben ist. + +Die grundlegende Verwendung umfasst die Erstellung einer Instanz von `NodeTraverser` und den Aufruf ihrer Methode `traverse()`, wobei der Wurzelknoten des AST und ein oder zwei "Visitor"-Callables übergeben werden: + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; + +(new NodeTraverser)->traverse( + $templateNode, + + // 'enter'-Visitor: Wird beim Betreten des Knotens aufgerufen (vor seinen Kindern) + enter: function (Node $node) { + echo "Betrete Knoten vom Typ: " . $node::class . "\n"; + // Hier können Sie den Knoten untersuchen + if ($node instanceof Nodes\TextNode) { + // echo "Gefundener Text: " . $node->content . "\n"; + } + }, + + // 'leave'-Visitor: Wird beim Verlassen des Knotens aufgerufen (nach seinen Kindern) + leave: function (Node $node) { + echo "Verlasse Knoten vom Typ: " . $node::class . "\n"; + // Hier können Sie Aktionen nach der Verarbeitung der Kinder durchführen + }, +); +``` + +Sie können nur den `enter`-Visitor, nur den `leave`-Visitor oder beide bereitstellen, je nach Ihren Bedürfnissen. + +**`enter(Node $node)`:** Diese Funktion wird für jeden Knoten ausgeführt, **bevor** der Traverser eines der Kinder dieses Knotens besucht. Sie ist nützlich für: + +- Das Sammeln von Informationen beim Durchlaufen des Baumes nach unten. +- Das Treffen von Entscheidungen *vor* der Verarbeitung der Kinder (wie die Entscheidung, sie zu überspringen, siehe [Traversal-Optimierung |#Optimierung der Traversierung]). +- Potenzielle Änderungen am Knoten vor dem Besuch der Kinder (seltener). + +**`leave(Node $node)`:** Diese Funktion wird für jeden Knoten ausgeführt, **nachdem** alle seine Kinder (und ihre gesamten Unterbäume) vollständig besucht wurden (sowohl Eintritt als auch Austritt). Sie ist der häufigste Ort für: + +Beide Visitor `enter` und `leave` können optional einen Wert zurückgeben, um den Traversierungsprozess zu beeinflussen. Die Rückgabe von `null` (oder nichts) setzt die Traversierung normal fort, die Rückgabe einer `Node`-Instanz ersetzt den aktuellen Knoten, und die Rückgabe spezieller Konstanten wie `NodeTraverser::RemoveNode` oder `NodeTraverser::StopTraversal` modifiziert den Fluss, wie in den folgenden Abschnitten erläutert. + + +Wie die Traversierung funktioniert +---------------------------------- + +`NodeTraverser` verwendet intern die Methode `getIterator()`, die jede `Node`-Klasse implementieren muss (wie in [Erstellung eigener Tags |custom-tags#Implementieren von getIterator für Unterknoten] diskutiert). Sie iteriert über die mit `getIterator()` erhaltenen Kinder, ruft rekursiv `traverse()` auf ihnen auf und stellt sicher, dass die `enter`- und `leave`-Visitor in der korrekten Tiefensuche-Reihenfolge für jeden über Iteratoren erreichbaren Knoten im Baum aufgerufen werden. Dies unterstreicht erneut, warum ein korrekt implementierter `getIterator()` in Ihren eigenen Tag-Knoten absolut notwendig für das korrekte Funktionieren von Kompilierungsdurchläufen ist. + +Schreiben wir einen einfachen Durchlauf, der zählt, wie oft der Tag `{do}` (repräsentiert durch `Latte\Essential\Nodes\DoNode`) im Template verwendet wird. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Essential\Nodes\DoNode; + +function countDoTags(TemplateNode $templateNode): void +{ + $count = 0; + (new NodeTraverser)->traverse( + $templateNode, + enter: function (Node $node) use (&$count): void { + if ($node instanceof DoNode) { + $count++; + } + }, + // 'leave'-Visitor ist für diese Aufgabe nicht erforderlich + ); + + echo "Tag {do} wurde $count Mal gefunden.\n"; +} + +$latte = new Latte\Engine; +$ast = $latte->parse($templateSource); +countDoTags($ast); +``` + +In diesem Beispiel benötigten wir nur den `enter`-Visitor, um den Typ jedes besuchten Knotens zu überprüfen. + +Als Nächstes untersuchen wir, wie diese Visitor den AST tatsächlich modifizieren. + + +Modifikation des AST +==================== + +Einer der Hauptzwecke von Kompilierungsdurchläufen ist die Modifikation des abstrakten Syntaxbaums. Dies ermöglicht leistungsstarke Transformationen, Optimierungen oder die Durchsetzung von Regeln direkt an der Struktur des Templates, bevor PHP-Code generiert wird. `NodeTraverser` bietet mehrere Möglichkeiten, dies innerhalb der `enter`- und `leave`-Visitor zu erreichen. + +**Wichtiger Hinweis:** Die Modifikation des AST erfordert Vorsicht. Falsche Änderungen – wie das Entfernen wesentlicher Knoten oder das Ersetzen eines Knotens durch einen inkompatiblen Typ – können zu Fehlern während der Codegenerierung führen oder unerwartetes Verhalten zur Laufzeit verursachen. Testen Sie Ihre Modifikationsdurchläufe immer gründlich. + + +Ändern von Knoteneigenschaften +------------------------------ + +Die einfachste Möglichkeit, den Baum zu modifizieren, ist die direkte Änderung der **öffentlichen Eigenschaften** der Knoten, die während der Traversierung besucht werden. Alle Knoten speichern ihre geparsten Argumente, Inhalte oder Attribute in öffentlichen Eigenschaften. + +**Beispiel:** Erstellen wir einen Durchlauf, der alle statischen Textknoten (`TextNode`, die normalen HTML- oder Text außerhalb von Latte-Tags repräsentieren) findet und ihren Inhalt *direkt im AST* in Großbuchstaben umwandelt. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\TextNode; + +function uppercaseStaticText(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // Wir können 'enter' verwenden, da TextNode keine zu verarbeitenden Kinder hat + enter: function (Node $node) { + // Ist dieser Knoten ein statischer Textblock? + if ($node instanceof TextNode) { + // Ja! Wir ändern direkt seine öffentliche Eigenschaft 'content'. + $node->content = mb_strtoupper(html_entity_decode($node->content)); + } + // Es muss nichts zurückgegeben werden; die Änderung wird direkt angewendet. + }, + ); +} +``` + +In diesem Beispiel prüft der `enter`-Visitor, ob der aktuelle `$node` vom Typ `TextNode` ist. Wenn ja, aktualisieren wir direkt seine öffentliche Eigenschaft `$content` mit `mb_strtoupper()`. Dies ändert direkt den Inhalt des statischen Textes, der im AST gespeichert ist, *bevor* PHP-Code generiert wird. Da wir das Objekt direkt modifizieren, müssen wir nichts vom Visitor zurückgeben. + +Effekt: Wenn das Template `

                                  Hello

                                  {= $var }World` enthielt, repräsentiert der AST nach diesem Durchlauf etwas wie: `

                                  HELLO

                                  {= $var }WORLD`. Dies beeinflusst NICHT den Inhalt von `$var`. + + +Ersetzen von Knoten +------------------- + +Eine leistungsfähigere Modifikationstechnik ist das vollständige Ersetzen eines Knotens durch einen anderen. Dies geschieht durch die **Rückgabe einer neuen `Node`-Instanz** vom `enter`- oder `leave`-Visitor. `NodeTraverser` ersetzt dann den ursprünglichen Knoten durch den zurückgegebenen in der Struktur des übergeordneten Knotens. + +**Beispiel:** Erstellen wir einen Durchlauf, der alle Verwendungen der Konstante `PHP_VERSION` (repräsentiert durch `ConstantFetchNode`) findet und sie direkt durch ein String-Literal (`StringNode`) ersetzt, das die *tatsächliche* PHP-Version enthält, die *während der Kompilierung* erkannt wurde. Dies ist eine Form der Optimierung zur Kompilierzeit. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\Php\Expression\ConstantFetchNode; +use Latte\Compiler\Nodes\Php\Scalar\StringNode; + +function inlinePhpVersion(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // 'leave' wird oft zum Ersetzen verwendet, stellt sicher, dass Kinder (falls vorhanden) + // zuerst verarbeitet werden, obwohl hier auch 'enter' funktionieren würde. + leave: function (Node $node) { + // Ist dieser Knoten ein Zugriff auf eine Konstante und der Name der Konstante 'PHP_VERSION'? + if ($node instanceof ConstantFetchNode && (string) $node->name === 'PHP_VERSION') { + // Wir erstellen einen neuen StringNode, der die aktuelle PHP-Version enthält + $newNode = new StringNode(PHP_VERSION); + + // Optional, aber gute Praxis: Kopieren der Positionsinformationen + $newNode->position = $node->position; + + // Wir geben den neuen StringNode zurück. Der Traverser ersetzt + // den ursprünglichen ConstantFetchNode durch diesen $newNode. + return $newNode; + } + // Wenn wir keinen Node zurückgeben, wird der ursprüngliche $node beibehalten. + }, + ); +} +``` + +Hier identifiziert der `leave`-Visitor den spezifischen `ConstantFetchNode` für `PHP_VERSION`. Anschließend erstellt er einen völlig neuen `StringNode`, der den Wert der Konstante `PHP_VERSION` *zur Kompilierzeit* enthält. Durch die Rückgabe dieses `$newNode` weist er den Traverser an, den ursprünglichen `ConstantFetchNode` im AST zu ersetzen. + +Effekt: Wenn das Template `{= PHP_VERSION }` enthielt und die Kompilierung auf PHP 8.2.1 läuft, repräsentiert der AST nach diesem Durchlauf effektiv `{= '8.2.1' }`. + +**Wahl zwischen `enter` und `leave` zum Ersetzen:** + +- Verwenden Sie `leave`, wenn die Erstellung des neuen Knotens von den Ergebnissen der Verarbeitung der Kinder des alten Knotens abhängt oder wenn Sie einfach sicherstellen möchten, dass die Kinder vor dem Ersetzen besucht werden (übliche Praxis). +- Verwenden Sie `enter`, wenn Sie einen Knoten ersetzen möchten, *bevor* seine Kinder überhaupt besucht werden. + + +Entfernen von Knoten +-------------------- + +Sie können einen Knoten vollständig aus dem AST entfernen, indem Sie die spezielle Konstante `NodeTraverser::RemoveNode` vom Visitor zurückgeben. + +**Beispiel:** Entfernen wir alle Template-Kommentare (`{* ... *}`), die durch `CommentNode` im vom Latte-Kern generierten AST repräsentiert werden (obwohl sie typischerweise früher verarbeitet werden, dient dies als Beispiel). + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\CommentNode; + +function removeCommentNodes(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // 'enter' ist hier in Ordnung, da wir keine Informationen über Kinder benötigen, um den Kommentar zu entfernen + enter: function (Node $node) { + if ($node instanceof CommentNode) { + // Wir signalisieren dem Traverser, diesen Knoten aus dem AST zu entfernen + return NodeTraverser::RemoveNode; + } + }, + ); +} +``` + +**Warnung:** Verwenden Sie `RemoveNode` mit Vorsicht. Das Entfernen eines Knotens, der wesentlichen Inhalt enthält oder die Struktur beeinflusst (wie das Entfernen des Inhaltsknotens einer Schleife), kann zu beschädigten Templates oder ungültigem generiertem Code führen. Am sichersten ist es für Knoten, die tatsächlich optional oder eigenständig sind (wie Kommentare oder Debugging-Tags) oder für leere strukturelle Knoten (z. B. kann ein leerer `FragmentNode` in einigen Kontexten sicher durch einen Bereinigungsdurchlauf entfernt werden). + +Diese drei Methoden - Änderung von Eigenschaften, Ersetzen von Knoten und Entfernen von Knoten - bieten die grundlegenden Werkzeuge zur Manipulation des AST im Rahmen Ihrer Kompilierungsdurchläufe. + + +Optimierung der Traversierung +============================= + +Der AST eines Templates kann ziemlich groß sein und potenziell Tausende von Knoten enthalten. Das Durchlaufen jedes einzelnen Knotens kann unnötig sein und die Kompilierungsleistung beeinträchtigen, wenn Ihr Durchlauf nur an spezifischen Teilen des Baumes interessiert ist. `NodeTraverser` bietet Möglichkeiten zur Optimierung der Traversierung: + + +Überspringen von Kindern +------------------------ + +Wenn Sie wissen, dass, sobald Sie auf einen bestimmten Knotentyp stoßen, keiner seiner Nachkommen die Knoten enthalten kann, nach denen Sie suchen, können Sie dem Traverser sagen, dass er den Besuch seiner Kinder überspringen soll. Dies geschieht durch die Rückgabe der Konstante `NodeTraverser::DontTraverseChildren` vom **`enter`**-Visitor. Dadurch werden ganze Zweige bei der Traversierung ausgelassen, was potenziell erheblich Zeit sparen kann, insbesondere in Templates mit komplexen PHP-Ausdrücken innerhalb von Tags. + + +Anhalten der Traversierung +-------------------------- + +Wenn Ihr Durchlauf nur das *erste* Vorkommen von etwas finden muss (ein spezifischer Knotentyp, die Erfüllung einer Bedingung), können Sie den gesamten Traversierungsprozess vollständig anhalten, sobald Sie es gefunden haben. Dies wird erreicht, indem die Konstante `NodeTraverser::StopTraversal` vom `enter`- oder `leave`-Visitor zurückgegeben wird. Die Methode `traverse()` hört auf, weitere Knoten zu besuchen. Dies ist äußerst effizient, wenn Sie nur die erste Übereinstimmung in einem potenziell sehr großen Baum benötigen. + + +Nützlicher Helfer `NodeHelpers` +=============================== + +Während `NodeTraverser` eine feingranulare Kontrolle bietet, stellt Latte auch eine praktische Hilfsklasse zur Verfügung, [api:Latte\Compiler\NodeHelpers], die `NodeTraverser` für mehrere gängige Such- und Analyseaufgaben kapselt und oft weniger Boilerplate-Code erfordert. + + +find(Node $startNode, callable $filter): array .[method] +-------------------------------------------------------- + +Diese statische Methode findet **alle** Knoten im Unterbaum, der bei `$startNode` beginnt (einschließlich), die dem Callback `$filter` entsprechen. Sie gibt ein Array der übereinstimmenden Knoten zurück. + +**Beispiel:** Finde alle Variablenknoten (`VariableNode`) im gesamten Template. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Php\Expression\VariableNode; +use Latte\Compiler\Nodes\TemplateNode; + +function findAllVariables(TemplateNode $templateNode): array +{ + return NodeHelpers::find( + $templateNode, + fn($node) => $node instanceof VariableNode, + ); +} +``` + + +findFirst(Node $startNode, callable $filter): ?Node .[method] +-------------------------------------------------------------- + +Ähnlich wie `find`, aber stoppt die Traversierung sofort nach dem Finden des **ersten** Knotens, der dem Callback `$filter` entspricht. Gibt das gefundene `Node`-Objekt oder `null` zurück, wenn kein übereinstimmender Knoten gefunden wird. Dies ist im Wesentlichen eine praktische Hülle um `NodeTraverser::StopTraversal`. + +**Beispiel:** Finde den `{parameters}`-Knoten (wie das manuelle Beispiel zuvor, aber kürzer). + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Essential\Nodes\ParametersNode; + +function findParametersNodeHelper(TemplateNode $templateNode): ?ParametersNode +{ + return NodeHelpers::findFirst( + $templateNode->head, // Suche nur im Hauptabschnitt zur Effizienz + fn($node) => $node instanceof ParametersNode, + ); +} +``` + + +toValue(ExpressionNode $node, bool $constants = false): mixed .[method] +----------------------------------------------------------------------- + +Diese statische Methode versucht, einen `ExpressionNode` **zur Kompilierzeit** auszuwerten und seinen entsprechenden PHP-Wert zurückzugeben. Sie funktioniert zuverlässig nur für einfache Literal-Knoten (`StringNode`, `IntegerNode`, `FloatNode`, `BooleanNode`, `NullNode`) und Instanzen von `ArrayNode`, die nur solche auswertbaren Elemente enthalten. + +Wenn `$constants` auf `true` gesetzt ist, versucht sie auch, `ConstantFetchNode` und `ClassConstantFetchNode` durch Überprüfung von `defined()` und Verwendung von `constant()` aufzulösen. + +Wenn der Knoten Variablen, Funktionsaufrufe oder andere dynamische Elemente enthält, kann er zur Kompilierzeit nicht ausgewertet werden, und die Methode löst eine `InvalidArgumentException` aus. + +**Anwendungsfall:** Erhalten des statischen Werts eines Tag-Arguments während der Kompilierung für Entscheidungen zur Kompilierzeit. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Php\ExpressionNode; + +function getStaticStringArgument(ExpressionNode $argumentNode): ?string +{ + try { + $value = NodeHelpers::toValue($argumentNode); + return is_string($value) ? $value : null; + } catch (\InvalidArgumentException $e) { + // Argument war kein statisches String-Literal + return null; + } +} +``` + + +toText(?Node $node): ?string .[method] +-------------------------------------- + +Diese statische Methode ist nützlich zum Extrahieren von einfachem Textinhalt aus einfachen Knoten. Sie funktioniert hauptsächlich mit: +- `TextNode`: Gibt seinen `$content` zurück. +- `FragmentNode`: Verkettet das Ergebnis von `toText()` für alle seine Kinder. Wenn ein Kind nicht in Text konvertierbar ist (z. B. einen `PrintNode` enthält), gibt sie `null` zurück. +- `NopNode`: Gibt einen leeren String zurück. +- Andere Knotentypen: Gibt `null` zurück. + +**Anwendungsfall:** Erhalten des statischen Textinhalts des Werts eines HTML-Attributs oder eines einfachen HTML-Elements zur Analyse während eines Kompilierungsdurchlaufs. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Html\AttributeNode; + +function getStaticAttributeValue(AttributeNode $attr): ?string +{ + // $attr->value ist typischerweise ein AreaNode (wie FragmentNode oder TextNode) + return NodeHelpers::toText($attr->value); +} + +// Beispielverwendung in einem Durchlauf: +// if ($node instanceof Html\ElementNode && $node->name === 'meta') { +// $nameAttrValue = getStaticAttributeValue($node->getAttributeNode('name')); +// if ($nameAttrValue === 'description') { ... } +// } +``` + +`NodeHelpers` kann Ihre Kompilierungsdurchläufe vereinfachen, indem es fertige Lösungen für gängige Aufgaben der AST-Traversierung und -Analyse bietet. + + +Praktische Beispiele +==================== + +Wenden wir die Konzepte der AST-Traversierung und -Modifikation an, um einige praktische Probleme zu lösen. Diese Beispiele demonstrieren gängige Muster, die in Kompilierungsdurchläufen verwendet werden. + + +Automatisches Hinzufügen von `loading="lazy"` zu `` +-------------------------------------------------------- + +Moderne Browser unterstützen natives Lazy Loading für Bilder mit dem Attribut `loading="lazy"`. Erstellen wir einen Durchlauf, der dieses Attribut automatisch zu allen ``-Tags hinzufügt, die noch kein `loading`-Attribut haben. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; +use Latte\Compiler\Nodes\Html; + +function addLazyLoading(Nodes\TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // Wir können 'enter' verwenden, da wir den Knoten direkt modifizieren + // und für diese Entscheidung nicht von Kindern abhängen. + enter: function (Node $node) { + // Ist es ein HTML-Element mit dem Namen 'img'? + if ($node instanceof Html\ElementNode && $node->name === 'img') { + // Sicherstellen, dass der Attributknoten existiert + $node->attributes ??= new Nodes\FragmentNode; + + // Überprüfen, ob bereits ein 'loading'-Attribut existiert (Groß-/Kleinschreibung egal) + foreach ($node->attributes->children as $attrNode) { + if ($attrNode instanceof Html\AttributeNode + && $attrNode->name instanceof Nodes\TextNode // Statischer Attributname + && strtolower($attrNode->name->content) === 'loading' + ) { + return; // Attribut existiert bereits, nichts tun + } + } + + // Leerzeichen hinzufügen, wenn Attribute nicht leer sind + if ($node->attributes->children) { + $node->attributes->children[] = new Nodes\TextNode(' '); + } + + // Neuen Attributknoten erstellen: loading="lazy" + $node->attributes->children[] = new Html\AttributeNode( + name: new Nodes\TextNode('loading'), + value: new Nodes\TextNode('lazy'), + quote: '"', + ); + // Die Änderung wird direkt im Objekt angewendet, es muss nichts zurückgegeben werden. + } + }, + ); +} +``` + +Erklärung: +- Der `enter`-Visitor sucht nach `Html\ElementNode`-Knoten mit dem Namen `img`. +- Er iteriert durch die vorhandenen Attribute (`$node->attributes->children`) und prüft, ob das `loading`-Attribut bereits vorhanden ist. +- Wenn es nicht gefunden wird, erstellt er einen neuen `Html\AttributeNode`, der `loading="lazy"` repräsentiert. + + +Überprüfung von Funktionsaufrufen +--------------------------------- + +Kompilierungsdurchläufe sind die Grundlage der Latte Sandbox. Obwohl die tatsächliche Sandbox ausgefeilt ist, können wir das Grundprinzip der Überprüfung verbotener Funktionsaufrufe demonstrieren. + +**Ziel:** Die Verwendung der potenziell gefährlichen Funktion `shell_exec` innerhalb von Template-Ausdrücken verhindern. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; +use Latte\Compiler\Nodes\Php; +use Latte\SecurityViolationException; + +function checkForbiddenFunctions(Nodes\TemplateNode $templateNode): void +{ + $forbiddenFunctions = ['shell_exec' => true, 'exec' => true]; // Einfache Liste + + $traverser = new NodeTraverser; + (new NodeTraverser)->traverse( + $templateNode, + enter: function (Node $node) use ($forbiddenFunctions) { + // Ist es ein direkter Funktionsaufrufknoten? + if ($node instanceof Php\Expression\FunctionCallNode + && $node->name instanceof Php\NameNode + && isset($forbiddenFunctions[strtolower((string) $node->name)]) + ) { + throw new SecurityViolationException( + "Die Funktion {$node->name}() ist nicht erlaubt.", + $node->position, + ); + } + }, + ); +} +``` + +Erklärung: +- Wir definieren eine Liste verbotener Funktionsnamen. +- Der `enter`-Visitor prüft auf `FunctionCallNode`. +- Wenn der Funktionsname (`$node->name`) ein statischer `NameNode` ist, prüfen wir seine kleingeschriebene String-Repräsentation gegen unsere verbotene Liste. +- Wenn eine verbotene Funktion gefunden wird, werfen wir eine `Latte\SecurityViolationException`, die klar die Verletzung der Sicherheitsregel anzeigt und die Kompilierung stoppt. + +Diese Beispiele zeigen, wie Kompilierungsdurchläufe mit `NodeTraverser` genutzt werden können, um Analyse, automatische Modifikationen und die Durchsetzung von Sicherheitsbeschränkungen durch direkte Interaktion mit der AST-Struktur des Templates zu erreichen. + + +Best Practices +============== + +Beachten Sie beim Schreiben von Kompilierungsdurchläufen diese Richtlinien, um robuste, wartbare und effiziente Erweiterungen zu erstellen: + +- **Reihenfolge ist wichtig:** Seien Sie sich der Reihenfolge bewusst, in der Durchläufe ausgeführt werden. Wenn Ihr Durchlauf von der AST-Struktur abhängt, die von einem anderen Durchlauf erstellt wurde (z. B. Latte-Kerndurchläufe oder ein anderer benutzerdefinierter Durchlauf), oder wenn andere Durchläufe von Ihren Modifikationen abhängen könnten, verwenden Sie den von `Extension::getPasses()` bereitgestellten Sortiermechanismus, um Abhängigkeiten zu definieren (`before`/`after`). Siehe Dokumentation zu [`Extension::getPasses()` |extending-latte#getPasses] für Details. +- **Einzelverantwortung:** Streben Sie nach Durchläufen, die eine gut definierte Aufgabe erfüllen. Für komplexe Transformationen erwägen Sie, die Logik in mehrere Durchläufe aufzuteilen – vielleicht einen für die Analyse und einen weiteren für die Modifikation basierend auf den Analyseergebnissen. Dies verbessert die Übersichtlichkeit und Testbarkeit. +- **Leistung:** Denken Sie daran, dass Kompilierungsdurchläufe die Kompilierungszeit des Templates erhöhen (obwohl dies normalerweise nur einmal geschieht, bis das Template geändert wird). Vermeiden Sie rechenintensive Operationen in Ihren Durchläufen, wenn möglich. Nutzen Sie Traversierungsoptimierungen wie `NodeTraverser::DontTraverseChildren` und `NodeTraverser::StopTraversal`, wann immer Sie wissen, dass Sie bestimmte Teile des AST nicht besuchen müssen. +- **Verwenden Sie `NodeHelpers`:** Für gängige Aufgaben wie das Finden spezifischer Knoten oder die statische Auswertung einfacher Ausdrücke prüfen Sie, ob `Latte\Compiler\NodeHelpers` eine geeignete Methode bietet, bevor Sie Ihre eigene `NodeTraverser`-Logik schreiben. Dies kann Zeit sparen und die Menge an Boilerplate-Code reduzieren. +- **Fehlerbehandlung:** Wenn Ihr Durchlauf einen Fehler oder einen ungültigen Zustand im AST des Templates erkennt, werfen Sie eine `Latte\CompileException` (oder `Latte\SecurityViolationException` für Sicherheitsprobleme) mit einer klaren Meldung und dem relevanten `Position`-Objekt (normalerweise `$node->position`). Dies gibt dem Template-Entwickler nützliches Feedback. +- **Idempotenz (wenn möglich):** Idealerweise sollte das mehrmalige Ausführen Ihres Durchlaufs auf demselben AST dasselbe Ergebnis liefern wie das einmalige Ausführen. Dies ist nicht immer machbar, vereinfacht aber das Debugging und das Nachdenken über Interaktionen zwischen Durchläufen, wenn es erreicht wird. Stellen Sie beispielsweise sicher, dass Ihr Modifikationsdurchlauf prüft, ob die Modifikation bereits angewendet wurde, bevor Sie sie erneut anwenden. + +Durch Befolgen dieser Praktiken können Sie Kompilierungsdurchläufe effektiv nutzen, um die Fähigkeiten von Latte auf leistungsstarke und zuverlässige Weise zu erweitern, was zu sichereren, optimierteren oder funktionsreicheren Template-Verarbeitungen beiträgt. diff --git a/latte/de/cookbook/@home.texy b/latte/de/cookbook/@home.texy index 32d8a0126c..c825ee27bb 100644 --- a/latte/de/cookbook/@home.texy +++ b/latte/de/cookbook/@home.texy @@ -1,13 +1,13 @@ -Kochbuch -******** +Anleitungen und Verfahren +************************* .[perex] -Beispielcodes und Rezepte für die Erledigung gängiger Aufgaben mit Latte. +Codebeispiele und Rezepte zur Durchführung gängiger Aufgaben mit Latte. -- [Alles, was Sie schon immer über {iterateWhile} wissen wollten |iteratewhile] +- [Verfahren für Entwickler |/develop] +- [Variablen über Templates hinweg übergeben |passing-variables] +- [Alles, was Sie schon immer über Gruppierung wissen wollten |grouping] - [Wie schreibt man SQL-Abfragen in Latte? |how-to-write-sql-queries-in-latte] -- [Umstieg von PHP |migration-from-php] -- [Umstieg von Twig |migration-from-twig] -- [Verwendung von Latte mit Slim 4 |slim-framework] - -{{leftbar: /@left-menu}} +- [Migration von PHP |migration-from-php] +- [Migration von Twig |migration-from-twig] +- [Latte mit Slim 4 verwenden |slim-framework] diff --git a/latte/de/cookbook/@meta.texy b/latte/de/cookbook/@meta.texy new file mode 100644 index 0000000000..b1fd0feedb --- /dev/null +++ b/latte/de/cookbook/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Latte Dokumentation}} +{{leftbar: /@left-menu}} diff --git a/latte/de/cookbook/grouping.texy b/latte/de/cookbook/grouping.texy new file mode 100644 index 0000000000..0576348ee5 --- /dev/null +++ b/latte/de/cookbook/grouping.texy @@ -0,0 +1,251 @@ +Alles, was Sie schon immer über Gruppierung wissen wollten +********************************************************** + +.[perex] +Bei der Arbeit mit Daten in Templates stoßen Sie oft auf die Notwendigkeit, diese nach bestimmten Kriterien zu gruppieren oder spezifisch anzuzeigen. Latte bietet zu diesem Zweck gleich mehrere leistungsstarke Werkzeuge. + +Der Filter und die Funktion `|group` ermöglichen eine effiziente Gruppierung von Daten nach einem festgelegten Kriterium, der Filter `|batch` erleichtert die Aufteilung von Daten in feste Blöcke und der Tag `{iterateWhile}` bietet die Möglichkeit einer komplexeren Steuerung des Schleifenverlaufs mit Bedingungen. Jeder dieser Tags bietet spezifische Möglichkeiten zur Datenverarbeitung und wird so zu einem unverzichtbaren Werkzeug für die dynamische und strukturierte Darstellung von Informationen in Latte-Templates. + + +Filter und Funktion `group` .{data-version:3.0.16} +================================================== + +Stellen Sie sich eine Datenbanktabelle `items` mit Einträgen vor, die in Kategorien unterteilt sind: + +| id | categoryId | name +|------------------ +| 1 | 1 | Apple +| 2 | 1 | Banana +| 3 | 2 | PHP +| 4 | 3 | Green +| 5 | 3 | Red +| 6 | 3 | Blue + +Eine einfache Liste aller Einträge mit einem Latte-Template würde so aussehen: + +```latte +
                                    +{foreach $items as $item} +
                                  • {$item->name}
                                  • +{/foreach} +
                                  +``` + +Wenn wir jedoch möchten, dass die Einträge nach Kategorien gruppiert werden, müssen wir sie so aufteilen, dass jede Kategorie ihre eigene Liste hat. Das Ergebnis sollte dann wie folgt aussehen: + +```latte +
                                    +
                                  • Apple
                                  • +
                                  • Banana
                                  • +
                                  + +
                                    +
                                  • PHP
                                  • +
                                  + +
                                    +
                                  • Green
                                  • +
                                  • Red
                                  • +
                                  • Blue
                                  • +
                                  +``` + +Die Aufgabe lässt sich einfach und elegant mit `|group` lösen. Als Parameter geben wir `categoryId` an, was bedeutet, dass die Einträge nach dem Wert von `$item->categoryId` in kleinere Arrays aufgeteilt werden (wenn `$item` ein Array wäre, würde `$item['categoryId']` verwendet): + +```latte +{foreach ($items|group: categoryId) as $categoryId => $categoryItems} +
                                    + {foreach $categoryItems as $item} +
                                  • {$item->name}
                                  • + {/foreach} +
                                  +{/foreach} +``` + +Der Filter kann in Latte auch als Funktion verwendet werden, was uns eine alternative Syntax gibt: `{foreach group($items, categoryId) ...}`. + +Wenn Sie Einträge nach komplexeren Kriterien gruppieren möchten, können Sie im Filterparameter eine Funktion verwenden. Zum Beispiel würde die Gruppierung von Einträgen nach der Länge des Namens so aussehen: + +```latte +{foreach ($items|group: fn($item) => strlen($item->name)) as $items} + ... +{/foreach} +``` + +Es ist wichtig zu beachten, dass `$categoryItems` kein gewöhnliches Array ist, sondern ein Objekt, das sich wie ein Iterator verhält. Um auf das erste Element der Gruppe zuzugreifen, können Sie die Funktion [`first()` |latte:functions#first] verwenden. + +Diese Flexibilität bei der Datengruppierung macht `group` zu einem außergewöhnlich nützlichen Werkzeug für die Datenpräsentation in Latte-Templates. + + +Verschachtelte Schleifen +------------------------ + +Stellen wir uns vor, wir haben eine Datenbanktabelle mit einer weiteren Spalte `subcategoryId`, die Unterkategorien für einzelne Einträge definiert. Wir möchten jede Hauptkategorie in einer separaten `
                                    `-Liste und jede Unterkategorie in einer separaten verschachtelten `
                                      `-Liste anzeigen: + +```latte +{foreach ($items|group: categoryId) as $categoryItems} +
                                        + {foreach ($categoryItems|group: subcategoryId) as $subcategoryItems} +
                                          + {foreach $subcategoryItems as $item} +
                                        1. {$item->name} + {/foreach} +
                                        + {/foreach} +
                                      +{/foreach} +``` + + +Verbindung mit Nette Database +----------------------------- + +Zeigen wir, wie man die Datengruppierung effektiv in Kombination mit Nette Database nutzt. Angenommen, wir arbeiten mit der Tabelle `items` aus dem Einführungsbeispiel, die über die Spalte `categoryId` mit dieser Tabelle `categories` verknüpft ist: + +| categoryId | name | +|------------|------------| +| 1 | Fruits | +| 2 | Languages | +| 3 | Colors | + +Die Daten aus der Tabelle `items` laden wir mit dem Nette Database Explorer Befehl `$items = $db->table('items')`. Während der Iteration über diese Daten haben wir die Möglichkeit, nicht nur auf Attribute wie `$item->name` und `$item->categoryId` zuzugreifen, sondern dank der Verknüpfung mit der Tabelle `categories` auch auf die zugehörige Zeile darin über `$item->category`. An dieser Verknüpfung lässt sich eine interessante Anwendung demonstrieren: + +```latte +{foreach ($items|group: category) as $category => $categoryItems} +

                                      {$category->name}

                                      +
                                        + {foreach $categoryItems as $item} +
                                      • {$item->name}
                                      • + {/foreach} +
                                      +{/foreach} +``` + +In diesem Fall verwenden wir den Filter `|group`, um nach der verknüpften Zeile `$item->category` zu gruppieren, nicht nur nach der Spalte `categoryId`. Dadurch haben wir in der Schlüsselvariable `$category` direkt die `ActiveRow` der jeweiligen Kategorie, was uns ermöglicht, ihren Namen direkt mit `{$category->name}` auszugeben. Dies ist ein praktisches Beispiel dafür, wie Gruppierung Templates übersichtlicher machen und die Arbeit mit Daten erleichtern kann. + + +Filter `|batch` +=============== + +Der Filter ermöglicht es, eine Liste von Elementen in Gruppen mit einer vorbestimmten Anzahl von Elementen aufzuteilen. Dieser Filter ist ideal für Situationen, in denen Sie Daten in mehreren kleineren Gruppen präsentieren möchten, beispielsweise zur besseren Übersichtlichkeit oder visuellen Anordnung auf der Seite. + +Stellen Sie sich vor, Sie haben eine Liste von Einträgen und möchten sie in Listen anzeigen, wobei jede Liste maximal drei Einträge enthält. Die Verwendung des Filters `|batch` ist in einem solchen Fall sehr praktisch: + +```latte +
                                        +{foreach ($items|batch: 3) as $batch} + {foreach $batch as $item} +
                                      • {$item->name}
                                      • + {/foreach} +{/foreach} +
                                      +``` + +In diesem Beispiel wird die Liste `$items` in kleinere Gruppen aufgeteilt, wobei jede Gruppe (`$batch`) bis zu drei Einträge enthält. Jede Gruppe wird dann in einer separaten `
                                        `-Liste angezeigt. + +Wenn die letzte Gruppe nicht genügend Elemente enthält, um die gewünschte Anzahl zu erreichen, ermöglicht der zweite Parameter des Filters, zu definieren, womit diese Gruppe aufgefüllt wird. Dies ist ideal für die ästhetische Ausrichtung von Elementen, wo eine unvollständige Reihe unordentlich wirken könnte. + +```latte +{foreach ($items|batch: 3, '—') as $batch} + ... +{/foreach} +``` + + +Tag `{iterateWhile}` +==================== + +Die gleichen Aufgaben, die wir mit dem Filter `|group` gelöst haben, zeigen wir mit dem Tag `{iterateWhile}`. Der Hauptunterschied zwischen den beiden Ansätzen besteht darin, dass `group` zuerst alle Eingabedaten verarbeitet und gruppiert, während `{iterateWhile}` den Verlauf von Schleifen mit Bedingungen steuert, sodass die Iteration schrittweise erfolgt. + +Zuerst rendern wir die Tabelle mit Kategorien mithilfe von iterateWhile: + +```latte +{foreach $items as $item} +
                                          + {iterateWhile} +
                                        • {$item->name}
                                        • + {/iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                                        +{/foreach} +``` + +Während `{foreach}` den äußeren Teil der Schleife markiert, also das Rendern der Listen für jede Kategorie, markiert der Tag `{iterateWhile}` den inneren Teil, also die einzelnen Einträge. Die Bedingung im End-Tag `{/iterateWhile}` besagt, dass die Wiederholung so lange fortgesetzt wird, wie das aktuelle und das nächste Element zur selben Kategorie gehören (`$iterator->nextValue` ist das [nächstes Element |/tags#iterator]). + +Wenn die Bedingung immer erfüllt wäre, würden alle Elemente in der inneren Schleife gerendert: + +```latte +{foreach $items as $item} +
                                          + {iterateWhile} +
                                        • {$item->name} + {/iterateWhile true} +
                                        +{/foreach} +``` + +Das Ergebnis würde so aussehen: + +```latte +
                                          +
                                        • Apple
                                        • +
                                        • Banana
                                        • +
                                        • PHP
                                        • +
                                        • Green
                                        • +
                                        • Red
                                        • +
                                        • Blue
                                        • +
                                        +``` + +Wozu ist eine solche Verwendung von iterateWhile gut? Wenn die Tabelle leer ist und keine Elemente enthält, wird kein leeres `
                                          ` ausgegeben. + +Wenn wir die Bedingung im öffnenden Tag `{iterateWhile}` angeben, ändert sich das Verhalten: Die Bedingung (und der Übergang zum nächsten Element) wird bereits am Anfang der inneren Schleife ausgeführt, nicht am Ende. Das heißt, während man in `{iterateWhile}` ohne Bedingung immer eintritt, tritt man in `{iterateWhile $cond}` nur ein, wenn die Bedingung `$cond` erfüllt ist. Und gleichzeitig wird das nächste Element in `$item` geschrieben. + +Das ist zum Beispiel nützlich, wenn wir das erste Element jeder Kategorie anders rendern möchten, zum Beispiel so: + +```latte +

                                          Apple

                                          +
                                            +
                                          • Banana
                                          • +
                                          + +

                                          PHP

                                          +
                                            +
                                          + +

                                          Green

                                          +
                                            +
                                          • Red
                                          • +
                                          • Blue
                                          • +
                                          +``` + +Wir passen den ursprünglichen Code so an, dass wir zuerst den ersten Eintrag rendern und dann in der inneren Schleife `{iterateWhile}` weitere Einträge derselben Kategorie rendern: + +```latte +{foreach $items as $item} +

                                          {$item->name}

                                          +
                                            + {iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                                          • {$item->name}
                                          • + {/iterateWhile} +
                                          +{/foreach} +``` + +Innerhalb einer Schleife können wir mehrere innere Schleifen erstellen und sie sogar verschachteln. So könnten beispielsweise Unterkategorien usw. gruppiert werden. + +Angenommen, in der Tabelle gibt es noch eine weitere Spalte `subcategoryId` und neben der Tatsache, dass jede Kategorie in einem separaten `
                                            ` steht, steht jede Unterkategorie in einem separaten `
                                              `: + +```latte +{foreach $items as $item} +
                                                + {iterateWhile} +
                                                  + {iterateWhile} +
                                                1. {$item->name} + {/iterateWhile $item->subcategoryId === $iterator->nextValue->subcategoryId} +
                                                + {/iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                                              +{/foreach} +``` diff --git a/latte/de/cookbook/how-to-write-sql-queries-in-latte.texy b/latte/de/cookbook/how-to-write-sql-queries-in-latte.texy index 52d47302fe..c9e266a5ba 100644 --- a/latte/de/cookbook/how-to-write-sql-queries-in-latte.texy +++ b/latte/de/cookbook/how-to-write-sql-queries-in-latte.texy @@ -2,9 +2,9 @@ Wie schreibt man SQL-Abfragen in Latte? *************************************** .[perex] -Latte kann auch für die Erstellung wirklich komplexer SQL-Abfragen nützlich sein. +Latte kann auch zum Generieren wirklich komplexer SQL-Abfragen nützlich sein. -Wenn die Erstellung einer SQL-Abfrage viele Bedingungen und Variablen enthält, kann es sehr viel übersichtlicher sein, sie in Latte zu schreiben. Ein sehr einfaches Beispiel: +Wenn die Erstellung einer SQL-Abfrage eine Reihe von Bedingungen und Variablen enthält, kann es wirklich übersichtlicher sein, sie in Latte zu schreiben. Ein sehr einfaches Beispiel: ```latte SELECT users.* FROM users @@ -14,8 +14,7 @@ SELECT users.* FROM users WHERE groups.name = 'Admins' {ifset $country} AND country.name = {$country} {/ifset} ``` -Mit `$latte->setContentType()` weisen wir Latte an, den Inhalt als reinen Text (nicht als HTML) zu behandeln und -dann bereiten wir eine Escaping-Funktion vor, die Zeichenketten direkt durch den Datenbanktreiber umwandelt: +Mit `$latte->setContentType()` sagen wir Latte, dass es den Inhalt als reinen Text behandeln soll (nicht als HTML) und bereiten weiterhin eine Escaping-Funktion vor, die Strings direkt mit dem Datenbanktreiber escapen wird: ```php $db = new PDO(/* ... */); @@ -31,13 +30,11 @@ $latte->addFilter('escape', fn($val) => match (true) { }); ``` -Die Verwendung würde wie folgt aussehen: +Die Verwendung würde so aussehen: ```php $sql = $latte->renderToString('query.sql.latte', ['country' => $country]); $result = $db->query($sql); ``` -*Dieses Beispiel erfordert Latte v3.0.5 oder höher.* - -{{leftbar: /@left-menu}} +*Das angegebene Beispiel erfordert Latte v3.0.5 oder höher.* diff --git a/latte/de/cookbook/iteratewhile.texy b/latte/de/cookbook/iteratewhile.texy deleted file mode 100644 index fa408e8cda..0000000000 --- a/latte/de/cookbook/iteratewhile.texy +++ /dev/null @@ -1,214 +0,0 @@ -Was Sie schon immer über {iterateWhile} wissen wollten -****************************************************** - -.[perex] -Das Tag `{iterateWhile}` ist für verschiedene Tricks in foreach-Zyklen geeignet. - -Angenommen, wir haben die folgende Datenbanktabelle, in der die Elemente in Kategorien unterteilt sind: - -| id | catId | name -|------------------ -| 1 | 1 | Apple -| 2 | 1 | Banana -| 3 | 2 | PHP -| 4 | 3 | Green -| 5 | 3 | Red -| 6 | 3 | Blue - -Es ist natürlich einfach, Elemente in einer foreach-Schleife als Liste zu zeichnen: - -```latte -
                                                -{foreach $items as $item} -
                                              • {$item->name}
                                              • -{/foreach} -
                                              -``` - -Aber was ist zu tun, wenn Sie jede Kategorie in einer separaten Liste darstellen wollen? Mit anderen Worten, wie kann man die Aufgabe lösen, Elemente aus einer linearen Liste in einem foreach-Zyklus zu gruppieren. Die Ausgabe sollte wie folgt aussehen: - -```latte -
                                                -
                                              • Apple
                                              • -
                                              • Banana
                                              • -
                                              - -
                                                -
                                              • PHP
                                              • -
                                              - -
                                                -
                                              • Green
                                              • -
                                              • Red
                                              • -
                                              • Blue
                                              • -
                                              -``` - -Wir werden Ihnen zeigen, wie einfach und elegant die Aufgabe mit iterateWhile gelöst werden kann: - -```latte -{foreach $items as $item} -
                                                - {iterateWhile} -
                                              • {$item->name}
                                              • - {/iterateWhile $item->catId === $iterator->nextValue->catId} -
                                              -{/foreach} -``` - -Während `{foreach}` den äußeren Teil des Zyklus markiert, d.h. die Erstellung von Listen für jede Kategorie, geben die Tags `{iterateWhile}` den inneren Teil an, d.h. die einzelnen Elemente. -Die Bedingung im End-Tag besagt, dass die Wiederholung so lange fortgesetzt wird, wie das aktuelle und das nächste Element zur selben Kategorie gehören (`$iterator->nextValue` ist das [nächste Element |/tags#$iterator]). - -Wenn die Bedingung immer erfüllt ist, werden alle Elemente im inneren Zyklus gezeichnet: - -```latte -{foreach $items as $item} -
                                                - {iterateWhile} -
                                              • {$item->name} - {/iterateWhile true} -
                                              -{/foreach} -``` - -Das Ergebnis sieht dann so aus: - -```latte -
                                                -
                                              • Apple
                                              • -
                                              • Banana
                                              • -
                                              • PHP
                                              • -
                                              • Green
                                              • -
                                              • Red
                                              • -
                                              • Blue
                                              • -
                                              -``` - -Wozu ist eine solche Verwendung von iterateWhile gut? Wie unterscheidet sie sich von der Lösung, die wir ganz am Anfang des Tutorials gezeigt haben? Der Unterschied besteht darin, dass die Tabelle, wenn sie leer ist und keine Elemente enthält, nicht leer dargestellt wird `
                                                `. - - -Lösung ohne `{iterateWhile}` .[#toc-solution-without-iteratewhile] ------------------------------------------------------------------- - -Würden wir die gleiche Aufgabe mit ganz einfachen Konstruktionen von Template-Systemen lösen, zum Beispiel in Twig, Blade oder reinem PHP, würde die Lösung etwa so aussehen: - -```latte -{var $prevCatId = null} -{foreach $items as $item} - {if $item->catId !== $prevCatId} - {* die Kategorie hat sich geändert *} - - {* wir schließen das vorherige
                                                  , wenn es nicht das erste Element ist *} - {if $prevCatId !== null} -
                                                - {/if} - - {* wir öffnen eine neue Liste *} -
                                                  - - {do $prevCatId = $item->catId} - {/if} - -
                                                • {$item->name}
                                                • -{/foreach} - -{if $prevCatId !== null} - {* wir schließen die letzte Liste *} -
                                                -{/if} -``` - -Dieser Code ist jedoch unverständlich und unintuitiv. Der Zusammenhang zwischen den öffnenden und schließenden HTML-Tags ist überhaupt nicht klar. Es ist nicht auf den ersten Blick klar, ob ein Fehler vorliegt. Und es werden Hilfsvariablen wie `$prevCatId` benötigt. - -Im Gegensatz dazu ist die Lösung mit `{iterateWhile}` sauber, klar, benötigt keine Hilfsvariablen und ist narrensicher. - - -Bedingung im abschließenden Tag .[#toc-condition-in-the-closing-tag] --------------------------------------------------------------------- - -Wenn wir im öffnenden Tag `{iterateWhile}` eine Bedingung angeben, ändert sich das Verhalten: Die Bedingung (und der Übergang zum nächsten Element) wird am Anfang des inneren Zyklus ausgeführt, nicht am Ende. -Während also `{iterateWhile}` ohne Bedingung immer eingegeben wird, wird `{iterateWhile $cond}` nur eingegeben, wenn die Bedingung `$cond` erfüllt ist. Gleichzeitig wird das folgende Element in `$item` geschrieben. - -Dies ist z. B. dann nützlich, wenn Sie das erste Element in jeder Kategorie auf unterschiedliche Weise darstellen möchten, z. B: - -```latte -

                                                Apple

                                                -
                                                  -
                                                • Banana
                                                • -
                                                - -

                                                PHP

                                                -
                                                  -
                                                - -

                                                Green

                                                -
                                                  -
                                                • Red
                                                • -
                                                • Blue
                                                • -
                                                -``` - -Ändern wir den ursprünglichen Code, zeichnen wir das erste Element und dann weitere Elemente aus derselben Kategorie in der inneren Schleife `{iterateWhile}`: - -```latte -{foreach $items as $item} -

                                                {$item->name}

                                                -
                                                  - {iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                • {$item->name}
                                                • - {/iterateWhile} -
                                                -{/foreach} -``` - - -Verschachtelte Schleifen .[#toc-nested-loops] ---------------------------------------------- - -Wir können mehrere innere Schleifen in einem Zyklus erstellen und sie sogar verschachteln. Auf diese Weise können zum Beispiel Unterkategorien gruppiert werden. - -Angenommen, es gibt eine weitere Spalte in der Tabelle `subCatId` und jede Kategorie befindet sich nicht nur in einer separaten `
                                                  `befindet, wird jede Unterkategorie in einer separaten `
                                                    `: - -```latte -{foreach $items as $item} -
                                                      - {iterateWhile} -
                                                        - {iterateWhile} -
                                                      1. {$item->name} - {/iterateWhile $item->subCatId === $iterator->nextValue->subCatId} -
                                                      - {/iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                    -{/foreach} -``` - - -Filter |Batch .[#toc-filter-batch] ----------------------------------- - -Die Gruppierung von linearen Elementen erfolgt ebenfalls über einen Filter `batch` in Stapel mit einer festen Anzahl von Elementen: - -```latte -
                                                      -{foreach ($items|batch:3) as $batch} - {foreach $batch as $item} -
                                                    • {$item->name}
                                                    • - {/foreach} -{/foreach} -
                                                    -``` - -Er kann wie folgt durch iterateWhile ersetzt werden: - -```latte -
                                                      -{foreach $items as $item} - {iterateWhile} -
                                                    • {$item->name}
                                                    • - {/iterateWhile $iterator->counter0 % 3} -{/foreach} -
                                                    -``` - -{{leftbar: /@left-menu}} diff --git a/latte/de/cookbook/migration-from-php.texy b/latte/de/cookbook/migration-from-php.texy index 73f6e165fa..cf6d12b922 100644 --- a/latte/de/cookbook/migration-from-php.texy +++ b/latte/de/cookbook/migration-from-php.texy @@ -1,28 +1,28 @@ -Umstellung von PHP auf Latte +Migration von PHP nach Latte **************************** .[perex] -Migrieren Sie ein altes Projekt, das in reinem PHP geschrieben wurde, nach Latte? Wir haben ein Tool, das Ihnen die Migration erleichtert. [Probieren Sie es online |https://php2latte.nette.org] aus. +Konvertieren Sie ein altes Projekt, das in reinem PHP geschrieben ist, nach Latte? Wir haben ein Werkzeug für Sie, das die Migration erleichtert. [Probieren Sie es online aus |https://fiddle.nette.org/php2latte/]. -Sie können das Tool von [GitHub |https://github.com/nette/latte-tools] herunterladen oder es mit Composer installieren: +Sie können das Werkzeug von [GitHub|https://github.com/nette/latte-tools] herunterladen oder mit Composer installieren: ```shell composer create-project latte/tools ``` -Der Konverter verwendet keine einfachen Ersetzungen mit regulären Ausdrücken, sondern greift direkt auf den PHP-Parser zu, so dass er jede komplexe Syntax verarbeiten kann. +Der Konverter verwendet keine einfachen Ersetzungen mit regulären Ausdrücken, sondern nutzt direkt den PHP-Parser, sodass er mit jeder noch so komplexen Syntax umgehen kann. -Das Skript `php-to-latte.php` wird für die Konvertierung von PHP nach Latte verwendet: +Zur Konvertierung von PHP nach Latte dient das Skript `php-to-latte.php`: ```shell php-to-latte.php input.php [output.latte] ``` -Beispiel .[#toc-example] ------------------------- +Beispiel +-------- -Die Eingabedatei könnte so aussehen (sie ist Teil des PunBB-Forum-Codes): +Die Eingabedatei kann zum Beispiel so aussehen (dies ist ein Teil des Codes des PunBB-Forums): ```php

                                                    @@ -48,7 +48,7 @@ foreach ($result as $cur_group) {
                                      ``` -Erzeugt diese Vorlage: +Es generiert dieses Template: ```latte

                                      {$lang_common['User list']}

                                      @@ -68,5 +68,3 @@ Erzeugt diese Vorlage:
                                      ``` - -{{leftbar: /@left-menu}} diff --git a/latte/de/cookbook/migration-from-twig.texy b/latte/de/cookbook/migration-from-twig.texy index c4778b4b1d..eaeba66ee3 100644 --- a/latte/de/cookbook/migration-from-twig.texy +++ b/latte/de/cookbook/migration-from-twig.texy @@ -1,38 +1,38 @@ -Migration von Twig zu Latte -*************************** +Migration von Twig nach Latte +***************************** .[perex] -Migrieren Sie ein in Twig geschriebenes Projekt auf das modernere Latte? Wir haben ein Tool, das Ihnen die Migration erleichtert. [Probieren Sie es online |https://twig2latte.nette.org] aus. +Konvertieren Sie ein Projekt, das in Twig geschrieben ist, in das modernere Latte? Wir haben ein Werkzeug für Sie, das die Migration erleichtert. [Probieren Sie es online aus |https://fiddle.nette.org/twig2latte/]. -Sie können das Tool von [GitHub |https://github.com/nette/latte-tools] herunterladen oder es mit Composer installieren: +Sie können das Werkzeug von [GitHub|https://github.com/nette/latte-tools] herunterladen oder mit Composer installieren: ```shell composer create-project latte/tools ``` -Der Konverter verwendet keine einfachen Ersetzungen durch reguläre Ausdrücke, sondern nutzt direkt den Twig-Parser, so dass er jede komplexe Syntax verarbeiten kann. +Der Konverter verwendet keine einfachen Ersetzungen mit regulären Ausdrücken, sondern nutzt direkt den Twig-Parser, sodass er mit jeder noch so komplexen Syntax umgehen kann. -Ein Skript `twig-to-latte.php` wird für die Konvertierung von Twig nach Latte verwendet: +Zur Konvertierung von Twig nach Latte dient das Skript `twig-to-latte.php`: ```shell twig-to-latte.php input.twig.html [output.latte] ``` -Konvertierung .[#toc-conversion] --------------------------------- +Konvertierung +------------- -Die Konvertierung erfordert eine manuelle Bearbeitung des Ergebnisses, da die Konvertierung nicht eindeutig erfolgen kann. Twig verwendet eine Punktsyntax, wobei `{{ a.b }}` `$a->b` bedeuten kann, `$a['b']` oder `$a->getB()` bedeuten kann, was beim Kompilieren nicht unterschieden werden kann. Der Konverter wandelt daher alles in `$a->b` um. +Die Konvertierung erfordert eine manuelle Anpassung des Ergebnisses, da die Konvertierung nicht eindeutig durchgeführt werden kann. Twig verwendet die Punkt-Syntax, wobei `{{ a.b }}` `$a->b`, `$a['b']` oder `$a->getB()` bedeuten kann, was bei der Kompilierung nicht unterschieden werden kann. Der Konverter konvertiert daher alles in `$a->b`. -Einige Funktionen, Filter oder Tags haben keine Entsprechung in Latte oder verhalten sich leicht anders. +Einige Funktionen, Filter oder Tags haben kein Äquivalent in Latte oder können sich geringfügig anders verhalten. -Beispiel .[#toc-example] ------------------------- +Beispiel +-------- -Die Eingabedatei könnte wie folgt aussehen: +Die Eingabedatei kann zum Beispiel so aussehen: -```latte +```twig {% use "blocks.twig" %} @@ -54,7 +54,7 @@ Die Eingabedatei könnte wie folgt aussehen: ``` -Nach der Konvertierung in Latte erhalten wir diese Vorlage: +Nach der Konvertierung in Latte erhalten wir dieses Template: ```latte {import 'blocks.latte'} @@ -77,5 +77,3 @@ Nach der Konvertierung in Latte erhalten wir diese Vorlage: ``` - -{{leftbar: /@left-menu}} diff --git a/latte/de/cookbook/passing-variables.texy b/latte/de/cookbook/passing-variables.texy new file mode 100644 index 0000000000..c04d1b9898 --- /dev/null +++ b/latte/de/cookbook/passing-variables.texy @@ -0,0 +1,158 @@ +Übergabe von Variablen zwischen Templates +***************************************** + +Diese Anleitung erklärt, wie Variablen zwischen Templates in Latte mithilfe verschiedener Tags wie `{include}`, `{import}`, `{embed}`, `{layout}`, `{sandbox}` und anderen übergeben werden. Sie erfahren auch, wie Sie mit Variablen in den Tags `{block}` und `{define}` arbeiten und wofür der Tag `{parameters}` dient. + + +Variablentypen +-------------- +Variablen in Latte können je nachdem, wie und wo sie definiert sind, in drei Kategorien eingeteilt werden: + +**Eingabevariablen** sind solche, die von außen an das Template übergeben werden, beispielsweise aus einem PHP-Skript oder mithilfe eines Tags wie `{include}`. + +```php +$latte->render('template.latte', ['userName' => 'Jan', 'userAge' => 30]); +``` + +**Umgebungsvariablen** sind Variablen, die an der Stelle eines bestimmten Tags existieren. Sie umfassen alle Eingabevariablen und weitere Variablen, die mit Tags wie `{var}`, `{default}` oder innerhalb einer `{foreach}`-Schleife erstellt wurden. + +```latte +{foreach $users as $user} + {include 'userBox.latte', user: $user} +{/foreach} +``` + +**Explizite Variablen** sind solche, die direkt innerhalb eines Tags spezifiziert und an das Ziel-Template gesendet werden. + +```latte +{include 'userBox.latte', name: $user->name, age: $user->age} +``` + + +`{block}` +--------- +Der Tag `{block}` wird verwendet, um wiederverwendbare Codeblöcke zu definieren, die in vererbten Templates angepasst oder erweitert werden können. Umgebungsvariablen, die vor dem Block definiert wurden, sind innerhalb des Blocks verfügbar, aber jegliche Änderungen an Variablen wirken sich nur innerhalb dieses Blocks aus. + +```latte +{var $foo = 'original'} +{block example} + {var $foo = 'geändert'} +{/block} + +{$foo} // gibt aus: original +``` + + +`{define}` +---------- +Der Tag `{define}` dient zur Erstellung von Blöcken, die erst gerendert werden, wenn sie mit `{include}` aufgerufen werden. Die innerhalb dieser Blöcke verfügbaren Variablen hängen davon ab, ob Parameter in der Definition angegeben sind. Wenn ja, haben sie nur Zugriff auf diese Parameter. Wenn nicht, haben sie Zugriff auf alle Eingabevariablen des Templates, in dem die Blöcke definiert sind. + +```latte +{define hello} + {* hat Zugriff auf alle Eingabevariablen des Templates *} +{/define} + +{define hello $name} + {* hat nur Zugriff auf den Parameter $name *} +{/define} +``` + + +`{parameters}` +-------------- +Der Tag `{parameters}` dient zur expliziten Deklaration erwarteter Eingabevariablen am Anfang des Templates. Auf diese Weise können erwartete Variablen und ihre Datentypen einfach dokumentiert werden. Es ist auch möglich, Standardwerte zu definieren. + +```latte +{parameters int $age, string $name = 'unbekannt'} +

                                      Alter: {$age}, Name: {$name}

                                      +``` + + +`{include file}` +---------------- +Der Tag `{include file}` dient zum Einfügen eines gesamten Templates. An dieses Template werden sowohl die Eingabevariablen des Templates, in dem der Tag verwendet wird, als auch die darin explizit definierten Variablen übergeben. Das Ziel-Template kann jedoch den Umfang mit `{parameters}` einschränken. + +```latte +{include 'profile.latte', userId: $user->id} +``` + + +`{include block}` +----------------- +Wenn Sie einen Block einfügen, der im selben Template definiert ist, werden alle Umgebungs- und explizit definierten Variablen an ihn übergeben: + +```latte +{define blockName} +

                                      Name: {$name}, Alter: {$age}

                                      +{/define} + +{var $name = 'Jan', $age = 30} +{include blockName} +``` + +In diesem Beispiel werden die Variablen `$name` und `$age` an den Block `blockName` übergeben. Genauso verhält sich auch `{include parent}`. + +Beim Einfügen eines Blocks aus einem anderen Template werden nur Eingabevariablen und explizit definierte Variablen übergeben. Umgebungsvariablen sind nicht automatisch verfügbar. + +```latte +{include blockInOtherTemplate, name: $name, age: $age} +``` + + +`{layout}` oder `{extends}` +--------------------------- +Diese Tags definieren ein Layout, an das die Eingabevariablen des untergeordneten Templates und weiterhin Variablen übergeben werden, die im Code vor den Blöcken erstellt wurden: + +```latte +{layout 'layout.latte'} +{var $seo = 'index, follow'} +``` + +Das Template `layout.latte`: + +```latte + + + +``` + + +`{embed}` +--------- +Der Tag `{embed}` ähnelt dem Tag `{include}`, ermöglicht jedoch das Einbetten von Blöcken in das Template. Im Gegensatz zu `{include}` werden nur explizit deklarierte Variablen übergeben: + +```latte +{embed 'menu.latte', items: $menuItems} +{/embed} +``` + +In diesem Beispiel hat das Template `menu.latte` nur Zugriff auf die Variable `$items`. + +Umgekehrt ist in Blöcken innerhalb von `{embed}` der Zugriff auf alle Umgebungsvariablen möglich: + +```latte +{var $name = 'Jan'} +{embed 'menu.latte', items: $menuItems} + {block foo} + {$name} + {/block} +{/embed} +``` + + +`{import}` +---------- +Der Tag `{import}` wird zum Laden von Blöcken aus anderen Templates verwendet. Es werden sowohl Eingabe- als auch explizit deklarierte Variablen an die importierten Blöcke übertragen. + +```latte +{import 'buttons.latte'} +``` + + +`{sandbox}` +----------- +Der Tag `{sandbox}` isoliert ein Template zur sicheren Verarbeitung. Variablen werden ausschließlich explizit übergeben. + +```latte +{sandbox 'secure.latte', data: $secureData} +``` diff --git a/latte/de/cookbook/slim-framework.texy b/latte/de/cookbook/slim-framework.texy index 86160d70ee..6c50daa805 100644 --- a/latte/de/cookbook/slim-framework.texy +++ b/latte/de/cookbook/slim-framework.texy @@ -2,42 +2,42 @@ Verwendung von Latte mit Slim 4 ******************************* .[perex] -Dieser Artikel von "Daniel Opitz":https://odan.github.io/2022/04/06/slim4-latte.html beschreibt, wie man Latte mit dem Slim Framework verwendet. +Dieser Artikel, dessen Autor "Daniel Opitz":https://odan.github.io/2022/04/06/slim4-latte.html ist, beschreibt die Verwendung von Latte mit dem Slim Framework. -Zuerst "installieren Sie das Slim Framework":https://odan.github.io/2019/11/05/slim4-tutorial.html und dann Latte mit Composer: +Installieren Sie zuerst das "Slim Framework":https://odan.github.io/2019/11/05/slim4-tutorial.html und anschließend Latte mit Composer: ```shell composer require latte/latte ``` -Konfiguration .[#toc-configuration] ------------------------------------ +Konfiguration +------------- -Erstellen Sie ein neues Verzeichnis `templates` in Ihrem Projekt-Stammverzeichnis. Alle Vorlagen werden später dort abgelegt. +Erstellen Sie im Stammverzeichnis des Projekts ein neues Verzeichnis `templates`. Alle Templates werden später darin abgelegt. -Fügen Sie einen neuen Konfigurationsschlüssel `template` in Ihrer Datei `config/defaults.php` hinzu: +Fügen Sie zur Datei `config/defaults.php` einen neuen Konfigurationsschlüssel `template` hinzu: ```php $settings['template'] = __DIR__ . '/../templates'; ``` -Latte kompiliert die Vorlagen in nativen PHP-Code und speichert sie in einem Cache auf der Festplatte. So sind sie so schnell, als wären sie in nativem PHP geschrieben worden. +Latte kompiliert Templates in nativen PHP-Code und speichert sie im Cache auf der Festplatte. Sie sind also genauso schnell, als wären sie in nativem PHP geschrieben. -Fügen Sie einen neuen Konfigurationsschlüssel `template_temp` in Ihrer Datei `config/defaults.php` hinzu: Stellen Sie sicher, dass das Verzeichnis `{project}/tmp/templates` existiert und Lese- und Schreibrechte besitzt. +Fügen Sie zur Datei `config/defaults.php` einen neuen Konfigurationsschlüssel `template_temp` hinzu: Stellen Sie sicher, dass das Verzeichnis `{project}/tmp/templates` existiert und Lese- und Schreibrechte hat. ```php $settings['template_temp'] = __DIR__ . '/../tmp/templates'; ``` -Latte regeneriert den Cache automatisch bei jeder Änderung der Vorlage, was in der Produktionsumgebung ausgeschaltet werden kann, um ein wenig Leistung zu sparen: +Latte regeneriert den Cache automatisch bei jeder Änderung des Templates, was in einer Produktionsumgebung deaktiviert werden kann, um etwas Leistung zu sparen: ```php -// in der Produktionsumgebung auf false ändern +// in Produktionsumgebung auf false ändern $settings['template_auto_refresh'] = true; ``` -Als nächstes fügen Sie eine DI-Container-Definition für die Klasse `Latte\Engine` hinzu. +Fügen Sie als Nächstes eine DI-Container-Definition für die Klasse `Latte\Engine` hinzu. ```php +
                                        {foreach $items as $item}
                                      • {$item|capitalize}
                                      • {/foreach}
                                      ``` -Wenn alles richtig konfiguriert ist, sollten Sie die folgende Ausgabe sehen: +Wenn alles korrekt konfiguriert ist, sollte folgende Ausgabe angezeigt werden: ```latte One @@ -155,4 +155,3 @@ Three ``` {{priority: -1}} -{{leftbar: /@left-menu}} diff --git a/latte/de/creating-extension.texy b/latte/de/creating-extension.texy deleted file mode 100644 index 147f81a087..0000000000 --- a/latte/de/creating-extension.texy +++ /dev/null @@ -1,579 +0,0 @@ -Erstellen einer Erweiterung -*************************** - -.[perex]{data-version:3.0} -Eine Erweiterung ist eine wiederverwendbare Klasse, die benutzerdefinierte Tags, Filter, Funktionen, Anbieter usw. definieren kann. - -Wir erstellen Erweiterungen, wenn wir unsere Latte-Anpassungen in verschiedenen Projekten wiederverwenden oder sie mit anderen teilen wollen. -Es ist auch nützlich, für jedes Webprojekt eine Erweiterung zu erstellen, die alle spezifischen Tags und Filter enthält, die Sie in den Projektvorlagen verwenden möchten. - - -Erweiterung Klasse .[#toc-extension-class] -========================================== - -Extension ist eine Klasse, die von [api:Latte\Extension] erbt. Sie wird bei Latte mit `addExtension()` (oder über eine [Konfigurationsdatei |application:configuration#Latte]) registriert: - -```php -$latte = new Latte\Engine; -$latte->addExtension(new MyLatteExtension); -``` - -Wenn Sie mehrere Erweiterungen registrieren und diese identisch benannte Tags, Filter oder Funktionen definieren, gewinnt die zuletzt hinzugefügte Erweiterung. Dies bedeutet auch, dass Ihre Erweiterungen native Tags/Filter/Funktionen außer Kraft setzen können. - -Immer wenn Sie eine Klasse ändern und die automatische Aktualisierung nicht ausgeschaltet ist, kompiliert Latte Ihre Vorlagen automatisch neu. - -Eine Klasse kann jede der folgenden Methoden implementieren: - -```php -abstract class Extension -{ - /** - * Initializes before template is compiler. - */ - public function beforeCompile(Engine $engine): void; - - /** - * Returns a list of parsers for Latte tags. - * @return array - */ - public function getTags(): array; - - /** - * Returns a list of compiler passes. - * @return array - */ - public function getPasses(): array; - - /** - * Returns a list of |filters. - * @return array - */ - public function getFilters(): array; - - /** - * Returns a list of functions used in templates. - * @return array - */ - public function getFunctions(): array; - - /** - * Returns a list of providers. - * @return array - */ - public function getProviders(): array; - - /** - * Returns a value to distinguish multiple versions of the template. - */ - public function getCacheKey(Engine $engine): mixed; - - /** - * Initializes before template is rendered. - */ - public function beforeRender(Template $template): void; -} -``` - -Um eine Vorstellung davon zu bekommen, wie die Erweiterung aussieht, sehen Sie sich die eingebaute "CoreExtension":https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php an. - - -beforeCompile(Latte\Engine $engine): void .[method] ---------------------------------------------------- - -Wird aufgerufen, bevor die Vorlage kompiliert wird. Die Methode kann z. B. für kompilierungsbezogene Initialisierungen verwendet werden. - - -getTags(): array .[method] --------------------------- - -Wird aufgerufen, wenn die Vorlage kompiliert wird. Gibt ein assoziatives Array *Tagname => callable* zurück, das [Tag-Parsing-Funktionen |#Tag Parsing Function] enthält. - -```php -public function getTags(): array -{ - return [ - 'foo' => [FooNode::class, 'create'], - 'bar' => [BarNode::class, 'create'], - 'n:baz' => [NBazNode::class, 'create'], - // ... - ]; -} -``` - -Das Tag `n:baz` stellt ein reines n:Attribut dar, d. h. es ist ein Tag, das nur als Attribut geschrieben werden kann. - -Im Falle der Tags `foo` und `bar` erkennt Latte automatisch, ob es sich um Paare handelt, und wenn ja, können sie automatisch mit n:Attributen geschrieben werden, einschließlich der Varianten mit den Präfixen `n:inner-foo` und `n:tag-foo`. - -Die Reihenfolge der Ausführung solcher n:Attribute wird durch ihre Reihenfolge in dem von `getTags()` zurückgegebenen Array bestimmt. So wird `n:foo` immer vor `n:bar` ausgeführt, auch wenn die Attribute im HTML-Tag in umgekehrter Reihenfolge aufgeführt sind als `
                                      `. - -Wenn Sie die Reihenfolge von n:Attributen über mehrere Erweiterungen hinweg bestimmen müssen, verwenden Sie die Hilfsmethode `order()`, bei der der Parameter `before` xor `after` bestimmt, welche Tags vor oder nach dem Tag angeordnet werden. - -```php -public function getTags(): array -{ - return [ - 'foo' => self::order([FooNode::class, 'create'], before: 'bar')] - 'bar' => self::order([BarNode::class, 'create'], after: ['block', 'snippet'])] - ]; -} -``` - - -getPasses(): array .[method] ----------------------------- - -Sie wird aufgerufen, wenn die Vorlage kompiliert wird. Gibt ein assoziatives Array *Name pass => callable* zurück, das Funktionen repräsentiert, die sogenannte [Compiler-Passes |#compiler passes] darstellen, die den AST durchlaufen und verändern. - -Auch hier kann die Hilfsmethode `order()` verwendet werden. Der Wert der Parameter `before` oder `after` kann `*` mit der Bedeutung before/after all sein. - -```php -public function getPasses(): array -{ - return [ - 'optimize' => [Passes::class, 'optimizePass'], - 'sandbox' => self::order([$this, 'sandboxPass'], before: '*'), - // ... - ]; -} -``` - - -beforeRender(Latte\Engine $engine): void .[method] --------------------------------------------------- - -Sie wird vor jedem Rendering einer Vorlage aufgerufen. Die Methode kann z. B. dazu verwendet werden, Variablen zu initialisieren, die während des Renderings verwendet werden. - - -getFilters(): array .[method] ------------------------------ - -Sie wird aufgerufen, bevor die Vorlage gerendert wird. Gibt [Filter |extending-latte#filters] als assoziatives Array zurück *Filtername => aufrufbar*. - -```php -public function getFilters(): array -{ - return [ - 'batch' => [$this, 'batchFilter'], - 'trim' => [$this, 'trimFilter'], - // ... - ]; -} -``` - - -getFunctions(): array .[method] -------------------------------- - -Wird aufgerufen, bevor die Vorlage gerendert wird. Gibt [Funktionen |extending-latte#functions] als assoziatives Array zurück *Funktionsname => aufrufbar*. - -```php -public function getFunctions(): array -{ - return [ - 'clamp' => [$this, 'clampFunction'], - 'divisibleBy' => [$this, 'divisibleByFunction'], - // ... - ]; -} -``` - - -getProviders(): array .[method] -------------------------------- - -Sie wird aufgerufen, bevor die Vorlage gerendert wird. Gibt ein Array von Anbietern zurück, bei denen es sich in der Regel um Objekte handelt, die Tags zur Laufzeit verwenden. Auf sie wird über `$this->global->...` zugegriffen. - -```php -public function getProviders(): array -{ - return [ - 'myFoo' => $this->foo, - 'myBar' => $this->bar, - // ... - ]; -} -``` - - -getCacheKey(Latte\Engine $engine): mixed .[method] --------------------------------------------------- - -Sie wird aufgerufen, bevor die Vorlage gerendert wird. Der Rückgabewert wird Teil des Schlüssels, dessen Hash im Namen der kompilierten Vorlagendatei enthalten ist. Für unterschiedliche Rückgabewerte erzeugt Latte also unterschiedliche Cache-Dateien. - - -Wie funktioniert Latte? .[#toc-how-does-latte-work] -=================================================== - -Um zu verstehen, wie man benutzerdefinierte Tags oder Compilerübergänge definiert, ist es wichtig zu verstehen, wie Latte unter der Haube funktioniert. - -Die Kompilierung von Vorlagen in Latte funktioniert vereinfacht gesagt wie folgt: - -- Zuerst zerlegt der **Lexer** den Quellcode der Vorlage in kleine Stücke (Token), um die Verarbeitung zu erleichtern. -- Dann wandelt der **Parser** den Token-Strom in einen sinnvollen Baum von Knoten um (den Abstract Syntax Tree, AST) -- Schließlich **generiert** der Compiler aus dem AST eine PHP-Klasse, die die Vorlage wiedergibt und im Zwischenspeicher ablegt. - -Eigentlich ist die Kompilierung ein bisschen komplizierter. Latte **hat zwei** Lexer und Parser: einen für die HTML-Vorlage und einen für den PHP-ähnlichen Code innerhalb der Tags. Außerdem läuft das Parsen nicht nach der Tokenisierung, sondern der Lexer und der Parser laufen parallel in zwei "Threads" und koordinieren sich. Das ist Raketentechnik :-) - -Außerdem haben alle Tags ihre eigenen Parsing-Routinen. Wenn der Parser auf ein Tag stößt, ruft er seine Parsing-Funktion auf (sie liefert [Extension::getTags() |#getTags]). -Ihre Aufgabe ist es, die Tag-Argumente und, im Falle von gepaarten Tags, den inneren Inhalt zu analysieren. Sie gibt einen *Knoten* zurück, der Teil des AST wird. Siehe [Tag-Parsing-Funktion |#Tag parsing function] für Details. - -Wenn der Parser seine Arbeit beendet hat, haben wir einen vollständigen AST, der die Vorlage repräsentiert. Der Wurzelknoten ist `Latte\Compiler\Nodes\TemplateNode`. Die einzelnen Knoten innerhalb des Baums repräsentieren dann nicht nur die Tags, sondern auch die HTML-Elemente, ihre Attribute, alle Ausdrücke, die innerhalb der Tags verwendet werden, usw. - -Danach kommen die so genannten [Compiler-Passes |#Compiler passes] ins Spiel, d. h. Funktionen (die von [Extension::getPasses() |#getPasses] zurückgegeben werden), die den AST verändern. - -Der gesamte Prozess, vom Laden des Vorlageninhalts über das Parsen bis hin zur Erzeugung der resultierenden Datei, lässt sich mit diesem Code abbilden, mit dem Sie experimentieren und die Zwischenergebnisse ausgeben können: - -```php -$latte = new Latte\Engine; -$source = $latte->getLoader()->getContent($file); -$ast = $latte->parse($source); -$latte->applyPasses($ast); -$code = $latte->generate($ast, $file); -``` - - -Beispiel für AST .[#toc-example-of-ast] ---------------------------------------- - -Um eine bessere Vorstellung vom AST zu bekommen, fügen wir ein Beispiel hinzu. Dies ist die Quelltextvorlage: - -```latte -{foreach $category->getItems() as $item} -
                                    1. {$item->name|upper}
                                    2. - {else} - no items found -{/foreach} -``` - -Und dies ist ihre Darstellung in Form von AST: - -/--pre -Latte\Compiler\Nodes\TemplateNode( - Latte\Compiler\Nodes\FragmentNode( - - Latte\Essential\Nodes\ForeachNode( - expression: Latte\Compiler\Nodes\Php\Expression\MethodCallNode( - object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$category') - name: Latte\Compiler\Nodes\Php\IdentifierNode('getItems') - ) - value: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') - content: Latte\Compiler\Nodes\FragmentNode( - - Latte\Compiler\Nodes\TextNode(' ') - - Latte\Compiler\Nodes\Html\ElementNode('li')( - content: Latte\Essential\Nodes\PrintNode( - expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode( - object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') - name: Latte\Compiler\Nodes\Php\IdentifierNode('name') - ) - modifier: Latte\Compiler\Nodes\Php\ModifierNode( - filters: - - Latte\Compiler\Nodes\Php\FilterNode('upper') - ) - ) - ) - ) - else: Latte\Compiler\Nodes\FragmentNode( - - Latte\Compiler\Nodes\TextNode('no items found') - ) - ) - ) -) -\-- - - -Benutzerdefinierte Tags .[#toc-custom-tags] -=========================================== - -Zur Definition eines neuen Tags sind drei Schritte erforderlich: - -- Definition der [Tag-Parsing-Funktion |#tag parsing function] (verantwortlich für das Parsen des Tags in einen Knoten) -- Erstellen einer Knotenklasse (verantwortlich für die [Erzeugung von PHP-Code |#generating PHP code] und [AST-Traversierung |#AST traversing]) -- Registrierung des Tags mit [Extension::getTags() |#getTags] - - -Tag-Parsing-Funktion .[#toc-tag-parsing-function] -------------------------------------------------- - -Das Parsen von Tags wird von der Parsing-Funktion durchgeführt (die von [Extension::getTags() |#getTags] zurückgegeben wird). Ihre Aufgabe ist es, alle Argumente innerhalb des Tags zu analysieren und zu überprüfen (sie verwendet TagParser, um dies zu tun). -Wenn das Tag ein Paar ist, bittet sie außerdem TemplateParser, den inneren Inhalt zu analysieren und zurückzugeben. -Die Funktion erstellt und gibt einen Knoten zurück, der in der Regel ein Kind von `Latte\Compiler\Nodes\StatementNode` ist und Teil des AST wird. - -Wir erstellen eine Klasse für jeden Knoten, was wir jetzt tun werden, und platzieren die Parsing-Funktion elegant als statische Factory in dieser Klasse. Versuchen wir als Beispiel, den bekannten Tag `{foreach}` zu erstellen: - -```php -use Latte\Compiler\Nodes\StatementNode; - -class ForeachNode extends StatementNode -{ - // eine Parsing-Funktion, die vorerst nur einen Knoten erstellt - public static function create(Latte\Compiler\Tag $tag): self - { - $node = new self; - return $node; - } - - public function print(Latte\Compiler\PrintContext $context): string - { - // Code wird später hinzugefügt - } - - public function &getIterator(): \Generator - { - // Code wird später hinzugefügt - } -} -``` - -Der Parsing-Funktion `create()` wird ein Objekt [api:Latte\Compiler\Tag] übergeben, das grundlegende Informationen über das Tag enthält (ob es sich um ein klassisches Tag oder ein n:-Attribut handelt, in welcher Zeile es sich befindet usw.) und hauptsächlich auf das [api:Latte\Compiler\TagParser] in `$tag->parser` zugreift. - -Wenn das Tag Argumente haben muss, prüfen Sie deren Vorhandensein, indem Sie `$tag->expectArguments()` aufrufen. Zum Parsen stehen die Methoden des `$tag->parser` Objekts zur Verfügung: - -- `parseExpression(): ExpressionNode` für einen PHP-ähnlichen Ausdruck (z. B. `10 + 3`) -- `parseUnquotedStringOrExpression(): ExpressionNode` für einen Ausdruck oder eine unquoted-string -- `parseArguments(): ArrayNode` für den Inhalt des Arrays (z.B. `10, true, foo => bar`) -- `parseModifier(): ModifierNode` für einen Modifikator (z. B. `|upper|truncate:10`) -- `parseType(): expressionNode` für einen Typ-Hinweis (z. B. `int|string` oder `Foo\Bar[]`) - -und ein low-level [api:Latte\Compiler\TokenStream], das direkt mit Token arbeitet: - -- `$tag->parser->stream->consume(...): Token` -- `$tag->parser->stream->tryConsume(...): ?Token` - -Latte erweitert die PHP-Syntax in kleinen Schritten, z.B. durch Hinzufügen von Modifikatoren, verkürzten ternären Operatoren oder der Möglichkeit, einfache alphanumerische Zeichenketten ohne Anführungszeichen zu schreiben. Aus diesem Grund verwenden wir den Begriff *PHP-ähnlich* anstelle von PHP. Die Methode `parseExpression()` analysiert also beispielsweise `foo` als `'foo'`. -Darüber hinaus ist *unquoted-string* ein Spezialfall einer Zeichenkette, die ebenfalls nicht in Anführungszeichen gesetzt werden muss, aber gleichzeitig nicht alphanumerisch sein muss. Dies ist zum Beispiel der Pfad zu einer Datei im Tag `{include ../file.latte}`. Die Methode `parseUnquotedStringOrExpression()` wird verwendet, um ihn zu parsen. - -.[note] -Das Studium der Knotenklassen, die Teil von Latte sind, ist der beste Weg, um alle Feinheiten des Parsing-Prozesses zu lernen. - -Kehren wir zum Tag `{foreach}` zurück. In diesem Tag erwarten wir Argumente der Form `expression + 'as' + second expression`, die wir wie folgt parsen: - -```php -use Latte\Compiler\Nodes\StatementNode; -use Latte\Compiler\Nodes\Php\ExpressionNode; -use Latte\Compiler\Nodes\AreaNode; - -class ForeachNode extends StatementNode -{ - public ExpressionNode $expression; - public ExpressionNode $value; - - public static function create(Latte\Compiler\Tag $tag): self - { - $tag->expectArguments(); - $node = new self; - $node->expression = $tag->parser->parseExpression(); - $tag->parser->stream->consume('as'); - $node->value = $parser->parseExpression(); - return $node; - } -} -``` - -Die Ausdrücke, die wir in die Variablen `$expression` und `$value` geschrieben haben, stellen Unterknoten dar. - -.[tip] -Definieren Sie Variablen mit Unterknoten als **öffentlich**, damit sie bei Bedarf in [weiteren Verarbeitungsschritten |#Compiler Passes] geändert werden können. Außerdem ist es notwendig, sie für die [Durchquerung |#AST Traversing]**verfügbar** zu machen. - -Bei gepaarten Tags, wie dem unseren, muss die Methode auch den TemplateParser den inneren Inhalt des Tags parsen lassen. Dies wird von `yield` erledigt, das ein Paar ''[innerer Inhalt, End-Tag]'' zurückgibt. Wir speichern den inneren Inhalt in der Variablen `$node->content`. - -```php -public AreaNode $content; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $endTag] = yield; - return $node; -} -``` - -Mit dem Schlüsselwort `yield` wird die Methode `create()` beendet und die Kontrolle an den TemplateParser zurückgegeben, der das Parsen des Inhalts fortsetzt, bis er auf das End-Tag stößt. Dann übergibt er die Kontrolle zurück an `create()`, der dort weitermacht, wo er aufgehört hat. Die Verwendung der Methode `yield` gibt automatisch `Generator` zurück. - -Sie können auch ein Array von Tag-Namen an `yield` übergeben, für die Sie das Parsen stoppen möchten, wenn sie vor dem End-Tag auftreten. Dies hilft uns bei der Implementierung des `{foreach}...{else}...{/foreach}` Konstrukts. Wenn `{else}` auftritt, parsen wir den Inhalt danach in `$node->elseContent`: - -```php -public AreaNode $content; -public ?AreaNode $elseContent = null; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $nextTag] = yield ['else']; - if ($nextTag?->name === 'else') { - [$node->elseContent] = yield; - } - - return $node; -} -``` - -Mit der Rückgabe des Knotens ist das Tag-Parsing abgeschlossen. - - -Erzeugen von PHP-Code .[#toc-generating-php-code] -------------------------------------------------- - -Jeder Knoten muss die Methode `print()` implementieren. Gibt PHP-Code zurück, der den angegebenen Teil der Vorlage wiedergibt (Laufzeitcode). Als Parameter wird ein Objekt [api:Latte\Compiler\PrintContext] übergeben, das über eine nützliche Methode `format()` verfügt, die das Zusammensetzen des resultierenden Codes vereinfacht. - -Die Methode `format(string $mask, ...$args)` akzeptiert die folgenden Platzhalter in der Maske: -- `%node` gibt Node aus -- `%dump` exportiert den Wert nach PHP -- `%raw` fügt den Text direkt ohne Transformation ein -- `%args` gibt ArrayNode als Argumente für den Funktionsaufruf aus -- `%line` gibt einen Kommentar mit einer Zeilennummer aus -- `%escape(...)` entschlüsselt den Inhalt -- `%modify(...)` wendet einen Modifikator an -- `%modifyContent(...)` wendet einen Modifikator auf Blöcke an - - -Unsere Funktion `print()` könnte wie folgt aussehen (der Einfachheit halber vernachlässigen wir den Zweig `else` ): - -```php -public function print(Latte\Compiler\PrintContext $context): string -{ - return $context->format( - <<<'XX' - foreach (%node as %node) %line { - %node - } - - XX, - $this->expression, - $this->value, - $this->position, - $this->content, - ); -} -``` - -Die Variable `$this->position` ist bereits durch die Klasse [api:Latte\Compiler\Node] definiert und wird durch den Parser gesetzt. Sie enthält ein [api:Latte\Compiler\Position] Objekt mit der Position des Tags im Quellcode in Form einer Zeilen- und Spaltennummer. - -Der Laufzeitcode kann Hilfsvariablen verwenden. Um Kollisionen mit Variablen zu vermeiden, die von der Vorlage selbst verwendet werden, ist es üblich, ihnen die Zeichen `$ʟ__` voranzustellen. - -Es kann zur Laufzeit auch beliebige Werte verwenden, die dem Template in Form von Providern mit der Methode [Extension::getProviders() |#getProviders] übergeben werden. Der Zugriff auf sie erfolgt über `$this->global->...`. - - -AST-Traversierung .[#toc-ast-traversing] ----------------------------------------- - -Um den AST-Baum in der Tiefe zu durchlaufen, ist es notwendig, die Methode `getIterator()` zu implementieren. Dies ermöglicht den Zugriff auf Unterknoten: - -```php -public function &getIterator(): \Generator -{ - yield $this->expression; - yield $this->value; - yield $this->content; - if ($this->elseContent) { - yield $this->elseContent; - } -} -``` - -Beachten Sie, dass `getIterator()` einen Verweis zurückgibt. Dies ermöglicht es den Besuchern von Knoten, einzelne Knoten durch andere Knoten zu ersetzen. - -.[warning] -Wenn ein Knoten Unterknoten hat, ist es notwendig, diese Methode zu implementieren und alle Unterknoten verfügbar zu machen. Andernfalls könnte eine Sicherheitslücke entstehen. So wäre der Sandbox-Modus beispielsweise nicht in der Lage, Unterknoten zu kontrollieren und sicherzustellen, dass in ihnen keine unerlaubten Konstrukte aufgerufen werden. - -Da das Schlüsselwort `yield` im Körper der Methode vorhanden sein muss, auch wenn sie keine Unterknoten hat, schreiben Sie sie wie folgt: - -```php -public function &getIterator(): \Generator -{ - if (false) { - yield; - } -} -``` - - -Compiler übergibt .[#toc-compiler-passes] -========================================= - -Compiler-Passes sind Funktionen, die ASTs modifizieren oder Informationen in ihnen sammeln. Sie werden von der Methode [Extension::getPasses() |#getPasses] zurückgegeben. - - -Node Traverser .[#toc-node-traverser] -------------------------------------- - -Die häufigste Art, mit dem AST zu arbeiten, ist die Verwendung eines [api:Latte\Compiler\NodeTraverser]: - -```php -use Latte\Compiler\Node; -use Latte\Compiler\NodeTraverser; - -$ast = (new NodeTraverser)->traverse( - $ast, - enter: fn(Node $node) => ..., - leave: fn(Node $node) => ..., -); -``` - -Die Funktion *enter* (d.h. visitor) wird aufgerufen, wenn ein Knoten zum ersten Mal gefunden wird, bevor seine Unterknoten verarbeitet werden. Die Funktion *leave* wird aufgerufen, nachdem alle Unterknoten besucht worden sind. -Ein gängiges Muster ist, dass *enter* dazu verwendet wird, einige Informationen zu sammeln, und *leave* dann Änderungen auf der Grundlage dieser Informationen vornimmt. Zu dem Zeitpunkt, an dem *leave* aufgerufen wird, ist der gesamte Code innerhalb des Knotens bereits besucht und die notwendigen Informationen gesammelt worden. - -Wie ändert man AST? Am einfachsten ist es, einfach die Eigenschaften der Knoten zu ändern. Die zweite Möglichkeit besteht darin, den Knoten vollständig zu ersetzen, indem ein neuer Knoten zurückgegeben wird. Beispiel: Der folgende Code ändert alle Ganzzahlen im AST in Strings (z.B. 42 wird in `'42'` geändert). - -```php -use Latte\Compiler\Nodes\Php; - -$ast = (new NodeTraverser)->traverse( - $ast, - leave: function (Node $node) { - if ($node instanceof Php\Scalar\IntegerNode) { - return new Php\Scalar\StringNode((string) $node->value); - } - }, -); -``` - -Ein AST kann leicht Tausende von Knoten enthalten, und das Durchlaufen all dieser Knoten kann langsam sein. In einigen Fällen ist es möglich, eine vollständige Durchquerung zu vermeiden. - -Wenn Sie nach allen `Html\ElementNode` in einem Baum suchen, wissen Sie, dass es keinen Sinn macht, auch alle untergeordneten Knoten zu überprüfen, sobald Sie `Php\ExpressionNode` gesehen haben, da HTML nicht in Ausdrücken enthalten sein kann. In diesem Fall können Sie den Traverser anweisen, nicht in den Klassenknoten zu rekursieren: - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Php\ExpressionNode) { - return NodeTraverser::DontTraverseChildren; - } - // ... - }, -); -``` - -Wenn Sie nur nach einem bestimmten Knoten suchen, ist es auch möglich, den Traversal nach dem Auffinden des Knotens ganz abzubrechen. - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Nodes\ParametersNode) { - return NodeTraverser::StopTraversal; - } - // ... - }, -); -``` - - -Knoten-Helfer .[#toc-node-helpers] ----------------------------------- - -Die Klasse [api:Latte\Compiler\NodeHelpers] bietet einige Methoden, mit denen AST-Knoten gefunden werden können, die entweder einen bestimmten Callback etc. erfüllen. Ein paar Beispiele werden gezeigt: - -```php -use Latte\Compiler\NodeHelpers; - -// findet alle HTML-Element-Knoten -$elements = NodeHelpers::find($ast, fn(Node $node) => $node instanceof Nodes\Html\ElementNode); - -// findet den ersten Textknoten -$text = NodeHelpers::findFirst($ast, fn(Node $node) => $node instanceof Nodes\TextNode); - -// wandelt PHP-Wert-Knoten in realen Wert um -$value = NodeHelpers::toValue($node); - -// konvertiert statische Textknoten in eine Zeichenkette -$text = NodeHelpers::toText($node); -``` diff --git a/latte/de/custom-filters.texy b/latte/de/custom-filters.texy new file mode 100644 index 0000000000..4cc69c600a --- /dev/null +++ b/latte/de/custom-filters.texy @@ -0,0 +1,231 @@ +Erstellung eigener Filter +************************* + +.[perex] +Filter sind leistungsstarke Werkzeuge zur Formatierung und Bearbeitung von Daten direkt in Latte-Templates. Sie bieten eine saubere Syntax mit dem Pipe-Symbol (`|`) zur Transformation von Variablen oder Ausdrucksergebnissen in das gewünschte Ausgabeformat. + + +Was sind Filter? +================ + +Filter in Latte sind im Grunde **PHP-Funktionen, die speziell dafür entwickelt wurden, einen Eingabewert in einen Ausgabewert zu transformieren**. Sie werden mit der Pipe-Notation (`|`) innerhalb von Template-Ausdrücken (`{...}`) angewendet. + +**Bequemlichkeit:** Filter ermöglichen es Ihnen, gängige Formatierungsaufgaben (wie Datumsformatierung, Groß-/Kleinschreibung, Kürzung) oder Datenmanipulationen in wiederverwendbare Einheiten zu kapseln. Anstatt komplexen PHP-Code in Ihren Templates zu wiederholen, können Sie einfach einen Filter anwenden: +```latte +{* Anstatt komplexem PHP zum Kürzen: *} +{$article->text|truncate:100} + +{* Anstatt Code zur Datumsformatierung: *} +{$event->startTime|date:'Y-m-d H:i'} + +{* Anwendung mehrerer Transformationen: *} +{$product->name|lower|capitalize} +``` + +**Lesbarkeit:** Die Verwendung von Filtern macht Templates übersichtlicher und stärker auf die Präsentation ausgerichtet, da die Transformationslogik in die Filterdefinition verschoben wird. + +**Kontextsensitivität:** Ein entscheidender Vorteil von Filtern in Latte ist ihre Fähigkeit, [kontextsensitiv |#Kontextsensitive Filter] zu sein. Das bedeutet, dass ein Filter den Inhaltstyp erkennen kann, mit dem er arbeitet (HTML, JavaScript, reiner Text usw.), und entsprechende Logik oder Escaping anwenden kann, was für Sicherheit und Korrektheit entscheidend ist, insbesondere bei der Generierung von HTML. + +**Integration mit Anwendungslogik:** Genau wie bei eigenen Funktionen kann das PHP-Callable hinter einem Filter eine Closure, eine statische Methode oder eine Instanzmethode sein. Dies ermöglicht es Filtern, bei Bedarf auf Anwendungsdienste oder Daten zuzugreifen, auch wenn ihr Hauptzweck die *Transformation des Eingabewertes* bleibt. + +Latte bietet standardmäßig eine umfangreiche Sammlung von [Standardfilter |filters]. Eigene Filter ermöglichen es Ihnen, diese Sammlung um projektspezifische Formatierungen und Transformationen zu erweitern. + +Wenn Sie Logik basierend auf *mehreren* Eingaben durchführen müssen oder keinen primären Wert zur Transformation haben, ist wahrscheinlich die Verwendung einer [eigenen Funktion |custom-functions] geeigneter. Wenn Sie komplexes Markup generieren oder den Template-Fluss steuern müssen, ziehen Sie einen [eigenen Tag |custom-tags] in Betracht. + + +Erstellung und Registrierung von Filtern +======================================== + +Es gibt mehrere Möglichkeiten, eigene Filter in Latte zu definieren und zu registrieren. + + +Direkte Registrierung über `addFilter()` +---------------------------------------- + +Der einfachste Weg, einen Filter hinzuzufügen, ist die Verwendung der Methode `addFilter()` direkt am `Latte\Engine`-Objekt. Sie geben den Namen des Filters (wie er im Template verwendet wird) und das entsprechende PHP-Callable an. + +```php +$latte = new Latte\Engine; + +// Einfacher Filter ohne Argumente +$latte->addFilter('initial', fn(string $s): string => mb_substr($s, 0, 1) . '.'); + +// Filter mit optionalem Argument +$latte->addFilter('shortify', function (string $s, int $len = 10): string { + return mb_substr($s, 0, $len); +}); + +// Filter zur Verarbeitung von Arrays +$latte->addFilter('sum', fn(array $numbers): int|float => array_sum($numbers)); +``` + +**Verwendung im Template:** + +```latte +{$name|initial} {* Gibt 'J.' aus, wenn $name 'John' ist *} +{$description|shortify} {* Verwendet die Standardlänge 10 *} +{$description|shortify:50} {* Verwendet die Länge 50 *} +{$prices|sum} {* Gibt die Summe der Elemente im Array $prices aus *} +``` + +**Übergabe von Argumenten:** + +Der Wert links von der Pipe (`|`) wird immer als *erstes* Argument an die Filterfunktion übergeben. Alle Parameter, die nach dem Doppelpunkt (`:`) im Template angegeben werden, werden als nachfolgende Argumente übergeben. + +```latte +{$text|shortify:30} +// Ruft die PHP-Funktion shortify($text, 30) auf +``` + + +Registrierung über eine Erweiterung +----------------------------------- + +Für eine bessere Organisation, insbesondere bei der Erstellung wiederverwendbarer Filtersets oder deren Weitergabe als Pakete, ist die empfohlene Methode, sie innerhalb einer [Latte-Erweiterung |extending-latte#Latte Extension] zu registrieren: + +```php +namespace App\Latte; + +use Latte\Extension; + +class MyLatteExtension extends Extension +{ + public function getFilters(): array + { + return [ + 'initial' => $this->initial(...), + 'shortify' => $this->shortify(...), + ]; + } + + public function initial(string $s): string + { + return mb_substr($s, 0, 1) . '.'; + } + + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// Registrierung +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +Dieser Ansatz hält Ihre Filterlogik gekapselt und die Registrierung einfach. + + +Verwendung eines Filter-Loaders +------------------------------- + +Latte ermöglicht die Registrierung eines Filter-Loaders mit `addFilterLoader()`. Dies ist ein einzelnes Callable, das Latte während der Kompilierung nach jedem unbekannten Filternamen fragt. Der Loader gibt das PHP-Callable des Filters oder `null` zurück. + +```php +$latte = new Latte\Engine; + +// Der Loader kann dynamisch Filter-Callables erstellen/abrufen +$latte->addFilterLoader(function (string $name): ?callable { + if ($name === 'myLazyFilter') { + // Stellen Sie sich hier eine aufwendige Initialisierung vor... + $service = get_some_expensive_service(); + return fn($value) => $service->process($value); + } + return null; +}); +``` + +Diese Methode war ursprünglich für das Lazy Loading von Filtern mit sehr **aufwendiger Initialisierung** gedacht. Moderne Dependency-Injection-Praktiken handhaben Lazy Services jedoch in der Regel effizienter. + +Filter-Loader erhöhen die Komplexität und werden im Allgemeinen nicht zugunsten der direkten Registrierung mit `addFilter()` oder innerhalb einer Erweiterung mit `getFilters()` empfohlen. Verwenden Sie Loader nur, wenn Sie einen zwingenden, spezifischen Grund im Zusammenhang mit Leistungsproblemen bei der Filterinitialisierung haben, der nicht anders gelöst werden kann. + + +Filter unter Verwendung einer Klasse mit Attributen +--------------------------------------------------- + +Eine weitere elegante Möglichkeit, Filter zu definieren, ist die Verwendung von Methoden in Ihrer [Template-Parameterklasse |develop#Parameter als Klasse]. Fügen Sie einfach das Attribut `#[Latte\Attributes\TemplateFilter]` zur Methode hinzu. + +```php +use Latte\Attributes\TemplateFilter; + +class TemplateParameters +{ + public function __construct( + public string $description, + // weitere Parameter... + ) {} + + #[TemplateFilter] + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// Übergabe des Objekts an das Template +$params = new TemplateParameters(description: '...'); +$latte->render('template.latte', $params); +``` + +Latte erkennt und registriert automatisch Methoden, die mit diesem Attribut gekennzeichnet sind, wenn das `TemplateParameters`-Objekt an das Template übergeben wird. Der Name des Filters im Template entspricht dem Namen der Methode (`shortify` in diesem Fall). + +```latte +{* Verwendung des in der Parameterklasse definierten Filters *} +{$description|shortify:50} +``` + + +Kontextsensitive Filter +======================= + +Manchmal benötigt ein Filter mehr Informationen als nur den Eingabewert. Er muss möglicherweise den **Inhaltstyp** des Strings kennen, mit dem er arbeitet (z. B. HTML, JavaScript, reiner Text) oder ihn sogar ändern. Dies ist eine Situation für kontextsensitive Filter. + +Ein kontextsensitiver Filter wird genauso definiert wie ein normaler Filter, aber sein **erster Parameter muss** als `Latte\Runtime\FilterInfo` typisiert sein. Latte erkennt diese Signatur automatisch und übergibt beim Aufruf des Filters das `FilterInfo`-Objekt. Die folgenden Parameter erhalten wie üblich die Filterargumente. + +```php +use Latte\Runtime\FilterInfo; +use Latte\ContentType; + +$latte->addFilter('money', function (FilterInfo $info, float $amount): string { + // 1. Überprüfen Sie den Eingabe-Inhaltstyp (optional, aber empfohlen) + // Erlauben Sie null (variabler Eingang) oder reinen Text. Lehnen Sie ab, wenn auf HTML usw. angewendet. + if (!in_array($info->contentType, [null, ContentType::Text], true)) { + $actualType = $info->contentType ?? 'mixed'; + throw new \RuntimeException( + "Filter |money wurde in inkompatiblem Inhaltstyp $actualType verwendet. Erwartet wurde Text oder null." + ); + } + + // 2. Führen Sie die Transformation durch + $formatted = number_format($amount, 2, '.', ',') . ' EUR'; + $htmlOutput = '' . htmlspecialchars($formatted) . ''; // Stellen Sie korrektes Escaping sicher! + + // 3. Deklarieren Sie den Ausgabe-Inhaltstyp + $info->contentType = ContentType::Html; + + // 4. Geben Sie das Ergebnis zurück + return $htmlOutput; +}); +``` + +`$info->contentType` ist eine String-Konstante aus `Latte\ContentType` (z. B. `ContentType::Html`, `ContentType::Text`, `ContentType::JavaScript`, etc.) oder `null`, wenn der Filter auf eine Variable angewendet wird (`{$var|filter}`). Sie können diesen Wert **lesen**, um den Eingabekontext zu überprüfen, und **schreiben**, um den Typ des Ausgabekontexts zu deklarieren. + +Indem Sie den Inhaltstyp auf HTML setzen, teilen Sie Latte mit, dass der von Ihrem Filter zurückgegebene String sicheres HTML ist. Latte wird dann auf dieses Ergebnis **nicht** sein standardmäßiges automatisches Escaping anwenden. Dies ist entscheidend, wenn Ihr Filter HTML-Markup generiert. + +.[warning] +Wenn Ihr Filter HTML generiert, **sind Sie für das korrekte Escaping aller Eingabedaten verantwortlich**, die in diesem HTML verwendet werden (wie im Fall des Aufrufs `htmlspecialchars($formatted)` oben). Eine Unterlassung kann XSS-Schwachstellen erzeugen. Wenn Ihr Filter nur reinen Text zurückgibt, müssen Sie `$info->contentType` nicht setzen. + + +Filter auf Blöcken +------------------ + +Alle Filter, die auf [Blöcke |tags#block] angewendet werden, *müssen* kontextsensitiv sein. Dies liegt daran, dass der Inhalt eines Blocks einen definierten Inhaltstyp hat (normalerweise HTML), dessen sich der Filter bewusst sein muss. + +```latte +{block heading|money}1000{/block} +{* Der Filter 'money' erhält '1000' als zweites Argument + und $info->contentType wird ContentType::Html sein *} +``` + +Kontextsensitive Filter bieten eine starke Kontrolle darüber, wie Daten basierend auf ihrem Kontext verarbeitet werden, ermöglichen fortgeschrittene Funktionen und stellen das korrekte Escaping-Verhalten sicher, insbesondere bei der Generierung von HTML-Inhalten. diff --git a/latte/de/custom-functions.texy b/latte/de/custom-functions.texy new file mode 100644 index 0000000000..0989efb89d --- /dev/null +++ b/latte/de/custom-functions.texy @@ -0,0 +1,144 @@ +Erstellung eigener Funktionen +***************************** + +.[perex] +Fügen Sie einfach eigene Hilfsfunktionen zu Latte-Templates hinzu. Rufen Sie PHP-Logik direkt in Ausdrücken für Berechnungen, den Zugriff auf Dienste oder die Generierung dynamischer Inhalte auf, was Ihre Templates sauber und leistungsstark hält. + + +Was sind Funktionen? +==================== + +Funktionen in Latte ermöglichen es Ihnen, den Satz von Funktionen zu erweitern, die innerhalb von Ausdrücken in Templates (`{...}`) aufgerufen werden können. Sie können sie sich als **benutzerdefinierte PHP-Funktionen vorstellen, die nur innerhalb Ihrer Latte-Templates verfügbar sind**. Dies bringt mehrere Vorteile mit sich: + +**Bequemlichkeit:** Sie können Hilfslogik definieren (wie Berechnungen, Formatierungen oder den Zugriff auf Anwendungsdaten) und sie mit einer einfachen, bekannten Funktionssyntax direkt im Template aufrufen, genauso wie Sie `strlen()` oder `date()` in PHP aufrufen würden. + +```latte +{var $userInitials = initials($userName)} {* z.B. 'J. D.' *} + +{if hasPermission('article', 'edit')} + Bearbeiten +{/if} +``` + +**Keine Verschmutzung des globalen Raums:** Im Gegensatz zur Definition einer echten globalen Funktion in PHP existieren Latte-Funktionen nur im Kontext des Template-Renderings. Sie müssen den globalen PHP-Namensraum nicht mit Helfern belasten, die nur für Templates spezifisch sind. + +**Integration mit Anwendungslogik:** Das PHP-Callable hinter einer Latte-Funktion kann alles sein – eine anonyme Funktion, eine statische Methode oder eine Instanzmethode. Das bedeutet, dass Ihre Funktionen in Templates problemlos auf Anwendungsdienste, Datenbanken, Konfigurationen oder jede andere benötigte Logik zugreifen können, indem sie Variablen erfassen (im Fall von anonymen Funktionen) oder Dependency Injection verwenden (im Fall von Objekten). Das obige Beispiel `hasPermission` demonstriert dies deutlich, da es wahrscheinlich im Hintergrund einen Autorisierungsdienst aufruft. + +**Überschreiben nativer Funktionen (optional):** Sie können sogar eine Latte-Funktion mit demselben Namen wie eine native PHP-Funktion definieren. Im Template wird anstelle der ursprünglichen Funktion Ihre eigene Version aufgerufen. Dies kann nützlich sein, um ein template-spezifisches Verhalten bereitzustellen oder eine konsistente Verarbeitung sicherzustellen (z. B. sicherstellen, dass `strlen` immer Multibyte-sicher ist). Verwenden Sie diese Funktion mit Vorsicht, um Missverständnisse zu vermeiden. + +Standardmäßig erlaubt Latte den Aufruf *aller* nativen PHP-Funktionen (sofern nicht durch die [Sandbox |sandbox] eingeschränkt). Eigene Funktionen erweitern diese integrierte Bibliothek um die spezifischen Bedürfnisse Ihres Projekts. + +Wenn Sie nur einen einzelnen Wert transformieren, ist möglicherweise die Verwendung eines [eigenen Filters |custom-filters] geeigneter. + + +Erstellung und Registrierung von Funktionen +=========================================== + +Ähnlich wie bei Filtern gibt es mehrere Möglichkeiten, eigene Funktionen zu definieren und zu registrieren. + + +Direkte Registrierung über `addFunction()` +------------------------------------------ + +Die einfachste Methode ist die Verwendung von `addFunction()` am `Latte\Engine`-Objekt. Sie geben den Namen der Funktion (wie sie im Template erscheinen wird) und das entsprechende PHP-Callable an. + +```php +$latte = new Latte\Engine; + +// Einfache Hilfsfunktion +$latte->addFunction('initials', function (string $name): string { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; +}); +``` + +**Verwendung im Template:** + +```latte +{var $userInitials = initials($userName)} +``` + +Die Argumente der Funktion im Template werden direkt in derselben Reihenfolge an das PHP-Callable übergeben. PHP-Funktionalitäten wie Typ-Hints, Standardwerte und variable Parameter (`...`) funktionieren wie erwartet. + + +Registrierung über eine Erweiterung +----------------------------------- + +Für eine bessere Organisation und Wiederverwendbarkeit registrieren Sie Funktionen innerhalb einer [Latte Erweiterung |extending-latte#Latte Extension]. Dieser Ansatz wird für komplexere Anwendungen oder gemeinsam genutzte Bibliotheken empfohlen. + +```php +namespace App\Latte; + +use Latte\Extension; +use Nette\Security\Authorizator; + +class MyLatteExtension extends Extension +{ + public function __construct( + // Angenommen, der Authorizator-Dienst existiert + private Authorizator $authorizator, + ) { + } + + public function getFunctions(): array + { + // Registrierung von Methoden als Latte-Funktionen + return [ + 'hasPermission' => $this->hasPermission(...), + ]; + } + + public function hasPermission(string $resource, string $action): bool + { + return $this->authorizator->isAllowed($resource, $action); + } +} + +// Registrierung (angenommen, $container enthält den DIC) +$extension = $container->getByType(App\Latte\MyLatteExtension::class); +$latte = new Latte\Engine; +$latte->addExtension($extension); +``` + +Dieser Ansatz zeigt anschaulich, wie in Latte definierte Funktionen durch Objektmethoden unterstützt werden können, die ihre eigenen Abhängigkeiten haben können, die vom Dependency Injection Container Ihrer Anwendung oder einer Factory verwaltet werden. Dies hält die Logik Ihrer Templates mit dem Kern der Anwendung verbunden und bewahrt gleichzeitig eine übersichtliche Organisation. + + +Funktionen unter Verwendung einer Klasse mit Attributen +------------------------------------------------------- + +Genau wie Filter können Funktionen als Methoden in Ihrer [Template-Parameterklasse |develop#Parameter als Klasse] mithilfe des Attributs `#[Latte\Attributes\TemplateFunction]` definiert werden. + +```php +use Latte\Attributes\TemplateFunction; + +class TemplateParameters +{ + public function __construct( + public string $userName, + // weitere Parameter... + ) {} + + // Diese Methode wird als {initials(...)} im Template verfügbar sein + #[TemplateFunction] + public function initials(string $name): string + { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; + } +} + +// Übergabe des Objekts an das Template +$params = new TemplateParameters(userName: 'John Doe', /* ... */); +$latte->render('template.latte', $params); +``` + +Latte entdeckt und registriert automatisch Methoden, die mit diesem Attribut gekennzeichnet sind, wenn das Parameterobjekt an das Template übergeben wird. Der Name der Funktion im Template entspricht dem Namen der Methode. + +```latte +{* Verwendung der in der Parameterklasse definierten Funktion *} +{var $inits = initials($userName)} +``` + +**Kontextsensitive Funktionen?** + +Im Gegensatz zu Filtern gibt es kein direktes Konzept von "kontextsensitiven Funktionen", die ein Objekt ähnlich `FilterInfo` erhalten würden. Funktionen arbeiten innerhalb von Ausdrücken und benötigen typischerweise keinen direkten Zugriff auf den Rendering-Kontext oder Informationen zum Inhaltstyp auf die gleiche Weise wie Filter, die auf Blöcke angewendet werden. diff --git a/latte/de/custom-tags.texy b/latte/de/custom-tags.texy new file mode 100644 index 0000000000..483b5f5820 --- /dev/null +++ b/latte/de/custom-tags.texy @@ -0,0 +1,1135 @@ +Erstellen benutzerdefinierter Tags +********************************** + +.[perex] +Diese Seite bietet eine umfassende Anleitung zum Erstellen benutzerdefinierter Tags in Latte. Wir behandeln alles von einfachen Tags bis hin zu komplexeren Szenarien mit verschachteltem Inhalt und spezifischen Parsing-Anforderungen, wobei wir auf Ihrem Verständnis davon aufbauen, wie Latte Templates kompiliert. + +Benutzerdefinierte Tags bieten die höchste Kontrolle über die Template-Syntax und die Rendering-Logik, sind aber auch der komplexeste Erweiterungspunkt. Bevor Sie sich entscheiden, ein benutzerdefiniertes Tag zu erstellen, überlegen Sie immer, ob [es eine einfachere Lösung gibt |extending-latte#Möglichkeiten Latte zu erweitern] oder ob bereits ein geeignetes Tag in der [Standard-Suite |tags] existiert. Verwenden Sie benutzerdefinierte Tags nur dann, wenn einfachere Alternativen für Ihre Bedürfnisse nicht ausreichen. + + +Verständnis des Kompilierungsprozesses +====================================== + +Um benutzerdefinierte Tags effektiv zu erstellen, ist es hilfreich zu erklären, wie Latte Templates verarbeitet. Das Verständnis dieses Prozesses verdeutlicht, warum Tags genau so strukturiert sind und wie sie in den größeren Kontext passen. + +Die Kompilierung eines Templates in Latte umfasst vereinfacht diese Schlüsselschritte: + +1. **Lexikalische Analyse:** Der Lexer liest den Quellcode des Templates (Datei `.latte`) und zerlegt ihn in eine Sequenz kleiner, unterschiedlicher Teile, die als **Tokens** bezeichnet werden (z. B. `{`, `foreach`, `$variable`, `}`, HTML-Text usw.). +2. **Parsen:** Der Parser nimmt diesen Token-Strom und konstruiert daraus eine sinnvolle Baumstruktur, die die Logik und den Inhalt des Templates repräsentiert. Dieser Baum wird als **Abstract Syntax Tree (AST)** bezeichnet. +3. **Kompilierungsdurchläufe:** Vor der Generierung von PHP-Code führt Latte [Kompilierungsdurchläufe |compiler-passes] aus. Dies sind Funktionen, die den gesamten AST durchlaufen und ihn modifizieren oder Informationen sammeln können. Dieser Schritt ist entscheidend für Funktionen wie Sicherheit ([Sandbox |sandbox]) oder Optimierungen. +4. **Code-Generierung:** Schließlich durchläuft der Compiler den (potenziell modifizierten) AST und generiert den entsprechenden PHP-Klassencode. Dieser PHP-Code ist das, was das Template tatsächlich beim Ausführen rendert. +5. **Caching:** Der generierte PHP-Code wird auf der Festplatte gespeichert, was nachfolgende Renderings sehr schnell macht, da die Schritte 1-4 übersprungen werden. + +In Wirklichkeit ist die Kompilierung etwas komplexer. Latte **hat zwei** Lexer und Parser: einen für das HTML-Template und einen für den PHP-ähnlichen Code innerhalb der Tags. Und auch das Parsen erfolgt nicht erst nach der Tokenisierung, sondern Lexer und Parser laufen parallel in zwei "Threads" und koordinieren sich. Glauben Sie mir, die Programmierung war Raketenwissenschaft :-) + +Der gesamte Prozess, vom Laden des Template-Inhalts über das Parsen bis zur Generierung der resultierenden Datei, kann mit diesem Code sequenziert werden, mit dem Sie experimentieren und Zwischenergebnisse ausgeben können: + +```php +$latte = new Latte\Engine; +$source = $latte->getLoader()->getContent($file); +$ast = $latte->parse($source); +$latte->applyPasses($ast); +$code = $latte->generate($ast, $file); +``` + + +Anatomie eines Tags +=================== + +Die Erstellung eines voll funktionsfähigen benutzerdefinierten Tags in Latte umfasst mehrere miteinander verbundene Teile. Bevor wir uns der Implementierung widmen, lassen Sie uns die grundlegenden Konzepte und die Terminologie verstehen, wobei wir eine Analogie zu HTML und dem Document Object Model (DOM) verwenden. + + +Tags vs. Knoten (Analogie zu HTML) +---------------------------------- + +In HTML schreiben wir **Tags** wie `

                                      ` oder `

                                      ...
                                      `. Diese Tags sind die Syntax im Quellcode. Wenn der Browser dieses HTML parst, erstellt er eine Speicherrepräsentation namens **Document Object Model (DOM)**. Im DOM werden HTML-Tags durch **Knoten** repräsentiert (insbesondere `Element`-Knoten in der JavaScript-DOM-Terminologie). Mit diesen *Knoten* arbeiten wir programmatisch (z. B. gibt JavaScripts `document.getElementById(...)` einen Element-Knoten zurück). Ein Tag ist nur die Textrepräsentation in der Quelldatei; ein Knoten ist die Objektrepräsentation im logischen Baum. + +Latte funktioniert ähnlich: + +- In der `.latte`-Template-Datei schreiben Sie **Latte-Tags**, wie `{foreach ...}` und `{/foreach}`. Dies ist die Syntax, mit der Sie als Template-Autor arbeiten. +- Wenn Latte das Template **parst**, baut es einen **Abstract Syntax Tree (AST)** auf. Dieser Baum besteht aus **Knoten**. Jeder Latte-Tag, jedes HTML-Element, jedes Textstück oder jeder Ausdruck im Template wird zu einem oder mehreren Knoten in diesem Baum. +- Die Basisklasse für alle Knoten im AST ist `Latte\Compiler\Node`. Genauso wie das DOM verschiedene Knotentypen hat (Element, Text, Comment), hat der AST von Latte verschiedene Knotentypen. Sie werden auf `Latte\Compiler\Nodes\TextNode` für statischen Text, `Latte\Compiler\Nodes\Html\ElementNode` für HTML-Elemente, `Latte\Compiler\Nodes\Php\ExpressionNode` für Ausdrücke innerhalb von Tags und, entscheidend für benutzerdefinierte Tags, auf Knoten treffen, die von `Latte\Compiler\Nodes\StatementNode` erben. + + +Warum `StatementNode`? +---------------------- + +HTML-Elemente (`Html\ElementNode`) repräsentieren hauptsächlich Struktur und Inhalt. PHP-Ausdrücke (`Php\ExpressionNode`) repräsentieren Werte oder Berechnungen. Aber was ist mit Latte-Tags wie `{if}`, `{foreach}` oder unserem eigenen `{datetime}`? Diese Tags *führen Aktionen aus*, steuern den Programmfluss oder generieren Ausgaben basierend auf Logik. Sie sind funktionale Einheiten, die Latte zu einer leistungsstarken Template-*Engine* machen, nicht nur zu einer Markup-Sprache. + +In der Programmierung werden solche Einheiten, die Aktionen ausführen, oft als "Statements" (Anweisungen) bezeichnet. Daher erben Knoten, die diese funktionalen Latte-Tags repräsentieren, typischerweise von `Latte\Compiler\Nodes\StatementNode`. Dies unterscheidet sie von rein strukturellen Knoten (wie HTML-Elementen) oder Knoten, die Werte repräsentieren (wie Ausdrücke). + + +Die Schlüsselkomponenten +======================== + +Lassen Sie uns die Hauptkomponenten durchgehen, die zum Erstellen eines benutzerdefinierten Tags benötigt werden: + + +Tag-Parsing-Funktion +-------------------- + +- Diese PHP-Callable-Funktion parst die Syntax des Latte-Tags (`{...}`) im Quelltemplate. +- Sie erhält Informationen über das Tag (wie seinen Namen, seine Position und ob es sich um ein n:Attribut handelt) über das Objekt [api:Latte\Compiler\Tag]. +- Ihr primäres Werkzeug zum Parsen von Argumenten und Ausdrücken innerhalb der Tag-Begrenzer ist das Objekt [api:Latte\Compiler\TagParser], zugänglich über `$tag->parser` (dies ist ein anderer Parser als derjenige, der das gesamte Template parst). +- Bei paarweisen Tags verwendet sie `yield`, um Latte zu signalisieren, den inneren Inhalt zwischen dem öffnenden und schließenden Tag zu parsen. +- Das Endziel der Parsing-Funktion ist es, eine Instanz der **Knotenklasse** zu erstellen und zurückzugeben, die dem AST hinzugefügt wird. +- Es ist üblich (wenn auch nicht erforderlich), die Parsing-Funktion als statische Methode (oft `create` genannt) direkt in der entsprechenden Knotenklasse zu implementieren. Dies hält die Parsing-Logik und die Knotenrepräsentation sauber in einem Paket, ermöglicht bei Bedarf den Zugriff auf private/geschützte Elemente der Klasse und verbessert die Organisation. + + +Knotenklasse +------------ + +- Repräsentiert die *logische Funktion* Ihres Tags im **Abstract Syntax Tree (AST)**. +- Enthält die geparsten Informationen (wie Argumente oder Inhalt) als öffentliche Eigenschaften. Diese Eigenschaften enthalten oft andere `Node`-Instanzen (z. B. `ExpressionNode` für geparste Argumente, `AreaNode` für geparsten Inhalt). +- Die Methode `print(PrintContext $context): string` generiert den *PHP-Code* (eine Anweisung oder eine Reihe von Anweisungen), der die Aktion des Tags während des Template-Renderings ausführt. +- Die Methode `getIterator(): \Generator` macht die Kindknoten (Argumente, Inhalt) für den Durchlauf durch die **Kompilierungsdurchläufe** zugänglich. Sie muss Referenzen (`&`) bereitstellen, damit die Durchläufe Unterknoten potenziell modifizieren oder ersetzen können. +- Nachdem das gesamte Template in einen AST geparst wurde, führt Latte eine Reihe von [Kompilierungsdurchläufen |compiler-passes] aus. Diese Durchläufe durchlaufen den *gesamten* AST mithilfe der von jedem Knoten bereitgestellten `getIterator()`-Methode. Sie können Knoten inspizieren, Informationen sammeln und sogar den Baum *modifizieren* (z. B. durch Ändern öffentlicher Eigenschaften von Knoten oder vollständiges Ersetzen von Knoten). Dieses Design, das ein umfassendes `getIterator()` erfordert, ist entscheidend. Es ermöglicht leistungsstarken Funktionen wie der [Sandbox |sandbox], das Verhalten *jedes* Teils des Templates, einschließlich Ihrer benutzerdefinierten Tags, zu analysieren und potenziell zu ändern, wodurch Sicherheit und Konsistenz gewährleistet werden. + + +Registrierung über eine Erweiterung +----------------------------------- + +- Sie müssen Latte über Ihr neues Tag informieren und welche Parsing-Funktion dafür verwendet werden soll. Dies geschieht innerhalb einer [Latte-Erweiterung |extending-latte#Latte Extension]. +- Innerhalb Ihrer Erweiterungsklasse implementieren Sie die Methode `getTags(): array`. Diese Methode gibt ein assoziatives Array zurück, bei dem die Schlüssel die Tag-Namen sind (z. B. `'mytag'`, `'n:myattribute'`) und die Werte die PHP-Callable-Funktionen sind, die ihre jeweiligen Parsing-Funktionen repräsentieren (z. B. `MyNamespace\DatetimeNode::create(...)`). + +Zusammenfassung: Die **Tag-Parsing-Funktion** wandelt den *Template-Quellcode* Ihres Tags in einen **AST-Knoten** um. Die **Knotenklasse** kann dann *sich selbst* in ausführbaren *PHP-Code* für das kompilierte Template umwandeln und macht ihre Unterknoten über `getIterator()` für die **Kompilierungsdurchläufe** zugänglich. Die **Registrierung über eine Erweiterung** verbindet den Tag-Namen mit der Parsing-Funktion und macht Latte darauf aufmerksam. + +Lassen Sie uns nun untersuchen, wie diese Komponenten Schritt für Schritt implementiert werden. + + +Erstellen eines einfachen Tags +============================== + +Lassen Sie uns mit der Erstellung Ihres ersten benutzerdefinierten Latte-Tags beginnen. Wir beginnen mit einem sehr einfachen Beispiel: ein Tag namens `{datetime}`, das das aktuelle Datum und die Uhrzeit ausgibt. **Zuerst wird dieses Tag keine Argumente akzeptieren**, aber wir werden es später im Abschnitt [#"Tag-Argumente parsen"] verbessern. Es hat auch keinen inneren Inhalt. + +Dieses Beispiel führt Sie durch die grundlegenden Schritte: Definieren der Knotenklasse, Implementieren ihrer `print()`- und `getIterator()`-Methoden, Erstellen der Parsing-Funktion und schließlich Registrieren des Tags. + +**Ziel:** Implementieren von `{datetime}` zur Ausgabe des aktuellen Datums und der Uhrzeit mithilfe der PHP-Funktion `date()`. + + +Erstellen der Knotenklasse +-------------------------- + +Zuerst benötigen wir eine Klasse, die unser Tag im Abstract Syntax Tree (AST) repräsentiert. Wie oben besprochen, erben wir von `Latte\Compiler\Nodes\StatementNode`. + +Erstellen Sie eine Datei (z. B. `DatetimeNode.php`) und definieren Sie die Klasse: + +```php +node = new self; + return $node; + } + + /** + * Generiert PHP-Code, der beim Rendern des Templates ausgeführt wird. + */ + public function print(PrintContext $context): string + { + return $context->format( + 'echo date(\'Y-m-d H:i:s\') %line;', + $this->position, + ); + } + + /** + * Bietet Zugriff auf Kindknoten für Latte-Kompilierungsdurchläufe. + */ + public function &getIterator(): \Generator + { + false && yield; + } +} +``` + +Wenn Latte auf `{datetime}` im Template trifft, ruft es die Parsing-Funktion `create()` auf. Ihre Aufgabe ist es, eine Instanz von `DatetimeNode` zurückzugeben. + +Die `print()`-Methode generiert PHP-Code, der beim Rendern des Templates ausgeführt wird. Wir rufen die Methode `$context->format()` auf, die die resultierende PHP-Code-Zeichenkette für das kompilierte Template zusammenstellt. Das erste Argument, `'echo date('Y-m-d H:i:s') %line;'`, ist eine Maske, in die die folgenden Parameter eingesetzt werden. Der Platzhalter `%line` weist die `format()`-Methode an, das zweite Argument, das `$this->position` ist, zu verwenden und einen Kommentar wie `/* line 15 */` einzufügen, der den generierten PHP-Code zurück zur ursprünglichen Template-Zeile verknüpft, was für das Debugging entscheidend ist. + +Die Eigenschaft `$this->position` wird von der Basisklasse `Node` geerbt und automatisch vom Latte-Parser gesetzt. Sie enthält ein Objekt [api:Latte\Compiler\Position], das angibt, wo das Tag in der `.latte`-Quelldatei gefunden wurde. + +Die `getIterator()`-Methode ist für Kompilierungsdurchläufe unerlässlich. Sie muss alle Kindknoten bereitstellen, aber unser einfacher `DatetimeNode` hat derzeit keine Argumente oder Inhalte, also keine Kindknoten. Dennoch muss die Methode existieren und ein Generator sein, d. h. das Schlüsselwort `yield` muss irgendwie im Methodenkörper vorhanden sein. + + +Registrierung über eine Erweiterung +----------------------------------- + +Informieren wir Latte schließlich über das neue Tag. Erstellen Sie eine [Erweiterungsklasse |extending-latte#Latte Extension] (z. B. `MyLatteExtension.php`) und registrieren Sie das Tag in ihrer `getTags()`-Methode. + +```php + Map: 'tag-name' => parsing-funktion + */ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + // Später hier weitere Tags registrieren + ]; + } +} +``` + +Registrieren Sie dann diese Erweiterung bei der Latte Engine: + +```php +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +Erstellen Sie ein Template: + +```latte +

                                      Seite generiert am: {datetime}

                                      +``` + +Erwartete Ausgabe: `

                                      Seite generiert am: 2023-10-27 11:00:00

                                      ` + + +Zusammenfassung dieser Phase +---------------------------- + +Wir haben erfolgreich ein grundlegendes benutzerdefiniertes Tag `{datetime}` erstellt. Wir haben seine Repräsentation im AST (`DatetimeNode`) definiert, sein Parsing (`create()`) verarbeitet, spezifiziert, wie es PHP-Code generieren soll (`print()`), sichergestellt, dass seine Kinder für den Durchlauf zugänglich sind (`getIterator()`), und es bei Latte registriert. + +Im nächsten Abschnitt werden wir dieses Tag verbessern, um Argumente zu akzeptieren, und zeigen, wie man Ausdrücke parst und Kindknoten verwaltet. + + +Tag-Argumente parsen +==================== + +Unser einfaches Tag `{datetime}` funktioniert, ist aber nicht sehr flexibel. Verbessern wir es, damit es ein optionales Argument akzeptiert: eine Formatierungszeichenkette für die Funktion `date()`. Die erforderliche Syntax ist `{datetime $format}`. + +**Ziel:** Ändern von `{datetime}` so, dass es einen optionalen PHP-Ausdruck als Argument akzeptiert, der als Formatierungszeichenkette für `date()` verwendet wird. + + +Einführung von `TagParser` +-------------------------- + +Bevor wir den Code ändern, ist es wichtig, das Werkzeug zu verstehen, das wir verwenden werden: [api:Latte\Compiler\TagParser]. Wenn der Haupt-Parser von Latte (`TemplateParser`) auf ein Latte-Tag wie `{datetime ...}` oder ein n:Attribut trifft, delegiert er das Parsen des Inhalts *innerhalb* des Tags (der Teil zwischen `{` und `}` oder der Attributwert) an einen spezialisierten `TagParser`. + +Dieser `TagParser` arbeitet ausschließlich mit den **Tag-Argumenten**. Seine Aufgabe ist es, die Tokens zu verarbeiten, die diese Argumente repräsentieren. Entscheidend ist, dass er **den gesamten ihm zur Verfügung gestellten Inhalt verarbeiten muss**. Wenn Ihre Parsing-Funktion endet, aber der `TagParser` das Ende der Argumente nicht erreicht hat (überprüft durch `$tag->parser->isEnd()`), wirft Latte eine Ausnahme, da dies darauf hinweist, dass unerwartete Tokens innerhalb des Tags übrig geblieben sind. Umgekehrt, wenn das Tag Argumente *erfordert*, sollten Sie am Anfang Ihrer Parsing-Funktion `$tag->expectArguments()` aufrufen. Diese Methode prüft, ob Argumente vorhanden sind, und wirft eine hilfreiche Ausnahme, wenn das Tag ohne Argumente verwendet wurde. + +Der `TagParser` bietet nützliche Methoden zum Parsen verschiedener Arten von Argumenten: + +- `parseExpression(): ExpressionNode`: Parst einen PHP-ähnlichen Ausdruck (Variablen, Literale, Operatoren, Funktions-/Methodenaufrufe usw.). Verarbeitet Latte-Syntaxzucker, wie z. B. die Behandlung einfacher alphanumerischer Zeichenketten als Zeichenketten in Anführungszeichen (z. B. wird `foo` geparst, als wäre es `'foo'`). +- `parseUnquotedStringOrExpression(): ExpressionNode`: Parst entweder einen Standardausdruck oder eine *nicht in Anführungszeichen gesetzte Zeichenkette*. Nicht in Anführungszeichen gesetzte Zeichenketten sind Sequenzen, die von Latte ohne Anführungszeichen erlaubt werden, oft für Dinge wie Dateipfade verwendet (z. B. `{include ../file.latte}`). Wenn es eine nicht in Anführungszeichen gesetzte Zeichenkette parst, gibt es einen `StringNode` zurück. +- `parseArguments(): ArrayNode`: Parst durch Kommas getrennte Argumente, potenziell mit Schlüsseln, wie `10, name: 'John', true`. +- `parseModifier(): ModifierNode`: Parst Filter wie `|upper|truncate:10`. +- `parseType(): ?SuperiorTypeNode`: Parst PHP-Typ-Hints wie `int`, `?string`, `array|Foo`. + +Für komplexere oder niedrigstufigere Parsing-Anforderungen können Sie direkt mit dem [Token-Stream |api:Latte\Compiler\TokenStream] über `$tag->parser->stream` interagieren. Dieses Objekt bietet Methoden zum Überprüfen und Verarbeiten einzelner Tokens: + +- `$tag->parser->stream->is(...): bool`: Überprüft, ob das *aktuelle* Token einem der angegebenen Typen (z. B. `Token::Php_Variable`) oder Literalwerten (z. B. `'as'`) entspricht, ohne es zu konsumieren. Nützlich zum Vorausschauen. +- `$tag->parser->stream->consume(...): Token`: Konsumiert das *aktuelle* Token und verschiebt die Stream-Position vorwärts. Wenn erwartete Token-Typen/-Werte als Argumente angegeben werden und das aktuelle Token nicht übereinstimmt, wird eine `CompileException` ausgelöst. Verwenden Sie dies, wenn Sie ein bestimmtes Token *erwarten*. +- `$tag->parser->stream->tryConsume(...): ?Token`: Versucht, das *aktuelle* Token *nur dann* zu konsumieren, wenn es einem der angegebenen Typen/Werte entspricht. Wenn es übereinstimmt, konsumiert es das Token und gibt es zurück. Wenn es nicht übereinstimmt, bleibt die Stream-Position unverändert und es wird `null` zurückgegeben. Verwenden Sie dies für optionale Tokens oder wenn Sie zwischen verschiedenen Syntaxpfaden wählen. + + +Aktualisieren der Parsing-Funktion `create()` +--------------------------------------------- + +Mit diesem Verständnis ändern wir die `create()`-Methode in `DatetimeNode` so, dass sie das optionale Formatierungsargument mit `$tag->parser` parst. + +```php +node = new self; + + // Prüfen, ob Tokens vorhanden sind + if (!$tag->parser->isEnd()) { + // Parsen Sie das Argument als PHP-ähnlichen Ausdruck mit TagParser. + $node->format = $tag->parser->parseExpression(); + } + + return $node; + } + + // ... print()- und getIterator()-Methoden werden weiter unten aktualisiert ... +} +``` + +Wir haben die öffentliche Eigenschaft `$format` hinzugefügt. In `create()` verwenden wir nun `$tag->parser->isEnd()`, um zu prüfen, ob Argumente *vorhanden sind*. Wenn ja, verarbeitet `$tag->parser->parseExpression()` die Tokens für den Ausdruck. Da der `TagParser` alle Eingabe-Tokens verarbeiten muss, wirft Latte automatisch einen Fehler, wenn der Benutzer etwas Unerwartetes nach dem Format-Ausdruck schreibt (z. B. `{datetime 'Y-m-d', unexpected}`). + + +Aktualisieren der `print()`-Methode +----------------------------------- + +Ändern wir nun die `print()`-Methode so, dass sie den geparsten Format-Ausdruck verwendet, der in `$this->format` gespeichert ist. Wenn kein Format angegeben wurde (`$this->format` ist `null`), sollten wir eine Standard-Formatierungszeichenkette verwenden, z. B. `'Y-m-d H:i:s'`. + +```php + public function print(PrintContext $context): string + { + $formatNode = $this->format ?? new StringNode('Y-m-d H:i:s'); + + // %node druckt die PHP-Code-Repräsentation von $formatNode. + return $context->format( + 'echo date(%node) %line;', + $formatNode, + $this->position + ); + } +``` + +Wir speichern den AST-Knoten, der die Formatierungszeichenkette für die PHP-Funktion `date()` repräsentiert, in der Variablen `$formatNode`. Wir verwenden hier den Null-Coalescing-Operator (`??`). Wenn der Benutzer ein Argument im Template angegeben hat (z. B. `{datetime 'd.m.Y'}`), enthält die Eigenschaft `$this->format` den entsprechenden Knoten (in diesem Fall einen `StringNode` mit dem Wert `'d.m.Y'`), und dieser Knoten wird verwendet. Wenn der Benutzer kein Argument angegeben hat (nur `{datetime}` geschrieben hat), ist die Eigenschaft `$this->format` `null`, und stattdessen erstellen wir einen neuen `StringNode` mit dem Standardformat `'Y-m-d H:i:s'`. Dies stellt sicher, dass `$formatNode` immer einen gültigen AST-Knoten für das Format enthält. + +In der Maske `'echo date(%node) %line;'` wird der neue Platzhalter `%node` verwendet, der die `format()`-Methode anweist, das erste folgende Argument (das unser `$formatNode` ist) zu nehmen, dessen `print()`-Methode aufzurufen (die seine PHP-Code-Repräsentation zurückgibt) und das Ergebnis an der Position des Platzhalters einzufügen. + + +Implementieren von `getIterator()` für Unterknoten +-------------------------------------------------- + +Unser `DatetimeNode` hat nun einen Kindknoten: den Ausdruck `$format`. Wir **müssen** diesen Kindknoten den Kompilierungsdurchläufen zugänglich machen, indem wir ihn in der `getIterator()`-Methode bereitstellen. Denken Sie daran, eine *Referenz* (`&`) bereitzustellen, damit die Durchläufe den Knoten potenziell ersetzen können. + +```php + public function &getIterator(): \Generator + { + if ($this->format) { + yield $this->format; + } + } +``` + +Warum ist das entscheidend? Stellen Sie sich einen Sandbox-Durchlauf vor, der prüfen muss, ob das Argument `$format` keinen verbotenen Funktionsaufruf enthält (z. B. `{datetime dangerousFunction()}`). Wenn `getIterator()` `$this->format` nicht bereitstellt, würde der Sandbox-Durchlauf den Aufruf von `dangerousFunction()` innerhalb des Arguments unseres Tags niemals sehen, was eine potenzielle Sicherheitslücke schaffen würde. Indem wir es bereitstellen, ermöglichen wir der Sandbox (und anderen Durchläufen), den Ausdrucksknoten `$format` zu prüfen und potenziell zu modifizieren. + + +Verwenden des verbesserten Tags +------------------------------- + +Das Tag verarbeitet nun das optionale Argument korrekt: + +```latte +Standardformat: {datetime} +Benutzerdefiniertes Format: {datetime 'd.m.Y'} +Variable verwenden: {datetime $userDateFormatPreference} + +{* Dies würde nach dem Parsen von 'd.m.Y' einen Fehler verursachen, da ", foo" unerwartet ist *} +{* {datetime 'd.m.Y', foo} *} +``` + +Als Nächstes untersuchen wir die Erstellung paarweiser Tags, die den Inhalt zwischen ihnen verarbeiten. + + +Verarbeiten paarweiser Tags +=========================== + +Bisher war unser Tag `{datetime}` *selbstschließend* (konzeptionell). Es hat keinen Inhalt zwischen dem öffnenden und schließenden Tag. Viele nützliche Tags arbeiten jedoch mit einem Block von Template-Inhalt. Diese werden als **paarweise Tags** bezeichnet. Beispiele sind `{if}...{/if}`, `{block}...{/block}` oder ein benutzerdefiniertes Tag, das wir jetzt erstellen werden: `{debug}...{/debug}`. + +Dieses Tag ermöglicht es uns, Debugging-Informationen in unsere Templates einzufügen, die nur während der Entwicklung sichtbar sein sollen. + +**Ziel:** Erstellen eines paarweisen Tags `{debug}`, dessen Inhalt nur gerendert wird, wenn ein spezifisches "Entwicklungsmodus"-Flag aktiv ist. + + +Einführung von Providern +------------------------ + +Manchmal benötigen Ihre Tags Zugriff auf Daten oder Dienste, die nicht direkt als Template-Parameter übergeben werden. Zum Beispiel die Bestimmung, ob sich die Anwendung im Entwicklungsmodus befindet, der Zugriff auf ein Benutzerobjekt oder das Abrufen von Konfigurationswerten. Latte bietet einen Mechanismus namens **Provider** (Anbieter) für diesen Zweck. + +Provider werden in Ihrer [Erweiterung |extending-latte#Latte Extension] mithilfe der `getProviders()`-Methode registriert. Diese Methode gibt ein assoziatives Array zurück, bei dem die Schlüssel die Namen sind, unter denen die Provider im Laufzeitcode des Templates zugänglich sind, und die Werte die tatsächlichen Daten oder Objekte sind. + +Innerhalb des von der `print()`-Methode Ihres Tags generierten PHP-Codes können Sie über die spezielle Eigenschaft `$this->global` auf diese Provider zugreifen. Da diese Eigenschaft von allen Erweiterungen gemeinsam genutzt wird, ist es eine gute Praxis, **die Namen Ihrer Provider mit einem Präfix zu versehen**, um potenzielle Namenskollisionen mit Kern-Providern von Latte oder Providern aus anderen Drittanbieter-Erweiterungen zu vermeiden. Eine gängige Konvention ist die Verwendung eines kurzen, eindeutigen Präfixes, das sich auf Ihren Hersteller oder den Namen der Erweiterung bezieht. Für unser Beispiel verwenden wir das Präfix `app`, und das Entwicklungsmodus-Flag ist als `$this->global->appDevMode` verfügbar. + + +Das `yield`-Schlüsselwort zum Parsen von Inhalten +------------------------------------------------- + +Wie sagen wir dem Latte-Parser, dass er den Inhalt *zwischen* `{debug}` und `{/debug}` verarbeiten soll? Hier kommt das Schlüsselwort `yield` ins Spiel. + +Wenn `yield` in der `create()`-Funktion verwendet wird, wird die Funktion zu einem [PHP-Generator |https://www.php.net/manual/en/language.generators.overview.php]. Ihre Ausführung wird angehalten, und die Kontrolle wird an den Haupt-`TemplateParser` zurückgegeben. Der `TemplateParser` fährt dann mit dem Parsen des Template-Inhalts fort, *bis* er auf das entsprechende schließende Tag trifft (`{/debug}` in unserem Fall). + +Sobald das schließende Tag gefunden wird, setzt der `TemplateParser` die Ausführung unserer `create()`-Funktion direkt nach der `yield`-Anweisung fort. Der von der `yield`-Anweisung *zurückgegebene* Wert ist ein Array mit zwei Elementen: + +1. Ein `AreaNode`, der den geparsten Inhalt zwischen dem öffnenden und schließenden Tag repräsentiert. +2. Ein `Tag`-Objekt, das das schließende Tag repräsentiert (z. B. `{/debug}`). + +Erstellen wir die Klasse `DebugNode` und ihre `create`-Methode, die `yield` verwendet. + +```php +node = new self; + + // Parsen anhalten, inneren Inhalt und End-Tag erhalten, wenn {/debug} gefunden wird + [$node->content, $endTag] = yield; + + return $node; + } + + // ... print() und getIterator() werden weiter unten implementiert ... +} +``` + +Hinweis: `$endTag` ist `null`, wenn das Tag als n:Attribut verwendet wird, d. h. `
                                      ...
                                      `. + + +Implementieren von `print()` für bedingtes Rendering +---------------------------------------------------- + +Die `print()`-Methode muss nun PHP-Code generieren, der zur Laufzeit den `appDevMode`-Provider überprüft und den Code für den inneren Inhalt nur ausführt, wenn das Flag true ist. + +```php + public function print(PrintContext $context): string + { + // Generiert eine PHP 'if'-Anweisung, die zur Laufzeit den Provider prüft + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + // Wenn im Entwicklungsmodus, den inneren Inhalt ausgeben + %node + } + + XX, + $this->position, // Für den %line-Kommentar + $this->content, // Der Knoten, der den AST des inneren Inhalts enthält + ); + } +``` + +Das ist einfach. Wir verwenden `PrintContext::format()`, um eine standardmäßige PHP `if`-Anweisung zu erstellen. Innerhalb des `if` platzieren wir den Platzhalter `%node` für `$this->content`. Latte ruft rekursiv `$this->content->print($context)` auf, um den PHP-Code für den inneren Teil des Tags zu generieren, aber nur, wenn `$this->global->appDevMode` zur Laufzeit als true ausgewertet wird. + + +Implementieren von `getIterator()` für den Inhalt +------------------------------------------------- + +Genau wie beim Argumentknoten im vorherigen Beispiel hat unser `DebugNode` nun einen Kindknoten: `AreaNode $content`. Wir müssen ihn zugänglich machen, indem wir ihn in `getIterator()` bereitstellen: + +```php + public function &getIterator(): \Generator + { + // Gibt eine Referenz auf den Inhaltsknoten zurück + yield $this->content; + } +``` + +Dies ermöglicht es Kompilierungsdurchläufen, in den Inhalt unseres `{debug}`-Tags hinabzusteigen, was wichtig ist, auch wenn der Inhalt bedingt gerendert wird. Zum Beispiel muss die Sandbox den Inhalt analysieren, unabhängig davon, ob `appDevMode` true oder false ist. + + +Registrierung und Verwendung +---------------------------- + +Registrieren Sie das Tag und den Provider in Ihrer Erweiterung: + +```php +class MyLatteExtension extends Extension +{ + // Angenommen, $isDevelopmentMode wird irgendwo bestimmt (z. B. aus der Konfiguration) + public function __construct( + private bool $isDevelopmentMode, + ) { + } + + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), // Registrierung des neuen Tags + ]; + } + + public function getProviders(): array + { + return [ + 'appDevMode' => $this->isDevelopmentMode, // Registrierung des Providers + ]; + } +} + +// Bei der Registrierung der Erweiterung: +$isDev = true; // Bestimmen Sie dies basierend auf Ihrer Anwendungs-Umgebung +$latte->addExtension(new App\Latte\MyLatteExtension($isDev)); +``` + +Und seine Verwendung im Template: + +```latte +

                                      Normaler Inhalt, immer sichtbar.

                                      + +{debug} +
                                      + ID des aktuellen Benutzers: {$user->id} + Anfragezeit: {=time()} +
                                      +{/debug} + +

                                      Weiterer normaler Inhalt.

                                      +``` + + +Integration von n:Attributen +---------------------------- + +Latte bietet eine bequeme Kurzschreibweise für viele paarweise Tags: [n:Attribute |syntax#n:Attribute]. Wenn Sie ein paarweises Tag wie `{tag}...{/tag}` haben und dessen Effekt direkt auf ein einzelnes HTML-Element anwenden möchten, können Sie es oft kürzer als Attribut `n:tag` an diesem Element schreiben. + +Für die meisten standardmäßigen paarweisen Tags, die Sie definieren (wie unser `{debug}`), aktiviert Latte automatisch die entsprechende `n:`-Attribut-Version. Sie müssen während der Registrierung nichts weiter tun: + +```latte +{* Standardmäßige Verwendung des paarweisen Tags *} +{debug}
                                      Debugging-Informationen
                                      {/debug} + +{* Äquivalente Verwendung mit n:Attribut *} +
                                      Debugging-Informationen
                                      +``` + +Beide Versionen rendern das `
                                      ` nur, wenn `$this->global->appDevMode` true ist. Die Präfixe `inner-` und `tag-` funktionieren ebenfalls wie erwartet. + +Manchmal muss sich die Logik Ihres Tags möglicherweise geringfügig anders verhalten, je nachdem, ob es als standardmäßiges paarweises Tag oder als n:Attribut verwendet wird, oder ob ein Präfix wie `n:inner-tag` oder `n:tag-tag` verwendet wird. Das `Latte\Compiler\Tag`-Objekt, das an Ihre Parsing-Funktion `create()` übergeben wird, liefert diese Informationen: + +- `$tag->isNAttribute(): bool`: Gibt `true` zurück, wenn das Tag als n:Attribut geparst wird. +- `$tag->prefix: ?string`: Gibt das mit dem n:Attribut verwendete Präfix zurück, das `null` (kein n:Attribut), `Tag::PrefixNone`, `Tag::PrefixInner` oder `Tag::PrefixTag` sein kann. + +Nachdem wir nun einfache Tags, Argumentparsing, paarweise Tags, Provider und n:Attribute verstanden haben, wenden wir uns einem komplexeren Szenario zu, das in anderen Tags verschachtelte Tags beinhaltet, wobei wir unser `{debug}`-Tag als Ausgangspunkt verwenden. + + +Zwischen-Tags +============= + +Einige paarweise Tags erlauben oder erfordern sogar, dass andere Tags *innerhalb* von ihnen vor dem endgültigen schließenden Tag erscheinen. Diese werden als **Zwischen-Tags** bezeichnet. Klassische Beispiele sind `{if}...{elseif}...{else}...{/if}` oder `{switch}...{case}...{default}...{/switch}`. + +Erweitern wir unser `{debug}`-Tag, um eine optionale `{else}`-Klausel zu unterstützen, die gerendert wird, wenn sich die Anwendung *nicht* im Entwicklungsmodus befindet. + +**Ziel:** Ändern von `{debug}` so, dass es ein optionales Zwischen-Tag `{else}` unterstützt. Die endgültige Syntax sollte `{debug} ... {else} ... {/debug}` sein. + + +Parsen von Zwischen-Tags mit `yield` +------------------------------------ + +Wir wissen bereits, dass `yield` die Parsing-Funktion `create()` anhält und den geparsten Inhalt zusammen mit dem End-Tag zurückgibt. `yield` bietet jedoch mehr Kontrolle: Sie können ihm ein Array von *Namen von Zwischen-Tags* übergeben. Wenn der Parser auf eines dieser angegebenen Tags **auf derselben Verschachtelungsebene** trifft (d. h. als direkte Kinder des übergeordneten Tags, nicht innerhalb anderer Blöcke oder Tags darin), stoppt er ebenfalls das Parsen. + +Wenn das Parsen aufgrund eines Zwischen-Tags stoppt, stoppt es das Parsen des Inhalts, setzt den `create()`-Generator fort und übergibt den teilweise geparsten Inhalt und das **Zwischen-Tag** selbst (anstelle des endgültigen End-Tags). Unsere `create()`-Funktion kann dann dieses Zwischen-Tag verarbeiten (z. B. seine Argumente parsen, falls es welche hatte) und `yield` erneut verwenden, um den *nächsten* Teil des Inhalts bis zum *endgültigen* End-Tag oder einem anderen erwarteten Zwischen-Tag zu parsen. + +Ändern wir `DebugNode::create()` so, dass es `{else}` erwartet: + +```php +node = new self; + + // yield und entweder {/debug} oder {else} erwarten + [$node->thenContent, $nextTag] = yield ['else']; + + // Prüfen, ob das Tag, bei dem wir angehalten haben, {else} war + if ($nextTag?->name === 'else') { + // Erneut yielden, um den Inhalt zwischen {else} und {/debug} zu parsen + [$node->elseContent, $endTag] = yield; + } + + return $node; + } + + // ... print() und getIterator() werden weiter unten aktualisiert ... +} +``` + +Jetzt weist `yield ['else']` Latte an, das Parsen nicht nur für `{/debug}`, sondern auch für `{else}` zu stoppen. Wenn `{else}` gefunden wird, enthält `$nextTag` das `Tag`-Objekt für `{else}`. Dann verwenden wir `yield` erneut ohne Argumente, was bedeutet, dass wir nun nur noch das endgültige Tag `{/debug}` erwarten, und speichern das Ergebnis in `$node->elseContent`. Wenn `{else}` nicht gefunden wurde, wäre `$nextTag` das `Tag` für `{/debug}` (oder `null`, wenn es als n:Attribut verwendet wird) und `$node->elseContent` würde `null` bleiben. + + +Implementieren von `print()` mit `{else}` +----------------------------------------- + +Die `print()`-Methode muss die neue Struktur widerspiegeln. Sie sollte eine PHP `if/else`-Anweisung generieren, die auf dem `appDevMode`-Provider basiert. + +```php + public function print(PrintContext $context): string + { + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + %node // Code für den 'then'-Zweig (Inhalt von {debug}) + } else { + %node // Code für den 'else'-Zweig (Inhalt von {else}) + } + + XX, + $this->position, // Zeilennummer für die 'if'-Bedingung + $this->thenContent, // Erster %node-Platzhalter + $this->elseContent ?? new NopNode, // Zweiter %node-Platzhalter + ); + } +``` + +Dies ist eine standardmäßige PHP `if/else`-Struktur. Wir verwenden `%node` zweimal; `format()` ersetzt die bereitgestellten Knoten nacheinander. Wir verwenden `?? new NopNode`, um Fehler zu vermeiden, wenn `$this->elseContent` `null` ist – `NopNode` druckt einfach nichts. + + +Implementieren von `getIterator()` für beide Inhalte +---------------------------------------------------- + +Wir haben nun potenziell zwei Kind-Inhaltsknoten (`$thenContent` und `$elseContent`). Wir müssen beide bereitstellen, wenn sie existieren: + +```php + public function &getIterator(): \Generator + { + yield $this->thenContent; + if ($this->elseContent) { + yield $this->elseContent; + } + } +``` + + +Verwenden des verbesserten Tags +------------------------------- + +Das Tag kann nun mit der optionalen `{else}`-Klausel verwendet werden: + +```latte +{debug} +

                                      Anzeigen von Debugging-Informationen, da devMode EIN ist.

                                      +{else} +

                                      Debugging-Informationen sind verborgen, da devMode AUS ist.

                                      +{/debug} +``` + + +Verarbeiten von Zustand und Verschachtelung +=========================================== + +Unsere vorherigen Beispiele (`{datetime}`, `{debug}`) waren innerhalb ihrer `print()`-Methoden relativ zustandslos. Sie haben entweder direkt Inhalte ausgegeben oder eine einfache bedingte Prüfung basierend auf einem globalen Provider durchgeführt. Viele Tags müssen jedoch während des Renderings irgendeine Form von **Zustand** verwalten oder beinhalten die Auswertung von Benutzerausdrücken, die aus Leistungs- oder Korrektheitsgründen nur einmal ausgeführt werden sollten. Weiterhin müssen wir berücksichtigen, was passiert, wenn unsere benutzerdefinierten Tags **verschachtelt** werden. + +Illustrieren wir diese Konzepte, indem wir ein Tag `{repeat $count}...{/repeat}` erstellen. Dieses Tag wiederholt seinen inneren Inhalt `$count`-Mal. + +**Ziel:** Implementieren von `{repeat $count}`, das seinen Inhalt die angegebene Anzahl von Malen wiederholt. + + +Die Notwendigkeit temporärer & eindeutiger Variablen +---------------------------------------------------- + +Stellen Sie sich vor, ein Benutzer schreibt: + +```latte +{repeat rand(1, 5)} Inhalt {/repeat} +``` + +Wenn wir naiv eine PHP `for`-Schleife auf diese Weise in unserer `print()`-Methode generieren würden: + +```php +// Vereinfachter, FALSCHER generierter Code +for ($i = 0; $i < rand(1, 5); $i++) { + // Inhalt ausgeben +} +``` +Das wäre falsch! Der Ausdruck `rand(1, 5)` würde **bei jeder Iteration der Schleife neu ausgewertet**, was zu einer unvorhersehbaren Anzahl von Wiederholungen führen würde. Wir müssen den Ausdruck `$count` *einmal* vor Beginn der Schleife auswerten und sein Ergebnis speichern. + +Wir generieren PHP-Code, der zuerst den Zählausdruck auswertet und ihn in einer **temporären Laufzeitvariablen** speichert. Um Kollisionen mit vom Template-Benutzer definierten Variablen *und* internen Latte-Variablen (wie `$ʟ_...`) zu vermeiden, verwenden wir die Konvention, unsere temporären Variablen mit **`$__` (doppelter Unterstrich)** zu präfixieren. + +Der generierte Code würde dann so aussehen: + +```php +$__count = rand(1, 5); +for ($__i = 0; $__i < $__count; $__i++) { + // Inhalt ausgeben +} +``` + +Betrachten wir nun die Verschachtelung: + +```latte +{repeat $countA} {* Äußere Schleife *} + {repeat $countB} {* Innere Schleife *} + ... + {/repeat} +{/repeat} +``` + +Wenn sowohl das äußere als auch das innere `{repeat}`-Tag Code generieren würden, der *dieselben* Namen für temporäre Variablen verwendet (z. B. `$__count` und `$__i`), würde die innere Schleife die Variablen der äußeren Schleife überschreiben, was die Logik stören würde. + +Wir müssen sicherstellen, dass die für jede Instanz des `{repeat}`-Tags generierten temporären Variablen **eindeutig** sind. Dies erreichen wir mit `PrintContext::generateId()`. Diese Methode gibt während der Kompilierungsphase eine eindeutige ganze Zahl zurück. Wir können diese ID an die Namen unserer temporären Variablen anhängen. + +Statt `$__count` generieren wir also `$__count_1` für das erste repeat-Tag, `$__count_2` für das zweite usw. Ähnlich verwenden wir für den Schleifenzähler `$__i_1`, `$__i_2` usw. + + +Implementieren von `RepeatNode` +------------------------------- + +Erstellen wir die Knotenklasse. + +```php +expectArguments(); // stellt sicher, dass $count angegeben wird + $node = $tag->node = new self; + // Parsen des Zählausdrucks + $node->count = $tag->parser->parseExpression(); + // Erhalten des inneren Inhalts + [$node->content] = yield; + return $node; + } + + /** + * Generiert eine PHP 'for'-Schleife mit eindeutigen Variablennamen. + */ + public function print(PrintContext $context): string + { + // Generieren eindeutiger Variablennamen + $id = $context->generateId(); + $countVar = '$__count_' . $id; // z.B. $__count_1, $__count_2, etc. + $iteratorVar = '$__i_' . $id; // z.B. $__i_1, $__i_2, etc. + + return $context->format( + <<<'XX' + // Auswertung des Zählausdrucks *einmal* und Speichern + %raw = (int) (%node); + // Schleife mit gespeichertem Zähler und eindeutiger Iterationsvariable + for (%raw = 0; %2.raw < %0.raw; %2.raw++) %line { + %node // Rendern des inneren Inhalts + } + + XX, + $countVar, // %0 - Variable zum Speichern des Zählers + $this->count, // %1 - Ausdrucksknoten für den Zähler + $iteratorVar, // %2 - Name der Iterationsvariable der Schleife + $this->position, // %3 - Kommentar mit Zeilennummer für die Schleife selbst + $this->content // %4 - Knoten des inneren Inhalts + ); + } + + /** + * Stellt die Kindknoten (Zählausdruck und Inhalt) bereit. + */ + public function &getIterator(): \Generator + { + yield $this->count; + yield $this->content; + } +} +``` + +Die `create()`-Methode parst den erforderlichen `$count`-Ausdruck mit `parseExpression()`. Zuerst wird `$tag->expectArguments()` aufgerufen. Dies stellt sicher, dass der Benutzer *etwas* nach `{repeat}` angegeben hat. Während `$tag->parser->parseExpression()` fehlschlagen würde, wenn nichts angegeben wird, könnte die Fehlermeldung von einer unerwarteten Syntax handeln. Die Verwendung von `expectArguments()` liefert einen viel klareren Fehler, der spezifisch besagt, dass Argumente für das `{repeat}`-Tag fehlen. + +Die `print()`-Methode generiert PHP-Code, der für die Ausführung der Wiederholungslogik zur Laufzeit verantwortlich ist. Sie beginnt mit der Generierung eindeutiger Namen für die temporären PHP-Variablen, die sie benötigt. + +Die `$context->format()`-Methode wird mit dem neuen Platzhalter `%raw` aufgerufen, der die *rohe Zeichenkette* einfügt, die als entsprechendes Argument bereitgestellt wird. Hier fügt sie den eindeutigen Variablennamen ein, der in `$countVar` gespeichert ist (z. B. `$__count_1`). Und was ist mit `%0.raw` und `%2.raw`? Dies demonstriert **Positionsplatzhalter**. Anstatt nur `%raw`, das das *nächste* verfügbare rohe Argument nimmt, nimmt `%2.raw` explizit das Argument am Index 2 (das `$iteratorVar` ist) und fügt seinen rohen Zeichenkettenwert ein. Dies ermöglicht es uns, die Zeichenkette `$iteratorVar` wiederzuverwenden, ohne sie mehrmals in der Argumentliste für `format()` zu übergeben. + +Dieser sorgfältig konstruierte `format()`-Aufruf generiert eine effiziente und sichere PHP-Schleife, die den Zählausdruck korrekt verarbeitet und Namenskollisionen bei Variablen vermeidet, selbst wenn `{repeat}`-Tags verschachtelt sind. + + +Registrierung und Verwendung +---------------------------- + +Registrieren Sie das Tag in Ihrer Erweiterung: + +```php +use App\Latte\RepeatNode; + +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), // Registrierung des repeat-Tags + ]; + } +} +``` + +Verwenden Sie es im Template, einschließlich Verschachtelung: + +```latte +{var $rows = rand(5, 7)} +{var $cols = rand(3, 5)} + +{repeat $rows} + + {repeat $cols} + Innere Schleife + {/repeat} + +{/repeat} +``` + +Dieses Beispiel demonstriert, wie Zustand (Schleifenzähler) und potenzielle Verschachtelungsprobleme mithilfe temporärer Variablen mit dem Präfix `$__` und eindeutigen IDs von `PrintContext::generateId()` behandelt werden. + + +Reine n:Attribute +----------------- + +Während viele `n:Attribute` wie `n:if` oder `n:foreach` als bequeme Abkürzungen für ihre paarweisen Tag-Gegenstücke (`{if}...{/if}`, `{foreach}...{/foreach}`) dienen, ermöglicht Latte auch die Definition von Tags, die *nur* in Form von n:Attributen existieren. Diese werden oft verwendet, um Attribute oder das Verhalten des HTML-Elements zu ändern, an das sie angehängt sind. + +Standardbeispiele, die in Latte integriert sind, umfassen [`n:class` |tags#n:class], das hilft, das `class`-Attribut dynamisch zusammenzustellen, und [`n:attr` |tags#n:attr], das mehrere beliebige Attribute setzen kann. + +Erstellen wir unser eigenes reines n:Attribut: `n:confirm`, das einen JavaScript-Bestätigungsdialog hinzufügt, bevor eine Aktion ausgeführt wird (wie das Folgen eines Links oder das Senden eines Formulars). + +**Ziel:** Implementieren von `n:confirm="'Sind Sie sicher?'"`, das einen `onclick`-Handler hinzufügt, um die Standardaktion zu verhindern, wenn der Benutzer den Bestätigungsdialog abbricht. + + +Implementieren von `ConfirmNode` +-------------------------------- + +Wir benötigen eine Node-Klasse und eine Parsing-Funktion. + +```php +expectArguments(); + $node = $tag->node = new self; + $node->message = $tag->parser->parseExpression(); + return $node; + } + + /** + * Generiert den Code des 'onclick'-Attributs mit korrektem Escaping. + */ + public function print(PrintContext $context): string + { + // Stellt korrektes Escaping für JavaScript- und HTML-Attributkontexte sicher. + return $context->format( + <<<'XX' + echo ' onclick="', LR\Filters::escapeHtmlAttr('return confirm(' . LR\Filters::escapeJs(%node) . ')'), '"' %line; + XX, + $this->message, + $this->position, + ); + } + + public function &getIterator(): \Generator + { + yield $this->message; + } +} +``` + +Die `print()`-Methode generiert PHP-Code, der schließlich während des Template-Renderings das HTML-Attribut `onclick="..."` ausgibt. Die Behandlung verschachtelter Kontexte (JavaScript innerhalb eines HTML-Attributs) erfordert sorgfältiges Escaping. Der Filter `LR\Filters::escapeJs(%node)` wird zur Laufzeit aufgerufen und escapet die Nachricht korrekt für die Verwendung innerhalb von JavaScript (die Ausgabe wäre wie `"Sind Sie sicher?"`). Dann escapet der Filter `LR\Filters::escapeHtmlAttr(...)` Zeichen, die in HTML-Attributen speziell sind, sodass die Ausgabe zu `return confirm("Sind Sie sicher?")` geändert würde. Dieses zweistufige Laufzeit-Escaping stellt sicher, dass die Nachricht für JavaScript sicher ist und der resultierende JavaScript-Code sicher in das HTML-Attribut `onclick` eingebettet werden kann. + + +Registrierung und Verwendung +---------------------------- + +Registrieren Sie das n:Attribut in Ihrer Erweiterung. Vergessen Sie nicht das Präfix `n:` im Schlüssel: + +```php +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), + 'n:confirm' => ConfirmNode::create(...), // Registrierung von n:confirm + ]; + } +} +``` + +Jetzt können Sie `n:confirm` auf Links, Schaltflächen oder Formularelementen verwenden: + +```latte +Löschen +``` + +Generiertes HTML: + +```html +Löschen +``` + +Wenn der Benutzer auf den Link klickt, führt der Browser den `onclick`-Code aus, zeigt den Bestätigungsdialog an und navigiert nur dann zu `delete.php`, wenn der Benutzer auf "OK" klickt. + +Dieses Beispiel demonstriert, wie ein reines n:Attribut erstellt werden kann, um das Verhalten oder die Attribute seines Host-HTML-Elements zu ändern, indem geeigneter PHP-Code in seiner `print()`-Methode generiert wird. Denken Sie an das doppelte Escaping, das oft erforderlich ist: einmal für den Zielkontext (JavaScript in diesem Fall) und erneut für den HTML-Attributkontext. + + +Fortgeschrittene Themen +======================= + +Während die vorherigen Abschnitte die grundlegenden Konzepte abdecken, gibt es hier einige fortgeschrittenere Themen, auf die Sie beim Erstellen benutzerdefinierter Latte-Tags stoßen könnten. + + +Tag-Ausgabemodi +--------------- + +Das `Tag`-Objekt, das an Ihre `create()`-Funktion übergeben wird, hat eine Eigenschaft `outputMode`. Diese Eigenschaft beeinflusst, wie Latte umgebende Leerzeichen und Einrückungen behandelt, insbesondere wenn das Tag auf einer eigenen Zeile verwendet wird. Sie können diese Eigenschaft in Ihrer `create()`-Funktion ändern. + +- `Tag::OutputKeepIndentation` (Standard für die meisten Tags wie `{=...}`): Latte versucht, die Einrückung vor dem Tag beizubehalten. Neue Zeilen *nach* dem Tag werden im Allgemeinen beibehalten. Dies ist für Tags geeignet, die Inhalte inline ausgeben. +- `Tag::OutputRemoveIndentation` (Standard für Block-Tags wie `{if}`, `{foreach}`): Latte entfernt die führende Einrückung und potenziell eine folgende neue Zeile. Dies hilft, den generierten PHP-Code sauberer zu halten und verhindert zusätzliche leere Zeilen in der HTML-Ausgabe, die durch das Tag selbst verursacht werden. Verwenden Sie dies für Tags, die Steuerstrukturen oder Blöcke darstellen, die selbst keine Leerzeichen hinzufügen sollten. +- `Tag::OutputNone` (Verwendet von Tags wie `{var}`, `{default}`): Ähnlich wie `RemoveIndentation`, signalisiert aber stärker, dass das Tag selbst keine direkte Ausgabe erzeugt, was die Verarbeitung von Leerzeichen um es herum möglicherweise noch aggressiver beeinflusst. Geeignet für deklarative oder Einstellungs-Tags. + +Wählen Sie den Modus, der am besten zum Zweck Ihres Tags passt. Für die meisten strukturellen oder steuernden Tags ist `OutputRemoveIndentation` normalerweise geeignet. + + +Zugriff auf übergeordnete/nächstgelegene Tags +--------------------------------------------- + +Manchmal muss das Verhalten eines Tags vom Kontext abhängen, in dem es verwendet wird, insbesondere davon, in welchem übergeordneten Tag(s) es sich befindet. Das `Tag`-Objekt, das an Ihre `create()`-Funktion übergeben wird, bietet genau zu diesem Zweck die Methode `closestTag(array $classes, ?callable $condition = null): ?Tag`. + +Diese Methode durchsucht die Hierarchie der aktuell geöffneten Tags nach oben (einschließlich HTML-Elementen, die intern während des Parsens repräsentiert werden) und gibt das `Tag`-Objekt des nächstgelegenen Vorfahren zurück, das den spezifischen Kriterien entspricht. Wenn kein übereinstimmender Vorfahre gefunden wird, gibt sie `null` zurück. + +Das Array `$classes` gibt an, nach welcher Art von Vorfahren-Tags Sie suchen. Es prüft, ob der zugehörige Knoten des Vorfahren-Tags (`$ancestorTag->node`) eine Instanz dieser Klasse ist. + +```php +function create(Tag $tag) +{ + // Suche nach dem nächstgelegenen Vorfahren-Tag, dessen Knoten eine Instanz von ForeachNode ist + $foreachTag = $tag->closestTag([ForeachNode::class]); + if ($foreachTag) { + // Wir können auf die Instanz von ForeachNode selbst zugreifen: + $foreachNode = $foreachTag->node; + } +} +``` + +Beachten Sie `$foreachTag->node`: Dies funktioniert nur, weil es Konvention in der Latte-Tag-Entwicklung ist, den erstellten Knoten sofort `$tag->node` innerhalb der `create()`-Methode zuzuweisen, wie wir es immer getan haben. + +Manchmal reicht der reine Vergleich des Knotentyps nicht aus. Möglicherweise müssen Sie eine spezifische Eigenschaft des potenziellen Vorfahren-Tags oder seines Knotens überprüfen. Das optionale zweite Argument für `closestTag()` ist ein Callable, das das potenzielle Vorfahren-`Tag`-Objekt empfängt und zurückgeben sollte, ob es eine gültige Übereinstimmung ist. + +```php +function create(Tag $tag) +{ + $dynamicBlockTag = $tag->closestTag( + [BlockNode::class], + // Bedingung: Der Block muss dynamisch sein + fn(Tag $blockTag) => $blockTag->node->block->isDynamic(), + ); +} +``` + +Die Verwendung von `closestTag()` ermöglicht die Erstellung von Tags, die kontextbewusst sind und die korrekte Verwendung innerhalb der Struktur Ihres Templates erzwingen, was zu robusteren und verständlicheren Templates führt. + + +`PrintContext::format()`-Platzhalter +------------------------------------ + +Wir haben oft `PrintContext::format()` verwendet, um PHP-Code in den `print()`-Methoden unserer Knoten zu generieren. Es akzeptiert eine Maskenzeichenkette und nachfolgende Argumente, die Platzhalter in der Maske ersetzen. Hier ist eine Zusammenfassung der verfügbaren Platzhalter: + +- **`%node`**: Das Argument muss eine Instanz von `Node` sein. Ruft die `print()`-Methode des Knotens auf und fügt die resultierende PHP-Code-Zeichenkette ein. +- **`%dump`**: Das Argument ist ein beliebiger PHP-Wert. Exportiert den Wert in gültigen PHP-Code. Geeignet für Skalare, Arrays, null. + - `$context->format('echo %dump;', 'Hello')` -> `echo 'Hello';` + - `$context->format('$arr = %dump;', [1, 2])` -> `$arr = [1, 2];` +- **`%raw`**: Fügt das Argument direkt in den Ausgabe-PHP-Code ein, ohne jegliches Escaping oder Modifikation. **Mit Vorsicht verwenden**, hauptsächlich zum Einfügen vorab generierter PHP-Code-Fragmente oder Variablennamen. + - `$context->format('%raw = 1;', '$variableName')` -> `$variableName = 1;` +- **`%args`**: Das Argument muss ein `Expression\ArrayNode` sein. Gibt die Array-Elemente formatiert als Argumente für einen Funktions- oder Methodenaufruf aus (durch Kommas getrennt, verarbeitet benannte Argumente, falls vorhanden). + - `$argsNode = new ArrayNode([...]);` + - `$context->format('myFunc(%args);', $argsNode)` -> `myFunc(1, name: 'Joe');` +- **`%line`**: Das Argument muss ein `Position`-Objekt sein (normalerweise `$this->position`). Fügt einen PHP-Kommentar `/* line X */` ein, der die Quellzeilennummer angibt. + - `$context->format('echo "Hi" %line;', $this->position)` -> `echo "Hi" /* line 42 */;` +- **`%escape(...)`**: Generiert PHP-Code, der den inneren Ausdruck *zur Laufzeit* mithilfe der aktuellen kontextbewussten Escaping-Regeln escapet. + - `$context->format('echo %escape(%node);', $variableNode)` +- **`%modify(...)`**: Das Argument muss ein `ModifierNode` sein. Generiert PHP-Code, der die im `ModifierNode` angegebenen Filter auf den inneren Inhalt anwendet, einschließlich kontextbewusstem Escaping, wenn nicht durch `|noescape` deaktiviert. + - `$context->format('%modify(%node);', $modifierNode, $variableNode)` +- **`%modifyContent(...)`**: Ähnlich wie `%modify`, aber für die Modifikation von Blöcken erfassten Inhalts (oft HTML) konzipiert. + +Sie können explizit auf Argumente nach ihrem Index (ab Null) verweisen: `%0.node`, `%1.dump`, `%2.raw` usw. Dies ermöglicht es, ein Argument mehrmals in der Maske wiederzuverwenden, ohne es wiederholt an `format()` zu übergeben. Siehe das Beispiel des `{repeat}`-Tags, wo `%0.raw` und `%2.raw` verwendet wurden. + + +Beispiel für komplexes Argumentparsing +-------------------------------------- + +Während `parseExpression()`, `parseArguments()` usw. viele Fälle abdecken, benötigen Sie manchmal komplexere Parsing-Logik unter Verwendung des niedrigstufigen `TokenStream`, der über `$tag->parser->stream` verfügbar ist. + +**Ziel:** Erstellen eines Tags `{embedYoutube $videoID, width: 640, height: 480}`. Wir möchten die erforderliche Video-ID (Zeichenkette oder Variable) parsen, gefolgt von optionalen Schlüssel-Wert-Paaren für die Dimensionen. + +```php +expectArguments(); + $node = $tag->node = new self; + // Parsen der erforderlichen Video-ID + $node->videoId = $tag->parser->parseExpression(); + + // Parsen optionaler Schlüssel-Wert-Paare + $stream = $tag->parser->stream; // Token-Stream abrufen + while ($stream->tryConsume(',')) { // Erfordert Trennung durch Komma + // Erwarten des Bezeichners 'width' oder 'height' + $keyToken = $stream->consume(Token::Php_Identifier); + $key = strtolower($keyToken->text); + + $stream->consume(':'); // Erwarten des Doppelpunkt-Trennzeichens + + $value = $tag->parser->parseExpression(); // Parsen des Wert-Ausdrucks + + if ($key === 'width') { + $node->width = $value; + } elseif ($key === 'height') { + $node->height = $value; + } else { + throw new CompileException("Unbekanntes Argument '$key'. Erwartet 'width' oder 'height'.", $keyToken->position); + } + } + + return $node; + } +} +``` + +Diese Kontrollebene ermöglicht es Ihnen, sehr spezifische und komplexe Syntaxen für Ihre benutzerdefinierten Tags zu definieren, indem Sie direkt mit dem Token-Stream interagieren. + + +Verwenden von `AuxiliaryNode` +----------------------------- + +Latte bietet allgemeine "Hilfs"-Knoten für spezielle Situationen während der Codegenerierung oder innerhalb von Kompilierungsdurchläufen. Dies sind `AuxiliaryNode` und `Php\Expression\AuxiliaryNode`. + +Betrachten Sie `AuxiliaryNode` als flexiblen Containerknoten, der seine Kernfunktionalitäten – Codegenerierung und Bereitstellung von Kindknoten – an die in seinem Konstruktor bereitgestellten Argumente delegiert: + +- `print()`-Delegation: Das erste Konstruktorargument ist eine PHP-**Closure**. Wenn Latte die `print()`-Methode auf einem `AuxiliaryNode` aufruft, führt es diese bereitgestellte Closure aus. Die Closure empfängt einen `PrintContext` und alle im zweiten Konstruktorargument übergebenen Knoten, sodass Sie zur Laufzeit eine vollständig benutzerdefinierte PHP-Code-Generierungslogik definieren können. +- `getIterator()`-Delegation: Das zweite Konstruktorargument ist ein **Array von `Node`-Objekten**. Wenn Latte die Kinder eines `AuxiliaryNode` durchlaufen muss (z. B. während Kompilierungsdurchläufen), stellt seine `getIterator()`-Methode einfach die in diesem Array aufgelisteten Knoten bereit. + +Beispiel: + +```php +$node = new AuxiliaryNode( + // 1. Diese Closure wird zum Körper von print() + fn(PrintContext $context, $arg1, $arg2) => $context->format('...%node...%node...', $arg1, $arg2), + + // 2. Diese Knoten werden von der getIterator()-Methode bereitgestellt und an die obige Closure übergeben + [$argumentNode1, $argumentNode2] +); +``` + +Latte bietet zwei verschiedene Typen, je nachdem, wo Sie den generierten Code einfügen müssen: + +- `Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode`: Verwenden Sie dies, wenn Sie ein Stück PHP-Code generieren müssen, das einen **Ausdruck** repräsentiert. +- `Latte\Compiler\Nodes\AuxiliaryNode`: Verwenden Sie dies für allgemeinere Zwecke, wenn Sie einen Block PHP-Code einfügen müssen, der eine oder mehrere **Anweisungen** repräsentiert. + +Ein wichtiger Grund, `AuxiliaryNode` anstelle von Standardknoten (wie `StaticMethodCallNode`) innerhalb Ihrer `print()`-Methode oder eines Kompilierungsdurchlaufs zu verwenden, ist die **Kontrolle der Sichtbarkeit für nachfolgende Kompilierungsdurchläufe**, insbesondere solche im Zusammenhang mit Sicherheit, wie die Sandbox. + +Betrachten Sie ein Szenario: Ihr Kompilierungsdurchlauf muss einen vom Benutzer bereitgestellten Ausdruck (`$userExpr`) mit einem Aufruf einer spezifischen, vertrauenswürdigen Hilfsfunktion `myInternalSanitize($userExpr)` umschließen. Wenn Sie einen Standardknoten `new FunctionCallNode('myInternalSanitize', [$userExpr])` erstellen, ist dieser für den AST-Durchlauf vollständig sichtbar. Wenn der Sandbox-Durchlauf später ausgeführt wird und `myInternalSanitize` *nicht* auf seiner Whitelist steht, kann die Sandbox diesen Aufruf *blockieren* oder modifizieren, was möglicherweise die interne Logik Ihres Tags stört, obwohl *Sie*, der Tag-Autor, wissen, dass dieser spezifische Aufruf sicher und notwendig ist. Sie können den Aufruf daher direkt innerhalb der Closure von `AuxiliaryNode` generieren. + +```php +use Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode; + +// ... innerhalb von print() oder Kompilierungsdurchlauf ... +$wrappedNode = new AuxiliaryNode( + fn(PrintContext $context, $userExpr) => $context->format( + 'myInternalSanitize(%node)', // Direkte Generierung von PHP-Code + $userExpr, + ), + // WICHTIG: Übergeben Sie hier immer noch den ursprünglichen Benutzer-Ausdrucksknoten! + [$userExpr], +); +``` + +In diesem Fall sieht der Sandbox-Durchlauf den `AuxiliaryNode`, aber **analysiert nicht den von seiner Closure generierten PHP-Code**. Er kann den Aufruf von `myInternalSanitize`, der *innerhalb* der Closure generiert wird, nicht direkt blockieren. + +Während der generierte PHP-Code selbst vor den Durchläufen verborgen ist, **müssen** die *Eingaben* zu diesem Code (Knoten, die Benutzerdaten oder Ausdrücke repräsentieren) **dennoch durchlaufbar sein**. Daher ist das zweite Argument des `AuxiliaryNode`-Konstruktors entscheidend. Sie **müssen** ein Array übergeben, das alle ursprünglichen Knoten enthält (wie `$userExpr` im obigen Beispiel), die Ihre Closure verwendet. Der `getIterator()` von `AuxiliaryNode` **stellt diese Knoten bereit**, sodass Kompilierungsdurchläufe wie die Sandbox sie auf potenzielle Probleme analysieren können. + + +Bewährte Praktiken +================== + +- **Klarer Zweck:** Stellen Sie sicher, dass Ihr Tag einen klaren und notwendigen Zweck hat. Erstellen Sie keine Tags für Aufgaben, die leicht mit [Filtern |custom-filters] oder [Funktionen |custom-functions] gelöst werden können. +- **`getIterator()` korrekt implementieren:** Implementieren Sie immer `getIterator()` und stellen Sie *Referenzen* (`&`) auf *alle* Kindknoten (Argumente, Inhalt) bereit, die aus dem Template geparst wurden. Dies ist unerlässlich für Kompilierungsdurchläufe, Sicherheit (Sandbox) und potenzielle zukünftige Optimierungen. +- **Öffentliche Eigenschaften für Knoten:** Machen Sie Eigenschaften, die Kindknoten enthalten, öffentlich, damit Kompilierungsdurchläufe sie bei Bedarf ändern können. +- **`PrintContext::format()` verwenden:** Nutzen Sie die `format()`-Methode zur Generierung von PHP-Code. Sie behandelt Anführungszeichen, escapet Platzhalter korrekt und fügt Zeilennummernkommentare automatisch hinzu. +- **Temporäre Variablen (`$__`):** Beim Generieren von Laufzeit-PHP-Code, der temporäre Variablen benötigt (z. B. zum Speichern von Zwischensummen, Schleifenzählern), verwenden Sie die Konvention, `$__` voranzustellen, um Kollisionen mit Benutzervariablen und internen Latte-Variablen `$ʟ_` zu vermeiden. +- **Verschachtelung und eindeutige IDs:** Wenn Ihr Tag verschachtelt werden kann oder zur Laufzeit instanzspezifischen Zustand benötigt, verwenden Sie `$context->generateId()` innerhalb Ihrer `print()`-Methode, um eindeutige Suffixe für Ihre temporären `$__`-Variablen zu erstellen. +- **Provider für externe Daten:** Verwenden Sie Provider (registriert über `Extension::getProviders()`), um auf Laufzeitdaten oder -dienste (`$this->global->...`) zuzugreifen, anstatt Werte fest zu codieren oder sich auf globalen Zustand zu verlassen. Verwenden Sie Herstellerpräfixe für Providernamen. +- **n:Attribute berücksichtigen:** Wenn Ihr paarweises Tag logischerweise auf einem einzelnen HTML-Element operiert, bietet Latte wahrscheinlich automatische `n:Attribut`-Unterstützung. Behalten Sie dies für die Benutzerfreundlichkeit im Hinterkopf. Wenn Sie ein Attribut-modifizierendes Tag erstellen, überlegen Sie, ob ein reines `n:Attribut` die geeignetste Form ist. +- **Testen:** Schreiben Sie Tests für Ihre Tags, die sowohl das Parsen verschiedener Syntaxeingaben als auch die Korrektheit der **PHP-Code**-Ausgabe abdecken. + +Durch die Befolgung dieser Richtlinien können Sie leistungsstarke, robuste und wartbare benutzerdefinierte Tags erstellen, die sich nahtlos in die Latte-Template-Engine integrieren. + +.[note] +Das Studium der Knotenklassen, die Teil von Latte sind, ist der beste Weg, um alle Details des Parsing-Prozesses zu lernen. diff --git a/latte/de/develop.texy b/latte/de/develop.texy index 15b3491a87..2a236935e2 100644 --- a/latte/de/develop.texy +++ b/latte/de/develop.texy @@ -1,70 +1,66 @@ -Praktiken für Entwickler -************************ +Entwicklerpraktiken +******************* -Installation .[#toc-installation] -================================= +Installation +============ -Der beste Weg, Latte zu installieren, ist die Verwendung eines Composers: +Der beste Weg, Latte zu installieren, ist über Composer: ```shell composer require latte/latte ``` -Unterstützte PHP-Versionen (gilt für die neuesten Patch-Latte-Versionen): +Unterstützte PHP-Versionen (gilt für die letzten Patch-Versionen von Latte): -| Version | kompatibel mit PHP +| Version | Kompatibel mit PHP |-----------------|------------------- -| Latte 3.0 | PHP 8.0 - 8.2 -| Latte 2.11 | PHP 7.1 - 8.2 -| Latte 2.8 - 2.10 | PHP 7.1 - 8.1 +| Latte 3.0 | PHP 8.0 – 8.2 -Wie man eine Vorlage rendert .[#toc-how-to-render-a-template] -============================================================= +Wie rendert man ein Template? +============================= -Wie rendert man eine Vorlage? Verwenden Sie einfach diesen einfachen Code: +Wie rendert man ein Template? Dafür genügt dieser einfache Code: ```php $latte = new Latte\Engine; -// Cache-Verzeichnis +// Verzeichnis für den Cache $latte->setTempDirectory('/path/to/tempdir'); $params = [ /* Template-Variablen */ ]; // oder $params = new TemplateParameters(/* ... */); -// zur Ausgabe rendern +// in die Ausgabe rendern $latte->render('template.latte', $params); -// oder in eine Variable rendern +// in eine Variable rendern $output = $latte->renderToString('template.latte', $params); ``` -Parameter können Arrays oder noch besser [Objekte |#Parameters as a class] sein, die eine Typüberprüfung und Vorschläge im Editor bieten. +Parameter können Arrays sein oder noch besser [ein Objekt |#Parameter als Klasse], das Typprüfung und Autovervollständigung in Editoren sicherstellt. .[note] Anwendungsbeispiele finden Sie auch im Repository [Latte examples |https://github.com/nette-examples/latte]. -Leistung und Caching .[#toc-performance-and-caching] -==================================================== +Leistung und Cache +================== -Latte-Vorlagen sind extrem schnell, weil Latte sie direkt in PHP-Code kompiliert und auf der Festplatte zwischenspeichert. Daher haben sie keinen zusätzlichen Overhead im Vergleich zu Vorlagen, die in reinem PHP geschrieben sind. +Templates in Latte sind extrem schnell, da Latte sie direkt in PHP-Code kompiliert und auf der Festplatte zwischenspeichert. Sie haben also keinen zusätzlichen Overhead gegenüber Templates, die in reinem PHP geschrieben sind. -Der Cache wird jedes Mal automatisch neu generiert, wenn Sie die Quelldatei ändern. So können Sie Ihre Latte-Vorlagen bequem während der Entwicklung bearbeiten und die Änderungen sofort im Browser sehen. In einer Produktionsumgebung können Sie diese Funktion deaktivieren und so ein wenig Leistung einsparen: +Der Cache wird automatisch jedes Mal neu generiert, wenn Sie die Quelldatei ändern. Während der Entwicklung können Sie also bequem Ihre Latte-Templates bearbeiten und die Änderungen sofort im Browser sehen. Sie können diese Funktion in der Produktionsumgebung deaktivieren, um ein wenig Leistung zu sparen: ```php $latte->setAutoRefresh(false); ``` -Beim Einsatz auf einem Produktionsserver kann die anfängliche Cache-Generierung, insbesondere bei größeren Anwendungen, verständlicherweise eine Weile dauern. Latte hat einen eingebauten Schutz gegen "Cache Stampede":https://en.wikipedia.org/wiki/Cache_stampede. -Dabei handelt es sich um eine Situation, in der ein Server eine große Anzahl gleichzeitiger Anfragen erhält, die alle gleichzeitig generiert werden, da der Cache von Latte noch nicht vorhanden ist. Das belastet die CPU. -Latte ist schlau, und wenn es mehrere gleichzeitige Anfragen gibt, erzeugt nur der erste Thread den Cache, die anderen warten und verwenden ihn dann. +Bei der Bereitstellung auf einem Produktionsserver kann die anfängliche Generierung des Caches, insbesondere bei größeren Anwendungen, natürlich einen Moment dauern. Latte verfügt über eine integrierte Prävention gegen "Cache Stampede":https://en.wikipedia.org/wiki/Cache_stampede. Dies ist eine Situation, in der eine größere Anzahl gleichzeitiger Anfragen eintrifft, die Latte starten, und da der Cache noch nicht existiert, würden sie alle gleichzeitig mit der Generierung beginnen. Dies würde den Server unverhältnismäßig belasten. Latte ist intelligent und bei mehreren gleichzeitigen Anfragen generiert nur der erste Thread den Cache, die anderen warten und nutzen ihn anschließend. -Parameter als Klasse .[#toc-parameters-as-a-class] -================================================== +Parameter als Klasse +==================== -Besser als Variablen als Arrays an die Vorlage zu übergeben ist es, eine Klasse zu erstellen. Sie erhalten eine [typsichere Notation |type-system], [nette Vorschläge in der IDE |recipes#Editors and IDE] und eine Möglichkeit, [Filter |extending-latte#Filters Using the Class] und [Funktionen |extending-latte#Functions Using the Class] [zu registrieren |extending-latte#Filters Using the Class]. +Besser als Variablen als Array an das Template zu übergeben, ist es, eine Klasse zu erstellen. Sie erhalten so eine [typsichere Schreibweise |type-system], [angenehme Autovervollständigung in IDEs |recipes#Editoren und IDEs] und einen Weg zur [Registrierung von Filtern |custom-filters#Filter unter Verwendung einer Klasse mit Attributen] und [Funktionen |custom-functions#Funktionen unter Verwendung einer Klasse mit Attributen]. ```php class MailTemplateParameters @@ -88,12 +84,12 @@ $latte->render('mail.latte', new MailTemplateParameters( ``` -Auto-Escaping von Variablen deaktivieren .[#toc-disabling-auto-escaping-of-variable] -==================================================================================== +Deaktivieren des automatischen Escapings einer Variablen +======================================================== -Wenn die Variable eine HTML-Zeichenkette enthält, können Sie sie so markieren, dass Latte sie nicht automatisch (und damit doppelt) umbricht. Auf diese Weise müssen Sie nicht `|noescape` in der Vorlage angeben. +Wenn eine Variable eine Zeichenkette in HTML enthält, können Sie sie so markieren, dass Latte sie nicht automatisch (und somit doppelt) escapet. Sie vermeiden so die Notwendigkeit, `|noescape` im Template anzugeben. -Am einfachsten ist es, die Zeichenkette in ein `Latte\Runtime\Html` Objekt zu verpacken: +Der einfachste Weg ist, die Zeichenkette in ein `Latte\Runtime\Html`-Objekt zu verpacken: ```php $params = [ @@ -101,7 +97,7 @@ $params = [ ]; ``` -Latte entschlüsselt auch nicht alle Objekte, die die Schnittstelle `Latte\HtmlStringable` implementieren. Sie können also eine eigene Klasse erstellen, deren Methode `__toString()` HTML-Code zurückgibt, der nicht automatisch escaped wird: +Latte escapet außerdem keine Objekte, die das Interface `Latte\HtmlStringable` implementieren. Sie können also eine eigene Klasse erstellen, deren `__toString()`-Methode HTML-Code zurückgibt, der nicht automatisch escapet wird: ```php class Emphasis extends Latte\HtmlStringable @@ -123,32 +119,85 @@ $params = [ ``` .[warning] -Die Methode `__toString` muss korrektes HTML zurückgeben und die Parameter escapen, sonst kann eine XSS-Schwachstelle entstehen! +Die `__toString`-Methode muss korrektes HTML zurückgeben und das Escaping von Parametern sicherstellen, andernfalls kann eine XSS-Schwachstelle entstehen! -Wie man Latte mit Filtern, Tags, etc. erweitert .[#toc-how-to-extend-latte-with-filters-tags-etc] -================================================================================================= +Wie erweitert man Latte um Filter, Tags usw.? +============================================= -Wie fügt man Latte einen eigenen Filter, eine Funktion, ein Tag usw. hinzu? Das erfahren Sie im Kapitel [Latte erweitern |extending Latte]. -Wenn Sie Ihre Änderungen in verschiedenen Projekten wiederverwenden oder sie mit anderen teilen wollen, sollten Sie [eine Erweiterung erstellen |creating-extension]. +Wie fügt man Latte eigene Filter, Funktionen, Tags usw. hinzu? Dies wird im Kapitel [Latte erweitern |extending-latte] behandelt. Wenn Sie Ihre Anpassungen in verschiedenen Projekten wiederverwenden oder mit anderen teilen möchten, sollten Sie [eine Erweiterung erstellen |extending-latte#Latte Extension]. -Beliebiger Code in der Vorlage `{php ...}` .{data-version:3.0}{toc: RawPhpExtension} -==================================================================================== +Beliebiger Code im Template `{php ...}` .{toc: RawPhpExtension} +=============================================================== -Nur PHP-Ausdrücke können innerhalb des [`{do}` |tags#do] Tags geschrieben werden, d.h. Sie können z.B. keine Konstrukte wie `if ... else` oder mit Semikolon beendete Anweisungen einfügen. +Innerhalb des Tags [`{do}` |tags#do] können nur PHP-Ausdrücke geschrieben werden, Sie können also keine Konstrukte wie `if ... else` oder Anweisungen, die mit einem Semikolon enden, einfügen. -Sie können jedoch die Erweiterung `RawPhpExtension` registrieren, die das Tag `{php ...}` hinzufügt, mit dem Sie auf Risiko des Vorlagenautors beliebigen PHP-Code einfügen können. +Sie können jedoch die Erweiterung `RawPhpExtension` registrieren, die das Tag `{php ...}` hinzufügt. Damit können Sie beliebigen PHP-Code einfügen. Für ihn gelten keine Sandbox-Regeln, die Verwendung liegt also in der Verantwortung des Template-Autors. ```php $latte->addExtension(new Latte\Essential\RawPhpExtension); ``` -Übersetzung in Schablonen .{data-version:3.0}{toc: TranslatorExtension} -======================================================================= +Überprüfung des generierten Codes .{data-version:3.0.7} +======================================================= + +Latte kompiliert Templates in PHP-Code. Natürlich achtet es darauf, dass der generierte Code syntaktisch valide ist. Bei Verwendung von Drittanbieter-Erweiterungen oder `RawPhpExtension` kann Latte jedoch die Korrektheit der generierten Datei nicht garantieren. Außerdem kann man in PHP Code schreiben, der zwar syntaktisch korrekt ist, aber verboten ist (z. B. Zuweisung eines Wertes zur Variablen `$this`) und einen PHP Compile Error verursacht. Wenn Sie eine solche Operation im Template schreiben, gelangt sie auch in den generierten PHP-Code. Da es in PHP über zweihundert verschiedene verbotene Operationen gibt, hat Latte nicht den Ehrgeiz, sie alle aufzudecken. PHP selbst weist erst beim Rendern darauf hin, was normalerweise kein Problem darstellt. + +Es gibt jedoch Situationen, in denen Sie bereits zur Kompilierungszeit des Templates wissen möchten, dass es keinen PHP Compile Error enthält. Insbesondere dann, wenn Templates von Benutzern bearbeitet werden können oder Sie die [Sandbox |sandbox] verwenden. In diesem Fall lassen Sie die Templates bereits zur Kompilierungszeit überprüfen. Diese Funktionalität aktivieren Sie mit der Methode `Engine::enablePhpLint()`. Da zur Überprüfung die PHP-Binärdatei aufgerufen werden muss, übergeben Sie den Pfad dorthin als Parameter: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); + +try { + $latte->compile('home.latte'); +} catch (Latte\CompileException $e) { + // fängt Fehler in Latte und auch Compile Errors in PHP ab + echo 'Fehler: ' . $e->getMessage(); +} +``` + + +Gebietsschema .{data-version:3.0.18}{toc: Locale} +================================================= + +Latte ermöglicht die Einstellung des Gebietsschemas, das die Formatierung von Zahlen, Daten und die Sortierung beeinflusst. Sie wird mit der Methode `setLocale()` eingestellt. Der Gebietsschema-Bezeichner folgt dem IETF-Sprach-Tag-Standard, der von der PHP-Erweiterung `intl` verwendet wird. Er besteht aus dem Sprachcode und optional dem Ländercode, z. B. `en_US` für Englisch in den Vereinigten Staaten, `de_DE` für Deutsch in Deutschland usw. + +```php +$latte = new Latte\Engine; +$latte->setLocale('de_DE'); +``` + +Die Gebietsschema-Einstellung beeinflusst die Filter [localDate |filters#localDate], [sort |filters#sort], [number |filters#number] und [bytes |filters#bytes]. + +.[note] +Erfordert die PHP-Erweiterung `intl`. Die Einstellung in Latte beeinflusst nicht die globale Locale-Einstellung in PHP. + + +Strikter Modus .{data-version:3.0.8} +==================================== + +Im strikten Parsing-Modus prüft Latte, ob schließende HTML-Tags fehlen und verbietet außerdem die Verwendung der Variablen `$this`. Sie aktivieren ihn so: + +```php +$latte = new Latte\Engine; +$latte->setStrictParsing(); +``` + +Die Generierung von Templates mit dem Header `declare(strict_types=1)` aktivieren Sie so: + +```php +$latte = new Latte\Engine; +$latte->setStrictTypes(); +``` -Verwenden Sie die Erweiterung `TranslatorExtension` zum Hinzufügen von [`{_...}` |tags#_], [`{translate}` |tags#translate] und Filter [`translate` |filters#translate] zur Vorlage hinzuzufügen. Sie werden verwendet, um Werte oder Teile der Vorlage in andere Sprachen zu übersetzen. Der Parameter ist die Methode (PHP Callable), die die Übersetzung durchführt: + +Übersetzung in Templates .{toc: TranslatorExtension} +==================================================== + +Mit der Erweiterung `TranslatorExtension` fügen Sie dem Template die Tags [`{_...}` |tags#], [`{translate}` |tags#translate] und den Filter [`translate` |filters#translate] hinzu. Sie dienen zur Übersetzung von Werten oder Teilen des Templates in andere Sprachen. Als Parameter geben wir eine Methode (PHP callable) an, die die Übersetzung durchführt: ```php class MyTranslator @@ -158,7 +207,7 @@ class MyTranslator public function translate(string $original): string { - // $translated aus $original gemäß $this->lang erstellen + // aus $original erstellen wir $translated gemäß $this->lang return $translated; } } @@ -170,7 +219,7 @@ $extension = new Latte\Essential\TranslatorExtension( $latte->addExtension($extension); ``` -Der Übersetzer wird zur Laufzeit aufgerufen, wenn die Vorlage gerendert wird. Latte kann jedoch alle statischen Texte während der Kompilierung der Vorlage übersetzen. Dies spart Leistung, da jede Zeichenkette nur einmal übersetzt wird und die resultierende Übersetzung in die kompilierte Datei geschrieben wird. Dadurch werden mehrere kompilierte Versionen der Vorlage im Cache-Verzeichnis erstellt, eine für jede Sprache. Dazu müssen Sie nur die Sprache als zweiten Parameter angeben: +Der Translator wird zur Laufzeit beim Rendern des Templates aufgerufen. Latte kann jedoch alle statischen Texte bereits während der Kompilierung des Templates übersetzen. Dadurch wird Leistung gespart, da jede Zeichenkette nur einmal übersetzt wird und die resultierende Übersetzung in die kompilierte Form geschrieben wird. Im Cache-Verzeichnis entstehen so mehrere kompilierte Versionen des Templates, eine für jede Sprache. Dazu genügt es, die Sprache als zweiten Parameter anzugeben: ```php $extension = new Latte\Essential\TranslatorExtension( @@ -179,9 +228,9 @@ $extension = new Latte\Essential\TranslatorExtension( ); ``` -Unter statischem Text verstehen wir z. B. `{_'hello'}` oder `{translate}hello{/translate}`. Nicht-statischer Text, wie z. B. `{_$foo}`, wird zur Laufzeit weiter übersetzt. +Statischer Text bedeutet z. B. `{_'hello'}` oder `{translate}hello{/translate}`. Nicht-statische Texte, wie z. B. `{_$foo}`, werden weiterhin zur Laufzeit übersetzt. -Die Vorlage kann dem Übersetzer auch zusätzliche Parameter über `{_$original, foo: bar}` oder `{translate foo: bar}` übergeben, die er als Array `$params` erhält: +Dem Übersetzer können aus dem Template auch zusätzliche Parameter übergeben werden, mittels `{_$original, foo: bar}` oder `{translate foo: bar}`, die er als Array `$params` erhält: ```php public function translate(string $original, ...$params): string @@ -191,66 +240,73 @@ public function translate(string $original, ...$params): string ``` -Fehlersuche und Tracy .[#toc-debugging-and-tracy] -================================================= +Debugging und Tracy +=================== -Latte versucht, die Entwicklung so angenehm wie möglich zu gestalten. Für die Fehlersuche gibt es drei Tags [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak] und [`{trace}` |tags#trace]. +Latte versucht Ihnen die Entwicklung so angenehm wie möglich zu gestalten. Direkt für Debugging-Zwecke gibt es drei Tags: [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak] und [`{trace}` |tags#trace]. -Den größten Komfort erhalten Sie, wenn Sie das großartige [Debugging-Tool Tracy |tracy:] installieren und das Latte-Plugin aktivieren: +Den größten Komfort erhalten Sie, wenn Sie zusätzlich das hervorragende [Debugging-Werkzeug Tracy |tracy:] installieren und das Add-on für Latte aktivieren: ```php -// aktiviert Tracy +// schaltet Tracy ein Tracy\Debugger::enable(); $latte = new Latte\Engine; -// Aktiviert Tracys Nebenstelle +// aktiviert die Erweiterung für Tracy $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension); ``` -Sie sehen nun alle Fehler in einem übersichtlichen roten Bildschirm, einschließlich Fehlern in Vorlagen mit Zeilen- und Spaltenhervorhebung ([Video |https://github.com/nette/tracy/releases/tag/v2.9.0]). -Gleichzeitig erscheint unten rechts in der so genannten Tracy-Bar ein Reiter für Latte, in dem Sie alle gerenderten Vorlagen und ihre Beziehungen (einschließlich der Möglichkeit, in die Vorlage oder den kompilierten Code zu klicken) sowie die Variablen übersichtlich sehen können: +Nun werden Ihnen alle Fehler in einem übersichtlichen roten Bildschirm angezeigt, einschließlich Fehlern in Templates mit Hervorhebung von Zeile und Spalte ([Video |https://github.com/nette/tracy/releases/tag/v2.9.0]). Gleichzeitig erscheint unten rechts in der sogenannten Tracy Bar ein Tab für Latte, wo alle gerenderten Templates und ihre gegenseitigen Beziehungen übersichtlich dargestellt werden (einschließlich der Möglichkeit, zum Template oder zum kompilierten Code durchzuklicken) sowie die Variablen: [* latte-debugging.webp *] -Da Latte die Vorlagen in lesbaren PHP-Code kompiliert, können Sie sie bequem in Ihrer IDE durchgehen. +Da Latte Templates in übersichtlichen PHP-Code kompiliert, können Sie sie bequem in Ihrer IDE schrittweise debuggen. -Linter: Validierung der Vorlagensyntax .{data-version:2.11}{toc: Linter} -======================================================================== +Linter: Syntaxvalidierung von Templates .{toc: Linter} +====================================================== -Das Tool Linter hilft Ihnen, alle Vorlagen durchzugehen und auf Syntaxfehler zu prüfen. Es wird von der Konsole aus gestartet: +Um alle Templates durchzugehen und zu überprüfen, ob sie Syntaxfehler enthalten, hilft Ihnen das Werkzeug Linter. Es wird von der Konsole aus gestartet: ```shell -vendor/bin/latte-lint +vendor/bin/latte-lint ``` -Wenn Sie benutzerdefinierte Tags verwenden, erstellen Sie auch Ihren eigenen Linter, z. B. `custom-latte-lint`: +Mit dem Parameter `--strict` aktivieren Sie den [strikten Modus |#Strikter Modus]. + +Wenn Sie eigene Tags verwenden, erstellen Sie sich auch eine eigene Version des Linters, z. B. `custom-latte-lint`: ```php #!/usr/bin/env php scanDirectory($path); +$path = $argv[1] ?? '.'; -$engine = new Latte\Engine; -// registriert hier die einzelnen Erweiterungen -$engine->addExtension(/* ... */); +$linter = new Latte\Tools\Linter; +$latte = $linter->getEngine(); +// hier fügen Sie Ihre einzelnen Erweiterungen hinzu +$latte->addExtension(/* ... */); -$path = $argv[1]; -$linter = new Latte\Tools\Linter(engine: $engine); $ok = $linter->scanDirectory($path); exit($ok ? 0 : 1); ``` +Alternativ können Sie ein eigenes `Latte\Engine`-Objekt an den Linter übergeben: + +```php +$latte = new Latte\Engine; +// hier konfigurieren wir das Objekt $latte +$linter = new Latte\Tools\Linter(engine: $latte); +``` + -Laden von Templates aus einem String .[#toc-loading-templates-from-a-string] -============================================================================ +Laden von Templates aus einer Zeichenkette +========================================== -Müssen Sie Vorlagen aus Strings statt aus Dateien laden, vielleicht zu Testzwecken? [StringLoader |extending-latte#stringloader] wird Ihnen helfen: +Müssen Sie Templates aus Zeichenketten anstelle von Dateien laden, z. B. zu Testzwecken? Der [StringLoader |loaders#StringLoader] hilft Ihnen dabei: ```php $latte->setLoader(new Latte\Loaders\StringLoader([ @@ -262,10 +318,10 @@ $latte->render('main.file', $params); ``` -Exception Handler .[#toc-exception-handler] -=========================================== +Exception Handler +================= -Sie können Ihren eigenen Handler für erwartete Ausnahmen definieren. Ausnahmen, die innerhalb von [`{try}` |tags#try] und in der [Sandbox |sandbox] ausgelöste Ausnahmen werden an ihn übergeben. +Sie können einen eigenen Handler für erwartete Ausnahmen definieren. Ihm werden Ausnahmen übergeben, die innerhalb von [`{try}` |tags#try] und in der [Sandbox |sandbox] entstehen. ```php $loggingHandler = function (Throwable $e, Latte\Runtime\Template $template) use ($logger) { @@ -277,17 +333,17 @@ $latte->setExceptionHandler($loggingHandler); ``` -Automatisches Layout-Lookup .[#toc-automatic-layout-lookup] -=========================================================== +Automatisches Suchen des Layouts +================================ -Mit Hilfe des Tags [`{layout}` |template-inheritance#layout-inheritance] bestimmt die Vorlage ihre übergeordnete Vorlage. Es ist auch möglich, das Layout automatisch suchen zu lassen, was das Schreiben von Vorlagen vereinfacht, da sie das Tag `{layout}` nicht enthalten müssen. +Mit dem Tag [`{layout}` |template-inheritance#Layout-Vererbung] bestimmt ein Template sein übergeordnetes Template. Es ist auch möglich, das Layout automatisch suchen zu lassen, was das Schreiben von Templates vereinfacht, da das Tag `{layout}` darin nicht notwendig ist. -Dies wird wie folgt erreicht: +Dies wird auf folgende Weise erreicht: ```php $finder = function (Latte\Runtime\Template $template) { if (!$template->getReferenceType()) { - // gibt er den Pfad zur übergeordneten Vorlagendatei zurück + // gibt den Pfad zur Layout-Datei zurück return 'automatic.layout.latte'; } }; @@ -296,4 +352,4 @@ $latte = new Latte\Engine; $latte->addProvider('coreParentFinder', $finder); ``` -Wenn die Vorlage kein Layout haben soll, wird dies mit dem Tag `{layout none}` angezeigt. +Wenn ein Template kein Layout haben soll, teilt es dies mit dem Tag `{layout none}` mit. diff --git a/latte/de/extending-latte.texy b/latte/de/extending-latte.texy index d4ac7b161d..8f8fadf3b3 100644 --- a/latte/de/extending-latte.texy +++ b/latte/de/extending-latte.texy @@ -1,285 +1,227 @@ -Latte verlängern -**************** +Latte erweitern +*************** .[perex] -Latte ist sehr flexibel und kann auf viele Arten erweitert werden: Sie können eigene Filter, Funktionen, Tags, Lader usw. hinzufügen. Wir zeigen Ihnen, wie Sie das tun können. +Latte wurde mit Blick auf Erweiterbarkeit entwickelt. Obwohl seine Standard-Suite von Tags, Filtern und Funktionen viele Anwendungsfälle abdeckt, müssen Sie oft Ihre eigene spezifische Logik oder Hilfswerkzeuge hinzufügen. Diese Seite bietet einen Überblick über die Möglichkeiten, Latte zu erweitern, damit es perfekt zu den Anforderungen Ihres Projekts passt - von einfachen Helfern bis hin zu komplexer neuer Syntax. -Dieses Kapitel beschreibt die verschiedenen Möglichkeiten, Latte zu erweitern. Wenn Sie Ihre Änderungen in verschiedenen Projekten wiederverwenden oder mit anderen teilen wollen, sollten Sie [eine sogenannte Erweiterung erstellen |creating-extension]. +Möglichkeiten, Latte zu erweitern +================================= -Wie viele Wege führen nach Rom? .[#toc-how-many-roads-lead-to-rome] -=================================================================== +Hier ist ein kurzer Überblick über die wichtigsten Möglichkeiten, wie Sie Latte anpassen und erweitern können: -Da einige der Möglichkeiten, Latte zu erweitern, vermischt werden können, wollen wir zunächst versuchen, die Unterschiede zwischen ihnen zu erklären. Als Beispiel wollen wir versuchen, einen *Lorem ipsum*-Generator zu implementieren, dem die Anzahl der zu erzeugenden Wörter übergeben wird. +- **[Benutzerdefinierte Filter |custom-filters]:** Zur Formatierung oder Transformation von Daten direkt in der Template-Ausgabe (z. B. `{$var|myFilter}`). Ideal für Aufgaben wie Datumsformatierung, Textbearbeitung oder Anwendung spezifischer Escapings. Sie können sie auch verwenden, um größere Blöcke von HTML-Inhalten zu bearbeiten, indem Sie den Inhalt in einen anonymen [`{block}` |tags#block] einschließen und einen benutzerdefinierten Filter darauf anwenden. +- **[Benutzerdefinierte Funktionen |custom-functions]:** Zum Hinzufügen wiederverwendbarer Logik, die innerhalb von Ausdrücken im Template aufgerufen werden kann (z. B. `{myFunction($arg1, $arg2)}`). Nützlich für Berechnungen, Zugriff auf Anwendungs-Hilfsfunktionen oder Generierung kleiner Inhaltsteile. +- **[Benutzerdefinierte Tags |custom-tags]:** Zum Erstellen völlig neuer Sprachkonstrukte (`{mytag}...{/mytag}` oder `n:mytag`). Tags bieten die meisten Möglichkeiten, ermöglichen die Definition eigener Strukturen, steuern das Template-Parsing und implementieren komplexe Rendering-Logik. +- **[Kompilierungsdurchläufe |compiler-passes]:** Funktionen, die den abstrakten Syntaxbaum (AST) des Templates nach dem Parsen, aber vor der Generierung von PHP-Code modifizieren. Sie werden für fortgeschrittene Optimierungen, Sicherheitsprüfungen (wie die Sandbox) oder automatische Code-Anpassungen verwendet. +- **[Benutzerdefinierte Loader |loaders]:** Um zu ändern, wie Latte Template-Dateien findet und lädt (z. B. Laden aus einer Datenbank, verschlüsseltem Speicher usw.). -Das wichtigste Konstrukt der Sprache Latte ist das Tag. Wir können einen Generator implementieren, indem wir Latte um einen neuen Tag erweitern: +Die Wahl der richtigen Erweiterungsmethode ist entscheidend. Bevor Sie ein komplexes Tag erstellen, überlegen Sie, ob ein einfacherer Filter oder eine Funktion ausreichen würde. Lassen Sie uns dies am Beispiel der Implementierung eines *Lorem ipsum*-Generators veranschaulichen, der die Anzahl der zu generierenden Wörter als Argument akzeptiert. -```latte -{lipsum 40} -``` - -Das Tag wird gut funktionieren. Allerdings ist der Generator in Form eines Tags möglicherweise nicht flexibel genug, da er nicht in einem Ausdruck verwendet werden kann. In der Praxis müssen Sie übrigens nur selten Tags generieren, und das ist gut so, denn Tags sind eine kompliziertere Art der Erweiterung. - -Versuchen wir also, einen Filter statt eines Tags zu erstellen: - -```latte -{=40|lipsum} -``` - -Auch das ist eine gültige Option. Aber der Filter sollte den übergebenen Wert in etwas anderes umwandeln. Hier verwenden wir den Wert `40`, der die Anzahl der erzeugten Wörter angibt, als Filterargument, nicht als den Wert, den wir umwandeln wollen. +- **Als Tag?** `{lipsum 40}` - Möglich, aber Tags sind besser für Steuerstrukturen oder die Generierung komplexen Markups geeignet. Tags können nicht direkt in Ausdrücken verwendet werden. +- **Als Filter?** `{=40|lipsum}` - Technisch funktioniert das, aber Filter sind dazu gedacht, den Eingabewert zu *transformieren*. Hier ist `40` ein *Argument*, nicht der Wert, der transformiert wird. Das wirkt semantisch falsch. +- **Als Funktion?** `{lipsum(40)}` - Dies ist die natürlichste Lösung! Funktionen akzeptieren Argumente und geben Werte zurück, was ideal für die Verwendung in beliebigen Ausdrücken ist: `{var $text = lipsum(40)}`. -Versuchen wir es also mit function: - -```latte -{lipsum(40)} -``` +**Allgemeine Empfehlung:** Verwenden Sie Funktionen für Berechnungen/Generierung, Filter für Transformationen und Tags für neue Sprachkonstrukte oder komplexes Markup. Verwenden Sie Durchläufe zur Manipulation des AST und Loader zum Abrufen von Templates. -So geht's! Für dieses spezielle Beispiel ist die Erstellung einer Funktion der ideale Erweiterungspunkt. Sie können sie überall dort aufrufen, wo ein Ausdruck akzeptiert wird, zum Beispiel: - -```latte -{var $text = lipsum(40)} -``` +Direkte Registrierung +===================== -Filter .[#toc-filters] -====================== +Für projektspezifische Hilfswerkzeuge oder schnelle Erweiterungen ermöglicht Latte die direkte Registrierung von Filtern und Funktionen im `Latte\Engine`-Objekt. -Erstellen Sie einen Filter, indem Sie seinen Namen und eine beliebige PHP-Aufrufbarkeit, wie z. B. eine Funktion, registrieren: +Zur Registrierung eines Filters verwenden Sie die Methode `addFilter()`. Das erste Argument Ihrer Filterfunktion ist der Wert vor dem `|`-Zeichen, und die folgenden Argumente sind diejenigen, die nach dem Doppelpunkt `:` übergeben werden. ```php $latte = new Latte\Engine; -$latte->addFilter('shortify', fn(string $s) => mb_substr($s, 0, 10)); // kürzt den Text auf 10 Zeichen -``` -In diesem Fall wäre es besser, wenn der Filter einen zusätzlichen Parameter erhält: - -```php -$latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); -``` +// Definition des Filters (Callable-Objekt: Funktion, statische Methode usw.) +$myTruncate = fn(string $s, int $length = 50) => mb_substr($s, 0, $length); -Wir verwenden ihn in einer Vorlage wie dieser: +// Registrierung +$latte->addFilter('truncate', $myTruncate); -```latte -

                                      {$text|shortify}

                                      -

                                      {$text|shortify:100}

                                      +// Verwendung im Template: {$text|truncate} oder {$text|truncate:100} ``` -Wie Sie sehen können, erhält die Funktion die linke Seite des Filters vor der Pipe `|` as the first argument and the arguments passed to the filter after `:` als nächste Argumente. - -Natürlich kann die Funktion, die den Filter darstellt, eine beliebige Anzahl von Parametern annehmen, und auch variable Parameter werden unterstützt. - - -Filter, die die Klasse .[#toc-filters-using-the-class] ------------------------------------------------------- - -Die zweite Möglichkeit, einen Filter zu definieren, ist die [Verwendung einer Klasse |develop#Parameters as a class]. Wir erstellen eine Methode mit dem Attribut `TemplateFilter`: +Sie können auch einen **Filter Loader** registrieren, eine Funktion, die dynamisch Callable-Objekte für Filter basierend auf dem angeforderten Namen bereitstellt: ```php -class TemplateParameters -{ - public function __construct( - // parameters - ) {} - - #[Latte\Attributes\TemplateFilter] - public function shortify(string $s, int $len = 10): string - { - return mb_substr($s, 0, $len); - } -} - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); +$latte->addFilterLoader(fn(string $name) => /* gibt Callable-Objekt oder null zurück */); ``` -Wenn Sie PHP 7.x und Latte 2.x verwenden, benutzen Sie die `/** @filter */` Annotation anstelle des Attributs. - -Filter-Lader .{data-version:2.10}[#toc-filter-loader] ------------------------------------------------------ - -Anstatt einzelne Filter zu registrieren, können Sie einen so genannten Loader erstellen. Dabei handelt es sich um eine Funktion, die mit dem Filternamen als Argument aufgerufen wird und ihre PHP-Aufrufbarkeit oder null zurückgibt. +Zur Registrierung einer Funktion, die in Template-Ausdrücken verwendet werden kann, verwenden Sie `addFunction()`. ```php -$latte->addFilterLoader([new Filters, 'load']); +$latte = new Latte\Engine; +// Definition der Funktion +$isWeekend = fn(DateTimeInterface $date) => $date->format('N') >= 6; -class Filters -{ - public function load(string $filter): ?callable - { - if (in_array($filter, get_class_methods($this))) { - return [$this, $filter]; - } - return null; - } - - public function shortify($s, $len = 10) - { - return mb_substr($s, 0, $len); - } - - // ... -} +// Registrierung +$latte->addFunction('isWeekend', $isWeekend); + +// Verwendung im Template: {if isWeekend($myDate)}Wochenende!{/if} ``` +Weitere Informationen finden Sie in den Abschnitten [Erstellen benutzerdefinierter Filter |custom-filters] und [Funktionen |custom-functions]. -Kontextuelle Filter .[#toc-contextual-filters] ----------------------------------------------- -Ein kontextbezogener Filter akzeptiert als ersten Parameter ein Objekt [api:Latte\Runtime\FilterInfo], gefolgt von weiteren Parametern wie bei klassischen Filtern. Er wird auf die gleiche Weise registriert, wobei Latte selbst erkennt, dass es sich um einen kontextuellen Filter handelt: +Robuster Weg: Latte Extension .{toc: Latte Extension} +===================================================== -```php -use Latte\Runtime\FilterInfo; +Während die direkte Registrierung einfach ist, ist der standardmäßige und empfohlene Weg, Latte-Erweiterungen zu verpacken und zu verteilen, über **Extension**-Klassen. Eine Extension dient als zentraler Konfigurationspunkt für die Registrierung mehrerer Tags, Filter, Funktionen, Kompilierungsdurchläufe und anderer Elemente. -$latte->addFilter('foo', function (FilterInfo $info, string $str): string { - // ... -}); -``` +Warum Extensions verwenden? -Kontextfilter können den Inhaltstyp erkennen und ändern, den sie in der Variable `$info->contentType` erhalten. Wird der Filter klassisch über eine Variable (z.B. `{$var|foo}`) aufgerufen, enthält `$info->contentType` null. +- **Organisation:** Hält zusammengehörige Erweiterungen (Tags, Filter usw. für eine bestimmte Funktion) in einer Klasse zusammen. +- **Wiederverwendbarkeit und Teilen:** Verpacken Sie Ihre Erweiterungen einfach zur Verwendung in anderen Projekten oder zum Teilen mit der Community (z. B. über Composer). +- **Volle Leistung:** Benutzerdefinierte Tags und Kompilierungsdurchläufe *können nur* über Extensions registriert werden. -Der Filter sollte zunächst prüfen, ob der Inhaltstyp der Eingabezeichenkette unterstützt wird. Er kann ihn auch ändern. Beispiel für einen Filter, der Text (oder Null) akzeptiert und HTML zurückgibt: + +Registrieren einer Extension +---------------------------- + +Eine Extension wird bei Latte mit der Methode `addExtension()` registriert (oder über die [Konfigurationsdatei |application:configuration#Latte-Templates]): ```php -use Latte\Runtime\FilterInfo; - -$latte->addFilter('money', function (FilterInfo $info, float $amount): string { - // zuerst prüfen wir, ob der Inhaltstyp der Eingabe Text ist - if (!in_array($info->contentType, [null, ContentType::Text])) { - throw new Exception("Filter |money in inkompatiblem Inhaltstyp $info->contentType verwendet."); - } - - // Inhaltstyp in HTML ändern - $info->contentType = ContentType::Html; - return "$num Kč"; -}); +$latte = new Latte\Engine; +$latte->addExtension(new MyProjectExtension); ``` -.[note] -In diesem Fall muss der Filter das korrekte Escaping der Daten sicherstellen. +Wenn Sie mehrere Erweiterungen registrieren und diese gleichnamige Tags, Filter oder Funktionen definieren, hat die zuletzt hinzugefügte Erweiterung Vorrang. Das bedeutet auch, dass Ihre Erweiterungen native Tags/Filter/Funktionen überschreiben können. -Alle Filter, die über [Blöcke |tags#block] verwendet werden (z.B. als `{block|foo}...{/block}`) müssen kontextabhängig sein. +Wann immer Sie eine Änderung an der Klasse vornehmen und die automatische Aktualisierung nicht deaktiviert ist, kompiliert Latte Ihre Templates automatisch neu. -Funktionen .{data-version:2.6}[#toc-functions] -============================================== +Erstellen einer Extension +------------------------- -Standardmäßig können alle nativen PHP-Funktionen in Latte verwendet werden, es sei denn, die Sandbox schaltet sie ab. Sie können aber auch Ihre eigenen Funktionen definieren. Diese können die nativen Funktionen außer Kraft setzen. +Um eine benutzerdefinierte Erweiterung zu erstellen, müssen Sie eine Klasse erstellen, die von [api:Latte\Extension] erbt. Um eine Vorstellung davon zu bekommen, wie eine solche Erweiterung aussieht, schauen Sie sich die integrierte [CoreExtension |https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php] an. -Erstellen Sie eine Funktion, indem Sie ihren Namen und eine beliebige PHP-Aufrufmöglichkeit registrieren: +Sehen wir uns die Methoden an, die Sie implementieren können: -```php -$latte = new Latte\Engine; -$latte->addFunction('random', function (...$args) { - return $args[array_rand($args)]; -}); -``` -Die Verwendung ist dann dieselbe wie beim Aufruf der PHP-Funktion: +beforeCompile(Latte\Engine $engine): void .[method] +--------------------------------------------------- -```latte -{random(apple, orange, lemon)} // prints for example: apple -``` +Wird vor der Kompilierung des Templates aufgerufen. Die Methode kann beispielsweise für Initialisierungen im Zusammenhang mit der Kompilierung verwendet werden. -Funktionen, die die Klasse verwenden .[#toc-functions-using-the-class] ----------------------------------------------------------------------- +getTags(): array .[method] +-------------------------- -Die zweite Möglichkeit, eine Funktion zu definieren, ist die [Verwendung der Klasse |develop#Parameters as a class]. Wir erstellen eine Methode mit dem Attribut `TemplateFunction`: +Wird bei der Kompilierung des Templates aufgerufen. Gibt ein assoziatives Array *Tag-Name => Callable-Objekt* zurück, wobei es sich um Funktionen zum Parsen von Tags handelt. [Weitere Informationen |custom-tags]. ```php -class TemplateParameters +public function getTags(): array { - public function __construct( - // parameters - ) {} - - #[Latte\Attributes\TemplateFunction] - public function random(...$args) - { - return $args[array_rand($args)]; - } + return [ + 'foo' => FooNode::create(...), + 'bar' => BarNode::create(...), + 'n:baz' => NBazNode::create(...), + // ... + ]; } - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); ``` -Wenn Sie PHP 7.x und Latte 2.x verwenden, benutzen Sie die `/** @function */` Annotation anstelle des Attributs. +Das Tag `n:baz` stellt ein reines [n:Attribut |syntax#n:Attribute] dar, also ein Tag, das nur als Attribut geschrieben werden kann. +Bei den Tags `foo` und `bar` erkennt Latte automatisch, ob es sich um paarweise Tags handelt, und wenn ja, können sie automatisch mit n:Attributen geschrieben werden, einschließlich Varianten mit Präfixen `n:inner-foo` und `n:tag-foo`. -Lader .[#toc-loaders] -===================== +Die Ausführungsreihenfolge solcher n:Attribute wird durch ihre Reihenfolge im von der `getTags()`-Methode zurückgegebenen Array bestimmt. `n:foo` wird also immer vor `n:bar` ausgeführt, auch wenn die Attribute im HTML-Tag in umgekehrter Reihenfolge als `
                                      ` angegeben sind. -Loader sind für das Laden von Vorlagen aus einer Quelle, z. B. einem Dateisystem, zuständig. Sie werden mit der Methode `setLoader()` festgelegt: +Wenn Sie die Reihenfolge von n:Attributen über mehrere Erweiterungen hinweg bestimmen müssen, verwenden Sie die Hilfsmethode `order()`, wobei der Parameter `before` xor `after` angibt, welche Tags vor oder nach dem Tag sortiert werden. ```php -$latte->setLoader(new MyLoader); +public function getTags(): array +{ + return [ + 'foo' => self::order(FooNode::create(...), before: 'bar'), + 'bar' => self::order(BarNode::create(...), after: ['block', 'snippet']), + ]; +} ``` -Die eingebauten Lader sind: +getPasses(): array .[method] +---------------------------- -FileLoader .[#toc-fileloader] ------------------------------ +Wird bei der Kompilierung des Templates aufgerufen. Gibt ein assoziatives Array *Durchlaufname => Callable-Objekt* zurück, wobei es sich um Funktionen handelt, die sogenannte [Kompilierungsdurchläufe |compiler-passes] darstellen, die den AST durchlaufen und modifizieren. -Standard-Lader. Lädt Vorlagen aus dem Dateisystem. - -Der Zugriff auf Dateien kann durch die Angabe des Basisverzeichnisses eingeschränkt werden: +Auch hier kann die Hilfsmethode `order()` verwendet werden. Der Wert der Parameter `before` oder `after` kann `*` mit der Bedeutung vor/nach allen sein. ```php -$latte->setLoader(new Latte\Loaders\FileLoader($templateDir)); -$latte->render('test.latte'); +public function getPasses(): array +{ + return [ + 'optimize' => Passes::optimizePass(...), + 'sandbox' => self::order($this->sandboxPass(...), before: '*'), + // ... + ]; +} ``` -StringLoader .[#toc-stringloader] ---------------------------------- +beforeRender(Latte\Engine $engine): void .[method] +-------------------------------------------------- -Lädt Vorlagen aus Strings. Dieser Lader ist sehr nützlich für Unit-Tests. Er kann auch für kleine Projekte verwendet werden, bei denen es sinnvoll sein kann, alle Vorlagen in einer einzigen PHP-Datei zu speichern. +Wird vor jedem Rendern des Templates aufgerufen. Die Methode kann beispielsweise zur Initialisierung von Variablen verwendet werden, die während des Renderings verwendet werden. -```php -$latte->setLoader(new Latte\Loaders\StringLoader([ - 'main.file' => '{include other.file}', - 'other.file' => '{if true} {$var} {/if}', -])); -$latte->render('main.file'); -``` +getFilters(): array .[method] +----------------------------- -Vereinfachte Nutzung: +Wird vor dem Rendern des Templates aufgerufen. Gibt Filter als assoziatives Array *Filtername => Callable-Objekt* zurück. [Weitere Informationen |custom-filters]. ```php -$template = '{if true} {$var} {/if}'; -$latte->setLoader(new Latte\Loaders\StringLoader); -$latte->render($template); +public function getFilters(): array +{ + return [ + 'batch' => $this->batchFilter(...), + 'trim' => $this->trimFilter(...), + // ... + ]; +} ``` -Erstellen eines Custom Loader .[#toc-creating-a-custom-loader] --------------------------------------------------------------- - -Loader ist eine Klasse, die die Schnittstelle [api:Latte\Loader] implementiert. +getFunctions(): array .[method] +------------------------------- +Wird vor dem Rendern des Templates aufgerufen. Gibt Funktionen als assoziatives Array *Funktionsname => Callable-Objekt* zurück. [Weitere Informationen |custom-functions]. -Tags .[#toc-tags] -================= +```php +public function getFunctions(): array +{ + return [ + 'clamp' => $this->clampFunction(...), + 'divisibleBy' => $this->divisibleByFunction(...), + // ... + ]; +} +``` -Eine der interessantesten Funktionen der Templating-Engine ist die Möglichkeit, neue Sprachkonstrukte mit Hilfe von Tags zu definieren. Es handelt sich dabei auch um eine komplexere Funktionalität und Sie müssen verstehen, wie Latte intern funktioniert. -In den meisten Fällen wird das Tag jedoch nicht benötigt: -- wenn es eine Ausgabe erzeugen soll, verwenden Sie stattdessen eine [Funktion |#functions] -- wenn es eine Eingabe verändern und zurückgeben soll, verwenden Sie stattdessen [filter |#filters] -- wenn ein Textbereich bearbeitet werden soll, umschließt man ihn mit einem [`{block}` |tags#block] Tag und verwende einen [Filter |#Contextual Filters] -- wenn es nichts ausgeben soll, sondern nur eine Funktion aufrufen soll, rufen Sie sie mit [`{do}` |tags#do] +getProviders(): array .[method] +------------------------------- -Wenn Sie trotzdem ein Tag erstellen wollen, prima! Alles Wesentliche finden Sie unter [Erstellen einer Erweiterung |creating-extension]. +Wird vor dem Rendern des Templates aufgerufen. Gibt ein Array von Providern zurück, bei denen es sich normalerweise um Objekte handelt, die von Tags zur Laufzeit verwendet werden. Der Zugriff erfolgt über `$this->global->...`. [Weitere Informationen |custom-tags#Einführung von Providern]. +```php +public function getProviders(): array +{ + return [ + 'myFoo' => $this->foo, + 'myBar' => $this->bar, + // ... + ]; +} +``` -Compiler-Pässe .{data-version:3.0}[#toc-compiler-passes] -======================================================== -Compiler-Passes sind Funktionen, die ASTs modifizieren oder Informationen in ihnen sammeln. In Latte ist zum Beispiel eine Sandbox auf diese Weise implementiert: Sie durchläuft alle Knoten eines AST, findet Funktions- und Methodenaufrufe und ersetzt sie durch kontrollierte Aufrufe. +getCacheKey(Latte\Engine $engine): mixed .[method] +-------------------------------------------------- -Wie bei den Tags handelt es sich hierbei um eine komplexere Funktionalität, und Sie müssen verstehen, wie Latte unter der Haube funktioniert. Alles Wesentliche dazu finden Sie im Kapitel [Erstellen einer Erweiterung |creating-extension]. +Wird vor dem Rendern des Templates aufgerufen. Der Rückgabewert wird Teil des Schlüssels, dessen Hash im Dateinamen des kompilierten Templates enthalten ist. Für unterschiedliche Rückgabewerte generiert Latte also unterschiedliche Cache-Dateien. diff --git a/latte/de/filters.texy b/latte/de/filters.texy index 7e5f203d51..c73dea6991 100644 --- a/latte/de/filters.texy +++ b/latte/de/filters.texy @@ -1,112 +1,114 @@ -Latte Filtern -************* +Latte-Filter +************ .[perex] -Filter sind Funktionen, die die Daten in der gewünschten Form verändern oder formatieren. Dies ist eine Zusammenfassung der eingebauten Filter, die verfügbar sind. +In Templates können wir Funktionen verwenden, die helfen, Daten in die endgültige Form zu ändern oder neu zu formatieren. Wir nennen sie *Filter*. .[table-latte-filters] -|## String / Array Transformation -| `batch` | [Auflistung linearer Daten in einer Tabelle |#batch] -| `breakLines` | [fügt HTML-Zeilenumbrüche vor allen Zeilenumbrüchen ein |#breakLines] -| `bytes` | [formatiert Größe in Bytes |#bytes] -| `clamp` | [klemmt Wert auf den Bereich |#clamp] -| `dataStream` | [Konvertierung des Daten-URI-Protokolls |#datastream] -| `date` | [formatiert Datum |#date] -| `explode` | [trennt eine Zeichenkette durch das angegebene Trennzeichen auf |#explode] -| `first` | [gibt das erste Element eines Arrays oder ein Zeichen einer Zeichenkette zurück |#first] -| `implode` | [fügt ein Array zu einer Zeichenkette zusammen |#implode] -| `indent` | [rückt den Text von links mit einer Anzahl von Tabulatorenein |#indent] -| `join` | [verbindet ein Array mit einer Zeichenkette|#implode] -| `last` | [gibt das letzte Element eines Arrays oder ein Zeichen einer Zeichenkette zurück |#last] -| `length` | [gibt die Länge einer Zeichenkette oder eines Arrays zurück |#length] -| `number` | [formatiert Zahlen |#number] -| `padLeft` | [vervollständigt die Zeichenkette auf die angegebene Länge von links |#padLeft] -| `padRight` | [vervollständigt die Zeichenkette auf die angegebene Länge von rechts |#padRight] -| `random` | [gibt zufällige Elemente eines Arrays oder Zeichen einer Zeichenkette zurück |#random] -| `repeat` | [Wiederholt die Zeichenkette |#repeat] -| `replace` | [ersetzt alle Vorkommen der gesuchten Zeichenkette durch die Ersetzung |#replace] -| `replaceRE` | [ersetzt alle Vorkommen entsprechend dem regulären Ausdruck |#replaceRE] -| `reverse` | [kehrt eine UTF-8 Zeichenkette oder ein Array um |#reverse] -| `slice` | [extrahiert einen Ausschnitt aus einem Array oder einer Zeichenkette |#slice] -| `sort` | [sortiert ein Array |#sort] -| `spaceless` | [entfernt Leerzeichen |#spaceless], ähnlich dem [spaceless |tags] tag -| `split` | [trennt eine Zeichenkette durch das angegebene Trennzeichen auf |#explode] -| `strip` | [Entfernt Leerzeichen |#spaceless] -| `stripHtml` | [entfernt HTML-Tags und konvertiert HTML-Entities in Text |#stripHtml] -| `substr` | [gibt einen Teil der Zeichenkette zurück |#substr] -| `trim` | [entfernt Leerzeichen aus der Zeichenkette |#trim] -| `translate` | [Übersetzung in andere Sprachen |#translate] -| `truncate` | [verkürzt die Länge unter Beibehaltung ganzer Wörter |#truncate] -| `webalize` | [passt die UTF-8-Zeichenfolge an die in der URL verwendete Forman |#webalize] +|## Transformation +| `batch` | [Ausgabe linearer Daten in einer Tabelle |#batch] +| `breakLines` | [Fügt HTML-Zeilenumbrüche vor Zeilenenden ein |#breakLines] +| `bytes` | [formatiert die Größe in Bytes |#bytes] +| `clamp` | [begrenzt einen Wert auf einen bestimmten Bereich |#clamp] +| `dataStream` | [Konvertierung für das Data-URI-Protokoll |#dataStream] +| `date` | [formatiert Datum und Uhrzeit |#date] +| `explode` | [teilt eine Zeichenkette anhand eines Trennzeichens in ein Array auf |#explode] +| `first` | [gibt das erste Element eines Arrays oder das erste Zeichen einer Zeichenkette zurück |#first] +| `group` | [gruppiert Daten nach verschiedenen Kriterien |#group] +| `implode` | [verbindet ein Array zu einer Zeichenkette |#implode] +| `indent` | [rückt Text von links um eine bestimmte Anzahl von Tabulatoren ein |#indent] +| `join` | [verbindet ein Array zu einer Zeichenkette |#implode] +| `last` | [gibt das letzte Element eines Arrays oder das letzte Zeichen einer Zeichenkette zurück |#last] +| `length` | [gibt die Länge einer Zeichenkette in Zeichen oder eines Arrays zurück |#length] +| `localDate` | [formatiert Datum und Uhrzeit gemäß der Spracheinstellung |#localDate] +| `number` | [formatiert eine Zahl |#number] +| `padLeft` | [füllt eine Zeichenkette von links auf die gewünschte Länge auf |#padLeft] +| `padRight` | [füllt eine Zeichenkette von rechts auf die gewünschte Länge auf |#padRight] +| `random` | [gibt ein zufälliges Element eines Arrays oder ein zufälliges Zeichen einer Zeichenkette zurück |#random] +| `repeat` | [Wiederholung einer Zeichenkette |#repeat] +| `replace` | [ersetzt Vorkommen einer gesuchten Zeichenkette |#replace] +| `replaceRE` | [ersetzt Vorkommen gemäß einem regulären Ausdruck |#replaceRE] +| `reverse` | [kehrt eine UTF-8-Zeichenkette oder ein Array um |#reverse] +| `slice` | [extrahiert einen Teil eines Arrays oder einer Zeichenkette |#slice] +| `sort` | [sortiert ein Array |#sort] +| `spaceless` | [entfernt Leerzeichen |#spaceless], ähnlich wie der [spaceless |tags] Tag +| `split` | [teilt eine Zeichenkette anhand eines Trennzeichens in ein Array auf |#explode] +| `strip` | [entfernt Leerzeichen |#spaceless] +| `stripHtml` | [entfernt HTML-Tags und konvertiert HTML-Entitäten in Zeichen |#stripHtml] +| `substr` | [gibt einen Teil einer Zeichenkette zurück |#substr] +| `trim` | [entfernt führende und nachfolgende Leerzeichen oder andere Zeichen |#trim] +| `translate` | [Übersetzung in andere Sprachen |#translate] +| `truncate` | [kürzt die Länge unter Beibehaltung von Wörtern |#truncate] +| `webalize` | [wandelt eine UTF-8-Zeichenkette in eine in URLs verwendete Form um |#webalize] .[table-latte-filters] -|## Buchstabenumbruch -| `capitalize` | [Kleinschreibung, der erste Buchstabe eines jeden Wortes wird großgeschrieben |#capitalize] -| `firstUpper` | [macht den ersten Buchstaben zu einem Großbuchstaben |#firstUpper] -| `lower` | [macht eine Zeichenfolge klein |#lower] -| `upper` | [macht eine Zeichenkette zu einem Großbuchstaben |#upper] +|## Groß-/Kleinschreibung +| `capitalize` | [Kleinbuchstaben, erster Buchstabe in Wörtern groß |#capitalize] +| `firstUpper` | [wandelt den ersten Buchstaben in einen Großbuchstaben um |#firstUpper] +| `lower` | [wandelt in Kleinbuchstaben um |#lower] +| `upper` | [wandelt in Großbuchstaben um |#upper] .[table-latte-filters] -|## Rundung von Zahlen -| `ceil` | [rundet eine Zahl auf eine bestimmte Genauigkeit auf|#ceil] -| `floor` | [rundet eine Zahl auf eine bestimmte Genauigkeit ab |#floor] -| `round` | [rundet eine Zahl auf eine bestimmte Genauigkeit|#round] +|## Runden +| `ceil` | [rundet eine Zahl auf die angegebene Genauigkeit auf |#ceil] +| `floor` | [rundet eine Zahl auf die angegebene Genauigkeit ab |#floor] +| `round` | [rundet eine Zahl auf die angegebene Genauigkeit |#round] .[table-latte-filters] |## Escaping -| `escapeUrl` | [gibt einen Parameter in der URL als Escapezeichen aus|#escapeUrl] -| `noescape` | [druckt eine Variable ohne Escaping |#noescape] -| `query` | [erzeugt eine Abfragezeichenfolge in der URL |#query] +| `escapeUrl` | [escapet einen Parameter in einer URL |#escapeUrl] +| `noescape` | [gibt eine Variable ohne Escaping aus |#noescape] +| `query` | [generiert einen Query-String in einer URL |#query] -Es gibt auch Escape-Filter für HTML (`escapeHtml` und `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) und iCalendar (`escapeICal`), die Latte dank [kontextsensitivem Escaping |safety-first#Context-aware escaping] selbst verwendet und die Sie nicht schreiben müssen. +Darüber hinaus gibt es Escaping-Filter für HTML (`escapeHtml` und `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) und iCalendar (`escapeICal`), die Latte dank [kontextsensitivem Escaping |safety-first#Kontextsensitives Escaping] selbst verwendet und die Sie nicht schreiben müssen. .[table-latte-filters] |## Sicherheit -| `checkUrl` | [säubert Zeichenketten für die Verwendung im href-Attribut |#checkUrl] -| `nocheck` | [verhindert automatische URL-Sanitisierung |#nocheck] +| `checkUrl` | [bereinigt eine URL-Adresse von gefährlichen Eingaben |#checkUrl] +| `nocheck` | [verhindert die automatische Bereinigung einer URL-Adresse |#nocheck] -Latte der `src` und `href` Attribute [prüft automatisch |safety-first#link checking], so dass Sie den `checkUrl` Filter fast nicht verwenden müssen. +Latte prüft die Attribute `src` und `href` [automatisch |safety-first#Überprüfung von Links], sodass Sie den Filter `checkUrl` fast nie verwenden müssen. .[note] -Alle eingebauten Filter arbeiten mit UTF-8 kodierten Zeichenketten. +Alle Standardfilter sind für Zeichenketten in UTF-8-Kodierung vorgesehen. -Verwendung .[#toc-usage] -======================== +Verwendung +========== -Latte erlaubt den Aufruf von Filtern unter Verwendung der Pipe-Schreibweise (vorangestelltes Leerzeichen ist erlaubt): +Filter werden nach einem senkrechten Strich geschrieben (ein Leerzeichen davor ist erlaubt): ```latte

                                      {$heading|upper}

                                      ``` -Filter können verkettet werden, in diesem Fall gelten sie in der Reihenfolge von links nach rechts: +Filter (in älteren Versionen Helfer genannt) können verkettet werden und werden dann in der Reihenfolge von links nach rechts angewendet: ```latte

                                      {$heading|lower|capitalize}

                                      ``` -Die Parameter werden durch Doppelpunkt oder Komma getrennt hinter den Filternamen gesetzt: +Parameter werden nach dem Filternamen, getrennt durch Doppelpunkte oder Kommas, angegeben: ```latte

                                      {$heading|truncate:20,''}

                                      ``` -Filter können auf Ausdrücke angewendet werden: +Filter können auch auf einen Ausdruck angewendet werden: ```latte {var $name = ($title|upper) . ($subtitle|lower)} ``` -[Benutzerdefinierte Filter |extending-latte#filters] können auf diese Weise registriert werden: +[Benutzerdefinierte Filter |custom-filters] können auf diese Weise registriert werden: ```php $latte = new Latte\Engine; $latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -Wir verwenden sie in einer Vorlage wie dieser: +Im Template wird er dann so aufgerufen: ```latte

                                      {$text|shortify}

                                      @@ -114,13 +116,13 @@ Wir verwenden sie in einer Vorlage wie dieser: ``` -Filter .[#toc-filters] -====================== +Filter +====== -batch(int length, mixed item): array .[filter]{data-version:2.7} ----------------------------------------------------------------- -Filter, der die Auflistung von linearen Daten in Form einer Tabelle vereinfacht. Er gibt ein Array von Arrays mit der angegebenen Anzahl von Elementen zurück. Wenn Sie einen zweiten Parameter angeben, wird dieser verwendet, um fehlende Elemente in der letzten Zeile aufzufüllen. +batch(int $length, mixed $item): array .[filter] +------------------------------------------------ +Ein Filter, der die Ausgabe linearer Daten in Tabellenform vereinfacht. Gibt ein Array von Arrays mit der angegebenen Anzahl von Elementen zurück. Wenn Sie einen zweiten Parameter angeben, wird dieser verwendet, um fehlende Elemente in der letzten Zeile zu ergänzen. ```latte {var $items = ['a', 'b', 'c', 'd', 'e']} @@ -135,7 +137,7 @@ Filter, der die Auflistung von linearen Daten in Form einer Tabelle vereinfacht. ``` -Druckt: +Gibt aus: ```latte @@ -152,20 +154,22 @@ Druckt:
                                      ``` +Siehe auch [#group] und der Tag [iterateWhile |tags#iterateWhile]. + breakLines .[filter] -------------------- -Fügt HTML-Zeilenumbrüche vor allen Zeilenumbrüchen ein. +Fügt vor jedem Zeilenumbruchzeichen das HTML-Tag `
                                      ` ein. ```latte {var $s = "Text & with \n newline"} -{$s|breakLines} {* gibt "Text & with
                                      \n newline" *} +{$s|breakLines} {* gibt aus "Text & with
                                      \n newline" *} ``` -bytes(int precision = 2) .[filter] ----------------------------------- -Formatiert eine Größe in Bytes in eine für Menschen lesbare Form. +bytes(int $precision=2) .[filter] +--------------------------------- +Formatiert die Größe in Bytes in eine für Menschen lesbare Form. Wenn die [Spracheinstellung |develop#Locale] gesetzt ist, werden die entsprechenden Dezimal- und Tausendertrennzeichen verwendet. ```latte {$size|bytes} 0 B, 1.25 GB, … @@ -173,53 +177,53 @@ Formatiert eine Größe in Bytes in eine für Menschen lesbare Form. ``` -ceil(int precision = 0) .[filter] ---------------------------------- -Rundet eine Zahl bis zu einer bestimmten Genauigkeit. +ceil(int $precision=0) .[filter] +-------------------------------- +Rundet eine Zahl auf die angegebene Genauigkeit auf. ```latte -{=3.4|ceil} {* gibt 4 *} -{=135.22|ceil:1} {* gibt 135.3 *} -{=135.22|ceil:3} {* gibt 135.22 *} +{=3.4|ceil} {* gibt 4 aus *} +{=135.22|ceil:1} {* gibt 135.3 aus *} +{=135.22|ceil:3} {* gibt 135.22 aus *} ``` -Siehe auch [Stockwerk |#floor], [Runden |#round]. +Siehe auch [#floor], [#round]. capitalize .[filter] -------------------- -Gibt eine Version des Wertes in Großbuchstaben zurück. Die Wörter beginnen mit Großbuchstaben, alle übrigen Zeichen sind Kleinbuchstaben. Erfordert die PHP-Erweiterung `mbstring`. +Wörter beginnen mit Großbuchstaben, alle übrigen Zeichen sind Kleinbuchstaben. Erfordert die PHP-Erweiterung `mbstring`. ```latte -{='i like LATTE'|capitalize} {* gibt 'I Like Latte' *} +{='i like LATTE'|capitalize} {* gibt 'I Like Latte' aus *} ``` -Siehe auch [firstUpper |#firstUpper], [lower |#lower], [upper |#upper]. +Siehe auch [#firstUpper], [#lower], [#upper]. checkUrl .[filter] ------------------ -Erzwingt URL-Sanitization. Sie prüft, ob die Variable eine Web-URL enthält (d. h. HTTP/HTTPS-Protokoll) und verhindert das Schreiben von Links, die ein Sicherheitsrisiko darstellen könnten. +Erzwingt die Bereinigung einer URL-Adresse. Überprüft, ob die Variable eine Web-URL enthält (d. h. das HTTP/HTTPS-Protokoll) und verhindert die Ausgabe von Links, die ein Sicherheitsrisiko darstellen können. ```latte {var $link = 'javascript:window.close()'} -checked -unchecked +kontrolliert +unkontrolliert ``` -Druckt: +Gibt aus: ```latte -checked -unchecked +kontrolliert +unkontrolliert ``` -Siehe auch [nocheck |#nocheck]. +Siehe auch [#nocheck]. -clamp(int|float min, int|float max) .[filter]{data-version:2.9} ---------------------------------------------------------------- -Gibt einen Wert zurück, der auf den einschließenden Bereich von min und max geklemmt ist. +clamp(int|float $min, int|float $max) .[filter] +----------------------------------------------- +Begrenzt einen Wert auf den angegebenen inklusiven Bereich von min und max. ```latte {$level|clamp: 0, 255} @@ -228,17 +232,17 @@ Gibt einen Wert zurück, der auf den einschließenden Bereich von min und max ge Existiert auch als [Funktion |functions#clamp]. -dataStream(string mimetype = detect) .[filter] ----------------------------------------------- -Konvertiert den Inhalt in ein Daten-URI-Schema. Es kann verwendet werden, um Bilder in HTML oder CSS einzufügen, ohne dass externe Dateien verlinkt werden müssen. +dataStream(string $mimetype=detect) .[filter] +--------------------------------------------- +Konvertiert den Inhalt in das data URI scheme. Damit können Bilder in HTML oder CSS eingebettet werden, ohne externe Dateien verlinken zu müssen. -Nehmen wir an, ein Bild befindet sich in einer Variablen `$img = Image::fromFile('obrazek.gif')`, dann +Nehmen wir an, wir haben ein Bild in der Variablen `$img = Image::fromFile('bild.gif')`, dann ```latte - + ``` -Druckt zum Beispiel: +Gibt beispielsweise aus: ```latte {$name} ``` -Siehe auch [Abfrage |#query]. +Siehe auch [#query]. -explode(string separator = '') .[filter]{data-version:2.10.2} -------------------------------------------------------------- -Teilt eine Zeichenkette durch den angegebenen Begrenzer und gibt ein Array von Zeichenketten zurück. Alias für `split`. +explode(string $separator='') .[filter] +--------------------------------------- +Teilt eine Zeichenkette anhand eines Trennzeichens in ein Array auf. Alias für `split`. ```latte -{='one,two,three'|explode:','} {* liefert ['one', 'two', 'three'] *} +{='one,two,three'|explode:','} {* gibt ['one', 'two', 'three'] zurück *} ``` Wenn das Trennzeichen eine leere Zeichenkette ist (Standardwert), wird die Eingabe in einzelne Zeichen aufgeteilt: ```latte -{='123'|explode} {* liefert ['1', '2', '3'] *} +{='123'|explode} {* gibt ['1', '2', '3'] zurück *} ``` Sie können auch den Alias `split` verwenden: ```latte -{='1,2,3'|split:','} {* liefert ['1', '2', '3'] *} +{='1,2,3'|split:','} {* gibt ['1', '2', '3'] zurück *} ``` -Siehe auch [implode |#implode]. +Siehe auch [#implode]. -first .[filter]{data-version:2.10.2} ------------------------------------- -Gibt das erste Element eines Arrays oder ein Zeichen einer Zeichenkette zurück: +first .[filter] +--------------- +Gibt das erste Element eines Arrays oder das erste Zeichen einer Zeichenkette zurück: ```latte -{=[1, 2, 3, 4]|first} {* gibt 1 *} -{='abcd'|first} {* gibt 'a' *} +{=[1, 2, 3, 4]|first} {* gibt 1 aus *} +{='abcd'|first} {* gibt 'a' aus *} ``` -Siehe auch [last |#last], [random |#random]. +Siehe auch [#last], [#random]. -floor(int precision = 0) .[filter] ----------------------------------- -Rundet eine Zahl auf eine bestimmte Genauigkeit ab. +floor(int $precision=0) .[filter] +--------------------------------- +Rundet eine Zahl auf die angegebene Genauigkeit ab. ```latte -{=3.5|floor} {* gibt 3 *} -{=135.79|floor:1} {* gibt 135.7 *} -{=135.79|floor:3} {* gibt 135.79 *} +{=3.5|floor} {* gibt 3 aus *} +{=135.79|floor:1} {* gibt 135.7 aus *} +{=135.79|floor:3} {* gibt 135.79 aus *} ``` -Siehe auch [ceil |#ceil], [round |#round]. +Siehe auch [#ceil], [#round]. firstUpper .[filter] -------------------- -Konvertiert den ersten Buchstaben eines Wertes in Großbuchstaben. Erfordert die PHP-Erweiterung `mbstring`. +Wandelt den ersten Buchstaben in einen Großbuchstaben um. Erfordert die PHP-Erweiterung `mbstring`. ```latte -{='the latte'|firstUpper} {* gibt 'The latte' *} +{='the latte'|firstUpper} {* gibt 'The latte' aus *} ``` -Siehe auch [capitalize |#capitalize], [lower |#lower], [upper |#upper]. +Siehe auch [#capitalize], [#lower], [#upper]. -implode(string glue = '') .[filter] ------------------------------------ -Gibt eine Zeichenkette zurück, die die Verkettung der Zeichenketten im Array ist. Alias für `join`. +group(string|int|\Closure $by): array .[filter]{data-version:3.0.16} +-------------------------------------------------------------------- +Der Filter gruppiert Daten nach verschiedenen Kriterien. + +In diesem Beispiel werden die Zeilen in der Tabelle nach der Spalte `categoryId` gruppiert. Die Ausgabe ist ein Array von Arrays, wobei der Schlüssel der Wert in der Spalte `categoryId` ist. [Lesen Sie die detaillierte Anleitung |cookbook/grouping]. ```latte -{=[1, 2, 3]|implode} {* gibt '123' *} -{=[1, 2, 3]|implode:'|'} {* gibt '1|2|3' *} +{foreach ($items|group: categoryId) as $categoryId => $categoryItems} +
                                        + {foreach $categoryItems as $item} +
                                      • {$item->name}
                                      • + {/foreach} +
                                      +{/foreach} ``` -Sie können auch einen Alias `join` verwenden: .{data-version:2.10.2} +Siehe auch [#batch], Funktion [group |functions#group] und Tag [iterateWhile |tags#iterateWhile]. + + +implode(string $glue='') .[filter] +---------------------------------- +Gibt eine Zeichenkette zurück, die die Verkettung der Elemente einer Sequenz ist. Alias für `join`. ```latte -{=[1, 2, 3]|join} {* gibt '123' *} +{=[1, 2, 3]|implode} {* gibt '123' aus *} +{=[1, 2, 3]|implode:'|'} {* gibt '1|2|3' aus *} ``` +Sie können auch den Alias `join` verwenden: + +```latte +{=[1, 2, 3]|join} {* gibt '123' aus *} +``` -indent(int level = 1, string char = "\t") .[filter] ---------------------------------------------------- -Rückt einen Text von links um eine bestimmte Anzahl von Tabulatoren oder anderen Zeichen ein, die wir im zweiten optionalen Argument angeben. Leerzeilen werden nicht eingerückt. + +indent(int $level=1, string $char="\t") .[filter] +------------------------------------------------- +Rückt Text von links um die angegebene Anzahl von Tabulatoren oder anderen Zeichen ein, die im zweiten Argument angegeben werden können. Leere Zeilen werden nicht eingerückt. ```latte
                                      {block |indent} -

                                      Hello

                                      +

                                      Hallo

                                      {/block}
                                      ``` -Druckt: +Gibt aus: ```latte
                                      -

                                      Hello

                                      +

                                      Hallo

                                      ``` -last .[filter]{data-version:2.10.2} ------------------------------------ -Gibt das letzte Element eines Arrays oder ein Zeichen einer Zeichenkette zurück: +last .[filter] +-------------- +Gibt das letzte Element eines Arrays oder das letzte Zeichen einer Zeichenkette zurück: ```latte -{=[1, 2, 3, 4]|last} {* gibt 4 *} -{='abcd'|last} {* gibt 'd' *} +{=[1, 2, 3, 4]|last} {* gibt 4 aus *} +{='abcd'|last} {* gibt 'd' aus *} ``` -Siehe auch [first |#first], [random |#random]. +Siehe auch [#first], [#random]. length .[filter] ---------------- Gibt die Länge einer Zeichenkette oder eines Arrays zurück. -- bei Strings wird die Länge in UTF-8 Zeichen zurückgegeben -- für Arrays wird die Anzahl der Elemente zurückgegeben -- bei Objekten, die die Schnittstelle Countable implementieren, wird der Rückgabewert der Funktion count() verwendet -- für Objekte, die die Schnittstelle IteratorAggregate implementieren, wird der Rückgabewert von iterator_count() verwendet. +- für Zeichenketten gibt die Länge in UTF-8-Zeichen zurück +- für Arrays gibt die Anzahl der Elemente zurück +- für Objekte, die das Interface Countable implementieren, verwendet den Rückgabewert der Methode count() +- für Objekte, die das Interface IteratorAggregate implementieren, verwendet den Rückgabewert der Funktion iterator_count() ```latte @@ -396,38 +420,100 @@ Gibt die Länge einer Zeichenkette oder eines Arrays zurück. ``` +localDate(?string $format=null, ?string $date=null, ?string $time=null) .[filter] +--------------------------------------------------------------------------------- +Formatiert Datum und Uhrzeit gemäß der [Spracheinstellung |develop#Locale], was eine konsistente und lokalisierte Anzeige von Zeitangaben über verschiedene Sprachen und Regionen hinweg gewährleistet. Der Filter akzeptiert das Datum als UNIX-Timestamp, Zeichenkette oder Objekt vom Typ `DateTimeInterface`. + +```latte +{$date|localDate} {* 15. April 2024 *} +{$date|localDate: format: yM} {* 4/2024 *} +{$date|localDate: date: medium} {* 15. 4. 2024 *} +``` + +Wenn Sie den Filter ohne Parameter verwenden, wird das Datum im `long`-Format ausgegeben, siehe unten. + +**a) Verwendung des Formats** + +Der Parameter `format` beschreibt, welche Zeitkomponenten angezeigt werden sollen. Er verwendet dafür Buchstabencodes, deren Wiederholungsanzahl die Breite der Ausgabe beeinflusst: + +| Jahr | `y` / `yy` / `yyyy` | `2024` / `24` / `2024` +| Monat | `M` / `MM` / `MMM` / `MMMM` | `8` / `08` / `Aug` / `August` +| Tag | `d` / `dd` / `E` / `EEEE` | `1` / `01` / `So` / `Sonntag` +| Stunde | `j` / `H` / `h` | bevorzugt / 24-Stunden / 12-Stunden +| Minute | `m` / `mm` | `5` / `05` (2 Ziffern in Kombination mit Sekunden) +| Sekunde | `s` / `ss` | `8` / `08` (2 Ziffern in Kombination mit Minuten) + +Die Reihenfolge der Codes im Format spielt keine Rolle, da die Reihenfolge der Komponenten gemäß den Gepflogenheiten der Spracheinstellung ausgegeben wird. Das Format ist also davon unabhängig. Zum Beispiel gibt das Format `yyyyMMMMd` in der Umgebung `en_US` `April 15, 2024` aus, während es in der Umgebung `de_DE` `15. April 2024` ausgibt: + +| locale: | de_DE | en_US +|--- +| `format: 'dMy'` | 10. 8. 2024 | 8/10/2024 +| `format: 'yM'` | 8/2024 | 8/2024 +| `format: 'yyyyMMMM'` | August 2024 | August 2024 +| `format: 'MMMM'` | August | August +| `format: 'jm'` | 17:22 | 5:22 PM +| `format: 'Hm'` | 17:22 | 17:22 +| `format: 'hm'` | 5:22 nachm. | 5:22 PM + + +**b) Verwendung vordefinierter Stile** + +Die Parameter `date` und `time` bestimmen, wie detailliert Datum und Uhrzeit ausgegeben werden sollen. Sie können aus mehreren Stufen wählen: `full`, `long`, `medium`, `short`. Es kann nur das Datum, nur die Uhrzeit oder beides ausgegeben werden: + +| locale: | de_DE | en_US +|--- +| `date: short` | 23.01.78 | 1/23/78 +| `date: medium` | 23.01.1978 | Jan 23, 1978 +| `date: long` | 23. Januar 1978 | January 23, 1978 +| `date: full` | Montag, 23. Januar 1978 | Monday, January 23, 1978 +| `time: short` | 08:30 | 8:30 AM +| `time: medium` | 08:30:59 | 8:30:59 AM +| `time: long` | 08:30:59 MEZ | 8:30:59 AM GMT+1 +| `date: short, time: short` | 23.01.78, 08:30 | 1/23/78, 8:30 AM +| `date: medium, time: short` | 23.01.1978, 08:30 | Jan 23, 1978, 8:30 AM +| `date: long, time: short` | 23. Januar 1978 um 08:30 | January 23, 1978 at 8:30 AM + +Beim Datum kann zusätzlich das Präfix `relative-` verwendet werden (z. B. `relative-short`), das für Daten nahe der Gegenwart `gestern`, `heute` oder `morgen` anzeigt, ansonsten wird es standardmäßig ausgegeben. + +```latte +{$date|localDate: date: relative-short} {* gestern *} +``` + +Siehe auch [#date]. + + lower .[filter] --------------- -Konvertiert einen Wert in Kleinbuchstaben. Erfordert die PHP-Erweiterung `mbstring`. +Wandelt eine Zeichenkette in Kleinbuchstaben um. Erfordert die PHP-Erweiterung `mbstring`. ```latte -{='LATTE'|lower} {* gibt 'latte' *} +{='LATTE'|lower} {* gibt 'latte' aus *} ``` -Siehe auch [capitalize |#capitalize], [firstUpper |#firstUpper], [upper |#upper]. +Siehe auch [#capitalize], [#firstUpper], [#upper]. nocheck .[filter] ----------------- -Verhindert die automatische URL-Sanitization. Latte [prüft automatisch |safety-first#Link checking], ob die Variable eine Web-URL enthält (d.h. HTTP/HTTPS-Protokoll) und verhindert das Schreiben von Links, die ein Sicherheitsrisiko darstellen könnten. +Verhindert die automatische Bereinigung einer URL-Adresse. Latte [prüft automatisch |safety-first#Überprüfung von Links], ob die Variable eine Web-URL enthält (d. h. das HTTP/HTTPS-Protokoll) und verhindert die Ausgabe von Links, die ein Sicherheitsrisiko darstellen können. -Wenn der Link ein anderes Schema verwendet, z. B. `javascript:` oder `data:`, und Sie sich des Inhalts sicher sind, können Sie die Prüfung über `|nocheck` deaktivieren. +Wenn der Link ein anderes Schema verwendet, z. B. `javascript:` oder `data:`, und Sie sich seines Inhalts sicher sind, können Sie die Überprüfung mit `|nocheck` deaktivieren. ```latte {var $link = 'javascript:window.close()'} -checked -unchecked +kontrolliert +unkontrolliert ``` -Drucke: +Gibt aus: ```latte -checked -unchecked +kontrolliert +unkontrolliert ``` -Siehe auch [checkUrl |#checkUrl]. +Siehe auch [#checkUrl]. noescape .[filter] @@ -435,165 +521,213 @@ noescape .[filter] Deaktiviert das automatische Escaping. ```latte -{var $trustedHtmlString = 'hello'} +{var $trustedHtmlString = 'hallo'} Escaped: {$trustedHtmlString} Unescaped: {$trustedHtmlString|noescape} ``` -Druckt: +Gibt aus: ```latte -Escaped: <b>hello</b> -Unescaped: hello +Escaped: <b>hallo</b> +Unescaped: hallo ``` .[warning] -Die missbräuchliche Verwendung des `noescape` Filters kann zu einer XSS-Schwachstelle führen! Verwenden Sie ihn nur, wenn Sie **absolut sicher** sind, was Sie tun und dass die Zeichenfolge, die Sie drucken, aus einer vertrauenswürdigen Quelle stammt. +Eine falsche Verwendung des `noescape`-Filters kann zu einer XSS-Schwachstelle führen! Verwenden Sie ihn niemals, wenn Sie nicht **absolut sicher** sind, was Sie tun, und dass die ausgegebene Zeichenkette aus einer vertrauenswürdigen Quelle stammt. -number(int decimals = 0, string decPoint = '.', string thousandsSep = ',') .[filter] ------------------------------------------------------------------------------------- -Formatiert eine Zahl auf eine bestimmte Anzahl von Dezimalstellen. Sie können auch ein Zeichen für den Dezimalpunkt und das Tausendertrennzeichen angeben. +number(int $decimals=0, string $decPoint='.', string $thousandsSep=',') .[filter] +--------------------------------------------------------------------------------- +Formatiert eine Zahl auf eine bestimmte Anzahl von Dezimalstellen. Wenn die [Spracheinstellung |develop#Locale] gesetzt ist, werden die entsprechenden Dezimal- und Tausendertrennzeichen verwendet. ```latte -{1234.20 |number} 1,234 -{1234.20 |number:1} 1,234.2 -{1234.20 |number:2} 1,234.20 -{1234.20 |number:2, ',', ' '} 1 234,20 +{1234.20|number} 1,234 +{1234.20|number:1} 1,234.2 +{1234.20|number:2} 1,234.20 +{1234.20|number:2, ',', ' '} 1 234,20 ``` -padLeft(int length, string pad = ' ') .[filter] +number(string $format) .[filter] +-------------------------------- +Der Parameter `format` ermöglicht es, das Erscheinungsbild von Zahlen genau nach Ihren Bedürfnissen zu definieren. Dazu muss die [Spracheinstellung |develop#Locale] gesetzt sein. Das Format besteht aus mehreren Sonderzeichen, deren vollständige Beschreibung Sie in der Dokumentation "DecimalFormat":https://unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns finden: + +- `0` obligatorische Ziffer, wird immer angezeigt, auch wenn es eine Null ist +- `#` optionale Ziffer, wird nur angezeigt, wenn an dieser Stelle tatsächlich eine Zahl steht +- `@` signifikante Ziffer, hilft, eine Zahl mit einer bestimmten Anzahl von signifikanten Ziffern anzuzeigen +- `.` gibt an, wo das Dezimaltrennzeichen (Komma oder Punkt, je nach Land) sein soll +- `,` dient zur Trennung von Zifferngruppen, meist Tausender +- `%` multipliziert die Zahl mit 100× und fügt das Prozentzeichen hinzu + +Sehen wir uns einige Beispiele an. Im ersten Beispiel sind zwei Dezimalstellen obligatorisch, im zweiten optional. Das dritte Beispiel zeigt das Auffüllen mit Nullen von links und rechts, das vierte zeigt nur vorhandene Ziffern an: + +```latte +{1234.5|number: '#,##0.00'} {* 1,234.50 *} +{1234.5|number: '#,##0.##'} {* 1,234.5 *} +{1.23 |number: '000.000'} {* 001.230 *} +{1.2 |number: '##.##'} {* 1.2 *} +``` + +Signifikante Ziffern bestimmen, wie viele Ziffern unabhängig vom Dezimaltrennzeichen angezeigt werden sollen, wobei gerundet wird: + +```latte +{1234|number: '@@'} {* 1200 *} +{1234|number: '@@@'} {* 1230 *} +{1234|number: '@@@#'} {* 1234 *} +{1.2345|number: '@@@'} {* 1.23 *} +{0.00123|number: '@@'} {* 0.0012 *} +``` + +Eine einfache Möglichkeit, eine Zahl als Prozentsatz anzuzeigen. Die Zahl wird mit 100× multipliziert und das Zeichen `%` hinzugefügt: + +```latte +{0.1234|number: '#.##%'} {* 12.34% *} +``` + +Wir können ein unterschiedliches Format für positive und negative Zahlen definieren, getrennt durch das Zeichen `;`. Auf diese Weise kann beispielsweise festgelegt werden, dass positive Zahlen mit einem `+`-Zeichen angezeigt werden sollen: + +```latte +{42|number: '#.##;(#.##)'} {* 42 *} +{-42|number: '#.##;(#.##)'} {* (42) *} +{42|number: '+#.##;-#.##'} {* +42 *} +{-42|number: '+#.##;-#.##'} {* -42 *} +``` + +Beachten Sie, dass das tatsächliche Erscheinungsbild von Zahlen je nach Ländereinstellung variieren kann. Beispielsweise wird in einigen Ländern ein Komma anstelle eines Punktes als Dezimaltrennzeichen verwendet. Dieser Filter berücksichtigt dies automatisch, und Sie müssen sich um nichts kümmern. + + +padLeft(int $length, string $pad=' ') .[filter] ----------------------------------------------- -Füllt eine Zeichenkette bis zu einer bestimmten Länge mit einer anderen Zeichenkette von links auf. +Füllt eine Zeichenkette auf eine bestimmte Länge mit einer anderen Zeichenkette von links auf. ```latte -{='hello'|padLeft: 10, '123'} {* gibt '12312hello' *} +{='hallo'|padLeft: 10, '123'} {* gibt '12312hallo' aus *} ``` -padRight(int length, string pad = ' ') .[filter] +padRight(int $length, string $pad=' ') .[filter] ------------------------------------------------ -Füllt eine Zeichenfolge auf eine bestimmte Länge mit einer anderen Zeichenfolge von rechts. +Füllt eine Zeichenkette auf eine bestimmte Länge mit einer anderen Zeichenkette von rechts auf. ```latte -{='hello'|padRight: 10, '123'} {* gibt 'hello12312' *} +{='hallo'|padRight: 10, '123'} {* gibt 'hallo12312' aus *} ``` -query .[filter]{data-version:2.10} ------------------------------------ -Erzeugt dynamisch eine Abfragezeichenfolge in der URL: +query .[filter] +--------------- +Generiert dynamisch einen Query-String in einer URL: ```latte -click -search +klicken +suchen ``` -Druckt: +Gibt aus: ```latte -click -search +klicken +suchen ``` -Tasten mit einem Wert von `null` werden ausgelassen. +Schlüssel mit dem Wert `null` werden ausgelassen. -Siehe auch [escapeUrl |#escapeUrl]. +Siehe auch [#escapeUrl]. -random .[filter]{data-version:2.10.2} -------------------------------------- -Gibt ein zufälliges Element eines Arrays oder ein Zeichen einer Zeichenkette zurück: +random .[filter] +---------------- +Gibt ein zufälliges Element eines Arrays oder ein zufälliges Zeichen einer Zeichenkette zurück: ```latte -{=[1, 2, 3, 4]|random} {* example output: 3 *} -{='abcd'|random} {* example output: 'b' *} +{=[1, 2, 3, 4]|random} {* gibt z.B. 3 aus *} +{='abcd'|random} {* gibt z.B. 'b' aus *} ``` -Siehe auch [first |#first], [last |#last]. +Siehe auch [#first], [#last]. -repeat(int count) .[filter] ---------------------------- -Wiederholt die Zeichenkette x-mal. +repeat(int $count) .[filter] +---------------------------- +Wiederholt eine Zeichenkette x-mal. ```latte -{='hello'|repeat: 3} {* gibt 'hellohellohello' *} +{='hallo'|repeat: 3} {* gibt 'hallohallohallo' aus *} ``` -replace(string|array search, string replace = '') .[filter] +replace(string|array $search, string $replace='') .[filter] ----------------------------------------------------------- -Ersetzt alle Vorkommen der Suchzeichenfolge durch die Ersatzzeichenfolge. +Ersetzt alle Vorkommen der Suchzeichenkette durch die Ersatzzeichenkette. ```latte -{='hello world'|replace: 'world', 'friend'} {* gibt 'hello friend' *} +{='hallo welt'|replace: 'welt', 'freund'} {* gibt 'hallo freund' aus *} ``` -Es können mehrere Ersetzungen auf einmal vorgenommen werden: .{data-version:2.10.2} +Es können auch mehrere Ersetzungen gleichzeitig durchgeführt werden: ```latte -{='hello world'|replace: [h => l, l => h]} {* gibt 'lehho worhd' *} +{='hallo welt'|replace: [h => l, l => h]} {* gibt 'lehho worhd' aus *} ``` -replaceRE(string pattern, string replace = '') .[filter] +replaceRE(string $pattern, string $replace='') .[filter] -------------------------------------------------------- -Ersetzt alle Vorkommen entsprechend dem regulären Ausdruck. +Führt eine Suche mit regulären Ausdrücken und Ersetzung durch. ```latte -{='hello world'|replaceRE: '/l.*/', 'l'} {* gibt 'hel' *} +{='hallo welt'|replaceRE: '/l.*/', 'l'} {* gibt 'hel' aus *} ``` reverse .[filter] ----------------- -Kehrt eine gegebene Zeichenkette oder ein gegebenes Array um. +Kehrt die gegebene Zeichenkette oder das Array um. ```latte {var $s = 'Nette'} -{$s|reverse} {* gibt 'etteN' *} +{$s|reverse} {* gibt 'etteN' aus *} {var $a = ['N', 'e', 't', 't', 'e']} -{$a|reverse} {* liefert ['e', 't', 't', 'e', 'N'] *} +{$a|reverse} {* gibt ['e', 't', 't', 'e', 'N'] zurück *} ``` -round(int precision = 0) .[filter] ----------------------------------- -Rundet eine Zahl auf eine bestimmte Genauigkeit. +round(int $precision=0) .[filter] +--------------------------------- +Rundet eine Zahl auf die angegebene Genauigkeit. ```latte -{=3.4|round} {* gibt 3 *} -{=3.5|round} {* gibt 4 *} -{=135.79|round:1} {* gibt 135.8 *} -{=135.79|round:3} {* gibt 135.79 *} +{=3.4|round} {* gibt 3 aus *} +{=3.5|round} {* gibt 4 aus *} +{=135.79|round:1} {* gibt 135.8 aus *} +{=135.79|round:3} {* gibt 135.79 aus *} ``` -Siehe auch [ceil |#ceil], [floor |#floor]. +Siehe auch [#ceil], [#floor]. -slice(int start, int length = null, bool preserveKeys = false) .[filter]{data-version:2.10.2} ---------------------------------------------------------------------------------------------- -Extrahiert einen Ausschnitt aus einem Array oder einer Zeichenkette. +slice(int $start, ?int $length=null, bool $preserveKeys=false) .[filter] +------------------------------------------------------------------------ +Extrahiert einen Teil eines Arrays oder einer Zeichenkette. ```latte -{='hello'|slice: 1, 2} {* gibt 'el' *} -{=['a', 'b', 'c']|slice: 1, 2} {* gibt ['b', 'c'] *} +{='hallo'|slice: 1, 2} {* gibt 'al' aus *} +{=['a', 'b', 'c']|slice: 1, 2} {* gibt ['b', 'c'] aus *} ``` -Der Slice-Filter funktioniert wie die PHP-Funktion `array_slice` für Arrays und `mb_substr` für Strings mit einem Fallback auf `iconv_substr` im UTF-8-Modus. +Der Filter funktioniert wie die PHP-Funktion `array_slice` für Arrays oder `mb_substr` für Zeichenketten mit einem Fallback auf die Funktion `iconv_substr` im UTF-8-Modus. -Wenn start nicht negativ ist, beginnt die Sequenz an diesem Anfang in der Variablen. Wenn start negativ ist, beginnt die Sequenz so weit vom Ende der Variablen entfernt. +Wenn `start` positiv ist, beginnt die Sequenz um diese Anzahl vom Anfang des Arrays/der Zeichenkette verschoben. Wenn es negativ ist, beginnt die Sequenz um so viel vom Ende verschoben. -Wenn length angegeben wird und positiv ist, wird die Sequenz bis zu dieser Anzahl von Elementen enthalten. Wenn die Variable kürzer als die Länge ist, werden nur die verfügbaren Elemente der Variablen angezeigt. Wenn length angegeben wird und negativ ist, endet die Sequenz so viele Elemente vor dem Ende der Variablen. Wird sie weggelassen, enthält die Sequenz alle Elemente vom Offset bis zum Ende der Variablen. +Wenn der Parameter `length` angegeben ist und positiv ist, enthält die Sequenz so viele Elemente. Wenn dieser Funktion ein negativer Parameter `length` übergeben wird, enthält die Sequenz alle Elemente des ursprünglichen Arrays, beginnend bei der Position `start` und endend bei einer Position, die um `length` Elemente kleiner ist als das Ende des Arrays. Wenn Sie diesen Parameter nicht angeben, enthält die Sequenz alle Elemente des ursprünglichen Arrays, beginnend bei der Position `start`. -Filter ordnet die Schlüssel des Integer-Arrays standardmäßig neu an und setzt sie zurück. Dieses Verhalten kann geändert werden, indem preserveKeys auf true gesetzt wird. String-Schlüssel werden immer beibehalten, unabhängig von diesem Parameter. +Standardmäßig ändert der Filter die Reihenfolge und setzt die Ganzzahl-Schlüssel des Arrays zurück. Dieses Verhalten kann durch Setzen von `preserveKeys` auf `true` geändert werden. Zeichenketten-Schlüssel bleiben immer erhalten, unabhängig von diesem Parameter. -sort .[filter]{data-version:2.9} ---------------------------------- -Filter, der ein Array sortiert und die Indexzuordnung beibehält. +sort(?Closure $comparison, string|int|\Closure|null $by=null, string|int|\Closure|bool $byKey=false) .[filter] +-------------------------------------------------------------------------------------------------------------- +Der Filter sortiert die Elemente eines Arrays oder Iterators und behält ihre assoziativen Schlüssel bei. Bei gesetzter [Spracheinstellung |develop#Locale] richtet sich die Sortierung nach deren Regeln, sofern keine eigene Vergleichsfunktion angegeben ist. ```latte {foreach ($names|sort) as $name} @@ -601,7 +735,7 @@ Filter, der ein Array sortiert und die Indexzuordnung beibehält. {/foreach} ``` -Array in umgekehrter Reihenfolge sortiert. +Sortiertes Array in umgekehrter Reihenfolge: ```latte {foreach ($names|sort|reverse) as $name} @@ -609,105 +743,131 @@ Array in umgekehrter Reihenfolge sortiert. {/foreach} ``` -Sie können Ihre eigene Vergleichsfunktion als Parameter übergeben: .{data-version:2.10.2} +Sie können eine eigene Vergleichsfunktion für die Sortierung angeben (das Beispiel zeigt, wie man die Sortierung von der größten zur kleinsten umkehrt): ```latte -{var $sorted = ($names|sort: fn($a, $b) => $b <=> $a)} +{var $reverted = ($names|sort: fn($a, $b) => $b <=> $a)} ``` +Der Filter `|sort` ermöglicht auch das Sortieren von Elementen nach Schlüsseln: -spaceless .[filter]{data-version:2.10.2} ------------------------------------------ +```latte +{foreach ($names|sort: byKey: true) as $name} + ... +{/foreach} +``` + +Wenn Sie eine Tabelle nach einer bestimmten Spalte sortieren müssen, können Sie den Parameter `by` verwenden. Der Wert `'name'` im Beispiel gibt an, dass nach `$item->name` oder `$item['name']` sortiert wird, je nachdem, ob `$item` ein Array oder ein Objekt ist: + +```latte +{foreach ($items|sort: by: 'name') as $item} + {$item->name} +{/foreach} +``` + +Sie können auch eine Callback-Funktion definieren, die den Wert bestimmt, nach dem sortiert werden soll: + +```latte +{foreach ($items|sort: by: fn($items) => $items->category->name) as $item} + {$item->name} +{/foreach} +``` + +Auf die gleiche Weise kann auch der Parameter `byKey` verwendet werden. + + +spaceless .[filter] +------------------- Entfernt unnötige Leerzeichen aus der Ausgabe. Sie können auch den Alias `strip` verwenden. ```latte {block |spaceless}
                                        -
                                      • Hello
                                      • +
                                      • Hallo
                                      {/block} ``` -Druckt: +Gibt aus: ```latte -
                                      • Hello
                                      +
                                      • Hallo
                                      ``` stripHtml .[filter] ------------------- -Konvertiert HTML in einfachen Text. Das heißt, es werden HTML-Tags entfernt und HTML-Elemente in Text umgewandelt. +Konvertiert HTML in reinen Text. Das heißt, es entfernt HTML-Tags und konvertiert HTML-Entitäten in Text. ```latte -{='

                                      one < two

                                      '|stripHtml} {* gibt 'one < two' *} +{='

                                      eins < zwei

                                      '|stripHtml} {* gibt 'eins < zwei' aus *} ``` -Der resultierende reine Text kann natürlich Zeichen enthalten, die HTML-Tags darstellen, zum Beispiel wird `'<p>'|stripHtml` in `

                                      `. Geben Sie den resultierenden Text niemals mit `|noescape` aus, da dies zu einer Sicherheitslücke führen kann. +Der resultierende reine Text kann natürlich Zeichen enthalten, die HTML-Tags darstellen, z. B. wird `'<p>'|stripHtml` in `

                                      ` konvertiert. Geben Sie solchen resultierenden Text keinesfalls mit `|noescape` aus, da dies zu einer Sicherheitslücke führen kann. -substr(int offset, int length = null) .[filter] ------------------------------------------------ -Extrahiert einen Ausschnitt aus einer Zeichenkette. Dieser Filter wurde durch einen [Slice-Filter |#slice] ersetzt. +substr(int $offset, ?int $length=null) .[filter] +------------------------------------------------ +Extrahiert einen Teil einer Zeichenkette. Dieser Filter wurde durch den Filter [#slice] ersetzt. ```latte {$string|substr: 1, 2} ``` -translate(string message, ...args) .[filter]{data-version:3.0} --------------------------------------------------------------- -Er übersetzt Ausdrücke in andere Sprachen. Um den Filter verfügbar zu machen, müssen Sie den [Übersetzer ein richten|develop#TranslatorExtension]. Sie können auch die [Tags für die Übersetzung |tags#Translation] verwenden. +translate(...$args) .[filter] +----------------------------- +Übersetzt Ausdrücke in andere Sprachen. Damit der Filter verfügbar ist, muss der [Übersetzer eingestellt werden |develop#TranslatorExtension]. Sie können auch [Tags für die Übersetzung |tags#Übersetzungen] verwenden. ```latte -{='Baskter'|translate} +{='Warenkorb'|translate} {$item|translate} ``` -trim(string charlist = " \t\n\r\0\x0B\u{A0}") .[filter] -------------------------------------------------------- -Entfernen Sie führende und nachgestellte Zeichen, standardmäßig Leerzeichen. +trim(string $charlist=" \t\n\r\0\x0B\u{A0}") .[filter] +------------------------------------------------------ +Entfernt Leerzeichen (oder andere Zeichen) vom Anfang und Ende einer Zeichenkette. ```latte -{=' I like Latte. '|trim} {* gibt 'I like Latte.' *} -{=' I like Latte.'|trim: '.'} {* gibt ' I like Latte' *} +{=' Ich mag Latte. '|trim} {* gibt 'Ich mag Latte.' aus *} +{=' Ich mag Latte.'|trim: '.'} {* gibt ' Ich mag Latte' aus *} ``` -truncate(int length, string append = '…') .[filter] +truncate(int $length, string $append='…') .[filter] --------------------------------------------------- -Kürzt eine Zeichenkette auf die maximal angegebene Länge, versucht aber, ganze Wörter zu erhalten. Wenn die Zeichenkette abgeschnitten ist, wird am Ende ein Auslassungszeichen hinzugefügt (dies kann durch den zweiten Parameter geändert werden). +Kürzt eine Zeichenkette auf die angegebene maximale Länge und versucht dabei, ganze Wörter beizubehalten. Wenn die Zeichenkette gekürzt wird, wird am Ende ein Auslassungspunkt hinzugefügt (kann durch den zweiten Parameter geändert werden). ```latte -{var $title = 'Hello, how are you?'} -{$title|truncate:5} {* Hell… *} -{$title|truncate:17} {* Hello, how are… *} -{$title|truncate:30} {* Hello, how are you? *} +{var $title = 'Hallo, wie geht es Ihnen?'} +{$title|truncate:5} {* Hallo… *} +{$title|truncate:17} {* Hallo, wie geht… *} +{$title|truncate:30} {* Hallo, wie geht es Ihnen? *} ``` upper .[filter] --------------- -Konvertiert einen Wert in Großbuchstaben. Erfordert die PHP-Erweiterung `mbstring`. +Wandelt eine Zeichenkette in Großbuchstaben um. Erfordert die PHP-Erweiterung `mbstring`. ```latte -{='latte'|upper} {* gibt 'LATTE' *} +{='latte'|upper} {* gibt 'LATTE' aus *} ``` -Siehe auch [capitalize |#capitalize], [firstUpper |#firstUpper], [lower |#lower]. +Siehe auch [#capitalize], [#firstUpper], [#lower]. webalize .[filter] ------------------ -Konvertiert nach ASCII. +Wandelt eine UTF-8-Zeichenkette in die in URLs verwendete Form um. -Konvertiert Leerzeichen in Bindestriche. Entfernt Zeichen, die keine alphanumerischen Zeichen, Unterstriche oder Bindestriche sind. Konvertiert in Kleinbuchstaben. Entfernt auch führende und nachfolgende Leerzeichen. +Wird in ASCII konvertiert. Wandelt Leerzeichen in Bindestriche um. Entfernt Zeichen, die keine alphanumerischen Zeichen, Unterstriche oder Bindestriche sind. Wandelt in Kleinbuchstaben um. Entfernt auch führende und nachfolgende Leerzeichen. ```latte -{var $s = 'Our 10. product'} -{$s|webalize} {* gibt 'our-10-product' *} +{var $s = 'Unser 10. Produkt'} +{$s|webalize} {* gibt 'unser-10-produkt' aus *} ``` .[caution] -Erfordert das Paket [nette/utils |utils:]. +Erfordert die Bibliothek [nette/utils |utils:]. diff --git a/latte/de/functions.texy b/latte/de/functions.texy index 79488d1750..3796e69c65 100644 --- a/latte/de/functions.texy +++ b/latte/de/functions.texy @@ -1,23 +1,25 @@ -Latte Funktionen +Latte-Funktionen **************** .[perex] -Zusätzlich zu den üblichen PHP-Funktionen können Sie diese auch in Vorlagen verwenden. +In Templates können wir neben den üblichen PHP-Funktionen auch diese zusätzlichen Funktionen verwenden. .[table-latte-filters] -| `clamp` | [klemmt den Wert auf den Bereich |#clamp] -| `divisibleBy`| [prüft ob eine Variable durch eine Zahl teilbar ist |#divisibleBy] -| `even` | [prüft ob die angegebene Zahl gerade ist |#even] -| `first` | [gibt das erste Element eines Arrays oder ein Zeichen einer Zeichenkette zurück |#first] -| `last` | [gibt das letzte Element eines Arrays oder ein Zeichen einer Zeichenkette zurück |#last] -| `odd` | [prüft ob die angegebene Zahl ungerade ist |#odd] -| `slice` | [extrahiert einen Ausschnitt aus einem Array oder einer Zeichenkette|#slice] +| `clamp` | [begrenzt einen Wert auf einen bestimmten Bereich |#clamp] +| `divisibleBy`| [prüft, ob eine Variable durch eine Zahl teilbar ist |#divisibleBy] +| `even` | [prüft, ob eine gegebene Zahl gerade ist |#even] +| `first` | [gibt das erste Element eines Arrays oder das erste Zeichen einer Zeichenkette zurück |#first] +| `group` | [gruppiert Daten nach verschiedenen Kriterien |#group] +| `hasBlock` | [prüft die Existenz eines Blocks |#hasBlock] +| `last` | [gibt das letzte Element eines Arrays oder das letzte Zeichen einer Zeichenkette zurück |#last] +| `odd` | [prüft, ob eine gegebene Zahl ungerade ist |#odd] +| `slice` | [extrahiert einen Teil eines Arrays oder einer Zeichenkette |#slice] -Verwendung .[#toc-usage] -======================== +Verwendung +========== -Funktionen werden auf die gleiche Weise verwendet wie die üblichen PHP-Funktionen und können in allen Ausdrücken verwendet werden: +Funktionen werden genauso wie übliche PHP-Funktionen verwendet und können in allen Ausdrücken eingesetzt werden: ```latte

                                      {clamp($num, 1, 100)}

                                      @@ -25,14 +27,14 @@ Funktionen werden auf die gleiche Weise verwendet wie die üblichen PHP-Funktion {if odd($num)} ... {/if} ``` -[Benutzerdefinierte Funktionen |extending-latte#functions] können auf diese Weise registriert werden: +[Benutzerdefinierte Funktionen |custom-functions] können auf diese Weise registriert werden: ```php $latte = new Latte\Engine; $latte->addFunction('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -Wir verwenden sie in einer Vorlage wie dieser: +Im Template wird sie dann so aufgerufen: ```latte

                                      {shortify($text)}

                                      @@ -40,23 +42,23 @@ Wir verwenden sie in einer Vorlage wie dieser: ``` -Funktionen .[#toc-functions] -============================ +Funktionen +========== -clamp(int|float $value, int|float $min, int|float $max): int|float .[method]{data-version:2.9} ----------------------------------------------------------------------------------------------- -Gibt einen Wert zurück, der auf den gesamten Bereich von min und max begrenzt ist. +clamp(int|float $value, int|float $min, int|float $max): int|float .[method] +---------------------------------------------------------------------------- +Begrenzt einen Wert auf den angegebenen inklusiven Bereich von min und max. ```latte {=clamp($level, 0, 255)} ``` -Siehe auch [Filter Klammer |filters#clamp]: +Siehe auch der [Filter clamp |filters#clamp]. -divisibleBy(int $value, int $by): bool .[method]{data-version:2.10.2} ---------------------------------------------------------------------- +divisibleBy(int $value, int $by): bool .[method] +------------------------------------------------ Prüft, ob eine Variable durch eine Zahl teilbar ist. ```latte @@ -64,61 +66,91 @@ Prüft, ob eine Variable durch eine Zahl teilbar ist. ``` -even(int $value): bool .[method]{data-version:2.10.2} ------------------------------------------------------ -Prüft, ob die angegebene Zahl gerade ist. +even(int $value): bool .[method] +-------------------------------- +Prüft, ob eine gegebene Zahl gerade ist. ```latte {if even($num)} ... {/if} ``` -first(string|array $value): mixed .[method]{data-version:2.10.2} ----------------------------------------------------------------- -Gibt das erste Element eines Arrays oder ein Zeichen einer Zeichenkette zurück: +first(string|iterable $value): mixed .[method] +---------------------------------------------- +Gibt das erste Element eines Arrays oder das erste Zeichen einer Zeichenkette zurück: ```latte -{=first([1, 2, 3, 4])} {* gibt 1 *} -{=first('abcd')} {* gibt 'a' *} +{=first([1, 2, 3, 4])} {* gibt 1 aus *} +{=first('abcd')} {* gibt 'a' aus *} ``` -Siehe auch [last |#last], [filter first |filters#first]. +Siehe auch [#last], [Filter first |filters#first]. -last(string|array $value): mixed .[method]{data-version:2.10.2} ---------------------------------------------------------------- -Gibt das letzte Element eines Arrays oder ein Zeichen einer Zeichenkette zurück: +group(iterable $data, string|int|\Closure $by): array .[method]{data-version:3.0.16} +------------------------------------------------------------------------------------ +Die Funktion gruppiert Daten nach verschiedenen Kriterien. + +In diesem Beispiel werden die Zeilen in der Tabelle nach der Spalte `categoryId` gruppiert. Die Ausgabe ist ein Array von Arrays, wobei der Schlüssel der Wert in der Spalte `categoryId` ist. [Lesen Sie die detaillierte Anleitung |cookbook/grouping]. + +```latte +{foreach group($items, categoryId) as $categoryId => $categoryItems} +
                                        + {foreach $categoryItems as $item} +
                                      • {$item->name}
                                      • + {/foreach} +
                                      +{/foreach} +``` + +Siehe auch der Filter [group |filters#group]. + + +hasBlock(string $name): bool .[method]{data-version:3.0.10} +----------------------------------------------------------- +Prüft, ob ein Block mit dem angegebenen Namen existiert: + +```latte +{if hasBlock(header)} ... {/if} +``` + +Siehe auch [Überprüfung der Existenz von Blöcken |template-inheritance#Existenzprüfung von Blöcken]. + + +last(string|array $value): mixed .[method] +------------------------------------------ +Gibt das letzte Element eines Arrays oder das letzte Zeichen einer Zeichenkette zurück: ```latte -{=last([1, 2, 3, 4])} {* gibt 4 *} -{=last('abcd')} {* gibt 'd' *} +{=last([1, 2, 3, 4])} {* gibt 4 aus *} +{=last('abcd')} {* gibt 'd' aus *} ``` -Siehe auch [first |#first], [filter last |filters#last]. +Siehe auch [#first], [Filter last |filters#last]. -odd(int $value): bool .[method]{data-version:2.10.2} ----------------------------------------------------- -Prüft, ob die angegebene Zahl ungerade ist. +odd(int $value): bool .[method] +------------------------------- +Prüft, ob eine gegebene Zahl ungerade ist. ```latte {if odd($num)} ... {/if} ``` -slice(string|array $value, int $start, int $length=null, bool $preserveKeys=false): string|array .[method]{data-version:2.10.2} -------------------------------------------------------------------------------------------------------------------------------- -Extrahiert einen Ausschnitt aus einem Array oder einer Zeichenkette. +slice(string|array $value, int $start, ?int $length=null, bool $preserveKeys=false): string|array .[method] +----------------------------------------------------------------------------------------------------------- +Extrahiert einen Teil eines Arrays oder einer Zeichenkette. ```latte -{=slice('hello', 1, 2)} {* gibt 'el' *} -{=slice(['a', 'b', 'c'], 1, 2)} {* gibt ['b', 'c'] *} +{=slice('hallo', 1, 2)} {* gibt 'al' aus *} +{=slice(['a', 'b', 'c'], 1, 2)} {* gibt ['b', 'c'] aus *} ``` -Der Slice-Filter funktioniert wie die PHP-Funktion `array_slice` für Arrays und `mb_substr` für Strings mit einem Fallback auf `iconv_substr` im UTF-8-Modus. +Die Funktion funktioniert wie die PHP-Funktion `array_slice` für Arrays oder `mb_substr` für Zeichenketten mit einem Fallback auf die Funktion `iconv_substr` im UTF-8-Modus. -Wenn start nicht negativ ist, beginnt die Sequenz an diesem Anfang in der Variablen. Wenn start negativ ist, beginnt die Sequenz so weit vom Ende der Variablen entfernt. +Wenn `start` positiv ist, beginnt die Sequenz um diese Anzahl vom Anfang des Arrays/der Zeichenkette verschoben. Wenn es negativ ist, beginnt die Sequenz um so viel vom Ende verschoben. -Wenn length angegeben wird und positiv ist, wird die Sequenz bis zu dieser Anzahl von Elementen enthalten. Wenn die Variable kürzer als die Länge ist, werden nur die verfügbaren Elemente der Variablen angezeigt. Wenn length angegeben wird und negativ ist, endet die Sequenz so viele Elemente vor dem Ende der Variablen. Wird sie weggelassen, enthält die Sequenz alle Elemente vom Offset bis zum Ende der Variablen. +Wenn der Parameter `length` angegeben ist und positiv ist, enthält die Sequenz so viele Elemente. Wenn dieser Funktion ein negativer Parameter `length` übergeben wird, enthält die Sequenz alle Elemente des ursprünglichen Arrays, beginnend bei der Position `start` und endend bei einer Position, die um `length` Elemente kleiner ist als das Ende des Arrays. Wenn Sie diesen Parameter nicht angeben, enthält die Sequenz alle Elemente des ursprünglichen Arrays, beginnend bei der Position `start`. -Filter ordnet die Schlüssel des Integer-Arrays standardmäßig neu an und setzt sie zurück. Dieses Verhalten kann geändert werden, indem preserveKeys auf true gesetzt wird. String-Schlüssel werden immer beibehalten, unabhängig von diesem Parameter. +Standardmäßig ändert der Filter die Reihenfolge und setzt die Ganzzahl-Schlüssel des Arrays zurück. Dieses Verhalten kann durch Setzen von `preserveKeys` auf `true` geändert werden. Zeichenketten-Schlüssel bleiben immer erhalten, unabhängig von diesem Parameter. diff --git a/latte/de/guide.texy b/latte/de/guide.texy index 1a97b28cc8..6f1c9d1dbd 100644 --- a/latte/de/guide.texy +++ b/latte/de/guide.texy @@ -1,46 +1,45 @@ -Erste Schritte mit Latte -************************ +Einstieg in Latte +*****************
                                      -Vorlagen verbessern die Organisation des Codes, trennen die Anwendungslogik von der Präsentation und erhöhen die Sicherheit. Sie bieten weitaus bessere Funktionen und aussagekräftigere Möglichkeiten zur Erzeugung von HTML als PHP selbst. +Templates verbessern die Organisation des Codes, trennen die Anwendungslogik von der Präsentation und erhöhen die Sicherheit. Sie bieten weitaus bessere Funktionen und Ausdrucksmittel zur Generierung von HTML als PHP allein. -Latte ist das sicherste Templating-System für PHP. Sie werden seine intuitive Syntax lieben. Eine breite Palette nützlicher Funktionen wird Ihre Arbeit erheblich vereinfachen. -Es bietet erstklassigen Schutz vor [kritischen Schwachstellen |safety-first] und ermöglicht es Ihnen, sich auf die Erstellung hochwertiger Anwendungen zu konzentrieren, ohne sich um deren Sicherheit sorgen zu müssen. +Latte ist das sicherste Template-System für PHP. Seine intuitive Syntax werden Sie lieben. Eine breite Palette nützlicher Funktionen wird Ihre Arbeit erheblich erleichtern. Es bietet erstklassigen Schutz vor [kritischen Schwachstellen |safety-first] und ermöglicht es Ihnen, sich auf die Entwicklung hochwertiger Anwendungen zu konzentrieren, ohne sich um deren Sicherheit sorgen zu müssen. -Wie schreibt man Vorlagen mit Latte? .[#toc-how-to-write-templates-using-latte] -------------------------------------------------------------------------------- +Wie schreibt man Templates mit Latte? +------------------------------------- -Latte ist clever konzipiert und für PHP-Kenner leicht zu erlernen, da sie die grundlegenden Tags schnell übernehmen können. +Latte ist intelligent konzipiert und für diejenigen, die PHP kennen und sich die grundlegenden Tags aneignen, leicht zu erlernen. -- Machen Sie sich zunächst mit der [Latte-Syntax |syntax] vertraut und [probieren Sie alles online |https://fiddle.nette.org/latte/]aus -- Werfen Sie einen Blick auf die Grundausstattung an [Tags |tags] und [Filtern |filters] -- Schreiben Sie Vorlagen im [Editor mit Latte-Unterstützung |recipes#Editors and IDE] +- Machen Sie sich zuerst mit der [Latte-Syntax |syntax] vertraut und [PROBIEREN SIE ES ONLINE AUS |https://fiddle.nette.org/latte/#9cc0cf6d89#9cc0cf6d89] +- Sehen Sie sich die grundlegende Suite von [Tags |tags] und [Filtern |filters] an +- Schreiben Sie Templates in einem [Editor mit Latte-Unterstützung |recipes#Editoren und IDEs] -Wie benutzt man Latte in PHP? .[#toc-how-to-use-latte-in-php] -------------------------------------------------------------- +Wie verwendet man Latte in PHP? +------------------------------- -Die Implementierung von Latte in Ihrer neuen Anwendung ist eine Sache von Minuten: +Latte in Ihre neue Anwendung zu integrieren, ist eine Frage von Minuten: -- [Installieren Sie |develop#Installation] zunächst [Latte und führen Sie es aus |develop#Installation]. -- Verwöhnen Sie sich mit dem [Debugging-Tool Tracy |develop#Debugging and Tracy] -- Erweitern Sie Latte um [eigene Funktionen |extending-latte] +- Zuerst [Latte installieren und starten |develop#Installation] +- Lassen Sie sich vom [Debugging-Werkzeug Tracy |develop#Debugging und Tracy] verwöhnen +- Erweitern Sie Latte um [eigene Funktionalität |extending-latte] -Wenn Sie ein altes, in einfachem PHP geschriebenes Projekt nach Latte konvertieren, wird das [Tool zur Konvertierung von PHP-Code nach Latte |cookbook/migration-from-php] die Migration für Sie erleichtern. Oder planen Sie, von Twig zu Latte zu wechseln? Wir haben einen [Konverter für Twig-Vorlagen nach Latte |cookbook/migration-from-twig] für Sie. +Wenn Sie ein altes Projekt, das in reinem PHP geschrieben ist, auf Latte umstellen, erleichtert Ihnen das [Werkzeug zur Konvertierung von PHP-Code nach Latte |cookbook/migration-from-php] die Migration. Oder planen Sie, von Twig auf Latte umzusteigen? Wir haben für Sie einen [Konverter für Twig-Templates nach Latte |cookbook/migration-from-twig]. -Was kann Latte sonst noch tun? .[#toc-what-else-can-latte-do] -------------------------------------------------------------- +Was kann Latte noch? +-------------------- -Die Latte ist komplett ausgestattet, mit allem, was dazugehört. +Latte erhalten Sie in voller Ausstattung, mit allem Wichtigen im Kern. -- Ihre Produktivität wird durch die [Vererbungsmechanismen |template-inheritance], die wiederkehrende Elemente und Strukturen wiederverwenden, gesteigert -- Der [Sandbox-Panzerbunker |Sandbox] isoliert Vorlagen von nicht vertrauenswürdigen Quellen, z. B. solchen, die von Benutzern selbst bearbeitet wurden -- Für weitere Inspiration finden Sie hier [Tipps und Tricks |recipes] +- Ihre Produktivität wird durch [Vererbungsmechanismen |template-inheritance] gesteigert, dank derer wiederholte Elemente und Strukturen wiederverwendet werden +- Der gepanzerte Bunker [Sandbox |sandbox] isoliert Templates aus nicht vertrauenswürdigen Quellen, die beispielsweise von den Benutzern selbst bearbeitet werden +- Für weitere Inspiration gibt es [Tipps und Tricks |recipes]
                                      -{{description: Latte ist das sicherste Templating-System für PHP. Es verhindert viele Sicherheitslücken. Sie werden die intuitive Syntax und die vielen nützlichen Optimierungen zu schätzen wissen.}} +{{description: Latte ist das sicherste Template-System für PHP. Es verhindert viele Sicherheitslücken. Sie werden seine intuitive Syntax und viele nützliche Funktionen schätzen.}} diff --git a/latte/de/loaders.texy b/latte/de/loaders.texy new file mode 100644 index 0000000000..9207c9b48f --- /dev/null +++ b/latte/de/loaders.texy @@ -0,0 +1,198 @@ +Loader +****** + +.[perex] +Loader sind der Mechanismus, den Latte verwendet, um den Quellcode Ihrer Templates abzurufen. Meistens sind Templates als Dateien auf der Festplatte gespeichert, aber dank des flexiblen Loader-Systems können Sie sie praktisch von überall laden oder sogar dynamisch generieren. + + +Was ist ein Loader? +=================== + +Wenn Sie mit Templates arbeiten, stellen Sie sich normalerweise `.latte`-Dateien vor, die sich in der Verzeichnisstruktur Ihres Projekts befinden. Darum kümmert sich der Standard-[#FileLoader] in Latte. Die Verbindung zwischen dem Template-Namen (wie `'main.latte'` oder `'components/card.latte'`) und seinem tatsächlichen Quellcode *muss* jedoch keine direkte Zuordnung zu einem Dateipfad sein. + +Genau hier kommen Loader ins Spiel. Ein Loader ist ein Objekt, dessen Aufgabe es ist, einen Template-Namen (eine Identifikationszeichenkette) zu nehmen und Latte seinen Quellcode bereitzustellen. Latte verlässt sich bei dieser Aufgabe vollständig auf den konfigurierten Loader. Dies gilt nicht nur für das anfängliche Template, das mit `$latte->render('main.latte')` angefordert wird, sondern auch für **jedes Template, auf das innerhalb** mit Tags wie `{include ...}`, `{layout ...}`, `{embed ...}` oder `{import ...}` verwiesen wird. + +Warum einen benutzerdefinierten Loader verwenden? + +- **Laden aus alternativen Quellen:** Abrufen von Templates, die in einer Datenbank, im Cache (wie Redis oder Memcached), in einem Versionskontrollsystem (wie Git, basierend auf einem bestimmten Commit) gespeichert oder dynamisch generiert werden. +- **Implementierung eigener Namenskonventionen:** Möglicherweise möchten Sie kürzere Aliase für Templates verwenden oder eine spezifische Logik für Suchpfade implementieren (z. B. zuerst im Themenverzeichnis suchen, dann zum Standardverzeichnis zurückkehren). +- **Hinzufügen von Sicherheit oder Zugriffskontrolle:** Ein benutzerdefinierter Loader kann Benutzerberechtigungen überprüfen, bevor bestimmte Templates geladen werden. +- **Vorverarbeitung:** Obwohl dies im Allgemeinen nicht empfohlen wird ([Kompilierungsdurchläufe |compiler-passes] sind besser), *könnte* ein Loader theoretisch den Template-Inhalt vorverarbeiten, bevor er ihn an Latte übergibt. + +Einen Loader für eine `Latte\Engine`-Instanz legen Sie mit der Methode `setLoader()` fest: + +```php +$latte = new Latte\Engine; + +// Verwendung des Standard-FileLoaders für Dateien in '/pfad/zu/templates' +$loader = new Latte\Loaders\FileLoader('/pfad/zu/templates'); +$latte->setLoader($loader); +``` + +Ein Loader muss das Interface `Latte\Loader` implementieren. + + +Integrierte Loader +================== + +Latte bietet mehrere Standard-Loader: + + +FileLoader +---------- + +Dies ist der **Standard-Loader**, der von der `Latte\Engine`-Klasse verwendet wird, wenn kein anderer angegeben ist. Er lädt Templates direkt aus dem Dateisystem. + +Optional können Sie ein Stammverzeichnis festlegen, um den Zugriff einzuschränken: + +```php +use Latte\Loaders\FileLoader; + +// Das Folgende erlaubt das Laden von Templates nur aus dem Verzeichnis /var/www/html/templates +$loader = new FileLoader('/var/www/html/templates'); +$latte->setLoader($loader); + +// $latte->render('../../../etc/passwd'); // Dies würde eine Ausnahme auslösen + +// Rendern eines Templates unter /var/www/html/templates/pages/contact.latte +$latte->render('pages/contact.latte'); +``` + +Bei Verwendung von Tags wie `{include}` oder `{layout}` löst er Template-Namen relativ zum aktuellen Template auf, es sei denn, es wird ein absoluter Pfad angegeben. + + +StringLoader +------------ + +Dieser Loader ruft den Template-Inhalt aus einem assoziativen Array ab, wobei die Schlüssel die Template-Namen (Identifikatoren) und die Werte die Template-Quellcode-Zeichenketten sind. Er ist besonders nützlich für Tests oder kleine Anwendungen, bei denen Templates direkt im PHP-Code gespeichert werden können. + +```php +use Latte\Loaders\StringLoader; + +$loader = new StringLoader([ + 'main.latte' => 'Hallo {$name}, include ist unten:{include helper.latte}', + 'helper.latte' => '{var $x = 10}Eingefügter Inhalt: {$x}', + // Fügen Sie bei Bedarf weitere Templates hinzu +]); + +$latte->setLoader($loader); + +$latte->render('main.latte', ['name' => 'Welt']); +// Ausgabe: Hallo Welt, include ist unten:Eingefügter Inhalt: 10 +``` + +Wenn Sie nur ein einzelnes Template direkt aus einer Zeichenkette rendern müssen, ohne dass Einfügungen oder Vererbungen erforderlich sind, die auf andere benannte Zeichenketten-Templates verweisen, können Sie die Zeichenkette direkt an die Methode `render()` oder `renderToString()` übergeben, wenn Sie `StringLoader` ohne Array verwenden: + +```php +$loader = new StringLoader; +$latte->setLoader($loader); + +$templateString = 'Hallo {$name}!'; +$output = $latte->renderToString($templateString, ['name' => 'Alice']); +// $output enthält 'Hallo Alice!' +``` + + +Erstellen eines benutzerdefinierten Loaders +=========================================== + +Um einen benutzerdefinierten Loader zu erstellen (z. B. zum Laden von Templates aus einer Datenbank, einem Cache, einem Versionskontrollsystem oder einer anderen Quelle), müssen Sie eine Klasse erstellen, die das Interface [api:Latte\Loader] implementiert. + +Sehen wir uns an, was jede Methode tun muss. + + +getContent(string $name): string .[method] +------------------------------------------ +Dies ist die Kernmethode des Loaders. Ihre Aufgabe ist es, den vollständigen Quellcode des durch `$name` identifizierten Templates abzurufen und zurückzugeben (wie an die Methode `$latte->render()` übergeben oder von der Methode [#getReferredName()] zurückgegeben). + +Wenn das Template nicht gefunden oder darauf nicht zugegriffen werden kann, **muss** diese Methode eine `Latte\RuntimeException` auslösen. + +```php +public function getContent(string $name): string +{ + // Beispiel: Laden aus einem hypothetischen internen Speicher + $content = $this->storage->read($name); + if ($content === null) { + throw new Latte\RuntimeException("Template '$name' kann nicht geladen werden."); + } + return $content; +} +``` + + +getReferredName(string $name, string $referringName): string .[method] +---------------------------------------------------------------------- +Diese Methode löst die Übersetzung von Template-Namen auf, die innerhalb von Tags wie `{include}`, `{layout}` usw. verwendet werden. Wenn Latte beispielsweise auf `{include 'partial.latte'}` innerhalb von `main.latte` trifft, ruft es diese Methode mit `$name = 'partial.latte'` und `$referringName = 'main.latte'` auf. + +Die Aufgabe der Methode besteht darin, `$name` in einen kanonischen Identifikator (z. B. einen absoluten Pfad, einen eindeutigen Datenbankschlüssel) zu übersetzen, der beim Aufruf anderer Loader-Methoden verwendet wird, basierend auf dem in `$referringName` bereitgestellten Kontext. + +```php +public function getReferredName(string $name, string $referringName): string +{ + return ...; +} +``` + + +getUniqueId(string $name): string .[method] +------------------------------------------- +Latte verwendet zur Leistungssteigerung einen Cache für kompilierte Templates. Jede kompilierte Template-Datei benötigt einen eindeutigen Namen, der vom Identifikator des Quelltemplates abgeleitet ist. Diese Methode liefert eine Zeichenkette, die das Template `$name` **eindeutig identifiziert**. + +Für dateibasierte Templates kann der absolute Pfad dienen. Für Templates in einer Datenbank ist eine Kombination aus Präfix und Datenbank-ID üblich. + +```php +public function getUniqueId(string $name): string +{ + return ...; +} +``` + + +Beispiel: Einfacher Datenbank-Loader +------------------------------------ + +Dieses Beispiel zeigt die grundlegende Struktur eines Loaders, der Templates lädt, die in einer Datenbanktabelle namens `templates` mit den Spalten `name` (eindeutiger Identifikator), `content` und `updated_at` gespeichert sind. + +```php +use Latte; + +class DatabaseLoader implements Latte\Loader +{ + public function __construct( + private \PDO $db, + ) { + } + + public function getContent(string $name): string + { + $stmt = $this->db->prepare('SELECT content FROM templates WHERE name = ?'); + $stmt->execute([$name]); + $content = $stmt->fetchColumn(); + if ($content === false) { + throw new Latte\RuntimeException("Template '$name' nicht in der Datenbank gefunden."); + } + return $content; + } + + // Dieses einfache Beispiel geht davon aus, dass die Template-Namen ('homepage', 'article', etc.) + // eindeutige IDs sind und Templates nicht relativ aufeinander verweisen. + public function getReferredName(string $name, string $referringName): string + { + return $name; + } + + public function getUniqueId(string $name): string + { + // Die Verwendung eines Präfixes und des Namens selbst ist hier eindeutig und ausreichend + return 'db_' . $name; + } +} + +// Verwendung: +$pdo = new \PDO(/* Verbindungsdetails */); +$loader = new DatabaseLoader($pdo); +$latte->setLoader($loader); +$latte->render('homepage'); // Lädt das Template mit dem Namen 'homepage' aus der DB +``` + +Benutzerdefinierte Loader geben Ihnen die vollständige Kontrolle darüber, woher Ihre Latte-Templates stammen, und ermöglichen die Integration mit verschiedenen Speichersystemen und Arbeitsabläufen. diff --git a/latte/de/recipes.texy b/latte/de/recipes.texy index 174f5a0254..b0255a6ada 100644 --- a/latte/de/recipes.texy +++ b/latte/de/recipes.texy @@ -2,44 +2,44 @@ Tipps und Tricks **************** -Editoren und IDE .[#toc-editors-and-ide] -======================================== +Editoren und IDEs +================= -Schreiben Sie Vorlagen in einem Editor oder einer IDE, die Latte unterstützt. Es wird viel angenehmer sein. +Schreiben Sie Templates in einem Editor oder einer IDE, die Latte unterstützt. Es wird viel angenehmer sein. -- NetBeans IDE hat integrierte Unterstützung -- PhpStorm: Installieren Sie das [Latte-Plugin |https://plugins.jetbrains.com/plugin/7457-latte] in `Settings > Plugins > Marketplace` -- VS Code: Suchen Sie auf markerplace nach dem "Nette Latte + Neon" Plugin -- Sublime Text 3: in der Paketverwaltung das Paket `Nette` suchen und installieren und Latte in auswählen `View > Syntax` -- in alten Editoren Smarty-Hervorhebung für .latte-Dateien verwenden +- PhpStorm: Installieren Sie in `Settings > Plugins > Marketplace` das [Plugin Latte |https://plugins.jetbrains.com/plugin/7457-latte] +- VS Code: Installieren Sie [Nette Latte + Neon |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte], [Nette Latte templates |https://marketplace.visualstudio.com/items?itemName=smuuf.latte-lang] oder das neueste [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode] Plugin +- NetBeans IDE: Native Latte-Unterstützung ist Teil der Installation +- Sublime Text 3: Finden und installieren Sie im Package Control das Paket `Nette` und wählen Sie Latte in `View > Syntax` +- In älteren Editoren verwenden Sie für .latte-Dateien die Smarty-Hervorhebung -Das Plugin für PhpStorm ist sehr fortschrittlich und kann perfekt PHP-Code vorschlagen. Um optimal zu arbeiten, verwenden Sie [getippte Vorlagen |type-system]. +Das Plugin für PhpStorm ist sehr fortschrittlich und kann PHP-Code hervorragend vorschlagen. Damit es optimal funktioniert, verwenden Sie [typisierte Templates |type-system]. [* latte-phpstorm-plugin.webp *] -Unterstützung für Latte kann auch in der Web-Code-Highlighter [Prism.js |https://prismjs.com/#supported-languages] und Editor [Ace |https://ace.c9.io] gefunden werden. +Unterstützung für Latte finden Sie auch im Web-Code-Highlighter [Prism.js |https://prismjs.com/#supported-languages] und im Editor [Ace |https://ace.c9.io]. -Latte innerhalb von JavaScript oder CSS .[#toc-latte-inside-javascript-or-css] -============================================================================== +Latte innerhalb von JavaScript oder CSS +======================================= -Latte kann sehr bequem innerhalb von JavaScript oder CSS verwendet werden. Aber wie lässt sich vermeiden, dass Latte JavaScript-Code oder CSS-Stil fälschlicherweise für ein Latte-Tag hält? +Latte kann sehr bequem auch innerhalb von JavaScript oder CSS verwendet werden. Wie vermeidet man jedoch die Situation, dass Latte fälschlicherweise JavaScript-Code oder CSS-Stil als Latte-Tag betrachtet? ```latte ``` -**Option 1** +**Variante 1** -Vermeiden Sie Situationen, in denen ein Buchstabe unmittelbar auf ein "{" folgt, indem Sie entweder ein Leerzeichen, einen Zeilenumbruch oder ein Anführungszeichen dazwischen setzen: +Vermeiden Sie die Situation, dass direkt nach `{` ein Buchstabe folgt, indem Sie davor ein Leerzeichen, einen Zeilenumbruch oder ein Anführungszeichen einfügen: ```latte -

                                      +

                                      ``` -Zwei Wege und zwei verschiedene Arten des Escapings von Daten. Innerhalb der ` ``` -Wenn wir ihn jedoch in ein HTML-Attribut einfügen wollen, müssen wir die Anführungszeichen in HTML-Entities umwandeln: +Wenn wir ihn jedoch in ein HTML-Attribut einfügen wollten, müssten wir die Anführungszeichen noch in HTML-Entitäten escapen: ```html
                                      ``` -Der verschachtelte Kontext muss jedoch nicht nur JS oder CSS sein. Es handelt sich auch häufig um eine URL. Parameter in URLs werden durch die Umwandlung von Sonderzeichen in Sequenzen, die mit "%" beginnen, geschützt. Beispiel: +Ein verschachtelter Kontext muss aber nicht nur JS oder CSS sein. Häufig ist es auch eine URL. Parameter in einer URL werden escapet, indem Zeichen mit besonderer Bedeutung in Sequenzen umgewandelt werden, die mit `%` beginnen. Beispiel: ``` https://example.org/?a=Jazz&b=Rock%27n%27Roll ``` -Wenn wir diese Zeichenkette in einem Attribut ausgeben, wenden wir immer noch Escaping entsprechend diesem Kontext an und ersetzen `&` with `&`: +Und wenn wir diese Zeichenkette in einem Attribut ausgeben, wenden wir noch das Escaping gemäß diesem Kontext an und ersetzen `&` durch `&`: ```html ``` -Wenn Sie bis hierher gelesen haben, herzlichen Glückwunsch, es war anstrengend. Jetzt haben Sie eine gute Vorstellung davon, was Kontexte und Escaping sind. Und Sie müssen sich keine Sorgen machen, dass es kompliziert wird. Latte macht das automatisch für Sie. +Wenn Sie bis hierher gelesen haben, herzlichen Glückwunsch, es war anstrengend. Jetzt haben Sie eine gute Vorstellung davon, was Kontexte und Escaping sind. Und Sie müssen keine Angst haben, dass es kompliziert ist. Latte erledigt das nämlich automatisch für Sie. -Latte vs. Naive Systeme .[#toc-latte-vs-naive-systems] -====================================================== +Latte vs. naive Systeme +======================= -Wir haben gezeigt, wie man in einem HTML-Dokument richtig escaped und wie wichtig es ist, den Kontext zu kennen, d.h. wo man die Daten ausgibt. Mit anderen Worten, wie kontextsensitives Escaping funktioniert. -Dies ist zwar eine Voraussetzung für eine funktionierende XSS-Abwehr, aber **Latte ist das einzige Templating-System für PHP, das dies tut.** +Wir haben gezeigt, wie man in einem HTML-Dokument korrekt escapet und wie entscheidend die Kenntnis des Kontexts ist, also des Ortes, an dem wir Daten ausgeben. Mit anderen Worten, wie kontextsensitives Escaping funktioniert. Obwohl dies eine notwendige Voraussetzung für eine funktionierende Abwehr von XSS ist, **ist Latte das einzige Template-System für PHP, das dies kann.** -Wie ist das möglich, wo doch heute alle Systeme behaupten, dass sie automatisch escapen? -Automatisches Escaping ohne Kenntnis des Kontexts ist ein Schwachsinn, der **ein falsches Gefühl von Sicherheit** erzeugt. +Wie ist das möglich, wenn alle Systeme heute behaupten, automatisches Escaping zu haben? Automatisches Escaping ohne Kontextkenntnis ist ein bisschen Blödsinn, der **ein falsches Gefühl der Sicherheit erzeugt**. -Templating-Systeme wie Twig, Laravel Blade und andere sehen keine HTML-Struktur in den Templates. Daher sehen sie auch keine Kontexte. Im Vergleich zu Latte sind sie blind und naiv. Sie verarbeiten nur ihr eigenes Markup, alles andere ist für sie ein irrelevanter Zeichenstrom: +Template-Systeme wie Twig, Laravel Blade und andere sehen keine HTML-Struktur im Template. Sie sehen daher auch keine Kontexte. Im Gegensatz zu Latte sind sie blind und naiv. Sie verarbeiten nur ihre eigenen Tags, alles andere ist für sie ein unwichtiger Zeichenstrom:
                                      -```twig .{file:Twig template as seen by Twig himself} -░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░ -░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░{{ text }}░░░░ +```twig .{file:Twig-Template, wie es Twig selbst sieht} +░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░ +░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░ ``` -```twig .{file:Twig template as the designer sees it} -- in text: {{ text }} -- in tag: -- in attribute: -- in unquoted attribute: -- in attribute containing URL: -- in attribute containing JavaScript: -- in attribute containing CSS: -- in JavaScriptu: -- in CSS: -- in comment: +```twig .{file:Twig-Template, wie es der Designer sieht} +- im Text: {{ foo }} +- im Tag: +- im Attribut: +- im Attribut ohne Anführungszeichen: +- im Attribut, das eine URL enthält: +- im Attribut, das JavaScript enthält: +- im Attribut, das CSS enthält: +- in JavaScript: +- in CSS: +- im Kommentar: ```
                                      -Naive Systeme wandeln die Zeichen von `< > & ' "` einfach mechanisch in HTML-Entities um, was in den meisten Fällen eine gültige Art des Escapings ist, aber bei weitem nicht immer. Daher können sie verschiedene Sicherheitslücken nicht erkennen oder verhindern, wie wir weiter unten zeigen werden. +Naive Systeme konvertieren nur mechanisch die Zeichen `< > & ' "` in HTML-Entitäten, was zwar in den meisten Anwendungsfällen eine gültige Escaping-Methode ist, aber bei weitem nicht immer. Sie können daher verschiedene Sicherheitslücken weder aufdecken noch verhindern, wie wir weiter unten zeigen werden. -Latte sieht die Vorlage auf die gleiche Weise wie Sie. Es versteht HTML, XML, erkennt Tags, Attribute usw. Und weil das so ist, unterscheidet es zwischen Kontexten und behandelt die Daten entsprechend. So bietet es einen wirklich effektiven Schutz gegen die kritische Cross-Site-Scripting-Schwachstelle. +Latte sieht das Template genauso wie Sie. Es versteht HTML, XML, erkennt Tags, Attribute usw. Und dank dessen unterscheidet es einzelne Kontexte und bereinigt die Daten entsprechend. Es bietet somit einen wirklich effektiven Schutz gegen die kritische Schwachstelle Cross-Site Scripting. + +
                                      + +```latte .{file:Latte-Template, wie es Latte sieht} +░░░░░░░░░░░{$foo} +░░░░░░░░░░ +░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░ +░░░░░░░░░ +░░░░░░░░░░░░░░░ +``` + +```latte .{file:Latte-Template, wie es der Designer sieht} +- im Text: {$foo} +- im Tag: +- im Attribut: +- im Attribut ohne Anführungszeichen: +- im Attribut, das eine URL enthält: +- im Attribut, das JavaScript enthält: +- im Attribut, das CSS enthält: +- in JavaScript: +- in CSS: +- im Kommentar: +``` + +
                                      -Live-Demonstration .[#toc-live-demonstration] -============================================= +Live-Demo +========= -Links sehen Sie die Vorlage in Latte, rechts den generierten HTML-Code. Die Variable `$text` wird mehrmals ausgegeben, jedes Mal in einem etwas anderen Kontext. Und daher auch ein bisschen anders escaped. Sie können den Code der Vorlage selbst bearbeiten, z.B. den Inhalt der Variablen ändern usw. Probieren Sie es aus: +Links sehen Sie das Template in Latte, rechts den generierten HTML-Code. Die Variable `$text` wird hier mehrmals ausgegeben, und jedes Mal in einem etwas anderen Kontext. Und daher auch etwas anders escapet. Sie können den Code des Templates selbst bearbeiten, zum Beispiel den Inhalt der Variablen ändern usw. Probieren Sie es aus:
                                      ``` .{file:template.latte; min-height: 14em}[fiddle-source] -{* TRY TO EDIT THIS TEMPLATE *} +{* VERSUCHEN SIE, DIESES TEMPLATE ZU BEARBEITEN *} {var $text = "Rock'n'Roll"} - {$text} - @@ -270,63 +286,61 @@ Links sehen Sie die Vorlage in Latte, rechts den generierten HTML-Code. Die Vari
                                      -Ist das nicht großartig! Latte macht kontextabhängiges Escaping automatisch, so dass der Programmierer: +Ist das nicht großartig! Latte erledigt das kontextsensitive Escaping automatisch, sodass der Programmierer: -- Er muss nicht nachdenken oder wissen, wie man Daten entschlüsselt +- nicht darüber nachdenken oder wissen muss, wie wo escapet wird - sich nicht irren kann -- es nicht vergessen kann +- das Escaping nicht vergessen kann -Dies sind noch nicht einmal alle Kontexte, die Latte bei der Ausgabe unterscheidet und für die es die Datenbehandlung anpasst. Wir werden jetzt weitere interessante Fälle durchgehen. +Das sind sogar nicht alle Kontexte, die Latte bei der Ausgabe unterscheidet und für die es die Datenbereinigung anpasst. Weitere interessante Fälle gehen wir jetzt durch. -Wie man naive Systeme hackt .[#toc-how-to-hack-naive-systems] -============================================================= +Wie man naive Systeme hackt +=========================== -Wir werden anhand einiger praktischer Beispiele zeigen, wie wichtig die Kontextdifferenzierung ist und warum naive Templating-Systeme im Gegensatz zu Latte keinen ausreichenden Schutz gegen XSS bieten. -Wir werden Twig als Vertreter eines naiven Systems in den Beispielen verwenden, aber das gilt auch für andere Systeme. +Anhand einiger praktischer Beispiele zeigen wir, wie wichtig die Unterscheidung von Kontexten ist und warum naive Template-Systeme keinen ausreichenden Schutz vor XSS bieten, im Gegensatz zu Latte. Als Vertreter eines naiven Systems verwenden wir in den Beispielen Twig, aber dasselbe gilt auch für andere Systeme. -Anfälligkeit für Attribute .[#toc-attribute-vulnerability] ----------------------------------------------------------- +Schwachstelle durch Attribut +---------------------------- -Versuchen wir nun, bösartigen Code in die Seite einzuschleusen, indem wir das HTML-Attribut wie [oben gezeigt |#How does the vulnerability arise] verwenden. Wir haben eine Vorlage in Twig, die ein Bild anzeigt: +Wir versuchen, bösartigen Code über ein HTML-Attribut in die Seite einzuschleusen, wie wir es [oben gezeigt haben |#Wie entsteht die Schwachstelle]. Nehmen wir ein Template in Twig, das ein Bild rendert: ```twig .{file:Twig} {{ ``` -Beachten Sie, dass die Attributwerte nicht in Anführungszeichen gesetzt sind. Der Programmierer hat sie vielleicht vergessen, was einfach passiert. In React wird der Code zum Beispiel so geschrieben, ohne Anführungszeichen, und ein Programmierer, der die Sprache wechselt, kann die Anführungszeichen leicht vergessen. +Beachten Sie, dass um die Attributwerte keine Anführungszeichen stehen. Der Programmierer könnte sie vergessen haben, was einfach passiert. Beispielsweise wird in React Code so geschrieben, ohne Anführungszeichen, und ein Programmierer, der zwischen Sprachen wechselt, kann die Anführungszeichen dann leicht vergessen. -Der Angreifer fügt eine geschickt konstruierte Zeichenfolge `foo onload=alert('Hacked!')` als Bildunterschrift ein. Wir wissen bereits, dass Twig nicht erkennen kann, ob eine Variable in einem HTML-Textstrom, in einem Attribut, in einem HTML-Kommentar usw. ausgegeben wird; kurz gesagt, es unterscheidet nicht zwischen den Kontexten. Und es konvertiert einfach mechanisch `< > & ' "` Zeichen in HTML-Entities. -Der resultierende Code sieht also so aus: +Ein Angreifer fügt als Bildbeschreibung eine geschickt konstruierte Zeichenkette `foo onload=alert('Gehackt!')` ein. Wir wissen bereits, dass Twig nicht erkennen kann, ob die Variable im Fluss des HTML-Textes, innerhalb eines Attributs, eines HTML-Kommentars usw. ausgegeben wird, kurz gesagt, es unterscheidet keine Kontexte. Und konvertiert nur mechanisch die Zeichen `< > & ' "` in HTML-Entitäten. Der resultierende Code sieht also so aus: ```html -foo +foo ``` -**Es wurde eine Sicherheitslücke geschaffen!** +**Und eine Sicherheitslücke ist entstanden!** -Ein gefälschtes `onload` -Attribut ist Teil der Seite geworden und der Browser führt es sofort nach dem Herunterladen des Bildes aus. +Teil der Seite wurde das eingeschleuste Attribut `onload`, und der Browser führt es sofort nach dem Herunterladen des Bildes aus. -Sehen wir uns nun an, wie Latte die gleiche Vorlage behandelt: +Sehen wir uns nun an, wie Latte mit demselben Template umgeht: ```latte .{file:Latte} {$imageAlt} ``` -Latte sieht die Vorlage auf die gleiche Weise wie Sie. Im Gegensatz zu Twig versteht es HTML und weiß, dass eine Variable als ein Attributwert gedruckt wird, der nicht in Anführungszeichen steht. Deshalb fügt es sie hinzu. Wenn ein Angreifer dieselbe Überschrift einfügt, sieht der resultierende Code wie folgt aus: +Latte sieht das Template genauso wie Sie. Im Gegensatz zu Twig versteht es HTML und weiß, dass die Variable als Wert eines Attributs ausgegeben wird, das nicht in Anführungszeichen steht. Deshalb ergänzt es sie. Wenn ein Angreifer dieselbe Beschreibung einfügt, sieht der resultierende Code so aus: ```html -foo onload=alert('Hacked!') +foo onload=alert('Gehackt!') ``` **Latte hat XSS erfolgreich verhindert.** -Drucken einer Variablen in JavaScript .[#toc-printing-a-variable-in-javascript] -------------------------------------------------------------------------------- +Ausgabe einer Variablen in JavaScript +------------------------------------- -Dank des kontextsensitiven Escapings ist es möglich, PHP-Variablen nativ in JavaScript zu verwenden. +Dank kontextsensitivem Escaping ist es ganz nativ möglich, PHP-Variablen innerhalb von JavaScript zu verwenden. ```latte

                                      {$movie}

                                      @@ -334,7 +348,7 @@ Dank des kontextsensitiven Escapings ist es möglich, PHP-Variablen nativ in Jav ``` -Wenn die Variable `$movie` die Zeichenkette `'Amarcord & 8 1/2'` speichert, erzeugt sie die folgende Ausgabe. Beachten Sie das unterschiedliche Escaping in HTML und JavaScript und auch im Attribut `onclick`: +Wenn die Variable `$movie` die Zeichenkette `'Amarcord & 8 1/2'` enthält, wird folgende Ausgabe generiert. Beachten Sie, dass innerhalb von HTML ein anderes Escaping verwendet wird als innerhalb von JavaScript und noch ein anderes im Attribut `onclick`: ```latte

                                      Amarcord & 8 1/2

                                      @@ -343,29 +357,27 @@ Wenn die Variable `$movie` die Zeichenkette `'Amarcord & 8 1/2'` speichert, erze ``` -Link-Prüfung .[#toc-link-checking] ----------------------------------- +Überprüfung von Links +--------------------- -Latte prüft automatisch, ob die in den Attributen `src` oder `href` verwendete Variable eine Web-URL (d.h. das HTTP-Protokoll) enthält und verhindert das Schreiben von Links, die ein Sicherheitsrisiko darstellen könnten. +Latte überprüft automatisch, ob eine in den Attributen `src` oder `href` verwendete Variable eine Web-URL enthält (d. h. das HTTP-Protokoll) und verhindert die Ausgabe von Links, die ein Sicherheitsrisiko darstellen können. ```latte {var $link = 'javascript:attack()'} -click here +klicken ``` -Schreibt: +Gibt aus: ```latte -click here +klicken ``` -Die Prüfung kann mit einem Filter [nocheck |filters#nocheck] ausgeschaltet werden. +Die Überprüfung kann mit dem Filter [nocheck |filters#nocheck] deaktiviert werden. -Grenzen der Latte .[#toc-limits-of-latte] -========================================= +Grenzen von Latte +================= -Latte ist kein vollständiger XSS-Schutz für die gesamte Anwendung. Wir wären unglücklich, wenn Sie bei der Verwendung von Latte aufhören würden, über Sicherheit nachzudenken. -Das Ziel von Latte ist es, sicherzustellen, dass ein Angreifer die Struktur einer Seite nicht verändern, HTML-Elemente oder Attribute manipulieren kann. Es prüft jedoch nicht die inhaltliche Korrektheit der ausgegebenen Daten. Oder die Korrektheit des Verhaltens von JavaScript. -Das liegt außerhalb des Aufgabenbereichs des Templating-Systems. Die Überprüfung der Korrektheit von Daten, insbesondere von solchen, die vom Benutzer eingegeben werden und daher nicht vertrauenswürdig sind, ist eine wichtige Aufgabe für den Programmierer. +Latte ist kein vollständiger Schutz vor XSS für die gesamte Anwendung. Wir möchten nicht, dass Sie bei der Verwendung von Latte aufhören, über Sicherheit nachzudenken. Das Ziel von Latte ist es sicherzustellen, dass ein Angreifer die Struktur der Seite nicht verändern, HTML-Elemente oder Attribute einschleusen kann. Aber es prüft nicht die inhaltliche Korrektheit der ausgegebenen Daten. Oder die Korrektheit des JavaScript-Verhaltens. Das geht über die Kompetenzen eines Template-Systems hinaus. Die Überprüfung der Korrektheit von Daten, insbesondere der vom Benutzer eingegebenen und somit nicht vertrauenswürdigen Daten, ist eine wichtige Aufgabe des Programmierers. diff --git a/latte/de/sandbox.texy b/latte/de/sandbox.texy index d933d30696..7c5d049942 100644 --- a/latte/de/sandbox.texy +++ b/latte/de/sandbox.texy @@ -1,12 +1,10 @@ -Sandkasten -********** +Sandbox +******* -.[perex]{data-version:2.8} -Sandbox bietet eine Sicherheitsebene, die Ihnen die Kontrolle darüber gibt, welche Tags, PHP-Funktionen, Methoden usw. in Vorlagen verwendet werden können. Dank des Sandbox-Modus können Sie bei der Vorlagenerstellung sicher mit einem Kunden oder einem externen Programmierer zusammenarbeiten, ohne sich Gedanken über eine Beeinträchtigung der Anwendung oder unerwünschte Vorgänge zu machen. +.[perex] +Die Sandbox bietet eine Sicherheitsschicht, die Ihnen die Kontrolle darüber gibt, welche Tags, PHP-Funktionen, Methoden usw. in Templates verwendet werden dürfen. Dank des Sandbox-Modus können Sie sicher mit Kunden oder externen Programmierern an der Erstellung von Templates zusammenarbeiten, ohne befürchten zu müssen, dass die Anwendung beschädigt wird oder unerwünschte Operationen ausgeführt werden. -Wie funktioniert das? Wir legen einfach fest, was wir in der Vorlage zulassen wollen. Zu Beginn ist alles verboten, und wir geben nach und nach die Berechtigungen frei: - -Der folgende Code erlaubt der Vorlage die Verwendung der Tags `{block}`, `{if}`, `{else}` und `{=}` (letzteres ist ein Tag zum [Drucken einer Variablen oder eines Ausdrucks |tags#Printing]) und aller Filter: +Wie funktioniert das? Wir definieren einfach, was dem Template alles erlaubt ist. Dabei ist standardmäßig alles verboten, und wir erlauben nach und nach mehr. Mit dem folgenden Code erlauben wir dem Template-Autor, die Tags `{block}`, `{if}`, `{else}` und `{=}` zu verwenden, was das Tag für die [Ausgabe einer Variablen oder eines Ausdrucks |tags#Ausgabe] ist, sowie alle Filter: ```php $policy = new Latte\Sandbox\SecurityPolicy; @@ -16,7 +14,7 @@ $policy->allowFilters($policy::All); $latte->setPolicy($policy); ``` -Wir können auch den Zugriff auf globale Funktionen, Methoden oder Eigenschaften von Objekten erlauben: +Weiterhin können wir einzelne Funktionen, Methoden oder Eigenschaften von Objekten erlauben: ```php $policy->allowFunctions(['trim', 'strlen']); @@ -24,30 +22,35 @@ $policy->allowMethods(Nette\Security\User::class, ['isLoggedIn', 'isAllowed']); $policy->allowProperties(Nette\Database\Row::class, $policy::All); ``` -Ist das nicht erstaunlich? Sie können alles auf einer sehr niedrigen Ebene kontrollieren. Wenn die Vorlage versucht, eine nicht zugelassene Funktion aufzurufen oder auf eine nicht zugelassene Methode oder Eigenschaft zuzugreifen, löst sie die Ausnahme `Latte\SecurityViolationException` aus. +Ist das nicht erstaunlich? Sie können auf sehr niedriger Ebene absolut alles kontrollieren. Wenn das Template versucht, eine nicht erlaubte Funktion aufzurufen oder auf eine nicht erlaubte Methode oder Eigenschaft zuzugreifen, endet dies mit einer Ausnahme `Latte\SecurityViolationException`. -Die Erstellung von Richtlinien von Grund auf, wenn alles verboten ist, ist vielleicht nicht sehr bequem, daher können Sie von einer sicheren Grundlage ausgehen: +Eine Policy von Grund auf zu erstellen, bei der absolut alles verboten ist, mag nicht bequem sein, daher können Sie von einer sicheren Basis ausgehen: ```php $policy = Latte\Sandbox\SecurityPolicy::createSafePolicy(); ``` -Das bedeutet, dass alle Standard-Tags erlaubt sind mit Ausnahme von `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget`. -Alle Standardfilter sind ebenfalls erlaubt, mit Ausnahme von `datastream`, `noescape` und `nocheck`. Schließlich ist auch der Zugriff auf die Methoden und Eigenschaften des Objekts `$iterator` erlaubt. +Sichere Basis bedeutet, dass alle Standard-Tags erlaubt sind außer `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget`. Erlaubt sind die Standardfilter außer `datastream`, `noescape` und `nocheck`. Und schließlich ist der Zugriff auf Methoden und Eigenschaften des Objekts `$iterator` erlaubt. -Die Regeln gelten für die Vorlage, die wir mit dem neuen [`{sandbox}` |tags#Including Templates] Tag einfügen. Das ist so etwas wie `{include}`, aber es schaltet den Sandbox-Modus ein und übergibt auch keine externen Variablen: +Die Regeln werden auf das Template angewendet, das wir mit dem Tag [`{sandbox}` |tags#Template einfügen] einfügen. Dies ist eine Art Analogon zu `{include}`, das jedoch den sicheren Modus einschaltet und auch keine Variablen übergibt: ```latte {sandbox 'untrusted.latte'} ``` -Das Layout und die einzelnen Seiten können also alle Tags und Variablen wie bisher verwenden, Einschränkungen gelten nur für die Vorlage `untrusted.latte`. +Layout und einzelne Seiten können also ungestört alle Tags und Variablen verwenden, nur auf das Template `untrusted.latte` werden die Einschränkungen angewendet. -Einige Verstöße, wie die Verwendung eines verbotenen Tags oder Filters, werden zur Kompilierzeit erkannt. Andere, wie z. B. der Aufruf nicht erlaubter Methoden eines Objekts, zur Laufzeit. -Die Vorlage kann auch beliebige andere Fehler enthalten. Um zu verhindern, dass eine Ausnahme von der Sandbox-Vorlage ausgelöst wird, die das gesamte Rendering unterbricht, können Sie einen [eigenen Exception-Handler |develop#exception handler] definieren, der die Ausnahme z. B. nur protokolliert. +Einige Verstöße, wie die Verwendung eines verbotenen Tags oder Filters, werden zur Kompilierungszeit erkannt. Andere, wie der Aufruf nicht erlaubter Methoden eines Objekts, erst zur Laufzeit. Das Template kann auch beliebige andere Fehler enthalten. Damit Ihnen aus dem sandboxed Template keine Ausnahme entweichen kann, die das gesamte Rendering stört, kann ein eigener [Ausnahme-Handler |develop#Exception Handler] definiert werden, der sie beispielsweise protokolliert. -Wenn wir den Sandbox-Modus direkt für alle Vorlagen einschalten wollen, ist das ganz einfach: +Wenn wir den Sandbox-Modus direkt für alle Templates aktivieren möchten, geht das einfach: ```php $latte->setSandboxMode(); ``` + +Um sicherzustellen, dass der Benutzer keinen PHP-Code in die Seite einfügt, der zwar syntaktisch korrekt ist, aber verboten ist und einen PHP Compile Error verursacht, empfehlen wir, [Templates vom PHP-Linter überprüfen zu lassen |develop#Überprüfung des generierten Codes]. Diese Funktionalität aktivieren Sie mit der Methode `Engine::enablePhpLint()`. Da zur Überprüfung die PHP-Binärdatei aufgerufen werden muss, übergeben Sie den Pfad dorthin als Parameter: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/pfad/zu/php'); +``` diff --git a/latte/de/syntax.texy b/latte/de/syntax.texy index 64815111c7..2e627762e3 100644 --- a/latte/de/syntax.texy +++ b/latte/de/syntax.texy @@ -2,78 +2,76 @@ Syntax ****** .[perex] -Syntax Latte wurde aus den praktischen Anforderungen von Webdesignern geboren. Wir waren auf der Suche nach einer möglichst benutzerfreundlichen Syntax, mit der man auf elegante Weise Konstrukte schreiben kann, die sonst eine echte Herausforderung sind. -Gleichzeitig sind alle Ausdrücke genau so geschrieben wie in PHP, so dass Sie keine neue Sprache lernen müssen. Sie machen einfach das Beste aus dem, was Sie bereits kennen. +Die Syntax von Latte entstand aus den praktischen Anforderungen von Webdesignern. Wir suchten nach der benutzerfreundlichsten Syntax, mit der Sie auch Konstrukte elegant schreiben können, die sonst eine echte Nuss darstellen. Gleichzeitig werden alle Ausdrücke genauso wie in PHP geschrieben, sodass Sie keine neue Sprache lernen müssen. Sie nutzen einfach das, was Sie schon lange können. -Im Folgenden finden Sie eine minimale Vorlage, die einige grundlegende Elemente veranschaulicht: Tags, n:Attribute, Kommentare und Filter. +Unten sehen Sie ein minimales Template, das einige grundlegende Elemente veranschaulicht: Tags, n:Attribute, Kommentare und Filter. ```latte {* dies ist ein Kommentar *} -
                                        {* n:if ist n:atribut *} -{foreach $items as $item} {* Tag, der eine foreach-Schleife darstellt *} +
                                          {* n:if ist ein n:Attribut *} +{foreach $items as $item} {* Tag, das eine foreach-Schleife darstellt *}
                                        • {$item|capitalize}
                                        • {* Tag, das eine Variable mit einem Filter ausgibt *} -{/foreach} {* Ende des Zyklus *} +{/foreach} {* Ende der Schleife *}
                                        ``` -Werfen wir einen genaueren Blick auf diese wichtigen Elemente und wie sie Ihnen helfen können, eine unglaubliche Vorlage zu erstellen. +Sehen wir uns diese wichtigen Elemente genauer an und wie sie Ihnen helfen können, ein erstaunliches Template zu erstellen. -Tags .[#toc-tags] -================= +Tags +==== -Eine Vorlage enthält Tags, die die Logik der Vorlage (z. B. *foreach*-Schleifen) oder die Ausgabeausdrücke steuern. Für beides wird ein einziges Begrenzungszeichen `{ ... }` verwendet, so dass Sie nicht wie bei anderen Systemen überlegen müssen, welches Begrenzungszeichen in welcher Situation zu verwenden ist. -Wenn auf das `{`-Zeichen ein Anführungszeichen oder ein Leerzeichen folgt, betrachtet Latte es nicht als Beginn eines Tags, so dass Sie ohne Probleme JavaScript-Konstrukte, JSON oder CSS-Regeln in Ihren Vorlagen verwenden können. +Ein Template enthält Tags, die die Logik des Templates steuern (z. B. *foreach*-Schleifen) oder Ausdrücke ausgeben. Für beides wird ein einziger Delimiter `{ ... }` verwendet, sodass Sie nicht überlegen müssen, welchen Delimiter Sie in welcher Situation verwenden sollen, wie es bei anderen Systemen der Fall ist. Wenn auf das Zeichen `{` ein Anführungszeichen oder ein Leerzeichen folgt, betrachtet Latte es nicht als Anfang eines Tags, wodurch Sie in Templates problemlos auch JavaScript-Konstrukte, JSON oder Regeln in CSS verwenden können. -Siehe [Übersicht über alle Tags |tags]. Darüber hinaus können Sie auch [benutzerdefinierte Tags |extending-latte#tags] erstellen. +Sehen Sie sich die [Übersicht aller Tags |tags] an. Darüber hinaus können Sie auch [eigene Tags erstellen |custom tags]. -Latte versteht PHP .[#toc-latte-understands-php] -================================================ +Latte versteht PHP +================== -Sie können PHP-Ausdrücke, die Sie gut kennen, innerhalb der Tags verwenden: +Innerhalb von Tags können Sie PHP-Ausdrücke verwenden, die Sie gut kennen: - Variablen -- Zeichenketten (einschließlich HEREDOC und NOWDOC), Arrays, Zahlen, usw. +- Zeichenketten (einschließlich HEREDOC und NOWDOC), Arrays, Zahlen usw. - [Operatoren |https://www.php.net/manual/en/language.operators.php] - Funktions- und Methodenaufrufe (die durch die [Sandbox |sandbox] eingeschränkt werden können) -- [Übereinstimmung |https://www.php.net/manual/en/control-structures.match.php] +- [match |https://www.php.net/manual/en/control-structures.match.php] - [anonyme Funktionen |https://www.php.net/manual/en/functions.arrow.php] -- [Rückrufe |https://www.php.net/manual/en/functions.first_class_callable_syntax.php] +- [Callbacks |https://www.php.net/manual/en/functions.first_class_callable_syntax.php] - mehrzeilige Kommentare `/* ... */` -- etc... +- usw. -Darüber hinaus fügt Latte einige [nette Erweiterungen |#Syntactic Sugar] der PHP-Syntax hinzu. +Latte ergänzt die PHP-Syntax außerdem um einige [angenehme Erweiterungen |#Syntaktischer Zucker]. -n:Attribute .[#toc-n-attributes] -================================ +n:Attribute +=========== -Jedes Tag-Paar, wie z. B. `{if} … {/if}`, das auf ein einzelnes HTML-Element wirkt, kann in [n:Attribut-Notation |#n:attribute] geschrieben werden. Zum Beispiel könnte `{foreach}` im obigen Beispiel auch so geschrieben werden: +Alle paarweisen Tags, wie z. B. `{if} … {/if}`, die auf einem einzelnen HTML-Element operieren, können in Form von n:Attributen umgeschrieben werden. So könnte beispielsweise auch `{foreach}` im einleitenden Beispiel geschrieben werden: ```latte -
                                          +
                                          • {$item|capitalize}
                                          ``` -Die Funktionalität entspricht dann dem HTML-Element, in das sie geschrieben ist: +Die Funktionalität bezieht sich dann auf das HTML-Element, in das sie platziert ist: ```latte -{var $items = ['I', '♥', 'Latte']} +{var $items = ['Ich', '♥', 'Latte']}

                                          {$item}

                                          ``` -Druckt: +gibt aus: ```latte -

                                          I

                                          +

                                          Ich

                                          Latte

                                          ``` -Durch die Verwendung des Präfixes `inner-` können wir das Verhalten so ändern, dass die Funktionalität nur für den Körper des Elements gilt: +Mit dem Präfix `inner-` können wir das Verhalten so anpassen, dass es sich nur auf den inneren Teil des Elements bezieht: ```latte
                                          @@ -82,11 +80,11 @@ Durch die Verwendung des Präfixes `inner-` können wir das Verhalten so ändern
                                          ``` -Druckt: +Es wird ausgegeben: ```latte
                                          -

                                          I

                                          +

                                          Ich



                                          @@ -95,68 +93,82 @@ Druckt:
                                          ``` -Oder durch die Verwendung des Präfixes `tag-` wird die Funktionalität nur auf die HTML-Tags angewendet: +Oder mit dem Präfix `tag-` wenden wir die Funktionalität nur auf die HTML-Tags selbst an: ```latte -

                                          Title

                                          +

                                          Titel

                                          ``` -Je nach dem Wert der Variable `$url` wird dies gedruckt: +Dies gibt abhängig von der Variablen `$url` aus: ```latte -// when $url is empty -

                                          Title

                                          +{* wenn $url leer ist *} +

                                          Titel

                                          -// when $url equals 'https://nette.org' -

                                          Title

                                          +{* wenn $url 'https://nette.org' enthält *} +

                                          Titel

                                          ``` -Allerdings sind n:Attribute nicht nur eine Abkürzung für Paar-Tags, es gibt auch einige reine n:Attribute, zum Beispiel den besten Freund des Programmierers [n:class |tags#n:class]. +n:Attribute sind jedoch nicht nur Abkürzungen für paarweise Tags. Es gibt auch reine n:Attribute, wie z. B. [n:href |application:creating-links#Im Presenter-Template] oder der sehr praktische Helfer für Programmierer [n:class |tags#n:class]. -Filter .[#toc-filters] -====================== +Filter +====== -Siehe die Zusammenfassung der [Standardfilter |filters]. +Sehen Sie sich die Übersicht der [Standardfilter |filters] an. -Latte erlaubt den Aufruf von Filtern mit Hilfe der Pipe-Schreibweise (vorangestelltes Leerzeichen ist erlaubt): +Filter werden nach einem senkrechten Strich geschrieben (ein Leerzeichen davor ist erlaubt): ```latte

                                          {$heading|upper}

                                          ``` -Filter können verkettet werden, in diesem Fall gelten sie in der Reihenfolge von links nach rechts: +Filter können verkettet werden und werden dann in der Reihenfolge von links nach rechts angewendet: ```latte

                                          {$heading|lower|capitalize}

                                          ``` -Die Parameter werden durch Doppelpunkt oder Komma getrennt hinter den Filternamen gesetzt: +Parameter werden nach dem Filternamen, getrennt durch Doppelpunkte oder Kommas, angegeben: ```latte

                                          {$heading|truncate:20,''}

                                          ``` -Filter können auf Ausdrücke angewendet werden: +Filter können auch auf einen Ausdruck angewendet werden: ```latte {var $name = ($title|upper) . ($subtitle|lower)} ``` -Auf Block: +Auf einen Block: ```latte

                                          {block |lower}{$heading}{/block}

                                          ``` -Oder direkt beim Wert (in Kombination mit [`{=expr}` | https://latte.nette.org/de/tags#printing] Tag): +Oder direkt auf den Wert (in Kombination mit dem Tag [`{=expr}` |tags#Ausgabe]): +```latte +

                                          {=' Hallo Welt '|trim}

                                          +``` + + +Dynamische HTML-Tags .{data-version:3.0.9} +========================================== + +Latte unterstützt dynamische HTML-Tags, die nützlich sind, wenn Sie Flexibilität bei den Tag-Namen benötigen: + ```latte -

                                          {=' Hello world '|trim}

                                          +Überschrift ``` +Der obige Code kann beispielsweise `

                                          Überschrift

                                          ` oder `

                                          Überschrift

                                          ` generieren, abhängig vom Wert der Variablen `$level`. Dynamische HTML-Tags in Latte müssen immer paarweise sein. Ihre Alternative ist [n:tag |tags#n:tag]. -Kommentare .[#toc-comments] -=========================== +Da Latte ein sicheres Template-System ist, prüft es, ob der resultierende Tag-Name gültig ist und keine unerwünschten oder schädlichen Werte enthält. Es stellt außerdem sicher, dass der Name des schließenden Tags immer derselbe ist wie der Name des öffnenden Tags. + + +Kommentare +========== Kommentare werden auf diese Weise geschrieben und gelangen nicht in die Ausgabe: @@ -164,109 +176,101 @@ Kommentare werden auf diese Weise geschrieben und gelangen nicht in die Ausgabe: {* dies ist ein Kommentar in Latte *} ``` -PHP-Kommentare funktionieren innerhalb von Tags: +Innerhalb von Tags funktionieren PHP-Kommentare: ```latte {include 'file.info', /* value: 123 */} ``` -Syntaktischer Zucker .[#toc-syntactic-sugar] -============================================ +Syntaktischer Zucker +==================== -Zeichenketten ohne Anführungszeichen .[#toc-strings-without-quotation-marks] ----------------------------------------------------------------------------- +Zeichenketten ohne Anführungszeichen +------------------------------------ Bei einfachen Zeichenketten können die Anführungszeichen weggelassen werden: ```latte -as in PHP: {var $arr = ['hello', 'btn--default', '€']} +wie in PHP: {var $arr = ['hallo', 'btn--default', '€']} + +verkürzt: {var $arr = [hallo, btn--default, €]} +``` + +Einfache Zeichenketten sind solche, die rein aus Buchstaben, Ziffern, Unterstrichen, Bindestrichen und Punkten bestehen. Sie dürfen nicht mit einer Ziffer beginnen und nicht mit einem Bindestrich beginnen oder enden. Sie dürfen nicht nur aus Großbuchstaben und Unterstrichen bestehen, da sie dann als Konstante betrachtet werden (z. B. `PHP_VERSION`). Und sie dürfen nicht mit Schlüsselwörtern kollidieren: `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor`. + -abbreviated: {var $arr = [hello, btn--default, €]} +Konstanten +---------- + +Da bei einfachen Zeichenketten die Anführungszeichen weggelassen werden können, empfehlen wir zur Unterscheidung, globale Konstanten mit einem Schrägstrich am Anfang zu schreiben: + +```latte +{if \PROJECT_ID === 1} ... {/if} ``` -Einfache Zeichenfolgen sind Zeichenfolgen, die nur aus Buchstaben, Ziffern, Unterstrichen, Bindestrichen und Punkten bestehen. Sie dürfen nicht mit einer Ziffer beginnen und dürfen nicht mit einem Bindestrich beginnen oder enden. -Sie darf nicht nur aus Großbuchstaben und Unterstrichen bestehen, denn dann gilt sie als Konstante (z. B. `PHP_VERSION`). -Und er darf nicht mit den Schlüsselwörtern `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor` kollidieren. +Diese Schreibweise ist in PHP selbst völlig gültig, der Schrägstrich besagt, dass die Konstante im globalen Namespace liegt. -Kurzer ternärer Operator .[#toc-short-ternary-operator] -------------------------------------------------------- +Verkürzter ternärer Operator +---------------------------- Wenn der dritte Wert des ternären Operators leer ist, kann er weggelassen werden: ```latte -as in PHP: {$stock ? 'In stock' : ''} +wie in PHP: {$stock ? 'Auf Lager' : ''} -abbreviated: {$stock ? 'In stock'} +verkürzt: {$stock ? 'Auf Lager'} ``` -Moderne Schlüsselschreibweise im Array .[#toc-modern-key-notation-in-the-array] -------------------------------------------------------------------------------- +Moderne Schreibweise von Schlüsseln in Arrays +--------------------------------------------- -Array-Schlüssel können ähnlich wie benannte Parameter beim Aufruf von Funktionen geschrieben werden: +Schlüssel in Arrays können ähnlich wie benannte Parameter beim Funktionsaufruf geschrieben werden: ```latte -as in PHP: {var $arr = ['one' => 'item 1', 'two' => 'item 2']} +wie in PHP: {var $arr = ['one' => 'item 1', 'two' => 'item 2']} modern: {var $arr = [one: 'item 1', two: 'item 2']} ``` -Filter .[#toc-filters] ----------------------- +Filter +------ -Filter können für jeden beliebigen Ausdruck verwendet werden, indem man das Ganze in Klammern setzt: +Filter können für beliebige Ausdrücke verwendet werden, es genügt, das Ganze in Klammern zu setzen: ```latte {var $content = ($text|truncate: 30|upper)} ``` -Operator `in` .[#toc-operator-in] ---------------------------------- +Operator `in` +------------- -Der Operator `in` kann anstelle der Funktion `in_array()` verwendet werden. Der Vergleich ist immer streng: +Mit dem Operator `in` kann die Funktion `in_array()` ersetzt werden. Der Vergleich ist immer strikt: ```latte -{* wie in_array($item, $items, true) *} +{* Äquivalent zu in_array($item, $items, true) *} {if $item in $items} ... {/if} ``` -.{data-version:2.9} -Optionale Verkettung mit Undefined-Safe-Operator .[#toc-optional-chaining-with-undefined-safe-operator] -------------------------------------------------------------------------------------------------------- +Historisches Fenster +-------------------- -Der undefiniert-sichere Operator `??->` ähnelt dem nullsicheren Operator `?->`, löst aber keinen Fehler aus, wenn eine Variable, eine Eigenschaft oder ein Index überhaupt nicht existiert. +Latte brachte im Laufe seiner Geschichte eine ganze Reihe von syntaktischen Zuckern mit, die nach einigen Jahren in PHP selbst auftauchten. Beispielsweise war es in Latte möglich, Arrays als `[1, 2, 3]` anstelle von `array(1, 2, 3)` zu schreiben oder den Nullsafe-Operator `$obj?->foo` zu verwenden, lange bevor dies in PHP selbst möglich war. Latte führte auch den Operator zum Entpacken von Arrays `(expand) $arr` ein, der dem heutigen Operator `...$arr` aus PHP entspricht. -```latte -{$order??->id} -``` +Der Undefined-Safe-Operator `??->`, eine Analogie zum Nullsafe-Operator `?->`, der jedoch keinen Fehler auslöst, wenn die Variable nicht existiert, entstand aus historischen Gründen, und heute empfehlen wir die Verwendung des Standard-PHP-Operators `?->`. -Dies ist eine Art zu sagen, dass, wenn `$order` definiert und nicht null ist, `$order->id` berechnet wird, aber wenn `$order` null ist oder nicht existiert, stoppen wir, was wir tun und geben einfach null zurück. -```latte -{$user??->address??->street} -// roughly means isset($user) && isset($user->address) ? $user->address->street : null -``` - - -Ein Fenster in die Geschichte .[#toc-a-window-into-history] ------------------------------------------------------------ - -Latte hat im Laufe seiner Geschichte eine Reihe von syntaktischen Bonbons entwickelt, die ein paar Jahre später in PHP selbst auftauchten. Zum Beispiel war es in Latte möglich, Arrays als `[1, 2, 3]` anstelle von `array(1, 2, 3)` zu schreiben oder den Nullsafe-Operator `$obj?->foo` zu verwenden, lange bevor dies in PHP selbst möglich war. Latte führte auch den Arrayerweiterungsoperator `(expand) $arr` ein, der dem heutigen `...$arr` Operator von PHP entspricht. - - -PHP-Einschränkungen in Latte .[#toc-php-limitations-in-latte] -============================================================= +Einschränkungen von PHP in Latte +================================ -In Latte können nur PHP-Ausdrücke geschrieben werden. Das heißt, Sie können keine Klassen deklarieren oder [Kontrollstrukturen |https://www.php.net/manual/en/language.control-structures.php] verwenden, wie `if`, `foreach`, `switch`, `return`, `try`, `throw` und andere, an deren Stelle Latte seine [Tags |tags] anbietet. -Sie können auch keine [Attribute |https://www.php.net/manual/en/language.attributes.php], [Backticks |https://www.php.net/manual/en/language.operators.execution.php] oder [magische Konstanten |https://www.php.net/manual/en/language.constants.magic.php] verwenden, denn das würde keinen Sinn machen. -Sie können nicht einmal `unset`, `echo`, `include`, `require`, `exit`, `eval` verwenden, da es sich nicht um Funktionen, sondern um spezielle PHP-Sprachkonstrukte und somit nicht um Ausdrücke handelt. +In Latte können nur PHP-Ausdrücke geschrieben werden. Das bedeutet, dass keine Anweisungen verwendet werden können, die mit einem Semikolon enden. Klassen können nicht deklariert oder [Steuerstrukturen |https://www.php.net/manual/en/language.control-structures.php] verwendet werden, z. B. `if`, `foreach`, `switch`, `return`, `try`, `throw` und andere, für die Latte seine eigenen [Tags |tags] anbietet. Ebenso können keine [Attribute |https://www.php.net/manual/en/language.attributes.php], [Backticks |https://www.php.net/manual/en/language.operators.execution.php] oder einige [magische Konstanten |https://www.php.net/manual/en/language.constants.magic.php] verwendet werden. Auch `unset`, `echo`, `include`, `require`, `exit`, `eval` können nicht verwendet werden, da es sich nicht um Funktionen, sondern um spezielle Sprachkonstrukte von PHP handelt und somit keine Ausdrücke sind. Kommentare werden nur als mehrzeilige `/* ... */` unterstützt. -Sie können diese Einschränkungen jedoch umgehen, indem Sie die [RawPhpExtension-Erweiterung |develop#RawPhpExtension] aktivieren, die es Ihnen ermöglicht, beliebigen PHP-Code im `{php ...}` -Tag auf Verantwortung des Vorlagenautors zu verwenden. +Diese Einschränkungen können jedoch umgangen werden, indem Sie die Erweiterung [RawPhpExtension |develop#RawPhpExtension] aktivieren, dank derer dann im Tag `{php ...}` beliebiger PHP-Code auf Verantwortung des Template-Autors verwendet werden kann. diff --git a/latte/de/tags.texy b/latte/de/tags.texy index 6fc77a6e71..dbb8553646 100644 --- a/latte/de/tags.texy +++ b/latte/de/tags.texy @@ -2,167 +2,173 @@ Latte Tags ********** .[perex] -Zusammenfassung und Beschreibung aller in Latte integrierten Tags. +Überblick und Beschreibung aller Tags, die Ihnen standardmäßig im Latte-Templating-System zur Verfügung stehen. .[table-latte-tags language-latte] -|## Drucken -| `{$var}`, `{...}` oder `{=...}` | [druckt eine Variable oder einen Ausdruck mit Escapezeichen |#printing] -| `{$var\|filter}` | [druckt mit Filtern |#filters] -| `{l}` oder `{r}` | druckt `{` or `}` Zeichen +|## Ausgabe +| `{$var}`, `{...}` oder `{=...}` | [gibt eine maskierte Variable oder einen Ausdruck aus |#Ausgabe] +| `{$var\|filter}` | [gibt unter Verwendung von Filtern aus |#Filter] +| `{l}` oder `{r}` | gibt das Zeichen `{` oder `}` aus .[table-latte-tags language-latte] |## Bedingungen -| `{if}`... `{elseif}`... `{else}`... `{/if}` | [Bedingung if |#if-elseif-else] -| `{ifset}`... `{elseifset}`... `{/ifset}` | [Bedingung ifset |#ifset-elseifset] -| `{ifchanged}`... `{/ifchanged}` | [Test, ob eine Änderung stattgefunden hat |#ifchanged] -| `{switch}` `{case}` `{default}` `{/switch}` | [Bedingung switch |#switch-case-default] +| `{if}` … `{elseif}` … `{else}` … `{/if}` | [if-Bedingung |#if elseif else] +| `{ifset}` … `{elseifset}` … `{/ifset}` | [ifset-Bedingung |#ifset elseifset] +| `{ifchanged}` … `{/ifchanged}` | [Prüft auf Änderungen |#ifchanged] +| `{switch}` `{case}` `{default}` `{/switch}` | [switch-Bedingung |#switch case default] +| `n:else` | [alternativer Inhalt für Bedingungen |#n:else] .[table-latte-tags language-latte] |## Schleifen -| `{foreach}`... `{/foreach}` | [foreach |#foreach] -| `{for}`... `{/for}` | [for |#for] -| `{while}`... `{/while}` | [while |#while] -| `{continueIf $cond}` | [weiter zur nächsten Iteration |#continueif-skipif-breakif] -| `{skipIf $cond}` | [überspringt die aktuelle Schleifeniteration |#continueif-skipif-breakif] -| `{breakIf $cond}` | [bricht Schleife ab |#continueif-skipif-breakif] -| `{exitIf $cond}` | [vorzeitiges Beenden |#exitif] -| `{first}`... `{/first}` | [ist dies die erste Iteration? |#first-last-sep] -| `{last}`... `{/last}` | [ist es die letzte Iteration? |#first-last-sep] -| `{sep}`... `{/sep}` | [wird die nächste Iteration folgen? |#first-last-sep] -| `{iterateWhile}`... `{/iterateWhile}` | [strukturiert foreach |#iterateWhile] -| `$iterator` | [Spezielle Variable innerhalb der foreach-Schleife |#$iterator] +| `{foreach}` … `{/foreach}` | [#foreach] +| `{for}` … `{/for}` | [#for] +| `{while}` … `{/while}` | [#while] +| `{continueIf $cond}` | [mit der nächsten Iteration fortfahren |#continueIf skipIf breakIf] +| `{skipIf $cond}` | [Iteration überspringen |#continueIf skipIf breakIf] +| `{breakIf $cond}` | [Schleife unterbrechen |#continueIf skipIf breakIf] +| `{exitIf $cond}` | [vorzeitige Beendigung |#exitIf] +| `{first}` … `{/first}` | [ist es der erste Durchlauf? |#first last sep] +| `{last}` … `{/last}` | [ist es der letzte Durchlauf? |#first last sep] +| `{sep}` … `{/sep}` | [wird noch ein Durchlauf folgen? |#first last sep] +| `{iterateWhile}` … `{/iterateWhile}` | [strukturierter foreach |#iterateWhile] +| `$iterator` | [spezielle Variable innerhalb von foreach |#iterator] .[table-latte-tags language-latte] -|## Einbindung anderer Vorlagen -| `{include 'file.latte'}` | [schließt eine Vorlage aus einer anderen Datei ein |#include] -| `{sandbox 'file.latte'}` | [Einfügen einer Vorlage im Sandbox-Modus |#sandbox] +|## Einfügen weiterer Templates +| `{include 'file.latte'}` | [lädt Template aus einer anderen Datei |#include] +| `{sandbox 'file.latte'}` | [lädt Template im Sandbox-Modus |#sandbox] .[table-latte-tags language-latte] -|## Blöcke, Layouts, Vererbung von Vorlagen -| `{block}` | [anonymer Block |#block] -| `{block blockname}` | [Blockdefinition |template-inheritance#blocks] -| `{define blockname}` | [Blockdefinition für zukünftige Verwendung |template-inheritance#definitions] -| `{include blockname}` | [druckt Block |template-inheritance#printing-blocks] -| `{include blockname from 'file.latte'}` | [druckt einen Block aus einer Datei |template-inheritance#printing-blocks] -| `{import 'file.latte'}` | [lädt Blöcke aus einer anderen Vorlage |template-inheritance#horizontal-reuse] -| `{layout 'file.latte'}` / `{extends}` | [gibt eine Layout-Datei an |template-inheritance#layout-inheritance] -| `{embed}`... `{/embed}` | [lädt die Vorlage oder den Block und ermöglicht das Überschreiben der Blöcke |template-inheritance#unit-inheritance] -| `{ifset blockname}`... `{/ifset}` | [Bedingung, wenn Block definiert ist |template-inheritance#checking-block-existence] +|## Blöcke, Layouts, Template-Vererbung +| `{block}` | [anonymer Block |#block] +| `{block blockname}` | [definiert einen Block |template-inheritance#Blöcke] +| `{define blockname}` | [definiert einen Block zur späteren Verwendung |template-inheritance#Definition] +| `{include blockname}` | [Rendern eines Blocks |template-inheritance#Rendern von Blöcken] +| `{include blockname from 'file.latte'}` | [rendert einen Block aus einer Datei |template-inheritance#Rendern von Blöcken] +| `{import 'file.latte'}` | [lädt Blöcke aus einem Template |template-inheritance#Horizontale Wiederverwendung] +| `{layout 'file.latte'}` / `{extends}` | [bestimmt die Datei mit dem Layout |template-inheritance#Layout-Vererbung] +| `{embed}` … `{/embed}` | [lädt ein Template oder einen Block und ermöglicht das Überschreiben von Blöcken |template-inheritance#Einheiten-Vererbung] +| `{ifset blockname}` … `{/ifset}` | [Bedingung, ob ein Block existiert |template-inheritance#Existenzprüfung von Blöcken] .[table-latte-tags language-latte] |## Ausnahmebehandlung -| `{try}`... `{else}`... `{/try}` | [Abfangen von Ausnahmen |#try] -| `{rollback}` | [verwirft try-Block |#rollback] +| `{try}` … `{else}` … `{/try}` | [Abfangen von Ausnahmen |#try] +| `{rollback}` | [Verwerfen des try-Blocks |#rollback] .[table-latte-tags language-latte] |## Variablen -| `{var $foo = value}` | [Erstellung von Variablen |#var-default] -| `{default $foo = value}` | [Standardwert, wenn Variable nicht deklariert ist |#var-default] -| `{parameters}` | [deklariert Variablen, gibt einen Standardwert ein |#parameters] -| `{capture}`... `{/capture}` | [erfasst einen Abschnitt in einer Variablen |#capture] +| `{var $foo = value}` | [erstellt eine Variable |#var default] +| `{default $foo = value}` | [erstellt eine Variable, falls sie nicht existiert |#var default] +| `{parameters}` | [deklariert Variablen, Typen und Standardwerte |#parameters] +| `{capture}` … `{/capture}` | [fängt einen Block in einer Variablen ab |#capture] .[table-latte-tags language-latte] |## Typen -| `{varType}` | [deklariert den Typ einer Variablen |type-system#varType] -| `{varPrint}` | [schlägt Variablentypen vor |type-system#varPrint] -| `{templateType}` | [deklariert Variablentypen mittels Klasse |type-system#templateType] -| `{templatePrint}` | [erzeugt Klasse mit Eigenschaften |type-system#templatePrint] +| `{varType}` | [deklariert den Typ einer Variablen |type-system#varType] +| `{varPrint}` | [schlägt Variablentypen vor |type-system#varPrint] +| `{templateType}` | [deklariert Variablentypen gemäß einer Klasse |type-system#templateType] +| `{templatePrint}` | [schlägt eine Klasse mit Variablentypen vor |type-system#templatePrint] .[table-latte-tags language-latte] -|## Übersetzung -| `{_string}` | [druckt übersetzt |#Translation] -| `{translate}`... `{/translate}` | [übersetzt den Inhalt |#Translation] +|## Übersetzungen +| `{_...}` | [gibt eine Übersetzung aus |#Übersetzungen] +| `{translate}` … `{/translate}` | [übersetzt den Inhalt |#Übersetzungen] .[table-latte-tags language-latte] -|## Andere -| `{contentType}` | [schaltet den Escaping-Modus um und sendet HTTP-Header |#contenttype] -| `{debugbreak}` | [setzt einen Haltepunkt im Code |#debugbreak] -| `{do}` | [wertet einen Ausdruck aus, ohne ihn zu drucken |#do] -| `{dump}` | [gibt Variablen in die Tracy Bar aus |#dump] -| `{spaceless}`... `{/spaceless}` | [entfernt unnötige Leerzeichen |#spaceless] -| `{syntax}` | [schaltet die Syntax während der Laufzeit um |#syntax] -| `{trace}` | [zeigt Stack-Trace |#trace] +|## Sonstiges +| `{contentType}` | [schaltet Escaping um und sendet HTTP-Header |#contentType] +| `{debugbreak}` | [platziert einen Breakpoint im Code |#debugbreak] +| `{do}` | [führt Code aus, gibt aber nichts aus |#do] +| `{dump}` | [dumpt Variablen in die Tracy Bar |#dump] +| `{php}` | [führt beliebigen PHP-Code aus |#php] +| `{spaceless}` … `{/spaceless}` | [entfernt überflüssige Leerzeichen |#spaceless] +| `{syntax}` | [Syntaxänderung zur Laufzeit |#syntax] +| `{trace}` | [zeigt den Stack Trace an |#trace] .[table-latte-tags language-latte] -|## HTML-Tag-Helfer -| `n:class` | [intelligentes Klassenattribut |#n:class] -| `n:attr` | [intelligente HTML-Attribute |#n:attr] -| `n:tag` | [Dynamischer Name des HTML-Elements |#n:tag] -| `n:ifcontent` | [Leeren HTML-Tag auslassen |#n:ifcontent] +|## HTML-Coder-Helfer +| `n:class` | [dynamische Erstellung des HTML-Attributs class |#n:class] +| `n:attr` | [dynamische Erstellung beliebiger HTML-Attribute |#n:attr] +| `n:tag` | [dynamische Erstellung des Namens eines HTML-Elements |#n:tag] +| `n:ifcontent` | [lässt leeren HTML-Tag aus |#n:ifcontent] .[table-latte-tags language-latte] -|## Nur in Nette Framework verfügbar -| `n:href` | [Link in `` HTML-Elementen |application:creating-links#In the Presenter Template] -| `{link}` | [gibt einen Link aus |application:creating-links#In the Presenter Template] -| `{plink}` | [druckt einen Link zu einem Presenter |application:creating-links#In the Presenter Template] -| `{control}` | [druckt eine Komponente |application:components#Rendering] -| `{snippet}`... `{/snippet}` | [ein Template-Snippet, das per AJAX gesendet werden kann |application:ajax#tag-snippet] -| `{snippetArea}` | Schnipsel Umschlag -| `{cache}`... `{/cache}` | [zwischenspeichert einen Vorlagenabschnitt |caching:#caching-in-latte] +|## Nur im Nette Framework verfügbar +| `n:href` | [Link, der in HTML-Elementen `` verwendet wird |application:creating-links#Im Presenter-Template] +| `{link}` | [gibt einen Link aus |application:creating-links#Im Presenter-Template] +| `{plink}` | [gibt einen Link zu einem Presenter aus |application:creating-links#Im Presenter-Template] +| `{control}` | [rendert eine Komponente |application:components#Rendern] +| `{snippet}` … `{/snippet}` | [Snippet, der per AJAX gesendet werden kann |application:ajax#Snippets in Latte] +| `{snippetArea}` | [Wrapper für Snippets |application:ajax#Snippet-Bereiche] +| `{cache}` … `{/cache}` | [cached einen Teil des Templates |caching:#Caching in Latte] .[table-latte-tags language-latte] |## Nur mit Nette Forms verfügbar -| `{form}`... `{/form}` | [druckt ein Formularelement |forms:rendering#form] -| `{label}`... `{/label}` | [druckt eine Formulareingabebezeichnung |forms:rendering#label-input] -| `{input}` | [druckt ein Formulareingabeelement |forms:rendering#label-input] -| `{inputError}` | [gibt eine Fehlermeldung für ein Formulareingabeelement aus |forms:rendering#inputError] -| `n:name` | [aktiviert ein HTML-Eingabeelement |forms:rendering#n:name] -| `{formPrint}` | [erzeugt einen Latte-Formular-Blaupause |forms:rendering#formPrint] -| `{formPrintClass}` | [gibt PHP-Klasse für Formulardaten aus |forms:in-presenter#mapping-to-classes] -| `{formContext}`... `{/formContext}` | [Teilweise Formularwiedergabe |forms:rendering#special-cases] +| `{form}` … `{/form}` | [rendert Formular-Tags |forms:rendering#form] +| `{label}` … `{/label}` | [rendert das Label eines Formularelements |forms:rendering#label input] +| `{input}` | [rendert ein Formularelement |forms:rendering#label input] +| `{inputError}` | [gibt die Fehlermeldung eines Formularelements aus |forms:rendering#inputError] +| `n:name` | [belebt ein Formularelement |forms:rendering#n:name] +| `{formContainer}` … `{/formContainer}` | [Rendern eines Formularcontainers |forms:rendering#Spezialfälle] + +.[table-latte-tags language-latte] +|## Nur mit Nette Assets verfügbar +| `{asset}` | [rendert ein Asset als HTML-Element oder URL |assets:#asset] +| `{preload}` | [generiert Preload-Hinweise zur Leistungsoptimierung |assets:#preload] +| `n:asset` | [fügt Asset-Attribute zu HTML-Elementen hinzu |assets:#n:asset] -Drucken .[#toc-printing] -======================== +Ausgabe +======= `{$var}` `{...}` `{=...}` ------------------------- -Latte verwendet das Tag `{=...}`, um einen beliebigen Ausdruck in der Ausgabe auszugeben. Wenn der Ausdruck mit einer Variablen oder einem Funktionsaufruf beginnt, ist es nicht nötig, ein Gleichheitszeichen zu schreiben. Das bedeutet in der Praxis, dass es fast nie geschrieben werden muss: +In Latte wird das Tag `{=...}` verwendet, um einen beliebigen Ausdruck in die Ausgabe zu schreiben. Latte legt Wert auf Ihren Komfort, daher müssen Sie kein Gleichheitszeichen schreiben, wenn der Ausdruck mit einer Variablen oder einem Funktionsaufruf beginnt. Das bedeutet in der Praxis, dass Sie es fast nie schreiben müssen: ```latte Name: {$name} {$surname}
                                          -Age: {date('Y') - $birth}
                                          +Alter: {date('Y') - $birth}
                                          ``` -Sie können alles, was Sie aus PHP kennen, als Ausdruck schreiben. Sie müssen nur keine neue Sprache lernen. Zum Beispiel: +Als Ausdruck können Sie alles schreiben, was Sie aus PHP kennen. Sie müssen keine neue Sprache lernen. Zum Beispiel: ```latte {='0' . ($num ?? $num * 3) . ', ' . PHP_VERSION} ``` -Bitte suchen Sie nicht nach einer Bedeutung in dem vorherigen Beispiel, aber wenn Sie eine finden, schreiben Sie uns :-) +Bitte suchen Sie im vorherigen Beispiel keinen Sinn, aber wenn Sie einen finden, schreiben Sie uns :-) -Ausweichende Ausgabe .[#toc-escaping-output] --------------------------------------------- +Escaping der Ausgabe +-------------------- -Was ist die wichtigste Aufgabe eines Template-Systems? Sicherheitslücken zu vermeiden. Und genau das tut Latte, wenn Sie etwas in die Ausgabe drucken. Es entschlüsselt automatisch alles: +Was ist die wichtigste Aufgabe eines Templating-Systems? Sicherheitslücken zu verhindern. Und genau das tut Latte immer, wenn Sie etwas ausgeben. Es maskiert es automatisch: ```latte -

                                          {='one < two'}

                                          {* prints: '

                                          one < two

                                          ' *} +

                                          {='one < two'}

                                          {* gibt aus: '

                                          one < two

                                          ' *} ``` -Um genau zu sein, verwendet Latte kontextabhängiges Escaping, eine so wichtige und einzigartige Funktion dass wir ihr [ein eigenes Kapitel |safety-first#context-aware-escaping] gewidmet haben. +Um genau zu sein, verwendet Latte kontextsensitives Escaping, was eine so wichtige und einzigartige Sache ist, dass wir ihr [ein separates Kapitel |safety-first#Kontextsensitives Escaping] gewidmet haben. -Und wenn Sie HTML-codierte Inhalte aus einer vertrauenswürdigen Quelle drucken? Dann können Sie das Escaping einfach abschalten: +Und was ist, wenn Sie HTML-kodierten Inhalt aus einer vertrauenswürdigen Quelle ausgeben? Dann können Sie das Escaping einfach deaktivieren: ```latte {$trustedHtmlString|noescape} ``` .[warning] -Der Missbrauch des `noescape`-Filters kann zu einer XSS-Schwachstelle führen! Verwenden Sie ihn nur, wenn Sie **absolut sicher** sind, was Sie tun und dass die Zeichenfolge, die Sie ausgeben, aus einer vertrauenswürdigen Quelle stammt. +Eine falsche Verwendung des `noescape`-Filters kann zu einer XSS-Schwachstelle führen! Verwenden Sie ihn niemals, wenn Sie sich nicht **absolut sicher** sind, was Sie tun und dass die ausgegebene Zeichenkette aus einer vertrauenswürdigen Quelle stammt. -Drucken in JavaScript .[#toc-printing-in-javascript] ----------------------------------------------------- +Ausgabe in JavaScript +--------------------- -Dank der kontextsensitiven Escape-Funktion ist es wunderbar einfach, Variablen innerhalb von JavaScript zu drucken, und Latte wird sie korrekt escapen. +Dank des kontextsensitiven Escapings ist es wunderbar einfach, Variablen innerhalb von JavaScript auszugeben, und Latte kümmert sich um das korrekte Escaping. -Die Variable muss keine Zeichenkette sein, es wird jeder Datentyp unterstützt, der dann als JSON kodiert wird: +Die Variable muss keine Zeichenkette sein, jeder Datentyp wird unterstützt und als JSON kodiert: ```latte {var $foo = ['hello', true, 1]} @@ -179,7 +185,7 @@ Erzeugt: ``` -Dies ist auch der Grund, warum **Variablen nicht in Anführungszeichen** gesetzt werden sollten: Latte fügt sie um Strings herum ein. Und wenn Sie eine String-Variable in einen anderen String einfügen wollen, verketten Sie sie einfach: +Dies ist auch der Grund, warum um die Variable **keine Anführungszeichen geschrieben werden**: Latte fügt sie bei Zeichenketten selbst hinzu. Und wenn Sie eine Zeichenkettenvariable in eine andere Zeichenkette einfügen möchten, verbinden Sie sie einfach: ```latte ``` -Filter .[#toc-filters] ----------------------- +Filter +------ -Der gedruckte Ausdruck kann [durch Filter |syntax#filters] verändert werden. In diesem Beispiel wird die Zeichenkette beispielsweise in Großbuchstaben umgewandelt und auf maximal 30 Zeichen gekürzt: +Der ausgegebene Ausdruck kann durch einen [Filter |syntax#Filter] modifiziert werden. So konvertieren wir beispielsweise eine Zeichenkette in Großbuchstaben und kürzen sie auf maximal 30 Zeichen: ```latte {$string|upper|truncate:30} ``` -Sie können auch Filter auf Teile eines Ausdrucks anwenden, wie folgt: +Sie können Filter auch auf Teilausdrücke anwenden: ```latte {$left . ($middle|upper) . $right} ``` -Bedingungen .[#toc-conditions] -============================== +Bedingungen +=========== `{if}` `{elseif}` `{else}` -------------------------- -Bedingungen verhalten sich genauso wie die entsprechenden PHP-Ausdrücke. Sie können die gleichen Ausdrücke verwenden, die Sie aus PHP kennen, Sie müssen keine neue Sprache lernen. +Bedingungen verhalten sich genauso wie ihre Gegenstücke in PHP. Sie können darin dieselben Ausdrücke verwenden, die Sie aus PHP kennen, Sie müssen keine neue Sprache lernen. ```latte {if $product->inStock > Stock::Minimum} - In stock + Auf Lager {elseif $product->isOnWay()} - On the way + Unterwegs {else} - Not available + Nicht verfügbar {/if} ``` -Wie jedes Paar-Tag kann ein Paar von `{if} ... {/ if}` zum Beispiel als [n:attribute |syntax#n:attributes] geschrieben werden: +Wie jedes gepaarte Tag kann auch das Paar `{if} ... {/if}` als [n:Attribut |syntax#n:Attribute] geschrieben werden, zum Beispiel: ```latte -

                                          In stock {$count} items

                                          +

                                          {$count} Stück auf Lager

                                          ``` -Wussten Sie, dass Sie das Präfix `tag-` zu n:Attributen hinzufügen können? Dann wirkt sich die Bedingung nur auf die HTML-Tags aus und der Inhalt zwischen ihnen wird immer gedruckt: +Wussten Sie, dass Sie n:Attributen das Präfix `tag-` hinzufügen können? Dann gilt die Bedingung nur für die Ausgabe der HTML-Tags, und der Inhalt dazwischen wird immer ausgegeben: + +```latte +
                                          Hallo + +{* gibt 'Hallo' aus, wenn $clickable falsch ist *} +{* gibt 'Hallo' aus, wenn $clickable wahr ist *} +``` + +Genial. + + +`n:else` .{data-version:3.0.11} +------------------------------- + +Wenn Sie die Bedingung `{if} ... {/if}` als [n:Attribut |syntax#n:Attribute] schreiben, haben Sie die Möglichkeit, einen alternativen Zweig mit `n:else` anzugeben: ```latte -Hello +{$count} Stück auf Lager -{* prints 'Hello' when $clickable is falsey *} -{* prints 'Hello' when $clickable is truthy *} +nicht verfügbar ``` -Schön. +Das Attribut `n:else` kann auch in Verbindung mit [`n:ifset` |#ifset elseifset], [`n:foreach` |#foreach], [`n:try` |#try], [#`n:ifcontent`] und [`n:ifchanged` |#ifchanged] verwendet werden. `{/if $cond}` ------------- -Sie werden vielleicht überrascht sein, dass der Ausdruck in der Bedingung `{if}` auch im End-Tag angegeben werden kann. Dies ist in Situationen nützlich, in denen wir den Wert der Bedingung noch nicht kennen, wenn das Tag geöffnet wird. Nennen wir es eine aufgeschobene Entscheidung. +Vielleicht überrascht es Sie, dass der Ausdruck in der Bedingung `{if}` auch im schließenden Tag angegeben werden kann. Dies ist nützlich in Situationen, in denen wir beim Öffnen der Bedingung ihren Wert noch nicht kennen. Nennen wir es eine aufgeschobene Entscheidung. -Wir beginnen z. B. mit der Auflistung einer Tabelle mit Datensätzen aus der Datenbank und stellen erst nach Fertigstellung des Berichts fest, dass kein Datensatz in der Datenbank vorhanden war. Also setzen wir die Bedingung in das End-Tag `{/if}`, und wenn es keinen Datensatz gibt, wird nichts davon gedruckt: +Zum Beispiel beginnen wir mit der Ausgabe einer Tabelle mit Datensätzen aus der Datenbank und stellen erst nach Abschluss der Ausgabe fest, dass keine Datensätze in der Datenbank vorhanden waren. Also fügen wir die Bedingung in das schließende Tag `{/if}` ein, und wenn keine Datensätze vorhanden sind, wird nichts davon ausgegeben: ```latte {if} -

                                          Printing rows from the database

                                          +

                                          Auflistung der Zeilen aus der Datenbank

                                          {foreach $resultSet as $row} @@ -266,28 +286,28 @@ Wir beginnen z. B. mit der Auflistung einer Tabelle mit Datensätzen aus der Dat Praktisch, nicht wahr? -Sie können auch `{else}` in der aufgeschobenen Bedingung verwenden, aber nicht `{elseif}`. +In der aufgeschobenen Bedingung kann auch `{else}` verwendet werden, jedoch nicht `{elseif}`. `{ifset}` `{elseifset}` ----------------------- .[note] -Siehe auch [`{ifset block}` |template-inheritance#checking-block-existence] +Siehe auch [`{ifset block}` |template-inheritance#Existenzprüfung von Blöcken] -Verwenden Sie die `{ifset $var}` Bedingung, um festzustellen, ob eine Variable (oder mehrere Variablen) existiert und einen Nicht-Null-Wert hat. Es ist eigentlich das Gleiche wie `if (isset($var))` in PHP. Wie jedes Paar-Tag kann es in der Form von [n:attribute |syntax#n:attributes] geschrieben werden, also zeigen wir es in einem Beispiel: +Mit der Bedingung `{ifset $var}` prüfen wir, ob eine Variable (oder mehrere Variablen) existiert und einen nicht-*null*-Wert hat. Es ist eigentlich dasselbe wie `if (isset($var))` in PHP. Wie jedes gepaarte Tag kann es auch als [n:Attribut |syntax#n:Attribute] geschrieben werden, also zeigen wir es als Beispiel: ```latte - + ``` -`{ifchanged}` .{data-version:2.9} ---------------------------------- +`{ifchanged}` +------------- -`{ifchanged}` prüft, ob sich der Wert einer Variablen seit der letzten Iteration in der Schleife (foreach, for oder while) geändert hat. +`{ifchanged}` prüft, ob sich der Wert einer Variablen seit der letzten Iteration in einer Schleife (foreach, for oder while) geändert hat. -Wenn wir eine oder mehrere Variablen im Tag angeben, wird geprüft, ob sich eine von ihnen geändert hat, und der Inhalt wird entsprechend ausgegeben. Im folgenden Beispiel wird beispielsweise bei der Auflistung von Namen jedes Mal der erste Buchstabe eines Namens als Überschrift ausgegeben, wenn er sich ändert: +Wenn wir eine oder mehrere Variablen im Tag angeben, prüft es, ob sich eine von ihnen geändert hat, und gibt den Inhalt entsprechend aus. Zum Beispiel gibt das folgende Beispiel den ersten Buchstaben des Namens als Überschrift aus, wann immer er sich bei der Ausgabe von Namen ändert: ```latte {foreach ($names|sort) as $name} @@ -297,7 +317,7 @@ Wenn wir eine oder mehrere Variablen im Tag angeben, wird geprüft, ob sich eine {/foreach} ``` -Wenn jedoch kein Argument angegeben wird, wird der gerenderte Inhalt selbst mit seinem vorherigen Zustand verglichen. Das bedeutet, dass wir im vorherigen Beispiel das Argument im Tag getrost weglassen können. Und natürlich können wir auch [n:attribute |syntax#n:attributes] verwenden: +Wenn wir jedoch kein Argument angeben, wird der gerenderte Inhalt mit seinem vorherigen Zustand verglichen. Das bedeutet, dass wir im vorherigen Beispiel das Argument im Tag einfach weglassen können. Und natürlich können wir auch ein [n:Attribut |syntax#n:Attribute] verwenden: ```latte {foreach ($names|sort) as $name} @@ -307,49 +327,49 @@ Wenn jedoch kein Argument angegeben wird, wird der gerenderte Inhalt selbst mit {/foreach} ``` -Sie können auch eine `{else}` Klausel innerhalb der `{ifchanged}` einfügen. +Innerhalb von `{ifchanged}` kann auch eine `{else}`-Klausel angegeben werden. `{switch}` `{case}` `{default}` ------------------------------- -Vergleicht den Wert mit mehreren Optionen. Dies ist ähnlich wie die `switch` Struktur, die Sie aus PHP kennen. Latte verbessert sie jedoch: +Vergleicht einen Wert mit mehreren Optionen. Es ist analog zur bedingten Anweisung `switch`, die Sie aus PHP kennen. Latte verbessert sie jedoch: -- verwendet einen strengen Vergleich (`===`) -- braucht kein `break` +- verwendet strikten Vergleich (`===`) +- benötigt kein `break` -Es ist also das exakte Äquivalent der `match` Struktur, die PHP 8.0 mitbringt. +Es ist also das genaue Äquivalent zur `match`-Struktur, die mit PHP 8.0 eingeführt wurde. ```latte {switch $transport} {case train} - By train + Mit dem Zug {case plane} - By plane + Mit dem Flugzeug {default} - Differently + Anderweitig {/switch} ``` -.{data-version:2.9} -Die Klausel `{case}` kann mehrere durch Kommas getrennte Werte enthalten: + +Die `{case}`-Klausel kann mehrere durch Kommas getrennte Werte enthalten: ```latte {switch $status} -{case $status::New}new item -{case $status::Sold, $status::Unknown}not available +{case $status::New}neuer Eintrag +{case $status::Sold, $status::Unknown}nicht verfügbar {/switch} ``` -Schleifen .[#toc-loops] -======================= +Schleifen +========= -In Latte stehen Ihnen alle Schleifen, die Sie aus PHP kennen, zur Verfügung: foreach, for und while. +In Latte finden Sie alle Schleifen, die Sie aus PHP kennen: foreach, for und while. `{foreach}` ----------- -Sie schreiben den Zyklus genau so wie in PHP: +Die Schleife wird genauso geschrieben wie in PHP: ```latte {foreach $langs as $code => $lang} @@ -357,11 +377,11 @@ Sie schreiben den Zyklus genau so wie in PHP: {/foreach} ``` -Darüber hinaus hat er einige praktische Verbesserungen, über die wir jetzt sprechen werden. +Darüber hinaus hat es einige praktische Verbesserungen, über die wir jetzt sprechen werden. -So stellt Latte zum Beispiel sicher, dass erstellte Variablen nicht versehentlich gleichnamige globale Variablen überschreiben. Das hilft Ihnen, wenn Sie davon ausgehen, dass `$lang` die aktuelle Sprache der Seite ist, und Sie nicht merken, dass `foreach $langs as $lang` diese Variable überschrieben hat. +Latte prüft beispielsweise, ob die erstellten Variablen versehentlich globale Variablen gleichen Namens überschreiben. Dies rettet Situationen, in denen Sie davon ausgehen, dass `$lang` die aktuelle Sprache der Seite ist, und nicht erkennen, dass `foreach $langs as $lang` diese Variable überschrieben hat. -Die foreach-Schleife kann auch sehr elegant und sparsam mit [n:attribute |syntax#n:attributes] geschrieben werden: +Die foreach-Schleife kann auch sehr elegant und sparsam mit einem [n:Attribut |syntax#n:Attribute] geschrieben werden: ```latte
                                            @@ -369,7 +389,7 @@ Die foreach-Schleife kann auch sehr elegant und sparsam mit [n:attribute |syntax
                                          ``` -Wussten Sie, dass Sie n:attributes das Präfix `inner-` voranstellen können? Dann wird in der Schleife nur noch der innere Teil des Elements wiederholt: +Wussten Sie, dass Sie n:Attributen das Präfix `inner-` hinzufügen können? Dann wird nur der innere Teil des Elements in der Schleife wiederholt: ```latte
                                          @@ -378,7 +398,7 @@ Wussten Sie, dass Sie n:attributes das Präfix `inner-` voranstellen können? Da
                                          ``` -Es wird also etwas gedruckt wie: +Es wird also etwa Folgendes ausgegeben: ```latte
                                          @@ -390,17 +410,17 @@ Es wird also etwas gedruckt wie: ``` -`{else}` .{data-version:2.9}{toc: foreach-else} ------------------------------------------------ +`{else}` .{toc: foreach-else} +----------------------------- -Die Schleife `foreach` kann eine optionale Klausel `{else}` enthalten, deren Text angezeigt wird, wenn das angegebene Feld leer ist: +Innerhalb der `foreach`-Schleife kann eine `{else}`-Klausel angegeben werden, deren Inhalt angezeigt wird, wenn die Schleife leer ist: ```latte
                                            {foreach $people as $person}
                                          • {$person->name}
                                          • {else} -
                                          • Sorry, no users in this list
                                          • +
                                          • Leider befinden sich keine Benutzer in dieser Liste
                                          • {/foreach}
                                          ``` @@ -409,15 +429,15 @@ Die Schleife `foreach` kann eine optionale Klausel `{else}` enthalten, deren Tex `$iterator` ----------- -Innerhalb der Schleife `foreach` wird die Variable `$iterator` initialisiert. Sie enthält wichtige Informationen über die aktuelle Schleife. +Innerhalb der `foreach`-Schleife erstellt Latte die Variable `$iterator`, mit der wir nützliche Informationen über die laufende Schleife abrufen können: -- `$iterator->first` - ist dies die erste Iteration? -- `$iterator->last` - ist dies die letzte Iteration? -- `$iterator->counter` - Iterationszähler, beginnt bei 1 -- `$iterator->counter0` - Iterationszähler, beginnt bei 0 .{data-version:2.9} -- `$iterator->odd` - Ist diese Iteration ungerade? -- `$iterator->even` - ist diese Iteration gerade? -- `$iterator->parent` - der Iterator, der den aktuellen Iterator umgibt .{data-version:2.9} +- `$iterator->first` - ist es der erste Durchlauf der Schleife? +- `$iterator->last` - ist es der letzte Durchlauf? +- `$iterator->counter` - der wievielte Durchlauf ist es, gezählt ab eins? +- `$iterator->counter0` - der wievielte Durchlauf ist es, gezählt ab null? +- `$iterator->odd` - ist es ein ungerader Durchlauf? +- `$iterator->even` - ist es ein gerader Durchlauf? +- `$iterator->parent` - der Iterator, der den aktuellen umschließt - `$iterator->nextValue` - das nächste Element in der Schleife - `$iterator->nextKey` - der Schlüssel des nächsten Elements in der Schleife @@ -435,20 +455,19 @@ Innerhalb der Schleife `foreach` wird die Variable `$iterator` initialisiert. Si {/foreach} ``` -Die Latte ist schlau und `$iterator->last` funktioniert nicht nur für Arrays, sondern auch, wenn die Schleife über einen allgemeinen Iterator läuft, bei dem die Anzahl der Elemente nicht im Voraus bekannt ist. +Latte ist schlau und `$iterator->last` funktioniert nicht nur bei Arrays, sondern auch, wenn die Schleife über einen allgemeinen Iterator läuft, bei dem die Anzahl der Elemente nicht im Voraus bekannt ist. `{first}` `{last}` `{sep}` -------------------------- -Diese Tags können innerhalb der Schleife `{foreach}` verwendet werden. Der Inhalt von `{first}` wird beim ersten Durchlauf gerendert. -Der Inhalt von `{last}` wird gerendert ... können Sie es erraten? Ja, für den letzten Durchlauf. Dies sind eigentlich Abkürzungen für `{if $iterator->first}` und `{if $iterator->last}`. +Diese Tags können innerhalb der `{foreach}`-Schleife verwendet werden. Der Inhalt von `{first}` wird gerendert, wenn es der erste Durchlauf ist. Der Inhalt von `{last}` wird gerendert … erraten Sie es? Ja, wenn es der letzte Durchlauf ist. Es handelt sich eigentlich um Abkürzungen für `{if $iterator->first}` und `{if $iterator->last}`. -Die Tags können auch als [n:attributes |syntax#n:attributes] geschrieben werden: +Die Tags können auch elegant als [n:Attribut |syntax#n:Attribute] verwendet werden: ```latte {foreach $rows as $row} - {first}

                                          List of names

                                          {/first} + {first}

                                          Liste der Namen

                                          {/first}

                                          {$row->name}

                                          @@ -456,21 +475,21 @@ Die Tags können auch als [n:attributes |syntax#n:attributes] geschrieben werden {/foreach} ``` -Der Inhalt von `{sep}` wird wiedergegeben, wenn es sich nicht um die letzte Iteration handelt, und eignet sich daher für die Ausgabe von Begrenzungszeichen, wie z. B. Kommas zwischen aufgelisteten Elementen: +Der Inhalt des Tags `{sep}` wird gerendert, wenn der Durchlauf nicht der letzte ist, er eignet sich also zum Rendern von Trennzeichen, beispielsweise Kommas zwischen den ausgegebenen Elementen: ```latte {foreach $items as $item} {$item} {sep}, {/sep} {/foreach} ``` -Das ist doch ziemlich praktisch, oder? +Das ist ziemlich praktisch, nicht wahr? -`{iterateWhile}` .{data-version:2.10} -------------------------------------- +`{iterateWhile}` +---------------- -Sie vereinfacht die Gruppierung von linearen Daten während der Iteration in einer foreach-Schleife, indem sie die Iteration in einer verschachtelten Schleife durchführt, solange die Bedingung erfüllt ist. [Lesen Sie die Anweisungen im Kochbuch |cookbook/iteratewhile]. +Vereinfacht die Gruppierung linearer Daten während der Iteration in einer foreach-Schleife, indem die Iteration in einer verschachtelten Schleife durchgeführt wird, solange die Bedingung erfüllt ist. [Lesen Sie die detaillierte Anleitung|cookbook/grouping]. -Sie kann auch `{first}` und `{last}` im obigen Beispiel elegant ersetzen: +Es kann auch elegant `{first}` und `{last}` im obigen Beispiel ersetzen: ```latte {foreach $rows as $row} @@ -487,19 +506,21 @@ Sie kann auch `{first}` und `{last}` im obigen Beispiel elegant ersetzen: {/foreach} ``` +Siehe auch die Filter [batch |filters#batch] und [group |filters#group]. + `{for}` ------- -Wir schreiben den Zyklus genau so wie in PHP: +Die Schleife wird genauso geschrieben wie in PHP: ```latte {for $i = 0; $i < 10; $i++} - Item #{$i} + Element {$i} {/for} ``` -Das Tag kann auch als [n:attribute |syntax#n:attributes] geschrieben werden: +Das Tag kann auch als [n:Attribut |syntax#n:Attribute] verwendet werden: ```latte

                                          {$i}

                                          @@ -509,7 +530,7 @@ Das Tag kann auch als [n:attribute |syntax#n:attributes] geschrieben werden: `{while}` --------- -Auch hier schreiben wir den Zyklus genau so, wie in PHP: +Die Schleife wird wiederum genauso geschrieben wie in PHP: ```latte {while $row = $result->fetch()} @@ -517,7 +538,7 @@ Auch hier schreiben wir den Zyklus genau so, wie in PHP: {/while} ``` -Oder als [n:Attribut |syntax#n:attributes]: +Oder als [n:Attribut |syntax#n:Attribute]: ```latte @@ -525,7 +546,7 @@ Oder als [n:Attribut |syntax#n:attributes]: ``` -Eine Variante mit einer Bedingung im End-Tag entspricht der do-while-Schleife in PHP: +Es ist auch eine Variante mit einer Bedingung im schließenden Tag möglich, die der do-while-Schleife in PHP entspricht: ```latte {while} @@ -537,7 +558,7 @@ Eine Variante mit einer Bedingung im End-Tag entspricht der do-while-Schleife in `{continueIf}` `{skipIf}` `{breakIf}` ------------------------------------- -Es gibt spezielle Tags, die Sie zur Steuerung jeder Schleife verwenden können - `{continueIf ?}` und `{breakIf ?}`, die zur nächsten Iteration springen bzw. die Schleife beenden, wenn die Bedingungen erfüllt sind: +Zur Steuerung jeder Schleife können die Tags `{continueIf ?}` und `{breakIf ?}` verwendet werden, die zum nächsten Element wechseln bzw. die Schleife beenden, wenn die Bedingung erfüllt ist: ```latte {foreach $rows as $row} @@ -547,8 +568,8 @@ Es gibt spezielle Tags, die Sie zur Steuerung jeder Schleife verwenden können - {/foreach} ``` -.{data-version:2.9} -Das Tag `{skipIf}` ist dem Tag `{continueIf}` sehr ähnlich, erhöht aber den Zähler nicht. So gibt es keine Löcher in der Nummerierung, wenn Sie `$iterator->counter` ausdrucken und einige Elemente überspringen. Auch die {else}-Klausel wird wiedergegeben, wenn Sie alle Elemente überspringen. + +Das Tag `{skipIf}` ist `{continueIf}` sehr ähnlich, aber es erhöht nicht den Zähler `$iterator->counter`. Wenn wir ihn also ausgeben und gleichzeitig einige Elemente überspringen, entstehen keine Lücken in der Nummerierung. Und auch die Klause `{else}` wird gerendert, wenn wir alle Elemente überspringen. ```latte
                                            @@ -556,7 +577,7 @@ Das Tag `{skipIf}` ist dem Tag `{continueIf}` sehr ähnlich, erhöht aber den Z {skipIf $person->age < 18}
                                          • {$iterator->counter}. {$person->name}
                                          • {else} -
                                          • Sorry, no adult users in this list
                                          • +
                                          • Leider befinden sich keine Erwachsenen in dieser Liste
                                          • {/foreach}
                                          ``` @@ -565,72 +586,68 @@ Das Tag `{skipIf}` ist dem Tag `{continueIf}` sehr ähnlich, erhöht aber den Z `{exitIf}` .{data-version:3.0.5} -------------------------------- -Beendet das Rendering einer Vorlage oder eines Blocks, wenn eine Bedingung erfüllt ist (d. h. "early exit"). +Beendet das Rendern des Templates oder Blocks, wenn die Bedingung erfüllt ist (sog. "early exit"). ```latte {exitIf !$messages} -

                                          Messages

                                          +

                                          Nachrichten

                                          {$message}
                                          ``` -Schablonen einbeziehen .[#toc-including-templates] -================================================== +Template einfügen +================= `{include 'file.latte'}` .{toc: include} ---------------------------------------- .[note] -Siehe auch [`{include block}` |template-inheritance#printing-blocks] +Siehe auch [`{include block}` |template-inheritance#Rendern von Blöcken] -Der `{include}` Tag lädt und rendert die angegebene Vorlage. In unserer Lieblingssprache PHP sieht das so aus: +Das Tag `{include}` lädt und rendert das angegebene Template. Wenn wir in der Sprache unserer Lieblingssprache PHP sprechen würden, wäre das so etwas wie: ```php ``` -Eingebundene Templates haben keinen Zugriff auf die Variablen des aktiven Kontexts, aber sie haben Zugriff auf die globalen Variablen. +Eingefügte Templates haben keinen Zugriff auf die Variablen des aktiven Kontexts, sie haben nur Zugriff auf globale Variablen. -Auf diese Weise können Sie Variablen übergeben: +Sie können Variablen auf diese Weise an das eingefügte Template übergeben: ```latte -{* seit Latte 2.9 *} {include 'template.latte', foo: 'bar', id: 123} - -{* vor Latte 2.9 *} -{include 'template.latte', foo => 'bar', id => 123} ``` -Der Name der Vorlage kann ein beliebiger PHP-Ausdruck sein: +Der Template-Name kann ein beliebiger PHP-Ausdruck sein: ```latte {include $someVar} {include $ajax ? 'ajax.latte' : 'not-ajax.latte'} ``` -Der eingefügte Inhalt kann durch [Filter |syntax#filters] verändert werden. Im folgenden Beispiel werden alle HTML-Elemente entfernt und die Groß- und Kleinschreibung angepasst: +Der eingefügte Inhalt kann mithilfe von [Filtern |syntax#Filter] modifiziert werden. Das folgende Beispiel entfernt sämtliches HTML und passt die Groß-/Kleinschreibung an: ```latte {include 'heading.latte' |stripHtml|capitalize} ``` -Die [Vorlagenvererbung |template inheritance] **ist** hier standardmäßig nicht beteiligt. Sie können zwar Block-Tags zu eingebundenen Vorlagen hinzufügen, diese ersetzen aber nicht die passenden Blöcke in der Vorlage, in die sie eingebunden sind. Betrachten Sie Includes als unabhängige und abgeschirmte Teile von Seiten oder Modulen. Dieses Verhalten kann mit dem Modifikator `with blocks` geändert werden (seit Latte 2.9.1): +Standardmäßig spielt die [Template-Vererbung|template-inheritance] in diesem Fall keine Rolle. Obwohl wir Blöcke im inkludierten Template verwenden können, werden die entsprechenden Blöcke im Template, in das inkludiert wird, nicht ersetzt. Stellen Sie sich inkludierte Templates als separate, abgeschirmte Teile von Seiten oder Modulen vor. Dieses Verhalten kann mit dem Modifikator `with blocks` geändert werden: ```latte {include 'template.latte' with blocks} ``` -Die Beziehung zwischen dem im Tag angegebenen Dateinamen und der Datei auf der Festplatte ist eine Sache des [Loaders |extending-latte#Loaders]. +Die Beziehung zwischen dem im Tag angegebenen Dateinamen und der Datei auf der Festplatte ist Sache des [Loaders|loaders]. -`{sandbox}` .{data-version:2.8} -------------------------------- +`{sandbox}` +----------- -Wenn Sie eine von einem Endbenutzer erstellte Vorlage einbinden, sollten Sie eine Sandbox verwenden (weitere Informationen finden Sie in der [Sandbox-Dokumentation |sandbox]): +Beim Einfügen eines vom Endbenutzer erstellten Templates sollten Sie den Sandbox-Modus in Betracht ziehen (weitere Informationen finden Sie in der [Sandbox-Dokumentation |sandbox]): ```latte {sandbox 'untrusted.latte', level: 3, data: $menu} @@ -641,9 +658,9 @@ Wenn Sie eine von einem Endbenutzer erstellte Vorlage einbinden, sollten Sie ein ========= .[note] -Siehe auch [`{block name}` |template-inheritance#blocks] +Siehe auch [`{block name}` |template-inheritance#Blöcke] -Blöcke ohne Namen dienen dazu, [Filter |syntax#filters] auf einen Teil der Vorlage anzuwenden. Sie können zum Beispiel einen [Streifenfilter |filters#strip] anwenden, um überflüssige Leerzeichen zu entfernen: +Blöcke ohne Namen dienen als Möglichkeit, [Filter |syntax#Filter] auf einen Teil des Templates anzuwenden. Zum Beispiel kann so der Filter [strip |filters#spaceless] angewendet werden, der überflüssige Leerzeichen entfernt: ```latte {block|strip} @@ -654,16 +671,16 @@ Blöcke ohne Namen dienen dazu, [Filter |syntax#filters] auf einen Teil der Vorl ``` -Behandlung von Ausnahmen .[#toc-exception-handling] -=================================================== +Ausnahmebehandlung +================== -`{try}` .{data-version:2.9} ---------------------------- +`{try}` +------- -Diese Tags machen es extrem einfach, robuste Vorlagen zu erstellen. +Dank dieses Tags ist es extrem einfach, robuste Templates zu erstellen. -Wenn beim Rendern des `{try}` -Blocks eine Ausnahme auftritt, wird der gesamte Block verworfen und das Rendern danach fortgesetzt: +Wenn beim Rendern des `{try}`-Blocks eine Ausnahme auftritt, wird der gesamte Block verworfen und das Rendern wird nach ihm fortgesetzt: ```latte {try} @@ -675,7 +692,7 @@ Wenn beim Rendern des `{try}` -Blocks eine Ausnahme auftritt, wird der gesamte B {/try} ``` -Der Inhalt der optionalen Klausel `{else}` wird nur gerendert, wenn eine Ausnahme auftritt: +Der Inhalt in der optionalen `{else}`-Klausel wird nur gerendert, wenn eine Ausnahme auftritt: ```latte {try} @@ -685,11 +702,11 @@ Der Inhalt der optionalen Klausel `{else}` wird nur gerendert, wenn eine Ausnahm {/foreach} {else} -

                                          Sorry, the tweets could not be loaded.

                                          +

                                          Es tut uns leid, die Tweets konnten nicht geladen werden.

                                          {/try} ``` -Der Tag kann auch als [n:attribute |syntax#n:attributes] geschrieben werden: +Das Tag kann auch als [n:Attribut |syntax#n:Attribute] verwendet werden: ```latte
                                            @@ -697,13 +714,13 @@ Der Tag kann auch als [n:attribute |syntax#n:attributes] geschrieben werden:
                                          ``` -Es ist auch möglich, [einen eigenen Exception-Handler |develop#exception handler] für z.B. die Protokollierung zu definieren: +Es ist auch möglich, einen eigenen [Ausnahme-Handler |develop#Exception Handler] zu definieren, zum Beispiel für die Protokollierung. -`{rollback}` .{data-version:2.9} --------------------------------- +`{rollback}` +------------ -Der Block `{try}` kann auch manuell mit `{rollback}` angehalten und übersprungen werden. So müssen Sie nicht alle Eingabedaten im Voraus prüfen und können erst während des Renderings entscheiden, ob es sinnvoll ist, das Objekt zu rendern. +Der `{try}`-Block kann auch manuell mit `{rollback}` gestoppt und übersprungen werden. Dadurch müssen Sie nicht alle Eingabedaten im Voraus überprüfen, sondern können erst während des Renderns entscheiden, dass Sie das Objekt überhaupt nicht rendern möchten: ```latte {try} @@ -719,14 +736,14 @@ Der Block `{try}` kann auch manuell mit `{rollback}` angehalten und übersprunge ``` -Variablen .[#toc-variables] -=========================== +Variablen +========= `{var}` `{default}` ------------------- -Wir werden neue Variablen in der Vorlage mit dem Tag `{var}` erstellen: +Neue Variablen erstellen wir im Template mit dem Tag `{var}`: ```latte {var $name = 'John Smith'} @@ -736,13 +753,13 @@ Wir werden neue Variablen in der Vorlage mit dem Tag `{var}` erstellen: {var $name = 'John Smith', $age = 27} ``` -Das Tag `{default}` funktioniert ähnlich, mit dem Unterschied, dass es Variablen nur dann anlegt, wenn sie nicht existieren: +Das Tag `{default}` funktioniert ähnlich, erstellt Variablen jedoch nur dann, wenn sie nicht existieren. Wenn die Variable bereits existiert und den Wert `null` enthält, wird sie nicht überschrieben: ```latte -{default $lang = 'cs'} +{default $lang = 'de'} ``` -Ab Latte 2.7 können Sie auch [Typen von Variablen |type-system] angeben. Im Moment sind sie informativ und werden von Latte nicht überprüft. +Sie können auch [Variablentypen|type-system] angeben. Diese sind vorerst informativ und werden von Latte nicht überprüft. ```latte {var string $name = $article->getTitle()} @@ -750,10 +767,10 @@ Ab Latte 2.7 können Sie auch [Typen von Variablen |type-system] angeben. Im Mom ``` -`{parameters}` .{data-version:2.9} ----------------------------------- +`{parameters}` +-------------- -So wie eine Funktion ihre Parameter deklariert, kann eine Vorlage ihre Variablen am Anfang deklarieren: +So wie eine Funktion ihre Parameter deklariert, kann auch ein Template am Anfang seine Variablen deklarieren: ```latte {parameters @@ -763,15 +780,15 @@ So wie eine Funktion ihre Parameter deklariert, kann eine Vorlage ihre Variablen } ``` -Die Variablen `$a` und `$b` ohne Standardwert haben automatisch den Standardwert `null`. Die deklarierten Typen sind immer noch informativ, und Latte überprüft sie nicht. +Die Variablen `$a` und `$b` ohne angegebenen Standardwert haben automatisch den Standardwert `null`. Die deklarierten Typen sind vorerst informativ und werden von Latte nicht überprüft. -Andere als die deklarierten Variablen werden nicht an die Vorlage übergeben. Dies ist ein Unterschied zum Tag `{default}`. +Andere als die deklarierten Variablen werden nicht an das Template übergeben. Dies unterscheidet sich vom Tag `{default}`. `{capture}` ----------- -Mit dem Tag `{capture}` können Sie die Ausgabe in einer Variablen erfassen: +Fängt die Ausgabe in einer Variablen ab: ```latte {capture $var} @@ -780,10 +797,10 @@ Mit dem Tag `{capture}` können Sie die Ausgabe in einer Variablen erfassen: {/capture} -

                                          Captured: {$var}

                                          +

                                          Abgefangen: {$var}

                                          ``` -Das Tag kann auch als [n:attribute |syntax#n:attributes] geschrieben werden: +Das Tag kann, wie jedes gepaarte Tag, auch als [n:Attribut |syntax#n:Attribute] geschrieben werden: ```latte
                                            @@ -791,15 +808,17 @@ Das Tag kann auch als [n:attribute |syntax#n:attributes] geschrieben werden:
                                          ``` +Die HTML-Ausgabe wird in der Variablen `$var` als Objekt `Latte\Runtime\Html` gespeichert, um [unerwünschtes Escaping |develop#Deaktivieren des automatischen Escapings einer Variablen] bei der Ausgabe zu verhindern. -Andere .[#toc-others] -===================== + +Sonstiges +========= `{contentType}` --------------- -Verwenden Sie das Tag, um anzugeben, welche Art von Inhalt die Vorlage darstellt. Die Optionen sind: +Mit diesem Tag geben Sie an, welchen Inhaltstyp das Template darstellt. Die Optionen sind: - `html` (Standardtyp) - `xml` @@ -808,16 +827,16 @@ Verwenden Sie das Tag, um anzugeben, welche Art von Inhalt die Vorlage darstellt - `calendar` (iCal) - `text` -Die Verwendung dieses Befehls ist wichtig, weil er die [kontextabhängige Escape-Funktion |safety-first#context-aware-escaping] einstellt und nur dann kann Latte korrekt escapen. Zum Beispiel schaltet `{contentType xml}` in den XML-Modus, `{contentType text}` schaltet das Escapen komplett ab. +Seine Verwendung ist wichtig, da es das [kontextsensitive Escaping |safety-first#Kontextsensitives Escaping] festlegt und nur so korrekt maskieren kann. Zum Beispiel schaltet `{contentType xml}` in den XML-Modus, `{contentType text}` deaktiviert das Escaping vollständig. -Handelt es sich bei dem Parameter um einen MIME-Typ mit vollem Funktionsumfang, wie z. B. `application/xml`, so wird auch ein HTTP-Header `Content-Type` an den Browser gesendet: +Wenn der Parameter ein vollwertiger MIME-Typ ist, wie z. B. `application/xml`, sendet er zusätzlich den HTTP-Header `Content-Type` an den Browser: ```latte {contentType application/xml} - RSS feed + RSS-Feed ... @@ -829,26 +848,24 @@ Handelt es sich bei dem Parameter um einen MIME-Typ mit vollem Funktionsumfang, `{debugbreak}` -------------- -Gibt die Stelle an, an der die Codeausführung unterbrochen wird. Sie wird zu Debugging-Zwecken verwendet, damit der Programmierer die Laufzeitumgebung überprüfen und sicherstellen kann, dass der Code wie erwartet ausgeführt wird. Er unterstützt [Xdebug |https://xdebug.org]. Zusätzlich können Sie eine Bedingung angeben, unter der der Code unterbrochen werden soll. +Markiert eine Stelle, an der die Programmausführung angehalten und der Debugger gestartet wird, damit der Programmierer die Laufzeitumgebung überprüfen und feststellen kann, ob das Programm wie erwartet funktioniert. Unterstützt [Xdebug |https://xdebug.org/]. Es kann eine Bedingung hinzugefügt werden, die bestimmt, wann das Programm angehalten werden soll. ```latte -{debugbreak} {* bricht das Programm *} +{debugbreak} {* hält das Programm an *} -{debugbreak $counter == 1} {* bricht das Programm ab, wenn die Bedingung erfüllt ist *} +{debugbreak $counter == 1} {* hält das Programm an, wenn die Bedingung erfüllt ist *} ``` `{do}` ------ -Führt den Code aus und druckt nichts aus. +Führt PHP-Code aus und gibt nichts aus. Wie bei allen anderen Tags bedeutet PHP-Code ein einzelner Ausdruck, siehe [PHP-Beschränkungen |syntax#Einschränkungen von PHP in Latte]. ```latte {do $num++} ``` -In Latte 2.7 und früher wurde die `{php}` verwendet. - `{dump}` -------- @@ -856,19 +873,25 @@ In Latte 2.7 und früher wurde die `{php}` verwendet. Gibt eine Variable oder den aktuellen Kontext aus. ```latte -{dump $name} {* gibt die Variable $name aus *} +{dump $name} {* Gibt die Variable $name aus *} -{dump} {* gibt alle definierten Variablen aus *} +{dump} {* Gibt alle aktuell definierten Variablen aus *} ``` .[caution] -Erfordert das Paket [Tracy |tracy:]. +Erfordert die Bibliothek [Tracy|tracy:]. + + +`{php}` +------- + +Ermöglicht die Ausführung von beliebigem PHP-Code. Das Tag muss mit der Erweiterung [RawPhpExtension |develop#RawPhpExtension] aktiviert werden. `{spaceless}` ------------- -Entfernt unnötige Leerzeichen. Ähnlich wie der [raumlose |filters#spaceless] Filter. +Entfernt überflüssigen Leerraum aus der Ausgabe. Funktioniert ähnlich wie der Filter [spaceless |filters#spaceless]. ```latte {spaceless} @@ -878,50 +901,50 @@ Entfernt unnötige Leerzeichen. Ähnlich wie der [raumlose |filters#spaceless] F {/spaceless} ``` -Ausgaben: +Erzeugt: ```latte
                                          • Hello
                                          ``` -Der Tag kann auch als [n:attribute |syntax#n:attributes] geschrieben werden: +Das Tag kann auch als [n:Attribut |syntax#n:Attribute] geschrieben werden. `{syntax}` ---------- -Latten-Tags müssen nicht nur in geschweifte Klammern eingeschlossen werden. Sie können ein anderes Trennzeichen wählen, auch zur Laufzeit. Dies geschieht durch `{syntax…}`, wobei der Parameter sein kann: +Latte-Tags müssen nicht nur in einfache geschweifte Klammern eingeschlossen sein. Wir können auch einen anderen Trenner wählen, sogar zur Laufzeit. Dazu dient `{syntax …}`, wobei als Parameter angegeben werden kann: - double: `{{...}}` -- off: deaktiviert die Latte-Tags vollständig +- off: deaktiviert die Verarbeitung von Latte-Tags vollständig -Mit der Notation n:attribute können wir Latte nur für einen JavaScript-Block deaktivieren: +Mit n:Attributen kann Latte beispielsweise nur für einen JavaScript-Block deaktiviert werden: ```latte ``` -Latte kann sehr bequem innerhalb von JavaScript verwendet werden, man sollte nur Konstrukte wie in diesem Beispiel vermeiden, bei denen der Buchstabe unmittelbar auf `{` folgt, siehe [Latte innerhalb von JavaScript oder CSS |recipes#Latte inside JavaScript or CSS]. +Latte kann sehr bequem innerhalb von JavaScript verwendet werden, man muss nur Konstruktionen wie in diesem Beispiel vermeiden, bei denen ein Buchstabe direkt auf `{` folgt, siehe [Latte innerhalb von JavaScript oder CSS |recipes#Latte innerhalb von JavaScript oder CSS]. -Wenn Sie Latte mit dem `{syntax off}` (d.h. Tag, nicht das n:-Attribut) ausschalten, werden alle Tags bis zu `{/syntax}` strikt ignoriert. +Wenn Sie Latte mit `{syntax off}` (d. h. mit dem Tag, nicht mit dem n:Attribut) deaktivieren, werden alle Tags bis `{/syntax}` konsequent ignoriert. -{trace} .{data-version:2.10} ----------------------------- +{trace} +------- -Wirft eine `Latte\RuntimeException` Exception, deren Stack-Trace im Sinne der Templates ist. Anstatt Funktionen und Methoden aufzurufen, werden also Blöcke aufgerufen und Vorlagen eingefügt. Wenn Sie ein Tool zur übersichtlichen Darstellung von geworfenen Ausnahmen wie [Tracy |tracy:] verwenden, sehen Sie deutlich den Aufrufstapel, einschließlich aller übergebenen Argumente. +Wirft eine `Latte\RuntimeException`, deren Stack Trace im Sinne von Templates aufgebaut ist. Das heißt, anstelle von Funktions- und Methodenaufrufen enthält er Aufrufe von Blöcken und das Einfügen von Templates. Wenn Sie ein Werkzeug zur übersichtlichen Anzeige geworfener Ausnahmen verwenden, wie z. B. [Tracy|tracy:], wird Ihnen der Call Stack einschließlich aller übergebenen Argumente übersichtlich angezeigt. -HTML-Tag-Hilfsmittel .[#toc-html-tag-helpers] -============================================= +HTML-Coder-Helfer +================= n:class ------- -Dank `n:class` ist es sehr einfach, das HTML-Attribut `class` genau so zu generieren, wie Sie es brauchen. +Dank `n:class` ist es sehr einfach, das HTML-Attribut `class` genau nach Ihren Vorstellungen zu generieren. Beispiel: Ich möchte, dass das aktive Element die Klasse `active` hat: @@ -931,7 +954,7 @@ Beispiel: Ich möchte, dass das aktive Element die Klasse `active` hat: {/foreach} ``` -Außerdem muss das erste Element die Klassen `first` und `main` haben: +Und außerdem soll das erste Element die Klassen `first` und `main` haben: ```latte {foreach $items as $item} @@ -939,7 +962,7 @@ Außerdem muss das erste Element die Klassen `first` und `main` haben: {/foreach} ``` -Und alle Elemente sollten die Klasse `list-item` haben: +Und alle Elemente sollen die Klasse `list-item` haben: ```latte {foreach $items as $item} @@ -947,13 +970,13 @@ Und alle Elemente sollten die Klasse `list-item` haben: {/foreach} ``` -Verblüffend einfach, nicht wahr? +Erstaunlich einfach, nicht wahr? n:attr ------ -Das Attribut `n:attr` kann beliebige HTML-Attribute mit der gleichen Eleganz wie [n:class |#n:class] erzeugen. +Das Attribut `n:attr` kann beliebige HTML-Attribute mit der gleichen Eleganz wie [#n:class] generieren. ```latte {foreach $data as $item} @@ -961,7 +984,7 @@ Das Attribut `n:attr` kann beliebige HTML-Attribute mit der gleichen Eleganz wie {/foreach} ``` -Abhängig von den zurückgegebenen Werten zeigt es z. B: +Abhängig von den zurückgegebenen Werten gibt es z. B. aus: ```latte @@ -972,8 +995,8 @@ Abhängig von den zurückgegebenen Werten zeigt es z. B: ``` -n:tag .{data-version:2.10} --------------------------- +n:tag +----- Das Attribut `n:tag` kann den Namen eines HTML-Elements dynamisch ändern. @@ -981,17 +1004,19 @@ Das Attribut `n:tag` kann den Namen eines HTML-Elements dynamisch ändern.

                                          {$title}

                                          ``` -Wenn `$heading === null`, wird das `

                                          ` Tag ohne Änderung gedruckt. Andernfalls wird der Elementname in den Wert der Variablen geändert, so dass für `$heading === 'h3'` geschrieben wird: +Wenn `$heading === null` ist, wird das Tag `

                                          ` unverändert ausgegeben. Andernfalls wird der Name des Elements in den Wert der Variablen geändert, sodass für `$heading === 'h3'` Folgendes ausgegeben wird: ```latte

                                          ...

                                          ``` +Da Latte ein sicheres Templating-System ist, prüft es, ob der neue Tag-Name gültig ist und keine unerwünschten oder schädlichen Werte enthält. + n:ifcontent ----------- -Verhindert, dass ein leeres HTML-Element gedruckt wird, d.h. ein Element, das nichts als Leerraum enthält. +Verhindert, dass ein leeres HTML-Element ausgegeben wird, d. h. ein Element, das nichts außer Leerzeichen enthält. ```latte
                                          @@ -999,24 +1024,24 @@ Verhindert, dass ein leeres HTML-Element gedruckt wird, d.h. ein Element, das ni
                                          ``` -Abhängig von den Werten der Variablen `$error` wird dies gedruckt: +Gibt abhängig vom Wert der Variablen `$error` aus: ```latte {* $error = '' *}
                                          -{* $error = 'Required' *} +{* $error = 'Erforderlich' *}
                                          -
                                          Required
                                          +
                                          Erforderlich
                                          ``` -Übersetzung .{data-version:3.0}[#toc-translation] -================================================= +Übersetzungen +============= -Damit die Übersetzungs-Tags funktionieren, müssen Sie den [Übersetzer ein richten|develop#TranslatorExtension]. Sie können auch den [`translate` |filters#translate] Filter für die Übersetzung verwenden. +Damit die Übersetzungs-Tags funktionieren, muss der [Übersetzer aktiviert werden |develop#TranslatorExtension]. Für die Übersetzung können Sie auch den Filter [`translate` |filters#translate] verwenden. `{_...}` @@ -1025,30 +1050,30 @@ Damit die Übersetzungs-Tags funktionieren, müssen Sie den [Übersetzer ein ric Übersetzt Werte in andere Sprachen. ```latte -{_'Basket'} +{_'Warenkorb'} {_$item} ``` -Es können auch andere Parameter an den Übersetzer übergeben werden: +Dem Übersetzer können auch weitere Parameter übergeben werden: ```latte -{_'Basket', domain: order} +{_'Warenkorb', domain: order} ``` `{translate}` ------------- -Překládá části šablony: +Übersetzt Teile des Templates: ```latte -

                                          {translate}Order{/translate}

                                          +

                                          {translate}Bestellung{/translate}

                                          {translate domain: order}Lorem ipsum ...{/translate} ``` -Der Tag kann auch als [n:attribute |syntax#n:attributes] geschrieben werden, um das Innere des Elements zu übersetzen: +Das Tag kann auch als [n:Attribut |syntax#n:Attribute] geschrieben werden, um den Inhalt des Elements zu übersetzen: ```latte -

                                          Order

                                          +

                                          Bestellung

                                          ``` diff --git a/latte/de/template-inheritance.texy b/latte/de/template-inheritance.texy index f92ff5f344..28467dece2 100644 --- a/latte/de/template-inheritance.texy +++ b/latte/de/template-inheritance.texy @@ -1,20 +1,20 @@ -Vererbung und Wiederverwendbarkeit von Vorlagen -*********************************************** +Template-Vererbung und Wiederverwendbarkeit +******************************************* .[perex] -Die Wiederverwendbarkeit von Vorlagen und die Vererbungsmechanismen sind dazu da, Ihre Produktivität zu steigern, da jede Vorlage nur ihren einzigartigen Inhalt enthält und die sich wiederholenden Elemente und Strukturen wiederverwendet werden. Wir stellen drei Konzepte vor: [Layout-Vererbung |#layout inheritance], [horizontale Wiederverwendung |#horizontal reuse] und [Unit-Vererbung |#unit inheritance]. +Mechanismen zur Wiederverwendung und Vererbung von Templates steigern Ihre Produktivität, da jedes Template nur seinen einzigartigen Inhalt enthält und wiederholte Elemente und Strukturen wiederverwendet werden. Wir stellen drei Konzepte vor: [#Layout-Vererbung], [#horizontale Wiederverwendung] und [#Einheiten-Vererbung]. -Das Konzept der Latte-Vorlagenvererbung ähnelt der PHP-Klassenvererbung. Sie definieren eine **Elternvorlage**, von der andere **Kindvorlagen** ableiten und Teile der Elternvorlage außer Kraft setzen können. Es funktioniert hervorragend, wenn Elemente eine gemeinsame Struktur haben. Klingt kompliziert? Keine Sorge, das ist es nicht. +Das Konzept der Template-Vererbung in Latte ähnelt der Klassenvererbung in PHP. Sie definieren ein **Eltern-Template**, von dem andere **Kind-Templates** erben und Teile des Eltern-Templates überschreiben können. Dies funktioniert hervorragend, wenn Elemente eine gemeinsame Struktur teilen. Klingt kompliziert? Keine Sorge, es ist sehr einfach. -Layout-Vererbung `{layout}` .{toc: Layout Inheritance} -====================================================== +Layout-Vererbung `{layout}` .{toc: Layout-Vererbung} +==================================================== -Lassen Sie uns die Vererbung von Layoutvorlagen anhand eines Beispiels betrachten. Dies ist eine übergeordnete Vorlage, die wir zum Beispiel `layout.latte` nennen und die ein HTML-Skelettdokument definiert. +Schauen wir uns die Vererbung des Template-Layouts direkt an einem Beispiel an. Dies ist das Eltern-Template, das wir beispielsweise `layout.latte` nennen und das die Grundstruktur eines HTML-Dokuments definiert: ```latte - + {block title}{/block} @@ -30,36 +30,36 @@ Lassen Sie uns die Vererbung von Layoutvorlagen anhand eines Beispiels betrachte ``` -Die `{block}` Tags definieren drei Blöcke, die von untergeordneten Vorlagen ausgefüllt werden können. Mit dem Block-Tag wird der Vorlagen-Engine lediglich mitgeteilt, dass eine untergeordnete Vorlage diese Teile der Vorlage außer Kraft setzen kann, indem sie einen eigenen Block mit demselben Namen definiert. +Die `{block}`-Tags definieren drei Blöcke, die von Kind-Templates gefüllt werden können. Das `block`-Tag teilt lediglich mit, dass dieser Bereich von einem Kind-Template durch Definieren eines eigenen Blocks mit demselben Namen überschrieben werden kann. -Eine untergeordnete Vorlage könnte wie folgt aussehen: +Ein Kind-Template könnte so aussehen: ```latte {layout 'layout.latte'} -{block title}My amazing blog{/block} +{block title}Mein erstaunlicher Blog{/block} {block content} -

                                          Welcome to my awesome homepage.

                                          +

                                          Willkommen auf meiner tollen Homepage.

                                          {/block} ``` -Der `{layout}` Tag ist hier der Schlüssel. Es teilt der Template-Engine mit, dass diese Vorlage eine andere Vorlage "erweitert". Wenn Latte diese Vorlage rendert, sucht es zunächst die übergeordnete Vorlage - in diesem Fall `layout.latte`. +Der Schlüssel hier ist das `{layout}`-Tag. Es teilt Latte mit, dass dieses Template ein anderes Template „erweitert“. Wenn Latte dieses Template rendert, findet es zuerst das Eltern-Template – in diesem Fall `layout.latte`. -An diesem Punkt bemerkt die Template-Engine die drei Block-Tags in `layout.latte` und ersetzt diese Blöcke durch den Inhalt der Kind-Vorlage. Da die untergeordnete Vorlage den *Fußzeilenblock* nicht definiert hat, wird stattdessen der Inhalt der übergeordneten Vorlage verwendet. Der Inhalt innerhalb eines `{block}` -Tags in einer übergeordneten Vorlage wird immer als Fallback verwendet. +An diesem Punkt bemerkt Latte die drei Block-Tags in `layout.latte` und ersetzt diese Blöcke durch den Inhalt des Kind-Templates. Da das Kind-Template den *footer*-Block nicht definiert hat, wird stattdessen der Inhalt aus dem Eltern-Template verwendet. Der Inhalt innerhalb des `{block}`-Tags im Eltern-Template wird immer als Fallback verwendet. -Die Ausgabe könnte wie folgt aussehen: +Die Ausgabe könnte so aussehen: ```latte - + - My amazing blog + Mein erstaunlicher Blog
                                          -

                                          Welcome to my awesome homepage.

                                          +

                                          Willkommen auf meiner tollen Homepage.

                                          + {repeat $cols} + + {/repeat} + +{/repeat} +``` + +Αυτό το παράδειγμα δείχνει πώς να χειριστείτε την κατάσταση (μετρητές βρόχου) και πιθανά προβλήματα ένθεσης χρησιμοποιώντας προσωρινές μεταβλητές με πρόθεμα `$__` και μοναδικές με ID από το `PrintContext::generateId()`. + + +Pure n:attributes +----------------- + +Ενώ πολλά `n:attributes` όπως `n:if` ή `n:foreach` χρησιμεύουν ως βολικές συντομεύσεις για τα αντίστοιχα paired tags τους (`{if}...{/if}`, `{foreach}...{/foreach}`), το Latte επιτρέπει επίσης τον ορισμό tags που *υπάρχουν μόνο* με τη μορφή n:attribute. Αυτά χρησιμοποιούνται συχνά για την τροποποίηση των attributes ή της συμπεριφοράς του HTML element στο οποίο είναι προσαρτημένα. + +Τυπικά παραδείγματα ενσωματωμένα στο Latte περιλαμβάνουν το [`n:class` |tags#n:class], το οποίο βοηθά στη δυναμική κατασκευή του attribute `class`, και το [`n:attr` |tags#n:attr], το οποίο μπορεί να ορίσει πολλαπλά αυθαίρετα attributes. + +Ας δημιουργήσουμε το δικό μας pure n:attribute: `n:confirm`, το οποίο προσθέτει ένα JavaScript confirmation dialog πριν από την εκτέλεση μιας ενέργειας (όπως η παρακολούθηση ενός συνδέσμου ή η υποβολή μιας φόρμας). + +**Στόχος:** Υλοποίηση του `n:confirm="'Είστε σίγουροι;'"`, το οποίο προσθέτει έναν `onclick` handler για την αποτροπή της προεπιλεγμένης ενέργειας εάν ο χρήστης ακυρώσει το confirmation dialog. + + +Υλοποίηση του `ConfirmNode` +--------------------------- + +Χρειαζόμαστε μια κλάση Node και μια συνάρτηση ανάλυσης. + +```php +expectArguments(); + $node = $tag->node = new self; + $node->message = $tag->parser->parseExpression(); + return $node; + } + + /** + * Δημιουργεί τον κώδικα του attribute 'onclick' με σωστό escaping. + */ + public function print(PrintContext $context): string + { + // Διασφαλίζει σωστό escaping για τα contexts JavaScript και HTML attribute. + return $context->format( + <<<'XX' + echo ' onclick="', LR\Filters::escapeHtmlAttr('return confirm(' . LR\Filters::escapeJs(%node) . ')'), '"' %line; + XX, + $this->message, + $this->position, + ); + } + + public function &getIterator(): \Generator + { + yield $this->message; + } +} +``` + +Η μέθοδος `print()` δημιουργεί κώδικα PHP ο οποίος τελικά, κατά την απόδοση του προτύπου, θα εκτυπώσει το HTML attribute `onclick="..."`. Ο χειρισμός των ένθετων contexts (JavaScript μέσα σε ένα HTML attribute) απαιτεί προσεκτικό escaping. Το φίλτρο `LR\Filters::escapeJs(%node)` καλείται κατά το runtime και κάνει escape το μήνυμα σωστά για χρήση εντός JavaScript (η έξοδος θα ήταν σαν `"Sure?"`). Στη συνέχεια, το φίλτρο `LR\Filters::escapeHtmlAttr(...)` κάνει escape τους χαρακτήρες που είναι ειδικοί στα HTML attributes, οπότε αυτό θα άλλαζε την έξοδο σε `return confirm("Sure?")`. Αυτό το διπλό runtime escaping διασφαλίζει ότι το μήνυμα είναι ασφαλές για JavaScript και ο προκύπτων κώδικας JavaScript είναι ασφαλής για ενσωμάτωση στο HTML attribute `onclick`. + + +Εγγραφή και χρήση +----------------- + +Καταχωρήστε το n:attribute στην επέκτασή σας. Μην ξεχάσετε το πρόθεμα `n:` στο κλειδί: + +```php +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), + 'n:confirm' => ConfirmNode::create(...), // Εγγραφή του n:confirm + ]; + } +} +``` + +Τώρα μπορείτε να χρησιμοποιήσετε το `n:confirm` σε συνδέσμους, κουμπιά ή στοιχεία φόρμας: + +```latte +Διαγραφή +``` + +Παραγόμενο HTML: + +```html +Διαγραφή +``` + +Όταν ο χρήστης κάνει κλικ στον σύνδεσμο, ο browser εκτελεί τον κώδικα `onclick`, εμφανίζει το confirmation dialog και μεταβαίνει στο `delete.php` μόνο εάν ο χρήστης κάνει κλικ στο "OK". + +Αυτό το παράδειγμα δείχνει πώς μπορεί να δημιουργηθεί ένα pure n:attribute για να τροποποιήσει τη συμπεριφορά ή τα attributes του host HTML element του, δημιουργώντας τον κατάλληλο κώδικα PHP στη μέθοδό του `print()`. Μην ξεχνάτε το διπλό escaping που συχνά απαιτείται: μία φορά για το target context (JavaScript σε αυτή την περίπτωση) και ξανά για το context του HTML attribute. + + +Προχωρημένα θέματα +================== + +Ενώ οι προηγούμενες ενότητες καλύπτουν τις βασικές έννοιες, εδώ είναι μερικά πιο προχωρημένα θέματα που μπορεί να συναντήσετε κατά τη δημιουργία προσαρμοσμένων Latte tags. + + +Λειτουργίες εξόδου tag +---------------------- + +Το αντικείμενο `Tag` που περνά στη συνάρτησή σας `create()` έχει μια ιδιότητα `outputMode`. Αυτή η ιδιότητα επηρεάζει τον τρόπο με τον οποίο το Latte χειρίζεται τα γύρω κενά διαστήματα και την εσοχή, ειδικά όταν το tag χρησιμοποιείται σε δική του γραμμή. Μπορείτε να τροποποιήσετε αυτή την ιδιότητα στη συνάρτησή σας `create()`. + +- `Tag::OutputKeepIndentation` (Προεπιλογή για τα περισσότερα tags όπως `{=...}`): Το Latte προσπαθεί να διατηρήσει την εσοχή πριν από το tag. Οι νέες γραμμές *μετά* το tag γενικά διατηρούνται. Αυτό είναι κατάλληλο για tags που εκτυπώνουν περιεχόμενο inline. +- `Tag::OutputRemoveIndentation` (Προεπιλογή για block tags όπως `{if}`, `{foreach}`): Το Latte αφαιρεί την αρχική εσοχή και πιθανώς μία επόμενη νέα γραμμή. Αυτό βοηθά να διατηρηθεί ο παραγόμενος κώδικας PHP καθαρότερος και αποτρέπει επιπλέον κενές γραμμές στην έξοδο HTML που προκαλούνται από το ίδιο το tag. Χρησιμοποιήστε το για tags που αντιπροσωπεύουν δομές ελέγχου ή μπλοκ που δεν θα πρέπει να προσθέτουν κενά διαστήματα από μόνα τους. +- `Tag::OutputNone` (Χρησιμοποιείται από tags όπως `{var}`, `{default}`): Παρόμοιο με το `RemoveIndentation`, αλλά σηματοδοτεί πιο έντονα ότι το ίδιο το tag δεν παράγει άμεση έξοδο, επηρεάζοντας πιθανώς την επεξεργασία των κενών διαστημάτων γύρω του ακόμη πιο επιθετικά. Κατάλληλο για δηλωτικά ή ρυθμιστικά tags. + +Επιλέξτε τη λειτουργία που ταιριάζει καλύτερα στον σκοπό του tag σας. Για τα περισσότερα δομικά ή ελεγκτικά tags, το `OutputRemoveIndentation` είναι συνήθως κατάλληλο. + + +Πρόσβαση σε γονικά/πλησιέστερα tags +----------------------------------- + +Μερικές φορές η συμπεριφορά ενός tag πρέπει να εξαρτάται από το context στο οποίο χρησιμοποιείται, συγκεκριμένα σε ποιο γονικό tag(s) βρίσκεται. Το αντικείμενο `Tag` που περνά στη συνάρτησή σας `create()` παρέχει τη μέθοδο `closestTag(array $classes, ?callable $condition = null): ?Tag` ακριβώς για αυτόν τον σκοπό. + +Αυτή η μέθοδος αναζητά προς τα πάνω την ιεραρχία των τρεχόντως ανοιχτών tags (συμπεριλαμβανομένων των HTML elements που αντιπροσωπεύονται εσωτερικά κατά την ανάλυση) και επιστρέφει το αντικείμενο `Tag` του πλησιέστερου προγόνου που ταιριάζει με συγκεκριμένα κριτήρια. Εάν δεν βρεθεί ταιριαστός πρόγονος, επιστρέφει `null`. + +Ο πίνακας `$classes` καθορίζει τι είδους προγονικά tags αναζητάτε. Ελέγχει εάν ο συσχετισμένος κόμβος του προγονικού tag (`$ancestorTag->node`) είναι μια παρουσία αυτής της κλάσης. + +```php +function create(Tag $tag) +{ + // Αναζήτηση του πλησιέστερου προγονικού tag του οποίου ο κόμβος είναι παρουσία του ForeachNode + $foreachTag = $tag->closestTag([ForeachNode::class]); + if ($foreachTag) { + // Μπορούμε να αποκτήσουμε πρόσβαση στην ίδια την παρουσία του ForeachNode: + $foreachNode = $foreachTag->node; + } +} +``` + +Σημειώστε το `$foreachTag->node`: Αυτό λειτουργεί μόνο επειδή είναι σύμβαση στην ανάπτυξη των Latte tags να ανατίθεται αμέσως ο δημιουργημένος κόμβος στο `$tag->node` εντός της μεθόδου `create()`, όπως κάναμε πάντα. + +Μερικές φορές η απλή σύγκριση του τύπου του κόμβου δεν αρκεί. Μπορεί να χρειαστεί να ελέγξετε μια συγκεκριμένη ιδιότητα του πιθανού προγονικού tag ή του κόμβου του. Το προαιρετικό δεύτερο argument για το `closestTag()` είναι ένα callable που δέχεται το πιθανό προγονικό αντικείμενο `Tag` και θα πρέπει να επιστρέφει εάν είναι έγκυρη αντιστοιχία. + +```php +function create(Tag $tag) +{ + $dynamicBlockTag = $tag->closestTag( + [BlockNode::class], + // Συνθήκη: το μπλοκ πρέπει να είναι δυναμικό + fn(Tag $blockTag) => $blockTag->node->block->isDynamic(), + ); +} +``` + +Η χρήση του `closestTag()` επιτρέπει τη δημιουργία tags που είναι context-aware και επιβάλλουν τη σωστή χρήση εντός της δομής του προτύπου σας, οδηγώντας σε πιο στιβαρά και κατανοητά πρότυπα. + + +Placeholders του `PrintContext::format()` +----------------------------------------- + +Έχουμε χρησιμοποιήσει συχνά το `PrintContext::format()` για τη δημιουργία κώδικα PHP στις μεθόδους `print()` των κόμβων μας. Δέχεται μια συμβολοσειρά μάσκας και επακόλουθα arguments που αντικαθιστούν τους placeholders στη μάσκα. Ακολουθεί μια σύνοψη των διαθέσιμων placeholders: + +- **`%node`**: Το argument πρέπει να είναι μια παρουσία `Node`. Καλεί τη μέθοδο `print()` του κόμβου και εισάγει την προκύπτουσα συμβολοσειρά κώδικα PHP. +- **`%dump`**: Το argument είναι οποιαδήποτε τιμή PHP. Εξάγει την τιμή σε έγκυρο κώδικα PHP. Κατάλληλο για scalars, arrays, null. + - `$context->format('echo %dump;', 'Hello')` -> `echo 'Hello';` + - `$context->format('$arr = %dump;', [1, 2])` -> `$arr = [1, 2];` +- **`%raw`**: Εισάγει το argument απευθείας στον κώδικα PHP εξόδου χωρίς κανένα escaping ή τροποποίηση. **Χρησιμοποιήστε με προσοχή**, κυρίως για την εισαγωγή προ-δημιουργημένων τμημάτων κώδικα PHP ή ονομάτων μεταβλητών. + - `$context->format('%raw = 1;', '$variableName')` -> `$variableName = 1;` +- **`%args`**: Το argument πρέπει να είναι `Expression\ArrayNode`. Εκτυπώνει τα στοιχεία του πίνακα μορφοποιημένα ως arguments για μια κλήση συνάρτησης ή μεθόδου (διαχωρισμένα με κόμμα, χειρίζεται named arguments εάν υπάρχουν). + - `$argsNode = new ArrayNode([...]);` + - `$context->format('myFunc(%args);', $argsNode)` -> `myFunc(1, name: 'Joe');` +- **`%line`**: Το argument πρέπει να είναι ένα αντικείμενο `Position` (συνήθως `$this->position`). Εισάγει ένα σχόλιο PHP `/* line X */` που υποδεικνύει τον αριθμό γραμμής της πηγής. + - `$context->format('echo "Hi" %line;', $this->position)` -> `echo "Hi" /* line 42 */;` +- **`%escape(...)`**: Δημιουργεί κώδικα PHP που *κατά το runtime* κάνει escape την εσωτερική έκφραση χρησιμοποιώντας τους τρέχοντες context-aware κανόνες escaping. + - `$context->format('echo %escape(%node);', $variableNode)` +- **`%modify(...)`**: Το argument πρέπει να είναι `ModifierNode`. Δημιουργεί κώδικα PHP που εφαρμόζει τα φίλτρα που καθορίζονται στο `ModifierNode` στο εσωτερικό περιεχόμενο, συμπεριλαμβανομένου του context-aware escaping, εκτός εάν απενεργοποιηθεί με `|noescape`. + - `$context->format('%modify(%node);', $modifierNode, $variableNode)` +- **`%modifyContent(...)`**: Παρόμοιο με το `%modify`, αλλά προορίζεται για την τροποποίηση μπλοκ συλληφθέντος περιεχομένου (συχνά HTML). + +Μπορείτε να αναφερθείτε ρητά στα arguments με τον index τους (ξεκινώντας από το μηδέν): `%0.node`, `%1.dump`, `%2.raw`, κ.λπ. Αυτό επιτρέπει την επαναχρησιμοποίηση ενός argument πολλές φορές στη μάσκα χωρίς να το περάσετε επανειλημμένα στο `format()`. Δείτε το παράδειγμα του tag `{repeat}`, όπου χρησιμοποιήθηκαν τα `%0.raw` και `%2.raw`. + + +Παράδειγμα σύνθετης ανάλυσης arguments +-------------------------------------- + +Ενώ τα `parseExpression()`, `parseArguments()`, κ.λπ., καλύπτουν πολλές περιπτώσεις, μερικές φορές χρειάζεστε πιο σύνθετη λογική ανάλυσης χρησιμοποιώντας το χαμηλότερου επιπέδου `TokenStream` που είναι διαθέσιμο μέσω του `$tag->parser->stream`. + +**Στόχος:** Δημιουργία ενός tag `{embedYoutube $videoID, width: 640, height: 480}`. Θέλουμε να αναλύσουμε το απαιτούμενο ID βίντεο (συμβολοσειρά ή μεταβλητή) ακολουθούμενο από προαιρετικά ζεύγη κλειδιού-τιμής για τις διαστάσεις. + +```php +expectArguments(); + $node = $tag->node = new self; + // Ανάλυση του απαιτούμενου ID βίντεο + $node->videoId = $tag->parser->parseExpression(); + + // Ανάλυση προαιρετικών ζευγών κλειδιού-τιμής + $stream = $tag->parser->stream; // Λήψη της ροής token + while ($stream->tryConsume(',')) { // Απαιτεί διαχωρισμό με κόμμα + // Αναμονή αναγνωριστικού 'width' ή 'height' + $keyToken = $stream->consume(Token::Php_Identifier); + $key = strtolower($keyToken->text); + + $stream->consume(':'); // Αναμονή διαχωριστικού άνω και κάτω τελείας + + $value = $tag->parser->parseExpression(); // Ανάλυση της έκφρασης τιμής + + if ($key === 'width') { + $node->width = $value; + } elseif ($key === 'height') { + $node->height = $value; + } else { + throw new CompileException("Άγνωστο argument '$key'. Αναμενόταν 'width' ή 'height'.", $keyToken->position); + } + } + + return $node; + } +} +``` + +Αυτό το επίπεδο ελέγχου σας επιτρέπει να ορίσετε πολύ συγκεκριμένες και σύνθετες συντάξεις για τα προσαρμοσμένα tags σας αλληλεπιδρώντας απευθείας με τη ροή token. + + +Χρήση του `AuxiliaryNode` +------------------------- + +Το Latte παρέχει γενικούς "βοηθητικούς" κόμβους για ειδικές καταστάσεις κατά τη δημιουργία κώδικα ή εντός των compilation passes. Αυτοί είναι οι `AuxiliaryNode` και `Php\Expression\AuxiliaryNode`. + +Θεωρήστε το `AuxiliaryNode` ως έναν ευέλικτο κόμβο container που αναθέτει τις βασικές του λειτουργίες - δημιουργία κώδικα και έκθεση παιδικών κόμβων - στα arguments που παρέχονται στον constructor του: + +- Ανάθεση `print()`: Το πρώτο argument του constructor είναι ένα PHP **closure**. Όταν το Latte καλεί τη μέθοδο `print()` στο `AuxiliaryNode`, εκτελεί αυτό το παρεχόμενο closure. Το closure δέχεται το `PrintContext` και οποιουσδήποτε κόμβους πέρασαν στο δεύτερο argument του constructor, επιτρέποντάς σας να ορίσετε εντελώς προσαρμοσμένη λογική δημιουργίας κώδικα PHP κατά το runtime. +- Ανάθεση `getIterator()`: Το δεύτερο argument του constructor είναι ένας **πίνακας αντικειμένων `Node`**. Όταν το Latte χρειάζεται να διασχίσει τα παιδιά του `AuxiliaryNode` (π.χ. κατά τη διάρκεια των compilation passes), η μέθοδός του `getIterator()` απλώς παρέχει τους κόμβους που αναφέρονται σε αυτόν τον πίνακα. + +Παράδειγμα: + +```php +$node = new AuxiliaryNode( + // 1. Αυτό το closure γίνεται το σώμα του print() + fn(PrintContext $context, $arg1, $arg2) => $context->format('...%node...%node...', $arg1, $arg2), + + // 2. Αυτοί οι κόμβοι παρέχονται από τη μέθοδο getIterator() και περνούν στο παραπάνω closure + [$argumentNode1, $argumentNode2] +); +``` + +Το Latte παρέχει δύο διακριτούς τύπους βασισμένους στο πού χρειάζεται να εισαγάγετε τον παραγόμενο κώδικα: + +- `Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode`: Χρησιμοποιήστε το όταν χρειάζεται να δημιουργήσετε ένα κομμάτι κώδικα PHP που αντιπροσωπεύει μια **έκφραση** +- `Latte\Compiler\Nodes\AuxiliaryNode`: Χρησιμοποιήστε το για πιο γενικούς σκοπούς, όταν χρειάζεται να εισαγάγετε ένα μπλοκ κώδικα PHP που αντιπροσωπεύει μία ή περισσότερες **εντολές** + +Ένας σημαντικός λόγος για τη χρήση του `AuxiliaryNode` αντί για τυπικούς κόμβους (όπως `StaticMethodCallNode`) εντός της μεθόδου `print()` ή ενός compilation pass είναι ο **έλεγχος της ορατότητας για τα επόμενα compilation passes**, ειδικά αυτά που σχετίζονται με την ασφάλεια, όπως το Sandbox. + +Εξετάστε ένα σενάριο: Το compilation pass σας χρειάζεται να περιβάλλει μια έκφραση που παρέχεται από τον χρήστη (`$userExpr`) με μια κλήση σε μια συγκεκριμένη, αξιόπιστη βοηθητική συνάρτηση `myInternalSanitize($userExpr)`. Εάν δημιουργήσετε έναν τυπικό κόμβο `new FunctionCallNode('myInternalSanitize', [$userExpr])`, θα είναι πλήρως ορατός στη διέλευση του AST. Εάν το Sandbox pass εκτελεστεί αργότερα και το `myInternalSanitize` *δεν* βρίσκεται στη λίστα επιτρεπόμενων του, το Sandbox μπορεί να *μπλοκάρει* ή να τροποποιήσει αυτή την κλήση, διαταράσσοντας πιθανώς την εσωτερική λογική του tag σας, ακόμα κι αν *εσείς*, ο συγγραφέας του tag, γνωρίζετε ότι αυτή η συγκεκριμένη κλήση είναι ασφαλής και απαραίτητη. Μπορείτε λοιπόν να δημιουργήσετε την κλήση απευθείας εντός του closure του `AuxiliaryNode`. + +```php +use Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode; + +// ... εντός του print() ή ενός compilation pass ... +$wrappedNode = new AuxiliaryNode( + fn(PrintContext $context, $userExpr) => $context->format( + 'myInternalSanitize(%node)', // Άμεση δημιουργία κώδικα PHP + $userExpr, + ), + // ΣΗΜΑΝΤΙΚΟ: Παρ' όλα αυτά, περάστε τον αρχικό κόμβο έκφρασης χρήστη εδώ! + [$userExpr], +); +``` + +Σε αυτή την περίπτωση, το Sandbox pass βλέπει το `AuxiliaryNode`, αλλά **δεν αναλύει τον κώδικα PHP που δημιουργείται από το closure του**. Δεν μπορεί να μπλοκάρει απευθείας την κλήση `myInternalSanitize` που δημιουργείται *εντός* του closure. + +Ενώ ο ίδιος ο παραγόμενος κώδικας PHP είναι κρυμμένος από τα passes, οι *είσοδοι* σε αυτόν τον κώδικα (οι κόμβοι που αντιπροσωπεύουν δεδομένα ή εκφράσεις χρήστη) **πρέπει ακόμα να είναι διασχίσιμοι**. Γι' αυτό το δεύτερο argument του constructor του `AuxiliaryNode` είναι κρίσιμο. **Πρέπει** να περάσετε έναν πίνακα που περιέχει όλους τους αρχικούς κόμβους (όπως `$userExpr` στο παραπάνω παράδειγμα) που χρησιμοποιεί το closure σας. Το `getIterator()` του `AuxiliaryNode` **θα παρέχει αυτούς τους κόμβους**, επιτρέποντας στα compilation passes όπως το Sandbox να τους αναλύσουν για πιθανά προβλήματα. + + +Βέλτιστες πρακτικές +=================== + +- **Σαφής σκοπός:** Βεβαιωθείτε ότι το tag σας έχει σαφή και απαραίτητο σκοπό. Μην δημιουργείτε tags για εργασίες που μπορούν εύκολα να αντιμετωπιστούν με [φίλτρα |custom-filters] ή [συναρτήσεις |custom-functions]. +- **Υλοποιήστε σωστά το `getIterator()`:** Πάντα υλοποιείτε το `getIterator()` και παρέχετε *αναφορές* (`&`) σε *όλους* τους παιδικούς κόμβους (arguments, περιεχόμενο) που αναλύθηκαν από το πρότυπο. Αυτό είναι απαραίτητο για τα compilation passes, την ασφάλεια (Sandbox) και πιθανές μελλοντικές βελτιστοποιήσεις. +- **Δημόσιες ιδιότητες για κόμβους:** Κάντε τις ιδιότητες που περιέχουν παιδικούς κόμβους δημόσιες, ώστε τα compilation passes να μπορούν να τις τροποποιήσουν εάν χρειαστεί. +- **Χρησιμοποιήστε το `PrintContext::format()`:** Αξιοποιήστε τη μέθοδο `format()` για τη δημιουργία κώδικα PHP. Χειρίζεται τα εισαγωγικά, κάνει σωστά escape τους placeholders και προσθέτει αυτόματα σχόλια με τον αριθμό γραμμής. +- **Προσωρινές μεταβλητές (`$__`):** Κατά τη δημιουργία runtime κώδικα PHP που χρειάζεται προσωρινές μεταβλητές (π.χ. για αποθήκευση ενδιάμεσων αθροισμάτων, μετρητές βρόχου), χρησιμοποιήστε τη σύμβαση του προθέματος `$__` για να αποφύγετε συγκρούσεις με μεταβλητές χρήστη και εσωτερικές μεταβλητές Latte `$ʟ_`. +- **Ένθεση και μοναδικά ID:** Εάν το tag σας μπορεί να είναι ένθετο ή χρειάζεται κατάσταση συγκεκριμένη για την παρουσία κατά το runtime, χρησιμοποιήστε το `$context->generateId()` εντός της μεθόδου `print()` για να δημιουργήσετε μοναδικά επιθέματα για τις προσωρινές σας μεταβλητές `$__`. +- **Providers για εξωτερικά δεδομένα:** Χρησιμοποιήστε providers (καταχωρημένους μέσω του `Extension::getProviders()`) για πρόσβαση σε runtime δεδομένα ή υπηρεσίες ($this->global->...) αντί να κωδικοποιείτε τιμές ή να βασίζεστε σε global state. Χρησιμοποιήστε προθέματα κατασκευαστή για τα ονόματα των providers. +- **Εξετάστε τα n:attributes:** Εάν το paired tag σας λειτουργεί λογικά σε ένα μόνο HTML element, το Latte πιθανότατα παρέχει αυτόματη υποστήριξη `n:attribute`. Λάβετε αυτό υπόψη για την ευκολία του χρήστη. Εάν δημιουργείτε ένα tag που τροποποιεί ένα attribute, εξετάστε εάν ένα pure `n:attribute` είναι η καταλληλότερη μορφή. +- **Testing:** Γράψτε tests για τα tags σας, καλύπτοντας τόσο την ανάλυση διαφόρων συντακτικών εισόδων όσο και την ορθότητα της εξόδου του παραγόμενου **κώδικα PHP**. + +Ακολουθώντας αυτές τις οδηγίες, μπορείτε να δημιουργήσετε ισχυρά, στιβαρά και συντηρήσιμα προσαρμοσμένα tags που ενσωματώνονται άψογα με τη μηχανή templating του Latte. + +.[note] +Η μελέτη των κλάσεων κόμβων που αποτελούν μέρος του Latte είναι ο καλύτερος τρόπος για να μάθετε όλες τις λεπτομέρειες της διαδικασίας ανάλυσης. diff --git a/latte/el/develop.texy b/latte/el/develop.texy index 509772eb0e..e0ac579613 100644 --- a/latte/el/develop.texy +++ b/latte/el/develop.texy @@ -1,70 +1,66 @@ -Πρακτικές για προγραμματιστές -***************************** +Πρακτικές Ανάπτυξης +******************* -Εγκατάσταση .[#toc-installation] -================================ +Εγκατάσταση +=========== -Ο καλύτερος τρόπος εγκατάστασης του Latte είναι η χρήση ενός Composer: +Ο καλύτερος τρόπος για να εγκαταστήσετε το Latte είναι χρησιμοποιώντας το Composer: ```shell composer require latte/latte ``` -Latte: Υποστηριζόμενες εκδόσεις PHP (ισχύει για τις πιο πρόσφατες εκδόσεις της Latte): +Υποστηριζόμενες εκδόσεις PHP (ισχύει για τις τελευταίες εκδόσεις patch του Latte): -| έκδοση | συμβατή με PHP +| έκδοση | συμβατό με PHP |-----------------|------------------- -| Latte 3.0 | PHP 8.0 - 8.2 -| Latte 2.11 | PHP 7.1 - 8.2 -| Latte 2.8 - 2.10 | PHP 7.1 - 8.1 +| Latte 3.0 | PHP 8.0 – 8.2 -Πώς να αποδώσετε ένα πρότυπο .[#toc-how-to-render-a-template] -============================================================= +Πώς να Αποδώσετε ένα Πρότυπο +============================ -Πώς να αποδώσετε ένα πρότυπο; Χρησιμοποιήστε αυτόν τον απλό κώδικα: +Πώς να αποδώσετε ένα πρότυπο; Αυτός ο απλός κώδικας είναι αρκετός: ```php $latte = new Latte\Engine; // κατάλογος cache $latte->setTempDirectory('/path/to/tempdir'); -$params = [ /* template variables */ ]; -// ή $params = new TemplateParameters(/* ... */), +$params = [ /* μεταβλητές προτύπου */ ]; +// ή $params = new TemplateParameters(/* ... */); // απόδοση στην έξοδο $latte->render('template.latte', $params); -// ή απόδοση σε μεταβλητή +// απόδοση σε μεταβλητή $output = $latte->renderToString('template.latte', $params); ``` -Οι παράμετροι μπορούν να είναι πίνακες ή ακόμα καλύτερα [αντικείμενο |#Parameters as a class], το οποίο θα παρέχει έλεγχο τύπου και πρόταση στον επεξεργαστή. +Οι παράμετροι μπορεί να είναι ένας πίνακας ή, ακόμα καλύτερα, ένα [αντικείμενο |#Παράμετροι ως Κλάση], το οποίο εξασφαλίζει έλεγχο τύπου και υποδείξεις σε επεξεργαστές. .[note] -Μπορείτε επίσης να βρείτε παραδείγματα χρήσης στο αποθετήριο [Latte examples |https://github.com/nette-examples/latte]. +Παραδείγματα χρήσης μπορείτε επίσης να βρείτε στο αποθετήριο [Latte examples |https://github.com/nette-examples/latte]. -Απόδοση και προσωρινή αποθήκευση .[#toc-performance-and-caching] -================================================================ +Απόδοση και Cache +================= -Τα πρότυπα Latte είναι εξαιρετικά γρήγορα, επειδή το Latte τα μεταγλωττίζει απευθείας σε κώδικα PHP και τα αποθηκεύει στην προσωρινή μνήμη στο δίσκο. Έτσι, δεν έχουν καμία επιπλέον επιβάρυνση σε σύγκριση με τα πρότυπα που είναι γραμμένα σε καθαρή PHP. +Τα πρότυπα στο Latte είναι εξαιρετικά γρήγορα, καθώς το Latte τα μεταγλωττίζει απευθείας σε κώδικα PHP και τα αποθηκεύει στην cache στον δίσκο. Επομένως, δεν έχουν επιπλέον επιβάρυνση σε σύγκριση με τα πρότυπα που γράφονται σε καθαρή PHP. -Η κρυφή μνήμη αναγεννάται αυτόματα κάθε φορά που αλλάζετε το αρχείο πηγής. Έτσι, μπορείτε άνετα να επεξεργάζεστε τα πρότυπα Latte κατά τη διάρκεια της ανάπτυξης και να βλέπετε τις αλλαγές αμέσως στο πρόγραμμα περιήγησης. Μπορείτε να απενεργοποιήσετε αυτό το χαρακτηριστικό σε περιβάλλον παραγωγής και να εξοικονομήσετε λίγη απόδοση: +Η cache αναδημιουργείται αυτόματα κάθε φορά που αλλάζετε το πηγαίο αρχείο. Έτσι, κατά τη διάρκεια της ανάπτυξης, μπορείτε εύκολα να επεξεργαστείτε τα πρότυπα του Latte και να δείτε αμέσως τις αλλαγές στον περιηγητή. Μπορείτε να απενεργοποιήσετε αυτή τη λειτουργία σε περιβάλλον παραγωγής για να εξοικονομήσετε λίγη απόδοση: ```php $latte->setAutoRefresh(false); ``` -Όταν αναπτύσσεται σε έναν διακομιστή παραγωγής, η αρχική δημιουργία προσωρινής μνήμης, ειδικά για μεγαλύτερες εφαρμογές, μπορεί δικαιολογημένα να πάρει αρκετό χρόνο. Το Latte διαθέτει ενσωματωμένη πρόληψη κατά του "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. -Πρόκειται για μια κατάσταση όπου ο διακομιστής λαμβάνει μεγάλο αριθμό ταυτόχρονων αιτήσεων και επειδή η κρυφή μνήμη cache του Latte δεν υπάρχει ακόμα, θα την δημιουργούσαν όλες ταυτόχρονα. Το οποίο αυξάνει την CPU. -Το Latte είναι έξυπνο, και όταν υπάρχουν πολλαπλές ταυτόχρονες αιτήσεις, μόνο το πρώτο νήμα δημιουργεί την κρυφή μνήμη, τα υπόλοιπα περιμένουν και στη συνέχεια τη χρησιμοποιούν. +Κατά την ανάπτυξη σε έναν διακομιστή παραγωγής, η αρχική δημιουργία της cache, ειδικά για μεγαλύτερες εφαρμογές, μπορεί φυσικά να διαρκέσει λίγο. Το Latte διαθέτει ενσωματωμένη πρόληψη έναντι του "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Αυτή είναι μια κατάσταση όπου ένας μεγάλος αριθμός ταυτόχρονων αιτημάτων φτάνει, τα οποία ξεκινούν το Latte, και επειδή η cache δεν υπάρχει ακόμα, όλα θα άρχιζαν να τη δημιουργούν ταυτόχρονα. Αυτό θα επιβάρυνε υπερβολικά τον διακομιστή. Το Latte είναι έξυπνο και σε περίπτωση πολλαπλών ταυτόχρονων αιτημάτων, μόνο το πρώτο νήμα δημιουργεί την cache, τα άλλα περιμένουν και στη συνέχεια τη χρησιμοποιούν. -Παράμετροι ως κλάση .[#toc-parameters-as-a-class] -================================================= +Παράμετροι ως Κλάση +=================== -Καλύτερα από το να περνάτε τις μεταβλητές στο πρότυπο ως πίνακες είναι να δημιουργήσετε μια κλάση. Μπορείτε να έχετε [συμβολισμό με ασφάλεια τύπου |type-system], [ωραία πρόταση στο IDE |recipes#Editors and IDE] και έναν τρόπο να [καταχωρείτε φίλτρα |extending-latte#Filters Using the Class] και [συναρτήσεις |extending-latte#Functions Using the Class]. +Καλύτερα από το να περνάτε μεταβλητές στο πρότυπο ως πίνακα είναι να δημιουργήσετε μια κλάση. Θα αποκτήσετε έτσι [type-safe notation|type-system], [ευχάριστες υποδείξεις σε IDE |recipes#Επεξεργαστές και IDE] και έναν τρόπο για [καταχώρηση φίλτρων |custom-filters#Φίλτρα που χρησιμοποιούν μια κλάση με attributes] και [συναρτήσεων |custom-functions#Συναρτήσεις που χρησιμοποιούν μια κλάση με attributes]. ```php class MailTemplateParameters @@ -88,12 +84,12 @@ $latte->render('mail.latte', new MailTemplateParameters( ``` -Απενεργοποίηση του Auto-Escaping της μεταβλητής .[#toc-disabling-auto-escaping-of-variable] -=========================================================================================== +Απενεργοποίηση Αυτόματης Διαφυγής Μεταβλητής +============================================ -Εάν η μεταβλητή περιέχει μια συμβολοσειρά HTML, μπορείτε να τη σημειώσετε έτσι ώστε το Latte να μην την αποφεύγει αυτόματα (και επομένως διπλά). Με αυτόν τον τρόπο αποφεύγεται η ανάγκη προσδιορισμού του `|noescape` στο πρότυπο. +Εάν μια μεταβλητή περιέχει μια συμβολοσειρά HTML, μπορείτε να την επισημάνετε έτσι ώστε το Latte να μην τη διαφεύγει αυτόματα (και επομένως διπλά). Αυτό αποφεύγει την ανάγκη χρήσης του `|noescape` στο πρότυπο. -Ο ευκολότερος τρόπος είναι να τυλίξετε το αλφαριθμητικό σε ένα αντικείμενο `Latte\Runtime\Html`: +Ο ευκολότερος τρόπος είναι να τυλίξετε τη συμβολοσειρά σε ένα αντικείμενο `Latte\Runtime\Html`: ```php $params = [ @@ -101,7 +97,7 @@ $params = [ ]; ``` -Latte δεν αποφεύγει επίσης όλα τα αντικείμενα που υλοποιούν τη διεπαφή `Latte\HtmlStringable`. Έτσι, μπορείτε να δημιουργήσετε τη δική σας κλάση της οποίας η μέθοδος `__toString()` θα επιστρέφει κώδικα HTML που δεν θα αποφεύγεται αυτόματα: +Το Latte επίσης δεν διαφεύγει όλα τα αντικείμενα που υλοποιούν τη διεπαφή `Latte\HtmlStringable`. Μπορείτε να δημιουργήσετε τη δική σας κλάση της οποίας η μέθοδος `__toString()` θα επιστρέφει κώδικα HTML που δεν θα διαφεύγεται αυτόματα: ```php class Emphasis extends Latte\HtmlStringable @@ -123,32 +119,85 @@ $params = [ ``` .[warning] -Η μέθοδος `__toString` πρέπει να επιστρέφει σωστή HTML και να παρέχει escaping παραμέτρων, διαφορετικά μπορεί να προκύψει ευπάθεια XSS! +Η μέθοδος `__toString` πρέπει να επιστρέφει έγκυρο HTML και να διασφαλίζει τη διαφυγή των παραμέτρων, διαφορετικά μπορεί να προκύψει ευπάθεια XSS! -Πώς να επεκτείνετε το Latte με φίλτρα, ετικέτες κ.λπ. .[#toc-how-to-extend-latte-with-filters-tags-etc] -======================================================================================================= +Πώς να Επεκτείνετε το Latte με Φίλτρα, Tags, κ.λπ. +================================================== -Πώς να προσθέσετε ένα προσαρμοσμένο φίλτρο, μια λειτουργία, μια ετικέτα κ.λπ. στο Latte; Μάθετε στο κεφάλαιο [επέκταση της Latte |extending Latte]. -Αν θέλετε να επαναχρησιμοποιήσετε τις αλλαγές σας σε διαφορετικά έργα ή αν θέλετε να τις μοιραστείτε με άλλους, τότε θα πρέπει να [δημιουργήσετε μια επέκταση |creating-extension]. +Πώς να προσθέσετε το δικό σας φίλτρο, συνάρτηση, tag, κ.λπ. στο Latte; Αυτό καλύπτεται στο κεφάλαιο [επεκτείνοντας το Latte |extending-latte]. Εάν θέλετε να επαναχρησιμοποιήσετε τις τροποποιήσεις σας σε διαφορετικά έργα ή να τις μοιραστείτε με άλλους, θα πρέπει να [δημιουργήσετε μια επέκταση |extending-latte#Latte Extension]. -Οποιοσδήποτε κώδικας στο πρότυπο `{php ...}` .{data-version:3.0}{toc: RawPhpExtension} -====================================================================================== +Οποιοσδήποτε Κώδικας στο Πρότυπο `{php ...}` .{toc: RawPhpExtension} +==================================================================== -Μόνο εκφράσεις PHP μπορούν να γραφτούν μέσα στο [`{do}` |tags#do] tag, οπότε δεν μπορείτε, για παράδειγμα, να εισάγετε δομές όπως `if ... else` ή δηλώσεις με τελεία και παύλα. +Μέσα στο tag [`{do}` |tags#do], μπορείτε να γράψετε μόνο εκφράσεις PHP, οπότε δεν μπορείτε, για παράδειγμα, να εισαγάγετε κατασκευές όπως `if ... else` ή εντολές που τελειώνουν με ερωτηματικό. -Ωστόσο, μπορείτε να καταχωρήσετε την επέκταση `RawPhpExtension`, η οποία προσθέτει την ετικέτα `{php ...}`, η οποία μπορεί να χρησιμοποιηθεί για την εισαγωγή οποιουδήποτε κώδικα PHP με ευθύνη του δημιουργού του προτύπου. +Ωστόσο, μπορείτε να καταχωρήσετε την επέκταση `RawPhpExtension`, η οποία προσθέτει το tag `{php ...}`. Χρησιμοποιώντας το, μπορείτε να εισαγάγετε οποιονδήποτε κώδικα PHP. Δεν ισχύουν κανόνες λειτουργίας sandbox, οπότε η χρήση είναι ευθύνη του συγγραφέα του προτύπου. ```php $latte->addExtension(new Latte\Essential\RawPhpExtension); ``` -Μετάφραση σε πρότυπα .{data-version:3.0}{toc: TranslatorExtension} -================================================================== +Έλεγχος Δημιουργημένου Κώδικα .{data-version:3.0.7} +=================================================== + +Το Latte μεταγλωττίζει τα πρότυπα σε κώδικα PHP. Φυσικά, διασφαλίζει ότι ο δημιουργημένος κώδικας είναι συντακτικά έγκυρος. Ωστόσο, όταν χρησιμοποιείτε επεκτάσεις τρίτων ή το `RawPhpExtension`, το Latte δεν μπορεί να εγγυηθεί την ορθότητα του δημιουργημένου αρχείου. Είναι επίσης δυνατό να γράψετε κώδικα στην PHP που είναι συντακτικά σωστός, αλλά απαγορευμένος (για παράδειγμα, ανάθεση τιμής στη μεταβλητή `$this`) και προκαλεί PHP Compile Error. Εάν γράψετε μια τέτοια λειτουργία σε ένα πρότυπο, θα συμπεριληφθεί και στον δημιουργημένο κώδικα PHP. Δεδομένου ότι υπάρχουν περίπου διακόσιες διαφορετικές απαγορευμένες λειτουργίες στην PHP, το Latte δεν φιλοδοξεί να τις ανιχνεύσει. Η ίδια η PHP θα τις επισημάνει κατά την απόδοση, κάτι που συνήθως δεν αποτελεί πρόβλημα. + +Ωστόσο, υπάρχουν καταστάσεις όπου θέλετε να γνωρίζετε ήδη κατά τη μεταγλώττιση του προτύπου ότι δεν περιέχει PHP Compile Errors. Ειδικά εάν τα πρότυπα μπορούν να επεξεργαστούν από χρήστες, ή εάν χρησιμοποιείτε το [Sandbox |sandbox]. Σε αυτή την περίπτωση, ελέγξτε τα πρότυπα ήδη κατά τη μεταγλώττιση. Αυτή η λειτουργικότητα ενεργοποιείται με τη μέθοδο `Engine::enablePhpLint()`. Δεδομένου ότι χρειάζεται να καλέσει το εκτελέσιμο PHP για τον έλεγχο, περάστε τη διαδρομή προς αυτό ως παράμετρο: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); + +try { + $latte->compile('home.latte'); +} catch (Latte\CompileException $e) { + // πιάνει σφάλματα στο Latte και επίσης Compile Errors στην PHP + echo 'Σφάλμα: ' . $e->getMessage(); +} +``` + + +Τοπικές Ρυθμίσεις .{data-version:3.0.18}{toc: Locale} +===================================================== + +Το Latte σας επιτρέπει να ορίσετε τις τοπικές ρυθμίσεις, οι οποίες επηρεάζουν τη μορφοποίηση αριθμών, ημερομηνιών και τη ταξινόμηση. Ορίζεται χρησιμοποιώντας τη μέθοδο `setLocale()`. Το αναγνωριστικό τοπικών ρυθμίσεων ακολουθεί το πρότυπο IETF language tag, το οποίο χρησιμοποιείται από την επέκταση PHP `intl`. Αποτελείται από τον κωδικό γλώσσας και προαιρετικά τον κωδικό χώρας, π.χ. `en_US` για Αγγλικά στις Ηνωμένες Πολιτείες, `de_DE` για Γερμανικά στη Γερμανία, κ.λπ. + +```php +$latte = new Latte\Engine; +$latte->setLocale('el'); +``` + +Η ρύθμιση των τοπικών ρυθμίσεων επηρεάζει τα φίλτρα [localDate |filters#localDate], [sort |filters#sort], [number |filters#number] και [bytes |filters#bytes]. + +.[note] +Απαιτεί την επέκταση PHP `intl`. Η ρύθμιση στο Latte δεν επηρεάζει τις καθολικές ρυθμίσεις τοπικών ρυθμίσεων στην PHP. + -Χρησιμοποιήστε την επέκταση `TranslatorExtension` για να προσθέσετε [`{_...}` |tags#_], [`{translate}` |tags#translate] και φιλτράρετε [`translate` |filters#translate] στο πρότυπο. Χρησιμοποιούνται για τη μετάφραση τιμών ή τμημάτων του προτύπου σε άλλες γλώσσες. Η παράμετρος είναι η μέθοδος (PHP callable) που εκτελεί τη μετάφραση: +Αυστηρή Λειτουργία .{data-version:3.0.8} +======================================== + +Στην αυστηρή λειτουργία ανάλυσης, το Latte ελέγχει εάν λείπουν τα κλεισίματα των tags HTML και επίσης απαγορεύει τη χρήση της μεταβλητής `$this`. Την ενεργοποιείτε ως εξής: + +```php +$latte = new Latte\Engine; +$latte->setStrictParsing(); +``` + +Για να ενεργοποιήσετε τη δημιουργία προτύπων με την κεφαλίδα `declare(strict_types=1)`, κάντε το ως εξής: + +```php +$latte = new Latte\Engine; +$latte->setStrictTypes(); +``` + + +Μετάφραση σε Πρότυπα .{toc: TranslatorExtension} +================================================ + +Χρησιμοποιώντας την επέκταση `TranslatorExtension`, προσθέτετε τα tags [`{_...}` |tags#], [`{translate}` |tags#translate] και το φίλτρο [`translate` |filters#translate] στο πρότυπο. Χρησιμοποιούνται για τη μετάφραση τιμών ή τμημάτων του προτύπου σε άλλες γλώσσες. Ως παράμετρο, καθορίζουμε τη μέθοδο (PHP callable) που εκτελεί τη μετάφραση: ```php class MyTranslator @@ -158,19 +207,19 @@ class MyTranslator public function translate(string $original): string { - // δημιουργία $translated από $original σύμφωνα με $this->lang + // δημιουργούμε το $translated από το $original σύμφωνα με το $this->lang return $translated; } } $translator = new MyTranslator($lang); $extension = new Latte\Essential\TranslatorExtension( - $translator->translate(...), // [$translator, 'translate'] σε PHP 8.0 + $translator->translate(...), // [$translator, 'translate'] στην PHP 8.0 ); $latte->addExtension($extension); ``` -Ο μεταφραστής καλείται κατά το χρόνο εκτέλεσης όταν το πρότυπο αποδίδεται. Ωστόσο, το Latte μπορεί να μεταφράσει όλα τα στατικά κείμενα κατά τη διάρκεια της σύνταξης του προτύπου. Αυτό εξοικονομεί απόδοση επειδή κάθε συμβολοσειρά μεταφράζεται μόνο μία φορά και η μετάφραση που προκύπτει γράφεται στο αρχείο που μεταγλωττίζεται. Αυτό δημιουργεί πολλαπλές μεταγλωττισμένες εκδόσεις του προτύπου στον κατάλογο cache, μία για κάθε γλώσσα. Για να το κάνετε αυτό, χρειάζεται μόνο να καθορίσετε τη γλώσσα ως δεύτερη παράμετρο: +Ο Translator καλείται κατά το χρόνο εκτέλεσης κατά την απόδοση του προτύπου. Ωστόσο, το Latte μπορεί να μεταφράσει όλα τα στατικά κείμενα ήδη κατά τη μεταγλώττιση του προτύπου. Αυτό εξοικονομεί απόδοση, καθώς κάθε συμβολοσειρά μεταφράζεται μόνο μία φορά και η προκύπτουσα μετάφραση γράφεται στη μεταγλωττισμένη μορφή. Έτσι, δημιουργούνται πολλαπλές μεταγλωττισμένες εκδόσεις του προτύπου στον κατάλογο cache, μία για κάθε γλώσσα. Για να το κάνετε αυτό, απλά καθορίστε τη γλώσσα ως δεύτερη παράμετρο: ```php $extension = new Latte\Essential\TranslatorExtension( @@ -179,9 +228,9 @@ $extension = new Latte\Essential\TranslatorExtension( ); ``` -Με τον όρο στατικό κείμενο εννοούμε, για παράδειγμα, το `{_'hello'}` ή το `{translate}hello{/translate}`. Μη στατικό κείμενο, όπως το `{_$foo}`, θα συνεχίσει να μεταφράζεται κατά την εκτέλεση. +Στατικό κείμενο σημαίνει κάτι σαν `{_'hello'}` ή `{translate}hello{/translate}`. Μη στατικά κείμενα, όπως `{_$foo}`, θα συνεχίσουν να μεταφράζονται κατά το χρόνο εκτέλεσης. -Το πρότυπο μπορεί επίσης να περάσει πρόσθετες παραμέτρους στον μεταφραστή μέσω των `{_$original, foo: bar}` ή `{translate foo: bar}`, τις οποίες λαμβάνει ως πίνακα `$params`: +Μπορείτε επίσης να περάσετε πρόσθετες παραμέτρους στον μεταφραστή από το πρότυπο χρησιμοποιώντας `{_$original, foo: bar}` ή `{translate foo: bar}`, τις οποίες λαμβάνει ως πίνακα `$params`: ```php public function translate(string $original, ...$params): string @@ -191,66 +240,73 @@ public function translate(string $original, ...$params): string ``` -Αποσφαλμάτωση και Tracy .[#toc-debugging-and-tracy] -=================================================== +Debugging και Tracy +=================== -Η Latte προσπαθεί να κάνει την ανάπτυξη όσο το δυνατόν πιο ευχάριστη. Για σκοπούς αποσφαλμάτωσης, υπάρχουν τρεις ετικέτες [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak] και [`{trace}` |tags#trace]. +Το Latte προσπαθεί να κάνει την ανάπτυξη όσο το δυνατόν πιο ευχάριστη για εσάς. Υπάρχουν τρία tags ειδικά για σκοπούς debugging: [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak] και [`{trace}` |tags#trace]. -Θα έχετε τη μεγαλύτερη άνεση αν εγκαταστήσετε το εξαιρετικό [εργαλείο εντοπισμού σφαλμάτων Tracy |tracy:] και ενεργοποιήσετε το πρόσθετο Latte: +Θα έχετε τη μεγαλύτερη άνεση εάν εγκαταστήσετε επίσης το εξαιρετικό [εργαλείο εντοπισμού σφαλμάτων Tracy|tracy:] και ενεργοποιήσετε το πρόσθετο για το Latte: ```php -// επιτρέπει Tracy +// ενεργοποίηση του Tracy Tracy\Debugger::enable(); $latte = new Latte\Engine; -// ενεργοποιεί την επέκταση της Tracy +// ενεργοποίηση της επέκτασης για το Tracy $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension); ``` -Τώρα θα βλέπετε όλα τα σφάλματα σε μια καθαρή κόκκινη οθόνη, συμπεριλαμβανομένων των σφαλμάτων σε πρότυπα με επισήμανση γραμμών και στηλών ([βίντεο |https://github.com/nette/tracy/releases/tag/v2.9.0]). -Ταυτόχρονα, στην κάτω δεξιά γωνία στη λεγόμενη Tracy Bar, εμφανίζεται μια καρτέλα για το Latte, όπου μπορείτε να δείτε ξεκάθαρα όλα τα αποδιδόμενα πρότυπα και τις σχέσεις τους (συμπεριλαμβανομένης της δυνατότητας να κάνετε κλικ στο πρότυπο ή στον μεταγλωττισμένο κώδικα), καθώς και τις μεταβλητές: +Τώρα, όλα τα σφάλματα θα εμφανίζονται σε μια καθαρή κόκκινη οθόνη, συμπεριλαμβανομένων των σφαλμάτων στα πρότυπα με επισήμανση γραμμής και στήλης ([βίντεο|https://github.com/nette/tracy/releases/tag/v2.9.0]). Ταυτόχρονα, στην κάτω δεξιά γωνία στο λεγόμενο Tracy Bar, θα εμφανιστεί μια καρτέλα για το Latte, όπου όλα τα αποδιδόμενα πρότυπα και οι αμοιβαίες σχέσεις τους είναι ορατά με σαφήνεια (συμπεριλαμβανομένης της δυνατότητας κλικ στο πρότυπο ή στον μεταγλωττισμένο κώδικα) καθώς και οι μεταβλητές: [* latte-debugging.webp *] -Δεδομένου ότι το Latte μεταγλωττίζει τα πρότυπα σε αναγνώσιμο κώδικα PHP, μπορείτε εύκολα να τα διαβάσετε στο IDE σας. +Δεδομένου ότι το Latte μεταγλωττίζει τα πρότυπα σε σαφή κώδικα PHP, μπορείτε εύκολα να τα βηματοποιήσετε στο IDE σας. -Linter: Επικύρωση του συντακτικού του προτύπου .{data-version:2.11}{toc: Linter} -================================================================================ +Linter: Επικύρωση Σύνταξης Προτύπου .{toc: Linter} +================================================== -Το εργαλείο Linter θα σας βοηθήσει να περάσετε από όλα τα πρότυπα και να ελέγξετε για συντακτικά λάθη. Εκκινείται από την κονσόλα: +Το εργαλείο Linter σας βοηθά να ελέγξετε όλα τα πρότυπα και να επαληθεύσετε ότι δεν περιέχουν συντακτικά σφάλματα. Εκτελείται από την κονσόλα: ```shell -vendor/bin/latte-lint +vendor/bin/latte-lint <διαδρομή> ``` -Εάν χρησιμοποιείτε προσαρμοσμένες ετικέτες, δημιουργήστε επίσης τον προσαρμοσμένο σας Linter, π.χ. `custom-latte-lint`: +Η παράμετρος `--strict` ενεργοποιεί την [#αυστηρή λειτουργία]. + +Εάν χρησιμοποιείτε τα δικά σας tags, δημιουργήστε επίσης τη δική σας έκδοση του Linter, π.χ. `custom-latte-lint`: ```php #!/usr/bin/env php scanDirectory($path); +$path = $argv[1] ?? '.'; -$engine = new Latte\Engine; -// καταχωρεί μεμονωμένες επεκτάσεις εδώ -$engine->addExtension(/* ... */); +$linter = new Latte\Tools\Linter; +$latte = $linter->getEngine(); +// προσθέστε τις μεμονωμένες επεκτάσεις σας εδώ +$latte->addExtension(/* ... */); -$path = $argv[1]; -$linter = new Latte\Tools\Linter(engine: $engine); $ok = $linter->scanDirectory($path); exit($ok ? 0 : 1); ``` +Εναλλακτικά, μπορείτε να περάσετε το δικό σας αντικείμενο `Latte\Engine` στο Linter: + +```php +$latte = new Latte\Engine; +// διαμορφώστε το αντικείμενο $latte εδώ +$linter = new Latte\Tools\Linter(engine: $latte); +``` + -Φόρτωση προτύπων από μια συμβολοσειρά .[#toc-loading-templates-from-a-string] -============================================================================= +Φόρτωση Προτύπων από Συμβολοσειρά +================================= -Χρειάζεται να φορτώσετε πρότυπα από συμβολοσειρές αντί για αρχεία, ίσως για σκοπούς δοκιμών; Το [StringLoader |extending-latte#stringloader] θα σας βοηθήσει: +Χρειάζεται να φορτώσετε πρότυπα από συμβολοσειρές αντί για αρχεία, ίσως για σκοπούς δοκιμών; Το [StringLoader |loaders#StringLoader] μπορεί να βοηθήσει: ```php $latte->setLoader(new Latte\Loaders\StringLoader([ @@ -262,10 +318,10 @@ $latte->render('main.file', $params); ``` -Χειριστής εξαιρέσεων .[#toc-exception-handler] -============================================== +Χειριστής Εξαιρέσεων +==================== -Μπορείτε να ορίσετε το δικό σας χειριστή για τις αναμενόμενες εξαιρέσεις. Εξαιρέσεις που εγείρονται μέσα σε [`{try}` |tags#try] και στο [sandbox |sandbox], μεταβιβάζονται σε αυτόν. +Μπορείτε να ορίσετε τον δικό σας χειριστή για αναμενόμενες εξαιρέσεις. Οι εξαιρέσεις που προκύπτουν μέσα στο [`{try}` |tags#try] και στο [sandbox|sandbox] θα του περάσουν. ```php $loggingHandler = function (Throwable $e, Latte\Runtime\Template $template) use ($logger) { @@ -277,17 +333,17 @@ $latte->setExceptionHandler($loggingHandler); ``` -Αυτόματη αναζήτηση διάταξης .[#toc-automatic-layout-lookup] -=========================================================== +Αυτόματη Αναζήτηση Layout +========================= -Χρήση της ετικέτας [`{layout}` |template-inheritance#layout-inheritance], το πρότυπο καθορίζει το γονικό του πρότυπο. Είναι επίσης δυνατό να γίνεται αυτόματη αναζήτηση της διάταξης, γεγονός που θα απλοποιήσει τη συγγραφή προτύπων, αφού δεν θα χρειάζεται να περιλαμβάνουν την ετικέτα `{layout}`. +Χρησιμοποιώντας το tag [`{layout}` |template-inheritance#Κληρονομικότητα διάταξης layout], ένα πρότυπο καθορίζει το γονικό του πρότυπο. Είναι επίσης δυνατό να γίνει αυτόματη αναζήτηση του layout, γεγονός που απλοποιεί τη γραφή των προτύπων, καθώς δεν θα είναι απαραίτητο να συμπεριληφθεί το tag `{layout}` σε αυτά. Αυτό επιτυγχάνεται ως εξής: ```php $finder = function (Latte\Runtime\Template $template) { if (!$template->getReferenceType()) { - // επιστρέφει τη διαδρομή προς το γονικό αρχείο προτύπου + // επιστρέφει τη διαδρομή προς το αρχείο layout return 'automatic.layout.latte'; } }; @@ -296,4 +352,4 @@ $latte = new Latte\Engine; $latte->addProvider('coreParentFinder', $finder); ``` -Εάν το πρότυπο δεν πρέπει να έχει διάταξη, θα το υποδείξει με την ετικέτα `{layout none}`. +Εάν ένα πρότυπο δεν πρέπει να έχει layout, το δηλώνει με το tag `{layout none}`. diff --git a/latte/el/extending-latte.texy b/latte/el/extending-latte.texy index fa52a8a675..d00523fad2 100644 --- a/latte/el/extending-latte.texy +++ b/latte/el/extending-latte.texy @@ -1,285 +1,227 @@ -Επέκταση Latte -************** +Επεκτείνοντας το Latte +********************** .[perex] -Το Latte είναι πολύ ευέλικτο και μπορεί να επεκταθεί με πολλούς τρόπους: μπορείτε να προσθέσετε προσαρμοσμένα φίλτρα, συναρτήσεις, ετικέτες, φορτωτές κ.λπ. Θα σας δείξουμε πώς να το κάνετε. +Το Latte σχεδιάστηκε με γνώμονα την επεκτασιμότητα. Αν και το τυπικό σύνολο των tags, φίλτρων και συναρτήσεων καλύπτει πολλές περιπτώσεις χρήσης, συχνά χρειάζεται να προσθέσετε τη δική σας συγκεκριμένη λογική ή βοηθητικά εργαλεία. Αυτή η σελίδα παρέχει μια επισκόπηση των τρόπων επέκτασης του Latte ώστε να ταιριάζει απόλυτα στις απαιτήσεις του έργου σας - από απλούς βοηθούς έως σύνθετη νέα σύνταξη. -Αυτό το κεφάλαιο περιγράφει τους διαφορετικούς τρόπους επέκτασης του Latte. Αν θέλετε να επαναχρησιμοποιήσετε τις αλλαγές σας σε διαφορετικά έργα ή αν θέλετε να τις μοιραστείτε με άλλους, τότε θα πρέπει να [δημιουργήσετε τη λεγόμενη επέκταση |creating-extension]. +Τρόποι επέκτασης του Latte +========================== -Πόσοι δρόμοι οδηγούν στη Ρώμη; .[#toc-how-many-roads-lead-to-rome] -================================================================== +Ακολουθεί μια γρήγορη επισκόπηση των κύριων τρόπων με τους οποίους μπορείτε να προσαρμόσετε και να επεκτείνετε το Latte: -Δεδομένου ότι ορισμένοι από τους τρόπους επέκτασης του Latte μπορούν να αναμειχθούν, ας προσπαθήσουμε πρώτα να εξηγήσουμε τις διαφορές μεταξύ τους. Ως παράδειγμα, ας προσπαθήσουμε να υλοποιήσουμε μια γεννήτρια *Lorem ipsum*, στην οποία δίνεται ο αριθμός των λέξεων που πρέπει να παραχθεί. +- **[Προσαρμοσμένα φίλτρα |custom-filters]:** Για μορφοποίηση ή μετασχηματισμό δεδομένων απευθείας στην έξοδο του προτύπου (π.χ. `{$var|myFilter}`). Ιδανικό για εργασίες όπως μορφοποίηση ημερομηνιών, επεξεργασία κειμένου ή εφαρμογή συγκεκριμένου escaping. Μπορείτε επίσης να τα χρησιμοποιήσετε για να τροποποιήσετε μεγαλύτερα μπλοκ περιεχομένου HTML, περικλείοντας το περιεχόμενο σε ένα ανώνυμο [`{block}` |tags#block] και εφαρμόζοντας ένα προσαρμοσμένο φίλτρο σε αυτό. +- **[Προσαρμοσμένες συναρτήσεις |custom-functions]:** Για την προσθήκη επαναχρησιμοποιήσιμης λογικής που μπορεί να κληθεί μέσα σε εκφράσεις στο πρότυπο (π.χ. `{myFunction($arg1, $arg2)}`). Χρήσιμο για υπολογισμούς, πρόσβαση σε βοηθητικές συναρτήσεις της εφαρμογής ή δημιουργία μικρών τμημάτων περιεχομένου. +- **[Προσαρμοσμένα tags |custom-tags]:** Για τη δημιουργία εντελώς νέων γλωσσικών κατασκευών (`{mytag}...{/mytag}` ή `n:mytag`). Τα tags προσφέρουν τις περισσότερες δυνατότητες, επιτρέποντας τον ορισμό προσαρμοσμένων δομών, τον έλεγχο της ανάλυσης του προτύπου και την υλοποίηση σύνθετης λογικής απόδοσης. +- **[Compilation Passes |compiler-passes]:** Συναρτήσεις που τροποποιούν το αφηρημένο συντακτικό δέντρο (AST) του προτύπου μετά την ανάλυση, αλλά πριν από τη δημιουργία του κώδικα PHP. Χρησιμοποιούνται για προηγμένες βελτιστοποιήσεις, ελέγχους ασφαλείας (όπως το Sandbox) ή αυτόματες τροποποιήσεις κώδικα. +- **[Προσαρμοσμένοι loaders |loaders]:** Για την αλλαγή του τρόπου με τον οποίο το Latte αναζητά και φορτώνει αρχεία προτύπου (π.χ. φόρτωση από βάση δεδομένων, κρυπτογραφημένο αποθηκευτικό χώρο κ.λπ.). -Η κύρια κατασκευή της γλώσσας Latte είναι η ετικέτα. Μπορούμε να υλοποιήσουμε μια γεννήτρια επεκτείνοντας τη Latte με μια νέα ετικέτα: +Η επιλογή της σωστής μεθόδου επέκτασης είναι κρίσιμη. Πριν δημιουργήσετε ένα σύνθετο tag, σκεφτείτε αν ένα απλούστερο φίλτρο ή συνάρτηση θα ήταν αρκετό. Ας το δούμε με ένα παράδειγμα: υλοποίηση μιας γεννήτριας *Lorem ipsum* που δέχεται ως όρισμα τον αριθμό των λέξεων προς δημιουργία. -```latte -{lipsum 40} -``` - -Η ετικέτα θα λειτουργήσει εξαιρετικά. Ωστόσο, η γεννήτρια με τη μορφή ετικέτας μπορεί να μην είναι αρκετά ευέλικτη, επειδή δεν μπορεί να χρησιμοποιηθεί σε μια έκφραση. Παρεμπιπτόντως, στην πράξη, σπάνια χρειάζεται να δημιουργήσετε ετικέτες- και αυτά είναι καλά νέα, επειδή οι ετικέτες είναι ένας πιο περίπλοκος τρόπος επέκτασης. - -Εντάξει, ας δοκιμάσουμε να δημιουργήσουμε ένα φίλτρο αντί για μια ετικέτα: - -```latte -{=40|lipsum} -``` - -Και πάλι, μια έγκυρη επιλογή. Αλλά το φίλτρο θα πρέπει να μετατρέπει την τιμή που περνάει σε κάτι άλλο. Εδώ χρησιμοποιούμε την τιμή `40`, η οποία δηλώνει τον αριθμό των παραγόμενων λέξεων, ως όρισμα του φίλτρου και όχι ως την τιμή που θέλουμε να μετασχηματίσουμε. +- **Ως tag;** `{lipsum 40}` - Πιθανό, αλλά τα tags είναι πιο κατάλληλα για δομές ελέγχου ή για τη δημιουργία σύνθετων tags. Τα tags δεν μπορούν να χρησιμοποιηθούν απευθείας σε εκφράσεις. +- **Ως φίλτρο;** `{=40|lipsum}` - Τεχνικά λειτουργεί, αλλά τα φίλτρα προορίζονται για *μετασχηματισμό* της τιμής εισόδου. Εδώ, το `40` είναι ένα *όρισμα*, όχι μια τιμή που μετασχηματίζεται. Αυτό φαίνεται σημασιολογικά λανθασμένο. +- **Ως συνάρτηση;** `{lipsum(40)}` - Αυτή είναι η πιο φυσική λύση! Οι συναρτήσεις δέχονται ορίσματα και επιστρέφουν τιμές, κάτι που είναι ιδανικό για χρήση σε οποιαδήποτε έκφραση: `{var $text = lipsum(40)}`. -Ας δοκιμάσουμε λοιπόν να χρησιμοποιήσουμε τη συνάρτηση: +**Γενική σύσταση:** Χρησιμοποιήστε συναρτήσεις για υπολογισμούς/δημιουργία, φίλτρα για μετασχηματισμό και tags για νέες γλωσσικές κατασκευές ή σύνθετα tags. Χρησιμοποιήστε τα περάσματα για χειρισμό του AST και τους loaders για την ανάκτηση των προτύπων. -```latte -{lipsum(40)} -``` -Αυτό είναι! Για το συγκεκριμένο παράδειγμα, η δημιουργία μιας συνάρτησης είναι το ιδανικό σημείο επέκτασης για χρήση. Μπορείτε να την καλέσετε οπουδήποτε γίνεται δεκτή μια έκφραση, για παράδειγμα: +Άμεση εγγραφή +============= -```latte -{var $text = lipsum(40)} -``` +Για βοηθητικά εργαλεία ειδικά για το έργο ή γρήγορες επεκτάσεις, το Latte επιτρέπει την άμεση εγγραφή φίλτρων και συναρτήσεων στο αντικείμενο `Latte\Engine`. - -Φίλτρα .[#toc-filters] -====================== - -Δημιουργήστε ένα φίλτρο καταχωρώντας το όνομά του και οποιαδήποτε δυνατότητα κλήσης της PHP, όπως μια συνάρτηση: +Για να εγγράψετε ένα φίλτρο, χρησιμοποιήστε τη μέθοδο `addFilter()`. Το πρώτο όρισμα της συνάρτησης φίλτρου σας θα είναι η τιμή πριν από τον χαρακτήρα `|` και τα επόμενα ορίσματα είναι αυτά που περνούν μετά την άνω και κάτω τελεία `:`. ```php $latte = new Latte\Engine; -$latte->addFilter('shortify', fn(string $s) => mb_substr($s, 0, 10)); // συντομεύει το κείμενο σε 10 χαρακτήρες -``` -Σε αυτή την περίπτωση θα ήταν καλύτερο για το φίλτρο να λαμβάνει μια πρόσθετη παράμετρο: +// Ορισμός φίλτρου (callable: συνάρτηση, στατική μέθοδος κ.λπ.) +$myTruncate = fn(string $s, int $length = 50) => mb_substr($s, 0, $length); -```php -$latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); -``` - -Το χρησιμοποιούμε σε ένα πρότυπο όπως αυτό: +// Εγγραφή +$latte->addFilter('truncate', $myTruncate); -```latte -

                                          {$text|shortify}

                                          -

                                          {$text|shortify:100}

                                          +// Χρήση στο πρότυπο: {$text|truncate} ή {$text|truncate:100} ``` -Όπως μπορείτε να δείτε, η συνάρτηση λαμβάνει την αριστερή πλευρά του φίλτρου πριν από τον σωλήνα `|` as the first argument and the arguments passed to the filter after `:` ως τα επόμενα ορίσματα. - -Φυσικά, η συνάρτηση που αναπαριστά το φίλτρο μπορεί να δεχτεί οποιονδήποτε αριθμό παραμέτρων, ενώ υποστηρίζονται και μεταβλητές παράμετροι. - - -Φίλτρα που χρησιμοποιούν την κλάση .[#toc-filters-using-the-class] ------------------------------------------------------------------- - -Ο δεύτερος τρόπος για να ορίσετε ένα φίλτρο είναι να [χρησιμοποιήσετε την κλάση |develop#Parameters as a class]. Δημιουργούμε μια μέθοδο με το χαρακτηριστικό `TemplateFilter`: +Μπορείτε επίσης να εγγράψετε έναν **Filter Loader**, μια συνάρτηση που παρέχει δυναμικά callables φίλτρων με βάση το ζητούμενο όνομα: ```php -class TemplateParameters -{ - public function __construct( - // παράμετροι - ) {} - - #[Latte\Attributes\TemplateFilter] - public function shortify(string $s, int $len = 10): string - { - return mb_substr($s, 0, $len); - } -} - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); +$latte->addFilterLoader(fn(string $name) => /* επιστρέφει ένα callable ή null */); ``` -Εάν χρησιμοποιείτε PHP 7.x και Latte 2.x, χρησιμοποιήστε το σχόλιο `/** @filter */` αντί για το χαρακτηριστικό. - -Φορτωτής φίλτρων .[#toc-filter-loader] --------------------------------------- - -Αντί να καταχωρίζετε μεμονωμένα φίλτρα, μπορείτε να δημιουργήσετε έναν λεγόμενο φορτωτή, ο οποίος είναι μια συνάρτηση που καλείται με το όνομα του φίλτρου ως όρισμα και επιστρέφει το PHP callable του ή null. +Για να εγγράψετε μια συνάρτηση που μπορεί να χρησιμοποιηθεί σε εκφράσεις του προτύπου, χρησιμοποιήστε το `addFunction()`. ```php -$latte->addFilterLoader([new Filters, 'load']); +$latte = new Latte\Engine; +// Ορισμός συνάρτησης +$isWeekend = fn(DateTimeInterface $date) => $date->format('N') >= 6; -class Filters -{ - public function load(string $filter): ?callable - { - if (in_array($filter, get_class_methods($this))) { - return [$this, $filter]; - } - return null; - } - - public function shortify($s, $len = 10) - { - return mb_substr($s, 0, $len); - } - - // ... -} +// Εγγραφή +$latte->addFunction('isWeekend', $isWeekend); + +// Χρήση στο πρότυπο: {if isWeekend($myDate)}Σαββατοκύριακο!{/if} ``` +Περισσότερες πληροφορίες θα βρείτε στην ενότητα [Δημιουργία προσαρμοσμένων φίλτρων |custom-filters] και [Συναρτήσεων |custom-functions]. -Φίλτρα πλαισίου .[#toc-contextual-filters] ------------------------------------------- -Ένα φίλτρο πλαισίου είναι ένα φίλτρο που δέχεται ένα αντικείμενο [api:Latte\Runtime\FilterInfo] στην πρώτη παράμετρο, ακολουθούμενο από άλλες παραμέτρους όπως στην περίπτωση των κλασικών φίλτρων. Καταχωρείται με τον ίδιο τρόπο, το ίδιο το Latte αναγνωρίζει ότι το φίλτρο είναι contextual: +Στιβαρός τρόπος: Latte Extension .{toc: Latte Extension} +======================================================== -```php -use Latte\Runtime\FilterInfo; +Ενώ η άμεση εγγραφή είναι απλή, ο τυπικός και συνιστώμενος τρόπος για τη συσκευασία και διανομή επεκτάσεων Latte είναι μέσω των κλάσεων **Extension**. Ένα Extension χρησιμεύει ως κεντρικό σημείο διαμόρφωσης για την εγγραφή πολλαπλών tags, φίλτρων, συναρτήσεων, compiler passes και άλλων στοιχείων. -$latte->addFilter('foo', function (FilterInfo $info, string $str): string { - // ... -}); -``` +Γιατί να χρησιμοποιήσετε Extensions; -Τα φίλτρα πλαισίου μπορούν να ανιχνεύσουν και να αλλάξουν τον τύπο περιεχομένου που λαμβάνουν στη μεταβλητή `$info->contentType`. Αν το φίλτρο κληθεί κλασικά πάνω σε μια μεταβλητή (π.χ. `{$var|foo}`), το `$info->contentType` θα περιέχει null. +- **Οργάνωση:** Διατηρεί σχετικές επεκτάσεις (tags, φίλτρα κ.λπ. για μια συγκεκριμένη λειτουργία) μαζί σε μία κλάση. +- **Επαναχρησιμοποίηση και κοινή χρήση:** Συσκευάστε εύκολα τις επεκτάσεις σας για χρήση σε άλλα έργα ή για κοινή χρήση με την κοινότητα (π.χ. μέσω Composer). +- **Πλήρης ισχύς:** Τα προσαρμοσμένα tags και τα compiler passes *μπορούν να εγγραφούν μόνο* μέσω Extensions. -Το φίλτρο θα πρέπει πρώτα να ελέγξει αν ο τύπος περιεχομένου της συμβολοσειράς εισόδου υποστηρίζεται. Μπορεί επίσης να τον αλλάξει. Παράδειγμα ενός φίλτρου που δέχεται κείμενο (ή null) και επιστρέφει HTML: + +Εγγραφή ενός Extension +---------------------- + +Ένα Extension εγγράφεται στο Latte χρησιμοποιώντας τη μέθοδο `addExtension()` (ή μέσω του [αρχείου διαμόρφωσης |application:configuration#Templates Latte]): ```php -use Latte\Runtime\FilterInfo; - -$latte->addFilter('money', function (FilterInfo $info, float $amount): string { - // πρώτα ελέγχουμε αν ο τύπος περιεχομένου της εισόδου είναι text - if (!in_array($info->contentType, [null, ContentType::Text])) { - throw new Exception("Filter |money used in incompatible content type $info->contentType."); - } - - // αλλάζουμε τον τύπο περιεχομένου σε HTML - $info->contentType = ContentType::Html; - return "$num Kč"; -}); +$latte = new Latte\Engine; +$latte->addExtension(new MyProjectExtension); ``` -.[note] -Σε αυτή την περίπτωση, το φίλτρο πρέπει να διασφαλίσει τη σωστή διαφυγή των δεδομένων. +Εάν εγγράψετε πολλαπλές επεκτάσεις και αυτές ορίζουν tags, φίλτρα ή συναρτήσεις με το ίδιο όνομα, υπερισχύει η τελευταία προστιθέμενη επέκταση. Αυτό σημαίνει επίσης ότι οι επεκτάσεις σας μπορούν να αντικαταστήσουν τα εγγενή tags/φίλτρα/συναρτήσεις. -Όλα τα φίλτρα που χρησιμοποιούνται πάνω από [μπλοκ |tags#block] (π.χ. ως `{block|foo}...{/block}`) πρέπει να είναι συμφραζόμενα. +Κάθε φορά που κάνετε μια αλλαγή στην κλάση και η αυτόματη ανανέωση δεν είναι απενεργοποιημένη, το Latte θα μεταγλωττίσει αυτόματα τα πρότυπά σας. -Συναρτήσεις .[#toc-functions] -============================= +Δημιουργία ενός Extension +------------------------- -Από προεπιλογή, όλες οι εγγενείς συναρτήσεις PHP μπορούν να χρησιμοποιηθούν στο Latte, εκτός αν το sandbox το απενεργοποιήσει. Αλλά μπορείτε επίσης να ορίσετε τις δικές σας συναρτήσεις. Αυτές μπορούν να αντικαταστήσουν τις εγγενείς συναρτήσεις. +Για να δημιουργήσετε τη δική σας επέκταση, πρέπει να δημιουργήσετε μια κλάση που κληρονομεί από την [api:Latte\Extension]. Για να πάρετε μια ιδέα για το πώς μοιάζει μια τέτοια επέκταση, δείτε την ενσωματωμένη "CoreExtension":https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php. -Δημιουργήστε μια συνάρτηση καταγράφοντας το όνομά της και οποιαδήποτε δυνατότητα κλήσης της PHP: +Ας δούμε τις μεθόδους που μπορείτε να υλοποιήσετε: -```php -$latte = new Latte\Engine; -$latte->addFunction('random', function (...$args) { - return $args[array_rand($args)]; -}); -``` -Η χρήση είναι τότε η ίδια με την κλήση της συνάρτησης PHP: +beforeCompile(Latte\Engine $engine): void .[method] +--------------------------------------------------- -```latte -{random(apple, orange, lemon)} // prints for example: apple -``` +Καλείται πριν από τη μεταγλώττιση του προτύπου. Η μέθοδος μπορεί να χρησιμοποιηθεί, για παράδειγμα, για αρχικοποιήσεις που σχετίζονται με τη μεταγλώττιση. -Συναρτήσεις που χρησιμοποιούν την κλάση .[#toc-functions-using-the-class] -------------------------------------------------------------------------- +getTags(): array .[method] +-------------------------- -Ο δεύτερος τρόπος ορισμού μιας συνάρτησης είναι η [χρήση της κλάσης |develop#Parameters as a class]. Δημιουργούμε μια μέθοδο με το χαρακτηριστικό `TemplateFunction`: +Καλείται κατά τη μεταγλώττιση του προτύπου. Επιστρέφει έναν συσχετιστικό πίνακα *όνομα tag => callable*, που είναι συναρτήσεις για την ανάλυση των tags. [Περισσότερες πληροφορίες |custom-tags]. ```php -class TemplateParameters +public function getTags(): array { - public function __construct( - // παράμετροι - ) {} - - #[Latte\Attributes\TemplateFunction] - public function random(...$args) - { - return $args[array_rand($args)]; - } + return [ + 'foo' => FooNode::create(...), + 'bar' => BarNode::create(...), + 'n:baz' => NBazNode::create(...), + // ... + ]; } - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); ``` -Εάν χρησιμοποιείτε PHP 7.x και Latte 2.x, χρησιμοποιήστε το σχόλιο `/** @function */` αντί για το χαρακτηριστικό. +Το tag `n:baz` αντιπροσωπεύει ένα καθαρό [n:attribute |syntax#n:attributes], δηλαδή ένα tag που μπορεί να γραφτεί μόνο ως attribute. +Για τα tags `foo` και `bar`, το Latte αναγνωρίζει αυτόματα αν είναι ζευγαρωτά tags, και αν ναι, μπορούν να γραφτούν αυτόματα χρησιμοποιώντας n:attributes, συμπεριλαμβανομένων των παραλλαγών με προθέματα `n:inner-foo` και `n:tag-foo`. -Φορτωτές .[#toc-loaders] -======================== +Η σειρά εκτέλεσης τέτοιων n:attributes καθορίζεται από τη σειρά τους στον πίνακα που επιστρέφεται από τη μέθοδο `getTags()`. Έτσι, το `n:foo` εκτελείται πάντα πριν από το `n:bar`, ακόμα κι αν τα attributes στο HTML tag αναφέρονται με την αντίστροφη σειρά, όπως `
                                          `. -Οι φορτωτές είναι υπεύθυνοι για τη φόρτωση προτύπων από μια πηγή, όπως ένα σύστημα αρχείων. Ορίζονται χρησιμοποιώντας τη μέθοδο `setLoader()`: +Εάν πρέπει να καθορίσετε τη σειρά των n:attributes σε πολλαπλές επεκτάσεις, χρησιμοποιήστε τη βοηθητική μέθοδο `order()`, όπου η παράμετρος `before` xor `after` καθορίζει ποια tags ταξινομούνται πριν ή μετά το tag. ```php -$latte->setLoader(new MyLoader); +public function getTags(): array +{ + return [ + 'foo' => self::order(FooNode::create(...), before: 'bar'), + 'bar' => self::order(BarNode::create(...), after: ['block', 'snippet']), + ]; +} ``` -Οι ενσωματωμένοι φορτωτές είναι οι εξής: +getPasses(): array .[method] +---------------------------- -FileLoader .[#toc-fileloader] ------------------------------ +Καλείται κατά τη μεταγλώττιση του προτύπου. Επιστρέφει έναν συσχετιστικό πίνακα *όνομα περάσματος => callable*, που είναι συναρτήσεις που αντιπροσωπεύουν τα λεγόμενα [compiler passes |compiler-passes], τα οποία διασχίζουν και τροποποιούν το AST. -Προεπιλεγμένος φορτωτής. Φορτώνει πρότυπα από το σύστημα αρχείων. - -Η πρόσβαση στα αρχεία μπορεί να περιοριστεί με τον καθορισμό του βασικού καταλόγου: +Και εδώ μπορεί να χρησιμοποιηθεί η βοηθητική μέθοδος `order()`. Η τιμή των παραμέτρων `before` ή `after` μπορεί να είναι `*` με την έννοια πριν/μετά από όλα. ```php -$latte->setLoader(new Latte\Loaders\FileLoader($templateDir)); -$latte->render('test.latte'); +public function getPasses(): array +{ + return [ + 'optimize' => Passes::optimizePass(...), + 'sandbox' => self::order($this->sandboxPass(...), before: '*'), + // ... + ]; +} ``` -StringLoader .[#toc-stringloader] ---------------------------------- +beforeRender(Latte\Engine $engine): void .[method] +-------------------------------------------------- -Φορτώνει πρότυπα από συμβολοσειρές. Αυτός ο φορτωτής είναι πολύ χρήσιμος για δοκιμές μονάδας. Μπορεί επίσης να χρησιμοποιηθεί για μικρά έργα όπου μπορεί να έχει νόημα να αποθηκεύσετε όλα τα πρότυπα σε ένα μόνο αρχείο PHP. +Καλείται πριν από κάθε απόδοση του προτύπου. Η μέθοδος μπορεί να χρησιμοποιηθεί, για παράδειγμα, για την αρχικοποίηση μεταβλητών που χρησιμοποιούνται κατά την απόδοση. -```php -$latte->setLoader(new Latte\Loaders\StringLoader([ - 'main.file' => '{include other.file}', - 'other.file' => '{if true} {$var} {/if}', -])); -$latte->render('main.file'); -``` +getFilters(): array .[method] +----------------------------- -Απλοποιημένη χρήση: +Καλείται πριν από την απόδοση του προτύπου. Επιστρέφει φίλτρα ως συσχετιστικό πίνακα *όνομα φίλτρου => callable*. [Περισσότερες πληροφορίες |custom-filters]. ```php -$template = '{if true} {$var} {/if}'; -$latte->setLoader(new Latte\Loaders\StringLoader); -$latte->render($template); +public function getFilters(): array +{ + return [ + 'batch' => $this->batchFilter(...), + 'trim' => $this->trimFilter(...), + // ... + ]; +} ``` -Δημιουργία ενός προσαρμοσμένου φορτωτή .[#toc-creating-a-custom-loader] ------------------------------------------------------------------------ - -Ο φορτωτής είναι μια κλάση που υλοποιεί τη διεπαφή [api:Latte\Loader]. +getFunctions(): array .[method] +------------------------------- +Καλείται πριν από την απόδοση του προτύπου. Επιστρέφει συναρτήσεις ως συσχετιστικό πίνακα *όνομα συνάρτησης => callable*. [Περισσότερες πληροφορίες |custom-functions]. -Ετικέτες .[#toc-tags] -===================== +```php +public function getFunctions(): array +{ + return [ + 'clamp' => $this->clampFunction(...), + 'divisibleBy' => $this->divisibleByFunction(...), + // ... + ]; +} +``` -Ένα από τα πιο ενδιαφέροντα χαρακτηριστικά της μηχανής διαμόρφωσης προτύπων είναι η δυνατότητα ορισμού νέων γλωσσικών δομών με τη χρήση ετικετών. Είναι επίσης μια πιο σύνθετη λειτουργικότητα και πρέπει να κατανοήσετε πώς λειτουργεί εσωτερικά η Latte. -Στις περισσότερες περιπτώσεις, ωστόσο, η ετικέτα δεν είναι απαραίτητη: -- αν πρέπει να παράγει κάποια έξοδο, χρησιμοποιήστε αντ' αυτού [τη συνάρτηση |#functions] -- αν επρόκειτο να τροποποιήσει κάποια είσοδο και να την επιστρέψει, χρησιμοποιήστε αντί αυτού [filter |#filters] -- αν επρόκειτο να επεξεργαστεί μια περιοχή κειμένου, τυλίξτε την με ένα [`{block}` |tags#block] ετικέτα και χρησιμοποιήστε ένα [φίλτρο |#Contextual Filters] -- αν δεν έπρεπε να παράγει κάτι αλλά απλώς να καλεί μια συνάρτηση, καλέστε την με την εντολή [`{do}` |tags#do] +getProviders(): array .[method] +------------------------------- -Αν εξακολουθείτε να θέλετε να δημιουργήσετε μια ετικέτα, τέλεια! Όλα τα βασικά στοιχεία μπορείτε να τα βρείτε στην ενότητα [Δημιουργία μιας επέκτασης |creating-extension]. +Καλέιται πριν από την απόδοση του template. Επιστρέφει έναν πίνακα παρόχων, οι οποίοι είναι συνήθως αντικείμενα που χρησιμοποιούνται από τα tags κατά το χρόνο εκτέλεσης. Η πρόσβαση σε αυτά γίνεται μέσω του `$this->global->...`. [Περισσότερες πληροφορίες |custom-tags#Εισαγωγή των providers]. +```php +public function getProviders(): array +{ + return [ + 'myFoo' => $this->foo, + 'myBar' => $this->bar, + // ... + ]; +} +``` -Περάσματα μεταγλωττιστή .[#toc-compiler-passes] -=============================================== -Τα περάσματα μεταγλωττιστή είναι συναρτήσεις που τροποποιούν τα AST ή συλλέγουν πληροφορίες σε αυτά. Στο Latte, για παράδειγμα, ένα sandbox υλοποιείται με αυτόν τον τρόπο: διατρέχει όλους τους κόμβους ενός AST, βρίσκει κλήσεις συναρτήσεων και μεθόδων και τις αντικαθιστά με ελεγχόμενες κλήσεις. +getCacheKey(Latte\Engine $engine): mixed .[method] +-------------------------------------------------- -Όπως και με τις ετικέτες, πρόκειται για μια πιο σύνθετη λειτουργικότητα και πρέπει να κατανοήσετε πώς λειτουργεί το Latte κάτω από το καπό. Όλα τα βασικά στοιχεία μπορείτε να τα βρείτε στο κεφάλαιο [Δημιουργία μιας επέκτασης |creating-extension]. +Καλείται πριν από την απόδοση του προτύπου. Η τιμή επιστροφής γίνεται μέρος του κλειδιού, το hash του οποίου περιέχεται στο όνομα αρχείου του μεταγλωττισμένου προτύπου. Έτσι, για διαφορετικές τιμές επιστροφής, το Latte θα δημιουργήσει διαφορετικά αρχεία cache. diff --git a/latte/el/filters.texy b/latte/el/filters.texy index c7817af441..7f41fb1a8f 100644 --- a/latte/el/filters.texy +++ b/latte/el/filters.texy @@ -2,111 +2,113 @@ ************ .[perex] -Τα φίλτρα είναι συναρτήσεις που αλλάζουν ή μορφοποιούν τα δεδομένα σε μια μορφή που θέλουμε. Αυτή είναι μια περίληψη των ενσωματωμένων φίλτρων που είναι διαθέσιμα. +Στα πρότυπα, μπορούμε να χρησιμοποιήσουμε συναρτήσεις που βοηθούν στην τροποποίηση ή την επαναμορφοποίηση των δεδομένων στην τελική τους μορφή. Τις ονομάζουμε *φίλτρα*. .[table-latte-filters] -|## Μετασχηματισμός αλφαριθμητικών/συστοιχιών -| `batch` | [καταχώριση γραμμικών δεδομένων σε πίνακα |#batch] -| `breakLines` | [Εισαγωγή διαχωρισμού γραμμών HTML πριν από όλες τις νέες γραμμές |#breakLines] -| `bytes` | [μορφοποιεί το μέγεθος σε bytes |#bytes] -| `clamp` | [συγκρατεί την τιμή στο εύρος |#clamp] -| `dataStream` | [Μετατροπή πρωτοκόλλου URI δεδομένων |#datastream] -| `date` | [μορφοποίηση ημερομηνίας |#date] -| `explode` | [Διαχωρίζει μια συμβολοσειρά με το δεδομένο διαχωριστικό |#explode] -| `first` | [επιστρέφει το πρώτο στοιχείο του πίνακα ή τον χαρακτήρα της συμβολοσειράς |#first] -| `implode` | [συνδέει έναν πίνακα με μια συμβολοσειρά |#implode] -| `indent` | [εσοχή του κειμένου από αριστερά με αριθμό tabs |#indent] -| `join` | [ενώνει έναν πίνακα σε μια συμβολοσειρά |#implode] -| `last` | [επιστρέφει το τελευταίο στοιχείο του πίνακα ή τον χαρακτήρα της συμβολοσειράς |#last] -| `length` | [επιστρέφει το μήκος μιας συμβολοσειράς ή ενός πίνακα |#length] -| `number` | [μορφοποίηση αριθμού |#number] -| `padLeft` | [συμπληρώνει τη συμβολοσειρά στο συγκεκριμένο μήκος από αριστερά |#padLeft] -| `padRight` | [συμπληρώνει τη συμβολοσειρά στο δεδομένο μήκος από τα δεξιά |#padRight] -| `random` | [επιστρέφει τυχαίο στοιχείο πίνακα ή χαρακτήρα συμβολοσειράς |#random] -| `repeat` | [επαναλαμβάνει τη συμβολοσειρά |#repeat] -| `replace` | [αντικαθιστά όλες τις εμφανίσεις της συμβολοσειράς αναζήτησης με την αντικατάσταση |#replace] -| `replaceRE` | [αντικαθιστά όλες τις εμφανίσεις σύμφωνα με την κανονική έκφραση |#replaceRE] -| `reverse` | [αντιστρέφει μια συμβολοσειρά ή έναν πίνακα UTF-8 |#reverse] -| `slice` | [εξάγει ένα τμήμα ενός πίνακα ή μιας συμβολοσειράς |#slice] -| `sort` | [ταξινομεί έναν πίνακα |#sort] -| `spaceless` | [αφαιρεί κενά |#spaceless], παρόμοια με την ετικέτα [χωρίς κενό |tags] -| `split` | [διαχωρίζει μια συμβολοσειρά με βάση το δεδομένο διαχωριστικό |#explode] -| `strip` | [αφαιρεί τα κενά |#spaceless] -| `stripHtml` | [αφαιρεί ετικέτες HTML και μετατρέπει οντότητες HTML σε κείμενο |#stripHtml] -| `substr` | [επιστρέφει μέρος της συμβολοσειράς |#substr] -| `trim` | [αφαιρεί το κενό διάστημα από τη συμβολοσειρά |#trim] -| `translate` | [μετάφραση σε άλλες γλώσσες|#translate] -| `truncate` | [συντομεύει το μήκος διατηρώντας ολόκληρες λέξεις |#truncate] -| `webalize` | [προσαρμόζει τη συμβολοσειρά UTF-8 στο σχήμα που χρησιμοποιείται στη διεύθυνση URL |#webalize] +|## Μετασχηματισμός +| `batch` | [εμφάνιση γραμμικών δεδομένων σε πίνακα |#batch] +| `breakLines` | [Προσθέτει αλλαγή γραμμής HTML πριν από το τέλος της γραμμής |#breakLines] +| `bytes` | [μορφοποιεί το μέγεθος σε bytes |#bytes] +| `clamp` | [περιορίζει την τιμή σε ένα δεδομένο εύρος |#clamp] +| `dataStream` | [μετατροπή για το πρωτόκολλο Data URI |#dataStream] +| `date` | [μορφοποιεί την ημερομηνία και την ώρα |#date] +| `explode` | [χωρίζει ένα string σε έναν πίνακα με βάση έναν διαχωριστή |#explode] +| `first` | [επιστρέφει το πρώτο στοιχείο ενός πίνακα ή τον πρώτο χαρακτήρα ενός string |#first] +| `group` | [ομαδοποιεί δεδομένα με βάση διάφορα κριτήρια |#group] +| `implode` | [ενώνει έναν πίνακα σε ένα string |#implode] +| `indent` | [κάνει εσοχή στο κείμενο από τα αριστερά κατά έναν δεδομένο αριθμό tabs |#indent] +| `join` | [ενώνει έναν πίνακα σε ένα string |#implode] +| `last` | [επιστρέφει το τελευταίο στοιχείο ενός πίνακα ή τον τελευταίο χαρακτήρα ενός string |#last] +| `length` | [επιστρέφει το μήκος ενός string σε χαρακτήρες ή ενός πίνακα |#length] +| `localDate` | [μορφοποιεί την ημερομηνία και την ώρα σύμφωνα με τις τοπικές ρυθμίσεις |#localDate] +| `number` | [μορφοποιεί έναν αριθμό |#number] +| `padLeft` | [συμπληρώνει ένα string από τα αριστερά στο επιθυμητό μήκος |#padLeft] +| `padRight` | [συμπληρώνει ένα string από τα δεξιά στο επιθυμητό μήκος |#padRight] +| `random` | [επιστρέφει ένα τυχαίο στοιχείο ενός πίνακα ή έναν τυχαίο χαρακτήρα ενός string |#random] +| `repeat` | [επανάληψη ενός string |#repeat] +| `replace` | [αντικαθιστά τις εμφανίσεις ενός string αναζήτησης |#replace] +| `replaceRE` | [αντικαθιστά τις εμφανίσεις σύμφωνα με μια regular expression |#replaceRE] +| `reverse` | [αντιστρέφει ένα UTF-8 string ή έναν πίνακα |#reverse] +| `slice` | [εξάγει ένα τμήμα ενός πίνακα ή ενός string |#slice] +| `sort` | [ταξινομεί έναν πίνακα |#sort] +| `spaceless` | [αφαιρεί το λευκό διάστημα |#spaceless], παρόμοια με το tag [spaceless |tags] +| `split` | [χωρίζει ένα string σε έναν πίνακα με βάση έναν διαχωριστή |#explode] +| `strip` | [αφαιρεί το λευκό διάστημα |#spaceless] +| `stripHtml` | [αφαιρεί τα HTML tags και μετατρέπει τις οντότητες HTML σε χαρακτήρες |#stripHtml] +| `substr` | [επιστρέφει ένα τμήμα ενός string |#substr] +| `trim` | [αφαιρεί τα αρχικά και τελικά κενά ή άλλους χαρακτήρες |#trim] +| `translate` | [μετάφραση σε άλλες γλώσσες |#translate] +| `truncate` | [περικόπτει το μήκος διατηρώντας τις λέξεις |#truncate] +| `webalize` | [τροποποιεί ένα UTF-8 string στη μορφή που χρησιμοποιείται στα URL |#webalize] .[table-latte-filters] -|## Περιτύπωμα γραμμάτων -| `capitalize` | [πεζά γράμματα, το πρώτο γράμμα κάθε λέξης κεφαλαίο |#capitalize] -| `firstUpper` | [κάνει το πρώτο γράμμα κεφαλαίο |#firstUpper] -| `lower` | [κάνει μια συμβολοσειρά πεζά |#lower] -| `upper` | [κάνει μια συμβολοσειρά κεφαλαίο |#upper] +|## Κεφαλαία/Μικρά +| `capitalize` | [μικρά γράμματα, το πρώτο γράμμα σε κάθε λέξη κεφαλαίο |#capitalize] +| `firstUpper` | [μετατρέπει το πρώτο γράμμα σε κεφαλαίο |#firstUpper] +| `lower` | [μετατρέπει σε μικρά γράμματα |#lower] +| `upper` | [μετατρέπει σε κεφαλαία γράμματα |#upper] .[table-latte-filters] -|## Στρογγυλοποίηση αριθμών -| `ceil` | [στρογγυλοποιεί έναν αριθμό με δεδομένη ακρίβεια |#ceil] -| `floor` | [στρογγυλοποιεί έναν αριθμό προς τα κάτω με δεδομένη ακρίβεια |#floor] -| `round` | [στρογγυλοποιεί έναν αριθμό σε δεδομένη ακρίβεια |#round] +|## Στρογγυλοποίηση +| `ceil` | [στρογγυλοποιεί έναν αριθμό προς τα πάνω στην καθορισμένη ακρίβεια |#ceil] +| `floor` | [στρογγυλοποιεί έναν αριθμό προς τα κάτω στην καθορισμένη ακρίβεια |#floor] +| `round` | [στρογγυλοποιεί έναν αριθμό στην καθορισμένη ακρίβεια |#round] .[table-latte-filters] |## Escaping -| `escapeUrl` | [αποφεύγει την παράμετρο στη διεύθυνση URL |#escapeUrl] -| `noescape` | [εκτυπώνει μια μεταβλητή χωρίς διαφυγή |#noescape] -| `query` | [δημιουργεί μια συμβολοσειρά ερωτήματος στη διεύθυνση URL |#query] +| `escapeUrl` | [κάνει escape μια παράμετρο σε ένα URL |#escapeUrl] +| `noescape` | [εκτυπώνει μια μεταβλητή χωρίς escaping |#noescape] +| `query` | [δημιουργεί ένα query string σε ένα URL |#query] -Υπάρχουν επίσης φίλτρα διαφυγής για HTML (`escapeHtml` και `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) και iCalendar (`escapeICal`), τα οποία το Latte χρησιμοποιεί το ίδιο χάρη στην [διαφυγή με επίγνωση του περιβάλλοντος |safety-first#Context-aware escaping] και δεν χρειάζεται να τα γράψετε. +Επιπλέον, υπάρχουν φίλτρα escaping για HTML (`escapeHtml` και `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) και iCalendar (`escapeICal`), τα οποία το Latte χρησιμοποιεί από μόνο του χάρη στο [context-aware escaping |safety-first#Context-Aware Escaping] και δεν χρειάζεται να τα γράψετε. .[table-latte-filters] |## Ασφάλεια -| `checkUrl` | [Αποκαθαρίζει τη συμβολοσειρά για χρήση μέσα στο χαρακτηριστικό href |#checkUrl]. -| `nocheck` | [αποτρέπει την αυτόματη εξυγίανση URL |#nocheck] +| `checkUrl` | [επεξεργάζεται μια διεύθυνση URL για επικίνδυνες εισόδους |#checkUrl] +| `nocheck` | [αποτρέπει την αυτόματη επεξεργασία μιας διεύθυνσης URL |#nocheck] -Latte οι [έλεγχοι |safety-first#link checking] των χαρακτηριστικών `src` και `href` γίνονται [αυτόματα |safety-first#link checking], οπότε σχεδόν δεν χρειάζεται να χρησιμοποιήσετε το φίλτρο `checkUrl`. +Τα attributes `src` και `href` του Latte [ελέγχονται αυτόματα |safety-first#Έλεγχος συνδέσμων], οπότε σχεδόν ποτέ δεν χρειάζεται να χρησιμοποιήσετε το φίλτρο `checkUrl`. .[note] -Όλα τα ενσωματωμένα φίλτρα λειτουργούν με κωδικοποιημένες συμβολοσειρές UTF-8. +Όλα τα προεπιλεγμένα φίλτρα προορίζονται για strings στην κωδικοποίηση UTF-8. -Χρήση .[#toc-usage] -=================== +Χρήση +===== -Το Latte επιτρέπει την κλήση φίλτρων με τη χρήση του συμβολισμού με το σύμβολο της πίπας (επιτρέπεται το κενό που προηγείται): +Τα φίλτρα γράφονται μετά από μια κάθετη γραμμή (μπορεί να υπάρχει κενό πριν από αυτήν): ```latte

                                          {$heading|upper}

                                          ``` -Τα φίλτρα μπορούν να είναι αλυσιδωτά, οπότε εφαρμόζονται με τη σειρά από αριστερά προς τα δεξιά: +Τα φίλτρα (σε παλαιότερες εκδόσεις helpers) μπορούν να συνδεθούν αλυσιδωτά και στη συνέχεια εφαρμόζονται με σειρά από αριστερά προς τα δεξιά: ```latte

                                          {$heading|lower|capitalize}

                                          ``` -Οι παράμετροι τοποθετούνται μετά το όνομα του φίλτρου χωρισμένες με άνω και κάτω τελεία ή κόμμα: +Οι παράμετροι εισάγονται μετά το όνομα του φίλτρου, χωρισμένες με άνω και κάτω τελείες ή κόμματα: ```latte

                                          {$heading|truncate:20,''}

                                          ``` -Τα φίλτρα μπορούν να εφαρμοστούν στην έκφραση: +Τα φίλτρα μπορούν επίσης να εφαρμοστούν σε μια έκφραση: ```latte {var $name = ($title|upper) . ($subtitle|lower)} ``` -[Προσαρμοσμένα φίλτρα |extending-latte#filters] μπορούν να καταχωρηθούν με αυτόν τον τρόπο: +[Προσαρμοσμένα φίλτρα|custom-filters] μπορούν να εγγραφούν με αυτόν τον τρόπο: ```php $latte = new Latte\Engine; $latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -Τα χρησιμοποιούμε σε ένα πρότυπο όπως αυτό: +Στο πρότυπο, καλείται στη συνέχεια ως εξής: ```latte

                                          {$text|shortify}

                                          @@ -114,13 +116,13 @@ $latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $ ``` -Φίλτρα .[#toc-filters] -====================== +Φίλτρα +====== -batch(int length, mixed item): array .[filter]{data-version:2.7} ----------------------------------------------------------------- -Φίλτρο που απλοποιεί την καταχώριση γραμμικών δεδομένων με τη μορφή πίνακα. Επιστρέφει έναν πίνακα με τον δεδομένο αριθμό στοιχείων. Εάν δώσετε μια δεύτερη παράμετρο, αυτή χρησιμοποιείται για να συμπληρώσει τα στοιχεία που λείπουν στην τελευταία γραμμή. +batch(int $length, mixed $item): array .[filter] +------------------------------------------------ +Ένα φίλτρο που απλοποιεί την εμφάνιση γραμμικών δεδομένων σε μορφή πίνακα. Επιστρέφει έναν πίνακα πινάκων με τον καθορισμένο αριθμό στοιχείων. Εάν καθορίσετε μια δεύτερη παράμετρο, θα χρησιμοποιηθεί για τη συμπλήρωση των στοιχείων που λείπουν στην τελευταία γραμμή. ```latte {var $items = ['a', 'b', 'c', 'd', 'e']} @@ -135,7 +137,7 @@ batch(int length, mixed item): array .[filter]{data-version:2.7}
                                          Εσωτερικός βρόχος
                                          ``` -Εκτυπώσεις: +Εκτυπώνει: ```latte @@ -152,20 +154,22 @@ batch(int length, mixed item): array .[filter]{data-version:2.7}
                                          ``` +Δείτε επίσης [#group] και το tag [iterateWhile |tags#iterateWhile]. + breakLines .[filter] -------------------- -Εισάγει διαλείμματα γραμμής HTML πριν από όλες τις νέες γραμμές. +Προσθέτει το HTML tag `
                                          ` πριν από κάθε χαρακτήρα νέας γραμμής. ```latte {var $s = "Text & with \n newline"} -{$s|breakLines} {* outputs "Text & with
                                          \n newline" *} +{$s|breakLines} {* εκτυπώνει "Text & with
                                          \n newline" *} ``` -bytes(int precision = 2) .[filter] ----------------------------------- -Διαμορφώνει ένα μέγεθος σε bytes σε μορφή αναγνώσιμη από τον άνθρωπο. +bytes(int $precision=2) .[filter] +--------------------------------- +Μορφοποιεί το μέγεθος σε bytes σε μια αναγνώσιμη από τον άνθρωπο μορφή. Εάν έχουν οριστεί [οι τοπικές ρυθμίσεις |develop#Locale], θα χρησιμοποιηθούν οι αντίστοιχοι διαχωριστές δεκαδικών και χιλιάδων. ```latte {$size|bytes} 0 B, 1.25 GB, … @@ -173,53 +177,53 @@ bytes(int precision = 2) .[filter] ``` -ceil(int precision = 0) .[filter] ---------------------------------- -Στρογγυλοποιεί έναν αριθμό με δεδομένη ακρίβεια. +ceil(int $precision=0) .[filter] +-------------------------------- +Στρογγυλοποιεί έναν αριθμό προς τα πάνω στην καθορισμένη ακρίβεια. ```latte -{=3.4|ceil} {* έξοδοι 4 *} -{=135.22|ceil:1} {* έξοδοι 135.3 *} -{=135.22|ceil:3} {* έξοδοι 135.22 *} +{=3.4|ceil} {* εκτυπώνει 4 *} +{=135.22|ceil:1} {* εκτυπώνει 135.3 *} +{=135.22|ceil:3} {* εκτυπώνει 135.22 *} ``` -Βλέπε επίσης [floor |#floor], [round |#round]. +Δείτε επίσης [#floor], [#round]. capitalize .[filter] -------------------- -Επιστρέφει μια έκδοση της τιμής με τίτλο. Οι λέξεις θα ξεκινούν με κεφαλαία γράμματα, όλοι οι υπόλοιποι χαρακτήρες είναι πεζά. Απαιτεί την επέκταση PHP `mbstring`. +Οι λέξεις θα ξεκινούν με κεφαλαία γράμματα, όλοι οι υπόλοιποι χαρακτήρες θα είναι μικρά. Απαιτεί την επέκταση PHP `mbstring`. ```latte -{='i like LATTE'|capitalize} {* outputs 'I Like Latte' *} +{='i like LATTE'|capitalize} {* εκτυπώνει 'I Like Latte' *} ``` -Βλέπε επίσης [firstUpper |#firstUpper], [lower |#lower], [upper |#upper]. +Δείτε επίσης [#firstUpper], [#lower], [#upper]. checkUrl .[filter] ------------------ -Επιβάλλει την εξυγίανση URL. Ελέγχει αν η μεταβλητή περιέχει μια διεύθυνση URL στο διαδίκτυο (δηλαδή πρωτόκολλο HTTP/HTTPS) και αποτρέπει τη συγγραφή συνδέσμων που μπορεί να αποτελούν κίνδυνο για την ασφάλεια. +Επιβάλλει την επεξεργασία μιας διεύθυνσης URL. Ελέγχει εάν η μεταβλητή περιέχει μια διεύθυνση URL ιστού (δηλαδή πρωτόκολλο HTTP/HTTPS) και αποτρέπει την εμφάνιση συνδέσμων που ενδέχεται να αποτελούν κίνδυνο για την ασφάλεια. ```latte {var $link = 'javascript:window.close()'} -checked -unchecked +ελεγχόμενο +μη ελεγχόμενο ``` Εκτυπώνει: ```latte -checked -unchecked +ελεγχόμενο +μη ελεγχόμενο ``` -Βλέπε επίσης [nocheck |#nocheck]. +Δείτε επίσης [#nocheck]. -clamp(int|float min, int|float max) .[filter]{data-version:2.9} ---------------------------------------------------------------- -Επιστρέφει την τιμή που έχει περιοριστεί στο συνολικό εύρος των min και max. +clamp(int|float $min, int|float $max) .[filter] +----------------------------------------------- +Περιορίζει την τιμή στο δεδομένο εύρος min και max (συμπεριλαμβανομένων). ```latte {$level|clamp: 0, 255} @@ -228,14 +232,14 @@ clamp(int|float min, int|float max) .[filter]{data-version:2.9} Υπάρχει επίσης ως [συνάρτηση |functions#clamp]. -dataStream(string mimetype = detect) .[filter] ----------------------------------------------- -Μετατρέπει το περιεχόμενο σε σχήμα URI δεδομένων. Μπορεί να χρησιμοποιηθεί για την εισαγωγή εικόνων σε HTML ή CSS χωρίς την ανάγκη σύνδεσης εξωτερικών αρχείων. +dataStream(string $mimetype=detect) .[filter] +--------------------------------------------- +Μετατρέπει το περιεχόμενο στο data URI scheme. Με αυτό, μπορείτε να ενσωματώσετε εικόνες σε HTML ή CSS χωρίς να χρειάζεται να συνδέσετε εξωτερικά αρχεία. -Ας έχουμε μια εικόνα σε μια μεταβλητή `$img = Image::fromFile('obrazek.gif')`, τότε +Ας έχουμε μια εικόνα στη μεταβλητή `$img = Image::fromFile('image.gif')`, τότε ```latte - + ``` Εκτυπώνει για παράδειγμα: @@ -247,39 +251,40 @@ AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO ``` .[caution] -Απαιτεί επέκταση PHP `fileinfo`. +Απαιτεί την επέκταση PHP `fileinfo`. -date(string format) .[filter] ------------------------------ -Επιστρέφει μια ημερομηνία στη δεδομένη μορφή χρησιμοποιώντας τις επιλογές των συναρτήσεων PHP [php:strftime] ή [php:date]. Το φίλτρο λαμβάνει μια ημερομηνία ως χρονοσφραγίδα UNIX, συμβολοσειρά ή αντικείμενο τύπου `DateTime`. +date(string $format) .[filter] +------------------------------ +Μορφοποιεί την ημερομηνία και την ώρα σύμφωνα με τη μάσκα που χρησιμοποιείται από τη συνάρτηση PHP [php:date]. Το φίλτρο δέχεται την ημερομηνία σε μορφή UNIX timestamp, ως string ή ως αντικείμενο τύπου `DateTimeInterface`. ```latte -{$today|date:'%d.%m.%Y'} {$today|date:'j. n. Y'} ``` +Δείτε επίσης [#localDate]. + escapeUrl .[filter] ------------------- -Διασώζει μια μεταβλητή που θα χρησιμοποιηθεί ως παράμετρος σε URL. +Κάνει escape μια μεταβλητή για χρήση ως παράμετρο σε ένα URL. ```latte {$name} ``` -Βλέπε επίσης [query |#query]. +Δείτε επίσης [#query]. -explode(string separator = '') .[filter]{data-version:2.10.2} -------------------------------------------------------------- -Διαχωρίζει μια συμβολοσειρά με βάση το δεδομένο διαχωριστικό και επιστρέφει έναν πίνακα συμβολοσειρών. Ψευδώνυμο για το `split`. +explode(string $separator='') .[filter] +--------------------------------------- +Χωρίζει ένα string σε έναν πίνακα με βάση έναν διαχωριστή. Ψευδώνυμο για το `split`. ```latte -{='one,two,three'|explode:','} {* returns ['one', 'two', 'three'] *} +{='one,two,three'|explode:','} {* επιστρέφει ['one', 'two', 'three'] *} ``` -Εάν ο διαχωριστής είναι κενή συμβολοσειρά (προεπιλεγμένη τιμή), η είσοδος θα διαιρεθεί σε μεμονωμένους χαρακτήρες: +Εάν ο διαχωριστής είναι ένα κενό string (η προεπιλεγμένη τιμή), η είσοδος θα χωριστεί σε μεμονωμένους χαρακτήρες: ```latte {='123'|explode} {* επιστρέφει ['1', '2', '3'] *} @@ -291,64 +296,83 @@ explode(string separator = '') .[filter]{data-version:2.10.2} {='1,2,3'|split:','} {* επιστρέφει ['1', '2', '3'] *} ``` -Δείτε επίσης [implode |#implode]. +Δείτε επίσης [#implode]. -first .[filter]{data-version:2.10.2} ------------------------------------- -Επιστρέφει το πρώτο στοιχείο του πίνακα ή τον χαρακτήρα της συμβολοσειράς: +first .[filter] +--------------- +Επιστρέφει το πρώτο στοιχείο ενός πίνακα ή τον πρώτο χαρακτήρα ενός string: ```latte -{=[1, 2, 3, 4]|first} {* έξοδοι 1 *} -{='abcd'|first} {* έξοδοι 'a' *} +{=[1, 2, 3, 4]|first} {* εκτυπώνει 1 *} +{='abcd'|first} {* εκτυπώνει 'a' *} ``` -Βλέπε επίσης [last |#last], [random |#random]. +Δείτε επίσης [#last], [#random]. -floor(int precision = 0) .[filter] ----------------------------------- -Στρογγυλοποιεί έναν αριθμό με δεδομένη ακρίβεια. +floor(int $precision=0) .[filter] +--------------------------------- +Στρογγυλοποιεί έναν αριθμό προς τα κάτω στην καθορισμένη ακρίβεια. ```latte -{=3.5|floor} {* έξοδοι 3 *} -{=135.79|floor:1} {* έξοδοι 135.7 *} -{=135.79|floor:3} {* έξοδοι 135.79 *} +{=3.5|floor} {* εκτυπώνει 3 *} +{=135.79|floor:1} {* εκτυπώνει 135.7 *} +{=135.79|floor:3} {* εκτυπώνει 135.79 *} ``` -Βλέπε επίσης [ceil |#ceil], [round |#round]. +Δείτε επίσης [#ceil], [#round]. firstUpper .[filter] -------------------- -Μετατρέπει το πρώτο γράμμα της τιμής σε κεφαλαίο. Απαιτεί την επέκταση PHP `mbstring`. +Μετατρέπει το πρώτο γράμμα σε κεφαλαίο. Απαιτεί την επέκταση PHP `mbstring`. ```latte -{='the latte'|firstUpper} {* outputs 'The latte' *} +{='the latte'|firstUpper} {* εκτυπώνει 'The latte' *} ``` -Βλέπε επίσης [capitalize |#capitalize], [lower |#lower], [upper |#upper]. +Δείτε επίσης [#capitalize], [#lower], [#upper]. -implode(string glue = '') .[filter] ------------------------------------ -Επιστρέφει μια συμβολοσειρά που είναι η συνένωση των συμβολοσειρών του πίνακα. Ψευδώνυμο για το `join`. +group(string|int|\Closure $by): array .[filter]{data-version:3.0.16} +-------------------------------------------------------------------- +Το φίλτρο ομαδοποιεί δεδομένα με βάση διάφορα κριτήρια. + +Σε αυτό το παράδειγμα, οι γραμμές στον πίνακα ομαδοποιούνται με βάση τη στήλη `categoryId`. Η έξοδος είναι ένας πίνακας πινάκων, όπου το κλειδί είναι η τιμή στη στήλη `categoryId`. [Διαβάστε τον αναλυτικό οδηγό|cookbook/grouping]. ```latte -{=[1, 2, 3]|implode} {* έξοδοι '123' *} -{=[1, 2, 3]|implode:'|'} {* έξοδοι '1|2|3' *} +{foreach ($items|group: categoryId) as $categoryId => $categoryItems} +
                                            + {foreach $categoryItems as $item} +
                                          • {$item->name}
                                          • + {/foreach} +
                                          +{/foreach} ``` -Μπορείτε επίσης να χρησιμοποιήσετε το ψευδώνυμο `join`: .{data-version:2.10.2} +Δείτε επίσης [#batch], τη συνάρτηση [group |functions#group] και το tag [iterateWhile |tags#iterateWhile]. + + +implode(string $glue='') .[filter] +---------------------------------- +Επιστρέφει ένα string που είναι η συνένωση των στοιχείων μιας ακολουθίας. Ψευδώνυμο για το `join`. ```latte -{=[1, 2, 3]|join} {* έξοδοι '123' *} +{=[1, 2, 3]|implode} {* εκτυπώνει '123' *} +{=[1, 2, 3]|implode:'|'} {* εκτυπώνει '1|2|3' *} ``` +Μπορείτε επίσης να χρησιμοποιήσετε το ψευδώνυμο `join`: + +```latte +{=[1, 2, 3]|join} {* εκτυπώνει '123' *} +``` -indent(int level = 1, string char = "\t") .[filter] ---------------------------------------------------- -Παρεμβάλλει ένα κείμενο από τα αριστερά κατά ένα δεδομένο αριθμό tabs ή άλλων χαρακτήρων που καθορίζουμε στο δεύτερο προαιρετικό όρισμα. Οι κενές γραμμές δεν εσοδεύονται. + +indent(int $level=1, string $char="\t") .[filter] +------------------------------------------------- +Κάνει εσοχή στο κείμενο από τα αριστερά κατά τον καθορισμένο αριθμό tabs ή άλλων χαρακτήρων, τους οποίους μπορούμε να καθορίσουμε στο δεύτερο όρισμα. Οι κενές γραμμές δεν έχουν εσοχή. ```latte
                                          @@ -367,26 +391,26 @@ indent(int level = 1, string char = "\t") .[filter] ``` -last .[filter]{data-version:2.10.2} ------------------------------------ -Επιστρέφει το τελευταίο στοιχείο του πίνακα ή το χαρακτήρα της συμβολοσειράς: +last .[filter] +-------------- +Επιστρέφει το τελευταίο στοιχείο ενός πίνακα ή τον τελευταίο χαρακτήρα ενός string: ```latte -{=[1, 2, 3, 4]|last} {* έξοδοι 4 *} -{='abcd'|last} {* έξοδοι 'd' *} +{=[1, 2, 3, 4]|last} {* εκτυπώνει 4 *} +{='abcd'|last} {* εκτυπώνει 'd' *} ``` -Βλέπε επίσης [first |#first], [random |#random]. +Δείτε επίσης [#first], [#random]. length .[filter] ---------------- -Επιστρέφει το μήκος μιας συμβολοσειράς ή ενός πίνακα. +Επιστρέφει το μήκος ενός string ή ενός πίνακα. -- για συμβολοσειρές, θα επιστρέψει το μήκος σε χαρακτήρες UTF-8 -- για πίνακες, επιστρέφει τον αριθμό των στοιχείων. -- για αντικείμενα που υλοποιούν τη διεπαφή Countable, θα χρησιμοποιήσει την τιμή επιστροφής της count() -- για αντικείμενα που υλοποιούν τη διεπαφή IteratorAggregate, θα χρησιμοποιήσει την τιμή επιστροφής της iterator_count() +- για strings, επιστρέφει το μήκος σε χαρακτήρες UTF-8 +- για πίνακες, επιστρέφει τον αριθμό των στοιχείων +- για αντικείμενα που υλοποιούν τη διεπαφή Countable, χρησιμοποιεί την τιμή επιστροφής της μεθόδου count() +- για αντικείμενα που υλοποιούν τη διεπαφή IteratorAggregate, χρησιμοποιεί την τιμή επιστροφής της συνάρτησης iterator_count() ```latte @@ -396,204 +420,314 @@ length .[filter] ``` +localDate(?string $format=null, ?string $date=null, ?string $time=null) .[filter] +--------------------------------------------------------------------------------- +Μορφοποιεί την ημερομηνία και την ώρα σύμφωνα με τις [τοπικές ρυθμίσεις |develop#Locale], εξασφαλίζοντας συνεπή και τοπικοποιημένη εμφάνιση των χρονικών δεδομένων σε διάφορες γλώσσες και περιοχές. Το φίλτρο δέχεται την ημερομηνία ως UNIX timestamp, string ή αντικείμενο τύπου `DateTimeInterface`. + +```latte +{$date|localDate} {* 15 Απριλίου 2024 *} +{$date|localDate: format: yM} {* 4/2024 *} +{$date|localDate: date: medium} {* 15/4/2024 *} +``` + +Εάν χρησιμοποιήσετε το φίλτρο χωρίς παραμέτρους, η ημερομηνία θα εκτυπωθεί στο επίπεδο `long`, δείτε παρακάτω. + +**α) Χρήση μορφής** + +Η παράμετρος `format` περιγράφει ποια χρονικά στοιχεία πρέπει να εμφανιστούν. Χρησιμοποιεί γράμματα-κωδικούς για αυτά, ο αριθμός των επαναλήψεων των οποίων επηρεάζει το πλάτος της εξόδου: + +| Έτος | `y` / `yy` / `yyyy` | `2024` / `24` / `2024` +| Μήνας | `M` / `MM` / `MMM` / `MMMM` | `8` / `08` / `Αυγ` / `Αύγουστος` +| Ημέρα | `d` / `dd` / `E` / `EEEE` | `1` / `01` / `Κυ` / `Κυριακή` +| Ώρα | `j` / `H` / `h` | Προτιμώμενο / 24ωρο / 12ωρο +| Λεπτό | `m` / `mm` | `5` / `05` (2 ψηφία σε συνδυασμό με δευτερόλεπτα) +| Δευτερόλεπτο | `s` / `ss` | `8` / `08` (2 ψηφία σε συνδυασμό με λεπτά) + +Η σειρά των κωδικών στη μορφή δεν έχει σημασία, καθώς η σειρά των στοιχείων εκτυπώνεται σύμφωνα με τις συμβάσεις των τοπικών ρυθμίσεων. Επομένως, η μορφή είναι ανεξάρτητη από αυτές. Για παράδειγμα, η μορφή `yyyyMMMMd` στο περιβάλλον `en_US` εκτυπώνει `April 15, 2024`, ενώ στο περιβάλλον `el_GR` εκτυπώνει `15 Απριλίου 2024`: + +| locale: | el_GR | en_US +|--- +| `format: 'dMy'` | 10/8/2024 | 8/10/2024 +| `format: 'yM'` | 8/2024 | 8/2024 +| `format: 'yyyyMMMM'` | Αύγουστος 2024 | August 2024 +| `format: 'MMMM'` | Αύγουστος | August +| `format: 'jm'` | 5:22 μ.μ. | 5:22 PM +| `format: 'Hm'` | 17:22 | 17:22 +| `format: 'hm'` | 5:22 μ.μ. | 5:22 PM + + +**β) Χρήση προκαθορισμένων στυλ** + +Οι παράμετροι `date` και `time` καθορίζουν πόσο λεπτομερώς πρέπει να εκτυπωθούν η ημερομηνία και η ώρα. Μπορείτε να επιλέξετε από διάφορα επίπεδα: `full`, `long`, `medium`, `short`. Μπορείτε να εκτυπώσετε μόνο την ημερομηνία, μόνο την ώρα ή και τα δύο: + +| locale: | el_GR | en_US +|--- +| `date: short` | 23/1/78 | 1/23/78 +| `date: medium` | 23 Ιαν 1978 | Jan 23, 1978 +| `date: long` | 23 Ιανουαρίου 1978 | January 23, 1978 +| `date: full` | Δευτέρα, 23 Ιανουαρίου 1978 | Monday, January 23, 1978 +| `time: short` | 8:30 π.μ. | 8:30 AM +| `time: medium` | 8:30:59 π.μ. | 8:30:59 AM +| `time: long` | 8:30:59 π.μ. EET | 8:30:59 AM GMT+2 +| `date: short, time: short` | 23/1/78, 8:30 π.μ. | 1/23/78, 8:30 AM +| `date: medium, time: short` | 23 Ιαν 1978, 8:30 π.μ. | Jan 23, 1978, 8:30 AM +| `date: long, time: short` | 23 Ιανουαρίου 1978 στις 8:30 π.μ. | January 23, 1978 at 8:30 AM + +Για την ημερομηνία, μπορείτε επιπλέον να χρησιμοποιήσετε το πρόθεμα `relative-` (π.χ. `relative-short`), το οποίο για ημερομηνίες κοντά στην τρέχουσα εμφανίζει `χθες`, `σήμερα` ή `αύριο`, διαφορετικά εκτυπώνεται με τον τυπικό τρόπο. + +```latte +{$date|localDate: date: relative-short} {* χθες *} +``` + +Δείτε επίσης [#date]. + + lower .[filter] --------------- -Μετατρέπει μια τιμή σε πεζά γράμματα. Απαιτεί την επέκταση PHP `mbstring`. +Μετατρέπει ένα string σε μικρά γράμματα. Απαιτεί την επέκταση PHP `mbstring`. ```latte -{='LATTE'|lower} {* έξοδοι 'latte' *} +{='LATTE'|lower} {* εκτυπώνει 'latte' *} ``` -Βλέπε επίσης [capitalize |#capitalize], [firstUpper |#firstUpper], [upper |#upper]. +Δείτε επίσης [#capitalize], [#firstUpper], [#upper]. nocheck .[filter] ----------------- -Αποτρέπει την αυτόματη εξυγίανση URL. Το Latte [ελέγχει αυτόματα |safety-first#Link checking] αν η μεταβλητή περιέχει μια διεύθυνση URL στο διαδίκτυο (δηλαδή πρωτόκολλο HTTP/HTTPS) και αποτρέπει τη συγγραφή συνδέσμων που μπορεί να αποτελούν κίνδυνο για την ασφάλεια. +Αποτρέπει την αυτόματη επεξεργασία μιας διεύθυνσης URL. Το Latte [ελέγχει αυτόματα |safety-first#Έλεγχος συνδέσμων] εάν η μεταβλητή περιέχει μια διεύθυνση URL ιστού (δηλαδή πρωτόκολλο HTTP/HTTPS) και αποτρέπει την εμφάνιση συνδέσμων που ενδέχεται να αποτελούν κίνδυνο για την ασφάλεια. -Αν ο σύνδεσμος χρησιμοποιεί διαφορετικό σχήμα, όπως `javascript:` ή `data:`, και είστε σίγουροι για το περιεχόμενό του, μπορείτε να απενεργοποιήσετε τον έλεγχο μέσω του `|nocheck`. +Εάν ο σύνδεσμος χρησιμοποιεί άλλο σχήμα, π.χ. `javascript:` ή `data:`, και είστε σίγουροι για το περιεχόμενό του, μπορείτε να απενεργοποιήσετε τον έλεγχο χρησιμοποιώντας το `|nocheck`. ```latte {var $link = 'javascript:window.close()'} -checked -unchecked +ελεγχόμενο +μη ελεγχόμενο ``` -Εκτυπώσεις: +Εκτυπώνει: ```latte -checked -unchecked +ελεγχόμενο +μη ελεγχόμενο ``` -Βλέπε επίσης [checkUrl |#checkUrl]. +Δείτε επίσης [#checkUrl]. noescape .[filter] ------------------ -Απενεργοποιεί την αυτόματη διαφυγή. +Απενεργοποιεί το αυτόματο escaping. ```latte {var $trustedHtmlString = 'hello'} -Escaped: {$trustedHtmlString} -Unescaped: {$trustedHtmlString|noescape} +Με escape: {$trustedHtmlString} +Χωρίς escape: {$trustedHtmlString|noescape} ``` Εκτυπώνει: ```latte -Escaped: <b>hello</b> -Unescaped: hello +Με escape: <b>hello</b> +Χωρίς escape: hello ``` .[warning] -Η κατάχρηση του φίλτρου `noescape` μπορεί να οδηγήσει σε ευπάθεια XSS! Ποτέ μην το χρησιμοποιείτε εκτός αν είστε **απολύτως σίγουροι** για το τι κάνετε και ότι η συμβολοσειρά που εκτυπώνετε προέρχεται από αξιόπιστη πηγή. +Η λανθασμένη χρήση του φίλτρου `noescape` μπορεί να οδηγήσει σε ευπάθεια XSS! Ποτέ μην το χρησιμοποιείτε αν δεν είστε **απολύτως σίγουροι** για το τι κάνετε και ότι το string που εκτυπώνεται προέρχεται από αξιόπιστη πηγή. + + +number(int $decimals=0, string $decPoint='.', string $thousandsSep=',') .[filter] +--------------------------------------------------------------------------------- +Μορφοποιεί έναν αριθμό σε έναν συγκεκριμένο αριθμό δεκαδικών ψηφίων. Εάν έχουν οριστεί [οι τοπικές ρυθμίσεις |develop#Locale], θα χρησιμοποιηθούν οι αντίστοιχοι διαχωριστές δεκαδικών και χιλιάδων. +```latte +{1234.20|number} 1,234 +{1234.20|number:1} 1,234.2 +{1234.20|number:2} 1,234.20 +{1234.20|number:2, ',', ' '} 1 234,20 +``` + + +number(string $format) .[filter] +-------------------------------- +Η παράμετρος `format` σας επιτρέπει να ορίσετε την εμφάνιση των αριθμών ακριβώς σύμφωνα με τις ανάγκες σας. Για αυτό, πρέπει να έχετε ορίσει τις [τοπικές ρυθμίσεις |develop#Locale]. Η μορφή αποτελείται από διάφορους ειδικούς χαρακτήρες, η πλήρης περιγραφή των οποίων βρίσκεται στην τεκμηρίωση "DecimalFormat":https://unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns: + +- `0` υποχρεωτικό ψηφίο, εμφανίζεται πάντα, ακόμα κι αν είναι μηδέν +- `#` προαιρετικό ψηφίο, εμφανίζεται μόνο εάν υπάρχει πραγματικά αριθμός σε αυτή τη θέση +- `@` σημαντικό ψηφίο, βοηθά στην εμφάνιση του αριθμού με έναν συγκεκριμένο αριθμό σημαντικών ψηφίων +- `.` υποδεικνύει πού πρέπει να είναι η υποδιαστολή (ή τελεία, ανάλογα με τη χώρα) +- `,` χρησιμοποιείται για τον διαχωρισμό ομάδων ψηφίων, συνηθέστερα χιλιάδων +- `%` πολλαπλασιάζει τον αριθμό επί 100× και προσθέτει το σύμβολο του ποσοστού + +Ας δούμε μερικά παραδείγματα. Στο πρώτο παράδειγμα, δύο δεκαδικά ψηφία είναι υποχρεωτικά, στο δεύτερο προαιρετικά. Το τρίτο παράδειγμα δείχνει τη συμπλήρωση με μηδενικά από αριστερά και δεξιά, το τέταρτο εμφανίζει μόνο τα υπάρχοντα ψηφία: -number(int decimals = 0, string decPoint = '.', string thousandsSep = ',') .[filter] ------------------------------------------------------------------------------------- -Διαμορφώνει έναν αριθμό σε δεδομένο αριθμό δεκαδικών ψηφίων. Μπορείτε επίσης να καθορίσετε έναν χαρακτήρα του δεκαδικού σημείου και του διαχωριστικού χιλιάδων. +```latte +{1234.5|number: '#,##0.00'} {* 1,234.50 *} +{1234.5|number: '#,##0.##'} {* 1,234.5 *} +{1.23 |number: '000.000'} {* 001.230 *} +{1.2 |number: '##.##'} {* 1.2 *} +``` + +Τα σημαντικά ψηφία καθορίζουν πόσα ψηφία, ανεξάρτητα από την υποδιαστολή, πρέπει να εμφανιστούν, με στρογγυλοποίηση: ```latte -{1234.20 |number} 1,234 -{1234.20 |number:1} 1,234.2 -{1234.20 |number:2} 1,234.20 -{1234.20 |number:2, ',', ' '} 1 234,20 +{1234|number: '@@'} {* 1200 *} +{1234|number: '@@@'} {* 1230 *} +{1234|number: '@@@#'} {* 1234 *} +{1.2345|number: '@@@'} {* 1.23 *} +{0.00123|number: '@@'} {* 0.0012 *} ``` +Ένας εύκολος τρόπος για να εμφανίσετε έναν αριθμό ως ποσοστό. Ο αριθμός πολλαπλασιάζεται επί 100× και προστίθεται το σύμβολο `%`: + +```latte +{0.1234|number: '#.##%'} {* 12.34% *} +``` -padLeft(int length, string pad = ' ') .[filter] +Μπορούμε να ορίσουμε διαφορετική μορφή για θετικούς και αρνητικούς αριθμούς, χωρίζονται με το σύμβολο `;`. Με αυτόν τον τρόπο, για παράδειγμα, μπορούμε να ορίσουμε ότι οι θετικοί αριθμοί πρέπει να εμφανίζονται με το σύμβολο `+`: + +```latte +{42|number: '#.##;(#.##)'} {* 42 *} +{-42|number: '#.##;(#.##)'} {* (42) *} +{42|number: '+#.##;-#.##'} {* +42 *} +{-42|number: '+#.##;-#.##'} {* -42 *} +``` + +Να θυμάστε ότι η πραγματική εμφάνιση των αριθμών μπορεί να διαφέρει ανάλογα με τις ρυθμίσεις της χώρας. Για παράδειγμα, σε ορισμένες χώρες χρησιμοποιείται κόμμα αντί για τελεία ως διαχωριστής δεκαδικών. Αυτό το φίλτρο το λαμβάνει αυτόματα υπόψη και δεν χρειάζεται να ανησυχείτε για τίποτα. + + +padLeft(int $length, string $pad=' ') .[filter] ----------------------------------------------- -Συμπληρώνει μια συμβολοσειρά σε συγκεκριμένο μήκος με μια άλλη συμβολοσειρά από αριστερά. +Συμπληρώνει ένα string σε ένα συγκεκριμένο μήκος με ένα άλλο string από τα αριστερά. ```latte -{='hello'|padLeft: 10, '123'} {* outputs '12312hello' *} +{='hello'|padLeft: 10, '123'} {* εκτυπώνει '12312hello' *} ``` -padRight(int length, string pad = ' ') .[filter] +padRight(int $length, string $pad=' ') .[filter] ------------------------------------------------ -Γεμίζει μια συμβολοσειρά σε ένα συγκεκριμένο μήκος με μια άλλη συμβολοσειρά από δεξιά. +Συμπληρώνει ένα string σε ένα συγκεκριμένο μήκος με ένα άλλο string από τα δεξιά. ```latte -{='hello'|padRight: 10, '123'} {* outputs 'hello12312' *} +{='hello'|padRight: 10, '123'} {* εκτυπώνει 'hello12312' *} ``` -query .[filter]{data-version:2.10} ------------------------------------ -Δημιουργεί δυναμικά μια συμβολοσειρά ερωτήματος στη διεύθυνση URL: +query .[filter] +--------------- +Δημιουργεί δυναμικά ένα query string σε ένα URL: ```latte -click -search +κάντε κλικ +αναζήτηση ``` -Εκτυπώσεις: +Εκτυπώνει: ```latte -click -search +κάντε κλικ +αναζήτηση ``` - `null` παραλείπονται. +Τα κλειδιά με τιμή `null` παραλείπονται. -Βλέπε επίσης [escapeUrl |#escapeUrl]. +Δείτε επίσης [#escapeUrl]. -random .[filter]{data-version:2.10.2} -------------------------------------- -Επιστρέφει τυχαίο στοιχείο πίνακα ή χαρακτήρα συμβολοσειράς: +random .[filter] +---------------- +Επιστρέφει ένα τυχαίο στοιχείο ενός πίνακα ή έναν τυχαίο χαρακτήρα ενός string: ```latte -{=[1, 2, 3, 4]|random} {* παράδειγμα εξόδου: 3 *} -{='abcd'|random} {* παράδειγμα εξόδου: 'b' *} +{=[1, 2, 3, 4]|random} {* εκτυπώνει π.χ.: 3 *} +{='abcd'|random} {* εκτυπώνει π.χ.: 'b' *} ``` -Βλέπε επίσης [first |#first], [last |#last]. +Δείτε επίσης [#first], [#last]. -repeat(int count) .[filter] ---------------------------- -Επαναλαμβάνει τη συμβολοσειρά x φορές. +repeat(int $count) .[filter] +---------------------------- +Επαναλαμβάνει ένα string x φορές. ```latte -{='hello'|repeat: 3} {* outputs 'hellohellohello' *} +{='hello'|repeat: 3} {* εκτυπώνει 'hellohellohello' *} ``` -replace(string|array search, string replace = '') .[filter] +replace(string|array $search, string $replace='') .[filter] ----------------------------------------------------------- -Αντικαθιστά όλες τις εμφανίσεις της συμβολοσειράς αναζήτησης με τη συμβολοσειρά αντικατάστασης. +Αντικαθιστά όλες τις εμφανίσεις του string αναζήτησης με το string αντικατάστασης. ```latte -{='hello world'|replace: 'world', 'friend'} {* outputs 'hello friend' *} +{='hello world'|replace: 'world', 'friend'} {* εκτυπώνει 'hello friend' *} ``` -Μπορούν να γίνουν πολλαπλές αντικαταστάσεις ταυτόχρονα: .{data-version:2.10.2} +Μπορούν να γίνουν και πολλαπλές αντικαταστάσεις ταυτόχρονα: ```latte -{='hello world'|replace: [h => l, l => h]} {* έξοδοι 'lehho worhd' *} +{='hello world'|replace: [h => l, l => h]} {* εκτυπώνει 'lehho worhd' *} ``` -replaceRE(string pattern, string replace = '') .[filter] +replaceRE(string $pattern, string $replace='') .[filter] -------------------------------------------------------- -Αντικαθιστά όλες τις εμφανίσεις σύμφωνα με την κανονική έκφραση. +Εκτελεί αναζήτηση με regular expressions με αντικατάσταση. ```latte -{='hello world'|replaceRE: '/l.*/', 'l'} {* έξοδοι 'hel' *} +{='hello world'|replaceRE: '/l.*/', 'l'} {* εκτυπώνει 'hel' *} ``` reverse .[filter] ----------------- -Αντιστρέφει δεδομένη συμβολοσειρά ή πίνακα. +Αντιστρέφει το δεδομένο string ή πίνακα. ```latte {var $s = 'Nette'} -{$s|reverse} {* έξοδοι 'etteN' *} +{$s|reverse} {* εκτυπώνει 'etteN' *} {var $a = ['N', 'e', 't', 't', 'e']} -{$a|reverse} {* returns ['e', 't', 't', 'e', 'N'] *} +{$a|reverse} {* επιστρέφει ['e', 't', 't', 'e', 'N'] *} ``` -round(int precision = 0) .[filter] ----------------------------------- -Στρογγυλοποιεί έναν αριθμό με δεδομένη ακρίβεια. +round(int $precision=0) .[filter] +--------------------------------- +Στρογγυλοποιεί έναν αριθμό στην καθορισμένη ακρίβεια. ```latte -{=3.4|round} {* έξοδοι 3 *} -{=3.5|round} {* έξοδοι 4 *} -{=135.79|round:1} {* έξοδοι 135.8 *} -{=135.79|round:3} {* έξοδοι 135.79 *} +{=3.4|round} {* εκτυπώνει 3 *} +{=3.5|round} {* εκτυπώνει 4 *} +{=135.79|round:1} {* εκτυπώνει 135.8 *} +{=135.79|round:3} {* εκτυπώνει 135.79 *} ``` -Βλέπε επίσης [ceil |#ceil], [floor |#floor]. +Δείτε επίσης [#ceil], [#floor]. -slice(int start, int length = null, bool preserveKeys = false) .[filter]{data-version:2.10.2} ---------------------------------------------------------------------------------------------- -Εξάγει μια φέτα ενός πίνακα ή μιας συμβολοσειράς. +slice(int $start, ?int $length=null, bool $preserveKeys=false) .[filter] +------------------------------------------------------------------------ +Εξάγει ένα τμήμα ενός πίνακα ή ενός string. ```latte -{='hello'|slice: 1, 2} {* έξοδοι 'el' *} -{=['a', 'b', 'c']|slice: 1, 2} {* outputs ['b', 'c'] *} +{='hello'|slice: 1, 2} {* εκτυπώνει 'el' *} +{=['a', 'b', 'c']|slice: 1, 2} {* εκτυπώνει ['b', 'c'] *} ``` -Το φίλτρο slice λειτουργεί όπως η συνάρτηση `array_slice` της PHP για πίνακες και `mb_substr` για συμβολοσειρές με μια υποχώρηση στο `iconv_substr` σε λειτουργία UTF-8. +Το φίλτρο λειτουργεί όπως η συνάρτηση PHP `array_slice` για πίνακες ή `mb_substr` για strings με εναλλακτική λύση τη συνάρτηση `iconv_substr` σε λειτουργία UTF-8. -Εάν η αρχή είναι μη αρνητική, η ακολουθία θα ξεκινήσει από αυτή την αρχή στη μεταβλητή. Εάν το start είναι αρνητικό, η ακολουθία θα ξεκινήσει τόσο μακριά από το τέλος της μεταβλητής. +Εάν η αρχή είναι θετική, η ακολουθία θα ξεκινήσει μετατοπισμένη κατά αυτόν τον αριθμό από την αρχή του πίνακα/string. Εάν είναι αρνητική, η ακολουθία θα ξεκινήσει μετατοπισμένη κατά τόσο από το τέλος. -Αν το length δίνεται και είναι θετικό, τότε η ακολουθία θα έχει μέχρι τόσα στοιχεία. Εάν η μεταβλητή είναι μικρότερη από το μήκος, τότε θα υπάρχουν μόνο τα διαθέσιμα στοιχεία της μεταβλητής. Αν το μήκος δίνεται και είναι αρνητικό, τότε η ακολουθία θα σταματήσει τόσα στοιχεία από το τέλος της μεταβλητής. Αν παραλείπεται, τότε η ακολουθία θα έχει τα πάντα από το offset μέχρι το τέλος της μεταβλητής. +Εάν η παράμετρος length καθοριστεί και είναι θετική, η ακολουθία θα περιέχει τόσα στοιχεία. Εάν σε αυτή τη συνάρτηση περάσει μια αρνητική παράμετρος length, η ακολουθία θα περιέχει όλα τα στοιχεία του αρχικού πίνακα, ξεκινώντας από τη θέση start και τελειώνοντας στη θέση μικρότερη κατά length στοιχεία από το τέλος του πίνακα. Εάν δεν καθορίσετε αυτήν την παράμετρο, η ακολουθία θα περιέχει όλα τα στοιχεία του αρχικού πίνακα, ξεκινώντας από τη θέση start. -Το φίλτρο θα αναδιατάξει και θα επαναφέρει τα κλειδιά του ακέραιου πίνακα από προεπιλογή. Αυτή η συμπεριφορά μπορεί να αλλάξει θέτοντας το preserveKeys σε true. Τα αλφαριθμητικά κλειδιά διατηρούνται πάντα, ανεξάρτητα από αυτήν την παράμετρο. +Από προεπιλογή, το φίλτρο αλλάζει τη σειρά και επαναφέρει τα ακέραια κλειδιά του πίνακα. Αυτή η συμπεριφορά μπορεί να αλλάξει ορίζοντας το preserveKeys σε true. Τα κλειδιά string διατηρούνται πάντα, ανεξάρτητα από αυτήν την παράμετρο. -sort .[filter]{data-version:2.9} ---------------------------------- -Φίλτρο που ταξινομεί έναν πίνακα και διατηρεί τη συσχέτιση δεικτών. +sort(?Closure $comparison, string|int|\Closure|null $by=null, string|int|\Closure|bool $byKey=false) .[filter] +-------------------------------------------------------------------------------------------------------------- +Το φίλτρο ταξινομεί τα στοιχεία ενός πίνακα ή ενός iterator και διατηρεί τα συσχετιστικά τους κλειδιά. Όταν ορίζονται [οι τοπικές ρυθμίσεις |develop#Locale], η ταξινόμηση διέπεται από τους κανόνες της, εκτός εάν καθοριστεί μια προσαρμοσμένη συνάρτηση σύγκρισης. ```latte {foreach ($names|sort) as $name} @@ -601,7 +735,7 @@ sort .[filter]{data-version:2.9} {/foreach} ``` -Πίνακας ταξινομημένος με αντίστροφη σειρά. +Ταξινομημένος πίνακας με αντίστροφη σειρά: ```latte {foreach ($names|sort|reverse) as $name} @@ -609,16 +743,42 @@ sort .[filter]{data-version:2.9} {/foreach} ``` -Μπορείτε να περάσετε τη δική σας συνάρτηση σύγκρισης ως παράμετρο: .{data-version:2.10.2} +Μπορείτε να καθορίσετε μια προσαρμοσμένη συνάρτηση σύγκρισης για ταξινόμηση (το παράδειγμα δείχνει πώς να αντιστρέψετε την ταξινόμηση από το μεγαλύτερο στο μικρότερο): + +```latte +{var $reverted = ($names|sort: fn($a, $b) => $b <=> $a)} +``` + +Το φίλτρο `|sort` επιτρέπει επίσης την ταξινόμηση στοιχείων με βάση τα κλειδιά: + +```latte +{foreach ($names|sort: byKey: true) as $name} + ... +{/foreach} +``` + +Εάν πρέπει να ταξινομήσετε έναν πίνακα με βάση μια συγκεκριμένη στήλη, μπορείτε να χρησιμοποιήσετε την παράμετρο `by`. Η τιμή `'name'` στο παράδειγμα καθορίζει ότι η ταξινόμηση θα γίνει με βάση το `$item->name` ή το `$item['name']`, ανάλογα με το αν το `$item` είναι πίνακας ή αντικείμενο: ```latte -{var $sorted = ($names|sort: fn($a, $b) => $b <=> $a)} +{foreach ($items|sort: by: 'name') as $item} + {$item->name} +{/foreach} +``` + +Μπορείτε επίσης να ορίσετε μια συνάρτηση callback που καθορίζει την τιμή με βάση την οποία θα γίνει η ταξινόμηση: + +```latte +{foreach ($items|sort: by: fn($item) => $item->category->name) as $item} + {$item->name} +{/foreach} ``` +Με τον ίδιο τρόπο μπορεί να χρησιμοποιηθεί και η παράμετρος `byKey`. -spaceless .[filter]{data-version:2.10.2} ------------------------------------------ -Αφαιρεί τα περιττά κενά από την έξοδο. Μπορείτε επίσης να χρησιμοποιήσετε το ψευδώνυμο `strip`. + +spaceless .[filter] +------------------- +Αφαιρεί το περιττό λευκό διάστημα (κενά) από την έξοδο. Μπορείτε επίσης να χρησιμοποιήσετε το ψευδώνυμο `strip`. ```latte {block |spaceless} @@ -637,47 +797,47 @@ spaceless .[filter]{data-version:2.10.2} stripHtml .[filter] ------------------- -Μετατρέπει την HTML σε απλό κείμενο. Δηλαδή, αφαιρεί τις ετικέτες HTML και μετατρέπει τις οντότητες HTML σε κείμενο. +Μετατρέπει το HTML σε απλό κείμενο. Δηλαδή, αφαιρεί τα HTML tags και μετατρέπει τις οντότητες HTML σε κείμενο. ```latte -{='

                                          one < two

                                          '|stripHtml} {* outputs 'one < two' *} +{='

                                          one < two

                                          '|stripHtml} {* εκτυπώνει 'one < two' *} ``` -Το προκύπτον απλό κείμενο μπορεί φυσικά να περιέχει χαρακτήρες που αντιπροσωπεύουν ετικέτες HTML, για παράδειγμα το `'<p>'|stripHtml` μετατρέπεται σε `

                                          `. Ποτέ μην εξάγετε το κείμενο που προκύπτει με `|noescape`, καθώς αυτό μπορεί να οδηγήσει σε ευπάθεια ασφαλείας. +Το προκύπτον απλό κείμενο μπορεί φυσικά να περιέχει χαρακτήρες που αντιπροσωπεύουν HTML tags, για παράδειγμα, το `'<p>'|stripHtml` μετατρέπεται σε `

                                          `. Σε καμία περίπτωση μην εκτυπώνετε το κείμενο που προκύπτει με αυτόν τον τρόπο με `|noescape`, καθώς αυτό μπορεί να οδηγήσει σε κενό ασφαλείας. -substr(int offset, int length = null) .[filter] ------------------------------------------------ -Εξάγει μια φέτα μιας συμβολοσειράς. Αυτό το φίλτρο έχει αντικατασταθεί από ένα φίλτρο [φέτας |#slice]. +substr(int $offset, ?int $length=null) .[filter] +------------------------------------------------ +Εξάγει ένα τμήμα ενός string. Αυτό το φίλτρο έχει αντικατασταθεί από το φίλτρο [#slice]. ```latte {$string|substr: 1, 2} ``` -translate(string message, ...args) .[filter]{data-version:3.0} --------------------------------------------------------------- -Μεταφράζει εκφράσεις σε άλλες γλώσσες. Για να καταστήσετε το φίλτρο διαθέσιμο, πρέπει να [ρυθμίσετε τον μεταφραστή |develop#TranslatorExtension]. Μπορείτε επίσης να χρησιμοποιήσετε τις [ετικέτες για τη μετάφραση |tags#Translation]. +translate(...$args) .[filter] +----------------------------- +Μεταφράζει εκφράσεις σε άλλες γλώσσες. Για να είναι διαθέσιμο το φίλτρο, πρέπει να [ρυθμίσετε τον μεταφραστή |develop#TranslatorExtension]. Μπορείτε επίσης να χρησιμοποιήσετε [tags για μετάφραση |tags#Μεταφράσεις]. ```latte -{='Baskter'|translate} +{='Καλάθι'|translate} {$item|translate} ``` -trim(string charlist = " \t\n\r\0\x0B\u{A0}") .[filter] -------------------------------------------------------- -Απογύμνωση αρχικών και τελικών χαρακτήρων, από προεπιλογή κενό διάστημα. +trim(string $charlist=" \t\n\r\0\x0B\u{A0}") .[filter] +------------------------------------------------------ +Αφαιρεί τους κενούς χαρακτήρες (ή άλλους χαρακτήρες) από την αρχή και το τέλος ενός string. ```latte -{=' I like Latte. '|trim} {* outputs 'I like Latte.' *} -{=' I like Latte.'|trim: '.'} {* outputs ' I like Latte' *} +{=' I like Latte. '|trim} {* εκτυπώνει 'I like Latte.' *} +{=' I like Latte.'|trim: '.'} {* εκτυπώνει ' I like Latte' *} ``` -truncate(int length, string append = '…') .[filter] +truncate(int $length, string $append='…') .[filter] --------------------------------------------------- -Συντομεύει μια συμβολοσειρά στο μέγιστο δοσμένο μήκος, αλλά προσπαθεί να διατηρήσει ολόκληρες λέξεις. Αν η συμβολοσειρά είναι κομμένη, προσθέτει ελλειψογράμματα στο τέλος (αυτό μπορεί να αλλάξει με τη δεύτερη παράμετρο). +Περικόπτει ένα string στο καθορισμένο μέγιστο μήκος, προσπαθώντας να διατηρήσει ολόκληρες λέξεις. Εάν το string περικοπεί, προσθέτει στο τέλος τρεις τελείες (μπορεί να αλλάξει με τη δεύτερη παράμετρο). ```latte {var $title = 'Hello, how are you?'} @@ -689,25 +849,25 @@ truncate(int length, string append = '…') .[filter] upper .[filter] --------------- -Μετατρέπει μια τιμή σε κεφαλαία γράμματα. Απαιτεί την επέκταση PHP `mbstring`. +Μετατρέπει ένα string σε κεφαλαία γράμματα. Απαιτεί την επέκταση PHP `mbstring`. ```latte -{='latte'|upper} {* έξοδοι 'LATTE' *} +{='latte'|upper} {* εκτυπώνει 'LATTE' *} ``` -Βλέπε επίσης [capitalize |#capitalize], [firstUpper |#firstUpper], [lower |#lower]. +Δείτε επίσης [#capitalize], [#firstUpper], [#lower]. webalize .[filter] ------------------ -Μετατρέπει σε ASCII. +Τροποποιεί ένα UTF-8 string στη μορφή που χρησιμοποιείται στα URL. -Μετατρέπει τα κενά σε παύλες. Αφαιρεί χαρακτήρες που δεν είναι αλφαριθμητικοί, υπογράμμιση ή παύλα. Μετατρέπει σε πεζά γράμματα. Αφαιρεί επίσης τα λευκά κενά που προηγούνται και ακολουθούν. +Μετατρέπεται σε ASCII. Μετατρέπει τα κενά σε παύλες. Αφαιρεί χαρακτήρες που δεν είναι αλφαριθμητικοί, κάτω παύλες ή παύλες. Μετατρέπει σε μικρά γράμματα. Επίσης, αφαιρεί τα αρχικά και τελικά κενά. ```latte -{var $s = 'Our 10. product'} -{$s|webalize} {* outputs 'our-10-product' *} +{var $s = 'Το 10ο προϊόν μας'} +{$s|webalize} {* εκτυπώνει 'to-10o-proion-mas' *} ``` .[caution] -Απαιτεί το πακέτο [nette/utils |utils:]. +Απαιτεί τη βιβλιοθήκη [nette/utils|utils:]. diff --git a/latte/el/functions.texy b/latte/el/functions.texy index ea54e72d73..0de88550ba 100644 --- a/latte/el/functions.texy +++ b/latte/el/functions.texy @@ -1,23 +1,25 @@ -Λειτουργίες Latte +Συναρτήσεις Latte ***************** .[perex] -Εκτός από τις κοινές συναρτήσεις PHP, μπορείτε επίσης να τις χρησιμοποιήσετε σε πρότυπα. +Στα πρότυπα, εκτός από τις συνήθεις συναρτήσεις PHP, μπορούμε να χρησιμοποιήσουμε και αυτές τις πρόσθετες συναρτήσεις. .[table-latte-filters] -| `clamp` | [συγκρατεί την τιμή στην περιοχή |#clamp] -| `divisibleBy`| [ελέγχει αν μια μεταβλητή διαιρείται με έναν αριθμό |#divisibleBy] -| `even` | [ελέγχει αν ο δεδομένος αριθμός είναι ζυγός |#even] -| `first` | [επιστρέφει το πρώτο στοιχείο του πίνακα ή τον χαρακτήρα της συμβολοσειράς |#first] -| `last` | [επιστρέφει το τελευταίο στοιχείο του πίνακα ή τον χαρακτήρα της συμβολοσειράς |#last] -| `odd` | [ελέγχει αν ο δεδομένος αριθμός είναι περιττός |#odd] -| `slice` | [εξάγει ένα τμήμα ενός πίνακα ή μιας συμβολοσειράς |#slice] +| `clamp` | [περιορίζει την τιμή σε ένα δεδομένο εύρος |#clamp] +| `divisibleBy`| [ελέγχει αν μια μεταβλητή είναι διαιρετή με έναν αριθμό |#divisibleBy] +| `even` | [ελέγχει αν ένας δεδομένος αριθμός είναι ζυγός |#even] +| `first` | [επιστρέφει το πρώτο στοιχείο ενός πίνακα ή τον πρώτο χαρακτήρα ενός string |#first] +| `group` | [ομαδοποιεί δεδομένα με βάση διάφορα κριτήρια |#group] +| `hasBlock` | [ελέγχει την ύπαρξη ενός μπλοκ |#hasBlock] +| `last` | [επιστρέφει το τελευταίο στοιχείο ενός πίνακα ή τον τελευταίο χαρακτήρα ενός string |#last] +| `odd` | [ελέγχει αν ένας δεδομένος αριθμός είναι περιττός |#odd] +| `slice` | [εξάγει ένα τμήμα ενός πίνακα ή ενός string |#slice] -Χρήση .[#toc-usage] -=================== +Χρήση +===== -Οι συναρτήσεις χρησιμοποιούνται με τον ίδιο τρόπο όπως οι κοινές συναρτήσεις της PHP και μπορούν να χρησιμοποιηθούν σε όλες τις εκφράσεις: +Οι συναρτήσεις χρησιμοποιούνται ακριβώς όπως οι συνήθεις συναρτήσεις PHP και μπορούν να χρησιμοποιηθούν σε όλες τις εκφράσεις: ```latte

                                          {clamp($num, 1, 100)}

                                          @@ -25,14 +27,14 @@ {if odd($num)} ... {/if} ``` -[Οι προσαρμοσμένες συναρτήσεις |extending-latte#functions] μπορούν να καταχωρηθούν με αυτόν τον τρόπο: +[Προσαρμοσμένες συναρτήσεις|custom-functions] μπορούν να εγγραφούν με αυτόν τον τρόπο: ```php $latte = new Latte\Engine; $latte->addFunction('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -Το χρησιμοποιούμε σε ένα πρότυπο όπως αυτό: +Στο πρότυπο, καλείται στη συνέχεια ως εξής: ```latte

                                          {shortify($text)}

                                          @@ -40,23 +42,23 @@ $latte->addFunction('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, ``` -Λειτουργίες .[#toc-functions] -============================= +Συναρτήσεις +=========== -clamp(int|float $value, int|float $min, int|float $max): int|float .[method]{data-version:2.9} ----------------------------------------------------------------------------------------------- -Επιστρέφει την τιμή που είναι δεσμευμένη στο εύρος των min και max. +clamp(int|float $value, int|float $min, int|float $max): int|float .[method] +---------------------------------------------------------------------------- +Περιορίζει την τιμή στο δεδομένο εύρος min και max (συμπεριλαμβανομένων). ```latte {=clamp($level, 0, 255)} ``` -Βλέπε επίσης [filter clamp |filters#clamp]: +Δείτε επίσης το [φίλτρο clamp |filters#clamp]. -divisibleBy(int $value, int $by): bool .[method]{data-version:2.10.2} ---------------------------------------------------------------------- +divisibleBy(int $value, int $by): bool .[method] +------------------------------------------------ Ελέγχει αν μια μεταβλητή είναι διαιρετή με έναν αριθμό. ```latte @@ -64,61 +66,91 @@ divisibleBy(int $value, int $by): bool .[method]{data-version:2.10.2} ``` -even(int $value): bool .[method]{data-version:2.10.2} ------------------------------------------------------ -Ελέγχει αν ο δεδομένος αριθμός είναι ζυγός. +even(int $value): bool .[method] +-------------------------------- +Ελέγχει αν ένας δεδομένος αριθμός είναι ζυγός. ```latte {if even($num)} ... {/if} ``` -first(string|array $value): mixed .[method]{data-version:2.10.2} ----------------------------------------------------------------- -Επιστρέφει το πρώτο στοιχείο του πίνακα ή τον χαρακτήρα της συμβολοσειράς: +first(string|iterable $value): mixed .[method] +---------------------------------------------- +Επιστρέφει το πρώτο στοιχείο ενός πίνακα ή τον πρώτο χαρακτήρα ενός string: ```latte -{=first([1, 2, 3, 4])} {* έξοδοι 1 *} -{=first('abcd')} {* έξοδοι 'a' *} +{=first([1, 2, 3, 4])} {* εκτυπώνει 1 *} +{=first('abcd')} {* εκτυπώνει 'a' *} ``` -Βλέπε επίσης [last |#last], [filter first |filters#first]. +Δείτε επίσης [#last], [φίλτρο first |filters#first]. -last(string|array $value): mixed .[method]{data-version:2.10.2} ---------------------------------------------------------------- -Επιστρέφει το τελευταίο στοιχείο του πίνακα ή τον τελευταίο χαρακτήρα της συμβολοσειράς: +group(iterable $data, string|int|\Closure $by): array .[method]{data-version:3.0.16} +------------------------------------------------------------------------------------ +Η συνάρτηση ομαδοποιεί δεδομένα με βάση διάφορα κριτήρια. + +Σε αυτό το παράδειγμα, οι γραμμές στον πίνακα ομαδοποιούνται με βάση τη στήλη `categoryId`. Η έξοδος είναι ένας πίνακας πινάκων, όπου το κλειδί είναι η τιμή στη στήλη `categoryId`. [Διαβάστε τον αναλυτικό οδηγό|cookbook/grouping]. + +```latte +{foreach group($items, categoryId) as $categoryId => $categoryItems} +
                                            + {foreach $categoryItems as $item} +
                                          • {$item->name}
                                          • + {/foreach} +
                                          +{/foreach} +``` + +Δείτε επίσης το φίλτρο [group |filters#group]. + + +hasBlock(string $name): bool .[method]{data-version:3.0.10} +----------------------------------------------------------- +Ελέγχει αν υπάρχει το μπλοκ με το δεδομένο όνομα: + +```latte +{if hasBlock(header)} ... {/if} +``` + +Δείτε επίσης τον [έλεγχο ύπαρξης μπλοκ |template-inheritance#Έλεγχος ύπαρξης μπλοκ ifset]. + + +last(string|array $value): mixed .[method] +------------------------------------------ +Επιστρέφει το τελευταίο στοιχείο ενός πίνακα ή τον τελευταίο χαρακτήρα ενός string: ```latte -{=last([1, 2, 3, 4])} {* έξοδοι 4 *} -{=last('abcd')} {* έξοδοι 'd' *} +{=last([1, 2, 3, 4])} {* εκτυπώνει 4 *} +{=last('abcd')} {* εκτυπώνει 'd' *} ``` -Βλέπε επίσης [first |#first], [filter last |filters#last]. +Δείτε επίσης [#first], [φίλτρο last |filters#last]. -odd(int $value): bool .[method]{data-version:2.10.2} ----------------------------------------------------- -Ελέγχει αν ο δεδομένος αριθμός είναι περιττός. +odd(int $value): bool .[method] +------------------------------- +Ελέγχει αν ένας δεδομένος αριθμός είναι περιττός. ```latte {if odd($num)} ... {/if} ``` -slice(string|array $value, int $start, int $length=null, bool $preserveKeys=false): string|array .[method]{data-version:2.10.2} -------------------------------------------------------------------------------------------------------------------------------- -Εξαγάγει μια φέτα ενός πίνακα ή μιας συμβολοσειράς. +slice(string|array $value, int $start, ?int $length=null, bool $preserveKeys=false): string|array .[method] +----------------------------------------------------------------------------------------------------------- +Εξάγει ένα τμήμα ενός πίνακα ή ενός string. ```latte -{=slice('hello', 1, 2)} {* έξοδοι 'el' *} -{=slice(['a', 'b', 'c'], 1, 2)} {* outputs ['b', 'c'] *} +{=slice('hello', 1, 2)} {* εκτυπώνει 'el' *} +{=slice(['a', 'b', 'c'], 1, 2)} {* εκτυπώνει ['b', 'c'] *} ``` -Το φίλτρο slice λειτουργεί όπως η συνάρτηση `array_slice` της PHP για πίνακες και `mb_substr` για συμβολοσειρές με μια υποχώρηση στο `iconv_substr` σε λειτουργία UTF-8. +Η συνάρτηση λειτουργεί όπως η συνάρτηση PHP `array_slice` για πίνακες ή `mb_substr` για strings με εναλλακτική λύση τη συνάρτηση `iconv_substr` σε λειτουργία UTF-8. -Εάν η αρχή είναι μη αρνητική, η ακολουθία θα ξεκινήσει από αυτή την αρχή στη μεταβλητή. Εάν το start είναι αρνητικό, η ακολουθία θα ξεκινήσει τόσο μακριά από το τέλος της μεταβλητής. +Εάν η αρχή είναι θετική, η ακολουθία θα ξεκινήσει μετατοπισμένη κατά αυτόν τον αριθμό από την αρχή του πίνακα/string. Εάν είναι αρνητική, η ακολουθία θα ξεκινήσει μετατοπισμένη κατά τόσο από το τέλος. -Αν το length δίνεται και είναι θετικό, τότε η ακολουθία θα έχει μέχρι τόσα στοιχεία. Εάν η μεταβλητή είναι μικρότερη από το μήκος, τότε θα υπάρχουν μόνο τα διαθέσιμα στοιχεία της μεταβλητής. Αν το μήκος δίνεται και είναι αρνητικό, τότε η ακολουθία θα σταματήσει τόσα στοιχεία από το τέλος της μεταβλητής. Αν παραλείπεται, τότε η ακολουθία θα έχει τα πάντα από το offset μέχρι το τέλος της μεταβλητής. +Εάν η παράμετρος length καθοριστεί και είναι θετική, η ακολουθία θα περιέχει τόσα στοιχεία. Εάν σε αυτή τη συνάρτηση περάσει μια αρνητική παράμετρος length, η ακολουθία θα περιέχει όλα τα στοιχεία του αρχικού πίνακα, ξεκινώντας από τη θέση start και τελειώνοντας στη θέση μικρότερη κατά length στοιχεία από το τέλος του πίνακα. Εάν δεν καθορίσετε αυτήν την παράμετρο, η ακολουθία θα περιέχει όλα τα στοιχεία του αρχικού πίνακα, ξεκινώντας από τη θέση start. -Το φίλτρο θα αναδιατάξει και θα επαναφέρει τα κλειδιά του ακέραιου πίνακα από προεπιλογή. Αυτή η συμπεριφορά μπορεί να αλλάξει θέτοντας το preserveKeys σε true. Τα αλφαριθμητικά κλειδιά διατηρούνται πάντα, ανεξάρτητα από αυτήν την παράμετρο. +Από προεπιλογή, η συνάρτηση αλλάζει τη σειρά και επαναφέρει τα ακέραια κλειδιά του πίνακα. Αυτή η συμπεριφορά μπορεί να αλλάξει ορίζοντας το preserveKeys σε true. Τα κλειδιά string διατηρούνται πάντα, ανεξάρτητα από αυτήν την παράμετρο. diff --git a/latte/el/guide.texy b/latte/el/guide.texy index eedc876986..54d86226f5 100644 --- a/latte/el/guide.texy +++ b/latte/el/guide.texy @@ -1,46 +1,45 @@ -Ξεκινώντας με Latte -******************* +Ξεκινώντας με το Latte +**********************
                                          -Τα πρότυπα βελτιώνουν την οργάνωση του κώδικα, διαχωρίζουν τη λογική της εφαρμογής από την παρουσίαση και ενισχύουν την ασφάλεια. Προσφέρουν πολύ καλύτερα χαρακτηριστικά και εκφραστικές δυνατότητες για τη δημιουργία HTML από την ίδια την PHP. +Τα πρότυπα βελτιώνουν την οργάνωση του κώδικα, διαχωρίζουν τη λογική της εφαρμογής από την παρουσίαση και αυξάνουν την ασφάλεια. Προσφέρουν πολύ καλύτερες λειτουργίες και εκφραστικά μέσα για τη δημιουργία HTML από την ίδια την PHP. -Το Latte είναι το ασφαλέστερο σύστημα δημιουργίας προτύπων για την PHP. Θα λατρέψετε τη διαισθητική σύνταξή του. Ένα ευρύ φάσμα χρήσιμων χαρακτηριστικών θα απλοποιήσει σημαντικά την εργασία σας. -Παρέχει κορυφαία προστασία από [κρίσιμα τρωτά σημεία |safety-first] και σας επιτρέπει να επικεντρωθείτε στη δημιουργία εφαρμογών υψηλής ποιότητας χωρίς να ανησυχείτε για την ασφάλειά τους. +Το Latte είναι το ασφαλέστερο σύστημα προτύπων για PHP. Θα λατρέψετε τη διαισθητική του σύνταξη. Η ευρεία γκάμα χρήσιμων λειτουργιών θα διευκολύνει σημαντικά την εργασία σας. Παρέχει κορυφαία προστασία από [κρίσιμες ευπάθειες|safety-first] και σας επιτρέπει να επικεντρωθείτε στη δημιουργία ποιοτικών εφαρμογών χωρίς να ανησυχείτε για την ασφάλειά τους. -Πώς να γράψετε πρότυπα χρησιμοποιώντας το Latte; .[#toc-how-to-write-templates-using-latte] -------------------------------------------------------------------------------------------- +Πώς να γράψετε πρότυπα με το Latte; +----------------------------------- -Το Latte είναι έξυπνα σχεδιασμένο και εύκολο στην εκμάθηση για όσους είναι εξοικειωμένοι με την PHP, καθώς μπορούν γρήγορα να υιοθετήσουν τις βασικές του ετικέτες. +Το Latte είναι έξυπνα σχεδιασμένο και εύκολο στην εκμάθηση για όσους γνωρίζουν PHP και υιοθετούν τα βασικά tags. -- Αρχικά, εξοικειωθείτε με [τη σύνταξη της Latte |syntax] και [δοκιμάστε τα όλα online |https://fiddle.nette.org/latte/] -- Ρίξτε μια ματιά στο βασικό σύνολο [ετικετών |tags] και [φίλτρων |filters] -- Γράψτε πρότυπα στον [επεξεργαστή με υποστήριξη Latte |recipes#Editors and IDE] +- Αρχικά, εξοικειωθείτε με [τη σύνταξη του Latte|syntax] και [ΔΟΚΙΜΑΣΤΕ ΤΟ ONLINE |https://fiddle.nette.org/latte/#9cc0cf6d89] +- Δείτε το βασικό σύνολο [tags|tags] και [φίλτρων|filters] +- Γράψτε πρότυπα σε [editor με υποστήριξη Latte |recipes#Επεξεργαστές και IDE] -Πώς να χρησιμοποιήσετε το Latte στην PHP; .[#toc-how-to-use-latte-in-php] -------------------------------------------------------------------------- +Πώς να χρησιμοποιήσετε το Latte στην PHP; +----------------------------------------- -Η εφαρμογή του Latte στη νέα σας εφαρμογή είναι θέμα λίγων λεπτών: +Η ανάπτυξη του Latte στη νέα σας εφαρμογή είναι θέμα λίγων λεπτών: -- Πρώτον, [εγκαταστήστε και εκτελέστε το Latte |develop#Installation] -- Περιποιηθείτε τον εαυτό σας με το [εργαλείο εντοπισμού σφαλμάτων Tracy |develop#Debugging and Tracy] +- Αρχικά [εγκαταστήστε και εκτελέστε το Latte |develop#Εγκατάσταση] +- Αφεθείτε στην περιποίηση του [εργαλείου εντοπισμού σφαλμάτων Tracy |develop#Debugging και Tracy] - Επεκτείνετε το Latte με [προσαρμοσμένη λειτουργικότητα |extending-latte] -Αν μετατρέπετε ένα παλιό έργο γραμμένο σε απλή PHP σε Latte, το [εργαλείο για τη μετατροπή κώδικα PHP σε Latte |cookbook/migration-from-php] θα σας διευκολύνει τη μετάβαση. Ή σχεδιάζετε να μεταβείτε στη Latte από το Twig; Έχουμε έναν [μετατροπέα προτύπων Twig σε Latte |cookbook/migration-from-twig] για εσάς. +Εάν μετατρέπετε ένα παλιό έργο γραμμένο σε καθαρή PHP σε Latte, η μετεγκατάσταση θα διευκολυνθεί από το [εργαλείο για τη μετατροπή κώδικα PHP σε Latte |cookbook/migration-from-php]. Ή σκοπεύετε να μεταβείτε στο Latte από το Twig; Έχουμε για εσάς έναν [μετατροπέα προτύπων Twig σε Latte |cookbook/migration-from-twig]. -Τι άλλο μπορεί να κάνει το Latte; .[#toc-what-else-can-latte-do] ----------------------------------------------------------------- +Τι άλλο μπορεί να κάνει το Latte; +--------------------------------- -Το latte έρχεται πλήρως εξοπλισμένο, με όλα τα απαραίτητα. +Το Latte παρέχεται πλήρως εξοπλισμένο, με όλα τα σημαντικά στοιχεία στη βάση του. -- Η παραγωγικότητά σας θα ενισχυθεί από τους [μηχανισμούς κληρονομικότητας |template-inheritance] που επαναχρησιμοποιούν επαναλαμβανόμενα στοιχεία και δομές -- Η θωρακισμένη αποθήκη [Sandbox |Sandbox] απομονώνει τα πρότυπα από μη αξιόπιστες πηγές, όπως αυτές που επεξεργάζονται οι ίδιοι οι χρήστες -- Για περαιτέρω έμπνευση, ακολουθούν [συμβουλές και κόλπα |recipes] +- Η παραγωγικότητά σας θα ενισχυθεί από τους [μηχανισμούς κληρονομικότητας |template-inheritance] χάρη στους οποίους επαναχρησιμοποιούνται επαναλαμβανόμενα στοιχεία και δομές +- Το θωρακισμένο καταφύγιο [sandbox] απομονώνει πρότυπα από μη αξιόπιστες πηγές, τα οποία για παράδειγμα επεξεργάζονται οι ίδιοι οι χρήστες +- Για περαιτέρω έμπνευση, υπάρχουν [συμβουλές και κόλπα |recipes]
                                          -{{description: Latte je nejbezpečnější šablonovací systém pro PHP. Zabraňuje spoustě bezpečnostních zranitelností. Oceníte jeho intuitivní syntaxi a oceníte spoustu užitečných vychytávek.}} +{{description: Το Latte είναι το ασφαλέστερο σύστημα προτύπων για PHP. Αποτρέπει πολλές ευπάθειες ασφαλείας. Θα εκτιμήσετε τη διαισθητική του σύνταξη και τις πολλές χρήσιμες δυνατότητες.}} diff --git a/latte/el/loaders.texy b/latte/el/loaders.texy new file mode 100644 index 0000000000..8d55e0d617 --- /dev/null +++ b/latte/el/loaders.texy @@ -0,0 +1,198 @@ +Loaders +******* + +.[perex] +Οι Loaders είναι ο μηχανισμός που χρησιμοποιεί το Latte για να αποκτήσει τον πηγαίο κώδικα των προτύπων σας. Συνηθέστερα, τα πρότυπα αποθηκεύονται ως αρχεία στο δίσκο, αλλά χάρη στο ευέλικτο σύστημα των loaders, μπορείτε να τα φορτώσετε πρακτικά από οπουδήποτε ή ακόμη και να τα δημιουργήσετε δυναμικά. + + +Τι είναι ένας Loader; +===================== + +Όταν εργάζεστε με πρότυπα, συνήθως φαντάζεστε αρχεία `.latte` τοποθετημένα στη δομή καταλόγων του project σας. Αυτό το φροντίζει ο προεπιλεγμένος [#FileLoader] στο Latte. Ωστόσο, η σύνδεση μεταξύ του ονόματος ενός προτύπου (όπως `'main.latte'` ή `'components/card.latte'`) και του πραγματικού πηγαίου κώδικά του *δεν χρειάζεται* να είναι μια άμεση αντιστοίχιση σε μια διαδρομή αρχείου. + +Εδώ ακριβώς μπαίνουν στο παιχνίδι οι loaders. Ένας loader είναι ένα αντικείμενο που έχει ως καθήκον να πάρει το όνομα ενός προτύπου (μια αναγνωριστική συμβολοσειρά) και να παρέχει στο Latte τον πηγαίο κώδικά του. Το Latte βασίζεται εξ ολοκλήρου στον διαμορφωμένο loader για αυτή την εργασία. Αυτό ισχύει όχι μόνο για το αρχικό πρότυπο που ζητήθηκε με το `$latte->render('main.latte')`, αλλά και για **κάθε πρότυπο στο οποίο γίνεται αναφορά εντός του** χρησιμοποιώντας tags όπως `{include ...}`, `{layout ...}`, `{embed ...}` ή `{import ...}`. + +Γιατί να χρησιμοποιήσετε έναν προσαρμοσμένο loader; + +- **Φόρτωση από εναλλακτικές πηγές:** Λήψη προτύπων αποθηκευμένων σε βάση δεδομένων, σε cache (όπως Redis ή Memcached), σε σύστημα διαχείρισης εκδόσεων (όπως Git, βάσει συγκεκριμένου commit) ή δυναμικά δημιουργημένων. +- **Υλοποίηση προσαρμοσμένων συμβάσεων ονοματοδοσίας:** Μπορεί να θέλετε να χρησιμοποιείτε συντομότερα ψευδώνυμα για πρότυπα ή να υλοποιήσετε συγκεκριμένη λογική διαδρομών αναζήτησης (π.χ. αναζήτηση πρώτα στον κατάλογο θέματος, μετά επιστροφή στον προεπιλεγμένο κατάλογο). +- **Προσθήκη ασφάλειας ή ελέγχου πρόσβασης:** Ένας προσαρμοσμένος loader μπορεί να επαληθεύσει τα δικαιώματα χρήστη πριν φορτώσει ορισμένα πρότυπα. +- **Προεπεξεργασία:** Αν και γενικά δεν συνιστάται ([compilation passes |compiler-passes] είναι καλύτερα), ένας loader *θα μπορούσε* θεωρητικά να προεπεξεργαστεί το περιεχόμενο του προτύπου πριν το παραδώσει στο Latte. + +Ορίζετε τον loader για μια παρουσία `Latte\Engine` χρησιμοποιώντας τη μέθοδο `setLoader()`: + +```php +$latte = new Latte\Engine; + +// Χρήση του προεπιλεγμένου FileLoader για αρχεία στο '/path/to/templates' +$loader = new Latte\Loaders\FileLoader('/path/to/templates'); +$latte->setLoader($loader); +``` + +Ο loader πρέπει να υλοποιεί το interface `Latte\Loader`. + + +Ενσωματωμένοι Loaders +===================== + +Το Latte προσφέρει αρκετούς τυπικούς loaders: + + +FileLoader +---------- + +Αυτός είναι ο **προεπιλεγμένος loader** που χρησιμοποιείται από την κλάση `Latte\Engine`, εάν δεν καθοριστεί άλλος. Φορτώνει πρότυπα απευθείας από το σύστημα αρχείων. + +Προαιρετικά, μπορείτε να ορίσετε έναν root directory για να περιορίσετε την πρόσβαση: + +```php +use Latte\Loaders\FileLoader; + +// Το ακόλουθο θα επιτρέψει τη φόρτωση προτύπων μόνο από τον κατάλογο /var/www/html/templates +$loader = new FileLoader('/var/www/html/templates'); +$latte->setLoader($loader); + +// $latte->render('../../../etc/passwd'); // Αυτό θα προκαλούσε εξαίρεση + +// Απόδοση ενός προτύπου που βρίσκεται στο /var/www/html/templates/pages/contact.latte +$latte->render('pages/contact.latte'); +``` + +Όταν χρησιμοποιείτε tags όπως `{include}` ή `{layout}`, επιλύει τα ονόματα των προτύπων σχετικά με το τρέχον πρότυπο, εκτός εάν παρέχεται μια absolute path. + + +StringLoader +------------ + +Αυτός ο loader λαμβάνει το περιεχόμενο του προτύπου από έναν συσχετιστικό πίνακα, όπου τα κλειδιά είναι τα ονόματα των προτύπων (identifiers) και οι τιμές είναι οι συμβολοσειρές του πηγαίου κώδικα του προτύπου. Είναι ιδιαίτερα χρήσιμος για testing ή μικρές εφαρμογές όπου τα πρότυπα μπορεί να αποθηκεύονται απευθείας στον κώδικα PHP. + +```php +use Latte\Loaders\StringLoader; + +$loader = new StringLoader([ + 'main.latte' => 'Hello {$name}, include is below:{include helper.latte}', + 'helper.latte' => '{var $x = 10}Included content: {$x}', + // Προσθέστε περισσότερα πρότυπα όπως απαιτείται +]); + +$latte->setLoader($loader); + +$latte->render('main.latte', ['name' => 'World']); +// Έξοδος: Hello World, include is below:Included content: 10 +``` + +Εάν χρειάζεται να αποδώσετε μόνο ένα πρότυπο απευθείας από μια συμβολοσειρά χωρίς την ανάγκη ενσωμάτωσης ή κληρονομικότητας που αναφέρεται σε άλλα ονομασμένα string πρότυπα, μπορείτε να περάσετε τη συμβολοσειρά απευθείας στη μέθοδο `render()` ή `renderToString()` όταν χρησιμοποιείτε `StringLoader` χωρίς πίνακα: + +```php +$loader = new StringLoader; +$latte->setLoader($loader); + +$templateString = 'Hello {$name}!'; +$output = $latte->renderToString($templateString, ['name' => 'Alice']); +// Το $output περιέχει 'Hello Alice!' +``` + + +Δημιουργία προσαρμοσμένου Loader +================================ + +Για να δημιουργήσετε έναν προσαρμοσμένο loader (π.χ. για φόρτωση προτύπων από βάση δεδομένων, cache, σύστημα διαχείρισης εκδόσεων ή άλλη πηγή), πρέπει να δημιουργήσετε μια κλάση που υλοποιεί το interface [api:Latte\Loader]. + +Ας δούμε τι πρέπει να κάνει κάθε μέθοδος. + + +getContent(string $name): string .[method] +------------------------------------------ +Αυτή είναι η βασική μέθοδος του loader. Ο ρόλος της είναι να λάβει και να επιστρέψει τον πλήρη πηγαίο κώδικα του προτύπου που αναγνωρίζεται από το `$name` (όπως περνά στη μέθοδο `$latte->render()` ή επιστρέφεται από τη μέθοδο [#getReferredName()]). + +Εάν το πρότυπο δεν μπορεί να βρεθεί ή να προσπελαστεί, αυτή η μέθοδος **πρέπει να προκαλέσει μια εξαίρεση `Latte\RuntimeException`**. + +```php +public function getContent(string $name): string +{ + // Παράδειγμα: Φόρτωση από υποθετικό εσωτερικό αποθηκευτικό χώρο + $content = $this->storage->read($name); + if ($content === null) { + throw new Latte\RuntimeException("Template '$name' cannot be loaded."); + } + return $content; +} +``` + + +getReferredName(string $name, string $referringName): string .[method] +---------------------------------------------------------------------- +Αυτή η μέθοδος χειρίζεται τη μετάφραση των ονομάτων των προτύπων που χρησιμοποιούνται εντός tags όπως `{include}`, `{layout}`, κ.λπ. Όταν το Latte συναντήσει, για παράδειγμα, το `{include 'partial.latte'}` εντός του `main.latte`, καλεί αυτή τη μέθοδο με `$name = 'partial.latte'` και `$referringName = 'main.latte'`. + +Ο ρόλος της μεθόδου είναι να μεταφράσει το `$name` σε ένα canonical identifier (π.χ. absolute path, μοναδικό κλειδί βάσης δεδομένων) που θα χρησιμοποιηθεί κατά την κλήση άλλων μεθόδων του loader, βάσει του context που παρέχεται στο `$referringName`. + +```php +public function getReferredName(string $name, string $referringName): string +{ + return ...; +} +``` + + +getUniqueId(string $name): string .[method] +------------------------------------------- +Το Latte χρησιμοποιεί μια cache μεταγλωττισμένων προτύπων για τη βελτίωση της απόδοσης. Κάθε αρχείο μεταγλωττισμένου προτύπου χρειάζεται ένα μοναδικό όνομα που προέρχεται από το identifier του πηγαίου προτύπου. Αυτή η μέθοδος παρέχει μια συμβολοσειρά που **αναγνωρίζει μοναδικά** το πρότυπο `$name`. + +Για πρότυπα που βασίζονται σε αρχεία, η absolute path μπορεί να χρησιμεύσει. Για πρότυπα σε βάση δεδομένων, ένας συνδυασμός προθέματος και ID βάσης δεδομένων είναι συνηθισμένος. + +```php +public function getUniqueId(string $name): string +{ + return ...; +} +``` + + +Παράδειγμα: Απλός Loader Βάσης Δεδομένων +---------------------------------------- + +Αυτό το παράδειγμα δείχνει τη βασική δομή ενός loader που φορτώνει πρότυπα αποθηκευμένα σε έναν πίνακα βάσης δεδομένων με όνομα `templates` με στήλες `name` (μοναδικό identifier), `content` και `updated_at`. + +```php +use Latte; + +class DatabaseLoader implements Latte\Loader +{ + public function __construct( + private \PDO $db, + ) { + } + + public function getContent(string $name): string + { + $stmt = $this->db->prepare('SELECT content FROM templates WHERE name = ?'); + $stmt->execute([$name]); + $content = $stmt->fetchColumn(); + if ($content === false) { + throw new Latte\RuntimeException("Template '$name' not found in database."); + } + return $content; + } + + // Αυτό το απλό παράδειγμα υποθέτει ότι τα ονόματα των προτύπων ('homepage', 'article', κ.λπ.) + // είναι μοναδικά ID και τα πρότυπα δεν αναφέρονται το ένα στο άλλο σχετικά. + public function getReferredName(string $name, string $referringName): string + { + return $name; + } + + public function getUniqueId(string $name): string + { + // Η χρήση ενός προθέματος και του ίδιου του ονόματος είναι μοναδική και επαρκής εδώ + return 'db_' . $name; + } +} + +// Χρήση: +$pdo = new \PDO(/* λεπτομέρειες σύνδεσης */); +$loader = new DatabaseLoader($pdo); +$latte->setLoader($loader); +$latte->render('homepage'); // Φορτώνει το πρότυπο με όνομα 'homepage' από τη ΒΔ +``` + +Οι προσαρμοσμένοι loaders σας δίνουν πλήρη έλεγχο από πού προέρχονται τα Latte πρότυπά σας, επιτρέποντας την ενσωμάτωση με διάφορα συστήματα αποθήκευσης και ροές εργασίας. diff --git a/latte/el/recipes.texy b/latte/el/recipes.texy index 358bbc29bf..12e9118451 100644 --- a/latte/el/recipes.texy +++ b/latte/el/recipes.texy @@ -2,44 +2,44 @@ ******************* -Επεξεργαστές και IDE .[#toc-editors-and-ide] -============================================ +Επεξεργαστές και IDE +==================== -Γράψτε πρότυπα σε έναν επεξεργαστή ή IDE που έχει υποστήριξη για Latte. Θα είναι πολύ πιο ευχάριστο. +Γράψτε πρότυπα σε έναν editor ή IDE που υποστηρίζει το Latte. Θα είναι πολύ πιο ευχάριστο. -- Το NetBeans IDE έχει ενσωματωμένη υποστήριξη -- PhpStorm: εγκαταστήστε το [πρόσθετο Latte |https://plugins.jetbrains.com/plugin/7457-latte] στο `Settings > Plugins > Marketplace` -- VS Code: αναζητήστε το markerplace για το plugin "Nette Latte + Neon". +- PhpStorm: εγκαταστήστε το [plugin Latte|https://plugins.jetbrains.com/plugin/7457-latte] από το `Settings > Plugins > Marketplace` +- VS Code: εγκαταστήστε το [Nette Latte + Neon|https://marketplace.visualstudio.com/items?itemName=Kasik96.latte], [Nette Latte templates|https://marketplace.visualstudio.com/items?itemName=smuuf.latte-lang] ή το νεότερο plugin [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode] +- NetBeans IDE: η εγγενής υποστήριξη Latte περιλαμβάνεται στην εγκατάσταση - Sublime Text 3: στο Package Control βρείτε και εγκαταστήστε το πακέτο `Nette` και επιλέξτε Latte στο `View > Syntax` -- σε παλιούς επεξεργαστές χρησιμοποιήστε την επισήμανση Smarty για τα αρχεία .latte +- σε παλιούς editors χρησιμοποιήστε την επισήμανση Smarty για αρχεία `.latte` -Το πρόσθετο για το PhpStorm είναι πολύ προηγμένο και μπορεί να προτείνει τέλεια κώδικα PHP. Για να λειτουργήσει βέλτιστα, χρησιμοποιήστε [τυποποιημένα πρότυπα |type-system]. +Το plugin για το PhpStorm είναι πολύ προηγμένο και μπορεί να παρέχει εξαιρετική βοήθεια για τον κώδικα PHP. Για να λειτουργεί βέλτιστα, χρησιμοποιήστε [τυποποιημένα πρότυπα|type-system]. [* latte-phpstorm-plugin.webp *] -Υποστήριξη για το Latte μπορεί επίσης να βρεθεί στον web code highlighter [Prism.js |https://prismjs.com/#supported-languages] και στον επεξεργαστή [Ace |https://ace.c9.io]. +Υποστήριξη για το Latte θα βρείτε επίσης στον web highlighter κώδικα [Prism.js|https://prismjs.com/#supported-languages] και στον editor [Ace|https://ace.c9.io]. -Latte μέσα σε JavaScript ή CSS .[#toc-latte-inside-javascript-or-css] -===================================================================== +Latte μέσα σε JavaScript ή CSS +============================== -Το Latte μπορεί να χρησιμοποιηθεί πολύ άνετα μέσα σε JavaScript ή CSS. Αλλά πώς να αποφύγετε το Latte να θεωρεί λανθασμένα τον κώδικα JavaScript ή το στυλ CSS ως ετικέτα Latte; +Το Latte μπορεί να χρησιμοποιηθεί πολύ άνετα και μέσα σε JavaScript ή CSS. Πώς όμως να αποφύγετε την κατάσταση όπου το Latte θα θεωρούσε λανθασμένα τον κώδικα JavaScript ή το στυλ CSS ως tag του Latte; ```latte ``` **Επιλογή 1** -Αποφύγετε τις περιπτώσεις όπου ένα γράμμα ακολουθεί αμέσως μετά από ένα `{`, είτε εισάγοντας ένα κενό, ένα διάλειμμα γραμμής ή ένα εισαγωγικό μεταξύ τους: +Αποφύγετε την κατάσταση όπου ένα γράμμα ακολουθεί αμέσως μετά το `{`, για παράδειγμα, εισάγοντας ένα κενό, μια αλλαγή γραμμής ή ένα εισαγωγικό πριν από αυτό: ```latte -- in comment: +```twig .{file:Πρότυπο Twig, όπως το βλέπει ο σχεδιαστής} +- σε κείμενο: {{ foo }} +- σε tag: +- σε attribute: +- σε attribute χωρίς εισαγωγικά: +- σε attribute που περιέχει URL: +- σε attribute που περιέχει JavaScript: +- σε attribute που περιέχει CSS: +- σε JavaScript: +- σε CSS: +- σε σχόλιο: ```
                                          -Τα αφελή συστήματα μετατρέπουν μηχανικά τους χαρακτήρες `< > & ' "` σε οντότητες HTML, που είναι ένας έγκυρος τρόπος διαφυγής στις περισσότερες χρήσεις, αλλά όχι πάντα. Έτσι, δεν μπορούν να ανιχνεύσουν ή να αποτρέψουν διάφορα κενά ασφαλείας, όπως θα δείξουμε παρακάτω. +Τα απλοϊκά συστήματα απλώς μετατρέπουν μηχανικά τους χαρακτήρες `< > & ' "` σε οντότητες HTML, κάτι που, αν και στις περισσότερες περιπτώσεις χρήσης είναι ένας έγκυρος τρόπος escaping, δεν ισχύει πάντα. Δεν μπορούν έτσι να αποκαλύψουν ούτε να αποτρέψουν τη δημιουργία διαφόρων κενών ασφαλείας, όπως θα δείξουμε παρακάτω. -Η Latte βλέπει το πρότυπο με τον ίδιο τρόπο που το βλέπετε κι εσείς. Κατανοεί την HTML, την XML, αναγνωρίζει τις ετικέτες, τα χαρακτηριστικά κ.λπ. Και εξαιτίας αυτού, κάνει διάκριση μεταξύ των πλαισίων και αντιμετωπίζει τα δεδομένα ανάλογα. Έτσι, προσφέρει πραγματικά αποτελεσματική προστασία από την κρίσιμη ευπάθεια Cross-site Scripting. +Το Latte βλέπει το πρότυπο όπως εσείς. Καταλαβαίνει HTML, XML, αναγνωρίζει tags, attributes κ.λπ. Και χάρη σε αυτό, διακρίνει τα επιμέρους contexts και επεξεργάζεται τα δεδομένα ανάλογα. Προσφέρει έτσι πραγματικά αποτελεσματική προστασία έναντι της κρίσιμης ευπάθειας Cross-site Scripting. + +
                                          + +```latte .{file:Πρότυπο Latte, όπως το βλέπει το Latte} +░░░░░░░░░░░{$foo} +░░░░░░░░░░ +░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░ +░░░░░░░░░ +░░░░░░░░░░░░░░░ +``` + +```latte .{file:Πρότυπο Latte, όπως το βλέπει ο σχεδιαστής} +- σε κείμενο: {$foo} +- σε tag: +- σε attribute: +- σε attribute χωρίς εισαγωγικά: +- σε attribute που περιέχει URL: +- σε attribute που περιέχει JavaScript: +- σε attribute που περιέχει CSS: +- σε JavaScript: +- σε CSS: +- σε σχόλιο: +``` + +
                                          -Ζωντανή επίδειξη .[#toc-live-demonstration] -=========================================== +Ζωντανό παράδειγμα +================== -Στα αριστερά μπορείτε να δείτε το πρότυπο στο Latte, ενώ στα δεξιά είναι ο παραγόμενος κώδικας HTML. Η μεταβλητή `$text` εξάγεται αρκετές φορές, κάθε φορά σε ελαφρώς διαφορετικό πλαίσιο. Και ως εκ τούτου διαφυλάσσεται λίγο διαφορετικά. Μπορείτε να επεξεργαστείτε τον κώδικα του προτύπου μόνοι σας, για παράδειγμα να αλλάξετε το περιεχόμενο της μεταβλητής κ.λπ. Δοκιμάστε το: +Αριστερά βλέπετε το πρότυπο στο Latte, δεξιά είναι ο παραγόμενος κώδικας HTML. Η μεταβλητή `$text` εκτυπώνεται αρκετές φορές εδώ και κάθε φορά σε ένα ελαφρώς διαφορετικό context. Και επομένως, γίνεται και ελαφρώς διαφορετικά escape. Μπορείτε να επεξεργαστείτε μόνοι σας τον κώδικα του προτύπου, για παράδειγμα, να αλλάξετε το περιεχόμενο της μεταβλητής κ.λπ. Δοκιμάστε το:
                                          ``` .{file:template.latte; min-height: 14em}[fiddle-source] -{* TRY TO EDIT THIS TEMPLATE *} +{* ΔΟΚΙΜΑΣΤΕ ΝΑ ΕΠΕΞΕΡΓΑΣΤΕΙΤΕ ΑΥΤΟ ΤΟ ΠΡΟΤΥΠΟ *} {var $text = "Rock'n'Roll"} - {$text} - @@ -270,63 +286,61 @@ Latte vs Naive Systems .[#toc-latte-vs-naive-systems]
                                          -Δεν είναι υπέροχο! Το Latte κάνει αυτόματα τη διαφυγή με ευαισθησία περιβάλλοντος, έτσι ώστε ο προγραμματιστής: +Δεν είναι υπέροχο! Το Latte κάνει το context-aware escaping αυτόματα, οπότε ο προγραμματιστής: -- δεν χρειάζεται να σκέφτεται ή να γνωρίζει πώς να αποφεύγει δεδομένα +- δεν χρειάζεται να σκέφτεται ούτε να ξέρει πώς γίνεται το escape πού - δεν μπορεί να κάνει λάθος -- δεν μπορεί να το ξεχάσει +- δεν μπορεί να ξεχάσει το escaping -Αυτά δεν είναι καν όλα τα συμφραζόμενα που διακρίνει η Latte κατά την εξαγωγή και για τα οποία προσαρμόζει την επεξεργασία των δεδομένων. Θα εξετάσουμε τώρα περισσότερες ενδιαφέρουσες περιπτώσεις. +Αυτά δεν είναι καν όλα τα contexts που διακρίνει το Latte κατά την εκτύπωση και για τα οποία προσαρμόζει την επεξεργασία των δεδομένων. Θα εξετάσουμε τώρα άλλες ενδιαφέρουσες περιπτώσεις. -Πώς να χακάρετε τα αφελή συστήματα .[#toc-how-to-hack-naive-systems] -==================================================================== +Πώς να χακάρετε απλοϊκά συστήματα +================================= -Θα χρησιμοποιήσουμε μερικά πρακτικά παραδείγματα για να δείξουμε πόσο σημαντική είναι η διαφοροποίηση του πλαισίου και γιατί τα αφελή συστήματα templating δεν παρέχουν επαρκή προστασία έναντι του XSS, σε αντίθεση με το Latte. -Στα παραδείγματα θα χρησιμοποιήσουμε το Twig ως εκπρόσωπο ενός αφελούς συστήματος, αλλά το ίδιο ισχύει και για άλλα συστήματα. +Σε μερικά πρακτικά παραδείγματα, θα δείξουμε πόσο σημαντική είναι η διάκριση των contexts και γιατί τα απλοϊκά συστήματα προτύπων δεν παρέχουν επαρκή προστασία από το XSS, σε αντίθεση με το Latte. Ως εκπρόσωπο ενός απλοϊκού συστήματος, θα χρησιμοποιήσουμε το Twig στα παραδείγματα, αλλά το ίδιο ισχύει και για άλλα συστήματα. -Ευπάθεια χαρακτηριστικών .[#toc-attribute-vulnerability] --------------------------------------------------------- +Ευπάθεια μέσω attribute +----------------------- -Ας προσπαθήσουμε να εισάγουμε κακόβουλο κώδικα στη σελίδα χρησιμοποιώντας το χαρακτηριστικό HTML όπως [δείξαμε παραπάνω |#How does the vulnerability arise]. Ας έχουμε ένα πρότυπο στο Twig που εμφανίζει μια εικόνα: +Θα προσπαθήσουμε να εισάγουμε κακόβουλο κώδικα στη σελίδα μέσω ενός attribute HTML, όπως [δείξαμε παραπάνω |#Πώς προκύπτει η ευπάθεια]. Ας έχουμε ένα πρότυπο στο Twig που αποδίδει μια εικόνα: ```twig .{file:Twig} {{ ``` -Σημειώστε ότι δεν υπάρχουν εισαγωγικά γύρω από τις τιμές του χαρακτηριστικού. Ο κωδικοποιητής μπορεί να τα ξέχασε, κάτι που απλά συμβαίνει. Για παράδειγμα, στο React, ο κώδικας γράφεται έτσι, χωρίς εισαγωγικά, και ένας προγραμματιστής που αλλάζει γλώσσα μπορεί εύκολα να ξεχάσει τα εισαγωγικά. +Παρατηρήστε ότι γύρω από τις τιμές των attributes δεν υπάρχουν εισαγωγικά. Ο κωδικοποιητής μπορεί να τα ξέχασε, κάτι που απλά συμβαίνει. Για παράδειγμα, στο React ο κώδικας γράφεται έτσι, χωρίς εισαγωγικά, και ένας κωδικοποιητής που αλλάζει γλώσσες μπορεί εύκολα να ξεχάσει τα εισαγωγικά. -Ο επιτιθέμενος εισάγει ένα έξυπνα κατασκευασμένο αλφαριθμητικό `foo onload=alert('Hacked!')` ως λεζάντα εικόνας. Γνωρίζουμε ήδη ότι το Twig δεν μπορεί να καταλάβει αν μια μεταβλητή εκτυπώνεται σε μια ροή κειμένου HTML, μέσα σε ένα χαρακτηριστικό, μέσα σε ένα σχόλιο HTML κ.ο.κ. Με λίγα λόγια, δεν κάνει διάκριση μεταξύ των πλαισίων. Και απλά μετατρέπει μηχανικά τους χαρακτήρες `< > & ' "` σε οντότητες HTML. -Έτσι, ο κώδικας που θα προκύψει θα μοιάζει ως εξής: +Ένας εισβολέας εισάγει ως λεζάντα της εικόνας ένα έξυπνα κατασκευασμένο string `foo onload=alert('Hacked!')`. Γνωρίζουμε ήδη ότι το Twig δεν μπορεί να αναγνωρίσει αν η μεταβλητή εκτυπώνεται στη ροή του κειμένου HTML, μέσα σε ένα attribute, σε ένα σχόλιο HTML κ.λπ., με λίγα λόγια δεν διακρίνει τα contexts. Και απλώς μετατρέπει μηχανικά τους χαρακτήρες `< > & ' "` σε οντότητες HTML. Έτσι, ο προκύπτων κώδικας θα μοιάζει ως εξής: ```html foo ``` -**Δημιουργήθηκε ένα κενό ασφαλείας!** +**Και δημιουργήθηκε ένα κενό ασφαλείας!** -Ένα ψεύτικο χαρακτηριστικό `onload` έχει γίνει μέρος της σελίδας και το πρόγραμμα περιήγησης το εκτελεί αμέσως μετά τη λήψη της εικόνας. +Μέρος της σελίδας έγινε το πλαστό attribute `onload` και ο browser το εκτελεί αμέσως μετά τη λήψη της εικόνας. -Ας δούμε τώρα πώς χειρίζεται το ίδιο πρότυπο η Latte: +Τώρα ας δούμε πώς το Latte χειρίζεται το ίδιο πρότυπο: ```latte .{file:Latte} {$imageAlt} ``` -Latte βλέπει το πρότυπο με τον ίδιο τρόπο που το βλέπετε εσείς. Σε αντίθεση με το Twig, καταλαβαίνει την HTML και γνωρίζει ότι μια μεταβλητή εκτυπώνεται ως τιμή χαρακτηριστικού που δεν είναι σε εισαγωγικά. Γι' αυτό και τα προσθέτει. Όταν ένας επιτιθέμενος εισάγει την ίδια λεζάντα, ο κώδικας που προκύπτει θα μοιάζει κάπως έτσι: +Το Latte βλέπει το πρότυπο όπως εσείς. Σε αντίθεση με το Twig, καταλαβαίνει HTML και ξέρει ότι η μεταβλητή εκτυπώνεται ως τιμή ενός attribute που δεν βρίσκεται σε εισαγωγικά. Γι' αυτό τα συμπληρώνει. Όταν ο εισβολέας εισάγει την ίδια λεζάντα, ο προκύπτων κώδικας θα μοιάζει ως εξής: ```html foo onload=alert('Hacked!') ``` -**Το latte απέτρεψε επιτυχώς το XSS.** +**Το Latte απέτρεψε με επιτυχία το XSS.** -Εκτύπωση μιας μεταβλητής σε JavaScript .[#toc-printing-a-variable-in-javascript] --------------------------------------------------------------------------------- +Εκτύπωση μεταβλητής σε JavaScript +--------------------------------- -Χάρη στη διαφυγή με ευαισθησία περιβάλλοντος, είναι δυνατή η χρήση μεταβλητών της PHP εγγενώς μέσα στη JavaScript. +Χάρη στο context-aware escaping, είναι εξαιρετικά εύκολο να χρησιμοποιείτε μεταβλητές PHP μέσα σε JavaScript. ```latte

                                          {$movie}

                                          @@ -334,7 +348,7 @@ Latte βλέπει το πρότυπο με τον ίδιο τρόπο που τ ``` -Εάν η μεταβλητή `$movie` αποθηκεύει το αλφαριθμητικό `'Amarcord & 8 1/2'`, παράγει την ακόλουθη έξοδο. Παρατηρήστε τη διαφορετική διαφυγή που χρησιμοποιείται στην HTML και τη JavaScript, καθώς και στο χαρακτηριστικό `onclick`: +Εάν η μεταβλητή `$movie` περιέχει το string `'Amarcord & 8 1/2'`, θα παραχθεί η ακόλουθη έξοδος. Παρατηρήστε ότι μέσα στο HTML χρησιμοποιείται διαφορετικό escaping από ό,τι μέσα στο JavaScript και ακόμα διαφορετικό στο attribute `onclick`: ```latte

                                          Amarcord & 8 1/2

                                          @@ -343,29 +357,27 @@ Latte βλέπει το πρότυπο με τον ίδιο τρόπο που τ ``` -Έλεγχος συνδέσμου .[#toc-link-checking] ---------------------------------------- +Έλεγχος συνδέσμων +----------------- -Το Latte ελέγχει αυτόματα αν η μεταβλητή που χρησιμοποιείται στα χαρακτηριστικά `src` ή `href` περιέχει μια διεύθυνση URL στο διαδίκτυο (δηλαδή πρωτόκολλο HTTP) και αποτρέπει τη συγγραφή συνδέσμων που μπορεί να αποτελέσουν κίνδυνο για την ασφάλεια. +Το Latte ελέγχει αυτόματα εάν η μεταβλητή που χρησιμοποιείται στα attributes `src` ή `href` περιέχει μια διεύθυνση URL ιστού (δηλαδή πρωτόκολλο HTTP) και αποτρέπει την εμφάνιση συνδέσμων που ενδέχεται να αποτελούν κίνδυνο για την ασφάλεια. ```latte {var $link = 'javascript:attack()'} -click here +κάντε κλικ ``` -Γράφει: +Εκτυπώνει: ```latte -click here +κάντε κλικ ``` Ο έλεγχος μπορεί να απενεργοποιηθεί χρησιμοποιώντας το φίλτρο [nocheck |filters#nocheck]. -Όρια του Latte .[#toc-limits-of-latte] -====================================== +Όρια του Latte +============== -Το Latte δεν αποτελεί πλήρη προστασία XSS για ολόκληρη την εφαρμογή. Θα μας δυσαρεστούσε αν σταματούσατε να σκέφτεστε την ασφάλεια όταν χρησιμοποιείτε το Latte. -Ο στόχος του Latte είναι να διασφαλίσει ότι ένας επιτιθέμενος δεν μπορεί να αλλάξει τη δομή μιας σελίδας, να αλλοιώσει στοιχεία ή χαρακτηριστικά HTML. Δεν ελέγχει όμως την ορθότητα του περιεχομένου των δεδομένων που εξάγονται. Ή την ορθότητα της συμπεριφοράς της JavaScript. -Αυτό είναι πέρα από το πεδίο εφαρμογής του συστήματος διαμόρφωσης προτύπων. Η επαλήθευση της ορθότητας των δεδομένων, ειδικά αυτών που εισάγονται από τον χρήστη και συνεπώς δεν είναι αξιόπιστα, είναι ένα σημαντικό έργο για τον προγραμματιστή. +Το Latte δεν αποτελεί εντελώς πλήρη προστασία από το XSS για ολόκληρη την εφαρμογή. Δεν θα θέλαμε να σταματήσετε να σκέφτεστε την ασφάλεια όταν χρησιμοποιείτε το Latte. Ο στόχος του Latte είναι να διασφαλίσει ότι ένας εισβολέας δεν μπορεί να τροποποιήσει τη δομή της σελίδας, να πλαστογραφήσει στοιχεία ή attributes HTML. Αλλά δεν ελέγχει την ορθότητα του περιεχομένου των εκτυπωμένων δεδομένων. Ούτε την ορθότητα της συμπεριφοράς του JavaScript. Αυτό ξεπερνά τις αρμοδιότητες ενός συστήματος προτύπων. Η επαλήθευση της ορθότητας των δεδομένων, ειδικά αυτών που εισάγονται από τον χρήστη και επομένως είναι μη αξιόπιστα, είναι ένα σημαντικό καθήκον του προγραμματιστή. diff --git a/latte/el/sandbox.texy b/latte/el/sandbox.texy index 0c724128ea..269b50d44f 100644 --- a/latte/el/sandbox.texy +++ b/latte/el/sandbox.texy @@ -1,12 +1,10 @@ Sandbox ******* -.[perex]{data-version:2.8} -Το Sandbox παρέχει ένα επίπεδο ασφαλείας που σας δίνει τον έλεγχο των ετικετών, των συναρτήσεων PHP, των μεθόδων κ.λπ. που μπορούν να χρησιμοποιηθούν στα πρότυπα. Χάρη στη λειτουργία sandbox, μπορείτε να συνεργαστείτε με ασφάλεια με έναν πελάτη ή έναν εξωτερικό προγραμματιστή στη δημιουργία προτύπων χωρίς να ανησυχείτε για τη διακινδύνευση της εφαρμογής ή για ανεπιθύμητες λειτουργίες. +.[perex] +Το Sandbox παρέχει ένα επίπεδο ασφαλείας που σας δίνει τον έλεγχο του ποια tags, συναρτήσεις PHP, μέθοδοι κ.λπ. μπορούν να χρησιμοποιηθούν στα πρότυπα. Χάρη στη λειτουργία sandbox, μπορείτε να συνεργαστείτε με ασφάλεια με τον πελάτη ή έναν εξωτερικό κωδικοποιητή στη δημιουργία προτύπων, χωρίς να χρειάζεται να ανησυχείτε για παραβίαση της εφαρμογής ή ανεπιθύμητες λειτουργίες. -Πώς λειτουργεί; Απλώς ορίζουμε τι θέλουμε να επιτρέψουμε στο πρότυπο. Στην αρχή, όλα απαγορεύονται και σταδιακά παραχωρούμε δικαιώματα: - -Ο παρακάτω κώδικας επιτρέπει στο πρότυπο να χρησιμοποιεί τις ετικέτες `{block}`, `{if}`, `{else}` και `{=}` (η τελευταία είναι μια ετικέτα για την [εκτύπωση μιας μεταβλητής ή έκφρασης |tags#Printing]) και όλα τα φίλτρα: +Πώς λειτουργεί; Απλά ορίζουμε τι επιτρέπουμε στο πρότυπο. Ενώ από προεπιλογή, όλα είναι απαγορευμένα και σταδιακά επιτρέπουμε πράγματα. Με τον ακόλουθο κώδικα, επιτρέπουμε στον συγγραφέα του προτύπου να χρησιμοποιεί τα tags `{block}`, `{if}`, `{else}` και `{=}`, που είναι το tag για [εκτύπωση μεταβλητής ή έκφρασης |tags#Εκτύπωση] και όλα τα φίλτρα: ```php $policy = new Latte\Sandbox\SecurityPolicy; @@ -16,7 +14,7 @@ $policy->allowFilters($policy::All); $latte->setPolicy($policy); ``` -Μπορούμε επίσης να επιτρέψουμε την πρόσβαση σε παγκόσμιες συναρτήσεις, μεθόδους ή ιδιότητες αντικειμένων: +Επιπλέον, μπορούμε να επιτρέψουμε μεμονωμένες συναρτήσεις, μεθόδους ή properties αντικειμένων: ```php $policy->allowFunctions(['trim', 'strlen']); @@ -24,30 +22,35 @@ $policy->allowMethods(Nette\Security\User::class, ['isLoggedIn', 'isAllowed']); $policy->allowProperties(Nette\Database\Row::class, $policy::All); ``` -Δεν είναι καταπληκτικό; Μπορείτε να ελέγχετε τα πάντα σε πολύ χαμηλό επίπεδο. Αν το πρότυπο προσπαθήσει να καλέσει μια μη εξουσιοδοτημένη συνάρτηση ή να αποκτήσει πρόσβαση σε μια μη εξουσιοδοτημένη μέθοδο ή ιδιότητα, θα πετάξει την εξαίρεση `Latte\SecurityViolationException`. +Δεν είναι υπέροχο; Μπορείτε να ελέγχετε τα πάντα σε πολύ χαμηλό επίπεδο. Εάν το πρότυπο προσπαθήσει να καλέσει μια μη επιτρεπόμενη συνάρτηση ή να αποκτήσει πρόσβαση σε μια μη επιτρεπόμενη μέθοδο ή property, θα καταλήξει σε εξαίρεση `Latte\SecurityViolationException`. -Η δημιουργία πολιτικών από το μηδέν, όταν όλα είναι απαγορευμένα, μπορεί να μην είναι βολική, οπότε μπορείτε να ξεκινήσετε από μια ασφαλή βάση: +Η δημιουργία μιας πολιτικής από το μηδέν, όπου τα πάντα είναι απαγορευμένα, μπορεί να μην είναι βολική, γι' αυτό μπορείτε να ξεκινήσετε από μια ασφαλή βάση: ```php $policy = Latte\Sandbox\SecurityPolicy::createSafePolicy(); ``` -Αυτό σημαίνει ότι επιτρέπονται όλες οι τυπικές ετικέτες εκτός από τις εξής: `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget`. -Όλα τα τυποποιημένα φίλτρα επιτρέπονται επίσης εκτός από τα `datastream`, `noescape` και `nocheck`. Τέλος, επιτρέπεται επίσης η πρόσβαση στις μεθόδους και τις ιδιότητες του αντικειμένου `$iterator`. +Η ασφαλής βάση σημαίνει ότι επιτρέπονται όλα τα τυπικά tags εκτός από τα `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget`. Επιτρέπονται τα τυπικά φίλτρα εκτός από τα `datastream`, `noescape` και `nocheck`. Και τέλος, επιτρέπεται η πρόσβαση στις μεθόδους και τις properties του αντικειμένου `$iterator`. -Οι κανόνες ισχύουν για το πρότυπο που εισάγουμε με το νέο [`{sandbox}` |tags#Including Templates] ετικέτα. Το οποίο είναι κάτι σαν το `{include}`, αλλά ενεργοποιεί τη λειτουργία sandbox και επίσης δεν περνάει εξωτερικές μεταβλητές: +Οι κανόνες εφαρμόζονται για το πρότυπο που εισάγουμε με το tag [`{sandbox}` |tags#Εισαγωγή template]. Το οποίο είναι κάτι σαν το `{include}`, αλλά ενεργοποιεί την ασφαλή λειτουργία και επίσης δεν περνάει καμία μεταβλητή: ```latte {sandbox 'untrusted.latte'} ``` -Έτσι, η διάταξη και οι μεμονωμένες σελίδες μπορούν να χρησιμοποιούν όλες τις ετικέτες και τις μεταβλητές όπως και πριν, οι περιορισμοί θα εφαρμόζονται μόνο στο πρότυπο `untrusted.latte`. +Έτσι, το layout και οι επιμέρους σελίδες μπορούν να χρησιμοποιούν ανεμπόδιστα όλα τα tags και τις μεταβλητές, μόνο στο πρότυπο `untrusted.latte` θα εφαρμοστούν οι περιορισμοί. -Ορισμένες παραβιάσεις, όπως η χρήση απαγορευμένης ετικέτας ή φίλτρου, ανιχνεύονται κατά τη μεταγλώττιση. Άλλες, όπως η κλήση μη επιτρεπόμενων μεθόδων ενός αντικειμένου, κατά το χρόνο εκτέλεσης. -Το πρότυπο μπορεί επίσης να περιέχει οποιαδήποτε άλλα σφάλματα. Προκειμένου να αποτρέψετε την απόρριψη μιας εξαίρεσης από το πρότυπο sandboxed, η οποία διαταράσσει ολόκληρη την απόδοση, μπορείτε να ορίσετε [το δικό σας χειριστή εξαιρέσεων |develop#exception handler], ο οποίος, για παράδειγμα, απλώς την καταγράφει. +Ορισμένες παραβάσεις, όπως η χρήση ενός απαγορευμένου tag ή φίλτρου, ανιχνεύονται κατά τη μεταγλώττιση. Άλλες, όπως η κλήση μη επιτρεπόμενων μεθόδων αντικειμένου, κατά το χρόνο εκτέλεσης. Το πρότυπο μπορεί επίσης να περιέχει οποιαδήποτε άλλα σφάλματα. Για να μην μπορεί να προκύψει μια εξαίρεση από το sandbox πρότυπο που θα διαταράξει ολόκληρη την απόδοση, μπορείτε να ορίσετε έναν προσαρμοσμένο [χειριστή εξαιρέσεων |develop#Χειριστής Εξαιρέσεων], ο οποίος για παράδειγμα θα την καταγράψει. -Αν θέλουμε να ενεργοποιήσουμε τη λειτουργία sandbox απευθείας για όλα τα πρότυπα, είναι εύκολο: +Αν θέλαμε να ενεργοποιήσουμε τη λειτουργία sandbox απευθείας για όλα τα πρότυπα, είναι εύκολο: ```php $latte->setSandboxMode(); ``` + +Για να βεβαιωθείτε ότι ο χρήστης δεν θα εισάγει κώδικα PHP στη σελίδα, ο οποίος είναι μεν συντακτικά σωστός, αλλά απαγορευμένος και προκαλεί PHP Compile Error, συνιστούμε να [ελέγχετε τα πρότυπα με τον PHP linter |develop#Έλεγχος Δημιουργημένου Κώδικα]. Αυτή η λειτουργία ενεργοποιείται με τη μέθοδο `Engine::enablePhpLint()`. Επειδή για τον έλεγχο χρειάζεται να καλέσει το εκτελέσιμο PHP, περάστε τη διαδρομή προς αυτό ως παράμετρο: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); +``` diff --git a/latte/el/syntax.texy b/latte/el/syntax.texy index 466d0dcbd2..0e4d441b8d 100644 --- a/latte/el/syntax.texy +++ b/latte/el/syntax.texy @@ -2,62 +2,60 @@ ******* .[perex] -Η Syntax Latte γεννήθηκε από τις πρακτικές απαιτήσεις των σχεδιαστών ιστοσελίδων. Ψάχναμε για την πιο φιλική προς το χρήστη σύνταξη, με την οποία μπορείτε να γράψετε κομψά δομές που διαφορετικά αποτελούν πραγματική πρόκληση. -Ταυτόχρονα, όλες οι εκφράσεις γράφονται ακριβώς όπως και στην PHP, οπότε δεν χρειάζεται να μάθετε μια νέα γλώσσα. Απλώς αξιοποιείτε στο έπακρο αυτό που ήδη γνωρίζετε. +Η σύνταξη του Latte προέκυψε από τις πρακτικές απαιτήσεις των web designers. Αναζητήσαμε την πιο φιλική σύνταξη, με την οποία μπορείτε να γράψετε κομψά ακόμα και κατασκευές που διαφορετικά αποτελούν πραγματικό γρίφο. Ταυτόχρονα, όλες οι εκφράσεις γράφονται ακριβώς όπως στην PHP, οπότε δεν χρειάζεται να μάθετε μια νέα γλώσσα. Απλά αξιοποιείτε ό,τι ήδη γνωρίζετε. -Ακολουθεί ένα ελάχιστο πρότυπο που απεικονίζει μερικά βασικά στοιχεία: ετικέτες, n:attributes, σχόλια και φίλτρα. +Παρακάτω παρουσιάζεται ένα ελάχιστο πρότυπο που απεικονίζει μερικά βασικά στοιχεία: tags, n:attributes, σχόλια και φίλτρα. ```latte {* αυτό είναι ένα σχόλιο *} -
                                            {* n:if is n:atribut *} +
                                              {* το n:if είναι ένα n:attribute *} {foreach $items as $item} {* tag που αντιπροσωπεύει τον βρόχο foreach *} -
                                            • {$item|capitalize}
                                            • {* ετικέτα που εκτυπώνει μια μεταβλητή με φίλτρο *} -{/foreach} {* τέλος του κύκλου *} +
                                            • {$item|capitalize}
                                            • {* tag που εκτυπώνει μια μεταβλητή με φίλτρο *} +{/foreach} {* τέλος βρόχου *}
                                            ``` -Ας ρίξουμε μια πιο προσεκτική ματιά σε αυτά τα σημαντικά στοιχεία και πώς μπορούν να σας βοηθήσουν να δημιουργήσετε ένα απίστευτο πρότυπο. +Ας δούμε πιο προσεκτικά αυτά τα σημαντικά στοιχεία και πώς μπορούν να σας βοηθήσουν να δημιουργήσετε ένα εκπληκτικό πρότυπο. -Ετικέτες .[#toc-tags] -===================== +Tags +==== -Ένα πρότυπο περιέχει ετικέτες που ελέγχουν τη λογική του προτύπου (για παράδειγμα, βρόχους *foreach*) ή εκφράσεις εξόδου. Και για τα δύο, χρησιμοποιείται ένας μόνο διαχωριστής `{ ... }`, οπότε δεν χρειάζεται να σκεφτείτε ποιο διαχωριστή να χρησιμοποιήσετε σε ποια περίπτωση, όπως συμβαίνει με άλλα συστήματα. -Αν ο χαρακτήρας `{` ακολουθείται από ένα εισαγωγικό ή ένα κενό, το Latte δεν θεωρεί ότι είναι η αρχή μιας ετικέτας, οπότε μπορείτε να χρησιμοποιήσετε δομές JavaScript, JSON ή κανόνες CSS στα πρότυπά σας χωρίς προβλήματα. +Το πρότυπο περιέχει tags που ελέγχουν τη λογική του προτύπου (για παράδειγμα, βρόχους *foreach*) ή εκτυπώνουν εκφράσεις. Και για τα δύο χρησιμοποιείται ένας μοναδικός οριοθέτης `{ ... }`, οπότε δεν χρειάζεται να σκέφτεστε ποιον οριοθέτη να χρησιμοποιήσετε σε ποια κατάσταση, όπως συμβαίνει σε άλλα συστήματα. Εάν μετά τον χαρακτήρα `{` ακολουθεί εισαγωγικό ή κενό, το Latte δεν το θεωρεί ως αρχή ενός tag, χάρη στο οποίο μπορείτε να χρησιμοποιείτε στα πρότυπα χωρίς προβλήματα και κατασκευές JavaScript, JSON ή κανόνες CSS. -Δείτε την [επισκόπηση όλων των ετικετών |tags]. Επιπλέον, μπορείτε επίσης να δημιουργήσετε [προσαρμοσμένες ετικέτες |extending-latte#tags]. +Δείτε την [επισκόπηση όλων των tags|tags]. Επιπλέον, μπορείτε να δημιουργήσετε και [προσαρμοσμένα tags|custom tags]. -Το Latte κατανοεί την PHP .[#toc-latte-understands-php] -======================================================= +Το Latte καταλαβαίνει PHP +========================= -Μπορείτε να χρησιμοποιήσετε εκφράσεις PHP που γνωρίζετε καλά μέσα στις ετικέτες: +Μέσα στα tags μπορείτε να χρησιμοποιείτε εκφράσεις PHP που γνωρίζετε καλά: - μεταβλητές -- συμβολοσειρές (συμπεριλαμβανομένων των HEREDOC και NOWDOC), πίνακες, αριθμοί, κ.λπ. +- strings (συμπεριλαμβανομένων HEREDOC και NOWDOC), πίνακες, αριθμούς, κ.λπ. - [τελεστές |https://www.php.net/manual/en/language.operators.php] -- κλήσεις συναρτήσεων και μεθόδων (οι οποίες μπορούν να περιοριστούν από [το sandbox |sandbox]) -- [αντιστοιχία |https://www.php.net/manual/en/control-structures.match.php] +- κλήσεις συναρτήσεων και μεθόδων (οι οποίες μπορούν να περιοριστούν με [sandbox|sandbox]) +- [match |https://www.php.net/manual/en/control-structures.match.php] - [ανώνυμες συναρτήσεις |https://www.php.net/manual/en/functions.arrow.php] -- [ανακλήσεις |https://www.php.net/manual/en/functions.first_class_callable_syntax.php] -- σχόλια πολλών γραμμών `/* ... */` -- κ.λπ... +- [callbacks |https://www.php.net/manual/en/functions.first_class_callable_syntax.php] +- σχόλια πολλαπλών γραμμών `/* ... */` +- κ.λπ… -Επιπλέον, η Latte προσθέτει αρκετές [ωραίες επεκτάσεις |#Syntactic Sugar] στη σύνταξη της PHP. +Επιπλέον, το Latte συμπληρώνει τη σύνταξη της PHP με μερικές [ευχάριστες επεκτάσεις |#Συντακτικό Ζαχαρούχο]. -n:attributes .[#toc-n-attributes] -================================= +n:attributes +============ -Κάθε ετικέτα ζεύγους, όπως το `{if} … {/if}`, που λειτουργεί σε ένα μόνο στοιχείο HTML μπορεί να γραφτεί με τον συμβολισμό [n:attribute |#n:attribute]. Για παράδειγμα, το `{foreach}` στο παραπάνω παράδειγμα θα μπορούσε επίσης να γραφτεί με αυτόν τον τρόπο: +Όλα τα ζευγαρωτά tags, για παράδειγμα `{if} … {/if}`, που λειτουργούν πάνω σε ένα στοιχείο HTML, μπορούν να ξαναγραφτούν με τη μορφή n:attributes. Με αυτόν τον τρόπο θα μπορούσε να γραφτεί για παράδειγμα και το `{foreach}` στο εισαγωγικό παράδειγμα: ```latte -
                                              +
                                              • {$item|capitalize}
                                              ``` -Η λειτουργικότητα αντιστοιχεί τότε στο στοιχείο HTML στο οποίο γράφεται: +Η λειτουργικότητα τότε σχετίζεται με το στοιχείο HTML στο οποίο τοποθετείται: ```latte {var $items = ['I', '♥', 'Latte']} @@ -65,7 +63,7 @@ n:attributes .[#toc-n-attributes]

                                              {$item}

                                              ``` -Εκτυπώσεις: +εκτυπώνει: ```latte

                                              I

                                              @@ -73,7 +71,7 @@ n:attributes .[#toc-n-attributes]

                                              Latte

                                              ``` -Με τη χρήση του προθέματος `inner-` μπορούμε να αλλάξουμε τη συμπεριφορά έτσι ώστε η λειτουργικότητα να ισχύει μόνο για το σώμα του στοιχείου: +Χρησιμοποιώντας το πρόθεμα `inner-`, μπορούμε να τροποποιήσουμε τη συμπεριφορά έτσι ώστε να σχετίζεται μόνο με το εσωτερικό μέρος του στοιχείου: ```latte
                                              @@ -82,7 +80,7 @@ n:attributes .[#toc-n-attributes]
                                              ``` -Εκτυπώσεις: +Εκτυπώνεται: ```latte
                                              @@ -95,178 +93,184 @@ n:attributes .[#toc-n-attributes]
                                              ``` -Η με τη χρήση του προθέματος `tag-` η λειτουργικότητα εφαρμόζεται μόνο στις ετικέτες HTML: +Ή χρησιμοποιώντας το πρόθεμα `tag-`, εφαρμόζουμε τη λειτουργικότητα μόνο στα ίδια τα HTML tags: ```latte -

                                              Title

                                              +

                                              Title

                                              ``` -Ανάλογα με την τιμή της μεταβλητής `$url` αυτό θα εκτυπωθεί: +Το οποίο εκτυπώνει ανάλογα με τη μεταβλητή `$url`: ```latte -// when $url is empty +{* όταν το $url είναι κενό *}

                                              Title

                                              -// when $url equals 'https://nette.org' +{* όταν το $url περιέχει 'https://nette.org' *}

                                              Title

                                              ``` -Ωστόσο, τα n:attributes δεν είναι μόνο μια συντόμευση για τις ετικέτες ζεύγους, υπάρχουν και κάποια καθαρά n:attributes, για παράδειγμα ο καλύτερος φίλος του κωδικοποιητή [n:class |tags#n:class]. +Ωστόσο, τα n:attributes δεν είναι απλώς μια συντομογραφία για ζευγαρωτά tags. Υπάρχουν και καθαρά n:attributes, όπως για παράδειγμα το [n:href |application:creating-links#Στο Πρότυπο του Presenter] ή ο πολύ χρήσιμος βοηθός του κωδικοποιητή [n:class |tags#n:class]. -Φίλτρα .[#toc-filters] -====================== +Φίλτρα +====== -Δείτε τη σύνοψη των [τυπικών φίλτρων |filters]. +Δείτε την επισκόπηση των [τυπικών φίλτρων |filters]. -Το Latte επιτρέπει την κλήση φίλτρων με τη χρήση του συμβολισμού με το σύμβολο του σωλήνα (επιτρέπεται το κενό που προηγείται): +Τα φίλτρα γράφονται μετά από μια κάθετη γραμμή (μπορεί να υπάρχει κενό πριν από αυτήν): ```latte

                                              {$heading|upper}

                                              ``` -Τα φίλτρα μπορούν να είναι αλυσιδωτά, οπότε εφαρμόζονται με τη σειρά από αριστερά προς τα δεξιά: +Τα φίλτρα μπορούν να συνδεθούν αλυσιδωτά και στη συνέχεια εφαρμόζονται με σειρά από αριστερά προς τα δεξιά: ```latte

                                              {$heading|lower|capitalize}

                                              ``` -Οι παράμετροι τοποθετούνται μετά το όνομα του φίλτρου χωρισμένες με άνω και κάτω τελεία ή κόμμα: +Οι παράμετροι εισάγονται μετά το όνομα του φίλτρου, χωρισμένες με άνω και κάτω τελείες ή κόμματα: ```latte

                                              {$heading|truncate:20,''}

                                              ``` -Τα φίλτρα μπορούν να εφαρμοστούν στην έκφραση: +Τα φίλτρα μπορούν επίσης να εφαρμοστούν σε μια έκφραση: ```latte {var $name = ($title|upper) . ($subtitle|lower)} ``` -Στο μπλοκάρισμα: +Σε ένα μπλοκ: ```latte

                                              {block |lower}{$heading}{/block}

                                              ``` -(σε συνδυασμό με [`{=expr}` | https://latte.nette.org/el/tags#printing] tag): +Ή απευθείας στην τιμή (σε συνδυασμό με το tag [`{=expr}` |tags#Εκτύπωση]): ```latte

                                              {=' Hello world '|trim}

                                              ``` -Σχόλια .[#toc-comments] -======================= +Δυναμικά HTML tags .{data-version:3.0.9} +======================================== -Τα σχόλια γράφονται με αυτόν τον τρόπο και δεν μπαίνουν στην έξοδο: +Το Latte υποστηρίζει δυναμικά HTML tags, τα οποία είναι χρήσιμα όταν χρειάζεστε ευελιξία στα ονόματα των tags: ```latte -{* Αυτό είναι ένα σχόλιο στο Latte *} +Heading ``` -Τα σχόλια της PHP λειτουργούν μέσα σε ετικέτες: +Ο παραπάνω κώδικας μπορεί για παράδειγμα να δημιουργήσει `

                                              Heading

                                              ` ή `

                                              Heading

                                              ` ανάλογα με την τιμή της μεταβλητής `$level`. Τα δυναμικά HTML tags στο Latte πρέπει πάντα να είναι ζευγαρωτά. Η εναλλακτική τους είναι το [n:tag |tags#n:tag]. + +Επειδή το Latte είναι ένα ασφαλές σύστημα προτύπων, ελέγχει αν το προκύπτον όνομα του tag είναι έγκυρο και δεν περιέχει ανεπιθύμητες ή κακόβουλες τιμές. Επιπλέον, διασφαλίζει ότι το όνομα του τελικού tag θα είναι πάντα το ίδιο με το όνομα του αρχικού tag. + + +Σχόλια +====== + +Τα σχόλια γράφονται με αυτόν τον τρόπο και δεν περιλαμβάνονται στην έξοδο: + +```latte +{* αυτό είναι ένα σχόλιο στο Latte *} +``` + +Μέσα στα tags λειτουργούν τα σχόλια PHP: ```latte {include 'file.info', /* value: 123 */} ``` -Συντακτική Ζάχαρη .[#toc-syntactic-sugar] -========================================= +Συντακτικό Ζαχαρούχο +==================== -Συμβολοσειρές χωρίς εισαγωγικά .[#toc-strings-without-quotation-marks] ----------------------------------------------------------------------- +Strings χωρίς εισαγωγικά +------------------------ -Τα εισαγωγικά μπορούν να παραλειφθούν για απλές συμβολοσειρές: +Για απλά strings, μπορείτε να παραλείψετε τα εισαγωγικά: ```latte -as in PHP: {var $arr = ['hello', 'btn--default', '€']} +όπως στην PHP: {var $arr = ['hello', 'btn--default', '€']} -abbreviated: {var $arr = [hello, btn--default, €]} +συντομογραφία: {var $arr = [hello, btn--default, €]} ``` -Οι απλές συμβολοσειρές είναι εκείνες που αποτελούνται αποκλειστικά από γράμματα, ψηφία, υπογράμμιση, παύλες και τελείες. Δεν πρέπει να αρχίζουν με ψηφίο και δεν πρέπει να αρχίζουν ή να τελειώνουν με παύλα. -Δεν πρέπει να αποτελείται μόνο από κεφαλαία γράμματα και υπογράμμιση, γιατί τότε θεωρείται σταθερά (π.χ. `PHP_VERSION`). -Και δεν πρέπει να συγκρούεται με τις λέξεις-κλειδιά `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor`. +Απλά strings είναι αυτά που αποτελούνται αποκλειστικά από γράμματα, αριθμούς, κάτω παύλες, παύλες και τελείες. Δεν πρέπει να ξεκινούν με αριθμό και δεν πρέπει να ξεκινούν ή να τελειώνουν με παύλα. Δεν πρέπει να αποτελούνται μόνο από κεφαλαία γράμματα και κάτω παύλες, γιατί τότε θεωρούνται σταθερές (π.χ. `PHP_VERSION`). Και δεν πρέπει να συγκρούονται με τις λέξεις-κλειδιά: `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor`. -Σύντομος τριμερής τελεστής .[#toc-short-ternary-operator] ---------------------------------------------------------- +Σταθερές +-------- -Εάν η τρίτη τιμή του τριμερούς τελεστή είναι κενή, μπορεί να παραλειφθεί: +Επειδή μπορείτε να παραλείψετε τα εισαγωγικά για απλά strings, συνιστούμε να γράφετε τις καθολικές σταθερές με μια κάθετο στην αρχή για διάκριση: ```latte -as in PHP: {$stock ? 'In stock' : ''} - -abbreviated: {$stock ? 'In stock'} +{if \PROJECT_ID === 1} ... {/if} ``` +Αυτή η γραφή είναι απολύτως έγκυρη στην ίδια την PHP, η κάθετος δηλώνει ότι η σταθερά βρίσκεται στο καθολικό namespace. -Σύγχρονος συμβολισμός κλειδιών στη συστοιχία .[#toc-modern-key-notation-in-the-array] -------------------------------------------------------------------------------------- -Τα κλειδιά της συστοιχίας μπορούν να γραφούν παρόμοια με τις ονομαστικές παραμέτρους κατά την κλήση συναρτήσεων: +Συντομογραφημένος τριαδικός τελεστής +------------------------------------ + +Εάν η τρίτη τιμή του τριαδικού τελεστή είναι κενή, μπορεί να παραλειφθεί: ```latte -as in PHP: {var $arr = ['one' => 'item 1', 'two' => 'item 2']} +όπως στην PHP: {$stock ? 'Σε απόθεμα' : ''} -modern: {var $arr = [one: 'item 1', two: 'item 2']} +συντομογραφία: {$stock ? 'Σε απόθεμα'} ``` -Φίλτρα .[#toc-filters] ----------------------- +Σύγχρονη γραφή κλειδιών σε πίνακα +--------------------------------- -Τα φίλτρα μπορούν να χρησιμοποιηθούν για οποιαδήποτε έκφραση, απλά περικλείστε το σύνολο σε αγκύλες: +Τα κλειδιά σε έναν πίνακα μπορούν να γραφτούν παρόμοια με τις ονομασμένες παραμέτρους κατά την κλήση συναρτήσεων: ```latte -{var $content = ($text|truncate: 30|upper)} +όπως στην PHP: {var $arr = ['one' => 'item 1', 'two' => 'item 2']} + +μοντέρνα: {var $arr = [one: 'item 1', two: 'item 2']} ``` -Χειριστής `in` .[#toc-operator-in] ----------------------------------- +Φίλτρα +------ -Ο τελεστής `in` μπορεί να χρησιμοποιηθεί για την αντικατάσταση της συνάρτησης `in_array()`. Η σύγκριση είναι πάντα αυστηρή: +Τα φίλτρα μπορούν να χρησιμοποιηθούν για οποιεσδήποτε εκφράσεις, αρκεί να περικλείσετε το σύνολο σε παρενθέσεις: ```latte -{* όπως in_array($item, $items, true) *} -{if $item in $items} - ... -{/if} +{var $content = ($text|truncate: 30|upper)} ``` -.{data-version:2.9} -Προαιρετική αλυσιδωτή σύνδεση με μη καθορισμένο-ασφαλή τελεστή .[#toc-optional-chaining-with-undefined-safe-operator] ---------------------------------------------------------------------------------------------------------------------- +Τελεστής `in` +------------- -Ο undefined-safe τελεστής `??->` είναι παρόμοιος με τον nullsafe τελεστή `?->`, αλλά δεν δημιουργεί σφάλμα αν μια μεταβλητή, ιδιότητα ή δείκτης δεν υπάρχει καθόλου. +Ο τελεστής `in` μπορεί να αντικαταστήσει τη συνάρτηση `in_array()`. Η σύγκριση είναι πάντα αυστηρή: ```latte -{$order??->id} +{* παρόμοιο με in_array($item, $items, true) *} +{if $item in $items} + ... +{/if} ``` -αυτός είναι ένας τρόπος να πούμε ότι όταν το `$order` είναι ορισμένο και δεν είναι null, το `$order->id` θα υπολογιστεί, αλλά όταν το `$order` είναι null ή δεν υπάρχει, σταματάμε αυτό που κάνουμε και απλά επιστρέφουμε null. - -```latte -{$user??->address??->street} -// roughly means isset($user) && isset($user->address) ? $user->address->street : null -``` +Ιστορικό Παράθυρο +----------------- -Ένα παράθυρο στην ιστορία .[#toc-a-window-into-history] -------------------------------------------------------- +Το Latte εισήγαγε κατά τη διάρκεια της ιστορίας του μια ολόκληρη σειρά συντακτικών ζαχαρούχων, τα οποία μετά από μερικά χρόνια εμφανίστηκαν στην ίδια την PHP. Για παράδειγμα, στο Latte ήταν δυνατό να γράφονται πίνακες ως `[1, 2, 3]` αντί για `array(1, 2, 3)` ή να χρησιμοποιείται ο nullsafe τελεστής `$obj?->foo` πολύ πριν αυτό γίνει δυνατό στην ίδια την PHP. Το Latte εισήγαγε επίσης τον τελεστή για την επέκταση πίνακα `(expand) $arr`, ο οποίος είναι ισοδύναμος με τον σημερινό τελεστή `...$arr` της PHP. -Η Latte έχει επινοήσει μια σειρά από συντακτικές καραμέλες κατά τη διάρκεια της ιστορίας της, οι οποίες εμφανίστηκαν στην ίδια την PHP λίγα χρόνια αργότερα. Για παράδειγμα, στη Latte ήταν δυνατό να γράψετε πίνακες ως `[1, 2, 3]` αντί για `array(1, 2, 3)` ή να χρησιμοποιήσετε τον τελεστή nullsafe `$obj?->foo` πολύ πριν αυτό καταστεί δυνατό στην ίδια την PHP. Η Latte εισήγαγε επίσης τον τελεστή επέκτασης πινάκων `(expand) $arr`, ο οποίος είναι ο αντίστοιχος του σημερινού τελεστή `...$arr` από την PHP. +Ο Undefined-safe τελεστής `??->`, ο οποίος είναι ανάλογος του nullsafe τελεστή `?->`, αλλά δεν προκαλεί σφάλμα εάν η μεταβλητή δεν υπάρχει, δημιουργήθηκε για ιστορικούς λόγους και σήμερα συνιστούμε τη χρήση του τυπικού τελεστή PHP `?->`. -Περιορισμοί της PHP στη Latte .[#toc-php-limitations-in-latte] -============================================================== +Περιορισμοί της PHP στο Latte +============================= -Μόνο εκφράσεις PHP μπορούν να γραφτούν στο Latte. Δηλαδή, δεν μπορείτε να δηλώσετε κλάσεις ή να χρησιμοποιήσετε [δομές ελέγχου |https://www.php.net/manual/en/language.control-structures.php], όπως `if`, `foreach`, `switch`, `return`, `try`, `throw` και άλλες, αντί των οποίων το Latte προσφέρει τις [ετικέτες |tags] του. -Επίσης, δεν μπορείτε να χρησιμοποιήσετε [χαρακτηριστικά |https://www.php.net/manual/en/language.attributes.php], [backticks |https://www.php.net/manual/en/language.operators.execution.php] ή [μαγικές σταθερές |https://www.php.net/manual/en/language.constants.magic.php], γιατί αυτό δεν θα είχε νόημα. -Δεν μπορείτε καν να χρησιμοποιήσετε τα `unset`, `echo`, `include`, `require`, `exit`, `eval`, επειδή δεν είναι συναρτήσεις, αλλά ειδικές κατασκευές της γλώσσας PHP, και επομένως δεν είναι εκφράσεις. +Στο Latte μπορούν να γραφτούν μόνο εκφράσεις PHP. Δηλαδή, δεν μπορούν να χρησιμοποιηθούν εντολές που τερματίζονται με ερωτηματικό. Δεν μπορούν να δηλωθούν κλάσεις ή να χρησιμοποιηθούν [δομές ελέγχου |https://www.php.net/manual/en/language.control-structures.php], π.χ. `if`, `foreach`, `switch`, `return`, `try`, `throw` και άλλες, αντί των οποίων το Latte προσφέρει τα δικά του [tags|tags]. Επίσης, δεν μπορούν να χρησιμοποιηθούν [attributes |https://www.php.net/manual/en/language.attributes.php], [backticks |https://www.php.net/manual/en/language.operators.execution.php] ή ορισμένες [μαγικές σταθερές |https://www.php.net/manual/en/language.constants.magic.php]. Δεν μπορούν να χρησιμοποιηθούν ούτε τα `unset`, `echo`, `include`, `require`, `exit`, `eval`, επειδή δεν πρόκειται για συναρτήσεις, αλλά για ειδικές γλωσσικές κατασκευές της PHP, και επομένως δεν είναι εκφράσεις. Τα σχόλια υποστηρίζονται μόνο ως σχόλια πολλαπλών γραμμών `/* ... */`. -Ωστόσο, μπορείτε να παρακάμψετε αυτούς τους περιορισμούς ενεργοποιώντας την επέκταση [RawPhpExtension |develop#RawPhpExtension], η οποία σας επιτρέπει να χρησιμοποιείτε οποιονδήποτε κώδικα PHP στην ετικέτα `{php ...}` με ευθύνη του συντάκτη του προτύπου. +Ωστόσο, αυτοί οι περιορισμοί μπορούν να παρακαμφθούν ενεργοποιώντας την επέκταση [RawPhpExtension |develop#RawPhpExtension], χάρη στην οποία μπορεί στη συνέχεια να χρησιμοποιηθεί στο tag `{php ...}` οποιοσδήποτε κώδικας PHP με ευθύνη του συγγραφέα του προτύπου. diff --git a/latte/el/tags.texy b/latte/el/tags.texy index 4194a34de9..b95867d7c8 100644 --- a/latte/el/tags.texy +++ b/latte/el/tags.texy @@ -1,168 +1,174 @@ -Ετικέτες Latte +Tags του Latte ************** .[perex] -Περίληψη και περιγραφή όλων των ενσωματωμένων ετικετών Latte. +Επισκόπηση και περιγραφή όλων των tags του συστήματος προτύπων Latte, τα οποία είναι διαθέσιμα από προεπιλογή. .[table-latte-tags language-latte] |## Εκτύπωση -| `{$var}`, `{...}` ή `{=...}` | [εκτυπώνει μια μεταβλητή ή έκφραση με διαφυγή |#printing] -| `{$var\|filter}` | [εκτυπώνει με φίλτρα |#filters] -| `{l}` ή `{r}` | εκτυπώνει χαρακτήρα `{` or `}` +| `{$var}`, `{...}` ή `{=...}` | [εκτυπώνει μια μεταβλητή ή έκφραση με διαφυγή |#Εκτύπωση] +| `{$var\|filter}` | [εκτυπώνει με χρήση φίλτρων |#Φίλτρα] +| `{l}` ή `{r}` | εκτυπώνει τον χαρακτήρα `{` ή `}` .[table-latte-tags language-latte] |## Συνθήκες -| `{if}`... `{elseif}`... `{else}`... `{/if}` | [condition if |#if-elseif-else] -| `{ifset}`... `{elseifset}`... `{/ifset}` | [συνθήκη ifset |#ifset-elseifset] -| `{ifchanged}`... `{/ifchanged}` | [test if there has been a change |#ifchanged] -| `{switch}` `{case}` `{default}` `{/switch}` | [condition switch |#switch-case-default] +| `{if}` … `{elseif}` … `{else}` … `{/if}` | [συνθήκη if |#if elseif else] +| `{ifset}` … `{elseifset}` … `{/ifset}` | [συνθήκη ifset |#ifset elseifset] +| `{ifchanged}` … `{/ifchanged}` | [έλεγχος αν υπήρξε αλλαγή |#ifchanged] +| `{switch}` `{case}` `{default}` `{/switch}` | [συνθήκη switch |#switch case default] +| `n:else` | [εναλλακτικό περιεχόμενο για συνθήκες |#n:else] .[table-latte-tags language-latte] |## Βρόχοι -| `{foreach}`... `{/foreach}` | [foreach |#foreach] -| `{for}`... `{/for}` | [for |#for] -| `{while}`... `{/while}` | [while |#while] -| `{continueIf $cond}` | [συνέχεια στην επόμενη επανάληψη |#continueif-skipif-breakif] -| `{skipIf $cond}` | [παράλειψη της τρέχουσας επανάληψης του βρόχου |#continueif-skipif-breakif] -| `{breakIf $cond}` | [διακόπτει το βρόχο |#continueif-skipif-breakif] -| `{exitIf $cond}` | [πρόωρη έξοδος |#exitif] -| `{first}`... `{/first}` | [είναι η πρώτη επανάληψη; |#first-last-sep] -| `{last}`... `{/last}` | [είναι η τελευταία επανάληψη; |#first-last-sep] -| `{sep}`... `{/sep}` | [θα ακολουθήσει επόμενη επανάληψη; |#first-last-sep] -| `{iterateWhile}`... `{/iterateWhile}` | [structured foreach |#iterateWhile] -| `$iterator` | [ειδική μεταβλητή μέσα στο βρόχο foreach |#$iterator] +| `{foreach}` … `{/foreach}` | [#foreach] +| `{for}` … `{/for}` | [#for] +| `{while}` … `{/while}` | [#while] +| `{continueIf $cond}` | [συνέχεια στην επόμενη επανάληψη |#continueIf skipIf breakIf] +| `{skipIf $cond}` | [παράλειψη επανάληψης |#continueIf skipIf breakIf] +| `{breakIf $cond}` | [διακοπή βρόχου |#continueIf skipIf breakIf] +| `{exitIf $cond}` | [πρόωρη λήξη |#exitIf] +| `{first}` … `{/first}` | [είναι η πρώτη διέλευση; |#first last sep] +| `{last}` … `{/last}` | [είναι η τελευταία διέλευση; |#first last sep] +| `{sep}` … `{/sep}` | [θα ακολουθήσει άλλη διέλευση; |#first last sep] +| `{iterateWhile}` … `{/iterateWhile}` | [δομημένος foreach |#iterateWhile] +| `$iterator` | [ειδική μεταβλητή μέσα στο foreach |#iterator] .[table-latte-tags language-latte] -|## Συμπερίληψη άλλων προτύπων -| `{include 'file.latte'}` | [περιλαμβάνει ένα πρότυπο από άλλο αρχείο |#include] -| `{sandbox 'file.latte'}` | [περιλαμβάνει ένα πρότυπο σε λειτουργία sandbox |#sandbox] +|## Εισαγωγή άλλων προτύπων +| `{include 'file.latte'}` | [φορτώνει ένα πρότυπο από άλλο αρχείο |#include] +| `{sandbox 'file.latte'}` | [φορτώνει ένα πρότυπο σε λειτουργία sandbox |#sandbox] .[table-latte-tags language-latte] -|## Μπλοκ, διατάξεις, κληρονομικότητα προτύπων -| `{block}` | [ανώνυμο μπλοκ |#block] -| `{block blockname}` | [ορισμός μπλοκ |template-inheritance#blocks] -| `{define blockname}` | [ορισμός μπλοκ για μελλοντική χρήση |template-inheritance#definitions] -| `{include blockname}` | [εκτυπώσεις μπλοκ |template-inheritance#printing-blocks] -| `{include blockname from 'file.latte'}` | [εκτυπώνει ένα μπλοκ από αρχείο |template-inheritance#printing-blocks] -| `{import 'file.latte'}` | [φορτώνει μπλοκ από άλλο πρότυπο |template-inheritance#horizontal-reuse] -| `{layout 'file.latte'}` / `{extends}` | [καθορίζει ένα αρχείο διάταξης |template-inheritance#layout-inheritance] -| `{embed}`... `{/embed}` | [φορτώνει το πρότυπο ή το μπλοκ και σας επιτρέπει να αντικαταστήσετε τα μπλοκ |template-inheritance#unit-inheritance] -| `{ifset blockname}`... `{/ifset}` | [συνθήκη αν έχει οριστεί μπλοκ |template-inheritance#checking-block-existence] +|## Μπλοκ, layouts, κληρονομικότητα προτύπων +| `{block}` | [ανώνυμο μπλοκ |#block] +| `{block blockname}` | [ορίζει ένα μπλοκ |template-inheritance#Μπλοκ block] +| `{define blockname}` | [ορίζει ένα μπλοκ για μεταγενέστερη χρήση |template-inheritance#Ορισμοί define] +| `{include blockname}` | [απόδοση μπλοκ |template-inheritance#Απόδοση μπλοκ include] +| `{include blockname from 'file.latte'}` | [αποδίδει ένα μπλοκ από ένα αρχείο |template-inheritance#Απόδοση μπλοκ include] +| `{import 'file.latte'}` | [φορτώνει μπλοκ από ένα πρότυπο |template-inheritance#Οριζόντια επαναχρησιμοποίηση import] +| `{layout 'file.latte'}` / `{extends}` | [καθορίζει το αρχείο με το layout |template-inheritance#Κληρονομικότητα διάταξης layout] +| `{embed}` … `{/embed}` | [φορτώνει ένα πρότυπο ή μπλοκ και επιτρέπει την αντικατάσταση μπλοκ |template-inheritance#Κληρονομικότητα μονάδας embed] +| `{ifset blockname}` … `{/ifset}` | [συνθήκη, εάν υπάρχει μπλοκ |template-inheritance#Έλεγχος ύπαρξης μπλοκ ifset] .[table-latte-tags language-latte] -|## Χειρισμός εξαιρέσεων -| `{try}`... `{else}`... `{/try}` | [αντιμετώπιση εξαιρέσεων |#try] -| `{rollback}` | [απορρίπτει το μπλοκ try |#rollback] +|## Διαχείριση εξαιρέσεων +| `{try}` … `{else}` … `{/try}` | [παγίδευση εξαιρέσεων |#try] +| `{rollback}` | [απόρριψη μπλοκ try |#rollback] .[table-latte-tags language-latte] |## Μεταβλητές -| `{var $foo = value}` | [δημιουργία μεταβλητών |#var-default] -| `{default $foo = value}` | [προεπιλεγμένη τιμή όταν η μεταβλητή δεν έχει δηλωθεί |#var-default] -| `{parameters}` | [δηλώνει μεταβλητές, τύπους και προεπιλεγμένες τιμές |#parameters] -| `{capture}`... `{/capture}` | [αποτυπώνει ένα τμήμα σε μια μεταβλητή |#capture] +| `{var $foo = value}` | [δημιουργεί μια μεταβλητή |#var default] +| `{default $foo = value}` | [δημιουργεί μια μεταβλητή, εάν δεν υπάρχει |#var default] +| `{parameters}` | [δηλώνει μεταβλητές, τύπους και προεπιλεγμένες τιμές |#parameters] +| `{capture}` … `{/capture}` | [συλλαμβάνει ένα μπλοκ σε μια μεταβλητή |#capture] .[table-latte-tags language-latte] |## Τύποι -| `{varType}` | [δηλώνει τον τύπο της μεταβλητής |type-system#varType] -| `{varPrint}` | [προτείνει τύπους μεταβλητών |type-system#varPrint] -| `{templateType}` | [δηλώνει τύπους μεταβλητών χρησιμοποιώντας κλάση |type-system#templateType] -| `{templatePrint}` | [δημιουργεί κλάση με ιδιότητες |type-system#templatePrint] +| `{varType}` | [δηλώνει τον τύπο μιας μεταβλητής |type-system#varType] +| `{varPrint}` | [προτείνει τύπους μεταβλητών |type-system#varPrint] +| `{templateType}` | [δηλώνει τύπους μεταβλητών σύμφωνα με μια κλάση |type-system#templateType] +| `{templatePrint}` | [προτείνει μια κλάση με τύπους μεταβλητών |type-system#templatePrint] .[table-latte-tags language-latte] -|## Μετάφραση -| `{_string}` | [εκτυπώνει μεταφρασμένα |#Translation] -| `{translate}`... `{/translate}` | [μεταφράζει το περιεχόμενο |#Translation] +|## Μεταφράσεις +| `{_...}` | [εκτυπώνει τη μετάφραση |#Μεταφράσεις] +| `{translate}` … `{/translate}` | [μεταφράζει το περιεχόμενο |#Μεταφράσεις] .[table-latte-tags language-latte] -|## Άλλοι -| `{contentType}` | [αλλάζει τον τρόπο διαφυγής και στέλνει επικεφαλίδα HTTP |#contenttype] -| `{debugbreak}` | [θέτει σημείο διακοπής στον κώδικα |#debugbreak] -| `{do}` | [αξιολογεί μια έκφραση χωρίς να την εκτυπώσει |#do] -| `{dump}` | [Απορρίπτει μεταβλητές στη γραμμή Tracy Bar|#dump] -| `{spaceless}`... `{/spaceless}` | [αφαιρεί τα περιττά κενά διαστήματα |#spaceless] -| `{syntax}` | [αλλάζει τη σύνταξη κατά την εκτέλεση |#syntax] -| `{trace}` | [εμφανίζει ίχνος στοίβας |#trace] +|## Άλλα +| `{contentType}` | [αλλάζει το escaping και στέλνει την κεφαλίδα HTTP |#contentType] +| `{debugbreak}` | [τοποθετεί ένα breakpoint στον κώδικα |#debugbreak] +| `{do}` | [εκτελεί κώδικα, αλλά δεν εκτυπώνει τίποτα |#do] +| `{dump}` | [κάνει dump μεταβλητών στο Tracy Bar |#dump] +| `{php}` | [εκτελεί οποιονδήποτε κώδικα PHP |#php] +| `{spaceless}` … `{/spaceless}` | [αφαιρεί τα περιττά κενά |#spaceless] +| `{syntax}` | [αλλαγή σύνταξης κατά το χρόνο εκτέλεσης |#syntax] +| `{trace}` | [εμφανίζει το stack trace |#trace] .[table-latte-tags language-latte] -|## Βοηθοί ετικετών HTML -| `n:class` | [έξυπνο χαρακτηριστικό κλάσης |#n:class] -| `n:attr` | [έξυπνα χαρακτηριστικά HTML |#n:attr] -| `n:tag` | [δυναμικό όνομα στοιχείου HTML |#n:tag] -| `n:ifcontent` | [Παράλειψη κενής ετικέτας HTML |#n:ifcontent] +|## Βοηθοί κωδικοποιητή HTML +| `n:class` | [δυναμική γραφή του attribute class HTML |#n:class] +| `n:attr` | [δυναμική γραφή οποιωνδήποτε attributes HTML |#n:attr] +| `n:tag` | [δυναμική γραφή του ονόματος του στοιχείου HTML |#n:tag] +| `n:ifcontent` | [παραλείπει το κενό tag HTML |#n:ifcontent] .[table-latte-tags language-latte] |## Διαθέσιμο μόνο στο Nette Framework -| `n:href` | [σύνδεσμος σε στοιχεία HTML `` |application:creating-links#In the Presenter Template] -| `{link}` | [εκτυπώνει έναν σύνδεσμο |application:creating-links#In the Presenter Template] -| `{plink}` | [εκτυπώνει έναν σύνδεσμο προς έναν παρουσιαστή |application:creating-links#In the Presenter Template] -| `{control}` | [εκτυπώνει ένα στοιχείο |application:components#Rendering] -| `{snippet}`... `{/snippet}` | [ένα απόσπασμα προτύπου που μπορεί να σταλεί μέσω AJAX |application:ajax#tag-snippet] -| `{snippetArea}` | φάκελος αποσπασμάτων -| `{cache}`... `{/cache}` | [προσωρινή αποθήκευση ενός τμήματος προτύπου |caching:en#caching-in-latte] +| `n:href` | [σύνδεσμος που χρησιμοποιείται στα στοιχεία HTML `` |application:creating-links#Στο Πρότυπο του Presenter] +| `{link}` | [εκτυπώνει έναν σύνδεσμο |application:creating-links#Στο Πρότυπο του Presenter] +| `{plink}` | [εκτυπώνει έναν σύνδεσμο προς έναν presenter |application:creating-links#Στο Πρότυπο του Presenter] +| `{control}` | [αποδίδει ένα component |application:components#Απόδοση] +| `{snippet}` … `{/snippet}` | [απόσπασμα που μπορεί να σταλεί με AJAX |application:ajax#Snippets στο Latte] +| `{snippetArea}` | [περιτύλιγμα για αποσπάσματα |application:ajax#Περιοχές Snippet] +| `{cache}` … `{/cache}` | [κάνει cache ένα μέρος του προτύπου |caching:#Caching στο Latte] .[table-latte-tags language-latte] -|## Διαθέσιμο μόνο με Nette Forms -| `{form}`... `{/form}` | [εκτυπώνει ένα στοιχείο φόρμας |forms:rendering#form] -| `{label}`... `{/label}` | [εκτυπώνει μια ετικέτα εισόδου φόρμας |forms:rendering#label-input] -| `{input}` | [εκτυπώνει ένα στοιχείο εισόδου φόρμας |forms:rendering#label-input] -| `{inputError}` | [εκτυπώνει μήνυμα σφάλματος για το στοιχείο εισόδου φόρμας |forms:rendering#inputError] -| `n:name` | [ενεργοποιεί ένα στοιχείο εισόδου HTML |forms:rendering#n:name] -| `{formPrint}` | [δημιουργεί σχέδιο φόρμας Latte |forms:rendering#formPrint] -| `{formPrintClass}` | [εκτυπώνει κλάση PHP για τα δεδομένα της φόρμας |forms:in-presenter#mapping-to-classes] -| `{formContext}`... `{/formContext}` | [μερική απόδοση φόρμας |forms:rendering#special-cases] +|## Διαθέσιμο μόνο με το Nette Forms +| `{form}` … `{/form}` | [αποδίδει τα tags της φόρμας |forms:rendering#form] +| `{label}` … `{/label}` | [αποδίδει την ετικέτα ενός στοιχείου φόρμας |forms:rendering#label input] +| `{input}` | [αποδίδει ένα στοιχείο φόρμας |forms:rendering#label input] +| `{inputError}` | [εκτυπώνει το μήνυμα σφάλματος ενός στοιχείου φόρμας |forms:rendering#inputError] +| `n:name` | [ζωντανεύει ένα στοιχείο φόρμας |forms:rendering#n:name] +| `{formContainer}` … `{/formContainer}` | [απόδοση ενός container φόρμας |forms:rendering#Ειδικές περιπτώσεις] + +.[table-latte-tags language-latte] +|## Διαθέσιμο μόνο με Nette Assets +| `{asset}` | [αποδίδει ένα περιουσιακό στοιχείο ως στοιχείο HTML ή URL |assets:#asset] +| `{preload}` | [δημιουργεί υποδείξεις προφόρτωσης για βελτιστοποίηση της απόδοσης |assets:#preload] +| `n:asset` | [προσθέτει χαρακτηριστικά περιουσιακών στοιχείων σε στοιχεία HTML |assets:#n:asset] -Εκτύπωση .[#toc-printing] -========================= +Εκτύπωση +======== `{$var}` `{...}` `{=...}` ------------------------- -Η Latte χρησιμοποιεί την ετικέτα `{=...}` για να εκτυπώσει οποιαδήποτε έκφραση στην έξοδο. Εάν η έκφραση αρχίζει με μια μεταβλητή ή κλήση συνάρτησης, δεν χρειάζεται να γράψετε ένα σύμβολο ισότητας. Το οποίο στην πράξη σημαίνει ότι δεν χρειάζεται σχεδόν ποτέ να γραφτεί: +Στο Latte, χρησιμοποιείται το tag `{=...}` για την εκτύπωση οποιασδήποτε έκφρασης στην έξοδο. Το Latte ενδιαφέρεται για την άνεσή σας, οπότε αν η έκφραση ξεκινά με μια μεταβλητή ή μια κλήση συνάρτησης, δεν χρειάζεται να γράψετε το σύμβολο ίσον. Αυτό στην πράξη σημαίνει ότι σχεδόν ποτέ δεν χρειάζεται να το γράψετε: ```latte -Name: {$name} {$surname}
                                              -Age: {date('Y') - $birth}
                                              +Όνομα: {$name} {$surname}
                                              +Ηλικία: {date('Y') - $birth}
                                              ``` -Μπορείτε να γράψετε οτιδήποτε γνωρίζετε από την PHP ως έκφραση. Απλά δεν χρειάζεται να μάθετε μια νέα γλώσσα. Για παράδειγμα: +Ως έκφραση μπορείτε να γράψετε οτιδήποτε γνωρίζετε από την PHP. Δεν χρειάζεται λοιπόν να μάθετε μια νέα γλώσσα. Έτσι, για παράδειγμα: ```latte {='0' . ($num ?? $num * 3) . ', ' . PHP_VERSION} ``` -αλλά αν βρείτε κάποιο εκεί, γράψτε μας :-) +Παρακαλώ, μην ψάχνετε για νόημα στο προηγούμενο παράδειγμα, αλλά αν βρείτε κάποιο, γράψτε μας :-) -Διαφυγή εξόδου .[#toc-escaping-output] --------------------------------------- +Escaping εξόδου +--------------- -Ποια είναι η πιο σημαντική εργασία ενός συστήματος προτύπων; Η αποφυγή κενών ασφαλείας. Και αυτό ακριβώς κάνει το Latte κάθε φορά που εκτυπώνετε κάτι στην έξοδο. Αποφεύγει αυτόματα τα πάντα: +Ποιο είναι το πιο σημαντικό καθήκον ενός συστήματος προτύπων; Να αποτρέπει τα κενά ασφαλείας. Και ακριβώς αυτό κάνει το Latte πάντα, όταν εκτυπώνετε κάτι. Το κάνει escape αυτόματα: ```latte -

                                              {='one < two'}

                                              {* prints: '

                                              one < two

                                              ' *} +

                                              {='one < two'}

                                              {* εκτυπώνει: '

                                              one < two

                                              ' *} ``` -Για να είμαστε ακριβείς, το Latte χρησιμοποιεί το context-sensitive escaping, το οποίο είναι ένα τόσο σημαντικό και μοναδικό χαρακτηριστικό που του έχουμε αφιερώσει [ένα ξεχωριστό κεφάλαιο |safety-first#context-aware-escaping]. +Για να είμαστε ακριβείς, το Latte χρησιμοποιεί context-aware escaping, το οποίο είναι τόσο σημαντικό και μοναδικό πράγμα, που του αφιερώσαμε [ξεχωριστό κεφάλαιο |safety-first#Context-Aware Escaping]. -Και αν εκτυπώνετε περιεχόμενο κωδικοποιημένο σε HTML από αξιόπιστη πηγή; Τότε μπορείτε εύκολα να απενεργοποιήσετε τη διαφυγή: +Και τι γίνεται αν εκτυπώνετε περιεχόμενο κωδικοποιημένο σε HTML από αξιόπιστη πηγή; Τότε μπορείτε εύκολα να απενεργοποιήσετε το escaping: ```latte {$trustedHtmlString|noescape} ``` .[warning] -Η κατάχρηση του φίλτρου `noescape` μπορεί να οδηγήσει σε ευπάθεια XSS! Ποτέ μην το χρησιμοποιείτε, εκτός αν είστε **απολύτως σίγουροι** για το τι κάνετε και ότι η συμβολοσειρά που εκτυπώνετε προέρχεται από αξιόπιστη πηγή. +Η λανθασμένη χρήση του φίλτρου `noescape` μπορεί να οδηγήσει σε ευπάθεια XSS! Ποτέ μην το χρησιμοποιείτε αν δεν είστε **απολύτως σίγουροι** για το τι κάνετε και ότι το string που εκτυπώνεται προέρχεται από αξιόπιστη πηγή. -Εκτύπωση σε JavaScript .[#toc-printing-in-javascript] ------------------------------------------------------ +Εκτύπωση σε JavaScript +---------------------- -Χάρη στο context-sensitive escaping, είναι θαυμάσια εύκολο να εκτυπώσετε μεταβλητές μέσα στη JavaScript, και το Latte θα τις αποφύγει σωστά. +Χάρη στο context-aware escaping, είναι εκπληκτικά εύκολο να εκτυπώνετε μεταβλητές μέσα σε JavaScript και το σωστό escaping το αναλαμβάνει το Latte. -Η μεταβλητή δεν χρειάζεται να είναι συμβολοσειρά, υποστηρίζεται οποιοσδήποτε τύπος δεδομένων, ο οποίος στη συνέχεια κωδικοποιείται ως JSON: +Η μεταβλητή δεν χρειάζεται να είναι μόνο string, υποστηρίζεται οποιοσδήποτε τύπος δεδομένων, ο οποίος στη συνέχεια κωδικοποιείται ως JSON: ```latte {var $foo = ['hello', true, 1]} @@ -171,7 +177,7 @@ Age: {date('Y') - $birth}
                                              ``` -Παράγει: +Δημιουργεί: ```latte ``` -Μην βάζετε τη μεταβλητή σε εισαγωγικά**: Latte τα προσθέτει γύρω από τις συμβολοσειρές. Και αν θέλετε να βάλετε μια μεταβλητή συμβολοσειράς μέσα σε μια άλλη συμβολοσειρά, απλά συνδέστε τις: +Αυτός είναι επίσης ο λόγος για τον οποίο **δεν γράφονται εισαγωγικά** γύρω από τη μεταβλητή: το Latte τα προσθέτει αυτόματα για τα strings. Και αν θέλετε να εισάγετε μια μεταβλητή string σε ένα άλλο string, απλά συνδέστε τα: ```latte ``` -Φίλτρα .[#toc-filters] ----------------------- +Φίλτρα +------ -Η εκτυπωμένη έκφραση μπορεί να τροποποιηθεί [με φίλτρα |syntax#filters]. Για παράδειγμα, αυτό το παράδειγμα μετατρέπει τη συμβολοσειρά σε κεφαλαία γράμματα και τη συντομεύει σε 30 χαρακτήρες το πολύ: +Η εκτυπωμένη έκφραση μπορεί να τροποποιηθεί με [φίλτρα |syntax#Φίλτρα]. Έτσι, για παράδειγμα, μετατρέπουμε ένα string σε κεφαλαία γράμματα και το περικόπτουμε σε μέγιστο 30 χαρακτήρες: ```latte {$string|upper|truncate:30} ``` -Μπορείτε επίσης να εφαρμόσετε φίλτρα σε τμήματα μιας έκφρασης ως εξής: +Μπορείτε να χρησιμοποιείτε φίλτρα και σε επιμέρους τμήματα της έκφρασης με αυτόν τον τρόπο: ```latte {$left . ($middle|upper) . $right} ``` -Συνθήκες .[#toc-conditions] -=========================== +Συνθήκες +======== `{if}` `{elseif}` `{else}` -------------------------- -Οι συνθήκες συμπεριφέρονται με τον ίδιο τρόπο όπως οι αντίστοιχες της PHP. Μπορείτε να χρησιμοποιήσετε τις ίδιες εκφράσεις που γνωρίζετε από την PHP, δεν χρειάζεται να μάθετε μια νέα γλώσσα. +Οι συνθήκες συμπεριφέρονται ακριβώς όπως οι αντίστοιχές τους στην PHP. Μπορείτε να χρησιμοποιείτε σε αυτές και τις ίδιες εκφράσεις που γνωρίζετε από την PHP, δεν χρειάζεται να μάθετε μια νέα γλώσσα. ```latte {if $product->inStock > Stock::Minimum} - In stock + Σε απόθεμα {elseif $product->isOnWay()} - On the way + Στο δρόμο {else} - Not available + Μη διαθέσιμο {/if} ``` -Όπως κάθε ετικέτα pair, ένα ζεύγος `{if} ... {/ if}` μπορεί να γραφτεί ως [n:attribute |syntax#n:attributes], για παράδειγμα: +Όπως κάθε ζευγαρωτό tag, έτσι και το ζεύγος `{if} ... {/if}` μπορεί να γραφτεί και με τη μορφή [n:attribute |syntax#n:attributes], για παράδειγμα: ```latte -

                                              In stock {$count} items

                                              +

                                              Σε απόθεμα {$count} τεμάχια

                                              ``` -Γνωρίζετε ότι μπορείτε να προσθέσετε το πρόθεμα `tag-` σε n:attributes; Τότε η συνθήκη θα επηρεάζει μόνο τις ετικέτες HTML και το περιεχόμενο μεταξύ τους θα εκτυπώνεται πάντα: +Γνωρίζετε ότι στα n:attributes μπορείτε να προσθέσετε το πρόθεμα `tag-`; Τότε η συνθήκη θα ισχύει μόνο για την εκτύπωση των HTML tags και το περιεχόμενο μεταξύ τους θα εκτυπώνεται πάντα: ```latte
                                              Hello -{* prints 'Hello' when $clickable is falsey *} -{* prints 'Hello' when $clickable is truthy *} +{* εκτυπώνει 'Hello' όταν το $clickable είναι ψευδές *} +{* εκτυπώνει 'Hello' όταν το $clickable είναι αληθές *} +``` + +Θεϊκό. + + +`n:else` .{data-version:3.0.11} +------------------------------- + +Εάν γράψετε τη συνθήκη `{if} ... {/if}` με τη μορφή [n:attribute |syntax#n:attributes], έχετε τη δυνατότητα να δηλώσετε και έναν εναλλακτικό κλάδο χρησιμοποιώντας το `n:else`: + +```latte +Σε απόθεμα {$count} τεμάχια + +μη διαθέσιμο ``` -Ωραία. +Το attribute `n:else` μπορεί επίσης να χρησιμοποιηθεί σε ζεύγος με τα [`n:ifset` |#ifset elseifset], [`n:foreach` |#foreach], [`n:try` |#try], [#`n:ifcontent`] και [`n:ifchanged` |#ifchanged]. `{/if $cond}` ------------- -Μπορεί να εκπλαγείτε από το γεγονός ότι η έκφραση στη συνθήκη `{if}` μπορεί επίσης να καθοριστεί στην ετικέτα end. Αυτό είναι χρήσιμο σε περιπτώσεις όπου δεν γνωρίζουμε ακόμα την τιμή της συνθήκης όταν ανοίγει η ετικέτα. Ας το ονομάσουμε αναβαλλόμενη απόφαση. +Ίσως σας εκπλήξει το γεγονός ότι η έκφραση στη συνθήκη `{if}` μπορεί να δηλωθεί και στο tag τερματισμού. Αυτό είναι χρήσιμο σε καταστάσεις όπου κατά το άνοιγμα της συνθήκης δεν γνωρίζουμε ακόμα την τιμή της. Ας το ονομάσουμε αναβαλλόμενη απόφαση. -Για παράδειγμα, ξεκινάμε την απαρίθμηση ενός πίνακα με εγγραφές από τη βάση δεδομένων και μόνο μετά την ολοκλήρωση της αναφοράς συνειδητοποιούμε ότι δεν υπήρχε καμία εγγραφή στη βάση δεδομένων. Έτσι, βάζουμε συνθήκη στο τέλος της ετικέτας `{/if}` και αν δεν υπάρχει εγγραφή, δεν θα εκτυπωθεί τίποτα από αυτά: +Για παράδειγμα, αρχίζουμε να εκτυπώνουμε έναν πίνακα με εγγραφές από μια βάση δεδομένων και μόνο μετά την ολοκλήρωση της εκτύπωσης συνειδητοποιούμε ότι δεν υπήρχε καμία εγγραφή στη βάση δεδομένων. Έτσι, βάζουμε μια συνθήκη για αυτό στο tag τερματισμού `{/if}` και αν δεν υπάρχει καμία εγγραφή, τίποτα από αυτά δεν θα εκτυπωθεί: ```latte {if} -

                                              Printing rows from the database

                                              +

                                              Λίστα γραμμών από τη βάση δεδομένων

                                              {foreach $resultSet as $row} @@ -264,30 +284,30 @@ Age: {date('Y') - $birth}
                                              {/if isset($row)} ``` -Δεν είναι πρακτικό; +Έξυπνο, έτσι δεν είναι; -Μπορείτε επίσης να χρησιμοποιήσετε το `{else}` στην αναβαλλόμενη συνθήκη, αλλά όχι το `{elseif}`. +Στην αναβαλλόμενη συνθήκη μπορεί να χρησιμοποιηθεί και το `{else}`, αλλά όχι το `{elseif}`. `{ifset}` `{elseifset}` ----------------------- .[note] -Βλέπε επίσης [`{ifset block}` |template-inheritance#checking-block-existence] +Δείτε επίσης [`{ifset block}` |template-inheritance#Έλεγχος ύπαρξης μπλοκ ifset] -Χρησιμοποιήστε τη συνθήκη `{ifset $var}` για να προσδιορίσετε αν μια μεταβλητή (ή πολλαπλές μεταβλητές) υπάρχει και έχει μη μηδενική τιμή. Στην πραγματικότητα είναι το ίδιο με το `if (isset($var))` στην PHP. Όπως κάθε ετικέτα pair, μπορεί να γραφτεί με τη μορφή [n:attribute |syntax#n:attributes], οπότε ας το δείξουμε σε παράδειγμα: +Με τη συνθήκη `{ifset $var}` ελέγχουμε αν η μεταβλητή (ή περισσότερες μεταβλητές) υπάρχει και έχει τιμή διαφορετική από *null*. Στην πραγματικότητα, είναι το ίδιο με το `if (isset($var))` στην PHP. Όπως κάθε ζευγαρωτό tag, μπορεί να γραφτεί και με τη μορφή [n:attribute |syntax#n:attributes], οπότε ας το δείξουμε ως παράδειγμα: ```latte - + ``` -`{ifchanged}` .{data-version:2.9} ---------------------------------- +`{ifchanged}` +------------- -`{ifchanged}` ελέγχει αν η τιμή μιας μεταβλητής έχει αλλάξει από την τελευταία επανάληψη του βρόχου (foreach, for ή while). +Το `{ifchanged}` ελέγχει αν η τιμή μιας μεταβλητής έχει αλλάξει από την τελευταία επανάληψη σε έναν βρόχο (foreach, for ή while). -Αν καθορίσουμε μία ή περισσότερες μεταβλητές στην ετικέτα, θα ελέγξει αν κάποια από αυτές έχει αλλάξει και θα εκτυπώσει τα περιεχόμενα ανάλογα. Για παράδειγμα, το ακόλουθο παράδειγμα εκτυπώνει το πρώτο γράμμα ενός ονόματος ως επικεφαλίδα κάθε φορά που αλλάζει κατά την καταχώριση ονομάτων: +Αν δηλώσουμε μία ή περισσότερες μεταβλητές στο tag, θα ελέγχει αν κάποια από αυτές έχει αλλάξει και ανάλογα θα εκτυπώσει το περιεχόμενο. Για παράδειγμα, το ακόλουθο παράδειγμα εκτυπώνει το πρώτο γράμμα του ονόματος ως επικεφαλίδα κάθε φορά που αλλάζει κατά την εκτύπωση των ονομάτων: ```latte {foreach ($names|sort) as $name} @@ -297,7 +317,7 @@ Age: {date('Y') - $birth}
                                              {/foreach} ``` -Ωστόσο, αν δεν δοθεί κανένα όρισμα, το ίδιο το περιεχόμενο που αποδίδεται θα ελεγχθεί σε σχέση με την προηγούμενη κατάστασή του. Αυτό σημαίνει ότι στο προηγούμενο παράδειγμα, μπορούμε με ασφάλεια να παραλείψουμε το όρισμα στην ετικέτα. Και φυσικά μπορούμε επίσης να χρησιμοποιήσουμε το [n:attribute |syntax#n:attributes]: +Ωστόσο, αν δεν δηλώσουμε κανένα όρισμα, θα ελέγχεται το αποδοθέν περιεχόμενο σε σχέση με την προηγούμενη κατάστασή του. Αυτό σημαίνει ότι στο προηγούμενο παράδειγμα, μπορούμε απλά να παραλείψουμε το όρισμα στο tag. Και φυσικά, μπορούμε επίσης να χρησιμοποιήσουμε το [n:attribute |syntax#n:attributes]: ```latte {foreach ($names|sort) as $name} @@ -307,49 +327,49 @@ Age: {date('Y') - $birth}
                                              {/foreach} ``` -Μπορείτε επίσης να συμπεριλάβετε μια ρήτρα `{else}` μέσα στο `{ifchanged}`. +Μέσα στο `{ifchanged}` μπορεί επίσης να δηλωθεί η ρήτρα `{else}`. `{switch}` `{case}` `{default}` ------------------------------- -Συγκρίνει την τιμή με πολλαπλές επιλογές. Είναι παρόμοια με τη δομή `switch` που γνωρίζετε από την PHP. Ωστόσο, η Latte τη βελτιώνει: +Συγκρίνει μια τιμή με πολλαπλές επιλογές. Πρόκειται για ανάλογο της εντολής `switch` που γνωρίζετε από την PHP. Ωστόσο, το Latte το βελτιώνει: - χρησιμοποιεί αυστηρή σύγκριση (`===`) -- δεν χρειάζεται μια `break` +- δεν χρειάζεται `break` -Έτσι είναι το ακριβές ισοδύναμο της δομής `match` που διαθέτει η PHP 8.0. +Είναι δηλαδή το ακριβές ισοδύναμο της δομής `match` με την οποία έρχεται η PHP 8.0. ```latte {switch $transport} {case train} - By train + Με τρένο {case plane} - By plane + Με αεροπλάνο {default} - Differently + Διαφορετικά {/switch} ``` -.{data-version:2.9} -Η ρήτρα `{case}` μπορεί να περιέχει πολλαπλές τιμές που χωρίζονται με κόμμα: + +Η ρήτρα `{case}` μπορεί να περιέχει πολλαπλές τιμές χωρισμένες με κόμματα: ```latte {switch $status} -{case $status::New}new item -{case $status::Sold, $status::Unknown}not available +{case $status::New}νέο στοιχείο +{case $status::Sold, $status::Unknown}μη διαθέσιμο {/switch} ``` -Βρόχοι .[#toc-loops] -==================== +Βρόχοι +====== -Στο Latte, όλοι οι βρόχοι που γνωρίζετε από την PHP είναι στη διάθεσή σας: foreach, for και while. +Στο Latte θα βρείτε όλους τους βρόχους που γνωρίζετε από την PHP: foreach, for και while. `{foreach}` ----------- -Γράφετε τον κύκλο με τον ίδιο ακριβώς τρόπο όπως και στην PHP: +Γράφουμε τον βρόχο ακριβώς όπως στην PHP: ```latte {foreach $langs as $code => $lang} @@ -357,11 +377,11 @@ Age: {date('Y') - $birth}
                                              {/foreach} ``` -Επιπλέον, έχει κάποιες εύχρηστες βελτιώσεις για τις οποίες θα μιλήσουμε τώρα. +Επιπλέον, έχει μερικές έξυπνες δυνατότητες για τις οποίες θα μιλήσουμε τώρα. -Για παράδειγμα, ο Latte ελέγχει ότι οι μεταβλητές που δημιουργούνται δεν αντικαθιστούν κατά λάθος παγκόσμιες μεταβλητές με το ίδιο όνομα. Αυτό θα σας σώσει όταν υποθέτετε ότι η `$lang` είναι η τρέχουσα γλώσσα της σελίδας και δεν αντιλαμβάνεστε ότι η `foreach $langs as $lang` έχει αντικαταστήσει αυτή τη μεταβλητή. +Το Latte, για παράδειγμα, ελέγχει αν οι δημιουργημένες μεταβλητές αντικαθιστούν κατά λάθος καθολικές μεταβλητές με το ίδιο όνομα. Αυτό σώζει καταστάσεις όπου υπολογίζετε ότι στο `$lang` βρίσκεται η τρέχουσα γλώσσα της σελίδας και δεν συνειδητοποιείτε ότι το `foreach $langs as $lang` σας αντικατέστησε αυτή τη μεταβλητή. -Ο βρόχος foreach μπορεί επίσης να γραφτεί πολύ κομψά και οικονομικά με το [n:attribute |syntax#n:attributes]: +Ο βρόχος foreach μπορεί επίσης να γραφτεί πολύ κομψά και σύντομα χρησιμοποιώντας το [n:attribute |syntax#n:attributes]: ```latte
                                                @@ -369,7 +389,7 @@ Age: {date('Y') - $birth}
                                              ``` -Γνωρίζατε ότι μπορείτε να προτάξετε το πρόθεμα `inner-` στο n:attributes; Τώρα τότε μόνο το εσωτερικό μέρος του στοιχείου θα επαναλαμβάνεται στο βρόχο: +Γνωρίζετε ότι στα n:attributes μπορείτε να προσθέσετε το πρόθεμα `inner-`; Τότε θα επαναλαμβάνεται στον βρόχο μόνο το εσωτερικό του στοιχείου: ```latte
                                              @@ -378,7 +398,7 @@ Age: {date('Y') - $birth}
                                              ``` -Έτσι, εκτυπώνεται κάτι σαν: +Έτσι, θα εκτυπωθεί κάτι σαν: ```latte
                                              @@ -390,17 +410,17 @@ Age: {date('Y') - $birth}
                                              ``` -`{else}` .{data-version:2.9}{toc: foreach-else} ------------------------------------------------ +`{else}` .{toc: foreach-else} +----------------------------- -Ο βρόχος `foreach` μπορεί να λάβει μια προαιρετική ρήτρα `{else}` της οποίας το κείμενο εμφανίζεται εάν ο δεδομένος πίνακας είναι άδειος: +Μέσα στον βρόχο `foreach` μπορεί να δηλωθεί η ρήτρα `{else}`, το περιεχόμενο της οποίας εμφανίζεται εάν ο βρόχος είναι κενός: ```latte
                                                {foreach $people as $person}
                                              • {$person->name}
                                              • {else} -
                                              • Sorry, no users in this list
                                              • +
                                              • Λυπούμαστε, δεν υπάρχουν χρήστες σε αυτή τη λίστα
                                              • {/foreach}
                                              ``` @@ -409,17 +429,17 @@ Age: {date('Y') - $birth}
                                              `$iterator` ----------- -Μέσα στο βρόχο `foreach` αρχικοποιείται η μεταβλητή `$iterator`. Διατηρεί σημαντικές πληροφορίες σχετικά με τον τρέχοντα βρόχο. +Μέσα στον βρόχο `foreach`, το Latte δημιουργεί τη μεταβλητή `$iterator`, με την οποία μπορούμε να λαμβάνουμε χρήσιμες πληροφορίες για τον τρέχοντα βρόχο: -- `$iterator->first` - είναι αυτή η πρώτη επανάληψη; -- `$iterator->last` - είναι αυτή η τελευταία επανάληψη; -- `$iterator->counter` - μετρητής επανάληψης, ξεκινά από το 1 -- `$iterator->counter0` - μετρητής επανάληψης, ξεκινά από 0 .{data-version:2.9} -- `$iterator->odd` - είναι αυτή η επανάληψη περιττή; -- `$iterator->even` - είναι αυτή η επανάληψη άρτια; -- `$iterator->parent` - ο επαναλήπτης που περιβάλλει τον τρέχοντα .{data-version:2.9} -- `$iterator->nextValue` - το επόμενο στοιχείο του βρόχου -- `$iterator->nextKey` - το κλειδί του επόμενου στοιχείου του βρόχου +- `$iterator->first` - είναι η πρώτη φορά που διασχίζεται ο βρόχος; +- `$iterator->last` - είναι η τελευταία διέλευση; +- `$iterator->counter` - πόση είναι η διέλευση μετρώντας από το ένα; +- `$iterator->counter0` - πόση είναι η διέλευση μετρώντας από το μηδέν; +- `$iterator->odd` - είναι μονή διέλευση; +- `$iterator->even` - είναι ζυγή διέλευση; +- `$iterator->parent` - ο iterator που περιβάλλει τον τρέχοντα +- `$iterator->nextValue` - το επόμενο στοιχείο στον βρόχο +- `$iterator->nextKey` - το κλειδί του επόμενου στοιχείου στον βρόχο ```latte @@ -435,20 +455,19 @@ Age: {date('Y') - $birth}
                                              {/foreach} ``` -Ο λάτε είναι έξυπνος και το `$iterator->last` λειτουργεί όχι μόνο για πίνακες, αλλά και όταν ο βρόχος εκτελείται πάνω σε έναν γενικό επαναλήπτη όπου ο αριθμός των στοιχείων δεν είναι γνωστός εκ των προτέρων. +Το Latte είναι έξυπνο και το `$iterator->last` λειτουργεί όχι μόνο για πίνακες, αλλά και όταν ο βρόχος διατρέχει έναν γενικό iterator, όπου ο αριθμός των στοιχείων δεν είναι γνωστός εκ των προτέρων. `{first}` `{last}` `{sep}` -------------------------- -Αυτές οι ετικέτες μπορούν να χρησιμοποιηθούν μέσα στο βρόχο `{foreach}`. Τα περιεχόμενα του `{first}` αποδίδονται για το πρώτο πέρασμα. -Τα περιεχόμενα του `{last}` αποδίδονται ... μπορείτε να μαντέψετε; Ναι, για το τελευταίο πέρασμα. Αυτές είναι στην πραγματικότητα συντομεύσεις για τις ετικέτες `{if $iterator->first}` και `{if $iterator->last}`. +Αυτά τα tags μπορούν να χρησιμοποιηθούν μέσα στον βρόχο `{foreach}`. Το περιεχόμενο του `{first}` αποδίδεται αν είναι η πρώτη διέλευση. Το περιεχόμενο του `{last}` αποδίδεται… μπορείτε να μαντέψετε; Ναι, αν είναι η τελευταία διέλευση. Πρόκειται στην πραγματικότητα για συντομογραφίες των `{if $iterator->first}` και `{if $iterator->last}`. -Οι ετικέτες μπορούν επίσης να γραφούν ως [n:attributes |syntax#n:attributes]: +Τα tags μπορούν επίσης να χρησιμοποιηθούν κομψά ως [n:attribute |syntax#n:attributes]: ```latte {foreach $rows as $row} - {first}

                                              List of names

                                              {/first} + {first}

                                              Λίστα ονομάτων

                                              {/first}

                                              {$row->name}

                                              @@ -456,7 +475,7 @@ Age: {date('Y') - $birth}
                                              {/foreach} ``` -Τα περιεχόμενα του `{sep}` αποδίδονται εάν η επανάληψη δεν είναι η τελευταία, οπότε είναι κατάλληλο για την εκτύπωση οριοθετών, όπως κόμματα μεταξύ των παρατιθέμενων στοιχείων: +Το περιεχόμενο του tag `{sep}` αποδίδεται αν η διέλευση δεν είναι η τελευταία, είναι λοιπόν χρήσιμο για την απόδοση διαχωριστικών, για παράδειγμα, κομμάτων μεταξύ των εκτυπωμένων στοιχείων: ```latte {foreach $items as $item} {$item} {sep}, {/sep} {/foreach} @@ -465,10 +484,10 @@ Age: {date('Y') - $birth}
                                              Αυτό είναι αρκετά πρακτικό, έτσι δεν είναι; -`{iterateWhile}` .{data-version:2.10} -------------------------------------- +`{iterateWhile}` +---------------- -Απλοποιεί την ομαδοποίηση των γραμμικών δεδομένων κατά την επανάληψη σε έναν βρόχο foreach, εκτελώντας την επανάληψη σε έναν εμφωλευμένο βρόχο εφόσον ικανοποιείται η συνθήκη. [Διαβάστε τις οδηγίες στο βιβλίο μαγειρικής |cookbook/iteratewhile]. +Απλοποιεί την ομαδοποίηση γραμμικών δεδομένων κατά την επανάληψη σε έναν βρόχο foreach, εκτελώντας την επανάληψη σε έναν ένθετο βρόχο, εφόσον η συνθήκη είναι αληθής. [Διαβάστε τον αναλυτικό οδηγό|cookbook/grouping]. Μπορεί επίσης να αντικαταστήσει κομψά τα `{first}` και `{last}` στο παραπάνω παράδειγμα: @@ -487,19 +506,21 @@ Age: {date('Y') - $birth}
                                              {/foreach} ``` +Δείτε επίσης τα φίλτρα [batch |filters#batch] και [group |filters#group]. + `{for}` ------- -Γράφουμε τον κύκλο με τον ίδιο ακριβώς τρόπο όπως και στην PHP: +Γράφουμε τον βρόχο ακριβώς όπως στην PHP: ```latte {for $i = 0; $i < 10; $i++} - Item #{$i} + Στοιχείο {$i} {/for} ``` -Η ετικέτα μπορεί επίσης να γραφτεί ως [n:attribute |syntax#n:attributes]: +Το tag μπορεί επίσης να χρησιμοποιηθεί ως [n:attribute |syntax#n:attributes]: ```latte

                                              {$i}

                                              @@ -509,7 +530,7 @@ Age: {date('Y') - $birth}
                                              `{while}` --------- -Και πάλι, γράφουμε τον κύκλο με τον ίδιο ακριβώς τρόπο όπως και στην PHP: +Γράφουμε ξανά τον βρόχο ακριβώς όπως στην PHP: ```latte {while $row = $result->fetch()} @@ -517,7 +538,7 @@ Age: {date('Y') - $birth}
                                              {/while} ``` -ή ως [n:attribute |syntax#n:attributes]: +Ή ως [n:attribute |syntax#n:attributes]: ```latte @@ -525,7 +546,7 @@ Age: {date('Y') - $birth}
                                              ``` -Μια παραλλαγή με συνθήκη στην ετικέτα τέλους αντιστοιχεί στον βρόχο do-while της PHP: +Είναι επίσης δυνατή μια παραλλαγή με συνθήκη στο tag τερματισμού, η οποία αντιστοιχεί στον βρόχο do-while στην PHP: ```latte {while} @@ -537,7 +558,7 @@ Age: {date('Y') - $birth}
                                              `{continueIf}` `{skipIf}` `{breakIf}` ------------------------------------- -Υπάρχουν ειδικές ετικέτες που μπορείτε να χρησιμοποιήσετε για τον έλεγχο οποιουδήποτε βρόχου - `{continueIf ?}` και `{breakIf ?}` οι οποίες μεταπηδούν στην επόμενη επανάληψη και τερματίζουν τον βρόχο, αντίστοιχα, εάν πληρούνται οι συνθήκες: +Για τον έλεγχο οποιουδήποτε βρόχου μπορούν να χρησιμοποιηθούν τα tags `{continueIf ?}` και `{breakIf ?}`, τα οποία μεταβαίνουν στο επόμενο στοιχείο ή τερματίζουν τον βρόχο αντίστοιχα, όταν πληρούται η συνθήκη: ```latte {foreach $rows as $row} @@ -547,8 +568,8 @@ Age: {date('Y') - $birth}
                                              {/foreach} ``` -.{data-version:2.9} -Η ετικέτα `{skipIf}` μοιάζει πολύ με την `{continueIf}`, αλλά δεν αυξάνει τον μετρητή. Έτσι δεν υπάρχουν κενά στην αρίθμηση όταν εκτυπώνετε το `$iterator->counter` και παραλείπετε κάποια στοιχεία. Επίσης, η ρήτρα {else} θα αποδίδεται όταν παραλείπετε όλα τα στοιχεία. + +Το tag `{skipIf}` είναι πολύ παρόμοιο με το `{continueIf}`, αλλά δεν αυξάνει τον μετρητή `$iterator->counter`, οπότε αν τον εκτυπώνουμε και ταυτόχρονα παραλείπουμε ορισμένα στοιχεία, δεν θα υπάρχουν κενά στην αρίθμηση. Επίσης, η ρήτρα `{else}` αποδίδεται όταν παραλείπουμε όλα τα στοιχεία. ```latte
                                                @@ -556,7 +577,7 @@ Age: {date('Y') - $birth}
                                                {skipIf $person->age < 18}
                                              • {$iterator->counter}. {$person->name}
                                              • {else} -
                                              • Sorry, no adult users in this list
                                              • +
                                              • Λυπούμαστε, δεν υπάρχουν ενήλικες σε αυτή τη λίστα
                                              • {/foreach}
                                              ``` @@ -565,72 +586,68 @@ Age: {date('Y') - $birth}
                                              `{exitIf}` .{data-version:3.0.5} -------------------------------- -Τερματίζει την απόδοση ενός προτύπου ή μπλοκ όταν ικανοποιείται μια συνθήκη (δηλαδή "πρόωρη έξοδος"). +Τερματίζει την απόδοση του προτύπου ή του μπλοκ όταν πληρούται η συνθήκη (το λεγόμενο "early exit"). ```latte {exitIf !$messages} -

                                              Messages

                                              +

                                              Μηνύματα

                                              {$message}
                                              ``` -Συμπερίληψη προτύπων .[#toc-including-templates] -================================================ +Εισαγωγή template +================= `{include 'file.latte'}` .{toc: include} ---------------------------------------- .[note] -Βλέπε επίσης [`{include block}` |template-inheritance#printing-blocks] +Δείτε επίσης [`{include block}` |template-inheritance#Απόδοση μπλοκ include] -Η ετικέτα `{include}` φορτώνει και αποδίδει το καθορισμένο πρότυπο. Στην αγαπημένη μας γλώσσα PHP είναι σαν: +Το tag `{include}` φορτώνει και αποδίδει το καθορισμένο πρότυπο. Αν μιλούσαμε στη γλώσσα της αγαπημένης μας PHP, είναι κάτι σαν: ```php ``` -Τα περιεχόμενα πρότυπα δεν έχουν πρόσβαση στις μεταβλητές του ενεργού πλαισίου, αλλά έχουν πρόσβαση στις παγκόσμιες μεταβλητές. +Τα ενσωματωμένα πρότυπα δεν έχουν πρόσβαση στις μεταβλητές του ενεργού context, έχουν πρόσβαση μόνο στις καθολικές μεταβλητές. -Μπορείτε να περάσετε μεταβλητές με αυτόν τον τρόπο: +Μπορείτε να περάσετε μεταβλητές στο ενσωματωμένο πρότυπο με αυτόν τον τρόπο: ```latte -{* από Latte 2.9 *} {include 'template.latte', foo: 'bar', id: 123} - -{* πριν το Latte 2.9 *} -{include 'template.latte', foo => 'bar', id => 123} ``` -Το όνομα του προτύπου μπορεί να είναι οποιαδήποτε έκφραση της PHP: +Το όνομα του προτύπου μπορεί να είναι οποιαδήποτε έκφραση PHP: ```latte {include $someVar} {include $ajax ? 'ajax.latte' : 'not-ajax.latte'} ``` -Το εισαγόμενο περιεχόμενο μπορεί να τροποποιηθεί με τη χρήση [φίλτρων |syntax#filters]. Το ακόλουθο παράδειγμα αφαιρεί όλα τα στοιχεία HTML και ρυθμίζει την περίπτωση: +Το ενσωματωμένο περιεχόμενο μπορεί να τροποποιηθεί με [φίλτρα |syntax#Φίλτρα]. Το ακόλουθο παράδειγμα αφαιρεί όλο το HTML και προσαρμόζει το μέγεθος των γραμμάτων: ```latte {include 'heading.latte' |stripHtml|capitalize} ``` -Η [κληρονομικότητα του προτύπου |template inheritance] **δεν εμπλέκεται** σε αυτό από προεπιλογή. Ενώ μπορείτε να προσθέσετε ετικέτες μπλοκ σε πρότυπα που περιλαμβάνονται, δεν θα αντικαταστήσουν τα αντίστοιχα μπλοκ στο πρότυπο στο οποίο περιλαμβάνονται. Σκεφτείτε τα includes ως ανεξάρτητα και θωρακισμένα μέρη σελίδων ή ενοτήτων. Αυτή η συμπεριφορά μπορεί να αλλάξει χρησιμοποιώντας τον τροποποιητή `with blocks` (από την Latte 2.9.1): +Από προεπιλογή, η [κληρονομικότητα προτύπων|template-inheritance] δεν παίζει κανένα ρόλο σε αυτή την περίπτωση. Παρόλο που μπορούμε να χρησιμοποιούμε μπλοκ στο ενσωματωμένο πρότυπο, δεν αντικαθίστανται τα αντίστοιχα μπλοκ στο πρότυπο στο οποίο ενσωματώνεται. Σκεφτείτε τα ενσωματωμένα πρότυπα ως ξεχωριστά, απομονωμένα τμήματα σελίδων ή modules. Αυτή η συμπεριφορά μπορεί να αλλάξει χρησιμοποιώντας τον τροποποιητή `with blocks`: ```latte {include 'template.latte' with blocks} ``` -Η σχέση μεταξύ του ονόματος αρχείου που καθορίζεται στην ετικέτα και του αρχείου στο δίσκο είναι θέμα του [φορτωτή |extending-latte#Loaders]. +Η σχέση μεταξύ του ονόματος αρχείου που αναφέρεται στο tag και του αρχείου στο δίσκο είναι θέμα του [loader|loaders]. -`{sandbox}` .{data-version:2.8} -------------------------------- +`{sandbox}` +----------- -Όταν συμπεριλαμβάνετε ένα πρότυπο που έχει δημιουργηθεί από έναν τελικό χρήστη, θα πρέπει να εξετάσετε το ενδεχόμενο να το κάνετε sandboxing (περισσότερες πληροφορίες στην [τεκμηρίωση sandbox |sandbox]): +Κατά την ενσωμάτωση ενός προτύπου που δημιουργήθηκε από τον τελικό χρήστη, θα πρέπει να εξετάσετε τη λειτουργία sandbox (περισσότερες πληροφορίες στην [τεκμηρίωση του sandbox |sandbox]): ```latte {sandbox 'untrusted.latte', level: 3, data: $menu} @@ -641,9 +658,9 @@ Age: {date('Y') - $birth}
                                              ========= .[note] -Βλέπε επίσης [`{block name}` |template-inheritance#blocks] +Δείτε επίσης [`{block name}` |template-inheritance#Μπλοκ block] -Τα μπλοκ χωρίς όνομα χρησιμεύουν στη δυνατότητα εφαρμογής [φίλτρων |syntax#filters] σε ένα τμήμα του προτύπου. Για παράδειγμα, μπορείτε να εφαρμόσετε ένα φίλτρο [strip |filters#strip] για να αφαιρέσετε τα περιττά κενά: +Τα μπλοκ χωρίς όνομα χρησιμεύουν ως τρόπος εφαρμογής [φίλτρων |syntax#Φίλτρα] σε ένα μέρος του προτύπου. Για παράδειγμα, έτσι μπορεί να εφαρμοστεί το φίλτρο [strip |filters#spaceless], το οποίο αφαιρεί τα περιττά κενά: ```latte {block|strip} @@ -654,16 +671,16 @@ Age: {date('Y') - $birth}
                                              ``` -Χειρισμός εξαιρέσεων .[#toc-exception-handling] -=============================================== +Διαχείριση εξαιρέσεων +===================== -`{try}` .{data-version:2.9} ---------------------------- +`{try}` +------- -Αυτή η ετικέτα καθιστά εξαιρετικά εύκολη την κατασκευή ισχυρών προτύπων. +Χάρη σε αυτό το tag, είναι εξαιρετικά εύκολο να δημιουργείτε ανθεκτικά πρότυπα. -Εάν προκύψει μια εξαίρεση κατά την απόδοση του μπλοκ `{try}`, ολόκληρο το μπλοκ απορρίπτεται και η απόδοση θα συνεχιστεί μετά από αυτό: +Εάν κατά την απόδοση του μπλοκ `{try}` προκύψει μια εξαίρεση, ολόκληρο το μπλοκ απορρίπτεται και η απόδοση συνεχίζεται μετά από αυτό: ```latte {try} @@ -675,7 +692,7 @@ Age: {date('Y') - $birth}
                                              {/try} ``` -Τα περιεχόμενα της προαιρετικής ρήτρας `{else}` αποδίδονται μόνο όταν εμφανιστεί εξαίρεση: +Το περιεχόμενο στην προαιρετική ρήτρα `{else}` αποδίδεται μόνο όταν προκύψει εξαίρεση: ```latte {try} @@ -685,11 +702,11 @@ Age: {date('Y') - $birth}
                                              {/foreach} {else} -

                                              Sorry, the tweets could not be loaded.

                                              +

                                              Λυπούμαστε, δεν ήταν δυνατή η φόρτωση των tweets.

                                              {/try} ``` -Η ετικέτα μπορεί επίσης να γραφτεί ως [n:attribute |syntax#n:attributes]: +Το tag μπορεί επίσης να χρησιμοποιηθεί ως [n:attribute |syntax#n:attributes]: ```latte
                                                @@ -697,13 +714,13 @@ Age: {date('Y') - $birth}
                                              ``` -Είναι επίσης δυνατό να ορίσετε [δικό σας χειριστή εξαιρέσεων |develop#exception handler] για π.χ. καταγραφή: +Είναι επίσης δυνατό να ορίσετε έναν προσαρμοσμένο [χειριστή εξαιρέσεων |develop#Χειριστής Εξαιρέσεων], για παράδειγμα, για καταγραφή. -`{rollback}` .{data-version:2.9} --------------------------------- +`{rollback}` +------------ -Το μπλοκ `{try}` μπορεί επίσης να σταματήσει και να παραλειφθεί χειροκίνητα χρησιμοποιώντας το `{rollback}`. Έτσι, δεν χρειάζεται να ελέγξετε εκ των προτέρων όλα τα δεδομένα εισόδου και μόνο κατά τη διάρκεια της απόδοσης μπορείτε να αποφασίσετε αν έχει νόημα να αποδοθεί το αντικείμενο. +Το μπλοκ `{try}` μπορεί επίσης να διακοπεί και να παραλειφθεί χειροκίνητα χρησιμοποιώντας το `{rollback}`. Χάρη σε αυτό, δεν χρειάζεται να ελέγχετε εκ των προτέρων όλα τα δεδομένα εισόδου και μπορείτε να αποφασίσετε κατά τη διάρκεια της απόδοσης ότι δεν θέλετε καθόλου να αποδώσετε το αντικείμενο: ```latte {try} @@ -719,14 +736,14 @@ Age: {date('Y') - $birth}
                                              ``` -Μεταβλητές .[#toc-variables] -============================ +Μεταβλητές +========== `{var}` `{default}` ------------------- -Θα δημιουργήσουμε νέες μεταβλητές στο πρότυπο με την ετικέτα `{var}`: +Δημιουργούμε νέες μεταβλητές στο template με το tag `{var}`: ```latte {var $name = 'John Smith'} @@ -736,13 +753,13 @@ Age: {date('Y') - $birth}
                                              {var $name = 'John Smith', $age = 27} ``` -Η ετικέτα `{default}` λειτουργεί παρόμοια, με τη διαφορά ότι δημιουργεί μεταβλητές μόνο αν δεν υπάρχουν: +Το tag `{default}` λειτουργεί παρόμοια, αλλά δημιουργεί μεταβλητές μόνο εάν δεν υπάρχουν. Εάν η μεταβλητή υπάρχει ήδη και περιέχει την τιμή `null`, δεν θα αντικατασταθεί: ```latte -{default $lang = 'cs'} +{default $lang = 'el'} ``` -Από τη Latte 2.7, μπορείτε επίσης να καθορίσετε [τύπους μεταβλητών |type-system]. Προς το παρόν, είναι πληροφοριακές και η Latte δεν τις ελέγχει. +Μπορείτε επίσης να δηλώσετε [τύπους μεταβλητών|type-system]. Προς το παρόν είναι πληροφοριακοί και το Latte δεν τους ελέγχει. ```latte {var string $name = $article->getTitle()} @@ -750,10 +767,10 @@ Age: {date('Y') - $birth}
                                              ``` -`{parameters}` .{data-version:2.9} ----------------------------------- +`{parameters}` +-------------- -Ακριβώς όπως μια συνάρτηση δηλώνει τις παραμέτρους της, ένα πρότυπο μπορεί να δηλώσει τις μεταβλητές του στην αρχή του: +Όπως μια συνάρτηση δηλώνει τις παραμέτρους της, έτσι και ένα πρότυπο μπορεί στην αρχή να δηλώσει τις μεταβλητές του: ```latte {parameters @@ -763,15 +780,15 @@ Age: {date('Y') - $birth}
                                              } ``` -Οι μεταβλητές `$a` και `$b` χωρίς προεπιλεγμένη τιμή έχουν αυτόματα την προεπιλεγμένη τιμή `null`. Οι δηλωμένοι τύποι εξακολουθούν να είναι πληροφοριακοί και το Latte δεν τους ελέγχει. +Οι μεταβλητές `$a` και `$b` χωρίς δηλωμένη προεπιλεγμένη τιμή έχουν αυτόματα την προεπιλεγμένη τιμή `null`. Οι δηλωμένοι τύποι είναι προς το παρόν πληροφοριακοί και το Latte δεν τους ελέγχει. -Εκτός από τις δηλωμένες μεταβλητές δεν περνούν στο πρότυπο. Αυτή είναι μια διαφορά από την ετικέτα `{default}`. +Άλλες μεταβλητές εκτός από τις δηλωμένες δεν μεταφέρονται στο πρότυπο. Αυτό τις διαφοροποιεί από το tag `{default}`. `{capture}` ----------- -Χρησιμοποιώντας την ετικέτα `{capture}` μπορείτε να αποτυπώσετε την έξοδο σε μια μεταβλητή: +Συλλαμβάνει την έξοδο σε μια μεταβλητή: ```latte {capture $var} @@ -783,7 +800,7 @@ Age: {date('Y') - $birth}

                                              Captured: {$var}

                                              ``` -Η ετικέτα μπορεί επίσης να γραφτεί ως [n:attribute |syntax#n:attributes]: +Το tag μπορεί, όπως κάθε ζευγαρωτό tag, να γραφτεί και ως [n:attribute |syntax#n:attributes]: ```latte
                                                @@ -791,15 +808,17 @@ Age: {date('Y') - $birth}
                                              ``` +Η έξοδος HTML αποθηκεύεται στη μεταβλητή `$var` με τη μορφή του αντικειμένου `Latte\Runtime\Html`, ώστε να [μην γίνει ανεπιθύμητο escaping |develop#Απενεργοποίηση Αυτόματης Διαφυγής Μεταβλητής] κατά την εκτύπωση. + -Άλλοι .[#toc-others] -==================== +Άλλα +==== `{contentType}` --------------- -Χρησιμοποιήστε την ετικέτα για να καθορίσετε τον τύπο περιεχομένου που αντιπροσωπεύει το πρότυπο. Οι επιλογές είναι οι εξής: +Με αυτό το tag καθορίζετε τι τύπο περιεχομένου αντιπροσωπεύει το πρότυπο. Οι επιλογές είναι: - `html` (προεπιλεγμένος τύπος) - `xml` @@ -808,9 +827,9 @@ Age: {date('Y') - $birth}
                                              - `calendar` (iCal) - `text` -Η χρήση του είναι σημαντική, διότι θέτει τη [διαφυγή σε συνάρτηση με το περιβάλλον |safety-first#context-aware-escaping] και μόνο τότε μπορεί το Latte να διαφύγει σωστά. Για παράδειγμα, το `{contentType xml}` μεταβαίνει σε λειτουργία XML, το `{contentType text}` απενεργοποιεί εντελώς την αποφυγή διαφυγής. +Η χρήση του είναι σημαντική, επειδή ορίζει το [context-aware escaping |safety-first#Context-Aware Escaping] και μόνο έτσι μπορεί να κάνει σωστά escape. Για παράδειγμα, το `{contentType xml}` αλλάζει σε λειτουργία XML, το `{contentType text}` απενεργοποιεί εντελώς το escaping. -Εάν η παράμετρος είναι ένας τύπος MIME με πλήρεις δυνατότητες, όπως το `application/xml`, στέλνει επίσης μια επικεφαλίδα HTTP `Content-Type` στο πρόγραμμα περιήγησης: +Εάν η παράμετρος είναι ένας πλήρης τύπος MIME, όπως για παράδειγμα `application/xml`, τότε επιπλέον στέλνει την κεφαλίδα HTTP `Content-Type` στον browser: ```latte {contentType application/xml} @@ -829,46 +848,50 @@ Age: {date('Y') - $birth}
                                              `{debugbreak}` -------------- -Καθορίζει το σημείο στο οποίο θα διακοπεί η εκτέλεση του κώδικα. Χρησιμοποιείται για σκοπούς αποσφαλμάτωσης για να επιθεωρήσει ο προγραμματιστής το περιβάλλον εκτέλεσης και να διασφαλίσει ότι ο κώδικας εκτελείται όπως αναμένεται. Υποστηρίζει το [Xdebug |https://xdebug.org]. Επιπλέον, μπορείτε να καθορίσετε μια συνθήκη κατά την οποία ο κώδικας θα πρέπει να διακόπτεται. +Υποδεικνύει το σημείο όπου η εκτέλεση του προγράμματος θα διακοπεί και θα εκκινηθεί ο debugger, ώστε ο προγραμματιστής να μπορεί να επιθεωρήσει το περιβάλλον εκτέλεσης και να διαπιστώσει αν το πρόγραμμα λειτουργεί όπως αναμένεται. Υποστηρίζει το [Xdebug |https://xdebug.org/]. Μπορεί να προστεθεί μια συνθήκη που καθορίζει πότε πρέπει να διακοπεί το πρόγραμμα. ```latte -{debugbreak} {* διακόπτει το πρόγραμμα *} +{debugbreak} {* παύει το πρόγραμμα *} -{debugbreak $counter == 1} {* σπάει το πρόγραμμα αν ικανοποιείται η συνθήκη *} +{debugbreak $counter == 1} {* παύει το πρόγραμμα όταν πληρούται η συνθήκη *} ``` `{do}` ------ -Εκτελεί τον κώδικα και δεν εκτυπώνει τίποτα. +Εκτελεί κώδικα PHP και δεν εκτυπώνει τίποτα. Όπως και με όλα τα άλλα tags, ο κώδικας PHP νοείται ως μία έκφραση, δείτε [περιορισμοί PHP |syntax#Περιορισμοί της PHP στο Latte]. ```latte {do $num++} ``` -Στη Latte 2.7 και σε προηγούμενες εκδόσεις, χρησιμοποιούνταν το `{php}`. - `{dump}` -------- -Απορρίπτει μια μεταβλητή ή το τρέχον πλαίσιο. +Κάνει dump μια μεταβλητή ή το τρέχον context. ```latte -{dump $name} {* Απορρίπτει τη μεταβλητή $name *} +{dump $name} {* Εκτυπώνει τη μεταβλητή $name *} -{dump} {* Απορρίπτει όλες τις καθορισμένες μεταβλητές *} +{dump} {* Εκτυπώνει όλες τις τρέχουσες ορισμένες μεταβλητές *} ``` .[caution] -Απαιτεί το πακέτο [Tracy |tracy:en]. +Απαιτεί τη βιβλιοθήκη [Tracy|tracy:]. + + +`{php}` +------- + +Επιτρέπει την εκτέλεση οποιουδήποτε κώδικα PHP. Το tag πρέπει να ενεργοποιηθεί με την επέκταση [RawPhpExtension |develop#RawPhpExtension]. `{spaceless}` ------------- -Αφαιρεί τα περιττά κενά διαστήματα. Είναι παρόμοιο με το φίλτρο [χωρίς κενά |filters#spaceless]. +Αφαιρεί το περιττό λευκό διάστημα από την έξοδο. Λειτουργεί παρόμοια με το φίλτρο [spaceless |filters#spaceless]. ```latte {spaceless} @@ -878,52 +901,52 @@ Age: {date('Y') - $birth}
                                              {/spaceless} ``` -Έξοδοι: +Δημιουργεί ```latte -
                                              • Hello
                                              +
                                              • Hello
                                              ``` -Η ετικέτα μπορεί επίσης να γραφτεί ως [n:attribute |syntax#n:attributes]: +Το tag μπορεί επίσης να γραφτεί ως [n:attribute |syntax#n:attributes]. `{syntax}` ---------- -Οι ετικέτες Latte δεν χρειάζεται να περικλείονται μόνο σε μονές αγκύλες. Μπορείτε να επιλέξετε άλλο διαχωριστικό, ακόμη και κατά την εκτέλεση. Αυτό γίνεται με το `{syntax…}`, όπου η παράμετρος μπορεί να είναι: +Τα tags του Latte δεν χρειάζεται να περικλείονται μόνο σε απλές αγκύλες. Μπορούμε να επιλέξουμε και άλλο οριοθέτη, ακόμη και κατά το χρόνο εκτέλεσης. Για αυτό χρησιμεύει το `{syntax …}`, όπου ως παράμετρος μπορεί να δηλωθεί: - double: `{{...}}` -- off: απενεργοποιεί εντελώς τις ετικέτες Latte +- off: απενεργοποιεί εντελώς την επεξεργασία των tags του Latte -Χρησιμοποιώντας τον συμβολισμό n:attribute μπορούμε να απενεργοποιήσουμε το Latte μόνο για ένα μπλοκ JavaScript: +Με τη χρήση n:attributes, μπορείτε να απενεργοποιήσετε το Latte για παράδειγμα μόνο για ένα μπλοκ JavaScript: ```latte ``` -Το Latte μπορεί να χρησιμοποιηθεί πολύ άνετα μέσα στη JavaScript, απλώς αποφύγετε κατασκευές όπως σε αυτό το παράδειγμα, όπου το γράμμα ακολουθεί αμέσως μετά το `{`, βλέπε [Latte μέσα στη JavaScript ή CSS |recipes#Latte inside JavaScript or CSS]. +Το Latte μπορεί να χρησιμοποιηθεί πολύ άνετα και μέσα σε JavaScript, αρκεί να αποφεύγετε κατασκευές όπως σε αυτό το παράδειγμα, όπου ένα γράμμα ακολουθεί αμέσως μετά το `{`, δείτε [Latte μέσα σε JavaScript ή CSS |recipes#Latte μέσα σε JavaScript ή CSS]. -Αν απενεργοποιήσετε το Latte με το `{syntax off}` (δηλαδή την ετικέτα, όχι το χαρακτηριστικό n:attribute), θα αγνοήσει αυστηρά όλες τις ετικέτες μέχρι το `{/syntax}`. +Εάν απενεργοποιήσετε το Latte χρησιμοποιώντας το `{syntax off}` (δηλαδή με το tag, όχι με το n:attribute), θα αγνοεί αυστηρά όλα τα tags μέχρι το `{/syntax}` -{trace} .{data-version:2.10} ----------------------------- +{trace} +------- -Εκτοξεύει μια εξαίρεση `Latte\RuntimeException`, της οποίας το ίχνος στοίβας είναι στο πνεύμα των προτύπων. Έτσι, αντί για κλήση συναρτήσεων και μεθόδων, περιλαμβάνει κλήση μπλοκ και εισαγωγή προτύπων. Αν χρησιμοποιείτε ένα εργαλείο για την ευκρινή εμφάνιση των πεταμένων εξαιρέσεων, όπως το [Tracy |tracy:en], θα δείτε καθαρά τη στοίβα κλήσης, συμπεριλαμβανομένων όλων των ορίων που έχουν περάσει. +Προκαλεί μια εξαίρεση `Latte\RuntimeException`, της οποίας το stack trace ακολουθεί το πνεύμα των προτύπων. Δηλαδή, αντί για κλήσεις συναρτήσεων και μεθόδων, περιέχει κλήσεις μπλοκ και ενσωματώσεις προτύπων. Εάν χρησιμοποιείτε ένα εργαλείο για σαφή εμφάνιση των εξαιρέσεων που προκλήθηκαν, όπως για παράδειγμα το [Tracy|tracy:], θα δείτε με σαφήνεια το call stack, συμπεριλαμβανομένων όλων των παραμέτρων που πέρασαν. -Βοηθοί ετικετών HTML .[#toc-html-tag-helpers] -============================================= +Βοηθοί κωδικοποιητή HTML +======================== -n:class .[#toc-n-class] ------------------------ +n:class +------- -Χάρη στο `n:class`, είναι πολύ εύκολο να δημιουργήσετε το χαρακτηριστικό HTML `class` ακριβώς όπως το χρειάζεστε. +Χάρη στο `n:class`, μπορείτε πολύ εύκολα να δημιουργήσετε το HTML attribute `class` ακριβώς όπως το φαντάζεστε. -Παράδειγμα: Θέλω το ενεργό στοιχείο να έχει την κλάση `active`: +Παράδειγμα: χρειάζομαι το ενεργό στοιχείο να έχει την κλάση `active`: ```latte {foreach $items as $item} @@ -931,7 +954,7 @@ n:class .[#toc-n-class] {/foreach} ``` -Και επιπλέον θέλω το πρώτο στοιχείο να έχει τις κλάσεις `first` και `main`: +Και επιπλέον, το πρώτο στοιχείο να έχει τις κλάσεις `first` και `main`: ```latte {foreach $items as $item} @@ -939,7 +962,7 @@ n:class .[#toc-n-class] {/foreach} ``` -Και όλα τα στοιχεία πρέπει να έχουν την κλάση `list-item`: +Και όλα τα στοιχεία να έχουν την κλάση `list-item`: ```latte {foreach $items as $item} @@ -947,13 +970,13 @@ n:class .[#toc-n-class] {/foreach} ``` -Δεν είναι εκπληκτικά απλό; +Εκπληκτικά απλό, έτσι δεν είναι; -n:attr .[#toc-n-attr] ---------------------- +n:attr +------ -Το χαρακτηριστικό `n:attr` μπορεί να δημιουργήσει αυθαίρετα χαρακτηριστικά HTML με την ίδια κομψότητα όπως το [n:class |#n:class]. +Το attribute `n:attr` μπορεί με την ίδια κομψότητα που έχει το [#n:class] να δημιουργεί οποιαδήποτε HTML attributes. ```latte {foreach $data as $item} @@ -961,7 +984,7 @@ n:attr .[#toc-n-attr] {/foreach} ``` -Ανάλογα με τις επιστρεφόμενες τιμές, εμφανίζει π.χ: +Ανάλογα με τις επιστρεφόμενες τιμές, εκτυπώνει π.χ.: ```latte @@ -972,24 +995,26 @@ n:attr .[#toc-n-attr] ``` -n:tag .[#toc-n-tag] -------------------- +n:tag +----- -Το χαρακτηριστικό `n:tag` μπορεί να αλλάξει δυναμικά το όνομα ενός στοιχείου HTML. +Το attribute `n:tag` μπορεί να αλλάξει δυναμικά το όνομα ενός στοιχείου HTML. ```latte

                                              {$title}

                                              ``` -Εάν `$heading === null`, το `

                                              ` εκτυπώνεται χωρίς αλλαγή. Διαφορετικά, το όνομα του στοιχείου αλλάζει με την τιμή της μεταβλητής, οπότε για το `$heading === 'h3'` γράφει: +Εάν το `$heading === null`, εκτυπώνεται χωρίς αλλαγή το tag `

                                              `. Διαφορετικά, αλλάζει το όνομα του στοιχείου στην τιμή της μεταβλητής, οπότε για `$heading === 'h3'` εκτυπώνεται: ```latte

                                              ...

                                              ``` +Επειδή το Latte είναι ένα ασφαλές σύστημα προτύπων, ελέγχει αν το νέο όνομα του tag είναι έγκυρο και δεν περιέχει ανεπιθύμητες ή κακόβουλες τιμές. -n:ifcontent .[#toc-n-ifcontent] -------------------------------- + +n:ifcontent +----------- Αποτρέπει την εκτύπωση ενός κενού στοιχείου HTML, δηλαδή ενός στοιχείου που δεν περιέχει τίποτα άλλο εκτός από κενά. @@ -999,7 +1024,7 @@ n:ifcontent .[#toc-n-ifcontent]
                                              ``` -Ανάλογα με τις τιμές της μεταβλητής `$error` αυτό θα εκτυπωθεί: +Εκτυπώνει ανάλογα με την τιμή της μεταβλητής `$error`: ```latte {* $error = '' *} @@ -1013,42 +1038,42 @@ n:ifcontent .[#toc-n-ifcontent] ``` -Μετάφραση .[#toc-translation] -============================= +Μεταφράσεις +=========== -Για να λειτουργήσουν οι ετικέτες μετάφρασης, πρέπει να [ρυθμίσετε το μεταφραστή |develop#TranslatorExtension]. Μπορείτε επίσης να χρησιμοποιήσετε το [`translate` |filters#translate] φίλτρο για τη μετάφραση. +Για να λειτουργούν τα tags μετάφρασης, πρέπει να [ενεργοποιήσετε τον μεταφραστή |develop#TranslatorExtension]. Για μετάφραση μπορείτε επίσης να χρησιμοποιήσετε το φίλτρο [`translate` |filters#translate]. `{_...}` -------- -Μεταφράζει τις τιμές σε άλλες γλώσσες. +Μεταφράζει τιμές σε άλλες γλώσσες. ```latte -{_'Basket'} +{_'Καλάθι'}{_$item} ``` -Στον μεταφραστή μπορούν επίσης να μεταβιβαστούν και άλλες παράμετροι: +Μπορείτε επίσης να περάσετε επιπλέον παραμέτρους στον μεταφραστή: ```latte -{_'Basket', domain: order} +{_'Καλάθι', domain: order} ``` `{translate}` ------------- -Překládá části šablony: +Μεταφράζει τμήματα του προτύπου: ```latte -

                                              {translate}Order{/translate}

                                              +

                                              {translate}Παραγγελία{/translate}

                                              {translate domain: order}Lorem ipsum ...{/translate} ``` - [attribute |syntax#n:attributes], για να μεταφραστεί το εσωτερικό του στοιχείου: +Το tag μπορεί επίσης να γραφτεί ως [n:attribute |syntax#n:attributes], για μετάφραση του εσωτερικού του στοιχείου: ```latte -

                                              Order

                                              +

                                              Παραγγελία

                                              ``` diff --git a/latte/el/template-inheritance.texy b/latte/el/template-inheritance.texy index a81e0fcbd5..ebd173e1b1 100644 --- a/latte/el/template-inheritance.texy +++ b/latte/el/template-inheritance.texy @@ -1,16 +1,16 @@ -Κληρονομικότητα προτύπων και επαναχρησιμοποίηση +Κληρονομικότητα και επαναχρησιμοποίηση προτύπων *********************************************** .[perex] -Οι μηχανισμοί επαναχρησιμοποίησης προτύπων και κληρονομικότητας είναι εδώ για να ενισχύσουν την παραγωγικότητά σας, επειδή κάθε πρότυπο περιέχει μόνο το μοναδικό του περιεχόμενο και τα επαναλαμβανόμενα στοιχεία και δομές επαναχρησιμοποιούνται. Παρουσιάζουμε τρεις έννοιες: [κληρονομικότητα διάταξης |#layout inheritance], [οριζόντια επαναχρησιμοποίηση |#horizontal reuse] και [κληρονομικότητα μονάδων |#unit inheritance]. +Οι μηχανισμοί επαναχρησιμοποίησης και κληρονομικότητας προτύπων θα αυξήσουν την παραγωγικότητά σας, καθώς κάθε πρότυπο περιέχει μόνο το μοναδικό του περιεχόμενο και τα επαναλαμβανόμενα στοιχεία και δομές επαναχρησιμοποιούνται. Παρουσιάζουμε τρεις έννοιες: [κληρονομικότητα διάταξης |#Κληρονομικότητα διάταξης layout], [οριζόντια επαναχρησιμοποίηση |#Οριζόντια επαναχρησιμοποίηση import] και [κληρονομικότητα μονάδας |#Κληρονομικότητα μονάδας embed]. -Η έννοια της κληρονομικότητας προτύπων Latte είναι παρόμοια με την κληρονομικότητα κλάσεων της PHP. Ορίζετε ένα **πρότυπο γονέα** από το οποίο άλλα **πρότυπα-παιδιά** μπορούν να επεκταθούν και να αντικαταστήσουν τμήματα του προτύπου γονέα. Λειτουργεί εξαιρετικά όταν τα στοιχεία μοιράζονται μια κοινή δομή. Ακούγεται περίπλοκο; Μην ανησυχείτε, δεν είναι. +Η έννοια της κληρονομικότητας προτύπων Latte είναι παρόμοια με την κληρονομικότητα κλάσεων στην PHP. Ορίζετε ένα **γονικό πρότυπο**, από το οποίο μπορούν να κληρονομήσουν άλλα **θυγατρικά πρότυπα** και μπορούν να αντικαταστήσουν τμήματα του γονικού προτύπου. Λειτουργεί εξαιρετικά όταν τα στοιχεία μοιράζονται μια κοινή δομή. Ακούγεται περίπλοκο; Μην ανησυχείτε, είναι πολύ εύκολο. -Κληρονομικότητα διάταξης `{layout}` .{toc: Layout Inheritance} -============================================================== +Κληρονομικότητα διάταξης `{layout}` +=================================== -Ας δούμε την κληρονομικότητα προτύπων διάταξης ξεκινώντας με ένα παράδειγμα. Πρόκειται για ένα πρότυπο γονέα το οποίο θα ονομάσουμε για παράδειγμα `layout.latte` και ορίζει ένα έγγραφο-σκελετό HTML. +Ας δούμε την κληρονομικότητα διάταξης προτύπου, δηλαδή τη διάταξη, με ένα παράδειγμα. Αυτό είναι ένα γονικό πρότυπο, το οποίο θα ονομάσουμε για παράδειγμα `layout.latte` και το οποίο ορίζει τον σκελετό ενός εγγράφου HTML: ```latte @@ -30,9 +30,9 @@ ``` -Οι ετικέτες `{block}` ορίζουν τρία μπλοκ που μπορούν να συμπληρώσουν τα πρότυπα-παιδιά. Το μόνο που κάνει η ετικέτα μπλοκ είναι να πει στη μηχανή προτύπων ότι ένα πρότυπο παιδί μπορεί να παρακάμψει αυτά τα τμήματα του προτύπου ορίζοντας το δικό του μπλοκ με το ίδιο όνομα. +Τα tags `{block}` ορίζουν τρία μπλοκ που μπορούν να συμπληρώσουν τα θυγατρικά πρότυπα. Το tag block απλώς δηλώνει ότι αυτή η θέση μπορεί να αντικατασταθεί από ένα θυγατρικό πρότυπο ορίζοντας το δικό του μπλοκ με το ίδιο όνομα. -Ένα υποκατάστατο πρότυπο μπορεί να μοιάζει ως εξής: +Ένα θυγατρικό πρότυπο μπορεί να μοιάζει κάπως έτσι: ```latte {layout 'layout.latte'} @@ -44,11 +44,11 @@ {/block} ``` -Η ετικέτα `{layout}` είναι το κλειδί εδώ. Λέει στη μηχανή προτύπων ότι αυτό το πρότυπο "επεκτείνει" ένα άλλο πρότυπο. Όταν το Latte αποδίδει αυτό το πρότυπο, πρώτα εντοπίζει τον γονέα - σε αυτή την περίπτωση, το `layout.latte`. +Το κλειδί εδώ είναι το tag `{layout}`. Λέει στο Latte ότι αυτό το πρότυπο "επεκτείνει" ένα άλλο πρότυπο. Όταν το Latte αποδίδει αυτό το πρότυπο, βρίσκει πρώτα το γονικό πρότυπο - σε αυτή την περίπτωση `layout.latte`. -Σε αυτό το σημείο, η μηχανή προτύπων θα παρατηρήσει τις τρεις ετικέτες μπλοκ στο `layout.latte` και θα αντικαταστήσει αυτά τα μπλοκ με τα περιεχόμενα του προτύπου-παιδί. Σημειώστε ότι εφόσον το πρότυπο παιδί δεν όρισε το μπλοκ *footer*, αντί αυτού χρησιμοποιείται το περιεχόμενο από το πρότυπο γονέα. Το περιεχόμενο μέσα σε μια ετικέτα `{block}` σε ένα γονικό πρότυπο χρησιμοποιείται πάντα ως εφεδρικό. +Σε αυτό το σημείο, το Latte παρατηρεί τα τρία tags μπλοκ στο `layout.latte` και αντικαθιστά αυτά τα μπλοκ με το περιεχόμενο του θυγατρικού προτύπου. Δεδομένου ότι το θυγατρικό πρότυπο δεν όρισε το μπλοκ *footer*, χρησιμοποιείται αντ' αυτού το περιεχόμενο από το γονικό πρότυπο. Το περιεχόμενο εντός του tag `{block}` στο γονικό πρότυπο χρησιμοποιείται πάντα ως εφεδρικό. -Η έξοδος μπορεί να μοιάζει ως εξής: +Η έξοδος μπορεί να μοιάζει κάπως έτσι: ```latte @@ -68,7 +68,7 @@ ``` -Σε ένα πρότυπο παιδί, τα μπλοκ μπορούν να βρίσκονται μόνο είτε στο ανώτερο επίπεδο είτε μέσα σε ένα άλλο μπλοκ, δηλ: +Σε ένα θυγατρικό πρότυπο, τα μπλοκ μπορούν να τοποθετηθούν μόνο στο ανώτατο επίπεδο ή μέσα σε ένα άλλο μπλοκ, δηλαδή: ```latte {block content} @@ -76,7 +76,7 @@ {/block} ``` -Επίσης, ένα μπλοκ θα δημιουργείται πάντα στο ανεξάρτητα από το αν η συνθήκη που το περιβάλλει `{if}` αξιολογείται ως αληθής ή ψευδής. Σε αντίθεση με ό,τι μπορεί να νομίζετε, αυτό το πρότυπο ορίζει ένα μπλοκ. +Επίσης, ένα μπλοκ θα δημιουργείται πάντα ανεξάρτητα από το αν η περιβάλλουσα συνθήκη `{if}` αξιολογείται ως αληθής ή ψευδής. Έτσι, ακόμα κι αν δεν φαίνεται έτσι, αυτό το πρότυπο θα ορίσει το μπλοκ. ```latte {if false} @@ -86,7 +86,7 @@ {/if} ``` -Αν θέλετε η έξοδος μέσα στο μπλοκ να εμφανίζεται υπό συνθήκη, χρησιμοποιήστε αντ' αυτού τα ακόλουθα: +Αν θέλετε η έξοδος μέσα στο μπλοκ να εμφανίζεται υπό συνθήκη, χρησιμοποιήστε αντ' αυτού τα παρακάτω: ```latte {block head} @@ -96,7 +96,7 @@ {/block} ``` -Τα δεδομένα έξω από ένα μπλοκ σε ένα πρότυπο παιδί εκτελούνται πριν από την απόδοση του προτύπου διάταξης, επομένως μπορείτε να το χρησιμοποιήσετε για να ορίσετε μεταβλητές όπως το `{var $foo = bar}` και να διαδώσετε δεδομένα σε ολόκληρη την αλυσίδα κληρονομικότητας: +Ο χώρος εκτός των μπλοκ στο θυγατρικό πρότυπο εκτελείται πριν από την απόδοση του προτύπου διάταξης, οπότε μπορείτε να τον χρησιμοποιήσετε για να ορίσετε μεταβλητές όπως `{var $foo = bar}` και να διαδώσετε δεδομένα σε όλη την αλυσίδα κληρονομικότητας: ```latte {layout 'layout.latte'} @@ -106,51 +106,50 @@ ``` -Κληρονομικότητα πολλαπλών επιπέδων .[#toc-multilevel-inheritance] ------------------------------------------------------------------ +Κληρονομικότητα πολλαπλών επιπέδων +---------------------------------- Μπορείτε να χρησιμοποιήσετε όσα επίπεδα κληρονομικότητας χρειάζεστε. Ένας συνηθισμένος τρόπος χρήσης της κληρονομικότητας διάταξης είναι η ακόλουθη προσέγγιση τριών επιπέδων: -1) Δημιουργήστε ένα πρότυπο `layout.latte` που κρατάει την κύρια εμφάνιση και αίσθηση του ιστότοπού σας. -2) Δημιουργήστε ένα πρότυπο `layout-SECTIONNAME.latte` για κάθε τμήμα του ιστότοπού σας. Για παράδειγμα, `layout-news.latte`, `layout-blog.latte` κ.λπ. Όλα αυτά τα πρότυπα επεκτείνουν το `layout.latte` και περιλαμβάνουν στυλ/σχεδιασμό για κάθε τμήμα. -3) Δημιουργήστε μεμονωμένα πρότυπα για κάθε τύπο σελίδας, όπως ένα άρθρο ειδήσεων ή μια καταχώρηση σε ιστολόγιο. Αυτά τα πρότυπα επεκτείνουν το κατάλληλο πρότυπο τμήματος. +1) Δημιουργήστε ένα πρότυπο `layout.latte` που περιέχει τον κύριο σκελετό της εμφάνισης του ιστότοπου. +2) Δημιουργήστε ένα πρότυπο `layout-SECTIONNAME.latte` για κάθε ενότητα του ιστότοπού σας. Για παράδειγμα, `layout-news.latte`, `layout-blog.latte` κ.λπ. Όλα αυτά τα πρότυπα επεκτείνουν το `layout.latte` και περιλαμβάνουν στυλ & σχεδιασμό ειδικά για κάθε ενότητα. +3) Δημιουργήστε μεμονωμένα πρότυπα για κάθε τύπο σελίδας, για παράδειγμα, ένα άρθρο εφημερίδας ή μια καταχώρηση ιστολογίου. Αυτά τα πρότυπα επεκτείνουν το αντίστοιχο πρότυπο ενότητας. -Κληρονομικότητα δυναμικής διάταξης .[#toc-dynamic-layout-inheritance] ---------------------------------------------------------------------- -Μπορείτε να χρησιμοποιήσετε μια μεταβλητή ή οποιαδήποτε έκφραση PHP ως όνομα του προτύπου γονέα, ώστε η κληρονομικότητα να συμπεριφέρεται δυναμικά: +Δυναμική κληρονομικότητα +------------------------ +Ως όνομα του γονικού προτύπου μπορεί να χρησιμοποιηθεί μια μεταβλητή ή οποιαδήποτε έκφραση PHP, οπότε η κληρονομικότητα μπορεί να συμπεριφέρεται δυναμικά: ```latte {layout $standalone ? 'minimum.latte' : 'layout.latte'} ``` -Μπορείτε επίσης να χρησιμοποιήσετε το API Latte για να επιλέξετε [αυτόματα |develop#automatic-layout-lookup] το πρότυπο διάταξης. +Μπορείτε επίσης να χρησιμοποιήσετε το Latte API για να επιλέξετε [αυτόματα |develop#Αυτόματη Αναζήτηση Layout] το πρότυπο διάταξης. -Συμβουλές .[#toc-tips] ----------------------- -Ακολουθούν ορισμένες συμβουλές για την εργασία με την κληρονομικότητα διάταξης: +Συμβουλές +--------- +Εδώ είναι μερικές συμβουλές για την εργασία με την κληρονομικότητα διάταξης: -- Εάν χρησιμοποιείτε το `{layout}` σε ένα πρότυπο, πρέπει να είναι η πρώτη ετικέτα προτύπου σε αυτό το πρότυπο. +- Αν χρησιμοποιήσετε `{layout}` σε ένα πρότυπο, πρέπει να είναι το πρώτο tag προτύπου σε αυτό το πρότυπο. -- Η διάταξη μπορεί να [αναζητηθεί αυτόματα |develop#automatic-layout-lookup] (όπως στους [παρουσιαστές |application:templates#search-for-templates]). Σε αυτή την περίπτωση, αν το πρότυπο δεν πρέπει να έχει διάταξη, θα το υποδείξει με την ετικέτα `{layout none}`. +- Η διάταξη μπορεί να [αναζητηθεί αυτόματα |develop#Αυτόματη Αναζήτηση Layout] (όπως για παράδειγμα στους [presenters |application:templates#Αναζήτηση προτύπου]). Σε αυτή την περίπτωση, αν το πρότυπο δεν πρέπει να έχει διάταξη, το δηλώνει με το tag `{layout none}`. -- Η ετικέτα `{layout}` έχει ψευδώνυμο `{extends}`. +- Το tag `{layout}` έχει ένα ψευδώνυμο `{extends}`. -- Το όνομα αρχείου του εκτεταμένου προτύπου εξαρτάται από τον [φορτωτή προτύπων |extending-latte#Loaders]. +- Το όνομα του αρχείου διάταξης εξαρτάται από τον [loader |loaders]. -- Μπορείτε να έχετε όσα μπλοκ θέλετε. Θυμηθείτε, τα υποδείγματα-παιδιά δεν χρειάζεται να ορίζουν όλα τα μπλοκ γονέων, οπότε μπορείτε να συμπληρώσετε λογικές προεπιλογές σε πολλά μπλοκ και στη συνέχεια να ορίσετε μόνο αυτά που χρειάζεστε αργότερα. +- Μπορείτε να έχετε όσα μπλοκ θέλετε. Θυμηθείτε, τα θυγατρικά πρότυπα δεν χρειάζεται να ορίζουν όλα τα γονικά μπλοκ, οπότε μπορείτε να συμπληρώσετε λογικές προεπιλεγμένες τιμές σε πολλά μπλοκ και στη συνέχεια να ορίσετε μόνο αυτά που χρειάζεστε αργότερα. -Μπλοκ `{block}` .{toc: Blocks} -============================== +Μπλοκ `{block}` +=============== .[note] -Βλέπε επίσης ανώνυμο [`{block}` |tags#block] +Δείτε επίσης το ανώνυμο [`{block}` |tags#block] -Ένα μπλοκ παρέχει έναν τρόπο να αλλάξετε τον τρόπο απόδοσης ενός συγκεκριμένου τμήματος ενός προτύπου, αλλά δεν παρεμβαίνει με κανέναν τρόπο στη λογική γύρω από αυτό. Ας πάρουμε το παρακάτω παράδειγμα για να δείξουμε πώς λειτουργεί ένα μπλοκ και κυρίως πώς δεν λειτουργεί: +Ένα μπλοκ αντιπροσωπεύει έναν τρόπο αλλαγής του τρόπου απόδοσης ενός συγκεκριμένου τμήματος ενός προτύπου, αλλά δεν παρεμβαίνει καθόλου στη λογική γύρω από αυτό. Στο παρακάτω παράδειγμα, θα δείξουμε πώς λειτουργεί ένα μπλοκ, αλλά και πώς δεν λειτουργεί: -```latte -{* parent.Latte *} +```latte .{file: parent.latte} {foreach $posts as $post} {block post}

                                              {$post->title}

                                              @@ -159,10 +158,9 @@ {/foreach} ``` -Αν αποδώσετε αυτό το πρότυπο, το αποτέλεσμα θα είναι ακριβώς το ίδιο με ή χωρίς τις ετικέτες μπλοκ. Τα μπλοκ έχουν πρόσβαση σε μεταβλητές από εξωτερικά πεδία εφαρμογής. Είναι απλώς ένας τρόπος για να το κάνετε να μπορεί να παρακαμφθεί από ένα πρότυπο-παιδί: +Αν αποδώσετε αυτό το πρότυπο, το αποτέλεσμα θα είναι ακριβώς το ίδιο με ή χωρίς τα tags `{block}`. Τα μπλοκ έχουν πρόσβαση σε μεταβλητές από εξωτερικά πεδία ορισμού. Απλώς δίνουν τη δυνατότητα να αντικατασταθούν από ένα θυγατρικό πρότυπο: -```latte -{* child.Latte *} +```latte .{file: child.latte} {layout 'parent.Latte'} {block post} @@ -173,7 +171,7 @@ {/block} ``` -Τώρα, κατά την απόδοση του παιδικού προτύπου, ο βρόχος θα χρησιμοποιήσει το μπλοκ που ορίζεται στο παιδικό πρότυπο `child.Latte` αντί για αυτό που ορίζεται στο βασικό `parent.Latte`; το εκτελούμενο πρότυπο είναι τότε ισοδύναμο με το ακόλουθο: +Τώρα, κατά την απόδοση του θυγατρικού προτύπου, ο βρόχος θα χρησιμοποιήσει το μπλοκ που ορίζεται στο θυγατρικό πρότυπο `child.Latte` αντί για το μπλοκ που ορίζεται στο `parent.Latte`. Το εκτελούμενο πρότυπο είναι τότε ισοδύναμο με το ακόλουθο: ```latte {foreach $posts as $post} @@ -184,7 +182,7 @@ {/foreach} ``` -Ωστόσο, αν δημιουργήσουμε μια νέα μεταβλητή μέσα σε ένα μπλοκ με όνομα ή αντικαταστήσουμε μια τιμή μιας υπάρχουσας, η αλλαγή θα είναι ορατή μόνο μέσα στο μπλοκ: +Ωστόσο, αν δημιουργήσουμε μια νέα μεταβλητή μέσα σε ένα ονομασμένο μπλοκ ή αντικαταστήσουμε την τιμή μιας υπάρχουσας, η αλλαγή θα είναι ορατή μόνο μέσα στο μπλοκ: ```latte {var $foo = 'foo'} @@ -193,17 +191,17 @@ {var $bar = 'bar'} {/block} -foo: {$foo} // prints: foo -bar: {$bar ?? 'not defined'} // prints: not defined +foo: {$foo} // εκτυπώνει: foo +bar: {$bar ?? 'not defined'} // εκτυπώνει: not defined ``` -Τα περιεχόμενα του μπλοκ μπορούν να τροποποιηθούν από [φίλτρα |syntax#filters]. Το ακόλουθο παράδειγμα αφαιρεί όλη την HTML και την αλλαγή τίτλου: +Το περιεχόμενο του μπλοκ μπορεί να τροποποιηθεί χρησιμοποιώντας [φίλτρα |syntax#Φίλτρα]. Το παρακάτω παράδειγμα αφαιρεί όλο το HTML και αλλάζει το μέγεθος των γραμμάτων: ```latte {block title|stripHtml|capitalize}...{/block} ``` -Η ετικέτα μπορεί επίσης να γραφτεί ως [n:attribute |syntax#n:attributes]: +Το tag μπορεί επίσης να γραφτεί ως [n:attribute |syntax#n:attributes]: ```latte
                                              @@ -212,10 +210,10 @@ bar: {$bar ?? 'not defined'} // prints: not defined ``` -Τοπικά μπλοκ .[#toc-local-blocks] ---------------------------------- +Τοπικά μπλοκ +------------ -Κάθε μπλοκ παρακάμπτει το περιεχόμενο του γονικού μπλοκ με το ίδιο όνομα. Εκτός από τα τοπικά μπλοκ. Είναι κάτι σαν τις ιδιωτικές μεθόδους της κλάσης. Μπορείτε να δημιουργήσετε ένα πρότυπο χωρίς να ανησυχείτε ότι - λόγω σύμπτωσης των ονομάτων των μπλοκ - θα αντικατασταθούν από το δεύτερο πρότυπο. +Κάθε μπλοκ αντικαθιστά το περιεχόμενο του γονικού μπλοκ με το ίδιο όνομα - εκτός από τα τοπικά μπλοκ. Στις κλάσεις, θα ήταν κάτι σαν ιδιωτικές μέθοδοι. Μπορείτε έτσι να δημιουργήσετε ένα πρότυπο χωρίς να ανησυχείτε ότι, λόγω σύμπτωσης ονομάτων μπλοκ, θα αντικατασταθούν από άλλο πρότυπο. ```latte {block local helper} @@ -224,13 +222,13 @@ bar: {$bar ?? 'not defined'} // prints: not defined ``` -Εκτύπωση μπλοκ `{include}` .{toc: Printing Blocks} --------------------------------------------------- +Απόδοση μπλοκ `{include}` +------------------------- .[note] -Βλέπε επίσης [`{include file}` |tags#include] +Δείτε επίσης [`{include file}` |tags#include] -Για να εκτυπώσετε ένα μπλοκ σε ένα συγκεκριμένο σημείο, χρησιμοποιήστε την ετικέτα `{include blockname}`: +Για να εκτυπώσετε ένα μπλοκ σε μια συγκεκριμένη θέση, χρησιμοποιήστε το tag `{include blockname}`: ```latte {block title}{/block} @@ -238,32 +236,28 @@ bar: {$bar ?? 'not defined'} // prints: not defined

                                              {include title}

                                              ``` -Μπορείτε επίσης να εμφανίσετε μπλοκ από άλλο πρότυπο: +Μπορείτε επίσης να εκτυπώσετε ένα μπλοκ από άλλο πρότυπο: ```latte {include footer from 'main.latte'} ``` -εκτός αν το μπλοκ έχει οριστεί στο ίδιο αρχείο όπου περιλαμβάνεται. Ωστόσο, έχουν πρόσβαση στις παγκόσμιες μεταβλητές. +Το αποδιδόμενο μπλοκ δεν έχει πρόσβαση στις μεταβλητές του ενεργού context, εκτός αν το μπλοκ ορίζεται στο ίδιο αρχείο όπου και ενσωματώνεται. Έχει όμως πρόσβαση στις καθολικές μεταβλητές. -Μπορείτε να περάσετε μεταβλητές με αυτόν τον τρόπο: +Μπορείτε να περάσετε μεταβλητές στο μπλοκ με αυτόν τον τρόπο: ```latte -{* από Latte 2.9 *} {include footer, foo: bar, id: 123} - -{* πριν το Latte 2.9 *} -{include footer, foo => bar, id => 123} ``` -Μπορείτε να χρησιμοποιήσετε μια μεταβλητή ή οποιαδήποτε έκφραση στην PHP ως όνομα μπλοκ. Σε αυτή την περίπτωση, προσθέστε τη λέξη-κλειδί `block` πριν από τη μεταβλητή, ώστε να είναι γνωστό κατά τη μεταγλώττιση ότι πρόκειται για μπλοκ και όχι για [insert template |tags#include], το όνομα του οποίου θα μπορούσε επίσης να βρίσκεται στη μεταβλητή: +Ως όνομα μπλοκ μπορεί να χρησιμοποιηθεί μια μεταβλητή ή οποιαδήποτε έκφραση PHP. Σε αυτή την περίπτωση, προσθέτουμε τη λέξη-κλειδί `block` πριν από τη μεταβλητή, ώστε το Latte να γνωρίζει ήδη κατά τη μεταγλώττιση ότι πρόκειται για μπλοκ και όχι για [ενσωμάτωση προτύπου |tags#include], το όνομα του οποίου θα μπορούσε επίσης να βρίσκεται σε μια μεταβλητή: ```latte {var $name = footer} {include block $name} ``` -Το μπλοκ μπορεί επίσης να εκτυπωθεί μέσα στον εαυτό του, κάτι που είναι χρήσιμο, για παράδειγμα, κατά την απόδοση μιας δενδρικής δομής: +Ένα μπλοκ μπορεί να αποδοθεί και μέσα στον εαυτό του, κάτι που είναι χρήσιμο, για παράδειγμα, κατά την απόδοση μιας δενδρικής δομής: ```latte {define menu, $items} @@ -281,19 +275,19 @@ bar: {$bar ?? 'not defined'} // prints: not defined {/define} ``` -Αντί του `{include menu, ...}` μπορούμε επίσης να γράψουμε `{include this, ...}` όπου `this` σημαίνει τρέχον μπλοκ. +Αντί για `{include menu, ...}`, μπορούμε στη συνέχεια να γράψουμε `{include this, ...}`, όπου `this` σημαίνει το τρέχον μπλοκ. -Το εκτυπωμένο περιεχόμενο μπορεί να τροποποιηθεί με [φίλτρα |syntax#filters]. Το ακόλουθο παράδειγμα αφαιρεί όλη την HTML και την αλλαγή του τίτλου: +Το αποδιδόμενο μπλοκ μπορεί να τροποποιηθεί χρησιμοποιώντας [φίλτρα |syntax#Φίλτρα]. Το παρακάτω παράδειγμα αφαιρεί όλο το HTML και αλλάζει το μέγεθος των γραμμάτων: ```latte {include heading|stripHtml|capitalize} ``` -Γονικό μπλοκ .[#toc-parent-block] ---------------------------------- +Γονικό μπλοκ +------------ -Εάν πρέπει να εκτυπώσετε το περιεχόμενο του μπλοκ από το γονικό πρότυπο, η δήλωση `{include parent}` θα κάνει το κόλπο. Αυτό είναι χρήσιμο αν θέλετε να προσθέσετε στα περιεχόμενα ενός γονικού μπλοκ αντί να το παρακάμψετε πλήρως. +Αν χρειάζεται να εκτυπώσετε το περιεχόμενο ενός μπλοκ από ένα γονικό πρότυπο, χρησιμοποιήστε το `{include parent}`. Αυτό είναι χρήσιμο αν θέλετε απλώς να συμπληρώσετε το περιεχόμενο του γονικού μπλοκ αντί να το αντικαταστήσετε πλήρως. ```latte {block footer} @@ -304,32 +298,31 @@ bar: {$bar ?? 'not defined'} // prints: not defined ``` -Ορισμοί `{define}` .{toc: Definitions} --------------------------------------- +Ορισμοί `{define}` +------------------ -Εκτός από τα μπλοκ, υπάρχουν επίσης "ορισμοί" στο Latte. Είναι συγκρίσιμοι με τις συναρτήσεις στις κανονικές γλώσσες προγραμματισμού. Είναι χρήσιμοι για την επαναχρησιμοποίηση τμημάτων προτύπων για να μην επαναλαμβάνεστε. +Εκτός από τα μπλοκ, υπάρχουν επίσης "ορισμοί" στο Latte. Σε συνηθισμένες γλώσσες προγραμματισμού, θα τους παρομοιάζαμε με συναρτήσεις. Είναι χρήσιμοι για την επαναχρησιμοποίηση τμημάτων προτύπων, ώστε να μην επαναλαμβάνεστε. -Η Latte προσπαθεί να κάνει τα πράγματα εύκολα, οπότε βασικά οι ορισμοί είναι ίδιοι με τα μπλοκ, και **ό,τι λέγεται για τα μπλοκ ισχύει και για τους ορισμούς**. Διαφέρει από τα μπλοκ μόνο με τρεις τρόπους: +Το Latte προσπαθεί να κάνει τα πράγματα απλά, οπότε βασικά οι ορισμοί είναι ίδιοι με τα μπλοκ και **ό,τι λέγεται για τα μπλοκ ισχύει και για τους ορισμούς**. Διαφέρουν από τα μπλοκ στο ότι: -1) μπορούν να δέχονται επιχειρήματα -2) δεν μπορούν να έχουν [φίλτρα |syntax#filters] -3) περικλείονται σε ετικέτες `{define}` και το περιεχόμενο μέσα σε αυτές τις ετικέτες δεν αποστέλλεται στην έξοδο μέχρι να τις συμπεριλάβετε. Χάρη σε αυτό, μπορείτε να τα δημιουργήσετε οπουδήποτε: +1) περικλείονται σε tags `{define}` +2) αποδίδονται μόνο όταν τους ενσωματώνετε μέσω `{include}` +3) μπορούν να τους οριστούν παράμετροι παρόμοια με τις συναρτήσεις στην PHP ```latte {block foo}

                                              Hello

                                              {/block} -{* prints:

                                              Hello

                                              *} +{* εκτυπώνει:

                                              Hello

                                              *} {define bar}

                                              World

                                              {/define} {* δεν εκτυπώνει τίποτα *} {include bar} -{* prints:

                                              World

                                              *} +{* εκτυπώνει:

                                              World

                                              *} ``` -Φανταστείτε να έχετε ένα γενικό βοηθητικό πρότυπο που ορίζει τον τρόπο απόδοσης των φορμών HTML μέσω ορισμών: +Φανταστείτε ότι έχετε ένα βοηθητικό πρότυπο με μια συλλογή ορισμών για το πώς να σχεδιάσετε φόρμες HTML. -```latte -{* forms.latte *} +```latte .{file: forms.latte} {define input, $name, $value, $type = 'text'} {/define} @@ -339,53 +332,51 @@ bar: {$bar ?? 'not defined'} // prints: not defined {/define} ``` -Τα επιχειρήματα ενός ορισμού είναι πάντα προαιρετικά με προεπιλεγμένη τιμή `null`, εκτός αν έχει καθοριστεί προεπιλεγμένη τιμή (εδώ `text` είναι η προεπιλεγμένη τιμή για το `$type`, που είναι δυνατή από το Latte 2.9.1). Από τη Latte 2.7, μπορούν επίσης να δηλωθούν τύποι παραμέτρων: `{define input, string $name, ...}`. +Τα ορίσματα είναι πάντα προαιρετικά με προεπιλεγμένη τιμή `null`, εκτός αν παρέχεται προεπιλεγμένη τιμή (εδώ το `'text'` είναι η προεπιλεγμένη τιμή για το `$type`). Μπορούν επίσης να δηλωθούν τύποι παραμέτρων: `{define input, string $name, ...}`. -Οι ορισμοί δεν έχουν πρόσβαση στις μεταβλητές του ενεργού πλαισίου, αλλά έχουν πρόσβαση σε παγκόσμιες μεταβλητές. - -Συμπεριλαμβάνονται με [τον ίδιο τρόπο όπως το block |#Printing Blocks]: +Φορτώνουμε το πρότυπο με τους ορισμούς χρησιμοποιώντας το [`{import}` |#Οριζόντια επαναχρησιμοποίηση import]. Οι ίδιοι οι ορισμοί αποδίδονται [με τον ίδιο τρόπο όπως τα μπλοκ |#Απόδοση μπλοκ include]: ```latte

                                              {include input, 'password', null, 'password'}

                                              {include textarea, 'comment'}

                                              ``` +Οι ορισμοί δεν έχουν πρόσβαση στις μεταβλητές του ενεργού context, αλλά έχουν πρόσβαση στις καθολικές μεταβλητές. -Δυναμικά ονόματα μπλοκ .[#toc-dynamic-block-names] --------------------------------------------------- -Η Latte επιτρέπει μεγάλη ευελιξία στον ορισμό των μπλοκ, επειδή το όνομα του μπλοκ μπορεί να είναι οποιαδήποτε έκφραση της PHP. Αυτό το παράδειγμα ορίζει τρία μπλοκ με τα ονόματα `hi-Peter`, `hi-John` και `hi-Mary`: +Δυναμικά ονόματα μπλοκ +---------------------- -```latte -{* parent.latte *} +Το Latte επιτρέπει μεγάλη ευελιξία στον ορισμό μπλοκ, καθώς το όνομα του μπλοκ μπορεί να είναι οποιαδήποτε έκφραση PHP. Αυτό το παράδειγμα ορίζει τρία μπλοκ με τα ονόματα `hi-Peter`, `hi-John` και `hi-Mary`: + +```latte .{file: parent.latte} {foreach [Peter, John, Mary] as $name} - {block "hi-$name"}Hi, I am {$name}.{/block} + {block "hi-$name"}Γεια, είμαι ο {$name}.{/block} {/foreach} ``` -Για παράδειγμα, μπορούμε να επαναπροσδιορίσουμε μόνο ένα μπλοκ σε ένα πρότυπο παιδί: +Στο θυγατρικό πρότυπο, μπορούμε στη συνέχεια να επαναπροσδιορίσουμε, για παράδειγμα, μόνο ένα μπλοκ: -```latte -{* child.latte *} -{block hi-John}Hello. I am {$name}.{/block} +```latte .{file: child.latte} +{block hi-John}Γεια. Είμαι ο {$name}.{/block} ``` -Έτσι, η έξοδος θα μοιάζει με αυτό: +Έτσι, η έξοδος θα μοιάζει κάπως έτσι: ```latte -Hi, I am Peter. -Hello. I am John. -Hi, I am Mary. +Γεια, είμαι ο Peter. +Γεια. Είμαι ο John. +Γεια, είμαι η Mary. ``` -Έλεγχος ύπαρξης μπλοκ `{ifset}` .{toc: Checking Block Existence} ----------------------------------------------------------------- +Έλεγχος ύπαρξης μπλοκ `{ifset}` +------------------------------- .[note] -Βλέπε επίσης [`{ifset $var}` |tags#ifset-elseifset] +Δείτε επίσης [`{ifset $var}` |tags#ifset elseifset] -Χρησιμοποιήστε τη δοκιμή `{ifset blockname}` για να ελέγξετε αν ένα μπλοκ (ή περισσότερα μπλοκ) υπάρχει στο τρέχον πλαίσιο: +Χρησιμοποιώντας τον έλεγχο `{ifset blockname}`, ελέγχουμε αν ένα μπλοκ (ή περισσότερα μπλοκ) υπάρχει στο τρέχον context: ```latte {ifset footer} @@ -397,7 +388,7 @@ Hi, I am Mary. {/ifset} ``` -Μπορείτε να χρησιμοποιήσετε μια μεταβλητή ή οποιαδήποτε έκφραση στην PHP ως όνομα μπλοκ. Σε αυτή την περίπτωση, προσθέστε τη λέξη-κλειδί `block` πριν από τη μεταβλητή για να καταστήσετε σαφές ότι δεν είναι η [μεταβλητή |tags#ifset-elseifset] που ελέγχεται: +Ως όνομα μπλοκ μπορεί να χρησιμοποιηθεί μια μεταβλητή ή οποιαδήποτε έκφραση PHP. Σε αυτή την περίπτωση, προσθέτουμε τη λέξη-κλειδί `block` πριν από τη μεταβλητή για να καταστήσουμε σαφές ότι δεν πρόκειται για έλεγχο ύπαρξης [μεταβλητών |tags#ifset elseifset]: ```latte {ifset block $name} @@ -405,71 +396,69 @@ Hi, I am Mary. {/ifset} ``` +Η ύπαρξη μπλοκ ελέγχεται επίσης από τη συνάρτηση [`hasBlock()` |functions#hasBlock]: -Συμβουλές .[#toc-tips] ----------------------- -Ακολουθούν μερικές συμβουλές για την εργασία με μπλοκ: - -- Το τελευταίο μπλοκ ανώτατου επιπέδου δεν χρειάζεται να έχει ετικέτα κλεισίματος (το μπλοκ τελειώνει με το τέλος του εγγράφου). Αυτό απλοποιεί τη συγγραφή των προτύπων-παιδιών, τα οποία ένα πρωτογενές μπλοκ. +```latte +{if hasBlock(header) || hasBlock(footer)} + ... +{/if} +``` -- Για μεγαλύτερη αναγνωσιμότητα, μπορείτε προαιρετικά να δώσετε ένα όνομα στην ετικέτα `{/block}`, για παράδειγμα `{/block footer}`. Ωστόσο, το όνομα πρέπει να ταιριάζει με το όνομα του μπλοκ. Σε μεγαλύτερα πρότυπα, αυτή η τεχνική σας βοηθά να βλέπετε ποιες ετικέτες μπλοκ κλείνουν. -- Δεν μπορείτε να ορίσετε απευθείας πολλαπλές ετικέτες μπλοκ με το ίδιο όνομα στο ίδιο πρότυπο. Αυτό όμως μπορεί να επιτευχθεί με τη χρήση [δυναμικών ονομάτων μπλοκ |#dynamic block names]. +Συμβουλές +--------- +Μερικές συμβουλές για την εργασία με μπλοκ: -- Μπορείτε να χρησιμοποιήσετε [n:attributes |syntax#n:attributes] για να ορίσετε μπλοκ όπως `

                                              Welcome to my awesome homepage

                                              ` +- Το τελευταίο μπλοκ ανώτατου επιπέδου δεν χρειάζεται να έχει tag κλεισίματος (το μπλοκ τελειώνει στο τέλος του εγγράφου). Αυτό απλοποιεί τη γραφή θυγατρικών προτύπων που περιέχουν ένα κύριο μπλοκ. -- Τα μπλοκ μπορούν επίσης να χρησιμοποιηθούν χωρίς ονόματα μόνο για την εφαρμογή των [φίλτρων |syntax#filters] στην έξοδο: `{block|strip} hello {/block}` +- Για καλύτερη αναγνωσιμότητα, μπορείτε να δώσετε το όνομα του μπλοκ στο tag `{/block}`, για παράδειγμα `{/block footer}`. Ωστόσο, το όνομα πρέπει να ταιριάζει με το όνομα του μπλοκ. Σε μεγαλύτερα πρότυπα, αυτή η τεχνική σας βοηθά να δείτε ποια tags μπλοκ κλείνουν. +- Δεν μπορείτε να ορίσετε απευθείας πολλαπλά tags μπλοκ με το ίδιο όνομα στο ίδιο πρότυπο. Ωστόσο, αυτό μπορεί να επιτευχθεί χρησιμοποιώντας [#δυναμικά ονόματα μπλοκ]. -Οριζόντια επαναχρησιμοποίηση `{import}` .{toc: Horizontal Reuse} -================================================================ +- Μπορείτε να χρησιμοποιήσετε [n:attributes |syntax#n:attributes] για να ορίσετε μπλοκ όπως `

                                              Καλώς ήρθατε στην καταπληκτική μου αρχική σελίδα

                                              ` -Η οριζόντια επαναχρησιμοποίηση είναι ένας τρίτος μηχανισμός επαναχρησιμοποίησης και κληρονομικότητας στο Latte. Σας επιτρέπει να φορτώνετε μπλοκ από άλλα πρότυπα. Είναι παρόμοιο με τη δημιουργία ενός αρχείου PHP με βοηθητικές συναρτήσεις ή ένα χαρακτηριστικό γνώρισμα. +- Τα μπλοκ μπορούν επίσης να χρησιμοποιηθούν χωρίς ονόματα μόνο για την εφαρμογή [φίλτρων |syntax#Φίλτρα]: `{block|strip} γεια {/block}` -Αν και η κληρονομικότητα προτύπων διάταξης είναι ένα από τα πιο ισχυρά χαρακτηριστικά του Latte, περιορίζεται σε απλή κληρονομικότητα- ένα πρότυπο μπορεί να επεκτείνει μόνο ένα άλλο πρότυπο. Αυτός ο περιορισμός καθιστά την κληρονομικότητα προτύπων απλή στην κατανόηση και εύκολη στην αποσφαλμάτωση: -```latte -{layout 'layout.latte'} +Οριζόντια επαναχρησιμοποίηση `{import}` +======================================= -{block title}...{/block} -{block content}...{/block} -``` +Η οριζόντια επαναχρησιμοποίηση είναι ο τρίτος μηχανισμός στο Latte για επαναχρησιμοποίηση και κληρονομικότητα. Επιτρέπει τη φόρτωση μπλοκ από άλλα πρότυπα. Είναι παρόμοιο με τη δημιουργία ενός αρχείου με βοηθητικές συναρτήσεις στην PHP, το οποίο στη συνέχεια φορτώνουμε χρησιμοποιώντας `require`. -Η οριζόντια επαναχρησιμοποίηση είναι ένας τρόπος για την επίτευξη του ίδιου στόχου με την πολλαπλή κληρονομικότητα, αλλά χωρίς τη σχετική πολυπλοκότητα: +Αν και η κληρονομικότητα διάταξης προτύπου είναι ένα από τα πιο ισχυρά χαρακτηριστικά του Latte, περιορίζεται στην απλή κληρονομικότητα - ένα πρότυπο μπορεί να επεκτείνει μόνο ένα άλλο πρότυπο. Η οριζόντια επαναχρησιμοποίηση είναι ένας τρόπος για να επιτευχθεί πολλαπλή κληρονομικότητα. -```latte -{layout 'layout.latte'} +Ας έχουμε ένα αρχείο με ορισμούς μπλοκ: -{import 'blocks.latte'} +```latte .{file: blocks.latte} +{block sidebar}...{/block} -{block title}...{/block} -{block content}...{/block} +{block menu}...{/block} ``` -Η δήλωση `{import}` λέει στο Latte να εισάγει όλα τα μπλοκ και τους [ορισμούς |#definitions] που ορίζονται στο `blocks.latte` στο τρέχον πρότυπο. +Χρησιμοποιώντας την εντολή `{import}`, εισάγουμε όλα τα μπλοκ και τους [ορισμούς |#Ορισμοί define] που ορίζονται στο `blocks.latte` σε ένα άλλο πρότυπο: -```latte -{* blocks.latte *} +```latte .{file: child.latte} +{import 'blocks.latte'} -{block sidebar}...{/block} +{* τώρα μπορείτε να χρησιμοποιήσετε τα μπλοκ sidebar και menu *} ``` -Σε αυτό το παράδειγμα, η δήλωση `{import}` εισάγει το μπλοκ `sidebar` στο κύριο πρότυπο. +Αν εισάγετε τα μπλοκ σε ένα γονικό πρότυπο (δηλαδή, χρησιμοποιήσετε `{import}` στο `layout.latte`), τα μπλοκ θα είναι διαθέσιμα και σε όλα τα θυγατρικά πρότυπα, κάτι που είναι πολύ πρακτικό. -Το εισαγόμενο πρότυπο δεν πρέπει να [επεκτείνει |#Layout Inheritance] άλλο πρότυπο και το σώμα του πρέπει να είναι κενό. Ωστόσο, το εισαγόμενο πρότυπο μπορεί να εισάγει άλλα πρότυπα. +Το πρότυπο που προορίζεται για εισαγωγή (π.χ., `blocks.latte`) δεν πρέπει να [επεκτείνει |#Κληρονομικότητα διάταξης layout] άλλο πρότυπο, δηλαδή να χρησιμοποιεί `{layout}`. Μπορεί όμως να εισάγει άλλα πρότυπα. -Η ετικέτα `{import}` πρέπει να είναι η πρώτη ετικέτα προτύπου μετά το `{layout}`. Το όνομα του προτύπου μπορεί να είναι οποιαδήποτε έκφραση PHP: +Το tag `{import}` πρέπει να είναι το πρώτο tag προτύπου μετά το `{layout}`. Το όνομα του προτύπου μπορεί να είναι οποιαδήποτε έκφραση PHP: ```latte {import $ajax ? 'ajax.latte' : 'not-ajax.latte'} ``` -Μπορείτε να χρησιμοποιήσετε όσες δηλώσεις `{import}` θέλετε σε οποιοδήποτε συγκεκριμένο πρότυπο. Αν δύο εισαγόμενα πρότυπα ορίζουν το ίδιο μπλοκ, το πρώτο κερδίζει. Ωστόσο, η υψηλότερη προτεραιότητα δίνεται στο κύριο πρότυπο, το οποίο μπορεί να αντικαταστήσει οποιοδήποτε εισαγόμενο μπλοκ. +Μπορείτε να χρησιμοποιήσετε όσες εντολές `{import}` θέλετε σε ένα πρότυπο. Αν δύο εισαγόμενα πρότυπα ορίζουν το ίδιο μπλοκ, κερδίζει το πρώτο. Ωστόσο, την υψηλότερη προτεραιότητα έχει το κύριο πρότυπο, το οποίο μπορεί να αντικαταστήσει οποιοδήποτε εισαγόμενο μπλοκ. -Όλα τα μπλοκ που αντικαθίστανται μπορούν να συμπεριληφθούν σταδιακά εισάγοντας τα ως [γονικό μπλοκ |#parent block]: +Το περιεχόμενο των αντικατασταθέντων μπλοκ μπορεί να διατηρηθεί εισάγοντας το μπλοκ με τον ίδιο τρόπο όπως εισάγεται ένα [#γονικό μπλοκ]: ```latte -{layout 'base.latte'} +{layout 'layout.latte'} {import 'blocks.latte'} @@ -481,17 +470,17 @@ Hi, I am Mary. {block content}...{/block} ``` -Σε αυτό το παράδειγμα, το `{include parent}` θα καλέσει σωστά το μπλοκ `sidebar` από το πρότυπο `blocks.latte`. +Σε αυτό το παράδειγμα, το `{include parent}` καλεί το μπλοκ `sidebar` από το πρότυπο `blocks.latte`. -Κληρονομικότητα μονάδων `{embed}` .{toc: Unit Inheritance}{data-version:2.9} -============================================================================ +Κληρονομικότητα μονάδας `{embed}` +================================= -Η κληρονομικότητα μονάδας μεταφέρει την ιδέα της κληρονομικότητας διάταξης στο επίπεδο των τμημάτων περιεχομένου. Ενώ η κληρονομικότητα διάταξης λειτουργεί με "σκελετούς εγγράφων", οι οποίοι ζωντανεύουν από πρότυπα-παιδιά, η κληρονομικότητα μονάδων σας επιτρέπει να δημιουργείτε σκελετούς για μικρότερες μονάδες περιεχομένου και να τους επαναχρησιμοποιείτε οπουδήποτε θέλετε. +Η κληρονομικότητα μονάδας επεκτείνει την ιδέα της κληρονομικότητας διάταξης στο επίπεδο των τμημάτων περιεχομένου. Ενώ η κληρονομικότητα διάταξης λειτουργεί με τον "σκελετό του εγγράφου", τον οποίο ζωντανεύουν τα θυγατρικά πρότυπα, η κληρονομικότητα μονάδας σας επιτρέπει να δημιουργείτε σκελετούς για μικρότερες μονάδες περιεχομένου και να τις επαναχρησιμοποιείτε οπουδήποτε θέλετε. -Στην κληρονομικότητα μονάδων η ετικέτα `{embed}` είναι το κλειδί. Συνδυάζει τη συμπεριφορά των `{include}` και `{layout}`. Σας επιτρέπει να συμπεριλάβετε τα περιεχόμενα ενός άλλου προτύπου ή μπλοκ και να περάσετε προαιρετικά μεταβλητές, όπως ακριβώς κάνει το `{include}`. Σας επιτρέπει επίσης να παρακάμψετε οποιοδήποτε μπλοκ που ορίζεται μέσα στο συμπεριλαμβανόμενο πρότυπο, όπως κάνει το `{layout}`. +Στην κληρονομικότητα μονάδας, το κλειδί είναι το tag `{embed}`. Συνδυάζει τη συμπεριφορά των `{include}` και `{layout}`. Επιτρέπει την ενσωμάτωση του περιεχομένου ενός άλλου προτύπου ή μπλοκ και προαιρετικά την παράδοση μεταβλητών, όπως στην περίπτωση του `{include}`. Επιτρέπει επίσης την αντικατάσταση οποιουδήποτε μπλοκ που ορίζεται μέσα στο ενσωματωμένο πρότυπο, όπως κατά τη χρήση του `{layout}`. -Για παράδειγμα, θα χρησιμοποιήσουμε το πτυσσόμενο στοιχείο ακορντεόν. Ας ρίξουμε μια ματιά στον σκελετό του στοιχείου στο πρότυπο `collapsible.latte`: +Για παράδειγμα, θα χρησιμοποιήσουμε ένα στοιχείο ακορντεόν. Ας δούμε τον σκελετό του στοιχείου που είναι αποθηκευμένος στο πρότυπο `collapsible.latte`: ```latte
                                              @@ -505,14 +494,14 @@ Hi, I am Mary.
                                              ``` -Οι ετικέτες `{block}` ορίζουν δύο μπλοκ που μπορούν να συμπληρώσουν τα πρότυπα-παιδιά. Ναι, όπως και στην περίπτωση του προτύπου γονέα στο πρότυπο κληρονομικότητας διάταξης. Βλέπετε επίσης τη μεταβλητή `$modifierClass`. +Τα tags `{block}` ορίζουν δύο μπλοκ που μπορούν να συμπληρώσουν τα θυγατρικά πρότυπα. Ναι, όπως στην περίπτωση του γονικού προτύπου στην κληρονομικότητα διάταξης. Βλέπετε επίσης τη μεταβλητή `$modifierClass`. -Ας χρησιμοποιήσουμε το στοιχείο μας στο πρότυπο. Σε αυτό το σημείο έρχεται το `{embed}`. Είναι ένα εξαιρετικά ισχυρό κομμάτι του κιτ που μας επιτρέπει να κάνουμε όλα τα πράγματα: να συμπεριλάβουμε τα περιεχόμενα του προτύπου του στοιχείου, να προσθέσουμε μεταβλητές σε αυτό και να προσθέσουμε μπλοκ με προσαρμοσμένη HTML σε αυτό: +Ας χρησιμοποιήσουμε το στοιχείο μας σε ένα πρότυπο. Εδώ έρχεται στο προσκήνιο το `{embed}`. Είναι ένα εξαιρετικά ισχυρό tag που μας επιτρέπει να κάνουμε τα πάντα: να ενσωματώσουμε το περιεχόμενο του προτύπου του στοιχείου, να προσθέσουμε μεταβλητές σε αυτό και να προσθέσουμε μπλοκ σε αυτό με το δικό μας HTML: ```latte {embed 'collapsible.latte', modifierClass: my-style} {block title} - Hello World + Γεια σου Κόσμε {/block} {block content} @@ -527,7 +516,7 @@ Hi, I am Mary. ```latte

                                              - Hello World + Γεια σου Κόσμε

                                              @@ -537,7 +526,7 @@ Hi, I am Mary.
                                              ``` -Τα μπλοκ μέσα σε ετικέτες ενσωμάτωσης σχηματίζουν ένα ξεχωριστό επίπεδο, ανεξάρτητο από άλλα μπλοκ. Επομένως, μπορούν να έχουν το ίδιο όνομα με το μπλοκ που βρίσκεται έξω από την ενσωμάτωση και δεν επηρεάζονται με κανέναν τρόπο. Χρησιμοποιώντας την ετικέτα [include |#Printing Blocks] μέσα στις ετικέτες `{embed}` μπορείτε να εισάγετε μπλοκ που δημιουργήθηκαν εδώ, μπλοκ από το ενσωματωμένο πρότυπο (τα οποία *δεν* είναι [τοπικά |#Local Blocks]), καθώς και μπλοκ από το κύριο πρότυπο τα οποία *είναι* τοπικά. Μπορείτε επίσης να [εισάγετε μπλοκ |#Horizontal Reuse] από άλλα αρχεία: +Τα μπλοκ μέσα στα ενσωματωμένα tags σχηματίζουν ένα ξεχωριστό επίπεδο ανεξάρτητο από τα άλλα μπλοκ. Επομένως, μπορούν να έχουν το ίδιο όνομα με ένα μπλοκ εκτός της ενσωμάτωσης και δεν επηρεάζονται καθόλου. Χρησιμοποιώντας το tag [include |#Απόδοση μπλοκ include] μέσα στα tags `{embed}`, μπορείτε να ενσωματώσετε μπλοκ που δημιουργήθηκαν εδώ, μπλοκ από το ενσωματωμένο πρότυπο (που *δεν είναι* [τοπικά |#Τοπικά μπλοκ]) και επίσης μπλοκ από το κύριο πρότυπο, τα οποία αντίθετα *είναι* τοπικά. Μπορείτε επίσης να [εισάγετε μπλοκ |#Οριζόντια επαναχρησιμοποίηση import] από άλλα αρχεία: ```latte {block outer}…{/block} @@ -549,18 +538,18 @@ Hi, I am Mary. {block inner}…{/block} {block title} - {include inner} {* λειτουργεί, το μπλοκ ορίζεται μέσα στην embed *} + {include inner} {* λειτουργεί, το μπλοκ ορίζεται μέσα στο embed *} {include hello} {* λειτουργεί, το μπλοκ είναι τοπικό σε αυτό το πρότυπο *} {include content} {* λειτουργεί, το μπλοκ ορίζεται στο ενσωματωμένο πρότυπο *} - {include aBlockDefinedInImportedTemplate} {* έργα *} - {include outer} {* δεν λειτουργεί! - το μπλοκ βρίσκεται στο εξωτερικό στρώμα *} + {include aBlockDefinedInImportedTemplate} {* λειτουργεί *} + {include outer} {* δεν λειτουργεί! - το μπλοκ βρίσκεται στο εξωτερικό επίπεδο *} {/block} {/embed} ``` -Τα ενσωματωμένα πρότυπα δεν έχουν πρόσβαση στις μεταβλητές του ενεργού πλαισίου, αλλά έχουν πρόσβαση στις παγκόσμιες μεταβλητές. +Τα ενσωματωμένα πρότυπα δεν έχουν πρόσβαση στις μεταβλητές του ενεργού context, αλλά έχουν πρόσβαση στις καθολικές μεταβλητές. -Με το `{embed}` μπορείτε να εισάγετε όχι μόνο πρότυπα αλλά και άλλα μπλοκ, οπότε το προηγούμενο παράδειγμα θα μπορούσε να γραφτεί ως εξής: .{data-version:2.10} +Με το `{embed}` μπορείτε να ενσωματώσετε όχι μόνο πρότυπα, αλλά και άλλα μπλοκ, και επομένως το προηγούμενο παράδειγμα θα μπορούσε να γραφτεί με αυτόν τον τρόπο: ```latte {define collapsible} @@ -575,36 +564,36 @@ Hi, I am Mary. {embed collapsible, modifierClass: my-style} {block title} - Hello World + Γεια σου Κόσμε {/block} ... {/embed} ``` -Αν περάσουμε μια έκφραση στο `{embed}` και δεν είναι σαφές αν πρόκειται για μπλοκ ή για όνομα αρχείου, προσθέστε τη λέξη-κλειδί `block` ή `file`: +Αν περάσουμε μια έκφραση στο `{embed}` και δεν είναι σαφές αν πρόκειται για όνομα μπλοκ ή αρχείου, προσθέτουμε τη λέξη-κλειδί `block` ή `file`: ```latte {embed block $name} ... {/embed} ``` -Περιπτώσεις χρήσης .[#toc-use-cases] -==================================== +Περιπτώσεις χρήσης +================== -Υπάρχουν διάφοροι τύποι κληρονομικότητας και επαναχρησιμοποίησης κώδικα στο Latte. Ας συνοψίσουμε τις κύριες έννοιες για περισσότερη σαφήνεια: +Στο Latte υπάρχουν διάφοροι τύποι κληρονομικότητας και επαναχρησιμοποίησης κώδικα. Ας συνοψίσουμε τις κύριες έννοιες για μεγαλύτερη σαφήνεια: `{include template}` -------------------- -**Περίπτωση χρήσης:** Χρήση των `header.latte` & `footer.latte` μέσα στο `layout.latte`. +**Περίπτωση χρήσης**: Χρήση των `header.latte` και `footer.latte` μέσα στο `layout.latte`. `header.latte` ```latte ``` @@ -612,7 +601,7 @@ Hi, I am Mary. ```latte
                                              -
                                              Copyright
                                              +
                                              Πνευματικά δικαιώματα
                                              ``` @@ -630,7 +619,7 @@ Hi, I am Mary. `{layout}` ---------- -**Περίπτωση χρήσης**: Επέκταση του `layout.latte` μέσα στο `homepage.latte` & `about.latte`. +**Περίπτωση χρήσης**: Επέκταση του `layout.latte` μέσα στα `homepage.latte` και `about.latte`. `layout.latte` @@ -648,7 +637,7 @@ Hi, I am Mary. {layout 'layout.latte'} {block main} -

                                              Homepage

                                              +

                                              Αρχική σελίδα

                                              {/block} ``` @@ -658,7 +647,7 @@ Hi, I am Mary. {layout 'layout.latte'} {block main} -

                                              About page

                                              +

                                              Σελίδα σχετικά

                                              {/block} ``` @@ -666,12 +655,12 @@ Hi, I am Mary. `{import}` ---------- -**Περίπτωση χρήσης**: `sidebar.latte` σε `single.product.latte` & `single.service.latte`. +**Περίπτωση χρήσης**: `sidebar.latte` στα `single.product.latte` και `single.service.latte`. `sidebar.latte` ```latte -{block sidebar}{/block} +{block sidebar}{/block} ``` `single.product.latte` @@ -681,7 +670,7 @@ Hi, I am Mary. {import 'sidebar.latte'} -{block main}
                                              Product page
                                              {/block} +{block main}
                                              Σελίδα προϊόντος
                                              {/block} ``` `single.service.latte` @@ -691,14 +680,14 @@ Hi, I am Mary. {import 'sidebar.latte'} -{block main}
                                              Service page
                                              {/block} +{block main}
                                              Σελίδα υπηρεσίας
                                              {/block} ``` `{define}` ---------- -**Περίπτωση χρήσης**: Μια συνάρτηση που παίρνει κάποιες μεταβλητές και εξάγει κάποια σήμανση. +**Περίπτωση χρήσης**: Συναρτήσεις στις οποίες περνάμε μεταβλητές και αποδίδουν κάτι. `form.latte` @@ -716,7 +705,7 @@ Hi, I am Mary.
                                              {include form-input, username}
                                              {include form-input, password}
                                              -
                                              {include form-input, submit, Submit, submit}
                                              +
                                              {include form-input, submit, Υποβολή, submit}
                                              ``` @@ -724,7 +713,7 @@ Hi, I am Mary. `{embed}` --------- -**Περίπτωση χρήσης**: Ενσωμάτωση του `pagination.latte` στο `product.table.latte` & `service.table.latte`. +**Περίπτωση χρήσης**: Ενσωμάτωση του `pagination.latte` στα `product.table.latte` και `service.table.latte`. `pagination.latte` @@ -744,8 +733,8 @@ Hi, I am Mary. ```latte {embed 'pagination.latte', min: 1, max: $products->count} - {block first}First Product Page{/block} - {block last}Last Product Page{/block} + {block first}Πρώτη σελίδα προϊόντος{/block} + {block last}Τελευταία σελίδα προϊόντος{/block} {/embed} ``` @@ -753,7 +742,7 @@ Hi, I am Mary. ```latte {embed 'pagination.latte', min: 1, max: $services->count} - {block first}First Service Page{/block} - {block last}Last Service Page{/block} + {block first}Πρώτη σελίδα υπηρεσίας{/block} + {block last}Τελευταία σελίδα υπηρεσίας{/block} {/embed} ``` diff --git a/latte/el/type-system.texy b/latte/el/type-system.texy index 80763ae990..26dff347f3 100644 --- a/latte/el/type-system.texy +++ b/latte/el/type-system.texy @@ -1,27 +1,27 @@ -Τύπος συστήματος -**************** +Σύστημα τύπων +************* -
                                              +
                                              -Το σύστημα τύπων είναι το κύριο πράγμα για την ανάπτυξη εύρωστων εφαρμογών. Το Latte φέρνει την υποστήριξη τύπων στα πρότυπα. Για να γνωρίζετε τι τύπος δεδομένων ή αντικειμένου είναι κάθε μεταβλητή επιτρέπει +Το σύστημα τύπων είναι κρίσιμο για την ανάπτυξη στιβαρών εφαρμογών. Το Latte φέρνει υποστήριξη τύπων και στα πρότυπα. Γνωρίζοντας τον τύπο δεδομένων ή αντικειμένου που υπάρχει σε κάθε μεταβλητή, μπορεί: -- IDE να συμπληρώνει σωστά αυτόματα (βλ. [ενσωμάτωση και πρόσθετα |recipes#Editors and IDE]) -- στατική ανάλυση για τον εντοπισμό σφαλμάτων +- το IDE να παρέχει σωστή αυτόματη συμπλήρωση (δείτε [ενσωμάτωση |recipes#Επεξεργαστές και IDE]) +- η στατική ανάλυση να εντοπίσει σφάλματα -Δύο σημεία που βελτιώνουν σημαντικά την ποιότητα και την ευκολία της ανάπτυξης. +Και τα δύο βελτιώνουν σημαντικά την ποιότητα και την ευκολία της ανάπτυξης.
                                              .[note] -Οι δηλωμένοι τύποι είναι πληροφοριακοί και η Latte δεν τους ελέγχει προς το παρόν. +Οι δηλωμένοι τύποι είναι πληροφοριακοί και το Latte δεν τους ελέγχει προς το παρόν. -Πώς να αρχίσετε να χρησιμοποιείτε τύπους; Δημιουργήστε μια πρότυπη κλάση, π.χ. `CatalogTemplateParameters`, που να αναπαριστά τις παραμέτρους που περνούν: +Πώς να αρχίσετε να χρησιμοποιείτε τύπους; Δημιουργήστε μια κλάση προτύπου, π.χ. `CatalogTemplateParameters`, που αντιπροσωπεύει τις παραμέτρους που περνιούνται, τους τύπους τους και ενδεχομένως τις προεπιλεγμένες τιμές τους: ```php class CatalogTemplateParameters { public function __construct( - public string $langs, + public string $lang, /** @var ProductEntity[] */ public array $products, public Address $address, @@ -35,19 +35,16 @@ $latte->render('template.latte', new CatalogTemplateParameters( )); ``` -Στη συνέχεια, εισάγετε την ετικέτα `{templateType}` με το πλήρες όνομα της κλάσης (συμπεριλαμβανομένου του χώρου ονομάτων) στην αρχή του προτύπου. Αυτό ορίζει ότι θα υπάρχουν μεταβλητές `$langs` και `$products` στο πρότυπο συμπεριλαμβανομένων των αντίστοιχων τύπων. -Μπορείτε επίσης να καθορίσετε τους τύπους των τοπικών μεταβλητών χρησιμοποιώντας ετικέτες [`{var}` |tags#var-default], `{varType}` και [`{define}` |template-inheritance#definitions]. +Και στη συνέχεια, στην αρχή του προτύπου, εισαγάγετε το tag `{templateType}` με το πλήρες όνομα της κλάσης (συμπεριλαμβανομένου του namespace). Αυτό ορίζει ότι στο πρότυπο υπάρχουν οι μεταβλητές `$lang` και `$products` συμπεριλαμβανομένων των αντίστοιχων τύπων τους. Μπορείτε να δηλώσετε τους τύπους των τοπικών μεταβλητών χρησιμοποιώντας τα tags [`{var}` |tags#var default], `{varType}`, [`{define}` |template-inheritance#Ορισμοί define]. -Τώρα το IDE μπορεί να κάνει σωστή αυτόματη συμπλήρωση. +Από εκείνη τη στιγμή, το IDE σας μπορεί να παρέχει σωστή αυτόματη συμπλήρωση. -Πώς να αποθηκεύσετε την εργασία; Πώς να γράψετε μια κλάση προτύπου ή τις ετικέτες `{varType}` όσο το δυνατόν πιο εύκολα; Πάρτε τα που δημιουργούνται. -Αυτό ακριβώς κάνει το ζεύγος ετικετών `{templatePrint}` και `{varPrint}`. -Αν τοποθετήσετε μία από αυτές τις ετικέτες σε ένα πρότυπο, ο κώδικας της κλάσης ή του προτύπου εμφανίζεται αντί της κανονικής απόδοσης. Στη συνέχεια, απλά επιλέξτε και αντιγράψτε τον κώδικα στο έργο σας. +Πώς να γλιτώσετε δουλειά; Πώς να γράψετε όσο το δυνατόν ευκολότερα την κλάση με τις παραμέτρους του προτύπου ή τα tags `{varType}`; Αφήστε τα να δημιουργηθούν αυτόματα. Γι' αυτό υπάρχει ένα ζεύγος tags `{templatePrint}` και `{varPrint}`. Αν τα τοποθετήσετε σε ένα πρότυπο, αντί για την κανονική απόδοση, θα εμφανιστεί μια πρόταση κώδικα της κλάσης ή μια λίστα tags `{varType}`. Στη συνέχεια, αρκεί να επιλέξετε τον κώδικα με ένα κλικ και να τον αντιγράψετε στο έργο σας. `{templateType}` ---------------- -Οι τύποι των παραμέτρων που περνούν στο πρότυπο δηλώνονται με τη χρήση class: +Δηλώνουμε τους τύπους των παραμέτρων που περνιούνται στο πρότυπο χρησιμοποιώντας μια κλάση: ```latte {templateType MyApp\CatalogTemplateParameters} @@ -56,7 +53,7 @@ $latte->render('template.latte', new CatalogTemplateParameters( `{varType}` ----------- -Πώς δηλώνουμε τύπους μεταβλητών; Για το σκοπό αυτό χρησιμοποιήστε την ετικέτα `{varType}` για μια υπάρχουσα μεταβλητή, ή [`{var}` |tags#var-default]: +Πώς να δηλώσετε τους τύπους των μεταβλητών; Γι' αυτό χρησιμεύουν τα tags `{varType}` για υπάρχουσες μεταβλητές, ή [`{var}` |tags#var default]: ```latte {varType Nette\Security\User $user} @@ -66,11 +63,11 @@ $latte->render('template.latte', new CatalogTemplateParameters( `{templatePrint}` ----------------- -Μπορείτε επίσης να δημιουργήσετε αυτή την κλάση χρησιμοποιώντας την ετικέτα `{templatePrint}`. Αν την τοποθετήσετε στην αρχή του προτύπου, εμφανίζεται ο κώδικας της κλάσης αντί του κανονικού προτύπου. Στη συνέχεια, απλά επιλέξτε και αντιγράψτε τον κώδικα στο έργο σας. +Μπορείτε επίσης να αφήσετε την κλάση να δημιουργηθεί αυτόματα χρησιμοποιώντας το tag `{templatePrint}`. Αν το τοποθετήσετε στην αρχή του προτύπου, αντί για την κανονική απόδοση, θα εμφανιστεί μια πρόταση κλάσης. Στη συνέχεια, αρκεί να επιλέξετε τον κώδικα με ένα κλικ και να τον αντιγράψετε στο έργο σας. `{varPrint}` ------------ -Η ετικέτα `{varPrint}` σας εξοικονομεί χρόνο. Εάν την τοποθετήσετε σε ένα πρότυπο, εμφανίζεται η λίστα των ετικετών `{varType}` αντί της κανονικής απόδοσης. Στη συνέχεια, απλά επιλέξτε και αντιγράψτε τον κώδικα στο πρότυπό σας. +Το tag `{varPrint}` σας γλιτώνει χρόνο από τη γραφή. Αν το τοποθετήσετε σε ένα πρότυπο, αντί για την κανονική απόδοση, θα εμφανιστεί μια πρόταση tags `{varType}` για τις τοπικές μεταβλητές. Στη συνέχεια, αρκεί να επιλέξετε τον κώδικα με ένα κλικ και να τον αντιγράψετε στο πρότυπο. -Η ετικέτα `{varPrint}` απαριθμεί τοπικές μεταβλητές που δεν είναι παράμετροι του προτύπου. Αν θέλετε να παραθέσετε όλες τις μεταβλητές, χρησιμοποιήστε το `{varPrint all}`. +Το ίδιο το `{varPrint}` εκτυπώνει μόνο τις τοπικές μεταβλητές που δεν είναι παράμετροι του προτύπου. Αν θέλετε να εκτυπώσετε όλες τις μεταβλητές, χρησιμοποιήστε το `{varPrint all}`. diff --git a/latte/el/why-use.texy b/latte/el/why-use.texy new file mode 100644 index 0000000000..1ae4af1a89 --- /dev/null +++ b/latte/el/why-use.texy @@ -0,0 +1,80 @@ +Γιατί να χρησιμοποιήσετε πρότυπα; +********************************* + + +Γιατί πρέπει να χρησιμοποιήσω ένα σύστημα προτύπων στην PHP; +------------------------------------------------------------ + +Γιατί να χρησιμοποιήσετε ένα σύστημα προτύπων στην PHP, όταν η ίδια η PHP είναι μια γλώσσα προτύπων; + +Ας ανακεφαλαιώσουμε πρώτα σύντομα την ιστορία αυτής της γλώσσας, η οποία είναι γεμάτη ενδιαφέρουσες ανατροπές. Μία από τις πρώτες γλώσσες προγραμματισμού που χρησιμοποιήθηκαν για τη δημιουργία σελίδων HTML ήταν η γλώσσα C. Ωστόσο, σύντομα αποδείχθηκε ότι η χρήση της για αυτόν τον σκοπό ήταν μη πρακτική. Ο Rasmus Lerdorf δημιούργησε λοιπόν την PHP, η οποία διευκόλυνε τη δημιουργία δυναμικού HTML με τη γλώσσα C στο backend. Η PHP σχεδιάστηκε λοιπόν αρχικά ως γλώσσα προτύπων, αλλά με την πάροδο του χρόνου απέκτησε πρόσθετες λειτουργίες και έγινε μια πλήρης γλώσσα προγραμματισμού. + +Παρ' όλα αυτά, εξακολουθεί να λειτουργεί και ως γλώσσα προτύπων. Ένα αρχείο PHP μπορεί να περιέχει μια σελίδα HTML, στην οποία εκτυπώνονται μεταβλητές κ.λπ. χρησιμοποιώντας το ``. + +Ήδη στα πρώτα στάδια της ιστορίας της PHP, δημιουργήθηκε το σύστημα προτύπων Smarty, σκοπός του οποίου ήταν ο αυστηρός διαχωρισμός της εμφάνισης (HTML/CSS) από τη λογική της εφαρμογής. Έτσι, παρείχε σκόπιμα μια πιο περιορισμένη γλώσσα από την ίδια την PHP, ώστε ο προγραμματιστής να μην μπορεί, για παράδειγμα, να εκτελέσει ένα ερώτημα βάσης δεδομένων από ένα πρότυπο κ.λπ. Από την άλλη πλευρά, αποτελούσε μια πρόσθετη εξάρτηση στα έργα, αύξανε την πολυπλοκότητά τους και οι προγραμματιστές έπρεπε να μάθουν μια νέα γλώσσα Smarty. Ένα τέτοιο όφελος ήταν αμφισβητήσιμο και η απλή PHP συνέχισε να χρησιμοποιείται για πρότυπα. + +Με την πάροδο του χρόνου, τα συστήματα προτύπων άρχισαν να γίνονται χρήσιμα. Εισήγαγαν την έννοια της [κληρονομικότητας |template-inheritance], του [sandbox mode|sandbox] και μια σειρά από άλλες λειτουργίες που απλοποίησαν σημαντικά τη δημιουργία προτύπων σε σύγκριση με την καθαρή PHP. Το θέμα της ασφάλειας, η ύπαρξη [ευπαθειών όπως το XSS|safety-first] και η ανάγκη για [escaping |#Τι είναι το escaping] ήρθαν στο προσκήνιο. Τα συστήματα προτύπων εισήγαγαν την αυτόματη διαφυγή για να εξαλείψουν τον κίνδυνο ο προγραμματιστής να το ξεχάσει και να δημιουργηθεί μια σοβαρή τρύπα ασφαλείας (σε λίγο θα δείξουμε ότι αυτό έχει κάποιες παγίδες). + +Τα οφέλη των συστημάτων προτύπων σήμερα υπερβαίνουν κατά πολύ το κόστος που σχετίζεται με την ανάπτυξή τους. Επομένως, αξίζει να τα χρησιμοποιείτε. + + +Γιατί το Latte είναι καλύτερο από το Twig ή το Blade; +----------------------------------------------------- + +Υπάρχουν πολλοί λόγοι - μερικοί είναι ευχάριστοι και άλλοι θεμελιωδώς χρήσιμοι. Το Latte είναι ένας συνδυασμός του ευχάριστου και του χρήσιμου. + +*Πρώτα το ευχάριστο:* Το Latte έχει την ίδια [σύνταξη με την PHP |syntax#Το Latte καταλαβαίνει PHP]. Διαφέρει μόνο η σύνταξη των tags, αντί για `` προτιμά τα συντομότερα `{` και `}`. Αυτό σημαίνει ότι δεν χρειάζεται να μάθετε μια νέα γλώσσα. Το κόστος εκπαίδευσης είναι ελάχιστο. Και κυρίως, κατά την ανάπτυξη, δεν χρειάζεται να "αλλάζετε" συνεχώς μεταξύ της γλώσσας PHP και της γλώσσας προτύπου, καθώς είναι και οι δύο ίδιες. Σε αντίθεση με τα πρότυπα Twig, τα οποία χρησιμοποιούν τη γλώσσα Python, και ο προγραμματιστής πρέπει έτσι να αλλάζει μεταξύ δύο διαφορετικών γλωσσών. + +*Και τώρα ο εξαιρετικά χρήσιμος λόγος*: Όλα τα συστήματα προτύπων, όπως το Twig, το Blade ή το Smarty, εισήγαγαν κατά τη διάρκεια της εξέλιξής τους προστασία κατά του XSS με τη μορφή αυτόματου [escaping |#Τι είναι το escaping]. Πιο συγκεκριμένα, της αυτόματης κλήσης της συνάρτησης `htmlspecialchars()`. Ωστόσο, οι δημιουργοί του Latte συνειδητοποίησαν ότι αυτή δεν είναι καθόλου η σωστή λύση. Επειδή σε διαφορετικά σημεία του εγγράφου η διαφυγή γίνεται με διαφορετικούς τρόπους. Η αφελής αυτόματη διαφυγή είναι μια επικίνδυνη λειτουργία, επειδή δημιουργεί μια ψευδή αίσθηση ασφάλειας. + +Για να είναι η αυτόματη διαφυγή λειτουργική και αξιόπιστη, πρέπει να αναγνωρίζει σε ποιο σημείο του εγγράφου εκτυπώνονται τα δεδομένα (τα ονομάζουμε contexts) και ανάλογα να επιλέγει τη συνάρτηση διαφυγής. Δηλαδή, πρέπει να είναι [context-aware |safety-first#Context-Aware Escaping]. Και αυτό ακριβώς κάνει το Latte. Κατανοεί την HTML. Δεν αντιλαμβάνεται το πρότυπο απλώς ως μια σειρά χαρακτήρων, αλλά καταλαβαίνει τι είναι τα tags, τα attributes κ.λπ. Και γι' αυτό διαφεύγει διαφορετικά στο κείμενο HTML, διαφορετικά μέσα σε ένα tag HTML, διαφορετικά μέσα στην JavaScript κ.λπ. + +Το Latte είναι το πρώτο και μοναδικό σύστημα προτύπων στην PHP που διαθέτει διαφυγή με επίγνωση του context. Αποτελεί έτσι το μοναδικό πραγματικά ασφαλές σύστημα προτύπων. + +*Και ένας ακόμη ευχάριστος λόγος*: Χάρη στο γεγονός ότι το Latte κατανοεί την HTML, προσφέρει και άλλα πολύ ευχάριστα χαρακτηριστικά. Για παράδειγμα, τα [n:attributes |syntax#n:attributes]. Ή τη δυνατότητα [ελέγχου συνδέσμων |safety-first#Έλεγχος συνδέσμων]. Και πολλά άλλα. + + +Τι είναι το escaping; +--------------------- + +Το Escaping είναι η διαδικασία αντικατάστασης χαρακτήρων με ειδική σημασία με τις αντίστοιχες ακολουθίες κατά την εισαγωγή ενός string σε ένα άλλο, για την αποφυγή ανεπιθύμητων φαινομένων ή σφαλμάτων. Για παράδειγμα, όταν εισάγουμε ένα string σε κείμενο HTML, στο οποίο ο χαρακτήρας `<` έχει ειδική σημασία, καθώς υποδηλώνει την αρχή ενός tag, τον αντικαθιστούμε με την αντίστοιχη ακολουθία, η οποία είναι η οντότητα HTML `<`. Χάρη σε αυτό, ο περιηγητής εμφανίζει σωστά το σύμβολο `<`. + +Ένα απλό παράδειγμα διαφυγής απευθείας κατά τη γραφή κώδικα στην PHP είναι η εισαγωγή ενός εισαγωγικού σε ένα string, όπου γράφουμε μια ανάποδη κάθετο πριν από αυτό. + +Αναλύουμε λεπτομερέστερα τη διαφυγή στο κεφάλαιο [Πώς να αμυνθείτε κατά του XSS |safety-first#Πώς να αμυνθείτε από το XSS]. + + +Μπορώ να εκτελέσω ένα ερώτημα βάσης δεδομένων από ένα πρότυπο στο Latte; +------------------------------------------------------------------------ + +Στα πρότυπα, μπορείτε να εργαστείτε με αντικείμενα που τους περνά ο προγραμματιστής. Επομένως, αν ο προγραμματιστής θέλει, μπορεί να περάσει ένα αντικείμενο βάσης δεδομένων στο πρότυπο και να εκτελέσει ένα ερώτημα πάνω σε αυτό. Αν έχει τέτοια πρόθεση, δεν υπάρχει λόγος να τον εμποδίσετε. + +Μια διαφορετική κατάσταση προκύπτει αν θέλετε να δώσετε τη δυνατότητα επεξεργασίας προτύπων σε πελάτες ή εξωτερικούς κωδικοποιητές. Σε αυτή την περίπτωση, σίγουρα δεν θέλετε να έχουν πρόσβαση στη βάση δεδομένων. Φυσικά, δεν θα περάσετε το αντικείμενο της βάσης δεδομένων στο πρότυπο, αλλά τι γίνεται αν μπορεί να προσεγγιστεί μέσω άλλου αντικειμένου; Η λύση είναι η [λειτουργία sandbox|sandbox], η οποία σας επιτρέπει να ορίσετε ποιες μέθοδοι μπορούν να κληθούν στα πρότυπα. Χάρη σε αυτό, δεν χρειάζεται να ανησυχείτε για παραβίαση της ασφάλειας. + + +Ποιες είναι οι κύριες διαφορές μεταξύ των συστημάτων προτύπων όπως το Latte, το Twig και το Blade; +-------------------------------------------------------------------------------------------------- + +Οι διαφορές μεταξύ των συστημάτων προτύπων Latte, Twig και Blade έγκεινται κυρίως στη σύνταξη, την ασφάλεια και τον τρόπο ενσωμάτωσης στα frameworks: + +- Latte: χρησιμοποιεί τη σύνταξη της γλώσσας PHP, γεγονός που διευκολύνει την εκμάθηση και τη χρήση. Παρέχει κορυφαία προστασία κατά των επιθέσεων XSS. +- Twig: χρησιμοποιεί τη σύνταξη της γλώσσας Python, η οποία διαφέρει αρκετά από την PHP. Κάνει διαφυγή χωρίς διάκριση context. Είναι καλά ενσωματωμένο στο Symfony framework. +- Blade: χρησιμοποιεί ένα μείγμα PHP και δικής του σύνταξης. Κάνει διαφυγή χωρίς διάκριση context. Είναι στενά ενσωματωμένο με τις λειτουργίες και το οικοσύστημα του Laravel. + + +Αξίζει για τις εταιρείες να χρησιμοποιούν ένα σύστημα προτύπων; +--------------------------------------------------------------- + +Πρώτα απ' όλα, το κόστος που σχετίζεται με την εκπαίδευση, τη χρήση και το συνολικό όφελος διαφέρει σημαντικά ανάλογα με το σύστημα. Το σύστημα προτύπων Latte, χάρη στο ότι χρησιμοποιεί τη σύνταξη της PHP, απλοποιεί πολύ την εκμάθηση για προγραμματιστές που είναι ήδη εξοικειωμένοι με αυτή τη γλώσσα. Συνήθως χρειάζονται λίγες ώρες για να εξοικειωθεί επαρκώς ένας προγραμματιστής με το Latte. Μειώνει έτσι το κόστος εκπαίδευσης. Ταυτόχρονα, επιταχύνει την υιοθέτηση της τεχνολογίας και κυρίως την αποτελεσματικότητα στην καθημερινή χρήση. + +Επιπλέον, το Latte παρέχει υψηλό επίπεδο προστασίας κατά της ευπάθειας XSS χάρη στη μοναδική τεχνολογία διαφυγής με επίγνωση του context. Αυτή η προστασία είναι κρίσιμη για τη διασφάλιση της ασφάλειας των διαδικτυακών εφαρμογών και την ελαχιστοποίηση του κινδύνου επιθέσεων που θα μπορούσαν να θέσουν σε κίνδυνο τους χρήστες ή τα εταιρικά δεδομένα. Η προστασία της ασφάλειας των διαδικτυακών εφαρμογών είναι επίσης σημαντική για τη διατήρηση της καλής φήμης της εταιρείας. Τα προβλήματα ασφαλείας μπορούν να προκαλέσουν απώλεια εμπιστοσύνης από την πλευρά των πελατών και να βλάψουν τη φήμη της εταιρείας στην αγορά. + +Η χρήση του Latte μειώνει επίσης το συνολικό κόστος ανάπτυξης και συντήρησης της εφαρμογής διευκολύνοντας και τα δύο. Η χρήση ενός συστήματος προτύπων επομένως αξίζει σαφώς τον κόπο. + + +Επηρεάζει το Latte την απόδοση των διαδικτυακών εφαρμογών; +---------------------------------------------------------- + +Αν και τα πρότυπα Latte επεξεργάζονται γρήγορα, αυτή η πτυχή στην πραγματικότητα δεν έχει σημασία. Ο λόγος είναι ότι η ανάλυση των αρχείων γίνεται μόνο μία φορά κατά την πρώτη εμφάνιση. Στη συνέχεια, μεταγλωττίζονται σε κώδικα PHP, αποθηκεύονται στον δίσκο και εκτελούνται σε κάθε επόμενο αίτημα, χωρίς να χρειάζεται να γίνει εκ νέου μεταγλώττιση. + +Αυτός είναι ο τρόπος λειτουργίας στο περιβάλλον παραγωγής. Κατά την ανάπτυξη, τα πρότυπα Latte μεταγλωττίζονται εκ νέου κάθε φορά που αλλάζει το περιεχόμενό τους, ώστε ο προγραμματιστής να βλέπει πάντα την τρέχουσα μορφή. diff --git a/latte/en/@home.texy b/latte/en/@home.texy index 107194ced2..f9b0c72ff1 100644 --- a/latte/en/@home.texy +++ b/latte/en/@home.texy @@ -1 +1,2 @@ {{maintitle: Latte – The Safest & Truly Intuitive Templates for PHP}} +{{description: Latte is the most secure templating system for PHP. It prevents many security vulnerabilities. You will appreciate its intuitive syntax and appreciate many useful features.}} diff --git a/latte/en/@left-menu.texy b/latte/en/@left-menu.texy index 0b903a9480..88fc173a96 100644 --- a/latte/en/@left-menu.texy +++ b/latte/en/@left-menu.texy @@ -1,25 +1,25 @@ - [Getting Started |Guide] -- Concepts +- [Why Use Templates? |why-use] +- Concepts ⚗️ - [Safety First] - [Template Inheritance] - [Type System] - [Sandbox] + - [HTML attributes] -- For Designers +- For Designers 🎨 - [Syntax] - [Tags] - [Filters] - [Functions] - [Tips and Tricks |recipes] -- For Developers +- For Developers 🧮 - [Practices for Developers |develop] - [Extending Latte] - - [Creating an Extension |creating-extension] -- [Cookbook |cookbook/@home] +- [Cookbook 💡|cookbook/@home] - [Migration from Twig |cookbook/migration-from-twig] - - [Migration from Latte 2 |cookbook/migration-from-latte2] - [… more |cookbook/@home] - "Playground .[link-external]":https://fiddle.nette.org/latte/ .{padding-top:1em} diff --git a/latte/en/@menu.texy b/latte/en/@menu.texy index c7cb62830e..daad209e3a 100644 --- a/latte/en/@menu.texy +++ b/latte/en/@menu.texy @@ -5,8 +5,8 @@ diff --git a/latte/en/@meta.texy b/latte/en/@meta.texy new file mode 100644 index 0000000000..44994d27ba --- /dev/null +++ b/latte/en/@meta.texy @@ -0,0 +1 @@ +{{sitename: Latte Documentation}} diff --git a/latte/en/compiler-passes.texy b/latte/en/compiler-passes.texy new file mode 100644 index 0000000000..e38a1f2599 --- /dev/null +++ b/latte/en/compiler-passes.texy @@ -0,0 +1,555 @@ +Creating Compiler Passes +************************ + +.[perex] +Compiler passes provide a powerful mechanism to analyze and modify Latte templates *after* they have been parsed into an Abstract Syntax Tree (AST) and *before* the final PHP code is generated. This allows for advanced template manipulation, optimizations, security checks (like the Sandbox), and collecting template insights. This guide will walk you through creating your own compiler passes. + + +What is a Compiler Pass? +======================== + +To understand the role of compiler passes, see [Latte's compilation process |custom-tags#Understanding the Compilation Process]. As you can see, compiler passes operate at a crucial stage, allowing deep intervention between the initial parsing and the final code output. + +At its core, a compiler pass is simply a PHP callable (like a function, a static method, or an instance method) that accepts one argument: the root node of the template's AST, which is always an instance of `Latte\Compiler\Nodes\TemplateNode`. + +The primary goal of a compiler pass is usually one or both of the following: + +- Analysis: To walk through the AST and gather information about the template (e.g., find all defined blocks, check for specific tag usage, ensure certain security constraints are met). +- Modification: To change the AST structure or node properties (e.g., automatically add HTML attributes, optimize certain tag combinations, replace deprecated tags with new ones, implement sandboxing rules). + + +Registration +============ + +Compiler passes are registered via an [Extension's |extending-latte#getPasses] `getPasses()` method. This method returns an associative array where keys are unique names for the passes (used internally and for ordering) and values are the PHP callables implementing the pass logic. + +```php +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Extension; + +class MyExtension extends Extension +{ + public function getPasses(): array + { + return [ + 'modificationPass' => $this->modifyTemplateAst(...), + // ... other passes ... + ]; + } + + public function modifyTemplateAst(TemplateNode $templateNode): void + { + // Implementation... + } +} +``` + +Passes registered by Latte's core extensions and your custom extensions run sequentially. The order can be important, especially if one pass relies on the results or modifications of another. Latte provides a helper mechanism to control this order if needed; see the documentation for [`Extension::getPasses()` |extending-latte#getPasses] for details. + + +Example of AST +============== + +To get a better idea of the AST, we add a sample. This is the source template: + +```latte +{foreach $category->getItems() as $item} +
                                            • {$item->name|upper}
                                            • + {else} + no items found +{/foreach} +``` + +And this is its representation in the form of AST: + +/--pre +Latte\Compiler\Nodes\TemplateNode( + Latte\Compiler\Nodes\FragmentNode( + - Latte\Essential\Nodes\ForeachNode( + expression: Latte\Compiler\Nodes\Php\Expression\MethodCallNode( + object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$category') + name: Latte\Compiler\Nodes\Php\IdentifierNode('getItems') + ) + value: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') + content: Latte\Compiler\Nodes\FragmentNode( + - Latte\Compiler\Nodes\TextNode(' ') + - Latte\Compiler\Nodes\Html\ElementNode('li')( + content: Latte\Essential\Nodes\PrintNode( + expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode( + object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') + name: Latte\Compiler\Nodes\Php\IdentifierNode('name') + ) + modifier: Latte\Compiler\Nodes\Php\ModifierNode( + filters: + - Latte\Compiler\Nodes\Php\FilterNode('upper') + ) + ) + ) + ) + else: Latte\Compiler\Nodes\FragmentNode( + - Latte\Compiler\Nodes\TextNode('no items found') + ) + ) + ) +) +\-- + + +Traversing the AST with `NodeTraverser` +======================================= + +Manually writing recursive functions to walk through the complex AST structure is tedious and error-prone. Latte provides a dedicated tool for this: [api:Latte\Compiler\NodeTraverser]. This class implements the [Visitor design pattern |https://en.wikipedia.org/wiki/Visitor_pattern], making AST traversal systematic and manageable. + +The basic usage involves creating an instance of `NodeTraverser` and calling its `traverse()` method, passing the root AST node and one or two "visitor" callables: + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; + +(new NodeTraverser)->traverse( + $templateNode, + + // 'enter' visitor: Called when entering a node (before its children) + enter: function (Node $node) { + echo "Entering node of type: " . $node::class . "\n"; + // You can inspect the node here + if ($node instanceof Nodes\TextNode) { + // echo "Found text: " . $node->content . "\n"; + } + }, + + // 'leave' visitor: Called when leaving a node (after its children) + leave: function (Node $node) { + echo "Leaving node of type: " . $node::class . "\n"; + // You might perform actions here after children have been processed + }, +); +``` + +You can provide only the `enter` visitor, only the `leave` visitor, or both, depending on your needs. + +**`enter(Node $node)`:** This function is executed for each node **before** the traverser visits any of that node's children. It's useful for: + +- Collecting information as you descend the tree. +- Making decisions *before* processing children (like deciding to skip them, see [#Optimizing Traversal]). +- Potentially modifying the node before children are visited (less common). + +**`leave(Node $node)`:** This function is executed for each node **after** all of its children (and their entire subtrees) have been fully visited (both entered and left). It's the most common place for: + +Both `enter` and `leave` visitors can optionally return a value to influence the traversal process. Returning `null` (or nothing) continues traversal normally, returning a `Node` instance replaces the current node, and returning special constants like `NodeTraverser::RemoveNode` or `NodeTraverser::StopTraversal` modifies the flow, as explained in the following sections. + + +How Traversal Works +------------------- + +The `NodeTraverser` internally uses the `getIterator()` method that every `Node` class must implement (as discussed in [Creating Custom Tags |custom-tags#Implementing getIterator for Subnodes]). It iterates over the children yielded by `getIterator()`, recursively calls `traverse()` on them, ensuring that the `enter` and `leave` visitors are called in the correct depth-first order for every node in the tree accessible via iterators. This highlights again why a correctly implemented `getIterator()` in your custom tag nodes is absolutely essential for compiler passes to function correctly. + +Let's write a simple pass that counts how many times the `{do}` tag (represented by `Latte\Essential\Nodes\DoNode`) is used in the template. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Essential\Nodes\DoNode; + +function countDoTags(TemplateNode $templateNode): void +{ + $count = 0; + (new NodeTraverser)->traverse( + $templateNode, + enter: function (Node $node) use (&$count): void { + if ($node instanceof DoNode) { + $count++; + } + }, + // 'leave' visitor is not needed for this task + ); + + echo "Found {do} tag $count times.\n"; +} + +$latte = new Latte\Engine; +$ast = $latte->parse($templateSource); +countDoTags($ast); +``` + +In this example, we only needed the `enter` visitor to check the type of each node encountered. + +Next, we'll explore how to use these visitors to actually modify the AST. + + +Modifying the AST +================= + +One of the main purposes of compiler passes is to modify the Abstract Syntax Tree. This allows for powerful transformations, optimizations, or the enforcement of rules directly on the template structure before PHP code is generated. `NodeTraverser` provides several ways to achieve this within the `enter` and `leave` visitors. + +**Important Note:** Modifying the AST requires care. Incorrect changes—like removing essential nodes or replacing a node with an incompatible type—can lead to errors during code generation or produce unexpected runtime behavior. Always test your modification passes thoroughly. + + +Changing Node Properties +------------------------ + +The simplest way to modify the tree is by directly changing the **public properties** of the nodes encountered during traversal. All nodes store their parsed arguments, content, or attributes in public properties. + +**Example:** Let's create a pass that finds all static text nodes (`TextNode`, representing plain HTML or text outside Latte tags) and converts their content to uppercase *directly within the AST*. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\TextNode; + +function uppercaseStaticText(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // We can use 'enter' as TextNode has no children to process first + enter: function (Node $node) { + // Is this node a static text block? + if ($node instanceof TextNode) { + // Yes! Directly modify its public 'content' property. + $node->content = mb_strtoupper(html_entity_decode($node->content)); + } + // No need to return anything; the modification happens in place. + }, + ); +} +``` + +In this example, the `enter` visitor checks if the current `$node` is a `TextNode`. If it is, we directly update its public `$content` property using `mb_strtoupper()`. This directly changes the static text content stored in the AST *before* PHP code generation. Because we're modifying the object directly, we don't need to return anything from the visitor. + +Effect: If the template contained `

                                              Hello

                                              {= $var }World`, after this pass, the AST will represent something like: `

                                              HELLO

                                              {= $var }WORLD`. This does NOT affect the content of `$var`. + + +Replacing Nodes +--------------- + +A more powerful modification technique is to completely replace a node with a different one. This is done by **returning the new `Node` instance** from the `enter` or `leave` visitor. The `NodeTraverser` will then substitute the original node with the returned one in the parent node's structure. + +**Example:** Let's create a pass that finds all usages of the `PHP_VERSION` constant (represented by `ConstantFetchNode`) and replaces them directly with a string literal (`StringNode`) containing the *actual* PHP version detected *during compilation*. This is a form of compile-time optimization. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\Php\Expression\ConstantFetchNode; +use Latte\Compiler\Nodes\Php\Scalar\StringNode; + +function inlinePhpVersion(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // 'leave' is often used for replacements, ensuring children (if any) + // are processed first, though 'enter' would work here too. + leave: function (Node $node) { + // Is this node a constant access and the constant name 'PHP_VERSION'? + if ($node instanceof ConstantFetchNode && (string) $node->name === 'PHP_VERSION') { + // Create a new StringNode holding the current PHP version + $newNode = new StringNode(PHP_VERSION); + + // Optional but good practice: copy position info + $newNode->position = $node->position; + + // Return the new StringNode. The traverser will replace + // the original ConstantFetchNode with this $newNode. + return $newNode; + } + // If we don't return a Node, the original $node is kept. + }, + ); +} +``` + +Here, the `leave` visitor identifies the specific `ConstantFetchNode` for `PHP_VERSION`. It then creates a completely new `StringNode` containing the value of the `PHP_VERSION` constant *at compile time*. By returning this `$newNode`, it tells the traverser to replace the original `ConstantFetchNode` in the AST. + +Effect: If the template contained `{= PHP_VERSION }` and compilation runs on PHP 8.2.1, the AST after this pass will effectively represent `{= '8.2.1' }`. + +**Choosing `enter` vs. `leave` for Replacement:** + +- Use `leave` if the creation of the new node depends on the results of processing the children of the old node, or if you simply want to ensure children are visited before replacement (common practice). +- Use `enter` if you want to replace a node *before* its children are even visited. + + +Removing Nodes +-------------- + +You can remove a node entirely from the AST by returning the special constant `NodeTraverser::RemoveNode` from a visitor. + +**Example:** Let's remove all template comments (`{* ... *}`), which are represented by `CommentNode` in the AST generated by Latte's core (though typically handled earlier, this serves as an example). + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\CommentNode; + +function removeCommentNodes(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // 'enter' is fine here, as we don't need children info to remove a comment + enter: function (Node $node) { + if ($node instanceof CommentNode) { + // Signal the traverser to remove this node from the AST + return NodeTraverser::RemoveNode; + } + }, + ); +} +``` + +**Caution:** Use `RemoveNode` with care. Removing a node that contains essential content or affects structure (like removing the content node of a loop) can lead to broken templates or invalid generated code. It's safest for nodes that are truly optional or self-contained (like comments or debug tags) or for empty structural nodes (e.g., an empty `FragmentNode` might be safely removed in some contexts by a cleanup pass). + +These three methods - modifying properties, replacing nodes, and removing nodes - provide the fundamental tools for manipulating the AST within your compiler passes. + + +Optimizing Traversal +==================== + +Template ASTs can become quite large, potentially containing thousands of nodes. Traversing every single node might be unnecessary and impact compilation performance if your pass is only interested in specific parts of the tree. `NodeTraverser` offers ways to optimize the traversal: + + +Skipping Children +----------------- + +If you know that once you encounter a certain type of node, none of its descendants can possibly contain the nodes you are looking for, you can tell the traverser to skip visiting its children. This is done by returning the constant `NodeTraverser::DontTraverseChildren` from the **`enter`** visitor. You prune entire branches from the traversal path, potentially saving significant time, especially in templates with complex PHP expressions inside tags. + + +Stopping Traversal +------------------ + +If your pass only needs to find the *first* occurrence of something (a specific node type, a condition being met), you can completely stop the entire traversal process once you've found it. This is achieved by returning the constant `NodeTraverser::StopTraversal` from either the `enter` or `leave` visitor. Method `traverse()` stops visiting any further nodes. This is highly effective if you only need the first match in a potentially very large tree. + + +Useful `NodeHelpers` Class +========================== + +While `NodeTraverser` offers fine-grained control, Latte also provides a convenient utility class, [api:Latte\Compiler\NodeHelpers], which wraps `NodeTraverser` for several common search and analysis tasks, often requiring less boilerplate code. + + +find(Node $startNode, callable $filter): array .[method] +-------------------------------------------------------- + +This static method finds **all** nodes within the subtree starting at `$startNode` (inclusive) that satisfy the `$filter` callback. It returns an array of the matching nodes. + +**Example:** Find all variable nodes (`VariableNode`) in the entire template. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Php\Expression\VariableNode; +use Latte\Compiler\Nodes\TemplateNode; + +function findAllVariables(TemplateNode $templateNode): array +{ + return NodeHelpers::find( + $templateNode, + fn($node) => $node instanceof VariableNode, + ); +} +``` + + +findFirst(Node $startNode, callable $filter): ?Node .[method] +-------------------------------------------------------------- + +Similar to `find`, but stops traversal immediately after finding the **first** node that satisfies the `$filter` callback. It returns the found `Node` object or `null` if no matching node is found. This is essentially a convenient wrapper around `NodeTraverser::StopTraversal`. + +**Example:** Find the `{parameters}` node (same as the manual example before, but shorter). + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Essential\Nodes\ParametersNode; + +function findParametersNodeHelper(TemplateNode $templateNode): ?ParametersNode +{ + return NodeHelpers::findFirst( + $templateNode->head, // Search only in the head section for efficiency + fn($node) => $node instanceof ParametersNode, + ); +} +``` + + +toValue(ExpressionNode $node, bool $constants = false): mixed .[method] +----------------------------------------------------------------------- + +This static method attempts to evaluate an `ExpressionNode` **at compile time** and return its corresponding PHP value. It works reliably only for simple literal nodes (`StringNode`, `IntegerNode`, `FloatNode`, `BooleanNode`, `NullNode`) and `ArrayNode` instances containing only such evaluable items. + +If `$constants` is set to `true`, it will also attempt to resolve `ConstantFetchNode` and `ClassConstantFetchNode` by checking `defined()` and using `constant()`. + +If the node contains variables, function calls, or other dynamic elements, it cannot be evaluated at compile time, and the method will throw an `InvalidArgumentException`. + +**Use Case:** Getting the static value of a tag argument during compilation to make compile-time decisions. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Php\ExpressionNode; + +function getStaticStringArgument(ExpressionNode $argumentNode): ?string +{ + try { + $value = NodeHelpers::toValue($argumentNode); + return is_string($value) ? $value : null; + } catch (\InvalidArgumentException $e) { + // The argument was not a static literal string + return null; + } +} +``` + + +toText(?Node $node): ?string .[method] +-------------------------------------- + +This static method is useful for extracting the plain text content from simple nodes. It works primarily with: +- `TextNode`: Returns its `$content`. +- `FragmentNode`: Concatenates the result of `toText()` for all its children. If any child is not convertible to text (e.g., contains a `PrintNode`), it returns `null`. +- `NopNode`: Returns an empty string. +- Other node types: Returns `null`. + +**Use Case:** Getting the static text content of an HTML attribute's value or a simple HTML element for analysis during a compiler pass. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Html\AttributeNode; + +function getStaticAttributeValue(AttributeNode $attr): ?string +{ + // $attr->value is typically an AreaNode (like FragmentNode or TextNode) + return NodeHelpers::toText($attr->value); +} + +// Example usage in a pass: +// if ($node instanceof Html\ElementNode && $node->name === 'meta') { +// $nameAttrValue = getStaticAttributeValue($node->getAttributeNode('name')); +// if ($nameAttrValue === 'description') { ... } +// } +``` + +`NodeHelpers` can simplify your compiler passes by providing ready-made solutions for common AST traversal and analysis tasks. + + +Practical Examples +================== + +Let's apply the concepts of AST traversal and modification to solve some practical problems. These examples demonstrate common patterns used in compiler passes. + + +Auto-Adding `loading="lazy"` to `` +--------------------------------------- + +Modern browsers support native lazy loading for images via the `loading="lazy"` attribute. Let's create a pass that automatically adds this attribute to all `` tags that don't already have a `loading` attribute. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; +use Latte\Compiler\Nodes\Html; + +function addLazyLoading(Nodes\TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // We can use 'enter' as we are modifying the node directly + // and don't depend on children for this decision. + enter: function (Node $node) { + // Is it an HTML element named 'img'? + if ($node instanceof Html\ElementNode && $node->name === 'img') { + // Ensure attributes node exists + $node->attributes ??= new Nodes\FragmentNode; + + // Check if 'loading' attribute already exists (case-insensitive) + foreach ($node->attributes->children as $attrNode) { + if ($attrNode instanceof Html\AttributeNode + && $attrNode->name instanceof Nodes\TextNode // Static attribute name + && strtolower($attrNode->name->content) === 'loading' + ) { + return; // Already exists, do nothing + } + } + + // Prepend a space if attributes are not empty + if ($node->attributes->children) { + $node->attributes->children[] = new Nodes\TextNode(' '); + } + + // Create the new attribute node: loading="lazy" + $node->attributes->children[] = new Html\AttributeNode( + name: new Nodes\TextNode('loading'), + value: new Nodes\TextNode('lazy'), + quote: '"', + ); + // Modification done in place, no return needed. + } + }, + ); +} +``` + +Explanation: +- The `enter` visitor looks for `Html\ElementNode` nodes named `img`. +- It iterates through the existing attributes (`$node->attributes->children`) to check if a `loading` attribute is already present. +- If not found, it creates a new `Html\AttributeNode` representing `loading="lazy"` and adds it (with a preceding space if needed). + + +Checking Function Calls +----------------------- + +Compiler passes are the foundation of Latte's Sandbox. While the real Sandbox is sophisticated, we can demonstrate the basic principle of checking for forbidden function calls. + +**Goal:** Prevent the use of the potentially dangerous `shell_exec` function within template expressions. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; +use Latte\Compiler\Nodes\Php; +use Latte\SecurityViolationException; + +function checkForbiddenFunctions(Nodes\TemplateNode $templateNode): void +{ + $forbiddenFunctions = ['shell_exec' => true, 'exec' => true]; // Simple list + + $traverser = new NodeTraverser; + (new NodeTraverser)->traverse( + $templateNode, + enter: function (Node $node) use ($forbiddenFunctions) { + // Is it a direct function call node? + if ($node instanceof Php\Expression\FunctionCallNode + && $node->name instanceof Php\NameNode + && isset($forbiddenFunctions[strtolower((string) $node->name)]) + ) { + throw new SecurityViolationException( + "Function {$node->name}() is not allowed.", + $node->position, + ); + } + }, + ); +} +``` + +Explanation: +- We define a list of forbidden function names. +- The `enter` visitor checks for `FunctionCallNode`. +- If the function name (`$node->name`) is a static `NameNode`, we check its lowercase string representation against our forbidden list. +- If a forbidden function is found, we throw a `Latte\SecurityViolationException`, which clearly indicates a security rule violation and halts compilation. + +These examples show how compiler passes, using `NodeTraverser`, can be employed for analysis, automated modifications, and enforcing security constraints by interacting directly with the template's AST structure. + + +Best Practices +============== + +When writing compiler passes, keep these guidelines in mind to create robust, maintainable, and efficient extensions: + +- **Order Matters:** Be conscious of the order in which passes run. If your pass relies on the AST structure created by another pass (e.g., core Latte passes or another custom pass), or if other passes might depend on your modifications, use the ordering mechanism provided by `Extension::getPasses()` to define dependencies (`before`/`after`). See the documentation for [`Extension::getPasses()` |extending-latte#getPasses] for details. +- **Single Responsibility:** Aim for passes that perform a single, well-defined task. For complex transformations, consider splitting the logic into multiple passes – perhaps one for analysis and another for modification based on the analysis results. This improves clarity and testability. +- **Performance:** Remember that compiler passes add to the template compilation time (though this usually happens only once until the template changes). Avoid computationally expensive operations within your passes if possible. Leverage traversal optimizations like `NodeTraverser::DontTraverseChildren` and `NodeTraverser::StopTraversal` whenever you know you don't need to visit certain parts of the AST. +- **Use `NodeHelpers`:** For common tasks like finding specific nodes or statically evaluating simple expressions, check if `Latte\Compiler\NodeHelpers` offers a suitable method before writing custom `NodeTraverser` logic. It can save time and reduce boilerplate. +- **Error Handling:** If your pass detects an error or an invalid state in the template AST, throw a `Latte\CompileException` (or `Latte\SecurityViolationException` for security issues) with a clear message and the relevant `Position` object (usually `$node->position`). This provides helpful feedback to the template developer. +- **Idempotency (If Possible):** Ideally, running your pass multiple times on the same AST should produce the same result as running it once. This isn't always feasible, but it simplifies debugging and reasoning about pass interactions if achieved. For example, ensure your modification pass checks if the modification has already been applied before applying it again. + +By adhering to these practices, you can effectively leverage compiler passes to extend Latte's capabilities in powerful and reliable ways, contributing to safer, more optimized, or feature-rich template processing. diff --git a/latte/en/cookbook/@home.texy b/latte/en/cookbook/@home.texy index cea48d6e0f..58b23d4136 100644 --- a/latte/en/cookbook/@home.texy +++ b/latte/en/cookbook/@home.texy @@ -4,11 +4,12 @@ Cookbook .[perex] Example codes and recipes for accomplishing common tasks with Latte. -- [Everything You Always Wanted to Know About {iterateWhile} |iteratewhile] +- [Developer guidelines |/develop] +- [Passing variables across templates |passing-variables] +- [Everything you always wanted to know about grouping |grouping] - [How to write SQL queries in Latte? |how-to-write-sql-queries-in-latte] +- [Migration from Latte 3.0 |migration-from-latte-30] - [Migration from Latte 2 |migration-from-latte2] - [Migration from PHP |migration-from-php] - [Migration from Twig |migration-from-twig] - [Using Latte with Slim 4 |slim-framework] - -{{leftbar: /@left-menu}} diff --git a/latte/en/cookbook/@meta.texy b/latte/en/cookbook/@meta.texy new file mode 100644 index 0000000000..562d8c982e --- /dev/null +++ b/latte/en/cookbook/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Latte Documentation}} +{{leftbar: /@left-menu}} diff --git a/latte/en/cookbook/grouping.texy b/latte/en/cookbook/grouping.texy new file mode 100644 index 0000000000..7a8458bb0c --- /dev/null +++ b/latte/en/cookbook/grouping.texy @@ -0,0 +1,251 @@ +Everything You Always Wanted to Know About Grouping +*************************************************** + +.[perex] +When working with data in templates, you often encounter the need to group them or display them specifically according to certain criteria. Latte offers several powerful tools for this purpose. + +The filter and function `|group` allow for efficient data grouping based on specified criteria, the `|batch` filter facilitates splitting data into fixed-size batches, and the `{iterateWhile}` tag provides the ability to control loop progression with more complex conditions. Each of these features offers specific options for working with data, making them indispensable tools for dynamic and structured display of information in Latte templates. + + +Filter and Function `group` .{data-version:3.0.16} +================================================== + +Imagine a database table `items` with items divided into categories: + +| id | categoryId | name +|-----|------------|-------- +| 1 | 1 | Apple +| 2 | 1 | Banana +| 3 | 2 | PHP +| 4 | 3 | Green +| 5 | 3 | Red +| 6 | 3 | Blue + +A simple list of all items using a Latte template would look like this: + +```latte +
                                                +{foreach $items as $item} +
                                              • {$item->name}
                                              • +{/foreach} +
                                              +``` + +However, if we wanted the items organized into groups by category, we need to divide them so that each category has its own list. The desired result would look like this: + +```latte +
                                                +
                                              • Apple
                                              • +
                                              • Banana
                                              • +
                                              + +
                                                +
                                              • PHP
                                              • +
                                              + +
                                                +
                                              • Green
                                              • +
                                              • Red
                                              • +
                                              • Blue
                                              • +
                                              +``` + +This task can be easily and elegantly solved using `|group`. We specify `categoryId` as the parameter, meaning the items will be divided into smaller arrays based on the value of `$item->categoryId` (if `$item` were an array, `$item['categoryId']` would be used): + +```latte +{foreach ($items|group: categoryId) as $categoryId => $categoryItems} +
                                                + {foreach $categoryItems as $item} +
                                              • {$item->name}
                                              • + {/foreach} +
                                              +{/foreach} +``` + +The filter can also be used as a function in Latte, providing an alternative syntax: `{foreach group($items, categoryId) ...}`. + +If you want to group items based on more complex criteria, you can use a function in the filter parameter. For example, grouping items by the length of their name would look like this: + +```latte +{foreach ($items|group: fn($item) => strlen($item->name)) as $items} + ... +{/foreach} +``` + +It’s important to note that `$categoryItems` is not a regular array, but an object that behaves like an iterator. To access the first item in the group, you can use the [`first()` |latte:functions#first] function. + +This flexibility in data grouping makes `group` an exceptionally useful tool for presenting data in Latte templates. + + +Nested Loops +------------ + +Let's imagine our database table has an additional column `subcategoryId`, defining subcategories for each item. We want to display each main category in a separate `
                                                ` list and each subcategory within that main category in a separate nested `
                                                  ` list: + +```latte +{foreach ($items|group: categoryId) as $categoryItems} +
                                                    + {foreach ($categoryItems|group: subcategoryId) as $subcategoryItems} +
                                                      + {foreach $subcategoryItems as $item} +
                                                    1. {$item->name} + {/foreach} +
                                                    + {/foreach} +
                                                  +{/foreach} +``` + + +Integration with Nette Database +------------------------------- + +Let's demonstrate how to effectively use data grouping in combination with Nette Database. Assume we are working with the `items` table from the introductory example, connected via the `categoryId` column to this `categories` table: + +| categoryId | name | +|------------|------------| +| 1 | Fruits | +| 2 | Languages | +| 3 | Colors | + +We load data from the `items` table using Nette Database Explorer with the command `$items = $db->table('items')`. While iterating over this data, we can access not only attributes like `$item->name` and `$item->categoryId`, but thanks to the relationship with the `categories` table, also the related row via `$item->category`. This relationship allows for interesting applications: + +```latte +{foreach ($items|group: category) as $category => $categoryItems} +

                                                  {$category->name}

                                                  +
                                                    + {foreach $categoryItems as $item} +
                                                  • {$item->name}
                                                  • + {/foreach} +
                                                  +{/foreach} +``` + +In this case, we use the `|group` filter to group by the related row object `$item->category`, not just the `categoryId` column. As a result, the key variable `$category` directly holds the `ActiveRow` object for that category, allowing us to display its name directly using `{$category->name}`. This is a practical example of how grouping can simplify templates and facilitate working with related data. + + +Filter `|batch` +=============== + +The `|batch` filter allows you to divide a list of items into groups (batches) with a predetermined number of items. This filter is ideal for situations where you want to present data in several smaller chunks, for example, for better clarity or visual layout on the page. + +Imagine we have a list of items and want to display them in lists, where each list contains a maximum of three items. Using the `|batch` filter is very practical in such a case: + +```latte +
                                                    +{foreach ($items|batch: 3) as $batch} + {foreach $batch as $item} +
                                                  • {$item->name}
                                                  • + {/foreach} +{/foreach} +
                                                  +``` + +In this example, the `$items` list is divided into smaller groups, where each group (`$batch`) contains up to three items. Each batch is then displayed in a separate `
                                                    ` list. + +If the last group does not contain enough elements to reach the desired number, the second parameter of the filter allows you to define what this group will be supplemented with. This is ideal for aesthetically aligning elements where an incomplete row might look disordered. + +```latte +{foreach ($items|batch: 3, '—') as $batch} + ... +{/foreach} +``` + + +Tag `{iterateWhile}` +==================== + +We will demonstrate the same tasks addressed with the `|group` filter using the `{iterateWhile}` tag. The main difference between the two approaches is that `|group` first processes and groups all input data, whereas `{iterateWhile}` controls the loop's progression based on conditions, allowing iteration to proceed sequentially. + +First, let's render the table with categories using `iterateWhile`: + +```latte +{foreach $items as $item} +
                                                      + {iterateWhile} +
                                                    • {$item->name}
                                                    • + {/iterateWhile $item->categoryId === $iterator->nextValue?->categoryId} +
                                                    +{/foreach} +``` + +While `{foreach}` marks the outer part of the cycle, i.e., drawing lists for each category, the `{iterateWhile}` tag marks the inner part, i.e., individual items. The condition in the end tag says that repetition will continue as long as the current and next element belong to the same category (`$iterator->nextValue` is the [next item |/tags#iterator]). + +If the condition were always true, all elements would be rendered within the first `
                                                      `: + +```latte +{foreach $items as $item} +
                                                        + {iterateWhile} +
                                                      • {$item->name} + {/iterateWhile true} +
                                                      +{/foreach} +``` + +The result would look like this: + +```latte +
                                                        +
                                                      • Apple
                                                      • +
                                                      • Banana
                                                      • +
                                                      • PHP
                                                      • +
                                                      • Green
                                                      • +
                                                      • Red
                                                      • +
                                                      • Blue
                                                      • +
                                                      +``` + +What's the benefit of using `iterateWhile` like this? If the `$items` array is empty, no empty `
                                                        ` tags will be printed. + +If we specify the condition in the opening `{iterateWhile}` tag, the behavior changes: the condition (and transition to the next element) is performed at the beginning of the inner cycle, not at the end. Thus, while you always enter `{iterateWhile}` without conditions, you enter `{iterateWhile $cond}` only when the condition `$cond` is met. And at the same time, the next element is written into `$item`. + +This is useful, for instance, when you want to render the first item in each category differently, like this: + +```latte +

                                                        Apple

                                                        +
                                                          +
                                                        • Banana
                                                        • +
                                                        + +

                                                        PHP

                                                        +
                                                          +
                                                        + +

                                                        Green

                                                        +
                                                          +
                                                        • Red
                                                        • +
                                                        • Blue
                                                        • +
                                                        +``` + +We modify the original code to first render the item as a heading, and then use the inner `{iterateWhile}` loop to render subsequent items from the same category as list items: + +```latte +{foreach $items as $item} +

                                                        {$item->name}

                                                        +
                                                          + {iterateWhile $item->categoryId === $iterator->nextValue?->categoryId} +
                                                        • {$item->name}
                                                        • + {/iterateWhile} +
                                                        +{/foreach} +``` + +Within a single `{foreach}` loop, you can create multiple inner `{iterateWhile}` loops and even nest them. This could be used, for example, to group subcategories. + +Let's assume the table has another column `subcategoryId`, and besides having each category in a separate `
                                                          `, each subcategory should be in a separate `
                                                            `: + +```latte +{foreach $items as $item} +
                                                              + {iterateWhile} +
                                                                + {iterateWhile} +
                                                              1. {$item->name} + {/iterateWhile $item->subcategoryId === $iterator->nextValue?->subcategoryId} +
                                                              + {/iterateWhile $item->categoryId === $iterator->nextValue?->categoryId} +
                                                            +{/foreach} +``` diff --git a/latte/en/cookbook/how-to-write-sql-queries-in-latte.texy b/latte/en/cookbook/how-to-write-sql-queries-in-latte.texy index eead8db711..bdf6e14bc8 100644 --- a/latte/en/cookbook/how-to-write-sql-queries-in-latte.texy +++ b/latte/en/cookbook/how-to-write-sql-queries-in-latte.texy @@ -2,20 +2,19 @@ How to Write SQL Queries in Latte? ********************************** .[perex] -Latte can also be useful for generating really complex SQL queries. +Latte can also be useful for generating really complex SQL queries in a more readable way. -If the creation of a SQL query contains many conditions and variables, it can be really clearer to write it in Latte. A very simple example: +If constructing an SQL query involves numerous conditions and variables, writing it in Latte can significantly improve clarity. Here's a very simple example: ```latte SELECT users.* FROM users LEFT JOIN users_groups ON users.user_id = users_groups.user_id LEFT JOIN groups ON groups.group_id = users_groups.group_id {ifset $country} LEFT JOIN country ON country.country_id = users.country_id {/ifset} -WHERE groups.name = 'Admins' {ifset $country} AND country.name = {$country} {/ifset} +WHERE groups.name = 'Admins' {ifset $country} AND country.name = {$country|escape} {/ifset} ``` -Using `$latte->setContentType()` we tell Latte to treat the content as plain text (not as HTML) and -then we prepare an escaping function that escapes strings directly by the database driver: +Using `$latte->setContentType()`, we tell Latte to treat the content as plain text (not HTML). Then, we prepare an escaping function that uses the database driver to escape strings properly: ```php $db = new PDO(/* ... */); @@ -39,5 +38,3 @@ $result = $db->query($sql); ``` *This example requires Latte v3.0.5 or higher.* - -{{leftbar: /@left-menu}} diff --git a/latte/en/cookbook/iteratewhile.texy b/latte/en/cookbook/iteratewhile.texy deleted file mode 100644 index 695a0e1406..0000000000 --- a/latte/en/cookbook/iteratewhile.texy +++ /dev/null @@ -1,214 +0,0 @@ -Everything You Always Wanted to Know About {iterateWhile} -********************************************************* - -.[perex] -The tag `{iterateWhile}` is suitable for various tricks in foreach cycles. - -Suppose we have the following database table, where the items are divided into categories: - -| id | catId | name -|------------------ -| 1 | 1 | Apple -| 2 | 1 | Banana -| 3 | 2 | PHP -| 4 | 3 | Green -| 5 | 3 | Red -| 6 | 3 | Blue - -Of course, drawing items in a foreach loop as a list is easy: - -```latte -
                                                              -{foreach $items as $item} -
                                                            • {$item->name}
                                                            • -{/foreach} -
                                                            -``` - -But what to do if you want render each category in a separate list? In other words, how to solve the task of grouping items from a linear list in a foreach cycle. The output should look like this: - -```latte -
                                                              -
                                                            • Apple
                                                            • -
                                                            • Banana
                                                            • -
                                                            - -
                                                              -
                                                            • PHP
                                                            • -
                                                            - -
                                                              -
                                                            • Green
                                                            • -
                                                            • Red
                                                            • -
                                                            • Blue
                                                            • -
                                                            -``` - -We will show you how easily and elegantly the task can be solved with iterateWhile: - -```latte -{foreach $items as $item} -
                                                              - {iterateWhile} -
                                                            • {$item->name}
                                                            • - {/iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                            -{/foreach} -``` - -While `{foreach}` marks the outer part of the cycle, ie the drawing of lists for each category, the tags `{iterateWhile}` indicate the inner part, ie the individual items. -The condition in the end tag says that the repetition will continue as long as the current and the next element belong to the same category (`$iterator->nextValue` is [next item |/tags#$iterator]). - -If the condition is always met, then all elements are drawn in the inner cycle: - -```latte -{foreach $items as $item} -
                                                              - {iterateWhile} -
                                                            • {$item->name} - {/iterateWhile true} -
                                                            -{/foreach} -``` - -The result will look like this: - -```latte -
                                                              -
                                                            • Apple
                                                            • -
                                                            • Banana
                                                            • -
                                                            • PHP
                                                            • -
                                                            • Green
                                                            • -
                                                            • Red
                                                            • -
                                                            • Blue
                                                            • -
                                                            -``` - -What good is such an use of iterateWhile? How it differs from the solution we showed at the very beginning of this tutorial? The difference is that if the table is empty and does not contain any elements, it will not render empty `
                                                              `. - - -Solution Without `{iterateWhile}` ---------------------------------- - -If we solved the same task with completely basic constructions of template systems, for example in Twig, Blade, or pure PHP, the solution would look something like this: - -```latte -{var $prevCatId = null} -{foreach $items as $item} - {if $item->catId !== $prevCatId} - {* the category has changed *} - - {* we close the previous
                                                                , if it is not the first item *} - {if $prevCatId !== null} -
                                                              - {/if} - - {* we will open a new list *} -
                                                                - - {do $prevCatId = $item->catId} - {/if} - -
                                                              • {$item->name}
                                                              • -{/foreach} - -{if $prevCatId !== null} - {* we close the last list *} -
                                                              -{/if} -``` - -However, this code is incomprehensible and unintuitive. The connection between the opening and closing HTML tags is not clear at all. It is not clear at first glance if there is a mistake. And it requires auxiliary variables like `$prevCatId`. - -In contrast, the solution with `{iterateWhile}` is clean, clear, does not need auxiliary variables and is foolproof. - - -Condition in the Closing Tag ----------------------------- - -If we specify a condition in the opening tag `{iterateWhile}`, the behavior changes: the condition (and the advance to the next element) is executed at the beginning of the inner cycle, not at the end. -Thus, while `{iterateWhile}` without condition is always entered, `{iterateWhile $cond}` is entered only when condition `$cond` is met. At the same time, the following element is written to `$item`. - -This is useful, for example, in a situation where you want to render the first element in each category in a different way, such as: - -```latte -

                                                              Apple

                                                              -
                                                                -
                                                              • Banana
                                                              • -
                                                              - -

                                                              PHP

                                                              -
                                                                -
                                                              - -

                                                              Green

                                                              -
                                                                -
                                                              • Red
                                                              • -
                                                              • Blue
                                                              • -
                                                              -``` - -Lets modify the original code, we draw first item and then additional items from the same category in the inner loop `{iterateWhile}`: - -```latte -{foreach $items as $item} -

                                                              {$item->name}

                                                              -
                                                                - {iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                              • {$item->name}
                                                              • - {/iterateWhile} -
                                                              -{/foreach} -``` - - -Nested Loops ------------- - -We can create multiple inner loops in one cycle and even nest them. In this way, for example, subcategories could be grouped. - -Suppose there is another column in the table `subCatId` and in addition to each category being in a separate `
                                                                `, each subcategory will be in a separate `
                                                                  `: - -```latte -{foreach $items as $item} -
                                                                    - {iterateWhile} -
                                                                      - {iterateWhile} -
                                                                    1. {$item->name} - {/iterateWhile $item->subCatId === $iterator->nextValue->subCatId} -
                                                                    - {/iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                                  -{/foreach} -``` - - -Filter |batch -------------- - -The grouping of linear items is also provided by a filter `batch`, into batches with a fixed number of elements: - -```latte -
                                                                    -{foreach ($items|batch:3) as $batch} - {foreach $batch as $item} -
                                                                  • {$item->name}
                                                                  • - {/foreach} -{/foreach} -
                                                                  -``` - -It can be replaced with iterateWhile as follows: - -```latte -
                                                                    -{foreach $items as $item} - {iterateWhile} -
                                                                  • {$item->name}
                                                                  • - {/iterateWhile $iterator->counter0 % 3} -{/foreach} -
                                                                  -``` - -{{leftbar: /@left-menu}} diff --git a/latte/en/cookbook/migration-from-latte-30.texy b/latte/en/cookbook/migration-from-latte-30.texy new file mode 100644 index 0000000000..0c73bfce8f --- /dev/null +++ b/latte/en/cookbook/migration-from-latte-30.texy @@ -0,0 +1,110 @@ +Migration from Latte 3.0 +************************ + +.[perex] +Latte 3.1 brings several improvements and changes that make templates safer and more convenient to write. Most changes are backward compatible, but some require attention during migration. This guide summarizes the breaking changes and how to handle them. + +Latte 3.1 requires **PHP 8.2** or newer. + + +Smart Attributes and Migration +============================== + +The most significant change in Latte 3.1 is the new behavior of [Smart Attributes |/html-attributes]. This affects how `null` values and boolean values in `data-` attributes are rendered. + +1. **`null` values:** Previously, `title={$null}` rendered as `title=""`. Now, the attribute is completely dropped. +2. **`data-` attributes:** Previously, `data-foo={=true}` / `data-foo={=false}` rendered as `data-foo="1"` / `data-foo=""`. Now, it renders as `data-foo="true"` / `data-foo="false"`. + +To help you identify places where the output has changed in your application, Latte provides a migration tool. + + +Migration Warnings +------------------ + +You can enable [migration warnings |/develop#Migration Warnings], which will warn you during rendering if the output differs from Latte 3.0. + +```php +$latte = new Latte\Engine; +$latte->setFeature(Latte\Feature::MigrationWarnings); +``` + +When enabled, check your application logs or Tracy bar for `E_USER_WARNING`s. Each warning will point to the specific line, and column in template. + +**How to resolve warnings:** + +If the new behavior is correct (e.g. you want the empty attribute to disappear), confirm it using the `|accept` filter to suppress the warning: + +```latte +
                                                                  +``` + +If you want to keep the attribute as empty (e.g. `title=""`) instead of dropping it, use the null coalescing operator: + +```latte +
                                                                  +``` + +Or, if you strictly require the old behavior (e.g. `"1"` for `true`), explicitly cast the value to string: + +```latte +
                                                                  +``` + +**After you resolve all warnings:** + +Once all warnings are resolved, disable migration warnings and **remove all** `|accept` filters from your templates, as they are no longer needed. + + +Strict Types +============ + +Latte 3.1 enables `declare(strict_types=1)` by default for all compiled templates. This improves type safety but might cause type errors in PHP expressions inside your templates if you were relying on loose typing. + +If you cannot fix the types immediately, you can disable this behavior: + +```php +$latte->setFeature(Latte\Feature::StrictTypes, false); +``` + + +Global Constants +================ + +The template parser has been improved to better distinguish between simple strings and constants. As a result, global constants must now be prefixed with a backslash `\`. + +```latte +{* Old way (throws a warning; in the future will be interpreted as the string 'PHP_VERSION') *} +{if PHP_VERSION > ...} + +{* New way (correctly interpreted as constant) *} +{if \PHP_VERSION > ...} +``` + +This change prevents ambiguity and allows you to use unquoted strings more freely. + + +Removed Features +================ + +**Reserved Variables:** Variables starting with `$__` (double underscore) and the variable `$this` are now strictly reserved for Latte's internal use. You cannot use them in your templates. + +**Undefined-safe Operator:** The `??->` operator, which was a Latte-specific feature created before PHP 8, has been removed. It is a historical relic. Please use the standard PHP nullsafe operator `?->`. + +**Filter Loader** +The `Engine::addFilterLoader()` method has been deprecated and removed. It was an inconsistent concept not found elsewhere in Latte. + +**Date Format** +The static property `Latte\Runtime\Filters::$dateFormat` was removed to avoid global state. + + +New Features +============ + +While migrating, you can start enjoying the new features: + +- **Smart HTML +Attributes:** Pass arrays to `class` and `style`, auto-drop `null` attributes. +- **Nullsafe filters:** Use `{$var?|filter}` to skip filtering null values. +- **`n:elseif`:** You can now use `n:elseif` alongside `n:if` and `n:else`. +- **Simplified syntax:** Write `
                                                                  ` without quotes. +- **Toggle filter:** Use `|toggle` for manual control over boolean attributes. diff --git a/latte/en/cookbook/migration-from-latte2.texy b/latte/en/cookbook/migration-from-latte2.texy index 430af298d3..7df098b91f 100644 --- a/latte/en/cookbook/migration-from-latte2.texy +++ b/latte/en/cookbook/migration-from-latte2.texy @@ -1,5 +1,5 @@ -Migration from Latte v2 to v3 -***************************** +Migration from Latte 2 to 3 +*************************** .[perex] Latte 3 has a completely rewritten compiler and a formally well-defined grammar. This should match Latte 2 as closely as possible, but there are some constructs that need minor tweaking. @@ -8,7 +8,7 @@ In practice, it turns out that the vast majority of the templates do not need an **First, install the transition version Latte 2.11.** -This version doesn't bring any new features, it just provides a warning using E_USER_DEPRECATED for cases it knows the new Latte won't support, and more importantly advises you how to fix them. To go through all the templates and test if they are compatible, you can use the [Linter|/develop#linter] tool that you run from the console: +This version doesn't bring any new features, it just provides a warning using E_USER_DEPRECATED for cases it knows the new Latte won't support, and more importantly advises you how to fix them. To go through all the templates and test if they are compatible, you can use the [Linter |/develop#Linter] tool that you run from the console: ```shell vendor/bin/latte-lint @@ -22,9 +22,9 @@ API Changes The API changes only apply to adding custom tags. The rest of the API remains the same as version 2, i.e. the same way to render templates, pass parameters, register filters. -The exception is the replacement of the so-called dynamic filter `Engine::addFilter(null, ...)` with [filter loader |/extending-latte#Filter Loader], which differs in that it always returns callable and is registered with the `Engine::addFilterLoader()` method. +The exception is the replacement of the so-called dynamic filter `Engine::addFilter(null, ...)` with [filter loader |/custom-filters#Filters Using the Class], which differs in that it always returns callable and is registered with the `Engine::addFilterLoader()` method. -The API for adding custom tags is completely different, so add-ons designed for Latte 2 won't work with it. See also [#Updates to add-ons]. +The API for adding custom tags is completely different, so add-ons designed for Latte 2 won't work with it. See also [#Updates to Add-Ons]. Syntax Changes @@ -46,14 +46,14 @@ And more edge cases: - the `n:inner-snippet` attribute must be written without inner- - the tags `` and `` must be terminated - the magic variable `$iterations` has been removed (not to be confused with `$iterator`!) -- replace the `{includeblock file.latte}` tag with [`{include file.latte with blocks}`|/tags#include] or [`{import}`|/template-inheritance#horizontal-reuse] +- replace the `{includeblock file.latte}` tag with [`{include file.latte with blocks}` |/tags#include] or [`{import}` |/template-inheritance#Horizontal Reuse] - `{include "abc"}` should be written as `{include file "abc"}` unless `"abc"` contains a period and it is clear that it is a file Updates to Add-Ons ================== -With the complete rewrite of the parser, the way to write custom tags has completely changed. If you have custom tags created for Latte, you will need to re-write them for version 3, see [documentation|/creating-extension]. +With the complete rewrite of the parser, the way to write custom tags has completely changed. If you have custom tags created for Latte, you will need to re-write them for version 3, see [documentation|/custom-tags]. If you are using a foreign add-on that adds tags, you will need to wait until the author releases a version for Latte 3. The `nette/application`, `nette/caching` and `nette/forms` libraries in version 3.1, as well as Texy, have already been updated and work with both Latte 2 and 3. @@ -128,6 +128,26 @@ $latte->addExtension(new Nette\Bridges\CacheLatte\CacheExtension($cacheStorage)) ``` +Tracy +----- + +The panel for Tracy is now also activated as an extension. + +Old code for Latte 2: + +```php +$latte = new Latte\Engine; +Latte\Bridges\Tracy\LattePanel::initialize($latte); +``` + +New code for Latte 3: + +```php +$latte = new Latte\Engine; +$latte->addExtension(new Latte\Bridges\Tracy\TracyExtension); +``` + + Translations ------------ @@ -151,13 +171,13 @@ In presenters, it is automatically activated by setting the translator to the te Configuration File ================== -In Latte 2 it was possible to register new tags using [config file |application:configuration#Latte] in the `latte › macros` section. In version 3, entire extensions are added this way: +In Latte 2 it was possible to register new tags using [config file |application:configuration#Latte Templates] in the `latte › macros` section. In version 3, entire extensions are added this way: ```neon latte: extensions: - App\Templating\LatteExtension - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` @@ -188,7 +208,7 @@ $this->latte->onCompile[] = function (Latte\Engine $latte) { }; ``` -Latte 3 is extended using [extensions|/creating-extension]. A trivial extension adding the `foo` tag would look like this: +Latte 3 is extended using [extensions|/extending-latte]. A trivial extension adding the `foo` tag would look like this: ```php // new code for Latte 3 @@ -234,7 +254,7 @@ class FooNode extends Latte\Compiler\Nodes\StatementNode } ``` -Furthermore, the mask in `$context->format()` no longer has `%node.***` abbreviations, it is assumed that you [parse the tag content |/creating-extension#Tag Parsing Function] first. So we use the parser to parse the content into variables (subnodes), and then we write it out: +Furthermore, the mask in `$context->format()` no longer has `%node.***` abbreviations, it is assumed that you [parse the tag content |/custom-tags#Tag Parsing Function] first. So we use the parser to parse the content into variables (subnodes), and then we write it out: ```php use Latte\Compiler\Nodes\Php\Expression\ArrayNode; @@ -266,7 +286,7 @@ class FooNode extends Latte\Compiler\Nodes\StatementNode } ``` -Finally, we will add the `getIterator()` method to allow subnodes to be traversed when [traversing |/creating-extension#Node Traverser]: +Finally, we will add the `getIterator()` method to allow subnodes to be traversed when [traversing |/custom-tags#Implementing getIterator for Subnodes]: ```php class FooNode extends Latte\Compiler\Nodes\StatementNode @@ -282,4 +302,3 @@ class FooNode extends Latte\Compiler\Nodes\StatementNode ``` {{priority: -1}} -{{leftbar: /@left-menu}} diff --git a/latte/en/cookbook/migration-from-php.texy b/latte/en/cookbook/migration-from-php.texy index a949d51cb4..9dc5a5ff6a 100644 --- a/latte/en/cookbook/migration-from-php.texy +++ b/latte/en/cookbook/migration-from-php.texy @@ -2,7 +2,7 @@ Migration from PHP to Latte *************************** .[perex] -Are you migrating an old project written in pure PHP to Latte? We have a tool to make the migration easier. [Try it out online |https://php2latte.nette.org]. +Are you converting an old project written in pure PHP to Latte? We have a tool to make the migration easier. [Try it out online |https://fiddle.nette.org/php2latte/]. You can download the tool from [GitHub|https://github.com/nette/latte-tools] or install it using Composer: @@ -10,19 +10,19 @@ You can download the tool from [GitHub|https://github.com/nette/latte-tools] or composer create-project latte/tools ``` -The converter does not use simple regular expression substitutions, instead it uses the PHP parser directly, so it can handle any complex syntax. +The converter doesn't rely on simple regular expression substitutions; instead, it utilizes the actual PHP parser, enabling it to handle arbitrarily complex syntax. -The script `php-to-latte.php` is used to convert from PHP to Latte: +The script `php-to-latte.php` is used for converting from PHP to Latte: ```shell -php-to-latte.php input.php [output.latte] +php php-to-latte.php input.php [output.latte] ``` Example ------- -The input file might look like this (it is part of the PunBB forum code): +An input file might look like this (this is part of the PunBB forum code): ```php

                                                                  @@ -48,7 +48,7 @@ foreach ($result as $cur_group) {
                                                                  ``` -Generates this template: +It generates this template: ```latte

                                                                  {$lang_common['User list']}

                                                                  @@ -68,5 +68,3 @@ Generates this template:
                                                  ``` - -{{leftbar: /@left-menu}} diff --git a/latte/en/cookbook/migration-from-twig.texy b/latte/en/cookbook/migration-from-twig.texy index ec072b271c..1af57367c1 100644 --- a/latte/en/cookbook/migration-from-twig.texy +++ b/latte/en/cookbook/migration-from-twig.texy @@ -2,7 +2,7 @@ Migration from Twig to Latte **************************** .[perex] -Are you migrating a project written in Twig to the more modern Latte? We have a tool to make the migration easier. [Try it out online |https://twig2latte.nette.org]. +Are you converting a project written in Twig to the more modern Latte? We have a tool to make the migration easier. [Try it out online |https://fiddle.nette.org/twig2latte/]. You can download the tool from [GitHub|https://github.com/nette/latte-tools] or install it using Composer: @@ -10,12 +10,12 @@ You can download the tool from [GitHub|https://github.com/nette/latte-tools] or composer create-project latte/tools ``` -The converter doesn't use simple regular expression substitutions, instead it uses the Twig parser directly, so it can handle any complex syntax. +The converter doesn't use simple regular expression substitutions; instead, it utilizes the actual Twig parser, enabling it to handle arbitrarily complex syntax. -A script `twig-to-latte.php` is used to convert from Twig to Latte: +The script `twig-to-latte.php` is used for converting from Twig to Latte: ```shell -twig-to-latte.php input.twig.html [output.latte] +php twig-to-latte.php input.twig.html [output.latte] ``` @@ -24,15 +24,15 @@ Conversion The conversion requires manual editing of the result, since the conversion cannot be done unambiguously. Twig uses dot syntax, where `{{ a.b }}` can mean `$a->b`, `$a['b']` or `$a->getB()`, which cannot be distinguished during compilation. The converter therefore converts everything to `$a->b`. -Some functions, filters or tags have no equivalent in Latte, or may behave slightly differently. +Some Twig functions, filters, or tags may not have direct equivalents in Latte, or they might behave slightly differently. Example ------- -The input file might look like this: +An input file might look like this: -```latte +```twig {% use "blocks.twig" %} @@ -77,5 +77,3 @@ After converting to Latte, we get this template: ``` - -{{leftbar: /@left-menu}} diff --git a/latte/en/cookbook/passing-variables.texy b/latte/en/cookbook/passing-variables.texy new file mode 100644 index 0000000000..6a77238810 --- /dev/null +++ b/latte/en/cookbook/passing-variables.texy @@ -0,0 +1,158 @@ +Passing Variables Across Templates +********************************** + +This guide explains how variables are passed between templates in Latte using various tags such as `{include}`, `{import}`, `{embed}`, `{layout}`, `{sandbox}`, and others. You will also learn how to work with variables within the `{block}` and `{define}` tags, and the purpose of the `{parameters}` tag. + + +Types of Variables +------------------ +Variables in Latte can be divided into three categories based on how and where they are defined: + +**Input Variables** are those that are passed to the template from outside, for example, from a PHP script or using a tag like `{include}`. + +```php +$latte->render('template.latte', ['userName' => 'Jan', 'userAge' => 30]); +``` + +**Surrounding Variables** are variables existing at the location of a specific tag. These include all input variables and other variables created using tags like `{var}`, `{default}`, or within a `{foreach}` loop. + +```latte +{foreach $users as $user} + {include 'userBox.latte', user: $user} +{/foreach} +``` + +**Explicit Variables** are those directly specified within a tag and sent to the target template. + +```latte +{include 'userBox.latte', name: $user->name, age: $user->age} +``` + + +`{block}` +--------- +The `{block}` tag is used to define reusable code blocks that can be customized or extended in inherited templates. Surrounding variables defined before the block are available inside the block, but any changes to the variables are reflected only within that block. + +```latte +{var $foo = 'original'} +{block example} + {var $foo = 'modified'} +{/block} + +{$foo} // outputs: original +``` + + +`{define}` +---------- +The `{define}` tag is used to create blocks that are rendered only when called using `{include}`. The variables available inside these blocks depend on whether parameters are specified in the definition. If parameters are specified, only those parameters are accessible. If not, all input variables of the template where the blocks are defined are accessible. + +```latte +{define hello} + {* has access to all input variables of the template *} +{/define} + +{define hello $name} + {* has access only to the $name parameter *} +{/define} +``` + + +`{parameters}` +-------------- +The `{parameters}` tag is used to explicitly declare expected input variables at the beginning of the template. This way, you can easily document expected variables and their data types. It is also possible to define default values. + +```latte +{parameters int $age, string $name = 'unknown'} +

                                                  Age: {$age}, Name: {$name}

                                                  +``` + + +`{include file}` +---------------- +The `{include file}` tag is used to insert an entire template. This template is passed both the input variables of the template where the tag is used and explicitly defined variables. However, the target template can limit the scope using `{parameters}`. + +```latte +{include 'profile.latte', userId: $user->id} +``` + + +`{include block}` +----------------- +When inserting a block defined in the same template, all surrounding and explicitly defined variables are passed to it: + +```latte +{define blockName} +

                                                  Name: {$name}, Age: {$age}

                                                  +{/define} + +{var $name = 'Jan', $age = 30} +{include blockName} +``` + +In this example, the `$name` and `$age` variables are passed to the `blockName` block. The same behavior applies to `{include parent}`. + +When inserting a block from another template, only input variables and explicitly defined variables are passed. Surrounding variables are not automatically available. + +```latte +{include blockInOtherTemplate, name: $name, age: $age} +``` + + +`{layout}` or `{extends}` +------------------------- +These tags define a layout to which input variables of the child template and variables created in the code before the blocks are passed: + +```latte +{layout 'layout.latte'} +{var $seo = 'index, follow'} +``` + +Template `layout.latte`: + +```latte + + + +``` + + +`{embed}` +--------- +The `{embed}` tag is similar to the `{include}` tag but allows embedding blocks into the template. Unlike `{include}`, only explicitly declared variables are passed: + +```latte +{embed 'menu.latte', items: $menuItems} +{/embed} +``` + +In this example, the `menu.latte` template has access only to the `$items` variable. + +Conversely, blocks inside `{embed}` have access to all surrounding variables: + +```latte +{var $name = 'Jan'} +{embed 'menu.latte', items: $menuItems} + {block foo} + {$name} + {/block} +{/embed} +``` + + +`{import}` +---------- +The `{import}` tag is used to load blocks from other templates. Both input and explicitly declared variables are passed to the imported blocks. + +```latte +{import 'buttons.latte'} +``` + + +`{sandbox}` +----------- +The `{sandbox}` tag isolates the template for safe processing. Variables are passed exclusively explicitly. + +```latte +{sandbox 'secure.latte', data: $secureData} +``` diff --git a/latte/en/cookbook/slim-framework.texy b/latte/en/cookbook/slim-framework.texy index e1beef395e..542b82dc2c 100644 --- a/latte/en/cookbook/slim-framework.texy +++ b/latte/en/cookbook/slim-framework.texy @@ -139,7 +139,7 @@ final class HomeAction To make it work, create a template file in `templates/home.latte` with this content: ```latte -
                                                    +
                                                      {foreach $items as $item}
                                                    • {$item|capitalize}
                                                    • {/foreach} @@ -155,4 +155,3 @@ Three ``` {{priority: -1}} -{{leftbar: /@left-menu}} diff --git a/latte/en/creating-extension.texy b/latte/en/creating-extension.texy deleted file mode 100644 index f1e82a7be1..0000000000 --- a/latte/en/creating-extension.texy +++ /dev/null @@ -1,579 +0,0 @@ -Creating an Extension -********************* - -.[perex]{data-version:3.0} -An extension is a reusable class that can define custom tags, filters, functions, providers, etc. - -We create extensions when we want to reuse our Latte customizations in different projects or share them with others. -It is also useful to create an extension for each web project that will contain all the specific tags and filters you want to use in the project templates. - - -Extension Class -=============== - -Extension is a class inheriting from [api:Latte\Extension]. It is registered with Latte using `addExtension()` (or via [configuration file |application:configuration#Latte]): - -```php -$latte = new Latte\Engine; -$latte->addExtension(new MyLatteExtension); -``` - -If you register multiple extensions and they define identically named tags, filters, or functions, the last added extension wins. This also implies that your extensions can override native tags/filters/functions. - -Whenever you make a change to a class and auto-refresh is not turned off, Latte will automatically recompile your templates. - -A class can implement any of the following methods: - -```php -abstract class Extension -{ - /** - * Initializes before template is compiler. - */ - public function beforeCompile(Engine $engine): void; - - /** - * Returns a list of parsers for Latte tags. - * @return array - */ - public function getTags(): array; - - /** - * Returns a list of compiler passes. - * @return array - */ - public function getPasses(): array; - - /** - * Returns a list of |filters. - * @return array - */ - public function getFilters(): array; - - /** - * Returns a list of functions used in templates. - * @return array - */ - public function getFunctions(): array; - - /** - * Returns a list of providers. - * @return array - */ - public function getProviders(): array; - - /** - * Returns a value to distinguish multiple versions of the template. - */ - public function getCacheKey(Engine $engine): mixed; - - /** - * Initializes before template is rendered. - */ - public function beforeRender(Template $template): void; -} -``` - -For an idea of what the extension looks like, take a look at the built-in "CoreExtension":https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php. - - -beforeCompile(Latte\Engine $engine): void .[method] ---------------------------------------------------- - -Called before the template is compiled. The method can be used for compilation-related initializations, for example. - - -getTags(): array .[method] --------------------------- - -Called when the template is compiled. Returns an associative array *tag name => callable*, which are [tag parsing functions|#Tag Parsing Function]. - -```php -public function getTags(): array -{ - return [ - 'foo' => [FooNode::class, 'create'], - 'bar' => [BarNode::class, 'create'], - 'n:baz' => [NBazNode::class, 'create'], - // ... - ]; -} -``` - -The `n:baz` tag represents a pure n:attribute, i.e. it is a tag that can only be written as an attribute. - -In the case of the `foo` and `bar` tags, Latte will automatically recognize whether they are pairs, and if so, they can be written automatically using n:attributes, including variants with the `n:inner-foo` and `n:tag-foo` prefixes. - -The order of execution of such n:attributes is determined by their order in the array returned by `getTags()`. Thus, `n:foo` is always executed before `n:bar`, even if the attributes are listed in reverse order in the HTML tag as `
                                                      `. - -If you need to determine the order of n:attributes across multiple extensions, use the `order()` helper method, where the `before` xor `after` parameter determines which tags are ordered before or after the tag. - -```php -public function getTags(): array -{ - return [ - 'foo' => self::order([FooNode::class, 'create'], before: 'bar')] - 'bar' => self::order([BarNode::class, 'create'], after: ['block', 'snippet'])] - ]; -} -``` - - -getPasses(): array .[method] ----------------------------- - -It is called when the template is compiled. Returns an associative array *name pass => callable*, which are functions representing so-called [#compiler passes] that traverse and modify the AST. - -Again, the `order()` helper method can be used. The value of the `before` or `after` parameters can be `*` with the meaning before/after all. - -```php -public function getPasses(): array -{ - return [ - 'optimize' => [Passes::class, 'optimizePass'], - 'sandbox' => self::order([$this, 'sandboxPass'], before: '*'), - // ... - ]; -} -``` - - -beforeRender(Latte\Engine $engine): void .[method] --------------------------------------------------- - -It is called before each template rendering. The method can be used, for example, to initialize variables used during rendering. - - -getFilters(): array .[method] ------------------------------ - -It is called before the template is rendered. Returns [filters|extending-latte#filters] as an associative array *filter name => callable*. - -```php -public function getFilters(): array -{ - return [ - 'batch' => [$this, 'batchFilter'], - 'trim' => [$this, 'trimFilter'], - // ... - ]; -} -``` - - -getFunctions(): array .[method] -------------------------------- - -It is called before the template is rendered. Returns [functions|extending-latte#functions] as an associative array *function name => callable*. - -```php -public function getFunctions(): array -{ - return [ - 'clamp' => [$this, 'clampFunction'], - 'divisibleBy' => [$this, 'divisibleByFunction'], - // ... - ]; -} -``` - - -getProviders(): array .[method] -------------------------------- - -It is called before the template is rendered. Returns an array of providers, which are usually objects that use tags at runtime. They are accessed via `$this->global->...`. - -```php -public function getProviders(): array -{ - return [ - 'myFoo' => $this->foo, - 'myBar' => $this->bar, - // ... - ]; -} -``` - - -getCacheKey(Latte\Engine $engine): mixed .[method] --------------------------------------------------- - -It is called before the template is rendered. The return value becomes part of the key whose hash is contained in the name of the compiled template file. Thus, for different return values, Latte will generate different cache files. - - -How Does Latte Work? -==================== - -To understand how to define custom tags or compiler passes, it is essential to understand how Latte works under the hood. - -Template compilation in Latte simplistically works like this: - -- First, the **lexer** tokenizes the template source code into small pieces (tokens) for easier processing -- Then, the **parser** converts the stream of tokens into a meaningful tree of nodes (the Abstract Syntax Tree, AST) -- Finally, the compiler **generates** a PHP class from the AST that renders the template and caches it. - -Actually, the compilation is a bit more complicated. Latte **has two** lexers and parsers: one for the HTML template and one for the PHP-like code inside the tags. Also, the parsing doesn't run after tokenization, but the lexer and parser run in parallel in two "threads" and coordinate. It's rocket science :-) - -Furthermore, all tags have their own parsing routines. When the parser encounters a tag, it calls its parsing function (it returns [Extension::getTags()|#getTags]). -Their job is to parse the tag arguments and, in the case of paired tags, the inner content. It returns a *node* that becomes part of the AST. See [#Tag parsing function] for details. - -When the parser finishes its work, we have a complete AST representing the template. The root node is `Latte\Compiler\Nodes\TemplateNode`. The individual nodes inside the tree then represent not only the tags, but also the HTML elements, their attributes, any expressions used inside the tags, etc. - -After this, the so-called [#Compiler passes] come into play, which are functions (returned by [Extension::getPasses()|#getPasses]) that modify the AST. - -The whole process, from loading the template content, through parsing, to generating the resulting file, can be sequenced with this code, which you can experiment with and dump the intermediate results: - -```php -$latte = new Latte\Engine; -$source = $latte->getLoader()->getContent($file); -$ast = $latte->parse($source); -$latte->applyPasses($ast); -$code = $latte->generate($ast, $file); -``` - - -Example of AST --------------- - -To get a better idea of the AST, we add a sample. This is the source template: - -```latte -{foreach $category->getItems() as $item} -
                                                    • {$item->name|upper}
                                                    • - {else} - no items found -{/foreach} -``` - -And this is its representation in the form of AST: - -/--pre -Latte\Compiler\Nodes\TemplateNode( - Latte\Compiler\Nodes\FragmentNode( - - Latte\Essential\Nodes\ForeachNode( - expression: Latte\Compiler\Nodes\Php\Expression\MethodCallNode( - object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$category') - name: Latte\Compiler\Nodes\Php\IdentifierNode('getItems') - ) - value: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') - content: Latte\Compiler\Nodes\FragmentNode( - - Latte\Compiler\Nodes\TextNode(' ') - - Latte\Compiler\Nodes\Html\ElementNode('li')( - content: Latte\Essential\Nodes\PrintNode( - expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode( - object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') - name: Latte\Compiler\Nodes\Php\IdentifierNode('name') - ) - modifier: Latte\Compiler\Nodes\Php\ModifierNode( - filters: - - Latte\Compiler\Nodes\Php\FilterNode('upper') - ) - ) - ) - ) - else: Latte\Compiler\Nodes\FragmentNode( - - Latte\Compiler\Nodes\TextNode('no items found') - ) - ) - ) -) -\-- - - -Custom Tags -=========== - -Three steps are needed to define a new tag: - -- defining [#tag parsing function] (responsible for parsing the tag into a node) -- creating a node class (responsible for [#generating PHP code] and [#AST traversing]) -- registering the tag using [Extension::getTags()|#getTags] - - -Tag Parsing Function --------------------- - -Parsing of tags is handled by its parsing function (the one returned by [Extension::getTags()|#getTags]). Its job is to parse and check any arguments inside the tag (it uses TagParser to do this). -Furthermore, if the tag is a pair, it will ask TemplateParser to parse and return the inner content. -The function creates and returns a node, which is usually a child of `Latte\Compiler\Nodes\StatementNode`, and this becomes part of the AST. - -We create a class for each node, which we'll do now, and elegantly place the parsing function into it as a static factory. As an example, let's try creating the familiar `{foreach}` tag: - -```php -use Latte\Compiler\Nodes\StatementNode; - -class ForeachNode extends StatementNode -{ - // a parsing function that just creates a node for now - public static function create(Latte\Compiler\Tag $tag): self - { - $node = new self; - return $node; - } - - public function print(Latte\Compiler\PrintContext $context): string - { - // code will be added later - } - - public function &getIterator(): \Generator - { - // code will be added later - } -} -``` - -The parsing function `create()` is passed an object [api:Latte\Compiler\Tag], which carries basic information about the tag (whether it is a classic tag or n:attribute, what line it is on, etc.) and mainly accesses the [api:Latte\Compiler\TagParser] in `$tag->parser`. - -If the tag must have arguments, check for their existence by calling `$tag->expectArguments()`. The methods of the `$tag->parser` object are available for parsing them: - -- `parseExpression(): ExpressionNode` for a PHP-like expression (e.g. `10 + 3`) -- `parseUnquotedStringOrExpression(): ExpressionNode` for an expression or unquoted-string -- `parseArguments(): ArrayNode` content of the array (e.g. `10, true, foo => bar`) -- `parseModifier(): ModifierNode` for a modifier (e.g. `|upper|truncate:10`) -- `parseType(): expressionNode` for typehint (e.g. `int|string` or `Foo\Bar[]`) - -and a low-level [api:Latte\Compiler\TokenStream] operating directly with tokens: - -- `$tag->parser->stream->consume(...): Token` -- `$tag->parser->stream->tryConsume(...): ?Token` - -Latte extends the PHP syntax in small ways, for example by adding modifiers, shortened ternary operators, or allowing simple alphanumeric strings to be written without quotes. This is why we use the term *PHP-like* instead of PHP. Thus, the `parseExpression()` method parses `foo` as `'foo'`, for example. -In addition, *unquoted-string* is a special case of a string that also does not need to be quoted, but at the same time does not need to be alphanumeric. For example, it is the path to a file in the `{include ../file.latte}` tag. The `parseUnquotedStringOrExpression()` method is used to parse it. - -.[note] -Studying the node classes that are part of Latte is the best way to learn all the nitty-gritty details of the parsing process. - -Let's go back to the `{foreach}` tag. In it, we expect arguments of the form `expression + 'as' + second expression`, which we parse as follows: - -```php -use Latte\Compiler\Nodes\StatementNode; -use Latte\Compiler\Nodes\Php\ExpressionNode; -use Latte\Compiler\Nodes\AreaNode; - -class ForeachNode extends StatementNode -{ - public ExpressionNode $expression; - public ExpressionNode $value; - - public static function create(Latte\Compiler\Tag $tag): self - { - $tag->expectArguments(); - $node = new self; - $node->expression = $tag->parser->parseExpression(); - $tag->parser->stream->consume('as'); - $node->value = $parser->parseExpression(); - return $node; - } -} -``` - -The expressions we have written into the variables `$expression` and `$value` represent subnodes. - -.[tip] -Define variables with subnodes as **public** so that they can be modified in [further processing steps |#Compiler Passes] if necessary. It is also necessary to **make them available** for [traversing |#AST Traversing]. - -For paired tags, like ours, the method must also let TemplateParser parse the inner contents of the tag. This is handled by `yield`, which returns a pair ''[inner content, end tag]''. We store the inner content in the `$node->content` variable. - -```php -public AreaNode $content; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $endTag] = yield; - return $node; -} -``` - -The `yield` keyword causes the `create()` method to terminate, returning control back to the TemplateParser, which continues parsing the content until it hits the end tag. It then passes control back to `create()`, which continues from where it left off. Using the `yield`, method automatically returns `Generator`. - -You can also pass an array of tag names to `yield` for which you want to stop parsing if they occur before the end tag. This helps us implement the `{foreach}...{else}...{/foreach}` construct. If `{else}` occurs, we parse the content after it into `$node->elseContent`: - -```php -public AreaNode $content; -public ?AreaNode $elseContent = null; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $nextTag] = yield ['else']; - if ($nextTag?->name === 'else') { - [$node->elseContent] = yield; - } - - return $node; -} -``` - -Returning node completes the tag parsing. - - -Generating PHP Code -------------------- - -Each node must implement the `print()` method. Returns PHP code that renders the given part of the template (runtime code). It is passed an object [api:Latte\Compiler\PrintContext] as a parameter, which has a useful `format()` method that simplifies the assembly of the resulting code. - -The `format(string $mask, ...$args)` method accepts the following placeholders in the mask: -- `%node` prints Node -- `%dump` exports the value to PHP -- `%raw` inserts the text directly without any transformation -- `%args` prints ArrayNode as arguments to the function call -- `%line` prints a comment with a line number -- `%escape(...)` escapes the content -- `%modify(...)` applies a modifier -- `%modifyContent(...)` applies a modifier to blocks - - -Our `print()` function might look like this (we neglect the `else` branch for simplicity): - -```php -public function print(Latte\Compiler\PrintContext $context): string -{ - return $context->format( - <<<'XX' - foreach (%node as %node) %line { - %node - } - - XX, - $this->expression, - $this->value, - $this->position, - $this->content, - ); -} -``` - -The `$this->position` variable is already defined by the [api:Latte\Compiler\Node] class and is set by the parser. It contains an [api:Latte\Compiler\Position] object with the position of the tag in the source code in the form of a row and column number. - -Runtime code may use auxiliary variables. To avoid collision with variables used by the template itself, it is convention to prefix them with `$ʟ__` characters. - -It can also use arbitrary values at runtime, which are passed to the template in the form of providers using the [Extension::getProviders()|#getProviders] method. It accesses them using `$this->global->...`. - - -AST Traversing --------------- - -In order to traverse the AST tree in depth, it is necessary to implement the `getIterator()` method. This will provide access to subnodes: - -```php -public function &getIterator(): \Generator -{ - yield $this->expression; - yield $this->value; - yield $this->content; - if ($this->elseContent) { - yield $this->elseContent; - } -} -``` - -Note that `getIterator()` returns a reference. This is what allows node visitors to replace individual nodes with other nodes. - -.[warning] -If a node has subnodes, it is necessary to implement this method and make all subnodes available. Otherwise, a security hole could be created. For example, sandbox mode would not be able to control subnodes and ensure that unallowed constructs are not called in them. - -Since the `yield` keyword must be present in the method body even if it has no child nodes, write it as follows: - -```php -public function &getIterator(): \Generator -{ - if (false) { - yield; - } -} -``` - - -Compiler Passes -=============== - -Compiler Passes are functions that modify ASTs or collect information in them. They are returned by the [Extension::getPasses()|#getPasses] method. - - -Node Traverser --------------- - -The most common way to work with the AST is by using a [api:Latte\Compiler\NodeTraverser]: - -```php -use Latte\Compiler\Node; -use Latte\Compiler\NodeTraverser; - -$ast = (new NodeTraverser)->traverse( - $ast, - enter: fn(Node $node) => ..., - leave: fn(Node $node) => ..., -); -``` - -The *enter* function (ie. visitor) is called when a node is first encountered, before its subnodes are processed. The *leave* function is called after all subnodes have been visited. -A common pattern is that *enter* is used to collect some information and then *leave* performs modifications based on that. At the time when *leave* is called, all the code inside the node will have already been visited and necessary information collected. - -How to modify AST? The easiest way is to simply change the properties of the nodes. The second way is to replace the node entirely by returning a new node. Example: the following code will change all integers in the AST to strings (e.g. 42 will be changed to `'42'`). - -```php -use Latte\Compiler\Nodes\Php; - -$ast = (new NodeTraverser)->traverse( - $ast, - leave: function (Node $node) { - if ($node instanceof Php\Scalar\IntegerNode) { - return new Php\Scalar\StringNode((string) $node->value); - } - }, -); -``` - -An AST can easily contain thousands of nodes, and traversing over all of them may be slow. In some cases, it is possible to avoid a full traversal. - -If you are looking for all `Html\ElementNode` in a tree, you know that once you've seen `Php\ExpressionNode`, there is no point in also checking all it's child nodes, because HTML cannot be inside in expressions. In this case, you can instruct the traverser to not recurse into the class node: - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Php\ExpressionNode) { - return NodeTraverser::DontTraverseChildren; - } - // ... - }, -); -``` - -If you are only looking for one specific node, it is also possible to abort the traversal entirely after finding it. - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Nodes\ParametersNode) { - return NodeTraverser::StopTraversal; - } - // ... - }, -); -``` - - -Node Helpers ------------- - -Class [api:Latte\Compiler\NodeHelpers] provides some methods which can find AST nodes that either satisfy a certain callback etc. A couple of examples are shown: - -```php -use Latte\Compiler\NodeHelpers; - -// finds all HTML element nodes -$elements = NodeHelpers::find($ast, fn(Node $node) => $node instanceof Nodes\Html\ElementNode); - -// finds first text node -$text = NodeHelpers::findFirst($ast, fn(Node $node) => $node instanceof Nodes\TextNode); - -// converts PHP value node to real value -$value = NodeHelpers::toValue($node); - -// converts static textual node to string -$text = NodeHelpers::toText($node); -``` diff --git a/latte/en/custom-filters.texy b/latte/en/custom-filters.texy new file mode 100644 index 0000000000..9fc3ea58b6 --- /dev/null +++ b/latte/en/custom-filters.texy @@ -0,0 +1,207 @@ +Creating Custom Filters +*********************** + +.[perex] +Filters are powerful tools for formatting and modifying data directly within Latte templates. They offer a clean syntax using the pipe symbol (`|`) to transform variables or expression results into the desired output format. + + +What are Filters? +================= + +Filters in Latte are essentially **PHP functions designed specifically to transform an input value into an output value**. They are applied using the pipe (`|`) notation within template expressions (`{...}`). + +**Convenience:** Filters allow you to encapsulate common formatting tasks (like date formatting, case conversion, truncation) or data manipulations into reusable units. Instead of repeating complex PHP code in your templates, you can simply apply a filter: +```latte +{* Instead of complex PHP for truncation: *} +{$article->text|truncate:100} + +{* Instead of date formatting code: *} +{$event->startTime|date:'Y-m-d H:i'} + +{* Applying multiple transformations: *} +{$product->name|lower|capitalize} +``` + +**Readability:** Using filters makes templates cleaner and more focused on presentation, moving the transformation logic into the filter's definition. + +**Context-Awareness:** A key strength of Latte filters is their ability to be [context-aware |#Contextual Filters]. This means a filter can understand the type of content it's operating on (HTML, JavaScript, plain text, etc.) and apply appropriate logic or escaping, which is crucial for security and correctness, especially when dealing with HTML generation. + +**Integration with Application Logic:** Just like custom functions, the PHP callable behind a filter can be a closure, a static method, or an instance method. This allows filters to access application services or data if needed, though their primary purpose remains *transforming the input value*. + +By default, Latte provides a rich set of [standard filters|filters]. Custom filters allow you to extend this set with your project-specific formatting and transformation needs. + +If you need to perform logic based on *multiple* inputs or don't have a primary value to transform, a [custom function|custom functions] is likely a better fit. If you need to generate complex markup or control template flow, consider a [custom tag|custom tags]. + + +Creating and Registering Filters +================================ + +There are several ways to define and register custom filters in Latte. + + +Direct Registration via `addFilter()` +------------------------------------- + +The simplest way to add a filter is using the `addFilter()` method directly on the `Latte\Engine` object. You provide the filter name (how it will be used in the template) and the corresponding PHP callable. + +```php +$latte = new Latte\Engine; + +// Simple filter without arguments +$latte->addFilter('initial', fn(string $s): string => mb_substr($s, 0, 1) . '.'); + +// Filter with an optional argument +$latte->addFilter('shortify', function (string $s, int $len = 10): string { + return mb_substr($s, 0, $len); +}); + +// Filter processing an array +$latte->addFilter('sum', fn(array $numbers): int|float => array_sum($numbers)); +``` + +**Usage in Template:** + +```latte +{$name|initial} {* Outputs 'J.' if $name is 'John' *} +{$description|shortify} {* Uses default length 10 *} +{$description|shortify:50} {* Uses length 50 *} +{$prices|sum} {* Outputs the sum of items in $prices array *} +``` + +**Argument Passing:** + +The value on the left side of the pipe (`|`) is always passed as the *first* argument to the filter function. Any parameters specified after the colon (`:`) in the template are passed as subsequent arguments. + +```latte +{$text|shortify:30} +// Calls the PHP function shortify($text, 30) +``` + + +Registration via Extension +-------------------------- + +For better organization, especially when creating reusable sets of filters or sharing them as packages, the recommended way is to register them within a [Latte Extension |extending-latte#Latte Extension]: + +```php +namespace App\Latte; + +use Latte\Extension; + +class MyLatteExtension extends Extension +{ + public function getFilters(): array + { + return [ + 'initial' => $this->initial(...), + 'shortify' => $this->shortify(...), + ]; + } + + public function initial(string $s): string + { + return mb_substr($s, 0, 1) . '.'; + } + + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// Registration +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +This approach keeps your filter logic encapsulated and makes registration straightforward. + + +Filters Using a Class with Attributes .{toc: Filters Using the Class} +--------------------------------------------------------------------- + +Another elegant way to define filters is by using methods within your [template parameters class |develop#Parameters as a Class]. Simply add the `#[Latte\Attributes\TemplateFilter]` attribute to the method. + +```php +use Latte\Attributes\TemplateFilter; + +class TemplateParameters +{ + public function __construct( + public string $description, + // other parameters... + ) {} + + #[TemplateFilter] + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// Pass the object to the template +$params = new TemplateParameters(description: '...'); +$latte->render('template.latte', $params); +``` + +Latte will automatically detect and register methods marked with this attribute when the `TemplateParameters` object is passed to the template. The filter name in the template will be the same as the method name (`shortify` in this case). + +```latte +{* Use the filter defined in the parameters class *} +{$description|shortify:50} +``` + + +Contextual Filters +================== + +Sometimes, a filter needs more information than just the input value. It might need to know the **content type** of the string it's processing (e.g., HTML, JavaScript, plain text) or even modify it. This is where contextual filters come in. + +A contextual filter is defined just like a regular filter, but its **first parameter must be** type-hinted as `Latte\Runtime\FilterInfo`. Latte automatically recognizes this signature and passes the `FilterInfo` object when calling the filter. Subsequent parameters receive the filter arguments as usual. + +```php +use Latte\Runtime\FilterInfo; +use Latte\ContentType; + +$latte->addFilter('money', function (FilterInfo $info, float $amount): string { + // 1. Check the input content type (optional but recommended) + // Allow null (variable input) or plain text. Reject if applied on HTML etc. + if (!in_array($info->contentType, [null, ContentType::Text], true)) { + $actualType = $info->contentType ?? 'mixed'; + throw new \RuntimeException( + "Filter |money used in incompatible content type $actualType. Expected text or null." + ); + } + + // 2. Perform the transformation + $formatted = number_format($amount, 2, '.', ',') . ' EUR'; + $htmlOutput = '' . htmlspecialchars($formatted) . ''; // Ensure proper escaping! + + // 3. Declare the output content type + $info->contentType = ContentType::Html; + + // 4. Return the result + return $htmlOutput; +}); +``` + +`$info->contentType` is a string constant from `Latte\ContentType` (e.g., `ContentType::Html`, `ContentType::Text`, `ContentType::JavaScript`, etc.), or `null` if the filter is applied to a variable (`{$var|filter}`). You can **read** this to check the input context and **write** to it to declare the output context type. + +By setting the content type to HTML, you tell Latte that the string returned by your filter is safe HTML. Latte will then **not** perform its default auto-escaping on this result. This is crucial if your filter generates HTML markup. + +.[warning] +If your filter generates HTML, **you are responsible for correctly escaping any input data** used within that HTML (like in the `htmlspecialchars($formatted)` call above). Failure to do so can create XSS vulnerabilities. If your filter only returns plain text, you don't need to set `$info->contentType`. + + +Filters on Blocks +----------------- + +All filters applied to [blocks |tags#block] *must* be contextual. This is because the block's content has a defined content type (usually HTML), which the filter needs to be aware of. + +```latte +{block heading|money}1000{/block} +{* The 'money' filter receives '1000' as the second argument + and $info->contentType will be ContentType::Html *} +``` + +Contextual filters provide powerful control over how data is processed based on its context, enabling advanced features and ensuring correct escaping behaviour, especially when generating HTML content. diff --git a/latte/en/custom-functions.texy b/latte/en/custom-functions.texy new file mode 100644 index 0000000000..4c4a4d423d --- /dev/null +++ b/latte/en/custom-functions.texy @@ -0,0 +1,144 @@ +Creating Custom Functions +************************* + +.[perex] +Easily add your own helper functions to Latte templates. Call PHP logic directly within expressions for calculations, accessing services, or generating dynamic content, keeping your templates clean and powerful. + + +What are Functions? +=================== + +Latte functions allow you to extend the set of functions callable within template expressions (`{...}`). Think of them as **custom PHP functions that are available only inside your Latte templates**. This provides several advantages: + +**Convenience:** You can define helper logic (like calculations, formatting, or accessing application data) and call it using a simple, familiar function syntax directly in the template, just like you would call `strlen()` or `date()` in PHP. + +```latte +{var $userInitials = initials($userName)} {* e.g., 'J. D.' *} + +{if hasPermission('article', 'edit')} + Edit +{/if} +``` + +**No Global Scope Pollution:** Unlike defining a true global function in PHP, Latte functions exist only within the context of template rendering. You don't need to clutter the global PHP namespace with template-specific helpers. + +**Integration with Application Logic:** The PHP callable behind a Latte function can be anything – a closure, a static method, or an instance method. This means your template functions can easily access application services, databases, configuration, or any other necessary logic by capturing variables (in closures) or through dependency injection (in objects). The `hasPermission` example above clearly demonstrates this, likely calling an authorization service in the background. + +**Override Native Functions (Optional):** You can even define a Latte function with the same name as a native PHP function. Within the template, your custom version will be called instead. This can be useful for providing template-specific behavior or ensuring consistent handling (e.g., making `strlen` always multibyte-safe). Use this feature with caution to avoid confusion. + +By default, Latte allows calling *all* native PHP functions (unless restricted by the [Sandbox|sandbox]). Custom functions augment this built-in library with your project's specific needs. + +If you are transforming a single value, a [custom filter|custom filters] might be more idiomatic. + + +Creating and Registering Functions +================================== + +Similar to filters, there are several ways to define and register custom functions. + + +Direct Registration via `addFunction()` +--------------------------------------- + +The simplest method is using `addFunction()` on the `Latte\Engine` object. You provide the function name (as it will appear in the template) and the corresponding PHP callable. + +```php +$latte = new Latte\Engine; + +// Simple helper function +$latte->addFunction('initials', function (string $name): string { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; +}); +``` + +**Usage in Template:** + +```latte +{var $userInitials = initials($userName)} +``` + +Function arguments in the template are passed directly to the PHP callable in the same order. PHP features like type hints, default values, and variadic parameters (`...`) work as expected. + + +Registration via Extension +-------------------------- + +For better organization and reusability, register functions within a [Latte Extension |extending-latte#Latte Extension]. This is the recommended approach for non-trivial applications or shared libraries. + +```php +namespace App\Latte; + +use Latte\Extension; +use Nette\Security\Authorizator; + +class MyLatteExtension extends Extension +{ + public function __construct( + // Assume Authorizator service exists + private Authorizator $authorizator, + ) { + } + + public function getFunctions(): array + { + // Register methods as Latte functions + return [ + 'hasPermission' => $this->hasPermission(...), + ]; + } + + public function hasPermission(string $resource, string $action): bool + { + return $this->authorizator->isAllowed($resource, $action); + } +} + +// Registration (assuming $container holds the DIC) +$extension = $container->getByType(App\Latte\MyLatteExtension::class); +$latte = new Latte\Engine; +$latte->addExtension($extension); +``` + +This approach clearly demonstrates how functions defined in Latte can be backed by object methods which, in turn, can have their own dependencies managed by your application's dependency injection container or factory. This keeps your template logic connected to your application's core while maintaining organization. + + +Functions Using a Class with Attributes .{toc: Functions Using the Class} +------------------------------------------------------------------------- + +Just like filters, functions can be defined as methods within your [template parameters class |develop#Parameters as a Class] using the `#[Latte\Attributes\TemplateFunction]` attribute. + +```php +use Latte\Attributes\TemplateFunction; + +class TemplateParameters +{ + public function __construct( + public string $userName, + // other parameters... + ) {} + + // This method becomes available as {initials(...)} in the template + #[TemplateFunction] + public function initials(string $name): string + { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; + } +} + +// Pass the object to the template +$params = new TemplateParameters(userName: 'John Doe', /* ... */); +$latte->render('template.latte', $params); +``` + +Latte automatically discovers and registers methods marked with this attribute when the parameters object is passed to the template. The function name in the template matches the method name. + +```latte +{* Use the function defined in the parameters class *} +{var $inits = initials($userName)} +``` + +**Contextual Functions?** + +Unlike filters, there isn't a direct concept of "contextual functions" that receive a `FilterInfo`-like object. Functions operate within expressions and typically don't need direct access to the rendering context or content type information in the same way filters applied to blocks do. diff --git a/latte/en/custom-tags.texy b/latte/en/custom-tags.texy new file mode 100644 index 0000000000..b427e74a27 --- /dev/null +++ b/latte/en/custom-tags.texy @@ -0,0 +1,1135 @@ +Creating Custom Tags +******************** + +.[perex] +This page provides a comprehensive guide for creating custom tags in Latte. We'll cover everything from simple tags to more complex scenarios with nested content and specific parsing needs, building upon your understanding of how Latte compiles templates. + +Custom tags provide the highest level of control over template syntax and rendering logic, but they are also the most complex extension point. Before deciding to create a custom tag, always consider if [a simpler solution exists |extending-latte#Ways to Extend Latte] or if a suitable tag already exists in the [standard set |tags]. Use custom tags only when the simpler alternatives are insufficient for your needs. + + +Understanding the Compilation Process +===================================== + +To effectively create custom tags, it's helpful to explain how Latte processes templates. Understanding this process clarifies why tags are structured the way they are and how they fit into the bigger picture. + +Template compilation in Latte, simplified, involves these key steps: + +1. **Lexing:** The lexer reads the template source code (`.latte` file) and breaks it down into a sequence of small, distinct pieces called **tokens** (e.g., `{`, `foreach`, `$variable`, `}`, HTML text, etc.). +2. **Parsing:** The parser takes this stream of tokens and constructs a meaningful tree structure representing the template's logic and content. This tree is called the **Abstract Syntax Tree (AST)**. +3. **Compiler Passes:** Before generating PHP code, Latte runs [compiler passes]. These are functions that traverse the entire AST and can modify it or gather information. This step is crucial for features like security ([Sandbox|sandbox]) or optimizations. +4. **Code Generation:** Finally, the compiler walks through the (potentially modified) AST and generates the corresponding PHP class code. This PHP code is what actually renders the template when executed. +5. **Caching:** The generated PHP code is cached on disk, making subsequent renders very fast as steps 1-4 are skipped. + +Actually, the compilation is a bit more complicated. Latte **has two** lexers and parsers: one for the HTML template and one for the PHP-like code inside the tags. Also, the parsing doesn't run after tokenization, but the lexer and parser run in parallel in two "threads" and coordinate. Take it from me, David Grudl - programming this felt like rocket science :-) + +The whole process, from loading the template content, through parsing, to generating the resulting file, can be sequenced with this code, which you can experiment with and dump the intermediate results: + +```php +$latte = new Latte\Engine; +$source = $latte->getLoader()->getContent($file); +$ast = $latte->parse($source); +$latte->applyPasses($ast); +$code = $latte->generate($ast, $file); +``` + + +The Anatomy of a Tag +==================== + +Creating a fully functional custom tag in Latte involves several interconnected parts. Before diving into the implementation, let's understand the core concepts and terminology, drawing an analogy to HTML and the Document Object Model (DOM). + + +Tags vs. Nodes (Analogy with HTML) +---------------------------------- + +In HTML, we write **tags** like `

                                                      ` or `

                                                      ...
                                                      `. These tags are syntax in the source code. When a browser parses this HTML, it creates an in-memory representation called the **Document Object Model (DOM)**. In the DOM, the HTML tags are represented by **nodes** (specifically, `Element` nodes in JavaScript DOM terminology). We interact with these *nodes* programmatically (e.g., using JavaScript `document.getElementById(...)` returns an Element node). The tag is just the textual representation in the source file; the node is the object representation in the logical tree. + +Latte works similarly: + +- In a `.latte` template file, you write **Latte tags**, like `{foreach ...}` and `{/foreach}`. This is the syntax you, as a template author, interact with. +- When Latte **parses** the template, it builds an **Abstract Syntax Tree (AST)**. This tree is composed of **Nodes**. Each Latte tag, HTML element, piece of text, or expression within the template becomes one or more nodes in this tree. +- The base class for all nodes in the AST is `Latte\Compiler\Node`. Just like the DOM has different node types (Element, Text, Comment), Latte's AST has various node types. You'll encounter `Latte\Compiler\Nodes\TextNode` for static text, `Latte\Compiler\Nodes\Html\ElementNode` for HTML elements, `Latte\Compiler\Nodes\Php\ExpressionNode` for expressions inside tags, and crucially for custom tags, nodes inheriting from `Latte\Compiler\Nodes\StatementNode`. + + +Why `StatementNode`? +-------------------- + +HTML elements (`Html\ElementNode`) primarily represent structure and content. PHP expressions (`Php\ExpressionNode`) represent values or calculations. But what about Latte tags like `{if}`, `{foreach}`, or our custom `{datetime}`? These tags *perform actions*, control program flow, or generate output based on logic. They are the functional units that make Latte a powerful templating *engine*, not just a markup language. + +In programming, such action-performing units are often called "statements". Therefore, nodes representing these functional Latte tags typically inherit from `Latte\Compiler\Nodes\StatementNode`. This distinguishes them from purely structural nodes (like HTML elements) or value-representing nodes (like expressions). + + +The Key Components +================== + +Let's revisit the main components needed to create a custom tag: + + +Tag Parsing Function +-------------------- + +- This PHP callable parses the Latte tag syntax (`{...}`) in the template source. +- It receives information about the tag (like its name, position, and whether it's an n:attribute) via a [api:Latte\Compiler\Tag] object. +- Its primary tool for parsing arguments and expressions within the tag delimiters is the [api:Latte\Compiler\TagParser] object, accessible via `$tag->parser` (this is a different parser than the one that parses the whole template). +- For paired tags, it uses `yield` to signal Latte to parse the inner content between the start and end tags. +- The ultimate goal of the parsing function is to create and return a **Node Class** instance, which gets added to the AST. +- It's customary (though not required) to implement the parsing function as a static method (often named `create`) directly within the corresponding Node class. This keeps the parsing logic and the node's representation neatly bundled together, allows access to private/protected class elements if needed, and improves organization. + + +Node Class +---------- + +- Represents the *logical function* of your tag within the **Abstract Syntax Tree (AST)**. +- Holds parsed information (like arguments or content) as public properties. These properties often contain other `Node` instances (e.g., `ExpressionNode` for parsed arguments, `AreaNode` for parsed content). +- The `print(PrintContext $context): string` method generates the *PHP code* (a statement or series of statements) that performs the tag's action during template rendering. +- The `getIterator(): \Generator` method makes child nodes (arguments, content) accessible for traversal by **Compiler Passes**. It must yield references (`&`) to allow passes to potentially modify or replace subnodes. +- After the entire template is parsed into an AST, Latte runs a series of [compiler passes|compiler-passes]. These passes traverse the *entire* AST using the `getIterator()` method provided by each node. They can inspect nodes, collect information, and even *modify* the tree (e.g., by changing the public properties of nodes or replacing nodes entirely). This design, requiring comprehensive `getIterator()`, is crucial. It allows powerful features like the [Sandbox|sandbox] to analyze and potentially alter the behavior of *any* part of the template, including your custom tags, ensuring security and consistency. + + +Registration via an Extension +----------------------------- + +- You need to tell Latte about your new tag and which parsing function to use for it. This happens within a [Latte Extension |extending-latte#Latte Extension]. +- Inside your extension class, you implement the `getTags(): array` method. This method returns an associative array where keys are the tag names (e.g., `'mytag'`, `'n:myattribute'`) and values are the PHP callables representing their respective parsing functions (e.g., `MyNamespace\DatetimeNode::create(...)`). + +In summary: The **tag parsing function** turns the *template source code* of your tag into an **AST Node**. The **Node class** then knows how to turn *itself* into executable *PHP code* for the compiled template and makes its subnodes available for **compiler passes** via `getIterator()`. **Registration via an Extension** connects the tag name to the parsing function and makes it known to Latte. + +We will now explore how to implement these components step-by-step. + + +Creating a Simple Tag +===================== + +Let's dive into creating your first custom Latte tag. We'll start with a very simple example: a tag named `{datetime}` that outputs the current date and time. **Initially, this tag won't accept any arguments**, but we will enhance it later in the [#"Parsing Tag Arguments"] section. It also doesn't have inner content. + +This example will guide you through the essential steps: defining the Node class, implementing its `print()` and `getIterator()` methods, creating the parsing function, and finally registering the tag. + +**Goal:** Implement `{datetime}` to output the current date and time using PHP's `date()` function. + + +Creation of the Node Class +-------------------------- + +First, we need a class to represent our tag in the Abstract Syntax Tree (AST). As discussed above, we inherit from `Latte\Compiler\Nodes\StatementNode`. + +Create a file (e.g., `DatetimeNode.php`) and define the class: + +```php +node = new self; + return $node; + } + + /** + * Generates the PHP code that will be executed when the template is rendered. + */ + public function print(PrintContext $context): string + { + return $context->format( + 'echo date(\'Y-m-d H:i:s\') %line;', + $this->position, + ); + } + + /** + * Provides access to child nodes for Latte's compiler passes. + */ + public function &getIterator(): \Generator + { + false && yield; + } +} +``` + +When Latte encounters `{datetime}` in a template, it calls the tag parsing function `create()`. Its job is to return an instance of `DatetimeNode`. + +The `print()` method generates the PHP code that will be executed when the template is rendered. We call the `$context->format()` method, which assembles the resulting PHP code string for the compiled template. The first argument, `'echo date('Y-m-d H:i:s') %line;'`, is the mask into which the subsequent parameters are substituted. The `%line` placeholder tells the `format()` method to take the second following argument, which is `$this->position`, and inserts a comment like `/* line 15 */` that links the generated PHP code back to the original template line, which is crucial for debugging. + +Property `$this->position` is inherited from the base `Node` class, and is automatically set by Latte's parser. It holds a [api:Latte\Compiler\Position] object indicating where the tag was found in the source `.latte` file. + +The `getIterator()` method is vital for compiler passes. It must yield all child nodes, but our simple `DatetimeNode` currently has no arguments or content, thus no child nodes. However, the method must still exist and be a generator, i.e. the `yield` keyword must be somehow present in the method body. + + +Registration via an Extension +----------------------------- + +Finally, tell Latte about the new tag. Create an [Extension class |extending-latte#Latte Extension] (e.g., `MyLatteExtension.php`) and register the tag in its `getTags()` method. + +```php + Map: 'tag-name' => parsing-function + */ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + // Register more tags here later + ]; + } +} +``` + +Then, register this extension with the Latte Engine: + +```php +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +Create template: + +```latte +

                                                      Page generated on: {datetime}

                                                      +``` + +Expected Output: `

                                                      Page generated on: 2023-10-27 11:00:00

                                                      ` + + +Summary of this Phase +--------------------- + +We've successfully created a basic custom tag `{datetime}`. We defined its representation in the AST (`DatetimeNode`), handled its parsing (`create()`), specified how it should generate PHP code (`print()`), ensured its children are traversable (`getIterator()`), and registered it with Latte. + +In the next section, we'll enhance this tag to accept arguments, demonstrating how to parse expressions and manage child nodes. + + +Parsing Tag Arguments +===================== + +Our simple `{datetime}` tag works, but it's not very flexible. Let's enhance it to accept an optional argument: a format string for the `date()` function. The desired syntax will be `{datetime $format}`. + +**Goal:** Modify `{datetime}` to accept an optional PHP expression as an argument, which will be used as the format string for `date()`. + + +Introducing `TagParser` +----------------------- + +Before we modify the code, it's important to understand the tool we'll use [api:Latte\Compiler\TagParser]. When Latte's main parser (`TemplateParser`) encounters a Latte tag like `{datetime ...}` or an n:attribute, it delegates the parsing of the content *inside* the tag (the part between `{` and `}`, or the attribute's value) to a specialized `TagParser`. + +This `TagParser` operates solely on the **tag's arguments**. Its job is to consume tokens representing these arguments. Crucially, it **must parse the entire content** provided to it. If your parsing function finishes but the `TagParser` hasn't reached the end of the arguments (checked via `$tag->parser->isEnd()`), Latte will throw an exception, because it indicates unexpected tokens were left inside the tag. Conversely, if a tag *requires* arguments, you should call `$tag->expectArguments()` at the beginning of your parsing function. This method checks if arguments are present and throws a helpful exception if the tag was used without any. + +`TagParser` offers useful methods for parsing different kinds of arguments: + +- `parseExpression(): ExpressionNode`: Parses a PHP-like expression (variables, literals, operators, function/method calls, etc.). It handles Latte's syntax sugar, like treating simple alphanumeric strings as quoted strings (e.g., `foo` is parsed as if it were `'foo'`). +- `parseUnquotedStringOrExpression(): ExpressionNode`: Parses either a standard expression or an *unquoted string*. Unquoted strings are sequences allowed by Latte without quotes, often used for things like file paths (e.g., `{include ../file.latte}`). If it parses an unquoted string, it returns a `StringNode`. +- `parseArguments(): ArrayNode`: Parses comma-separated arguments, potentially with keys, like `10, name: 'John', true`. +- `parseModifier(): ModifierNode`: Parses filters like `|upper|truncate:10`. +- `parseType(): ?SuperiorTypeNode`: Parses PHP type hints like `int`, `?string`, `array|Foo`. + +For more complex or low-level parsing needs, you can directly interact with the [token stream|api:Latte\Compiler\TokenStream] via `$tag->parser->stream`. This object provides methods to inspect and consume individual tokens: + +- `$tag->parser->stream->is(...): bool`: Checks if the *current* token matches any of the specified types (e.g., `Token::Php_Variable`) or literal values (e.g., `'as'`) without consuming it. Useful for looking ahead. +- `$tag->parser->stream->consume(...): Token`: Consumes the *current* token and moves the stream position forward. If expected token types/values are provided as arguments and the current token doesn't match, it throws a `CompileException`. Use this when you *expect* a certain token. +- `$tag->parser->stream->tryConsume(...): ?Token`: Attempts to consume the *current* token *only if* it matches one of the specified types/values. If it matches, it consumes the token and returns it. If it doesn't match, it leaves the stream position unchanged and returns `null`. Use this for optional tokens or when choosing between different syntax paths. + + +Updating the Parsing Function `create()` +---------------------------------------- + +With that understanding, let's modify the `create()` method in `DatetimeNode` to parse the optional format argument using `$tag->parser`. + +```php +node = new self; + + // Check if there are any tokens + if (!$tag->parser->isEnd()) { + // Parse the argument as a PHP-like expression using the TagParser. + $node->format = $tag->parser->parseExpression(); + } + + return $node; + } + + // ... print() and getIterator() methods will be updated next ... +} +``` + +We added the public property `$format`. In `create()`, we now use `$tag->parser->isEnd()` to check if there *are* arguments. If so, `$tag->parser->parseExpression()` consumes the tokens for the expression. Because the `TagParser` must consume all its input tokens, Latte will automatically throw an error if the user writes something unexpected after the format expression (e.g., `{datetime 'Y-m-d', unexpected}`). + + +Updating the `print()` Method +----------------------------- + +Now, modify the `print()` method to use the parsed format expression stored in `$this->format`. If no format was provided (`$this->format` is `null`), we should use a default format string, for example, `'Y-m-d H:i:s'`. + +```php + public function print(PrintContext $context): string + { + $formatNode = $this->format ?? new StringNode('Y-m-d H:i:s'); + + // %node prints the PHP code representation of the $formatNode. + return $context->format( + 'echo date(%node) %line;', + $formatNode, + $this->position + ); + } +``` + +Into the `$formatNode` variable, we store the AST node representing the format string for the PHP `date()` function. We use the null coalescing operator (`??`) here. If the user provided an argument in the template (e.g., `{datetime 'd.m.Y'}`), then the `$this->format` property holds the corresponding node (in this case, a `StringNode` with the value `'d.m.Y'`), and that node is used. If the user did not provide an argument (wrote just `{datetime}`), the `$this->format` property is `null`, and instead, we create a new `StringNode` with the default format `'Y-m-d H:i:s'`. This ensures that `$formatNode` always contains a valid AST node for the format. + +In the mask `'echo date(%node) %line;'`, the new placeholder `%node` is used, which tells the `format()` method to take the first following argument (which is our `$formatNode`), call its `print()` method (which returns its PHP code representation), and insert that result at the placeholder's position. + + +Implementing `getIterator()` for Subnodes +----------------------------------------- + +Our `DatetimeNode` now has a child node: the `$format` expression. We **must** make this child node accessible to compiler passes by yielding it in the `getIterator()` method. Remember to yield a *reference* (`&`) to allow passes to potentially replace the node. + +```php + public function &getIterator(): \Generator + { + if ($this->format) { + yield $this->format; + } + } +``` + +Why is this crucial? Imagine a Sandbox pass that needs to check if the `$format` argument contains a forbidden function call (e.g., `{datetime dangerousFunction()}`). If `getIterator()` doesn't yield `$this->format`, the Sandbox pass would never see the `dangerousFunction()` call inside our tag's argument, creating a potential security hole. By yielding it, we allow the Sandbox (and other passes) to inspect and potentially modify the `$format` expression node. + + +Using the Enhanced Tag +---------------------- + +The tag now correctly handles an optional argument: + +```latte +Default format: {datetime} +Custom format: {datetime 'd.m.Y'} +Using variable: {datetime $userDateFormatPreference} + +{* This would cause an error after parsing 'd.m.Y' because ", foo" is unexpected *} +{* {datetime 'd.m.Y', foo} *} +``` + +Next, we'll look at creating paired tags that process the content between them. + + +Handling Paired Tags +==================== + +So far, our `{datetime}` tag is *self-closing* (conceptually). It doesn't have any content between a start and end tag. Many useful tags, however, operate on a block of template content. These are called **paired tags**. Examples include `{if}...{/if}`, `{block}...{/block}`, or the custom tag we'll build now: `{debug}...{/debug}`. + +This tag will allow us to include debugging information in our templates that should only be visible during development. + +**Goal:** Create a paired tag `{debug}` whose content is only rendered if a specific "development mode" flag is active. + + +Introducing Providers +--------------------- + +Sometimes, your tags need access to data or services that aren't passed directly as template parameters. For instance, determining if the application is in development mode, accessing a user object, or getting configuration values. Latte provides a mechanism called **Providers** for this. + +Providers are registered within your [Extension |extending-latte#Latte Extension] using the `getProviders()` method. This method returns an associative array where keys are names under which the providers will be accessible in the template's runtime code, and values are the actual data or objects. + +Inside the PHP code generated by your tag's `print()` method, you can then access these providers via the special object property `$this->global`. Since this property is shared across all extensions, it's a good practice to **prefix your provider names** to avoid potential naming collisions with Latte's core providers or providers from other third-party extensions. A common convention is to use a short, unique prefix related to your vendor or extension name. For our example, let's use the prefix `app` and the development mode flag will be available as `$this->global->appDevMode`. + + +The `yield` Keyword for Parsing Content +--------------------------------------- + +How do we tell Latte's parser to process the content *between* `{debug}` and `{/debug}`? This is where the `yield` keyword comes into play. + +When `yield` is used in the `create()` function, the function becomes a [PHP Generator |https://www.php.net/manual/en/language.generators.overview.php]. Its execution pauses, and control returns to the main `TemplateParser`. The `TemplateParser` then continues parsing the template content *until* it encounters the corresponding closing tag (`{/debug}` in our case). + +Once the closing tag is found, the `TemplateParser` resumes the execution of our `create()` function right after the `yield` statement. The value *returned* by `yield` is an array containing two elements: + +1. An `AreaNode` representing the parsed content between the start and end tags. +2. The `Tag` object representing the closing tag (e.g., `{/debug}`). + +Let's create the `DebugNode` class and its `create` method using `yield`. + +```php +node = new self; + + // Pause parsing, get inner content and end tag when {/debug} is found + [$node->content, $endTag] = yield; + + return $node; + } + + // ... print() and getIterator() will be implemented next ... +} +``` + +Note: `$endTag` is `null` if the tag is used as n:attribute, i.e. `
                                                      ...
                                                      `. + + +Implementing `print()` for Conditional Rendering +------------------------------------------------ + +The `print()` method now needs to generate PHP code that checks the `appDevMode` provider at runtime and only executes the code for the inner content if the flag is true. + +```php + public function print(PrintContext $context): string + { + // Generate a PHP 'if' statement that checks the provider at runtime + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + // If in dev mode, print the inner content + %node + } + + XX, + $this->position, // For the %line comment + $this->content, // The node containing the inner content's AST + ); + } +``` + +This is straightforward. We use `PrintContext::format()` to create a standard PHP `if` statement. Inside the `if`, we place the `%node` placeholder for `$this->content`. Latte will recursively call `$this->content->print($context)` to generate the PHP code for the inner part of the tag, but only if `$this->global->appDevMode` evaluates to true at runtime. + + +Implementing `getIterator()` for Content +---------------------------------------- + +Just like with the argument node in the previous example, our `DebugNode` now has a child node: the `AreaNode $content`. We must make it traversable by yielding it in `getIterator()`: + +```php + public function &getIterator(): \Generator + { + // Yield the reference to the content node + yield $this->content; + } +``` + +This allows compiler passes to descend into the content of our `{debug}` tag, which is important even if the content is conditionally rendered. For example, the Sandbox needs to analyze the content regardless of whether `appDevMode` is true or false. + + +Registration and Usage +---------------------- + +Register the tag and the provider in your extension: + +```php +class MyLatteExtension extends Extension +{ + // Assuming $isDevelopmentMode is determined somewhere (e.g., from config) + public function __construct( + private bool $isDevelopmentMode, + ) { + } + + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), // Register the new tag + ]; + } + + public function getProviders(): array + { + return [ + 'appDevMode' => $this->isDevelopmentMode, // Register the provider + ]; + } +} + +// When registering the extension: +$isDev = true; // Determine this based on your application's environment +$latte->addExtension(new App\Latte\MyLatteExtension($isDev)); +``` + +And use it in a template: + +```latte +

                                                      Regular content visible always.

                                                      + +{debug} +
                                                      + Current user ID: {$user->id} + Request time: {=time()} +
                                                      +{/debug} + +

                                                      More regular content.

                                                      +``` + + +n:attributes Integration +------------------------ + +Latte offers a convenient shorthand for many paired tags: [n:attributes |syntax#n:attributes]. If you have a paired tag like `{tag}...{/tag}` and you want its effect to apply directly to a single HTML element, you can often write it more concisely as an `n:tag` attribute on that element. + +For most standard paired tags you define (like our `{debug}`), Latte automatically enables the corresponding `n:` attribute version. You don't need to do anything extra during registration: + +```latte +{* Standard paired tag usage *} +{debug}
                                                      Debug info
                                                      {/debug} + +{* Equivalent usage with n:attribute *} +
                                                      Debug info
                                                      +``` + +Both will render the `
                                                      ` only if `$this->global->appDevMode` is true. The `inner-` and `tag-` prefixes also work as expected. + +Sometimes, your tag's logic might need to behave slightly differently depending on whether it's used as a standard pair tag or as an n:attribute, or if a prefix like `n:inner-tag` or `n:tag-tag` is used. The `Latte\Compiler\Tag` object, passed to your `create()` parsing function, provides this information: + +- `$tag->isNAttribute(): bool`: Returns `true` if the tag is being parsed as an n:attribute +- `$tag->prefix: ?string`: Returns the prefix used with the n:attribute, which can be `null` (not an n:attribute), `Tag::PrefixNone`, `Tag::PrefixInner`, or `Tag::PrefixTag` + +Now that we understand simple tags, argument parsing, paired tags, providers, and n:attributes, let's tackle a more complex scenario involving tags nested within other tags, using our `{debug}` tag as a starting point. + + +Intermediate Tags +================= + +Some paired tags allow or even require other tags to appear *inside* them before the final closing tag. These are called **intermediate tags**. Classic examples include `{if}...{elseif}...{else}...{/if}` or `{switch}...{case}...{default}...{/switch}`. + +Let's extend our `{debug}` tag to support an optional `{else}` clause, which will be rendered when the application is *not* in development mode. + +**Goal:** Modify `{debug}` to support an optional `{else}` intermediate tag. The final syntax should be `{debug} ... {else} ... {/debug}`. + + +Parsing Intermediate Tags with `yield` +-------------------------------------- + +We already know that `yield` pauses the parsing function `create()` and returns the parsed content along with the end tag. However, `yield` offers more control: you can provide it with an array of *intermediate tag names*. When the parser encounters any of these specified tags **at the same nesting level** (i.e., as direct children of the parent tag, not inside other blocks or tags within it), it will also stop parsing the content. + +When parsing stops due to an intermediate tag, it stops parsing the content, resumes the `create()` generator, and passes back the partially parsed content and the **intermediate tag** itself (instead of the final end tag). Our `create()` function can then handle this intermediate tag (e.g., parse its arguments if it had any) and `yield` again to parse the *next* part of the content until the *final* end tag or another expected intermediate tag is found. + +Let's modify `DebugNode::create()` to expect `{else}`: + +```php +node = new self; + + // yield and expect either {/debug} or {else} + [$node->thenContent, $nextTag] = yield ['else']; + + // Check if the tag we stopped at was {else} + if ($nextTag?->name === 'else') { + // Yield again to parse the content between {else} and {/debug} + [$node->elseContent, $endTag] = yield; + } + + return $node; + } + + // ... print() and getIterator() will be updated next ... +} +``` + +Now, `yield ['else']` tells Latte to stop parsing not only for `{/debug}` but also for `{else}`. If `{else}` is encountered, `$nextTag` will contain the `Tag` object for `{else}`. We then `yield` again without any arguments, meaning we now only expect the final `{/debug}` tag, and store the result in `$node->elseContent`. If `{else}` was not found, `$nextTag` would be the `Tag` for `{/debug}` (or `null` if used as n:attribute), and `$node->elseContent` would remain `null`. + + +Implementing `print()` with `{else}` +------------------------------------ + +The `print()` method needs to reflect the new structure. It should generate a PHP `if/else` statement based on the `appDevMode` provider. + +```php + public function print(PrintContext $context): string + { + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + %node // Code for the 'then' branch ({debug} content) + } else { + %node // Code for the 'else' branch ({else} content) + } + + XX, + $this->position, // Line number for the 'if' condition + $this->thenContent, // First %node placeholder + $this->elseContent ?? new NopNode, // Second %node placeholder + ); + } +``` + +This is a standard PHP `if/else` structure. We use `%node` twice; `format()` substitutes the provided nodes sequentially. We use `?? new NopNode` to avoid errors if `$this->elseContent` is `null` – the `NopNode` simply prints nothing. + + +Implementing `getIterator()` for Both Contents +---------------------------------------------- + +We now have potentially two child content nodes (`$thenContent` and `$elseContent`). We must yield both if they exist: + +```php + public function &getIterator(): \Generator + { + yield $this->thenContent; + if ($this->elseContent) { + yield $this->elseContent; + } + } +``` + + +Using the Enhanced Tag +---------------------- + +The tag can now be used with an optional `{else}` clause: + +```latte +{debug} +

                                                      Showing debug info because devMode is ON.

                                                      +{else} +

                                                      Debug info is hidden because devMode is OFF.

                                                      +{/debug} +``` + + +Handling State and Nesting +========================== + +Our previous examples (`{datetime}`, `{debug}`) were relatively stateless within their `print()` methods. They either directly outputted content or made a simple conditional check based on a global provider. However, many tags need to manage some form of **state** during rendering or involve evaluating user-provided expressions that should only be run once for performance or correctness. Furthermore, we need to consider what happens when our custom tags are **nested**. + +Let's illustrate these concepts by creating a `{repeat $count}...{/repeat}` tag. This tag will repeat its inner content `$count` times. + +**Goal:** Implement `{repeat $count}` which repeats its content a specified number of times. + + +The Need for Temporary & Unique Variables +----------------------------------------- + +Imagine the user writes: + +```latte +{repeat rand(1, 5)} Content {/repeat} +``` + +If we naively generated a PHP `for` loop like this in our `print()` method: + +```php +// Simplified, INCORRECT generated code +for ($i = 0; $i < rand(1, 5); $i++) { + // print content +} +``` +This would be wrong! The `rand(1, 5)` expression would be **re-evaluated on every loop iteration**, leading to an unpredictable number of repetitions. We need to evaluate the `$count` expression *once* before the loop starts and store its result. + +We'll generate PHP code that first evaluates the count expression and stores it in a **temporary runtime variable**. To avoid clashes with variables defined by the template user *and* internal Latte variables (like `$ʟ_...`), we'll use the **`$__` (double underscore)** prefix convention for our temporary variables. + +The generated code would then look like this: + +```php +$__count = rand(1, 5); +for ($__i = 0; $__i < $__count; $__i++) { + // print content +} +``` + +Now consider nesting: + +```latte +{repeat $countA} {* Outer loop *} + {repeat $countB} {* Inner loop *} + ... + {/repeat} +{/repeat} +``` + +If both the outer and inner `{repeat}` tags generated code using the *same* temporary variable names (e.g., `$__count` and `$__i`), the inner loop would overwrite the outer loop's variables, breaking the logic. + +We need to ensure that the temporary variables generated for each instance of the `{repeat}` tag are **unique**. We achieve this using `PrintContext::generateId()`. This method returns a unique integer during the compilation phase. We can append this ID to our temporary variable names. + +So, instead of `$__count`, we'll generate `$__count_1` for the first repeat tag, `$__count_2` for the second, and so on. Similarly for the loop counter, we'll use `$__i_1`, `$__i_2`, etc. + + +Implementing `RepeatNode` +------------------------- + +Let's create the node class. + +```php +expectArguments(); // ensure $count is provided + $node = $tag->node = new self; + // Parse the count expression + $node->count = $tag->parser->parseExpression(); + // Get the inner content + [$node->content] = yield; + return $node; + } + + /** + * Generates the PHP 'for' loop with unique variable names. + */ + public function print(PrintContext $context): string + { + // Generate unique variable names + $id = $context->generateId(); + $countVar = '$__count_' . $id; // e.g., $__count_1, $__count_2, etc. + $iteratorVar = '$__i_' . $id; // e.g., $__i_1, $__i_2, etc. + + return $context->format( + <<<'XX' + // Evaluate the count expression *once* and store it + %raw = (int) (%node); + // Loop using the stored count and unique iterator variable + for (%raw = 0; %2.raw < %0.raw; %2.raw++) %line { + %node // Render the inner content + } + + XX, + $countVar, // %0 - Variable to store the count + $this->count, // %1 - The expression node for the count + $iteratorVar, // %2 - Loop iterator variable name + $this->position, // %3 - Line number comment for the loop itself + $this->content // %4 - The inner content node + ); + } + + /** + * Yields child nodes (the count expression and the content). + */ + public function &getIterator(): \Generator + { + yield $this->count; + yield $this->content; + } +} +``` + +Method `create()` parses the required `$count` expression using `parseExpression()`. First, `$tag->expectArguments()` is called. This ensures that the user has provided *something* after `{repeat}`. While `$tag->parser->parseExpression()` would fail if nothing was provided, the error message might be about unexpected syntax. Using `expectArguments()` gives a much clearer error, specifically stating that arguments are missing for the `{repeat}` tag. + +The `print()` method generates the PHP code responsible for executing the repeat logic at runtime. It starts by generating unique names for the temporary PHP variables it will need. + +Method `$context->format()` is called with the new placeholder `%raw` that inserts the *raw string* provided as the corresponding argument. Here, it inserts the unique variable name stored in `$countVar` (e.g., `$__count_1`). And what about `%0.raw` and `%2.raw`? This demonstrates **positional placeholders**. Instead of just `%raw` which takes the *next* available raw argument, `%2.raw` explicitly takes the argument at index 2 (which is `$iteratorVar`) and inserts its raw string value. This allows us to reuse the `$iteratorVar` string without passing it multiple times in the arguments list to `format()`. + +This carefully constructed `format()` call generates an efficient and safe PHP loop that correctly handles the count expression and avoids variable name collisions even when `{repeat}` tags are nested. + + +Registration and Usage +---------------------- + +Register the tag in your extension: + +```php +use App\Latte\RepeatNode; + +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), // Register the repeat tag + ]; + } +} +``` + +Use it in a template, including nesting: + +```latte +{var $rows = rand(5, 7)} +{var $cols = rand(3, 5)} + +{repeat $rows} +
                                                  + {repeat $cols} + + {/repeat} + +{/repeat} +``` + +This example demonstrates how to handle state (loop counters) and potential nesting issues using temporary variables prefixed with `$__` and made unique with IDs from `PrintContext::generateId()`. + + +Pure n:attributes +----------------- + +While many `n:attributes` like `n:if` or `n:foreach` serve as convenient shorthands for their paired tag counterparts (`{if}...{/if}`, `{foreach}...{/foreach}`), Latte also allows you to define tags that *only* exist in the n:attribute form. These are often used to modify the attributes or behavior of the HTML element they are attached to. + +Standard examples built into Latte include [`n:class` |tags#n:class], which helps dynamically build the `class` attribute, and [`n:attr` |tags#n:attr], which can set multiple arbitrary attributes. + +Let's create our own pure n:attribute: `n:confirm`, which will add a JavaScript confirmation dialog before an action (like following a link or submitting a form) is performed. + +**Goal:** Implement `n:confirm="'Are you sure?'"` which adds an `onclick` handler to prevent the default action if the user cancels the confirmation dialog. + + +Implementing `ConfirmNode` +-------------------------- + +We need a Node class and a parsing function. + +```php +expectArguments(); + $node = $tag->node = new self; + $node->message = $tag->parser->parseExpression(); + return $node; + } + + /** + * Generates the 'onclick' attribute code with proper escaping. + */ + public function print(PrintContext $context): string + { + // It ensures correct escaping for both JavaScript and HTML attribute contexts. + return $context->format( + <<<'XX' + echo ' onclick="', LR\Filters::escapeHtmlAttr('return confirm(' . LR\Filters::escapeJs(%node) . ')'), '"' %line; + XX, + $this->message, + $this->position, + ); + } + + public function &getIterator(): \Generator + { + yield $this->message; + } +} +``` + +Method `print()` generates the PHP code that will ultimately output the `onclick="..."` HTML attribute during template rendering. Handling nested contexts (JavaScript inside an HTML attribute) requires careful escaping. The `LR\Filters::escapeJs(%node)` filter is called at runtime and escapes the message correctly for use inside JavaScript (the output would be like `"Sure?"`). Then the `LR\Filters::escapeHtmlAttr(...)` filter escapes characters that are special within HTML attributes, so it would turn the output into `return confirm("Sure?")`. This two-step runtime escaping ensures that the message is safe for JavaScript and the resulting JavaScript code is safe for embedding within the HTML `onclick` attribute. + + +Registration and Usage +---------------------- + +Register the n:attribute in your extension. Remember the `n:` prefix in the key: + +```php +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), + 'n:confirm' => ConfirmNode::create(...), // Register n:confirm + ]; + } +} +``` + +Now you can use `n:confirm` on links, buttons, or form elements: + +```latte +Delete +``` + +Generated HTML: + +```html +Delete +``` + +When the user clicks the link, the browser will execute the `onclick` code, display the confirmation dialog, and only proceed to `delete.php` if the user clicks "OK". + +This example demonstrates how a pure n:attribute can be created to modify the behavior or attributes of its host HTML element by generating appropriate PHP code within its `print()` method. Remember the double escaping often required: once for the target context (JavaScript in this case) and again for the HTML attribute context. + + +Advanced Topics +=============== + +While the previous sections cover the core concepts, here are a few more advanced topics you might encounter when creating custom Latte tags. + + +Tag Output Modes +---------------- + +The `Tag` object passed to your `create()` function has a property `outputMode`. This property influences how Latte handles surrounding whitespace and indentation, particularly when the tag is used on its own line. You can modify this property within your `create()` function. + +- `Tag::OutputKeepIndentation` (Default for most tags like `{=...}`): Latte tries to preserve the indentation before the tag. Newlines *after* the tag are generally kept. This is suitable for tags that output content inline. +- `Tag::OutputRemoveIndentation` (Default for block tags like `{if}`, `{foreach}`): Latte removes leading indentation and potentially a single trailing newline. This helps keep the generated PHP code cleaner and avoids extra blank lines in the HTML output caused by the tag itself. Use this for tags that represent control structures or blocks that shouldn't add whitespace themselves. +- `Tag::OutputNone` (Used by tags like `{var}`, `{default}`): Similar to `RemoveIndentation`, but signals more strongly that the tag itself produces no direct output, potentially influencing whitespace handling around it even more aggressively. Suitable for declaration or setup tags. + +Choose the mode that best fits your tag's purpose. For most structural or control-flow tags, `OutputRemoveIndentation` is usually appropriate. + + +Accessing Parent/Closest Tags +----------------------------- + +Sometimes, a tag's behavior needs to depend on the context it's used in, specifically which parent tag(s) it resides within. The `Tag` object passed to your `create()` function provides the `closestTag(array $classes, ?callable $condition = null): ?Tag` method precisely for this purpose. + +This method searches upwards through the hierarchy of currently open tags (including HTML elements represented internally during parsing) and returns the `Tag` object of the nearest ancestor that matches specific criteria. If no matching ancestor is found, it returns `null`. + +The `$classes` array specifies what kind of ancestor tags you are looking for. It checks if the ancestor tag's associated node (`$ancestorTag->node`) is an instance of this class. + +```php +function create(Tag $tag) +{ + // Look for the nearest ancestor tag whose node is an instance of ForeachNode + $foreachTag = $tag->closestTag([ForeachNode::class]); + if ($foreachTag) { + // We can access the ForeachNode instance itself: + $foreachNode = $foreachTag->node; + } +} +``` + +Notice the `$foreachTag->node`: This works only because it's a convention in Latte tag development to immediately assign the created node to `$tag->node` within the `create()` method, like we always did. + +Sometimes, just matching the node type isn't enough. You might need to check a specific property of the potential ancestor tag or its node. The optional second argument to `closestTag()` is a callable that receives the potential ancestor `Tag` object and should return if it's a valid match. + +```php +function create(Tag $tag) +{ + $dynamicBlockTag = $tag->closestTag( + [BlockNode::class], + // Condition: block must be dynamic + fn(Tag $blockTag) => $blockTag->node->block->isDynamic(), + ); +} +``` + +Using `closestTag()` allows you to create tags that are context-aware and enforce correct usage within your template structure, leading to more robust and understandable templates. + + +`PrintContext::format()` Placeholders +------------------------------------- + +We've frequently used `PrintContext::format()` to generate PHP code in the `print()` methods of our nodes. It accepts a mask string and subsequent arguments that replace placeholders in the mask. Here's a summary of the available placeholders: + +- **`%node`**: Argument must be a `Node` instance. It calls the node's `print()` method and inserts the resulting PHP code string. +- **`%dump`**: Argument is any PHP value. It exports the value into valid PHP code. Suitable for scalars, arrays, null. + - `$context->format('echo %dump;', 'Hello')` -> `echo 'Hello';` + - `$context->format('$arr = %dump;', [1, 2])` -> `$arr = [1, 2];` +- **`%raw`**: Inserts argument directly into the output PHP code without any escaping or modification. **Use with caution**, primarily for inserting pre-generated PHP code snippets or variable names. + - `$context->format('%raw = 1;', '$variableName')` -> `$variableName = 1;` +- **`%args`**: Argument must be an `Expression\ArrayNode`. It prints the array items formatted as arguments for a function or method call (comma-separated, handling named arguments if present). + - `$argsNode = new ArrayNode([...]);` + - `$context->format('myFunc(%args);', $argsNode)` -> `myFunc(1, name: 'Joe');` +- **`%line`**: Argument must be a `Position` object (usually `$this->position`). It inserts a PHP comment `/* line X */` indicating the source line number. + - `$context->format('echo "Hi" %line;', $this->position)` -> `echo "Hi" /* line 42:1 */;` +- **`%escape(...)`**: It generates PHP code that, *at runtime*, will escape the inner expression using the current context-aware escaping rules. + - `$context->format('echo %escape(%node);', $variableNode)` +- **`%modify(...)`**: Argument must be a `ModifierNode`. It generates PHP code that applies the filters specified in the `ModifierNode` to the inner content, including context-aware escaping if not disabled by `|noescape`. + - `$context->format('%modify(%node);', $modifierNode, $variableNode)` +- **`%modifyContent(...)`**: Similar to `%modify`, but intended for modifying blocks of captured content (often HTML). + +You can explicitly reference arguments by their zero-based index: `%0.node`, `%1.dump`, `%2.raw`, etc. This allows reusing an argument multiple times in the mask without passing it repeatedly to `format()`. See the `{repeat}` tag example where `%0.raw` and `%2.raw` were used. + + +Complex Argument Parsing Example +-------------------------------- + +While `parseExpression()`, `parseArguments()`, etc., cover many cases, sometimes you need more intricate parsing logic using the lower-level `TokenStream` available via `$tag->parser->stream`. + +**Goal:** Create a tag `{embedYoutube $videoID, width: 640, height: 480}`. We want to parse a required video ID (string or variable) followed by optional key-value pairs for dimensions. + +```php +expectArguments(); + $node = $tag->node = new self; + // Parse the required video ID + $node->videoId = $tag->parser->parseExpression(); + + // Parse optional key-value pairs + $stream = $tag->parser->stream; // Get the token stream + while ($stream->tryConsume(',')) { // Requires comma separation + // Expect 'width' or 'height' identifier + $keyToken = $stream->consume(Token::Php_Identifier); + $key = strtolower($keyToken->text); + + $stream->consume(':'); // Expect colon separator + + $value = $tag->parser->parseExpression(); // Parse the value expression + + if ($key === 'width') { + $node->width = $value; + } elseif ($key === 'height') { + $node->height = $value; + } else { + throw new CompileException("Unknown argument '$key'. Expected 'width' or 'height'.", $keyToken->position); + } + } + + return $node; + } +} +``` + +This level of control allows you to define very specific and complex syntaxes for your custom tags by directly interacting with the token stream. + + +Using `AuxiliaryNode` +--------------------- + +Latte provides generic "helper" nodes for special situations during code generation or within compiler passes. These are `AuxiliaryNode` and `Php\Expression\AuxiliaryNode`. + +Think of `AuxiliaryNode` as a flexible container node that delegates its core functionalities - code generation and child node exposure - to arguments provided in its constructor: + +- `print()` Delegation: The first constructor argument is a PHP **closure**. When Latte calls the `print()` method on an `AuxiliaryNode`, it executes this provided closure. The closure receives the `PrintContext` and any nodes passed in the second constructor argument, allowing you to define completely custom PHP code generation logic on the fly. +- `getIterator()` Delegation: The second constructor argument is an **array of `Node` objects**. When Latte needs to traverse the children of an `AuxiliaryNode` (e.g., during compiler passes), its `getIterator()` method simply yields the nodes provided in this array. + +Example: + +```php +$node = new AuxiliaryNode( + // 1. This closure becomes the body of print() + fn(PrintContext $context, $arg1, $arg2) => $context->format('...%node...%node...', $arg1, $arg2), + + // 2. These nodes are yielded by getIterator() and passed to the closure above + [$argumentNode1, $argumentNode2] +); +``` + +Latte provides two distinct types based on where you need to insert the generated code: + +- `Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode`: Use this when you need to generate a piece of PHP code that represents an **expression** +- `Latte\Compiler\Nodes\AuxiliaryNode`: Use this for more general purposes when you need to insert a block of PHP code representing one or more **statements** + +The important reason to use `AuxiliaryNode` instead of standard nodes (like `StaticMethodCallNode`) within your `print()` method or a compiler pass is to **control visibility for subsequent compiler passes**, especially security-related ones like the Sandbox. + +Consider a scenario: Your compiler pass needs to wrap a user-provided expression (`$userExpr`) with a call to a specific, trusted helper function `myInternalSanitize($userExpr)`. If you create a standard node `new FunctionCallNode('myInternalSanitize', [$userExpr])`, it will be fully visible to the AST traverser. If a Sandbox pass runs later and `myInternalSanitize` is *not* on its allowlist, the Sandbox might *block* or modify this call, potentially breaking your tag's internal logic, even though *you*, the tag author, know this specific call is safe and necessary. So you can generate the call directly within the `AuxiliaryNode`'s closure. + +```php +use Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode; + +// ... inside print() or a compiler pass ... +$wrappedNode = new AuxiliaryNode( + fn(PrintContext $context, $userExpr) => $context->format( + 'myInternalSanitize(%node)', // Direct PHP code generation + $userExpr, + ), + // IMPORTANT: Still pass the original user expression node here! + [$userExpr], +); +``` + +In this case, the Sandbox pass sees the `AuxiliaryNode` but **does not analyze the PHP code generated by its closure**. It cannot directly block the `myInternalSanitize` call generated *inside* the closure. + +While the generated PHP code itself is hidden from passes, the *inputs* to that code (nodes representing user data or expressions) **must still be made traversable**. This is why the second argument to the `AuxiliaryNode` constructor is crucial. You **must** pass an array containing all the original nodes (like `$userExpr` in the example above) that your closure uses. `AuxiliaryNode`'s `getIterator()` **will yield these nodes**, allowing compiler passes like the Sandbox to analyze them for potential issues. + + +Best Practices +============== + +- **Clear Purpose:** Ensure your tag has a clear and necessary purpose. Don't create tags for tasks easily solvable by [filters|custom-filters] or [functions|custom-functions]. +- **Implement `getIterator()` Correctly:** Always implement `getIterator()` and yield *references* (`&`) to *all* child nodes (arguments, content) that were parsed from the template. This is essential for compiler passes, security (Sandbox), and potential future optimizations. +- **Public Properties for Nodes:** Make properties holding child nodes public so that compiler passes can potentially modify them if needed. +- **Use `PrintContext::format()`:** Utilize the `format()` method for generating PHP code. It handles quoting, escaping placeholders correctly, and adds line number comments automatically. +- **Temporary Variables (`$__`):** When generating runtime PHP code that needs temporary variables (e.g., to store intermediate results, loop counters), use the `$__` prefix convention to avoid collisions with user variables and Latte's internal `$ʟ_` variables. +- **Nesting and Unique IDs:** If your tag can be nested or needs instance-specific state at runtime, use `$context->generateId()` within your `print()` method to create unique suffixes for your `$__` temporary variables. +- **Providers for External Data:** Use Providers (registered via `Extension::getProviders()`) to access runtime data or services ($this->global->...) instead of hardcoding values or relying on global state. Use vendor prefixes for provider names. +- **Consider n:attributes:** If your paired tag operates logically on a single HTML element, Latte likely provides automatic `n:attribute` support. Keep this in mind for user convenience. If creating an attribute-modifying tag, consider if a pure `n:attribute` is the most appropriate form. +- **Testing:** Write tests for your tags, covering both parsing various syntax inputs and the correctness of the generated PHP code's output. + +By following these guidelines, you can create powerful, robust, and maintainable custom tags that seamlessly integrate with the Latte templating engine. + +.[note] +Studying the node classes that are part of Latte is the best way to learn all the nitty-gritty details of the parsing process. diff --git a/latte/en/develop.texy b/latte/en/develop.texy index 0393acde7e..37871079b3 100644 --- a/latte/en/develop.texy +++ b/latte/en/develop.texy @@ -15,9 +15,8 @@ Supported PHP versions (applies to the latest patch Latte versions): | version | compatible with PHP |-----------------|------------------- -| Latte 3.0 | PHP 8.0 – 8.2 -| Latte 2.11 | PHP 7.1 – 8.2 -| Latte 2.8 – 2.10| PHP 7.1 – 8.1 +| Latte 3.1 | PHP 8.2 – 8.5 +| Latte 3.0 | PHP 8.0 – 8.5 How to Render a Template @@ -28,7 +27,7 @@ How to render a template? Just use this simple code: ```php $latte = new Latte\Engine; // cache directory -$latte->setTempDirectory('/path/to/tempdir'); +$latte->setCacheDirectory('/path/to/tempdir'); $params = [ /* template variables */ ]; // or $params = new TemplateParameters(/* ... */); @@ -39,7 +38,7 @@ $latte->render('template.latte', $params); $output = $latte->renderToString('template.latte', $params); ``` -Parameters can be arrays or even better [object|#Parameters as a class], which will provide type checking and suggestion in the editor. +Parameters can be arrays or even better [object |#Parameters as a Class], which will provide type checking and suggestion in the editor. .[note] You can also find usage examples in the repository [Latte examples |https://github.com/nette-examples/latte]. @@ -56,15 +55,13 @@ The cache is automatically regenerated every time you change the source file. So $latte->setAutoRefresh(false); ``` -When deployed on a production server, the initial cache generation, especially for larger applications, can understandably take a while. Latte has built-in prevention against "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. -This is a situation where server receives a large number of concurrent requests and because Latte's cache does not yet exist, they would all generate it at the same time. Which spikes CPU. -Latte is smart, and when there are multiple concurrent requests, only the first thread generates the cache, the others wait and then use it. +When deployed on a production server, the initial cache generation, especially for larger applications, can understandably take a while. Latte has built-in prevention against "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. This is a situation where server receives a large number of concurrent requests and because Latte's cache does not yet exist, they would all generate it at the same time. Which spikes CPU. Latte is smart, and when there are multiple concurrent requests, only the first thread generates the cache, the others wait and then use it. Parameters as a Class ===================== -Better than passing variables to the template as arrays is to create a class. You get [type-safe notation|type-system], [nice suggestion in IDE|recipes#Editors and IDE] and a way to [register filters|extending-latte#Filters Using the Class] and [functions|extending-latte#Functions Using the Class]. +Better than passing variables to the template as arrays is to create a class. You get [type-safe notation|type-system], [nice suggestion in IDE |recipes#Editors and IDE] and a way to [register filters |custom-filters#Filters Using the Class] and [functions |custom-functions#Functions Using the Class]. ```php class MailTemplateParameters @@ -129,26 +126,129 @@ The `__toString` method must return correct HTML and provide parameter escaping, How to Extend Latte with Filters, Tags, etc. ============================================ -How to add a custom filter, function, tag, etc. to Latte? Find out in the chapter [extending Latte]. -If you want to reuse your changes in different projects or if you want to share them with others, you should then [create an extension |creating-extension]. +How to add a custom filter, function, tag, etc. to Latte? Find out in the chapter [extending Latte]. If you want to reuse your changes in different projects or if you want to share them with others, you should then [create an extension |extending-latte#Latte Extension]. -Any Code in Template `{php ...}` .{data-version:3.0}{toc: RawPhpExtension} -========================================================================== +Any Code in Template `{php ...}` .{toc: RawPhpExtension} +======================================================== -Only PHP expressions can be written inside the [`{do}`|tags#do] tag, so you can't, for example, insert constructs like `if ... else` or semicolon-terminated statements. +Only PHP expressions can be written inside the [`{do}` |tags#do] tag, so you can't, for example, insert constructs like `if ... else` or semicolon-terminated statements. -However, you can register the `RawPhpExtension` extension, which adds the `{php ...}` tag, which can be used to insert any PHP code at the template author's risk. +However, you can register the `RawPhpExtension` extension, which adds the `{php ...}` tag. You can use this to insert any PHP code. It is not subject to any sandbox mode rules, so use is the responsibility of the template author. ```php $latte->addExtension(new Latte\Essential\RawPhpExtension); ``` -Translation in Templates .{data-version:3.0}{toc: TranslatorExtension} -====================================================================== +Checking Generated Code .{data-version:3.0.7} +============================================= -Use the `TranslatorExtension` extension to add [`{_...}`|tags#_], [`{translate}`|tags#translate] and filter [`translate`|filters#translate] to the template. They are used to translate values or parts of the template into other languages. The parameter is the method (PHP callable) that performs the translation: +Latte compiles templates into PHP code. Of course, it ensures that the generated code is syntactically valid. However, when using third-party extensions or `RawPhpExtension`, Latte cannot guarantee the correctness of the generated file. Also, in PHP, you can write code that is syntactically correct but is forbidden (for example, assigning a value to the `$this` variable) and causes a PHP Compile Error. If you write such an operation in a template, it will also be included in the generated PHP code. Since there are over two hundred different forbidden operations in PHP, Latte does not aim to detect them. PHP itself will flag them upon rendering, which usually isn't a problem. + +However, there are situations where you want to know during the template compilation that it contains no PHP Compile Errors. Especially when templates can be edited by users, or you use [Sandbox |sandbox]. In such a case, have the templates checked during compilation. You can activate this functionality using the `Engine::enablePhpLint()` method. Since it needs to call the PHP binary for the check, pass its path as a parameter: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); + +try { + $latte->compile('home.latte'); +} catch (Latte\CompileException $e) { + // catches Latte errors and also Compile Error in PHP + echo 'Error: ' . $e->getMessage(); +} +``` + + +Locale .{data-version:3.0.18} +============================= + +Latte allows you to set the locale, which affects the formatting of numbers, dates, and sorting. It is set using the `setLocale()` method. The locale identifier follows the IETF language tag standard, which uses the PHP `intl` extension. It consists of a language code and possibly a country code, for example, `en_US` for English in the United States, `de_DE` for German in Germany, etc. + +```php +$latte = new Latte\Engine; +$latte->setLocale('en_US'); +``` + +The locale setting affects the filters [localDate |filters#localDate], [sort |filters#sort], [number |filters#number], and [bytes |filters#bytes]. + +.[note] +Requires the PHP `intl` extension. The setting in Latte does not affect the global locale setting in PHP. + + +Strict Mode .{data-version:3.0.8} +================================= + +In strict parsing mode, Latte checks for missing closing HTML tags and also disables the use of the `$this` variable. To turn it on: + +```php +$latte = new Latte\Engine; +$latte->setFeature(Latte\Feature::StrictParsing); +``` + +To generate templates with the `declare(strict_types=1)` header, do the following: + +```php +$latte = new Latte\Engine; +$latte->setFeature(Latte\Feature::StrictTypes); +``` + +.[note] +Since Latte 3.1, strict types are enabled by default. You can disable them with `$latte->setFeature(Latte\Feature::StrictTypes, false)`. + + +Migration Warnings .{data-version:3.1} +====================================== + +Latte 3.1 changes the behavior of some [HTML attributes|html-attributes]. For example, `null` values now drop the attribute instead of printing an empty string. To easily find places where this change affects your templates, you can enable migration warnings: + +```php +$latte->setFeature(Latte\Feature::MigrationWarnings); +``` + +When enabled, Latte checks rendered attributes and triggers a user warning (`E_USER_WARNING`) if the output differs from what Latte 3.0 would have produced. When you encounter a warning, apply one of the solutions: + +1. If the new output is correct for your use case (e.g., you prefer the attribute to disappear when `null`), suppress the warning by adding the `|accept` filter +2. If you want the attribute to be rendered as empty (e.g. `title=""`) instead of being dropped when the variable is `null`, provide an empty string as a fallback: `title={$val ?? ''}` +3. If you strictly require the old behavior (e.g., printing `"1"` for `true` instead of `"true"`), explicitly cast the value to a string: `data-foo={(string) $val}` + +Once all warnings are resolved, disable migration warnings and **remove all** `|accept` filters from your templates, as they are no longer needed. + + +Scoped Loop Variables .{data-version:3.1.2} +=========================================== + +By default, variables defined in `{foreach}` loops (like `$key` and `$value`) remain accessible after the loop ends, which mirrors PHP's native behavior. This can lead to unexpected variable overwrites when a loop variable has the same name as an existing template variable. + +With the `ScopedLoopVariables` feature, loop variables exist only within the loop. After the loop ends, the original variable value is restored (if it existed before), or the variable is unset: + +```php +$latte = new Latte\Engine; +$latte->setFeature(Latte\Feature::ScopedLoopVariables); +``` + +Example of the difference: + +```latte +{var $item = 'original'} +{foreach [1, 2] as $item}{$item}, {/foreach} +{$item} +``` + +Without `ScopedLoopVariables`: outputs `1, 2, 2` (variable is overwritten) +With `ScopedLoopVariables`: outputs `1, 2, original` (variable is restored) + +This also works with destructuring syntax like `{foreach $array as [$a, $b]}`. + +.[note] +Loop variables using references (`{foreach $array as &$value}`) or property assignments (`{foreach $array as $obj->prop}`) are not scoped, as this would break their intended functionality. + + +Translation in Templates .{toc: TranslatorExtension} +==================================================== + +Use the `TranslatorExtension` extension to add [`{_...}` |tags#], [`{translate}` |tags#translate] and filter [`translate` |filters#translate] to the template. They are used to translate values or parts of the template into other languages. The parameter is the method (PHP callable) that performs the translation: ```php class MyTranslator @@ -194,7 +294,7 @@ public function translate(string $original, ...$params): string Debugging and Tracy =================== -Latte tries to make the development as pleasant as possible. For debugging purposes, there are three tags [`{dump}`|tags#dump], [`{debugbreak}`|tags#debugbreak] and [`{trace}`|tags#trace]. +Latte tries to make the development as pleasant as possible. For debugging purposes, there are three tags [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak] and [`{trace}` |tags#trace]. You'll get the most comfort if you install the great [debugging tool Tracy|tracy:] and activate the Latte plugin: @@ -207,24 +307,27 @@ $latte = new Latte\Engine; $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension); ``` -You will now see all errors in a neat red screen, including errors in templates with row and column highlighting ([video|https://github.com/nette/tracy/releases/tag/v2.9.0]). -At the same time, in the bottom right corner in the so-called Tracy Bar, a tab for Latte appears, where you can clearly see all rendered templates and their relationships (including the possibility to click into the template or compiled code), as well as variables: +You will now see all errors in a neat red screen, including errors in templates with row and column highlighting ([video|https://github.com/nette/tracy/releases/tag/v2.9.0]). At the same time, in the bottom right corner in the so-called Tracy Bar, a tab for Latte appears, where you can clearly see all rendered templates and their relationships (including the possibility to click into the template or compiled code), as well as variables: [* latte-debugging.webp *] Since Latte compiles templates into readable PHP code, you can conveniently step through them in your IDE. -Linter: Validating the Template Syntax .{data-version:2.11}{toc: Linter} -======================================================================== +Linter: Validating the Template Syntax .{toc: Linter} +===================================================== -The Linter tool will help you go through all templates and check for syntax errors. It is launched from the console: +The **Linter** tool is used to validate all templates. Its purpose is to scan the specified files and ensure that they contain no syntax errors and no references to non-existent tags, filters, functions, classes, or similar constructs. + +The Linter is executed from the command line: ```shell vendor/bin/latte-lint ``` -If you use custom tags, also create your customized Linter, e.g. `custom-latte-lint`: +Use the `--strict` parameter to activate [#strict mode]. + +If you use custom tags, filters, or other Latte extensions, you need to create your own variant of the Linter, for example `custom-latte-lint`. In this script, you register all required extensions before the actual template validation takes place: ```php #!/usr/bin/env php @@ -233,24 +336,32 @@ If you use custom tags, also create your customized Linter, e.g. `custom-latte-l // enter the actual path to the autoload.php file require __DIR__ . '/vendor/autoload.php'; -$linter = new Latte\Tools\Linter($engine); -$linter->scanDirectory($path); +$path = $argv[1] ?? '.'; -$engine = new Latte\Engine; -// registers individual extensions here -$engine->addExtension(/* ... */); +$linter = new Latte\Tools\Linter; +$latte = $linter->getEngine(); +// add your individual extensions here +$latte->addExtension(/* ... */); -$path = $argv[1]; -$linter = new Latte\Tools\Linter(engine: $engine); $ok = $linter->scanDirectory($path); exit($ok ? 0 : 1); ``` +Alternatively, you can pass your own `Latte\Engine` object to the Linter: + +```php +$latte = new Latte\Engine; +// here we configure the $latte object +$linter = new Latte\Tools\Linter(engine: $latte); +``` + +The resulting customized linter can then be used in the same way as the standard tool, but with full knowledge of all your custom extensions. + Loading Templates from a String =============================== -Need to load templates from strings instead of files, perhaps for testing purposes? [StringLoader|extending-latte#stringloader] will help you: +Need to load templates from strings instead of files, perhaps for testing purposes? [StringLoader |loaders#StringLoader] will help you: ```php $latte->setLoader(new Latte\Loaders\StringLoader([ @@ -265,7 +376,7 @@ $latte->render('main.file', $params); Exception Handler ================= -You can define your own handler for expected exceptions. Exceptions raised inside [`{try}`|tags#try] and in the [sandbox] are passed to it. +You can define your own handler for expected exceptions. Exceptions raised inside [`{try}` |tags#try] and in the [sandbox] are passed to it. ```php $loggingHandler = function (Throwable $e, Latte\Runtime\Template $template) use ($logger) { @@ -280,7 +391,7 @@ $latte->setExceptionHandler($loggingHandler); Automatic Layout Lookup ======================= -Using the tag [`{layout}`|template-inheritance#layout-inheritance], the template determines its parent template. It's also possible to have the layout searched automatically, which will simplify writing templates since they won't need to include the `{layout}` tag. +Using the tag [`{layout}` |template-inheritance#Layout Inheritance], the template determines its parent template. It's also possible to have the layout searched automatically, which will simplify writing templates since they won't need to include the `{layout}` tag. This is achieved as follows: diff --git a/latte/en/extending-latte.texy b/latte/en/extending-latte.texy index 6a853c7303..22499f514a 100644 --- a/latte/en/extending-latte.texy +++ b/latte/en/extending-latte.texy @@ -2,284 +2,219 @@ Extending Latte *************** .[perex] -Latte is very flexible and can be extended in many ways: you can add custom filters, functions, tags, loaders, etc. We will show you how to do it. +Latte is designed with extensibility in mind. While its standard set of tags, filters, and functions covers many use cases, you often need to add your own specific logic or helpers. This page provides an overview of how you can extend Latte to perfectly fit your project's requirements, from simple helpers to complex new syntax. -This chapter describes the different ways to extend Latte. If you want to reuse your changes in different projects or if you want to share them with others, you should then [create so-called extension |creating-extension]. +Ways to Extend Latte +==================== -How Many Roads Lead to Rome? -============================ +Here's a quick overview of the main ways you can customize and extend Latte: -Since some of the ways of extending Latte can be blended, let's first try to explain the differences between them. As an example, let's try to implement a *Lorem ipsum* generator, which is passed the number of words to generate. +- **[Custom Filters]:** For formatting or transforming data directly in the template output (e.g., `{$var|myFilter}`). Ideal for tasks like date formatting, text manipulation, or applying specific escaping. You can also use them to modify larger blocks of HTML content by wrapping the content in an anonymous [`{block}` |tags#block] and applying a custom filter. +- **[Custom Functions]:** For adding reusable logic that can be called within template expressions (e.g., `{myFunction($arg1, $arg2)}`). Useful for calculations, accessing application helpers, or generating small pieces of content. +- **[Custom Tags]:** For creating entirely new language constructs (`{mytag}...{/mytag}` or `n:mytag`). Tags offer the most power, allowing you to define custom structures, control template parsing, and implement complex rendering logic. +- **[Compiler Passes]:** Functions that modify the template's Abstract Syntax Tree (AST) after parsing but before PHP code generation. Used for advanced optimizations, security checks (like the Sandbox), or automatic code modifications. +- **[Custom Loaders|loaders]:** For changing how Latte finds and loads template files (e.g., loading from a database, encrypted storage, etc.). -The main Latte language construct is the tag. We can implement a generator by extending Latte with a new tag: +Choosing the right extension method is key. Before creating a complex tag, consider if a simpler filter or function would suffice. Let's illustrate with an example: implementing a *Lorem ipsum* generator that takes the number of words to generate as an argument. -```latte -{lipsum 40} -``` - -The tag will work great. However, the generator in the form of a tag may not be flexible enough because it cannot be used in an expression. By the way, in practice, you rarely need to generate tags; and that's good news, because tags are a more complicated way to extend. - -Okay, let's try creating a filter instead of a tag: - -```latte -{=40|lipsum} -``` - -Again, a valid option. But the filter should transform the passed value into something else. Here we use the value `40`, which indicates the number of words generated, as the filter argument, not as the value we want to transform. +- **As a tag?** `{lipsum 40}` - Possible, but tags are better suited for control structures or complex markup generation. Tags cannot be used directly within expressions. +- **As a filter?** `{=40|lipsum}` - Technically works, but filters are meant to *transform* input. Here, `40` is an *argument*, not the value being transformed. It feels semantically incorrect. +- **As a function?** `{lipsum(40)}` - This is the most natural fit! Functions accept arguments and return values, making them perfect for use within any expression: `{var $text = lipsum(40)}`. -So let's try using function: +**General Guidance:** Use functions for calculations/generation, filters for transformation, and tags for new language structures or complex markup. Use passes for AST manipulation and loaders for template retrieval. -```latte -{lipsum(40)} -``` -That's it! For this particular example, creating a function is the ideal extension point to use. You can call it anywhere where an expression is accepted, for example: +Direct Registration +=================== -```latte -{var $text = lipsum(40)} -``` +For project-specific helpers or quick additions, Latte allows direct registration of filters and functions onto the `Latte\Engine` object. - -Filters -======= - -Create a filter by registering its name and any PHP callable, such as a function: +Use `addFilter()` to register a filter. The first argument to your filter function will be the value before the `|` pipe, and subsequent arguments are those passed after the `:` colon. ```php $latte = new Latte\Engine; -$latte->addFilter('shortify', fn(string $s) => mb_substr($s, 0, 10)); // shortens the text to 10 characters -``` -In this case it would be better for the filter to get an additional parameter: +// Filter definition (callable: function, static method, etc.) +$myTruncate = fn(string $s, int $length = 50) => mb_substr($s, 0, $length); -```php -$latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); -``` - -We use it in a template like this: +// Register it +$latte->addFilter('truncate', $myTruncate); -```latte -

                                                  {$text|shortify}

                                                  -

                                                  {$text|shortify:100}

                                                  +// Template usage: {$text|truncate} or {$text|truncate:100} ``` -As you can see, the function receives the left side of the filter before the pipe `|` as the first argument and the arguments passed to the filter after `:` as the next arguments. - -Of course, the function representing the filter can accept any number of parameters, and variadic parameters are also supported. - - -Filters Using the Class ------------------------ - -The second way to define a filter is to [use class|develop#Parameters as a class]. We create a method with the `TemplateFilter` attribute: +Use `addFunction()` to register a function usable within template expressions. ```php -class TemplateParameters -{ - public function __construct( - // parameters - ) {} - - #[Latte\Attributes\TemplateFilter] - public function shortify(string $s, int $len = 10): string - { - return mb_substr($s, 0, $len); - } -} - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); -``` - -If you are using PHP 7.x and Latte 2.x, use the `/** @filter */` annotation instead of the attribute. - +$latte = new Latte\Engine; -Filter Loader .{data-version:2.10} ----------------------------------- +// Function definition +$isWeekend = fn(DateTimeInterface $date) => $date->format('N') >= 6; -Instead of registering individual filters, you can create a so-called loader, which is a function that is called with the filter name as an argument and returns its PHP callable, or null. +// Register it +$latte->addFunction('isWeekend', $isWeekend); -```php -$latte->addFilterLoader([new Filters, 'load']); +// Template usage: {if isWeekend($myDate)}Weekend!{/if} +``` +For more details, see [Creating Custom Filters|custom-filters] and [Functions|custom-functions]. -class Filters -{ - public function load(string $filter): ?callable - { - if (in_array($filter, get_class_methods($this))) { - return [$this, $filter]; - } - return null; - } - - public function shortify($s, $len = 10) - { - return mb_substr($s, 0, $len); - } - - // ... -} -``` +The Robust Way: Latte Extension .{toc: Latte Extension} +======================================================= -Contextual Filters ------------------- +While direct registration is simple, the standard and recommended way to bundle and distribute Latte customizations is through **Extension classes**. An Extension acts as a central configuration point for registering multiple tags, filters, functions, compiler passes, and more. -A contextual filter is one that accepts an object [api:Latte\Runtime\FilterInfo] in the first parameter, followed by other parameters as in the case of classical filters. It is registered in the same way, Latte itself recognizes that the filter is contextual: +Why use Extensions? -```php -use Latte\Runtime\FilterInfo; +- **Organization:** Keeps related customizations (tags, filters, etc. for a specific feature) together in one class. +- **Reusability & Sharing:** Easily package your extensions for use in other projects or for sharing with the community (e.g., via Composer). +- **Full Power:** Custom tags and compiler passes *can only* be registered via Extensions. -$latte->addFilter('foo', function (FilterInfo $info, string $str): string { - // ... -}); -``` -Context filters can detect and change the content-type they receive in the `$info->contentType` variable. If the filter is called classically over a variable (e.g. `{$var|foo}`), the `$info->contentType` will contain null. +Registering an Extension +------------------------ -The filter should first check if the content-type of the input string is supported. It can also change it. Example of a filter that accepts text (or null) and returns HTML: +Extension is registered with Latte using `addExtension()` (or via [configuration file |application:configuration#Latte Templates]): ```php -use Latte\Runtime\FilterInfo; - -$latte->addFilter('money', function (FilterInfo $info, float $amount): string { - // first we check if the input's content-type is text - if (!in_array($info->contentType, [null, ContentType::Text])) { - throw new Exception("Filter |money used in incompatible content type $info->contentType."); - } - - // change content-type to HTML - $info->contentType = ContentType::Html; - return "$num Kč"; -}); +$latte = new Latte\Engine; +$latte->addExtension(new MyProjectExtension); ``` -.[note] -In this case, the filter must ensure correct escaping of the data. +If you register multiple extensions and they define identically named tags, filters, or functions, the last added extension wins. This also implies that your extensions can override native tags/filters/functions. -All filters that are used over [blocks|tags#block] (e.g. as `{block|foo}...{/block}`) must be contextual. +Whenever you make a change to a class and auto-refresh is not turned off, Latte will automatically recompile your templates. -Functions .{data-version:2.6} -============================= +Creating an Extension +--------------------- -By default, all native PHP functions can be used in Latte, unless the sandbox disables it. But you can also define your own functions. They can override the native functions. +To create your own extension, you need to create a class that inherits from [api:Latte\Extension]. For an idea of what the extension looks like, take a look at the built-in [CoreExtension |https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php]. -Create a function by registering its name and any PHP callable: +Let's look at what methods you can implement: -```php -$latte = new Latte\Engine; -$latte->addFunction('random', function (...$args) { - return $args[array_rand($args)]; -}); -``` -The usage is then the same as when calling the PHP function: +beforeCompile(Latte\Engine $engine): void .[method] +--------------------------------------------------- -```latte -{random(apple, orange, lemon)} // prints for example: apple -``` +Called before the template is compiled. The method can be used for compilation-related initializations, for example. -Functions Using the Class -------------------------- +getTags(): array .[method] +-------------------------- -The second way to define a function is to [use class|develop#Parameters as a class]. We create a method with the `TemplateFunction` attribute: +Called when the template is compiled. Returns an associative array *tag name => callable*, which are tag parsing functions. [Learn more|custom-tags]. ```php -class TemplateParameters +public function getTags(): array { - public function __construct( - // parameters - ) {} - - #[Latte\Attributes\TemplateFunction] - public function random(...$args) - { - return $args[array_rand($args)]; - } + return [ + 'foo' => FooNode::create(...), + 'bar' => BarNode::create(...), + 'n:baz' => NBazNode::create(...), + // ... + ]; } - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); ``` -If you are using PHP 7.x and Latte 2.x, use the `/** @function */` annotation instead of the attribute. +The `n:baz` tag represents a pure [n:attribute |syntax#n:attributes], i.e. it is a tag that can only be written as an attribute. +In the case of the `foo` and `bar` tags, Latte will automatically recognize whether they are pairs, and if so, they can be written automatically using n:attributes, including variants with the `n:inner-foo` and `n:tag-foo` prefixes. -Loaders -======= +The order of execution of such n:attributes is determined by their order in the array returned by `getTags()`. Thus, `n:foo` is always executed before `n:bar`, even if the attributes are listed in reverse order in the HTML tag as `
                                                  `. -Loaders are responsible for loading templates from a source, such as a file system. They are set using the `setLoader()` method: +If you need to determine the order of n:attributes across multiple extensions, use the `order()` helper method, where the `before` xor `after` parameter determines which tags are ordered before or after the tag. ```php -$latte->setLoader(new MyLoader); +public function getTags(): array +{ + return [ + 'foo' => self::order(FooNode::create(...), before: 'bar'), + 'bar' => self::order(BarNode::create(...), after: ['block', 'snippet']), + ]; +} ``` -The built-in loaders are: +getPasses(): array .[method] +---------------------------- -FileLoader ----------- +It is called when the template is compiled. Returns an associative array *name pass => callable*, which are functions representing so-called [compiler passes|compiler-passes] that traverse and modify the AST. -Default loader. Loads templates from the filesystem. - -Access to files can be restricted by setting the base directory: +Again, the `order()` helper method can be used. The value of the `before` or `after` parameters can be `*` with the meaning before/after all. ```php -$latte->setLoader(new Latte\Loaders\FileLoader($templateDir)); -$latte->render('test.latte'); +public function getPasses(): array +{ + return [ + 'optimize' => Passes::optimizePass(...), + 'sandbox' => self::order($this->sandboxPass(...), before: '*'), + // ... + ]; +} ``` -StringLoader ------------- +beforeRender(Latte\Engine $engine): void .[method] +-------------------------------------------------- -Loads templates from strings. This loader is very useful for unit testing. It can also be used for small projects where it may make sense to store all templates in a single PHP file. +It is called before each template rendering. The method can be used, for example, to initialize variables used during rendering. -```php -$latte->setLoader(new Latte\Loaders\StringLoader([ - 'main.file' => '{include other.file}', - 'other.file' => '{if true} {$var} {/if}', -])); -$latte->render('main.file'); -``` +getFilters(): array .[method] +----------------------------- -Simplified use: +It is called before the template is rendered. Returns filters as an associative array *filter name => callable*. [Learn more|custom-filters]. ```php -$template = '{if true} {$var} {/if}'; -$latte->setLoader(new Latte\Loaders\StringLoader); -$latte->render($template); +public function getFilters(): array +{ + return [ + 'batch' => $this->batchFilter(...), + 'trim' => $this->trimFilter(...), + // ... + ]; +} ``` -Creating a Custom Loader ------------------------- - -Loader is a class that implements the [api:Latte\Loader] interface. +getFunctions(): array .[method] +------------------------------- +It is called before the template is rendered. Returns functions as an associative array *function name => callable*. [Learn more|custom-functions]. -Tags -==== +```php +public function getFunctions(): array +{ + return [ + 'clamp' => $this->clampFunction(...), + 'divisibleBy' => $this->divisibleByFunction(...), + // ... + ]; +} +``` -One of the most interesting features of the templating engine is the ability to define new language constructs using tags. It's also a more complex functionality and you need to understand how Latte internally works. -In most cases, however, the tag is not needed: -- if it should generate some output, use [function|#functions] instead -- if it was to modify some input and return it, use [filter|#filters] instead -- if it was to edit a area of text, wrap it with a [`{block}`|tags#block] tag and use a [filter|#Contextual Filters] -- if it was not supposed to output anything but just call a function, call it with [`{do}`|tags#do] +getProviders(): array .[method] +------------------------------- -If you still want to create a tag, great! All the essentials can be found in [Creating an Extension|creating-extension]. +It is called before the template is rendered. Returns an array of providers, which are usually objects that use tags at runtime. They are accessed via `$this->global->...`. [Learn more |custom-tags#Introducing Providers]. +```php +public function getProviders(): array +{ + return [ + 'myFoo' => $this->foo, + 'myBar' => $this->bar, + // ... + ]; +} +``` -Compiler Passes .{data-version:3.0} -=================================== -Compiler passes are functions that modify ASTs or collect information in them. In Latte, for example, a sandbox is implemented in this way: it traverses all the nodes of an AST, finds function and method calls, and replaces them with controlled calls. +getCacheKey(Latte\Engine $engine): mixed .[method] +-------------------------------------------------- -As with tags, this is more complex functionality and you need to understand how Latte works under the hood. All the essentials can be found in the [Creating an Extension|creating-extension] chapter. +It is called before the template is rendered. The return value becomes part of the key whose hash is contained in the name of the compiled template file. Thus, for different return values, Latte will generate different cache files. diff --git a/latte/en/filters.texy b/latte/en/filters.texy index 61717e2b4c..83a39c1d69 100644 --- a/latte/en/filters.texy +++ b/latte/en/filters.texy @@ -2,111 +2,121 @@ Latte Filters ************* .[perex] -Filters are functions that change or format the data to a form we want. This is summary of the built-in filters which are available. +In templates, we can use functions that help modify or reformat data into its final form. We call them *filters*. .[table-latte-filters] -|## String / array transformation +|## Transformation | `batch` | [listing linear data in a table |#batch] | `breakLines` | [Inserts HTML line breaks before all newlines |#breakLines] | `bytes` | [formats size in bytes |#bytes] -| `clamp` | [clamps value to the range |#clamp] -| `dataStream` | [Data URI protocol conversion |#datastream] -| `date` | [formats date |#date] -| `explode` | [splits a string by the given delimiter |#explode] -| `first` | [returns first element of array or character of string |#first] -| `implode` | [joins an array to a string |#implode] -| `indent` | [indents the text from left with number of tabs |#indent] -| `join` | [joins an array to a string |#implode] -| `last` | [returns last element of array or character of string |#last] -| `length` | [returns length of a string or array |#length] -| `number` | [formats number |#number] -| `padLeft` | [completes the string to given length from left |#padLeft] -| `padRight` | [completes the string to given length from right |#padRight] -| `random` | [returns random element of array or character of string |#random] -| `repeat` | [repeats the string |#repeat] -| `replace` | [replaces all occurrences of the search string with the replacement |#replace] -| `replaceRE` | [replaces all occurrences according to regular expression |#replaceRE] -| `reverse` | [reverses an UTF‑8 string or array |#reverse] +| `clamp` | [clamps a value to the given range |#clamp] +| `column` | [extracts a single column from an array |#column] +| `commas` | [joins an array with commas |#commas] +| `dataStream` | [Data URI protocol conversion |#dataStream] +| `date` | [formats the date and time |#date] +| `explode` | [splits a string into an array by a delimiter |#explode] +| `first` | [returns the first element of an array or character of a string |#first] +| `group` | [groups data according to various criteria |#group] +| `implode` | [joins an array into a string |#implode] +| `indent` | [indents the text from the left by a given number of tabs |#indent] +| `join` | [joins an array into a string |#implode] +| `last` | [returns the last element of an array or character of a string |#last] +| `length` | [returns the length of a string or array |#length] +| `localDate` | [formats the date and time according to the locale |#localDate] +| `number` | [formats a number |#number] +| `padLeft` | [pads a string to a certain length from the left |#padLeft] +| `padRight` | [pads a string to a certain length from the right |#padRight] +| `random` | [returns a random element of an array or character of a string |#random] +| `repeat` | [repeats a string |#repeat] +| `replace` | [replaces occurrences of the search string |#replace] +| `replaceRE` | [replaces occurrences based on a regular expression |#replaceRE] +| `reverse` | [reverses a UTF‑8 string or array |#reverse] | `slice` | [extracts a slice of an array or a string |#slice] | `sort` | [sorts an array |#sort] -| `spaceless` | [removes whitespace |#spaceless], similar to [spaceless |tags] tag -| `split` | [splits a string by the given delimiter |#explode] +| `spaceless` | [removes whitespace |#spaceless], similar to the [spaceless |tags] tag +| `split` | [splits a string into an array by a delimiter |#explode] | `strip` | [removes whitespace |#spaceless] -| `stripHtml` | [removes HTML tags and converts HTML entities to text |#stripHtml] -| `substr` | [returns part of the string |#substr] -| `trim` | [strips whitespace from the string |#trim] +| `stripHtml` | [removes HTML tags and converts HTML entities to characters |#stripHtml] +| `substr` | [returns part of a string |#substr] +| `trim` | [strips leading and trailing whitespace or other characters |#trim] | `translate` | [translation into other languages |#translate] | `truncate` | [shortens the length preserving whole words |#truncate] -| `webalize` | [adjusts the UTF‑8 string to the shape used in the URL |#webalize] +| `webalize` | [adjusts a UTF‑8 string to the format used in URLs |#webalize] .[table-latte-filters] -|## Letter casing -| `capitalize` | [lower case, the first letter of each word upper case |#capitalize] -| `firstUpper` | [makes the first letter upper case |#firstUpper] -| `lower` | [makes a string lower case |#lower] -| `upper` | [makes a string upper case |#upper] +|## Letter Casing +| `capitalize` | [lowercase, first letter of each word uppercase |#capitalize] +| `firstLower` | [converts the first letter to lower case |#firstLower] +| `firstUpper` | [converts the first letter to uppercase |#firstUpper] +| `lower` | [converts to lowercase |#lower] +| `upper` | [converts to uppercase |#upper] .[table-latte-filters] -|## Rounding numbers +|## Rounding | `ceil` | [rounds a number up to a given precision |#ceil] | `floor` | [rounds a number down to a given precision |#floor] | `round` | [rounds a number to a given precision |#round] +.[table-latte-filters] +|## HTML Attributes +| `accept` | [accepts the new behavior of smart attributes |#accept] +| `toggle` | [toggles the presence of an HTML attribute |#toggle] + .[table-latte-filters] |## Escaping -| `escapeUrl` | [escapes parameter in URL |#escapeUrl] -| `noescape` | [prints a variable without escaping |#noescape] -| `query` | [generates a query string in the URL |#query] +| `escapeUrl` | [escapes a parameter in a URL |#escapeUrl] +| `noescape` | [outputs a variable without escaping |#noescape] +| `query` | [generates a query string in a URL |#query] -There are also escaping filters for HTML (`escapeHtml` and `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) and iCalendar (`escapeICal`), which Latte uses itself thanks to [context-aware escaping |safety-first#Context-aware escaping] and you do not need to write them. +There are also escaping filters for HTML (`escapeHtml` and `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) and iCalendar (`escapeICal`), which Latte uses itself thanks to [context-aware escaping |safety-first#Context-Aware Escaping] and you do not need to write them. .[table-latte-filters] |## Security -| `checkUrl` | [sanitizes string for use inside href attribute |#checkUrl] +| `checkUrl` | [sanitizes a URL address from dangerous inputs |#checkUrl] | `nocheck` | [prevents automatic URL sanitization |#nocheck] -Latte the `src` and `href` attributes [checks automatically |safety-first#link checking], so you almost don't need to use the `checkUrl` filter. +Latte the `src` and `href` attributes [checks automatically |safety-first#Link Checking], so you almost don't need to use the `checkUrl` filter. .[note] -All built-in filters work with UTF‑8 encoded strings. +All built-in filters are designed for strings in UTF‑8 encoding. Usage ===== -Latte allows calling filters by using the pipe sign notation (preceding space is allowed): +Filters are written after the pipe symbol (a space before it is allowed): ```latte

                                                  {$heading|upper}

                                                  ``` -Filters can be chained, in that case they apply in order from left to right: +Filters can be chained, and they are applied in order from left to right: ```latte

                                                  {$heading|lower|capitalize}

                                                  ``` -Parameters are put after the filter name separated by colon or comma: +Parameters are entered after the filter name, separated by colons or commas: ```latte

                                                  {$heading|truncate:20,''}

                                                  ``` -Filters can be applied on expression: +Filters can also be applied to an expression: ```latte {var $name = ($title|upper) . ($subtitle|lower)} ``` -[Custom filters|extending-latte#filters] can be registered this way: +[Custom filters|custom-filters] can be registered this way: ```php $latte = new Latte\Engine; $latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -We use it in a template like this: +It is then called in the template like this: ```latte

                                                  {$text|shortify}

                                                  @@ -114,13 +124,34 @@ We use it in a template like this: ``` +Nullsafe Filters .{data-version:3.1} +------------------------------------ + +Any filter can be made nullsafe by using `?|` instead of `|`. If the value is `null`, the filter is not executed and `null` is returned. Subsequent filters in the chain are also skipped. + +This is useful in combination with HTML attributes, which are omitted if the value is `null`. + +```latte +
                                                  +{* If $title is null:
                                                  *} +{* If $title is 'hello':
                                                  *} +``` + + Filters ======= -batch(int length, mixed item): array .[filter]{data-version:2.7} ----------------------------------------------------------------- -Filter that simplifies the listing of linear data in the form of a table. It returns an array of array with the given number of items. If you provide a second parameter this is used to fill up missing items on the last row. +accept .[filter]{data-version:3.1} +---------------------------------- +The filter is used during [migration from Latte 3.0|cookbook/migration-from-latte-30] to acknowledge that you've reviewed the attribute behavior change and accept it. It does not modify the value. + +This is a temporary tool. Once the migration is complete and migration warnings are disabled, you should remove this filter from your templates. + + +batch(int $length, mixed $item): array .[filter] +------------------------------------------------ +A filter that simplifies listing linear data in a table format. It returns an array of arrays with the specified number of items. If you provide a second parameter, it will be used to fill in missing items in the last row. ```latte {var $items = ['a', 'b', 'c', 'd', 'e']} @@ -135,7 +166,7 @@ Filter that simplifies the listing of linear data in the form of a table. It ret
                                                  Inner loop
                                                  ``` -Prints: +Outputs: ```latte @@ -152,10 +183,12 @@ Prints:
                                                  ``` +See also [#group] and the [iterateWhile |tags#iterateWhile] tag. + breakLines .[filter] -------------------- -Inserts HTML line breaks before all newlines. +Inserts an HTML `
                                                  ` tag before each newline character. ```latte {var $s = "Text & with \n newline"} @@ -163,19 +196,19 @@ Inserts HTML line breaks before all newlines. ``` -bytes(int precision = 2) .[filter] ----------------------------------- -Formats a size in bytes to human-readable form. +bytes(int $precision=2) .[filter] +--------------------------------- +Formats the size in bytes into a human-readable form. If the [locale |develop#Locale] is set, the corresponding decimal and thousand separators are used. ```latte -{$size|bytes} 0 B, 1.25 GB, … -{$size|bytes:0} 10 B, 1 GB, … +{$size|bytes} {* 0 B, 1.25 GB, … *} +{$size|bytes:0} {* 10 B, 1 GB, … *} ``` -ceil(int precision = 0) .[filter] ---------------------------------- -Rounds a number up to a given precision. +ceil(int $precision=0) .[filter] +-------------------------------- +Rounds a number up to the given precision. ```latte {=3.4|ceil} {* outputs 4 *} @@ -188,26 +221,26 @@ See also [#floor], [#round]. capitalize .[filter] -------------------- -Returns a title-cased version of the value. Words will start with uppercase letters, all remaining characters are lowercase. Requires PHP extension `mbstring`. +Words will start with uppercase letters, all remaining characters will be lowercase. Requires the `mbstring` PHP extension. ```latte {='i like LATTE'|capitalize} {* outputs 'I Like Latte' *} ``` -See also [#firstUpper], [#lower], [#upper]. +See also [#firstLower], [#firstUpper], [#lower], [#upper]. checkUrl .[filter] ------------------ -Enforces URL sanitization. It checks if the variable contains a web URL (ie. HTTP/HTTPS protocol) and prevents the writing of links that may pose a security risk. +Enforces URL sanitization. It checks if the variable contains a web URL (i.e., HTTP/HTTPS protocol) and prevents the output of links that could pose a security risk. ```latte {var $link = 'javascript:window.close()'} -checked -unchecked +checked +unchecked ``` -Prints: +Outputs: ```latte checked @@ -217,28 +250,72 @@ Prints: See also [#nocheck]. -clamp(int|float min, int|float max) .[filter]{data-version:2.9} ---------------------------------------------------------------- -Returns value clamped to the inclusive range of min and max. +clamp(int|float $min, int|float $max) .[filter] +----------------------------------------------- +Clamps a value to the given inclusive range of min and max. ```latte {$level|clamp: 0, 255} ``` -Also exists as [function|functions#clamp]. +Also exists as a [function |functions#clamp]. + + +column(string|int|null $columnKey, string|int|null $indexKey=null) .[filter]{data-version:3.1.2} +------------------------------------------------------------------------------------------------ +Returns all values of a specific column `$columnKey` from a multidimensional array as a new simple array. The filter can also be used on arrays of objects to extract property values. + +```latte +{var $users = [ + [id: 30, name: 'John', age: 30], + [id: 32, name: 'Jane', age: 25], + [id: 33, age: 35], +]} + +{$users|column: 'name'} +{* returns ['John', 'Jane'] *} + +{$users|column: 'name', 'id'} +{* returns [30 => 'John', 32 => 'Jane'] *} +``` + +If you pass `null` as the column key, it will reindex the array according to `$indexKey`. + + +commas(?string $lastGlue=null) .[filter]{data-version:3.1.2} +------------------------------------------------------------ +Joins array elements with a comma and space (`', '`). This is a convenient shortcut for the common use case of listing items in a human-readable format. + +```latte +{var $items = ['apples', 'oranges', 'bananas']} +{$items|commas} +{* outputs 'apples, oranges, bananas' *} +``` + +You can optionally provide a custom separator for the last pair of items: + +```latte +{$items|commas: ' and '} +{* outputs 'apples, oranges and bananas' *} + +{=['PHP', 'JavaScript', 'Python']|commas: ', or '} +{* outputs 'PHP, JavaScript, or Python' *} +``` + +See also [#implode]. -dataStream(string mimetype = detect) .[filter] ----------------------------------------------- -Converts the content to data URI scheme. It can be used to insert images into HTML or CSS without the need to link external files. +dataStream(string $mimetype='detect') .[filter] +----------------------------------------------- +Converts content to the data URI scheme. This allows embedding images into HTML or CSS without needing to link external files. -Lets have an image in a variable `$img = Image::fromFile('obrazek.gif')`, then +Let's have an image in the variable `$img = Image::fromFile('image.gif')`, then ```latte - + ``` -Prints for example: +Outputs, for example: ```latte {$name} @@ -271,21 +349,21 @@ Escapes a variable to be used as a parameter in URL. See also [#query]. -explode(string separator = '') .[filter]{data-version:2.10.2} -------------------------------------------------------------- -Splits a string by the given delimiter and returns an array of strings. Alias for `split`. +explode(string $separator='') .[filter] +--------------------------------------- +Splits a string into an array by a delimiter. Alias for `split`. ```latte {='one,two,three'|explode:','} {* returns ['one', 'two', 'three'] *} ``` -If the delimiter is an empty string (default value), the input will be divided into individual characters: +If the delimiter is an empty string (the default value), the input will be split into individual characters: ```latte {='123'|explode} {* returns ['1', '2', '3'] *} ``` -You can use also alias `split`: +You can also use the alias `split`: ```latte {='1,2,3'|split:','} {* returns ['1', '2', '3'] *} @@ -294,9 +372,9 @@ You can use also alias `split`: See also [#implode]. -first .[filter]{data-version:2.10.2} ------------------------------------- -Returns the first element of array or character of string: +first .[filter] +--------------- +Returns the first element of an array or the first character of a string: ```latte {=[1, 2, 3, 4]|first} {* outputs 1 *} @@ -306,9 +384,9 @@ Returns the first element of array or character of string: See also [#last], [#random]. -floor(int precision = 0) .[filter] ----------------------------------- -Rounds a number down to a given precision. +floor(int $precision=0) .[filter] +--------------------------------- +Rounds a number down to the given precision. ```latte {=3.5|floor} {* outputs 3 *} @@ -319,36 +397,68 @@ Rounds a number down to a given precision. See also [#ceil], [#round]. +firstLower .[filter]{data-version:3.0.22} +----------------------------------------- +Converts the first letter to lower case. Requires PHP extension `mbstring`. + +```latte +{='The Latte'|firstLower} {* vypíše 'the Latte' *} +``` + +See also [#capitalize], [#firstUpper], [#lower], [#upper]. + + firstUpper .[filter] -------------------- -Converts a first letter of value to uppercase. Requires PHP extension `mbstring`. +Converts the first letter to uppercase. Requires the `mbstring` PHP extension. ```latte {='the latte'|firstUpper} {* outputs 'The latte' *} ``` -See also [#capitalize], [#lower], [#upper]. +See also [#capitalize], [#firstLower], [#lower], [#upper]. + +group(string|int|\Closure $by): array .[filter]{data-version:3.0.16} +-------------------------------------------------------------------- +The filter groups data according to various criteria. -implode(string glue = '') .[filter] ------------------------------------ -Return a string which is the concatenation of the strings in the array. Alias for `join`. +In this example, rows in the table are grouped by the `categoryId` column. The output is an array of arrays, where the key is the value in the `categoryId` column. [Read the detailed guide|cookbook/grouping]. + +```latte +{foreach ($items|group: categoryId) as $categoryId => $categoryItems} +
                                                    + {foreach $categoryItems as $item} +
                                                  • {$item->name}
                                                  • + {/foreach} +
                                                  +{/foreach} +``` + +See also [#batch], the [group |functions#group] function, and the [iterateWhile |tags#iterateWhile] tag. + + +implode(string $glue='') .[filter] +---------------------------------- +Returns a string which is the concatenation of the items in the sequence. Alias for `join`. ```latte {=[1, 2, 3]|implode} {* outputs '123' *} {=[1, 2, 3]|implode:'|'} {* outputs '1|2|3' *} ``` -You can also use an alias `join`: .{data-version:2.10.2} +You can also use the alias `join`: ```latte {=[1, 2, 3]|join} {* outputs '123' *} ``` +See also [#commas], [#explode]. -indent(int level = 1, string char = "\t") .[filter] ---------------------------------------------------- -Indents a text from left by a given number of tabs or other characters which we specify in the second optional argument. Blank lines are not indented. + +indent(int $level=1, string $char="\t") .[filter] +------------------------------------------------- +Indents text from the left by a given number of tabs or other characters specified in the second argument. Blank lines are not indented. ```latte
                                                  @@ -358,7 +468,7 @@ Indents a text from left by a given number of tabs or other characters which we
                                                  ``` -Prints: +Outputs: ```latte
                                                  @@ -367,9 +477,9 @@ Prints: ``` -last .[filter]{data-version:2.10.2} ------------------------------------ -Returns the last element of array or character of string: +last .[filter] +-------------- +Returns the last element of an array or the last character of a string: ```latte {=[1, 2, 3, 4]|last} {* outputs 4 *} @@ -381,12 +491,12 @@ See also [#first], [#random]. length .[filter] ---------------- -Returns length of a string or array. +Returns the length of a string or array. -- for strings, it will return length in UTF‑8 characters -- for arrays, it will return count of items -- for objects that implement the Countable interface, it will use the return value of the count() -- for objects that implement the IteratorAggregate interface, it will use the return value of the iterator_count() +- for strings, it returns the length in UTF‑8 characters +- for arrays, it returns the number of items +- for objects implementing the Countable interface, it uses the return value of the `count()` method +- for objects implementing the IteratorAggregate interface, it uses the return value of the `iterator_count()` function ```latte @@ -396,31 +506,93 @@ Returns length of a string or array. ``` +localDate(?string $format=null, ?string $date=null, ?string $time=null) .[filter] +--------------------------------------------------------------------------------- +Formats date and time according to the [locale |develop#Locale], ensuring consistent and localized display of time data across different languages and regions. The filter accepts the date as a UNIX timestamp, string, or `DateTimeInterface` object. + +```latte +{$date|localDate} {* 15. dubna 2024 *} +{$date|localDate: format: yM} {* 4/2024 *} +{$date|localDate: date: medium} {* 15. 4. 2024 *} +``` + +If you use the filter without parameters, it will output the date at the `long` level, see below. + +**a) Using format** + +The `format` parameter describes which time components should be displayed. It uses letter codes, where the number of repetitions affects the width of the output: + +| Year | `y` / `yy` / `yyyy` | `2024` / `24` / `2024` +| Month | `M` / `MM` / `MMM` / `MMMM` | `8` / `08` / `Aug` / `August` +| Day | `d` / `dd` / `E` / `EEEE` | `1` / `01` / `Sun` / `Sunday` +| Hour | `j` / `H` / `h` | preferred / 24-hour / 12-hour +| Minute | `m` / `mm` | `5` / `05` (2 digits when combined with seconds) +| Second | `s` / `ss` | `8` / `08` (2 digits when combined with minutes) + +The order of codes in the format does not matter, as the order of components will be displayed according to the locale's conventions. Thus, the format is locale-independent. For example, the format `yyyyMMMMd` in the `en_US` locale outputs `April 15, 2024`, while in the `cs_CZ` locale it outputs `15. dubna 2024`: + +| locale: | cs_CZ | en_US +|--- +| `format: 'dMy'` | 10. 8. 2024 | 8/10/2024 +| `format: 'yM'` | 8/2024 | 8/2024 +| `format: 'yyyyMMMM'` | srpen 2024 | August 2024 +| `format: 'MMMM'` | srpen | August +| `format: 'jm'` | 17:22 | 5:22 PM +| `format: 'Hm'` | 17:22 | 17:22 +| `format: 'hm'` | 5:22 odp. | 5:22 PM + + +**b) Using preset styles** + +The `date` and `time` parameters determine how detailed the date and time should be displayed. You can choose from several levels: `full`, `long`, `medium`, `short`. You can choose to display only the date, only the time, or both: + +| locale: | cs_CZ | en_US +|--- +| `date: short` | 23.01.78 | 1/23/78 +| `date: medium` | 23. 1. 1978 | Jan 23, 1978 +| `date: long` | 23. ledna 1978 | January 23, 1978 +| `date: full` | pondělí 23. ledna 1978 | Monday, January 23, 1978 +| `time: short` | 8:30 | 8:30 AM +| `time: medium` | 8:30:59 | 8:30:59 AM +| `time: long` | 8:30:59 SEČ | 8:30:59 AM GMT+1 +| `date: short, time: short` | 23.01.78 8:30 | 1/23/78, 8:30 AM +| `date: medium, time: short` | 23. 1. 1978 8:30 | Jan 23, 1978, 8:30 AM +| `date: long, time: short` | 23. ledna 1978 v 8:30 | January 23, 1978 at 8:30 AM + +For the date, you can also use the prefix `relative-` (e.g., `relative-short`), which for dates close to the present will display `yesterday`, `today`, or `tomorrow`; otherwise, it will display in the standard way. + +```latte +{$date|localDate: date: relative-short} {* yesterday *} +``` + +See also [#date]. + + lower .[filter] --------------- -Converts a value to lowercase. Requires PHP extension `mbstring`. +Converts a string to lowercase. Requires the `mbstring` PHP extension. ```latte {='LATTE'|lower} {* outputs 'latte' *} ``` -See also [#capitalize], [#firstUpper], [#upper]. +See also [#capitalize], [#firstLower], [#firstUpper], [#upper]. nocheck .[filter] ----------------- -Prevents automatic URL sanitization. Latte [automatically checks|safety-first#Link checking] if the variable contains a web URL (ie. HTTP/HTTPS protocol) and prevents the writing of links that may pose a security risk. +Prevents automatic URL sanitization. Latte [automatically checks |safety-first#Link Checking] if the variable contains a web URL (ie. HTTP/HTTPS protocol) and prevents the writing of links that may pose a security risk. -If the link uses a different scheme, such as `javascript:` or `data:`, and you are sure of its contents, you can disable the check via `|nocheck`. +If the link uses a different scheme, such as `javascript:` or `data:`, and you are sure of its content, you can disable the check using `|nocheck`. ```latte {var $link = 'javascript:window.close()'} -checked -unchecked +checked +unchecked ``` -Prints: +Outputs: ```latte checked @@ -440,7 +612,7 @@ Escaped: {$trustedHtmlString} Unescaped: {$trustedHtmlString|noescape} ``` -Prints: +Outputs: ```latte Escaped: <b>hello</b> @@ -451,71 +623,119 @@ Unescaped: hello Misuse of the `noescape` filter can lead to an XSS vulnerability! Never use it unless you are **absolutely sure** what you are doing and that the string you are printing comes from a trusted source. -number(int decimals = 0, string decPoint = '.', string thousandsSep = ',') .[filter] ------------------------------------------------------------------------------------- -Formats a number to given number of decimal places. You can also specify a character of the decimal point and thousands separator. +number(int $decimals=0, string $decPoint='.', string $thousandsSep=',') .[filter] +--------------------------------------------------------------------------------- +Formats a number to a specified number of decimal places. If the [locale |develop#Locale] is set, the corresponding decimal and thousand separators are used. ```latte -{1234.20 |number} 1,234 -{1234.20 |number:1} 1,234.2 -{1234.20 |number:2} 1,234.20 -{1234.20 |number:2, ',', ' '} 1 234,20 +{1234.20|number} {* 1,234 *} +{1234.20|number:1} {* 1,234.2 *} +{1234.20|number:2} {* 1,234.20 *} +{1234.20|number:2, ',', ' '} {* 1 234,20 *} ``` -padLeft(int length, string pad = ' ') .[filter] +number(string $format) .[filter] +-------------------------------- +The `format` parameter allows you to define the appearance of numbers exactly according to your needs. This requires the [locale |develop#Locale] to be set. The format consists of several special characters, a complete description of which can be found in the "DecimalFormat":https://unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns documentation: + +- `0` mandatory digit, always displayed even if it's zero +- `#` optional digit, displayed only if the number actually has a digit in this place +- `@` significant digit, helps display the number with a certain number of significant digits +- `.` indicates where the decimal separator should be (dot or comma, depending on the country) +- `,` serves to separate groups of digits, most often thousands +- `%` multiplies the number by 100 and adds the percent sign + +Let's look at some examples. In the first example, two decimal places are mandatory; in the second, they are optional. The third example shows padding with zeros from the left and right, the fourth displays only existing digits: + +```latte +{1234.5|number: '#,##0.00'} {* 1,234.50 *} +{1234.5|number: '#,##0.##'} {* 1,234.5 *} +{1.23 |number: '000.000'} {* 001.230 *} +{1.2 |number: '##.##'} {* 1.2 *} +``` + +Significant digits determine how many digits, regardless of the decimal point, should be displayed, rounding if necessary: + +```latte +{1234|number: '@@'} {* 1200 *} +{1234|number: '@@@'} {* 1230 *} +{1234|number: '@@@#'} {* 1234 *} +{1.2345|number: '@@@'} {* 1.23 *} +{0.00123|number: '@@'} {* 0.0012 *} +``` + +An easy way to display a number as a percentage. The number is multiplied by 100, and the `%` sign is added: + +```latte +{0.1234|number: '#.##%'} {* 12.34% *} +``` + +We can define a different format for positive and negative numbers, separated by a `;` character. This way, for example, positive numbers can be displayed with a `+` sign: + +```latte +{42|number: '#.##;(#.##)'} {* 42 *} +{-42|number: '#.##;(#.##)'} {* (42) *} +{42|number: '+#.##;-#.##'} {* +42 *} +{-42|number: '+#.##;-#.##'} {* -42 *} +``` + +Remember that the actual appearance of numbers may vary depending on the country settings. For example, in some countries, a comma is used instead of a dot as the decimal separator. This filter automatically takes this into account, so you don't need to worry about it. + + +padLeft(int $length, string $pad=' ') .[filter] ----------------------------------------------- -Pads a string to a certain length with another string from left. +Pads a string to a certain length with another string from the left. ```latte {='hello'|padLeft: 10, '123'} {* outputs '12312hello' *} ``` -padRight(int length, string pad = ' ') .[filter] +padRight(int $length, string $pad=' ') .[filter] ------------------------------------------------ -Pads a string to a certain length with another string from right. +Pads a string to a certain length with another string from the right. ```latte {='hello'|padRight: 10, '123'} {* outputs 'hello12312' *} ``` -query .[filter]{data-version:2.10} ------------------------------------ -Dynamically generates a query string in the URL: +query .[filter] +--------------- +Dynamically generates a query string in a URL: ```latte click search ``` -Prints: +Outputs: ```latte click search ``` -Keys with a value of `null` are omitted. +Keys with a `null` value are omitted. See also [#escapeUrl]. -random .[filter]{data-version:2.10.2} -------------------------------------- -Returns random element of array or character of string: +random .[filter] +---------------- +Returns a random element of an array or a random character of a string: ```latte -{=[1, 2, 3, 4]|random} {* example output: 3 *} -{='abcd'|random} {* example output: 'b' *} +{=[1, 2, 3, 4]|random} {* outputs e.g.: 3 *} +{='abcd'|random} {* outputs e.g.: 'b' *} ``` See also [#first], [#last]. -repeat(int count) .[filter] ---------------------------- +repeat(int $count) .[filter] +---------------------------- Repeats the string x-times. ```latte @@ -523,7 +743,7 @@ Repeats the string x-times. ``` -replace(string|array search, string replace = '') .[filter] +replace(string|array $search, string $replace='') .[filter] ----------------------------------------------------------- Replaces all occurrences of the search string with the replacement string. @@ -531,16 +751,16 @@ Replaces all occurrences of the search string with the replacement string. {='hello world'|replace: 'world', 'friend'} {* outputs 'hello friend' *} ``` -Multiple replacements can be made at once: .{data-version:2.10.2} +Multiple replacements can be made at once: ```latte {='hello world'|replace: [h => l, l => h]} {* outputs 'lehho worhd' *} ``` -replaceRE(string pattern, string replace = '') .[filter] +replaceRE(string $pattern, string $replace='') .[filter] -------------------------------------------------------- -Replaces all occurrences according to regular expression. +Performs a regular expression search and replace. ```latte {='hello world'|replaceRE: '/l.*/', 'l'} {* outputs 'hel' *} @@ -549,7 +769,7 @@ Replaces all occurrences according to regular expression. reverse .[filter] ----------------- -Reverses given string or array. +Reverses the given string or array. ```latte {var $s = 'Nette'} @@ -559,9 +779,9 @@ Reverses given string or array. ``` -round(int precision = 0) .[filter] ----------------------------------- -Rounds a number to a given precision. +round(int $precision=0) .[filter] +--------------------------------- +Rounds a number to the given precision. ```latte {=3.4|round} {* outputs 3 *} @@ -573,8 +793,8 @@ Rounds a number to a given precision. See also [#ceil], [#floor]. -slice(int start, int length = null, bool preserveKeys = false) .[filter]{data-version:2.10.2} ---------------------------------------------------------------------------------------------- +slice(int $start, ?int $length=null, bool $preserveKeys=false) .[filter] +------------------------------------------------------------------------ Extracts a slice of an array or a string. ```latte @@ -582,18 +802,18 @@ Extracts a slice of an array or a string. {=['a', 'b', 'c']|slice: 1, 2} {* outputs ['b', 'c'] *} ``` -The slice filter works as the `array_slice` PHP function for arrays and `mb_substr` for strings with a fallback to `iconv_substr` in UTF‑8 mode. +The filter works like the PHP function `array_slice` for arrays or `mb_substr` for strings, with a fallback to the `iconv_substr` function in UTF‑8 mode. -If the start is non-negative, the sequence will start at that start in the variable. If start is negative, the sequence will start that far from the end of the variable. +If `start` is non-negative, the sequence will start at that offset from the beginning of the array/string. If `start` is negative, the sequence will start that far from the end. -If length is given and is positive, then the sequence will have up to that many elements in it. If the variable is shorter than the length, then only the available variable elements will be present. If length is given and is negative then the sequence will stop that many elements from the end of the variable. If it is omitted, then the sequence will have everything from offset up until the end of the variable. +If `length` is given and is positive, then the sequence will have up to that many elements. If the input is shorter than the `length`, then only the available elements will be present. If `length` is given and is negative, the sequence will stop that many elements from the end of the input. If it is omitted, the sequence will have everything from `start` up until the end of the input. -Filter will reorder and reset the integer array keys by default. This behaviour can be changed by setting preserveKeys to true. String keys are always preserved, regardless of this parameter. +By default, the filter reorders and resets the integer array keys. This behavior can be changed by setting `preserveKeys` to true. String keys are always preserved, regardless of this parameter. -sort .[filter]{data-version:2.9} ---------------------------------- -Filter that sorts an array and maintain index association. +sort(?Closure $comparison, string|int|\Closure|null $by=null, string|int|\Closure|bool $byKey=false) .[filter] +-------------------------------------------------------------------------------------------------------------- +The filter sorts elements of an array or iterator and preserves their associative keys. When a [locale |develop#Locale] is set, the sorting follows its rules unless a custom comparison function is specified. ```latte {foreach ($names|sort) as $name} @@ -601,7 +821,7 @@ Filter that sorts an array and maintain index association. {/foreach} ``` -Array sorted in reverse order. +Sorted array in reverse order: ```latte {foreach ($names|sort|reverse) as $name} @@ -609,16 +829,42 @@ Array sorted in reverse order. {/foreach} ``` -You can pass your own comparison function as a parameter: .{data-version:2.10.2} +You can specify a custom comparison function for sorting (the example shows how to reverse the sort from largest to smallest): ```latte -{var $sorted = ($names|sort: fn($a, $b) => $b <=> $a)} +{var $reverted = ($names|sort: fn($a, $b) => $b <=> $a)} ``` +The `|sort` filter also allows sorting elements by keys: -spaceless .[filter]{data-version:2.10.2} ------------------------------------------ -Removes unnecessary whitespace from the output. You can also use alias `strip`. +```latte +{foreach ($names|sort: byKey: true) as $name} + ... +{/foreach} +``` + +If you need to sort a table by a specific column, you can use the `by` parameter. The value `'name'` in the example specifies that sorting will be done by `$item->name` or `$item['name']`, depending on whether `$item` is an array or an object: + +```latte +{foreach ($items|sort: by: 'name') as $item} + {$item->name} +{/foreach} +``` + +You can also define a callback function that determines the value to sort by: + +```latte +{foreach ($items|sort: by: fn($item) => $item->category->name) as $item} + {$item->name} +{/foreach} +``` + +The `byKey` parameter can be used in the same way. + + +spaceless .[filter] +------------------- +Removes unnecessary whitespace from the output. You can also use the alias `strip`. ```latte {block |spaceless} @@ -628,7 +874,7 @@ Removes unnecessary whitespace from the output. You can also use alias `strip`. {/block} ``` -Prints: +Outputs: ```latte
                                                  • Hello
                                                  @@ -637,37 +883,52 @@ Prints: stripHtml .[filter] ------------------- -Converts HTML to plain text. That is, it removes HTML tags and converts HTML entities to text. +Converts HTML to plain text. That is, it removes HTML tags and converts HTML entities to text characters. ```latte {='

                                                  one < two

                                                  '|stripHtml} {* outputs 'one < two' *} ``` -The resulting plain text can naturally contain characters that represent HTML tags, for example `'<p>'|stripHtml` is converted to `

                                                  `. Never output the resulting text with `|noescape`, as this may lead to a security vulnerability. +The resulting plain text can naturally contain characters that represent HTML tags, for example `'<p>'|stripHtml` is converted to `

                                                  `. Never output the resulting text with `|noescape`, as this can lead to a security vulnerability. -substr(int offset, int length = null) .[filter] ------------------------------------------------ -Extracts a slice of a string. This filter has been replaced by a [#slice] filter. +substr(int $offset, ?int $length=null) .[filter] +------------------------------------------------ +Extracts a portion of a string. This filter has been replaced by the [#slice] filter. ```latte {$string|substr: 1, 2} ``` -translate(string message, ...args) .[filter]{data-version:3.0} --------------------------------------------------------------- -It translates expressions into other languages. To make the filter available, you need [set up translator|develop#TranslatorExtension]. You can also use the [tags for translation|tags#Translation]. +toggle .[filter]{data-version:3.1} +---------------------------------- +The `toggle` filter controls the presence of an attribute based on a boolean value. If the value is truthy, the attribute is present; if falsy, the attribute is omitted entirely: + +```latte +

                                                  +{* If $isGrid is truthy:
                                                  *} +{* If $isGrid is falsy:
                                                  *} +``` + +This filter is useful for custom attributes or JavaScript library attributes that require presence/absence control similar to HTML boolean attributes. + +The filter can only be used within HTML attributes. + + +translate(...$args) .[filter] +----------------------------- +Translates expressions into other languages. To make the filter available, you need to [set up the translator |develop#TranslatorExtension]. You can also use the [tags for translation |tags#Translation]. ```latte -{='Baskter'|translate} +{='Basket'|translate} {$item|translate} ``` -trim(string charlist = " \t\n\r\0\x0B\u{A0}") .[filter] -------------------------------------------------------- -Strip leading and trailing characters, by default whitespace. +trim(string $charlist=" \t\n\r\0\x0B\u{A0}") .[filter] +------------------------------------------------------ +Strips whitespace (or other characters) from the beginning and end of a string. ```latte {=' I like Latte. '|trim} {* outputs 'I like Latte.' *} @@ -675,9 +936,9 @@ Strip leading and trailing characters, by default whitespace. ``` -truncate(int length, string append = '…') .[filter] +truncate(int $length, string $append='…') .[filter] --------------------------------------------------- -Shortens a string to the maximum given length but tries to preserve whole words. If the string is truncated it adds ellipsis at the end (this can be changed by the second parameter). +Truncates a string to the specified maximum length, while trying to preserve whole words. If the string is shortened, it adds an ellipsis at the end (can be changed with the second parameter). ```latte {var $title = 'Hello, how are you?'} @@ -689,25 +950,25 @@ Shortens a string to the maximum given length but tries to preserve whole words. upper .[filter] --------------- -Converts a value to uppercase. Requires PHP extension `mbstring`. +Converts a string to uppercase. Requires the `mbstring` PHP extension. ```latte {='latte'|upper} {* outputs 'LATTE' *} ``` -See also [#capitalize], [#firstUpper], [#lower]. +See also [#capitalize], [#firstLower], [#firstUpper], [#lower]. webalize .[filter] ------------------ -Converts to ASCII. +Adjusts a UTF‑8 string to the format used in URLs. -Converts spaces to hyphens. Removes characters that aren’t alphanumerics, underscores, or hyphens. Converts to lowercase. Also strips leading and trailing whitespace. +Converts to ASCII. Converts spaces to hyphens. Removes characters that are not alphanumeric, underscores, or hyphens. Converts to lowercase. Also strips leading and trailing whitespace. ```latte -{var $s = 'Our 10. product'} -{$s|webalize} {* outputs 'our-10-product' *} +{var $s = 'Our 10th product'} +{$s|webalize} {* outputs 'our-10th-product' *} ``` .[caution] -Requires package [nette/utils|utils:]. +Requires the [nette/utils|utils:] library. diff --git a/latte/en/functions.texy b/latte/en/functions.texy index 304ec1ce77..f172c26df8 100644 --- a/latte/en/functions.texy +++ b/latte/en/functions.texy @@ -2,14 +2,17 @@ Latte Functions *************** .[perex] -In addition to the common PHP functions, you can also use these in templates. +In addition to common PHP functions, you can also use these functions in templates. .[table-latte-filters] -| `clamp` | [clamps value to the range |#clamp] +| `clamp` | [clamps a value to the given range |#clamp] | `divisibleBy`| [checks if a variable is divisible by a number |#divisibleBy] | `even` | [checks if the given number is even |#even] -| `first` | [returns first element of array or character of string |#first] -| `last` | [returns last element of array or character of string |#last] +| `first` | [returns the first element of an array or character of a string |#first] +| `group` | [groups data according to various criteria |#group] +| `hasBlock` | [detects the existence of a block |#hasBlock] +| `hasTemplate`| [detects the existence of a template |#hasTemplate] +| `last` | [returns the last element of an array or character of a string |#last] | `odd` | [checks if the given number is odd |#odd] | `slice` | [extracts a slice of an array or a string |#slice] @@ -25,14 +28,14 @@ Functions are used in the same way as common PHP functions and can be used in al {if odd($num)} ... {/if} ``` -[Custom functions|extending-latte#functions] can be registered this way: +[Custom functions|custom-functions] can be registered this way: ```php $latte = new Latte\Engine; $latte->addFunction('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -We use it in a template like this: +It is then called in the template like this: ```latte

                                                  {shortify($text)}

                                                  @@ -44,19 +47,19 @@ Functions ========= -clamp(int|float $value, int|float $min, int|float $max): int|float .[method]{data-version:2.9} ----------------------------------------------------------------------------------------------- -Returns value clamped to the inclusive range of min and max. +clamp(int|float $value, int|float $min, int|float $max): int|float .[method] +---------------------------------------------------------------------------- +Clamps a value to the given inclusive range of min and max. ```latte {=clamp($level, 0, 255)} ``` -See also [filter clamp|filters#clamp]: +See also the [clamp filter |filters#clamp]. -divisibleBy(int $value, int $by): bool .[method]{data-version:2.10.2} ---------------------------------------------------------------------- +divisibleBy(int $value, int $by): bool .[method] +------------------------------------------------ Checks if a variable is divisible by a number. ```latte @@ -64,8 +67,8 @@ Checks if a variable is divisible by a number. ``` -even(int $value): bool .[method]{data-version:2.10.2} ------------------------------------------------------ +even(int $value): bool .[method] +-------------------------------- Checks if the given number is even. ```latte @@ -73,32 +76,71 @@ Checks if the given number is even. ``` -first(string|array $value): mixed .[method]{data-version:2.10.2} ----------------------------------------------------------------- -Returns the first element of array or character of string: +first(string|iterable $value): mixed .[method] +---------------------------------------------- +Returns the first element of an array or the first character of a string: ```latte {=first([1, 2, 3, 4])} {* outputs 1 *} {=first('abcd')} {* outputs 'a' *} ``` -See also [#last], [filter first|filters#first]. +See also [#last], [first filter |filters#first]. -last(string|array $value): mixed .[method]{data-version:2.10.2} ---------------------------------------------------------------- -Returns the last element of array or character of string: +group(iterable $data, string|int|\Closure $by): array .[method]{data-version:3.0.16} +------------------------------------------------------------------------------------ +This function groups data according to different criteria. + +In this example, rows in the table are grouped by the `categoryId` column. The output is an array of arrays, where the key is the value in the `categoryId` column. [Read the detailed guide|cookbook/grouping]. + +```latte +{foreach group($items, categoryId) as $categoryId => $categoryItems} +
                                                    + {foreach $categoryItems as $item} +
                                                  • {$item->name}
                                                  • + {/foreach} +
                                                  +{/foreach} +``` + +See also the [group filter |filters#group]. + + +hasBlock(string $name): bool .[method]{data-version:3.0.10} +----------------------------------------------------------- +Checks if a block of the specified name exists: + +```latte +{if hasBlock(header)} ... {/if} +``` + +See also [block existence check |template-inheritance#Checking Block Existence]. + + +hasTemplate(string $name): bool .[method]{data-version:3.0.22} +-------------------------------------------------------------- +Determine if a template of the specified name exists: + +```latte +{if hasTemplate('foo.latte')} ... {/if} +``` + + +last(string|array $value): mixed .[method] +------------------------------------------ +Returns the last element of an array or the last character of a string: ```latte {=last([1, 2, 3, 4])} {* outputs 4 *} {=last('abcd')} {* outputs 'd' *} ``` -See also [#first], [filter last|filters#last]. +See also [#first], [last filter |filters#last]. -odd(int $value): bool .[method]{data-version:2.10.2} ----------------------------------------------------- +odd(int $value): bool .[method] +------------------------------- Checks if the given number is odd. ```latte @@ -106,8 +148,8 @@ Checks if the given number is odd. ``` -slice(string|array $value, int $start, int $length=null, bool $preserveKeys=false): string|array .[method]{data-version:2.10.2} -------------------------------------------------------------------------------------------------------------------------------- +slice(string|array $value, int $start, ?int $length=null, bool $preserveKeys=false): string|array .[method] +----------------------------------------------------------------------------------------------------------- Extracts a slice of an array or a string. ```latte @@ -115,10 +157,10 @@ Extracts a slice of an array or a string. {=slice(['a', 'b', 'c'], 1, 2)} {* outputs ['b', 'c'] *} ``` -The slice filter works as the `array_slice` PHP function for arrays and `mb_substr` for strings with a fallback to `iconv_substr` in UTF‑8 mode. +The function works like the PHP function `array_slice` for arrays or `mb_substr` for strings, with a fallback to the `iconv_substr` function in UTF‑8 mode. -If the start is non-negative, the sequence will start at that start in the variable. If start is negative, the sequence will start that far from the end of the variable. +If `start` is non-negative, the sequence will start at that offset from the beginning of the array/string. If `start` is negative, the sequence will start that far from the end. -If length is given and is positive, then the sequence will have up to that many elements in it. If the variable is shorter than the length, then only the available variable elements will be present. If length is given and is negative then the sequence will stop that many elements from the end of the variable. If it is omitted, then the sequence will have everything from offset up until the end of the variable. +If `length` is given and is positive, then the sequence will have up to that many elements. If the input is shorter than the `length`, then only the available elements will be present. If `length` is given and is negative, the sequence will stop that many elements from the end of the input. If it is omitted, the sequence will have everything from `start` up until the end of the input. -Filter will reorder and reset the integer array keys by default. This behaviour can be changed by setting preserveKeys to true. String keys are always preserved, regardless of this parameter. +By default, the function reorders and resets the integer array keys. This behavior can be changed by setting `preserveKeys` to true. String keys are always preserved, regardless of this parameter. diff --git a/latte/en/guide.texy b/latte/en/guide.texy index d10c780047..a54936a8f5 100644 --- a/latte/en/guide.texy +++ b/latte/en/guide.texy @@ -3,18 +3,17 @@ Getting Started with Latte
                                                  -Templates improve code organization, separate application logic from presentation, and enhance security. They offer far better features and expressive capabilities for generating HTML than PHP itself. +Templates improve code organization, separate application logic from presentation, and enhance security. They offer far better features and expressive means for generating HTML than PHP itself. -Latte is the safest templating system for PHP. You'll love its intuitive syntax. A wide range of useful features will significantly simplify your work. -It provides top-notch protection against [critical vulnerabilities|safety-first] and allows you to focus on creating high-quality applications without worrying about their security. +Latte is the safest templating system for PHP. You will love its intuitive syntax. A wide range of useful features will significantly simplify your work. It provides top-notch protection against [critical vulnerabilities|safety-first] and allows you to focus on creating high-quality applications without worrying about their security. How to write templates using Latte? ----------------------------------- -Latte is cleverly designed and easy to learn for those familiar with PHP, as they can quickly adopt its basic tags. +Latte is cleverly designed and easy to learn for those familiar with PHP who adopt the basic tags. -- First, familiarize yourself with [Latte syntax|syntax] and [try it all online |https://fiddle.nette.org/latte/] +- First, familiarize yourself with [Latte syntax|syntax] and [TRY IT ONLINE |https://fiddle.nette.org/latte/#9cc0cf6d89] - Take a look at the basic set of [tags] and [filters] - Write templates in [editor with Latte support |recipes#Editors and IDE] @@ -22,25 +21,25 @@ Latte is cleverly designed and easy to learn for those familiar with PHP, as the How to Use Latte in PHP? ------------------------ -Implementing Latte in your new application is a matter of minutes: +Deploying Latte into your new application takes just a few minutes: - First, [install and run Latte |develop#Installation] - Pamper yourself with the [Tracy debugging tool |develop#Debugging and Tracy] - Extend Latte with [custom functionality |extending-latte] -If you are converting an old project written in plain PHP to Latte, the [tool for converting PHP code to Latte |cookbook/migration-from-php] will make the migration easier for you. Or are you planning to switch to Latte from Twig? We have a [Twig template converter to Latte |cookbook/migration-from-twig] for you. +If you are converting an old project written in pure PHP to Latte, the [tool for converting PHP code to Latte |cookbook/migration-from-php] will make the migration easier. Or are you planning to switch to Latte from Twig? We have a [Twig template converter to Latte |cookbook/migration-from-twig] for you. What Else Can Latte Do? ----------------------- -The latte comes fully equipped, with all the essentials included. +Latte comes fully equipped, with everything important included in the base. -- Your productivity will be boosted by the [mechanisms of inheritance |template-inheritance] that reuse repeated elements and structures -- The [Sandbox] armour bunker isolates templates from untrusted sources, such as those edited by users themselves +- Your productivity will be boosted by [inheritance mechanisms |template-inheritance] which allow reuse of repeated elements and structures +- The armored [sandbox|sandbox] bunker isolates templates from untrusted sources, such as those edited by users themselves - For further inspiration, here are [tips and tricks |recipes]
                                                  -{{description: Latte je nejbezpečnější šablonovací systém pro PHP. Zabraňuje spoustě bezpečnostních zranitelností. Oceníte jeho intuitivní syntaxi a oceníte spoustu užitečných vychytávek.}} +{{description: Latte is the safest templating system for PHP. It prevents numerous security vulnerabilities. You'll appreciate its intuitive syntax and enjoy many useful features.}} diff --git a/latte/en/html-attributes.texy b/latte/en/html-attributes.texy new file mode 100644 index 0000000000..1107579e99 --- /dev/null +++ b/latte/en/html-attributes.texy @@ -0,0 +1,151 @@ +Smart HTML Attributes +********************* + +.[perex] +Latte 3.1 comes with a set of improvements that focuses on one of the most common activities in templates – printing HTML attributes. It brings more convenience, flexibility and security. + + +Boolean Attributes +================== + +HTML uses special attributes like `checked`, `disabled`, `selected`, or `hidden`, where the specific value is irrelevant—only their presence matters. They act as simple flags. + +Latte handles them automatically. You can pass any expression to the attribute. If it is truthy, the attribute is rendered. If it is falsey (e.g. `false`, `null`, `0`, or an empty string), the attribute is completely omitted. + +This means you can say goodbye to cumbersome macro conditions or `n:attr` and simply use: + +```latte + +``` + +If `$isDisabled` is `false` and `$isReadOnly` is `true`, it renders: + +```latte + +``` + +If you need this toggling behavior for standard attributes that don't have this automatic handling (like `data-` or `aria-` attributes), use the [toggle |filters#toggle] filter. + + +Null Values +=========== + +This is one of the most pleasant changes. Previously, if a variable was `null`, it printed as an empty string `""`. This often led to empty attributes in HTML like `class=""` or `title=""`. + +In Latte 3.1, a new universal rule applies: **A value of `null` means the attribute does not exist.** + +```latte +
                                                  +``` + +If `$title` is `null`, the output is `
                                                  `. If it contains a string, e.g. "Hello", the output is `
                                                  `. Thanks to this, you don't have to wrap attributes in conditions. + +If you use filters, keep in mind that they usually convert `null` to a string (e.g. empty string). To prevent this, use the [nullsafe filter |filters#Nullsafe Filters] `?|`: + +```latte +
                                                  +``` + + +Classes +======= + +You can pass an array to the `class` attribute. This is perfect for conditional classes: if the array is associative, the keys are used as class names and the values as conditions. The class is rendered only if the condition is true. + +```latte + +``` + +If `$isActive` is true, it renders: + +```latte + +``` + +This behavior is not limited to `class`. It works for any HTML attribute that expects a space-separated list of values, such as `itemprop`, `rel`, `sandbox`, etc. + +```latte + $isExternal]}>link +``` + + +Styles +====== + +The `style` attribute also supports arrays. It is especially useful for conditional styles. If an array item contains a key (CSS property) and a value, the property is rendered only if the value is not `null`. + +```latte +
                                                  lightblue, + display => $isVisible ? block : null, + font-size => '16px', +]}>
                                                  +``` + +If `$isVisible` is false, it renders: + +```latte +
                                                  +``` + + +Data Attributes +=============== + +Often we need to pass configuration for JavaScript into HTML. Previously this was done via `json_encode`. Now you can simply pass an array or stdClass object to a `data-` attribute and Latte will serialize it to JSON: + +```latte +
                                                  +``` + +Outputs: + +```latte +
                                                  +``` + +Also, `true` and `false` are rendered as strings `"true"` and `"false"` (i.e. valid JSON). + + +Aria Attributes +=============== + +The WAI-ARIA specification requires text values `"true"` and `"false"` for boolean values. Latte handles this automatically for `aria-` attributes: + +```latte + +``` + +Outputs: + +```latte + +``` + + +Type Checking +============= + +Have you ever seen `` in your generated HTML? It's a classic bug that often goes unnoticed. Latte introduces strict type checking for HTML attributes to make your templates more resilient against such oversight. + +Latte knows which attributes are which and what values they expect: + +- **Standard attributes** (like `href`, `id`, `value`, `placeholder`...) expect a value that can be rendered as text. This includes strings, numbers, or stringable objects. `null` is also accepted (it drops the attribute). However, if you accidentally pass an array, boolean or a generic object, Latte triggers a warning and intelligently ignores the invalid value. +- **Boolean attributes** (like `checked`, `disabled`...) accept any type, as their presence is determined by truthy/falsey logic. +- **Smart attributes** (like `class`, `style`, `data-`...) specifically handle arrays as valid inputs. + +This check ensures that your application doesn't produce unexpected HTML. + + +Migration from Latte 3.0 +======================== + +Since the behavior of `null` (it used to print `""`, now it drops the attribute) and `data-` attributes (booleans used to print `"1"`/`""`, now `"true"`/`"false"`) has changed, you might need to update your templates. + +For a smooth transition, Latte provides a migration mode that highlights differences. Read the detailed guide [Migration from Latte 3.0 to 3.1|cookbook/migration-from-latte-30]. + +[* html-attributes.webp *] diff --git a/latte/en/loaders.texy b/latte/en/loaders.texy new file mode 100644 index 0000000000..6d76066022 --- /dev/null +++ b/latte/en/loaders.texy @@ -0,0 +1,198 @@ +Loaders +******* + +.[perex] +Loaders are the mechanism Latte uses to retrieve the source code of your templates. Most commonly, templates are files stored on disk, but Latte's flexible loader system allows you to load them from virtually anywhere, or even generate them dynamically. + + +What is a Loader? +================= + +Typically, when you work with templates, you think of `.latte` files residing in your project's directory structure. This is handled by Latte's default [#FileLoader]. However, the connection between a template name (like `'main.latte'` or `'components/card.latte'`) and its actual source code content doesn't *have* to be a direct file path mapping. + +This is where loaders come in. A loader is an object responsible for taking a template name (an identifier string) and providing Latte with its source code. Latte relies entirely on the configured loader for this task. This applies not only to the initial template requested via `$latte->render('main.latte')` but also to **every template referenced within** using tags like `{include ...}`, `{layout ...}`, `{embed ...}`, or `{import ...}`. + +Why use a custom loader? + +- **Loading from alternative sources:** Fetching templates stored in a database, a cache (like Redis or Memcached), a version control system (like Git, based on a specific commit), or generated dynamically. +- **Implementing custom naming conventions:** You might want to use shorter aliases for templates or implement specific search path logic (e.g., looking in a theme directory first, then falling back to a default directory). +- **Adding security or access control:** A custom loader could verify user permissions before loading certain templates. +- **Preprocessing:** While generally discouraged ([compiler passes|compiler-passes] are better), a loader *could* theoretically preprocess template content before handing it to Latte. + +You set the loader for a `Latte\Engine` instance using the `setLoader()` method: + +```php +$latte = new Latte\Engine; + +// Using the default FileLoader for files in '/path/to/templates' +$loader = new Latte\Loaders\FileLoader('/path/to/templates'); +$latte->setLoader($loader); +``` + +A loader must implement the `Latte\Loader` interface. + + +Built-in Loaders +================ + +Latte offers several standard loaders: + + +FileLoader +---------- + +This is the **default loader** used by the `Latte\Engine` class if no other is specified. It loads templates directly from the file system. + +You can optionally set a root directory to restrict access: + +```php +use Latte\Loaders\FileLoader; + +// The following will only allow loading templates from the /var/www/html/templates directory +$loader = new FileLoader('/var/www/html/templates'); +$latte->setLoader($loader); + +// $latte->render('../../../etc/passwd'); // This would throw an exception + +// Rendering a template located at /var/www/html/templates/pages/contact.latte +$latte->render('pages/contact.latte'); +``` + +When using tags like `{include}` or `{layout}`, it resolves template names relative to the current template, unless an absolute path is specified. + + +StringLoader +------------ + +This loader retrieves template content from an associative array, where keys are template names (identifiers) and values are the template source code strings. It is particularly useful for testing or small applications where templates might be stored directly in PHP code. + +```php +use Latte\Loaders\StringLoader; + +$loader = new StringLoader([ + 'main.latte' => 'Hello {$name}, include is below:{include helper.latte}', + 'helper.latte' => '{var $x = 10}Included content: {$x}', + // Add more templates as needed +]); + +$latte->setLoader($loader); + +$latte->render('main.latte', ['name' => 'World']); +// Output: Hello World, include is below:Included content: 10 +``` + +If you need to render only a single template directly from a string without needing includes or inheritance referencing other named string templates, you can pass the string directly to the `render()` or `renderToString()` method when using `StringLoader` without an array: + +```php +$loader = new StringLoader; +$latte->setLoader($loader); + +$templateString = 'Hello {$name}!'; +$output = $latte->renderToString($templateString, ['name' => 'Alice']); +// $output contains 'Hello Alice!' +``` + + +Creating a Custom Loader +======================== + +To create your own loader (e.g., for loading templates from a database, cache, version control system, or another source), you must create a class that implements the [api:Latte\Loader] interface. + +Let's look at what each method must do. + + +getContent(string $name): string .[method] +------------------------------------------ +This is the core method of the loader. Its task is to retrieve and return the full source code of the template identified by `$name` (as passed to the `$latte->render()` method or returned by the [#getReferredName()] method). + +If the template cannot be found or accessed, this method **must throw an `Latte\TemplateNotFoundException`**. + +```php +public function getContent(string $name): string +{ + // Example: Loading from a hypothetical internal storage + $content = $this->storage->read($name); + if ($content === null) { + throw new Latte\RuntimeException("Template '$name' cannot be loaded."); + } + return $content; +} +``` + + +getReferredName(string $name, string $referringName): string .[method] +---------------------------------------------------------------------- +This method handles the resolution of template names used within tags like `{include}`, `{layout}`, etc. When Latte encounters, for example, `{include 'partial.latte'}` inside `main.latte`, it calls this method with `$name = 'partial.latte'` and `$referringName = 'main.latte'`. + +The method's job is to resolve `$name` into a canonical identifier (e.g., an absolute path, a unique database key) that will be used when calling other loader methods, based on the context provided by `$referringName`. + +```php +public function getReferredName(string $name, string $referringName): string +{ + return ...; +} +``` + + +getUniqueId(string $name): string .[method] +------------------------------------------- +Latte uses a cache of compiled templates for performance improvement. Each compiled template file needs a unique name derived from the source template's identifier. This method provides a string that **uniquely identifies** the template `$name`. + +For file-based templates, the absolute path can serve this purpose. For templates in a database, a combination of a prefix and the database ID is common. + +```php +public function getUniqueId(string $name): string +{ + return ...; +} +``` + + +Example: Simple Database Loader +------------------------------- + +This example shows the basic structure of a loader that loads templates stored in a database table named `templates` with columns `name` (unique identifier), `content`, and `updated_at`. + +```php +use Latte; + +class DatabaseLoader implements Latte\Loader +{ + public function __construct( + private \PDO $db, + ) { + } + + public function getContent(string $name): string + { + $stmt = $this->db->prepare('SELECT content FROM templates WHERE name = ?'); + $stmt->execute([$name]); + $content = $stmt->fetchColumn(); + if ($content === false) { +throw new Latte\TemplateNotFoundException("Template '$name' not found in database."); + } + return $content; + } + + // This simple example assumes that template names ('homepage', 'article', etc.) + // are unique IDs and templates do not reference each other relatively. + public function getReferredName(string $name, string $referringName): string + { + return $name; + } + + public function getUniqueId(string $name): string + { + // Using a prefix and the name itself is unique and sufficient here + return 'db_' . $name; + } +} + +// Usage: +$pdo = new \PDO(/* connection details */); +$loader = new DatabaseLoader($pdo); +$latte->setLoader($loader); +$latte->render('homepage'); // Loads the template named 'homepage' from the DB +``` + +Custom loaders give you complete control over where your Latte templates come from, allowing integration with various storage systems and workflows. diff --git a/latte/en/recipes.texy b/latte/en/recipes.texy index c205005472..37896b3ee5 100644 --- a/latte/en/recipes.texy +++ b/latte/en/recipes.texy @@ -5,25 +5,25 @@ Tips and Tricks Editors and IDE =============== -Write templates in an editor or IDE that has support for Latte. It will be much more pleasant. +Write templates in an editor or IDE that supports Latte. It will be much more pleasant. -- NetBeans IDE has built-in support - PhpStorm: install the [Latte plugin|https://plugins.jetbrains.com/plugin/7457-latte] in `Settings > Plugins > Marketplace` -- VS Code: search markerplace for "Nette Latte + Neon" plugin -- Sublime Text 3: in Package Control find and install `Nette` package and select Latte in `View > Syntax` -- in old editors use Smarty highlighting for .latte files +- VS Code: install [Nette Latte + Neon|https://marketplace.visualstudio.com/items?itemName=Kasik96.latte], [Nette Latte templates|https://marketplace.visualstudio.com/items?itemName=smuuf.latte-lang] or the latest [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode] plugin +- NetBeans IDE: native support for Latte is included in the installation +- Sublime Text 3: find and install the `Nette` package in Package Control and choose Latte in `View > Syntax` +- in older editors, use Smarty highlighting for `.latte` files -The plugin for PhpStorm is very advanced and can perfectly suggest PHP code. To work optimally, use [typed templates|type-system]. +The plugin for PhpStorm is very advanced and can provide excellent suggestions for PHP code. For optimal functionality, use [typed templates|type-system]. [* latte-phpstorm-plugin.webp *] -Support for Latte can also be found in the web code highlighter [Prism.js|https://prismjs.com/#supported-languages] and editor [Ace|https://ace.c9.io]. +Support for Latte can also be found in the web code highlighter [Prism.js|https://prismjs.com/#supported-languages] and the editor [Ace|https://ace.c9.io]. Latte Inside JavaScript or CSS ============================== -Latte can be used very comfortably inside JavaScript or CSS. But how to avoid Latte mistakenly considering JavaScript code or CSS style to be a Latte tag? +Latte can be used very comfortably inside JavaScript or CSS. However, how can you avoid situations where Latte mistakenly considers JavaScript code or CSS styles as Latte tags? ```latte -- in comment: +- in text: {{ foo }} +- in tag: +- in attribute: +- in unquoted attribute: +- in attribute containing URL: +- in attribute containing JavaScript: +- in attribute containing CSS: +- in JavaScript: +- in CSS: +- in comment: ```
                                                  -Naive systems just mechanically convert `< > & ' "` characters to HTML entities, which is a valid way of escaping in most uses, but far from always. Thus, they cannot detect or prevent various security holes, as we will show below. +Naive systems just mechanically convert the characters `< > & ' "` to HTML entities, which, while a valid method of escaping in most use cases, is far from always sufficient. Thus, they cannot detect or prevent the creation of various security holes, as we will show below. + +Latte sees the template just like you do. It understands HTML, XML, recognizes tags, attributes, etc. And thanks to this, it distinguishes individual contexts and treats data accordingly. It thus offers truly effective protection against the critical Cross-site Scripting vulnerability. -Latte sees the template the same way you do. It understands HTML, XML, recognizes tags, attributes, etc. And because of this, it distinguishes between contexts and treats data accordingly. So it offers really effective protection against the critical Cross-site Scripting vulnerability. +
                                                  + +```latte .{file:Latte template as seen by Latte itself} +░░░░░░░░░░░{$foo} +░░░░░░░░░░ +░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░ +░░░░░░░░░░░░░░ +``` + +```latte .{file:Latte template as the designer sees it} +- in text: {$foo} +- in tag: +- in attribute: +- in unquoted attribute: +- in attribute containing URL: +- in attribute containing JavaScript: +- in attribute containing CSS: +- in JavaScript: +- in CSS: +- in comment: +``` + +
                                                  Live Demonstration ================== -On the left you can see the template in Latte, on the right is the generated HTML code. The `$text` variable is output several times, each time in a slightly different context. And therefore escaped a bit differently. You can edit the template code yourself, for example change the content of the variable etc. Try it: +On the left, you see the template in Latte; on the right is the generated HTML code. The variable `$text` is printed several times, each time in a slightly different context. And thus, escaped slightly differently. You can edit the template code yourself, for example, change the content of the variable, etc. Try it:
                                                  @@ -270,35 +286,33 @@ On the left you can see the template in Latte, on the right is the generated HTM
                                                  -Isn't that great! Latte does context-sensitive escaping automatically, so the programmer: +Isn't that great! Latte performs context-aware escaping automatically, so the programmer: -- doesn't have to think or know how to escape data -- can't be wrong -- can't forget about it +- doesn't need to think about or know how to escape where +- cannot make a mistake +- cannot forget about escaping -These aren't even all the contexts that Latte distinguishes when outputting and for which it customizes data treatment. We'll go through more interesting cases now. +These are not even all the contexts that Latte distinguishes when printing and for which it adapts data handling. We will now go through other interesting cases. How to Hack Naive Systems ========================= -We will use a few practical examples to show how important context differentiation is and why naive templating systems do not provide sufficient protection against XSS, unlike Latte. -We will use Twig as a representative of a naive system in the examples, but the same applies to other systems. +Using several practical examples, we will show how important context differentiation is and why naive templating systems do not provide sufficient protection against XSS, unlike Latte. We will use Twig as a representative of a naive system in the examples, but the same applies to other systems. Attribute Vulnerability ----------------------- -Let's try to inject malicious code into the page using the HTML attribute as we [showed above|#How does the vulnerability arise]. Let's have a template in Twig displaying an image: +Let's try to inject malicious code into the page using an HTML attribute, as we [showed above |#How Does the Vulnerability Arise]. Let's have a template in Twig rendering an image: ```twig .{file:Twig} {{ ``` -Note that there are no quotes around the attribute values. The coder may have forgotten them, which just happens. For example, in React, the code is written like this, without quotes, and a coder who is switching languages can easily forget about the quotes. +Notice that there are no quotes around the attribute values. The coder might have forgotten them, which simply happens. For example, in React, code is written this way, without quotes, and a coder who switches between languages can easily forget the quotes. -The attacker inserts a cleverly constructed string `foo onload=alert('Hacked!')` as the image caption. We already know that Twig can't tell if a variable is being printed in a stream of HTML text, inside an attribute, inside an HTML comment, etc.; in short, it doesn't distinguish between contexts. And it just mechanically converts `< > & ' "` characters to HTML entities. -So the resulting code will look like this: +An attacker inserts a cleverly crafted string `foo onload=alert('Hacked!')` as the image caption. We already know that Twig cannot determine whether a variable is being printed in the HTML text flow, inside an attribute, an HTML comment, etc.; in short, it does not distinguish contexts. And it just mechanically converts the characters `< > & ' "` into HTML entities. So the resulting code will look like this: ```html foo @@ -306,7 +320,7 @@ So the resulting code will look like this: **A security hole has been created!** -A fake `onload` attribute has become part of the page and the browser executes it immediately after downloading the image. +A forged `onload` attribute has become part of the page, and the browser executes it immediately after downloading the image. Now let's see how Latte handles the same template: @@ -314,7 +328,7 @@ Now let's see how Latte handles the same template: {$imageAlt} ``` -Latte sees the template the same way you do. Unlike Twig, it understands HTML and knows that a variable is printed as an attribute value that is not in quotes. That's why it adds them. When an attacker inserts the same caption, the resulting code will look like this: +Latte sees the template the same way you do. Unlike Twig, it understands HTML and knows that the variable is being printed as the value of an attribute that is not enclosed in quotes. Therefore, it adds them. When an attacker inserts the same caption, the resulting code will look like this: ```html foo onload=alert('Hacked!') @@ -326,7 +340,7 @@ Latte sees the template the same way you do. Unlike Twig, it understands HTML an Printing a Variable in JavaScript --------------------------------- -Thanks to context-sensitive escaping, it is possible to use PHP variables natively inside JavaScript. +Thanks to context-aware escaping, it is possible to use PHP variables natively within JavaScript. ```latte

                                                  {$movie}

                                                  @@ -334,7 +348,7 @@ Thanks to context-sensitive escaping, it is possible to use PHP variables native ``` -If `$movie` variable stores `'Amarcord & 8 1/2'` string it generates the following output. Notice different escaping used in HTML and JavaScript and also in `onclick` attribute: +If the variable `$movie` contains the string `'Amarcord & 8 1/2'`, the following output will be generated. Notice the different escaping used within HTML compared to within JavaScript, and yet another different escaping in the `onclick` attribute: ```latte

                                                  Amarcord & 8 1/2

                                                  @@ -346,26 +360,24 @@ If `$movie` variable stores `'Amarcord & 8 1/2'` string it generates the followi Link Checking ------------- -Latte automatically checks whether the variable used in the `src` or `href` attributes contains a web URL (ie protocol HTTP) and prevents the writing of links that may pose a security risk. +Latte automatically checks whether a variable used in `src` or `href` attributes contains a web URL (i.e., HTTP protocol) and prevents the output of links that could pose a security risk. ```latte {var $link = 'javascript:attack()'} -click here +click here ``` -Writes: +Outputs: ```latte click here ``` -The check can be turned off using a filter [nocheck|filters#nocheck]. +The check can be disabled using the [nocheck |filters#nocheck] filter. Limits of Latte =============== -Latte is not a complete XSS protection for the entire application. We would be unhappy if you stopped to think about security when using Latte. -The goal of Latte is to ensure that an attacker cannot alter the structure of a page, tamper with HTML elements or attributes. But it does not check the content correctness of the data being output. Or the correctness of JavaScript behavior. -That's beyond the scope of the templating system. Verifying the correctness of data, especially those entered by the user and thus untrusted, is an important task for the programmer. +Latte is not a complete XSS protection for the entire application. We would be unhappy if you stopped thinking about security when using Latte. Latte's goal is to ensure that an attacker cannot alter the page structure, forge HTML elements or attributes. But it does not check the content correctness of the printed data. Nor the correctness of JavaScript behavior. That goes beyond the competence of the templating system. Verifying the correctness of data, especially data entered by the user and therefore untrusted, is an important task for the programmer. diff --git a/latte/en/sandbox.texy b/latte/en/sandbox.texy index e8ed900b5a..230afc9881 100644 --- a/latte/en/sandbox.texy +++ b/latte/en/sandbox.texy @@ -1,12 +1,10 @@ Sandbox ******* -.[perex]{data-version:2.8} -Sandbox provides a security layer that gives you control over which tags, PHP functions, methods, etc. can be used in templates. Thanks to the sandbox mode, you can safely collaborate with a client or external coder on template creation without worrying about compromising the application or unwanted operations. +.[perex] +Sandbox provides a security layer that gives you control over which tags, PHP functions, methods, etc., can be used in templates. Thanks to the sandbox mode, you can safely collaborate with a client or external coder on template creation without worrying about compromising the application or performing unwanted operations. -How does it work? We simply define what we want to allow in the template. In the beginning, everything is forbidden and we gradually grant permissions: - -The following code allows the the template to use the `{block}`, `{if}`, `{else}` and `{=}` tags (the latter is a tag for [printing a variable or expression |tags#Printing]) and all filters: +How does it work? We simply define what we want to allow in the template. Initially, everything is forbidden and we gradually grant permissions. The following code allows the template author to use the `{block}`, `{if}`, `{else}` and `{=}` tags (the latter is a tag for [printing a variable or expression |tags#Printing]) and all filters: ```php $policy = new Latte\Sandbox\SecurityPolicy; @@ -16,7 +14,7 @@ $policy->allowFilters($policy::All); $latte->setPolicy($policy); ``` -We can also allow access to global functions, methods or properties of objects: +We can also allow access to individual global functions, methods, or properties of objects: ```php $policy->allowFunctions(['trim', 'strlen']); @@ -24,30 +22,35 @@ $policy->allowMethods(Nette\Security\User::class, ['isLoggedIn', 'isAllowed']); $policy->allowProperties(Nette\Database\Row::class, $policy::All); ``` -Isn't that amazing? You can control everything at a very low level. If the template tries to call an unauthorized function or access an unauthorized method or property, it throws exception `Latte\SecurityViolationException`. +Isn't that amazing? You can control everything at a very low level. If the template attempts to call a disallowed function or access a disallowed method or property, it throws a `Latte\SecurityViolationException`. -Creating policies from scratch, when everything is forbidden, may not be convenient, so you can start from a safe foundation: +Creating a policy from scratch, where everything is forbidden, might not be convenient, so you can start from a secure baseline: ```php $policy = Latte\Sandbox\SecurityPolicy::createSafePolicy(); ``` -This means that all standard tags are allowed except for `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget`. -All standard filters are allowed as well except for `datastream`, `noescape` and `nocheck`. Finally, access to the methods and properties of object `$iterator` is allowed too. +This secure baseline means that all standard tags are allowed except for `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget`. All standard filters are allowed except for `datastream`, `noescape`, and `nocheck`. Finally, access to the methods and properties of the `$iterator` object is allowed. -The rules apply to the template that we insert with the new [`{sandbox}` |tags#Including Templates] tag. Which is a something like `{include}`, but it turns on sandbox mode and also doesn't pass any external variables: +The rules apply to the template that we insert with the [`{sandbox}` |tags#Including Templates] tag. This is somewhat analogous to `{include}`, but it enables sandbox mode and also does not pass any external variables: ```latte {sandbox 'untrusted.latte'} ``` -Thus, the layout and individual pages can use all tags and variables as before, restrictions will be applied only to the template `untrusted.latte`. +Thus, the layout and individual pages can freely use all tags and variables; restrictions will only be applied to the `untrusted.latte` template. -Some violations, such as the use of a forbidden tag or filter, are detected at compile time. Others, such as calling unallowed methods of an object, at runtime. -The template can also contain any other bugs. In order to prevent an exception from throwing from the sandboxed template, which disrupts the entire rendering, you can define your [own exception handler|develop#exception handler], which, for example, just logs it. +Some violations, like using a forbidden tag or filter, are detected at compile time. Others, like calling disallowed methods of an object, are detected at runtime. The template can also contain any other errors. To prevent an exception from the sandboxed template from disrupting the entire rendering process, you can define your [own exception handler |develop#Exception Handler], which might, for example, just log it. -If we want to turn on sandbox mode directly for all templates, it's easy: +If we wanted to enable sandbox mode directly for all templates, it's easy: ```php $latte->setSandboxMode(); ``` + +To ensure that a user doesn't insert PHP code into the page that is syntactically correct but forbidden and causes a PHP Compile Error, we recommend having [templates checked by the PHP linter |develop#Checking Generated Code]. You can activate this functionality using the `Engine::enablePhpLint()` method. Since it needs to call the PHP binary for the check, pass its path as a parameter: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); +``` diff --git a/latte/en/syntax.texy b/latte/en/syntax.texy index 73cf9ccb8f..ef926e75be 100644 --- a/latte/en/syntax.texy +++ b/latte/en/syntax.texy @@ -2,62 +2,60 @@ Syntax ****** .[perex] -Syntax Latte was born out of the practical requirements of web designers. We were looking for the most user-friendly syntax, with which you can elegantly write constructs that are otherwise a real challenge. -At the same time, all expressions are written exactly the same as in PHP, so you don't have to learn a new language. You just make the most of what you already know. +Latte syntax originated from the practical requirements of web designers. We were looking for the most user-friendly syntax, with which you can elegantly write constructs that are otherwise a real challenge. At the same time, all expressions are written exactly the same as in PHP, so you don't have to learn a new language. You simply leverage what you already know. -Below is a minimal template that illustrates a few basics elements: tags, n:attributes, comments and filters. +Below is a minimal template that illustrates several basic elements: tags, n:attributes, comments, and filters. ```latte {* this is a comment *} -
                                                    {* n:if is n:atribut *} -{foreach $items as $item} {* tag representing foreach loop *} +
                                                      {* n:if is an n:attribute *} +{foreach $items as $item} {* tag representing a foreach loop *}
                                                    • {$item|capitalize}
                                                    • {* tag that prints a variable with a filter *} -{/foreach} {* end of cycle *} +{/foreach} {* end of the loop *}
                                                    ``` -Let's take a closer look at these important elements and how they can help you build an incredible template. +Let's take a closer look at these important elements and how they can help you create an amazing template. Tags ==== -A template contains tags that control the template logic (for example, *foreach* loops) or output expressions. For both, a single delimiter `{ ... }` is used, so you don't have to think about which delimiter to use in which situation, as with other systems. -If the `{` character is followed by a quote or space, Latte doesn't consider it to be the beginning of a tag, so you can use JavaScript constructs, JSON, or CSS rules in your templates without any problems. +A template contains tags that control the template logic (for example, *foreach* loops) or output expressions. A single delimiter `{ ... }` is used for both, so you don't have to think about which delimiter to use in which situation, unlike other systems. If the `{` character is followed by a quote or space, Latte does not consider it the beginning of a tag, allowing you to use JavaScript constructs, JSON, or CSS rules in your templates without problems. -See [overview of all tags|tags]. In addition, you can also create [custom tags|extending-latte#tags]. +See the [overview of all tags|tags]. Additionally, you can create your own [custom tags|custom-tags]. Latte Understands PHP ===================== -You can use PHP expressions that you know well inside the tags: +Inside the tags, you can use PHP expressions that you are familiar with: - variables - strings (including HEREDOC and NOWDOC), arrays, numbers, etc. - [operators |https://www.php.net/manual/en/language.operators.php] -- function and method calls (which can be restricted by [sandbox]) +- function and method calls (which can be restricted by [sandbox|sandbox]) - [match |https://www.php.net/manual/en/control-structures.match.php] -- [anonymous functions |https://www.php.net/manual/en/functions.arrow.php] -- [callbacks |https://www.php.net/manual/en/functions.first_class_callable_syntax.php] +- [arrow functions |https://www.php.net/manual/en/functions.arrow.php] +- [first class callable syntax |https://www.php.net/manual/en/functions.first_class_callable_syntax.php] - multi-line comments `/* ... */` -- etc... +- etc… -In addition, Latte adds several [nice extensions |#Syntactic Sugar] to the PHP syntax. +Furthermore, Latte enhances PHP syntax with several [nice extensions |#Syntactic Sugar]. n:attributes ============ -Each pair tag, such as `{if} … {/if}`, operating upon single HTML element can be written in [n:attribute |#n:attribute] notation. For example, `{foreach}` in the above example could also be written this way: +Every pair tag, such as `{if} … {/if}`, operating on a single HTML element, can be rewritten in the form of n:attributes. For example, the `{foreach}` in the introductory example could also be written this way: ```latte -
                                                      +
                                                      • {$item|capitalize}
                                                      ``` -The functionality then corresponds to the HTML element in which it is written: +The functionality then applies to the HTML element in which it is placed: ```latte {var $items = ['I', '♥', 'Latte']} @@ -65,7 +63,7 @@ The functionality then corresponds to the HTML element in which it is written:

                                                      {$item}

                                                      ``` -Prints: +outputs: ```latte

                                                      I

                                                      @@ -73,7 +71,7 @@ Prints:

                                                      Latte

                                                      ``` -By using `inner-` prefix we can alter the behavior so that the functionality applies only to the body of the element: +Using the `inner-` prefix, we can modify the behavior so that it applies only to the inner part of the element: ```latte
                                                      @@ -82,7 +80,7 @@ By using `inner-` prefix we can alter the behavior so that the functionality app
                                                      ``` -Prints: +Outputs: ```latte
                                                      @@ -95,65 +93,114 @@ Prints:
                                                      ``` -Or by using `tag-` prefix the functionality is applied on the HTML tags only: +Or, using the `tag-` prefix, we apply the functionality only to the HTML tags themselves: ```latte -

                                                      Title

                                                      +

                                                      Title

                                                      ``` -Depending on the value of `$url` variable this will print: +Which outputs, depending on the variable `$url`: ```latte -// when $url is empty +{* when $url is empty *}

                                                      Title

                                                      -// when $url equals 'https://nette.org' +{* when $url contains 'https://nette.org' *}

                                                      Title

                                                      ``` -However, n:attributes are not only a shortcut for pair tags, there are some pure n:attributes as well, for example the coder's best friend [n:class|tags#n:class]. +However, n:attributes are not only a shortcut for pair tags, there are some pure n:attributes as well, for example the coder's best friend [n:class|tags#n:class] or the very handy [n:href |application:creating-links#In the Presenter Template]. + +In addition to the syntax using quotes `
                                                      `, you can use alternative syntax with curly braces `
                                                      `. The main advantage is that you can freely use both single and double quotes inside `{...}`: + +```latte +
                                                      ...
                                                      +``` + + +Smart HTML Attributes .{data-version:3.1} +========================================= + +Latte makes working with standard HTML attributes incredibly easy. It handles boolean attributes like `checked` for you, removes attributes containing `null`, and allows you to compose `class` and `style` values using arrays. It even automatically serializes data for `data-` attributes into JSON. + +```latte +{* null removes the attribute *} +
                                                      + +{* boolean controls presence of boolean attributes *} + + +{* arrays work in class *} +
                                                      $isActive]}> + +{* arrays are JSON-encoded in data- attributes *} +
                                                      +``` + +Read more in the separate chapter [Smart HTML Attributes|html-attributes]. Filters ======= -See the summary of [standard filters|filters]. +See the overview of [standard filters |filters]. -Latte allows calling filters by using the pipe sign notation (preceding space is allowed): +Filters are written after the pipe symbol (a preceding space is allowed): ```latte

                                                      {$heading|upper}

                                                      ``` -Filters can be chained, in that case they apply in order from left to right: +Filters can be chained, and they are applied in order from left to right: ```latte

                                                      {$heading|lower|capitalize}

                                                      ``` -Parameters are put after the filter name separated by colon or comma: +Parameters are entered after the filter name, separated by colons or commas: ```latte

                                                      {$heading|truncate:20,''}

                                                      ``` -Filters can be applied on expression: +Filters can also be applied to an expression: ```latte {var $name = ($title|upper) . ($subtitle|lower)} ``` -On block: +On a block: ```latte

                                                      {block |lower}{$heading}{/block}

                                                      ``` -Or directly on value (in combination with [`{=expr}`| https://latte.nette.org/en/tags#printing] tag): +Or directly on a value (in combination with the [`{=expr}` |tags#Printing] tag): + ```latte

                                                      {=' Hello world '|trim}

                                                      ``` +If the value can be `null` and you want to avoid applying the filter in that case, use the [nullsafe filter |filters#Nullsafe Filters] `?|`: + +```latte +

                                                      {$heading?|upper}

                                                      +``` + + +Dynamic HTML Tags .{data-version:3.0.9} +======================================= + +Latte supports dynamic HTML tags, which are useful when you need flexibility in tag names: + +```latte +Heading +``` + +For example, the code above can generate `

                                                      Heading

                                                      ` or `

                                                      Heading

                                                      ` depending on the value of the variable `$level`. Dynamic HTML tags in Latte must always be paired. Their alternative is [n:tag |tags#n:tag]. + +Because Latte is a secure templating system, it checks that the resulting tag name is valid and does not contain any unwanted or malicious values. It also ensures that the end tag name always matches the opening tag name. + Comments ======== @@ -186,9 +233,19 @@ as in PHP: {var $arr = ['hello', 'btn--default', '€']} abbreviated: {var $arr = [hello, btn--default, €]} ``` -Simple strings are those that are made up purely of letters, digits, underscores, hyphens and periods. They must not begin with a digit and must not begin or end with a hyphen. -It must not be composed of only uppercase letters and underscores, because then it is considered a constant (e.g. `PHP_VERSION`). -And it must not collide with the keywords `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor`. +Simple strings are those composed purely of letters, digits, underscores, hyphens, and periods. They must not start with a digit and must not start or end with a hyphen. They must not consist solely of uppercase letters and underscores, as they are then considered constants (e.g., `PHP_VERSION`). And they must not conflict with the keywords: `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor`. + + +Constants +--------- + +Use the global namespace separator to distinguish global constants from simple strings: + +```latte +{if \PROJECT_ID === 1} ... {/if} +``` + +This notation is entirely valid in PHP itself, where the slash indicates that the constant is in the global namespace. Short Ternary Operator @@ -203,8 +260,8 @@ abbreviated: {$stock ? 'In stock'} ``` -Modern Key Notation in the Array --------------------------------- +Modern Key Notation in Arrays +----------------------------- Array keys can be written similarly to named parameters when calling functions: @@ -218,7 +275,7 @@ modern: {var $arr = [one: 'item 1', two: 'item 2']} Filters ------- -Filters can be used for any expression, just enclose the whole in brackets: +Filters can be used for any expression; just enclose the whole expression in parentheses: ```latte {var $content = ($text|truncate: 30|upper)} @@ -228,7 +285,7 @@ Filters can be used for any expression, just enclose the whole in brackets: Operator `in` ------------- -The `in` operator can be used to replace the `in_array()` function. The comparison is always strict: +The `in` operator can replace the `in_array()` function. The comparison is always strict: ```latte {* like in_array($item, $items, true) *} @@ -238,35 +295,15 @@ The `in` operator can be used to replace the `in_array()` function. The comparis ``` -.{data-version:2.9} -Optional Chaining with Undefined-Safe Operator ----------------------------------------------- - -The undefined-safe operator `??->` is similar to the nullsafe operator `?->`, but does not raise an error if a variable, property, or index does not exist at all. - -```latte -{$order??->id} -``` - -this is a way of saying that when `$order` is defined and not null, `$order->id` will be computed, but when `$order` is null or doesn't exist, stop what we’re doing and just return null. - -```latte -{$user??->address??->street} -// roughly means isset($user) && isset($user->address) ? $user->address->street : null -``` - - A Window into History --------------------- -Latte has come up with a number of syntactic candies over the course of its history, which appeared in PHP itself a few years later. For example, in Latte it was possible to write arrays as `[1, 2, 3]` instead of `array(1, 2, 3)` or use the nullsafe operator `$obj?->foo` long before it was possible in PHP itself. Latte also introduced the array expansion operator `(expand) $arr`, which is the equivalent of today's `...$arr` operator from PHP. +Over its history, Latte introduced several syntactic sugar features that appeared in PHP itself a few years later. For example, in Latte, it was possible to write arrays as `[1, 2, 3]` instead of `array(1, 2, 3)` or use the nullsafe operator `$obj?->foo` long before it was possible in PHP itself. Latte also introduced the array expansion operator `(expand) $arr`, which is equivalent to today's `...$arr` operator from PHP. PHP Limitations in Latte ======================== -Only PHP expressions can be written in Latte. That is, you cannot declare classes or use [control structures |https://www.php.net/manual/en/language.control-structures.php], such as `if`, `foreach`, `switch`, `return`, `try`, `throw` and others, instead of which Latte offers its [tags]. -You also can't use [attributes |https://www.php.net/manual/en/language.attributes.php], [backticks |https://www.php.net/manual/en/language.operators.execution.php] or [magic constants |https://www.php.net/manual/en/language.constants.magic.php], because that wouldn't make sense. -You can't even use `unset`, `echo`, `include`, `require`, `exit`, `eval`, because they are not functions, but special PHP language constructs, and thus not expressions. +Only PHP expressions can be written in Latte. That is, statements ending with a semicolon cannot be used. You cannot declare classes or use [control structures |https://www.php.net/manual/en/language.control-structures.php], such as `if`, `foreach`, `switch`, `return`, `try`, `throw`, and others, for which Latte offers its [tags|tags]. You also cannot use [attributes |https://www.php.net/manual/en/language.attributes.php], [backticks |https://www.php.net/manual/en/language.operators.execution.php], or some [magic constants |https://www.php.net/manual/en/language.constants.magic.php]. You cannot use `unset`, `echo`, `include`, `require`, `exit`, `eval` either, because they are not functions but special PHP language constructs, and thus not expressions. Only multi-line comments `/* ... */` are supported. -However, you can work around these limitations by activating the [RawPhpExtension |develop#RawPhpExtension] extension, which allows you to use any PHP code in the `{php ...}` tag at the responsibility of the template author. +However, these limitations can be bypassed by activating the [RawPhpExtension |develop#RawPhpExtension] extension, which allows you to use any PHP code within the `{php ...}` tag at the template author's responsibility. diff --git a/latte/en/tags.texy b/latte/en/tags.texy index 5c27d2d5c8..cfbc94ae6f 100644 --- a/latte/en/tags.texy +++ b/latte/en/tags.texy @@ -2,114 +2,121 @@ Latte Tags ********** .[perex] -Summary and description of all Latte built-in tags. +An overview and description of all the tags available by default in the Latte templating system. .[table-latte-tags language-latte] |## Printing -| `{$var}`, `{...}` or `{=...}` | [prints an escaped variable or expression |#printing] -| `{$var\|filter}` | [prints with filters |#filters] -| `{l}` or `{r}` | prints `{` or `}` character +| `{$var}`, `{...}` or `{=...}` | [prints an escaped variable or expression |#Printing] +| `{$var\|filter}` | [prints with filters applied |#Filters] +| `{l}` or `{r}` | prints the `{` or `}` character .[table-latte-tags language-latte] |## Conditions -| `{if}` … `{elseif}` … `{else}` … `{/if}` | [condition if|#if-elseif-else] -| `{ifset}` … `{elseifset}` … `{/ifset}` | [condition ifset|#ifset-elseifset] -| `{ifchanged}` … `{/ifchanged}` | [test if there has been a change|#ifchanged] -| `{switch}` `{case}` `{default}` `{/switch}` | [condition switch|#switch-case-default] +| `{if}` … `{elseif}` … `{else}` … `{/if}` | [if condition |#if elseif else] +| `{ifset}` … `{elseifset}` … `{/ifset}` | [ifset condition |#ifset elseifset] +| `{ifchanged}` … `{/ifchanged}` | [tests if a value has changed |#ifchanged] +| `{switch}` `{case}` `{default}` `{/switch}` | [switch condition |#switch case default] +| `n:else`, `n:elseif` | [alternative content for conditions |#n:else] .[table-latte-tags language-latte] |## Loops | `{foreach}` … `{/foreach}` | [#foreach] | `{for}` … `{/for}` | [#for] | `{while}` … `{/while}` | [#while] -| `{continueIf $cond}` | [continue to next iteration |#continueif-skipif-breakif] -| `{skipIf $cond}` | [skip the current loop iteration |#continueif-skipif-breakif] -| `{breakIf $cond}` | [breaks loop |#continueif-skipif-breakif] -| `{exitIf $cond}` | [early exit |#exitif] -| `{first}` … `{/first}` | [is it the first iteration? |#first-last-sep] -| `{last}` … `{/last}` | [is it the last iteration? |#first-last-sep] -| `{sep}` … `{/sep}` | [will next iteration follow? |#first-last-sep] -| `{iterateWhile}` … `{/iterateWhile}` | [structured foreach|#iterateWhile] -| `$iterator` | [special variable inside foreach loop |#$iterator] +| `{continueIf $cond}` | [continue to the next iteration |#continueIf skipIf breakIf] +| `{skipIf $cond}` | [skip the current loop iteration |#continueIf skipIf breakIf] +| `{breakIf $cond}` | [break the loop |#continueIf skipIf breakIf] +| `{exitIf $cond}` | [early exit |#exitIf] +| `{first}` … `{/first}` | [is it the first iteration? |#first last sep] +| `{last}` … `{/last}` | [is it the last iteration? |#first last sep] +| `{sep}` … `{/sep}` | [will the next iteration follow? |#first last sep] +| `{iterateWhile}` … `{/iterateWhile}` | [structured foreach |#iterateWhile] +| `$iterator` | [special variable inside the foreach loop |#iterator] .[table-latte-tags language-latte] -|## Including other Templates -| `{include 'file.latte'}` | [includes a template from other file |#include] +|## Including Other Templates +| `{include 'file.latte'}` | [includes a template from another file |#include] | `{sandbox 'file.latte'}` | [includes a template in sandbox mode |#sandbox] .[table-latte-tags language-latte] -|## Blocks, layouts, template inheritance -| `{block}` | [anonymous block|#block] -| `{block blockname}` | [block definition |template-inheritance#blocks] -| `{define blockname}` | [block defintion for future use |template-inheritance#definitions] -| `{include blockname}` | [prints block |template-inheritance#printing-blocks] -| `{include blockname from 'file.latte'}` | [prints a block from file |template-inheritance#printing-blocks] -| `{import 'file.latte'}` | [loads blocks from another template |template-inheritance#horizontal-reuse] -| `{layout 'file.latte'}` / `{extends}` | [specifies a layout file |template-inheritance#layout-inheritance] -| `{embed}` … `{/embed}` | [loads the template or block and allows you to overwrite the blocks |template-inheritance#unit-inheritance] -| `{ifset blockname}` … `{/ifset}` | [condition if block is defined |template-inheritance#checking-block-existence] +|## Blocks, Layouts, Template Inheritance +| `{block}` | [anonymous block |#block] +| `{block blockname}` | [defines a block |template-inheritance#Blocks] +| `{define blockname}` | [defines a block for later use |template-inheritance#Definitions] +| `{include blockname}` | [renders a block |template-inheritance#Printing Blocks] +| `{include blockname from 'file.latte'}` | [renders a block from a file |template-inheritance#Printing Blocks] +| `{import 'file.latte'}` | [imports blocks from a template |template-inheritance#Horizontal Reuse] +| `{layout 'file.latte'}` / `{extends}` | [specifies a layout file |template-inheritance#Layout Inheritance] +| `{embed}` … `{/embed}` | [embeds a template or block and allows overriding blocks |template-inheritance#Unit Inheritance] +| `{ifset blockname}` … `{/ifset}` | [condition checking if a block exists |template-inheritance#Checking Block Existence] .[table-latte-tags language-latte] -|## Exception handling +|## Exception Handling | `{try}` … `{else}` … `{/try}` | [catching exceptions |#try] -| `{rollback}` | [discards try block |#rollback] +| `{rollback}` | [discards the try block |#rollback] .[table-latte-tags language-latte] |## Variables | `{var $foo = value}` | [variable creation |#var-default] -| `{default $foo = value}` | [default value when variable isn't declared |#var-default] -| `{parameters}` | [declares variables, types a default values |#parameters] -| `{capture}` … `{/capture}` | [captures a section to a variable |#capture] +| `{default $foo = value}` | [creates a variable if it doesn't exist |#var-default] +| `{parameters}` | [declares variables, types, and default values |#parameters] +| `{capture}` … `{/capture}` | [captures output into a variable |#capture] .[table-latte-tags language-latte] |## Types -| `{varType}` | [declares type of variable |type-system#varType] -| `{varPrint}` | [suggests types of variables |type-system#varPrint] -| `{templateType}` | [declares types of variables using class |type-system#templateType] -| `{templatePrint}` | [generates class with properties |type-system#templatePrint] +| `{varType}` | [declares the type of a variable |type-system#varType] +| `{varPrint}` | [suggests variable types |type-system#varPrint] +| `{templateType}` | [declares variable types based on a class |type-system#templateType] +| `{templatePrint}` | [suggests a class with variable types |type-system#templatePrint] .[table-latte-tags language-latte] |## Translation -| `{_string}` | [prints translated |#Translation] +| `{_...}` | [prints translation |#Translation] | `{translate}` … `{/translate}` | [translates the content |#Translation] .[table-latte-tags language-latte] |## Others -| `{contentType}` | [switches the escaping mode and sends HTTP header |#contenttype] -| `{debugbreak}` | [sets breakpoint to the code |#debugbreak] -| `{do}` | [evaluates an expression without printing it |#do] +| `{contentType}` | [switches escaping and sends HTTP header |#contentType] +| `{debugbreak}` | [places a breakpoint in the code |#debugbreak] +| `{do}` | [executes code without printing anything |#do] | `{dump}` | [dumps variables to the Tracy Bar |#dump] -| `{spaceless}` … `{/spaceless}` | [removes unnecessary whitespace|#spaceless] -| `{syntax}` | [switches the syntax at runtime |#syntax] -| `{trace}` | [shows stack trace |#trace] +| `{php}` | [executes any PHP code |#php] +| `{spaceless}` … `{/spaceless}` | [removes unnecessary whitespace |#spaceless] +| `{syntax}` | [changes syntax at runtime |#syntax] +| `{trace}` | [displays stack trace |#trace] .[table-latte-tags language-latte] -|## HTML tag helpers -| `n:class` | [smart class attribute |#n:class] -| `n:attr` | [smart HTML attributes |#n:attr] -| `n:tag` | [dynamic name of HTML element |#n:tag] -| `n:ifcontent` | [Omit empty HTML tag |#n:ifcontent] +|## HTML Coder Helpers +| `n:class` | [dynamic HTML class attribute |#n:class] +| `n:attr` | [dynamic HTML attributes |#n:attr] +| `n:tag` | [dynamic HTML element name |#n:tag] +| `n:ifcontent` | [omits empty HTML tag |#n:ifcontent] .[table-latte-tags language-latte] |## Available only in Nette Framework -| `n:href` | [link in `` HTML elements |application:creating-links#In the Presenter Template] +| `n:href` | [link used in `` HTML elements |application:creating-links#In the Presenter Template] | `{link}` | [prints a link |application:creating-links#In the Presenter Template] | `{plink}` | [prints a link to a presenter |application:creating-links#In the Presenter Template] -| `{control}` | [prints a component |application:components#Rendering] -| `{snippet}` … `{/snippet}` | [a template snippet that can be sent by AJAX |application:ajax#tag-snippet] -| `{snippetArea}` | snippets envelope -| `{cache}` … `{/cache}` | [caches a template section |caching:#caching-in-latte] +| `{linkBase}` | [changing link base |application:creating-links#Changing Link Base] +| `{control}` | [renders a component |application:components#Rendering] +| `{snippet}` … `{/snippet}` | [a template snippet that can be sent via AJAX |application:ajax#Snippets in Latte] +| `{snippetArea}` | [snippet wrapper |application:ajax#Snippet Areas] +| `{cache}` … `{/cache}` | [caches a part of the template |caching:#Caching in Latte] .[table-latte-tags language-latte] |## Available only with Nette Forms -| `{form}` … `{/form}` | [prints a form element |forms:rendering#form] -| `{label}` … `{/label}` | [prints a form input label |forms:rendering#label-input] -| `{input}` | [prints a form input element |forms:rendering#label-input] -| `{inputError}` | [prints error message for form input element|forms:rendering#inputError] -| `n:name` | [activates an HTML input element |forms:rendering#n:name] -| `{formPrint}` | [generates Latte form blueprint |forms:rendering#formPrint] -| `{formPrintClass}` | [prints PHP class for form data |forms:in-presenter#mapping-to-classes] -| `{formContext}` … `{/formContext}` | [partial form rendering |forms:rendering#special-cases] +| `{form}` … `{/form}` | [renders form tags |forms:rendering#form] +| `{label}` … `{/label}` | [renders a form control label |forms:rendering#label input] +| `{input}` | [renders a form control |forms:rendering#label input] +| `{inputError}` | [prints the error message of a form control |forms:rendering#inputError] +| `n:name` | [activates a form control |forms:rendering#n:name] +| `{formContainer}` … `{/formContainer}` | [renders a form container |forms:rendering#Special Cases] + +.[table-latte-tags language-latte] +|## Available only with Nette Assets +| `{asset}` | [renders an asset as HTML element or URL |assets:#asset] +| `{preload}` | [generates preload hints for performance optimization |assets:#preload] +| `n:asset` | [adds asset attributes to HTML elements |assets:#n:asset] Printing @@ -119,35 +126,35 @@ Printing `{$var}` `{...}` `{=...}` ------------------------- -Latte uses the `{=...}` tag to print any expression to the output. If the expression starts with a variable or function call, there is no need to write an equal sign. Which in practice means that it almost never needs to be written: +In Latte, the `{=...}` tag is used to print any expression to the output. Latte cares about your comfort, so if the expression starts with a variable or a function call, there's no need to write the equals sign. Which in practice means it almost never needs to be written: ```latte Name: {$name} {$surname}
                                                      Age: {date('Y') - $birth}
                                                      ``` -You can write anything you know from PHP as an expression. You just don't have to learn a new language. For example: +You can write anything you know from PHP as an expression. You simply don't have to learn a new language. For example: ```latte {='0' . ($num ?? $num * 3) . ', ' . PHP_VERSION} ``` -Please don't look for any meaning in the previous example, but if you find one there, write to us :-) +Please don't look for any meaning in the previous example, but if you find one, let us know :-) -Escaping Output +Output Escaping --------------- -What is the most important task of a template system? To avoid security holes. And that's exactly what Latte does whenever you print something to output. It automatically escapes everything: +What is the most important task of a templating system? To prevent security vulnerabilities. And that's exactly what Latte does whenever you print something. It automatically escapes it: ```latte

                                                      {='one < two'}

                                                      {* prints: '

                                                      one < two

                                                      ' *} ``` -To be precise, Latte uses context-sensitive escaping, which is such an important and unique feature that we've devoted [a separate chapter to it|safety-first#context-aware-escaping]. +To be precise, Latte uses context-aware escaping, which is such an important and unique feature that we've dedicated [a separate chapter to it |safety-first#Context-Aware Escaping]. -And if you print HTML-encoded content from a trusted source? Then you can easily turn off escaping: +And what if you're printing HTML-encoded content from a trusted source? Then you can easily disable escaping: ```latte {$trustedHtmlString|noescape} @@ -160,9 +167,9 @@ Misuse of the `noescape` filter can lead to an XSS vulnerability! Never use it u Printing in JavaScript ---------------------- -Thanks to context-sensitive escaping, it is wonderfully easy to print variables inside JavaScript, and Latte will properly escape them. +Thanks to context-aware escaping, it's wonderfully easy to print variables inside JavaScript, and Latte will handle the proper escaping. -The variable does not have to be a string, any data type is supported, which is then encoded as JSON: +The variable doesn't have to be a string; any data type is supported, which is then encoded as JSON: ```latte {var $foo = ['hello', true, 1]} @@ -179,7 +186,7 @@ Generates: ``` -This is also the reason why **do not put variable in quotes**: Latte adds them around strings. And if you want to put a string variable into another string, simply concatenate them: +This is also why you **should not write quotes** around the variable: Latte adds them for strings automatically. And if you want to insert a string variable into another string, simply concatenate them: ```latte ``` -Latte can be used very comfortably inside JavaScript, just avoid constructs like in this example, where the letter immediately follows `{`, see [Latte inside JavaScript or CSS|recipes#Latte inside JavaScript or CSS]. +Latte can be used very comfortably inside JavaScript, just avoid constructs like in this example, where a letter immediately follows `{`, see [Latte inside JavaScript or CSS |recipes#Latte Inside JavaScript or CSS]. -If you turn off Latte with the `{syntax off}` (ie tag, not the n:attribute), it will strictly ignore all tags up to `{/syntax}`. +If you disable Latte using `{syntax off}` (i.e., the tag, not the n:attribute), it will strictly ignore all tags up to `{/syntax}`. -{trace} .{data-version:2.10} ----------------------------- +`{trace}` +--------- -Throws an `Latte\RuntimeException` exception, whose stack trace is in the spirit of the templates. Thus, instead of calling functions and methods, it involves calling blocks and inserting templates. If you use a tool for clearly displaying thrown exceptions, such as [Tracy|tracy:], you will clearly see the call stack, including all passed arguments. +Throws a `Latte\RuntimeException` exception, whose stack trace follows the spirit of templates. Thus, instead of function and method calls, it involves block calls and template inclusions. If you use a tool for clear display of thrown exceptions, such as [Tracy|tracy:], you will clearly see the call stack, including all passed arguments. -HTML Tag Helpers -================ +HTML Coder Helpers +================== -n:class -------- +`n:class` +--------- + +.[note] +Since Latte 3.1, the standard HTML class attribute has gained the [same functionality |html-attributes#classes]. So you don't need to use n:class anymore -Thanks to `n:class`, it is very easy to generate the HTML attribute `class` exactly as you need. +Thanks to `n:class`, it's very easy to generate the HTML `class` attribute exactly as needed. -Example: I need the active element to have the `active` class: +Example: I need the active element to have the class `active`: ```latte {foreach $items as $item} @@ -931,7 +962,7 @@ Example: I need the active element to have the `active` class: {/foreach} ``` -And I further need that the first element have the classes `first` and `main`: +And further, I need the first element to have the classes `first` and `main`: ```latte {foreach $items as $item} @@ -939,7 +970,7 @@ And I further need that the first element have the classes `first` and `main`: {/foreach} ``` -And all elements should have the `list-item` class: +And all elements should have the class `list-item`: ```latte {foreach $items as $item} @@ -950,10 +981,10 @@ And all elements should have the `list-item` class: Amazingly simple, isn't it? -n:attr ------- +`n:attr` +-------- -The `n:attr` attribute can generate arbitrary HTML attributes with the same elegance as [n:class|#n:class]. +The `n:attr` attribute can generate arbitrary HTML attributes with the same elegance as [#n:class]. ```latte {foreach $data as $item} @@ -961,7 +992,7 @@ The `n:attr` attribute can generate arbitrary HTML attributes with the same eleg {/foreach} ``` -Depending on the returned values, it displays eg: +Depending on the returned values, it prints, for example: ```latte @@ -971,9 +1002,15 @@ Depending on the returned values, it displays eg: ``` +Smart attribute features in Latte 3.1, such as dropping `null` values or passing arrays into `class` or `style`, also work within `n:attr`: + +```latte +
                                                      +``` -n:tag .{data-version:2.10} --------------------------- + +`n:tag` +------- The `n:tag` attribute can dynamically change the name of an HTML element. @@ -981,17 +1018,19 @@ The `n:tag` attribute can dynamically change the name of an HTML element.

                                                      {$title}

                                                      ``` -If `$heading === null`, the `

                                                      ` tag is printed without change. Otherwise, the element name is changed to the value of the variable, so for `$heading === 'h3'` it writes: +If `$heading === null`, the `

                                                      ` tag is printed without change. Otherwise, the element name is changed to the value of the variable, so for `$heading === 'h3'`, it writes: ```latte

                                                      ...

                                                      ``` +Because Latte is a secure templating system, it checks that the new tag name is valid and does not contain any unwanted or malicious values. -n:ifcontent ------------ -Prevents an empty HTML element from being printed, ie an element containing nothing but whitespace. +`n:ifcontent` +------------- + +Prevents an empty HTML element from being printed, i.e., an element containing nothing but whitespace. ```latte
                                                      @@ -999,7 +1038,7 @@ Prevents an empty HTML element from being printed, ie an element containing noth
                                                      ``` -Depending on the values of the variable `$error` this will print: +Depending on the value of the variable `$error`, this will print: ```latte {* $error = '' *} @@ -1013,10 +1052,10 @@ Depending on the values of the variable `$error` this will print: ``` -Translation .{data-version:3.0} -=============================== +Translation +=========== -To make the translation tags work, you need to [set up translator|develop#TranslatorExtension]. You can also use the [`translate`|filters#translate] filter for translation. +For the translation tags to work, you need to [activate the translator |develop#TranslatorExtension]. You can also use the [`translate` |filters#translate] filter for translation. `{_...}` @@ -1039,7 +1078,7 @@ Other parameters can also be passed to the translator: `{translate}` ------------- -Překládá části šablony: +Translates parts of the template: ```latte

                                                      {translate}Order{/translate}

                                                      @@ -1047,7 +1086,7 @@ Překládá části šablony: {translate domain: order}Lorem ipsum ...{/translate} ``` -The tag can also be written as [n:attribute|syntax#n:attributes], to translate the inside of the element: +The tag can also be written as an [n:attribute |syntax#n:attributes], to translate the inside of the element: ```latte

                                                      Order

                                                      diff --git a/latte/en/template-inheritance.texy b/latte/en/template-inheritance.texy index 7d3e10002b..70902ee4ac 100644 --- a/latte/en/template-inheritance.texy +++ b/latte/en/template-inheritance.texy @@ -2,15 +2,15 @@ Template Inheritance and Reusability ************************************ .[perex] -Template reusability and inheritance mechanisms are here to boosts your productivity because each template contains only its unique contents and the repeated elements and structures are reused. We introduce three concepts: [#layout inheritance], [#horizontal reuse] and [#unit inheritance]. +Template reusability and inheritance mechanisms are here to boost your productivity because each template contains only its unique content, and repeated elements and structures are reused. We introduce three concepts: [#layout-inheritance], [#horizontal-reuse], and [#unit-inheritance]. -The concept of Latte template inheritance is similar to PHP class inheritance. You define a **parent template** that other **child templates** can extend from and can override parts of the parent template. It works great when elements share a common structure. Sounds complicated? Don't worry, it's not. +The concept of Latte template inheritance is similar to PHP class inheritance. You define a **parent template** from which other **child templates** can inherit and can override parts of the parent template. It works great when elements share a common structure. Sound complicated? Don't worry, it's very easy. Layout Inheritance `{layout}` .{toc: Layout Inheritance} ======================================================== -Let’s look at layout template inheritance by starting with an example. This is a parent template which we’ll call for example `layout.latte` and it defines an HTML skeleton document. +Let's look at layout template inheritance with an example. This is a parent template, let's call it `layout.latte`, which defines the skeleton of an HTML document: ```latte @@ -30,7 +30,7 @@ Let’s look at layout template inheritance by starting with an example. This is ``` -The `{block}` tags defines three blocks that child templates can fill in. All the block tag does is to tell the template engine that a child template may override those portions of the template by defining their own block of the same name. +The `{block}` tags define three blocks that child templates can fill. All the block tag does is tell the template engine that a child template may override these portions by defining its own block with the same name. A child template might look like this: @@ -44,11 +44,11 @@ A child template might look like this: {/block} ``` -The `{layout}` tag is the key here. It tells the template engine that this template “extends” another template. When Latte renderes this template, first it locates the parent – in this case, `layout.latte`. +The `{layout}` tag is key here. It tells Latte that this template "extends" another template. When Latte renders this template, it first locates the parent template - in this case, `layout.latte`. -At that point, the template engine will notice the three block tags in `layout.latte` and replace those blocks with the contents of the child template. Note that since the child template didn’t define the *footer* block, the contents from the parent template is used instead. Content within a `{block}` tag in a parent template is always used as a fallback. +At this point, Latte notices the three block tags in `layout.latte` and replaces these blocks with the content of the child template. Since the child template did not define the *footer* block, the content from the parent template is used instead. Content within a `{block}` tag in the parent template is always used as a fallback. -The output might look like: +The output might look like this: ```latte @@ -68,7 +68,7 @@ The output might look like: ``` -In a child template, blocks can only be located either at the top level or inside another block, ie: +In a child template, blocks can only be located at the top level or inside another block, i.e.: ```latte {block content} @@ -76,7 +76,7 @@ In a child template, blocks can only be located either at the top level or insid {/block} ``` -Also a block will always be created in regardless of whether the surrounding `{if}` condition is evaluated to be true or false. Contrary to what you might think, this template does define a block. +Also, a block will always be created regardless of whether the surrounding `{if}` condition is evaluated as true or false. So, even though it might not seem like it, this template does define the block. ```latte {if false} @@ -86,7 +86,7 @@ Also a block will always be created in regardless of whether the surrounding `{i {/if} ``` -If you want the output inside block to be displayed conditionally, use the following instead: +If you want the output inside the block to be displayed conditionally, use the following instead: ```latte {block head} @@ -96,7 +96,7 @@ If you want the output inside block to be displayed conditionally, use the follo {/block} ``` -Data outside of a blocks in a child template are executed before the layout template is rendered, thus you can use it to define variables like `{var $foo = bar}` and propagate data to the whole inheritance chain: +Code outside blocks in the child template is executed before rendering the layout template, so you can use it to define variables like `{var $foo = bar}` and propagate data throughout the inheritance chain: ```latte {layout 'layout.latte'} @@ -110,9 +110,9 @@ Multilevel Inheritance ---------------------- You can use as many levels of inheritance as needed. One common way of using layout inheritance is the following three-level approach: -1) Create a `layout.latte` template that holds the main look-and-feel of your site. -2) Create a `layout-SECTIONNAME.latte` template for each section of your site. For example, `layout-news.latte`, `layout-blog.latte` etc. These templates all extend `layout.latte` and include section-specific styles/design. -3) Create individual templates for each type of page, such as a news article or blog entry. These templates extend the appropriate section template. +1) Create a `layout.latte` template that holds the main look and feel of your site. +2) Create a `layout-SECTIONNAME.latte` template for each section of your site. For example, `layout-news.latte`, `layout-blog.latte`, etc. All these templates extend `layout.latte` and include styles & design specific to each section. +3) Create individual templates for each type of page, such as a news article or blog post. These templates extend the appropriate section template. Dynamic Layout Inheritance @@ -123,7 +123,7 @@ You can use a variable or any PHP expression as the name of the parent template, {layout $standalone ? 'minimum.latte' : 'layout.latte'} ``` -You can also use the Latte API to choose layout template [automatically|develop#automatic-layout-lookup]. +You can also use the Latte API to choose the layout template [automatically |develop#Automatic Layout Lookup]. Tips @@ -132,13 +132,13 @@ Here are some tips for working with layout inheritance: - If you use `{layout}` in a template, it must be the first template tag in that template. -- Layout can be [searched automatically |develop#automatic-layout-lookup] (like in [presenters |application:templates#search-for-templates]). In this case, if the template should not have a layout, it will indicate this with the `{layout none}` tag. +- The layout can be [found automatically |develop#Automatic Layout Lookup] (like in [presenters |application:templates#Template Lookup]). In this case, if the template should not have a layout, it indicates this with the `{layout none}` tag. -- Tag `{layout}` has alias `{extends}`. +- The `{layout}` tag has an alias `{extends}`. -- The filename of the extended template depends on the [template loader|extending-latte#Loaders]. +- The layout file name depends on the [loader |loaders]. -- You can have as many blocks as you want. Remember, child templates don’t have to define all parent blocks, so you can fill in reasonable defaults in a number of blocks, then only define the ones you need later. +- You can have as many blocks as you want. Remember that child templates do not have to define all parent blocks, so you can fill in reasonable defaults in several blocks and then define only those you need later. Blocks `{block}` .{toc: Blocks} @@ -147,10 +147,9 @@ Blocks `{block}` .{toc: Blocks} .[note] See also anonymous [`{block}` |tags#block] -A block provides a way to change how a certain part of a template is rendered but it does not interfere in any way with the logic around it. Let’s take the following example to illustrate how a block works and more importantly, how it does not work: +A block provides a way to change how a certain part of a template is rendered, but it does not interfere in any way with the logic around it. Let's illustrate how a block works, and more importantly, how it doesn't work, with the following example: -```latte -{* parent.Latte *} +```latte .{file: parent.latte} {foreach $posts as $post} {block post}

                                                      {$post->title}

                                                      @@ -159,10 +158,9 @@ A block provides a way to change how a certain part of a template is rendered bu {/foreach} ``` -If you render this template, the result would be exactly the same with or without the block tags. Blocks have access to variables from outer scopes. It is just a way to make it overridable by a child template: +If you render this template, the result will be exactly the same with or without the `{block}` tags. Blocks have access to variables from outer scopes. They just provide a way to be overridden by a child template: -```latte -{* child.Latte *} +```latte .{file: child.latte} {layout 'parent.Latte'} {block post} @@ -173,7 +171,7 @@ If you render this template, the result would be exactly the same with or withou {/block} ``` -Now, when rendering the child template, the loop is going to use the block defined in the child template `child.Latte` instead of the one defined in the base one `parent.Latte`; the executed template is then equivalent to the following one: +Now, when rendering the child template, the loop will use the block defined in the child template `child.Latte` instead of the one defined in `parent.Latte`; the executed template is then equivalent to the following: ```latte {foreach $posts as $post} @@ -184,7 +182,7 @@ Now, when rendering the child template, the loop is going to use the block defin {/foreach} ``` -However, if we create a new variable inside a named block or replace a value of existing one, the change will be visible only inside the block: +However, if we create a new variable inside a named block or replace the value of an existing one, the change will only be visible inside the block: ```latte {var $foo = 'foo'} @@ -197,13 +195,13 @@ foo: {$foo} // prints: foo bar: {$bar ?? 'not defined'} // prints: not defined ``` -Contents of block can be modified by [filters|syntax#filters]. The following example removes all HTML and title-cases it: +The content of a block can be modified by [filters |syntax#Filters]. The following example removes all HTML and capitalizes it: ```latte {block title|stripHtml|capitalize}...{/block} ``` -The tag can also be written as [n:attribute|syntax#n:attributes]: +The tag can also be written as an [n:attribute |syntax#n:attributes]: ```latte
                                                      @@ -212,10 +210,10 @@ The tag can also be written as [n:attribute|syntax#n:attributes]: ``` -Local Blocks .{data-version:2.9} --------------------------------- +Local Blocks +------------ -Every block overrides content of parent block of the same name. Except for local blocks. They are something like private methods in class. You can create a template without worrying that – due to coincidence of block names – they would be overwritten by second template. +Every block overrides the content of the parent block with the same name – except for local blocks. They are analogous to private methods in classes. You can create a template without worrying that, due to a coincidence of block names, they might be overwritten by another template. ```latte {block local helper} @@ -238,32 +236,28 @@ To print a block in a specific place, use the `{include blockname}` tag:

                                                      {include title}

                                                      ``` -You can also display block from another template: +You can also print a block from another template: ```latte {include footer from 'main.latte'} ``` -Printed block have not access to the variables of the active context, except if the block is defined in the same file where it is included. However they have access to the global variables. +The rendered block does not have access to the variables of the active context, unless the block is defined in the same file where it is included. However, it does have access to global variables. -You can pass variables this way: +You can pass variables to the block like this: ```latte -{* since Latte 2.9 *} {include footer, foo: bar, id: 123} - -{* before Latte 2.9 *} -{include footer, foo => bar, id => 123} ``` -You can use a variable or any expression in PHP as the block name. In this case, add the keyword `block` before the variable, so that it is known at compile-time that it is a block, and not [insert template |tags#include], whose name could also be in the variable: +The block name can be a variable or any PHP expression. In this case, add the keyword `block` before the variable so that Latte knows at compile time that it's a block and not an [included template |tags#include], whose name could also be in a variable: ```latte {var $name = footer} {include block $name} ``` -Block can also be printed inside itself, which is useful, for example, when rendering a tree structure: +A block can also be rendered within itself, which is useful, for example, when rendering a tree structure: ```latte {define menu, $items} @@ -281,9 +275,9 @@ Block can also be printed inside itself, which is useful, for example, when rend {/define} ``` -Instead of `{include menu, ...}` we can also write `{include this, ...}` where `this` means current block. +Instead of `{include menu, ...}`, we can also write `{include this, ...}`, where `this` means the current block. -Printed content can be modified by [filters|syntax#filters]. The following example removes all HTML and title-cases it: +The rendered block content can be modified by [filters |syntax#Filters]. The following example removes all HTML and capitalizes it: ```latte {include heading|stripHtml|capitalize} @@ -293,7 +287,7 @@ Printed content can be modified by [filters|syntax#filters]. The following examp Parent Block ------------ -If you need to print the content of the block from the parent template, the `{include parent}` statement will do the trick. This is useful if you want to add to the contents of a parent block instead of completely overriding it. +If you need to print the content of the block from the parent template, use `{include parent}`. This is useful if you want to supplement the content of the parent block instead of completely overriding it. ```latte {block footer} @@ -307,13 +301,13 @@ If you need to print the content of the block from the parent template, the `{in Definitions `{define}` .{toc: Definitions} ------------------------------------------ -In addition to the blocks, there are also "definitions" in Latte. They are comparable with functions in regular programming languages. They are useful to reuse template fragments to not repeat yourself. +In addition to blocks, Latte also has "definitions". In common programming languages, they would be comparable to functions. They are useful for reusing template fragments to avoid repetition. -Latte tries to do things easily, so basically the definitions are the same as blocks, and **everything that is said about blocks also applies to definitions**. It differs from blocks in only three ways: +Latte tries to keep things simple, so basically, definitions are the same as blocks, and **everything said about blocks also applies to definitions**. They differ from blocks in that: -1) they can accept arguments -2) they can't have [filters|syntax#filters] -3) they are enclosed in tags `{define}` and the content inside these tags is not send to output until you include them. Thanks to that, you can create them anywhere: +1) they are enclosed in `{define}` tags +2) they are rendered only when inserted via `{include}` +3) you can define parameters for them, similar to functions in PHP ```latte {block foo}

                                                      Hello

                                                      {/block} @@ -326,10 +320,9 @@ Latte tries to do things easily, so basically the definitions are the same as bl {* prints:

                                                      World

                                                      *} ``` -Imagine having a generic helper template that defines how to render HTML forms via definitions: +Imagine you have a helper template with a collection of definitions on how to draw HTML forms. -```latte -{* forms.latte *} +```latte .{file: forms.latte} {define input, $name, $value, $type = 'text'} {/define} @@ -339,34 +332,32 @@ Imagine having a generic helper template that defines how to render HTML forms v {/define} ``` -Arguments of a definitions are always optional with default value `null`, unless default value is specified (here `text` is the default value for `$type`, possible since Latte 2.9.1). As of Latte 2.7, parameter types can also be declared: `{define input, string $name, ...}`. +Arguments are always optional with a default value of `null`, unless a default value is specified (here `'text'` is the default value for `$type`). Parameter types can also be declared: `{define input, string $name, ...}`. -Definitions don’t have access to the variables of the active context, but they have access to global variables. - -They are included the [same way as block|#Printing Blocks]: +The template with the definitions is loaded using [`{import}` |#Horizontal Reuse]. The definitions themselves are rendered [in the same way as blocks |#Printing Blocks]: ```latte

                                                      {include input, 'password', null, 'password'}

                                                      {include textarea, 'comment'}

                                                      ``` +Definitions do not have access to the variables of the active context, but they do have access to global variables. + Dynamic Block Names ------------------- -Latte allows great flexibility in defining blocks because the block name can be any PHP expression. This example defines three blocks named `hi-Peter`, `hi-John` and `hi-Mary`: +Latte allows great flexibility in defining blocks because the block name can be any PHP expression. This example defines three blocks named `hi-Peter`, `hi-John`, and `hi-Mary`: -```latte -{* parent.latte *} +```latte .{file: parent.latte} {foreach [Peter, John, Mary] as $name} {block "hi-$name"}Hi, I am {$name}.{/block} {/foreach} ``` -For example, we can redefine only one block in a child template: +In the child template, we can then redefine, for example, just one block: -```latte -{* child.latte *} +```latte .{file: child.latte} {block hi-John}Hello. I am {$name}.{/block} ``` @@ -383,9 +374,9 @@ Checking Block Existence `{ifset}` .{toc: Checking Block Existence} ------------------------------------------------------------------- .[note] -See also [`{ifset $var}` |tags#ifset-elseifset] +See also [`{ifset $var}` |tags#ifset elseifset] -Use the `{ifset blockname}` test to check if a block (or more blocks) exists in the current context: +Use the `{ifset blockname}` test to check if a block (or multiple blocks) exists in the current context: ```latte {ifset footer} @@ -397,7 +388,7 @@ Use the `{ifset blockname}` test to check if a block (or more blocks) exists in {/ifset} ``` -You can use a variable or any expression in PHP as the block name. In this case, add the keyword `block` before the variable to make it clear that it is not the [variable|tags#ifset-elseifset] that is checked: +The block name can be a variable or any PHP expression. In this case, add the keyword `block` before the variable to clarify that it's not a check for the existence of [variables |tags#ifset elseifset]: ```latte {ifset block $name} @@ -405,58 +396,56 @@ You can use a variable or any expression in PHP as the block name. In this case, {/ifset} ``` +The existence of blocks is also checked by the [`hasBlock()` |functions#hasBlock] function: + +```latte +{if hasBlock(header) || hasBlock(footer)} + ... +{/if} +``` + Tips ---- -Here are some tips for working with blocks: +Some tips for working with blocks: -- The last top-level block does not need to have closing tag (block ends with the end of the document). This simplifies the writing of child templates, which one primary block. +- The last top-level block does not need a closing tag (the block ends with the end of the document). This simplifies writing child templates that contain one primary block. -- For extra readability, you can optionally give a name to your `{/block}` tag, for example `{/block footer}`. However, the name must match the block name. In larger templates, this technique helps you see which block tags are being closed. +- For better readability, you can optionally give the block name in the `{/block}` tag, for example `{/block footer}`. However, the name must match the block name. In larger templates, this technique helps you see which block tags are being closed. - You can’t directly define multiple block tags with the same name in the same template. But this can be achieved using [#dynamic block names]. -- You can use [n:attributes|syntax#n:attributes] to define blocks like `

                                                      Welcome to my awesome homepage

                                                      ` +- You can use [n:attributes |syntax#n:attributes] to define blocks like `

                                                      Welcome to my awesome homepage

                                                      ` -- Blocks can also be used without names only to apply the [filters |syntax#filters] to the output: `{block|strip} hello {/block}` +- Blocks can also be used without names only to apply [filters |syntax#Filters] to the output: `{block|strip} hello {/block}` Horizontal Reuse `{import}` .{toc: Horizontal Reuse} ==================================================== -The horizontal reuse is a third reusability and inheritance mechanism in Latte. It allows you to load blocks from other templates. It's similar to creating a PHP file with helper functions or a trait. +Horizontal reuse is the third mechanism for reuse and inheritance in Latte. It allows loading blocks from other templates. It's similar to creating a file with helper functions in PHP and then loading it using `require`. -Although layout template inheritance is one of the most powerful features of Latte, it is limited to single inheritance; a template can only extend one other template. This limitation makes template inheritance simple to understand and easy to debug: +While template layout inheritance is one of Latte's most powerful features, it is limited to simple inheritance - a template can only extend one other template. Horizontal reuse is a way to achieve multiple inheritance. -```latte -{layout 'layout.latte'} +Let's have a file with block definitions: -{block title}...{/block} -{block content}...{/block} -``` - -Horizontal reuse is a way to achieve the same goal as multiple inheritance, but without the associated complexity: - -```latte -{layout 'layout.latte'} - -{import 'blocks.latte'} +```latte .{file: blocks.latte} +{block sidebar}...{/block} -{block title}...{/block} -{block content}...{/block} +{block menu}...{/block} ``` -The `{import}` statement tells Latte to import all blocks and [#definitions] defined in `blocks.latte` into the current template. +Using the `{import}` command, we import all the blocks and [#definitions] defined in `blocks.latte` into another template: -```latte -{* blocks.latte *} +```latte .{file: child.latte} +{import 'blocks.latte'} -{block sidebar}...{/block} +{* sidebar and menu blocks can now be used *} ``` -In this example, the `{import}` statement imports the `sidebar` block into the main template. +If you import blocks in the parent template (i.e., use `{import}` in `layout.latte`), the blocks will also be available in all child templates, which is very practical. -The imported template must not [extend|#Layout Inheritance] another template, and its body should be empty. However, the imported template can import other templates. +The template that is intended to be imported (e.g. `blocks.latte`) must not [extend |#Layout Inheritance] another template, i.e., use `{layout}`. However, it can import other templates. The `{import}` tag should be the first template tag after `{layout}`. The template name can be any PHP expression: @@ -464,12 +453,12 @@ The `{import}` tag should be the first template tag after `{layout}`. The templa {import $ajax ? 'ajax.latte' : 'not-ajax.latte'} ``` -You can use as many `{import}` statements as you want in any given template. If two imported templates define the same block, the first one wins. However, the highest priority is given to the main template, which can overwrite any imported block. +You can use as many `{import}` statements as you want in a template. If two imported templates define the same block, the first one wins. However, the main template has the highest priority and can override any imported block. -All overridden blocks can be included gradually by inserting them as [#parent block]: +The content of overwritten blocks can be preserved by inserting the block in the same way as a [#parent-block]: ```latte -{layout 'base.latte'} +{layout 'layout.latte'} {import 'blocks.latte'} @@ -481,17 +470,17 @@ All overridden blocks can be included gradually by inserting them as [#parent bl {block content}...{/block} ``` -In this example, `{include parent}` will correctly call the `sidebar` block from the `blocks.latte` template. +In this example, `{include parent}` calls the `sidebar` block from the `blocks.latte` template. -Unit Inheritance `{embed}` .{toc: Unit Inheritance}{data-version:2.9} -===================================================================== +Unit Inheritance `{embed}` .{toc: Unit Inheritance} +=================================================== -The unit inheritance takes the idea of layout inheritance to the level of content fragments. While layout inheritance works with “document skeletons”, which are brought to life by child templates, the unit inheritance allows you to create skeletons for smaller units of content and reuse them anywhere you like. +Unit inheritance extends the idea of layout inheritance to the level of content fragments. While layout inheritance works with "document skeletons" brought to life by child templates, unit inheritance allows you to create skeletons for smaller units of content and reuse them wherever you want. -In unit inheritance the `{embed}` tag is the key. It combines the behavior of `{include}` and `{layout}`. It allows you to include another template’s or block's contents and optionally pass variables, just like `{include}` does. It also allows you to override any block defined inside the included template, like `{layout}` does. +In unit inheritance, the `{embed}` tag is key. It combines the behavior of `{include}` and `{layout}`. It allows you to embed the content of another template or block and optionally pass variables, just like `{include}`. It also allows you to override any block defined inside the embedded template, like `{layout}`. -For example we are going to use the collapsible accordion element. Let’s take a look at the element skeleton in template `collapsible.latte`: +For example, let's use an accordion element. Take a look at the element skeleton stored in the `collapsible.latte` template: ```latte
                                                      @@ -505,9 +494,9 @@ For example we are going to use the collapsible accordion element. Let’s take
                                                      ``` -The `{block}` tags defines two blocks that child templates can fill in. Yes, like in the case of parent template in the layout inheritance template. You also see `$modifierClass` variable. +The `{block}` tags define two blocks that child templates can fill. Yes, just like in the case of the parent template in layout inheritance. You also see the `$modifierClass` variable. -Let's use our element in template. This is where `{embed}` comes in. It’s a super powerful piece of kit that lets us do all the things: include element's template contents, add variables to it, and add blocks with custom HTML to it: +Let's use our element in a template. This is where `{embed}` comes in. It's an extremely powerful tag that allows us to do all these things: embed the element's template content, add variables to it, and add blocks with custom HTML to it: ```latte {embed 'collapsible.latte', modifierClass: my-style} @@ -522,7 +511,7 @@ Let's use our element in template. This is where `{embed}` comes in. It’s a su {/embed} ``` -The output might look like: +The output might look like this: ```latte
                                                      @@ -537,7 +526,7 @@ The output might look like:
                                                      ``` -Blocks inside embed tags form a separate layer independent of other blocks. Therefore, they can have the same name as the block outside the embed and are not affected in any way. Using the tag [include|#Printing Blocks] inside `{embed}` tags you can insert blocks here created, blocks from embedded template (which *are not* [local|#Local Blocks]), and also blocks from main template which *are* local. You can also [import blocks|#Horizontal Reuse] from other files: +Blocks inside embed tags form a separate layer independent of other blocks. Therefore, they can have the same name as a block outside the embed and are not affected in any way. Using the [include |#Printing Blocks] tag inside `{embed}` tags, you can insert blocks created here, blocks from the embedded template (which *are not* [local |#Local Blocks]), and also blocks from the main template that *are* local. You can also [import blocks |#Horizontal Reuse] from other files: ```latte {block outer}…{/block} @@ -558,9 +547,9 @@ Blocks inside embed tags form a separate layer independent of other blocks. Ther {/embed} ``` -Embeded templates have not access to the variables of the active context, but they have access to the global variables. +Embedded templates do not have access to the variables of the active context, but they do have access to global variables. -With `{embed}` you can insert not only templates but also other blocks, so the previous example could be written like this: .{data-version:2.10} +Using `{embed}`, you can embed not only templates but also other blocks, so the previous example could be written this way: ```latte {define collapsible} @@ -581,7 +570,7 @@ With `{embed}` you can insert not only templates but also other blocks, so the p {/embed} ``` -If we pass an expression to `{embed}` and it is not clear whether it is a block or file name, add the keyword `block` or `file`: +If we pass an expression to `{embed}` and it's not clear whether it's a block name or a file name, add the keyword `block` or `file`: ```latte {embed block $name} ... {/embed} @@ -591,13 +580,13 @@ If we pass an expression to `{embed}` and it is not clear whether it is a block Use Cases ========= -There are various types of inheritance and code reuse in Latte. Let's summarize the main concepts for more clearance: +There are various types of inheritance and code reuse in Latte. Let's summarize the main concepts for better clarity: `{include template}` -------------------- -**Use Case:** Using `header.latte` & `footer.latte` inside `layout.latte`. +**Use Case**: Using `header.latte` and `footer.latte` inside `layout.latte`. `header.latte` @@ -630,7 +619,7 @@ There are various types of inheritance and code reuse in Latte. Let's summarize `{layout}` ---------- -**Use Case**: Extending `layout.latte` inside `homepage.latte` & `about.latte`. +**Use Case**: Extending `layout.latte` inside `homepage.latte` and `about.latte`. `layout.latte` @@ -666,7 +655,7 @@ There are various types of inheritance and code reuse in Latte. Let's summarize `{import}` ---------- -**Use Case**: `sidebar.latte` in `single.product.latte` & `single.service.latte`. +**Use Case**: Using `sidebar.latte` in `single.product.latte` and `single.service.latte`. `sidebar.latte` @@ -698,7 +687,7 @@ There are various types of inheritance and code reuse in Latte. Let's summarize `{define}` ---------- -**Use Case**: A function which gets some variables and outputs some markup. +**Use Case**: Functions that receive variables and render something. `form.latte` @@ -724,7 +713,7 @@ There are various types of inheritance and code reuse in Latte. Let's summarize `{embed}` --------- -**Use Case**: Embedding `pagination.latte` in `product.table.latte` & `service.table.latte`. +**Use Case**: Embedding `pagination.latte` into `product.table.latte` and `service.table.latte`. `pagination.latte` diff --git a/latte/en/type-system.texy b/latte/en/type-system.texy index 3a5238b23f..eac2758138 100644 --- a/latte/en/type-system.texy +++ b/latte/en/type-system.texy @@ -1,27 +1,27 @@ Type System *********** -
                                                      +
                                                      -Type system is main thing for the development of robust applications. Latte brings type support to templates. To knowing what data or object type each variable is allows +The type system is crucial for developing robust applications. Latte brings type support to templates as well. By knowing the data or object type of each variable, it allows: -- IDE to correctly autocomplete (see [integration and plugins|recipes#Editors and IDE]) +- IDE to correctly autocomplete (see [integration and plugins |recipes#Editors and IDE]) - static analysis to detect errors -Two points that significantly improve the quality and convenience of development. +Both significantly increase the quality and convenience of development.
                                                      .[note] -The declared types are informative and Latte does not check them at this time. +Declared types are informative, and Latte does not check them at this time. -How to start using types? Create a template class, eg `CatalogTemplateParameters`, representing the passed parameters: +How to start using types? Create a template class, e.g., `CatalogTemplateParameters`, representing the passed parameters, their types, and optionally their default values: ```php class CatalogTemplateParameters { public function __construct( - public string $langs, + public string $lang, /** @var ProductEntity[] */ public array $products, public Address $address, @@ -35,19 +35,16 @@ $latte->render('template.latte', new CatalogTemplateParameters( )); ``` -Then insert the `{templateType}` tag with the full class name (including the namespace) at the beginning of the template. This defines that there are be variables `$langs` and `$products` in the template including the corresponding types. -You can also specify the types of local variables using tags [`{var}` |tags#var-default], `{varType}` and [`{define}` |template-inheritance#definitions]. +Then insert the `{templateType}` tag with the full class name (including the namespace) at the beginning of the template. This defines that the variables `$lang` and `$products` exist in the template, including their respective types. You can also specify the types of local variables using the [`{var}` |tags#var-default], `{varType}`, and [`{define}` |template-inheritance#Definitions] tags. -Now the IDE can correctly autocomplete. +From this point on, your IDE can correctly provide autocompletion. -How to save work? How to write a template class or `{varType}` tags as easily as possible? Get them generated. -That is precisely what pair of tags `{templatePrint}` and `{varPrint}` do. -If you place one of these tags in a template, the code of class or template is displayed instead of the normal rendering. Then simply select and copy the code into your project. +How to save work? What's the easiest way to write a template parameter class or `{varType}` tags? Have them generated! That's what the pair of tags `{templatePrint}` and `{varPrint}` are for. If you place them in a template, instead of the normal rendering, a suggestion for the class code or a list of `{varType}` tags will be displayed. Then, simply select the code with one click and copy it into your project. `{templateType}` ---------------- -The types of parameters passed to the template are declared using class: +Types of parameters passed to the template are declared using a class: ```latte {templateType MyApp\CatalogTemplateParameters} @@ -56,7 +53,7 @@ The types of parameters passed to the template are declared using class: `{varType}` ----------- -How to declare types of variables? For this purpose use tag `{varType}` for an existing variable, or [`{var}` |tags#var-default]: +How to declare variable types? Use the `{varType}` tag for existing variables, or [`{var}` |tags#var-default]: ```latte {varType Nette\Security\User $user} @@ -66,11 +63,11 @@ How to declare types of variables? For this purpose use tag `{varType}` for an e `{templatePrint}` ----------------- -You can also generate this class using the `{templatePrint}` tag. If you place it at the beginning of the template, the code of class is displayed instead of the normal template. Then simply select and copy the code into your project. +You can also have the class generated using the `{templatePrint}` tag. If you place it at the beginning of the template, instead of the normal rendering, a suggestion for the class code will be displayed. Then simply select the code with one click and copy it into your project. `{varPrint}` ------------ -The `{varPrint}` tag saves you time. If you place it in a template, the list of `{varType}` tags is displayed instead of the normal rendering. Then simply select and copy the code into your template. +The `{varPrint}` tag saves you writing time. If you place it in a template, instead of the normal rendering, a suggestion for `{varType}` tags for local variables will be displayed. Then simply select the code with one click and copy it into your template. -The `{varPrint}` lists local variables that are not template parameters. If you want to list all variables, use `{varPrint all}`. +`{varPrint}` itself only lists local variables that are not template parameters. If you want to list all variables, use `{varPrint all}`. diff --git a/latte/en/why-use.texy b/latte/en/why-use.texy new file mode 100644 index 0000000000..684a4b0eba --- /dev/null +++ b/latte/en/why-use.texy @@ -0,0 +1,80 @@ +Why Use Templates? +****************** + + +Why should I use a templating system in PHP? +-------------------------------------------- + +Why use a templating system in PHP when PHP itself is a templating language? + +Let's first briefly recap the history of this language, which is full of interesting twists and turns. One of the first programming languages used for generating HTML pages was C. However, it soon became apparent that using it for this purpose was impractical. Rasmus Lerdorf thus created PHP, which facilitated the generation of dynamic HTML with C on the backend. PHP was originally designed as a templating language, but over time it acquired additional features and became a fully-fledged programming language. + +Nevertheless, it still functions as a templating language. A PHP file can contain an HTML page, in which variables are output using ``, etc. + +Early in PHP's history, the Smarty templating system was created, with the purpose of strictly separating the presentation (HTML/CSS) from the application logic. It deliberately provided a more limited language than PHP itself, so that, for example, a developer could not make a database query from a template. On the other hand, it represented an additional dependency in projects, increased their complexity, and required programmers to learn the new Smarty language. Such benefits were debatable, and plain PHP continued to be used for templates. + +Over time, template systems began to become useful. They introduced concepts such as [inheritance|template-inheritance], [sandbox mode|sandbox], and a range of other features that significantly simplified template creation compared to pure PHP. The topic of security, the existence of [vulnerabilities like XSS|safety-first], and the need for [escaping |#What is escaping] came to the forefront. Template systems introduced auto-escaping to eliminate the risk of a programmer forgetting it and creating a serious security hole (we'll see shortly that this has certain pitfalls). + +Today, the benefits of templating systems significantly outweigh the costs associated with their deployment. Therefore, it makes sense to use them. + + +Why is Latte better than Twig or Blade? +--------------------------------------- + +There are several reasons – some are convenient, and others are fundamentally useful. Latte is a combination of convenience and utility. + +*First, the pleasant:* Latte has the same [syntax as PHP |syntax#Latte Understands PHP]. The only difference is in the notation of tags, preferring shorter `{` and `}` instead of ``. This means that you don't have to learn a new language. Training costs are minimal. Most importantly, during development, you don't have to constantly "switch" between the PHP language and the template language, since they are both the same. This is unlike Twig templates, which use Python-like syntax, forcing the programmer to switch between two different languages. + +*Now for the immensely useful reason:* All template systems, like Twig, Blade, or Smarty, have evolved to include protection against XSS in the form of automatic [escaping |#What is escaping]. More precisely, the automatic calling of the `htmlspecialchars()` function. However, the creators of Latte realized that this is not the right solution at all. This is because different parts of the document require different escaping methods. Naive auto-escaping is a dangerous feature because it creates a false sense of security. + +For auto-escaping to be functional and reliable, it must recognize where in the document the data is being output (we call these contexts) and choose the escaping function accordingly. Therefore, it must be [context-sensitive |safety-first#Context-Aware Escaping]. And this is what Latte can do. It understands HTML. It doesn't perceive the template as just a string of characters but understands what tags, attributes, etc., are. Therefore, it escapes differently in HTML text, within HTML tags, inside JavaScript, etc. + +Latte is the first and only PHP templating system with context-sensitive escaping. It represents the only truly secure templating system. + +*And another pleasant reason:* Because Latte understands HTML, it offers other very pleasant features. For example, [n:attributes |syntax#n:attributes]. Or the ability to [check links |safety-first#Link Checking]. And many more. + + +What is escaping? +----------------- + +Escaping is the process of replacing characters with special meanings with corresponding sequences when inserting one string into another, to prevent unwanted effects or errors. For example, when inserting a string into HTML text where the character `<` has a special meaning (indicating the start of a tag), we replace it with the corresponding sequence, which is the HTML entity `<`. This allows the browser to correctly display the `<` symbol. + +A simple example of escaping directly when writing PHP code is inserting a quotation mark into a string by prefixing it with a backslash. + +We discuss escaping in more detail in the chapter [How to defend against XSS |safety-first#How to Defend Against XSS]. + + +Can a database query be executed from a Latte template? +------------------------------------------------------- + +In templates, you can work with objects that the programmer passes to them. If the programmer wishes, they can pass a database object to the template and perform a query on it. If this is their intention, there is no reason to prevent it. + +A different situation arises if you want to give clients or external coders the ability to edit templates. In this case, you definitely don't want them to have access to the database. Of course, you won't pass the database object to the template, but what if it can be accessed through another object? The solution is the [sandbox mode|sandbox], which allows you to define which methods can be called in templates. Thanks to this, you don't have to worry about security breaches. + + +What are the main differences between templating systems like Latte, Twig, and Blade? +------------------------------------------------------------------------------------- + +The differences between templating systems like Latte, Twig, and Blade mainly lie in their syntax, security, and integration with frameworks: + +- **Latte**: Uses PHP syntax, making it easy to learn and use. Provides top-notch protection against XSS attacks with context-aware escaping. Well-integrated with the Nette Framework but usable standalone. +- **Twig**: Uses Python-like syntax, which differs significantly from PHP. Escapes without context distinction (basic `htmlspecialchars`). Well-integrated with the Symfony framework. +- **Blade**: Uses a mix of PHP and custom syntax. Escapes without context distinction (basic `htmlspecialchars`). Tightly integrated with Laravel features and ecosystem. + + +Is it worthwhile for companies to use a templating system? +---------------------------------------------------------- + +Firstly, the costs associated with training, usage, and overall benefits vary significantly depending on the system. The Latte templating system, thanks to its use of PHP syntax, greatly simplifies learning for programmers already familiar with this language. It usually takes only a few hours for a programmer to become sufficiently acquainted with Latte, thus reducing training costs and accelerating technology adoption and, most importantly, efficiency in daily use. + +Furthermore, Latte provides a high level of protection against XSS vulnerabilities thanks to its unique context-aware escaping technology. This protection is crucial for ensuring web application security and minimizing the risk of attacks that could endanger users or company data. Web application security is also important for maintaining a company's reputation. Security issues can lead to a loss of customer trust and damage the company's image in the market. + +Using Latte also reduces overall development and maintenance costs by making both easier. Therefore, using a templating system is definitely worthwhile. + + +Does Latte affect the performance of web applications? +------------------------------------------------------ + +Although Latte templates are processed quickly, this aspect doesn't really matter in production. The reason is that template files are parsed only once upon their first display. They are then compiled into native PHP code, stored on disk, and executed on every subsequent request without requiring recompilation. + +This is how it works in a production environment. During development, Latte templates are recompiled every time their content changes, so the developer always sees the current version. diff --git a/latte/es/@home.texy b/latte/es/@home.texy index 243caf0c40..b4be54fe88 100644 --- a/latte/es/@home.texy +++ b/latte/es/@home.texy @@ -1 +1,2 @@ -{{maintitle: Latte - Las plantillas más seguras y realmente intuitivas para PHP}} +{{maintitle: Latte – las plantillas más seguras y realmente intuitivas para PHP}} +{{description: Latte es el sistema de plantillas más seguro para PHP. Previene muchas vulnerabilidades de seguridad. Apreciará su sintaxis intuitiva y muchas características útiles.}} diff --git a/latte/es/@left-menu.texy b/latte/es/@left-menu.texy index 6ab06af07a..ad96b47fc7 100644 --- a/latte/es/@left-menu.texy +++ b/latte/es/@left-menu.texy @@ -1,24 +1,24 @@ -- [Primeros pasos |Guide] -- Conceptos - - [La seguridad ante todo |Safety First] +- [Empezando con Latte |guide] +- [¿Por qué usar plantillas? |why-use] +- Conceptos ⚗️ + - [La seguridad es lo primero |safety-first] - [Herencia de plantillas |Template Inheritance] - - [Sistema de tipos |Type System] - - [Sandbox] + - [Sistema de tipos |type-system] + - [Sandbox |Sandbox] -- Para diseñadores - - [Sintaxis |Syntax] - - [Tags] - - [Filtros |Filters] - - [Funciones |Functions] - - [Trucos y consejos |recipes] +- Para diseñadores 🎨 + - [Sintaxis |syntax] + - [Etiquetas |tags] + - [Filtros |filters] + - [Funciones |functions] + - [Consejos y trucos |recipes] -- Para desarrolladores - - [Prácticas para desarrolladores |develop] - - [Ampliación de Latte |Extending Latte] - - [Creación de una extensión |creating-extension] +- Para desarrolladores 🧮 + - [Prácticas de desarrollo |develop] + - [Extendiendo Latte |extending-latte] -- [Libro de recetas |cookbook/@home] +- [Tutoriales y procedimientos 💡|cookbook/@home] - [Migración desde Twig |cookbook/migration-from-twig] - - [... más |cookbook/@home] + - [… otros |cookbook/@home] -- "Zona de juegos .[link-external]":https://fiddle.nette.org/latte/ .{padding-top:1em} +- "Playground .[link-external]":https://fiddle.nette.org/latte/ .{padding-top:1em} diff --git a/latte/es/@menu.texy b/latte/es/@menu.texy index fd82f5d8e8..588b76348f 100644 --- a/latte/es/@menu.texy +++ b/latte/es/@menu.texy @@ -1,12 +1,12 @@
                                                      diff --git a/latte/es/@meta.texy b/latte/es/@meta.texy new file mode 100644 index 0000000000..ff4ce063ad --- /dev/null +++ b/latte/es/@meta.texy @@ -0,0 +1 @@ +{{sitename: Latte Documentación}} diff --git a/latte/es/compiler-passes.texy b/latte/es/compiler-passes.texy new file mode 100644 index 0000000000..3fbb00ba08 --- /dev/null +++ b/latte/es/compiler-passes.texy @@ -0,0 +1,555 @@ +Pases de compilación +******************** + +.[perex] +Los pases de compilación proporcionan un mecanismo poderoso para analizar y modificar las plantillas Latte *después* de que se analizan en un árbol de sintaxis abstracto (AST) y *antes* de que se genere el código PHP final. Esto permite la manipulación avanzada de plantillas, optimizaciones, controles de seguridad (como Sandbox) y la recopilación de información sobre las plantillas. Esta guía le guiará a través de la creación de sus propios pases de compilación. + + +¿Qué es un pase de compilación? +=============================== + +Para comprender el rol de los pases de compilación, consulte el [proceso de compilación de Latte |custom-tags#Entendiendo el proceso de compilación]. Como puede ver, los pases de compilación operan en una fase crucial, permitiendo una intervención profunda entre el análisis inicial y la salida final del código. + +En esencia, un pase de compilación es simplemente un objeto PHP invocable (como una función, método estático o método de instancia) que acepta un solo argumento: el nodo raíz del AST de la plantilla, que siempre es una instancia de `Latte\Compiler\Nodes\TemplateNode`. + +El objetivo principal de un pase de compilación suele ser uno o ambos de los siguientes: + +- Análisis: Recorrer el AST y recopilar información sobre la plantilla (p. ej., encontrar todos los bloques definidos, verificar el uso de etiquetas específicas, asegurar que se cumplan ciertas restricciones de seguridad). +- Modificación: Cambiar la estructura del AST o los atributos de los nodos (p. ej., agregar automáticamente atributos HTML, optimizar ciertas combinaciones de etiquetas, reemplazar etiquetas obsoletas por nuevas, implementar reglas de sandbox). + + +Registro +======== + +Los pases de compilación se registran usando el método de [extensión |extending-latte#getPasses] `getPasses()`. Este método devuelve un array asociativo donde las claves son nombres únicos de los pases (usados internamente y para ordenar) y los valores son objetos PHP invocables que implementan la lógica del pase. + +```php +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Extension; + +class MyExtension extends Extension +{ + public function getPasses(): array + { + return [ + 'modificationPass' => $this->modifyTemplateAst(...), + // ... otros pases ... + ]; + } + + public function modifyTemplateAst(TemplateNode $templateNode): void + { + // Implementación... + } +} +``` + +Los pases registrados por las extensiones base de Latte y sus propias extensiones se ejecutan secuencialmente. El orden puede ser importante, especialmente si un pase depende de los resultados o modificaciones de otro. Latte proporciona un mecanismo auxiliar para controlar este orden si es necesario; consulte la documentación de [`Extension::getPasses()` |extending-latte#getPasses] para obtener detalles. + + +Ejemplo de AST +============== + +Para tener una mejor idea del AST, agregamos un ejemplo. Esta es la plantilla de origen: + +```latte +{foreach $category->getItems() as $item} +
                                                    • {$item->name|upper}
                                                    • + {else} + no items found +{/foreach} +``` + +Y esta es su representación en forma de AST: + +/--pre +Latte\Compiler\Nodes\TemplateNode( + Latte\Compiler\Nodes\FragmentNode( + - Latte\Essential\Nodes\ForeachNode( + expression: Latte\Compiler\Nodes\Php\Expression\MethodCallNode( + object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$category') + name: Latte\Compiler\Nodes\Php\IdentifierNode('getItems') + ) + value: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') + content: Latte\Compiler\Nodes\FragmentNode( + - Latte\Compiler\Nodes\TextNode(' ') + - Latte\Compiler\Nodes\Html\ElementNode('li')( + content: Latte\Essential\Nodes\PrintNode( + expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode( + object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') + name: Latte\Compiler\Nodes\Php\IdentifierNode('name') + ) + modifier: Latte\Compiler\Nodes\Php\ModifierNode( + filters: + - Latte\Compiler\Nodes\Php\FilterNode('upper') + ) + ) + ) + ) + else: Latte\Compiler\Nodes\FragmentNode( + - Latte\Compiler\Nodes\TextNode('no items found') + ) + ) + ) +) +\-- + + +Recorriendo el AST con `NodeTraverser` +====================================== + +Escribir manualmente funciones recursivas para recorrer la compleja estructura del AST es tedioso y propenso a errores. Latte proporciona una herramienta especial para este propósito: [api:Latte\Compiler\NodeTraverser]. Esta clase implementa el [patrón de diseño Visitor |https://en.wikipedia.org/wiki/Visitor_pattern], que hace que el recorrido del AST sea sistemático y fácil de manejar. + +El uso básico implica crear una instancia de `NodeTraverser` y llamar a su método `traverse()`, pasando el nodo raíz del AST y uno o dos objetos invocables "visitor": + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; + +(new NodeTraverser)->traverse( + $templateNode, + + // visitor 'enter': Se llama al entrar en el nodo (antes de sus hijos) + enter: function (Node $node) { + echo "Entrando en el nodo de tipo: " . $node::class . "\n"; + // Aquí puede examinar el nodo + if ($node instanceof Nodes\TextNode) { + // echo "Texto encontrado: " . $node->content . "\n"; + } + }, + + // visitor 'leave': Se llama al salir del nodo (después de sus hijos) + leave: function (Node $node) { + echo "Saliendo del nodo de tipo: " . $node::class . "\n"; + // Aquí puede realizar acciones después de procesar los hijos + }, +); +``` + +Puede proporcionar solo el visitor `enter`, solo el visitor `leave`, o ambos, según sus necesidades. + +**`enter(Node $node)`:** Esta función se ejecuta para cada nodo **antes** de que el traverser visite cualquiera de los hijos de ese nodo. Es útil para: + +- Recopilar información al recorrer el árbol hacia abajo. +- Tomar decisiones *antes* de procesar los hijos (como decidir omitirlos, consulte [#Optimización del recorrido]). +- Potencialmente modificar el nodo antes de visitar a los hijos (menos común). + +**`leave(Node $node)`:** Esta función se ejecuta para cada nodo **después** de que todos sus hijos (y sus subárboles completos) hayan sido visitados por completo (tanto entrada como salida). Es el lugar más común para: + +Ambos visitors `enter` y `leave` pueden opcionalmente devolver un valor para influir en el proceso de recorrido. Devolver `null` (o nada) continúa el recorrido normalmente, devolver una instancia de `Node` reemplaza el nodo actual, y devolver constantes especiales como `NodeTraverser::RemoveNode` o `NodeTraverser::StopTraversal` modifica el flujo, como se explica en las siguientes secciones. + + +Cómo funciona el recorrido +-------------------------- + +`NodeTraverser` utiliza internamente el método `getIterator()`, que cada clase `Node` debe implementar (como se discutió en [Creación de etiquetas personalizadas |custom-tags#Implementación de getIterator para subnodos]). Itera sobre los hijos obtenidos mediante `getIterator()`, llama recursivamente a `traverse()` en ellos y asegura que los visitors `enter` y `leave` se llamen en el orden correcto de profundidad primero para cada nodo en el árbol accesible a través de los iteradores. Esto vuelve a enfatizar por qué un `getIterator()` correctamente implementado en sus propios nodos de etiqueta es absolutamente esencial para el correcto funcionamiento de los pases de compilación. + +Escribamos un recorrido simple que cuente cuántas veces se usa la etiqueta `{do}` (representada por `Latte\Essential\Nodes\DoNode`) en la plantilla. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Essential\Nodes\DoNode; + +function countDoTags(TemplateNode $templateNode): void +{ + $count = 0; + (new NodeTraverser)->traverse( + $templateNode, + enter: function (Node $node) use (&$count): void { + if ($node instanceof DoNode) { + $count++; + } + }, + // El visitor 'leave' no es necesario para esta tarea + ); + + echo "Etiqueta {do} encontrada $count veces.\n"; +} + +$latte = new Latte\Engine; +$ast = $latte->parse($templateSource); +countDoTags($ast); +``` + +En este ejemplo, solo necesitamos el visitor `enter` para verificar el tipo de cada nodo visitado. + +A continuación, exploraremos cómo estos visitors realmente modifican el AST. + + +Modificación del AST +==================== + +Uno de los propósitos principales de los pases de compilación es modificar el árbol de sintaxis abstracto. Esto permite transformaciones potentes, optimizaciones o la aplicación de reglas directamente en la estructura de la plantilla antes de generar el código PHP. `NodeTraverser` proporciona varias formas de lograr esto dentro de los visitors `enter` y `leave`. + +**Nota importante:** La modificación del AST requiere precaución. Cambios incorrectos, como eliminar nodos esenciales o reemplazar un nodo con un tipo incompatible, pueden provocar errores durante la generación de código o causar un comportamiento inesperado durante la ejecución del programa. Siempre pruebe a fondo sus pases de modificación. + + +Modificación de propiedades de nodos +------------------------------------ + +La forma más sencilla de modificar el árbol es cambiar directamente las **propiedades públicas** de los nodos visitados durante el recorrido. Todos los nodos almacenan sus argumentos analizados, contenido o atributos en propiedades públicas. + +**Ejemplo:** Creemos un pase que encuentre todos los nodos de texto estático (`TextNode`, que representan HTML normal o texto fuera de las etiquetas Latte) y convierta su contenido a mayúsculas *directamente en el AST*. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\TextNode; + +function uppercaseStaticText(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // Podemos usar 'enter', ya que TextNode no tiene hijos que procesar + enter: function (Node $node) { + // ¿Es este nodo un bloque de texto estático? + if ($node instanceof TextNode) { + // ¡Sí! Modificamos directamente su propiedad pública 'content'. + $node->content = mb_strtoupper(html_entity_decode($node->content)); + } + // No es necesario devolver nada; el cambio se aplica directamente. + }, + ); +} +``` + +En este ejemplo, el visitor `enter` comprueba si el `$node` actual es de tipo `TextNode`. Si es así, actualizamos directamente su propiedad pública `$content` usando `mb_strtoupper()`. Esto cambia directamente el contenido del texto estático almacenado en el AST *antes* de generar el código PHP. Como estamos modificando el objeto directamente, no necesitamos devolver nada desde el visitor. + +Efecto: Si la plantilla contenía `

                                                      Hola

                                                      {= $var }Mundo`, después de este pase, el AST representará algo como: `

                                                      HOLA

                                                      {= $var }MUNDO`. Esto NO AFECTA el contenido de `$var`. + + +Reemplazo de nodos +------------------ + +Una técnica de modificación más potente es reemplazar completamente un nodo por otro. Esto se hace **devolviendo una nueva instancia de `Node`** desde el visitor `enter` o `leave`. `NodeTraverser` luego reemplaza el nodo original por el devuelto en la estructura del nodo padre. + +**Ejemplo:** Creemos un pase que encuentre todos los usos de la constante `PHP_VERSION` (representada por `ConstantFetchNode`) y los reemplace directamente con un literal de cadena (`StringNode`) que contenga la versión *real* de PHP detectada *durante la compilación*. Esta es una forma de optimización en tiempo de compilación. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\Php\Expression\ConstantFetchNode; +use Latte\Compiler\Nodes\Php\Scalar\StringNode; + +function inlinePhpVersion(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // 'leave' se usa a menudo para reemplazar, asegurando que los hijos (si existen) + // se procesen primero, aunque 'enter' también funcionaría aquí. + leave: function (Node $node) { + // ¿Es este nodo un acceso a constante y el nombre de la constante es 'PHP_VERSION'? + if ($node instanceof ConstantFetchNode && (string) $node->name === 'PHP_VERSION') { + // Creamos un nuevo StringNode que contiene la versión actual de PHP + $newNode = new StringNode(PHP_VERSION); + + // Opcional, pero buena práctica: copiar la información de posición + $newNode->position = $node->position; + + // Devolvemos el nuevo StringNode. Traverser reemplazará + // el ConstantFetchNode original con este $newNode. + return $newNode; + } + // Si no devolvemos un Node, se conserva el $node original. + }, + ); +} +``` + +Aquí, el visitor `leave` identifica el `ConstantFetchNode` específico para `PHP_VERSION`. Luego, crea un `StringNode` completamente nuevo que contiene el valor de la constante `PHP_VERSION` *en tiempo de compilación*. Al devolver este `$newNode`, le dice al traverser que reemplace el `ConstantFetchNode` original en el AST. + +Efecto: Si la plantilla contenía `{= PHP_VERSION }` y la compilación se ejecuta en PHP 8.2.1, el AST después de este pase representará efectivamente `{= '8.2.1' }`. + +**Elección entre `enter` vs. `leave` para el reemplazo:** + +- Use `leave` si la creación del nuevo nodo depende de los resultados del procesamiento de los hijos del nodo antiguo, o si simplemente desea asegurarse de que los hijos se visiten antes del reemplazo (práctica común). +- Use `enter` si desea reemplazar el nodo *antes* de que se visiten sus hijos. + + +Eliminación de nodos +-------------------- + +Puede eliminar completamente un nodo del AST devolviendo la constante especial `NodeTraverser::RemoveNode` desde el visitor. + +**Ejemplo:** Eliminemos todos los comentarios de la plantilla (`{* ... *}`), que están representados por `CommentNode` en el AST generado por el núcleo de Latte (aunque típicamente se procesan antes, esto sirve como ejemplo). + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\CommentNode; + +function removeCommentNodes(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // 'enter' está bien aquí, ya que no necesitamos información sobre los hijos para eliminar el comentario + enter: function (Node $node) { + if ($node instanceof CommentNode) { + // Indicamos al traverser que elimine este nodo del AST + return NodeTraverser::RemoveNode; + } + }, + ); +} +``` + +**Advertencia:** Use `RemoveNode` con precaución. Eliminar un nodo que contiene contenido esencial o afecta la estructura (como eliminar el nodo de contenido de un bucle) puede llevar a plantillas dañadas o código generado inválido. Es más seguro para nodos que son verdaderamente opcionales o independientes (como comentarios o etiquetas de depuración) o para nodos estructurales vacíos (p. ej., un `FragmentNode` vacío puede eliminarse de forma segura en algunos contextos mediante un pase de limpieza). + +Estos tres métodos (modificación de propiedades, reemplazo de nodos y eliminación de nodos) proporcionan las herramientas fundamentales para manipular el AST dentro de sus pases de compilación. + + +Optimización del recorrido +========================== + +El AST de las plantillas puede ser bastante grande, conteniendo potencialmente miles de nodos. Recorrer cada nodo individual puede ser innecesario y afectar el rendimiento de la compilación si su pase solo está interesado en partes específicas del árbol. `NodeTraverser` ofrece formas de optimizar el recorrido: + + +Omitir hijos +------------ + +Si sabe que una vez que encuentra un cierto tipo de nodo, ninguno de sus descendientes puede contener los nodos que está buscando, puede decirle al traverser que omita la visita de sus hijos. Esto se hace devolviendo la constante `NodeTraverser::DontTraverseChildren` desde el visitor **`enter`**. Esto omite ramas enteras durante el recorrido, lo que potencialmente ahorra un tiempo considerable, especialmente en plantillas con expresiones PHP complejas dentro de las etiquetas. + + +Detener el recorrido +-------------------- + +Si su pase solo necesita encontrar la *primera* ocurrencia de algo (un tipo específico de nodo, el cumplimiento de una condición), puede detener por completo todo el proceso de recorrido una vez que lo encuentre. Esto se logra devolviendo la constante `NodeTraverser::StopTraversal` desde el visitor `enter` o `leave`. El método `traverse()` dejará de visitar cualquier nodo adicional. Esto es muy eficiente si solo necesita la primera coincidencia en un árbol potencialmente muy grande. + + +Ayudante útil `NodeHelpers` +=========================== + +Aunque `NodeTraverser` ofrece un control detallado, Latte también proporciona una clase auxiliar práctica, [api:Latte\Compiler\NodeHelpers], que encapsula `NodeTraverser` para varias tareas comunes de búsqueda y análisis, a menudo requiriendo menos código preparatorio. + + +find(Node $startNode, callable $filter): array .[method] +-------------------------------------------------------- + +Este método estático encuentra **todos** los nodos en el subárbol que comienza en `$startNode` (inclusive) que satisfacen el callback `$filter`. Devuelve un array de los nodos coincidentes. + +**Ejemplo:** Encontrar todos los nodos de variables (`VariableNode`) en toda la plantilla. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Php\Expression\VariableNode; +use Latte\Compiler\Nodes\TemplateNode; + +function findAllVariables(TemplateNode $templateNode): array +{ + return NodeHelpers::find( + $templateNode, + fn($node) => $node instanceof VariableNode, + ); +} +``` + + +findFirst(Node $startNode, callable $filter): ?Node .[method] +-------------------------------------------------------------- + +Similar a `find`, pero detiene el recorrido inmediatamente después de encontrar el **primer** nodo que satisface el callback `$filter`. Devuelve el objeto `Node` encontrado o `null` si no se encuentra ningún nodo coincidente. Esto es esencialmente un envoltorio práctico alrededor de `NodeTraverser::StopTraversal`. + +**Ejemplo:** Encontrar el nodo `{parameters}` (igual que el ejemplo manual anterior, pero más corto). + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Essential\Nodes\ParametersNode; + +function findParametersNodeHelper(TemplateNode $templateNode): ?ParametersNode +{ + return NodeHelpers::findFirst( + $templateNode->head, // Buscar solo en la sección principal para mayor eficiencia + fn($node) => $node instanceof ParametersNode, + ); +} +``` + + +toValue(ExpressionNode $node, bool $constants = false): mixed .[method] +----------------------------------------------------------------------- + +Este método estático intenta evaluar un `ExpressionNode` **en tiempo de compilación** y devolver su valor PHP correspondiente. Funciona de manera confiable solo para nodos literales simples (`StringNode`, `IntegerNode`, `FloatNode`, `BooleanNode`, `NullNode`) e instancias de `ArrayNode` que contienen solo dichos elementos evaluables. + +Si `$constants` se establece en `true`, también intentará resolver `ConstantFetchNode` y `ClassConstantFetchNode` verificando `defined()` y usando `constant()`. + +Si el nodo contiene variables, llamadas a funciones u otros elementos dinámicos, no se puede evaluar en tiempo de compilación y el método lanzará una `InvalidArgumentException`. + +**Caso de uso:** Obtener el valor estático de un argumento de etiqueta durante la compilación para tomar decisiones en tiempo de compilación. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Php\ExpressionNode; + +function getStaticStringArgument(ExpressionNode $argumentNode): ?string +{ + try { + $value = NodeHelpers::toValue($argumentNode); + return is_string($value) ? $value : null; + } catch (\InvalidArgumentException $e) { + // El argumento no era una cadena literal estática + return null; + } +} +``` + + +toText(?Node $node): ?string .[method] +-------------------------------------- + +Este método estático es útil para extraer el contenido de texto plano de nodos simples. Funciona principalmente con: +- `TextNode`: Devuelve su `$content`. +- `FragmentNode`: Concatena el resultado de `toText()` para todos sus hijos. Si algún hijo no es convertible a texto (p. ej., contiene un `PrintNode`), devuelve `null`. +- `NopNode`: Devuelve una cadena vacía. +- Otros tipos de nodos: Devuelve `null`. + +**Caso de uso:** Obtener el contenido de texto estático del valor de un atributo HTML o un elemento HTML simple para analizarlo durante un pase de compilación. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Html\AttributeNode; + +function getStaticAttributeValue(AttributeNode $attr): ?string +{ + // $attr->value es típicamente un AreaNode (como FragmentNode o TextNode) + return NodeHelpers::toText($attr->value); +} + +// Ejemplo de uso en un pase: +// if ($node instanceof Html\ElementNode && $node->name === 'meta') { +// $nameAttrValue = getStaticAttributeValue($node->getAttributeNode('name')); +// if ($nameAttrValue === 'description') { ... } +// } +``` + +`NodeHelpers` puede simplificar sus pases de compilación proporcionando soluciones listas para usar para tareas comunes de recorrido y análisis del AST. + + +Ejemplos prácticos +================== + +Apliquemos los conceptos de recorrido y modificación del AST para resolver algunos problemas prácticos. Estos ejemplos demuestran patrones comunes utilizados en los pases de compilación. + + +Adición automática de `loading="lazy"` a `` +------------------------------------------------ + +Los navegadores modernos admiten la carga diferida nativa para imágenes mediante el atributo `loading="lazy"`. Creemos un pase que agregue automáticamente este atributo a todas las etiquetas `` que aún no tengan un atributo `loading`. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; +use Latte\Compiler\Nodes\Html; + +function addLazyLoading(Nodes\TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // Podemos usar 'enter', ya que modificamos el nodo directamente + // y no dependemos de los hijos para esta decisión. + enter: function (Node $node) { + // ¿Es un elemento HTML con el nombre 'img'? + if ($node instanceof Html\ElementNode && $node->name === 'img') { + // Aseguramos que el nodo de atributos exista + $node->attributes ??= new Nodes\FragmentNode; + + // Verificamos si ya existe un atributo 'loading' (sin importar mayúsculas/minúsculas) + foreach ($node->attributes->children as $attrNode) { + if ($attrNode instanceof Html\AttributeNode + && $attrNode->name instanceof Nodes\TextNode // Nombre de atributo estático + && strtolower($attrNode->name->content) === 'loading' + ) { + return; // Ya existe, no hacer nada + } + } + + // Agregamos un espacio si los atributos no están vacíos + if ($node->attributes->children) { + $node->attributes->children[] = new Nodes\TextNode(' '); + } + + // Creamos un nuevo nodo de atributo: loading="lazy" + $node->attributes->children[] = new Html\AttributeNode( + name: new Nodes\TextNode('loading'), + value: new Nodes\TextNode('lazy'), + quote: '"', + ); + // El cambio se aplica directamente en el objeto, no es necesario devolver nada. + } + }, + ); +} +``` + +Explicación: +- El visitor `enter` busca nodos `Html\ElementNode` con el nombre `img`. +- Itera sobre los atributos existentes (`$node->attributes->children`) y comprueba si el atributo `loading` ya está presente. +- Si no se encuentra, crea un nuevo `Html\AttributeNode` que representa `loading="lazy"`. + + +Verificación de llamadas a funciones +------------------------------------ + +Los pases de compilación son la base de Latte Sandbox. Aunque el Sandbox real es sofisticado, podemos demostrar el principio básico de verificar llamadas a funciones prohibidas. + +**Objetivo:** Prevenir el uso de la función potencialmente peligrosa `shell_exec` dentro de las expresiones de la plantilla. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; +use Latte\Compiler\Nodes\Php; +use Latte\SecurityViolationException; + +function checkForbiddenFunctions(Nodes\TemplateNode $templateNode): void +{ + $forbiddenFunctions = ['shell_exec' => true, 'exec' => true]; // Lista simple + + $traverser = new NodeTraverser; + (new NodeTraverser)->traverse( + $templateNode, + enter: function (Node $node) use ($forbiddenFunctions) { + // ¿Es un nodo de llamada directa a función? + if ($node instanceof Php\Expression\FunctionCallNode + && $node->name instanceof Php\NameNode + && isset($forbiddenFunctions[strtolower((string) $node->name)]) + ) { + throw new SecurityViolationException( + "La función {$node->name}() no está permitida.", + $node->position, + ); + } + }, + ); +} +``` + +Explicación: +- Definimos una lista de nombres de funciones prohibidas. +- El visitor `enter` comprueba `FunctionCallNode`. +- Si el nombre de la función (`$node->name`) es un `NameNode` estático, verificamos su representación de cadena en minúsculas contra nuestra lista prohibida. +- Si se encuentra una función prohibida, lanzamos `Latte\SecurityViolationException`, que indica claramente una violación de la regla de seguridad y detiene la compilación. + +Estos ejemplos muestran cómo los pases de compilación que usan `NodeTraverser` pueden aprovecharse para análisis, modificaciones automáticas y aplicación de restricciones de seguridad interactuando directamente con la estructura AST de la plantilla. + + +Mejores prácticas +================= + +Al escribir pases de compilación, tenga en cuenta estas pautas para crear extensiones robustas, mantenibles y eficientes: + +- **El orden es importante:** Sea consciente del orden en que se ejecutan los pases. Si su pase depende de la estructura AST creada por otro pase (p. ej., pases base de Latte u otro pase personalizado), o si otros pases pueden depender de sus modificaciones, use el mecanismo de ordenación proporcionado por `Extension::getPasses()` para definir dependencias (`before`/`after`). Consulte la documentación de [`Extension::getPasses()` |extending-latte#getPasses] para obtener detalles. +- **Responsabilidad única:** Intente que los pases realicen una tarea bien definida. Para transformaciones complejas, considere dividir la lógica en múltiples pases, tal vez uno para análisis y otro para modificación basado en los resultados del análisis. Esto mejora la claridad y la capacidad de prueba. +- **Rendimiento:** Recuerde que los pases de compilación agregan tiempo a la compilación de la plantilla (aunque esto generalmente ocurre solo una vez, hasta que la plantilla cambia). Evite operaciones computacionalmente costosas en sus pases si es posible. Aproveche las optimizaciones de recorrido como `NodeTraverser::DontTraverseChildren` y `NodeTraverser::StopTraversal` siempre que sepa que no necesita visitar ciertas partes del AST. +- **Use `NodeHelpers`:** Para tareas comunes como encontrar nodos específicos o evaluar estáticamente expresiones simples, verifique si `Latte\Compiler\NodeHelpers` ofrece un método adecuado antes de escribir su propia lógica `NodeTraverser`. Puede ahorrar tiempo y reducir la cantidad de código preparatorio. +- **Manejo de errores:** Si su pase detecta un error o un estado inválido en el AST de la plantilla, lance `Latte\CompileException` (o `Latte\SecurityViolationException` para problemas de seguridad) con un mensaje claro y el objeto `Position` relevante (generalmente `$node->position`). Esto proporciona retroalimentación útil al desarrollador de la plantilla. +- **Idempotencia (si es posible):** Idealmente, ejecutar su pase varias veces en el mismo AST debería producir el mismo resultado que ejecutarlo una sola vez. Esto no siempre es factible, pero simplifica la depuración y el razonamiento sobre las interacciones de los pases si se logra. Por ejemplo, asegúrese de que su pase de modificación verifique si la modificación ya se ha aplicado antes de aplicarla nuevamente. + +Siguiendo estas prácticas, puede aprovechar eficazmente los pases de compilación para ampliar las capacidades de Latte de manera potente y confiable, contribuyendo a un procesamiento de plantillas más seguro, optimizado o funcionalmente más rico. diff --git a/latte/es/cookbook/@home.texy b/latte/es/cookbook/@home.texy index ca75fda004..c52c04ac36 100644 --- a/latte/es/cookbook/@home.texy +++ b/latte/es/cookbook/@home.texy @@ -1,13 +1,13 @@ -Libro de cocina -*************** +Tutoriales y procedimientos +*************************** .[perex] -Códigos de ejemplo y recetas para realizar tareas comunes con Latte. +Ejemplos de código y recetas para realizar tareas comunes con Latte. -- [Todo lo que siempre quiso saber sobre {iterateWhile} |iteratewhile] +- [Procedimientos para desarrolladores |/develop] +- [Pasar variables entre plantillas |passing-variables] +- [Todo lo que siempre quiso saber sobre la agrupación |grouping] - [¿Cómo escribir consultas SQL en Latte? |how-to-write-sql-queries-in-latte] - [Migración desde PHP |migration-from-php] - [Migración desde Twig |migration-from-twig] -- [Usando Latte con Slim 4 |slim-framework] - -{{leftbar: /@left-menu}} +- [Usar Latte con Slim 4 |slim-framework] diff --git a/latte/es/cookbook/@meta.texy b/latte/es/cookbook/@meta.texy new file mode 100644 index 0000000000..03bf4d4c26 --- /dev/null +++ b/latte/es/cookbook/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Latte Documentación}} +{{leftbar: /@left-menu}} diff --git a/latte/es/cookbook/grouping.texy b/latte/es/cookbook/grouping.texy new file mode 100644 index 0000000000..d17635e747 --- /dev/null +++ b/latte/es/cookbook/grouping.texy @@ -0,0 +1,251 @@ +Todo lo que siempre quisiste saber sobre la agrupación +****************************************************** + +.[perex] +Al trabajar con datos en plantillas, a menudo puedes encontrar la necesidad de agruparlos o mostrarlos de forma específica según ciertos criterios. Latte ofrece varias herramientas potentes para este propósito. + +El filtro y la función `|group` permiten una agrupación eficiente de datos según un criterio especificado, el filtro `|batch` facilita la división de datos en lotes de tamaño fijo y la etiqueta `{iterateWhile}` proporciona la capacidad de controlar de forma más compleja el flujo de los bucles mediante condiciones. Cada una de estas características ofrece posibilidades específicas para trabajar con datos, lo que las convierte en herramientas indispensables para la visualización dinámica y estructurada de información en las plantillas Latte. + + +Filtro y función `group` .{data-version:3.0.16} +=============================================== + +Imagina una tabla de base de datos `items` con elementos divididos en categorías: + +| id | categoryId | name +|------------------ +| 1 | 1 | Apple +| 2 | 1 | Banana +| 3 | 2 | PHP +| 4 | 3 | Green +| 5 | 3 | Red +| 6 | 3 | Blue + +Una lista simple de todos los elementos usando una plantilla Latte se vería así: + +```latte +
                                                        +{foreach $items as $item} +
                                                      • {$item->name}
                                                      • +{/foreach} +
                                                      +``` + +Sin embargo, si quisiéramos que los elementos estuvieran organizados en grupos por categoría, necesitaríamos dividirlos de modo que cada categoría tuviera su propia lista. El resultado debería verse así: + +```latte +
                                                        +
                                                      • Apple
                                                      • +
                                                      • Banana
                                                      • +
                                                      + +
                                                        +
                                                      • PHP
                                                      • +
                                                      + +
                                                        +
                                                      • Green
                                                      • +
                                                      • Red
                                                      • +
                                                      • Blue
                                                      • +
                                                      +``` + +La tarea se puede resolver fácil y elegantemente usando `|group`. Como parámetro, especificamos `categoryId`, lo que significa que los elementos se dividirán en arrays más pequeños según el valor de `$item->categoryId` (si `$item` fuera un array, se usaría `$item['categoryId']`): + +```latte +{foreach ($items|group: categoryId) as $categoryId => $categoryItems} +
                                                        + {foreach $categoryItems as $item} +
                                                      • {$item->name}
                                                      • + {/foreach} +
                                                      +{/foreach} +``` + +El filtro también se puede usar en Latte como una función, lo que nos da una sintaxis alternativa: `{foreach group($items, categoryId) ...}`. + +Si deseas agrupar elementos según criterios más complejos, puedes usar una función en el parámetro del filtro. Por ejemplo, agrupar elementos por la longitud del nombre se vería así: + +```latte +{foreach ($items|group: fn($item) => strlen($item->name)) as $items} + ... +{/foreach} +``` + +Es importante tener en cuenta que `$categoryItems` no es un array común, sino un objeto que se comporta como un iterador. Para acceder al primer elemento del grupo, puedes usar la función [`first()` |latte:functions#first]. + +Esta flexibilidad en la agrupación de datos hace que `group` sea una herramienta excepcionalmente útil para presentar datos en plantillas Latte. + + +Bucles anidados +--------------- + +Imaginemos que tenemos una tabla de base de datos con otra columna `subcategoryId`, que define las subcategorías de los elementos individuales. Queremos mostrar cada categoría principal en una lista `
                                                        ` separada y cada subcategoría en una lista `
                                                          ` anidada separada: + +```latte +{foreach ($items|group: categoryId) as $categoryItems} +
                                                            + {foreach ($categoryItems|group: subcategoryId) as $subcategoryItems} +
                                                              + {foreach $subcategoryItems as $item} +
                                                            1. {$item->name} + {/foreach} +
                                                            + {/foreach} +
                                                          +{/foreach} +``` + + +Conexión con Nette Database +--------------------------- + +Veamos cómo utilizar eficazmente la agrupación de datos en combinación con Nette Database. Supongamos que estamos trabajando con la tabla `items` del ejemplo introductorio, que está conectada a través de la columna `categoryId` con esta tabla `categories`: + +| categoryId | name | +|------------|------------| +| 1 | Fruits | +| 2 | Languages | +| 3 | Colors | + +Cargamos los datos de la tabla `items` usando Nette Database Explorer con el comando `$items = $db->table('items');`. Durante la iteración sobre estos datos, tenemos la posibilidad de acceder no solo a atributos como `$item->name` y `$item->categoryId`, sino también, gracias a la conexión con la tabla `categories`, a la fila relacionada en ella a través de `$item->category`. Esta conexión permite un uso interesante: + +```latte +{foreach ($items|group: category) as $category => $categoryItems} +

                                                          {$category->name}

                                                          +
                                                            + {foreach $categoryItems as $item} +
                                                          • {$item->name}
                                                          • + {/foreach} +
                                                          +{/foreach} +``` + +En este caso, usamos el filtro `|group` para agrupar según la fila conectada `$item->category`, no solo según la columna `categoryId`. Gracias a esto, en la variable clave tenemos directamente el `ActiveRow` de la categoría dada, lo que nos permite imprimir directamente su nombre usando `{$category->name}`. Este es un ejemplo práctico de cómo la agrupación puede aclarar las plantillas y facilitar el trabajo con los datos. + + +Filtro `|batch` +=============== + +El filtro permite dividir una lista de elementos en grupos con un número predeterminado de elementos. Este filtro es ideal para situaciones en las que deseas presentar datos en varios grupos más pequeños, por ejemplo, para una mejor claridad u organización visual en la página. + +Imaginemos que tenemos una lista de elementos y queremos mostrarlos en listas donde cada una contenga como máximo tres elementos. El uso del filtro `|batch` es muy práctico en tal caso: + +```latte +
                                                            +{foreach ($items|batch: 3) as $batch} + {foreach $batch as $item} +
                                                          • {$item->name}
                                                          • + {/foreach} +{/foreach} +
                                                          +``` + +En este ejemplo, la lista `$items` se divide en grupos más pequeños, donde cada grupo (`$batch`) contiene hasta tres elementos. Cada grupo se muestra luego en una lista `
                                                            ` separada. + +Si el último grupo no contiene suficientes elementos para alcanzar el número deseado, el segundo parámetro del filtro permite definir con qué se rellenará este grupo. Esto es ideal para alinear estéticamente los elementos donde una fila incompleta podría parecer desordenada. + +```latte +{foreach ($items|batch: 3, '—') as $batch} + ... +{/foreach} +``` + + +Etiqueta `{iterateWhile}` +========================= + +Las mismas tareas que resolvimos con el filtro `|group`, las mostraremos usando la etiqueta `{iterateWhile}`. La principal diferencia entre ambos enfoques es que `group` primero procesa y agrupa todos los datos de entrada, mientras que `{iterateWhile}` controla el flujo del bucle mediante condiciones, por lo que la iteración se realiza de forma progresiva. + +Primero, renderizamos la tabla con categorías usando `iterateWhile`: + +```latte +{foreach $items as $item} +
                                                              + {iterateWhile} +
                                                            • {$item->name}
                                                            • + {/iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                                                            +{/foreach} +``` + +Mientras que `{foreach}` marca la parte externa del bucle (la renderización de listas para cada categoría), la etiqueta `{iterateWhile}` marca la parte interna (los elementos individuales). La condición en la etiqueta de cierre indica que la repetición continuará mientras el elemento actual y el siguiente pertenezcan a la misma categoría (`$iterator->nextValue` es el [elemento siguiente |/tags#iterator]). + +Si la condición se cumpliera siempre, todos los elementos se renderizarían en el bucle interno: + +```latte +{foreach $items as $item} +
                                                              + {iterateWhile} +
                                                            • {$item->name} + {/iterateWhile true} +
                                                            +{/foreach} +``` + +El resultado se verá así: + +```latte +
                                                              +
                                                            • Apple
                                                            • +
                                                            • Banana
                                                            • +
                                                            • PHP
                                                            • +
                                                            • Green
                                                            • +
                                                            • Red
                                                            • +
                                                            • Blue
                                                            • +
                                                            +``` + +¿Para qué sirve este uso de `iterateWhile`? Cuando la tabla esté vacía y no contenga ningún elemento, no se imprimirá un `
                                                              ` vacío. + +Si especificamos la condición en la etiqueta de apertura `{iterateWhile}`, el comportamiento cambia: la condición (y el paso al siguiente elemento) se evalúa al principio del bucle interno, no al final. Por lo tanto, mientras que siempre se entra en `{iterateWhile}` sin condición, solo se entra en `{iterateWhile $cond}` si se cumple la condición `$cond`. Y al mismo tiempo, el siguiente elemento se asigna a `$item`. + +Esto es útil, por ejemplo, en una situación en la que queramos renderizar el primer elemento de cada categoría de manera diferente, como en este ejemplo: + +```latte +

                                                              Apple

                                                              +
                                                                +
                                                              • Banana
                                                              • +
                                                              + +

                                                              PHP

                                                              +
                                                                +
                                                              + +

                                                              Green

                                                              +
                                                                +
                                                              • Red
                                                              • +
                                                              • Blue
                                                              • +
                                                              +``` + +Modificamos el código original para que primero renderice el primer elemento y luego, en el bucle interno `{iterateWhile}`, renderice los demás elementos de la misma categoría: + +```latte +{foreach $items as $item} +

                                                              {$item->name}

                                                              +
                                                                + {iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                                                              • {$item->name}
                                                              • + {/iterateWhile} +
                                                              +{/foreach} +``` + +Dentro de un mismo bucle `{foreach}`, podemos crear múltiples bucles internos `{iterateWhile}` e incluso anidarlos. De esta manera, se podrían agrupar, por ejemplo, subcategorías. + +Supongamos que en la tabla hay otra columna `subcategoryId` y, además de que cada categoría esté en un `
                                                                ` separado, cada subcategoría debe estar en un `
                                                                  ` separado: + +```latte +{foreach $items as $item} +
                                                                    + {iterateWhile} +
                                                                      + {iterateWhile} +
                                                                    1. {$item->name} + {/iterateWhile $item->subcategoryId === $iterator->nextValue->subcategoryId} +
                                                                    + {/iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                                                                  +{/foreach} +``` diff --git a/latte/es/cookbook/how-to-write-sql-queries-in-latte.texy b/latte/es/cookbook/how-to-write-sql-queries-in-latte.texy index c4f97860af..11140022f6 100644 --- a/latte/es/cookbook/how-to-write-sql-queries-in-latte.texy +++ b/latte/es/cookbook/how-to-write-sql-queries-in-latte.texy @@ -4,7 +4,7 @@ .[perex] Latte también puede ser útil para generar consultas SQL realmente complejas. -Si la creación de una consulta SQL contiene muchas condiciones y variables, puede ser realmente más claro escribirla en Latte. Un ejemplo muy sencillo: +Si la creación de una consulta SQL implica numerosas condiciones y variables, puede resultar mucho más claro escribirla en Latte. Un ejemplo muy sencillo: ```latte SELECT users.* FROM users @@ -14,8 +14,7 @@ SELECT users.* FROM users WHERE groups.name = 'Admins' {ifset $country} AND country.name = {$country} {/ifset} ``` -Usando `$latte->setContentType()` le decimos a Latte que trate el contenido como texto plano (no como HTML) y -luego preparamos una función de escape que escape las cadenas directamente por el controlador de la base de datos: +Mediante `$latte->setContentType()` indicamos a Latte que trate el contenido como texto plano (no como HTML) y, además, preparamos una función de escape que escapará las cadenas directamente utilizando el *driver* de la base de datos: ```php $db = new PDO(/* ... */); @@ -27,17 +26,15 @@ $latte->addFilter('escape', fn($val) => match (true) { is_int($val), is_float($val) => (string) $val, is_bool($val) => $val ? '1' : '0', is_null($val) => 'NULL', - default => throw new Exception('Unsupported type'), + default => throw new Exception('Tipo no soportado'), }); ``` -El uso sería el siguiente: +El uso se vería así: ```php $sql = $latte->renderToString('query.sql.latte', ['country' => $country]); $result = $db->query($sql); ``` -*Este ejemplo requiere Latte v3.0.5 o superior.* - -{{leftbar: /@left-menu}} +*El ejemplo proporcionado requiere Latte v3.0.5 o superior.* diff --git a/latte/es/cookbook/iteratewhile.texy b/latte/es/cookbook/iteratewhile.texy deleted file mode 100644 index 80e27fc269..0000000000 --- a/latte/es/cookbook/iteratewhile.texy +++ /dev/null @@ -1,214 +0,0 @@ -Todo lo que siempre quiso saber sobre {iterateWhile} -**************************************************** - -.[perex] -La etiqueta `{iterateWhile}` es adecuada para varios trucos en ciclos foreach. - -Supongamos que tenemos la siguiente tabla de base de datos, donde los elementos se dividen en categorías: - -| id | catId | name -|------------------ -| 1 | 1 | Apple -| 2 | 1 | Banana -| 3 | 2 | PHP -| 4 | 3 | Green -| 5 | 3 | Red -| 6 | 3 | Blue - -Por supuesto, dibujar elementos en un bucle foreach como una lista es fácil: - -```latte -
                                                                    -{foreach $items as $item} -
                                                                  • {$item->name}
                                                                  • -{/foreach} -
                                                                  -``` - -Pero, ¿qué hacer si se desea representar cada categoría en una lista separada? En otras palabras, cómo resolver la tarea de agrupar elementos de una lista lineal en un ciclo foreach. La salida debería tener este aspecto: - -```latte -
                                                                    -
                                                                  • Apple
                                                                  • -
                                                                  • Banana
                                                                  • -
                                                                  - -
                                                                    -
                                                                  • PHP
                                                                  • -
                                                                  - -
                                                                    -
                                                                  • Green
                                                                  • -
                                                                  • Red
                                                                  • -
                                                                  • Blue
                                                                  • -
                                                                  -``` - -Le mostraremos con qué facilidad y elegancia se puede resolver la tarea con iterateWhile: - -```latte -{foreach $items as $item} -
                                                                    - {iterateWhile} -
                                                                  • {$item->name}
                                                                  • - {/iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                                  -{/foreach} -``` - -Mientras que `{foreach}` marca la parte externa del ciclo, es decir, el dibujo de las listas para cada categoría, las etiquetas `{iterateWhile}` indican la parte interna, es decir, los elementos individuales. -La condición en la etiqueta final dice que la repetición continuará mientras el elemento actual y el siguiente pertenezcan a la misma categoría (`$iterator->nextValue` es [siguiente elemento |/tags#$iterator]). - -Si la condición se cumple siempre, todos los elementos se dibujan en el ciclo interior: - -```latte -{foreach $items as $item} -
                                                                    - {iterateWhile} -
                                                                  • {$item->name} - {/iterateWhile true} -
                                                                  -{/foreach} -``` - -El resultado tendrá este aspecto: - -```latte -
                                                                    -
                                                                  • Apple
                                                                  • -
                                                                  • Banana
                                                                  • -
                                                                  • PHP
                                                                  • -
                                                                  • Green
                                                                  • -
                                                                  • Red
                                                                  • -
                                                                  • Blue
                                                                  • -
                                                                  -``` - -¿Para qué sirve este uso de iterateWhile? ¿En qué se diferencia de la solución que mostramos al principio de este tutorial? La diferencia es que si la tabla está vacía y no contiene ningún elemento, no se mostrará vacía `
                                                                    `. - - -Solución sin `{iterateWhile}` .[#toc-solution-without-iteratewhile] -------------------------------------------------------------------- - -Si resolviéramos la misma tarea con construcciones completamente básicas de sistemas de plantillas, por ejemplo en Twig, Blade, o PHP puro, la solución sería algo así: - -```latte -{var $prevCatId = null} -{foreach $items as $item} - {if $item->catId !== $prevCatId} - {* the category has changed *} - - {* we close the previous
                                                                      , if it is not the first item *} - {if $prevCatId !== null} -
                                                                    - {/if} - - {* we will open a new list *} -
                                                                      - - {do $prevCatId = $item->catId} - {/if} - -
                                                                    • {$item->name}
                                                                    • -{/foreach} - -{if $prevCatId !== null} - {* we close the last list *} -
                                                                    -{/if} -``` - -Sin embargo, este código es incomprensible y poco intuitivo. La conexión entre las etiquetas HTML de apertura y cierre no está nada clara. No está claro a primera vista si hay un error. Y requiere variables auxiliares como `$prevCatId`. - -En cambio, la solución con `{iterateWhile}` es limpia, clara, no necesita variables auxiliares y es infalible. - - -Condición en la etiqueta de cierre .[#toc-condition-in-the-closing-tag] ------------------------------------------------------------------------ - -Si especificamos una condición en la etiqueta de apertura `{iterateWhile}`, el comportamiento cambia: la condición (y el avance al elemento siguiente) se ejecuta al principio del ciclo interno, no al final. -Así, mientras que `{iterateWhile}` sin condición se introduce siempre, `{iterateWhile $cond}` se introduce sólo cuando se cumple la condición `$cond`. Al mismo tiempo, el siguiente elemento se escribe en `$item`. - -Esto es útil, por ejemplo, en una situación en la que se desea renderizar el primer elemento de cada categoría de una forma diferente, como: - -```latte -

                                                                    Apple

                                                                    -
                                                                      -
                                                                    • Banana
                                                                    • -
                                                                    - -

                                                                    PHP

                                                                    -
                                                                      -
                                                                    - -

                                                                    Green

                                                                    -
                                                                      -
                                                                    • Red
                                                                    • -
                                                                    • Blue
                                                                    • -
                                                                    -``` - -Modifiquemos el código original, dibujamos el primer elemento y luego los elementos adicionales de la misma categoría en el bucle interno `{iterateWhile}`: - -```latte -{foreach $items as $item} -

                                                                    {$item->name}

                                                                    -
                                                                      - {iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                                    • {$item->name}
                                                                    • - {/iterateWhile} -
                                                                    -{/foreach} -``` - - -Bucles anidados .[#toc-nested-loops] ------------------------------------- - -Podemos crear varios bucles internos en un ciclo e incluso anidarlos. De esta forma, por ejemplo, se podrían agrupar subcategorías. - -Supongamos que hay otra columna en la tabla `subCatId` y además de que cada categoría esté en una separada `
                                                                      `, cada subcategoría estará en un `
                                                                        `: - -```latte -{foreach $items as $item} -
                                                                          - {iterateWhile} -
                                                                            - {iterateWhile} -
                                                                          1. {$item->name} - {/iterateWhile $item->subCatId === $iterator->nextValue->subCatId} -
                                                                          - {/iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                                        -{/foreach} -``` - - -Filtro |batch .[#toc-filter-batch] ----------------------------------- - -La agrupación de elementos lineales también se realiza mediante un filtro `batch`, en lotes con un número fijo de elementos: - -```latte -
                                                                          -{foreach ($items|batch:3) as $batch} - {foreach $batch as $item} -
                                                                        • {$item->name}
                                                                        • - {/foreach} -{/foreach} -
                                                                        -``` - -Puede sustituirse por iterateWhile de la siguiente manera: - -```latte -
                                                                          -{foreach $items as $item} - {iterateWhile} -
                                                                        • {$item->name}
                                                                        • - {/iterateWhile $iterator->counter0 % 3} -{/foreach} -
                                                                        -``` - -{{leftbar: /@left-menu}} diff --git a/latte/es/cookbook/migration-from-php.texy b/latte/es/cookbook/migration-from-php.texy index ecad648049..6f3cd6e4ad 100644 --- a/latte/es/cookbook/migration-from-php.texy +++ b/latte/es/cookbook/migration-from-php.texy @@ -2,27 +2,27 @@ Migración de PHP a Latte ************************ .[perex] -¿Está migrando un viejo proyecto escrito en PHP puro a Latte? Tenemos una herramienta para facilitar la migración. [Pruébela en línea |https://php2latte.nette.org]. +¿Estás convirtiendo un proyecto antiguo escrito en PHP puro a Latte? Tenemos una herramienta para ti que facilitará la migración. [Pruébala en línea |https://fiddle.nette.org/php2latte/]. -Puedes descargar la herramienta desde [GitHub |https://github.com/nette/latte-tools] o instalarla usando Composer: +Puedes descargar la herramienta desde [GitHub|https://github.com/nette/latte-tools] o instalarla usando Composer: ```shell composer create-project latte/tools ``` -El conversor no utiliza sustituciones simples de expresiones regulares, sino que utiliza directamente el analizador sintáctico de PHP, por lo que puede manejar cualquier sintaxis compleja. +El convertidor no utiliza reemplazos simples mediante expresiones regulares, sino que aprovecha directamente el *parser* de PHP, por lo que puede manejar sintaxis de cualquier complejidad. -El script `php-to-latte.php` se utiliza para convertir de PHP a Latte: +Para la conversión de PHP a Latte se utiliza el script `php-to-latte.php`: ```shell -php-to-latte.php input.php [output.latte] +php php-to-latte.php input.php [output.latte] ``` -Ejemplo .[#toc-example] ------------------------ +Ejemplo +------- -El archivo de entrada podría tener este aspecto (forma parte del código del foro PunBB): +El archivo de entrada puede tener este aspecto (es parte del código del foro PunBB): ```php

                                                                        @@ -48,7 +48,7 @@ foreach ($result as $cur_group) {
                                                          ``` -Genera esta plantilla: +Genera esta plantilla Latte: ```latte

                                                          {$lang_common['User list']}

                                                          @@ -68,5 +68,3 @@ Genera esta plantilla:
                                                          ``` - -{{leftbar: /@left-menu}} diff --git a/latte/es/cookbook/migration-from-twig.texy b/latte/es/cookbook/migration-from-twig.texy index c630d9083b..443a6f381f 100644 --- a/latte/es/cookbook/migration-from-twig.texy +++ b/latte/es/cookbook/migration-from-twig.texy @@ -2,37 +2,37 @@ Migración de Twig a Latte ************************* .[perex] -¿Estás migrando un proyecto escrito en Twig al más moderno Latte? Tenemos una herramienta para facilitar la migración. [Pruébala en línea |https://twig2latte.nette.org]. +¿Estás convirtiendo un proyecto escrito en Twig al más moderno Latte? Tenemos una herramienta para ti que facilitará la migración. [Pruébala en línea |https://fiddle.nette.org/twig2latte/]. -Puedes descargar la herramienta desde [GitHub |https://github.com/nette/latte-tools] o instalarla usando Composer: +Puedes descargar la herramienta desde [GitHub|https://github.com/nette/latte-tools] o instalarla usando Composer: ```shell composer create-project latte/tools ``` -El conversor no utiliza sustituciones simples de expresiones regulares, sino que utiliza directamente el analizador sintáctico de Twig, por lo que puede manejar cualquier sintaxis compleja. +El convertidor no utiliza reemplazos simples mediante expresiones regulares, sino que aprovecha directamente el *parser* de Twig, por lo que puede manejar sintaxis de cualquier complejidad. -Se utiliza un script `twig-to-latte.php` para convertir de Twig a Latte: +Para la conversión de Twig a Latte se utiliza el script `twig-to-latte.php`: ```shell -twig-to-latte.php input.twig.html [output.latte] +php twig-to-latte.php input.twig.html [output.latte] ``` -Conversión .[#toc-conversion] ------------------------------ +Conversión +---------- -La conversión requiere la edición manual del resultado, ya que la conversión no puede hacerse de forma inequívoca. Twig utiliza la sintaxis de puntos, en la que `{{ a.b }}` puede significar `$a->b`, `$a['b']` o `$a->getB()`, que no pueden distinguirse durante la compilación. Por ello, el conversor convierte todo a `$a->b`. +La conversión presupone una revisión manual del resultado, ya que no siempre se puede realizar de forma inequívoca. Twig utiliza la sintaxis de puntos, donde `{{ a.b }}` puede significar `$a->b`, `$a['b']` o `$a->getB()`, lo cual no se puede distinguir durante la compilación. Por lo tanto, el convertidor convierte todo a `$a->b`. -Algunas funciones, filtros o etiquetas no tienen equivalente en Latte, o pueden comportarse de forma ligeramente diferente. +Algunas funciones, filtros o etiquetas pueden no tener un equivalente directo en Latte, o pueden comportarse de manera ligeramente diferente. -Ejemplo .[#toc-example] ------------------------ +Ejemplo +------- -El archivo de entrada podría tener este aspecto: +El archivo de entrada puede tener este aspecto: -```latte +```twig {% use "blocks.twig" %} @@ -54,7 +54,7 @@ El archivo de entrada podría tener este aspecto: ``` -Después de convertir a Latte, obtenemos esta plantilla: +Después de la conversión a Latte, obtenemos esta plantilla: ```latte {import 'blocks.latte'} @@ -77,5 +77,3 @@ Después de convertir a Latte, obtenemos esta plantilla: ``` - -{{leftbar: /@left-menu}} diff --git a/latte/es/cookbook/passing-variables.texy b/latte/es/cookbook/passing-variables.texy new file mode 100644 index 0000000000..21ed20c634 --- /dev/null +++ b/latte/es/cookbook/passing-variables.texy @@ -0,0 +1,158 @@ +Paso de variables entre plantillas +********************************** + +Esta guía te explicará cómo se pasan las variables entre plantillas en Latte usando diferentes etiquetas como `{include}`, `{import}`, `{embed}`, `{layout}`, `{sandbox}` y otras. También aprenderás cómo trabajar con variables en las etiquetas `{block}` y `{define}`, y para qué sirve la etiqueta `{parameters}`. + + +Tipos de variables +------------------ +Las variables en Latte se pueden dividir en tres categorías según cómo y dónde se definen: + +**Variables de entrada**: Son aquellas que se pasan a la plantilla desde el exterior, por ejemplo, desde un script PHP o mediante una etiqueta como `{include}`. + +```php +$latte->render('template.latte', ['userName' => 'Jan', 'userAge' => 30]); +``` + +**Variables de entorno**: Son las variables que existen en el lugar donde se utiliza una etiqueta específica. Incluyen todas las variables de entrada y otras variables creadas mediante etiquetas como `{var}`, `{default}` o dentro de un bucle `{foreach}`. + +```latte +{foreach $users as $user} + {include 'userBox.latte', user: $user} +{/foreach} +``` + +**Variables explícitas**: Son aquellas que se especifican directamente dentro de la etiqueta y se envían a la plantilla de destino. + +```latte +{include 'userBox.latte', name: $user->name, age: $user->age} +``` + + +`{block}` +--------- +La etiqueta `{block}` se utiliza para definir bloques de código reutilizables que se pueden personalizar o ampliar en plantillas heredadas. Las variables de entorno definidas antes del bloque están disponibles dentro del bloque, pero cualquier modificación de estas variables solo tendrá efecto dentro del ámbito de ese bloque. + +```latte +{var $foo = 'původní'} +{block example} + {var $foo = 'změněný'} +{/block} + +{$foo} // imprime: `původní` +``` + + +`{define}` +---------- +La etiqueta `{define}` sirve para crear bloques que se renderizan solo cuando son llamados mediante `{include}`. Las variables disponibles dentro de estos bloques dependen de si se especifican parámetros en su definición. Si se especifican parámetros, solo tienen acceso a dichos parámetros. Si no se especifican parámetros, tienen acceso a todas las variables de entrada de la plantilla donde se define el bloque. + +```latte +{define hello} + {* tiene acceso a todas las variables de entrada de la plantilla *} +{/define} + +{define hello $name} + {* solo tiene acceso al parámetro `$name` *} +{/define} +``` + + +`{parameters}` +-------------- +La etiqueta `{parameters}` sirve para declarar explícitamente las variables de entrada esperadas al principio de la plantilla. De esta manera, se pueden documentar fácilmente las variables esperadas y sus tipos de datos. También es posible definir valores predeterminados. + +```latte +{parameters int $age, string $name = 'neznámé'} +

                                                          Edad: {$age}, Nombre: {$name}

                                                          +``` + + +`{include file}` +---------------- +La etiqueta `{include file}` sirve para insertar una plantilla completa. A esta plantilla se le pasan tanto las variables de entrada de la plantilla donde se usa la etiqueta, como las variables definidas explícitamente en la propia etiqueta `{include}`. Sin embargo, la plantilla de destino puede limitar el alcance de las variables disponibles usando `{parameters}`. + +```latte +{include 'profile.latte', userId: $user->id} +``` + + +`{include block}` +----------------- +Cuando insertas un bloque definido en la misma plantilla, se le pasan todas las variables de entorno y las definidas explícitamente: + +```latte +{define blockName} +

                                                          Nombre: {$name}, Edad: {$age}

                                                          +{/define} + +{var $name = 'Jan', $age = 30} +{include blockName} +``` + +En este ejemplo, las variables `$name` y `$age` se pasan al bloque `blockName`. De la misma manera se comporta `{include parent}`. + +Al insertar un bloque desde otra plantilla (usando `{include block from 'file.latte'}`), solo se pasan las variables de entrada de la plantilla actual y las definidas explícitamente en la etiqueta `{include}`. Las variables de entorno no están disponibles automáticamente. + +```latte +{include blockInOtherTemplate, name: $name, age: $age} +``` + + +`{layout}` o `{extends}` +------------------------ +Estas etiquetas definen el *layout* (plantilla padre), al que se pasan las variables de entrada de la plantilla hija y, además, las variables creadas en el código de la plantilla hija antes de los bloques `{block}`: + +```latte +{layout 'layout.latte'} +{var $seo = 'index, follow'} +``` + +Plantilla `layout.latte`: + +```latte + + + +``` + + +`{embed}` +--------- +La etiqueta `{embed}` es similar a la etiqueta `{include}`, pero permite incrustar bloques definidos dentro de ella en la plantilla de destino. A diferencia de `{include file}`, solo se pasan las variables declaradas explícitamente en la etiqueta `{embed}`: + +```latte +{embed 'menu.latte', items: $menuItems} +{/embed} +``` + +En este ejemplo, la plantilla `menu.latte` solo tiene acceso a la variable `$items`. + +Por el contrario, los bloques `{block}` definidos dentro de `{embed}` tienen acceso a todas las variables de entorno del lugar donde se usa `{embed}`: + +```latte +{var $name = 'Jan'} +{embed 'menu.latte', items: $menuItems} + {block foo} + {$name} + {/block} +{/embed} +``` + + +`{import}` +---------- +La etiqueta `{import}` se utiliza para importar bloques desde otras plantillas. A los bloques importados se les pasan tanto las variables de entrada de la plantilla actual como las declaradas explícitamente en la etiqueta `{import}`. + +```latte +{import 'buttons.latte'} +``` + + +`{sandbox}` +----------- +La etiqueta `{sandbox}` aísla la plantilla para un procesamiento seguro. Las variables se pasan exclusivamente de forma explícita. + +```latte +{sandbox 'secure.latte', data: $secureData} +``` diff --git a/latte/es/cookbook/slim-framework.texy b/latte/es/cookbook/slim-framework.texy index c277345381..7a2a59103b 100644 --- a/latte/es/cookbook/slim-framework.texy +++ b/latte/es/cookbook/slim-framework.texy @@ -2,42 +2,42 @@ Uso de Latte con Slim 4 *********************** .[perex] -Este artículo escrito por "Daniel Opitz":https://odan.github.io/2022/04/06/slim4-latte.html describe cómo utilizar Latte con el Slim Framework. +Este artículo, cuyo autor es "Daniel Opitz":https://odan.github.io/2022/04/06/slim4-latte.html, describe el uso de Latte con Slim Framework. -Primero, "instala el Slim Framework":https://odan.github.io/2019/11/05/slim4-tutorial.html y luego Latte usando Composer: +Primero, "instalar Slim Framework":https://odan.github.io/2019/11/05/slim4-tutorial.html y luego Latte usando Composer: ```shell composer require latte/latte ``` -Configuración .[#toc-configuration] ------------------------------------ +Configuración +------------- -Cree un nuevo directorio `templates` en el directorio raíz de su proyecto. Todas las plantillas se colocarán allí más tarde. +En el directorio raíz del proyecto, cree un nuevo directorio `templates`. Todas las plantillas se ubicarán allí más tarde. -Añada una nueva clave de configuración `template` en su archivo `config/defaults.php`: +En el archivo `config/defaults.php`, agregue una nueva clave de configuración `template`: ```php $settings['template'] = __DIR__ . '/../templates'; ``` -Latte compila las plantillas a código PHP nativo y las almacena en una caché en el disco. Así son tan rápidas como si hubieran sido escritas en PHP nativo. +Latte compila las plantillas en código PHP nativo y las almacena en caché en el disco. Por lo tanto, son tan rápidas como si estuvieran escritas en lenguaje PHP nativo. -Añade una nueva clave de configuración `template_temp` en tu archivo `config/defaults.php`: Asegúrate de que el directorio `{project}/tmp/templates` existe y tiene permisos de acceso de lectura y escritura. +En el archivo `config/defaults.php`, agregue una nueva clave de configuración `template_temp`: Asegúrese de que el directorio `{project}/tmp/templates` exista y tenga permisos de lectura y escritura. ```php $settings['template_temp'] = __DIR__ . '/../tmp/templates'; ``` -Latte regenera automáticamente la caché cada vez que cambia la plantilla, lo que puede desactivarse en el entorno de producción para ahorrar un poco de rendimiento: +Latte regenera automáticamente la caché cada vez que se cambia la plantilla, lo que se puede desactivar en el entorno de producción para ahorrar un poco de rendimiento: ```php -// change to false in the production environment +// en entorno de producción cambiar a false $settings['template_auto_refresh'] = true; ``` -A continuación, añada una definición de contenedor DI para la clase `Latte\Engine`. +A continuación, agregue la definición del contenedor DI para la clase `Latte\Engine`. ```php +
                                                            {foreach $items as $item}
                                                          • {$item|capitalize}
                                                          • {/foreach}
                                                          ``` -Si todo está configurado correctamente deberías ver la siguiente salida: +Si todo está configurado correctamente, debería mostrarse la siguiente salida: ```latte One @@ -155,4 +155,3 @@ Three ``` {{priority: -1}} -{{leftbar: /@left-menu}} diff --git a/latte/es/creating-extension.texy b/latte/es/creating-extension.texy deleted file mode 100644 index 6e662038e0..0000000000 --- a/latte/es/creating-extension.texy +++ /dev/null @@ -1,579 +0,0 @@ -Creación de una extensión -************************* - -.[perex]{data-version:3.0} -Una extensión es una clase reutilizable que puede definir etiquetas personalizadas, filtros, funciones, proveedores, etc. - -Creamos extensiones cuando queremos reutilizar nuestras personalizaciones Latte en diferentes proyectos o compartirlas con otros. -También es útil crear una extensión para cada proyecto web que contendrá todas las etiquetas y filtros específicos que queremos utilizar en las plantillas del proyecto. - - -Clase de extensión .[#toc-extension-class] -========================================== - -Extension es una clase que hereda de [api:Latte\Extension]. Se registra con Latte utilizando `addExtension()` (o a través de [un archivo de configuración |application:configuration#Latte]): - -```php -$latte = new Latte\Engine; -$latte->addExtension(new MyLatteExtension); -``` - -Si registras varias extensiones y éstas definen etiquetas, filtros o funciones con nombres idénticos, gana la última extensión añadida. Esto también implica que tus extensiones pueden anular etiquetas/filtros/funciones nativas. - -Cada vez que realice un cambio en una clase y la actualización automática no esté desactivada, Latte recompilará automáticamente sus plantillas. - -Una clase puede implementar cualquiera de los siguientes métodos: - -```php -abstract class Extension -{ - /** - * Initializes before template is compiler. - */ - public function beforeCompile(Engine $engine): void; - - /** - * Returns a list of parsers for Latte tags. - * @return array - */ - public function getTags(): array; - - /** - * Returns a list of compiler passes. - * @return array - */ - public function getPasses(): array; - - /** - * Returns a list of |filters. - * @return array - */ - public function getFilters(): array; - - /** - * Returns a list of functions used in templates. - * @return array - */ - public function getFunctions(): array; - - /** - * Returns a list of providers. - * @return array - */ - public function getProviders(): array; - - /** - * Returns a value to distinguish multiple versions of the template. - */ - public function getCacheKey(Engine $engine): mixed; - - /** - * Initializes before template is rendered. - */ - public function beforeRender(Template $template): void; -} -``` - -Para hacerse una idea del aspecto de la extensión, eche un vistazo a la "CoreExtension":https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php incorporada. - - -beforeCompile(Latte\Engine $engine): void .[method] ---------------------------------------------------- - -Llamado antes de que la plantilla sea compilada. El método se puede utilizar para inicializaciones relacionadas con la compilación, por ejemplo. - - -getTags(): array .[method] --------------------------- - -Se ejecuta cuando se compila la plantilla. Devuelve un array asociativo *nombre de etiqueta => callable*, que son [funciones de análisis de etiquetas |#Tag Parsing Function]. - -```php -public function getTags(): array -{ - return [ - 'foo' => [FooNode::class, 'create'], - 'bar' => [BarNode::class, 'create'], - 'n:baz' => [NBazNode::class, 'create'], - // ... - ]; -} -``` - -La etiqueta `n:baz` representa un atributo n:puro, es decir, es una etiqueta que sólo puede escribirse como atributo. - -En el caso de las etiquetas `foo` y `bar`, Latte reconocerá automáticamente si son pares y, en caso afirmativo, podrán escribirse automáticamente utilizando atributos n:, incluidas las variantes con los prefijos `n:inner-foo` y `n:tag-foo`. - -El orden de ejecución de dichos n:attributes viene determinado por su orden en la matriz devuelta por `getTags()`. Así, `n:foo` se ejecuta siempre antes que `n:bar`, incluso si los atributos se enumeran en orden inverso en la etiqueta HTML como `
                                                          `. - -Si necesitas determinar el orden de n:atributos a través de múltiples extensiones, utiliza el método de ayuda `order()`, donde el parámetro `before` xor `after` determina qué etiquetas se ordenan antes o después de la etiqueta. - -```php -public function getTags(): array -{ - return [ - 'foo' => self::order([FooNode::class, 'create'], before: 'bar')] - 'bar' => self::order([BarNode::class, 'create'], after: ['block', 'snippet'])] - ]; -} -``` - - -getPasses(): array .[method] ----------------------------- - -Se llama cuando se compila la plantilla. Devuelve un array asociativo *name pass => callable*, que son funciones que representan los llamados [pases del compilador |#compiler passes] que recorren y modifican el AST. - -De nuevo, se puede utilizar el método de ayuda `order()`. El valor de los parámetros `before` o `after` puede ser `*` con el significado antes/después de todo. - -```php -public function getPasses(): array -{ - return [ - 'optimize' => [Passes::class, 'optimizePass'], - 'sandbox' => self::order([$this, 'sandboxPass'], before: '*'), - // ... - ]; -} -``` - - -beforeRender(Latte\Engine $engine): void .[method] --------------------------------------------------- - -Se llama antes de cada renderización de la plantilla. El método se puede utilizar, por ejemplo, para inicializar las variables utilizadas durante el renderizado. - - -getFilters(): array .[method] ------------------------------ - -Se llama antes de renderizar la plantilla. Devuelve [los filtros |extending-latte#filters] como un array asociativo *nombre del filtro => callable*. - -```php -public function getFilters(): array -{ - return [ - 'batch' => [$this, 'batchFilter'], - 'trim' => [$this, 'trimFilter'], - // ... - ]; -} -``` - - -getFunctions(): array .[method] -------------------------------- - -Se llama antes de renderizar la plantilla. Devuelve [funciones |extending-latte#functions] como una matriz asociativa *nombre de función => invocable*. - -```php -public function getFunctions(): array -{ - return [ - 'clamp' => [$this, 'clampFunction'], - 'divisibleBy' => [$this, 'divisibleByFunction'], - // ... - ]; -} -``` - - -getProviders(): array .[method] -------------------------------- - -Se llama antes de renderizar la plantilla. Devuelve un array de proveedores, que suelen ser objetos que utilizan etiquetas en tiempo de ejecución. Se accede a ellos a través de `$this->global->...`. - -```php -public function getProviders(): array -{ - return [ - 'myFoo' => $this->foo, - 'myBar' => $this->bar, - // ... - ]; -} -``` - - -getCacheKey(Latte\Engine $engine): mixed .[method] --------------------------------------------------- - -Se llama antes de renderizar la plantilla. El valor de retorno pasa a formar parte de la clave cuyo hash está contenido en el nombre del fichero de plantilla compilado. Así, para diferentes valores de retorno, Latte generará diferentes archivos de caché. - - -¿Cómo funciona Latte? .[#toc-how-does-latte-work] -================================================= - -Para entender cómo definir etiquetas personalizadas o pases de compilador, es esencial entender cómo funciona Latte bajo el capó. - -La compilación de plantillas en Latte funciona de la siguiente manera: - -- En primer lugar, el **lexer** tokeniza el código fuente de la plantilla en pequeños trozos (tokens) para facilitar su procesamiento. -- A continuación, el **parser** convierte el flujo de tokens en un árbol de nodos con sentido (el Árbol de Sintaxis Abstracta, AST). -- Finalmente, el compilador **genera** una clase PHP a partir del AST que renderiza la plantilla y la almacena en caché. - -En realidad, la compilación es un poco más complicada. Latte **tiene dos** lexers y parsers: uno para la plantilla HTML y otro para el código PHP dentro de las etiquetas. Además, el análisis sintáctico no se ejecuta después de la tokenización, sino que el lexer y el parser se ejecutan en paralelo en dos "hilos" y se coordinan. Es ciencia de cohetes :-) - -Además, todas las etiquetas tienen sus propias rutinas de análisis. Cuando el analizador encuentra una etiqueta, llama a su función de análisis (devuelve [Extension::getTags() |#getTags]). -Su trabajo consiste en analizar los argumentos de la etiqueta y, en el caso de etiquetas emparejadas, el contenido interno. Devuelve un *nodo* que pasa a formar parte del AST. Véase [Función de análisis sintáctico de etiquetas |#Tag parsing function] para más detalles. - -Cuando el analizador termina su trabajo, tenemos un AST completo que representa la plantilla. El nodo raíz es `Latte\Compiler\Nodes\TemplateNode`. Los nodos individuales dentro del árbol representan no sólo las etiquetas, sino también los elementos HTML, sus atributos, cualquier expresión usada dentro de las etiquetas, etc. - -Después de esto, entran en juego los llamados [pases del compilador |#Compiler passes], que son funciones (devueltas por [Extension::getPasses() |#getPasses]) que modifican el AST. - -Todo el proceso, desde la carga del contenido de la plantilla, pasando por el análisis sintáctico, hasta la generación del fichero resultante, puede secuenciarse con este código, con el que puedes experimentar y volcar los resultados intermedios: - -```php -$latte = new Latte\Engine; -$source = $latte->getLoader()->getContent($file); -$ast = $latte->parse($source); -$latte->applyPasses($ast); -$code = $latte->generate($ast, $file); -``` - - -Ejemplo de AST .[#toc-example-of-ast] -------------------------------------- - -Para tener una mejor idea del AST, añadimos un ejemplo. Esta es la plantilla fuente: - -```latte -{foreach $category->getItems() as $item} -
                                                        1. {$item->name|upper}
                                                        2. - {else} - no items found -{/foreach} -``` - -Y esta es su representación en forma de AST: - -/--pre -Latte\Compiler\Nodes\TemplateNode( - Latte\Compiler\Nodes\FragmentNode( - - Latte\Essential\Nodes\ForeachNode( - expression: Latte\Compiler\Nodes\Php\Expression\MethodCallNode( - object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$category') - name: Latte\Compiler\Nodes\Php\IdentifierNode('getItems') - ) - value: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') - content: Latte\Compiler\Nodes\FragmentNode( - - Latte\Compiler\Nodes\TextNode(' ') - - Latte\Compiler\Nodes\Html\ElementNode('li')( - content: Latte\Essential\Nodes\PrintNode( - expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode( - object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') - name: Latte\Compiler\Nodes\Php\IdentifierNode('name') - ) - modifier: Latte\Compiler\Nodes\Php\ModifierNode( - filters: - - Latte\Compiler\Nodes\Php\FilterNode('upper') - ) - ) - ) - ) - else: Latte\Compiler\Nodes\FragmentNode( - - Latte\Compiler\Nodes\TextNode('no items found') - ) - ) - ) -) -\-- - - -Etiquetas personalizadas .[#toc-custom-tags] -============================================ - -Se necesitan tres pasos para definir una nueva etiqueta: - -- definir la [función de análisis |#tag parsing function] de la etiqueta (responsable de analizar la etiqueta en un nodo) -- crear una clase de nodo (responsable de [generar el código PHP |#generating PHP code] y de [recorrer la AST |#AST traversing]) -- registrar la etiqueta usando [Extension::getTags() |#getTags] - - -Función de análisis de etiquetas .[#toc-tag-parsing-function] -------------------------------------------------------------- - -El análisis de las etiquetas es manejado por su función de análisis (la devuelta por [Extension::getTags() |#getTags]). Su trabajo es analizar y comprobar cualquier argumento dentro de la etiqueta (utiliza TagParser para hacer esto). -Además, si la etiqueta es un par, pedirá a TemplateParser que analice y devuelva el contenido interior. -La función crea y devuelve un nodo, que normalmente es hijo de `Latte\Compiler\Nodes\StatementNode`, y éste pasa a formar parte del AST. - -Creamos una clase para cada nodo, lo que haremos ahora, y colocamos elegantemente la función de parseo en ella como una fábrica estática. Como ejemplo, intentemos crear la conocida etiqueta `{foreach}`: - -```php -use Latte\Compiler\Nodes\StatementNode; - -class ForeachNode extends StatementNode -{ - // a parsing function that just creates a node for now - public static function create(Latte\Compiler\Tag $tag): self - { - $node = new self; - return $node; - } - - public function print(Latte\Compiler\PrintContext $context): string - { - // code will be added later - } - - public function &getIterator(): \Generator - { - // code will be added later - } -} -``` - -A la función de análisis `create()` se le pasa un objeto [api:Latte\Compiler\Tag], que contiene información básica sobre la etiqueta (si es una etiqueta clásica o n:attribute, en qué línea está, etc.) y principalmente accede a [api:Latte\Compiler\TagParser] en `$tag->parser`. - -Si la etiqueta debe tener argumentos, comprueba su existencia llamando a `$tag->expectArguments()`. Los métodos del objeto `$tag->parser` están disponibles para analizarlos: - -- `parseExpression(): ExpressionNode` para una expresión tipo PHP (por ejemplo `10 + 3`) -- `parseUnquotedStringOrExpression(): ExpressionNode` para una expresión o cadena sin comillas -- `parseArguments(): ArrayNode` para el contenido de una matriz (por ejemplo, `10, true, foo => bar`) -- `parseModifier(): ModifierNode` para un modificador (e.g. `|upper|truncate:10`) -- `parseType(): expressionNode` para typehint (p.ej. `int|string` o `Foo\Bar[]`) - -y un [api:Latte\Compiler\TokenStream] de bajo nivel que opera directamente con tokens: - -- `$tag->parser->stream->consume(...): Token` -- `$tag->parser->stream->tryConsume(...): ?Token` - -Latte extiende la sintaxis de PHP en pequeñas formas, por ejemplo añadiendo modificadores, operadores ternarios acortados, o permitiendo escribir cadenas alfanuméricas simples sin comillas. Esta es la razón por la que usamos el término *PHP-like* en lugar de PHP. Así, el método `parseExpression()` interpreta `foo` como `'foo'`, por ejemplo. -Además, *unquoted-string* es un caso especial de una cadena que tampoco necesita ser entrecomillada, pero que al mismo tiempo no necesita ser alfanumérica. Por ejemplo, es la ruta a un archivo en la etiqueta `{include ../file.latte}`. Para analizarla se utiliza el método `parseUnquotedStringOrExpression()`. - -.[note] -Estudiar las clases de nodos que forman parte de Latte es la mejor manera de aprender todos los detalles del proceso de análisis sintáctico. - -Volvamos a la etiqueta `{foreach}`. En ella, esperamos argumentos de la forma `expression + 'as' + second expression`, que analizamos como sigue: - -```php -use Latte\Compiler\Nodes\StatementNode; -use Latte\Compiler\Nodes\Php\ExpressionNode; -use Latte\Compiler\Nodes\AreaNode; - -class ForeachNode extends StatementNode -{ - public ExpressionNode $expression; - public ExpressionNode $value; - - public static function create(Latte\Compiler\Tag $tag): self - { - $tag->expectArguments(); - $node = new self; - $node->expression = $tag->parser->parseExpression(); - $tag->parser->stream->consume('as'); - $node->value = $parser->parseExpression(); - return $node; - } -} -``` - -Las expresiones que hemos escrito en las variables `$expression` y `$value` representan subnodos. - -.[tip] -Define las variables con subnodos como **públicas** para que puedan ser modificadas en [posteriores pasos de |#Compiler Passes] procesamiento si es necesario. También es necesario **hacerlas disponibles** para [recorrerlas |#AST Traversing]. - -Para las etiquetas emparejadas, como la nuestra, el método también debe permitir que TemplateParser analice el contenido interno de la etiqueta. De esto se encarga `yield`, que devuelve un par ''[contenido interno, etiqueta final]''. Almacenamos el contenido interno en la variable `$node->content`. - -```php -public AreaNode $content; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $endTag] = yield; - return $node; -} -``` - -La palabra clave `yield` hace que el método `create()` termine, devolviendo el control al TemplateParser, que continúa analizando el contenido hasta que llega a la etiqueta final. Entonces devuelve el control a `create()`, que continúa desde donde lo dejó. El uso del método `yield`, devuelve automáticamente `Generator`. - -También puede pasar una matriz de nombres de etiquetas a `yield` para los que desea detener el análisis si aparecen antes de la etiqueta final. Esto nos ayuda a implementar la construcción `{foreach}...{else}...{/foreach}` . Si aparece `{else}`, analizamos el contenido que le sigue en `$node->elseContent`: - -```php -public AreaNode $content; -public ?AreaNode $elseContent = null; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $nextTag] = yield ['else']; - if ($nextTag?->name === 'else') { - [$node->elseContent] = yield; - } - - return $node; -} -``` - -El nodo devuelto completa el análisis de la etiqueta. - - -Generación de código PHP .[#toc-generating-php-code] ----------------------------------------------------- - -Cada nodo debe implementar el método `print()`. Devuelve código PHP que renderiza la parte dada de la plantilla (código en tiempo de ejecución). Se le pasa como parámetro un objeto [api:Latte\Compiler\PrintContext], que tiene un útil método `format()` que simplifica el ensamblaje del código resultante. - -El método `format(string $mask, ...$args)` acepta los siguientes marcadores de posición en la máscara: -- `%node` imprime Nodo -- `%dump` exporta el valor a PHP -- `%raw` inserta el texto directamente sin ninguna transformación -- `%args` imprime ArrayNode como argumentos de la llamada a la función -- `%line` imprime un comentario con un número de línea -- `%escape(...)` escapa el contenido -- `%modify(...)` aplica un modificador -- `%modifyContent(...)` aplica un modificador a los bloques - - -Nuestra función `print()` podría tener este aspecto (omitimos la rama `else` para simplificar): - -```php -public function print(Latte\Compiler\PrintContext $context): string -{ - return $context->format( - <<<'XX' - foreach (%node as %node) %line { - %node - } - - XX, - $this->expression, - $this->value, - $this->position, - $this->content, - ); -} -``` - -La variable `$this->position` ya está definida por la clase [api:Latte\Compiler\Node] y es establecida por el analizador sintáctico. Contiene un objeto [api:Latte\Compiler\Position] con la posición de la etiqueta en el código fuente en forma de número de fila y columna. - -El código en tiempo de ejecución puede utilizar variables auxiliares. Para evitar la colisión con variables utilizadas por la propia plantilla, es convención anteponerles los caracteres `$ʟ__`. - -También puede utilizar valores arbitrarios en tiempo de ejecución, que se pasan a la plantilla en forma de proveedores utilizando el método [Extension::getProviders() |#getProviders]. Se accede a ellos usando `$this->global->...`. - - -Recorrido AST .[#toc-ast-traversing] ------------------------------------- - -Para recorrer el árbol AST en profundidad, es necesario implementar el método `getIterator()`. Esto proporcionará acceso a los subnodos: - -```php -public function &getIterator(): \Generator -{ - yield $this->expression; - yield $this->value; - yield $this->content; - if ($this->elseContent) { - yield $this->elseContent; - } -} -``` - -Observe que `getIterator()` devuelve una referencia. Esto es lo que permite a los visitantes de nodos sustituir nodos individuales por otros nodos. - -.[warning] -Si un nodo tiene subnodos, es necesario implementar este método y hacer que todos los subnodos estén disponibles. De lo contrario, se podría crear un agujero de seguridad. Por ejemplo, el modo sandbox no sería capaz de controlar los subnodos y asegurar que las construcciones no permitidas no son llamadas en ellos. - -Dado que la palabra clave `yield` debe estar presente en el cuerpo del método incluso si no tiene nodos hijos, escríbalo de la siguiente manera: - -```php -public function &getIterator(): \Generator -{ - if (false) { - yield; - } -} -``` - - -El compilador pasa .[#toc-compiler-passes] -========================================== - -Los Pases de Compilador son funciones que modifican ASTs o recogen información en ellos. Son devueltos por el método [Extension::getPasses() |#getPasses]. - - -Node Traverser .[#toc-node-traverser] -------------------------------------- - -La forma más común de trabajar con el AST es utilizando un [api:Latte\Compiler\NodeTraverser]: - -```php -use Latte\Compiler\Node; -use Latte\Compiler\NodeTraverser; - -$ast = (new NodeTraverser)->traverse( - $ast, - enter: fn(Node $node) => ..., - leave: fn(Node $node) => ..., -); -``` - -La función *enter* (visitante) se ejecuta cuando se encuentra un nodo por primera vez, antes de procesar sus subnodos. La función *leave* se ejecuta cuando se han visitado todos los subnodos. -Un patrón común es que *enter* se utiliza para recoger alguna información y luego *leave* realiza modificaciones basadas en ella. En el momento en que se llama a *leave*, todo el código dentro del nodo ya habrá sido visitado y se habrá recogido la información necesaria. - -¿Cómo modificar AST? La forma más fácil es simplemente cambiar las propiedades de los nodos. La segunda forma es reemplazar el nodo por completo devolviendo un nuevo nodo. Ejemplo: el siguiente código cambiará todos los enteros del AST por cadenas (por ejemplo, 42 se cambiará por `'42'`). - -```php -use Latte\Compiler\Nodes\Php; - -$ast = (new NodeTraverser)->traverse( - $ast, - leave: function (Node $node) { - if ($node instanceof Php\Scalar\IntegerNode) { - return new Php\Scalar\StringNode((string) $node->value); - } - }, -); -``` - -Un AST puede contener fácilmente miles de nodos, y recorrerlos todos puede ser lento. En algunos casos, es posible evitar un recorrido completo. - -Si está buscando todos los `Html\ElementNode` en un árbol, sabe que una vez que ha visto `Php\ExpressionNode`, no tiene sentido comprobar también todos sus nodos hijos, porque HTML no puede estar dentro de expresiones. En este caso, puede instruir al traverser para que no recurse en el nodo de la clase: - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Php\ExpressionNode) { - return NodeTraverser::DontTraverseChildren; - } - // ... - }, -); -``` - -Si sólo está buscando un nodo específico, también es posible abortar completamente la búsqueda después de encontrarlo. - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Nodes\ParametersNode) { - return NodeTraverser::StopTraversal; - } - // ... - }, -); -``` - - -Ayudantes de nodo .[#toc-node-helpers] --------------------------------------- - -La clase [api:Latte\Compiler\NodeHelpers] proporciona algunos métodos que pueden encontrar nodos AST que satisfagan una determinada llamada de retorno, etc. Se muestran un par de ejemplos: - -```php -use Latte\Compiler\NodeHelpers; - -// finds all HTML element nodes -$elements = NodeHelpers::find($ast, fn(Node $node) => $node instanceof Nodes\Html\ElementNode); - -// finds first text node -$text = NodeHelpers::findFirst($ast, fn(Node $node) => $node instanceof Nodes\TextNode); - -// converts PHP value node to real value -$value = NodeHelpers::toValue($node); - -// converts static textual node to string -$text = NodeHelpers::toText($node); -``` diff --git a/latte/es/custom-filters.texy b/latte/es/custom-filters.texy new file mode 100644 index 0000000000..6de5e13f5e --- /dev/null +++ b/latte/es/custom-filters.texy @@ -0,0 +1,231 @@ +Creación de filtros personalizados +********************************** + +.[perex] +Los filtros son herramientas poderosas para formatear y modificar datos directamente en las plantillas Latte. Ofrecen una sintaxis limpia usando el símbolo de barra vertical (`|`) para transformar variables o resultados de expresiones al formato de salida deseado. + + +¿Qué son los filtros? +===================== + +Los filtros en Latte son esencialmente **funciones PHP diseñadas específicamente para transformar un valor de entrada en un valor de salida**. Se aplican usando la notación de barra vertical (`|`) dentro de las expresiones de la plantilla (`{...}`). + +**Conveniencia:** Los filtros le permiten encapsular tareas comunes de formato (como formatear fechas, cambiar mayúsculas/minúsculas, truncar) o manipulación de datos en unidades reutilizables. En lugar de repetir código PHP complejo en sus plantillas, simplemente puede aplicar un filtro: +```latte +{* En lugar de PHP complejo para truncar: *} +{$article->text|truncate:100} + +{* En lugar de código para formatear fechas: *} +{$event->startTime|date:'Y-m-d H:i'} + +{* Aplicación de múltiples transformaciones: *} +{$product->name|lower|capitalize} +``` + +**Legibilidad:** El uso de filtros hace que las plantillas sean más claras y más enfocadas en la presentación, ya que la lógica de transformación se traslada a la definición del filtro. + +**Sensibilidad al contexto:** Una ventaja clave de los filtros en Latte es su capacidad para ser [sensibles al contexto |#Filtros contextuales]. Esto significa que un filtro puede reconocer el tipo de contenido con el que está trabajando (HTML, JavaScript, texto plano, etc.) y aplicar la lógica o el escapado correspondientes, lo cual es crucial para la seguridad y la corrección, especialmente al generar HTML. + +**Integración con la lógica de la aplicación:** Al igual que las funciones personalizadas, el PHP invocable detrás de un filtro puede ser un closure, un método estático o un método de instancia. Esto permite que los filtros accedan a servicios o datos de la aplicación si es necesario, aunque su propósito principal sigue siendo *transformar el valor de entrada*. + +Latte proporciona por defecto un rico conjunto de [filtros estándar |filters]. Los filtros personalizados le permiten ampliar este conjunto con formato y transformaciones específicas de su proyecto. + +Si necesita realizar lógica basada en *múltiples* entradas o no tiene un valor principal para transformar, probablemente sea más apropiado usar una [función personalizada |custom-functions]. Si necesita generar marcado complejo o controlar el flujo de la plantilla, considere una [etiqueta personalizada |custom-tags]. + + +Creación y registro de filtros +============================== + +Hay varias formas de definir y registrar filtros personalizados en Latte. + + +Registro directo mediante `addFilter()` +--------------------------------------- + +La forma más sencilla de agregar un filtro es usar el método `addFilter()` directamente en el objeto `Latte\Engine`. Especifique el nombre del filtro (cómo se usará en la plantilla) y el PHP invocable correspondiente. + +```php +$latte = new Latte\Engine; + +// Filtro simple sin argumentos +$latte->addFilter('initial', fn(string $s): string => mb_substr($s, 0, 1) . '.'); + +// Filtro con argumento opcional +$latte->addFilter('shortify', function (string $s, int $len = 10): string { + return mb_substr($s, 0, $len); +}); + +// Filtro que procesa un array +$latte->addFilter('sum', fn(array $numbers): int|float => array_sum($numbers)); +``` + +**Uso en la plantilla:** + +```latte +{$name|initial} {* Imprime 'J.' si $name es 'John' *} +{$description|shortify} {* Usa la longitud predeterminada 10 *} +{$description|shortify:50} {* Usa la longitud 50 *} +{$prices|sum} {* Imprime la suma de los elementos en el array $prices *} +``` + +**Paso de argumentos:** + +El valor a la izquierda de la barra vertical (`|`) siempre se pasa como el *primer* argumento a la función del filtro. Cualquier parámetro especificado después de los dos puntos (`:`) en la plantilla se pasa como los siguientes argumentos. + +```latte +{$text|shortify:30} +// Llama a la función PHP shortify($text, 30) +``` + + +Registro mediante extensión +--------------------------- + +Para una mejor organización, especialmente al crear conjuntos de filtros reutilizables o compartirlos como paquetes, la forma recomendada es registrarlos dentro de una [extensión Latte |extending-latte#Latte Extension]: + +```php +namespace App\Latte; + +use Latte\Extension; + +class MyLatteExtension extends Extension +{ + public function getFilters(): array + { + return [ + 'initial' => $this->initial(...), + 'shortify' => $this->shortify(...), + ]; + } + + public function initial(string $s): string + { + return mb_substr($s, 0, 1) . '.'; + } + + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// Registro +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +Este enfoque mantiene la lógica de su filtro encapsulada y el registro simple. + + +Uso del cargador de filtros +--------------------------- + +Latte permite registrar un cargador de filtros usando `addFilterLoader()`. Es un único PHP invocable al que Latte solicitará cualquier nombre de filtro desconocido durante la compilación. El cargador devuelve el PHP invocable del filtro o `null`. + +```php +$latte = new Latte\Engine; + +// El cargador puede crear/obtener dinámicamente filtros invocables +$latte->addFilterLoader(function (string $name): ?callable { + if ($name === 'myLazyFilter') { + // Imagine aquí una inicialización costosa... + $service = get_some_expensive_service(); + return fn($value) => $service->process($value); + } + return null; // Indica que este cargador no proporciona el filtro solicitado +}); +``` + +Este método estaba destinado principalmente a la carga diferida de filtros con una inicialización muy **costosa**. Sin embargo, las prácticas modernas de inyección de dependencias generalmente manejan los servicios diferidos de manera más eficiente. + +Los cargadores de filtros agregan complejidad y generalmente no se recomiendan en favor del registro directo mediante `addFilter()` o dentro de una extensión usando `getFilters()`. Use cargadores solo si tiene una razón seria y específica relacionada con problemas de rendimiento en la inicialización de filtros que no se pueden resolver de otra manera. + + +Filtros usando una clase con atributos +-------------------------------------- + +Otra forma elegante de definir filtros es usar métodos en su [clase de parámetros de plantilla |develop#Parámetros como clase]. Simplemente agregue el atributo `#[Latte\Attributes\TemplateFilter]` al método. + +```php +use Latte\Attributes\TemplateFilter; + +class TemplateParameters +{ + public function __construct( + public string $description, + // otros parámetros... + ) {} + + #[TemplateFilter] + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// Pasar el objeto a la plantilla +$params = new TemplateParameters(description: '...'); +$latte->render('template.latte', $params); +``` + +Latte reconocerá y registrará automáticamente los métodos marcados con este atributo cuando el objeto `TemplateParameters` se pase a la plantilla. El nombre del filtro en la plantilla será el mismo que el nombre del método (`shortify` en este caso). + +```latte +{* Uso del filtro definido en la clase de parámetros *} +{$description|shortify:50} +``` + + +Filtros contextuales +==================== + +A veces, un filtro necesita más información que solo el valor de entrada. Puede necesitar conocer el **tipo de contenido** de la cadena con la que está trabajando (p. ej., HTML, JavaScript, texto plano) o incluso modificarlo. Esta es la situación para los filtros contextuales. + +Un filtro contextual se define igual que un filtro normal, pero su **primer parámetro debe ser** tipado como `Latte\Runtime\FilterInfo`. Latte reconoce automáticamente esta firma y pasa el objeto `FilterInfo` al llamar al filtro. Los siguientes parámetros reciben los argumentos del filtro como de costumbre. + +```php +use Latte\Runtime\FilterInfo; +use Latte\ContentType; + +$latte->addFilter('money', function (FilterInfo $info, float $amount): string { + // 1. Verifique el tipo de contenido de entrada (opcional, pero recomendado) + // Permita null (entrada variable) o texto plano. Rechace si se aplica a HTML, etc. + if (!in_array($info->contentType, [null, ContentType::Text], true)) { + $actualType = $info->contentType ?? 'mixed'; + throw new \RuntimeException( + "Filtro |money usado en tipo de contenido incompatible $actualType. Se esperaba text o null." + ); + } + + // 2. Realice la transformación + $formatted = number_format($amount, 2, '.', ',') . ' EUR'; + $htmlOutput = '' . htmlspecialchars($formatted) . ''; // ¡Asegúrese de un escape adecuado! + + // 3. Declare el tipo de contenido de salida + $info->contentType = ContentType::Html; + + // 4. Devuelva el resultado + return $htmlOutput; +}); +``` + +`$info->contentType` es una constante de cadena de `Latte\ContentType` (p. ej., `ContentType::Html`, `ContentType::Text`, `ContentType::JavaScript`, etc.) o `null`, si el filtro se aplica a una variable (`{$var|filter}`). Puede **leer** este valor para verificar el contexto de entrada y **escribir** en él para declarar el tipo de contexto de salida. + +Al establecer el tipo de contenido en HTML, le está diciendo a Latte que la cadena devuelta por su filtro es HTML seguro. Latte entonces **no aplicará** su escapado automático predeterminado a este resultado. Esto es crucial si su filtro genera marcado HTML. + +.[warning] +Si su filtro genera HTML, **usted es responsable de escapar correctamente cualquier dato de entrada** utilizado en ese HTML (como en el caso de llamar a `htmlspecialchars($formatted)` arriba). Omitirlo puede crear vulnerabilidades XSS. Si su filtro devuelve solo texto plano, no necesita establecer `$info->contentType`. + + +Filtros en bloques +------------------ + +Todos los filtros aplicados a [bloques |tags#block] *deben* ser contextuales. Esto se debe a que el contenido del bloque tiene un tipo de contenido definido (generalmente HTML), del cual el filtro debe ser consciente. + +```latte +{block heading|money}1000{/block} +{* El filtro 'money' recibirá '1000' como segundo argumento + y $info->contentType será ContentType::Html *} +``` + +Los filtros contextuales proporcionan un control sólido sobre cómo se procesan los datos según su contexto, permiten funciones avanzadas y garantizan un comportamiento de escapado correcto, especialmente al generar contenido HTML. diff --git a/latte/es/custom-functions.texy b/latte/es/custom-functions.texy new file mode 100644 index 0000000000..a12b59b429 --- /dev/null +++ b/latte/es/custom-functions.texy @@ -0,0 +1,144 @@ +Creación de funciones personalizadas +************************************ + +.[perex] +Agregue fácilmente funciones auxiliares personalizadas a las plantillas Latte. Llame a la lógica PHP directamente en las expresiones para cálculos, acceso a servicios o generación de contenido dinámico, manteniendo sus plantillas limpias y potentes. + + +¿Qué son las funciones? +======================= + +Las funciones en Latte le permiten ampliar el conjunto de funciones que se pueden llamar dentro de las expresiones en las plantillas (`{...}`). Puede pensar en ellas como **funciones PHP personalizadas disponibles solo dentro de sus plantillas Latte**. Esto aporta varias ventajas: + +**Conveniencia:** Puede definir lógica auxiliar (como cálculos, formato o acceso a datos de la aplicación) y llamarla usando una sintaxis de función simple y familiar directamente en la plantilla, tal como llamaría a `strlen()` o `date()` en PHP. + +```latte +{var $userInitials = initials($userName)} {* p. ej. 'J. D.' *} + +{if hasPermission('article', 'edit')} + Editar +{/if} +``` + +**Sin contaminación del espacio global:** A diferencia de definir una función global real en PHP, las funciones Latte solo existen en el contexto de la representación de la plantilla. No necesita cargar el espacio de nombres global de PHP con auxiliares que son específicos solo para las plantillas. + +**Integración con la lógica de la aplicación:** El PHP invocable detrás de una función Latte puede ser cualquier cosa: una función anónima, un método estático o un método de instancia. Esto significa que sus funciones en las plantillas pueden acceder fácilmente a los servicios de la aplicación, bases de datos, configuración o cualquier otra lógica necesaria capturando variables (en el caso de funciones anónimas) o usando inyección de dependencias (en el caso de objetos). El ejemplo anterior `hasPermission` lo demuestra claramente, ya que probablemente llama a un servicio de autorización en segundo plano. + +**Sobrescritura de funciones nativas (opcional):** Incluso puede definir una función Latte con el mismo nombre que una función PHP nativa. En la plantilla, se llamará a su propia versión en lugar de la función original. Esto puede ser útil para proporcionar un comportamiento específico de la plantilla o garantizar un procesamiento consistente (p. ej., asegurar que `strlen` sea siempre seguro para multibyte). Use esta función con precaución para evitar malentendidos. + +Por defecto, Latte permite llamar a *todas* las funciones PHP nativas (a menos que estén restringidas por [Sandbox |sandbox]). Las funciones personalizadas amplían esta biblioteca incorporada con las necesidades específicas de su proyecto. + +Si solo está transformando un único valor, puede ser más apropiado usar un [filtro personalizado |custom-filters]. + + +Creación y registro de funciones +================================ + +Al igual que con los filtros, hay varias formas de definir y registrar funciones personalizadas. + + +Registro directo mediante `addFunction()` +----------------------------------------- + +El método más simple es usar `addFunction()` en el objeto `Latte\Engine`. Especifique el nombre de la función (cómo aparecerá en la plantilla) y el PHP invocable correspondiente. + +```php +$latte = new Latte\Engine; + +// Función auxiliar simple +$latte->addFunction('initials', function (string $name): string { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; +}); +``` + +**Uso en la plantilla:** + +```latte +{var $userInitials = initials($userName)} +``` + +Los argumentos de la función en la plantilla se pasan directamente al PHP invocable en el mismo orden. Las funcionalidades de PHP como las sugerencias de tipo, los valores predeterminados y los parámetros variables (`...`) funcionan como se espera. + + +Registro mediante extensión +--------------------------- + +Para una mejor organización y reutilización, registre funciones dentro de una [extensión Latte |extending-latte#Latte Extension]. Este enfoque se recomienda para aplicaciones más complejas o librerías compartidas. + +```php +namespace App\Latte; + +use Latte\Extension; +use Nette\Security\Authorizator; + +class MyLatteExtension extends Extension +{ + public function __construct( + // Suponemos que existe el servicio Authorizator + private Authorizator $authorizator, + ) { + } + + public function getFunctions(): array + { + // Registro de métodos como funciones Latte + return [ + 'hasPermission' => $this->hasPermission(...), + ]; + } + + public function hasPermission(string $resource, string $action): bool + { + return $this->authorizator->isAllowed($resource, $action); + } +} + +// Registro (suponemos que $container contiene DIC) +$extension = $container->getByType(App\Latte\MyLatteExtension::class); +$latte = new Latte\Engine; +$latte->addExtension($extension); +``` + +Este enfoque ilustra cómo las funciones definidas en Latte pueden ser respaldadas por métodos de objetos, que pueden tener sus propias dependencias gestionadas por el contenedor de inyección de dependencias de su aplicación o una factoría. Esto mantiene la lógica de sus plantillas conectada con el núcleo de la aplicación mientras se mantiene una organización clara. + + +Funciones usando una clase con atributos +---------------------------------------- + +Al igual que los filtros, las funciones pueden definirse como métodos en su [clase de parámetros de plantilla |develop#Parámetros como clase] usando el atributo `#[Latte\Attributes\TemplateFunction]`. + +```php +use Latte\Attributes\TemplateFunction; + +class TemplateParameters +{ + public function __construct( + public string $userName, + // otros parámetros... + ) {} + + // Este método estará disponible como {initials(...)} en la plantilla + #[TemplateFunction] + public function initials(string $name): string + { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; + } +} + +// Pasar el objeto a la plantilla +$params = new TemplateParameters(userName: 'John Doe', /* ... */); +$latte->render('template.latte', $params); +``` + +Latte descubrirá y registrará automáticamente los métodos marcados con este atributo cuando el objeto de parámetros se pase a la plantilla. El nombre de la función en la plantilla corresponde al nombre del método. + +```latte +{* Uso de la función definida en la clase de parámetros *} +{var $inits = initials($userName)} +``` + +**¿Funciones contextuales?** + +A diferencia de los filtros, no existe un concepto directo de "funciones contextuales" que recibirían un objeto similar a `FilterInfo`. Las funciones operan dentro de expresiones y típicamente no necesitan acceso directo al contexto de representación o información sobre el tipo de contenido de la misma manera que los filtros aplicados a bloques. diff --git a/latte/es/custom-tags.texy b/latte/es/custom-tags.texy new file mode 100644 index 0000000000..f32e309910 --- /dev/null +++ b/latte/es/custom-tags.texy @@ -0,0 +1,1135 @@ +Creación de etiquetas personalizadas +************************************ + +.[perex] +Esta página proporciona una guía completa para crear etiquetas personalizadas en Latte. Discutiremos todo, desde etiquetas simples hasta escenarios más complejos con contenido anidado y necesidades específicas de análisis sintáctico, basándonos en su comprensión de cómo Latte compila las plantillas. + +Las etiquetas personalizadas proporcionan el nivel más alto de control sobre la sintaxis de la plantilla y la lógica de renderizado, pero también son el punto de extensión más complejo. Antes de decidir crear una etiqueta personalizada, siempre considere si [no existe una solución más simple |extending-latte#Formas de extender Latte] o si ya existe una etiqueta adecuada en el [conjunto estándar |tags]. Use etiquetas personalizadas solo cuando las alternativas más simples no sean suficientes para sus necesidades. + + +Entendiendo el proceso de compilación +===================================== + +Para crear etiquetas personalizadas de manera efectiva, es útil explicar cómo Latte procesa las plantillas. Comprender este proceso aclara por qué las etiquetas están estructuradas de esta manera y cómo encajan en el contexto más amplio. + +La compilación de una plantilla en Latte, simplificada, incluye estos pasos clave: + +1. **Análisis léxico:** El analizador léxico (lexer) lee el código fuente de la plantilla (archivo `.latte`) y lo divide en una secuencia de pequeñas partes distintas llamadas **tokens** (por ejemplo, `{`, `foreach`, `$variable`, `}`, texto HTML, etc.). +2. **Análisis sintáctico:** El analizador sintáctico (parser) toma este flujo de tokens y construye una estructura de árbol significativa que representa la lógica y el contenido de la plantilla. Este árbol se llama **árbol de sintaxis abstracto (AST)**. +3. **Pasos de compilación:** Antes de generar el código PHP, Latte ejecuta [pasos de compilación |compiler-passes]. Son funciones que recorren todo el AST y pueden modificarlo o recopilar información. Este paso es crucial para funciones como la seguridad ([Sandbox |sandbox]) u optimizaciones. +4. **Generación de código:** Finalmente, el compilador recorre el AST (potencialmente modificado) y genera el código de clase PHP correspondiente. Este código PHP es lo que realmente renderiza la plantilla cuando se ejecuta. +5. **Almacenamiento en caché (Caching):** El código PHP generado se almacena en el disco, lo que hace que las renderizaciones posteriores sean muy rápidas, ya que se omiten los pasos 1-4. + +En realidad, la compilación es un poco más compleja. Latte **tiene dos** analizadores léxicos y sintácticos: uno para la plantilla HTML y otro para el código similar a PHP dentro de las etiquetas. Y tampoco el análisis sintáctico ocurre después de la tokenización, sino que el analizador léxico y el sintáctico se ejecutan en paralelo en dos "hilos" y se coordinan. Créanme, programarlo fue ciencia de cohetes :-) + +Todo el proceso, desde cargar el contenido de la plantilla, pasando por el análisis sintáctico, hasta generar el archivo resultante, se puede secuenciar con este código, con el que puede experimentar e imprimir resultados intermedios: + +```php +$latte = new Latte\Engine; +$source = $latte->getLoader()->getContent($file); +$ast = $latte->parse($source); +$latte->applyPasses($ast); +$code = $latte->generate($ast, $file); +``` + + +Anatomía de una etiqueta +======================== + +La creación de una etiqueta personalizada completamente funcional en Latte implica varias partes interconectadas. Antes de sumergirnos en la implementación, comprendamos los conceptos básicos y la terminología, utilizando una analogía con HTML y el Document Object Model (DOM). + + +Etiquetas vs. Nodos (Analogía con HTML) +--------------------------------------- + +En HTML, escribimos **etiquetas** como `

                                                          ` o `

                                                          ...
                                                          `. Estas etiquetas son la sintaxis en el código fuente. Cuando un navegador analiza este HTML, crea una representación en memoria llamada **Document Object Model (DOM)**. En el DOM, las etiquetas HTML están representadas por **nodos** (específicamente nodos `Element` en la terminología del DOM de JavaScript). Trabajamos programáticamente con estos *nodos* (por ejemplo, usando `document.getElementById(...)` de JavaScript se devuelve un nodo Element). Una etiqueta es solo una representación textual en el archivo fuente; un nodo es una representación de objeto en el árbol lógico. + +Latte funciona de manera similar: + +- En el archivo de plantilla `.latte`, escribe **etiquetas Latte**, como `{foreach ...}` y `{/foreach}`. Esta es la sintaxis con la que usted, como autor de la plantilla, trabaja. +- Cuando Latte **analiza sintácticamente** la plantilla, construye un **Árbol de Sintaxis Abstracto (AST)**. Este árbol está compuesto por **nodos**. Cada etiqueta Latte, elemento HTML, trozo de texto o expresión en la plantilla se convierte en uno o más nodos en este árbol. +- La clase base para todos los nodos en el AST es `Latte\Compiler\Node`. Al igual que el DOM tiene diferentes tipos de nodos (Element, Text, Comment), el AST de Latte tiene diferentes tipos de nodos. Se encontrará con `Latte\Compiler\Nodes\TextNode` para texto estático, `Latte\Compiler\Nodes\Html\ElementNode` para elementos HTML, `Latte\Compiler\Nodes\Php\ExpressionNode` para expresiones dentro de etiquetas y, crucialmente para etiquetas personalizadas, nodos que heredan de `Latte\Compiler\Nodes\StatementNode`. + + +¿Por qué `StatementNode`? +------------------------- + +Los elementos HTML (`Html\ElementNode`) representan principalmente estructura y contenido. Las expresiones PHP (`Php\ExpressionNode`) representan valores o cálculos. Pero, ¿qué pasa con las etiquetas Latte como `{if}`, `{foreach}` o nuestra propia `{datetime}`? Estas etiquetas *realizan acciones*, controlan el flujo del programa o generan salida basada en la lógica. Son unidades funcionales que hacen de Latte un potente *motor* de plantillas, no solo un lenguaje de marcado. + +En programación, tales unidades que realizan acciones a menudo se llaman "statements" (sentencias). Por lo tanto, los nodos que representan estas etiquetas Latte funcionales típicamente heredan de `Latte\Compiler\Nodes\StatementNode`. Esto los distingue de los nodos puramente estructurales (como los elementos HTML) o los nodos que representan valores (como las expresiones). + + +Los componentes clave +===================== + +Repasemos los componentes principales necesarios para crear una etiqueta personalizada: + + +Función de análisis sintáctico de etiquetas +------------------------------------------- + +- Esta función PHP callable analiza la sintaxis de la etiqueta Latte (`{...}`) en la plantilla fuente. +- Recibe información sobre la etiqueta (como su nombre, posición y si es un n:atributo) a través del objeto [api:Latte\Compiler\Tag]. +- Su herramienta principal para analizar argumentos y expresiones dentro de los delimitadores de la etiqueta es el objeto [api:Latte\Compiler\TagParser], accesible a través de `$tag->parser` (este es un analizador sintáctico diferente al que analiza toda la plantilla). +- Para etiquetas emparejadas, usa `yield` para indicar a Latte que analice el contenido interno entre la etiqueta de apertura y la de cierre. +- El objetivo final de la función de análisis sintáctico es crear y devolver una instancia de la **clase de nodo**, que se agrega al AST. +- Es costumbre (aunque no obligatorio) implementar la función de análisis sintáctico como un método estático (a menudo llamado `create`) directamente en la clase de nodo correspondiente. Esto mantiene la lógica de análisis y la representación del nodo ordenadamente en un solo paquete, permite el acceso a elementos privados/protegidos de la clase si es necesario y mejora la organización. + + +Clase de nodo +------------- + +- Representa la *función lógica* de su etiqueta en el **Árbol de Sintaxis Abstracto (AST)**. +- Contiene información analizada (como argumentos o contenido) como propiedades públicas. Estas propiedades a menudo contienen otras instancias de `Node` (por ejemplo, `ExpressionNode` para argumentos analizados, `AreaNode` para contenido analizado). +- El método `print(PrintContext $context): string` genera el *código PHP* (una sentencia o serie de sentencias) que realiza la acción de la etiqueta durante la renderización de la plantilla. +- El método `getIterator(): \Generator` expone los nodos hijos (argumentos, contenido) para el recorrido por los **pasos de compilación**. Debe proporcionar referencias (`&`) para permitir que los pasos modifiquen o reemplacen potencialmente los subnodos. +- Después de que toda la plantilla se analiza sintácticamente en un AST, Latte ejecuta una serie de [pasos de compilación |compiler-passes]. Estos pasos recorren *todo* el AST utilizando el método `getIterator()` proporcionado por cada nodo. Pueden inspeccionar nodos, recopilar información e incluso *modificar* el árbol (por ejemplo, cambiando las propiedades públicas de los nodos o reemplazando nodos por completo). Este diseño, que requiere un `getIterator()` completo, es crucial. Permite que funciones potentes como [Sandbox |sandbox] analicen y potencialmente cambien el comportamiento de *cualquier* parte de la plantilla, incluidas sus propias etiquetas personalizadas, garantizando la seguridad y la coherencia. + + +Registro a través de una extensión +---------------------------------- + +- Necesita informar a Latte sobre su nueva etiqueta y qué función de análisis sintáctico debe usarse para ella. Esto se hace dentro de una [extensión Latte |extending-latte#Latte Extension]. +- Dentro de su clase de extensión, implementa el método `getTags(): array`. Este método devuelve un array asociativo donde las claves son los nombres de las etiquetas (por ejemplo, `'mytag'`, `'n:myattribute'`) y los valores son funciones PHP callable que representan sus respectivas funciones de análisis sintáctico (por ejemplo, `MyNamespace\DatetimeNode::create(...)`). + +Resumen: La **función de análisis sintáctico de etiquetas** transforma el *código fuente de la plantilla* de su etiqueta en un **nodo AST**. La **clase de nodo** puede entonces transformar *a sí misma* en *código PHP* ejecutable para la plantilla compilada y expone sus subnodos para los **pasos de compilación** a través de `getIterator()`. El **registro a través de una extensión** conecta el nombre de la etiqueta con la función de análisis sintáctico y se lo informa a Latte. + +Ahora exploremos cómo implementar estos componentes paso a paso. + + +Creación de una etiqueta simple +=============================== + +Vamos a crear su primera etiqueta Latte personalizada. Comenzaremos con un ejemplo muy simple: una etiqueta llamada `{datetime}` que imprime la fecha y hora actuales. **Inicialmente, esta etiqueta no aceptará ningún argumento**, pero la mejoraremos más adelante en la sección [#Análisis de argumentos de etiqueta]. Tampoco tiene contenido interno. + +Este ejemplo lo guiará a través de los pasos básicos: definir la clase de nodo, implementar sus métodos `print()` y `getIterator()`, crear la función de análisis sintáctico y, finalmente, registrar la etiqueta. + +**Objetivo:** Implementar `{datetime}` para generar la fecha y hora actuales usando la función PHP `date()`. + + +Creación de la clase de nodo +---------------------------- + +Primero, necesitamos una clase que represente nuestra etiqueta en el Árbol de Sintaxis Abstracto (AST). Como se discutió anteriormente, heredamos de `Latte\Compiler\Nodes\StatementNode`. + +Cree un archivo (por ejemplo, `DatetimeNode.php`) y defina la clase: + +```php +node = new self; + return $node; + } + + /** + * Genera el código PHP que se ejecutará al renderizar la plantilla. + */ + public function print(PrintContext $context): string + { + return $context->format( + 'echo date(\'Y-m-d H:i:s\') %line;', + $this->position, + ); + } + + /** + * Proporciona acceso a los nodos hijos para los pasos de compilación de Latte. + */ + public function &getIterator(): \Generator + { + false && yield; + } +} +``` + +Cuando Latte encuentra `{datetime}` en una plantilla, llama a la función de análisis sintáctico `create()`. Su tarea es devolver una instancia de `DatetimeNode`. + +El método `print()` genera el código PHP que se ejecutará al renderizar la plantilla. Llamamos al método `$context->format()`, que construye la cadena de código PHP resultante para la plantilla compilada. El primer argumento, `'echo date('Y-m-d H:i:s') %line;'`, es una máscara en la que se insertan los siguientes parámetros. El marcador de posición `%line` le dice al método `format()` que use el segundo argumento, que es `$this->position`, e inserte un comentario como `/* line 15 */`, que vincula el código PHP generado de nuevo a la línea original de la plantilla, lo cual es crucial para la depuración. + +La propiedad `$this->position` se hereda de la clase base `Node` y es establecida automáticamente por el analizador sintáctico de Latte. Contiene un objeto [api:Latte\Compiler\Position] que indica dónde se encontró la etiqueta en el archivo fuente `.latte`. + +El método `getIterator()` es crucial para los pasos de compilación. Debe proporcionar todos los nodos hijos, pero nuestro simple `DatetimeNode` actualmente no tiene argumentos ni contenido, por lo tanto, no tiene nodos hijos. Sin embargo, el método aún debe existir y ser un generador, es decir, la palabra clave `yield` debe estar presente de alguna manera en el cuerpo del método. + + +Registro a través de una extensión +---------------------------------- + +Finalmente, informemos a Latte sobre la nueva etiqueta. Cree una [clase de extensión |extending-latte#Latte Extension] (por ejemplo, `MyLatteExtension.php`) y registre la etiqueta en su método `getTags()`. + +```php + Mapa: 'nombre-etiqueta' => funcion-analisis-sintactico + */ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + // Registre más etiquetas aquí más tarde + ]; + } +} +``` + +Luego registre esta extensión en el Latte Engine: + +```php +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +Cree una plantilla: + +```latte +

                                                          Página generada: {datetime}

                                                          +``` + +Salida esperada: `

                                                          Página generada: 2023-10-27 11:00:00

                                                          ` + + +Resumen de esta fase +-------------------- + +Hemos creado con éxito una etiqueta personalizada básica `{datetime}`. Definimos su representación en el AST (`DatetimeNode`), manejamos su análisis sintáctico (`create()`), especificamos cómo debería generar código PHP (`print()`), nos aseguramos de que sus hijos sean accesibles para el recorrido (`getIterator()`) y la registramos en Latte. + +En la siguiente sección, mejoraremos esta etiqueta para aceptar argumentos y mostraremos cómo analizar expresiones y administrar nodos hijos. + + +Análisis de argumentos de etiqueta +================================== + +Nuestra etiqueta simple `{datetime}` funciona, pero no es muy flexible. Mejorémosla para que acepte un argumento opcional: una cadena de formato para la función `date()`. La sintaxis requerida será `{datetime $format}`. + +**Objetivo:** Modificar `{datetime}` para que acepte una expresión PHP opcional como argumento, que se utilizará como cadena de formato para `date()`. + + +Introducción a `TagParser` +-------------------------- + +Antes de modificar el código, es importante comprender la herramienta que utilizaremos: [api:Latte\Compiler\TagParser]. Cuando el analizador sintáctico principal de Latte (`TemplateParser`) encuentra una etiqueta Latte como `{datetime ...}` o un n:atributo, delega el análisis del contenido *dentro* de la etiqueta (la parte entre `{` y `}` o el valor del atributo) a un `TagParser` especializado. + +Este `TagParser` trabaja exclusivamente con los **argumentos de la etiqueta**. Su tarea es procesar los tokens que representan estos argumentos. Es crucial que **debe procesar todo el contenido** que se le proporciona. Si su función de análisis sintáctico termina, pero `TagParser` no ha llegado al final de los argumentos (verificado a través de `$tag->parser->isEnd()`), Latte lanzará una excepción, ya que indica que quedaron tokens inesperados dentro de la etiqueta. Por el contrario, si la etiqueta *requiere* argumentos, debe llamar a `$tag->expectArguments()` al principio de su función de análisis sintáctico. Este método verifica si hay argumentos presentes y lanza una excepción útil si la etiqueta se usó sin ningún argumento. + +`TagParser` ofrece métodos útiles para analizar diferentes tipos de argumentos: + +- `parseExpression(): ExpressionNode`: Analiza una expresión similar a PHP (variables, literales, operadores, llamadas a funciones/métodos, etc.). Maneja el azúcar sintáctico de Latte, como tratar cadenas alfanuméricas simples como cadenas entre comillas (por ejemplo, `foo` se analiza como si fuera `'foo'`). +- `parseUnquotedStringOrExpression(): ExpressionNode`: Analiza una expresión estándar o una *cadena sin comillas*. Las cadenas sin comillas son secuencias permitidas por Latte sin comillas, a menudo utilizadas para cosas como rutas de archivos (por ejemplo, `{include ../file.latte}`). Si analiza una cadena sin comillas, devuelve un `StringNode`. +- `parseArguments(): ArrayNode`: Analiza argumentos separados por comas, potencialmente con claves, como `10, name: 'John', true`. +- `parseModifier(): ModifierNode`: Analiza filtros como `|upper|truncate:10`. +- `parseType(): ?SuperiorTypeNode`: Analiza type hints de PHP como `int`, `?string`, `array|Foo`. + +Para necesidades de análisis sintáctico más complejas o de bajo nivel, puede interactuar directamente con el [flujo de tokens |api:Latte\Compiler\TokenStream] a través de `$tag->parser->stream`. Este objeto proporciona métodos para verificar y procesar tokens individuales: + +- `$tag->parser->stream->is(...): bool`: Verifica si el token *actual* coincide con alguno de los tipos especificados (por ejemplo, `Token::Php_Variable`) o valores literales (por ejemplo, `'as'`) sin consumirlo. Útil para mirar hacia adelante. +- `$tag->parser->stream->consume(...): Token`: Consume el token *actual* y avanza la posición del flujo. Si se proporcionan tipos/valores de token esperados como argumentos y el token actual no coincide, lanza `CompileException`. Use esto cuando *espere* un token específico. +- `$tag->parser->stream->tryConsume(...): ?Token`: Intenta consumir el token *actual* *solo si* coincide con uno de los tipos/valores especificados. Si coincide, consume el token y lo devuelve. Si no coincide, deja la posición del flujo sin cambios y devuelve `null`. Use esto para tokens opcionales o cuando elija entre diferentes rutas sintácticas. + + +Actualización de la función de análisis sintáctico `create()` +------------------------------------------------------------- + +Con esta comprensión, modifiquemos el método `create()` en `DatetimeNode` para analizar el argumento de formato opcional usando `$tag->parser`. + +```php +node = new self; + + // Verificamos si existen algunos tokens + if (!$tag->parser->isEnd()) { + // Analizamos el argumento como una expresión similar a PHP usando TagParser. + $node->format = $tag->parser->parseExpression(); + } + + return $node; + } + + // ... los métodos print() y getIterator() se actualizarán a continuación ... +} +``` + +Agregamos una propiedad pública `$format`. En `create()`, ahora usamos `$tag->parser->isEnd()` para verificar si *existen* argumentos. Si es así, `$tag->parser->parseExpression()` procesa los tokens para la expresión. Dado que `TagParser` debe procesar todos los tokens de entrada, Latte lanzará automáticamente un error si el usuario escribe algo inesperado después de la expresión de formato (por ejemplo, `{datetime 'Y-m-d', unexpected}`). + + +Actualización del método `print()` +---------------------------------- + +Ahora modifiquemos el método `print()` para usar la expresión de formato analizada almacenada en `$this->format`. Si no se proporcionó ningún formato (`$this->format` es `null`), deberíamos usar una cadena de formato predeterminada, por ejemplo, `'Y-m-d H:i:s'`. + +```php + public function print(PrintContext $context): string + { + $formatNode = $this->format ?? new StringNode('Y-m-d H:i:s'); + + // %node imprime la representación de código PHP de $formatNode. + return $context->format( + 'echo date(%node) %line;', + $formatNode, + $this->position + ); + } +``` + +En la variable `$formatNode` almacenamos el nodo AST que representa la cadena de formato para la función PHP `date()`. Usamos aquí el operador de fusión de null (`??`). Si el usuario proporcionó un argumento en la plantilla (por ejemplo, `{datetime 'd.m.Y'}`), entonces la propiedad `$this->format` contiene el nodo correspondiente (en este caso, un `StringNode` con el valor `'d.m.Y'`), y se usa este nodo. Si el usuario no proporcionó un argumento (solo escribió `{datetime}`), la propiedad `$this->format` es `null`, y en su lugar creamos un nuevo `StringNode` con el formato predeterminado `'Y-m-d H:i:s'`. Esto asegura que `$formatNode` siempre contenga un nodo AST válido para el formato. + +En la máscara `'echo date(%node) %line;'` se utiliza un nuevo marcador de posición `%node`, que le dice al método `format()` que tome el primer argumento siguiente (que es nuestro `$formatNode`), llame a su método `print()` (que devolverá su representación de código PHP) e inserte el resultado en la posición del marcador de posición. + + +Implementación de `getIterator()` para subnodos +----------------------------------------------- + +Nuestro `DatetimeNode` ahora tiene un nodo hijo: la expresión `$format`. **Debemos** hacer que este nodo hijo sea accesible para los pasos de compilación proporcionándolo en el método `getIterator()`. No olvide proporcionar una *referencia* (`&`) para permitir que los pasos reemplacen potencialmente el nodo. + +```php + public function &getIterator(): \Generator + { + if ($this->format) { + yield $this->format; + } + } +``` + +¿Por qué es esto crucial? Imagine un paso de Sandbox que necesita verificar si el argumento `$format` no contiene una llamada a función prohibida (por ejemplo, `{datetime dangerousFunction()}`). Si `getIterator()` no proporciona `$this->format`, el paso de Sandbox nunca vería la llamada a `dangerousFunction()` dentro del argumento de nuestra etiqueta, lo que crearía una posible brecha de seguridad. Al proporcionarlo, permitimos que Sandbox (y otros pasos) inspeccionen y potencialmente modifiquen el nodo de expresión `$format`. + + +Uso de la etiqueta mejorada +--------------------------- + +La etiqueta ahora maneja correctamente el argumento opcional: + +```latte +Formato predeterminado: {datetime} +Formato personalizado: {datetime 'd.m.Y'} +Uso de variable: {datetime $userDateFormatPreference} + +{* Esto causaría un error después de analizar 'd.m.Y', porque ", foo" es inesperado *} +{* {datetime 'd.m.Y', foo} *} +``` + +A continuación, veremos la creación de etiquetas emparejadas que procesan el contenido entre ellas. + + +Manejo de etiquetas emparejadas +=============================== + +Hasta ahora, nuestra etiqueta `{datetime}` era *auto-cerrada* (conceptualmente). No tiene contenido entre la etiqueta de apertura y la de cierre. Sin embargo, muchas etiquetas útiles trabajan con un bloque de contenido de plantilla. Estas se llaman **etiquetas emparejadas**. Ejemplos incluyen `{if}...{/if}`, `{block}...{/block}` o una etiqueta personalizada que ahora crearemos: `{debug}...{/debug}`. + +Esta etiqueta nos permitirá incluir información de depuración en nuestras plantillas que solo debería ser visible durante el desarrollo. + +**Objetivo:** Crear una etiqueta emparejada `{debug}`, cuyo contenido se renderiza solo si está activa una bandera específica de "modo de desarrollo". + + +Introducción a los proveedores +------------------------------ + +A veces, sus etiquetas necesitan acceso a datos o servicios que no se pasan directamente como parámetros de plantilla. Por ejemplo, determinar si la aplicación está en modo de desarrollo, acceder al objeto de usuario u obtener valores de configuración. Latte proporciona un mecanismo llamado **proveedores** (Providers) para este propósito. + +Los proveedores se registran en su [extensión |extending-latte#Latte Extension] usando el método `getProviders()`. Este método devuelve un array asociativo donde las claves son los nombres bajo los cuales los proveedores serán accesibles en el código de tiempo de ejecución de la plantilla, y los valores son los datos u objetos reales. + +Dentro del código PHP generado por el método `print()` de su etiqueta, puede acceder a estos proveedores a través de la propiedad especial del objeto `$this->global`. Dado que esta propiedad se comparte entre todas las extensiones, es una buena práctica **prefijar los nombres de sus proveedores** para evitar posibles colisiones de nombres con proveedores clave de Latte o proveedores de otras extensiones de terceros. Una convención común es usar un prefijo corto y único relacionado con su fabricante o nombre de extensión. Para nuestro ejemplo, usaremos el prefijo `app` y la bandera de modo de desarrollo estará disponible como `$this->global->appDevMode`. + + +La palabra clave `yield` para analizar contenido +------------------------------------------------ + +¿Cómo le decimos al analizador sintáctico de Latte que procese el contenido *entre* `{debug}` y `{/debug}`? Aquí es donde entra en juego la palabra clave `yield`. + +Cuando se usa `yield` en la función `create()`, la función se convierte en un [generador PHP |https://www.php.net/manual/en/language.generators.overview.php]. Su ejecución se pausa y el control vuelve al `TemplateParser` principal. El `TemplateParser` luego continúa analizando el contenido de la plantilla *hasta que* encuentra la etiqueta de cierre correspondiente (`{/debug}` en nuestro caso). + +Una vez que se encuentra la etiqueta de cierre, `TemplateParser` reanuda la ejecución de nuestra función `create()` justo después de la sentencia `yield`. El valor *devuelto* por la sentencia `yield` es un array que contiene dos elementos: + +1. Un `AreaNode` que representa el contenido analizado entre las etiquetas de apertura y cierre. +2. Un objeto `Tag` que representa la etiqueta de cierre (por ejemplo, `{/debug}`). + +Creemos la clase `DebugNode` y su método `create` utilizando `yield`. + +```php +node = new self; + + // Pausar el análisis sintáctico, obtener el contenido interno y la etiqueta final cuando se encuentre {/debug} + [$node->content, $endTag] = yield; + + return $node; + } + + // ... print() y getIterator() se implementarán a continuación ... +} +``` + +Nota: `$endTag` es `null` si la etiqueta se usa como un n:atributo, es decir, `
                                                          ...
                                                          `. + + +Implementación de `print()` para renderizado condicional +-------------------------------------------------------- + +El método `print()` ahora necesita generar código PHP que verifique en tiempo de ejecución el proveedor `appDevMode` y solo ejecute el código para el contenido interno si la bandera es true. + +```php + public function print(PrintContext $context): string + { + // Genera una sentencia PHP 'if' que verifica el proveedor en tiempo de ejecución + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + // Si está en modo de desarrollo, imprime el contenido interno + %node + } + + XX, + $this->position, // Para el comentario %line + $this->content, // El nodo que contiene el AST del contenido interno + ); + } +``` + +Esto es simple. Usamos `PrintContext::format()` para crear una sentencia PHP `if` estándar. Dentro del `if`, colocamos el marcador de posición `%node` para `$this->content`. Latte llamará recursivamente a `$this->content->print($context)` para generar el código PHP para la parte interna de la etiqueta, pero solo si `$this->global->appDevMode` se evalúa como true en tiempo de ejecución. + + +Implementación de `getIterator()` para contenido +------------------------------------------------ + +Al igual que con el nodo de argumento en el ejemplo anterior, nuestro `DebugNode` ahora tiene un nodo hijo: `AreaNode $content`. Debemos hacerlo accesible proporcionándolo en `getIterator()`: + +```php + public function &getIterator(): \Generator + { + // Proporciona una referencia al nodo de contenido + yield $this->content; + } +``` + +Esto permite que los pasos de compilación desciendan al contenido de nuestra etiqueta `{debug}`, lo cual es importante incluso si el contenido se renderiza condicionalmente. Por ejemplo, Sandbox necesita analizar el contenido independientemente de si `appDevMode` es true o false. + + +Registro y uso +-------------- + +Registre la etiqueta y el proveedor en su extensión: + +```php +class MyLatteExtension extends Extension +{ + // Suponemos que $isDevelopmentMode se determina en algún lugar (por ejemplo, desde la configuración) + public function __construct( + private bool $isDevelopmentMode, + ) { + } + + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), // Registro de la nueva etiqueta + ]; + } + + public function getProviders(): array + { + return [ + 'appDevMode' => $this->isDevelopmentMode, // Registro del proveedor + ]; + } +} + +// Al registrar la extensión: +$isDev = true; // Determine esto según el entorno de su aplicación +$latte->addExtension(new App\Latte\MyLatteExtension($isDev)); +``` + +Y su uso en la plantilla: + +```latte +

                                                          Contenido normal visible siempre.

                                                          + +{debug} +
                                                          + ID del usuario actual: {$user->id} + Tiempo de la petición: {=time()} +
                                                          +{/debug} + +

                                                          Otro contenido normal.

                                                          +``` + + +Integración de n:atributos +-------------------------- + +Latte ofrece una notación abreviada conveniente para muchas etiquetas emparejadas: [n:atributos |syntax#n:atributos]. Si tiene una etiqueta emparejada como `{tag}...{/tag}` y desea que su efecto se aplique directamente a un solo elemento HTML, a menudo puede escribirlo de manera más concisa como un atributo `n:tag` en ese elemento. + +Para la mayoría de las etiquetas emparejadas estándar que defina (como nuestra `{debug}`), Latte habilitará automáticamente la versión de atributo `n:` correspondiente. No necesita hacer nada adicional durante el registro: + +```latte +{* Uso estándar de la etiqueta emparejada *} +{debug}
                                                          Información de depuración
                                                          {/debug} + +{* Uso equivalente con n:atributo *} +
                                                          Información de depuración
                                                          +``` + +Ambas versiones renderizarán el `
                                                          ` solo si `$this->global->appDevMode` es true. Los prefijos `inner-` y `tag-` también funcionan como se espera. + +A veces, la lógica de su etiqueta puede necesitar comportarse de manera ligeramente diferente dependiendo de si se usa como una etiqueta emparejada estándar o como un n:atributo, o si se usa un prefijo como `n:inner-tag` o `n:tag-tag`. El objeto `Latte\Compiler\Tag`, pasado a su función de análisis sintáctico `create()`, proporciona esta información: + +- `$tag->isNAttribute(): bool`: Devuelve `true` si la etiqueta se analiza como un n:atributo +- `$tag->prefix: ?string`: Devuelve el prefijo utilizado con el n:atributo, que puede ser `null` (no es un n:atributo), `Tag::PrefixNone`, `Tag::PrefixInner` o `Tag::PrefixTag` + +Ahora que entendemos las etiquetas simples, el análisis de argumentos, las etiquetas emparejadas, los proveedores y los n:atributos, abordemos un escenario más complejo que involucra etiquetas anidadas dentro de otras etiquetas, utilizando nuestra etiqueta `{debug}` como punto de partida. + + +Etiquetas intermedias +===================== + +Algunas etiquetas emparejadas permiten o incluso requieren que otras etiquetas aparezcan *dentro* de ellas antes de la etiqueta de cierre final. Estas se llaman **etiquetas intermedias**. Ejemplos clásicos incluyen `{if}...{elseif}...{else}...{/if}` o `{switch}...{case}...{default}...{/switch}`. + +Ampliemos nuestra etiqueta `{debug}` para admitir una cláusula `{else}` opcional, que se renderizará cuando la aplicación *no* esté en modo de desarrollo. + +**Objetivo:** Modificar `{debug}` para admitir una etiqueta intermedia opcional `{else}`. La sintaxis final debería ser `{debug} ... {else} ... {/debug}`. + + +Análisis de etiquetas intermedias con `yield` +--------------------------------------------- + +Ya sabemos que `yield` pausa la función de análisis sintáctico `create()` y devuelve el contenido analizado junto con la etiqueta final. Sin embargo, `yield` ofrece más control: puede proporcionarle un array de *nombres de etiquetas intermedias*. Cuando el analizador sintáctico encuentra cualquiera de estas etiquetas especificadas **en el mismo nivel de anidamiento** (es decir, como hijos directos de la etiqueta padre, no dentro de otros bloques o etiquetas dentro de ella), también detiene el análisis. + +Cuando el análisis se detiene debido a una etiqueta intermedia, detiene el análisis del contenido, reanuda el generador `create()` y devuelve el contenido parcialmente analizado y la **etiqueta intermedia** misma (en lugar de la etiqueta final). Nuestra función `create()` puede entonces procesar esta etiqueta intermedia (por ejemplo, analizar sus argumentos si los tuviera) y usar `yield` nuevamente para analizar la *siguiente* parte del contenido hasta la etiqueta final *final* u otra etiqueta intermedia esperada. + +Modifiquemos `DebugNode::create()` para esperar `{else}`: + +```php +node = new self; + + // yield y esperar {/debug} o {else} + [$node->thenContent, $nextTag] = yield ['else']; + + // Verificar si la etiqueta en la que nos detuvimos fue {else} + if ($nextTag?->name === 'else') { + // Yield de nuevo para analizar el contenido entre {else} y {/debug} + [$node->elseContent, $endTag] = yield; + } + + return $node; + } + + // ... print() y getIterator() se actualizarán a continuación ... +} +``` + +Ahora `yield ['else']` le dice a Latte que detenga el análisis no solo para `{/debug}`, sino también para `{else}`. Si se encuentra `{else}`, `$nextTag` contendrá el objeto `Tag` para `{else}`. Luego usamos `yield` nuevamente sin argumentos, lo que significa que ahora solo esperamos la etiqueta final `{/debug}`, y almacenamos el resultado en `$node->elseContent`. Si no se encontró `{else}`, `$nextTag` sería el `Tag` para `{/debug}` (o `null` si se usa como n:atributo) y `$node->elseContent` permanecería `null`. + + +Implementación de `print()` con `{else}` +---------------------------------------- + +El método `print()` necesita reflejar la nueva estructura. Debería generar una sentencia PHP `if/else` basada en el proveedor `devMode`. + +```php + public function print(PrintContext $context): string + { + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + %node // Código para la rama 'then' (contenido de {debug}) + } else { + %node // Código para la rama 'else' (contenido de {else}) + } + + XX, + $this->position, // Número de línea para la condición 'if' + $this->thenContent, // Primer marcador de posición %node + $this->elseContent ?? new NopNode, // Segundo marcador de posición %node + ); + } +``` + +Esta es una estructura PHP `if/else` estándar. Usamos `%node` dos veces; `format()` reemplaza los nodos proporcionados secuencialmente. Usamos `?? new NopNode` para evitar errores si `$this->elseContent` es `null` – `NopNode` simplemente no imprime nada. + + +Implementación de `getIterator()` para ambos contenidos +------------------------------------------------------- + +Ahora tenemos potencialmente dos nodos hijos de contenido (`$thenContent` y `$elseContent`). Debemos proporcionar ambos si existen: + +```php + public function &getIterator(): \Generator + { + yield $this->thenContent; + if ($this->elseContent) { + yield $this->elseContent; + } + } +``` + + +Uso de la etiqueta mejorada +--------------------------- + +La etiqueta ahora se puede usar con la cláusula `{else}` opcional: + +```latte +{debug} +

                                                          Mostrando información de depuración, porque devMode está ACTIVADO.

                                                          +{else} +

                                                          La información de depuración está oculta, porque devMode está DESACTIVADO.

                                                          +{/debug} +``` + + +Manejo de estado y anidamiento +============================== + +Nuestros ejemplos anteriores (`{datetime}`, `{debug}`) eran relativamente sin estado dentro de sus métodos `print()`. O bien imprimían directamente el contenido o realizaban una simple verificación condicional basada en un proveedor global. Sin embargo, muchas etiquetas necesitan administrar alguna forma de **estado** durante la renderización o implican la evaluación de expresiones de usuario que deberían ejecutarse solo una vez por rendimiento o corrección. Además, debemos considerar qué sucede cuando nuestras etiquetas personalizadas están **anidadas**. + +Ilustremos estos conceptos creando una etiqueta `{repeat $count}...{/repeat}`. Esta etiqueta repetirá su contenido interno `$count` veces. + +**Objetivo:** Implementar `{repeat $count}`, que repite su contenido un número específico de veces. + + +La necesidad de variables temporales y únicas +--------------------------------------------- + +Imagine que un usuario escribe: + +```latte +{repeat rand(1, 5)} Contenido {/repeat} +``` + +Si generáramos ingenuamente un bucle `for` de PHP de esta manera en nuestro método `print()`: + +```php +// Código generado simplificado, INCORRECTO +for ($i = 0; $i < rand(1, 5); $i++) { + // imprimir contenido +} +``` +¡Esto estaría mal! La expresión `rand(1, 5)` se **volvería a evaluar en cada iteración del bucle**, lo que llevaría a un número impredecible de repeticiones. Necesitamos evaluar la expresión `$count` *una vez* antes de que comience el bucle y almacenar su resultado. + +Generaremos código PHP que primero evalúe la expresión de conteo y la almacene en una **variable temporal en tiempo de ejecución**. Para evitar colisiones con variables definidas por el usuario de la plantilla *y* variables internas de Latte (como `$ʟ_...`), usaremos la convención de prefijar **`$__` (doble guion bajo)** para nuestras variables temporales. + +El código generado se vería así: + +```php +$__count = rand(1, 5); +for ($__i = 0; $__i < $__count; $__i++) { + // imprimir contenido +} +``` + +Ahora considere el anidamiento: + +```latte +{repeat $countA} {* Bucle externo *} + {repeat $countB} {* Bucle interno *} + ... + {/repeat} +{/repeat} +``` + +Si tanto la etiqueta `{repeat}` externa como la interna generaran código usando los *mismos* nombres de variables temporales (por ejemplo, `$__count` y `$__i`), el bucle interno sobrescribiría las variables del bucle externo, rompiendo la lógica. + +Necesitamos asegurarnos de que las variables temporales generadas para cada instancia de la etiqueta `{repeat}` sean **únicas**. Logramos esto usando `PrintContext::generateId()`. Este método devuelve un entero único durante la fase de compilación. Podemos agregar este ID a los nombres de nuestras variables temporales. + +Entonces, en lugar de `$__count`, generaremos `$__count_1` para la primera etiqueta repeat, `$__count_2` para la segunda, etc. De manera similar, para el contador del bucle, usaremos `$__i_1`, `$__i_2`, etc. + + +Implementación de `RepeatNode` +------------------------------ + +Vamos a crear la clase de nodo. + +```php +expectArguments(); // se asegura de que $count se proporcione + $node = $tag->node = new self; + // Analiza la expresión de conteo + $node->count = $tag->parser->parseExpression(); + // Obtiene el contenido interno + [$node->content] = yield; + return $node; + } + + /** + * Genera un bucle 'for' de PHP con nombres de variables únicos. + */ + public function print(PrintContext $context): string + { + // Generación de nombres de variables únicos + $id = $context->generateId(); + $countVar = '$__count_' . $id; // ej. $__count_1, $__count_2, etc. + $iteratorVar = '$__i_' . $id; // ej. $__i_1, $__i_2, etc. + + return $context->format( + <<<'XX' + // Evaluar la expresión de conteo *una vez* y almacenar + %raw = (int) (%node); + // Bucle usando el conteo almacenado y una variable de iteración única + for (%raw = 0; %2.raw < %0.raw; %2.raw++) %line { + %node // Renderizar el contenido interno + } + + XX, + $countVar, // %0 - Variable para almacenar el conteo + $this->count, // %1 - Nodo de expresión para el conteo + $iteratorVar, // %2 - Nombre de la variable de iteración del bucle + $this->position, // %3 - Comentario con el número de línea para el bucle mismo + $this->content // %4 - Nodo del contenido interno + ); + } + + /** + * Proporciona los nodos hijos (expresión de conteo y contenido). + */ + public function &getIterator(): \Generator + { + yield $this->count; + yield $this->content; + } +} +``` + +El método `create()` analiza la expresión `$count` requerida usando `parseExpression()`. Primero se llama a `$tag->expectArguments()`. Esto asegura que el usuario proporcionó *algo* después de `{repeat}`. Si bien `$tag->parser->parseExpression()` fallaría si no se proporcionara nada, el mensaje de error podría ser sobre sintaxis inesperada. Usar `expectArguments()` proporciona un error mucho más claro, indicando específicamente que faltan argumentos para la etiqueta `{repeat}`. + +El método `print()` genera el código PHP responsable de ejecutar la lógica de repetición en tiempo de ejecución. Comienza generando nombres únicos para las variables PHP temporales que necesitará. + +Se llama al método `$context->format()` con un nuevo marcador de posición `%raw`, que inserta la *cadena cruda* proporcionada como el argumento correspondiente. Aquí inserta el nombre de variable único almacenado en `$countVar` (por ejemplo, `$__count_1`). ¿Y qué pasa con `%0.raw` y `%2.raw`? Esto demuestra los **marcadores de posición posicionales**. En lugar de simplemente `%raw`, que toma el *siguiente* argumento crudo disponible, `%2.raw` toma explícitamente el argumento en el índice 2 (que es `$iteratorVar`) e inserta su valor de cadena cruda. Esto nos permite reutilizar la cadena `$iteratorVar` sin pasarla varias veces en la lista de argumentos para `format()`. + +Esta llamada a `format()` cuidadosamente construida genera un bucle PHP eficiente y seguro que maneja correctamente la expresión de conteo y evita colisiones de nombres de variables incluso cuando las etiquetas `{repeat}` están anidadas. + + +Registro y uso +-------------- + +Registre la etiqueta en su extensión: + +```php +use App\Latte\RepeatNode; + +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), // Registro de la etiqueta repeat + ]; + } +} +``` + +Úsela en la plantilla, incluido el anidamiento: + +```latte +{var $rows = rand(5, 7)} +{var $cols = rand(3, 5)} + +{repeat $rows} + + {repeat $cols} + Bucle interno + {/repeat} + +{/repeat} +``` + +Este ejemplo demuestra cómo manejar el estado (contadores de bucle) y posibles problemas de anidamiento utilizando variables temporales con prefijo `$__` y únicas con un ID de `PrintContext::generateId()`. + + +n:atributos puros +----------------- + +Si bien muchos `n:atributos` como `n:if` o `n:foreach` sirven como atajos convenientes para sus contrapartes de etiquetas emparejadas (`{if}...{/if}`, `{foreach}...{/foreach}`), Latte también permite definir etiquetas que *existen solo* en forma de n:atributo. Estos se usan a menudo para modificar atributos o el comportamiento del elemento HTML al que están adjuntos. + +Ejemplos estándar incorporados en Latte incluyen [`n:class` |tags#n:class], que ayuda a construir dinámicamente el atributo `class`, y [`n:attr` |tags#n:attr], que puede establecer múltiples atributos arbitrarios. + +Creemos nuestro propio n:atributo puro: `n:confirm`, que agregará un diálogo de confirmación de JavaScript antes de realizar una acción (como seguir un enlace o enviar un formulario). + +**Objetivo:** Implementar `n:confirm="'¿Estás seguro?'"`, que agrega un manejador `onclick` para prevenir la acción predeterminada si el usuario cancela el diálogo de confirmación. + + +Implementación de `ConfirmNode` +------------------------------- + +Necesitamos una clase Node y una función de análisis sintáctico. + +```php +expectArguments(); + $node = $tag->node = new self; + $node->message = $tag->parser->parseExpression(); + return $node; + } + + /** + * Genera el código del atributo 'onclick' con el escapado correcto. + */ + public function print(PrintContext $context): string + { + // Asegura el escapado correcto para los contextos de atributo JavaScript y HTML. + return $context->format( + <<<'XX' + echo ' onclick="', LR\Filters::escapeHtmlAttr('return confirm(' . LR\Filters::escapeJs(%node) . ')'), '"' %line; + XX, + $this->message, + $this->position, + ); + } + + public function &getIterator(): \Generator + { + yield $this->message; + } +} +``` + +El método `print()` genera código PHP que finalmente, durante la renderización de la plantilla, imprimirá el atributo HTML `onclick="..."`. El manejo de contextos anidados (JavaScript dentro de un atributo HTML) requiere un escapado cuidadoso. El filtro `LR\Filters::escapeJs(%node)` se llama en tiempo de ejecución y escapa el mensaje correctamente para su uso dentro de JavaScript (la salida sería como `"Sure?"`). Luego, el filtro `LR\Filters::escapeHtmlAttr(...)` escapa los caracteres que son especiales en los atributos HTML, por lo que cambiaría la salida a `return confirm("Sure?")`. Este escapado de dos pasos en tiempo de ejecución asegura que el mensaje sea seguro para JavaScript y que el código JavaScript resultante sea seguro para incrustar en el atributo HTML `onclick`. + + +Registro y uso +-------------- + +Registre el n:atributo en su extensión. No olvide el prefijo `n:` en la clave: + +```php +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), + 'n:confirm' => ConfirmNode::create(...), // Registro de n:confirm + ]; + } +} +``` + +Ahora puede usar `n:confirm` en enlaces, botones o elementos de formulario: + +```latte +Eliminar +``` + +HTML generado: + +```html +Eliminar +``` + +Cuando el usuario hace clic en el enlace, el navegador ejecuta el código `onclick`, muestra el diálogo de confirmación y solo navega a `delete.php` si el usuario hace clic en "Aceptar". + +Este ejemplo demuestra cómo se puede crear un n:atributo puro para modificar el comportamiento o los atributos de su elemento HTML anfitrión generando el código PHP apropiado en su método `print()`. No olvide el doble escapado que a menudo se requiere: una vez para el contexto de destino (JavaScript en este caso) y nuevamente para el contexto del atributo HTML. + + +Temas avanzados +=============== + +Si bien las secciones anteriores cubren los conceptos básicos, aquí hay algunos temas más avanzados que puede encontrar al crear etiquetas Latte personalizadas. + + +Modos de salida de etiquetas +---------------------------- + +El objeto `Tag` pasado a su función `create()` tiene una propiedad `outputMode`. Esta propiedad afecta cómo Latte maneja los espacios en blanco y la indentación circundantes, especialmente cuando la etiqueta se usa en su propia línea. Puede modificar esta propiedad en su función `create()`. + +- `Tag::OutputKeepIndentation` (Predeterminado para la mayoría de las etiquetas como `{=...}`): Latte intenta preservar la indentación antes de la etiqueta. Las nuevas líneas *después* de la etiqueta generalmente se conservan. Esto es adecuado para etiquetas que imprimen contenido en línea. +- `Tag::OutputRemoveIndentation` (Predeterminado para etiquetas de bloque como `{if}`, `{foreach}`): Latte elimina la indentación inicial y potencialmente una nueva línea siguiente. Esto ayuda a mantener el código PHP generado más limpio y evita líneas vacías adicionales en la salida HTML causadas por la propia etiqueta. Use esto para etiquetas que representan estructuras de control o bloques que no deberían agregar espacios en blanco por sí mismos. +- `Tag::OutputNone` (Usado por etiquetas como `{var}`, `{default}`): Similar a `RemoveIndentation`, pero indica más fuertemente que la etiqueta en sí no produce salida directa, lo que potencialmente afecta el manejo de espacios en blanco a su alrededor de manera aún más agresiva. Adecuado para etiquetas declarativas o de configuración. + +Elija el modo que mejor se adapte al propósito de su etiqueta. Para la mayoría de las etiquetas estructurales o de control, `OutputRemoveIndentation` suele ser apropiado. + + +Acceso a etiquetas padre/más cercanas +------------------------------------- + +A veces, el comportamiento de una etiqueta necesita depender del contexto en el que se usa, específicamente en qué etiqueta(s) padre se encuentra. El objeto `Tag` pasado a su función `create()` proporciona el método `closestTag(array $classes, ?callable $condition = null): ?Tag` exactamente para este propósito. + +Este método busca hacia arriba en la jerarquía de etiquetas actualmente abiertas (incluidos los elementos HTML representados internamente durante el análisis) y devuelve el objeto `Tag` del ancestro más cercano que coincida con criterios específicos. Si no se encuentra ningún ancestro coincidente, devuelve `null`. + +El array `$classes` especifica qué tipo de etiquetas ancestro está buscando. Comprueba si el nodo asociado de la etiqueta ancestro (`$ancestorTag->node`) es una instancia de esta clase. + +```php +function create(Tag $tag) +{ + // Busca la etiqueta ancestro más cercana cuyo nodo sea una instancia de ForeachNode + $foreachTag = $tag->closestTag([ForeachNode::class]); + if ($foreachTag) { + // Podemos acceder a la instancia de ForeachNode misma: + $foreachNode = $foreachTag->node; + } +} +``` + +Observe `$foreachTag->node`: Esto solo funciona porque es una convención en el desarrollo de etiquetas Latte asignar inmediatamente el nodo creado a `$tag->node` dentro del método `create()`, como siempre hemos hecho. + +A veces, simplemente comparar el tipo de nodo no es suficiente. Es posible que necesite verificar una propiedad específica de la etiqueta ancestro potencial o su nodo. El segundo argumento opcional para `closestTag()` es un callable que recibe el objeto `Tag` ancestro potencial y debe devolver si es una coincidencia válida. + +```php +function create(Tag $tag) +{ + $dynamicBlockTag = $tag->closestTag( + [BlockNode::class], + // Condición: el bloque debe ser dinámico + fn(Tag $blockTag) => $blockTag->node->block->isDynamic(), + ); +} +``` + +Usar `closestTag()` le permite crear etiquetas que son conscientes del contexto y hacer cumplir el uso correcto dentro de la estructura de su plantilla, lo que lleva a plantillas más robustas y comprensibles. + + +Marcadores de posición de `PrintContext::format()` +-------------------------------------------------- + +A menudo hemos usado `PrintContext::format()` para generar código PHP en los métodos `print()` de nuestros nodos. Acepta una cadena de máscara y argumentos posteriores que reemplazan los marcadores de posición en la máscara. Aquí hay un resumen de los marcadores de posición disponibles: + +- **`%node`**: El argumento debe ser una instancia de `Node`. Llama al método `print()` del nodo e inserta la cadena de código PHP resultante. +- **`%dump`**: El argumento es cualquier valor PHP. Exporta el valor a código PHP válido. Adecuado para escalares, arrays, null. + - `$context->format('echo %dump;', 'Hello')` -> `echo 'Hello';` + - `$context->format('$arr = %dump;', [1, 2])` -> `$arr = [1, 2];` +- **`%raw`**: Inserta el argumento directamente en el código PHP de salida sin ningún escapado o modificación. **Use con precaución**, principalmente para insertar fragmentos de código PHP pregenerados o nombres de variables. + - `$context->format('%raw = 1;', '$variableName')` -> `$variableName = 1;` +- **`%args`**: El argumento debe ser un `Expression\ArrayNode`. Imprime los elementos del array formateados como argumentos para una llamada a función o método (separados por comas, maneja argumentos con nombre si están presentes). + - `$argsNode = new ArrayNode([...]);` + - `$context->format('myFunc(%args);', $argsNode)` -> `myFunc(1, name: 'Joe');` +- **`%line`**: El argumento debe ser un objeto `Position` (generalmente `$this->position`). Inserta un comentario PHP `/* line X */` que indica el número de línea de origen. + - `$context->format('echo "Hi" %line;', $this->position)` -> `echo "Hi" /* line 42 */;` +- **`%escape(...)`**: Genera código PHP que escapa *en tiempo de ejecución* la expresión interna utilizando las reglas de escapado actuales conscientes del contexto. + - `$context->format('echo %escape(%node);', $variableNode)` +- **`%modify(...)`**: El argumento debe ser un `ModifierNode`. Genera código PHP que aplica los filtros especificados en el `ModifierNode` al contenido interno, incluido el escapado consciente del contexto si no está deshabilitado con `|noescape`. + - `$context->format('%modify(%node);', $modifierNode, $variableNode)` +- **`%modifyContent(...)`**: Similar a `%modify`, pero diseñado para modificar bloques de contenido capturado (a menudo HTML). + +Puede hacer referencia explícita a los argumentos por su índice (basado en cero): `%0.node`, `%1.dump`, `%2.raw`, etc. Esto le permite reutilizar un argumento varias veces en la máscara sin pasarlo repetidamente a `format()`. Consulte el ejemplo de la etiqueta `{repeat}`, donde se usaron `%0.raw` y `%2.raw`. + + +Ejemplo de análisis sintáctico complejo de argumentos +----------------------------------------------------- + +Si bien `parseExpression()`, `parseArguments()`, etc., cubren muchos casos, a veces necesita una lógica de análisis sintáctico más compleja utilizando el `TokenStream` de nivel inferior disponible a través de `$tag->parser->stream`. + +**Objetivo:** Crear una etiqueta `{embedYoutube $videoID, width: 640, height: 480}`. Queremos analizar el ID de video requerido (cadena o variable) seguido de pares clave-valor opcionales para las dimensiones. + +```php +expectArguments(); + $node = $tag->node = new self; + // Analizar el ID de video requerido + $node->videoId = $tag->parser->parseExpression(); + + // Analizar pares clave-valor opcionales + $stream = $tag->parser->stream; // Obtener el flujo de tokens + while ($stream->tryConsume(',')) { // Requiere separación por coma + // Esperar el identificador 'width' o 'height' + $keyToken = $stream->consume(Token::Php_Identifier); + $key = strtolower($keyToken->text); + + $stream->consume(':'); // Esperar el separador de dos puntos + + $value = $tag->parser->parseExpression(); // Analizar la expresión del valor + + if ($key === 'width') { + $node->width = $value; + } elseif ($key === 'height') { + $node->height = $value; + } else { + throw new CompileException("Argumento desconocido '$key'. Se esperaba 'width' o 'height'.", $keyToken->position); + } + } + + return $node; + } +} +``` + +Este nivel de control le permite definir sintaxis muy específicas y complejas para sus etiquetas personalizadas interactuando directamente con el flujo de tokens. + + +Uso de `AuxiliaryNode` +---------------------- + +Latte proporciona nodos "auxiliares" genéricos para situaciones especiales durante la generación de código o dentro de los pasos de compilación. Son `AuxiliaryNode` y `Php\Expression\AuxiliaryNode`. + +Considere `AuxiliaryNode` como un nodo contenedor flexible que delega sus funcionalidades principales (generación de código y exposición de nodos hijos) a los argumentos proporcionados en su constructor: + +- Delegación de `print()`: El primer argumento del constructor es un **closure** PHP. Cuando Latte llama al método `print()` en un `AuxiliaryNode`, ejecuta este closure proporcionado. El closure recibe un `PrintContext` y cualquier nodo pasado en el segundo argumento del constructor, lo que le permite definir una lógica de generación de código PHP completamente personalizada en tiempo de ejecución. +- Delegación de `getIterator()`: El segundo argumento del constructor es un **array de objetos `Node`**. Cuando Latte necesita recorrer los hijos de un `AuxiliaryNode` (por ejemplo, durante los pasos de compilación), su método `getIterator()` simplemente proporciona los nodos listados en este array. + +Ejemplo: + +```php +$node = new AuxiliaryNode( + // 1. Este closure se convierte en el cuerpo de print() + fn(PrintContext $context, $arg1, $arg2) => $context->format('...%node...%node...', $arg1, $arg2), + + // 2. Estos nodos son proporcionados por el método getIterator() y pasados al closure anterior + [$argumentNode1, $argumentNode2] +); +``` + +Latte proporciona dos tipos distintos basados en dónde necesita insertar el código generado: + +- `Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode`: Use esto cuando necesite generar un fragmento de código PHP que represente una **expresión** +- `Latte\Compiler\Nodes\AuxiliaryNode`: Use esto para propósitos más generales, cuando necesite insertar un bloque de código PHP que represente una o más **sentencias** + +Una razón importante para usar `AuxiliaryNode` en lugar de nodos estándar (como `StaticMethodCallNode`) dentro de su método `print()` o paso de compilación es **controlar la visibilidad para los pasos de compilación posteriores**, especialmente aquellos relacionados con la seguridad como Sandbox. + +Considere un escenario: Su paso de compilación necesita envolver una expresión proporcionada por el usuario (`$userExpr`) con una llamada a una función auxiliar específica y confiable `myInternalSanitize($userExpr)`. Si crea un nodo estándar `new FunctionCallNode('myInternalSanitize', [$userExpr])`, será completamente visible para el recorrido del AST. Si un paso de Sandbox se ejecuta más tarde y `myInternalSanitize` *no* está en su lista blanca, Sandbox podría *bloquear* o modificar esta llamada, interrumpiendo potencialmente la lógica interna de su etiqueta, incluso si *usted*, el autor de la etiqueta, sabe que esta llamada específica es segura y necesaria. Por lo tanto, puede generar la llamada directamente dentro del closure de `AuxiliaryNode`. + +```php +use Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode; + +// ... dentro de print() o un paso de compilación ... +$wrappedNode = new AuxiliaryNode( + fn(PrintContext $context, $userExpr) => $context->format( + 'myInternalSanitize(%node)', // Generación directa de código PHP + $userExpr, + ), + // IMPORTANTE: ¡Aún así, pase el nodo de expresión de usuario original aquí! + [$userExpr], +); +``` + +En este caso, el paso de Sandbox ve el `AuxiliaryNode`, pero **no analiza el código PHP generado por su closure**. No puede bloquear directamente la llamada a `myInternalSanitize` generada *dentro* del closure. + +Si bien el código PHP generado en sí está oculto a los pasos, las *entradas* a ese código (nodos que representan datos o expresiones del usuario) **aún deben ser recorribles**. Por eso, el segundo argumento del constructor de `AuxiliaryNode` es crucial. **Debe** pasar un array que contenga todos los nodos originales (como `$userExpr` en el ejemplo anterior) que usa su closure. El `getIterator()` de `AuxiliaryNode` **proporcionará estos nodos**, permitiendo que los pasos de compilación como Sandbox los analicen en busca de posibles problemas. + + +Mejores prácticas +================= + +- **Propósito claro:** Asegúrese de que su etiqueta tenga un propósito claro y necesario. No cree etiquetas para tareas que se puedan resolver fácilmente con [filtros |custom-filters] o [funciones |custom-functions]. +- **Implemente `getIterator()` correctamente:** Siempre implemente `getIterator()` y proporcione *referencias* (`&`) a *todos* los nodos hijos (argumentos, contenido) que se analizaron desde la plantilla. Esto es esencial para los pasos de compilación, la seguridad (Sandbox) y posibles optimizaciones futuras. +- **Propiedades públicas para nodos:** Haga públicas las propiedades que contienen nodos hijos para que los pasos de compilación puedan modificarlos si es necesario. +- **Use `PrintContext::format()`:** Utilice el método `format()` para generar código PHP. Maneja las comillas, escapa correctamente los marcadores de posición y agrega comentarios con el número de línea automáticamente. +- **Variables temporales (`$__`):** Al generar código PHP en tiempo de ejecución que necesita variables temporales (por ejemplo, para almacenar subtotales, contadores de bucle), use la convención de prefijo `$__` para evitar colisiones con variables de usuario y variables internas de Latte `$ʟ_`. +- **Anidamiento e IDs únicos:** Si su etiqueta puede anidarse o necesita un estado específico de instancia en tiempo de ejecución, use `$context->generateId()` dentro de su método `print()` para crear sufijos únicos para sus variables temporales `$__`. +- **Proveedores para datos externos:** Use proveedores (registrados a través de `Extension::getProviders()`) para acceder a datos o servicios en tiempo de ejecución ($this->global->...) en lugar de codificar valores o depender del estado global. Use prefijos de fabricante para los nombres de los proveedores. +- **Considere los n:atributos:** Si su etiqueta emparejada opera lógicamente en un solo elemento HTML, Latte probablemente proporciona soporte automático de `n:atributo`. Tenga esto en cuenta para la conveniencia del usuario. Si está creando una etiqueta modificadora de atributos, considere si un `n:atributo` puro es la forma más apropiada. +- **Pruebas:** Escriba pruebas para sus etiquetas, cubriendo tanto el análisis sintáctico de diferentes entradas como la corrección de la salida del **código PHP** generado. + +Siguiendo estas pautas, puede crear etiquetas personalizadas potentes, robustas y mantenibles que se integren perfectamente con el motor de plantillas Latte. + +.[note] +Estudiar las clases de nodos que forman parte de Latte es la mejor manera de aprender todos los detalles sobre el proceso de análisis sintáctico. diff --git a/latte/es/develop.texy b/latte/es/develop.texy index e2306c7881..bd7211ae6b 100644 --- a/latte/es/develop.texy +++ b/latte/es/develop.texy @@ -1,70 +1,66 @@ -Prácticas para desarrolladores -****************************** +Prácticas de desarrollo +*********************** -Instalación .[#toc-installation] -================================ +Instalación +=========== -La mejor manera de instalar Latte es utilizar un Compositor: +La mejor manera de instalar Latte es usando Composer: ```shell composer require latte/latte ``` -Versiones PHP soportadas (se aplica a las últimas versiones del parche Latte): +Versiones de PHP soportadas (se aplica a las últimas versiones menores de Latte): -| versión | compatible con PHP +| versión | compatible con PHP |-----------------|------------------- | Latte 3.0 | PHP 8.0 – 8.2 -| Latte 2.11 | PHP 7.1 – 8.2 -| Latte 2.8 – 2.10| PHP 7.1 – 8.1 -Cómo renderizar una plantilla .[#toc-how-to-render-a-template] -============================================================== +Cómo renderizar una plantilla +============================= -¿Cómo renderizar una plantilla? Utilice este sencillo código: +¿Cómo renderizar una plantilla? Basta con este simple código: ```php $latte = new Latte\Engine; -// directorio caché +// directorio para la caché $latte->setTempDirectory('/path/to/tempdir'); -$params = [ /* template variables */ ]; +$params = [ /* variables de plantilla */ ]; // o $params = new TemplateParameters(/* ... */); -// renderizar a la salida +// renderizar en la salida $latte->render('template.latte', $params); -// o renderizar a variable +// renderizar en una variable $output = $latte->renderToString('template.latte', $params); ``` -Los parámetros pueden ser matrices o incluso mejor [objeto |#Parameters as a class], que proporcionará la comprobación de tipo y sugerencia en el editor. +Los parámetros pueden ser arrays o, mejor aún, un [objeto |#Parámetros como clase], que asegurará la comprobación de tipos y el autocompletado en los editores. .[note] -También puedes encontrar ejemplos de uso en el repositorio [Latte examples |https://github.com/nette-examples/latte]. +También puede encontrar ejemplos de uso en el repositorio [Latte examples |https://github.com/nette-examples/latte]. -Rendimiento y caché .[#toc-performance-and-caching] -=================================================== +Rendimiento y caché +=================== -Las plantillas Latte son extremadamente rápidas, porque Latte las compila directamente en código PHP y las almacena en caché en disco. Por lo tanto, no tienen sobrecarga adicional en comparación con las plantillas escritas en PHP puro. +Las plantillas en Latte son extremadamente rápidas, ya que Latte las compila directamente a código PHP y las almacena en caché en el disco. Por lo tanto, no tienen ninguna sobrecarga adicional en comparación con las plantillas escritas en PHP puro. -La caché se regenera automáticamente cada vez que se modifica el fichero fuente. Así que puede editar cómodamente sus plantillas Latte durante el desarrollo y ver los cambios inmediatamente en el navegador. Puede desactivar esta característica en un entorno de producción y ahorrar un poco de rendimiento: +La caché se regenera automáticamente cada vez que cambia el archivo fuente. Durante el desarrollo, puede editar cómodamente las plantillas en Latte y ver los cambios inmediatamente en el navegador. Puede desactivar esta función en el entorno de producción para ahorrar un poco de rendimiento: ```php $latte->setAutoRefresh(false); ``` -Cuando se despliega en un servidor de producción, la generación inicial de caché, especialmente para aplicaciones grandes, puede tardar un poco. Latte tiene incorporada una prevención contra la "estampida de caché":https://en.wikipedia.org/wiki/Cache_stampede. -Esta es una situación en la que el servidor recibe un gran número de peticiones concurrentes y debido a que la caché de Latte aún no existe, todas ellas la generarían al mismo tiempo. Lo que dispara la CPU. -Latte es inteligente, y cuando hay múltiples peticiones concurrentes, sólo el primer hilo genera la caché, los demás esperan y luego la usan. +Al desplegar en un servidor de producción, la generación inicial de la caché, especialmente en aplicaciones más grandes, puede tardar comprensiblemente un momento. Latte tiene una prevención incorporada contra la [estampida de caché |https://en.wikipedia.org/wiki/Cache_stampede]. Esta es una situación en la que un gran número de solicitudes concurrentes que inician Latte coinciden, y como la caché aún no existe, todas comenzarían a generarla simultáneamente. Lo que sobrecargaría desproporcionadamente el servidor. Latte es inteligente y, en caso de múltiples solicitudes concurrentes, solo el primer hilo genera la caché, los demás esperan y luego la utilizan. -Parámetros como clase .[#toc-parameters-as-a-class] -=================================================== +Parámetros como clase +===================== -Mejor que pasar variables a la plantilla como arrays es crear una clase. Obtienes [notación de tipo seguro |type-system], [buena sugerencia en IDE |recipes#Editors and IDE] y una forma de [registrar filtros |extending-latte#Filters Using the Class] y [funciones |extending-latte#Functions Using the Class]. +Mejor que pasar variables a la plantilla como un array es crear una clase. Obtendrá así una [escritura segura de tipos |type-system], un [agradable autocompletado en IDE |recipes#Editores e IDE] y una forma de [registro de filtros |custom-filters#Filtros usando una clase con atributos] y [funciones |custom-functions#Funciones usando una clase con atributos]. ```php class MailTemplateParameters @@ -88,10 +84,10 @@ $latte->render('mail.latte', new MailTemplateParameters( ``` -Deshabilitando Auto-Escaping de Variable .[#toc-disabling-auto-escaping-of-variable] -==================================================================================== +Desactivar el auto-escapado de variables +======================================== -Si la variable contiene una cadena HTML, puede marcarla para que Latte no la escape automáticamente (y por tanto doblemente). Esto evita la necesidad de especificar `|noescape` en la plantilla. +Si una variable contiene una cadena en HTML, puede marcarla para que Latte no la escape automáticamente (y por lo tanto doblemente). Evitará así la necesidad de indicar `|noescape` en la plantilla. La forma más sencilla es envolver la cadena en un objeto `Latte\Runtime\Html`: @@ -101,7 +97,7 @@ $params = [ ]; ``` -Latte tampoco escapa todos los objetos que implementan la interfaz `Latte\HtmlStringable`. Así que puedes crear tu propia clase cuyo método `__toString()` devuelva código HTML que no será escapado automáticamente: +Latte además no escapa todos los objetos que implementan la interfaz `Latte\HtmlStringable`. Puede así crear su propia clase cuyo método `__toString()` devuelva código HTML que no se escapará automáticamente: ```php class Emphasis extends Latte\HtmlStringable @@ -123,32 +119,85 @@ $params = [ ``` .[warning] -El método `__toString` debe devolver HTML correcto y proporcionar escapado de parámetros, ¡de lo contrario puede producirse una vulnerabilidad XSS! +El método `__toString` debe devolver HTML correcto y asegurar el escapado de los parámetros, de lo contrario puede producirse una vulnerabilidad XSS. -Cómo extender Latte con filtros, etiquetas, etc. .[#toc-how-to-extend-latte-with-filters-tags-etc] -================================================================================================== +Cómo extender Latte con filtros, etiquetas, etc. +================================================ -¿Cómo añadir un filtro personalizado, función, etiqueta, etc. a Latte? Descúbrelo en el capítulo [Extendiendo |extending Latte] Latte. -Si quieres reutilizar tus cambios en diferentes proyectos o si quieres compartirlos con otros, entonces debes [crear una extensión |creating-extension]. +¿Cómo añadir a Latte su propio filtro, función, etiqueta, etc.? De eso trata el capítulo [Extender Latte |extending-latte]. Si desea reutilizar sus modificaciones en diferentes proyectos o compartirlas con otros, debería [crear una extensión |extending-latte#Latte Extension]. -Cualquier código en la plantilla `{php ...}` .{data-version:3.0}{toc: RawPhpExtension} -====================================================================================== +Código arbitrario en la plantilla `{php ...}` .{toc: RawPhpExtension} +===================================================================== -Dentro de la etiqueta [`{do}` |tags#do] por lo que no puede, por ejemplo, insertar construcciones como `if ... else` o sentencias terminadas en punto y coma. +Dentro de la etiqueta [`{do}` |tags#do] solo se pueden escribir expresiones PHP, no puede, por ejemplo, insertar construcciones como `if ... else` o sentencias terminadas con punto y coma. -Sin embargo, puede registrar la extensión `RawPhpExtension`, que añade la etiqueta `{php ...}`, que puede utilizarse para insertar cualquier código PHP bajo la responsabilidad del autor de la plantilla. +Sin embargo, puede registrar la extensión `RawPhpExtension`, que añade la etiqueta `{php ...}`. Con ella se puede insertar cualquier código PHP. No se le aplican ninguna de las reglas del modo sandbox, por lo que su uso es responsabilidad del autor de la plantilla. ```php $latte->addExtension(new Latte\Essential\RawPhpExtension); ``` -Traducción en plantillas .{data-version:3.0}{toc: TranslatorExtension} -====================================================================== +Comprobación del código generado .{data-version:3.0.7} +====================================================== -Utilice la extensión `TranslatorExtension` para añadir [`{_...}` |tags#_], [`{translate}` |tags#translate] y filtrar [`translate` |filters#translate] a la plantilla. Se utilizan para traducir valores o partes de la plantilla a otros idiomas. El parámetro es el método (PHP callable) que realiza la traducción: +Latte compila las plantillas en código PHP. Por supuesto, se asegura de que el código generado sea sintácticamente válido. Sin embargo, al usar extensiones de terceros o `RawPhpExtension`, Latte no puede garantizar la corrección del archivo generado. También es posible escribir en PHP código que, aunque sintácticamente correcto, está prohibido (por ejemplo, asignar un valor a la variable `$this`) y causa un PHP Compile Error. Si escribe tal operación en la plantilla, también llegará al código PHP generado. Dado que en PHP existen unas doscientas operaciones prohibidas diferentes, Latte no tiene la ambición de detectarlas. El propio PHP las señalará al renderizar, lo que normalmente no importa. + +Sin embargo, hay situaciones en las que desea saber ya en el momento de la compilación de la plantilla que no contiene ningún PHP Compile Error. Especialmente si las plantillas pueden ser editadas por los usuarios, o si utiliza [Sandbox |sandbox]. En tal caso, haga que las plantillas se comprueben ya en el momento de la compilación. Esta funcionalidad se activa con el método `Engine::enablePhpLint()`. Dado que para la comprobación necesita llamar al binario de PHP, pase la ruta a él como parámetro: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); + +try { + $latte->compile('home.latte'); +} catch (Latte\CompileException $e) { + // captura errores en Latte y también Compile Error en PHP + echo 'Error: ' . $e->getMessage(); +} +``` + + +Configuración regional .{data-version:3.0.18}{toc: Locale} +========================================================== + +Latte permite establecer la configuración regional, que afecta al formato de números, fechas y ordenación. Se establece mediante el método `setLocale()`. El identificador de entorno sigue el estándar IETF language tag, que utiliza la extensión PHP `intl`. Se compone del código de idioma y, opcionalmente, del código de país, p. ej., `en_US` para inglés en Estados Unidos, `de_DE` para alemán en Alemania, etc. + +```php +$latte = new Latte\Engine; +$latte->setLocale('es_ES'); +``` + +La configuración del entorno afecta a los filtros [localDate |filters#localDate], [sort |filters#sort], [number |filters#number] y [bytes |filters#bytes]. + +.[note] +Requiere la extensión PHP `intl`. La configuración en Latte no afecta a la configuración global de locale en PHP. + + +Modo estricto .{data-version:3.0.8} +=================================== + +En el modo estricto de análisis sintáctico, Latte comprueba si faltan etiquetas HTML de cierre y también prohíbe el uso de la variable `$this`. Se activa así: + +```php +$latte = new Latte\Engine; +$latte->setStrictParsing(); +``` + +La generación de plantillas con la cabecera `declare(strict_types=1)` se activa así: + +```php +$latte = new Latte\Engine; +$latte->setStrictTypes(); +``` + + +Traducción en plantillas .{toc: TranslatorExtension} +==================================================== + +Usando la extensión `TranslatorExtension`, añade a la plantilla las etiquetas [`{_...}` |tags#], [`{translate}` |tags#translate] y el filtro [`translate` |filters#translate]. Sirven para traducir valores o partes de la plantilla a otros idiomas. Como parámetro, especificamos el método (PHP callable) que realiza la traducción: ```php class MyTranslator @@ -158,7 +207,7 @@ class MyTranslator public function translate(string $original): string { - // crear $traducido a partir de $original según $this->lang + // creamos $translated a partir de $original según $this->lang return $translated; } } @@ -170,7 +219,7 @@ $extension = new Latte\Essential\TranslatorExtension( $latte->addExtension($extension); ``` -El traductor es llamado en tiempo de ejecución cuando la plantilla es renderizada. Sin embargo, Latte puede traducir todos los textos estáticos durante la compilación de la plantilla. Esto ahorra rendimiento porque cada cadena se traduce sólo una vez y la traducción resultante se escribe en el archivo compilado. Esto crea múltiples versiones compiladas de la plantilla en el directorio caché, una para cada idioma. Para ello, sólo tiene que especificar el idioma como segundo parámetro: +El traductor se llama en tiempo de ejecución al renderizar la plantilla. Sin embargo, Latte puede traducir todos los textos estáticos ya durante la compilación de la plantilla. Esto ahorra rendimiento, ya que cada cadena se traduce solo una vez y la traducción resultante se escribe en la forma compilada. En el directorio de caché se crean así múltiples versiones compiladas de la plantilla, una para cada idioma. Para ello, basta con indicar el idioma como segundo parámetro: ```php $extension = new Latte\Essential\TranslatorExtension( @@ -179,9 +228,9 @@ $extension = new Latte\Essential\TranslatorExtension( ); ``` -Por texto estático entendemos, por ejemplo, `{_'hello'}` o `{translate}hello{/translate}`. El texto no estático, como `{_$foo}`, seguirá traduciéndose en tiempo de ejecución. +Texto estático significa, por ejemplo, `{_'hola'}` o `{translate}hola{/translate}`. Los textos no estáticos, como `{_$foo}`, seguirán traduciéndose en tiempo de ejecución. -La plantilla también puede pasar parámetros adicionales al traductor a través de `{_$original, foo: bar}` o `{translate foo: bar}`, que recibe como matriz `$params`: +También se pueden pasar parámetros adicionales al traductor desde la plantilla usando `{_$original, foo: bar}` o `{translate foo: bar}`, que obtendrá como array `$params` en el segundo (y siguientes) argumento(s) de la función `translate`: ```php public function translate(string $original, ...$params): string @@ -191,40 +240,41 @@ public function translate(string $original, ...$params): string ``` -Depuración y Tracy .[#toc-debugging-and-tracy] -============================================== +Depuración y Tracy +================== -Latte intenta que el desarrollo sea lo más agradable posible. A efectos de depuración, existen tres etiquetas [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak] y [`{trace}` |tags#trace]. +Latte intenta facilitarle el desarrollo tanto como sea posible. Directamente para fines de depuración existen tres etiquetas [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak] y [`{trace}` |tags#trace]. -Obtendrás la mayor comodidad si instalas la gran [herramienta de depuración Tracy |tracy:] y activas el plugin Latte: +Obtendrá la mayor comodidad si además instala la excelente [herramienta de depuración Tracy |tracy:] y activa el complemento para Latte: ```php // activa Tracy Tracy\Debugger::enable(); $latte = new Latte\Engine; -// activa la extensión de Tracy +// activa la extensión para Tracy $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension); ``` -Ahora verás todos los errores en una pulcra pantalla roja, incluidos los errores en plantillas con resaltado de filas y columnas ([vídeo |https://github.com/nette/tracy/releases/tag/v2.9.0]). -Al mismo tiempo, en la esquina inferior derecha, en la llamada Tracy Bar, aparece una pestaña para Latte, donde puedes ver claramente todas las plantillas renderizadas y sus relaciones (incluyendo la posibilidad de hacer clic en la plantilla o en el código compilado), así como las variables: +Ahora todos los errores se mostrarán en una pantalla roja clara, incluidos los errores en las plantillas con resaltado de línea y columna ([vídeo |https://github.com/nette/tracy/releases/tag/v2.9.0]). Al mismo tiempo, en la esquina inferior derecha, en la llamada Tracy Bar, aparecerá una pestaña para Latte, donde se ven claramente todas las plantillas renderizadas y sus relaciones mutuas (incluida la posibilidad de hacer clic para ir a la plantilla o al código compilado) y también las variables: [* latte-debugging.webp *] -Dado que Latte compila las plantillas en código PHP legible, puede recorrerlas cómodamente en su IDE. +Dado que Latte compila las plantillas en código PHP claro, puede recorrerlas cómodamente en su IDE. -Linter: Validando la Sintaxis de la Plantilla .{data-version:2.11}{toc: Linter} -=============================================================================== +Linter: validación de la sintaxis de las plantillas .{toc: Linter} +================================================================== -La herramienta Linter le ayudará a revisar todas las plantillas y comprobar si hay errores de sintaxis. Se ejecuta desde la consola: +La herramienta Linter le ayuda a recorrer todas las plantillas y comprobar si contienen errores de sintaxis. Se ejecuta desde la consola: ```shell -vendor/bin/latte-lint +vendor/bin/latte-lint ``` -Si utiliza etiquetas personalizadas, cree también su Linter personalizado, por ejemplo `custom-latte-lint`: +Con el parámetro `--strict` activa el [#modo estricto]. + +Si utiliza etiquetas personalizadas, cree también su propia versión de Linter, p. ej., `custom-latte-lint`: ```php #!/usr/bin/env php @@ -233,24 +283,30 @@ Si utiliza etiquetas personalizadas, cree también su Linter personalizado, por // introduzca la ruta real al archivo autoload.php require __DIR__ . '/vendor/autoload.php'; -$linter = new Latte\Tools\Linter($engine); -$linter->scanDirectory($path); +$path = $argv[1] ?? '.'; -$engine = new Latte\Engine; -// registra aquí las extensiones individuales -$engine->addExtension(/* ... */); +$linter = new Latte\Tools\Linter; +$latte = $linter->getEngine(); +// añada aquí sus extensiones individuales +$latte->addExtension(/* ... */); -$path = $argv[1]; -$linter = new Latte\Tools\Linter(engine: $engine); $ok = $linter->scanDirectory($path); exit($ok ? 0 : 1); ``` +Alternativamente, puede pasar su propio objeto `Latte\Engine` al Linter: + +```php +$latte = new Latte\Engine; +// aquí configuramos el objeto $latte +$linter = new Latte\Tools\Linter(engine: $latte); +``` + -Carga de plantillas desde una cadena .[#toc-loading-templates-from-a-string] -============================================================================ +Carga de plantillas desde una cadena +==================================== -¿Necesita cargar plantillas a partir de cadenas en lugar de archivos, quizás con fines de prueba? [StringLoader |extending-latte#stringloader] le ayudará: +¿Necesita cargar plantillas desde cadenas en lugar de archivos, por ejemplo, para fines de prueba? Le ayudará [StringLoader |loaders#StringLoader]: ```php $latte->setLoader(new Latte\Loaders\StringLoader([ @@ -262,10 +318,10 @@ $latte->render('main.file', $params); ``` -Manejador de Excepciones .[#toc-exception-handler] -================================================== +Manejador de excepciones +======================== -Puede definir su propio manejador para las excepciones esperadas. Las excepciones que se produzcan dentro de [`{try}` |tags#try] y en el [sandbox |sandbox] se le pasan. +Puede definir su propio manejador para excepciones esperadas. Se le pasarán las excepciones que surjan dentro de [`{try}` |tags#try] y en el [sandbox |sandbox]. ```php $loggingHandler = function (Throwable $e, Latte\Runtime\Template $template) use ($logger) { @@ -277,17 +333,17 @@ $latte->setExceptionHandler($loggingHandler); ``` -Búsqueda automática de diseños .[#toc-automatic-layout-lookup] -============================================================== +Búsqueda automática de layout +============================= -Mediante la etiqueta [`{layout}` |template-inheritance#layout-inheritance] la plantilla determina su plantilla padre. También es posible que el diseño se busque automáticamente, lo que simplificará la escritura de plantillas, ya que no tendrán que incluir la etiqueta `{layout}`. +Usando la etiqueta [`{layout}` |template-inheritance#Herencia de layout layout], la plantilla determina su plantilla padre. También es posible dejar que el layout se busque automáticamente, lo que simplificará la escritura de plantillas, ya que no será necesario indicar la etiqueta `{layout}` en ellas. -Esto se consigue de la siguiente manera: +Se logra de la siguiente manera: ```php $finder = function (Latte\Runtime\Template $template) { if (!$template->getReferenceType()) { - // devuelve la ruta al archivo de plantilla padre + // devuelve la ruta al archivo de layout return 'automatic.layout.latte'; } }; @@ -296,4 +352,4 @@ $latte = new Latte\Engine; $latte->addProvider('coreParentFinder', $finder); ``` -Si la plantilla no debe tener diseño, lo indicará con la etiqueta `{layout none}`. +Si la plantilla no debe tener layout, lo indica con la etiqueta `{layout none}`. diff --git a/latte/es/extending-latte.texy b/latte/es/extending-latte.texy index 005086d301..88f1a8e32b 100644 --- a/latte/es/extending-latte.texy +++ b/latte/es/extending-latte.texy @@ -1,285 +1,227 @@ -Ampliación de Latte -******************* +Extender Latte +************** .[perex] -Latte es muy flexible y se puede ampliar de muchas maneras: puede añadir filtros personalizados, funciones, etiquetas, cargadores, etc. Le mostraremos cómo hacerlo. +Latte está diseñado pensando en la extensibilidad. Aunque su conjunto estándar de etiquetas, filtros y funciones cubre muchos casos de uso, a menudo necesita añadir su propia lógica específica o herramientas auxiliares. Esta página ofrece una visión general de las formas de extender Latte para que se ajuste perfectamente a los requisitos de su proyecto, desde simples ayudantes hasta complejas sintaxis nuevas. -Este capítulo describe las diferentes formas de extender Latte. Si quieres reutilizar tus cambios en diferentes proyectos o si quieres compartirlos con otros, entonces deberías [crear la llamada extensión |creating-extension]. +Formas de extender Latte +======================== -¿Cuántos caminos llevan a Roma? .[#toc-how-many-roads-lead-to-rome] -=================================================================== +Aquí tiene un resumen rápido de las principales formas en que puede personalizar y extender Latte: -Dado que algunas de las formas de extender Latte pueden mezclarse, intentemos primero explicar las diferencias entre ellas. Como ejemplo, intentemos implementar un generador *Lorem ipsum*, al que se le pasa el número de palabras a generar. +- **[Filtros personalizados |custom-filters]:** Para formatear o transformar datos directamente en la salida de la plantilla (por ejemplo, `{$var|myFilter}`). Ideal para tareas como formatear fechas, modificar texto o aplicar un escape específico. También puede usarlos para modificar bloques más grandes de contenido HTML envolviendo el contenido en un [`{block}` |tags#block] anónimo y aplicándole un filtro personalizado. +- **[Funciones personalizadas |custom-functions]:** Para añadir lógica reutilizable que se puede llamar dentro de expresiones en la plantilla (por ejemplo, `{myFunction($arg1, $arg2)}`). Útil para cálculos, acceso a funciones auxiliares de la aplicación o generación de pequeñas partes de contenido. +- **[Etiquetas personalizadas |custom-tags]:** Para crear construcciones lingüísticas completamente nuevas (`{mytag}...{/mytag}` o `n:mytag`). Las etiquetas ofrecen la mayor flexibilidad, permitiéndole definir sus propias estructuras, controlar el análisis sintáctico de la plantilla e implementar lógica de renderizado compleja. +- **[Pasos de compilación |compiler-passes]:** Funciones que modifican el árbol de sintaxis abstracto (AST) de la plantilla después del análisis sintáctico, pero antes de generar el código PHP. Se utilizan para optimizaciones avanzadas, controles de seguridad (como Sandbox) o modificaciones automáticas del código. +- **[Loaders personalizados |loaders]:** Para cambiar la forma en que Latte busca y carga los archivos de plantilla (por ejemplo, cargando desde una base de datos, almacenamiento cifrado, etc.). -La principal construcción del lenguaje Latte es la etiqueta. Podemos implementar un generador extendiendo Latte con una nueva etiqueta: +Elegir el método de extensión correcto es crucial. Antes de crear una etiqueta compleja, considere si un filtro o función más simple sería suficiente. Veamos un ejemplo: implementar un generador de *Lorem ipsum* que toma como argumento el número de palabras a generar. -```latte -{lipsum 40} -``` - -La etiqueta funcionará muy bien. Sin embargo, el generador en forma de etiqueta puede no ser lo suficientemente flexible porque no se puede utilizar en una expresión. Por cierto, en la práctica, rara vez necesitarás generar etiquetas; y eso es una buena noticia, porque las etiquetas son una forma más complicada de extender. - -Bien, intentemos crear un filtro en lugar de una etiqueta: - -```latte -{=40|lipsum} -``` - -De nuevo, una opción válida. Pero el filtro debería transformar el valor pasado en otra cosa. Aquí usamos el valor `40`, que indica el número de palabras generadas, como argumento del filtro, no como el valor que queremos transformar. - -Así que intentemos usar la función +- **¿Como etiqueta?** `{lipsum 40}` - Posible, pero las etiquetas son más adecuadas para estructuras de control o para generar etiquetas complejas. Las etiquetas no se pueden usar directamente en expresiones. +- **¿Como filtro?** `{=40|lipsum}` - Técnicamente funciona, pero los filtros están diseñados para *transformar* el valor de entrada. Aquí, `40` es un *argumento*, no un valor que se transforma. Esto parece semánticamente incorrecto. +- **¿Como función?** `{lipsum(40)}` - ¡Esta es la solución más natural! Las funciones aceptan argumentos y devuelven valores, lo cual es ideal para usar en cualquier expresión: `{var $text = lipsum(40)}`. -```latte -{lipsum(40)} -``` - -¡Eso es! Para este ejemplo en particular, crear una función es el punto de extensión ideal a utilizar. Puedes llamarla en cualquier lugar donde se acepte una expresión, por ejemplo: +**Recomendación general:** Use funciones para cálculos/generación, filtros para transformación y etiquetas para nuevas construcciones lingüísticas o etiquetas complejas. Use pasos para la manipulación del AST y loaders para obtener plantillas. -```latte -{var $text = lipsum(40)} -``` +Registro directo +================ -Filtros .[#toc-filters] -======================= +Para herramientas auxiliares específicas del proyecto o extensiones rápidas, Latte permite el registro directo de filtros y funciones en el objeto `Latte\Engine`. -Crea un filtro registrando su nombre y cualquier llamada PHP, como una función: +Para registrar un filtro, use el método `addFilter()`. El primer argumento de su función de filtro será el valor antes del carácter `|` y los argumentos siguientes son los que se pasan después de los dos puntos `:`. ```php $latte = new Latte\Engine; -$latte->addFilter('shortify', fn(string $s) => mb_substr($s, 0, 10)); // acorta el texto a 10 caracteres -``` -En este caso sería mejor que el filtro recibiera un parámetro adicional: - -```php -$latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); -``` +// Definición del filtro (objeto invocable: función, método estático, etc.) +$myTruncate = fn(string $s, int $length = 50) => mb_substr($s, 0, $length); -Lo usamos en una plantilla como esta: +// Registro +$latte->addFilter('truncate', $myTruncate); -```latte -

                                                          {$text|shortify}

                                                          -

                                                          {$text|shortify:100}

                                                          +// Uso en la plantilla: {$text|truncate} o {$text|truncate:100} ``` -Como puede ver, la función recibe la parte izquierda del filtro antes de la tubería `|` as the first argument and the arguments passed to the filter after `:` como los siguientes argumentos. - -Por supuesto, la función que representa el filtro puede aceptar cualquier número de parámetros, y también se admiten parámetros variádicos. - - -Filtros que utilizan la clase .[#toc-filters-using-the-class] -------------------------------------------------------------- - -La segunda forma de definir un filtro es [utilizar |develop#Parameters as a class] la clase. Creamos un método con el atributo `TemplateFilter`: +También puede registrar un **Filter Loader**, una función que proporciona dinámicamente objetos invocables de filtro según el nombre solicitado: ```php -class TemplateParameters -{ - public function __construct( - // parameters - ) {} - - #[Latte\Attributes\TemplateFilter] - public function shortify(string $s, int $len = 10): string - { - return mb_substr($s, 0, $len); - } -} - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); +$latte->addFilterLoader(fn(string $name) => /* devuelve un objeto invocable o null */); ``` -Si utiliza PHP 7.x y Latte 2.x, utilice la anotación `/** @filter */` en lugar del atributo. - -Cargador de filtros .{data-version:2.10}[#toc-filter-loader] ------------------------------------------------------------- - -En lugar de registrar filtros individuales, puede crear un llamado cargador, que es una función que se llama con el nombre del filtro como argumento y devuelve su PHP callable, o null. +Para registrar una función utilizable en expresiones de plantilla, use `addFunction()`. ```php -$latte->addFilterLoader([new Filters, 'load']); +$latte = new Latte\Engine; +// Definición de la función +$isWeekend = fn(DateTimeInterface $date) => $date->format('N') >= 6; -class Filters -{ - public function load(string $filter): ?callable - { - if (in_array($filter, get_class_methods($this))) { - return [$this, $filter]; - } - return null; - } - - public function shortify($s, $len = 10) - { - return mb_substr($s, 0, $len); - } - - // ... -} +// Registro +$latte->addFunction('isWeekend', $isWeekend); + +// Uso en la plantilla: {if isWeekend($myDate)}¡Fin de semana!{/if} ``` +Para más información, consulte las secciones [Creación de filtros personalizados |custom-filters] y [Funciones |custom-functions]. -Filtros contextuales .[#toc-contextual-filters] ------------------------------------------------ -Un filtro contextual es aquel que acepta un objeto [api:Latte\Runtime\FilterInfo] en el primer parámetro, seguido de otros parámetros como en el caso de los filtros clásicos. Se registra de la misma manera, el propio Latte reconoce que el filtro es contextual: +Método robusto: Latte Extension .{toc: Latte Extension} +======================================================= -```php -use Latte\Runtime\FilterInfo; +Si bien el registro directo es simple, la forma estándar y recomendada de empaquetar y distribuir extensiones Latte es a través de clases **Extension**. Una Extension sirve como un punto de configuración central para registrar múltiples etiquetas, filtros, funciones, pasos de compilación y otros elementos. -$latte->addFilter('foo', function (FilterInfo $info, string $str): string { - // ... -}); -``` +¿Por qué usar Extensions? + +- **Organización:** Mantiene las extensiones relacionadas (etiquetas, filtros, etc. para una función específica) juntas en una sola clase. +- **Reutilización y compartición:** Empaquete fácilmente sus extensiones para usarlas en otros proyectos o para compartirlas con la comunidad (por ejemplo, a través de Composer). +- **Potencia completa:** Las etiquetas personalizadas y los pasos de compilación *solo se pueden registrar* a través de Extensions. -Los filtros contextuales pueden detectar y cambiar el tipo de contenido que reciben en la variable `$info->contentType`. Si el filtro se llama clásicamente sobre una variable (por ejemplo `{$var|foo}`), el `$info->contentType` contendrá null. -El filtro debe comprobar primero si el tipo de contenido de la cadena de entrada es compatible. También puede cambiarlo. Ejemplo de un filtro que acepta texto (o null) y devuelve HTML: +Registrar una Extension +----------------------- + +Una Extension se registra en Latte usando el método `addExtension()` (o a través del [archivo de configuración |application:configuration#Plantillas Latte]): ```php -use Latte\Runtime\FilterInfo; - -$latte->addFilter('money', function (FilterInfo $info, float $amount): string { - // first we check if the input's content-type is text - if (!in_array($info->contentType, [null, ContentType::Text])) { - throw new Exception("Filter |money used in incompatible content type $info->contentType."); - } - - // change content-type to HTML - $info->contentType = ContentType::Html; - return "$num Kč"; -}); +$latte = new Latte\Engine; +$latte->addExtension(new MyProjectExtension); ``` -.[note] -En este caso, el filtro debe garantizar el correcto escapado de los datos. +Si registra múltiples extensiones y definen etiquetas, filtros o funciones con el mismo nombre, la extensión agregada más recientemente tiene prioridad. Esto también significa que sus extensiones pueden sobrescribir etiquetas/filtros/funciones nativas. -Todos los filtros que se utilicen sobre [bloques |tags#block] (por ejemplo, como `{block|foo}...{/block}`) deben ser contextuales. +Cada vez que realice un cambio en la clase y la actualización automática no esté desactivada, Latte recompilará automáticamente sus plantillas. -Funciones .{data-version:2.6}[#toc-functions] -============================================= +Crear una Extension +------------------- -Por defecto, todas las funciones nativas de PHP pueden ser usadas en Latte, a menos que el sandbox lo deshabilite. Pero también puede definir sus propias funciones. Estas pueden sobreescribir las funciones nativas. +Para crear su propia extensión, necesita crear una clase que herede de [api:Latte\Extension]. Para tener una idea de cómo se ve una extensión de este tipo, eche un vistazo a la [CoreExtension |https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php] integrada. -Crear una función mediante el registro de su nombre y cualquier PHP callable: +Veamos los métodos que puede implementar: -```php -$latte = new Latte\Engine; -$latte->addFunction('random', function (...$args) { - return $args[array_rand($args)]; -}); -``` -El uso es entonces el mismo que cuando se llama a la función PHP: +beforeCompile(Latte\Engine $engine): void .[method] +--------------------------------------------------- -```latte -{random(apple, orange, lemon)} // prints for example: apple -``` +Se llama antes de compilar la plantilla. El método se puede usar, por ejemplo, para inicializaciones relacionadas con la compilación. -Funciones usando la clase .[#toc-functions-using-the-class] ------------------------------------------------------------ +getTags(): array .[method] +-------------------------- -La segunda forma de definir una función es [utilizar |develop#Parameters as a class] la clase. Creamos un método con el atributo `TemplateFunction`: +Se llama al compilar la plantilla. Devuelve un array asociativo *nombre de etiqueta => objeto invocable*, que son funciones para analizar sintácticamente etiquetas. [Más información |custom-tags]. ```php -class TemplateParameters +public function getTags(): array { - public function __construct( - // parameters - ) {} - - #[Latte\Attributes\TemplateFunction] - public function random(...$args) - { - return $args[array_rand($args)]; - } + return [ + 'foo' => FooNode::create(...), + 'bar' => BarNode::create(...), + 'n:baz' => NBazNode::create(...), + // ... + ]; } - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); ``` -Si utiliza PHP 7.x y Latte 2.x, utilice la anotación `/** @function */` en lugar del atributo. +La etiqueta `n:baz` representa un [n:atributo |syntax#n:atributos] puro, es decir, una etiqueta que solo se puede escribir como un atributo. +Para las etiquetas `foo` y `bar`, Latte reconoce automáticamente si son etiquetas pares y, si es así, se pueden escribir automáticamente usando n:atributos, incluidas las variantes con los prefijos `n:inner-foo` y `n:tag-foo`. -Cargadores .[#toc-loaders] -========================== +El orden de ejecución de dichos n:atributos está determinado por su orden en el array devuelto por el método `getTags()`. Por lo tanto, `n:foo` siempre se ejecuta antes que `n:bar`, incluso si los atributos en la etiqueta HTML se enumeran en orden inverso como `
                                                          `. -Los cargadores se encargan de cargar plantillas desde una fuente, como un sistema de archivos. Se configuran utilizando el método `setLoader()`: +Si necesita especificar el orden de los n:atributos en múltiples extensiones, use el método auxiliar `order()`, donde el parámetro `before` o `after` especifica qué etiquetas se ordenan antes o después de la etiqueta. ```php -$latte->setLoader(new MyLoader); +public function getTags(): array +{ + return [ + 'foo' => self::order(FooNode::create(...), before: 'bar'), + 'bar' => self::order(BarNode::create(...), after: ['block', 'snippet']), + ]; +} ``` -Los cargadores incorporados son: +getPasses(): array .[method] +---------------------------- -Cargador de archivos .[#toc-fileloader] ---------------------------------------- +Se llama al compilar la plantilla. Devuelve un array asociativo *nombre de pase => objeto invocable*, que son funciones que representan los llamados [pasos de compilación |compiler-passes], que recorren y modifican el AST. -Cargador por defecto. Carga plantillas desde el sistema de archivos. - -El acceso a los archivos puede restringirse estableciendo el directorio base: +Aquí también se puede usar el método auxiliar `order()`. El valor de los parámetros `before` o `after` puede ser `*` con el significado de antes/después de todo. ```php -$latte->setLoader(new Latte\Loaders\FileLoader($templateDir)); -$latte->render('test.latte'); +public function getPasses(): array +{ + return [ + 'optimize' => Passes::optimizePass(...), + 'sandbox' => self::order($this->sandboxPass(...), before: '*'), + // ... + ]; +} ``` -StringLoader .[#toc-stringloader] ---------------------------------- +beforeRender(Latte\Engine $engine): void .[method] +-------------------------------------------------- -Carga plantillas a partir de cadenas. Este cargador es muy útil para pruebas unitarias. También se puede utilizar para proyectos pequeños donde puede tener sentido almacenar todas las plantillas en un único archivo PHP. +Se llama antes de cada renderizado de la plantilla. El método se puede usar, por ejemplo, para inicializar variables utilizadas durante el renderizado. -```php -$latte->setLoader(new Latte\Loaders\StringLoader([ - 'main.file' => '{include other.file}', - 'other.file' => '{if true} {$var} {/if}', -])); -$latte->render('main.file'); -``` +getFilters(): array .[method] +----------------------------- -Uso simplificado: +Se llama antes de renderizar la plantilla. Devuelve filtros como un array asociativo *nombre de filtro => objeto invocable*. [Más información |custom-filters]. ```php -$template = '{if true} {$var} {/if}'; -$latte->setLoader(new Latte\Loaders\StringLoader); -$latte->render($template); +public function getFilters(): array +{ + return [ + 'batch' => $this->batchFilter(...), + 'trim' => $this->trimFilter(...), + // ... + ]; +} ``` -Creación de un cargador personalizado .[#toc-creating-a-custom-loader] ----------------------------------------------------------------------- +getFunctions(): array .[method] +------------------------------- -Loader es una clase que implementa la interfaz [api:Latte\Loader]. +Se llama antes de renderizar la plantilla. Devuelve funciones como un array asociativo *nombre de función => objeto invocable*. [Más información |custom-functions]. +```php +public function getFunctions(): array +{ + return [ + 'clamp' => $this->clampFunction(...), + 'divisibleBy' => $this->divisibleByFunction(...), + // ... + ]; +} +``` -Etiquetas .[#toc-tags] -====================== - -Una de las características más interesantes del motor de plantillas es la posibilidad de definir nuevas construcciones del lenguaje mediante etiquetas. También es una funcionalidad más compleja y es necesario entender cómo funciona internamente Latte. -En la mayoría de los casos, sin embargo, la etiqueta no es necesaria: -- si debe generar alguna salida, utilice [function |#functions] en su lugar -- si fuera a modificar alguna entrada y devolverla, use [filtro |#filters] en su lugar -- si fuera a editar un área de texto, envuélvalo con una etiqueta [`{block}` |tags#block] y utilice un [filtro |#Contextual Filters] -- si no se supone que debe generar ninguna salida sino sólo llamar a una función, llámela con [`{do}` |tags#do] +getProviders(): array .[method] +------------------------------- -Si todavía quieres crear una etiqueta, ¡genial! Todo lo esencial se puede encontrar en [Creación de una Extensión |creating-extension]. +Se llama antes de renderizar la plantilla. Devuelve un array de proveedores, que suelen ser objetos que usan las etiquetas en tiempo de ejecución. Se accede a ellos a través de `$this->global->...`. [Más información |custom-tags#Introducción a los proveedores]. +```php +public function getProviders(): array +{ + return [ + 'myFoo' => $this->foo, + 'myBar' => $this->bar, + // ... + ]; +} +``` -Pases de compilador .{data-version:3.0}[#toc-compiler-passes] -============================================================= -Los pases de compilador son funciones que modifican ASTs o recogen información en ellos. En Latte, por ejemplo, un sandbox se implementa de esta manera: recorre todos los nodos de un AST, encuentra llamadas a funciones y métodos, y los reemplaza por llamadas controladas. +getCacheKey(Latte\Engine $engine): mixed .[method] +-------------------------------------------------- -Al igual que con las etiquetas, se trata de una funcionalidad más compleja y es necesario entender cómo funciona Latte bajo el capó. Todo lo esencial se puede encontrar en el capítulo [Creación de una Extensión |creating-extension]. +Se llama antes de renderizar la plantilla. El valor de retorno se convierte en parte de la clave, cuyo hash está contenido en el nombre del archivo de la plantilla compilada. Por lo tanto, para diferentes valores de retorno, Latte generará diferentes archivos de caché. diff --git a/latte/es/filters.texy b/latte/es/filters.texy index adfac8f280..70614e19f9 100644 --- a/latte/es/filters.texy +++ b/latte/es/filters.texy @@ -2,111 +2,113 @@ Filtros Latte ************* .[perex] -Los filtros son funciones que cambian o formatean los datos de la forma que queramos. Este es un resumen de los filtros incorporados que están disponibles. +En las plantillas, podemos usar funciones que ayudan a modificar o reformatear los datos a su forma final. Los llamamos *filtros*. .[table-latte-filters] -|## Transformación de cadenas / matrices -| `batch` | [Listado de datos lineales en una tabla |#batch] -| `breakLines` | [Inserta saltos de línea HTML antes de todas las nuevas líneas |#breakLines] -| `bytes` | [formatea el tamaño en bytes |#bytes] -| `clamp` | [sujeta el valor al rango|#clamp] -| `dataStream` | [Conversión de protocolo URI de datos|#datastream] -| `date` | [Formatea la fecha |#date] -| `explode` | [divide una cadena por el delimitador dado |#explode] -| `first` | [devuelve el primer elemento de una matriz o carácter de una cadena |#first] -| `implode` | [une una matriz a una cadena |#implode] -| `indent` | [sangrar el texto desde la izquierda con un número de tabulaciones |#indent] -| `join` | [une una matriz con una cadena|#implode] -| `last` | [devuelve el último elemento de la matriz o carácter de la cadena |#last] -| `length` | [devuelve la longitud de una cadena o matriz |#length] -| `number` | [Formatea un número |#number] -| `padLeft` | [completa la cadena a la longitud dada desde la izquierda |#padLeft] -| `padRight` | [completa la cadena a la longitud dada desde la derecha|#padRight] -| `random` | [devuelve un elemento aleatorio de una matriz o un carácter de una cadena |#random] -| `repeat` | [repite la cadena |#repeat] -| `replace` | [reemplaza todas las ocurrencias de la cadena buscada con el reemplazo |#replace] -| `replaceRE` | [reemplaza todas las apariciones según la expresión regular |#replaceRE] -| `reverse` | [invierte una cadena o matriz UTF-8 |#reverse] -| `slice` | [Extrae una porción de una matriz o cadena |#slice] -| `sort` | [ordena una matriz |#sort] -| `spaceless` | [Elimina espacios en blanco |#spaceless], similar a la etiqueta [sin espacios |tags]. -| `split` | [divide una cadena por el delimitador dado |#explode] -| `strip` | [elimina los espacios en blanco |#spaceless] -| `stripHtml` | [elimina etiquetas HTML y convierte entidades HTML en texto |#stripHtml] -| `substr` | [devuelve parte de la cadena|#substr] -| `trim` | [elimina los espacios en blanco de la cadena |#trim] -| `translate` | [traducción a otros idiomas|#translate] -| `truncate` | [acorta la longitud conservando palabras enteras |#truncate] -| `webalize` | [ajusta la cadena UTF-8 a la forma utilizada en la URL |#webalize] +|## Transformación +| `batch` | [visualización de datos lineales en una tabla |#batch] +| `breakLines` | [Añade saltos de línea HTML antes de los finales de línea |#breakLines] +| `bytes` | [formatea el tamaño en bytes |#bytes] +| `clamp` | [limita el valor a un rango dado |#clamp] +| `dataStream` | [conversión para el protocolo Data URI |#dataStream] +| `date` | [formatea la fecha y la hora |#date] +| `explode` | [divide una cadena en un array por un delimitador |#explode] +| `first` | [devuelve el primer elemento de un array o carácter de una cadena |#first] +| `group` | [agrupa los datos según diferentes criterios |#group] +| `implode` | [une un array en una cadena |#implode] +| `indent` | [indenta el texto desde la izquierda un número determinado de tabuladores |#indent] +| `join` | [une un array en una cadena |#implode] +| `last` | [devuelve el último elemento de un array o carácter de una cadena |#last] +| `length` | [devuelve la longitud de una cadena en caracteres o un array |#length] +| `localDate` | [formatea la fecha y la hora según la configuración regional |#localDate] +| `number` | [formatea un número |#number] +| `padLeft` | [rellena una cadena desde la izquierda hasta la longitud deseada |#padLeft] +| `padRight` | [rellena una cadena desde la derecha hasta la longitud deseada |#padRight] +| `random` | [devuelve un elemento aleatorio de un array o carácter de una cadena |#random] +| `repeat` | [repetición de una cadena |#repeat] +| `replace` | [reemplaza las ocurrencias de la cadena buscada |#replace] +| `replaceRE` | [reemplaza las ocurrencias según una expresión regular |#replaceRE] +| `reverse` | [invierte una cadena UTF-8 o un array |#reverse] +| `slice` | [extrae una parte de un array o cadena |#slice] +| `sort` | [ordena un array |#sort] +| `spaceless` | [elimina espacios en blanco |#spaceless], similar a la etiqueta [spaceless |tags] tag +| `split` | [divide una cadena en un array por un delimitador |#explode] +| `strip` | [elimina espacios en blanco |#spaceless] +| `stripHtml` | [elimina etiquetas HTML y convierte entidades HTML en caracteres |#stripHtml] +| `substr` | [devuelve una parte de una cadena |#substr] +| `trim` | [elimina espacios u otros caracteres iniciales y finales |#trim] +| `translate` | [traducción a otros idiomas |#translate] +| `truncate` | [acorta la longitud conservando las palabras |#truncate] +| `webalize` | [modifica una cadena UTF-8 a la forma utilizada en las URL |#webalize] .[table-latte-filters] -|## Mayúsculas -| `capitalize` | [minúsculas, la primera letra de cada palabra mayúsculas |#capitalize] -| `firstUpper` | [convierte la primera letra en mayúscula|#firstUpper] -| `lower` | [convierte una cadena en minúscula |#lower] -| `upper` | [convierte una cadena en mayúscula |#upper] +|## Mayúsculas/Minúsculas +| `capitalize` | [minúsculas, primera letra de las palabras en mayúscula |#capitalize] +| `firstUpper` | [convierte la primera letra a mayúscula |#firstUpper] +| `lower` | [convierte a minúsculas |#lower] +| `upper` | [convierte a mayúsculas |#upper] .[table-latte-filters] -|## Redondeo de números -| `ceil` | [redondea un número hacia arriba a una precisión dada|#ceil] -| `floor` | [redondea un número hacia abajo a una precisión dada|#floor] -| `round` | [redondea un número a una precisión dada|#round] +|## Redondeo +| `ceil` | [redondea un número hacia arriba a la precisión dada |#ceil] +| `floor` | [redondea un número hacia abajo a la precisión dada |#floor] +| `round` | [redondea un número a la precisión dada |#round] .[table-latte-filters] |## Escape -| `escapeUrl` | [escapa parámetro en URL |#escapeUrl] -| `noescape` | [imprime una variable sin escapar|#noescape] -| `query` | [genera una cadena de consulta en la URL|#query] +| `escapeUrl` | [escapa un parámetro en una URL |#escapeUrl] +| `noescape` | [imprime una variable sin escapar |#noescape] +| `query` | [genera una cadena de consulta en una URL |#query] -También existen filtros de escapado para HTML (`escapeHtml` y `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) e iCalendar (`escapeICal`), que Latte utiliza por sí mismo gracias al [escapado consciente del |safety-first#Context-aware escaping] contexto y no es necesario escribirlos. +Además, existen filtros de escape para HTML (`escapeHtml` y `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) e iCalendar (`escapeICal`), que Latte utiliza por sí mismo gracias al [escape sensible al contexto |safety-first#Escape sensible al contexto] y no necesita escribirlos. .[table-latte-filters] |## Seguridad -| `checkUrl` | [desinfecta la cadena para su uso dentro del atributo href |#checkUrl] -| `nocheck` | [evita la desinfección automática de URLs |#nocheck] +| `checkUrl` | [sanea una dirección URL de entradas peligrosas |#checkUrl] +| `nocheck` | [evita el saneamiento automático de la dirección URL |#nocheck] -Latte los atributos `src` y `href` [comprueba automáticamente |safety-first#link checking], por lo que casi no es necesario utilizar el filtro `checkUrl`. +Latte [comprueba automáticamente |safety-first#Comprobación de enlaces] los atributos `src` y `href`, por lo que casi nunca necesitará usar el filtro `checkUrl`. .[note] -Todos los filtros incorporados funcionan con cadenas codificadas en UTF-8. +Todos los filtros predeterminados están diseñados para cadenas en codificación UTF‑8. -Utilización .[#toc-usage] -========================= +Uso +=== -Latte permite llamar a filtros utilizando la notación del signo pipa (se permite el espacio precedente): +Los filtros se escriben después de una barra vertical (puede haber un espacio antes de ella): ```latte

                                                          {$heading|upper}

                                                          ``` -Los filtros pueden encadenarse, en cuyo caso se aplican en orden de izquierda a derecha: +Los filtros (en versiones anteriores, helpers) se pueden encadenar y se aplican en orden de izquierda a derecha: ```latte

                                                          {$heading|lower|capitalize}

                                                          ``` -Los parámetros se ponen después del nombre del filtro separados por dos puntos o coma: +Los parámetros se especifican después del nombre del filtro, separados por dos puntos o comas: ```latte

                                                          {$heading|truncate:20,''}

                                                          ``` -Los filtros se pueden aplicar sobre la expresión: +Los filtros también se pueden aplicar a una expresión: ```latte {var $name = ($title|upper) . ($subtitle|lower)} ``` -Los filtros [personalizados |extending-latte#filters] se pueden registrar de esta manera: +Los [Filtros personalizados|custom-filters] se pueden registrar de esta manera: ```php $latte = new Latte\Engine; $latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -Lo usamos en una plantilla como esta: +Luego se llama en la plantilla así: ```latte

                                                          {$text|shortify}

                                                          @@ -114,13 +116,13 @@ Lo usamos en una plantilla como esta: ``` -Filtros .[#toc-filters] -======================= +Filtros +======= -batch(int length, mixed item): array .[filter]{data-version:2.7} ----------------------------------------------------------------- -Filtro que simplifica el listado de datos lineales en forma de tabla. Devuelve un array de array con el número de elementos dado. Si se proporciona un segundo parámetro, éste se utiliza para rellenar los elementos que faltan en la última fila. +batch(int $length, mixed $item): array .[filter] +------------------------------------------------ +Filtro que simplifica la visualización de datos lineales en forma de tabla. Devuelve un array de arrays con el número especificado de elementos. Si especifica el segundo parámetro, se utiliza para rellenar los elementos faltantes en la última fila. ```latte {var $items = ['a', 'b', 'c', 'd', 'e']} @@ -152,20 +154,22 @@ Imprime: ``` +Vea también [#group] y la etiqueta [iterateWhile |tags#iterateWhile]. + breakLines .[filter] -------------------- -Inserta saltos de línea HTML antes de todas las nuevas líneas. +Añade la etiqueta HTML `
                                                          ` antes de cada carácter de nueva línea. ```latte {var $s = "Text & with \n newline"} -{$s|breakLines} {* salidas "Text & with
                                                          \n newline" *} +{$s|breakLines} {* imprime "Text & with
                                                          \n newline" *} ``` -bytes(int precision = 2) .[filter] ----------------------------------- -Formatea un tamaño en bytes a formato legible por humanos. +bytes(int $precision=2) .[filter] +--------------------------------- +Formatea el tamaño en bytes en un formato legible por humanos. Si se establece la [configuración regional |develop#Locale], se utilizan los separadores decimales y de miles correspondientes. ```latte {$size|bytes} 0 B, 1.25 GB, … @@ -173,53 +177,53 @@ Formatea un tamaño en bytes a formato legible por humanos. ``` -ceil(int precision = 0) .[filter] ---------------------------------- -Redondea un número hasta una precisión dada. +ceil(int $precision=0) .[filter] +-------------------------------- +Redondea un número hacia arriba a la precisión dada. ```latte -{=3.4|ceil} {* salidas 4 *} -{=135.22|ceil:1} {* salidas 135.3 *} -{=135.22|ceil:3} {* salidas 135.22 *} +{=3.4|ceil} {* imprime 4 *} +{=135.22|ceil:1} {* imprime 135.3 *} +{=135.22|ceil:3} {* imprime 135.22 *} ``` -Ver también [floor |#floor], [round |#round]. +Vea también [#floor], [#round]. capitalize .[filter] -------------------- -Devuelve una versión en mayúsculas del valor. Las palabras comenzarán con mayúsculas, todos los caracteres restantes en minúsculas. Requiere la extensión PHP `mbstring`. +Las palabras comenzarán con mayúsculas, todos los caracteres restantes estarán en minúsculas. Requiere la extensión PHP `mbstring`. ```latte -{='i like LATTE'|capitalize} {* salidas 'I Like Latte' *} +{='i like LATTE'|capitalize} {* imprime 'I Like Latte' *} ``` -Vea también [firstUpper |#firstUpper], [lower |#lower], [upper |#upper]. +Vea también [#firstUpper], [#lower], [#upper]. checkUrl .[filter] ------------------ -Aplica la limpieza de URL. Comprueba si la variable contiene una URL web (es decir, protocolo HTTP/HTTPS) e impide la escritura de enlaces que puedan suponer un riesgo para la seguridad. +Fuerza el saneamiento de la dirección URL. Comprueba si la variable contiene una URL web (es decir, protocolo HTTP/HTTPS) y evita la impresión de enlaces que puedan suponer un riesgo de seguridad. ```latte {var $link = 'javascript:window.close()'} -checked -unchecked +comprobado +no comprobado ``` Imprime: ```latte -checked -unchecked +comprobado +no comprobado ``` -Véase también [nocheck |#nocheck]. +Vea también [#nocheck]. -clamp(int|float min, int|float max) .[filter]{data-version:2.9} ---------------------------------------------------------------- -Devuelve el valor ajustado al rango inclusivo de mín y máx. +clamp(int|float $min, int|float $max) .[filter] +----------------------------------------------- +Limita el valor al rango inclusivo dado de min y max. ```latte {$level|clamp: 0, 255} @@ -228,17 +232,17 @@ Devuelve el valor ajustado al rango inclusivo de mín y máx. También existe como [función |functions#clamp]. -dataStream(string mimetype = detect) .[filter] ----------------------------------------------- -Convierte el contenido en un esquema URI de datos. Puede utilizarse para insertar imágenes en HTML o CSS sin necesidad de enlazar archivos externos. +dataStream(string $mimetype=detect) .[filter] +--------------------------------------------- +Convierte el contenido al esquema data URI. Permite insertar imágenes en HTML o CSS sin necesidad de enlazar archivos externos. -Tengamos una imagen en una variable `$img = Image::fromFile('obrazek.gif')`, entonces +Supongamos que tenemos una imagen en la variable `$img = Image::fromFile('imagen.gif')`, entonces ```latte - + ``` -Imprime por ejemplo +Imprime, por ejemplo: ```latte {$name} ``` -Véase también [query |#query]. +Vea también [#query]. -explode(string separator = '') .[filter]{data-version:2.10.2} -------------------------------------------------------------- -Divide una cadena por el delimitador dado y devuelve una matriz de cadenas. Alias de `split`. +explode(string $separator='') .[filter] +--------------------------------------- +Divide una cadena en un array por un delimitador. Alias para `split`. ```latte -{='one,two,three'|explode:','} {* returns ['one', 'two', 'three'] *} +{='one,two,three'|explode:','} {* devuelve ['one', 'two', 'three'] *} ``` -Si el delimitador es una cadena vacía (valor por defecto), la entrada se dividirá en caracteres individuales: +Si el delimitador es una cadena vacía (valor predeterminado), la entrada se dividirá en caracteres individuales: ```latte -{='123'|explode} {* returns ['1', '2', '3'] *} +{='123'|explode} {* devuelve ['1', '2', '3'] *} ``` -También puede utilizar el alias `split`: +También puede usar el alias `split`: ```latte -{='1,2,3'|split:','} {* returns ['1', '2', '3'] *} +{='1,2,3'|split:','} {* devuelve ['1', '2', '3'] *} ``` -Ver también [implode |#implode]. +Vea también [#implode]. -first .[filter]{data-version:2.10.2} ------------------------------------- +first .[filter] +--------------- Devuelve el primer elemento de un array o carácter de una cadena: ```latte -{=[1, 2, 3, 4]|first} {* salidas 1 *} -{='abcd'|first} {* salidas 'a' *} +{=[1, 2, 3, 4]|first} {* imprime 1 *} +{='abcd'|first} {* imprime 'a' *} ``` -Ver también [last |#last], [random |#random]. +Vea también [#last], [#random]. -floor(int precision = 0) .[filter] ----------------------------------- -Redondea un número a una precisión dada. +floor(int $precision=0) .[filter] +--------------------------------- +Redondea un número hacia abajo a la precisión dada. ```latte -{=3.5|floor} {* salidas 3 *} -{=135.79|floor:1} {* salidas 135.7 *} -{=135.79|floor:3} {* salidas 135.79 *} +{=3.5|floor} {* imprime 3 *} +{=135.79|floor:1} {* imprime 135.7 *} +{=135.79|floor:3} {* imprime 135.79 *} ``` -Ver también [ceil |#ceil], [round |#round]. +Vea también [#ceil], [#round]. firstUpper .[filter] -------------------- -Convierte la primera letra de un valor a mayúsculas. Requiere la extensión PHP `mbstring`. +Convierte la primera letra a mayúscula. Requiere la extensión PHP `mbstring`. ```latte -{='the latte'|firstUpper} {* salidas 'The latte' *} +{='the latte'|firstUpper} {* imprime 'The latte' *} ``` -Vea también [capitalize |#capitalize], [lower |#lower], [upper |#upper]. +Vea también [#capitalize], [#lower], [#upper]. -implode(string glue = '') .[filter] ------------------------------------ -Devuelve una cadena que es la concatenación de las cadenas de la matriz. Alias de `join`. +group(string|int|\Closure $by): array .[filter]{data-version:3.0.16} +-------------------------------------------------------------------- +El filtro agrupa los datos según diferentes criterios. + +En este ejemplo, las filas de la tabla se agrupan por la columna `categoryId`. La salida es un array de arrays donde la clave es el valor de la columna `categoryId`. [Lea el tutorial detallado|cookbook/grouping]. ```latte -{=[1, 2, 3]|implode} {* salidas '123' *} -{=[1, 2, 3]|implode:'|'} {* salidas '1|2|3' *} +{foreach ($items|group: categoryId) as $categoryId => $categoryItems} +
                                                            + {foreach $categoryItems as $item} +
                                                          • {$item->name}
                                                          • + {/foreach} +
                                                          +{/foreach} ``` -También puede utilizar un alias `join`: .{data-version:2.10.2} +Vea también [#batch], la función [group |functions#group] y la etiqueta [iterateWhile |tags#iterateWhile]. + + +implode(string $glue='') .[filter] +---------------------------------- +Devuelve una cadena que es la concatenación de los elementos de una secuencia. Alias para `join`. ```latte -{=[1, 2, 3]|join} {* salidas '123' *} +{=[1, 2, 3]|implode} {* imprime '123' *} +{=[1, 2, 3]|implode:'|'} {* imprime '1|2|3' *} ``` +También puede usar el alias `join`: + +```latte +{=[1, 2, 3]|join} {* imprime '123' *} +``` -indent(int level = 1, string char = "\t") .[filter] ---------------------------------------------------- -Indenta un texto desde la izquierda un número determinado de tabulaciones u otros caracteres que especifiquemos en el segundo argumento opcional. Las líneas en blanco no se sangrarán. + +indent(int $level=1, string $char="\t") .[filter] +------------------------------------------------- +Indenta el texto desde la izquierda un número determinado de tabuladores u otros caracteres, que podemos especificar en el segundo argumento. Las líneas vacías no se indentan. ```latte
                                                          @@ -367,26 +391,26 @@ Imprime: ``` -last .[filter]{data-version:2.10.2} ------------------------------------ -Devuelve el último elemento del array o carácter de la cadena: +last .[filter] +-------------- +Devuelve el último elemento de un array o carácter de una cadena: ```latte -{=[1, 2, 3, 4]|last} {* salidas 4 *} -{='abcd'|last} {* salidas 'd' *} +{=[1, 2, 3, 4]|last} {* imprime 4 *} +{='abcd'|last} {* imprime 'd' *} ``` -Ver también [first |#first], [random |#random]. +Vea también [#first], [#random]. length .[filter] ---------------- -Devuelve la longitud de una cadena o matriz. +Devuelve la longitud de una cadena o un array. -- para cadenas, devolverá la longitud en caracteres UTF-8 -- para matrices, devolverá el recuento de elementos -- para objetos que implementen la interfaz Countable, utilizará el valor de retorno de count() -- para objetos que implementen la interfaz IteratorAggregate, utilizará el valor de retorno de iterator_count() +- para cadenas, devuelve la longitud en caracteres UTF‑8 +- para arrays, devuelve el número de elementos +- para objetos que implementan la interfaz Countable, utiliza el valor de retorno del método count() +- para objetos que implementan la interfaz IteratorAggregate, utiliza el valor de retorno de la función iterator_count() ```latte @@ -396,94 +420,204 @@ Devuelve la longitud de una cadena o matriz. ``` +localDate(?string $format=null, ?string $date=null, ?string $time=null) .[filter] +--------------------------------------------------------------------------------- +Formatea la fecha y la hora según la [configuración regional |develop#Locale], lo que garantiza una visualización consistente y localizada de los datos de tiempo en diferentes idiomas y regiones. El filtro acepta la fecha como marca de tiempo UNIX, cadena u objeto de tipo `DateTimeInterface`. + +```latte +{$date|localDate} {* 15 de abril de 2024 *} +{$date|localDate: format: yM} {* 4/2024 *} +{$date|localDate: date: medium} {* 15/4/2024 *} +``` + +Si utiliza el filtro sin parámetros, la fecha se mostrará en el nivel `long`, vea más abajo. + +**a) Uso del formato** + +El parámetro `format` describe qué componentes de tiempo se deben mostrar. Utiliza códigos de letras para ellos, cuyo número de repeticiones afecta el ancho de la salida: + +| año | `y` / `yy` / `yyyy` | `2024` / `24` / `2024` +| mes | `M` / `MM` / `MMM` / `MMMM` | `8` / `08` / `ago` / `agosto` +| día | `d` / `dd` / `E` / `EEEE` | `1` / `01` / `dom` / `domingo` +| hora | `j` / `H` / `h` | preferido / 24 horas / 12 horas +| minuto | `m` / `mm` | `5` / `05` (2 dígitos en combinación con segundos) +| segundo | `s` / `ss` | `8` / `08` (2 dígitos en combinación con minutos) + +El orden de los códigos en el formato no importa, ya que el orden de los componentes se mostrará según las convenciones de la configuración regional. Por lo tanto, el formato es independiente de ella. Por ejemplo, el formato `yyyyMMMMd` en el entorno `en_US` mostrará `April 15, 2024`, mientras que en el entorno `es_ES` mostrará `15 de abril de 2024`: + +| locale: | es_ES | en_US +|--- +| `format: 'dMy'` | 10/8/2024 | 8/10/2024 +| `format: 'yM'` | 8/2024 | 8/2024 +| `format: 'yyyyMMMM'` | agosto de 2024 | August 2024 +| `format: 'MMMM'` | agosto | August +| `format: 'jm'` | 17:22 | 5:22 PM +| `format: 'Hm'` | 17:22 | 17:22 +| `format: 'hm'` | 5:22 p. m. | 5:22 PM + + +**b) Uso de estilos preestablecidos** + +Los parámetros `date` y `time` determinan con qué detalle se deben mostrar la fecha y la hora. Puede elegir entre varios niveles: `full`, `long`, `medium`, `short`. Puede mostrar solo la fecha, solo la hora o ambas: + +| locale: | es_ES | en_US +|--- +| `date: short` | 23/1/78 | 1/23/78 +| `date: medium` | 23 ene 1978 | Jan 23, 1978 +| `date: long` | 23 de enero de 1978 | January 23, 1978 +| `date: full` | lunes, 23 de enero de 1978 | Monday, January 23, 1978 +| `time: short` | 8:30 | 8:30 AM +| `time: medium` | 8:30:59 | 8:30:59 AM +| `time: long` | 8:30:59 CET | 8:30:59 AM GMT+1 +| `date: short, time: short` | 23/1/78 8:30 | 1/23/78, 8:30 AM +| `date: medium, time: short` | 23 ene 1978 8:30 | Jan 23, 1978, 8:30 AM +| `date: long, time: short` | 23 de enero de 1978, 8:30 | January 23, 1978 at 8:30 AM + +Para la fecha, también puede usar el prefijo `relative-` (por ejemplo, `relative-short`), que para fechas cercanas a la actual mostrará `ayer`, `hoy` o `mañana`, de lo contrario se mostrará de la manera estándar. + +```latte +{$date|localDate: date: relative-short} {* ayer *} +``` + +Vea también [#date]. + + lower .[filter] --------------- -Convierte un valor a minúsculas. Requiere la extensión PHP `mbstring`. +Convierte una cadena a minúsculas. Requiere la extensión PHP `mbstring`. ```latte -{='LATTE'|lower} {* salidas 'latte' *} +{='LATTE'|lower} {* imprime 'latte' *} ``` -Vea también [capitalize |#capitalize], [firstUpper |#firstUpper], [upper |#upper]. +Vea también [#capitalize], [#firstUpper], [#upper]. nocheck .[filter] ----------------- -Evita la desinfección automática de URL. Latte [comprueba automáticamente |safety-first#Link checking] si la variable contiene una URL web (es decir, protocolo HTTP/HTTPS) e impide la escritura de enlaces que puedan suponer un riesgo para la seguridad. +Evita el saneamiento automático de la dirección URL. Latte [comprueba automáticamente |safety-first#Comprobación de enlaces] si la variable contiene una URL web (es decir, protocolo HTTP/HTTPS) y evita la impresión de enlaces que puedan suponer un riesgo de seguridad. -Si el enlace utiliza un esquema diferente, como `javascript:` o `data:`, y está seguro de su contenido, puede desactivar la comprobación a través de `|nocheck`. +Si el enlace utiliza otro esquema, como `javascript:` o `data:`, y está seguro de su contenido, puede desactivar la comprobación con `|nocheck`. ```latte {var $link = 'javascript:window.close()'} -checked -unchecked +comprobado +no comprobado ``` Imprime: ```latte -checked -unchecked +comprobado +no comprobado ``` -Véase también [checkUrl |#checkUrl]. +Vea también [#checkUrl]. noescape .[filter] ------------------ -Desactiva el escape automático. +Deshabilita el escape automático. ```latte {var $trustedHtmlString = 'hello'} -Escaped: {$trustedHtmlString} -Unescaped: {$trustedHtmlString|noescape} +Escapado: {$trustedHtmlString} +No escapado: {$trustedHtmlString|noescape} ``` Imprime: ```latte -Escaped: <b>hello</b> -Unescaped: hello +Escapado: <b>hello</b> +No escapado: hello ``` .[warning] -¡El mal uso del filtro `noescape` puede llevar a una vulnerabilidad XSS! Nunca lo utilices a menos que estés **absolutamente seguro** de lo que estás haciendo y de que la cadena que estás imprimiendo proviene de una fuente de confianza. +¡El uso incorrecto del filtro `noescape` puede llevar a una vulnerabilidad XSS! Nunca lo use si no está **completamente seguro** de lo que está haciendo y de que la cadena que se imprime proviene de una fuente confiable. -number(int decimals = 0, string decPoint = '.', string thousandsSep = ',') .[filter] ------------------------------------------------------------------------------------- -Formatea un número con un número dado de decimales. También puede especificar un carácter del punto decimal y del separador de miles. +number(int $decimals=0, string $decPoint='.', string $thousandsSep=',') .[filter] +--------------------------------------------------------------------------------- +Formatea un número a un número específico de lugares decimales. Si se establece la [configuración regional |develop#Locale], se utilizan los separadores decimales y de miles correspondientes. ```latte -{1234.20 |number} 1,234 -{1234.20 |number:1} 1,234.2 -{1234.20 |number:2} 1,234.20 -{1234.20 |number:2, ',', ' '} 1 234,20 +{1234.20|number} 1,234 +{1234.20|number:1} 1,234.2 +{1234.20|number:2} 1,234.20 +{1234.20|number:2, ',', ' '} 1 234,20 ``` -padLeft(int length, string pad = ' ') .[filter] +number(string $format) .[filter] +-------------------------------- +El parámetro `format` permite definir la apariencia de los números exactamente según sus necesidades. Para ello, es necesario tener establecida la [configuración regional |develop#Locale]. El formato consta de varios caracteres especiales, cuya descripción completa encontrará en la documentación de "DecimalFormat":https://unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns: + +- `0` dígito obligatorio, siempre se muestra, aunque sea cero +- `#` dígito opcional, se muestra solo si realmente hay un número en esa posición +- `@` dígito significativo, ayuda a mostrar el número con un cierto número de dígitos significativos +- `.` indica dónde debe estar la coma decimal (o el punto, según el país) +- `,` sirve para separar grupos de dígitos, generalmente miles +- `%` multiplica el número por 100 y añade el signo de porcentaje + +Veamos algunos ejemplos. En el primer ejemplo, se requieren dos lugares decimales, en el segundo son opcionales. El tercer ejemplo muestra el relleno con ceros a la izquierda y a la derecha, el cuarto muestra solo los dígitos existentes: + +```latte +{1234.5|number: '#,##0.00'} {* 1,234.50 *} +{1234.5|number: '#,##0.##'} {* 1,234.5 *} +{1.23 |number: '000.000'} {* 001.230 *} +{1.2 |number: '##.##'} {* 1.2 *} +``` + +Los dígitos significativos determinan cuántos dígitos se deben mostrar independientemente de la coma decimal, redondeando: + +```latte +{1234|number: '@@'} {* 1200 *} +{1234|number: '@@@'} {* 1230 *} +{1234|number: '@@@#'} {* 1234 *} +{1.2345|number: '@@@'} {* 1.23 *} +{0.00123|number: '@@'} {* 0.0012 *} +``` + +Una forma fácil de mostrar un número como porcentaje. El número se multiplica por 100 y se añade el signo `%`: + +```latte +{0.1234|number: '#.##%'} {* 12.34% *} +``` + +Podemos definir un formato diferente para números positivos y negativos, separados por el carácter `;`. De esta manera, por ejemplo, se puede configurar que los números positivos se muestren con el signo `+`: + +```latte +{42|number: '#.##;(#.##)'} {* 42 *} +{-42|number: '#.##;(#.##)'} {* (42) *} +{42|number: '+#.##;-#.##'} {* +42 *} +{-42|number: '+#.##;-#.##'} {* -42 *} +``` + +Recuerde que la apariencia real de los números puede variar según la configuración del país. Por ejemplo, en algunos países se utiliza una coma en lugar de un punto como separador decimal. Este filtro lo tiene en cuenta automáticamente y no tiene que preocuparse por nada. + + +padLeft(int $length, string $pad=' ') .[filter] ----------------------------------------------- -Rellena una cadena de una longitud determinada con otra cadena de la izquierda. +Rellena una cadena hasta una longitud determinada con otra cadena desde la izquierda. ```latte -{='hello'|padLeft: 10, '123'} {* outputs '12312hello' *} +{='hello'|padLeft: 10, '123'} {* imprime '12312hello' *} ``` -padRight(int length, string pad = ' ') .[filter] +padRight(int $length, string $pad=' ') .[filter] ------------------------------------------------ -Rellena una cadena de cierta longitud con otra cadena de la derecha. +Rellena una cadena hasta una longitud determinada con otra cadena desde la derecha. ```latte -{='hello'|padRight: 10, '123'} {* outputs 'hello12312' *} +{='hello'|padRight: 10, '123'} {* imprime 'hello12312' *} ``` -query .[filter]{data-version:2.10} ------------------------------------ -Genera dinámicamente una cadena de consulta en la URL: +query .[filter] +--------------- +Genera dinámicamente una cadena de consulta en una URL: ```latte click @@ -497,103 +631,103 @@ Imprime: search ``` -Las teclas con el valor `null` se omiten. +Las claves con valor `null` se omiten. -Véase también [escapeUrl |#escapeUrl]. +Vea también [#escapeUrl]. -random .[filter]{data-version:2.10.2} -------------------------------------- -Devuelve un elemento aleatorio de una matriz o un carácter de una cadena: +random .[filter] +---------------- +Devuelve un elemento aleatorio de un array o carácter de una cadena: ```latte -{=[1, 2, 3, 4]|random} {* example output: 3 *} -{='abcd'|random} {* example output: 'b' *} +{=[1, 2, 3, 4]|random} {* imprime por ejemplo: 3 *} +{='abcd'|random} {* imprime por ejemplo: 'b' *} ``` -Véase también [first |#first], [last |#last]. +Vea también [#first], [#last]. -repeat(int count) .[filter] ---------------------------- -Repite la cadena x veces. +repeat(int $count) .[filter] +---------------------------- +Repite una cadena x veces. ```latte -{='hello'|repeat: 3} {* salidas 'hellohellohello' *} +{='hello'|repeat: 3} {* imprime 'hellohellohello' *} ``` -replace(string|array search, string replace = '') .[filter] +replace(string|array $search, string $replace='') .[filter] ----------------------------------------------------------- -Sustituye todas las apariciones de la cadena de búsqueda por la cadena de sustitución. +Reemplaza todas las ocurrencias de la cadena de búsqueda con la cadena de reemplazo. ```latte -{='hello world'|replace: 'world', 'friend'} {* salidas 'hello friend' *} +{='hello world'|replace: 'world', 'friend'} {* imprime 'hello friend' *} ``` -Se pueden realizar varias sustituciones a la vez: .{data-version:2.10.2} +También se pueden realizar múltiples reemplazos a la vez: ```latte -{='hello world'|replace: [h => l, l => h]} {* salidas 'lehho worhd' *} +{='hello world'|replace: [h => l, l => h]} {* imprime 'lehho worhd' *} ``` -replaceRE(string pattern, string replace = '') .[filter] +replaceRE(string $pattern, string $replace='') .[filter] -------------------------------------------------------- -Reemplaza todas las ocurrencias según la expresión regular. +Realiza una búsqueda de expresiones regulares con reemplazo. ```latte -{='hello world'|replaceRE: '/l.*/', 'l'} {* salidas 'hel' *} +{='hello world'|replaceRE: '/l.*/', 'l'} {* imprime 'hel' *} ``` reverse .[filter] ----------------- -Invierte la cadena o matriz dada. +Invierte la cadena o el array dado. ```latte {var $s = 'Nette'} -{$s|reverse} {* salidas 'etteN' *} +{$s|reverse} {* imprime 'etteN' *} {var $a = ['N', 'e', 't', 't', 'e']} -{$a|reverse} {* returns ['e', 't', 't', 'e', 'N'] *} +{$a|reverse} {* devuelve ['e', 't', 't', 'e', 'N'] *} ``` -round(int precision = 0) .[filter] ----------------------------------- -Redondea un número a una precisión dada. +round(int $precision=0) .[filter] +--------------------------------- +Redondea un número a la precisión dada. ```latte -{=3.4|round} {* salidas 3 *} -{=3.5|round} {* salidas 4 *} -{=135.79|round:1} {* salidas 135.8 *} -{=135.79|round:3} {* salidas 135.79 *} +{=3.4|round} {* imprime 3 *} +{=3.5|round} {* imprime 4 *} +{=135.79|round:1} {* imprime 135.8 *} +{=135.79|round:3} {* imprime 135.79 *} ``` -Ver también [ceil |#ceil], [floor |#floor]. +Vea también [#ceil], [#floor]. -slice(int start, int length = null, bool preserveKeys = false) .[filter]{data-version:2.10.2} ---------------------------------------------------------------------------------------------- -Extrae una porción de una matriz o una cadena. +slice(int $start, ?int $length=null, bool $preserveKeys=false) .[filter] +------------------------------------------------------------------------ +Extrae una parte de un array o cadena. ```latte -{='hello'|slice: 1, 2} {* salidas 'el' *} -{=['a', 'b', 'c']|slice: 1, 2} {* salidas ['b', 'c'] *} +{='hello'|slice: 1, 2} {* imprime 'el' *} +{=['a', 'b', 'c']|slice: 1, 2} {* imprime ['b', 'c'] *} ``` -El filtro de rebanada funciona como la función PHP `array_slice` para matrices y `mb_substr` para cadenas con un fallback a `iconv_substr` en modo UTF-8. +El filtro funciona como la función PHP `array_slice` para arrays o `mb_substr` para cadenas con fallback a la función `iconv_substr` en modo UTF‑8. -Si start es no negativo, la secuencia comenzará en ese inicio de la variable. Si start es negativo, la secuencia comenzará a esa distancia del final de la variable. +Si start es positivo, la secuencia comenzará desplazada este número desde el inicio del array/cadena. Si es negativo, la secuencia comenzará desplazada tanto desde el final. -Si la longitud es positiva, la secuencia tendrá hasta ese número de elementos. Si la variable es más corta que la longitud, sólo estarán presentes los elementos disponibles de la variable. Si la longitud es negativa, la secuencia se detendrá a esa cantidad de elementos del final de la variable. Si se omite, entonces la secuencia tendrá todo desde offset hasta el final de la variable. +Si se especifica el parámetro length y es positivo, la secuencia contendrá tantos elementos. Si se pasa un parámetro length negativo a esta función, la secuencia contendrá todos los elementos del array original, comenzando en la posición start y terminando en la posición menor a length elementos desde el final del array. Si no especifica este parámetro, la secuencia contendrá todos los elementos del array original, comenzando en la posición start. -Filter reordenará y restablecerá las claves de la matriz de enteros por defecto. Este comportamiento puede cambiarse estableciendo preserveKeys a true. Las claves de cadena siempre se conservan, independientemente de este parámetro. +Por defecto, el filtro cambia el orden y restablece las claves enteras del array. Este comportamiento se puede cambiar estableciendo preserveKeys a true. Las claves de cadena siempre se conservan, independientemente de este parámetro. -sort .[filter]{data-version:2.9} ---------------------------------- -Filtro que ordena un array y mantiene la asociación de índices. +sort(?Closure $comparison, string|int|\Closure|null $by=null, string|int|\Closure|bool $byKey=false) .[filter] +-------------------------------------------------------------------------------------------------------------- +El filtro ordena los elementos de un array o iterador y conserva sus claves asociativas. Si se establece la [configuración regional |develop#Locale], la ordenación se rige por sus reglas, a menos que se especifique una función de comparación personalizada. ```latte {foreach ($names|sort) as $name} @@ -601,7 +735,7 @@ Filtro que ordena un array y mantiene la asociación de índices. {/foreach} ``` -Matriz ordenada en orden inverso. +Array ordenado en orden inverso: ```latte {foreach ($names|sort|reverse) as $name} @@ -609,16 +743,42 @@ Matriz ordenada en orden inverso. {/foreach} ``` -Puede pasar su propia función de comparación como parámetro: .{data-version:2.10.2} +Puede especificar una función de comparación personalizada para la ordenación (el ejemplo muestra cómo invertir la ordenación de mayor a menor): ```latte -{var $sorted = ($names|sort: fn($a, $b) => $b <=> $a)} +{var $reverted = ($names|sort: fn($a, $b) => $b <=> $a)} ``` +El filtro `|sort` también permite ordenar elementos por claves: -spaceless .[filter]{data-version:2.10.2} ------------------------------------------ -Elimina los espacios en blanco innecesarios de la salida. También puede utilizar el alias `strip`. +```latte +{foreach ($names|sort: byKey: true) as $name} + ... +{/foreach} +``` + +Si necesita ordenar una tabla por una columna específica, puede usar el parámetro `by`. El valor `'name'` en el ejemplo indica que se ordenará por `$item->name` o `$item['name']`, dependiendo de si `$item` es un array o un objeto: + +```latte +{foreach ($items|sort: by: 'name') as $item} + {$item->name} +{/foreach} +``` + +También puede definir una función de callback que determine el valor por el cual ordenar: + +```latte +{foreach ($items|sort: by: fn($item) => $item->category->name) as $item} + {$item->name} +{/foreach} +``` + +De la misma manera, también se puede utilizar el parámetro `byKey`. + + +spaceless .[filter] +------------------- +Elimina los espacios en blanco innecesarios de la salida. También puede usar el alias `strip`. ```latte {block |spaceless} @@ -637,47 +797,47 @@ Imprime: stripHtml .[filter] ------------------- -Convierte HTML en texto sin formato. Es decir, elimina las etiquetas HTML y convierte las entidades HTML en texto. +Convierte HTML a texto plano. Es decir, elimina las etiquetas HTML y convierte las entidades HTML en texto. ```latte -{='

                                                          one < two

                                                          '|stripHtml} {* salidas 'one < two' *} +{='

                                                          one < two

                                                          '|stripHtml} {* imprime 'one < two' *} ``` -El texto plano resultante puede contener naturalmente caracteres que representen etiquetas HTML, por ejemplo `'<p>'|stripHtml` se convierte en `

                                                          `. Nunca envíe el texto resultante con `|noescape`, ya que esto puede dar lugar a una vulnerabilidad de seguridad. +El texto plano resultante puede contener naturalmente caracteres que representan etiquetas HTML, por ejemplo, `'<p>'|stripHtml` se convierte en `

                                                          `. En ningún caso imprima el texto resultante con `|noescape`, ya que esto puede llevar a un agujero de seguridad. -substr(int offset, int length = null) .[filter] ------------------------------------------------ -Extrae una porción de una cadena. Este filtro ha sido sustituido por un filtro de [trozos |#slice]. +substr(int $offset, ?int $length=null) .[filter] +------------------------------------------------ +Extrae una parte de una cadena. Este filtro ha sido reemplazado por el filtro [#slice]. ```latte {$string|substr: 1, 2} ``` -translate(string message, ...args) .[filter]{data-version:3.0} --------------------------------------------------------------- -Traduce expresiones a otros idiomas. Para que el filtro esté disponible, es necesario [configurar el traductor |develop#TranslatorExtension]. También puede utilizar las [etiquetas para la traducción |tags#Translation]. +translate(...$args) .[filter] +----------------------------- +Traduce expresiones a otros idiomas. Para que el filtro esté disponible, es necesario [configurar el traductor |develop#TranslatorExtension]. También puede usar [etiquetas para traducción |tags#Traducciones]. ```latte -{='Baskter'|translate} +{='Cesta'|translate} {$item|translate} ``` -trim(string charlist = " \t\n\r\0\x0B\u{A0}") .[filter] -------------------------------------------------------- -Elimina los caracteres iniciales y finales, por defecto los espacios en blanco. +trim(string $charlist=" \t\n\r\0\x0B\u{A0}") .[filter] +------------------------------------------------------ +Elimina los caracteres en blanco (u otros caracteres) del principio y el final de una cadena. ```latte -{=' I like Latte. '|trim} {* salidas 'I like Latte.' *} -{=' I like Latte.'|trim: '.'} {* salidas ' I like Latte' *} +{=' I like Latte. '|trim} {* imprime 'I like Latte.' *} +{=' I like Latte.'|trim: '.'} {* imprime ' I like Latte' *} ``` -truncate(int length, string append = '…') .[filter] +truncate(int $length, string $append='…') .[filter] --------------------------------------------------- -Acorta una cadena a la longitud máxima dada, pero intenta conservar las palabras enteras. Si la cadena está truncada, añade elipsis al final (esto puede cambiarse con el segundo parámetro). +Recorta una cadena a la longitud máxima especificada, intentando conservar palabras completas. Si la cadena se acorta, añade puntos suspensivos al final (se puede cambiar con el segundo parámetro). ```latte {var $title = 'Hello, how are you?'} @@ -689,25 +849,25 @@ Acorta una cadena a la longitud máxima dada, pero intenta conservar las palabra upper .[filter] --------------- -Convierte un valor a mayúsculas. Requiere la extensión PHP `mbstring`. +Convierte una cadena a mayúsculas. Requiere la extensión PHP `mbstring`. ```latte -{='latte'|upper} {* salidas 'LATTE' *} +{='latte'|upper} {* imprime 'LATTE' *} ``` -Vea también [capitalize |#capitalize], [firstUpper |#firstUpper], [lower |#lower]. +Vea también [#capitalize], [#firstUpper], [#lower]. webalize .[filter] ------------------ -Convierte a ASCII. +Modifica una cadena UTF‑8 a la forma utilizada en las URL. -Convierte los espacios en guiones. Elimina caracteres que no sean alfanuméricos, guiones bajos o guiones. Convierte a minúsculas. También elimina los espacios en blanco iniciales y finales. +Se convierte a ASCII. Convierte los espacios en guiones. Elimina los caracteres que no son alfanuméricos, guiones bajos o guiones. Convierte a minúsculas. También elimina los espacios iniciales y finales. ```latte -{var $s = 'Our 10. product'} -{$s|webalize} {* salidas 'our-10-product' *} +{var $s = 'Nuestro 10º producto'} +{$s|webalize} {* imprime 'nuestro-10-producto' *} ``` .[caution] -Requiere el paquete [nette/utils |utils:]. +Requiere la librería [nette/utils|utils:]. diff --git a/latte/es/functions.texy b/latte/es/functions.texy index 147d5f96a1..f127a8d060 100644 --- a/latte/es/functions.texy +++ b/latte/es/functions.texy @@ -2,22 +2,24 @@ Funciones Latte *************** .[perex] -Además de las funciones comunes de PHP, también puede utilizarlas en plantillas. +En las plantillas, además de las funciones PHP habituales, también podemos usar estas otras. .[table-latte-filters] -| `clamp` | [sujeta el valor al rango |#clamp] +| `clamp` | [limita el valor a un rango dado |#clamp] | `divisibleBy`| [comprueba si una variable es divisible por un número |#divisibleBy] -| `even` | [comprueba si el número dado es par |#even] -| `first` | [devuelve el primer elemento de una matriz o un carácter de una cadena |#first] -| `last` | [devuelve el último elemento de la matriz o carácter de la cadena|#last] -| `odd` | [comprueba si el número dado es impar|#odd] -| `slice` | [extrae un trozo de una matriz o de una cadena |#slice] +| `even` | [comprueba si un número dado es par |#even] +| `first` | [devuelve el primer elemento de un array o carácter de una cadena |#first] +| `group` | [agrupa los datos según diferentes criterios |#group] +| `hasBlock` | [detecta la existencia de un bloque |#hasBlock] +| `last` | [devuelve el último elemento de un array o carácter de una cadena |#last] +| `odd` | [comprueba si un número dado es impar |#odd] +| `slice` | [extrae una parte de un array o cadena |#slice] -Uso .[#toc-usage] -================= +Uso +=== -Las funciones se utilizan de la misma manera que las funciones comunes de PHP y se pueden utilizar en todas las expresiones: +Las funciones se utilizan igual que las funciones PHP habituales y se pueden usar en todas las expresiones: ```latte

                                                          {clamp($num, 1, 100)}

                                                          @@ -25,14 +27,14 @@ Las funciones se utilizan de la misma manera que las funciones comunes de PHP y {if odd($num)} ... {/if} ``` -Las funciones [personalizadas |extending-latte#functions] se pueden registrar de esta manera: +Las [Funciones personalizadas|custom-functions] se pueden registrar de esta manera: ```php $latte = new Latte\Engine; $latte->addFunction('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -La usamos en una plantilla como esta: +Luego se llama en la plantilla así: ```latte

                                                          {shortify($text)}

                                                          @@ -40,23 +42,23 @@ La usamos en una plantilla como esta: ``` -Funciones .[#toc-functions] -=========================== +Funciones +========= -clamp(int|float $value, int|float $min, int|float $max): int|float .[method]{data-version:2.9} ----------------------------------------------------------------------------------------------- -Devuelve el valor ajustado al rango inclusivo de mín y máx. +clamp(int|float $value, int|float $min, int|float $max): int|float .[method] +---------------------------------------------------------------------------- +Limita el valor al rango inclusivo dado de min y max. ```latte {=clamp($level, 0, 255)} ``` -Véase también [abrazadera de filtro |filters#clamp]: +Vea también [filtro clamp |filters#clamp]. -divisibleBy(int $value, int $by): bool .[method]{data-version:2.10.2} ---------------------------------------------------------------------- +divisibleBy(int $value, int $by): bool .[method] +------------------------------------------------ Comprueba si una variable es divisible por un número. ```latte @@ -64,61 +66,91 @@ Comprueba si una variable es divisible por un número. ``` -even(int $value): bool .[method]{data-version:2.10.2} ------------------------------------------------------ -Comprueba si el número dado es par. +even(int $value): bool .[method] +-------------------------------- +Comprueba si un número dado es par. ```latte {if even($num)} ... {/if} ``` -first(string|array $value): mixed .[method]{data-version:2.10.2} ----------------------------------------------------------------- +first(string|iterable $value): mixed .[method] +---------------------------------------------- Devuelve el primer elemento de un array o carácter de una cadena: ```latte -{=first([1, 2, 3, 4])} {* salidas 1 *} -{=first('abcd')} {* salidas 'a' *} +{=first([1, 2, 3, 4])} {* imprime 1 *} +{=first('abcd')} {* imprime 'a' *} ``` -Véase también [last |#last], [filter first |filters#first]. +Vea también [#last], [filtro first |filters#first]. -last(string|array $value): mixed .[method]{data-version:2.10.2} ---------------------------------------------------------------- -Devuelve el último elemento del array o carácter de la cadena: +group(iterable $data, string|int|\Closure $by): array .[method]{data-version:3.0.16} +------------------------------------------------------------------------------------ +La función agrupa los datos según diferentes criterios. + +En este ejemplo, las filas de la tabla se agrupan por la columna `categoryId`. La salida es un array de arrays donde la clave es el valor de la columna `categoryId`. [Lea el tutorial detallado|cookbook/grouping]. + +```latte +{foreach group($items, categoryId) as $categoryId => $categoryItems} +
                                                            + {foreach $categoryItems as $item} +
                                                          • {$item->name}
                                                          • + {/foreach} +
                                                          +{/foreach} +``` + +Vea también el filtro [group |filters#group]. + + +hasBlock(string $name): bool .[method]{data-version:3.0.10} +----------------------------------------------------------- +Detecta si existe un bloque con el nombre especificado: + +```latte +{if hasBlock(header)} ... {/if} +``` + +Vea también [comprobación de la existencia de bloques |template-inheritance#Comprobación de existencia de bloques ifset]. + + +last(string|array $value): mixed .[method] +------------------------------------------ +Devuelve el último elemento de un array o carácter de una cadena: ```latte -{=last([1, 2, 3, 4])} {* salidas 4 *} -{=last('abcd')} {* salidas 'd' *} +{=last([1, 2, 3, 4])} {* imprime 4 *} +{=last('abcd')} {* imprime 'd' *} ``` -Véase también [first |#first], [filter last |filters#last]. +Vea también [#first], [filtro last |filters#last]. -odd(int $value): bool .[method]{data-version:2.10.2} ----------------------------------------------------- -Comprueba si el número dado es impar. +odd(int $value): bool .[method] +------------------------------- +Comprueba si un número dado es impar. ```latte {if odd($num)} ... {/if} ``` -slice(string|array $value, int $start, int $length=null, bool $preserveKeys=false): string|array .[method]{data-version:2.10.2} -------------------------------------------------------------------------------------------------------------------------------- -Extrae una porción de un array o de una cadena. +slice(string|array $value, int $start, ?int $length=null, bool $preserveKeys=false): string|array .[method] +----------------------------------------------------------------------------------------------------------- +Extrae una parte de un array o cadena. ```latte -{=slice('hello', 1, 2)} {* salidas 'el' *} -{=slice(['a', 'b', 'c'], 1, 2)} {* salidas ['b', 'c'] *} +{=slice('hello', 1, 2)} {* imprime 'el' *} +{=slice(['a', 'b', 'c'], 1, 2)} {* imprime ['b', 'c'] *} ``` -El filtro de rebanada funciona como la función PHP `array_slice` para matrices y `mb_substr` para cadenas con un fallback a `iconv_substr` en modo UTF-8. +La función funciona como la función PHP `array_slice` para arrays o `mb_substr` para cadenas con fallback a la función `iconv_substr` en modo UTF‑8. -Si start es no negativo, la secuencia comenzará en ese inicio de la variable. Si start es negativo, la secuencia comenzará a esa distancia del final de la variable. +Si start es positivo, la secuencia comenzará desplazada este número desde el inicio del array/cadena. Si es negativo, la secuencia comenzará desplazada tanto desde el final. -Si la longitud es positiva, la secuencia tendrá hasta ese número de elementos. Si la variable es más corta que la longitud, sólo estarán presentes los elementos disponibles de la variable. Si la longitud es negativa, la secuencia se detendrá a esa cantidad de elementos del final de la variable. Si se omite, entonces la secuencia tendrá todo desde offset hasta el final de la variable. +Si se especifica el parámetro length y es positivo, la secuencia contendrá tantos elementos. Si se pasa un parámetro length negativo a esta función, la secuencia contendrá todos los elementos del array original, comenzando en la posición start y terminando en la posición menor a length elementos desde el final del array. Si no especifica este parámetro, la secuencia contendrá todos los elementos del array original, comenzando en la posición start. -Filter reordenará y restablecerá las claves de la matriz de enteros por defecto. Este comportamiento puede cambiarse estableciendo preserveKeys a true. Las claves de cadena siempre se conservan, independientemente de este parámetro. +Por defecto, la función cambia el orden y restablece las claves enteras del array. Este comportamiento se puede cambiar estableciendo preserveKeys a true. Las claves de cadena siempre se conservan, independientemente de este parámetro. diff --git a/latte/es/guide.texy b/latte/es/guide.texy index e8da5c420a..52c4fce221 100644 --- a/latte/es/guide.texy +++ b/latte/es/guide.texy @@ -1,46 +1,45 @@ -Primeros pasos con Latte -************************ +Empezando con Latte +*******************
                                                          -Las plantillas mejoran la organización del código, separan la lógica de la aplicación de la presentación y mejoran la seguridad. Ofrecen funciones y capacidades expresivas para generar HTML mucho mejores que el propio PHP. +Las plantillas mejoran la organización del código, separan la lógica de la aplicación de la presentación y aumentan la seguridad. Ofrecen funciones y medios expresivos mucho mejores para generar HTML que PHP solo. -Latte es el sistema de plantillas más seguro para PHP. Te encantará su sintaxis intuitiva. Una amplia gama de características útiles simplificará significativamente su trabajo. -Proporciona una protección de primera clase contra [vulnerabilidades críticas |safety-first] y le permite centrarse en la creación de aplicaciones de alta calidad sin preocuparse por su seguridad. +Latte es el sistema de plantillas más seguro para PHP. Le encantará su sintaxis intuitiva. Una amplia gama de funciones útiles le facilitará enormemente el trabajo. Proporciona una protección de primera clase contra [vulnerabilidades críticas|safety-first] y le permite centrarse en crear aplicaciones de calidad sin preocuparse por su seguridad. -¿Cómo escribir plantillas con Latte? .[#toc-how-to-write-templates-using-latte] -------------------------------------------------------------------------------- +¿Cómo escribir plantillas con Latte? +------------------------------------ -Latte está inteligentemente diseñado y es fácil de aprender para aquellos familiarizados con PHP, ya que pueden adoptar rápidamente sus etiquetas básicas. +Latte está diseñado de forma inteligente y es fácil de aprender para aquellos que conocen PHP y dominan las etiquetas básicas. -- Primero, familiarízate con la [sintaxis |syntax] de Latte y [pruébalo todo online |https://fiddle.nette.org/latte/] -- Echa un vistazo al conjunto básico de [etiquetas |tags] y [filtros |filters] -- Escriba plantillas en [un editor compatible con |recipes#Editors and IDE] Latte +- Primero, familiarícese con la [sintaxis Latte|syntax] y [PRUÉBALO ONLINE |https://fiddle.nette.org/latte/#9cc0cf6d89#9cc0cf6d89] +- Eche un vistazo al conjunto básico de [etiquetas|tags] y [filtros|filters] +- Escriba plantillas en un [editor con soporte Latte |recipes#Editores e IDE] -¿Cómo usar Latte en PHP? .[#toc-how-to-use-latte-in-php] --------------------------------------------------------- +¿Cómo usar Latte en PHP? +------------------------ Implementar Latte en su nueva aplicación es cuestión de minutos: -- Primero, [instala y ejecuta Latte |develop#Installation] -- Mímese con la [herramienta de depuración Tracy |develop#Debugging and Tracy] -- Amplíe Latte con [funciones personalizadas |extending-latte] +- Primero [Instalar y ejecutar Latte |develop#Instalación] +- Déjese mimar por la [herramienta de depuración Tracy |develop#Depuración y Tracy] +- Extienda Latte con [funcionalidad personalizada |extending-latte] -Si está convirtiendo un antiguo proyecto escrito en PHP plano a Latte, la [herramienta para convertir código PHP a Lat |cookbook/migration-from-php] te le facilitará la migración. ¿O está planeando cambiar a Latte desde Twig? Tenemos un [conversor de plantillas Twig |cookbook/migration-from-twig] a Latte para ti. +Si está convirtiendo un proyecto antiguo escrito en PHP puro a Latte, la migración le resultará más fácil con la [herramienta para convertir código PHP a Latte |cookbook/migration-from-php]. ¿O está pensando en pasar a Latte desde Twig? Tenemos para usted un [convertidor de plantillas Twig a Latte |cookbook/migration-from-twig]. -¿Qué más puede hacer Latte? .[#toc-what-else-can-latte-do] ----------------------------------------------------------- +¿Qué más puede hacer Latte? +--------------------------- -El Latte viene totalmente equipado, con todo lo esencial incluido. +Latte viene completamente equipado, con todo lo importante de base. -- Su productividad se verá impulsada por los [mecanismos de herencia |template-inheritance] que reutilizan elementos y estructuras repetidos -- El blindaje [Sandbox] aísla las plantillas de fuentes no fiables, como las editadas por los propios usuarios -- Para más inspiración, aquí tienes [consejos y trucos |recipes] +- Su productividad se verá impulsada por los [mecanismos de herencia |template-inheritance] gracias a los cuales se reutilizan elementos y estructuras repetidos +- El búnker blindado [sandbox] aísla las plantillas de fuentes no confiables, que por ejemplo editan los propios usuarios +- Para más inspiración, aquí tiene [consejos y trucos |recipes]
                                                          -{{description: Latte es el sistema de plantillas más seguro para PHP. Evita muchas vulnerabilidades de seguridad. Apreciarás su sintaxis intuitiva y un montón de ajustes útiles.}} +{{description: Latte es el sistema de plantillas más seguro para PHP. Previene muchas vulnerabilidades de seguridad. Apreciará su sintaxis intuitiva y valorará muchas características útiles.}} diff --git a/latte/es/loaders.texy b/latte/es/loaders.texy new file mode 100644 index 0000000000..50edbd0c22 --- /dev/null +++ b/latte/es/loaders.texy @@ -0,0 +1,198 @@ +Loaders +******* + +.[perex] +Los loaders son el mecanismo que Latte utiliza para obtener el código fuente de sus plantillas. Muy a menudo, las plantillas se almacenan como archivos en el disco, pero gracias al sistema flexible de loaders, puede cargarlas desde prácticamente cualquier lugar o incluso generarlas dinámicamente. + + +¿Qué es un Loader? +================== + +Cuando trabaja con plantillas, generalmente imagina archivos `.latte` ubicados en la estructura de directorios de su proyecto. De esto se encarga el [#FileLoader] predeterminado en Latte. Sin embargo, la conexión entre el nombre de la plantilla (como `'main.latte'` o `'components/card.latte'`) y su código fuente real *no tiene* que ser un mapeo directo a una ruta de archivo. + +Aquí es donde entran en juego los loaders. Un loader es un objeto que tiene la tarea de tomar el nombre de una plantilla (una cadena de identificación) y proporcionar a Latte su código fuente. Latte depende completamente del loader configurado para esta tarea. Esto se aplica no solo a la plantilla inicial solicitada con `$latte->render('main.latte')`, sino también a **cada plantilla referenciada dentro** usando etiquetas como `{include ...}`, `{layout ...}`, `{embed ...}` o `{import ...}`. + +¿Por qué usar un loader personalizado? + +- **Carga desde fuentes alternativas:** Obtener plantillas almacenadas en una base de datos, en caché (como Redis o Memcached), en un sistema de control de versiones (como Git, basado en un commit específico) o generadas dinámicamente. +- **Implementación de convenciones de nomenclatura personalizadas:** Es posible que desee usar alias más cortos para las plantillas o implementar una lógica específica de rutas de búsqueda (por ejemplo, buscar primero en el directorio del tema, luego volver al directorio predeterminado). +- **Agregar seguridad o control de acceso:** Un loader personalizado puede verificar los permisos del usuario antes de cargar ciertas plantillas. +- **Preprocesamiento:** Aunque generalmente no se recomienda ([los pasos de compilación |compiler-passes] son mejores), un loader *podría* teóricamente preprocesar el contenido de la plantilla antes de pasarlo a Latte. + +Un loader para una instancia de `Latte\Engine` se establece usando el método `setLoader()`: + +```php +$latte = new Latte\Engine; + +// Uso del FileLoader predeterminado para archivos en '/path/to/templates' +$loader = new Latte\Loaders\FileLoader('/path/to/templates'); +$latte->setLoader($loader); +``` + +El loader debe implementar la interfaz `Latte\Loader`. + + +Loaders incorporados +==================== + +Latte ofrece varios loaders estándar: + + +FileLoader +---------- + +Este es el **loader predeterminado** utilizado por la clase `Latte\Engine` si no se especifica ningún otro. Carga plantillas directamente desde el sistema de archivos. + +Opcionalmente, puede establecer un directorio raíz para restringir el acceso: + +```php +use Latte\Loaders\FileLoader; + +// Lo siguiente permitirá cargar plantillas solo desde el directorio /var/www/html/templates +$loader = new FileLoader('/var/www/html/templates'); +$latte->setLoader($loader); + +// $latte->render('../../../etc/passwd'); // Esto lanzaría una excepción + +// Renderizar una plantilla ubicada en /var/www/html/templates/pages/contact.latte +$latte->render('pages/contact.latte'); +``` + +Cuando se usan etiquetas como `{include}` o `{layout}`, resuelve los nombres de las plantillas en relación con la plantilla actual, a menos que se especifique una ruta absoluta. + + +StringLoader +------------ + +Este loader obtiene el contenido de la plantilla de un array asociativo, donde las claves son los nombres de las plantillas (identificadores) y los valores son las cadenas de código fuente de la plantilla. Es particularmente útil para pruebas o aplicaciones pequeñas donde las plantillas pueden almacenarse directamente en el código PHP. + +```php +use Latte\Loaders\StringLoader; + +$loader = new StringLoader([ + 'main.latte' => 'Hello {$name}, include is below:{include helper.latte}', + 'helper.latte' => '{var $x = 10}Included content: {$x}', + // Agregue más plantillas según sea necesario +]); + +$latte->setLoader($loader); + +$latte->render('main.latte', ['name' => 'World']); +// Salida: Hello World, include is below:Included content: 10 +``` + +Si necesita renderizar solo una plantilla directamente desde una cadena sin necesidad de inclusión o herencia que haga referencia a otras plantillas de cadena con nombre, puede pasar la cadena directamente al método `render()` o `renderToString()` cuando use `StringLoader` sin un array: + +```php +$loader = new StringLoader; +$latte->setLoader($loader); + +$templateString = 'Hello {$name}!'; +$output = $latte->renderToString($templateString, ['name' => 'Alice']); +// $output contiene 'Hello Alice!' +``` + + +Creación de un Loader personalizado +=================================== + +Para crear un loader personalizado (por ejemplo, para cargar plantillas desde una base de datos, caché, sistema de control de versiones u otra fuente), debe crear una clase que implemente la interfaz [api:Latte\Loader]. + +Veamos qué debe hacer cada método. + + +getContent(string $name): string .[method] +------------------------------------------ +Este es el método principal del loader. Su tarea es obtener y devolver el código fuente completo de la plantilla identificada por `$name` (como se pasa al método `$latte->render()` o se devuelve por el método [#getReferredName()]). + +Si la plantilla no se puede encontrar o acceder, este método **debe lanzar una excepción `Latte\RuntimeException`**. + +```php +public function getContent(string $name): string +{ + // Ejemplo: Carga desde un almacenamiento interno hipotético + $content = $this->storage->read($name); + if ($content === null) { + throw new Latte\RuntimeException("Template '$name' cannot be loaded."); + } + return $content; +} +``` + + +getReferredName(string $name, string $referringName): string .[method] +---------------------------------------------------------------------- +Este método maneja la resolución de nombres de plantillas utilizadas dentro de etiquetas como `{include}`, `{layout}`, etc. Cuando Latte encuentra, por ejemplo, `{include 'partial.latte'}` dentro de `main.latte`, llama a este método con `$name = 'partial.latte'` y `$referringName = 'main.latte'`. + +La tarea del método es traducir `$name` a un identificador canónico (por ejemplo, ruta absoluta, clave de base de datos única) que se utilizará al llamar a otros métodos del loader, según el contexto proporcionado en `$referringName`. + +```php +public function getReferredName(string $name, string $referringName): string +{ + return ...; +} +``` + + +getUniqueId(string $name): string .[method] +------------------------------------------- +Latte utiliza una caché de plantillas compiladas para mejorar el rendimiento. Cada archivo de plantilla compilado necesita un nombre único derivado del identificador de la plantilla fuente. Este método proporciona una cadena que **identifica unívocamente** la plantilla `$name`. + +Para plantillas basadas en archivos, la ruta absoluta puede servir. Para plantillas en una base de datos, una combinación de un prefijo y el ID de la base de datos es común. + +```php +public function getUniqueId(string $name): string +{ + return ...; +} +``` + + +Ejemplo: Loader simple de base de datos +--------------------------------------- + +Este ejemplo muestra la estructura básica de un loader que carga plantillas almacenadas en una tabla de base de datos llamada `templates` con columnas `name` (identificador único), `content` y `updated_at`. + +```php +use Latte; + +class DatabaseLoader implements Latte\Loader +{ + public function __construct( + private \PDO $db, + ) { + } + + public function getContent(string $name): string + { + $stmt = $this->db->prepare('SELECT content FROM templates WHERE name = ?'); + $stmt->execute([$name]); + $content = $stmt->fetchColumn(); + if ($content === false) { + throw new Latte\RuntimeException("Template '$name' not found in database."); + } + return $content; + } + + // Este ejemplo simple asume que los nombres de las plantillas ('homepage', 'article', etc.) + // son IDs únicos y las plantillas no se refieren entre sí relativamente. + public function getReferredName(string $name, string $referringName): string + { + return $name; + } + + public function getUniqueId(string $name): string + { + // Usar un prefijo y el propio nombre es único y suficiente aquí + return 'db_' . $name; + } +} + +// Uso: +$pdo = new \PDO(/* detalles de conexión */); +$loader = new DatabaseLoader($pdo); +$latte->setLoader($loader); +$latte->render('homepage'); // Carga la plantilla con el nombre 'homepage' de la BD +``` + +Los loaders personalizados le brindan un control total sobre de dónde provienen sus plantillas Latte, lo que permite la integración con diversos sistemas de almacenamiento y flujos de trabajo. diff --git a/latte/es/recipes.texy b/latte/es/recipes.texy index 1774ae053f..a3a48d4dd0 100644 --- a/latte/es/recipes.texy +++ b/latte/es/recipes.texy @@ -1,45 +1,45 @@ -Trucos y consejos +Consejos y trucos ***************** -Editores e IDE .[#toc-editors-and-ide] -====================================== +Editores e IDE +============== Escriba plantillas en un editor o IDE que tenga soporte para Latte. Será mucho más agradable. -- NetBeans IDE tiene soporte incorporado -- PhpStorm: instale el [plugin |https://plugins.jetbrains.com/plugin/7457-latte] Latte en `Settings > Plugins > Marketplace` -- VS Code: busque en markerplace el plugin "Nette Latte + Neon -- Sublime Text 3: en Package Control busque e instale el paquete `Nette` y seleccione Latte en `View > Syntax` -- en editores antiguos utilice Smarty para resaltar los archivos .latte +- PhpStorm: instale el [plugin Latte|https://plugins.jetbrains.com/plugin/7457-latte] en `Settings > Plugins > Marketplace` +- VS Code: instale [Nette Latte + Neon|https://marketplace.visualstudio.com/items?itemName=Kasik96.latte], [Nette Latte templates|https://marketplace.visualstudio.com/items?itemName=smuuf.latte-lang] o el más reciente [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode] plugin +- NetBeans IDE: el soporte nativo para Latte forma parte de la instalación +- Sublime Text 3: en Package Control, busque e instale el paquete `Nette` y elija Latte en `View > Syntax` +- en editores antiguos, use el resaltado de Smarty para archivos .latte -El plugin para PhpStorm es muy avanzado y puede perfectamente sugerir código PHP. Para trabajar de forma óptima, utilice [plantillas tipadas |type-system]. +El plugin para PhpStorm es muy avanzado y puede sugerir código PHP de manera excelente. Para que funcione de manera óptima, use [plantillas tipadas|type-system]. [* latte-phpstorm-plugin.webp *] -También se puede encontrar soporte para Latte en el resaltador de código web [Prism.js |https://prismjs.com/#supported-languages] y en el editor [Ace |https://ace.c9.io]. +También encontrará soporte para Latte en el resaltador de código web [Prism.js|https://prismjs.com/#supported-languages] y el editor [Ace|https://ace.c9.io]. -Latte dentro de JavaScript o CSS .[#toc-latte-inside-javascript-or-css] -======================================================================= +Latte dentro de JavaScript o CSS +================================ -Latte puede utilizarse muy cómodamente dentro de JavaScript o CSS. Pero, ¿cómo evitar que Latte considere erróneamente el código JavaScript o el estilo CSS como una etiqueta Latte? +Latte se puede usar muy cómodamente también dentro de JavaScript o CSS. Sin embargo, ¿cómo evitar la situación en la que Latte consideraría erróneamente el código JavaScript o el estilo CSS como una etiqueta Latte? ```latte ``` **Opción 1** -Evite situaciones en las que una letra siga inmediatamente a una `{`, ya sea insertando un espacio, un salto de línea o una comilla entre ellas: +Evite la situación en la que una letra sigue inmediatamente después de `{`, por ejemplo, insertando un espacio, un salto de línea o una comilla antes de ella: ```latte -

                                                          + +

                                                          ``` -Dos formas y dos tipos diferentes de escapar los datos. Dentro de los elementos ` ``` -Sin embargo, si queremos insertarlo en un atributo HTML, todavía necesitamos escapar las comillas a entidades HTML: +Sin embargo, si quisiéramos insertarlo en un atributo HTML, aún debemos escapar las comillas a entidades HTML: ```html
                                                          ``` -Sin embargo, el contexto anidado no tiene por qué ser sólo JS o CSS. También suele ser una URL. Los parámetros de las URL se escapan convirtiendo los caracteres especiales en secuencias que empiezan por `%`. Ejemplo: +Pero el contexto anidado no tiene por qué ser solo JS o CSS. Comúnmente también es una URL. Los parámetros en una URL se escapan convirtiendo los caracteres con significado especial en secuencias que comienzan con `%`. Ejemplo: ``` https://example.org/?a=Jazz&b=Rock%27n%27Roll ``` -Y cuando imprimimos esta cadena en un atributo, seguimos aplicando el escapado según este contexto y sustituimos `&` with `&`: +Y cuando imprimimos esta cadena en un atributo, aún aplicamos el escape según este contexto y reemplazamos `&` por `&`: ```html ``` -Si has leído hasta aquí, enhorabuena, ha sido agotador. Ahora tienes una buena idea de lo que son los contextos y el escaping. Y no tienes que preocuparte de que sea complicado. Latte lo hace por ti automáticamente. +Si ha leído hasta aquí, felicidades, ha sido exhaustivo. Ahora tiene una buena idea de qué son los contextos y el escape. Y no tiene que preocuparse de que sea complicado. Latte hace esto por usted automáticamente. -Latte vs Sistemas Naive .[#toc-latte-vs-naive-systems] -====================================================== +Latte vs sistemas ingenuos +========================== -Hemos mostrado cómo escapar correctamente en un documento HTML y lo crucial que es conocer el contexto, es decir, dónde estás imprimiendo los datos. En otras palabras, cómo funciona el escape sensible al contexto. -Aunque esto es un prerrequisito para una defensa funcional contra XSS, **Latte es el único sistema de plantillas para PHP que lo hace.** +Hemos mostrado cómo escapar correctamente en un documento HTML y cuán fundamental es el conocimiento del contexto, es decir, el lugar donde imprimimos los datos. En otras palabras, cómo funciona el escape sensible al contexto. Aunque es un requisito previo necesario para una defensa funcional contra XSS, **Latte es el único sistema de plantillas para PHP que puede hacer esto.** -¿Cómo es esto posible cuando todos los sistemas hoy en día afirman tener escapado automático? -El escape automático sin conocer el contexto es una gilipollez que **crea una falsa sensación de seguridad**. +¿Cómo es posible, cuando todos los sistemas hoy en día afirman tener escape automático? El escape automático sin conocimiento del contexto es un poco bullshit, que **crea una falsa impresión de seguridad**. -Los sistemas de plantillas como Twig, Laravel Blade y otros no ven ninguna estructura HTML en la plantilla. Por lo tanto, tampoco ven contextos. Comparados con Latte, son ciegos e ingenuos. Sólo manejan su propio marcado, todo lo demás es un flujo de caracteres irrelevante para ellos: +Los sistemas de plantillas, como Twig, Laravel Blade y otros, no ven ninguna estructura HTML en la plantilla. Por lo tanto, tampoco ven los contextos. En comparación con Latte, son ciegos e ingenuos. Solo procesan sus propias etiquetas, todo lo demás es para ellos un flujo de caracteres irrelevante:
                                                          -```twig .{file:Twig template as seen by Twig himself} -░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░ -░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░{{ text }}░░░░ +```twig .{file:Plantilla Twig, como la ve el propio Twig} +░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░ +░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░ ``` -```twig .{file:Twig template as the designer sees it} -- in text: {{ text }} -- in tag: -- in attribute: -- in unquoted attribute: -- in attribute containing URL: -- in attribute containing JavaScript: -- in attribute containing CSS: -- in JavaScriptu: -- in CSS: -- in comment: +```twig .{file:Plantilla Twig, como la ve el diseñador} +- en texto: {{ foo }} +- en etiqueta: +- en atributo: +- en atributo sin comillas: +- en atributo que contiene URL: +- en atributo que contiene JavaScript: +- en atributo que contiene CSS: +- en JavaScript: +- en CSS: +- en comentario: ```
                                                          -Los sistemas ingenuos se limitan a convertir mecánicamente los caracteres de `< > & ' "` en entidades HTML, lo cual es una forma válida de escapar en la mayoría de los usos, pero dista mucho de serlo siempre. Por lo tanto, no pueden detectar ni evitar varios agujeros de seguridad, como mostraremos a continuación. +Los sistemas ingenuos solo convierten mecánicamente los caracteres `< > & ' "` en entidades HTML, lo cual es un método de escape válido en la mayoría de los casos de uso, pero no siempre. Por lo tanto, no pueden detectar ni prevenir el origen de varios agujeros de seguridad, como mostraremos a continuación. -Latte ve la plantilla de la misma manera que tú. Entiende HTML, XML, reconoce etiquetas, atributos, etc. Y por ello, distingue entre contextos y trata los datos en consecuencia. Así que ofrece una protección realmente eficaz contra la crítica vulnerabilidad Cross-site Scripting. +Latte ve la plantilla igual que usted. Entiende HTML, XML, reconoce etiquetas, atributos, etc. Y gracias a esto, distingue los contextos individuales y sanea los datos según ellos. Ofrece así una protección realmente eficaz contra la vulnerabilidad crítica Cross-site Scripting. + +
                                                          + +```latte .{file:Plantilla Latte, como la ve Latte} +░░░░░░░░░░░{$foo} +░░░░░░░░░░ +░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░ +░░░░░░░░░ +░░░░░░░░░░░░░░░ +``` + +```latte .{file:Plantilla Latte, como la ve el diseñador} +- en texto: {$foo} +- en etiqueta: +- en atributo: +- en atributo sin comillas: +- en atributo que contiene URL: +- en atributo que contiene JavaScript: +- en atributo que contiene CSS: +- en JavaScript: +- en CSS: +- en comentario: +``` + +
                                                          -Demostración en directo .[#toc-live-demonstration] -================================================== +Demostración en vivo +==================== -A la izquierda puede ver la plantilla en Latte, a la derecha el código HTML generado. La variable `$text` se muestra varias veces, cada vez en un contexto ligeramente diferente. Y, por lo tanto, se escapa un poco diferente. Usted mismo puede editar el código de la plantilla, por ejemplo cambiar el contenido de la variable, etc. Pruébelo: +A la izquierda ve la plantilla en Latte, a la derecha está el código HTML generado. La variable `$text` se imprime varias veces aquí y cada vez en un contexto ligeramente diferente. Y, por lo tanto, también escapada de forma ligeramente diferente. Puede editar el código de la plantilla usted mismo, por ejemplo, cambiar el contenido de la variable, etc. Pruébelo:
                                                          ``` .{file:template.latte; min-height: 14em}[fiddle-source] -{* TRY TO EDIT THIS TEMPLATE *} +{* INTENTA EDITAR ESTA PLANTILLA *} {var $text = "Rock'n'Roll"} - {$text} - @@ -270,43 +286,41 @@ A la izquierda puede ver la plantilla en Latte, a la derecha el código HTML gen
                                                          -¿No es genial? Latte hace el escape sensible al contexto de forma automática, por lo que el programador: +¡No es genial! Latte realiza el escape sensible al contexto automáticamente, por lo que el programador: -- no tiene que pensar o saber cómo escapar datos +- no tiene que pensar ni saber cómo escapar en cada lugar - no puede equivocarse -- no puede olvidarse de ello +- no puede olvidar el escape -Estos ni siquiera son todos los contextos que Latte distingue a la hora de emitir y para los que personaliza el tratamiento de los datos. Ahora veremos más casos interesantes. +Estos ni siquiera son todos los contextos que Latte distingue al imprimir y para los cuales adapta el saneamiento de datos. Ahora repasaremos otros casos interesantes. -Cómo piratear sistemas ingenuos .[#toc-how-to-hack-naive-systems] -================================================================= +Cómo hackear sistemas ingenuos +============================== -Utilizaremos algunos ejemplos prácticos para mostrar lo importante que es la diferenciación de contexto y por qué los sistemas de plantillas ingenuos no proporcionan suficiente protección contra XSS, a diferencia de Latte. -Utilizaremos Twig como representante de un sistema ingenuo en los ejemplos, pero lo mismo se aplica a otros sistemas. +En varios ejemplos prácticos, mostraremos cuán importante es la distinción de contextos y por qué los sistemas de plantillas ingenuos no proporcionan una protección suficiente contra XSS, a diferencia de Latte. Como representante de un sistema ingenuo, usaremos Twig en las demostraciones, pero lo mismo se aplica a otros sistemas. -Vulnerabilidad de atributos .[#toc-attribute-vulnerability] ------------------------------------------------------------ +Vulnerabilidad por atributo +--------------------------- -Intentemos inyectar código malicioso en la página utilizando el atributo HTML como [mostramos anteriormente |#How does the vulnerability arise]. Tengamos una plantilla en Twig mostrando una imagen: +Intentaremos inyectar código malicioso en la página mediante un atributo HTML, como [mostramos anteriormente |#Cómo surge la vulnerabilidad]. Supongamos una plantilla en Twig que renderiza una imagen: ```twig .{file:Twig} {{ ``` -Observa que no hay comillas alrededor de los valores del atributo. El programador puede haberlas olvidado, lo que sucede. Por ejemplo, en React, el código se escribe así, sin comillas, y un programador que está cambiando de lenguaje puede olvidarse fácilmente de las comillas. +Observe que no hay comillas alrededor de los valores de los atributos. El codificador podría haberlas olvidado, lo que simplemente sucede. Por ejemplo, en React, el código se escribe así, sin comillas, y un codificador que alterna lenguajes puede olvidarlas fácilmente. -El atacante inserta una cadena inteligentemente construida `foo onload=alert('Hacked!')` como pie de imagen. Ya sabemos que Twig no puede distinguir si una variable se está imprimiendo en un flujo de texto HTML, dentro de un atributo, dentro de un comentario HTML, etc.; en resumen, no distingue entre contextos. Y sólo convierte mecánicamente los caracteres `< > & ' "` en entidades HTML. -Así que el código resultante tendrá este aspecto: +Un atacante inserta como descripción de la imagen una cadena hábilmente construida `foo onload=alert('Hacked!')`. Ya sabemos que Twig no puede saber si la variable se imprime en el flujo de texto HTML, dentro de un atributo, comentario HTML, etc., en resumen, no distingue contextos. Y solo convierte mecánicamente los caracteres `< > & ' "` en entidades HTML. Así que el código resultante se verá así: ```html foo ``` -**¡Se ha creado un agujero de seguridad!** +**¡Y se ha creado un agujero de seguridad!** -Un atributo `onload` falso ha pasado a formar parte de la página y el navegador lo ejecuta inmediatamente después de descargar la imagen. +Ahora forma parte de la página un atributo `onload` falsificado y el navegador lo ejecuta inmediatamente después de descargar la imagen. Ahora veamos cómo Latte maneja la misma plantilla: @@ -314,19 +328,19 @@ Ahora veamos cómo Latte maneja la misma plantilla: {$imageAlt} ``` -Latte ve la plantilla de la misma manera que tú. A diferencia de Twig, entiende HTML y sabe que una variable se imprime como un valor de atributo que no está entre comillas. Por eso las añade. Cuando un atacante inserta la misma leyenda, el código resultante se verá así: +Latte ve la plantilla igual que usted. A diferencia de Twig, entiende HTML y sabe que la variable se imprime como el valor de un atributo que no está entre comillas. Por eso las añade. Cuando un atacante inserta la misma descripción, el código resultante se verá así: ```html foo onload=alert('Hacked!') ``` -**Latte ha evitado con éxito el XSS.** +**Latte previno exitosamente el XSS.** -Impresión de una variable en JavaScript .[#toc-printing-a-variable-in-javascript] ---------------------------------------------------------------------------------- +Impresión de variable en JavaScript +----------------------------------- -Gracias al escape sensible al contexto, es posible usar variables PHP nativamente dentro de JavaScript. +Gracias al escape sensible al contexto, es posible usar variables PHP de forma completamente nativa dentro de JavaScript. ```latte

                                                          {$movie}

                                                          @@ -334,7 +348,7 @@ Gracias al escape sensible al contexto, es posible usar variables PHP nativament ``` -Si la variable `$movie` almacena la cadena `'Amarcord & 8 1/2'` genera la siguiente salida. Observe el diferente escapado usado en HTML y JavaScript y también en el atributo `onclick`: +Si la variable `$movie` contiene la cadena `'Amarcord & 8 1/2'`, se generará la siguiente salida. Observe que dentro de HTML se usa un escape diferente al que se usa dentro de JavaScript y aún otro diferente en el atributo `onclick`: ```latte

                                                          Amarcord & 8 1/2

                                                          @@ -343,29 +357,27 @@ Si la variable `$movie` almacena la cadena `'Amarcord & 8 1/2'` genera la siguie ``` -Comprobación de enlaces .[#toc-link-checking] ---------------------------------------------- +Comprobación de enlaces +----------------------- -Latte comprueba automáticamente si la variable utilizada en los atributos `src` o `href` contiene una URL web (es decir, protocolo HTTP) e impide la escritura de enlaces que puedan suponer un riesgo para la seguridad. +Latte comprueba automáticamente si la variable utilizada en los atributos `src` o `href` contiene una URL web (es decir, protocolo HTTP) y evita la impresión de enlaces que puedan suponer un riesgo de seguridad. ```latte {var $link = 'javascript:attack()'} -click here +haz clic ``` -Escribe: +Imprime: ```latte -click here +haz clic ``` -La comprobación puede desactivarse mediante un filtro [nocheck |filters#nocheck]. +La comprobación se puede desactivar con el filtro [nocheck |filters#nocheck]. -Límites de Latte .[#toc-limits-of-latte] -======================================== +Límites de Latte +================ -Latte no es una protección XSS completa para toda la aplicación. No nos gustaría que se parase a pensar en la seguridad cuando utilice Latte. -El objetivo de Latte es asegurar que un atacante no pueda alterar la estructura de una página, manipular elementos HTML o atributos. Pero no comprueba la corrección del contenido de los datos que se emiten. Ni la corrección del comportamiento de JavaScript. -Eso está fuera del alcance del sistema de plantillas. Verificar la corrección de los datos, especialmente los introducidos por el usuario y, por tanto, no fiables, es una tarea importante para el programador. +Latte no es una protección completamente completa contra XSS para toda la aplicación. No nos gustaría que dejara de pensar en la seguridad al usar Latte. El objetivo de Latte es asegurar que un atacante no pueda modificar la estructura de la página, falsificar elementos o atributos HTML. Pero no controla la corrección del contenido de los datos impresos. Ni la corrección del comportamiento de JavaScript. Esto ya está fuera de la competencia del sistema de plantillas. La verificación de la corrección de los datos, especialmente los insertados por el usuario y, por lo tanto, no confiables, es una tarea importante del programador. diff --git a/latte/es/sandbox.texy b/latte/es/sandbox.texy index 367662bbf6..14b212956d 100644 --- a/latte/es/sandbox.texy +++ b/latte/es/sandbox.texy @@ -1,12 +1,10 @@ -Cajón de arena -************** +Sandbox +******* -.[perex]{data-version:2.8} -Sandbox proporciona una capa de seguridad que te permite controlar qué etiquetas, funciones PHP, métodos, etc. se pueden utilizar en las plantillas. Gracias al modo sandbox, puedes colaborar de forma segura con un cliente o codificador externo en la creación de plantillas sin preocuparte de comprometer la aplicación o de operaciones no deseadas. +.[perex] +Sandbox proporciona una capa de seguridad que le da control sobre qué etiquetas, funciones PHP, métodos, etc., se pueden usar en las plantillas. Gracias al modo sandbox, puede colaborar de forma segura con el cliente o un codificador externo en la creación de plantillas sin tener que preocuparse por violaciones de la aplicación u operaciones no deseadas. -¿Cómo funciona? Simplemente definimos lo que queremos permitir en la plantilla. Al principio, todo está prohibido y poco a poco vamos concediendo permisos: - -El siguiente código permite a la plantilla utilizar las etiquetas `{block}`, `{if}`, `{else}` y `{=}` (esta última es una etiqueta para [imprimir una variable o expresión |tags#Printing]) y todos los filtros: +¿Cómo funciona? Simplemente definimos todo lo que permitiremos en la plantilla. Por defecto, todo está prohibido y vamos permitiendo gradualmente. Con el siguiente código, permitimos al autor de la plantilla usar las etiquetas `{block}`, `{if}`, `{else}` y `{=}`, que es la etiqueta para [imprimir una variable o expresión |tags#Impresión] y todos los filtros: ```php $policy = new Latte\Sandbox\SecurityPolicy; @@ -16,7 +14,7 @@ $policy->allowFilters($policy::All); $latte->setPolicy($policy); ``` -También podemos permitir el acceso a funciones globales, métodos o propiedades de objetos: +Además, podemos permitir funciones, métodos o propiedades individuales de objetos: ```php $policy->allowFunctions(['trim', 'strlen']); @@ -24,30 +22,35 @@ $policy->allowMethods(Nette\Security\User::class, ['isLoggedIn', 'isAllowed']); $policy->allowProperties(Nette\Database\Row::class, $policy::All); ``` -¿No es increíble? Puedes controlarlo todo a un nivel muy bajo. Si la plantilla intenta llamar a una función no autorizada o acceder a un método o propiedad no autorizados, lanza la excepción `Latte\SecurityViolationException`. +¿No es asombroso? Puede controlar absolutamente todo a un nivel muy bajo. Si la plantilla intenta llamar a una función no permitida o acceder a un método o propiedad no permitidos, terminará con una excepción `Latte\SecurityViolationException`. -Crear políticas desde cero, cuando todo está prohibido, puede no ser conveniente, así que puedes empezar desde una base segura: +Crear una política desde cero, donde todo está prohibido, puede no ser cómodo, por lo que puede empezar desde una base segura: ```php $policy = Latte\Sandbox\SecurityPolicy::createSafePolicy(); ``` -Esto significa que todas las etiquetas estándar están permitidas excepto `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget`. -También se permiten todos los filtros estándar excepto `datastream`, `noescape` y `nocheck`. Por último, también se permite el acceso a los métodos y propiedades del objeto `$iterator`. +La base segura significa que todas las etiquetas estándar están permitidas excepto `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget`. Están permitidos los filtros estándar excepto `datastream`, `noescape` y `nocheck`. Y finalmente, está permitido el acceso a los métodos y propiedades del objeto `$iterator`. -Las reglas se aplican a la plantilla que insertamos con la nueva etiqueta [`{sandbox}` |tags#Including Templates] etiqueta. Que es algo así como `{include}`, pero activa el modo sandbox y además no pasa ninguna variable externa: +Las reglas se aplican a la plantilla que insertamos con la etiqueta [`{sandbox}` |tags#Inclusión de plantillas]. Que es algo análogo a `{include}`, pero que activa el modo seguro y tampoco pasa ninguna variable: ```latte {sandbox 'untrusted.latte'} ``` -Así, el diseño y las páginas individuales pueden utilizar todas las etiquetas y variables como antes, las restricciones se aplicarán sólo a la plantilla `untrusted.latte`. +Por lo tanto, el layout y las páginas individuales pueden usar libremente todas las etiquetas y variables, solo a la plantilla `untrusted.latte` se le aplicarán restricciones. -Algunas infracciones, como el uso de una etiqueta o filtro prohibidos, se detectan en tiempo de compilación. Otras, como la llamada a métodos no permitidos de un objeto, en tiempo de ejecución. -La plantilla también puede contener cualquier otro error. Para evitar que se lance una excepción desde la plantilla sandboxed, lo que interrumpe toda la renderización, puedes definir [tu propio manejador de excepciones |develop#exception handler], que, por ejemplo, se limita a registrarla. +Algunas infracciones, como el uso de una etiqueta o filtro prohibido, se detectan en tiempo de compilación. Otras, como la llamada a métodos de objeto no permitidos, solo en tiempo de ejecución. La plantilla también puede contener cualquier otro error. Para evitar que una excepción de una plantilla en sandbox interrumpa todo el renderizado, puede definir un [manejador de excepciones |develop#Manejador de excepciones] personalizado que, por ejemplo, la registre. -Si queremos activar el modo sandbox directamente para todas las plantillas, es fácil: +Si quisiéramos activar el modo sandbox directamente para todas las plantillas, es fácil: ```php $latte->setSandboxMode(); ``` + +Para asegurarse de que el usuario no inserte en la página código PHP que sea sintácticamente correcto pero prohibido y cause un PHP Compile Error, recomendamos [comprobar las plantillas con el linter PHP |develop#Comprobación del código generado]. Esta funcionalidad se activa con el método `Engine::enablePhpLint()`. Dado que necesita llamar al binario de PHP para la comprobación, pase la ruta como parámetro: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); +``` diff --git a/latte/es/syntax.texy b/latte/es/syntax.texy index 42f1c98dc4..e1938ea8b0 100644 --- a/latte/es/syntax.texy +++ b/latte/es/syntax.texy @@ -2,62 +2,60 @@ Sintaxis ******** .[perex] -Syntax Latte nació de las necesidades prácticas de los diseñadores web. Buscábamos la sintaxis más fácil de usar, con la que poder escribir con elegancia construcciones que de otro modo supondrían un verdadero reto. -Al mismo tiempo, todas las expresiones se escriben exactamente igual que en PHP, por lo que no es necesario aprender un nuevo lenguaje. Sólo tienes que aprovechar al máximo lo que ya sabes. +La sintaxis de Latte surgió de los requisitos prácticos de los diseñadores web. Buscamos la sintaxis más amigable con la que pueda escribir elegantemente incluso construcciones que de otro modo serían un verdadero desafío. Al mismo tiempo, todas las expresiones se escriben exactamente igual que en PHP, por lo que no tiene que aprender un nuevo lenguaje. Simplemente aprovecha lo que ya sabe desde hace mucho tiempo. -A continuación se muestra una plantilla mínima que ilustra algunos elementos básicos: etiquetas, n:attributes, comentarios y filtros. +A continuación se muestra una plantilla mínima que ilustra varios elementos básicos: etiquetas, n:atributos, comentarios y filtros. ```latte {* esto es un comentario *} -
                                                            {* n:if es n:atributos *} +
                                                              {* n:if es un n:atributo *} {foreach $items as $item} {* etiqueta que representa el bucle foreach *}
                                                            • {$item|capitalize}
                                                            • {* etiqueta que imprime una variable con un filtro *} -{/foreach} {* fin de ciclo *} +{/foreach} {* fin del bucle *}
                                                            ``` -Echemos un vistazo más de cerca a estos importantes elementos y cómo pueden ayudarte a construir una plantilla increíble. +Veamos más de cerca estos elementos importantes y cómo pueden ayudarle a crear una plantilla increíble. -Tags .[#toc-tags] -================= +Etiquetas +========= -Una plantilla contiene etiquetas que controlan la lógica de la plantilla (por ejemplo, bucles *foreach*) o las expresiones de salida. Para ambos, se utiliza un único delimitador `{ ... }`, por lo que no hay que pensar qué delimitador utilizar en cada situación, como ocurre con otros sistemas. -Si el carácter `{` va seguido de una comilla o un espacio, Latte no lo considera el comienzo de una etiqueta, por lo que puede utilizar construcciones JavaScript, JSON o reglas CSS en sus plantillas sin ningún problema. +La plantilla contiene etiquetas que controlan la lógica de la plantilla (por ejemplo, bucles *foreach*) o imprimen expresiones. Para ambos se utiliza un único delimitador `{ ... }`, por lo que no tiene que pensar qué delimitador usar en qué situación, como ocurre en otros sistemas. Si al carácter `{` le sigue una comilla o un espacio, Latte no lo considera el inicio de una etiqueta, gracias a lo cual puede usar construcciones JavaScript, JSON o reglas CSS en las plantillas sin problemas. -Ver [resumen de todas las etiquetas |tags]. Además, también puede crear [etiquetas personalizadas |extending-latte#tags]. +Vea el [resumen de todas las etiquetas|tags]. Además, también puede crear sus propias [etiquetas personalizadas|custom tags]. -Latte entiende PHP .[#toc-latte-understands-php] -================================================ +Latte entiende PHP +================== -Puedes usar expresiones PHP que conozcas bien dentro de las etiquetas: +Dentro de las etiquetas puede usar expresiones PHP que conoce bien: - variables -- cadenas (incluyendo HEREDOC y NOWDOC), matrices, números, etc. +- cadenas (incluyendo HEREDOC y NOWDOC), arrays, números, etc. - [operadores |https://www.php.net/manual/en/language.operators.php] -- llamadas a funciones y métodos (que pueden estar restringidas por [sandbox]) -- [coincidencia |https://www.php.net/manual/en/control-structures.match.php] +- llamadas a funciones y métodos (que se pueden limitar con [sandbox|sandbox]) +- [match |https://www.php.net/manual/en/control-structures.match.php] - [funciones anónimas |https://www.php.net/manual/en/functions.arrow.php] -- [devoluciones de llamada |https://www.php.net/manual/en/functions.first_class_callable_syntax.php] +- [callbacks |https://www.php.net/manual/en/functions.first_class_callable_syntax.php] - comentarios multilínea `/* ... */` -- etc. +- etc… -Además, Latte añade varias [buenas extensiones |#Syntactic Sugar] a la sintaxis PHP. +Latte además complementa la sintaxis de PHP con varias [extensiones agradables |#Azúcar sintáctico]. -n:atributos .[#toc-n-attributes] -================================ +n:atributos +=========== -Cada etiqueta de par, como `{if} … {/if}`, que opera sobre un único elemento HTML puede escribirse en notación [n:attribute |#n:attribute]. Por ejemplo, `{foreach}` en el ejemplo anterior también podría escribirse de esta manera: +Todas las etiquetas pares, por ejemplo `{if} … {/if}`, que operan sobre un único elemento HTML, se pueden reescribir en forma de n:atributos. Así se podría escribir, por ejemplo, también el `{foreach}` en el ejemplo introductorio: ```latte -
                                                              +
                                                              • {$item|capitalize}
                                                              ``` -La funcionalidad corresponde entonces al elemento HTML en el que está escrita: +La funcionalidad entonces se refiere al elemento HTML en el que se coloca: ```latte {var $items = ['I', '♥', 'Latte']} @@ -65,7 +63,7 @@ La funcionalidad corresponde entonces al elemento HTML en el que está escrita:

                                                              {$item}

                                                              ``` -Imprime: +imprime: ```latte

                                                              I

                                                              @@ -73,7 +71,7 @@ Imprime:

                                                              Latte

                                                              ``` -Utilizando el prefijo `inner-` podemos alterar el comportamiento para que la funcionalidad se aplique sólo al cuerpo del elemento: +Usando el prefijo `inner-` podemos ajustar el comportamiento para que se refiera solo a la parte interna del elemento: ```latte
                                                              @@ -82,7 +80,7 @@ Utilizando el prefijo `inner-` podemos alterar el comportamiento para que la fun
                                                              ``` -Imprime: +Se imprimirá: ```latte
                                                              @@ -95,178 +93,184 @@ Imprime:
                                                              ``` -O utilizando `tag-` prefijo la funcionalidad se aplica en las etiquetas HTML solamente: +O usando el prefijo `tag-` aplicamos la funcionalidad solo a las propias etiquetas HTML: ```latte -

                                                              Title

                                                              +

                                                              Title

                                                              ``` -Dependiendo del valor de la variable `$url` se imprimirá: +Lo que imprime dependiendo de la variable `$url`: ```latte -// cuando $url está vacía -

                                                              Título

                                                              +{* cuando $url está vacío *} +

                                                              Title

                                                              -// cuando $url es igual a 'https://nette.org' -

                                                              Título

                                                              +{* cuando $url contiene 'https://nette.org' *} +

                                                              Title

                                                              ``` -Sin embargo, los atributos n:no son sólo un atajo para las etiquetas de pares, también hay algunos atributos n:puros, por ejemplo el mejor amigo del programador, [n:class |tags#n:class]. +Sin embargo, los n:atributos no son solo un atajo para etiquetas pares. También existen n:atributos puros, como [n:href |application:creating-links#En la plantilla del presenter] o el muy útil ayudante del codificador [n:class |tags#n:class]. -Filtros .[#toc-filters] -======================= +Filtros +======= -Consulte el resumen de filtros [estándar |filters]. +Vea el resumen de [filtros estándar |filters]. -Latte permite llamar a los filtros utilizando la notación del signo pipa (se permite el espacio precedente): +Los filtros se escriben después de una barra vertical (puede haber un espacio antes de ella): ```latte

                                                              {$heading|upper}

                                                              ``` -Los filtros pueden encadenarse, en cuyo caso se aplican en orden de izquierda a derecha: +Los filtros se pueden encadenar y luego se aplican en orden de izquierda a derecha: ```latte

                                                              {$heading|lower|capitalize}

                                                              ``` -Los parámetros se ponen después del nombre del filtro separados por dos puntos o coma: +Los parámetros se introducen después del nombre del filtro separados por dos puntos o comas: ```latte

                                                              {$heading|truncate:20,''}

                                                              ``` -Los filtros se pueden aplicar sobre la expresión: +Los filtros también se pueden aplicar a una expresión: ```latte {var $name = ($title|upper) . ($subtitle|lower)} ``` -En bloque: +A un bloque: ```latte

                                                              {block |lower}{$heading}{/block}

                                                              ``` -O directamente sobre el valor (en combinación con [`{=expr}` | https://latte.nette.org/es/tags#printing] etiqueta): +O directamente al valor (en combinación con la etiqueta [`{=expr}` |tags#Impresión]): ```latte

                                                              {=' Hello world '|trim}

                                                              ``` -Comentarios .[#toc-comments] -============================ +Etiquetas HTML dinámicas .{data-version:3.0.9} +============================================== -Los comentarios se escriben de esta manera y no entran en la salida: +Latte admite etiquetas HTML dinámicas, que son útiles cuando necesita flexibilidad en los nombres de las etiquetas: + +```latte +Heading +``` + +El código anterior puede, por ejemplo, generar `

                                                              Heading

                                                              ` o `

                                                              Heading

                                                              ` dependiendo del valor de la variable `$level`. Las etiquetas HTML dinámicas en Latte siempre deben ser pares. Su alternativa es [n:tag |tags#n:tag]. + +Dado que Latte es un sistema de plantillas seguro, comprueba que el nombre de etiqueta resultante sea válido y no contenga ningún valor no deseado o malicioso. Además, asegura que el nombre de la etiqueta de cierre siempre sea el mismo que el nombre de la etiqueta de apertura. + + +Comentarios +=========== + +Los comentarios se escriben de esta manera y no llegan a la salida: ```latte {* esto es un comentario en Latte *} ``` -Los comentarios PHP funcionan dentro de las etiquetas: +Dentro de las etiquetas funcionan los comentarios PHP: ```latte {include 'file.info', /* value: 123 */} ``` -Azúcar sintáctico .[#toc-syntactic-sugar] -========================================= +Azúcar sintáctico +================= -Cadenas sin comillas .[#toc-strings-without-quotation-marks] ------------------------------------------------------------- +Cadenas sin comillas +-------------------- -Las comillas pueden omitirse en las cadenas simples: +Para cadenas simples se pueden omitir las comillas: ```latte -como en PHP: {var $arr = ['hello', 'btn--default', '€']} +como en PHP: {var $arr = ['hello', 'btn--default', '€']} -abreviado: {var $arr = [hello, btn--default, €]} +abreviado: {var $arr = [hello, btn--default, €]} ``` -Las cadenas simples son las que están formadas únicamente por letras, cifras, guiones bajos, guiones y puntos. No deben empezar por un dígito ni comenzar o terminar con un guión. -No debe estar compuesto únicamente por letras mayúsculas y guiones bajos, porque entonces se considera una constante (por ejemplo, `PHP_VERSION`). -Y no debe colisionar con las palabras clave `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor`. +Las cadenas simples son aquellas que están formadas puramente por letras, dígitos, guiones bajos, guiones y puntos. No deben comenzar con un dígito y no deben comenzar ni terminar con un guion. No deben estar compuestas solo por letras mayúsculas y guiones bajos, porque entonces se considera una constante (por ejemplo, `PHP_VERSION`). Y no deben colisionar con las palabras clave: `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor`. -Operador ternario corto .[#toc-short-ternary-operator] ------------------------------------------------------- +Constantes +---------- -Si el tercer valor del operador ternario está vacío, puede omitirse: +Dado que se pueden omitir las comillas en las cadenas simples, recomendamos escribir las constantes globales con una barra inclinada al principio para distinguirlas: ```latte -como en PHP: {$stock ? 'In stock' : ''} - -abreviado: {$stock ? 'In stock'} +{if \PROJECT_ID === 1} ... {/if} ``` +Esta notación es completamente válida en el propio PHP, la barra inclinada dice que la constante está en el namespace global. + -Notación moderna de clave en el array .[#toc-modern-key-notation-in-the-array] ------------------------------------------------------------------------------- +Operador ternario abreviado +--------------------------- -Las claves de los arrays pueden escribirse de forma similar a los parámetros con nombre al llamar a funciones: +Si el tercer valor del operador ternario está vacío, se puede omitir: ```latte -como en PHP: {var $arr = ['one' => 'item 1', 'two' => 'item 2']} +como en PHP: {$stock ? 'En stock' : ''} -modern: {var $arr = [one: 'item 1', two: 'item 2']} +abreviado: {$stock ? 'En stock'} ``` -Filtros .[#toc-filters] ------------------------ +Notación moderna de claves en array +----------------------------------- -Los filtros pueden utilizarse para cualquier expresión, basta con encerrar el conjunto entre paréntesis: +Las claves en un array se pueden escribir de forma similar a los parámetros nombrados al llamar a funciones: ```latte -{var $content = ($text|truncate: 30|upper)} +como en PHP: {var $arr = ['one' => 'item 1', 'two' => 'item 2']} + +moderno: {var $arr = [one: 'item 1', two: 'item 2']} ``` -Operador `in` .[#toc-operator-in] ---------------------------------- +Filtros +------- -El operador `in` puede utilizarse para sustituir a la función `in_array()`. La comparación es siempre estricta: +Los filtros se pueden usar para cualquier expresión, basta con encerrar el conjunto en paréntesis: ```latte -{* like in_array($item, $items, true) *} -{if $item in $items} - ... -{/if} +{var $content = ($text|truncate: 30|upper)} ``` -.{data-version:2.9} -Encadenamiento opcional con el operador Undefined-Safe .[#toc-optional-chaining-with-undefined-safe-operator] -------------------------------------------------------------------------------------------------------------- +Operador `in` +------------- -El operador undefined-safe `??->` es similar al operador nullsafe `?->`, pero no lanza un error si una variable, propiedad o índice no existe en absoluto. +El operador `in` puede reemplazar la función `in_array()`. La comparación siempre es estricta: ```latte -{$order??->id} +{* análogo a in_array($item, $items, true) *} +{if $item in $items} + ... +{/if} ``` -esto es una forma de decir que cuando `$order` está definido y no es nulo, `$order->id` será computado, pero cuando `$order` es nulo o no existe, deja de hacer lo que estamos haciendo y simplemente devuelve null. - -```latte -{$user??->address??->street} -// roughly means isset($user) && isset($user->address) ? $user->address->street : null -``` +Ventana histórica +----------------- -Una ventana a la historia .[#toc-a-window-into-history] -------------------------------------------------------- +Latte introdujo a lo largo de su historia una serie de azúcares sintácticos que aparecieron unos años después en el propio PHP. Por ejemplo, en Latte era posible escribir arrays como `[1, 2, 3]` en lugar de `array(1, 2, 3)` o usar el operador nullsafe `$obj?->foo` mucho antes de que fuera posible en el propio PHP. Latte también introdujo el operador para el desempaquetado de arrays `(expand) $arr`, que es el equivalente del actual operador `...$arr` de PHP. -Latte ha ideado una serie de caramelos sintácticos a lo largo de su historia, que aparecieron en el propio PHP unos años más tarde. Por ejemplo, en Latte era posible escribir arrays como `[1, 2, 3]` en lugar de `array(1, 2, 3)` o usar el operador nullsafe `$obj?->foo` mucho antes de que fuera posible en el propio PHP. Latte también introdujo el operador de expansión de arrays `(expand) $arr`, que es el equivalente al actual operador `...$arr` de PHP. +El operador seguro para indefinidos `??->`, que es análogo al operador nullsafe `?->`, pero que no lanza un error si la variable no existe, surgió por razones históricas y hoy recomendamos usar el operador PHP estándar `?->`. -Limitaciones de PHP en Latte .[#toc-php-limitations-in-latte] -============================================================= +Limitaciones de PHP en Latte +============================ -En Latte sólo se pueden escribir expresiones PHP. Es decir, no se pueden declarar clases ni utilizar [estructuras de |https://www.php.net/manual/en/language.control-structures.php] control, como `if`, `foreach`, `switch`, `return`, `try`, `throw` y otras, en lugar de las cuales Latte ofrece sus [etiquetas |tags]. -Tampoco puedes usar [atributos |https://www.php.net/manual/en/language.attributes.php], [backticks |https://www.php.net/manual/en/language.operators.execution.php] o [constantes mágicas |https://www.php.net/manual/en/language.constants.magic.php], porque no tendría sentido. -Ni siquiera puedes usar `unset`, `echo`, `include`, `require`, `exit`, `eval`, porque no son funciones, sino construcciones especiales del lenguaje PHP, y por tanto no son expresiones. +En Latte solo se pueden escribir expresiones PHP. Por lo tanto, no se pueden usar sentencias terminadas en punto y coma. No se pueden declarar clases ni usar [estructuras de control |https://www.php.net/manual/en/language.control-structures.php], por ejemplo, `if`, `foreach`, `switch`, `return`, `try`, `throw` y otras, en lugar de las cuales Latte ofrece sus [etiquetas|tags]. Tampoco se pueden usar [atributos |https://www.php.net/manual/en/language.attributes.php], [backticks |https://www.php.net/manual/en/language.operators.execution.php] ni algunas [constantes mágicas |https://www.php.net/manual/en/language.constants.magic.php]. Tampoco se pueden usar `unset`, `echo`, `include`, `require`, `exit`, `eval`, porque no son funciones, sino construcciones especiales del lenguaje PHP, y por lo tanto no son expresiones. Los comentarios solo se admiten los multilínea `/* ... */`. -Sin embargo, puede trabajar alrededor de estas limitaciones activando la extensión [RawPhpExtension |develop#RawPhpExtension], que le permite usar cualquier código PHP en la etiqueta `{php ...}` bajo la responsabilidad del autor de la plantilla. +Sin embargo, estas limitaciones se pueden eludir activando la extensión [RawPhpExtension |develop#RawPhpExtension], gracias a la cual se puede usar cualquier código PHP en la etiqueta `{php ...}` bajo la responsabilidad del autor de la plantilla. diff --git a/latte/es/tags.texy b/latte/es/tags.texy index cdd175f392..79c5c3f0cb 100644 --- a/latte/es/tags.texy +++ b/latte/es/tags.texy @@ -1,168 +1,174 @@ -Tags in Latte -************* +Etiquetas Latte +*************** .[perex] -Resumen y descripción de todas las etiquetas Latte incorporadas. +Resumen y descripción de todas las etiquetas del sistema de plantillas Latte que están disponibles de forma estándar. .[table-latte-tags language-latte] |## Impresión -| `{$var}`, `{...}` o `{=...}` | [imprime una variable o expresión escapada |#printing] -| `{$var\|filter}` | [imprime con filtros |#filters] -| `{l}` o `{r}` | imprime el carácter `{` or `}` +| `{$var}`, `{...}` o `{=...}` | [imprime una variable o expresión escapada |#Impresión] +| `{$var\|filter}` | [imprime usando filtros |#Filtros] +| `{l}` o `{r}` | imprime el carácter `{` o `}` .[table-latte-tags language-latte] |## Condiciones -| `{if}`... `{elseif}`... `{else}`... `{/if}` | [condición if |#if-elseif-else] -| `{ifset}`... `{elseifset}`... `{/ifset}` | [condición ifset |#ifset-elseifset] -| `{ifchanged}`... `{/ifchanged}` | [prueba si ha habido un cambio |#ifchanged] -| `{switch}` `{case}` `{default}` `{/switch}` | [condition switch |#switch-case-default] +| `{if}` … `{elseif}` … `{else}` … `{/if}` | [condición if |#if elseif else] +| `{ifset}` … `{elseifset}` … `{/ifset}` | [condición ifset |#ifset elseifset] +| `{ifchanged}` … `{/ifchanged}` | [prueba si ha habido un cambio |#ifchanged] +| `{switch}` `{case}` `{default}` `{/switch}` | [condición switch |#switch case default] +| `n:else` | [contenido alternativo para condiciones |#n:else] .[table-latte-tags language-latte] |## Bucles -| `{foreach}`... `{/foreach}` | [foreach |#foreach] -| `{for}`... `{/for}` | [for |#for] -| `{while}`... `{/while}` | [while |#while] -| `{continueIf $cond}` | [Continúa en la siguiente iteración |#continueif-skipif-breakif]. -| `{skipIf $cond}` | [salta la iteración actual del bucle|#continueif-skipif-breakif] -| `{breakIf $cond}` | [rompe el bucle |#continueif-skipif-breakif] -| `{exitIf $cond}` | [Salida anticipada |#exitif] -| `{first}`... `{/first}` | [¿Es la primera iteración? |#first-last-sep] -| `{last}`... `{/last}` | [¿Es la última iteración? |#first-last-sep] -| `{sep}`... `{/sep}` | [¿Seguirá la siguiente iteración? |#first-last-sep] -| `{iterateWhile}`... `{/iterateWhile}` | [foreach estructurado |#iterateWhile] -| `$iterator` | [Variable especial dentro del bucle foreach |#$iterator] +| `{foreach}` … `{/foreach}` | [#foreach] +| `{for}` … `{/for}` | [#for] +| `{while}` … `{/while}` | [#while] +| `{continueIf $cond}` | [continuar con la siguiente iteración |#continueIf skipIf breakIf] +| `{skipIf $cond}` | [saltar la iteración |#continueIf skipIf breakIf] +| `{breakIf $cond}` | [interrupción del bucle |#continueIf skipIf breakIf] +| `{exitIf $cond}` | [terminación anticipada |#exitIf] +| `{first}` … `{/first}` | [¿es la primera pasada? |#first last sep] +| `{last}` … `{/last}` | [¿es la última pasada? |#first last sep] +| `{sep}` … `{/sep}` | [¿seguirá otra pasada? |#first last sep] +| `{iterateWhile}` … `{/iterateWhile}` | [foreach estructurado |#iterateWhile] +| `$iterator` | [variable especial dentro de foreach |#iterator] .[table-latte-tags language-latte] -|## Incluyendo otras Plantillas -| `{include 'file.latte'}` | [incluye una plantilla de otro archivo |#include] -| `{sandbox 'file.latte'}` | [incluye una plantilla en modo sandbox |#sandbox] +|## Inclusión de otras plantillas +| `{include 'file.latte'}` | [carga una plantilla desde otro archivo |#include] +| `{sandbox 'file.latte'}` | [carga una plantilla en modo sandbox |#sandbox] .[table-latte-tags language-latte] -|## Bloques, diseños, herencia de plantillas -| `{block}` | [bloque anónimo |#block] -| `{block blockname}` | [definición de bloque |template-inheritance#blocks] -| `{define blockname}` | [definición de bloque para uso futuro |template-inheritance#definitions] -| `{include blockname}` | [imprime un bloque |template-inheritance#printing-blocks] -| `{include blockname from 'file.latte'}` | [imprime un bloque desde un archivo |template-inheritance#printing-blocks] -| `{import 'file.latte'}` | [carga bloques de otra plantilla |template-inheritance#horizontal-reuse] -| `{layout 'file.latte'}` / `{extends}` | [especifica un archivo de diseño |template-inheritance#layout-inheritance] -| `{embed}`... `{/embed}` | [carga la plantilla o el bloque y permite sobrescribir los bloques|template-inheritance#unit-inheritance] -| `{ifset blockname}`... `{/ifset}` | [condiciona si el bloque está definido|template-inheritance#checking-block-existence] +|## Bloques, layouts, herencia de plantillas +| `{block}` | [bloque anónimo |#block] +| `{block blockname}` | [define un bloque |template-inheritance#Bloques block] +| `{define blockname}` | [define un bloque para uso posterior |template-inheritance#Definiciones define] +| `{include blockname}` | [renderizado de bloque |template-inheritance#Renderizado de bloques include] +| `{include blockname from 'file.latte'}` | [renderiza un bloque desde un archivo |template-inheritance#Renderizado de bloques include] +| `{import 'file.latte'}` | [carga bloques desde una plantilla |template-inheritance#Reutilización horizontal import] +| `{layout 'file.latte'}` / `{extends}` | [especifica el archivo de layout |template-inheritance#Herencia de layout layout] +| `{embed}` … `{/embed}` | [carga una plantilla o bloque y permite sobrescribir bloques |template-inheritance#Herencia de unidades embed] +| `{ifset blockname}` … `{/ifset}` | [condición si existe un bloque |template-inheritance#Comprobación de existencia de bloques ifset] .[table-latte-tags language-latte] |## Manejo de excepciones -| `{try}`... `{else}`... `{/try}` | [captura de excepciones |#try] -| `{rollback}` | [descarta el bloque try|#rollback] +| `{try}` … `{else}` … `{/try}` | [captura de excepciones |#try] +| `{rollback}` | [descarte del bloque try |#rollback] .[table-latte-tags language-latte] |## Variables -| `{var $foo = value}` | [creación de variables|#var-default] -| `{default $foo = value}` | [valor por defecto cuando la variable no está declarada |#var-default] -| `{parameters}` | [declara variables, escribe un valor por defecto|#parameters] -| `{capture}`... `{/capture}` | [captura una sección a una variable|#capture] +| `{var $foo = value}` | [crea una variable |#var default] +| `{default $foo = value}` | [crea una variable si no existe |#var default] +| `{parameters}` | [declara variables, tipos y valores por defecto |#parameters] +| `{capture}` … `{/capture}` | [captura un bloque en una variable |#capture] .[table-latte-tags language-latte] |## Tipos -| `{varType}` | [declara tipo de variable|type-system#varType] -| `{varPrint}` | [sugiere tipos de variables|type-system#varPrint] -| `{templateType}` | [declara tipos de variables usando clase|type-system#templateType] -| `{templatePrint}` | [genera clase con propiedades |type-system#templatePrint] +| `{varType}` | [declara el tipo de variable |type-system#varType] +| `{varPrint}` | [sugiere tipos de variables |type-system#varPrint] +| `{templateType}` | [declara tipos de variables según una clase |type-system#templateType] +| `{templatePrint}` | [sugiere una clase con tipos de variables |type-system#templatePrint] .[table-latte-tags language-latte] -|## Traducción -| `{_string}` | [imprime traducido |#Translation] -| `{translate}`... `{/translate}` | [traduce el contenido |#Translation] +|## Traducciones +| `{_...}` | [imprime la traducción |#Traducciones] +| `{translate}` … `{/translate}` | [traduce el contenido |#Traducciones] .[table-latte-tags language-latte] |## Otros -| `{contentType}` | [cambia el modo de escape y envía la cabecera HTTP |#contenttype] -| `{debugbreak}` | [establece un punto de interrupción en el código |#debugbreak] -| `{do}` | [evalúa una expresión sin imprimirla |#do] -| `{dump}` | [vuelca variables a la Tracy Bar |#dump] -| `{spaceless}`... `{/spaceless}` | [elimina los espacios en blanco innecesarios |#spaceless]. -| `{syntax}` | [cambia la sintaxis en tiempo de ejecución |#syntax] -| `{trace}` | [muestra la traza de la pila |#trace] +| `{contentType}` | [cambia el escapado y envía la cabecera HTTP |#contentType] +| `{debugbreak}` | [coloca un breakpoint en el código |#debugbreak] +| `{do}` | [ejecuta código, pero no imprime nada |#do] +| `{dump}` | [vuelca variables a la Tracy Bar |#dump] +| `{php}` | [ejecuta cualquier código PHP |#php] +| `{spaceless}` … `{/spaceless}` | [elimina espacios superfluos |#spaceless] +| `{syntax}` | [cambio de sintaxis en tiempo de ejecución |#syntax] +| `{trace}` | [muestra el stack trace |#trace] .[table-latte-tags language-latte] -|## Ayudantes de etiquetas HTML -| `n:class` | [atributo de clase inteligente|#n:class] -| `n:attr` | [atributos HTML inteligentes|#n:attr] -| `n:tag` | [nombre dinámico del elemento HTML|#n:tag] -| `n:ifcontent` | [Omitir etiqueta HTML vacía |#n:ifcontent] +|## Ayudantes del codificador HTML +| `n:class` | [escritura dinámica del atributo HTML class |#n:class] +| `n:attr` | [escritura dinámica de cualquier atributo HTML |#n:attr] +| `n:tag` | [escritura dinámica del nombre del elemento HTML |#n:tag] +| `n:ifcontent` | [omite la etiqueta HTML vacía |#n:ifcontent] .[table-latte-tags language-latte] -|## Disponible sólo en Nette Framework -| `n:href` | [enlace en elementos HTML `` |application:creating-links#In the Presenter Template] -| `{link}` | [imprime un enlace |application:creating-links#In the Presenter Template] -| `{plink}` | [imprime un enlace a un [presentador |application:creating-links#In the Presenter Template] -| `{control}` | [imprime un componente |application:components#Rendering] -| `{snippet}`... `{/snippet}` | [un fragmento de plantilla que puede ser enviado por AJAX |application:ajax#tag-snippet] -| `{snippetArea}` | snippets sobre -| `{cache}`... `{/cache}` | [almacena en caché una sección de plantilla|caching:#caching-in-latte] +|## Disponible solo en Nette Framework +| `n:href` | [enlace utilizado en elementos HTML `` |application:creating-links#En la plantilla del presenter] +| `{link}` | [imprime un enlace |application:creating-links#En la plantilla del presenter] +| `{plink}` | [imprime un enlace a un presenter |application:creating-links#En la plantilla del presenter] +| `{control}` | [renderiza un componente |application:components#Renderizado] +| `{snippet}` … `{/snippet}` | [fragmento que se puede enviar por AJAX |application:ajax#Fragmentos en Latte] +| `{snippetArea}` | [contenedor para fragmentos |application:ajax#Áreas de fragmentos] +| `{cache}` … `{/cache}` | [cachea parte de la plantilla |caching:#Almacenamiento en caché en Latte] .[table-latte-tags language-latte] -|## Disponible sólo con Nette Forms -| `{form}`... `{/form}` | [imprime un elemento de formulario|forms:rendering#form] -| `{label}`... `{/label}` | [imprime una etiqueta de entrada de formulario|forms:rendering#label-input] -| `{input}` | [imprime un elemento del formulario|forms:rendering#label-input] -| `{inputError}` | [imprime un mensaje de error para el elemento de entrada del formulario|forms:rendering#inputError] -| `n:name` | [activa un elemento de entrada HTML |forms:rendering#n:name] -| `{formPrint}` | [genera un modelo de formulario Latte |forms:rendering#formPrint] -| `{formPrintClass}` | [imprime la clase PHP para los datos del formulario |forms:in-presenter#mapping-to-classes] -| `{formContext}`... `{/formContext}` | [renderización parcial del formulario|forms:rendering#special-cases] +|## Disponible solo con Nette Forms +| `{form}` … `{/form}` | [renderiza las etiquetas del formulario |forms:rendering#form] +| `{label}` … `{/label}` | [renderiza la etiqueta de un elemento de formulario |forms:rendering#label input] +| `{input}` | [renderiza un elemento de formulario |forms:rendering#label input] +| `{inputError}` | [imprime el mensaje de error de un elemento de formulario |forms:rendering#inputError] +| `n:name` | [activa un elemento de formulario |forms:rendering#n:name] +| `{formContainer}` … `{/formContainer}` | [renderizado de un contenedor de formulario |forms:rendering#Casos especiales] + +.[table-latte-tags language-latte] +|## Disponible sólo con Nette Assets +| `{asset}` | [renderiza un activo como elemento HTML o URL |assets:#asset] +| `{preload}` | [genera sugerencias de precarga para optimizar el rendimiento |assets:#preload] +| `n:asset` | [añade atributos de activos a elementos HTML |assets:#n:asset] -Impresión .[#toc-printing] -========================== +Impresión +========= `{$var}` `{...}` `{=...}` ------------------------- -Latte utiliza la etiqueta `{=...}` para imprimir cualquier expresión en la salida. Si la expresión comienza con una variable o llamada a una función, no hay necesidad de escribir un signo igual. Lo que en la práctica significa que casi nunca es necesario escribirlo: +En Latte se utiliza la etiqueta `{=...}` para imprimir cualquier expresión en la salida. Latte se preocupa por su comodidad, por lo que si la expresión comienza con una variable o una llamada de función, no es necesario escribir el signo igual. Lo que en la práctica significa que casi nunca es necesario escribirlo: ```latte -Name: {$name} {$surname}
                                                              -Age: {date('Y') - $birth}
                                                              +Nombre: {$name} {$surname}
                                                              +Edad: {date('Y') - $birth}
                                                              ``` -Puedes escribir cualquier cosa que conozcas de PHP como una expresión. Simplemente no tienes que aprender un nuevo lenguaje. Por ejemplo: +Como expresión puede escribir cualquier cosa que conozca de PHP. Simplemente no tiene que aprender un nuevo lenguaje. Por ejemplo: ```latte {='0' . ($num ?? $num * 3) . ', ' . PHP_VERSION} ``` -Por favor, no busque ningún significado en el ejemplo anterior, pero si encuentra alguno, escríbanos :-) +Por favor, no busque ningún sentido en el ejemplo anterior, pero si encuentra alguno, escríbanos :-) -Salida de escape .[#toc-escaping-output] ----------------------------------------- +Escapado de la salida +--------------------- -¿Cuál es la tarea más importante de un sistema de plantillas? Evitar agujeros de seguridad. Y eso es exactamente lo que Latte hace cada vez que imprimes algo a la salida. Automáticamente escapa todo: +¿Cuál es la tarea más importante de un sistema de plantillas? Prevenir agujeros de seguridad. Y eso es exactamente lo que hace Latte siempre que imprime algo. Lo escapa automáticamente: ```latte -

                                                              {='one < two'}

                                                              {* prints: '

                                                              one < two

                                                              ' *} +

                                                              {='one < two'}

                                                              {* imprime: '

                                                              one < two

                                                              ' *} ``` -Para ser precisos, Latte utiliza el escape sensible al contexto, que es una característica tan importante y única que le hemos dedicado [un capítulo aparte|safety-first#context-aware-escaping]. +Para ser precisos, Latte utiliza el [escapado sensible al contexto |safety-first#Escape sensible al contexto], que es algo tan importante y único que le hemos dedicado un capítulo aparte. -¿Y si imprimes contenido codificado en HTML de una fuente de confianza? Entonces puede desactivar fácilmente el escape: +¿Y qué pasa si imprime contenido codificado en HTML de una fuente confiable? Entonces se puede desactivar fácilmente el escapado: ```latte {$trustedHtmlString|noescape} ``` .[warning] -El uso incorrecto del filtro `noescape` puede dar lugar a una vulnerabilidad XSS. Nunca lo utilices a menos que estés **absolutamente seguro** de lo que estás haciendo y de que la cadena que estás imprimiendo proviene de una fuente de confianza. +¡El uso incorrecto del filtro `noescape` puede llevar a una vulnerabilidad XSS! Nunca lo use si no está **completamente seguro** de lo que está haciendo y de que la cadena que se imprime proviene de una fuente confiable. -Impresión en JavaScript .[#toc-printing-in-javascript] ------------------------------------------------------- +Impresión en JavaScript +----------------------- -Gracias al escape sensible al contexto, es maravillosamente fácil imprimir variables dentro de JavaScript, y Latte las escapará correctamente. +Gracias al escapado sensible al contexto, es maravillosamente fácil imprimir variables dentro de JavaScript y Latte se encarga del escapado correcto. -La variable no tiene por qué ser una cadena, cualquier tipo de datos es compatible, que luego se codifica como JSON: +La variable no tiene por qué ser solo una cadena, se admite cualquier tipo de dato, que luego se codifica como JSON: ```latte {var $foo = ['hello', true, 1]} @@ -179,7 +185,7 @@ Genera: ``` -Esta es también la razón por la que **no ponga la variable entre comillas**: Latte las añade alrededor de las cadenas. Y si quieres poner una variable de cadena dentro de otra cadena, simplemente concaténalas: +Esa es también la razón por la que **no se escriben comillas** alrededor de la variable: Latte las añade por sí mismo para las cadenas. Y si quisiera insertar una variable de cadena en otra cadena, simplemente concaténelas: ```latte ``` -Filtros .[#toc-filters] ------------------------ +Filtros +------- -La expresión impresa puede modificarse [mediante |syntax#filters] filtros. Por ejemplo, este ejemplo convierte la cadena a mayúsculas y la acorta a un máximo de 30 caracteres: +La expresión impresa puede ser modificada por un [filtro |syntax#Filtros]. Así, por ejemplo, convertimos una cadena a mayúsculas y la acortamos a un máximo de 30 caracteres: ```latte {$string|upper|truncate:30} ``` -También puede aplicar filtros a partes de una expresión de la siguiente manera: +También puede usar filtros en partes parciales de la expresión de esta manera: ```latte {$left . ($middle|upper) . $right} ``` -Condiciones .[#toc-conditions] -============================== +Condiciones +=========== `{if}` `{elseif}` `{else}` -------------------------- -Las condiciones se comportan de la misma manera que sus equivalentes en PHP. Puedes usar las mismas expresiones que conoces de PHP, no tienes que aprender un nuevo lenguaje. +Las condiciones se comportan igual que sus contrapartes en PHP. Puede usar en ellas las mismas expresiones que conoce de PHP, no tiene que aprender un nuevo lenguaje. ```latte {if $product->inStock > Stock::Minimum} - In stock + En stock {elseif $product->isOnWay()} - On the way + En camino {else} - Not available + No disponible {/if} ``` -Como cualquier etiqueta de par, un par de `{if} ... {/ if}` puede escribirse como [n:atributo |syntax#n:attributes], por ejemplo: +Como cualquier etiqueta par, el par `{if} ... {/if}` también se puede escribir en forma de [n:atributo |syntax#n:atributos], por ejemplo: + +```latte +

                                                              En stock {$count} unidades

                                                              +``` + +¿Sabía que a los n:atributos puede adjuntar el prefijo `tag-`? Entonces la condición se referirá solo a la impresión de las etiquetas HTML y el contenido entre ellas se imprimirá siempre: ```latte -

                                                              In stock {$count} items

                                                              +
                                                              Hola + +{* imprime 'Hola' cuando $clickable es false *} +{* imprime 'Hola' cuando $clickable es true *} ``` -¿Sabe que puede añadir el prefijo `tag-` a n:atributos? Entonces la condición sólo afectará a las etiquetas HTML y el contenido entre ellas se imprimirá siempre: +Genial. + + +`n:else` .{data-version:3.0.11} +------------------------------- + +Si escribe la condición `{if} ... {/if}` en forma de [n:atributo |syntax#n:atributos], tiene la opción de indicar también una rama alternativa usando `n:else`: ```latte -Hello +En stock {$count} unidades -{* prints 'Hello' when $clickable is falsey *} -{* prints 'Hello' when $clickable is truthy *} +no disponible ``` -Bien. +El atributo `n:else` también se puede usar en pareja con [`n:ifset` |#ifset elseifset], [`n:foreach` |#foreach], [`n:try` |#try], [#`n:ifcontent`] y [`n:ifchanged` |#ifchanged]. `{/if $cond}` ------------- -Quizá le sorprenda que la expresión de la condición `{if}` también pueda especificarse en la etiqueta final. Esto es útil en situaciones en las que aún no conocemos el valor de la condición cuando se abre la etiqueta. Llamémoslo decisión diferida. +Quizás le sorprenda que la expresión en la condición `{if}` también se pueda indicar en la etiqueta de cierre. Esto es útil en situaciones en las que al abrir la condición aún no conocemos su valor. Llamémoslo decisión diferida. -Por ejemplo, empezamos a listar una tabla con registros de la base de datos, y sólo después de completar el informe nos damos cuenta de que no había ningún registro en la base de datos. Entonces ponemos la condición en la etiqueta final `{/if}` y si no hay ningún registro, no se imprimirá ninguno: +Por ejemplo, comenzamos a imprimir una tabla con registros de una base de datos y solo después de finalizar la impresión nos damos cuenta de que no había ningún registro en la base de datos. Entonces ponemos una condición en la etiqueta final `{/if}` y si no hay ningún registro, no se imprimirá nada de eso: ```latte {if} -

                                                              Printing rows from the database

                                                              +

                                                              Listado de filas de la base de datos

                                                              {foreach $resultSet as $row} @@ -264,30 +284,30 @@ Por ejemplo, empezamos a listar una tabla con registros de la base de datos, y s {/if isset($row)} ``` -Práctico, ¿verdad? +Ingenioso, ¿verdad? -También puede utilizar `{else}` en la condición diferida, pero no `{elseif}`. +En la condición diferida también se puede usar `{else}`, pero no `{elseif}`. `{ifset}` `{elseifset}` ----------------------- .[note] -Véase también [`{ifset block}` |template-inheritance#checking-block-existence] +Vea también [`{ifset block}` |template-inheritance#Comprobación de existencia de bloques ifset] -Utilice la condición `{ifset $var}` para determinar si una variable (o múltiples variables) existe y tiene un valor no nulo. En realidad es lo mismo que `if (isset($var))` en PHP. Como cualquier etiqueta de par, esto puede ser escrito en la forma de [n:attribute |syntax#n:attributes], así que vamos a mostrarlo en el ejemplo: +Usando la condición `{ifset $var}` averiguamos si una variable (o varias variables) existe y tiene un valor no *null*. En realidad, es lo mismo que `if (isset($var))` en PHP. Como cualquier etiqueta par, también se puede escribir en forma de [n:atributo |syntax#n:atributos], así que mostrémoslo como ejemplo: ```latte - + ``` -`{ifchanged}` .{data-version:2.9} ---------------------------------- +`{ifchanged}` +------------- -`{ifchanged}` comprueba si el valor de una variable ha cambiado desde la última iteración del bucle (foreach, for o while). +`{ifchanged}` comprueba si el valor de una variable ha cambiado desde la última iteración en el bucle (foreach, for o while). -Si especificamos una o más variables en la etiqueta, comprobará si alguna de ellas ha cambiado e imprime el contenido en consecuencia. Por ejemplo, el siguiente ejemplo imprime la primera letra de un nombre como encabezado cada vez que cambia al listar nombres: +Si indicamos una o más variables en la etiqueta, comprobará si alguna de ellas ha cambiado y, en consecuencia, imprimirá el contenido. Por ejemplo, el siguiente ejemplo imprime la primera letra del nombre como título cada vez que cambia al imprimir los nombres: ```latte {foreach ($names|sort) as $name} @@ -297,7 +317,7 @@ Si especificamos una o más variables en la etiqueta, comprobará si alguna de e {/foreach} ``` -Sin embargo, si no se da ningún argumento, el contenido renderizado se comprobará con su estado anterior. Esto significa que en el ejemplo anterior, podemos omitir con seguridad el argumento en la etiqueta. Y, por supuesto, también podemos utilizar [n:attribute |syntax#n:attributes]: +Sin embargo, si no indicamos ningún argumento, se comprobará el contenido renderizado en comparación con su estado anterior. Esto significa que en el ejemplo anterior podemos omitir tranquilamente el argumento en la etiqueta. Y, por supuesto, también podemos usar un [n:atributo |syntax#n:atributos]: ```latte {foreach ($names|sort) as $name} @@ -307,49 +327,49 @@ Sin embargo, si no se da ningún argumento, el contenido renderizado se comproba {/foreach} ``` -También se puede incluir una cláusula `{else}` dentro de `{ifchanged}`. +Dentro de `{ifchanged}` también se puede indicar la cláusula `{else}`. `{switch}` `{case}` `{default}` ------------------------------- -Compara el valor con múltiples opciones. Es similar a la estructura `switch` que conoce de PHP. Sin embargo, Latte la mejora: +Compara un valor con múltiples opciones. Es análogo a la sentencia condicional `switch` que conoce de PHP. Sin embargo, Latte lo mejora: - utiliza comparación estricta (`===`) -- no necesita un `break` +- no necesita `break` -Así que es el equivalente exacto de la estructura `match` con la que viene PHP 8.0. +Es, por tanto, el equivalente exacto de la estructura `match` que introduce PHP 8.0. ```latte {switch $transport} {case train} - By train + En tren {case plane} - By plane + En avión {default} - Differently + De otra manera {/switch} ``` -.{data-version:2.9} + La cláusula `{case}` puede contener múltiples valores separados por comas: ```latte {switch $status} -{case $status::New}new item -{case $status::Sold, $status::Unknown}not available +{case $status::New}nuevo elemento +{case $status::Sold, $status::Unknown}no disponible {/switch} ``` -Bucles .[#toc-loops] -==================== +Bucles +====== -En Latte, todos los bucles que conoces de PHP están a tu disposición: foreach, for y while. +En Latte encontrará todos los bucles que conoce de PHP: foreach, for y while. `{foreach}` ----------- -El ciclo se escribe exactamente igual que en PHP: +Escribimos el bucle exactamente igual que en PHP: ```latte {foreach $langs as $code => $lang} @@ -357,11 +377,11 @@ El ciclo se escribe exactamente igual que en PHP: {/foreach} ``` -Además, tiene algunos ajustes útiles de los que hablaremos ahora. +Además, tiene varias características ingeniosas de las que hablaremos ahora. -Por ejemplo, Latte comprueba que las variables creadas no sobrescriban accidentalmente las variables globales del mismo nombre. Esto te salvará cuando asumas que `$lang` es el idioma actual de la página, y no te des cuenta de que `foreach $langs as $lang` ha sobrescrito esa variable. +Latte, por ejemplo, comprueba si las variables creadas sobrescriben accidentalmente variables globales del mismo nombre. Esto salva situaciones en las que cuenta con que `$lang` contiene el idioma actual de la página y no se da cuenta de que `foreach $langs as $lang` le ha sobrescrito esa variable. -El bucle foreach también puede escribirse de forma muy elegante y económica con [n:attribute |syntax#n:attributes]: +El bucle foreach también se puede escribir de forma muy elegante y económica mediante un [n:atributo |syntax#n:atributos]: ```latte
                                                                @@ -369,7 +389,7 @@ El bucle foreach también puede escribirse de forma muy elegante y económica co
                                                              ``` -¿Sabía que puede anteponer el prefijo `inner-` a n:attributes? Ahora sólo se repetirá en el bucle la parte interior del elemento: +¿Sabía que a los n:atributos puede adjuntar el prefijo `inner-`? Entonces solo se repetirá el interior del elemento en el bucle: ```latte
                                                              @@ -378,7 +398,7 @@ El bucle foreach también puede escribirse de forma muy elegante y económica co
                                                              ``` -Así que imprime algo como: +Así que se imprimirá algo como: ```latte
                                                              @@ -390,17 +410,17 @@ Así que imprime algo como: ``` -`{else}` .{data-version:2.9}{toc: foreach-else} ------------------------------------------------ +`{else}` .{toc: foreach-else} +----------------------------- -El bucle `foreach` puede tomar una cláusula opcional `{else}` cuyo texto se muestra si la matriz dada está vacía: +Dentro del bucle `foreach` se puede indicar la cláusula `{else}`, cuyo contenido se mostrará si el bucle está vacío: ```latte
                                                                {foreach $people as $person}
                                                              • {$person->name}
                                                              • {else} -
                                                              • Sorry, no users in this list
                                                              • +
                                                              • Lo sentimos, no hay usuarios en esta lista
                                                              • {/foreach}
                                                              ``` @@ -409,17 +429,17 @@ El bucle `foreach` puede tomar una cláusula opcional `{else}` cuyo texto se mue `$iterator` ----------- -Dentro del bucle `foreach` se inicializa la variable `$iterator`. Contiene información importante sobre el bucle actual. +Dentro del bucle `foreach`, Latte crea la variable `$iterator`, mediante la cual podemos averiguar información útil sobre el bucle en curso: -- `$iterator->first` - ¿es ésta la primera iteración? -- `$iterator->last` - ¿es la última iteración? -- `$iterator->counter` - contador de iteraciones, comienza en 1 -- `$iterator->counter0` - contador de iteraciones, comienza en 0 .{data-version:2.9} -- `$iterator->odd` - ¿es esta iteración impar? -- `$iterator->even` - ¿es esta iteración par? -- `$iterator->parent` - el iterador que rodea al actual .{data-version:2.9} +- `$iterator->first` - ¿es la primera pasada por el bucle? +- `$iterator->last` - ¿es la última pasada? +- `$iterator->counter` - ¿cuántas pasadas van contadas desde uno? +- `$iterator->counter0` - ¿cuántas pasadas van contadas desde cero? +- `$iterator->odd` - ¿es una pasada impar? +- `$iterator->even` - ¿es una pasada par? +- `$iterator->parent` - el iterador que envuelve al actual - `$iterator->nextValue` - el siguiente elemento en el bucle -- `$iterator->nextKey` - la clave del siguiente elemento del bucle +- `$iterator->nextKey` - la clave del siguiente elemento en el bucle ```latte @@ -435,20 +455,19 @@ Dentro del bucle `foreach` se inicializa la variable `$iterator`. Contiene infor {/foreach} ``` -El latte es inteligente y `$iterator->last` funciona no sólo para matrices, sino también cuando el bucle se ejecuta sobre un iterador general donde el número de elementos no se conoce de antemano. +Latte es astuto y `$iterator->last` funciona no solo con arrays, sino también cuando el bucle se ejecuta sobre un iterador general donde no se conoce de antemano el número de elementos. `{first}` `{last}` `{sep}` -------------------------- -Estas etiquetas pueden utilizarse dentro del bucle `{foreach}`. El contenido de `{first}` se renderiza en la primera pasada. -El contenido de `{last}` se renderiza... ¿lo adivina? Sí, en la última pasada. En realidad son atajos para `{if $iterator->first}` y `{if $iterator->last}`. +Estas etiquetas se pueden usar dentro del bucle `{foreach}`. El contenido de `{first}` se renderizará si es la primera pasada. El contenido de `{last}` se renderizará… ¿adivina? Sí, si es la última pasada. En realidad, son atajos para `{if $iterator->first}` y `{if $iterator->last}`. -Las etiquetas también pueden escribirse como [n:attributes |syntax#n:attributes]: +Las etiquetas también se pueden usar elegantemente como [n:atributo |syntax#n:atributos]: ```latte {foreach $rows as $row} - {first}

                                                              List of names

                                                              {/first} + {first}

                                                              Lista de nombres

                                                              {/first}

                                                              {$row->name}

                                                              @@ -456,21 +475,21 @@ Las etiquetas también pueden escribirse como [n:attributes |syntax#n:attributes {/foreach} ``` -El contenido de `{sep}` se muestra si la iteración no es la última, por lo que es adecuado para imprimir delimitadores, como comas entre elementos de la lista: +El contenido de la etiqueta `{sep}` se renderizará si la pasada no es la última, por lo que es útil para renderizar separadores, por ejemplo, comas entre los elementos impresos: ```latte {foreach $items as $item} {$item} {sep}, {/sep} {/foreach} ``` -Es bastante práctico, ¿no? +Es bastante práctico, ¿verdad? -`{iterateWhile}` .{data-version:2.10} -------------------------------------- +`{iterateWhile}` +---------------- -Simplifica la agrupación de datos lineales durante la iteración en un bucle foreach realizando la iteración en un bucle anidado mientras se cumpla la condición. [Lee las instrucciones en el cookbook |cookbook/iteratewhile]. +Simplifica la agrupación de datos lineales durante la iteración en un bucle foreach realizando la iteración en un bucle anidado mientras se cumpla la condición. [Lea el tutorial detallado|cookbook/grouping]. -También puede sustituir elegantemente a `{first}` y `{last}` en el ejemplo anterior: +También puede reemplazar elegantemente `{first}` y `{last}` en el ejemplo anterior: ```latte {foreach $rows as $row} @@ -487,19 +506,21 @@ También puede sustituir elegantemente a `{first}` y `{last}` en el ejemplo ante {/foreach} ``` +Vea también los filtros [batch |filters#batch] y [group |filters#group]. + `{for}` ------- -Escribimos el ciclo exactamente igual que en PHP: +Escribimos el bucle exactamente igual que en PHP: ```latte {for $i = 0; $i < 10; $i++} - Item #{$i} + Elemento {$i} {/for} ``` -La etiqueta también se puede escribir como [n:attribute |syntax#n:attributes]: +La etiqueta también se puede usar como [n:atributo |syntax#n:atributos]: ```latte

                                                              {$i}

                                                              @@ -509,7 +530,7 @@ La etiqueta también se puede escribir como [n:attribute |syntax#n:attributes]: `{while}` --------- -De nuevo, escribimos el ciclo exactamente igual que en PHP: +Nuevamente, escribimos el bucle exactamente igual que en PHP: ```latte {while $row = $result->fetch()} @@ -517,7 +538,7 @@ De nuevo, escribimos el ciclo exactamente igual que en PHP: {/while} ``` -O como [n:attribute |syntax#n:attributes]: +O como [n:atributo |syntax#n:atributos]: ```latte @@ -525,7 +546,7 @@ O como [n:attribute |syntax#n:attributes]: ``` -Una variante con una condición en la etiqueta final corresponde al bucle do-while en PHP: +También es posible una variante con la condición en la etiqueta final, que corresponde en PHP al bucle do-while: ```latte {while} @@ -537,7 +558,7 @@ Una variante con una condición en la etiqueta final corresponde al bucle do-whi `{continueIf}` `{skipIf}` `{breakIf}` ------------------------------------- -Hay etiquetas especiales que puedes utilizar para controlar cualquier bucle - `{continueIf ?}` y `{breakIf ?}` que saltan a la siguiente iteración y terminan el bucle, respectivamente, si se cumplen las condiciones: +Para controlar cualquier bucle se pueden usar las etiquetas `{continueIf ?}` y `{breakIf ?}`, que pasan al siguiente elemento o terminan el bucle respectivamente si se cumple la condición: ```latte {foreach $rows as $row} @@ -547,8 +568,8 @@ Hay etiquetas especiales que puedes utilizar para controlar cualquier bucle - `{ {/foreach} ``` -.{data-version:2.9} -La etiqueta `{skipIf}` es muy similar a `{continueIf}`, pero no incrementa el contador. Así no hay agujeros en la numeración cuando se imprime `$iterator->counter` y se saltan algunos elementos. Además, la cláusula {else} se mostrará cuando se salten todos los elementos. + +La etiqueta `{skipIf}` es muy similar a `{continueIf}`, pero no incrementa el contador `$iterator->counter`, por lo que si lo imprimimos y al mismo tiempo saltamos algunos elementos, no habrá huecos en la numeración. Y también la cláusula `{else}` se renderizará si saltamos todos los elementos. ```latte
                                                                @@ -556,7 +577,7 @@ La etiqueta `{skipIf}` es muy similar a `{continueIf}`, pero no incrementa el co {skipIf $person->age < 18}
                                                              • {$iterator->counter}. {$person->name}
                                                              • {else} -
                                                              • Sorry, no adult users in this list
                                                              • +
                                                              • Lo sentimos, no hay adultos en esta lista
                                                              • {/foreach}
                                                              ``` @@ -565,72 +586,68 @@ La etiqueta `{skipIf}` es muy similar a `{continueIf}`, pero no incrementa el co `{exitIf}` .{data-version:3.0.5} -------------------------------- -Finaliza la renderización de una plantilla o bloque cuando se cumple una condición (es decir, "salida anticipada"). +Termina el renderizado de la plantilla o bloque si se cumple la condición (llamado "early exit"). ```latte {exitIf !$messages} -

                                                              Messages

                                                              +

                                                              Mensajes

                                                              {$message}
                                                              ``` -Inclusión de plantillas .[#toc-including-templates] -=================================================== +Inclusión de plantillas +======================= `{include 'file.latte'}` .{toc: include} ---------------------------------------- .[note] -Véase también [`{include block}` |template-inheritance#printing-blocks] +Vea también [`{include block}` |template-inheritance#Renderizado de bloques include] -La etiqueta `{include}` carga y renderiza la plantilla especificada. En nuestro lenguaje PHP favorito es como: +La etiqueta `{include}` carga y renderiza la plantilla especificada. Si habláramos en el lenguaje de nuestro querido lenguaje PHP, sería algo como: ```php ``` -Las plantillas incluidas no tienen acceso a las variables del contexto activo, pero tienen acceso a las variables globales. +Las plantillas incluidas no tienen acceso a las variables del contexto activo, solo tienen acceso a las variables globales. -Puede pasar variables de esta manera: +Puede pasar variables a la plantilla incluida de esta manera: ```latte -{* since Latte 2.9 *} {include 'template.latte', foo: 'bar', id: 123} - -{* before Latte 2.9 *} -{include 'template.latte', foo => 'bar', id => 123} ``` -El nombre de la plantilla puede ser cualquier expresión PHP: +El nombre de la plantilla puede ser cualquier expresión en PHP: ```latte {include $someVar} {include $ajax ? 'ajax.latte' : 'not-ajax.latte'} ``` -El contenido insertado puede ser modificado usando [filtros |syntax#filters]. El siguiente ejemplo elimina todo el material HTML y ajusta las mayúsculas y minúsculas: +El contenido incluido se puede modificar mediante [filtros |syntax#Filtros]. El siguiente ejemplo elimina todo el HTML y ajusta las mayúsculas/minúsculas: ```latte {include 'heading.latte' |stripHtml|capitalize} ``` -La [herencia de la |template inheritance] plantilla **no interviene** en esto por defecto. Aunque puede añadir etiquetas de bloque a las plantillas que se incluyen, no sustituirán a los bloques coincidentes de la plantilla en la que se incluyen. Piense en los includes como partes independientes y blindadas de páginas o módulos. Este comportamiento puede cambiarse usando el modificador `with blocks` (desde Latte 2.9.1): +Por defecto, la [herencia de plantillas|template-inheritance] no figura de ninguna manera en este caso. Aunque podemos usar bloques en la plantilla incluida, no se reemplazarán los bloques correspondientes en la plantilla en la que se incluye. Piense en las plantillas incluidas como partes independientes y aisladas de páginas o módulos. Este comportamiento se puede cambiar usando el modificador `with blocks`: ```latte {include 'template.latte' with blocks} ``` -La relación entre el nombre de archivo especificado en la etiqueta y el archivo en disco es una cuestión de [cargador |extending-latte#Loaders]. +La relación entre el nombre de archivo especificado en la etiqueta y el archivo en el disco es asunto del [loader|loaders]. -`{sandbox}` .{data-version:2.8} -------------------------------- +`{sandbox}` +----------- -Cuando incluya una plantilla creada por un usuario final, debería considerar la posibilidad de ponerla en un sandbox (más información en [la documentación sobre sandbox |sandbox]): +Al incluir una plantilla creada por el usuario final, debería considerar el modo sandbox (más información en la [documentación del sandbox |sandbox]): ```latte {sandbox 'untrusted.latte', level: 3, data: $menu} @@ -641,9 +658,9 @@ Cuando incluya una plantilla creada por un usuario final, debería considerar la ========= .[note] -Véase también [`{block name}` |template-inheritance#blocks] +Vea también [`{block name}` |template-inheritance#Bloques block] -Los bloques sin nombre sirven para aplicar [filtros |syntax#filters] a una parte de la plantilla. Por ejemplo, puede aplicar un filtro de [franja |filters#strip] para eliminar los espacios innecesarios: +Los bloques sin nombre sirven como una forma de aplicar [filtros |syntax#Filtros] a una parte de la plantilla. Por ejemplo, así se puede aplicar el filtro [strip |filters#spaceless], que elimina los espacios innecesarios: ```latte {block|strip} @@ -654,16 +671,16 @@ Los bloques sin nombre sirven para aplicar [filtros |syntax#filters] a una parte ``` -Manejo de excepciones .[#toc-exception-handling] -================================================ +Manejo de excepciones +===================== -`{try}` .{data-version:2.9} ---------------------------- +`{try}` +------- -Estas etiquetas facilitan enormemente la creación de plantillas robustas. +Gracias a esta etiqueta, es extremadamente fácil crear plantillas robustas. -Si se produce una excepción mientras se renderiza el bloque `{try}`, se desecha todo el bloque y la renderización continuará después: +Si ocurre una excepción durante el renderizado del bloque `{try}`, todo el bloque se descarta y el renderizado continuará después de él: ```latte {try} @@ -675,7 +692,7 @@ Si se produce una excepción mientras se renderiza el bloque `{try}`, se desecha {/try} ``` -El contenido de la cláusula opcional `{else}` sólo se renderiza cuando se produce una excepción: +El contenido en la cláusula opcional `{else}` se renderizará solo si ocurre una excepción: ```latte {try} @@ -685,11 +702,11 @@ El contenido de la cláusula opcional `{else}` sólo se renderiza cuando se prod {/foreach} {else} -

                                                              Sorry, the tweets could not be loaded.

                                                              +

                                                              Lo sentimos, no se pudieron cargar los tweets.

                                                              {/try} ``` -La etiqueta también puede escribirse como [n:attribute |syntax#n:attributes]: +La etiqueta también se puede usar como [n:atributo |syntax#n:atributos]: ```latte
                                                                @@ -697,13 +714,13 @@ La etiqueta también puede escribirse como [n:attribute |syntax#n:attributes]:
                                                              ``` -También es posible definir [un gestor de excepciones propio |develop#exception handler] para, por ejemplo, el registro: +También es posible definir un [manejador de excepciones |develop#Manejador de excepciones] personalizado, por ejemplo, para el registro. -`{rollback}` .{data-version:2.9} --------------------------------- +`{rollback}` +------------ -El bloque `{try}` también puede detenerse y omitirse manualmente utilizando `{rollback}`. Así no es necesario comprobar todos los datos de entrada de antemano, y sólo durante el renderizado se puede decidir si tiene sentido renderizar el objeto. +El bloque `{try}` también se puede detener y saltar manualmente usando `{rollback}`. Gracias a esto, no necesita verificar todos los datos de entrada de antemano y puede decidir durante el renderizado que no quiere renderizar el objeto en absoluto: ```latte {try} @@ -719,30 +736,30 @@ El bloque `{try}` también puede detenerse y omitirse manualmente utilizando `{r ``` -Variables .[#toc-variables] -=========================== +Variables +========= `{var}` `{default}` ------------------- -Crearemos nuevas variables en la plantilla con la etiqueta `{var}`: +Creamos nuevas variables en la plantilla con la etiqueta `{var}`: ```latte {var $name = 'John Smith'} {var $age = 27} -{* Multiple declaration *} +{* Declaración múltiple *} {var $name = 'John Smith', $age = 27} ``` -La etiqueta `{default}` funciona de forma similar, salvo que crea variables sólo si no existen: +La etiqueta `{default}` funciona de manera similar, pero crea variables solo si no existen. Si la variable ya existe y contiene el valor `null`, no será sobrescrita: ```latte -{default $lang = 'cs'} +{default $lang = 'es'} ``` -A partir de Latte 2.7, también se pueden especificar [tipos de variables |type-system]. Por ahora, son informativas y Latte no las comprueba. +También puede indicar [tipos de variables|type-system]. Por ahora son informativos y Latte no los comprueba. ```latte {var string $name = $article->getTitle()} @@ -750,10 +767,10 @@ A partir de Latte 2.7, también se pueden especificar [tipos de variables |type- ``` -`{parameters}` .{data-version:2.9} ----------------------------------- +`{parameters}` +-------------- -Al igual que una función declara sus parámetros, una plantilla puede declarar sus variables al principio: +Así como una función declara sus parámetros, una plantilla también puede declarar sus variables al principio: ```latte {parameters @@ -763,15 +780,15 @@ Al igual que una función declara sus parámetros, una plantilla puede declarar } ``` -Las variables `$a` y `$b` sin valor por defecto tienen automáticamente un valor por defecto de `null`. Los tipos declarados siguen siendo informativos y Latte no los comprueba. +Las variables `$a` y `$b` sin un valor por defecto especificado tienen automáticamente el valor por defecto `null`. Los tipos declarados son por ahora informativos y Latte no los comprueba. -Aparte de las variables declaradas no se pasan a la plantilla. Esta es una diferencia con respecto a la etiqueta `{default}`. +No se transfieren a la plantilla otras variables que no sean las declaradas. En esto se diferencia de la etiqueta `{default}`. `{capture}` ----------- -Utilizando la etiqueta `{capture}` puede capturar la salida a una variable: +Captura la salida en una variable: ```latte {capture $var} @@ -780,10 +797,10 @@ Utilizando la etiqueta `{capture}` puede capturar la salida a una variable: {/capture} -

                                                              Captured: {$var}

                                                              +

                                                              Capturado: {$var}

                                                              ``` -La etiqueta también se puede escribir como [n:attribute |syntax#n:attributes]: +La etiqueta, como cualquier etiqueta par, también se puede escribir como [n:atributo |syntax#n:atributos]: ```latte
                                                                @@ -791,15 +808,17 @@ La etiqueta también se puede escribir como [n:attribute |syntax#n:attributes]:
                                                              ``` +La salida HTML se guarda en la variable `$var` en forma de objeto `Latte\Runtime\Html`, para que [no se produzca un escapado no deseado |develop#Desactivar el auto-escapado de variables] al imprimirla. -Otros .[#toc-others] -==================== + +Otros +===== `{contentType}` --------------- -Utilice la etiqueta para especificar qué tipo de contenido representa la plantilla. Las opciones son: +Con esta etiqueta especifica qué tipo de contenido representa la plantilla. Las opciones son: - `html` (tipo por defecto) - `xml` @@ -808,9 +827,9 @@ Utilice la etiqueta para especificar qué tipo de contenido representa la planti - `calendar` (iCal) - `text` -Su uso es importante porque establece el escape [sensible al contexto |safety-first#context-aware-escaping] y sólo entonces Latte puede escapar correctamente. Por ejemplo, `{contentType xml}` cambia al modo XML, `{contentType text}` desactiva completamente el escape. +Su uso es importante porque establece el [escapado sensible al contexto |safety-first#Escape sensible al contexto] y solo así puede escapar correctamente. Por ejemplo, `{contentType xml}` cambia al modo XML, `{contentType text}` desactiva completamente el escapado. -Si el parámetro es un tipo MIME completo, como `application/xml`, también envía una cabecera HTTP `Content-Type` al navegador: +Si el parámetro es un tipo MIME completo, como por ejemplo `application/xml`, también envía la cabecera HTTP `Content-Type` al navegador: ```latte {contentType application/xml} @@ -829,26 +848,24 @@ Si el parámetro es un tipo MIME completo, como `application/xml`, también env `{debugbreak}` -------------- -Especifica el lugar donde se interrumpirá la ejecución del código. Se utiliza con fines de depuración para que el programador inspeccione el entorno de ejecución y se asegure de que el código se ejecuta según lo esperado. Es compatible con [Xdebug |https://xdebug.org]. Además, puede especificar una condición cuando el código debe romperse. +Marca el lugar donde se suspenderá la ejecución del programa y se iniciará el depurador, para que el programador pueda inspeccionar el entorno de ejecución y averiguar si el programa funciona como se esperaba. Admite [Xdebug |https://xdebug.org/]. Se puede añadir una condición que determine cuándo debe suspenderse el programa. ```latte -{debugbreak} {* breaks the program *} +{debugbreak} {* suspende el programa *} -{debugbreak $counter == 1} {* breaks the program if the condition is met *} +{debugbreak $counter == 1} {* suspende el programa si se cumple la condición *} ``` `{do}` ------ -Ejecuta el código y no imprime nada. +Ejecuta código PHP y no imprime nada. Al igual que con todas las demás etiquetas, por código PHP se entiende una única expresión, vea [limitaciones de PHP |syntax#Limitaciones de PHP en Latte]. ```latte {do $num++} ``` -En Latte 2.7 y anteriores, se utilizaba `{php}`. - `{dump}` -------- @@ -856,19 +873,25 @@ En Latte 2.7 y anteriores, se utilizaba `{php}`. Vuelca una variable o el contexto actual. ```latte -{dump $name} {* dumps the $name variable *} +{dump $name} {* Vuelca la variable $name *} -{dump} {* dumps all the defined variables *} +{dump} {* Vuelca todas las variables definidas actualmente *} ``` .[caution] -Requiere el paquete [Tracy |tracy:]. +Requiere la librería [Tracy|tracy:]. + + +`{php}` +------- + +Permite ejecutar cualquier código PHP. La etiqueta debe activarse mediante la extensión [RawPhpExtension |develop#RawPhpExtension]. `{spaceless}` ------------- -Elimina los espacios en blanco innecesarios. Es similar al filtro [sin |filters#spaceless] espacios. +Elimina los espacios en blanco innecesarios de la salida. Funciona de manera similar al filtro [spaceless |filters#spaceless]. ```latte {spaceless} @@ -878,52 +901,52 @@ Elimina los espacios en blanco innecesarios. Es similar al filtro [sin |filters# {/spaceless} ``` -Salida: +Genera ```latte
                                                              • Hello
                                                              ``` -La etiqueta también puede escribirse como [n:atributo |syntax#n:attributes]: +La etiqueta también se puede escribir como [n:atributo |syntax#n:atributos]. `{syntax}` ---------- -Las etiquetas Latte no tienen por qué ir encerradas únicamente entre llaves. Puede elegir otro separador, incluso en tiempo de ejecución. Esto se hace mediante `{syntax…}`, donde el parámetro puede ser: +Las etiquetas Latte no tienen por qué estar delimitadas solo por llaves simples. Podemos elegir otro delimitador e incluso en tiempo de ejecución. Para esto sirve `{syntax …}`, donde como parámetro se puede indicar: -- doble: `{{...}}` -- off: desactiva completamente las etiquetas Latte +- double: `{{...}}` +- off: desactiva completamente el procesamiento de etiquetas Latte -Utilizando la notación n:attribute podemos desactivar Latte sólo para un bloque JavaScript: +Con el uso de n:atributos se puede desactivar Latte, por ejemplo, solo para un bloque de JavaScript: ```latte ``` -Latte se puede utilizar muy cómodamente dentro de JavaScript, sólo hay que evitar construcciones como en este ejemplo, donde la letra sigue inmediatamente a `{`, ver [Latte dentro de JavaScript o CSS |recipes#Latte inside JavaScript or CSS]. +Latte se puede usar muy cómodamente también dentro de JavaScript, solo hay que evitar construcciones como en este ejemplo, donde una letra sigue inmediatamente después de `{`, vea [Latte dentro de JavaScript o CSS |recipes#Latte dentro de JavaScript o CSS]. -Si desactiva Latte con `{syntax off}` (es decir, la etiqueta, no el atributo n:attribute), ignorará estrictamente todas las etiquetas hasta `{/syntax}`. +Si desactiva Latte usando `{syntax off}` (es decir, con la etiqueta, no con el n:atributo), ignorará consistentemente todas las etiquetas hasta `{/syntax}` -{trace} .{data-version:2.10} ----------------------------- +`{trace}` +--------- -Lanza una excepción `Latte\RuntimeException`, cuya traza de pila sigue el espíritu de las plantillas. Así, en lugar de llamar a funciones y métodos, se trata de llamar a bloques e insertar plantillas. Si utiliza una herramienta para mostrar claramente las excepciones lanzadas, como [Tracy |tracy:], verá claramente la pila de llamadas, incluidos todos los argumentos pasados. +Lanza una excepción `Latte\RuntimeException`, cuyo stack trace sigue el espíritu de las plantillas. Es decir, en lugar de llamadas a funciones y métodos, contiene llamadas a bloques e inclusiones de plantillas. Si utiliza una herramienta para la visualización clara de las excepciones lanzadas, como por ejemplo [Tracy|tracy:], se le mostrará claramente el call stack incluyendo todos los argumentos pasados. -Ayudantes de etiquetas HTML .[#toc-html-tag-helpers] -==================================================== +Ayudantes del codificador HTML +============================== -n:clase .[#toc-n-class] ------------------------ +`n:class` +--------- -Gracias a `n:class`, es muy fácil generar el atributo HTML `class` exactamente como lo necesita. +Gracias a `n:class` es muy fácil generar el atributo HTML `class` exactamente según sus ideas. -Ejemplo: Necesito que el elemento activo tenga la clase `active`: +Ejemplo: necesito que el elemento activo tenga la clase `active`: ```latte {foreach $items as $item} @@ -931,7 +954,7 @@ Ejemplo: Necesito que el elemento activo tenga la clase `active`: {/foreach} ``` -Y además necesito que el primer elemento tenga las clases `first` y `main`: +Y además, que el primer elemento tenga las clases `first` y `main`: ```latte {foreach $items as $item} @@ -947,13 +970,13 @@ Y que todos los elementos tengan la clase `list-item`: {/foreach} ``` -Asombrosamente sencillo, ¿verdad? +Asombrosamente simple, ¿verdad? -n:attr .[#toc-n-attr] ---------------------- +`n:attr` +-------- -El atributo `n:attr` puede generar atributos HTML arbitrarios con la misma elegancia que [n:class |#n:class]. +El atributo `n:attr` puede generar cualquier atributo HTML con la misma elegancia que tiene [#n:class]. ```latte {foreach $data as $item} @@ -961,7 +984,7 @@ El atributo `n:attr` puede generar atributos HTML arbitrarios con la misma elega {/foreach} ``` -En función de los valores devueltos, muestra, por ejemplo +Dependiendo de los valores devueltos, imprime por ejemplo: ```latte @@ -972,26 +995,28 @@ En función de los valores devueltos, muestra, por ejemplo ``` -n:tag .{data-version:2.10} --------------------------- +`n:tag` +------- -El atributo `n:tag` puede cambiar dinámicamente el nombre de un elemento HTML. +El atributo `n:tag` puede cambiar dinámicamente el nombre del elemento HTML. ```latte

                                                              {$title}

                                                              ``` -Si `$heading === null`, la etiqueta `

                                                              ` se imprime sin cambios. De lo contrario, el nombre del elemento se cambia por el valor de la variable, por lo que para `$heading === 'h3'` escribe: +Si `$heading === null`, se imprimirá la etiqueta `

                                                              ` sin cambios. De lo contrario, el nombre del elemento cambiará al valor de la variable, por lo que para `$heading === 'h3'` se imprimirá: ```latte

                                                              ...

                                                              ``` +Dado que Latte es un sistema de plantillas seguro, comprueba que el nuevo nombre de etiqueta sea válido y no contenga ningún valor no deseado o malicioso. -n:ifcontent .[#toc-n-ifcontent] -------------------------------- -Evita que se imprima un elemento HTML vacío, es decir, un elemento que sólo contiene espacios en blanco. +`n:ifcontent` +------------- + +Evita que se imprima un elemento HTML vacío, es decir, un elemento que no contiene nada más que espacios. ```latte
                                                              @@ -999,7 +1024,7 @@ Evita que se imprima un elemento HTML vacío, es decir, un elemento que sólo co
                                                              ``` -Dependiendo de los valores de la variable `$error` se imprimirá: +Imprime dependiendo del valor de la variable `$error`: ```latte {* $error = '' *} @@ -1013,42 +1038,42 @@ Dependiendo de los valores de la variable `$error` se imprimirá: ``` -Traducción .{data-version:3.0}[#toc-translation] -================================================ +Traducciones +============ -Para que las etiquetas de traducción funcionen, necesitas [configurar el traductor |develop#TranslatorExtension]. También puede utilizar el [`translate` |filters#translate] para la traducción. +Para que las etiquetas de traducción funcionen, es necesario [activar el traductor |develop#TranslatorExtension]. Para la traducción también puede usar el filtro [`translate` |filters#translate]. `{_...}` -------- -Traduce los valores a otros idiomas. +Traduce valores a otros idiomas. ```latte -{_'Basket'} +{_'Cesta'} {_$item} ``` También se pueden pasar otros parámetros al traductor: ```latte -{_'Basket', domain: order} +{_'Cesta', domain: order} ``` `{translate}` ------------- -Překládá části šablony: +Traduce partes de la plantilla: ```latte -

                                                              {translate}Order{/translate}

                                                              +

                                                              {translate}Pedido{/translate}

                                                              {translate domain: order}Lorem ipsum ...{/translate} ``` -La etiqueta también puede escribirse como [n:attribute |syntax#n:attributes], para traducir el interior del elemento: +La etiqueta también se puede escribir como [n:atributo |syntax#n:atributos], para traducir el interior del elemento: ```latte -

                                                              Order

                                                              +

                                                              Pedido

                                                              ``` diff --git a/latte/es/template-inheritance.texy b/latte/es/template-inheritance.texy index 8ac35f7efb..f608837d13 100644 --- a/latte/es/template-inheritance.texy +++ b/latte/es/template-inheritance.texy @@ -2,15 +2,15 @@ Herencia y reutilización de plantillas ************************************** .[perex] -La reutilización de plantillas y los mecanismos de herencia están aquí para aumentar tu productividad, ya que cada plantilla contiene sólo su contenido único y los elementos y estructuras repetidos se reutilizan. Introducimos tres conceptos: herencia de [plantillas |#layout inheritance], [reutilización horizontal |#horizontal reuse] y [herencia de unidades |#unit inheritance]. +Los mecanismos de reutilización y herencia de plantillas aumentarán su productividad, ya que cada plantilla contiene sólo su contenido único y los elementos y estructuras repetidos se reutilizan. Presentamos tres conceptos: [herencia de layout |#Herencia de layout layout], [reutilización horizontal |#Reutilización horizontal import] y [herencia de unidades |#Herencia de unidades embed]. -El concepto de herencia de plantillas Latte es similar a la herencia de clases PHP. Se define una **plantilla padre** a partir de la cual otras **plantillas hijas** pueden extender y anular partes de la plantilla padre. Funciona muy bien cuando los elementos comparten una estructura común. ¿Suena complicado? No te preocupes, no lo es. +El concepto de herencia de plantillas Latte es similar a la herencia de clases en PHP. Se define una **plantilla padre** de la que otras **plantillas hijas** pueden heredar y pueden sobrescribir partes de la plantilla padre. Funciona muy bien cuando los elementos comparten una estructura común. ¿Suena complicado? No se preocupe, es muy fácil. -Herencia de diseño `{layout}` .{toc: Layout Inheritance} -======================================================== +Herencia de layout `{layout}` +============================= -Veamos la herencia de plantillas de diseño comenzando con un ejemplo. Esta es una plantilla padre que llamaremos por ejemplo `layout.latte` y define un documento HTML esqueleto. +Veamos la herencia de la plantilla de diseño, es decir, el layout, con un ejemplo. Esta es la plantilla padre, que llamaremos, por ejemplo, `layout.latte`, y que define el esqueleto del documento HTML: ```latte @@ -30,36 +30,36 @@ Veamos la herencia de plantillas de diseño comenzando con un ejemplo. Esta es u ``` -La etiqueta `{block}` define tres bloques que las plantillas hijas pueden rellenar. Todo lo que hace la etiqueta block es decirle al motor de plantillas que una plantilla hija puede sobreescribir esas partes de la plantilla definiendo su propio bloque con el mismo nombre. +Las etiquetas `{block}` definen tres bloques que las plantillas hijas pueden rellenar. La etiqueta block simplemente anuncia que este lugar puede ser sobrescrito por una plantilla hija definiendo su propio bloque con el mismo nombre. -Una plantilla hija podría tener este aspecto: +Una plantilla hija puede verse así: ```latte {layout 'layout.latte'} -{block title}My amazing blog{/block} +{block title}Mi increíble blog{/block} {block content} -

                                                              Welcome to my awesome homepage.

                                                              +

                                                              Bienvenido a mi impresionante página de inicio.

                                                              {/block} ``` -La etiqueta `{layout}` es la clave aquí. Indica al motor de plantillas que esta plantilla "extiende" otra plantilla. Cuando Latte renderiza esta plantilla, primero localiza el padre - en este caso, `layout.latte`. +La clave aquí es la etiqueta `{layout}`. Le dice a Latte que esta plantilla "extiende" otra plantilla. Cuando Latte renderiza esta plantilla, primero encuentra la plantilla padre - en este caso `layout.latte`. -En ese momento, el motor de plantillas se dará cuenta de las tres etiquetas de bloque en `layout.latte` y reemplazará esos bloques con el contenido de la plantilla hija. Tenga en cuenta que, dado que la plantilla hija no definió el bloque *footer*, en su lugar se utiliza el contenido de la plantilla padre. El contenido de una etiqueta `{block}` en una plantilla padre se utiliza siempre como alternativa. +En este punto, Latte nota las tres etiquetas de bloque en `layout.latte` y reemplaza estos bloques con el contenido de la plantilla hija. Dado que la plantilla hija no definió el bloque *footer*, se utiliza en su lugar el contenido de la plantilla padre. El contenido dentro de la etiqueta `{block}` en la plantilla padre siempre se utiliza como respaldo. -El resultado podría ser el siguiente: +La salida puede verse así: ```latte - My amazing blog + Mi increíble blog
                                                              -

                                                              Welcome to my awesome homepage.

                                                              +

                                                              Bienvenido a mi impresionante página de inicio.

                                                              ``` - -{{leftbar: /@left-menu}} diff --git a/latte/fr/cookbook/migration-from-twig.texy b/latte/fr/cookbook/migration-from-twig.texy index 746f266584..551678e9ab 100644 --- a/latte/fr/cookbook/migration-from-twig.texy +++ b/latte/fr/cookbook/migration-from-twig.texy @@ -2,37 +2,37 @@ Migration de Twig vers Latte **************************** .[perex] -Êtes-vous en train de migrer un projet écrit en Twig vers Latte, plus moderne ? Nous avons un outil pour faciliter cette migration. [Essayez-le en ligne |https://twig2latte.nette.org]. +Vous convertissez un projet écrit en Twig vers le plus moderne Latte ? Nous avons un outil pour vous faciliter la migration. [Essayez-le en ligne |https://fiddle.nette.org/twig2latte/]. -Vous pouvez télécharger l'outil depuis [GitHub |https://github.com/nette/latte-tools] ou l'installer en utilisant Composer : +Vous pouvez télécharger l'outil depuis [GitHub |https://github.com/nette/latte-tools] ou l'installer via Composer : ```shell composer create-project latte/tools ``` -Le convertisseur n'utilise pas de simples substitutions d'expressions régulières, mais utilise directement l'analyseur Twig, ce qui lui permet de traiter toute syntaxe complexe. +Le convertisseur n'utilise pas de simples remplacements par expressions régulières, mais utilise directement le parser Twig, il peut donc gérer n'importe quelle syntaxe complexe. -Un script `twig-to-latte.php` est utilisé pour convertir Twig en Latte : +Le script `twig-to-latte.php` est utilisé pour la conversion de Twig vers Latte : ```shell -twig-to-latte.php input.twig.html [output.latte] +php twig-to-latte.php input.twig.html [output.latte] ``` -Conversion .[#toc-conversion] ------------------------------ +Conversion +---------- -La conversion nécessite une édition manuelle du résultat, car la conversion ne peut pas être faite sans ambiguïté. Twig utilise la syntaxe par points, où `{{ a.b }}` peut signifier `$a->b`, `$a['b']` ou `$a->getB()`, ce qui ne peut être distingué lors de la compilation. Le convertisseur convertit donc tout en `$a->b`. +La conversion suppose une modification manuelle du résultat, car elle ne peut pas être effectuée de manière univoque. Twig utilise la syntaxe à points, où `{{ a.b }}` peut signifier `$a->b`, `$a['b']` ou `$a->getB()`, ce qui ne peut pas être distingué lors de la compilation. Le convertisseur convertit donc tout en `$a->b`. -Certaines fonctions, filtres ou balises n'ont pas d'équivalent dans Latte, ou peuvent se comporter de manière légèrement différente. +Certaines fonctions, filtres ou balises n'ont pas d'équivalent dans Latte, ou peuvent se comporter légèrement différemment. -Exemple .[#toc-example] ------------------------ +Exemple +------- -Le fichier d'entrée pourrait ressembler à ceci : +Le fichier d'entrée peut ressembler à ceci : -```latte +```twig {% use "blocks.twig" %} @@ -54,7 +54,7 @@ Le fichier d'entrée pourrait ressembler à ceci : ``` -Après avoir converti en Latte, nous obtenons ce modèle : +Après conversion en Latte, nous obtenons ce template : ```latte {import 'blocks.latte'} @@ -77,5 +77,3 @@ Après avoir converti en Latte, nous obtenons ce modèle : ``` - -{{leftbar: /@left-menu}} diff --git a/latte/fr/cookbook/passing-variables.texy b/latte/fr/cookbook/passing-variables.texy new file mode 100644 index 0000000000..5aa0e33d3b --- /dev/null +++ b/latte/fr/cookbook/passing-variables.texy @@ -0,0 +1,158 @@ +Passage de variables entre templates +************************************ + +Ce guide vous explique comment les variables sont passées entre les templates dans Latte en utilisant différentes balises comme `{include}`, `{import}`, `{embed}`, `{layout}`, `{sandbox}`, et d'autres. Vous apprendrez également comment travailler avec les variables dans les balises `{block}` et `{define}`, et à quoi sert la balise `{parameters}`. + + +Types de variables +------------------ +Les variables dans Latte peuvent être divisées en trois catégories selon comment et où elles sont définies : + +**Variables d'entrée** sont celles qui sont passées au template depuis l'extérieur, par exemple depuis un script PHP ou en utilisant une balise comme `{include}`. + +```php +$latte->render('template.latte', ['userName' => 'Jan', 'userAge' => 30]); +``` + +**Variables environnantes** sont les variables existant à l'endroit d'une certaine balise. Elles incluent toutes les variables d'entrée et d'autres variables créées à l'aide de balises comme `{var}`, `{default}` ou dans le cadre d'une boucle `{foreach}`. + +```latte +{foreach $users as $user} + {include 'userBox.latte', user: $user} +{/foreach} +``` + +**Variables explicites** sont celles qui sont directement spécifiées à l'intérieur de la balise et sont envoyées au template cible. + +```latte +{include 'userBox.latte', name: $user->name, age: $user->age} +``` + + +`{block}` +--------- +La balise `{block}` est utilisée pour définir des blocs de code réutilisables qui peuvent être personnalisés ou étendus dans les templates hérités. Les variables environnantes définies avant le bloc sont disponibles à l'intérieur du bloc, mais toute modification des variables n'est effective qu'à l'intérieur de ce bloc. + +```latte +{var $foo = 'original'} +{block example} + {var $foo = 'modifié'} +{/block} + +{$foo} // affiche : original +``` + + +`{define}` +---------- +La balise `{define}` sert à créer des blocs qui ne sont rendus qu'après leur appel via `{include}`. Les variables disponibles à l'intérieur de ces blocs dépendent de si des paramètres sont spécifiés dans la définition. Si oui, elles n'ont accès qu'à ces paramètres. Sinon, elles ont accès à toutes les variables d'entrée du template dans lequel les blocs sont définis. + +```latte +{define hello} + {* a accès à toutes les variables d'entrée du template *} +{/define} + +{define hello $name} + {* a accès uniquement au paramètre $name *} +{/define} +``` + + +`{parameters}` +-------------- +La balise `{parameters}` sert à déclarer explicitement les variables d'entrée attendues au début du template. De cette manière, il est facile de documenter les variables attendues et leurs types de données. Il est également possible de définir des valeurs par défaut. + +```latte +{parameters int $age, string $name = 'inconnu'} +

                                                              Âge : {$age}, Nom : {$name}

                                                              +``` + + +`{include file}` +---------------- +La balise `{include file}` sert à inclure un template entier. Les variables d'entrée du template dans lequel la balise est utilisée, ainsi que les variables explicitement définies, sont passées à ce template. Cependant, le template cible peut limiter la portée à l'aide de `{parameters}`. + +```latte +{include 'profile.latte', userId: $user->id} +``` + + +`{include block}` +----------------- +Lorsque vous incluez un bloc défini dans le même template, toutes les variables environnantes et explicitement définies lui sont passées : + +```latte +{define blockName} +

                                                              Nom : {$name}, Âge : {$age}

                                                              +{/define} + +{var $name = 'Jan', $age = 30} +{include blockName} +``` + +Dans cet exemple, les variables `$name` et `$age` sont passées au bloc `blockName`. `{include parent}` se comporte de la même manière. + +Lors de l'inclusion d'un bloc d'un autre template, seules les variables d'entrée et celles explicitement définies sont passées. Les variables environnantes ne sont pas automatiquement disponibles. + +```latte +{include blockInOtherTemplate, name: $name, age: $age} +``` + + +`{layout}` ou `{extends}` +------------------------- +Ces balises définissent le layout auquel sont passées les variables d'entrée du template enfant ainsi que les variables créées dans le code avant les blocs : + +```latte +{layout 'layout.latte'} +{var $seo = 'index, follow'} +``` + +Template `layout.latte` : + +```latte + + + +``` + + +`{embed}` +--------- +La balise `{embed}` est similaire à la balise `{include}`, mais permet d'intégrer des blocs dans le template. Contrairement à `{include}`, seules les variables explicitement déclarées sont passées : + +```latte +{embed 'menu.latte', items: $menuItems} +{/embed} +``` + +Dans cet exemple, le template `menu.latte` n'a accès qu'à la variable `$items`. + +Inversement, dans les blocs à l'intérieur de `{embed}`, toutes les variables environnantes sont accessibles : + +```latte +{var $name = 'Jan'} +{embed 'menu.latte', items: $menuItems} + {block foo} + {$name} + {/block} +{/embed} +``` + + +`{import}` +---------- +La balise `{import}` est utilisée pour charger des blocs depuis d'autres templates. Les variables d'entrée ainsi que les variables explicitement déclarées sont transmises aux blocs importés. + +```latte +{import 'buttons.latte'} +``` + + +`{sandbox}` +----------- +La balise `{sandbox}` isole le template pour un traitement sécurisé. Les variables sont passées exclusivement de manière explicite. + +```latte +{sandbox 'secure.latte', data: $secureData} +``` diff --git a/latte/fr/cookbook/slim-framework.texy b/latte/fr/cookbook/slim-framework.texy index d6b4ed33b1..9006774afe 100644 --- a/latte/fr/cookbook/slim-framework.texy +++ b/latte/fr/cookbook/slim-framework.texy @@ -2,42 +2,42 @@ Utilisation de Latte avec Slim 4 ******************************** .[perex] -Cet article écrit par "Daniel Opitz":https://odan.github.io/2022/04/06/slim4-latte.html décrit comment utiliser Latte avec le Framework Slim. +Cet article, dont l'auteur est "Daniel Opitz":https://odan.github.io/2022/04/06/slim4-latte.html, décrit l'utilisation de Latte avec le Slim Framework. -Tout d'abord, "installez le Slim Framework":https://odan.github.io/2019/11/05/slim4-tutorial.html et ensuite Latte en utilisant Composer : +Tout d'abord, "installez le Slim Framework":https://odan.github.io/2019/11/05/slim4-tutorial.html puis Latte en utilisant Composer : ```shell composer require latte/latte ``` -Configuration .[#toc-configuration] ------------------------------------ +Configuration +------------- -Créez un nouveau répertoire `templates` dans le répertoire racine de votre projet. Tous les modèles y seront placés ultérieurement. +Dans le répertoire racine du projet, créez un nouveau répertoire `templates`. Tous les templates y seront placés ultérieurement. -Ajoutez une nouvelle clé de configuration `template` dans votre fichier `config/defaults.php`: +Dans le fichier `config/defaults.php`, ajoutez une nouvelle clé de configuration `template` : ```php $settings['template'] = __DIR__ . '/../templates'; ``` -Latte compile les modèles en code PHP natif et les stocke dans un cache sur le disque. Ils sont donc aussi rapides que s'ils avaient été écrits en PHP natif. +Latte compile les templates en code PHP natif et les stocke dans un cache sur le disque. Ils sont donc aussi rapides que s'ils avaient été écrits en PHP natif. -Ajoutez une nouvelle clé de configuration `template_temp` dans votre fichier `config/defaults.php`: Assurez-vous que le répertoire `{project}/tmp/templates` existe et qu'il dispose des droits d'accès en lecture et en écriture. +Dans le fichier `config/defaults.php`, ajoutez une nouvelle clé de configuration `template_temp` : Assurez-vous que le répertoire `{project}/tmp/templates` existe et dispose des droits de lecture et d'écriture. ```php $settings['template_temp'] = __DIR__ . '/../tmp/templates'; ``` -Latte régénère automatiquement le cache chaque fois que vous modifiez le modèle, ce qui peut être désactivé dans l'environnement de production pour gagner un peu de performance : +Latte régénère automatiquement le cache à chaque modification du template, ce qui peut être désactivé en environnement de production pour économiser un peu de performance : ```php -// changer en false dans l'environnement de production +// en environnement de production, changez en false $settings['template_auto_refresh'] = true; ``` -Ensuite, ajoutez une définition de conteneur DI pour la classe `Latte\Engine`. +Ensuite, ajoutez la définition du conteneur DI pour la classe `Latte\Engine`. ```php +
                                                                {foreach $items as $item}
                                                              • {$item|capitalize}
                                                              • {/foreach}
                                                              ``` -Si tout est configuré correctement, vous devriez voir la sortie suivante : +Si tout est correctement configuré, la sortie suivante devrait s'afficher : ```latte One @@ -155,4 +155,3 @@ Three ``` {{priority: -1}} -{{leftbar: /@left-menu}} diff --git a/latte/fr/creating-extension.texy b/latte/fr/creating-extension.texy deleted file mode 100644 index 50d84dee73..0000000000 --- a/latte/fr/creating-extension.texy +++ /dev/null @@ -1,579 +0,0 @@ -Création d'une extension -************************ - -.[perex]{data-version:3.0} -Une extension est une classe réutilisable qui permet de définir des balises, des filtres, des fonctions, des fournisseurs, etc. personnalisés. - -Nous créons des extensions lorsque nous voulons réutiliser nos personnalisations Latte dans différents projets ou les partager avec d'autres. -Il est également utile de créer une extension pour chaque projet Web qui contiendra toutes les balises et tous les filtres spécifiques que vous souhaitez utiliser dans les modèles de projet. - - -Classe d'extension .[#toc-extension-class] -========================================== - -Extension est une classe héritant de [api:Latte\Extension]. Elle est enregistrée dans Latte à l'aide de `addExtension()` (ou via le [fichier de configuration |application:configuration#Latte]) : - -```php -$latte = new Latte\Engine; -$latte->addExtension(new MyLatteExtension); -``` - -Si vous enregistrez plusieurs extensions et qu'elles définissent des balises, des filtres ou des fonctions de même nom, la dernière extension ajoutée l'emporte. Cela implique également que vos extensions peuvent remplacer les tags/filtres/fonctions natifs. - -Chaque fois que vous apportez une modification à une classe et que l'actualisation automatique n'est pas désactivée, Latte recompile automatiquement vos modèles. - -Une classe peut mettre en œuvre l'une des méthodes suivantes : - -```php -abstract class Extension -{ - /** - * Initializes before template is compiler. - */ - public function beforeCompile(Engine $engine): void; - - /** - * Returns a list of parsers for Latte tags. - * @return array - */ - public function getTags(): array; - - /** - * Returns a list of compiler passes. - * @return array - */ - public function getPasses(): array; - - /** - * Returns a list of |filters. - * @return array - */ - public function getFilters(): array; - - /** - * Returns a list of functions used in templates. - * @return array - */ - public function getFunctions(): array; - - /** - * Returns a list of providers. - * @return array - */ - public function getProviders(): array; - - /** - * Returns a value to distinguish multiple versions of the template. - */ - public function getCacheKey(Engine $engine): mixed; - - /** - * Initializes before template is rendered. - */ - public function beforeRender(Template $template): void; -} -``` - -Pour avoir une idée de ce à quoi ressemble l'extension, jetez un coup d'œil à la "CoreExtension":https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php intégrée. - - -beforeCompile(Latte\Engine $engine): void .[method] ---------------------------------------------------- - -Appelé avant que le modèle ne soit compilé. Cette méthode peut être utilisée pour les initialisations liées à la compilation, par exemple. - - -getTags(): array .[method] --------------------------- - -Appelé lorsque le modèle est compilé. Retourne un tableau associatif *nom de la balise => appelable*, qui sont des [fonctions d'analyse de la balise |#Tag Parsing Function]. - -```php -public function getTags(): array -{ - return [ - 'foo' => [FooNode::class, 'create'], - 'bar' => [BarNode::class, 'create'], - 'n:baz' => [NBazNode::class, 'create'], - // ... - ]; -} -``` - -La balise `n:baz` représente un pur n:attribut, c'est-à-dire une balise qui ne peut être écrite que comme un attribut. - -Dans le cas des balises `foo` et `bar`, Latte reconnaîtra automatiquement s'il s'agit de paires, et si c'est le cas, elles peuvent être écrites automatiquement en utilisant des n:attributs, y compris les variantes avec les préfixes `n:inner-foo` et `n:tag-foo`. - -L'ordre d'exécution de ces n:attributes est déterminé par leur ordre dans le tableau renvoyé par `getTags()`. Ainsi, `n:foo` est toujours exécuté avant `n:bar`, même si les attributs sont listés dans l'ordre inverse dans la balise HTML comme `
                                                              `. - -Si vous devez déterminer l'ordre des n:attributs sur plusieurs extensions, utilisez la méthode d'aide `order()`, où le paramètre `before` xor `after` détermine quelles balises sont ordonnées avant ou après la balise . - -```php -public function getTags(): array -{ - return [ - 'foo' => self::order([FooNode::class, 'create'], before: 'bar')] - 'bar' => self::order([BarNode::class, 'create'], after: ['block', 'snippet'])] - ]; -} -``` - - -getPasses(): array .[method] ----------------------------- - -Elle est appelée lorsque le modèle est compilé. Elle renvoie un tableau associatif *name pass => callable*, qui sont des fonctions représentant ce qu'on appelle des [passes de compilation |#compiler passes] qui traversent et modifient l'AST. - -Là encore, la méthode d'aide `order()` peut être utilisée. La valeur des paramètres `before` ou `after` peut être `*` avec la signification avant/après tout. - -```php -public function getPasses(): array -{ - return [ - 'optimize' => [Passes::class, 'optimizePass'], - 'sandbox' => self::order([$this, 'sandboxPass'], before: '*'), - // ... - ]; -} -``` - - -beforeRender(Latte\Engine $engine): void .[method] --------------------------------------------------- - -Elle est appelée avant chaque rendu de modèle. La méthode peut être utilisée, par exemple, pour initialiser les variables utilisées pendant le rendu. - - -getFilters(): array .[method] ------------------------------ - -Il est appelé avant que le modèle soit rendu. Retourne les [filtres |extending-latte#filters] sous la forme d'un tableau associatif *nom du filtre => appelable*. - -```php -public function getFilters(): array -{ - return [ - 'batch' => [$this, 'batchFilter'], - 'trim' => [$this, 'trimFilter'], - // ... - ]; -} -``` - - -getFunctions(): array .[method] -------------------------------- - -Il est appelé avant que le modèle ne soit rendu. Retourne les [fonctions |extending-latte#functions] sous la forme d'un tableau associatif *nom de la fonction => appelable*. - -```php -public function getFunctions(): array -{ - return [ - 'clamp' => [$this, 'clampFunction'], - 'divisibleBy' => [$this, 'divisibleByFunction'], - // ... - ]; -} -``` - - -getProviders(): array .[method] -------------------------------- - -Elle est appelée avant le rendu du modèle. Renvoie un tableau de fournisseurs, qui sont généralement des objets qui utilisent des balises au moment de l'exécution. Ils sont accessibles via `$this->global->...`. - -```php -public function getProviders(): array -{ - return [ - 'myFoo' => $this->foo, - 'myBar' => $this->bar, - // ... - ]; -} -``` - - -getCacheKey(Latte\Engine $engine): mixed .[method] --------------------------------------------------- - -Elle est appelée avant le rendu du modèle. La valeur de retour fait partie de la clé dont le hachage est contenu dans le nom du fichier de modèle compilé. Ainsi, pour différentes valeurs de retour, Latte générera différents fichiers de cache. - - -Comment fonctionne Latte ? .[#toc-how-does-latte-work] -====================================================== - -Pour comprendre comment définir des balises personnalisées ou des passages de compilateur, il est essentiel de comprendre comment Latte fonctionne sous le capot. - -La compilation de modèles dans Latte fonctionne de manière simpliste comme suit : - -- Tout d'abord, le **lexer** segmente le code source du modèle en petits morceaux (tokens) pour faciliter le traitement. -- Ensuite, le **parser** convertit le flux de tokens en un arbre de nœuds significatif (l'arbre de syntaxe abstraite, AST). -- Enfin, le compilateur **génère** une classe PHP à partir de l'AST qui rend le modèle et le met en cache. - -En fait, la compilation est un peu plus compliquée. Latte **a deux** lexers et parsers : un pour le modèle HTML et un pour le code PHP à l'intérieur des balises. De plus, l'analyse syntaxique ne s'exécute pas après la tokénisation, mais le lexer et l'analyse syntaxique s'exécutent en parallèle dans deux "threads" et se coordonnent. C'est de la science-fiction :-) - -En outre, toutes les balises ont leurs propres routines d'analyse. Lorsque l'analyseur rencontre une balise, il appelle sa fonction d'analyse (il renvoie [Extension::getTags() |#getTags]). -Son travail consiste à analyser les arguments de la balise et, dans le cas de balises appariées, le contenu interne. Elle renvoie un *node* qui devient une partie de l'AST. Voir [Fonction d'analyse syntaxique des balises |#Tag parsing function] pour plus de détails. - -Lorsque l'analyseur syntaxique termine son travail, nous avons un AST complet représentant le modèle. Le nœud racine est `Latte\Compiler\Nodes\TemplateNode`. Les nœuds individuels à l'intérieur de l'arbre représentent non seulement les balises, mais aussi les éléments HTML, leurs attributs, les expressions utilisées à l'intérieur des balises, etc. - -Ensuite, les [passes du compilateur |#Compiler passes] entrent en jeu. Il s'agit de fonctions (renvoyées par [Extension::getPasses() |#getPasses]) qui modifient l'AST. - -L'ensemble du processus, depuis le chargement du contenu du modèle, en passant par l'analyse syntaxique, jusqu'à la génération du fichier résultant, peut être séquencé à l'aide de ce code, avec lequel vous pouvez expérimenter et vidanger les résultats intermédiaires : - -```php -$latte = new Latte\Engine; -$source = $latte->getLoader()->getContent($file); -$ast = $latte->parse($source); -$latte->applyPasses($ast); -$code = $latte->generate($ast, $file); -``` - - -Exemple d'AST .[#toc-example-of-ast] ------------------------------------- - -Pour avoir une meilleure idée de l'AST, nous ajoutons un exemple. Il s'agit du modèle source : - -```latte -{foreach $category->getItems() as $item} -
                                                            • {$item->name|upper}
                                                            • - {else} - no items found -{/foreach} -``` - -Et voici sa représentation sous forme d'AST : - -/--pre -Latte\Compiler\Nodes\TemplateNode( - Latte\Compiler\Nodes\FragmentNode( - - Latte\Essential\Nodes\ForeachNode( - expression: Latte\Compiler\Nodes\Php\Expression\MethodCallNode( - object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$category') - name: Latte\Compiler\Nodes\Php\IdentifierNode('getItems') - ) - value: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') - content: Latte\Compiler\Nodes\FragmentNode( - - Latte\Compiler\Nodes\TextNode(' ') - - Latte\Compiler\Nodes\Html\ElementNode('li')( - content: Latte\Essential\Nodes\PrintNode( - expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode( - object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') - name: Latte\Compiler\Nodes\Php\IdentifierNode('name') - ) - modifier: Latte\Compiler\Nodes\Php\ModifierNode( - filters: - - Latte\Compiler\Nodes\Php\FilterNode('upper') - ) - ) - ) - ) - else: Latte\Compiler\Nodes\FragmentNode( - - Latte\Compiler\Nodes\TextNode('no items found') - ) - ) - ) -) -\-- - - -Balises personnalisées .[#toc-custom-tags] -========================================== - -Trois étapes sont nécessaires pour définir une nouvelle balise : - -- définition de la [fonction d'analyse de la balise |#tag parsing function] (responsable de l'analyse de la balise en un nœud) -- création d'une classe de nœud (responsable de la [génération du code PHP |#generating PHP code] et de la [traversée de l'AST |#AST traversing]) -- enregistrement de la balise à l'aide de [Extension::getTags() |#getTags] - - -Fonction d'analyse syntaxique des balises .[#toc-tag-parsing-function] ----------------------------------------------------------------------- - -L'analyse des balises est gérée par leur fonction d'analyse (celle renvoyée par [Extension::getTags() |#getTags]). Son travail consiste à analyser et à vérifier les arguments contenus dans la balise (elle utilise TagParser pour ce faire). -De plus, si la balise est une paire, elle demandera à TemplateParser d'analyser et de retourner le contenu interne. -La fonction crée et renvoie un nœud, qui est généralement un enfant de `Latte\Compiler\Nodes\StatementNode`, et qui devient une partie de l'AST. - -Nous créons une classe pour chaque nœud, ce que nous allons faire maintenant, et nous y plaçons élégamment la fonction d'analyse en tant que fabrique statique. À titre d'exemple, essayons de créer la balise familière `{foreach}`: - -```php -use Latte\Compiler\Nodes\StatementNode; - -class ForeachNode extends StatementNode -{ - // une fonction d'analyse syntaxique qui crée simplement un nœud pour l'instant - public static function create(Latte\Compiler\Tag $tag): self - { - $node = new self; - return $node; - } - - public function print(Latte\Compiler\PrintContext $context): string - { - // le code sera ajouté plus tard - } - - public function &getIterator(): \Generator - { - // le code sera ajouté plus tard - } -} -``` - -La fonction d'analyse syntaxique `create()` reçoit un objet [api:Latte\Compiler\Tag], qui contient des informations de base sur la balise (s'il s'agit d'une balise classique ou d'un n:attribut, sur quelle ligne elle se trouve, etc.) et accède principalement à [api:Latte\Compiler\TagParser] dans `$tag->parser`. - -Si la balise doit avoir des arguments, vérifiez leur existence en appelant `$tag->expectArguments()`. Les méthodes de l'objet `$tag->parser` sont disponibles pour les analyser : - -- `parseExpression(): ExpressionNode` pour une expression de type PHP (par exemple `10 + 3`) -- `parseUnquotedStringOrExpression(): ExpressionNode` pour une expression ou une chaîne de caractères non citée -- `parseArguments(): ArrayNode` pour le contenu d'un tableau (par exemple, `10, true, foo => bar`) -- `parseModifier(): ModifierNode` pour un modificateur (par ex. `|upper|truncate:10`) -- `parseType(): expressionNode` pour un indice de type (par exemple, `int|string` ou `Foo\Bar[]`) - -et un bas niveau [api:Latte\Compiler\TokenStream] opérant directement avec les jetons : - -- `$tag->parser->stream->consume(...): Token` -- `$tag->parser->stream->tryConsume(...): ?Token` - -Latte étend la syntaxe de PHP par petites touches, par exemple en ajoutant des modificateurs, des opérateurs ternaires raccourcis, ou en permettant d'écrire des chaînes alphanumériques simples sans guillemets. C'est pourquoi nous utilisons le terme *PHP-like* au lieu de PHP. Ainsi, la méthode `parseExpression()` analyse `foo` comme `'foo'`, par exemple. -En outre, *unquoted-string* est un cas particulier de chaîne de caractères qui n'a pas besoin d'être citée, mais qui n'a pas besoin d'être alphanumérique. Par exemple, il s'agit du chemin d'accès à un fichier dans la balise `{include ../file.latte}`. La méthode `parseUnquotedStringOrExpression()` est utilisée pour l'analyser. - -.[note] -L'étude des classes de nœuds qui font partie de Latte est la meilleure façon d'apprendre tous les détails minutieux du processus d'analyse. - -Revenons à la balise `{foreach}`. Dans cette balise, nous attendons des arguments de la forme `expression + 'as' + second expression`, que nous analysons comme suit : - -```php -use Latte\Compiler\Nodes\StatementNode; -use Latte\Compiler\Nodes\Php\ExpressionNode; -use Latte\Compiler\Nodes\AreaNode; - -class ForeachNode extends StatementNode -{ - public ExpressionNode $expression; - public ExpressionNode $value; - - public static function create(Latte\Compiler\Tag $tag): self - { - $tag->expectArguments(); - $node = new self; - $node->expression = $tag->parser->parseExpression(); - $tag->parser->stream->consume('as'); - $node->value = $parser->parseExpression(); - return $node; - } -} -``` - -Les expressions que nous avons écrites dans les variables `$expression` et `$value` représentent des sous-nœuds. - -.[tip] -Définissez les variables avec les sous-nœuds comme **publics** afin qu'ils puissent être modifiés dans les [étapes de traitement ultérieures |#Compiler Passes] si nécessaire. Il est également nécessaire de les rendre **disponibles** pour la [navigation |#AST Traversing]. - -Pour les balises appariées, comme la nôtre, la méthode doit également laisser TemplateParser analyser le contenu interne de la balise. Ceci est géré par `yield`, qui renvoie une paire ''[contenu interne, balise finale]''. Nous stockons le contenu interne dans la variable `$node->content`. - -```php -public AreaNode $content; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $endTag] = yield; - return $node; -} -``` - -Le mot-clé `yield` entraîne la fin de la méthode `create()`, qui redonne le contrôle au TemplateParser, qui continue à analyser le contenu jusqu'à ce qu'il atteigne la balise de fin. Il transmet ensuite le contrôle à `create()`, qui reprend là où il s'est arrêté. L'utilisation de la méthode `yield`, renvoie automatiquement `Generator`. - -Vous pouvez également transmettre à `yield` un tableau de noms de balises pour lesquelles vous souhaitez arrêter l'analyse s'ils apparaissent avant la balise de fin. Cela nous aide à mettre en œuvre la construction `{foreach}...{else}...{/foreach}` construction. Si `{else}` apparaît, nous analysons le contenu qui le suit dans `$node->elseContent`: - -```php -public AreaNode $content; -public ?AreaNode $elseContent = null; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $nextTag] = yield ['else']; - if ($nextTag?->name === 'else') { - [$node->elseContent] = yield; - } - - return $node; -} -``` - -Le nœud de retour termine l'analyse des balises. - - -Génération du code PHP .[#toc-generating-php-code] --------------------------------------------------- - -Chaque nœud doit implémenter la méthode `print()`. Renvoie le code PHP qui rend la partie donnée du modèle (code d'exécution). On lui passe un objet [api:Latte\Compiler\PrintContext] en paramètre, qui possède une méthode utile `format()` qui simplifie l'assemblage du code résultant. - -La méthode `format(string $mask, ...$args)` accepte les caractères de remplacement suivants dans le masque : -- `%node` imprime Node -- `%dump` exporte la valeur vers PHP -- `%raw` insère le texte directement sans aucune transformation -- `%args` imprime ArrayNode comme arguments à l'appel de fonction -- `%line` imprime un commentaire avec un numéro de ligne -- `%escape(...)` échappe le contenu -- `%modify(...)` applique un modificateur -- `%modifyContent(...)` applique un modificateur aux blocs - - -Notre fonction `print()` pourrait ressembler à ceci (nous négligeons la branche `else` pour simplifier) : - -```php -public function print(Latte\Compiler\PrintContext $context): string -{ - return $context->format( - <<<'XX' - foreach (%node as %node) %line { - %node - } - - XX, - $this->expression, - $this->value, - $this->position, - $this->content, - ); -} -``` - -La variable `$this->position` est déjà définie par la classe [api:Latte\Compiler\Node] et est définie par l'analyseur syntaxique. Elle contient un objet [api:Latte\Compiler\Position] avec la position de la balise dans le code source sous la forme d'un numéro de ligne et de colonne. - -Le code d'exécution peut utiliser des variables auxiliaires. Pour éviter toute collision avec les variables utilisées par le modèle lui-même, il est de convention de les préfixer avec les caractères `$ʟ__`. - -Il peut également utiliser des valeurs arbitraires au moment de l'exécution, qui sont transmises au modèle sous la forme de fournisseurs à l'aide de la méthode [Extension::getProviders() |#getProviders]. Il y accède en utilisant `$this->global->...`. - - -Traversée de l'AST .[#toc-ast-traversing] ------------------------------------------ - -Afin de traverser l'arbre AST en profondeur, il est nécessaire d'implémenter la méthode `getIterator()`. Cela permettra d'accéder aux sous-nœuds : - -```php -public function &getIterator(): \Generator -{ - yield $this->expression; - yield $this->value; - yield $this->content; - if ($this->elseContent) { - yield $this->elseContent; - } -} -``` - -Notez que `getIterator()` renvoie une référence. C'est ce qui permet aux visiteurs des nœuds de remplacer les nœuds individuels par d'autres nœuds. - -.[warning] -Si un nœud a des sous-nœuds, il est nécessaire d'implémenter cette méthode et de rendre tous les sous-nœuds disponibles. Sinon, une faille de sécurité pourrait être créée. Par exemple, le mode sandbox ne serait pas en mesure de contrôler les sous-nœuds et de garantir que les constructions non autorisées ne sont pas appelées dans ces derniers. - -Étant donné que le mot-clé `yield` doit être présent dans le corps de la méthode, même si celle-ci n'a pas de nœuds enfants, écrivez-la comme suit : - -```php -public function &getIterator(): \Generator -{ - if (false) { - yield; - } -} -``` - - -Le compilateur passe .[#toc-compiler-passes] -============================================ - -Les Compiler Passes sont des fonctions qui modifient les AST ou collectent des informations dans ces derniers. Elles sont renvoyées par la méthode [Extension::getPasses() |#getPasses]. - - -Traverseur de nœuds .[#toc-node-traverser] ------------------------------------------- - -La façon la plus courante de travailler avec l'AST est d'utiliser un [api:Latte\Compiler\NodeTraverser]: - -```php -use Latte\Compiler\Node; -use Latte\Compiler\NodeTraverser; - -$ast = (new NodeTraverser)->traverse( - $ast, - enter: fn(Node $node) => ..., - leave: fn(Node $node) => ..., -); -``` - -La fonction *enter* (c'est-à-dire le visiteur) est appelée lorsqu'un nœud est rencontré pour la première fois, avant que ses sous-nœuds ne soient traités. La fonction *leave* est appelée après que tous les sous-nœuds aient été visités. -Un modèle commun est que la fonction *enter* est utilisée pour collecter certaines informations, puis la fonction *leave* effectue des modifications sur cette base. Au moment où *leave* est appelée, tout le code à l'intérieur du nœud aura déjà été visité et les informations nécessaires auront été collectées. - -Comment modifier l'AST ? La manière la plus simple est de changer simplement les propriétés des nœuds. La deuxième façon est de remplacer entièrement le nœud en retournant un nouveau nœud. Exemple : le code suivant changera tous les entiers de l'AST en chaînes de caractères (par exemple, 42 sera changé en `'42'`). - -```php -use Latte\Compiler\Nodes\Php; - -$ast = (new NodeTraverser)->traverse( - $ast, - leave: function (Node $node) { - if ($node instanceof Php\Scalar\IntegerNode) { - return new Php\Scalar\StringNode((string) $node->value); - } - }, -); -``` - -Un AST peut facilement contenir des milliers de nœuds, et les parcourir tous peut être lent. Dans certains cas, il est possible d'éviter une traversée complète. - -Si vous cherchez tous les `Html\ElementNode` dans un arbre, vous savez qu'une fois que vous avez vu `Php\ExpressionNode`, il est inutile de vérifier également tous ses nœuds enfants, car le HTML ne peut pas être à l'intérieur des expressions. Dans ce cas, vous pouvez demander au traverseur de ne pas faire de récursion dans le nœud de classe : - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Php\ExpressionNode) { - return NodeTraverser::DontTraverseChildren; - } - // ... - }, -); -``` - -Si vous ne recherchez qu'un seul nœud spécifique, il est également possible d'interrompre entièrement la traversée après l'avoir trouvé. - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Nodes\ParametersNode) { - return NodeTraverser::StopTraversal; - } - // ... - }, -); -``` - - -Aides pour les nœuds .[#toc-node-helpers] ------------------------------------------ - -La classe [api:Latte\Compiler\NodeHelpers] fournit quelques méthodes qui peuvent trouver des noeuds AST qui satisfont un certain callback etc. Quelques exemples sont montrés : - -```php -use Latte\Compiler\NodeHelpers; - -// trouve tous les nœuds d'éléments HTML -$elements = NodeHelpers::find($ast, fn(Node $node) => $node instanceof Nodes\Html\ElementNode); - -// trouve le premier noeud de texte -$text = NodeHelpers::findFirst($ast, fn(Node $node) => $node instanceof Nodes\TextNode); - -// convertit le noeud de valeur PHP en valeur réelle -$value = NodeHelpers::toValue($node); - -// convertit un noeud textuel statique en chaîne de caractères -$text = NodeHelpers::toText($node); -``` diff --git a/latte/fr/custom-filters.texy b/latte/fr/custom-filters.texy new file mode 100644 index 0000000000..191f29e82d --- /dev/null +++ b/latte/fr/custom-filters.texy @@ -0,0 +1,231 @@ +Création de filtres personnalisés +********************************* + +.[perex] +Les filtres sont des outils puissants pour formater et modifier les données directement dans les templates Latte. Ils offrent une syntaxe claire utilisant le symbole pipe (`|`) pour transformer les variables ou les résultats d'expressions au format de sortie souhaité. + + +Que sont les filtres ? +====================== + +Les filtres dans Latte sont essentiellement des **fonctions PHP conçues spécifiquement pour transformer une valeur d'entrée en une valeur de sortie**. Ils sont appliqués à l'aide de la notation pipe (`|`) à l'intérieur des expressions de template (`{...}`). + +**Commodité :** Les filtres vous permettent d'encapsuler des tâches de formatage courantes (comme le formatage des dates, la modification de la casse, le raccourcissement) ou la manipulation de données dans des unités réutilisables. Au lieu de répéter du code PHP complexe dans vos templates, vous pouvez simplement appliquer un filtre : +```latte +{* Au lieu de PHP complexe pour raccourcir : *} +{$article->text|truncate:100} + +{* Au lieu de code pour formater la date : *} +{$event->startTime|date:'Y-m-d H:i'} + +{* Application de plusieurs transformations : *} +{$product->name|lower|capitalize} +``` + +**Lisibilité :** L'utilisation de filtres rend les templates plus clairs et plus axés sur la présentation, car la logique de transformation est déplacée vers la définition du filtre. + +**Sensibilité au contexte :** Un avantage clé des filtres dans Latte est leur capacité à être [sensibles au contexte |#Filtres contextuels]. Cela signifie qu'un filtre peut reconnaître le type de contenu avec lequel il travaille (HTML, JavaScript, texte brut, etc.) et appliquer la logique ou l'échappement approprié, ce qui est crucial pour la sécurité et l'exactitude, en particulier lors de la génération de HTML. + +**Intégration avec la logique applicative :** Tout comme les fonctions personnalisées, l'appelable PHP derrière un filtre peut être une closure, une méthode statique ou une méthode d'instance. Cela permet aux filtres d'accéder aux services applicatifs ou aux données si nécessaire, bien que leur objectif principal reste la *transformation de la valeur d'entrée*. + +Latte fournit par défaut un riche ensemble de [filtres standard |filters]. Les filtres personnalisés vous permettent d'étendre cet ensemble avec des formatages et des transformations spécifiques à votre projet. + +Si vous avez besoin d'effectuer une logique basée sur *plusieurs* entrées ou si vous n'avez pas de valeur principale à transformer, il est probablement plus approprié d'utiliser une [fonction personnalisée |custom-functions]. Si vous avez besoin de générer un balisage complexe ou de contrôler le flux du template, envisagez une [balise personnalisée |custom-tags]. + + +Création et enregistrement de filtres +===================================== + +Il existe plusieurs façons de définir et d'enregistrer des filtres personnalisés dans Latte. + + +Enregistrement direct via `addFilter()` +--------------------------------------- + +La manière la plus simple d'ajouter un filtre est d'utiliser la méthode `addFilter()` directement sur l'objet `Latte\Engine`. Vous spécifiez le nom du filtre (tel qu'il sera utilisé dans le template) et l'appelable PHP correspondant. + +```php +$latte = new Latte\Engine; + +// Filtre simple sans arguments +$latte->addFilter('initial', fn(string $s): string => mb_substr($s, 0, 1) . '.'); + +// Filtre avec un argument optionnel +$latte->addFilter('shortify', function (string $s, int $len = 10): string { + return mb_substr($s, 0, $len); +}); + +// Filtre traitant un tableau +$latte->addFilter('sum', fn(array $numbers): int|float => array_sum($numbers)); +``` + +**Utilisation dans le template :** + +```latte +{$name|initial} {* Affiche 'J.' si $name est 'John' *} +{$description|shortify} {* Utilise la longueur par défaut de 10 *} +{$description|shortify:50} {* Utilise la longueur 50 *} +{$prices|sum} {* Affiche la somme des éléments du tableau $prices *} +``` + +**Passage d'arguments :** + +La valeur à gauche du pipe (`|`) est toujours passée comme *premier* argument à la fonction filtre. Tous les paramètres spécifiés après les deux points (`:`) dans le template sont passés comme arguments suivants. + +```latte +{$text|shortify:30} +// Appelle la fonction PHP shortify($text, 30) +``` + + +Enregistrement via une extension +-------------------------------- + +Pour une meilleure organisation, en particulier lors de la création d'ensembles de filtres réutilisables ou de leur partage en tant que paquets, la méthode recommandée est de les enregistrer dans une [extension Latte |extending-latte#Latte Extension] : + +```php +namespace App\Latte; + +use Latte\Extension; + +class MyLatteExtension extends Extension +{ + public function getFilters(): array + { + return [ + 'initial' => $this->initial(...), + 'shortify' => $this->shortify(...), + ]; + } + + public function initial(string $s): string + { + return mb_substr($s, 0, 1) . '.'; + } + + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// Enregistrement +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +Cette approche maintient la logique de votre filtre encapsulée et l'enregistrement simple. + + +Utilisation du chargeur de filtres +---------------------------------- + +Latte permet d'enregistrer un chargeur de filtres à l'aide de `addFilterLoader()`. Il s'agit d'un seul appelable que Latte demandera pour tout nom de filtre inconnu lors de la compilation. Le chargeur retourne l'appelable PHP du filtre ou `null`. + +```php +$latte = new Latte\Engine; + +// Le chargeur peut créer/obtenir dynamiquement des appelables de filtre +$latte->addFilterLoader(function (string $name): ?callable { + if ($name === 'myLazyFilter') { + // Imaginez une initialisation coûteuse ici... + $service = get_some_expensive_service(); + return fn($value) => $service->process($value); + } + return null; +}); +``` + +Cette méthode était principalement destinée au chargement paresseux de filtres avec une initialisation très **coûteuse**. Cependant, les pratiques modernes d'injection de dépendances gèrent généralement les services paresseux plus efficacement. + +Les chargeurs de filtres ajoutent de la complexité et ne sont généralement pas recommandés au profit de l'enregistrement direct via `addFilter()` ou dans une extension via `getFilters()`. Utilisez les chargeurs uniquement si vous avez une raison sérieuse et spécifique liée à des problèmes de performance lors de l'initialisation des filtres qui ne peuvent pas être résolus autrement. + + +Filtres utilisant une classe avec attributs +------------------------------------------- + +Une autre manière élégante de définir des filtres est d'utiliser des méthodes dans votre [classe de paramètres de template |develop#Paramètres en tant que classe]. Ajoutez simplement l'attribut `#[Latte\Attributes\TemplateFilter]` à la méthode. + +```php +use Latte\Attributes\TemplateFilter; + +class TemplateParameters +{ + public function __construct( + public string $description, + // autres paramètres... + ) {} + + #[TemplateFilter] + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// Passage de l'objet au template +$params = new TemplateParameters(description: '...'); +$latte->render('template.latte', $params); +``` + +Latte reconnaîtra et enregistrera automatiquement les méthodes marquées de cet attribut lorsque l'objet `TemplateParameters` est passé au template. Le nom du filtre dans le template sera le même que le nom de la méthode (`shortify` dans ce cas). + +```latte +{* Utilisation du filtre défini dans la classe de paramètres *} +{$description|shortify:50} +``` + + +Filtres contextuels +=================== + +Parfois, un filtre a besoin de plus d'informations que la simple valeur d'entrée. Il peut avoir besoin de connaître le **type de contenu** de la chaîne avec laquelle il travaille (par exemple, HTML, JavaScript, texte brut) ou même de le modifier. C'est là qu'interviennent les filtres contextuels. + +Un filtre contextuel est défini de la même manière qu'un filtre normal, mais son **premier paramètre doit être** typé comme `Latte\Runtime\FilterInfo`. Latte reconnaît automatiquement cette signature et passe l'objet `FilterInfo` lors de l'appel du filtre. Les paramètres suivants reçoivent les arguments du filtre comme d'habitude. + +```php +use Latte\Runtime\FilterInfo; +use Latte\ContentType; + +$latte->addFilter('money', function (FilterInfo $info, float $amount): string { + // 1. Vérifiez le type de contenu d'entrée (facultatif, mais recommandé) + // Autorisez null (entrée variable) ou texte brut. Refusez si appliqué à du HTML, etc. + if (!in_array($info->contentType, [null, ContentType::Text], true)) { + $actualType = $info->contentType ?? 'mixed'; + throw new \RuntimeException( + "Le filtre |money a été utilisé dans un type de contenu incompatible $actualType. Attendu text ou null." + ); + } + + // 2. Effectuez la transformation + $formatted = number_format($amount, 2, '.', ',') . ' EUR'; + $htmlOutput = '' . htmlspecialchars($formatted) . ''; // Assurez un échappement correct ! + + // 3. Déclarez le type de contenu de sortie + $info->contentType = ContentType::Html; + + // 4. Retournez le résultat + return $htmlOutput; +}); +``` + +`$info->contentType` est une constante de chaîne de `Latte\ContentType` (par exemple, `ContentType::Html`, `ContentType::Text`, `ContentType::JavaScript`, etc.) ou `null` si le filtre est appliqué à une variable (`{$var|filter}`). Vous pouvez **lire** cette valeur pour vérifier le contexte d'entrée, et **écrire** dessus pour déclarer le type du contexte de sortie. + +En définissant le type de contenu sur HTML, vous indiquez à Latte que la chaîne retournée par votre filtre est du HTML sûr. Latte n'appliquera alors **pas** son échappement automatique par défaut à ce résultat. C'est crucial si votre filtre génère du balisage HTML. + +.[warning] +Si votre filtre génère du HTML, **vous êtes responsable de l'échappement correct de toutes les données d'entrée** utilisées dans ce HTML (comme dans le cas de l'appel `htmlspecialchars($formatted)` ci-dessus). Omettre cela peut créer des vulnérabilités XSS. Si votre filtre ne retourne que du texte brut, vous n'avez pas besoin de définir `$info->contentType`. + + +Filtres sur les blocs +--------------------- + +Tous les filtres appliqués aux [blocs |tags#block] *doivent* être contextuels. C'est parce que le contenu du bloc a un type de contenu défini (généralement HTML), dont le filtre doit être conscient. + +```latte +{block heading|money}1000{/block} +{* Le filtre 'money' recevra '1000' comme deuxième argument + et $info->contentType sera ContentType::Html *} +``` + +Les filtres contextuels offrent un contrôle puissant sur la manière dont les données sont traitées en fonction de leur contexte, permettant des fonctionnalités avancées et garantissant un comportement d'échappement correct, en particulier lors de la génération de contenu HTML. diff --git a/latte/fr/custom-functions.texy b/latte/fr/custom-functions.texy new file mode 100644 index 0000000000..5c742aca01 --- /dev/null +++ b/latte/fr/custom-functions.texy @@ -0,0 +1,144 @@ +Création de fonctions personnalisées +************************************ + +.[perex] +Ajoutez facilement des fonctions d'aide personnalisées aux templates Latte. Appelez la logique PHP directement dans les expressions pour les calculs, l'accès aux services ou la génération de contenu dynamique, ce qui maintient vos templates propres et performants. + + +Que sont les fonctions ? +======================== + +Les fonctions dans Latte vous permettent d'étendre l'ensemble des fonctions qui peuvent être appelées dans les expressions des templates (`{...}`). Vous pouvez les considérer comme des **fonctions PHP personnalisées disponibles uniquement à l'intérieur de vos templates Latte**. Cela apporte plusieurs avantages : + +**Commodité :** Vous pouvez définir une logique d'aide (comme des calculs, du formatage ou l'accès aux données de l'application) et l'appeler en utilisant une syntaxe de fonction simple et familière directement dans le template, tout comme vous appelleriez `strlen()` ou `date()` en PHP. + +```latte +{var $userInitials = initials($userName)} {* par ex. 'J. D.' *} + +{if hasPermission('article', 'edit')} + Éditer +{/if} +``` + +**Pas de pollution de l'espace global :** Contrairement à la définition d'une fonction globale réelle en PHP, les fonctions Latte n'existent que dans le contexte du rendu du template. Vous n'avez pas besoin d'encombrer l'espace de noms global PHP avec des assistants spécifiques aux templates. + +**Intégration avec la logique de l'application :** L'appelable PHP derrière une fonction Latte peut être n'importe quoi – une fonction anonyme, une méthode statique ou une méthode d'instance. Cela signifie que vos fonctions dans les templates peuvent facilement accéder aux services de l'application, aux bases de données, à la configuration ou à toute autre logique nécessaire en capturant des variables (dans le cas de fonctions anonymes) ou en utilisant l'injection de dépendances (dans le cas d'objets). L'exemple `hasPermission` ci-dessus le démontre clairement, car il appelle probablement un service d'autorisation en arrière-plan. + +**Remplacement des fonctions natives (facultatif) :** Vous pouvez même définir une fonction Latte avec le même nom qu'une fonction PHP native. Dans le template, votre propre version sera appelée à la place de la fonction originale. Cela peut être utile pour fournir un comportement spécifique au template ou assurer un traitement cohérent (par exemple, s'assurer que `strlen` est toujours compatible avec les caractères multi-octets). Utilisez cette fonctionnalité avec prudence pour éviter toute confusion. + +Par défaut, Latte autorise l'appel de *toutes* les fonctions PHP natives (sauf si elles sont restreintes par le [Sandbox |sandbox]). Les fonctions personnalisées étendent cette bibliothèque intégrée avec les besoins spécifiques de votre projet. + +Si vous ne transformez qu'une seule valeur, il peut être plus approprié d'utiliser un [filtre personnalisé |custom-filters]. + + +Création et enregistrement de fonctions +======================================= + +Comme pour les filtres, il existe plusieurs façons de définir et d'enregistrer des fonctions personnalisées. + + +Enregistrement direct via `addFunction()` +----------------------------------------- + +La méthode la plus simple consiste à utiliser `addFunction()` sur l'objet `Latte\Engine`. Vous spécifiez le nom de la fonction (tel qu'il apparaîtra dans le template) et l'appelable PHP correspondant. + +```php +$latte = new Latte\Engine; + +// Fonction d'aide simple +$latte->addFunction('initials', function (string $name): string { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; +}); +``` + +**Utilisation dans le template :** + +```latte +{var $userInitials = initials($userName)} +``` + +Les arguments de la fonction dans le template sont passés directement à l'appelable PHP dans le même ordre. Les fonctionnalités PHP telles que les indications de type, les valeurs par défaut et les paramètres variables (`...`) fonctionnent comme prévu. + + +Enregistrement via une extension +-------------------------------- + +Pour une meilleure organisation et réutilisabilité, enregistrez les fonctions dans une [extension Latte |extending-latte#Latte Extension]. Cette approche est recommandée pour les applications plus complexes ou les bibliothèques partagées. + +```php +namespace App\Latte; + +use Latte\Extension; +use Nette\Security\Authorizator; + +class MyLatteExtension extends Extension +{ + public function __construct( + // En supposant que le service Authorizator existe et est injecté + private Authorizator $authorizator, + ) { + } + + public function getFunctions(): array + { + // Enregistrement des méthodes comme fonctions Latte + return [ + 'hasPermission' => $this->hasPermission(...), + ]; + } + + public function hasPermission(string $resource, string $action): bool + { + return $this->authorizator->isAllowed($resource, $action); + } +} + +// Enregistrement (en supposant que $container contient le conteneur DI) +$extension = $container->getByType(App\Latte\MyLatteExtension::class); +$latte = new Latte\Engine; +$latte->addExtension($extension); +``` + +Cette approche illustre comment les fonctions définies dans Latte peuvent être soutenues par des méthodes d'objets, qui peuvent avoir leurs propres dépendances gérées par le conteneur d'injection de dépendances de votre application ou une factory. Cela maintient la logique de vos templates connectée au cœur de l'application tout en préservant une organisation claire. + + +Fonctions utilisant une classe avec attributs +--------------------------------------------- + +Tout comme les filtres, les fonctions peuvent être définies comme des méthodes dans votre [classe de paramètres de template |develop#Paramètres en tant que classe] à l'aide de l'attribut `#[Latte\Attributes\TemplateFunction]`. + +```php +use Latte\Attributes\TemplateFunction; + +class TemplateParameters +{ + public function __construct( + public string $userName, + // autres paramètres... + ) {} + + // Cette méthode sera disponible comme {initials(...)} dans le template + #[TemplateFunction] + public function initials(string $name): string + { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; + } +} + +// Passage de l'objet au template +$params = new TemplateParameters(userName: 'John Doe', /* ... */); +$latte->render('template.latte', $params); +``` + +Latte découvrira et enregistrera automatiquement les méthodes marquées de cet attribut lorsque l'objet de paramètres est passé au template. Le nom de la fonction dans le template correspond au nom de la méthode. + +```latte +{* Utilisation de la fonction définie dans la classe de paramètres *} +{var $inits = initials($userName)} +``` + +**Fonctions contextuelles ?** + +Contrairement aux filtres, il n'existe pas de concept direct de "fonctions contextuelles" qui recevraient un objet similaire à `FilterInfo`. Les fonctions opèrent au sein d'expressions et n'ont généralement pas besoin d'un accès direct au contexte de rendu ou aux informations sur le type de contenu de la même manière que les filtres appliqués aux blocs. diff --git a/latte/fr/custom-tags.texy b/latte/fr/custom-tags.texy new file mode 100644 index 0000000000..29c14a08a8 --- /dev/null +++ b/latte/fr/custom-tags.texy @@ -0,0 +1,1135 @@ +Création de balises personnalisées +********************************** + +.[perex] +Cette page fournit un guide complet pour créer des balises personnalisées dans Latte. Nous aborderons tout, des balises simples aux scénarios plus complexes avec du contenu imbriqué et des besoins d'analyse syntaxique spécifiques, en nous basant sur votre compréhension de la manière dont Latte compile les templates. + +Les balises personnalisées offrent le plus haut niveau de contrôle sur la syntaxe du template et la logique de rendu, mais elles constituent également le point d'extension le plus complexe. Avant de décider de créer votre propre balise, envisagez toujours s'il [n'existe pas de solution plus simple |extending-latte#Méthodes d extension de Latte] ou si une balise appropriée n'existe pas déjà dans [l'ensemble standard |tags]. N'utilisez des balises personnalisées que lorsque les alternatives plus simples ne suffisent pas à vos besoins. + + +Comprendre le processus de compilation +====================================== + +Pour créer efficacement des balises personnalisées, il est utile d'expliquer comment Latte traite les templates. Comprendre ce processus clarifie pourquoi les balises sont structurées de cette manière et comment elles s'intègrent dans le contexte plus large. + +La compilation d'un template dans Latte, de manière simplifiée, comprend ces étapes clés : + +1. **Analyse lexicale :** Le lexer lit le code source du template (fichier `.latte`) et le divise en une séquence de petites parties distinctes appelées **tokens** (par exemple, `{`, `foreach`, `$variable`, `}`, texte HTML, etc.). +2. **Analyse syntaxique (Parsing) :** Le parser prend ce flux de tokens et construit à partir de celui-ci une structure arborescente significative représentant la logique et le contenu du template. Cet arbre est appelé **arbre syntaxique abstrait (AST)**. +3. **Passes de compilation :** Avant de générer le code PHP, Latte exécute des [passes de compilation |compiler passes]. Ce sont des fonctions qui parcourent l'ensemble de l'AST et peuvent le modifier ou collecter des informations. Cette étape est cruciale pour des fonctionnalités telles que la sécurité ([Sandbox |sandbox]) ou l'optimisation. +4. **Génération de code :** Enfin, le compilateur parcourt l'AST (potentiellement modifié) et génère le code de classe PHP correspondant. Ce code PHP est ce qui rend réellement le template lors de l'exécution. +5. **Mise en cache :** Le code PHP généré est stocké sur le disque, ce qui rend les rendus ultérieurs très rapides, car les étapes 1 à 4 sont ignorées. + +En réalité, la compilation est un peu plus complexe. Latte **a deux** lexers et parseurs : un pour le template HTML et un autre pour le code de type PHP à l'intérieur des balises. De plus, l'analyse syntaxique ne se déroule pas après la tokenisation, mais le lexer et le parseur s'exécutent en parallèle dans deux "threads" et se coordonnent. Croyez-moi, programmer cela relevait de la science des fusées :-) + +L'ensemble du processus, du chargement du contenu du template, en passant par l'analyse syntaxique, jusqu'à la génération du fichier résultant, peut être séquencé avec ce code, avec lequel vous pouvez expérimenter et afficher les résultats intermédiaires : + +```php +$latte = new Latte\Engine; +$source = $latte->getLoader()->getContent($file); +$ast = $latte->parse($source); +$latte->applyPasses($ast); +$code = $latte->generate($ast, $file); +``` + + +Anatomie d'une balise +===================== + +La création d'une balise personnalisée entièrement fonctionnelle dans Latte implique plusieurs parties interconnectées. Avant de nous lancer dans l'implémentation, comprenons les concepts et la terminologie de base, en utilisant une analogie avec HTML et le Document Object Model (DOM). + + +Balises vs Nœuds (Analogie avec HTML) +------------------------------------- + +En HTML, nous écrivons des **balises** comme `

                                                              ` ou `

                                                              ...
                                                              `. Ces balises constituent la syntaxe dans le code source. Lorsque le navigateur analyse ce HTML, il crée une représentation en mémoire appelée **Document Object Model (DOM)**. Dans le DOM, les balises HTML sont représentées par des **nœuds** (spécifiquement des nœuds `Element` dans la terminologie du DOM JavaScript). Nous travaillons programmatiquement avec ces *nœuds* (par exemple, en utilisant `document.getElementById(...)` en JavaScript, qui renvoie un nœud Element). Une balise n'est qu'une représentation textuelle dans le fichier source ; un nœud est une représentation objet dans l'arbre logique. + +Latte fonctionne de manière similaire : + +- Dans le fichier de template `.latte`, vous écrivez des **balises Latte**, comme `{foreach ...}` et `{/foreach}`. C'est la syntaxe avec laquelle vous, en tant qu'auteur du template, travaillez. +- Lorsque Latte **analyse** le template, il construit un **Arbre Syntaxique Abstrait (AST)**. Cet arbre est composé de **nœuds**. Chaque balise Latte, élément HTML, morceau de texte ou expression dans le template devient un ou plusieurs nœuds dans cet arbre. +- La classe de base pour tous les nœuds dans l'AST est `Latte\Compiler\Node`. Tout comme le DOM a différents types de nœuds (Element, Text, Comment), l'AST de Latte a différents types de nœuds. Vous rencontrerez `Latte\Compiler\Nodes\TextNode` pour le texte statique, `Latte\Compiler\Nodes\Html\ElementNode` pour les éléments HTML, `Latte\Compiler\Nodes\Php\ExpressionNode` pour les expressions à l'intérieur des balises, et de manière cruciale pour les balises personnalisées, des nœuds héritant de `Latte\Compiler\Nodes\StatementNode`. + + +Pourquoi `StatementNode`? +------------------------- + +Les éléments HTML (`Html\ElementNode`) représentent principalement la structure et le contenu. Les expressions PHP (`Php\ExpressionNode`) représentent des valeurs ou des calculs. Mais qu'en est-il des balises Latte comme `{if}`, `{foreach}` ou notre propre `{datetime}` ? Ces balises *effectuent des actions*, contrôlent le flux du programme ou génèrent une sortie basée sur la logique. Ce sont des unités fonctionnelles qui font de Latte un puissant *moteur* de template, et non pas seulement un langage de balisage. + +En programmation, de telles unités effectuant des actions sont souvent appelées "statements" (instructions). Par conséquent, les nœuds représentant ces balises Latte fonctionnelles héritent généralement de `Latte\Compiler\Nodes\StatementNode`. Cela les distingue des nœuds purement structurels (comme les éléments HTML) ou des nœuds représentant des valeurs (comme les expressions). + + +Les composants clés +=================== + +Passons en revue les principaux composants nécessaires pour créer une balise personnalisée : + + +Fonction d'analyse de la balise +------------------------------- + +- Cette fonction PHP callable analyse la syntaxe de la balise Latte (`{...}`) dans le template source. +- Elle reçoit des informations sur la balise (comme son nom, sa position et s'il s'agit d'un n:attribut) via l'objet [api:Latte\Compiler\Tag]. +- Son outil principal pour analyser les arguments et les expressions à l'intérieur des délimiteurs de la balise est l'objet [api:Latte\Compiler\TagParser], accessible via `$tag->parser` (c'est un parseur différent de celui qui analyse l'ensemble du template). +- Pour les balises appariées, elle utilise `yield` pour signaler à Latte d'analyser le contenu interne entre la balise ouvrante et la balise fermante. +- L'objectif final de la fonction d'analyse est de créer et de retourner une instance de la **classe de nœud**, qui est ajoutée à l'AST. +- Il est courant (bien que non obligatoire) d'implémenter la fonction d'analyse comme une méthode statique (souvent nommée `create`) directement dans la classe de nœud correspondante. Cela maintient la logique d'analyse et la représentation du nœud proprement regroupées, permet l'accès aux éléments privés/protégés de la classe si nécessaire, et améliore l'organisation. + + +Classe de nœud +-------------- + +- Représente la *fonction logique* de votre balise dans l'**Arbre Syntaxique Abstrait (AST)**. +- Contient les informations analysées (comme les arguments ou le contenu) en tant que propriétés publiques. Ces propriétés contiennent souvent d'autres instances de `Node` (par exemple, `ExpressionNode` pour les arguments analysés, `AreaNode` pour le contenu analysé). +- La méthode `print(PrintContext $context): string` génère le *code PHP* (une instruction ou une série d'instructions) qui exécute l'action de la balise pendant le rendu du template. +- La méthode `getIterator(): \Generator` expose les nœuds enfants (arguments, contenu) pour le parcours par les **passes de compilation**. Elle doit fournir des références (`&`) pour permettre aux passes de potentiellement modifier ou remplacer les sous-nœuds. +- Une fois que l'ensemble du template est analysé en AST, Latte exécute une série de [passes de compilation |compiler-passes]. Ces passes parcourent l'*ensemble* de l'AST en utilisant la méthode `getIterator()` fournie par chaque nœud. Elles peuvent inspecter les nœuds, collecter des informations et même *modifier* l'arbre (par exemple, en changeant les propriétés publiques des nœuds ou en remplaçant complètement des nœuds). Cette conception, nécessitant un `getIterator()` complet, est cruciale. Elle permet à des fonctionnalités puissantes comme [Sandbox |sandbox] d'analyser et de potentiellement modifier le comportement de *n'importe quelle* partie du template, y compris vos propres balises, garantissant la sécurité et la cohérence. + + +Enregistrement via une extension +-------------------------------- + +- Vous devez informer Latte de votre nouvelle balise et de la fonction d'analyse à utiliser pour celle-ci. Cela se fait dans le cadre d'une [extension Latte |extending-latte#Latte Extension]. +- À l'intérieur de votre classe d'extension, vous implémentez la méthode `getTags(): array`. Cette méthode retourne un tableau associatif où les clés sont les noms des balises (par exemple, `'mytag'`, `'n:myattribute'`) et les valeurs sont les fonctions PHP callable représentant leurs fonctions d'analyse respectives (par exemple, `MyNamespace\DatetimeNode::create(...)`). + +Résumé : La **fonction d'analyse de la balise** transforme le *code source du template* de votre balise en un **nœud AST**. La **classe de nœud** peut ensuite transformer *elle-même* en *code PHP* exécutable pour le template compilé et expose ses sous-nœuds pour les **passes de compilation** via `getIterator()`. L'**enregistrement via une extension** lie le nom de la balise à la fonction d'analyse et en informe Latte. + +Explorons maintenant comment implémenter ces composants étape par étape. + + +Création d'une balise simple +============================ + +Lançons-nous dans la création de votre première balise Latte personnalisée. Nous commencerons par un exemple très simple : une balise nommée `{datetime}` qui affiche la date et l'heure actuelles. **Initialement, cette balise n'acceptera aucun argument**, mais nous l'améliorerons plus tard dans la section [#"Analyse des arguments de la balise"]. Elle n'a pas non plus de contenu interne. + +Cet exemple vous guidera à travers les étapes de base : définir la classe de nœud, implémenter ses méthodes `print()` et `getIterator()`, créer la fonction d'analyse, et enfin enregistrer la balise. + +**Objectif :** Implémenter `{datetime}` pour afficher la date et l'heure actuelles en utilisant la fonction PHP `date()`. + + +Création de la classe de nœud +----------------------------- + +Tout d'abord, nous avons besoin d'une classe pour représenter notre balise dans l'Arbre Syntaxique Abstrait (AST). Comme discuté précédemment, nous héritons de `Latte\Compiler\Nodes\StatementNode`. + +Créez un fichier (par exemple, `DatetimeNode.php`) et définissez la classe : + +```php +node = new self; + return $node; + } + + /** + * Génère le code PHP qui sera exécuté lors du rendu du template. + */ + public function print(PrintContext $context): string + { + return $context->format( + 'echo date(\'Y-m-d H:i:s\') %line;', + $this->position, + ); + } + + /** + * Fournit l'accès aux nœuds enfants pour les passes de compilation de Latte. + */ + public function &getIterator(): \Generator + { + false && yield; + } +} +``` + +Lorsque Latte rencontre `{datetime}` dans un template, il appelle la fonction d'analyse `create()`. Sa tâche est de retourner une instance de `DatetimeNode`. + +La méthode `print()` génère le code PHP qui sera exécuté lors du rendu du template. Nous appelons la méthode `$context->format()`, qui assemble la chaîne de code PHP résultante pour le template compilé. Le premier argument, `'echo date('Y-m-d H:i:s') %line;'`, est un masque dans lequel les paramètres suivants sont insérés. Le placeholder `%line` indique à la méthode `format()` d'utiliser le deuxième argument, qui est `$this->position`, et d'insérer un commentaire comme `/* line 15 */`, qui relie le code PHP généré à la ligne originale du template, ce qui est crucial pour le débogage. + +La propriété `$this->position` est héritée de la classe de base `Node` et est automatiquement définie par le parseur Latte. Elle contient un objet [api:Latte\Compiler\Position] qui indique où la balise a été trouvée dans le fichier source `.latte`. + +La méthode `getIterator()` est cruciale pour les passes de compilation. Elle doit fournir tous les nœuds enfants, mais notre simple `DatetimeNode` n'a actuellement ni arguments ni contenu, donc pas de nœuds enfants. Cependant, la méthode doit toujours exister et être un générateur, c'est-à-dire que le mot-clé `yield` doit être présent d'une manière ou d'une autre dans le corps de la méthode. + + +Enregistrement via une extension +-------------------------------- + +Enfin, informons Latte de la nouvelle balise. Créez une [classe d'extension |extending-latte#Latte Extension] (par exemple, `MyLatteExtension.php`) et enregistrez la balise dans sa méthode `getTags()`. + +```php + Map: 'nom-balise' => fonction-analyse + */ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + // Enregistrez plus de balises ici plus tard + ]; + } +} +``` + +Ensuite, enregistrez cette extension dans le moteur Latte : + +```php +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +Créez un template : + +```latte +

                                                              Page générée : {datetime}

                                                              +``` + +Sortie attendue : `

                                                              Page générée : 2023-10-27 11:00:00

                                                              ` + + +Résumé de cette phase +--------------------- + +Nous avons créé avec succès une balise personnalisée de base `{datetime}`. Nous avons défini sa représentation dans l'AST (`DatetimeNode`), géré son analyse (`create()`), spécifié comment il devait générer le code PHP (`print()`), assuré que ses enfants sont accessibles pour le parcours (`getIterator()`), et l'avons enregistré dans Latte. + +Dans la section suivante, nous améliorerons cette balise pour accepter des arguments et montrerons comment analyser les expressions et gérer les nœuds enfants. + + +Analyse des arguments de la balise +================================== + +Notre balise simple `{datetime}` fonctionne, mais n'est pas très flexible. Améliorons-la pour qu'elle accepte un argument optionnel : une chaîne de formatage pour la fonction `date()`. La syntaxe requise sera `{datetime $format}`. + +**Objectif :** Modifier `{datetime}` pour accepter une expression PHP optionnelle comme argument, qui sera utilisée comme chaîne de formatage pour `date()`. + + +Présentation de `TagParser` +--------------------------- + +Avant de modifier le code, il est important de comprendre l'outil que nous allons utiliser [api:Latte\Compiler\TagParser]. Lorsque le parseur principal de Latte (`TemplateParser`) rencontre une balise Latte comme `{datetime ...}` ou un n:attribut, il délègue l'analyse du contenu *à l'intérieur* de la balise (la partie entre `{` et `}` ou la valeur de l'attribut) à un `TagParser` spécialisé. + +Ce `TagParser` travaille exclusivement avec les **arguments de la balise**. Sa tâche est de traiter les tokens représentant ces arguments. Il est crucial qu'il **doive traiter tout le contenu** qui lui est fourni. Si votre fonction d'analyse se termine mais que le `TagParser` n'a pas atteint la fin des arguments (vérifié via `$tag->parser->isEnd()`), Latte lèvera une exception, car cela indique qu'il reste des tokens inattendus à l'intérieur de la balise. Inversement, si une balise *nécessite* des arguments, vous devriez appeler `$tag->expectArguments()` au début de votre fonction d'analyse. Cette méthode vérifie si des arguments sont présents et lève une exception utile si la balise a été utilisée sans aucun argument. + +`TagParser` offre des méthodes utiles pour analyser différents types d'arguments : + +- `parseExpression(): ExpressionNode`: Analyse une expression de type PHP (variables, littéraux, opérateurs, appels de fonctions/méthodes, etc.). Gère le sucre syntaxique de Latte, comme le traitement des chaînes alphanumériques simples comme des chaînes entre guillemets (par exemple, `foo` est analysé comme s'il s'agissait de `'foo'`). +- `parseUnquotedStringOrExpression(): ExpressionNode`: Analyse soit une expression standard, soit une *chaîne non guillemetée*. Les chaînes non guillemetées sont des séquences autorisées par Latte sans guillemets, souvent utilisées pour des choses comme les chemins de fichiers (par exemple, `{include ../file.latte}`). S'il analyse une chaîne non guillemetée, il retourne un `StringNode`. +- `parseArguments(): ArrayNode`: Analyse les arguments séparés par des virgules, potentiellement avec des clés, comme `10, name: 'John', true`. +- `parseModifier(): ModifierNode`: Analyse les filtres comme `|upper|truncate:10`. +- `parseType(): ?SuperiorTypeNode`: Analyse les indications de type PHP comme `int`, `?string`, `array|Foo`. + +Pour des besoins d'analyse plus complexes ou de bas niveau, vous pouvez interagir directement avec le [flux de tokens |api:Latte\Compiler\TokenStream] via `$tag->parser->stream`. Cet objet fournit des méthodes pour inspecter et traiter les tokens individuels : + +- `$tag->parser->stream->is(...): bool`: Vérifie si le token *actuel* correspond à l'un des types spécifiés (par exemple, `Token::Php_Variable`) ou des valeurs littérales (par exemple, `'as'`) sans le consommer. Utile pour regarder en avant. +- `$tag->parser->stream->consume(...): Token`: Consomme le token *actuel* et avance la position du flux. Si des types/valeurs de tokens attendus sont fournis comme arguments et que le token actuel ne correspond pas, lève une `CompileException`. Utilisez ceci lorsque vous *attendez* un certain token. +- `$tag->parser->stream->tryConsume(...): ?Token`: Tente de consommer le token *actuel* *uniquement si* il correspond à l'un des types/valeurs spécifiés. S'il correspond, consomme le token et le retourne. S'il ne correspond pas, laisse la position du flux inchangée et retourne `null`. Utilisez ceci pour les tokens optionnels ou lorsque vous choisissez entre différentes voies syntaxiques. + + +Mise à jour de la fonction d'analyse `create()` +----------------------------------------------- + +Avec cette compréhension, modifions la méthode `create()` dans `DatetimeNode` pour analyser l'argument de format optionnel en utilisant `$tag->parser`. + +```php +node = new self; + + // Vérifions s'il y a des tokens + if (!$tag->parser->isEnd()) { + // Analysons l'argument comme une expression de type PHP en utilisant TagParser. + $node->format = $tag->parser->parseExpression(); + } + + return $node; + } + + // ... les méthodes print() et getIterator() seront mises à jour ensuite ... +} +``` + +Nous avons ajouté une propriété publique `$format`. Dans `create()`, nous utilisons maintenant `$tag->parser->isEnd()` pour vérifier si des arguments *existent*. Si c'est le cas, `$tag->parser->parseExpression()` traite les tokens pour l'expression. Étant donné que `TagParser` doit traiter tous les tokens d'entrée, Latte lèvera automatiquement une erreur si l'utilisateur écrit quelque chose d'inattendu après l'expression de format (par exemple, `{datetime 'Y-m-d', unexpected}`). + + +Mise à jour de la méthode `print()` +----------------------------------- + +Modifions maintenant la méthode `print()` pour utiliser l'expression de format analysée stockée dans `$this->format`. Si aucun format n'a été fourni (`$this->format` est `null`), nous devrions utiliser une chaîne de formatage par défaut, par exemple `'Y-m-d H:i:s'`. + +```php + public function print(PrintContext $context): string + { + $formatNode = $this->format ?? new StringNode('Y-m-d H:i:s'); + + // %node imprime la représentation en code PHP de $formatNode. + return $context->format( + 'echo date(%node) %line;', + $formatNode, + $this->position + ); + } +``` + +Dans la variable `$formatNode`, nous stockons le nœud AST représentant la chaîne de formatage pour la fonction PHP `date()`. Nous utilisons ici l'opérateur de coalescence nulle (`??`). Si l'utilisateur a fourni un argument dans le template (par exemple, `{datetime 'd.m.Y'}`), alors la propriété `$this->format` contient le nœud correspondant (dans ce cas, un `StringNode` avec la valeur `'d.m.Y'`), et ce nœud est utilisé. Si l'utilisateur n'a pas fourni d'argument (il a juste écrit `{datetime}`), la propriété `$this->format` est `null`, et à la place, nous créons un nouveau `StringNode` avec le format par défaut `'Y-m-d H:i:s'`. Cela garantit que `$formatNode` contient toujours un nœud AST valide pour le format. + +Dans le masque `'echo date(%node) %line;'`, un nouveau placeholder `%node` est utilisé, qui indique à la méthode `format()` de prendre le premier argument suivant (qui est notre `$formatNode`), d'appeler sa méthode `print()` (qui retournera sa représentation en code PHP) et d'insérer le résultat à la position du placeholder. + + +Implémentation de `getIterator()` pour les sous-nœuds +----------------------------------------------------- + +Notre `DatetimeNode` a maintenant un nœud enfant : l'expression `$format`. Nous **devons** rendre ce nœud enfant accessible aux passes de compilation en le fournissant dans la méthode `getIterator()`. N'oubliez pas de fournir une *référence* (`&`) pour permettre aux passes de potentiellement remplacer le nœud. + +```php + public function &getIterator(): \Generator + { + if ($this->format) { + yield $this->format; + } + } +``` + +Pourquoi est-ce crucial ? Imaginez une passe Sandbox qui doit vérifier si l'argument `$format` ne contient pas un appel de fonction interdit (par exemple, `{datetime dangerousFunction()}`). Si `getIterator()` ne fournit pas `$this->format`, la passe Sandbox ne verrait jamais l'appel `dangerousFunction()` à l'intérieur de l'argument de notre balise, créant ainsi une faille de sécurité potentielle. En le fournissant, nous permettons à Sandbox (et aux autres passes) d'inspecter et potentiellement de modifier le nœud d'expression `$format`. + + +Utilisation de la balise améliorée +---------------------------------- + +La balise gère maintenant correctement l'argument optionnel : + +```latte +Format par défaut : {datetime} +Format personnalisé : {datetime 'd.m.Y'} +Utilisation d'une variable : {datetime $userDateFormatPreference} + +{* Ceci provoquerait une erreur après l'analyse de 'd.m.Y', car ", foo" est inattendu *} +{* {datetime 'd.m.Y', foo} *} +``` + +Ensuite, nous examinerons la création de balises appariées qui traitent le contenu entre elles. + + +Gestion des balises appariées +============================= + +Jusqu'à présent, notre balise `{datetime}` était *auto-fermante* (conceptuellement). Elle n'a pas de contenu entre une balise ouvrante et une balise fermante. Cependant, de nombreuses balises utiles fonctionnent avec un bloc de contenu de template. Celles-ci sont appelées **balises appariées**. Les exemples incluent `{if}...{/if}`, `{block}...{/block}` ou une balise personnalisée que nous allons créer maintenant : `{debug}...{/debug}`. + +Cette balise nous permettra d'inclure des informations de débogage dans nos templates, qui ne devraient être visibles qu'en cours de développement. + +**Objectif :** Créer une balise appariée `{debug}` dont le contenu n'est rendu que lorsqu'un indicateur spécifique de "mode développement" est actif. + + +Présentation des fournisseurs +----------------------------- + +Parfois, vos balises ont besoin d'accéder à des données ou des services qui ne sont pas passés directement comme paramètres de template. Par exemple, déterminer si l'application est en mode développement, accéder à l'objet utilisateur ou obtenir des valeurs de configuration. Latte fournit un mécanisme appelé **fournisseurs** (Providers) à cet effet. + +Les fournisseurs sont enregistrés dans votre [extension |extending-latte#Latte Extension] en utilisant la méthode `getProviders()`. Cette méthode retourne un tableau associatif où les clés sont les noms sous lesquels les fournisseurs seront accessibles dans le code d'exécution du template, et les valeurs sont les données ou objets réels. + +À l'intérieur du code PHP généré par la méthode `print()` de votre balise, vous pouvez accéder à ces fournisseurs via la propriété spéciale de l'objet `$this->global`. Comme cette propriété est partagée entre toutes les extensions, il est de bonne pratique de **préfixer les noms de vos fournisseurs** pour éviter les collisions de noms potentielles avec les fournisseurs clés de Latte ou les fournisseurs d'autres extensions tierces. Une convention courante consiste à utiliser un préfixe court et unique lié à votre fournisseur ou au nom de l'extension. Pour notre exemple, nous utiliserons le préfixe `app` et l'indicateur de mode développement sera disponible sous `$this->global->appDevMode`. + + +Le mot-clé `yield` pour l'analyse du contenu +-------------------------------------------- + +Comment disons-nous au parseur Latte de traiter le contenu *entre* `{debug}` et `{/debug}` ? C'est là qu'intervient le mot-clé `yield`. + +Lorsque `yield` est utilisé dans la fonction `create()`, la fonction devient un [générateur PHP |https://www.php.net/manual/en/language.generators.overview.php]. Son exécution est suspendue et le contrôle est rendu au `TemplateParser` principal. Le `TemplateParser` continue alors à analyser le contenu du template *jusqu'à* ce qu'il rencontre la balise fermante correspondante (`{/debug}` dans notre cas). + +Une fois la balise fermante trouvée, le `TemplateParser` reprend l'exécution de notre fonction `create()` juste après l'instruction `yield`. La valeur *retournée* par l'instruction `yield` est un tableau contenant deux éléments : + +1. Un `AreaNode` représentant le contenu analysé entre les balises ouvrante et fermante. +2. L'objet `Tag` représentant la balise fermante (par exemple, `{/debug}`). + +Créons la classe `DebugNode` et sa méthode `create` utilisant `yield`. + +```php +node = new self; + + // Suspendre l'analyse, obtenir le contenu interne et la balise fermante lorsque {/debug} est trouvé + [$node->content, $endTag] = yield; + + return $node; + } + + // ... print() et getIterator() seront implémentés ensuite ... +} +``` + +Note : `$endTag` est `null` si la balise est utilisée comme n:attribut, c'est-à-dire `
                                                              ...
                                                              `. + + +Implémentation de `print()` pour le rendu conditionnel +------------------------------------------------------ + +La méthode `print()` doit maintenant générer du code PHP qui vérifie le fournisseur `appDevMode` à l'exécution et n'exécute le code pour le contenu interne que si l'indicateur est vrai. + +```php + public function print(PrintContext $context): string + { + // Génère une instruction PHP 'if' qui vérifie le fournisseur à l'exécution + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + // Si en mode développement, imprime le contenu interne + %node + } + + XX, + $this->position, // Pour le commentaire %line + $this->content, // Le nœud contenant l'AST du contenu interne + ); + } +``` + +C'est simple. Nous utilisons `PrintContext::format()` pour créer une instruction PHP `if` standard. À l'intérieur de l'`if`, nous plaçons le placeholder `%node` pour `$this->content`. Latte appellera récursivement `$this->content->print($context)` pour générer le code PHP pour la partie interne de la balise, mais seulement si `$this->global->appDevMode` est évalué à vrai à l'exécution. + + +Implémentation de `getIterator()` pour le contenu +------------------------------------------------- + +Comme pour le nœud d'argument dans l'exemple précédent, notre `DebugNode` a maintenant un nœud enfant : `AreaNode $content`. Nous devons le rendre accessible en le fournissant dans `getIterator()` : + +```php + public function &getIterator(): \Generator + { + // Fournit une référence au nœud de contenu + yield $this->content; + } +``` + +Cela permet aux passes de compilation de descendre dans le contenu de notre balise `{debug}`, ce qui est important même si le contenu est rendu conditionnellement. Par exemple, Sandbox doit analyser le contenu, que `appDevMode` soit vrai ou faux. + + +Enregistrement et utilisation +----------------------------- + +Enregistrez la balise et le fournisseur dans votre extension : + +```php +class MyLatteExtension extends Extension +{ + // Supposons que $isDevelopmentMode est déterminé quelque part (par ex. depuis la configuration) + public function __construct( + private bool $isDevelopmentMode, + ) { + } + + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), // Enregistrement de la nouvelle balise + ]; + } + + public function getProviders(): array + { + return [ + 'appDevMode' => $this->isDevelopmentMode, // Enregistrement du fournisseur + ]; + } +} + +// Lors de l'enregistrement de l'extension : +$isDev = true; // Déterminez ceci en fonction de l'environnement de votre application +$latte->addExtension(new App\Latte\MyLatteExtension($isDev)); +``` + +Et son utilisation dans le template : + +```latte +

                                                              Contenu normal toujours visible.

                                                              + +{debug} +
                                                              + ID de l'utilisateur actuel : {$user->id} + Heure de la requête : {=time()} +
                                                              +{/debug} + +

                                                              Autre contenu normal.

                                                              +``` + + +Intégration des n:attributs +--------------------------- + +Latte offre une notation abrégée pratique pour de nombreuses balises appariées : les [n:attributs |syntax#n:attributs]. Si vous avez une balise appariée comme `{tag}...{/tag}` et que vous souhaitez que son effet s'applique directement à un seul élément HTML, vous pouvez souvent l'écrire de manière plus concise comme un attribut `n:tag` sur cet élément. + +Pour la plupart des balises appariées standard que vous définissez (comme notre `{debug}`), Latte activera automatiquement la version d'attribut `n:` correspondante. Vous n'avez rien à faire de plus lors de l'enregistrement : + +```latte +{* Utilisation standard de la balise appariée *} +{debug}
                                                              Informations de débogage
                                                              {/debug} + +{* Utilisation équivalente avec n:attribut *} +
                                                              Informations de débogage
                                                              +``` + +Les deux versions rendront le `
                                                              ` uniquement si `$this->global->appDevMode` est vrai. Les préfixes `inner-` et `tag-` fonctionnent également comme prévu. + +Parfois, la logique de votre balise peut avoir besoin de se comporter légèrement différemment selon qu'elle est utilisée comme une balise appariée standard ou comme un n:attribut, ou si un préfixe comme `n:inner-tag` ou `n:tag-tag` est utilisé. L'objet `Latte\Compiler\Tag`, passé à votre fonction d'analyse `create()`, fournit ces informations : + +- `$tag->isNAttribute(): bool`: Retourne `true` si la balise est analysée comme un n:attribut +- `$tag->prefix: ?string`: Retourne le préfixe utilisé avec le n:attribut, qui peut être `null` (pas un n:attribut), `Tag::PrefixNone`, `Tag::PrefixInner` ou `Tag::PrefixTag` + +Maintenant que nous comprenons les balises simples, l'analyse des arguments, les balises appariées, les fournisseurs et les n:attributs, abordons un scénario plus complexe impliquant des balises imbriquées dans d'autres balises, en utilisant notre balise `{debug}` comme point de départ. + + +Balises intermédiaires +====================== + +Certaines balises appariées permettent ou même nécessitent que d'autres balises apparaissent *à l'intérieur* d'elles avant la balise fermante finale. Celles-ci sont appelées **balises intermédiaires**. Les exemples classiques incluent `{if}...{elseif}...{else}...{/if}` ou `{switch}...{case}...{default}...{/switch}`. + +Étendons notre balise `{debug}` pour prendre en charge une clause `{else}` optionnelle, qui sera rendue lorsque l'application *n'est pas* en mode développement. + +**Objectif :** Modifier `{debug}` pour prendre en charge une balise intermédiaire optionnelle `{else}`. La syntaxe finale devrait être `{debug} ... {else} ... {/debug}`. + + +Analyse des balises intermédiaires avec `yield` +----------------------------------------------- + +Nous savons déjà que `yield` suspend la fonction d'analyse `create()` et retourne le contenu analysé ainsi que la balise fermante. Cependant, `yield` offre plus de contrôle : vous pouvez lui fournir un tableau de *noms de balises intermédiaires*. Lorsque le parseur rencontre l'une de ces balises spécifiées **au même niveau d'imbrication** (c'est-à-dire comme enfants directs de la balise parente, pas à l'intérieur d'autres blocs ou balises à l'intérieur), il arrête également l'analyse. + +Lorsque l'analyse s'arrête à cause d'une balise intermédiaire, elle arrête d'analyser le contenu, reprend le générateur `create()` et renvoie le contenu partiellement analysé et la **balise intermédiaire** elle-même (au lieu de la balise fermante finale). Notre fonction `create()` peut alors traiter cette balise intermédiaire (par exemple, analyser ses arguments si elle en avait) et utiliser à nouveau `yield` pour analyser la *partie suivante* du contenu jusqu'à la balise fermante *finale* ou une autre balise intermédiaire attendue. + +Modifions `DebugNode::create()` pour attendre `{else}` : + +```php +node = new self; + + // yield et attendre soit {/debug} soit {else} + [$node->thenContent, $nextTag] = yield ['else']; + + // Vérifier si la balise à laquelle nous nous sommes arrêtés était {else} + if ($nextTag?->name === 'else') { + // Yield à nouveau pour analyser le contenu entre {else} et {/debug} + [$node->elseContent, $endTag] = yield; + } + + return $node; + } + + // ... print() et getIterator() seront mis à jour ensuite ... +} +``` + +Maintenant, `yield ['else']` dit à Latte d'arrêter l'analyse non seulement pour `{/debug}`, mais aussi pour `{else}`. Si `{else}` est trouvé, `$nextTag` contiendra l'objet `Tag` pour `{else}`. Ensuite, nous utilisons à nouveau `yield` sans arguments, ce qui signifie que nous n'attendons maintenant que la balise finale `{/debug}`, et nous stockons le résultat dans `$node->elseContent`. Si `{else}` n'a pas été trouvé, `$nextTag` serait le `Tag` pour `{/debug}` (ou `null` s'il est utilisé comme n:attribut) et `$node->elseContent` resterait `null`. + + +Implémentation de `print()` avec `{else}` +----------------------------------------- + +La méthode `print()` doit refléter la nouvelle structure. Elle devrait générer une instruction PHP `if/else` basée sur le fournisseur `appDevMode`. + +```php + public function print(PrintContext $context): string + { + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + %node // Code pour la branche 'then' (contenu {debug}) + } else { + %node // Code pour la branche 'else' (contenu {else}) + } + + XX, + $this->position, // Numéro de ligne pour la condition 'if' + $this->thenContent, // Premier placeholder %node + $this->elseContent ?? new NopNode, // Deuxième placeholder %node + ); + } +``` + +Ceci est une structure PHP `if/else` standard. Nous utilisons `%node` deux fois ; `format()` remplace les nœuds fournis séquentiellement. Nous utilisons `?? new NopNode` pour éviter les erreurs si `$this->elseContent` est `null` – `NopNode` n'imprime simplement rien. + + +Implémentation de `getIterator()` pour les deux contenus +-------------------------------------------------------- + +Nous avons maintenant potentiellement deux nœuds enfants de contenu (`$thenContent` et `$elseContent`). Nous devons fournir les deux s'ils existent : + +```php + public function &getIterator(): \Generator + { + yield $this->thenContent; + if ($this->elseContent) { + yield $this->elseContent; + } + } +``` + + +Utilisation de la balise améliorée +---------------------------------- + +La balise peut maintenant être utilisée avec la clause `{else}` optionnelle : + +```latte +{debug} +

                                                              Affichage des informations de débogage car devMode est ACTIVÉ.

                                                              +{else} +

                                                              Les informations de débogage sont masquées car devMode est DÉSACTIVÉ.

                                                              +{/debug} +``` + + +Gestion de l'état et de l'imbrication +===================================== + +Nos exemples précédents (`{datetime}`, `{debug}`) étaient relativement sans état dans leurs méthodes `print()`. Ils imprimaient directement le contenu ou effectuaient une simple vérification conditionnelle basée sur un fournisseur global. Cependant, de nombreuses balises doivent gérer une certaine forme d'**état** pendant le rendu ou impliquent l'évaluation d'expressions utilisateur qui ne devraient être exécutées qu'une seule fois pour des raisons de performance ou d'exactitude. De plus, nous devons considérer ce qui se passe lorsque nos balises personnalisées sont **imbriquées**. + +Illustrons ces concepts en créant une balise `{repeat $count}...{/repeat}`. Cette balise répétera son contenu interne `$count` fois. + +**Objectif :** Implémenter `{repeat $count}` qui répète son contenu le nombre de fois spécifié. + + +Le besoin de variables temporaires & uniques +-------------------------------------------- + +Imaginez que l'utilisateur écrive : + +```latte +{repeat rand(1, 5)} Contenu {/repeat} +``` + +Si nous générions naïvement une boucle PHP `for` de cette manière dans notre méthode `print()` : + +```php +// Code généré simplifié et INCORRECT +for ($i = 0; $i < rand(1, 5); $i++) { + // imprimer le contenu +} +``` +Ce serait faux ! L'expression `rand(1, 5)` serait **réévaluée à chaque itération de la boucle**, conduisant à un nombre imprévisible de répétitions. Nous devons évaluer l'expression `$count` *une fois* avant le début de la boucle et stocker son résultat. + +Nous générerons du code PHP qui évalue d'abord l'expression de comptage et la stocke dans une **variable temporaire d'exécution**. Pour éviter les collisions avec les variables définies par l'utilisateur du template *et* les variables internes de Latte (comme `$ʟ_...`), nous utiliserons la convention de préfixer nos variables temporaires par **`$__` (double soulignement)**. + +Le code généré ressemblerait alors à ceci : + +```php +$__count = rand(1, 5); +for ($__i = 0; $__i < $__count; $__i++) { + // imprimer le contenu +} +``` + +Considérons maintenant l'imbrication : + +```latte +{repeat $countA} {* Boucle externe *} + {repeat $countB} {* Boucle interne *} + ... + {/repeat} +{/repeat} +``` + +Si les balises `{repeat}` externe et interne généraient du code utilisant les *mêmes* noms de variables temporaires (par exemple, `$__count` et `$__i`), la boucle interne écraserait les variables de la boucle externe, brisant la logique. + +Nous devons nous assurer que les variables temporaires générées pour chaque instance de la balise `{repeat}` sont **uniques**. Nous y parvenons en utilisant `PrintContext::generateId()`. Cette méthode retourne un entier unique pendant la phase de compilation. Nous pouvons ajouter cet ID aux noms de nos variables temporaires. + +Ainsi, au lieu de `$__count`, nous générerons `$__count_1` pour la première balise repeat, `$__count_2` pour la deuxième, etc. De même, pour le compteur de boucle, nous utiliserons `$__i_1`, `$__i_2`, etc. + + +Implémentation de `RepeatNode` +------------------------------ + +Créons la classe de nœud. + +```php +expectArguments(); // s'assure que $count est fourni + $node = $tag->node = new self; + // Analyse l'expression de comptage + $node->count = $tag->parser->parseExpression(); + // Obtention du contenu interne + [$node->content] = yield; + return $node; + } + + /** + * Génère une boucle PHP 'for' avec des noms de variables uniques. + */ + public function print(PrintContext $context): string + { + // Génération de noms de variables uniques + $id = $context->generateId(); + $countVar = '$__count_' . $id; // par ex. $__count_1, $__count_2, etc. + $iteratorVar = '$__i_' . $id; // par ex. $__i_1, $__i_2, etc. + + return $context->format( + <<<'XX' + // Évaluation de l'expression de comptage *une fois* et stockage + %raw = (int) (%node); + // Boucle utilisant le comptage stocké et une variable d'itération unique + for (%raw = 0; %2.raw < %0.raw; %2.raw++) %line { + %node // Rendu du contenu interne + } + + XX, + $countVar, // %0 - Variable pour stocker le comptage + $this->count, // %1 - Nœud d'expression pour le comptage + $iteratorVar, // %2 - Nom de la variable d'itération de la boucle + $this->position, // %3 - Commentaire avec le numéro de ligne pour la boucle elle-même + $this->content // %4 - Nœud du contenu interne + ); + } + + /** + * Fournit les nœuds enfants (expression de comptage et contenu). + */ + public function &getIterator(): \Generator + { + yield $this->count; + yield $this->content; + } +} +``` + +La méthode `create()` analyse l'expression `$count` requise en utilisant `parseExpression()`. D'abord, `$tag->expectArguments()` est appelé. Cela garantit que l'utilisateur a fourni *quelque chose* après `{repeat}`. Bien que `$tag->parser->parseExpression()` échouerait si rien n'était fourni, le message d'erreur pourrait concerner une syntaxe inattendue. L'utilisation de `expectArguments()` fournit une erreur beaucoup plus claire, indiquant spécifiquement que les arguments manquent pour la balise `{repeat}`. + +La méthode `print()` génère le code PHP responsable de l'exécution de la logique de répétition à l'exécution. Elle commence par générer des noms uniques pour les variables PHP temporaires dont elle aura besoin. + +La méthode `$context->format()` est appelée avec un nouveau placeholder `%raw`, qui insère la *chaîne brute* fournie comme argument correspondant. Ici, elle insère le nom de variable unique stocké dans `$countVar` (par exemple, `$__count_1`). Et qu'en est-il de `%0.raw` et `%2.raw` ? Ceci démontre les **placeholders positionnels**. Au lieu de simplement `%raw`, qui prend le *prochain* argument brut disponible, `%2.raw` prend explicitement l'argument à l'index 2 (qui est `$iteratorVar`) et insère sa valeur de chaîne brute. Cela nous permet de réutiliser la chaîne `$iteratorVar` sans la passer plusieurs fois dans la liste d'arguments de `format()`. + +Cet appel `format()` soigneusement construit génère une boucle PHP efficace et sûre qui gère correctement l'expression de comptage et évite les collisions de noms de variables même lorsque les balises `{repeat}` sont imbriquées. + + +Enregistrement et utilisation +----------------------------- + +Enregistrez la balise dans votre extension : + +```php +use App\Latte\RepeatNode; + +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), // Enregistrement de la balise repeat + ]; + } +} +``` + +Utilisez-la dans le template, y compris l'imbrication : + +```latte +{var $rows = rand(5, 7)} +{var $cols = rand(3, 5)} + +{repeat $rows} +
                                                              + {repeat $cols} + + {/repeat} + +{/repeat} +``` + +Cet exemple démontre comment gérer l'état (compteurs de boucle) et les problèmes potentiels d'imbrication en utilisant des variables temporaires préfixées par `$__` et rendues uniques avec un ID de `PrintContext::generateId()`. + + +n:attributs purs +---------------- + +Alors que de nombreux `n:attributs` comme `n:if` ou `n:foreach` servent de raccourcis pratiques pour leurs homologues de balises appariées (`{if}...{/if}`, `{foreach}...{/foreach}`), Latte permet également de définir des balises qui *n'existent que* sous forme de n:attribut. Ceux-ci sont souvent utilisés pour modifier les attributs ou le comportement de l'élément HTML auquel ils sont attachés. + +Les exemples standard intégrés à Latte incluent [`n:class` |tags#n:class], qui aide à construire dynamiquement l'attribut `class`, et [`n:attr` |tags#n:attr], qui peut définir plusieurs attributs arbitraires. + +Créons notre propre n:attribut pur : `n:confirm`, qui ajoutera une boîte de dialogue de confirmation JavaScript avant d'exécuter une action (comme suivre un lien ou soumettre un formulaire). + +**Objectif :** Implémenter `n:confirm="'Êtes-vous sûr ?'"` qui ajoute un gestionnaire `onclick` pour empêcher l'action par défaut si l'utilisateur annule la boîte de dialogue de confirmation. + + +Implémentation de `ConfirmNode` +------------------------------- + +Nous avons besoin d'une classe Node et d'une fonction d'analyse. + +```php +expectArguments(); + $node = $tag->node = new self; + $node->message = $tag->parser->parseExpression(); + return $node; + } + + /** + * Génère le code de l'attribut 'onclick' avec un échappement correct. + */ + public function print(PrintContext $context): string + { + // Assure un échappement correct pour les contextes d'attribut JavaScript et HTML. + return $context->format( + <<<'XX' + echo ' onclick="', LR\Filters::escapeHtmlAttr('return confirm(' . LR\Filters::escapeJs(%node) . ')'), '"' %line; + XX, + $this->message, + $this->position, + ); + } + + public function &getIterator(): \Generator + { + yield $this->message; + } +} +``` + +La méthode `print()` génère du code PHP qui finira par imprimer l'attribut HTML `onclick="..."` pendant le rendu du template. La gestion des contextes imbriqués (JavaScript à l'intérieur d'un attribut HTML) nécessite un échappement soigneux. Le filtre `LR\Filters::escapeJs(%node)` est appelé à l'exécution et échappe correctement le message pour une utilisation à l'intérieur de JavaScript (la sortie serait comme `"Sure?"`). Ensuite, le filtre `LR\Filters::escapeHtmlAttr(...)` échappe les caractères qui sont spéciaux dans les attributs HTML, ce qui changerait la sortie en `return confirm("Sure?")`. Cet échappement d'exécution en deux étapes garantit que le message est sûr pour JavaScript et que le code JavaScript résultant est sûr pour être intégré dans l'attribut HTML `onclick`. + + +Enregistrement et utilisation +----------------------------- + +Enregistrez le n:attribut dans votre extension. N'oubliez pas le préfixe `n:` dans la clé : + +```php +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), + 'n:confirm' => ConfirmNode::create(...), // Enregistrement de n:confirm + ]; + } +} +``` + +Vous pouvez maintenant utiliser `n:confirm` sur des liens, des boutons ou des éléments de formulaire : + +```latte +Supprimer +``` + +HTML généré : + +```html +Supprimer +``` + +Lorsque l'utilisateur clique sur le lien, le navigateur exécute le code `onclick`, affiche la boîte de dialogue de confirmation et ne navigue vers `delete.php` que si l'utilisateur clique sur "OK". + +Cet exemple démontre comment un n:attribut pur peut être créé pour modifier le comportement ou les attributs de son élément HTML hôte en générant le code PHP approprié dans sa méthode `print()`. N'oubliez pas le double échappement souvent requis : une fois pour le contexte cible (JavaScript dans ce cas) et à nouveau pour le contexte de l'attribut HTML. + + +Sujets avancés +============== + +Alors que les sections précédentes couvrent les concepts fondamentaux, voici quelques sujets plus avancés que vous pourriez rencontrer lors de la création de balises Latte personnalisées. + + +Modes de sortie des balises +--------------------------- + +L'objet `Tag` passé à votre fonction `create()` a une propriété `outputMode`. Cette propriété affecte la manière dont Latte traite les espaces et l'indentation environnants, en particulier lorsqu'une balise est utilisée sur sa propre ligne. Vous pouvez modifier cette propriété dans votre fonction `create()`. + +- `Tag::OutputKeepIndentation` (Par défaut pour la plupart des balises comme `{=...}`) : Latte essaie de préserver l'indentation avant la balise. Les nouvelles lignes *après* la balise sont généralement préservées. Ceci convient aux balises qui impriment du contenu en ligne. +- `Tag::OutputRemoveIndentation` (Par défaut pour les balises de bloc comme `{if}`, `{foreach}`) : Latte supprime l'indentation initiale et potentiellement une nouvelle ligne suivante. Cela aide à garder le code PHP généré plus propre et évite les lignes vides supplémentaires dans la sortie HTML causées par la balise elle-même. Utilisez ceci pour les balises qui représentent des structures de contrôle ou des blocs qui ne devraient pas ajouter d'espaces eux-mêmes. +- `Tag::OutputNone` (Utilisé par des balises comme `{var}`, `{default}`) : Similaire à `RemoveIndentation`, mais signale plus fortement que la balise elle-même ne produit pas de sortie directe, affectant potentiellement le traitement des espaces autour d'elle de manière encore plus agressive. Convient aux balises déclaratives ou de configuration. + +Choisissez le mode qui correspond le mieux à l'objectif de votre balise. Pour la plupart des balises structurelles ou de contrôle, `OutputRemoveIndentation` est généralement approprié. + + +Accès aux balises parentes/les plus proches +------------------------------------------- + +Parfois, le comportement d'une balise doit dépendre du contexte dans lequel elle est utilisée, en particulier dans quelle(s) balise(s) parente(s) elle se trouve. L'objet `Tag` passé à votre fonction `create()` fournit la méthode `closestTag(array $classes, ?callable $condition = null): ?Tag` précisément à cet effet. + +Cette méthode recherche vers le haut dans la hiérarchie des balises actuellement ouvertes (y compris les éléments HTML représentés en interne pendant l'analyse) et retourne l'objet `Tag` de l'ancêtre le plus proche qui correspond aux critères spécifiques. Si aucun ancêtre correspondant n'est trouvé, elle retourne `null`. + +Le tableau `$classes` spécifie le type de balises ancêtres que vous recherchez. Il vérifie si le nœud associé de la balise ancêtre (`$ancestorTag->node`) est une instance de cette classe. + +```php +function create(Tag $tag) +{ + // Recherche de la balise ancêtre la plus proche dont le nœud est une instance de ForeachNode + $foreachTag = $tag->closestTag([ForeachNode::class]); + if ($foreachTag) { + // Nous pouvons accéder à l'instance ForeachNode elle-même : + $foreachNode = $foreachTag->node; + } +} +``` + +Notez `$foreachTag->node` : Cela ne fonctionne que parce qu'il est conventionnel dans le développement de balises Latte d'assigner immédiatement le nœud créé à `$tag->node` dans la méthode `create()`, comme nous l'avons toujours fait. + +Parfois, la simple comparaison du type de nœud ne suffit pas. Vous pourriez avoir besoin de vérifier une propriété spécifique de la balise ancêtre potentielle ou de son nœud. Le deuxième argument optionnel de `closestTag()` est un callable qui reçoit l'objet `Tag` de l'ancêtre potentiel et doit retourner s'il s'agit d'une correspondance valide. + +```php +function create(Tag $tag) +{ + $dynamicBlockTag = $tag->closestTag( + [BlockNode::class], + // Condition : le bloc doit être dynamique + fn(Tag $blockTag) => $blockTag->node->block->isDynamic(), + ); +} +``` + +L'utilisation de `closestTag()` permet de créer des balises contextuelles et d'imposer une utilisation correcte dans la structure de votre template, conduisant à des templates plus robustes et compréhensibles. + + +Placeholders `PrintContext::format()` +------------------------------------- + +Nous avons souvent utilisé `PrintContext::format()` pour générer du code PHP dans les méthodes `print()` de nos nœuds. Il accepte une chaîne de masque et des arguments suivants qui remplacent les placeholders dans le masque. Voici un résumé des placeholders disponibles : + +- **`%node`**: L'argument doit être une instance de `Node`. Appelle la méthode `print()` du nœud et insère la chaîne de code PHP résultante. +- **`%dump`**: L'argument est n'importe quelle valeur PHP. Exporte la valeur en code PHP valide. Convient aux scalaires, tableaux, null. + - `$context->format('echo %dump;', 'Hello')` -> `echo 'Hello';` + - `$context->format('$arr = %dump;', [1, 2])` -> `$arr = [1, 2];` +- **`%raw`**: Insère l'argument directement dans le code PHP de sortie sans aucun échappement ni modification. **Utilisez avec prudence**, principalement pour insérer des fragments de code PHP pré-générés ou des noms de variables. + - `$context->format('%raw = 1;', '$variableName')` -> `$variableName = 1;` +- **`%args`**: L'argument doit être un `Expression\ArrayNode`. Imprime les éléments du tableau formatés comme arguments pour un appel de fonction ou de méthode (séparés par des virgules, gère les arguments nommés s'ils sont présents). + - `$argsNode = new ArrayNode([...]);` + - `$context->format('myFunc(%args);', $argsNode)` -> `myFunc(1, name: 'Joe');` +- **`%line`**: L'argument doit être un objet `Position` (généralement `$this->position`). Insère un commentaire PHP `/* line X */` indiquant le numéro de ligne source. + - `$context->format('echo "Hi" %line;', $this->position)` -> `echo "Hi" /* line 42 */;` +- **`%escape(...)`**: Génère du code PHP qui échappe *à l'exécution* l'expression interne en utilisant les règles d'échappement contextuelles actuelles. + - `$context->format('echo %escape(%node);', $variableNode)` +- **`%modify(...)`**: L'argument doit être un `ModifierNode`. Génère du code PHP qui applique les filtres spécifiés dans le `ModifierNode` au contenu interne, y compris l'échappement contextuel, sauf si désactivé avec `|noescape`. + - `$context->format('%modify(%node);', $modifierNode, $variableNode)` +- **`%modifyContent(...)`**: Similaire à `%modify`, mais destiné à modifier des blocs de contenu capturé (souvent HTML). + +Vous pouvez explicitement faire référence aux arguments par leur index (à partir de zéro) : `%0.node`, `%1.dump`, `%2.raw`, etc. Cela permet de réutiliser un argument plusieurs fois dans le masque sans le passer à plusieurs reprises à `format()`. Voir l'exemple de la balise `{repeat}` où `%0.raw` et `%2.raw` ont été utilisés. + + +Exemple d'analyse d'arguments complexes +--------------------------------------- + +Alors que `parseExpression()`, `parseArguments()`, etc., couvrent de nombreux cas, vous avez parfois besoin d'une logique d'analyse plus complexe utilisant le `TokenStream` de bas niveau disponible via `$tag->parser->stream`. + +**Objectif :** Créer une balise `{embedYoutube $videoID, width: 640, height: 480}`. Nous voulons analyser l'ID vidéo requis (chaîne ou variable) suivi de paires clé-valeur optionnelles pour les dimensions. + +```php +expectArguments(); + $node = $tag->node = new self; + // Analyse de l'ID vidéo requis + $node->videoId = $tag->parser->parseExpression(); + + // Analyse des paires clé-valeur optionnelles + $stream = $tag->parser->stream; // Obtention du flux de tokens + while ($stream->tryConsume(',')) { // Nécessite une séparation par virgule + // Attente de l'identifiant 'width' ou 'height' + $keyToken = $stream->consume(Token::Php_Identifier); + $key = strtolower($keyToken->text); + + $stream->consume(':'); // Attente du séparateur deux-points + + $value = $tag->parser->parseExpression(); // Analyse de l'expression de valeur + + if ($key === 'width') { + $node->width = $value; + } elseif ($key === 'height') { + $node->height = $value; + } else { + throw new CompileException("Argument inconnu '$key'. Attendu 'width' ou 'height'.", $keyToken->position); + } + } + + return $node; + } +} +``` + +Ce niveau de contrôle vous permet de définir des syntaxes très spécifiques et complexes pour vos propres balises en interagissant directement avec le flux de tokens. + + +Utilisation de `AuxiliaryNode` +------------------------------ + +Latte fournit des nœuds "auxiliaires" génériques pour des situations spéciales lors de la génération de code ou dans les passes de compilation. Ce sont `AuxiliaryNode` et `Php\Expression\AuxiliaryNode`. + +Considérez `AuxiliaryNode` comme un nœud conteneur flexible qui délègue ses fonctionnalités de base - génération de code et exposition des nœuds enfants - aux arguments fournis dans son constructeur : + +- Délégation de `print()` : Le premier argument du constructeur est une **closure** PHP. Lorsque Latte appelle la méthode `print()` sur un `AuxiliaryNode`, il exécute cette closure fournie. La closure reçoit un `PrintContext` et tous les nœuds passés dans le deuxième argument du constructeur, vous permettant de définir une logique de génération de code PHP entièrement personnalisée à la volée. +- Délégation de `getIterator()` : Le deuxième argument du constructeur est un **tableau d'objets `Node`**. Lorsque Latte a besoin de parcourir les enfants d'un `AuxiliaryNode` (par exemple, pendant les passes de compilation), sa méthode `getIterator()` fournit simplement les nœuds listés dans ce tableau. + +Exemple : + +```php +$node = new AuxiliaryNode( + // 1. Cette closure devient le corps de print() + fn(PrintContext $context, $arg1, $arg2) => $context->format('...%node...%node...', $arg1, $arg2), + + // 2. Ces nœuds sont fournis par la méthode getIterator() et passés à la closure ci-dessus + [$argumentNode1, $argumentNode2] +); +``` + +Latte fournit deux types distincts basés sur l'endroit où vous devez insérer le code généré : + +- `Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode` : Utilisez ceci lorsque vous devez générer un morceau de code PHP qui représente une **expression** +- `Latte\Compiler\Nodes\AuxiliaryNode` : Utilisez ceci à des fins plus générales lorsque vous devez insérer un bloc de code PHP représentant une ou plusieurs **instructions** + +Une raison importante d'utiliser `AuxiliaryNode` au lieu de nœuds standard (comme `StaticMethodCallNode`) dans votre méthode `print()` ou une passe de compilation est le **contrôle de la visibilité pour les passes de compilation suivantes**, en particulier celles liées à la sécurité comme Sandbox. + +Considérez le scénario suivant : Votre passe de compilation doit envelopper une expression fournie par l'utilisateur (`$userExpr`) dans un appel à une fonction d'aide spécifique et fiable `myInternalSanitize($userExpr)`. Si vous créez un nœud standard `new FunctionCallNode('myInternalSanitize', [$userExpr])`, il sera entièrement visible pour le parcours de l'AST. Si la passe Sandbox s'exécute plus tard et que `myInternalSanitize` *n'est pas* sur sa liste blanche, Sandbox pourrait *bloquer* ou modifier cet appel, perturbant potentiellement la logique interne de votre balise, même si *vous*, l'auteur de la balise, savez que cet appel spécifique est sûr et nécessaire. Vous pouvez donc générer l'appel directement dans la closure de `AuxiliaryNode`. + +```php +use Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode; + +// ... à l'intérieur de print() ou d'une passe de compilation ... +$wrappedNode = new AuxiliaryNode( + fn(PrintContext $context, $userExpr) => $context->format( + 'myInternalSanitize(%node)', // Génération directe du code PHP + $userExpr, + ), + // IMPORTANT : Passez toujours le nœud d'expression utilisateur original ici ! + [$userExpr], +); +``` + +Dans ce cas, la passe Sandbox voit l'`AuxiliaryNode`, mais **n'analyse pas le code PHP généré par sa closure**. Elle ne peut pas bloquer directement l'appel `myInternalSanitize` généré *à l'intérieur* de la closure. + +Alors que le code PHP généré lui-même est caché aux passes, les *entrées* de ce code (les nœuds représentant les données ou expressions utilisateur) **doivent toujours être parcourables**. C'est pourquoi le deuxième argument du constructeur `AuxiliaryNode` est crucial. Vous **devez** passer un tableau contenant tous les nœuds originaux (comme `$userExpr` dans l'exemple ci-dessus) que votre closure utilise. Le `getIterator()` de `AuxiliaryNode` **fournira ces nœuds**, permettant aux passes de compilation comme Sandbox de les analyser pour des problèmes potentiels. + + +Meilleures pratiques +==================== + +- **Objectif clair :** Assurez-vous que votre balise a un objectif clair et nécessaire. Ne créez pas de balises pour des tâches qui peuvent être facilement résolues avec des [filtres |custom-filters] ou des [fonctions |custom-functions]. +- **Implémentez correctement `getIterator()` :** Implémentez toujours `getIterator()` et fournissez des *références* (`&`) à *tous* les nœuds enfants (arguments, contenu) qui ont été analysés à partir du template. Ceci est essentiel pour les passes de compilation, la sécurité (Sandbox) et les optimisations futures potentielles. +- **Propriétés publiques pour les nœuds :** Rendez publiques les propriétés contenant des nœuds enfants afin que les passes de compilation puissent les modifier si nécessaire. +- **Utilisez `PrintContext::format()` :** Utilisez la méthode `format()` pour générer du code PHP. Elle gère les guillemets, échappe correctement les placeholders et ajoute automatiquement les commentaires de numéro de ligne. +- **Variables temporaires (`$__`) :** Lors de la génération de code PHP d'exécution qui nécessite des variables temporaires (par exemple, pour stocker des sous-totaux, des compteurs de boucle), utilisez la convention de préfixe `$__` pour éviter les collisions avec les variables utilisateur et les variables internes de Latte `$ʟ_`. +- **Imbrication et ID uniques :** Si votre balise peut être imbriquée ou nécessite un état spécifique à l'instance à l'exécution, utilisez `$context->generateId()` dans votre méthode `print()` pour créer des suffixes uniques pour vos variables temporaires `$__`. +- **Fournisseurs pour les données externes :** Utilisez des fournisseurs (enregistrés via `Extension::getProviders()`) pour accéder aux données ou services d'exécution (`$this->global->...`) au lieu de coder en dur des valeurs ou de dépendre de l'état global. Utilisez des préfixes de fabricant pour les noms de fournisseurs. +- **Considérez les n:attributs :** Si votre balise appariée opère logiquement sur un seul élément HTML, Latte fournit probablement un support automatique de `n:attribut`. Gardez cela à l'esprit pour la commodité de l'utilisateur. Si vous créez une balise modifiant un attribut, demandez-vous si un `n:attribut` pur est la forme la plus appropriée. +- **Tests :** Écrivez des tests pour vos balises, couvrant à la fois l'analyse de différentes entrées syntaxiques et l'exactitude de la sortie du **code PHP** généré. + +En suivant ces directives, vous pouvez créer des balises personnalisées puissantes, robustes et maintenables qui s'intègrent de manière transparente au moteur de template Latte. + +.[note] +L'étude des classes de nœuds fournies avec Latte est le meilleur moyen d'apprendre tous les détails du processus d'analyse syntaxique. diff --git a/latte/fr/develop.texy b/latte/fr/develop.texy index 35fbc2e5e0..eb19311655 100644 --- a/latte/fr/develop.texy +++ b/latte/fr/develop.texy @@ -1,70 +1,66 @@ -Pratiques pour les développeurs -******************************* +Pratiques de développement +************************** -Installation .[#toc-installation] -================================= +Installation +============ -La meilleure façon d'installer Latte est d'utiliser un Composer : +La meilleure façon d'installer Latte est d'utiliser Composer : ```shell composer require latte/latte ``` -Versions PHP supportées (s'applique aux dernières versions de Latte patch) : +Versions PHP supportées (s'applique aux dernières versions mineures de Latte) : -| version | compatible avec PHP +| version | compatible avec PHP |-----------------|------------------- -| Latte 3.0 | PHP 8.0 - 8.2 -| Latte 2.11 | PHP 7.1 - 8.2 -| Latte 2.8 - 2.10 | PHP 7.1 - 8.1 +| Latte 3.0 | PHP 8.0 – 8.2 -Comment rendre un modèle .[#toc-how-to-render-a-template] -========================================================= +Comment rendre un template +========================== -Comment rendre un modèle ? Il suffit d'utiliser ce code simple : +Comment rendre un template ? Il suffit de ce simple code : ```php $latte = new Latte\Engine; -// répertoire de cache +// répertoire pour le cache $latte->setTempDirectory('/path/to/tempdir'); -$params = [ /* variables de modèle */ ]; +$params = [ /* variables du template */ ]; // ou $params = new TemplateParameters(/* ... */); -// rendre vers la sortie +// rend vers la sortie $latte->render('template.latte', $params); -// ou rendu vers la variable +// rend dans une variable $output = $latte->renderToString('template.latte', $params); ``` -Les paramètres peuvent être des tableaux ou mieux encore des [objets |#Parameters as a class], ce qui permettra de vérifier et de suggérer le type dans l'éditeur. +Les paramètres peuvent être un tableau ou, encore mieux, un [objet |#Paramètres en tant que classe], qui assure le contrôle de type et l'autocomplétion dans les éditeurs. .[note] -Vous pouvez également trouver des exemples d'utilisation dans le référentiel [Latte examples |https://github.com/nette-examples/latte]. +Vous trouverez également des exemples d'utilisation dans le dépôt [Latte examples |https://github.com/nette-examples/latte]. -Performances et mise en cache .[#toc-performance-and-caching] -============================================================= +Performance et cache +==================== -Les modèles Latte sont extrêmement rapides, car Latte les compile directement en code PHP et les met en cache sur le disque. Ainsi, ils n'ont pas de surcharge supplémentaire par rapport aux modèles écrits en PHP pur. +Les templates dans Latte sont extrêmement rapides, car Latte les compile directement en code PHP et les stocke dans un cache sur disque. Ils n'ont donc aucune surcharge par rapport aux templates écrits en PHP pur. -Le cache est automatiquement régénéré chaque fois que vous modifiez le fichier source. Vous pouvez donc facilement modifier vos modèles Latte pendant le développement et voir les changements immédiatement dans le navigateur. Vous pouvez désactiver cette fonction dans un environnement de production et économiser un peu de performance : +Le cache est automatiquement régénéré chaque fois que vous modifiez le fichier source. Ainsi, pendant le développement, vous pouvez confortablement éditer vos templates Latte et voir les changements immédiatement dans le navigateur. Vous pouvez désactiver cette fonctionnalité dans l'environnement de production pour économiser un peu de performance : ```php $latte->setAutoRefresh(false); ``` -Lorsqu'elle est déployée sur un serveur de production, la génération initiale du cache, en particulier pour les grandes applications, peut naturellement prendre un certain temps. Latte a une prévention intégrée contre la "ruée vers le cache":https://en.wikipedia.org/wiki/Cache_stampede. -Il s'agit d'une situation où le serveur reçoit un grand nombre de demandes simultanées et, comme le cache de Latte n'existe pas encore, elles le génèrent toutes en même temps. Ce qui fait grimper le CPU en flèche. -Latte est intelligent, et lorsqu'il y a plusieurs demandes simultanées, seul le premier thread génère le cache, les autres attendent et l'utilisent ensuite. +Lors du déploiement sur un serveur de production, la génération initiale du cache, en particulier pour les applications plus importantes, peut bien sûr prendre un certain temps. Latte intègre une prévention contre le "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Il s'agit d'une situation où un grand nombre de requêtes simultanées arrivent, déclenchant Latte, et comme le cache n'existe pas encore, toutes commenceraient à le générer en même temps. Ce qui surchargerait indûment le serveur. Latte est intelligent et, en cas de requêtes simultanées multiples, seul le premier thread génère le cache, les autres attendent puis l'utilisent. -Paramètres en tant que classe .[#toc-parameters-as-a-class] -=========================================================== +Paramètres en tant que classe +============================= -Il est préférable de créer une classe plutôt que de transmettre des variables au modèle sous forme de tableaux. Vous obtenez une [notation sûre |type-system], une [suggestion agréable dans l'IDE |recipes#Editors and IDE] et un moyen d'[enregistrer des filtres |extending-latte#Filters Using the Class] et des [fonctions |extending-latte#Functions Using the Class]. +Plutôt que de passer des variables au template sous forme de tableau, il est préférable de créer une classe. Vous obtiendrez ainsi une [écriture typée |type-system], une [autocomplétion agréable dans l'IDE |recipes#Éditeurs et IDE] et un moyen pour [l'enregistrement des filtres |custom-filters#Filtres utilisant une classe avec attributs] et des [fonctions |custom-functions#Fonctions utilisant une classe avec attributs]. ```php class MailTemplateParameters @@ -88,12 +84,12 @@ $latte->render('mail.latte', new MailTemplateParameters( ``` -Désactiver l'échappement automatique d'une variable .[#toc-disabling-auto-escaping-of-variable] -=============================================================================================== +Désactivation de l'échappement automatique des variables +======================================================== -Si la variable contient une chaîne HTML, vous pouvez la marquer pour que Latte ne l'échappe pas automatiquement (et donc doublement). Cela évite d'avoir à spécifier `|noescape` dans le modèle. +Si une variable contient une chaîne HTML, vous pouvez la marquer pour que Latte ne l'échappe pas automatiquement (et donc doublement). Vous éviterez ainsi d'avoir à utiliser `|noescape` dans le template. -La méthode la plus simple consiste à envelopper la chaîne dans un objet `Latte\Runtime\Html`: +Le moyen le plus simple est d'encapsuler la chaîne dans un objet `Latte\Runtime\Html` : ```php $params = [ @@ -101,7 +97,7 @@ $params = [ ]; ``` -Latte n'échappe pas non plus tous les objets qui implémentent l'interface `Latte\HtmlStringable`. Vous pouvez donc créer votre propre classe dont la méthode `__toString()` renverra du code HTML qui ne sera pas échappé automatiquement : +Latte n'échappe pas non plus tous les objets qui implémentent l'interface `Latte\HtmlStringable`. Vous pouvez ainsi créer votre propre classe dont la méthode `__toString()` retournera du code HTML qui ne sera pas automatiquement échappé : ```php class Emphasis extends Latte\HtmlStringable @@ -123,32 +119,85 @@ $params = [ ``` .[warning] -La méthode `__toString` doit retourner du HTML correct et fournir l'échappement des paramètres, sinon une vulnérabilité XSS peut se produire ! +La méthode `__toString` doit retourner du HTML correct et assurer l'échappement des paramètres, sinon une vulnérabilité XSS peut survenir ! -Comment étendre Latte avec des filtres, des balises, etc. .[#toc-how-to-extend-latte-with-filters-tags-etc] -=========================================================================================================== +Comment étendre Latte avec des filtres, des balises, etc. +========================================================= -Comment ajouter un filtre, une fonction, une balise, etc. personnalisés à Latte ? Découvrez-le dans le chapitre consacré à l'[extension de Latte |extending Latte]. -Si vous voulez réutiliser vos modifications dans différents projets ou si vous voulez les partager avec d'autres, vous devez alors [créer une extension |creating-extension]. +Comment ajouter un filtre, une fonction, une balise personnalisée, etc. à Latte ? C'est ce que traite le chapitre [étendre Latte |extending-latte]. Si vous souhaitez réutiliser vos modifications dans différents projets ou les partager avec d'autres, vous devriez [créer une extension |extending-latte#Latte Extension]. -Tout code dans le modèle `{php ...}` .{data-version:3.0}{toc: RawPhpExtension} -============================================================================== +Code arbitraire dans le template `{php ...}` .{toc: RawPhpExtension} +==================================================================== -Seules les expressions PHP peuvent être écrites à l'intérieur de la balise [`{do}` |tags#do] Vous ne pouvez donc pas, par exemple, insérer des constructions comme `if ... else` ou des déclarations terminées par un point-virgule. +À l'intérieur de la balise [`{do}` |tags#do], seules les expressions PHP peuvent être écrites, vous ne pouvez donc pas insérer de constructions comme `if ... else` ou des instructions terminées par un point-virgule. -Toutefois, vous pouvez enregistrer l'extension `RawPhpExtension`, qui ajoute la balise `{php ...}`, laquelle peut être utilisée pour insérer n'importe quel code PHP, aux risques de l'auteur du modèle. +Cependant, vous pouvez enregistrer l'extension `RawPhpExtension`, qui ajoute la balise `{php ...}`. Grâce à elle, vous pouvez insérer n'importe quel code PHP. Aucune règle du mode sandbox ne s'applique à elle, son utilisation relève donc de la responsabilité de l'auteur du template. ```php $latte->addExtension(new Latte\Essential\RawPhpExtension); ``` -Traduction dans les modèles .{data-version:3.0}{toc: TranslatorExtension} -========================================================================= +Vérification du code généré .{data-version:3.0.7} +================================================= + +Latte compile les templates en code PHP. Bien sûr, il veille à ce que le code généré soit syntaxiquement valide. Cependant, lors de l'utilisation d'extensions tierces ou de `RawPhpExtension`, Latte ne peut garantir l'exactitude du fichier généré. Il est également possible d'écrire en PHP du code qui est syntaxiquement correct, mais interdit (par exemple, l'affectation d'une valeur à la variable `$this`) et provoque une erreur de compilation PHP (PHP Compile Error). Si vous écrivez une telle opération dans un template, elle se retrouvera également dans le code PHP généré. Comme il existe environ deux cents opérations interdites différentes en PHP, Latte n'a pas l'ambition de les détecter toutes. C'est PHP lui-même qui les signalera lors du rendu, ce qui n'est généralement pas un problème. + +Cependant, il existe des situations où vous voulez savoir dès la compilation du template qu'il ne contient aucune erreur de compilation PHP. C'est notamment le cas si les templates peuvent être édités par les utilisateurs, ou si vous utilisez [Sandbox |sandbox]. Dans ce cas, faites vérifier vos templates dès la compilation. Cette fonctionnalité est activée par la méthode `Engine::enablePhpLint()`. Comme elle nécessite d'appeler l'exécutable PHP pour la vérification, passez son chemin en paramètre : + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); + +try { + $latte->compile('home.latte'); +} catch (Latte\CompileException $e) { + // capture les erreurs Latte et aussi les Compile Error en PHP + echo 'Error: ' . $e->getMessage(); +} +``` + + +Paramètres régionaux .{data-version:3.0.18}{toc: Locale} +======================================================== -Utilisez l'extension `TranslatorExtension` pour ajouter [`{_...}` |tags#_], [`{translate}` |tags#translate] et le filtre [`translate` |filters#translate] au modèle. Ils sont utilisés pour traduire des valeurs ou des parties du modèle dans d'autres langues. Le paramètre est la méthode (appelable en PHP) qui effectue la traduction : +Latte permet de définir les paramètres régionaux, qui influencent le formatage des nombres, des dates et le tri. Ils sont définis à l'aide de la méthode `setLocale()`. L'identifiant des paramètres régionaux suit la norme IETF language tag, utilisée par l'extension PHP `intl`. Il se compose du code de langue et éventuellement du code de pays, par exemple `en_US` pour l'anglais aux États-Unis, `de_DE` pour l'allemand en Allemagne, etc. + +```php +$latte = new Latte\Engine; +$latte->setLocale('fr_FR'); // Exemple pour le français en France +``` + +La définition des paramètres régionaux affecte les filtres [localDate |filters#localDate], [sort |filters#sort], [number |filters#number] et [bytes |filters#bytes]. + +.[note] +Nécessite l'extension PHP `intl`. La configuration dans Latte n'affecte pas les paramètres régionaux globaux de PHP. + + +Mode strict .{data-version:3.0.8} +================================= + +En mode d'analyse strict, Latte vérifie si les balises HTML de fermeture ne manquent pas et interdit également l'utilisation de la variable `$this`. Activez-le comme suit : + +```php +$latte = new Latte\Engine; +$latte->setStrictParsing(); +``` + +Pour générer des templates avec l'en-tête `declare(strict_types=1)`, activez-le comme suit : + +```php +$latte = new Latte\Engine; +$latte->setStrictTypes(); +``` + + +Traduction dans les templates .{toc: TranslatorExtension} +========================================================= + +Avec l'extension `TranslatorExtension`, vous ajoutez au template les balises [`{_...}` |tags#], [`{translate}` |tags#translate] et le filtre [`translate` |filters#translate]. Ils servent à traduire des valeurs ou des parties du template dans d'autres langues. Comme paramètre, nous spécifions la méthode (PHP callable) effectuant la traduction : ```php class MyTranslator @@ -158,7 +207,7 @@ class MyTranslator public function translate(string $original): string { - // créer $translated à partir de $original en fonction de $this->langue + // à partir de $original, nous créons $translated selon $this->lang return $translated; } } @@ -170,7 +219,7 @@ $extension = new Latte\Essential\TranslatorExtension( $latte->addExtension($extension); ``` -Le traducteur est appelé au moment de l'exécution lorsque le modèle est rendu. Cependant, Latte peut traduire tous les textes statiques pendant la compilation du modèle. Cela permet de gagner en performance car chaque chaîne n'est traduite qu'une seule fois et la traduction résultante est écrite dans le fichier compilé. Cela crée plusieurs versions compilées du modèle dans le répertoire de cache, une pour chaque langue. Pour ce faire, il suffit de spécifier la langue comme deuxième paramètre : +Le traducteur est appelé à l'exécution lors du rendu du template. Cependant, Latte peut traduire tous les textes statiques dès la compilation du template. Cela économise des performances, car chaque chaîne n'est traduite qu'une seule fois et la traduction résultante est écrite dans la forme compilée. Ainsi, dans le répertoire cache, plusieurs versions compilées du template sont créées, une pour chaque langue. Pour cela, il suffit de spécifier la langue comme deuxième paramètre : ```php $extension = new Latte\Essential\TranslatorExtension( @@ -179,9 +228,9 @@ $extension = new Latte\Essential\TranslatorExtension( ); ``` -Par texte statique, nous entendons, par exemple, `{_'hello'}` ou `{translate}hello{/translate}`. Le texte non statique, tel que `{_$foo}`, continuera à être traduit au moment de l'exécution. +Par texte statique, on entend par exemple `{_'hello'}` ou `{translate}hello{/translate}`. Les textes non statiques, comme `{_$foo}`, continueront d'être traduits à l'exécution. -Le modèle peut également transmettre des paramètres supplémentaires au traducteur via `{_$original, foo: bar}` ou `{translate foo: bar}`, qu'il reçoit dans le tableau `$params`: +Il est également possible de passer des paramètres supplémentaires au traducteur depuis le template en utilisant `{_$original, foo: bar}` ou `{translate foo: bar}`, qu'il recevra sous forme de tableau `$params` : ```php public function translate(string $original, ...$params): string @@ -191,66 +240,73 @@ public function translate(string $original, ...$params): string ``` -Débogage et Tracy .[#toc-debugging-and-tracy] -============================================= +Débogage et Tracy +================= -Latte essaie de rendre le développement aussi agréable que possible. Pour le débogage, il existe trois balises [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak] et [`{trace}` |tags#trace]. +Latte essaie de rendre votre développement aussi agréable que possible. Directement à des fins de débogage, il existe un trio de balises [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak] et [`{trace}` |tags#trace]. -Vous obtiendrez plus de confort en installant l'excellent [outil de débogage Tracy |tracy:] et en activant le plugin Latte : +Vous obtiendrez le plus grand confort si vous installez également l'excellent [outil de débogage Tracy |tracy:] et activez le plugin pour Latte : ```php // active Tracy Tracy\Debugger::enable(); $latte = new Latte\Engine; -// active l'extension de Tracy +// active l'extension pour Tracy $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension); ``` -Vous verrez alors toutes les erreurs dans un écran rouge soigné, y compris les erreurs dans les modèles avec mise en évidence des lignes et des colonnes ([vidéo |https://github.com/nette/tracy/releases/tag/v2.9.0]). -En même temps, dans le coin inférieur droit de la barre Tracy, un onglet pour Latte apparaît, où vous pouvez voir clairement tous les modèles rendus et leurs relations (y compris la possibilité de cliquer dans le modèle ou le code compilé), ainsi que les variables : +Désormais, toutes les erreurs s'afficheront dans un écran rouge clair, y compris les erreurs dans les templates avec mise en évidence de la ligne et de la colonne ([vidéo|https://github.com/nette/tracy/releases/tag/v2.9.0]). En même temps, dans le coin inférieur droit, dans ce qu'on appelle la barre Tracy, un onglet pour Latte apparaîtra, où tous les templates rendus et leurs relations mutuelles sont clairement visibles (y compris la possibilité de cliquer pour accéder au template ou au code compilé) ainsi que les variables : [* latte-debugging.webp *] -Comme Latte compile les modèles en code PHP lisible, vous pouvez facilement les parcourir dans votre IDE. +Comme Latte compile les templates en code PHP clair, vous pouvez facilement les parcourir pas à pas dans votre IDE. -Linter : Validation de la syntaxe du modèle .{data-version:2.11}{toc: Linter} -============================================================================= +Linter : validation de la syntaxe des templates .{toc: Linter} +============================================================== -L'outil Linter vous aidera à passer en revue tous les modèles et à vérifier les erreurs de syntaxe. Il est lancé à partir de la console : +L'outil Linter vous aide à parcourir tous les templates et à vérifier s'ils contiennent des erreurs de syntaxe. Il se lance depuis la console : ```shell -vendor/bin/latte-lint +vendor/bin/latte-lint ``` -Si vous utilisez des balises personnalisées, créez également votre Linter personnalisé, par exemple `custom-latte-lint`: +Le paramètre `--strict` active le [#mode strict]. + +Si vous utilisez des balises personnalisées, créez également votre propre version du Linter, par ex. `custom-latte-lint` : ```php #!/usr/bin/env php scanDirectory($path); +$path = $argv[1] ?? '.'; -$engine = new Latte\Engine; -// enregistre les extensions individuelles ici -$engine->addExtension(/* ... */); +$linter = new Latte\Tools\Linter; +$latte = $linter->getEngine(); +// ajoutez ici vos extensions individuelles +$latte->addExtension(/* ... */); -$path = $argv[1]; -$linter = new Latte\Tools\Linter(engine: $engine); $ok = $linter->scanDirectory($path); exit($ok ? 0 : 1); ``` +Alternativement, vous pouvez passer votre propre objet `Latte\Engine` au Linter : + +```php +$latte = new Latte\Engine; +// ici nous configurons l'objet $latte +$linter = new Latte\Tools\Linter(engine: $latte); +``` + -Chargement de modèles à partir d'une chaîne .[#toc-loading-templates-from-a-string] -=================================================================================== +Chargement de templates depuis une chaîne +========================================= -Vous avez besoin de charger des modèles à partir de chaînes de caractères plutôt que de fichiers, peut-être à des fins de test ? [StringLoader |extending-latte#stringloader] vous aidera : +Avez-vous besoin de charger des templates à partir de chaînes plutôt que de fichiers, par exemple à des fins de test ? [StringLoader |loaders#StringLoader] vous aidera : ```php $latte->setLoader(new Latte\Loaders\StringLoader([ @@ -262,10 +318,10 @@ $latte->render('main.file', $params); ``` -Gestionnaire d'exceptions .[#toc-exception-handler] -=================================================== +Gestionnaire d'exceptions +========================= -Vous pouvez définir votre propre gestionnaire pour les exceptions attendues. Les exceptions levées dans [`{try}` |tags#try] et dans le [bac à sable |sandbox] lui sont transmises. +Vous pouvez définir votre propre gestionnaire pour les exceptions attendues. Les exceptions survenant à l'intérieur de [`{try}` |tags#try] et dans le [sandbox |sandbox] lui seront transmises. ```php $loggingHandler = function (Throwable $e, Latte\Runtime\Template $template) use ($logger) { @@ -277,17 +333,17 @@ $latte->setExceptionHandler($loggingHandler); ``` -Recherche automatique de mise en page .[#toc-automatic-layout-lookup] -===================================================================== +Recherche automatique de layout +=============================== -En utilisant la balise [`{layout}` |template-inheritance#layout-inheritance] le modèle détermine son modèle parent. Il est également possible de faire en sorte que la mise en page soit recherchée automatiquement, ce qui simplifiera l'écriture des modèles puisqu'ils n'auront pas besoin d'inclure la balise `{layout}`. +Avec la balise [`{layout}` |template-inheritance#Héritage de layout], le template spécifie son template parent. Il est également possible de laisser la recherche du layout se faire automatiquement, ce qui simplifie l'écriture des templates, car il ne sera pas nécessaire d'y inclure la balise `{layout}`. -Cela s'effectue comme suit : +Cela s'obtient de la manière suivante : ```php $finder = function (Latte\Runtime\Template $template) { if (!$template->getReferenceType()) { - // il renvoie le chemin d'accès au fichier modèle parent + // retourne le chemin vers le fichier de layout return 'automatic.layout.latte'; } }; @@ -296,4 +352,4 @@ $latte = new Latte\Engine; $latte->addProvider('coreParentFinder', $finder); ``` -Si le modèle ne doit pas avoir de mise en page, il l'indique avec la balise `{layout none}`. +Si le template ne doit pas avoir de layout, il l'indique avec la balise `{layout none}`. diff --git a/latte/fr/extending-latte.texy b/latte/fr/extending-latte.texy index 7c191c616f..fd92ef9f84 100644 --- a/latte/fr/extending-latte.texy +++ b/latte/fr/extending-latte.texy @@ -1,285 +1,227 @@ -Latte d'extension -***************** +Extension de Latte +****************** .[perex] -Latte est très flexible et peut être étendu de nombreuses façons : vous pouvez ajouter des filtres, des fonctions, des balises, des chargeurs, etc. personnalisés. Nous allons vous montrer comment le faire. +Latte est conçu pour être extensible. Bien que son ensemble standard de balises, filtres et fonctions couvre de nombreux cas d'utilisation, vous avez souvent besoin d'ajouter votre propre logique spécifique ou des outils d'aide. Cette page fournit un aperçu des façons d'étendre Latte pour qu'il corresponde parfaitement aux exigences de votre projet - des simples aides aux nouvelles syntaxes complexes. -Ce chapitre décrit les différentes façons d'étendre Latte. Si vous voulez réutiliser vos modifications dans différents projets ou si vous voulez les partager avec d'autres, vous devez alors [créer ce que l'on appelle une extension |creating-extension]. +Méthodes d'extension de Latte +============================= -Combien de routes mènent à Rome ? .[#toc-how-many-roads-lead-to-rome] -===================================================================== +Voici un aperçu rapide des principales façons de personnaliser et d'étendre Latte : -Puisque certaines des façons d'étendre Latte peuvent être mélangées, essayons d'abord d'expliquer les différences entre elles. À titre d'exemple, essayons d'implémenter un générateur de *Lorem ipsum*, auquel on transmet le nombre de mots à générer. +- **[Filtres personnalisés |Custom Filters]:** Pour formater ou transformer des données directement dans la sortie du template (par ex. `{$var|myFilter}`). Idéal pour des tâches comme le formatage de dates, la modification de texte ou l'application d'un échappement spécifique. Vous pouvez également les utiliser pour modifier de plus grands blocs de contenu HTML en enveloppant le contenu dans un [`{block}` |tags#block] anonyme et en lui appliquant un filtre personnalisé. +- **[Fonctions personnalisées |Custom Functions]:** Pour ajouter une logique réutilisable qui peut être appelée dans les expressions du template (par ex. `{myFunction($arg1, $arg2)}`). Utile pour les calculs, l'accès aux fonctions d'aide de l'application ou la génération de petites parties de contenu. +- **[Balises personnalisées |Custom Tags]:** Pour créer de toutes nouvelles constructions de langage (`{mytag}...{/mytag}` ou `n:mytag`). Les balises offrent le plus de possibilités, permettent de définir des structures personnalisées, de contrôler l'analyse syntaxique du template et d'implémenter une logique de rendu complexe. +- **[Passes de compilation |Compiler Passes]:** Fonctions qui modifient l'arbre syntaxique abstrait (AST) du template après l'analyse syntaxique, mais avant la génération du code PHP. Elles sont utilisées pour des optimisations avancées, des contrôles de sécurité (comme le Sandbox) ou des modifications automatiques du code. +- **[Chargeurs personnalisés |loaders]:** Pour changer la façon dont Latte recherche et charge les fichiers de template (par ex. chargement depuis une base de données, un stockage chiffré, etc.). -La principale construction du langage Latte est la balise. Nous pouvons implémenter un générateur en étendant Latte avec une nouvelle balise : +Choisir la bonne méthode d'extension est crucial. Avant de créer une balise complexe, demandez-vous si un filtre ou une fonction plus simple ne suffirait pas. Illustrons cela avec un exemple : l'implémentation d'un générateur *Lorem ipsum* qui prend en argument le nombre de mots à générer. -```latte -{lipsum 40} -``` - -La balise fonctionnera parfaitement. Cependant, le générateur sous la forme d'une balise peut ne pas être assez flexible car il ne peut pas être utilisé dans une expression. D'ailleurs, dans la pratique, vous avez rarement besoin de générer des balises ; et c'est une bonne nouvelle, car les balises sont un moyen plus compliqué d'étendre. - -Bon, essayons de créer un filtre au lieu d'une balise : - -```latte -{=40|lipsum} -``` - -Encore une fois, une option valide. Mais le filtre doit transformer la valeur passée en quelque chose d'autre. Ici, nous utilisons la valeur `40`, qui indique le nombre de mots générés, comme argument de filtre, et non comme la valeur que nous voulons transformer. +- **Comme balise ?** `{lipsum 40}` - Possible, mais les balises sont plus adaptées aux structures de contrôle ou à la génération de balises complexes. Les balises ne peuvent pas être utilisées directement dans les expressions. +- **Comme filtre ?** `{=40|lipsum}` - Techniquement, cela fonctionne, mais les filtres sont conçus pour *transformer* la valeur d'entrée. Ici, `40` est un *argument*, pas une valeur qui est transformée. Cela semble sémantiquement incorrect. +- **Comme fonction ?** `{lipsum(40)}` - C'est la solution la plus naturelle ! Les fonctions acceptent des arguments et retournent des valeurs, ce qui est idéal pour une utilisation dans n'importe quelle expression : `{var $text = lipsum(40)}`. -Essayons donc d'utiliser la fonction : +**Recommandation générale :** Utilisez les fonctions pour les calculs/générations, les filtres pour la transformation et les balises pour les nouvelles constructions de langage ou les balises complexes. Utilisez les passes pour la manipulation de l'AST et les chargeurs pour obtenir les templates. -```latte -{lipsum(40)} -``` -C'est ça ! Pour cet exemple particulier, la création d'une fonction est le point d'extension idéal à utiliser. Vous pouvez l'appeler partout où une expression est acceptée, par exemple : +Enregistrement direct +===================== -```latte -{var $text = lipsum(40)} -``` +Pour les outils d'aide spécifiques au projet ou les extensions rapides, Latte permet l'enregistrement direct de filtres et de fonctions dans l'objet `Latte\Engine`. - -Filtres .[#toc-filters] -======================= - -Créez un filtre en enregistrant son nom et tout appelable en PHP, comme une fonction : +Pour enregistrer un filtre, utilisez la méthode `addFilter()`. Le premier argument de votre fonction de filtre sera la valeur avant le caractère `|` et les arguments suivants sont ceux qui sont passés après les deux-points `:`. ```php $latte = new Latte\Engine; -$latte->addFilter('shortify', fn(string $s) => mb_substr($s, 0, 10)); // raccourcit le texte à 10 caractères. -``` -Dans ce cas, il serait préférable que le filtre obtienne un paramètre supplémentaire : +// Définition du filtre (objet appelable : fonction, méthode statique, etc.) +$myTruncate = fn(string $s, int $length = 50) => mb_substr($s, 0, $length); -```php -$latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); -``` - -Nous l'utilisons dans un modèle comme celui-ci : +// Enregistrement +$latte->addFilter('truncate', $myTruncate); -```latte -

                                                              {$text|shortify}

                                                              -

                                                              {$text|shortify:100}

                                                              +// Utilisation dans le template : {$text|truncate} ou {$text|truncate:100} ``` -Comme vous pouvez le voir, la fonction reçoit le côté gauche du filtre avant le pipe `|` as the first argument and the arguments passed to the filter after `:` comme arguments suivants. - -Bien sûr, la fonction représentant le filtre peut accepter n'importe quel nombre de paramètres, et les paramètres variadiques sont également supportés. - - -Filtres utilisant la classe .[#toc-filters-using-the-class] ------------------------------------------------------------ - -La deuxième façon de définir un filtre est d'[utiliser la classe |develop#Parameters as a class]. Nous créons une méthode avec l'attribut `TemplateFilter`: +Vous pouvez également enregistrer un **Chargeur de Filtre**, une fonction qui fournit dynamiquement des objets appelables de filtres selon le nom requis : ```php -class TemplateParameters -{ - public function __construct( - // parameters - ) {} - - #[Latte\Attributes\TemplateFilter] - public function shortify(string $s, int $len = 10): string - { - return mb_substr($s, 0, $len); - } -} - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); +$latte->addFilterLoader(fn(string $name) => /* retourne un objet appelable ou null */); ``` -Si vous utilisez PHP 7.x et Latte 2.x, utilisez l'annotation `/** @filter */` au lieu de l'attribut. - -Chargeur de filtre .{data-version:2.10}[#toc-filter-loader] ------------------------------------------------------------ - -Au lieu d'enregistrer des filtres individuels, vous pouvez créer un "chargeur", qui est une fonction appelée avec le nom du filtre comme argument et qui renvoie son appelable PHP, ou null. +Pour enregistrer une fonction utilisable dans les expressions du template, utilisez `addFunction()`. ```php -$latte->addFilterLoader([new Filters, 'load']); +$latte = new Latte\Engine; +// Définition de la fonction +$isWeekend = fn(DateTimeInterface $date) => $date->format('N') >= 6; -class Filters -{ - public function load(string $filter): ?callable - { - if (in_array($filter, get_class_methods($this))) { - return [$this, $filter]; - } - return null; - } - - public function shortify($s, $len = 10) - { - return mb_substr($s, 0, $len); - } - - // ... -} +// Enregistrement +$latte->addFunction('isWeekend', $isWeekend); + +// Utilisation dans le template : {if isWeekend($myDate)}Week-end !{/if} ``` +Pour plus d'informations, consultez les sections [Création de filtres personnalisés |custom-filters] et [Fonctions |custom-functions]. -Filtres contextuels .[#toc-contextual-filters] ----------------------------------------------- -Un filtre contextuel est un filtre qui accepte un objet [api:Latte\Runtime\FilterInfo] en premier paramètre, suivi d'autres paramètres comme dans le cas des filtres classiques. Il est enregistré de la même manière, Latte reconnaît lui-même que le filtre est contextuel : +Méthode robuste : Extension Latte .{toc: Latte Extension} +========================================================= -```php -use Latte\Runtime\FilterInfo; +Bien que l'enregistrement direct soit simple, la manière standard et recommandée d'emballer et de distribuer les extensions Latte est via les classes **Extension**. Une Extension sert de point de configuration central pour enregistrer plusieurs balises, filtres, fonctions, passes de compilation et autres éléments. -$latte->addFilter('foo', function (FilterInfo $info, string $str): string { - // ... -}); -``` +Pourquoi utiliser les Extensions ? -Les filtres contextuels peuvent détecter et modifier le type de contenu qu'ils reçoivent dans la variable `$info->contentType`. Si le filtre est appelé classiquement sur une variable (par exemple `{$var|foo}`), la variable `$info->contentType` contiendra null. +- **Organisation :** Maintient les extensions liées (balises, filtres, etc. pour une fonctionnalité spécifique) ensemble dans une seule classe. +- **Réutilisabilité et partage :** Emballez facilement vos extensions pour les utiliser dans d'autres projets ou pour les partager avec la communauté (par ex. via Composer). +- **Pleine puissance :** Les balises personnalisées et les passes de compilation *ne peuvent être enregistrées que* via les Extensions. -Le filtre doit d'abord vérifier si le type de contenu de la chaîne d'entrée est pris en charge. Il peut également le modifier. Exemple d'un filtre qui accepte du texte (ou null) et renvoie du HTML : + +Enregistrement d'une Extension +------------------------------ + +Une Extension est enregistrée dans Latte à l'aide de la méthode `addExtension()` (ou via le [fichier de configuration |application:configuration#Templates Latte]) : ```php -use Latte\Runtime\FilterInfo; - -$latte->addFilter('money', function (FilterInfo $info, float $amount): string { - // Nous vérifions d'abord si le type de contenu de l'entrée est du texte. - if (!in_array($info->contentType, [null, ContentType::Text])) { - throw new Exception("Filter |money used in incompatible content type $info->contentType."); - } - - // changez le type de contenu en HTML - $info->contentType = ContentType::Html; - return "$num Kč"; -}); +$latte = new Latte\Engine; +$latte->addExtension(new MyProjectExtension); ``` -.[note] -Dans ce cas, le filtre doit assurer un échappement correct des données. +Si vous enregistrez plusieurs extensions et qu'elles définissent des balises, filtres ou fonctions du même nom, la dernière extension ajoutée a la priorité. Cela signifie également que vos extensions peuvent remplacer les balises/filtres/fonctions natifs. -Tous les filtres qui sont utilisés sur des [blocs |tags#block] (par exemple, en tant que `{block|foo}...{/block}`) doivent être contextuels. +Chaque fois que vous effectuez une modification dans la classe et que le renouvellement automatique n'est pas désactivé, Latte recompilera automatiquement vos templates. -Fonctions .{data-version:2.6}[#toc-functions] -============================================= +Création d'une Extension +------------------------ -Par défaut, toutes les fonctions natives de PHP peuvent être utilisées dans Latte, sauf si la sandbox le désactive. Mais vous pouvez aussi définir vos propres fonctions. Elles peuvent remplacer les fonctions natives. +Pour créer votre propre extension, vous devez créer une classe qui hérite de [api:Latte\Extension]. Pour avoir une idée de ce à quoi ressemble une telle extension, jetez un œil à la "CoreExtension":https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php intégrée. -Créez une fonction en enregistrant son nom et tout appelable PHP : +Examinons les méthodes que vous pouvez implémenter : -```php -$latte = new Latte\Engine; -$latte->addFunction('random', function (...$args) { - return $args[array_rand($args)]; -}); -``` -L'utilisation est alors la même que lors de l'appel de la fonction PHP : +beforeCompile(Latte\Engine $engine): void .[method] +--------------------------------------------------- -```latte -{random(apple, orange, lemon)} // prints for example: apple -``` +Appelée avant la compilation du template. La méthode peut être utilisée par exemple pour des initialisations liées à la compilation. -Fonctions utilisant la classe .[#toc-functions-using-the-class] ---------------------------------------------------------------- +getTags(): array .[method] +-------------------------- -La deuxième façon de définir une fonction est d'[utiliser la classe |develop#Parameters as a class]. Nous créons une méthode avec l'attribut `TemplateFunction`: +Appelée lors de la compilation du template. Retourne un tableau associatif *nom de la balise => objet appelable*, qui sont des fonctions pour analyser les balises. [Plus d'informations |custom-tags]. ```php -class TemplateParameters +public function getTags(): array { - public function __construct( - // parameters - ) {} - - #[Latte\Attributes\TemplateFunction] - public function random(...$args) - { - return $args[array_rand($args)]; - } + return [ + 'foo' => FooNode::create(...), + 'bar' => BarNode::create(...), + 'n:baz' => NBazNode::create(...), + // ... + ]; } - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); ``` -Si vous utilisez PHP 7.x et Latte 2.x, utilisez l'annotation `/** @function */` au lieu de l'attribut. +La balise `n:baz` représente un pur [n:attribut |syntax#n:attributs], c'est-à-dire une balise qui ne peut être écrite que comme un attribut. +Pour les balises `foo` et `bar`, Latte reconnaît automatiquement s'il s'agit de balises paires, et si oui, elles peuvent être automatiquement écrites à l'aide de n:attributs, y compris les variantes avec les préfixes `n:inner-foo` et `n:tag-foo`. -Chargeurs .[#toc-loaders] -========================= +L'ordre d'exécution de ces n:attributs est déterminé par leur ordre dans le tableau retourné par la méthode `getTags()`. Ainsi, `n:foo` est toujours exécuté avant `n:bar`, même si les attributs dans la balise HTML sont listés dans l'ordre inverse comme `
                                                              `. -Les chargeurs sont responsables du chargement des modèles à partir d'une source, comme un système de fichiers. Ils sont définis à l'aide de la méthode `setLoader()`: +Si vous avez besoin de déterminer l'ordre des n:attributs sur plusieurs extensions, utilisez la méthode d'aide `order()`, où le paramètre `before` xor `after` spécifie quelles balises sont triées avant ou après la balise. ```php -$latte->setLoader(new MyLoader); +public function getTags(): array +{ + return [ + 'foo' => self::order(FooNode::create(...), before: 'bar')] + 'bar' => self::order(BarNode::create(...), after: ['block', 'snippet'])] + ]; +} ``` -Les chargeurs intégrés sont : +getPasses(): array .[method] +---------------------------- -FileLoader .[#toc-fileloader] ------------------------------ +Appelée lors de la compilation du template. Retourne un tableau associatif *nom de la passe => objet appelable*, qui sont des fonctions représentant les [passes de compilation |compiler-passes], qui parcourent et modifient l'AST. -Chargeur par défaut. Charge les modèles à partir du système de fichiers. - -L'accès aux fichiers peut être restreint en définissant le répertoire de base : +Ici aussi, la méthode d'aide `order()` peut être utilisée. La valeur des paramètres `before` ou `after` peut être `*` signifiant avant/après tout. ```php -$latte->setLoader(new Latte\Loaders\FileLoader($templateDir)); -$latte->render('test.latte'); +public function getPasses(): array +{ + return [ + 'optimize' => Passes::optimizePass(...), + 'sandbox' => self::order($this->sandboxPass(...), before: '*'), + // ... + ]; +} ``` -StringLoader .[#toc-stringloader] ---------------------------------- +beforeRender(Latte\Engine $engine): void .[method] +-------------------------------------------------- -Charge les modèles à partir de chaînes de caractères. Ce chargeur est très utile pour les tests unitaires. Il peut également être utilisé pour les petits projets où il peut être judicieux de stocker tous les modèles dans un seul fichier PHP. +Appelée avant chaque rendu du template. La méthode peut être utilisée par exemple pour initialiser des variables utilisées pendant le rendu. -```php -$latte->setLoader(new Latte\Loaders\StringLoader([ - 'main.file' => '{include other.file}', - 'other.file' => '{if true} {$var} {/if}', -])); -$latte->render('main.file'); -``` +getFilters(): array .[method] +----------------------------- -Utilisation simplifiée : +Appelée avant le rendu du template. Retourne les filtres sous forme de tableau associatif *nom du filtre => objet appelable*. [Plus d'informations |custom-filters]. ```php -$template = '{if true} {$var} {/if}'; -$latte->setLoader(new Latte\Loaders\StringLoader); -$latte->render($template); +public function getFilters(): array +{ + return [ + 'batch' => $this->batchFilter(...), + 'trim' => $this->trimFilter(...), + // ... + ]; +} ``` -Création d'un chargeur personnalisé .[#toc-creating-a-custom-loader] --------------------------------------------------------------------- - -Loader est une classe qui implémente l'interface [api:Latte\Loader]. +getFunctions(): array .[method] +------------------------------- +Appelée avant le rendu du template. Retourne les fonctions sous forme de tableau associatif *nom de la fonction => objet appelable*. [Plus d'informations |custom-functions]. -Tags .[#toc-tags] -================= +```php +public function getFunctions(): array +{ + return [ + 'clamp' => $this->clampFunction(...), + 'divisibleBy' => $this->divisibleByFunction(...), + // ... + ]; +} +``` -L'une des fonctionnalités les plus intéressantes du moteur de création de modèles est la possibilité de définir de nouvelles constructions linguistiques à l'aide de balises. Il s'agit également d'une fonctionnalité plus complexe et vous devez comprendre le fonctionnement interne de Latte. -Dans la plupart des cas, cependant, la balise n'est pas nécessaire : -- si elle doit générer une sortie, utilisez plutôt la [fonction |#functions] -- s'il s'agit de modifier une entrée et de la renvoyer, utilisez plutôt le [filtre |#filters] -- s'il s'agit d'éditer une zone de texte, il faut l'entourer d'une balise [`{block}` |tags#block] et utiliser un [filtre |#Contextual Filters] -- si elle n'est pas censée produire quelque chose mais juste appeler une fonction, appelez-la avec [`{do}` |tags#do] +getProviders(): array .[method] +------------------------------- -Si vous souhaitez toujours créer une balise, c'est parfait ! Vous trouverez tous les éléments essentiels dans la section [Créer une extension |creating-extension]. +Appelée avant le rendu du template. Retourne un tableau de fournisseurs, qui sont généralement des objets utilisés par les balises à l'exécution. On y accède via `$this->global->...`. [Plus d'informations |custom-tags#Présentation des fournisseurs]. +```php +public function getProviders(): array +{ + return [ + 'myFoo' => $this->foo, + 'myBar' => $this->bar, + // ... + ]; +} +``` -Passes du compilateur .{data-version:3.0}[#toc-compiler-passes] -=============================================================== -Les passes du compilateur sont des fonctions qui modifient les AST ou collectent des informations dans ces derniers. Dans Latte, par exemple, un bac à sable est implémenté de cette manière : il parcourt tous les noeuds d'un AST, trouve les appels de fonctions et de méthodes, et les remplace par des appels contrôlés. +getCacheKey(Latte\Engine $engine): mixed .[method] +-------------------------------------------------- -Comme pour les balises, il s'agit d'une fonctionnalité plus complexe et vous devez comprendre comment Latte fonctionne sous le capot. Vous trouverez tous les éléments essentiels dans le chapitre [Création d'une extension |creating-extension]. +Appelée avant le rendu du template. La valeur de retour fait partie de la clé dont le hachage est contenu dans le nom du fichier du template compilé. Pour différentes valeurs de retour, Latte générera donc différents fichiers de cache. diff --git a/latte/fr/filters.texy b/latte/fr/filters.texy index c9c825332f..4e9669f3f9 100644 --- a/latte/fr/filters.texy +++ b/latte/fr/filters.texy @@ -1,112 +1,114 @@ -Filtres de Latte -**************** +Filtres Latte +************* .[perex] -Les filtres sont des fonctions qui modifient ou formatent les données selon nos souhaits. Voici un résumé des filtres intégrés disponibles. +Dans les templates, nous pouvons utiliser des fonctions qui aident à modifier ou reformater les données dans leur forme finale. Nous les appelons *filtres*. .[table-latte-filters] -|## Transformation de chaînes / tableaux -| `batch` | [Liste les données linéaires dans un tableau |#batch] -| `breakLines` | [Insère des sauts de ligne HTML avant toutes les nouvelles lignes |#breakLines] -| `bytes` | [Formate la taille en octets |#bytes] -| `clamp` | [Fixe la valeur à l'intervalle |#clamp] -| `dataStream` | [Conversion du protocole URI des données |#datastream] -| `date` | [Formate la date |#date] -| `explode` | [divise une chaîne de caractères par le délimiteur donné |#explode] -| `first` | [renvoie le premier élément d'un tableau ou le premier caractère d'une chaîne de caractères |#first] -| `implode` | [joint un tableau à une chaîne de caractères |#implode] -| `indent` | [indente le texte à partir de la gauche avec un nombre de tabulations |#indent] -| `join` | [joint un tableau à une chaîne de caractères |#implode] -| `last` | [renvoie le dernier élément d'un tableau ou le dernier caractère d'une chaîne de caractères |#last] -| `length` | [retourne la longueur d'une chaîne ou d'un tableau |#length] -| `number` | [Formate un nombre |#number] -| `padLeft` | [Complète une chaîne de caractères à une longueur donnée en partant de la gauche |#padLeft] -| `padRight` | [Complète la chaîne à la longueur donnée à partir de la droite |#padRight] -| `random` | [renvoie un élément aléatoire d'un tableau ou un caractère d'une chaîne de caractères |#random] -| `repeat` | [répète la chaîne de caractères |#repeat] -| `replace` | [remplace toutes les occurrences de la chaîne de recherche par le remplacement |#replace] -| `replaceRE` | [remplace toutes les occurrences selon l'expression régulière |#replaceRE] -| `reverse` | [inverse une chaîne ou un tableau UTF-8 |#reverse] -| `slice` | [extrait une tranche d'un tableau ou d'une chaîne de caractères |#slice] -| `sort` | [trie un tableau |#sort] -| `spaceless` | [supprime les espaces |#spaceless], similaire à la balise [spaceless |tags] -| `split` | [divise une chaîne de caractères par le délimiteur donné |#explode] -| `strip` | [supprime les espaces blancs |#spaceless] -| `stripHtml` | [supprime les balises HTML et convertit les entités HTML en texte |#stripHtml] -| `substr` | [retourne une partie de la chaîne |#substr] -| `trim` | [supprime les espaces de la chaîne de caractères |#trim] -| `translate` | [traduction dans d'autres langues |#translate] -| `truncate` | [raccourcit la longueur en préservant les mots entiers |#truncate] -| `webalize` | [ajuste la chaîne UTF-8 à la forme utilisée dans l'URL |#webalize] +|## Transformation +| `batch` | [affichage des données linéaires dans un tableau |#batch] +| `breakLines` | [Ajoute des sauts de ligne HTML avant les fins de ligne |#breakLines] +| `bytes` | [formate la taille en octets |#bytes] +| `clamp` | [limite la valeur à une plage donnée |#clamp] +| `dataStream` | [conversion pour le protocole Data URI |#dataStream] +| `date` | [formate la date et l'heure |#date] +| `explode` | [divise une chaîne en un tableau par un délimiteur |#explode] +| `first` | [retourne le premier élément d'un tableau ou caractère d'une chaîne |#first] +| `group` | [regroupe les données selon différents critères |#group] +| `implode` | [joint un tableau en une chaîne |#implode] +| `indent` | [indente le texte depuis la gauche d'un nombre donné de tabulations |#indent] +| `join` | [joint un tableau en une chaîne |#implode] +| `last` | [retourne le dernier élément d'un tableau ou caractère d'une chaîne |#last] +| `length` | [retourne la longueur d'une chaîne en caractères ou d'un tableau |#length] +| `localDate` | [formate la date et l'heure selon les paramètres régionaux |#localDate] +| `number` | [formate un nombre |#number] +| `padLeft` | [complète une chaîne depuis la gauche jusqu'à la longueur souhaitée |#padLeft] +| `padRight` | [complète une chaîne depuis la droite jusqu'à la longueur souhaitée |#padRight] +| `random` | [retourne un élément aléatoire d'un tableau ou caractère d'une chaîne |#random] +| `repeat` | [répétition d'une chaîne |#repeat] +| `replace` | [remplace les occurrences de la chaîne recherchée |#replace] +| `replaceRE` | [remplace les occurrences selon une expression régulière |#replaceRE] +| `reverse` | [inverse une chaîne UTF-8 ou un tableau |#reverse] +| `slice` | [extrait une partie d'un tableau ou d'une chaîne |#slice] +| `sort` | [trie un tableau |#sort] +| `spaceless` | [supprime les espaces blancs |#spaceless], similaire à la balise [spaceless |tags] +| `split` | [divise une chaîne en un tableau par un délimiteur |#explode] +| `strip` | [supprime les espaces blancs |#spaceless] +| `stripHtml` | [supprime les balises HTML et convertit les entités HTML en caractères |#stripHtml] +| `substr` | [retourne une partie d'une chaîne |#substr] +| `trim` | [supprime les espaces ou autres caractères en début et fin de chaîne |#trim] +| `translate` | [traduction dans d'autres langues |#translate] +| `truncate` | [raccourcit la longueur en préservant les mots |#truncate] +| `webalize` | [modifie une chaîne UTF-8 dans la forme utilisée dans les URL |#webalize] .[table-latte-filters] -|## Mise en forme des lettres -| `capitalize` | [minuscule, la première lettre de chaque mot majuscule |#capitalize] -| `firstUpper` | [met la première lettre en majuscule |#firstUpper] -| `lower` | [met une chaîne de caractères en minuscule |#lower] -| `upper` | [met une chaîne de caractères en majuscule |#upper] +|## Casse des lettres +| `capitalize` | [minuscules, première lettre des mots en majuscule |#capitalize] +| `firstUpper` | [convertit la première lettre en majuscule |#firstUpper] +| `lower` | [convertit en minuscules |#lower] +| `upper` | [convertit en majuscules |#upper] .[table-latte-filters] -|## Arrondir les nombres -| `ceil` | [arrondit un nombre à une précision donnée |#ceil] -| `floor` | [arrondit un nombre à une précision donnée vers le bas |#floor] -| `round` | [arrondit un nombre à une précision donnée |#round] +|## Arrondi +| `ceil` | [arrondit un nombre vers le haut à la précision donnée |#ceil] +| `floor` | [arrondit un nombre vers le bas à la précision donnée |#floor] +| `round` | [arrondit un nombre à la précision donnée |#round] .[table-latte-filters] -|## Escapes -| `escapeUrl` | [échappe le paramètre dans l'URL |#escapeUrl] -| `noescape` | [imprime une variable sans échappement |#noescape] -| `query` | [génère une chaîne de requête dans l'URL |#query] +|## Échappement +| `escapeUrl` | [échappe un paramètre dans une URL |#escapeUrl] +| `noescape` | [affiche la variable sans échappement |#noescape] +| `query` | [génère une chaîne de requête dans une URL |#query] -Il existe également des filtres d'échappement pour HTML (`escapeHtml` et `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) et iCalendar (`escapeICal`), que Latte utilise lui-même grâce à l'[échappement contextuel |safety-first#Context-aware escaping] et que vous n'avez pas besoin d'écrire. +De plus, il existe des filtres d'échappement pour HTML (`escapeHtml` et `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) et iCalendar (`escapeICal`), que Latte utilise lui-même grâce à l'[échappement contextuel |safety-first#Échappement contextuel] et que vous n'avez pas besoin d'écrire. .[table-latte-filters] |## Sécurité -| `checkUrl` | [aseptise la chaîne de caractères à utiliser dans l'attribut href |#checkUrl] -| `nocheck` | [empêche la désinfection automatique des URLs |#nocheck] +| `checkUrl` | [nettoie une adresse URL des entrées dangereuses |#checkUrl] +| `nocheck` | [empêche le nettoyage automatique de l'adresse URL |#nocheck] -Les [vérifications des |safety-first#link checking] attributs `src` et `href` sont [automatiques |safety-first#link checking], de sorte que vous n'avez pratiquement pas besoin d'utiliser le filtre `checkUrl`. +Les attributs Latte `src` et `href` [vérifient automatiquement |safety-first#Vérification des liens], donc vous n'avez presque jamais besoin d'utiliser le filtre `checkUrl`. .[note] -Tous les filtres intégrés fonctionnent avec des chaînes encodées en UTF-8. +Tous les filtres par défaut sont conçus pour les chaînes encodées en UTF‑8. -Utilisation .[#toc-usage] -========================= +Utilisation +=========== -Latte permet d'appeler des filtres en utilisant la notation du signe pipe (l'espace précédent est autorisé) : +Les filtres s'écrivent après une barre verticale (il peut y avoir un espace avant) : ```latte

                                                              {$heading|upper}

                                                              ``` -Les filtres peuvent être enchaînés, dans ce cas ils s'appliquent dans l'ordre de gauche à droite : +Les filtres (appelés helpers dans les anciennes versions) peuvent être chaînés et sont alors appliqués dans l'ordre de gauche à droite : ```latte

                                                              {$heading|lower|capitalize}

                                                              ``` -Les paramètres sont placés après le nom du filtre, séparés par des deux points ou des virgules : +Les paramètres sont spécifiés après le nom du filtre, séparés par des deux-points ou des virgules : ```latte

                                                              {$heading|truncate:20,''}

                                                              ``` -Les filtres peuvent être appliqués sur une expression : +Les filtres peuvent également être appliqués à une expression : ```latte {var $name = ($title|upper) . ($subtitle|lower)} ``` -Les [filtres personnalisés |extending-latte#filters] peuvent être enregistrés de cette manière : +Les [filtres personnalisés|custom-filters] peuvent être enregistrés de cette manière : ```php $latte = new Latte\Engine; $latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -Nous l'utilisons dans un modèle comme celui-ci : +Dans le template, on l'appelle ensuite comme ceci : ```latte

                                                              {$text|shortify}

                                                              @@ -114,18 +116,18 @@ Nous l'utilisons dans un modèle comme celui-ci : ``` -Filtres .[#toc-filters] -======================= +Filtres +======= -batch(int length, mixed item): array .[filter]{data-version:2.7} ----------------------------------------------------------------- -Filtre qui simplifie l'énumération de données linéaires sous la forme d'un tableau. Il retourne un tableau de tableaux avec le nombre d'éléments donné. Si vous fournissez un second paramètre, celui-ci est utilisé pour remplir les éléments manquants sur la dernière ligne. +batch(int $length, mixed $item): array .[filter] +------------------------------------------------ +Filtre qui simplifie l'affichage de données linéaires sous forme de tableau. Retourne un tableau de tableaux avec le nombre d'éléments spécifié. Si vous spécifiez le deuxième paramètre, il sera utilisé pour compléter les éléments manquants sur la dernière ligne. ```latte {var $items = ['a', 'b', 'c', 'd', 'e']}
                                                              Boucle interne
                                                              -{foreach ($items|batch: 3, 'No item') as $row} +{foreach ($items|batch: 3, 'Aucun élément') as $row} {foreach $row as $column} @@ -135,7 +137,7 @@ Filtre qui simplifie l'énumération de données linéaires sous la forme d'un t
                                                              {$column}
                                                              ``` -Imprime : +Affiche : ```latte @@ -147,98 +149,100 @@ Imprime : - +
                                                              d eNo itemAucun élément
                                                              ``` +Voir aussi [#group] et la balise [iterateWhile |tags#iterateWhile]. + breakLines .[filter] -------------------- -Insère des sauts de ligne HTML avant tous les retours à la ligne. +Ajoute la balise HTML `
                                                              ` avant chaque caractère de nouvelle ligne. ```latte -{var $s = "Text & with \n newline"} -{$s|breakLines} {* sorties "Text & with
                                                              \n newline" *} +{var $s = "Texte & avec \n nouvelle ligne"} +{$s|breakLines} {* affiche "Texte & avec
                                                              \n nouvelle ligne" *} ``` -bytes(int precision = 2) .[filter] ----------------------------------- -Formate une taille en octets sous une forme lisible par l'homme. +bytes(int $precision=2) .[filter] +--------------------------------- +Formate la taille en octets dans un format lisible par l'homme. Si les [paramètres régionaux |develop#Locale] sont définis, les séparateurs décimaux et de milliers correspondants sont utilisés. ```latte -{$size|bytes} 0 B, 1.25 GB, … -{$size|bytes:0} 10 B, 1 GB, … +{$size|bytes} 0 o, 1.25 Go, … +{$size|bytes:0} 10 o, 1 Go, … ``` -ceil(int precision = 0) .[filter] ---------------------------------- -Arrondit un nombre à une précision donnée. +ceil(int $precision=0) .[filter] +-------------------------------- +Arrondit un nombre vers le haut à la précision donnée. ```latte -{=3.4|ceil} {* sorties 4 *} -{=135.22|ceil:1} {* sorties 135.3 *} -{=135.22|ceil:3} {* sorties 135.22 *} +{=3.4|ceil} {* affiche 4 *} +{=135.22|ceil:1} {* affiche 135.3 *} +{=135.22|ceil:3} {* affiche 135.22 *} ``` -Voir aussi [plancher |#floor], [rond |#round]. +Voir aussi [#floor], [#round]. capitalize .[filter] -------------------- -Renvoie une version en capitales de la valeur. Les mots commencent par des majuscules, tous les autres caractères sont en minuscules. Nécessite l'extension PHP `mbstring`. +Les mots commenceront par des majuscules, tous les caractères restants seront en minuscules. Nécessite l'extension PHP `mbstring`. ```latte -{='i like LATTE'|capitalize} {* outputs 'I Like Latte' *} +{='j\'aime LATTE'|capitalize} {* affiche 'J\'aime Latte' *} ``` -Voir aussi [firstUpper |#firstUpper], [lower |#lower], [upper |#upper]. +Voir aussi [#firstUpper], [#lower], [#upper]. checkUrl .[filter] ------------------ -Assure la désinfection des URL. Il vérifie si la variable contient une URL web (c'est-à-dire un protocole HTTP/HTTPS) et empêche l'écriture de liens qui peuvent présenter un risque pour la sécurité. +Force le nettoyage de l'adresse URL. Vérifie si la variable contient une URL web (c'est-à-dire protocole HTTP/HTTPS) et empêche l'affichage de liens qui pourraient présenter un risque de sécurité. ```latte {var $link = 'javascript:window.close()'} -checked -unchecked +vérifié +non vérifié ``` -Imprime : +Affiche : ```latte -checked -unchecked +vérifié +non vérifié ``` -Voir aussi [nocheck |#nocheck]. +Voir aussi [#nocheck]. -clamp(int|float min, int|float max) .[filter]{data-version:2.9} ---------------------------------------------------------------- -Renvoie une valeur limitée à l'intervalle inclusif de min et max. +clamp(int|float $min, int|float $max) .[filter] +----------------------------------------------- +Limite la valeur à la plage inclusive donnée min et max. ```latte {$level|clamp: 0, 255} ``` -Existe aussi en tant que [fonction |functions#clamp]. +Existe aussi comme [fonction |functions#clamp]. -dataStream(string mimetype = detect) .[filter] ----------------------------------------------- -Convertit le contenu en schéma URI de données. Il peut être utilisé pour insérer des images dans le HTML ou le CSS sans avoir besoin de lier des fichiers externes. +dataStream(string $mimetype=detect) .[filter] +--------------------------------------------- +Convertit le contenu en schéma data URI. Permet d'insérer des images dans HTML ou CSS sans avoir besoin de lier des fichiers externes. -Si nous avons une image dans une variable `$img = Image::fromFile('obrazek.gif')`, alors +Ayons une image dans la variable `$img = Image::fromFile('image.gif')`, alors ```latte - + ``` -Imprime par exemple : +Affiche par exemple : ```latte {$name} ``` -Voir aussi [query |#query]. +Voir aussi [#query]. -explode(string separator = '') .[filter]{data-version:2.10.2} -------------------------------------------------------------- -Divise une chaîne de caractères par le délimiteur donné et renvoie un tableau de chaînes de caractères. Alias pour `split`. +explode(string $separator='') .[filter] +--------------------------------------- +Divise une chaîne en un tableau selon le délimiteur. Alias pour `split`. ```latte -{='one,two,three'|explode:','} {* returns ['one', 'two', 'three'] *} +{='un,deux,trois'|explode:','} {* retourne ['un', 'deux', 'trois'] *} ``` Si le délimiteur est une chaîne vide (valeur par défaut), l'entrée sera divisée en caractères individuels : ```latte -{='123'|explode} {* returns ['1', '2', '3'] *} +{='123'|explode} {* retourne ['1', '2', '3'] *} ``` -Vous pouvez également utiliser l'alias `split`: +Vous pouvez également utiliser l'alias `split` : ```latte -{='1,2,3'|split:','} {* returns ['1', '2', '3'] *} +{='1,2,3'|split:','} {* retourne ['1', '2', '3'] *} ``` -Voir aussi [implode |#implode]. +Voir aussi [#implode]. -first .[filter]{data-version:2.10.2} ------------------------------------- -Renvoie le premier élément d'un tableau ou le premier caractère d'une chaîne de caractères : +first .[filter] +--------------- +Retourne le premier élément d'un tableau ou caractère d'une chaîne : ```latte -{=[1, 2, 3, 4]|first} {* sorties 1 *} -{='abcd'|first} {* sorties 'a' *} +{=[1, 2, 3, 4]|first} {* affiche 1 *} +{='abcd'|first} {* affiche 'a' *} ``` -Voir aussi [last |#last], [random |#random]. +Voir aussi [#last], [#random]. -floor(int precision = 0) .[filter] ----------------------------------- -Arrondit un nombre à une précision donnée. +floor(int $precision=0) .[filter] +--------------------------------- +Arrondit un nombre vers le bas à la précision donnée. ```latte -{=3.5|floor} {* sorties 3 *} -{=135.79|floor:1} {* sorties 135.7 *} -{=135.79|floor:3} {* sorties 135.79 *} +{=3.5|floor} {* affiche 3 *} +{=135.79|floor:1} {* affiche 135.7 *} +{=135.79|floor:3} {* affiche 135.79 *} ``` -Voir aussi [ceil |#ceil], [round |#round]. +Voir aussi [#ceil], [#round]. firstUpper .[filter] -------------------- -Convertit la première lettre d'une valeur en majuscule. Nécessite l'extension PHP `mbstring`. +Convertit la première lettre en majuscule. Nécessite l'extension PHP `mbstring`. ```latte -{='the latte'|firstUpper} {* sorties 'The latte' *} +{='le latte'|firstUpper} {* affiche 'Le latte' *} ``` -Voir aussi [majuscule |#capitalize], [inférieur |#lower], [supérieur |#upper]. +Voir aussi [#capitalize], [#lower], [#upper]. -implode(string glue = '') .[filter] ------------------------------------ -Retourne une chaîne de caractères qui est la concaténation des chaînes de caractères du tableau. Alias pour `join`. +group(string|int|\Closure $by): array .[filter]{data-version:3.0.16} +-------------------------------------------------------------------- +Le filtre regroupe les données selon différents critères. + +Dans cet exemple, les lignes du tableau sont regroupées par la colonne `categoryId`. La sortie est un tableau de tableaux où la clé est la valeur de la colonne `categoryId`. [Lisez le tutoriel détaillé|cookbook/grouping]. ```latte -{=[1, 2, 3]|implode} {* sorties '123' *} -{=[1, 2, 3]|implode:'|'} {* sorties '1|2|3' *} +{foreach ($items|group: categoryId) as $categoryId => $categoryItems} +
                                                                + {foreach $categoryItems as $item} +
                                                              • {$item->name}
                                                              • + {/foreach} +
                                                              +{/foreach} ``` -Vous pouvez également utiliser un alias `join`: .{data-version:2.10.2} +Voir aussi [#batch], la fonction [group |functions#group] et la balise [iterateWhile |tags#iterateWhile]. + + +implode(string $glue='') .[filter] +---------------------------------- +Retourne une chaîne qui est la concaténation des éléments de la séquence. Alias pour `join`. ```latte -{=[1, 2, 3]|join} {* sorties '123' *} +{=[1, 2, 3]|implode} {* affiche '123' *} +{=[1, 2, 3]|implode:'|'} {* affiche '1|2|3' *} ``` +Vous pouvez également utiliser l'alias `join` : + +```latte +{=[1, 2, 3]|join} {* affiche '123' *} +``` -indent(int level = 1, string char = "\t") .[filter] ---------------------------------------------------- -Indente un texte à partir de la gauche d'un nombre donné de tabulations ou d'autres caractères que nous spécifions dans le deuxième argument facultatif. Les lignes vides ne sont pas indentées. + +indent(int $level=1, string $char="\t") .[filter] +------------------------------------------------- +Indente le texte depuis la gauche d'un nombre donné de tabulations ou d'autres caractères que nous pouvons spécifier dans le deuxième argument. Les lignes vides ne sont pas indentées. ```latte
                                                              {block |indent} -

                                                              Hello

                                                              +

                                                              Bonjour

                                                              {/block}
                                                              ``` -Imprime : +Affiche : ```latte
                                                              -

                                                              Hello

                                                              +

                                                              Bonjour

                                                              ``` -last .[filter]{data-version:2.10.2} ------------------------------------ -Renvoie le dernier élément du tableau ou le dernier caractère de la chaîne de caractères : +last .[filter] +-------------- +Retourne le dernier élément d'un tableau ou caractère d'une chaîne : ```latte -{=[1, 2, 3, 4]|last} {* sorties 4 *} -{='abcd'|last} {* sorties 'd' *} +{=[1, 2, 3, 4]|last} {* affiche 4 *} +{='abcd'|last} {* affiche 'd' *} ``` -Voir aussi [first |#first], [random |#random]. +Voir aussi [#first], [#random]. length .[filter] ---------------- -Renvoie la longueur d'une chaîne ou d'un tableau. +Retourne la longueur d'une chaîne ou d'un tableau. -- pour les chaînes, elle renvoie la longueur en caractères UTF-8 -- pour les tableaux, elle renvoie le nombre d'éléments. -- pour les objets qui implémentent l'interface Countable, elle utilisera la valeur de retour de la fonction count() -- pour les objets qui implémentent l'interface IteratorAggregate, il utilisera la valeur de retour de la fonction iterator_count(). +- pour les chaînes, retourne la longueur en caractères UTF‑8 +- pour les tableaux, retourne le nombre d'éléments +- pour les objets qui implémentent l'interface Countable, utilise la valeur de retour de la méthode count() +- pour les objets qui implémentent l'interface IteratorAggregate, utilise la valeur de retour de la fonction iterator_count() ```latte @@ -396,38 +420,100 @@ Renvoie la longueur d'une chaîne ou d'un tableau. ``` +localDate(?string $format=null, ?string $date=null, ?string $time=null) .[filter] +--------------------------------------------------------------------------------- +Formate la date et l'heure selon les [paramètres régionaux |develop#Locale], ce qui assure un affichage cohérent et localisé des données temporelles à travers différentes langues et régions. Le filtre accepte la date comme timestamp UNIX, chaîne ou objet de type `DateTimeInterface`. + +```latte +{$date|localDate} {* 15 avril 2024 *} +{$date|localDate: format: yM} {* 4/2024 *} +{$date|localDate: date: medium} {* 15 avr. 2024 *} +``` + +Si vous utilisez le filtre sans paramètres, la date sera affichée au niveau `long`, voir ci-dessous. + +**a) utilisation du format** + +Le paramètre `format` décrit quels composants temporels doivent être affichés. Il utilise pour cela des codes alphabétiques, dont le nombre de répétitions influence la largeur de la sortie : + +| année | `y` / `yy` / `yyyy` | `2024` / `24` / `2024` +| mois | `M` / `MM` / `MMM` / `MMMM` | `8` / `08` / `août` / `août` +| jour | `d` / `dd` / `E` / `EEEE` | `1` / `01` / `dim.` / `dimanche` +| heure | `j` / `H` / `h` | préféré / format 24h / format 12h +| minute | `m` / `mm` | `5` / `05` (2 chiffres en combinaison avec les secondes) +| seconde | `s` / `ss` | `8` / `08` (2 chiffres en combinaison avec les minutes) + +L'ordre des codes dans le format n'a pas d'importance, car l'ordre des composants sera affiché selon les conventions des paramètres régionaux. Le format est donc indépendant de ceux-ci. Par exemple, le format `yyyyMMMMd` dans l'environnement `en_US` affichera `April 15, 2024`, tandis que dans l'environnement `fr_FR` il affichera `15 avril 2024` : + +| locale: | fr_FR | en_US +|--- +| `format: 'dMy'` | 10/8/2024 | 8/10/2024 +| `format: 'yM'` | 8/2024 | 8/2024 +| `format: 'yyyyMMMM'` | août 2024 | August 2024 +| `format: 'MMMM'` | août | August +| `format: 'jm'` | 17:22 | 5:22 PM +| `format: 'Hm'` | 17:22 | 17:22 +| `format: 'hm'` | 17:22 | 5:22 PM + + +**b) utilisation de styles prédéfinis** + +Les paramètres `date` et `time` déterminent le niveau de détail avec lequel la date et l'heure doivent être affichées. Vous pouvez choisir parmi plusieurs niveaux : `full`, `long`, `medium`, `short`. Il est possible de n'afficher que la date, que l'heure, ou les deux : + +| locale: | fr_FR | en_US +|--- +| `date: short` | 23/01/78 | 1/23/78 +| `date: medium` | 23 janv. 1978 | Jan 23, 1978 +| `date: long` | 23 janvier 1978 | January 23, 1978 +| `date: full` | lundi 23 janvier 1978 | Monday, January 23, 1978 +| `time: short` | 08:30 | 8:30 AM +| `time: medium` | 08:30:59 | 8:30:59 AM +| `time: long` | 08:30:59 UTC+1 | 8:30:59 AM GMT+1 +| `date: short, time: short` | 23/01/78 08:30 | 1/23/78, 8:30 AM +| `date: medium, time: short` | 23 janv. 1978 08:30 | Jan 23, 1978, 8:30 AM +| `date: long, time: short` | 23 janvier 1978 à 08:30 | January 23, 1978 at 8:30 AM + +Pour la date, vous pouvez en plus utiliser le préfixe `relative-` (par ex. `relative-short`), qui pour les dates proches de l'actuelle affichera `hier`, `aujourd'hui` ou `demain`, sinon elle sera affichée de manière standard. + +```latte +{$date|localDate: date: relative-short} {* hier *} +``` + +Voir aussi [#date]. + + lower .[filter] --------------- -Convertit une valeur en minuscule. Nécessite l'extension PHP `mbstring`. +Convertit une chaîne en minuscules. Nécessite l'extension PHP `mbstring`. ```latte -{='LATTE'|lower} {* sorties 'latte' *} +{='LATTE'|lower} {* affiche 'latte' *} ``` -Voir aussi [capitalize |#capitalize], [firstUpper |#firstUpper], [upper |#upper]. +Voir aussi [#capitalize], [#firstUpper], [#upper]. nocheck .[filter] ----------------- -Empêche la désinfection automatique des URL. Latte [vérifie automatiquement |safety-first#Link checking] si la variable contient une URL web (c'est-à-dire un protocole HTTP/HTTPS) et empêche l'écriture de liens pouvant présenter un risque pour la sécurité. +Empêche le nettoyage automatique de l'adresse URL. Latte [vérifie automatiquement |safety-first#Vérification des liens] si la variable contient une URL web (c'est-à-dire protocole HTTP/HTTPS) et empêche l'affichage de liens qui pourraient présenter un risque de sécurité. -Si le lien utilise un schéma différent, tel que `javascript:` ou `data:`, et que vous êtes sûr de son contenu, vous pouvez désactiver la vérification via `|nocheck`. +Si le lien utilise un autre schéma, par ex. `javascript:` ou `data:`, et que vous êtes sûr de son contenu, vous pouvez désactiver la vérification à l'aide de `|nocheck`. ```latte {var $link = 'javascript:window.close()'} -checked -unchecked +vérifié +non vérifié ``` -Imprimés : +Affiche : ```latte -checked -unchecked +vérifié +non vérifié ``` -Voir aussi [checkUrl |#checkUrl]. +Voir aussi [#checkUrl]. noescape .[filter] @@ -435,165 +521,213 @@ noescape .[filter] Désactive l'échappement automatique. ```latte -{var $trustedHtmlString = 'hello'} -Escaped: {$trustedHtmlString} -Unescaped: {$trustedHtmlString|noescape} +{var $trustedHtmlString = 'bonjour'} +Échappé : {$trustedHtmlString} +Non échappé : {$trustedHtmlString|noescape} ``` -Imprime : +Affiche : ```latte -Escaped: <b>hello</b> -Unescaped: hello +Échappé : <b>bonjour</b> +Non échappé : bonjour ``` .[warning] -Une mauvaise utilisation du filtre `noescape` peut conduire à une vulnérabilité XSS ! Ne l'utilisez jamais sans être **absolument sûr** de ce que vous faites et que la chaîne que vous imprimez provient d'une source fiable. +Une mauvaise utilisation du filtre `noescape` peut entraîner une vulnérabilité XSS ! Ne l'utilisez jamais si vous n'êtes pas **absolument sûr** de ce que vous faites et que la chaîne affichée provient d'une source fiable. -number(int decimals = 0, string decPoint = '.', string thousandsSep = ',') .[filter] ------------------------------------------------------------------------------------- -Formate un nombre avec un nombre donné de décimales. Vous pouvez également spécifier un caractère du point décimal et du séparateur de milliers. +number(int $decimals=0, string $decPoint='.', string $thousandsSep=',') .[filter] +--------------------------------------------------------------------------------- +Formate un nombre à un certain nombre de décimales. Si les [paramètres régionaux |develop#Locale] sont définis, les séparateurs décimaux et de milliers correspondants sont utilisés. ```latte -{1234.20 |number} 1,234 -{1234.20 |number:1} 1,234.2 -{1234.20 |number:2} 1,234.20 -{1234.20 |number:2, ',', ' '} 1 234,20 +{1234.20|number} 1 234 +{1234.20|number:1} 1 234,2 +{1234.20|number:2} 1 234,20 +{1234.20|number:2, ',', ' '} 1 234,20 ``` -padLeft(int length, string pad = ' ') .[filter] +number(string $format) .[filter] +-------------------------------- +Le paramètre `format` permet de définir l'apparence des nombres exactement selon vos besoins. Pour cela, il est nécessaire d'avoir défini les [paramètres régionaux |develop#Locale]. Le format se compose de plusieurs caractères spéciaux, dont la description complète se trouve dans la documentation "DecimalFormat":https://unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns : + +- `0` chiffre obligatoire, s'affiche toujours, même s'il s'agit d'un zéro +- `#` chiffre facultatif, ne s'affiche que si le nombre existe réellement à cet endroit +- `@` chiffre significatif, aide à afficher le nombre avec un certain nombre de chiffres valides +- `.` indique où doit se trouver la virgule décimale (ou le point, selon le pays) +- `,` sert à séparer les groupes de chiffres, le plus souvent les milliers +- `%` multiplie le nombre par 100× et ajoute le signe pourcentage + +Regardons quelques exemples. Dans le premier exemple, deux décimales sont obligatoires, dans le second, elles sont facultatives. Le troisième exemple montre le remplissage avec des zéros à gauche et à droite, le quatrième n'affiche que les chiffres existants : + +```latte +{1234.5|number: '#,##0.00'} {* 1 234,50 *} +{1234.5|number: '#,##0.##'} {* 1 234,5 *} +{1.23 |number: '000.000'} {* 001,230 *} +{1.2 |number: '##.##'} {* 1,2 *} +``` + +Les chiffres significatifs déterminent combien de chiffres, indépendamment de la virgule décimale, doivent être affichés, en arrondissant : + +```latte +{1234|number: '@@'} {* 1 200 *} +{1234|number: '@@@'} {* 1 230 *} +{1234|number: '@@@#'} {* 1 234 *} +{1.2345|number: '@@@'} {* 1,23 *} +{0.00123|number: '@@'} {* 0,0012 *} +``` + +Un moyen facile d'afficher un nombre en pourcentage. Le nombre est multiplié par 100× et le signe `%` est ajouté : + +```latte +{0.1234|number: '#.##%'} {* 12,34 % *} +``` + +Nous pouvons définir un format différent pour les nombres positifs et négatifs, séparés par le caractère `;`. De cette manière, on peut par exemple définir que les nombres positifs doivent être affichés avec le signe `+` : + +```latte +{42|number: '#.##;(#.##)'} {* 42 *} +{-42|number: '#.##;(#.##)'} {* (42) *} +{42|number: '+#.##;-#.##'} {* +42 *} +{-42|number: '+#.##;-#.##'} {* -42 *} +``` + +N'oubliez pas que l'apparence réelle des nombres peut varier en fonction des paramètres du pays. Par exemple, dans certains pays, on utilise une virgule au lieu d'un point comme séparateur décimal. Ce filtre en tient compte automatiquement et vous n'avez rien à faire. + + +padLeft(int $length, string $pad=' ') .[filter] ----------------------------------------------- -Remplit une chaîne de caractères d'une certaine longueur avec une autre chaîne de caractères à partir de la gauche. +Complète une chaîne à une certaine longueur avec une autre chaîne depuis la gauche. ```latte -{='hello'|padLeft: 10, '123'} {* outputs '12312hello' *} +{='bonjour'|padLeft: 10, '123'} {* affiche '1231bonjour' *} ``` -padRight(int length, string pad = ' ') .[filter] +padRight(int $length, string $pad=' ') .[filter] ------------------------------------------------ -Remplir une chaîne de caractères d'une certaine longueur avec une autre chaîne de caractères de droite. +Complète une chaîne à une certaine longueur avec une autre chaîne depuis la droite. ```latte -{='hello'|padRight: 10, '123'} {* outputs 'hello12312' *} +{='bonjour'|padRight: 10, '123'} {* affiche 'bonjour1231' *} ``` -query .[filter]{data-version:2.10} ------------------------------------ -Génère dynamiquement une chaîne de requête dans l'URL : +query .[filter] +--------------- +Génère dynamiquement la chaîne de requête dans une URL : ```latte -click -search +cliquer +rechercher ``` -Imprime : +Affiche : ```latte -click -search +cliquer +rechercher ``` -Les touches dont la valeur est `null` sont omises. +Les clés avec la valeur `null` sont omises. -Voir également [escapeUrl |#escapeUrl]. +Voir aussi [#escapeUrl]. -random .[filter]{data-version:2.10.2} -------------------------------------- -Renvoie un élément aléatoire du tableau ou un caractère de la chaîne : +random .[filter] +---------------- +Retourne un élément aléatoire d'un tableau ou caractère d'une chaîne : ```latte -{=[1, 2, 3, 4]|random} {* example output: 3 *} -{='abcd'|random} {* example output: 'b' *} +{=[1, 2, 3, 4]|random} {* affiche par ex. : 3 *} +{='abcd'|random} {* affiche par ex. : 'b' *} ``` -Voir aussi [first |#first], [last |#last]. +Voir aussi [#first], [#last]. -repeat(int count) .[filter] ---------------------------- +repeat(int $count) .[filter] +---------------------------- Répète la chaîne x fois. ```latte -{='hello'|repeat: 3} {* outputs 'hellohellohello' *} +{='bonjour'|repeat: 3} {* affiche 'bonjourbonjourbonjour' *} ``` -replace(string|array search, string replace = '') .[filter] +replace(string|array $search, string $replace='') .[filter] ----------------------------------------------------------- Remplace toutes les occurrences de la chaîne de recherche par la chaîne de remplacement. ```latte -{='hello world'|replace: 'world', 'friend'} {* outputs 'hello friend' *} +{='bonjour le monde'|replace: 'monde', 'ami'} {* affiche 'bonjour ami' *} ``` -Plusieurs remplacements peuvent être effectués en même temps : .{data-version:2.10.2} +Il est possible d'effectuer plusieurs remplacements à la fois : ```latte -{='hello world'|replace: [h => l, l => h]} {* outputs 'lehho worhd' *} +{='bonjour le monde'|replace: [b => l, l => b]} {* affiche 'lonjour be monde' *} ``` -replaceRE(string pattern, string replace = '') .[filter] +replaceRE(string $pattern, string $replace='') .[filter] -------------------------------------------------------- -Remplace toutes les occurrences selon l'expression régulière. +Effectue une recherche d'expressions régulières avec remplacement. ```latte -{='hello world'|replaceRE: '/l.*/', 'l'} {* outputs 'hel' *} +{='bonjour le monde'|replaceRE: '/l.*/', 'l'} {* affiche 'bonjol' *} ``` reverse .[filter] ----------------- -Inverse une chaîne ou un tableau donné. +Inverse la chaîne ou le tableau donné. ```latte {var $s = 'Nette'} -{$s|reverse} {* sorties 'etteN' *} +{$s|reverse} {* affiche 'etteN' *} {var $a = ['N', 'e', 't', 't', 'e']} -{$a|reverse} {* returns ['e', 't', 't', 'e', 'N'] *} +{$a|reverse} {* retourne ['e', 't', 't', 'e', 'N'] *} ``` -round(int precision = 0) .[filter] ----------------------------------- -Arrondit un nombre à une précision donnée. +round(int $precision=0) .[filter] +--------------------------------- +Arrondit un nombre à la précision donnée. ```latte -{=3.4|round} {* sorties 3 *} -{=3.5|round} {* sorties 4 *} -{=135.79|round:1} {* sorties 135.8 *} -{=135.79|round:3} {* sorties 135.79 *} +{=3.4|round} {* affiche 3 *} +{=3.5|round} {* affiche 4 *} +{=135.79|round:1} {* affiche 135.8 *} +{=135.79|round:3} {* affiche 135.79 *} ``` -Voir aussi [ceil |#ceil], [floor |#floor]. +Voir aussi [#ceil], [#floor]. -slice(int start, int length = null, bool preserveKeys = false) .[filter]{data-version:2.10.2} ---------------------------------------------------------------------------------------------- -Extrait une tranche d'un tableau ou d'une chaîne de caractères. +slice(int $start, ?int $length=null, bool $preserveKeys=false) .[filter] +------------------------------------------------------------------------ +Extrait une partie d'un tableau ou d'une chaîne. ```latte -{='hello'|slice: 1, 2} {* sorties 'el' *} -{=['a', 'b', 'c']|slice: 1, 2} {* sorties ['b', 'c'] *} +{='bonjour'|slice: 1, 2} {* affiche 'on' *} +{=['a', 'b', 'c']|slice: 1, 2} {* affiche ['b', 'c'] *} ``` -Le filtre de tranche fonctionne comme la fonction PHP `array_slice` pour les tableaux et `mb_substr` pour les chaînes de caractères, avec un retour à `iconv_substr` en mode UTF-8. +Le filtre fonctionne comme la fonction PHP `array_slice` pour les tableaux ou `mb_substr` pour les chaînes avec un fallback sur la fonction `iconv_substr` en mode UTF‑8. -Si start est non négatif, la séquence commencera à ce point de départ dans la variable. Si start est négatif, la séquence commencera à cette distance de la fin de la variable. +Si start est positif, la séquence commencera décalée de ce nombre depuis le début du tableau/chaîne. Si il est négatif, la séquence commencera décalée d'autant depuis la fin. -Si length est donné et est positif, alors la séquence aura jusqu'à ce nombre d'éléments. Si la variable est plus courte que la longueur, seuls les éléments disponibles de la variable seront présents. Si la longueur est donnée et qu'elle est négative, la séquence s'arrêtera à ce nombre d'éléments à partir de la fin de la variable. Si elle est omise, alors la séquence contiendra tous les éléments depuis le décalage jusqu'à la fin de la variable. +Si le paramètre length est spécifié et est positif, la séquence contiendra autant d'éléments. Si un paramètre length négatif est passé à cette fonction, la séquence contiendra tous les éléments du tableau d'origine, commençant à la position start et se terminant à la position inférieure de length éléments par rapport à la fin du tableau. Si vous ne spécifiez pas ce paramètre, la séquence contiendra tous les éléments du tableau d'origine, commençant à la position start. -Filter réordonnera et réinitialisera les clés du tableau d'entiers par défaut. Ce comportement peut être modifié en définissant preserveKeys à true. Les clés des chaînes de caractères sont toujours préservées, quel que soit ce paramètre. +Par défaut, le filtre modifie l'ordre et réinitialise les clés entières du tableau. Ce comportement peut être modifié en définissant preserveKeys sur true. Les clés de chaîne sont toujours préservées, quel que soit ce paramètre. -sort .[filter]{data-version:2.9} ---------------------------------- -Filtre qui trie un tableau et maintient l'association des index. +sort(?Closure $comparison, string|int|\Closure|null $by=null, string|int|\Closure|bool $byKey=false) .[filter] +-------------------------------------------------------------------------------------------------------------- +Le filtre trie les éléments d'un tableau ou d'un itérateur et préserve leurs clés associatives. Si les [paramètres régionaux |develop#Locale] sont définis, le tri suit ses règles, sauf si une fonction de comparaison personnalisée est spécifiée. ```latte {foreach ($names|sort) as $name} @@ -601,7 +735,7 @@ Filtre qui trie un tableau et maintient l'association des index. {/foreach} ``` -Tableau trié dans l'ordre inverse. +Tableau trié en ordre inverse : ```latte {foreach ($names|sort|reverse) as $name} @@ -609,29 +743,55 @@ Tableau trié dans l'ordre inverse. {/foreach} ``` -Vous pouvez passer votre propre fonction de comparaison en paramètre : .{data-version:2.10.2} +Vous pouvez spécifier une fonction de comparaison personnalisée pour le tri (l'exemple montre comment inverser le tri du plus grand au plus petit) : ```latte -{var $sorted = ($names|sort: fn($a, $b) => $b <=> $a)} +{var $reverted = ($names|sort: fn($a, $b) => $b <=> $a)} ``` +Le filtre `|sort` permet également de trier les éléments par clés : -spaceless .[filter]{data-version:2.10.2} ------------------------------------------ -Supprime les espaces inutiles de la sortie. Vous pouvez également utiliser l'alias `strip`. +```latte +{foreach ($names|sort: byKey: true) as $name} + ... +{/foreach} +``` + +Si vous avez besoin de trier un tableau selon une colonne spécifique, vous pouvez utiliser le paramètre `by`. La valeur `'name'` dans l'exemple indique que le tri se fera selon `$item->name` ou `$item['name']`, selon que `$item` est un tableau ou un objet : + +```latte +{foreach ($items|sort: by: 'name') as $item} + {$item->name} +{/foreach} +``` + +Vous pouvez également définir une fonction de rappel qui déterminera la valeur selon laquelle trier : + +```latte +{foreach ($items|sort: by: fn($items) => $items->category->name) as $item} + {$item->name} +{/foreach} +``` + +Le paramètre `byKey` peut être utilisé de la même manière. + + +spaceless .[filter] +------------------- +Supprime les espaces blancs inutiles de la sortie. Vous pouvez également utiliser l'alias `strip`. ```latte {block |spaceless}
                                                                -
                                                              • Hello
                                                              • +
                                                              • Bonjour
                                                              {/block} ``` -Imprime : +Affiche : ```latte -
                                                              • Hello
                                                              +
                                                              • Bonjour
                                                              ``` @@ -640,74 +800,74 @@ stripHtml .[filter] Convertit le HTML en texte brut. C'est-à-dire qu'il supprime les balises HTML et convertit les entités HTML en texte. ```latte -{='

                                                              one < two

                                                              '|stripHtml} {* sorties 'one < two' *} +{='

                                                              un < deux

                                                              '|stripHtml} {* affiche 'un < deux' *} ``` -Le texte brut résultant peut naturellement contenir des caractères qui représentent des balises HTML, par exemple `'<p>'|stripHtml` est converti en `

                                                              `. N'éditez jamais le texte résultant avec `|noescape`, car cela pourrait entraîner une faille de sécurité. +Le texte brut résultant peut naturellement contenir des caractères qui représentent des balises HTML, par exemple `'<p>'|stripHtml` est converti en `

                                                              `. N'affichez en aucun cas le texte ainsi obtenu avec `|noescape`, car cela peut entraîner une faille de sécurité. -substr(int offset, int length = null) .[filter] ------------------------------------------------ -Extrait une tranche d'une chaîne de caractères. Ce filtre a été remplacé par un filtre de [tranche |#slice]. +substr(int $offset, ?int $length=null) .[filter] +------------------------------------------------ +Extrait une partie d'une chaîne. Ce filtre a été remplacé par le filtre [#slice]. ```latte {$string|substr: 1, 2} ``` -translate(string message, ...args) .[filter]{data-version:3.0} --------------------------------------------------------------- -Il traduit les expressions dans d'autres langues. Pour rendre ce filtre disponible, vous devez [configurer le traducteur |develop#TranslatorExtension]. Vous pouvez également utiliser les [balises pour la traduction |tags#Translation]. +translate(...$args) .[filter] +----------------------------- +Traduit les expressions dans d'autres langues. Pour que le filtre soit disponible, il faut [configurer le traducteur |develop#TranslatorExtension]. Vous pouvez également utiliser les [balises de traduction |tags#Traductions]. ```latte -{='Baskter'|translate} +{='Panier'|translate} {$item|translate} ``` -trim(string charlist = " \t\n\r\0\x0B\u{A0}") .[filter] -------------------------------------------------------- -Supprime les caractères de tête et de queue, par défaut les espaces blancs. +trim(string $charlist=" \t\n\r\0\x0B\u{A0}") .[filter] +------------------------------------------------------ +Supprime les espaces blancs (ou autres caractères) du début et de la fin de la chaîne. ```latte -{=' I like Latte. '|trim} {* sorties 'I like Latte.' *} -{=' I like Latte.'|trim: '.'} {* sorties ' I like Latte' *} +{=' J\'aime Latte. '|trim} {* affiche 'J\'aime Latte.' *} +{=' J\'aime Latte.'|trim: '.'} {* affiche ' J\'aime Latte' *} ``` -truncate(int length, string append = '…') .[filter] +truncate(int $length, string $append='…') .[filter] --------------------------------------------------- -Raccourcit une chaîne de caractères à la longueur maximale donnée mais essaie de préserver les mots entiers. Si la chaîne est tronquée, elle ajoute des points de suspension à la fin (ceci peut être modifié par le second paramètre). +Tronque une chaîne à la longueur maximale spécifiée, en essayant de conserver les mots entiers. Si la chaîne est raccourcie, ajoute des points de suspension à la fin (peut être modifié avec le deuxième paramètre). ```latte -{var $title = 'Hello, how are you?'} -{$title|truncate:5} {* Hell… *} -{$title|truncate:17} {* Hello, how are… *} -{$title|truncate:30} {* Hello, how are you? *} +{var $title = 'Bonjour, comment allez-vous ?'} +{$title|truncate:5} {* Bonj… *} +{$title|truncate:17} {* Bonjour, comment… *} +{$title|truncate:30} {* Bonjour, comment allez-vous ? *} ``` upper .[filter] --------------- -Convertit une valeur en majuscule. Nécessite l'extension PHP `mbstring`. +Convertit une chaîne en majuscules. Nécessite l'extension PHP `mbstring`. ```latte -{='latte'|upper} {* sorties 'LATTE' *} +{='latte'|upper} {* affiche 'LATTE' *} ``` -Voir aussi [capitalize |#capitalize], [firstUpper |#firstUpper], [lower |#lower]. +Voir aussi [#capitalize], [#firstUpper], [#lower]. webalize .[filter] ------------------ -Convertit en ASCII. +Modifie une chaîne UTF‑8 dans la forme utilisée dans les URL. -Convertit les espaces en traits d'union. Supprime les caractères qui ne sont pas des caractères alphanumériques, des traits de soulignement ou des traits d'union. Convertit en minuscules. Supprime également les espaces avant et arrière. +Convertit en ASCII. Convertit les espaces en tirets. Supprime les caractères qui ne sont pas alphanumériques, des traits de soulignement ou des tirets. Convertit en minuscules. Supprime également les espaces de début et de fin. ```latte -{var $s = 'Our 10. product'} -{$s|webalize} {* sorties 'our-10-product' *} +{var $s = 'Notre 10ème produit'} +{$s|webalize} {* affiche 'notre-10eme-produit' *} ``` .[caution] -Nécessite le paquet [nette/utils |utils:]. +Nécessite la bibliothèque [nette/utils|utils:]. diff --git a/latte/fr/functions.texy b/latte/fr/functions.texy index a1f287455e..955f9dcd0a 100644 --- a/latte/fr/functions.texy +++ b/latte/fr/functions.texy @@ -1,23 +1,25 @@ -Fonctions de Latte -****************** +Fonctions Latte +*************** .[perex] -Outre les fonctions PHP courantes, vous pouvez également les utiliser dans les modèles. +Dans les templates, en plus des fonctions PHP courantes, nous pouvons utiliser ces fonctions supplémentaires. .[table-latte-filters] -| `clamp` | [fixe la valeur à l'intervalle |#clamp] +| `clamp` | [limite la valeur à une plage donnée |#clamp] | `divisibleBy`| [vérifie si une variable est divisible par un nombre |#divisibleBy] -| `even` | [vérifie si le nombre donné est pair |#even] -| `first` | [renvoie le premier élément d'un tableau ou un caractère d'une chaîne de caractères |#first] -| `last` | [renvoie le dernier élément d'un tableau ou un caractère d'une chaîne de caractères |#last] -| `odd` | [vérifie si le nombre donné est impair |#odd] -| `slice` | [extrait une tranche d'un tableau ou d'une chaîne de caractères |#slice] +| `even` | [vérifie si un nombre donné est pair |#even] +| `first` | [retourne le premier élément d'un tableau ou caractère d'une chaîne |#first] +| `group` | [regroupe les données selon différents critères |#group] +| `hasBlock` | [vérifie l'existence d'un bloc |#hasBlock] +| `last` | [retourne le dernier élément d'un tableau ou caractère d'une chaîne |#last] +| `odd` | [vérifie si un nombre donné est impair |#odd] +| `slice` | [extrait une partie d'un tableau ou d'une chaîne |#slice] -Utilisation .[#toc-usage] -========================= +Utilisation +=========== -Les fonctions sont utilisées de la même manière que les fonctions PHP courantes et peuvent être utilisées dans toutes les expressions : +Les fonctions s'utilisent de la même manière que les fonctions PHP courantes et peuvent être utilisées dans toutes les expressions : ```latte

                                                              {clamp($num, 1, 100)}

                                                              @@ -25,14 +27,14 @@ Les fonctions sont utilisées de la même manière que les fonctions PHP courant {if odd($num)} ... {/if} ``` -Les [fonctions personnalisées |extending-latte#functions] peuvent être enregistrées de cette façon : +Les [fonctions personnalisées|custom-functions] peuvent être enregistrées de cette manière : ```php $latte = new Latte\Engine; $latte->addFunction('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -Nous l'utilisons dans un modèle comme celui-ci : +Dans le template, on l'appelle ensuite comme ceci : ```latte

                                                              {shortify($text)}

                                                              @@ -40,23 +42,23 @@ Nous l'utilisons dans un modèle comme celui-ci : ``` -Fonctions .[#toc-functions] -=========================== +Fonctions +========= -clamp(int|float $value, int|float $min, int|float $max): int|float .[method]{data-version:2.9} ----------------------------------------------------------------------------------------------- -Renvoie une valeur limitée à l'intervalle inclusif de min et max. +clamp(int|float $value, int|float $min, int|float $max): int|float .[method] +---------------------------------------------------------------------------- +Limite la valeur à la plage inclusive donnée min et max. ```latte {=clamp($level, 0, 255)} ``` -Voir également le [filtre clamp |filters#clamp]: +Voir aussi [le filtre clamp |filters#clamp]. -divisibleBy(int $value, int $by): bool .[method]{data-version:2.10.2} ---------------------------------------------------------------------- +divisibleBy(int $value, int $by): bool .[method] +------------------------------------------------ Vérifie si une variable est divisible par un nombre. ```latte @@ -64,61 +66,91 @@ Vérifie si une variable est divisible par un nombre. ``` -even(int $value): bool .[method]{data-version:2.10.2} ------------------------------------------------------ -Vérifie si le nombre donné est pair. +even(int $value): bool .[method] +-------------------------------- +Vérifie si un nombre donné est pair. ```latte {if even($num)} ... {/if} ``` -first(string|array $value): mixed .[method]{data-version:2.10.2} ----------------------------------------------------------------- -Retourne le premier élément d'un tableau ou le premier caractère d'une chaîne de caractères : +first(string|iterable $value): mixed .[method] +---------------------------------------------- +Retourne le premier élément d'un tableau ou caractère d'une chaîne : ```latte -{=first([1, 2, 3, 4])} {* sorties 1 *} -{=first('abcd')} {* sorties 'a' *} +{=first([1, 2, 3, 4])} {* affiche 1 *} +{=first('abcd')} {* affiche 'a' *} ``` -Voir aussi [last |#last], [filter first |filters#first]. +Voir aussi [#last], [le filtre first |filters#first]. -last(string|array $value): mixed .[method]{data-version:2.10.2} ---------------------------------------------------------------- -Renvoie le dernier élément du tableau ou le dernier caractère de la chaîne : +group(iterable $data, string|int|\Closure $by): array .[method]{data-version:3.0.16} +------------------------------------------------------------------------------------ +La fonction regroupe les données selon différents critères. + +Dans cet exemple, les lignes du tableau sont regroupées par la colonne `categoryId`. La sortie est un tableau de tableaux où la clé est la valeur de la colonne `categoryId`. [Lisez le tutoriel détaillé|cookbook/grouping]. + +```latte +{foreach group($items, categoryId) as $categoryId => $categoryItems} +
                                                                + {foreach $categoryItems as $item} +
                                                              • {$item->name}
                                                              • + {/foreach} +
                                                              +{/foreach} +``` + +Voir aussi le filtre [group |filters#group]. + + +hasBlock(string $name): bool .[method]{data-version:3.0.10} +----------------------------------------------------------- +Vérifie si le bloc du nom spécifié existe : + +```latte +{if hasBlock(header)} ... {/if} +``` + +Voir aussi [la vérification de l'existence des blocs |template-inheritance#Vérification de l existence des blocs]. + + +last(string|array $value): mixed .[method] +------------------------------------------ +Retourne le dernier élément d'un tableau ou caractère d'une chaîne : ```latte -{=last([1, 2, 3, 4])} {* sorties 4 *} -{=last('abcd')} {* sorties 'd' *} +{=last([1, 2, 3, 4])} {* affiche 4 *} +{=last('abcd')} {* affiche 'd' *} ``` -Voir aussi [first |#first], [filter last |filters#last]. +Voir aussi [#first], [le filtre last |filters#last]. -odd(int $value): bool .[method]{data-version:2.10.2} ----------------------------------------------------- -Vérifie si le nombre donné est impair. +odd(int $value): bool .[method] +------------------------------- +Vérifie si un nombre donné est impair. ```latte {if odd($num)} ... {/if} ``` -slice(string|array $value, int $start, int $length=null, bool $preserveKeys=false): string|array .[method]{data-version:2.10.2} -------------------------------------------------------------------------------------------------------------------------------- -Extrait une tranche d'un tableau ou d'une chaîne de caractères. +slice(string|array $value, int $start, ?int $length=null, bool $preserveKeys=false): string|array .[method] +----------------------------------------------------------------------------------------------------------- +Extrait une partie d'un tableau ou d'une chaîne. ```latte -{=slice('hello', 1, 2)} {* sorties 'el' *} -{=slice(['a', 'b', 'c'], 1, 2)} {* sorties ['b', 'c'] *} +{=slice('bonjour', 1, 2)} {* affiche 'on' *} +{=slice(['a', 'b', 'c'], 1, 2)} {* affiche ['b', 'c'] *} ``` -Le filtre de tranche fonctionne comme la fonction PHP `array_slice` pour les tableaux et `mb_substr` pour les chaînes de caractères, avec un retour à `iconv_substr` en mode UTF-8. +Le filtre fonctionne comme la fonction PHP `array_slice` pour les tableaux ou `mb_substr` pour les chaînes avec un fallback sur la fonction `iconv_substr` en mode UTF‑8. -Si start est non négatif, la séquence commencera à ce point de départ dans la variable. Si start est négatif, la séquence commencera à cette distance de la fin de la variable. +Si start est positif, la séquence commencera décalée de ce nombre depuis le début du tableau/chaîne. Si il est négatif, la séquence commencera décalée d'autant depuis la fin. -Si length est donné et est positif, alors la séquence aura jusqu'à ce nombre d'éléments. Si la variable est plus courte que la longueur, seuls les éléments disponibles de la variable seront présents. Si la longueur est donnée et qu'elle est négative, la séquence s'arrêtera à ce nombre d'éléments à partir de la fin de la variable. Si elle est omise, alors la séquence contiendra tous les éléments depuis le décalage jusqu'à la fin de la variable. +Si le paramètre length est spécifié et est positif, la séquence contiendra autant d'éléments. Si un paramètre length négatif est passé à cette fonction, la séquence contiendra tous les éléments du tableau d'origine, commençant à la position start et se terminant à la position inférieure de length éléments par rapport à la fin du tableau. Si vous ne spécifiez pas ce paramètre, la séquence contiendra tous les éléments du tableau d'origine, commençant à la position start. -Filter réordonnera et réinitialisera les clés du tableau d'entiers par défaut. Ce comportement peut être modifié en définissant preserveKeys à true. Les clés des chaînes de caractères sont toujours préservées, quel que soit ce paramètre. +Par défaut, le filtre modifie l'ordre et réinitialise les clés entières du tableau. Ce comportement peut être modifié en définissant preserveKeys sur true. Les clés de chaîne sont toujours préservées, quel que soit ce paramètre. diff --git a/latte/fr/guide.texy b/latte/fr/guide.texy index 2b6d8ba4d9..852fbda641 100644 --- a/latte/fr/guide.texy +++ b/latte/fr/guide.texy @@ -1,46 +1,45 @@ -Démarrer avec Latte -******************* +Commencer avec Latte +********************
                                                              -Les modèles améliorent l'organisation du code, séparent la logique de l'application de la présentation et renforcent la sécurité. Ils offrent de bien meilleures fonctionnalités et capacités d'expression pour générer du HTML que PHP lui-même. +Les templates améliorent l'organisation du code, séparent la logique de l'application de la présentation et augmentent la sécurité. Ils offrent des fonctionnalités et des moyens d'expression bien meilleurs pour générer du HTML que PHP seul. -Latte est le système de templates le plus sûr pour PHP. Vous apprécierez sa syntaxe intuitive. Un large éventail de fonctionnalités utiles simplifiera considérablement votre travail. -Il offre une protection de premier ordre contre les [vulnérabilités critiques |safety-first] et vous permet de vous concentrer sur la création d'applications de haute qualité sans vous soucier de leur sécurité. +Latte est le système de templates le plus sécurisé pour PHP. Vous allez adorer sa syntaxe intuitive. Une large gamme de fonctionnalités utiles vous facilitera grandement le travail. Il offre une protection de pointe contre les [vulnérabilités critiques|safety-first] et vous permet de vous concentrer sur la création d'applications de qualité sans vous soucier de leur sécurité. -Comment écrire des modèles avec Latte ? .[#toc-how-to-write-templates-using-latte] ----------------------------------------------------------------------------------- +Comment écrire des templates avec Latte ? +----------------------------------------- -Latte est intelligemment conçu et facile à apprendre pour ceux qui sont familiers avec PHP, car ils peuvent rapidement adopter ses balises de base. +Latte est intelligemment conçu et facile à apprendre pour ceux qui connaissent PHP et maîtrisent les balises de base. -- Tout d'abord, familiarisez-vous avec la [syntaxe de Latte |syntax] et [essayez-la en ligne |https://fiddle.nette.org/latte/] -- Jetez un coup d'œil à l'ensemble des [balises |tags] et des [filtres de |filters]base -- Écrire des modèles dans l'[éditeur avec le support de Latte |recipes#Editors and IDE] +- Familiarisez-vous d'abord avec la [syntaxe Latte|syntax] et [ESSAYEZ-LE EN LIGNE |https://fiddle.nette.org/latte/#9cc0cf6d89#9cc0cf6d89] +- Découvrez l'ensemble de base des [balises|tags] et [filtres|filters] +- Écrivez des templates dans un [éditeur avec support Latte |recipes#Éditeurs et IDE] -Comment utiliser Latte en PHP ? .[#toc-how-to-use-latte-in-php] ---------------------------------------------------------------- +Comment utiliser Latte en PHP ? +------------------------------- -Il est facile d'intégrer Latte dans votre nouvelle application en quelques minutes : +Déployer Latte dans votre nouvelle application est une question de quelques minutes : -- Tout d'abord, [installez et exécutez Latte |develop#Installation] -- Chouchoutez vous avec l'[outil de débogage Tracy |develop#Debugging and Tracy] -- Étendez les fonctionnalités de Latte avec des [fonctionnalités personnalisées |extending-latte] +- D'abord, [installez et lancez Latte |develop#Installation] +- Laissez-vous choyer par l'[outil de débogage Tracy |develop#Débogage et Tracy] +- Étendez Latte avec des [fonctionnalités personnalisées |extending-latte] -Si vous convertissez un ancien projet écrit en PHP vers Latte, l'[outil de conversion de code PHP vers Latte |cookbook/migration-from-php] vous facilitera la migration. Ou prévoyez-vous de passer de Twig à Latte ? Nous avons un [convertisseur de modèle Twig vers Latte |cookbook/migration-from-twig] pour vous. +Si vous convertissez un ancien projet écrit en PHP pur vers Latte, la migration sera facilitée par l'[outil de conversion de code PHP vers Latte |cookbook/migration-from-php]. Ou prévoyez-vous de passer de Twig à Latte ? Nous avons pour vous un [convertisseur de templates Twig vers Latte |cookbook/migration-from-twig]. -Que peut faire d'autre Latte ? .[#toc-what-else-can-latte-do] -------------------------------------------------------------- +Que peut faire d'autre Latte ? +------------------------------ -Latte est livré avec tous les éléments nécessaires pour vous faciliter la vie. +Latte est livré entièrement équipé, avec tout l'essentiel inclus. -- Augmentez votre productivité grâce aux [mécanismes d'héritage |template-inheritance] qui permettent de réutiliser les éléments et les structures répétées. -- Le bunker blindé [Sandbox] protège les modèles des sources non fiables, telles que celles modifiées par les utilisateurs. -- Pour plus d'inspiration, consultez les [conseils et astuces |recipes]. +- Votre productivité sera boostée par les [mécanismes d'héritage |template-inheritance] grâce auxquels les éléments et structures répétitifs sont réutilisés +- Le bunker blindé [sandbox |sandbox] isole les templates provenant de sources non fiables, que les utilisateurs eux-mêmes éditent par exemple +- Pour plus d'inspiration, voici des [trucs et astuces |recipes]
                                                              -{{description: Latte est le système de templating le plus sûr pour PHP. Il permet d'éviter de nombreuses failles de sécurité. Vous apprécierez sa syntaxe intuitive et vous apprécierez un grand nombre d'ajustements utiles.}} +{{description: Latte est le système de templates le plus sécurisé pour PHP. Il prévient de nombreuses vulnérabilités de sécurité. Vous apprécierez sa syntaxe intuitive et ses nombreuses fonctionnalités utiles.}} diff --git a/latte/fr/loaders.texy b/latte/fr/loaders.texy new file mode 100644 index 0000000000..bb0efe5f84 --- /dev/null +++ b/latte/fr/loaders.texy @@ -0,0 +1,198 @@ +Chargeurs (Loaders) +******************* + +.[perex] +Les chargeurs (Loaders) sont le mécanisme que Latte utilise pour obtenir le code source de vos templates. Le plus souvent, les templates sont stockés sous forme de fichiers sur le disque, mais grâce au système flexible de chargeurs, vous pouvez les charger depuis pratiquement n'importe où ou même les générer dynamiquement. + + +Qu'est-ce qu'un chargeur ? +========================== + +Lorsque vous travaillez avec des templates, vous imaginez généralement des fichiers `.latte` situés dans la structure de répertoires de votre projet. C'est ce que gère le [#FileLoader] par défaut de Latte. Cependant, le lien entre le nom d'un template (comme `'main.latte'` ou `'components/card.latte'`) et son code source réel *n'a pas* besoin d'être un mappage direct vers un chemin de fichier. + +C'est là que les chargeurs entrent en jeu. Un chargeur est un objet chargé de prendre un nom de template (une chaîne d'identification) et de fournir à Latte son code source. Latte s'appuie entièrement sur le chargeur configuré pour cette tâche. Cela s'applique non seulement au template initial demandé via `$latte->render('main.latte')`, mais aussi à **chaque template référencé à l'intérieur** à l'aide de balises comme `{include ...}`, `{layout ...}`, `{embed ...}` ou `{import ...}`. + +Pourquoi utiliser un chargeur personnalisé ? + +- **Chargement depuis des sources alternatives :** Récupération de templates stockés dans une base de données, dans un cache (comme Redis ou Memcached), dans un système de gestion de versions (comme Git, basé sur un commit spécifique) ou générés dynamiquement. +- **Implémentation de conventions de nommage personnalisées :** Vous pourriez vouloir utiliser des alias plus courts pour les templates ou implémenter une logique de chemin de recherche spécifique (par exemple, chercher d'abord dans le répertoire du thème, puis revenir au répertoire par défaut). +- **Ajout de sécurité ou de contrôle d'accès :** Un chargeur personnalisé peut vérifier les permissions utilisateur avant de charger certains templates. +- **Prétraitement :** Bien que généralement déconseillé ([les passes de compilation |compiler-passes] sont meilleures), un chargeur *pourrait* théoriquement prétraiter le contenu du template avant de le transmettre à Latte. + +Vous définissez le chargeur pour une instance de `Latte\Engine` à l'aide de la méthode `setLoader()` : + +```php +$latte = new Latte\Engine; + +// Utilisation du FileLoader par défaut pour les fichiers dans '/path/to/templates' +$loader = new Latte\Loaders\FileLoader('/path/to/templates'); +$latte->setLoader($loader); +``` + +Le chargeur doit implémenter l'interface `Latte\Loader`. + + +Chargeurs intégrés +================== + +Latte propose plusieurs chargeurs standard : + + +FileLoader +---------- + +C'est le **chargeur par défaut** utilisé par la classe `Latte\Engine` si aucun autre n'est spécifié. Il charge les templates directement depuis le système de fichiers. + +Vous pouvez éventuellement définir un répertoire racine pour restreindre l'accès : + +```php +use Latte\Loaders\FileLoader; + +// Ce qui suit permettra de charger des templates uniquement depuis le répertoire /var/www/html/templates +$loader = new FileLoader('/var/www/html/templates'); +$latte->setLoader($loader); + +// $latte->render('../../../etc/passwd'); // Ceci lèverait une exception + +// Rendu d'un template situé dans /var/www/html/templates/pages/contact.latte +$latte->render('pages/contact.latte'); +``` + +Lors de l'utilisation de balises comme `{include}` ou `{layout}`, il résout les noms de templates relativement au template actuel, sauf si un chemin absolu est fourni. + + +StringLoader +------------ + +Ce chargeur récupère le contenu du template à partir d'un tableau associatif, où les clés sont les noms des templates (identifiants) et les valeurs sont les chaînes de code source du template. Il est particulièrement utile pour les tests ou les petites applications où les templates peuvent être stockés directement dans le code PHP. + +```php +use Latte\Loaders\StringLoader; + +$loader = new StringLoader([ + 'main.latte' => 'Bonjour {$name}, l\'inclusion est ci-dessous:{include helper.latte}', + 'helper.latte' => '{var $x = 10}Contenu inclus : {$x}', + // Ajoutez d'autres templates selon les besoins +]); + +$latte->setLoader($loader); + +$latte->render('main.latte', ['name' => 'Monde']); +// Sortie : Bonjour Monde, l'inclusion est ci-dessous:Contenu inclus : 10 +``` + +Si vous avez besoin de rendre un seul template directement à partir d'une chaîne sans avoir besoin d'inclusions ou d'héritage faisant référence à d'autres templates de chaîne nommés, vous pouvez passer la chaîne directement à la méthode `render()` ou `renderToString()` en utilisant `StringLoader` sans tableau : + +```php +$loader = new StringLoader; +$latte->setLoader($loader); + +$templateString = 'Bonjour {$name}!'; +$output = $latte->renderToString($templateString, ['name' => 'Alice']); +// $output contient 'Bonjour Alice!' +``` + + +Création d'un chargeur personnalisé +=================================== + +Pour créer un chargeur personnalisé (par exemple, pour charger des templates depuis une base de données, un cache, un système de gestion de versions ou une autre source), vous devez créer une classe qui implémente l'interface [api:Latte\Loader]. + +Voyons ce que chaque méthode doit faire. + + +getContent(string $name): string .[method] +------------------------------------------ +C'est la méthode principale du chargeur. Sa tâche est de récupérer et de retourner le code source complet du template identifié par `$name` (tel que passé à la méthode `$latte->render()` ou retourné par la méthode [#getReferredName()]). + +Si le template ne peut pas être trouvé ou accédé, cette méthode **doit lever une exception `Latte\RuntimeException`**. + +```php +public function getContent(string $name): string +{ + // Exemple : Chargement depuis un stockage interne hypothétique + $content = $this->storage->read($name); + if ($content === null) { + throw new Latte\RuntimeException("Le template '$name' ne peut pas être chargé."); + } + return $content; +} +``` + + +getReferredName(string $name, string $referringName): string .[method] +---------------------------------------------------------------------- +Cette méthode gère la résolution des noms de templates utilisés dans des balises comme `{include}`, `{layout}`, etc. Lorsque Latte rencontre par exemple `{include 'partial.latte'}` à l'intérieur de `main.latte`, il appelle cette méthode avec `$name = 'partial.latte'` et `$referringName = 'main.latte'`. + +La tâche de la méthode est de résoudre `$name` en un identifiant canonique (par exemple, un chemin absolu, une clé de base de données unique) qui sera utilisé lors de l'appel d'autres méthodes du chargeur, en fonction du contexte fourni dans `$referringName`. + +```php +public function getReferredName(string $name, string $referringName): string +{ + return ...; +} +``` + + +getUniqueId(string $name): string .[method] +------------------------------------------- +Latte utilise un cache de templates compilés pour améliorer les performances. Chaque fichier de template compilé a besoin d'un nom unique dérivé de l'identifiant du template source. Cette méthode fournit une chaîne qui **identifie de manière unique** le template `$name`. + +Pour les templates basés sur des fichiers, le chemin absolu peut suffire. Pour les templates en base de données, une combinaison d'un préfixe et de l'ID de la base de données est courante. + +```php +public function getUniqueId(string $name): string +{ + return ...; +} +``` + + +Exemple : Un chargeur de base de données simple +----------------------------------------------- + +Cet exemple montre la structure de base d'un chargeur qui charge des templates stockés dans une table de base de données nommée `templates` avec les colonnes `name` (identifiant unique), `content` et `updated_at`. + +```php +use Latte; + +class DatabaseLoader implements Latte\Loader +{ + public function __construct( + private \PDO $db, + ) { + } + + public function getContent(string $name): string + { + $stmt = $this->db->prepare('SELECT content FROM templates WHERE name = ?'); + $stmt->execute([$name]); + $content = $stmt->fetchColumn(); + if ($content === false) { + throw new Latte\RuntimeException("Template '$name' non trouvé dans la base de données."); + } + return $content; + } + + // Cet exemple simple suppose que les noms de templates ('homepage', 'article', etc.) + // sont des ID uniques et que les templates ne se référencent pas relativement. + public function getReferredName(string $name, string $referringName): string + { + return $name; + } + + public function getUniqueId(string $name): string + { + // Utiliser un préfixe et le nom lui-même est unique et suffisant ici + return 'db_' . $name; + } +} + +// Utilisation : +$pdo = new \PDO(/* détails de connexion */); +$loader = new DatabaseLoader($pdo); +$latte->setLoader($loader); +$latte->render('homepage'); // Charge le template nommé 'homepage' depuis la BDD +``` + +Les chargeurs personnalisés vous donnent un contrôle total sur l'origine de vos templates Latte, permettant l'intégration avec divers systèmes de stockage et flux de travail. diff --git a/latte/fr/recipes.texy b/latte/fr/recipes.texy index 0fafcd16d6..165b31b214 100644 --- a/latte/fr/recipes.texy +++ b/latte/fr/recipes.texy @@ -1,45 +1,45 @@ -Conseils et astuces -******************* +Trucs et astuces +**************** -Éditeurs et IDE .[#toc-editors-and-ide] -======================================= +Éditeurs et IDE +=============== -Écrivez des modèles dans un éditeur ou un IDE qui prend en charge Latte. Ce sera beaucoup plus agréable. +Écrivez des templates dans un éditeur ou IDE qui prend en charge Latte. Ce sera beaucoup plus agréable. -- L'EDI NetBeans a un support intégré -- PhpStorm : installez le [plugin Latte |https://plugins.jetbrains.com/plugin/7457-latte] en `Settings > Plugins > Marketplace` -- VS Code : cherchez dans markerplace le plugin "Nette Latte + Neon". -- Sublime Text 3 : dans Package Control, trouvez et installez le paquet `Nette` et sélectionnez Latte dans le menu déroulant. `View > Syntax` -- Dans les anciens éditeurs, utilisez la mise en évidence Smarty pour les fichiers .latte. +- PhpStorm : installez le [plugin Latte|https://plugins.jetbrains.com/plugin/7457-latte] dans `Settings > Plugins > Marketplace` +- VS Code : installez [Nette Latte + Neon|https://marketplace.visualstudio.com/items?itemName=Kasik96.latte], [Nette Latte templates|https://marketplace.visualstudio.com/items?itemName=smuuf.latte-lang] ou le dernier plugin [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode] +- NetBeans IDE : le support natif de Latte fait partie de l'installation +- Sublime Text 3 : dans Package Control, trouvez et installez le paquet `Nette` et choisissez Latte dans `View > Syntax` +- dans les anciens éditeurs, utilisez la coloration syntaxique Smarty pour les fichiers .latte -Le plugin pour PhpStorm est très avancé et peut parfaitement suggérer du code PHP. Pour un fonctionnement optimal, utilisez des [templates typés |type-system]. +Le plugin pour PhpStorm est très avancé et offre une excellente assistance pour le code PHP. Pour qu'il fonctionne de manière optimale, utilisez des [templates typés|type-system]. [* latte-phpstorm-plugin.webp *] -Le support de Latte peut également être trouvé dans le surligneur de code web [Prism.js |https://prismjs.com/#supported-languages] et l'éditeur [Ace |https://ace.c9.io]. +Le support de Latte se trouve également dans le colorateur de code web [Prism.js|https://prismjs.com/#supported-languages] et l'éditeur [Ace|https://ace.c9.io]. -Latte dans JavaScript ou CSS .[#toc-latte-inside-javascript-or-css] -=================================================================== +Latte à l'intérieur de JavaScript ou CSS +======================================== -Latte peut être utilisé très confortablement dans JavaScript ou CSS. Mais comment éviter que Latte ne considère par erreur le code JavaScript ou le style CSS comme une balise Latte ? +Latte peut être utilisé très confortablement à l'intérieur de JavaScript ou CSS. Mais comment éviter que Latte considère à tort du code JavaScript ou un style CSS comme une balise Latte ? ```latte ``` **Option 1** -Évitez les situations où une lettre suit immédiatement un `{`, soit en insérant un espace, un saut de ligne ou un guillemet entre les deux : +Évitez la situation où une lettre suit immédiatement `{`, par exemple en insérant un espace, un saut de ligne ou un guillemet avant : ```latte -

                                                              +

                                                              ``` -Deux façons et deux types différents d'échapper les données. Dans les éléments ` ``` -Cependant, si nous voulons l'insérer dans un attribut HTML, nous devons encore échapper les guillemets aux entités HTML : +Cependant, si nous voulions l'insérer dans un attribut HTML, nous devrions encore échapper les guillemets en entités HTML : ```html
                                                              ``` -Cependant, le contexte imbriqué ne doit pas être uniquement JS ou CSS. Il peut également s'agir d'une URL. Les paramètres dans les URL sont échappés en convertissant les caractères spéciaux en séquences commençant par `%`. Exemple : +Mais le contexte imbriqué ne doit pas nécessairement être uniquement JS ou CSS. Il s'agit couramment aussi d'une URL. Les paramètres dans une URL sont échappés en convertissant les caractères ayant une signification spéciale en séquences commençant par `%`. Exemple : ``` https://example.org/?a=Jazz&b=Rock%27n%27Roll ``` -Et lorsque nous sortons cette chaîne dans un attribut, nous appliquons toujours l'échappement en fonction de ce contexte et remplaçons `&` with `&`: +Et lorsque nous affichons cette chaîne dans un attribut, nous appliquons encore l'échappement selon ce contexte et remplaçons `&` par `&`: ```html ``` -Si vous avez lu jusqu'ici, félicitations, c'était épuisant. Vous avez maintenant une bonne idée de ce que sont les contextes et l'échappement. Et vous n'avez pas à vous inquiéter de savoir si c'est compliqué. Latte le fait automatiquement pour vous. +Si vous avez lu jusqu'ici, félicitations, c'était épuisant. Vous avez maintenant une bonne idée de ce que sont les contextes et l'échappement. Et ne vous inquiétez pas, ce n'est pas compliqué. Latte fait tout cela automatiquement pour vous. -Latte contre les systèmes naïfs .[#toc-latte-vs-naive-systems] -============================================================== +Latte vs systèmes naïfs +======================= -Nous avons montré comment utiliser correctement l'échappement dans un document HTML et combien il est crucial de connaître le contexte, c'est-à-dire l'endroit où vous produisez les données. En d'autres termes, comment fonctionne l'échappement sensible au contexte. -Bien qu'il s'agisse d'un prérequis pour une défense XSS fonctionnelle, **Latte est le seul système de templating pour PHP qui le fait**. +Nous avons montré comment échapper correctement dans un document HTML et à quel point la connaissance du contexte, c'est-à-dire l'endroit où nous affichons les données, est essentielle. En d'autres termes, comment fonctionne l'échappement contextuel. Bien qu'il s'agisse d'une condition préalable nécessaire à une défense fonctionnelle contre XSS, **Latte est le seul système de templates pour PHP qui sait le faire.** -Comment cela est-il possible alors que tous les systèmes actuels prétendent avoir un échappement automatique ? -L'échappement automatique sans connaître le contexte est une connerie qui **crée un faux sentiment de sécurité**. +Comment est-ce possible, alors que tous les systèmes prétendent aujourd'hui avoir un échappement automatique ? L'échappement automatique sans connaissance du contexte est un peu une bêtise, qui **crée une fausse impression de sécurité**. -Les systèmes de modélisation comme Twig, Laravel Blade et d'autres ne voient pas de structure HTML dans le modèle. Par conséquent, ils ne voient pas non plus les contextes. Comparés à Latte, ils sont aveugles et naïfs. Ils ne gèrent que leur propre balisage, tout le reste n'est qu'un flux de caractères non pertinent pour eux : +Les systèmes de templates, tels que Twig, Laravel Blade et autres, ne voient aucune structure HTML dans le template. Ils ne voient donc pas non plus les contextes. Par rapport à Latte, ils sont aveugles et naïfs. Ils ne traitent que leurs propres balises, tout le reste est pour eux un flux de caractères sans importance :
                                                              -```twig .{file:Twig template as seen by Twig himself} -░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░ -░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░{{ text }}░░░░ +```twig .{file:Template Twig, tel que Twig le voit} +░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░ +░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░ ``` -```twig .{file:Twig template as the designer sees it} -- in text: {{ text }} -- in tag: -- in attribute: -- in unquoted attribute: -- in attribute containing URL: -- in attribute containing JavaScript: -- in attribute containing CSS: -- in JavaScriptu: -- in CSS: -- in comment: +```twig .{file:Template Twig, tel que le designer le voit} +- dans le texte : {{ foo }} +- dans la balise : +- dans l'attribut : +- dans l'attribut sans guillemets : +- dans l'attribut contenant une URL : +- dans l'attribut contenant du JavaScript : +- dans l'attribut contenant du CSS : +- en JavaScript : +- en CSS : +- dans le commentaire : ```
                                                              -Les systèmes naïfs se contentent de convertir mécaniquement les caractères `< > & ' "` en entités HTML, ce qui est une manière valide d'échapper à la fraude dans la plupart des cas, mais loin d'être systématique. Ils ne peuvent donc pas détecter ou prévenir diverses failles de sécurité, comme nous allons le montrer ci-dessous. +Les systèmes naïfs ne font que convertir mécaniquement les caractères `< > & ' "` en entités HTML, ce qui est certes une méthode d'échappement valide dans la plupart des cas d'utilisation, mais loin d'être toujours le cas. Ils ne peuvent donc ni détecter ni prévenir la création de diverses failles de sécurité, comme nous le montrerons plus loin. -Latte voit le modèle de la même manière que vous. Il comprend le HTML, le XML, reconnaît les balises, les attributs, etc. Et de ce fait, il distingue les contextes et traite les données en conséquence. Il offre donc une protection vraiment efficace contre la vulnérabilité critique Cross-site Scripting. +Latte voit le template de la même manière que vous. Il comprend HTML, XML, reconnaît les balises, les attributs, etc. Et grâce à cela, il distingue les différents contextes et traite les données en conséquence. Il offre ainsi une protection vraiment efficace contre la vulnérabilité critique Cross-site Scripting. + +
                                                              + +```latte .{file:Template Latte, tel que Latte le voit} +░░░░░░░░░░░{$foo} +░░░░░░░░░░ +░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░ +░░░░░░░░░ +░░░░░░░░░░░░░░░ +``` + +```latte .{file:Template Latte, tel que le designer le voit} +- dans le texte : {$foo} +- dans la balise : +- dans l'attribut : +- dans l'attribut sans guillemets : +- dans l'attribut contenant une URL : +- dans l'attribut contenant du JavaScript : +- dans l'attribut contenant du CSS : +- en JavaScript : +- en CSS : +- dans le commentaire : +``` + +
                                                              -Démonstration en direct .[#toc-live-demonstration] -================================================== +Démonstration en direct +======================= -À gauche, vous pouvez voir le modèle en Latte, à droite, le code HTML généré. La variable `$text` est éditée plusieurs fois, chaque fois dans un contexte légèrement différent. Et donc échappée un peu différemment. Vous pouvez modifier vous-même le code du modèle, par exemple changer le contenu de la variable, etc. Essayez-le : +À gauche, vous voyez le template en Latte, à droite le code HTML généré. La variable `$text` est affichée plusieurs fois, et à chaque fois dans un contexte légèrement différent. Et donc aussi échappée légèrement différemment. Vous pouvez éditer vous-même le code du template, par exemple changer le contenu de la variable, etc. Essayez :
                                                              ``` .{file:template.latte; min-height: 14em}[fiddle-source] -{* TRY TO EDIT THIS TEMPLATE *} +{* ESSAYEZ DE MODIFIER CE TEMPLATE *} {var $text = "Rock'n'Roll"} - {$text} - @@ -270,63 +286,61 @@ Démonstration en direct .[#toc-live-demonstration]
                                                              -C'est pas génial ? Latte fait automatiquement l'échappement sensible au contexte, donc le programmeur : +N'est-ce pas génial ! Latte effectue l'échappement contextuel automatiquement, de sorte que le programmeur : -- n'a pas à penser ou à savoir comment échapper les données +- n'a pas besoin de réfléchir ni de savoir comment échapper où - ne peut pas se tromper -- ne peut pas l'oublier +- ne peut pas oublier d'échapper -Ce ne sont même pas tous les contextes que Latte distingue lors de la sortie et pour lesquels il personnalise le traitement des données. Nous allons maintenant passer en revue des cas plus intéressants. +Ce ne sont même pas tous les contextes que Latte distingue lors de l'affichage et pour lesquels il adapte le traitement des données. Nous allons maintenant passer en revue d'autres cas intéressants. -Comment pirater les systèmes naïfs .[#toc-how-to-hack-naive-systems] -==================================================================== +Comment pirater les systèmes naïfs +================================== -Nous allons utiliser quelques exemples pratiques pour montrer à quel point la différenciation du contexte est importante et pourquoi les systèmes de templating naïfs ne fournissent pas une protection suffisante contre XSS, contrairement à Latte. -Nous utiliserons Twig comme représentant d'un système naïf dans les exemples, mais la même chose s'applique aux autres systèmes. +Sur plusieurs exemples pratiques, nous allons montrer à quel point la distinction des contextes est importante et pourquoi les systèmes de templates naïfs n'offrent pas une protection suffisante contre XSS, contrairement à Latte. Comme représentant d'un système naïf, nous utiliserons Twig dans les exemples, mais la même chose s'applique aux autres systèmes. -Vulnérabilité des attributs .[#toc-attribute-vulnerability] ------------------------------------------------------------ +Vulnérabilité par attribut +-------------------------- -Essayons d'injecter du code malveillant dans la page en utilisant l'attribut HTML comme nous l'avons [montré ci-dessus |#How does the vulnerability arise]. Nous avons un modèle dans Twig qui affiche une image : +Nous allons essayer d'injecter du code malveillant dans la page à l'aide d'un attribut HTML, comme nous l'avons [montré ci-dessus |#Comment naît la vulnérabilité]. Ayons un template en Twig affichant une image : ```twig .{file:Twig} {{ ``` -Notez qu'il n'y a pas de guillemets autour des valeurs de l'attribut. Le codeur les a peut-être oubliés, ce qui arrive tout simplement. Par exemple, dans React, le code est écrit comme ceci, sans guillemets, et un codeur qui change de langage peut facilement oublier les guillemets. +Notez qu'il n'y a pas de guillemets autour des valeurs des attributs. Le codeur a pu les oublier, ce qui arrive tout simplement. Par exemple, en React, le code s'écrit ainsi, sans guillemets, et un codeur qui alterne les langages peut alors facilement oublier les guillemets. -L'attaquant insère une chaîne de caractères astucieusement construite `foo onload=alert('Hacked!')` comme légende de l'image. Nous savons déjà que Twig ne peut pas dire si une variable est imprimée dans un flux de texte HTML, dans un attribut, dans un commentaire HTML, etc. Bref, il ne fait pas la distinction entre les contextes. Et il convertit mécaniquement les caractères `< > & ' "` en entités HTML. -Le code résultant ressemblera donc à ceci : +L'attaquant insère comme légende de l'image une chaîne habilement construite `foo onload=alert('Piraté !')`. Nous savons déjà que Twig ne peut pas savoir si la variable est affichée dans le flux de texte HTML, à l'intérieur d'un attribut, d'un commentaire HTML, etc., bref, il ne distingue pas les contextes. Et il ne fait que convertir mécaniquement les caractères `< > & ' "` en entités HTML. Le code résultant ressemblera donc à ceci : ```html -foo +foo ``` -**Un trou de sécurité a été créé!** +**Et une faille de sécurité a été créée !** -Un faux attribut `onload` s'est intégré à la page et le navigateur l'exécute immédiatement après le téléchargement de l'image. +L'attribut `onload` falsifié fait partie de la page et le navigateur l'exécute immédiatement après le téléchargement de l'image. -Voyons maintenant comment Latte traite le même modèle : +Voyons maintenant comment Latte gère le même template : ```latte .{file:Latte} {$imageAlt} ``` -Latte voit le modèle de la même manière que vous. Contrairement à Twig, il comprend le HTML et sait qu'une variable est imprimée comme une valeur d'attribut qui n'est pas entre guillemets. C'est pourquoi il les ajoute. Lorsqu'un attaquant insère la même légende, le code résultant ressemblera à ceci : +Latte voit le template de la même manière que vous. Contrairement à Twig, il comprend HTML et sait que la variable est affichée comme valeur d'un attribut qui n'est pas entre guillemets. C'est pourquoi il les ajoute. Lorsque l'attaquant insère la même légende, le code résultant ressemblera à ceci : ```html -foo onload=alert('Hacked!') +foo onload=alert('Piraté !') ``` **Latte a réussi à empêcher XSS.** -Impression d'une variable en JavaScript .[#toc-printing-a-variable-in-javascript] ---------------------------------------------------------------------------------- +Affichage d'une variable en JavaScript +-------------------------------------- -Grâce à l'échappement contextuel, il est possible d'utiliser les variables PHP de manière native dans JavaScript. +Grâce à l'échappement contextuel, il est tout à fait natif d'utiliser des variables PHP à l'intérieur de JavaScript. ```latte

                                                              {$movie}

                                                              @@ -334,7 +348,7 @@ Grâce à l'échappement contextuel, il est possible d'utiliser les variables PH ``` -Si la variable `$movie` stocke la chaîne `'Amarcord & 8 1/2'`, elle génère la sortie suivante. Notez que l'échappement utilisé dans HTML et JavaScript est différent, ainsi que dans l'attribut `onclick`: +Si la variable `$movie` contient la chaîne `'Amarcord & 8 1/2'`, la sortie suivante sera générée. Notez qu'à l'intérieur du HTML, un échappement différent est utilisé par rapport à l'intérieur du JavaScript, et encore un autre dans l'attribut `onclick`: ```latte

                                                              Amarcord & 8 1/2

                                                              @@ -343,29 +357,27 @@ Si la variable `$movie` stocke la chaîne `'Amarcord & 8 1/2'`, elle génère la ``` -Vérification du lien .[#toc-link-checking] ------------------------------------------- +Vérification des liens +---------------------- -Latte vérifie automatiquement si la variable utilisée dans les attributs `src` ou `href` contient une URL web (c'est-à-dire le protocole HTTP) et empêche l'écriture de liens susceptibles de présenter un risque pour la sécurité. +Latte vérifie automatiquement si la variable utilisée dans les attributs `src` ou `href` contient une URL web (c'est-à-dire le protocole HTTP) et empêche l'affichage de liens qui pourraient présenter un risque de sécurité. ```latte {var $link = 'javascript:attack()'} -click here +cliquez ``` -Écrit : +Affiche : ```latte -click here +cliquez ``` -La vérification peut être désactivée à l'aide d'un filtre [nocheck |filters#nocheck]. +La vérification peut être désactivée à l'aide du filtre [nocheck |filters#nocheck]. -Limites de Latte .[#toc-limits-of-latte] -======================================== +Limites de Latte +================ -Latte ne constitue pas une protection XSS complète pour l'ensemble de l'application. Nous serions malheureux si vous cessiez de penser à la sécurité lorsque vous utilisez Latte. -L'objectif de Latte est de s'assurer qu'un attaquant ne peut pas modifier la structure d'une page, altérer les éléments ou attributs HTML. Mais il ne vérifie pas l'exactitude du contenu des données produites. Ou l'exactitude du comportement de JavaScript. -C'est au-delà de la portée du système de modélisation. La vérification de l'exactitude des données, en particulier celles saisies par l'utilisateur et donc non fiables, est une tâche importante pour le programmeur. +Latte n'est pas une protection totalement complète contre XSS pour l'ensemble de l'application. Nous ne voudrions pas que vous cessiez de penser à la sécurité en utilisant Latte. L'objectif de Latte est de s'assurer qu'un attaquant ne peut pas modifier la structure de la page, falsifier des éléments ou attributs HTML. Mais il ne contrôle pas l'exactitude du contenu des données affichées. Ni l'exactitude du comportement de JavaScript. Cela dépasse les compétences d'un système de templates. La vérification de l'exactitude des données, en particulier celles insérées par l'utilisateur et donc non fiables, est une tâche importante du programmeur. diff --git a/latte/fr/sandbox.texy b/latte/fr/sandbox.texy index b2e9f0a97c..7c24418486 100644 --- a/latte/fr/sandbox.texy +++ b/latte/fr/sandbox.texy @@ -1,12 +1,10 @@ -Bac à sable -*********** +Sandbox +******* -.[perex]{data-version:2.8} -Sandbox fournit une couche de sécurité qui vous permet de contrôler les balises, les fonctions PHP, les méthodes, etc. qui peuvent être utilisées dans les modèles. Grâce au mode bac à sable, vous pouvez collaborer en toute sécurité avec un client ou un codeur externe sur la création de modèles sans craindre de compromettre l'application ou d'effectuer des opérations non souhaitées. +.[perex] +Le Sandbox fournit une couche de sécurité qui vous donne le contrôle sur les balises, fonctions PHP, méthodes, etc. qui peuvent être utilisées dans les templates. Grâce au mode sandbox, vous pouvez collaborer en toute sécurité avec le client ou un codeur externe sur la création de templates, sans avoir à craindre que l'application soit compromise ou que des opérations indésirables soient effectuées. -Comment cela fonctionne-t-il ? Nous définissons simplement ce que nous voulons autoriser dans le modèle. Au début, tout est interdit et nous accordons progressivement des permissions : - -Le code suivant permet au modèle d'utiliser les balises `{block}`, `{if}`, `{else}` et `{=}` (cette dernière est une balise pour [imprimer une variable ou une expression |tags#Printing]) et tous les filtres : +Comment ça marche ? Nous définissons simplement tout ce que nous autorisons au template. Par défaut, tout est interdit et nous autorisons progressivement. Le code suivant permet à l'auteur du template d'utiliser les balises `{block}`, `{if}`, `{else}` et `{=}`, qui est la balise pour [afficher une variable ou une expression |tags#Affichage] et tous les filtres : ```php $policy = new Latte\Sandbox\SecurityPolicy; @@ -16,7 +14,7 @@ $policy->allowFilters($policy::All); $latte->setPolicy($policy); ``` -Nous pouvons également autoriser l'accès aux fonctions, méthodes ou propriétés globales des objets : +Ensuite, nous pouvons autoriser des fonctions, méthodes ou propriétés d'objets individuelles : ```php $policy->allowFunctions(['trim', 'strlen']); @@ -24,30 +22,35 @@ $policy->allowMethods(Nette\Security\User::class, ['isLoggedIn', 'isAllowed']); $policy->allowProperties(Nette\Database\Row::class, $policy::All); ``` -N'est-ce pas incroyable ? Vous pouvez tout contrôler à un niveau très bas. Si le modèle tente d'appeler une fonction non autorisée ou d'accéder à une méthode ou une propriété non autorisée, il lève l'exception `Latte\SecurityViolationException`. +N'est-ce pas merveilleux ? Vous pouvez contrôler absolument tout à un niveau très bas. Si le template tente d'appeler une fonction non autorisée ou d'accéder à une méthode ou propriété non autorisée, cela se terminera par une exception `Latte\SecurityViolationException`. -Créer des politiques à partir de zéro, lorsque tout est interdit, peut ne pas être pratique, vous pouvez donc commencer à partir d'une base sûre : +Créer une politique à partir de zéro, où tout est interdit, peut ne pas être pratique, vous pouvez donc commencer à partir d'une base sûre : ```php $policy = Latte\Sandbox\SecurityPolicy::createSafePolicy(); ``` -Cela signifie que toutes les balises standard sont autorisées, sauf `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget`. -Tous les filtres standard sont également autorisés, à l'exception de `datastream`, `noescape` et `nocheck`. Enfin, l'accès aux méthodes et aux propriétés de l'objet `$iterator` est également autorisé. +Une base sûre signifie que toutes les balises standard sont autorisées sauf `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget`. Les filtres standard sont autorisés sauf `datastream`, `noescape` et `nocheck`. Et enfin, l'accès aux méthodes et propriétés de l'objet `$iterator` est autorisé. -Les règles s'appliquent au modèle que nous insérons avec la nouvelle balise [`{sandbox}` |tags#Including Templates] avec la nouvelle balise. Il s'agit d'un modèle semblable à `{include}`, mais qui active le mode "sandbox" et ne transmet pas de variables externes : +Les règles s'appliquent au template que nous insérons avec la balise [`{sandbox}` |tags#Inclusion de template]. C'est une sorte d'équivalent de `{include}`, qui active cependant le mode sécurisé et ne transmet également aucune variable : ```latte {sandbox 'untrusted.latte'} ``` -Ainsi, la mise en page et les pages individuelles peuvent utiliser toutes les balises et variables comme auparavant, les restrictions ne seront appliquées qu'au modèle `untrusted.latte`. +Ainsi, le layout et les pages individuelles peuvent utiliser sans interruption toutes les balises et variables, seules les restrictions seront appliquées au template `untrusted.latte`. -Certaines violations, comme l'utilisation d'une balise ou d'un filtre interdit, sont détectées au moment de la compilation. D'autres, comme l'appel de méthodes non autorisées d'un objet, au moment de l'exécution. -Le modèle peut également contenir d'autres bogues. Afin d'éviter qu'une exception ne soit levée à partir du modèle sandboxé, ce qui perturbe l'ensemble du rendu, vous pouvez définir [votre propre gestionnaire d'exception |develop#exception handler], qui, par exemple, se contente de l'enregistrer. +Certaines infractions, comme l'utilisation d'une balise ou d'un filtre interdit, sont détectées au moment de la compilation. D'autres, comme l'appel de méthodes d'objet non autorisées, seulement à l'exécution. Le template peut également contenir d'autres erreurs. Pour éviter qu'une exception provenant d'un template sandboxé ne perturbe l'ensemble du rendu, vous pouvez définir un [gestionnaire d'exceptions personnalisé |develop#Gestionnaire d exceptions], qui la journalisera par exemple. -Si nous voulons activer le mode sandbox directement pour tous les modèles, c'est facile : +Si nous voulions activer le mode sandbox directement pour tous les templates, c'est facile : ```php $latte->setSandboxMode(); ``` + +Pour être sûr que l'utilisateur n'insère pas dans la page du code PHP qui est syntaxiquement correct, mais interdit et provoque une erreur de compilation PHP (PHP Compile Error), nous recommandons de faire [vérifier les templates avec le linter PHP |develop#Vérification du code généré]. Vous activez cette fonctionnalité avec la méthode `Engine::enablePhpLint()`. Comme elle a besoin d'appeler le binaire PHP pour la vérification, passez son chemin en paramètre : + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); +``` diff --git a/latte/fr/syntax.texy b/latte/fr/syntax.texy index 429fad06c7..9d3002cad9 100644 --- a/latte/fr/syntax.texy +++ b/latte/fr/syntax.texy @@ -2,78 +2,76 @@ Syntaxe ******* .[perex] -Syntax Latte est né des besoins pratiques des concepteurs de sites Web. Nous étions à la recherche de la syntaxe la plus conviviale, avec laquelle vous pouvez écrire de manière élégante des constructions qui représentent autrement un véritable défi. -En même temps, toutes les expressions sont écrites exactement de la même manière qu'en PHP, de sorte que vous n'avez pas à apprendre un nouveau langage. Il suffit de tirer le meilleur parti de ce que l'on connaît déjà. +La syntaxe de Latte est née des exigences pratiques des webdesigners. Nous avons recherché la syntaxe la plus conviviale, avec laquelle vous pouvez écrire élégamment même des constructions qui représentent autrement un véritable casse-tête. En même temps, toutes les expressions s'écrivent exactement comme en PHP, vous n'avez donc pas besoin d'apprendre un nouveau langage. Vous mettez simplement à profit ce que vous savez déjà. -Vous trouverez ci-dessous un modèle minimal qui illustre quelques éléments de base : balises, n:attributes, commentaires et filtres. +Ci-dessous se trouve un template minimal qui illustre plusieurs éléments de base : balises, n:attributs, commentaires et filtres. ```latte {* ceci est un commentaire *} -
                                                                {* n:if est n:atribut *} -{foreach $items as $item} {* tag représentant la boucle foreach *} -
                                                              • {$item|capitalize}
                                                              • {* qui imprime une variable avec un filtre *} -{/foreach} {* fin du cycle *} +
                                                                  {* n:if est un n:attribut *} +{foreach $items as $item} {* balise représentant une boucle foreach *} +
                                                                • {$item|capitalize}
                                                                • {* balise affichant une variable avec un filtre *} +{/foreach} {* fin de la boucle *}
                                                                ``` -Examinons de plus près ces éléments importants et comment ils peuvent vous aider à construire un modèle incroyable. +Examinons de plus près ces éléments importants et comment ils peuvent vous aider à créer un template incroyable. -Marqueurs .[#toc-tags] -====================== +Balises +======= -Un modèle contient des balises qui contrôlent la logique du modèle (par exemple, les boucles *foreach*) ou les expressions de sortie. Les deux utilisent le même délimiteur `{ ... }`, il n'est donc pas nécessaire de se demander quel délimiteur utiliser dans chaque situation, contrairement à d'autres systèmes. -Si le caractère { est suivi d'un guillemet ou d'un espace, Latte ne le considère pas comme le début d'une balise, permettant ainsi l'utilisation sans problème de constructions JavaScript, JSON ou CSS dans les modèles. +Un template contient des balises qui contrôlent la logique du template (par exemple, les boucles *foreach*) ou affichent des expressions. Pour les deux, un seul délimiteur `{ ... }` est utilisé, vous n'avez donc pas à réfléchir au délimiteur à utiliser dans quelle situation, comme c'est le cas avec d'autres systèmes. Si un guillemet ou un espace suit le caractère `{`, Latte ne le considère pas comme le début d'une balise, ce qui vous permet d'utiliser sans problème des constructions JavaScript, JSON ou des règles CSS dans les templates. -Voir l'[aperçu de toutes les balises |tags]. En outre, vous pouvez également créer des [balises personnalisées |extending-latte#tags]. +Consultez l'[aperçu de toutes les balises|tags]. De plus, vous pouvez créer vos propres [balises personnalisées|custom tags]. -Latte comprend le PHP .[#toc-latte-understands-php] -=================================================== +Latte comprend PHP +================== -Vous pouvez utiliser des expressions PHP que vous connaissez bien à l'intérieur des balises : +À l'intérieur des balises, vous pouvez utiliser les expressions PHP que vous connaissez bien : - variables -- chaînes de caractères (y compris HEREDOC et NOWDOC), tableaux, nombres, etc. +- chaînes (y compris HEREDOC et NOWDOC), tableaux, nombres, etc. - [opérateurs |https://www.php.net/manual/en/language.operators.php] -- les appels de fonctions et de méthodes (qui peuvent être restreints par la [sandbox]) -- [correspondance |https://www.php.net/manual/en/control-structures.match.php] +- appels de fonctions et de méthodes (qui peuvent être limités par le [sandbox|sandbox]) +- [match |https://www.php.net/manual/en/control-structures.match.php] - [fonctions anonymes |https://www.php.net/manual/en/functions.arrow.php] -- les [callbacks |https://www.php.net/manual/en/functions.first_class_callable_syntax.php] -- commentaires multi-lignes `/* ... */` -- etc... +- [callbacks |https://www.php.net/manual/en/functions.first_class_callable_syntax.php] +- commentaires multilignes `/* ... */` +- etc… -De plus, Latte ajoute plusieurs [extensions |#Syntactic Sugar] à la syntaxe PHP. +De plus, Latte complète la syntaxe PHP avec plusieurs [extensions agréables |#Sucre syntaxique]. -n:attributs .[#toc-n-attributes] -================================ +n:attributs +=========== -Chaque balise de paire, telle que `{if} … {/if}`, opérant sur un seul élément HTML peut être écrite en notation [n:attribut |#n:attribute]. Par exemple, `{foreach}` dans l'exemple ci-dessus pourrait également être écrit de cette façon : +Toutes les balises paires, par exemple `{if} … {/if}`, opérant sur un seul élément HTML, peuvent être réécrites sous forme de n:attributs. C'est ainsi que l'on pourrait écrire par exemple le `{foreach}` de l'exemple initial : ```latte -
                                                                  +
                                                                  • {$item|capitalize}
                                                                  ``` -La fonctionnalité correspond alors à l'élément HTML dans lequel elle est écrite : +La fonctionnalité s'applique alors à l'élément HTML dans lequel elle est placée : ```latte -{var $items = ['I', '♥', 'Latte']} +{var $items = ['J\'', '♥', 'Latte']}

                                                                  {$item}

                                                                  ``` -Imprimés : +affiche : ```latte -

                                                                  I

                                                                  +

                                                                  J'

                                                                  Latte

                                                                  ``` -En utilisant le préfixe `inner-`, nous pouvons modifier le comportement de sorte que la fonctionnalité s'applique uniquement au corps de l'élément : +À l'aide du préfixe `inner-`, nous pouvons modifier le comportement pour qu'il ne s'applique qu'à la partie interne de l'élément : ```latte
                                                                  @@ -82,11 +80,11 @@ En utilisant le préfixe `inner-`, nous pouvons modifier le comportement de sort
                                                                  ``` -Imprimés : +S'affichera : ```latte
                                                                  -

                                                                  I

                                                                  +

                                                                  J'



                                                                  @@ -95,178 +93,184 @@ Imprimés :
                                                                  ``` -Ou en utilisant le préfixe `tag-` la fonctionnalité est appliquée sur les balises HTML uniquement : +Ou à l'aide du préfixe `tag-`, nous appliquons la fonctionnalité uniquement aux balises HTML elles-mêmes : ```latte -

                                                                  Title

                                                                  +

                                                                  Titre

                                                                  ``` -Selon la valeur de la variable `$url`, ceci s'imprimera : +Ce qui affiche en fonction de la variable `$url`: ```latte -// when $url is empty -

                                                                  Title

                                                                  +{* quand $url est vide *} +

                                                                  Titre

                                                                  -// when $url equals 'https://nette.org' -

                                                                  Title

                                                                  +{* quand $url contient 'https://nette.org' *} +

                                                                  Titre

                                                                  ``` -Cependant, les attributs n:ne sont pas seulement un raccourci pour les balises de paire, il existe également des attributs n:purs, par exemple le meilleur ami du codeur, [n:class |tags#n:class]. +Cependant, les n:attributs ne sont pas seulement un raccourci pour les balises paires. Il existe aussi des n:attributs purs, comme [n:href |application:creating-links#Dans le template du presenter] ou l'aide très pratique pour le codeur [n:class |tags#n:class]. -Filtres .[#toc-filters] -======================= +Filtres +======= -Voir le résumé des [filtres standard |filters]. +Consultez l'aperçu des [filtres standard |filters]. -Latte permet d'appeler les filtres en utilisant la notation du signe pipe (l'espace précédent est autorisé) : +Les filtres s'écrivent après une barre verticale (il peut y avoir un espace avant) : ```latte

                                                                  {$heading|upper}

                                                                  ``` -Les filtres peuvent être enchaînés, dans ce cas ils s'appliquent dans l'ordre de gauche à droite : +Les filtres peuvent être chaînés et sont alors appliqués dans l'ordre de gauche à droite : ```latte

                                                                  {$heading|lower|capitalize}

                                                                  ``` -Les paramètres sont placés après le nom du filtre, séparés par des deux points ou des virgules : +Les paramètres sont spécifiés après le nom du filtre, séparés par des deux-points ou des virgules : ```latte

                                                                  {$heading|truncate:20,''}

                                                                  ``` -Les filtres peuvent être appliqués sur une expression : +Les filtres peuvent également être appliqués à une expression : ```latte {var $name = ($title|upper) . ($subtitle|lower)} ``` -Sur le bloc : +Sur un bloc : ```latte

                                                                  {block |lower}{$heading}{/block}

                                                                  ``` -Ou directement sur la valeur (en combinaison avec [`{=expr}` | https://latte.nette.org/fr/tags#printing] tag) : +Ou directement sur la valeur (en combinaison avec la balise [`{=expr}` |tags#Affichage]): ```latte -

                                                                  {=' Hello world '|trim}

                                                                  +

                                                                  {=' Bonjour le monde '|trim}

                                                                  ``` -Commentaires .[#toc-comments] -============================= +Balises HTML dynamiques .{data-version:3.0.9} +============================================= -Les commentaires sont écrits de cette façon et n'apparaissent pas dans la sortie : +Latte prend en charge les balises HTML dynamiques, qui sont utiles lorsque vous avez besoin de flexibilité dans les noms de balises : + +```latte +Titre +``` + +Le code ci-dessus peut par exemple générer `

                                                                  Titre

                                                                  ` ou `

                                                                  Titre

                                                                  ` en fonction de la valeur de la variable `$level`. Les balises HTML dynamiques dans Latte doivent toujours être paires. Leur alternative est [n:tag |tags#n:tag]. + +Comme Latte est un système de templates sécurisé, il vérifie si le nom de balise résultant est valide et ne contient aucune valeur indésirable ou nuisible. De plus, il garantit que le nom de la balise de fin sera toujours le même que le nom de la balise d'ouverture. + + +Commentaires +============ + +Les commentaires s'écrivent de cette manière et n'apparaissent pas dans la sortie : ```latte {* ceci est un commentaire en Latte *} ``` -Les commentaires PHP fonctionnent à l'intérieur des balises : +À l'intérieur des balises, les commentaires PHP fonctionnent : ```latte {include 'file.info', /* value: 123 */} ``` -Le sucre syntaxique .[#toc-syntactic-sugar] -=========================================== +Sucre syntaxique +================ -Chaînes de caractères sans guillemets .[#toc-strings-without-quotation-marks] ------------------------------------------------------------------------------ +Chaînes sans guillemets +----------------------- -Les guillemets peuvent être omis pour les chaînes de caractères simples : +Pour les chaînes simples, on peut omettre les guillemets : ```latte -as in PHP: {var $arr = ['hello', 'btn--default', '€']} +comme en PHP : {var $arr = ['bonjour', 'btn--default', '€']} -abbreviated: {var $arr = [hello, btn--default, €]} +raccourci : {var $arr = [bonjour, btn--default, €]} ``` -Les chaînes de caractères simples sont celles qui sont composées uniquement de lettres, de chiffres, de traits de soulignement, de traits d'union et de points. Elles ne doivent pas commencer par un chiffre et ne doivent pas commencer ou finir par un trait d'union. -Elle ne doit pas être composée uniquement de lettres majuscules et de traits de soulignement, car elle est alors considérée comme une constante (par exemple `PHP_VERSION`). -Et il ne doit pas entrer en collision avec les mots-clés `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor`. +Les chaînes simples sont celles qui sont composées uniquement de lettres, chiffres, traits de soulignement, tirets et points. Elles ne doivent pas commencer par un chiffre et ne doivent pas commencer ou finir par un tiret. Elles ne doivent pas être composées uniquement de majuscules et de traits de soulignement, car elles sont alors considérées comme des constantes (par ex. `PHP_VERSION`). Et elles ne doivent pas entrer en conflit avec les mots-clés : `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor`. -Opérateur ternaire court .[#toc-short-ternary-operator] -------------------------------------------------------- +Constantes +---------- -Si la troisième valeur de l'opérateur ternaire est vide, elle peut être omise : +Comme il est possible d'omettre les guillemets pour les chaînes simples, nous recommandons d'écrire les constantes globales avec une barre oblique au début pour les distinguer : ```latte -as in PHP: {$stock ? 'In stock' : ''} - -abbreviated: {$stock ? 'In stock'} +{if \PROJECT_ID === 1} ... {/if} ``` +Cette écriture est tout à fait valide en PHP même, la barre oblique indique que la constante est dans l'espace de noms global. + -La notation moderne des clés dans le tableau .[#toc-modern-key-notation-in-the-array] -------------------------------------------------------------------------------------- +Opérateur ternaire abrégé +------------------------- -Les clés d'un tableau peuvent être écrites de la même manière que les paramètres nommés lors de l'appel de fonctions : +Si la troisième valeur de l'opérateur ternaire est vide, on peut l'omettre : ```latte -as in PHP: {var $arr = ['one' => 'item 1', 'two' => 'item 2']} +comme en PHP : {$stock ? 'En stock' : ''} -modern: {var $arr = [one: 'item 1', two: 'item 2']} +raccourci : {$stock ? 'En stock'} ``` -Filtres .[#toc-filters] ------------------------ +Notation moderne des clés dans un tableau +----------------------------------------- -Les filtres peuvent être utilisés pour n'importe quelle expression, il suffit de mettre le tout entre parenthèses : +Les clés dans un tableau peuvent être écrites de manière similaire aux paramètres nommés lors de l'appel de fonctions : ```latte -{var $content = ($text|truncate: 30|upper)} +comme en PHP : {var $arr = ['one' => 'item 1', 'two' => 'item 2']} + +moderne : {var $arr = [one: 'item 1', two: 'item 2']} ``` -Opérateur `in` .[#toc-operator-in] ----------------------------------- +Filtres +------- -L'opérateur `in` peut être utilisé pour remplacer la fonction `in_array()`. La comparaison est toujours stricte : +Les filtres peuvent être utilisés pour n'importe quelle expression, il suffit d'enfermer l'ensemble entre parenthèses : ```latte -{* comme in_array($item, $items, true) *} -{if $item in $items} - ... -{/if} +{var $content = ($text|truncate: 30|upper)} ``` -.{data-version:2.9} -Chaînage optionnel avec l'opérateur indéfini-sûr .[#toc-optional-chaining-with-undefined-safe-operator] -------------------------------------------------------------------------------------------------------- +Opérateur `in` +-------------- -L'opérateur undefined-safe `??->` est similaire à l'opérateur nullsafe `?->`, mais ne génère pas d'erreur si une variable, une propriété ou un index n'existe pas du tout. +L'opérateur `in` peut remplacer la fonction `in_array()`. La comparaison est toujours stricte : ```latte -{$order??->id} +{* équivalent de in_array($item, $items, true) *} +{if $item in $items} + ... +{/if} ``` -C'est une façon de dire que lorsque `$order` est défini et n'est pas nul, `$order->id` sera calculé, mais que lorsque `$order` est nul ou n'existe pas, on arrête ce qu'on fait et on renvoie simplement null. - -```latte -{$user??->address??->street} -// roughly means isset($user) && isset($user->address) ? $user->address->street : null -``` +Fenêtre historique +------------------ -Une fenêtre dans l'histoire .[#toc-a-window-into-history] ---------------------------------------------------------- +Latte a introduit au fil de son histoire toute une série de sucres syntaxiques qui sont apparus quelques années plus tard dans PHP même. Par exemple, en Latte, il était possible d'écrire des tableaux comme `[1, 2, 3]` au lieu de `array(1, 2, 3)` ou d'utiliser l'opérateur nullsafe `$obj?->foo` bien avant que cela ne soit possible en PHP même. Latte a également introduit l'opérateur pour déballer un tableau `(expand) $arr`, qui est l'équivalent de l'opérateur actuel `...$arr` de PHP. -Latte a proposé un certain nombre de bonbons syntaxiques au cours de son histoire, qui sont apparus dans PHP lui-même quelques années plus tard. Par exemple, en Latte, il était possible d'écrire les tableaux en tant que `[1, 2, 3]` au lieu de `array(1, 2, 3)` ou d'utiliser l'opérateur nullsafe `$obj?->foo` bien avant que cela ne soit possible en PHP. Latte a également introduit l'opérateur d'expansion de tableau `(expand) $arr`, qui est l'équivalent de l'opérateur `...$arr` de PHP. +L'opérateur Undefined-safe `??->`, qui est un équivalent de l'opérateur nullsafe `?->`, mais qui ne déclenche pas d'erreur si la variable n'existe pas, a été créé pour des raisons historiques et aujourd'hui nous recommandons d'utiliser l'opérateur PHP standard `?->`. -Limitations de PHP dans Latte .[#toc-php-limitations-in-latte] -============================================================== +Limitations de PHP dans Latte +============================= -Seules les expressions PHP peuvent être écrites dans Latte. En d'autres termes, vous ne pouvez pas déclarer de classes ni utiliser de [structures de contrôle |https://www.php.net/manual/en/language.control-structures.php], telles que `if`, `foreach`, `switch`, `return`, `try`, `throw` et autres, au lieu desquelles Latte propose ses [balises |tags]. -Vous ne pouvez pas non plus utiliser d'[attributs |https://www.php.net/manual/en/language.attributes.php], de [backticks |https://www.php.net/manual/en/language.operators.execution.php] ou de [constantes magiques |https://www.php.net/manual/en/language.constants.magic.php], car cela n'aurait aucun sens. -Vous ne pouvez même pas utiliser `unset`, `echo`, `include`, `require`, `exit`, `eval`, car ce ne sont pas des fonctions, mais des constructions spéciales du langage PHP, et donc pas des expressions. +En Latte, on ne peut écrire que des expressions PHP. C'est-à-dire qu'on ne peut pas utiliser d'instructions terminées par un point-virgule. On ne peut pas déclarer de classes ou utiliser des [structures de contrôle |https://www.php.net/manual/en/language.control-structures.php], par ex. `if`, `foreach`, `switch`, `return`, `try`, `throw` et autres, pour lesquelles Latte propose ses [balises|tags]. On ne peut pas non plus utiliser d'[attributs |https://www.php.net/manual/en/language.attributes.php], de [backticks |https://www.php.net/manual/en/language.operators.execution.php] ou certaines [constantes magiques |https://www.php.net/manual/en/language.constants.magic.php]. On ne peut pas non plus utiliser `unset`, `echo`, `include`, `require`, `exit`, `eval`, car ce ne sont pas des fonctions, mais des constructions spéciales du langage PHP, et ce ne sont donc pas des expressions. Seuls les commentaires multilignes `/* ... */` sont pris en charge. -Toutefois, vous pouvez contourner ces limitations en activant l'extension [RawPhpExtension |develop#RawPhpExtension], qui vous permet d'utiliser n'importe quel code PHP dans la balise `{php ...}`, sous la responsabilité de l'auteur du modèle. +Ces limitations peuvent cependant être contournées en activant l'extension [RawPhpExtension |develop#RawPhpExtension], grâce à laquelle on peut ensuite utiliser n'importe quel code PHP dans la balise `{php ...}` sous la responsabilité de l'auteur du template. diff --git a/latte/fr/tags.texy b/latte/fr/tags.texy index 827e8c2074..8624c2ab1b 100644 --- a/latte/fr/tags.texy +++ b/latte/fr/tags.texy @@ -1,168 +1,174 @@ -Latte Marqueurs -*************** +Balises Latte +************* .[perex] -Résumé et description de toutes les balises intégrées de Latte. +Aperçu et description de toutes les balises du système de templates Latte qui sont disponibles par défaut. .[table-latte-tags language-latte] -|## Impression -| `{$var}`, `{...}` ou `{=...}` | [imprime une variable ou une expression échappée |#printing] -| `{$var\|filter}` | [imprime avec des filtres |#filters] -| `{l}` ou `{r}` | imprime le caractère `{` or `}` +|## Affichage +| `{$var}`, `{...}` ou `{=...}` | [affiche une variable ou une expression échappée |#Affichage] +| `{$var\|filter}` | [affiche en utilisant des filtres |#Filtres] +| `{l}` ou `{r}` | affiche le caractère `{` ou `}` .[table-latte-tags language-latte] |## Conditions -| `{if}`... `{elseif}`... `{else}`... `{/if}` | [condition if |#if-elseif-else] -| `{ifset}`... `{elseifset}`... `{/ifset}` | [condition ifset |#ifset-elseifset] -| `{ifchanged}`... `{/ifchanged}` | [test si un changement est intervenu|#ifchanged] -| `{switch}` `{case}` `{default}` `{/switch}` | [condition switch |#switch-case-default] +| `{if}` … `{elseif}` … `{else}` … `{/if}` | [condition if |#if elseif else] +| `{ifset}` … `{elseifset}` … `{/ifset}` | [condition ifset |#ifset elseifset] +| `{ifchanged}` … `{/ifchanged}` | [teste si un changement a eu lieu |#ifchanged] +| `{switch}` `{case}` `{default}` `{/switch}` | [condition switch |#switch case default] +| `n:else` | [contenu alternatif pour les conditions |#n:else] .[table-latte-tags language-latte] |## Boucles -| `{foreach}`... `{/foreach}` | [foreach |#foreach] -| `{for}`... `{/for}` | [for |#for] -| `{while}`... `{/while}` | [while |#while] -| `{continueIf $cond}` | [continuer à l'itération suivante |#continueif-skipif-breakif] -| `{skipIf $cond}` | [sauter l'itération de la boucle actuelle |#continueif-skipif-breakif] -| `{breakIf $cond}` | [interrompt la boucle |#continueif-skipif-breakif] -| `{exitIf $cond}` | [sortie anticipée |#exitif] -| `{first}`... `{/first}` | [est-ce la première itération ? |#first-last-sep] -| `{last}`... `{/last}` | [est-ce la dernière itération ? |#first-last-sep] -| `{sep}`... `{/sep}` | [la prochaine itération suivra-t-elle ? |#first-last-sep] -| `{iterateWhile}`... `{/iterateWhile}` | [foreach structuré |#iterateWhile] -| `$iterator` | [variable spéciale dans la boucle foreach |#$iterator] +| `{foreach}` … `{/foreach}` | [#Foreach] +| `{for}` … `{/for}` | [#For] +| `{while}` … `{/while}` | [#While] +| `{continueIf $cond}` | [continuer à l'itération suivante |#continueIf skipIf breakIf] +| `{skipIf $cond}` | [sauter l'itération |#continueIf skipIf breakIf] +| `{breakIf $cond}` | [interruption de la boucle |#continueIf skipIf breakIf] +| `{exitIf $cond}` | [terminaison anticipée |#exitIf] +| `{first}` … `{/first}` | [est-ce la première itération ? |#first last sep] +| `{last}` … `{/last}` | [est-ce la dernière itération ? |#first last sep] +| `{sep}` … `{/sep}` | [une itération suivra-t-elle encore ? |#first last sep] +| `{iterateWhile}` … `{/iterateWhile}` | [foreach structuré |#iterateWhile] +| `$iterator` | [variable spéciale à l'intérieur de foreach |#iterator] .[table-latte-tags language-latte] -|## Inclure d'autres modèles -| `{include 'file.latte'}` | [inclut un modèle d'un autre fichier |#include] -| `{sandbox 'file.latte'}` | [inclut un modèle en mode sandbox |#sandbox] +|## Inclusion d'autres templates +| `{include 'file.latte'}` | [charge un template depuis un autre fichier |#include] +| `{sandbox 'file.latte'}` | [charge un template en mode sandbox |#sandbox] .[table-latte-tags language-latte] -|## Blocs, mises en page, héritage de modèles -| `{block}` | [bloc anonyme |#block] -| `{block blockname}` | [définition du bloc |template-inheritance#blocks] -| `{define blockname}` | [Définition du bloc pour une utilisation future |template-inheritance#definitions] -| `{include blockname}` | [Imprime le bloc |template-inheritance#printing-blocks] -| `{include blockname from 'file.latte'}` | [Imprime un bloc depuis un fichier |template-inheritance#printing-blocks] -| `{import 'file.latte'}` | [charge des blocs à partir d'un autre modèle |template-inheritance#horizontal-reuse] -| `{layout 'file.latte'}` / `{extends}` | [spécifie un fichier de mise en page |template-inheritance#layout-inheritance] -| `{embed}`... `{/embed}` | [charge le modèle ou le bloc et vous permet d'écraser les blocs |template-inheritance#unit-inheritance] -| `{ifset blockname}`... `{/ifset}` | [conditionne la définition d'un bloc |template-inheritance#checking-block-existence] +|## Blocs, layouts, héritage de templates +| `{block}` | [bloc anonyme |#block] +| `{block blockname}` | [définit un bloc |template-inheritance#Blocs] +| `{define blockname}` | [définit un bloc pour une utilisation ultérieure |template-inheritance#Définitions] +| `{include blockname}` | [rendu d'un bloc |template-inheritance#Rendu des blocs] +| `{include blockname from 'file.latte'}` | [rend un bloc depuis un fichier |template-inheritance#Rendu des blocs] +| `{import 'file.latte'}` | [charge les blocs depuis un template |template-inheritance#Réutilisation horizontale] +| `{layout 'file.latte'}` / `{extends}` | [spécifie le fichier de layout |template-inheritance#Héritage de layout] +| `{embed}` … `{/embed}` | [charge un template ou un bloc et permet de remplacer les blocs |template-inheritance#Héritage unitaire] +| `{ifset blockname}` … `{/ifset}` | [condition, si un bloc existe |template-inheritance#Vérification de l existence des blocs] .[table-latte-tags language-latte] -|## Traitement des exceptions -| `{try}`... `{else}`... `{/try}` | [attrape les exceptions |#try] -| `{rollback}` | [rejeter le bloc d'essai |#rollback] +|## Gestion des exceptions +| `{try}` … `{else}` … `{/try}` | [capture des exceptions |#try] +| `{rollback}` | [abandon du bloc try |#rollback] .[table-latte-tags language-latte] |## Variables -| `{var $foo = value}` | [création de variables |#var-default] -| `{default $foo = value}` | [valeur par défaut quand la variable n'est pas déclarée |#var-default] -| `{parameters}` | [déclare des variables, des types et des valeurs par défaut |#parameters] -| `{capture}`... `{/capture}` | [capture une section à une variable |#capture] +| `{var $foo = value}` | [crée une variable |#var default] +| `{default $foo = value}` | [crée une variable si elle n'existe pas |#var default] +| `{parameters}` | [déclare des variables, types et valeurs par défaut |#parameters] +| `{capture}` … `{/capture}` | [capture un bloc dans une variable |#capture] .[table-latte-tags language-latte] |## Types -| `{varType}` | [déclare le type de la variable |type-system#varType] -| `{varPrint}` | [suggère des types de variables |type-system#varPrint] -| `{templateType}` | [déclare les types de variables en utilisant une classe |type-system#templateType] -| `{templatePrint}` | [génère une classe avec des propriétés |type-system#templatePrint] +| `{varType}` | [déclare le type d'une variable |type-system#varType] +| `{varPrint}` | [suggère les types des variables |type-system#varPrint] +| `{templateType}` | [déclare les types des variables selon une classe |type-system#templateType] +| `{templatePrint}` | [suggère une classe avec les types des variables |type-system#templatePrint] .[table-latte-tags language-latte] -|## Traduction -| `{_string}` | [imprime la traduction |#Translation] -| `{translate}`... `{/translate}` | [traduit le contenu |#Translation] +|## Traductions +| `{_...}` | [affiche la traduction |#Traductions] +| `{translate}` … `{/translate}` | [traduit le contenu |#Traductions] .[table-latte-tags language-latte] |## Autres -| `{contentType}` | [change le mode d'échappement et envoie l'en-tête HTTP |#contenttype] -| `{debugbreak}` | [met un point d'arrêt au code |#debugbreak] -| `{do}` | [évalue une expression sans l'imprimer |#do] -| `{dump}` | [Décharge les variables dans la barre de Tracy |#dump] -| `{spaceless}`... `{/spaceless}` | [supprime les espaces blancs inutiles |#spaceless] -| `{syntax}` | [change la syntaxe au moment de l'exécution |#syntax] -| `{trace}` | [montre la trace de la pile |#trace] +| `{contentType}` | [change l'échappement et envoie l'en-tête HTTP |#contentType] +| `{debugbreak}` | [place un point d'arrêt dans le code |#debugbreak] +| `{do}` | [exécute du code, mais n'affiche rien |#do] +| `{dump}` | [dumpe les variables dans la barre Tracy |#dump] +| `{php}` | [exécute n'importe quel code PHP |#php] +| `{spaceless}` … `{/spaceless}` | [supprime les espaces superflus |#spaceless] +| `{syntax}` | [changement de syntaxe à la volée |#syntax] +| `{trace}` | [affiche la trace de la pile |#trace] .[table-latte-tags language-latte] -|## Aides pour les balises HTML -| `n:class` | [Attribut de classe intelligent |#n:class] -| `n:attr` | [attributs HTML intelligents |#n:attr] -| `n:tag` | [Nom dynamique de l'élément HTML |#n:tag] -| `n:ifcontent` | [Omettre une balise HTML vide |#n:ifcontent] +|## Aides pour le codeur HTML +| `n:class` | [écriture dynamique de l'attribut HTML class |#n:class] +| `n:attr` | [écriture dynamique de n'importe quels attributs HTML |#n:attr] +| `n:tag` | [écriture dynamique du nom de l'élément HTML |#n:tag] +| `n:ifcontent` | [omet la balise HTML vide |#n:ifcontent] .[table-latte-tags language-latte] |## Disponible uniquement dans Nette Framework -| `n:href` | [lien dans les éléments HTML `` |application:creating-links#In the Presenter Template] -| `{link}` | [imprime un lien |application:creating-links#In the Presenter Template] -| `{plink}` | [imprime un lien vers un présentateur |application:creating-links#In the Presenter Template] -| `{control}` | [imprime un composant |application:components#Rendering] -| `{snippet}`... `{/snippet}` | [un snippet de modèle qui peut être envoyé par AJAX |application:ajax#tag-snippet] -| `{snippetArea}` | enveloppe des snippets -| `{cache}`... `{/cache}` | [met en cache une section de modèle |caching:#caching-in-latte] +| `n:href` | [lien utilisé dans les éléments HTML `` |application:creating-links#Dans le template du presenter] +| `{link}` | [affiche un lien |application:creating-links#Dans le template du presenter] +| `{plink}` | [affiche un lien vers un presenter |application:creating-links#Dans le template du presenter] +| `{control}` | [rend un composant |application:components#Rendu] +| `{snippet}` … `{/snippet}` | [extrait qui peut être envoyé par AJAX |application:ajax#Snippets dans Latte] +| `{snippetArea}` | [enveloppe pour les extraits |application:ajax#Zones de Snippets] +| `{cache}` … `{/cache}` | [met en cache une partie du template |caching:#Mise en cache dans Latte] .[table-latte-tags language-latte] |## Disponible uniquement avec Nette Forms -| `{form}`... `{/form}` | [imprime un élément de formulaire |forms:rendering#form] -| `{label}`... `{/label}` | [imprime une étiquette d'entrée de formulaire |forms:rendering#label-input] -| `{input}` | [imprime un élément de saisie de formulaire |forms:rendering#label-input] -| `{inputError}` | [imprime un message d'erreur pour l'élément de saisie du formulaire |forms:rendering#inputError] -| `n:name` | [active un élément de saisie HTML |forms:rendering#n:name] -| `{formPrint}` | [génère le plan du formulaire Latte |forms:rendering#formPrint] -| `{formPrintClass}` | [imprime la classe PHP pour les données du formulaire |forms:in-presenter#mapping-to-classes] -| `{formContext}`... `{/formContext}` | [rendu partiel du formulaire |forms:rendering#special-cases] +| `{form}` … `{/form}` | [rend les balises de formulaire |forms:rendering#form] +| `{label}` … `{/label}` | [rend l'étiquette d'un élément de formulaire |forms:rendering#label input] +| `{input}` | [rend un élément de formulaire |forms:rendering#label input] +| `{inputError}` | [affiche le message d'erreur d'un élément de formulaire |forms:rendering#inputError] +| `n:name` | [anime un élément de formulaire |forms:rendering#n:name] +| `{formContainer}` … `{/formContainer}` | [dessin d'un conteneur de formulaire |forms:rendering#Cas spéciaux] + +.[table-latte-tags language-latte] +|## Disponible uniquement avec Nette Assets +| `{asset}` | [rend un actif sous forme d'élément HTML ou d'URL |assets:#asset] +| `{preload}` - [génère des indices de préchargement pour l'optimisation des performances |assets:#preload] +| `n:asset` - [ajoute des attributs d'actifs aux éléments HTML |assets:#n:asset] -Impression de .[#toc-printing] -============================== +Affichage +========= `{$var}` `{...}` `{=...}` ------------------------- -Latte utilise la balise `{=...}` pour imprimer n'importe quelle expression sur la sortie. Si l'expression commence par une variable ou un appel de fonction, il n'est pas nécessaire d'écrire un signe égal. Ce qui, en pratique, signifie qu'il n'est presque jamais nécessaire de l'écrire : +En Latte, la balise `{=...}` est utilisée pour afficher n'importe quelle expression en sortie. Latte se soucie de votre confort, donc si l'expression commence par une variable ou un appel de fonction, il n'est pas nécessaire d'écrire le signe égal. Ce qui signifie en pratique qu'il n'est presque jamais nécessaire de l'écrire : ```latte -Name: {$name} {$surname}
                                                                  -Age: {date('Y') - $birth}
                                                                  +Nom : {$name} {$surname}
                                                                  +Âge : {date('Y') - $birth}
                                                                  ``` -Vous pouvez écrire tout ce que vous connaissez de PHP sous forme d'expression. Vous n'avez simplement pas besoin d'apprendre un nouveau langage. Par exemple : +Comme expression, vous pouvez écrire tout ce que vous connaissez de PHP. Vous n'avez tout simplement pas besoin d'apprendre un nouveau langage. Par exemple : ```latte {='0' . ($num ?? $num * 3) . ', ' . PHP_VERSION} ``` -Ne cherchez pas de sens dans l'exemple précédent, mais si vous en trouvez un, écrivez-nous :-) +S'il vous plaît, ne cherchez aucun sens dans l'exemple précédent, mais si vous en trouvez un, écrivez-nous :-) -Sortie d'échappement .[#toc-escaping-output] --------------------------------------------- +Échappement de la sortie +------------------------ -Quelle est la tâche la plus importante d'un système de modèles ? Éviter les failles de sécurité. Et c'est exactement ce que fait Latte lorsque vous imprimez quelque chose en sortie. Il échappe automatiquement tout : +Quelle est la tâche la plus importante d'un système de templates ? Empêcher les failles de sécurité. Et c'est exactement ce que fait Latte chaque fois que vous affichez quelque chose. Il l'échappe automatiquement : ```latte -

                                                                  {='one < two'}

                                                                  {* prints: '

                                                                  one < two

                                                                  ' *} +

                                                                  {='one < two'}

                                                                  {* affiche : '

                                                                  one < two

                                                                  ' *} ``` -Pour être précis, Latte utilise l'échappement contextuel, qui est une fonctionnalité tellement importante et unique que nous lui avons consacré [un chapitre séparé|safety-first#context-aware-escaping]. +Pour être précis, Latte utilise l'[échappement contextuel |safety-first#Échappement contextuel], ce qui est une chose si importante et unique que nous lui avons consacré un chapitre séparé. -Et si vous imprimez du contenu codé en HTML à partir d'une source fiable ? Vous pouvez alors facilement désactiver l'échappement : +Et si vous affichez du contenu encodé en HTML provenant d'une source fiable ? Alors, vous pouvez facilement désactiver l'échappement : ```latte {$trustedHtmlString|noescape} ``` .[warning] -Une mauvaise utilisation du filtre `noescape` peut conduire à une vulnérabilité XSS ! Ne l'utilisez jamais sans être **absolument sûr** de ce que vous faites et que la chaîne que vous imprimez provient d'une source fiable. +Une mauvaise utilisation du filtre `noescape` peut entraîner une vulnérabilité XSS ! Ne l'utilisez jamais si vous n'êtes pas **absolument sûr** de ce que vous faites et que la chaîne affichée provient d'une source fiable. -Impression en JavaScript .[#toc-printing-in-javascript] -------------------------------------------------------- +Affichage en JavaScript +----------------------- -Grâce à l'échappement contextuel, il est merveilleusement facile d'imprimer des variables en JavaScript, et Latte les échappera correctement. +Grâce à l'échappement contextuel, il est merveilleusement facile d'afficher des variables à l'intérieur de JavaScript et Latte s'occupe de l'échappement correct. -La variable ne doit pas nécessairement être une chaîne de caractères, n'importe quel type de données est pris en charge, qui est ensuite encodé en JSON : +La variable ne doit pas nécessairement être une chaîne, n'importe quel type de données est pris en charge, qui sera ensuite encodé en JSON : ```latte {var $foo = ['hello', true, 1]} @@ -179,7 +185,7 @@ Génère : ``` -C'est aussi la raison pour laquelle **ne pas mettre les variables entre guillemets** : Latte les ajoute autour des chaînes de caractères. Et si vous voulez mettre une variable de chaîne dans une autre chaîne, il suffit de les concaténer : +C'est aussi la raison pour laquelle **on n'écrit pas de guillemets** autour de la variable : Latte les ajoute lui-même pour les chaînes. Et si vous vouliez insérer une variable de chaîne dans une autre chaîne, concaténez-les simplement : ```latte ``` -Filtres .[#toc-filters] ------------------------ +Filtres +------- -L'expression imprimée peut être modifiée [par des filtres |syntax#filters]. Par exemple, cet exemple convertit la chaîne en majuscules et la raccourcit à un maximum de 30 caractères : +L'expression affichée peut être modifiée par un [filtre |syntax#Filtres]. Ainsi, par exemple, nous convertissons une chaîne en majuscules et la raccourcissons à un maximum de 30 caractères : ```latte {$string|upper|truncate:30} ``` -Vous pouvez également appliquer des filtres à des parties d'une expression comme suit : +Vous pouvez également appliquer des filtres à des parties partielles de l'expression de cette manière : ```latte {$left . ($middle|upper) . $right} ``` -Conditions .[#toc-conditions] -============================= +Conditions +========== `{if}` `{elseif}` `{else}` -------------------------- -Les conditions se comportent de la même manière que leurs équivalents en PHP. Vous pouvez utiliser les mêmes expressions que celles que vous connaissez en PHP, vous n'avez pas besoin d'apprendre un nouveau langage. +Les conditions se comportent de la même manière que leurs homologues en PHP. Vous pouvez y utiliser les mêmes expressions que vous connaissez de PHP, vous n'avez pas besoin d'apprendre un nouveau langage. ```latte {if $product->inStock > Stock::Minimum} - In stock + En stock {elseif $product->isOnWay()} - On the way + En route {else} - Not available + Non disponible {/if} ``` -Comme toute balise de paire, une paire de `{if} ... {/ if}` peut être écrite comme [n:attribut |syntax#n:attributes], par exemple : +Comme toute balise paire, la paire `{if} ... {/if}` peut également être écrite sous forme de [n:attribut |syntax#n:attributs], par exemple : ```latte -

                                                                  In stock {$count} items

                                                                  +

                                                                  {$count} pièces en stock

                                                                  ``` -Savez-vous que vous pouvez ajouter le préfixe `tag-` aux n:attributs ? La condition n'affectera alors que les balises HTML et le contenu situé entre elles sera toujours imprimé : +Savez-vous que vous pouvez ajouter le préfixe `tag-` aux n:attributs ? Alors la condition ne s'appliquera qu'à l'affichage des balises HTML et le contenu entre elles sera toujours affiché : ```latte
                                                                  Hello -{* prints 'Hello' when $clickable is falsey *} -{* prints 'Hello' when $clickable is truthy *} +{* affiche 'Hello' si $clickable est faux *} +{* affiche 'Hello' si $clickable est vrai *} +``` + +Génial. + + +`n:else` .{data-version:3.0.11} +------------------------------- + +Si vous écrivez la condition `{if} ... {/if}` sous forme de [n:attribut |syntax#n:attributs], vous avez la possibilité d'indiquer également une branche alternative à l'aide de `n:else`: + +```latte +{$count} pièces en stock + +non disponible ``` -Bien. +L'attribut `n:else` peut également être utilisé en paire avec [`n:ifset` |#ifset elseifset], [`n:foreach` |#foreach], [`n:try` |#try], [#`n:ifcontent`] et [`n:ifchanged` |#ifchanged]. `{/if $cond}` ------------- -Vous serez peut-être surpris de constater que l'expression de la condition `{if}` peut également être spécifiée dans la balise de fin. Cela est utile dans les situations où nous ne connaissons pas encore la valeur de la condition à l'ouverture de la balise. Appelons cela une décision différée. +Vous serez peut-être surpris que l'expression dans la condition `{if}` puisse également être indiquée dans la balise de fin. C'est utile dans les situations où, lors de l'ouverture de la condition, nous ne connaissons pas encore sa valeur. Appelons cela une décision différée. -Par exemple, nous commençons à lister un tableau avec des enregistrements de la base de données, et ce n'est qu'après avoir terminé le rapport que nous nous rendons compte qu'il n'y avait aucun enregistrement dans la base de données. Nous mettons donc une condition dans la balise de fin `{/if}` et s'il n'y a pas d'enregistrement, rien ne sera imprimé : +Par exemple, nous commençons à afficher un tableau avec des enregistrements de la base de données et ce n'est qu'après avoir terminé l'affichage que nous réalisons qu'il n'y avait aucun enregistrement dans la base de données. Nous mettons donc une condition pour cela dans la balise de fin `{/if}` et s'il n'y a aucun enregistrement, rien de tout cela ne sera affiché : ```latte {if} -

                                                                  Printing rows from the database

                                                                  +

                                                                  Liste des lignes de la base de données

                                                                  {foreach $resultSet as $row} @@ -266,28 +286,28 @@ Par exemple, nous commençons à lister un tableau avec des enregistrements de l Pratique, n'est-ce pas ? -Vous pouvez également utiliser `{else}` dans la condition différée, mais pas `{elseif}`. +Dans la condition différée, on peut aussi utiliser `{else}`, mais pas `{elseif}`. `{ifset}` `{elseifset}` ----------------------- .[note] -Voir aussi [`{ifset block}` |template-inheritance#checking-block-existence] +Voir aussi [`{ifset block}` |template-inheritance#Vérification de l existence des blocs] -Utilisez la condition `{ifset $var}` pour déterminer si une variable (ou plusieurs variables) existe et a une valeur non nulle. C'est en fait la même chose que `if (isset($var))` en PHP. Comme toute balise de paire, elle peut être écrite sous la forme [n:attribut |syntax#n:attributes], alors montrons-la en exemple : +À l'aide de la condition `{ifset $var}`, nous vérifions si la variable (ou plusieurs variables) existe et a une valeur non-*null*. En fait, c'est la même chose que `if (isset($var))` en PHP. Comme toute balise paire, elle peut également être écrite sous forme de [n:attribut |syntax#n:attributs], montrons-le comme exemple : ```latte - + ``` -`{ifchanged}` .{data-version:2.9} ---------------------------------- +`{ifchanged}` +------------- -`{ifchanged}` vérifie si la valeur d'une variable a changé depuis la dernière itération de la boucle (foreach, for, ou while). +`{ifchanged}` vérifie si la valeur de la variable a changé depuis la dernière itération dans la boucle (foreach, for ou while). -Si nous spécifions une ou plusieurs variables dans la balise, elle vérifiera si l'une d'entre elles a changé et imprimera le contenu en conséquence. Par exemple, l'exemple suivant imprime la première lettre d'un nom en tant qu'en-tête chaque fois qu'elle change lors de l'énumération des noms : +Si nous indiquons une ou plusieurs variables dans la balise, elle vérifiera si l'une d'elles a changé et affichera le contenu en conséquence. Par exemple, l'exemple suivant affiche la première lettre du nom comme titre chaque fois qu'elle change lors de l'affichage des noms : ```latte {foreach ($names|sort) as $name} @@ -297,7 +317,7 @@ Si nous spécifions une ou plusieurs variables dans la balise, elle vérifiera s {/foreach} ``` -Toutefois, si aucun argument n'est fourni, le contenu rendu lui-même sera vérifié par rapport à son état précédent. Cela signifie que dans l'exemple précédent, nous pouvons omettre sans risque l'argument dans la balise. Et bien sûr, nous pouvons également utiliser [n:attribute |syntax#n:attributes]: +Cependant, si nous n'indiquons aucun argument, le contenu rendu sera comparé à son état précédent. Cela signifie que dans l'exemple précédent, nous pouvons tout à fait omettre l'argument dans la balise. Et bien sûr, nous pouvons aussi utiliser un [n:attribut |syntax#n:attributs]: ```latte {foreach ($names|sort) as $name} @@ -307,49 +327,49 @@ Toutefois, si aucun argument n'est fourni, le contenu rendu lui-même sera véri {/foreach} ``` -Vous pouvez également inclure une clause `{else}` à l'intérieur de `{ifchanged}`. +À l'intérieur de `{ifchanged}`, on peut aussi indiquer une clause `{else}`. `{switch}` `{case}` `{default}` ------------------------------- -Compare la valeur avec plusieurs options. Cette structure est similaire à la structure `switch` que vous connaissez de PHP. Cependant, Latte l'améliore : +Compare la valeur avec plusieurs options. C'est un équivalent de l'instruction conditionnelle `switch` que vous connaissez de PHP. Cependant, Latte l'améliore : - utilise une comparaison stricte (`===`) -- n'a pas besoin d'un `break` +- n'a pas besoin de `break` -C'est donc l'équivalent exact de la structure `match` fournie par PHP 8.0. +C'est donc l'équivalent exact de la structure `match` introduite avec PHP 8.0. ```latte {switch $transport} {case train} - By train + En train {case plane} - By plane + En avion {default} - Differently + Autrement {/switch} ``` -.{data-version:2.9} + La clause `{case}` peut contenir plusieurs valeurs séparées par des virgules : ```latte {switch $status} -{case $status::New}new item -{case $status::Sold, $status::Unknown}not available +{case $status::New}nouvel élément +{case $status::Sold, $status::Unknown}non disponible {/switch} ``` -Boucles .[#toc-loops] -===================== +Boucles +======= -Dans Latte, toutes les boucles que vous connaissez de PHP sont à votre disposition : foreach, for et while. +En Latte, vous trouverez toutes les boucles que vous connaissez de PHP : foreach, for et while. `{foreach}` ----------- -Vous écrivez le cycle exactement de la même manière qu'en PHP : +Nous écrivons la boucle exactement comme en PHP : ```latte {foreach $langs as $code => $lang} @@ -357,11 +377,11 @@ Vous écrivez le cycle exactement de la même manière qu'en PHP : {/foreach} ``` -En outre, il a quelques tweaks pratiques dont nous allons parler maintenant. +De plus, elle a quelques astuces pratiques dont nous allons parler maintenant. -Par exemple, Latte vérifie que les variables créées n'écrasent pas accidentellement les variables globales du même nom. Cela vous sauvera lorsque vous supposerez que `$lang` est la langue actuelle de la page, et que vous ne réaliserez pas que `foreach $langs as $lang` a écrasé cette variable. +Latte vérifie par exemple si les variables créées n'écrasent pas accidentellement des variables globales du même nom. Cela sauve des situations où vous comptez sur le fait que `$lang` contient la langue actuelle de la page, et ne réalisez pas que `foreach $langs as $lang` vous a écrasé cette variable. -La boucle foreach peut également être écrite de manière très élégante et économique avec [n:attribute |syntax#n:attributes]: +La boucle foreach peut également être écrite de manière très élégante et concise à l'aide d'un [n:attribut |syntax#n:attributs]: ```latte
                                                                    @@ -369,7 +389,7 @@ La boucle foreach peut également être écrite de manière très élégante et
                                                                  ``` -Saviez-vous que vous pouvez ajouter le préfixe `inner-` au préfixe n:attribute ? Ainsi, seule la partie intérieure de l'élément sera répétée dans la boucle : +Savez-vous que vous pouvez ajouter le préfixe `inner-` aux n:attributs ? Alors seul l'intérieur de l'élément sera répété dans la boucle : ```latte
                                                                  @@ -378,7 +398,7 @@ Saviez-vous que vous pouvez ajouter le préfixe `inner-` au préfixe n:attribute
                                                                  ``` -Donc ça imprime quelque chose comme : +Donc, quelque chose comme ceci sera affiché : ```latte
                                                                  @@ -390,17 +410,17 @@ Donc ça imprime quelque chose comme : ``` -`{else}` .{data-version:2.9}{toc: foreach-else} ------------------------------------------------ +`{else}` .{toc: foreach-else} +----------------------------- -La boucle `foreach` peut prendre une clause optionnelle `{else}` dont le texte est affiché si le tableau donné est vide : +À l'intérieur de la boucle `foreach`, on peut indiquer une clause `{else}`, dont le contenu s'affichera si la boucle est vide : ```latte
                                                                    {foreach $people as $person}
                                                                  • {$person->name}
                                                                  • {else} -
                                                                  • Sorry, no users in this list
                                                                  • +
                                                                  • Désolé, il n'y a aucun utilisateur dans cette liste
                                                                  • {/foreach}
                                                                  ``` @@ -409,15 +429,15 @@ La boucle `foreach` peut prendre une clause optionnelle `{else}` dont le texte e `$iterator` ----------- -À l'intérieur de la boucle `foreach`, la variable `$iterator` est initialisée. Elle contient des informations importantes sur la boucle en cours. +À l'intérieur de la boucle `foreach`, Latte crée la variable `$iterator`, à l'aide de laquelle nous pouvons obtenir des informations utiles sur la boucle en cours : -- `$iterator->first` - est-ce la première itération ? +- `$iterator->first` - est-ce la première itération de la boucle ? - `$iterator->last` - est-ce la dernière itération ? -- `$iterator->counter` - compteur d'itérations, commence à 1 -- `$iterator->counter0` - compteur d'itérations, démarre à 0 .{data-version:2.9} -- `$iterator->odd` - cette itération est-elle impaire ? -- `$iterator->even` - cette itération est-elle paire ? -- `$iterator->parent` - l'itérateur qui entoure l'itérateur actuel .{data-version:2.9} +- `$iterator->counter` - quelle est l'itération en cours, comptée à partir de un ? +- `$iterator->counter0` - quelle est l'itération en cours, comptée à partir de zéro ? +- `$iterator->odd` - est-ce une itération impaire ? +- `$iterator->even` - est-ce une itération paire ? +- `$iterator->parent` - l'itérateur entourant celui actuel - `$iterator->nextValue` - l'élément suivant dans la boucle - `$iterator->nextKey` - la clé de l'élément suivant dans la boucle @@ -435,20 +455,19 @@ La boucle `foreach` peut prendre une clause optionnelle `{else}` dont le texte e {/foreach} ``` -La latte est intelligente et `$iterator->last` fonctionne non seulement pour les tableaux, mais aussi lorsque la boucle passe sur un itérateur général où le nombre d'éléments n'est pas connu à l'avance. +Latte est malin et `$iterator->last` fonctionne non seulement pour les tableaux, mais aussi lorsque la boucle s'exécute sur un itérateur général, où le nombre d'éléments n'est pas connu à l'avance. `{first}` `{last}` `{sep}` -------------------------- -Ces balises peuvent être utilisées à l'intérieur de la boucle `{foreach}`. Le contenu de `{first}` est rendu lors du premier passage. -Le contenu de `{last}` est rendu... pouvez-vous deviner ? Oui, pour le dernier passage. Ce sont en fait des raccourcis pour `{if $iterator->first}` et `{if $iterator->last}`. +Ces balises peuvent être utilisées à l'intérieur de la boucle `{foreach}`. Le contenu de `{first}` est rendu s'il s'agit de la première itération. Le contenu de `{last}` est rendu… devinerez-vous ? Oui, s'il s'agit de la dernière itération. Ce sont en fait des raccourcis pour `{if $iterator->first}` et `{if $iterator->last}`. -Les balises peuvent également être écrites sous la forme [n:attributes |syntax#n:attributes]: +Les balises peuvent également être utilisées élégamment comme [n:attribut |syntax#n:attributs]: ```latte {foreach $rows as $row} - {first}

                                                                  List of names

                                                                  {/first} + {first}

                                                                  Liste des noms

                                                                  {/first}

                                                                  {$row->name}

                                                                  @@ -456,21 +475,21 @@ Les balises peuvent également être écrites sous la forme [n:attributes |synta {/foreach} ``` -Le contenu du site `{sep}` est rendu si l'itération n'est pas la dernière, il convient donc pour imprimer des délimiteurs, tels que des virgules entre les éléments énumérés : +Le contenu de la balise `{sep}` est rendu si l'itération n'est pas la dernière, il est donc utile pour afficher des séparateurs, par exemple des virgules entre les éléments affichés : ```latte {foreach $items as $item} {$item} {sep}, {/sep} {/foreach} ``` -C'est plutôt pratique, non ? +C'est assez pratique, n'est-ce pas ? -`{iterateWhile}` .{data-version:2.10} -------------------------------------- +`{iterateWhile}` +---------------- -Il simplifie le regroupement des données linéaires pendant l'itération dans une boucle foreach en effectuant l'itération dans une boucle imbriquée tant que la condition est remplie. [Lire les instructions dans le livre de cuisine |cookbook/iteratewhile]. +Simplifie le regroupement de données linéaires lors de l'itération dans une boucle foreach en effectuant l'itération dans une boucle imbriquée tant que la condition est remplie. [Lisez le tutoriel détaillé|cookbook/grouping]. -Elle peut aussi remplacer élégamment `{first}` et `{last}` dans l'exemple ci-dessus : +Peut aussi remplacer élégamment `{first}` et `{last}` dans l'exemple ci-dessus : ```latte {foreach $rows as $row} @@ -487,19 +506,21 @@ Elle peut aussi remplacer élégamment `{first}` et `{last}` dans l'exemple ci-d {/foreach} ``` +Voir aussi les filtres [batch |filters#batch] et [group |filters#group]. + `{for}` ------- -Nous écrivons le cycle exactement de la même manière qu'en PHP : +Nous écrivons la boucle exactement comme en PHP : ```latte {for $i = 0; $i < 10; $i++} - Item #{$i} + Élément {$i} {/for} ``` -La balise peut aussi être écrite comme [n:attribut |syntax#n:attributes]: +La balise peut également être utilisée comme [n:attribut |syntax#n:attributs]: ```latte

                                                                  {$i}

                                                                  @@ -509,7 +530,7 @@ La balise peut aussi être écrite comme [n:attribut |syntax#n:attributes]: `{while}` --------- -Là encore, nous écrivons le cycle exactement de la même manière qu'en PHP : +Nous écrivons à nouveau la boucle exactement comme en PHP : ```latte {while $row = $result->fetch()} @@ -517,7 +538,7 @@ Là encore, nous écrivons le cycle exactement de la même manière qu'en PHP : {/while} ``` -Ou comme [n:attribut |syntax#n:attributes]: +Ou comme [n:attribut |syntax#n:attributs]: ```latte @@ -525,7 +546,7 @@ Ou comme [n:attribut |syntax#n:attributes]: ``` -Une variante avec une condition dans la balise de fin correspond à la boucle do-while en PHP : +Une variante avec la condition dans la balise de fin est également possible, ce qui correspond en PHP à la boucle do-while : ```latte {while} @@ -537,7 +558,7 @@ Une variante avec une condition dans la balise de fin correspond à la boucle do `{continueIf}` `{skipIf}` `{breakIf}` ------------------------------------- -Il existe des balises spéciales que vous pouvez utiliser pour contrôler n'importe quelle boucle - `{continueIf ?}` et `{breakIf ?}` qui sautent à l'itération suivante et mettent fin à la boucle, respectivement, si les conditions sont remplies : +Pour contrôler n'importe quelle boucle, on peut utiliser les balises `{continueIf ?}` et `{breakIf ?}`, qui passent à l'élément suivant resp. terminent la boucle si la condition est remplie : ```latte {foreach $rows as $row} @@ -547,8 +568,8 @@ Il existe des balises spéciales que vous pouvez utiliser pour contrôler n'impo {/foreach} ``` -.{data-version:2.9} -L'étiquette `{skipIf}` est très similaire à `{continueIf}`, mais n'incrémente pas le compteur. Il n'y a donc pas de trous dans la numérotation lorsque vous imprimez `$iterator->counter` et sautez certains éléments. De même, la clause {else} sera rendue lorsque vous sautez tous les éléments. + +La balise `{skipIf}` est très similaire à `{continueIf}`, mais n'incrémente pas le compteur `$iterator->counter`, donc si nous l'affichons et sautons en même temps certains éléments, il n'y aura pas de trous dans la numérotation. Et aussi la clause `{else}` sera rendue si nous sautons tous les éléments. ```latte
                                                                    @@ -556,7 +577,7 @@ L'étiquette `{skipIf}` est très similaire à `{continueIf}`, mais n'incrément {skipIf $person->age < 18}
                                                                  • {$iterator->counter}. {$person->name}
                                                                  • {else} -
                                                                  • Sorry, no adult users in this list
                                                                  • +
                                                                  • Désolé, il n'y a aucun adulte dans cette liste
                                                                  • {/foreach}
                                                                  ``` @@ -565,7 +586,7 @@ L'étiquette `{skipIf}` est très similaire à `{continueIf}`, mais n'incrément `{exitIf}` .{data-version:3.0.5} -------------------------------- -Termine le rendu d'un modèle ou d'un bloc lorsqu'une condition est remplie (c'est-à-dire une "sortie anticipée"). +Termine le rendu du template ou du bloc si la condition est remplie (appelé "early exit"). ```latte {exitIf !$messages} @@ -577,60 +598,56 @@ Termine le rendu d'un modèle ou d'un bloc lorsqu'une condition est remplie (c'e ``` -Inclure des modèles .[#toc-including-templates] -=============================================== +Inclusion de template +===================== `{include 'file.latte'}` .{toc: include} ---------------------------------------- .[note] -Voir aussi [`{include block}` |template-inheritance#printing-blocks] +Voir aussi [`{include block}` |template-inheritance#Rendu des blocs] -La balise `{include}` charge et rend le modèle spécifié. Dans notre langage PHP préféré, c'est comme ça : +La balise `{include}` charge et rend le template spécifié. Si nous parlions dans le langage de notre langage PHP préféré, ce serait quelque chose comme : ```php ``` -Les templates inclus n'ont pas accès aux variables du contexte actif, mais ont accès aux variables globales. +Les templates inclus n'ont pas accès aux variables du contexte actif, ils n'ont accès qu'aux variables globales. -Vous pouvez passer des variables de cette manière : +Vous pouvez passer des variables au template inclus de cette manière : ```latte -{* depuis Latte 2.9 *} {include 'template.latte', foo: 'bar', id: 123} - -{* avant Latte 2.9 *} -{include 'template.latte', foo => 'bar', id => 123} ``` -Le nom du modèle peut être n'importe quelle expression PHP : +Le nom du template peut être n'importe quelle expression PHP : ```latte {include $someVar} {include $ajax ? 'ajax.latte' : 'not-ajax.latte'} ``` -Le contenu inséré peut être modifié à l'aide de [filtres |syntax#filters]. L'exemple suivant supprime tous les éléments HTML et ajuste la casse : +Le contenu inclus peut être modifié à l'aide de [filtres |syntax#Filtres]. L'exemple suivant supprime tout le HTML et modifie la casse des lettres : ```latte {include 'heading.latte' |stripHtml|capitalize} ``` -L'[héritage du modèle |template inheritance] **n'est pas impliqué** dans ceci par défaut. Bien que vous puissiez ajouter des balises de bloc aux modèles qui sont inclus, elles ne remplaceront pas les blocs correspondants dans le modèle dans lequel elles sont incluses. Considérez les includes comme des parties indépendantes et protégées de pages ou de modules. Ce comportement peut être modifié à l'aide du modificateur `with blocks` (depuis Latte 2.9.1) : +Par défaut, l'[héritage de templates|template-inheritance] ne joue aucun rôle dans ce cas. Même si nous pouvons utiliser des blocs dans le template inclus, les blocs correspondants dans le template dans lequel il est inclus ne seront pas remplacés. Pensez aux templates inclus comme des parties de pages ou de modules distinctes et isolées. Ce comportement peut être modifié à l'aide du modificateur `with blocks`: ```latte {include 'template.latte' with blocks} ``` -La relation entre le nom de fichier spécifié dans la balise et le fichier sur le disque est une question de [chargeur |extending-latte#Loaders]. +La relation entre le nom de fichier spécifié dans la balise et le fichier sur le disque dépend du [chargeur|loaders]. -`{sandbox}` .{data-version:2.8} -------------------------------- +`{sandbox}` +----------- -Lorsque vous incluez un modèle créé par un utilisateur final, vous devez envisager de le mettre en sandbox (plus d'informations dans la [documentation sur le sandbox |sandbox]) : +Lors de l'inclusion d'un template créé par l'utilisateur final, vous devriez envisager le mode sandbox (plus d'informations dans la [documentation du sandbox |sandbox]): ```latte {sandbox 'untrusted.latte', level: 3, data: $menu} @@ -641,9 +658,9 @@ Lorsque vous incluez un modèle créé par un utilisateur final, vous devez envi ========= .[note] -Voir aussi [`{block name}` |template-inheritance#blocks] +Voir aussi [`{block name}` |template-inheritance#Blocs] -Les blocs sans nom permettent d'appliquer des [filtres |syntax#filters] à une partie du modèle. Par exemple, vous pouvez appliquer un filtre à [bandes |filters#strip] pour supprimer les espaces inutiles : +Les blocs sans nom servent de moyen pour appliquer des [filtres |syntax#Filtres] à une partie du template. Par exemple, on peut ainsi appliquer le filtre [strip |filters#spaceless], qui supprime les espaces inutiles : ```latte {block|strip} @@ -654,16 +671,16 @@ Les blocs sans nom permettent d'appliquer des [filtres |syntax#filters] à une p ``` -Traitement des exceptions .[#toc-exception-handling] -==================================================== +Gestion des exceptions +====================== -`{try}` .{data-version:2.9} ---------------------------- +`{try}` +------- -Cette balise permet de créer très facilement des modèles robustes. +Grâce à cette balise, il est extrêmement facile de créer des templates robustes. -Si une exception se produit lors du rendu du bloc `{try}`, le bloc entier est jeté et le rendu se poursuit après : +Si une exception se produit lors du rendu du bloc `{try}`, tout le bloc est abandonné et le rendu continue après lui : ```latte {try} @@ -685,11 +702,11 @@ Le contenu de la clause facultative `{else}` n'est rendu que lorsqu'une exceptio {/foreach} {else} -

                                                                  Sorry, the tweets could not be loaded.

                                                                  +

                                                                  Désolé, impossible de charger les tweets.

                                                                  {/try} ``` -La balise peut également être écrite comme [n:attribut |syntax#n:attributes]: +La balise peut également être utilisée comme [n:attribut |syntax#n:attributs]: ```latte
                                                                    @@ -697,13 +714,13 @@ La balise peut également être écrite comme [n:attribut |syntax#n:attributes]:
                                                                  ``` -Il est également possible de définir [son propre gestionnaire d'exception |develop#exception handler], par exemple pour la journalisation : +Il est également possible de définir un [gestionnaire d'exceptions personnalisé |develop#Gestionnaire d exceptions], par exemple pour la journalisation. -`{rollback}` .{data-version:2.9} --------------------------------- +`{rollback}` +------------ -Le bloc `{try}` peut également être arrêté et sauté manuellement à l'aide de `{rollback}`. Il n'est donc pas nécessaire de vérifier toutes les données d'entrée à l'avance, et ce n'est que pendant le rendu que vous pouvez décider s'il est judicieux de rendre l'objet. +Le bloc `{try}` peut également être arrêté et sauté manuellement à l'aide de `{rollback}`. Grâce à cela, vous n'avez pas besoin de vérifier toutes les données d'entrée à l'avance et vous pouvez décider pendant le rendu que vous ne voulez pas du tout rendre l'objet : ```latte {try} @@ -719,14 +736,14 @@ Le bloc `{try}` peut également être arrêté et sauté manuellement à l'aide ``` -Variables .[#toc-variables] -=========================== +Variables +========= `{var}` `{default}` ------------------- -Nous allons créer de nouvelles variables dans le modèle avec la balise `{var}`: +Nous créons de nouvelles variables dans le template avec la balise `{var}`: ```latte {var $name = 'John Smith'} @@ -736,13 +753,13 @@ Nous allons créer de nouvelles variables dans le modèle avec la balise `{var}` {var $name = 'John Smith', $age = 27} ``` -La balise `{default}` fonctionne de manière similaire, sauf qu'elle crée des variables uniquement si elles n'existent pas : +La balise `{default}` fonctionne de manière similaire, mais ne crée des variables que si elles n'existent pas. Si la variable existe déjà et contient la valeur `null`, elle ne sera pas écrasée : ```latte -{default $lang = 'cs'} +{default $lang = 'fr'} ``` -À partir de Latte 2.7, vous pouvez également spécifier les [types de variables |type-system]. Pour l'instant, ils sont informatifs et Latte ne les vérifie pas. +Vous pouvez également indiquer les [types de variables|type-system]. Pour l'instant, ils sont informatifs et Latte ne les vérifie pas. ```latte {var string $name = $article->getTitle()} @@ -750,10 +767,10 @@ La balise `{default}` fonctionne de manière similaire, sauf qu'elle crée des v ``` -`{parameters}` .{data-version:2.9} ----------------------------------- +`{parameters}` +-------------- -Tout comme une fonction déclare ses paramètres, un modèle peut déclarer ses variables à son début : +Tout comme une fonction déclare ses paramètres, un template peut également déclarer ses variables au début : ```latte {parameters @@ -763,15 +780,15 @@ Tout comme une fonction déclare ses paramètres, un modèle peut déclarer ses } ``` -Les variables `$a` et `$b` sans valeur par défaut ont automatiquement une valeur par défaut de `null`. Les types déclarés sont toujours informatifs et Latte ne les vérifie pas. +Les variables `$a` et `$b` sans valeur par défaut spécifiée ont automatiquement la valeur par défaut `null`. Les types déclarés sont pour l'instant informatifs et Latte ne les vérifie pas. -En dehors de cela, les variables déclarées ne sont pas passées dans le modèle. C'est une différence par rapport à la balise `{default}`. +D'autres variables que celles déclarées ne sont pas transmises au template. C'est ce qui la différencie de la balise `{default}`. `{capture}` ----------- -En utilisant la balise `{capture}`, vous pouvez capturer la sortie vers une variable : +Capture la sortie dans une variable : ```latte {capture $var} @@ -780,10 +797,10 @@ En utilisant la balise `{capture}`, vous pouvez capturer la sortie vers une vari {/capture} -

                                                                  Captured: {$var}

                                                                  +

                                                                  Capturé : {$var}

                                                                  ``` -La balise peut également être écrite comme [n:attribut |syntax#n:attributes]: +La balise peut, comme toute balise paire, être également écrite comme un [n:attribut |syntax#n:attributs]: ```latte
                                                                    @@ -791,15 +808,17 @@ La balise peut également être écrite comme [n:attribut |syntax#n:attributes]:
                                                                  ``` +La sortie HTML est enregistrée dans la variable `$var` sous forme d'objet `Latte\Runtime\Html`, pour [éviter un échappement indésirable |develop#Désactivation de l échappement automatique des variables] lors de l'affichage. -Autres .[#toc-others] -===================== + +Autres +====== `{contentType}` --------------- -Utilisez la balise pour spécifier le type de contenu que le modèle représente. Les options sont les suivantes : +Avec cette balise, vous spécifiez le type de contenu que représente le template. Les options sont : - `html` (type par défaut) - `xml` @@ -808,16 +827,16 @@ Utilisez la balise pour spécifier le type de contenu que le modèle représente - `calendar` (iCal) - `text` -Son utilisation est importante car elle définit l'[échappement contextuel |safety-first#context-aware-escaping] et ce n'est qu'alors que Latte peut s'échapper correctement. Par exemple, `{contentType xml}` passe en mode XML, `{contentType text}` désactive complètement l'échappement. +Son utilisation est importante car elle définit l'[échappement contextuel |safety-first#Échappement contextuel] et ce n'est qu'ainsi qu'elle peut échapper correctement. Par exemple, `{contentType xml}` passe en mode XML, `{contentType text}` désactive complètement l'échappement. -Si le paramètre est un type MIME complet, tel que `application/xml`, il envoie également un en-tête HTTP `Content-Type` au navigateur : +Si le paramètre est un type MIME complet, comme par exemple `application/xml`, alors il envoie également l'en-tête HTTP `Content-Type` au navigateur : ```latte {contentType application/xml} - RSS feed + Flux RSS ... @@ -829,46 +848,50 @@ Si le paramètre est un type MIME complet, tel que `application/xml`, il envoie `{debugbreak}` -------------- -Spécifie l'endroit où l'exécution du code sera interrompue. Il est utilisé à des fins de débogage pour que le programmeur puisse inspecter l'environnement d'exécution et s'assurer que le code s'exécute comme prévu. Il prend en charge [Xdebug |https://xdebug.org]. En outre, vous pouvez spécifier une condition à laquelle le code doit être interrompu. +Indique l'endroit où l'exécution du programme sera suspendue et le débogueur démarré, afin que le programmeur puisse inspecter l'environnement d'exécution et vérifier si le programme fonctionne comme prévu. Prend en charge [Xdebug |https://xdebug.org/]. On peut ajouter une condition qui détermine quand le programme doit être suspendu. ```latte -{debugbreak} {* casse le programme *} +{debugbreak} {* suspend le programme *} -{debugbreak $counter == 1} {* interrompt le programme si la condition est remplie *} +{debugbreak $counter == 1} {* suspend le programme si la condition est remplie *} ``` `{do}` ------ -Exécute le code et n'imprime rien. +Exécute du code PHP et n'affiche rien. Comme pour toutes les autres balises, le code PHP s'entend comme une seule expression, voir [limitations de PHP |syntax#Limitations de PHP dans Latte]. ```latte {do $num++} ``` -Dans Latte 2.7 et les versions antérieures, le site `{php}` était utilisé. - `{dump}` -------- -Décharge une variable ou le contexte actuel. +Affiche une variable ou le contexte actuel. ```latte -{dump $name} {* vide la variable $name *} +{dump $name} {* Affiche la variable $name *} -{dump} {* vide toutes les variables définies *} +{dump} {* Affiche toutes les variables actuellement définies *} ``` .[caution] -Nécessite le paquet [Tracy |tracy:]. +Nécessite la bibliothèque [Tracy|tracy:]. + + +`{php}` +------- + +Permet d'exécuter n'importe quel code PHP. La balise doit être activée à l'aide de l'extension [RawPhpExtension |develop#RawPhpExtension]. `{spaceless}` ------------- -Supprime les espaces blancs inutiles. Il est similaire au filtre [sans espace |filters#spaceless]. +Supprime les espaces blancs inutiles de la sortie. Fonctionne de manière similaire au filtre [spaceless |filters#spaceless]. ```latte {spaceless} @@ -878,52 +901,52 @@ Supprime les espaces blancs inutiles. Il est similaire au filtre [sans espace |f {/spaceless} ``` -Sorties : +Génère ```latte
                                                                  • Hello
                                                                  ``` -La balise peut aussi être écrite comme [n:attribut |syntax#n:attributes]: +La balise peut également être écrite comme un [n:attribut |syntax#n:attributs]. `{syntax}` ---------- -Les balises Latte ne doivent pas nécessairement être entourées d'accolades simples uniquement. Vous pouvez choisir un autre séparateur, même au moment de l'exécution. Ceci est fait par `{syntax…}`, où le paramètre peut être : +Les balises Latte ne doivent pas nécessairement être délimitées uniquement par des accolades simples. Nous pouvons choisir un autre délimiteur, et ce même à l'exécution. La balise `{syntax …}` sert à cela, où l'on peut indiquer comme paramètre : -- double : `{{...}}` -- off : désactive complètement les balises Latte +- double: `{{...}}` +- off: désactive complètement le traitement des balises Latte -En utilisant la notation n:attribute, nous pouvons désactiver Latte pour un bloc JavaScript uniquement : +En utilisant les n:attributs, on peut désactiver Latte par exemple pour un seul bloc JavaScript : ```latte ``` -Latte peut être utilisé très confortablement en JavaScript, il suffit d'éviter les constructions comme dans cet exemple, où la lettre suit immédiatement `{`, voir [Latte en JavaScript ou CSS |recipes#Latte inside JavaScript or CSS]. +Latte peut être utilisé très confortablement à l'intérieur de JavaScript, il suffit d'éviter les constructions comme dans cet exemple, où une lettre suit immédiatement `{`, voir [Latte à l'intérieur de JavaScript ou CSS |recipes#Latte à l intérieur de JavaScript ou CSS]. -Si vous désactivez Latte avec la balise `{syntax off}` (c'est-à-dire la balise, pas l'attribut n :), il ignorera strictement toutes les balises jusqu'à `{/syntax}`. +Si vous désactivez Latte à l'aide de `{syntax off}` (c'est-à-dire avec la balise, pas le n:attribut), il ignorera strictement toutes les balises jusqu'à `{/syntax}` -{trace} .{data-version:2.10} ----------------------------- +{trace} +------- -Lance une exception `Latte\RuntimeException`, dont la trace de pile est dans l'esprit des templates. Ainsi, au lieu d'appeler des fonctions et des méthodes, il s'agit d'appeler des blocs et d'insérer des templates. Si vous utilisez un outil permettant d'afficher clairement les exceptions lancées, tel que [Tracy |tracy:], vous verrez clairement la pile d'appel, y compris tous les arguments passés. +Déclenche une exception `Latte\RuntimeException`, dont la trace de la pile (stack trace) est dans l'esprit des templates. C'est-à-dire qu'au lieu des appels de fonctions et de méthodes, elle contient des appels de blocs et des inclusions de templates. Si vous utilisez un outil pour afficher clairement les exceptions levées, comme par exemple [Tracy|tracy:], la pile d'appels s'affichera clairement, y compris tous les arguments passés. -Aides de balises HTML .[#toc-html-tag-helpers] -============================================== +Aides pour le codeur HTML +========================= n:class ------- -Grâce à `n:class`, il est très facile de générer l'attribut HTML `class` exactement comme vous le souhaitez. +Grâce à `n:class`, vous pouvez très facilement générer l'attribut HTML `class` exactement selon vos souhaits. -Exemple : J'ai besoin que l'élément actif ait la classe `active`: +Exemple : j'ai besoin que l'élément actif ait la classe `active`: ```latte {foreach $items as $item} @@ -931,7 +954,7 @@ Exemple : J'ai besoin que l'élément actif ait la classe `active`: {/foreach} ``` -Et j'ai également besoin que le premier élément ait les classes `first` et `main`: +Et de plus, que le premier élément ait les classes `first` et `main`: ```latte {foreach $items as $item} @@ -939,7 +962,7 @@ Et j'ai également besoin que le premier élément ait les classes `first` et `m {/foreach} ``` -Et tous les éléments doivent avoir la classe `list-item`: +Et que tous les éléments aient la classe `list-item`: ```latte {foreach $items as $item} @@ -953,7 +976,7 @@ Incroyablement simple, n'est-ce pas ? n:attr ------ -L'attribut `n:attr` peut générer des attributs HTML arbitraires avec la même élégance que [n:class |#n:class]. +L'attribut `n:attr` peut générer n'importe quels attributs HTML avec la même élégance que [#n:class]. ```latte {foreach $data as $item} @@ -961,7 +984,7 @@ L'attribut `n:attr` peut générer des attributs HTML arbitraires avec la même {/foreach} ``` -En fonction des valeurs renvoyées, il affiche eg : +En fonction des valeurs retournées, affiche par ex. : ```latte @@ -972,26 +995,28 @@ En fonction des valeurs renvoyées, il affiche eg : ``` -n:tag .{data-version:2.10} --------------------------- +n:tag +----- -L'attribut `n:tag` permet de modifier dynamiquement le nom d'un élément HTML. +L'attribut `n:tag` peut changer dynamiquement le nom de l'élément HTML. ```latte

                                                                  {$title}

                                                                  ``` -Si `$heading === null`, la balise `

                                                                  ` est imprimé sans changement. Sinon, le nom de l'élément est changé en la valeur de la variable, donc pour `$heading === 'h3'` il s'écrit : +Si `$heading === null`, la balise `

                                                                  ` sera affichée sans changement. Sinon, le nom de l'élément sera changé en la valeur de la variable, donc pour `$heading === 'h3'`, s'affichera : ```latte

                                                                  ...

                                                                  ``` +Comme Latte est un système de templates sécurisé, il vérifie si le nouveau nom de balise est valide et ne contient aucune valeur indésirable ou nuisible. + n:ifcontent ----------- -Empêche l'impression d'un élément HTML vide, c'est-à-dire un élément ne contenant que des espaces blancs. +Empêche l'affichage d'un élément HTML vide, c'est-à-dire un élément ne contenant rien d'autre que des espaces. ```latte
                                                                  @@ -999,7 +1024,7 @@ Empêche l'impression d'un élément HTML vide, c'est-à-dire un élément ne co
                                                                  ``` -En fonction des valeurs de la variable `$error`, cet élément sera imprimé : +Affiche en fonction de la valeur de la variable `$error`: ```latte {* $error = '' *} @@ -1013,10 +1038,10 @@ En fonction des valeurs de la variable `$error`, cet élément sera imprimé : ``` -Traduction .{data-version:3.0}[#toc-translation] -================================================ +Traductions +=========== -Pour que les balises de traduction fonctionnent, vous devez configurer [translator |develop#TranslatorExtension]. Vous pouvez également utiliser le [`translate` |filters#translate] pour la traduction. +Pour que les balises de traduction fonctionnent, il faut [activer le traducteur |develop#TranslatorExtension]. Pour la traduction, vous pouvez également utiliser le filtre [`translate` |filters#translate]. `{_...}` @@ -1025,30 +1050,30 @@ Pour que les balises de traduction fonctionnent, vous devez configurer [translat Traduit les valeurs dans d'autres langues. ```latte -{_'Basket'} +{_'Panier'} {_$item} ``` -D'autres paramètres peuvent également être transmis au traducteur : +On peut également passer d'autres paramètres au traducteur : ```latte -{_'Basket', domain: order} +{_'Panier', domain: order} ``` `{translate}` ------------- -Překládá části šablony : +Traduit des parties du template : ```latte -

                                                                  {translate}Order{/translate}

                                                                  +

                                                                  {translate}Commande{/translate}

                                                                  {translate domain: order}Lorem ipsum ...{/translate} ``` -La balise peut aussi s'écrire [n:attribut |syntax#n:attributes], pour traduire l'intérieur de l'élément : +La balise peut également être écrite comme un [n:attribut |syntax#n:attributs], pour traduire l'intérieur de l'élément : ```latte -

                                                                  Order

                                                                  +

                                                                  Commande

                                                                  ``` diff --git a/latte/fr/template-inheritance.texy b/latte/fr/template-inheritance.texy index 871677ec23..77c482ffbc 100644 --- a/latte/fr/template-inheritance.texy +++ b/latte/fr/template-inheritance.texy @@ -1,16 +1,16 @@ -Héritage et réutilisabilité des modèles -*************************************** +Héritage et réutilisabilité des templates +***************************************** .[perex] -Les mécanismes de réutilisation et d'héritage des modèles sont là pour booster votre productivité car chaque modèle ne contient que son contenu unique et les éléments et structures répétés sont réutilisés. Nous présentons trois concepts : l'[héritage des modèles |#layout inheritance], la [réutilisation horizontale |#horizontal reuse] et l'[héritage des unités |#unit inheritance]. +Les mécanismes de réutilisation et d'héritage des templates augmenteront votre productivité, car chaque template ne contient que son contenu unique, et les éléments et structures répétés sont réutilisés. Nous introduisons trois concepts : [#Héritage de layout], [#Réutilisation horizontale] et [#Héritage unitaire]. -Le concept d'héritage des modèles Latte est similaire à l'héritage des classes PHP. Vous définissez un **modèle parent** à partir duquel les autres **modèles enfants** peuvent s'étendre et remplacer certaines parties du modèle parent. Ce concept fonctionne parfaitement lorsque les éléments partagent une structure commune. Cela vous semble compliqué ? Ne vous inquiétez pas, ce n'est pas le cas. +Le concept d'héritage de template Latte est similaire à l'héritage de classe en PHP. Vous définissez un **template parent**, dont d'autres **templates enfants** peuvent hériter et peuvent remplacer des parties du template parent. Cela fonctionne très bien lorsque les éléments partagent une structure commune. Cela semble compliqué ? Ne vous inquiétez pas, c'est très facile. -Héritage de la mise en page `{layout}` .{toc: Layout Inheritance} -================================================================= +Héritage de layout `{layout}` .{toc:Héritage de layout} +======================================================= -Examinons l'héritage des modèles de mise en page en commençant par un exemple. Il s'agit d'un modèle parent que nous appellerons par exemple `layout.latte` et qui définit un squelette de document HTML. +Voyons l'héritage de template de layout, c'est-à-dire la mise en page, avec un exemple. Ceci est le template parent, que nous appellerons par exemple `layout.latte`, et qui définit le squelette du document HTML : ```latte @@ -30,9 +30,9 @@ Examinons l'héritage des modèles de mise en page en commençant par un exemple ``` -La balise `{block}` définit trois blocs que les modèles enfants peuvent remplir. La balise block indique au moteur de modèle qu'un modèle enfant peut remplacer ces parties du modèle en définissant son propre bloc du même nom. +Les balises `{block}` définissent trois blocs que les templates enfants peuvent remplir. La balise block ne fait qu'indiquer que cet emplacement peut être remplacé par un template enfant en définissant son propre bloc avec le même nom. -Un modèle enfant peut ressembler à ceci : +Un template enfant peut ressembler à ceci : ```latte {layout 'layout.latte'} @@ -44,11 +44,11 @@ Un modèle enfant peut ressembler à ceci : {/block} ``` -La balise `{layout}` est la clé ici. Elle indique au moteur de modèle que ce modèle "étend" un autre modèle. Lorsque Latte rend ce modèle, il localise d'abord le parent - dans ce cas, `layout.latte`. +La clé ici est la balise `{layout}`. Elle indique à Latte que ce template "étend" un autre template. Lorsque Latte rend ce template, il trouve d'abord le template parent - dans ce cas, `layout.latte`. -À ce stade, le moteur de modèle remarque les trois balises de bloc dans `layout.latte` et remplace ces blocs par le contenu du modèle enfant. Notez que, comme le modèle enfant n'a pas défini le bloc *footer*, le contenu du modèle parent est utilisé à la place. Le contenu d'une balise `{block}` dans un modèle parent est toujours utilisé comme solution de repli. +À ce stade, Latte remarque les trois balises block dans `layout.latte` et remplace ces blocs par le contenu du template enfant. Étant donné que le template enfant n'a pas défini de bloc *footer*, le contenu du template parent est utilisé à la place. Le contenu de la balise `{block}` dans le template parent est toujours utilisé comme solution de secours. -Le résultat peut ressembler à ceci : +La sortie peut ressembler à ceci : ```latte @@ -68,7 +68,7 @@ Le résultat peut ressembler à ceci : ``` -Dans un modèle enfant, les blocs ne peuvent être situés qu'au niveau supérieur ou à l'intérieur d'un autre bloc, par exemple : +Dans le template enfant, les blocs ne peuvent être placés qu'au niveau supérieur ou à l'intérieur d'un autre bloc, c'est-à-dire : ```latte {block content} @@ -76,7 +76,7 @@ Dans un modèle enfant, les blocs ne peuvent être situés qu'au niveau supérie {/block} ``` -De plus, un bloc sera toujours créé dans le modèle, que la condition environnante `{if}` soit évaluée comme étant vraie ou fausse. Contrairement à ce que vous pourriez penser, ce modèle définit bien un bloc. +De plus, un bloc sera toujours créé, que la condition `{if}` environnante soit évaluée comme vraie ou fausse. Donc, même si cela ne semble pas être le cas, ce template définira le bloc. ```latte {if false} @@ -86,7 +86,7 @@ De plus, un bloc sera toujours créé dans le modèle, que la condition environn {/if} ``` -Si vous souhaitez que la sortie à l'intérieur du bloc soit affichée de manière conditionnelle, utilisez plutôt ce qui suit : +Si vous souhaitez que la sortie à l'intérieur du bloc s'affiche conditionnellement, utilisez plutôt ce qui suit : ```latte {block head} @@ -96,7 +96,7 @@ Si vous souhaitez que la sortie à l'intérieur du bloc soit affichée de maniè {/block} ``` -Les données situées à l'extérieur d'un bloc dans un modèle enfant sont exécutées avant que le modèle de présentation ne soit rendu. Vous pouvez donc l'utiliser pour définir des variables telles que `{var $foo = bar}` et propager les données à l'ensemble de la chaîne d'héritage : +L'espace en dehors des blocs dans le template enfant est exécuté avant le rendu du template de layout, vous pouvez donc l'utiliser pour définir des variables comme `{var $foo = bar}` et pour propager des données à travers toute la chaîne d'héritage : ```latte {layout 'layout.latte'} @@ -106,51 +106,50 @@ Les données situées à l'extérieur d'un bloc dans un modèle enfant sont exé ``` -Héritage multi-niveaux .[#toc-multilevel-inheritance] ------------------------------------------------------ -Vous pouvez utiliser autant de niveaux d'héritage que nécessaire. Une façon courante d'utiliser l'héritage de mise en page est l'approche à trois niveaux suivante : +Héritage à plusieurs niveaux +---------------------------- +Vous pouvez utiliser autant de niveaux d'héritage que nécessaire. Une manière courante d'utiliser l'héritage de layout est l'approche à trois niveaux suivante : -1) Créez un modèle `layout.latte` qui contient l'aspect et la convivialité principaux de votre site. -2) Créez un modèle `layout-SECTIONNAME.latte` pour chaque section de votre site. Par exemple, `layout-news.latte`, `layout-blog.latte` etc. Ces modèles étendent tous `layout.latte` et incluent des styles/conceptions spécifiques à chaque section. -3) Créez des modèles individuels pour chaque type de page, par exemple un article d'actualité ou une entrée de blog. Ces modèles étendent le modèle de section approprié. +1) Créez un template `layout.latte` qui contient le squelette principal de l'apparence du site. +2) Créez un template `layout-SECTIONNAME.latte` pour chaque section de votre site. Par exemple, `layout-news.latte`, `layout-blog.latte`, etc. Tous ces templates étendent `layout.latte` et incluent les styles & design spécifiques à chaque section. +3) Créez des templates individuels pour chaque type de page, par exemple un article de journal ou une entrée de blog. Ces templates étendent le template de section approprié. -Héritage dynamique de la mise en page .[#toc-dynamic-layout-inheritance] ------------------------------------------------------------------------- -Vous pouvez utiliser une variable ou toute expression PHP comme nom du modèle parent, de sorte que l'héritage peut se comporter de manière dynamique : +Héritage dynamique +------------------ +Le nom du template parent peut être une variable ou toute expression PHP, de sorte que l'héritage peut se comporter de manière dynamique : ```latte {layout $standalone ? 'minimum.latte' : 'layout.latte'} ``` -Vous pouvez également utiliser l'API Latte pour choisir [automatiquement |develop#automatic-layout-lookup] le modèle de mise en page. +Vous pouvez également utiliser l'API Latte pour [sélectionner automatiquement |develop#Recherche automatique de layout] le template de layout. -Conseils .[#toc-tips] ---------------------- -Voici quelques conseils pour travailler avec l'héritage de mise en page : +Conseils +-------- +Voici quelques conseils pour travailler avec l'héritage de layout : -- Si vous utilisez `{layout}` dans un modèle, il doit s'agir de la première balise de modèle de ce modèle. +- Si vous utilisez `{layout}` dans un template, ce doit être la première balise du template. -- La mise en page peut être [recherchée automatiquement |develop#automatic-layout-lookup] (comme dans les [présentateurs |application:templates#search-for-templates]). Dans ce cas, si le modèle ne doit pas avoir de mise en page, il l'indiquera avec la balise `{layout none}`. +- Le layout peut être [recherché automatiquement |develop#Recherche automatique de layout] (comme par exemple dans les [presenters |application:templates#Recherche de templates]). Dans ce cas, si le template ne doit pas avoir de layout, il l'indique avec la balise `{layout none}`. - La balise `{layout}` a un alias `{extends}`. -- Le nom de fichier du modèle étendu dépend du [chargeur de modèle |extending-latte#Loaders]. +- Le nom du fichier de layout dépend du [chargeur |loaders]. -- Vous pouvez avoir autant de blocs que vous le souhaitez. Rappelez-vous que les modèles enfants n'ont pas à définir tous les blocs parents, vous pouvez donc remplir des valeurs par défaut raisonnables dans un certain nombre de blocs, puis ne définir que ceux dont vous avez besoin plus tard. +- Vous pouvez avoir autant de blocs que vous le souhaitez. N'oubliez pas que les templates enfants n'ont pas besoin de définir tous les blocs parents, vous pouvez donc remplir des valeurs par défaut raisonnables dans plusieurs blocs, puis ne définir que ceux dont vous avez besoin plus tard. -Blocs `{block}` .{toc: Blocks} -============================== +Blocs `{block}` .{toc: Blocs} +============================= .[note] -Voir aussi anonyme [`{block}` |tags#block] +Voir aussi le [`{block}` |tags#block] anonyme -Un bloc permet de modifier la façon dont une certaine partie d'un modèle est rendue, mais il n'interfère en aucune façon avec la logique qui l'entoure. Prenons l'exemple suivant pour illustrer comment un bloc fonctionne et, surtout, comment il ne fonctionne pas : +Un bloc représente une manière de modifier la façon dont une certaine partie du template est rendue, mais n'interfère en aucune façon avec la logique qui l'entoure. Dans l'exemple suivant, nous montrerons comment un bloc fonctionne, mais aussi comment il ne fonctionne pas : -```latte -{* parent.Latte *} +```latte .{file: parent.latte} {foreach $posts as $post} {block post}

                                                                  {$post->title}

                                                                  @@ -159,10 +158,9 @@ Un bloc permet de modifier la façon dont une certaine partie d'un modèle est r {/foreach} ``` -Si vous rendez ce modèle, le résultat sera exactement le même avec ou sans les balises de bloc. Les blocs ont accès aux variables des scopes externes. Il s'agit simplement d'un moyen de les rendre surmontables par un modèle enfant : +Si vous rendez ce template, le résultat sera exactement le même avec ou sans les balises `{block}`. Les blocs ont accès aux variables des portées externes. Ils donnent simplement la possibilité d'être remplacés par un template enfant : -```latte -{* child.Latte *} +```latte .{file: child.latte} {layout 'parent.Latte'} {block post} @@ -173,7 +171,7 @@ Si vous rendez ce modèle, le résultat sera exactement le même avec ou sans le {/block} ``` -Maintenant, lors du rendu du modèle enfant, la boucle va utiliser le bloc défini dans le modèle enfant `child.Latte` au lieu de celui défini dans le modèle de base `parent.Latte`; le modèle exécuté est alors équivalent au modèle suivant : +Maintenant, lors du rendu du template enfant, la boucle utilisera le bloc défini dans le template enfant `child.Latte` au lieu du bloc défini dans `parent.Latte`; le template exécuté est alors équivalent à ce qui suit : ```latte {foreach $posts as $post} @@ -184,7 +182,7 @@ Maintenant, lors du rendu du modèle enfant, la boucle va utiliser le bloc défi {/foreach} ``` -Cependant, si nous créons une nouvelle variable à l'intérieur d'un bloc nommé ou si nous remplaçons une valeur d'une variable existante, le changement sera visible uniquement à l'intérieur du bloc : +Cependant, si nous créons une nouvelle variable à l'intérieur d'un bloc nommé ou remplaçons la valeur d'une variable existante, le changement ne sera visible qu'à l'intérieur du bloc : ```latte {var $foo = 'foo'} @@ -193,17 +191,17 @@ Cependant, si nous créons une nouvelle variable à l'intérieur d'un bloc nomm {var $bar = 'bar'} {/block} -foo: {$foo} // prints: foo -bar: {$bar ?? 'not defined'} // prints: not defined +foo: {$foo} // affiche : foo +bar: {$bar ?? 'not defined'} // affiche : not defined ``` -Le contenu du bloc peut être modifié par des [filtres |syntax#filters]. L'exemple suivant supprime tout le HTML et met le titre en majuscule : +Le contenu du bloc peut être modifié à l'aide de [filtres |syntax#Filtres]. L'exemple suivant supprime tout le HTML et modifie la casse : ```latte {block title|stripHtml|capitalize}...{/block} ``` -La balise peut aussi être écrite comme [n:attribute |syntax#n:attributes]: +La balise peut également être écrite comme un [n:attribut |syntax#n:attributs] : ```latte
                                                                  @@ -212,10 +210,10 @@ La balise peut aussi être écrite comme [n:attribute |syntax#n:attributes]: ``` -Blocs locaux .{data-version:2.9}[#toc-local-blocks] ---------------------------------------------------- +Blocs locaux +------------ -Chaque bloc remplace le contenu du bloc parent du même nom. Sauf pour les blocs locaux. Ils sont un peu comme les méthodes privées d'une classe. Vous pouvez créer un modèle sans craindre que - en raison de la coïncidence des noms des blocs - ils soient écrasés par le second modèle. +Chaque bloc remplace le contenu du bloc parent du même nom – à l'exception des blocs locaux. Dans les classes, ce serait quelque chose comme des méthodes privées. Vous pouvez ainsi créer un template sans craindre que, en raison de la correspondance des noms de blocs, ils soient remplacés par un autre template. ```latte {block local helper} @@ -224,13 +222,13 @@ Chaque bloc remplace le contenu du bloc parent du même nom. Sauf pour les blocs ``` -Blocs d'impression `{include}` .{toc: Printing Blocks} ------------------------------------------------------- +Rendu des blocs `{include}` .{toc: Rendu des blocs} +--------------------------------------------------- .[note] Voir aussi [`{include file}` |tags#include] -Pour imprimer un bloc à un endroit précis, utilisez la balise `{include blockname}`: +Pour afficher un bloc à un endroit spécifique, utilisez la balise `{include blockname}` : ```latte {block title}{/block} @@ -238,32 +236,28 @@ Pour imprimer un bloc à un endroit précis, utilisez la balise `{include blockn

                                                                  {include title}

                                                                  ``` -Vous pouvez également afficher le bloc à partir d'un autre modèle : +Il est également possible d'afficher un bloc d'un autre template : ```latte {include footer from 'main.latte'} ``` -Les blocs imprimés n'ont pas accès aux variables du contexte actif, sauf si le bloc est défini dans le même fichier où il est inclus. Cependant, ils ont accès aux variables globales. +Le bloc rendu n'a pas accès aux variables du contexte actif, sauf si le bloc est défini dans le même fichier où il est inclus. Cependant, il a accès aux variables globales. -Vous pouvez passer des variables de cette manière : +Vous pouvez passer des variables au bloc de cette manière : ```latte -{* depuis Latte 2.9 *} {include footer, foo: bar, id: 123} - -{* avant Latte 2.9 *} -{include footer, foo => bar, id => 123} ``` -Vous pouvez utiliser une variable ou toute expression en PHP comme nom de bloc. Dans ce cas, ajoutez le mot-clé `block` devant la variable, afin que l'on sache à la compilation qu'il s'agit d'un bloc, et non d'un [modèle d'insertion |tags#include], dont le nom pourrait également se trouver dans la variable : +Le nom du bloc peut être une variable ou toute expression PHP. Dans ce cas, nous ajoutons le mot-clé `block` avant la variable pour que Latte sache déjà au moment de la compilation qu'il s'agit d'un bloc et non d'une [inclusion de template |tags#include], dont le nom pourrait également être dans une variable : ```latte {var $name = footer} {include block $name} ``` -Le bloc peut également être imprimé à l'intérieur de lui-même, ce qui est utile, par exemple, lors du rendu d'une structure arborescente : +Un bloc peut être rendu à l'intérieur de lui-même, ce qui est utile par exemple pour rendre une structure arborescente : ```latte {define menu, $items} @@ -281,19 +275,19 @@ Le bloc peut également être imprimé à l'intérieur de lui-même, ce qui est {/define} ``` -Au lieu de `{include menu, ...}`, nous pouvons également écrire `{include this, ...}` où `this` signifie le bloc actuel. +Au lieu de `{include menu, ...}`, nous pouvons alors écrire `{include this, ...}`, où `this` signifie le bloc actuel. -Le contenu imprimé peut être modifié par des [filtres |syntax#filters]. L'exemple suivant supprime tout le HTML et met le titre en majuscule : +Le bloc rendu peut être modifié à l'aide de [filtres |syntax#Filtres]. L'exemple suivant supprime tout le HTML et modifie la casse : ```latte {include heading|stripHtml|capitalize} ``` -Bloc parent .[#toc-parent-block] --------------------------------- +Bloc parent +----------- -Si vous devez imprimer le contenu du bloc à partir du modèle parent, l'instruction `{include parent}` fera l'affaire. Elle est utile si vous souhaitez compléter le contenu d'un bloc parent au lieu de le remplacer complètement. +Si vous avez besoin d'afficher le contenu d'un bloc du template parent, utilisez `{include parent}`. C'est utile si vous souhaitez simplement compléter le contenu du bloc parent au lieu de le remplacer complètement. ```latte {block footer} @@ -304,32 +298,31 @@ Si vous devez imprimer le contenu du bloc à partir du modèle parent, l'instruc ``` -Définitions `{define}` .{toc: Definitions} +Définitions `{define}` .{toc: Définitions} ------------------------------------------ -En plus des blocs, il existe également des "définitions" dans Latte. Elles sont comparables aux fonctions dans les langages de programmation ordinaires. Elles sont utiles pour réutiliser des fragments de modèles afin de ne pas se répéter. +En plus des blocs, Latte propose également des "définitions". Dans les langages de programmation courants, nous les comparerions à des fonctions. Elles sont utiles pour réutiliser des fragments de template afin de ne pas vous répéter. -Latte essaie de faire les choses facilement, donc fondamentalement les définitions sont les mêmes que les blocs, et **tout ce qui est dit sur les blocs s'applique également aux définitions**. Il ne diffère des blocs que de trois façons : +Latte essaie de simplifier les choses, donc en gros, les définitions sont identiques aux blocs et **tout ce qui est dit sur les blocs s'applique également aux définitions**. Elles diffèrent des blocs en ce que : -1) ils peuvent accepter des arguments -2) ils ne peuvent pas avoir de [filtres |syntax#filters] -3) elles sont enfermées dans des balises `{define}` et le contenu de ces balises n'est pas envoyé en sortie tant que vous ne les avez pas incluses. Grâce à cela, vous pouvez les créer n'importe où : +1) elles sont enfermées dans des balises `{define}` +2) elles ne sont rendues que lorsque vous les incluez via `{include}` +3) on peut leur définir des paramètres de la même manière que les fonctions en PHP ```latte {block foo}

                                                                  Hello

                                                                  {/block} -{* prints:

                                                                  Hello

                                                                  *} +{* affiche :

                                                                  Hello

                                                                  *} {define bar}

                                                                  World

                                                                  {/define} -{* prints nothing *} +{* n'affiche rien *} {include bar} -{* prints:

                                                                  World

                                                                  *} +{* affiche :

                                                                  World

                                                                  *} ``` -Imaginez que vous disposez d'un modèle d'aide générique qui définit la manière de rendre les formulaires HTML via des définitions : +Imaginez que vous ayez un template d'aide avec une collection de définitions sur la façon de dessiner des formulaires HTML. -```latte -{* forms.latte *} +```latte .{file: forms.latte} {define input, $name, $value, $type = 'text'} {/define} @@ -339,53 +332,51 @@ Imaginez que vous disposez d'un modèle d'aide générique qui définit la mani {/define} ``` -Les arguments d'une définition sont toujours optionnels avec une valeur par défaut `null`, à moins que la valeur par défaut ne soit spécifiée (ici `text` est la valeur par défaut pour `$type`, possible depuis Latte 2.9.1). À partir de Latte 2.7, les types de paramètres peuvent également être déclarés : `{define input, string $name, ...}`. - -Les définitions n'ont pas accès aux variables du contexte actif, mais elles ont accès aux variables globales. +Les arguments sont toujours facultatifs avec une valeur par défaut de `null`, sauf si une valeur par défaut est spécifiée (ici `'text'` est la valeur par défaut pour `$type`). Les types de paramètres peuvent également être déclarés : `{define input, string $name, ...}`. -Elles sont incluses de la [même manière que les blocs |#Printing Blocks]: +Nous chargeons le template avec les définitions à l'aide de [`{import}` |#Réutilisation horizontale]. Les définitions elles-mêmes sont rendues [de la même manière que les blocs |#Rendu des blocs] : ```latte

                                                                  {include input, 'password', null, 'password'}

                                                                  {include textarea, 'comment'}

                                                                  ``` +Les définitions n'ont pas accès aux variables du contexte actif, mais elles ont accès aux variables globales. -Noms de blocs dynamiques .[#toc-dynamic-block-names] ----------------------------------------------------- -Latte permet une grande flexibilité dans la définition des blocs car le nom du bloc peut être n'importe quelle expression PHP. Cet exemple définit trois blocs nommés `hi-Peter`, `hi-John` et `hi-Mary`: +Noms de blocs dynamiques +------------------------ -```latte -{* parent.latte *} -{foreach [Pierre, Jean, Marie] as $name} - {block "hi-$name"}Hi, je suis {$name}.{/block} +Latte permet une grande flexibilité dans la définition des blocs, car le nom du bloc peut être n'importe quelle expression PHP. Cet exemple définit trois blocs nommés `hi-Peter`, `hi-John` et `hi-Mary` : + +```latte .{file: parent.latte} +{foreach [Peter, John, Mary] as $name} + {block "hi-$name"}Hi, I am {$name}.{/block} {/foreach} ``` -Par exemple, nous ne pouvons redéfinir qu'un seul bloc dans un modèle enfant : +Dans le template enfant, nous pouvons alors redéfinir, par exemple, un seul bloc : -```latte -{* child.latte *} -{block hi-Jean}Bonjour. Je suis {$name}.{/block} +```latte .{file: child.latte} +{block hi-John}Hello. I am {$name}.{/block} ``` Ainsi, la sortie ressemblera à ceci : ```latte -Hi, I am Pierre. -Hello. I am Jean. -Hi, I am Marie. +Hi, I am Peter. +Hello. I am John. +Hi, I am Mary. ``` -Checking Block Existence `{ifset}` .{toc: Checking Block Existence} -------------------------------------------------------------------- +Vérification de l'existence des blocs `{ifset}` .{toc: Vérification de l'existence des blocs} +--------------------------------------------------------------------------------------------- .[note] -Voir aussi [`{ifset $var}` |tags#ifset-elseifset] +Voir aussi [`{ifset $var}` |tags#ifset elseifset] -Utilisez le test `{ifset blockname}` pour vérifier si un bloc (ou plusieurs blocs) existe dans le contexte actuel : +À l'aide du test `{ifset blockname}`, nous vérifions si un bloc (ou plusieurs blocs) existe dans le contexte actuel : ```latte {ifset footer} @@ -397,7 +388,7 @@ Utilisez le test `{ifset blockname}` pour vérifier si un bloc (ou plusieurs blo {/ifset} ``` -Vous pouvez utiliser une variable ou toute expression en PHP comme nom de bloc. Dans ce cas, ajoutez le mot-clé `block` devant la variable pour indiquer clairement que ce n'est pas la [variable |tags#ifset-elseifset] qui est vérifiée : +Le nom du bloc peut être une variable ou toute expression PHP. Dans ce cas, nous ajoutons le mot-clé `block` avant la variable pour qu'il soit clair qu'il ne s'agit pas d'un test d'existence de [variables |tags#ifset elseifset] : ```latte {ifset block $name} @@ -405,71 +396,69 @@ Vous pouvez utiliser une variable ou toute expression en PHP comme nom de bloc. {/ifset} ``` +L'existence des blocs est également vérifiée par la fonction [`hasBlock()` |functions#hasBlock] : -Conseils .[#toc-tips] ---------------------- -Voici quelques conseils pour travailler avec des blocs : - -- Le dernier bloc de premier niveau n'a pas besoin d'avoir de balise de fermeture (le bloc se termine à la fin du document). Cela simplifie l'écriture des modèles enfants, qui n'ont qu'un seul bloc primaire. +```latte +{if hasBlock(header) || hasBlock(footer)} + ... +{/if} +``` -- Pour plus de lisibilité, vous pouvez éventuellement donner un nom à votre balise `{/block}`, par exemple `{/block footer}`. Toutefois, ce nom doit correspondre au nom du bloc. Dans les modèles de grande taille, cette technique vous aide à voir quelles balises de bloc sont fermées. -- Vous ne pouvez pas définir directement plusieurs balises de bloc portant le même nom dans un même modèle. Mais vous pouvez y parvenir en utilisant des [noms de blocs dynamiques |#dynamic block names]. +Conseils +-------- +Quelques conseils pour travailler avec les blocs : -- Vous pouvez utiliser [n:attributes |syntax#n:attributes] pour définir des blocs tels que `

                                                                  Welcome to my awesome homepage

                                                                  ` +- Le dernier bloc de niveau supérieur n'a pas besoin d'avoir une balise de fermeture (le bloc se termine à la fin du document). Cela simplifie l'écriture des templates enfants qui contiennent un bloc principal unique. -- Les blocs peuvent également être utilisés sans nom uniquement pour appliquer les [filtres |syntax#filters] à la sortie : `{block|strip} hello {/block}` +- Pour une meilleure lisibilité, vous pouvez indiquer le nom du bloc dans la balise `{/block}`, par exemple `{/block footer}`. Cependant, le nom doit correspondre au nom du bloc. Dans les templates plus volumineux, cette technique vous aidera à voir quelles balises de bloc se ferment. +- Vous ne pouvez pas définir directement plusieurs balises de bloc avec le même nom dans le même template. Cependant, cela peut être réalisé en utilisant des [#noms de blocs dynamiques]. -Réutilisation horizontale `{import}` .{toc: Horizontal Reuse} -============================================================= +- Vous pouvez utiliser des [n:attributs |syntax#n:attributs] pour définir des blocs comme `

                                                                  Welcome to my awesome homepage

                                                                  ` -La réutilisation horizontale est un troisième mécanisme de réutilisation et d'héritage dans Latte. Elle vous permet de charger des blocs à partir d'autres modèles. C'est similaire à la création d'un fichier PHP avec des fonctions d'aide ou un trait. +- Les blocs peuvent également être utilisés sans nom uniquement pour appliquer des [filtres |syntax#Filtres] : `{block|strip} hello {/block}` -Bien que l'héritage de modèles soit l'une des fonctionnalités les plus puissantes de Latte, il est limité à un seul héritage ; un modèle ne peut étendre qu'un seul autre modèle. Cette limitation rend l'héritage de modèles simple à comprendre et facile à déboguer : -```latte -{layout 'layout.latte'} +Réutilisation horizontale `{import}` .{toc: Réutilisation horizontale} +====================================================================== -{block title}...{/block} -{block content}...{/block} -``` +La réutilisation horizontale est le troisième mécanisme de réutilisation et d'héritage dans Latte. Elle permet de charger des blocs à partir d'autres templates. C'est similaire à la création d'un fichier avec des fonctions d'aide en PHP, que nous chargeons ensuite à l'aide de `require`. -La réutilisation horizontale est un moyen d'atteindre le même objectif que l'héritage multiple, mais sans la complexité associée : +Bien que l'héritage de layout de template soit l'une des fonctionnalités les plus puissantes de Latte, il est limité à l'héritage simple - un template ne peut étendre qu'un seul autre template. La réutilisation horizontale est un moyen d'atteindre l'héritage multiple. -```latte -{layout 'layout.latte'} +Ayons un fichier avec des définitions de blocs : -{import 'blocks.latte'} +```latte .{file: blocks.latte} +{block sidebar}...{/block} -{block title}...{/block} -{block content}...{/block} +{block menu}...{/block} ``` -L'instruction `{import}` indique à Latte d'importer tous les blocs et [définitions |#definitions] définis dans `blocks.latte` dans le modèle actuel. +À l'aide de la commande `{import}`, nous importons tous les blocs et [#définitions] définis dans `blocks.latte` dans un autre template : -```latte -{* blocks.latte *} +```latte .{file: child.latte} +{import 'blocks.latte'} -{block sidebar}...{/block} +{* maintenant les blocs sidebar et menu peuvent être utilisés *} ``` -Dans cet exemple, l'instruction `{import}` importe le bloc `sidebar` dans le modèle principal. +Si vous importez des blocs dans le template parent (c'est-à-dire que vous utilisez `{import}` dans `layout.latte`), les blocs seront également disponibles dans tous les templates enfants, ce qui est très pratique. -Le modèle importé ne doit pas [étendre |#Layout Inheritance] un autre modèle, et son corps doit être vide. Toutefois, le modèle importé peut importer d'autres modèles. +Le template destiné à être importé (par exemple `blocks.latte`) ne doit pas [étendre |#Héritage de layout] un autre template, c'est-à-dire utiliser `{layout}`. Cependant, il peut importer d'autres templates. -La balise `{import}` doit être la première balise de modèle après `{layout}`. Le nom du modèle peut être une expression PHP quelconque : +La balise `{import}` doit être la première balise du template après `{layout}`. Le nom du template peut être n'importe quelle expression PHP : ```latte {import $ajax ? 'ajax.latte' : 'not-ajax.latte'} ``` -Vous pouvez utiliser autant d'instructions `{import}` que vous le souhaitez dans un modèle donné. Si deux modèles importés définissent le même bloc, le premier l'emporte. Toutefois, la plus haute priorité est accordée au modèle principal, qui peut écraser tout bloc importé. +Vous pouvez utiliser autant de commandes `{import}` que vous le souhaitez dans un template. Si deux templates importés définissent le même bloc, le premier l'emporte. Cependant, le template principal a la priorité la plus élevée et peut remplacer n'importe quel bloc importé. -Tous les blocs remplacés peuvent être inclus progressivement en les insérant comme [bloc parent |#parent block]: +Le contenu des blocs remplacés peut être préservé en insérant le bloc de la même manière que le [#bloc parent] est inséré : ```latte -{layout 'base.latte'} +{layout 'layout.latte'} {import 'blocks.latte'} @@ -481,17 +470,17 @@ Tous les blocs remplacés peuvent être inclus progressivement en les insérant {block content}...{/block} ``` -Dans cet exemple, `{include parent}` appellera correctement le bloc `sidebar` à partir du modèle `blocks.latte`. +Dans cet exemple, `{include parent}` appelle le bloc `sidebar` du template `blocks.latte`. -Héritage des unités `{embed}` .{toc: Unit Inheritance}{data-version:2.9} -======================================================================== +Héritage unitaire `{embed}` .{toc: Héritage unitaire} +===================================================== -L'héritage des unités reprend l'idée de l'héritage des mises en page au niveau des fragments de contenu. Alors que l'héritage de la mise en page fonctionne avec des "squelettes de documents", qui prennent vie grâce à des modèles enfants, l'héritage des unités vous permet de créer des squelettes pour de plus petites unités de contenu et de les réutiliser où vous le souhaitez. +L'héritage unitaire étend l'idée de l'héritage de layout au niveau des fragments de contenu. Alors que l'héritage de layout fonctionne avec le "squelette du document", qui est animé par les templates enfants, l'héritage unitaire vous permet de créer des squelettes pour des unités de contenu plus petites et de les réutiliser où vous le souhaitez. -Dans l'héritage d'unités, la balise `{embed}` est la clé. Elle combine le comportement de `{include}` et `{layout}`. Elle vous permet d'inclure le contenu d'un autre modèle ou bloc et de transmettre éventuellement des variables, comme le fait `{include}`. Elle vous permet également de remplacer tout bloc défini à l'intérieur du modèle inclus, comme le fait `{layout}`. +Dans l'héritage unitaire, la clé est la balise `{embed}`. Elle combine le comportement de `{include}` et `{layout}`. Elle permet d'insérer le contenu d'un autre template ou bloc et de passer éventuellement des variables, tout comme avec `{include}`. Elle permet également de remplacer n'importe quel bloc défini à l'intérieur du template inséré, comme lors de l'utilisation de `{layout}`. -Par exemple, nous allons utiliser l'élément accordéon pliable. Jetons un coup d'œil au squelette de l'élément dans le modèle `collapsible.latte`: +Par exemple, utilisons un élément accordéon. Regardons le squelette de l'élément stocké dans le template `collapsible.latte` : ```latte
                                                                  @@ -505,9 +494,9 @@ Par exemple, nous allons utiliser l'élément accordéon pliable. Jetons un coup
                                                                  ``` -Les balises `{block}` définissent deux blocs que les modèles enfants peuvent remplir. Oui, comme dans le cas du modèle parent dans le modèle d'héritage de mise en page. Vous voyez aussi la variable `$modifierClass`. +Les balises `{block}` définissent deux blocs que les templates enfants peuvent remplir. Oui, comme dans le cas du template parent dans l'héritage de layout. Vous voyez également la variable `$modifierClass`. -Utilisons notre élément dans le modèle. C'est là que `{embed}` entre en jeu. Il s'agit d'un kit super puissant qui nous permet de tout faire : inclure le contenu du modèle de l'élément, y ajouter des variables et y ajouter des blocs avec du HTML personnalisé : +Utilisons notre élément dans un template. C'est là qu'intervient `{embed}`. C'est une balise extrêmement puissante qui nous permet de faire toutes ces choses : insérer le contenu du template de l'élément, y ajouter des variables et y ajouter des blocs avec notre propre HTML : ```latte {embed 'collapsible.latte', modifierClass: my-style} @@ -522,7 +511,7 @@ Utilisons notre élément dans le modèle. C'est là que `{embed}` entre en jeu. {/embed} ``` -Le résultat pourrait ressembler à ça : +La sortie peut ressembler à ceci : ```latte
                                                                  @@ -537,7 +526,7 @@ Le résultat pourrait ressembler à ça :
                                                                  ``` -Les blocs à l'intérieur des balises d'intégration forment une couche distincte indépendante des autres blocs. Par conséquent, ils peuvent avoir le même nom que le bloc à l'extérieur de la balise embed et ne sont en aucun cas affectés. En utilisant la balise [include |#Printing Blocks] à l'intérieur des balises `{embed}`, vous pouvez insérer des blocs créés ici, des blocs du modèle incorporé (qui *ne sont pas* [locaux |#Local Blocks]), ainsi que des blocs du modèle principal qui *sont* locaux. Vous pouvez également [importer des blocs |#Horizontal Reuse] à partir d'autres fichiers : +Les blocs à l'intérieur des balises insérées forment une couche distincte indépendante des autres blocs. Par conséquent, ils peuvent avoir le même nom qu'un bloc en dehors de l'insertion et ne sont en aucun cas affectés. En utilisant la balise [include |#Rendu des blocs] à l'intérieur des balises `{embed}`, vous pouvez insérer les blocs créés ici, les blocs du template inséré (qui ne sont *pas* [locaux |#Blocs locaux]) ainsi que les blocs du template principal qui, au contraire, *sont* locaux. Vous pouvez également [importer des blocs |#Réutilisation horizontale] à partir d'autres fichiers : ```latte {block outer}…{/block} @@ -549,18 +538,18 @@ Les blocs à l'intérieur des balises d'intégration forment une couche distinct {block inner}…{/block} {block title} - {include inner} {* fonctionne, le bloc est défini dans l'inclusion *} - {include hello} {* fonctionne, le bloc est local dans ce modèle *} - {include content} {* fonctionne, le bloc est défini dans le modèle intégré *} + {include inner} {* fonctionne, le bloc est défini à l'intérieur de embed *} + {include hello} {* fonctionne, le bloc est local dans ce template *} + {include content} {* fonctionne, le bloc est défini dans le template inséré *} {include aBlockDefinedInImportedTemplate} {* fonctionne *} {include outer} {* ne fonctionne pas ! - le bloc est dans la couche externe *} {/block} {/embed} ``` -Les modèles intégrés n'ont pas accès aux variables du contexte actif, mais ils ont accès aux variables globales. +Les templates insérés n'ont pas accès aux variables du contexte actif, mais ils ont accès aux variables globales. -Avec `{embed}` vous pouvez insérer non seulement des modèles mais aussi d'autres blocs, ainsi l'exemple précédent pourrait être écrit comme ceci : .{data-version:2.10} +Avec `{embed}`, on peut insérer non seulement des templates, mais aussi d'autres blocs, et donc l'exemple précédent pourrait être écrit de cette manière : ```latte {define collapsible} @@ -581,23 +570,23 @@ Avec `{embed}` vous pouvez insérer non seulement des modèles mais aussi d'autr {/embed} ``` -Si nous passons une expression à `{embed}` et qu'il n'est pas clair s'il s'agit d'un bloc ou d'un nom de fichier, ajoutez le mot-clé `block` ou `file`: +Si nous passons une expression à `{embed}` et qu'il n'est pas clair s'il s'agit d'un nom de bloc ou de fichier, nous ajoutons le mot-clé `block` ou `file` : ```latte {embed block $name} ... {/embed} ``` -Cas d'utilisation .[#toc-use-cases] -=================================== +Cas d'utilisation +================= -Il existe différents types d'héritage et de réutilisation du code dans Latte. Résumons les principaux concepts pour plus de clarté : +Dans Latte, il existe différents types d'héritage et de réutilisation de code. Résumons les concepts principaux pour une meilleure clarté : `{include template}` -------------------- -**Use Case:** Utilisation de `header.latte` & `footer.latte` dans `layout.latte`. +**Cas d'utilisation**: Utilisation de `header.latte` et `footer.latte` à l'intérieur de `layout.latte`. `header.latte` @@ -630,7 +619,7 @@ Il existe différents types d'héritage et de réutilisation du code dans Latte. `{layout}` ---------- -**Cas d'utilisation** : Extension de `layout.latte` à l'intérieur de `homepage.latte` & `about.latte`. +**Cas d'utilisation**: Extension de `layout.latte` à l'intérieur de `homepage.latte` et `about.latte`. `layout.latte` @@ -666,7 +655,7 @@ Il existe différents types d'héritage et de réutilisation du code dans Latte. `{import}` ---------- -**Cas d'utilisation** : `sidebar.latte` dans `single.product.latte` & `single.service.latte`. +**Cas d'utilisation**: `sidebar.latte` dans `single.product.latte` et `single.service.latte`. `sidebar.latte` @@ -698,7 +687,7 @@ Il existe différents types d'héritage et de réutilisation du code dans Latte. `{define}` ---------- -**Cas d'utilisation** : Une fonction qui récupère des variables et produit des balises. +**Cas d'utilisation**: Fonctions auxquelles on passe des variables et qui affichent quelque chose. `form.latte` @@ -724,7 +713,7 @@ Il existe différents types d'héritage et de réutilisation du code dans Latte. `{embed}` --------- -**Cas d'utilisation** : Incorporation de `pagination.latte` dans `product.table.latte` & `service.table.latte`. +**Cas d'utilisation**: Insertion de `pagination.latte` dans `product.table.latte` et `service.table.latte`. `pagination.latte` diff --git a/latte/fr/type-system.texy b/latte/fr/type-system.texy index 83d12780af..0cac1c1ac0 100644 --- a/latte/fr/type-system.texy +++ b/latte/fr/type-system.texy @@ -1,27 +1,27 @@ -Système d'identification -************************ +Système de types +**************** -
                                                                  +
                                                                  -Le système de types est un élément essentiel pour le développement d'applications robustes. Latte apporte le support des types aux modèles. Savoir quel type de données ou d'objet est chaque variable permet de +Le système de types est crucial pour le développement d'applications robustes. Latte apporte également la prise en charge des types dans les templates. Grâce au fait que nous savons quel type de données ou d'objet se trouve dans chaque variable, -- à l'IDE d'autocompléter correctement (voir [intégration et plugins |recipes#Editors and IDE]) -- analyse statique pour détecter les erreurs +- l'IDE peut correctement suggérer (voir [intégration |recipes#Éditeurs et IDE]) +- l'analyse statique peut détecter les erreurs -Deux points qui améliorent considérablement la qualité et la commodité du développement. +Les deux augmentent considérablement la qualité et le confort du développement.
                                                                  .[note] -Les types déclarés sont informatifs et Latte ne les vérifie pas pour l'instant. +Les types déclarés sont informatifs et Latte ne les vérifie pas pour le moment. -Comment commencer à utiliser les types ? Créez une classe modèle, par exemple `CatalogTemplateParameters`, représentant les paramètres passés : +Comment commencer à utiliser les types ? Créez une classe de template, par exemple `CatalogTemplateParameters`, représentant les paramètres passés, leurs types et éventuellement leurs valeurs par défaut : ```php class CatalogTemplateParameters { public function __construct( - public string $langs, + public string $lang, /** @var ProductEntity[] */ public array $products, public Address $address, @@ -35,19 +35,16 @@ $latte->render('template.latte', new CatalogTemplateParameters( )); ``` -Insérez ensuite la balise `{templateType}` avec le nom complet de la classe (y compris l'espace de nom) au début du modèle. Cela définit qu'il y a des variables `$langs` et `$products` dans le modèle, y compris les types correspondants. -Vous pouvez également spécifier les types des variables locales en utilisant les balises [`{var}` |tags#var-default], `{varType}` et [`{define}` |template-inheritance#definitions]. +Ensuite, au début du template, insérez la balise `{templateType}` avec le nom complet de la classe (y compris le namespace). Cela définit que les variables `$lang` et `$products` existent dans le template, y compris leurs types respectifs. Vous pouvez spécifier les types des variables locales à l'aide des balises [`{var}` |tags#var default], `{varType}`, [`{define}` |template-inheritance#Définitions]. -L'IDE peut maintenant effectuer une autocomplétion correcte. +À partir de ce moment, l'IDE peut correctement vous faire des suggestions. -Comment sauvegarder le travail ? Comment écrire une classe modèle ou des balises `{varType}` le plus facilement possible ? Les faire générer. -C'est précisément ce que font la paire de balises `{templatePrint}` et `{varPrint}`. -Si vous placez l'une de ces balises dans un modèle, le code de la classe ou du modèle s'affiche au lieu du rendu normal. Il suffit ensuite de sélectionner et de copier le code dans votre projet. +Comment économiser du travail ? Quelle est la manière la plus simple d'écrire une classe avec des paramètres de template ou des balises `{varType}` ? Laissez-les être générés. C'est à cela que servent les deux balises `{templatePrint}` et `{varPrint}`. Si vous les placez dans un template, au lieu du rendu normal, une proposition de code de classe ou une liste de balises `{varType}` s'affichera. Il suffit ensuite de sélectionner le code d'un clic et de le copier dans votre projet. `{templateType}` ---------------- -Les types de paramètres passés au modèle sont déclarés à l'aide de la classe : +Nous déclarons les types des paramètres passés au template à l'aide d'une classe : ```latte {templateType MyApp\CatalogTemplateParameters} @@ -56,7 +53,7 @@ Les types de paramètres passés au modèle sont déclarés à l'aide de la clas `{varType}` ----------- -Comment déclarer les types de variables ? Pour cela, utiliser la balise `{varType}` pour une variable existante, ou bien [`{var}` |tags#var-default]: +Comment déclarer les types de variables ? Pour cela, utilisez les balises `{varType}` pour les variables existantes, ou [`{var}` |tags#var default] : ```latte {varType Nette\Security\User $user} @@ -66,11 +63,11 @@ Comment déclarer les types de variables ? Pour cela, utiliser la balise `{varTy `{templatePrint}` ----------------- -Vous pouvez également générer cette classe à l'aide de la balise `{templatePrint}`. Si vous la placez au début du modèle, le code de la classe s'affiche à la place du modèle normal. Il suffit ensuite de sélectionner et de copier le code dans votre projet. +Vous pouvez également faire générer la classe à l'aide de la balise `{templatePrint}`. Si vous la placez au début du template, au lieu du rendu normal, une proposition de classe s'affichera. Il suffit ensuite de sélectionner le code d'un clic et de le copier dans votre projet. `{varPrint}` ------------ -La balise `{varPrint}` vous permet de gagner du temps. Si vous la placez dans un modèle, la liste des balises `{varType}` s'affiche au lieu du rendu normal. Il suffit ensuite de sélectionner et de copier le code dans votre modèle. +La balise `{varPrint}` vous fera gagner du temps d'écriture. Si vous la placez dans le template, au lieu du rendu normal, une proposition de balises `{varType}` pour les variables locales s'affichera. Il suffit ensuite de sélectionner le code d'un clic et de le copier dans le template. -La balise `{varPrint}` liste les variables locales qui ne sont pas des paramètres du modèle. Si vous voulez lister toutes les variables, utilisez `{varPrint all}`. +`{varPrint}` seul n'affiche que les variables locales qui ne sont pas des paramètres de template. Si vous souhaitez afficher toutes les variables, utilisez `{varPrint all}`. diff --git a/latte/fr/why-use.texy b/latte/fr/why-use.texy new file mode 100644 index 0000000000..c648da93d6 --- /dev/null +++ b/latte/fr/why-use.texy @@ -0,0 +1,80 @@ +Pourquoi utiliser des templates ? +********************************* + + +Pourquoi devrais-je utiliser un système de templates en PHP ? +------------------------------------------------------------- + +Pourquoi utiliser un système de templates en PHP, alors que PHP est lui-même un langage de template ? + +Commençons par résumer brièvement l'histoire de ce langage, qui est pleine de rebondissements intéressants. L'un des premiers langages de programmation utilisés pour générer des pages HTML était le langage C. Cependant, il s'est vite avéré que son utilisation à cette fin était peu pratique. Rasmus Lerdorf a donc créé PHP, qui facilitait la génération de HTML dynamique avec le langage C en backend. PHP a donc été initialement conçu comme un langage de template, mais au fil du temps, il a acquis d'autres fonctionnalités et est devenu un langage de programmation à part entière. + +Pourtant, il fonctionne toujours aussi comme un langage de template. Un fichier PHP peut contenir une page HTML dans laquelle des variables sont affichées à l'aide de ``, etc. + +Dès les débuts de l'histoire de PHP, le système de templates Smarty a été créé, dont le but était de séparer strictement l'apparence (HTML/CSS) de la logique applicative. Il fournissait donc intentionnellement un langage plus limité que PHP lui-même, afin que le développeur ne puisse pas, par exemple, exécuter une requête de base de données depuis un template, etc. D'un autre côté, il représentait une dépendance supplémentaire dans les projets, augmentait leur complexité et les programmeurs devaient apprendre un nouveau langage Smarty. Un tel avantage était discutable et PHP simple continuait d'être utilisé pour les templates. + +Au fil du temps, les systèmes de templates ont commencé à devenir utiles. Ils ont introduit le concept d'[héritage |template-inheritance], le [mode sandbox|sandbox] et un certain nombre d'autres fonctionnalités qui ont considérablement simplifié la création de templates par rapport à PHP pur. Le sujet de la sécurité, l'existence de [vulnérabilités comme XSS|safety-first] et la nécessité d'[échappement |#Qu est-ce que l échappement] sont passés au premier plan. Les systèmes de templates ont introduit l'auto-échappement pour éliminer le risque que le programmeur l'oublie et crée une faille de sécurité grave (nous verrons dans un instant que cela présente certains écueils). + +Les avantages des systèmes de templates dépassent aujourd'hui largement les coûts associés à leur déploiement. C'est pourquoi il est judicieux de les utiliser. + + +Pourquoi Latte est-il meilleur que, par exemple, Twig ou Blade ? +---------------------------------------------------------------- + +Il y a plusieurs raisons – certaines sont agréables et d'autres fondamentalement utiles. Latte est une combinaison de l'agréable et de l'utile. + +*D'abord l'agréable :* Latte a la même [syntaxe que PHP |syntax#Latte comprend PHP]. Seule la notation des balises diffère, au lieu de ``, il préfère les plus courtes `{` et `}`. Cela signifie que vous n'avez pas besoin d'apprendre un nouveau langage. Les coûts de formation sont minimes. Et surtout, pendant le développement, vous n'avez pas besoin de "basculer" constamment entre le langage PHP et le langage de template, car ils sont tous les deux identiques. Contrairement aux templates Twig, qui utilisent la syntaxe de Python, et le programmeur doit ainsi basculer entre deux langages différents. + +*Et maintenant la raison extrêmement utile* : Tous les systèmes de templates, tels que Twig, Blade ou Smarty, ont introduit au cours de leur évolution une protection contre XSS sous la forme d'un [échappement |#Qu est-ce que l échappement] automatique. Plus précisément, l'appel automatique de la fonction `htmlspecialchars()`. Cependant, les créateurs de Latte ont réalisé que ce n'était pas du tout la bonne solution. Parce que l'échappement se fait de différentes manières à différents endroits du document. L'auto-échappement naïf est une fonction dangereuse car il crée un faux sentiment de sécurité. + +Pour que l'auto-échappement soit fonctionnel et fiable, il doit reconnaître où les données sont affichées dans le document (nous les appelons contextes) et choisir la fonction d'échappement en conséquence. Il doit donc être [sensible au contexte |safety-first#Échappement contextuel]. Et c'est exactement ce que fait Latte. Il comprend le HTML. Il ne perçoit pas le template uniquement comme une chaîne de caractères, mais comprend ce que sont les balises, les attributs, etc. Et c'est pourquoi il échappe différemment dans le texte HTML, différemment à l'intérieur d'une balise HTML, différemment à l'intérieur de JavaScript, etc. + +Latte est le premier et le seul système de templates en PHP à disposer d'un échappement sensible au contexte. Il représente ainsi le seul système de templates vraiment sécurisé. + +*Et une autre raison agréable* : Grâce au fait que Latte comprend le HTML, il offre d'autres fonctionnalités très agréables. Par exemple, les [n:attributs |syntax#n:attributs]. Ou la capacité de [vérifier les liens |safety-first#Vérification des liens]. Et bien d'autres. + + +Qu'est-ce que l'échappement ? +----------------------------- + +L'échappement est le processus qui consiste à remplacer les caractères ayant une signification spéciale par des séquences correspondantes lors de l'insertion d'une chaîne dans une autre, afin d'éviter des phénomènes indésirables ou des erreurs. Par exemple, lorsque nous insérons une chaîne dans du texte HTML, où le caractère `<` a une signification spéciale car il marque le début d'une balise, nous le remplaçons par la séquence correspondante, qui est l'entité HTML `<`. Grâce à cela, le navigateur affichera correctement le symbole `<`. + +Un exemple simple d'échappement directement lors de l'écriture de code en PHP est l'insertion d'un guillemet dans une chaîne, où nous le faisons précéder d'une barre oblique inverse. + +Nous discutons plus en détail de l'échappement dans le chapitre [Comment se défendre contre XSS |safety-first#Comment se défendre contre XSS]. + + +Peut-on exécuter une requête de base de données depuis un template en Latte ? +----------------------------------------------------------------------------- + +Dans les templates, il est possible de travailler avec des objets que le programmeur leur transmet. Si le programmeur le souhaite, il peut donc transmettre un objet de base de données au template et exécuter une requête dessus. S'il a une telle intention, il n'y a aucune raison de l'en empêcher. + +Une situation différente se présente si vous souhaitez donner la possibilité d'éditer les templates aux clients ou aux codeurs externes. Dans ce cas, vous ne voulez certainement pas qu'ils aient accès à la base de données. Bien sûr, vous ne transmettrez pas l'objet de base de données au template, mais que se passe-t-il s'il est possible d'y accéder via un autre objet ? La solution est le [mode sandbox|sandbox], qui permet de définir quelles méthodes peuvent être appelées dans les templates. Grâce à cela, vous n'avez pas à craindre une violation de la sécurité. + + +Quelles sont les principales différences entre les systèmes de templates comme Latte, Twig et Blade ? +----------------------------------------------------------------------------------------------------- + +Les différences entre les systèmes de templates Latte, Twig et Blade résident principalement dans la syntaxe, la sécurité et la manière d'intégration dans les frameworks + +- Latte : utilise la syntaxe du langage PHP, ce qui facilite l'apprentissage et l'utilisation. Il offre une protection de pointe contre les attaques XSS grâce à son échappement sensible au contexte. +- Twig : utilise une syntaxe inspirée de Python, qui diffère de PHP. Il échappe sans distinction de contexte. Il est bien intégré au framework Symfony. +- Blade : utilise un mélange de PHP et de sa propre syntaxe. Il échappe sans distinction de contexte. Il est étroitement intégré aux fonctions et à l'écosystème de Laravel. + + +Est-il rentable pour les entreprises d'utiliser un système de templates ? +------------------------------------------------------------------------- + +Tout d'abord, les coûts associés à la formation, à l'utilisation et à l'avantage global varient considérablement selon le système. Le système de templates Latte, grâce à son utilisation de la syntaxe PHP, simplifie grandement l'apprentissage pour les programmeurs déjà familiarisés avec ce langage. Il faut généralement quelques heures pour qu'un programmeur se familiarise suffisamment avec Latte. Il réduit donc les coûts de formation. En même temps, il accélère l'adoption de la technologie et surtout l'efficacité de l'utilisation quotidienne. + +De plus, Latte offre un haut niveau de protection contre la vulnérabilité XSS grâce à sa technologie unique d'échappement sensible au contexte. Cette protection est cruciale pour assurer la sécurité des applications web et minimiser le risque d'attaques qui pourraient menacer les utilisateurs ou les données de l'entreprise. La protection de la sécurité des applications web est également importante pour maintenir la bonne réputation de l'entreprise. Les problèmes de sécurité peuvent entraîner une perte de confiance de la part des clients et nuire à la réputation de l'entreprise sur le marché. + +L'utilisation de Latte réduit également les coûts globaux de développement et de maintenance de l'application en facilitant les deux. L'utilisation d'un système de templates est donc clairement rentable. + + +Latte affecte-t-il les performances des applications web ? +---------------------------------------------------------- + +Bien que les templates Latte soient traités rapidement, cet aspect n'a en fait pas d'importance. La raison en est que l'analyse des fichiers ne se produit qu'une seule fois lors du premier affichage. Ensuite, ils sont compilés en code PHP, stockés sur le disque et exécutés à chaque requête ultérieure, sans qu'il soit nécessaire d'effectuer une nouvelle compilation. + +C'est ainsi que cela fonctionne dans un environnement de production. Pendant le développement, les templates Latte sont recompilés chaque fois que leur contenu est modifié, afin que le développeur voie toujours la version actuelle. diff --git a/latte/hu/@home.texy b/latte/hu/@home.texy index 000326ab7e..cd4a77c8b7 100644 --- a/latte/hu/@home.texy +++ b/latte/hu/@home.texy @@ -1 +1,2 @@ -{{maintitle: Latte - A legbiztonságosabb és igazán intuitív sablonok PHP-hoz}} +{{maintitle: Latte – a legbiztonságosabb & igazán intuitív sablonok PHP-hoz}} +{{description: A Latte a legbiztonságosabb sablonrendszer PHP-hoz. Megakadályozza számos biztonsági rés kihasználását. Értékelni fogja intuitív szintaxisát és számos hasznos funkcióját.}} diff --git a/latte/hu/@left-menu.texy b/latte/hu/@left-menu.texy index bb5bc21b48..623fa91b26 100644 --- a/latte/hu/@left-menu.texy +++ b/latte/hu/@left-menu.texy @@ -1,24 +1,24 @@ -- [Kezdő lépések |Guide] -- Fogalmak - - [Első a biztonság |Safety First] +- [Ismerkedés a Latte-val |guide] +- [Miért használjunk sablonokat? |why-use] +- Koncepciók ⚗️ + - [Biztonság mindenekelőtt |safety-first] - [Sablon öröklődés |Template Inheritance] - - [Típus rendszer |Type System] - - [Homokozó |Sandbox] + - [Típusrendszer |type-system] + - [Sandbox |Sandbox] -- Tervezőknek - - [Szintaxis |Syntax] - - [Címkék |Tags] - - [Szűrők |Filters] - - [Funkciók |Functions] +- Tervezőknek 🎨 + - [Szintaxis |syntax] + - [Tagek |tags] + - [Szűrők |filters] + - [Függvények |functions] - [Tippek és trükkök |recipes] -- Fejlesztőknek - - [Gyakorlatok fejlesztőknek |develop] - - [Latte bővítése |Extending Latte] - - [Bővítmény létrehozása |creating-extension] +- Fejlesztőknek 🧮 + - [Fejlesztői eljárások |develop] + - [A Latte bővítése |extending-latte] -- [Szakácskönyv |cookbook/@home] - - [Migráció a Twig-ről |cookbook/migration-from-twig] - - [... tovább |cookbook/@home] +- [Útmutatók és eljárások 💡|cookbook/@home] + - [Migráció Twig-ből |cookbook/migration-from-twig] + - [… továbbiak |cookbook/@home] - "Játszótér .[link-external]":https://fiddle.nette.org/latte/ .{padding-top:1em} diff --git a/latte/hu/@menu.texy b/latte/hu/@menu.texy index 803264f6d8..440653c9f5 100644 --- a/latte/hu/@menu.texy +++ b/latte/hu/@menu.texy @@ -1,12 +1,12 @@
                                                                    -- [Otthon |@home] -- [Dokumentáció |Guide] +- [Bevezetés |@home] +- [Dokumentáció |guide] - "GitHub .[link-external]":https://github.com/nette/latte
                                                                  diff --git a/latte/hu/@meta.texy b/latte/hu/@meta.texy new file mode 100644 index 0000000000..e9ee4e35c0 --- /dev/null +++ b/latte/hu/@meta.texy @@ -0,0 +1 @@ +{{sitename: Latte dokumentáció}} diff --git a/latte/hu/compiler-passes.texy b/latte/hu/compiler-passes.texy new file mode 100644 index 0000000000..a5c96226e7 --- /dev/null +++ b/latte/hu/compiler-passes.texy @@ -0,0 +1,555 @@ +Fordítási menetek +***************** + +.[perex] +A fordítási menetek hatékony mechanizmust biztosítanak a Latte sablonok elemzésére és módosítására *miután* azokat absztrakt szintaxisfává (AST) elemezték, és *mielőtt* a végső PHP kódot generálnák. Ez lehetővé teszi a sablonok fejlett manipulálását, optimalizálását, biztonsági ellenőrzését (mint például a [Sandbox |sandbox]) és a sablonokkal kapcsolatos információk gyűjtését. Ez az útmutató végigvezeti Önt saját fordítási menetek létrehozásán. + + +Mi az a fordítási menet? +======================== + +A fordítási menetek szerepének megértéséhez tekintse meg a [Latte fordítási folyamatát |custom-tags#A fordítási folyamat megértése]. Amint láthatja, a fordítási menetek kulcsfontosságú szakaszban működnek, lehetővé téve a mély beavatkozást a kezdeti elemzés és a végső kódkimenet között. + +Lényegében a fordítási menet egyszerűen egy PHP [callable |php:language.types.callable] objektum (mint egy függvény, statikus metódus vagy példány metódus), amely egyetlen argumentumot fogad el: a sablon AST gyökércsomópontját, amely mindig a `Latte\Compiler\Nodes\TemplateNode` egy példánya. + +A fordítási menet elsődleges célja általában az alábbiak közül egy vagy mindkettő: + +- Elemzés: Az AST bejárása és információk gyűjtése a sablonról (pl. az összes definiált blokk megtalálása, specifikus tagek használatának ellenőrzése, bizonyos biztonsági korlátozások teljesülésének biztosítása). +- Módosítás: Az AST struktúrájának vagy a csomópontok attribútumainak megváltoztatása (pl. HTML attribútumok automatikus hozzáadása, bizonyos tag kombinációk optimalizálása, elavult tagek lecserélése újakra, sandbox szabályok implementálása). + + +Regisztráció +============ + +A fordítási meneteket a [bővítmény |extending-latte#getPasses] `getPasses()` metódusával regisztráljuk. Ez a metódus egy asszociatív tömböt ad vissza, ahol a kulcsok a menetek egyedi nevei (belsőleg és a sorrendezéshez használatosak), az értékek pedig a menet logikáját implementáló PHP [callable |php:language.types.callable] objektumok. + +```php +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Extension; + +class MyExtension extends Extension +{ + public function getPasses(): array + { + return [ + 'modificationPass' => $this->modifyTemplateAst(...), + // ... további menetek ... + ]; + } + + public function modifyTemplateAst(TemplateNode $templateNode): void + { + // Implementáció... + } +} +``` + +A Latte alap bővítményei és a saját bővítményei által regisztrált menetek szekvenciálisan futnak le. A sorrend fontos lehet, különösen, ha egy menet egy másik eredményeitől vagy módosításaitól függ. A Latte segédmechanizmust biztosít ennek a sorrendnek a szabályozására, ha szükséges; lásd a [`Extension::getPasses()` |extending-latte#getPasses] dokumentációját a részletekért. + + +AST példa +========= + +Az AST jobb megértése érdekében hozzáadunk egy példát. Ez a forrás sablon: + +```latte +{foreach $category->getItems() as $item} +
                                                                • {$item->name|upper}
                                                                • + {else} + nincsenek elemek +{/foreach} +``` + +És ez annak reprezentációja AST formájában: + +/--pre +Latte\Compiler\Nodes\TemplateNode( + Latte\Compiler\Nodes\FragmentNode( + - Latte\Essential\Nodes\ForeachNode( + expression: Latte\Compiler\Nodes\Php\Expression\MethodCallNode( + object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$category') + name: Latte\Compiler\Nodes\Php\IdentifierNode('getItems') + ) + value: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') + content: Latte\Compiler\Nodes\FragmentNode( + - Latte\Compiler\Nodes\TextNode(' ') + - Latte\Compiler\Nodes\Html\ElementNode('li')( + content: Latte\Essential\Nodes\PrintNode( + expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode( + object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') + name: Latte\Compiler\Nodes\Php\IdentifierNode('name') + ) + modifier: Latte\Compiler\Nodes\Php\ModifierNode( + filters: + - Latte\Compiler\Nodes\Php\FilterNode('upper') + ) + ) + ) + ) + else: Latte\Compiler\Nodes\FragmentNode( + - Latte\Compiler\Nodes\TextNode('nincsenek elemek') + ) + ) + ) +) +\-- + + +AST bejárása a `NodeTraverser` segítségével +=========================================== + +Rekurzív függvények kézi írása az AST komplex struktúrájának bejárásához fárasztó és hibára hajlamos. A Latte speciális eszközt biztosít erre a célra: [api:Latte\Compiler\NodeTraverser]. Ez az osztály a [Visitor tervezési mintát |https://en.wikipedia.org/wiki/Visitor_pattern] implementálja, amelynek köszönhetően az AST bejárása szisztematikus és könnyen kezelhető. + +Az alapvető használat magában foglalja egy `NodeTraverser` példány létrehozását és annak `traverse()` metódusának meghívását, átadva az AST gyökércsomópontját és egy vagy két "visitor" [callable |php:language.types.callable] objektumot: + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; + +(new NodeTraverser)->traverse( + $templateNode, + + // 'enter' visitor: Csomópontba lépéskor hívódik meg (a gyermekei előtt) + enter: function (Node $node) { + echo "Belépés a csomópontba, típusa: " . $node::class . "\n"; + // Itt vizsgálhatja a csomópontot + if ($node instanceof Nodes\TextNode) { + // echo "Talált szöveg: " . $node->content . "\n"; + } + }, + + // 'leave' visitor: Csomópont elhagyásakor hívódik meg (a gyermekei után) + leave: function (Node $node) { + echo "Kilépés a csomópontból, típusa: " . $node::class . "\n"; + // Itt végezhet műveleteket a gyermekek feldolgozása után + }, +); +``` + +Megadhat csak `enter` visitort, csak `leave` visitort, vagy mindkettőt, az igényeitől függően. + +**`enter(Node $node)`:** Ez a függvény minden csomópontra végrehajtódik **mielőtt** a bejáró meglátogatná a csomópont bármely gyermekét. Hasznos a következőkre: + +- Információgyűjtés a fa lefelé történő bejárása során. +- Döntéshozatal *mielőtt* a gyermekeket feldolgozná (például döntés azok kihagyásáról, lásd [#Bejárás optimalizálása]). +- A csomópont potenciális módosítása a gyermekek meglátogatása előtt (ritkábban). + +**`leave(Node $node)`:** Ez a függvény minden csomópontra végrehajtódik **miután** az összes gyermeke (és azok teljes aláfája) teljesen meglátogatásra került (mind belépés, mind kilépés). Ez a leggyakoribb hely a következőkre: + +Mind az `enter`, mind a `leave` visitor opcionálisan visszaadhat egy értéket a bejárási folyamat befolyásolására. A `null` (vagy semmi) visszaadása normálisan folytatja a bejárást, egy `Node` példány visszaadása lecseréli az aktuális csomópontot, és speciális konstansok, mint a `NodeTraverser::RemoveNode` vagy `NodeTraverser::StopTraversal` visszaadása módosítja a folyamatot, ahogy azt a következő szakaszokban kifejtjük. + + +Hogyan működik a bejárás +------------------------ + +A `NodeTraverser` belsőleg a `getIterator()` metódust használja, amelyet minden `Node` osztálynak implementálnia kell (ahogy azt az [Egyéni tagek létrehozása |custom-tags#A getIterator implementálása gyermek csomópontokhoz] részben tárgyaltuk). Iterál a `getIterator()` által kapott gyermekeken, rekurzívan meghívja rajtuk a `traverse()`-t, és biztosítja, hogy az `enter` és `leave` visitorok a megfelelő mélységi-első sorrendben legyenek meghívva minden, az iterátorokon keresztül elérhető csomópontra a fában. Ez újra hangsúlyozza, miért elengedhetetlen a saját tag csomópontjaiban a helyesen implementált `getIterator()` a fordítási menetek megfelelő működéséhez. + +Írjunk egy egyszerű menetet, amely megszámolja, hányszor használják a `{do}` taget (amelyet a `Latte\Essential\Nodes\DoNode` reprezentál) a sablonban. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Essential\Nodes\DoNode; + +function countDoTags(TemplateNode $templateNode): void +{ + $count = 0; + (new NodeTraverser)->traverse( + $templateNode, + enter: function (Node $node) use (&$count): void { + if ($node instanceof DoNode) { + $count++; + } + }, + // 'leave' visitor nem szükséges ehhez a feladathoz + ); + + echo "A {do} tag $count alkalommal található meg.\n"; +} + +$latte = new Latte\Engine; +$ast = $latte->parse($templateSource); +countDoTags($ast); +``` + +Ebben a példában csak az `enter` visitorra volt szükségünk, hogy ellenőrizzük minden meglátogatott csomópont típusát. + +Ezután megvizsgáljuk, hogyan módosítják ezek a visitorok valójában az AST-t. + + +AST módosítása +============== + +A fordítási menetek egyik fő célja az absztrakt szintaxisfa módosítása. Ez lehetővé teszi erőteljes átalakításokat, optimalizálásokat vagy szabályok kikényszerítését közvetlenül a sablon struktúráján, mielőtt PHP kódot generálnánk. A `NodeTraverser` több módot kínál ennek elérésére az `enter` és `leave` visitorokon belül. + +**Fontos megjegyzés:** Az AST módosítása óvatosságot igényel. A helytelen változtatások – mint például alapvető csomópontok eltávolítása vagy egy csomópont nem kompatibilis típussal való helyettesítése – hibákhoz vezethetnek a kódgenerálás során, vagy váratlan viselkedést okozhatnak a program futása közben. Mindig alaposan tesztelje a módosító meneteit. + + +Csomópont tulajdonságainak módosítása +------------------------------------- + +A fa módosításának legegyszerűbb módja a bejárás során meglátogatott csomópontok **nyilvános property-jeinek** közvetlen megváltoztatása. Minden csomópont az elemzett argumentumait, tartalmát vagy attribútumait nyilvános property-kben tárolja. + +**Példa:** Hozzunk létre egy menetet, amely megtalálja az összes statikus szöveges csomópontot (`TextNode`, amely a Latte tageken kívüli normál HTML-t vagy szöveget reprezentálja), és átalakítja a tartalmukat nagybetűssé *közvetlenül az AST-ben*. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\TextNode; + +function uppercaseStaticText(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // Használhatjuk az 'enter'-t, mivel a TextNode-nak nincsenek feldolgozandó gyermekei + enter: function (Node $node) { + // Ez a csomópont egy statikus szövegblokk? + if ($node instanceof TextNode) { + // Igen! Közvetlenül módosítjuk a 'content' nyilvános property-jét. + $node->content = mb_strtoupper(html_entity_decode($node->content)); + } + // Nincs szükség semmit visszaadni; a változás közvetlenül alkalmazódik. + }, + ); +} +``` + +Ebben a példában az `enter` visitor ellenőrzi, hogy az aktuális `$node` `TextNode` típusú-e. Ha igen, közvetlenül frissítjük a `$content` nyilvános property-jét a `mb_strtoupper()` segítségével. Ez közvetlenül megváltoztatja az AST-ben tárolt statikus szöveg tartalmát *mielőtt* PHP kódot generálnánk. Mivel közvetlenül az objektumot módosítjuk, nem kell semmit visszaadnunk a visitorból. + +Hatás: Ha a sablon `

                                                                  Hello

                                                                  {= $var }World` tartalmazott, ezen menet után az AST valami ilyesmit fog reprezentálni: `

                                                                  HELLO

                                                                  {= $var }WORLD`. Ez NEM befolyásolja a `$var` tartalmát. + + +Csomópontok cseréje +------------------- + +Egy erőteljesebb módosítási technika egy csomópont teljes lecserélése egy másikkal. Ezt úgy végezzük, hogy **egy új `Node` példányt adunk vissza** az `enter` vagy `leave` visitorból. A `NodeTraverser` ezután lecseréli az eredeti csomópontot a visszaadottal a szülő csomópont struktúrájában. + +**Példa:** Hozzunk létre egy menetet, amely megtalálja a `PHP_VERSION` konstans összes használatát (amelyet a `ConstantFetchNode` reprezentál), és lecseréli őket közvetlenül egy string literállal (`StringNode`), amely a *fordításkor* észlelt *valódi* PHP verziót tartalmazza. Ez egyfajta fordítási idejű optimalizálás. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\Php\Expression\ConstantFetchNode; +use Latte\Compiler\Nodes\Php\Scalar\StringNode; + +function inlinePhpVersion(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // A 'leave'-et gyakran használják cserére, biztosítva, hogy a gyermekek (ha vannak) + // először legyenek feldolgozva, bár itt az 'enter' is működne. + leave: function (Node $node) { + // Ez a csomópont egy konstans hozzáférés, és a konstans neve 'PHP_VERSION'? + if ($node instanceof ConstantFetchNode && (string) $node->name === 'PHP_VERSION') { + // Létrehozunk egy új StringNode-ot, amely az aktuális PHP verziót tartalmazza + $newNode = new StringNode(PHP_VERSION); + + // Opcionális, de jó gyakorlat: másoljuk a pozíció információt + $newNode->position = $node->position; + + // Visszaadjuk az új StringNode-ot. A Traverser lecseréli + // az eredeti ConstantFetchNode-ot ezzel a $newNode-nal. + return $newNode; + } + // Ha nem adunk vissza Node-ot, az eredeti $node megmarad. + }, + ); +} +``` + +Itt a `leave` visitor azonosítja a specifikus `ConstantFetchNode`-ot a `PHP_VERSION`-hoz. Ezután létrehoz egy teljesen új `StringNode`-ot, amely a `PHP_VERSION` konstans értékét tartalmazza *a fordítás idején*. Ennek a `$newNode`-nak a visszaadásával jelzi a traversernek, hogy cserélje le az eredeti `ConstantFetchNode`-ot az AST-ben. + +Hatás: Ha a sablon `{= PHP_VERSION }`-t tartalmazott, és a fordítás PHP 8.2.1-en fut, az AST ezen menet után hatékonyan `{= '8.2.1' }`-t fog reprezentálni. + +**Az `enter` vs. `leave` választása a cseréhez:** + +- Használja a `leave`-et, ha az új csomópont létrehozása a régi csomópont gyermekeinek feldolgozásának eredményeitől függ, vagy ha egyszerűen biztosítani szeretné, hogy a gyermekek meglátogatásra kerüljenek a csere előtt (gyakori gyakorlat). +- Használja az `enter`-t, ha le akarja cserélni a csomópontot *mielőtt* annak gyermekei egyáltalán meglátogatásra kerülnének. + + +Csomópontok eltávolítása +------------------------ + +Teljesen eltávolíthat egy csomópontot az AST-ből a `NodeTraverser::RemoveNode` speciális konstans visszaadásával a visitorból. + +**Példa:** Távolítsuk el az összes sablon kommentet (`{* ... *}`), amelyeket a Latte magja által generált AST-ben a `CommentNode` reprezentál (bár általában korábban kezelik őket, ez példaként szolgál). + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\CommentNode; + +function removeCommentNodes(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // Az 'enter' itt rendben van, mivel nincs szükségünk a gyermekek információira a komment eltávolításához + enter: function (Node $node) { + if ($node instanceof CommentNode) { + // Jelezzük a traversernek, hogy távolítsa el ezt a csomópontot az AST-ből + return NodeTraverser::RemoveNode; + } + }, + ); +} +``` + +**Figyelmeztetés:** Használja a `RemoveNode`-ot óvatosan. Egy olyan csomópont eltávolítása, amely alapvető tartalmat tartalmaz vagy befolyásolja a struktúrát (mint egy ciklus tartalom csomópontjának eltávolítása), sérült sablonokhoz vagy érvénytelen generált kódhoz vezethet. Legbiztonságosabb olyan csomópontokhoz, amelyek valóban opcionálisak vagy önállóak (mint a kommentek vagy hibakereső tagek), vagy üres strukturális csomópontokhoz (pl. egy üres `FragmentNode` bizonyos kontextusokban biztonságosan eltávolítható egy tisztító menettel). + +Ez a három módszer - property-k módosítása, csomópontok cseréje és csomópontok eltávolítása - biztosítja az alapvető eszközöket az AST manipulálásához a fordítási menetein belül. + + +Bejárás optimalizálása +====================== + +A sablonok AST-je meglehetősen nagy lehet, potenciálisan több ezer csomópontot tartalmazva. Minden egyes csomópont bejárása felesleges lehet, és befolyásolhatja a fordítási teljesítményt, ha a menet csak a fa specifikus részei iránt érdeklődik. A `NodeTraverser` módszereket kínál a bejárás optimalizálására: + + +Gyermekek kihagyása +------------------- + +Ha tudja, hogy amint egy bizonyos típusú csomópontra bukkan, annak egyetlen leszármazottja sem tartalmazhatja a keresett csomópontokat, megmondhatja a traversernek, hogy hagyja ki a gyermekei meglátogatását. Ezt a `NodeTraverser::DontTraverseChildren` konstans visszaadásával teheti meg az **`enter`** visitorból. Ezzel egész ágakat hagy ki a bejárás során, potenciálisan jelentős időt takarítva meg, különösen komplex PHP kifejezéseket tartalmazó tagekkel rendelkező sablonokban. + + +Bejárás leállítása +------------------ + +Ha a menetnek csak valaminek az *első* előfordulását kell megtalálnia (egy specifikus csomóponttípus, egy feltétel teljesülése), teljesen leállíthatja az egész bejárási folyamatot, amint megtalálta. Ezt a `NodeTraverser::StopTraversal` konstans visszaadásával érheti el az `enter` vagy `leave` visitorból. A `traverse()` metódus abbahagyja bármely további csomópont meglátogatását. Ez rendkívül hatékony, ha csak az első egyezésre van szüksége egy potenciálisan nagyon nagy fában. + + +Hasznos segítő `NodeHelpers` +============================ + +Míg a `NodeTraverser` finomhangolt vezérlést kínál, a Latte egy praktikus segédosztályt is biztosít, a [api:Latte\Compiler\NodeHelpers]-t, amely beburkolja a `NodeTraverser`-t számos gyakori keresési és elemzési feladathoz, gyakran kevesebb boilerplate kódot igényelve. + + +find(Node $startNode, callable $filter): array .[method] +-------------------------------------------------------- + +Ez a statikus metódus megtalálja **az összes** csomópontot a `$startNode`-tól kezdődő (beleértve) aláfában, amelyek megfelelnek a `$filter` callbacknek. Visszaadja a megfelelő csomópontok tömbjét. + +**Példa:** Találja meg az összes változó csomópontot (`VariableNode`) az egész sablonban. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Php\Expression\VariableNode; +use Latte\Compiler\Nodes\TemplateNode; + +function findAllVariables(TemplateNode $templateNode): array +{ + return NodeHelpers::find( + $templateNode, + fn($node) => $node instanceof VariableNode, + ); +} +``` + + +findFirst(Node $startNode, callable $filter): ?Node .[method] +-------------------------------------------------------------- + +Hasonló a `find`-hoz, de azonnal leállítja a bejárást, miután megtalálta az **első** csomópontot, amely megfelel a `$filter` callbacknek. Visszaadja a talált `Node` objektumot vagy `null`-t, ha nem található megfelelő csomópont. Ez lényegében egy praktikus burkoló a `NodeTraverser::StopTraversal` köré. + +**Példa:** Találja meg a `{parameters}` csomópontot (ugyanaz, mint a korábbi manuális példa, de rövidebb). + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Essential\Nodes\ParametersNode; + +function findParametersNodeHelper(TemplateNode $templateNode): ?ParametersNode +{ + return NodeHelpers::findFirst( + $templateNode->head, // Csak a fő szekcióban keresünk a hatékonyság érdekében + fn($node) => $node instanceof ParametersNode, + ); +} +``` + + +toValue(ExpressionNode $node, bool $constants = false): mixed .[method] +----------------------------------------------------------------------- + +Ez a statikus metódus megpróbálja kiértékelni az `ExpressionNode`-ot **fordítási időben**, és visszaadni a megfelelő PHP értékét. Megbízhatóan csak egyszerű literális csomópontokra (`StringNode`, `IntegerNode`, `FloatNode`, `BooleanNode`, `NullNode`) és olyan `ArrayNode` példányokra működik, amelyek csak ilyen kiértékelhető elemeket tartalmaznak. + +Ha a `$constants` `true`-ra van állítva, megpróbálja feloldani a `ConstantFetchNode`-ot és a `ClassConstantFetchNode`-ot is a `defined()` ellenőrzésével és a `constant()` használatával. + +Ha a csomópont változókat, függvényhívásokat vagy más dinamikus elemeket tartalmaz, nem értékelhető ki fordítási időben, és a metódus `InvalidArgumentException`-t dob. + +**Használati eset:** Egy tag argumentumának statikus értékének lekérése fordításkor fordítási idejű döntéshozatalhoz. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Php\ExpressionNode; + +function getStaticStringArgument(ExpressionNode $argumentNode): ?string +{ + try { + $value = NodeHelpers::toValue($argumentNode); + return is_string($value) ? $value : null; + } catch (\InvalidArgumentException $e) { + // Az argumentum nem volt statikus literális string + return null; + } +} +``` + + +toText(?Node $node): ?string .[method] +-------------------------------------- + +Ez a statikus metódus hasznos egyszerű csomópontokból származó egyszerű szöveges tartalom kinyerésére. Elsősorban a következőkkel működik: +- `TextNode`: Visszaadja a `$content`-jét. +- `FragmentNode`: Összefűzi a `toText()` eredményét az összes gyermekére. Ha valamelyik gyermek nem alakítható szöveggé (pl. `PrintNode`-ot tartalmaz), `null`-t ad vissza. +- `NopNode`: Üres stringet ad vissza. +- Más csomóponttípusok: `null`-t ad vissza. + +**Használati eset:** Egy HTML attribútum értékének vagy egy egyszerű HTML elem statikus szöveges tartalmának lekérése elemzéshez egy fordítási menet során. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Html\AttributeNode; + +function getStaticAttributeValue(AttributeNode $attr): ?string +{ + // $attr->value általában AreaNode (mint FragmentNode vagy TextNode) + return NodeHelpers::toText($attr->value); +} + +// Példa használat egy menetben: +// if ($node instanceof Html\ElementNode && $node->name === 'meta') { +// $nameAttrValue = getStaticAttributeValue($node->getAttributeNode('name')); +// if ($nameAttrValue === 'description') { ... } +// } +``` + +A `NodeHelpers` egyszerűsítheti a fordítási meneteit azáltal, hogy kész megoldásokat kínál a gyakori AST bejárási és elemzési feladatokra. + + +Gyakorlati példák +================= + +Alkalmazzuk a bejárási és módosítási koncepciókat néhány gyakorlati probléma megoldására. Ezek a példák a fordítási menetekben használt gyakori mintákat mutatják be. + + +`loading="lazy"` automatikus hozzáadása az ``-hez +------------------------------------------------------ + +A modern böngészők támogatják a képek natív lusta betöltését a `loading="lazy"` attribútummal. Hozzunk létre egy menetet, amely automatikusan hozzáadja ezt az attribútumot minden `` taghez, amely még nem rendelkezik `loading` attribútummal. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; +use Latte\Compiler\Nodes\Html; + +function addLazyLoading(Nodes\TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // Használhatjuk az 'enter'-t, mivel közvetlenül módosítjuk a csomópontot + // és nem függünk a gyermekektől ehhez a döntéshez. + enter: function (Node $node) { + // Ez egy HTML elem 'img' névvel? + if ($node instanceof Html\ElementNode && $node->name === 'img') { + // Biztosítjuk, hogy az attribútum csomópont létezik + $node->attributes ??= new Nodes\FragmentNode; + + // Ellenőrizzük, hogy létezik-e már 'loading' attribútum (kis- és nagybetű érzéketlenül) + foreach ($node->attributes->children as $attrNode) { + if ($attrNode instanceof Html\AttributeNode + && $attrNode->name instanceof Nodes\TextNode // Statikus attribútum név + && strtolower($attrNode->name->content) === 'loading' + ) { + return; // Már létezik, ne tegyünk semmit + } + } + + // Hozzáadunk egy szóközt, ha az attribútumok nem üresek + if ($node->attributes->children) { + $node->attributes->children[] = new Nodes\TextNode(' '); + } + + // Létrehozunk egy új attribútum csomópontot: loading="lazy" + $node->attributes->children[] = new Html\AttributeNode( + name: new Nodes\TextNode('loading'), + value: new Nodes\TextNode('lazy'), + quote: '"', + ); + // A változás közvetlenül az objektumban alkalmazódik, nincs szükség semmit visszaadni. + } + }, + ); +} +``` + +Magyarázat: +- Az `enter` visitor `Html\ElementNode` csomópontokat keres `img` névvel. +- Iterál a meglévő attribútumokon (`$node->attributes->children`), és ellenőrzi, hogy a `loading` attribútum már jelen van-e. +- Ha nem található, létrehoz egy új `Html\AttributeNode`-ot, amely a `loading="lazy"`-t reprezentálja. + + +Függvényhívások ellenőrzése +--------------------------- + +A fordítási menetek a Latte [Sandbox |sandbox] alapját képezik. Bár a valódi Sandbox kifinomult, demonstrálhatjuk a tiltott függvényhívások ellenőrzésének alapelvét. + +**Cél:** Megakadályozni a potenciálisan veszélyes `shell_exec` függvény használatát a sablon kifejezéseken belül. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; +use Latte\Compiler\Nodes\Php; +use Latte\SecurityViolationException; + +function checkForbiddenFunctions(Nodes\TemplateNode $templateNode): void +{ + $forbiddenFunctions = ['shell_exec' => true, 'exec' => true]; // Egyszerű lista + + $traverser = new NodeTraverser; + (new NodeTraverser)->traverse( + $templateNode, + enter: function (Node $node) use ($forbiddenFunctions) { + // Ez egy közvetlen függvényhívás csomópont? + if ($node instanceof Php\Expression\FunctionCallNode + && $node->name instanceof Php\NameNode + && isset($forbiddenFunctions[strtolower((string) $node->name)]) + ) { + throw new SecurityViolationException( + "A(z) {$node->name}() függvény nem engedélyezett.", + $node->position, + ); + } + }, + ); +} +``` + +Magyarázat: +- Definiálunk egy listát a tiltott függvénynevekről. +- Az `enter` visitor ellenőrzi a `FunctionCallNode`-ot. +- Ha a függvény neve (`$node->name`) egy statikus `NameNode`, ellenőrizzük annak kisbetűs string reprezentációját a tiltott listánkhoz képest. +- Ha tiltott függvényt találunk, `Latte\SecurityViolationException`-t dobunk, amely egyértelműen jelzi a biztonsági szabály megsértését és leállítja a fordítást. + +Ezek a példák bemutatják, hogyan használhatók a fordítási menetek a `NodeTraverser` segítségével elemzésre, automatikus módosításokra és biztonsági korlátozások kikényszerítésére közvetlenül a sablon AST struktúrájával való interakció révén. + + +Legjobb gyakorlatok +=================== + +Fordítási menetek írásakor tartsa szem előtt ezeket az irányelveket robusztus, karbantartható és hatékony bővítmények létrehozásához: + +- **A sorrend számít:** Legyen tisztában a menetek futási sorrendjével. Ha a menet egy másik menet által létrehozott AST struktúrától függ (pl. a Latte alap meneteitől vagy egy másik egyéni menettől), vagy ha más menetek függhetnek az Ön módosításaitól, használja az `Extension::getPasses()` által biztosított sorrendezési mechanizmust a függőségek (`before`/`after`) definiálásához. Lásd a [`Extension::getPasses()` |extending-latte#getPasses] dokumentációját a részletekért. +- **Egy felelősség:** Törekedjen olyan menetekre, amelyek egy jól definiált feladatot végeznek. Komplex átalakításokhoz fontolja meg a logika több menetre bontását – esetleg egyet az elemzéshez és egy másikat a módosításhoz az elemzési eredmények alapján. Ez javítja az áttekinthetőséget és a tesztelhetőséget. +- **Teljesítmény:** Ne feledje, hogy a fordítási menetek hozzáadnak a sablon fordítási idejéhez (bár ez általában csak egyszer történik, amíg a sablon nem változik). Kerülje a számításigényes műveleteket a meneteiben, ha lehetséges. Használja ki a bejárási optimalizálásokat, mint a `NodeTraverser::DontTraverseChildren` és `NodeTraverser::StopTraversal`, amikor tudja, hogy nem kell meglátogatnia az AST bizonyos részeit. +- **Használja a `NodeHelpers`-t:** Gyakori feladatokhoz, mint specifikus csomópontok keresése vagy egyszerű kifejezések statikus kiértékelése, ellenőrizze, hogy a `Latte\Compiler\NodeHelpers` kínál-e megfelelő metódust, mielőtt saját `NodeTraverser` logikát írna. Ez időt takaríthat meg és csökkentheti a boilerplate kód mennyiségét. +- **Hibakezelés:** Ha a menet hibát vagy érvénytelen állapotot észlel a sablon AST-jében, dobjon `Latte\CompileException`-t (vagy `Latte\SecurityViolationException`-t biztonsági problémák esetén) egyértelmű üzenettel és a releváns `Position` objektummal (általában `$node->position`). Ez hasznos visszajelzést nyújt a sablon fejlesztőjének. +- **Idempotencia (ha lehetséges):** Ideális esetben a menet többszöri futtatása ugyanazon az AST-n ugyanazt az eredményt kell, hogy produkálja, mint az egyszeri futtatása. Ez nem mindig megvalósítható, de egyszerűsíti a hibakeresést és a menetek interakcióiról való gondolkodást, ha elérik. Például győződjön meg róla, hogy a módosító menet ellenőrzi, hogy a módosítás már alkalmazva lett-e, mielőtt újra alkalmazná. + +Ezen gyakorlatok követésével hatékonyan használhatja a fordítási meneteket a Latte képességeinek erőteljes és megbízható módon történő kiterjesztésére, hozzájárulva a biztonságosabb, optimalizáltabb vagy funkciókban gazdagabb sablonfeldolgozáshoz. diff --git a/latte/hu/cookbook/@home.texy b/latte/hu/cookbook/@home.texy index 4b9e44b15f..b1351a25fb 100644 --- a/latte/hu/cookbook/@home.texy +++ b/latte/hu/cookbook/@home.texy @@ -1,13 +1,13 @@ -Szakácskönyv -************ +Útmutatók és eljárások +********************** .[perex] -Példakódok és receptek a Latte segítségével végzett gyakori feladatok elvégzéséhez. +Kódpéldák és receptek gyakori feladatok elvégzéséhez Latte segítségével. -- [Minden, amit mindig is tudni akartál az {iterateWhile} |iteratewhile] -- [Hogyan írjunk SQL-lekérdezéseket Latte-ban? |how-to-write-sql-queries-in-latte] -- [Átállás PHP-ről |migration-from-php] -- [Átállás a Twig-ről |migration-from-twig] -- [Latte használata a Slim 4-gyel |slim-framework] - -{{leftbar: /@left-menu}} +- [Eljárások fejlesztőknek |/develop] +- [Változók átadása sablonok között |passing-variables] +- [Minden, amit valaha tudni akartál a csoportosításról |grouping] +- [Hogyan írjunk SQL lekérdezéseket Latte-ban? |how-to-write-sql-queries-in-latte] +- [Migráció PHP-ból |migration-from-php] +- [Migráció Twig-ből |migration-from-twig] +- [Latte használata Slim 4-gyel |slim-framework] diff --git a/latte/hu/cookbook/@meta.texy b/latte/hu/cookbook/@meta.texy new file mode 100644 index 0000000000..f7e3fec4be --- /dev/null +++ b/latte/hu/cookbook/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Latte dokumentáció}} +{{leftbar: /@left-menu}} diff --git a/latte/hu/cookbook/grouping.texy b/latte/hu/cookbook/grouping.texy new file mode 100644 index 0000000000..b74f61ca75 --- /dev/null +++ b/latte/hu/cookbook/grouping.texy @@ -0,0 +1,251 @@ +Minden, amit valaha tudni akartál a csoportosításról +**************************************************** + +.[perex] +Amikor sablonokban adatokkal dolgozol, gyakran találkozhatsz azzal az igénnyel, hogy azokat csoportosítani vagy bizonyos kritériumok szerint specifikusan megjeleníteni kell. A Latte erre a célra több erős eszközt is kínál. + +A `|group` szűrő és függvény lehetővé teszi az adatok hatékony csoportosítását a megadott kritérium szerint, a `|batch` szűrő megkönnyíti az adatok fix méretű adagokra osztását, a `{iterateWhile}` tag pedig lehetőséget nyújt a ciklusok bonyolultabb vezérlésére feltételekkel. Mindegyik tag specifikus lehetőségeket kínál az adatokkal való munkához, így nélkülözhetetlen eszközökké válnak az információk dinamikus és strukturált megjelenítéséhez a Latte sablonokban. + + +A `group` szűrő és függvény .{data-version:3.0.16} +================================================== + +Képzelj el egy `items` adatbázistáblát, amely kategóriákba sorolt elemeket tartalmaz: + +| id | categoryId | name +|------------------ +| 1 | 1 | Apple +| 2 | 1 | Banana +| 3 | 2 | PHP +| 4 | 3 | Green +| 5 | 3 | Red +| 6 | 3 | Blue + +Az összes elem egyszerű listája Latte sablonnal így nézne ki: + +```latte +
                                                                    +{foreach $items as $item} +
                                                                  • {$item->name}
                                                                  • +{/foreach} +
                                                                  +``` + +Ha azonban azt szeretnénk, hogy az elemek kategóriák szerint csoportosítva legyenek, úgy kell őket felosztani, hogy minden kategóriának saját listája legyen. Az eredménynek így kellene kinéznie: + +```latte +
                                                                    +
                                                                  • Apple
                                                                  • +
                                                                  • Banana
                                                                  • +
                                                                  + +
                                                                    +
                                                                  • PHP
                                                                  • +
                                                                  + +
                                                                    +
                                                                  • Green
                                                                  • +
                                                                  • Red
                                                                  • +
                                                                  • Blue
                                                                  • +
                                                                  +``` + +A feladat könnyen és elegánsan megoldható a `|group` segítségével. Paraméterként a `categoryId`-t adjuk meg, ami azt jelenti, hogy az elemek kisebb tömbökre lesznek osztva a `$item->categoryId` értéke alapján (ha `$item` tömb lenne, akkor `$item['categoryId']` kerülne felhasználásra): + +```latte +{foreach ($items|group: categoryId) as $categoryId => $categoryItems} +
                                                                    + {foreach $categoryItems as $item} +
                                                                  • {$item->name}
                                                                  • + {/foreach} +
                                                                  +{/foreach} +``` + +A szűrőt Latte-ban függvényként is lehet használni, ami alternatív szintaxist ad nekünk: `{foreach group($items, categoryId) ...}`. + +Ha bonyolultabb kritériumok szerint szeretnéd csoportosítani az elemeket, használhatsz függvényt a szűrő paraméterében. Például az elemek csoportosítása a név hossza szerint így nézne ki: + +```latte +{foreach ($items|group: fn($item) => strlen($item->name)) as $items} + ... +{/foreach} +``` + +Fontos megjegyezni, hogy a `$categoryItems` nem egy szokásos tömb, hanem egy objektum, amely iterátorként viselkedik. A csoport első eleméhez való hozzáféréshez használhatod a [`first()` |latte:functions#first] függvényt. + +Ez a rugalmasság az adatok csoportosításában teszi a `group`-ot kivételesen hasznos eszközzé az adatok Latte sablonokban történő megjelenítéséhez. + + +Beágyazott ciklusok +------------------- + +Képzeljük el, hogy van egy adatbázistáblánk egy további `subcategoryId` oszloppal, amely az egyes elemek alkategóriáit definiálja. Szeretnénk minden fő kategóriát egy külön `
                                                                    ` listában, és minden alkategóriát egy külön beágyazott `
                                                                      ` listában megjeleníteni: + +```latte +{foreach ($items|group: categoryId) as $categoryItems} +
                                                                        + {foreach ($categoryItems|group: subcategoryId) as $subcategoryItems} +
                                                                          + {foreach $subcategoryItems as $item} +
                                                                        1. {$item->name} + {/foreach} +
                                                                        + {/foreach} +
                                                                      +{/foreach} +``` + + +Kapcsolat a Nette Database-zel +------------------------------ + +Nézzük meg, hogyan lehet hatékonyan kihasználni az adatok csoportosítását a Nette Database-zel kombinálva. Tegyük fel, hogy az `items` táblával dolgozunk a bevezető példából, amely a `categoryId` oszlopon keresztül kapcsolódik ehhez a `categories` táblához: + +| categoryId | name | +|------------|------------| +| 1 | Fruits | +| 2 | Languages | +| 3 | Colors | + +Az `items` tábla adatait a Nette Database Explorer segítségével olvassuk be a `$items = $db->table('items')` paranccsal. Ezen adatok feletti iteráció során nemcsak az olyan attribútumokhoz férhetünk hozzá, mint a `$item->name` és `$item->categoryId`, hanem a `categories` táblával való kapcsolatnak köszönhetően a kapcsolódó sorhoz is a `$item->category`-n keresztül. Ezen a kapcsolaton keresztül érdekes felhasználást demonstrálhatunk: + +```latte +{foreach ($items|group: category) as $category => $categoryItems} +

                                                                      {$category->name}

                                                                      +
                                                                        + {foreach $categoryItems as $item} +
                                                                      • {$item->name}
                                                                      • + {/foreach} +
                                                                      +{/foreach} +``` + +Ebben az esetben a `|group` szűrőt használjuk a kapcsolódó `$item->category` sor szerinti csoportosításhoz, nem csak a `categoryId` oszlop szerint. Ennek köszönhetően a kulcs változóban közvetlenül az adott kategória `ActiveRow`-ja van, ami lehetővé teszi számunkra, hogy közvetlenül kiírjuk a nevét a `{$category->name}` segítségével. Ez egy gyakorlati példa arra, hogyan teheti áttekinthetőbbé a csoportosítás a sablonokat és könnyítheti meg az adatokkal való munkát. + + +A `|batch` szűrő +================ + +A szűrő lehetővé teszi az elemek listájának felosztását előre meghatározott számú elemet tartalmazó csoportokra. Ez a szűrő ideális olyan helyzetekben, amikor az adatokat több kisebb csoportban szeretné megjeleníteni, például a jobb áttekinthetőség vagy a vizuális elrendezés érdekében az oldalon. + +Képzeljük el, hogy van egy listánk elemekkel, és szeretnénk őket listákban megjeleníteni, ahol mindegyik legfeljebb három elemet tartalmaz. A `|batch` szűrő használata ilyen esetben nagyon praktikus: + +```latte +
                                                                        +{foreach ($items|batch: 3) as $batch} + {foreach $batch as $item} +
                                                                      • {$item->name}
                                                                      • + {/foreach} +{/foreach} +
                                                                      +``` + +Ebben a példában az `$items` lista kisebb csoportokra van osztva, ahol minden csoport (`$batch`) legfeljebb három elemet tartalmaz. Minden csoport ezután egy külön `
                                                                        ` listában jelenik meg. + +Ha az utolsó csoport nem tartalmaz elegendő elemet a kívánt szám eléréséhez, a szűrő második paramétere lehetővé teszi annak meghatározását, hogy mivel legyen ez a csoport kiegészítve. Ez ideális az elemek esztétikus igazításához ott, ahol egy hiányos sor rendezetlennek tűnhet. + +```latte +{foreach ($items|batch: 3, '—') as $batch} + ... +{/foreach} +``` + + +A `{iterateWhile}` tag +====================== + +Ugyanazokat a feladatokat, amelyeket a `|group` szűrővel oldottunk meg, megmutatjuk a `{iterateWhile}` tag használatával. A két megközelítés közötti fő különbség az, hogy a `group` először feldolgozza és csoportosítja az összes bemeneti adatot, míg a `{iterateWhile}` a ciklusok menetét vezérli feltételekkel, így az iteráció fokozatosan történik. + +Először a kategóriákkal rendelkező táblázatot rajzoljuk ki az iterateWhile segítségével: + +```latte +{foreach $items as $item} +
                                                                          + {iterateWhile} +
                                                                        • {$item->name}
                                                                        • + {/iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                                                                        +{/foreach} +``` + +Míg a `{foreach}` a ciklus külső részét jelöli, azaz a listák kirajzolását minden kategóriához, addig a `{iterateWhile}` tag a belső részt jelöli, azaz az egyes elemeket. A záró tagben lévő feltétel azt mondja, hogy az ismétlés addig tart, amíg az aktuális és a következő elem ugyanabba a kategóriába tartozik (`$iterator->nextValue` a [következő elem |/tags#iterator]). + +Ha a feltétel mindig teljesülne, akkor a belső ciklusban az összes elem kirajzolódna: + +```latte +{foreach $items as $item} +
                                                                          + {iterateWhile} +
                                                                        • {$item->name} + {/iterateWhile true} +
                                                                        +{/foreach} +``` + +Az eredmény így fog kinézni: + +```latte +
                                                                          +
                                                                        • Apple
                                                                        • +
                                                                        • Banana
                                                                        • +
                                                                        • PHP
                                                                        • +
                                                                        • Green
                                                                        • +
                                                                        • Red
                                                                        • +
                                                                        • Blue
                                                                        • +
                                                                        +``` + +Mire jó az iterateWhile ilyen használata? Ha a táblázat üres és nem tartalmaz elemeket, nem íródik ki üres `
                                                                          `. + +Ha a feltételt a nyitó `{iterateWhile}` tagben adjuk meg, akkor a viselkedés megváltozik: a feltétel (és a következő elemre való áttérés) már a belső ciklus elején végrehajtódik, nem a végén. Tehát míg a feltétel nélküli `{iterateWhile}`-be mindig belépünk, addig a `{iterateWhile $cond}`-be csak a `$cond` feltétel teljesülése esetén. És ezzel egyidejűleg a következő elem beíródik az `$item`-be. + +Ez hasznos például abban a helyzetben, amikor minden kategória első elemét más módon szeretnénk kirajzolni, például így: + +```latte +

                                                                          Apple

                                                                          +
                                                                            +
                                                                          • Banana
                                                                          • +
                                                                          + +

                                                                          PHP

                                                                          +
                                                                            +
                                                                          + +

                                                                          Green

                                                                          +
                                                                            +
                                                                          • Red
                                                                          • +
                                                                          • Blue
                                                                          • +
                                                                          +``` + +Az eredeti kódot úgy módosítjuk, hogy először kirajzoljuk az első elemet, majd a belső `{iterateWhile}` ciklusban kirajzoljuk a többi elemet ugyanabból a kategóriából: + +```latte +{foreach $items as $item} +

                                                                          {$item->name}

                                                                          +
                                                                            + {iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                                                                          • {$item->name}
                                                                          • + {/iterateWhile} +
                                                                          +{/foreach} +``` + +Egy cikluson belül több belső ciklust is létrehozhatunk, és akár beágyazhatjuk őket. Így lehetne például alkategóriákat csoportosítani stb. + +Tegyük fel, hogy a táblázatban van még egy `subcategoryId` oszlop, és amellett, hogy minden kategória külön `
                                                                            `-ben van, minden alkategória külön `
                                                                              `-ban: + +```latte +{foreach $items as $item} +
                                                                                + {iterateWhile} +
                                                                                  + {iterateWhile} +
                                                                                1. {$item->name} + {/iterateWhile $item->subcategoryId === $iterator->nextValue->subcategoryId} +
                                                                                + {/iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                                                                              +{/foreach} +``` diff --git a/latte/hu/cookbook/how-to-write-sql-queries-in-latte.texy b/latte/hu/cookbook/how-to-write-sql-queries-in-latte.texy index 3184947e4c..298a9b06a0 100644 --- a/latte/hu/cookbook/how-to-write-sql-queries-in-latte.texy +++ b/latte/hu/cookbook/how-to-write-sql-queries-in-latte.texy @@ -1,10 +1,10 @@ -Hogyan írjunk SQL-lekérdezéseket Latte-ban? +Hogyan írjunk SQL lekérdezéseket Latte-ban? ******************************************* .[perex] -A Latte igazán összetett SQL-lekérdezések generálásához is hasznos lehet. +A Latte hasznos lehet igazán bonyolult SQL lekérdezések generálásához is. -Ha egy SQL-lekérdezés létrehozása sok feltételt és változót tartalmaz, akkor igazán áttekinthetőbb lehet a Latte-ban megírni. Egy nagyon egyszerű példa: +Ha egy SQL lekérdezés létrehozása számos feltételt és változót tartalmaz, valóban áttekinthetőbb lehet azt Latte-ban megírni. Egy nagyon egyszerű példa: ```latte SELECT users.* FROM users @@ -14,8 +14,7 @@ SELECT users.* FROM users WHERE groups.name = 'Admins' {ifset $country} AND country.name = {$country} {/ifset} ``` -A `$latte->setContentType()` segítségével megmondjuk a Latte-nak, hogy a tartalmat egyszerű szövegként kezelje (ne HTML-ként) és -majd elkészítünk egy escaping függvényt, amely a karakterláncokat közvetlenül az adatbázis-illesztőprogrammal eszkábálja: +A `$latte->setContentType()` segítségével megmondjuk a Latte-nak, hogy a tartalmat egyszerű szövegként kezelje (nem HTML-ként), és továbbá előkészítünk egy escapelő függvényt, amely a stringeket közvetlenül az adatbázis driverrel fogja escapelni: ```php $db = new PDO(/* ... */); @@ -27,7 +26,7 @@ $latte->addFilter('escape', fn($val) => match (true) { is_int($val), is_float($val) => (string) $val, is_bool($val) => $val ? '1' : '0', is_null($val) => 'NULL', - default => throw new Exception('Unsupported type'), + default => throw new Exception('Nem támogatott típus'), }); ``` @@ -38,6 +37,4 @@ $sql = $latte->renderToString('query.sql.latte', ['country' => $country]); $result = $db->query($sql); ``` -*Ez a példa Latte v3.0.5 vagy magasabb verziószámot igényel.* - -{{leftbar: /@left-menu}} +*A megadott példa Latte 3.0.5 vagy újabb verziót igényel.* diff --git a/latte/hu/cookbook/iteratewhile.texy b/latte/hu/cookbook/iteratewhile.texy deleted file mode 100644 index d4952557bd..0000000000 --- a/latte/hu/cookbook/iteratewhile.texy +++ /dev/null @@ -1,214 +0,0 @@ -Minden, amit mindig is tudni akartál az {iterateWhile} -****************************************************** - -.[perex] -A `{iterateWhile}` címke a foreach ciklusokban különböző trükkökre alkalmas. - -Tegyük fel, hogy a következő adatbázis táblával rendelkezünk, ahol az elemek kategóriákba vannak osztva: - -| id | catId | name -|------------------ -| 1 | 1 | Apple -| 2 | 1 | Banana -| 3 | 2 | PHP -| 4 | 3 | Green -| 5 | 3 | Red -| 6 | 3 | Blue - -Természetesen a foreach ciklusban az elemek listaként való kirajzolása egyszerű: - -```latte -
                                                                                -{foreach $items as $item} -
                                                                              • {$item->name}
                                                                              • -{/foreach} -
                                                                              -``` - -De mit tegyünk, ha minden kategóriát külön listában szeretnénk megjeleníteni? Más szóval, hogyan oldjuk meg egy foreach ciklusban egy lineáris lista elemeinek csoportosítását. A kimenetnek így kellene kinéznie: - -```latte -
                                                                                -
                                                                              • Apple
                                                                              • -
                                                                              • Banana
                                                                              • -
                                                                              - -
                                                                                -
                                                                              • PHP
                                                                              • -
                                                                              - -
                                                                                -
                                                                              • Green
                                                                              • -
                                                                              • Red
                                                                              • -
                                                                              • Blue
                                                                              • -
                                                                              -``` - -Megmutatjuk, milyen egyszerűen és elegánsan megoldható a feladat az iterateWhile segítségével: - -```latte -{foreach $items as $item} -
                                                                                - {iterateWhile} -
                                                                              • {$item->name}
                                                                              • - {/iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                                              -{/foreach} -``` - -Míg a `{foreach}` a ciklus külső részét jelöli, azaz az egyes kategóriákhoz tartozó listák rajzolását, addig a `{iterateWhile}` címkék a belső részt, azaz az egyes elemeket jelölik. -Az end tagben lévő feltétel azt mondja, hogy az ismétlés addig folytatódik, amíg az aktuális és a következő elem ugyanahhoz a kategóriához tartozik (`$iterator->nextValue` a [következő elem |/tags#$iterator]). - -Ha a feltétel mindig teljesül, akkor a belső ciklusban minden elem kirajzolódik: - -```latte -{foreach $items as $item} -
                                                                                - {iterateWhile} -
                                                                              • {$item->name} - {/iterateWhile true} -
                                                                              -{/foreach} -``` - -Az eredmény így fog kinézni: - -```latte -
                                                                                -
                                                                              • Apple
                                                                              • -
                                                                              • Banana
                                                                              • -
                                                                              • PHP
                                                                              • -
                                                                              • Green
                                                                              • -
                                                                              • Red
                                                                              • -
                                                                              • Blue
                                                                              • -
                                                                              -``` - -Mire jó az iterateWhile ilyen használata? Miben különbözik attól a megoldástól, amit a bemutató legelején mutattunk? A különbség az, hogy ha a táblázat üres, és nem tartalmaz semmilyen elemet, akkor nem fog üresen renderelni `
                                                                                `. - - -Megoldás a `{iterateWhile}` nélkül .[#toc-solution-without-iteratewhile] ------------------------------------------------------------------------- - -Ha ugyanazt a feladatot a sablonrendszerek teljesen alapvető konstrukcióival oldanánk meg, például Twigben, Blade-ben vagy tiszta PHP-ben, a megoldás valahogy így nézne ki: - -```latte -{var $prevCatId = null} -{foreach $items as $item} - {if $item->catId !== $prevCatId} - {* the category has changed *} - - {* we close the previous
                                                                                  , if it is not the first item *} - {if $prevCatId !== null} -
                                                                                - {/if} - - {* új listát nyitunk *} -
                                                                                  - - {do $prevCatId = $item->catId} - {/if} - -
                                                                                • {$item->name}
                                                                                • -{/foreach} - -{if $prevCatId !== null} - {* lezárjuk az utolsó listát *} -
                                                                                -{/if} -``` - -Ez a kód azonban érthetetlen és nem intuitív. A nyitó és záró HTML-címkék közötti kapcsolat egyáltalán nem egyértelmű. Első pillantásra nem egyértelmű, hogy hiba van-e benne. És olyan segédváltozókat igényel, mint a `$prevCatId`. - -Ezzel szemben a `{iterateWhile}` megoldása tiszta, egyértelmű, nem igényel segédváltozókat, és bolondbiztos. - - -Feltétel a záró tagben .[#toc-condition-in-the-closing-tag] ------------------------------------------------------------ - -Ha a nyitó tagben adunk meg egy feltételt `{iterateWhile}`, a viselkedés megváltozik: a feltétel (és a következő elemre való továbblépés) a belső ciklus elején hajtódik végre, nem pedig a végén. -Így míg a `{iterateWhile}` feltétel nélkül mindig belép, a `{iterateWhile $cond}` csak akkor lép be, ha a `$cond` feltétel teljesül. Ezzel egyidejűleg a következő elemet írja a `$item`. - -Ez hasznos például olyan helyzetekben, amikor az egyes kategóriák első elemét másképp szeretnénk megjeleníteni, például: - -```latte -

                                                                                Apple

                                                                                -
                                                                                  -
                                                                                • Banana
                                                                                • -
                                                                                - -

                                                                                PHP

                                                                                -
                                                                                  -
                                                                                - -

                                                                                Green

                                                                                -
                                                                                  -
                                                                                • Red
                                                                                • -
                                                                                • Blue
                                                                                • -
                                                                                -``` - -Módosítsuk az eredeti kódot, rajzoljuk az első elemet, majd további elemeket ugyanabból a kategóriából a belső ciklusban `{iterateWhile}`: - -```latte -{foreach $items as $item} -

                                                                                {$item->name}

                                                                                -
                                                                                  - {iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                                                • {$item->name}
                                                                                • - {/iterateWhile} -
                                                                                -{/foreach} -``` - - -Beágyazott hurkok .[#toc-nested-loops] --------------------------------------- - -Egy ciklusban több belső hurkot is létrehozhatunk, és akár egymásba is ágyazhatjuk őket. Így például alkategóriákat csoportosíthatunk. - -Tegyük fel, hogy van egy másik oszlop a `subCatId` táblázatban, és amellett, hogy minden kategória egy különálló `
                                                                                  `, minden alkategória egy külön oszlopban lesz. `
                                                                                    `: - -```latte -{foreach $items as $item} -
                                                                                      - {iterateWhile} -
                                                                                        - {iterateWhile} -
                                                                                      1. {$item->name} - {/iterateWhile $item->subCatId === $iterator->nextValue->subCatId} -
                                                                                      - {/iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                                                    -{/foreach} -``` - - -Szűrő |batch .[#toc-filter-batch] ---------------------------------- - -A lineáris elemek csoportosítását szintén a `batch` szűrő biztosítja, mégpedig fix elemszámú tételekbe: - -```latte -
                                                                                      -{foreach ($items|batch:3) as $batch} - {foreach $batch as $item} -
                                                                                    • {$item->name}
                                                                                    • - {/foreach} -{/foreach} -
                                                                                    -``` - -Ez helyettesíthető iterateWhile-val az alábbiak szerint: - -```latte -
                                                                                      -{foreach $items as $item} - {iterateWhile} -
                                                                                    • {$item->name}
                                                                                    • - {/iterateWhile $iterator->counter0 % 3} -{/foreach} -
                                                                                    -``` - -{{leftbar: /@left-menu}} diff --git a/latte/hu/cookbook/migration-from-php.texy b/latte/hu/cookbook/migration-from-php.texy index 303626ca47..5b008b1c15 100644 --- a/latte/hu/cookbook/migration-from-php.texy +++ b/latte/hu/cookbook/migration-from-php.texy @@ -1,28 +1,28 @@ -Átállás PHP-ről Latte-ra -************************ +Migráció PHP-ból Latte-ba +************************* .[perex] -Egy régi, tiszta PHP-ben írt projektet migrálsz át Latte-ra? Van egy eszközünk, amely megkönnyíti a migrációt. [Próbálja ki online |https://php2latte.nette.org]. +Átalakít egy régi, tiszta PHP-ban írt projektet Latte-ra? Van egy eszközünk, amely megkönnyíti a migrációt. [Próbálja ki online |https://fiddle.nette.org/php2latte/]. -Az eszközt letöltheted a [GitHubról |https://github.com/nette/latte-tools], vagy telepítheted a Composer segítségével: +Az eszközt letöltheti a [GitHubról|https://github.com/nette/latte-tools] vagy telepítheti a Composer segítségével: ```shell composer create-project latte/tools ``` -Az átalakító nem használ egyszerű reguláris kifejezések helyettesítését, hanem közvetlenül a PHP parsert használja, így bármilyen összetett szintaxist képes kezelni. +Az átalakító nem használ egyszerű cseréket reguláris kifejezésekkel, hanem közvetlenül a PHP parsert használja, így bármilyen bonyolult szintaxissal megbirkózik. -A `php-to-latte.php` szkriptet a PHP-ről Latte-ra történő konvertáláshoz használjuk: +A PHP-ból Latte-ba történő átalakításhoz a `php-to-latte.php` szkript szolgál: ```shell php-to-latte.php input.php [output.latte] ``` -Példa: .[#toc-example] ----------------------- +Példa +----- -A bemeneti fájl így nézhet ki (ez a PunBB fórum kódjának része): +A bemeneti fájl például így nézhet ki (ez a PunBB fórum kódjának egy része): ```php

                                                                                    @@ -48,7 +48,7 @@ foreach ($result as $cur_group) {
                                                                      ``` -Ezt a sablont generálja: +Ez a sablon generálódik: ```latte

                                                                      {$lang_common['User list']}

                                                                      @@ -68,5 +68,3 @@ Ezt a sablont generálja:
                                                                      ``` - -{{leftbar: /@left-menu}} diff --git a/latte/hu/cookbook/migration-from-twig.texy b/latte/hu/cookbook/migration-from-twig.texy index 8bb81dc58b..caed7d2013 100644 --- a/latte/hu/cookbook/migration-from-twig.texy +++ b/latte/hu/cookbook/migration-from-twig.texy @@ -1,38 +1,38 @@ -Átállás a Twigről a Latte-ra -**************************** +Migráció Twig-ből Latte-ba +************************** .[perex] -Egy Twig-ben írt projektet migrál a modernebb Latte-ra? Van egy eszközünk, amely megkönnyíti a migrációt. [Próbálja ki online |https://twig2latte.nette.org]. +Átalakít egy Twig-ben írt projektet a modernebb Latte-ra? Van egy eszközünk, amely megkönnyíti a migrációt. [Próbálja ki online |https://fiddle.nette.org/twig2latte/]. -Az eszközt letöltheti a [GitHubról |https://github.com/nette/latte-tools], vagy telepítheti a Composer segítségével: +Az eszközt letöltheti a [GitHubról|https://github.com/nette/latte-tools] vagy telepítheti a Composer segítségével: ```shell composer create-project latte/tools ``` -Az átalakító nem használ egyszerű reguláris kifejezések helyettesítését, hanem közvetlenül a Twig elemzőt használja, így bármilyen összetett szintaxist képes kezelni. +Az átalakító nem használ egyszerű cseréket reguláris kifejezésekkel, hanem közvetlenül a Twig parsert használja, így bármilyen bonyolult szintaxissal megbirkózik. -A `twig-to-latte.php` szkriptet használja a Twigről a Latte-ra való konvertáláshoz: +A Twig-ből Latte-ba történő átalakításhoz a `twig-to-latte.php` szkript szolgál: ```shell twig-to-latte.php input.twig.html [output.latte] ``` -Konvertálás .[#toc-conversion] ------------------------------- +Konverzió +--------- -A konverzió kézi szerkesztést igényel, mivel a konverzió nem végezhető el egyértelműen. A Twig pont szintaxist használ, ahol `{{ a.b }}` jelentheti a `$a->b`, `$a['b']` vagy `$a->getB()`, amit a fordítás során nem lehet megkülönböztetni. A konverter ezért mindent `$a->b`-ra konvertál. +Az átalakítás feltételezi az eredmény kézi módosítását, mivel a konverziót nem lehet egyértelműen elvégezni. A Twig pont szintaxist használ, ahol `{{ a.b }}` jelentheti `$a->b`-t, `$a['b']`-t vagy `$a->getB()`-t, amit nem lehet megkülönböztetni a fordítás során. Az átalakító ezért mindent `$a->b`-re konvertál. -Néhány függvénynek, szűrőnek vagy címkének nincs megfelelője a Latte-ban, vagy kissé eltérően viselkedhet. +Néhány függvény, szűrő vagy tag nem rendelkezik megfelelővel a Latte-ban, vagy kissé eltérően viselkedhetnek. -Példa .[#toc-example] ---------------------- +Példa +----- -A bemeneti fájl így nézhet ki: +A bemeneti fájl például így nézhet ki: -```latte +```twig {% use "blocks.twig" %} @@ -54,7 +54,7 @@ A bemeneti fájl így nézhet ki: ``` -A Latte-ba való konvertálás után ezt a sablont kapjuk: +A Latte-ba történő konverzió után ezt a sablont kapjuk: ```latte {import 'blocks.latte'} @@ -77,5 +77,3 @@ A Latte-ba való konvertálás után ezt a sablont kapjuk: ``` - -{{leftbar: /@left-menu}} diff --git a/latte/hu/cookbook/passing-variables.texy b/latte/hu/cookbook/passing-variables.texy new file mode 100644 index 0000000000..4106596008 --- /dev/null +++ b/latte/hu/cookbook/passing-variables.texy @@ -0,0 +1,158 @@ +Változók átadása sablonok között +******************************** + +Ez az útmutató elmagyarázza, hogyan adódnak át a változók a sablonok között a Latte-ban különböző tagek, mint például `{include}`, `{import}`, `{embed}`, `{layout}`, `{sandbox}` és mások segítségével. Megtudhatod azt is, hogyan dolgozz a változókkal a `{block}` és `{define}` tagekben, és mire szolgál a `{parameters}` tag. + + +Változók típusai +---------------- +A Latte változóit három kategóriába sorolhatjuk attól függően, hogyan és hol vannak definiálva: + +**Bemeneti változók** azok, amelyeket kívülről adnak át a sablonnak, például egy PHP szkriptből vagy egy tag, mint például `{include}` segítségével. + +```php +$latte->render('template.latte', ['userName' => 'Jan', 'userAge' => 30]); +``` + +**Környezeti változók** azok a változók, amelyek egy adott tag helyén léteznek. Tartalmazzák az összes bemeneti változót és más, tagek, mint például `{var}`, `{default}` vagy a `{foreach}` ciklus keretében létrehozott változókat. + +```latte +{foreach $users as $user} + {include 'userBox.latte', user: $user} +{/foreach} +``` + +**Explicit változók** azok, amelyeket közvetlenül a tagben specifikálnak, és a cél sablonnak küldenek. + +```latte +{include 'userBox.latte', name: $user->name, age: $user->age} +``` + + +`{block}` +--------- +A `{block}` tag újra felhasználható kódblokkok definiálására szolgál, amelyeket az öröklődő sablonokban testre lehet szabni vagy bővíteni. A blokk előtt definiált környezeti változók elérhetők a blokkon belül, de a változók bármilyen módosítása csak a blokkon belül érvényesül. + +```latte +{var $foo = 'eredeti'} +{block example} + {var $foo = 'megváltozott'} +{/block} + +{$foo} // kiírja: eredeti +``` + + +`{define}` +---------- +A `{define}` tag olyan blokkok létrehozására szolgál, amelyek csak akkor renderelődnek, amikor a `{include}` segítségével meghívják őket. Az ezekben a blokkokban elérhető változók attól függnek, hogy a definícióban meg vannak-e adva paraméterek. Ha igen, csak ezekhez a paraméterekhez férnek hozzá. Ha nem, akkor hozzáférnek a sablon összes bemeneti változójához, amelyben a blokkok definiálva vannak. + +```latte +{define hello} + {* hozzáfér a sablon összes bemeneti változójához *} +{/define} + +{define hello $name} + {* csak a $name paraméterhez fér hozzá *} +{/define} +``` + + +`{parameters}` +-------------- +A `{parameters}` tag a várt bemeneti változók explicit deklarálására szolgál a sablon elején. Ezzel a módszerrel könnyen dokumentálhatók a várt változók és azok adattípusai. Lehetőség van alapértelmezett értékek definiálására is. + +```latte +{parameters int $age, string $name = 'ismeretlen'} +

                                                                      Kor: {$age}, Név: {$name}

                                                                      +``` + + +`{include file}` +---------------- +A `{include file}` tag egy teljes sablon beillesztésére szolgál. Ennek a sablonnak átadódnak mind a sablon bemeneti változói, amelyben a tagot használják, mind a benne expliciten definiált változók. A cél sablon azonban korlátozhatja a hatókört a `{parameters}` segítségével. + +```latte +{include 'profile.latte', userId: $user->id} +``` + + +`{include block}` +----------------- +Amikor egy ugyanabban a sablonban definiált blokkot illesztesz be, átadódnak neki az összes környezeti és expliciten definiált változó: + +```latte +{define blockName} +

                                                                      Név: {$name}, Kor: {$age}

                                                                      +{/define} + +{var $name = 'Jan', $age = 30} +{include blockName} +``` + +Ebben a példában a `$name` és `$age` változók átadódnak a `blockName` blokknak. Ugyanígy viselkedik a `{include parent}` is. + +Amikor egy másik sablonból származó blokkot illesztesz be, csak a bemeneti és az expliciten definiált változók adódnak át. A környezeti változók nem érhetők el automatikusan. + +```latte +{include blockInOtherTemplate, name: $name, age: $age} +``` + + +`{layout}` vagy `{extends}` +--------------------------- +Ezek a tagek definiálják a layoutot, amelynek átadódnak az alárendelt sablon bemeneti változói, valamint a kódblokkok előtt létrehozott változók: + +```latte +{layout 'layout.latte'} +{var $seo = 'index, follow'} +``` + +A `layout.latte` sablon: + +```latte + + + +``` + + +`{embed}` +--------- +A `{embed}` tag hasonló a `{include}` taghez, de lehetővé teszi blokkok beágyazását a sablonba. Ellentétben a `{include}`-dal, csak az expliciten deklarált változók adódnak át: + +```latte +{embed 'menu.latte', items: $menuItems} +{/embed} +``` + +Ebben a példában a `menu.latte` sablon csak az `$items` változóhoz fér hozzá. + +Ezzel szemben a `{embed}` belüli blokkokban hozzáférés van az összes környezeti változóhoz: + +```latte +{var $name = 'Jan'} +{embed 'menu.latte', items: $menuItems} + {block foo} + {$nam} + {/block} +{/embed} +``` + + +`{import}` +---------- +A `{import}` tag más sablonokból származó blokkok betöltésére szolgál. Mind a bemeneti, mind az expliciten deklarált változók átadódnak az importált blokkoknak. + +```latte +{import 'buttons.latte'} +``` + + +`{sandbox}` +----------- +A `{sandbox}` tag izolálja a sablont a biztonságos feldolgozáshoz. A változók kizárólag expliciten adódnak át. + +```latte +{sandbox 'secure.latte', data: $secureData} +``` diff --git a/latte/hu/cookbook/slim-framework.texy b/latte/hu/cookbook/slim-framework.texy index a0e7b96641..309feb6a15 100644 --- a/latte/hu/cookbook/slim-framework.texy +++ b/latte/hu/cookbook/slim-framework.texy @@ -1,43 +1,43 @@ -Latte használata a Slim 4-gyel -****************************** +Latte használata Slim 4-gyel +**************************** .[perex] -Ez a "Daniel Opitz":https://odan.github.io/2022/04/06/slim4-latte.html által írt cikk a Latte és a Slim Framework használatát írja le. +Ez a cikk, amelynek szerzője "Daniel Opitz":https://odan.github.io/2022/04/06/slim4-latte.html, a Latte használatát írja le a Slim Frameworkkel. -Először "telepítse a Slim Frameworket":https://odan.github.io/2019/11/05/slim4-tutorial.html, majd a Latte-t a Composer segítségével: +Először "telepítse a Slim Frameworköt":https://odan.github.io/2019/11/05/slim4-tutorial.html, majd a Latte-t a Composer segítségével: ```shell composer require latte/latte ``` -Configuration .[#toc-configuration] ------------------------------------ +Konfiguráció +------------ -Hozzon létre egy új `templates` könyvtárat a projekt gyökérkönyvtárában. Az összes sablon a későbbiekben oda kerül. +A projekt gyökérkönyvtárában hozzon létre egy új `templates` könyvtárat. Minden sablon később ebbe kerül. -Adjon hozzá egy új `template` konfigurációs kulcsot a `config/defaults.php` fájlban: +A `config/defaults.php` fájlba adjon hozzá egy új `template` konfigurációs kulcsot: ```php $settings['template'] = __DIR__ . '/../templates'; ``` -A Latte a sablonokat natív PHP kóddá fordítja, és a lemezen lévő gyorsítótárban tárolja. Így olyan gyorsak, mintha natív PHP-ben íródtak volna. +A Latte lefordítja a sablonokat natív PHP kódra, és a lemezen lévő gyorsítótárban tárolja őket. Így ugyanolyan gyorsak, mintha natív PHP nyelven írták volna őket. -Adjon hozzá egy új `template_temp` konfigurációs kulcsot a `config/defaults.php` fájlban: Győződjön meg róla, hogy a `{project}/tmp/templates` könyvtár létezik, és rendelkezik olvasási és írási hozzáférési jogosultságokkal. +A `config/defaults.php` fájlba adjon hozzá egy új `template_temp` konfigurációs kulcsot: Győződjön meg róla, hogy a `{project}/tmp/templates` könyvtár létezik, és rendelkezik olvasási és írási jogokkal. ```php $settings['template_temp'] = __DIR__ . '/../tmp/templates'; ``` -A Latte automatikusan újratermeli a gyorsítótárat minden alkalommal, amikor megváltoztatja a sablont, ami kikapcsolható a termelési környezetben, hogy egy kis teljesítményt takarítson meg: +A Latte automatikusan újragenerálja a gyorsítótárat minden sablonváltozáskor, amit a produkciós környezetben ki lehet kapcsolni, hogy egy kis teljesítményt spóroljunk: ```php -// a termelési környezetben false-ra változik +// v produkčním prostředí změňte na false $settings['template_auto_refresh'] = true; ``` -Ezután adjunk hozzá egy DI konténerdefiníciót a `Latte\Engine` osztályhoz. +Továbbá adjon hozzá egy DI konténer definíciót a `Latte\Engine` osztályhoz. ```php +
                                                                        {foreach $items as $item}
                                                                      • {$item|capitalize}
                                                                      • {/foreach}
                                                                      ``` -Ha minden helyesen van beállítva, a következő kimenetet kell látnia: +Ha minden helyesen van konfigurálva, a következő kimenetnek kell megjelennie: ```latte One @@ -155,4 +155,3 @@ Three ``` {{priority: -1}} -{{leftbar: /@left-menu}} diff --git a/latte/hu/creating-extension.texy b/latte/hu/creating-extension.texy deleted file mode 100644 index a84df6e1b4..0000000000 --- a/latte/hu/creating-extension.texy +++ /dev/null @@ -1,579 +0,0 @@ -Bővítmény létrehozása -********************* - -.[perex]{data-version:3.0} -A kiterjesztés egy újrafelhasználható osztály, amely egyéni címkéket, szűrőket, függvényeket, szolgáltatókat stb. definiálhat. - -Bővítményeket akkor hozunk létre, amikor a Latte testreszabásainkat különböző projektekben szeretnénk újra felhasználni, vagy megosztani másokkal. -Az is hasznos, ha minden egyes webes projekthez létrehozunk egy bővítményt, amely tartalmazza az összes olyan egyedi címkét és szűrőt, amelyet a projekt sablonjaiban használni szeretnénk. - - -Bővítmény osztály .[#toc-extension-class] -========================================= - -A Extension egy osztály, amely a [api:Latte\Extension] osztályból származik. A Latte-ba a `addExtension()` segítségével (vagy a [konfigurációs fájlon |application:configuration#Latte] keresztül) regisztráljuk: - -```php -$latte = new Latte\Engine; -$latte->addExtension(new MyLatteExtension); -``` - -Ha több kiterjesztést regisztrál, és azok azonos nevű címkéket, szűrőket vagy függvényeket definiálnak, az utoljára hozzáadott kiterjesztés nyer. Ez azt is jelenti, hogy a kiterjesztések felülbírálhatják a natív címkéket/szűrőket/funkciókat. - -Amikor módosít egy osztályt, és az automatikus frissítés nincs kikapcsolva, a Latte automatikusan újrafordítja a sablonokat. - -Egy osztály a következő metódusok bármelyikét megvalósíthatja: - -```php -abstract class Extension -{ - /** - * Initializes before template is compiler. - */ - public function beforeCompile(Engine $engine): void; - - /** - * Returns a list of parsers for Latte tags. - * @return array - */ - public function getTags(): array; - - /** - * Returns a list of compiler passes. - * @return array - */ - public function getPasses(): array; - - /** - * Returns a list of |filters. - * @return array - */ - public function getFilters(): array; - - /** - * Returns a list of functions used in templates. - * @return array - */ - public function getFunctions(): array; - - /** - * Returns a list of providers. - * @return array - */ - public function getProviders(): array; - - /** - * Returns a value to distinguish multiple versions of the template. - */ - public function getCacheKey(Engine $engine): mixed; - - /** - * Initializes before template is rendered. - */ - public function beforeRender(Template $template): void; -} -``` - -A kiterjesztés kinézetéről a beépített "CoreExtension:https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php"-t nézzük meg. - - -beforeCompile(Latte\Engine $engine): void .[method] ---------------------------------------------------- - -A sablon lefordítása előtt hívódik. A metódus használható például a fordítással kapcsolatos inicializáláshoz. - - -getTags(): array .[method] --------------------------- - -A sablon fordításakor hívódik. Visszaad egy asszociatív tömböt *tag name => callable*, amelyek [tag elemző függvények |#Tag Parsing Function]. - -```php -public function getTags(): array -{ - return [ - 'foo' => [FooNode::class, 'create'], - 'bar' => [BarNode::class, 'create'], - 'n:baz' => [NBazNode::class, 'create'], - // ... - ]; -} -``` - -A `n:baz` tag egy tiszta n:attribútumot képvisel, azaz egy olyan tag, amely csak attribútumként írható ki. - -A `foo` és `bar` címkék esetében a Latte automatikusan felismeri, hogy ezek párok-e, és ha igen, akkor automatikusan n:attribútumokkal írhatók, beleértve a `n:inner-foo` és `n:tag-foo` előtaggal ellátott változatokat is. - -Az ilyen n:attribútumok végrehajtási sorrendjét a `getTags()` által visszaadott tömbben elfoglalt sorrendjük határozza meg. Így a `n:foo` mindig a `n:bar` előtt kerül végrehajtásra, még akkor is, ha az attribútumok fordított sorrendben szerepelnek a HTML tagben, mint a `
                                                                      `. - -Ha az n:attribútumok sorrendjét több kiterjesztésen keresztül kell meghatározni, használja a `order()` segédmódszert, ahol a `before` xor `after` paraméter határozza meg, hogy melyik tag előtt vagy után kerül sorra. - -```php -public function getTags(): array -{ - return [ - 'foo' => self::order([FooNode::class, 'create'], before: 'bar')] - 'bar' => self::order([BarNode::class, 'create'], after: ['block', 'snippet'])] - ]; -} -``` - - -getPasses(): array .[method] ----------------------------- - -A sablon fordításakor hívódik meg. Visszaad egy asszociatív tömböt *name pass => callable*, amelyek úgynevezett [fordítói passzokat |#compiler passes] reprezentáló függvények, amelyek átjárják és módosítják az AST-et. - -Ismét használható a `order()` segédmódszer. A `before` vagy `after` paraméterek értéke `*` lehet, az összes előtt/után jelentéssel. - -```php -public function getPasses(): array -{ - return [ - 'optimize' => [Passes::class, 'optimizePass'], - 'sandbox' => self::order([$this, 'sandboxPass'], before: '*'), - // ... - ]; -} -``` - - -beforeRender(Latte\Engine $engine): void .[method] --------------------------------------------------- - -Minden egyes sablon renderelés előtt meghívásra kerül. A metódus használható például a renderelés során használt változók inicializálására. - - -getFilters(): array .[method] ------------------------------ - -A sablon renderelése előtt hívódik meg. A [szűrőket |extending-latte#filters] asszociatív tömbként adja vissza *filter name => callable*. - -```php -public function getFilters(): array -{ - return [ - 'batch' => [$this, 'batchFilter'], - 'trim' => [$this, 'trimFilter'], - // ... - ]; -} -``` - - -getFunctions(): array .[method] -------------------------------- - -A sablon renderelése előtt hívódik meg. A [függvényeket |extending-latte#functions] asszociatív tömbként adja vissza *funkció neve => hívható*. - -```php -public function getFunctions(): array -{ - return [ - 'clamp' => [$this, 'clampFunction'], - 'divisibleBy' => [$this, 'divisibleByFunction'], - // ... - ]; -} -``` - - -getProviders(): array .[method] -------------------------------- - -A sablon renderelése előtt hívódik meg. Visszaadja a szolgáltatók tömbjét, amelyek általában olyan objektumok, amelyek futásidőben használnak címkéket. Hozzáférésük a `$this->global->...` címen keresztül történik. - -```php -public function getProviders(): array -{ - return [ - 'myFoo' => $this->foo, - 'myBar' => $this->bar, - // ... - ]; -} -``` - - -getCacheKey(Latte\Engine $engine): mixed .[method] --------------------------------------------------- - -A sablon renderelése előtt hívódik meg. A visszatérési érték része lesz annak a kulcsnak, amelynek hash-ját a lefordított sablonfájl neve tartalmazza. Így különböző visszatérési értékek esetén a Latte különböző gyorsítótárfájlokat fog létrehozni. - - -Hogyan működik a Latte? .[#toc-how-does-latte-work] -=================================================== - -Ahhoz, hogy megértsük, hogyan definiálhatunk egyéni címkéket vagy fordítói passzokat, elengedhetetlen, hogy megértsük, hogyan működik a Latte a motorháztető alatt. - -A sablonfordítás a Latte-ban leegyszerűsítve így működik: - -- Először a **lexer** a könnyebb feldolgozás érdekében a sablon forráskódját kis darabokra (tokenekre) bontja. -- Ezután a **elemző** a tokenek folyamát egy értelmes csomópontfává (absztrakt szintaxisfa, AST) alakítja át. -- Végül a fordító **generál** egy PHP-osztályt az AST-ből, amely megjeleníti a sablont, és gyorsítótárba helyezi. - -Valójában a fordítás egy kicsit bonyolultabb. A Latte-nak **kettő** lexere és elemzője van: egy a HTML sablonhoz és egy a címkéken belüli PHP-szerű kódhoz. Továbbá a parszing nem a tokenizálás után fut, hanem a lexer és a parser párhuzamosan fut két "szálban" és koordinál. Ez rakétatudomány :-) - -Továbbá minden tagnek saját elemző rutinja van. Amikor az elemző találkozik egy címkével, meghívja annak elemző függvényét (ez adja vissza a [Extension::getTags() |#getTags] függvényt). -Feladatuk a tag argumentumainak és párosított tagek esetén a belső tartalom elemzése. Visszaad egy *csomópontot*, amely az AST részévé válik. Lásd a [Tag elemző funkciót |#Tag parsing function] a részletekért. - -Amikor az elemző befejezi a munkáját, egy teljes AST-vel rendelkezünk, amely a sablont reprezentálja. A gyökércsomópont a `Latte\Compiler\Nodes\TemplateNode`. A fán belüli egyes csomópontok ezután nem csak a címkéket, hanem a HTML-elemeket, azok attribútumait, a címkéken belül használt kifejezéseket stb. is reprezentálják. - -Ezek után jönnek az úgynevezett [Compiler pass-ok |#Compiler passes], amelyek olyan függvények (amelyeket az [Extension::getPasses() |#getPasses] ad vissza), amelyek módosítják az AST-et. - -Az egész folyamat, a sablon tartalmának betöltésétől kezdve a parsingon át a kapott fájl generálásáig, ezzel a kóddal szekvenálható, amivel kísérletezhetünk, és a köztes eredményeket kidobhatjuk: - -```php -$latte = new Latte\Engine; -$source = $latte->getLoader()->getContent($file); -$ast = $latte->parse($source); -$latte->applyPasses($ast); -$code = $latte->generate($ast, $file); -``` - - -Példa az AST-re .[#toc-example-of-ast] --------------------------------------- - -Hogy jobban megismerjük az AST-et, adunk hozzá egy mintát. Ez a forrássablon: - -```latte -{foreach $category->getItems() as $item} -
                                                                    1. {$item->name|upper}
                                                                    2. - {else} - no items found -{/foreach} -``` - -Ez pedig az AST formájában való megjelenítése: - -/--pre -Latte\Compiler\Nodes\TemplateNode( - Latte\Compiler\Nodes\FragmentNode( - - Latte\Essential\Nodes\ForeachNode( - expression: Latte\Compiler\Nodes\Php\Expression\MethodCallNode( - object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$category') - name: Latte\Compiler\Nodes\Php\IdentifierNode('getItems') - ) - value: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') - content: Latte\Compiler\Nodes\FragmentNode( - - Latte\Compiler\Nodes\TextNode(' ') - - Latte\Compiler\Nodes\Html\ElementNode('li')( - content: Latte\Essential\Nodes\PrintNode( - expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode( - object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') - name: Latte\Compiler\Nodes\Php\IdentifierNode('name') - ) - modifier: Latte\Compiler\Nodes\Php\ModifierNode( - filters: - - Latte\Compiler\Nodes\Php\FilterNode('upper') - ) - ) - ) - ) - else: Latte\Compiler\Nodes\FragmentNode( - - Latte\Compiler\Nodes\TextNode('no items found') - ) - ) - ) -) -\-- - - -Egyéni címkék .[#toc-custom-tags] -================================= - -Egy új címke definiálásához három lépésre van szükség: - -- [a címke elemző függvény |#tag parsing function] definiálása (felelős a címke csomóponttá történő elemzéséért) -- csomópont osztály létrehozása (felelős a [PHP kód generálásáért |#generating PHP code] és az [AST átjárásáért |#AST traversing]). -- a címke regisztrálása az [Extension::getTags() |#getTags] segítségével. - - -Tag elemző funkció .[#toc-tag-parsing-function] ------------------------------------------------ - -A címkék elemzése a saját elemző függvényével történik (az [Extension::getTags() |#getTags] által visszaadott függvény). Feladata a tagben lévő argumentumok elemzése és ellenőrzése (ehhez a TagParser-t használja). -Továbbá, ha a tag egy pár, akkor megkéri a TemplateParsert, hogy elemezze és adja vissza a belső tartalmat. -A függvény létrehoz és visszaad egy csomópontot, amely általában a `Latte\Compiler\Nodes\StatementNode` gyermeke, és ez az AST részévé válik. - -Minden csomóponthoz létrehozunk egy osztályt, amit most meg is teszünk, és elegánsan elhelyezzük benne a parsing függvényt, mint statikus gyárat. Példaként próbáljuk meg létrehozni az ismert `{foreach}` címkét: - -```php -use Latte\Compiler\Nodes\StatementNode; - -class ForeachNode extends StatementNode -{ - // egy elemző függvény, amely egyelőre csak egy csomópontot hoz létre. - public static function create(Latte\Compiler\Tag $tag): self - { - $node = new self; - return $node; - } - - public function print(Latte\Compiler\PrintContext $context): string - { - // a kódot később adjuk hozzá - } - - public function &getIterator(): \Generator - { - // a kódot később adjuk hozzá - } -} -``` - -A `create()` elemző függvénynek átadunk egy [api:Latte\Compiler\Tag] objektumot, amely alapvető információkat hordoz a tagről (hogy klasszikus tag vagy n:attribútum, milyen sorban van, stb.), és főként a `$tag->parser`-ban lévő [api:Latte\Compiler\TagParser] objektumhoz fér hozzá. - -Ha a címkének argumentumokkal kell rendelkeznie, akkor a `$tag->expectArguments()` meghívásával ellenőrzi azok meglétét. Ezek elemzésére a `$tag->parser` objektum metódusai állnak rendelkezésre: - -- `parseExpression(): ExpressionNode` egy PHP-szerű kifejezéshez (pl. `10 + 3`). -- `parseUnquotedStringOrExpression(): ExpressionNode` kifejezés vagy idézőjel nélküli karakterlánc esetén. -- `parseArguments(): ArrayNode` tömb tartalma (pl. `10, true, foo => bar`). -- `parseModifier(): ModifierNode` egy módosítóhoz (pl. `|upper|truncate:10`). -- `parseType(): expressionNode` a tipehintre (pl. `int|string` vagy `Foo\Bar[]`). - -és egy alacsony szintű [api:Latte\Compiler\TokenStream], amely közvetlenül tokenekkel dolgozik: - -- `$tag->parser->stream->consume(...): Token` -- `$tag->parser->stream->tryConsume(...): ?Token` - -A Latte kis mértékben bővíti a PHP szintaxist, például módosítókkal, rövidített terner operátorokkal, vagy lehetővé teszi az egyszerű alfanumerikus karakterláncok idézőjelek nélküli írását. Ezért használjuk a PHP helyett a *PHP-szerű* kifejezést. Így például a `parseExpression()` módszer a `foo` -t `'foo'`-ként elemzi. -Ezenkívül a *unquoted-string* egy speciális esete egy olyan karakterláncnak, amelyet szintén nem kell idézőjelbe tenni, ugyanakkor nem kell alfanumerikusnak lennie. Ez például egy fájl elérési útvonala a `{include ../file.latte}` címkében. A `parseUnquotedStringOrExpression()` módszerrel elemezhető. - -.[note] -A Latte részét képező csomópontosztályok tanulmányozása a legjobb módja annak, hogy megismerjük a parsing folyamat minden apró részletét. - -Térjünk vissza a `{foreach}` címkéhez. Ebben a `expression + 'as' + second expression` formájú argumentumokat várunk, amelyeket a következőképpen elemzünk: - -```php -use Latte\Compiler\Nodes\StatementNode; -use Latte\Compiler\Nodes\Php\ExpressionNode; -use Latte\Compiler\Nodes\AreaNode; - -class ForeachNode extends StatementNode -{ - public ExpressionNode $expression; - public ExpressionNode $value; - - public static function create(Latte\Compiler\Tag $tag): self - { - $tag->expectArguments(); - $node = new self; - $node->expression = $tag->parser->parseExpression(); - $tag->parser->stream->consume('as'); - $node->value = $parser->parseExpression(); - return $node; - } -} -``` - -A `$expression` és `$value` változókba írt kifejezések alcsomópontokat jelentenek. - -.[tip] -Az alcsomópontokkal rendelkező változókat **nyilvánosnak** definiáljuk, hogy szükség esetén a [további feldolgozási lépésekben |#Compiler Passes] módosíthatók legyenek. Szükséges továbbá, hogy **elérhetővé** tegyük őket a [bejáráshoz |#AST Traversing]. - -A párosított címkék esetében, mint a miénk, a módszernek hagynia kell, hogy a TemplateParser elemezze a címke belső tartalmát is. Ezt a `yield` kezeli, amely egy ''[belső tartalom, végcímke]'' párt ad vissza. A belső tartalmat a `$node->content` változóban tároljuk. - -```php -public AreaNode $content; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $endTag] = yield; - return $node; -} -``` - -A `yield` kulcsszó hatására a `create()` metódus befejeződik, visszaadva a vezérlést a TemplateParser-nek, amely folytatja a tartalom elemzését, amíg el nem éri a végtagot. Ezután visszaadja a vezérlést a `create()`-nak, amely ott folytatja, ahol abbahagyta. A `yield`, metódus használata automatikusan visszaadja a `Generator` metódust. - -Átadhat egy olyan címkenevekből álló tömböt is a `yield` címsornak, amelyek elemzése leáll, ha a végcímke előtt fordulnak elő. Ez segít nekünk megvalósítani a `{foreach}...{else}...{/foreach}` konstrukciót. Ha a `{else}` előfordul, akkor az utána lévő tartalmat elemezzük a `$node->elseContent` címkébe: - -```php -public AreaNode $content; -public ?AreaNode $elseContent = null; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $nextTag] = yield ['else']; - if ($nextTag?->name === 'else') { - [$node->elseContent] = yield; - } - - return $node; -} -``` - -A visszatérő csomópont befejezi a címkék elemzését. - - -PHP kód generálása .[#toc-generating-php-code] ----------------------------------------------- - -Minden csomópontnak implementálnia kell a `print()` metódust. Visszaadja a sablon adott részét megjelenítő PHP kódot (futásidejű kód). Paraméterként egy [api:Latte\Compiler\PrintContext] objektumot kap, amely rendelkezik egy hasznos `format()` metódussal, amely leegyszerűsíti a kapott kód összeállítását. - -A `format(string $mask, ...$args)` metódus a következő helyőrzőket fogadja el a maszkban: -- `%node` prints Node -- `%dump` exportálja az értéket PHP-be -- `%raw` a szöveget közvetlenül, transzformáció nélkül beszúrja. -- `%args` ArrayNode-ot nyomtat a függvényhívás argumentumaként. -- `%line` egy sorszámmal ellátott megjegyzést nyomtat -- `%escape(...)` escapes a tartalom -- `%modify(...)` módosítót alkalmaz -- `%modifyContent(...)` módosítót alkalmaz a blokkokra - - -A `print()` függvényünk így nézhet ki (az egyszerűség kedvéért elhanyagoljuk a `else` ágat): - -```php -public function print(Latte\Compiler\PrintContext $context): string -{ - return $context->format( - <<<'XX' - foreach (%node as %node) %line { - %node - } - - XX, - $this->expression, - $this->value, - $this->position, - $this->content, - ); -} -``` - -A `$this->position` változót már definiálta a [api:Latte\Compiler\Node] osztály, és az elemző állítja be. Tartalmaz egy [api:Latte\Compiler\Position] objektumot, amely a tag pozícióját tartalmazza a forráskódban sor- és oszlopszám formájában. - -A futásidejű kód használhat segédváltozókat. A sablon által használt változókkal való ütközés elkerülése érdekében konvenció szerint a `$ʟ__` karaktereket kell eléjük illeszteni. - -A futásidőben tetszőleges értékeket is használhat, amelyeket a [Extension::getProviders() |#getProviders] metódus segítségével szolgáltatók formájában adunk át a sablonhoz. A `$this->global->...` segítségével fér hozzájuk. - - -AST átszelés .[#toc-ast-traversing] ------------------------------------ - -Ahhoz, hogy az AST fát mélységében bejárhassuk, a `getIterator()` metódust kell implementálni. Ez hozzáférést biztosít az alcsomópontokhoz: - -```php -public function &getIterator(): \Generator -{ - yield $this->expression; - yield $this->value; - yield $this->content; - if ($this->elseContent) { - yield $this->elseContent; - } -} -``` - -Vegyük észre, hogy a `getIterator()` egy hivatkozást ad vissza. Ez teszi lehetővé a csomópontok látogatóinak, hogy az egyes csomópontokat más csomópontokkal helyettesítsék. - -.[warning] -Ha egy csomópontnak vannak alcsomópontjai, akkor ezt a metódust meg kell valósítani, és az összes alcsomópontot elérhetővé kell tenni. Ellenkező esetben biztonsági rés keletkezhet. Például a homokozó üzemmód nem tudná ellenőrizni az alcsomópontokat, és nem tudná biztosítani, hogy nem engedélyezett konstrukciókat ne hívjanak meg bennük. - -Mivel a `yield` kulcsszónak akkor is jelen kell lennie a metódus testében, ha nincsenek gyermekcsomópontjai, írjuk le a következőképpen: - -```php -public function &getIterator(): \Generator -{ - if (false) { - yield; - } -} -``` - - -Compiler Passes .[#toc-compiler-passes] -======================================= - -A Compiler Passes olyan függvények, amelyek módosítják az AST-eket vagy információt gyűjtenek bennük. Ezeket az [Extension::getPasses() |#getPasses] metódus adja vissza. - - -Node Traverser .[#toc-node-traverser] -------------------------------------- - -Az AST-vel való munka legáltalánosabb módja a [api:Latte\Compiler\NodeTraverser]: - -```php -use Latte\Compiler\Node; -use Latte\Compiler\NodeTraverser; - -$ast = (new NodeTraverser)->traverse( - $ast, - enter: fn(Node $node) => ..., - leave: fn(Node $node) => ..., -); -``` - -A *enter* függvényt (azaz a látogatót) akkor hívjuk meg, amikor először találkozunk egy csomóponttal, mielőtt annak alcsomópontjait feldolgoznánk. A *leave* függvényt az összes alcsomópont meglátogatása után hívjuk meg. -Gyakori minta, hogy a *enter* funkciót bizonyos információk összegyűjtésére használják, majd a *leave* funkció ezek alapján végez módosításokat. A *leave* meghívásának időpontjában a csomóponton belüli összes kódot már meglátogattuk, és begyűjtöttük a szükséges információkat. - -Hogyan módosítható az AST? A legegyszerűbb módja a csomópontok tulajdonságainak egyszerű módosítása. A második mód a csomópont teljes lecserélése egy új csomópont visszaadásával. Példa: a következő kód az AST-ben lévő összes egész számot karakterláncra változtatja (pl. a 42-es számot `'42'`). - -```php -use Latte\Compiler\Nodes\Php; - -$ast = (new NodeTraverser)->traverse( - $ast, - leave: function (Node $node) { - if ($node instanceof Php\Scalar\IntegerNode) { - return new Php\Scalar\StringNode((string) $node->value); - } - }, -); -``` - -Egy AST könnyen tartalmazhat több ezer csomópontot, és az összes csomóponton való áthaladás lassú lehet. Bizonyos esetekben elkerülhető a teljes átszelés. - -Ha az összes `Html\ElementNode` címet keresi egy fában, akkor tudja, hogy ha már látta a `Php\ExpressionNode` címet, akkor nincs értelme az összes gyermekcsomópontját is ellenőrizni, mert a HTML nem lehet a kifejezéseken belül. Ebben az esetben utasíthatjuk a traverzert, hogy ne lépjen vissza az osztálycsomópontba: - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Php\ExpressionNode) { - return NodeTraverser::DontTraverseChildren; - } - // ... - }, -); -``` - -Ha csak egy adott csomópontot keresünk, akkor lehetőség van arra is, hogy a megtalálása után teljesen megszakítsuk a traverzálást. - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Nodes\ParametersNode) { - return NodeTraverser::StopTraversal; - } - // ... - }, -); -``` - - -Csomópont-segédprogramok .[#toc-node-helpers] ---------------------------------------------- - -A [api:Latte\Compiler\NodeHelpers] osztály biztosít néhány metódust, amelyek képesek megtalálni azokat az AST csomópontokat, amelyek megfelelnek egy bizonyos visszahívásnak stb. Néhány példa látható: - -```php -use Latte\Compiler\NodeHelpers; - -// megtalálja az összes HTML elem csomópontját -$elements = NodeHelpers::find($ast, fn(Node $node) => $node instanceof Nodes\Html\ElementNode); - -// megtalálja az első szöveges csomópontot -$text = NodeHelpers::findFirst($ast, fn(Node $node) => $node instanceof Nodes\TextNode); - -// PHP-érték csomópontot valós értékké alakítja át -$value = NodeHelpers::toValue($node); - -// statikus szöveges csomópontot konvertál sztringgé -$text = NodeHelpers::toText($node); -``` diff --git a/latte/hu/custom-filters.texy b/latte/hu/custom-filters.texy new file mode 100644 index 0000000000..e29c0ae457 --- /dev/null +++ b/latte/hu/custom-filters.texy @@ -0,0 +1,231 @@ +Egyéni szűrők létrehozása +************************* + +.[perex] +A szűrők hatékony eszközök az adatok formázására és módosítására közvetlenül a Latte sablonokban. Tiszta szintaxist kínálnak a csővezeték szimbólum (`|`) használatával a változók vagy kifejezések eredményeinek a kívánt kimeneti formátumra történő átalakításához. + + +Mik azok a szűrők? +================== + +A szűrők a Latte-ban lényegében **PHP függvények, amelyeket kifejezetten egy bemeneti érték kimeneti értékké alakítására terveztek**. A csővezeték jelöléssel (`|`) alkalmazzák őket a sablon kifejezéseken (`{...}`) belül. + +**Kényelem:** A szűrők lehetővé teszik a gyakori formázási feladatok (mint a dátumok formázása, kis- és nagybetűk váltása, rövidítés) vagy adatmanipulációk újrafelhasználható egységekbe való beágyazását. Ahelyett, hogy bonyolult PHP kódot ismételgetne a sablonjaiban, egyszerűen alkalmazhat egy szűrőt: +```latte +{* Bonyolult PHP helyett a rövidítéshez: *} +{$article->text|truncate:100} + +{* Dátumformázó kód helyett: *} +{$event->startTime|date:'Y-m-d H:i'} + +{* Több átalakítás alkalmazása: *} +{$product->name|lower|capitalize} +``` + +**Olvashatóság:** A szűrők használata tisztábbá és inkább a prezentációra összpontosítóvá teszi a sablonokat, mivel az átalakítási logika a szűrő definíciójába kerül át. + +**Kontextusérzékenység:** A Latte szűrőinek kulcsfontosságú előnye, hogy képesek [kontextusérzékenyek |#Kontextusérzékeny szűrők] lenni. Ez azt jelenti, hogy a szűrő felismerheti a tartalom típusát, amellyel dolgozik (HTML, JavaScript, egyszerű szöveg stb.), és alkalmazhatja a megfelelő logikát vagy escapelést, ami elengedhetetlen a biztonság és a helyesség szempontjából, különösen HTML generálásakor. + +**Integráció az alkalmazáslogikával:** Mint az egyéni függvények, a szűrő mögötti PHP callable lehet closure, statikus metódus vagy példány metódus. Ez lehetővé teszi a szűrők számára, hogy hozzáférjenek az alkalmazás szolgáltatásaihoz vagy adataihoz, ha szükséges, bár fő céljuk továbbraও a *bemeneti érték átalakítása* marad. + +A Latte alapértelmezés szerint gazdag [standard szűrőket |filters] biztosít. Az egyéni szűrők lehetővé teszik ennek a készletnek a kiterjesztését a projekt-specifikus formázásokkal és átalakításokkal. + +Ha *több* bemeneten alapuló logikát kell végrehajtania, vagy nincs elsődleges átalakítandó értéke, valószínűleg megfelelőbb egy [egyéni függvényt |custom-functions] használni. Ha bonyolult jelölést kell generálnia vagy a sablon folyamatát kell vezérelnie, fontolja meg egy [egyéni taget |custom-tags]. + + +Szűrők létrehozása és regisztrálása +=================================== + +Több módja van az egyéni szűrők definiálásának és regisztrálásának a Latte-ban. + + +Közvetlen regisztráció az `addFilter()` segítségével +---------------------------------------------------- + +A szűrő hozzáadásának legegyszerűbb módja az `addFilter()` metódus használata közvetlenül a `Latte\Engine` objektumon. Megadja a szűrő nevét (ahogyan a sablonban használni fogják) és a megfelelő PHP callable-t. + +```php +$latte = new Latte\Engine; + +// Egyszerű szűrő argumentumok nélkül +$latte->addFilter('initial', fn(string $s): string => mb_substr($s, 0, 1) . '.'); + +// Szűrő opcionális argumentummal +$latte->addFilter('shortify', function (string $s, int $len = 10): string { + return mb_substr($s, 0, $len); +}); + +// Tömböt feldolgozó szűrő +$latte->addFilter('sum', fn(array $numbers): int|float => array_sum($numbers)); +``` + +**Használat a sablonban:** + +```latte +{$name|initial} {* Kiírja 'J.' ha $name 'John' *} +{$description|shortify} {* Az alapértelmezett 10-es hosszt használja *} +{$description|shortify:50} {* 50-es hosszt használ *} +{$prices|sum} {* Kiírja a $prices tömb elemeinek összegét *} +``` + +**Argumentumok átadása:** + +A csővezeték (`|`) bal oldalán lévő érték mindig a szűrő függvény *első* argumentumaként kerül átadásra. A sablonban a kettőspont (`:`) után megadott paraméterek a következő argumentumokként kerülnek átadásra. + +```latte +{$text|shortify:30} +// Meghívja a shortify($text, 30) PHP függvényt +``` + + +Regisztráció bővítményen keresztül +---------------------------------- + +A jobb szervezés érdekében, különösen újrafelhasználható szűrőkészletek létrehozásakor vagy csomagként való megosztásukkor, az ajánlott módszer a [Latte bővítményben |extending-latte#Latte Extension] való regisztrálásuk: + +```php +namespace App\Latte; + +use Latte\Extension; + +class MyLatteExtension extends Extension +{ + public function getFilters(): array + { + return [ + 'initial' => $this->initial(...), + 'shortify' => $this->shortify(...), + ]; + } + + public function initial(string $s): string + { + return mb_substr($s, 0, 1) . '.'; + } + + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// Regisztráció +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +Ez a megközelítés beágyazva tartja a szűrő logikáját, és egyszerűvé teszi a regisztrációt. + + +Szűrőbetöltő használata +----------------------- + +A Latte lehetővé teszi egy szűrőbetöltő regisztrálását az `addFilterLoader()` segítségével. Ez egyetlen callable, amelyet a Latte bármely ismeretlen szűrőnévre megkérdez a fordítás során. A betöltő visszaadja a szűrő PHP callable-ját vagy `null`-t. + +```php +$latte = new Latte\Engine; + +// A betöltő dinamikusan hozhat létre/szerezhet be szűrő callable-okat +$latte->addFilterLoader(function (string $name): ?callable { + if ($name === 'myLazyFilter') { + // Képzeljünk el itt egy költséges inicializálást... + $service = get_some_expensive_service(); + return fn($value) => $service->process($value); + } + return null; +}); +``` + +Ezt a módszert elsősorban a nagyon **költséges inicializálású** szűrők lusta betöltésére szánták. Azonban a modern dependency injection gyakorlatok általában hatékonyabban kezelik a lusta szolgáltatásokat. + +A szűrőbetöltők bonyolultságot adnak hozzá, és általában nem ajánlottak a közvetlen regisztrációval szemben az `addFilter()` vagy a bővítményen belüli `getFilters()` segítségével. Csak akkor használjon betöltőket, ha súlyos, specifikus oka van a szűrők inicializálásával kapcsolatos teljesítményproblémákra, amelyeket másképp nem lehet megoldani. + + +Szűrők osztály használatával attribútumokkal +-------------------------------------------- + +Egy másik elegáns módja a szűrők definiálásának a metódusok használata a [sablon paraméter osztályában |develop#Paraméterek osztályként]. Csak adjon hozzá egy `#[Latte\Attributes\TemplateFilter]` attribútumot a metódushoz. + +```php +use Latte\Attributes\TemplateFilter; + +class TemplateParameters +{ + public function __construct( + public string $description, + // további paraméterek... + ) {} + + #[TemplateFilter] + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// Objektum átadása a sablonnak +$params = new TemplateParameters(description: '...'); +$latte->render('template.latte', $params); +``` + +A Latte automatikusan felismeri és regisztrálja az ezzel az attribútummal megjelölt metódusokat, amikor a `TemplateParameters` objektumot átadják a sablonnak. A szűrő neve a sablonban ugyanaz lesz, mint a metódus neve (`shortify` ebben az esetben). + +```latte +{* A paraméter osztályban definiált szűrő használata *} +{$description|shortify:50} +``` + + +Kontextusérzékeny szűrők +======================== + +Néha egy szűrőnek több információra van szüksége, mint csak a bemeneti érték. Szüksége lehet tudni a string **tartalomtípusát**, amellyel dolgozik (pl. HTML, JavaScript, egyszerű szöveg), vagy akár módosítani is azt. Ez a helyzet a kontextusérzékeny szűrők esetében. + +Egy kontextusérzékeny szűrő ugyanúgy definiálódik, mint egy normál szűrő, de az **első paraméterének** `Latte\Runtime\FilterInfo` típusúnak kell lennie. A Latte automatikusan felismeri ezt az aláírást, és a szűrő hívásakor átadja a `FilterInfo` objektumot. A következő paraméterek a szűrő argumentumait kapják meg a szokásos módon. + +```php +use Latte\Runtime\FilterInfo; +use Latte\ContentType; + +$latte->addFilter('money', function (FilterInfo $info, float $amount): string { + // 1. Ellenőrizze a bemeneti tartalomtípust (opcionális, de ajánlott) + // Engedélyezze a null-t (változó bemenet) vagy az egyszerű szöveget. Utasítsa el, ha HTML-re stb. alkalmazzák. + if (!in_array($info->contentType, [null, ContentType::Text], true)) { + $actualType = $info->contentType ?? 'mixed'; + throw new \RuntimeException( + "A |money szűrő inkompatibilis tartalomtípusban ($actualType) lett használva. Szöveg vagy null volt elvárva." + ); + } + + // 2. Végezze el az átalakítást + $formatted = number_format($amount, 2, '.', ',') . ' EUR'; + $htmlOutput = '' . htmlspecialchars($formatted) . ''; // Biztosítsa a helyes escapelést! + + // 3. Deklarálja a kimeneti tartalomtípust + $info->contentType = ContentType::Html; + + // 4. Adja vissza az eredményt + return $htmlOutput; +}); +``` + +A `$info->contentType` egy string konstans a `Latte\ContentType`-ból (pl. `ContentType::Html`, `ContentType::Text`, `ContentType::JavaScript` stb.) vagy `null`, ha a szűrőt egy változóra alkalmazzák (`{$var|filter}`). Ezt az értéket **olvashatja**, hogy ellenőrizze a bemeneti kontextust, és **írhatja** bele, hogy deklarálja a kimeneti kontextus típusát. + +A tartalomtípus HTML-re állításával közli a Latte-val, hogy a szűrő által visszaadott string biztonságos HTML. A Latte ezután **nem** alkalmazza erre az eredményre az alapértelmezett automatikus escapelést. Ez elengedhetetlen, ha a szűrő HTML jelölést generál. + +.[warning] +Ha a szűrő HTML-t generál, **Ön felelős a benne használt bármely bemeneti adat helyes escapeléséért** (mint a fenti `htmlspecialchars($formatted)` hívás esetén). Ennek elmulasztása XSS sebezhetőségeket hozhat létre. Ha a szűrő csak egyszerű szöveget ad vissza, nem kell beállítania a `$info->contentType`-t. + + +Szűrők blokkokon +---------------- + +Minden [blokkokra |tags#block] alkalmazott szűrőnek *kontextusérzékenynek kell lennie*. Ez azért van, mert a blokk tartalmának van egy definiált tartalomtípusa (általában HTML), amelyről a szűrőnek tudnia kell. + +```latte +{block heading|money}1000{/block} +{* A 'money' szűrő a '1000'-et kapja második argumentumként + és a $info->contentType ContentType::Html lesz *} +``` + +A kontextusérzékeny szűrők erőteljes vezérlést biztosítanak az adatok feldolgozása felett azok kontextusa alapján, lehetővé téve a fejlett funkciókat és biztosítva a helyes escapelési viselkedést, különösen HTML tartalom generálásakor. diff --git a/latte/hu/custom-functions.texy b/latte/hu/custom-functions.texy new file mode 100644 index 0000000000..ba9f625d19 --- /dev/null +++ b/latte/hu/custom-functions.texy @@ -0,0 +1,144 @@ +Egyéni függvények létrehozása +***************************** + +.[perex] +Könnyedén adjon hozzá egyéni segédfüggvényeket a Latte sablonokhoz. Hívjon PHP logikát közvetlenül a kifejezésekben számításokhoz, szolgáltatások eléréséhez vagy dinamikus tartalom generálásához, ami tisztán és hatékonyan tartja a sablonjait. + + +Mik azok a függvények? +====================== + +A Latte függvényei lehetővé teszik a sablonokban (`{...}`) a kifejezéseken belül hívható függvények körének kibővítését. Úgy gondolhat rájuk, mint **egyéni PHP függvényekre, amelyek csak a Latte sablonokon belül érhetők el**. Ez számos előnnyel jár: + +**Kényelem:** Definiálhat segédlogikát (mint számítások, formázás vagy alkalmazásadatok elérése), és egyszerű, ismerős függvényszintaxissal hívhatja meg közvetlenül a sablonban, ugyanúgy, ahogy a `strlen()`-t vagy a `date()`-t hívná PHP-ban. + +```latte +{var $userInitials = initials($userName)} {* pl. 'J. D.' *} + +{if hasPermission('article', 'edit')} + Szerkesztés +{/if} +``` + +**Nincs globális tér szennyezése:** Ellentétben egy valódi globális függvény definiálásával PHP-ban, a Latte függvények csak a sablon renderelési kontextusában léteznek. Nem kell terhelnie a PHP globális névterét olyan segédfüggvényekkel, amelyek csak a sablonokhoz specifikusak. + +**Integráció az alkalmazáslogikával:** A Latte függvény mögött álló PHP callable bármi lehet – anonim függvény, statikus metódus vagy példány metódus. Ez azt jelenti, hogy a sablonfüggvényei könnyen hozzáférhetnek az alkalmazás szolgáltatásaihoz, adatbázisaihoz, konfigurációjához vagy bármely más szükséges logikához változók rögzítésével (anonim függvények esetén) vagy dependency injection használatával (objektumok esetén). A fenti `hasPermission` példa ezt világosan demonstrálja, amikor valószínűleg egy jogosultságkezelő szolgáltatást hív a háttérben. + +**Natív függvények felülírása (opcionális):** Még egy Latte függvényt is definiálhat ugyanazzal a névvel, mint egy natív PHP függvény. A sablonban a saját verziója lesz meghívva az eredeti függvény helyett. Ez hasznos lehet sablon-specifikus viselkedés biztosítására vagy konzisztens feldolgozás biztosítására (pl. annak biztosítása, hogy a `strlen` mindig több bájtos biztonságú legyen). Ezt a funkciót óvatosan használja a félreértések elkerülése érdekében. + +Alapértelmezés szerint a Latte *minden* natív PHP függvény hívását engedélyezi (hacsak a [Sandbox |sandbox] nem korlátozza). Az egyéni függvények kibővítik ezt a beépített könyvtárat a projekt specifikus igényeivel. + +Ha csak egyetlen értéket alakít át, lehet, hogy megfelelőbb egy [egyéni szűrő |custom-filters] használata. + + +Függvények létrehozása és regisztrálása +======================================= + +Hasonlóan a szűrőkhöz, több módja van az egyéni függvények definiálásának és regisztrálásának. + + +Közvetlen regisztráció az `addFunction()` segítségével +------------------------------------------------------ + +A legegyszerűbb módszer az `addFunction()` használata a `Latte\Engine` objektumon. Megadja a függvény nevét (ahogyan a sablonban megjelenik) és a megfelelő PHP callable objektumot. + +```php +$latte = new Latte\Engine; + +// Egyszerű segédfüggvény +$latte->addFunction('initials', function (string $name): string { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; +}); +``` + +**Használat a sablonban:** + +```latte +{var $userInitials = initials($userName)} +``` + +A sablonban lévő függvény argumentumai közvetlenül a PHP callable objektumnak kerülnek átadásra ugyanabban a sorrendben. A PHP funkciók, mint a típusjelzések, alapértelmezett értékek és variadikus paraméterek (`...`), a várt módon működnek. + + +Regisztráció bővítményen keresztül +---------------------------------- + +A jobb szervezés és újrafelhasználhatóság érdekében regisztrálja a függvényeket egy [Latte bővítményben |extending-latte#Latte Extension]. Ez a megközelítés ajánlott összetettebb alkalmazásokhoz vagy megosztott könyvtárakhoz. + +```php +namespace App\Latte; + +use Latte\Extension; +use Nette\Security\Authorizator; + +class MyLatteExtension extends Extension +{ + public function __construct( + // Feltételezzük, hogy az Authorizator szolgáltatás létezik + private Authorizator $authorizator, + ) { + } + + public function getFunctions(): array + { + // Metódusok regisztrálása Latte függvényként + return [ + 'hasPermission' => $this->hasPermission(...), + ]; + } + + public function hasPermission(string $resource, string $action): bool + { + return $this->authorizator->isAllowed($resource, $action); + } +} + +// Regisztráció (feltételezzük, hogy a $container tartalmazza a DIC-t) +$extension = $container->getByType(App\Latte\MyLatteExtension::class); +$latte = new Latte\Engine; +$latte->addExtension($extension); +``` + +Ez a megközelítés jól szemlélteti, hogyan támogathatják a Latte-ban definiált függvényeket olyan objektumok metódusai, amelyeknek saját függőségeik lehetnek, amelyeket az alkalmazás dependency injection konténere vagy factory-ja kezel. Ez összekapcsolja a sablonok logikáját az alkalmazás magjával, miközben megőrzi a tiszta szervezést. + + +Függvények osztály használatával attribútumokkal +------------------------------------------------ + +Mint a szűrők, a függvények is definiálhatók metódusként a [sablon paraméter osztályában |develop#Paraméterek osztályként] a `#[Latte\Attributes\TemplateFunction]` attribútum használatával. + +```php +use Latte\Attributes\TemplateFunction; + +class TemplateParameters +{ + public function __construct( + public string $userName, + // további paraméterek... + ) {} + + // Ez a metódus elérhető lesz mint {initials(...)} a sablonban + #[TemplateFunction] + public function initials(string $name): string + { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; + } +} + +// Objektum átadása a sablonnak +$params = new TemplateParameters(userName: 'John Doe', /* ... */); +$latte->render('template.latte', $params); +``` + +A Latte automatikusan felfedezi és regisztrálja az ezzel az attribútummal megjelölt metódusokat, amikor a paraméter objektumot átadják a sablonnak. A függvény neve a sablonban megegyezik a metódus nevével. + +```latte +{* A paraméter osztályban definiált függvény használata *} +{var $inits = initials($userName)} +``` + +**Kontextusérzékeny függvények?** + +Ellentétben a szűrőkkel, nincs közvetlen "kontextusérzékeny függvény" koncepció, amely egy `FilterInfo`-hoz hasonló objektumot kapna. A függvények a kifejezéseken belül működnek, és általában nincs szükségük közvetlen hozzáférésre a renderelési kontextushoz vagy a tartalomtípus információkhoz ugyanúgy, mint a blokkokra alkalmazott szűrőknek. diff --git a/latte/hu/custom-tags.texy b/latte/hu/custom-tags.texy new file mode 100644 index 0000000000..f8e2946305 --- /dev/null +++ b/latte/hu/custom-tags.texy @@ -0,0 +1,1135 @@ +Egyéni tagek létrehozása +************************ + +.[perex] +Ez az oldal átfogó útmutatót nyújt az egyéni tagek létrehozásához a Latte-ban. Mindent megvitatunk az egyszerű tagektől a bonyolultabb, beágyazott tartalommal és specifikus parzolási igényekkel rendelkező forgatókönyvekig, építve arra a megértésre, hogy a Latte hogyan fordítja a sablonokat. + +Az egyéni tagek a legmagasabb szintű ellenőrzést biztosítják a sablon szintaxisa és a renderelési logika felett, de egyben a legbonyolultabb bővítési pontot is jelentik. Mielőtt úgy döntene, hogy egyéni taget hoz létre, mindig fontolja meg, hogy [nincs egyszerűbb megoldás |extending-latte#A Latte bővítésének módjai], vagy hogy nem létezik-e már megfelelő tag a [standard készletben |tags]. Csak akkor használjon egyéni tageket, ha az egyszerűbb alternatívák nem elegendőek az Ön igényeihez. + + +A fordítási folyamat megértése +============================== + +Az egyéni tagek hatékony létrehozásához hasznos elmagyarázni, hogyan dolgozza fel a Latte a sablonokat. Ennek a folyamatnak a megértése tisztázza, hogy a tagek miért éppen így vannak strukturálva, és hogyan illeszkednek a tágabb kontextusba. + +A sablon fordítása a Latte-ban, leegyszerűsítve, a következő kulcsfontosságú lépéseket tartalmazza: + +1. **Lexikális elemzés:** A lexer beolvassa a sablon forráskódját (a `.latte` fájlt), és kis, különálló részekre, úgynevezett **tokenekre** bontja (pl. `{`, `foreach`, `$variable`, `}`, HTML szöveg stb.). +2. **Parzolás:** A parser veszi ezt a tokenfolyamot, és egy értelmes fastruktúrát épít belőle, amely a sablon logikáját és tartalmát reprezentálja. Ezt a fát **absztrakt szintaxisfának (AST)** nevezik. +3. **Fordítási menetek:** A PHP kód generálása előtt a Latte [fordítási meneteket |compiler-passes] futtat. Ezek olyan függvények, amelyek végigjárják a teljes AST-t, és módosíthatják azt, vagy információkat gyűjthetnek. Ez a lépés kulcsfontosságú az olyan funkciókhoz, mint a biztonság ([Sandbox |sandbox]) vagy az optimalizálás. +4. **Kódgenerálás:** Végül a fordító végigjárja a (potenciálisan módosított) AST-t, és generálja a megfelelő PHP osztálykódot. Ez a PHP kód az, ami ténylegesen rendereli a sablont futás közben. +5. **Gyorsítótárazás:** A generált PHP kód a lemezre kerül mentésre, ami a későbbi rendereléseket nagyon gyorssá teszi, mivel az 1-4. lépések kihagyásra kerülnek. + +Valójában a fordítás egy kicsit bonyolultabb. A Latte **két** lexerrel és parserrel rendelkezik: egy a HTML sablonhoz és egy másik a tageken belüli PHP-szerű kódhoz. És a parzolás sem a tokenizálás után történik, hanem a lexer és a parser párhuzamosan fut két "szálon", és koordinálnak. Hidd el, ennek a programozása rakétatudomány volt :-) + +Az egész folyamat, a sablon tartalmának betöltésétől a parzoláson át a végső fájl generálásáig, ezzel a kóddal szekvenálható, amellyel kísérletezhet és kiírathatja a köztes eredményeket: + +```php +$latte = new Latte\Engine; +$source = $latte->getLoader()->getContent($file); +$ast = $latte->parse($source); +$latte->applyPasses($ast); +$code = $latte->generate($ast, $file); +``` + + +Egy tag anatómiája +================== + +Egy teljesen működőképes egyéni tag létrehozása a Latte-ban több összekapcsolt részből áll. Mielőtt belekezdenénk a implementációba, értsük meg az alapvető koncepciókat és terminológiát, a HTML és a Document Object Model (DOM) analógiáját használva. + + +Tagek vs. Csomópontok (Analógia a HTML-lel) +------------------------------------------- + +A HTML-ben **tageket** írunk, mint `

                                                                      ` vagy `

                                                                      ...
                                                                      `. Ezek a tagek a forráskódban lévő szintaxist jelentik. Amikor a böngésző parzolja ezt a HTML-t, létrehoz egy memóriabeli reprezentációt, amelyet **Document Object Model (DOM)**-nak neveznek. A DOM-ban a HTML tageket **csomópontok** reprezentálják (konkrétan `Element` csomópontok a JavaScript DOM terminológiájában). Ezekkel a *csomópontokkal* dolgozunk programozottan (pl. a JavaScript `document.getElementById(...)` egy Element csomópontot ad vissza). A tag csak egy szöveges reprezentáció a forrásfájlban; a csomópont egy objektum reprezentáció a logikai fában. + +A Latte hasonlóan működik: + +- A `.latte` sablonfájlban **Latte tageket** ír, mint `{foreach ...}` és `{/foreach}`. Ez az a szintaxis, amellyel Ön, mint sablon szerző dolgozik. +- Amikor a Latte **parzolja** a sablont, egy **Absztrakt Szintaxisfát (AST)** épít. Ez a fa **csomópontokból** áll. Minden Latte tag, HTML elem, szövegrész vagy kifejezés a sablonban egy vagy több csomóponttá válik ebben a fában. +- Az AST összes csomópontjának alaposztálya a `Latte\Compiler\Node`. Ahogy a DOM-nak különböző típusú csomópontjai vannak (Element, Text, Comment), úgy a Latte AST-jének is különböző típusú csomópontjai vannak. Találkozni fog a `Latte\Compiler\Nodes\TextNode`-dal a statikus szöveghez, a `Latte\Compiler\Nodes\Html\ElementNode`-dal a HTML elemekhez, a `Latte\Compiler\Nodes\Php\ExpressionNode`-dal a tageken belüli kifejezésekhez, és kulcsfontosságúan az egyéni tagekhez, a `Latte\Compiler\Nodes\StatementNode`-ból öröklődő csomópontokkal. + + +Miért `StatementNode`? +---------------------- + +A HTML elemek (`Html\ElementNode`) elsősorban struktúrát és tartalmat reprezentálnak. A PHP kifejezések (`Php\ExpressionNode`) értékeket vagy számításokat reprezentálnak. De mi a helyzet az olyan Latte tagekkel, mint `{if}`, `{foreach}` vagy a saját `{datetime}` tagünk? Ezek a tagek *akciókat hajtanak végre*, vezérlik a programfolyamatot, vagy logikán alapuló kimenetet generálnak. Funkcionális egységek, amelyek a Latte-t erőteljes sablon *motorrá* teszik, nem csak egy jelölőnyelvvé. + +A programozásban az ilyen akciókat végrehajtó egységeket gyakran "statements"-nek (utasításoknak) nevezik. Ezért az ezeket a funkcionális Latte tageket reprezentáló csomópontok tipikusan a `Latte\Compiler\Nodes\StatementNode`-ból öröklődnek. Ez megkülönbözteti őket a tisztán strukturális csomópontoktól (mint a HTML elemek) vagy az értékeket reprezentáló csomópontoktól (mint a kifejezések). + + +Kulcsfontosságú komponensek +=========================== + +Nézzük át a fő komponenseket, amelyek egy egyéni tag létrehozásához szükségesek: + + +Tag parzoló függvény +-------------------- + +- Ez a PHP callable függvény parzolja a Latte tag szintaxisát (`{...}`) a forrássablonban. +- Információkat kap a tagről (mint a neve, pozíciója és hogy n:attribútum-e) a [api:Latte\Compiler\Tag] objektumon keresztül. +- Elsődleges eszköze az argumentumok és kifejezések parzolására a tag határolóin belül a [api:Latte\Compiler\TagParser] objektum, amely a `$tag->parser`-en keresztül érhető el (ez egy másik parser, mint az, amelyik az egész sablont parzolja). +- Páros tagek esetén a `yield` segítségével jelzi a Latte-nak, hogy parzolja a kezdő és záró tag közötti belső tartalmat. +- A parzoló függvény végső célja egy **csomópont osztály** példányának létrehozása és visszaadása, amely hozzáadódik az AST-hez. +- Szokás (bár nem kötelező) a parzoló függvényt statikus metódusként (gyakran `create`-nek nevezve) implementálni közvetlenül a megfelelő csomópont osztályban. Ez a parzolási logikát és a csomópont reprezentációját szépen egy csomagban tartja, lehetővé teszi az osztály privát/védett elemeihez való hozzáférést, ha szükséges, és javítja a szervezést. + + +Csomópont osztály +----------------- + +- Reprezentálja a tag *logikai funkcióját* az **Absztrakt Szintaxisfában (AST)**. +- Tartalmazza a parzolt információkat (mint argumentumok vagy tartalom) publikus property-ként. Ezek a property-k gyakran más `Node` példányokat tartalmaznak (pl. `ExpressionNode` a parzolt argumentumokhoz, `AreaNode` a parzolt tartalomhoz). +- A `print(PrintContext $context): string` metódus generálja a *PHP kódot* (utasítást vagy utasítássorozatot), amely végrehajtja a tag akcióját a sablon renderelése során. +- A `getIterator(): \Generator` metódus hozzáférhetővé teszi a gyermek csomópontokat (argumentumok, tartalom) a **fordítási menetek** számára. Referenciákat (`&`) kell biztosítania, hogy lehetővé tegye a menetek számára a potenciális módosítást vagy a gyermek csomópontok cseréjét. +- Miután az egész sablon AST-vé parzolódott, a Latte egy sor [fordítási menetet |compiler-passes] futtat. Ezek a menetek végigjárják a *teljes* AST-t a minden csomópont által biztosított `getIterator()` metódus segítségével. Ellenőrizhetik a csomópontokat, információkat gyűjthetnek, és akár *módosíthatják* is a fát (pl. a csomópontok publikus property-jeinek megváltoztatásával vagy a csomópontok teljes cseréjével). Ez a tervezés, amely egy komplex `getIterator()`-t igényel, alapvető. Lehetővé teszi az olyan erőteljes funkciók számára, mint a [Sandbox |sandbox], hogy elemezzék és potenciálisan megváltoztassák a sablon *bármely* részének viselkedését, beleértve az Ön egyéni tagjeit is, biztosítva a biztonságot és a konzisztenciát. + + +Regisztráció kiterjesztésen keresztül +------------------------------------- + +- Tájékoztatnia kell a Latte-t az új tagről és arról, hogy melyik parzoló függvényt kell hozzá használni. Ez egy [Latte kiterjesztésen |extending-latte#Latte Extension] belül történik. +- A kiterjesztés osztályán belül implementálja a `getTags(): array` metódust. Ez a metódus egy asszociatív tömböt ad vissza, ahol a kulcsok a tag nevek (pl. `'mytag'`, `'n:myattribute'`), az értékek pedig a PHP callable függvények, amelyek a megfelelő parzoló függvényeiket reprezentálják (pl. `MyNamespace\DatetimeNode::create(...)`). + +Összefoglalás: A **tag parzoló függvény** átalakítja a tag *forráskódját a sablonban* egy **AST csomóponttá**. A **csomópont osztály** ezután képes átalakítani *önmagát* futtatható *PHP kóddá* a fordított sablonhoz, és hozzáférhetővé teszi a gyermek csomópontjait a **fordítási menetek** számára a `getIterator()`-on keresztül. A **regisztráció kiterjesztésen keresztül** összekapcsolja a tag nevét a parzoló függvénnyel, és tudatja a Latte-val. + +Most megvizsgáljuk, hogyan implementáljuk ezeket a komponenseket lépésről lépésre. + + +Egyszerű tag létrehozása +======================== + +Kezdjünk bele az első egyéni Latte tag létrehozásába. Egy nagyon egyszerű példával kezdünk: egy `{datetime}` nevű taggel, amely kiírja az aktuális dátumot és időt. **Kezdetben ez a tag nem fogad argumentumokat**, de később javítjuk a [#"Tag argumentumok parzolása"] szakaszban. Nincs belső tartalma sem. + +Ez a példa végigvezet az alapvető lépéseken: a csomópont osztály definiálása, a `print()` és `getIterator()` metódusainak implementálása, a parzoló függvény létrehozása, és végül a tag regisztrálása. + +**Cél:** Implementálni a `{datetime}` taget az aktuális dátum és idő kiírására a PHP `date()` függvényével. + + +A csomópont osztály létrehozása +------------------------------- + +Először szükségünk van egy osztályra, amely reprezentálja a tagünket az Absztrakt Szintaxisfában (AST). Ahogy fentebb tárgyaltuk, a `Latte\Compiler\Nodes\StatementNode`-ból öröklünk. + +Hozzon létre egy fájlt (pl. `DatetimeNode.php`) és definiálja az osztályt: + +```php +node = new self; + return $node; + } + + /** + * Generálja a PHP kódot, amely a sablon renderelésekor fut le. + */ + public function print(PrintContext $context): string + { + return $context->format( + 'echo date(\'Y-m-d H:i:s\') %line;', + $this->position, + ); + } + + /** + * Hozzáférést biztosít a gyermek csomópontokhoz a Latte fordítási menetei számára. + */ + public function &getIterator(): \Generator + { + false && yield; + } +} +``` + +Amikor a Latte találkozik a `{datetime}` taggel a sablonban, meghívja a `create()` parzoló függvényt. Ennek feladata egy `DatetimeNode` példány visszaadása. + +A `print()` metódus generálja a PHP kódot, amely a sablon renderelésekor fut le. Meghívjuk a `$context->format()` metódust, amely összeállítja a végső PHP kódsztringet a fordított sablonhoz. Az első argumentum, `'echo date('Y-m-d H:i:s') %line;'`, egy maszk, amelybe a következő paraméterek kerülnek beillesztésre. A `%line` helyettesítő karakter azt mondja a `format()` metódusnak, hogy használja a második argumentumot, ami a `$this->position`, és illesszen be egy kommentárt, mint `/* line 15 */`, amely összekapcsolja a generált PHP kódot a sablon eredeti sorával, ami kulcsfontosságú a debuggoláshoz. + +A `$this->position` property az alap `Node` osztályból öröklődik, és a Latte parser automatikusan beállítja. Egy [api:Latte\Compiler\Position] objektumot tartalmaz, amely jelzi, hol található a tag a forrás `.latte` fájlban. + +A `getIterator()` metódus alapvető a fordítási menetekhez. Minden gyermek csomópontot biztosítania kell, de az egyszerű `DatetimeNode`-unknak jelenleg nincsenek argumentumai vagy tartalma, tehát nincsenek gyermek csomópontjai. Azonban a metódusnak továbbra is léteznie kell, és generátornak kell lennie, azaz a `yield` kulcsszónak valamilyen módon jelen kell lennie a metódus törzsében. + + +Regisztráció kiterjesztésen keresztül +------------------------------------- + +Végül tájékoztassuk a Latte-t az új tagről. Hozzon létre egy [kiterjesztés osztályt |extending-latte#Latte Extension] (pl. `MyLatteExtension.php`) és regisztrálja a taget a `getTags()` metódusában. + +```php + Térkép: 'tag-neve' => parzolo-fuggveny + */ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + // Később itt regisztráljon több taget + ]; + } +} +``` + +Ezután regisztrálja ezt a kiterjesztést a Latte Engine-ben: + +```php +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +Hozzon létre egy sablont: + +```latte +

                                                                      Az oldal generálva: {datetime}

                                                                      +``` + +Várható kimenet: `

                                                                      Az oldal generálva: 2023-10-27 11:00:00

                                                                      ` + + +Ennek a fázisnak az összefoglalása +---------------------------------- + +Sikeresen létrehoztunk egy alapvető egyéni `{datetime}` taget. Definiáltuk a reprezentációját az AST-ben (`DatetimeNode`), kezeltük a parzolását (`create()`), meghatároztuk, hogyan kell PHP kódot generálnia (`print()`), biztosítottuk, hogy a gyermekei hozzáférhetők legyenek a menetek számára (`getIterator()`), és regisztráltuk a Latte-ban. + +A következő szakaszban javítjuk ezt a taget, hogy argumentumokat fogadjon el, és megmutatjuk, hogyan kell kifejezéseket parzolni és gyermek csomópontokat kezelni. + + +Tag argumentumok parzolása +========================== + +Az egyszerű `{datetime}` tagünk működik, de nem túl rugalmas. Javítsuk meg, hogy elfogadjon egy opcionális argumentumot: egy formázó sztringet a `date()` függvényhez. A kívánt szintaxis `{datetime $format}` lesz. + +**Cél:** Módosítani a `{datetime}` taget úgy, hogy elfogadjon egy opcionális PHP kifejezést argumentumként, amelyet a `date()` formázó sztringjeként használunk. + + +A `TagParser` bemutatása +------------------------ + +Mielőtt módosítanánk a kódot, fontos megérteni az eszközt, amelyet használni fogunk: a [api:Latte\Compiler\TagParser]-t. Amikor a Latte fő parsere (`TemplateParser`) találkozik egy Latte taggel, mint `{datetime ...}` vagy egy n:attribútummal, a tag *belsejének* (a `{` és `}` közötti résznek vagy az attribútum értékének) parzolását egy specializált `TagParser`-re delegálja. + +Ez a `TagParser` kizárólag a **tag argumentumokkal** dolgozik. Feladata a tokenek feldolgozása, amelyek ezeket az argumentumokat reprezentálják. Kulcsfontosságú, hogy **fel kell dolgoznia a teljes tartalmat**, amelyet kap. Ha a parzoló függvény befejeződik, de a `TagParser` nem érte el az argumentumok végét (ellenőrizve a `$tag->parser->isEnd()`-en keresztül), a Latte kivételt dob, mert ez azt jelzi, hogy váratlan tokenek maradtak a tagen belül. Ezzel szemben, ha a tag argumentumokat *követel meg*, akkor a parzoló függvény elején meg kell hívnia a `$tag->expectArguments()`-t. Ez a metódus ellenőrzi, hogy vannak-e argumentumok, és segítőkész kivételt dob, ha a taget argumentumok nélkül használták. + +A `TagParser` hasznos metódusokat kínál különböző típusú argumentumok parzolására: + +- `parseExpression(): ExpressionNode`: Parzol egy PHP-szerű kifejezést (változók, literálok, operátorok, függvény/metódus hívások stb.). Kezeli a Latte szintaktikai cukrait, mint például az egyszerű alfanumerikus sztringek idézőjeles sztringként való kezelése (pl. a `foo` úgy parzolódik, mintha `'foo'` lenne). +- `parseUnquotedStringOrExpression(): ExpressionNode`: Parzol vagy egy standard kifejezést, vagy egy *idézőjel nélküli sztringet*. Az idézőjel nélküli sztringek a Latte által engedélyezett, idézőjelek nélküli szekvenciák, amelyeket gyakran használnak olyan dolgokhoz, mint a fájlútvonalak (pl. `{include ../file.latte}`). Ha idézőjel nélküli sztringet parzol, `StringNode`-ot ad vissza. +- `parseArguments(): ArrayNode`: Parzol vesszővel elválasztott argumentumokat, potenciálisan kulcsokkal, mint `10, name: 'John', true`. +- `parseModifier(): ModifierNode`: Parzol szűrőket, mint `|upper|truncate:10`. +- `parseType(): ?SuperiorTypeNode`: Parzol PHP típus-hintet, mint `int`, `?string`, `array|Foo`. + +Bonyolultabb vagy alacsonyabb szintű parzolási igényekhez közvetlenül interakcióba léphet a [tokenfolyammal |api:Latte\Compiler\TokenStream] a `$tag->parser->stream`-en keresztül. Ez az objektum metódusokat biztosít az egyes tokenek ellenőrzésére és feldolgozására: + +- `$tag->parser->stream->is(...): bool`: Ellenőrzi, hogy az *aktuális* token megfelel-e a megadott típusoknak (pl. `Token::Php_Variable`) vagy literális értékeknek (pl. `'as'`) anélkül, hogy elfogyasztaná. Hasznos előretekintéshez. +- `$tag->parser->stream->consume(...): Token`: Elfogyasztja az *aktuális* tokent, és előre mozgatja a folyam pozícióját. Ha várt token típusok/értékek vannak megadva argumentumként, és az aktuális token nem felel meg, `CompileException`-t dob. Használja ezt, ha *vár* egy bizonyos tokent. +- `$tag->parser->stream->tryConsume(...): ?Token`: Megpróbálja elfogyasztani az *aktuális* tokent *csak akkor, ha* megfelel a megadott típusok/értékek egyikének. Ha megfelel, elfogyasztja a tokent és visszaadja. Ha nem felel meg, változatlanul hagyja a folyam pozícióját és `null`-t ad vissza. Használja ezt opcionális tokenekhez, vagy ha különböző szintaktikai utak között választ. + + +A `create()` parzoló függvény frissítése +---------------------------------------- + +Ezzel a megértéssel módosítsuk a `create()` metódust a `DatetimeNode`-ban, hogy parzolja az opcionális formátum argumentumot a `$tag->parser` segítségével. + +```php +node = new self; + + // Ellenőrizzük, hogy vannak-e tokenek + if (!$tag->parser->isEnd()) { + // Parzoljuk az argumentumot PHP-szerű kifejezésként a TagParser segítségével. + $node->format = $tag->parser->parseExpression(); + } + + return $node; + } + + // ... a print() és getIterator() metódusok később frissülnek ... +} +``` + +Hozzáadtunk egy publikus `$format` property-t. A `create()`-ben most a `$tag->parser->isEnd()`-t használjuk annak ellenőrzésére, hogy *léteznek-e* argumentumok. Ha igen, a `$tag->parser->parseExpression()` feldolgozza a kifejezés tokenjeit. Mivel a `TagParser`-nek fel kell dolgoznia az összes bemeneti tokent, a Latte automatikusan hibát dob, ha a felhasználó valami váratlant ír a formátum kifejezés után (pl. `{datetime 'Y-m-d', unexpected}`). + + +A `print()` metódus frissítése +------------------------------ + +Most módosítsuk a `print()` metódust, hogy használja a `$this->format`-ban tárolt parzolt formátum kifejezést. Ha nem adtak meg formátumot (`$this->format` `null`), akkor egy alapértelmezett formázó sztringet kell használnunk, például `'Y-m-d H:i:s'`. + +```php + public function print(PrintContext $context): string + { + $formatNode = $this->format ?? new StringNode('Y-m-d H:i:s'); + + // A %node kiírja a $formatNode PHP kód reprezentációját. + return $context->format( + 'echo date(%node) %line;', + $formatNode, + $this->position + ); + } +``` + +A `$formatNode` változóba tároljuk az AST csomópontot, amely a PHP `date()` függvény formázó sztringjét reprezentálja. Itt a null coalescing operátort (`??`) használjuk. Ha a felhasználó megadott egy argumentumot a sablonban (pl. `{datetime 'd.m.Y'}`), akkor a `$this->format` property a megfelelő csomópontot tartalmazza (ebben az esetben egy `StringNode`-ot `'d.m.Y'` értékkel), és ezt a csomópontot használjuk. Ha a felhasználó nem adott meg argumentumot (csak `{datetime}`-et írt), a `$this->format` property `null`, és ehelyett létrehozunk egy új `StringNode`-ot az alapértelmezett `'Y-m-d H:i:s'` formátummal. Ez biztosítja, hogy a `$formatNode` mindig egy érvényes AST csomópontot tartalmazzon a formátumhoz. + +Az `'echo date(%node) %line;'` maszkban egy új `%node` helyettesítő karaktert használunk, amely azt mondja a `format()` metódusnak, hogy vegye az első következő argumentumot (ami a mi `$formatNode`-unk), hívja meg annak `print()` metódusát (amely visszaadja a PHP kód reprezentációját), és illessze be az eredményt a helyettesítő karakter pozíciójába. + + +A `getIterator()` implementálása gyermek csomópontokhoz +------------------------------------------------------- + +A `DatetimeNode`-unknak most van egy gyermek csomópontja: a `$format` kifejezés. **Muszáj** ezt a gyermek csomópontot hozzáférhetővé tennünk a fordítási menetek számára a `getIterator()` metódusban történő biztosítással. Ne felejtsen el *referenciát* (`&`) biztosítani, hogy lehetővé tegye a menetek számára a csomópont potenciális cseréjét. + +```php + public function &getIterator(): \Generator + { + if ($this->format) { + yield $this->format; + } + } +``` + +Miért alapvető ez? Képzeljen el egy Sandbox menetet, amelynek ellenőriznie kell, hogy a `$format` argumentum nem tartalmaz-e tiltott függvényhívást (pl. `{datetime dangerousFunction()}`). Ha a `getIterator()` nem biztosítja a `$this->format`-ot, a Sandbox menet soha nem látná a `dangerousFunction()` hívást a tagünk argumentumán belül, ami potenciális biztonsági rést hozna létre. Annak biztosításával lehetővé tesszük a Sandboxnak (és más meneteknek), hogy ellenőrizzék és potenciálisan módosítsák a `$format` kifejezés csomópontot. + + +A javított tag használata +------------------------- + +A tag most helyesen kezeli az opcionális argumentumot: + +```latte +Alapértelmezett formátum: {datetime} +Egyéni formátum: {datetime 'd.m.Y'} +Változó használata: {datetime $userDateFormatPreference} + +{* Ez hibát okozna a 'd.m.Y' parzolása után, mert a ", foo" váratlan *} +{* {datetime 'd.m.Y', foo} *} +``` + +Ezután megnézzük a páros tagek létrehozását, amelyek a közöttük lévő tartalmat dolgozzák fel. + + +Páros tagek kezelése +==================== + +Eddig a `{datetime}` tagünk *önlezáró* volt (koncepcionálisan). Nem volt tartalma a kezdő és záró tag között. Sok hasznos tag azonban egy sablon tartalomblokkjával dolgozik. Ezeket **páros tageknek** nevezik. Példák erre a `{if}...{/if}`, `{block}...{/block}` vagy egy egyéni tag, amelyet most létrehozunk: `{debug}...{/debug}`. + +Ez a tag lehetővé teszi számunkra, hogy hibakeresési információkat tartalmazzunk a sablonjainkban, amelyek csak fejlesztés közben legyenek láthatók. + +**Cél:** Létrehozni egy `{debug}` páros taget, amelynek tartalma csak akkor renderelődik, ha egy specifikus "fejlesztői mód" jelző aktív. + + +Szolgáltatók bemutatása +----------------------- + +Néha a tageknek olyan adatokhoz vagy szolgáltatásokhoz kell hozzáférniük, amelyeket nem közvetlenül sablon paraméterként adnak át. Például annak meghatározása, hogy az alkalmazás fejlesztői módban van-e, hozzáférés a felhasználói objektumhoz, vagy konfigurációs értékek lekérése. A Latte egy **szolgáltatók** (Providers) nevű mechanizmust biztosít erre a célra. + +A szolgáltatókat a [kiterjesztésben |extending-latte#Latte Extension] regisztráljuk a `getProviders()` metódus segítségével. Ez a metódus egy asszociatív tömböt ad vissza, ahol a kulcsok azok a nevek, amelyek alatt a szolgáltatók elérhetők lesznek a sablon futásidejű kódjában, az értékek pedig a tényleges adatok vagy objektumok. + +A tag `print()` metódusa által generált PHP kódon belül ezekhez a szolgáltatókhoz a `$this->global` objektum speciális property-jén keresztül férhet hozzá. Mivel ez a property megosztott az összes kiterjesztés között, jó gyakorlat **előtaggal ellátni a szolgáltatók nevét**, hogy elkerüljük a potenciális névütközéseket a Latte kulcsfontosságú szolgáltatóival vagy más harmadik féltől származó kiterjesztések szolgáltatóival. Gyakori konvenció egy rövid, egyedi előtag használata, amely a gyártóhoz vagy a kiterjesztés nevéhez kapcsolódik. Példánkhoz az `app` előtagot fogjuk használni, és a fejlesztői mód jelzője `$this->global->appDevMode`-ként lesz elérhető. + + +A `yield` kulcsszó a tartalom parzolásához +------------------------------------------ + +Hogyan mondjuk meg a Latte parsernek, hogy dolgozza fel a `{debug}` és `{/debug}` *közötti* tartalmat? Itt jön képbe a `yield` kulcsszó. + +Amikor a `yield`-et használjuk a `create()` függvényben, a függvény [PHP generátorrá |https://www.php.net/manual/en/language.generators.overview.php] válik. Végrehajtása felfüggesztődik, és a vezérlés visszatér a fő `TemplateParser`-hez. A `TemplateParser` ezután folytatja a sablon tartalmának parzolását, *amíg* nem találkozik a megfelelő záró taggel (`{/debug}` a mi esetünkben). + +Amint a záró taget megtalálja, a `TemplateParser` folytatja a `create()` függvényünk végrehajtását közvetlenül a `yield` utasítás után. A `yield` utasítás által *visszaadott* érték egy két elemet tartalmazó tömb: + +1. Egy `AreaNode`, amely a kezdő és záró tag között parzolt tartalmat reprezentálja. +2. Egy `Tag` objektum, amely a záró taget reprezentálja (pl. `{/debug}`). + +Hozzuk létre a `DebugNode` osztályt és annak `create` metódusát, amely a `yield`-et használja. + +```php +node = new self; + + // Parzolás felfüggesztése, belső tartalom és záró tag lekérése, amikor {/debug} található + [$node->content, $endTag] = yield; + + return $node; + } + + // ... a print() és getIterator() később implementálódik ... +} +``` + +Megjegyzés: Az `$endTag` `null`, ha a taget n:attribútumként használják, azaz `
                                                                      ...
                                                                      `. + + +A `print()` implementálása feltételes rendereléshez +--------------------------------------------------- + +A `print()` metódusnak most olyan PHP kódot kell generálnia, amely futásidőben ellenőrzi az `appDevMode` szolgáltatót, és csak akkor hajtja végre a belső tartalom kódját, ha a jelző igaz. + +```php + public function print(PrintContext $context): string + { + // Generál egy PHP 'if' utasítást, amely futásidőben ellenőrzi a szolgáltatót + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + // Ha fejlesztői módban van, kiírja a belső tartalmat + %node + } + + XX, + $this->position, // A %line kommentárhoz + $this->content, // A belső tartalom AST-jét tartalmazó csomópont + ); + } +``` + +Ez egyszerű. A `PrintContext::format()`-ot használjuk egy standard PHP `if` utasítás létrehozásához. Az `if`-en belül a `%node` helyettesítő karaktert helyezzük el a `$this->content`-hez. A Latte rekurzívan meghívja a `$this->content->print($context)`-et, hogy generálja a PHP kódot a tag belső részéhez, de csak akkor, ha a `$this->global->appDevMode` futásidőben igazra értékelődik. + + +A `getIterator()` implementálása tartalomhoz +-------------------------------------------- + +Ahogy az előző példában az argumentum csomópontnál, a `DebugNode`-unknak most van egy gyermek csomópontja: az `AreaNode $content`. Hozzáférhetővé kell tennünk a `getIterator()`-ban történő biztosítással: + +```php + public function &getIterator(): \Generator + { + // Referenciát biztosít a tartalom csomóponthoz + yield $this->content; + } +``` + +Ez lehetővé teszi a fordítási menetek számára, hogy leereszkedjenek a `{debug}` tagünk tartalmába, ami akkor is fontos, ha a tartalom feltételesen renderelődik. Például a Sandboxnak elemeznie kell a tartalmat, függetlenül attól, hogy az `appDevMode` igaz vagy hamis. + + +Regisztráció és használat +------------------------- + +Regisztrálja a taget és a szolgáltatót a kiterjesztésében: + +```php +class MyLatteExtension extends Extension +{ + // Feltételezzük, hogy az $isDevelopmentMode valahol meghatározásra kerül (pl. konfigurációból) + public function __construct( + private bool $isDevelopmentMode, + ) { + } + + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), // Az új tag regisztrálása + ]; + } + + public function getProviders(): array + { + return [ + 'appDevMode' => $this->isDevelopmentMode, // A szolgáltató regisztrálása + ]; + } +} + +// A kiterjesztés regisztrálásakor: +$isDev = true; // Határozza meg ezt az alkalmazás környezete alapján +$latte->addExtension(new App\Latte\MyLatteExtension($isDev)); +``` + +És használata a sablonban: + +```latte +

                                                                      Mindig látható normál tartalom.

                                                                      + +{debug} +
                                                                      + Aktuális felhasználó ID-ja: {$user->id} + Kérés ideje: {=time()} +
                                                                      +{/debug} + +

                                                                      További normál tartalom.

                                                                      +``` + + +n:attribútum integráció +----------------------- + +A Latte kényelmes rövidítést kínál sok páros taghez: az [n:attribútumokat |syntax#n:attribútumok]. Ha van egy páros tagje, mint `{tag}...{/tag}`, és azt szeretné, hogy hatása közvetlenül egyetlen HTML elemre vonatkozzon, gyakran tömörebben írhatja `n:tag` attribútumként ezen az elemen. + +A legtöbb standard páros taghez, amelyet definiál (mint a mi `{debug}` tagünk), a Latte automatikusan engedélyezi a megfelelő `n:` attribútum verziót. A regisztráció során nem kell semmi extrát tennie: + +```latte +{* Standard páros tag használata *} +{debug}
                                                                      Hibakeresési információk
                                                                      {/debug} + +{* Ekvivalens használat n:attribútummal *} +
                                                                      Hibakeresési információk
                                                                      +``` + +Mindkét verzió csak akkor rendereli a `
                                                                      `-et, ha a `$this->global->appDevMode` igaz. Az `inner-` és `tag-` előtagok is a várt módon működnek. + +Néha a tag logikájának kissé eltérően kell viselkednie attól függően, hogy standard páros tagként vagy n:attribútumként használják-e, vagy hogy használnak-e előtagot, mint `n:inner-tag` vagy `n:tag-tag`. A `Latte\Compiler\Tag` objektum, amelyet a `create()` parzoló függvénynek adnak át, biztosítja ezt az információt: + +- `$tag->isNAttribute(): bool`: `true`-t ad vissza, ha a taget n:attribútumként parzolják. +- `$tag->prefix: ?string`: Visszaadja az n:attribútummal használt előtagot, ami lehet `null` (nem n:attribútum), `Tag::PrefixNone`, `Tag::PrefixInner` vagy `Tag::PrefixTag`. + +Most, hogy megértettük az egyszerű tageket, az argumentumok parzolását, a páros tageket, a szolgáltatókat és az n:attribútumokat, foglalkozzunk egy bonyolultabb forgatókönyvvel, amely más tagekbe ágyazott tageket tartalmaz, a `{debug}` tagünket kiindulópontként használva. + + +Köztes tagek +============ + +Néhány páros tag lehetővé teszi, vagy akár megköveteli, hogy más tagek jelenjenek meg *bennük* a végső záró tag előtt. Ezeket **köztes tageknek** nevezik. Klasszikus példák a `{if}...{elseif}...{else}...{/if}` vagy a `{switch}...{case}...{default}...{/switch}`. + +Bővítsük a `{debug}` tagünket, hogy támogasson egy opcionális `{else}` klauzult, amely akkor renderelődik, amikor az alkalmazás *nincs* fejlesztői módban. + +**Cél:** Módosítani a `{debug}` taget, hogy támogasson egy opcionális `{else}` köztes taget. A végső szintaxisnak `{debug} ... {else} ... {/debug}`-nak kell lennie. + + +Köztes tagek parzolása `yield`-del +---------------------------------- + +Már tudjuk, hogy a `yield` felfüggeszti a `create()` parzoló függvényt, és visszaadja a parzolt tartalmat a záró taggel együtt. A `yield` azonban több kontrollt kínál: megadhat neki egy tömböt a *köztes tag nevekből*. Amikor a parser találkozik ezen megadott tagek bármelyikével **ugyanazon a beágyazási szinten** (azaz a szülő tag közvetlen gyermekeiként, nem más blokkokon vagy tageken belül), szintén leállítja a parzolást. + +Amikor a parzolás egy köztes tag miatt áll le, leállítja a tartalom parzolását, folytatja a `create()` generátort, és visszaadja a részben parzolt tartalmat és magát a **köztes taget** (a végső záró tag helyett). A `create()` függvényünk ezután feldolgozhatja ezt a köztes taget (pl. parzolhatja az argumentumait, ha voltak), és újra használhatja a `yield`-et a tartalom *következő* részének parzolására egészen a *végső* záró tagig vagy egy másik várt köztes tagig. + +Módosítsuk a `DebugNode::create()`-et, hogy várja az `{else}`-t: + +```php +node = new self; + + // yield és várni vagy a {/debug}-ot vagy az {else}-t + [$node->thenContent, $nextTag] = yield ['else']; + + // Ellenőrizni, hogy a tag, amelynél megálltunk, {else} volt-e + if ($nextTag?->name === 'else') { + // Újra yield a tartalom parzolásához az {else} és {/debug} között + [$node->elseContent, $endTag] = yield; + } + + return $node; + } + + // ... a print() és getIterator() később frissülnek ... +} +``` + +Most a `yield ['else']` azt mondja a Latte-nak, hogy ne csak a `{/debug}`-ra, hanem az `{else}`-re is álljon le a parzolással. Ha `{else}` található, a `$nextTag` tartalmazni fogja az `{else}` `Tag` objektumát. Ezután újra használjuk a `yield`-et argumentumok nélkül, ami azt jelenti, hogy most már csak a végső `{/debug}` taget várjuk, és az eredményt a `$node->elseContent`-be mentjük. Ha az `{else}` nem található, a `$nextTag` a `{/debug}` `Tag`-ja lenne (vagy `null`, ha n:attribútumként használják), és a `$node->elseContent` `null` maradna. + + +A `print()` implementálása `{else}`-szel +---------------------------------------- + +A `print()` metódusnak tükröznie kell az új struktúrát. Generálnia kell egy PHP `if/else` utasítást a `devMode` szolgáltató alapján. + +```php + public function print(PrintContext $context): string + { + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + %node // Kód a 'then' ághoz ({debug} tartalom) + } else { + %node // Kód az 'else' ághoz ({else} tartalom) + } + + XX, + $this->position, // Sorszám az 'if' feltételhez + $this->thenContent, // Első %node helyettesítő + $this->elseContent ?? new NopNode, // Második %node helyettesítő + ); + } +``` + +Ez egy standard PHP `if/else` struktúra. Kétszer használjuk a `%node`-ot; a `format()` sorban helyettesíti a megadott csomópontokat. A `?? new NopNode`-ot használjuk a hibák elkerülésére, ha a `$this->elseContent` `null` – a `NopNode` egyszerűen nem nyomtat ki semmit. + + +A `getIterator()` implementálása mindkét tartalomhoz +---------------------------------------------------- + +Most potenciálisan két gyermek tartalom csomópontunk van (`$thenContent` és `$elseContent`). Mindkettőt biztosítanunk kell, ha léteznek: + +```php + public function &getIterator(): \Generator + { + yield $this->thenContent; + if ($this->elseContent) { + yield $this->elseContent; + } + } +``` + + +A javított tag használata +------------------------- + +A tag most már használható az opcionális `{else}` klauzullal: + +```latte +{debug} +

                                                                      Hibakeresési információk megjelenítése, mert a devMode BE van kapcsolva.

                                                                      +{else} +

                                                                      A hibakeresési információk elrejtve, mert a devMode KI van kapcsolva.

                                                                      +{/debug} +``` + + +Állapot és beágyazás kezelése +============================= + +Korábbi példáink (`{datetime}`, `{debug}`) viszonylag állapotmentesek voltak a `print()` metódusaikon belül. Vagy közvetlenül kiírták a tartalmat, vagy egyszerű feltételes ellenőrzést végeztek egy globális szolgáltató alapján. Sok tagnek azonban valamilyen formában **állapotot** kell kezelnie a renderelés során, vagy felhasználói kifejezések kiértékelését foglalja magában, amelyeket csak egyszer kellene futtatni a teljesítmény vagy a helyesség érdekében. Továbbá figyelembe kell vennünk, mi történik, ha az egyéni tagjeink **beágyazva** vannak. + +Illusztráljuk ezeket a koncepciókat egy `{repeat $count}...{/repeat}` tag létrehozásával. Ez a tag a belső tartalmát `$count`-szor ismétli meg. + +**Cél:** Implementálni a `{repeat $count}` taget, amely a tartalmát a megadott számú alkalommal ismétli meg. + + +Ideiglenes és egyedi változók szükségessége +------------------------------------------- + +Képzelje el, hogy a felhasználó ezt írja: + +```latte +{repeat rand(1, 5)} Tartalom {/repeat} +``` + +Ha naivan generálnánk egy PHP `for` ciklust így a `print()` metódusunkban: + +```php +// Egyszerűsített, HELYTELEN generált kód +for ($i = 0; $i < rand(1, 5); $i++) { + // tartalom kiírása +} +``` +Ez rossz lenne! A `rand(1, 5)` kifejezés **minden ciklus iterációban újra kiértékelődne**, ami kiszámíthatatlan számú ismétléshez vezetne. Ki kell értékelnünk a `$count` kifejezést *egyszer* a ciklus kezdete előtt, és tárolnunk kell az eredményét. + +Generálunk egy PHP kódot, amely először kiértékeli a darabszám kifejezést, és egy **ideiglenes futásidejű változóba** menti. Annak érdekében, hogy elkerüljük az ütközéseket a sablon felhasználója által definiált változókkal *és* a Latte belső változóival (mint a `$ʟ_...`), a **`$__` (dupla aláhúzás)** előtag konvenciót használjuk az ideiglenes változóinkhoz. + +A generált kód ekkor így nézne ki: + +```php +$__count = rand(1, 5); +for ($__i = 0; $__i < $__count; $__i++) { + // tartalom kiírása +} +``` + +Most vegyük fontolóra a beágyazást: + +```latte +{repeat $countA} {* Külső ciklus *} + {repeat $countB} {* Belső ciklus *} + ... + {/repeat} +{/repeat} +``` + +Ha mind a külső, mind a belső `{repeat}` tag olyan kódot generálna, amely *ugyanazokat* az ideiglenes változóneveket használja (pl. `$__count` és `$__i`), a belső ciklus felülírná a külső ciklus változóit, ami megbontaná a logikát. + +Biztosítanunk kell, hogy az egyes `{repeat}` tag példányokhoz generált ideiglenes változók **egyediek** legyenek. Ezt a `PrintContext::generateId()` segítségével érjük el. Ez a metódus egy egyedi egész számot ad vissza a fordítási fázisban. Ezt az ID-t hozzáfűzhetjük az ideiglenes változóink nevéhez. + +Tehát a `$__count` helyett `$__count_1`-et generálunk az első repeat taghez, `$__count_2`-t a másodikhoz, és így tovább. Hasonlóképpen a ciklusszámlálóhoz `$__i_1`-et, `$__i_2`-t stb. használunk. + + +A `RepeatNode` implementálása +----------------------------- + +Hozzuk létre a csomópont osztályt. + +```php +expectArguments(); // biztosítja, hogy a $count meg legyen adva + $node = $tag->node = new self; + // Parzolja a darabszám kifejezést + $node->count = $tag->parser->parseExpression(); + // Belső tartalom lekérése + [$node->content] = yield; + return $node; + } + + /** + * Generál egy PHP 'for' ciklust egyedi változónevekkel. + */ + public function print(PrintContext $context): string + { + // Egyedi változónevek generálása + $id = $context->generateId(); + $countVar = '$__count_' . $id; // pl. $__count_1, $__count_2, stb. + $iteratorVar = '$__i_' . $id; // pl. $__i_1, $__i_2, stb. + + return $context->format( + <<<'XX' + // A darabszám kifejezés kiértékelése *egyszer* és tárolása + %raw = (int) (%node); + // Ciklus a tárolt darabszámmal és egyedi iterációs változóval + for (%raw = 0; %2.raw < %0.raw; %2.raw++) %line { + %node // Belső tartalom renderelése + } + + XX, + $countVar, // %0 - Változó a darabszám tárolására + $this->count, // %1 - Kifejezés csomópont a darabszámhoz + $iteratorVar, // %2 - A ciklus iterációs változójának neve + $this->position, // %3 - Kommentár a sorszámmal magához a ciklushoz + $this->content // %4 - Belső tartalom csomópont + ); + } + + /** + * Biztosítja a gyermek csomópontokat (darabszám kifejezés és tartalom). + */ + public function &getIterator(): \Generator + { + yield $this->count; + yield $this->content; + } +} +``` + +A `create()` metódus parzolja a kötelező `$count` kifejezést a `parseExpression()` segítségével. Először a `$tag->expectArguments()` kerül meghívásra. Ez biztosítja, hogy a felhasználó megadott *valamit* a `{repeat}` után. Míg a `$tag->parser->parseExpression()` meghiúsulna, ha semmit sem adtak volna meg, a hibaüzenet váratlan szintaxisról szólhatna. Az `expectArguments()` használata sokkal világosabb hibát ad, konkrétan megjelölve, hogy hiányoznak az argumentumok a `{repeat}` taghez. + +A `print()` metódus generálja a PHP kódot, amely felelős az ismétlési logika futásidejű végrehajtásáért. Az egyedi nevek generálásával kezdődik az ideiglenes PHP változókhoz, amelyekre szüksége lesz. + +A `$context->format()` metódus egy új `%raw` helyettesítő karakterrel kerül meghívásra, amely beilleszti a megfelelő argumentumként megadott *nyers sztringet*. Itt beilleszti a `$countVar`-ban tárolt egyedi változónevet (pl. `$__count_1`). És mi a helyzet a `%0.raw`-val és a `%2.raw`-val? Ez a **pozíciós helyettesítőket** demonstrálja. Ahelyett, hogy csak a `%raw`-t használnánk, amely a *következő* elérhető nyers argumentumot veszi, a `%2.raw` expliciten a 2-es indexű argumentumot veszi (ami a `$iteratorVar`), és beilleszti annak nyers sztring értékét. Ez lehetővé teszi számunkra, hogy újra felhasználjuk a `$iteratorVar` sztringet anélkül, hogy többször átadnánk a `format()` argumentumlistájában. + +Ez a gondosan megkonstruált `format()` hívás hatékony és biztonságos PHP ciklust generál, amely helyesen kezeli a darabszám kifejezést, és elkerüli a változónév-ütközéseket még akkor is, ha a `{repeat}` tagek beágyazva vannak. + + +Regisztráció és használat +------------------------- + +Regisztrálja a taget a kiterjesztésében: + +```php +use App\Latte\RepeatNode; + +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), // A repeat tag regisztrálása + ]; + } +} +``` + +Használja a sablonban, beleértve a beágyazást is: + +```latte +{var $rows = rand(5, 7)} +{var $cols = rand(3, 5)} + +{repeat $rows} +
                                                                      + {repeat $cols} + + {/repeat} + +{/repeat} +``` + +Ez a példa demonstrálja, hogyan kell kezelni az állapotot (ciklusszámlálók) és a potenciális beágyazási problémákat ideiglenes, `$__` előtagú és a `PrintContext::generateId()`-től származó egyedi ID-vel ellátott változók segítségével. + + +Tiszta n:attribútumok +--------------------- + +Míg sok `n:attribútum`, mint az `n:if` vagy `n:foreach`, kényelmes rövidítésként szolgál a páros tag megfelelőikhez (`{if}...{/if}`, `{foreach}...{/foreach}`), a Latte lehetővé teszi olyan tagek definiálását is, amelyek *csak* n:attribútum formájában léteznek. Ezeket gyakran használják a HTML elem attribútumainak vagy viselkedésének módosítására, amelyhez csatolva vannak. + +A Latte-ba beépített standard példák közé tartozik a [`n:class` |tags#n:class], amely segít dinamikusan összeállítani a `class` attribútumot, és a [`n:attr` |tags#n:attr], amely több tetszőleges attribútumot állíthat be. + +Hozzuk létre saját tiszta n:attribútumunkat: `n:confirm`, amely egy JavaScript megerősítő párbeszédablakot ad hozzá egy művelet végrehajtása előtt (mint egy link követése vagy egy űrlap elküldése). + +**Cél:** Implementálni az `n:confirm="'Biztos benne?'"`-t, amely hozzáad egy `onclick` kezelőt az alapértelmezett művelet megakadályozására, ha a felhasználó megszakítja a megerősítő párbeszédablakot. + + +A `ConfirmNode` implementálása +------------------------------ + +Szükségünk van egy Node osztályra és egy parzoló függvényre. + +```php +expectArguments(); + $node = $tag->node = new self; + $node->message = $tag->parser->parseExpression(); + return $node; + } + + /** + * Generálja az 'onclick' attribútum kódját helyes escapeléssel. + */ + public function print(PrintContext $context): string + { + // Biztosítja a helyes escapelést mind a JavaScript, mind a HTML attribútum kontextusokhoz. + return $context->format( + <<<'XX' + echo ' onclick="', LR\Filters::escapeHtmlAttr('return confirm(' . LR\Filters::escapeJs(%node) . ')'), '"' %line; + XX, + $this->message, + $this->position, + ); + } + + public function &getIterator(): \Generator + { + yield $this->message; + } +} +``` + +A `print()` metódus generálja a PHP kódot, amely végül a sablon renderelése során kiírja a `onclick="..."` HTML attribútumot. A beágyazott kontextusok (JavaScript egy HTML attribútumon belül) kezelése gondos escapelést igényel. A `LR\Filters::escapeJs(%node)` szűrő futásidőben kerül meghívásra, és helyesen escapeli az üzenetet a JavaScripten belüli használathoz (a kimenet olyan lenne, mint `"Sure?"`). Ezután a `LR\Filters::escapeHtmlAttr(...)` szűrő escapeli azokat a karaktereket, amelyek speciálisak a HTML attribútumokban, így ez a kimenetet `return confirm("Sure?")`-re változtatná. Ez a kétlépcsős futásidejű escapelés biztosítja, hogy az üzenet biztonságos legyen a JavaScript számára, és az eredményül kapott JavaScript kód biztonságos legyen a `onclick` HTML attribútumba való beágyazáshoz. + + +Regisztráció és használat +------------------------- + +Regisztrálja az n:attribútumot a kiterjesztésében. Ne felejtse el az `n:` előtagot a kulcsban: + +```php +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), + 'n:confirm' => ConfirmNode::create(...), // Az n:confirm regisztrálása + ]; + } +} +``` + +Most már használhatja az `n:confirm`-ot linkeken, gombokon vagy űrlap elemeken: + +```latte +Törlés +``` + +Generált HTML: + +```html +Törlés +``` + +Amikor a felhasználó a linkre kattint, a böngésző végrehajtja az `onclick` kódot, megjeleníti a megerősítő párbeszédablakot, és csak akkor navigál a `delete.php`-re, ha a felhasználó az "OK"-ra kattint. + +Ez a példa demonstrálja, hogyan lehet létrehozni egy tiszta n:attribútumot a gazda HTML elem viselkedésének vagy attribútumainak módosítására a megfelelő PHP kód generálásával a `print()` metódusában. Ne feledkezzen meg a gyakran szükséges dupla escapelésről: egyszer a célkontextushoz (ebben az esetben JavaScript), és újra a HTML attribútum kontextusához. + + +Haladó témák +============ + +Míg az előző szakaszok az alapvető koncepciókat fedték le, itt van néhány haladóbb téma, amellyel találkozhat az egyéni Latte tagek létrehozása során. + + +Tag kimeneti módok +------------------ + +A `create()` függvénynek átadott `Tag` objektumnak van egy `outputMode` property-je. Ez a property befolyásolja, hogyan kezeli a Latte a környező szóközöket és behúzásokat, különösen, ha a taget saját sorában használják. Ezt a property-t módosíthatja a `create()` függvényében. + +- `Tag::OutputKeepIndentation` (Alapértelmezett a legtöbb tagnél, mint `{=...}`): A Latte megpróbálja megőrizni a tag előtti behúzást. A tag *utáni* új sorok általában megmaradnak. Ez alkalmas olyan tagekhez, amelyek soron belüli tartalmat írnak ki. +- `Tag::OutputRemoveIndentation` (Alapértelmezett a blokk tageknél, mint `{if}`, `{foreach}`): A Latte eltávolítja a kezdő behúzást és potenciálisan egy következő új sort. Ez segít tisztábban tartani a generált PHP kódot, és megakadályozza a HTML kimenetben a tag által okozott további üres sorokat. Használja ezt olyan tagekhez, amelyek vezérlési struktúrákat vagy blokkokat reprezentálnak, amelyeknek maguknak nem szabadna szóközöket hozzáadniuk. +- `Tag::OutputNone` (Olyan tagek használják, mint `{var}`, `{default}`): Hasonló a `RemoveIndentation`-höz, de erősebben jelzi, hogy a tag maga nem produkál közvetlen kimenetet, potenciálisan még agresszívebben befolyásolva a körülötte lévő szóközök kezelését. Alkalmas deklaratív vagy beállító tagekhez. + +Válassza ki azt a módot, amely a legjobban megfelel a tag céljának. A legtöbb strukturális vagy vezérlő taghez általában az `OutputRemoveIndentation` a megfelelő. + + +Szülő/legközelebbi tagek elérése +-------------------------------- + +Néha egy tag viselkedésének attól a kontextustól kell függenie, amelyben használják, konkrétan attól, hogy melyik szülő tag(ek)ben található. A `create()` függvénynek átadott `Tag` objektum biztosítja a `closestTag(array $classes, ?callable $condition = null): ?Tag` metódust pontosan erre a célra. + +Ez a metódus felfelé keres a jelenleg nyitott tagek hierarchiájában (beleértve a parzolás során belsőleg reprezentált HTML elemeket is), és visszaadja a legközelebbi ős `Tag` objektumát, amely megfelel a specifikus kritériumoknak. Ha nem található megfelelő ős, `null`-t ad vissza. + +Az `$classes` tömb meghatározza, milyen típusú ős tageket keres. Ellenőrzi, hogy az ős tag társított csomópontja (`$ancestorTag->node`) ennek az osztálynak a példánya-e. + +```php +function create(Tag $tag) +{ + // A legközelebbi ős tag keresése, amelynek csomópontja ForeachNode példány + $foreachTag = $tag->closestTag([ForeachNode::class]); + if ($foreachTag) { + // Hozzáférhetünk magához a ForeachNode példányhoz: + $foreachNode = $foreachTag->node; + } +} +``` + +Vegye észre a `$foreachTag->node`-ot: Ez csak azért működik, mert konvenció a Latte tag fejlesztésben, hogy azonnal hozzárendeljük a létrehozott csomópontot a `$tag->node`-hoz a `create()` metóduson belül, ahogy mindig is tettük. + +Néha a csomópont típusának egyszerű összehasonlítása nem elegendő. Lehet, hogy ellenőriznie kell egy potenciális ős tag vagy annak csomópontjának specifikus property-jét. A `closestTag()` opcionális második argumentuma egy callable, amely megkapja a potenciális ős `Tag` objektumot, és vissza kell adnia, hogy érvényes egyezés-e. + +```php +function create(Tag $tag) +{ + $dynamicBlockTag = $tag->closestTag( + [BlockNode::class], + // Feltétel: a blokknak dinamikusnak kell lennie + fn(Tag $blockTag) => $blockTag->node->block->isDynamic(), + ); +} +``` + +A `closestTag()` használata lehetővé teszi olyan tagek létrehozását, amelyek kontextus-tudatosak és kikényszerítik a helyes használatot a sablon struktúráján belül, ami robusztusabb és érthetőbb sablonokhoz vezet. + + +`PrintContext::format()` helyettesítő karakterek +------------------------------------------------ + +Gyakran használtuk a `PrintContext::format()`-ot PHP kód generálására a csomópontjaink `print()` metódusaiban. Elfogad egy maszk sztringet és további argumentumokat, amelyek helyettesítik a maszkban lévő helyettesítő karaktereket. Itt van egy összefoglaló az elérhető helyettesítő karakterekről: + +- **`%node`**: Az argumentumnak `Node` példánynak kell lennie. Meghívja a csomópont `print()` metódusát, és beilleszti az eredményül kapott PHP kódsztringet. +- **`%dump`**: Az argumentum bármilyen PHP érték. Exportálja az értéket érvényes PHP kódba. Alkalmas skalárokhoz, tömbökhöz, null-hoz. + - `$context->format('echo %dump;', 'Hello')` -> `echo 'Hello';` + - `$context->format('$arr = %dump;', [1, 2])` -> `$arr = [1, 2];` +- **`%raw`**: Beilleszti az argumentumot közvetlenül a kimeneti PHP kódba bármilyen escapelés vagy módosítás nélkül. **Használja óvatosan**, elsősorban előre generált PHP kódrészletek vagy változónevek beillesztésére. + - `$context->format('%raw = 1;', '$variableName')` -> `$variableName = 1;` +- **`%args`**: Az argumentumnak `Expression\ArrayNode`-nak kell lennie. Kiírja a tömb elemeit függvény- vagy metódushívás argumentumaként formázva (vesszővel elválasztva, kezeli a névvel ellátott argumentumokat, ha vannak). + - `$argsNode = new ArrayNode([...]);` + - `$context->format('myFunc(%args);', $argsNode)` -> `myFunc(1, name: 'Joe');` +- **`%line`**: Az argumentumnak `Position` objektumnak kell lennie (általában `$this->position`). Beilleszt egy PHP kommentárt `/* line X */`, amely a forrás sorszámát jelzi. + - `$context->format('echo "Hi" %line;', $this->position)` -> `echo "Hi" /* line 42 */;` +- **`%escape(...)`**: Generál egy PHP kódot, amely *futásidőben* escapeli a belső kifejezést az aktuális kontextus-tudatos escapelési szabályok szerint. + - `$context->format('echo %escape(%node);', $variableNode)` +- **`%modify(...)`**: Az argumentumnak `ModifierNode`-nak kell lennie. Generál egy PHP kódot, amely alkalmazza a `ModifierNode`-ban megadott szűrőket a belső tartalomra, beleértve a kontextus-tudatos escapelést, hacsak nincs letiltva a `|noescape`-pel. + - `$context->format('%modify(%node);', $modifierNode, $variableNode)` +- **`%modifyContent(...)`**: Hasonló a `%modify`-hoz, de elfogott tartalomblokkok (gyakran HTML) módosítására szolgál. + +Explicit módon hivatkozhat az argumentumokra az indexük alapján (nullától kezdve): `%0.node`, `%1.dump`, `%2.raw`, stb. Ez lehetővé teszi egy argumentum többszöri újrafelhasználását a maszkban anélkül, hogy ismételten át kellene adni a `format()`-nak. Lásd a `{repeat}` tag példáját, ahol a `%0.raw` és `%2.raw` volt használva. + + +Komplex argumentum parzolási példa +---------------------------------- + +Míg a `parseExpression()`, `parseArguments()`, stb. sok esetet lefednek, néha bonyolultabb parzolási logikára van szükség az alacsonyabb szintű `TokenStream` használatával, amely a `$tag->parser->stream`-en keresztül érhető el. + +**Cél:** Létrehozni egy `{embedYoutube $videoID, width: 640, height: 480}` taget. Parzolni akarjuk a kötelező videó ID-t (sztring vagy változó), amelyet opcionális kulcs-érték párok követnek a méretekhez. + +```php +expectArguments(); + $node = $tag->node = new self; + // Kötelező videó ID parzolása + $node->videoId = $tag->parser->parseExpression(); + + // Opcionális kulcs-érték párok parzolása + $stream = $tag->parser->stream; // Tokenfolyam lekérése + while ($stream->tryConsume(',')) { // Vesszővel való elválasztást követel meg + // 'width' vagy 'height' azonosító várása + $keyToken = $stream->consume(Token::Php_Identifier); + $key = strtolower($keyToken->text); + + $stream->consume(':'); // Kettőspont elválasztó várása + + $value = $tag->parser->parseExpression(); // Érték kifejezés parzolása + + if ($key === 'width') { + $node->width = $value; + } elseif ($key === 'height') { + $node->height = $value; + } else { + throw new CompileException("Ismeretlen argumentum '$key'. Várt 'width' vagy 'height'.", $keyToken->position); + } + } + + return $node; + } +} +``` + +Ez a kontrollszint lehetővé teszi nagyon specifikus és komplex szintaxisok definiálását az egyéni tagekhez a tokenfolyammal való közvetlen interakció révén. + + +Az `AuxiliaryNode` használata +----------------------------- + +A Latte általános "segéd" csomópontokat biztosít speciális helyzetekhez a kódgenerálás során vagy a fordítási meneteken belül. Ezek az `AuxiliaryNode` és a `Php\Expression\AuxiliaryNode`. + +Tekintse az `AuxiliaryNode`-ot egy rugalmas konténer csomópontnak, amely delegálja alapvető funkcionalitásait - a kódgenerálást és a gyermek csomópontok kiadását - a konstruktorában megadott argumentumoknak: + +- `print()` delegálása: A konstruktor első argumentuma egy PHP **closure**. Amikor a Latte meghívja a `print()` metódust az `AuxiliaryNode`-on, végrehajtja ezt a megadott closure-t. A closure megkapja a `PrintContext`-et és a konstruktor második argumentumában átadott csomópontokat, lehetővé téve teljesen egyéni PHP kódgenerálási logika definiálását futásidőben. +- `getIterator()` delegálása: A konstruktor második argumentuma egy **`Node` objektumokból álló tömb**. Amikor a Latte-nak végig kell járnia az `AuxiliaryNode` gyermekeit (pl. fordítási menetek során), annak `getIterator()` metódusa egyszerűen kiadja a ebben a tömbben felsorolt csomópontokat. + +Példa: + +```php +$node = new AuxiliaryNode( + // 1. Ez a closure lesz a print() törzse + fn(PrintContext $context, $arg1, $arg2) => $context->format('...%node...%node...', $arg1, $arg2), + + // 2. Ezeket a csomópontokat adja ki a getIterator() metódus, és adja át a fenti closure-nek + [$argumentNode1, $argumentNode2] +); +``` + +A Latte két különböző típust biztosít attól függően, hogy hova kell beillesztenie a generált kódot: + +- `Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode`: Használja ezt, ha olyan PHP kódrészletet kell generálnia, amely egy **kifejezést** reprezentál. +- `Latte\Compiler\Nodes\AuxiliaryNode`: Használja ezt általánosabb célokra, amikor egy vagy több **utasítást** reprezentáló PHP kódblokkot kell beillesztenie. + +Fontos ok az `AuxiliaryNode` használatára a standard csomópontok (mint a `StaticMethodCallNode`) helyett a `print()` metódusában vagy egy fordítási menetben a **láthatóság ellenőrzése a következő fordítási menetek számára**, különösen azok számára, amelyek biztonsággal kapcsolatosak, mint a Sandbox. + +Vegye fontolóra a következő forgatókönyvet: A fordítási menete egy felhasználó által megadott kifejezést (`$userExpr`) egy specifikus, megbízható segédfüggvény `myInternalSanitize($userExpr)` hívásába kell csomagolnia. Ha egy standard `new FunctionCallNode('myInternalSanitize', [$userExpr])` csomópontot hoz létre, az teljesen látható lesz az AST menet számára. Ha a Sandbox menet később fut, és a `myInternalSanitize` *nincs* az engedélyezett listáján, a Sandbox *blokkolhatja* vagy módosíthatja ezt a hívást, potenciálisan megbontva a tag belső logikáját, még akkor is, ha *Ön*, a tag szerzője, tudja, hogy ez a specifikus hívás biztonságos és szükséges. Ezért generálhatja a hívást közvetlenül az `AuxiliaryNode` closure-én belül. + +```php +use Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode; + +// ... a print() vagy egy fordítási menetben ... +$wrappedNode = new AuxiliaryNode( + fn(PrintContext $context, $userExpr) => $context->format( + 'myInternalSanitize(%node)', // Közvetlen PHP kód generálása + $userExpr, + ), + // FONTOS: Itt továbbra is adja át az eredeti felhasználói kifejezés csomópontot! + [$userExpr], +); +``` + +Ebben az esetben a Sandbox menet látja az `AuxiliaryNode`-ot, de **nem elemzi a closure által generált PHP kódot**. Nem tudja közvetlenül blokkolni a closure *belsejében* generált `myInternalSanitize` hívást. + +Míg maga a generált PHP kód rejtve van a menetek elől, a kód *bemenetei* (a felhasználói adatokat vagy kifejezéseket reprezentáló csomópontok) **továbbra is bejárhatónak kell lenniük**. Ezért az `AuxiliaryNode` konstruktorának második argumentuma alapvető. **Muszáj** átadnia egy tömböt, amely tartalmazza az összes eredeti csomópontot (mint a `$userExpr` a fenti példában), amelyet a closure használ. Az `AuxiliaryNode` `getIterator()`-ja **kiadja ezeket a csomópontokat**, lehetővé téve a fordítási meneteknek, mint a Sandbox, hogy elemezzék őket potenciális problémák szempontjából. + + +Bevált gyakorlatok +================== + +- **Világos cél:** Győződjön meg róla, hogy a tagjának világos és szükséges célja van. Ne hozzon létre tageket olyan feladatokhoz, amelyeket könnyen meg lehet oldani [szűrőkkel |custom-filters] vagy [függvényekkel |custom-functions]. +- **Implementálja helyesen a `getIterator()`-t:** Mindig implementálja a `getIterator()`-t, és biztosítson *referenciákat* (`&`) *minden* gyermek csomóponthoz (argumentumok, tartalom), amelyeket a sablonból parzoltak. Ez elengedhetetlen a fordítási menetekhez, a biztonsághoz (Sandbox) és a potenciális jövőbeli optimalizációkhoz. +- **Publikus property-k a csomópontokhoz:** Tegye publikussá a gyermek csomópontokat tartalmazó property-ket, hogy a fordítási menetek szükség esetén módosíthassák őket. +- **Használja a `PrintContext::format()`-ot:** Használja ki a `format()` metódust PHP kód generálására. Kezeli az idézőjeleket, helyesen escapeli a helyettesítő karaktereket, és automatikusan hozzáadja a sorszám kommentárokat. +- **Ideiglenes változók (`$__`):** Amikor futásidejű PHP kódot generál, amely ideiglenes változókat igényel (pl. köztes összegek tárolására, ciklusszámlálókhoz), használja a `$__` előtag konvenciót, hogy elkerülje az ütközéseket a felhasználói változókkal és a Latte belső `$ʟ_` változóival. +- **Beágyazás és egyedi ID-k:** Ha a tagja beágyazható, vagy példány-specifikus állapotra van szüksége futásidőben, használja a `$context->generateId()`-t a `print()` metódusán belül, hogy egyedi utótagokat hozzon létre az ideiglenes `$__` változóihoz. +- **Szolgáltatók külső adatokhoz:** Használjon szolgáltatókat (regisztrálva az `Extension::getProviders()`-en keresztül) futásidejű adatokhoz vagy szolgáltatásokhoz való hozzáféréshez (`$this->global->...`) ahelyett, hogy értékeket hardkódolna vagy globális állapotra támaszkodna. Használjon gyártói előtagokat a szolgáltatónevekhez. +- **Fontolja meg az n:attribútumokat:** Ha a páros tagja logikailag egyetlen HTML elemen működik, a Latte valószínűleg automatikus `n:attribútum` támogatást nyújt. Tartsa ezt szem előtt a felhasználói kényelem érdekében. Ha attribútum-módosító taget hoz létre, fontolja meg, hogy egy tiszta `n:attribútum` a legmegfelelőbb forma-e. +- **Tesztelés:** Írjon teszteket a tagjeihez, lefedve mind a különböző szintaktikai bemenetek parzolását, mind a generált **PHP kód** kimenetének helyességét. + +Ezen irányelvek követésével erőteljes, robusztus és karbantartható egyéni tageket hozhat létre, amelyek zökkenőmentesen integrálódnak a Latte sablonmotorral. + +.[note] +A Latte részét képező csomópont osztályok tanulmányozása a legjobb módja annak, hogy megtanulja a parzolási folyamat minden részletét. diff --git a/latte/hu/develop.texy b/latte/hu/develop.texy index 65a14f58a8..aa4460ca0b 100644 --- a/latte/hu/develop.texy +++ b/latte/hu/develop.texy @@ -1,9 +1,9 @@ -Gyakorlatok fejlesztőknek -************************* +Fejlesztői eljárások +******************** -Telepítés .[#toc-installation] -============================== +Telepítés +========= A Latte telepítésének legjobb módja a Composer használata: @@ -11,60 +11,56 @@ A Latte telepítésének legjobb módja a Composer használata: composer require latte/latte ``` -Támogatott PHP-verziók (a legújabb javított Latte-verziókra vonatkozik): +Támogatott PHP verziók (a Latte legutolsó alverzióira vonatkozik): -| verzió | kompatibilis a PHP-vel +| verzió | kompatibilis PHP-vel |-----------------|------------------- -| Latte 3.0 | PHP 8.0 - 8.2 -| Latte 2.11 | PHP 7.1 - 8.2 -| Latte 2.8 - 2.10 | PHP 7.1 - 8.1 +| Latte 3.0 | PHP 8.0 – 8.2 -Sablonok renderelése .[#toc-how-to-render-a-template] -===================================================== +Hogyan rendereljünk sablont +=========================== -Hogyan kell renderelni egy sablont? Csak használja ezt az egyszerű kódot: +Hogyan rendereljünk sablont? Ehhez elég ez az egyszerű kód: ```php $latte = new Latte\Engine; -// cache könyvtár +// könyvtár a gyorsítótárhoz $latte->setTempDirectory('/path/to/tempdir'); -$params = [ /* sablonváltozók */ ]; +$params = [ /* sablon változók */ ]; // vagy $params = new TemplateParameters(/* ... */); -// renderelés a kimenetre +// rajzolás a kimenetre $latte->render('template.latte', $params); -// vagy renderelés változóba +// rajzolás változóba $output = $latte->renderToString('template.latte', $params); ``` -A paraméterek lehetnek tömbök vagy még jobb [objektumok |#Parameters as a class], amelyek típusellenőrzést és javaslatot biztosítanak a szerkesztőben. +A paraméterek lehetnek tömbök vagy még jobb [egy objektum |#Paraméterek osztályként], amely biztosítja a típusellenőrzést és a súgást a szerkesztőkben. .[note] -Használati példákat is talál a [Latte examples |https://github.com/nette-examples/latte] tárolóban. +Használati példákat a [Latte examples |https://github.com/nette-examples/latte] repozitóriumban is találhat. -Teljesítmény és gyorsítótárazás .[#toc-performance-and-caching] -=============================================================== +Teljesítmény és gyorsítótár +=========================== -A Latte sablonok rendkívül gyorsak, mivel a Latte közvetlenül PHP kóddá fordítja őket, és a lemezen gyorsítótárba helyezi őket. Így a tisztán PHP nyelven írt sablonokhoz képest nincs extra többletköltségük. +A Latte sablonok rendkívül gyorsak, mivel a Latte közvetlenül PHP kódra fordítja őket, és a lemezen lévő gyorsítótárban tárolja. Így nincs többlet terhelésük a tiszta PHP-ban írt sablonokhoz képest. -A gyorsítótár automatikusan újratermelődik minden alkalommal, amikor megváltoztatja a forrásfájlt. Így a fejlesztés során kényelmesen szerkesztheti a Latte sablonokat, és a változásokat azonnal láthatja a böngészőben. Termelési környezetben kikapcsolhatja ezt a funkciót, és ezzel egy kis teljesítményt takaríthat meg: +A gyorsítótár automatikusan újragenerálódik minden alkalommal, amikor megváltoztatja a forrásfájlt. Így a fejlesztés során kényelmesen szerkesztheti a Latte sablonokat, és a változásokat azonnal láthatja a böngészőben. Ezt a funkciót a produkciós környezetben kikapcsolhatja, hogy egy kis teljesítményt spóroljon: ```php $latte->setAutoRefresh(false); ``` -Termelőszerverre telepítve a kezdeti gyorsítótár-generálás, különösen a nagyobb alkalmazások esetében, érthető módon eltarthat egy ideig. A Latte beépített megelőzéssel rendelkezik a "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede ellen. -Ez az a helyzet, amikor a szerver nagyszámú egyidejű kérést kap, és mivel a Latte gyorsítótár még nem létezik, ezek mind egyszerre generálnák azt. Ami felpörgeti a CPU-t. -A Latte okos, és több egyidejű kérés esetén csak az első szál generálja a cache-t, a többiek várnak, majd használják azt. +A produkciós szerverre történő telepítéskor a gyorsítótár kezdeti generálása, különösen nagyobb alkalmazások esetén, természetesen eltarthat egy ideig. A Latte beépített védelemmel rendelkezik a "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede ellen. Ez egy olyan helyzet, amikor nagyobb számú párhuzamos kérés érkezik, amelyek elindítják a Latte-t, és mivel a gyorsítótár még nem létezik, mindegyik egyszerre kezdené el generálni. Ez aránytalanul megterhelné a szervert. A Latte okos, és több párhuzamos kérés esetén csak az első szál generálja a gyorsítótárat, a többiek várnak, majd azt használják. -Paraméterek mint osztály .[#toc-parameters-as-a-class] -====================================================== +Paraméterek osztályként +======================= -Jobb, mintha a változókat tömbként adnánk át a sablonhoz, ha egy osztályt hozunk létre. Így [típusbiztos jelölést |type-system], [szép javaslatot |recipes#Editors and IDE] kapunk az [IDE-ben |recipes#Editors and IDE], és lehetőségünk van [szűrők |extending-latte#Filters Using the Class] és [függvények |extending-latte#Functions Using the Class] [regisztrálására |extending-latte#Filters Using the Class]. +Jobb, mint a változókat tömbként átadni a sablonnak, ha létrehozunk egy osztályt. Így [típusbiztos írást|type-system], [kellemes súgást az IDE-ben |recipes#Szerkesztők és IDE-k] és utat kapunk a [szűrők |custom-filters#Szűrők osztály használatával attribútumokkal] és [függvények |custom-functions#Függvények osztály használatával attribútumokkal] regisztrálásához. ```php class MailTemplateParameters @@ -88,12 +84,12 @@ $latte->render('mail.latte', new MailTemplateParameters( ``` -A változó automatikus kikerülésének letiltása .[#toc-disabling-auto-escaping-of-variable] -========================================================================================= +Változó automatikus escapelésének kikapcsolása +============================================== -Ha a változó HTML karakterláncot tartalmaz, akkor megjelölheti, hogy a Latte automatikusan (és ezért duplán) ne lépjen ki belőle. Ezzel elkerülhető a `|noescape` megadása a sablonban. +Ha egy változó HTML stringet tartalmaz, megjelölheti úgy, hogy a Latte ne escapelje automatikusan (és így duplán). Ezzel elkerülheti a `|noescape` használatának szükségességét a sablonban. -A legegyszerűbb, ha a karakterláncot egy `Latte\Runtime\Html` objektumba csomagoljuk: +A legegyszerűbb módja, ha a stringet egy `Latte\Runtime\Html` objektumba csomagolja: ```php $params = [ @@ -101,7 +97,7 @@ $params = [ ]; ``` -A Latte nem lép ki minden olyan objektumot, amely megvalósítja a `Latte\HtmlStringable` interfészt. Így létrehozhatsz egy saját osztályt, amelynek `__toString()` metódusa olyan HTML kódot ad vissza, amely nem lesz automatikusan eszkábálva: +A Latte továbbá nem escapeli az összes olyan objektumot, amely implementálja a `Latte\HtmlStringable` interfészt. Így létrehozhat saját osztályt, amelynek `__toString()` metódusa olyan HTML kódot ad vissza, amely nem lesz automatikusan escapelve: ```php class Emphasis extends Latte\HtmlStringable @@ -123,32 +119,85 @@ $params = [ ``` .[warning] -A `__toString` metódusnak helyes HTML-t kell visszaadnia, és biztosítania kell a paraméterek eszkábálását, különben XSS sebezhetőség léphet fel! +A `__toString` metódusnak korrekt HTML-t kell visszaadnia, és biztosítania kell a paraméterek escapelését, különben XSS sebezhetőség léphet fel! -Hogyan bővítsük a Latte-t szűrőkkel, címkékkel stb. .[#toc-how-to-extend-latte-with-filters-tags-etc] -===================================================================================================== +Hogyan bővítsük a Latte-t szűrőkkel, tagekkel stb. +================================================== -Hogyan adhatunk hozzá egyéni szűrőt, funkciót, címkét stb. a Latte-hoz? Megtudhatja a [Latte bővítése |extending Latte] című fejezetben. -Ha a módosításait különböző projektekben szeretné újra felhasználni, vagy ha meg akarja osztani másokkal, akkor [hozzon létre egy kiterjesztést |creating-extension]. +Hogyan adjunk hozzá saját szűrőt, függvényt, taget stb. a Latte-hoz? Erről szól a [Latte bővítése |extending-latte] fejezet. Ha a módosításait különböző projektekben szeretné újra felhasználni, vagy megosztani másokkal, akkor [hozzon létre egy kiterjesztést |extending-latte#Latte Extension]. -Bármilyen kód a sablonban `{php ...}` .{data-version:3.0}{toc: RawPhpExtension} -=============================================================================== +Tetszőleges kód a sablonban `{php ...}` .{toc: RawPhpExtension} +=============================================================== -Csak PHP kifejezéseket lehet írni a [`{do}` |tags#do] taget, így például nem lehet olyan konstrukciókat beilleszteni, mint a `if ... else` vagy a pontosvesszővel végződő utasítások. +A [`{do}` |tags#do] tag belsejében csak PHP kifejezéseket lehet írni, így például nem illeszthet be olyan konstrukciókat, mint `if ... else` vagy pontosvesszővel lezárt utasításokat. -Regisztrálhatja azonban a `RawPhpExtension` bővítményt, amely hozzáadja a `{php ...}` taget, amely a sablon szerzőjének felelősségére bármilyen PHP-kód beillesztésére használható. +Azonban regisztrálhatja a `RawPhpExtension` kiterjesztést, amely hozzáadja a `{php ...}` taget. Ezzel bármilyen PHP kódot beilleszthet. Rá nem vonatkoznak a sandbox mód szabályai, így a használata a sablon szerzőjének felelőssége. ```php $latte->addExtension(new Latte\Essential\RawPhpExtension); ``` -Fordítás a sablonokban .{data-version:3.0}{toc: TranslatorExtension} -==================================================================== +Generált kód ellenőrzése .{data-version:3.0.7} +============================================== + +A Latte a sablonokat PHP kódra fordítja. Természetesen ügyel arra, hogy a generált kód szintaktikailag érvényes legyen. Azonban harmadik féltől származó kiterjesztések vagy a `RawPhpExtension` használata esetén a Latte nem tudja garantálni a generált fájl helyességét. Továbbá PHP-ban lehet olyan kódot írni, amely bár szintaktikailag helyes, de tiltott (például érték hozzárendelése a `$this` változóhoz), és PHP Compile Errort okoz. Ha ilyen műveletet ír a sablonba, az bekerül a generált PHP kódba is. Mivel a PHP-ban több mint kétszáz különböző tiltott művelet létezik, a Latte-nak nincs ambíciója ezeket felderíteni. Ezekre csak maga a PHP hívja fel a figyelmet a rendereléskor, ami általában nem okoz problémát. + +Vannak azonban helyzetek, amikor már a sablon fordításakor tudni szeretné, hogy nem tartalmaz PHP Compile Errort. Különösen akkor, ha a sablonokat a felhasználók szerkeszthetik, vagy [Sandboxot|sandbox] használ. Ebben az esetben ellenőriztesse a sablonokat már a fordításkor. Ezt a funkcionalitást az `Engine::enablePhpLint()` metódussal kapcsolhatja be. Mivel az ellenőrzéshez PHP bináris fájlt kell hívnia, adja át annak elérési útját paraméterként: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); + +try { + $latte->compile('home.latte'); +} catch (Latte\CompileException $e) { + // elkapja a Latte hibáit és a PHP Compile Error-t is + echo 'Hiba: ' . $e->getMessage(); +} +``` + + +Nemzeti beállítások .{data-version:3.0.18}{toc: Locale} +======================================================= + +A Latte lehetővé teszi a nemzeti beállítások megadását, amelyek befolyásolják a számok, dátumok formázását és a rendezést. Ezt a `setLocale()` metódussal állíthatja be. A környezet azonosítója az IETF language tag szabványt követi, amelyet a PHP `intl` kiterjesztése használ. A nyelv kódjából és adott esetben az ország kódjából áll, pl. `en_US` az angolhoz az Egyesült Államokban, `de_DE` a némethez Németországban stb. + +```php +$latte = new Latte\Engine; +$latte->setLocale('hu'); +``` + +A környezet beállítása befolyásolja a [localDate |filters#localDate], [sort |filters#sort], [number |filters#number] és [bytes |filters#bytes] szűrőket. + +.[note] +Szükséges a PHP `intl` kiterjesztés. A Latte beállítása nem befolyásolja a PHP globális locale beállításait. + + +Szigorú mód .{data-version:3.0.8} +================================= + +Szigorú parzolási módban a Latte ellenőrzi, hogy hiányoznak-e a záró HTML tagek, és letiltja a `$this` változó használatát is. Így kapcsolhatja be: + +```php +$latte = new Latte\Engine; +$latte->setStrictParsing(); +``` + +A sablonok generálását `declare(strict_types=1)` fejléccel így kapcsolhatja be: + +```php +$latte = new Latte\Engine; +$latte->setStrictTypes(); +``` -A `TranslatorExtension` kiterjesztés használatával [`{_...}` |tags#_], [`{translate}` |tags#translate] és szűrje [`translate` |filters#translate] a sablonhoz. Ezek a sablon értékeinek vagy részeinek más nyelvekre történő lefordítására szolgálnak. A paraméter az a metódus (PHP hívható), amely a fordítást elvégzi: + +Fordítás sablonokban .{toc: TranslatorExtension} +================================================ + +A `TranslatorExtension` kiterjesztés segítségével hozzáadhatja a sablonhoz a [`{_...}` |tags#], [`{translate}` |tags#translate] tageket és a [`translate` |filters#translate] szűrőt. Ezek értékek vagy sablonrészek más nyelvekre történő fordítására szolgálnak. Paraméterként megadjuk a fordítást végző metódust (PHP callable): ```php class MyTranslator @@ -158,19 +207,19 @@ class MyTranslator public function translate(string $original): string { - // $translated létrehozása $originalból a $this->langnak megfelelően + // az $original alapján létrehozzuk a $translated-et a $this->lang szerint return $translated; } } $translator = new MyTranslator($lang); $extension = new Latte\Essential\TranslatorExtension( - $translator->translate(...), // [$translator, 'translate'] a PHP 8.0-ban + $translator->translate(...), // [$translator, 'translate'] PHP 8.0-ban ); $latte->addExtension($extension); ``` -A fordítót a sablon megjelenítésekor, futásidőben hívja meg a rendszer. A Latte azonban minden statikus szöveget le tud fordítani a sablon összeállítása során. Ez teljesítményt takarít meg, mivel minden karakterláncot csak egyszer fordít le, és az így kapott fordítás a lefordított fájlba kerül. Ez a sablon több lefordított változatát hozza létre a gyorsítótárban, egyet-egyet minden nyelvhez. Ehhez csak a nyelvet kell megadni második paraméterként: +A fordító futásidőben hívódik meg a sablon renderelésekor. A Latte azonban képes az összes statikus szöveget már a sablon fordítása során lefordítani. Ezzel teljesítményt takarítunk meg, mivel minden string csak egyszer fordítódik le, és az eredményül kapott fordítás beíródik a lefordított formába. Így a gyorsítótár könyvtárában több lefordított sablonverzió jön létre, minden nyelvhez egy. Ehhez elég csak a nyelvet megadni második paraméterként: ```php $extension = new Latte\Essential\TranslatorExtension( @@ -179,9 +228,9 @@ $extension = new Latte\Essential\TranslatorExtension( ); ``` -Statikus szöveg alatt például a `{_'hello'}` vagy a `{translate}hello{/translate}` szöveget értjük. A nem statikus szövegek, például a `{_$foo}`, futásidőben továbbra is le lesznek fordítva. +Statikus szöveg alatt például a `{_'hello'}` vagy `{translate}hello{/translate}` értendő. A nem statikus szövegek, mint például `{_$foo}`, továbbra is futásidőben kerülnek fordításra. -A sablon további paramétereket is átadhat a fordítónak a `{_$original, foo: bar}` vagy a `{translate foo: bar}` oldalon keresztül, amelyeket a `$params` tömbként kap meg: +A fordítónak a sablonból kiegészítő paramétereket is átadhatunk a `{_$original, foo: bar}` vagy `{translate foo: bar}` segítségével, amelyeket `$params` tömbként kap meg: ```php public function translate(string $original, ...$params): string @@ -191,66 +240,73 @@ public function translate(string $original, ...$params): string ``` -Hibakeresés és Tracy .[#toc-debugging-and-tracy] -================================================ +Debuggolás és Tracy +=================== -Latte igyekszik a fejlesztést a lehető legkellemesebbé tenni. A hibakereséshez három címke áll rendelkezésre [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak] és [`{trace}` |tags#trace]. +A Latte igyekszik a fejlesztést a lehető legkellemesebbé tenni. Közvetlenül a debuggolás céljára létezik három tag: [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak] és [`{trace}` |tags#trace]. -A legnagyobb kényelmet akkor kapja, ha telepíti a nagyszerű [hibakereső eszközt Tracy |tracy:] és aktiválja a Latte plugint: +A legnagyobb kényelmet akkor éri el, ha még telepíti a kiváló [Tracy hibakereső eszközt|tracy:] és aktiválja a Latte kiegészítőt: ```php -// lehetővé teszi Tracy +// bekapcsolja a Tracy-t Tracy\Debugger::enable(); $latte = new Latte\Engine; -// aktiválja Tracy mellékét +// aktiválja a Tracy kiterjesztést $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension); ``` -Mostantól minden hibát egy takaros piros képernyőn fogsz látni, beleértve a sablonok hibáit is, sor- és oszlopkiemeléssel ([videó |https://github.com/nette/tracy/releases/tag/v2.9.0]). -Ugyanakkor a jobb alsó sarokban, az úgynevezett Tracy Barban megjelenik a Latte fülecske, ahol jól látható az összes renderelt sablon és azok kapcsolatai (beleértve a sablonba vagy a fordított kódba való kattintás lehetőségét), valamint a változók: +Mostantól minden hiba áttekinthető piros képernyőn jelenik meg, beleértve a sablonhibákat is a sor és oszlop kiemelésével ([videó|https://github.com/nette/tracy/releases/tag/v2.9.0]). Ugyanakkor a jobb alsó sarokban, az ún. Tracy Barban megjelenik egy fül a Latte számára, ahol áttekinthetően láthatók az összes renderelt sablon és azok kölcsönös kapcsolatai (beleértve a sablonba vagy a lefordított kódba való átkattintás lehetőségét is), valamint a változók: [* latte-debugging.webp *] -Mivel a Latte a sablonokat olvasható PHP-kóddá fordítja le, kényelmesen lépkedhetsz rajtuk az IDE-ben. +Mivel a Latte a sablonokat áttekinthető PHP kódra fordítja, kényelmesen lépésenként végigkövetheti őket az IDE-jében. -Linter: A sablon szintaxisának validálása .{data-version:2.11}{toc: Linter} -=========================================================================== +Linter: sablonok szintaxisának validálása .{toc: Linter} +======================================================== -A Linter eszköz segít átnézni az összes sablont, és ellenőrizni a szintaxis hibákat. A konzolról indítható: +Az összes sablon átnézéséhez és annak ellenőrzéséhez, hogy nem tartalmaznak-e szintaktikai hibákat, a Linter eszköz segít. Konzolból indítható: ```shell -vendor/bin/latte-lint +vendor/bin/latte-lint <útvonal> ``` -Ha egyéni címkéket használ, hozza létre az egyéni Lintert is, például `custom-latte-lint`: +A `--strict` paraméterrel aktiválhatja a [szigorú módot |#Szigorú mód]. + +Ha saját tageket használ, hozzon létre saját Linter verziót is, pl. `custom-latte-lint`: ```php #!/usr/bin/env php scanDirectory($path); +$path = $argv[1] ?? '.'; -$engine = new Latte\Engine; -// itt regisztrálja az egyes kiterjesztéseket -$engine->addExtension(/* ... */); +$linter = new Latte\Tools\Linter; +$latte = $linter->getEngine(); +// itt adja hozzá az egyes saját kiterjesztéseit +$latte->addExtension(/* ... */); -$path = $argv[1]; -$linter = new Latte\Tools\Linter(engine: $engine); $ok = $linter->scanDirectory($path); exit($ok ? 0 : 1); ``` +Alternatívaként átadhatja a saját `Latte\Engine` objektumát a Linternek: + +```php +$latte = new Latte\Engine; +// itt konfiguráljuk a $latte objektumot +$linter = new Latte\Tools\Linter(engine: $latte); +``` + -Sablonok betöltése egy karakterláncból .[#toc-loading-templates-from-a-string] -============================================================================== +Sablonok betöltése stringből +============================ -Fájlok helyett karakterláncokból kell sablonokat betöltenie, esetleg tesztelési céllal? A [StringLoader |extending-latte#stringloader] segít Önnek: +Szüksége van sablonok betöltésére stringekből fájlok helyett, például tesztelési célokra? Segít a [StringLoader |loaders#StringLoader]: ```php $latte->setLoader(new Latte\Loaders\StringLoader([ @@ -262,10 +318,10 @@ $latte->render('main.file', $params); ``` -Exception Handler .[#toc-exception-handler] -=========================================== +Exception handler +================= -Saját kezelőt definiálhat a várható kivételekhez. Az alábbiakban felvetett kivételek [`{try}` |tags#try] és a [homokozóban |sandbox] keletkeznek, átadódnak neki. +Definiálhat saját kezelőt a várt kivételekhez. Átadódnak neki a [`{try}` |tags#try] belsejében és a [sandboxban|sandbox] keletkezett kivételek. ```php $loggingHandler = function (Throwable $e, Latte\Runtime\Template $template) use ($logger) { @@ -277,17 +333,17 @@ $latte->setExceptionHandler($loggingHandler); ``` -Automatikus elrendezés keresés .[#toc-automatic-layout-lookup] -============================================================== +Layout automatikus keresése +=========================== -A címke használata [`{layout}` |template-inheritance#layout-inheritance], a sablon meghatározza a szülő sablonját. Lehetőség van arra is, hogy az elrendezést automatikusan keressük, ami egyszerűsíti a sablonok írását, mivel nem kell tartalmazniuk a `{layout}` taget. +A [`{layout}` |template-inheritance#Layout öröklődés layout] tag segítségével a sablon meghatározza a szülő sablonját. Lehetőség van a layout automatikus keresésére is, ami leegyszerűsíti a sablonok írását, mivel nem lesz szükség bennük a `{layout}` tag megadására. -Ez a következőképpen érhető el: +Ezt a következő módon érhetjük el: ```php $finder = function (Latte\Runtime\Template $template) { if (!$template->getReferenceType()) { - // a szülő sablon fájl elérési útvonalát adja vissza + // visszaadja a layout fájl elérési útját return 'automatic.layout.latte'; } }; @@ -296,4 +352,4 @@ $latte = new Latte\Engine; $latte->addProvider('coreParentFinder', $finder); ``` -Ha a sablon nem rendelkezik elrendezéssel, akkor ezt a `{layout none}` címkével jelzi. +Ha a sablonnak nem kell layout, azt a `{layout none}` taggal jelzi. diff --git a/latte/hu/extending-latte.texy b/latte/hu/extending-latte.texy index 77c226df7d..67492946f5 100644 --- a/latte/hu/extending-latte.texy +++ b/latte/hu/extending-latte.texy @@ -1,285 +1,227 @@ -Extending Latte -*************** +Latte Bővítése +************** .[perex] -A Latte nagyon rugalmas, és sokféleképpen bővíthető: hozzáadhatsz egyéni szűrőket, funkciókat, címkéket, betöltőket stb. Megmutatjuk, hogyan kell ezt megtenni. +A Latte-t a bővíthetőséget szem előtt tartva tervezték. Bár a standard tag-, szűrő- és függvénykészlete számos felhasználási esetet lefed, gyakran szükség van saját specifikus logika vagy segédeszközök hozzáadására. Ez az oldal áttekintést nyújt a Latte bővítésének módjairól, hogy tökéletesen megfeleljen a projekt követelményeinek - az egyszerű segítőktől a komplex új szintaxisig. -Ez a fejezet a Latte bővítésének különböző módjait ismerteti. Ha a változtatásait különböző projektekben szeretné újra felhasználni, vagy ha meg akarja osztani másokkal, akkor [ún. kiterjesztést |creating-extension] kell [létrehoznia |creating-extension]. +A Latte bővítésének módjai +========================== -Hány út vezet Rómába? .[#toc-how-many-roads-lead-to-rome] -========================================================= - -Mivel a Latte kiterjesztésének néhány módja keveredhet, először próbáljuk meg elmagyarázni a köztük lévő különbségeket. Példaként próbáljunk meg egy *Lorem ipsum* generátort implementálni, amelynek átadjuk a generálandó szavak számát. - -A Latte nyelv fő konstrukciója a tag. Egy generátort úgy tudunk megvalósítani, hogy a Latte-t egy új címkével bővítjük: - -```latte -{lipsum 40} -``` - -A tag nagyszerűen fog működni. A generátor tag formájában azonban nem biztos, hogy elég rugalmas, mert nem lehet kifejezésben használni. Egyébként a gyakorlatban ritkán van szükség címkék generálására; és ez jó hír, mert a címkék bonyolultabb módja a bővítésnek. - -Oké, próbáljunk meg címke helyett szűrőt létrehozni: - -```latte -{=40|lipsum} -``` - -Ismét egy érvényes lehetőség. De a szűrőnek az átadott értéket valami mássá kell átalakítania. Itt a `40` értéket, amely a generált szavak számát jelzi, használjuk a szűrő argumentumaként, nem pedig az átalakítandó értékként. +Íme egy gyors áttekintés a Latte testreszabásának és bővítésének fő módjairól: -Próbáljuk meg tehát a függvényt használni: +- **[Egyéni szűrők |Custom Filters]:** Az adatok formázására vagy átalakítására közvetlenül a sablon kimenetében (pl. `{$var|myFilter}`). Ideális olyan feladatokhoz, mint a dátumformázás, szövegszerkesztés vagy specifikus escapelés alkalmazása. Nagyobb HTML-tartalomblokkok módosítására is használhatja őket úgy, hogy a tartalmat egy névtelen [`{block}` |tags#block] tagbe csomagolja, és egyéni szűrőt alkalmaz rá. +- **[Egyéni függvények |Custom Functions]:** Újrafelhasználható logika hozzáadásához, amelyet a sablon kifejezésein belül lehet meghívni (pl. `{myFunction($arg1, $arg2)}`). Hasznos számításokhoz, az alkalmazás segédfüggvényeihez való hozzáféréshez vagy kisebb tartalomrészek generálásához. +- **[Egyéni tagek |Custom Tags]:** Teljesen új nyelvi konstrukciók létrehozásához (`{mytag}...{/mytag}` vagy `n:mytag`). A tagek kínálják a legtöbb lehetőséget, lehetővé teszik saját struktúrák definiálását, a sablon parzolásának vezérlését és komplex renderelési logika implementálását. +- **[Fordítási menetek |Compiler Passes]:** Függvények, amelyek módosítják a sablon absztrakt szintaxisfáját (AST) a parzolás után, de a PHP kód generálása előtt. Fejlett optimalizálásokhoz, biztonsági ellenőrzésekhez (mint például a Sandbox) vagy automatikus kódmódosításokhoz használják. +- **[Egyéni betöltők |loaders]:** Annak megváltoztatásához, ahogyan a Latte megkeresi és betölti a sablonfájlokat (pl. betöltés adatbázisból, titkosított tárolóból stb.). -```latte -{lipsum(40)} -``` +A megfelelő bővítési módszer kiválasztása kulcsfontosságú. Mielőtt létrehozna egy összetett taget, fontolja meg, hogy elegendő lenne-e egy egyszerűbb szűrő vagy függvény. Mutassuk be ezt egy példán: egy *Lorem ipsum* generátor implementálása, amely argumentumként a generálandó szavak számát fogadja el. -Ez az! Ehhez a konkrét példához a függvény létrehozása az ideális kiterjesztési pont. Bárhol meghívhatja, ahol például egy kifejezést elfogadnak: +- **Tagként?** `{lipsum 40}` - Lehetséges, de a tagek inkább vezérlési struktúrákhoz vagy összetett jelölések generálásához alkalmasak. A tagek nem használhatók közvetlenül kifejezésekben. +- **Szűrőként?** `{=40|lipsum}` - Technikailag működik, de a szűrők a bemeneti érték *átalakítására* szolgálnak. Itt a `40` egy *argumentum*, nem pedig egy átalakítandó érték. Ez szemantikailag helytelennek tűnik. +- **Függvényként?** `{lipsum(40)}` - Ez a legtermészetesebb megoldás! A függvények argumentumokat fogadnak el és értékeket adnak vissza, ami ideális bármely kifejezésben való használatra: `{var $text = lipsum(40)}`. -```latte -{var $text = lipsum(40)} -``` +**Általános ajánlás:** Használjon függvényeket számításokhoz/generáláshoz, szűrőket átalakításhoz és tageket új nyelvi konstrukciókhoz vagy összetett jelölésekhez. Használjon meneteket az AST manipulálásához és loadereket a sablonok lekéréséhez. -Szűrők .[#toc-filters] +Közvetlen regisztráció ====================== -Hozzon létre egy szűrőt a nevének és bármely PHP-meghívható elemnek, például függvénynek a regisztrálásával: +Projektspecifikus segédeszközökhöz vagy gyors bővítésekhez a Latte lehetővé teszi a szűrők és függvények közvetlen regisztrálását a `Latte\Engine` objektumba. + +Szűrő regisztrálásához használja az `addFilter()` metódust. A szűrőfüggvény első argumentuma a `|` jel előtti érték lesz, a következő argumentumok pedig azok, amelyeket a kettőspont `:` után adunk át. ```php $latte = new Latte\Engine; -$latte->addFilter('shortify', fn(string $s) => mb_substr($s, 0, 10)); // a szöveget 10 karakterre rövidíti. -``` -Ebben az esetben jobb lenne, ha a szűrő kapna egy további paramétert: +// Szűrő definíciója (hívható objektum: függvény, statikus metódus stb.) +$myTruncate = fn(string $s, int $length = 50) => mb_substr($s, 0, $length); -```php -$latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); -``` +// Regisztráció +$latte->addFilter('truncate', $myTruncate); -A sablonban így használjuk: - -```latte -

                                                                      {$text|shortify}

                                                                      -

                                                                      {$text|shortify:100}

                                                                      +// Használat a sablonban: {$text|truncate} vagy {$text|truncate:100} ``` -Mint látható, a függvény a következő argumentumként a szűrő bal oldalát kapja a pipe `|` as the first argument and the arguments passed to the filter after `:` előtt. - -Természetesen a szűrőt reprezentáló függvény tetszőleges számú paramétert fogadhat el, és a változó paraméterek is támogatottak. - - -Az osztályt használó szűrők .[#toc-filters-using-the-class] ------------------------------------------------------------ - -A szűrő definiálásának második módja az [osztály használata |develop#Parameters as a class]. Létrehozunk egy metódust a `TemplateFilter` attribútummal: +Regisztrálhat egy **Szűrő Betöltőt (Filter Loader)** is, egy függvényt, amely dinamikusan biztosítja a hívható szűrőobjektumokat a kért név alapján: ```php -class TemplateParameters -{ - public function __construct( - // paraméterek - ) {} - - #[Latte\Attributes\TemplateFilter] - public function shortify(string $s, int $len = 10): string - { - return mb_substr($s, 0, $len); - } -} - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); +$latte->addFilterLoader(fn(string $name) => /* visszaad egy hívható objektumot vagy null-t */); ``` -Ha PHP 7.x és Latte 2.x rendszert használ, az attribútum helyett a `/** @filter */` megjegyzést használja. - -Szűrő betöltő .[#toc-filter-loader] ------------------------------------ - -Az egyedi szűrők regisztrálása helyett létrehozhat egy úgynevezett betöltőt, amely egy olyan függvény, amelyet a szűrő nevével mint argumentummal hívunk meg, és amely visszaadja a PHP hívhatóságát, vagy nullát. +A sablon kifejezéseiben használható függvény regisztrálásához használja az `addFunction()` metódust. ```php -$latte->addFilterLoader([new Filters, 'load']); +$latte = new Latte\Engine; +// Függvény definíciója +$isWeekend = fn(DateTimeInterface $date) => $date->format('N') >= 6; -class Filters -{ - public function load(string $filter): ?callable - { - if (in_array($filter, get_class_methods($this))) { - return [$this, $filter]; - } - return null; - } - - public function shortify($s, $len = 10) - { - return mb_substr($s, 0, $len); - } - - // ... -} +// Regisztráció +$latte->addFunction('isWeekend', $isWeekend); + +// Használat a sablonban: {if isWeekend($myDate)}Hétvége!{/if} ``` +További információkért lásd az [Egyéni szűrők létrehozása |custom-filters] és [Függvények |custom-functions] részeket. -Kontextuális szűrők .[#toc-contextual-filters] ----------------------------------------------- -A kontextuális szűrő olyan szűrő, amely az első paraméterként egy objektumot fogad el: [api:Latte\Runtime\FilterInfo], amelyet a klasszikus szűrőkhöz hasonlóan további paraméterek követnek. Ugyanúgy regisztrálódik, maga a Latte ismeri fel, hogy a szűrő kontextuális: +Robusztus módszer: Latte Extension .{toc: Latte Extension} +========================================================== -```php -use Latte\Runtime\FilterInfo; +Míg a közvetlen regisztráció egyszerű, a Latte bővítmények csomagolásának és terjesztésének standard és ajánlott módja az **Extension** osztályokon keresztül történik. Az Extension központi konfigurációs pontként szolgál több tag, szűrő, függvény, fordítási menet és egyéb elem regisztrálásához. -$latte->addFilter('foo', function (FilterInfo $info, string $str): string { - // ... -}); -``` +Miért használjunk Extension-öket? + +- **Szervezettség:** Összetartozó bővítményeket (tagek, szűrők stb. egy adott funkcióhoz) egy osztályban tart. +- **Újrafelhasználhatóság és megosztás:** Könnyen csomagolhatja bővítményeit más projektekben való használatra vagy a közösséggel való megosztásra (pl. Composer segítségével). +- **Teljes erő:** Az egyéni tagek és fordítási menetek *csak* Extension-ökön keresztül regisztrálhatók. -A kontextusszűrők képesek felismerni és megváltoztatni a `$info->contentType` változóban kapott tartalomtípust. Ha a szűrőt klasszikusan egy változó (pl. `{$var|foo}`) felett hívjuk meg, a `$info->contentType` nullát fog tartalmazni. -A szűrőnek először azt kell ellenőriznie, hogy a bemeneti karakterlánc tartalomtípusa támogatott-e. Meg is változtathatja azt. Példa egy olyan szűrőre, amely szöveget (vagy nullot) fogad el és HTML-t ad vissza: +Extension regisztrálása +----------------------- + +Az Extension a Latte-ban az `addExtension()` metódussal regisztrálható (vagy a [konfigurációs fájlon |application:configuration#Latte sablonok] keresztül): ```php -use Latte\Runtime\FilterInfo; - -$latte->addFilter('money', function (FilterInfo $info, float $amount): string { - // először ellenőrizzük, hogy a bemenet tartalma text-e. - if (!in_array($info->contentType, [null, ContentType::Text])) { - throw new Exception("Szűrő |pénz nem kompatibilis tartalomtípusban használt $info->contentType."); - } - - // a tartalomtípust HTML-re módosítja - $info->contentType = ContentType::Html; - return "$num Kč"; -}); +$latte = new Latte\Engine; +$latte->addExtension(new MyProjectExtension); ``` -.[note] -Ebben az esetben a szűrőnek biztosítania kell az adatok helyes escapingjét. +Ha több bővítményt regisztrál, és azok azonos nevű tageket, szűrőket vagy függvényeket definiálnak, az utoljára hozzáadott bővítmény élvez elsőbbséget. Ez azt is jelenti, hogy a bővítményei felülírhatják a natív tageket/szűrőket/függvényeket. -Minden olyan szűrő, amelyet [blokkok |tags#block] felett használunk (pl. mint a `{block|foo}...{/block}`) kontextusfüggőnek kell lennie. +Amikor módosítást végez az osztályon, és az automatikus frissítés nincs kikapcsolva, a Latte automatikusan újrafordítja a sablonjait. -Funkciók .[#toc-functions] -========================== +Extension létrehozása +--------------------- -Alapértelmezés szerint minden natív PHP függvény használható a Latte-ban, hacsak a homokozó nem tiltja le. De definiálhatsz saját függvényeket is. Ezek felülírhatják a natív függvényeket. +Egyéni bővítmény létrehozásához létre kell hoznia egy osztályt, amely a [api:Latte\Extension] osztályból öröklődik. Hogy képet kapjon arról, hogyan néz ki egy ilyen bővítmény, tekintse meg a beépített "CoreExtension":https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php. -Hozzon létre egy függvényt a nevének és bármely PHP hívhatóságának regisztrálásával: +Nézzük meg azokat a metódusokat, amelyeket implementálhat: -```php -$latte = new Latte\Engine; -$latte->addFunction('random', function (...$args) { - return $args[array_rand($args)]; -}); -``` -A használat ezután ugyanaz, mint a PHP-funkció hívásakor: +beforeCompile(Latte\Engine $engine): void .[method] +--------------------------------------------------- -```latte -{random(apple, orange, lemon)} // prints for example: apple -``` +A sablon fordítása előtt hívódik meg. A metódus például a fordítással kapcsolatos inicializálásokhoz használható. -Az osztályt használó függvények .[#toc-functions-using-the-class] ------------------------------------------------------------------ +getTags(): array .[method] +-------------------------- -A függvények definiálásának második módja az [osztály használata |develop#Parameters as a class]. Létrehozunk egy metódust a `TemplateFunction` attribútummal: +A sablon fordításakor hívódik meg. Asszociatív tömböt ad vissza *tag neve => hívható objektum* formában, amelyek a tag parzoló függvények. [További információk |custom-tags]. ```php -class TemplateParameters +public function getTags(): array { - public function __construct( - // paraméterek - ) {} - - #[Latte\Attributes\TemplateFunction] - public function random(...$args) - { - return $args[array_rand($args)]; - } + return [ + 'foo' => FooNode::create(...), + 'bar' => BarNode::create(...), + 'n:baz' => NBazNode::create(...), + // ... + ]; } - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); ``` -Ha PHP 7.x és Latte 2.x programot használ, az attribútum helyett a `/** @function */` megjegyzést használja. +Az `n:baz` tag egy tiszta [n:attribútumot |syntax#n:attribútumok] képvisel, azaz egy olyan taget, amely csak attribútumként írható. +A `foo` és `bar` tagek esetében a Latte automatikusan felismeri, hogy páros tagekről van-e szó, és ha igen, akkor automatikusan írhatók n:attribútumokkal, beleértve az `n:inner-foo` és `n:tag-foo` előtagú változatokat is. -Betöltők .[#toc-loaders] -======================== +Az ilyen n:attribútumok végrehajtási sorrendjét a `getTags()` metódus által visszaadott tömbben elfoglalt sorrendjük határozza meg. Tehát az `n:foo` mindig az `n:bar` előtt kerül végrehajtásra, még akkor is, ha az attribútumok a HTML tagben fordított sorrendben vannak megadva, mint `
                                                                      `. -A betöltők felelősek a sablonok betöltéséért egy forrásból, például egy fájlrendszerből. Ezek beállítása a `setLoader()` módszerrel történik: +Ha több bővítmény között kell meghatároznia az n:attribútumok sorrendjét, használja az `order()` segédmetódust, ahol a `before` xor `after` paraméter határozza meg, hogy mely tagek kerülnek a tag elé vagy mögé. ```php -$latte->setLoader(new MyLoader); +public function getTags(): array +{ + return [ + 'foo' => self::order(FooNode::create(...), before: 'bar'), + 'bar' => self::order(BarNode::create(...), after: ['block', 'snippet']), + ]; +} ``` -A beépített betöltők a következők: +getPasses(): array .[method] +---------------------------- -FileLoader .[#toc-fileloader] ------------------------------ - -Alapértelmezett betöltő. Sablonokat tölt be a fájlrendszerből. +A sablon fordításakor hívódik meg. Asszociatív tömböt ad vissza *menet neve => hívható objektum* formában, amelyek az úgynevezett [fordítási meneteket |compiler-passes] képviselik, amelyek bejárják és módosítják az AST-t. -A fájlokhoz való hozzáférés korlátozható az alapkönyvtár beállításával: +Itt is használható az `order()` segédmetódus. A `before` vagy `after` paraméterek értéke lehet `*` s jelentéssel: minden előtt/után. ```php -$latte->setLoader(new Latte\Loaders\FileLoader($templateDir)); -$latte->render('test.latte'); +public function getPasses(): array +{ + return [ + 'optimize' => Passes::optimizePass(...), + 'sandbox' => self::order($this->sandboxPass(...), before: '*'), + // ... + ]; +} ``` -StringLoader .[#toc-stringloader] ---------------------------------- +beforeRender(Latte\Engine $engine): void .[method] +-------------------------------------------------- -Sablonokat tölt be stringekből. Ez a betöltő nagyon hasznos a unit teszteléshez. Kisebb projekteknél is használható, ahol érdemes az összes sablon egyetlen PHP fájlban tárolni. +Minden sablon renderelése előtt hívódik meg. A metódus például a renderelés során használt változók inicializálására használható. -```php -$latte->setLoader(new Latte\Loaders\StringLoader([ - 'main.file' => '{include other.file}', - 'other.file' => '{if true} {$var} {/if}', -])); -$latte->render('main.file'); -``` +getFilters(): array .[method] +----------------------------- -Egyszerűsített használat: +A sablon renderelése előtt hívódik meg. A szűrőket asszociatív tömbként adja vissza *szűrő neve => hívható objektum* formában. [További információk |custom-filters]. ```php -$template = '{if true} {$var} {/if}'; -$latte->setLoader(new Latte\Loaders\StringLoader); -$latte->render($template); +public function getFilters(): array +{ + return [ + 'batch' => $this->batchFilter(...), + 'trim' => $this->trimFilter(...), + // ... + ]; +} ``` -Egyéni betöltő létrehozása .[#toc-creating-a-custom-loader] ------------------------------------------------------------ +getFunctions(): array .[method] +------------------------------- -A Loader egy osztály, amely a [api:Latte\Loader] interfészt valósítja meg. +A sablon renderelése előtt hívódik meg. A függvényeket asszociatív tömbként adja vissza *függvény neve => hívható objektum* formában. [További információk |custom-functions]. +```php +public function getFunctions(): array +{ + return [ + 'clamp' => $this->clampFunction(...), + 'divisibleBy' => $this->divisibleByFunction(...), + // ... + ]; +} +``` -Címkék .[#toc-tags] -=================== - -A templating motor egyik legérdekesebb funkciója, hogy új nyelvi konstrukciókat definiálhatunk címkék segítségével. Ez egyben egy összetettebb funkcionalitás is, és meg kell értenie, hogyan működik a Latte belsőleg. -A legtöbb esetben azonban nincs szükség a címkére: -- Ha valamilyen kimenetet kell generálnia, használjon helyette [függvényt |#functions]. -- ha valamilyen bemenetet kellene módosítania és visszaadnia, használjon helyette [filtert |#filters] -- ha egy szövegrészletet kellene szerkesztenie, akkor csomagolja be egy [`{block}` |tags#block] címkével és egy [szűrővel |#Contextual Filters] -- ha nem kellett volna semmit kiadnia, hanem csak egy függvényt hívnia, hívja meg a [`{do}` |tags#do] +getProviders(): array .[method] +------------------------------- -Ha mégis taget akarsz létrehozni, remek! Minden lényeges tudnivalót megtalálsz a [Bővítmény létrehozása |creating-extension] című fejezetben. +A sablon renderelése előtt hívódik meg. Szolgáltatók tömbjét adja vissza, amelyek általában olyan objektumok, amelyeket a tagek futásidőben használnak. Hozzájuk a `$this->global->...` segítségével lehet hozzáférni. [További információk |custom-tags#Szolgáltatók bemutatása]. +```php +public function getProviders(): array +{ + return [ + 'myFoo' => $this->foo, + 'myBar' => $this->bar, + // ... + ]; +} +``` -Fordítói passzusok .[#toc-compiler-passes] -========================================== -A fordítói átmenetek olyan függvények, amelyek módosítják az AST-eket vagy információt gyűjtenek bennük. A Latte-ban például egy homokozót így valósítanak meg: végigjárja egy AST összes csomópontját, megtalálja a függvény- és metódushívásokat, és azokat ellenőrzött hívásokkal helyettesíti. +getCacheKey(Latte\Engine $engine): mixed .[method] +-------------------------------------------------- -A címkékhez hasonlóan ez is összetettebb funkcionalitás, és meg kell értenie, hogyan működik a Latte a motorháztető alatt. Az összes lényeges tudnivaló megtalálható a [Bővítmény létrehozása |creating-extension] fejezetben. +A sablon renderelése előtt hívódik meg. A visszatérési érték a kulcs részévé válik, amelynek hash-értéke a lefordított sablonfájl nevében található. Tehát különböző visszatérési értékek esetén a Latte különböző cache fájlokat generál. diff --git a/latte/hu/filters.texy b/latte/hu/filters.texy index 4cef0c07e2..9160678cff 100644 --- a/latte/hu/filters.texy +++ b/latte/hu/filters.texy @@ -2,111 +2,113 @@ Latte szűrők ************ .[perex] -A szűrők olyan függvények, amelyek megváltoztatják vagy formázzák az adatokat a kívánt formára. Ez az összefoglaló a rendelkezésre álló beépített szűrőkről. +A sablonokban olyan függvényeket használhatunk, amelyek segítenek az adatok módosításában vagy újraformázásában a végső formába. Ezeket *szűrőknek* nevezzük. .[table-latte-filters] -|## String / tömb átalakítás -| `batch` | [lineáris adatok listázása egy táblázatban |#batch] -| `breakLines` | [HTML sorkizárt beillesztése az újsorok előtt |#breakLines]. -| `bytes` | [méretformázás bájtban |#bytes] -| `clamp` | [az értéket a tartományba szorítja |#clamp]. -| `dataStream` | [Adat URI protokoll átalakítás |#datastream] -| `date` | [dátumformázás |#date] -| `explode` | [a karakterláncot a megadott elválasztóval osztja fel |#explode]. -| `first` | [visszaadja a tömb első elemét vagy a karakterlánc első karakterét |#first]. -| `implode` | [egy tömböt egy karakterlánccal kapcsol össze |#implode]. -| `indent` | [a szöveget balról behúzza a tabulátorok számával |#indent] -| `join` | [egy tömböt egy karakterlánchoz kapcsol |#implode]. -| `last` | [visszaadja a tömb utolsó elemét vagy a karakterlánc utolsó karakterét |#last]. -| `length` | [egy karakterlánc vagy tömb hosszát adja vissza |#length]. -| `number` | [formázza a számot |#number] -| `padLeft` | [balról balra kiegészíti a karakterláncot a megadott hosszúságúra |#padLeft]. -| `padRight` | [a stringet jobbról a megadott hosszúságra egészíti ki |#padRight]. -| `random` | [visszaadja a tömb véletlenszerű elemét vagy a karakterlánc karakterét |#random]. -| `repeat` | [megismétli a karakterláncot |#repeat] -| `replace` | [a keresett karakterlánc minden előfordulását helyettesíti a helyettesítő karakterlánccal |#replace]. -| `replaceRE` | [az összes előfordulást a reguláris kifejezésnek megfelelően helyettesíti |#replaceRE]. -| `reverse` | [megfordítja az UTF-8 karakterláncot vagy tömböt |#reverse]. -| `slice` | [kivonja egy tömb vagy egy karakterlánc egy szeletét |#slice]. -| `sort` | [rendezi a tömböt |#sort] -| `spaceless` | [eltávolítja a szóközöket |#spaceless], hasonlóan a [spaceless |tags] taghez. -| `split` | [egy karakterláncot a megadott elválasztójel alapján szétválaszt |#explode]. -| `strip` | [eltávolítja a szóközöket |#spaceless] -| `stripHtml` | [eltávolítja a HTML-címkéket és a HTML-elemeket szöveggé alakítja át |#stripHtml]. -| `substr` | [visszaadja a karakterlánc egy részét |#substr] -| `trim` | [eltávolítja a szóközöket a karakterláncból |#trim]. -| `translate` | [fordítás más nyelvekre |#translate] -| `truncate` | [lerövidíti a hosszúságot, megőrizve az egész szavakat |#truncate]. -| `webalize` | [az UTF-8 karakterláncot az URL-ben használt formához igazítja |#webalize]. +|## Átalakítás +| `batch` | [lineáris adatok kiírása táblázatba |#batch] +| `breakLines` | [HTML sortörést ad a sorvégek elé |#breakLines] +| `bytes` | [méretet formáz bájtokban |#bytes] +| `clamp` | [értéket korlátoz adott tartományba |#clamp] +| `dataStream` | [konverzió Data URI protokollhoz |#dataStream] +| `date` | [dátumot és időt formáz |#date] +| `explode` | [stringet tömbre bont elválasztó szerint |#explode] +| `first` | [visszaadja a tömb első elemét vagy a string első karakterét |#first] +| `group` | [adatokat csoportosít különböző kritériumok szerint |#group] +| `implode` | [tömböt stringgé fűz össze |#implode] +| `indent` | [szöveget behúz balról adott számú tabulátorral |#indent] +| `join` | [tömböt stringgé fűz össze |#implode] +| `last` | [visszaadja a tömb utolsó elemét vagy a string utolsó karakterét |#last] +| `length` | [visszaadja a string hosszát karakterekben vagy a tömb hosszát |#length] +| `localDate` | [dátumot és időt formáz a nemzeti beállítások szerint |#localDate] +| `number` | [számot formáz |#number] +| `padLeft` | [stringet balról kiegészít a kívánt hosszúságra |#padLeft] +| `padRight` | [stringet jobbról kiegészít a kívánt hosszúságra |#padRight] +| `random` | [visszaadja a tömb véletlen elemét vagy a string véletlen karakterét |#random] +| `repeat` | [string ismétlése |#repeat] +| `replace` | [lecseréli a keresett string előfordulásait |#replace] +| `replaceRE` | [lecseréli az előfordulásokat reguláris kifejezés szerint |#replaceRE] +| `reverse` | [megfordítja az UTF‑8 stringet vagy tömböt |#reverse] +| `slice` | [kivon egy részt a tömbből vagy stringből |#slice] +| `sort` | [rendezi a tömböt |#sort] +| `spaceless` | [eltávolítja a felesleges szóközöket |#spaceless], hasonlóan a [spaceless |tags] taghez +| `split` | [stringet tömbre bont elválasztó szerint |#explode] +| `strip` | [eltávolítja a felesleges szóközöket |#spaceless] +| `stripHtml` | [eltávolítja a HTML tageket és a HTML entitásokat karakterekké alakítja |#stripHtml] +| `substr` | [visszaadja a string egy részét |#substr] +| `trim` | [eltávolítja a kezdő és záró szóközöket vagy más karaktereket |#trim] +| `translate` | [fordítás más nyelvekre |#translate] +| `truncate` | [rövidíti a hosszt a szavak megőrzésével |#truncate] +| `webalize` | [UTF‑8 stringet URL-ben használt formára alakít |#webalize] .[table-latte-filters] -|## Betűhüvelyezés -| `capitalize` | [kisbetűs, minden szó első betűje nagybetűs |#capitalize] -| `firstUpper` | [az első betűt nagybetűvé teszi |#firstUpper]. -| `lower` | [a karakterláncot kisbetűvé teszi |#lower] -| `upper` | [nagybetűvé teszi a karakterláncot |#upper] +|## Kis- és nagybetűk +| `capitalize` | [kisbetűk, a szavak első betűje nagy |#capitalize] +| `firstUpper` | [az első betűt naggyá alakítja |#firstUpper] +| `lower` | [kisbetűssé alakít |#lower] +| `upper` | [nagybetűssé alakít |#upper] .[table-latte-filters] -|## Számok kerekítése -| `ceil` | [kerekít egy számot egy adott pontosságig |#ceil]. -| `floor` | [Egy számot adott pontosságra kerekít lefelé |#floor]. -| `round` | [kerekít egy számot adott pontosságra |#round]. +|## Kerekítés +| `ceil` | [számot felfelé kerekít adott pontosságra |#ceil] +| `floor` | [számot lefelé kerekít adott pontosságra |#floor] +| `round` | [számot adott pontosságra kerekít |#round] .[table-latte-filters] -|## Escaping -| `escapeUrl` | [az URL-ben szereplő paramétert elrejti |#escapeUrl] -| `noescape` | [változó nyomtatása szaggatás nélkül |#noescape]. -| `query` | [lekérdezési karakterláncot generál az URL-ben |#query]. +|## Escapelés +| `escapeUrl` | [escapeli a paramétert az URL-ben |#escapeUrl] +| `noescape` | [kiírja a változót escapelés nélkül |#noescape] +| `query` | [query stringet generál az URL-ben |#query] -A HTML (`escapeHtml` és `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) és iCalendar (`escapeICal`) számára is vannak escaping szűrők, amelyeket a Latte a [kontextustudatos escapingnek |safety-first#Context-aware escaping] köszönhetően maga használ, és nem kell megírni őket. +Továbbá léteznek escapelő szűrők HTML-hez (`escapeHtml` és `escapeHtmlComment`), XML-hez (`escapeXml`), JavaScripthez (`escapeJs`), CSS-hez (`escapeCss`) és iCalendarhez (`escapeICal`), amelyeket a Latte maga használ a [kontextusérzékeny escapelés |safety-first#Kontextusérzékeny escapelés] révén, és nem kell őket kiírni. .[table-latte-filters] |## Biztonság -| `checkUrl` | [szanálja a href attribútumon belül használt karakterláncot |#checkUrl]. -| `nocheck` | [megakadályozza az automatikus URL-szanálást |#nocheck]. +| `checkUrl` | [megtisztítja az URL címet a veszélyes bemenetektől |#checkUrl] +| `nocheck` | [megakadályozza az URL cím automatikus tisztítását |#nocheck] -A `src` és a `href` attribútumokat [automatikusan ellenőrzi |safety-first#link checking], így a `checkUrl` szűrőt szinte nem is kell használni. +A Latte `src` és `href` attribútumokat [automatikusan ellenőrzi |safety-first#Linkek ellenőrzése], így a `checkUrl` szűrőt szinte soha nem kell használni. .[note] -Minden beépített szűrő UTF-8 kódolású karakterláncokkal működik. +Minden alapértelmezett szűrő UTF‑8 kódolású stringekhez készült. -Használat .[#toc-usage] -======================= +Használat +========= -A Latte lehetővé teszi a szűrők hívását a pipajel jelölés használatával (az előző szóköz megengedett): +A szűrőket függőleges vonal után írjuk (előtte lehet szóköz): ```latte

                                                                      {$heading|upper}

                                                                      ``` -A szűrők láncolhatók, ebben az esetben balról jobbra haladva alkalmazandók: +A szűrőket (régebbi verziókban helper-eket) láncolni lehet, és balról jobbra kerülnek alkalmazásra: ```latte

                                                                      {$heading|lower|capitalize}

                                                                      ``` -A paraméterek a szűrő neve után kerülnek, kettősponttal vagy vesszővel elválasztva: +A paramétereket a szűrő neve után adjuk meg, kettősponttal vagy vesszővel elválasztva: ```latte

                                                                      {$heading|truncate:20,''}

                                                                      ``` -A szűrők kifejezésekre alkalmazhatók: +A szűrőket kifejezésre is lehet alkalmazni: ```latte {var $name = ($title|upper) . ($subtitle|lower)} ``` -[Egyéni szűrők |extending-latte#filters] így regisztrálhatók: +[Egyéni szűrőket|custom-filters] így lehet regisztrálni: ```php $latte = new Latte\Engine; $latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -Egy sablonban így használjuk: +A sablonban pedig így hívjuk meg: ```latte

                                                                      {$text|shortify}

                                                                      @@ -114,18 +116,18 @@ Egy sablonban így használjuk: ``` -Szűrők .[#toc-filters] -====================== +Szűrők +====== -batch(int length, mixed item): array .[filter]{data-version:2.7} ----------------------------------------------------------------- -Szűrő, amely leegyszerűsíti a lineáris adatok táblázatos formában történő felsorolását. Egy tömb tömböt ad vissza a megadott számú elemmel. Ha megad egy második paramétert, akkor ezt az utolsó sor hiányzó elemeinek kitöltésére használja. +batch(int $length, mixed $item): array .[filter] +------------------------------------------------ +Szűrő, amely leegyszerűsíti a lineáris adatok táblázatos formában történő kiírását. Visszaad egy tömbökből álló tömböt a megadott számú elemmel. Ha megadja a második paramétert, azt használja a hiányzó elemek pótlására az utolsó sorban. ```latte {var $items = ['a', 'b', 'c', 'd', 'e']}
                                                                      Belső ciklus
                                                                      -{foreach ($items|batch: 3, 'No item') as $row} +{foreach ($items|batch: 3, 'Nincs elem') as $row} {foreach $row as $column} @@ -135,7 +137,7 @@ Szűrő, amely leegyszerűsíti a lineáris adatok táblázatos formában tört
                                                                      {$column}
                                                                      ``` -Nyomtatás: +Kiírja: ```latte @@ -147,25 +149,27 @@ Nyomtatás: - +
                                                                      d eNo itemNincs elem
                                                                      ``` +Lásd még [#group] és a [iterateWhile |tags#iterateWhile] taget. + breakLines .[filter] -------------------- -HTML-sorszünetet illeszt be minden újsor előtt. +Minden új sor karakter elé beszúrja a `
                                                                      ` HTML taget. ```latte -{var $s = "Text & with \n newline"} -{$s|breakLines} {* kimenetek "Text & with
                                                                      \n newline" *} +{var $s = "Szöveg & \n új sorral"} +{$s|breakLines} {* kiírja: "Szöveg &
                                                                      \n új sorral" *} ``` -bytes(int precision = 2) .[filter] ----------------------------------- -Bájtban megadott méretet formázza ember által olvashatóvá. +bytes(int $precision=2) .[filter] +--------------------------------- +A bájtban megadott méretet ember által olvasható formátumra alakítja. Ha a [nemzeti beállítások |develop#Locale] be vannak állítva, a megfelelő tizedes- és ezreselválasztókat használja. ```latte {$size|bytes} 0 B, 1.25 GB, … @@ -173,53 +177,53 @@ Bájtban megadott méretet formázza ember által olvashatóvá. ``` -ceil(int precision = 0) .[filter] ---------------------------------- -Egy számot adott pontosságra kerekít. +ceil(int $precision=0) .[filter] +-------------------------------- +Felfelé kerekíti a számot a megadott pontosságra. ```latte -{=3.4|ceil} {* kimenet 4 *} -{=135.22|ceil:1} {* kimenet 135.3 *} -{=135.22|ceil:3} {* kimenet 135.22 *} +{=3.4|ceil} {* kiírja: 4 *} +{=135.22|ceil:1} {* kiírja: 135.3 *} +{=135.22|ceil:3} {* kiírja: 135.22 *} ``` -Lásd még [floor |#floor], [round |#round]. +Lásd még [#floor], [#round]. capitalize .[filter] -------------------- -Visszaadja az érték címsoros változatát. A szavak nagybetűkkel kezdődnek, az összes többi karakter kisbetűs. PHP-bővítményt igényel: `mbstring`. +A szavak nagybetűvel kezdődnek, az összes többi karakter kisbetűs lesz. Szükséges a `mbstring` PHP kiterjesztés. ```latte -{='i like LATTE'|capitalize} {* kimenet 'I Like Latte' *} +{='szeretem a LATTÉt'|capitalize} {* kiírja: 'Szeretem A Lattét' *} ``` -Lásd még [firstUpper |#firstUpper], [lower |#lower], [upper |#upper]. +Lásd még [#firstUpper], [#lower], [#upper]. checkUrl .[filter] ------------------ -Kényszeríti az URL szanálását. Ellenőrzi, hogy a változó tartalmaz-e webes URL-t (azaz HTTP/HTTPS protokollt), és megakadályozza a biztonsági kockázatot jelentő linkek írását. +Kikényszeríti az URL cím tisztítását. Ellenőrzi, hogy a változó webes URL-t tartalmaz-e (azaz HTTP/HTTPS protokollt), és megakadályozza az olyan linkek kiírását, amelyek biztonsági kockázatot jelenthetnek. ```latte {var $link = 'javascript:window.close()'} -checked -unchecked +ellenőrzött +nem ellenőrzött ``` -Nyomtat: +Kiírja: ```latte -checked -unchecked +ellenőrzött +nem ellenőrzött ``` -Lásd [mégeck |#nocheck]. +Lásd még [#nocheck]. -clamp(int|float min, int|float max) .[filter]{data-version:2.9} ---------------------------------------------------------------- -A min és max tartományba szorított értéket adja vissza. +clamp(int|float $min, int|float $max) .[filter] +----------------------------------------------- +Az értéket a megadott inkluzív min és max tartományba korlátozza. ```latte {$level|clamp: 0, 255} @@ -228,17 +232,17 @@ A min és max tartományba szorított értéket adja vissza. Létezik [függvényként |functions#clamp] is. -dataStream(string mimetype = detect) .[filter] ----------------------------------------------- -A tartalmat adat URI-sémává alakítja át. Használható képek HTML- vagy CSS-be való beillesztésére anélkül, hogy külső fájlokat kellene linkelni. +dataStream(string $mimetype=detect) .[filter] +--------------------------------------------- +A tartalmat data URI scheme-re konvertálja. Segítségével képeket lehet beágyazni HTML-be vagy CSS-be külső fájlok linkelése nélkül. -Legyen egy kép egy változóban `$img = Image::fromFile('obrazek.gif')`, akkor +Legyen egy kép a `$img = Image::fromFile('kep.gif')` változóban, ekkor ```latte - + ``` -Kinyomtatja például: +Kiírja például: ```latte {$name} ``` -Lásd még [query |#query]. +Lásd még [#query]. -explode(string separator = '') .[filter]{data-version:2.10.2} -------------------------------------------------------------- -Egy karakterláncot a megadott elválasztójel alapján szétválaszt, és karakterláncok tömbjét adja vissza. Alias a `split` számára. +explode(string $separator='') .[filter] +--------------------------------------- +Stringet tömbre bont elválasztó szerint. Alias a `split`-re. ```latte -{='one,two,three'|explode:','} {* visszatér ['one', 'two', 'three'] *} +{='egy,kettő,három'|explode:','} {* visszaadja: ['egy', 'kettő', 'három'] *} ``` -Ha az elválasztó üres karakterlánc (alapértelmezett érték), akkor a bemenet különálló karakterekre lesz felosztva: +Ha az elválasztó üres string (alapértelmezett érték), a bemenet egyes karakterekre lesz bontva: ```latte -{='123'|explode} {* returns ['1', '2', '3'] *} +{='123'|explode} {* visszaadja: ['1', '2', '3'] *} ``` Használhatja a `split` aliast is: ```latte -{='1,2,3'|split:','} {* visszatér ['1', '2', '3'] *} +{='1,2,3'|split:','} {* visszaadja: ['1', '2', '3'] *} ``` -Lásd még: [implode |#implode]. +Lásd még [#implode]. -first .[filter]{data-version:2.10.2} ------------------------------------- -Visszaadja a tömb első elemét vagy a karakterlánc első karakterét: +first .[filter] +--------------- +Visszaadja a tömb első elemét vagy a string első karakterét: ```latte -{=[1, 2, 3, 4]|first} {* kimenet 1 *} -{='abcd'|first} {* kimenet 'a' *} +{=[1, 2, 3, 4]|first} {* kiírja: 1 *} +{='abcd'|first} {* kiírja: 'a' *} ``` -Lásd még [last |#last], [random |#random]. +Lásd még [#last], [#random]. -floor(int precision = 0) .[filter] ----------------------------------- -Egy számot adott pontosságra kerekít. +floor(int $precision=0) .[filter] +--------------------------------- +Lefelé kerekíti a számot a megadott pontosságra. ```latte -{=3.5|floor} {* outputs 3 *} -{=135.79|floor:1} {* outputs 135.7 *} -{=135.79|floor:3} {* outputs 135.79 *} +{=3.5|floor} {* kiírja: 3 *} +{=135.79|floor:1} {* kiírja: 135.7 *} +{=135.79|floor:3} {* kiírja: 135.79 *} ``` -Lásd még [ceil |#ceil], [round |#round]. +Lásd még [#ceil], [#round]. firstUpper .[filter] -------------------- -Az érték első betűjét nagybetűvé alakítja. PHP-bővítményt igényel `mbstring`. +Az első betűt naggyá alakítja. Szükséges a `mbstring` PHP kiterjesztés. ```latte -{='the latte'|firstUpper} {* kimenetek 'The latte' *} +{='a latte'|firstUpper} {* kiírja: 'A latte' *} ``` -Lásd még [nagybetű |#capitalize], [kisbetű |#lower], [nagybetű |#upper]. +Lásd még [#capitalize], [#lower], [#upper]. -implode(string glue = '') .[filter] ------------------------------------ -Visszaad egy stringet, amely a tömbben lévő stringek összevonása. Alias a következőhöz: `join`. +group(string|int|\Closure $by): array .[filter]{data-version:3.0.16} +-------------------------------------------------------------------- +A szűrő csoportosítja az adatokat különböző kritériumok szerint. + +Ebben a példában a táblázat sorai a `categoryId` oszlop szerint vannak csoportosítva. A kimenet egy tömbökből álló tömb, ahol a kulcs a `categoryId` oszlop értéke. [Olvassa el a részletes útmutatót|cookbook/grouping]. ```latte -{=[1, 2, 3]|implode} {* outputs '123' *} -{=[1, 2, 3]|implode:'|'} {* outputs '1|2|3' *} +{foreach ($items|group: categoryId) as $categoryId => $categoryItems} +
                                                                        + {foreach $categoryItems as $item} +
                                                                      • {$item->name}
                                                                      • + {/foreach} +
                                                                      +{/foreach} ``` -A `join` alias is használható: .{data-version:2.10.2} +Lásd még [#batch], a [group |functions#group] függvény és a [iterateWhile |tags#iterateWhile] tag. + + +implode(string $glue='') .[filter] +---------------------------------- +Visszaad egy stringet, amely a szekvencia elemeinek összefűzése. Alias a `join`-ra. ```latte -{=[1, 2, 3]|join} {* outputs '123' *} +{=[1, 2, 3]|implode} {* kiírja: '123' *} +{=[1, 2, 3]|implode:'|'} {* kiírja: '1|2|3' *} ``` +Használhatja a `join` aliast is: + +```latte +{=[1, 2, 3]|join} {* kiírja: '123' *} +``` -indent(int level = 1, string char = "\t") .[filter] ---------------------------------------------------- -A szöveg balról történő behúzása egy adott számú tabulátorral vagy más karakterrel, amelyet a második választható argumentumban adunk meg. Az üres sorok nem kerülnek behúzásra. + +indent(int $level=1, string $char="\t") .[filter] +------------------------------------------------- +A szöveget balról behúzza a megadott számú tabulátorral vagy más karakterrel, amelyet a második argumentumban adhatunk meg. Az üres sorok nincsenek behúzva. ```latte
                                                                      @@ -358,7 +382,7 @@ A szöveg balról történő behúzása egy adott számú tabulátorral vagy má
                                                                      ``` -Nyomtatás: +Kiírja: ```latte
                                                                      @@ -367,26 +391,26 @@ Nyomtatás: ``` -last .[filter]{data-version:2.10.2} ------------------------------------ -Visszaadja a tömb utolsó elemét vagy a karakterlánc utolsó karakterét: +last .[filter] +-------------- +Visszaadja a tömb utolsó elemét vagy a string utolsó karakterét: ```latte -{=[1, 2, 3, 4]|last} {* outputs 4 *} -{='abcd'|last} {* outputs 'd' *} +{=[1, 2, 3, 4]|last} {* kiírja: 4 *} +{='abcd'|last} {* kiírja: 'd' *} ``` -Lásd még [first |#first], [random |#random]. +Lásd még [#first], [#random]. length .[filter] ---------------- -Egy karakterlánc vagy tömb hosszát adja vissza. +Visszaadja a string vagy a tömb hosszát. -- a stringek esetében UTF-8 karakterekben adja vissza a hosszúságot. -- tömbök esetén az elemek számát adja vissza. -- a Countable interfészt megvalósító objektumok esetében a count() visszatérési értékét használja. -- az IteratorAggregate interfészt megvalósító objektumok esetében az iterator_count() visszatérési értékét használja. +- stringek esetén visszaadja a hosszt UTF‑8 karakterekben +- tömbök esetén visszaadja az elemek számát +- a `Countable` interfészt implementáló objektumok esetén a `count()` metódus visszatérési értékét használja +- az `IteratorAggregate` interfészt implementáló objektumok esetén az `iterator_count()` függvény visszatérési értékét használja ```latte @@ -396,204 +420,314 @@ Egy karakterlánc vagy tömb hosszát adja vissza. ``` +localDate(?string $format=null, ?string $date=null, ?string $time=null) .[filter] +--------------------------------------------------------------------------------- +Dátumot és időt formáz a [nemzeti beállítások |develop#Locale] szerint, ami biztosítja az időadatok következetes és lokalizált megjelenítését a különböző nyelveken és régiókban. A szűrő elfogadja a dátumot UNIX timestampként, stringként vagy `DateTimeInterface` típusú objektumként. + +```latte +{$date|localDate} {* 2024. április 15. *} +{$date|localDate: format: yM} {* 2024/4 *} +{$date|localDate: date: medium} {* 2024. ápr. 15. *} +``` + +Ha a szűrőt paraméterek nélkül használja, a dátum a `long` szinten kerül kiírásra, lásd alább. + +**a) formátum használata** + +A `format` paraméter leírja, hogy mely időösszetevőket kell megjeleníteni. Ehhez betűkódokat használ, amelyek ismétlődésének száma befolyásolja a kimenet szélességét: + +| év | `y` / `yy` / `yyyy` | `2024` / `24` / `2024` +| hónap | `M` / `MM` / `MMM` / `MMMM` | `8` / `08` / `aug.` / `augusztus` +| nap | `d` / `dd` / `E` / `EEEE` | `1` / `01` / `V` / `vasárnap` +| óra | `j` / `H` / `h` | preferált / 24 órás / 12 órás +| perc | `m` / `mm` | `5` / `05` (2 számjegy másodpercekkel kombinálva) +| másodperc | `s` / `ss` | `8` / `08` (2 számjegy percekkel kombinálva) + +A kódok sorrendje a formátumban nem számít, mert az összetevők sorrendje a nemzeti beállítások szokásai szerint kerül kiírásra. A formátum tehát független tőle. Például a `yyyyMMMMd` formátum `en_US` környezetben `April 15, 2024`-et ír ki, míg `hu_HU` környezetben `2024. április 15.`-öt: + +| locale: | hu_HU | en_US +|--- +| `format: 'dMy'` | 2024. 08. 10. | 8/10/2024 +| `format: 'yM'` | 2024/8 | 8/2024 +| `format: 'yyyyMMMM'` | 2024. augusztus | August 2024 +| `format: 'MMMM'` | augusztus | August +| `format: 'jm'` | 17:22 | 5:22 PM +| `format: 'Hm'` | 17:22 | 17:22 +| `format: 'hm'` | du. 5:22 | 5:22 PM + + +**b) előre beállított stílusok használata** + +A `date` és `time` paraméterek határozzák meg, hogy milyen részletességgel kell a dátumot és az időt kiírni. Több szint közül választhat: `full`, `long`, `medium`, `short`. Kiíratható csak a dátum, csak az idő, vagy mindkettő: + +| locale: | hu_HU | en_US +|--- +| `date: short` | 78. 01. 23. | 1/23/78 +| `date: medium` | 1978. jan. 23. | Jan 23, 1978 +| `date: long` | 1978. január 23. | January 23, 1978 +| `date: full` | 1978. január 23., hétfő | Monday, January 23, 1978 +| `time: short` | 8:30 | 8:30 AM +| `time: medium` | 8:30:59 | 8:30:59 AM +| `time: long` | 8:30:59 CET | 8:30:59 AM GMT+1 +| `date: short, time: short` | 78. 01. 23. 8:30 | 1/23/78, 8:30 AM +| `date: medium, time: short` | 1978. jan. 23. 8:30 | Jan 23, 1978, 8:30 AM +| `date: long, time: short` | 1978. január 23. 8:30 | January 23, 1978 at 8:30 AM + +A dátumnál ezenkívül használható a `relative-` előtag (pl. `relative-short`), amely a jelenhez közeli dátumok esetén `tegnap`, `ma` vagy `holnap` szöveget jelenít meg, egyébként a standard módon írja ki. + +```latte +{$date|localDate: date: relative-short} {* tegnap *} +``` + +Lásd még [#date]. + + lower .[filter] --------------- -Egy értéket kisbetűvé alakít. PHP-bővítményt igényel `mbstring`. +A stringet kisbetűssé alakítja. Szükséges a `mbstring` PHP kiterjesztés. ```latte -{='LATTE'|lower} {* outputs 'latte' *} +{='LATTE'|lower} {* kiírja: 'latte' *} ``` -Lásd még [capitalize |#capitalize], [firstUpper |#firstUpper], [upper |#upper]. +Lásd még [#capitalize], [#firstUpper], [#upper]. nocheck .[filter] ----------------- -Megakadályozza az automatikus URL-szanálást. A Latte [automatikusan ellenőrzi |safety-first#Link checking], hogy a változó tartalmaz-e webes URL-t (azaz HTTP/HTTPS protokollt), és megakadályozza a biztonsági kockázatot jelentő linkek írását. +Megakadályozza az URL cím automatikus tisztítását. A Latte [automatikusan ellenőrzi |safety-first#Linkek ellenőrzése], hogy a változó webes URL-t tartalmaz-e (azaz HTTP/HTTPS protokollt), és megakadályozza az olyan linkek kiírását, amelyek biztonsági kockázatot jelenthetnek. -Ha a hivatkozás más sémát használ, például `javascript:` vagy `data:`, és biztos a tartalmában, akkor a `|nocheck` segítségével kikapcsolhatja az ellenőrzést. +Ha a link más sémát használ, pl. `javascript:` vagy `data:`, és biztos a tartalmában, a `|nocheck` segítségével kikapcsolhatja az ellenőrzést. ```latte {var $link = 'javascript:window.close()'} -checked -unchecked +ellenőrzött +nem ellenőrzött ``` -Nyomtatások: +Kiírja: ```latte -checked -unchecked +ellenőrzött +nem ellenőrzött ``` -Lásd még [checkUrl |#checkUrl]. +Lásd még [#checkUrl]. noescape .[filter] ------------------ -Letiltja az automatikus escapinget. +Letiltja az automatikus escapelést. ```latte -{var $trustedHtmlString = 'hello'} -Escaped: {$trustedHtmlString} -Unescaped: {$trustedHtmlString|noescape} +{var $trustedHtmlString = 'helló'} +Escapelt: {$trustedHtmlString} +Nem escapelt: {$trustedHtmlString|noescape} ``` -Nyomtat: +Kiírja: ```latte -Escaped: <b>hello</b> -Unescaped: hello +Escapelt: <b>helló</b> +Nem escapelt: helló ``` .[warning] -A `noescape` szűrő visszaélése XSS sebezhetőséghez vezethet! Soha ne használja, hacsak nem **teljesen biztos** abban, hogy mit csinál, és hogy a nyomtatott karakterlánc megbízható forrásból származik. +A `noescape` szűrő helytelen használata XSS sebezhetőséghez vezethet! Soha ne használja, ha nem **teljesen biztos** abban, amit csinál, és hogy a kiírt string megbízható forrásból származik. -number(int decimals = 0, string decPoint = '.', string thousandsSep = ',') .[filter] ------------------------------------------------------------------------------------- -Egy számot adott számú tizedesjegyig formáz. Megadhatja a tizedesvessző és az ezres elválasztó karakterét is. +number(int $decimals=0, string $decPoint='.', string $thousandsSep=',') .[filter] +--------------------------------------------------------------------------------- +Számot formáz adott számú tizedesjegyre. Ha a [nemzeti beállítások |develop#Locale] be vannak állítva, a megfelelő tizedes- és ezreselválasztókat használja. ```latte -{1234.20 |number} 1,234 -{1234.20 |number:1} 1,234.2 -{1234.20 |number:2} 1,234.20 -{1234.20 |number:2, ',', ' '} 1 234,20 +{1234.20|number} 1 234 +{1234.20|number:1} 1 234,2 +{1234.20|number:2} 1 234,20 +{1234.20|number:2, ',', ' '} 1 234,20 ``` -padLeft(int length, string pad = ' ') .[filter] +number(string $format) .[filter] +-------------------------------- +A `format` paraméter lehetővé teszi a számok megjelenésének pontos meghatározását az Ön igényei szerint. Ehhez be kell állítani a [nemzeti beállításokat |develop#Locale]. A formátum több speciális karakterből áll, amelyek teljes leírását a "DecimalFormat":https://unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns dokumentációban találja: + +- `0` kötelező számjegy, mindig megjelenik, még akkor is, ha nulla +- `#` opcionális számjegy, csak akkor jelenik meg, ha ezen a helyen valóban van számjegy +- `@` jelentős számjegy, segít a szám megjelenítésében adott számú érvényes számjeggyel +- `.` jelzi, hol legyen a tizedesvessző (vagy pont, országtól függően) +- `,` a számjegycsoportok, leggyakrabban ezresek elválasztására szolgál +- `%` a számot 100-zal szorozza és hozzáadja a százalékjelet + +Nézzünk néhány példát. Az első példában két tizedesjegy kötelező, a másodikban opcionális. A harmadik példa a nullákkal való kiegészítést mutatja balról és jobbról, a negyedik csak a létező számjegyeket jeleníti meg: + +```latte +{1234.5|number: '#,##0.00'} {* 1 234,50 *} +{1234.5|number: '#,##0.##'} {* 1 234,5 *} +{1.23 |number: '000.000'} {* 001,230 *} +{1.2 |number: '##.##'} {* 1,2 *} +``` + +A jelentős számjegyek határozzák meg, hogy hány számjegy jelenjen meg a tizedesvesszőtől függetlenül, kerekítéssel: + +```latte +{1234|number: '@@'} {* 1200 *} +{1234|number: '@@@'} {* 1230 *} +{1234|number: '@@@#'} {* 1234 *} +{1.2345|number: '@@@'} {* 1,23 *} +{0.00123|number: '@@'} {* 0,0012 *} +``` + +Egyszerű módja a szám százalékként való megjelenítésének. A számot 100-zal szorozza és hozzáadja a `%` jelet: + +```latte +{0.1234|number: '#.##%'} {* 12,34% *} +``` + +Definiálhatunk eltérő formátumot a pozitív és negatív számokra, ezeket a `;` jel választja el. Ezzel a módszerrel például beállítható, hogy a pozitív számok `+` jellel jelenjenek meg: + +```latte +{42|number: '#.##;(#.##)'} {* 42 *} +{-42|number: '#.##;(#.##)'} {* (42) *} +{42|number: '+#.##;-#.##'} {* +42 *} +{-42|number: '+#.##;-#.##'} {* -42 *} +``` + +Ne feledje, hogy a számok tényleges megjelenése eltérhet az ország beállításaitól függően. Például néhány országban vesszőt használnak pont helyett tizedesjelként. Ez a szűrő ezt automatikusan figyelembe veszi, és nem kell semmiről gondoskodnia. + + +padLeft(int $length, string $pad=' ') .[filter] ----------------------------------------------- -Egy adott hosszúságú karakterláncot balról egy másik karakterlánccal kitölti. +A stringet balról kiegészíti egy másik stringgel a megadott hosszúságra. ```latte -{='hello'|padLeft: 10, '123'} {* outputs '12312hello' *} +{='helló'|padLeft: 10, '123'} {* kiírja: '12312helló' *} ``` -padRight(int length, string pad = ' ') .[filter] +padRight(int $length, string $pad=' ') .[filter] ------------------------------------------------ -Egy adott hosszúságú karakterláncot egy másik, jobbról jövő karakterlánccal kitölti. +A stringet jobbról kiegészíti egy másik stringgel a megadott hosszúságra. ```latte -{='hello'|padRight: 10, '123'} {* outputs 'hello12312' *} +{='helló'|padRight: 10, '123'} {* kiírja: 'helló12312' *} ``` -query .[filter]{data-version:2.10} ------------------------------------ -Dinamikusan generál egy lekérdezési karakterláncot az URL-ben: +query .[filter] +--------------- +Dinamikusan generál query stringet az URL-ben: ```latte -click -search +kattints +keresés ``` -Nyomtat: +Kiírja: ```latte -click -search +kattints +keresés ``` -A `null` értékű kulcsok nem szerepelnek. +A `null` értékű kulcsok kimaradnak. -Lásd még [escapeUrl |#escapeUrl]. +Lásd még [#escapeUrl]. -random .[filter]{data-version:2.10.2} -------------------------------------- -Visszaadja a tömb véletlenszerű elemét vagy a karakterlánc karakterét: +random .[filter] +---------------- +Visszaadja a tömb véletlen elemét vagy a string véletlen karakterét: ```latte -{=[1, 2, 3, 4]|random} {* example output: 3 *} -{='abcd'|random} {* example output: 'b' *} +{=[1, 2, 3, 4]|random} {* kiírja pl.: 3 *} +{='abcd'|random} {* kiírja pl.: 'b' *} ``` -Lásd még [first |#first], [last |#last]. +Lásd még [#first], [#last]. -repeat(int count) .[filter] ---------------------------- -Megismétli a karakterláncot x-szer. +repeat(int $count) .[filter] +---------------------------- +A stringet x-szer ismétli. ```latte -{='hello'|repeat: 3} {* outputs 'hellohellohello' *} +{='helló'|repeat: 3} {* kiírja: 'hellóhellóhelló' *} ``` -replace(string|array search, string replace = '') .[filter] +replace(string|array $search, string $replace='') .[filter] ----------------------------------------------------------- -A keresett karakterlánc minden előfordulását helyettesítő karakterlánccal helyettesíti. +A keresett string összes előfordulását lecseréli a helyettesítő stringre. ```latte -{='hello world'|replace: 'world', 'friend'} {* outputs 'hello friend' *} +{='helló világ'|replace: 'világ', 'barát'} {* kiírja: 'helló barát' *} ``` -Egyszerre több csere is elvégezhető: .{data-version:2.10.2} +Több cserét is végre lehet hajtani egyszerre: ```latte -{='hello world'|replace: [h => l, l => h]} {* outputs 'lehho worhd' *} +{='helló világ'|replace: [h => l, l => h]} {* kiírja: 'lehho vihág' *} ``` -replaceRE(string pattern, string replace = '') .[filter] +replaceRE(string $pattern, string $replace='') .[filter] -------------------------------------------------------- -Az összes előfordulást helyettesíti a reguláris kifejezésnek megfelelően. +Reguláris kifejezés keresést végez cserével. ```latte -{='hello world'|replaceRE: '/l.*/', 'l'} {* outputs 'hel' *} +{='helló világ'|replaceRE: '/l.*/', 'l'} {* kiírja: 'hel' *} ``` reverse .[filter] ----------------- -Megfordítja a megadott karakterláncot vagy tömböt. +Megfordítja a megadott stringet vagy tömböt. ```latte {var $s = 'Nette'} -{$s|reverse} {* outputs 'etteN' *} +{$s|reverse} {* kiírja: 'etteN' *} {var $a = ['N', 'e', 't', 't', 'e']} -{$a|reverse} {* returns ['e', 't', 't', 'e', 'N'] *} +{$a|reverse} {* visszaadja: ['e', 't', 't', 'e', 'N'] *} ``` -round(int precision = 0) .[filter] ----------------------------------- -Egy számot adott pontosságra kerekít. +round(int $precision=0) .[filter] +--------------------------------- +A számot a megadott pontosságra kerekíti. ```latte -{=3.4|round} {* outputs 3 *} -{=3.5|round} {* outputs 4 *} -{=135.79|round:1} {* outputs 135.8 *} -{=135.79|round:3} {* outputs 135.79 *} +{=3.4|round} {* kiírja: 3 *} +{=3.5|round} {* kiírja: 4 *} +{=135.79|round:1} {* kiírja: 135.8 *} +{=135.79|round:3} {* kiírja: 135.79 *} ``` -Lásd még [ceil |#ceil], [floor |#floor]. +Lásd még [#ceil], [#floor]. -slice(int start, int length = null, bool preserveKeys = false) .[filter]{data-version:2.10.2} ---------------------------------------------------------------------------------------------- -Egy tömb vagy egy karakterlánc egy szeletének kivonása. +slice(int $start, ?int $length=null, bool $preserveKeys=false) .[filter] +------------------------------------------------------------------------ +Kivon egy részt a tömbből vagy stringből. ```latte -{='hello'|slice: 1, 2} {* outputs 'el' *} -{=['a', 'b', 'c']|slice: 1, 2} {* outputs ['b', 'c'] *} +{='helló'|slice: 1, 2} {* kiírja: 'el' *} +{=['a', 'b', 'c']|slice: 1, 2} {* kiírja: ['b', 'c'] *} ``` -A slice-szűrő a `array_slice` PHP-funkcióként működik tömbök esetében, a `mb_substr` pedig karakterláncok esetében, UTF-8 módban a `iconv_substr` -re való visszalépéssel. +A szűrő úgy működik, mint a PHP `array_slice` függvénye tömbökre vagy az `mb_substr` stringekre, fallbackként az `iconv_substr` függvénnyel UTF‑8 módban. -Ha a kezdet nem negatív, akkor a szekvencia a változóban ezzel a kezdőponttal kezdődik. Ha a start negatív, akkor a szekvencia a változó végétől ilyen messze kezdődik. +Ha a start pozitív, a szekvencia ennyivel eltolva kezdődik a tömb/string elejétől. Ha negatív, a szekvencia ennyivel eltolva kezdődik a végétől. -Ha a length értéke pozitív, akkor a szekvencia legfeljebb ennyi elemet tartalmaz. Ha a változó rövidebb, mint a hossz, akkor csak a rendelkezésre álló változóelemek lesznek jelen. Ha a hossz meg van adva és negatív, akkor a sorozat ennyi elemmel a változó vége előtt fog megállni. Ha nem adjuk meg, akkor a szekvencia az eltolódástól a változó végéig mindent tartalmazni fog. +Ha a length paraméter meg van adva és pozitív, a szekvencia ennyi elemet fog tartalmazni. Ha negatív length paramétert adunk át ennek a függvénynek, a szekvencia az eredeti tömb összes elemét tartalmazza, a start pozíciótól kezdve és a tömb végétől számított length elemmel korábban végződve. Ha ezt a paramétert nem adja meg, a szekvencia az eredeti tömb összes elemét tartalmazza, a start pozíciótól kezdve. -A Filter alapértelmezés szerint átrendezi és visszaállítja az integer tömb kulcsát. Ez a viselkedés megváltoztatható a preserveKeys true értékre állításával. A string kulcsok ettől a paramétertől függetlenül mindig megmaradnak. +Alapértelmezés szerint a szűrő megváltoztatja a sorrendet és visszaállítja a tömb egész számú kulcsait. Ez a viselkedés megváltoztatható a `preserveKeys` `true`-ra állításával. A string kulcsok mindig megmaradnak, ettől a paramétertől függetlenül. -sort .[filter]{data-version:2.9} ---------------------------------- -Szűrő, amely egy tömböt rendezi és fenntartja az index hozzárendelést. +sort(?Closure $comparison, string|int|\Closure|null $by=null, string|int|\Closure|bool $byKey=false) .[filter] +-------------------------------------------------------------------------------------------------------------- +A szűrő rendezi a tömb vagy iterátor elemeit, és megőrzi azok asszociatív kulcsait. Ha a [nemzeti beállítások |develop#Locale] be vannak állítva, a rendezés annak szabályai szerint történik, hacsak nincs megadva saját összehasonlító függvény. ```latte {foreach ($names|sort) as $name} @@ -601,7 +735,7 @@ Szűrő, amely egy tömböt rendezi és fenntartja az index hozzárendelést. {/foreach} ``` -A tömb fordított sorrendbe rendezve. +Rendezett tömb fordított sorrendben: ```latte {foreach ($names|sort|reverse) as $name} @@ -609,105 +743,131 @@ A tömb fordított sorrendbe rendezve. {/foreach} ``` -Saját összehasonlító függvényt adhat át paraméterként: .{data-version:2.10.2} +Megadhat saját összehasonlító függvényt a rendezéshez (a példa megmutatja, hogyan lehet fordítani a rendezést a legnagyobbtól a legkisebbig): ```latte -{var $sorted = ($names|sort: fn($a, $b) => $b <=> $a)} +{var $reverted = ($names|sort: fn($a, $b) => $b <=> $a)} ``` +A `|sort` szűrő lehetővé teszi az elemek kulcsok szerinti rendezését is: -spaceless .[filter]{data-version:2.10.2} ------------------------------------------ -Eltávolítja a felesleges szóközöket a kimenetből. Használhatod a `strip` aliast is. +```latte +{foreach ($names|sort: byKey: true) as $name} + ... +{/foreach} +``` + +Ha egy táblázatot egy adott oszlop szerint kell rendeznie, használhatja a `by` paramétert. A `'name'` érték a példában azt jelzi, hogy a rendezés `$item->name` vagy `$item['name']` szerint történik, attól függően, hogy `$item` tömb vagy objektum: + +```latte +{foreach ($items|sort: by: 'name') as $item} + {$item->name} +{/foreach} +``` + +Definiálhat egy callback függvényt is, amely meghatározza az értéket, amely szerint rendezni kell: + +```latte +{foreach ($items|sort: by: fn($items) => $items->category->name) as $item} + {$item->name} +{/foreach} +``` + +Ugyanígy használható a `byKey` paraméter is. + + +spaceless .[filter] +------------------- +Eltávolítja a felesleges szóközöket a kimenetből. Használhatja a `strip` aliast is. ```latte {block |spaceless}
                                                                        -
                                                                      • Hello
                                                                      • +
                                                                      • Helló
                                                                      {/block} ``` -Nyomtat: +Kiírja: ```latte -
                                                                      • Hello
                                                                      +
                                                                      • Helló
                                                                      ``` stripHtml .[filter] ------------------- -A HTML-t egyszerű szöveggé alakítja. Vagyis eltávolítja a HTML-címkéket, és a HTML-egységeket szöveggé alakítja. +A HTML-t tiszta szöveggé alakítja. Tehát eltávolítja belőle a HTML tageket és a HTML entitásokat szöveggé alakítja. ```latte -{='

                                                                      one < two

                                                                      '|stripHtml} {* outputs 'one < two' *} +{='

                                                                      egy < kettő

                                                                      '|stripHtml} {* kiírja: 'egy < kettő' *} ``` -Az így kapott sima szöveg természetesen tartalmazhat olyan karaktereket, amelyek HTML-címkéket képviselnek, például a `'<p>'|stripHtml` átváltozik `

                                                                      `. Soha ne adja ki az eredményül kapott szöveget a `|noescape` címmel, mivel ez biztonsági réshez vezethet. +Az eredményül kapott tiszta szöveg természetesen tartalmazhat olyan karaktereket, amelyek HTML tageket képviselnek, például a `'<p>'|stripHtml` `

                                                                      `-vé alakul. Semmi esetre se írja ki az így keletkezett szöveget `|noescape`-pel, mert ez biztonsági rés kialakulásához vezethet. -substr(int offset, int length = null) .[filter] ------------------------------------------------ -Kivonja egy karakterlánc egy szeletét. Ezt a szűrőt felváltotta a [slice |#slice] szűrő. +substr(int $offset, ?int $length=null) .[filter] +------------------------------------------------ +Kivon egy részt a stringből. Ezt a szűrőt felváltotta a [#slice] szűrő. ```latte {$string|substr: 1, 2} ``` -translate(string message, ...args) .[filter]{data-version:3.0} --------------------------------------------------------------- -Kifejezéseket fordít le más nyelvekre. Ahhoz, hogy a szűrő elérhető legyen, be kell [állítania a fordítót |develop#TranslatorExtension]. A [címkéket |tags#Translation] is használhatja [a fordításhoz |tags#Translation]. +translate(...$args) .[filter] +----------------------------- +Kifejezéseket fordít más nyelvekre. Ahhoz, hogy a szűrő elérhető legyen, [be kell állítani a fordítót |develop#TranslatorExtension]. Használhatja a [fordítási tageket |tags#Fordítások] is. ```latte -{='Baskter'|translate} +{='Kosár'|translate} {$item|translate} ``` -trim(string charlist = " \t\n\r\0\x0B\u{A0}") .[filter] -------------------------------------------------------- -Vezető és követő karakterek eltávolítása, alapértelmezés szerint szóköz. +trim(string $charlist=" \t\n\r\0\x0B\u{A0}") .[filter] +------------------------------------------------------ +Eltávolítja a szóközöket (vagy más karaktereket) a string elejéről és végéről. ```latte -{=' I like Latte. '|trim} {* outputs 'I like Latte.' *} -{=' I like Latte.'|trim: '.'} {* outputs ' I like Latte' *} +{=' Szeretem a Latte-t. '|trim} {* kiírja: 'Szeretem a Latte-t.' *} +{=' Szeretem a Latte-t.'|trim: '.'} {* kiírja: ' Szeretem a Latte-t' *} ``` -truncate(int length, string append = '…') .[filter] +truncate(int $length, string $append='…') .[filter] --------------------------------------------------- -Rövidíti a karakterláncot a megadott maximális hosszúságra, de megpróbálja megőrizni az egész szavakat. Ha a karakterlánc csonkolva van, ellipszist ad a végére (ez a második paraméterrel módosítható). +Levágja a stringet a megadott maximális hosszúságra, miközben megpróbálja megőrizni az egész szavakat. Ha a string rövidül, a végére három pontot tesz (ez a második paraméterrel megváltoztatható). ```latte -{var $title = 'Hello, how are you?'} +{var $title = 'Helló, hogy vagy?'} {$title|truncate:5} {* Hell… *} -{$title|truncate:17} {* Hello, how are… *} -{$title|truncate:30} {* Hello, how are you? *} +{$title|truncate:17} {* Helló, hogy va… *} +{$title|truncate:30} {* Helló, hogy vagy? *} ``` upper .[filter] --------------- -Egy értéket nagybetűvé alakít. A `mbstring` PHP-bővítményt igényli. +A stringet nagybetűssé alakítja. Szükséges a `mbstring` PHP kiterjesztés. ```latte -{='latte'|upper} {* outputs 'LATTE' *} +{='latte'|upper} {* kiírja: 'LATTE' *} ``` -Lásd még [capitalize |#capitalize], [firstUpper |#firstUpper], [lower |#lower]. +Lásd még [#capitalize], [#firstUpper], [#lower]. webalize .[filter] ------------------ -Átalakítja ASCII-re. +Az UTF‑8 stringet URL-ben használt formára alakítja. -A szóközöket kötőjelekké alakítja. Eltávolítja a nem alfanumerikus karaktereket, aláhúzásokat vagy kötőjeleket. Átalakítja kisbetűvé. Eltávolítja a vezető és az utolsó szóközöket is. +ASCII-ra konvertálja. A szóközöket kötőjelekre cseréli. Eltávolítja azokat a karaktereket, amelyek nem alfanumerikusak, aláhúzások vagy kötőjelek. Kisbetűssé alakítja. Eltávolítja az elülső és hátsó szóközöket is. ```latte -{var $s = 'Our 10. product'} -{$s|webalize} {* outputs 'our-10-product' *} +{var $s = 'A mi 10. termékünk'} +{$s|webalize} {* kiírja: 'a-mi-10-termekunk' *} ``` .[caution] -Szükséges a [nette/utils |utils:] csomag. +Szükséges a [nette/utils|utils:] könyvtár. diff --git a/latte/hu/functions.texy b/latte/hu/functions.texy index 52d05ef6eb..32b630d673 100644 --- a/latte/hu/functions.texy +++ b/latte/hu/functions.texy @@ -1,23 +1,25 @@ -Latte funkciók -************** +Latte függvények +**************** .[perex] -Az általános PHP-funkciók mellett ezeket is használhatod a sablonokban. +A sablonokban a szokásos PHP függvényeken kívül ezeket a továbbiakat is használhatjuk. .[table-latte-filters] -| `clamp` | [az értéket a tartományba szorítja |#clamp]. -| `divisibleBy`| [ellenőrzi, hogy egy változó osztható-e egy számmal |#divisibleBy] -| `even` | [ellenőrzi, hogy a megadott szám páros-e |#even] -| `first` | [visszaadja a tömb első elemét vagy a karakterlánc első karakterét |#first]. -| `last` | [visszaadja a tömb utolsó elemét vagy a karakterlánc utolsó karakterét |#last]. -| `odd` | [ellenőrzi, hogy az adott szám páratlan-e |#odd]. -| `slice` | [kivonja egy tömb vagy egy karakterlánc egy szeletét |#slice]. +| `clamp` | [értéket korlátoz adott tartományba |#clamp] +| `divisibleBy`| [ellenőrzi, hogy a változó osztható-e egy számmal |#divisibleBy] +| `even` | [ellenőrzi, hogy a megadott szám páros-e |#even] +| `first` | [visszaadja a tömb első elemét vagy a string első karakterét |#first] +| `group` | [adatokat csoportosít különböző kritériumok szerint |#group] +| `hasBlock` | [megállapítja egy blokk létezését |#hasBlock] +| `last` | [visszaadja a tömb utolsó elemét vagy a string utolsó karakterét |#last] +| `odd` | [ellenőrzi, hogy a megadott szám páratlan-e |#odd] +| `slice` | [kivon egy részt a tömbből vagy stringből |#slice] -Használat .[#toc-usage] -======================= +Használat +========= -A függvények ugyanúgy használhatók, mint az általános PHP függvények, és minden kifejezésben használhatók: +A függvényeket ugyanúgy használjuk, mint a szokásos PHP függvényeket, és minden kifejezésben használhatók: ```latte

                                                                      {clamp($num, 1, 100)}

                                                                      @@ -25,14 +27,14 @@ A függvények ugyanúgy használhatók, mint az általános PHP függvények, {if odd($num)} ... {/if} ``` -[Egyéni függvények |extending-latte#functions] így regisztrálhatók: +[Egyéni függvényeket|custom-functions] így lehet regisztrálni: ```php $latte = new Latte\Engine; $latte->addFunction('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -Egy sablonban így használjuk: +A sablonban pedig így hívjuk meg: ```latte

                                                                      {shortify($text)}

                                                                      @@ -40,65 +42,95 @@ Egy sablonban így használjuk: ``` -Funkciók .[#toc-functions] -========================== +Függvények +========== -clamp(int|float $value, int|float $min, int|float $max): int|float .[method]{data-version:2.9} ----------------------------------------------------------------------------------------------- -Visszaadja a min és max tartományba szorított értéket. +clamp(int|float $value, int|float $min, int|float $max): int|float .[method] +---------------------------------------------------------------------------- +Az értéket a megadott inkluzív min és max tartományba korlátozza. ```latte {=clamp($level, 0, 255)} ``` -Lásd még [szűrőbilincs |filters#clamp]: +Lásd még a [clamp szűrőt |filters#clamp]. -divisibleBy(int $value, int $by): bool .[method]{data-version:2.10.2} ---------------------------------------------------------------------- -Ellenőrzi, hogy egy változó osztható-e egy számmal. +divisibleBy(int $value, int $by): bool .[method] +------------------------------------------------ +Ellenőrzi, hogy a változó osztható-e egy számmal. ```latte {if divisibleBy($num, 5)} ... {/if} ``` -even(int $value): bool .[method]{data-version:2.10.2} ------------------------------------------------------ -Ellenőrzi, hogy az adott szám páros-e. +even(int $value): bool .[method] +-------------------------------- +Ellenőrzi, hogy a megadott szám páros-e. ```latte {if even($num)} ... {/if} ``` -first(string|array $value): mixed .[method]{data-version:2.10.2} ----------------------------------------------------------------- -Visszaadja a tömb első elemét vagy a karakterlánc első karakterét: +first(string|iterable $value): mixed .[method] +---------------------------------------------- +Visszaadja a tömb első elemét vagy a string első karakterét: ```latte -{=first([1, 2, 3, 4])} {* kimenet 1 *} -{=first('abcd')} {* kimenet 'a' *} +{=first([1, 2, 3, 4])} {* kiírja: 1 *} +{=first('abcd')} {* kiírja: 'a' *} ``` -Lásd még [last |#last], [filter first |filters#first]. +Lásd még [#last], a [first szűrőt |filters#first]. -last(string|array $value): mixed .[method]{data-version:2.10.2} ---------------------------------------------------------------- -A tömb utolsó elemét vagy a karakterlánc utolsó karakterét adja vissza: +group(iterable $data, string|int|\Closure $by): array .[method]{data-version:3.0.16} +------------------------------------------------------------------------------------ +A függvény csoportosítja az adatokat különböző kritériumok szerint. + +Ebben a példában a táblázat sorai a `categoryId` oszlop szerint vannak csoportosítva. A kimenet egy tömbökből álló tömb, ahol a kulcs a `categoryId` oszlop értéke. [Olvassa el a részletes útmutatót|cookbook/grouping]. + +```latte +{foreach group($items, categoryId) as $categoryId => $categoryItems} +
                                                                        + {foreach $categoryItems as $item} +
                                                                      • {$item->name}
                                                                      • + {/foreach} +
                                                                      +{/foreach} +``` + +Lásd még a [group szűrőt |filters#group]. + + +hasBlock(string $name): bool .[method]{data-version:3.0.10} +----------------------------------------------------------- +Megállapítja, hogy a megadott nevű blokk létezik-e: + +```latte +{if hasBlock(header)} ... {/if} +``` + +Lásd még a [blokkok létezésének ellenőrzését |template-inheritance#Blokkok létezésének ellenőrzése ifset]. + + +last(string|array $value): mixed .[method] +------------------------------------------ +Visszaadja a tömb utolsó elemét vagy a string utolsó karakterét: ```latte -{=last([1, 2, 3, 4])} {* 4 kimenet *} -{=last('abcd')} {* kimenet 'd' *} +{=last([1, 2, 3, 4])} {* kiírja: 4 *} +{=last('abcd')} {* kiírja: 'd' *} ``` -Lásd még [first |#first], [filter last |filters#last]. +Lásd még [#first], a [last szűrőt |filters#last]. -odd(int $value): bool .[method]{data-version:2.10.2} ----------------------------------------------------- +odd(int $value): bool .[method] +------------------------------- Ellenőrzi, hogy a megadott szám páratlan-e. ```latte @@ -106,19 +138,19 @@ Ellenőrzi, hogy a megadott szám páratlan-e. ``` -slice(string|array $value, int $start, int $length=null, bool $preserveKeys=false): string|array .[method]{data-version:2.10.2} -------------------------------------------------------------------------------------------------------------------------------- -Kivonja egy tömb vagy egy karakterlánc egy szeletét. +slice(string|array $value, int $start, ?int $length=null, bool $preserveKeys=false): string|array .[method] +----------------------------------------------------------------------------------------------------------- +Kivon egy részt a tömbből vagy stringből. ```latte -{=slice('hello', 1, 2)} {* kimenet 'el' *} -{=slice(['a', 'b', 'c'], 1, 2)} {* kimenet ['b', 'c'] *} +{=slice('helló', 1, 2)} {* kiírja: 'el' *} +{=slice(['a', 'b', 'c'], 1, 2)} {* kiírja: ['b', 'c'] *} ``` -A slice-szűrő a `array_slice` PHP-funkcióként működik tömbök esetében, a `mb_substr` pedig karakterláncok esetében, UTF-8 módban pedig a `iconv_substr` funkcióra való visszalépéssel. +A függvény úgy működik, mint a PHP `array_slice` függvénye tömbökre vagy az `mb_substr` stringekre, fallbackként az `iconv_substr` függvénnyel UTF‑8 módban. -Ha a kezdet nem negatív, akkor a szekvencia a változóban ezzel a kezdőponttal kezdődik. Ha a start negatív, akkor a szekvencia a változó végétől ilyen messze kezdődik. +Ha a start pozitív, a szekvencia ennyivel eltolva kezdődik a tömb/string elejétől. Ha negatív, a szekvencia ennyivel eltolva kezdődik a végétől. -Ha a length értéke pozitív, akkor a szekvencia legfeljebb ennyi elemet tartalmaz. Ha a változó rövidebb, mint a hossz, akkor csak a rendelkezésre álló változóelemek lesznek jelen. Ha a hossz meg van adva és negatív, akkor a sorozat ennyi elemmel a változó vége előtt fog megállni. Ha nem adjuk meg, akkor a szekvencia az eltolódástól a változó végéig mindent tartalmazni fog. +Ha a length paraméter meg van adva és pozitív, a szekvencia ennyi elemet fog tartalmazni. Ha negatív length paramétert adunk át ennek a függvénynek, a szekvencia az eredeti tömb összes elemét tartalmazza, a start pozíciótól kezdve és a tömb végétől számított length elemmel korábban végződve. Ha ezt a paramétert nem adja meg, a szekvencia az eredeti tömb összes elemét tartalmazza, a start pozíciótól kezdve. -A Filter alapértelmezés szerint átrendezi és visszaállítja az integer tömb kulcsát. Ez a viselkedés megváltoztatható a preserveKeys true értékre állításával. A string kulcsok ettől a paramétertől függetlenül mindig megmaradnak. +Alapértelmezés szerint a függvény megváltoztatja a sorrendet és visszaállítja a tömb egész számú kulcsait. Ez a viselkedés megváltoztatható a `preserveKeys` `true`-ra állításával. A string kulcsok mindig megmaradnak, ettől a paramétertől függetlenül. diff --git a/latte/hu/guide.texy b/latte/hu/guide.texy index 46302b8b28..611eab692d 100644 --- a/latte/hu/guide.texy +++ b/latte/hu/guide.texy @@ -1,46 +1,45 @@ -Kezdő lépések a Latte-val -************************* +Első lépések a Latte-val +************************
                                                                      -A sablonok javítják a kódszervezést, elválasztják az alkalmazás logikáját a megjelenítéstől, és növelik a biztonságot. A HTML generálásához sokkal jobb funkciókat és kifejező képességeket kínálnak, mint maga a PHP. +A sablonok javítják a kód szervezettségét, elválasztják az alkalmazás logikáját a prezentációtól és növelik a biztonságot. Sokkal jobb funkciókat és kifejezőeszközöket kínálnak a HTML generálásához, mint maga a PHP. -A Latte a legbiztonságosabb sablonkészítő rendszer a PHP számára. Imádni fogja intuitív szintaxisát. A hasznos funkciók széles skálája jelentősen leegyszerűsíti a munkáját. -Kiváló védelmet nyújt a [kritikus sebezhetőségekkel |safety-first] szemben, és lehetővé teszi, hogy a magas színvonalú alkalmazások létrehozására összpontosítson anélkül, hogy azok biztonsága miatt aggódnia kellene. +A Latte a legbiztonságosabb sablonrendszer PHP-hoz. Imádni fogja intuitív szintaxisát. A hasznos funkciók széles skálája jelentősen megkönnyíti a munkáját. Csúcsminőségű védelmet nyújt a [kritikus sebezhetőségekkel|safety-first] szemben, és lehetővé teszi, hogy a minőségi alkalmazások létrehozására összpontosítson anélkül, hogy aggódnia kellene azok biztonsága miatt. -Hogyan írhat sablonokat a Latte használatával? .[#toc-how-to-write-templates-using-latte] ------------------------------------------------------------------------------------------ +Hogyan írjunk sablonokat Latte segítségével? +-------------------------------------------- -A Latte okosan megtervezett és könnyen megtanulható a PHP-t ismerők számára, mivel gyorsan elsajátíthatják az alapvető címkéket. +A Latte okosan van megtervezve, és könnyen megtanulható azok számára, akik ismerik a PHP-t és elsajátítják az alapvető tageket. -- Először ismerkedjen meg a [Latte szintaxisával |syntax], és [próbálja ki az egészet online |https://fiddle.nette.org/latte/] -- Nézze meg a [címkék |tags] és [szűrők |filters]alapkészletét -- Írjon sablonokat a [Latte-támogatással rendelkező szerkesztőben |recipes#Editors and IDE] +- Először ismerkedjen meg a [Latte szintaxisával|syntax] és [PRÓBÁLJA KI ONLINE |https://fiddle.nette.org/latte/#9cc0cf6d89] +- Tekintse meg az alapvető [tagek|tags] és [szűrők|filters] készletét +- Írjon sablonokat [Latte támogatással rendelkező szerkesztőben |recipes#Szerkesztők és IDE-k] -Hogyan használjuk a Latte-t PHP-ben? .[#toc-how-to-use-latte-in-php] --------------------------------------------------------------------- +Hogyan használjuk a Latte-t PHP-ban? +------------------------------------ -A Latte implementálása új alkalmazásába percek kérdése: +A Latte bevezetése az új alkalmazásába néhány perc kérdése: -- Először is, [telepítse és futtassa a Latte |develop#Installation] -- Kényeztesse magát a [Tracy hibakereső eszközzel |develop#Debugging and Tracy] -- Bővítse a Latte-ot [egyéni funkciókkal |extending-latte] +- Először [Telepítse és futtassa a Latte-t |develop#Telepítés] +- Kényeztesse magát a [Tracy hibakereső eszközzel |develop#Debuggolás és Tracy] +- Bővítse a Latte-t [egyéni funkcionalitással |extending-latte] -Ha egy régi, egyszerű PHP nyelven írt projektet konvertál Latte-ra, a [PHP kód Latte-ra való konvertálására szolgáló eszköz |cookbook/migration-from-php] megkönnyíti az áttérést. Vagy azt tervezi, hogy a Twigről áttér a Latte-ra? Van egy [Twig sablon konvertáló Latte-ra |cookbook/migration-from-twig] az Ön számára. +Ha egy régi, tiszta PHP-ban írt projektet konvertál Latte-ra, a migrációt megkönnyíti az [eszköz a PHP kód Latte-ba konvertálásához |cookbook/migration-from-php]. Vagy éppen Twig-ről készül átállni Latte-ra? Van számunkra [Twig sablonok Latte-ba konvertálója |cookbook/migration-from-twig]. -Mit tud még a Latte? .[#toc-what-else-can-latte-do] ---------------------------------------------------- +Mit tud még a Latte? +-------------------- -A Latte teljesen felszerelt, minden lényeges dologgal együtt. +A Latte teljes felszereléssel érkezik, minden fontos dologgal az alapokban. -- Termelékenységét az [öröklési mechanizmusok |template-inheritance] növelik, amelyek ismétlődő elemeket és struktúrákat használnak újra. -- A [Sandbox |Sandbox] páncélbunker elszigeteli a sablonokat a nem megbízható forrásoktól, például a felhasználók által saját maguk által szerkesztettektől -- További inspirációként itt talál [tippeket és trükköket |recipes] +- Termelékenységét felturbózzák az [öröklődési mechanizmusok |template-inheritance], amelyeknek köszönhetően az ismétlődő elemek és struktúrák újra felhasználhatók +- A páncélozott bunker [sandbox] izolálja a nem megbízható forrásokból származó sablonokat, amelyeket például maguk a felhasználók szerkesztenek +- További inspirációért itt vannak a [tippek és trükkök |recipes]
                                                                      -{{description: A Latte a legbiztonságosabb templating rendszer a PHP számára. Számos biztonsági rést megelőz. Értékelni fogod az intuitív szintaxisát, és értékelni fogod a sok hasznos finomítást.}} +{{description: A Latte a legbiztonságosabb sablonrendszer PHP-hoz. Megakadályozza számos biztonsági sebezhetőséget. Értékelni fogja intuitív szintaxisát és a sok hasznos funkciót.}} diff --git a/latte/hu/loaders.texy b/latte/hu/loaders.texy new file mode 100644 index 0000000000..7676302d96 --- /dev/null +++ b/latte/hu/loaders.texy @@ -0,0 +1,198 @@ +Loaderek +******** + +.[perex] +A loaderek az a mechanizmus, amelyet a Latte használ a sablonok forráskódjának lekérésére. Leggyakrabban a sablonok fájlokként vannak tárolva a lemezen, de a rugalmas loader rendszernek köszönhetően gyakorlatilag bárhonnan betöltheti őket, vagy akár dinamikusan is generálhatja. + + +Mi az a Loader? +=============== + +Amikor sablonokkal dolgozik, általában a projekt könyvtárstruktúrájában elhelyezett `.latte` fájlokra gondol. Erről gondoskodik az alapértelmezett [#FileLoader] a Latte-ban. Azonban a sablon neve (mint `'main.latte'` vagy `'components/card.latte'`) és a tényleges forráskódja közötti kapcsolat *nem feltétlenül* közvetlen leképezés egy fájlútvonalra. + +Itt jönnek képbe a loaderek. A loader egy objektum, amelynek feladata, hogy vegyen egy sablonnevet (egy azonosító stringet), és biztosítsa a Latte számára annak forráskódját. A Latte ebben a feladatban teljes mértékben a konfigurált loaderre támaszkodik. Ez nemcsak a `$latte->render('main.latte')`-val kért kezdeti sablonra vonatkozik, hanem **minden belülről hivatkozott sablonra** is, olyan tagekkel, mint `{include ...}`, `{layout ...}`, `{embed ...}` vagy `{import ...}`. + +Miért használna egyéni loadert? + +- **Betöltés alternatív forrásokból:** Sablonok lekérése adatbázisból, gyorsítótárból (mint Redis vagy Memcached), verziókezelő rendszerből (mint Git, egy konkrét commit alapján) vagy dinamikusan generáltak. +- **Egyéni elnevezési konvenciók implementálása:** Lehet, hogy rövidebb aliasokat szeretne használni a sablonokhoz, vagy egy specifikus keresési útvonal logikát implementálna (pl. először a téma könyvtárában keres, majd visszatér az alapértelmezett könyvtárhoz). +- **Biztonság vagy hozzáférés-vezérlés hozzáadása:** Egy egyéni loader ellenőrizheti a felhasználói jogosultságokat bizonyos sablonok betöltése előtt. +- **Előfeldolgozás:** Bár általában nem ajánlott ([fordítási menetek |compiler-passes] jobbak), egy loader *elméletileg* előfeldolgozhatná a sablon tartalmát, mielőtt átadná a Latte-nak. + +A `Latte\Engine` példányhoz tartozó loadert a `setLoader()` metódussal állíthatja be: + +```php +$latte = new Latte\Engine; + +// Az alapértelmezett FileLoader használata a '/path/to/templates' fájlokhoz +$loader = new Latte\Loaders\FileLoader('/path/to/templates'); +$latte->setLoader($loader); +``` + +A loadernek implementálnia kell a `Latte\Loader` interfészt. + + +Beépített Loaderek +================== + +A Latte több standard loadert kínál: + + +FileLoader +---------- + +Ez az **alapértelmezett loader**, amelyet a `Latte\Engine` osztály használ, ha nincs más megadva. Közvetlenül a fájlrendszerből tölti be a sablonokat. + +Opcionálisan beállíthat egy gyökérkönyvtárat a hozzáférés korlátozására: + +```php +use Latte\Loaders\FileLoader; + +// A következő csak a /var/www/html/templates könyvtárból engedélyezi a sablonok betöltését +$loader = new FileLoader('/var/www/html/templates'); +$latte->setLoader($loader); + +// $latte->render('../../../etc/passwd'); // Ez kivételt dobna + +// A /var/www/html/templates/pages/contact.latte helyen található sablon renderelése +$latte->render('pages/contact.latte'); +``` + +Az olyan tagek használatakor, mint `{include}` vagy `{layout}`, a sablonneveket relatívan oldja fel az aktuális sablonhoz képest, hacsak nincs abszolút útvonal megadva. + + +StringLoader +------------ + +Ez a loader a sablon tartalmát egy asszociatív tömbből szerzi be, ahol a kulcsok a sablonnevek (azonosítók), az értékek pedig a sablon forráskód stringjei. Különösen hasznos teszteléshez vagy kis alkalmazásokhoz, ahol a sablonok közvetlenül a PHP kódban tárolhatók. + +```php +use Latte\Loaders\StringLoader; + +$loader = new StringLoader([ + 'main.latte' => 'Helló {$name}, az include lentebb van:{include helper.latte}', + 'helper.latte' => '{var $x = 10}Beillesztett tartalom: {$x}', + // Adjon hozzá további sablonokat szükség szerint +]); + +$latte->setLoader($loader); + +$latte->render('main.latte', ['name' => 'Világ']); +// Kimenet: Helló Világ, az include lentebb van:Beillesztett tartalom: 10 +``` + +Ha csak egyetlen sablont kell közvetlenül egy stringből renderelnie, anélkül, hogy beágyazásra vagy öröklődésre lenne szüksége, amely más névvel ellátott string sablonokra hivatkozik, átadhatja a stringet közvetlenül a `render()` vagy `renderToString()` metódusnak, amikor a `StringLoader`-t tömb nélkül használja: + +```php +$loader = new StringLoader; +$latte->setLoader($loader); + +$templateString = 'Helló {$name}!'; +$output = $latte->renderToString($templateString, ['name' => 'Alice']); +// $output tartalmazza: 'Helló Alice!' +``` + + +Egyéni Loader létrehozása +========================= + +Egyéni loader létrehozásához (pl. sablonok betöltése adatbázisból, gyorsítótárból, verziókezelő rendszerből vagy más forrásból) létre kell hoznia egy osztályt, amely implementálja a [api:Latte\Loader] interfészt. + +Nézzük meg, mit kell tennie minden metódusnak. + + +getContent(string $name): string .[method] +------------------------------------------ +Ez a loader alapvető metódusa. Feladata a `$name` által azonosított sablon teljes forráskódjának lekérése és visszaadása (ahogy a `$latte->render()` metódusnak átadták, vagy a [#getReferredName()] metódus visszaadta). + +Ha a sablont nem lehet megtalálni vagy hozzáférni, ez a metódus **`Latte\RuntimeException` kivételt kell, hogy dobjon**. + +```php +public function getContent(string $name): string +{ + // Példa: Betöltés egy hipotetikus belső tárolóból + $content = $this->storage->read($name); + if ($content === null) { + throw new Latte\RuntimeException("A(z) '$name' sablon nem tölthető be."); + } + return $content; +} +``` + + +getReferredName(string $name, string $referringName): string .[method] +---------------------------------------------------------------------- +Ez a metódus kezeli a sablonnevek feloldását, amelyeket olyan tagekben használnak, mint `{include}`, `{layout}`, stb. Amikor a Latte például találkozik egy `{include 'partial.latte'}` taggel a `main.latte`-n belül, meghívja ezt a metódust `$name = 'partial.latte'` és `$referringName = 'main.latte'` értékekkel. + +A metódus feladata a `$name` lefordítása egy kanonikus azonosítóra (pl. abszolút útvonal, egyedi adatbázis kulcs), amelyet a loader további metódusainak hívásakor használnak, a `$referringName`-ben megadott kontextus alapján. + +```php +public function getReferredName(string $name, string $referringName): string +{ + return ...; +} +``` + + +getUniqueId(string $name): string .[method] +------------------------------------------- +A Latte a teljesítmény javítása érdekében a fordított sablonok gyorsítótárát használja. Minden fordított sablonfájlnak egyedi névre van szüksége, amely a forrássablon azonosítójából származik. Ez a metódus egy stringet biztosít, amely **egyértelműen azonosítja** a `$name` sablont. + +Fájl alapú sablonok esetén az abszolút útvonal szolgálhat erre. Adatbázisban lévő sablonok esetén gyakori egy előtag és az adatbázis ID kombinációja. + +```php +public function getUniqueId(string $name): string +{ + return ...; +} +``` + + +Példa: Egyszerű adatbázis Loader +-------------------------------- + +Ez a példa egy olyan loader alapvető struktúráját mutatja be, amely egy `templates` nevű adatbázis táblában tárolt sablonokat tölt be, `name` (egyedi azonosító), `content` és `updated_at` oszlopokkal. + +```php +use Latte; + +class DatabaseLoader implements Latte\Loader +{ + public function __construct( + private \PDO $db, + ) { + } + + public function getContent(string $name): string + { + $stmt = $this->db->prepare('SELECT content FROM templates WHERE name = ?'); + $stmt->execute([$name]); + $content = $stmt->fetchColumn(); + if ($content === false) { + throw new Latte\RuntimeException("A(z) '$name' sablon nem található az adatbázisban."); + } + return $content; + } + + // Ez az egyszerű példa feltételezi, hogy a sablonnevek ('homepage', 'article', stb.) + // egyedi ID-k, és a sablonok nem hivatkoznak egymásra relatívan. + public function getReferredName(string $name, string $referringName): string + { + return $name; + } + + public function getUniqueId(string $name): string + { + // Az előtag és maga a név használata itt egyedi és elegendő + return 'db_' . $name; + } +} + +// Használat: +$pdo = new \PDO(/* csatlakozási részletek */); +$loader = new DatabaseLoader($pdo); +$latte->setLoader($loader); +$latte->render('homepage'); // Betölti a 'homepage' nevű sablont az DB-ből +``` + +Az egyéni loaderek teljes kontrollt adnak Önnek afölött, honnan származnak a Latte sablonjai, lehetővé téve az integrációt különböző tárolórendszerekkel és munkafolyamatokkal. diff --git a/latte/hu/recipes.texy b/latte/hu/recipes.texy index 099db80aa9..dc4f8f03df 100644 --- a/latte/hu/recipes.texy +++ b/latte/hu/recipes.texy @@ -2,44 +2,44 @@ Tippek és trükkök ***************** -Szerkesztők és IDE .[#toc-editors-and-ide] -========================================== +Szerkesztők és IDE-k +==================== -Írjon sablonokat olyan szerkesztőprogramban vagy IDE-ben, amely támogatja a Latte-t. Sokkal kellemesebb lesz. +Írjon sablonokat olyan szerkesztőben vagy IDE-ben, amely támogatja a Latte-t. Sokkal kellemesebb lesz. -- A NetBeans IDE beépített támogatással rendelkezik -- PhpStorm: telepítse a [Latte bővítményt |https://plugins.jetbrains.com/plugin/7457-latte] a `Settings > Plugins > Marketplace` -- VS Code: keresse meg a markerplace-t a "Nette Latte + Neon" pluginnal. -- Sublime Text 3: a Package Controlban keresse meg és telepítse a `Nette` csomagot és válassza ki a Latte-t a `View > Syntax` -- a régi szerkesztőkben használja a Smarty kiemelést a .latte fájlokra +- PhpStorm: telepítse a `Settings > Plugins > Marketplace` alatt a [Latte plugin|https://plugins.jetbrains.com/plugin/7457-latte] bővítményt +- VS Code: telepítse a [Nette Latte + Neon|https://marketplace.visualstudio.com/items?itemName=Kasik96.latte], [Nette Latte templates|https://marketplace.visualstudio.com/items?itemName=smuuf.latte-lang] vagy a legújabb [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode] bővítményt +- NetBeans IDE: a natív Latte támogatás a telepítés része +- Sublime Text 3: a Package Controlban keresse meg és telepítse a `Nette` csomagot, majd válassza a Latte-t a `View > Syntax` menüben +- régi szerkesztőkben használja a Smarty kiemelést a .latte fájlokhoz -A PhpStorm plugin nagyon fejlett, és tökéletesen tud PHP kódot sugallni. Az optimális működéshez használjon [tipizált sablonokat |type-system]. +A PhpStorm plugin nagyon fejlett és kiválóan tudja javasolni a PHP kódot. Az optimális működéshez használjon [típusos sablonokat|type-system]. [* latte-phpstorm-plugin.webp *] -A Latte támogatása megtalálható a [Prism.js |https://prismjs.com/#supported-languages] webes kódkiemelőben és az [Ace |https://ace.c9.io] szerkesztőben is. +A Latte támogatását megtalálja a [Prism.js|https://prismjs.com/#supported-languages] webes kódkiemelőben és az [Ace|https://ace.c9.io] szerkesztőben is. -Latte JavaScript vagy CSS nyelven belül .[#toc-latte-inside-javascript-or-css] -============================================================================== +Latte JavaScripten vagy CSS-en belül +==================================== -A Latte nagyon kényelmesen használható JavaScript vagy CSS nyelven belül. De hogyan lehet elkerülni, hogy a Latte tévesen JavaScript kódot vagy CSS stílust tekintsen Latte tagnek? +A Latte nagyon kényelmesen használható JavaScripten vagy CSS-en belül is. Hogyan kerülhetjük el azonban azt a helyzetet, hogy a Latte tévesen Latte tagnek tekintse a JavaScript kódot vagy a CSS stílust? ```latte ``` -**1. lehetőség** +**1. változat** -Kerülje az olyan helyzeteket, amikor egy betű közvetlenül egy `{` után következik, akár szóköz, akár sortörés, akár idézőjel beiktatásával: +Kerülje el azt a helyzetet, amikor a `{` jelet közvetlenül betű követi, például úgy, hogy elé szóközt, sortörést vagy idézőjelet tesz: ```latte -

                                                                      + +

                                                                      ``` -Kétféleképpen és kétféleképpen lehet az adatokat elszkanderezni. A ` ``` -Ha azonban be akarjuk illeszteni egy HTML-attribútumba, akkor továbbra is szükségünk van az idézőjelek HTML-egységekbe történő szedésére: +Ha azonban HTML attribútumba szeretnénk beilleszteni, még escapelnünk kell az idézőjeleket HTML entitásokra: ```html
                                                                      ``` -A beágyazott kontextusnak azonban nem csak JS-nek vagy CSS-nek kell lennie. Ez általában egy URL is. Az URL-ekben szereplő paramétereket a speciális karakterek `%` kezdetű szekvenciákká alakításával szüntetjük meg. Példa: +De a beágyazott kontextus nemcsak JS vagy CSS lehet. Gyakran URL is. Az URL paramétereket úgy escapeljük, hogy a speciális jelentéssel bíró karaktereket `%`-kal kezdődő szekvenciákra alakítjuk át. Példa: ``` https://example.org/?a=Jazz&b=Rock%27n%27Roll ``` -Amikor pedig ezt a karakterláncot egy attribútumban adjuk ki, akkor is a kontextusnak megfelelően alkalmazzuk az escapinget, és a `&` with `&` helyébe a : +És amikor ezt a stringet egy attribútumban írjuk ki, még alkalmazzuk az escapelést ennek a kontextusnak megfelelően, és helyettesítjük az `&`-t `&`-pal: ```html ``` -Ha idáig elolvastad, gratulálok, kimerítő volt. Most már van egy jó elképzelésed arról, hogy mi a kontextus és az escaping. És nem kell aggódnod amiatt, hogy bonyolult. A Latte ezt automatikusan megteszi helyetted. +Ha idáig elolvasta, gratulálunk, kimerítő volt. Most már jó elképzelése van arról, mik azok a kontextusok és az escapelés. És nem kell aggódnia, hogy bonyolult. A Latte ezt ugyanis automatikusan megteszi Ön helyett. -Latte vs. Naiv rendszerek .[#toc-latte-vs-naive-systems] -======================================================== +Latte vs naiv rendszerek +======================== -Megmutattuk, hogyan kell megfelelően eszkábálni egy HTML dokumentumban, és hogy mennyire fontos ismerni a kontextust, azaz azt, hogy hol adjuk ki az adatokat. Más szóval, hogyan működik a kontextusérzékeny escaping. -Bár ez előfeltétele a funkcionális XSS-védelemnek, **a **Latte az egyetlen olyan PHP templating rendszer, amely ezt megteszi.** +Megmutattuk, hogyan kell helyesen escapelni egy HTML dokumentumban, és mennyire alapvető a kontextus ismerete, azaz annak a helynek az ismerete, ahol az adatokat kiírjuk. Más szóval, hogyan működik a kontextusérzékeny escapelés. Bár ez elengedhetetlen feltétele a működő XSS elleni védelemnek, **a Latte az egyetlen PHP sablonrendszer, amely ezt tudja.** -Hogyan lehetséges ez, amikor manapság minden rendszer azt állítja, hogy automatikus escapinggel rendelkezik? -Az automatikus menekítés a kontextus ismerete nélkül egy kis baromság, amely **hamis biztonságérzetet kelt**. +Hogyan lehetséges ez, amikor ma minden rendszer azt állítja, hogy automatikus escapeléssel rendelkezik? Az automatikus escapelés kontextus ismerete nélkül egy kicsit bullshit, amely **a biztonság hamis érzetét kelti**. -Az olyan sablonkészítő rendszerek, mint a Twig, Laravel Blade és mások nem látnak semmilyen HTML struktúrát a sablonban. Ezért nem látják a kontextusokat sem. A Latte-hoz képest vakok és naivak. Csak a saját markupjukat kezelik, minden más egy irreleváns karakterfolyam számukra: +Az olyan sablonrendszerek, mint a Twig, a Laravel Blade és mások, nem látnak semmilyen HTML struktúrát a sablonban. Így nem látják a kontextusokat sem. A Latte-val ellentétben vakok és naivak. Csak a saját tagjeiket dolgozzák fel, minden más számukra lényegtelen karakterfolyam:
                                                                      -```twig .{file:Twig template as seen by Twig himself} -░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░ -░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░{{ text }}░░░░ +```twig .{file:Twig sablon, ahogy maga a Twig látja} +░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░ +░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░ ``` -```twig .{file:Twig template as the designer sees it} -- in text: {{ text }} -- in tag: -- in attribute: -- in unquoted attribute: -- in attribute containing URL: -- in attribute containing JavaScript: -- in attribute containing CSS: -- in JavaScriptu: -- in CSS: -- in comment: +```twig .{file:Twig sablon, ahogy a tervező látja} +- szövegben: {{ foo }} +- tagben: +- attribútumban: +- attribútumban idézőjelek nélkül: +- URL-t tartalmazó attribútumban: +- JavaScriptet tartalmazó attribútumban: +- CSS-t tartalmazó attribútumban: +- JavaScriptben: +- CSS-ben: +- kommentárban: ```
                                                                      -A naiv rendszerek egyszerűen mechanikusan konvertálják a `< > & ' "` karaktereket HTML-egységekké, ami a legtöbb felhasználásnál érvényes módja az escapingnek, de messze nem mindig. Így nem képesek felismerni vagy megelőzni a különböző biztonsági réseket, amint azt az alábbiakban bemutatjuk. +A naiv rendszerek csak mechanikusan alakítják át a `< > & ' "` karaktereket HTML entitásokká, ami ugyan a legtöbb felhasználási esetben érvényes escapelési mód, de korántsem mindig. Így nem tudják felfedezni vagy megelőzni a különböző biztonsági rések kialakulását, ahogy azt később bemutatjuk. -A Latte ugyanúgy látja a sablont, mint te. Érti a HTML-t, az XML-t, felismeri a címkéket, attribútumokat stb. És ennek köszönhetően különbséget tesz a kontextusok között, és ennek megfelelően kezeli az adatokat. Így valóban hatékony védelmet nyújt a kritikus Cross-site Scripting sebezhetőség ellen. +A Latte ugyanúgy látja a sablont, mint Ön. Érti a HTML-t, XML-t, felismeri a tageket, attribútumokat stb. És ennek köszönhetően megkülönbözteti az egyes kontextusokat és azok szerint kezeli az adatokat. Így valóban hatékony védelmet nyújt a kritikus Cross-site Scripting sebezhetőség ellen. + +
                                                                      + +```latte .{file:Latte sablon, ahogy a Latte látja} +░░░░░░░░░░░{$foo} +░░░░░░░░░░ +░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░ +░░░░░░░░░ +░░░░░░░░░░░░░░░ +``` + +```latte .{file:Latte sablon, ahogy a tervező látja} +- szövegben: {$foo} +- tagben: +- attribútumban: +- attribútumban idézőjelek nélkül: +- URL-t tartalmazó attribútumban: +- JavaScriptet tartalmazó attribútumban: +- CSS-t tartalmazó attribútumban: +- JavaScriptben: +- CSS-ben: +- kommentárban: +``` + +
                                                                      -Élő bemutató .[#toc-live-demonstration] -======================================= +Élő bemutató +============ -Balra látható a sablon a Latte-ban, jobbra pedig a generált HTML kód. A `$text` változó többször is kikerül, minden alkalommal kissé eltérő kontextusban. És ezért egy kicsit másképp is eszkábálódik. A sablon kódját maga is szerkesztheti, például megváltoztathatja a változó tartalmát stb. Próbálja ki: +Bal oldalon a Latte sablont látja, jobb oldalon a generált HTML kódot. A `$text` változó többször kiíródik, és minden alkalommal egy kicsit más kontextusban. És ezért egy kicsit másképp escapelve. A sablon kódját Ön is szerkesztheti, például megváltoztathatja a változó tartalmát stb. Próbálja ki:
                                                                      ``` .{file:template.latte; min-height: 14em}[fiddle-source] -{* TRY TO EDIT THIS TEMPLATE *} +{* PRÓBÁLJA MEGSZERKESZTENI EZT A SABLONT *} {var $text = "Rock'n'Roll"} - {$text} - @@ -270,63 +286,61 @@ Balra látható a sablon a Latte-ban, jobbra pedig a generált HTML kód. A `$te
                                                                      -Hát nem nagyszerű! A Latte automatikusan elvégzi a kontextusfüggő eszkópálást, így a programozó: +Hát nem nagyszerű! A Latte automatikusan végzi a kontextusérzékeny escapelést, így a programozó: -- nem kell gondolkodnia, vagy tudnia, hogyan kell az adatokat kikerülni. +- nem kell gondolkodnia vagy tudnia, hol hogyan kell escapelni - nem tévedhet -- nem tud róla megfeledkezni +- nem felejtheti el az escapelést -Ezek még csak nem is az összes olyan kontextus, amelyet a Latte megkülönböztet a kiadáskor, és amelyre testre szabja az adatok kezelését. Most további érdekes eseteket fogunk végigvenni. +Ez még nem is az összes kontextus, amelyet a Latte megkülönböztet a kiíráskor, és amelyekhez igazítja az adatkezelést. További érdekes eseteket fogunk most áttekinteni. -Hogyan hackeljük meg a naiv rendszereket .[#toc-how-to-hack-naive-systems] -========================================================================== +Hogyan törjünk fel naiv rendszereket +==================================== -Néhány gyakorlati példán keresztül mutatjuk be, hogy mennyire fontos a kontextus megkülönböztetés, és hogy a naiv templating rendszerek miért nem nyújtanak elegendő védelmet az XSS ellen, ellentétben a Latte-val. -A példákban a Twig-et fogjuk használni egy naiv rendszer képviselőjeként, de ugyanez vonatkozik más rendszerekre is. +Néhány gyakorlati példán keresztül bemutatjuk, mennyire fontos a kontextusok megkülönböztetése, és miért nem nyújtanak a naiv sablonrendszerek elegendő védelmet az XSS ellen, ellentétben a Latte-val. A naiv rendszer képviselőjeként a Twig-et használjuk a példákban, de ugyanez érvényes a többi rendszerre is. -Attribútum sebezhetőség .[#toc-attribute-vulnerability] -------------------------------------------------------- +Sebezhetőség attribútummal +-------------------------- -Próbáljunk meg rosszindulatú kódot bejuttatni az oldalba a HTML attribútum segítségével, ahogy azt [fentebb bemutattuk |#How does the vulnerability arise]. Legyen egy Twig sablonunk, amely egy képet jelenít meg: +Megpróbálunk rosszindulatú kódot injektálni az oldalba HTML attribútum segítségével, ahogy azt [fentebb mutattuk |#Hogyan keletkezik a sebezhetőség]. Legyen egy Twig sablonunk, amely egy képet jelenít meg: ```twig .{file:Twig} {{ ``` -Vegyük észre, hogy az attribútum értékei körül nincsenek idézőjelek. A kódoló talán elfelejtette őket, ami csak úgy megtörténik. A Reactban például a kódot így, idézőjelek nélkül írják, és egy nyelvváltás közbeni kódoló könnyen elfelejtheti az idézőjeleket. +Figyelje meg, hogy az attribútumok értékei körül nincsenek idézőjelek. A kódoló elfelejthette őket, ami egyszerűen előfordul. Például a Reactban a kódot így írják, idézőjelek nélkül, és a nyelveket váltogató kódoló könnyen elfelejtheti az idézőjeleket. -A támadó egy ügyesen felépített sztringet illeszt be `foo onload=alert('Hacked!')` képaláírásként. Azt már tudjuk, hogy a Twig nem tudja megmondani, hogy egy változót egy HTML-szövegfolyamban, egy attribútumon belül, egy HTML-kommentárban stb. ír ki; röviden, nem tesz különbséget a kontextusok között. És csak mechanikusan konvertálja a `< > & ' "` karaktereket HTML-egységekké. -Így az eredményül kapott kód így fog kinézni: +A támadó a kép leírásaként egy ügyesen összeállított `foo onload=alert('Hacked!')` stringet illeszt be. Már tudjuk, hogy a Twig nem tudja megállapítani, hogy a változó a HTML szövegfolyamban, egy attribútumon belül, HTML kommentárban stb. íródik-e ki, röviden nem különbözteti meg a kontextusokat. És csak mechanikusan alakítja át a `< > & ' "` karaktereket HTML entitásokká. Így az eredményül kapott kód így fog kinézni: ```html foo ``` -**Egy biztonsági rés keletkezett!** +**És létrejött egy biztonsági rés!** -Egy hamis `onload` attribútum az oldal részévé vált, és a böngésző a kép letöltése után azonnal végrehajtja azt. +Az oldal részévé vált egy becsempészett `onload` attribútum, és a böngésző azonnal a kép letöltése után lefuttatja. -Most nézzük meg, hogyan kezeli a Latte ugyanazt a sablont: +Most nézzük meg, hogyan bánik el ugyanezzel a sablonnal a Latte: ```latte .{file:Latte} {$imageAlt} ``` -Latte ugyanúgy látja a sablont, mint te. A Twiggel ellentétben megérti a HTML-t, és tudja, hogy egy változó olyan attribútumértékként kerül kiírásra, amely nincs idézőjelben. Ezért adja hozzá őket. Ha egy támadó beilleszti ugyanazt a feliratot, a kapott kód így fog kinézni: +A Latte ugyanúgy látja a sablont, mint Ön. A Twiggel ellentétben érti a HTML-t, és tudja, hogy a változó egy attribútum értékeként íródik ki, amely nincs idézőjelek között. Ezért kiegészíti őket. Ha a támadó ugyanazt a leírást illeszti be, az eredményül kapott kód így fog kinézni: ```html foo onload=alert('Hacked!') ``` -**Sikeresen megakadályozta az XSS-t.** +**A Latte sikeresen megakadályozta az XSS-t.** -Változó nyomtatása JavaScriptben .[#toc-printing-a-variable-in-javascript] --------------------------------------------------------------------------- +Változó kiírása JavaScriptben +----------------------------- -A kontextusérzékeny eszkópolásnak köszönhetően a PHP-változókat natívan használhatjuk JavaScriptben. +A kontextusérzékeny escapelésnek köszönhetően teljesen natívan lehet használni a PHP változókat JavaScripten belül. ```latte

                                                                      {$movie}

                                                                      @@ -334,7 +348,7 @@ A kontextusérzékeny eszkópolásnak köszönhetően a PHP-változókat natíva ``` -Ha a `$movie` változó a `'Amarcord & 8 1/2'` karakterláncot tárolja, a következő kimenetet generálja. Figyeljük meg a HTML-ben és a JavaScriptben, valamint a `onclick` attribútumban használt eltérő eszkópálást: +Ha a `$movie` változó az `'Amarcord & 8 1/2'` stringet tartalmazza, a következő kimenet generálódik. Figyelje meg, hogy a HTML-en belül más escapelés használatos, mint a JavaScripten belül, és megint más az `onclick` attribútumban: ```latte

                                                                      Amarcord & 8 1/2

                                                                      @@ -343,29 +357,27 @@ Ha a `$movie` változó a `'Amarcord & 8 1/2'` karakterláncot tárolja, a köve ``` -Linkellenőrzés .[#toc-link-checking] ------------------------------------- +Linkek ellenőrzése +------------------ -A Latte automatikusan ellenőrzi, hogy a `src` vagy a `href` attribútumban használt változó tartalmaz-e webes URL-t (azaz HTTP protokollt), és megakadályozza a biztonsági kockázatot jelentő linkek írását. +A Latte automatikusan ellenőrzi, hogy a `src` vagy `href` attribútumokban használt változó webes URL-t tartalmaz-e (azaz HTTP protokollt), és megakadályozza az olyan linkek kiírását, amelyek biztonsági kockázatot jelenthetnek. ```latte {var $link = 'javascript:attack()'} -click here +kattints ``` -Írja: +Kiírja: ```latte -click here +kattints ``` -Az ellenőrzés kikapcsolható a [nocheck |filters#nocheck] szűrővel. +Az ellenőrzést a [nocheck |filters#nocheck] szűrővel lehet kikapcsolni. -A Latte határai .[#toc-limits-of-latte] -======================================= +A Latte korlátai +================ -A Latte nem nyújt teljes XSS-védelmet az egész alkalmazás számára. Nem örülnénk, ha a Latte használatakor nem gondolna a biztonságra. -A Latte célja annak biztosítása, hogy egy támadó ne tudja megváltoztatni az oldal szerkezetét, nem tudja manipulálni a HTML elemeket vagy attribútumokat. De nem ellenőrzi a kimenő adatok tartalmi helyességét. Vagy a JavaScript viselkedés helyességét. -Ez túlmutat a templating rendszer hatáskörén. Az adatok helyességének ellenőrzése, különösen a felhasználó által bevitt és így nem megbízható adatoké, a programozó fontos feladata. +A Latte nem nyújt teljesen teljes körű védelmet az XSS ellen az egész alkalmazás számára. Nem szeretnénk, ha a Latte használatakor abbahagyná a biztonságra való gondolkodást. A Latte célja annak biztosítása, hogy a támadó ne tudja megváltoztatni az oldal struktúráját, becsempészni HTML elemeket vagy attribútumokat. De nem ellenőrzi a kiírt adatok tartalmi helyességét. Vagy a JavaScript viselkedésének helyességét. Ez már túlmutat a sablonrendszer kompetenciáin. Az adatok helyességének ellenőrzése, különösen a felhasználó által bevitt és ezért nem megbízható adatoké, a programozó fontos feladata. diff --git a/latte/hu/sandbox.texy b/latte/hu/sandbox.texy index 4eed6ece26..5da5fff9c5 100644 --- a/latte/hu/sandbox.texy +++ b/latte/hu/sandbox.texy @@ -1,12 +1,10 @@ Sandbox ******* -.[perex]{data-version:2.8} -A Sandbox egy olyan biztonsági réteget biztosít, amely lehetővé teszi, hogy ellenőrizze, mely címkék, PHP-funkciók, metódusok stb. használhatók a sablonokban. A sandbox módnak köszönhetően biztonságosan együttműködhet egy ügyféllel vagy külső kódolóval a sablonok létrehozásában anélkül, hogy aggódnia kellene az alkalmazás veszélyeztetése vagy nem kívánt műveletek miatt. +.[perex] +A Sandbox egy biztonsági réteget biztosít, amely lehetővé teszi annak szabályozását, hogy milyen tagek, PHP függvények, metódusok stb. használhatók a sablonokban. A sandbox módnak köszönhetően biztonságosan együttműködhet az ügyféllel vagy külső kódolóval a sablonok létrehozásában anélkül, hogy aggódnia kellene az alkalmazás megsértése vagy nem kívánt műveletek miatt. -Hogyan működik? Egyszerűen meghatározzuk, hogy mit szeretnénk engedélyezni a sablonban. Kezdetben minden tiltott, és fokozatosan adunk engedélyeket: - -A következő kód engedélyezi a sablon számára a `{block}`, `{if}`, `{else}` és `{=}` címkék (ez utóbbi [egy változó vagy kifejezés nyomtatására |tags#Printing] szolgáló címke) és az összes szűrő használatát: +Hogyan működik? Egyszerűen definiáljuk, mit engedélyezünk a sablonnak. Alapértelmezés szerint minden tiltva van, és fokozatosan engedélyezünk dolgokat. A következő kóddal lehetővé tesszük a sablon szerzőjének a `{block}`, `{if}`, `{else}` és `{=}` tagek használatát, ami a [változó vagy kifejezés kiírására |tags#Kiírás] szolgáló tag, valamint az összes szűrőt: ```php $policy = new Latte\Sandbox\SecurityPolicy; @@ -16,7 +14,7 @@ $policy->allowFilters($policy::All); $latte->setPolicy($policy); ``` -Az objektumok globális függvényeihez, módszereihez vagy tulajdonságaihoz való hozzáférést is engedélyezhetjük: +Továbbá engedélyezhetjük az egyes függvényeket, metódusokat vagy objektumok property-jeit: ```php $policy->allowFunctions(['trim', 'strlen']); @@ -24,30 +22,35 @@ $policy->allowMethods(Nette\Security\User::class, ['isLoggedIn', 'isAllowed']); $policy->allowProperties(Nette\Database\Row::class, $policy::All); ``` -Hát nem csodálatos? Nagyon alacsony szinten mindent irányíthatsz. Ha a sablon megpróbál meghívni egy nem engedélyezett függvényt vagy hozzáférni egy nem engedélyezett metódushoz vagy tulajdonsághoz, akkor kivételt dob `Latte\SecurityViolationException`. +Hát nem csodálatos? Nagyon alacsony szinten mindent ellenőrizhet. Ha a sablon megpróbál meghívni egy nem engedélyezett függvényt, vagy hozzáférni egy nem engedélyezett metódushoz vagy property-hez, az `Latte\SecurityViolationException` kivételt eredményez. -A házirendek létrehozása a semmiből, amikor minden tiltott, nem biztos, hogy kényelmes, így egy biztonságos alapról indulhat: +A policy létrehozása a nulláról, amikor minden teljesen tiltva van, nem biztos, hogy kényelmes, ezért kezdhet egy biztonságos alapról: ```php $policy = Latte\Sandbox\SecurityPolicy::createSafePolicy(); ``` -Ez azt jelenti, hogy minden szabványos címke engedélyezett, kivéve a `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget`. -Az összes szabványos szűrő is megengedett, kivéve a `datastream`, `noescape` és `nocheck`. Végül a `$iterator` objektum metódusaihoz és tulajdonságaihoz való hozzáférés is engedélyezett. +A biztonságos alap azt jelenti, hogy minden standard tag engedélyezett, kivéve a `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget` tageket. Minden standard szűrő engedélyezett, kivéve a `datastream`, `noescape` és `nocheck` szűrőket. Végül engedélyezett a hozzáférés a `$iterator` objektum metódusaihoz és property-jeihez. -A szabályok az új sablonra vonatkoznak, amelyet az új [`{sandbox}` |tags#Including Templates] címkével. Ami egy olyasmi, mint a `{include}`, de bekapcsolja a sandbox módot, és nem ad át külső változókat sem: +A szabályok arra a sablonra vonatkoznak, amelyet a [`{sandbox}` |tags#Sablon beillesztése] taggel illesztünk be. Ez egyfajta `{include}` megfelelője, amely azonban bekapcsolja a biztonságos módot, és nem ad át semmilyen változót: ```latte {sandbox 'untrusted.latte'} ``` -Így az elrendezés és az egyes oldalak ugyanúgy használhatják az összes taget és változót, mint korábban, a korlátozások csak a sablonra vonatkoznak `untrusted.latte`. +Tehát a layout és az egyes oldalak zavartalanul használhatják az összes taget és változót, csak az `untrusted.latte` sablonra vonatkoznak a korlátozások. -Egyes szabálysértések, például tiltott címkék vagy szűrők használata, már a fordításkor észlelésre kerülnek. Mások, mint például egy objektum nem engedélyezett metódusainak meghívása, futásidőben. -A sablon bármilyen más hibát is tartalmazhat. Annak érdekében, hogy a sandboxolt sablonból ne dobjon ki kivételt, ami megzavarja a teljes renderelést, definiálhat [saját kivételkezelőt |develop#exception handler], amely például csak naplózza azt. +Néhány vétség, mint például egy tiltott tag vagy szűrő használata, a fordítási időben derül ki. Mások, mint például egy objektum tiltott metódusának hívása, csak futás közben. A sablon tartalmazhat bármilyen más hibát is. Annak érdekében, hogy a sandboxolt sablonból ne ugorhasson ki olyan kivétel, amely megzavarja az egész megjelenítést, definiálhat egy saját [kivételkezelő handler |develop#Exception handler]-t, amely például naplózza azt. -Ha közvetlenül az összes sablonra szeretnénk bekapcsolni a sandbox módot, az egyszerű: +Ha a sandbox módot közvetlenül az összes sablonra szeretnénk bekapcsolni, az egyszerűen megtehető: ```php $latte->setSandboxMode(); ``` + +Annak biztosítása érdekében, hogy a felhasználó ne illesszen be olyan PHP kódot az oldalra, amely ugyan szintaktikailag helyes, de tiltott és PHP Compile Error-t okoz, javasoljuk, hogy a [sablonokat ellenőriztesse PHP linterrel |develop#Generált kód ellenőrzése]. Ezt a funkcionalitást az `Engine::enablePhpLint()` metódussal kapcsolhatja be. Mivel az ellenőrzéshez a PHP binárisát kell hívnia, adja át annak elérési útját paraméterként: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); +``` diff --git a/latte/hu/syntax.texy b/latte/hu/syntax.texy index 722533cfc9..7a9eb43f96 100644 --- a/latte/hu/syntax.texy +++ b/latte/hu/syntax.texy @@ -2,78 +2,76 @@ Szintaxis ********* .[perex] -A Syntax Latte a webdesignerek gyakorlati igényeiből született. Kerestük a leginkább felhasználóbarát szintaxist, amellyel elegánsan lehet írni olyan konstrukciókat, amelyek egyébként igazi kihívást jelentenek. -Ugyanakkor minden kifejezés pontosan ugyanúgy van megírva, mint a PHP-ben, így nem kell új nyelvet tanulnod. Csak a lehető legtöbbet hozod ki abból, amit már tudsz. +A Latte szintaxisa a webdesignerek gyakorlati igényeiből fakadt. A legbarátságosabb szintaxist kerestük, amellyel elegánsan leírhatók olyan konstrukciók is, amelyek egyébként igazi fejtörést okoznának. Ugyanakkor minden kifejezés pontosan ugyanúgy íródik, mint PHP-ban, így nem kell új nyelvet tanulnia. Egyszerűen kamatoztathatja azt, amit már régóta tud. -Az alábbiakban egy minimális sablont mutatunk be, amely néhány alapelemet szemléltet: címkék, n:attribútumok, megjegyzések és szűrők. +Az alábbiakban egy minimális sablon látható, amely néhány alapvető elemet illusztrál: tageket, n:attribútumokat, kommentárokat és szűrőket. ```latte -{* ez egy megjegyzés *} -
                                                                        {* n:if is n:atribut *} -{foreach $items as $item} {* tag representing foreach loop *} -
                                                                      • {$item|capitalize}
                                                                      • {* egy változót szűrővel kiíró tag *} +{* ez egy kommentár *} +
                                                                          {* n:if egy n:attribútum *} +{foreach $items as $item} {* foreach ciklust képviselő tag *} +
                                                                        • {$item|capitalize}
                                                                        • {* változót szűrővel kiíró tag *} {/foreach} {* ciklus vége *}
                                                                        ``` -Nézzük meg közelebbről ezeket a fontos elemeket, és azt, hogyan segíthetnek egy hihetetlen sablon létrehozásában. +Nézzük meg közelebbről ezeket a fontos elemeket, és hogy hogyan segíthetnek Önnek lenyűgöző sablont létrehozni. -Címkék .[#toc-tags] -=================== +Tagek +===== -A sablon olyan címkéket tartalmaz, amelyek a sablon logikáját (például *foreach* ciklusok) vagy kimeneti kifejezéseket vezérlik. Mindkettőhöz egyetlen elválasztójelet használ `{ ... }`, így nem kell azon gondolkodni, hogy más rendszerekhez hasonlóan melyik elválasztójelet melyik helyzetben használjuk. -Ha a `{` karaktert idézőjel vagy szóköz követi, a Latte nem tekinti azt egy tag kezdetének, így a sablonokban gond nélkül használhat JavaScript-konstrukciókat, JSON-t vagy CSS-szabályokat. +A sablon tageket tartalmaz, amelyek a sablon logikáját vezérlik (például *foreach* ciklusok) vagy kifejezéseket írnak ki. Mindkettőhöz egyetlen elválasztót `{ ... }` használnak, így nem kell azon gondolkodnia, hogy milyen elválasztót használjon milyen helyzetben, mint más rendszereknél. Ha a `{` karaktert idézőjel vagy szóköz követi, a Latte nem tekinti azt tag kezdetének, így a sablonokban problémamentesen használhat JavaScript konstrukciókat, JSON-t vagy CSS szabályokat is. -Lásd az [összes címke áttekintését |tags]. Ezenkívül [egyéni címkéket |extending-latte#tags] is létrehozhat. +Tekintse meg [az összes tag áttekintését|tags]. Ezenkívül létrehozhat [egyéni tageket|custom tags] is. -A Latte érti a PHP-t .[#toc-latte-understands-php] -================================================== +A Latte érti a PHP-t +==================== -A címkéken belül jól ismert PHP-kifejezéseket használhatsz: +A tageken belül használhat PHP kifejezéseket, amelyeket jól ismer: - változók -- karakterláncok (beleértve a HEREDOC és NOWDOC karakterláncokat), tömbök, számok stb. +- stringek (beleértve a HEREDOC és NOWDOC), tömbök, számok stb. - [operátorok |https://www.php.net/manual/en/language.operators.php] -- függvény- és metódushívások (amelyeket a [sandbox |sandbox] korlátozhat) -- [mérkőzés |https://www.php.net/manual/en/control-structures.match.php] +- függvény- és metódushívások (amelyeket [sandboxszal|sandbox] lehet korlátozni) +- [match |https://www.php.net/manual/en/control-structures.match.php] - [névtelen függvények |https://www.php.net/manual/en/functions.arrow.php] -- [visszahívások |https://www.php.net/manual/en/functions.first_class_callable_syntax.php] -- többsoros megjegyzések `/* ... */` -- stb... +- [callbackek |https://www.php.net/manual/en/functions.first_class_callable_syntax.php] +- többsoros kommentárok `/* ... */` +- stb… -Ezen kívül a Latte számos [szép kiterjesztést |#Syntactic Sugar] ad a PHP szintaxisához. +A Latte ezenkívül a PHP szintaxisát néhány [kellemes bővítménnyel |#Szintaktikai cukor] egészíti ki. -n:attribútumok .[#toc-n-attributes] -=================================== +n:attribútumok +============== -Az egyes HTML-elemekre ható páros tagek, mint például a `{if} … {/if}`, [n:attribútum |#n:attribute] jelöléssel írhatók. A fenti példában szereplő `{foreach}` például így is írható: +Minden páros tag, például `{if} … {/if}`, amely egyetlen HTML elemen működik, átírható n:attribútum formájába. Így lehetne például leírni a `{foreach}`-et is a bevezető példában: ```latte -
                                                                          +
                                                                          • {$item|capitalize}
                                                                          ``` -A funkció ekkor annak a HTML-elemnek felel meg, amelyikbe be van írva: +A funkcionalitás ekkor arra a HTML elemre vonatkozik, amelybe elhelyezték: ```latte -{var $items = ['I', '♥', 'Latte']} +{var $items = ['Én', '♥', 'Latte']}

                                                                          {$item}

                                                                          ``` -Nyomtatás: +kiírja: ```latte -

                                                                          I

                                                                          +

                                                                          Én

                                                                          Latte

                                                                          ``` -A `inner-` előtag használatával megváltoztathatjuk a viselkedést úgy, hogy a funkció csak az elem testére vonatkozzon: +Az `inner-` előtag segítségével módosíthatjuk a viselkedést úgy, hogy az csak az elem belső részére vonatkozzon: ```latte
                                                                          @@ -82,11 +80,11 @@ A `inner-` előtag használatával megváltoztathatjuk a viselkedést úgy, hogy
                                                                          ``` -Nyomtatás: +Kiírja: ```latte
                                                                          -

                                                                          I

                                                                          +

                                                                          Én



                                                                          @@ -95,178 +93,184 @@ Nyomtatás:
                                                                          ``` -A `tag-` előtag használatával a funkciót csak a HTML-címkékre alkalmazzuk: +Vagy a `tag-` előtag segítségével a funkcionalitást csak magukra a HTML tagekre alkalmazzuk: ```latte -

                                                                          Title

                                                                          +

                                                                          Cím

                                                                          ``` -A `$url` változó értékétől függően ez kiírásra kerül: +Ami a `$url` változótól függően írja ki: ```latte -// when $url is empty -

                                                                          Title

                                                                          +{* ha $url üres *} +

                                                                          Cím

                                                                          -// when $url equals 'https://nette.org' -

                                                                          Title

                                                                          +{* ha $url 'https://nette.org'-ot tartalmaz *} +

                                                                          Cím

                                                                          ``` -A n:attribútumok azonban nem csak a páros címkék rövidítései, vannak tisztán n:attribútumok is, például a kódoló legjobb barátja, [az n:class |tags#n:class]. +Azonban az n:attribútumok nemcsak a páros tagek rövidítései. Léteznek tiszta n:attribútumok is, mint például a [n:href |application:creating-links#A presenter sablonjában] vagy a kódoló rendkívül ügyes segítője, a [n:class |tags#n:class]. -Szűrők .[#toc-filters] -====================== +Szűrők +====== -Lásd a [szabványos szűrők |filters] összefoglalóját. +Tekintse meg a [standard szűrők |filters] áttekintését. -A Latte lehetővé teszi a szűrők hívását a pipajel jelölés használatával (az előző szóköz megengedett): +A szűrőket függőleges vonal után írjuk (előtte lehet szóköz): ```latte

                                                                          {$heading|upper}

                                                                          ``` -A szűrők láncolhatók, ebben az esetben balról jobbra haladva alkalmazandók: +A szűrőket láncolni lehet, és balról jobbra kerülnek alkalmazásra: ```latte

                                                                          {$heading|lower|capitalize}

                                                                          ``` -A paraméterek a szűrő neve után kerülnek, kettősponttal vagy vesszővel elválasztva: +A paramétereket a szűrő neve után adjuk meg, kettősponttal vagy vesszővel elválasztva: ```latte

                                                                          {$heading|truncate:20,''}

                                                                          ``` -A szűrők kifejezésekre alkalmazhatók: +A szűrőket kifejezésre is lehet alkalmazni: ```latte {var $name = ($title|upper) . ($subtitle|lower)} ``` -Blokkoláskor: +Blokkra: ```latte

                                                                          {block |lower}{$heading}{/block}

                                                                          ``` -Vagy közvetlenül az értékre (a [`{=expr}` | https://latte.nette.org/hu/tags#printing] taggel): +Vagy közvetlenül az értékre (a [`{=expr}` |tags#Kiírás] taggel kombinálva): ```latte -

                                                                          {=' Hello world '|trim}

                                                                          +

                                                                          {=' Helló világ '|trim}

                                                                          ``` -Hozzászólások .[#toc-comments] -============================== +Dinamikus HTML tagek .{data-version:3.0.9} +========================================== + +A Latte támogatja a dinamikus HTML tageket, amelyek hasznosak, ha rugalmasságra van szükség a tagnevekben: + +```latte +Címsor +``` + +A fenti kód például `

                                                                          Címsor

                                                                          ` vagy `

                                                                          Címsor

                                                                          ` kódot generálhat a `$level` változó értékétől függően. A dinamikus HTML tageknek a Latte-ban mindig párosnak kell lenniük. Alternatívájuk a [n:tag |tags#n:tag]. -A megjegyzések így íródnak, és nem kerülnek be a kimenetbe: +Mivel a Latte egy biztonságos sablonrendszer, ellenőrzi, hogy az eredményül kapott tagnév érvényes-e, és nem tartalmaz-e nem kívánt vagy káros értékeket. Továbbá biztosítja, hogy a záró tag neve mindig megegyezzen a nyitó tag nevével. + + +Kommentárok +=========== + +A kommentárokat így írjuk, és nem kerülnek be a kimenetbe: ```latte -{* ez egy komment a Latte nyelven *} +{* ez egy kommentár Latte-ban *} ``` -A PHP-kommentárok a címkéken belül működnek: +A tageken belül működnek a PHP kommentárok: ```latte {include 'file.info', /* value: 123 */} ``` -Szintaktikai cukor .[#toc-syntactic-sugar] -========================================== +Szintaktikai cukor +================== -Idézőjelek nélküli karakterláncok .[#toc-strings-without-quotation-marks] -------------------------------------------------------------------------- +Stringek idézőjelek nélkül +-------------------------- -Az idézőjelek elhagyhatók az egyszerű karakterláncok esetében: +Egyszerű stringeknél elhagyhatók az idézőjelek: ```latte -as in PHP: {var $arr = ['hello', 'btn--default', '€']} +mint PHP-ban: {var $arr = ['helló', 'btn--default', '€']} -abbreviated: {var $arr = [hello, btn--default, €]} +rövidítve: {var $arr = [helló, btn--default, €]} ``` -Az egyszerű karakterláncok olyanok, amelyek kizárólag betűkből, számjegyekből, aláhúzásokból, kötőjelekből és pontokból állnak. Nem kezdődhetnek számjegyekkel, és nem kezdődhetnek és nem végződhetnek kötőjellel. -Nem állhat csak nagybetűkből és aláhúzásokból, mert akkor konstansnak minősül (pl. `PHP_VERSION`). -És nem ütközhet a `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor` kulcsszavakkal. +Egyszerű stringek azok, amelyek tisztán betűkből, számjegyekből, aláhúzásokból, kötőjelekből és pontokból állnak. Nem kezdődhetnek számjeggyel, és nem kezdődhetnek vagy végződhetnek kötőjellel. Nem állhatnak csak nagybetűkből és aláhúzásokból, mert akkor konstansnak minősülnek (pl. `PHP_VERSION`). És nem ütközhetnek a kulcsszavakkal: `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor`. -Rövid hármas operátor .[#toc-short-ternary-operator] ----------------------------------------------------- +Konstansok +---------- -Ha a terner operátor harmadik értéke üres, akkor elhagyható: +Mivel az egyszerű stringeknél elhagyhatók az idézőjelek, javasoljuk, hogy a megkülönböztetés érdekében a globális konstansokat perjellel kezdve írja: ```latte -as in PHP: {$stock ? 'In stock' : ''} - -abbreviated: {$stock ? 'In stock'} +{if \PROJECT_ID === 1} ... {/if} ``` +Ez az írásmód teljesen érvényes magában a PHP-ban is, a perjel azt jelzi, hogy a konstans a globális névtérben van. + -Modern kulcsjelölés a tömbben .[#toc-modern-key-notation-in-the-array] ----------------------------------------------------------------------- +Rövidített ternáris operátor +---------------------------- -A tömbkulcsok a függvények hívásakor a megnevezett paraméterekhez hasonlóan írhatók: +Ha a ternáris operátor harmadik értéke üres, elhagyható: ```latte -as in PHP: {var $arr = ['one' => 'item 1', 'two' => 'item 2']} +mint PHP-ban: {$stock ? 'Raktáron' : ''} -modern: {var $arr = [one: 'item 1', two: 'item 2']} +rövidítve: {$stock ? 'Raktáron'} ``` -Szűrők .[#toc-filters] ----------------------- +Kulcsok modern írásmódja tömbökben +---------------------------------- -A szűrők bármilyen kifejezéshez használhatók, csak zárójelbe kell tenni az egészet: +A tömbök kulcsait hasonlóan lehet írni, mint a névvel ellátott paramétereket a függvényhívásoknál: ```latte -{var $content = ($text|truncate: 30|upper)} +mint PHP-ban: {var $arr = ['one' => 'elem 1', 'two' => 'elem 2']} + +modernül: {var $arr = [one: 'elem 1', two: 'elem 2']} ``` -Operátor `in` .[#toc-operator-in] ---------------------------------- +Szűrők +------ -A `in` operátor a `in_array()` függvény helyettesítésére használható. Az összehasonlítás mindig szigorú: +A szűrőket bármilyen kifejezésre lehet alkalmazni, csak az egészet zárójelek közé kell tenni: ```latte -{* like in_array($item, $items, true) *} -{if $item in $items} - ... -{/if} +{var $content = ($text|truncate: 30|upper)} ``` -.{data-version:2.9} -Választható láncolás a meghatározatlan biztonságos operátorral .[#toc-optional-chaining-with-undefined-safe-operator] ---------------------------------------------------------------------------------------------------------------------- +Az `in` operátor +---------------- -Az undefined-safe operátor `??->` hasonló a nullsafe operátorhoz `?->`, de nem okoz hibát, ha egy változó, tulajdonság vagy index egyáltalán nem létezik. +Az `in` operátorral helyettesíthető az `in_array()` függvény. Az összehasonlítás mindig szigorú: ```latte -{$order??->id} +{* az in_array($item, $items, true) megfelelője *} +{if $item in $items} + ... +{/if} ``` -ezzel azt akarjuk mondani, hogy ha a `$order` definiált és nem null, akkor a `$order->id` lesz kiszámítva, de ha a `$order` null vagy nem létezik, akkor hagyjuk abba, amit csinálunk, és csak nullát adunk vissza. - -```latte -{$user??->address??->street} -// roughly means isset($user) && isset($user->address) ? $user->address->street : null -``` +Történelmi kitekintő +-------------------- -Egy ablak a történelembe .[#toc-a-window-into-history] ------------------------------------------------------- +A Latte története során számos szintaktikai cukorkával állt elő, amelyek néhány év múlva magában a PHP-ban is megjelentek. Például a Latte-ban már régen lehetett tömböket `[1, 2, 3]` formában írni az `array(1, 2, 3)` helyett, vagy használni a nullsafe operátort `$obj?->foo`, mielőtt ez magában a PHP-ban lehetséges lett volna. A Latte bevezette a tömb kibontására szolgáló `(expand) $arr` operátort is, amely a mai PHP `...$arr` operátorának felel meg. -A Latte története során számos szintaktikai cukorkával rukkolt elő, amelyek néhány évvel később megjelentek magában a PHP-ben is. Például a Latte-ban lehetséges volt tömböket írni úgy, hogy `[1, 2, 3]` `array(1, 2, 3)` helyett, vagy használni a nullsafe operátort `$obj?->foo` jóval azelőtt, hogy ez magában a PHP-ben lehetséges lett volna. A Latte bevezette a `(expand) $arr` tömbbővítő operátort is, amely a mai PHP `...$arr` operátornak felel meg. +Az Undefined-safe operátor `??->`, amely a nullsafe operátor `?->` megfelelője, de nem vált ki hibát, ha a változó nem létezik, történelmi okokból jött létre, és ma a standard PHP `?->` operátor használatát javasoljuk. -PHP korlátozások a Latte-ban .[#toc-php-limitations-in-latte] -============================================================= +PHP korlátozások Latte-ban +========================== -A Latte-ban csak PHP kifejezéseket lehet írni. Vagyis nem lehet osztályokat deklarálni vagy [vezérlési struktúrákat |https://www.php.net/manual/en/language.control-structures.php] használni, mint például a `if`, `foreach`, `switch`, `return`, `try`, `throw` és mások, amelyek helyett a Latte kínálja a [címkéket |tags]. -Szintén nem használhatsz [attribútumokat |https://www.php.net/manual/en/language.attributes.php], [backtickeket |https://www.php.net/manual/en/language.operators.execution.php] vagy [mágikus konstansokat |https://www.php.net/manual/en/language.constants.magic.php], mert annak nem lenne értelme. -Nem használhatod még a `unset`, `echo`, `include`, `require`, `exit`, `eval`, mert ezek nem függvények, hanem speciális PHP nyelvi konstrukciók, tehát nem kifejezések. +Latte-ban csak PHP kifejezéseket lehet írni. Tehát nem lehet pontosvesszővel lezárt utasításokat használni. Nem lehet osztályokat deklarálni vagy [vezérlési struktúrákat |https://www.php.net/manual/en/language.control-structures.php] használni, pl. `if`, `foreach`, `switch`, `return`, `try`, `throw` és másokat, amelyek helyett a Latte saját [tageket|tags] kínál. Szintén nem lehet [attribútumokat |https://www.php.net/manual/en/language.attributes.php], [backtickeket |https://www.php.net/manual/en/language.operators.execution.php] vagy néhány [mágikus konstanst |https://www.php.net/manual/en/language.constants.magic.php] használni. Nem használható az `unset`, `echo`, `include`, `require`, `exit`, `eval` sem, mert ezek nem függvények, hanem speciális PHP nyelvi konstrukciók, és ezért nem kifejezések. A kommentárok közül csak a többsoros `/* ... */` támogatott. -Ezeket a korlátozásokat azonban megkerülheti a [RawPhpExtension |develop#RawPhpExtension] kiterjesztés aktiválásával, amely lehetővé teszi, hogy a sablon szerzőjének felelősségére bármilyen PHP kódot használjon a `{php ...}` tagben. +Ezeket a korlátozásokat azonban meg lehet kerülni a [RawPhpExtension |develop#RawPhpExtension] bővítmény aktiválásával, amely lehetővé teszi bármilyen PHP kód használatát a `{php ...}` tagben a sablon szerzőjének felelősségére. diff --git a/latte/hu/tags.texy b/latte/hu/tags.texy index 64f6191383..80f647e569 100644 --- a/latte/hu/tags.texy +++ b/latte/hu/tags.texy @@ -1,168 +1,174 @@ -Latte címkék -************ +Latte tagek +*********** .[perex] -Az összes beépített Latte-címke összefoglalása és leírása. +A Latte sablonrendszer összes tagjének áttekintése és leírása, amelyek alapértelmezés szerint rendelkezésre állnak. .[table-latte-tags language-latte] -|## Nyomtatás -| `{$var}`, `{...}` vagy `{=...}` | [nyomtat egy szkanderezett változót vagy kifejezést |#printing] -| `{$var\|filter}` | [nyomtatás szűrőkkel |#filters] -| `{l}` vagy `{r}` | kinyomtatja a `{` or `}` karaktert. +|## Kiírás +| `{$var}`, `{...}` vagy `{=...}` | [kiírja az escapelt változót vagy kifejezést |#Kiírás] +| `{$var\|filter}` | [szűrők használatával írja ki |#Szűrők] +| `{l}` vagy `{r}` | kiírja a `{` vagy `}` karaktert .[table-latte-tags language-latte] |## Feltételek -| `{if}`... `{elseif}`... `{else}`... `{/if}` | [condition if |#if-elseif-else] -| `{ifset}`... `{elseifset}`... `{/ifset}` | [feltétel ifset |#ifset-elseifset] -| `{ifchanged}`... `{/ifchanged}` | [tesztelés, hogy történt-e változás |#ifchanged] -| `{switch}` `{case}` `{default}` `{/switch}` | [condition switch |#switch-case-default] +| `{if}` … `{elseif}` … `{else}` … `{/if}` | [if feltétel |#if elseif else] +| `{ifset}` … `{elseifset}` … `{/ifset}` | [ifset feltétel |#ifset elseifset] +| `{ifchanged}` … `{/ifchanged}` | [teszteli, hogy történt-e változás |#ifchanged] +| `{switch}` `{case}` `{default}` `{/switch}` | [switch feltétel |#switch case default] +| `n:else` | [alternatív tartalom feltételekhez |#n:else] .[table-latte-tags language-latte] -|## Loops -| `{foreach}`... `{/foreach}` | [foreach |#foreach] -| `{for}`... `{/for}` | [for |#for] -| `{while}`... `{/while}` | [while |#while] -| `{continueIf $cond}` | [folytatás a következő iterációhoz |#continueif-skipif-breakif] -| `{skipIf $cond}` | [kihagyja az aktuális ciklus iterációját |#continueif-skipif-breakif] -| `{breakIf $cond}` | [a ciklus megszakítása |#continueif-skipif-breakif] -| `{exitIf $cond}` | [korai kilépés |#exitif] -| `{first}`... `{/first}` | [ez az első iteráció? |#first-last-sep] -| `{last}`... `{/last}` | [ez az utolsó ismétlés? |#first-last-sep] -| `{sep}`... `{/sep}` | [Következik a következő ismétlés? |#first-last-sep] -| `{iterateWhile}`... `{/iterateWhile}` | [strukturált foreach |#iterateWhile] -| `$iterator` | [speciális változó a foreach cikluson belül |#$iterator] +|## Ciklusok +| `{foreach}` … `{/foreach}` | [#foreach] +| `{for}` … `{/for}` | [#for] +| `{while}` … `{/while}` | [#while] +| `{continueIf $cond}` | [folytatás a következő iterációval |#continueIf skipIf breakIf] +| `{skipIf $cond}` | [iteráció kihagyása |#continueIf skipIf breakIf] +| `{breakIf $cond}` | [ciklus megszakítása |#continueIf skipIf breakIf] +| `{exitIf $cond}` | [korai kilépés |#exitIf] +| `{first}` … `{/first}` | [ez az első futás? |#first last sep] +| `{last}` … `{/last}` | [ez az utolsó futás? |#first last sep] +| `{sep}` … `{/sep}` | [lesz még következő futás? |#first last sep] +| `{iterateWhile}` … `{/iterateWhile}` | [strukturált foreach |#iterateWhile] +| `$iterator` | [speciális változó a foreach-en belül |#iterator] .[table-latte-tags language-latte] -|## Más sablonok bevonása -| `{include 'file.latte'}` | [más fájlból származó sablont tartalmaz |#include] -| `{sandbox 'file.latte'}` | [sablon felvétele sandbox módban |#sandbox] +|## További sablonok beillesztése +| `{include 'file.latte'}` | [betölti a sablont egy másik fájlból |#include] +| `{sandbox 'file.latte'}` | [betölti a sablont sandbox módban |#sandbox] .[table-latte-tags language-latte] -|## Blokkok, elrendezések, sablonöröklés -| `{block}` | [névtelen blokk |#block] -| `{block blockname}` | [blokkdefiníció |template-inheritance#blocks] -| `{define blockname}` | [blokkdefiníció jövőbeli használatra |template-inheritance#definitions] -| `{include blockname}` | [nyomtatási blokk |template-inheritance#printing-blocks] -| `{include blockname from 'file.latte'}` | [blokk nyomtatása fájlból |template-inheritance#printing-blocks] -| `{import 'file.latte'}` | [blokkok betöltése egy másik sablonból |template-inheritance#horizontal-reuse] -| `{layout 'file.latte'}` / `{extends}` | [elrendezési fájl megadása |template-inheritance#layout-inheritance] -| `{embed}`... `{/embed}` | [betölti a sablont vagy a blokkot, és lehetővé teszi a blokkok felülírását |template-inheritance#unit-inheritance] -| `{ifset blockname}`... `{/ifset}` | [feltétel, ha a blokk definiálva van |template-inheritance#checking-block-existence] +|## Blokkok, elrendezések, sablonöröklődés +| `{block}` | [névtelen blokk |#block] +| `{block blockname}` | [definiál egy blokkot |template-inheritance#Blokkok block] +| `{define blockname}` | [definiál egy blokkot későbbi használatra |template-inheritance#Definíciók define] +| `{include blockname}` | [blokk megjelenítése |template-inheritance#Blokkok renderelése include] +| `{include blockname from 'file.latte'}` | [megjeleníti a blokkot egy fájlból |template-inheritance#Blokkok renderelése include] +| `{import 'file.latte'}` | [betölti a blokkokat egy sablonból |template-inheritance#Horizontális újrafelhasználás import] +| `{layout 'file.latte'}` / `{extends}` | [meghatározza a layout fájlt |template-inheritance#Layout öröklődés layout] +| `{embed}` … `{/embed}` | [betölt egy sablont vagy blokkot és lehetővé teszi a blokkok felülírását |template-inheritance#Egység öröklődés embed] +| `{ifset blockname}` … `{/ifset}` | [feltétel, hogy létezik-e a blokk |template-inheritance#Blokkok létezésének ellenőrzése ifset] .[table-latte-tags language-latte] |## Kivételkezelés -| `{try}`... `{else}`... `{/try}` | [kivételek kezelése |#try] -| `{rollback}` | [elveti a try blokkot |#rollback] +| `{try}` … `{else}` … `{/try}` | [kivételek elkapása |#try] +| `{rollback}` | [a try blokk eldobása |#rollback] .[table-latte-tags language-latte] |## Változók -| `{var $foo = value}` | [változó létrehozása |#var-default] -| `{default $foo = value}` | [alapértelmezett érték, ha a változót nem deklarálták |#var-default] -| `{parameters}` | [változók deklarálása, alapértelmezett értékek beírása |#parameters] -| `{capture}`... `{/capture}` | [egy szakasz rögzítése egy változóhoz |#capture] +| `{var $foo = value}` | [létrehoz egy változót |#var default] +| `{default $foo = value}` | [létrehoz egy változót, ha nem létezik |#var default] +| `{parameters}` | [deklarálja a változókat, típusokat és alapértelmezett értékeket |#parameters] +| `{capture}` … `{/capture}` | [elkapja a blokkot egy változóba |#capture] .[table-latte-tags language-latte] |## Típusok -| `{varType}` | [a változó típusának deklarálása |type-system#varType] -| `{varPrint}` | [Javasolja a változók típusait |type-system#varPrint] -| `{templateType}` | [a változók típusainak deklarálása osztály használatával |type-system#templateType] -| `{templatePrint}` | [osztályt generál tulajdonságokkal |type-system#templatePrint] +| `{varType}` | [deklarálja a változó típusát |type-system#varType] +| `{varPrint}` | [javaslatot tesz a változók típusaira |type-system#varPrint] +| `{templateType}` | [deklarálja a változók típusait egy osztály szerint |type-system#templateType] +| `{templatePrint}` | [javaslatot tesz egy osztályra a változók típusaival |type-system#templatePrint] .[table-latte-tags language-latte] -|## Fordítás -| `{_string}` | [lefordítva nyomtat |#Translation] -| `{translate}`... `{/translate}` | [lefordítja a tartalmat |#Translation] +|## Fordítások +| `{_...}` | [kiírja a fordítást |#Fordítások] +| `{translate}` … `{/translate}` | [lefordítja a tartalmat |#Fordítások] .[table-latte-tags language-latte] |## Egyéb -| `{contentType}` | [átkapcsolja az escaping módot és elküldi a HTTP fejlécet |#contenttype] -| `{debugbreak}` | [töréspontot állít a kódhoz |#debugbreak] -| `{do}` | [kiértékel egy kifejezést nyomtatás nélkül |#do] -| `{dump}` | [a változókat a Tracy Bar-ba dobja ki |#dump] -| `{spaceless}`... `{/spaceless}` | [eltávolítja a felesleges szóközöket |#spaceless] -| `{syntax}` | [futás közben váltja a szintaxist |#syntax] -| `{trace}` | [megjeleníti a veremkövetést |#trace] +| `{contentType}` | [átkapcsolja az escapelést és HTTP fejlécet küld |#contentType] +| `{debugbreak}` | [breakpointet helyez el a kódban |#debugbreak] +| `{do}` | [végrehajtja a kódot, de semmit sem ír ki |#do] +| `{dump}` | [dumpolja a változókat a Tracy Bar-ba |#dump] +| `{php}` | [végrehajt bármilyen PHP kódot |#php] +| `{spaceless}` … `{/spaceless}` | [eltávolítja a felesleges szóközöket |#spaceless] +| `{syntax}` | [szintaxis váltás futás közben |#syntax] +| `{trace}` | [megjeleníti a stack trace-t |#trace] .[table-latte-tags language-latte] -|## HTML tag-segédprogramok -| `n:class` | [smart class attribútum |#n:class] -| `n:attr` | [intelligens HTML attribútumok |#n:attr] -| `n:tag` | [HTML elem dinamikus neve |#n:tag] -| `n:ifcontent` | [Üres HTML tag elhagyása |#n:ifcontent] +|## HTML kódoló segédeszközök +| `n:class` | [dinamikus HTML class attribútum írás |#n:class] +| `n:attr` | [dinamikus bármilyen HTML attribútum írás |#n:attr] +| `n:tag` | [dinamikus HTML elem név írás |#n:tag] +| `n:ifcontent` | [kihagyja az üres HTML taget |#n:ifcontent] .[table-latte-tags language-latte] -|## Csak a Nette keretrendszerben érhető el -| `n:href` | [hivatkozás a `` HTML-elemekben |application:creating-links#In the Presenter Template] -| `{link}` | [linket nyomtat |application:creating-links#In the Presenter Template] -| `{plink}` | [bemutatóra mutató link nyomtatása |application:creating-links#In the Presenter Template] -| `{control}` | [komponens nyomtatása |application:components#Rendering] -| `{snippet}`... `{/snippet}` | [AJAX-szel küldhető sablonrészlet |application:ajax#tag-snippet] -| `{snippetArea}` | snippet borítékolás -| `{cache}`... `{/cache}` | [egy sablonrészlet gyorsítótárba helyezése |caching:#caching-in-latte] +|## Csak a Nette Frameworkben érhető el +| `n:href` | [link, amelyet `` HTML elemekben használnak |application:creating-links#A presenter sablonjában] +| `{link}` | [kiír egy linket |application:creating-links#A presenter sablonjában] +| `{plink}` | [kiír egy linket egy presenterhez |application:creating-links#A presenter sablonjában] +| `{control}` | [megjelenít egy komponenst |application:components#Renderelés] +| `{snippet}` … `{/snippet}` | [kivágat, amelyet AJAX-szal lehet küldeni |application:ajax#Snippetek a Latte-ban] +| `{snippetArea}` | [burkoló kivágatokhoz |application:ajax#Snippet területek] +| `{cache}` … `{/cache}` | [gyorsítótárazza a sablon egy részét |caching:#Gyorsítótárazás Latte-ban] .[table-latte-tags language-latte] -|## Csak Nette Forms esetén érhető el -| `{form}`... `{/form}` | [nyomtat egy űrlapelemet |forms:rendering#form] -| `{label}`... `{/label}` | [nyomtat egy űrlap beviteli címkét |forms:rendering#label-input] -| `{input}` | [nyomtat egy űrlap beviteli elemet |forms:rendering#label-input] -| `{inputError}` | [hibaüzenetet nyomtat az űrlap beviteli eleméhez |forms:rendering#inputError] -| `n:name` | [HTML beviteli elem aktiválása |forms:rendering#n:name] -| `{formPrint}` | [Latte űrlap tervezetet készít |forms:rendering#formPrint] -| `{formPrintClass}` | [PHP osztály nyomtatása az űrlap adataihoz |forms:in-presenter#mapping-to-classes] -| `{formContext}`... `{/formContext}` | [részleges űrlap renderelés |forms:rendering#special-cases] +|## Csak a Nette Forms-szal érhető el +| `{form}` … `{/form}` | [megjeleníti az űrlap tageket |forms:rendering#form] +| `{label}` … `{/label}` | [megjeleníti az űrlap elem címkéjét |forms:rendering#label input] +| `{input}` | [megjeleníti az űrlap elemet |forms:rendering#label input] +| `{inputError}` | [kiírja az űrlap elem hibaüzenetét |forms:rendering#inputError] +| `n:name` | [életre kelti az űrlap elemet |forms:rendering#n:name] +| `{formContainer}` … `{/formContainer}` | [űrlap konténer rajzolása |forms:rendering#Speciális esetek] + +.[table-latte-tags language-latte] +|## Csak Nette Assets esetén elérhető +| `{asset}` | [egy eszköz HTML elemként vagy URL-ként való megjelenítése |assets:#asset] +| `{preload}` | [előbetöltési tippeket generál a teljesítmény optimalizálásához |assets:#preload]. +| `n:asset` | [eszközattribútumokat ad a HTML-elemekhez |assets:#n:asset]. -Nyomtatás .[#toc-printing] -========================== +Kiírás +====== `{$var}` `{...}` `{=...}` ------------------------- -A Latte a `{=...}` címkét használja bármely kifejezés kimeneti kiadására. Ha a kifejezés változóval vagy függvényhívással kezdődik, nem szükséges egyenlőségjelet írni. Ami a gyakorlatban azt jelenti, hogy szinte soha nem kell kiírni: +A Latte-ban a `{=...}` taget használjuk bármilyen kifejezés kiírására a kimenetre. A Latte törődik a kényelmével, így ha a kifejezés változóval vagy függvényhívással kezdődik, nem szükséges az egyenlőségjelet írni. Ami a gyakorlatban azt jelenti, hogy szinte soha nem kell írni: ```latte -Name: {$name} {$surname}
                                                                          -Age: {date('Y') - $birth}
                                                                          +Név: {$name} {$surname}
                                                                          +Kor: {date('Y') - $birth}
                                                                          ``` -Bármit leírhatsz kifejezésként, amit a PHP-ból ismersz. Csak nem kell megtanulnod egy új nyelvet. Például: +Kifejezésként bármit leírhat, amit PHP-ból ismer. Egyszerűen nem kell új nyelvet tanulnia. Így például: ```latte {='0' . ($num ?? $num * 3) . ', ' . PHP_VERSION} ``` -Kérjük, ne keressenek értelmet az előző példában, de ha találnak benne, írják meg nekünk :-) +Kérjük, ne keressen semmi értelmet az előző példában, de ha találna benne valamit, írjon nekünk :-) -Kimenet menekülése .[#toc-escaping-output] ------------------------------------------- +Kimenet escapelése +------------------ -Mi a sablonrendszer legfontosabb feladata? A biztonsági rések elkerülése. És pontosan ezt teszi a Latte, amikor valamit a kimenetre nyomtat. Automatikusan mindent elrejt: +Mi a sablonrendszer legfontosabb feladata? Megakadályozni a biztonsági réseket. És pontosan ezt teszi a Latte mindig, amikor valamit kiír. Automatikusan escapeli: ```latte -

                                                                          {='one < two'}

                                                                          {* prints: '

                                                                          one < two

                                                                          ' *} +

                                                                          {='egy < kettő'}

                                                                          {* kiírja: '

                                                                          egy < kettő

                                                                          ' *} ``` -Pontosabban, a Latte kontextusfüggő eszkópálást használ, ami olyan fontos és egyedülálló funkció, hogy [külön fejezetet |safety-first#context-aware-escaping] szenteltünk neki. +Hogy pontosak legyünk, a Latte kontextusérzékeny escapelést használ, ami olyan fontos és egyedi dolog, hogy [külön fejezetet |safety-first#Kontextusérzékeny escapelés] szenteltünk neki. -És ha megbízható forrásból származó HTML-kódolt tartalmat nyomtat? Akkor egyszerűen kikapcsolhatja az escapinget: +És mi van, ha megbízható forrásból származó HTML-kódolt tartalmat ír ki? Akkor könnyen kikapcsolható az escapelés: ```latte {$trustedHtmlString|noescape} ``` .[warning] -A `noescape` szűrő visszaélésszerű használata XSS sebezhetőséghez vezethet! Soha ne használd, hacsak nem vagy **teljesen biztos** abban, hogy mit csinálsz, és hogy a nyomtatott karakterlánc megbízható forrásból származik. +A `noescape` szűrő helytelen használata XSS sebezhetőséghez vezethet! Soha ne használja, ha nem **teljesen biztos** abban, amit csinál, és hogy a kiírt string megbízható forrásból származik. -Nyomtatás JavaScriptben .[#toc-printing-in-javascript] ------------------------------------------------------- +Kiírás JavaScriptben +-------------------- -A kontextusérzékeny escapingnek köszönhetően csodálatosan egyszerű a változók JavaScripten belüli nyomtatása, és a Latte megfelelően kikerüli őket. +A kontextusérzékeny escapelésnek köszönhetően csodálatosan egyszerű a változók kiírása JavaScripten belül, és a helyes escapelést a Latte elintézi. -A változónak nem kell sztringnek lennie, bármilyen adattípus támogatott, amit aztán JSON-ként kódol: +A változó nemcsak string lehet, bármely adattípus támogatott, amely aztán JSON-ként kódolódik: ```latte {var $foo = ['hello', true, 1]} @@ -171,7 +177,7 @@ A változónak nem kell sztringnek lennie, bármilyen adattípus támogatott, am ``` -Generál: +Generálja: ```latte ``` -Ez az oka annak is, hogy **nem szabad a változót idézőjelbe tenni**: Latte a karakterláncok köré teszi őket. Ha pedig egy stringváltozót egy másik stringbe akarsz tenni, egyszerűen kapcsold össze őket: +Ez az oka annak is, hogy a változó köré **nem írnak idézőjeleket**: a Latte a stringeknél automatikusan hozzáadja őket. És ha egy string változót egy másik stringbe szeretne beilleszteni, egyszerűen fűzze össze őket: ```latte ``` -Szűrők .[#toc-filters] ----------------------- +Szűrők +------ -A nyomtatott kifejezés [szűrőkkel |syntax#filters] módosítható. Ez a példa például a karakterláncot nagybetűvé alakítja, és legfeljebb 30 karakterre rövidíti: +A kiírt kifejezést módosíthatja [szűrővel |syntax#Szűrők]. Így például egy stringet nagybetűssé alakíthatunk és maximum 30 karakterre rövidíthetünk: ```latte {$string|upper|truncate:30} ``` -A kifejezés részeire is alkalmazhat szűrőket az alábbiak szerint: +A szűrőket a kifejezés részeire is alkalmazhatja így: ```latte {$left . ($middle|upper) . $right} ``` -Feltételek .[#toc-conditions] -============================= +Feltételek +========== `{if}` `{elseif}` `{else}` -------------------------- -A feltételek ugyanúgy viselkednek, mint a PHP megfelelőik. Használhatja ugyanazokat a kifejezéseket, amelyeket a PHP-ból ismer, nem kell új nyelvet tanulnia. +A feltételek ugyanúgy viselkednek, mint PHP megfelelőik. Használhatja bennük ugyanazokat a kifejezéseket, amelyeket PHP-ból ismer, nem kell új nyelvet tanulnia. ```latte {if $product->inStock > Stock::Minimum} - In stock + Raktáron {elseif $product->isOnWay()} - On the way + Úton {else} - Not available + Nem elérhető {/if} ``` -Mint minden páros tag, a `{if} ... {/ if}` párja is leírható például [n:attribútumként |syntax#n:attributes]: +Mint minden páros taget, úgy az `{if} ... {/if}` párt is lehet [n:attributumként |syntax#n:attribútumok] írni, például: ```latte -

                                                                          In stock {$count} items

                                                                          +

                                                                          Raktáron {$count} darab

                                                                          ``` -Tudtad, hogy az n:attribútumokhoz hozzáadhatod a `tag-` előtagot? Ekkor a feltétel csak a HTML-címkéket érinti, és a köztük lévő tartalom mindig ki lesz nyomtatva: +Tudta, hogy az n:attribútumokhoz csatolhat `tag-` előtagot? Akkor a feltétel csak a HTML tagek kiírására vonatkozik, és a köztük lévő tartalom mindig kiíródik: ```latte
                                                                          Hello -{* prints 'Hello' when $clickable is falsey *} -{* prints 'Hello' when $clickable is truthy *} +{* kiírja 'Hello', ha $clickable hamis *} +{* kiírja 'Hello', ha $clickable igaz *} +``` + +Nagyszerű. + + +`n:else` .{data-version:3.0.11} +------------------------------- + +Ha az `{if} ... {/if}` feltételt [n:attributumként |syntax#n:attribútumok] írja, lehetősége van alternatív ágat is megadni az `n:else` segítségével: + +```latte +Raktáron {$count} darab + +nem elérhető ``` -Szép. +Az `n:else` attribútumot párban is használhatja a [`n:ifset` |#ifset elseifset], [`n:foreach` |#foreach], [`n:try` |#try], [#`n:ifcontent`] és [`n:ifchanged` |#ifchanged] attribútumokkal. `{/if $cond}` ------------- -Meglepő lehet, hogy a `{if}` feltételben szereplő kifejezést a végtagban is meg lehet adni. Ez olyan helyzetekben hasznos, amikor a tag megnyitásakor még nem ismerjük a feltétel értékét. Nevezzük ezt halasztott döntésnek. +Talán meglepő, hogy az `{if}` feltételben lévő kifejezést a záró tagben is meg lehet adni. Ez olyan helyzetekben hasznos, amikor a feltétel megnyitásakor még nem ismerjük az értékét. Nevezzük ezt elhalasztott döntésnek. -Például elkezdünk listázni egy táblázatot rekordokkal az adatbázisból, és csak a jelentés befejezése után vesszük észre, hogy nem volt rekord az adatbázisban. Ezért a `{/if}` címke végére feltételt teszünk, és ha nincs rekord, akkor egyik sem kerül kiírásra: +Például elkezdünk kiírni egy táblázatot adatbázis-rekordokkal, és csak a kiírás befejezése után vesszük észre, hogy az adatbázisban nem volt rekord. Így a feltételt a `{/if}` záró tagbe tesszük, és ha nincs rekord, semmi sem íródik ki: ```latte {if} -

                                                                          Printing rows from the database

                                                                          +

                                                                          Adatbázis sorainak listája

                                                                          {foreach $resultSet as $row} @@ -264,30 +284,30 @@ Például elkezdünk listázni egy táblázatot rekordokkal az adatbázisból, {/if isset($row)} ``` -Praktikus, ugye? +Ügyes, igaz? -A `{else}` a halasztott feltételben is használható, de a `{elseif}` nem. +Az elhalasztott feltételben használható `{else}`, de `{elseif}` nem. `{ifset}` `{elseifset}` ----------------------- .[note] -Lásd még [`{ifset block}` |template-inheritance#checking-block-existence] +Lásd még [`{ifset block}` |template-inheritance#Blokkok létezésének ellenőrzése ifset] -A `{ifset $var}` feltétel segítségével meghatározhatja, hogy egy változó (vagy több változó) létezik-e, és van-e nem null értékű értéke. Ez tulajdonképpen ugyanaz, mint a `if (isset($var))` a PHP-ben. Mint minden páros tag, ez is leírható [n:attribútum |syntax#n:attributes] formában, ezért mutassuk meg példában: +Az `{ifset $var}` feltétellel megállapíthatjuk, hogy egy változó (vagy több változó) létezik-e és nem `null` értékű-e. Valójában ugyanaz, mint az `if (isset($var))` PHP-ban. Mint minden páros taget, ezt is lehet [n:attributumként |syntax#n:attribútumok] írni, mutassuk be példaként: ```latte - + ``` -`{ifchanged}` .{data-version:2.9} ---------------------------------- +`{ifchanged}` +------------- -`{ifchanged}` ellenőrzi, hogy egy változó értéke megváltozott-e a ciklus (foreach, for vagy while) utolsó iterációja óta. +Az `{ifchanged}` ellenőrzi, hogy a változó értéke megváltozott-e a ciklus (foreach, for vagy while) utolsó iterációja óta. -Ha egy vagy több változót adunk meg a címkében, akkor ellenőrzi, hogy valamelyik változott-e, és ennek megfelelően kiírja a tartalmát. A következő példa például a nevek felsorolásakor minden egyes változáskor a név első betűjét írja ki címként: +Ha a tagben egy vagy több változót adunk meg, ellenőrzi, hogy valamelyikük megváltozott-e, és ennek megfelelően írja ki a tartalmat. Például a következő példa a név első betűjét írja ki címként minden alkalommal, amikor a nevek kiírása során megváltozik: ```latte {foreach ($names|sort) as $name} @@ -297,7 +317,7 @@ Ha egy vagy több változót adunk meg a címkében, akkor ellenőrzi, hogy vala {/foreach} ``` -Ha azonban nem adunk meg argumentumot, akkor maga a megjelenített tartalom kerül ellenőrzésre a korábbi állapothoz képest. Ez azt jelenti, hogy az előző példában nyugodtan elhagyhatjuk az argumentumot a címkében. És természetesen használhatjuk az [n:attribute-ot |syntax#n:attributes] is: +Ha azonban nem adunk meg argumentumot, a megjelenített tartalmat hasonlítja össze az előző állapotával. Ez azt jelenti, hogy az előző példában nyugodtan elhagyhatjuk az argumentumot a tagben. És természetesen használhatunk [n:attributumot |syntax#n:attribútumok] is: ```latte {foreach ($names|sort) as $name} @@ -307,49 +327,49 @@ Ha azonban nem adunk meg argumentumot, akkor maga a megjelenített tartalom ker {/foreach} ``` -`{else}` záradékot is beilleszthetünk a `{ifchanged}` belsejébe. +Az `{ifchanged}`-en belül `{else}` klauzulát is meg lehet adni. `{switch}` `{case}` `{default}` ------------------------------- -Összehasonlítja az értéket több opcióval. Ez hasonló a PHP-ből ismert `switch` struktúrához. A Latte azonban továbbfejleszti: +Összehasonlítja az értéket több lehetőséggel. Ez a PHP-ból ismert `switch` feltételes utasítás megfelelője. Azonban a Latte javítja: -- szigorú összehasonlítást használ (`===`) -- nem igényel `break` +- szigorú összehasonlítást (`===`) használ +- nincs szüksége `break`-re -Tehát ez pontosan megegyezik a PHP 8.0-ban található `match` struktúrával. +Tehát pontosan megegyezik a PHP 8.0-val érkező `match` struktúrával. ```latte {switch $transport} {case train} - By train + Vonattal {case plane} - By plane + Repülővel {default} - Differently + Másképp {/switch} ``` -.{data-version:2.9} -A `{case}` záradék több, vesszővel elválasztott értéket is tartalmazhat: + +A `{case}` klauzula több, vesszővel elválasztott értéket is tartalmazhat: ```latte {switch $status} -{case $status::New}new item -{case $status::Sold, $status::Unknown}not available +{case $status::New}új tétel +{case $status::Sold, $status::Unknown}nem elérhető {/switch} ``` -Hurok .[#toc-loops] -=================== +Ciklusok +======== -A Latte-ban a PHP-ból ismert ciklusok állnak rendelkezésedre: foreach, for és while. +A Latte-ban megtalálja az összes ciklust, amelyet PHP-ból ismer: foreach, for és while. `{foreach}` ----------- -A ciklust pontosan ugyanúgy írja meg, mint a PHP-ben: +A ciklust pontosan ugyanúgy írjuk, mint PHP-ban: ```latte {foreach $langs as $code => $lang} @@ -357,11 +377,11 @@ A ciklust pontosan ugyanúgy írja meg, mint a PHP-ben: {/foreach} ``` -Ezen kívül van néhány praktikus csípése, amiről most beszélni fogunk. +Ezenkívül van néhány ügyes trükkje, amelyekről most beszélünk. -Például Latte ellenőrzi, hogy a létrehozott változók véletlenül se írják felül az azonos nevű globális változókat. Ez megmenti Önt, amikor feltételezi, hogy a `$lang` az oldal aktuális nyelve, és nem veszi észre, hogy a `foreach $langs as $lang` felülírta az adott változót. +A Latte például ellenőrzi, hogy a létrehozott változók véletlenül nem írják-e felül az azonos nevű globális változókat. Ez megmenti azokat a helyzeteket, amikor arra számít, hogy a `$lang`-ban az oldal aktuális nyelve van, és nem veszi észre, hogy a `foreach $langs as $lang` felülírta ezt a változót. -A foreach ciklus is nagyon elegánsan és gazdaságosan írható meg az [n:attribútummal |syntax#n:attributes]: +A foreach ciklust nagyon elegánsan és tömören is le lehet írni [n:attributummal |syntax#n:attribútumok]: ```latte
                                                                            @@ -369,7 +389,7 @@ A foreach ciklus is nagyon elegánsan és gazdaságosan írható meg az [n:attri
                                                                          ``` -Tudtad, hogy az n:attribútumokhoz a `inner-` előtagot is be lehet illeszteni? Most akkor csak az elem belső része fog ismétlődni a ciklusban: +Tudta, hogy az n:attribútumokhoz csatolhat `inner-` előtagot? Akkor a ciklusban csak az elem belseje ismétlődik: ```latte
                                                                          @@ -378,7 +398,7 @@ Tudtad, hogy az n:attribútumokhoz a `inner-` előtagot is be lehet illeszteni?
                                                                          ``` -Tehát valami ilyesmit ír ki: +Tehát valami ilyesmi íródik ki: ```latte
                                                                          @@ -390,17 +410,17 @@ Tehát valami ilyesmit ír ki: ``` -`{else}` .{data-version:2.9}{toc: foreach-else} ------------------------------------------------ +`{else}` .{toc: foreach-else} +----------------------------- -A `foreach` ciklushoz választható egy opcionális `{else}` záradék, amelynek szövege akkor jelenik meg, ha a megadott tömb üres: +A `foreach` cikluson belül megadhat egy `{else}` klauzulát, amelynek tartalma akkor jelenik meg, ha a ciklus üres: ```latte
                                                                            {foreach $people as $person}
                                                                          • {$person->name}
                                                                          • {else} -
                                                                          • Sorry, no users in this list
                                                                          • +
                                                                          • Sajnáljuk, ebben a listában nincsenek felhasználók
                                                                          • {/foreach}
                                                                          ``` @@ -409,17 +429,17 @@ A `foreach` ciklushoz választható egy opcionális `{else}` záradék, amelynek `$iterator` ----------- -A `foreach` cikluson belül a `$iterator` változót inicializáljuk. Az aktuális ciklusra vonatkozó fontos információkat tárolja. +A `foreach` cikluson belül a Latte létrehozza a `$iterator` változót, amely segítségével hasznos információkat tudhatunk meg a folyamatban lévő ciklusról: -- `$iterator->first` - ez az első iteráció? -- `$iterator->last` - ez az utolsó iteráció? -- `$iterator->counter` - iterációs számláló, 1-től kezdődik. -- `$iterator->counter0` - iterációs számláló, 0-ról indul. .{data-version:2.9} -- `$iterator->odd` - ez az iteráció páratlan? -- `$iterator->even` - ez az iteráció páros? -- `$iterator->parent` - az aktuális iterátort körülvevő iterátor. .{data-version:2.9} -- `$iterator->nextValue` - a ciklus következő eleme -- `$iterator->nextKey` - a ciklus következő elemének kulcsa. +- `$iterator->first` - ez az első futás a cikluson? +- `$iterator->last` - ez az utolsó futás? +- `$iterator->counter` - hányadik futás ez egytől számolva? +- `$iterator->counter0` - hányadik futás ez nullától számolva? +- `$iterator->odd` - ez páratlan futás? +- `$iterator->even` - ez páros futás? +- `$iterator->parent` - az aktuálisat körülvevő iterátor +- `$iterator->nextValue` - a következő elem a ciklusban +- `$iterator->nextKey` - a következő elem kulcsa a ciklusban ```latte @@ -435,20 +455,19 @@ A `foreach` cikluson belül a `$iterator` változót inicializáljuk. Az aktuál {/foreach} ``` -A láta okos és a `$iterator->last` nemcsak tömbök esetén működik, hanem akkor is, ha a ciklus egy általános iterátoron fut át, ahol az elemek száma nem ismert előre. +A Latte okos, és a `$iterator->last` nemcsak tömböknél működik, hanem akkor is, ha a ciklus egy általános iterátoron fut, ahol előre nem ismert az elemek száma. `{first}` `{last}` `{sep}` -------------------------- -Ezek a címkék a `{foreach}` cikluson belül használhatók. A `{first}` tartalma az első menetben kerül megjelenítésre. -A `{last}` tartalma renderelésre kerül ... kitalálod? Igen, az utolsó lépésnél. Ezek valójában a `{if $iterator->first}` és a `{if $iterator->last}` rövidítései. +Ezeket a tageket a `{foreach}` cikluson belül lehet használni. A `{first}` tartalma akkor jelenik meg, ha ez az első futás. A `{last}` tartalma akkor jelenik meg… kitalálja? Igen, ha ez az utolsó futás. Valójában ezek a `{if $iterator->first}` és `{if $iterator->last}` rövidítései. -A címkék [n:attribútumként |syntax#n:attributes] is leírhatók: +A tageket elegánsan lehet [n:attributumként |syntax#n:attribútumok] is használni: ```latte {foreach $rows as $row} - {first}

                                                                          List of names

                                                                          {/first} + {first}

                                                                          Nevek listája

                                                                          {/first}

                                                                          {$row->name}

                                                                          @@ -456,21 +475,21 @@ A címkék [n:attribútumként |syntax#n:attributes] is leírhatók: {/foreach} ``` -A `{sep}` tartalma akkor kerül megjelenítésre, ha az iteráció nem az utolsó, így alkalmas olyan elhatárolójelek, mint például a felsorolt elemek közötti vesszők kiírására: +A `{sep}` tag tartalma akkor jelenik meg, ha a futás nem az utolsó, tehát hasznos elválasztók kiírására, például vesszők a kiírt elemek között: ```latte {foreach $items as $item} {$item} {sep}, {/sep} {/foreach} ``` -Ez elég praktikus, nem igaz? +Ez elég praktikus, igaz? -`{iterateWhile}` .{data-version:2.10} -------------------------------------- +`{iterateWhile}` +---------------- -Egyszerűsíti a lineáris adatok csoportosítását a foreach ciklusban történő iteráció során azáltal, hogy az iterációt egy beágyazott ciklusban hajtja végre, amíg a feltétel teljesül. [Olvassa el az utasításokat a szakácskönyvben |cookbook/iteratewhile]. +Leegyszerűsíti a lineáris adatok csoportosítását a foreach ciklusban történő iterálás során azáltal, hogy az iterációt egy beágyazott ciklusban végzi, amíg a feltétel teljesül. [Olvassa el a részletes útmutatót|cookbook/grouping]. -Elegánsan helyettesítheti a `{first}` és a `{last}` címet is a fenti példában: +Elegánsan helyettesítheti a `{first}` és `{last}` tageket is a fenti példában: ```latte {foreach $rows as $row} @@ -487,19 +506,21 @@ Elegánsan helyettesítheti a `{first}` és a `{last}` címet is a fenti példá {/foreach} ``` +Lásd még a [batch |filters#batch] és [group |filters#group] szűrőket. + `{for}` ------- -A ciklust pontosan ugyanúgy írjuk le, mint a PHP-ben: +A ciklust pontosan ugyanúgy írjuk, mint PHP-ban: ```latte {for $i = 0; $i < 10; $i++} - Item #{$i} + Elem {$i} {/for} ``` -A címkét [n:attribútumként |syntax#n:attributes] is írhatjuk: +A taget [n:attributumként |syntax#n:attribútumok] is lehet használni: ```latte

                                                                          {$i}

                                                                          @@ -509,7 +530,7 @@ A címkét [n:attribútumként |syntax#n:attributes] is írhatjuk: `{while}` --------- -A ciklust ismét pontosan ugyanúgy írjuk le, mint a PHP-ben: +A ciklust ismét pontosan ugyanúgy írjuk, mint PHP-ban: ```latte {while $row = $result->fetch()} @@ -517,7 +538,7 @@ A ciklust ismét pontosan ugyanúgy írjuk le, mint a PHP-ben: {/while} ``` -Vagy [n:attribútumként |syntax#n:attributes]: +Vagy [n:attributumként |syntax#n:attribútumok]: ```latte @@ -525,7 +546,7 @@ Vagy [n:attribútumként |syntax#n:attributes]: ``` -A végtagban lévő feltétellel rendelkező változat megfelel a do-while ciklusnak a PHP-ban: +Lehetséges egy változat is a feltétellel a záró tagben, amely a PHP do-while ciklusának felel meg: ```latte {while} @@ -537,7 +558,7 @@ A végtagban lévő feltétellel rendelkező változat megfelel a do-while ciklu `{continueIf}` `{skipIf}` `{breakIf}` ------------------------------------- -Vannak speciális címkék, amelyekkel bármely ciklus vezérlésére használhatod - `{continueIf ?}` és `{breakIf ?}`, amelyek a feltételek teljesülése esetén a következő iterációra ugranak, illetve befejezik a kört: +Bármely ciklus vezérlésére használhatók a `{continueIf ?}` és `{breakIf ?}` tagek, amelyek a következő elemre ugranak, ill. befejezik a ciklust a feltétel teljesülésekor: ```latte {foreach $rows as $row} @@ -547,8 +568,8 @@ Vannak speciális címkék, amelyekkel bármely ciklus vezérlésére használha {/foreach} ``` -.{data-version:2.9} -A `{skipIf}` címke nagyon hasonlít a `{continueIf}` címkéjéhez, de nem növeli a számlálót. Így nem keletkeznek lyukak a számozásban, ha a `$iterator->counter` kiírásával néhány elemet kihagyunk. A {else} záradék is megjelenik, ha minden elemet kihagyunk. + +A `{skipIf}` tag nagyon hasonló a `{continueIf}`-hez, de nem növeli a `$iterator->counter` számlálót, így ha kiírjuk és közben kihagyunk néhány elemet, nem lesznek lyukak a számozásban. És az `{else}` klauzula is megjelenik, ha minden elemet kihagyunk. ```latte
                                                                            @@ -556,7 +577,7 @@ A `{skipIf}` címke nagyon hasonlít a `{continueIf}` címkéjéhez, de nem növ {skipIf $person->age < 18}
                                                                          • {$iterator->counter}. {$person->name}
                                                                          • {else} -
                                                                          • Sorry, no adult users in this list
                                                                          • +
                                                                          • Sajnáljuk, ebben a listában nincsenek felnőttek
                                                                          • {/foreach}
                                                                          ``` @@ -565,72 +586,68 @@ A `{skipIf}` címke nagyon hasonlít a `{continueIf}` címkéjéhez, de nem növ `{exitIf}` .{data-version:3.0.5} -------------------------------- -Befejezi egy sablon vagy blokk megjelenítését, ha egy feltétel teljesül (azaz "korai kilépés"). +Befejezi a sablon vagy blokk megjelenítését a feltétel teljesülésekor (ún. "early exit"). ```latte {exitIf !$messages} -

                                                                          Messages

                                                                          +

                                                                          Üzenetek

                                                                          {$message}
                                                                          ``` -Beleértve a sablonokat .[#toc-including-templates] -================================================== +Sablon beillesztése +=================== `{include 'file.latte'}` .{toc: include} ---------------------------------------- .[note] -Lásd még [`{include block}` |template-inheritance#printing-blocks] +Lásd még [`{include block}` |template-inheritance#Blokkok renderelése include] -A `{include}` címke betölti és megjeleníti a megadott sablont. Kedvenc PHP nyelvünkön ez így hangzik: +Az `{include}` tag betölti és megjeleníti a megadott sablont. Ha kedvenc nyelvünk, a PHP nyelvén beszélnénk, ez valami ilyesmi: ```php ``` -A bevont sablonok nem férnek hozzá az aktív kontextus változóihoz, de hozzáférnek a globális változókhoz. +A beillesztett sablonok nem férnek hozzá az aktív kontextus változóihoz, csak a globális változókhoz férnek hozzá. -A változókat így adhatja át: +Változókat így adhat át a beillesztett sablonnak: ```latte -{* Latte 2.9 óta *} {include 'template.latte', foo: 'bar', id: 123} - -{* a Latte 2.9 előtt *} -{include 'template.latte', foo => 'bar', id => 123} ``` -A sablon neve bármilyen PHP-kifejezés lehet: +A sablon neve bármilyen PHP kifejezés lehet: ```latte {include $someVar} {include $ajax ? 'ajax.latte' : 'not-ajax.latte'} ``` -A beillesztett tartalom [szűrőkkel |syntax#filters] módosítható. A következő példa eltávolít minden HTML-t és beállítja az esetet: +A beillesztett tartalmat módosíthatja [szűrőkkel |syntax#Szűrők]. A következő példa eltávolít minden HTML-t és módosítja a betűméretet: ```latte {include 'heading.latte' |stripHtml|capitalize} ``` -A [sablon öröklés |template inheritance] **nem vesz részt** ebben alapértelmezés szerint. Bár a bevont sablonokhoz hozzáadhat blokkcímkéket, ezek nem fogják helyettesíteni a megfelelő blokkokat abban a sablonban, amelybe bevonták őket. Gondoljon az inklúziókra úgy, mint az oldalak vagy modulok független és árnyékolt részeire. Ez a viselkedés megváltoztatható a `with blocks` módosítóval (a Latte 2.9.1 óta): +Alapértelmezés szerint a [sablonöröklődés|template-inheritance] ebben az esetben semmilyen módon nem játszik szerepet. Bár a beillesztett sablonban használhatunk blokkokat, nem történik meg a megfelelő blokkok helyettesítése abban a sablonban, amelybe beillesztjük. Gondoljon a beillesztett sablonokra mint önálló, árnyékolt oldalrészekre vagy modulokra. Ez a viselkedés megváltoztatható a `with blocks` módosítóval: ```latte {include 'template.latte' with blocks} ``` -A címkében megadott fájlnév és a lemezen lévő fájl közötti kapcsolat a [betöltő |extending-latte#Loaders] függvénye. +A tagben megadott fájlnév és a lemezen lévő fájl közötti kapcsolat a [loader|loaders] dolga. -`{sandbox}` .{data-version:2.8} -------------------------------- +`{sandbox}` +----------- -Ha egy végfelhasználó által létrehozott sablont vesz fel, fontolja meg a sandboxolást (további információ a [sandbox dokumentációban |sandbox]): +Végfelhasználó által létrehozott sablon beillesztésekor érdemes megfontolni a sandbox módot (további információk a [sandbox dokumentációjában |sandbox]): ```latte {sandbox 'untrusted.latte', level: 3, data: $menu} @@ -641,9 +658,9 @@ Ha egy végfelhasználó által létrehozott sablont vesz fel, fontolja meg a sa ========= .[note] -Lásd még [`{block name}` |template-inheritance#blocks] +Lásd még [`{block name}` |template-inheritance#Blokkok block] -A név nélküli blokkok arra szolgálnak, hogy [szűrőket |syntax#filters] alkalmazhassunk a sablon egy részére. Például alkalmazhat egy [csíkszűrőt |filters#strip] a felesleges szóközök eltávolítására: +A név nélküli blokkok arra szolgálnak, hogy [szűrőket |syntax#Szűrők] alkalmazzanak a sablon egy részére. Például így lehet alkalmazni a [strip |filters#spaceless] szűrőt, amely eltávolítja a felesleges szóközöket: ```latte {block|strip} @@ -654,16 +671,16 @@ A név nélküli blokkok arra szolgálnak, hogy [szűrőket |syntax#filters] alk ``` -Kivételkezelés .[#toc-exception-handling] -========================================= +Kivételkezelés +============== -`{try}` .{data-version:2.9} ---------------------------- +`{try}` +------- -Ez a címke rendkívül megkönnyíti a robusztus sablonok készítését. +Ennek a tagnek köszönhetően rendkívül egyszerű robusztus sablonokat létrehozni. -Ha a `{try}` blokk renderelése közben kivétel lép fel, az egész blokk elvetésre kerül, és a renderelés utána folytatódik: +Ha a `{try}` blokk megjelenítése során kivétel történik, az egész blokk eldobódik, és a megjelenítés utána folytatódik: ```latte {try} @@ -675,7 +692,7 @@ Ha a `{try}` blokk renderelése közben kivétel lép fel, az egész blokk elvet {/try} ``` -A `{else}` opcionális záradék tartalma csak akkor kerül renderelésre, ha kivétel történik: +Az opcionális `{else}` klauzula tartalma csak akkor jelenik meg, ha kivétel történik: ```latte {try} @@ -685,11 +702,11 @@ A `{else}` opcionális záradék tartalma csak akkor kerül renderelésre, ha ki {/foreach} {else} -

                                                                          Sorry, the tweets could not be loaded.

                                                                          +

                                                                          Sajnáljuk, nem sikerült betölteni a tweeteket.

                                                                          {/try} ``` -A címke [n:attribútumként |syntax#n:attributes] is írható: +A taget [n:attributumként |syntax#n:attribútumok] is lehet használni: ```latte
                                                                            @@ -697,13 +714,13 @@ A címke [n:attribútumként |syntax#n:attributes] is írható:
                                                                          ``` -Lehetőség van [saját kivételkezelő |develop#exception handler] definiálására is, pl. naplózáshoz: +Lehetőség van saját [kivételkezelő handler |develop#Exception handler] definiálására is, például naplózás céljából. -`{rollback}` .{data-version:2.9} --------------------------------- +`{rollback}` +------------ -A `{try}` blokk manuálisan is megállítható és kihagyható a `{rollback}` segítségével. Így nem kell előre ellenőrizni az összes bemeneti adatot, és csak a renderelés során lehet eldönteni, hogy van-e értelme renderelni az objektumot. +A `{try}` blokkot manuálisan is le lehet állítani és átugrani a `{rollback}` segítségével. Ennek köszönhetően nem kell előre ellenőrizni az összes bemeneti adatot, és csak a megjelenítés során dönthet úgy, hogy az objektumot egyáltalán nem szeretné megjeleníteni: ```latte {try} @@ -719,30 +736,30 @@ A `{try}` blokk manuálisan is megállítható és kihagyható a `{rollback}` se ``` -Változók .[#toc-variables] -========================== +Változók +======== `{var}` `{default}` ------------------- -A sablonban új változókat hozunk létre a `{var}` címkével: +Új változókat a sablonban a `{var}` taggel hozunk létre: ```latte {var $name = 'John Smith'} {var $age = 27} -{* Többszörös nyilatkozat *} +{* Többszörös deklaráció *} {var $name = 'John Smith', $age = 27} ``` -A `{default}` címke hasonlóan működik, azzal a különbséggel, hogy csak akkor hoz létre változókat, ha azok nem léteznek: +A `{default}` tag hasonlóan működik, de csak akkor hoz létre változókat, ha azok nem léteznek. Ha a változó már létezik és `null` értéket tartalmaz, nem íródik felül: ```latte -{default $lang = 'cs'} +{default $lang = 'hu'} ``` -A Latte 2.7-től kezdve a [változók típusait |type-system] is meg lehet adni. Egyelőre ezek tájékoztató jellegűek, és a Latte nem ellenőrzi őket. +Megadhat [változótípusokat|type-system] is. Ezek egyelőre informatívak, és a Latte nem ellenőrzi őket. ```latte {var string $name = $article->getTitle()} @@ -750,10 +767,10 @@ A Latte 2.7-től kezdve a [változók típusait |type-system] is meg lehet adni. ``` -`{parameters}` .{data-version:2.9} ----------------------------------- +`{parameters}` +-------------- -Ahogyan egy függvény deklarálja a paramétereit, úgy egy sablon is deklarálhatja a változókat az elején: +Ahogy a függvények deklarálják a paramétereiket, úgy a sablon is deklarálhatja a változóit az elején: ```latte {parameters @@ -763,15 +780,15 @@ Ahogyan egy függvény deklarálja a paramétereit, úgy egy sablon is deklarál } ``` -A `$a` és `$b` változók alapértelmezett érték nélkül automatikusan a `null` alapértelmezett értéket kapják. A deklarált típusok továbbra is tájékoztató jellegűek, és a Latte nem ellenőrzi őket. +Az `$a` és `$b` változók megadott alapértelmezett érték nélkül automatikusan `null` alapértelmezett értéket kapnak. A deklarált típusok egyelőre informatívak, és a Latte nem ellenőrzi őket. -A deklarált változókon kívül más nem kerül át a sablonba. Ez egy különbség a `{default}` címkéhez képest. +A deklaráltakon kívül más változók nem kerülnek át a sablonba. Ebben különbözik a `{default}` tagtől. `{capture}` ----------- -A `{capture}` címke használatával a kimenetet egy változóba rögzítheti: +Elkapja a kimenetet egy változóba: ```latte {capture $var} @@ -780,10 +797,10 @@ A `{capture}` címke használatával a kimenetet egy változóba rögzítheti: {/capture} -

                                                                          Captured: {$var}

                                                                          +

                                                                          Elkapott: {$var}

                                                                          ``` -A címke írható [n:attribútumként |syntax#n:attributes] is: +A taget, mint minden páros taget, [n:attributumként |syntax#n:attribútumok] is lehet írni: ```latte
                                                                            @@ -791,15 +808,17 @@ A címke írható [n:attribútumként |syntax#n:attributes] is:
                                                                          ``` +A HTML kimenet a `$var` változóba `Latte\Runtime\Html` objektumként kerül mentésre, hogy [ne történjen nem kívánt escapelés |develop#Változó automatikus escapelésének kikapcsolása] a kiíráskor. -Egyéb .[#toc-others] -==================== + +Egyéb +===== `{contentType}` --------------- -A címkével megadhatja, hogy a sablon milyen típusú tartalmat képvisel. A lehetőségek a következők: +A taggel meghatározhatja, milyen típusú tartalmat képvisel a sablon. A lehetőségek: - `html` (alapértelmezett típus) - `xml` @@ -808,9 +827,9 @@ A címkével megadhatja, hogy a sablon milyen típusú tartalmat képvisel. A le - `calendar` (iCal) - `text` -Használata azért fontos, mert ez állítja be a [kontextusfüggő escapinget |safety-first#context-aware-escaping], és csak így tud a Latte helyesen menekülni. Például a `{contentType xml}` XML módba kapcsol, a `{contentType text}` pedig teljesen kikapcsolja az escapinget. +Használata fontos, mert beállítja a [kontextusérzékeny escapelést |safety-first#Kontextusérzékeny escapelés], és csak így tud helyesen escapelni. Például a `{contentType xml}` átkapcsol XML módba, a `{contentType text}` teljesen kikapcsolja az escapelést. -Ha a paraméter egy teljes értékű MIME-típus, például `application/xml`, akkor a `Content-Type` HTTP fejlécet is elküldi a böngészőnek: +Ha a paraméter egy teljes értékű MIME típus, mint például `application/xml`, akkor még a `Content-Type` HTTP fejlécet is elküldi a böngészőnek: ```latte {contentType application/xml} @@ -829,46 +848,50 @@ Ha a paraméter egy teljes értékű MIME-típus, például `application/xml`, a `{debugbreak}` -------------- -Megadja azt a helyet, ahol a kód végrehajtása megszakad. Hibakeresési célokra szolgál, hogy a programozó ellenőrizze a futási környezetet, és biztosítsa, hogy a kód az elvárásoknak megfelelően fut. Támogatja az [Xdebugot |https://xdebug.org]. Ezenkívül megadható egy feltétel, amikor a kódnak meg kell törnie. +Jelzi azt a helyet, ahol a program futása leáll, és elindul a hibakereső, hogy a programozó megvizsgálhassa a futási környezetet, és megállapíthassa, hogy a program az elvárásoknak megfelelően működik-e. Támogatja az [Xdebug |https://xdebug.org/]-ot. Hozzáadható egy feltétel, amely meghatározza, mikor kell a programot leállítani. ```latte -{debugbreak} {* megszakítja a programot *} +{debugbreak} {* leállítja a programot *} -{debugbreak $counter == 1} {* megszakítja a programot, ha a feltétel teljesül *} +{debugbreak $counter == 1} {* leállítja a programot a feltétel teljesülésekor *} ``` `{do}` ------ -Végrehajtja a kódot, de nem ír ki semmit. +Végrehajtja a PHP kódot, és semmit sem ír ki. Mint minden más tagnél, a PHP kód egyetlen kifejezést jelent, lásd [PHP korlátozások |syntax#PHP korlátozások Latte-ban]. ```latte {do $num++} ``` -A Latte 2.7 és korábbi verziókban a `{php}` címet használták. - `{dump}` -------- -Egy változó vagy az aktuális kontextus kiürítése. +Kiírja a változót vagy az aktuális kontextust. ```latte -{dump $név} {* a $név változó kiírása *} +{dump $name} {* Kiírja a $name változót *} -{dump} {* dumps az összes definiált változót *} +{dump} {* Kiírja az összes aktuálisan definiált változót *} ``` .[caution] -A [Tracy |tracy:] csomagot igényli. +Szükséges a [Tracy|tracy:] könyvtár. + + +`{php}` +------- + +Lehetővé teszi bármilyen PHP kód végrehajtását. A taget aktiválni kell a [RawPhpExtension |develop#RawPhpExtension] bővítménnyel. `{spaceless}` ------------- -Eltávolítja a felesleges szóközöket. Hasonló a [szóköz nélküli |filters#spaceless] szűrőhöz. +Eltávolítja a felesleges fehér szóközt a kimenetből. Hasonlóan működik, mint a [spaceless |filters#spaceless] szűrő. ```latte {spaceless} @@ -878,52 +901,52 @@ Eltávolítja a felesleges szóközöket. Hasonló a [szóköz nélküli |filter {/spaceless} ``` -Kimenetek: +Generálja ```latte
                                                                          • Hello
                                                                          ``` -A címke [n:attribútumként |syntax#n:attributes] is írható: +A taget [n:attributumként |syntax#n:attribútumok] is lehet írni. `{syntax}` ---------- -A latte címkéket nem kell csak szimpla szögletes zárójelek közé zárni. Választhat más elválasztójelet is, akár futás közben is. Erre szolgál a `{syntax…}`, ahol a paraméter lehet: +A Latte tageknek nem kell csak egyszerű kapcsos zárójelekkel határolva lenniük. Választhatunk más elválasztót is, akár futás közben is. Erre szolgál a `{syntax …}`, ahol paraméterként megadható: - double: `{{...}}` -- off: teljesen kikapcsolja a Latte címkéket +- off: teljesen kikapcsolja a Latte tagek feldolgozását -Az n:attribútum jelölés használatával csak egy JavaScript blokk esetében tilthatjuk le a Latte-et: +Az n:attribútumok használatával kikapcsolhatja a Latte-t például csak egy JavaScript blokkra: ```latte ``` -A Latte nagyon kényelmesen használható JavaScript-en belül, csak kerüljük az olyan konstrukciókat, mint ebben a példában, ahol a betű közvetlenül a `{` után következik, lásd: [Latte JavaScripten vagy CSS-en belül |recipes#Latte inside JavaScript or CSS]. +A Latte nagyon kényelmesen használható JavaScripten belül is, csak kerülni kell az olyan konstrukciókat, mint ebben a példában, amikor a `{` jelet közvetlenül betű követi, lásd [Latte JavaScripten vagy CSS-en belül |recipes#Latte JavaScripten vagy CSS-en belül]. -Ha a `{syntax off}` (azaz tag, nem az n:attribútum) segítségével kapcsolja ki a Latte-t, akkor szigorúan figyelmen kívül hagyja az összes taget a `{/syntax}`-ig. +Ha a Latte-t a `{syntax off}` segítségével kapcsolja ki (azaz taggel, nem n:attribútummal), akkor következetesen figyelmen kívül hagyja az összes taget a `{/syntax}`-ig. -{trace} .{data-version:2.10} ----------------------------- +{trace} +------- -Dob egy `Latte\RuntimeException` kivételt, amelynek stack trace-je a sablonok szellemében van. Így függvények és metódusok hívása helyett blokkok hívását és sablonok beillesztését foglalja magában. Ha a dobott kivételek egyértelmű megjelenítésére szolgáló eszközt, például a [Tracy-t |tracy:] használja, akkor jól láthatóvá válik a hívási verem, beleértve az összes átadott argumentumot is. +Kivált egy `Latte\RuntimeException` kivételt, amelynek stack trace-je a sablonok szellemében van. Tehát a függvény- és metódushívások helyett blokkok hívását és sablonok beillesztését tartalmazza. Ha olyan eszközt használ a kiváltott kivételek áttekinthető megjelenítésére, mint például a [Tracy|tracy:], áttekinthetően megjelenik a hívási verem, beleértve az összes átadott argumentumot. -HTML-tag-segédprogramok .[#toc-html-tag-helpers] -================================================ +HTML kódoló segédeszközök +========================= -n:class .[#toc-n-class] ------------------------ +n:class +------- -A `n:class` segítségével nagyon könnyen létrehozható a `class` HTML-attribútum pontosan a kívánt módon. +Az `n:class` segítségével nagyon egyszerűen generálhat HTML `class` attribútumot pontosan az elképzelések szerint. -Példa: Az aktív elemnek a `active` osztállyal kell rendelkeznie: +Példa: szükségem van arra, hogy az aktív elemnek `active` osztálya legyen: ```latte {foreach $items as $item} @@ -931,7 +954,7 @@ Példa: Az aktív elemnek a `active` osztállyal kell rendelkeznie: {/foreach} ``` -Továbbá az első elemnek a `first` és a `main` osztályokkal kell rendelkeznie: +Továbbá, hogy az első elemnek `first` és `main` osztálya legyen: ```latte {foreach $items as $item} @@ -939,7 +962,7 @@ Továbbá az első elemnek a `first` és a `main` osztályokkal kell rendelkezni {/foreach} ``` -És minden elemnek a `list-item` osztállyal kell rendelkeznie: +És minden elemnek legyen `list-item` osztálya: ```latte {foreach $items as $item} @@ -947,13 +970,13 @@ Továbbá az első elemnek a `first` és a `main` osztályokkal kell rendelkezni {/foreach} ``` -Elképesztően egyszerű, nem igaz? +Csodálatosan egyszerű, igaz? -n:attr .[#toc-n-attr] ---------------------- +n:attr +------ -A `n:attr` attribútum tetszőleges HTML-attribútumokat generálhat, ugyanolyan eleganciával, mint az [n:class |#n:class]. +Az `n:attr` attribútum ugyanolyan eleganciával tud generálni bármilyen HTML attribútumot, mint a [#n:class]. ```latte {foreach $data as $item} @@ -961,7 +984,7 @@ A `n:attr` attribútum tetszőleges HTML-attribútumokat generálhat, ugyanolyan {/foreach} ``` -A visszaadott értékektől függően megjeleníti pl: +A visszaadott értékektől függően kiírja pl.: ```latte @@ -972,26 +995,28 @@ A visszaadott értékektől függően megjeleníti pl: ``` -n:tag .[#toc-n-tag] -------------------- +n:tag +----- -A `n:tag` attribútum dinamikusan megváltoztathatja egy HTML-elem nevét. +Az `n:tag` attribútum dinamikusan tudja megváltoztatni a HTML elem nevét. ```latte

                                                                          {$title}

                                                                          ``` -Ha a `$heading === null`, a `

                                                                          ` címke változatlanul kiírásra kerül. Ellenkező esetben az elem neve a változó értékére változik, így a `$heading === 'h3'` esetében azt írja ki: +Ha `$heading === null`, akkor változatlanul a `

                                                                          ` tag íródik ki. Ellenkező esetben az elem neve a változó értékére változik, tehát `$heading === 'h3'` esetén kiíródik: ```latte

                                                                          ...

                                                                          ``` +Mivel a Latte egy biztonságos sablonrendszer, ellenőrzi, hogy az új tagnév érvényes-e, és nem tartalmaz-e nem kívánt vagy káros értékeket. -n:ifcontent .[#toc-n-ifcontent] -------------------------------- -Megakadályozza, hogy egy üres HTML-elem kiírásra kerüljön, azaz egy olyan elem, amely csak szóközöket tartalmaz. +n:ifcontent +----------- + +Megakadályozza, hogy üres HTML elem íródjon ki, azaz olyan elem, amely semmit sem tartalmaz a szóközökön kívül. ```latte
                                                                          @@ -999,7 +1024,7 @@ Megakadályozza, hogy egy üres HTML-elem kiírásra kerüljön, azaz egy olyan
                                                                          ``` -A `$error` változó értékeitől függően ez kiíródik: +A `$error` változó értékétől függően írja ki: ```latte {* $error = '' *} @@ -1013,42 +1038,42 @@ A `$error` változó értékeitől függően ez kiíródik: ``` -Fordítás .[#toc-translation] -============================ +Fordítások +========== -Ahhoz, hogy a fordítási címkék működjenek, be kell állítania a [fordítót |develop#TranslatorExtension]. Használhatja a [`translate` |filters#translate] szűrőt a fordításhoz. +Ahhoz, hogy a fordítási tagek működjenek, [aktiválni kell a fordítót |develop#TranslatorExtension]. A fordításhoz használhatja a [`translate` |filters#translate] szűrőt is. `{_...}` -------- -Lefordítja az értékeket más nyelvekre. +Értékeket fordít más nyelvekre. ```latte -{_'Basket'} +{_'Kosár'} {_$item} ``` -A fordítónak más paraméterek is átadhatók: +A fordítónak további paramétereket is át lehet adni: ```latte -{_'Basket', domain: order} +{_'Kosár', domain: order} ``` `{translate}` ------------- -Překládá části šablony: +Lefordítja a sablon részeit: ```latte -

                                                                          {translate}Order{/translate}

                                                                          +

                                                                          {translate}Rendelés{/translate}

                                                                          {translate domain: order}Lorem ipsum ...{/translate} ``` -[attribútumként |syntax#n:attributes] is írható, hogy lefordítsuk az elem belsejét: +A taget [n:attributumként |syntax#n:attribútumok] is lehet írni, az elem belsejének fordításához: ```latte -

                                                                          Order

                                                                          +

                                                                          Rendelés

                                                                          ``` diff --git a/latte/hu/template-inheritance.texy b/latte/hu/template-inheritance.texy index 7ed8e1e0ec..7248f7163b 100644 --- a/latte/hu/template-inheritance.texy +++ b/latte/hu/template-inheritance.texy @@ -1,20 +1,20 @@ -Sablon öröklődés és újrafelhasználhatóság -***************************************** +Sablonok öröklődése és újrafelhasználhatósága +********************************************* .[perex] -A sablonok újrafelhasználhatósága és az öröklési mechanizmusok azért vannak itt, hogy növeljék a termelékenységet, mivel minden sablon csak az egyedi tartalmát tartalmazza, és az ismétlődő elemek és struktúrák újrafelhasználásra kerülnek. Három fogalmat mutatunk be: az [elrendezés öröklést |#layout inheritance], a [horizontális újrafelhasználást |#horizontal reuse] és az [egység öröklést |#unit inheritance]. +A sablonok újrafelhasználási és öröklődési mechanizmusai növelik a termelékenységet, mivel minden sablon csak az egyedi tartalmát tartalmazza, az ismétlődő elemek és struktúrák pedig újrafelhasználhatók. Három koncepciót mutatunk be: [elrendezés öröklődés |#Layout öröklődés layout], [horizontális újrafelhasználás |#Horizontális újrafelhasználás import] és [egység öröklődés |#Egység öröklődés embed]. -A Latte sablon öröklés koncepciója hasonló a PHP osztály örökléshez. Meghatározunk egy **szülő sablont**, amelyből más **gyermeksablonok** kiterjeszthetők, és felülírhatják a szülő sablon egyes részeit. Nagyszerűen működik, ha az elemek közös szerkezetűek. Bonyolultnak hangzik? Ne aggódjon, nem az. +A Latte sablon öröklődési koncepciója hasonló a PHP osztályöröklődéséhez. Definiál egy **szülő sablont**, amelytől más **gyermek sablonok** örökölhetnek, és felülírhatják a szülő sablon részeit. Ez nagyszerűen működik, amikor az elemek közös struktúrán osztoznak. Bonyolultnak hangzik? Ne aggódjon, nagyon egyszerű. -Layout öröklődés `{layout}` .{toc: Layout Inheritance} -====================================================== +Layout öröklődés `{layout}` +=========================== -Nézzük meg az elrendezési sablon öröklődését egy példával kezdve. Ez egy szülő sablon, amelyet például `layout.latte` -nak fogunk hívni, és egy HTML vázdokumentumot definiál. +Nézzük meg a layout sablon öröklődését, azaz a layoutot, egy példával. Ez egy szülő sablon, amelyet például `layout.latte`-nak nevezünk, és amely meghatározza a HTML dokumentum vázát: ```latte - + {block title}{/block} @@ -30,36 +30,36 @@ Nézzük meg az elrendezési sablon öröklődését egy példával kezdve. Ez e ``` -A `{block}` címkék három blokkot határoznak meg, amelyeket a gyermek sablonok kitölthetnek. A blokkcímke csak annyit tesz, hogy közli a sablonmotorral, hogy a gyermek sablonok felülbírálhatják a sablon ezen részeit a saját, azonos nevű blokkjuk meghatározásával. +A `{block}` tagek három blokkot definiálnak, amelyeket a gyermek sablonok kitölthetnek. A `block` tag csak annyit tesz, hogy jelzi, hogy ezt a helyet a gyermek sablon felülírhatja egy saját, azonos nevű blokk definiálásával. Egy gyermek sablon így nézhet ki: ```latte {layout 'layout.latte'} -{block title}My amazing blog{/block} +{block title}Az én csodálatos blogom{/block} {block content} -

                                                                          Welcome to my awesome homepage.

                                                                          +

                                                                          Üdvözöllek a fantasztikus honlapomon.

                                                                          {/block} ``` -A `{layout}` címke itt a kulcs. Azt mondja a sablonmotornak, hogy ez a sablon egy másik sablont "bővít". Amikor a Latte rendereli ezt a sablont, először megkeresi a szülő sablont - ebben az esetben a `layout.latte`. +A kulcs itt a `{layout}` tag. Ez közli a Latte-val, hogy ez a sablon „kiterjeszt“ egy másik sablont. Amikor a Latte rendereli ezt a sablont, először megtalálja a szülő sablont - ebben az esetben a `layout.latte`-t. -Ekkor a sablonmotor észreveszi a `layout.latte` három blokkcímkét, és ezeket a blokkokat a gyermek sablon tartalmával helyettesíti. Vegye figyelembe, hogy mivel a gyermek sablon nem definiálta a *footer* blokkot, helyette a szülő sablon tartalmát használja. A szülő sablon `{block}` címkén belüli tartalom mindig tartalékként kerül felhasználásra. +Ezen a ponton a Latte észreveszi a három blokk taget a `layout.latte`-ban, és ezeket a blokkokat a gyermek sablon tartalmával helyettesíti. Mivel a gyermek sablon nem definiálta a `footer` blokkot, helyette a szülő sablon tartalma kerül felhasználásra. A `{block}` tag tartalma a szülő sablonban mindig tartalékként használatos. A kimenet így nézhet ki: ```latte - + - My amazing blog + Az én csodálatos blogom
                                                                          -

                                                                          Welcome to my awesome homepage.

                                                                          +

                                                                          Üdvözöllek a fantasztikus honlapomon.

                                                                          ``` - -{{leftbar: /@left-menu}} diff --git a/latte/it/cookbook/migration-from-twig.texy b/latte/it/cookbook/migration-from-twig.texy index d3630705bd..1f2e1cce4e 100644 --- a/latte/it/cookbook/migration-from-twig.texy +++ b/latte/it/cookbook/migration-from-twig.texy @@ -2,37 +2,37 @@ Migrazione da Twig a Latte ************************** .[perex] -State migrando un progetto scritto in Twig al più moderno Latte? Abbiamo uno strumento che facilita la migrazione. [Provatelo online |https://twig2latte.nette.org]. +State convertendo un progetto scritto in Twig al più moderno Latte? Abbiamo uno strumento per voi che faciliterà la migrazione. [Provalo online |https://fiddle.nette.org/twig2latte/]. -È possibile scaricare lo strumento da [GitHub |https://github.com/nette/latte-tools] o installarlo utilizzando Composer: +Potete scaricare lo strumento da [GitHub|https://github.com/nette/latte-tools] o installarlo tramite Composer: ```shell composer create-project latte/tools ``` -Il convertitore non usa semplici sostituzioni di espressioni regolari, ma utilizza direttamente il parser di Twig, in modo da poter gestire qualsiasi sintassi complessa. +Il convertitore non utilizza semplici sostituzioni tramite espressioni regolari, ma sfrutta direttamente il parser Twig, quindi può gestire sintassi complesse di qualsiasi tipo. -Per la conversione da Twig a Latte viene utilizzato lo script `twig-to-latte.php`: +Per la conversione da Twig a Latte si usa lo script `twig-to-latte.php`: ```shell twig-to-latte.php input.twig.html [output.latte] ``` -Conversione .[#toc-conversion] ------------------------------- +Conversione +----------- -La conversione richiede la modifica manuale del risultato, poiché non può essere effettuata in modo univoco. Twig usa la sintassi dei punti, dove `{{ a.b }}` può significare `$a->b`, `$a['b']` o `$a->getB()`, che non possono essere distinti durante la compilazione. Il convertitore converte quindi tutto in `$a->b`. +La conversione presuppone una modifica manuale del risultato, poiché la conversione non può essere eseguita in modo univoco. Twig utilizza la sintassi a punti, dove `{{ a.b }}` può significare `$a->b`, `$a['b']` o `$a->getB()`, il che non può essere distinto durante la compilazione. Il convertitore converte quindi tutto in `$a->b`. -Alcune funzioni, filtri o tag non hanno un equivalente in Latte, o possono comportarsi in modo leggermente diverso. +Alcune funzioni, filtri o tag non hanno un equivalente in Latte, o potrebbero comportarsi in modo leggermente diverso. -Esempio .[#toc-example] ------------------------ +Esempio +------- -Il file di input potrebbe assomigliare a questo: +Il file di input può assomigliare a questo: -```latte +```twig {% use "blocks.twig" %} @@ -54,7 +54,7 @@ Il file di input potrebbe assomigliare a questo: ``` -Dopo la conversione in Latte, otteniamo questo modello: +Dopo la conversione in Latte, otteniamo questo template: ```latte {import 'blocks.latte'} @@ -77,5 +77,3 @@ Dopo la conversione in Latte, otteniamo questo modello: ``` - -{{leftbar: /@left-menu}} diff --git a/latte/it/cookbook/passing-variables.texy b/latte/it/cookbook/passing-variables.texy new file mode 100644 index 0000000000..d7b58e30a1 --- /dev/null +++ b/latte/it/cookbook/passing-variables.texy @@ -0,0 +1,158 @@ +Passaggio di Variabili tra Template +*********************************** + +Questa guida spiega come le variabili vengono passate tra i template in Latte utilizzando vari tag come `{include}`, `{import}`, `{embed}`, `{layout}`, `{sandbox}` e altri. Imparerete anche come lavorare con le variabili nei tag `{block}` e `{define}`, e a cosa serve il tag `{parameters}`. + + +Tipi di Variabili +----------------- +Le variabili in Latte possono essere divise in tre categorie a seconda di come e dove sono definite: + +**Variabili di input** sono quelle passate al template dall'esterno, ad esempio da uno script PHP o tramite un tag come `{include}`. + +```php +$latte->render('template.latte', ['userName' => 'Jan', 'userAge' => 30]); +``` + +**Variabili d'ambiente** sono variabili esistenti nel punto di un certo tag. Includono tutte le variabili di input e altre variabili create usando tag come `{var}`, `{default}` o all'interno di un ciclo `{foreach}`. + +```latte +{foreach $users as $user} + {include 'userBox.latte', user: $user} +{/foreach} +``` + +**Variabili esplicite** sono quelle specificate direttamente all'interno di un tag e inviate al template di destinazione. + +```latte +{include 'userBox.latte', name: $user->name, age: $user->age} +``` + + +`{block}` +--------- +Il tag `{block}` viene utilizzato per definire blocchi di codice riutilizzabili che possono essere personalizzati o estesi nei template ereditati. Le variabili d'ambiente definite prima del blocco sono disponibili all'interno del blocco, ma qualsiasi modifica alle variabili si rifletterà solo all'interno di quel blocco. + +```latte +{var $foo = 'originale'} +{block example} + {var $foo = 'modificato'} +{/block} + +{$foo} // stampa: originale +``` + + +`{define}` +---------- +Il tag `{define}` serve per creare blocchi che vengono renderizzati solo dopo essere stati chiamati tramite `{include}`. Le variabili disponibili all'interno di questi blocchi dipendono dal fatto che siano specificati parametri nella definizione. Se sì, hanno accesso solo a questi parametri. Se no, hanno accesso a tutte le variabili di input del template in cui sono definiti i blocchi. + +```latte +{define hello} + {* ha accesso a tutte le variabili di input del template *} +{/define} + +{define hello $name} + {* ha accesso solo al parametro $name *} +{/define} +``` + + +`{parameters}` +-------------- +Il tag `{parameters}` serve per dichiarare esplicitamente le variabili di input attese all'inizio del template. In questo modo è facile documentare le variabili attese e i loro tipi di dati. È anche possibile definire valori predefiniti. + +```latte +{parameters int $age, string $name = 'sconosciuto'} +

                                                                          Età: {$age}, Nome: {$name}

                                                                          +``` + + +`{include file}` +---------------- +Il tag `{include file}` serve per inserire un intero template. A questo template vengono passate sia le variabili di input del template in cui viene utilizzato il tag, sia le variabili definite esplicitamente in esso. Il template di destinazione può però limitare lo scope usando `{parameters}`. + +```latte +{include 'profile.latte', userId: $user->id} +``` + + +`{include block}` +----------------- +Quando si include un blocco definito nello stesso template, vengono passate ad esso tutte le variabili d'ambiente ed esplicitamente definite: + +```latte +{define blockName} +

                                                                          Nome: {$name}, Età: {$age}

                                                                          +{/define} + +{var $name = 'Jan', $age = 30} +{include blockName} +``` + +In questo esempio, le variabili `$name` e `$age` vengono passate al blocco `blockName`. Allo stesso modo si comporta anche `{include parent}`. + +Quando si include un blocco da un altro template, vengono passate solo le variabili di input ed esplicitamente definite. Le variabili d'ambiente non sono automaticamente disponibili. + +```latte +{include blockInOtherTemplate, name: $name, age: $age} +``` + + +`{layout}` o `{extends}` +------------------------ +Questi tag definiscono il layout a cui vengono passate le variabili di input del template figlio e inoltre le variabili create nel codice prima dei blocchi: + +```latte +{layout 'layout.latte'} +{var $seo = 'index, follow'} +``` + +Template `layout.latte`: + +```latte + + + +``` + + +`{embed}` +--------- +Il tag `{embed}` è simile al tag `{include}`, ma consente l'inserimento di blocchi nel template. A differenza di `{include}`, vengono passate solo le variabili dichiarate esplicitamente: + +```latte +{embed 'menu.latte', items: $menuItems} +{/embed} +``` + +In questo esempio, il template `menu.latte` ha accesso solo alla variabile `$items`. + +Al contrario, nei blocchi all'interno di `{embed}` c'è accesso a tutte le variabili d'ambiente: + +```latte +{var $name = 'Jan'} +{embed 'menu.latte', items: $menuItems} + {block foo} + {$name} + {/block} +{/embed} +``` + + +`{import}` +---------- +Il tag `{import}` viene utilizzato per caricare blocchi da altri template. Vengono trasferite sia le variabili di input che quelle dichiarate esplicitamente ai blocchi importati. + +```latte +{import 'buttons.latte'} +``` + + +`{sandbox}` +----------- +Il tag `{sandbox}` isola il template per un'elaborazione sicura. Le variabili vengono passate esclusivamente in modo esplicito. + +```latte +{sandbox 'secure.latte', data: $secureData} +``` diff --git a/latte/it/cookbook/slim-framework.texy b/latte/it/cookbook/slim-framework.texy index 1cd1396e41..3da6dc567a 100644 --- a/latte/it/cookbook/slim-framework.texy +++ b/latte/it/cookbook/slim-framework.texy @@ -2,42 +2,42 @@ Utilizzo di Latte con Slim 4 **************************** .[perex] -Questo articolo scritto da "Daniel Opitz":https://odan.github.io/2022/04/06/slim4-latte.html descrive come utilizzare Latte con il framework Slim. +Questo articolo, il cui autore è "Daniel Opitz":https://odan.github.io/2022/04/06/slim4-latte.html, descrive l'uso di Latte con Slim Framework. -Per prima cosa, "installate il framework Slim":https://odan.github.io/2019/11/05/slim4-tutorial.html e poi Latte usando Composer: +Per prima cosa, "installa Slim Framework":https://odan.github.io/2019/11/05/slim4-tutorial.html e poi Latte tramite Composer: ```shell composer require latte/latte ``` -Configurazione .[#toc-configuration] ------------------------------------- +Configurazione +-------------- -Creare una nuova cartella `templates` nella cartella principale del progetto. Tutti i modelli saranno collocati lì in seguito. +Nella directory radice del progetto, crea una nuova directory `templates`. Tutti i template verranno posizionati lì successivamente. -Aggiungere una nuova chiave di configurazione `template` nel file `config/defaults.php`: +Nel file `config/defaults.php` aggiungi una nuova chiave di configurazione `template`: ```php $settings['template'] = __DIR__ . '/../templates'; ``` -Latte compila i modelli in codice PHP nativo e li memorizza in una cache sul disco. In questo modo sono veloci come se fossero stati scritti in PHP nativo. +Latte compila i template in codice PHP nativo e li salva in una cache su disco. Sono quindi veloci quanto se fossero scritti in linguaggio PHP nativo. -Aggiungete una nuova chiave di configurazione `template_temp` nel vostro file `config/defaults.php`: Assicuratevi che la cartella `{project}/tmp/templates` esista e abbia i permessi di accesso in lettura e scrittura. +Nel file `config/defaults.php` aggiungi una nuova chiave di configurazione `template_temp`: Assicurati che la directory `{project}/tmp/templates` esista e abbia i permessi di lettura e scrittura. ```php $settings['template_temp'] = __DIR__ . '/../tmp/templates'; ``` -Latte rigenera automaticamente la cache ogni volta che si cambia il modello, cosa che può essere disattivata nell'ambiente di produzione per risparmiare un po' di prestazioni: +Latte rigenera automaticamente la cache ogni volta che il template viene modificato, il che può essere disabilitato in ambiente di produzione per risparmiare un po' di prestazioni: ```php -// cambiare in false nell'ambiente di produzione +// in ambiente di produzione, cambiare in false $settings['template_auto_refresh'] = true; ``` -Quindi, aggiungere le definizioni del contenitore DI per la classe `Latte\Engine`. +Successivamente, aggiungi la definizione del container DI per la classe `Latte\Engine`. ```php +
                                                                            {foreach $items as $item}
                                                                          • {$item|capitalize}
                                                                          • {/foreach}
                                                                          ``` -Se tutto è configurato correttamente, si dovrebbe vedere il seguente output: +Se tutto è configurato correttamente, dovrebbe essere visualizzato il seguente output: ```latte One @@ -155,4 +155,3 @@ Three ``` {{priority: -1}} -{{leftbar: /@left-menu}} diff --git a/latte/it/creating-extension.texy b/latte/it/creating-extension.texy deleted file mode 100644 index dbe795e62b..0000000000 --- a/latte/it/creating-extension.texy +++ /dev/null @@ -1,579 +0,0 @@ -Creare un'estensione -******************** - -.[perex]{data-version:3.0} -Un'estensione è una classe riutilizzabile che può definire tag personalizzati, filtri, funzioni, provider, ecc. - -Creiamo estensioni quando vogliamo riutilizzare le nostre personalizzazioni di Latte in progetti diversi o condividerle con altri. -È anche utile creare un'estensione per ogni progetto web, che conterrà tutti i tag e i filtri specifici che si vogliono usare nei modelli di progetto. - - -Classe di estensione .[#toc-extension-class] -============================================ - -L'estensione è una classe che eredita da [api:Latte\Extension]. Viene registrata con Latte usando `addExtension()` (o tramite il [file di configurazione |application:configuration#Latte]): - -```php -$latte = new Latte\Engine; -$latte->addExtension(new MyLatteExtension); -``` - -Se si registrano più estensioni e queste definiscono tag, filtri o funzioni con nomi identici, vince l'ultima estensione aggiunta. Questo implica anche che le estensioni possono sovrascrivere tag/filtri/funzioni nativi. - -Ogni volta che si apporta una modifica a una classe e l'aggiornamento automatico non è disattivato, Latte ricompila automaticamente i modelli. - -Una classe può implementare uno dei seguenti metodi: - -```php -abstract class Extension -{ - /** - * Initializes before template is compiler. - */ - public function beforeCompile(Engine $engine): void; - - /** - * Returns a list of parsers for Latte tags. - * @return array - */ - public function getTags(): array; - - /** - * Returns a list of compiler passes. - * @return array - */ - public function getPasses(): array; - - /** - * Returns a list of |filters. - * @return array - */ - public function getFilters(): array; - - /** - * Returns a list of functions used in templates. - * @return array - */ - public function getFunctions(): array; - - /** - * Returns a list of providers. - * @return array - */ - public function getProviders(): array; - - /** - * Returns a value to distinguish multiple versions of the template. - */ - public function getCacheKey(Engine $engine): mixed; - - /** - * Initializes before template is rendered. - */ - public function beforeRender(Template $template): void; -} -``` - -Per avere un'idea dell'aspetto dell'estensione, dare un'occhiata al built-in "CoreExtension":https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php. - - -beforeCompile(Latte\Engine $engine): void .[method] ---------------------------------------------------- - -Richiamato prima della compilazione del template. Il metodo può essere usato per le inizializzazioni legate alla compilazione, ad esempio. - - -getTags(): array .[method] --------------------------- - -Richiamato quando il template viene compilato. Restituisce un array associativo *nome tag => callable*, che sono [funzioni di parsing dei tag |#Tag Parsing Function]. - -```php -public function getTags(): array -{ - return [ - 'foo' => [FooNode::class, 'create'], - 'bar' => [BarNode::class, 'create'], - 'n:baz' => [NBazNode::class, 'create'], - // ... - ]; -} -``` - -Il tag `n:baz` rappresenta un attributo n:puro, cioè un tag che può essere scritto solo come attributo. - -Nel caso dei tag `foo` e `bar`, Latte riconosce automaticamente se si tratta di coppie e, in tal caso, può scriverli automaticamente usando n:attributes, comprese le varianti con i prefissi `n:inner-foo` e `n:tag-foo`. - -L'ordine di esecuzione di tali n:attributi è determinato dal loro ordine nell'array restituito da `getTags()`. Pertanto, `n:foo` viene sempre eseguito prima di `n:bar`, anche se gli attributi sono elencati in ordine inverso nel tag HTML come `
                                                                          `. - -Se è necessario determinare l'ordine di n:attributi tra più estensioni, si può usare il metodo di aiuto `order()`, dove il parametro `before` xor `after` determina quali tag sono ordinati prima o dopo il tag. - -```php -public function getTags(): array -{ - return [ - 'foo' => self::order([FooNode::class, 'create'], before: 'bar')] - 'bar' => self::order([BarNode::class, 'create'], after: ['block', 'snippet'])] - ]; -} -``` - - -getPasses(): array .[method] ----------------------------- - -Viene richiamato quando il template viene compilato. Restituisce un array associativo *nome-pass => callable*, che sono funzioni che rappresentano i cosiddetti [passaggi del compilatore |#compiler passes] che attraversano e modificano l'AST. - -Anche in questo caso, si può usare il metodo helper `order()`. Il valore dei parametri `before` o `after` può essere `*` con il significato di prima/dopo tutto. - -```php -public function getPasses(): array -{ - return [ - 'optimize' => [Passes::class, 'optimizePass'], - 'sandbox' => self::order([$this, 'sandboxPass'], before: '*'), - // ... - ]; -} -``` - - -beforeRender(Latte\Engine $engine): void .[method] --------------------------------------------------- - -Viene richiamato prima di ogni rendering del template. Il metodo può essere usato, ad esempio, per inizializzare le variabili utilizzate durante il rendering. - - -getFilters(): array .[method] ------------------------------ - -Viene richiamato prima che il template sia reso. Restituisce i [filtri |extending-latte#filters] come array associativo *nome filtro => callable*. - -```php -public function getFilters(): array -{ - return [ - 'batch' => [$this, 'batchFilter'], - 'trim' => [$this, 'trimFilter'], - // ... - ]; -} -``` - - -getFunctions(): array .[method] -------------------------------- - -Viene richiamato prima che il modello sia reso. Restituisce le [funzioni |extending-latte#functions] come array associativo *nome funzione => callable*. - -```php -public function getFunctions(): array -{ - return [ - 'clamp' => [$this, 'clampFunction'], - 'divisibleBy' => [$this, 'divisibleByFunction'], - // ... - ]; -} -``` - - -getProviders(): array .[method] -------------------------------- - -Viene richiamato prima che il template sia reso. Restituisce un array di fornitori, che di solito sono oggetti che usano i tag in fase di esecuzione. Vi si accede tramite `$this->global->...`. - -```php -public function getProviders(): array -{ - return [ - 'myFoo' => $this->foo, - 'myBar' => $this->bar, - // ... - ]; -} -``` - - -getCacheKey(Latte\Engine $engine): mixed .[method] --------------------------------------------------- - -Viene richiamato prima che il template venga reso. Il valore di ritorno diventa parte della chiave il cui hash è contenuto nel nome del file del template compilato. Pertanto, per valori di ritorno diversi, Latte genererà file di cache diversi. - - -Come funziona Latte? .[#toc-how-does-latte-work] -================================================ - -Per capire come definire tag personalizzati o passaggi del compilatore, è essenziale capire come funziona Latte sotto il cofano. - -La compilazione dei template in Latte funziona semplicisticamente in questo modo: - -- Per prima cosa, il **lexer** tokenizza il codice sorgente del template in piccoli pezzi (tokens) per facilitarne l'elaborazione. -- Poi, il **parser** converte il flusso di token in un albero di nodi significativo (l'Abstract Syntax Tree, AST). -- Infine, il compilatore **genera** una classe PHP dall'AST che rende il template e lo mette in cache. - -In realtà, la compilazione è un po' più complicata. Latte **ha due** lexer e parser: uno per il modello HTML e uno per il codice PHP all'interno dei tag. Inoltre, il parsing non viene eseguito dopo la tokenizzazione, ma il lexer e il parser vengono eseguiti in parallelo in due "thread" e si coordinano. Si tratta di scienza missilistica :-) - -Inoltre, tutti i tag hanno le proprie routine di parsing. Quando il parser incontra un tag, chiama la sua funzione di parsing (restituisce [Extension::getTags() |#getTags]). -Il suo compito è analizzare gli argomenti del tag e, nel caso di tag accoppiati, il contenuto interno. Restituisce un *nodo* che diventa parte dell'AST. Per maggiori dettagli, vedere la [funzione di parsing dei tag |#Tag parsing function]. - -Quando il parser finisce il suo lavoro, abbiamo un AST completo che rappresenta il template. Il nodo radice è `Latte\Compiler\Nodes\TemplateNode`. I singoli nodi all'interno dell'albero rappresentano non solo i tag, ma anche gli elementi HTML, i loro attributi, le espressioni utilizzate all'interno dei tag, ecc. - -A questo punto, entrano in gioco i cosiddetti [Compiler pass |#Compiler passes], che sono funzioni (restituite da [Extension::getPasses() |#getPasses]) che modificano l'AST. - -L'intero processo, dal caricamento del contenuto del modello, al parsing, fino alla generazione del file risultante, può essere messo in sequenza con questo codice, che si può sperimentare e scaricare i risultati intermedi: - -```php -$latte = new Latte\Engine; -$source = $latte->getLoader()->getContent($file); -$ast = $latte->parse($source); -$latte->applyPasses($ast); -$code = $latte->generate($ast, $file); -``` - - -Esempio di AST .[#toc-example-of-ast] -------------------------------------- - -Per avere un'idea più precisa dell'AST, aggiungiamo un esempio. Questo è il modello sorgente: - -```latte -{foreach $category->getItems() as $item} -
                                                                        • {$item->name|upper}
                                                                        • - {else} - no items found -{/foreach} -``` - -E questa è la sua rappresentazione sotto forma di AST: - -/--pre -Latte\Compiler\Nodes\TemplateNode( - Latte\Compiler\Nodes\FragmentNode( - - Latte\Essential\Nodes\ForeachNode( - expression: Latte\Compiler\Nodes\Php\Expression\MethodCallNode( - object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$category') - name: Latte\Compiler\Nodes\Php\IdentifierNode('getItems') - ) - value: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') - content: Latte\Compiler\Nodes\FragmentNode( - - Latte\Compiler\Nodes\TextNode(' ') - - Latte\Compiler\Nodes\Html\ElementNode('li')( - content: Latte\Essential\Nodes\PrintNode( - expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode( - object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') - name: Latte\Compiler\Nodes\Php\IdentifierNode('name') - ) - modifier: Latte\Compiler\Nodes\Php\ModifierNode( - filters: - - Latte\Compiler\Nodes\Php\FilterNode('upper') - ) - ) - ) - ) - else: Latte\Compiler\Nodes\FragmentNode( - - Latte\Compiler\Nodes\TextNode('no items found') - ) - ) - ) -) -\-- - - -Tag personalizzati .[#toc-custom-tags] -====================================== - -Per definire un nuovo tag sono necessari tre passaggi: - -- definizione della [funzione di parsing del tag |#tag parsing function] (responsabile del parsing del tag in un nodo) -- creazione di una classe nodo (responsabile della [generazione del codice PHP |#generating PHP code] e dell'[attraversamento dell'AST |#AST traversing]) -- registrare il tag usando [Extension::getTags() |#getTags] - - -Funzione di parsing del tag .[#toc-tag-parsing-function] --------------------------------------------------------- - -L'analisi dei tag è gestita dalla funzione di parsing (quella restituita da [Extension::getTags() |#getTags]). Il suo compito è quello di analizzare e controllare qualsiasi argomento all'interno del tag (utilizza TagParser per farlo). -Inoltre, se il tag è una coppia, chiederà a TemplateParser di analizzare e restituire il contenuto interno. -La funzione crea e restituisce un nodo, di solito figlio di `Latte\Compiler\Nodes\StatementNode`, che diventa parte dell'AST. - -Creiamo una classe per ogni nodo, cosa che faremo ora, e inseriamo elegantemente la funzione di parsing in essa come factory statica. Come esempio, proviamo a creare il noto tag `{foreach}`: - -```php -use Latte\Compiler\Nodes\StatementNode; - -class ForeachNode extends StatementNode -{ - // una funzione di parsing che per ora crea solo un nodo - public static function create(Latte\Compiler\Tag $tag): self - { - $node = new self; - return $node; - } - - public function print(Latte\Compiler\PrintContext $context): string - { - // il codice sarà aggiunto in seguito - } - - public function &getIterator(): \Generator - { - // il codice sarà aggiunto in seguito - } -} -``` - -[api:Latte\Compiler\TagParser] `$tag->parser`Alla funzione di parsing `create()` viene passato un oggetto [api:Latte\Compiler\Tag], che contiene informazioni di base sul tag (se si tratta di un tag classico o di un n:attributo, su quale riga si trova, ecc. - -Se il tag deve avere degli argomenti, si controlla la loro esistenza chiamando `$tag->expectArguments()`. I metodi dell'oggetto `$tag->parser` sono disponibili per analizzarli: - -- `parseExpression(): ExpressionNode` per un'espressione simile a quella di PHP (per esempio `10 + 3`) -- `parseUnquotedStringOrExpression(): ExpressionNode` per un'espressione o una stringa non quotata -- `parseArguments(): ArrayNode` contenuto dell'array (ad es. `10, true, foo => bar`) -- `parseModifier(): ModifierNode` per un modificatore (ad es. `|upper|truncate:10`) -- `parseType(): expressionNode` per un suggerimento di tipo (ad es. `int|string` o `Foo\Bar[]`) - -e un [api:Latte\Compiler\TokenStream] di basso livello che opera direttamente con i token: - -- `$tag->parser->stream->consume(...): Token` -- `$tag->parser->stream->tryConsume(...): ?Token` - -Latte estende la sintassi di PHP in piccoli modi, ad esempio aggiungendo modificatori, operatori ternari abbreviati o permettendo di scrivere semplici stringhe alfanumeriche senza virgolette. Per questo motivo si usa il termine *PHP-like* invece di PHP. Così, il metodo `parseExpression()` analizza `foo` come `'foo'`, per esempio. -Inoltre, *stringa non quotata* è un caso speciale di stringa che non ha bisogno di essere quotata, ma allo stesso tempo non ha bisogno di essere alfanumerica. Ad esempio, è il percorso di un file nel tag `{include ../file.latte}`. Per analizzarla si usa il metodo `parseUnquotedStringOrExpression()`. - -.[note] -Studiare le classi di nodi che fanno parte di Latte è il modo migliore per imparare tutti i dettagli del processo di parsing. - -Torniamo al tag `{foreach}`. In esso ci aspettiamo argomenti della forma `expression + 'as' + second expression`, che analizziamo come segue: - -```php -use Latte\Compiler\Nodes\StatementNode; -use Latte\Compiler\Nodes\Php\ExpressionNode; -use Latte\Compiler\Nodes\AreaNode; - -class ForeachNode extends StatementNode -{ - public ExpressionNode $expression; - public ExpressionNode $value; - - public static function create(Latte\Compiler\Tag $tag): self - { - $tag->expectArguments(); - $node = new self; - $node->expression = $tag->parser->parseExpression(); - $tag->parser->stream->consume('as'); - $node->value = $parser->parseExpression(); - return $node; - } -} -``` - -Le espressioni che abbiamo scritto nelle variabili `$expression` e `$value` rappresentano dei sottonodi. - -.[tip] -Definire le variabili con i sottonodi come **pubbliche**, in modo che possano essere modificate in [ulteriori fasi di elaborazione |#Compiler Passes], se necessario. È anche necessario **renderle disponibili** per l'[attraversamento |#AST Traversing]. - -Per i tag accoppiati, come il nostro, il metodo deve anche permettere a TemplateParser di analizzare il contenuto interno del tag. Questo viene gestito da `yield`, che restituisce una coppia ''[contenuto interno, tag finale]''. Il contenuto interno viene memorizzato nella variabile `$node->content`. - -```php -public AreaNode $content; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $endTag] = yield; - return $node; -} -``` - -La parola chiave `yield` fa terminare il metodo `create()`, restituendo il controllo al TemplateParser, che continua ad analizzare il contenuto finché non raggiunge il tag finale. A questo punto, passa il controllo a `create()`, che continua da dove si era interrotto. L'uso del metodo `yield`, restituisce automaticamente `Generator`. - -Si può anche passare a `yield` un array di nomi di tag per i quali si vuole interrompere l'analisi se si verificano prima del tag finale. Questo ci aiuta a implementare il costrutto `{foreach}...{else}...{/foreach}` . Se si verifica `{else}`, si analizza il contenuto dopo di esso in `$node->elseContent`: - -```php -public AreaNode $content; -public ?AreaNode $elseContent = null; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $nextTag] = yield ['else']; - if ($nextTag?->name === 'else') { - [$node->elseContent] = yield; - } - - return $node; -} -``` - -Il nodo di ritorno completa l'analisi dei tag. - - -Generazione del codice PHP .[#toc-generating-php-code] ------------------------------------------------------- - -Ogni nodo deve implementare il metodo `print()`. Restituisce il codice PHP che rende la parte data del template (codice di runtime). Viene passato come parametro un oggetto [api:Latte\Compiler\PrintContext], che ha un utile metodo `format()` che semplifica l'assemblaggio del codice risultante. - -Il metodo `format(string $mask, ...$args)` accetta i seguenti segnaposto nella maschera: -- `%node` stampa Nodo -- `%dump` esporta il valore in PHP -- `%raw` inserisce il testo direttamente senza alcuna trasformazione -- `%args` stampa ArrayNode come argomenti della chiamata di funzione -- `%line` stampa un commento con un numero di riga -- `%escape(...)` esegue l'escape del contenuto -- `%modify(...)` applica un modificatore -- `%modifyContent(...)` applica un modificatore ai blocchi - - -La nostra funzione `print()` potrebbe avere questo aspetto (trascuriamo il ramo `else` per semplicità): - -```php -public function print(Latte\Compiler\PrintContext $context): string -{ - return $context->format( - <<<'XX' - foreach (%node as %node) %line { - %node - } - - XX, - $this->expression, - $this->value, - $this->position, - $this->content, - ); -} -``` - -La variabile `$this->position` è già definita dalla classe [api:Latte\Compiler\Node] e viene impostata dal parser. Contiene un oggetto [api:Latte\Compiler\Position] con la posizione del tag nel codice sorgente sotto forma di numero di riga e di colonna. - -Il codice di runtime può utilizzare variabili ausiliarie. Per evitare collisioni con le variabili usate dal template stesso, è convenzione prefissarle con i caratteri `$ʟ__`. - -Può anche utilizzare valori arbitrari in fase di esecuzione, che vengono passati al template sotto forma di provider utilizzando il metodo [Extension::getProviders() |#getProviders]. Si accede ad essi usando `$this->global->...`. - - -Attraversamento dell'AST .[#toc-ast-traversing] ------------------------------------------------ - -Per attraversare l'albero AST in profondità, è necessario implementare il metodo `getIterator()`. Questo metodo consente di accedere ai sottonodi: - -```php -public function &getIterator(): \Generator -{ - yield $this->expression; - yield $this->value; - yield $this->content; - if ($this->elseContent) { - yield $this->elseContent; - } -} -``` - -Si noti che `getIterator()` restituisce un riferimento. Questo è ciò che consente ai visitatori dei nodi di sostituire i singoli nodi con altri nodi. - -.[warning] -Se un nodo ha dei sottonodi, è necessario implementare questo metodo e rendere disponibili tutti i sottonodi. In caso contrario, si potrebbe creare una falla nella sicurezza. Ad esempio, la modalità sandbox non sarebbe in grado di controllare i sottonodi e di garantire che in essi non vengano richiamati costrutti non consentiti. - -Poiché la parola chiave `yield` deve essere presente nel corpo del metodo anche se non ha nodi figli, scriverlo come segue: - -```php -public function &getIterator(): \Generator -{ - if (false) { - yield; - } -} -``` - - -Il compilatore passa .[#toc-compiler-passes] -============================================ - -I passi del compilatore sono funzioni che modificano gli AST o raccolgono informazioni in essi. Sono restituiti dal metodo [Extension::getPasses() |#getPasses]. - - -Traverser di nodi .[#toc-node-traverser] ----------------------------------------- - -Il modo più comune di lavorare con l'AST è quello di utilizzare un [api:Latte\Compiler\NodeTraverser]: - -```php -use Latte\Compiler\Node; -use Latte\Compiler\NodeTraverser; - -$ast = (new NodeTraverser)->traverse( - $ast, - enter: fn(Node $node) => ..., - leave: fn(Node $node) => ..., -); -``` - -La funzione *enter* (cioè visitatore) viene chiamata quando si incontra per la prima volta un nodo, prima che i suoi sottonodi vengano elaborati. La funzione *leave* viene chiamata dopo che tutti i sottonodi sono stati visitati. -Uno schema comune è che *enter* viene usato per raccogliere alcune informazioni e poi *leave* esegue modifiche in base a queste. Nel momento in cui viene chiamata la funzione *leave*, tutto il codice all'interno del nodo sarà già stato visitato e saranno state raccolte le informazioni necessarie. - -Come modificare l'AST? Il modo più semplice è cambiare semplicemente le proprietà dei nodi. Il secondo modo è quello di sostituire interamente il nodo, restituendo un nuovo nodo. Esempio: il codice seguente cambierà tutti gli interi nell'AST in stringhe (ad esempio, 42 sarà cambiato in `'42'`). - -```php -use Latte\Compiler\Nodes\Php; - -$ast = (new NodeTraverser)->traverse( - $ast, - leave: function (Node $node) { - if ($node instanceof Php\Scalar\IntegerNode) { - return new Php\Scalar\StringNode((string) $node->value); - } - }, -); -``` - -Un AST può facilmente contenere migliaia di nodi, e la loro esplorazione può essere lenta. In alcuni casi, è possibile evitare una traversata completa. - -Se si cercano tutti i `Html\ElementNode` in un albero, si sa che una volta visto `Php\ExpressionNode`, non ha senso controllare anche tutti i suoi nodi figli, perché l'HTML non può essere contenuto nelle espressioni. In questo caso, si può istruire il traverser a non ricorrervi all'interno del nodo della classe: - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Php\ExpressionNode) { - return NodeTraverser::DontTraverseChildren; - } - // ... - }, -); -``` - -Se si cerca solo un nodo specifico, è anche possibile interrompere completamente l'attraversamento dopo averlo trovato. - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Nodes\ParametersNode) { - return NodeTraverser::StopTraversal; - } - // ... - }, -); -``` - - -Aiutanti dei nodi .[#toc-node-helpers] --------------------------------------- - -La classe [api:Latte\Compiler\NodeHelpers] fornisce alcuni metodi che possono trovare nodi AST che soddisfano un certo callback, ecc. Vengono mostrati un paio di esempi: - -```php -use Latte\Compiler\NodeHelpers; - -// trova tutti i nodi degli elementi HTML -$elements = NodeHelpers::find($ast, fn(Node $node) => $node instanceof Nodes\Html\ElementNode); - -// Trova il primo nodo di testo -$text = NodeHelpers::findFirst($ast, fn(Node $node) => $node instanceof Nodes\TextNode); - -// converte il nodo valore PHP in valore reale -$value = NodeHelpers::toValue($node); - -// converte il nodo testuale statico in stringa -$text = NodeHelpers::toText($node); -``` diff --git a/latte/it/custom-filters.texy b/latte/it/custom-filters.texy new file mode 100644 index 0000000000..3dc2bad81a --- /dev/null +++ b/latte/it/custom-filters.texy @@ -0,0 +1,231 @@ +Creazione di filtri personalizzati +********************************** + +.[perex] +I filtri sono potenti strumenti per formattare e modificare i dati direttamente nei template Latte. Offrono una sintassi pulita utilizzando il simbolo della pipe (`|`) per trasformare variabili o risultati di espressioni nel formato di output desiderato. + + +Cosa sono i filtri? +=================== + +I filtri in Latte sono essenzialmente **funzioni PHP progettate specificamente per trasformare un valore di input in un valore di output**. Si applicano utilizzando la notazione con la pipe (`|`) all'interno delle espressioni del template (`{...}`). + +**Comodità:** I filtri consentono di incapsulare comuni attività di formattazione (come formattazione di date, modifica del case, troncamento) o manipolazione dei dati in unità riutilizzabili. Invece di ripetere complesso codice PHP nei tuoi template, puoi semplicemente applicare un filtro: +```latte +{* Invece di complesso PHP per troncare: *} +{$article->text|truncate:100} + +{* Invece di codice per formattare la data: *} +{$event->startTime|date:'Y-m-d H:i'} + +{* Applicazione di più trasformazioni: *} +{$product->name|lower|capitalize} +``` + +**Leggibilità:** L'uso dei filtri rende i template più chiari e più focalizzati sulla presentazione, poiché la logica di trasformazione viene spostata nella definizione del filtro. + +**Sensibilità al contesto:** Un vantaggio chiave dei filtri in Latte è la loro capacità di essere [sensibili al contesto |#Filtri contestuali]. Ciò significa che un filtro può riconoscere il tipo di contenuto con cui sta lavorando (HTML, JavaScript, testo semplice, ecc.) e applicare la logica o l'escaping corrispondente, il che è fondamentale per la sicurezza e la correttezza, specialmente quando si genera HTML. + +**Integrazione con la logica dell'applicazione:** Come le funzioni personalizzate, il callable PHP dietro un filtro può essere una closure, un metodo statico o un metodo di istanza. Ciò consente ai filtri di accedere ai servizi o ai dati dell'applicazione, se necessario, anche se il loro scopo principale rimane la *trasformazione del valore di input*. + +Latte fornisce di default un ricco set di [filtri standard |filters]. I filtri personalizzati ti consentono di estendere questo set con formattazioni e trasformazioni specifiche per il tuo progetto. + +Se hai bisogno di eseguire logica basata su *più* input o non hai un valore primario da trasformare, è probabilmente più appropriato usare una [funzione personalizzata |custom-functions]. Se hai bisogno di generare markup complesso o controllare il flusso del template, considera un [tag personalizzato |custom-tags]. + + +Creazione e registrazione dei filtri +==================================== + +Esistono diversi modi per definire e registrare filtri personalizzati in Latte. + + +Registrazione diretta tramite `addFilter()` +------------------------------------------- + +Il modo più semplice per aggiungere un filtro è usare il metodo `addFilter()` direttamente sull'oggetto `Latte\Engine`. Specifichi il nome del filtro (come verrà usato nel template) e il callable PHP corrispondente. + +```php +$latte = new Latte\Engine; + +// Filtro semplice senza argomenti +$latte->addFilter('initial', fn(string $s): string => mb_substr($s, 0, 1) . '.'); + +// Filtro con argomento opzionale +$latte->addFilter('shortify', function (string $s, int $len = 10): string { + return mb_substr($s, 0, $len); +}); + +// Filtro che elabora un array +$latte->addFilter('sum', fn(array $numbers): int|float => array_sum($numbers)); +``` + +**Uso nel template:** + +```latte +{$name|initial} {* Stampa 'J.' se $name è 'John' *} +{$description|shortify} {* Usa la lunghezza predefinita di 10 *} +{$description|shortify:50} {* Usa la lunghezza di 50 *} +{$prices|sum} {* Stampa la somma degli elementi nell'array $prices *} +``` + +**Passaggio degli argomenti:** + +Il valore a sinistra della pipe (`|`) viene sempre passato come *primo* argomento alla funzione del filtro. Qualsiasi parametro specificato dopo i due punti (`:`) nel template viene passato come argomenti successivi. + +```latte +{$text|shortify:30} +// Chiama la funzione PHP shortify($text, 30) +``` + + +Registrazione tramite estensione +-------------------------------- + +Per una migliore organizzazione, specialmente quando si creano set di filtri riutilizzabili o li si condivide come pacchetti, il modo consigliato è registrarli all'interno di un'[estensione Latte |extending-latte#Latte Extension]: + +```php +namespace App\Latte; + +use Latte\Extension; + +class MyLatteExtension extends Extension +{ + public function getFilters(): array + { + return [ + 'initial' => $this->initial(...), + 'shortify' => $this->shortify(...), + ]; + } + + public function initial(string $s): string + { + return mb_substr($s, 0, 1) . '.'; + } + + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// Registrazione +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +Questo approccio mantiene la logica del tuo filtro incapsulata e la registrazione semplice. + + +Utilizzo del caricatore di filtri +--------------------------------- + +Latte consente di registrare un caricatore di filtri tramite `addFilterLoader()`. Si tratta di un unico callable che Latte richiederà per qualsiasi nome di filtro sconosciuto durante la compilazione. Il caricatore restituisce il callable del filtro PHP o `null`. + +```php +$latte = new Latte\Engine; + +// Il caricatore può creare/ottenere dinamicamente callable di filtri +$latte->addFilterLoader(function (string $name): ?callable { + if ($name === 'myLazyFilter') { + // Immagina qui un'inizializzazione costosa... + $service = get_some_expensive_service(); + return fn($value) => $service->process($value); + } + return null; +}); +``` + +Questo metodo era principalmente destinato al caricamento pigro di filtri con un'inizializzazione molto **costosa**. Tuttavia, le pratiche moderne di dependency injection gestiscono solitamente i servizi pigri in modo più efficiente. + +I caricatori di filtri aggiungono complessità e generalmente non sono raccomandati a favore della registrazione diretta tramite `addFilter()` o all'interno di un'estensione tramite `getFilters()`. Utilizza i caricatori solo se hai una ragione seria e specifica legata a problemi di prestazioni nell'inizializzazione dei filtri che non possono essere risolti altrimenti. + + +Filtri che utilizzano una classe con attributi +---------------------------------------------- + +Un altro modo elegante per definire i filtri è utilizzare metodi nella tua [classe dei parametri del template |develop#Parametri come Classe]. Basta aggiungere l'attributo `#[Latte\Attributes\TemplateFilter]` al metodo. + +```php +use Latte\Attributes\TemplateFilter; + +class TemplateParameters +{ + public function __construct( + public string $description, + // altri parametri... + ) {} + + #[TemplateFilter] + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// Passaggio dell'oggetto al template +$params = new TemplateParameters(description: '...'); +$latte->render('template.latte', $params); +``` + +Latte riconoscerà e registrerà automaticamente i metodi contrassegnati con questo attributo quando l'oggetto `TemplateParameters` viene passato al template. Il nome del filtro nel template sarà lo stesso del nome del metodo (`shortify` in questo caso). + +```latte +{* Uso del filtro definito nella classe dei parametri *} +{$description|shortify:50} +``` + + +Filtri contestuali +================== + +A volte un filtro necessita di più informazioni del semplice valore di input. Potrebbe aver bisogno di conoscere il **tipo di contenuto** della stringa con cui sta lavorando (ad esempio HTML, JavaScript, testo semplice) o addirittura di modificarlo. Questa è la situazione per i filtri contestuali. + +Un filtro contestuale è definito come un filtro normale, ma il suo **primo parametro deve essere** tipizzato come `Latte\Runtime\FilterInfo`. Latte riconosce automaticamente questa firma e passa l'oggetto `FilterInfo` quando chiama il filtro. I parametri successivi ricevono gli argomenti del filtro come di consueto. + +```php +use Latte\Runtime\FilterInfo; +use Latte\ContentType; + +$latte->addFilter('money', function (FilterInfo $info, float $amount): string { + // 1. Controlla il tipo di contenuto di input (opzionale, ma raccomandato) + // Consenti null (input variabile) o testo semplice. Rifiuta se applicato a HTML ecc. + if (!in_array($info->contentType, [null, ContentType::Text], true)) { + $actualType = $info->contentType ?? 'mixed'; + throw new \RuntimeException( + "Filtro |money usato in un tipo di contenuto incompatibile $actualType. Atteso text o null." + ); + } + + // 2. Esegui la trasformazione + $formatted = number_format($amount, 2, '.', ',') . ' EUR'; + $htmlOutput = '' . htmlspecialchars($formatted) . ''; // Assicurati un escaping corretto! + + // 3. Dichiara il tipo di contenuto di output + $info->contentType = ContentType::Html; + + // 4. Restituisci il risultato + return $htmlOutput; +}); +``` + +`$info->contentType` è una costante stringa da `Latte\ContentType` (ad esempio `ContentType::Html`, `ContentType::Text`, `ContentType::JavaScript`, ecc.) o `null` se il filtro è applicato a una variabile (`{$var|filter}`). Puoi **leggere** questo valore per controllare il contesto di input e **scriverci** per dichiarare il tipo di contesto di output. + +Impostando il tipo di contenuto su HTML, comunichi a Latte che la stringa restituita dal tuo filtro è HTML sicuro. Latte quindi **non** applicherà il suo escaping automatico predefinito a questo risultato. Questo è fondamentale se il tuo filtro genera markup HTML. + +.[warning] +Se il tuo filtro genera HTML, **sei responsabile del corretto escaping di qualsiasi dato di input** utilizzato in questo HTML (come nel caso della chiamata `htmlspecialchars($formatted)` sopra). L'omissione può creare vulnerabilità XSS. Se il tuo filtro restituisce solo testo semplice, non è necessario impostare `$info->contentType`. + + +Filtri sui blocchi +------------------ + +Tutti i filtri applicati ai [blocchi |tags#block] *devono* essere contestuali. Questo perché il contenuto del blocco ha un tipo di contenuto definito (solitamente HTML), di cui il filtro deve essere consapevole. + +```latte +{block heading|money}1000{/block} +{* Il filtro 'money' riceverà '1000' come secondo argomento + e $info->contentType sarà ContentType::Html *} +``` + +I filtri contestuali forniscono un forte controllo su come i dati vengono elaborati in base al loro contesto, consentendo funzionalità avanzate e garantendo un comportamento di escaping corretto, specialmente durante la generazione di contenuto HTML. diff --git a/latte/it/custom-functions.texy b/latte/it/custom-functions.texy new file mode 100644 index 0000000000..5a018a5310 --- /dev/null +++ b/latte/it/custom-functions.texy @@ -0,0 +1,144 @@ +Creazione di funzioni personalizzate +************************************ + +.[perex] +Aggiungi facilmente funzioni di aiuto personalizzate ai template Latte. Chiama la logica PHP direttamente nelle espressioni per calcoli, accesso ai servizi o generazione di contenuto dinamico, mantenendo i tuoi template puliti e potenti. + + +Cosa sono le funzioni? +====================== + +Le funzioni in Latte ti consentono di estendere il set di funzioni che possono essere chiamate all'interno delle espressioni nei template (`{...}`). Puoi pensarle come **funzioni PHP personalizzate disponibili solo all'interno dei tuoi template Latte**. Ciò porta diversi vantaggi: + +**Comodità:** Puoi definire logica di aiuto (come calcoli, formattazione o accesso ai dati dell'applicazione) e chiamarla usando una sintassi di funzione semplice e familiare direttamente nel template, proprio come chiameresti `strlen()` o `date()` in PHP. + +```latte +{var $userInitials = initials($userName)} {* ad es. 'J. D.' *} + +{if hasPermission('article', 'edit')} + Modifica +{/if} +``` + +**Nessun inquinamento dello spazio globale:** A differenza della definizione di una vera funzione globale in PHP, le funzioni Latte esistono solo nel contesto del rendering del template. Non devi appesantire lo spazio dei nomi globale di PHP con helper specifici solo per i template. + +**Integrazione con la logica dell'applicazione:** Il callable PHP dietro una funzione Latte può essere qualsiasi cosa – una funzione anonima, un metodo statico o un metodo di istanza. Ciò significa che le tue funzioni nei template possono accedere facilmente ai servizi dell'applicazione, database, configurazione o qualsiasi altra logica necessaria catturando variabili (nel caso di funzioni anonime) o usando la dependency injection (nel caso di oggetti). L'esempio `hasPermission` sopra lo dimostra chiaramente, quando probabilmente chiama in background un servizio di autorizzazione. + +**Sovrascrittura di funzioni native (opzionale):** Puoi persino definire una funzione Latte con lo stesso nome di una funzione PHP nativa. Nel template, verrà chiamata la tua versione personalizzata invece della funzione originale. Questo può essere utile per fornire un comportamento specifico per il template o garantire un'elaborazione coerente (ad esempio, assicurando che `strlen` sia sempre sicuro per multibyte). Usa questa funzione con cautela per evitare malintesi. + +Di default, Latte consente la chiamata di *tutte* le funzioni PHP native (a meno che non siano limitate dalla [Sandbox |sandbox]). Le funzioni personalizzate estendono questa libreria integrata con le esigenze specifiche del tuo progetto. + +Se stai solo trasformando un singolo valore, potrebbe essere più appropriato usare un [filtro personalizzato |custom-filters]. + + +Creazione e registrazione delle funzioni +======================================== + +Similmente ai filtri, esistono diversi modi per definire e registrare funzioni personalizzate. + + +Registrazione diretta tramite `addFunction()` +--------------------------------------------- + +Il metodo più semplice è usare `addFunction()` sull'oggetto `Latte\Engine`. Specifichi il nome della funzione (come apparirà nel template) e il callable PHP corrispondente. + +```php +$latte = new Latte\Engine; + +// Semplice funzione di aiuto +$latte->addFunction('initials', function (string $name): string { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; +}); +``` + +**Uso nel template:** + +```latte +{var $userInitials = initials($userName)} +``` + +Gli argomenti della funzione nel template vengono passati direttamente al callable PHP nello stesso ordine. Le funzionalità PHP come type hint, valori predefiniti e parametri variabili (`...`) funzionano come previsto. + + +Registrazione tramite estensione +-------------------------------- + +Per una migliore organizzazione e riutilizzabilità, registra le funzioni all'interno di un'[estensione Latte |extending-latte#Latte Extension]. Questo approccio è raccomandato per applicazioni più complesse o librerie condivise. + +```php +namespace App\Latte; + +use Latte\Extension; +use Nette\Security\Authorizator; + +class MyLatteExtension extends Extension +{ + public function __construct( + // Supponiamo che il servizio Authorizator esista + private Authorizator $authorizator, + ) { + } + + public function getFunctions(): array + { + // Registrazione di metodi come funzioni Latte + return [ + 'hasPermission' => $this->hasPermission(...), + ]; + } + + public function hasPermission(string $resource, string $action): bool + { + return $this->authorizator->isAllowed($resource, $action); + } +} + +// Registrazione (supponiamo che $container contenga un container DI) +$extension = $container->getByType(App\Latte\MyLatteExtension::class); +$latte = new Latte\Engine; +$latte->addExtension($extension); +``` + +Questo approccio illustra come le funzioni definite in Latte possano essere supportate da metodi di oggetti, che possono avere le proprie dipendenze gestite dal container di dependency injection della tua applicazione o da una factory. Ciò mantiene la logica dei tuoi template collegata al nucleo dell'applicazione, preservando al contempo un'organizzazione chiara. + + +Funzioni che utilizzano una classe con attributi +------------------------------------------------ + +Come i filtri, le funzioni possono essere definite come metodi nella tua [classe dei parametri del template |develop#Parametri come Classe] usando l'attributo `#[Latte\Attributes\TemplateFunction]`. + +```php +use Latte\Attributes\TemplateFunction; + +class TemplateParameters +{ + public function __construct( + public string $userName, + // altri parametri... + ) {} + + // Questo metodo sarà disponibile come {initials(...)} nel template + #[TemplateFunction] + public function initials(string $name): string + { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; + } +} + +// Passaggio dell'oggetto al template +$params = new TemplateParameters(userName: 'John Doe', /* ... */); +$latte->render('template.latte', $params); +``` + +Latte scoprirà e registrerà automaticamente i metodi contrassegnati con questo attributo quando l'oggetto dei parametri viene passato al template. Il nome della funzione nel template corrisponde al nome del metodo. + +```latte +{* Uso della funzione definita nella classe dei parametri *} +{var $inits = initials($userName)} +``` + +**Funzioni contestuali?** + +A differenza dei filtri, non esiste un concetto diretto di "funzioni contestuali" che riceverebbero un oggetto simile a `FilterInfo`. Le funzioni operano all'interno delle espressioni e tipicamente non necessitano di accesso diretto al contesto di rendering o alle informazioni sul tipo di contenuto nello stesso modo dei filtri applicati ai blocchi. diff --git a/latte/it/custom-tags.texy b/latte/it/custom-tags.texy new file mode 100644 index 0000000000..40b065cc9a --- /dev/null +++ b/latte/it/custom-tags.texy @@ -0,0 +1,1135 @@ +Creazione di tag personalizzati +******************************* + +.[perex] +Questa pagina fornisce una guida completa per la creazione di tag personalizzati in Latte. Discuteremo di tutto, dai tag semplici a scenari più complessi con contenuto nidificato e specifiche esigenze di parsing, basandoci sulla vostra comprensione di come Latte compila i template. + +I tag personalizzati forniscono il massimo livello di controllo sulla sintassi del template e sulla logica di rendering, ma sono anche il punto di estensione più complesso. Prima di decidere di creare un tag personalizzato, considerate sempre se [non esista una soluzione più semplice |extending-latte#Modi per estendere Latte] o se un tag adatto non esista già nel [set standard |tags]. Utilizzate i tag personalizzati solo quando le alternative più semplici non sono sufficienti per le vostre esigenze. + + +Comprensione del processo di compilazione +========================================= + +Per creare efficacemente tag personalizzati, è utile spiegare come Latte elabora i template. Comprendere questo processo chiarisce perché i tag sono strutturati in questo modo e come si inseriscono nel contesto più ampio. + +La compilazione di un template in Latte, in modo semplificato, include questi passaggi chiave: + +1. **Analisi lessicale:** Il lexer legge il codice sorgente del template (file `.latte`) e lo divide in una sequenza di piccole parti distinte chiamate **token** (ad es. `{`, `foreach`, `$variable`, `}`, testo HTML, ecc.). +2. **Parsing:** Il parser prende questo flusso di token e costruisce da esso una struttura ad albero significativa che rappresenta la logica e il contenuto del template. Questo albero è chiamato **Abstract Syntax Tree (AST)**. +3. **Passaggi di compilazione:** Prima di generare il codice PHP, Latte esegue i [passaggi di compilazione |compiler-passes]. Si tratta di funzioni che attraversano l'intero AST e possono modificarlo o raccogliere informazioni. Questo passaggio è fondamentale per funzionalità come la sicurezza ([Sandbox |sandbox]) o l'ottimizzazione. +4. **Generazione del codice:** Infine, il compilatore attraversa l'AST (potenzialmente modificato) e genera il codice della classe PHP corrispondente. Questo codice PHP è ciò che effettivamente renderizza il template durante l'esecuzione. +5. **Caching:** Il codice PHP generato viene salvato su disco, rendendo i rendering successivi molto veloci, poiché i passaggi 1-4 vengono saltati. + +In realtà, la compilazione è un po' più complessa. Latte **ha due** lexer e parser: uno per il template HTML e un altro per il codice PHP-like all'interno dei tag. Inoltre, il parsing non avviene dopo la tokenizzazione, ma lexer e parser vengono eseguiti parallelamente in due "thread" e si coordinano. Credetemi, programmarlo è stata scienza missilistica :-) + +L'intero processo, dal caricamento del contenuto del template, attraverso il parsing, fino alla generazione del file risultante, può essere sequenziato con questo codice, con cui è possibile sperimentare e stampare i risultati intermedi: + +```php +$latte = new Latte\Engine; +$source = $latte->getLoader()->getContent($file); +$ast = $latte->parse($source); +$latte->applyPasses($ast); +$code = $latte->generate($ast, $file); +``` + + +Anatomia di un tag +================== + +La creazione di un tag personalizzato completamente funzionale in Latte coinvolge diverse parti interconnesse. Prima di immergerci nell'implementazione, comprendiamo i concetti di base e la terminologia, utilizzando un'analogia con HTML e il Document Object Model (DOM). + + +Tag vs. Nodi (Analogia con HTML) +-------------------------------- + +In HTML, scriviamo **tag** come `

                                                                          ` o `

                                                                          ...
                                                                          `. Questi tag sono la sintassi nel codice sorgente. Quando il browser analizza questo HTML, crea una rappresentazione in memoria chiamata **Document Object Model (DOM)**. Nel DOM, i tag HTML sono rappresentati da **nodi** (specificamente nodi `Element` nella terminologia del DOM JavaScript). Lavoriamo programmaticamente con questi *nodi* (ad esempio, usando `document.getElementById(...)` di JavaScript si ottiene un nodo Element). Un tag è solo una rappresentazione testuale nel file sorgente; un nodo è una rappresentazione orientata agli oggetti nell'albero logico. + +Latte funziona in modo simile: + +- Nel file di template `.latte`, scrivete **tag Latte**, come `{foreach ...}` e `{/foreach}`. Questa è la sintassi con cui voi, come autori del template, lavorate. +- Quando Latte **analizza** il template, costruisce un **Abstract Syntax Tree (AST)**. Questo albero è composto da **nodi**. Ogni tag Latte, elemento HTML, pezzo di testo o espressione nel template diventa uno o più nodi in questo albero. +- La classe base per tutti i nodi nell'AST è `Latte\Compiler\Node`. Proprio come il DOM ha diversi tipi di nodi (Element, Text, Comment), l'AST di Latte ha diversi tipi di nodi. Incontrerete `Latte\Compiler\Nodes\TextNode` per il testo statico, `Latte\Compiler\Nodes\Html\ElementNode` per gli elementi HTML, `Latte\Compiler\Nodes\Php\ExpressionNode` per le espressioni all'interno dei tag e, fondamentale per i tag personalizzati, nodi che ereditano da `Latte\Compiler\Nodes\StatementNode`. + + +Perché `StatementNode`? +----------------------- + +Gli elementi HTML (`Html\ElementNode`) rappresentano principalmente struttura e contenuto. Le espressioni PHP (`Php\ExpressionNode`) rappresentano valori o calcoli. Ma che dire dei tag Latte come `{if}`, `{foreach}` o il nostro `{datetime}` personalizzato? Questi tag *eseguono azioni*, controllano il flusso del programma o generano output basato sulla logica. Sono unità funzionali che rendono Latte un potente *engine* di template, non solo un linguaggio di markup. + +Nella programmazione, tali unità che eseguono azioni sono spesso chiamate "statements" (istruzioni). Pertanto, i nodi che rappresentano questi tag Latte funzionali ereditano tipicamente da `Latte\Compiler\Nodes\StatementNode`. Questo li distingue dai nodi puramente strutturali (come gli elementi HTML) o dai nodi che rappresentano valori (come le espressioni). + + +Componenti chiave +================= + +Esaminiamo i componenti principali necessari per creare un tag personalizzato: + + +Funzione di parsing del tag +--------------------------- + +- Questa funzione PHP callable analizza la sintassi del tag Latte (`{...}`) nel template sorgente. +- Riceve informazioni sul tag (come il suo nome, posizione e se si tratta di un n:attribute) tramite l'oggetto [api:Latte\Compiler\Tag]. +- Il suo strumento principale per analizzare gli argomenti e le espressioni all'interno dei delimitatori del tag è l'oggetto [api:Latte\Compiler\TagParser], accessibile tramite `$tag->parser` (questo è un parser diverso da quello che analizza l'intero template). +- Per i tag accoppiati, utilizza `yield` per segnalare a Latte di analizzare il contenuto interno tra il tag di apertura e quello di chiusura. +- L'obiettivo finale della funzione di parsing è creare e restituire un'istanza della **classe del nodo**, che viene aggiunta all'AST. +- È consuetudine (anche se non obbligatorio) implementare la funzione di parsing come metodo statico (spesso chiamato `create`) direttamente nella classe del nodo corrispondente. Ciò mantiene la logica di parsing e la rappresentazione del nodo ordinatamente in un unico pacchetto, consente l'accesso agli elementi privati/protetti della classe, se necessario, e migliora l'organizzazione. + + +Classe del nodo +--------------- + +- Rappresenta la *funzione logica* del vostro tag nell'**Abstract Syntax Tree (AST)**. +- Contiene le informazioni analizzate (come argomenti o contenuto) come proprietà pubbliche. Queste proprietà spesso contengono altre istanze di `Node` (ad es. `ExpressionNode` per gli argomenti analizzati, `AreaNode` per il contenuto analizzato). +- Il metodo `print(PrintContext $context): string` genera il *codice PHP* (un'istruzione o una serie di istruzioni) che esegue l'azione del tag durante il rendering del template. +- Il metodo `getIterator(): \Generator` rende accessibili i nodi figli (argomenti, contenuto) per l'attraversamento da parte dei **passaggi di compilazione**. Deve fornire riferimenti (`&`) per consentire ai passaggi di modificare o sostituire potenzialmente i sottonodi. +- Dopo che l'intero template è stato analizzato nell'AST, Latte esegue una serie di [passaggi di compilazione |compiler-passes]. Questi passaggi attraversano l'*intero* AST utilizzando il metodo `getIterator()` fornito da ciascun nodo. Possono ispezionare i nodi, raccogliere informazioni e persino *modificare* l'albero (ad es. cambiando le proprietà pubbliche dei nodi o sostituendo completamente i nodi). Questo design, che richiede un `getIterator()` completo, è cruciale. Consente a potenti funzionalità come [Sandbox |sandbox] di analizzare e potenzialmente modificare il comportamento di *qualsiasi* parte del template, inclusi i vostri tag personalizzati, garantendo sicurezza e coerenza. + + +Registrazione tramite un'estensione +----------------------------------- + +- Dovete informare Latte del vostro nuovo tag e quale funzione di parsing deve essere utilizzata per esso. Questo avviene all'interno di un'[estensione Latte |extending-latte#Latte Extension]. +- All'interno della vostra classe di estensione, implementate il metodo `getTags(): array`. Questo metodo restituisce un array associativo in cui le chiavi sono i nomi dei tag (ad es. `'mytag'`, `'n:myattribute'`) e i valori sono le funzioni PHP callable che rappresentano le rispettive funzioni di parsing (ad es. `MyNamespace\DatetimeNode::create(...)`). + +Riepilogo: La **funzione di parsing del tag** trasforma il *codice sorgente del template* del vostro tag in un **nodo AST**. La **classe del nodo** può quindi trasformare *se stessa* in *codice PHP* eseguibile per il template compilato e rende accessibili i suoi sottonodi per i **passaggi di compilazione** tramite `getIterator()`. La **registrazione tramite estensione** collega il nome del tag alla funzione di parsing e lo rende noto a Latte. + +Ora esploreremo come implementare questi componenti passo dopo passo. + + +Creazione di un tag semplice +============================ + +Immergiamoci nella creazione del vostro primo tag Latte personalizzato. Inizieremo con un esempio molto semplice: un tag chiamato `{datetime}` che stampa la data e l'ora correnti. **Inizialmente, questo tag non accetterà alcun argomento**, ma lo miglioreremo più avanti nella sezione [#"Parsing degli Argomenti del Tag"]. Non ha nemmeno alcun contenuto interno. + +Questo esempio vi guiderà attraverso i passaggi fondamentali: definire la classe del nodo, implementare i suoi metodi `print()` e `getIterator()`, creare la funzione di parsing e infine registrare il tag. + +**Obiettivo:** Implementare `{datetime}` per stampare la data e l'ora correnti utilizzando la funzione PHP `date()`. + + +Creazione della classe del nodo +------------------------------- + +Innanzitutto, abbiamo bisogno di una classe che rappresenti il nostro tag nell'Abstract Syntax Tree (AST). Come discusso sopra, ereditiamo da `Latte\Compiler\Nodes\StatementNode`. + +Create un file (ad es. `DatetimeNode.php`) e definite la classe: + +```php +node = new self; + return $node; + } + + /** + * Genera il codice PHP che verrà eseguito durante il rendering del template. + */ + public function print(PrintContext $context): string + { + return $context->format( + 'echo date(\'Y-m-d H:i:s\') %line;', + $this->position, + ); + } + + /** + * Fornisce accesso ai nodi figli per i passaggi di compilazione di Latte. + */ + public function &getIterator(): \Generator + { + false && yield; + } +} +``` + +Quando Latte incontra `{datetime}` in un template, chiama la funzione di parsing `create()`. Il suo compito è restituire un'istanza di `DatetimeNode`. + +Il metodo `print()` genera il codice PHP che verrà eseguito durante il rendering del template. Chiamiamo il metodo `$context->format()`, che costruisce la stringa finale del codice PHP per il template compilato. Il primo argomento, `'echo date('Y-m-d H:i:s') %line;'`, è una maschera in cui vengono inseriti i parametri successivi. Il segnaposto `%line` dice al metodo `format()` di utilizzare il secondo argomento, che è `$this->position`, e inserire un commento come `/* line 15 */`, che collega il codice PHP generato alla riga originale del template, il che è fondamentale per il debugging. + +La proprietà `$this->position` è ereditata dalla classe base `Node` ed è impostata automaticamente dal parser di Latte. Contiene un oggetto [api:Latte\Compiler\Position] che indica dove è stato trovato il tag nel file sorgente `.latte`. + +Il metodo `getIterator()` è fondamentale per i passaggi di compilazione. Deve fornire tutti i nodi figli, ma il nostro semplice `DatetimeNode` attualmente non ha argomenti né contenuto, quindi nessun nodo figlio. Tuttavia, il metodo deve comunque esistere ed essere un generatore, cioè la parola chiave `yield` deve essere presente in qualche modo nel corpo del metodo. + + +Registrazione tramite un'estensione +----------------------------------- + +Infine, informiamo Latte del nuovo tag. Create una [classe di estensione |extending-latte#Latte Extension] (ad es. `MyLatteExtension.php`) e registrate il tag nel suo metodo `getTags()`. + +```php + Mappa: 'nome-tag' => funzione-parsing + */ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + // Registra altri tag qui più tardi + ]; + } +} +``` + +Quindi, registrate questa estensione nell'Engine di Latte: + +```php +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +Create un template: + +```latte +

                                                                          Pagina generata il: {datetime}

                                                                          +``` + +Output atteso: `

                                                                          Pagina generata il: 2023-10-27 11:00:00

                                                                          ` + + +Riepilogo di questa fase +------------------------ + +Abbiamo creato con successo un tag personalizzato di base `{datetime}`. Abbiamo definito la sua rappresentazione nell'AST (`DatetimeNode`), gestito il suo parsing (`create()`), specificato come dovrebbe generare codice PHP (`print()`), assicurato che i suoi figli siano accessibili per l'attraversamento (`getIterator()`) e lo abbiamo registrato in Latte. + +Nella prossima sezione, miglioreremo questo tag per accettare argomenti e mostreremo come analizzare le espressioni e gestire i nodi figli. + + +Parsing degli argomenti del tag +=============================== + +Il nostro semplice tag `{datetime}` funziona, ma non è molto flessibile. Miglioriamolo per accettare un argomento opzionale: una stringa di formato per la funzione `date()`. La sintassi richiesta sarà `{datetime $format}`. + +**Obiettivo:** Modificare `{datetime}` in modo che accetti un'espressione PHP opzionale come argomento, che verrà utilizzata come stringa di formato per `date()`. + + +Introduzione a `TagParser` +-------------------------- + +Prima di modificare il codice, è importante comprendere lo strumento che useremo: [api:Latte\Compiler\TagParser]. Quando il parser principale di Latte (`TemplateParser`) incontra un tag Latte come `{datetime ...}` o un n:attribute, delega il parsing del contenuto *all'interno* del tag (la parte tra `{` e `}` o il valore dell'attributo) a un `TagParser` specializzato. + +Questo `TagParser` lavora esclusivamente con gli **argomenti del tag**. Il suo compito è elaborare i token che rappresentano questi argomenti. È fondamentale che **elabori l'intero contenuto** che gli viene fornito. Se la vostra funzione di parsing termina, ma `TagParser` non ha raggiunto la fine degli argomenti (controllato tramite `$tag->parser->isEnd()`), Latte lancerà un'eccezione, poiché ciò indica che all'interno del tag sono rimasti token imprevisti. Al contrario, se il tag *richiede* argomenti, dovreste chiamare `$tag->expectArguments()` all'inizio della vostra funzione di parsing. Questo metodo controlla se gli argomenti sono presenti e lancia un'eccezione utile se il tag è stato utilizzato senza alcun argomento. + +`TagParser` offre metodi utili per analizzare diversi tipi di argomenti: + +- `parseExpression(): ExpressionNode`: Analizza un'espressione PHP-like (variabili, letterali, operatori, chiamate di funzioni/metodi, ecc.). Gestisce lo zucchero sintattico di Latte, come trattare semplici stringhe alfanumeriche come stringhe tra virgolette (ad es. `foo` viene analizzato come se fosse `'foo'`). +- `parseUnquotedStringOrExpression(): ExpressionNode`: Analizza o un'espressione standard o una *stringa non tra virgolette*. Le stringhe non tra virgolette sono sequenze consentite da Latte senza virgolette, spesso utilizzate per cose come percorsi di file (ad es. `{include ../file.latte}`). Se analizza una stringa non tra virgolette, restituisce `StringNode`. +- `parseArguments(): ArrayNode`: Analizza argomenti separati da virgole, potenzialmente con chiavi, come `10, name: 'John', true`. +- `parseModifier(): ModifierNode`: Analizza filtri come `|upper|truncate:10`. +- `parseType(): ?SuperiorTypeNode`: Analizza type hint PHP come `int`, `?string`, `array|Foo`. + +Per esigenze di parsing più complesse o di basso livello, potete interagire direttamente con il [flusso di token |api:Latte\Compiler\TokenStream] tramite `$tag->parser->stream`. Questo oggetto fornisce metodi per controllare ed elaborare singoli token: + +- `$tag->parser->stream->is(...): bool`: Controlla se il token *corrente* corrisponde a uno dei tipi specificati (ad es. `Token::Php_Variable`) o valori letterali (ad es. `'as'`) senza consumarlo. Utile per guardare avanti. +- `$tag->parser->stream->consume(...): Token`: Consuma il token *corrente* e sposta la posizione del flusso in avanti. Se vengono forniti tipi/valori di token attesi come argomenti e il token corrente non corrisponde, lancia `CompileException`. Usatelo quando *vi aspettate* un certo token. +- `$tag->parser->stream->tryConsume(...): ?Token`: Tenta di consumare il token *corrente* *solo se* corrisponde a uno dei tipi/valori specificati. Se corrisponde, consuma il token e lo restituisce. Se non corrisponde, lascia invariata la posizione del flusso e restituisce `null`. Usatelo per token opzionali o quando scegliete tra diversi percorsi sintattici. + + +Aggiornamento della funzione di parsing `create()` +-------------------------------------------------- + +Con questa comprensione, modifichiamo il metodo `create()` in `DatetimeNode` per analizzare l'argomento di formato opzionale usando `$tag->parser`. + +```php +node = new self; + + // Controlliamo se ci sono token + if (!$tag->parser->isEnd()) { + // Analizziamo l'argomento come un'espressione PHP-like usando TagParser. + $node->format = $tag->parser->parseExpression(); + } + + return $node; + } + + // ... i metodi print() e getIterator() saranno aggiornati più avanti ... +} +``` + +Abbiamo aggiunto una proprietà pubblica `$format`. In `create()`, ora usiamo `$tag->parser->isEnd()` per controllare se *esistono* argomenti. Se sì, `$tag->parser->parseExpression()` elabora i token per l'espressione. Poiché `TagParser` deve elaborare tutti i token di input, Latte lancerà automaticamente un errore se l'utente scrive qualcosa di inaspettato dopo l'espressione del formato (ad es. `{datetime 'Y-m-d', unexpected}`). + + +Aggiornamento del metodo `print()` +---------------------------------- + +Ora modifichiamo il metodo `print()` per utilizzare l'espressione del formato analizzata memorizzata in `$this->format`. Se non è stato fornito alcun formato (`$this->format` è `null`), dovremmo usare una stringa di formato predefinita, ad esempio `'Y-m-d H:i:s'`. + +```php + public function print(PrintContext $context): string + { + $formatNode = $this->format ?? new StringNode('Y-m-d H:i:s'); + + // %node stampa la rappresentazione del codice PHP di $formatNode. + return $context->format( + 'echo date(%node) %line;', + $formatNode, + $this->position + ); + } +``` + +Nella variabile `$formatNode` memorizziamo il nodo AST che rappresenta la stringa di formato per la funzione PHP `date()`. Usiamo qui l'operatore di coalescenza nullo (`??`). Se l'utente ha fornito un argomento nel template (ad es. `{datetime 'd.m.Y'}`), allora la proprietà `$this->format` contiene il nodo corrispondente (in questo caso `StringNode` con il valore `'d.m.Y'`), e questo nodo viene utilizzato. Se l'utente non ha fornito un argomento (ha scritto solo `{datetime}`), la proprietà `$this->format` è `null`, e creiamo invece un nuovo `StringNode` con il formato predefinito `'Y-m-d H:i:s'`. Ciò garantisce che `$formatNode` contenga sempre un nodo AST valido per il formato. + +Nella maschera `'echo date(%node) %line;'` viene utilizzato un nuovo segnaposto `%node`, che dice al metodo `format()` di prendere il primo argomento successivo (che è il nostro `$formatNode`), chiamare il suo metodo `print()` (che restituirà la sua rappresentazione del codice PHP) e inserire il risultato nella posizione del segnaposto. + + +Implementazione di `getIterator()` per i sottonodi +-------------------------------------------------- + +Il nostro `DatetimeNode` ora ha un nodo figlio: l'espressione `$format`. **Dobbiamo** rendere questo nodo figlio accessibile ai passaggi di compilazione fornendolo nel metodo `getIterator()`. Ricordate di fornire un *riferimento* (`&`) per consentire ai passaggi di sostituire potenzialmente il nodo. + +```php + public function &getIterator(): \Generator + { + if ($this->format) { + yield $this->format; + } + } +``` + +Perché è fondamentale? Immaginate un passaggio Sandbox che deve controllare se l'argomento `$format` non contiene una chiamata a una funzione vietata (ad es. `{datetime dangerousFunction()}`). Se `getIterator()` non fornisce `$this->format`, il passaggio Sandbox non vedrebbe mai la chiamata `dangerousFunction()` all'interno dell'argomento del nostro tag, creando una potenziale falla di sicurezza. Fornendolo, consentiamo a Sandbox (e ad altri passaggi) di controllare e potenzialmente modificare il nodo dell'espressione `$format`. + + +Utilizzo del tag migliorato +--------------------------- + +Il tag ora gestisce correttamente l'argomento opzionale: + +```latte +Formato predefinito: {datetime} +Formato personalizzato: {datetime 'd.m.Y'} +Utilizzo di una variabile: {datetime $userDateFormatPreference} + +{* Questo causerebbe un errore dopo il parsing di 'd.m.Y', perché ", foo" è inaspettato *} +{* {datetime 'd.m.Y', foo} *} +``` + +Successivamente, esamineremo la creazione di tag accoppiati che elaborano il contenuto tra di loro. + + +Gestione dei tag accoppiati +=========================== + +Finora, il nostro tag `{datetime}` era *auto-chiudente* (concettualmente). Non aveva alcun contenuto tra il tag di apertura e quello di chiusura. Tuttavia, molti tag utili lavorano con un blocco di contenuto del template. Questi sono chiamati **tag accoppiati**. Esempi includono `{if}...{/if}`, `{block}...{/block}` o un tag personalizzato che creeremo ora: `{debug}...{/debug}`. + +Questo tag ci consentirà di includere informazioni di debug nei nostri template, che dovrebbero essere visibili solo durante lo sviluppo. + +**Obiettivo:** Creare un tag accoppiato `{debug}`, il cui contenuto viene renderizzato solo quando è attivo uno specifico flag "modalità di sviluppo". + + +Introduzione ai provider +------------------------ + +A volte i vostri tag necessitano di accedere a dati o servizi che non vengono passati direttamente come parametri del template. Ad esempio, determinare se l'applicazione è in modalità di sviluppo, accedere all'oggetto utente o ottenere valori di configurazione. Latte fornisce un meccanismo chiamato **provider** per questo scopo. + +I provider vengono registrati nella vostra [estensione |extending-latte#Latte Extension] utilizzando il metodo `getProviders()`. Questo metodo restituisce un array associativo in cui le chiavi sono i nomi con cui i provider saranno accessibili nel codice di runtime del template e i valori sono i dati o gli oggetti effettivi. + +All'interno del codice PHP generato dal metodo `print()` del vostro tag, potete accedere a questi provider tramite una proprietà speciale dell'oggetto `$this->global`. Poiché questa proprietà è condivisa tra tutte le estensioni, è buona pratica **prefissare i nomi dei vostri provider** per evitare potenziali conflitti di nomi con i provider principali di Latte o provider di altre estensioni di terze parti. Una convenzione comune è utilizzare un prefisso breve e univoco correlato al vostro produttore o al nome dell'estensione. Per il nostro esempio, useremo il prefisso `app` e il flag della modalità di sviluppo sarà disponibile come `$this->global->appDevMode`. + + +La parola chiave `yield` per il parsing del contenuto +----------------------------------------------------- + +Come diciamo al parser di Latte di elaborare il contenuto *tra* `{debug}` e `{/debug}`? Qui entra in gioco la parola chiave `yield`. + +Quando `yield` viene utilizzato nella funzione `create()`, la funzione diventa un [generatore PHP |https://www.php.net/manual/en/language.generators.overview.php]. La sua esecuzione viene sospesa e il controllo ritorna al `TemplateParser` principale. `TemplateParser` continua quindi ad analizzare il contenuto del template *fino a quando* non incontra il tag di chiusura corrispondente (`{/debug}` nel nostro caso). + +Una volta trovato il tag di chiusura, `TemplateParser` riprende l'esecuzione della nostra funzione `create()` subito dopo l'istruzione `yield`. Il valore *restituito* dall'istruzione `yield` è un array contenente due elementi: + +1. Un `AreaNode` che rappresenta il contenuto analizzato tra il tag di apertura e quello di chiusura. +2. Un oggetto `Tag` che rappresenta il tag di chiusura (ad es. `{/debug}`). + +Creiamo la classe `DebugNode` e il suo metodo `create` utilizzando `yield`. + +```php +node = new self; + + // Sospendere il parsing, ottenere il contenuto interno e il tag finale quando viene trovato {/debug} + [$node->content, $endTag] = yield; + + return $node; + } + + // ... print() e getIterator() saranno implementati più avanti ... +} +``` + +Nota: `$endTag` è `null` se il tag viene utilizzato come n:attribute, cioè `
                                                                          ...
                                                                          `. + + +Implementazione di `print()` per il rendering condizionale +---------------------------------------------------------- + +Il metodo `print()` ora deve generare codice PHP che, a runtime, controlli il provider `appDevMode` ed esegua il codice per il contenuto interno solo se il flag è true. + +```php + public function print(PrintContext $context): string + { + // Genera un'istruzione PHP 'if' che controlla il provider a runtime + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + // Se in modalità di sviluppo, stampa il contenuto interno + %node + } + + XX, + $this->position, // Per il commento %line + $this->content, // Il nodo contenente l'AST del contenuto interno + ); + } +``` + +Questo è semplice. Usiamo `PrintContext::format()` per creare un'istruzione PHP `if` standard. All'interno dell'`if`, posizioniamo il segnaposto `%node` per `$this->content`. Latte chiamerà ricorsivamente `$this->content->print($context)` per generare il codice PHP per la parte interna del tag, ma solo se `$this->global->appDevMode` viene valutato come true a runtime. + + +Implementazione di `getIterator()` per il contenuto +--------------------------------------------------- + +Proprio come con il nodo dell'argomento nell'esempio precedente, il nostro `DebugNode` ora ha un nodo figlio: `AreaNode $content`. Dobbiamo renderlo accessibile fornendolo in `getIterator()`: + +```php + public function &getIterator(): \Generator + { + // Fornisce un riferimento al nodo del contenuto + yield $this->content; + } +``` + +Ciò consente ai passaggi di compilazione di scendere nel contenuto del nostro tag `{debug}`, il che è importante anche se il contenuto viene renderizzato condizionalmente. Ad esempio, Sandbox deve analizzare il contenuto indipendentemente dal fatto che `appDevMode` sia true o false. + + +Registrazione e utilizzo +------------------------ + +Registrate il tag e il provider nella vostra estensione: + +```php +class MyLatteExtension extends Extension +{ + // Supponiamo che $isDevelopmentMode sia determinato da qualche parte (ad es. dalla configurazione) + public function __construct( + private bool $isDevelopmentMode, + ) { + } + + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), // Registrazione del nuovo tag + ]; + } + + public function getProviders(): array + { + return [ + 'appDevMode' => $this->isDevelopmentMode, // Registrazione del provider + ]; + } +} + +// Durante la registrazione dell'estensione: +$isDev = true; // Determinate questo in base all'ambiente della vostra applicazione +$latte->addExtension(new App\Latte\MyLatteExtension($isDev)); +``` + +E il suo utilizzo nel template: + +```latte +

                                                                          Contenuto normale visibile sempre.

                                                                          + +{debug} +
                                                                          + ID utente corrente: {$user->id} + Ora della richiesta: {=time()} +
                                                                          +{/debug} + +

                                                                          Altro contenuto normale.

                                                                          +``` + + +Integrazione di n:attributi +--------------------------- + +Latte offre una comoda notazione abbreviata per molti tag accoppiati: [n:attributi |syntax#n:attributi]. Se avete un tag accoppiato come `{tag}...{/tag}` e desiderate che il suo effetto si applichi direttamente a un singolo elemento HTML, potete spesso scriverlo in modo più conciso come attributo `n:tag` su quell'elemento. + +Per la maggior parte dei tag accoppiati standard che definite (come il nostro `{debug}`), Latte abiliterà automaticamente la versione dell'attributo `n:` corrispondente. Non dovete fare nulla di extra durante la registrazione: + +```latte +{* Uso standard del tag accoppiato *} +{debug}
                                                                          Informazioni per il debug
                                                                          {/debug} + +{* Uso equivalente con n:attributo *} +
                                                                          Informazioni per il debug
                                                                          +``` + +Entrambe le versioni renderizzeranno il `
                                                                          ` solo se `$this->global->appDevMode` è true. Anche i prefissi `inner-` e `tag-` funzionano come previsto. + +A volte la logica del vostro tag potrebbe dover comportarsi leggermente diversamente a seconda che venga utilizzato come tag accoppiato standard o come n:attributo, o se viene utilizzato un prefisso come `n:inner-tag` o `n:tag-tag`. L'oggetto `Latte\Compiler\Tag`, passato alla vostra funzione di parsing `create()`, fornisce queste informazioni: + +- `$tag->isNAttribute(): bool`: Restituisce `true` se il tag viene analizzato come n:attributo +- `$tag->prefix: ?string`: Restituisce il prefisso utilizzato con l'n:attributo, che può essere `null` (non è un n:attributo), `Tag::PrefixNone`, `Tag::PrefixInner` o `Tag::PrefixTag` + +Ora che comprendiamo i tag semplici, il parsing degli argomenti, i tag accoppiati, i provider e gli n:attributi, affrontiamo uno scenario più complesso che coinvolge tag nidificati all'interno di altri tag, utilizzando il nostro tag `{debug}` come punto di partenza. + + +Tag intermedi +============= + +Alcuni tag accoppiati consentono o addirittura richiedono che altri tag appaiano *al loro interno* prima del tag di chiusura finale. Questi sono chiamati **tag intermedi**. Esempi classici includono `{if}...{elseif}...{else}...{/if}` o `{switch}...{case}...{default}...{/switch}`. + +Estendiamo il nostro tag `{debug}` per supportare una clausola `{else}` opzionale, che verrà renderizzata quando l'applicazione *non* è in modalità di sviluppo. + +**Obiettivo:** Modificare `{debug}` per supportare un tag intermedio opzionale `{else}`. La sintassi finale dovrebbe essere `{debug} ... {else} ... {/debug}`. + + +Parsing dei tag intermedi con `yield` +------------------------------------- + +Sappiamo già che `yield` sospende la funzione di parsing `create()` e restituisce il contenuto analizzato insieme al tag finale. Tuttavia, `yield` offre un maggiore controllo: potete fornirgli un array di *nomi di tag intermedi*. Quando il parser incontra uno qualsiasi di questi tag specificati **allo stesso livello di nidificazione** (cioè come figli diretti del tag genitore, non all'interno di altri blocchi o tag al suo interno), interrompe anche il parsing. + +Quando il parsing si interrompe a causa di un tag intermedio, interrompe il parsing del contenuto, riprende il generatore `create()` e passa indietro il contenuto parzialmente analizzato e il **tag intermedio** stesso (invece del tag finale di chiusura). La nostra funzione `create()` può quindi elaborare questo tag intermedio (ad es. analizzare i suoi argomenti, se ne avesse) e utilizzare nuovamente `yield` per analizzare la *prossima* parte del contenuto fino al tag finale *finale* o a un altro tag intermedio atteso. + +Modifichiamo `DebugNode::create()` per aspettarsi `{else}`: + +```php +node = new self; + + // yield e aspettarsi o {/debug} o {else} + [$node->thenContent, $nextTag] = yield ['else']; + + // Controllare se il tag in cui ci siamo fermati era {else} + if ($nextTag?->name === 'else') { + // Yield di nuovo per analizzare il contenuto tra {else} e {/debug} + [$node->elseContent, $endTag] = yield; + } + + return $node; + } + + // ... print() e getIterator() saranno aggiornati più avanti ... +} +``` + +Ora `yield ['else']` dice a Latte di interrompere il parsing non solo per `{/debug}`, ma anche per `{else}`. Se viene trovato `{else}`, `$nextTag` conterrà l'oggetto `Tag` per `{else}`. Quindi usiamo di nuovo `yield` senza argomenti, il che significa che ora ci aspettiamo solo il tag finale `{/debug}`, e memorizziamo il risultato in `$node->elseContent`. Se `{else}` non è stato trovato, `$nextTag` sarebbe il `Tag` per `{/debug}` (o `null` se usato come n:attributo) e `$node->elseContent` rimarrebbe `null`. + + +Implementazione di `print()` con `{else}` +----------------------------------------- + +Il metodo `print()` deve riflettere la nuova struttura. Dovrebbe generare un'istruzione PHP `if/else` basata sul provider `devMode`. + +```php + public function print(PrintContext $context): string + { + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + %node // Codice per il ramo 'then' (contenuto {debug}) + } else { + %node // Codice per il ramo 'else' (contenuto {else}) + } + + XX, + $this->position, // Numero di riga per la condizione 'if' + $this->thenContent, // Primo segnaposto %node + $this->elseContent ?? new NopNode, // Secondo segnaposto %node + ); + } +``` + +Questa è una struttura PHP `if/else` standard. Usiamo `%node` due volte; `format()` sostituisce i nodi forniti in sequenza. Usiamo `?? new NopNode` per evitare errori se `$this->elseContent` è `null` – `NopNode` semplicemente non stampa nulla. + + +Implementazione di `getIterator()` per entrambi i contenuti +----------------------------------------------------------- + +Ora abbiamo potenzialmente due nodi figli di contenuto (`$thenContent` e `$elseContent`). Dobbiamo fornire entrambi, se esistono: + +```php + public function &getIterator(): \Generator + { + yield $this->thenContent; + if ($this->elseContent) { + yield $this->elseContent; + } + } +``` + + +Utilizzo del tag migliorato +--------------------------- + +Il tag può ora essere utilizzato con la clausola `{else}` opzionale: + +```latte +{debug} +

                                                                          Visualizzazione delle informazioni di debug perché devMode è ON.

                                                                          +{else} +

                                                                          Le informazioni di debug sono nascoste perché devMode è OFF.

                                                                          +{/debug} +``` + + +Gestione dello stato e della nidificazione +========================================== + +I nostri esempi precedenti (`{datetime}`, `{debug}`) erano relativamente senza stato all'interno dei loro metodi `print()`. O stampavano direttamente il contenuto o eseguivano un semplice controllo condizionale basato su un provider globale. Tuttavia, molti tag devono gestire una qualche forma di **stato** durante il rendering o comportano la valutazione di espressioni utente che dovrebbero essere eseguite solo una volta per motivi di prestazioni o correttezza. Inoltre, dobbiamo considerare cosa succede quando i nostri tag personalizzati sono **nidificati**. + +Illustriamo questi concetti creando un tag `{repeat $count}...{/repeat}`. Questo tag ripeterà il suo contenuto interno `$count` volte. + +**Obiettivo:** Implementare `{repeat $count}`, che ripete il suo contenuto un numero specificato di volte. + + +La necessità di variabili temporanee e univoche +----------------------------------------------- + +Immaginate che l'utente scriva: + +```latte +{repeat rand(1, 5)} Contenuto {/repeat} +``` + +Se generassimo ingenuamente un ciclo `for` PHP in questo modo nel nostro metodo `print()`: + +```php +// Codice generato semplificato e ERRATO +for ($i = 0; $i < rand(1, 5); $i++) { + // stampa contenuto +} +``` +Questo sarebbe sbagliato! L'espressione `rand(1, 5)` verrebbe **rivalutata ad ogni iterazione del ciclo**, portando a un numero imprevedibile di ripetizioni. Dobbiamo valutare l'espressione `$count` *una volta* prima dell'inizio del ciclo e memorizzare il suo risultato. + +Genereremo codice PHP che prima valuta l'espressione del conteggio e la memorizza in una **variabile temporanea di runtime**. Per evitare collisioni con le variabili definite dall'utente del template *e* le variabili interne di Latte (come `$ʟ_...`), useremo la convenzione di prefissare le nostre variabili temporanee con **`$__` (doppio trattino basso)**. + +Il codice generato apparirebbe quindi così: + +```php +$__count = rand(1, 5); +for ($__i = 0; $__i < $__count; $__i++) { + // stampa contenuto +} +``` + +Ora consideriamo la nidificazione: + +```latte +{repeat $countA} {* Ciclo esterno *} + {repeat $countB} {* Ciclo interno *} + ... + {/repeat} +{/repeat} +``` + +Se sia il tag `{repeat}` esterno che quello interno generassero codice utilizzando gli *stessi* nomi di variabili temporanee (ad es. `$__count` e `$__i`), il ciclo interno sovrascriverebbe le variabili del ciclo esterno, interrompendo la logica. + +Dobbiamo garantire che le variabili temporanee generate per ogni istanza del tag `{repeat}` siano **univoche**. Raggiungiamo questo obiettivo utilizzando `PrintContext::generateId()`. Questo metodo restituisce un intero univoco durante la fase di compilazione. Possiamo aggiungere questo ID ai nomi delle nostre variabili temporanee. + +Quindi, invece di `$__count`, genereremo `$__count_1` per il primo tag repeat, `$__count_2` per il secondo, e così via. Allo stesso modo, per il contatore del ciclo, useremo `$__i_1`, `$__i_2`, ecc. + + +Implementazione di `RepeatNode` +------------------------------- + +Creiamo la classe del nodo. + +```php +expectArguments(); // assicura che $count sia fornito + $node = $tag->node = new self; + // Analizza l'espressione del conteggio + $node->count = $tag->parser->parseExpression(); + // Ottiene il contenuto interno + [$node->content] = yield; + return $node; + } + + /** + * Genera un ciclo PHP 'for' con nomi di variabili univoci. + */ + public function print(PrintContext $context): string + { + // Generazione di nomi di variabili univoci + $id = $context->generateId(); + $countVar = '$__count_' . $id; // es. $__count_1, $__count_2, ecc. + $iteratorVar = '$__i_' . $id; // es. $__i_1, $__i_2, ecc. + + return $context->format( + <<<'XX' + // Valutazione dell'espressione del conteggio *una volta* e memorizzazione + %raw = (int) (%node); + // Ciclo utilizzando il conteggio memorizzato e una variabile di iterazione univoca + for (%raw = 0; %2.raw < %0.raw; %2.raw++) %line { + %node // Rendering del contenuto interno + } + + XX, + $countVar, // %0 - Variabile per memorizzare il conteggio + $this->count, // %1 - Nodo dell'espressione per il conteggio + $iteratorVar, // %2 - Nome della variabile di iterazione del ciclo + $this->position, // %3 - Commento con numero di riga per il ciclo stesso + $this->content // %4 - Nodo del contenuto interno + ); + } + + /** + * Fornisce i nodi figli (espressione del conteggio e contenuto). + */ + public function &getIterator(): \Generator + { + yield $this->count; + yield $this->content; + } +} +``` + +Il metodo `create()` analizza l'espressione `$count` richiesta usando `parseExpression()`. Prima viene chiamato `$tag->expectArguments()`. Ciò garantisce che l'utente abbia fornito *qualcosa* dopo `{repeat}`. Mentre `$tag->parser->parseExpression()` fallirebbe se non venisse fornito nulla, il messaggio di errore potrebbe riguardare una sintassi imprevista. L'uso di `expectArguments()` fornisce un errore molto più chiaro, affermando specificamente che mancano argomenti per il tag `{repeat}`. + +Il metodo `print()` genera il codice PHP responsabile dell'esecuzione della logica di ripetizione a runtime. Inizia generando nomi univoci per le variabili PHP temporanee di cui avrà bisogno. + +Il metodo `$context->format()` viene chiamato con un nuovo segnaposto `%raw`, che inserisce la *stringa grezza* fornita come argomento corrispondente. Qui inserisce il nome univoco della variabile memorizzato in `$countVar` (ad es. `$__count_1`). E che dire di `%0.raw` e `%2.raw`? Questo dimostra i **segnaposto posizionali**. Invece di usare semplicemente `%raw`, che prende il *prossimo* argomento grezzo disponibile, `%2.raw` prende esplicitamente l'argomento all'indice 2 (che è `$iteratorVar`) e inserisce il suo valore stringa grezzo. Ciò ci consente di riutilizzare la stringa `$iteratorVar` senza passarla più volte nell'elenco degli argomenti per `format()`. + +Questa chiamata `format()` attentamente costruita genera un ciclo PHP efficiente e sicuro che gestisce correttamente l'espressione del conteggio ed evita collisioni di nomi di variabili anche quando i tag `{repeat}` sono nidificati. + + +Registrazione e utilizzo +------------------------ + +Registrate il tag nella vostra estensione: + +```php +use App\Latte\RepeatNode; + +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), // Registrazione del tag repeat + ]; + } +} +``` + +Usatelo nel template, inclusa la nidificazione: + +```latte +{var $rows = rand(5, 7)} +{var $cols = rand(3, 5)} + +{repeat $rows} +
                                                                          + {repeat $cols} + + {/repeat} + +{/repeat} +``` + +Questo esempio dimostra come gestire lo stato (contatori di cicli) e potenziali problemi di nidificazione utilizzando variabili temporanee con prefisso `$__` e univoche con ID da `PrintContext::generateId()`. + + +n:attributi puri +---------------- + +Mentre molti `n:attributi` come `n:if` o `n:foreach` fungono da comode scorciatoie per le loro controparti nei tag accoppiati (`{if}...{/if}`, `{foreach}...{/foreach}`), Latte consente anche di definire tag che *esistono solo* sotto forma di n:attributo. Questi vengono spesso utilizzati per modificare gli attributi o il comportamento dell'elemento HTML a cui sono collegati. + +Esempi standard integrati in Latte includono [`n:class` |tags#n:class], che aiuta a costruire dinamicamente l'attributo `class`, e [`n:attr` |tags#n:attr], che può impostare più attributi arbitrari. + +Creiamo il nostro n:attributo puro: `n:confirm`, che aggiunge una finestra di dialogo di conferma JavaScript prima di eseguire un'azione (come seguire un link o inviare un form). + +**Obiettivo:** Implementare `n:confirm="'Sei sicuro?'"`, che aggiunge un gestore `onclick` per prevenire l'azione predefinita se l'utente annulla la finestra di dialogo di conferma. + + +Implementazione di `ConfirmNode` +-------------------------------- + +Abbiamo bisogno di una classe Node e di una funzione di parsing. + +```php +expectArguments(); + $node = $tag->node = new self; + $node->message = $tag->parser->parseExpression(); + return $node; + } + + /** + * Genera il codice dell'attributo 'onclick' con l'escaping corretto. + */ + public function print(PrintContext $context): string + { + // Assicura l'escaping corretto per i contesti JavaScript e attributo HTML. + return $context->format( + <<<'XX' + echo ' onclick="', LR\Filters::escapeHtmlAttr('return confirm(' . LR\Filters::escapeJs(%node) . ')'), '"' %line; + XX, + $this->message, + $this->position, + ); + } + + public function &getIterator(): \Generator + { + yield $this->message; + } +} +``` + +Il metodo `print()` genera codice PHP che alla fine, durante il rendering del template, stamperà l'attributo HTML `onclick="..."`. La gestione dei contesti nidificati (JavaScript all'interno di un attributo HTML) richiede un attento escaping. Il filtro `LR\Filters::escapeJs(%node)` viene chiamato a runtime ed esegue l'escape del messaggio correttamente per l'uso all'interno di JavaScript (l'output sarebbe come `"Sure?"`). Successivamente, il filtro `LR\Filters::escapeHtmlAttr(...)` esegue l'escape dei caratteri speciali negli attributi HTML, quindi cambierebbe l'output in `return confirm("Sure?")`. Questo escaping a due fasi a runtime garantisce che il messaggio sia sicuro per JavaScript e che il codice JavaScript risultante sia sicuro per l'inserimento nell'attributo HTML `onclick`. + + +Registrazione e utilizzo +------------------------ + +Registrate l'n:attributo nella vostra estensione. Non dimenticate il prefisso `n:` nella chiave: + +```php +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), + 'n:confirm' => ConfirmNode::create(...), // Registrazione di n:confirm + ]; + } +} +``` + +Ora potete usare `n:confirm` su link, pulsanti o elementi del form: + +```latte +Elimina +``` + +HTML generato: + +```html +Elimina +``` + +Quando l'utente fa clic sul link, il browser esegue il codice `onclick`, visualizza la finestra di dialogo di conferma e passa a `delete.php` solo se l'utente fa clic su "OK". + +Questo esempio dimostra come è possibile creare un n:attributo puro per modificare il comportamento o gli attributi del suo elemento HTML host generando il codice PHP appropriato nel suo metodo `print()`. Non dimenticate il doppio escaping, che è spesso richiesto: una volta per il contesto di destinazione (JavaScript in questo caso) e di nuovo per il contesto dell'attributo HTML. + + +Argomenti avanzati +================== + +Mentre le sezioni precedenti coprono i concetti fondamentali, ecco alcuni argomenti più avanzati che potreste incontrare durante la creazione di tag Latte personalizzati. + + +Modalità di output dei tag +-------------------------- + +L'oggetto `Tag` passato alla vostra funzione `create()` ha una proprietà `outputMode`. Questa proprietà influenza il modo in cui Latte gestisce gli spazi bianchi e l'indentazione circostanti, specialmente quando il tag viene utilizzato su una riga propria. Potete modificare questa proprietà nella vostra funzione `create()`. + +- `Tag::OutputKeepIndentation` (Predefinito per la maggior parte dei tag come `{=...}`): Latte cerca di preservare l'indentazione prima del tag. Le nuove righe *dopo* il tag vengono generalmente preservate. Questo è adatto per i tag che stampano contenuto in linea. +- `Tag::OutputRemoveIndentation` (Predefinito per i tag di blocco come `{if}`, `{foreach}`): Latte rimuove l'indentazione iniziale e potenzialmente una nuova riga successiva. Questo aiuta a mantenere il codice PHP generato più pulito e previene ulteriori righe vuote nell'output HTML causate dal tag stesso. Usatelo per i tag che rappresentano strutture di controllo o blocchi che non dovrebbero aggiungere spazi bianchi da soli. +- `Tag::OutputNone` (Utilizzato da tag come `{var}`, `{default}`): Simile a `RemoveIndentation`, ma segnala più fortemente che il tag stesso non produce output diretto, influenzando potenzialmente l'elaborazione degli spazi bianchi intorno ad esso in modo ancora più aggressivo. Adatto per tag dichiarativi o di impostazione. + +Scegliete la modalità che meglio si adatta allo scopo del vostro tag. Per la maggior parte dei tag strutturali o di controllo, `OutputRemoveIndentation` è solitamente appropriato. + + +Accesso ai tag genitore/più vicini +---------------------------------- + +A volte il comportamento di un tag deve dipendere dal contesto in cui viene utilizzato, specificamente in quale tag genitore(i) si trova. L'oggetto `Tag` passato alla vostra funzione `create()` fornisce il metodo `closestTag(array $classes, ?callable $condition = null): ?Tag` esattamente per questo scopo. + +Questo metodo cerca verso l'alto nella gerarchia dei tag attualmente aperti (inclusi gli elementi HTML rappresentati internamente durante il parsing) e restituisce l'oggetto `Tag` dell'antenato più vicino che corrisponde a criteri specifici. Se non viene trovato alcun antenato corrispondente, restituisce `null`. + +L'array `$classes` specifica quale tipo di tag antenato state cercando. Controlla se il nodo associato del tag antenato (`$ancestorTag->node`) è un'istanza di questa classe. + +```php +function create(Tag $tag) +{ + // Ricerca del tag antenato più vicino il cui nodo è un'istanza di ForeachNode + $foreachTag = $tag->closestTag([ForeachNode::class]); + if ($foreachTag) { + // Possiamo accedere all'istanza ForeachNode stessa: + $foreachNode = $foreachTag->node; + } +} +``` + +Notate `$foreachTag->node`: Funziona solo perché è una convenzione nello sviluppo dei tag Latte assegnare immediatamente il nodo creato a `$tag->node` all'interno del metodo `create()`, come abbiamo sempre fatto. + +A volte il semplice confronto del tipo di nodo non è sufficiente. Potrebbe essere necessario controllare una proprietà specifica del potenziale tag antenato o del suo nodo. Il secondo argomento opzionale per `closestTag()` è un callable che riceve il potenziale oggetto `Tag` antenato e dovrebbe restituire se è una corrispondenza valida. + +```php +function create(Tag $tag) +{ + $dynamicBlockTag = $tag->closestTag( + [BlockNode::class], + // Condizione: il blocco deve essere dinamico + fn(Tag $blockTag) => $blockTag->node->block->isDynamic(), + ); +} +``` + +L'uso di `closestTag()` consente di creare tag consapevoli del contesto e di imporre un uso corretto all'interno della struttura del vostro template, portando a template più robusti e comprensibili. + + +Segnaposto di `PrintContext::format()` +-------------------------------------- + +Abbiamo spesso usato `PrintContext::format()` per generare codice PHP nei metodi `print()` dei nostri nodi. Accetta una stringa di maschera e argomenti successivi che sostituiscono i segnaposto nella maschera. Ecco un riepilogo dei segnaposto disponibili: + +- **`%node`**: L'argomento deve essere un'istanza di `Node`. Chiama il metodo `print()` del nodo e inserisce la stringa di codice PHP risultante. +- **`%dump`**: L'argomento è un qualsiasi valore PHP. Esporta il valore in codice PHP valido. Adatto per scalari, array, null. + - `$context->format('echo %dump;', 'Hello')` -> `echo 'Hello';` + - `$context->format('$arr = %dump;', [1, 2])` -> `$arr = [1, 2];` +- **`%raw`**: Inserisce l'argomento direttamente nel codice PHP di output senza alcun escaping o modifica. **Usare con cautela**, principalmente per inserire frammenti di codice PHP pregenerati o nomi di variabili. + - `$context->format('%raw = 1;', '$variableName')` -> `$variableName = 1;` +- **`%args`**: L'argomento deve essere `Expression\ArrayNode`. Stampa gli elementi dell'array formattati come argomenti per una chiamata di funzione o metodo (separati da virgole, gestisce argomenti nominati se presenti). + - `$argsNode = new ArrayNode([...]);` + - `$context->format('myFunc(%args);', $argsNode)` -> `myFunc(1, name: 'Joe');` +- **`%line`**: L'argomento deve essere un oggetto `Position` (solitamente `$this->position`). Inserisce un commento PHP `/* line X */` che indica il numero di riga della sorgente. + - `$context->format('echo "Hi" %line;', $this->position)` -> `echo "Hi" /* line 42 */;` +- **`%escape(...)`**: Genera codice PHP che *a runtime* esegue l'escape dell'espressione interna utilizzando le attuali regole di escaping consapevoli del contesto. + - `$context->format('echo %escape(%node);', $variableNode)` +- **`%modify(...)`**: L'argomento deve essere `ModifierNode`. Genera codice PHP che applica i filtri specificati nel `ModifierNode` al contenuto interno, incluso l'escaping consapevole del contesto se non disabilitato con `|noescape`. + - `$context->format('%modify(%node);', $modifierNode, $variableNode)` +- **`%modifyContent(...)`**: Simile a `%modify`, ma destinato alla modifica di blocchi di contenuto catturato (spesso HTML). + +Potete fare riferimento esplicitamente agli argomenti tramite il loro indice (a partire da zero): `%0.node`, `%1.dump`, `%2.raw`, ecc. Ciò consente di riutilizzare un argomento più volte nella maschera senza passarlo ripetutamente a `format()`. Vedete l'esempio del tag `{repeat}`, dove sono stati usati `%0.raw` e `%2.raw`. + + +Esempio di parsing complesso degli argomenti +-------------------------------------------- + +Mentre `parseExpression()`, `parseArguments()`, ecc., coprono molti casi, a volte è necessaria una logica di parsing più complessa utilizzando il `TokenStream` di livello inferiore disponibile tramite `$tag->parser->stream`. + +**Obiettivo:** Creare un tag `{embedYoutube $videoID, width: 640, height: 480}`. Vogliamo analizzare l'ID video richiesto (stringa o variabile) seguito da coppie chiave-valore opzionali per le dimensioni. + +```php +expectArguments(); + $node = $tag->node = new self; + // Analisi dell'ID video richiesto + $node->videoId = $tag->parser->parseExpression(); + + // Analisi delle coppie chiave-valore opzionali + $stream = $tag->parser->stream; // Ottenimento del flusso di token + while ($stream->tryConsume(',')) { // Richiede la separazione con virgola + // Attesa dell'identificatore 'width' o 'height' + $keyToken = $stream->consume(Token::Php_Identifier); + $key = strtolower($keyToken->text); + + $stream->consume(':'); // Attesa del separatore due punti + + $value = $tag->parser->parseExpression(); // Analisi dell'espressione del valore + + if ($key === 'width') { + $node->width = $value; + } elseif ($key === 'height') { + $node->height = $value; + } else { + throw new CompileException("Argomento sconosciuto '$key'. Atteso 'width' o 'height'.", $keyToken->position); + } + } + + return $node; + } +} +``` + +Questo livello di controllo consente di definire sintassi molto specifiche e complesse per i vostri tag personalizzati interagendo direttamente con il flusso di token. + + +Utilizzo di `AuxiliaryNode` +--------------------------- + +Latte fornisce nodi "ausiliari" generici per situazioni speciali durante la generazione del codice o all'interno dei passaggi di compilazione. Sono `AuxiliaryNode` e `Php\Expression\AuxiliaryNode`. + +Considerate `AuxiliaryNode` come un nodo contenitore flessibile che delega le sue funzionalità principali - generazione del codice ed esposizione dei nodi figli - agli argomenti forniti nel suo costruttore: + +- Delega di `print()`: Il primo argomento del costruttore è una **closure** PHP. Quando Latte chiama il metodo `print()` su `AuxiliaryNode`, esegue questa closure fornita. La closure riceve `PrintContext` e qualsiasi nodo passato nel secondo argomento del costruttore, consentendovi di definire una logica di generazione del codice PHP completamente personalizzata a runtime. +- Delega di `getIterator()`: Il secondo argomento del costruttore è un **array di oggetti `Node`**. Quando Latte deve attraversare i figli di `AuxiliaryNode` (ad es. durante i passaggi di compilazione), il suo metodo `getIterator()` fornisce semplicemente i nodi elencati in questo array. + +Esempio: + +```php +$node = new AuxiliaryNode( + // 1. Questa closure diventa il corpo di print() + fn(PrintContext $context, $arg1, $arg2) => $context->format('...%node...%node...', $arg1, $arg2), + + // 2. Questi nodi sono forniti dal metodo getIterator() e passati alla closure sopra + [$argumentNode1, $argumentNode2] +); +``` + +Latte fornisce due tipi distinti basati su dove è necessario inserire il codice generato: + +- `Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode`: Usatelo quando dovete generare un pezzo di codice PHP che rappresenta un'**espressione** +- `Latte\Compiler\Nodes\AuxiliaryNode`: Usatelo per scopi più generali, quando dovete inserire un blocco di codice PHP che rappresenta una o più **istruzioni** + +Un motivo importante per utilizzare `AuxiliaryNode` invece dei nodi standard (come `StaticMethodCallNode`) all'interno del vostro metodo `print()` o del passaggio di compilazione è il **controllo della visibilità per i passaggi di compilazione successivi**, in particolare quelli relativi alla sicurezza come Sandbox. + +Considerate uno scenario: il vostro passaggio di compilazione deve avvolgere un'espressione fornita dall'utente (`$userExpr`) con una chiamata a una funzione ausiliaria specifica e affidabile `myInternalSanitize($userExpr)`. Se create un nodo standard `new FunctionCallNode('myInternalSanitize', [$userExpr])`, sarà completamente visibile per l'attraversamento dell'AST. Se il passaggio Sandbox viene eseguito successivamente e `myInternalSanitize` *non* è nella sua lista consentita, Sandbox potrebbe *bloccare* o modificare questa chiamata, potenzialmente interrompendo la logica interna del vostro tag, anche se *voi*, l'autore del tag, sapete che questa specifica chiamata è sicura e necessaria. Potete quindi generare la chiamata direttamente all'interno della closure di `AuxiliaryNode`. + +```php +use Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode; + +// ... all'interno di print() o di un passaggio di compilazione ... +$wrappedNode = new AuxiliaryNode( + fn(PrintContext $context, $userExpr) => $context->format( + 'myInternalSanitize(%node)', // Generazione diretta del codice PHP + $userExpr, + ), + // IMPORTANTE: Passate comunque il nodo originale dell'espressione utente qui! + [$userExpr], +); +``` + +In questo caso, il passaggio Sandbox vede `AuxiliaryNode`, ma **non analizza il codice PHP generato dalla sua closure**. Non può bloccare direttamente la chiamata `myInternalSanitize` generata *all'interno* della closure. + +Mentre il codice PHP generato stesso è nascosto ai passaggi, gli *input* a questo codice (nodi che rappresentano dati o espressioni utente) **devono essere ancora attraversabili**. Pertanto, il secondo argomento del costruttore di `AuxiliaryNode` è cruciale. **Dovete** passare un array contenente tutti i nodi originali (come `$userExpr` nell'esempio sopra) che la vostra closure utilizza. `getIterator()` di `AuxiliaryNode` **fornirà questi nodi**, consentendo ai passaggi di compilazione come Sandbox di analizzarli per potenziali problemi. + + +Best practice +============= + +- **Scopo chiaro:** Assicuratevi che il vostro tag abbia uno scopo chiaro e necessario. Non create tag per compiti che possono essere facilmente risolti con [filtri |custom-filters] o [funzioni |custom-functions]. +- **Implementate correttamente `getIterator()`:** Implementate sempre `getIterator()` e fornite *riferimenti* (`&`) a *tutti* i nodi figli (argomenti, contenuto) che sono stati analizzati dal template. Questo è essenziale per i passaggi di compilazione, la sicurezza (Sandbox) e potenziali ottimizzazioni future. +- **Proprietà pubbliche per i nodi:** Rendete pubbliche le proprietà contenenti nodi figli, in modo che i passaggi di compilazione possano modificarle se necessario. +- **Usate `PrintContext::format()`:** Sfruttate il metodo `format()` per generare codice PHP. Gestisce le virgolette, esegue correttamente l'escape dei segnaposto e aggiunge automaticamente commenti con il numero di riga. +- **Variabili temporanee (`$__`):** Quando generate codice PHP di runtime che necessita di variabili temporanee (ad es. per memorizzare subtotali, contatori di cicli), usate la convenzione del prefisso `$__` per evitare collisioni con le variabili utente e le variabili interne di Latte `$ʟ_`. +- **Nidificazione e ID univoci:** Se il vostro tag può essere nidificato o necessita di uno stato specifico dell'istanza a runtime, usate `$context->generateId()` all'interno del vostro metodo `print()` per creare suffissi univoci per le vostre variabili temporanee `$__`. +- **Provider per dati esterni:** Usate i provider (registrati tramite `Extension::getProviders()`) per accedere a dati o servizi di runtime (`$this->global->...`) invece di hardcodare valori o fare affidamento sullo stato globale. Usate prefissi del produttore per i nomi dei provider. +- **Considerate gli n:attributi:** Se il vostro tag accoppiato opera logicamente su un singolo elemento HTML, Latte probabilmente fornisce supporto automatico per `n:attributo`. Tenetelo a mente per la comodità dell'utente. Se state creando un tag che modifica un attributo, considerate se un `n:attributo` puro è la forma più appropriata. +- **Test:** Scrivete test per i vostri tag, coprendo sia il parsing di diversi input sintattici sia la correttezza dell'output del **codice PHP** generato. + +Seguendo queste linee guida, potete creare tag personalizzati potenti, robusti e manutenibili che si integrano perfettamente con l'engine di template Latte. + +.[note] +Studiare le classi dei nodi che fanno parte di Latte è il modo migliore per imparare tutti i dettagli del processo di parsing. diff --git a/latte/it/develop.texy b/latte/it/develop.texy index ef255d7e91..0ba94db796 100644 --- a/latte/it/develop.texy +++ b/latte/it/develop.texy @@ -1,70 +1,66 @@ -Pratiche per gli sviluppatori -***************************** +Pratiche di Sviluppo +******************** -Installazione .[#toc-installation] -================================== +Installazione +============= -Il modo migliore per installare Latte è utilizzare un Composer: +Il modo migliore per installare Latte è tramite Composer: ```shell composer require latte/latte ``` -Versioni PHP supportate (si applica alle ultime versioni di Latte): +Versioni PHP supportate (valido per le ultime versioni patch di Latte): -| versione | compatibile con PHP +| versione | compatibile con PHP |-----------------|------------------- -| Latte 3.0 | PHP 8.0 - 8.2 -| Latte 2.11 | PHP 7.1 - 8.2 -| Latte 2.8 - 2.10| PHP 7.1 - 8.1 +| Latte 3.0 | PHP 8.0 – 8.2 -Come renderizzare un modello .[#toc-how-to-render-a-template] -============================================================= +Come Renderizzare un Template +============================= -Come renderizzare un template? Basta usare questo semplice codice: +Come renderizzare un template? Basta questo semplice codice: ```php $latte = new Latte\Engine; -// directory della cache +// directory per la cache $latte->setTempDirectory('/path/to/tempdir'); -$params = [ /* template variables */ ]; +$params = [ /* variabili del template */ ]; // oppure $params = new TemplateParameters(/* ... */); -// rendere all'output +// disegna sull'output $latte->render('template.latte', $params); -// o rendere alla variabile +// disegna nella variabile $output = $latte->renderToString('template.latte', $params); ``` -I parametri possono essere array o meglio ancora [oggetti |#Parameters as a class], che forniranno il controllo del tipo e il suggerimento nell'editor. +I parametri possono essere un array o, ancora meglio, un [oggetto |#Parametri come Classe], che garantirà il controllo dei tipi e i suggerimenti negli editor. .[note] -Si possono trovare esempi di utilizzo nel repository [Latte examples |https://github.com/nette-examples/latte]. +Esempi di utilizzo si trovano anche nel repository [Esempi di Latte |https://github.com/nette-examples/latte]. -Prestazioni e cache .[#toc-performance-and-caching] -=================================================== +Prestazioni e Cache +=================== -I modelli di Latte sono estremamente veloci, perché Latte li compila direttamente in codice PHP e li memorizza su disco. Pertanto, non hanno alcun sovraccarico rispetto ai modelli scritti in PHP puro. +I template in Latte sono estremamente veloci, poiché Latte li compila direttamente in codice PHP e li salva nella cache su disco. Non hanno quindi alcun overhead aggiuntivo rispetto ai template scritti in PHP puro. -La cache viene rigenerata automaticamente ogni volta che si modifica il file sorgente. È quindi possibile modificare comodamente i modelli Latte durante lo sviluppo e vedere immediatamente le modifiche nel browser. È possibile disabilitare questa funzione in un ambiente di produzione e risparmiare un po' di prestazioni: +La cache si rigenera automaticamente ogni volta che modificate il file sorgente. Durante lo sviluppo, potete quindi modificare comodamente i template in Latte e vedere immediatamente le modifiche nel browser. Potete disabilitare questa funzione nell'ambiente di produzione per risparmiare un po' di prestazioni: ```php $latte->setAutoRefresh(false); ``` -Quando viene distribuito su un server di produzione, la generazione iniziale della cache, soprattutto per le applicazioni più grandi, può comprensibilmente richiedere un po' di tempo. Latte ha una prevenzione integrata contro il "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. -Si tratta di una situazione in cui il server riceve un gran numero di richieste simultanee e, poiché la cache di Latte non esiste ancora, tutte le richieste vengono generate contemporaneamente. Il che fa impennare la CPU. -Latte è intelligente e quando ci sono più richieste simultanee, solo il primo thread genera la cache, gli altri aspettano e poi la usano. +Durante il deployment su un server di produzione, la generazione iniziale della cache, specialmente per applicazioni più estese, può ovviamente richiedere un po' di tempo. Latte ha una prevenzione integrata contro la "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Si tratta di una situazione in cui un gran numero di richieste concorrenti avvia Latte e, poiché la cache non esiste ancora, tutte inizierebbero a generarla contemporaneamente. Ciò sovraccaricherebbe eccessivamente il server. Latte è intelligente e, in caso di più richieste concorrenti, solo il primo thread genera la cache, gli altri aspettano e poi la utilizzano. -Parametri come classe .[#toc-parameters-as-a-class] -=================================================== +Parametri come Classe +===================== -Meglio che passare le variabili al template come array è creare una classe. Si ottiene una [notazione sicura per il tipo |type-system], un [buon suggerimento nell'IDE |recipes#Editors and IDE] e un modo per [registrare filtri |extending-latte#Filters Using the Class] e [funzioni |extending-latte#Functions Using the Class]. +Meglio che passare le variabili al template come array è creare una classe. Otterrete così una [scrittura type-safe |type-system], [suggerimenti utili nell'IDE |recipes#Editor e IDE] e un modo per [registrare filtri |custom-filters#Filtri che utilizzano una classe con attributi] e [funzioni |custom-functions#Funzioni che utilizzano una classe con attributi]. ```php class MailTemplateParameters @@ -88,12 +84,12 @@ $latte->render('mail.latte', new MailTemplateParameters( ``` -Disabilitare la cancellazione automatica di una variabile .[#toc-disabling-auto-escaping-of-variable] -===================================================================================================== +Disabilitazione dell'Auto-Escaping di una Variabile +=================================================== -Se la variabile contiene una stringa HTML, è possibile contrassegnarla in modo che Latte non esegua automaticamente (e quindi doppiamente) l'escape. Questo evita la necessità di specificare `|noescape` nel modello. +Se una variabile contiene una stringa HTML, potete contrassegnarla in modo che Latte non la esegua automaticamente (e quindi doppiamente) l'escape. Eviterete così la necessità di specificare `|noescape` nel template. -Il modo più semplice è avvolgere la stringa in un oggetto `Latte\Runtime\Html`: +Il modo più semplice è racchiudere la stringa in un oggetto `Latte\Runtime\Html`: ```php $params = [ @@ -101,7 +97,7 @@ $params = [ ]; ``` -Latte inoltre non esegue l'escape di tutti gli oggetti che implementano l'interfaccia `Latte\HtmlStringable`. È quindi possibile creare una propria classe il cui metodo `__toString()` restituirà un codice HTML che non sarà sottoposto a escape automatico: +Latte inoltre non esegue l'escape di tutti gli oggetti che implementano l'interfaccia `Latte\HtmlStringable`. Potete quindi creare la vostra classe, il cui metodo `__toString()` restituirà codice HTML che non verrà automaticamente escapato: ```php class Emphasis extends Latte\HtmlStringable @@ -123,32 +119,85 @@ $params = [ ``` .[warning] -Il metodo `__toString` deve restituire un codice HTML corretto e fornire l'escape dei parametri, altrimenti si potrebbe verificare una vulnerabilità XSS! +Il metodo `__toString` deve restituire HTML corretto e garantire l'escaping dei parametri, altrimenti può verificarsi una vulnerabilità XSS! -Come estendere Latte con filtri, tag, ecc. .[#toc-how-to-extend-latte-with-filters-tags-etc] -============================================================================================ +Come Estendere Latte con Filtri, Tag, ecc. +========================================== -Come aggiungere a Latte un filtro, una funzione, un tag, ecc. personalizzati? Scopritelo nel capitolo [Estensione di Latte |extending Latte]. -Se volete riutilizzare le vostre modifiche in progetti diversi o se volete condividerle con altri, dovete [creare un'estensione |creating-extension]. +Come aggiungere a Latte un filtro, una funzione, un tag personalizzato, ecc.? Questo è trattato nel capitolo [estendere Latte |extending-latte]. Se volete riutilizzare le vostre modifiche in diversi progetti o condividerle con altri, dovreste [creare un'estensione |extending-latte#Latte Extension]. -Qualsiasi codice nel modello `{php ...}` .{data-version:3.0}{toc: RawPhpExtension} -================================================================================== +Codice PHP Arbitrario nel Template `{php ...}` .{toc: RawPhpExtension} +====================================================================== -Solo le espressioni PHP possono essere scritte all'interno del tag [`{do}` |tags#do] quindi non è possibile, ad esempio, inserire costrutti come `if ... else` o dichiarazioni terminate con punto e virgola. +All'interno del tag [`{do}` |tags#do] è possibile scrivere solo espressioni PHP, non potete quindi inserire costrutti come `if ... else` o istruzioni terminate da punto e virgola. -Tuttavia, è possibile registrare l'estensione `RawPhpExtension`, che aggiunge il tag `{php ...}`, che può essere usato per inserire qualsiasi codice PHP a rischio dell'autore del template. +Tuttavia, potete registrare l'estensione `RawPhpExtension`, che aggiunge il tag `{php ...}`. Con questo potete inserire qualsiasi codice PHP. Non si applicano ad esso regole della modalità sandbox, l'uso è quindi responsabilità dell'autore del template. ```php $latte->addExtension(new Latte\Essential\RawPhpExtension); ``` -Traduzione nei template .{data-version:3.0}{toc: TranslatorExtension} -===================================================================== +Controllo del Codice Generato .{data-version:3.0.7} +=================================================== + +Latte compila i template in codice PHP. Naturalmente, si preoccupa che il codice generato sia sintatticamente valido. Tuttavia, quando si utilizzano estensioni di terze parti o `RawPhpExtension`, Latte non può garantire la correttezza del file generato. Inoltre, è possibile scrivere in PHP codice che, sebbene sintatticamente corretto, è vietato (ad esempio, l'assegnazione di un valore alla variabile `$this`) e causa un PHP Compile Error. Se scrivete tale operazione in un template, finirà anche nel codice PHP generato. Poiché in PHP esistono circa duecento diverse operazioni vietate, Latte non ha l'ambizione di rilevarle. Sarà lo stesso PHP a segnalarle durante il rendering, il che di solito non è un problema. + +Ci sono però situazioni in cui volete sapere già al momento della compilazione del template che non contiene alcun PHP Compile Error. Soprattutto se i template possono essere modificati dagli utenti, o se utilizzate [Sandbox |sandbox]. In tal caso, fate controllare i template già durante la compilazione. Questa funzionalità si attiva con il metodo `Engine::enablePhpLint()`. Poiché per il controllo necessita di chiamare l'eseguibile PHP, passate il percorso ad esso come parametro: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); + +try { + $latte->compile('home.latte'); +} catch (Latte\CompileException $e) { + // cattura gli errori in Latte e anche Compile Error in PHP + echo 'Errore: ' . $e->getMessage(); +} +``` + + +Impostazioni Regionali .{data-version:3.0.18}{toc: Locale} +========================================================== -Utilizzare l'estensione `TranslatorExtension` per aggiungere [`{_...}` |tags#_], [`{translate}` |tags#translate] e il filtro [`translate` |filters#translate] al template. Sono usati per tradurre valori o parti del template in altre lingue. Il parametro è il metodo (callable PHP) che esegue la traduzione: +Latte consente di impostare le impostazioni regionali, che influenzano la formattazione di numeri, date e l'ordinamento. Si imposta tramite il metodo `setLocale()`. L'identificatore delle impostazioni regionali segue lo standard IETF language tag, utilizzato dall'estensione PHP `intl`. È composto dal codice della lingua e opzionalmente dal codice del paese, ad es. `en_US` per l'inglese negli Stati Uniti, `de_DE` per il tedesco in Germania, `it_IT` per l'italiano in Italia, ecc. + +```php +$latte = new Latte\Engine; +$latte->setLocale('it_IT'); +``` + +L'impostazione delle impostazioni regionali influisce sui filtri [localDate |filters#localDate], [sort |filters#sort], [number |filters#number] e [bytes |filters#bytes]. + +.[note] +Richiede l'estensione PHP `intl`. L'impostazione in Latte non influisce sull'impostazione globale delle impostazioni regionali in PHP. + + +Modalità Rigorosa .{data-version:3.0.8}{toc: strict-mode} +========================================================= + +Nella modalità di parsing rigorosa, Latte controlla se mancano tag HTML di chiusura e vieta anche l'uso della variabile `$this`. La attivate così: + +```php +$latte = new Latte\Engine; +$latte->setStrictParsing(); +``` + +La generazione di template con l'header `declare(strict_types=1)` si attiva così: + +```php +$latte = new Latte\Engine; +$latte->setStrictTypes(); +``` + + +Traduzione nei Template .{toc: TranslatorExtension} +=================================================== + +Usando l'estensione `TranslatorExtension` aggiungete al template i tag [`{_...}` |tags#], [`{translate}` |tags#translate] e il filtro [`translate` |filters#translate]. Servono per tradurre valori o parti del template in altre lingue. Come parametro specifichiamo il metodo (PHP callable) che esegue la traduzione: ```php class MyTranslator @@ -158,7 +207,7 @@ class MyTranslator public function translate(string $original): string { - // crea $tradotto da $originale secondo $this->lang + // da $original creiamo $translated secondo $this->lang return $translated; } } @@ -170,7 +219,7 @@ $extension = new Latte\Essential\TranslatorExtension( $latte->addExtension($extension); ``` -Il traduttore viene chiamato a runtime quando il template viene reso. Tuttavia, Latte può tradurre tutti i testi statici durante la compilazione del template. Ciò consente di risparmiare sulle prestazioni, perché ogni stringa viene tradotta una sola volta e la traduzione risultante viene scritta nel file compilato. In questo modo si creano più versioni compilate del modello nella cartella cache, una per ogni lingua. Per farlo, è sufficiente specificare la lingua come secondo parametro: +Il traduttore viene chiamato a runtime durante il rendering del template. Latte, tuttavia, può tradurre tutti i testi statici già durante la compilazione del template. Ciò consente di risparmiare prestazioni, poiché ogni stringa viene tradotta una sola volta e la traduzione risultante viene scritta nella forma compilata. Nella directory della cache verranno quindi create più versioni compilate del template, una per ogni lingua. Per fare ciò, basta specificare la lingua come secondo parametro: ```php $extension = new Latte\Essential\TranslatorExtension( @@ -179,9 +228,9 @@ $extension = new Latte\Essential\TranslatorExtension( ); ``` -Per testo statico si intende, ad esempio, `{_'hello'}` o `{translate}hello{/translate}`. Il testo non statico, come `{_$foo}`, continuerà a essere tradotto in fase di esecuzione. +Per testo statico si intende ad esempio `{_'hello'}` o `{translate}hello{/translate}`. I testi non statici, come `{_$foo}`, continueranno ad essere tradotti a runtime. -Il template può anche passare parametri aggiuntivi al traduttore tramite `{_$original, foo: bar}` o `{translate foo: bar}`, che riceve come array `$params`: +Al traduttore è possibile passare dal template anche parametri aggiuntivi usando `{_$original, foo: bar}` o `{translate foo: bar}`, che riceverà come array `$params`: ```php public function translate(string $original, ...$params): string @@ -191,66 +240,73 @@ public function translate(string $original, ...$params): string ``` -Debug e Tracy .[#toc-debugging-and-tracy] -========================================= +Debugging e Tracy +================= -Latte cerca di rendere lo sviluppo il più piacevole possibile. Per il debug, sono disponibili tre tag [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak] e [`{trace}` |tags#trace]. +Latte cerca di rendere lo sviluppo il più piacevole possibile. Direttamente per scopi di debug esistono tre tag [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak] e [`{trace}` |tags#trace]. -Il massimo del comfort si ottiene installando l'ottimo [strumento di debug Tracy |tracy:] e attivando il plugin Latte: +Il massimo comfort si ottiene installando anche l'eccellente [strumento di debug Tracy |tracy:] e attivando l'add-on per Latte: ```php -// abilita Tracy +// attiva Tracy Tracy\Debugger::enable(); $latte = new Latte\Engine; -// attiva l'estensione di Tracy +// attiva l'estensione per Tracy $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension); ``` -Ora vedrete tutti gli errori in un'ordinata schermata rossa, compresi gli errori nei modelli con evidenziazione di righe e colonne ([video |https://github.com/nette/tracy/releases/tag/v2.9.0]). -Allo stesso tempo, nell'angolo in basso a destra della cosiddetta barra di Tracy, appare una scheda per Latte, dove si possono vedere chiaramente tutti i modelli renderizzati e le loro relazioni (compresa la possibilità di fare clic nel modello o nel codice compilato), nonché le variabili: +Ora tutti gli errori verranno visualizzati in una chiara schermata rossa, inclusi gli errori nei template con evidenziazione della riga e della colonna ([video |https://github.com/nette/tracy/releases/tag/v2.9.0]). Allo stesso tempo, nell'angolo in basso a destra nella cosiddetta Tracy Bar apparirà una scheda per Latte, dove sono chiaramente visibili tutti i template renderizzati e le loro relazioni reciproche (inclusa la possibilità di fare clic per passare al template o al codice compilato) e anche le variabili: [* latte-debugging.webp *] -Dal momento che Latte compila i template in codice PHP leggibile, si può comodamente passare attraverso di essi nel proprio IDE. +Poiché Latte compila i template in codice PHP leggibile, potete comodamente eseguirne il debug passo passo nel vostro IDE. -Linter: Convalidare la sintassi dei template .{data-version:2.11}{toc: Linter} -============================================================================== +Linter: Validazione della Sintassi dei Template .{toc: Linter} +============================================================== -Lo strumento Linter aiuta a esaminare tutti i template e a verificare la presenza di errori di sintassi. Viene lanciato dalla console: +Lo strumento Linter vi aiuta a scorrere tutti i template e a verificare che non contengano errori di sintassi. Si avvia dalla console: ```shell -vendor/bin/latte-lint +vendor/bin/latte-lint ``` -Se si usano tag personalizzati, creare anche un Linter personalizzato, ad esempio `custom-latte-lint`: +Il parametro `--strict` attiva la [modalità rigorosa |#strict-mode]. + +Se utilizzate tag personalizzati, create anche la vostra versione di Linter, ad es. `custom-latte-lint`: ```php #!/usr/bin/env php scanDirectory($path); +$path = $argv[1] ?? '.'; -$engine = new Latte\Engine; -// registra qui le singole estensioni -$engine->addExtension(/* ... */); +$linter = new Latte\Tools\Linter; +$latte = $linter->getEngine(); +// aggiungi qui le tue estensioni individuali +$latte->addExtension(/* ... */); -$path = $argv[1]; -$linter = new Latte\Tools\Linter(engine: $engine); $ok = $linter->scanDirectory($path); exit($ok ? 0 : 1); ``` +In alternativa, potete passare il vostro oggetto `Latte\Engine` personalizzato a Linter: + +```php +$latte = new Latte\Engine; +// qui configuriamo l'oggetto $latte +$linter = new Latte\Tools\Linter(engine: $latte); +``` + -Caricare modelli da una stringa .[#toc-loading-templates-from-a-string] -======================================================================= +Caricamento dei Template da Stringa +=================================== -Avete bisogno di caricare modelli da stringhe invece che da file, magari a scopo di test? [StringLoader |extending-latte#stringloader] vi aiuterà: +Avete bisogno di caricare template da stringhe invece che da file, ad esempio per scopi di test? [StringLoader |loaders#StringLoader] vi aiuterà: ```php $latte->setLoader(new Latte\Loaders\StringLoader([ @@ -262,10 +318,10 @@ $latte->render('main.file', $params); ``` -Gestore delle eccezioni .[#toc-exception-handler] -================================================= +Handler delle Eccezioni +======================= -È possibile definire un proprio gestore per le eccezioni previste. Le eccezioni sollevate all'interno di [`{try}` |tags#try] e nella [sandbox |sandbox] vengono passate ad esso. +Potete definire il vostro handler personalizzato per le eccezioni attese. Gli verranno passate le eccezioni sorte all'interno di [`{try}` |tags#try] e nel [sandbox |sandbox]. ```php $loggingHandler = function (Throwable $e, Latte\Runtime\Template $template) use ($logger) { @@ -277,17 +333,17 @@ $latte->setExceptionHandler($loggingHandler); ``` -Ricerca automatica del layout .[#toc-automatic-layout-lookup] -============================================================= +Ricerca Automatica del Layout +============================= -Utilizzando il tag [`{layout}` |template-inheritance#layout-inheritance] il template determina il suo template padre. È anche possibile fare in modo che il layout venga cercato automaticamente, il che semplificherà la scrittura dei template, poiché non dovranno includere il tag `{layout}`. +Usando il tag [`{layout}` |template-inheritance#Ereditarietà del layout layout] il template specifica il suo template genitore. È anche possibile far cercare il layout automaticamente, il che semplifica la scrittura dei template, poiché non sarà necessario specificare il tag `{layout}` in essi. -Questo si ottiene come segue: +Ciò si ottiene nel seguente modo: ```php $finder = function (Latte\Runtime\Template $template) { if (!$template->getReferenceType()) { - // restituisce il percorso del file del modello padre + // restituisce il percorso al file di layout return 'automatic.layout.latte'; } }; @@ -296,4 +352,4 @@ $latte = new Latte\Engine; $latte->addProvider('coreParentFinder', $finder); ``` -Se il modello non deve avere un layout, lo indicherà con il tag `{layout none}`. +Se il template non deve avere un layout, lo comunica con il tag `{layout none}`. diff --git a/latte/it/extending-latte.texy b/latte/it/extending-latte.texy index 1995a0dfde..b230f44ca9 100644 --- a/latte/it/extending-latte.texy +++ b/latte/it/extending-latte.texy @@ -1,285 +1,227 @@ -Latte allungabile -***************** +Estensione di Latte +******************* .[perex] -Latte è molto flessibile e può essere esteso in molti modi: si possono aggiungere filtri personalizzati, funzioni, tag, caricatori, ecc. Vi mostriamo come fare. +Latte è progettato pensando all'estensibilità. Sebbene il suo set standard di tag, filtri e funzioni copra molti casi d'uso, spesso è necessario aggiungere logica personalizzata specifica o strumenti di supporto. Questa pagina fornisce una panoramica dei modi per estendere Latte in modo che corrisponda perfettamente ai requisiti del vostro progetto - da semplici helper a nuove sintassi complesse. -Questo capitolo descrive i diversi modi per estendere Latte. Se volete riutilizzare le vostre modifiche in progetti diversi o se volete condividerle con altri, dovete [creare una cosiddetta estensione |creating-extension]. +Modi per estendere Latte +======================== -Quante strade portano a Roma? .[#toc-how-many-roads-lead-to-rome] -================================================================= +Ecco una rapida panoramica dei principali modi in cui potete personalizzare ed estendere Latte: -Poiché alcuni dei modi di estendere Latte possono essere mescolati, proviamo prima a spiegare le differenze tra loro. Per esempio, proviamo a implementare un generatore di *Lorem ipsum*, al quale viene passato il numero di parole da generare. +- **[Filtri personalizzati |custom-filters]:** Per formattare o trasformare i dati direttamente nell'output del template (es. `{$var|myFilter}`). Ideale per compiti come la formattazione delle date, la modifica del testo o l'applicazione di un escaping specifico. Potete anche usarli per modificare blocchi più grandi di contenuto HTML avvolgendo il contenuto in un [`{block}` |tags#block] anonimo e applicandovi un filtro personalizzato. +- **[Funzioni personalizzate |custom-functions]:** Per aggiungere logica riutilizzabile che può essere chiamata all'interno delle espressioni nel template (es. `{myFunction($arg1, $arg2)}`). Utile per calcoli, accesso a funzioni di supporto dell'applicazione o generazione di piccole parti di contenuto. +- **[Tag personalizzati |custom-tags]:** Per creare costrutti linguistici completamente nuovi (`{mytag}...{/mytag}` o `n:mytag`). I tag offrono la massima flessibilità, consentendo di definire strutture personalizzate, controllare il parsing del template e implementare logiche di rendering complesse. +- **[Passaggi di compilazione |compiler-passes]:** Funzioni che modificano l'albero sintattico astratto (AST) del template dopo il parsing, ma prima della generazione del codice PHP. Vengono utilizzati per ottimizzazioni avanzate, controlli di sicurezza (come Sandbox) o modifiche automatiche del codice. +- **[Loader personalizzati |loaders]:** Per modificare il modo in cui Latte cerca e carica i file dei template (es. caricamento da database, storage crittografato, ecc.). -Il costrutto principale del linguaggio Latte è il tag. Possiamo implementare un generatore estendendo Latte con un nuovo tag: +La scelta del metodo di estensione corretto è fondamentale. Prima di creare un tag complesso, considerate se un filtro o una funzione più semplice potrebbero essere sufficienti. Vediamo un esempio: implementazione di un generatore *Lorem ipsum* che accetta come argomento il numero di parole da generare. -```latte -{lipsum 40} -``` - -Il tag funzionerà benissimo. Tuttavia, il generatore sotto forma di tag potrebbe non essere abbastanza flessibile, perché non può essere usato in un'espressione. Comunque, nella pratica, raramente si ha bisogno di generare tag; e questa è una buona notizia, perché i tag sono un modo più complicato di estendere. - -Proviamo a creare un filtro invece di un tag: - -```latte -{=40|lipsum} -``` - -Anche in questo caso, si tratta di un'opzione valida. Ma il filtro dovrebbe trasformare il valore passato in qualcos'altro. Qui usiamo il valore `40`, che indica il numero di parole generate, come argomento del filtro, non come valore da trasformare. +- **Come tag?** `{lipsum 40}` - Possibile, ma i tag sono più adatti per strutture di controllo o per generare markup complessi. I tag non possono essere utilizzati direttamente nelle espressioni. +- **Come filtro?** `{=40|lipsum}` - Tecnicamente funziona, ma i filtri sono progettati per *trasformare* il valore di input. Qui, `40` è un *argomento*, non un valore che viene trasformato. Questo sembra semanticamente errato. +- **Come funzione?** `{lipsum(40)}` - Questa è la soluzione più naturale! Le funzioni accettano argomenti e restituiscono valori, il che è ideale per l'uso in qualsiasi espressione: `{var $text = lipsum(40)}`. -Proviamo quindi a usare la funzione +**Raccomandazione generale:** Utilizzate le funzioni per calcoli/generazione, i filtri per la trasformazione e i tag per nuovi costrutti linguistici o markup complessi. Utilizzate i passaggi per la manipolazione dell'AST e i loader per ottenere i template. -```latte -{lipsum(40)} -``` -Ecco fatto! Per questo particolare esempio, la creazione di una funzione è il punto di estensione ideale da utilizzare. È possibile chiamarla ovunque sia accettata un'espressione, ad esempio: +Registrazione diretta +===================== -```latte -{var $text = lipsum(40)} -``` +Per strumenti di supporto specifici del progetto o estensioni rapide, Latte consente la registrazione diretta di filtri e funzioni nell'oggetto `Latte\Engine`. - -Filtri .[#toc-filters] -====================== - -Creare un filtro registrando il suo nome e qualsiasi callable PHP, come ad esempio una funzione: +Per registrare un filtro, utilizzate il metodo `addFilter()`. Il primo argomento della vostra funzione filtro sarà il valore prima del carattere `|` e gli argomenti successivi sono quelli passati dopo i due punti `:`. ```php $latte = new Latte\Engine; -$latte->addFilter('shortify', fn(string $s) => mb_substr($s, 0, 10)); // accorcia il testo a 10 caratteri -``` -In questo caso sarebbe meglio che il filtro ottenesse un parametro aggiuntivo: +// Definizione del filtro (oggetto richiamabile: funzione, metodo statico, ecc.) +$myTruncate = fn(string $s, int $length = 50) => mb_substr($s, 0, $length); -```php -$latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); -``` - -Lo usiamo in un modello come questo: +// Registrazione +$latte->addFilter('truncate', $myTruncate); -```latte -

                                                                          {$text|shortify}

                                                                          -

                                                                          {$text|shortify:100}

                                                                          +// Utilizzo nel template: {$text|truncate} o {$text|truncate:100} ``` -Come si può vedere, la funzione riceve il lato sinistro del filtro prima della pipe `|` as the first argument and the arguments passed to the filter after `:` come prossimo argomento. - -Naturalmente, la funzione che rappresenta il filtro può accettare un numero qualsiasi di parametri e sono supportati anche i parametri variabili. - - -Filtri che utilizzano la classe .[#toc-filters-using-the-class] ---------------------------------------------------------------- - -Il secondo modo per definire un filtro è quello di [usare la classe |develop#Parameters as a class]. Si crea un metodo con l'attributo `TemplateFilter`: +Potete anche registrare un **Filter Loader**, una funzione che fornisce dinamicamente oggetti richiamabili di filtri in base al nome richiesto: ```php -class TemplateParameters -{ - public function __construct( - // parametri - ) {} - - #[Latte\Attributes\TemplateFilter] - public function shortify(string $s, int $len = 10): string - { - return mb_substr($s, 0, $len); - } -} - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); +$latte->addFilterLoader(fn(string $name) => /* restituisce un oggetto richiamabile o null */); ``` -Se si utilizza PHP 7.x e Latte 2.x, utilizzare l'annotazione `/** @filter */` invece dell'attributo. - -Caricatore di filtri .[#toc-filter-loader] ------------------------------------------- - -Invece di registrare i singoli filtri, si può creare un cosiddetto caricatore, che è una funzione che viene chiamata con il nome del filtro come parametro e restituisce il suo callable PHP, oppure null. +Per registrare una funzione utilizzabile nelle espressioni del template, utilizzate `addFunction()`. ```php -$latte->addFilterLoader([new Filters, 'load']); +$latte = new Latte\Engine; +// Definizione della funzione +$isWeekend = fn(DateTimeInterface $date) => $date->format('N') >= 6; -class Filters -{ - public function load(string $filter): ?callable - { - if (in_array($filter, get_class_methods($this))) { - return [$this, $filter]; - } - return null; - } - - public function shortify($s, $len = 10) - { - return mb_substr($s, 0, $len); - } - - // ... -} +// Registrazione +$latte->addFunction('isWeekend', $isWeekend); + +// Utilizzo nel template: {if isWeekend($myDate)}Weekend!{/if} ``` +Per maggiori informazioni, consultate le sezioni [Creazione di filtri personalizzati |custom-filters] e [Funzioni |custom-functions]. -Filtri contestuali .[#toc-contextual-filters] ---------------------------------------------- -Un filtro contestuale è un filtro che accetta un oggetto [api:Latte\Runtime\FilterInfo] come primo parametro, seguito da altri parametri come nel caso dei filtri classici. Viene registrato allo stesso modo, Latte stesso riconosce che il filtro è contestuale: +Metodo robusto: Estensione Latte .{toc: Latte Extension} +======================================================== -```php -use Latte\Runtime\FilterInfo; +Sebbene la registrazione diretta sia semplice, il modo standard e raccomandato per impacchettare e distribuire le estensioni Latte è tramite le classi **Extension**. Un'estensione funge da punto di configurazione centrale per la registrazione di più tag, filtri, funzioni, passaggi di compilazione e altri elementi. -$latte->addFilter('foo', function (FilterInfo $info, string $str): string { - // ... -}); -``` +Perché usare le estensioni? -I filtri contestuali possono rilevare e modificare il tipo di contenuto che ricevono nella variabile `$info->contentType`. Se il filtro viene chiamato classicamente su una variabile (ad esempio `{$var|foo}`), `$info->contentType` conterrà null. +- **Organizzazione:** Mantiene insieme le estensioni correlate (tag, filtri, ecc. per una funzionalità specifica) in un'unica classe. +- **Riutilizzabilità e condivisione:** Impacchettate facilmente le vostre estensioni per l'uso in altri progetti o per la condivisione con la comunità (ad es. tramite Composer). +- **Piena potenza:** Tag personalizzati e passaggi di compilazione *possono essere registrati solo* tramite le estensioni. -Il filtro deve innanzitutto verificare se il tipo di contenuto della stringa in ingresso è supportato. Può anche modificarlo. Esempio di filtro che accetta testo (o null) e restituisce HTML: + +Registrazione di un'estensione +------------------------------ + +Un'estensione viene registrata in Latte utilizzando il metodo `addExtension()` (o tramite il [file di configurazione |application:configuration#Template Latte]): ```php -use Latte\Runtime\FilterInfo; - -$latte->addFilter('money', function (FilterInfo $info, float $amount): string { - // per prima cosa controlliamo se il tipo di contenuto dell'input è text - if (!in_array($info->contentType, [null, ContentType::Text])) { - throw new Exception("Filtro |money usato in un tipo di contenuto incompatibile $info->contentType."); - } - - // cambia il tipo di contenuto in HTML - $info->contentType = ContentType::Html; - return "$num Kč"; -}); +$latte = new Latte\Engine; +$latte->addExtension(new MyProjectExtension); ``` -.[note] -In questo caso, il filtro deve garantire il corretto escape dei dati. +Se registrate più estensioni e queste definiscono tag, filtri o funzioni con lo stesso nome, l'estensione aggiunta per ultima ha la precedenza. Ciò significa anche che le vostre estensioni possono sovrascrivere tag/filtri/funzioni nativi. -Tutti i filtri che vengono utilizzati sopra i [blocchi |tags#block] (ad esempio come `{block|foo}...{/block}`) devono essere contestuali. +Ogni volta che apportate una modifica alla classe e l'aggiornamento automatico non è disabilitato, Latte ricompilerà automaticamente i vostri template. -Funzioni .[#toc-functions] -========================== +Creazione di un'estensione +-------------------------- -Per impostazione predefinita, tutte le funzioni native di PHP possono essere utilizzate in Latte, a meno che la sandbox non le disabiliti. Ma è anche possibile definire le proprie funzioni. Queste possono sovrascrivere le funzioni native. +Per creare la vostra estensione, dovete creare una classe che erediti da [api:Latte\Extension]. Per avere un'idea di come appare un'estensione del genere, date un'occhiata alla [CoreExtension](https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php) integrata. -Creare una funzione registrando il suo nome e qualsiasi callable PHP: +Diamo un'occhiata ai metodi che potete implementare: -```php -$latte = new Latte\Engine; -$latte->addFunction('random', function (...$args) { - return $args[array_rand($args)]; -}); -``` -L'uso è quindi lo stesso di quando si chiama la funzione PHP: +beforeCompile(Latte\Engine $engine): void .[method] +--------------------------------------------------- -```latte -{random(apple, orange, lemon)} // prints for example: apple -``` +Chiamato prima della compilazione del template. Il metodo può essere utilizzato, ad esempio, per inizializzazioni relative alla compilazione. -Funzioni che utilizzano la classe .[#toc-functions-using-the-class] -------------------------------------------------------------------- +getTags(): array .[method] +-------------------------- -Il secondo modo per definire una funzione è quello di [usare la classe |develop#Parameters as a class]. Creiamo un metodo con l'attributo `TemplateFunction`: +Chiamato durante la compilazione del template. Restituisce un array associativo *nome tag => oggetto richiamabile*, che sono funzioni per il parsing dei tag. [Maggiori informazioni |custom-tags]. ```php -class TemplateParameters +public function getTags(): array { - public function __construct( - // parametri - ) {} - - #[Latte\Attributes\TemplateFunction] - public function random(...$args) - { - return $args[array_rand($args)]; - } + return [ + 'foo' => FooNode::create(...), + 'bar' => BarNode::create(...), + 'n:baz' => NBazNode::create(...), + // ... + ]; } - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); ``` -Se si utilizza PHP 7.x e Latte 2.x, utilizzare l'annotazione `/** @function */` invece dell'attributo. +Il tag `n:baz` rappresenta un puro [n:attributo |syntax#n:attributi], cioè un tag che può essere scritto solo come attributo. +Per i tag `foo` e `bar`, Latte riconosce automaticamente se sono tag accoppiati e, in tal caso, possono essere scritti automaticamente utilizzando n:attributi, incluse le varianti con i prefissi `n:inner-foo` e `n:tag-foo`. -Caricatori .[#toc-loaders] -========================== +L'ordine di esecuzione di tali n:attributi è determinato dal loro ordine nell'array restituito dal metodo `getTags()`. Quindi `n:foo` viene sempre eseguito prima di `n:bar`, anche se gli attributi nel tag HTML sono elencati nell'ordine opposto come `
                                                                          `. -I caricatori sono responsabili del caricamento dei template da una fonte, come un file system. Vengono impostati con il metodo `setLoader()`: +Se è necessario specificare l'ordine degli n:attributi tra più estensioni, utilizzate il metodo helper `order()`, dove il parametro `before` xor `after` specifica quali tag sono ordinati prima o dopo il tag. ```php -$latte->setLoader(new MyLoader); +public function getTags(): array +{ + return [ + 'foo' => self::order(FooNode::create(...), before: 'bar')] + 'bar' => self::order(BarNode::create(...), after: ['block', 'snippet'])] + ]; +} ``` -I caricatori incorporati sono: +getPasses(): array .[method] +---------------------------- -FileLoader .[#toc-fileloader] ------------------------------ +Chiamato durante la compilazione del template. Restituisce un array associativo *nome passaggio => oggetto richiamabile*, che sono funzioni che rappresentano i cosiddetti [passaggi di compilazione |compiler-passes], che attraversano e modificano l'AST. -Caricatore predefinito. Carica i modelli dal filesystem. - -L'accesso ai file può essere limitato impostando la directory di base: +Anche qui è possibile utilizzare il metodo helper `order()`. Il valore dei parametri `before` o `after` può essere `*` con il significato di prima/dopo tutto. ```php -$latte->setLoader(new Latte\Loaders\FileLoader($templateDir)); -$latte->render('test.latte'); +public function getPasses(): array +{ + return [ + 'optimize' => Passes::optimizePass(...), + 'sandbox' => self::order($this->sandboxPass(...), before: '*'), + // ... + ]; +} ``` -StringLoader .[#toc-stringloader] ---------------------------------- +beforeRender(Latte\Engine $engine): void .[method] +-------------------------------------------------- -Carica i modelli dalle stringhe. Questo caricatore è molto utile per i test unitari. Può anche essere usato per piccoli progetti in cui può avere senso memorizzare tutti i template in un singolo file PHP. +Chiamato prima di ogni rendering del template. Il metodo può essere utilizzato, ad esempio, per inizializzare le variabili utilizzate durante il rendering. -```php -$latte->setLoader(new Latte\Loaders\StringLoader([ - 'main.file' => '{include other.file}', - 'other.file' => '{if true} {$var} {/if}', -])); -$latte->render('main.file'); -``` +getFilters(): array .[method] +----------------------------- -Utilizzo semplificato: +Chiamato prima del rendering del template. Restituisce i filtri come un array associativo *nome filtro => oggetto richiamabile*. [Maggiori informazioni |custom-filters]. ```php -$template = '{if true} {$var} {/if}'; -$latte->setLoader(new Latte\Loaders\StringLoader); -$latte->render($template); +public function getFilters(): array +{ + return [ + 'batch' => $this->batchFilter(...), + 'trim' => $this->trimFilter(...), + // ... + ]; +} ``` -Creare un caricatore personalizzato .[#toc-creating-a-custom-loader] --------------------------------------------------------------------- - -Loader è una classe che implementa l'interfaccia [api:Latte\Loader]. +getFunctions(): array .[method] +------------------------------- +Chiamato prima del rendering del template. Restituisce le funzioni come un array associativo *nome funzione => oggetto richiamabile*. [Maggiori informazioni |custom-functions]. -Tag .[#toc-tags] -================ +```php +public function getFunctions(): array +{ + return [ + 'clamp' => $this->clampFunction(...), + 'divisibleBy' => $this->divisibleByFunction(...), + // ... + ]; +} +``` -Una delle caratteristiche più interessanti del motore di template è la possibilità di definire nuovi costrutti linguistici usando i tag. È anche una funzionalità più complessa ed è necessario capire come funziona internamente Latte. -Nella maggior parte dei casi, tuttavia, il tag non è necessario: -- se deve generare un output, usare invece [function |#functions] -- se deve modificare un input e restituirlo, usare il [filtro |#filters] -- se deve modificare un'area di testo, avvolgerla con un tag [`{block}` |tags#block] e usare un [filtro |#Contextual Filters] -- se non deve generare alcun output, ma solo richiamare una funzione, chiamarla con [`{do}` |tags#do] +getProviders(): array .[method] +------------------------------- -Se si vuole ancora creare un tag, bene! Tutti gli elementi essenziali si trovano in [Creare un'estensione |creating-extension]. +Chiamato prima del rendering del template. Restituisce un array di provider, che sono solitamente oggetti utilizzati dai tag in fase di esecuzione. Vi si accede tramite `$this->global->...`. [Maggiori informazioni |custom-tags#Introduzione ai provider]. +```php +public function getProviders(): array +{ + return [ + 'myFoo' => $this->foo, + 'myBar' => $this->bar, + // ... + ]; +} +``` -Passaggi del compilatore .[#toc-compiler-passes] -================================================ -I passi del compilatore sono funzioni che modificano gli AST o raccolgono informazioni in essi. In Latte, per esempio, una sandbox è implementata in questo modo: attraversa tutti i nodi di un AST, trova le chiamate a funzioni e metodi e le sostituisce con chiamate controllate. +getCacheKey(Latte\Engine $engine): mixed .[method] +-------------------------------------------------- -Come per i tag, si tratta di una funzionalità più complessa e occorre capire come funziona Latte sotto il cofano. Tutti gli elementi essenziali si trovano nel capitolo [Creare un'estensione |creating-extension]. +Chiamato prima del rendering del template. Il valore restituito diventa parte della chiave, il cui hash è contenuto nel nome del file del template compilato. Pertanto, per valori restituiti diversi, Latte genererà file di cache diversi. diff --git a/latte/it/filters.texy b/latte/it/filters.texy index 62c85414b1..ae5d4a7aae 100644 --- a/latte/it/filters.texy +++ b/latte/it/filters.texy @@ -1,112 +1,114 @@ -Filtri per latte -**************** +Filtri Latte +************ .[perex] -I filtri sono funzioni che modificano o formattano i dati nella forma desiderata. Questo è un riassunto dei filtri incorporati disponibili. +Nei template possiamo utilizzare funzioni che aiutano a modificare o riformattare i dati nella loro forma finale. Li chiamiamo *filtri*. .[table-latte-filters] -|## Trasformazione di stringhe/array -| `batch` | [elenca i dati lineari in una tabella |#batch] -| `breakLines` | [Inserisce le interruzioni di riga HTML prima di tutte le newline |#breakLines] -| `bytes` | [Formatta la dimensione in byte |#bytes] -| `clamp` | [blocca il valore all'intervallo |#clamp] -| `dataStream` | [Conversione del protocollo URI dei dati |#datastream] -| `date` | [formatta la data |#date] -| `explode` | [divide una stringa in base al delimitatore dato |#explode] -| `first` | [restituisce il primo elemento di un array o un carattere di una stringa |#first] -| `implode` | [unisce un array a una stringa |#implode] -| `indent` | [indenta il testo da sinistra con un certo numero di tabulazioni |#indent] -| `join` | [unisce un array a una stringa |#implode] -| `last` | [restituisce l'ultimo elemento di un array o un carattere di una stringa |#last] -| `length` | [restituisce la lunghezza di una stringa o di un array |#length] -| `number` | [formatta un numero |#number] -| `padLeft` | [completa la stringa alla lunghezza data da sinistra |#padLeft] -| `padRight` | [completa la stringa alla lunghezza data da destra |#padRight] -| `random` | [restituisce un elemento casuale di un array o un carattere di una stringa |#random] -| `repeat` | [ripete la stringa |#repeat] -| `replace` | [sostituisce tutte le occorrenze della stringa cercata con la sostituzione |#replace] -| `replaceRE` | [sostituisce tutte le occorrenze secondo l'espressione regolare |#replaceRE] -| `reverse` | [inverte una stringa o un array UTF-8 |#reverse] -| `slice` | [estrae una fetta di un array o di una stringa |#slice] -| `sort` | [ordina un array |#sort] -| `spaceless` | [rimuove gli spazi bianchi |#spaceless], simile al tag [spaceless |tags] -| `split` | [divide una stringa in base al delimitatore dato |#explode] -| `strip` | [rimuove gli spazi bianchi |#spaceless] -| `stripHtml` | [rimuove i tag HTML e converte le entità HTML in testo |#stripHtml] -| `substr` | [restituisce una parte della stringa |#substr] -| `trim` | [elimina gli spazi bianchi dalla stringa |#trim] -| `translate` | [traduzione in altre lingue |#translate] -| `truncate` | [accorcia la lunghezza preservando le parole intere |#truncate] -| `webalize` | [adatta la stringa UTF-8 alla forma utilizzata nell'URL |#webalize] +|## Trasformazione +| `batch` | [stampa dati lineari in una tabella |#batch] +| `breakLines` | [Aggiunge interruzioni di riga HTML prima delle interruzioni di riga |#breakLines] +| `bytes` | [formatta la dimensione in byte |#bytes] +| `clamp` | [limita un valore all'interno di un intervallo specificato |#clamp] +| `dataStream` | [conversione per il protocollo Data URI |#dataStream] +| `date` | [formatta data e ora |#date] +| `explode` | [divide una stringa in un array in base a un delimitatore |#explode] +| `first` | [restituisce il primo elemento di un array o carattere di una stringa |#first] +| `group` | [raggruppa i dati secondo vari criteri |#group] +| `implode` | [unisce un array in una stringa |#implode] +| `indent` | [indenta il testo da sinistra di un dato numero di tabulazioni |#indent] +| `join` | [unisce un array in una stringa |#implode] +| `last` | [restituisce l'ultimo elemento di un array o carattere di una stringa |#last] +| `length` | [restituisce la lunghezza di una stringa in caratteri o di un array |#length] +| `localDate` | [formatta data e ora secondo il locale |#localDate] +| `number` | [formatta un numero |#number] +| `padLeft` | [riempie una stringa a sinistra alla lunghezza desiderata |#padLeft] +| `padRight` | [riempie una stringa a destra alla lunghezza desiderata |#padRight] +| `random` | [restituisce un elemento casuale di un array o carattere di una stringa |#random] +| `repeat` | [ripetizione di una stringa |#repeat] +| `replace` | [sostituisce le occorrenze di una stringa cercata |#replace] +| `replaceRE` | [sostituisce le occorrenze secondo un'espressione regolare |#replaceRE] +| `reverse` | [inverte una stringa UTF‑8 o un array |#reverse] +| `slice` | [estrae una parte di un array o di una stringa |#slice] +| `sort` | [ordina un array |#sort] +| `spaceless` | [rimuove gli spazi bianchi |#spaceless], simile al tag [spaceless |tags] +| `split` | [divide una stringa in un array in base a un delimitatore |#explode] +| `strip` | [rimuove gli spazi bianchi |#spaceless] +| `stripHtml` | [rimuove i tag HTML e converte le entità HTML in caratteri |#stripHtml] +| `substr` | [restituisce una parte di una stringa |#substr] +| `trim` | [rimuove gli spazi iniziali e finali o altri caratteri |#trim] +| `translate` | [traduzione in altre lingue |#translate] +| `truncate` | [accorcia la lunghezza mantenendo le parole |#truncate] +| `webalize` | [modifica una stringa UTF‑8 nella forma utilizzata negli URL |#webalize] .[table-latte-filters] -|## Incasellamento delle lettere -| `capitalize` | [minuscolo, la prima lettera di ogni parola maiuscola |#capitalize] -| `firstUpper` | [rende la prima lettera maiuscola |#firstUpper] -| `lower` | [rende una stringa minuscola |#lower] -| `upper` | [rende una stringa maiuscola |#upper] +|## Maiuscole/Minuscole +| `capitalize` | [minuscolo, prima lettera delle parole maiuscola |#capitalize] +| `firstUpper` | [converte la prima lettera in maiuscolo |#firstUpper] +| `lower` | [converte in minuscolo |#lower] +| `upper` | [converte in maiuscolo |#upper] .[table-latte-filters] -|## Arrotondamento dei numeri -| `ceil` | [arrotonda un numero fino a una determinata precisione |#ceil] -| `floor` | [arrotonda un numero per difetto a una determinata precisione|#floor] -| `round` | [arrotonda un numero a una determinata precisione|#round] +|## Arrotondamento +| `ceil` | [arrotonda un numero per eccesso alla precisione specificata |#ceil] +| `floor` | [arrotonda un numero per difetto alla precisione specificata |#floor] +| `round` | [arrotonda un numero alla precisione specificata |#round] .[table-latte-filters] |## Escaping -| `escapeUrl` | [esegue l'escape di un parametro nell'URL |#escapeUrl] -| `noescape` | [stampa una variabile senza escape |#noescape] -| `query` | [genera una stringa di query nell'URL |#query] +| `escapeUrl` | [esegue l'escaping di un parametro in un URL |#escapeUrl] +| `noescape` | [stampa una variabile senza escaping |#noescape] +| `query` | [genera una query string in un URL |#query] -Esistono anche filtri di escape per HTML (`escapeHtml` e `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) e iCalendar (`escapeICal`), che Latte utilizza autonomamente grazie all'[escape context-aware |safety-first#Context-aware escaping] e che non è necessario scrivere. +Inoltre, esistono filtri di escaping per HTML (`escapeHtml` e `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) e iCalendar (`escapeICal`), che Latte utilizza autonomamente grazie all'[escaping sensibile al contesto |safety-first#Escaping sensibile al contesto] e non è necessario scriverli. .[table-latte-filters] |## Sicurezza -| `checkUrl` | [sanifica le stringhe da usare nell'attributo href |#checkUrl] -| `nocheck` | [impedisce la sanitizzazione automatica degli URL |#nocheck] +| `checkUrl` | [pulisce un indirizzo URL da input pericolosi |#checkUrl] +| `nocheck` | [impedisce la pulizia automatica dell'indirizzo URL |#nocheck] -I [controlli |safety-first#link checking] degli attributi `src` e `href` vengono eseguiti [automaticamente |safety-first#link checking], quindi non è necessario usare il filtro `checkUrl`. +Gli attributi Latte `src` e `href` [vengono controllati automaticamente |safety-first#Controllo dei link], quindi non è quasi mai necessario utilizzare il filtro `checkUrl`. .[note] -Tutti i filtri incorporati funzionano con stringhe codificate UTF-8. +Tutti i filtri predefiniti sono progettati per stringhe con codifica UTF‑8. -Uso .[#toc-usage] -================= +Utilizzo +======== -Latte consente di chiamare i filtri usando la notazione del segno di pipe (è ammesso lo spazio precedente): +I filtri vengono scritti dopo una barra verticale (può esserci uno spazio prima): ```latte

                                                                          {$heading|upper}

                                                                          ``` -I filtri possono essere concatenati, in tal caso si applicano in ordine da sinistra a destra: +I filtri (nelle versioni precedenti helper) possono essere concatenati e vengono applicati nell'ordine da sinistra a destra: ```latte

                                                                          {$heading|lower|capitalize}

                                                                          ``` -I parametri vengono inseriti dopo il nome del filtro, separati da due punti o da una virgola: +I parametri vengono specificati dopo il nome del filtro, separati da due punti o virgole: ```latte

                                                                          {$heading|truncate:20,''}

                                                                          ``` -I filtri possono essere applicati alle espressioni: +I filtri possono essere applicati anche a un'espressione: ```latte {var $name = ($title|upper) . ($subtitle|lower)} ``` -[I filtri personalizzati |extending-latte#filters] possono essere registrati in questo modo: +[Filtri personalizzati|custom-filters] possono essere registrati in questo modo: ```php $latte = new Latte\Engine; $latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -Lo utilizziamo in un modello come questo: +Nel template, viene quindi chiamato così: ```latte

                                                                          {$text|shortify}

                                                                          @@ -114,13 +116,13 @@ Lo utilizziamo in un modello come questo: ``` -Filtri .[#toc-filters] -====================== +Filtri +====== -batch(int length, mixed item): array .[filter]{data-version:2.7} ----------------------------------------------------------------- -Filtro che semplifica l'elencazione di dati lineari sotto forma di tabella. Restituisce un array di array con il numero di elementi indicato. Se si fornisce un secondo parametro, questo viene utilizzato per riempire gli elementi mancanti nell'ultima riga. +batch(int $length, mixed $item): array .[filter] +------------------------------------------------ +Filtro che semplifica la visualizzazione di dati lineari sotto forma di tabella. Restituisce un array di array con il numero specificato di elementi. Se si specifica il secondo parametro, verrà utilizzato per completare gli elementi mancanti nell'ultima riga. ```latte {var $items = ['a', 'b', 'c', 'd', 'e']} @@ -152,20 +154,22 @@ Stampa:
                                                                          Ciclo interno
                                                                          ``` +Vedi anche [#group] e il tag [iterateWhile |tags#iterateWhile]. + breakLines .[filter] -------------------- -Inserisce le interruzioni di riga HTML prima di tutti i newline. +Aggiunge un tag HTML `
                                                                          ` prima di ogni carattere di nuova riga. ```latte {var $s = "Text & with \n newline"} -{$s|breakLines} {* uscite "Text & with
                                                                          \n newline" *} +{$s|breakLines} {* stampa "Text & with
                                                                          \n newline" *} ``` -bytes(int precision = 2) .[filter] ----------------------------------- -Formatta una dimensione in byte in forma leggibile. +bytes(int $precision=2) .[filter] +--------------------------------- +Formatta la dimensione in byte in un formato leggibile dall'uomo. Se è impostato il [locale |develop#Locale], verranno utilizzati i separatori decimali e delle migliaia corrispondenti. ```latte {$size|bytes} 0 B, 1.25 GB, … @@ -173,53 +177,53 @@ Formatta una dimensione in byte in forma leggibile. ``` -ceil(int precision = 0) .[filter] ---------------------------------- -Arrotonda un numero fino a una determinata precisione. +ceil(int $precision=0) .[filter] +-------------------------------- +Arrotonda un numero per eccesso alla precisione specificata. ```latte -{=3.4|ceil} {* uscite 4 *} -{=135.22|ceil:1} {* uscite 135.3 *} -{=135.22|ceil:3} {* uscite 135.22 *} +{=3.4|ceil} {* stampa 4 *} +{=135.22|ceil:1} {* stampa 135.3 *} +{=135.22|ceil:3} {* stampa 135.22 *} ``` -Vedere anche [piano |#floor], [rotondo |#round]. +Vedi anche [#floor], [#round]. capitalize .[filter] -------------------- -Restituisce una versione con titolo del valore. Le parole inizieranno con lettere maiuscole, tutti i caratteri rimanenti saranno minuscoli. Richiede l'estensione PHP `mbstring`. +Le parole inizieranno con lettere maiuscole, tutti i caratteri rimanenti saranno minuscoli. Richiede l'estensione PHP `mbstring`. ```latte -{='i like LATTE'|capitalize} {* uscite 'I Like Latte' *} +{='i like LATTE'|capitalize} {* stampa 'I Like Latte' *} ``` -Vedere anche [firstUpper |#firstUpper], [lower |#lower], [upper |#upper]. +Vedi anche [#firstUpper], [#lower], [#upper]. checkUrl .[filter] ------------------ -Impone la sanitizzazione degli URL. Controlla se la variabile contiene un URL web (cioè un protocollo HTTP/HTTPS) e impedisce la scrittura di link che potrebbero rappresentare un rischio per la sicurezza. +Forza la pulizia dell'indirizzo URL. Controlla se la variabile contiene un URL web (cioè protocollo HTTP/HTTPS) e impedisce la visualizzazione di link che potrebbero rappresentare un rischio per la sicurezza. ```latte {var $link = 'javascript:window.close()'} -checked -unchecked +controllato +non controllato ``` Stampa: ```latte -checked -unchecked +controllato +non controllato ``` -Vedere anche [nocheck |#nocheck]. +Vedi anche [#nocheck]. -clamp(int|float min, int|float max) .[filter]{data-version:2.9} ---------------------------------------------------------------- -Restituisce un valore limitato all'intervallo inclusivo di min e max. +clamp(int|float $min, int|float $max) .[filter] +----------------------------------------------- +Limita un valore all'interno dell'intervallo inclusivo specificato min e max. ```latte {$level|clamp: 0, 255} @@ -228,14 +232,14 @@ Restituisce un valore limitato all'intervallo inclusivo di min e max. Esiste anche come [funzione |functions#clamp]. -dataStream(string mimetype = detect) .[filter] ----------------------------------------------- -Converte il contenuto in uno schema URI di dati. Può essere usato per inserire immagini in HTML o CSS senza la necessità di collegare file esterni. +dataStream(string $mimetype=detect) .[filter] +--------------------------------------------- +Converte il contenuto nello schema URI dei dati. Può essere utilizzato per incorporare immagini in HTML o CSS senza la necessità di collegare file esterni. -Poniamo di avere un'immagine in una variabile `$img = Image::fromFile('obrazek.gif')`, allora +Supponiamo di avere un'immagine nella variabile `$img = Image::fromFile('image.gif')`, quindi ```latte - + ``` Stampa ad esempio: @@ -250,105 +254,125 @@ AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO Richiede l'estensione PHP `fileinfo`. -date(string format) .[filter] ------------------------------ -Restituisce una data nel formato indicato, utilizzando le opzioni delle funzioni PHP [php:strftime] o [php:date]. Il filtro ottiene una data come timestamp UNIX, una stringa o un oggetto di tipo `DateTime`. +date(string $format) .[filter] +------------------------------ +Formatta data e ora secondo la maschera utilizzata dalla funzione PHP [php:date]. Il filtro accetta la data nel formato timestamp UNIX, come stringa o oggetto di tipo `DateTimeInterface`. ```latte -{$today|date:'%d.%m.%Y'} {$today|date:'j. n. Y'} ``` +Vedi anche [#localDate]. + escapeUrl .[filter] ------------------- -Permette di separare una variabile da usare come parametro nell'URL. +Esegue l'escaping di una variabile per l'uso come parametro in un URL. ```latte {$name} ``` -Vedere anche [query |#query]. +Vedi anche [#query]. -explode(string separator = '') .[filter]{data-version:2.10.2} -------------------------------------------------------------- -Divide una stringa in base al delimitatore dato e restituisce un array di stringhe. Alias di `split`. +explode(string $separator='') .[filter] +--------------------------------------- +Divide una stringa in un array in base a un delimitatore. Alias per `split`. ```latte -{='one,two,three'|explode:','} {* returns ['one', 'two', 'three'] *} +{='one,two,three'|explode:','} {* restituisce ['one', 'two', 'three'] *} ``` Se il delimitatore è una stringa vuota (valore predefinito), l'input verrà diviso in singoli caratteri: ```latte -{='123'|explode} {* returns ['1', '2', '3'] *} +{='123'|explode} {* restituisce ['1', '2', '3'] *} ``` -È possibile utilizzare anche l'alias `split`: +Puoi anche usare l'alias `split`: ```latte -{='1,2,3'|split:','} {* returns ['1', '2', '3'] *} +{='1,2,3'|split:','} {* restituisce ['1', '2', '3'] *} ``` -Vedere anche [implode |#implode]. +Vedi anche [#implode]. -first .[filter]{data-version:2.10.2} ------------------------------------- -Restituisce il primo elemento di una matrice o un carattere di una stringa: +first .[filter] +--------------- +Restituisce il primo elemento di un array o carattere di una stringa: ```latte -{=[1, 2, 3, 4]|first} {* uscite 1 *} -{='abcd'|first} {* uscite 'a' *} +{=[1, 2, 3, 4]|first} {* stampa 1 *} +{='abcd'|first} {* stampa 'a' *} ``` -Vedere anche [last |#last], [random |#random]. +Vedi anche [#last], [#random]. -floor(int precision = 0) .[filter] ----------------------------------- -Arrotonda un numero fino a una determinata precisione. +floor(int $precision=0) .[filter] +--------------------------------- +Arrotonda un numero per difetto alla precisione specificata. ```latte -{=3.5|floor} {* uscite 3 *} -{=135.79|floor:1} {* uscite 135.7 *} -{=135.79|floor:3} {* uscite 135.79 *} +{=3.5|floor} {* stampa 3 *} +{=135.79|floor:1} {* stampa 135.7 *} +{=135.79|floor:3} {* stampa 135.79 *} ``` -Vedere anche [ceil |#ceil], [round |#round]. +Vedi anche [#ceil], [#round]. firstUpper .[filter] -------------------- -Converte la prima lettera di un valore in maiuscolo. Richiede l'estensione PHP `mbstring`. +Converte la prima lettera in maiuscolo. Richiede l'estensione PHP `mbstring`. ```latte -{='the latte'|firstUpper} {* uscite 'The latte' *} +{='the latte'|firstUpper} {* stampa 'The latte' *} ``` -Vedere anche [capitalize |#capitalize], [lower |#lower], [upper |#upper]. +Vedi anche [#capitalize], [#lower], [#upper]. -implode(string glue = '') .[filter] ------------------------------------ -Restituisce una stringa che è la concatenazione delle stringhe dell'array. Alias di `join`. +group(string|int|\Closure $by): array .[filter]{data-version:3.0.16} +-------------------------------------------------------------------- +Il filtro raggruppa i dati secondo vari criteri. + +In questo esempio, le righe della tabella vengono raggruppate per la colonna `categoryId`. L'output è un array di array, dove la chiave è il valore nella colonna `categoryId`. [Leggi il tutorial dettagliato|cookbook/grouping]. ```latte -{=[1, 2, 3]|implode} {* uscite '123' *} -{=[1, 2, 3]|implode:'|'} {* uscite '1|2|3' *} +{foreach ($items|group: categoryId) as $categoryId => $categoryItems} +
                                                                            + {foreach $categoryItems as $item} +
                                                                          • {$item->name}
                                                                          • + {/foreach} +
                                                                          +{/foreach} ``` -È possibile utilizzare anche l'alias `join`: .{data-version:2.10.2} +Vedi anche [#batch], la funzione [group |functions#group] e il tag [iterateWhile |tags#iterateWhile]. + + +implode(string $glue='') .[filter] +---------------------------------- +Restituisce una stringa che è la concatenazione degli elementi della sequenza. Alias per `join`. ```latte -{=[1, 2, 3]|join} {* uscite '123' *} +{=[1, 2, 3]|implode} {* stampa '123' *} +{=[1, 2, 3]|implode:'|'} {* stampa '1|2|3' *} ``` +Puoi anche usare l'alias `join`: + +```latte +{=[1, 2, 3]|join} {* stampa '123' *} +``` -indent(int level = 1, string char = "\t") .[filter] ---------------------------------------------------- -Rientra un testo da sinistra di un determinato numero di tabulazioni o di altri caratteri, specificati nel secondo argomento opzionale. Le righe vuote non sono rientrate. + +indent(int $level=1, string $char="\t") .[filter] +------------------------------------------------- +Indenta il testo da sinistra di un dato numero di tabulazioni o altri caratteri, che possiamo specificare nel secondo argomento. Le righe vuote non vengono indentate. ```latte
                                                                          @@ -367,26 +391,26 @@ Stampa: ``` -last .[filter]{data-version:2.10.2} ------------------------------------ -Restituisce l'ultimo elemento della matrice o il carattere della stringa: +last .[filter] +-------------- +Restituisce l'ultimo elemento di un array o carattere di una stringa: ```latte -{=[1, 2, 3, 4]|last} {* uscite 4 *} -{='abcd'|last} {* uscite 'd' *} +{=[1, 2, 3, 4]|last} {* stampa 4 *} +{='abcd'|last} {* stampa 'd' *} ``` -Vedere anche [first |#first], [random |#random]. +Vedi anche [#first], [#random]. length .[filter] ---------------- Restituisce la lunghezza di una stringa o di un array. -- per le stringhe, restituisce la lunghezza in caratteri UTF-8 -- per gli array, restituisce il conteggio degli elementi -- per gli oggetti che implementano l'interfaccia Countable, utilizzerà il valore di ritorno del metodo count() -- per gli oggetti che implementano l'interfaccia IteratorAggregate, utilizzerà il valore di ritorno dell'iterator_count() +- per le stringhe, restituisce la lunghezza in caratteri UTF‑8 +- per gli array, restituisce il numero di elementi +- per gli oggetti che implementano l'interfaccia Countable, utilizza il valore restituito dal metodo count() +- per gli oggetti che implementano l'interfaccia IteratorAggregate, utilizza il valore restituito dalla funzione iterator_count() ```latte @@ -396,94 +420,204 @@ Restituisce la lunghezza di una stringa o di un array. ``` +localDate(?string $format=null, ?string $date=null, ?string $time=null) .[filter] +--------------------------------------------------------------------------------- +Formatta data e ora secondo il [locale |develop#Locale], garantendo una visualizzazione coerente e localizzata dei dati temporali tra diverse lingue e regioni. Il filtro accetta la data come timestamp UNIX, stringa o oggetto di tipo `DateTimeInterface`. + +```latte +{$date|localDate} {* 15 aprile 2024 *} +{$date|localDate: format: yM} {* 4/2024 *} +{$date|localDate: date: medium} {* 15/4/2024 *} +``` + +Se si utilizza il filtro senza parametri, la data verrà visualizzata al livello `long`, vedi oltre. + +**a) utilizzo del formato** + +Il parametro `format` descrive quali componenti temporali devono essere visualizzati. Utilizza codici letterali per questo, il cui numero di ripetizioni influenza la larghezza dell'output: + +| anno | `y` / `yy` / `yyyy` | `2024` / `24` / `2024` +| mese | `M` / `MM` / `MMM` / `MMMM` | `8` / `08` / `ago` / `agosto` +| giorno | `d` / `dd` / `E` / `EEEE` | `1` / `01` / `dom` / `domenica` +| ora | `j` / `H` / `h` | preferito / 24 ore / 12 ore +| minuto | `m` / `mm` | `5` / `05` (2 cifre in combinazione con i secondi) +| secondo | `s` / `ss` | `8` / `08` (2 cifre in combinazione con i minuti) + +L'ordine dei codici nel formato non ha importanza, poiché l'ordine dei componenti viene visualizzato secondo le convenzioni del locale. Il formato è quindi indipendente da esso. Ad esempio, il formato `yyyyMMMMd` nell'ambiente `en_US` stampa `April 15, 2024`, mentre nell'ambiente `it_IT` stampa `15 aprile 2024`: + +| locale: | it_IT | en_US +|--- +| `format: 'dMy'` | 10/8/2024 | 8/10/2024 +| `format: 'yM'` | 8/2024 | 8/2024 +| `format: 'yyyyMMMM'` | agosto 2024 | August 2024 +| `format: 'MMMM'` | agosto | August +| `format: 'jm'` | 17:22 | 5:22 PM +| `format: 'Hm'` | 17:22 | 17:22 +| `format: 'hm'` | 5:22 PM | 5:22 PM + + +**b) utilizzo di stili preimpostati** + +I parametri `date` e `time` specificano quanto dettagliatamente devono essere visualizzati data e ora. È possibile scegliere tra diversi livelli: `full`, `long`, `medium`, `short`. È possibile visualizzare solo la data, solo l'ora o entrambi: + +| locale: | it_IT | en_US +|--- +| `date: short` | 23/01/78 | 1/23/78 +| `date: medium` | 23 gen 1978 | Jan 23, 1978 +| `date: long` | 23 gennaio 1978 | January 23, 1978 +| `date: full` | lunedì 23 gennaio 1978 | Monday, January 23, 1978 +| `time: short` | 08:30 | 8:30 AM +| `time: medium` | 08:30:59 | 8:30:59 AM +| `time: long` | 08:30:59 CET | 8:30:59 AM GMT+1 +| `date: short, time: short` | 23/01/78, 08:30 | 1/23/78, 8:30 AM +| `date: medium, time: short` | 23 gen 1978, 08:30 | Jan 23, 1978, 8:30 AM +| `date: long, time: short` | 23 gennaio 1978 alle 08:30 | January 23, 1978 at 8:30 AM + +Per la data, è possibile utilizzare anche il prefisso `relative-` (es. `relative-short`), che per le date vicine a quella attuale visualizzerà `ieri`, `oggi` o `domani`, altrimenti verrà visualizzato nel modo standard. + +```latte +{$date|localDate: date: relative-short} {* ieri *} +``` + +Vedi anche [#date]. + + lower .[filter] --------------- -Converte un valore in minuscolo. Richiede l'estensione PHP `mbstring`. +Converte una stringa in minuscolo. Richiede l'estensione PHP `mbstring`. ```latte -{='LATTE'|lower} {* uscite 'latte' *} +{='LATTE'|lower} {* stampa 'latte' *} ``` -Vedere anche [capitalize |#capitalize], [firstUpper |#firstUpper], [upper |#upper]. +Vedi anche [#capitalize], [#firstUpper], [#upper]. nocheck .[filter] ----------------- -Impedisce la sanificazione automatica degli URL. Latte [controlla automaticamente |safety-first#Link checking] se la variabile contiene un URL web (cioè un protocollo HTTP/HTTPS) e impedisce la scrittura di link che potrebbero rappresentare un rischio per la sicurezza. +Impedisce la pulizia automatica dell'indirizzo URL. Latte [controlla automaticamente |safety-first#Controllo dei link] se la variabile contiene un URL web (cioè protocollo HTTP/HTTPS) e impedisce la visualizzazione di link che potrebbero rappresentare un rischio per la sicurezza. -Se il link utilizza uno schema diverso, come `javascript:` o `data:`, e si è sicuri del suo contenuto, si può disabilitare il controllo tramite `|nocheck`. +Se il link utilizza un altro schema, ad esempio `javascript:` o `data:`, e sei sicuro del suo contenuto, puoi disabilitare il controllo usando `|nocheck`. ```latte {var $link = 'javascript:window.close()'} -checked -unchecked +controllato +non controllato ``` -Stampe: +Stampa: ```latte -checked -unchecked +controllato +non controllato ``` -Vedere anche [checkUrl |#checkUrl]. +Vedi anche [#checkUrl]. noescape .[filter] ------------------ -Disabilita l'escape automatico. +Disabilita l'escaping automatico. ```latte {var $trustedHtmlString = 'hello'} -Escaped: {$trustedHtmlString} -Unescaped: {$trustedHtmlString|noescape} +Escapato: {$trustedHtmlString} +Non escapato: {$trustedHtmlString|noescape} ``` Stampa: ```latte -Escaped: <b>hello</b> -Unescaped: hello +Escapato: <b>hello</b> +Non escapato: hello ``` .[warning] -L'uso improprio del filtro `noescape` può portare a una vulnerabilità XSS! Non utilizzatelo mai a meno che non siate **assolutamente sicuri** di quello che state facendo e che la stringa che state stampando provenga da una fonte affidabile. +L'uso improprio del filtro `noescape` può portare a una vulnerabilità XSS! Non utilizzarlo mai a meno che tu non sia **assolutamente sicuro** di quello che stai facendo e che la stringa stampata provenga da una fonte attendibile. + + +number(int $decimals=0, string $decPoint='.', string $thousandsSep=',') .[filter] +--------------------------------------------------------------------------------- +Formatta un numero a un certo numero di cifre decimali. Se è impostato il [locale |develop#Locale], verranno utilizzati i separatori decimali e delle migliaia corrispondenti. +```latte +{1234.20|number} 1,234 +{1234.20|number:1} 1,234.2 +{1234.20|number:2} 1,234.20 +{1234.20|number:2, ',', ' '} 1 234,20 +``` + + +number(string $format) .[filter] +-------------------------------- +Il parametro `format` consente di definire l'aspetto dei numeri esattamente secondo le proprie esigenze. Per questo è necessario aver impostato il [locale |develop#Locale]. Il formato è composto da diversi caratteri speciali, la cui descrizione completa si trova nella documentazione "DecimalFormat":https://unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns: + +- `0` cifra obbligatoria, viene sempre visualizzata, anche se è zero +- `#` cifra facoltativa, viene visualizzata solo se il numero è effettivamente presente in quella posizione +- `@` cifra significativa, aiuta a visualizzare il numero con un certo numero di cifre significative +- `.` indica dove deve trovarsi la virgola decimale (o il punto, a seconda del paese) +- `,` serve per separare i gruppi di cifre, più comunemente le migliaia +- `%` moltiplica il numero per 100× e aggiunge il simbolo di percentuale + +Vediamo alcuni esempi. Nel primo esempio, due cifre decimali sono obbligatorie, nel secondo facoltative. Il terzo esempio mostra il riempimento con zeri a sinistra e a destra, il quarto visualizza solo le cifre esistenti: + +```latte +{1234.5|number: '#,##0.00'} {* 1,234.50 *} +{1234.5|number: '#,##0.##'} {* 1,234.5 *} +{1.23 |number: '000.000'} {* 001.230 *} +{1.2 |number: '##.##'} {* 1.2 *} +``` + +Le cifre significative determinano quante cifre devono essere visualizzate indipendentemente dalla virgola decimale, arrotondando: + +```latte +{1234|number: '@@'} {* 1200 *} +{1234|number: '@@@'} {* 1230 *} +{1234|number: '@@@#'} {* 1234 *} +{1.2345|number: '@@@'} {* 1.23 *} +{0.00123|number: '@@'} {* 0.0012 *} +``` + +Un modo semplice per visualizzare un numero come percentuale. Il numero viene moltiplicato per 100× e viene aggiunto il simbolo `%`: + +```latte +{0.1234|number: '#.##%'} {* 12.34% *} +``` -number(int decimals = 0, string decPoint = '.', string thousandsSep = ',') .[filter] ------------------------------------------------------------------------------------- -Formatta un numero con un determinato numero di cifre decimali. È anche possibile specificare un carattere del punto decimale e del separatore delle migliaia. +Possiamo definire un formato diverso per i numeri positivi e negativi, separati dal carattere `;`. In questo modo, ad esempio, è possibile impostare che i numeri positivi vengano visualizzati con il segno `+`: ```latte -{1234.20 |number} 1,234 -{1234.20 |number:1} 1,234.2 -{1234.20 |number:2} 1,234.20 -{1234.20 |number:2, ',', ' '} 1 234,20 +{42|number: '#.##;(#.##)'} {* 42 *} +{-42|number: '#.##;(#.##)'} {* (42) *} +{42|number: '+#.##;-#.##'} {* +42 *} +{-42|number: '+#.##;-#.##'} {* -42 *} ``` +Ricorda che l'aspetto effettivo dei numeri può variare a seconda delle impostazioni del paese. Ad esempio, in alcuni paesi si usa la virgola invece del punto come separatore decimale. Questo filtro lo tiene automaticamente in considerazione e non devi preoccuparti di nulla. -padLeft(int length, string pad = ' ') .[filter] + +padLeft(int $length, string $pad=' ') .[filter] ----------------------------------------------- -Imbottisce una stringa di una certa lunghezza con un'altra stringa a partire da sinistra. +Riempie una stringa a una certa lunghezza con un'altra stringa da sinistra. ```latte -{='hello'|padLeft: 10, '123'} {* uscite '12312hello' *} +{='hello'|padLeft: 10, '123'} {* stampa '12312hello' *} ``` -padRight(int length, string pad = ' ') .[filter] +padRight(int $length, string $pad=' ') .[filter] ------------------------------------------------ -Imbottisce una stringa di una certa lunghezza con un'altra stringa proveniente da destra. +Riempie una stringa a una certa lunghezza con un'altra stringa da destra. ```latte -{='hello'|padRight: 10, '123'} {* uscite 'hello12312' *} +{='hello'|padRight: 10, '123'} {* stampa 'hello12312' *} ``` -query .[filter]{data-version:2.10} ------------------------------------ -Genera dinamicamente una stringa di query nell'URL: +query .[filter] +--------------- +Genera dinamicamente una query string in un URL: ```latte click @@ -497,103 +631,103 @@ Stampa: search ``` -I tasti con valore `null` vengono omessi. +Le chiavi con valore `null` vengono omesse. -Vedere anche [escapeUrl |#escapeUrl]. +Vedi anche [#escapeUrl]. -random .[filter]{data-version:2.10.2} -------------------------------------- -Restituisce un elemento casuale di una matrice o un carattere di una stringa: +random .[filter] +---------------- +Restituisce un elemento casuale di un array o carattere di una stringa: ```latte -{=[1, 2, 3, 4]|random} {* esempio di uscita: 3 *} -{='abcd'|random} {* esempio di uscita: 'b' *} +{=[1, 2, 3, 4]|random} {* stampa ad es.: 3 *} +{='abcd'|random} {* stampa ad es.: 'b' *} ``` -Vedere anche [primo |#first], [ultimo |#last]. +Vedi anche [#first], [#last]. -repeat(int count) .[filter] ---------------------------- -Ripete la stringa x volte. +repeat(int $count) .[filter] +---------------------------- +Ripete una stringa x volte. ```latte -{='hello'|repeat: 3} {* produce 'hellohellohello' *} +{='hello'|repeat: 3} {* stampa 'hellohellohello' *} ``` -replace(string|array search, string replace = '') .[filter] +replace(string|array $search, string $replace='') .[filter] ----------------------------------------------------------- Sostituisce tutte le occorrenze della stringa di ricerca con la stringa di sostituzione. ```latte -{='hello world'|replace: 'world', 'friend'} {* outputs 'hello friend' *} +{='hello world'|replace: 'world', 'friend'} {* stampa 'hello friend' *} ``` -È possibile effettuare più sostituzioni contemporaneamente: .{data-version:2.10.2} +È possibile eseguire più sostituzioni contemporaneamente: ```latte -{='hello world'|replace: [h => l, l => h]} {* outputs 'lehho worhd' *} +{='hello world'|replace: [h => l, l => h]} {* stampa 'lehho worhd' *} ``` -replaceRE(string pattern, string replace = '') .[filter] +replaceRE(string $pattern, string $replace='') .[filter] -------------------------------------------------------- -Sostituisce tutte le occorrenze in base all'espressione regolare. +Esegue una ricerca tramite espressione regolare con sostituzione. ```latte -{='hello world'|replaceRE: '/l.*/', 'l'} {* outputs 'hel' *} +{='hello world'|replaceRE: '/l.*/', 'l'} {* stampa 'hel' *} ``` reverse .[filter] ----------------- -Inverte la stringa o l'array dato. +Inverte la stringa o l'array specificato. ```latte {var $s = 'Nette'} -{$s|reverse} {* outputs 'etteN' *} +{$s|reverse} {* stampa 'etteN' *} {var $a = ['N', 'e', 't', 't', 'e']} -{$a|reverse} {* returns ['e', 't', 't', 'e', 'N'] *} +{$a|reverse} {* restituisce ['e', 't', 't', 'e', 'N'] *} ``` -round(int precision = 0) .[filter] ----------------------------------- -Arrotonda un numero a una determinata precisione. +round(int $precision=0) .[filter] +--------------------------------- +Arrotonda un numero alla precisione specificata. ```latte -{=3.4|round} {* uscite 3 *} -{=3.5|round} {* uscite 4 *} -{=135.79|round:1} {* uscite 135.8 *} -{=135.79|round:3} {* uscite 135.79 *} +{=3.4|round} {* stampa 3 *} +{=3.5|round} {* stampa 4 *} +{=135.79|round:1} {* stampa 135.8 *} +{=135.79|round:3} {* stampa 135.79 *} ``` -Vedere anche [ceil |#ceil], [floor |#floor]. +Vedi anche [#ceil], [#floor]. -slice(int start, int length = null, bool preserveKeys = false) .[filter]{data-version:2.10.2} ---------------------------------------------------------------------------------------------- -Estrae una fetta di un array o di una stringa. +slice(int $start, ?int $length=null, bool $preserveKeys=false) .[filter] +------------------------------------------------------------------------ +Estrae una parte di un array o di una stringa. ```latte -{='hello'|slice: 1, 2} {* outputs 'el' *} -{=['a', 'b', 'c']|slice: 1, 2} {* outputs ['b', 'c'] *} +{='hello'|slice: 1, 2} {* stampa 'el' *} +{=['a', 'b', 'c']|slice: 1, 2} {* stampa ['b', 'c'] *} ``` -Il filtro slice funziona come la funzione PHP `array_slice` per gli array e `mb_substr` per le stringhe, con un fallback a `iconv_substr` in modalità UTF-8. +Il filtro funziona come la funzione PHP `array_slice` per gli array o `mb_substr` per le stringhe con fallback alla funzione `iconv_substr` in modalità UTF‑8. -Se start è non negativo, la sequenza inizierà da quell'inizio nella variabile. Se start è negativo, la sequenza inizierà a quella distanza dalla fine della variabile. +Se start è positivo, la sequenza inizierà spostata di questo numero dall'inizio dell'array/stringa. Se è negativo, la sequenza inizierà spostata di tanto dalla fine. -Se la lunghezza è data ed è positiva, la sequenza conterrà fino a quel numero di elementi. Se la variabile è più corta della lunghezza, saranno presenti solo gli elementi disponibili della variabile. Se la lunghezza è data ed è negativa, la sequenza si fermerà a tanti elementi dalla fine della variabile. Se viene omesso, la sequenza conterrà tutti gli elementi dall'offset fino alla fine della variabile. +Se il parametro length è specificato ed è positivo, la sequenza conterrà tanti elementi. Se a questa funzione viene passato un parametro length negativo, la sequenza conterrà tutti gli elementi dell'array originale, iniziando dalla posizione start e terminando alla posizione minore di length elementi dalla fine dell'array. Se questo parametro non viene specificato, la sequenza conterrà tutti gli elementi dell'array originale, iniziando dalla posizione start. -Per impostazione predefinita, Filter riordina e reimposta le chiavi dell'array di interi. Questo comportamento può essere modificato impostando preserveKeys su true. Le chiavi stringa vengono sempre conservate, indipendentemente da questo parametro. +Per impostazione predefinita, il filtro riordina e reimposta le chiavi intere dell'array. Questo comportamento può essere modificato impostando preserveKeys su true. Le chiavi stringa vengono sempre conservate, indipendentemente da questo parametro. -sort .[filter]{data-version:2.9} ---------------------------------- -Filtro che ordina un array e mantiene l'associazione degli indici. +sort(?Closure $comparison, string|int|\Closure|null $by=null, string|int|\Closure|bool $byKey=false) .[filter] +-------------------------------------------------------------------------------------------------------------- +Il filtro ordina gli elementi di un array o di un iteratore e conserva le loro chiavi associative. Se è impostato il [locale |develop#Locale], l'ordinamento segue le sue regole, a meno che non sia specificata una funzione di confronto personalizzata. ```latte {foreach ($names|sort) as $name} @@ -601,7 +735,7 @@ Filtro che ordina un array e mantiene l'associazione degli indici. {/foreach} ``` -Array ordinato in ordine inverso. +Array ordinato in ordine inverso: ```latte {foreach ($names|sort|reverse) as $name} @@ -609,16 +743,42 @@ Array ordinato in ordine inverso. {/foreach} ``` -È possibile passare la propria funzione di confronto come parametro: .{data-version:2.10.2} +È possibile specificare una funzione di confronto personalizzata per l'ordinamento (l'esempio mostra come invertire l'ordinamento dal più grande al più piccolo): + +```latte +{var $reverted = ($names|sort: fn($a, $b) => $b <=> $a)} +``` + +Il filtro `|sort` consente anche di ordinare gli elementi per chiave: + +```latte +{foreach ($names|sort: byKey: true) as $name} + ... +{/foreach} +``` + +Se è necessario ordinare una tabella per una colonna specifica, è possibile utilizzare il parametro `by`. Il valore `'name'` nell'esempio specifica che l'ordinamento avverrà per `$item->name` o `$item['name']`, a seconda che `$item` sia un array o un oggetto: ```latte -{var $sorted = ($names|sort: fn($a, $b) => $b <=> $a)} +{foreach ($items|sort: by: 'name') as $item} + {$item->name} +{/foreach} +``` + +È anche possibile definire una funzione di callback che determini il valore in base al quale ordinare: + +```latte +{foreach ($items|sort: by: fn($items) => $items->category->name) as $item} + {$item->name} +{/foreach} ``` +Allo stesso modo si può utilizzare anche il parametro `byKey`. -spaceless .[filter]{data-version:2.10.2} ------------------------------------------ -Rimuove gli spazi bianchi non necessari dall'output. Si può anche usare l'alias `strip`. + +spaceless .[filter] +------------------- +Rimuove gli spazi bianchi non necessari dall'output. Puoi anche usare l'alias `strip`. ```latte {block |spaceless} @@ -637,47 +797,47 @@ Stampa: stripHtml .[filter] ------------------- -Converte l'HTML in testo normale. Ossia, rimuove i tag HTML e converte le entità HTML in testo. +Converte HTML in testo puro. Cioè, rimuove i tag HTML e converte le entità HTML in testo. ```latte -{='

                                                                          one < two

                                                                          '|stripHtml} {* outputs 'one < two' *} +{='

                                                                          one < two

                                                                          '|stripHtml} {* stampa 'one < two' *} ``` -Il testo normale risultante può naturalmente contenere caratteri che rappresentano tag HTML, ad esempio `'<p>'|stripHtml` viene convertito in `

                                                                          `. Non inviare mai il testo risultante con `|noescape`, perché ciò potrebbe causare una vulnerabilità della sicurezza. +Il testo puro risultante può naturalmente contenere caratteri che rappresentano tag HTML, ad esempio `'<p>'|stripHtml` viene convertito in `

                                                                          `. In nessun caso stampare il testo così generato con `|noescape`, poiché ciò può portare a una falla di sicurezza. -substr(int offset, int length = null) .[filter] ------------------------------------------------ -Estrae una fetta di una stringa. Questo filtro è stato sostituito da un filtro [a fetta |#slice]. +substr(int $offset, ?int $length=null) .[filter] +------------------------------------------------ +Estrae una parte di una stringa. Questo filtro è stato sostituito dal filtro [#slice]. ```latte {$string|substr: 1, 2} ``` -translate(string message, ...args) .[filter]{data-version:3.0} --------------------------------------------------------------- -Traduce le espressioni in altre lingue. Per rendere disponibile il filtro, è necessario [impostare il traduttore |develop#TranslatorExtension]. Si possono anche usare i [tag per la traduzione |tags#Translation]. +translate(...$args) .[filter] +----------------------------- +Traduce le espressioni in altre lingue. Affinché il filtro sia disponibile, è necessario [impostare il traduttore |develop#TranslatorExtension]. È inoltre possibile utilizzare i [tag per la traduzione |tags#Traduzioni]. ```latte -{='Baskter'|translate} +{='Carrello'|translate} {$item|translate} ``` -trim(string charlist = " \t\n\r\0\x0B\u{A0}") .[filter] -------------------------------------------------------- -Spogliare i caratteri iniziali e finali, per impostazione predefinita gli spazi bianchi. +trim(string $charlist=" \t\n\r\0\x0B\u{A0}") .[filter] +------------------------------------------------------ +Rimuove gli spazi bianchi (o altri caratteri) dall'inizio e dalla fine di una stringa. ```latte -{=' I like Latte. '|trim} {* outputs 'I like Latte.' *} -{=' I like Latte.'|trim: '.'} {* outputs ' I like Latte' *} +{=' I like Latte. '|trim} {* stampa 'I like Latte.' *} +{=' I like Latte.'|trim: '.'} {* stampa ' I like Latte' *} ``` -truncate(int length, string append = '…') .[filter] +truncate(int $length, string $append='…') .[filter] --------------------------------------------------- -Accorcia una stringa alla lunghezza massima indicata, ma cerca di conservare le parole intere. Se la stringa è troncata, aggiunge un'ellissi alla fine (questo può essere cambiato con il secondo parametro). +Tronca una stringa alla lunghezza massima specificata, cercando di preservare le parole intere. Se la stringa viene accorciata, aggiunge alla fine tre punti (modificabili con il secondo parametro). ```latte {var $title = 'Hello, how are you?'} @@ -689,25 +849,25 @@ Accorcia una stringa alla lunghezza massima indicata, ma cerca di conservare le upper .[filter] --------------- -Converte un valore in maiuscolo. Richiede l'estensione PHP `mbstring`. +Converte una stringa in maiuscolo. Richiede l'estensione PHP `mbstring`. ```latte -{='latte'|upper} {* outputs 'LATTE' *} +{='latte'|upper} {* stampa 'LATTE' *} ``` -Vedere anche [capitalize |#capitalize], [firstUpper |#firstUpper], [lower |#lower]. +Vedi anche [#capitalize], [#firstUpper], [#lower]. webalize .[filter] ------------------ -Converte in ASCII. +Modifica una stringa UTF‑8 nella forma utilizzata negli URL. -Converte gli spazi in trattini. Rimuove i caratteri che non sono alfanumerici, sottolineati o trattini. Converte in minuscolo. Elimina anche gli spazi bianchi iniziali e finali. +Converte in ASCII. Converte gli spazi in trattini. Rimuove i caratteri che non sono alfanumerici, trattini bassi o trattini. Converte in minuscolo. Rimuove anche gli spazi iniziali e finali. ```latte -{var $s = 'Our 10. product'} -{$s|webalize} {* outputs 'our-10-product' *} +{var $s = 'Il nostro 10° prodotto'} +{$s|webalize} {* stampa 'il-nostro-10-prodotto' *} ``` .[caution] -Richiede il pacchetto [nette/utils |utils:]. +Richiede la libreria [nette/utils|utils:]. diff --git a/latte/it/functions.texy b/latte/it/functions.texy index 4b3ffcec12..69ebea678c 100644 --- a/latte/it/functions.texy +++ b/latte/it/functions.texy @@ -1,23 +1,25 @@ -Funzioni del latte -****************** +Funzioni Latte +************** .[perex] -Oltre alle comuni funzioni PHP, è possibile utilizzarle anche nei template. +Nei template, oltre alle normali funzioni PHP, possiamo utilizzare anche queste funzioni aggiuntive. .[table-latte-filters] -| `clamp` | [blocca il valore all'intervallo |#clamp] +| `clamp` | [limita un valore all'interno di un intervallo specificato |#clamp] | `divisibleBy`| [controlla se una variabile è divisibile per un numero |#divisibleBy] -| `even` | [controlla se il numero dato è pari |#even] -| `first` | [restituisce il primo elemento di un array o un carattere di una stringa |#first] -| `last` | [restituisce l'ultimo elemento di un array o un carattere di una stringa |#last] -| `odd` | [controlla se il numero dato è dispari |#odd] -| `slice` | [estrae una fetta di un array o di una stringa |#slice] +| `even` | [controlla se un dato numero è pari |#even] +| `first` | [restituisce il primo elemento di un array o carattere di una stringa |#first] +| `group` | [raggruppa i dati secondo vari criteri |#group] +| `hasBlock` | [verifica l'esistenza di un blocco |#hasBlock] +| `last` | [restituisce l'ultimo elemento di un array o carattere di una stringa |#last] +| `odd` | [controlla se un dato numero è dispari |#odd] +| `slice` | [estrae una parte di un array o di una stringa |#slice] -Utilizzo .[#toc-usage] -====================== +Utilizzo +======== -Le funzioni sono utilizzate allo stesso modo delle comuni funzioni PHP e possono essere usate in tutte le espressioni: +Le funzioni vengono utilizzate allo stesso modo delle normali funzioni PHP e possono essere utilizzate in tutte le espressioni: ```latte

                                                                          {clamp($num, 1, 100)}

                                                                          @@ -25,14 +27,14 @@ Le funzioni sono utilizzate allo stesso modo delle comuni funzioni PHP e possono {if odd($num)} ... {/if} ``` -[Le funzioni personalizzate |extending-latte#functions] possono essere registrate in questo modo: +[Funzioni personalizzate|custom-functions] possono essere registrate in questo modo: ```php $latte = new Latte\Engine; $latte->addFunction('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -Le usiamo in un modello come questo: +Nel template, viene quindi chiamata così: ```latte

                                                                          {shortify($text)}

                                                                          @@ -40,85 +42,115 @@ Le usiamo in un modello come questo: ``` -Funzioni .[#toc-functions] -========================== +Funzioni +======== -clamp(int|float $value, int|float $min, int|float $max): int|float .[method]{data-version:2.9} ----------------------------------------------------------------------------------------------- -Restituisce un valore limitato all'intervallo compreso tra min e max. +clamp(int|float $value, int|float $min, int|float $max): int|float .[method] +---------------------------------------------------------------------------- +Limita un valore all'interno dell'intervallo inclusivo specificato min e max. ```latte {=clamp($level, 0, 255)} ``` -Vedere anche [filtro clamp |filters#clamp]: +Vedi anche [filtro clamp |filters#clamp]. -divisibleBy(int $value, int $by): bool .[method]{data-version:2.10.2} ---------------------------------------------------------------------- -Verifica se una variabile è divisibile per un numero. +divisibleBy(int $value, int $by): bool .[method] +------------------------------------------------ +Controlla se una variabile è divisibile per un numero. ```latte {if divisibleBy($num, 5)} ... {/if} ``` -even(int $value): bool .[method]{data-version:2.10.2} ------------------------------------------------------ -Verifica se il numero dato è pari. +even(int $value): bool .[method] +-------------------------------- +Controlla se un dato numero è pari. ```latte {if even($num)} ... {/if} ``` -first(string|array $value): mixed .[method]{data-version:2.10.2} ----------------------------------------------------------------- -Restituisce il primo elemento di una matrice o un carattere di una stringa: +first(string|iterable $value): mixed .[method] +---------------------------------------------- +Restituisce il primo elemento di un array o carattere di una stringa: ```latte -{=first([1, 2, 3, 4])} {* uscite 1 *} -{=first('abcd')} {* uscite 'a' *} +{=first([1, 2, 3, 4])} {* stampa 1 *} +{=first('abcd')} {* stampa 'a' *} ``` -Vedere anche [last |#last], [filter first |filters#first]. +Vedi anche [#last], [filtro first |filters#first]. -last(string|array $value): mixed .[method]{data-version:2.10.2} ---------------------------------------------------------------- -Restituisce l'ultimo elemento della matrice o il carattere della stringa: +group(iterable $data, string|int|\Closure $by): array .[method]{data-version:3.0.16} +------------------------------------------------------------------------------------ +La funzione raggruppa i dati secondo vari criteri. + +In questo esempio, le righe della tabella vengono raggruppate per la colonna `categoryId`. L'output è un array di array, dove la chiave è il valore nella colonna `categoryId`. [Leggi il tutorial dettagliato|cookbook/grouping]. + +```latte +{foreach group($items, categoryId) as $categoryId => $categoryItems} +
                                                                            + {foreach $categoryItems as $item} +
                                                                          • {$item->name}
                                                                          • + {/foreach} +
                                                                          +{/foreach} +``` + +Vedi anche il filtro [group |filters#group]. + + +hasBlock(string $name): bool .[method]{data-version:3.0.10} +----------------------------------------------------------- +Verifica se esiste un blocco con il nome specificato: + +```latte +{if hasBlock(header)} ... {/if} +``` + +Vedi anche [controllo dell'esistenza dei blocchi |template-inheritance#Controllo dell esistenza dei blocchi ifset]. + + +last(string|array $value): mixed .[method] +------------------------------------------ +Restituisce l'ultimo elemento di un array o carattere di una stringa: ```latte -{=last([1, 2, 3, 4])} {* uscite 4 *} -{=last('abcd')} {* uscite 'd' *} +{=last([1, 2, 3, 4])} {* stampa 4 *} +{=last('abcd')} {* stampa 'd' *} ``` -Vedere anche [first |#first], [filter last |filters#last]. +Vedi anche [#first], [filtro last |filters#last]. -odd(int $value): bool .[method]{data-version:2.10.2} ----------------------------------------------------- -Controlla se il numero dato è dispari. +odd(int $value): bool .[method] +------------------------------- +Controlla se un dato numero è dispari. ```latte {if odd($num)} ... {/if} ``` -slice(string|array $value, int $start, int $length=null, bool $preserveKeys=false): string|array .[method]{data-version:2.10.2} -------------------------------------------------------------------------------------------------------------------------------- -Estrae una fetta di un array o di una stringa. +slice(string|array $value, int $start, ?int $length=null, bool $preserveKeys=false): string|array .[method] +----------------------------------------------------------------------------------------------------------- +Estrae una parte di un array o di una stringa. ```latte -{=slice('hello', 1, 2)} {* output 'el' *} -{=slice(['a', 'b', 'c'], 1, 2)} {* output ['b', 'c'] *} +{=slice('hello', 1, 2)} {* stampa 'el' *} +{=slice(['a', 'b', 'c'], 1, 2)} {* stampa ['b', 'c'] *} ``` -Il filtro slice funziona come la funzione PHP `array_slice` per gli array e `mb_substr` per le stringhe con un fallback a `iconv_substr` in modalità UTF-8. +Il filtro funziona come la funzione PHP `array_slice` per gli array o `mb_substr` per le stringhe con fallback alla funzione `iconv_substr` in modalità UTF‑8. -Se start è non negativo, la sequenza inizierà da quell'inizio nella variabile. Se start è negativo, la sequenza inizierà a quella distanza dalla fine della variabile. +Se start è positivo, la sequenza inizierà spostata di questo numero dall'inizio dell'array/stringa. Se è negativo, la sequenza inizierà spostata di tanto dalla fine. -Se la lunghezza è data ed è positiva, la sequenza conterrà fino a quel numero di elementi. Se la variabile è più corta della lunghezza, saranno presenti solo gli elementi disponibili della variabile. Se la lunghezza è data ed è negativa, la sequenza si fermerà a tanti elementi dalla fine della variabile. Se viene omesso, la sequenza conterrà tutti gli elementi dall'offset fino alla fine della variabile. +Se il parametro length è specificato ed è positivo, la sequenza conterrà tanti elementi. Se a questa funzione viene passato un parametro length negativo, la sequenza conterrà tutti gli elementi dell'array originale, iniziando dalla posizione start e terminando alla posizione minore di length elementi dalla fine dell'array. Se questo parametro non viene specificato, la sequenza conterrà tutti gli elementi dell'array originale, iniziando dalla posizione start. -Per impostazione predefinita, Filter riordina e reimposta le chiavi dell'array di interi. Questo comportamento può essere modificato impostando preserveKeys su true. Le chiavi stringa vengono sempre conservate, indipendentemente da questo parametro. +Per impostazione predefinita, il filtro riordina e reimposta le chiavi intere dell'array. Questo comportamento può essere modificato impostando preserveKeys su true. Le chiavi stringa vengono sempre conservate, indipendentemente da questo parametro. diff --git a/latte/it/guide.texy b/latte/it/guide.texy index 48252e1fc3..f74013466e 100644 --- a/latte/it/guide.texy +++ b/latte/it/guide.texy @@ -1,46 +1,45 @@ -Come iniziare con il Latte -************************** +Iniziare con Latte +******************
                                                                          -I modelli migliorano l'organizzazione del codice, separano la logica dell'applicazione dalla presentazione e migliorano la sicurezza. Offrono caratteristiche e capacità espressive per la generazione di HTML di gran lunga superiori a quelle di PHP stesso. +I template migliorano l'organizzazione del codice, separano la logica dell'applicazione dalla presentazione e aumentano la sicurezza. Offrono funzionalità e mezzi espressivi molto migliori per generare HTML rispetto al solo PHP. -Latte è il sistema di template più sicuro per PHP. Vi piacerà la sua sintassi intuitiva. Un'ampia gamma di funzioni utili semplificherà notevolmente il vostro lavoro. -Offre una protezione di prim'ordine contro le [vulnerabilità critiche |safety-first] e consente di concentrarsi sulla creazione di applicazioni di alta qualità senza preoccuparsi della loro sicurezza. +Latte è il sistema di templating più sicuro per PHP. Amerai la sua sintassi intuitiva. Un'ampia gamma di funzioni utili ti faciliterà notevolmente il lavoro. Fornisce una protezione di prim'ordine contro le [vulnerabilità critiche|safety-first] e ti consente di concentrarti sulla creazione di applicazioni di qualità senza preoccuparti della loro sicurezza. -Come scrivere modelli con Latte? .[#toc-how-to-write-templates-using-latte] ---------------------------------------------------------------------------- +Come scrivere template usando Latte? +------------------------------------ -Latte è progettato in modo intelligente ed è facile da imparare per chi ha familiarità con PHP, in quanto può adottare rapidamente i suoi tag di base. +Latte è progettato in modo intelligente ed è facile da imparare per coloro che conoscono PHP e padroneggiano i tag di base. -- Per prima cosa, familiarizzate con la [sintassi di Latte |syntax] e [provate tutto online |https://fiddle.nette.org/latte/] -- Dare un'occhiata all'insieme di [tag |tags] e [filtri |filters]di base -- Scrivere modelli nell'[editor con il supporto di Latte |recipes#Editors and IDE] +- Innanzitutto, familiarizza con la [sintassi di Latte|syntax] e [PROVALA ONLINE |https://fiddle.nette.org/latte/#9cc0cf6d89#9cc0cf6d89] +- Dai un'occhiata al set di base di [tag|tags] e [filtri|filters] +- Scrivi i template in un [editor con supporto per Latte |recipes#Editor e IDE] -Come usare Latte in PHP? .[#toc-how-to-use-latte-in-php] --------------------------------------------------------- +Come usare Latte in PHP? +------------------------ -Implementare Latte nella vostra nuova applicazione è una questione di minuti: +Implementare Latte nella tua nuova applicazione è questione di pochi minuti: -- Innanzitutto, [installate ed eseguite Latte |develop#Installation] -- Coccolarsi con lo [strumento di debug Tracy |develop#Debugging and Tracy] -- Estendere Latte con [funzionalità personalizzate |extending-latte] +- Innanzitutto [installa ed esegui Latte |develop#Installazione] +- Lasciati coccolare dallo [strumento di debug Tracy |develop#Debugging e Tracy] +- Estendi Latte con [funzionalità personalizzate |extending-latte] -Se state convertendo un vecchio progetto scritto in PHP a Latte, lo [strumento per la conversione del codice PHP a Latte |cookbook/migration-from-php] vi faciliterà la migrazione. Oppure state pensando di passare a Latte da Twig? Abbiamo un [convertitore di template Twig in Latte |cookbook/migration-from-twig] per voi. +Se stai convertendo un vecchio progetto scritto in PHP puro a Latte, la migrazione sarà facilitata dallo [strumento per convertire codice PHP in Latte |cookbook/migration-from-php]. O stai pensando di passare a Latte da Twig? Abbiamo per te un [convertitore di template da Twig a Latte |cookbook/migration-from-twig]. -Cos'altro può fare Latte? .[#toc-what-else-can-latte-do] --------------------------------------------------------- +Cos'altro può fare Latte? +------------------------- -Il Latte viene fornito completamente equipaggiato, con tutti gli elementi essenziali inclusi. +Latte viene fornito completamente equipaggiato, con tutto l'essenziale incluso. -- La vostra produttività sarà incrementata dai [meccanismi di ereditarietà |template-inheritance] che riutilizzano elementi e strutture ripetute. -- L'armatura [Sandbox |Sandbox] isola i modelli da fonti non attendibili, come quelle modificate dagli stessi utenti. -- Per ulteriore ispirazione, ecco [suggerimenti e trucchi |recipes] +- La tua produttività sarà potenziata dai [meccanismi di ereditarietà |template-inheritance] grazie ai quali elementi e strutture ripetute vengono riutilizzati +- Il bunker corazzato [sandbox] isola i template da fonti non attendibili, che ad esempio vengono modificati dagli stessi utenti +- Per ulteriore ispirazione, ci sono [consigli e trucchi |recipes]
                                                                          -{{description: Latte è il sistema di template più sicuro per PHP. Previene molte vulnerabilità di sicurezza. Apprezzerete la sua sintassi intuitiva e apprezzerete molte utili modifiche.}} +{{description: Latte è il sistema di templating più sicuro per PHP. Previene molte vulnerabilità di sicurezza. Apprezzerai la sua sintassi intuitiva e le numerose funzionalità utili.}} diff --git a/latte/it/loaders.texy b/latte/it/loaders.texy new file mode 100644 index 0000000000..d4c5c0b9a7 --- /dev/null +++ b/latte/it/loaders.texy @@ -0,0 +1,198 @@ +Loader +****** + +.[perex] +I loader sono il meccanismo che Latte utilizza per ottenere il codice sorgente dei tuoi template. Molto spesso i template sono memorizzati come file su disco, ma grazie al sistema flessibile dei loader, puoi caricarli praticamente da qualsiasi luogo o persino generarli dinamicamente. + + +Cos'è un Loader? +================ + +Quando lavori con i template, di solito immagini file `.latte` situati nella struttura delle directory del tuo progetto. Di questo si occupa il [#FileLoader] predefinito in Latte. Tuttavia, la connessione tra il nome del template (come `'main.latte'` o `'components/card.latte'`) e il suo codice sorgente effettivo *non deve* essere necessariamente una mappatura diretta a un percorso di file. + +È qui che entrano in gioco i loader. Un loader è un oggetto che ha il compito di prendere il nome di un template (una stringa identificativa) e fornire a Latte il suo codice sorgente. Latte si affida completamente al loader configurato per questo compito. Questo vale non solo per il template iniziale richiesto tramite `$latte->render('main.latte')`, ma anche per **ogni template referenziato all'interno** utilizzando tag come `{include ...}`, `{layout ...}`, `{embed ...}` o `{import ...}`. + +Perché usare un loader personalizzato? + +- **Caricamento da fonti alternative:** Ottenere template memorizzati in un database, in una cache (come Redis o Memcached), in un sistema di controllo versione (come Git, basato su un commit specifico) o generati dinamicamente. +- **Implementazione di convenzioni di denominazione personalizzate:** Potresti voler utilizzare alias più brevi per i template o implementare una logica specifica per i percorsi di ricerca (ad es. cercare prima nella directory del tema, poi tornare alla directory predefinita). +- **Aggiunta di sicurezza o controllo degli accessi:** Un loader personalizzato può verificare i permessi dell'utente prima di caricare determinati template. +- **Pre-elaborazione:** Sebbene generalmente non sia raccomandato ([i passaggi di compilazione |compiler-passes] sono migliori), un loader *potrebbe* teoricamente pre-elaborare il contenuto del template prima di passarlo a Latte. + +Imposti il loader per un'istanza `Latte\Engine` utilizzando il metodo `setLoader()`: + +```php +$latte = new Latte\Engine; + +// Utilizzo del FileLoader predefinito per i file in '/path/to/templates' +$loader = new Latte\Loaders\FileLoader('/path/to/templates'); +$latte->setLoader($loader); +``` + +Il loader deve implementare l'interfaccia `Latte\Loader`. + + +Loader integrati +================ + +Latte offre diversi loader standard: + + +FileLoader +---------- + +Questo è il **loader predefinito** utilizzato dalla classe `Latte\Engine` se non ne viene specificato un altro. Carica i template direttamente dal file system. + +Opzionalmente, puoi impostare una directory radice per limitare l'accesso: + +```php +use Latte\Loaders\FileLoader; + +// Quanto segue consentirà il caricamento dei template solo dalla directory /var/www/html/templates +$loader = new FileLoader('/var/www/html/templates'); +$latte->setLoader($loader); + +// $latte->render('../../../etc/passwd'); // Questo lancerebbe un'eccezione + +// Rendering di un template situato in /var/www/html/templates/pages/contact.latte +$latte->render('pages/contact.latte'); +``` + +Quando si utilizzano tag come `{include}` o `{layout}`, risolve i nomi dei template relativamente al template corrente, a meno che non venga specificato un percorso assoluto. + + +StringLoader +------------ + +Questo loader ottiene il contenuto del template da un array associativo, dove le chiavi sono i nomi dei template (identificatori) e i valori sono le stringhe del codice sorgente del template. È particolarmente utile per i test o piccole applicazioni in cui i template possono essere memorizzati direttamente nel codice PHP. + +```php +use Latte\Loaders\StringLoader; + +$loader = new StringLoader([ + 'main.latte' => 'Hello {$name}, include is below:{include helper.latte}', + 'helper.latte' => '{var $x = 10}Included content: {$x}', + // Aggiungi altri template secondo necessità +]); + +$latte->setLoader($loader); + +$latte->render('main.latte', ['name' => 'World']); +// Output: Hello World, include is below:Included content: 10 +``` + +Se hai bisogno di renderizzare solo un singolo template direttamente da una stringa senza la necessità di inclusioni o ereditarietà che fanno riferimento ad altri template stringa nominati, puoi passare la stringa direttamente al metodo `render()` o `renderToString()` quando usi `StringLoader` senza un array: + +```php +$loader = new StringLoader; +$latte->setLoader($loader); + +$templateString = 'Hello {$name}!'; +$output = $latte->renderToString($templateString, ['name' => 'Alice']); +// $output contiene 'Hello Alice!' +``` + + +Creazione di un Loader personalizzato +===================================== + +Per creare un loader personalizzato (ad es. per caricare template da un database, cache, sistema di controllo versione o altra fonte), devi creare una classe che implementi l'interfaccia [api:Latte\Loader]. + +Vediamo cosa deve fare ogni metodo. + + +getContent(string $name): string .[method] +------------------------------------------ +Questo è il metodo principale del loader. Il suo compito è ottenere e restituire il codice sorgente completo del template identificato da `$name` (come passato al metodo `$latte->render()` o restituito dal metodo [#getReferredName()]). + +Se il template non può essere trovato o accessibile, questo metodo **deve lanciare un'eccezione `Latte\RuntimeException`**. + +```php +public function getContent(string $name): string +{ + // Esempio: Caricamento da un ipotetico storage interno + $content = $this->storage->read($name); + if ($content === null) { + throw new Latte\RuntimeException("Template '$name' cannot be loaded."); + } + return $content; +} +``` + + +getReferredName(string $name, string $referringName): string .[method] +---------------------------------------------------------------------- +Questo metodo gestisce la traduzione dei nomi dei template utilizzati all'interno di tag come `{include}`, `{layout}`, ecc. Quando Latte incontra, ad esempio, `{include 'partial.latte'}` all'interno di `main.latte`, chiama questo metodo con `$name = 'partial.latte'` e `$referringName = 'main.latte'`. + +Il compito del metodo è tradurre `$name` in un identificatore canonico (ad es. percorso assoluto, chiave univoca del database) che verrà utilizzato quando si chiamano altri metodi del loader, in base al contesto fornito in `$referringName`. + +```php +public function getReferredName(string $name, string $referringName): string +{ + return ...; +} +``` + + +getUniqueId(string $name): string .[method] +------------------------------------------- +Latte utilizza una cache dei template compilati per migliorare le prestazioni. Ogni file di template compilato necessita di un nome univoco derivato dall'identificatore del template sorgente. Questo metodo fornisce una stringa che **identifica univocamente** il template `$name`. + +Per i template basati su file, il percorso assoluto può servire. Per i template in un database, è comune una combinazione di un prefisso e dell'ID del database. + +```php +public function getUniqueId(string $name): string +{ + return ...; +} +``` + + +Esempio: Loader Database Semplice +--------------------------------- + +Questo esempio mostra la struttura di base di un loader che carica template memorizzati in una tabella di database chiamata `templates` con colonne `name` (identificatore univoco), `content` e `updated_at`. + +```php +use Latte; + +class DatabaseLoader implements Latte\Loader +{ + public function __construct( + private \PDO $db, + ) { + } + + public function getContent(string $name): string + { + $stmt = $this->db->prepare('SELECT content FROM templates WHERE name = ?'); + $stmt->execute([$name]); + $content = $stmt->fetchColumn(); + if ($content === false) { + throw new Latte\RuntimeException("Template '$name' not found in database."); + } + return $content; + } + + // Questo semplice esempio presuppone che i nomi dei template ('homepage', 'article', ecc.) + // siano ID univoci e che i template non si riferiscano l'un l'altro relativamente. + public function getReferredName(string $name, string $referringName): string + { + return $name; + } + + public function getUniqueId(string $name): string + { + // L'uso di un prefisso e del nome stesso è univoco e sufficiente qui + return 'db_' . $name; + } +} + +// Utilizzo: +$pdo = new \PDO(/* dettagli connessione */); +$loader = new DatabaseLoader($pdo); +$latte->setLoader($loader); +$latte->render('homepage'); // Carica il template chiamato 'homepage' dal DB +``` + +I loader personalizzati ti danno il controllo completo su da dove provengono i tuoi template Latte, consentendo l'integrazione con vari sistemi di storage e flussi di lavoro. diff --git a/latte/it/recipes.texy b/latte/it/recipes.texy index 5985254606..6da969f8fe 100644 --- a/latte/it/recipes.texy +++ b/latte/it/recipes.texy @@ -1,45 +1,45 @@ -Suggerimenti e trucchi -********************** +Consigli e Trucchi +****************** -Editor e IDE .[#toc-editors-and-ide] -==================================== +Editor e IDE +============ -Scrivete i modelli in un editor o IDE che supporti Latte. Sarà molto più piacevole. +Scrivi i template in un editor o IDE che supporti Latte. Sarà molto più piacevole. -- NetBeans IDE ha un supporto integrato -- PhpStorm: installare il [plugin Latte |https://plugins.jetbrains.com/plugin/7457-latte] in `Settings > Plugins > Marketplace` -- VS Code: cercare il plugin "Nette Latte + Neon" in Markerplace -- Sublime Text 3: in Package Control trovare e installare il pacchetto `Nette` e selezionare Latte in `View > Syntax` -- nei vecchi editor usare l'evidenziazione Smarty per i file .latte +- PhpStorm: installa il [plugin Latte|https://plugins.jetbrains.com/plugin/7457-latte] in `Settings > Plugins > Marketplace` +- VS Code: installa [Nette Latte + Neon|https://marketplace.visualstudio.com/items?itemName=Kasik96.latte], [Nette Latte templates|https://marketplace.visualstudio.com/items?itemName=smuuf.latte-lang] o il più recente plugin [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode] +- NetBeans IDE: il supporto nativo per Latte è incluso nell'installazione +- Sublime Text 3: in Package Control trova e installa il pacchetto `Nette` e scegli Latte in `View > Syntax` +- nei vecchi editor, usa l'evidenziazione Smarty per i file .latte -Il plugin per PhpStorm è molto avanzato e può suggerire perfettamente il codice PHP. Per lavorare in modo ottimale, utilizzare [modelli digitati |type-system]. +Il plugin per PhpStorm è molto avanzato e offre un eccellente suggerimento del codice PHP. Per funzionare in modo ottimale, utilizza [template tipizzati|type-system]. [* latte-phpstorm-plugin.webp *] -Il supporto per Latte si trova anche nel web code highlighter [Prism.js |https://prismjs.com/#supported-languages] e nell'editor [Ace |https://ace.c9.io]. +Il supporto per Latte si trova anche nell'evidenziatore di codice web [Prism.js|https://prismjs.com/#supported-languages] e nell'editor [Ace|https://ace.c9.io]. -Latte all'interno di JavaScript o CSS .[#toc-latte-inside-javascript-or-css] -============================================================================ +Latte all'interno di JavaScript o CSS +===================================== -Latte può essere usato molto comodamente all'interno di JavaScript o CSS. Ma come evitare che Latte consideri erroneamente il codice JavaScript o lo stile CSS come un tag Latte? +Latte può essere utilizzato molto comodamente anche all'interno di JavaScript o CSS. Ma come evitare la situazione in cui Latte considererebbe erroneamente il codice JavaScript o lo stile CSS come un tag Latte? ```latte ``` -**Opzione 1** +**Variante 1** -Evitare le situazioni in cui una lettera segue immediatamente una `{`, inserendo uno spazio, un'interruzione di riga o una virgoletta tra di esse: +Evita la situazione in cui una lettera segue immediatamente `{`, ad esempio inserendo uno spazio, un'interruzione di riga o una virgoletta prima di essa: ```latte -

                                                                          +

                                                                          ``` -Due modi e due tipi diversi di escape dei dati. All'interno degli elementi ` ``` -Tuttavia, se vogliamo inserirlo in un attributo HTML, dobbiamo ancora eseguire l'escape delle virgolette nelle entità HTML: +Se però volessimo inserirlo in un attributo HTML, dobbiamo ancora eseguire l'escaping degli apici in entità HTML: ```html
                                                                          ``` -Tuttavia, il contesto annidato non deve essere solo JS o CSS. In genere si tratta anche di un URL. I parametri negli URL vengono evasi convertendo i caratteri speciali in sequenze che iniziano con `%`. Esempio: +Ma il contesto annidato non deve essere necessariamente solo JS o CSS. Comunemente è anche un URL. I parametri nell'URL vengono escapati convertendo i caratteri con significato speciale in sequenze che iniziano con `%`. Esempio: ``` https://example.org/?a=Jazz&b=Rock%27n%27Roll ``` -E quando produciamo questa stringa in un attributo, applichiamo ancora l'escape in base a questo contesto e sostituiamo `&` with `&`: +E quando stampiamo questa stringa in un attributo, applichiamo ancora l'escaping secondo questo contesto e sostituiamo `&` con `&`: ```html ``` -Se siete arrivati a leggere fin qui, congratulazioni, è stato faticoso. Ora avete una buona idea di cosa siano i contesti e l'escape. E non dovete preoccuparvi che sia complicato. Latte lo fa automaticamente. +Se siete arrivati fin qui, congratulazioni, è stato estenuante. Ora avete una buona idea di cosa siano i contesti e l'escaping. E non dovete preoccuparvi che sia complicato. Latte fa tutto questo per voi automaticamente. -Latte contro i sistemi ingenui .[#toc-latte-vs-naive-systems] -============================================================= +Latte vs sistemi ingenui +======================== -Abbiamo mostrato come eseguire correttamente l'escape in un documento HTML e come sia fondamentale conoscere il contesto, cioè dove si stanno producendo i dati. In altre parole, come funziona l'escape sensibile al contesto. -Sebbene questo sia un prerequisito per una difesa funzionale dagli XSS, **Latte è l'unico sistema di template per PHP che lo fa **. +Abbiamo mostrato come si esegue correttamente l'escaping in un documento HTML e quanto sia fondamentale la conoscenza del contesto, cioè del luogo in cui stampiamo i dati. In altre parole, come funziona l'escaping sensibile al contesto. Sebbene sia un prerequisito indispensabile per una difesa funzionale contro XSS, **Latte è l'unico sistema di templating per PHP che sa fare questo.** -Com'è possibile quando tutti i sistemi oggi affermano di avere l'escape automatico? -L'escape automatico senza conoscere il contesto è una stronzata che **crea un falso senso di sicurezza**. +Come è possibile, quando tutti i sistemi oggi affermano di avere l'escaping automatico? L'escaping automatico senza conoscenza del contesto è un po' una sciocchezza, che **crea una falsa impressione di sicurezza**. -I sistemi di template come Twig, Laravel Blade e altri non vedono alcuna struttura HTML nel template. Pertanto, non vedono nemmeno i contesti. Rispetto a Latte, sono ciechi e ingenui. Gestiscono solo il proprio markup, tutto il resto è un flusso di caratteri irrilevante per loro: +I sistemi di templating, come Twig, Laravel Blade e altri, non vedono alcuna struttura HTML nel template. Non vedono quindi nemmeno i contesti. Rispetto a Latte, sono ciechi e ingenui. Elaborano solo i propri tag, tutto il resto è per loro un flusso di caratteri irrilevante:
                                                                          -```twig .{file:Twig template as seen by Twig himself} -░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░ -░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░{{ text }}░░░░ +```twig .{file:Template Twig, come lo vede Twig stesso} +░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░ +░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░ ``` -```twig .{file:Twig template as the designer sees it} -- in text: {{ text }} -- in tag: -- in attribute: -- in unquoted attribute: -- in attribute containing URL: -- in attribute containing JavaScript: -- in attribute containing CSS: -- in JavaScriptu: -- in CSS: -- in comment: +```twig .{file:Template Twig, come lo vede il designer} +- nel testo: {{ foo }} +- nel tag: +- nell'attributo: +- nell'attributo senza virgolette: +- nell'attributo contenente URL: +- nell'attributo contenente JavaScript: +- nell'attributo contenente CSS: +- in JavaScript: +- in CSS: +- nel commento: ```
                                                                          -I sistemi ingenui si limitano a convertire meccanicamente i caratteri di `< > & ' "` in entità HTML, il che è un modo valido di evadere nella maggior parte degli usi, ma non sempre. Pertanto, non sono in grado di rilevare o prevenire varie falle di sicurezza, come mostreremo di seguito. +I sistemi ingenui convertono meccanicamente solo i caratteri `< > & ' "` in entità HTML, che sebbene sia un metodo di escaping valido nella maggior parte dei casi d'uso, non lo è affatto sempre. Non possono quindi né rilevare né prevenire la creazione di varie falle di sicurezza, come mostreremo oltre. -Latte vede il template nello stesso modo in cui lo vedete voi. Capisce l'HTML, l'XML, riconosce i tag, gli attributi, ecc. Per questo motivo, distingue i contesti e tratta i dati di conseguenza. Offre quindi una protezione davvero efficace contro la vulnerabilità critica Cross-site Scripting. +Latte vede il template come lo vedi tu. Capisce HTML, XML, riconosce tag, attributi, ecc. E grazie a ciò distingue i singoli contesti e tratta i dati di conseguenza. Offre così una protezione davvero efficace contro la vulnerabilità critica Cross-site Scripting. + +
                                                                          + +```latte .{file:Template Latte, come lo vede Latte} +░░░░░░░░░░░{$foo} +░░░░░░░░░░ +░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░ +░░░░░░░░░ +░░░░░░░░░░░░░░░ +``` + +```latte .{file:Template Latte, come lo vede il designer} +- nel testo: {$foo} +- nel tag: +- nell'attributo: +- nell'attributo senza virgolette: +- nell'attributo contenente URL: +- nell'attributo contenente JavaScript: +- nell'attributo contenente CSS: +- in JavaScript: +- in CSS: +- nel commento: +``` + +
                                                                          -Dimostrazione dal vivo .[#toc-live-demonstration] -================================================= +Dimostrazione dal vivo +====================== -A sinistra si vede il modello in Latte, a destra il codice HTML generato. La variabile `$text` viene emessa più volte, ogni volta in un contesto leggermente diverso. E quindi viene evasa in modo un po' diverso. È possibile modificare il codice del modello, ad esempio cambiare il contenuto della variabile, ecc. Provate: +A sinistra vedi il template in Latte, a destra il codice HTML generato. La variabile `$text` viene stampata più volte qui, e ogni volta in un contesto leggermente diverso. E quindi anche escapata in modo leggermente diverso. Puoi modificare tu stesso il codice del template, ad esempio cambiare il contenuto della variabile, ecc. Prova:
                                                                          ``` .{file:template.latte; min-height: 14em}[fiddle-source] -{* TRY TO EDIT THIS TEMPLATE *} +{* PROVA A MODIFICARE QUESTO TEMPLATE *} {var $text = "Rock'n'Roll"} - {$text} - @@ -270,63 +286,61 @@ A sinistra si vede il modello in Latte, a destra il codice HTML generato. La var
                                                                          -Non è fantastico? Latte esegue automaticamente l'escape sensibile al contesto, così il programmatore: +Non è fantastico! Latte esegue l'escaping sensibile al contesto automaticamente, quindi il programmatore: -- non deve pensare o sapere come eseguire l'escape dei dati +- non deve pensare né sapere come si esegue l'escaping dove - non può sbagliare -- non può dimenticarsene +- non può dimenticare l'escaping -Questi non sono nemmeno tutti i contesti che Latte distingue durante l'output e per i quali personalizza il trattamento dei dati. Passeremo ora in rassegna altri casi interessanti. +Questi non sono nemmeno tutti i contesti che Latte distingue durante la stampa e per i quali adatta il trattamento dei dati. Vedremo ora altri casi interessanti. -Come hackerare i sistemi ingenui .[#toc-how-to-hack-naive-systems] -================================================================== +Come hackerare i sistemi ingenui +================================ -Utilizzeremo alcuni esempi pratici per mostrare quanto sia importante la differenziazione del contesto e perché i sistemi di template ingenui non forniscono una protezione sufficiente contro gli XSS, a differenza di Latte. -Negli esempi useremo Twig come rappresentante di un sistema ingenuo, ma lo stesso vale per altri sistemi. +Su alcuni esempi pratici, mostreremo quanto sia importante la distinzione dei contesti e perché i sistemi di templating ingenui non forniscono una protezione sufficiente contro XSS, a differenza di Latte. Come rappresentante di un sistema ingenuo, useremo Twig negli esempi, ma lo stesso vale anche per altri sistemi. -Vulnerabilità degli attributi .[#toc-attribute-vulnerability] -------------------------------------------------------------- +Vulnerabilità dell'attributo +---------------------------- -Proviamo a iniettare codice dannoso nella pagina usando l'attributo HTML come abbiamo [mostrato sopra |#How does the vulnerability arise]. Abbiamo un template in Twig che mostra un'immagine: +Cercheremo di iniettare codice dannoso nella pagina tramite un attributo HTML, come abbiamo [mostrato sopra |#Come nasce la vulnerabilità]. Supponiamo di avere un template in Twig che renderizza un'immagine: ```twig .{file:Twig} {{ ``` -Si noti che non ci sono virgolette intorno ai valori degli attributi. Il codificatore potrebbe averle dimenticate, cosa che capita. Ad esempio, in React il codice è scritto così, senza virgolette, e un codificatore che cambia linguaggio può facilmente dimenticare le virgolette. +Notate che non ci sono virgolette attorno ai valori degli attributi. Il codificatore potrebbe averle dimenticate, cosa che semplicemente accade. Ad esempio, in React, il codice viene scritto così, senza virgolette, e un codificatore che alterna i linguaggi può quindi facilmente dimenticare le virgolette. -L'aggressore inserisce una stringa abilmente costruita `foo onload=alert('Hacked!')` come didascalia dell'immagine. Sappiamo già che Twig non è in grado di capire se una variabile viene stampata in un flusso di testo HTML, all'interno di un attributo, di un commento HTML e così via; in breve, non distingue i contesti. E converte meccanicamente i caratteri di `< > & ' "` in entità HTML. -Quindi il codice risultante sarà simile a questo: +Un aggressore inserisce come didascalia dell'immagine una stringa abilmente costruita `foo onload=alert('Hacked!')`. Sappiamo già che Twig non può riconoscere se la variabile viene stampata nel flusso del testo HTML, all'interno di un attributo, di un commento HTML, ecc., in breve non distingue i contesti. E converte meccanicamente solo i caratteri `< > & ' "` in entità HTML. Quindi il codice risultante sarà simile a questo: ```html foo ``` -**È stata creata una falla di sicurezza! +**E si è creata una falla di sicurezza!** -Un falso attributo `onload` è diventato parte della pagina e il browser lo esegue immediatamente dopo aver scaricato l'immagine. +L'attributo `onload` contraffatto è diventato parte della pagina e il browser lo esegue immediatamente dopo aver scaricato l'immagine. -Vediamo ora come Latte gestisce lo stesso modello: +Ora vediamo come Latte gestisce lo stesso template: ```latte .{file:Latte} {$imageAlt} ``` -Latte vede il template nello stesso modo in cui lo vedete voi. A differenza di Twig, capisce l'HTML e sa che una variabile viene stampata come valore di un attributo non tra virgolette. Ecco perché le aggiunge. Quando un utente malintenzionato inserisce la stessa didascalia, il codice risultante sarà simile a questo: +Latte vede il template come lo vedi tu. A differenza di Twig, capisce HTML e sa che la variabile viene stampata come valore di un attributo che non è tra virgolette. Pertanto le aggiunge. Quando l'aggressore inserisce la stessa didascalia, il codice risultante sarà simile a questo: ```html foo onload=alert('Hacked!') ``` -**Latte ha impedito con successo l'XSS.** +**Latte ha prevenuto con successo l'XSS.** -Stampare una variabile in JavaScript .[#toc-printing-a-variable-in-javascript] ------------------------------------------------------------------------------- +Stampa di variabili in JavaScript +--------------------------------- -Grazie all'escape sensibile al contesto, è possibile utilizzare le variabili PHP in modo nativo all'interno di JavaScript. +Grazie all'escaping sensibile al contesto, è possibile utilizzare in modo completamente nativo le variabili PHP all'interno di JavaScript. ```latte

                                                                          {$movie}

                                                                          @@ -334,7 +348,7 @@ Grazie all'escape sensibile al contesto, è possibile utilizzare le variabili PH ``` -Se la variabile `$movie` memorizza la stringa `'Amarcord & 8 1/2'`, genera il seguente output. Si noti il diverso escape usato in HTML e JavaScript e anche nell'attributo `onclick`: +Se la variabile `$movie` contiene la stringa `'Amarcord & 8 1/2'`, verrà generato il seguente output. Notate che all'interno dell'HTML viene utilizzato un escaping diverso rispetto a quello all'interno di JavaScript e ancora diverso nell'attributo `onclick`: ```latte

                                                                          Amarcord & 8 1/2

                                                                          @@ -343,29 +357,27 @@ Se la variabile `$movie` memorizza la stringa `'Amarcord & 8 1/2'`, genera il se ``` -Controllo del collegamento .[#toc-link-checking] ------------------------------------------------- +Controllo dei link +------------------ -Latte controlla automaticamente se la variabile utilizzata negli attributi `src` o `href` contiene un URL web (cioè un protocollo HTTP) e impedisce la scrittura di link che potrebbero rappresentare un rischio per la sicurezza. +Latte controlla automaticamente se la variabile utilizzata negli attributi `src` o `href` contiene un URL web (cioè protocollo HTTP) e impedisce la visualizzazione di link che potrebbero rappresentare un rischio per la sicurezza. ```latte {var $link = 'javascript:attack()'} -click here +clicca ``` -Scrive: +Stampa: ```latte -click here +clicca ``` -Il controllo può essere disattivato utilizzando il filtro [nocheck |filters#nocheck]. +Il controllo può essere disabilitato usando il filtro [nocheck |filters#nocheck]. -Limiti del Latte .[#toc-limits-of-latte] -======================================== +Limiti di Latte +=============== -Latte non è una protezione XSS completa per l'intera applicazione. Non saremmo contenti se vi fermaste a pensare alla sicurezza quando usate Latte. -L'obiettivo di Latte è garantire che un aggressore non possa alterare la struttura di una pagina, manomettere elementi o attributi HTML. Ma non controlla la correttezza del contenuto dei dati in uscita. O la correttezza del comportamento di JavaScript. -Questo va oltre lo scopo del sistema di template. La verifica della correttezza dei dati, soprattutto di quelli inseriti dall'utente e quindi non attendibili, è un compito importante per il programmatore. +Latte non è una protezione completamente completa contro XSS per l'intera applicazione. Non vorremmo che smetteste di pensare alla sicurezza quando usate Latte. L'obiettivo di Latte è garantire che un aggressore non possa modificare la struttura della pagina, contraffare elementi o attributi HTML. Ma non controlla la correttezza del contenuto dei dati stampati. O la correttezza del comportamento di JavaScript. Questo va oltre le competenze del sistema di templating. La verifica della correttezza dei dati, specialmente quelli inseriti dall'utente e quindi non attendibili, è un compito importante del programmatore. diff --git a/latte/it/sandbox.texy b/latte/it/sandbox.texy index f29d57488e..3cc3ee89d8 100644 --- a/latte/it/sandbox.texy +++ b/latte/it/sandbox.texy @@ -1,12 +1,10 @@ -Scatola di sabbia -***************** +Sandbox +******* -.[perex]{data-version:2.8} -Sandbox fornisce un livello di sicurezza che consente di controllare quali tag, funzioni PHP, metodi ecc. possono essere utilizzati nei template. Grazie alla modalità sandbox, è possibile collaborare in modo sicuro con un cliente o un codificatore esterno alla creazione di modelli, senza preoccuparsi di compromettere l'applicazione o di effettuare operazioni indesiderate. +.[perex] +Sandbox fornisce un livello di sicurezza che ti dà il controllo su quali tag, funzioni PHP, metodi, ecc. possono essere utilizzati nei template. Grazie alla modalità sandbox, puoi collaborare in sicurezza con il cliente o un codificatore esterno alla creazione dei template, senza doverti preoccupare che l'applicazione venga compromessa o che vengano eseguite operazioni indesiderate. -Come funziona? Basta definire ciò che si vuole consentire nel modello. All'inizio, tutto è vietato e si concedono gradualmente le autorizzazioni: - -Il codice seguente consente al template di utilizzare i tag `{block}`, `{if}`, `{else}` e `{=}` (quest'ultimo è un tag per [stampare una variabile o un'espressione |tags#Printing]) e tutti i filtri: +Come funziona? Semplicemente definiamo tutto ciò che permettiamo al template. Di base, tutto è vietato e noi gradualmente permettiamo le cose. Con il seguente codice, permettiamo all'autore del template di utilizzare i tag `{block}`, `{if}`, `{else}` e `{=}`, che è il tag per [stampare una variabile o un'espressione |tags#Stampa] e tutti i filtri: ```php $policy = new Latte\Sandbox\SecurityPolicy; @@ -16,7 +14,7 @@ $policy->allowFilters($policy::All); $latte->setPolicy($policy); ``` -Possiamo anche consentire l'accesso a funzioni, metodi o proprietà globali degli oggetti: +Inoltre, possiamo permettere singole funzioni, metodi o proprietà degli oggetti: ```php $policy->allowFunctions(['trim', 'strlen']); @@ -24,30 +22,35 @@ $policy->allowMethods(Nette\Security\User::class, ['isLoggedIn', 'isAllowed']); $policy->allowProperties(Nette\Database\Row::class, $policy::All); ``` -Non è incredibile? Si può controllare tutto a un livello molto basso. Se il template tenta di chiamare una funzione non autorizzata o di accedere a un metodo o a una proprietà non autorizzati, viene lanciata l'eccezione `Latte\SecurityViolationException`. +Non è fantastico? Puoi controllare tutto a un livello molto basso. Se il template tenta di chiamare una funzione non consentita o di accedere a un metodo o proprietà non consentiti, terminerà con un'eccezione `Latte\SecurityViolationException`. -Creare politiche da zero, quando tutto è vietato, potrebbe non essere conveniente, quindi si può partire da una base sicura: +Creare una policy da zero, dove tutto è vietato, potrebbe non essere comodo, quindi puoi iniziare da una base sicura: ```php $policy = Latte\Sandbox\SecurityPolicy::createSafePolicy(); ``` -Questo significa che tutti i tag standard sono consentiti, tranne `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget`. -Sono ammessi anche tutti i filtri standard, tranne `datastream`, `noescape` e `nocheck`. Infine, è consentito anche l'accesso ai metodi e alle proprietà dell'oggetto `$iterator`. +Una base sicura significa che sono consentiti tutti i tag standard tranne `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget`. Sono consentiti i filtri standard tranne `datastream`, `noescape` e `nocheck`. E infine è consentito l'accesso ai metodi e alle proprietà dell'oggetto `$iterator`. -Le regole si applicano al template che inseriamo con il nuovo tag [`{sandbox}` |tags#Including Templates] che è qualcosa di simile a , e . Che è qualcosa di simile a `{include}`, ma attiva la modalità sandbox e non passa alcuna variabile esterna: +Le regole si applicano al template che inseriamo con il tag [`{sandbox}` |tags#Inclusione di template]. Che è una sorta di analogo di `{include}`, ma attiva la modalità sicura e non passa alcuna variabile: ```latte {sandbox 'untrusted.latte'} ``` -In questo modo, il layout e le singole pagine possono usare tutti i tag e le variabili come prima, le restrizioni saranno applicate solo al template `untrusted.latte`. +Quindi il layout e le singole pagine possono utilizzare liberamente tutti i tag e le variabili, solo al template `untrusted.latte` verranno applicate le restrizioni. -Alcune violazioni, come l'uso di un tag o di un filtro proibito, vengono rilevate in fase di compilazione. Altre, come la chiamata di metodi non consentiti di un oggetto, a tempo di esecuzione. -Il template può contenere anche altri bug. Per evitare che venga lanciata un'eccezione dal template sandboxed, che interrompe l'intero rendering, è possibile definire un [proprio gestore di eccezioni |develop#exception handler], che, ad esempio, si limita a registrarla. +Alcune violazioni, come l'uso di un tag o filtro vietato, vengono rilevate in fase di compilazione. Altre, come la chiamata a metodi di oggetti non consentiti, solo in fase di esecuzione. Il template può anche contenere qualsiasi altro errore. Affinché un'eccezione dal template in sandbox non possa interrompere l'intero rendering, è possibile definire un [gestore di eccezioni personalizzato |develop#Handler delle Eccezioni], che ad esempio la registrerà. -Se si vuole attivare la modalità sandbox direttamente per tutti i template, è facile: +Se volessimo attivare la modalità sandbox direttamente per tutti i template, è facile: ```php $latte->setSandboxMode(); ``` + +Per assicurarti che l'utente non inserisca nella pagina codice PHP che sia sintatticamente corretto ma vietato e causi un PHP Compile Error, consigliamo di far [controllare i template dal linter PHP |develop#Controllo del Codice Generato]. Questa funzionalità si attiva con il metodo `Engine::enablePhpLint()`. Poiché per il controllo è necessario chiamare l'eseguibile PHP, passa il percorso ad esso come parametro: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); +``` diff --git a/latte/it/syntax.texy b/latte/it/syntax.texy index ae07510f7c..8f9e36997c 100644 --- a/latte/it/syntax.texy +++ b/latte/it/syntax.texy @@ -2,62 +2,60 @@ Sintassi ******** .[perex] -Syntax Latte è nata dalle esigenze pratiche dei web designer. Eravamo alla ricerca della sintassi più facile da usare, con la quale è possibile scrivere in modo elegante costrutti che altrimenti sarebbero una vera sfida. -Allo stesso tempo, tutte le espressioni sono scritte esattamente come in PHP, quindi non è necessario imparare un nuovo linguaggio. È sufficiente sfruttare al meglio ciò che già si conosce. +La sintassi di Latte è nata dalle esigenze pratiche dei web designer. Abbiamo cercato la sintassi più user-friendly, con la quale puoi scrivere elegantemente anche costrutti che altrimenti rappresenterebbero una vera sfida. Allo stesso tempo, tutte le espressioni sono scritte esattamente come in PHP, quindi non devi imparare un nuovo linguaggio. Semplicemente sfrutti ciò che sai già fare. -Di seguito è riportato un modello minimale che illustra alcuni elementi di base: tag, n:attributi, commenti e filtri. +Di seguito è riportato un template minimo che illustra alcuni elementi di base: tag, n:attributi, commenti e filtri. ```latte {* questo è un commento *} -
                                                                            {* n:if è n:atribut *} +
                                                                              {* n:if è un n:attributo *} {foreach $items as $item} {* tag che rappresenta il ciclo foreach *}
                                                                            • {$item|capitalize}
                                                                            • {* tag che stampa una variabile con un filtro *} {/foreach} {* fine del ciclo *}
                                                                            ``` -Diamo un'occhiata più da vicino a questi importanti elementi e a come possono aiutare a costruire un template incredibile. +Diamo un'occhiata più da vicino a questi importanti elementi e a come possono aiutarti a creare un template straordinario. -Tag .[#toc-tags] -================ +Tag +=== -Un modello contiene tag che controllano la logica del modello (per esempio, i cicli *foreach*) o le espressioni di output. Per entrambi viene usato un unico delimitatore `{ ... }`, in modo da non dover pensare a quale delimitatore usare in quale situazione, come avviene con altri sistemi. -Se il carattere `{` è seguito da una virgoletta o da uno spazio, Latte non lo considera l'inizio di un tag, per cui si possono usare costrutti JavaScript, JSON o regole CSS nei template senza problemi. +Il template contiene tag che controllano la logica del template (ad esempio i cicli *foreach*) o stampano espressioni. Per entrambi si utilizza un unico delimitatore `{ ... }`, quindi non devi pensare a quale delimitatore usare in quale situazione, come accade in altri sistemi. Se il carattere `{` è seguito da una virgoletta o da uno spazio, Latte non lo considera l'inizio di un tag, grazie al quale puoi utilizzare senza problemi anche costrutti JavaScript, JSON o regole CSS nei template. -Vedere la [panoramica di tutti i tag |tags]. Inoltre, è possibile creare [tag personalizzati |extending-latte#tags]. +Dai un'occhiata alla [panoramica di tutti i tag|tags]. Inoltre, puoi creare anche [tag personalizzati|custom tags]. -Latte capisce il PHP .[#toc-latte-understands-php] -================================================== +Latte capisce PHP +================= -È possibile utilizzare espressioni PHP che si conoscono bene all'interno dei tag: +All'interno dei tag puoi utilizzare le espressioni PHP che conosci bene: - variabili -- stringhe (compresi HEREDOC e NOWDOC), array, numeri, ecc. +- stringhe (inclusi HEREDOC e NOWDOC), array, numeri, ecc. - [operatori |https://www.php.net/manual/en/language.operators.php] -- chiamate a funzioni e metodi (che possono essere limitate dalla [sandbox |sandbox]) -- [corrispondenza |https://www.php.net/manual/en/control-structures.match.php] +- chiamate a funzioni e metodi (che possono essere limitate da [sandbox|sandbox]) +- [match |https://www.php.net/manual/en/control-structures.match.php] - [funzioni anonime |https://www.php.net/manual/en/functions.arrow.php] - [callback |https://www.php.net/manual/en/functions.first_class_callable_syntax.php] -- commenti multilinea `/* ... */` -- ecc. +- commenti multiriga `/* ... */` +- ecc… -Inoltre, Latte aggiunge diverse [estensioni |#Syntactic Sugar] alla sintassi di PHP. +Inoltre, Latte completa la sintassi PHP con alcune [piacevoli estensioni |#Zucchero sintattico]. -n:attributi .[#toc-n-attributes] -================================ +n:attributi +=========== -Ogni tag di coppia, come `{if} … {/if}`, che opera su un singolo elemento HTML può essere scritto in notazione [n:attributi |#n:attribute]. Per esempio, `{foreach}` nell'esempio precedente potrebbe essere scritto anche in questo modo: +Tutti i tag accoppiati, ad esempio `{if} … {/if}`, che operano su un singolo elemento HTML, possono essere riscritti sotto forma di n:attributi. In questo modo sarebbe possibile scrivere, ad esempio, anche `{foreach}` nell'esempio introduttivo: ```latte -
                                                                              +
                                                                              • {$item|capitalize}
                                                                              ``` -La funzionalità corrisponde quindi all'elemento HTML in cui è scritta: +La funzionalità si applica quindi all'elemento HTML in cui è posizionata: ```latte {var $items = ['I', '♥', 'Latte']} @@ -65,7 +63,7 @@ La funzionalità corrisponde quindi all'elemento HTML in cui è scritta:

                                                                              {$item}

                                                                              ``` -Stampe: +stampa: ```latte

                                                                              I

                                                                              @@ -73,7 +71,7 @@ Stampe:

                                                                              Latte

                                                                              ``` -Utilizzando il prefisso `inner-` possiamo alterare il comportamento in modo che la funzionalità si applichi solo al corpo dell'elemento: +Utilizzando il prefisso `inner-` possiamo modificare il comportamento in modo che si applichi solo alla parte interna dell'elemento: ```latte
                                                                              @@ -82,7 +80,7 @@ Utilizzando il prefisso `inner-` possiamo alterare il comportamento in modo che
                                                                              ``` -Stampe: +Verrà stampato: ```latte
                                                                              @@ -95,178 +93,184 @@ Stampe:
                                                                              ``` -Oppure utilizzando il prefisso `tag-` la funzionalità viene applicata solo ai tag HTML: +Oppure, utilizzando il prefisso `tag-`, applichiamo la funzionalità solo ai tag HTML stessi: ```latte -

                                                                              Title

                                                                              +

                                                                              Title

                                                                              ``` -A seconda del valore della variabile `$url` verrà stampato: +Che stamperà a seconda della variabile `$url`: ```latte -// when $url is empty +{* quando $url è vuoto *}

                                                                              Title

                                                                              -// when $url equals 'https://nette.org' +{* quando $url contiene 'https://nette.org' *}

                                                                              Title

                                                                              ``` -Tuttavia, gli attributi n:non sono solo una scorciatoia per i tag di coppia, ma esistono anche alcuni attributi n:puri, come ad esempio il migliore amico del codificatore [n:class |tags#n:class]. +Tuttavia, gli n:attributi non sono solo una scorciatoia per i tag accoppiati. Esistono anche n:attributi puri, come [n:href |application:creating-links#Nel template del presenter] o l'aiutante molto utile per il codificatore [n:class |tags#n:class]. -Filtri .[#toc-filters] -====================== +Filtri +====== -Vedere il riepilogo dei [filtri standard |filters]. +Dai un'occhiata alla panoramica dei [filtri standard |filters]. -Latte consente di chiamare i filtri utilizzando la notazione del segno di pipe (è consentito lo spazio precedente): +I filtri vengono scritti dopo una barra verticale (può esserci uno spazio prima): ```latte

                                                                              {$heading|upper}

                                                                              ``` -I filtri possono essere concatenati, in tal caso si applicano in ordine da sinistra a destra: +I filtri possono essere concatenati e vengono applicati nell'ordine da sinistra a destra: ```latte

                                                                              {$heading|lower|capitalize}

                                                                              ``` -I parametri vengono inseriti dopo il nome del filtro, separati da due punti o da una virgola: +I parametri vengono specificati dopo il nome del filtro, separati da due punti o virgole: ```latte

                                                                              {$heading|truncate:20,''}

                                                                              ``` -I filtri possono essere applicati alle espressioni: +I filtri possono essere applicati anche a un'espressione: ```latte {var $name = ($title|upper) . ($subtitle|lower)} ``` -Su blocco: +A un blocco: ```latte

                                                                              {block |lower}{$heading}{/block}

                                                                              ``` -O direttamente sul valore (in combinazione con [`{=expr}` | https://latte.nette.org/it/tags#printing] ): +Oppure direttamente al valore (in combinazione con il tag [`{=expr}` |tags#Stampa]): ```latte

                                                                              {=' Hello world '|trim}

                                                                              ``` -Commenti .[#toc-comments] -========================= +Tag HTML dinamici .{data-version:3.0.9} +======================================= -I commenti sono scritti in questo modo e non vengono inseriti nell'output: +Latte supporta i tag HTML dinamici, che sono utili quando hai bisogno di flessibilità nei nomi dei tag: ```latte -{Questo è un commento in Latte.} +Heading ``` -I commenti PHP funzionano all'interno dei tag: +Il codice sopra può ad esempio generare `

                                                                              Heading

                                                                              ` o `

                                                                              Heading

                                                                              ` a seconda del valore della variabile `$level`. I tag HTML dinamici in Latte devono essere sempre accoppiati. La loro alternativa è [n:tag |tags#n:tag]. + +Poiché Latte è un sistema di templating sicuro, controlla che il nome del tag risultante sia valido e non contenga valori indesiderati o dannosi. Inoltre, garantisce che il nome del tag di chiusura sia sempre lo stesso del nome del tag di apertura. + + +Commenti +======== + +I commenti vengono scritti in questo modo e non vengono inclusi nell'output: + +```latte +{* questo è un commento in Latte *} +``` + +All'interno dei tag funzionano i commenti PHP: ```latte {include 'file.info', /* value: 123 */} ``` -Zucchero sintattico .[#toc-syntactic-sugar] -=========================================== +Zucchero sintattico +=================== -Stringhe senza virgolette .[#toc-strings-without-quotation-marks] ------------------------------------------------------------------ +Stringhe senza virgolette +------------------------- -Le virgolette possono essere omesse per le stringhe semplici: +Per le stringhe semplici è possibile omettere le virgolette: ```latte -as in PHP: {var $arr = ['hello', 'btn--default', '€']} +come in PHP: {var $arr = ['hello', 'btn--default', '€']} -abbreviated: {var $arr = [hello, btn--default, €]} +abbreviato: {var $arr = [hello, btn--default, €]} ``` -Le stringhe semplici sono quelle composte esclusivamente da lettere, cifre, trattini bassi, trattini e punti. Non devono iniziare con una cifra e non devono iniziare o terminare con un trattino. -Non deve essere composta solo da lettere maiuscole e trattini bassi, perché in tal caso viene considerata una costante (ad esempio `PHP_VERSION`). -E non deve collidere con le parole chiave `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor`. +Le stringhe semplici sono quelle composte esclusivamente da lettere, numeri, trattini bassi, trattini e punti. Non devono iniziare con un numero e non devono iniziare o finire con un trattino. Non devono essere composte solo da lettere maiuscole e trattini bassi, perché in tal caso vengono considerate costanti (es. `PHP_VERSION`). E non devono entrare in conflitto con le parole chiave: `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor`. -Operatore ternario breve .[#toc-short-ternary-operator] -------------------------------------------------------- +Costanti +-------- -Se il terzo valore dell'operatore ternario è vuoto, può essere omesso: +Poiché è possibile omettere le virgolette per le stringhe semplici, consigliamo di scrivere le costanti globali con una barra all'inizio per distinguerle: ```latte -as in PHP: {$stock ? 'In stock' : ''} - -abbreviated: {$stock ? 'In stock'} +{if \PROJECT_ID === 1} ... {/if} ``` +Questa scrittura è completamente valida in PHP stesso, la barra indica che la costante si trova nel namespace globale. + -Notazione moderna della chiave nell'array .[#toc-modern-key-notation-in-the-array] ----------------------------------------------------------------------------------- +Operatore ternario abbreviato +----------------------------- -Le chiavi dell'array possono essere scritte in modo simile ai parametri denominati quando si chiamano le funzioni: +Se il terzo valore dell'operatore ternario è vuoto, può essere omesso: ```latte -as in PHP: {var $arr = ['one' => 'item 1', 'two' => 'item 2']} +come in PHP: {$stock ? 'Disponibile' : ''} -modern: {var $arr = [one: 'item 1', two: 'item 2']} +abbreviato: {$stock ? 'Disponibile'} ``` -Filtri .[#toc-filters] ----------------------- +Notazione moderna delle chiavi negli array +------------------------------------------ -I filtri possono essere utilizzati per qualsiasi espressione, basta racchiudere il tutto tra parentesi: +Le chiavi negli array possono essere scritte in modo simile ai parametri nominati nella chiamata di funzioni: ```latte -{var $content = ($text|truncate: 30|upper)} +come in PHP: {var $arr = ['one' => 'item 1', 'two' => 'item 2']} + +moderno: {var $arr = [one: 'item 1', two: 'item 2']} ``` -Operatore `in` .[#toc-operator-in] ----------------------------------- +Filtri +------ -L'operatore `in` può essere utilizzato per sostituire la funzione `in_array()`. Il confronto è sempre rigoroso: +I filtri possono essere utilizzati per qualsiasi espressione, basta racchiudere l'intero costrutto tra parentesi: ```latte -{* come in_array($item, $items, true) *} -{if $item in $items} - ... -{/if} +{var $content = ($text|truncate: 30|upper)} ``` -.{data-version:2.9} -Concatenamento opzionale con l'operatore Undefined-Safe .[#toc-optional-chaining-with-undefined-safe-operator] --------------------------------------------------------------------------------------------------------------- +Operatore `in` +-------------- -L'operatore undefined-safe `??->` è simile all'operatore nullsafe `?->`, ma non solleva un errore se una variabile, una proprietà o un indice non esistono affatto. +L'operatore `in` può sostituire la funzione `in_array()`. Il confronto è sempre rigoroso: ```latte -{$order??->id} +{* analogo a in_array($item, $items, true) *} +{if $item in $items} + ... +{/if} ``` -Questo è un modo per dire che quando `$order` è definito e non è nullo, `$order->id` verrà calcolato, ma quando `$order` è nullo o non esiste, interrompiamo quello che stiamo facendo e restituiamo semplicemente null. - -```latte -{$user??->address??->street} -// roughly means isset($user) && isset($user->address) ? $user->address->street : null -``` +Finestra storica +---------------- -Una finestra nella storia .[#toc-a-window-into-history] -------------------------------------------------------- +Latte ha introdotto nel corso della sua storia una serie di zuccheri sintattici che sono apparsi in PHP stesso dopo alcuni anni. Ad esempio, in Latte era possibile scrivere array come `[1, 2, 3]` invece di `array(1, 2, 3)` o utilizzare l'operatore nullsafe `$obj?->foo` molto prima che fosse possibile in PHP stesso. Latte ha anche introdotto l'operatore per l'espansione dell'array `(expand) $arr`, che è l'equivalente dell'odierno operatore `...$arr` di PHP. -Nel corso della sua storia, Latte ha inventato una serie di caramelle sintattiche che sono apparse in PHP qualche anno dopo. Per esempio, in Latte era possibile scrivere gli array come `[1, 2, 3]` invece di `array(1, 2, 3)` o usare l'operatore nullsafe `$obj?->foo` molto prima che fosse possibile in PHP. Latte ha anche introdotto l'operatore di espansione degli array `(expand) $arr`, che è l'equivalente dell'odierno operatore `...$arr` di PHP. +L'operatore undefined-safe `??->`, che è un analogo dell'operatore nullsafe `?->`, ma non genera un errore se la variabile non esiste, è nato per ragioni storiche e oggi consigliamo di utilizzare l'operatore PHP standard `?->`. -Limitazioni di PHP in Latte .[#toc-php-limitations-in-latte] -============================================================ +Limitazioni di PHP in Latte +=========================== -In Latte si possono scrivere solo espressioni PHP. Cioè, non si possono dichiarare classi o usare [strutture di controllo |https://www.php.net/manual/en/language.control-structures.php], come `if`, `foreach`, `switch`, `return`, `try`, `throw` e altre, al posto delle quali Latte offre i suoi [tag |tags]. -Non si possono nemmeno usare [attributi |https://www.php.net/manual/en/language.attributes.php], [backtick |https://www.php.net/manual/en/language.operators.execution.php] o [costanti magiche |https://www.php.net/manual/en/language.constants.magic.php], perché non avrebbe senso. -Non si possono nemmeno usare `unset`, `echo`, `include`, `require`, `exit`, `eval`, perché non sono funzioni, ma costrutti speciali del linguaggio PHP e quindi non espressioni. +In Latte è possibile scrivere solo espressioni PHP. Quindi non è possibile utilizzare istruzioni terminate da punto e virgola. Non è possibile dichiarare classi o utilizzare [strutture di controllo |https://www.php.net/manual/en/language.control-structures.php], es. `if`, `foreach`, `switch`, `return`, `try`, `throw` e altre, al posto delle quali Latte offre i suoi [tag|tags]. Inoltre, non è possibile utilizzare [attributi |https://www.php.net/manual/en/language.attributes.php], [backticks |https://www.php.net/manual/en/language.operators.execution.php] o alcune [costanti magiche |https://www.php.net/manual/en/language.constants.magic.php]. Non è possibile utilizzare nemmeno `unset`, `echo`, `include`, `require`, `exit`, `eval`, perché non sono funzioni, ma costrutti linguistici speciali di PHP, e non sono quindi espressioni. I commenti sono supportati solo quelli multiriga `/* ... */`. -Tuttavia, è possibile aggirare queste limitazioni attivando l'estensione [RawPhpExtension |develop#RawPhpExtension], che consente di utilizzare qualsiasi codice PHP nel tag `{php ...}` sotto la responsabilità dell'autore del template. +Queste limitazioni possono tuttavia essere aggirate attivando l'estensione [RawPhpExtension |develop#RawPhpExtension], grazie alla quale è possibile utilizzare nel tag `{php ...}` qualsiasi codice PHP sotto la responsabilità dell'autore del template. diff --git a/latte/it/tags.texy b/latte/it/tags.texy index c9ccbf1521..8e59024a38 100644 --- a/latte/it/tags.texy +++ b/latte/it/tags.texy @@ -1,168 +1,174 @@ -Etichette per il latte -********************** +Tag Latte +********* .[perex] -Riepilogo e descrizione di tutti i tag incorporati di Latte. +Panoramica e descrizione di tutti i tag del sistema di templating Latte, disponibili di default. .[table-latte-tags language-latte] |## Stampa -| `{$var}`, `{...}` o `{=...}` | [stampa una variabile o un'espressione con escape |#printing] -| `{$var\|filter}` | [stampa con filtri |#filters] -| `{l}` o `{r}` | stampa il carattere `{` or `}` +| `{$var}`, `{...}` o `{=...}` | [stampa la variabile o l'espressione con escaping |#Stampa] +| `{$var\|filter}` | [stampa utilizzando i filtri |#Filtri] +| `{l}` o `{r}` | stampa il carattere `{` o `}` .[table-latte-tags language-latte] |## Condizioni -| `{if}`... `{elseif}`... `{else}`... `{/if}` | [condizione if |#if-elseif-else] -| `{ifset}`... `{elseifset}`... `{/ifset}` | [condizione ifset |#ifset-elseifset] -| `{ifchanged}`... `{/ifchanged}` | [verifica se c'è stata una modifica |#ifchanged] -| `{switch}` `{case}` `{default}` `{/switch}` | [condizione switch |#switch-case-default] +| `{if}` … `{elseif}` … `{else}` … `{/if}` | [condizione if |#if elseif else] +| `{ifset}` … `{elseifset}` … `{/ifset}` | [condizione ifset |#ifset elseifset] +| `{ifchanged}` … `{/ifchanged}` | [test se c'è stato un cambiamento |#ifchanged] +| `{switch}` `{case}` `{default}` `{/switch}` | [condizione switch |#switch case default] +| `n:else` | [contenuto alternativo per le condizioni |#n:else] .[table-latte-tags language-latte] |## Cicli -| `{foreach}`... `{/foreach}` | [foreach |#foreach] -| `{for}`... `{/for}` | [for |#for] -| `{while}`... `{/while}` | [while |#while] -| `{continueIf $cond}` | [continua all'iterazione successiva |#continueif-skipif-breakif] -| `{skipIf $cond}` | [salta l'iterazione corrente del ciclo |#continueif-skipif-breakif] -| `{breakIf $cond}` | [interrompe il ciclo |#continueif-skipif-breakif] -| `{exitIf $cond}` | [uscita anticipata |#exitif] -| `{first}`... `{/first}` | [è la prima iterazione? |#first-last-sep] -| `{last}`... `{/last}` | [è l'ultima iterazione? |#first-last-sep] -| `{sep}`... `{/sep}` | [seguirà la prossima iterazione? |#first-last-sep] -| `{iterateWhile}`... `{/iterateWhile}` | [foreach strutturato |#iterateWhile] -| `$iterator` | [variabile speciale all'interno del ciclo foreach |#$iterator] +| `{foreach}` … `{/foreach}` | [#foreach] +| `{for}` … `{/for}` | [#for] +| `{while}` … `{/while}` | [#while] +| `{continueIf $cond}` | [continua con la prossima iterazione |#continueIf skipIf breakIf] +| `{skipIf $cond}` | [salta l'iterazione |#continueIf skipIf breakIf] +| `{breakIf $cond}` | [interrompe il ciclo |#continueIf skipIf breakIf] +| `{exitIf $cond}` | [terminazione anticipata |#exitIf] +| `{first}` … `{/first}` | [è la prima iterazione? |#first last sep] +| `{last}` … `{/last}` | [è l'ultima iterazione? |#first last sep] +| `{sep}` … `{/sep}` | [seguirà un'altra iterazione? |#first last sep] +| `{iterateWhile}` … `{/iterateWhile}` | [foreach strutturato |#iterateWhile] +| `$iterator` | [variabile speciale all'interno di foreach |#iterator] .[table-latte-tags language-latte] -|## Inclusione di altri modelli -| `{include 'file.latte'}` | [include un modello da un altro file |#include] -| `{sandbox 'file.latte'}` | [include un modello in modalità sandbox |#sandbox] +|## Inclusione di altri template +| `{include 'file.latte'}` | [carica il template da un altro file |#include] +| `{sandbox 'file.latte'}` | [carica il template in modalità sandbox |#sandbox] .[table-latte-tags language-latte] |## Blocchi, layout, ereditarietà dei template -| `{block}` | [blocco anonimo |#block] -| `{block blockname}` | [definizione di blocco |template-inheritance#blocks] -| `{define blockname}` | [definizione di blocco per uso futuro |template-inheritance#definitions] -| `{include blockname}` | [stampa il blocco |template-inheritance#printing-blocks] -| `{include blockname from 'file.latte'}` | [stampa un blocco da file |template-inheritance#printing-blocks] -| `{import 'file.latte'}` | [carica i blocchi da un altro modello |template-inheritance#horizontal-reuse] -| `{layout 'file.latte'}` / `{extends}` | [specifica un file di layout |template-inheritance#layout-inheritance] -| `{embed}`... `{/embed}` | [carica il modello o il blocco e consente di sovrascrivere i blocchi |template-inheritance#unit-inheritance] -| `{ifset blockname}`... `{/ifset}` | [condizione se il blocco è definito |template-inheritance#checking-block-existence] +| `{block}` | [blocco anonimo |#block] +| `{block blockname}` | [definisce un blocco |template-inheritance#Blocchi block] +| `{define blockname}` | [definisce un blocco per un uso successivo |template-inheritance#Definizioni define] +| `{include blockname}` | [Rendering del blocco |template-inheritance#Rendering dei blocchi include] +| `{include blockname from 'file.latte'}` | [renderizza un blocco da un file |template-inheritance#Rendering dei blocchi include] +| `{import 'file.latte'}` | [carica i blocchi da un template |template-inheritance#Riutilizzo orizzontale import] +| `{layout 'file.latte'}` / `{extends}` | [specifica il file con il layout |template-inheritance#Ereditarietà del layout layout] +| `{embed}` … `{/embed}` | [carica un template o un blocco e permette di sovrascrivere i blocchi |template-inheritance#Ereditarietà unitaria embed] +| `{ifset blockname}` … `{/ifset}` | [condizione, se esiste un blocco |template-inheritance#Controllo dell esistenza dei blocchi ifset] .[table-latte-tags language-latte] |## Gestione delle eccezioni -| `{try}`... `{else}`... `{/try}` | [cattura le eccezioni |#try] -| `{rollback}` | [scarta il blocco try |#rollback] +| `{try}` … `{else}` … `{/try}` | [cattura delle eccezioni |#try] +| `{rollback}` | [scarta il blocco try |#rollback] .[table-latte-tags language-latte] |## Variabili -| `{var $foo = value}` | [creazione di una variabile |#var-default] -| `{default $foo = value}` | [valore predefinito quando la variabile non è dichiarata |#var-default] -| `{parameters}` | [dichiara variabili, tipizza un valore predefinito |#parameters] -| `{capture}`... `{/capture}` | [cattura una sezione in una variabile |#capture] +| `{var $foo = value}` | [crea una variabile |#var default] +| `{default $foo = value}` | [crea una variabile, se non esiste |#var default] +| `{parameters}` | [dichiara variabili, tipi e valori predefiniti |#parameters] +| `{capture}` … `{/capture}` | [cattura un blocco in una variabile |#capture] .[table-latte-tags language-latte] |## Tipi -| `{varType}` | [dichiara il tipo di variabile |type-system#varType] -| `{varPrint}` | [suggerisce tipi di variabili |type-system#varPrint] -| `{templateType}` | [dichiara i tipi di variabili usando la classe |type-system#templateType] -| `{templatePrint}` | [genera una classe con proprietà |type-system#templatePrint] +| `{varType}` | [dichiara il tipo di una variabile |type-system#varType] +| `{varPrint}` | [suggerisce i tipi delle variabili |type-system#varPrint] +| `{templateType}` | [dichiara i tipi delle variabili secondo una classe |type-system#templateType] +| `{templatePrint}` | [suggerisce una classe con i tipi delle variabili |type-system#templatePrint] .[table-latte-tags language-latte] -|## Traduzione -| `{_string}` | [stampa la traduzione |#Translation] -| `{translate}`... `{/translate}` | [traduce il contenuto |#Translation] +|## Traduzioni +| `{_...}` | [stampa la traduzione |#Traduzioni] +| `{translate}` … `{/translate}` | [traduce il contenuto |#Traduzioni] .[table-latte-tags language-latte] |## Altri -| `{contentType}` | [cambia la modalità di escape e invia l'intestazione HTTP |#contenttype] -| `{debugbreak}` | [imposta un punto di interruzione nel codice |#debugbreak] -| `{do}` | [valuta un'espressione senza stamparla |#do] -| `{dump}` | [scarica le variabili nella barra Tracy |#dump] -| `{spaceless}`... `{/spaceless}` | [rimuove gli spazi bianchi non necessari |#spaceless] -| `{syntax}` | [cambia la sintassi in fase di esecuzione |#syntax] -| `{trace}` | [mostra la traccia dello stack |#trace] +| `{contentType}` | [cambia l'escaping e invia l'header HTTP |#contentType] +| `{debugbreak}` | [inserisce un breakpoint nel codice |#debugbreak] +| `{do}` | [esegue il codice, ma non stampa nulla |#do] +| `{dump}` | [esegue il dump delle variabili nella Tracy Bar |#dump] +| `{php}` | [esegue qualsiasi codice PHP |#php] +| `{spaceless}` … `{/spaceless}` | [rimuove gli spazi superflui |#spaceless] +| `{syntax}` | [cambio di sintassi in fase di esecuzione |#syntax] +| `{trace}` | [visualizza lo stack trace |#trace] .[table-latte-tags language-latte] -|## Aiutanti dei tag HTML -| `n:class` | [attributo di classe intelligente |#n:class] -| `n:attr` | [attributi HTML intelligenti |#n:attr] -| `n:tag` | [nome dinamico dell'elemento HTML |#n:tag] -| `n:ifcontent` | [Omettere il tag HTML vuoto |#n:ifcontent] +|## Aiutanti per il codificatore HTML +| `n:class` | [scrittura dinamica dell'attributo HTML class |#n:class] +| `n:attr` | [scrittura dinamica di qualsiasi attributo HTML |#n:attr] +| `n:tag` | [scrittura dinamica del nome dell'elemento HTML |#n:tag] +| `n:ifcontent` | [omette il tag HTML vuoto |#n:ifcontent] .[table-latte-tags language-latte] |## Disponibile solo in Nette Framework -| `n:href` | [collegamento in elementi HTML `` |application:creating-links#In the Presenter Template] -| `{link}` | [stampa un link |application:creating-links#In the Presenter Template] -| `{plink}` | [stampa un link a un presentatore |application:creating-links#In the Presenter Template] -| `{control}` | [stampa un componente |application:components#Rendering] -| `{snippet}`... `{/snippet}` | [uno snippet di modello che può essere inviato tramite AJAX |application:ajax#tag-snippet] -| `{snippetArea}` | snippet busta -| `{cache}`... `{/cache}` | [memorizza nella cache una sezione del template |caching:#caching-in-latte] +| `n:href` | [link utilizzato negli elementi HTML `` |application:creating-links#Nel template del presenter] +| `{link}` | [stampa un link |application:creating-links#Nel template del presenter] +| `{plink}` | [stampa un link a un presenter |application:creating-links#Nel template del presenter] +| `{control}` | [renderizza un componente |application:components#Rendering] +| `{snippet}` … `{/snippet}` | [snippet che può essere inviato tramite AJAX |application:ajax#Snippet in Latte] +| `{snippetArea}` | [wrapper per snippet |application:ajax#Aree di snippet] +| `{cache}` … `{/cache}` | [mette in cache una parte del template |caching:#Caching in Latte] .[table-latte-tags language-latte] |## Disponibile solo con Nette Forms -| `{form}`... `{/form}` | [stampa un elemento del modulo |forms:rendering#form] -| `{label}`... `{/label}` | [stampa un'etichetta di input del modulo |forms:rendering#label-input] -| `{input}` | [stampa un elemento di input del modulo |forms:rendering#label-input] -| `{inputError}` | [stampa il messaggio di errore per l'elemento di input del modulo |forms:rendering#inputError] -| `n:name` | [attiva un elemento di input HTML |forms:rendering#n:name] -| `{formPrint}` | [genera il blueprint del modulo Latte |forms:rendering#formPrint] -| `{formPrintClass}` | [stampa la classe PHP per i dati del modulo |forms:in-presenter#mapping-to-classes] -| `{formContext}`... `{/formContext}` | [rendering parziale del modulo |forms:rendering#special-cases] +| `{form}` … `{/form}` | [renderizza i tag del form |forms:rendering#form] +| `{label}` … `{/label}` | [renderizza l'etichetta di un elemento del form |forms:rendering#label input] +| `{input}` | [renderizza un elemento del form |forms:rendering#label input] +| `{inputError}` | [stampa il messaggio di errore di un elemento del form |forms:rendering#inputError] +| `n:name` | [anima un elemento del form |forms:rendering#n:name] +| `{formContainer}` … `{/formContainer}` | [rendering di un container del form |forms:rendering#Casi speciali] + +.[table-latte-tags language-latte] +|## Disponibile solo con le risorse Nette +| `{asset}` | [rende un asset come elemento HTML o URL |assets:#asset] +| `{preload}` | [genera suggerimenti di precaricamento per ottimizzare le prestazioni |assets:#preload] +| `n:asset` | [aggiunge attributi di asset agli elementi HTML |assets:#n:asset] -Stampa .[#toc-printing] -======================= +Stampa +====== `{$var}` `{...}` `{=...}` ------------------------- -Latte utilizza il tag `{=...}` per stampare qualsiasi espressione in uscita. Se l'espressione inizia con una variabile o una chiamata di funzione, non è necessario scrivere il segno di uguale. In pratica, ciò significa che non è quasi mai necessario scriverlo: +In Latte, si utilizza il tag `{=...}` per stampare qualsiasi espressione nell'output. Latte tiene alla vostra comodità, quindi se l'espressione inizia con una variabile o una chiamata di funzione, non è necessario scrivere il segno di uguale. Il che in pratica significa che non è quasi mai necessario scriverlo: ```latte -Name: {$name} {$surname}
                                                                              -Age: {date('Y') - $birth}
                                                                              +Nome: {$name} {$surname}
                                                                              +Età: {date('Y') - $birth}
                                                                              ``` -È possibile scrivere qualsiasi cosa si conosca di PHP come espressione. Non è necessario imparare un nuovo linguaggio. Per esempio: +Come espressione potete scrivere qualsiasi cosa conosciate da PHP. Semplicemente non dovete imparare un nuovo linguaggio. Ad esempio: ```latte {='0' . ($num ?? $num * 3) . ', ' . PHP_VERSION} ``` -Non cercate alcun significato nell'esempio precedente, ma se ne trovate uno, scriveteci :-) +Per favore, non cercate alcun significato nell'esempio precedente, ma se ne trovate uno, scriveteci :-) -Uscita in fuga .[#toc-escaping-output] --------------------------------------- +Escaping dell'output +-------------------- -Qual è il compito più importante di un sistema di template? Evitare le falle di sicurezza. Ed è proprio questo che Latte fa ogni volta che si stampa qualcosa in output. Esegue automaticamente l'escape di tutto: +Qual è il compito più importante di un sistema di templating? Prevenire le falle di sicurezza. Ed è esattamente quello che fa Latte ogni volta che stampate qualcosa. Esegue automaticamente l'escaping: ```latte -

                                                                              {='one < two'}

                                                                              {* prints: '

                                                                              one < two

                                                                              ' *} +

                                                                              {='one < two'}

                                                                              {* stampa: '

                                                                              one < two

                                                                              ' *} ``` -Per essere precisi, Latte utilizza l'escape sensibile al contesto, una caratteristica talmente importante e unica che [le abbiamo dedicato un capitolo a parte |safety-first#context-aware-escaping]. +Per essere precisi, Latte utilizza l'escaping sensibile al contesto, una caratteristica così importante e unica che le abbiamo dedicato un [capitolo separato |safety-first#Escaping sensibile al contesto]. -E se stampate contenuti codificati in HTML da una fonte affidabile? Allora potete facilmente disattivare l'escape: +E cosa succede se stampate contenuto codificato in HTML da una fonte attendibile? Allora potete facilmente disabilitare l'escaping: ```latte {$trustedHtmlString|noescape} ``` .[warning] -L'uso improprio del filtro `noescape` può portare a una vulnerabilità XSS! Non usatelo mai a meno che non siate **assolutamente sicuri** di quello che state facendo e che la stringa che state stampando provenga da una fonte affidabile. +L'uso improprio del filtro `noescape` può portare a una vulnerabilità XSS! Non utilizzatelo mai a meno che non siate **assolutamente sicuri** di quello che state facendo e che la stringa stampata provenga da una fonte attendibile. -Stampa in JavaScript .[#toc-printing-in-javascript] ---------------------------------------------------- +Stampa in JavaScript +-------------------- -Grazie all'escape sensibile al contesto, è molto facile stampare le variabili all'interno di JavaScript e Latte le metterà correttamente in escape. +Grazie all'escaping sensibile al contesto, è incredibilmente facile stampare variabili all'interno di JavaScript, e Latte si occupa del corretto escaping. -La variabile non deve essere necessariamente una stringa, è supportato qualsiasi tipo di dato, che viene poi codificato come JSON: +La variabile non deve essere solo una stringa, è supportato qualsiasi tipo di dato, che viene poi codificato come JSON: ```latte {var $foo = ['hello', true, 1]} @@ -179,7 +185,7 @@ Genera: ``` -Questo è anche il motivo per cui **non mettere le variabili tra virgolette**: Latte le aggiunge intorno alle stringhe. E se si vuole inserire una variabile stringa in un'altra stringa, basta concatenarle: +Questo è anche il motivo per cui **non si scrivono virgolette** attorno alla variabile: Latte le aggiunge automaticamente per le stringhe. E se voleste inserire una variabile stringa in un'altra stringa, semplicemente concatenatele: ```latte ``` -Filtri .[#toc-filters] ----------------------- +Filtri +------ -L'espressione stampata può essere modificata [da filtri |syntax#filters]. Ad esempio, questo esempio converte la stringa in maiuscolo e la accorcia a un massimo di 30 caratteri: +L'espressione stampata può essere modificata da un [filtro |syntax#Filtri]. Ad esempio, così convertiamo una stringa in maiuscolo e la accorciamo a un massimo di 30 caratteri: ```latte {$string|upper|truncate:30} ``` -È inoltre possibile applicare filtri a parti di un'espressione come segue: +Potete applicare i filtri anche a parti parziali dell'espressione in questo modo: ```latte {$left . ($middle|upper) . $right} ``` -Condizioni .[#toc-conditions] -============================= +Condizioni +========== `{if}` `{elseif}` `{else}` -------------------------- -Le condizioni si comportano allo stesso modo delle loro controparti PHP. È possibile utilizzare le stesse espressioni conosciute in PHP, senza dover imparare un nuovo linguaggio. +Le condizioni si comportano allo stesso modo delle loro controparti in PHP. Potete utilizzare al loro interno le stesse espressioni che conoscete da PHP, non dovete imparare un nuovo linguaggio. ```latte {if $product->inStock > Stock::Minimum} - In stock + Disponibile {elseif $product->isOnWay()} - On the way + In arrivo {else} - Not available + Non disponibile {/if} ``` -Come qualsiasi tag di coppia, una coppia di `{if} ... {/ if}` può essere scritta come [n:attributo |syntax#n:attributes], ad esempio: +Come ogni tag accoppiato, anche la coppia `{if} ... {/if}` può essere scritta sotto forma di [n:attributo |syntax#n:attributi], ad esempio: ```latte -

                                                                              In stock {$count} items

                                                                              +

                                                                              Disponibili {$count} pezzi

                                                                              ``` -Sapete che è possibile aggiungere il prefisso `tag-` a n:attributi? In questo modo la condizione riguarderà solo i tag HTML e il contenuto tra di essi verrà sempre stampato: +Sapete che agli n:attributi potete aggiungere il prefisso `tag-`? Allora la condizione si applicherà solo alla stampa dei tag HTML e il contenuto tra di essi verrà sempre stampato: ```latte -
                                                                              Ciao +Hello -{* prints 'Hello' when $clickable is falsey *} -{* prints 'Hello' when $clickable is truthy *} +{* stampa 'Hello' quando $clickable è falso *} +{* stampa 'Hello' quando $clickable è vero *} ``` -Bello. +Fantastico. + + +`n:else` .{data-version:3.0.11} +------------------------------- + +Se scrivete la condizione `{if} ... {/if}` sotto forma di [n:attributo |syntax#n:attributi], avete la possibilità di indicare anche un ramo alternativo usando `n:else`: + +```latte +Disponibili {$count} pezzi + +non disponibile +``` + +L'attributo `n:else` può essere utilizzato anche in coppia con [`n:ifset` |#ifset elseifset], [`n:foreach` |#foreach], [`n:try` |#try], [#`n:ifcontent`] e [`n:ifchanged` |#ifchanged]. `{/if $cond}` ------------- -Potreste essere sorpresi dal fatto che l'espressione della condizione `{if}` può essere specificata anche nel tag finale. Questo è utile in situazioni in cui non si conosce ancora il valore della condizione quando il tag viene aperto. Chiamiamola decisione differita. +Potreste essere sorpresi che l'espressione nella condizione `{if}` possa essere specificata anche nel tag di chiusura. Questo è utile in situazioni in cui, all'apertura della condizione, non conosciamo ancora il suo valore. Chiamiamola decisione posticipata. -Ad esempio, iniziamo a elencare una tabella con i record del database e solo dopo aver completato il report ci rendiamo conto che non c'era nessun record nel database. Quindi inseriamo la condizione nel tag finale `{/if}` e se non c'è nessun record, non verrà stampato nulla: +Ad esempio, iniziamo a stampare una tabella con record da un database e solo dopo aver completato la stampa ci rendiamo conto che non c'era alcun record nel database. Quindi mettiamo una condizione su questo nel tag di chiusura `{/if}` e se non ci sono record, non verrà stampato nulla: ```latte {if} -

                                                                              Printing rows from the database

                                                                              +

                                                                              Elenco righe dal database

                                                                              {foreach $resultSet as $row} @@ -266,28 +286,28 @@ Ad esempio, iniziamo a elencare una tabella con i record del database e solo dop Comodo, vero? -Si può usare anche `{else}` nella condizione differita, ma non `{elseif}`. +Nella condizione posticipata si può usare anche `{else}`, ma non `{elseif}`. `{ifset}` `{elseifset}` ----------------------- .[note] -Vedi anche [`{ifset block}` |template-inheritance#checking-block-existence] +Vedi anche [`{ifset block}` |template-inheritance#Controllo dell esistenza dei blocchi ifset] -Utilizzare la condizione `{ifset $var}` per determinare se una variabile (o più variabili) esiste e ha un valore non nullo. In realtà è la stessa cosa di `if (isset($var))` in PHP. Come ogni tag di coppia, può essere scritto nella forma [n:attribute |syntax#n:attributes], quindi lo mostriamo in un esempio: +Usando la condizione `{ifset $var}` verifichiamo se una variabile (o più variabili) esiste e ha un valore non *null*. In pratica, è la stessa cosa di `if (isset($var))` in PHP. Come ogni tag accoppiato, può essere scritto anche sotto forma di [n:attributo |syntax#n:attributi], quindi vediamolo come esempio: ```latte - + ``` -`{ifchanged}` .{data-version:2.9} ---------------------------------- +`{ifchanged}` +------------- -`{ifchanged}` controlla se il valore di una variabile è cambiato dall'ultima iterazione del ciclo (foreach, for o while). +`{ifchanged}` controlla se il valore di una variabile è cambiato dall'ultima iterazione nel ciclo (foreach, for o while). -Se si specificano una o più variabili nel tag, questo controlla se una di esse è cambiata e stampa il contenuto di conseguenza. Ad esempio, l'esempio seguente stampa la prima lettera di un nome come titolo ogni volta che cambia nell'elenco dei nomi: +Se nel tag specifichiamo una o più variabili, controllerà se qualcuna di esse è cambiata e stamperà il contenuto di conseguenza. Ad esempio, l'esempio seguente stamperà la prima lettera del nome come titolo ogni volta che cambia durante la stampa dei nomi: ```latte {foreach ($names|sort) as $name} @@ -297,7 +317,7 @@ Se si specificano una o più variabili nel tag, questo controlla se una di esse {/foreach} ``` -Tuttavia, se non viene fornito alcun argomento, il contenuto reso viene controllato rispetto al suo stato precedente. Ciò significa che nell'esempio precedente si può tranquillamente omettere l'argomento nel tag. Naturalmente, possiamo anche usare [n:attribute |syntax#n:attributes]: +Tuttavia, se non specifichiamo alcun argomento, verrà controllato il contenuto renderizzato rispetto al suo stato precedente. Ciò significa che nell'esempio precedente possiamo tranquillamente omettere l'argomento nel tag. E ovviamente possiamo anche usare un [n:attributo |syntax#n:attributi]: ```latte {foreach ($names|sort) as $name} @@ -307,49 +327,49 @@ Tuttavia, se non viene fornito alcun argomento, il contenuto reso viene controll {/foreach} ``` -Si può anche includere una clausola `{else}` all'interno del tag `{ifchanged}`. +All'interno di `{ifchanged}` è possibile specificare anche la clausola `{else}`. `{switch}` `{case}` `{default}` ------------------------------- -Confronta il valore con più opzioni. È simile alla struttura `switch`, conosciuta in PHP. Tuttavia, Latte la migliora: +Confronta un valore con più opzioni. È un analogo dell'istruzione condizionale `switch` che conoscete da PHP. Tuttavia, Latte la migliora: - utilizza un confronto rigoroso (`===`) -- non ha bisogno di un elemento `break` +- non necessita di `break` -Quindi è l'esatto equivalente della struttura `match` di cui PHP 8.0 è dotato. +È quindi l'esatto equivalente della struttura `match` introdotta con PHP 8.0. ```latte {switch $transport} {case train} - By train + In treno {case plane} - By plane + In aereo {default} - Differently + Altrimenti {/switch} ``` -.{data-version:2.9} + La clausola `{case}` può contenere più valori separati da virgole: ```latte {switch $status} -{case $status::New}new item -{case $status::Sold, $status::Unknown}not available +{case $status::New}nuovo elemento +{case $status::Sold, $status::Unknown}non disponibile {/switch} ``` -Cicli .[#toc-loops] -=================== +Cicli +===== -In Latte sono disponibili tutti i cicli conosciuti in PHP: foreach, for e while. +In Latte trovate tutti i cicli che conoscete da PHP: foreach, for e while. `{foreach}` ----------- -Si scrive il ciclo esattamente come in PHP: +Scriviamo il ciclo esattamente come in PHP: ```latte {foreach $langs as $code => $lang} @@ -357,11 +377,11 @@ Si scrive il ciclo esattamente come in PHP: {/foreach} ``` -Inoltre, ha alcune utili modifiche di cui parleremo ora. +Inoltre, ha alcune comode funzionalità di cui parleremo ora. -Per esempio, Latte controlla che le variabili create non sovrascrivano accidentalmente le variabili globali con lo stesso nome. Questo vi salverà quando assumete che `$lang` sia la lingua corrente della pagina e non vi rendete conto che `foreach $langs as $lang` ha sovrascritto quella variabile. +Ad esempio, Latte controlla se le variabili create sovrascrivono accidentalmente variabili globali con lo stesso nome. Questo salva situazioni in cui vi aspettate che `$lang` contenga la lingua corrente della pagina e non vi rendete conto che `foreach $langs as $lang` vi ha sovrascritto quella variabile. -Anche il ciclo foreach può essere scritto in modo molto elegante ed economico con [n:attribute |syntax#n:attributes]: +Il ciclo foreach può anche essere scritto in modo molto elegante e conciso usando un [n:attributo |syntax#n:attributi]: ```latte
                                                                                @@ -369,7 +389,7 @@ Anche il ciclo foreach può essere scritto in modo molto elegante ed economico c
                                                                              ``` -Sapevate che potete anteporre il prefisso `inner-` a n:attribute? In questo modo, solo la parte interna dell'elemento verrà ripetuta nel ciclo: +Sapete che agli n:attributi potete aggiungere il prefisso `inner-`? Allora solo l'interno dell'elemento verrà ripetuto nel ciclo: ```latte
                                                                              @@ -378,7 +398,7 @@ Sapevate che potete anteporre il prefisso `inner-` a n:attribute? In questo modo
                                                                              ``` -Quindi viene stampato qualcosa come: +Quindi verrà stampato qualcosa come: ```latte
                                                                              @@ -390,17 +410,17 @@ Quindi viene stampato qualcosa come: ``` -`{else}` .{data-version:2.9}{toc: foreach-else} ------------------------------------------------ +`{else}` .{toc: foreach-else} +----------------------------- -Il ciclo `foreach` può contenere una clausola opzionale `{else}` il cui testo viene visualizzato se l'array dato è vuoto: +All'interno del ciclo `foreach` è possibile specificare la clausola `{else}`, il cui contenuto viene visualizzato se il ciclo è vuoto: ```latte
                                                                                {foreach $people as $person}
                                                                              • {$person->name}
                                                                              • {else} -
                                                                              • Sorry, no users in this list
                                                                              • +
                                                                              • Siamo spiacenti, non ci sono utenti in questo elenco
                                                                              • {/foreach}
                                                                              ``` @@ -409,17 +429,17 @@ Il ciclo `foreach` può contenere una clausola opzionale `{else}` il cui testo v `$iterator` ----------- -All'interno del ciclo `foreach` viene inizializzata la variabile `$iterator`. Essa contiene informazioni importanti sul ciclo corrente. +All'interno del ciclo `foreach`, Latte crea la variabile `$iterator`, tramite la quale possiamo ottenere informazioni utili sul ciclo in corso: -- `$iterator->first` - è la prima iterazione? -- `$iterator->last` - è l'ultima iterazione? -- `$iterator->counter` - contatore di iterazioni, parte da 1 -- `$iterator->counter0` - contatore di iterazioni, parte da 0 .{data-version:2.9} -- `$iterator->odd` - questa iterazione è dispari? -- `$iterator->even` - questa iterazione è pari? -- `$iterator->parent` - l'iteratore che circonda quello attuale .{data-version:2.9} -- `$iterator->nextValue` - il prossimo elemento del ciclo -- `$iterator->nextKey` - la chiave del prossimo elemento del ciclo +- `$iterator->first` - è la prima volta che si attraversa il ciclo? +- `$iterator->last` - è l'ultimo passaggio? +- `$iterator->counter` - qual è il numero del passaggio contando da uno? +- `$iterator->counter0` - qual è il numero del passaggio contando da zero? +- `$iterator->odd` - è un passaggio dispari? +- `$iterator->even` - è un passaggio pari? +- `$iterator->parent` - l'iteratore che racchiude quello attuale +- `$iterator->nextValue` - l'elemento successivo nel ciclo +- `$iterator->nextKey` - la chiave dell'elemento successivo nel ciclo ```latte @@ -435,20 +455,19 @@ All'interno del ciclo `foreach` viene inizializzata la variabile `$iterator`. Es {/foreach} ``` -Il latte è intelligente e `$iterator->last` funziona non solo per gli array, ma anche quando il ciclo scorre su un iteratore generico in cui il numero di elementi non è noto in anticipo. +Latte è intelligente e `$iterator->last` funziona non solo con gli array, ma anche quando il ciclo viene eseguito su un iteratore generico, dove il numero di elementi non è noto in anticipo. `{first}` `{last}` `{sep}` -------------------------- -Questi tag possono essere utilizzati all'interno del ciclo `{foreach}`. Il contenuto di `{first}` viene reso per il primo passaggio. -Il contenuto di `{last}` viene reso... indovinate? Sì, per l'ultimo passaggio. Si tratta in realtà di scorciatoie per `{if $iterator->first}` e `{if $iterator->last}`. +Questi tag possono essere utilizzati all'interno del ciclo `{foreach}`. Il contenuto di `{first}` viene renderizzato se è il primo passaggio. Il contenuto di `{last}` viene renderizzato... indovinerete? Sì, se è l'ultimo passaggio. Sono in realtà scorciatoie per `{if $iterator->first}` e `{if $iterator->last}`. -I tag possono anche essere scritti come [n:attributes |syntax#n:attributes]: +I tag possono anche essere utilizzati elegantemente come [n:attributo |syntax#n:attributi]: ```latte {foreach $rows as $row} - {first}

                                                                              List of names

                                                                              {/first} + {first}

                                                                              Elenco dei nomi

                                                                              {/first}

                                                                              {$row->name}

                                                                              @@ -456,7 +475,7 @@ I tag possono anche essere scritti come [n:attributes |syntax#n:attributes]: {/foreach} ``` -Il contenuto di `{sep}` viene reso se l'iterazione non è l'ultima, quindi è adatto alla stampa di delimitatori, come le virgole tra gli elementi elencati: +Il contenuto del tag `{sep}` viene renderizzato se il passaggio non è l'ultimo, quindi è utile per renderizzare separatori, ad esempio virgole tra gli elementi stampati: ```latte {foreach $items as $item} {$item} {sep}, {/sep} {/foreach} @@ -465,12 +484,12 @@ Il contenuto di `{sep}` viene reso se l'iterazione non è l'ultima, quindi è ad È piuttosto pratico, vero? -`{iterateWhile}` .{data-version:2.10} -------------------------------------- +`{iterateWhile}` +---------------- -Semplifica il raggruppamento dei dati lineari durante l'iterazione in un ciclo foreach, eseguendo l'iterazione in un ciclo annidato finché la condizione è soddisfatta. [Leggete le istruzioni nel ricettario |cookbook/iteratewhile]. +Semplifica il raggruppamento di dati lineari durante l'iterazione in un ciclo foreach eseguendo l'iterazione in un ciclo annidato finché la condizione è soddisfatta. [Leggi il tutorial dettagliato|cookbook/grouping]. -Può anche sostituire elegantemente `{first}` e `{last}` nell'esempio precedente: +Può anche sostituire elegantemente `{first}` e `{last}` nell'esempio sopra: ```latte {foreach $rows as $row} @@ -487,6 +506,8 @@ Può anche sostituire elegantemente `{first}` e `{last}` nell'esempio precedente {/foreach} ``` +Vedi anche i filtri [batch |filters#batch] e [group |filters#group]. + `{for}` ------- @@ -495,11 +516,11 @@ Scriviamo il ciclo esattamente come in PHP: ```latte {for $i = 0; $i < 10; $i++} - Item #{$i} + Elemento {$i} {/for} ``` -Il tag può anche essere scritto come [n:attribute |syntax#n:attributes]: +Il tag può essere utilizzato anche come [n:attributo |syntax#n:attributi]: ```latte

                                                                              {$i}

                                                                              @@ -517,7 +538,7 @@ Anche in questo caso, scriviamo il ciclo esattamente come in PHP: {/while} ``` -O come [n:attributo |syntax#n:attributes]: +O come [n:attributo |syntax#n:attributi]: ```latte @@ -525,7 +546,7 @@ O come [n:attributo |syntax#n:attributes]: ``` -Una variante con una condizione nel tag finale corrisponde al ciclo do-while di PHP: +È possibile anche una variante con la condizione nel tag di chiusura, che corrisponde al ciclo do-while in PHP: ```latte {while} @@ -537,7 +558,7 @@ Una variante con una condizione nel tag finale corrisponde al ciclo do-while di `{continueIf}` `{skipIf}` `{breakIf}` ------------------------------------- -Esistono tag speciali che possono essere utilizzati per controllare qualsiasi ciclo: `{continueIf ?}` e `{breakIf ?}`, che saltano rispettivamente all'iterazione successiva e terminano il ciclo, se le condizioni sono soddisfatte: +Per controllare qualsiasi ciclo è possibile utilizzare i tag `{continueIf ?}` e `{breakIf ?}`, che passano all'elemento successivo rispettivamente terminano il ciclo se la condizione è soddisfatta: ```latte {foreach $rows as $row} @@ -547,8 +568,8 @@ Esistono tag speciali che possono essere utilizzati per controllare qualsiasi ci {/foreach} ``` -.{data-version:2.9} -Il tag `{skipIf}` è molto simile a `{continueIf}`, ma non incrementa il contatore. Quindi non ci sono buchi nella numerazione quando si stampa `$iterator->counter` e si saltano alcuni elementi. Anche la clausola {else} sarà resa quando si saltano tutti gli elementi. + +Il tag `{skipIf}` è molto simile a `{continueIf}`, ma non incrementa il contatore `$iterator->counter`, quindi se lo stampiamo e allo stesso tempo saltiamo alcuni elementi, non ci saranno buchi nella numerazione. E anche la clausola `{else}` viene renderizzata se saltiamo tutti gli elementi. ```latte
                                                                                @@ -556,7 +577,7 @@ Il tag `{skipIf}` è molto simile a `{continueIf}`, ma non incrementa il contato {skipIf $person->age < 18}
                                                                              • {$iterator->counter}. {$person->name}
                                                                              • {else} -
                                                                              • Sorry, no adult users in this list
                                                                              • +
                                                                              • Siamo spiacenti, non ci sono adulti in questo elenco
                                                                              • {/foreach}
                                                                              ``` @@ -565,72 +586,68 @@ Il tag `{skipIf}` è molto simile a `{continueIf}`, ma non incrementa il contato `{exitIf}` .{data-version:3.0.5} -------------------------------- -Termina il rendering di un modello o di un blocco quando viene soddisfatta una condizione (cioè "uscita anticipata"). +Termina il rendering del template o del blocco se la condizione è soddisfatta (il cosiddetto "early exit"). ```latte {exitIf !$messages} -

                                                                              Messages

                                                                              +

                                                                              Messaggi

                                                                              {$message}
                                                                              ``` -Inclusione di modelli .[#toc-including-templates] -================================================= +Inclusione di template +====================== `{include 'file.latte'}` .{toc: include} ---------------------------------------- .[note] -Vedi anche [`{include block}` |template-inheritance#printing-blocks] +Vedi anche [`{include block}` |template-inheritance#Rendering dei blocchi include] -Il tag `{include}` carica e rende il modello specificato. Nel nostro linguaggio PHP preferito è come: +Il tag `{include}` carica e renderizza il template specificato. Se dovessimo parlare nel linguaggio del nostro linguaggio preferito PHP, sarebbe qualcosa come: ```php ``` -I template inclusi non hanno accesso alle variabili del contesto attivo, ma hanno accesso alle variabili globali. +I template inclusi non hanno accesso alle variabili del contesto attivo, hanno accesso solo alle variabili globali. -È possibile passare le variabili in questo modo: +Potete passare variabili al template incluso in questo modo: ```latte -{* da Latte 2.9 *} {include 'template.latte', foo: 'bar', id: 123} - -{* prima di Latte 2.9 *} -{include 'template.latte', foo => 'bar', id => 123} ``` -Il nome del modello può essere una qualsiasi espressione PHP: +Il nome del template può essere qualsiasi espressione PHP: ```latte {include $someVar} {include $ajax ? 'ajax.latte' : 'not-ajax.latte'} ``` -Il contenuto inserito può essere modificato utilizzando dei [filtri |syntax#filters]. L'esempio seguente rimuove tutto il materiale HTML e regola il caso: +Il contenuto incluso può essere modificato usando i [filtri |syntax#Filtri]. L'esempio seguente rimuove tutto l'HTML e modifica la dimensione dei caratteri: ```latte {include 'heading.latte' |stripHtml|capitalize} ``` -L'[eredità del template |template inheritance] **non è coinvolta** in questo per impostazione predefinita. Sebbene sia possibile aggiungere tag di blocco ai template che sono inclusi, essi non sostituiranno i blocchi corrispondenti nel template in cui sono inclusi. Si pensi agli include come a parti indipendenti e protette di pagine o moduli. Questo comportamento può essere modificato usando il modificatore `with blocks` (da Latte 2.9.1): +Per impostazione predefinita, l'[ereditarietà dei template|template-inheritance] non figura in alcun modo in questo caso. Anche se possiamo usare i blocchi nel template incluso, i blocchi corrispondenti nel template in cui viene incluso non verranno sostituiti. Pensate ai template inclusi come parti separate e isolate di pagine o moduli. Questo comportamento può essere modificato usando il modificatore `with blocks`: ```latte {include 'template.latte' with blocks} ``` -La relazione tra il nome del file specificato nel tag e il file sul disco è una questione di [caricatore |extending-latte#Loaders]. +La relazione tra il nome del file specificato nel tag e il file sul disco è compito del [loader|loaders]. -`{sandbox}` .{data-version:2.8} -------------------------------- +`{sandbox}` +----------- -Quando si include un modello creato da un utente finale, si dovrebbe considerare la possibilità di metterlo in sandbox (maggiori informazioni nella [documentazione di sandbox |sandbox]): +Quando si include un template creato dall'utente finale, si dovrebbe considerare la modalità sandbox (maggiori informazioni nella [documentazione di sandbox |sandbox]): ```latte {sandbox 'untrusted.latte', level: 3, data: $menu} @@ -641,9 +658,9 @@ Quando si include un modello creato da un utente finale, si dovrebbe considerare ========= .[note] -Vedi anche [`{block name}` |template-inheritance#blocks] +Vedi anche [`{block name}` |template-inheritance#Blocchi block] -I blocchi senza nome servono per applicare [filtri |syntax#filters] a una parte del modello. Ad esempio, è possibile applicare un filtro [striscia |filters#strip] per rimuovere gli spazi non necessari: +I blocchi senza nome servono come modo per applicare i [filtri |syntax#Filtri] a una parte del template. Ad esempio, così si può applicare il filtro [strip |filters#spaceless], che rimuove gli spazi superflui: ```latte {block|strip} @@ -654,16 +671,16 @@ I blocchi senza nome servono per applicare [filtri |syntax#filters] a una parte ``` -Gestione delle eccezioni .[#toc-exception-handling] -=================================================== +Gestione delle eccezioni +======================== -`{try}` .{data-version:2.9} ---------------------------- +`{try}` +------- -Questo tag rende estremamente facile la costruzione di modelli robusti. +Grazie a questo tag, è estremamente facile creare template robusti. -Se si verifica un'eccezione durante la resa del blocco `{try}`, l'intero blocco viene gettato via e la resa continuerà dopo di esso: +Se si verifica un'eccezione durante il rendering del blocco `{try}`, l'intero blocco viene scartato e il rendering continuerà dopo di esso: ```latte {try} @@ -675,7 +692,7 @@ Se si verifica un'eccezione durante la resa del blocco `{try}`, l'intero blocco {/try} ``` -Il contenuto della clausola opzionale `{else}` viene reso solo quando si verifica un'eccezione: +Il contenuto nella clausola opzionale `{else}` viene renderizzato solo quando si verifica un'eccezione: ```latte {try} @@ -685,11 +702,11 @@ Il contenuto della clausola opzionale `{else}` viene reso solo quando si verific {/foreach} {else} -

                                                                              Sorry, the tweets could not be loaded.

                                                                              +

                                                                              Siamo spiacenti, non è stato possibile caricare i tweet.

                                                                              {/try} ``` -Il tag può anche essere scritto come [n:attribute |syntax#n:attributes]: +Il tag può essere utilizzato anche come [n:attributo |syntax#n:attributi]: ```latte
                                                                                @@ -697,13 +714,13 @@ Il tag può anche essere scritto come [n:attribute |syntax#n:attributes]:
                                                                              ``` -È anche possibile definire un [proprio gestore di eccezioni |develop#exception handler] per esempio per la registrazione: +È anche possibile definire un [gestore di eccezioni personalizzato |develop#Handler delle Eccezioni], ad esempio per il logging. -`{rollback}` .{data-version:2.9} --------------------------------- +`{rollback}` +------------ -Il blocco `{try}` può anche essere fermato e saltato manualmente usando `{rollback}`. In questo modo non è necessario controllare in anticipo tutti i dati di input e solo durante il rendering si può decidere se ha senso eseguire il rendering dell'oggetto. +Il blocco `{try}` può essere interrotto e saltato anche manually usando `{rollback}`. Grazie a ciò, non è necessario controllare in anticipo tutti i dati di input e solo durante il rendering si può decidere che non si desidera affatto renderizzare l'oggetto: ```latte {try} @@ -719,14 +736,14 @@ Il blocco `{try}` può anche essere fermato e saltato manualmente usando `{rollb ``` -Variabili .[#toc-variables] -=========================== +Variabili +========= `{var}` `{default}` ------------------- -Creeremo nuove variabili nel template con il tag `{var}`: +Creiamo nuove variabili nel template con il tag `{var}`: ```latte {var $name = 'John Smith'} @@ -736,13 +753,13 @@ Creeremo nuove variabili nel template con il tag `{var}`: {var $name = 'John Smith', $age = 27} ``` -Il tag `{default}` funziona in modo simile, ma crea le variabili solo se non esistono: +Il tag `{default}` funziona in modo simile, ma crea variabili solo se non esistono. Se la variabile esiste già e contiene il valore `null`, non verrà sovrascritta: ```latte {default $lang = 'cs'} ``` -A partire da Latte 2.7, è possibile specificare anche [i tipi di variabili |type-system]. Per ora sono informative e Latte non le controlla. +Potete specificare anche i [tipi delle variabili|type-system]. Per ora sono informativi e Latte non li controlla. ```latte {var string $name = $article->getTitle()} @@ -750,10 +767,10 @@ A partire da Latte 2.7, è possibile specificare anche [i tipi di variabili |typ ``` -`{parameters}` .{data-version:2.9} ----------------------------------- +`{parameters}` +-------------- -Proprio come una funzione dichiara i suoi parametri, un modello può dichiarare le sue variabili all'inizio: +Proprio come una funzione dichiara i suoi parametri, anche un template può dichiarare le sue variabili all'inizio: ```latte {parameters @@ -763,15 +780,15 @@ Proprio come una funzione dichiara i suoi parametri, un modello può dichiarare } ``` -Le variabili `$a` e `$b` senza un valore predefinito hanno automaticamente un valore predefinito di `null`. I tipi dichiarati sono ancora informativi e Latte non li controlla. +Le variabili `$a` e `$b` senza un valore predefinito specificato hanno automaticamente il valore predefinito `null`. I tipi dichiarati sono per ora informativi e Latte non li controlla. -Oltre alle variabili dichiarate, non vengono passate nel template. Questa è una differenza rispetto al tag `{default}`. +Altre variabili diverse da quelle dichiarate non vengono trasferite al template. Questo le differenzia dal tag `{default}`. `{capture}` ----------- -Utilizzando il tag `{capture}` è possibile catturare l'output in una variabile: +Cattura l'output in una variabile: ```latte {capture $var} @@ -780,10 +797,10 @@ Utilizzando il tag `{capture}` è possibile catturare l'output in una variabile: {/capture} -

                                                                              Captured: {$var}

                                                                              +

                                                                              Catturato: {$var}

                                                                              ``` -Il tag può anche essere scritto come [n:attribute |syntax#n:attributes]: +Il tag può, come ogni tag accoppiato, essere scritto anche come [n:attributo |syntax#n:attributi]: ```latte
                                                                                @@ -791,15 +808,17 @@ Il tag può anche essere scritto come [n:attribute |syntax#n:attributes]:
                                                                              ``` +L'output HTML viene salvato nella variabile `$var` sotto forma di oggetto `Latte\Runtime\Html`, per [evitare l'escaping indesiderato |develop#Disabilitazione dell Auto-Escaping di una Variabile] durante la stampa. + -Altri .[#toc-others] -==================== +Altri +===== `{contentType}` --------------- -Usare il tag per specificare il tipo di contenuto che il modello rappresenta. Le opzioni sono: +Con questo tag specifichi quale tipo di contenuto rappresenta il template. Le opzioni sono: - `html` (tipo predefinito) - `xml` @@ -808,16 +827,16 @@ Usare il tag per specificare il tipo di contenuto che il modello rappresenta. Le - `calendar` (iCal) - `text` -Il suo uso è importante perché imposta l'[escape sensibile al contesto |safety-first#context-aware-escaping] e solo allora Latte può eseguire l'escape correttamente. Ad esempio, `{contentType xml}` passa alla modalità XML, `{contentType text}` disattiva completamente l'escape. +Il suo utilizzo è importante perché imposta l'[escaping sensibile al contesto |safety-first#Escaping sensibile al contesto] e solo così può eseguire l'escaping correttamente. Ad esempio, `{contentType xml}` passa alla modalità XML, `{contentType text}` disabilita completamente l'escaping. -Se il parametro è un tipo MIME completo, come `application/xml`, invia anche un'intestazione HTTP `Content-Type` al browser: +Se il parametro è un tipo MIME completo, come ad esempio `application/xml`, invierà anche l'header HTTP `Content-Type` al browser: ```latte {contentType application/xml} - RSS feed + Feed RSS ... @@ -829,26 +848,24 @@ Se il parametro è un tipo MIME completo, come `application/xml`, invia anche un `{debugbreak}` -------------- -Specifica il punto in cui l'esecuzione del codice si interrompe. Viene utilizzato a scopo di debug per consentire al programmatore di ispezionare l'ambiente di runtime e di assicurarsi che il codice venga eseguito come previsto. Supporta [Xdebug |https://xdebug.org]. Inoltre, è possibile specificare una condizione in cui il codice deve interrompersi. +Indica il punto in cui l'esecuzione del programma verrà sospesa e verrà avviato il debugger, in modo che il programmatore possa ispezionare l'ambiente di runtime e verificare se il programma funziona come previsto. Supporta [Xdebug |https://xdebug.org/]. È possibile aggiungere una condizione che determina quando il programma deve essere sospeso. ```latte -{debugbreak} {* interrompe il programma *} +{debugbreak} {* sospende il programma *} -{debugbreak $counter == 1} {* interrompe il programma se la condizione è soddisfatta *} +{debugbreak $counter == 1} {* sospende il programma se la condizione è soddisfatta *} ``` `{do}` ------ -Esegue il codice e non stampa nulla. +Esegue codice PHP e non stampa nulla. Come per tutti gli altri tag, per codice PHP si intende una singola espressione, vedi [limitazioni di PHP |syntax#Limitazioni di PHP in Latte]. ```latte {do $num++} ``` -In Latte 2.7 e precedenti, veniva utilizzato `{php}`. - `{dump}` -------- @@ -856,19 +873,25 @@ In Latte 2.7 e precedenti, veniva utilizzato `{php}`. Esegue il dump di una variabile o del contesto corrente. ```latte -{dump $nome} {* scarica la variabile $nome *} +{dump $name} {* Esegue il dump della variabile $name *} -{dump} {* cancella tutte le variabili definite *} +{dump} {* Esegue il dump di tutte le variabili attualmente definite *} ``` .[caution] -Richiede il pacchetto [Tracy |tracy:]. +Richiede la libreria [Tracy|tracy:]. + + +`{php}` +------- + +Permette di eseguire qualsiasi codice PHP. Il tag deve essere attivato tramite l'estensione [RawPhpExtension |develop#RawPhpExtension]. `{spaceless}` ------------- -Rimuove gli spazi bianchi non necessari. È simile al filtro [senza spazi |filters#spaceless]. +Rimuove gli spazi bianchi superflui dall'output. Funziona in modo simile al filtro [spaceless |filters#spaceless]. ```latte {spaceless} @@ -878,52 +901,52 @@ Rimuove gli spazi bianchi non necessari. È simile al filtro [senza spazi |filte {/spaceless} ``` -Uscite: +Genera ```latte
                                                                              • Hello
                                                                              ``` -Il tag può essere scritto anche come [n:attribute |syntax#n:attributes]: +Il tag può anche essere scritto come [n:attributo |syntax#n:attributi]. `{syntax}` ---------- -I tag Latte non devono essere racchiusi solo tra parentesi graffe singole. È possibile scegliere un altro separatore, anche in fase di esecuzione. Questo viene fatto da `{syntax…}`, dove il parametro può essere: +I tag Latte non devono essere delimitati solo da semplici parentesi graffe. Possiamo scegliere anche un altro delimitatore, e persino in fase di esecuzione. A questo serve `{syntax …}`, dove come parametro è possibile specificare: - double: `{{...}}` -- off: disabilita completamente i tag Latte +- off: disabilita completamente l'elaborazione dei tag Latte -Usando la notazione n:attribute si può disabilitare Latte solo per un blocco JavaScript: +Utilizzando gli n:attributi è possibile disabilitare Latte ad esempio solo per un blocco JavaScript: ```latte ``` -Latte può essere usato molto comodamente all'interno di JavaScript, basta evitare i costrutti come in questo esempio, dove la lettera segue immediatamente `{`, vedi [Latte all'interno di JavaScript o CSS |recipes#Latte inside JavaScript or CSS]. +Latte può essere utilizzato molto comodamente anche all'interno di JavaScript, basta evitare costrutti come in questo esempio, dove una lettera segue immediatamente `{`, vedi [Latte all'interno di JavaScript o CSS |recipes#Latte all interno di JavaScript o CSS]. -Se si disattiva Latte con `{syntax off}` (cioè con il tag, non con l'attributo n:), esso ignorerà rigorosamente tutti i tag fino a `{/syntax}`. +Se disabiliti Latte usando `{syntax off}` (cioè con il tag, non con l'n:attributo), ignorerà rigorosamente tutti i tag fino a `{/syntax}` -{trace} .{data-version:2.10} ----------------------------- +{trace} +------- -Lancia un'eccezione `Latte\RuntimeException`, la cui traccia di stack è nello spirito dei template. Quindi, invece di chiamare funzioni e metodi, comporta la chiamata di blocchi e l'inserimento di modelli. Se si utilizza uno strumento per visualizzare chiaramente le eccezioni lanciate, come [Tracy |tracy:], si vedrà chiaramente lo stack delle chiamate, compresi tutti gli argomenti passati. +Lancia un'eccezione `Latte\RuntimeException`, il cui stack trace è nello spirito dei template. Cioè, invece di chiamate a funzioni e metodi, contiene chiamate a blocchi e inclusioni di template. Se utilizzate uno strumento per la visualizzazione chiara delle eccezioni lanciate, come ad esempio [Tracy|tracy:], vedrete chiaramente lo stack delle chiamate inclusi tutti gli argomenti passati. -Aiutanti dei tag HTML .[#toc-html-tag-helpers] -============================================== +Aiutanti per il codificatore HTML +================================= -n:classe .[#toc-n-class] ------------------------- +n:class +------- -Grazie a `n:class`, è molto facile generare l'attributo HTML `class` esattamente come serve. +Grazie a `n:class` è molto facile generare l'attributo HTML `class` esattamente come desiderato. -Esempio: Ho bisogno che l'elemento attivo abbia la classe `active`: +Esempio: ho bisogno che l'elemento attivo abbia la classe `active`: ```latte {foreach $items as $item} @@ -931,7 +954,7 @@ Esempio: Ho bisogno che l'elemento attivo abbia la classe `active`: {/foreach} ``` -Inoltre, ho bisogno che il primo elemento abbia le classi `first` e `main`: +E inoltre, che il primo elemento abbia le classi `first` e `main`: ```latte {foreach $items as $item} @@ -950,10 +973,10 @@ E tutti gli elementi devono avere la classe `list-item`: Incredibilmente semplice, vero? -n:attr .[#toc-n-attr] ---------------------- +n:attr +------ -L'attributo `n:attr` può generare attributi HTML arbitrari con la stessa eleganza di [n:class |#n:class]. +L'attributo `n:attr` sa generare qualsiasi attributo HTML con la stessa eleganza di [#n:class]. ```latte {foreach $data as $item} @@ -961,7 +984,7 @@ L'attributo `n:attr` può generare attributi HTML arbitrari con la stessa elegan {/foreach} ``` -A seconda dei valori restituiti, visualizza ad es: +A seconda dei valori restituiti, stamperà ad es.: ```latte @@ -972,26 +995,28 @@ A seconda dei valori restituiti, visualizza ad es: ``` -n:tag .[#toc-n-tag] -------------------- +n:tag +----- -L'attributo `n:tag` può cambiare dinamicamente il nome di un elemento HTML. +L'attributo `n:tag` sa cambiare dinamicamente il nome di un elemento HTML. ```latte

                                                                              {$title}

                                                                              ``` -Se `$heading === null`, il tag `

                                                                              ` viene stampato senza modifiche. Altrimenti, il nome dell'elemento viene cambiato con il valore della variabile, quindi per `$heading === 'h3'` si scrive: +Se `$heading === null`, verrà stampato il tag `

                                                                              ` senza modifiche. Altrimenti, il nome dell'elemento verrà cambiato nel valore della variabile, quindi per `$heading === 'h3'` verrà stampato: ```latte

                                                                              ...

                                                                              ``` +Poiché Latte è un sistema di templating sicuro, controlla che il nuovo nome del tag sia valido e non contenga valori indesiderati o dannosi. -n:ifcontent .[#toc-n-ifcontent] -------------------------------- -Impedisce che venga stampato un elemento HTML vuoto, cioè un elemento contenente solo spazi bianchi. +n:ifcontent +----------- + +Impedisce che venga stampato un elemento HTML vuoto, cioè un elemento che non contiene nulla tranne spazi bianchi. ```latte
                                                                              @@ -999,24 +1024,24 @@ Impedisce che venga stampato un elemento HTML vuoto, cioè un elemento contenent
                                                                              ``` -A seconda dei valori della variabile `$error` questo verrà stampato: +Stampa a seconda del valore della variabile `$error`: ```latte {* $error = '' *}
                                                                              -{* $error = 'Richiesto' *} +{* $error = 'Required' *}
                                                                              -
                                                                              Richiesto
                                                                              +
                                                                              Required
                                                                              ``` -Traduzione .[#toc-translation] -============================== +Traduzioni +========== -Per far funzionare i tag di traduzione, è necessario [impostare translator |develop#TranslatorExtension]. Si può anche usare il filtro [`translate` |filters#translate] per la traduzione. +Affinché i tag per la traduzione funzionino, è necessario [attivare il traduttore |develop#TranslatorExtension]. Per la traduzione potete anche utilizzare il filtro [`translate` |filters#translate]. `{_...}` @@ -1025,30 +1050,30 @@ Per far funzionare i tag di traduzione, è necessario [impostare translator |dev Traduce i valori in altre lingue. ```latte -{_'Basket'} +{_'Carrello'} {_$item} ``` -Al traduttore possono essere passati anche altri parametri: +Al traduttore è possibile passare anche altri parametri: ```latte -{_'Basket', domain: order} +{_'Carrello', domain: order} ``` `{translate}` ------------- -Překládá části šablony: +Traduce parti del template: ```latte -

                                                                              {translate}Order{/translate}

                                                                              +

                                                                              {translate}Ordine{/translate}

                                                                              {translate domain: order}Lorem ipsum ...{/translate} ``` -Il tag può essere scritto anche come [n:attribute |syntax#n:attributes], per tradurre l'interno dell'elemento: +Il tag può anche essere scritto come [n:attributo |syntax#n:attributi], per tradurre l'interno dell'elemento: ```latte -

                                                                              Order

                                                                              +

                                                                              Ordine

                                                                              ``` diff --git a/latte/it/template-inheritance.texy b/latte/it/template-inheritance.texy index 10d70b4ad3..936e8be6ae 100644 --- a/latte/it/template-inheritance.texy +++ b/latte/it/template-inheritance.texy @@ -1,16 +1,16 @@ -Ereditarietà e riusabilità dei template -*************************************** +Ereditarietà e riutilizzo dei template +************************************** .[perex] -La riusabilità dei template e i meccanismi di ereditarietà sono qui per aumentare la produttività, perché ogni template contiene solo il suo contenuto unico e gli elementi e le strutture ripetuti vengono riutilizzati. Introduciamo tre concetti: [ereditarietà del layout |#layout inheritance], [riutilizzo orizzontale |#horizontal reuse] ed [ereditarietà delle unità |#unit inheritance]. +I meccanismi di riutilizzo e di ereditarietà dei template aumenteranno la vostra produttività, poiché ogni template contiene solo il suo contenuto unico e gli elementi e le strutture ripetuti vengono riutilizzati. Introduciamo tre concetti: [ereditarietà del layout |#Ereditarietà del layout layout], [riutilizzo orizzontale |#Riutilizzo orizzontale import] e [ereditarietà unitaria |#Ereditarietà unitaria embed]. -Il concetto di ereditarietà dei template di Latte è simile all'ereditarietà delle classi di PHP. Si definisce un **modello padre** da cui altri **modelli figli** possono estendere e sovrascrivere parti del modello padre. Funziona bene quando gli elementi condividono una struttura comune. Sembra complicato? Non preoccupatevi, non lo è. +Il concetto di ereditarietà dei template di Latte è simile all'ereditarietà delle classi in PHP. Si definisce un **template genitore** da cui altri **template figli** possono ereditare e sovrascrivere parti del template genitore. Funziona alla grande quando gli elementi condividono una struttura comune. Sembra complicato? Non preoccupatevi, è molto semplice. -Ereditarietà del layout `{layout}` .{toc: Layout Inheritance} -============================================================= +Ereditarietà del layout `{layout}` +================================== -Vediamo l'ereditarietà dei modelli di layout partendo da un esempio. Si tratta di un modello padre, che chiameremo per esempio `layout.latte`, che definisce uno scheletro di documento HTML. +Vediamo l'ereditarietà del template di layout, cioè del layout, con un esempio. Questo è un template genitore, che chiameremo ad esempio `layout.latte`, e che definisce lo scheletro di un documento HTML: ```latte @@ -30,9 +30,9 @@ Vediamo l'ereditarietà dei modelli di layout partendo da un esempio. Si tratta ``` -Il tag `{block}` definisce tre blocchi che i template figli possono riempire. Il tag block serve solo a dire al motore del template che un template figlio può sovrascrivere quelle porzioni del template definendo il proprio blocco con lo stesso nome. +I tag `{block}` definiscono tre blocchi che i template figli possono riempire. Il tag block fa solo questo: annuncia che questo posto può essere sovrascritto da un template figlio definendo il proprio blocco con lo stesso nome. -Un template figlio potrebbe avere questo aspetto: +Un template figlio può assomigliare a questo: ```latte {layout 'layout.latte'} @@ -44,11 +44,11 @@ Un template figlio potrebbe avere questo aspetto: {/block} ``` -Il tag `{layout}` è la chiave. Indica al motore dei template che questo template "estende" un altro template. Quando Latte esegue il rendering di questo modello, per prima cosa individua il genitore, in questo caso `layout.latte`. +La chiave qui è il tag `{layout}`. Dice a Latte che questo template "estende" un altro template. Quando Latte renderizza questo template, prima trova il template genitore - in questo caso `layout.latte`. -A quel punto, il motore del template noterà i tre tag di blocco in `layout.latte` e sostituirà quei blocchi con il contenuto del template figlio. Si noti che, poiché il template figlio non ha definito il blocco *footer*, viene utilizzato il contenuto del template padre. Il contenuto all'interno di un tag `{block}` in un template padre viene sempre usato come fallback. +A questo punto, Latte nota i tre tag block in `layout.latte` e sostituisce questi blocchi con il contenuto del template figlio. Poiché il template figlio non ha definito un blocco *footer*, viene utilizzato invece il contenuto del template genitore. Il contenuto all'interno del tag `{block}` nel template genitore viene sempre utilizzato come fallback. -L'output potrebbe essere simile a: +L'output potrebbe assomigliare a questo: ```latte @@ -68,7 +68,7 @@ L'output potrebbe essere simile a: ``` -In un template figlio, i blocchi possono essere posizionati solo al livello superiore o all'interno di un altro blocco, ad esempio: +Nel template figlio, i blocchi possono essere posizionati solo al livello più alto o all'interno di un altro blocco, cioè: ```latte {block content} @@ -76,7 +76,7 @@ In un template figlio, i blocchi possono essere posizionati solo al livello supe {/block} ``` -Inoltre, un blocco verrà sempre creato, indipendentemente dal fatto che la condizione circostante `{if}` sia valutata come vera o falsa. Contrariamente a quanto si potrebbe pensare, questo modello definisce un blocco. +Inoltre, un blocco verrà sempre creato indipendentemente dal fatto che la condizione `{if}` circostante sia valutata come vera o falsa. Quindi, anche se non sembra, questo template definisce il blocco. ```latte {if false} @@ -86,7 +86,7 @@ Inoltre, un blocco verrà sempre creato, indipendentemente dal fatto che la cond {/if} ``` -Se si vuole che l'output all'interno del blocco sia visualizzato in modo condizionale, utilizzare il seguente metodo: +Se si desidera che l'output all'interno del blocco venga visualizzato condizionatamente, utilizzare invece quanto segue: ```latte {block head} @@ -96,7 +96,7 @@ Se si vuole che l'output all'interno del blocco sia visualizzato in modo condizi {/block} ``` -I dati al di fuori di un blocco in un modello figlio vengono eseguiti prima che il modello di layout venga reso, pertanto è possibile utilizzarlo per definire variabili come `{var $foo = bar}` e propagare i dati all'intera catena ereditaria: +Lo spazio al di fuori dei blocchi nel template figlio viene eseguito prima del rendering del template di layout, quindi è possibile utilizzarlo per definire variabili come `{var $foo = bar}` e per propagare i dati lungo l'intera catena di ereditarietà: ```latte {layout 'layout.latte'} @@ -106,51 +106,50 @@ I dati al di fuori di un blocco in un modello figlio vengono eseguiti prima che ``` -Ereditarietà multilivello .[#toc-multilevel-inheritance] --------------------------------------------------------- -Si possono usare tutti i livelli di ereditarietà necessari. Un modo comune di usare l'ereditarietà dei layout è il seguente approccio a tre livelli: +Ereditarietà multilivello +------------------------- +È possibile utilizzare tutti i livelli di ereditarietà necessari. Un modo comune per utilizzare l'ereditarietà del layout è il seguente approccio a tre livelli: -1) Creare un modello `layout.latte` che contenga l'aspetto principale del sito. -2) Creare un modello `layout-SECTIONNAME.latte` per ogni sezione del sito. Per esempio, `layout-news.latte`, `layout-blog.latte` ecc. Questi template estendono tutti `layout.latte` e includono stili e design specifici per ogni sezione. -3) Creare modelli individuali per ogni tipo di pagina, come ad esempio un articolo di notizie o un articolo di blog. Questi modelli estendono il modello di sezione appropriato. +1) Creare un template `layout.latte` che contenga lo scheletro principale dell'aspetto del sito. +2) Creare un template `layout-SECTIONNAME.latte` per ogni sezione del sito. Ad esempio `layout-news.latte`, `layout-blog.latte`, ecc. Tutti questi template estendono `layout.latte` e includono stili e design specifici per le singole sezioni. +3) Creare template individuali per ogni tipo di pagina, ad esempio un articolo di giornale o un post di blog. Questi template estendono il template della sezione corrispondente. -Ereditarietà dinamica del layout .[#toc-dynamic-layout-inheritance] -------------------------------------------------------------------- -È possibile usare una variabile o una qualsiasi espressione PHP come nome del template genitore, in modo che l'ereditarietà possa comportarsi dinamicamente: +Ereditarietà dinamica +--------------------- +Come nome del template genitore si può usare una variabile o qualsiasi espressione PHP, quindi l'ereditarietà può comportarsi dinamicamente: ```latte {layout $standalone ? 'minimum.latte' : 'layout.latte'} ``` -Si può anche usare l'API Latte per scegliere [automaticamente |develop#automatic-layout-lookup] il modello di layout. +È anche possibile utilizzare l'API di Latte per selezionare [automaticamente |develop#Ricerca Automatica del Layout] il template di layout. -Suggerimenti .[#toc-tips] -------------------------- -Ecco alcuni suggerimenti per lavorare con l'ereditarietà dei layout: +Suggerimenti +------------ +Ecco alcuni suggerimenti per lavorare con l'ereditarietà del layout: -- Se si usa `{layout}` in un modello, deve essere il primo tag del modello in quel modello. +- Se si utilizza `{layout}` in un template, deve essere il primo tag del template in quel template. -- Il layout può essere [cercato automaticamente |develop#automatic-layout-lookup] (come nei [presentatori |application:templates#search-for-templates]). In questo caso, se il modello non deve avere un layout, lo indicherà con il tag `{layout none}`. +- Il layout può essere [trovato automaticamente |develop#Ricerca Automatica del Layout] (come ad esempio nei [presenter |application:templates#Ricerca dei template]). In tal caso, se il template non deve avere un layout, lo annuncia con il tag `{layout none}`. -- Il tag `{layout}` ha l'alias `{extends}`. +- Il tag `{layout}` ha un alias `{extends}`. -- Il nome del file del template esteso dipende dal [caricatore di template |extending-latte#Loaders]. +- Il nome del file di layout dipende dal [loader |loaders]. -- Si possono avere tutti i blocchi che si vogliono. Si ricordi che i template figli non devono definire tutti i blocchi padre, per cui si possono inserire delle impostazioni predefinite ragionevoli in un certo numero di blocchi, per poi definire solo quelli necessari in seguito. +- È possibile avere quanti blocchi si desidera. Ricordate che i template figli non devono definire tutti i blocchi genitori, quindi potete riempire valori predefiniti ragionevoli in alcuni blocchi e poi definire solo quelli necessari in seguito. -Blocchi `{block}` .{toc: Blocks} -================================ +Blocchi `{block}` +================= .[note] -Vedi anche anonimo [`{block}` |tags#block] +Vedi anche [`{block}` anonimo |tags#block] -Un blocco fornisce un modo per modificare la resa di una certa parte di un template, ma non interferisce in alcun modo con la logica che lo circonda. Prendiamo il seguente esempio per illustrare come funziona un blocco e, soprattutto, come non funziona: +Un blocco rappresenta un modo per modificare come viene renderizzata una certa parte del template, ma non interferisce in alcun modo con la logica circostante. Nell'esempio seguente, mostreremo come funziona un blocco, ma anche come non funziona: -```latte -{* parent.Latte *} +```latte .{file: parent.latte} {foreach $posts as $post} {block post}

                                                                              {$post->title}

                                                                              @@ -159,10 +158,9 @@ Un blocco fornisce un modo per modificare la resa di una certa parte di un templ {/foreach} ``` -Se si renderizza questo modello, il risultato sarebbe esattamente lo stesso con o senza i tag di blocco. I blocchi hanno accesso alle variabili degli ambiti esterni. È solo un modo per renderlo sovrascrivibile da un template figlio: +Se si renderizza questo template, il risultato sarà esattamente lo stesso con o senza i tag `{block}`. I blocchi hanno accesso alle variabili dagli ambiti esterni. Danno solo la possibilità di essere sovrascritti da un template figlio: -```latte -{* child.Latte *} +```latte .{file: child.latte} {layout 'parent.Latte'} {block post} @@ -173,7 +171,7 @@ Se si renderizza questo modello, il risultato sarebbe esattamente lo stesso con {/block} ``` -Ora, durante il rendering del template figlio, il ciclo utilizzerà il blocco definito nel template figlio `child.Latte` invece di quello definito in quello base `parent.Latte`; il template eseguito è quindi equivalente al seguente: +Ora, durante il rendering del template figlio, il ciclo utilizzerà il blocco definito nel template figlio `child.Latte` invece del blocco definito in `parent.Latte`; il template eseguito è quindi equivalente al seguente: ```latte {foreach $posts as $post} @@ -184,7 +182,7 @@ Ora, durante il rendering del template figlio, il ciclo utilizzerà il blocco de {/foreach} ``` -Tuttavia, se creiamo una nuova variabile all'interno di un blocco con nome o sostituiamo un valore di una variabile esistente, la modifica sarà visibile solo all'interno del blocco: +Tuttavia, se creiamo una nuova variabile all'interno di un blocco nominato o sostituiamo il valore di una esistente, la modifica sarà visibile solo all'interno del blocco: ```latte {var $foo = 'foo'} @@ -197,13 +195,13 @@ foo: {$foo} // prints: foo bar: {$bar ?? 'not defined'} // prints: not defined ``` -Il contenuto del blocco può essere modificato dai [filtri |syntax#filters]. L'esempio seguente rimuove tutto l'HTML e lo titola: +Il contenuto del blocco può essere modificato utilizzando i [filtri |syntax#Filtri]. L'esempio seguente rimuove tutto l'HTML e cambia le maiuscole/minuscole: ```latte {block title|stripHtml|capitalize}...{/block} ``` -Il tag può anche essere scritto come [n:attribute |syntax#n:attributes]: +Il tag può anche essere scritto come [n:attributo |syntax#n:attributi]: ```latte
                                                                              @@ -212,10 +210,10 @@ Il tag può anche essere scritto come [n:attribute |syntax#n:attributes]: ``` -Blocchi locali .[#toc-local-blocks] ------------------------------------ +Blocchi locali +-------------- -Ogni blocco sovrascrive il contenuto del blocco padre con lo stesso nome. Ad eccezione dei blocchi locali. Sono come i metodi privati di una classe. Si può creare un modello senza preoccuparsi che, a causa della coincidenza dei nomi dei blocchi, questi vengano sovrascritti dal secondo modello. +Ogni blocco sovrascrive il contenuto del blocco genitore con lo stesso nome - ad eccezione dei blocchi locali. Nelle classi, sarebbero qualcosa come metodi privati. In questo modo è possibile creare un template senza preoccuparsi che, a causa della corrispondenza dei nomi dei blocchi, vengano sovrascritti da un altro template. ```latte {block local helper} @@ -224,13 +222,13 @@ Ogni blocco sovrascrive il contenuto del blocco padre con lo stesso nome. Ad ecc ``` -Stampa dei blocchi `{include}` .{toc: Printing Blocks} ------------------------------------------------------- +Rendering dei blocchi `{include}` +--------------------------------- .[note] Vedi anche [`{include file}` |tags#include] -Per stampare un blocco in un punto specifico, utilizzare il tag `{include blockname}`: +Per visualizzare un blocco in una posizione specifica, utilizzare il tag `{include blockname}`: ```latte {block title}{/block} @@ -238,32 +236,28 @@ Per stampare un blocco in un punto specifico, utilizzare il tag `{include blockn

                                                                              {include title}

                                                                              ``` -È anche possibile visualizzare i blocchi di un altro modello: +È anche possibile visualizzare un blocco da un altro template: ```latte {include footer from 'main.latte'} ``` -I blocchi stampati non hanno accesso alle variabili del contesto attivo, tranne se il blocco è definito nello stesso file in cui è incluso. Tuttavia, hanno accesso alle variabili globali. +Il blocco renderizzato non ha accesso alle variabili del contesto attivo, tranne nel caso in cui il blocco sia definito nello stesso file in cui è incluso. Tuttavia, ha accesso alle variabili globali. -È possibile passare le variabili in questo modo: +È possibile passare variabili al blocco in questo modo: ```latte -{* da Latte 2.9 *} {include footer, foo: bar, id: 123} - -{* prima di Latte 2.9 *} -{include footer, foo => bar, id => 123} ``` -Si può usare una variabile o qualsiasi espressione in PHP come nome del blocco. In questo caso, aggiungere la parola chiave `block` prima della variabile, in modo che sia noto a tempo di compilazione che si tratta di un blocco e non di un [modello di inserimento |tags#include], il cui nome potrebbe anche essere nella variabile: +Come nome del blocco si può usare una variabile o qualsiasi espressione PHP. In tal caso, aggiungiamo la parola chiave `block` prima della variabile, in modo che Latte sappia già in fase di compilazione che si tratta di un blocco e non di [inclusione del template |tags#include], il cui nome potrebbe anche essere in una variabile: ```latte {var $name = footer} {include block $name} ``` -Il blocco può anche essere stampato all'interno di se stesso, il che è utile, ad esempio, quando si rende una struttura ad albero: +Un blocco può essere renderizzato anche all'interno di sé stesso, il che è utile ad esempio per renderizzare una struttura ad albero: ```latte {define menu, $items} @@ -281,19 +275,19 @@ Il blocco può anche essere stampato all'interno di se stesso, il che è utile, {/define} ``` -Invece di `{include menu, ...}` si può scrivere `{include this, ...}`, dove `this` indica il blocco corrente. +Invece di `{include menu, ...}` possiamo quindi scrivere `{include this, ...}`, dove `this` significa il blocco corrente. -Il contenuto stampato può essere modificato da [filtri |syntax#filters]. L'esempio seguente rimuove tutto l'HTML e lo titola: +Il blocco renderizzato può essere modificato utilizzando i [filtri |syntax#Filtri]. L'esempio seguente rimuove tutto l'HTML e cambia le maiuscole/minuscole: ```latte {include heading|stripHtml|capitalize} ``` -Blocco genitore .[#toc-parent-block] ------------------------------------- +Blocco genitore +--------------- -Se è necessario stampare il contenuto del blocco dal modello genitore, l'istruzione `{include parent}` è sufficiente. È utile se si vuole aggiungere qualcosa al contenuto di un blocco genitore, invece di sovrascriverlo completamente. +Se è necessario visualizzare il contenuto di un blocco dal template genitore, utilizzare `{include parent}`. Questo è utile se si desidera solo aggiungere contenuto al blocco genitore invece di sovrascriverlo completamente. ```latte {block footer} @@ -304,16 +298,16 @@ Se è necessario stampare il contenuto del blocco dal modello genitore, l'istruz ``` -Definizioni `{define}` .{toc: Definitions} ------------------------------------------- +Definizioni `{define}` +---------------------- -Oltre ai blocchi, in Latte esistono anche le "definizioni". Sono paragonabili alle funzioni dei normali linguaggi di programmazione. Sono utili per riutilizzare frammenti di modelli e non ripetersi. +Oltre ai blocchi, in Latte esistono anche le "definizioni". Nei linguaggi di programmazione comuni, le paragoneremmo alle funzioni. Sono utili per riutilizzare frammenti di template per non ripetersi. -Latte cerca di fare le cose in modo semplice, quindi fondamentalmente le definizioni sono uguali ai blocchi e **tutto ciò che viene detto sui blocchi vale anche per le definizioni**. Si differenzia dai blocchi solo per tre aspetti: +Latte cerca di rendere le cose semplici, quindi fondamentalmente le definizioni sono uguali ai blocchi e **tutto ciò che viene detto sui blocchi vale anche per le definizioni**. Si differenziano dai blocchi in quanto: -1) possono accettare argomenti -2) non possono avere [filtri |syntax#filters] -3) sono racchiusi in tag `{define}` e il contenuto all'interno di questi tag non viene inviato all'output finché non li si include. Grazie a ciò, è possibile crearli ovunque: +1) sono racchiuse nei tag `{define}` +2) vengono renderizzate solo quando le si include tramite `{include}` +3) è possibile definire parametri per loro, in modo simile alle funzioni in PHP ```latte {block foo}

                                                                              Hello

                                                                              {/block} @@ -326,10 +320,9 @@ Latte cerca di fare le cose in modo semplice, quindi fondamentalmente le definiz {* prints:

                                                                              World

                                                                              *} ``` -Immaginiamo di avere un modello generico di aiuto che definisce come rendere i moduli HTML tramite definizioni: +Immaginate di avere un template di aiuto con una raccolta di definizioni su come disegnare form HTML. -```latte -{* forms.latte *} +```latte .{file: forms.latte} {define input, $name, $value, $type = 'text'} {/define} @@ -339,38 +332,36 @@ Immaginiamo di avere un modello generico di aiuto che definisce come rendere i m {/define} ``` -Gli argomenti di una definizione sono sempre opzionali con valore predefinito `null`, a meno che non sia specificato un valore predefinito (qui `text` è il valore predefinito per `$type`, possibile da Latte 2.9.1). A partire da Latte 2.7, è possibile dichiarare anche i tipi di parametro: `{define input, string $name, ...}`. - -Le definizioni non hanno accesso alle variabili del contesto attivo, ma hanno accesso alle variabili globali. +Gli argomenti sono sempre opzionali con un valore predefinito `null`, a meno che non venga specificato un valore predefinito (qui `'text'` è il valore predefinito per `$type`). È possibile dichiarare anche i tipi dei parametri: `{define input, string $name, ...}`. -Vengono incluse [allo stesso modo dei blocchi |#Printing Blocks]: +Carichiamo il template con le definizioni utilizzando [`{import}` |#Riutilizzo orizzontale import]. Le definizioni stesse vengono renderizzate [allo stesso modo dei blocchi |#Rendering dei blocchi include]: ```latte

                                                                              {include input, 'password', null, 'password'}

                                                                              {include textarea, 'comment'}

                                                                              ``` +Le definizioni non hanno accesso alle variabili del contesto attivo, ma hanno accesso alle variabili globali. -Nomi dinamici dei blocchi .[#toc-dynamic-block-names] ------------------------------------------------------ -Latte consente una grande flessibilità nella definizione dei blocchi, perché il nome del blocco può essere una qualsiasi espressione PHP. Questo esempio definisce tre blocchi denominati `hi-Peter`, `hi-John` e `hi-Mary`: +Nomi dinamici dei blocchi +------------------------- -```latte -{* parent.latte *} +Latte consente una grande flessibilità nella definizione dei blocchi, poiché il nome del blocco può essere qualsiasi espressione PHP. Questo esempio definisce tre blocchi con i nomi `hi-Peter`, `hi-John` e `hi-Mary`: + +```latte .{file: parent.latte} {foreach [Peter, John, Mary] as $name} {block "hi-$name"}Hi, I am {$name}.{/block} {/foreach} ``` -Per esempio, possiamo ridefinire solo un blocco in un modello figlio: +Nel template figlio possiamo quindi ridefinire, ad esempio, solo un blocco: -```latte -{* child.latte *} +```latte .{file: child.latte} {block hi-John}Hello. I am {$name}.{/block} ``` -Quindi l'output sarà simile a questo: +Quindi l'output assomiglierà a questo: ```latte Hi, I am Peter. @@ -379,13 +370,13 @@ Hi, I am Mary. ``` -Verifica dell'esistenza del blocco `{ifset}` .{toc: Checking Block Existence} ------------------------------------------------------------------------------ +Controllo dell'esistenza dei blocchi `{ifset}` +---------------------------------------------- .[note] -Vedi anche [`{ifset $var}` |tags#ifset-elseifset] +Vedi anche [`{ifset $var}` |tags#ifset elseifset] -Utilizzare il test `{ifset blockname}` per verificare se un blocco (o più blocchi) esiste nel contesto corrente: +Utilizzando il test `{ifset blockname}` verifichiamo se nel contesto corrente esiste un blocco (o più blocchi): ```latte {ifset footer} @@ -397,7 +388,7 @@ Utilizzare il test `{ifset blockname}` per verificare se un blocco (o più blocc {/ifset} ``` -Si può usare una variabile o qualsiasi espressione in PHP come nome del blocco. In questo caso, aggiungere la parola chiave `block` prima della variabile, per chiarire che non è la [variabile |tags#ifset-elseifset] a essere controllata: +Come nome del blocco si può usare una variabile o qualsiasi espressione PHP. In tal caso, aggiungiamo la parola chiave `block` prima della variabile per chiarire che non si tratta di un test sull'esistenza di [variabili |tags#ifset elseifset]: ```latte {ifset block $name} @@ -405,71 +396,69 @@ Si può usare una variabile o qualsiasi espressione in PHP come nome del blocco. {/ifset} ``` +L'esistenza dei blocchi viene verificata anche dalla funzione [`hasBlock()` |functions#hasBlock]: -Suggerimenti .[#toc-tips] -------------------------- -Ecco alcuni suggerimenti per lavorare con i blocchi: - -- L'ultimo blocco di primo livello non deve avere il tag di chiusura (il blocco termina con la fine del documento). Questo semplifica la scrittura di modelli figli, che hanno un unico blocco primario. +```latte +{if hasBlock(header) || hasBlock(footer)} + ... +{/if} +``` -- Per una maggiore leggibilità, si può opzionalmente dare un nome al tag `{/block}`, per esempio `{/block footer}`. Tuttavia, il nome deve corrispondere al nome del blocco. Nei template più grandi, questa tecnica aiuta a vedere quali tag di blocco vengono chiusi. -- Non è possibile definire direttamente più tag di blocco con lo stesso nome nello stesso template. Tuttavia, si può ottenere questo risultato usando [nomi di blocco dinamici |#dynamic block names]. +Suggerimenti +------------ +Alcuni suggerimenti per lavorare con i blocchi: -- Si possono usare [n:attributi |syntax#n:attributes] per definire blocchi come `

                                                                              Welcome to my awesome homepage

                                                                              ` +- L'ultimo blocco di livello superiore non deve avere un tag di chiusura (il blocco termina alla fine del documento). Questo semplifica la scrittura di template figli che contengono un blocco primario. -- I blocchi possono anche essere usati senza nome solo per applicare i [filtri |syntax#filters] all'output: `{block|strip} hello {/block}` +- Per una migliore leggibilità, è possibile specificare il nome del blocco nel tag `{/block}`, ad esempio `{/block footer}`. Tuttavia, il nome deve corrispondere al nome del blocco. Nei template più grandi, questa tecnica vi aiuterà a vedere quali tag di blocco vengono chiusi. +- Non è possibile definire direttamente più tag di blocco con lo stesso nome nello stesso template. Tuttavia, questo può essere ottenuto utilizzando [#nomi dinamici dei blocchi]. -Riutilizzo orizzontale `{import}` .{toc: Horizontal Reuse} -========================================================== +- È possibile utilizzare [n:attributi |syntax#n:attributi] per definire blocchi come `

                                                                              Welcome to my awesome homepage

                                                                              ` -Il riutilizzo orizzontale è un terzo meccanismo di riusabilità ed ereditarietà di Latte. Permette di caricare blocchi da altri modelli. È simile alla creazione di un file PHP con funzioni di aiuto o un tratto. +- I blocchi possono essere utilizzati anche senza nomi solo per applicare [filtri |syntax#Filtri]: `{block|strip} hello {/block}` -Sebbene l'ereditarietà dei modelli di layout sia una delle caratteristiche più potenti di Latte, è limitata all'ereditarietà singola; un modello può estendere solo un altro modello. Questa limitazione rende l'ereditarietà dei template semplice da capire e facile da debuggare: -```latte -{layout 'layout.latte'} +Riutilizzo orizzontale `{import}` +================================= -{block title}...{/block} -{block content}...{/block} -``` +Il riutilizzo orizzontale è il terzo meccanismo in Latte per il riutilizzo e l'ereditarietà. Consente di caricare blocchi da altri template. È simile a quando in PHP creiamo un file con funzioni di aiuto che poi carichiamo usando `require`. -Il riutilizzo orizzontale è un modo per raggiungere lo stesso obiettivo dell'ereditarietà multipla, ma senza la complessità associata: +Sebbene l'ereditarietà del layout del template sia una delle funzionalità più potenti di Latte, è limitata all'ereditarietà semplice: un template può estendere solo un altro template. Il riutilizzo orizzontale è un modo per ottenere l'ereditarietà multipla. -```latte -{layout 'layout.latte'} +Abbiamo un file con definizioni di blocchi: -{import 'blocks.latte'} +```latte .{file: blocks.latte} +{block sidebar}...{/block} -{block title}...{/block} -{block content}...{/block} +{block menu}...{/block} ``` -L'istruzione `{import}` dice a Latte di importare tutti i blocchi e le [definizioni |#definitions] definiti in `blocks.latte` nel modello corrente. +Utilizzando il comando `{import}`, importiamo tutti i blocchi e le [definizioni |#Definizioni define] definite in `blocks.latte` in un altro template: -```latte -{* blocks.latte *} +```latte .{file: child.latte} +{import 'blocks.latte'} -{block sidebar}...{/block} +{* ora è possibile utilizzare i blocchi sidebar e menu *} ``` -In questo esempio, l'istruzione `{import}` importa il blocco `sidebar` nel modello principale. +Se si importano i blocchi nel template genitore (cioè si usa `{import}` in `layout.latte`), i blocchi saranno disponibili anche in tutti i template figli, il che è molto pratico. -Il template importato non deve [estendere |#Layout Inheritance] un altro template e il suo corpo deve essere vuoto. Tuttavia, il template importato può importare altri template. +Il template destinato all'importazione (ad es. `blocks.latte`) non deve [estendere |#Ereditarietà del layout layout] un altro template, cioè usare `{layout}`. Tuttavia, può importare altri template. -Il tag `{import}` deve essere il primo tag del template dopo `{layout}`. Il nome del template può essere una qualsiasi espressione PHP: +Il tag `{import}` dovrebbe essere il primo tag del template dopo `{layout}`. Il nome del template può essere qualsiasi espressione PHP: ```latte {import $ajax ? 'ajax.latte' : 'not-ajax.latte'} ``` -Si possono usare tutte le dichiarazioni `{import}` che si vogliono in un dato template. Se due template importati definiscono lo stesso blocco, vince il primo. Tuttavia, la massima priorità è data al modello principale, che può sovrascrivere qualsiasi blocco importato. +È possibile utilizzare quanti comandi `{import}` si desidera nel template. Se due template importati definiscono lo stesso blocco, vince il primo. Tuttavia, la priorità più alta spetta al template principale, che può sovrascrivere qualsiasi blocco importato. -Tutti i blocchi sovrascritti possono essere inclusi gradualmente inserendoli come [blocco padre |#parent block]: +Il contenuto dei blocchi sovrascritti può essere preservato includendo il blocco nello stesso modo in cui viene incluso il [#blocco genitore]: ```latte -{layout 'base.latte'} +{layout 'layout.latte'} {import 'blocks.latte'} @@ -481,17 +470,17 @@ Tutti i blocchi sovrascritti possono essere inclusi gradualmente inserendoli com {block content}...{/block} ``` -In questo esempio, `{include parent}` richiamerà correttamente il blocco `sidebar` dal modello `blocks.latte`. +In questo esempio, `{include parent}` chiama il blocco `sidebar` dal template `blocks.latte`. -Ereditarietà delle unità `{embed}` .{toc: Unit Inheritance}{data-version:2.9} -============================================================================= +Ereditarietà unitaria `{embed}` +=============================== -L'ereditarietà delle unità porta l'idea dell'ereditarietà del layout al livello dei frammenti di contenuto. Mentre l'ereditarietà del layout funziona con gli "scheletri del documento", che vengono portati in vita da modelli figli, l'ereditarietà delle unità consente di creare scheletri per unità di contenuto più piccole e di riutilizzarle dove si vuole. +L'ereditarietà unitaria estende l'idea dell'ereditarietà del layout al livello dei frammenti di contenuto. Mentre l'ereditarietà del layout lavora con lo "scheletro del documento" che viene animato dai template figli, l'ereditarietà unitaria consente di creare scheletri per unità di contenuto più piccole e riutilizzarle ovunque si desideri. -Nell'ereditarietà delle unità il tag `{embed}` è la chiave. Combina il comportamento di `{include}` e `{layout}`. Permette di includere il contenuto di un altro template o blocco e di passare opzionalmente delle variabili, proprio come fa `{include}`. Permette anche di sovrascrivere qualsiasi blocco definito all'interno del template incluso, come fa `{layout}`. +Nell'ereditarietà unitaria, la chiave è il tag `{embed}`. Combina il comportamento di `{include}` e `{layout}`. Consente di incorporare il contenuto di un altro template o blocco e passare opzionalmente variabili, proprio come nel caso di `{include}`. Consente inoltre di sovrascrivere qualsiasi blocco definito all'interno del template incorporato, come quando si utilizza `{layout}`. -Per esempio, utilizzeremo l'elemento fisarmonica pieghevole. Diamo un'occhiata allo scheletro dell'elemento nel template `collapsible.latte`: +Ad esempio, utilizziamo un elemento accordion. Vediamo lo scheletro dell'elemento salvato nel template `collapsible.latte`: ```latte
                                                                              @@ -505,9 +494,9 @@ Per esempio, utilizzeremo l'elemento fisarmonica pieghevole. Diamo un'occhiata a
                                                                              ``` -Il tag `{block}` definisce due blocchi che i template figli possono riempire. Sì, come nel caso del template genitore nel template di ereditarietà del layout. Si veda anche la variabile `$modifierClass`. +I tag `{block}` definiscono due blocchi che i template figli possono riempire. Sì, come nel caso del template genitore nell'ereditarietà del layout. Vedete anche la variabile `$modifierClass`. -Utilizziamo il nostro elemento nel template. Qui entra in gioco `{embed}`. È un kit potentissimo che ci permette di fare tutto: includere il contenuto del template dell'elemento, aggiungere variabili e aggiungere blocchi con HTML personalizzato: +Utilizziamo il nostro elemento nel template. Qui entra in gioco `{embed}`. È un tag estremamente potente che ci consente di fare tutte queste cose: incorporare il contenuto del template dell'elemento, aggiungervi variabili e aggiungervi blocchi con il nostro HTML personalizzato: ```latte {embed 'collapsible.latte', modifierClass: my-style} @@ -522,7 +511,7 @@ Utilizziamo il nostro elemento nel template. Qui entra in gioco `{embed}`. È un {/embed} ``` -L'output potrebbe assomigliare a: +L'output potrebbe assomigliare a questo: ```latte
                                                                              @@ -537,7 +526,7 @@ L'output potrebbe assomigliare a:
                                                                              ``` -I blocchi all'interno dei tag embed formano un livello separato, indipendente dagli altri blocchi. Pertanto, possono avere lo stesso nome del blocco fuori dal tag embed e non sono influenzati in alcun modo. Usando il tag [include |#Printing Blocks] all'interno dei tag `{embed}` si possono inserire blocchi creati qui, blocchi dal template incorporato (che *non* sono [locali |#Local Blocks]) e anche blocchi dal template principale che *sono* locali. Si possono anche [importare blocchi |#Horizontal Reuse] da altri file: +I blocchi all'interno dei tag incorporati formano un livello separato indipendente dagli altri blocchi. Pertanto, possono avere lo stesso nome di un blocco al di fuori dell'incorporamento e non ne sono influenzati in alcun modo. Utilizzando il tag [include |#Rendering dei blocchi include] all'interno dei tag `{embed}`, è possibile includere i blocchi creati qui, i blocchi dal template incorporato (che *non sono* [locali |#Blocchi locali]) e anche i blocchi dal template principale, che invece *sono* locali. È anche possibile [importare blocchi |#Riutilizzo orizzontale import] da altri file: ```latte {block outer}…{/block} @@ -549,18 +538,18 @@ I blocchi all'interno dei tag embed formano un livello separato, indipendente da {block inner}…{/block} {block title} - {include inner} {* funziona, il blocco è definito all'interno dell'embed *} - {include hello} {* funziona, il blocco è locale in questo template *} - {include content} {* funziona, il blocco è definito nel template incorporato *} - {include aBlockDefinedInImportedTemplate} {* funziona *} - {include outer} {* non funziona! - il blocco è nel livello esterno *} + {include inner} {* works, block is defined inside embed *} + {include hello} {* works, block is local in this template *} + {include content} {* works, block is defined in embedded template *} + {include aBlockDefinedInImportedTemplate} {* works *} + {include outer} {* does not work! - block is in outer layer *} {/block} {/embed} ``` I template incorporati non hanno accesso alle variabili del contesto attivo, ma hanno accesso alle variabili globali. -Con `{embed}` è possibile inserire non solo modelli, ma anche altri blocchi, per cui l'esempio precedente potrebbe essere scritto in questo modo: .{data-version:2.10} +Utilizzando `{embed}` è possibile includere non solo template, ma anche altri blocchi, e quindi l'esempio precedente potrebbe essere scritto in questo modo: ```latte {define collapsible} @@ -581,23 +570,23 @@ Con `{embed}` è possibile inserire non solo modelli, ma anche altri blocchi, pe {/embed} ``` -Se si passa un'espressione a `{embed}` e non è chiaro se si tratta di un blocco o di un nome di file, si aggiunge la parola chiave `block` o `file`: +Se passiamo un'espressione a `{embed}` e non è chiaro se si tratti del nome di un blocco o di un file, aggiungiamo la parola chiave `block` o `file`: ```latte {embed block $name} ... {/embed} ``` -Casi d'uso .[#toc-use-cases] -============================ +Casi d'uso +========== -In Latte esistono vari tipi di ereditarietà e di riutilizzo del codice. Riassumiamo i concetti principali per una maggiore chiarezza: +In Latte esistono diversi tipi di ereditarietà e riutilizzo del codice. Riassumiamo i concetti principali per una maggiore chiarezza: `{include template}` -------------------- -**Caso d'uso:** Utilizzo di `header.latte` e `footer.latte` all'interno di `layout.latte`. +**Caso d'uso**: Utilizzo di `header.latte` e `footer.latte` all'interno di `layout.latte`. `header.latte` @@ -698,7 +687,7 @@ In Latte esistono vari tipi di ereditarietà e di riutilizzo del codice. Riassum `{define}` ---------- -**Caso d'uso**: Una funzione che ottiene alcune variabili e produce un markup. +**Caso d'uso**: Funzioni a cui passiamo variabili e che renderizzano qualcosa. `form.latte` @@ -724,7 +713,7 @@ In Latte esistono vari tipi di ereditarietà e di riutilizzo del codice. Riassum `{embed}` --------- -**Caso d'uso**: Incorporare `pagination.latte` in `product.table.latte` e `service.table.latte`. +**Caso d'uso**: Inserimento di `pagination.latte` in `product.table.latte` e `service.table.latte`. `pagination.latte` diff --git a/latte/it/type-system.texy b/latte/it/type-system.texy index 230940e0b7..a5fb971b3c 100644 --- a/latte/it/type-system.texy +++ b/latte/it/type-system.texy @@ -1,27 +1,27 @@ -Sistema di tipizzazione -*********************** +Sistema dei Tipi +**************** -
                                                                              +
                                                                              -Il sistema di tipi è l'elemento principale per lo sviluppo di applicazioni robuste. Latte offre il supporto dei tipi ai modelli. Conoscere il tipo di dato o di oggetto di ciascuna variabile permette di +Il sistema dei tipi è fondamentale per lo sviluppo di applicazioni robuste. Latte porta il supporto dei tipi anche nei template. Sapendo quale tipo di dato o oggetto è presente in ogni variabile, -- all'IDE di completare correttamente l'autocompletamento (vedere [integrazione e plugin |recipes#Editors and IDE]) -- analisi statica per rilevare gli errori +- l'IDE può suggerire correttamente (vedi [integrazione |recipes#Editor e IDE]) +- l'analisi statica può rilevare errori -Due punti che migliorano significativamente la qualità e la convenienza dello sviluppo. +Entrambi aumentano significativamente la qualità e la comodità dello sviluppo.
                                                                              .[note] -I tipi dichiarati sono informativi e Latte non li controlla in questo momento. +I tipi dichiarati sono informativi e Latte attualmente non li controlla. -Come iniziare a usare i tipi? Creare una classe modello, ad esempio `CatalogTemplateParameters`, che rappresenti i parametri passati: +Come iniziare a usare i tipi? Create una classe di template, ad es. `CatalogTemplateParameters`, che rappresenti i parametri passati, i loro tipi e, facoltativamente, i valori predefiniti: ```php class CatalogTemplateParameters { public function __construct( - public string $langs, + public string $lang, /** @var ProductEntity[] */ public array $products, public Address $address, @@ -35,19 +35,16 @@ $latte->render('template.latte', new CatalogTemplateParameters( )); ``` -Inserire quindi il tag `{templateType}` con il nome completo della classe (incluso lo spazio dei nomi) all'inizio del template. Questo definisce che ci sono variabili `$langs` e `$products` nel template, compresi i tipi corrispondenti. -Si possono anche specificare i tipi di variabili locali usando i tag [`{var}` |tags#var-default], `{varType}` e [`{define}` |template-inheritance#definitions]. +Quindi, all'inizio del template, inserite il tag `{templateType}` con il nome completo della classe (incluso il namespace). Questo definisce che nel template ci sono le variabili `$lang` e `$products` con i rispettivi tipi. Potete specificare i tipi delle variabili locali usando i tag [`{var}` |tags#var default], `{varType}`, [`{define}` |template-inheritance#Definizioni define]. -Ora l'IDE è in grado di effettuare correttamente il completamento automatico. +Da quel momento, l'IDE può suggerire correttamente. -Come salvare il lavoro? Come scrivere una classe template o i tag di `{varType}` nel modo più semplice possibile? Generarli. -Questo è esattamente ciò che fa la coppia di tag `{templatePrint}` e `{varPrint}`. -Se si inserisce uno di questi tag in un modello, il codice della classe o del modello viene visualizzato invece del normale rendering. È sufficiente selezionare e copiare il codice nel progetto. +Come risparmiare lavoro? Qual è il modo più semplice per scrivere una classe con i parametri del template o i tag `{varType}`? Fateli generare. A questo servono i due tag `{templatePrint}` e `{varPrint}`. Se li inserite nel template, invece del rendering normale, verrà visualizzata una bozza del codice della classe o un elenco di tag `{varType}`. Basta quindi selezionare il codice con un clic e copiarlo nel progetto. `{templateType}` ---------------- -I tipi di parametri passati al template sono dichiarati usando la classe: +Dichiariamo i tipi dei parametri passati al template usando una classe: ```latte {templateType MyApp\CatalogTemplateParameters} @@ -56,7 +53,7 @@ I tipi di parametri passati al template sono dichiarati usando la classe: `{varType}` ----------- -Come dichiarare i tipi di variabili? A questo scopo, utilizzare il tag `{varType}` per una variabile esistente, oppure [`{var}` |tags#var-default]: +Come dichiarare i tipi delle variabili? A questo servono i tag `{varType}` per le variabili esistenti, o [`{var}` |tags#var default]: ```latte {varType Nette\Security\User $user} @@ -66,11 +63,11 @@ Come dichiarare i tipi di variabili? A questo scopo, utilizzare il tag `{varType `{templatePrint}` ----------------- -È possibile generare questa classe anche utilizzando il tag `{templatePrint}`. Se lo si posiziona all'inizio del template, il codice della classe viene visualizzato al posto del template normale. È sufficiente selezionare e copiare il codice nel progetto. +Potete anche far generare la classe usando il tag `{templatePrint}`. Se lo inserite all'inizio del template, invece del rendering normale, verrà visualizzata una bozza della classe. Basta quindi selezionare il codice con un clic e copiarlo nel progetto. `{varPrint}` ------------ -Il tag `{varPrint}` consente di risparmiare tempo. Se lo si inserisce in un modello, viene visualizzato l'elenco dei tag `{varType}` invece del normale rendering. È sufficiente selezionare e copiare il codice nel modello. +Il tag `{varPrint}` vi farà risparmiare tempo nella scrittura. Se lo inserite nel template, invece del rendering normale, verrà visualizzata una bozza dei tag `{varType}` per le variabili locali. Basta quindi selezionare il codice con un clic e copiarlo nel template. -Il tag `{varPrint}` elenca le variabili locali che non sono parametri del template. Se si desidera elencare tutte le variabili, usare `{varPrint all}`. +`{varPrint}` da solo elenca solo le variabili locali che non sono parametri del template. Se volete elencare tutte le variabili, usate `{varPrint all}`. diff --git a/latte/it/why-use.texy b/latte/it/why-use.texy new file mode 100644 index 0000000000..26e9fb9303 --- /dev/null +++ b/latte/it/why-use.texy @@ -0,0 +1,80 @@ +Perché usare i template? +************************ + + +Perché dovrei usare un sistema di template in PHP? +-------------------------------------------------- + +Perché usare un sistema di template in PHP, quando PHP stesso è un linguaggio di template? + +Ripercorriamo brevemente la storia di questo linguaggio, che è piena di colpi di scena interessanti. Uno dei primi linguaggi di programmazione utilizzati per generare pagine HTML è stato il linguaggio C. Tuttavia, si è presto rivelato poco pratico per questo scopo. Rasmus Lerdorf ha quindi creato PHP, che ha facilitato la generazione di HTML dinamico con il linguaggio C nel backend. PHP è stato quindi originariamente progettato come linguaggio di template, ma nel tempo ha acquisito ulteriori funzionalità ed è diventato un linguaggio di programmazione completo. + +Tuttavia, funziona ancora anche come linguaggio di template. In un file PHP può essere scritta una pagina HTML, in cui le variabili vengono visualizzate usando ``, ecc. + +Già agli albori della storia di PHP è nato il sistema di template Smarty, il cui scopo era separare rigorosamente l'aspetto (HTML/CSS) dalla logica applicativa. Quindi, forniva intenzionalmente un linguaggio più limitato rispetto a PHP stesso, in modo che lo sviluppatore non potesse, ad esempio, eseguire una query al database dal template, ecc. D'altra parte, rappresentava un'ulteriore dipendenza nei progetti, aumentava la loro complessità e i programmatori dovevano imparare un nuovo linguaggio Smarty. Tale vantaggio era discutibile e si continuò a utilizzare PHP semplice per i template. + +Nel corso del tempo, i sistemi di template hanno iniziato a diventare utili. Hanno introdotto il concetto di [ereditarietà |template-inheritance], la [modalità sandbox|sandbox] e una serie di altre funzionalità che hanno semplificato notevolmente la creazione di template rispetto a PHP puro. È emerso il tema della sicurezza, l'esistenza di [vulnerabilità come XSS|safety-first] e la necessità di [escaping |#Cos è l escaping]. I sistemi di template hanno introdotto l'autoescaping per eliminare il rischio che il programmatore se ne dimenticasse e si creasse una grave falla di sicurezza (tra poco vedremo che questo presenta alcuni tranelli). + +I vantaggi dei sistemi di template oggi superano di gran lunga i costi associati alla loro implementazione. Pertanto, ha senso utilizzarli. + + +Perché Latte è migliore di Twig o Blade? +---------------------------------------- + +Ci sono diverse ragioni - alcune sono piacevoli e altre fondamentalmente utili. Latte è una combinazione di piacevole e utile. + +*Prima quella piacevole:* Latte ha la stessa [sintassi di PHP |syntax#Latte capisce PHP]. Differisce solo la scrittura dei tag, invece di `` preferisce i più brevi `{` e `}`. Ciò significa che non è necessario imparare un nuovo linguaggio. I costi di formazione sono minimi. E soprattutto, durante lo sviluppo non è necessario "passare" continuamente tra il linguaggio PHP e il linguaggio del template, poiché sono entrambi uguali. A differenza dei template Twig, che utilizzano il linguaggio Python, e il programmatore deve quindi passare tra due linguaggi diversi. + +*E ora la ragione estremamente utile*: Tutti i sistemi di template, come Twig, Blade o Smarty, hanno introdotto nel corso dell'evoluzione la protezione contro XSS sotto forma di [escaping |#Cos è l escaping] automatico. Più precisamente, la chiamata automatica della funzione `htmlspecialchars()`. Ma i creatori di Latte si sono resi conto che questa non era affatto la soluzione giusta. Perché in diverse parti del documento l'escaping avviene in modi diversi. L'autoescaping ingenuo è una funzione pericolosa perché crea un falso senso di sicurezza. + +Affinché l'autoescaping sia funzionale e affidabile, deve riconoscere in quale punto del documento vengono visualizzati i dati (li chiamiamo contesti) e scegliere la funzione di escaping in base ad esso. Quindi deve essere [sensibile al contesto |safety-first#Escaping sensibile al contesto]. E questo è proprio ciò che Latte sa fare. Capisce l'HTML. Non percepisce il template solo come una stringa di caratteri, ma capisce cosa sono i tag, gli attributi, ecc. E quindi esegue l'escaping in modo diverso nel testo HTML, diversamente all'interno di un tag HTML, diversamente all'interno di JavaScript, ecc. + +Latte è il primo e unico sistema di template in PHP ad avere l'escaping sensibile al contesto. Rappresenta quindi l'unico sistema di template veramente sicuro. + +*E un'altra ragione piacevole*: Grazie al fatto che Latte capisce l'HTML, offre altre funzionalità molto piacevoli. Ad esempio, gli [n:attributi |syntax#n:attributi]. O la capacità di [controllare i link |safety-first#Controllo dei link]. E molte altre. + + +Cos'è l'escaping? +----------------- + +L'escaping è il processo che consiste nel sostituire i caratteri con un significato speciale con sequenze corrispondenti quando si inserisce una stringa in un'altra, per evitare fenomeni indesiderati o errori. Ad esempio, quando inseriamo una stringa in un testo HTML, in cui il carattere `<` ha un significato speciale, poiché indica l'inizio di un tag, lo sostituiamo con la sequenza corrispondente, che è l'entità HTML `<`. Grazie a ciò, il browser visualizzerà correttamente il simbolo `<`. + +Un semplice esempio di escaping direttamente durante la scrittura del codice in PHP è l'inserimento di virgolette in una stringa, anteponendo ad esse una barra rovesciata. + +Analizziamo l'escaping in modo più dettagliato nel capitolo [Come difendersi da XSS |safety-first#Come difendersi da XSS]. + + +È possibile eseguire una query al database dal template in Latte? +----------------------------------------------------------------- + +Nei template è possibile lavorare con oggetti che il programmatore passa loro. Se quindi il programmatore lo desidera, può passare al template un oggetto database ed eseguire una query su di esso. Se ha tale intenzione, non c'è motivo di impedirglielo. + +Una situazione diversa si verifica se si desidera dare la possibilità di modificare i template a clienti o codificatori esterni. In tal caso, sicuramente non si desidera che abbiano accesso al database. Ovviamente non si passerà l'oggetto database al template, ma cosa succede se è possibile accedervi tramite un altro oggetto? La soluzione è la [modalità sandbox|sandbox], che consente di definire quali metodi possono essere chiamati nei template. Grazie a ciò, non è necessario preoccuparsi di violazioni della sicurezza. + + +Quali sono le principali differenze tra sistemi di template come Latte, Twig e Blade? +------------------------------------------------------------------------------------- + +Le differenze tra i sistemi di template Latte, Twig e Blade risiedono principalmente nella sintassi, nella sicurezza e nel modo di integrazione nei framework + +- Latte: utilizza la sintassi del linguaggio PHP, il che facilita l'apprendimento e l'uso. Fornisce una protezione di prim'ordine contro gli attacchi XSS. +- Twig: utilizza la sintassi del linguaggio Python, che differisce notevolmente da PHP. Esegue l'escaping senza distinzione di contesto. È ben integrato nel framework Symfony. +- Blade: utilizza un mix di PHP e sintassi propria. Esegue l'escaping senza distinzione di contesto. È strettamente integrato con le funzioni e l'ecosistema di Laravel. + + +Conviene alle aziende utilizzare un sistema di template? +-------------------------------------------------------- + +Innanzitutto, i costi associati alla formazione, all'uso e al beneficio complessivo variano significativamente a seconda del sistema. Il sistema di template Latte, grazie al fatto che utilizza la sintassi PHP, semplifica notevolmente l'apprendimento per i programmatori già familiari con questo linguaggio. Di solito ci vogliono poche ore prima che un programmatore familiarizzi sufficientemente con Latte. Riduce quindi i costi di formazione. Allo stesso tempo, accelera l'adozione della tecnologia e soprattutto l'efficienza nell'uso quotidiano. + +Inoltre, Latte fornisce un elevato livello di protezione contro la vulnerabilità XSS grazie alla tecnologia unica di escaping sensibile al contesto. Questa protezione è fondamentale per garantire la sicurezza delle applicazioni web e minimizzare il rischio di attacchi che potrebbero mettere in pericolo gli utenti o i dati aziendali. La protezione della sicurezza delle applicazioni web è importante anche per mantenere una buona reputazione dell'azienda. I problemi di sicurezza possono causare la perdita di fiducia da parte dei clienti e danneggiare la reputazione dell'azienda sul mercato. + +L'uso di Latte riduce anche i costi complessivi di sviluppo e manutenzione dell'applicazione facilitando entrambi. L'uso di un sistema di template è quindi decisamente conveniente. + + +Latte influisce sulle prestazioni delle applicazioni web? +--------------------------------------------------------- + +Sebbene i template Latte vengano elaborati rapidamente, questo aspetto in realtà non ha importanza. Il motivo è che l'analisi dei file avviene solo una volta alla prima visualizzazione. Successivamente vengono compilati in codice PHP, salvati su disco ed eseguiti ad ogni richiesta successiva, senza la necessità di eseguire nuovamente la compilazione. + +Questo è il modo in cui funziona in un ambiente di produzione. Durante lo sviluppo, i template Latte vengono ricompilati ogni volta che il loro contenuto viene modificato, in modo che lo sviluppatore veda sempre la versione attuale. diff --git a/latte/ja/@home.texy b/latte/ja/@home.texy index 433a6cb1d7..f113076618 100644 --- a/latte/ja/@home.texy +++ b/latte/ja/@home.texy @@ -1 +1,2 @@ -{{maintitle:Latte - 最も安全で真に直感的なPHP用テンプレート}} +{{maintitle: Latte – PHP用の最も安全で本当に直感的なテンプレート}} +{{description: LatteはPHP用の最も安全なテンプレートシステムです。多くのセキュリティ脆弱性を防ぎます。直感的な構文と多くの便利な機能に感謝するでしょう。}} diff --git a/latte/ja/@left-menu.texy b/latte/ja/@left-menu.texy index 0f32757aea..4813f4e4e9 100644 --- a/latte/ja/@left-menu.texy +++ b/latte/ja/@left-menu.texy @@ -1,24 +1,24 @@ -- [はじめに |Guide] -- コンセプト - - [安全第一 |Safety First] +- [Latte を始めましょう |guide] +- [なぜテンプレートを使うのですか? |why-use] +- コンセプト ⚗️ + - [セキュリティ第一 |safety-first] - [テンプレート継承 |Template Inheritance] - - [型システム |Type System] - - [サンドボックス |Sandbox] + - [型システム |type-system] + - [Sandbox |Sandbox] -- 設計者向け - - [構文 |Syntax] - - [タグ |Tags] - - [フィルター |Filters] - - [関数 |Functions] +- デザイナー向け 🎨 + - [構文 |syntax] + - [タグ |tags] + - [フィルタ |filters] + - [関数 |functions] - [ヒントとコツ |recipes] -- 開発者向け - - [開発者の ためのプラクティス |develop] - - [Latteを拡張する |Extending Latte] - - [拡張機能の 作成 |creating-extension] +- 開発者向け 🧮 + - [開発手順 |develop] + - [Latte の拡張 |extending-latte] -- [クックブック |cookbook/@home] - - [Twigからの移行 |cookbook/migration-from-twig] - - [... もっと見る |cookbook/@home] +- [ガイドとベストプラクティス 💡|cookbook/@home] + - [Twig からの移行 |cookbook/migration-from-twig] + - [… その他 |cookbook/@home] - "プレイグラウンド .[link-external]":https://fiddle.nette.org/latte/ .{padding-top:1em} diff --git a/latte/ja/@menu.texy b/latte/ja/@menu.texy index 01b49ef259..8b6d85a875 100644 --- a/latte/ja/@menu.texy +++ b/latte/ja/@menu.texy @@ -1,12 +1,12 @@
                                                                                -- [ホーム |@home] -- [ドキュメント |Guide] +- [はじめに |@home] +- [ドキュメント |guide] - "GitHub .[link-external]":https://github.com/nette/latte
                                                                              diff --git a/latte/ja/@meta.texy b/latte/ja/@meta.texy new file mode 100644 index 0000000000..70f3f2da3d --- /dev/null +++ b/latte/ja/@meta.texy @@ -0,0 +1 @@ +{{sitename: Latte ドキュメンテーション}} diff --git a/latte/ja/compiler-passes.texy b/latte/ja/compiler-passes.texy new file mode 100644 index 0000000000..6db3a7b33c --- /dev/null +++ b/latte/ja/compiler-passes.texy @@ -0,0 +1,555 @@ +コンパイルパス +******* + +.[perex] +コンパイルパスは、Latteテンプレートを抽象構文木(AST)に解析した後、最終的なPHPコードを生成する前に、テンプレートを分析および変更するための強力なメカニズムを提供します。これにより、テンプレートの高度な操作、最適化、セキュリティチェック(Sandboxなど)、およびテンプレートに関する情報の収集が可能になります。このガイドでは、独自のコンパイルパスの作成について説明します。 + + +コンパイルパスとは? +========== + +コンパイルパスの役割を理解するために、[Latteのコンパイルプロセス |custom-tags#コンパイルプロセスの理解]をご覧ください。ご覧のとおり、コンパイルパスは重要な段階で動作し、最初の解析と最終的なコード出力の間に深い介入を可能にします。 + +基本的に、コンパイルパスは単なるPHP callableオブジェクト(関数、静的メソッド、またはインスタンスメソッドなど)であり、単一の引数を受け取ります:テンプレートのルートASTノード。これは常に`Latte\Compiler\Nodes\TemplateNode`のインスタンスです。 + +コンパイルパスの主な目的は、通常、次の1つまたは両方です: + +- 分析:ASTを走査し、テンプレートに関する情報を収集します(例:定義されたすべてのブロック を見つける、特定のタグの使用を確認する、特定のセキュリティ制約が満たされていることを確認する)。 +- 変更:ASTの構造またはノードの属性を変更します(例:HTML属性を自動的に追加する、特定のタグの組み合わせを最適化する、古いタグを新しいタグに置き換える、サンドボックスルールを実装する)。 + + +登録 +======================= + +コンパイルパスは、[拡張機能 |extending-latte#getPasses]の`getPasses()`メソッドを使用して登録されます。このメソッドは連想配列を返します。キーはパスの一意の名前(内部で使用され、ソートに使用されます)であり、値はパスのロジックを実装するPHP callableオブジェクトです。 + +```php +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Extension; + +class MyExtension extends Extension +{ + public function getPasses(): array + { + return [ + 'modificationPass' => $this->modifyTemplateAst(...), + // ... その他のパス ... + ]; + } + + public function modifyTemplateAst(TemplateNode $templateNode): void + { + // 実装... + } +} +``` + +Latteの基本拡張機能と独自の拡張機能によって登録されたパスは順次実行されます。1つのパスが別のパスの結果または変更に依存する場合、順序が重要になることがあります。Latteは、必要に応じてこの順序を制御するための補助メカニズムを提供します。詳細については、[`Extension::getPasses()` |extending-latte#getPasses]のドキュメントを参照してください。 + + +ASTの例 +===== + +ASTのより良いイメージを得るために、例を追加します。これはソーステンプレートです: + +```latte +{foreach $category->getItems() as $item} +
                                                                            • {$item->name|upper}
                                                                            • + {else} + no items found +{/foreach} +``` + +そして、これはAST形式でのその表現です: + +/--pre +Latte\Compiler\Nodes\TemplateNode( + Latte\Compiler\Nodes\FragmentNode( + - Latte\Essential\Nodes\ForeachNode( + expression: Latte\Compiler\Nodes\Php\Expression\MethodCallNode( + object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$category') + name: Latte\Compiler\Nodes\Php\IdentifierNode('getItems') + ) + value: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') + content: Latte\Compiler\Nodes\FragmentNode( + - Latte\Compiler\Nodes\TextNode(' ') + - Latte\Compiler\Nodes\Html\ElementNode('li')( + content: Latte\Essential\Nodes\PrintNode( + expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode( + object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') + name: Latte\Compiler\Nodes\Php\IdentifierNode('name') + ) + modifier: Latte\Compiler\Nodes\Php\ModifierNode( + filters: + - Latte\Compiler\Nodes\Php\FilterNode('upper') + ) + ) + ) + ) + else: Latte\Compiler\Nodes\FragmentNode( + - Latte\Compiler\Nodes\TextNode('no items found') + ) + ) + ) +) +\-- + + +`NodeTraverser` を使用したASTの走査 +=========================== + +複雑なAST構造を走査するために再帰関数を手動で記述するのは、退屈でエラーが発生しやすいです。Latteはこの目的のために特別なツールを提供しています:[api:Latte\Compiler\NodeTraverser]。このクラスは[Visitorデザインパターン |https://en.wikipedia.org/wiki/Visitor_pattern]を実装しており、ASTの走査を体系的かつ管理しやすくします。 + +基本的な使用法は、`NodeTraverser` のインスタンスを作成し、その `traverse()` メソッドを呼び出し、ASTのルートノードと1つまたは2つの「ビジター」callableオブジェクトを渡すことです: + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; + +(new NodeTraverser)->traverse( + $templateNode, + + // 'enter' ビジター:ノードに入るとき(その子の前)に呼び出されます + enter: function (Node $node) { + echo "ノードタイプへの入力: " . $node::class . "\n"; + // ここでノードを調べることができます + if ($node instanceof Nodes\TextNode) { + // echo "見つかったテキスト: " . $node->content . "\n"; + } + }, + + // 'leave' ビジター:ノードを離れるとき(その子の後)に呼び出されます + leave: function (Node $node) { + echo "ノードタイプからの退出: " . $node::class . "\n"; + // ここで子の処理後にアクションを実行できます + }, +); +``` + +ニーズに応じて、`enter` ビジターのみ、`leave` ビジターのみ、または両方を提供できます。 + +**`enter(Node $node)`:** この関数は、トラバーサーがこのノードの子のいずれかを訪問する**前**に各ノードに対して実行されます。以下に役立ちます: + +- ツリーを下に移動しながら情報を収集する。 +- 子を処理する**前**に決定を行う([#走査の最適化]で説明されているように、それらをスキップすることを決定するなど)。 +- 子を訪問する前にノードを潜在的に変更する(あまり一般的ではありません)。 + +**`leave(Node $node)`:** この関数は、すべての子(およびそれらの完全なサブツリー)が完全に訪問された(入力と退出の両方)**後**に各ノードに対して実行されます。以下に最も一般的な場所です: + +`enter` と `leave` の両方のビジターは、オプションで値を返して走査プロセスに影響を与えることができます。`null`(または何も)を返すと、通常どおり走査が続行されます。`Node` のインスタンスを返すと、現在のノードが置き換えられます。`NodeTraverser::RemoveNode` や `NodeTraverser::StopTraversal` などの特別な定数を返すと、次のセクションで説明するようにフローが変更されます。 + + +走査の仕組み +------ + +`NodeTraverser` は内部的に `getIterator()` メソッドを使用します。これは、各 `Node` クラスが実装する必要があるものです([カスタムタグの作成 |custom-tags#サブノードのための getIterator の実装]で説明されています)。`getIterator()` を使用して取得した子を反復処理し、それらに対して再帰的に `traverse()` を呼び出し、`enter` と `leave` のビジターがイテレータを介してアクセス可能なツリー内の各ノードに対して正しい深さ優先順序で呼び出されることを保証します。これは、カスタムタグノードで正しく実装された `getIterator()` がコンパイルパスが正しく機能するために絶対に不可欠である理由を再度強調しています。 + +テンプレート内で `{do}` タグ(`Latte\Essential\Nodes\DoNode` で表される)が何回使用されているかをカウントする簡単なパスを作成しましょう。 + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Essential\Nodes\DoNode; + +function countDoTags(TemplateNode $templateNode): void +{ + $count = 0; + (new NodeTraverser)->traverse( + $templateNode, + enter: function (Node $node) use (&$count): void { + if ($node instanceof DoNode) { + $count++; + } + }, + // このタスクには 'leave' ビジターは必要ありません + ); + + echo "{do} タグが $count 回見つかりました。\n"; +} + +$latte = new Latte\Engine; +$ast = $latte->parse($templateSource); +countDoTags($ast); +``` + +この例では、訪問した各ノードのタイプを確認するために `enter` ビジターのみが必要でした。 + +次に、これらのビジターが実際にASTをどのように変更するかを探ります。 + + +ASTの変更 +====== + +コンパイルパスの主な目的の1つは、抽象構文木を変更することです。これにより、PHPコードを生成する前に、テンプレート構造に対して強力な変換、最適化、またはルールの強制が可能になります。`NodeTraverser` は、`enter` および `leave` ビジター内でこれを達成するためのいくつかの方法を提供します。 + +**重要な注意:** ASTの変更には注意が必要です。基本的なノードの削除やノードを互換性のないタイプに置き換えるなど、不適切な変更は、コード生成中にエラーを引き起こしたり、実行時に予期しない動作を引き起こしたりする可能性があります。変更パスは常に徹底的にテストしてください。 + + +ノードプロパティの変更 +----------- + +ツリーを変更する最も簡単な方法は、走査中に訪問したノードの**パブリックプロパティ**を直接変更することです。すべてのノードは、解析された引数、コンテンツ、または属性をパブリックプロパティに格納します。 + +**例:** すべての静的テキストノード(`TextNode`、通常のHTMLまたはLatteタグ外のテキストを表す)を見つけ、その内容を*AST内で直接*大文字に変換するパスを作成しましょう。 + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\TextNode; + +function uppercaseStaticText(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // TextNodeには処理する子がないため、'enter'を使用できます + enter: function (Node $node) { + // このノードは静的テキストブロックですか? + if ($node instanceof TextNode) { + // はい!パブリックプロパティ 'content' を直接変更します。 + $node->content = mb_strtoupper(html_entity_decode($node->content)); + } + // 何も返す必要はありません。変更は直接適用されます。 + }, + ); +} +``` + +この例では、`enter` ビジターは現在の `$node` が `TextNode` タイプであるかどうかを確認します。そうであれば、`mb_strtoupper()` を使用してパブリックプロパティ `$content` を直接更新します。これにより、PHPコードを生成する*前*にASTに格納されている静的テキストの内容が直接変更されます。オブジェクトを直接変更しているため、ビジターから何も返す必要はありません。 + +効果:テンプレートに `

                                                                              Hello

                                                                              {= $var }World` が含まれていた場合、このパスの後、ASTは `

                                                                              HELLO

                                                                              {= $var }WORLD` のようなものを表します。これは`$var`の内容には影響しません。 + + +ノードの置換 +------ + +より強力な変更手法は、ノードを別のノードに完全に置き換えることです。これは、`enter` または `leave` ビジターから**新しい `Node` インスタンスを返す**ことによって行われます。`NodeTraverser` は、元のノードを親ノードの構造内で返されたノードに置き換えます。 + +**例:** 定数 `PHP_VERSION` のすべての使用(`ConstantFetchNode` で表される)を見つけ、それらを*コンパイル時*に検出された*実際の* PHPバージョンを含む文字列リテラル(`StringNode`)に直接置き換えるパスを作成しましょう。これはコンパイル時の最適化の一形態です。 + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\Php\Expression\ConstantFetchNode; +use Latte\Compiler\Nodes\Php\Scalar\StringNode; + +function inlinePhpVersion(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // 置換には 'leave' がよく使用され、子(存在する場合)が + // 最初に処理されることを保証しますが、ここでは 'enter' も機能します。 + leave: function (Node $node) { + // このノードは定数アクセスであり、定数名は 'PHP_VERSION' ですか? + if ($node instanceof ConstantFetchNode && (string) $node->name === 'PHP_VERSION') { + // 現在のPHPバージョンを含む新しいStringNodeを作成します + $newNode = new StringNode(PHP_VERSION); + + // オプションですが、良い習慣です:位置情報をコピーします + $newNode->position = $node->position; + + // 新しいStringNodeを返します。Traverserは + // 元のConstantFetchNodeをこの$newNodeに置き換えます。 + return $newNode; + } + // Nodeを返さない場合、元の$nodeは保持されます。 + }, + ); +} +``` + +ここでは、`leave` ビジターが `PHP_VERSION` の特定の `ConstantFetchNode` を識別します。次に、*コンパイル時*の `PHP_VERSION` 定数の値を含むまったく新しい `StringNode` を作成します。この `$newNode` を返すことで、トラバーサーに元の `ConstantFetchNode` をAST内で置き換えるように指示します。 + +効果:テンプレートに `{= PHP_VERSION }` が含まれており、コンパイルがPHP 8.2.1で実行されている場合、このパスの後のASTは効果的に `{= '8.2.1' }` を表します。 + +**置換のための `enter` vs. `leave` の選択:** + +- 新しいノードの作成が古いノードの子の処理結果に依存する場合、または単に子が置換前に訪問されることを保証したい場合(一般的な慣行)は、`leave` を使用します。 +- 子が訪問される*前*にノードを置き換えたい場合は、`enter` を使用します。 + + +ノードの削除 +------ + +ビジターから特別な定数 `NodeTraverser::RemoveNode` を返すことで、ASTからノードを完全に削除できます。 + +**例:** Latteコアによって生成されたAST内の `CommentNode` で表されるすべてのテンプレートコメント(`{* ... *}`)を削除しましょう(通常は以前に処理されますが、これは例として役立ちます)。 + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\CommentNode; + +function removeCommentNodes(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // コメントを削除するために子の情報は必要ないので、ここでは 'enter' で問題ありません + enter: function (Node $node) { + if ($node instanceof CommentNode) { + // このノードをASTから削除するようにトラバーサーに通知します + return NodeTraverser::RemoveNode; + } + }, + ); +} +``` + +**注意:** `RemoveNode` は慎重に使用してください。基本的なコンテンツを含むノードや構造に影響を与えるノード(サイクルのコンテンツノードの削除など)を削除すると、破損したテンプレートや無効な生成コードにつながる可能性があります。本当にオプションまたは自己完結型のノード(コメントやデバッグタグなど)や空の構造ノード(たとえば、空の `FragmentNode` は、クリーンアップパスによって一部のコンテキストで安全に削除できます)に対して最も安全です。 + +これら3つのメソッド - プロパティの変更、ノードの置換、ノードの削除 - は、コンパイルパス内でASTを操作するための基本的なツールを提供します。 + + +走査の最適化 +====== + +テンプレートのASTはかなり大きくなる可能性があり、潜在的に数千のノードを含むことがあります。パスがツリーの特定の部分にのみ関心がある場合、すべての個々のノードを走査することは不要であり、コンパイルパフォーマンスに影響を与える可能性があります。`NodeTraverser` は、走査を最適化する方法を提供します: + + +子のスキップ +------ + +特定のタイプのノードに遭遇したら、その子孫のいずれにも探しているノードが含まれていないことがわかっている場合は、トラバーサーにその子の訪問をスキップするように指示できます。これは、**`enter`** ビジターから定数 `NodeTraverser::DontTraverseChildren` を返すことによって行われます。これにより、走査中にブランチ全体が省略され、特にタグ内に複雑なPHP式を含むテンプレートで、かなりの時間を節約できる可能性があります。 + + +走査の停止 +----- + +パスが何か(特定のタイプのノード、条件の満たし)の*最初*の出現を見つけるだけでよい場合は、それを見つけたらすぐに走査プロセス全体を完全に停止できます。これは、`enter` または `leave` ビジターから定数 `NodeTraverser::StopTraversal` を返すことによって達成されます。`traverse()` メソッドは、それ以上のノードの訪問を停止します。潜在的に非常に大きなツリーで最初の一致のみが必要な場合に非常に効果的です。 + + +便利なヘルパー `NodeHelpers` +===================== + +`NodeTraverser` はきめ細かい制御を提供しますが、Latteは便利なヘルパークラス[api:Latte\Compiler\NodeHelpers]も提供しています。これは、いくつかの一般的な検索および分析タスクのために `NodeTraverser` をカプセル化し、多くの場合、より少ない定型コードで済みます。 + + +find(Node $startNode, callable $filter): array .[method] +-------------------------------------------------------- + +この静的メソッドは、`$startNode` で始まる(含む)サブツリー内の、コールバック `$filter` を満たすすべてのノードを見つけます。一致するノードの配列を返します。 + +**例:** テンプレート全体ですべての変数ノード(`VariableNode`)を見つけます。 + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Php\Expression\VariableNode; +use Latte\Compiler\Nodes\TemplateNode; + +function findAllVariables(TemplateNode $templateNode): array +{ + return NodeHelpers::find( + $templateNode, + fn($node) => $node instanceof VariableNode, + ); +} +``` + + +findFirst(Node $startNode, callable $filter): ?Node .[method] +-------------------------------------------------------------- + +`find` に似ていますが、コールバック `$filter` を満たす**最初**のノードが見つかった直後に走査を停止します。見つかった `Node` オブジェクトまたは一致するノードが見つからない場合は `null` を返します。これは基本的に `NodeTraverser::StopTraversal` の便利なラッパーです。 + +**例:** `{parameters}` ノードを見つけます(以前の手動の例と同じですが、より短いです)。 + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Essential\Nodes\ParametersNode; + +function findParametersNodeHelper(TemplateNode $templateNode): ?ParametersNode +{ + return NodeHelpers::findFirst( + $templateNode->head, // 効率のためにヘッドセクションのみを検索 + fn($node) => $node instanceof ParametersNode, + ); +} +``` + + +toValue(ExpressionNode $node, bool $constants = false): mixed .[method] +----------------------------------------------------------------------- + +この静的メソッドは、`ExpressionNode` を**コンパイル時**に評価し、対応するPHP値を返そうとします。単純なリテラルノード(`StringNode`、`IntegerNode`、`FloatNode`、`BooleanNode`、`NullNode`)およびそのような評価可能なアイテムのみを含む `ArrayNode` インスタンスに対してのみ確実に機能します。 + +`$constants` が `true` に設定されている場合、`defined()` をチェックし `constant()` を使用して `ConstantFetchNode` および `ClassConstantFetchNode` を解決しようとします。 + +ノードに変数が含まれている場合、関数呼び出し、またはその他の動的要素が含まれている場合、コンパイル時に評価できず、メソッドは `InvalidArgumentException` をスローします。 + +**使用例:** コンパイル時の決定のために、コンパイル中にタグ引数の静的な値を取得します。 + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Php\ExpressionNode; + +function getStaticStringArgument(ExpressionNode $argumentNode): ?string +{ + try { + $value = NodeHelpers::toValue($argumentNode); + return is_string($value) ? $value : null; + } catch (\InvalidArgumentException $e) { + // 引数は静的な文字列リテラルではありませんでした + return null; + } +} +``` + + +toText(?Node $node): ?string .[method] +-------------------------------------- + +この静的メソッドは、単純なノードからプレーンテキストコンテンツを抽出するのに役立ちます。主に以下で機能します: +- `TextNode`: その `$content` を返します。 +- `FragmentNode`: すべての子に対して `toText()` の結果を連結します。いずれかの子がテキストに変換できない場合(例:`PrintNode` を含む)、`null` を返します。 +- `NopNode`: 空の文字列を返します。 +- その他のノードタイプ:`null` を返します。 + +**使用例:** コンパイルパス中の分析のために、HTML属性値または単純なHTML要素の静的なテキストコンテンツを取得します。 + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Html\AttributeNode; + +function getStaticAttributeValue(AttributeNode $attr): ?string +{ + // $attr->value は通常 AreaNode (FragmentNode や TextNode など) です + return NodeHelpers::toText($attr->value); +} + +// パスでの使用例: +// if ($node instanceof Html\ElementNode && $node->name === 'meta') { +// $nameAttrValue = getStaticAttributeValue($node->getAttributeNode('name')); +// if ($nameAttrValue === 'description') { ... } +// } +``` + +`NodeHelpers` は、一般的なAST走査および分析タスクのための既製のソリューションを提供することで、コンパイルパスを簡素化できます。 + + +実践的な例 +===== + +いくつかの実践的な問題を解決するために、ASTの走査と変更の概念を適用しましょう。これらの例は、コンパイルパスで使用される一般的なパターンを示しています。 + + +`` に `loading="lazy"` を自動的に追加する +------------------------------------ + +最新のブラウザは、`loading="lazy"` 属性を使用して画像のネイティブ遅延読み込みをサポートしています。まだ `loading` 属性を持たないすべての `` タグにこの属性を自動的に追加するパスを作成しましょう。 + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; +use Latte\Compiler\Nodes\Html; + +function addLazyLoading(Nodes\TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // ノードを直接変更しており、この決定のために子に依存しないため、'enter' を使用できます。 + // + enter: function (Node $node) { + // これは名前が 'img' のHTML要素ですか? + if ($node instanceof Html\ElementNode && $node->name === 'img') { + // 属性ノードが存在することを確認します + $node->attributes ??= new Nodes\FragmentNode; + + // 'loading' 属性がすでに存在するかどうかを確認します(大文字と小文字を区別しない) + foreach ($node->attributes->children as $attrNode) { + if ($attrNode instanceof Html\AttributeNode + && $attrNode->name instanceof Nodes\TextNode // 静的属性名 + && strtolower($attrNode->name->content) === 'loading' + ) { + return; + } + } + + // 属性が空でない場合はスペースを追加します + if ($node->attributes->children) { + $node->attributes->children[] = new Nodes\TextNode(' '); + } + + // 新しい属性ノードを作成します:loading="lazy" + $node->attributes->children[] = new Html\AttributeNode( + name: new Nodes\TextNode('loading'), + value: new Nodes\TextNode('lazy'), + quote: '"', + ); + // 変更はオブジェクトに直接適用されるため、何も返す必要はありません。 + } + }, + ); +} +``` + +説明: +- `enter` ビジターは、名前が `img` の `Html\ElementNode` ノードを探します。 +- 既存の属性(`$node->attributes->children`)を反復処理し、`loading` 属性がすでに存在するかどうかを確認します。 +- 見つからない場合は、`loading="lazy"` を表す新しい `Html\AttributeNode` を作成します。 + + +関数呼び出しのチェック +----------- + +コンパイルパスはLatte Sandboxの基盤です。実際のSandboxは洗練されていますが、禁止された関数呼び出しをチェックする基本原則を示すことができます。 + +**目標:** テンプレート式内で潜在的に危険な関数 `shell_exec` の使用を防ぎます。 + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; +use Latte\Compiler\Nodes\Php; +use Latte\SecurityViolationException; + +function checkForbiddenFunctions(Nodes\TemplateNode $templateNode): void +{ + $forbiddenFunctions = ['shell_exec' => true, 'exec' => true]; // 単純なリスト + + $traverser = new NodeTraverser; + (new NodeTraverser)->traverse( + $templateNode, + enter: function (Node $node) use ($forbiddenFunctions) { + // これは直接関数呼び出しノードですか? + if ($node instanceof Php\Expression\FunctionCallNode + && $node->name instanceof Php\NameNode + && isset($forbiddenFunctions[strtolower((string) $node->name)]) + ) { + throw new SecurityViolationException( + "関数 {$node->name}() は許可されていません。", + $node->position, + ); + } + }, + ); +} +``` + +説明: +- 禁止された関数名のリストを定義します。 +- `enter` ビジターは `FunctionCallNode` をチェックします。 +- 関数名(`$node->name`)が静的な `NameNode` である場合、その小文字の文字列表現を禁止リストと照合します。 +- 禁止された関数が見つかった場合、セキュリティルールの違反を明確に示し、コンパイルを停止する `Latte\SecurityViolationException` をスローします。 + +これらの例は、`NodeTraverser` を使用したコンパイルパスが、テンプレートのAST構造と直接対話することにより、分析、自動変更、およびセキュリティ制約の強制にどのように活用できるかを示しています。 + + +ベストプラクティス +========= + +コンパイルパスを作成する際には、堅牢で保守可能で効率的な拡張機能を作成するために、これらのガイドラインを念頭に置いてください: + +- **順序は重要です:** パスが実行される順序に注意してください。パスが別のパス(例:Latteの基本パスまたは別のカスタムパス)によって作成されたAST構造に依存する場合、または他のパスが変更に依存する可能性がある場合は、`Extension::getPasses()` によって提供される順序付けメカニズムを使用して依存関係(`before`/`after`)を定義します。詳細については、[`Extension::getPasses()` |extending-latte#getPasses]のドキュメントを参照してください。 +- **単一責任:** 1つの明確に定義されたタスクを実行するパスを目指してください。複雑な変換の場合は、ロジックを複数のパスに分割することを検討してください - 分析用に1つ、分析結果に基づいて変更用に別のパスなど。これにより、明確さとテスト容易性が向上します。 +- **パフォーマンス:** コンパイルパスはテンプレートのコンパイル時間を追加することに注意してください(通常はテンプレートが変更されるまで一度だけ発生します)。可能であれば、パス内で計算負荷の高い操作を避けてください。特定のASTの部分を訪問する必要がないことがわかっている場合は常に、`NodeTraverser::DontTraverseChildren` や `NodeTraverser::StopTraversal` などの走査最適化を活用してください。 +- **`NodeHelpers` を使用する:** 特定のノードの検索や単純な式の静的評価などの一般的なタスクについては、独自の `NodeTraverser` ロジックを作成する前に、`Latte\Compiler\NodeHelpers` が適切なメソッドを提供しているかどうかを確認してください。これにより、時間を節約し、定型コードの量を減らすことができます。 +- **エラー処理:** パスがテンプレートASTでエラーまたは無効な状態を検出した場合、明確なメッセージと関連する `Position` オブジェクト(通常は `$node->position`)を含む `Latte\CompileException`(またはセキュリティ問題の場合は `Latte\SecurityViolationException`)をスローします。これにより、テンプレート開発者に役立つフィードバックが提供されます。 +- **べき等性(可能であれば):** 理想的には、同じASTに対してパスを複数回実行しても、一度実行した場合と同じ結果が得られるはずです。これは常に実現可能ではありませんが、達成されればデバッグとパスの相互作用に関する推論が簡素化されます。たとえば、変更パスが変更を再度適用する前に、変更がすでに適用されているかどうかを確認するようにしてください。 + +これらのプラクティスに従うことで、コンパイルパスを効果的に活用して、Latteの機能を強力かつ信頼性の高い方法で拡張し、より安全で最適化された、または機能豊富なテンプレート処理に貢献できます。 diff --git a/latte/ja/cookbook/@home.texy b/latte/ja/cookbook/@home.texy index 965ad57a73..60e83fedbc 100644 --- a/latte/ja/cookbook/@home.texy +++ b/latte/ja/cookbook/@home.texy @@ -1,13 +1,13 @@ -クックブック -****** +ガイドとベストプラクティス +************* .[perex] -Latteでよくある作業を実現するためのコード例とレシピを紹介。 +Latteを使用して一般的なタスクを実行するためのコード例とレシピ。 -- [{iterateWhile}についてあなたがいつも知りたかったことすべて |iteratewhile] -- [LatteでSQLクエリを書くには? |how-to-write-sql-queries-in-latte] -- [PHPからの移行 |migration-from-php] -- [Twigからの移行 |migration-from-twig] -- [Slim 4でLatteを使う |slim-framework] - -{{leftbar: /@left-menu}} +- [開発者向け手順 |/develop] +- [テンプレート間での変数の受け渡し |passing-variables] +- [グループ化について知りたいことすべて |grouping] +- [Latte で SQL クエリを書く方法は? |how-to-write-sql-queries-in-latte] +- [PHP からの移行 |migration-from-php] +- [Twig からの移行 |migration-from-twig] +- [Slim 4 で Latte を使用する |slim-framework] diff --git a/latte/ja/cookbook/@meta.texy b/latte/ja/cookbook/@meta.texy new file mode 100644 index 0000000000..fc4493feb0 --- /dev/null +++ b/latte/ja/cookbook/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Latte ドキュメンテーション}} +{{leftbar: /@left-menu}} diff --git a/latte/ja/cookbook/grouping.texy b/latte/ja/cookbook/grouping.texy new file mode 100644 index 0000000000..bca2b47186 --- /dev/null +++ b/latte/ja/cookbook/grouping.texy @@ -0,0 +1,251 @@ +グループ化について知りたかったことすべて +******************** + +.[perex] +テンプレートでデータを扱う際、特定の基準に従ってデータをグループ化したり、特別に表示したりする必要性にしばしば直面します。Latte はこの目的のために、いくつかの強力なツールを提供しています。 + +フィルタと関数 `|group` は、指定された基準に従ってデータを効率的にグループ化することを可能にし、フィルタ `|batch` はデータを固定サイズのバッチに分割することを容易にし、タグ `{iterateWhile}` は条件付きでループの進行をより複雑に制御する可能性を提供します。 これらの各タグは、データを扱うための特定の可能性を提供し、Latte テンプレートで情報を動的かつ構造化して表示するための不可欠なツールになります。 + + +フィルタと関数 `group` .{data-version:3.0.16} +====================================== + +カテゴリに分割された項目を持つデータベーステーブル `items` を想像してください: + +| id | categoryId | name +|------------------ +| 1 | 1 | Apple +| 2 | 1 | Banana +| 3 | 2 | PHP +| 4 | 3 | Green +| 5 | 3 | Red +| 6 | 3 | Blue + +Latte テンプレートを使用したすべての項目の簡単なリストは次のようになります: + +```latte +
                                                                                +{foreach $items as $item} +
                                                                              • {$item->name}
                                                                              • +{/foreach} +
                                                                              +``` + +しかし、項目をカテゴリ別にグループ化して表示したい場合は、各カテゴリが独自のリストを持つように分割する必要があります。結果は次のようになります: + +```latte +
                                                                                +
                                                                              • Apple
                                                                              • +
                                                                              • Banana
                                                                              • +
                                                                              + +
                                                                                +
                                                                              • PHP
                                                                              • +
                                                                              + +
                                                                                +
                                                                              • Green
                                                                              • +
                                                                              • Red
                                                                              • +
                                                                              • Blue
                                                                              • +
                                                                              +``` + +このタスクは `|group` を使用して簡単かつエレガントに解決できます。パラメータとして `categoryId` を指定します。これは、項目が `$item->categoryId` の値に基づいて小さな配列に分割されることを意味します(`$item` が配列の場合、`$item['categoryId']` が使用されます): + +```latte +{foreach ($items|group: categoryId) as $categoryId => $categoryItems} +
                                                                                + {foreach $categoryItems as $item} +
                                                                              • {$item->name}
                                                                              • + {/foreach} +
                                                                              +{/foreach} +``` + +Latte ではフィルタを関数としても使用できるため、代替構文 `{foreach group($items, categoryId) ...}` が得られます。 + +より複雑な基準で項目をグループ化したい場合は、フィルタのパラメータに関数を使用できます。例えば、名前の長さで項目をグループ化すると次のようになります: + +```latte +{foreach ($items|group: fn($item) => strlen($item->name)) as $items} + ... +{/foreach} +``` + +`$categoryItems` は通常の配列ではなく、イテレータのように動作するオブジェクトであることに注意することが重要です。グループの最初の項目にアクセスするには、[`first()` |latte:functions#first] 関数を使用できます。 + +このデータのグループ化における柔軟性により、`group` は Latte テンプレートでデータを表示するための非常に便利なツールになります。 + + +ネストされたループ +--------- + +個々の項目のサブカテゴリを定義する別の列 `subcategoryId` を持つデータベーステーブルがあるとします。各メインカテゴリを個別の `
                                                                                ` リストに表示し、各サブカテゴリを個別のネストされた `
                                                                                  ` リストに表示したいとします: + +```latte +{foreach ($items|group: categoryId) as $categoryItems} +
                                                                                    + {foreach ($categoryItems|group: subcategoryId) as $subcategoryItems} +
                                                                                      + {foreach $subcategoryItems as $item} +
                                                                                    1. {$item->name} + {/foreach} +
                                                                                    + {/foreach} +
                                                                                  +{/foreach} +``` + + +Nette Database との連携 +------------------- + +Nette Database と組み合わせてデータのグループ化を効果的に活用する方法を示しましょう。冒頭の例の `items` テーブルを扱っていると仮定します。これは、列 `categoryId` を介して次の `categories` テーブルに接続されています: + +| categoryId | name | +|------------|------------| +| 1 | Fruits | +| 2 | Languages | +| 3 | Colors | + +`items` テーブルからのデータは、Nette Database Explorer コマンド `$items = $db->table('items')` を使用して読み込みます。これらのデータを反復処理する間、`$item->name` や `$item->categoryId` などの属性にアクセスできるだけでなく、`categories` テーブルとの接続のおかげで、`$item->category` を介して関連する行にもアクセスできます。この接続で興味深い使用法を示すことができます: + +```latte +{foreach ($items|group: category) as $category => $categoryItems} +

                                                                                  {$category->name}

                                                                                  +
                                                                                    + {foreach $categoryItems as $item} +
                                                                                  • {$item->name}
                                                                                  • + {/foreach} +
                                                                                  +{/foreach} +``` + +この場合、`|group` フィルタを使用して、列 `categoryId` だけでなく、接続された行 `$item->category` に基づいてグループ化します。これにより、キー変数に直接そのカテゴリの `ActiveRow` が含まれるため、`{$category->name}` を使用してその名前を直接出力できます。これは、グループ化がテンプレートをどのように明確にし、データの操作を容易にするかを示す実用的な例です。 + + +フィルタ `|batch` +============= + +フィルタを使用すると、要素のリストを事前に決定された数の要素を持つグループに分割できます。このフィルタは、例えば、ページのより良い明瞭さや視覚的な配置のために、データを複数の小さなグループで表示したい場合に理想的です。 + +項目のリストがあり、それぞれ最大 3 つの項目を含むリストで表示したいとします。このような場合、`|batch` フィルタの使用は非常に実用的です: + +```latte +
                                                                                    +{foreach ($items|batch: 3) as $batch} + {foreach $batch as $item} +
                                                                                  • {$item->name}
                                                                                  • + {/foreach} +{/foreach} +
                                                                                  +``` + +この例では、リスト `$items` は小さなグループに分割され、各グループ(`$batch`)には最大 3 つの項目が含まれます。各グループはその後、個別の `
                                                                                    ` リストに表示されます。 + +最後のグループに必要な数の要素が含まれていない場合、フィルタの 2 番目のパラメータを使用すると、このグループを何で補完するかを定義できます。これは、不完全な行が乱雑に見える可能性がある場合に、要素を美的に整列させるのに理想的です。 + +```latte +{foreach ($items|batch: 3, '—') as $batch} + ... +{/foreach} +``` + + +タグ `{iterateWhile}` +=================== + +`|group` フィルタで解決したのと同じタスクを、`{iterateWhile}` タグを使用して示します。両方のアプローチの主な違いは、`group` が最初に入力データ全体を処理してグループ化するのに対し、`{iterateWhile}` は条件付きでループの進行を制御するため、反復が段階的に行われることです。 + +まず、iterateWhile を使用してカテゴリを持つテーブルを描画します: + +```latte +{foreach $items as $item} +
                                                                                      + {iterateWhile} +
                                                                                    • {$item->name}
                                                                                    • + {/iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                                                                                    +{/foreach} +``` + +`{foreach}` はループの外側の部分、つまり各カテゴリのリストの描画を示し、`{iterateWhile}` タグは内側の部分、つまり個々の項目を示します。終了タグの条件は、現在および次の要素が同じカテゴリに属する限り(`$iterator->nextValue` は[次の項目 |/tags#iterator]です)、繰り返しが続くことを示しています。 + +条件が常に満たされる場合、内側のループですべての要素が描画されます: + +```latte +{foreach $items as $item} +
                                                                                      + {iterateWhile} +
                                                                                    • {$item->name} + {/iterateWhile true} +
                                                                                    +{/foreach} +``` + +結果は次のようになります: + +```latte +
                                                                                      +
                                                                                    • Apple
                                                                                    • +
                                                                                    • Banana
                                                                                    • +
                                                                                    • PHP
                                                                                    • +
                                                                                    • Green
                                                                                    • +
                                                                                    • Red
                                                                                    • +
                                                                                    • Blue
                                                                                    • +
                                                                                    +``` + +このような iterateWhile の使用は何に役立ちますか?テーブルが空で要素が含まれていない場合、空の `
                                                                                      ` は出力されません。 + +開始タグ `{iterateWhile}` に条件を指定すると、動作が変わります:条件(および次の要素への移行)は、終了時ではなく、内側のループの開始時に実行されます。 したがって、条件なしの `{iterateWhile}` には常にエントリしますが、`{iterateWhile $cond}` には条件 `$cond` が満たされた場合にのみエントリします。同時に、次の要素が `$item` に書き込まれます。 + +これは、例えば、各カテゴリの最初の要素を異なる方法で描画したい場合に便利です。例えば、次のように: + +```latte +

                                                                                      Apple

                                                                                      +
                                                                                        +
                                                                                      • Banana
                                                                                      • +
                                                                                      + +

                                                                                      PHP

                                                                                      +
                                                                                        +
                                                                                      + +

                                                                                      Green

                                                                                      +
                                                                                        +
                                                                                      • Red
                                                                                      • +
                                                                                      • Blue
                                                                                      • +
                                                                                      +``` + +元のコードを修正して、最初に最初の項目を描画し、次に内側のループ `{iterateWhile}` で同じカテゴリの他の項目を描画します: + +```latte +{foreach $items as $item} +

                                                                                      {$item->name}

                                                                                      +
                                                                                        + {iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                                                                                      • {$item->name}
                                                                                      • + {/iterateWhile} +
                                                                                      +{/foreach} +``` + +1 つのループ内で、複数の内側のループを作成し、それらをネストすることもできます。このようにして、サブカテゴリなどをグループ化できます。 + +テーブルに別の列 `subcategoryId` があり、各カテゴリが個別の `
                                                                                        ` にあるだけでなく、各サブカテゴリが個別の `
                                                                                          ` にあるとします: + +```latte +{foreach $items as $item} +
                                                                                            + {iterateWhile} +
                                                                                              + {iterateWhile} +
                                                                                            1. {$item->name} + {/iterateWhile $item->subcategoryId === $iterator->nextValue->subcategoryId} +
                                                                                            + {/iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                                                                                          +{/foreach} +``` diff --git a/latte/ja/cookbook/how-to-write-sql-queries-in-latte.texy b/latte/ja/cookbook/how-to-write-sql-queries-in-latte.texy index 461b113c1d..2f619ee6ee 100644 --- a/latte/ja/cookbook/how-to-write-sql-queries-in-latte.texy +++ b/latte/ja/cookbook/how-to-write-sql-queries-in-latte.texy @@ -1,10 +1,10 @@ -LatteでSQLクエリを書くには? -****************** +Latte で SQL クエリを作成するには? +*********************** .[perex] -Latteは本当に複雑なSQLクエリを生成するのにも便利です。 +Latte は、本当に複雑な SQL クエリの生成にも役立ちます。 -SQLクエリの作成に多くの条件や変数が含まれている場合、Latteで書くと非常に分かりやすくなります。とても簡単な例です。 +SQL クエリの作成に多数の条件と変数が含まれる場合、Latte で記述する方が本当に分かりやすくなることがあります。非常に簡単な例: ```latte SELECT users.* FROM users @@ -14,8 +14,7 @@ SELECT users.* FROM users WHERE groups.name = 'Admins' {ifset $country} AND country.name = {$country} {/ifset} ``` -`$latte->setContentType()` を使って、コンテンツを(HTMLとしてではなく)プレーンテキストとして扱うようにLatteに指示します。 -データベースドライバで直接文字列をエスケープする関数を用意します。 +`$latte->setContentType()` を使用して、Latte にコンテンツをプレーンテキストとして(HTML としてではなく)扱うように指示し、さらに文字列をデータベースドライバで直接エスケープするエスケープ関数を準備します: ```php $db = new PDO(/* ... */); @@ -31,13 +30,11 @@ $latte->addFilter('escape', fn($val) => match (true) { }); ``` -使い方は以下のような感じです。 +使用方法は次のようになります: ```php $sql = $latte->renderToString('query.sql.latte', ['country' => $country]); $result = $db->query($sql); ``` -*この例では、Latte v3.0.5以降が必要です。 - -{{leftbar: /@left-menu}} +*記載の例は Latte v3.0.5 以降が必要です。* diff --git a/latte/ja/cookbook/iteratewhile.texy b/latte/ja/cookbook/iteratewhile.texy deleted file mode 100644 index 9686a95cd5..0000000000 --- a/latte/ja/cookbook/iteratewhile.texy +++ /dev/null @@ -1,214 +0,0 @@ -iterateWhile}について、あなたがいつも知りたかったことすべて。 -************************************* - -.[perex] -`{iterateWhile}` というタグは、foreach サイクルでの様々なトリックに適しています。 - -次のようなデータベースのテーブルがあり、項目がカテゴリに分けられているとします。 - -| id | catId | name -|------------------ -| 1 | 1 | Apple -| 2 | 1 | Banana -| 3 | 2 | PHP -| 4 | 3 | Green -| 5 | 3 | Red -| 6 | 3 | Blue - -もちろん、foreachループのアイテムをリストとして描画するのは簡単です。 - -```latte -
                                                                                            -{foreach $items as $item} -
                                                                                          • {$item->name}
                                                                                          • -{/foreach} -
                                                                                          -``` - -しかし、各カテゴリを別々のリストで表示したい場合はどうすればよいのだろうか?言い換えれば、foreachサイクルで線形リストからアイテムをグループ化するタスクをどのように解決するかということです。出力はこのようになるはずだ。 - -```latte -
                                                                                            -
                                                                                          • Apple
                                                                                          • -
                                                                                          • Banana
                                                                                          • -
                                                                                          - -
                                                                                            -
                                                                                          • PHP
                                                                                          • -
                                                                                          - -
                                                                                            -
                                                                                          • Green
                                                                                          • -
                                                                                          • Red
                                                                                          • -
                                                                                          • Blue
                                                                                          • -
                                                                                          -``` - -このタスクをiterateWhileでいかに簡単かつエレガントに解決できるかをお見せします。 - -```latte -{foreach $items as $item} -
                                                                                            - {iterateWhile} -
                                                                                          • {$item->name}
                                                                                          • - {/iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                                                          -{/foreach} -``` - -`{foreach}` がサイクルの外側、つまり各カテゴリのリストの描画を示すのに対して、`{iterateWhile}` タグは内側、つまり個々の項目を示しています。 -終了タグの条件は、現在の要素と次の要素が同じカテゴリに属している限り、繰り返しを続けるというものです(`$iterator->nextValue` は[次のアイテム |/tags#$iterator])。 - -もし、この条件が常に満たされるなら、すべての要素が内部のサイクルで描かれることになる。 - -```latte -{foreach $items as $item} -
                                                                                            - {iterateWhile} -
                                                                                          • {$item->name} - {/iterateWhile true} -
                                                                                          -{/foreach} -``` - -結果は次のようになる。 - -```latte -
                                                                                            -
                                                                                          • Apple
                                                                                          • -
                                                                                          • Banana
                                                                                          • -
                                                                                          • PHP
                                                                                          • -
                                                                                          • Green
                                                                                          • -
                                                                                          • Red
                                                                                          • -
                                                                                          • Blue
                                                                                          • -
                                                                                          -``` - -このような iterateWhile の使い方にどんな利点があるのでしょうか?このチュートリアルの一番最初に紹介した解決策とどう違うのでしょうか?違いは、テーブルが空で要素を含んでいない場合、レンダリングが空にならないことです。 `
                                                                                            `. - - -`{iterateWhile}` のない場合の解決策 .[#toc-solution-without-iteratewhile] ----------------------------------------------------------------- - -もし、同じタスクを完全に基本的な構造のテンプレートシステム、例えばTwigやBlade、あるいは純粋なPHPで解決するとしたら、解決策は次のようになります。 - -```latte -{var $prevCatId = null} -{foreach $items as $item} - {if $item->catId !== $prevCatId} - {* the category has changed *} - - {* we close the previous
                                                                                              , if it is not the first item *} - {if $prevCatId !== null} -
                                                                                            - {/if} - - {* we will open a new list *} -
                                                                                              - - {do $prevCatId = $item->catId} - {/if} - -
                                                                                            • {$item->name}
                                                                                            • -{/foreach} - -{if $prevCatId !== null} - {* we close the last list *} -
                                                                                            -{/if} -``` - -しかし、このコードは理解しがたく、直感的ではありません。HTMLタグの開始と終了の間の接続がまったく明確ではありません。間違いがあっても一目瞭然ではありません。しかも、`$prevCatId` のような補助変数を必要とする。 - -これに対して、`{iterateWhile}` を使った解決策は、すっきりしていて、明確で、補助変数が必要なく、間違いがありません。 - - -終了タグの条件 .[#toc-condition-in-the-closing-tag] ---------------------------------------------- - -開始タグ`{iterateWhile}` で条件を指定すると、動作が変わります。条件 (と次の要素への移動) は内部サイクルの最初で実行され、最後では実行されなくなります。 -したがって、条件なしの`{iterateWhile}` は常に入力されますが、`{iterateWhile $cond}` は条件`$cond` が満たされたときだけ入力されます。同時に、次の要素が`$item` に書き込まれる。 - -これは,たとえば,各カテゴリーの最初の要素を異なる方法でレンダリングしたいような場合に便利である. - -```latte -

                                                                                            Apple

                                                                                            -
                                                                                              -
                                                                                            • Banana
                                                                                            • -
                                                                                            - -

                                                                                            PHP

                                                                                            -
                                                                                              -
                                                                                            - -

                                                                                            Green

                                                                                            -
                                                                                              -
                                                                                            • Red
                                                                                            • -
                                                                                            • Blue
                                                                                            • -
                                                                                            -``` - -元のコードを修正して、最初の項目を描画し、次に同じカテゴリから追加の項目を内部ループで描画することにしましょう`{iterateWhile}`: - -```latte -{foreach $items as $item} -

                                                                                            {$item->name}

                                                                                            -
                                                                                              - {iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                                                            • {$item->name}
                                                                                            • - {/iterateWhile} -
                                                                                            -{/foreach} -``` - - -ネストされたループ .[#toc-nested-loops] ------------------------------- - -1つのサイクルで複数の内部ループを作成し、さらにそれらを入れ子にすることができる。この方法で、例えば、サブカテゴリーをグループ化することができる。 - -テーブル`subCatId` に別のカラムがあるとします。各カテゴリが別のカラムにあることに加えて、各サブカテゴリは別のカラムにあります。 `
                                                                                              `にあることに加え、各サブカテゴリーは別々の `
                                                                                                `: - -```latte -{foreach $items as $item} -
                                                                                                  - {iterateWhile} -
                                                                                                    - {iterateWhile} -
                                                                                                  1. {$item->name} - {/iterateWhile $item->subCatId === $iterator->nextValue->subCatId} -
                                                                                                  - {/iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                                                                -{/foreach} -``` - - -フィルタ|バッチ .[#toc-filter-batch] ------------------------------ - -線形項目のグループ化は、フィルタ`batch` によっても提供され、一定の要素数を持つバッチに分けられます。 - -```latte -
                                                                                                  -{foreach ($items|batch:3) as $batch} - {foreach $batch as $item} -
                                                                                                • {$item->name}
                                                                                                • - {/foreach} -{/foreach} -
                                                                                                -``` - -これは,次のように iterateWhile で置き換えることができます. - -```latte -
                                                                                                  -{foreach $items as $item} - {iterateWhile} -
                                                                                                • {$item->name}
                                                                                                • - {/iterateWhile $iterator->counter0 % 3} -{/foreach} -
                                                                                                -``` - -{{leftbar: /@left-menu}} diff --git a/latte/ja/cookbook/migration-from-php.texy b/latte/ja/cookbook/migration-from-php.texy index 305ed0b450..94b53a2c46 100644 --- a/latte/ja/cookbook/migration-from-php.texy +++ b/latte/ja/cookbook/migration-from-php.texy @@ -1,28 +1,28 @@ -PHPからLatteへの移行について -****************** +PHP から Latte への移行 +***************** .[perex] -純粋なPHPで書かれた古いプロジェクトをLatteに移行しているのでしょうか?移行を簡単にするためのツールを用意しています。[オンラインで |https://php2latte.nette.org]お試しください。 +純粋な PHP で書かれた古いプロジェクトを Latte に変換していますか?移行を容易にするツールがあります。[オンラインでお試しください |https://fiddle.nette.org/php2latte/]。 -[GitHubから |https://github.com/nette/latte-tools]ツールをダウンロードするか、Composerを使ってインストールすることができます。 +ツールは [GitHub|https://github.com/nette/latte-tools] からダウンロードするか、Composer を使用してインストールできます: ```shell composer create-project latte/tools ``` -このコンバータは単純な正規表現による置換を行わず、 PHP パーサを直接使用するので、どんな複雑な構文も扱うことができます。 +コンバーターは正規表現を使用した単純な置換を使用せず、代わりに PHP パーサーを直接利用するため、どんなに複雑な構文でも処理できます。 -PHPからLatteへの変換は、`php-to-latte.php` というスクリプトを使用します。 +PHP から Latte への変換には `php-to-latte.php` スクリプトを使用します: ```shell php-to-latte.php input.php [output.latte] ``` -例 .[#toc-example] ------------------ +例 +------- -入力ファイルは次のようなものです(PunBBフォーラムのコードの一部です)。 +入力ファイルは次のようになります(これはフォーラム PunBB のコードの一部です): ```php

                                                                                                @@ -48,7 +48,7 @@ foreach ($result as $cur_group) {
                                                                                  ``` -このテンプレートを生成します。 +このテンプレートを生成します: ```latte

                                                                                  {$lang_common['User list']}

                                                                                  @@ -68,5 +68,3 @@ foreach ($result as $cur_group) {
                                                                                  ``` - -{{leftbar: /@left-menu}} diff --git a/latte/ja/cookbook/migration-from-twig.texy b/latte/ja/cookbook/migration-from-twig.texy index 820a3e8051..cd6d872fd8 100644 --- a/latte/ja/cookbook/migration-from-twig.texy +++ b/latte/ja/cookbook/migration-from-twig.texy @@ -1,38 +1,38 @@ -TwigからLatteへの移行について -******************* +Twig から Latte への移行 +****************** .[perex] -Twigで書かれたプロジェクトをよりモダンなLatteに移行していますか?私たちは移行を簡単にするためのツールを用意しています。[オンラインで試してみて |https://twig2latte.nette.org]ください。 +Twig で書かれたプロジェクトをよりモダンな Latte に変換していますか?移行を容易にするツールがあります。[オンラインでお試しください |https://fiddle.nette.org/twig2latte/]。 -[GitHubから |https://github.com/nette/latte-tools]ツールをダウンロードするか、Composerを使ってインストールすることができます。 +ツールは [GitHub|https://github.com/nette/latte-tools] からダウンロードするか、Composer を使用してインストールできます: ```shell composer create-project latte/tools ``` -このコンバータは単純な正規表現の置換を使わず、Twigパーサを直接使うので、どんな複雑な構文でも扱うことができます。 +コンバーターは正規表現を使用した単純な置換を使用せず、代わりに Twig パーサーを直接利用するため、どんなに複雑な構文でも処理できます。 -TwigからLatteへの変換にはスクリプト(`twig-to-latte.php` )が使われます。 +Twig から Latte への変換には `twig-to-latte.php` スクリプトを使用します: ```shell twig-to-latte.php input.twig.html [output.latte] ``` -変換方法 .[#toc-conversion] ------------------------ +変換 +-------- -変換は一義的に行うことができないため、結果を手動で編集する必要があります。Twigはドットシンタックスを使用しており、ここで `{{ a.b }}`は`$a->b` を意味します。 `$a['b']`または`$a->getB()` を意味し、コンパイル中に区別することができません。したがって、コンバーターはすべてを`$a->b` に変換します。 +変換は一意に行うことができないため、結果の手動修正を前提としています。Twig はドット構文を使用しており、`{{ a.b }}` は `$a->b`、`$a['b']`、または `$a->getB()` を意味する可能性があり、コンパイル時に区別できません。したがって、コンバーターはすべてを `$a->b` に変換します。 -いくつかの関数、フィルタ、タグはLatteに相当するものがないか、わずかに異なる動作をするかもしれません。 +一部の関数、フィルタ、またはタグは Latte に同等のものがなかったり、わずかに異なる動作をする場合があります。 -例 .[#toc-example] ------------------ +例 +------- -入力ファイルは次のようなものです。 +入力ファイルは次のようになります: -```latte +```twig {% use "blocks.twig" %} @@ -54,7 +54,7 @@ twig-to-latte.php input.twig.html [output.latte] ``` -Latteに変換すると、このようなテンプレートが得られます。 +Latte に変換すると、次のテンプレートが得られます: ```latte {import 'blocks.latte'} @@ -77,5 +77,3 @@ Latteに変換すると、このようなテンプレートが得られます。 ``` - -{{leftbar: /@left-menu}} diff --git a/latte/ja/cookbook/passing-variables.texy b/latte/ja/cookbook/passing-variables.texy new file mode 100644 index 0000000000..828faab09d --- /dev/null +++ b/latte/ja/cookbook/passing-variables.texy @@ -0,0 +1,158 @@ +テンプレート間での変数の受け渡し +**************** + +このガイドでは、`{include}`、`{import}`、`{embed}`、`{layout}`、`{sandbox}` などのさまざまなタグを使用して、Latte でテンプレート間で変数がどのように渡されるかを説明します。また、`{block}` タグと `{define}` タグで変数を操作する方法、および `{parameters}` タグの目的についても学びます。 + + +変数の種類 +----- +Latte の変数は、どのようにどこで定義されるかに応じて、3 つのカテゴリに分類できます: + +**入力変数** は、PHP スクリプトから、または `{include}` などのタグを使用して、外部からテンプレートに渡される変数です。 + +```php +$latte->render('template.latte', ['userName' => 'Jan', 'userAge' => 30]); +``` + +**環境変数** は、特定のタグの場所で存在する変数です。すべての入力変数と、`{var}`、`{default}` などのタグ、または `{foreach}` ループ内で作成された他の変数が含まれます。 + +```latte +{foreach $users as $user} + {include 'userBox.latte', user: $user} +{/foreach} +``` + +**明示的な変数** は、タグ内で直接指定され、ターゲットテンプレートに送信される変数です。 + +```latte +{include 'userBox.latte', name: $user->name, age: $user->age} +``` + + +`{block}` +--------- +`{block}` タグは、継承テンプレートでカスタマイズまたは拡張できる、再利用可能なコードブロックを定義するために使用されます。ブロックの前に定義された環境変数はブロック内で利用可能ですが、変数の変更はそのブロック内でのみ有効です。 + +```latte +{var $foo = '元の値'} +{block example} + {var $foo = '変更された値'} +{/block} + +{$foo} // 出力: 元の値 +``` + + +`{define}` +---------- +`{define}` タグは、`{include}` を使用して呼び出された後にのみレンダリングされるブロックを作成するために使用されます。これらのブロック内で利用可能な変数は、定義にパラメータが指定されているかどうかによって異なります。指定されている場合は、これらのパラメータにのみアクセスできます。指定されていない場合は、ブロックが定義されているテンプレートのすべての入力変数にアクセスできます。 + +```latte +{define hello} + {* テンプレートのすべての入力変数にアクセスできます *} +{/define} + +{define hello $name} + {* パラメータ $name にのみアクセスできます *} +{/define} +``` + + +`{parameters}` +-------------- +`{parameters}` タグは、テンプレートの先頭で期待される入力変数を明示的に宣言するために使用されます。これにより、期待される変数とそのデータ型を簡単に文書化できます。デフォルト値を定義することも可能です。 + +```latte +{parameters int $age, string $name = '不明'} +

                                                                                  年齢: {$age}, 名前: {$name}

                                                                                  +``` + + +`{include file}` +---------------- +`{include file}` タグは、テンプレート全体を挿入するために使用されます。タグが使用されているテンプレートの入力変数と、その中で明示的に定義された変数の両方がこのテンプレートに渡されます。ただし、ターゲットテンプレートは `{parameters}` を使用して範囲を制限できます。 + +```latte +{include 'profile.latte', userId: $user->id} +``` + + +`{include block}` +----------------- +同じテンプレートで定義されたブロックを挿入する場合、すべての環境変数と明示的に定義された変数が渡されます: + +```latte +{define blockName} +

                                                                                  名前: {$name}, 年齢: {$age}

                                                                                  +{/define} + +{var $name = 'Jan', $age = 30} +{include blockName} +``` + +この例では、変数 `$name` と `$age` が `blockName` ブロックに渡されます。`{include parent}` も同様に動作します。 + +別のテンプレートからブロックを挿入する場合、入力変数と明示的に定義された変数のみが渡されます。環境変数は自動的に利用可能ではありません。 + +```latte +{include blockInOtherTemplate, name: $name, age: $age} +``` + + +`{layout}` または `{extends}` +-------------------------- +これらのタグは、子テンプレートの入力変数と、ブロックの前のコードで作成された変数が渡されるレイアウトを定義します: + +```latte +{layout 'layout.latte'} +{var $seo = 'index, follow'} +``` + +テンプレート `layout.latte`: + +```latte + + + +``` + + +`{embed}` +--------- +`{embed}` タグは `{include}` タグに似ていますが、テンプレートにブロックを挿入できます。`{include}` とは異なり、明示的に宣言された変数のみが渡されます: + +```latte +{embed 'menu.latte', items: $menuItems} +{/embed} +``` + +この例では、テンプレート `menu.latte` は変数 `$items` にのみアクセスできます。 + +逆に、`{embed}` 内のブロックでは、すべての環境変数にアクセスできます: + +```latte +{var $name = 'Jan'} +{embed 'menu.latte', items: $menuItems} + {block foo} + {$name} + {/block} +{/embed} +``` + + +`{import}` +---------- +`{import}` タグは、他のテンプレートからブロックをロードするために使用されます。入力変数と明示的に宣言された変数の両方がインポートされたブロックに転送されます。 + +```latte +{import 'buttons.latte'} +``` + + +`{sandbox}` +----------- +`{sandbox}` タグは、安全な処理のためにテンプレートを分離します。変数は明示的にのみ渡されます。 + +```latte +{sandbox 'secure.latte', data: $secureData} +``` diff --git a/latte/ja/cookbook/slim-framework.texy b/latte/ja/cookbook/slim-framework.texy index 168547bbb6..7c0f73e82d 100644 --- a/latte/ja/cookbook/slim-framework.texy +++ b/latte/ja/cookbook/slim-framework.texy @@ -1,39 +1,39 @@ -スリム4でラテを使う -********** +Slim 4 で Latte を使用する +******************** .[perex] -この記事は「Daniel Opitz」によって書かれたもので、Slim FrameworkでLatteを使用する方法について書かれています。 +この記事は、"Daniel Opitz":https://odan.github.io/2022/04/06/slim4-latte.html によって書かれ、Slim Framework で Latte を使用する方法を説明しています。 -まず、「Slim Frameworkをインストール」し、「Composerを使ってLatteをインストール」します。 +まず、"Slim Framework をインストール":https://odan.github.io/2019/11/05/slim4-tutorial.html し、次に Composer を使用して Latte をインストールします: ```shell composer require latte/latte ``` -コンフィギュレーション .[#toc-configuration] ---------------------------------- +設定 +----------- -プロジェクトのルート・ディレクトリに新しいディレクトリ`templates` を作成します。すべてのテンプレートは、後でそこに配置されます。 +プロジェクトのルートディレクトリに新しいディレクトリ `templates` を作成します。すべてのテンプレートは後でそこに配置されます。 -`config/defaults.php` ファイルに、新しい`template` 設定キーを追加します。 +`config/defaults.php` ファイルに新しい設定キー `template` を追加します: ```php $settings['template'] = __DIR__ . '/../templates'; ``` -LatteはテンプレートをネイティブのPHPコードにコンパイルし、ディスク上のキャッシュに保存します。そのため、あたかもネイティブのPHPで書かれたように高速に動作します。 +Latte はテンプレートをネイティブ PHP コードにコンパイルし、ディスク上のキャッシュに保存します。したがって、ネイティブ PHP で書かれた場合と同じくらい高速です。 -`config/defaults.php` ファイルに新しい`template_temp` 設定キーを追加してください。`{project}/tmp/templates` ディレクトリが存在し、読み取りと書き込みのアクセス権があることを確認します。 +`config/defaults.php` ファイルに新しい設定キー `template_temp` を追加します:ディレクトリ `{project}/tmp/templates` が存在し、読み取りおよび書き込み権限があることを確認してください。 ```php $settings['template_temp'] = __DIR__ . '/../tmp/templates'; ``` -Latteはテンプレートを変更するたびに自動的にキャッシュを再生成しますが、本番環境ではこれをオフにすることで、パフォーマンスを少し抑えることができます。 +Latte はテンプレートが変更されるたびにキャッシュを自動的に再生成しますが、これは本番環境では無効にしてパフォーマンスを少し節約できます: ```php -// change to false in the production environment +// 本番環境では false に変更します $settings['template_auto_refresh'] = true; ``` @@ -63,11 +63,11 @@ return [ ]; ``` -これだけでも技術的には Latte テンプレートをレンダリングすることができますが、PSR-7 レスポンスオブジェクトと連動させる必要があります。 +Latte テンプレート自体のレンダリングは技術的には機能しますが、PSR-7 レスポンスオブジェクトでも機能するようにする必要があります。 -この目的のために、特別な`TemplateRenderer` クラスを作成し、この作業を代行させます。 +この目的のために、この作業を行う特別なクラス `TemplateRenderer` を作成します。 -では次に、`src/Renderer/TemplateRenderer.php` にファイルを作成し、このコードをコピー&ペーストしてください。 +次に、`src/Renderer/TemplateRenderer.php` ファイルを作成し、このコードをコピー/貼り付けます: ```php +
                                                                                    {foreach $items as $item}
                                                                                  • {$item|capitalize}
                                                                                  • {/foreach}
                                                                                  ``` -すべてが正しく設定されている場合、次のような出力が表示されます。 +すべてが正しく設定されていれば、次の出力が表示されるはずです: ```latte One @@ -155,4 +155,3 @@ Three ``` {{priority: -1}} -{{leftbar: /@left-menu}} diff --git a/latte/ja/creating-extension.texy b/latte/ja/creating-extension.texy deleted file mode 100644 index c2b8cbe060..0000000000 --- a/latte/ja/creating-extension.texy +++ /dev/null @@ -1,579 +0,0 @@ -エクステンションの作成 -*********** - -.[perex]{data-version:3.0} -エクステンションは、カスタムタグ、フィルタ、関数、プロバイダなどを定義できる再利用可能なクラスです。 - -Latteでカスタマイズしたものを別のプロジェクトで再利用したり、他の人と共有したりするときにエクステンションを作成します。 -また、プロジェクトのテンプレートで使用したい特定のタグやフィルタをすべて含む、各ウェブプロジェクト用の拡張機能を作成することも便利です。 - - -拡張機能クラス .[#toc-extension-class] -=============================== - -Extensionは、[api:Latte\Extension] を継承したクラスです。 Latteへの登録は、`addExtension()` を用いて(あるいは[設定ファイルによって |application:en:configuration#Latte])行います。 - -```php -$latte = new Latte\Engine; -$latte->addExtension(new MyLatteExtension); -``` - -複数の拡張機能を登録し、それらが同じ名前のタグ、フィルタ、または関数を定義している場合、最後に追加された拡張機能が優先されます。これは、拡張機能がネイティブタグ/フィルタ/関数をオーバーライドできることも意味しています。 - -クラスに変更を加え、自動リフレッシュがオフになっていない場合、Latteは自動的にテンプレートを再コンパイルします。 - -クラスは以下のメソッドのいずれかを実装することができます。 - -```php -abstract class Extension -{ - /** - * Initializes before template is compiler. - */ - public function beforeCompile(Engine $engine): void; - - /** - * Returns a list of parsers for Latte tags. - * @return array - */ - public function getTags(): array; - - /** - * Returns a list of compiler passes. - * @return array - */ - public function getPasses(): array; - - /** - * Returns a list of |filters. - * @return array - */ - public function getFilters(): array; - - /** - * Returns a list of functions used in templates. - * @return array - */ - public function getFunctions(): array; - - /** - * Returns a list of providers. - * @return array - */ - public function getProviders(): array; - - /** - * Returns a value to distinguish multiple versions of the template. - */ - public function getCacheKey(Engine $engine): mixed; - - /** - * Initializes before template is rendered. - */ - public function beforeRender(Template $template): void; -} -``` - -拡張機能がどのようなものであるかについては、組み込みの"CoreExtension":https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php をご覧ください。 - - -beforeCompile(Latte\Engine $engine): void .[method] ----------------------------------------------------- - -テンプレートがコンパイルされる前に呼び出されます。このメソッドは、例えばコンパイルに関連する初期化などに使用することができます。 - - -getTags(): array .[method] ---------------------------- - -テンプレートがコンパイルされたときに呼び出されます。[タグ解析関数 |#Tag Parsing Function]である *タグ名 => callable* の連想配列を返します。 - -```php -public function getTags(): array -{ - return [ - 'foo' => [FooNode::class, 'create'], - 'bar' => [BarNode::class, 'create'], - 'n:baz' => [NBazNode::class, 'create'], - // ... - ]; -} -``` - -`n:baz` タグは純粋なn:attributeを表し、すなわち属性としてのみ記述可能なタグである。 - -`foo` と`bar` のタグの場合、Latte はそれらがペアであるかどうかを自動的に認識し、ペアであれば`n:inner-foo` と`n:tag-foo` の接頭辞を持つ変種を含めて n:attribute を使って自動的に記述することができます。 - -このようなn:attributeの実行順序は`getTags()` が返す配列の中の順序で決まります.従って、`n:foo` は`n:bar` の前に実行されます。 `
                                                                                  `. - -複数の拡張子にわたってn:属性の順序を決定する必要がある場合には、`order()` ヘルパーメソッドを使用して下さい。`before` xor`after` パラメータはどのタグがタグの前と後に順序付けられるかを決定します。 - -```php -public function getTags(): array -{ - return [ - 'foo' => self::order([FooNode::class, 'create'], before: 'bar')] - 'bar' => self::order([BarNode::class, 'create'], after: ['block', 'snippet'])] - ]; -} -``` - - -getPasses(): array .[method] ----------------------------- - -テンプレートがコンパイルされるときに呼び出されます。ASTを走査し修正するいわゆる[コンパイラパスを |#compiler passes]表す関数である、連想配列 *name pass => callable* を返します。 - -ここでも、`order()` ヘルパー・メソッドが使えます。`before` または`after` パラメータの値には、before/after all の意味を持つ `*` を指定することができます。 - -```php -public function getPasses(): array -{ - return [ - 'optimize' => [Passes::class, 'optimizePass'], - 'sandbox' => self::order([$this, 'sandboxPass'], before: '*'), - // ... - ]; -} -``` - - -beforeRender(Latte\Engine $engine): void .[method] --------------------------------------------------- - -これは各テンプレートのレンダリングの前に呼び出されます。このメソッドは、例えば、レンダリング中に使用される変数を初期化するために使用することができます。 - - -getFilters(): array .[method] ------------------------------ - -テンプレートがレンダリングされる前に呼び出されます。[フィルタを |extending-latte#filters]連想配列で返します *フィルタ名 => callable*. - -```php -public function getFilters(): array -{ - return [ - 'batch' => [$this, 'batchFilter'], - 'trim' => [$this, 'trimFilter'], - // ... - ]; -} -``` - - -getFunctions(): array .[method] -------------------------------- - -テンプレートがレンダリングされる前に呼び出されます。[関数を |extending-latte#functions]連想配列で返します *関数名 => callable*. - -```php -public function getFunctions(): array -{ - return [ - 'clamp' => [$this, 'clampFunction'], - 'divisibleBy' => [$this, 'divisibleByFunction'], - // ... - ]; -} -``` - - -getProviders(): array .[method] -------------------------------- - -テンプレートがレンダリングされる前に呼び出されます。プロバイダの配列を返します。プロバイダは通常、ランタイムにタグを使用するオブジェクトです。これらのプロバイダーは、`$this->global->...` を介してアクセスします。 - -```php -public function getProviders(): array -{ - return [ - 'myFoo' => $this->foo, - 'myBar' => $this->bar, - // ... - ]; -} -``` - - -getCacheKey(Latte\Engine $engine): mixed .[method] --------------------------------------------------- - -テンプレートがレンダリングされる前に呼び出されます。戻り値はコンパイルされたテンプレートファイルの名前に含まれるハッシュを持つキーの一部になります。したがって、異なる戻り値に対して、Latteは異なるキャッシュファイルを生成します。 - - -Latteはどのように動くのか? .[#toc-how-does-latte-work] -============================================ - -カスタムタグやコンパイラパスの定義方法を理解するためには、Latteがどのように動作しているかを理解することが不可欠です。 - -Latteのテンプレートコンパイルは簡単に言うと以下のような仕組みになっています。 - -- まず、**レクサー**がテンプレートのソースコードを処理しやすいように小さな断片(トークン)にトークン化します。 -- 次に、**パーサ**がトークンのストリームを意味のあるノードツリー(抽象構文木、AST)に変換します。 -- 最後に、コンパイラはASTからテンプレートをレンダリングするPHPクラスを **生成** して、それをキャッシュします。 - -実は、コンパイルはもう少し複雑です。Latteは2つ**のレキサとパーサを持っています。1つはHTMLテンプレート用、もう1つはタグの中にあるPHPのようなコード用のレキサです。また、トークン化の後にパーシングが実行されるわけではなく、レキサーとパーサーが2つの「スレッド」で並行して実行され、協調しているのです。ロケットサイエンスですね :-) - -さらに、すべてのタグは独自のパーシング ルーチンを持っています。パーサーはタグに遭遇すると、そのパース関数を呼び出します([Extension::getTags() |#getTags]を返します)。 -その仕事は、タグの引数と、ペアタグの場合は内部のコンテンツを解析することです。それはASTの一部となる*node*を返します。詳細については、[タグ解析関数を |#Tag parsing function]参照してください。 - -パーサーが作業を終えると、テンプレートを表す完全なASTができあがります。ルート・ノードは`Latte\Compiler\Nodes\TemplateNode` です。ツリー内の個々のノードは、タグだけでなく、HTML要素、その属性、タグの内部で使用されるすべての式などを表します。 - -この後、いわゆる[コンパイラー・パスが |#Compiler passes]登場します。これは、ASTを修正する関数([Extension::getPasses() |#getPasses]によって返されます)です。 - -テンプレートのコンテンツの読み込みから、パース、結果のファイルの生成までの全プロセスは、このコードでシーケンス化することができ、実験して中間結果をダンプすることができます。 - -```php -$latte = new Latte\Engine; -$source = $latte->getLoader()->getContent($file); -$ast = $latte->parse($source); -$latte->applyPasses($ast); -$code = $latte->generate($ast, $file); -``` - - -ASTの例 .[#toc-example-of-ast] ----------------------------- - -ASTのイメージをつかむために、サンプルを追加します。これはソースのテンプレートです。 - -```latte -{foreach $category->getItems() as $item} -
                                                                                1. {$item->name|upper}
                                                                                2. - {else} - no items found -{/foreach} -``` - -そして、これがASTの形式での表現です。 - -/--pre -Latte\Compiler\Nodes\TemplateNode( - Latte\Compiler\Nodes\FragmentNode( - - Latte\Essential\Nodes\ForeachNode( - expression: Latte\Compiler\Nodes\Php\Expression\MethodCallNode( - object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$category') - name: Latte\Compiler\Nodes\Php\IdentifierNode('getItems') - ) - value: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') - content: Latte\Compiler\Nodes\FragmentNode( - - Latte\Compiler\Nodes\TextNode(' ') - - Latte\Compiler\Nodes\Html\ElementNode('li')( - content: Latte\Essential\Nodes\PrintNode( - expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode( - object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') - name: Latte\Compiler\Nodes\Php\IdentifierNode('name') - ) - modifier: Latte\Compiler\Nodes\Php\ModifierNode( - filters: - - Latte\Compiler\Nodes\Php\FilterNode('upper') - ) - ) - ) - ) - else: Latte\Compiler\Nodes\FragmentNode( - - Latte\Compiler\Nodes\TextNode('no items found') - ) - ) - ) -) -\-- - - -カスタムタグ .[#toc-custom-tags] -========================== - -新しいタグを定義するには、3つのステップが必要です。 - -[- タグのパース関数を |#tag parsing function]定義する (タグをノードにパースする役割を果たす) -- ノードクラスを作成する([PHPコードの生成と |#generating PHP code] [ASTのトラバースを |#AST traversing]担当する) --[Extension::getTags() |#getTags]を使ってタグを登録する - - -タグのパース関数 .[#toc-tag-parsing-function] -------------------------------------- - -タグのパース処理は、そのパース関数([Extension::getTags() |#getTags]によって返されるもの)によって処理されます。この関数の仕事は、タグの中にあるすべての引数を解析し、チェックすることです(これを行うにはTagParserを使用します)。 -さらに、タグがペアである場合、TemplateParserに依頼して内部のコンテンツを解析して返します。 -この関数はノードを生成して返します.ノードは通常,`Latte\Compiler\Nodes\StatementNode` の子であり,これがASTの一部となります. - -各ノードに対してクラスを作成し,パース関数を静的ファクトリとしてその中にエレガントに配置します.例として、おなじみの`{foreach}` タグを作ってみましょう。 - -```php -use Latte\Compiler\Nodes\StatementNode; - -class ForeachNode extends StatementNode -{ - // a parsing function that just creates a node for now - public static function create(Latte\Compiler\Tag $tag): self - { - $node = new self; - return $node; - } - - public function print(Latte\Compiler\PrintContext $context): string - { - // code will be added later - } - - public function &getIterator(): \Generator - { - // code will be added later - } -} -``` - -解析関数`create()` はオブジェクト[api:Latte\Compiler\Tag] に渡され、タグの基本情報(クラシックタグか n:attribute か、どの行にあるかなど)を持ち、主に`$tag->parser` にある[api:Latte\Compiler\TagParser] にアクセスします。 - -タグが引数を持たなければならない場合は、`$tag->expectArguments()` を呼び出して引数の存在をチェックします。`$tag->parser` オブジェクトのメソッドはそれらをパースするために利用できます。 - --`parseExpression(): ExpressionNode` PHP 風の式 (例:`10 + 3`) に対応します。 --`parseUnquotedStringOrExpression(): ExpressionNode` 式または引用符で囲まれていない文字列に対して --`parseArguments(): ArrayNode` 配列の内容 (例:`10, true, foo => bar`) --`parseModifier(): ModifierNode` 修飾子に対して (例:`|upper|truncate:10`) --`parseType(): expressionNode` typehint 用 (例:`int|string` または`Foo\Bar[]`) - -と、トークンを直接操作する低レベルの[api:Latte\Compiler\TokenStream] があります。 - --`$tag->parser->stream->consume(...): Token` --`$tag->parser->stream->tryConsume(...): ?Token` - -Latte は PHP の構文を少しずつ拡張しています。例えば、修飾子を追加したり、三項演算子を短くしたり、 単純な英数字の文字列を引用符なしで書けるようにしたりしています。これが、PHPの代わりに*PHP-like*という言葉を使う理由です。したがって、`parseExpression()` メソッドは`foo` を`'foo'` のようにパースします。 -さらに、*unquoted-string* は、引用符で囲む必要がなく、同時に英数字である必要もない文字列の特殊なケースです。例えば、`{include ../file.latte}` タグのファイルへのパスがこれにあたります。これをパースするには、`parseUnquotedStringOrExpression()` メソッドを使用します。 - -.[note] -Latteの一部であるノードクラスを勉強することは,パース処理の細かな部分をすべて学ぶのに最適な方法です. - -`{foreach}` タグに戻りましょう。このタグでは、`expression + 'as' + second expression` という形式の引数を想定して、次のように解析しています。 - -```php -use Latte\Compiler\Nodes\StatementNode; -use Latte\Compiler\Nodes\Php\ExpressionNode; -use Latte\Compiler\Nodes\AreaNode; - -class ForeachNode extends StatementNode -{ - public ExpressionNode $expression; - public ExpressionNode $value; - - public static function create(Latte\Compiler\Tag $tag): self - { - $tag->expectArguments(); - $node = new self; - $node->expression = $tag->parser->parseExpression(); - $tag->parser->stream->consume('as'); - $node->value = $parser->parseExpression(); - return $node; - } -} -``` - -変数`$expression` と`$value` に書き込んだ式は、サブノードを表しています。 - -.[tip] -サブノードを持つ変数は **public** として定義し、必要であれば[以降の処理ステップで |#Compiler Passes]変更できるようにする。また、[トラバースの |#AST Traversing]ために**利用可能にする**ことも必要である。 - -私たちのようなペアのタグの場合、メソッドはTemplateParserにタグの内部コンテンツを解析させなければなりません。これは`yield` で処理され、''[inner content, end tag]'' というペアが返されます。内部コンテンツを変数`$node->content` に格納します。 - -```php -public AreaNode $content; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $endTag] = yield; - return $node; -} -``` - -`yield` キーワードによって`create()` メソッドが終了し、TemplateParser に制御が戻されます。その後、`create()` に制御が戻され、中断したところから続行されます。`yield`, メソッドを使用すると、自動的に`Generator` が返されます。 - -また、`yield` にタグ名の配列を渡して、終了タグの前に出現した場合にパースを停止させることもできます。これは `{foreach}...{else}...{/foreach}`の実装に役立ちます。`{else}` が発生した場合、それ以降の内容を`$node->elseContent` にパースします。 - -```php -public AreaNode $content; -public ?AreaNode $elseContent = null; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $nextTag] = yield ['else']; - if ($nextTag?->name === 'else') { - [$node->elseContent] = yield; - } - - return $node; -} -``` - -ノードを返すとタグのパースが完了する。 - - -PHPコードの生成 .[#toc-generating-php-code] -------------------------------------- - -各ノードは`print()` メソッドを実装する必要があります。テンプレートの指定された部分をレンダリングするPHPコード(ランタイムコード)を返します。パラメータとしてオブジェクト[api:Latte\Compiler\PrintContext] が渡されます。このオブジェクトには、 結果のコードの組み立てを簡略化する便利なメソッド`format()` があります。 - -`format(string $mask, ...$args)` メソッドは、マスクに以下のプレースホルダーを受け付けます。 --`%node` はノードを表示します。 --`%dump` PHP に値をエクスポートします。 --`%raw` は、テキストを変換せずに直接挿入します。 --`%args` は、関数呼び出しの引数として ArrayNode をプリントします。 --`%line` は行番号付きのコメントを出力します。 --`%escape(...)` 内容をエスケープします。 --`%modify(...)` モディファイアを適用する --`%modifyContent(...)` ブロックにモディファイアを適用 - - -`print()` 関数は次のようになります (簡単のために`else` ブランチは無視します)。 - -```php -public function print(Latte\Compiler\PrintContext $context): string -{ - return $context->format( - <<<'XX' - foreach (%node as %node) %line { - %node - } - - XX, - $this->expression, - $this->value, - $this->position, - $this->content, - ); -} -``` - -変数`$this->position` は[api:Latte\Compiler\Node] クラスですでに定義されており、パーサーによって設定されます。この変数には、ソースコード内のタグの位置を行と列の番号で表した[api:Latte\Compiler\Position] オブジェクトが含まれます。 - -ランタイムコードは、補助変数を使用することができます。テンプレート自身が使用する変数との衝突を避けるために、それらの変数の前に`$ʟ__` という文字を付けるのが慣例となっています。 - -また、実行時に任意の値を使用することができ、それらは[Extension::getProviders() |#getProviders]メソッドを使用してプロバイダの形でテンプレートに渡されます。これらの値には`$this->global->...` を使ってアクセスします。 - - -ASTのトラバース .[#toc-ast-traversing] --------------------------------- - -ASTツリーを深くトラバースするためには、`getIterator()` メソッドを実装する必要があります。これによって、サブノードへのアクセスが可能になる。 - -```php -public function &getIterator(): \Generator -{ - yield $this->expression; - yield $this->value; - yield $this->content; - if ($this->elseContent) { - yield $this->elseContent; - } -} -``` - -`getIterator()` は参照を返すことに注意。これは、ノードビジターが個々のノードを他のノードに置き換えることができるようにするものである。 - -.[warning] -ノードがサブノードを持つ場合、このメソッドを実装し、すべてのサブノードを利用できるようにする必要がある。さもなければ、セキュリティホールができてしまう。例えば、サンドボックスモードではサブノードを制御できず、許可されていないコンストラクトがその中で呼び出されないようにすることができない。 - -子ノードがない場合でも、メソッド本体に`yield` キーワードが存在しなければならないので、次のように記述する。 - -```php -public function &getIterator(): \Generator -{ - if (false) { - yield; - } -} -``` - - -コンパイラのパス .[#toc-compiler-passes] -================================ - -コンパイラパスは、ASTを変更したり、ASTの情報を収集するための関数です。これらは[Extension::getPasses() |#getPasses]メソッドによって返されます。 - - -ノードトラバーサ .[#toc-node-traverser] -------------------------------- - -AST を扱う最も一般的な方法は、[api:Latte\Compiler\NodeTraverser] を使うことです。 - -```php -use Latte\Compiler\Node; -use Latte\Compiler\NodeTraverser; - -$ast = (new NodeTraverser)->traverse( - $ast, - enter: fn(Node $node) => ..., - leave: fn(Node $node) => ..., -); -``` - -enter* 関数 (すなわち visitor) は、ノードが最初に遭遇したとき、そのサブノードが処理される前に呼び出される。leave*関数は、すべてのサブノードが訪問された後に呼び出される。 -よくあるパターンは、*enter*で何らかの情報を収集し、それに基づいて*leave*で修正を行うというものである。leave*が呼ばれた時点で、ノード内のすべてのコードはすでに訪問され、必要な情報が収集されている。 - -ASTを変更するには?最も簡単な方法は、単にノードのプロパティを変更することである。もう一つの方法は、新しいノードを返すことで、ノードを完全に置き換えることである。例:次のコードは,ASTのすべての整数を文字列に変更する(例えば,42は`'42'` に変更される). - -```php -use Latte\Compiler\Nodes\Php; - -$ast = (new NodeTraverser)->traverse( - $ast, - leave: function (Node $node) { - if ($node instanceof Php\Scalar\IntegerNode) { - return new Php\Scalar\StringNode((string) $node->value); - } - }, -); -``` - -ASTは数千のノードを含むことがあり,そのすべてを走査するのは時間がかかる場合がある.場合によっては,完全な探索を避けることも可能である. - -ツリー内のすべての`Html\ElementNode` を探す場合、一度`Php\ExpressionNode` を見てしまうと、その子ノードをすべてチェックする意味がないことがわかります。なぜなら、HTML は式の中に入れることができないからです。この場合、トラバーサーにクラス・ノードに再帰しないように指示することができます。 - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Php\ExpressionNode) { - return NodeTraverser::DontTraverseChildren; - } - // ... - }, -); -``` - -特定の1つのノードだけを探している場合は、そのノードを見つけた後に探索を完全に中断することも可能です。 - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Nodes\ParametersNode) { - return NodeTraverser::StopTraversal; - } - // ... - }, -); -``` - - -ノードヘルパー .[#toc-node-helpers] ----------------------------- - -クラス[api:Latte\Compiler\NodeHelpers] は、特定のコールバックなどを満たす AST ノードを見つけることができるメソッドをいくつか提供します。いくつかの例を示します。 - -```php -use Latte\Compiler\NodeHelpers; - -// finds all HTML element nodes -$elements = NodeHelpers::find($ast, fn(Node $node) => $node instanceof Nodes\Html\ElementNode); - -// finds first text node -$text = NodeHelpers::findFirst($ast, fn(Node $node) => $node instanceof Nodes\TextNode); - -// converts PHP value node to real value -$value = NodeHelpers::toValue($node); - -// converts static textual node to string -$text = NodeHelpers::toText($node); -``` diff --git a/latte/ja/custom-filters.texy b/latte/ja/custom-filters.texy new file mode 100644 index 0000000000..5315276e27 --- /dev/null +++ b/latte/ja/custom-filters.texy @@ -0,0 +1,231 @@ +カスタムフィルタの作成 +*********** + +.[perex] +フィルタは、Latteテンプレート内で直接データをフォーマットおよび変更するための強力なツールです。パイプ記号(`|`)を使用して、変数または式の結果を目的の出力形式に変換するためのクリーンな構文を提供します。 + + +フィルタとは? +======= + +Latteのフィルタは、基本的に**入力値を変換して出力値にするために特別に設計されたPHP関数**です。テンプレート式(`{...}`)内でパイプ表記(`|`)を使用して適用されます。 + +**利便性:** フィルタを使用すると、一般的なフォーマットタスク(日付のフォーマット、大文字小文字の変更、切り捨てなど)やデータ操作を再利用可能な単位にカプセル化できます。テンプレート内で複雑なPHPコードを繰り返す代わりに、単にフィルタを適用できます: +```latte +{* 切り捨てのための複雑なPHPの代わりに: *} +{$article->text|truncate:100} + +{* 日付フォーマットコードの代わりに: *} +{$event->startTime|date:'Y-m-d H:i'} + +{* 複数の変換を適用する: *} +{$product->name|lower|capitalize} +``` + +**可読性:** フィルタを使用すると、変換ロジックがフィルタ定義に移動されるため、テンプレートがよりクリーンになり、プレゼンテーションに集中できます。 + +**コンテキスト認識:** Latteのフィルタの重要な利点は、[コンテキスト認識 |#コンテキストフィルタ]できることです。これは、フィルタが操作しているコンテンツのタイプ(HTML、JavaScript、プレーンテキストなど)を認識し、適切なロジックまたはエスケープを適用できることを意味します。これは、特にHTMLを生成する場合のセキュリティと正確性のために不可欠です。 + +**アプリケーションロジックとの統合:** カスタム関数と同様に、フィルタの背後にあるPHP callableはクロージャ、静的メソッド、またはインスタンスメソッドにすることができます。これにより、フィルタは必要に応じてアプリケーションサービスまたはデータにアクセスできますが、その主な目的は*入力値の変換*のままです。 + +Latteはデフォルトで豊富な[標準フィルタ |filters]セットを提供します。カスタムフィルタを使用すると、プロジェクト固有のフォーマットと変換でこのセットを拡張できます。 + +*複数*の入力に基づいてロジックを実行する必要がある場合、または変換する主要な値がない場合は、おそらく[カスタム関数 |custom-functions]を使用する方が適切です。複雑なマークアップを生成したり、テンプレートフローを制御したりする必要がある場合は、[カスタムタグ |custom-tags]を検討してください。 + + +フィルタの作成と登録 +========== + +Latteでカスタムフィルタを定義および登録するには、いくつかの方法があります。 + + +`addFilter()` を使用した直接登録 +----------------------- + +フィルタを追加する最も簡単な方法は、`Latte\Engine` オブジェクトで直接 `addFilter()` メソッドを使用することです。フィルタ名(テンプレートで使用される名前)と対応するPHP callableを指定します。 + +```php +$latte = new Latte\Engine; + +// 引数のない単純なフィルタ +$latte->addFilter('initial', fn(string $s): string => mb_substr($s, 0, 1) . '.'); + +// オプションの引数を持つフィルタ +$latte->addFilter('shortify', function (string $s, int $len = 10): string { + return mb_substr($s, 0, $len); +}); + +// 配列を処理するフィルタ +$latte->addFilter('sum', fn(array $numbers): int|float => array_sum($numbers)); +``` + +**テンプレートでの使用:** + +```latte +{$name|initial} {* $name が 'John' の場合 'J.' を出力 *} +{$description|shortify} {* デフォルトの長さ 10 を使用 *} +{$description|shortify:50} {* 長さ 50 を使用 *} +{$prices|sum} {* 配列 $prices のアイテムの合計を出力 *} +``` + +**引数の受け渡し:** + +パイプ(`|`)の左側の値は、常にフィルタ関数の*最初の*引数として渡されます。テンプレート内でコロン(`:`)の後にリストされているパラメータは、後続の引数として渡されます。 + +```latte +{$text|shortify:30} +// PHP関数 shortify($text, 30) を呼び出します +``` + + +拡張機能による登録 +--------- + +特に再利用可能なフィルタセットを作成したり、パッケージとして共有したりする場合のより良い整理のために、推奨される方法は、それらを[Latte拡張機能 |extending-latte#Latte Extension]内で登録することです: + +```php +namespace App\Latte; + +use Latte\Extension; + +class MyLatteExtension extends Extension +{ + public function getFilters(): array + { + return [ + 'initial' => $this->initial(...), + 'shortify' => $this->shortify(...), + ]; + } + + public function initial(string $s): string + { + return mb_substr($s, 0, 1) . '.'; + } + + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// 登録 +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +このアプローチは、フィルタロジックをカプセル化し、登録をシンプルに保ちます。 + + +フィルタローダーの使用 +----------- + +Latteでは、`addFilterLoader()` を使用してフィルタローダーを登録できます。これは、コンパイル中にLatteが未知のフィルタ名を要求する単一のcallableです。ローダーはフィルタのPHP callableまたは `null` を返します。 + +```php +$latte = new Latte\Engine; + +// ローダーは動的にフィルタcallableを作成/取得できます +$latte->addFilterLoader(function (string $name): ?callable { + if ($name === 'myLazyFilter') { + // ここで時間のかかる初期化を想像してください... + $service = get_some_expensive_service(); + return fn($value) => $service->process($value); + } + return null; +}); +``` + +このメソッドは、主に非常に**時間のかかる初期化**を伴うフィルタの遅延読み込みを目的としていました。しかし、現代の依存性注入(dependency injection)の実践は、通常、遅延サービスをより効果的に処理します。 + +フィルタローダーは複雑さを増し、一般的には `addFilter()` を使用した直接登録、または `getFilters()` を使用した拡張機能内での登録が推奨されます。他の方法で解決できないフィルタ初期化のパフォーマンス問題に関連する重大かつ特定の理由がない限り、ローダーを使用しないでください。 + + +属性付きクラスを使用したフィルタ +---------------- + +フィルタを定義する別のエレガントな方法は、[テンプレートパラメータクラス |develop#クラスとしてのパラメータ]のメソッドを使用することです。メソッドに `#[Latte\Attributes\TemplateFilter]` 属性を追加するだけです。 + +```php +use Latte\Attributes\TemplateFilter; + +class TemplateParameters +{ + public function __construct( + public string $description, + // その他のパラメータ... + ) {} + + #[TemplateFilter] + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// オブジェクトをテンプレートに渡す +$params = new TemplateParameters(description: '...'); +$latte->render('template.latte', $params); +``` + +`TemplateParameters` オブジェクトがテンプレートに渡されると、Latteはこの属性でマークされたメソッドを自動的に認識して登録します。テンプレート内のフィルタ名は、メソッド名(この場合は `shortify`)と同じになります。 + +```latte +{* パラメータクラスで定義されたフィルタを使用する *} +{$description|shortify:50} +``` + + +コンテキストフィルタ +========== + +フィルタは、入力値以上の情報を必要とすることがあります。操作している文字列の**コンテンツタイプ**(例:HTML、JavaScript、プレーンテキスト)を知る必要があるか、それを変更する必要があるかもしれません。これがコンテキストフィルタの出番です。 + +コンテキストフィルタは通常のフィルタと同じように定義されますが、その**最初のパラメータは** `Latte\Runtime\FilterInfo` として型ヒントされている必要があります。Latteはこのシグネチャを自動的に認識し、フィルタを呼び出すときに `FilterInfo` オブジェクトを渡します。後続のパラメータは、通常どおりフィルタ引数を受け取ります。 + +```php +use Latte\Runtime\FilterInfo; +use Latte\ContentType; + +$latte->addFilter('money', function (FilterInfo $info, float $amount): string { + // 1. 入力コンテンツタイプを確認します(オプションですが推奨) + // null(可変入力)またはプレーンテキストを許可します。HTMLなどに適用された場合は拒否します。 + if (!in_array($info->contentType, [null, ContentType::Text], true)) { + $actualType = $info->contentType ?? 'mixed'; + throw new \RuntimeException( + "フィルタ |money は互換性のないコンテンツタイプ $actualType で使用されました。テキストまたはnullが必要です。" + ); + } + + // 2. 変換を実行します + $formatted = number_format($amount, 2, '.', ',') . ' EUR'; + $htmlOutput = '' . htmlspecialchars($formatted) . ''; // 適切なエスケープを確保してください! + + // 3. 出力コンテンツタイプを宣言します + $info->contentType = ContentType::Html; + + // 4. 結果を返します + return $htmlOutput; +}); +``` + +`$info->contentType` は `Latte\ContentType` の文字列定数(例:`ContentType::Html`、`ContentType::Text`、`ContentType::JavaScript` など)またはフィルタが変数(`{$var|filter}`)に適用される場合は `null` です。この値を**読み取り**、入力コンテキストを確認し、**書き込み**して出力コンテキストタイプを宣言できます。 + +コンテンツタイプをHTMLに設定することで、フィルタによって返される文字列が安全なHTMLであることをLatteに伝えます。Latteは、この結果にデフォルトの自動エスケープを**適用しません**。これは、フィルタがHTMLマークアップを生成する場合に不可欠です。 + +.[warning] +フィルタがHTMLを生成する場合、そのHTMLで使用される**入力データを適切にエスケープする責任があります**(上記の `htmlspecialchars($formatted)` の呼び出しの場合のように)。これを怠ると、XSS脆弱性が生じる可能性があります。フィルタがプレーンテキストのみを返す場合は、`$info->contentType` を設定する必要はありません。 + + +ブロック上のフィルタ +---------- + +[ブロック |tags#block]に適用されるすべてのフィルタは、コンテキスト認識型で*なければなりません*。これは、ブロックのコンテンツには定義されたコンテンツタイプ(通常はHTML)があり、フィルタがそれを認識する必要があるためです。 + +```latte +{block heading|money}1000{/block} +{* フィルタ 'money' は2番目の引数として '1000' を受け取ります + そして $info->contentType は ContentType::Html になります *} +``` + +コンテキストフィルタは、データがそのコンテキストに基づいてどのように処理されるかを強力に制御し、高度な機能を可能にし、特にHTMLコンテンツを生成する場合に適切なエスケープ動作を保証します。 diff --git a/latte/ja/custom-functions.texy b/latte/ja/custom-functions.texy new file mode 100644 index 0000000000..d85eba91de --- /dev/null +++ b/latte/ja/custom-functions.texy @@ -0,0 +1,144 @@ +カスタム関数の作成 +********* + +.[perex] +Latteテンプレートにカスタムヘルパー関数を簡単に追加できます。計算、サービスへのアクセス、または動的コンテンツの生成のために、式内で直接PHPロジックを呼び出し、テンプレートをクリーンで強力に保ちます。 + + +関数とは? +===== + +Latteの関数を使用すると、テンプレート内の式(`{...}`)内で呼び出すことができる関数のセットを拡張できます。これらは、**Latteテンプレート内でのみ利用可能なカスタムPHP関数**と考えることができます。これにはいくつかの利点があります: + +**利便性:** ヘルパーロジック(計算、フォーマット、アプリケーションデータへのアクセスなど)を定義し、PHPで `strlen()` や `date()` を呼び出すのと同じように、テンプレート内で直接、シンプルで使い慣れた関数構文を使用して呼び出すことができます。 + +```latte +{var $userInitials = initials($userName)} {* 例:'J. D.' *} + +{if hasPermission('article', 'edit')} + Edit +{/if} +``` + +**グローバルスコープの汚染なし:** PHPで実際のグローバル関数を定義するのとは異なり、Latte関数はテンプレートレンダリングコンテキスト内でのみ存在します。テンプレート固有のヘルパーでPHPグローバル名前空間を汚染する必要はありません。 + +**アプリケーションロジックとの統合:** Latte関数の背後にあるPHP callableは、クロージャ、静的メソッド、またはインスタンスメソッドなど、何でもかまいません。これは、テンプレート内の関数が、変数をキャプチャする(クロージャの場合)か、依存性注入を使用する(オブジェクトの場合)ことによって、アプリケーションサービス、データベース、設定、またはその他の必要なロジックに簡単にアクセスできることを意味します。上記の `hasPermission` の例は、おそらくバックグラウンドで認証サービスを呼び出すことによって、これを明確に示しています。 + +**ネイティブ関数の上書き(オプション):** ネイティブPHP関数と同じ名前のLatte関数を定義することもできます。テンプレートでは、元の関数の代わりにカスタムバージョンが呼び出されます。これは、テンプレート固有の動作を提供したり、一貫した処理を保証したりするのに役立ちます(たとえば、`strlen` が常にマルチバイトセーフであることを保証するなど)。誤解を避けるために、この機能は慎重に使用してください。 + +デフォルトでは、Latteは*すべて*のネイティブPHP関数の呼び出しを許可します([Sandbox |sandbox]によって制限されていない限り)。カスタム関数は、プロジェクト固有のニーズに合わせてこの組み込みライブラリを拡張します。 + +単一の値を変換するだけの場合は、[カスタムフィルタ |custom-filters]を使用する方が適切な場合があります。 + + +関数の作成と登録 +======== + +フィルタと同様に、カスタム関数を定義および登録するにはいくつかの方法があります。 + + +`addFunction()` を使用した直接登録 +------------------------- + +最も簡単な方法は、`Latte\Engine` オブジェクトで `addFunction()` を使用することです。関数名(テンプレートに表示される名前)と対応するPHP callableを指定します。 + +```php +$latte = new Latte\Engine; + +// シンプルなヘルパー関数 +$latte->addFunction('initials', function (string $name): string { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; +}); +``` + +**テンプレートでの使用:** + +```latte +{var $userInitials = initials($userName)} +``` + +テンプレート内の関数引数は、PHP callableに同じ順序で直接渡されます。型ヒント、デフォルト値、可変長引数(`...`)などのPHP機能は期待どおりに機能します。 + + +拡張機能による登録 +--------- + +より良い整理と再利用性のために、[Latte拡張機能 |extending-latte#Latte Extension]内で関数を登録します。このアプローチは、より複雑なアプリケーションや共有ライブラリに推奨されます。 + +```php +namespace App\Latte; + +use Latte\Extension; +use Nette\Security\Authorizator; + +class MyLatteExtension extends Extension +{ + public function __construct( + // Authorizatorサービスが存在すると仮定します + private Authorizator $authorizator, + ) { + } + + public function getFunctions(): array + { + // メソッドをLatte関数として登録します + return [ + 'hasPermission' => $this->hasPermission(...), + ]; + } + + public function hasPermission(string $resource, string $action): bool + { + return $this->authorizator->isAllowed($resource, $action); + } +} + +// 登録($containerがDICを含むと仮定します) +$extension = $container->getByType(App\Latte\MyLatteExtension::class); +$latte = new Latte\Engine; +$latte->addExtension($extension); +``` + +このアプローチは、Latteで定義された関数が、アプリケーションの依存性注入コンテナまたはファクトリによって管理される独自の依存関係を持つことができるオブジェクトメソッドによってどのようにバックアップされるかを明確に示しています。これにより、テンプレートロジックがアプリケーションコアに接続され、クリーンな整理が維持されます。 + + +属性付きクラスを使用した関数 +-------------- + +フィルタと同様に、関数は `#[Latte\Attributes\TemplateFunction]` 属性を使用して[テンプレートパラメータクラス |develop#クラスとしてのパラメータ]のメソッドとして定義できます。 + +```php +use Latte\Attributes\TemplateFunction; + +class TemplateParameters +{ + public function __construct( + public string $userName, + // その他のパラメータ... + ) {} + + // このメソッドはテンプレート内で {initials(...)} として利用可能になります + #[TemplateFunction] + public function initials(string $name): string + { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; + } +} + +// オブジェクトをテンプレートに渡す +$params = new TemplateParameters(userName: 'John Doe', /* ... */); +$latte->render('template.latte', $params); +``` + +パラメータオブジェクトがテンプレートに渡されると、Latteはこの属性でマークされたメソッドを自動的に検出して登録します。テンプレート内の関数名はメソッド名に対応します。 + +```latte +{* パラメータクラスで定義された関数を使用する *} +{var $inits = initials($userName)} +``` + +**コンテキスト関数?** + +フィルタとは異なり、`FilterInfo` に似たオブジェクトを受け取る「コンテキスト関数」の直接的な概念はありません。関数は式内で動作し、通常、ブロックに適用されるフィルタと同じ方法でレンダリングコンテキストまたはコンテンツタイプ情報への直接アクセスを必要としません。 diff --git a/latte/ja/custom-tags.texy b/latte/ja/custom-tags.texy new file mode 100644 index 0000000000..a3114b7964 --- /dev/null +++ b/latte/ja/custom-tags.texy @@ -0,0 +1,1135 @@ +カスタムタグの作成 +********* + +.[perex] +このページでは、Latteでカスタムタグを作成するための包括的なガイドを提供します。Latteがテンプレートをどのようにコンパイルするかについての理解を基に、単純なタグから、ネストされたコンテンツや特定の解析ニーズを持つより複雑なシナリオまで、すべてを説明します。 + +カスタムタグは、テンプレートの構文とレンダリングロジックに対する最高レベルの制御を提供しますが、拡張機能としては最も複雑な点でもあります。[より簡単な解決策が存在しないか |extending-latte#Latteを拡張する方法]、または適切なタグがすでに[標準セット |tags]に存在しないかを常に検討してください。カスタムタグは、ニーズに対してより簡単な代替手段が不十分な場合にのみ使用してください。 + + +コンパイルプロセスの理解 +============ + +カスタムタグを効果的に作成するためには、Latteがテンプレートをどのように処理するかを説明することが役立ちます。このプロセスを理解することで、タグがなぜこのように構造化されているのか、そしてそれらがより広いコンテキストにどのように適合するのかが明らかになります。 + +Latteでのテンプレートのコンパイルは、簡単に言うと、以下の主要なステップを含みます: + +1. **字句解析:** レキサー(Lexer)はテンプレートのソースコード(`.latte`ファイル)を読み取り、それを**トークン**(例:`{`、`foreach`、`$variable`、`}`、HTMLテキストなど)と呼ばれる小さく、区別可能な部分のシーケンスに分割します。 +2. **構文解析:** パーサー(Parser)はこのトークンのストリームを受け取り、テンプレートのロジックとコンテンツを表す意味のあるツリー構造を構築します。このツリーは**抽象構文木(AST)**と呼ばれます。 +3. **コンパイルパス:** PHPコードを生成する前に、Latteは[コンパイルパス |compiler passes]を実行します。これらはAST全体を走査し、それを変更したり情報を収集したりできる関数です。このステップは、セキュリティ([Sandbox |sandbox])や最適化などの機能にとって重要です。 +4. **コード生成:** 最後に、コンパイラは(潜在的に変更された)ASTを走査し、対応するPHPクラスコードを生成します。このPHPコードが、実行時に実際にテンプレートをレンダリングするものです。 +5. **キャッシング:** 生成されたPHPコードはディスクに保存され、ステップ1〜4がスキップされるため、後続のレンダリングが非常に高速になります。 + +実際には、コンパイルはもう少し複雑です。Latteには**2つの**レキサーとパーサーがあります。1つはHTMLテンプレート用、もう1つはタグ内のPHPライクなコード用です。また、構文解析はトークン化の後に行われるのではなく、レキサーとパーサーは2つの「スレッド」で並行して実行され、連携します。信じてください、これをプログラムするのはロケット科学でした :-) + +テンプレートコンテンツの読み込みから、解析、最終的なファイルの生成までの全プロセスは、以下のコードでシーケンス化できます。これを使って実験し、中間結果を出力することができます: + +```php +$latte = new Latte\Engine; +$source = $latte->getLoader()->getContent($file); +$ast = $latte->parse($source); +$latte->applyPasses($ast); +$code = $latte->generate($ast, $file); +``` + + +タグの構造 +===== + +Latteで完全に機能するカスタムタグを作成するには、いくつかの連携する部分が含まれます。実装に入る前に、HTMLとDocument Object Model(DOM)との類推を使用して、基本的な概念と用語を理解しましょう。 + + +タグ vs ノード (HTMLとの類推) +-------------------- + +HTMLでは、`

                                                                                  `や`

                                                                                  ...
                                                                                  `のような**タグ**を書きます。これらのタグはソースコード内の構文です。ブラウザがこのHTMLを解析すると、**Document Object Model(DOM)**と呼ばれるメモリ内の表現を作成します。DOMでは、HTMLタグは**ノード**(具体的にはJavaScript DOMの用語で`Element`ノード)によって表されます。これらの*ノード*をプログラムで操作します(例:JavaScriptの`document.getElementById(...)`はElementノードを返します)。タグはソースファイル内のテキスト表現にすぎません。ノードは論理ツリー内のオブジェクト表現です。 + +Latteも同様に機能します: + +- `.latte`テンプレートファイルでは、`{foreach ...}`や`{/foreach}`のような**Latteタグ**を書きます。これは、テンプレートの作者としてあなたが扱う構文です。 +- Latteがテンプレートを**解析**すると、**抽象構文木(AST)**を構築します。このツリーは**ノード**で構成されます。テンプレート内の各Latteタグ、HTML要素、テキストの一部、または式は、このツリー内の1つ以上のノードになります。 +- AST内のすべてのノードの基本クラスは`Latte\Compiler\Node`です。DOMにさまざまなタイプのノード(Element、Text、Comment)があるように、LatteのASTにもさまざまなタイプのノードがあります。静的テキスト用の`Latte\Compiler\Nodes\TextNode`、HTML要素用の`Latte\Compiler\Nodes\Html\ElementNode`、タグ内の式用の`Latte\Compiler\Nodes\Php\ExpressionNode`、そしてカスタムタグにとって重要な、`Latte\Compiler\Nodes\StatementNode`から継承するノードに出会うでしょう。 + + +なぜ `StatementNode` なのか? +----------------------- + +HTML要素(`Html\ElementNode`)は主に構造とコンテンツを表します。PHP式(`Php\ExpressionNode`)は値や計算を表します。しかし、`{if}`、`{foreach}`、または私たちのカスタム`{datetime}`のようなLatteタグはどうでしょうか?これらのタグは*アクションを実行*し、プログラムの流れを制御したり、ロジックに基づいて出力を生成したりします。これらはLatteを単なるマークアップ言語ではなく、強力なテンプレート*エンジン*にする機能的な単位です。 + +プログラミングでは、アクションを実行するこのような単位はしばしば「ステートメント」(文)と呼ばれます。そのため、これらの機能的なLatteタグを表すノードは、通常`Latte\Compiler\Nodes\StatementNode`から継承します。これにより、純粋に構造的なノード(HTML要素など)や値を表すノード(式など)と区別されます。 + + +主要なコンポーネント +========== + +カスタムタグを作成するために必要な主要なコンポーネントを見ていきましょう: + + +タグ解析関数 +------ + +- このPHPコーラブル関数は、ソーステンプレート内のLatteタグ(`{...}`)の構文を解析します。 +- タグに関する情報(名前、位置、n:属性かどうかなど)を[api:Latte\Compiler\Tag]オブジェクトを通じて受け取ります。 +- タグの区切り文字内の引数や式を解析するための主要なツールは、[api:Latte\Compiler\TagParser]オブジェクトであり、`$tag->parser`を通じてアクセスできます(これはテンプレート全体を解析するパーサーとは異なります)。 +- ペアタグの場合、`yield`を使用してLatteに開始タグと終了タグの間の内部コンテンツを解析するように指示します。 +- 解析関数の最終的な目標は、ASTに追加される**ノードクラス**のインスタンスを作成して返すことです。 +- (必須ではありませんが)解析関数を対応するノードクラス内に直接静的メソッド(しばしば`create`と呼ばれる)として実装するのが慣例です。これにより、解析ロジックとノード表現がきれいに1つのパッケージにまとめられ、必要に応じてクラスのプライベート/プロテクテッドメンバーにアクセスでき、整理が向上します。 + + +ノードクラス +------ + +- **抽象構文木(AST)**におけるタグの*論理的な機能*を表します。 +- 解析された情報(引数やコンテンツなど)をパブリックプロパティとして含みます。これらのプロパティはしばしば他の`Node`インスタンスを含みます(例:解析された引数用の`ExpressionNode`、解析されたコンテンツ用の`AreaNode`)。 +- `print(PrintContext $context): string`メソッドは、テンプレートのレンダリング中にタグのアクションを実行する*PHPコード*(ステートメントまたは一連のステートメント)を生成します。 +- `getIterator(): \Generator`メソッドは、**コンパイルパス**による走査のために子ノード(引数、コンテンツ)を公開します。パスが潜在的にサブノードを変更または置換できるように、参照(`&`)を提供する必要があります。 +- テンプレート全体がASTに解析された後、Latteは一連の[コンパイルパス |compiler-passes]を実行します。これらのパスは、各ノードによって提供される`getIterator()`メソッドを使用して*AST全体*を走査します。ノードを検査し、情報を収集し、さらにはツリーを*変更*することができます(例:ノードのパブリックプロパティを変更したり、ノードを完全に置き換えたり)。複雑な`getIterator()`を必要とするこの設計は不可欠です。[Sandbox |sandbox]のような強力な機能が、カスタムタグを含むテンプレートの*任意*の部分の動作を分析し、潜在的に変更することを可能にし、安全性と一貫性を確保します。 + + +拡張機能による登録 +--------- + +- 新しいタグと、それに対して使用する解析関数についてLatteに通知する必要があります。これは[Latte拡張機能 |extending-latte#Latte Extension]内で行われます。 +- 拡張機能クラス内で`getTags(): array`メソッドを実装します。このメソッドは連想配列を返し、キーはタグ名(例:`'mytag'`、`'n:myattribute'`)、値はその対応する解析関数を表すPHPコーラブル関数(例:`MyNamespace\DatetimeNode::create(...)`)です。 + +要約:**タグ解析関数**は、タグの*テンプレートソースコード*を**ASTノード**に変換します。**ノードクラス**は、*自身*をコンパイル済みテンプレート用の実行可能な*PHPコード*に変換し、`getIterator()`を通じて**コンパイルパス**のためにそのサブノードを公開します。**拡張機能による登録**は、タグ名を解析関数にリンクし、Latteにそれを知らせます。 + +次に、これらのコンポーネントを段階的に実装する方法を探ります。 + + +シンプルなタグの作成 +========== + +最初のカスタムLatteタグの作成に取り掛かりましょう。非常に簡単な例から始めます:現在の日付と時刻を出力する`{datetime}`という名前のタグです。**最初は、このタグは引数を受け付けません**が、後で[#タグ引数の解析]セクションで改善します。また、内部コンテンツもありません。 + +この例では、基本的な手順を説明します:ノードクラスの定義、`print()`および`getIterator()`メソッドの実装、解析関数の作成、そして最後にタグの登録です。 + +**目標:** PHP関数`date()`を使用して現在の日付と時刻を出力する`{datetime}`を実装します。 + + +ノードクラスの作成 +--------- + +まず、抽象構文木(AST)でタグを表すクラスが必要です。上で説明したように、`Latte\Compiler\Nodes\StatementNode`から継承します。 + +ファイル(例:`DatetimeNode.php`)を作成し、クラスを定義します: + +```php +node = new self; + return $node; + } + + /** + * テンプレートレンダリング時に実行されるPHPコードを生成します。 + */ + public function print(PrintContext $context): string + { + return $context->format( + 'echo date(\'Y-m-d H:i:s\') %line;', + $this->position, + ); + } + + /** + * Latteコンパイルパスのために子ノードへのアクセスを提供します。 + */ + public function &getIterator(): \Generator + { + false && yield; + } +} +``` + +Latteがテンプレート内で`{datetime}`に遭遇すると、解析関数`create()`を呼び出します。その仕事は`DatetimeNode`のインスタンスを返すことです。 + +`print()`メソッドは、テンプレートのレンダリング時に実行されるPHPコードを生成します。`$context->format()`メソッドを呼び出し、コンパイル済みテンプレート用の最終的なPHPコード文字列を構築します。最初の引数`'echo date('Y-m-d H:i:s') %line;'`は、後続のパラメータが補完されるマスクです。プレースホルダー`%line`は、`format()`メソッドに2番目の引数である`$this->position`を使用し、`/* line 15 */`のようなコメントを挿入するように指示します。これにより、生成されたPHPコードが元のテンプレート行にリンクされ、デバッグに不可欠です。 + +`$this->position`プロパティは基本クラス`Node`から継承され、Latteパーサーによって自動的に設定されます。これには、タグがソース`.latte`ファイル内で見つかった場所を示す[api:Latte\Compiler\Position]オブジェクトが含まれます。 + +`getIterator()`メソッドはコンパイルパスにとって不可欠です。すべての子ノードを提供する必要がありますが、私たちのシンプルな`DatetimeNode`は現在、引数もコンテンツも持たないため、子ノードはありません。ただし、メソッドは存在し、ジェネレータである必要があります。つまり、`yield`キーワードがメソッド本体のどこかに存在する必要があります。 + + +拡張機能による登録 +--------- + +最後に、Latteに新しいタグについて通知しましょう。[拡張機能クラス |extending-latte#Latte Extension](例:`MyLatteExtension.php`)を作成し、その`getTags()`メソッドでタグを登録します。 + +```php + マップ: 'タグ名' => 解析関数 + */ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + // 後でここにより多くのタグを登録します + ]; + } +} +``` + +次に、この拡張機能をLatte Engineに登録します: + +```php +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +テンプレートを作成します: + +```latte +

                                                                                  ページ生成日時: {datetime}

                                                                                  +``` + +期待される出力:`

                                                                                  ページ生成日時: 2023-10-27 11:00:00

                                                                                  ` + + +このフェーズの要約 +--------- + +基本的なカスタムタグ`{datetime}`の作成に成功しました。ASTでの表現(`DatetimeNode`)を定義し、その解析(`create()`)を処理し、PHPコードを生成する方法(`print()`)を指定し、その子が走査可能であることを確認し(`getIterator()`)、Latteに登録しました。 + +次のセクションでは、このタグを引数を受け入れるように強化し、式を解析し、子ノードを管理する方法を示します。 + + +タグ引数の解析 +======= + +シンプルな`{datetime}`タグは機能しますが、あまり柔軟ではありません。`date()`関数のフォーマット文字列というオプションの引数を受け入れるように強化しましょう。必要な構文は`{datetime $format}`になります。 + +**目標:** `date()`のフォーマット文字列として使用されるオプションのPHP式を引数として受け入れるように`{datetime}`を変更します。 + + +`TagParser`の紹介 +-------------- + +コードを変更する前に、使用するツール[api:Latte\Compiler\TagParser]を理解することが重要です。メインのLatteパーサー(`TemplateParser`)が`{datetime ...}`やn:属性のようなLatteタグに遭遇すると、タグ*内部*のコンテンツ(`{`と`}`の間、または属性値)の解析を専門の`TagParser`に委譲します。 + +この`TagParser`は**タグ引数**のみを扱います。その仕事は、これらの引数を表すトークンを処理することです。重要なのは、**提供されたすべてのコンテンツを処理しなければならない**ということです。解析関数が終了しても`TagParser`が引数の終わりに達していない場合(`$tag->parser->isEnd()`でチェック)、Latteは例外をスローします。これは、タグ内に予期しないトークンが残っていることを示します。逆に、タグが引数を*必要とする*場合は、解析関数の最初に`$tag->expectArguments()`を呼び出す必要があります。このメソッドは引数が存在するかどうかをチェックし、タグが引数なしで使用された場合に役立つ例外をスローします。 + +`TagParser`は、さまざまな種類の引数を解析するための便利なメソッドを提供します: + +- `parseExpression(): ExpressionNode`: PHPライクな式(変数、リテラル、演算子、関数/メソッド呼び出しなど)を解析します。単純な英数字文字列を引用符で囲まれた文字列として扱うなど、Latteのシンタックスシュガーを処理します(例:`foo`は`'foo'`であるかのように解析されます)。 +- `parseUnquotedStringOrExpression(): ExpressionNode`: 標準的な式または*引用符なし文字列*のいずれかを解析します。引用符なし文字列は、Latteが引用符なしで許可するシーケンスであり、ファイルパス(例:`{include ../file.latte}`)などによく使用されます。引用符なし文字列を解析した場合、`StringNode`を返します。 +- `parseArguments(): ArrayNode`: `10, name: 'John', true`のように、キーを持つ可能性のあるカンマ区切りの引数を解析します。 +- `parseModifier(): ModifierNode`: `|upper|truncate:10`のようなフィルタを解析します。 +- `parseType(): ?SuperiorTypeNode`: `int`、`?string`、`array|Foo`のようなPHPタイプヒントを解析します。 + +より複雑な、または低レベルの解析ニーズのために、`$tag->parser->stream`を通じて[トークンストリーム |api:Latte\Compiler\TokenStream]と直接対話できます。このオブジェクトは、個々のトークンを検査および処理するためのメソッドを提供します: + +- `$tag->parser->stream->is(...): bool`: *現在の*トークンが指定されたタイプ(例:`Token::Php_Variable`)またはリテラル値(例:`'as'`)のいずれかに一致するかどうかを、消費せずにチェックします。先読みするのに便利です。 +- `$tag->parser->stream->consume(...): Token`: *現在の*トークンを消費し、ストリームの位置を進めます。期待されるトークンタイプ/値が引数として提供され、現在のトークンが一致しない場合、`CompileException`をスローします。特定のトークンを*期待する*場合に使用します。 +- `$tag->parser->stream->tryConsume(...): ?Token`: *現在の*トークンが指定されたタイプ/値のいずれかに一致する場合*のみ*、それを消費しようとします。一致する場合、トークンを消費して返します。一致しない場合、ストリームの位置を変更せずに`null`を返します。オプションのトークンや、異なる構文パス間で選択する場合に使用します。 + + +解析関数 `create()` の更新 +------------------- + +この理解をもとに、`DatetimeNode`の`create()`メソッドを修正して、`$tag->parser`を使用してオプションのフォーマット引数を解析するようにしましょう。 + +```php +node = new self; + + // トークンが存在するかどうかを確認 + if (!$tag->parser->isEnd()) { + // TagParserを使用して引数をPHPライクな式として解析します。 + $node->format = $tag->parser->parseExpression(); + } + + return $node; + } + + // ... print() と getIterator() メソッドは後で更新されます ... +} +``` + +パブリックプロパティ`$format`を追加しました。`create()`では、`$tag->parser->isEnd()`を使用して引数が*存在するか*どうかをチェックします。存在する場合、`$tag->parser->parseExpression()`が式のトークンを処理します。`TagParser`はすべての入力トークンを処理する必要があるため、ユーザーがフォーマット式の後に予期しないものを記述した場合(例:`{datetime 'Y-m-d', unexpected}`)、Latteは自動的にエラーをスローします。 + + +`print()` メソッドの更新 +----------------- + +次に、`$this->format`に格納されている解析されたフォーマット式を使用するように`print()`メソッドを修正しましょう。フォーマットが提供されなかった場合(`$this->format`が`null`の場合)、デフォルトのフォーマット文字列(例:`'Y-m-d H:i:s'`)を使用する必要があります。 + +```php + public function print(PrintContext $context): string + { + $formatNode = $this->format ?? new StringNode('Y-m-d H:i:s'); + + // %nodeは$formatNodeのPHPコード表現を出力します。 + return $context->format( + 'echo date(%node) %line;', + $formatNode, + $this->position + ); + } +``` + +変数`$formatNode`に、PHP関数`date()`のフォーマット文字列を表すASTノードを格納します。ここではnull合体演算子(`??`)を使用しています。ユーザーがテンプレートで引数を提供した場合(例:`{datetime 'd.m.Y'}`)、`$this->format`プロパティには対応するノード(この場合は値`'d.m.Y'`を持つ`StringNode`)が含まれ、このノードが使用されます。ユーザーが引数を提供しなかった場合(単に`{datetime}`と記述した場合)、`$this->format`プロパティは`null`であり、代わりにデフォルトのフォーマット`'Y-m-d H:i:s'`を持つ新しい`StringNode`を作成します。これにより、`$formatNode`には常にフォーマット用の有効なASTノードが含まれることが保証されます。 + +マスク`'echo date(%node) %line;'`では、新しいプレースホルダー`%node`が使用されています。これは`format()`メソッドに、次の引数(私たちの`$formatNode`)を取り、その`print()`メソッド(PHPコード表現を返す)を呼び出し、その結果をプレースホルダーの位置に挿入するように指示します。 + + +サブノードのための `getIterator()` の実装 +----------------------------- + +`DatetimeNode`には子ノード `$format` 式があります。**必ず** `getIterator()` メソッドで提供することにより、この子ノードをコンパイルパスに公開する必要があります。パスがノードを潜在的に置き換えることができるように、*参照* (`&`) を提供することを忘れないでください。 + +```php + public function &getIterator(): \Generator + { + if ($this->format) { + yield $this->format; + } + } +``` + +なぜこれが不可欠なのでしょうか?Sandboxパスが、`$format`引数に禁止された関数呼び出しが含まれていないか(例:`{datetime dangerousFunction()}`)をチェックする必要があると想像してみてください。`getIterator()`が`$this->format`を提供しない場合、Sandboxパスはタグの引数内の`dangerousFunction()`呼び出しを決して見ることができず、潜在的なセキュリティホールが作成されます。提供することで、Sandbox(および他のパス)が`$format`式ノードを検査し、潜在的に変更することを可能にします。 + + +強化されたタグの使用 +---------- + +タグはオプションの引数を正しく処理するようになりました: + +```latte +デフォルトフォーマット: {datetime} +カスタムフォーマット: {datetime 'd.m.Y'} +変数の使用: {datetime $userDateFormatPreference} + +{* これは 'd.m.Y' の解析後にエラーを引き起こします。 ", foo" は予期されていません *} +{* {datetime 'd.m.Y', foo} *} +``` + +次に、ペアタグの作成を見ていきましょう。これは、それらの間のコンテンツを処理します。 + + +ペアタグの処理 +======= + +これまでのところ、`{datetime}`タグは*自己完結型*(概念的に)でした。開始タグと終了タグの間にコンテンツはありません。しかし、多くの便利なタグはテンプレートコンテンツのブロックを操作します。これらは**ペアタグ**と呼ばれます。例としては、`{if}...{/if}`、`{block}...{/block}`、またはこれから作成するカスタムタグ`{debug}...{/debug}`があります。 + +このタグを使用すると、開発中にのみ表示されるべきデバッグ情報をテンプレートに含めることができます。 + +**目標:** 特定の「開発モード」フラグがアクティブな場合にのみコンテンツがレンダリングされるペアタグ`{debug}`を作成します。 + + +プロバイダの紹介 +-------- + +タグが、テンプレートパラメータとして直接渡されないデータやサービスにアクセスする必要がある場合があります。たとえば、アプリケーションが開発モードであるかどうかを判断したり、ユーザーオブジェクトにアクセスしたり、設定値を取得したりする場合です。Latteはこの目的のために**プロバイダ**(Providers)と呼ばれるメカニズムを提供します。 + +プロバイダは、`getProviders()`メソッドを使用して[拡張機能 |extending-latte#Latte Extension]に登録されます。このメソッドは連想配列を返し、キーはテンプレートのランタイムコードでプロバイダにアクセスする名前、値は実際のデータまたはオブジェクトです。 + +タグの`print()`メソッドによって生成されたPHPコード内では、特別な`$this->global`オブジェクトプロパティを通じてこれらのプロバイダにアクセスできます。 このプロパティはすべての拡張機能で共有されるため、Latteのコアプロバイダや他のサードパーティ拡張機能からのプロバイダとの潜在的な名前の衝突を避けるために、**プロバイダ名にプレフィックスを付ける**ことが良い習慣です。一般的な慣例は、ベンダーまたは拡張機能名に関連する短く、一意のプレフィックスを使用することです。例として、プレフィックス`app`を使用し、開発モードフラグは`$this->global->appDevMode`として利用可能になります。 + + +コンテンツ解析のための `yield` キーワード +------------------------- + +`{debug}`と`{/debug}`の*間*のコンテンツを処理するようにLatteパーサーに指示するにはどうすればよいでしょうか?ここで`yield`キーワードが登場します。 + +`create()`関数内で`yield`が使用されると、関数は[PHPジェネレータ |https://www.php.net/manual/en/language.generators.overview.php]になります。その実行は一時停止され、制御はメインの`TemplateParser`に戻ります。`TemplateParser`は、対応する終了タグ(この場合は`{/debug}`)に遭遇する*まで*テンプレートコンテンツの解析を続行します。 + +終了タグが見つかると、`TemplateParser`は`yield`ステートメントの直後で`create()`関数の実行を再開します。`yield`ステートメントによって*返される*値は、2つの要素を含む配列です: + +1. 開始タグと終了タグの間で解析されたコンテンツを表す`AreaNode`。 +2. 終了タグ(例:`{/debug}`)を表す`Tag`オブジェクト。 + +`DebugNode`クラスとその`create`メソッドを作成し、`yield`を利用しましょう。 + +```php +node = new self; + + // 解析を一時停止し、{/debug}が見つかったときに内部コンテンツと終了タグを取得します + [$node->content, $endTag] = yield; + + return $node; + } + + // ... print() と getIterator() は後で実装されます ... +} +``` + +注意:タグがn:属性として使用されている場合、つまり`
                                                                                  ...
                                                                                  `の場合、`$endTag`は`null`になります。 + + +条件付きレンダリングのための `print()` の実装 +---------------------------- + +`print()`メソッドは、実行時に`appDevMode`プロバイダをチェックし、フラグがtrueの場合にのみ内部コンテンツのコードを実行するPHPコードを生成する必要があります。 + +```php + public function print(PrintContext $context): string + { + // 実行時にプロバイダをチェックするPHP 'if' ステートメントを生成します + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + // 開発モードの場合、内部コンテンツを出力します + %node + } + + XX, + $this->position, // %line コメント用 + $this->content, // 内部コンテンツのASTを含むノード + ); + } +``` + +これは簡単です。`PrintContext::format()`を使用して標準的なPHP `if`ステートメントを作成します。`if`の内側に、`$this->content`のプレースホルダー`%node`を配置します。Latteは再帰的に`$this->content->print($context)`を呼び出してタグの内部部分のPHPコードを生成しますが、これは`$this->global->appDevMode`が実行時にtrueと評価された場合に限ります。 + + +コンテンツのための `getIterator()` の実装 +----------------------------- + +前の例の引数ノードと同様に、`DebugNode`には子ノード `AreaNode $content` があります。`getIterator()`で提供することにより、これを公開する必要があります: + +```php + public function &getIterator(): \Generator + { + // コンテンツノードへの参照を提供します + yield $this->content; + } +``` + +これにより、コンパイルパスが`{debug}`タグのコンテンツに降りていくことができます。これは、コンテンツが条件付きでレンダリングされる場合でも重要です。たとえば、Sandboxは`appDevMode`がtrueかfalseかに関係なくコンテンツを分析する必要があります。 + + +登録と使用法 +------ + +拡張機能でタグとプロバイダを登録します: + +```php +class MyLatteExtension extends Extension +{ + // $isDevelopmentMode がどこかで決定されると仮定します(例:設定から) + public function __construct( + private bool $isDevelopmentMode, + ) { + } + + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), // 新しいタグの登録 + ]; + } + + public function getProviders(): array + { + return [ + 'appDevMode' => $this->isDevelopmentMode, // プロバイダの登録 + ]; + } +} + +// 拡張機能を登録する際: +$isDev = true; // アプリケーションの環境に基づいてこれを決定します +$latte->addExtension(new App\Latte\MyLatteExtension($isDev)); +``` + +そしてテンプレートでの使用法: + +```latte +

                                                                                  常に表示される通常のコンテンツ。

                                                                                  + +{debug} +
                                                                                  + 現在のユーザーID: {$user->id} + リクエスト時間: {=time()} +
                                                                                  +{/debug} + +

                                                                                  その他の通常のコンテンツ。

                                                                                  +``` + + +n:属性の統合 +------- + +Latteは、多くのペアタグに対して便利な短縮記法を提供します:[n:属性 |syntax#n:属性]。`{tag}...{/tag}`のようなペアタグがあり、その効果を単一のHTML要素に直接適用したい場合、多くの場合、その要素の`n:tag`属性としてより簡潔に記述できます。 + +定義するほとんどの標準的なペアタグ(私たちの`{debug}`のような)に対して、Latteは対応する`n:`属性バージョンを自動的に有効にします。登録中に特別なことをする必要はありません: + +```latte +{* 標準的なペアタグの使用法 *} +{debug}
                                                                                  デバッグ情報
                                                                                  {/debug} + +{* n:属性を使用した同等の使用法 *} +
                                                                                  デバッグ情報
                                                                                  +``` + +どちらのバージョンも、`$this->global->appDevMode`がtrueの場合にのみ`
                                                                                  `をレンダリングします。`inner-`および`tag-`プレフィックスも期待どおりに機能します。 + +タグのロジックが、標準的なペアタグとして使用されるか、n:属性として使用されるか、または`n:inner-tag`や`n:tag-tag`のようなプレフィックスが使用されるかによって、わずかに異なる動作をする必要がある場合があります。解析関数`create()`に渡される`Latte\Compiler\Tag`オブジェクトは、この情報を提供します: + +- `$tag->isNAttribute(): bool`: タグがn:属性として解析されている場合は`true`を返します +- `$tag->prefix: ?string`: n:属性で使用されるプレフィックスを返します。これは`null`(n:属性ではない)、`Tag::PrefixNone`、`Tag::PrefixInner`、または`Tag::PrefixTag`のいずれかになります + +これで、単純なタグ、引数の解析、ペアタグ、プロバイダ、n:属性を理解したので、`{debug}`タグを出発点として使用し、他のタグ内にネストされたタグを含む、より複雑なシナリオに取り組みましょう。 + + +中間タグ +==== + +一部のペアタグは、最終的な終了タグの前に他のタグが*内部に*出現することを許可、あるいは要求さえします。これらは**中間タグ**と呼ばれます。古典的な例としては、`{if}...{elseif}...{else}...{/if}`や`{switch}...{case}...{default}...{/switch}`があります。 + +アプリケーションが開発モード*でない*場合にレンダリングされるオプションの`{else}`句をサポートするように、`{debug}`タグを拡張しましょう。 + +**目標:** オプションの中間タグ`{else}`をサポートするように`{debug}`を修正します。最終的な構文は`{debug} ... {else} ... {/debug}`であるべきです。 + + +`yield` を使用した中間タグの解析 +-------------------- + +`yield`が`create()`解析関数を一時停止し、解析されたコンテンツと終了タグを返すことはすでに知っています。しかし、`yield`はより多くの制御を提供します:*中間タグ名の配列*を提供できます。パーサーがこれらの指定されたタグのいずれかに**同じネストレベルで**(つまり、親タグの直接の子として、他のブロックやタグの内部ではなく)遭遇すると、解析も停止します。 + +中間タグのために解析が停止すると、コンテンツの解析を停止し、`create()`ジェネレータを再開し、部分的に解析されたコンテンツと**中間タグ**自体(最終的な終了タグの代わりに)を返します。`create()`関数は、この中間タグを処理し(例:引数があれば解析する)、*最終的な*終了タグまたは別の期待される中間タグまでの*次の*コンテンツ部分を解析するために再度`yield`を使用できます。 + +`{else}`を期待するように`DebugNode::create()`を修正しましょう: + +```php +node = new self; + + // yield して {/debug} または {else} のいずれかを期待します + [$node->thenContent, $nextTag] = yield ['else']; + + // 停止したタグが {else} であったかどうかを確認します + if ($nextTag?->name === 'else') { + // {else} と {/debug} の間のコンテンツを解析するために再度 yield します + [$node->elseContent, $endTag] = yield; + } + + return $node; + } + + // ... print() と getIterator() は後で更新されます ... +} +``` + +これで`yield ['else']`は、Latteに`{/debug}`だけでなく`{else}`でも解析を停止するように指示します。`{else}`が見つかった場合、`$nextTag`には`{else}`の`Tag`オブジェクトが含まれます。次に、引数なしで再度`yield`を使用します。これは、最終的な`{/debug}`タグのみを期待していることを意味し、結果を`$node->elseContent`に格納します。`{else}`が見つからなかった場合、`$nextTag`は`{/debug}`の`Tag`(またはn:属性として使用されている場合は`null`)になり、`$node->elseContent`は`null`のままになります。 + + +`{else}` を伴う `print()` の実装 +-------------------------- + +`print()`メソッドは新しい構造を反映する必要があります。`devMode`プロバイダに基づいてPHP `if/else`ステートメントを生成する必要があります。 + +```php + public function print(PrintContext $context): string + { + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + %node // 'then' ブランチのコード ({debug} コンテンツ) + } else { + %node // 'else' ブランチのコード ({else} コンテンツ) + } + + XX, + $this->position, // 'if' 条件の行番号 + $this->thenContent, // 最初の %node プレースホルダー + $this->elseContent ?? new NopNode, // 2番目の %node プレースホルダー + ); + } +``` + +これは標準的なPHP `if/else`構造です。`%node`を2回使用しています。`format()`は提供されたノードを順番に置き換えます。`$this->elseContent`が`null`の場合のエラーを回避するために`?? new NopNode`を使用しています – `NopNode`は単に何も出力しません。 + + +両方のコンテンツのための `getIterator()` の実装 +-------------------------------- + +潜在的に2つのコンテンツ子ノード(`$thenContent`と`$elseContent`)があります。存在する場合は両方を提供する必要があります: + +```php + public function &getIterator(): \Generator + { + yield $this->thenContent; + if ($this->elseContent) { + yield $this->elseContent; + } + } +``` + + +強化されたタグの使用 +---------- + +タグはオプションの`{else}`句で使用できるようになりました: + +```latte +{debug} +

                                                                                  devModeがONなのでデバッグ情報を表示しています。

                                                                                  +{else} +

                                                                                  devModeがOFFなのでデバッグ情報は非表示です。

                                                                                  +{/debug} +``` + + +状態とネストの処理 +========= + +以前の例(`{datetime}`、`{debug}`)は、`print()`メソッド内で比較的ステートレスでした。コンテンツを直接出力するか、グローバルプロバイダに基づいて単純な条件付きチェックを実行するかのいずれかでした。しかし、多くのタグは、レンダリング中に何らかの形の**状態**を管理する必要があるか、パフォーマンスや正確性のために一度だけ実行されるべきユーザー式の評価を含みます。さらに、カスタムタグが**ネスト**された場合に何が起こるかを考慮する必要があります。 + +これらの概念を説明するために、`{repeat $count}...{/repeat}`タグを作成しましょう。このタグは、内部コンテンツを`$count`回繰り返します。 + +**目標:** 指定された回数だけコンテンツを繰り返す`{repeat $count}`を実装します。 + + +一時的 & 一意な変数の必要性 +--------------- + +ユーザーが次のように書いたと想像してください: + +```latte +{repeat rand(1, 5)} コンテンツ {/repeat} +``` + +`print()`メソッドで単純にPHP `for`ループをこのように生成した場合: + +```php +// 簡略化された、誤った生成コード +for ($i = 0; $i < rand(1, 5); $i++) { + // コンテンツの出力 +} +``` +これは間違っています!`rand(1, 5)`式は**ループの各反復で再評価**され、予測不能な繰り返し回数につながります。ループを開始する前に`$count`式を*一度*評価し、その結果を保存する必要があります。 + +カウント式を最初に評価し、それを**一時的なランタイム変数**に格納するPHPコードを生成します。テンプレートユーザーによって定義された変数*および*Latteの内部変数(`$ʟ_...`など)との衝突を避けるために、一時変数には**`$__`(二重アンダースコア)**プレフィックスの規約を使用します。 + +生成されるコードは次のようになります: + +```php +$__count = rand(1, 5); +for ($__i = 0; $__i < $__count; $__i++) { + // コンテンツの出力 +} +``` + +次に、ネストを考えてみましょう: + +```latte +{repeat $countA} {* 外側のループ *} + {repeat $countB} {* 内側のループ *} + ... + {/repeat} +{/repeat} +``` + +外側と内側の両方の`{repeat}`タグが*同じ*一時変数名(例:`$__count`と`$__i`)を使用するコードを生成した場合、内側のループは外側のループの変数を上書きし、ロジックを壊します。 + +`{repeat}`タグの各インスタンスに対して生成される一時変数が**一意**であることを確認する必要があります。これは`PrintContext::generateId()`を使用して実現します。このメソッドは、コンパイルフェーズ中に一意の整数を返します。このIDを一時変数名に追加できます。 + +したがって、`$__count`の代わりに、最初のrepeatタグには`$__count_1`、2番目には`$__count_2`などを生成します。同様に、ループカウンタには`$__i_1`、`$__i_2`などを使用します。 + + +`RepeatNode` の実装 +---------------- + +ノードクラスを作成しましょう。 + +```php +expectArguments(); // $count が提供されていることを確認します + $node = $tag->node = new self; + // カウント式を解析します + $node->count = $tag->parser->parseExpression(); + // 内部コンテンツを取得します + [$node->content] = yield; + return $node; + } + + /** + * 一意な変数名を持つPHP 'for' ループを生成します。 + */ + public function print(PrintContext $context): string + { + // 一意な変数名を生成します + $id = $context->generateId(); + $countVar = '$__count_' . $id; // 例:$__count_1, $__count_2, など + $iteratorVar = '$__i_' . $id; // 例:$__i_1, $__i_2, など + + return $context->format( + <<<'XX' + // カウント式を *一度* 評価して保存します + %raw = (int) (%node); + // 保存されたカウントと一意なイテレータ変数を使用してループします + for (%raw = 0; %2.raw < %0.raw; %2.raw++) %line { + %node // 内部コンテンツをレンダリングします + } + + XX, + $countVar, // %0 - カウントを保存する変数 + $this->count, // %1 - カウントの式ノード + $iteratorVar, // %2 - ループイテレータ変数名 + $this->position, // %3 - ループ自体の行番号コメント + $this->content // %4 - 内部コンテンツノード + ); + } + + /** + * 子ノード(カウント式とコンテンツ)を提供します。 + */ + public function &getIterator(): \Generator + { + yield $this->count; + yield $this->content; + } +} +``` + +`create()`メソッドは、`parseExpression()`を使用して必須の`$count`式を解析します。最初に`$tag->expectArguments()`が呼び出されます。これにより、ユーザーが`{repeat}`の後に*何か*を提供したことが保証されます。`$tag->parser->parseExpression()`は何も提供されなければ失敗しますが、エラーメッセージは予期しない構文に関するものである可能性があります。`expectArguments()`を使用すると、`{repeat}`タグに引数が欠落していることを具体的に示す、はるかに明確なエラーが提供されます。 + +`print()`メソッドは、実行時に繰り返しロジックを実行する責任があるPHPコードを生成します。必要となる一時的なPHP変数の一意な名前を生成することから始まります。 + +`$context->format()`メソッドは、新しいプレースホルダー`%raw`と共に呼び出されます。これは、対応する引数として提供された*生の文字列*を挿入します。ここでは、`$countVar`に格納されている一意な変数名(例:`$__count_1`)を挿入します。そして`%0.raw`と`%2.raw`はどうでしょうか?これは**位置指定プレースホルダー**を示します。単に*次の*利用可能な生引数を取る`%raw`の代わりに、`%2.raw`は明示的にインデックス2の引数(`$iteratorVar`)を取り、その生の文字列値を挿入します。これにより、`format()`の引数リストで複数回渡すことなく、`$iteratorVar`文字列を再利用できます。 + +この慎重に構築された`format()`呼び出しは、カウント式を正しく処理し、`{repeat}`タグがネストされている場合でも変数名の衝突を回避する、効率的で安全なPHPループを生成します。 + + +登録と使用法 +------ + +拡張機能でタグを登録します: + +```php +use App\Latte\RepeatNode; + +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), // repeat タグの登録 + ]; + } +} +``` + +ネストを含め、テンプレートで使用します: + +```latte +{var $rows = rand(5, 7)} +{var $cols = rand(3, 5)} + +{repeat $rows} +
                                                                                  + {repeat $cols} + + {/repeat} + +{/repeat} +``` + +この例は、`$__`プレフィックス付きの一時変数と`PrintContext::generateId()`からの一意なIDを使用して、状態(ループカウンタ)と潜在的なネストの問題を処理する方法を示しています。 + + +純粋なn:属性 +------- + +`n:if`や`n:foreach`のような多くの`n:属性`は、ペアタグの対応物(`{if}...{/if}`、`{foreach}...{/foreach}`)の便利な短縮形として機能しますが、Latteはn:属性の形で*のみ*存在するタグを定義することもできます。これらは、しばしば、それらが付けられているHTML要素の属性や動作を変更するために使用されます。 + +Latteに組み込まれている標準的な例には、動的に`class`属性を構築するのに役立つ[`n:class` |tags#n:class]や、複数の任意の属性を設定できる[`n:attr` |tags#n:attr]があります。 + +独自の純粋なn:属性を作成しましょう:`n:confirm`。これは、アクション(リンクのフォローやフォームの送信など)を実行する前にJavaScriptの確認ダイアログを追加します。 + +**目標:** ユーザーが確認ダイアログをキャンセルした場合にデフォルトのアクションを防ぐ`onclick`ハンドラを追加する`n:confirm="'よろしいですか?'"`を実装します。 + + +`ConfirmNode` の実装 +----------------- + +Nodeクラスと解析関数が必要です。 + +```php +expectArguments(); + $node = $tag->node = new self; + $node->message = $tag->parser->parseExpression(); + return $node; + } + + /** + * 適切なエスケープを含む 'onclick' 属性コードを生成します。 + */ + public function print(PrintContext $context): string + { + // JavaScript と HTML 属性の両方のコンテキストで適切なエスケープを保証します。 + return $context->format( + <<<'XX' + echo ' onclick="', LR\Filters::escapeHtmlAttr('return confirm(' . LR\Filters::escapeJs(%node) . ')'), '"' %line; + XX, + $this->message, + $this->position, + ); + } + + public function &getIterator(): \Generator + { + yield $this->message; + } +} +``` + +`print()`メソッドは、最終的にテンプレートのレンダリング中にHTML属性`onclick="..."`を出力するPHPコードを生成します。ネストされたコンテキスト(HTML属性内のJavaScript)の処理には、慎重なエスケープが必要です。フィルタ`LR\Filters::escapeJs(%node)`は実行時に呼び出され、JavaScript内で使用するためにメッセージを適切にエスケープします(出力は`"Sure?"`のようになります)。次に、フィルタ`LR\Filters::escapeHtmlAttr(...)`はHTML属性で特別な文字をエスケープするため、出力を`return confirm("Sure?")`に変更します。 この2段階のランタイムエスケープにより、メッセージがJavaScriptに対して安全であり、結果のJavaScriptコードがHTML `onclick`属性に埋め込むのに安全であることが保証されます。 + + +登録と使用法 +------ + +拡張機能でn:属性を登録します。キーの`n:`プレフィックスを忘れないでください: + +```php +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), + 'n:confirm' => ConfirmNode::create(...), // n:confirm の登録 + ]; + } +} +``` + +これで、リンク、ボタン、またはフォーム要素で`n:confirm`を使用できます: + +```latte +削除 +``` + +生成されたHTML: + +```html +削除 +``` + +ユーザーがリンクをクリックすると、ブラウザは`onclick`コードを実行し、確認ダイアログを表示し、ユーザーが「OK」をクリックした場合にのみ`delete.php`に移動します。 + +この例は、`print()`メソッドで適切なPHPコードを生成することにより、ホストHTML要素の動作や属性を変更するための純粋なn:属性を作成する方法を示しています。しばしば必要となる二重エスケープを忘れないでください:ターゲットコンテキスト(この場合はJavaScript)用に1回、HTML属性コンテキスト用に再度。 + + +高度なトピック +======= + +前のセクションでは基本的な概念をカバーしましたが、カスタムLatteタグを作成する際に遭遇する可能性のある、より高度なトピックをいくつか紹介します。 + + +タグ出力モード +------- + +`create()`関数に渡される`Tag`オブジェクトには`outputMode`プロパティがあります。このプロパティは、特にタグが独自の行で使用される場合に、Latteが周囲の空白やインデントをどのように扱うかに影響します。`create()`関数でこのプロパティを変更できます。 + +- `Tag::OutputKeepIndentation`(`{=...}`のようなほとんどのタグのデフォルト):Latteはタグの前のインデントを保持しようとします。タグの*後*の改行は一般的に保持されます。インラインでコンテンツを出力するタグに適しています。 +- `Tag::OutputRemoveIndentation`(`{if}`、`{foreach}`のようなブロックタグのデフォルト):Latteは先頭のインデントと、潜在的に1つの後続の改行を削除します。これは、生成されたPHPコードをよりきれいに保ち、タグ自体によって引き起こされるHTML出力の余分な空白行を防ぐのに役立ちます。制御構造やブロックを表し、それ自体が空白を追加すべきでないタグに使用します。 +- `Tag::OutputNone`(`{var}`、`{default}`のようなタグで使用):`RemoveIndentation`に似ていますが、タグ自体が直接的な出力を生成しないことをより強く示し、周囲の空白の処理にさらに積極的に影響を与える可能性があります。宣言型または設定型のタグに適しています。 + +タグの目的に最も適したモードを選択してください。ほとんどの構造的または制御的なタグには、通常`OutputRemoveIndentation`が適しています。 + + +親/最も近いタグへのアクセス +-------------- + +タグの動作が、それが使用されるコンテキスト、具体的にはどの親タグ内にあるかに依存する必要がある場合があります。`create()`関数に渡される`Tag`オブジェクトは、まさにこの目的のために`closestTag(array $classes, ?callable $condition = null): ?Tag`メソッドを提供します。 + +このメソッドは、現在開いているタグ(解析中に内部的に表されるHTML要素を含む)の階層を上方に検索し、特定の基準に一致する最も近い祖先の`Tag`オブジェクトを返します。一致する祖先が見つからない場合は`null`を返します。 + +`$classes`配列は、探している祖先タグの種類を指定します。祖先タグに関連付けられたノード(`$ancestorTag->node`)がこのクラスのインスタンスであるかどうかをチェックします。 + +```php +function create(Tag $tag) +{ + // ノードが ForeachNode のインスタンスである最も近い祖先タグを検索します + $foreachTag = $tag->closestTag([ForeachNode::class]); + if ($foreachTag) { + // ForeachNode インスタンス自体にアクセスできます: + $foreachNode = $foreachTag->node; + } +} +``` + +`$foreachTag->node`に注意してください:これは、Latteタグ開発の慣例として、作成されたノードを`create()`メソッド内で即座に`$tag->node`に割り当てるためだけに機能します。これは、私たちが行ってきたことです。 + +ノードタイプを比較するだけでは不十分な場合があります。潜在的な祖先タグまたはそのノードの特定のプロパティを確認する必要があるかもしれません。`closestTag()`のオプションの2番目の引数は、潜在的な祖先`Tag`オブジェクトを受け取り、それが有効な一致であるかどうかを返す必要があるコーラブルです。 + +```php +function create(Tag $tag) +{ + $dynamicBlockTag = $tag->closestTag( + [BlockNode::class], + // 条件:ブロックは動的でなければなりません + fn(Tag $blockTag) => $blockTag->node->block->isDynamic(), + ); +} +``` + +`closestTag()`を使用すると、コンテキストを認識し、テンプレート構造内での適切な使用を強制するタグを作成でき、より堅牢で理解しやすいテンプレートにつながります。 + + +`PrintContext::format()` プレースホルダー +--------------------------------- + +ノードの`print()`メソッドでPHPコードを生成するために`PrintContext::format()`を頻繁に使用してきました。これはマスク文字列と、マスク内のプレースホルダーを置き換える後続の引数を受け取ります。利用可能なプレースホルダーの概要は次のとおりです: + +- **`%node`**: 引数は`Node`のインスタンスでなければなりません。ノードの`print()`メソッドを呼び出し、結果のPHPコード文字列を挿入します。 +- **`%dump`**: 引数は任意のPHP値です。値を有効なPHPコードにエクスポートします。スカラー、配列、nullに適しています。 + - `$context->format('echo %dump;', 'Hello')` -> `echo 'Hello';` + - `$context->format('$arr = %dump;', [1, 2])` -> `$arr = [1, 2];` +- **`%raw`**: 引数をエスケープや変更なしで出力PHPコードに直接挿入します。**注意して使用してください**。主に、事前に生成されたPHPコードフラグメントや変数名を挿入するために使用します。 + - `$context->format('%raw = 1;', '$variableName')` -> `$variableName = 1;` +- **`%args`**: 引数は`Expression\ArrayNode`でなければなりません。関数またはメソッド呼び出しの引数としてフォーマットされた配列項目を出力します(カンマ区切り、存在する場合は名前付き引数を処理します)。 + - `$argsNode = new ArrayNode([...]);` + - `$context->format('myFunc(%args);', $argsNode)` -> `myFunc(1, name: 'Joe');` +- **`%line`**: 引数は`Position`オブジェクト(通常は`$this->position`)でなければなりません。ソース行番号を示すPHPコメント`/* line X */`を挿入します。 + - `$context->format('echo "Hi" %line;', $this->position)` -> `echo "Hi" /* line 42 */;` +- **`%escape(...)`**: *実行時に*現在のコンテキスト対応エスケープルールを使用して内部式をエスケープするPHPコードを生成します。 + - `$context->format('echo %escape(%node);', $variableNode)` +- **`%modify(...)`**: 引数は`ModifierNode`でなければなりません。`ModifierNode`で指定されたフィルタを内部コンテンツに適用するPHPコードを生成します。`|noescape`で無効にされていない限り、コンテキスト対応エスケープを含みます。 + - `$context->format('%modify(%node);', $modifierNode, $variableNode)` +- **`%modifyContent(...)`**: `%modify`に似ていますが、キャプチャされたコンテンツ(多くの場合HTML)のブロックを変更するために設計されています。 + +インデックス(ゼロから)によって引数を明示的に参照できます:`%0.node`、`%1.dump`、`%2.raw`など。これにより、`format()`に繰り返し渡すことなく、マスク内で引数を複数回再利用できます。`%0.raw`と`%2.raw`が使用された`{repeat}`タグの例を参照してください。 + + +複雑な引数解析の例 +--------- + +`parseExpression()`、`parseArguments()`などが多くのケースをカバーしますが、`$tag->parser->stream`を通じて利用可能な低レベルの`TokenStream`を使用した、より複雑な解析ロジックが必要になる場合があります。 + +**目標:** `{embedYoutube $videoID, width: 640, height: 480}`タグを作成します。必須のビデオID(文字列または変数)と、それに続くオプションのキーと値のペア(次元用)を解析したいです。 + +```php +expectArguments(); + $node = $tag->node = new self; + // 必須のビデオIDを解析します + $node->videoId = $tag->parser->parseExpression(); + + // オプションのキーと値のペアを解析します + $stream = $tag->parser->stream; // トークンストリームを取得します + while ($stream->tryConsume(',')) { // カンマ区切りが必要です + // 'width' または 'height' 識別子を期待します + $keyToken = $stream->consume(Token::Php_Identifier); + $key = strtolower($keyToken->text); + + $stream->consume(':'); // コロン区切り文字を期待します + + $value = $tag->parser->parseExpression(); // 値の式を解析します + + if ($key === 'width') { + $node->width = $value; + } elseif ($key === 'height') { + $node->height = $value; + } else { + throw new CompileException("不明な引数 '$key'。'width' または 'height' が期待されます。", $keyToken->position); + } + } + + return $node; + } +} +``` + +このレベルの制御により、トークンストリームと直接対話することで、カスタムタグに対して非常に具体的で複雑な構文を定義できます。 + + +`AuxiliaryNode` の使用 +------------------- + +Latteは、コード生成中またはコンパイルパス内の特別な状況のために、一般的な「補助」ノードを提供します。これらは`AuxiliaryNode`と`Php\Expression\AuxiliaryNode`です。 + +`AuxiliaryNode`を、そのコア機能(コード生成と子ノードの公開)をコンストラクタで提供される引数に委譲する、柔軟なコンテナノードと考えてください: + +- `print()`の委譲:コンストラクタの最初の引数はPHP**クロージャ**です。Latteが`AuxiliaryNode`で`print()`メソッドを呼び出すと、提供されたこのクロージャを実行します。クロージャは`PrintContext`と、コンストラクタの2番目の引数で渡されたノードを受け取り、完全にカスタムなPHPコード生成ロジックを実行時に定義できます。 +- `getIterator()`の委譲:コンストラクタの2番目の引数は**`Node`オブジェクトの配列**です。Latteが`AuxiliaryNode`の子を走査する必要がある場合(例:コンパイルパス中)、その`getIterator()`メソッドはこの配列にリストされているノードを単純に提供します。 + +例: + +```php +$node = new AuxiliaryNode( + // 1. このクロージャが print() の本体になります + fn(PrintContext $context, $arg1, $arg2) => $context->format('...%node...%node...', $arg1, $arg2), + + // 2. これらのノードは getIterator() メソッドによって提供され、上記のクロージャに渡されます + [$argumentNode1, $argumentNode2] +); +``` + +Latteは、生成されたコードをどこに挿入する必要があるかに基づいて、2つの異なるタイプを提供します: + +- `Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode`: **式**を表すPHPコードの一部を生成する必要がある場合に使用します +- `Latte\Compiler\Nodes\AuxiliaryNode`: 1つ以上の**ステートメント**を表すPHPコードのブロックを挿入する必要がある、より一般的な目的で使用します + +`print()`メソッドまたはコンパイルパス内で標準ノード(`StaticMethodCallNode`など)の代わりに`AuxiliaryNode`を使用する重要な理由は、**後続のコンパイルパス、特にSandboxなどのセキュリティ関連のものに対する可視性の制御**です。 + +シナリオを考えてみましょう:コンパイルパスが、ユーザー提供の式(`$userExpr`)を特定の信頼できるヘルパー関数`myInternalSanitize($userExpr)`の呼び出しでラップする必要があるとします。標準の`new FunctionCallNode('myInternalSanitize', [$userExpr])`ノードを作成すると、ASTパスに対して完全に表示されます。Sandboxパスが後で実行され、`myInternalSanitize`が許可リストに*ない*場合、Sandboxはこの呼び出しを*ブロック*または変更する可能性があり、タグの作成者である*あなた*がこの特定の呼び出しが安全で必要であると知っていても、タグの内部ロジックを潜在的に壊す可能性があります。したがって、`AuxiliaryNode`クロージャ内で直接呼び出しを生成できます。 + +```php +use Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode; + +// ... print() またはコンパイルパス内 ... +$wrappedNode = new AuxiliaryNode( + fn(PrintContext $context, $userExpr) => $context->format( + 'myInternalSanitize(%node)', // PHPコードを直接生成 + $userExpr, + ), + // 重要:元のユーザー式ノードをここに渡してください! + [$userExpr], +); +``` + +この場合、Sandboxパスは`AuxiliaryNode`を見ますが、そのクロージャによって生成されたPHPコードを**解析しません**。クロージャ*内部*で生成された`myInternalSanitize`呼び出しを直接ブロックすることはできません。 + +生成されたPHPコード自体はパスから隠されていますが、そのコードへの*入力*(ユーザーデータや式を表すノード)は**依然として走査可能でなければなりません**。これが、`AuxiliaryNode`のコンストラクタの2番目の引数が不可欠である理由です。クロージャが使用するすべての元のノード(上記の例の`$userExpr`など)を含む配列を**必ず**渡す必要があります。`AuxiliaryNode`の`getIterator()`は**これらのノードを提供し**、Sandboxのようなコンパイルパスが潜在的な問題についてそれらを分析できるようにします。 + + +ベストプラクティス +========= + +- **明確な目的:** タグが明確で必要な目的を持っていることを確認してください。[フィルタ |custom-filters]や[関数 |custom-functions]で簡単に解決できるタスクのためにタグを作成しないでください。 +- **`getIterator()`を正しく実装する:** 常に`getIterator()`を実装し、テンプレートから解析された*すべて*の子ノード(引数、コンテンツ)への*参照*(`&`)を提供してください。これは、コンパイルパス、セキュリティ([Sandbox |Sandbox])、および将来の潜在的な最適化に不可欠です。 +- **ノードのパブリックプロパティ:** 子ノードを含むプロパティは、必要に応じてコンパイルパスが変更できるようにパブリックにしてください。 +- **`PrintContext::format()`を使用する:** PHPコードを生成するために`format()`メソッドを活用してください。引用符を処理し、プレースホルダーを適切にエスケープし、行番号コメントを自動的に追加します。 +- **一時変数(`$__`):** 一時変数が必要なランタイムPHPコードを生成する場合(例:中間合計、ループカウンタを格納するため)、ユーザー変数やLatteの内部変数`$ʟ_`との衝突を避けるために`$__`プレフィックス規約を使用してください。 +- **ネストと一意なID:** タグがネストされる可能性がある場合、または実行時にインスタンス固有の状態が必要な場合は、`print()`メソッド内で`$context->generateId()`を使用して、`$__`一時変数の一意なサフィックスを作成してください。 +- **外部データ用のプロバイダ:** ランタイムデータやサービス(`$this->global->...`)にアクセスするために、値をハードコーディングしたりグローバル状態に依存したりする代わりに、プロバイダ(`Extension::getProviders()`を通じて登録)を使用してください。プロバイダ名にはベンダープレフィックスを使用してください。 +- **n:属性を検討する:** ペアタグが論理的に単一のHTML要素で動作する場合、Latteはおそらく自動的な`n:属性`サポートを提供します。ユーザーの利便性のためにこれを念頭に置いてください。属性変更タグを作成する場合は、純粋な`n:属性`が最も適切な形式であるかどうかを検討してください。 +- **テスト:** さまざまな構文入力の解析と、生成された**PHPコード**の出力の正確性の両方をカバーするタグのテストを作成してください。 + +これらのガイドラインに従うことで、Latteテンプレートエンジンとシームレスに統合する、強力で堅牢、かつ保守可能なカスタムタグを作成できます。 + +.[note] +Latteに含まれるノードクラスを研究することは、解析プロセスのすべての詳細を学ぶための最良の方法です。 diff --git a/latte/ja/develop.texy b/latte/ja/develop.texy index 2c8bd18d61..9ebfe7c46e 100644 --- a/latte/ja/develop.texy +++ b/latte/ja/develop.texy @@ -1,70 +1,66 @@ -開発者向けプラクティス -*********** +開発プラクティス +******** -インストール .[#toc-installation] -=========================== +インストール +====== -Latteのインストールには、Composerを使うのが一番です。 +Latte をインストールする最良の方法は Composer を使用することです: ```shell composer require latte/latte ``` -対応するPHPのバージョン(最新のパッチLatteのバージョンに適用されます)。 +サポートされている PHP バージョン(最新のマイナーバージョンの Latte に適用): -| バージョン|PHPと互換性がある +| バージョン | PHP との互換性 |-----------------|------------------- -| Latte 3.0 | PHP 8.0 - 8.2 -| ラテ 2.11|PHP 7.1~8.2 -| ラテ 2.8~2.10|PHP 7.1~8.1 +| Latte 3.0 | PHP 8.0 – 8.2 -テンプレートのレンダリング方法 .[#toc-how-to-render-a-template] -================================================ +テンプレートをレンダリングする方法 +================= -テンプレートをレンダリングする方法は?この簡単なコードを使うだけです。 +テンプレートをレンダリングするにはどうすればよいですか?この簡単なコードで十分です: ```php $latte = new Latte\Engine; -// cache directory +// キャッシュディレクトリ $latte->setTempDirectory('/path/to/tempdir'); -$params = [ /* template variables */ ]; -// or $params = new TemplateParameters(/* ... */); +$params = [ /* テンプレート変数 */ ]; +// または $params = new TemplateParameters(/* ... */); -// render to output +// 出力にレンダリング $latte->render('template.latte', $params); -// or render to variable +// 変数にレンダリング $output = $latte->renderToString('template.latte', $params); ``` -パラメータには配列や[オブジェクトを |#Parameters as a class]指定することができ、エディターで型チェックやサジェストを行うことができます。 +パラメータは配列、または[オブジェクト |#クラスとしてのパラメータ]である方が望ましいです。これにより、型チェックとエディタでの補完が保証されます。 .[note] -また、[Latte examplesという |https://github.com/nette-examples/latte]リポジトリで使用例を見ることができます。 +使用例は [Latte examples |https://github.com/nette-examples/latte] リポジトリにもあります。 -パフォーマンスとキャッシング .[#toc-performance-and-caching] -============================================== +パフォーマンスとキャッシュ +============= -Latteのテンプレートは非常に高速です。Latteはテンプレートを直接PHPコードにコンパイルし、ディスク上にキャッシュしているからです。したがって、純粋なPHPで書かれたテンプレートと比較して、余分なオーバーヘッドがありません。 +Latte のテンプレートは非常に高速です。Latte はそれらを直接 PHP コードにコンパイルし、ディスク上のキャッシュに保存します。したがって、純粋な PHP で書かれたテンプレートと比較して追加のオーバーヘッドはありません。 -キャッシュは、ソースファイルを変更するたびに自動的に再生成されます。そのため、開発中にLatteのテンプレートを編集しても、すぐにブラウザで変更内容を確認できる便利な機能です。本番環境ではこの機能を無効にして、パフォーマンスを少し節約することができます。 +ソースファイルを変更するたびに、キャッシュは自動的に再生成されます。したがって、開発中は Latte テンプレートを快適に編集し、変更をすぐにブラウザで確認できます。この機能は本番環境で無効にして、パフォーマンスを少し節約できます: ```php $latte->setAutoRefresh(false); ``` -本番サーバーに導入した場合、特に大規模なアプリケーションの場合、最初のキャッシュ生成には当然ながら時間がかかることがあります。Latteは「キャッシュスタンピード」に対する:https://en.wikipedia.org/wiki/Cache_stampede予防策を内蔵しています。 -これは、サーバーが多数の同時リクエストを受け、Latteのキャッシュがまだ存在しないため、それらがすべて同時にキャッシュを生成してしまう状況です。これがCPUを急上昇させるのです。 -Latteは賢く、複数の同時リクエストがあった場合、最初のスレッドだけがキャッシュを生成し、他のスレッドは待ってからキャッシュを使用します。 +本番サーバーにデプロイする場合、特に大規模なアプリケーションでは、最初のキャッシュ生成に少し時間がかかることがあります。Latte には「キャッシュスタンピード」に対する組み込みの防止策があります:https://en.wikipedia.org/wiki/Cache_stampede。 これは、Latte を起動する多数の同時リクエストが発生し、キャッシュがまだ存在しないため、すべてが同時に生成を開始する状況です。これにより、サーバーに過度の負荷がかかります。 Latte は賢く、複数の同時リクエストがある場合、最初のスレッドのみがキャッシュを生成し、他のスレッドは待機して後でそれを使用します。 -クラスとしてのパラメータ .[#toc-parameters-as-a-class] -========================================== +クラスとしてのパラメータ +============ -テンプレートに変数を配列として渡すよりも、クラスを作成する方がよいでしょう。[型安全な記法が |type-system]得られるし、[IDEでの表示もきれい |recipes#Editors and IDE]だし、[フィルタや |extending-latte#Filters Using the Class] [関数を |extending-latte#Functions Using the Class] [登録 |extending-latte#Filters Using the Class]する方法もある。 +テンプレートに変数を配列として渡すよりも、クラスを作成する方が良いです。[型安全な記述 |type-system]、[IDEでの快適な補完 |recipes#エディタとIDE]、および[フィルタ |custom-filters#属性付きクラスを使用したフィルタ]や[関数 |custom-functions#属性付きクラスを使用した関数]を登録するためのパスが得られます。 ```php class MailTemplateParameters @@ -88,12 +84,12 @@ $latte->render('mail.latte', new MailTemplateParameters( ``` -変数の自動エスケープを無効にする .[#toc-disabling-auto-escaping-of-variable] -============================================================ +変数の自動エスケープの無効化 +============== -変数にHTML文字列が含まれている場合、Latteが自動的に(つまり二重に)エスケープしないようにマークすることができます。これにより、テンプレートで`|noescape` を指定する必要がなくなります。 +変数に HTML 文字列が含まれている場合、Latte が自動的に(したがって二重に)エスケープしないようにマークできます。これにより、テンプレートで `|noescape` を指定する必要がなくなります。 -最も簡単な方法は、文字列を`Latte\Runtime\Html` オブジェクトで囲むことです。 +最も簡単な方法は、文字列を `Latte\Runtime\Html` オブジェクトでラップすることです: ```php $params = [ @@ -101,7 +97,7 @@ $params = [ ]; ``` -また、Latteは、`Latte\HtmlStringable` インターフェースを実装するすべてのオブジェクトをエスケープしません。そこで、`__toString()` メソッドが自動的にエスケープされないHTMLコードを返すクラスを独自に作成することができます。 +Latte はさらに、`Latte\HtmlStringable` インターフェースを実装するすべてのオブジェクトをエスケープしません。したがって、`__toString()` メソッドが自動的にエスケープされない HTML コードを返す独自のクラスを作成できます: ```php class Emphasis extends Latte\HtmlStringable @@ -123,32 +119,85 @@ $params = [ ``` .[warning] -`__toString` メソッドは正しい HTML を返し、パラメータのエスケープを提供しなければなりません。そうでなければ、XSS 脆弱性が発生する可能性があります。 +`__toString` メソッドは正しい HTML を返し、パラメータのエスケープを保証する必要があります。そうしないと、XSS 脆弱性が発生する可能性があります! -フィルターやタグなどを使ってラテを拡張する方法。 .[#toc-how-to-extend-latte-with-filters-tags-etc] -========================================================================== +フィルタ、タグなどで Latte を拡張する方法 +======================== -Latteにカスタムフィルタ、関数、タグなどを追加するには?Latte[の拡張の |extending Latte]章を参照してください。 -変更した内容を別のプロジェクトで再利用したい場合や、他の人と共有したい場合は、[拡張機能を作成 |creating-extension]する必要があります。 +カスタムフィルタ、関数、タグなどを Latte に追加するにはどうすればよいですか?これについては、[Latte の拡張 |extending-latte]の章で説明します。 変更をさまざまなプロジェクトで再利用したり、他の人と共有したりしたい場合は、[拡張機能を作成 |extending-latte#Latte Extension]する必要があります。 -テンプレート内の任意のコード`{php ...}` .{data-version:3.0}{toc: RawPhpExtension} -=================================================================== +テンプレート内の任意のコード `{php ...}` .{toc: RawPhpExtension} +================================================== -の中に書くことができるのは、PHPの式だけです。 [`{do}` |tags#do]タグを使用するため、例えば、`if ... else` やセミコロンで終端されたステートメントのような構成要素を挿入することはできません。 +[`{do}` |tags#do] タグ内では PHP 式のみを記述できます。したがって、`if ... else` などの構文やセミコロンで終わるステートメントを挿入することはできません。 -ただし、`RawPhpExtension` 拡張機能を登録することで、`{php ...}` タグが追加され、テンプレート作者の責任において任意の PHP コードを挿入することができます。 +ただし、`{php ...}` タグを追加する `RawPhpExtension` 拡張機能を登録できます。これにより、任意の PHP コードを挿入できます。サンドボックスモードのルールは適用されないため、使用はテンプレート作成者の責任となります。 ```php $latte->addExtension(new Latte\Essential\RawPhpExtension); ``` -テンプレートでの翻訳 .{data-version:3.0}{toc: TranslatorExtension} -======================================================== +生成されたコードのチェック .{data-version:3.0.7} +=================================== + +Latte はテンプレートを PHP コードにコンパイルします。もちろん、生成されたコードが構文的に有効であることを保証します。ただし、サードパーティの拡張機能または `RawPhpExtension` を使用する場合、Latte は生成されたファイルの正確性を保証できません。 また、PHP では構文的に正しいが禁止されているコード(例えば、変数 `$this` への値の代入)を記述することができ、PHP コンパイルエラーが発生します。 このような操作をテンプレートに記述すると、生成された PHP コードにも含まれます。PHP には約 200 の異なる禁止された操作があるため、Latte はそれらを検出することを目指していません。通常、レンダリング時に PHP 自体がそれらを警告しますが、これは通常問題ありません。 + +ただし、テンプレートのコンパイル時に PHP コンパイルエラーが含まれていないことを知りたい場合があります。特に、テンプレートをユーザーが編集できる場合、または[サンドボックス |Sandbox]を使用している場合です。このような場合は、コンパイル時にテンプレートをチェックさせてください。 この機能は `Engine::enablePhpLint()` メソッドで有効にします。チェックには PHP バイナリを呼び出す必要があるため、そのパスをパラメータとして渡します: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); + +try { + $latte->compile('home.latte'); +} catch (Latte\CompileException $e) { + // Latte のエラーと PHP のコンパイルエラーをキャッチします + echo 'Error: ' . $e->getMessage(); +} +``` + + +ロケール .{data-version:3.0.18}{toc: Locale} +======================================== + +Latte を使用すると、数値、日付、および並べ替えの書式設定に影響を与えるロケールを設定できます。これは `setLocale()` メソッドを使用して設定されます。ロケール識別子は、PHP 拡張機能 `intl` が使用する IETF 言語タグ標準に従います。これは、言語コードと、場合によっては国コードで構成されます。例:米国の英語の場合は `en_US`、ドイツのドイツ語の場合は `de_DE` など。 + +```php +$latte = new Latte\Engine; +$latte->setLocale('cs'); +``` + +ロケール設定は、フィルタ [localDate |filters#localDate]、[sort |filters#sort]、[number |filters#number]、および [bytes |filters#bytes] に影響します。 + +.[note] +PHP 拡張機能 `intl` が必要です。Latte の設定は PHP のグローバルロケール設定には影響しません。 + + +厳格モード .{data-version:3.0.8} +=========================== + +厳格な解析モードでは、Latte は閉じ HTML タグが欠落していないかチェックし、変数 `$this` の使用も禁止します。次のように有効にします: + +```php +$latte = new Latte\Engine; +$latte->setStrictParsing(); +``` + +`declare(strict_types=1)` ヘッダーを持つテンプレートの生成は、次のように有効にします: + +```php +$latte = new Latte\Engine; +$latte->setStrictTypes(); +``` -`TranslatorExtension` の拡張機能を使用して、追加します。 [`{_...}` |tags#_], [`{translate}` |tags#translate]とフィルター [`translate` |filters#translate]をテンプレートに追加します。これらは、テンプレートの値や部分を他の言語に翻訳するために使用されます。パラメータは、翻訳を実行するメソッド(PHP callable)です。 + +テンプレートでの翻訳 .{toc: TranslatorExtension} +====================================== + +`TranslatorExtension` 拡張機能を使用すると、テンプレートにタグ [`{_...}` |tags#]、[`{translate}` |tags#translate]、およびフィルタ [`translate` |filters#translate] を追加できます。これらは、値またはテンプレートの一部を他の言語に翻訳するために使用されます。パラメータとして、翻訳を実行するメソッド(PHP callable)を指定します: ```php class MyTranslator @@ -158,19 +207,19 @@ class MyTranslator public function translate(string $original): string { - // create $translated from $original according to $this->lang + // $original から $this->lang に従って $translated を作成します return $translated; } } $translator = new MyTranslator($lang); $extension = new Latte\Essential\TranslatorExtension( - $translator->translate(...), // [$translator, 'translate'] in PHP 8.0 + $translator->translate(...), // PHP 8.0 では [$translator, 'translate'] ); $latte->addExtension($extension); ``` -トランスレータは、テンプレートがレンダリングされるときに実行時に呼び出されます。しかし、Latteはテンプレートのコンパイル時にすべての静的テキストを翻訳することができます。これは、各文字列が一度だけ翻訳され、その結果の翻訳がコンパイルされたファイルに書き込まれるため、パフォーマンスを節約することができます。これにより、キャッシュディレクトリに、各言語ごとに複数のコンパイル済みバージョンのテンプレートが作成されます。これを行うには、第2パラメータとして言語を指定するだけです。 +トランスレータは、テンプレートのレンダリング時に実行時に呼び出されます。ただし、Latte はテンプレートのコンパイル中にすべての静的テキストを翻訳できます。これにより、各文字列が 1 回だけ翻訳され、結果の翻訳がコンパイルされた形式に書き込まれるため、パフォーマンスが節約されます。したがって、キャッシュディレクトリには、言語ごとに 1 つずつ、複数のコンパイル済みバージョンのテンプレートが作成されます。これを行うには、言語を 2 番目のパラメータとして指定するだけです: ```php $extension = new Latte\Essential\TranslatorExtension( @@ -179,9 +228,9 @@ $extension = new Latte\Essential\TranslatorExtension( ); ``` -静的テキストとは、例えば、`{_'hello'}` や`{translate}hello{/translate}` のような静的でないテキストを意味します。`{_$foo}` のような静的でないテキストは、実行時に翻訳が継続されます。 +静的テキストとは、例えば `{_'hello'}` や `{translate}hello{/translate}` のようなものを意味します。`{_$foo}` のような非静的テキストは、引き続き実行時に翻訳されます。 -また,テンプレートは,`{_$original, foo: bar}` または`{translate foo: bar}` を介して,トランスレータに追加のパラメータを渡すことができ,トランスレータはこれを`$params` の配列として受け取ります. +トランスレータには、`{_$original, foo: bar}` または `{translate foo: bar}` を使用してテンプレートから追加のパラメータを渡すこともできます。これらは配列 `$params` として取得されます: ```php public function translate(string $original, ...$params): string @@ -191,66 +240,73 @@ public function translate(string $original, ...$params): string ``` -デバッグとトレイシー .[#toc-debugging-and-tracy] -====================================== +デバッグと Tracy +=========== -ラテは、できるだけ快適に開発できるように心がけています。デバッグのために、以下の3つのタグを用意しています。 [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak]と [`{trace}` |tags#trace]. +Latte は開発をできるだけ快適にしようとします。デバッグ目的のために、3 つのタグ [`{dump}` |tags#dump]、[`{debugbreak}` |tags#debugbreak]、[`{trace}` |tags#trace] があります。 -優れた[デバッグツールであるTracyを |tracy:en]インストールし、Latteプラグインを有効化すれば、最も快適になります。 +さらに優れた[デバッグツール Tracy |tracy:]をインストールし、Latte アドオンを有効にすると、最高の快適さが得られます: ```php -// enables Tracy +// Tracy を有効にします Tracy\Debugger::enable(); $latte = new Latte\Engine; -// activates Tracy's extension +// Tracy の拡張機能を有効にします $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension); ``` -これで、行と列が強調表示されたテンプレート内のエラーも含め、すべてのエラーがきちんと赤い画面で確認できるようになります[(動画 |https://github.com/nette/tracy/releases/tag/v2.9.0])。 -同時に、右下のいわゆるトレーシーバーにラテのタブが表示され、レンダリングされたすべてのテンプレートとその関係(テンプレートやコンパイルされたコードにクリックする可能性を含む)、および変数を明確に見ることができます。 +これで、すべてのエラーが、行と列が強調表示されたテンプレートのエラーを含め、明確な赤い画面に表示されます([ビデオ |https://github.com/nette/tracy/releases/tag/v2.9.0])。 同時に、右下のいわゆる Tracy Bar に Latte のタブが表示され、レンダリングされたすべてのテンプレートとその相互関係(テンプレートまたはコンパイル済みコードにクリックして移動する可能性を含む)および変数が明確に表示されます: [* latte-debugging.webp *] -Latteはテンプレートを読みやすいPHPコードにコンパイルするので、IDEでステップを踏んでいけるのが便利です。 +Latte はテンプレートを読みやすい PHP コードにコンパイルするため、IDE で快適にステップ実行できます。 -リンターテンプレートの構文を検証する .{data-version:2.11}{toc: Linter} -==================================================== +Linter:テンプレート構文の検証 .{toc: Linter} +================================= -Linterツールは、すべてのテンプレートを調べて、シンタックスエラーをチェックするのに役立ちます。コンソールから起動します。 +Linter ツールは、すべてのテンプレートを調べて、構文エラーが含まれていないかチェックするのに役立ちます。コンソールから起動します: ```shell vendor/bin/latte-lint ``` -カスタムタグを使用する場合は、カスタマイズしたLinterも作成してください(例:`custom-latte-lint` )。 +`--strict` パラメータは[#厳格モード]を有効にします。 + +カスタムタグを使用している場合は、独自のバージョンの Linter を作成します。例:`custom-latte-lint`: ```php #!/usr/bin/env php scanDirectory($path); +$path = $argv[1] ?? '.'; -$engine = new Latte\Engine; -// registers individual extensions here -$engine->addExtension(/* ... */); +$linter = new Latte\Tools\Linter; +$latte = $linter->getEngine(); +// ここに個々の拡張機能を追加します +$latte->addExtension(/* ... */); -$path = $argv[1]; -$linter = new Latte\Tools\Linter(engine: $engine); $ok = $linter->scanDirectory($path); exit($ok ? 0 : 1); ``` +あるいは、カスタム `Latte\Engine` オブジェクトを Linter に渡すこともできます: + +```php +$latte = new Latte\Engine; +// ここで $latte オブジェクトを設定します +$linter = new Latte\Tools\Linter(engine: $latte); +``` + -文字列からテンプレートを読み込む .[#toc-loading-templates-from-a-string] -======================================================== +文字列からのテンプレートの読み込み +================= -テスト用に、ファイルではなく文字列からテンプレートをロードする必要がありますか?[StringLoaderは |extending-latte#stringloader]、そんなあなたのお役に立ちます。 +テスト目的などで、ファイルではなく文字列からテンプレートを読み込む必要がありますか?[StringLoader |loaders#StringLoader] が役立ちます: ```php $latte->setLoader(new Latte\Loaders\StringLoader([ @@ -262,10 +318,10 @@ $latte->render('main.file', $params); ``` -例外ハンドラ .[#toc-exception-handler] -================================ +例外ハンドラ +====== -期待される例外に対して、独自のハンドラを定義することができます。の内部で発生する例外は [`{try}` |tags#try]と[サンドボックス |sandbox]内に渡されます。 +予期される例外に対して独自のハンドラを定義できます。[`{try}` |tags#try] 内および[サンドボックス |sandbox]内で発生した例外が渡されます。 ```php $loggingHandler = function (Throwable $e, Latte\Runtime\Template $template) use ($logger) { @@ -277,17 +333,17 @@ $latte->setExceptionHandler($loggingHandler); ``` -レイアウトの自動検索 .[#toc-automatic-layout-lookup] -========================================== +自動レイアウト検索 +========= -タグの使用 [`{layout}` |template-inheritance#layout-inheritance]を指定すると、そのテンプレートが親テンプレートを決定します。また、レイアウトを自動的に検索させることも可能で、この場合、`{layout}` タグを記述する必要がなくなるため、テンプレートの記述が簡略化されます。 +[`{layout}` |template-inheritance#Layout inheritance] タグを使用して、テンプレートはその親テンプレートを指定します。レイアウトを自動的に検索させることも可能です。これにより、テンプレートに `{layout}` タグを含める必要がなくなるため、テンプレートの記述が簡素化されます。 -これを実現するために、次のような工夫をしています。 +これは次の方法で実現されます: ```php $finder = function (Latte\Runtime\Template $template) { if (!$template->getReferenceType()) { - // it returns the path to the parent template file + // レイアウトファイルへのパスを返します return 'automatic.layout.latte'; } }; @@ -296,4 +352,4 @@ $latte = new Latte\Engine; $latte->addProvider('coreParentFinder', $finder); ``` -テンプレートがレイアウトを持つべきではない場合は、`{layout none}` タグでその旨を表示します。 +テンプレートにレイアウトがない場合は、`{layout none}` タグで示します。 diff --git a/latte/ja/extending-latte.texy b/latte/ja/extending-latte.texy index 248cc1fdcb..4dcdfdd04e 100644 --- a/latte/ja/extending-latte.texy +++ b/latte/ja/extending-latte.texy @@ -1,285 +1,227 @@ -エクステンド・ラテ -********* +Latteの拡張 +******** .[perex] -Latteは非常に柔軟性が高く、様々な方法で拡張することができます:カスタムフィルタ、関数、タグ、ローダーなどを追加することができます。その方法をご紹介します。 +Latteは拡張性を考慮して設計されています。標準のタグ、フィルタ、関数のセットは多くのユースケースをカバーしていますが、独自の特定のロジックやヘルパーツールを追加する必要があることがよくあります。このページでは、単純なヘルパーから複雑な新しい構文まで、プロジェクトの要件に完全に一致するようにLatteを拡張する方法の概要を説明します。 -この章では、Latteを拡張するための様々な方法を説明します。もしあなたが、変更した内容を別のプロジェクトで再利用したい、あるいは他の人と共有したいのであれば、次に[いわゆる拡張機能を作成 |creating-extension]する必要があります。 +Latteを拡張する方法 +============ -ローマに続く道はいくつある? .[#toc-how-many-roads-lead-to-rome] -================================================== +Latteをカスタマイズおよび拡張するための主な方法の概要を以下に示します。 -ラテの拡張方法の中にはブレンドできるものもあるので、まずはそれぞれの違いを説明してみよう。例として、*Lorem ipsum* ジェネレータを実装してみましょう。ジェネレータには、生成する単語の数が渡されます。 +- **[カスタムフィルタ |Custom Filters]:** テンプレートの出力で直接データをフォーマットまたは変換するため(例:`{$var|myFilter}`)。日付のフォーマット、テキストの編集、特定のエスケープの適用などのタスクに最適です。コンテンツを匿名の[`{block}` |tags#block]でラップし、カスタムフィルタを適用することで、より大きなHTMLコンテンツのブロックを編集するためにも使用できます。 +- **[カスタム関数 |Custom Functions]:** テンプレート内の式で呼び出すことができる再利用可能なロジックを追加するため(例:`{myFunction($arg1, $arg2)}`)。計算、アプリケーションヘルパー関数へのアクセス、またはコンテンツの小さな部分の生成に役立ちます。 +- **[カスタムタグ |Custom Tags]:** 全く新しい言語構造を作成するため(`{mytag}...{/mytag}`または`n:mytag`)。タグは最も多くの可能性を提供し、独自の構造を定義し、テンプレートのパースを制御し、複雑なレンダリングロジックを実装できます。 +- **[コンパイルパス |Compiler Passes]:** パース後、PHPコード生成前にテンプレートの抽象構文木(AST)を変更する関数。高度な最適化、セキュリティチェック(Sandboxなど)、または自動コード変更に使用されます。 +- **[カスタムローダー |loaders]:** Latteがテンプレートファイルを検索して読み込む方法を変更するため(例:データベース、暗号化されたストレージなどからの読み込み)。 -Latte言語の主要な構成要素はタグです。このタグを使ってLatteを拡張することにより,ジェネレータを実装することができます. +適切な拡張方法を選択することが重要です。複雑なタグを作成する前に、より単純なフィルタや関数で十分かどうかを検討してください。例として、生成する単語数を引数として受け取る*Lorem ipsum*ジェネレータの実装を考えてみましょう。 -```latte -{lipsum 40} -``` - -タグは素晴らしい働きをするでしょう。しかし、タグの形をしたジェネレータは式の中で使うことができないので、柔軟性に欠けるかもしれません。ところで、実際には、タグを生成する必要はほとんどありません。タグは拡張するためのより複雑な方法なので、これは良いニュースです。 - -では、タグの代わりにフィルタを作ってみましょう。 - -```latte -{=40|lipsum} -``` - -これも有効なオプションです。しかし、フィルタは渡された値を別のものに変換する必要があります。ここでは、生成された単語の数を示す値`40` を、変換したい値としてではなく、フィルタの引数として使っています。 +- **タグとして?** `{lipsum 40}` - 可能ですが、タグは制御構造や複雑なマークアップの生成により適しています。タグは式で直接使用することはできません。 +- **フィルタとして?** `{=40|lipsum}` - 技術的には機能しますが、フィルタは入力値を*変換*することを目的としています。ここでは、`40`は変換される値ではなく*引数*です。これは意味的に間違っているように感じられます。 +- **関数として?** `{lipsum(40)}` - これが最も自然な解決策です!関数は引数を受け取り、値を返します。これは、任意の式で使用するのに理想的です:`{var $text = lipsum(40)}`。 -そこで、関数を使ってみよう。 +**一般的な推奨事項:** 計算/生成には関数を、変換にはフィルタを、新しい言語構造や複雑なマークアップにはタグを使用してください。ASTの操作にはパスを、テンプレートの取得にはローダーを使用してください。 -```latte -{lipsum(40)} -``` -これだけです。この特定の例では、関数を作成することが理想的な拡張ポイントです。例えば、式が受け入れられるところならどこでもそれを呼び出すことができます。 +直接登録 +==== -```latte -{var $text = lipsum(40)} -``` +プロジェクト固有のヘルパーツールや迅速な拡張のために、Latteは`Latte\Engine`オブジェクトへのフィルタと関数の直接登録を許可します。 - -フィルター .[#toc-filters] -===================== - -フィルタの名前と関数などのPHP呼び出し可能なものを登録することでフィルタを作成します。 +フィルタを登録するには、`addFilter()`メソッドを使用します。フィルタ関数の最初の引数は`|`文字の前の値になり、後続の引数はコロン`:`の後に渡されるものです。 ```php $latte = new Latte\Engine; -$latte->addFilter('shortify', fn(string $s) => mb_substr($s, 0, 10)); // shortens the text to 10 characters -``` -この場合、フィルタが追加のパラメータを受け取るようにするのがよいでしょう。 +// フィルタ定義(呼び出し可能なオブジェクト:関数、静的メソッドなど) +$myTruncate = fn(string $s, int $length = 50) => mb_substr($s, 0, $length); -```php -$latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); -``` - -このようなテンプレートで使用します。 +// 登録 +$latte->addFilter('truncate', $myTruncate); -```latte -

                                                                                  {$text|shortify}

                                                                                  -

                                                                                  {$text|shortify:100}

                                                                                  +// テンプレートでの使用:{$text|truncate} または {$text|truncate:100} ``` -見ての通り、この関数は次の引数として、パイプ`|` as the first argument and the arguments passed to the filter after `:` の前にあるフィルタの左側を受け取ります。 - -もちろん、フィルタを表す関数は任意の数のパラメータを受け取ることができ、可変長のパラメータもサポートされています。 - - -クラスを使ったフィルタ .[#toc-filters-using-the-class] -------------------------------------------- - -フィルタを定義する第二の方法は、[クラスを |develop#Parameters as a class]使うことです。`TemplateFilter` 属性でメソッドを作成します。 +**フィルタローダー**を登録することもできます。これは、要求された名前に基づいてフィルタの呼び出し可能なオブジェクトを動的に提供する関数です。 ```php -class TemplateParameters -{ - public function __construct( - // parameters - ) {} - - #[Latte\Attributes\TemplateFilter] - public function shortify(string $s, int $len = 10): string - { - return mb_substr($s, 0, $len); - } -} - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); +$latte->addFilterLoader(fn(string $name) => /* 呼び出し可能なオブジェクトまたはnullを返す */); ``` -PHP 7.x および Latte 2.x を使用している場合は、属性のかわりに`/** @filter */` アノテーションを使用します。 - -フィルターローダー .[#toc-filter-loader] -------------------------------- - -個々のフィルタを登録する代わりに、いわゆるローダーを作成することができます。これは、フィルタ名を引数として呼び出され、そのPHP callable、あるいはnullを返す関数です。 +テンプレート式で使用可能な関数を登録するには、`addFunction()`を使用します。 ```php -$latte->addFilterLoader([new Filters, 'load']); +$latte = new Latte\Engine; +// 関数定義 +$isWeekend = fn(DateTimeInterface $date) => $date->format('N') >= 6; -class Filters -{ - public function load(string $filter): ?callable - { - if (in_array($filter, get_class_methods($this))) { - return [$this, $filter]; - } - return null; - } - - public function shortify($s, $len = 10) - { - return mb_substr($s, 0, $len); - } - - // ... -} +// 登録 +$latte->addFunction('isWeekend', $isWeekend); + +// テンプレートでの使用:{if isWeekend($myDate)}週末!{/if} ``` +詳細については、[カスタムフィルタの作成 |custom-filters]および[関数 |custom-functions]セクションを参照してください。 -コンテキスト・フィルタ .[#toc-contextual-filters] --------------------------------------- -コンテキストフィルタは、古典的なフィルタと同様に、最初のパラメータにオブジェクト[api:Latte\Runtime\FilterInfo] を、その後に他のパラメータを受け付けるフィルタです。登録方法は同じで、Latte自身が文脈的なフィルタであることを認識します。 +堅牢な方法:Latte Extension .{toc: Latte Extension} +============================================= -```php -use Latte\Runtime\FilterInfo; +直接登録は簡単ですが、Latte拡張機能をパッケージ化して配布するための標準的で推奨される方法は、**Extension**クラスを使用することです。Extensionは、複数のタグ、フィルタ、関数、コンパイルパス、およびその他の要素を登録するための中心的な設定ポイントとして機能します。 -$latte->addFilter('foo', function (FilterInfo $info, string $str): string { - // ... -}); -``` +なぜExtensionsを使用するのですか? -コンテキストフィルタは、`$info->contentType` 変数で受け取ったコンテントタイプを検知して変更することができます。もしフィルタが変数上で古典的に呼ばれた場合(例えば`{$var|foo}` )、`$info->contentType` には null が入ります。 +- **整理:** 関連する拡張機能(特定の機能のためのタグ、フィルタなど)を1つのクラスにまとめます。 +- **再利用性と共有:** 他のプロジェクトで使用したり、コミュニティと共有したりするために拡張機能を簡単にパッケージ化できます(例:Composer経由)。 +- **フルパワー:** カスタムタグとコンパイルパスは、Extensionsを介して*のみ*登録できます。 -フィルタはまず入力文字列のcontent-typeがサポートされているかどうかをチェックします。また、それを変更することもできます。テキスト (または null) を受け取り、HTML を返すフィルタの例。 + +Extensionの登録 +------------ + +Extensionは、`addExtension()`メソッドを使用してLatteに登録されます(または[設定ファイル |application:configuration#Latte テンプレート]を介して): ```php -use Latte\Runtime\FilterInfo; - -$latte->addFilter('money', function (FilterInfo $info, float $amount): string { - // first we check if the input's content-type is text - if (!in_array($info->contentType, [null, ContentType::Text])) { - throw new Exception("Filter |money used in incompatible content type $info->contentType."); - } - - // change content-type to HTML - $info->contentType = ContentType::Html; - return "$num Kč"; -}); +$latte = new Latte\Engine; +$latte->addExtension(new MyProjectExtension); ``` -.[note] -この場合、フィルタはデータの正しいエスケープを保証しなければなりません。 +複数の拡張機能を登録し、それらが同じ名前のタグ、フィルタ、または関数を定義する場合、最後に追加された拡張機能が優先されます。これは、拡張機能がネイティブのタグ/フィルタ/関数を上書きできることも意味します。 -[ブロックの |tags#block]上で使われるすべてのフィルタ (例えば `{block|foo}.. .{/block}`のように) ブロック上で使われるフィルタはすべて文脈に沿ったものでなければなりません。 +クラスに変更を加え、自動更新が無効になっていない場合、Latteは自動的にテンプレートを再コンパイルします。 -関数 .[#toc-functions] -==================== +Extensionの作成 +------------ -デフォルトでは、サンドボックスで無効にされない限り、すべてのPHPネイティブ関数がLatteで使用可能です。しかし、独自の関数を定義することも可能です。それらはネイティブ関数をオーバーライドすることができます。 +カスタム拡張機能を作成するには、[api:Latte\Extension]を継承するクラスを作成する必要があります。そのような拡張機能がどのように見えるかを知るには、組み込みの[CoreExtension](https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php)を参照してください。 -関数名と任意の PHP callable を登録することで、関数を作成します。 +実装できるメソッドを見てみましょう: -```php -$latte = new Latte\Engine; -$latte->addFunction('random', function (...$args) { - return $args[array_rand($args)]; -}); -``` -その場合の使用法は、PHP の関数をコールする場合と同じです。 +beforeCompile(Latte\Engine $engine): void .[method] +--------------------------------------------------- -```latte -{random(apple, orange, lemon)} // prints for example: apple -``` +テンプレートのコンパイル前に呼び出されます。このメソッドは、例えばコンパイルに関連する初期化に使用できます。 -クラスを使った関数 .[#toc-functions-using-the-class] -------------------------------------------- +getTags(): array .[method] +-------------------------- -関数を定義する第二の方法は、[クラスを |develop#Parameters as a class]使用することです。`TemplateFunction` 属性でメソッドを作成します。 +テンプレートのコンパイル時に呼び出されます。*タグ名 => 呼び出し可能なオブジェクト*の連想配列を返します。これらはタグをパースするための関数です。[詳細情報 |custom-tags]。 ```php -class TemplateParameters +public function getTags(): array { - public function __construct( - // parameters - ) {} - - #[Latte\Attributes\TemplateFunction] - public function random(...$args) - { - return $args[array_rand($args)]; - } + return [ + 'foo' => FooNode::create(...), + 'bar' => BarNode::create(...), + 'n:baz' => NBazNode::create(...), + // ... + ]; } - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); ``` -PHP 7.x および Latte 2.x を使用している場合は、属性の代わりに`/** @function */` というアノテーションを使用します。 +タグ`n:baz`は、純粋な[n:属性 |syntax#n:属性]、つまり属性としてのみ記述できるタグを表します。 +タグ`foo`と`bar`については、Latteはそれらがペアタグであるかどうかを自動的に認識し、そうであれば、`n:inner-foo`や`n:tag-foo`といったプレフィックス付きのバリアントを含むn:属性を使用して自動的に記述できます。 -ローダー .[#toc-loaders] -==================== +このようなn:属性の実行順序は、`getTags()`メソッドによって返される配列内の順序によって決定されます。したがって、HTMLタグ内で属性が`
                                                                                  `のように逆の順序でリストされていても、`n:foo`は常に`n:bar`の前に実行されます。 -ローダーは、ファイルシステムなどのソースからテンプレートをロードする役割を果たします。ローダーは`setLoader()` メソッドで設定します。 +複数の拡張機能にわたるn:属性の順序を指定する必要がある場合は、ヘルパーメソッド`order()`を使用します。ここで、パラメータ`before` xor `after`は、どのタグが指定されたタグの前または後にソートされるかを指定します。 ```php -$latte->setLoader(new MyLoader); +public function getTags(): array +{ + return [ + 'foo' => self::order(FooNode::create(...), before: 'bar')] + 'bar' => self::order(BarNode::create(...), after: ['block', 'snippet'])] + ]; +} ``` -組み込みのローダーは次のとおりです。 +getPasses(): array .[method] +---------------------------- -FileLoader .[#toc-fileloader] ------------------------------ - -デフォルトのローダーです。ファイルシステムからテンプレートをロードします。 +テンプレートのコンパイル時に呼び出されます。*パス名 => 呼び出し可能なオブジェクト*の連想配列を返します。これらは、ASTを走査して変更する、いわゆる[コンパイルパス |compiler-passes]を表す関数です。 -ベースディレクトリを設定することで、ファイルへのアクセスを制限することができます。 +ここでもヘルパーメソッド`order()`を使用できます。パラメータ`before`または`after`の値は、すべてより前/後を意味する`*`にすることができます。 ```php -$latte->setLoader(new Latte\Loaders\FileLoader($templateDir)); -$latte->render('test.latte'); +public function getPasses(): array +{ + return [ + 'optimize' => Passes::optimizePass(...), + 'sandbox' => self::order($this->sandboxPass(...), before: '*'), + // ... + ]; +} ``` -StringLoader .[#toc-stringloader] ---------------------------------- +beforeRender(Latte\Engine $engine): void .[method] +-------------------------------------------------- -文字列からテンプレートをロードします。このローダーはユニットテストのために非常に便利です。また、すべてのテンプレートを単一の PHP ファイルに保存することが理にかなっているような小さなプロジェクトにも使用できます。 +各テンプレートのレンダリング前に呼び出されます。このメソッドは、例えばレンダリング中に使用される変数の初期化に使用できます。 -```php -$latte->setLoader(new Latte\Loaders\StringLoader([ - 'main.file' => '{include other.file}', - 'other.file' => '{if true} {$var} {/if}', -])); -$latte->render('main.file'); -``` +getFilters(): array .[method] +----------------------------- -簡略化された使用方法。 +テンプレートのレンダリング前に呼び出されます。フィルタを*フィルタ名 => 呼び出し可能なオブジェクト*の連想配列として返します。[詳細情報 |custom-filters]。 ```php -$template = '{if true} {$var} {/if}'; -$latte->setLoader(new Latte\Loaders\StringLoader); -$latte->render($template); +public function getFilters(): array +{ + return [ + 'batch' => $this->batchFilter(...), + 'trim' => $this->trimFilter(...), + // ... + ]; +} ``` -カスタムローダーの作成 .[#toc-creating-a-custom-loader] --------------------------------------------- - -Loader は、[api:Latte\Loader] インターフェースを実装したクラスです。 +getFunctions(): array .[method] +------------------------------- +テンプレートのレンダリング前に呼び出されます。関数を*関数名 => 呼び出し可能なオブジェクト*の連想配列として返します。[詳細情報 |custom-functions]。 -タグ .[#toc-tags] -=============== +```php +public function getFunctions(): array +{ + return [ + 'clamp' => $this->clampFunction(...), + 'divisibleBy' => $this->divisibleByFunction(...), + // ... + ]; +} +``` -テンプレートエンジンの最も興味深い機能の1つは、タグを使って新しい言語構造を定義する機能です。また、より複雑な機能であり、Latteが内部でどのように動作しているかを理解する必要があります。 -しかし、ほとんどの場合、このタグは必要ありません。 -- もし出力を生成するのであれば、[関数で |#functions]代用できます。 -- 入力を加工して返すのであれば、[filterを |#filters]使う。 -- テキストを編集するのであれば、そのテキストを [`{block}` |tags#block]タグで囲み、[フィルタを |#Contextual Filters]使う -- 何も出力せず、ただ関数を呼び出すのであれば、その関数を [`{do}` |tags#do] +getProviders(): array .[method] +------------------------------- -それでもまだタグを作りたいなら、すばらしいことです。すべての要点は、[Creating an Extensionに |creating-extension]記載されています。 +テンプレートのレンダリング前に呼び出されます。プロバイダーの配列を返します。これらは通常、実行時にタグによって使用されるオブジェクトです。それらは`$this->global->...`を介してアクセスされます。[詳細情報 |custom-tags#プロバイダの紹介]。 +```php +public function getProviders(): array +{ + return [ + 'myFoo' => $this->foo, + 'myBar' => $this->bar, + // ... + ]; +} +``` -コンパイラのパス .[#toc-compiler-passes] -================================ -コンパイラパスとは,ASTを修正したり,AST内の情報を収集したりする関数です.例えばLatteでは,サンドボックスがこのように実装されています.ASTのすべてのノードを走査して,関数やメソッドの呼び出しを見つけ,制御された呼び出しに置き換えます. +getCacheKey(Latte\Engine $engine): mixed .[method] +-------------------------------------------------- -タグと同様、この機能はより複雑であり、Latteがどのように動作しているかを理解する必要があります。重要なことは「[拡張機能の作成 |creating-extension]」の章に書かれています。 +テンプレートのレンダリング前に呼び出されます。戻り値は、コンパイルされたテンプレートのファイル名に含まれるハッシュのキーの一部になります。したがって、異なる戻り値に対して、Latteは異なるキャッシュファイルを生成します。 diff --git a/latte/ja/filters.texy b/latte/ja/filters.texy index a234c80655..932c6a6edc 100644 --- a/latte/ja/filters.texy +++ b/latte/ja/filters.texy @@ -1,112 +1,114 @@ -ラテフィルタ -****** +Latte フィルタ +********** .[perex] -フィルタは、データを好きな形に変更したり、フォーマットしたりする機能です。ここでは、内蔵されているフィルタの概要を説明します。 +テンプレートでは、データを最終的な形式に編集または再フォーマットするのに役立つ関数を使用できます。これらを*フィルタ*と呼びます。 .[table-latte-filters] -|## 文字列・配列の変換 -|`batch` | [テーブルの中の線形データをリストアップする|#batch] -|`breakLines` | [すべての改行の前に HTML の改行を挿入する|#breakLines] -|`bytes` | [サイズをバイト単位でフォーマットする|#bytes] -|`clamp` | [値を範囲にクランプする|#clamp] -|`dataStream` | [データ URI プロトコル変換 |#datastream] -|`date` | [日付の書式設定|#date] -|`explode` | [与えられたデリミターで文字列を分割する|#explode] -|`first` | [配列の最初の要素または文字列の文字を返します |#first] -|`implode` | [配列と文字列を結合します|#implode] -|`indent` | テキストを[左からタブの数だけインデントする|#indent] -|`join` | [配列と 文字列を結合します|#implode] -|`last` | 配列の[最後の要素または文字列の文字を返します|#last] -|`length` | [文字列または配列の長さを返す|#length] -|`number` | [数値をフォーマットする|#number] -|`padLeft` | [左から指定された長さまで文字列を伸ばします|#padLeft] -|`padRight` | 右[から指定 された長さまで文字列を補完する|#padRight] -|`random` | [配列のランダムな要素または文字列の文字を返します|#random] -|`repeat` | [文字列を繰り返す |#repeat] -|`replace` | [検索文字列のすべての出現箇所を置換文字列で置き換えます|#replace] -|`replaceRE` | [正規表現にしたがってすべての出現箇所を置換します|#replaceRE] -|`reverse` | [UTF-8 文字列または配列を反転します|#reverse] -|`slice` | [配列または文字列のスライスを抽出します|#slice] -|`sort` | [配列をソートします|#sort] -|`spaceless` | [空白を削除 spaceless |tags] -|`split` | [与えられたデリミタで文字列を分割する|#explode] -|`strip` | [空白を削除します|#spaceless] -|`stripHtml` | [HTML タグを削除し、HTML エンティティをテキストに変換します|#stripHtml] -|`substr` | [文字列の一部を返します |#substr] -|`trim` | [文字 列からホワイトスペースを除去します|#trim] -|`translate` | [他の言語への翻訳 |#translate] -|`truncate` | [単語全体を保存する長さを短縮します|#truncate] -|`webalize` | [UTF-8 文字列を URL で使用されている形に調整します|#webalize] +|## 変換 +| `batch` | [線形データのテーブルへの出力 |#batch] +| `breakLines` | [行末にHTMLの改行を追加 |#breakLines] +| `bytes` | [バイト単位のサイズをフォーマット |#bytes] +| `clamp` | [値を指定された範囲内に制限 |#clamp] +| `dataStream` | [Data URIプロトコルへの変換 |#dataStream] +| `date` | [日付と時刻をフォーマット |#date] +| `explode` | [区切り文字で文字列を配列に分割 |#explode] +| `first` | [配列の最初の要素または文字列の最初の文字を返す |#first] +| `group` | [さまざまな基準でデータをグループ化 |#group] +| `implode` | [配列を文字列に結合 |#implode] +| `indent` | [指定された数のタブでテキストを左からインデント |#indent] +| `join` | [配列を文字列に結合 |#implode] +| `last` | [配列の最後の要素または文字列の最後の文字を返す |#last] +| `length` | [文字列の文字数または配列の長さを返す |#length] +| `localDate` | [ロケールに従って日付と時刻をフォーマット |#localDate] +| `number` | [数値をフォーマット |#number] +| `padLeft` | [文字列を左から指定の長さにパディング |#padLeft] +| `padRight` | [文字列を右から指定の長さにパディング |#padRight] +| `random` | [配列のランダムな要素または文字列のランダムな文字を返す |#random] +| `repeat` | [文字列の繰り返し |#repeat] +| `replace` | [検索文字列の出現箇所を置換 |#replace] +| `replaceRE` | [正規表現に従って出現箇所を置換 |#replaceRE] +| `reverse` | [UTF‑8文字列または配列を反転 |#reverse] +| `slice` | [配列または文字列の一部を抽出 |#slice] +| `sort` | [配列をソート |#sort] +| `spaceless` | [空白を除去 |#spaceless]、[spaceless |tags]タグと同様 +| `split` | [区切り文字で文字列を配列に分割 |#explode] +| `strip` | [空白を除去 |#spaceless] +| `stripHtml` | [HTMLタグを除去し、HTMLエンティティを文字に変換 |#stripHtml] +| `substr` | [文字列の一部を返す |#substr] +| `trim` | [先頭と末尾の空白または他の文字を除去 |#trim] +| `translate` | [他の言語への翻訳 |#translate] +| `truncate` | [単語を保持したまま長さを短縮 |#truncate] +| `webalize` | [UTF‑8文字列をURLで使用される形式に変換 |#webalize] .[table-latte-filters] -|## 文字のケーシング -|`capitalize` | [小文字にし、各単語の最初の文字を大文字にする。|#capitalize] -|`firstUpper` | [最初の文字を大文字にします。|#firstUpper] -|`lower` | [文字列を小文字にする|#lower] -|`upper` | [文字列を大文字にする|#upper] +|## 大文字小文字 +| `capitalize` | [小文字にし、単語の最初の文字を大文字に |#capitalize] +| `firstUpper` | [最初の文字を大文字に変換 |#firstUpper] +| `lower` | [小文字に変換 |#lower] +| `upper` | [大文字に変換 |#upper] .[table-latte-filters] -|## 数値の丸め -|`ceil` | [指定された精度に数値を丸めます |#ceil] -|`floor` | [指定された精度に数値を丸めます |#floor] -|`round` | [与えられた精度に数値を丸めます |#round] +|## 丸め +| `ceil` | [数値を指定された精度で切り上げ |#ceil] +| `floor` | [数値を指定された精度で切り捨て |#floor] +| `round` | [数値を指定された精度で四捨五入 |#round] .[table-latte-filters] -|# エスケープ -|`escapeUrl` | [URLのパラメータをエスケープする|#escapeUrl] -|`noescape` | [変数をエスケープせずに表示する|#noescape] -|`query` | [URLにクエリ文字列を生成します|#query] +|## エスケープ +| `escapeUrl` | [URL内のパラメータをエスケープ |#escapeUrl] +| `noescape` | [変数をエスケープせずに出力 |#noescape] +| `query` | [URLのクエリ文字列を生成 |#query] -HTML (`escapeHtml` と`escapeHtmlComment`)、XML (`escapeXml`)、JavaScript (`escapeJs`)、CSS (`escapeCss`)、iCalendar (`escapeICal`)用のエスケープフィルタもあります。これらは[コンテキストアウェアエスケープの |safety-first#Context-aware escaping]おかげでラテが勝手に使ってくれるので、書く必要はありません。 +さらに、HTML (`escapeHtml` および `escapeHtmlComment`)、XML (`escapeXml`)、JavaScript (`escapeJs`)、CSS (`escapeCss`)、および iCalendar (`escapeICal`) 用のエスケープフィルタがあります。これらは Latte が[コンテキストに応じたエスケープ |safety-first#コンテキストに応じたエスケープ]のおかげで自動的に使用するため、記述する必要はありません。 .[table-latte-filters] |## セキュリティ -|`checkUrl` | [href属性で使用する文字列をサニタイズします|#checkUrl] -|`nocheck` | [自動URLサニタイズ機能を無効にする|#nocheck] +| `checkUrl` | [危険な入力からURLアドレスをサニタイズ |#checkUrl] +| `nocheck` | [URLアドレスの自動サニタイズを防止 |#nocheck] -`src` と`href` の属性[チェックは自動的に |safety-first#link checking]行われるため、`checkUrl` のフィルタを使用する必要はほとんどありません。 +Latte の `src` および `href` 属性は[自動的にチェック |safety-first#リンクのチェック]されるため、`checkUrl` フィルタを使用する必要はほとんどありません。 .[note] -すべての組み込みフィルタは、UTF-8でエンコードされた文字列を扱うことができます。 +すべてのデフォルトフィルタは、UTF-8 エンコーディングの文字列を対象としています。 -使用方法 .[#toc-usage] -================== +使用法 +=== -Latteでは,パイプ記号を使った記法でフィルタを呼び出すことができます(直前のスペースも可). +フィルタは縦棒の後ろに記述します(前に空白があってもかまいません): ```latte

                                                                                  {$heading|upper}

                                                                                  ``` -フィルターは連結することができ、その場合、左から右の順に適用されます。 +フィルタ(古いバージョンではヘルパー)は連結でき、左から右の順に適用されます: ```latte

                                                                                  {$heading|lower|capitalize}

                                                                                  ``` -パラメータはコロンまたはカンマで区切られたフィルタ名の後に置かれます。 +パラメータはフィルタ名の後にコロンまたはカンマで区切って指定します: ```latte

                                                                                  {$heading|truncate:20,''}

                                                                                  ``` -フィルタは式に対して適用することができる。 +フィルタは式にも適用できます: ```latte {var $name = ($title|upper) . ($subtitle|lower)} ``` -[カスタムフィルターは |extending-latte#filters]、このように登録することができます。 +[カスタムフィルタ|custom-filters]はこのように登録できます: ```php $latte = new Latte\Engine; $latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -このようなテンプレートで使用します。 +テンプレートでは次のように呼び出されます: ```latte

                                                                                  {$text|shortify}

                                                                                  @@ -114,13 +116,13 @@ $latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $ ``` -フィルター .[#toc-filters] -===================== +フィルタ +==== -batch(int length, mixed item): array .[filter]{data-version:2.7} ----------------------------------------------------------------- -線形データの表形式でのリストアップを簡略化するフィルタです。これは、指定された数の項目を持つ配列の配列を返します。第2パラメータを指定した場合、これは最終行の欠落した項目を埋めるために使用されます。 +batch(int $length, mixed $item): array .[filter] +------------------------------------------------ +線形データをテーブル形式で出力するのを簡略化するフィルタ。指定された数の項目を持つ配列の配列を返します。2番目のパラメータを指定すると、最後の行の欠落している項目を補完するために使用されます。 ```latte {var $items = ['a', 'b', 'c', 'd', 'e']} @@ -135,7 +137,7 @@ batch(int length, mixed item): array .[filter]{data-version:2.7}
                                                                                  内側のループ
                                                                                  ``` -印刷します。 +出力: ```latte @@ -152,93 +154,95 @@ batch(int length, mixed item): array .[filter]{data-version:2.7}
                                                                                  ``` +参照 [#group] および [iterateWhile |tags#iterateWhile] タグ。 + breakLines .[filter] -------------------- -すべての改行文字の前にHTMLの改行を挿入します。 +各改行文字の前にHTMLタグ `
                                                                                  ` を追加します。 ```latte {var $s = "Text & with \n newline"} -{$s|breakLines} {* outputs "Text & with
                                                                                  \n newline" *} +{$s|breakLines} {* "Text & with
                                                                                  \n newline" を出力 *} ``` -bytes(int precision = 2) .[filter] ----------------------------------- -バイト単位のサイズを人間が読みやすい形に整形する。 +bytes(int $precision=2) .[filter] +--------------------------------- +バイト単位のサイズを人間が読める形式にフォーマットします。[ロケール |develop#Locale]が設定されている場合、対応する小数点および千単位の区切り文字が使用されます。 ```latte -{$size|bytes} 0 B, 1.25 GB, … -{$size|bytes:0} 10 B, 1 GB, … +{$size|bytes} {* 0 B, 1.25 GB, … *} +{$size|bytes:0} {* 10 B, 1 GB, … *} ``` -ceil(int precision = 0) .[filter] ---------------------------------- -数値を指定された精度で丸める。 +ceil(int $precision=0) .[filter] +-------------------------------- +数値を指定された精度で切り上げます。 ```latte -{=3.4|ceil} {* outputs 4 *} -{=135.22|ceil:1} {* outputs 135.3 *} -{=135.22|ceil:3} {* outputs 135.22 *} +{=3.4|ceil} {* 4 を出力 *} +{=135.22|ceil:1} {* 135.3 を出力 *} +{=135.22|ceil:3} {* 135.22 を出力 *} ``` -[floor |#floor],[roundも |#round]参照のこと。 +参照 [#floor], [#round]。 capitalize .[filter] -------------------- -値をタイトルケースに入れたものを返します。単語は大文字で始まり、残りの文字はすべて小文字になります。PHP 拡張モジュールが必要です`mbstring`. +単語の最初の文字を大文字にし、残りのすべての文字を小文字にします。PHP拡張機能 `mbstring` が必要です。 ```latte -{='i like LATTE'|capitalize} {* outputs 'I Like Latte' *} +{='i like LATTE'|capitalize} {* 'I Like Latte' を出力 *} ``` -[firstUpper |#firstUpper],[lower |#lower],[upper |#upper] も参照ください。 +参照 [#firstUpper], [#lower], [#upper]。 checkUrl .[filter] ------------------ -URLのサニタイズ処理を行います。変数が Web URL (つまり HTTP/HTTPS プロトコル) を含んでいるかどうかをチェックし、セキュリティ上のリスクをもたらす可能性のあるリンクの書き込みを防止します。 +URLアドレスのサニタイズを強制します。変数がWeb URL(つまりHTTP/HTTPSプロトコル)を含んでいるかを確認し、セキュリティリスクをもたらす可能性のあるリンクの出力を防ぎます。 ```latte {var $link = 'javascript:window.close()'} -checked -unchecked +チェック済み +未チェック ``` -印刷します。 +出力: ```latte -checked -unchecked +チェック済み +未チェック ``` -[nocheckも |#nocheck]参照してください。 +参照 [#nocheck]。 -clamp(int|float min, int|float max) .[filter]{data-version:2.9} ---------------------------------------------------------------- -minとmaxの包括的な範囲にクランプされた値を返す。 +clamp(int|float $min, int|float $max) .[filter] +----------------------------------------------- +値を指定された包括的な範囲minとmax内に制限します。 ```latte {$level|clamp: 0, 255} ``` -[関数としても |functions#clamp]存在する。 +[関数 |functions#clamp]としても存在します。 -dataStream(string mimetype = detect) .[filter] ----------------------------------------------- -コンテンツをデータURIスキームに変換します。外部ファイルへのリンクを必要とせず、HTMLやCSSに画像を挿入するのに利用できます。 +dataStream(string $mimetype=detect) .[filter] +--------------------------------------------- +コンテンツを data URI scheme に変換します。これにより、外部ファイルをリンクする必要なく、HTMLまたはCSSに画像を埋め込むことができます。 -変数`$img = Image::fromFile('obrazek.gif')` に画像を入れておくとします。 +変数に画像があるとします `$img = Image::fromFile('obrazek.gif')`、すると ```latte - + ``` -例えば印刷します。 +例えば次のように出力されます: ```latte {$name} ``` -[クエリも |#query]参照。 +参照 [#query]。 -explode(string separator = '') .[filter]{data-version:2.10.2} -------------------------------------------------------------- -文字列を指定された区切り文字で分割し、文字列の配列を返します。`split` のエイリアス . +explode(string $separator='') .[filter] +--------------------------------------- +区切り文字に基づいて文字列を配列に分割します。`split` のエイリアス。 ```latte -{='one,two,three'|explode:','} {* returns ['one', 'two', 'three'] *} +{='one,two,three'|explode:','} {* ['one', 'two', 'three'] を返す *} ``` -デリミタが空文字列(デフォルト値)の場合、入力は個々の文字に分割されます。 +区切り文字が空の文字列(デフォルト値)の場合、入力は個々の文字に分割されます: ```latte -{='123'|explode} {* returns ['1', '2', '3'] *} +{='123'|explode} {* ['1', '2', '3'] を返す *} ``` -また,`split` というエイリアスも使用できます. +エイリアス `split` を使用することもできます: ```latte -{='1,2,3'|split:','} {* returns ['1', '2', '3'] *} +{='1,2,3'|split:','} {* ['1', '2', '3'] を返す *} ``` -[implode |#implode] も参照してください。 +参照 [#implode]。 -first .[filter]{data-version:2.10.2} ------------------------------------- -配列の最初の要素、または文字列の1文字を返します。 +first .[filter] +--------------- +配列の最初の要素または文字列の最初の文字を返します: ```latte -{=[1, 2, 3, 4]|first} {* outputs 1 *} -{='abcd'|first} {* outputs 'a' *} +{=[1, 2, 3, 4]|first} {* 1 を出力 *} +{='abcd'|first} {* 'a' を出力 *} ``` -[last |#last],[randomも |#random]参照のこと。 +参照 [#last], [#random]。 -floor(int precision = 0) .[filter] ----------------------------------- -与えられた精度で数値を丸める。 +floor(int $precision=0) .[filter] +--------------------------------- +数値を指定された精度で切り捨てます。 ```latte -{=3.5|floor} {* outputs 3 *} -{=135.79|floor:1} {* outputs 135.7 *} -{=135.79|floor:3} {* outputs 135.79 *} +{=3.5|floor} {* 3 を出力 *} +{=135.79|floor:1} {* 135.7 を出力 *} +{=135.79|floor:3} {* 135.79 を出力 *} ``` -[ceil |#ceil],[roundも |#round]参照のこと。 +参照 [#ceil], [#round]。 firstUpper .[filter] -------------------- -値の最初の文字を大文字に変換します。PHP 拡張モジュールが必要です`mbstring`. +最初の文字を大文字に変換します。PHP拡張機能 `mbstring` が必要です。 ```latte -{='the latte'|firstUpper} {* outputs 'The latte' *} +{='the latte'|firstUpper} {* 'The latte' を出力 *} ``` -[capitalize |#capitalize],[lower |#lower],[upper |#upper] も参照ください。 +参照 [#capitalize], [#lower], [#upper]。 -implode(string glue = '') .[filter] ------------------------------------ -配列中の文字列を連結した文字列を返します。`join` のエイリアス. +group(string|int|\Closure $by): array .[filter]{data-version:3.0.16} +-------------------------------------------------------------------- +フィルタは、さまざまな基準に基づいてデータをグループ化します。 + +この例では、テーブルの行は `categoryId` 列に基づいてグループ化されます。出力は配列の配列で、キーは `categoryId` 列の値です。[詳細なチュートリアルを読む|cookbook/grouping]。 ```latte -{=[1, 2, 3]|implode} {* outputs '123' *} -{=[1, 2, 3]|implode:'|'} {* outputs '1|2|3' *} +{foreach ($items|group: categoryId) as $categoryId => $categoryItems} +
                                                                                    + {foreach $categoryItems as $item} +
                                                                                  • {$item->name}
                                                                                  • + {/foreach} +
                                                                                  +{/foreach} ``` -また、エイリアスとして`join` を使用することもできます。 .{data-version:2.10.2} +参照 [#batch], 関数 [group |functions#group] および [iterateWhile |tags#iterateWhile] タグ。 + + +implode(string $glue='') .[filter] +---------------------------------- +シーケンスの項目を連結した文字列を返します。`join` のエイリアス。 ```latte -{=[1, 2, 3]|join} {* outputs '123' *} +{=[1, 2, 3]|implode} {* '123' を出力 *} +{=[1, 2, 3]|implode:'|'} {* '1|2|3' を出力 *} ``` +エイリアス `join` を使用することもできます: + +```latte +{=[1, 2, 3]|join} {* '123' を出力 *} +``` -indent(int level = 1, string char = "\t") .[filter] ---------------------------------------------------- -テキストを左から、オプションの第2引数で指定した数のタブまたは他の文字でインデントします。空白行はインデントされません。 + +indent(int $level=1, string $char="\t") .[filter] +------------------------------------------------- +テキストを左から指定された数のタブまたは他の文字(2番目の引数で指定可能)でインデントします。空行はインデントされません。 ```latte
                                                                                  @@ -358,7 +382,7 @@ indent(int level = 1, string char = "\t") .[filter]
                                                                                  ``` -印刷します。 +出力: ```latte
                                                                                  @@ -367,26 +391,26 @@ indent(int level = 1, string char = "\t") .[filter] ``` -last .[filter]{data-version:2.10.2} ------------------------------------ -配列の最後の要素、または文字列の文字を返します。 +last .[filter] +-------------- +配列の最後の要素または文字列の最後の文字を返します: ```latte -{=[1, 2, 3, 4]|last} {* outputs 4 *} -{='abcd'|last} {* outputs 'd' *} +{=[1, 2, 3, 4]|last} {* 4 を出力 *} +{='abcd'|last} {* 'd' を出力 *} ``` -[first |#first],[random |#random] も参照してください。 +参照 [#first], [#random]。 length .[filter] ---------------- 文字列または配列の長さを返します。 -- 文字列の場合、UTF-8文字で長さを返します。 -- 配列の場合は、項目の数を返します。 -- Countableインタフェースを実装したオブジェクトの場合, count()の戻り値を使用します。 -- IteratorAggregate インターフェースを実装したオブジェクトの場合、 iterator_count() の戻り値を使用する。 +- 文字列の場合、UTF‑8文字での長さを返します +- 配列の場合、項目数を返します +- `Countable` インターフェースを実装するオブジェクトの場合、`count()` メソッドの戻り値を使用します +- `IteratorAggregate` インターフェースを実装するオブジェクトの場合、`iterator_count()` 関数の戻り値を使用します ```latte @@ -396,38 +420,100 @@ length .[filter] ``` +localDate(?string $format=null, ?string $date=null, ?string $time=null) .[filter] +--------------------------------------------------------------------------------- +[ロケール |develop#Locale]に従って日付と時刻をフォーマットし、異なる言語や地域間で一貫性のあるローカライズされた時間データの表示を保証します。フィルタは、UNIXタイムスタンプ、文字列、または `DateTimeInterface` 型のオブジェクトとして日付を受け入れます。 + +```latte +{$date|localDate} {* 2024年4月15日 *} +{$date|localDate: format: yM} {* 2024/4 *} +{$date|localDate: date: medium} {* 2024/04/15 *} +``` + +パラメータなしでフィルタを使用すると、日付は `long` レベルで表示されます(下記参照)。 + +**a) フォーマットの使用** + +`format` パラメータは、表示する時間コンポーネントを記述します。文字コードを使用し、その繰り返し回数が出力の幅に影響します: + +| 年 | `y` / `yy` / `yyyy` | `2024` / `24` / `2024` +| 月 | `M` / `MM` / `MMM` / `MMMM` | `8` / `08` / `8月` / `8月` +| 日 | `d` / `dd` / `E` / `EEEE` | `1` / `01` / `日` / `日曜日` +| 時 | `j` / `H` / `h` | 推奨 / 24時間形式 / 12時間形式 +| 分 | `m` / `mm` | `5` / `05` (秒と組み合わせる場合は2桁) +| 秒 | `s` / `ss` | `8` / `08` (分と組み合わせる場合は2桁) + +フォーマット内のコードの順序は関係ありません。コンポーネントの順序はロケールの慣習に従って表示されるためです。したがって、フォーマットはロケールに依存しません。例えば、フォーマット `yyyyMMMMd` は、`en_US` 環境では `April 15, 2024` と表示されますが、`ja_JP` 環境では `2024年4月15日` と表示されます: + +| ロケール: | ja_JP | en_US +|--- +| `format: 'dMy'` | 2024/8/10 | 8/10/2024 +| `format: 'yM'` | 2024/8 | 8/2024 +| `format: 'yyyyMMMM'` | 2024年8月 | August 2024 +| `format: 'MMMM'` | 8月 | August +| `format: 'jm'` | 17:22 | 5:22 PM +| `format: 'Hm'` | 17:22 | 17:22 +| `format: 'hm'` | 午後5:22 | 5:22 PM + + +**b) 事前定義されたスタイルの使用** + +`date` および `time` パラメータは、日付と時刻をどの程度詳細に表示するかを指定します。いくつかのレベルから選択できます:`full`、`long`、`medium`、`short`。日付のみ、時刻のみ、または両方を表示させることができます: + +| ロケール: | ja_JP | en_US +|--- +| `date: short` | 78/01/23 | 1/23/78 +| `date: medium` | 1978/01/23 | Jan 23, 1978 +| `date: long` | 1978年1月23日 | January 23, 1978 +| `date: full` | 1978年1月23日月曜日 | Monday, January 23, 1978 +| `time: short` | 8:30 | 8:30 AM +| `time: medium` | 8:30:59 | 8:30:59 AM +| `time: long` | 8:30:59 GMT+9 | 8:30:59 AM GMT+1 +| `date: short, time: short` | 78/01/23 8:30 | 1/23/78, 8:30 AM +| `date: medium, time: short` | 1978/01/23 8:30 | Jan 23, 1978, 8:30 AM +| `date: long, time: short` | 1978年1月23日 8:30 | January 23, 1978 at 8:30 AM + +日付については、さらにプレフィックス `relative-`(例:`relative-short`)を使用できます。これは、現在に近い日付に対して `昨日`、`今日`、または `明日` を表示し、それ以外の場合は標準的な方法で表示します。 + +```latte +{$date|localDate: date: relative-short} {* 昨日 *} +``` + +参照 [#date]。 + + lower .[filter] --------------- -値を小文字に変換します。PHP 拡張モジュール`mbstring` が必要です。 +文字列を小文字に変換します。PHP拡張機能 `mbstring` が必要です。 ```latte -{='LATTE'|lower} {* outputs 'latte' *} +{='LATTE'|lower} {* 'latte' を出力 *} ``` -[capitalize |#capitalize],[firstUpper |#firstUpper],[upper |#upper] も参照ください。 +参照 [#capitalize], [#firstUpper], [#upper]。 nocheck .[filter] ----------------- -自動的なURLサニタイズを防止します。Latte は変数に Web URL (すなわち HTTP/HTTPS プロトコル) が含まれているかどうかを[自動的にチェック |safety-first#Link checking]し、セキュリティ上のリスクがあるリンクの書き込みを防止します。 +URLアドレスの自動サニタイズを防ぎます。Latteは [自動的にチェック |safety-first#リンクのチェック]し、変数がWeb URL(つまりHTTP/HTTPSプロトコル)を含んでいるかを確認し、セキュリティリスクをもたらす可能性のあるリンクの出力を防ぎます。 -リンクに`javascript:` や`data:` などの別のスキームが使われていて、その内容が確かな場合は、`|nocheck` を使ってチェックを無効にすることができます。 +リンクが `javascript:` や `data:` などの他のスキームを使用しており、その内容に自信がある場合は、`|nocheck` を使用してチェックを無効にできます。 ```latte {var $link = 'javascript:window.close()'} -checked -unchecked +チェック済み +未チェック ``` -印刷物 +出力: ```latte -checked -unchecked +チェック済み +未チェック ``` -[checkUrlも |#checkUrl]ご参照ください。 +参照 [#checkUrl]。 noescape .[filter] @@ -436,164 +522,212 @@ noescape .[filter] ```latte {var $trustedHtmlString = 'hello'} -Escaped: {$trustedHtmlString} -Unescaped: {$trustedHtmlString|noescape} +エスケープ済み: {$trustedHtmlString} +エスケープなし: {$trustedHtmlString|noescape} ``` -印刷します。 +出力: ```latte -Escaped: <b>hello</b> -Unescaped: hello +エスケープ済み: <b>hello</b> +エスケープなし: hello ``` .[warning] -`noescape` フィルタを誤用すると、XSS 脆弱性につながる可能性があります!自分が何をしているのか、そして印刷する文字列が信頼できるソースから来たものであるという **絶対** の確信がない限り、決してこれを使わないでください。 +`noescape` フィルタの誤った使用は、XSS脆弱性の発生につながる可能性があります!何をしているかを **完全に確信** しており、出力される文字列が信頼できるソースからのものである場合を除き、絶対に使用しないでください。 -number(int decimals = 0, string decPoint = '.', string thousandsSep = ',') .[filter] ------------------------------------------------------------------------------------- -数値を小数点以下の桁数で表示します。また、小数点、桁区切りの文字も指定できます。 +number(int $decimals=0, string $decPoint='.', string $thousandsSep=',') .[filter] +--------------------------------------------------------------------------------- +数値を指定された小数点以下の桁数にフォーマットします。[ロケール |develop#Locale]が設定されている場合、対応する小数点および千単位の区切り文字が使用されます。 ```latte -{1234.20 |number} 1,234 -{1234.20 |number:1} 1,234.2 -{1234.20 |number:2} 1,234.20 -{1234.20 |number:2, ',', ' '} 1 234,20 +{1234.20|number} {* 1,234 *} +{1234.20|number:1} {* 1,234.2 *} +{1234.20|number:2} {* 1,234.20 *} +{1234.20|number:2, ',', ' '} {* 1 234,20 *} ``` -padLeft(int length, string pad = ' ') .[filter] +number(string $format) .[filter] +-------------------------------- +`format` パラメータを使用すると、ニーズに合わせて数値の外観を正確に定義できます。これには [ロケール |develop#Locale] を設定する必要があります。フォーマットはいくつかの特殊文字で構成されており、その完全な説明は "DecimalFormat":https://unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns ドキュメントにあります: + +- `0` 必須の数字。ゼロであっても常に表示されます。 +- `#` オプションの数字。その場所に数字が実際に存在する場合にのみ表示されます。 +- `@` 有効数字。特定の有効桁数で数値を表示するのに役立ちます。 +- `.` 小数点の場所を示します(国によってはカンマ)。 +- `,` 数字のグループ、通常は千単位を区切るために使用されます。 +- `%` 数値に100を掛け、パーセント記号を追加します。 + +例を見てみましょう。最初の例では、小数点以下2桁が必須ですが、2番目の例ではオプションです。3番目の例は、左側と右側のゼロ埋めを示し、4番目の例は存在する数字のみを表示します: + +```latte +{1234.5|number: '#,##0.00'} {* 1,234.50 *} +{1234.5|number: '#,##0.##'} {* 1,234.5 *} +{1.23 |number: '000.000'} {* 001.230 *} +{1.2 |number: '##.##'} {* 1.2 *} +``` + +有効数字は、小数点に関係なく表示される桁数を決定し、丸めが行われます: + +```latte +{1234|number: '@@'} {* 1200 *} +{1234|number: '@@@'} {* 1230 *} +{1234|number: '@@@#'} {* 1234 *} +{1.2345|number: '@@@'} {* 1.23 *} +{0.00123|number: '@@'} {* 0.0012 *} +``` + +数値をパーセンテージとして表示する簡単な方法。数値に100が掛けられ、`%` 記号が追加されます: + +```latte +{0.1234|number: '#.##%'} {* 12.34% *} +``` + +正の数と負の数に対して異なるフォーマットを定義できます。これらは `;` 記号で区切られます。このようにして、例えば、正の数に `+` 記号を表示するように設定できます: + +```latte +{42|number: '#.##;(#.##)'} {* 42 *} +{-42|number: '#.##;(#.##)'} {* (42) *} +{42|number: '+#.##;-#.##'} {* +42 *} +{-42|number: '+#.##;-#.##'} {* -42 *} +``` + +数値の実際の外観は、国の設定によって異なる場合があることに注意してください。例えば、一部の国では、小数点区切り文字としてドットの代わりにカンマが使用されます。このフィルタはこれを自動的に考慮し、何も心配する必要はありません。 + + +padLeft(int $length, string $pad=' ') .[filter] ----------------------------------------------- -指定された長さの文字列を、左から別の文字列で埋め尽くします。 +文字列を左から別の文字列で指定された長さにパディングします。 ```latte -{='hello'|padLeft: 10, '123'} {* outputs '12312hello' *} +{='hello'|padLeft: 10, '123'} {* '12312hello' を出力 *} ``` -padRight(int length, string pad = ' ') .[filter] +padRight(int $length, string $pad=' ') .[filter] ------------------------------------------------ -ある文字列を右から別の文字列で一定の長さになるように詰めます。 +文字列を右から別の文字列で指定された長さにパディングします。 ```latte -{='hello'|padRight: 10, '123'} {* outputs 'hello12312' *} +{='hello'|padRight: 10, '123'} {* 'hello12312' を出力 *} ``` -query .[filter]{data-version:2.10} ------------------------------------ -URLにクエリ文字列を動的に生成します。 +query .[filter] +--------------- +URLのクエリ文字列を動的に生成します: ```latte -click -search +クリック +検索 ``` -印刷します。 +出力: ```latte -click -search +クリック +検索 ``` -値が`null` のキーは省略されます。 +値が `null` のキーは省略されます。 -[escapeUrlも |#escapeUrl]参照してください。 +参照 [#escapeUrl]。 -random .[filter]{data-version:2.10.2} -------------------------------------- -配列のランダムな要素、または文字列の一文字を返します。 +random .[filter] +---------------- +配列のランダムな要素または文字列のランダムな文字を返します: ```latte -{=[1, 2, 3, 4]|random} {* example output: 3 *} -{='abcd'|random} {* example output: 'b' *} +{=[1, 2, 3, 4]|random} {* 例:3 を出力 *} +{='abcd'|random} {* 例:'b' を出力 *} ``` -[first |#first],[last |#last] も参照してください。 +参照 [#first], [#last]。 -repeat(int count) .[filter] ---------------------------- -文字列をx回繰り返す。 +repeat(int $count) .[filter] +---------------------------- +文字列をx回繰り返します。 ```latte -{='hello'|repeat: 3} {* outputs 'hellohellohello' *} +{='hello'|repeat: 3} {* 'hellohellohello' を出力 *} ``` -replace(string|array search, string replace = '') .[filter] +replace(string|array $search, string $replace='') .[filter] ----------------------------------------------------------- -検索文字列を置換文字列で置き換えます。 +検索文字列のすべての出現箇所を置換文字列で置き換えます。 ```latte -{='hello world'|replace: 'world', 'friend'} {* outputs 'hello friend' *} +{='hello world'|replace: 'world', 'friend'} {* 'hello friend' を出力 *} ``` -一度に複数の置換を行うことができます。 .{data-version:2.10.2} +一度に複数の置換を実行することもできます: ```latte -{='hello world'|replace: [h => l, l => h]} {* outputs 'lehho worhd' *} +{='hello world'|replace: [h => l, l => h]} {* 'lehho worhd' を出力 *} ``` -replaceRE(string pattern, string replace = '') .[filter] +replaceRE(string $pattern, string $replace='') .[filter] -------------------------------------------------------- -正規表現にしたがって、すべての出現箇所を置き換えます。 +置換を伴う正規表現検索を実行します。 ```latte -{='hello world'|replaceRE: '/l.*/', 'l'} {* outputs 'hel' *} +{='hello world'|replaceRE: '/l.*/', 'l'} {* 'hel' を出力 *} ``` reverse .[filter] ----------------- -与えられた文字列または配列を反転させます。 +指定された文字列または配列を反転します。 ```latte {var $s = 'Nette'} -{$s|reverse} {* outputs 'etteN' *} +{$s|reverse} {* 'etteN' を出力 *} {var $a = ['N', 'e', 't', 't', 'e']} -{$a|reverse} {* returns ['e', 't', 't', 'e', 'N'] *} +{$a|reverse} {* ['e', 't', 't', 'e', 'N'] を返す *} ``` -round(int precision = 0) .[filter] ----------------------------------- -指定された精度で数値を丸めます。 +round(int $precision=0) .[filter] +--------------------------------- +数値を指定された精度で四捨五入します。 ```latte -{=3.4|round} {* outputs 3 *} -{=3.5|round} {* outputs 4 *} -{=135.79|round:1} {* outputs 135.8 *} -{=135.79|round:3} {* outputs 135.79 *} +{=3.4|round} {* 3 を出力 *} +{=3.5|round} {* 4 を出力 *} +{=135.79|round:1} {* 135.8 を出力 *} +{=135.79|round:3} {* 135.79 を出力 *} ``` -[ceil |#ceil],[floorも |#floor]参照のこと。 +参照 [#ceil], [#floor]。 -slice(int start, int length = null, bool preserveKeys = false) .[filter]{data-version:2.10.2} ---------------------------------------------------------------------------------------------- -配列または文字列のスライスを抽出します。 +slice(int $start, ?int $length=null, bool $preserveKeys=false) .[filter] +------------------------------------------------------------------------ +配列または文字列の一部を抽出します。 ```latte -{='hello'|slice: 1, 2} {* outputs 'el' *} -{=['a', 'b', 'c']|slice: 1, 2} {* outputs ['b', 'c'] *} +{='hello'|slice: 1, 2} {* 'el' を出力 *} +{=['a', 'b', 'c']|slice: 1, 2} {* ['b', 'c'] を出力 *} ``` -このスライスフィルタは、配列に対しては`array_slice` PHP 関数として、文字列に対しては`mb_substr` として動作し、UTF-8 モードでは`iconv_substr` にフォールバックします。 +フィルタは、配列の場合はPHP関数 `array_slice` として、文字列の場合は `mb_substr` として機能し、UTF‑8 モードでは `iconv_substr` 関数へのフォールバックがあります。 -start が負でない場合、シーケンスは変数内のその位置から始まります。start が負の場合は、シーケンスは変数の終端からその距離だけ離れたところから始まります。 +`start` が正の場合、シーケンスは配列/文字列の先頭からこの数だけオフセットされて開始します。負の場合、シーケンスは末尾からそれだけオフセットされて開始します。 -length が正の値であれば,シーケンスはその要素数までとなる。もし変数が length よりも短ければ、利用可能な変数の要素だけが存在することになります。もし length が負数なら、シーケンスは変数の終端からその数だけ要素を持つことになります。もし length が省略された場合は、offset から変数の終わりまでがシーケンスに含まれます。 +`length` パラメータが指定され、正の場合、シーケンスにはその数の要素が含まれます。この関数に負の `length` パラメータが渡された場合、シーケンスには元の配列のすべての要素が含まれ、`start` 位置から開始し、配列の末尾から `length` 要素少ない位置で終了します。このパラメータを指定しない場合、シーケンスには元の配列のすべての要素が含まれ、`start` 位置から開始します。 -Filter は、デフォルトで整数配列のキーを並べ替え、リセットします。この挙動は、preserveKeys を true に設定することで変更可能です。文字列のキーは、このパラメータに関係なく、常に保存されます。 +デフォルトでは、フィルタは順序を変更し、配列の整数キーをリセットします。この動作は、`preserveKeys` を `true` に設定することで変更できます。文字列キーは、このパラメータに関係なく常に保持されます。 -sort .[filter]{data-version:2.9} ---------------------------------- -配列をソートし、インデックスの関連付けを維持するフィルタです。 +sort(?Closure $comparison, string|int|\Closure|null $by=null, string|int|\Closure|bool $byKey=false) .[filter] +-------------------------------------------------------------------------------------------------------------- +フィルタは、配列またはイテレータの要素をソートし、それらの連想キーを保持します。[ロケール |develop#Locale]が設定されている場合、独自の比較関数が指定されていない限り、ソートはそのルールに従います。 ```latte {foreach ($names|sort) as $name} @@ -601,7 +735,7 @@ sort .[filter]{data-version:2.9} {/foreach} ``` -逆順にソートされた配列。 +逆順にソートされた配列: ```latte {foreach ($names|sort|reverse) as $name} @@ -609,16 +743,42 @@ sort .[filter]{data-version:2.9} {/foreach} ``` -パラメータとして、独自の比較関数を渡すことができます。 .{data-version:2.10.2} +ソート用の独自の比較関数を指定できます(例は、最大から最小への逆順ソート方法を示しています): ```latte -{var $sorted = ($names|sort: fn($a, $b) => $b <=> $a)} +{var $reverted = ($names|sort: fn($a, $b) => $b <=> $a)} ``` +`|sort` フィルタでは、キーによる要素のソートも可能です: -spaceless .[filter]{data-version:2.10.2} ------------------------------------------ -出力から不要な空白を削除します。また、エイリアス`strip` を使用することもできます。 +```latte +{foreach ($names|sort: byKey: true) as $name} + ... +{/foreach} +``` + +特定の列でテーブルをソートする必要がある場合は、`by` パラメータを使用できます。例の `'name'` の値は、`$item` が配列かオブジェクトかに応じて、`$item->name` または `$item['name']` でソートすることを示します: + +```latte +{foreach ($items|sort: by: 'name') as $item} + {$item->name} +{/foreach} +``` + +ソート基準となる値を決定するコールバック関数を定義することもできます: + +```latte +{foreach ($items|sort: by: fn($item) => $item->category->name) as $item} + {$item->name} +{/foreach} +``` + +`byKey` パラメータも同様に使用できます。 + + +spaceless .[filter] +------------------- +出力から不要な空白(スペース)を除去します。エイリアス `strip` を使用することもできます。 ```latte {block |spaceless} @@ -628,56 +788,56 @@ spaceless .[filter]{data-version:2.10.2} {/block} ``` -印刷します。 +出力: ```latte -
                                                                                  • Hello
                                                                                  +
                                                                                  • Hello
                                                                                  ``` stripHtml .[filter] ------------------- -HTMLをプレーンテキストに変換する。つまり、HTMLタグを削除し、HTMLの実体をテキストに変換します。 +HTMLをプレーンテキストに変換します。つまり、HTMLタグを除去し、HTMLエンティティをテキストに変換します。 ```latte -{='

                                                                                  one < two

                                                                                  '|stripHtml} {* outputs 'one < two' *} +{='

                                                                                  one < two

                                                                                  '|stripHtml} {* 'one < two' を出力 *} ``` -変換後のプレーンテキストには、当然ながらHTMLタグを表す文字が含まれます。例えば、`'<p>'|stripHtml` は、次のように変換されます。 `

                                                                                  `.セキュリティ上の脆弱性があるため、結果のテキストを`|noescape` で出力することは絶対に避けてください。 +結果のプレーンテキストには、自然にHTMLタグを表す文字が含まれる可能性があります。例えば、`'<p>'|stripHtml` は `

                                                                                  ` に変換されます。このようなテキストを `|noescape` で出力しないでください。セキュリティホールの原因となる可能性があります。 -substr(int offset, int length = null) .[filter] ------------------------------------------------ -文字列のスライスを抽出する。このフィルタは、[スライス |#slice]フィルタに置き換えられました。 +substr(int $offset, ?int $length=null) .[filter] +------------------------------------------------ +文字列の一部を抽出します。このフィルタは [#slice] フィルタに置き換えられました。 ```latte {$string|substr: 1, 2} ``` -translate(string message, ...args) .[filter]{data-version:3.0} --------------------------------------------------------------- -式を他の言語に翻訳します。このフィルタを利用できるようにするには、[トランスレータを |develop#TranslatorExtension]設定する必要があります。また、[翻訳用のタグを |tags#Translation]使用することもできます。 +translate(...$args) .[filter] +----------------------------- +式を他の言語に翻訳します。フィルタを利用可能にするには、[トランスレータを設定 |develop#TranslatorExtension]する必要があります。[翻訳タグ |tags#翻訳]を使用することもできます。 ```latte -{='Baskter'|translate} +{='Košík'|translate} {* Assuming 'Košík' translates to 'Cart' *} {$item|translate} ``` -trim(string charlist = " \t\n\r\0\x0B\u{A0}") .[filter] -------------------------------------------------------- -先頭と末尾の文字を除去します。デフォルトは空白文字です。 +trim(string $charlist=" \t\n\r\0\x0B\u{A0}") .[filter] +------------------------------------------------------ +文字列の先頭と末尾から空白文字(または他の文字)を除去します。 ```latte -{=' I like Latte. '|trim} {* outputs 'I like Latte.' *} -{=' I like Latte.'|trim: '.'} {* outputs ' I like Latte' *} +{=' I like Latte. '|trim} {* 'I like Latte.' を出力 *} +{=' I like Latte.'|trim: '.'} {* ' I like Latte' を出力 *} ``` -truncate(int length, string append = '…') .[filter] +truncate(int $length, string $append='…') .[filter] --------------------------------------------------- -文字列を与えられた最大の長さまで短縮するが、単語全体を保存しようとする。文字列が切り詰められた場合、最後に省略記号を付加します (これは第2引数で変更可能です)。 +文字列を指定された最大長に切り詰めますが、単語全体を保持しようとします。文字列が短縮された場合、最後に省略記号(2番目のパラメータで変更可能)を追加します。 ```latte {var $title = 'Hello, how are you?'} @@ -689,25 +849,25 @@ truncate(int length, string append = '…') .[filter] upper .[filter] --------------- -値を大文字に変換します。PHP 拡張モジュールが必要です`mbstring`. +文字列を大文字に変換します。PHP拡張機能 `mbstring` が必要です。 ```latte -{='latte'|upper} {* outputs 'LATTE' *} +{='latte'|upper} {* 'LATTE' を出力 *} ``` -[capitalize |#capitalize],[firstUpper |#firstUpper],[lower |#lower] も参照ください。 +参照 [#capitalize], [#firstUpper], [#lower]。 webalize .[filter] ------------------ -ASCIIに変換する。 +UTF‑8文字列をURLで使用される形式に変換します。 -空白をハイフンに変換します。英数字、アンダースコア、ハイフンでない文字を削除します。小文字に変換します。また、先頭と末尾の空白を除去します。 +ASCIIに変換されます。スペースをハイフンに変換します。英数字、アンダースコア、ハイフン以外の文字を除去します。小文字に変換します。先頭と末尾のスペースも除去します。 ```latte -{var $s = 'Our 10. product'} -{$s|webalize} {* outputs 'our-10-product' *} +{var $s = 'Náš 10. produkt'} +{$s|webalize} {* 'nas-10-produkt' を出力 *} ``` .[caution] -[nette/utils |utils:en]パッケージが必要です。 +ライブラリ [nette/utils|utils:] が必要です。 diff --git a/latte/ja/functions.texy b/latte/ja/functions.texy index 36bc703ab2..2801546446 100644 --- a/latte/ja/functions.texy +++ b/latte/ja/functions.texy @@ -1,23 +1,25 @@ -ラテ機能 -**** +Latte 関数 +******** .[perex] -PHPの一般的な関数に加え、テンプレートでも使用することができます。 +テンプレートでは、通常のPHP関数に加えて、これらの追加関数を使用できます。 .[table-latte-filters] -|`clamp` |[値を範囲にクランプする|#clamp] -|`divisibleBy`|[ある変数がある数値で割り切れるかどうかをチェックします|#divisibleBy] -|`even` |[与えられた数値が偶数かどうかをチェックします|#even] -|`first` | [配列の最初の要素または文字列の文字を返します|#first] -|`last` | [配列の最後の要素、または文字列の文字を返す|#last] -|`odd` | [与えられた数が奇数かどうかをチェックする|#odd] -|`slice` | [配列または文字列のスライスを抽出します|#slice] +| `clamp` | [値を指定された範囲内に制限 |#clamp] +| `divisibleBy`| [変数が数値で割り切れるかチェック |#divisibleBy] +| `even` | [指定された数値が偶数かチェック |#even] +| `first` | [配列の最初の要素または文字列の最初の文字を返す |#first] +| `group` | [さまざまな基準でデータをグループ化 |#group] +| `hasBlock` | [ブロックの存在を確認 |#hasBlock] +| `last` | [配列の最後の要素または文字列の最後の文字を返す |#last] +| `odd` | [指定された数値が奇数かチェック |#odd] +| `slice` | [配列または文字列の一部を抽出 |#slice] -使用法 .[#toc-usage] -================= +使用法 +=== -関数は一般的なPHPの関数と同じように使用され、すべての式で使用することができます。 +関数は通常のPHP関数と同じように使用され、すべての式で使用できます: ```latte

                                                                                  {clamp($num, 1, 100)}

                                                                                  @@ -25,14 +27,14 @@ PHPの一般的な関数に加え、テンプレートでも使用すること {if odd($num)} ... {/if} ``` -[カスタム関 |extending-latte#functions]数は、この方法で登録することができます。 +[カスタム関数|custom-functions]はこのように登録できます: ```php $latte = new Latte\Engine; $latte->addFunction('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -このようなテンプレートで使用します。 +テンプレートでは次のように呼び出されます: ```latte

                                                                                  {shortify($text)}

                                                                                  @@ -40,85 +42,115 @@ $latte->addFunction('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, ``` -機能 .[#toc-functions] -==================== +関数 +====== -clamp(int|float $value, int|float $min, int|float $max): int|float .[method]{data-version:2.9} ----------------------------------------------------------------------------------------------- -minとmaxを含む範囲にクランプされた値を返します。 +clamp(int|float $value, int|float $min, int|float $max): int|float .[method] +---------------------------------------------------------------------------- +値を指定された包括的な範囲minとmax内に制限します。 ```latte {=clamp($level, 0, 255)} ``` -[フィルタクランプの |filters#clamp]項も参照。 +参照 [clampフィルタ |filters#clamp]。 -divisibleBy(int $value, int $by): bool .[method]{data-version:2.10.2} ---------------------------------------------------------------------- -変数がある数値で割り切れるかどうかをチェックする。 +divisibleBy(int $value, int $by): bool .[method] +------------------------------------------------ +変数が数値で割り切れるかどうかをチェックします。 ```latte {if divisibleBy($num, 5)} ... {/if} ``` -even(int $value): bool .[method]{data-version:2.10.2} ------------------------------------------------------ -与えられた数字が偶数かどうかをチェックする。 +even(int $value): bool .[method] +-------------------------------- +指定された数値が偶数かどうかをチェックします。 ```latte {if even($num)} ... {/if} ``` -first(string|array $value): mixed .[method]{data-version:2.10.2} ----------------------------------------------------------------- -配列の最初の要素、または文字列の1文字を返します。 +first(string|iterable $value): mixed .[method] +---------------------------------------------- +配列の最初の要素または文字列の最初の文字を返します: ```latte -{=first([1, 2, 3, 4])} {* outputs 1 *} -{=first('abcd')} {* outputs 'a' *} +{=first([1, 2, 3, 4])} {* 1 を出力 *} +{=first('abcd')} {* 'a' を出力 *} ``` -[last |#last],[filter first |filters#first] も参照してください。 +参照 [#last], [firstフィルタ |filters#first]。 -last(string|array $value): mixed .[method]{data-version:2.10.2} ---------------------------------------------------------------- -配列の最後の要素または文字列の文字を返します。 +group(iterable $data, string|int|\Closure $by): array .[method]{data-version:3.0.16} +------------------------------------------------------------------------------------ +関数は、さまざまな基準に基づいてデータをグループ化します。 + +この例では、テーブルの行は `categoryId` 列に基づいてグループ化されます。出力は配列の配列で、キーは `categoryId` 列の値です。[詳細なチュートリアルを読む|cookbook/grouping]。 + +```latte +{foreach group($items, categoryId) as $categoryId => $categoryItems} +
                                                                                    + {foreach $categoryItems as $item} +
                                                                                  • {$item->name}
                                                                                  • + {/foreach} +
                                                                                  +{/foreach} +``` + +参照 [groupフィルタ |filters#group]。 + + +hasBlock(string $name): bool .[method]{data-version:3.0.10} +----------------------------------------------------------- +指定された名前のブロックが存在するかどうかを確認します: + +```latte +{if hasBlock(header)} ... {/if} +``` + +参照 [ブロックの存在チェック |template-inheritance#Checking block existence]。 + + +last(string|array $value): mixed .[method] +------------------------------------------ +配列の最後の要素または文字列の最後の文字を返します: ```latte -{=last([1, 2, 3, 4])} {* outputs 4 *} -{=last('abcd')} {* outputs 'd' *} +{=last([1, 2, 3, 4])} {* 4 を出力 *} +{=last('abcd')} {* 'd' を出力 *} ``` -[first |#first],[filter last |filters#last] も参照してください。 +参照 [#first], [lastフィルタ |filters#last]。 -odd(int $value): bool .[method]{data-version:2.10.2} ----------------------------------------------------- -与えられた数字が奇数かどうかをチェックする。 +odd(int $value): bool .[method] +------------------------------- +指定された数値が奇数かどうかをチェックします。 ```latte {if odd($num)} ... {/if} ``` -slice(string|array $value, int $start, int $length=null, bool $preserveKeys=false): string|array .[method]{data-version:2.10.2} -------------------------------------------------------------------------------------------------------------------------------- -配列または文字列のスライスを抽出します。 +slice(string|array $value, int $start, ?int $length=null, bool $preserveKeys=false): string|array .[method] +----------------------------------------------------------------------------------------------------------- +配列または文字列の一部を抽出します。 ```latte -{=slice('hello', 1, 2)} {* outputs 'el' *} -{=slice(['a', 'b', 'c'], 1, 2)} {* outputs ['b', 'c'] *} +{=slice('hello', 1, 2)} {* 'el' を出力 *} +{=slice(['a', 'b', 'c'], 1, 2)} {* ['b', 'c'] を出力 *} ``` -このスライスフィルタは、配列に対しては`array_slice` PHP 関数として、文字列に対しては`mb_substr` として動作し、UTF-8 モードでは`iconv_substr` にフォールバックします。 +この関数は、配列の場合はPHP関数 `array_slice` として、文字列の場合は `mb_substr` として機能し、UTF‑8 モードでは `iconv_substr` 関数へのフォールバックがあります。 -start が負でない場合、シーケンスは変数内のその位置から始まります。start が負の場合は、シーケンスは変数の終端からその距離だけ離れたところから始まります。 +`start` が正の場合、シーケンスは配列/文字列の先頭からこの数だけオフセットされて開始します。負の場合、シーケンスは末尾からそれだけオフセットされて開始します。 -length が正の値であれば,シーケンスはその要素数までとなる。もし変数が length よりも短ければ、利用可能な変数の要素だけが存在することになります。もし length が負数なら、シーケンスは変数の終端からその数だけ要素を持つことになります。もし length が省略された場合は、offset から変数の終わりまでがシーケンスに含まれます。 +`length` パラメータが指定され、正の場合、シーケンスにはその数の要素が含まれます。この関数に負の `length` パラメータが渡された場合、シーケンスには元の配列のすべての要素が含まれ、`start` 位置から開始し、配列の末尾から `length` 要素少ない位置で終了します。このパラメータを指定しない場合、シーケンスには元の配列のすべての要素が含まれ、`start` 位置から開始します。 -Filter は、デフォルトで整数の配列のキーを並べ替え、リセットします。この挙動は、preserveKeys を true に設定することで変更可能です。文字列のキーは、このパラメータに関係なく、常に保存されます。 +デフォルトでは、この関数は順序を変更し、配列の整数キーをリセットします。この動作は、`preserveKeys` を `true` に設定することで変更できます。文字列キーは、このパラメータに関係なく常に保持されます。 diff --git a/latte/ja/guide.texy b/latte/ja/guide.texy index 9b24d1e7e8..1911b50319 100644 --- a/latte/ja/guide.texy +++ b/latte/ja/guide.texy @@ -1,46 +1,45 @@ -ラテをはじめよう -******** +Latteをはじめよう +***********
                                                                                  -テンプレートは、コードの構成を改善し、アプリケーションロジックをプレゼンテーションから分離し、セキュリティを強化します。HTMLを生成するための機能および表現力は、PHPそのものよりもはるかに優れています。 +テンプレートはコードの整理を改善し、アプリケーションロジックをプレゼンテーションから分離し、セキュリティを向上させます。PHP単体よりもHTMLを生成するためのより優れた機能と表現力を提供します。 -Latteは、PHPのための最も安全なテンプレートシステムです。その直感的な構文が気に入ることでしょう。幅広い便利な機能は、あなたの作業を大幅に簡素化します。 -[重要な脆弱 |safety-first]性に対して一流の保護を提供し、セキュリティを心配することなく、高品質のアプリケーションの作成に集中することを可能にします。 +LatteはPHPで最も安全なテンプレートエンジンです。その直感的な構文を気に入るはずです。幅広い便利な機能が作業を大幅に容易にします。[重大な脆弱性|safety-first]に対する最高レベルの保護を提供し、セキュリティを心配することなく高品質なアプリケーションの作成に集中できます。 -Latteを使ったテンプレートの書き方とは? .[#toc-how-to-write-templates-using-latte] ------------------------------------------------------------------ +Latteを使ってテンプレートを作成する方法は? +------------------------ -Latteは巧妙に設計されており、PHPに慣れている方でも、その基本的なタグをすぐに採用できるため、学習しやすいと思います。 +Latteは賢く設計されており、PHPを知っていて基本的なタグを習得すれば簡単に学べます。 -- まずは[Latteの構文に |syntax]慣れ、[オンラインですべて試して |https://fiddle.nette.org/latte/]みる -- [タグと |tags] [フィルターの |filters]基本セットを見てみましょう。 -- Latte対応でエディタに |recipes#Editors and IDE]テンプレートを書き込む +- まず、[Latteの構文|syntax]に慣れ、[オンラインで試す |https://fiddle.nette.org/latte/#9cc0cf6d89] +- 基本的な[タグ|tags]と[フィルタ|filters]のセットを確認してください +- [Latteをサポートするエディタ |recipes#エディタとIDE]でテンプレートを作成してください -PHPでLatteを使うには? .[#toc-how-to-use-latte-in-php] ------------------------------------------------ +PHPでLatteを使用する方法は? +------------------ -新しいアプリケーションにLatteを実装するのは、数分のことです。 +新しいアプリケーションにLatteを導入するのは数分で完了します: -- まず、[Latteをインストールし、実行 |develop#Installation]します。 -- [Tracyデバッギング |develop#Debugging and Tracy]ツールで自分を甘やかす -- [カスタム機能で |extending-latte]Latteを拡張する +- まず、[Latteをインストールして実行 |develop#インストール] +- [デバッグツールTracy |develop#デバッグと Tracy]で快適なデバッグ体験を +- [カスタム機能 |extending-latte]でLatteを拡張してください -もし、プレーンなPHPで書かれた古いプロジェクトをLatteに変換するのであれば、[PHPコードをLatteに変換する |cookbook/migration-from-php]ツールを使えば、移行は簡単になります。あるいは、TwigからLatteに乗り換えようと思っていますか?そんなあなたのために、[TwigのテンプレートをLatteに変換 |cookbook/migration-from-twig]するツールを用意しています。 +純粋なPHPで書かれた古いプロジェクトをLatteに変換する場合、[PHPコードをLatteに変換するツール |cookbook/migration-from-php]が移行を容易にします。または、TwigからLatteに移行する予定ですか?[TwigテンプレートをLatteに変換するコンバータ |cookbook/migration-from-twig]をご用意しています。 -Latteは他に何ができるのか? .[#toc-what-else-can-latte-do] ------------------------------------------------ +Latteには他にどんな機能がありますか? +--------------------- -ラテには、必要なものがすべて含まれています。 +Latteは、重要なものがすべて基本に含まれたフル装備で提供されます。 -- 繰り返される要素や構造を再利用する[継承の |template-inheritance]メカニズムによって、あなたの生産性は向上します。 -- [サンドボックスは |Sandbox]、ユーザー自身が編集したテンプレートなど、信頼できないソースからテンプレートを分離するためのアーマーバンカーです。 -- さらにインスピレーションを得るために、[ヒントとトリックを |recipes]紹介します。 +- [継承メカニズム |template-inheritance]により、繰り返し要素や構造が再利用され、生産性が向上します +- 装甲バンカー[sandbox]は、例えばユーザー自身が編集するような信頼できないソースからのテンプレートを隔離します +- さらなるインスピレーションのために、[ヒントとコツ |recipes]があります
                                                                                  -{{description:Latte は、PHP による新しいソフトウェア開発システムです。このシステムには、強力な開発者向けソフトウェアが含まれています。直感的な構文と、より高度な機能を提供します}} +{{description: LatteはPHPで最も安全なテンプレートエンジンです。多くのセキュリティ脆弱性を防ぎます。その直感的な構文と多くの便利な機能を高く評価するでしょう。}} diff --git a/latte/ja/loaders.texy b/latte/ja/loaders.texy new file mode 100644 index 0000000000..4cd9b98329 --- /dev/null +++ b/latte/ja/loaders.texy @@ -0,0 +1,198 @@ +ローダー +**** + +.[perex] +ローダーは、Latteがテンプレートのソースコードを取得するために使用するメカニズムです。最も一般的には、テンプレートはディスク上のファイルとして保存されますが、柔軟なローダーシステムのおかげで、事実上どこからでもロードしたり、動的に生成したりすることもできます。 + + +ローダーとは? +======= + +テンプレートを扱うとき、通常はプロジェクトのディレクトリ構造に配置された `.latte` ファイルを想像します。これはLatteのデフォルトの [#FileLoader] が担当します。しかし、テンプレート名(`'main.latte'` や `'components/card.latte'` など)とその実際のソースコードとの間の接続は、*必ずしも*ファイルパスへの直接的なマッピングである必要はありません。 + +ここでローダーが登場します。ローダーは、テンプレート名(識別文字列)を受け取り、Latteにそのソースコードを提供する責任を持つオブジェクトです。Latteはこのタスクのために設定されたローダーに完全に依存します。これは、`$latte->render('main.latte')` で要求された最初のテンプレートだけでなく、`{include ...}`、`{layout ...}`、`{embed ...}`、`{import ...}` のようなタグを使用して **内部で参照されるすべてのテンプレート** にも適用されます。 + +なぜカスタムローダーを使用するのですか? + +- **代替ソースからの読み込み:** データベース、キャッシュ(RedisやMemcachedなど)、バージョン管理システム(特定のコミットに基づくGitなど)に保存されているテンプレート、または動的に生成されたテンプレートを取得します。 +- **カスタム命名規則の実装:** テンプレートに短いエイリアスを使用したり、特定の検索パスロジック(例:最初にテーマディレクトリを検索し、次にデフォルトディレクトリにフォールバックする)を実装したりすることができます。 +- **セキュリティまたはアクセス制御の追加:** カスタムローダーは、特定のテンプレートをロードする前にユーザー権限を検証できます。 +- **前処理:** 一般的には推奨されませんが([コンパイルパス |compiler-passes] の方が優れています)、ローダーは理論的にはテンプレートコンテンツをLatteに渡す前に前処理する *可能性* があります。 + +`Latte\Engine` インスタンスのローダーは `setLoader()` メソッドを使用して設定します: + +```php +$latte = new Latte\Engine; + +// '/path/to/templates' 内のファイルにデフォルトの FileLoader を使用 +$loader = new Latte\Loaders\FileLoader('/path/to/templates'); +$latte->setLoader($loader); +``` + +ローダーは `Latte\Loader` インターフェースを実装する必要があります。 + + +組み込みローダー +======== + +Latteはいくつかの標準ローダーを提供します: + + +FileLoader +---------- + +これは、他に指定されていない場合に `Latte\Engine` クラスで使用される **デフォルトのローダー** です。ファイルシステムから直接テンプレートをロードします。 + +オプションで、アクセスを制限するためにルートディレクトリを設定できます: + +```php +use Latte\Loaders\FileLoader; + +// 以下は /var/www/html/templates ディレクトリからのみテンプレートのロードを許可します +$loader = new FileLoader('/var/www/html/templates'); +$latte->setLoader($loader); + +// $latte->render('../../../etc/passwd'); // これは例外をスローします + +// /var/www/html/templates/pages/contact.latte にあるテンプレートをレンダリング +$latte->render('pages/contact.latte'); +``` + +`{include}` や `{layout}` のようなタグを使用する場合、絶対パスが指定されていない限り、現在のテンプレートからの相対パスでテンプレート名を解決します。 + + +StringLoader +------------ + +このローダーは、キーがテンプレート名(識別子)、値がテンプレートソースコード文字列である連想配列からテンプレートコンテンツを取得します。テストや、テンプレートがPHPコード内に直接保存される可能性のある小規模なアプリケーションに特に役立ちます。 + +```php +use Latte\Loaders\StringLoader; + +$loader = new StringLoader([ + 'main.latte' => 'Hello {$name}, include is below:{include helper.latte}', + 'helper.latte' => '{var $x = 10}Included content: {$x}', + // 必要に応じて他のテンプレートを追加 +]); + +$latte->setLoader($loader); + +$latte->render('main.latte', ['name' => 'World']); +// 出力: Hello World, include is below:Included content: 10 +``` + +他の名前付き文字列テンプレートを参照するインクルードや継承を必要とせずに、文字列から直接1つのテンプレートのみをレンダリングする必要がある場合は、配列なしで `StringLoader` を使用するときに `render()` または `renderToString()` メソッドに文字列を直接渡すことができます: + +```php +$loader = new StringLoader; +$latte->setLoader($loader); + +$templateString = 'Hello {$name}!'; +$output = $latte->renderToString($templateString, ['name' => 'Alice']); +// $output には 'Hello Alice!' が含まれます +``` + + +カスタムローダーの作成 +=========== + +カスタムローダー(例:データベース、キャッシュ、バージョン管理システム、または他のソースからテンプレートをロードするため)を作成するには、[api:Latte\Loader] インターフェースを実装するクラスを作成する必要があります。 + +各メソッドが何をする必要があるかを見てみましょう。 + + +getContent(string $name): string .[method] +------------------------------------------ +これはローダーのコアメソッドです。その仕事は、`$name`(`$latte->render()` メソッドに渡されるか、[#getReferredName()] メソッドによって返される)によって識別されるテンプレートの完全なソースコードを取得して返すことです。 + +テンプレートが見つからないかアクセスできない場合、このメソッドは **`Latte\RuntimeException` 例外をスローする必要があります**。 + +```php +public function getContent(string $name): string +{ + // 例:仮説的な内部ストレージからの読み込み + $content = $this->storage->read($name); + if ($content === null) { + throw new Latte\RuntimeException("Template '$name' cannot be loaded."); + } + return $content; +} +``` + + +getReferredName(string $name, string $referringName): string .[method] +---------------------------------------------------------------------- +このメソッドは、`{include}`、`{layout}` などのタグ内で使用されるテンプレート名の解決を処理します。Latteが `main.latte` 内で `{include 'partial.latte'}` などに遭遇すると、`$name = 'partial.latte'` および `$referringName = 'main.latte'` でこのメソッドを呼び出します。 + +メソッドの仕事は、`$referringName` で提供されるコンテキストに基づいて、`$name` を他のローダーメソッドを呼び出すときに使用される正規の識別子(例:絶対パス、一意のデータベースキー)に解決することです。 + +```php +public function getReferredName(string $name, string $referringName): string +{ + return ...; +} +``` + + +getUniqueId(string $name): string .[method] +------------------------------------------- +Latteはパフォーマンス向上のためにコンパイル済みテンプレートキャッシュを使用します。各コンパイル済みテンプレートファイルには、ソーステンプレート識別子から派生した一意の名前が必要です。このメソッドは、テンプレート `$name` を **一意に識別する** 文字列を提供します。 + +ファイルベースのテンプレートの場合、絶対パスが役立ちます。データベース内のテンプレートの場合、プレフィックスとデータベースIDの組み合わせが一般的です。 + +```php +public function getUniqueId(string $name): string +{ + return ...; +} +``` + + +例:シンプルなデータベースローダー +----------------- + +この例は、`templates` という名前のデータベーステーブル(列 `name`(一意の識別子)、`content`、`updated_at` を持つ)に保存されているテンプレートをロードするローダーの基本的な構造を示しています。 + +```php +use Latte; + +class DatabaseLoader implements Latte\Loader +{ + public function __construct( + private \PDO $db, + ) { + } + + public function getContent(string $name): string + { + $stmt = $this->db->prepare('SELECT content FROM templates WHERE name = ?'); + $stmt->execute([$name]); + $content = $stmt->fetchColumn(); + if ($content === false) { + throw new Latte\RuntimeException("Template '$name' not found in database."); + } + return $content; + } + + // この単純な例では、テンプレート名('homepage'、'article'など)が + // 一意のIDであり、テンプレートが互いに相対的に参照しないと仮定します。 + public function getReferredName(string $name, string $referringName): string + { + return $name; + } + + public function getUniqueId(string $name): string + { + // ここではプレフィックスと名前自体を使用するのが一意で十分です + return 'db_' . $name; + } +} + +// 使用法: +$pdo = new \PDO(/* 接続詳細 */); +$loader = new DatabaseLoader($pdo); +$latte->setLoader($loader); +$latte->render('homepage'); // DBから 'homepage' という名前のテンプレートをロードします +``` + +カスタムローダーを使用すると、Latteテンプレートの取得元を完全に制御でき、さまざまなストレージシステムやワークフローとの統合が可能になります。 diff --git a/latte/ja/recipes.texy b/latte/ja/recipes.texy index 56ba9e0068..94eb860777 100644 --- a/latte/ja/recipes.texy +++ b/latte/ja/recipes.texy @@ -2,44 +2,44 @@ ****** -エディタとIDE .[#toc-editors-and-ide] -================================ +エディタとIDE +======== -Latteに対応したエディタやIDEでテンプレートを書きましょう。より快適になるはずです。 +LatteをサポートするエディタまたはIDEでテンプレートを作成してください。はるかに快適になります。 -- NetBeans IDEはサポートが組み込まれています -- PhpStorm:[Latteプラグ |https://plugins.jetbrains.com/plugin/7457-latte]インをインストールする`Settings > Plugins > Marketplace` -- VS Code:マーカープレイスで「Nette Latte + Neon」プラグインを探す -- Sublime Text 3: パッケージコントロールで`Nette` パッケージを検索してインストールし、Latteを選択します。`View > Syntax` -- 古いエディタでは、.latte ファイルに Smarty ハイライトを使用します。 +- PhpStorm: `Settings > Plugins > Marketplace` で [Latteプラグイン|https://plugins.jetbrains.com/plugin/7457-latte] をインストールします +- VS Code: [Nette Latte + Neon|https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]、[Nette Latte templates|https://marketplace.visualstudio.com/items?itemName=smuuf.latte-lang]、または最新の [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode] プラグインをインストールします +- NetBeans IDE: Latteのネイティブサポートはインストールに含まれています +- Sublime Text 3: Package Controlで `Nette` パッケージを見つけてインストールし、`View > Syntax` でLatteを選択します +- 古いエディタでは、`.latte` ファイルにSmartyハイライトを使用してください -PhpStormのプラグインは非常に高度で、PHPコードを完全に示唆することができます。最適に動作させるためには、[型付きテンプレートを |type-system]使用します。 +PhpStorm用のプラグインは非常に高度で、PHPコードの補完に優れています。最適に機能させるには、[型付きテンプレート|type-system]を使用してください。 [* latte-phpstorm-plugin.webp *] -Latteのサポートは、ウェブコードハイライター[Prism.jsと |https://prismjs.com/#supported-languages]エディタ[Aceでも |https://ace.c9.io]見ることができます。 +Latteのサポートは、Webコードハイライター [Prism.js|https://prismjs.com/#supported-languages] およびエディタ [Ace|https://ace.c9.io] でも利用できます。 -Latte インサイドの JavaScript または CSS .[#toc-latte-inside-javascript-or-css] -====================================================================== +JavaScriptまたはCSS内のLatte +======================= -LatteはJavaScriptやCSSの内部でとても快適に使うことができます。しかし、LatteがJavaScriptのコードやCSSのスタイルをLatteのタグと誤認しないようにするにはどうしたらよいのでしょうか。 +LatteはJavaScriptやCSS内でも非常に快適に使用できます。しかし、Latteが誤ってJavaScriptコードやCSSスタイルをLatteタグと見なす状況をどのように回避できるでしょうか? ```latte ``` -**選択肢1** +**方法 1** -文字と文字の間にスペース、改行、引用符を挿入することで、文字が `{` の直後に続くような状況を避けることができます。 +`{` の直後に文字が続く状況を避けてください。例えば、その前にスペース、改行、または引用符を挿入します: ```latte -- in comment: +```twig .{file:Twigテンプレート、デザイナーが見る方法} +- テキスト内: {{ foo }} +- タグ内: +- 属性内: +- 引用符なしの属性内: +- URLを含む属性内: +- JavaScriptを含む属性内: +- CSSを含む属性内: +- JavaScript内: +- CSS内: +- コメント内: ```
                                                                                  -素朴なシステムでは、`< > & ' "` の文字を機械的に HTML の実体に変換するだけです。これはほとんどの用途で有効なエスケープの方法ですが、常にそうであるとは限りません。そのため、以下に示すように、様々なセキュリティホールを検出したり、防止したりすることができません。 +ナイーブなシステムは、文字 `< > & ' "` を機械的にHTMLエンティティに変換するだけです。これは、ほとんどの使用例で有効なエスケープ方法ですが、常にそうであるとは限りません。したがって、後で示すように、さまざまなセキュリティホールの発生を検出したり防いだりすることはできません。 -ラテはあなたが見ているのと同じようにテンプレートを見ています。ラテはHTMLやXMLを理解し、タグや属性などを認識します。そしてそのためにコンテクストを区別し、それに応じてデータを扱います。そのため、重要なクロスサイトスクリプティングの脆弱性に対して、実に効果的な防御を提供しています。 +Latteはテンプレートをあなたと同じように見ます。HTML、XMLを理解し、タグ、属性などを認識します。そして、そのおかげで個々のコンテキストを区別し、それに応じてデータをサニタイズします。したがって、重大な脆弱性であるクロスサイトスクリプティングに対する本当に効果的な保護を提供します。 + +
                                                                                  + +```latte .{file:Latteテンプレート、Latteが見る方法} +░░░░░░░░░░░{$foo} +░░░░░░░░░░ +░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░ +░░░░░░░░░ +░░░░░░░░░░░░░░░ +``` + +```latte .{file:Latteテンプレート、デザイナーが見る方法} +- テキスト内: {$foo} +- タグ内: +- 属性内: +- 引用符なしの属性内: +- URLを含む属性内: +- JavaScriptを含む属性内: +- CSSを含む属性内: +- JavaScript内: +- CSS内: +- コメント内: +``` + +
                                                                                  -ライブデモ .[#toc-live-demonstration] -================================ +ライブデモ +===== -左側がLatteのテンプレートで、右側が生成されたHTMLコードです。`$text` という変数が何度も出力され、その都度、微妙に異なる文脈で出力されています。そのため、エスケープも少し違っています。テンプレートのコードは自分で編集することができます。例えば、変数の内容を変更するなど。試してみてください。 +左側にはLatteのテンプレート、右側には生成されたHTMLコードが表示されます。変数 `$text` が数回出力され、毎回少し異なるコンテキストで出力されます。したがって、エスケープも少し異なります。テンプレートコードは自分で編集できます。例えば、変数の内容を変更するなどです。試してみてください:
                                                                                  ``` .{file:template.latte; min-height: 14em}[fiddle-source] -{* TRY TO EDIT THIS TEMPLATE *} +{* このテンプレートを編集してみてください *} {var $text = "Rock'n'Roll"} - {$text} - @@ -270,63 +286,61 @@ TwigやLaravel Bladeなどのテンプレートシステムは、テンプレー
                                                                                  -すごいでしょう!?ラテは文脈に応じたエスケープを自動で行ってくれるので、プログラマーは +素晴らしいでしょう!Latteはコンテキストに応じたエスケープを自動的に行うので、プログラマーは: -- データをどのようにエスケープするか考える必要も知る必要もない -- 間違うことがない -- 忘れることがない +- どこでどのようにエスケープするかを考えたり知ったりする必要がない +- 間違えることがない +- エスケープを忘れることがない -ラテが出力時に区別し、データの扱いをカスタマイズする文脈はこれだけではありません。これからもっと面白いケースを見ていくことにしましょう。 +これらは、Latteが出力時に区別し、データのサニタイズを調整するすべてのコンテキストではありません。さらに興味深いケースを今から見ていきましょう。 -ナイーブシステムをハックする方法 .[#toc-how-to-hack-naive-systems] -================================================== +ナイーブなシステムをハックする方法 +================= -コンテキストを区別することがいかに重要であるか、そしてなぜ素朴なテンプレートシステムは Latte とは異なり XSS に対して十分な防御を提供できないかを示すために、いくつかの実用的な例を使用します。 -例では、素朴なシステムの代表としてTwigを使用しますが、他のシステムにも同じことが当てはまります。 +いくつかの実践的な例を通して、コンテキストの区別がいかに重要であり、ナイーブなテンプレートエンジンがLatteとは異なり、XSSに対する十分な保護を提供しない理由を示します。 ナイーブなシステムの代表として、例ではTwigを使用しますが、他のシステムにも同じことが当てはまります。 -属性の脆弱性 .[#toc-attribute-vulnerability] --------------------------------------- +属性による脆弱性 +-------- -[上で示した |#How does the vulnerability arise]ように、HTML 属性を使用してページに悪意のあるコードを注入してみましょう。Twig のテンプレートに画像を表示させることにしましょう。 +[上記で示したように |#脆弱性はどのように発生しますか]、HTML属性を使用して悪意のあるコードをページに注入しようとします。画像をレンダリングするTwigのテンプレートがあるとします: ```twig .{file:Twig} {{ ``` -属性値の周りに引用符がないことに注意してください。コーダーが引用符を忘れてしまったのでしょう。例えば、Reactでは、コードは引用符なしでこのように書かれており、言語を切り替えているコーダーは引用符のことを簡単に忘れてしまいます。 +属性値の周りに引用符がないことに注意してください。コーダーがそれらを忘れた可能性があります。これは単に起こることです。例えば、Reactではコードはこのように引用符なしで書かれ、言語を切り替えるコーダーは簡単に引用符を忘れる可能性があります。 -攻撃者は、巧みに構成された文字列`foo onload=alert('Hacked!')` を画像のキャプションとして挿入します。Twigは変数がHTMLテキストのストリームに表示されているか、属性の中に表示されているか、HTMLコメントの中に表示されているか、などを区別できないことはすでに知っています。そして、`< > & ' "` の文字を機械的にHTMLの実体に変換しているだけです。 -その結果、次のようなコードになる。 +攻撃者は、画像の説明として巧妙に作成された文字列 `foo onload=alert('Hacked!')` を挿入します。Twigは、変数がHTMLテキストの流れの中、属性内、HTMLコメント内など、どこに出力されているかを判断できないこと、つまりコンテキストを区別しないことをすでに知っています。そして、文字 `< > & ' "` を機械的にHTMLエンティティに変換するだけです。 したがって、結果のコードは次のようになります: ```html foo ``` -**セキュリティホールが発生しました!** +**そして、セキュリティホールが発生しました!** -偽の`onload` 属性がページの一部となり、ブラウザは画像をダウンロードした後すぐにそれを実行します。 +偽装された `onload` 属性がページの一部になり、ブラウザは画像をダウンロードするとすぐにそれを実行します。 -では、同じテンプレートをラテがどう扱うか見てみましょう。 +次に、Latteが同じテンプレートをどのように処理するかを見てみましょう: ```latte .{file:Latte} {$imageAlt} ``` -Latteは、あなたと同じようにテンプレートを見ます。Twigとは異なり、LatteはHTMLを理解し、変数が引用符で囲まれていない属性値として出力されることを理解しています。そのため、引用符を付加しています。攻撃者が同じキャプションを挿入すると、結果としてコードは次のようになります。 +Latteはテンプレートをあなたと同じように見ます。Twigとは異なり、HTMLを理解し、変数が引用符で囲まれていない属性の値として出力されていることを知っています。したがって、それらを補完します。攻撃者が同じ説明を挿入すると、結果のコードは次のようになります: ```html foo onload=alert('Hacked!') ``` -LatteはXSSの防止に成功しました。 +**LatteはXSSを正常に防止しました。** -JavaScriptで変数を表示する .[#toc-printing-a-variable-in-javascript] ------------------------------------------------------------- +JavaScriptでの変数の出力 +----------------- -文脈依存のエスケープのおかげで、PHP の変数を JavaScript 内部でネイティブに使用することができます。 +コンテキストに応じたエスケープのおかげで、JavaScript内でPHP変数を完全にネイティブに使用することが可能です。 ```latte

                                                                                  {$movie}

                                                                                  @@ -334,7 +348,7 @@ JavaScriptで変数を表示する .[#toc-printing-a-variable-in-javascript] ``` -`$movie` 変数に`'Amarcord & 8 1/2'` の文字列を格納すると、以下のような出力が生成されます。HTMLとJavaScriptで使われているエスケープが違うことと、`onclick` 属性で使われていることに注意してください。 +変数 `$movie` に文字列 `'Amarcord & 8 1/2'` が含まれている場合、次の出力が生成されます。HTML内ではJavaScript内とは異なるエスケープが使用され、`onclick` 属性内ではさらに異なるエスケープが使用されることに注意してください: ```latte

                                                                                  Amarcord & 8 1/2

                                                                                  @@ -343,29 +357,27 @@ JavaScriptで変数を表示する .[#toc-printing-a-variable-in-javascript] ``` -リンクチェック .[#toc-link-checking] ------------------------------ +リンクのチェック +-------- -Latte は`src` または`href` 属性で使用されている変数に Web URL (つまり HTTP プロトコル) が含まれているかどうかを自動的にチェックし、セキュリティ上のリスクがあるリンクの書き込みを防止しています。 +Latteは、`src` または `href` 属性で使用される変数がWeb URL(つまりHTTPプロトコル)を含んでいるかどうかを自動的にチェックし、セキュリティリスクをもたらす可能性のあるリンクの出力を防ぎます。 ```latte {var $link = 'javascript:attack()'} -click here +クリック ``` -書き込みを行います。 +出力: ```latte -click here +クリック ``` -フィルタ[nocheck |filters#nocheck] を使ってチェックをオフにすることができます。 +チェックは [nocheck |filters#nocheck] フィルタを使用して無効にできます。 -ラテの限界 .[#toc-limits-of-latte] -============================= +Latteの制限 +======== -Latteはアプリケーション全体に対する完全なXSS対策ではありません。Latteを使う際にセキュリティについて考えるのをやめてしまうと不幸なことになります。 -Latteの目標は、攻撃者がページの構造を変えたり、HTMLの要素や属性を改竄したりできないようにすることです。しかし、出力されるデータの内容の正しさをチェックするものではありません。また、JavaScriptの動作が正しいかどうかもチェックしません。 -それはテンプレートシステムの範疇を超えています。特にユーザーが入力したデータの正しさを検証することは、信頼できないプログラマーの重要な仕事です。 +Latteは、アプリケーション全体に対するXSSからの完全な保護ではありません。Latteを使用する際にセキュリティについて考えるのをやめてほしくありません。 Latteの目標は、攻撃者がページの構造を変更したり、HTML要素や属性を偽装したりできないようにすることです。しかし、出力されるデータのコンテンツの正確性や、JavaScriptの動作の正確性はチェックしません。 これはテンプレートエンジンの能力を超えています。データの正確性、特にユーザーによって挿入された信頼できないデータの検証は、プログラマーの重要なタスクです。 diff --git a/latte/ja/sandbox.texy b/latte/ja/sandbox.texy index 20d83693b1..a1fdf14c23 100644 --- a/latte/ja/sandbox.texy +++ b/latte/ja/sandbox.texy @@ -1,12 +1,10 @@ -サンドボックス +Sandbox ******* -.[perex]{data-version:2.8} -サンドボックスは、テンプレートで使用できるタグ、PHP関数、メソッドなどを制御できるセキュリティレイヤーを提供します。サンドボックスモードのおかげで、アプリケーションの破損や不要な操作を心配することなく、クライアントや外部のコーダーとテンプレート作成で安全に共同作業することができます。 +.[perex] +Sandboxは、テンプレートで使用できるタグ、PHP関数、メソッドなどを制御できるセキュリティ層を提供します。Sandboxモードのおかげで、アプリケーションの侵害や望ましくない操作を心配することなく、クライアントや外部コーダーと安全にテンプレート作成で協力できます。 -どのように機能するのですか?テンプレートで許可したいものを定義するだけです。最初のうちは、すべてが禁止されており、徐々に許可を与えていきます: - -次のコードは、テンプレートが`{block}`,`{if}`,`{else}`,`{=}` タグ(後者は[変数や式を表示 |tags#Printing]するタグ)とすべてのフィルタを使用することを許可しています。 +どのように機能しますか?単に、テンプレートに許可するものをすべて定義します。デフォルトではすべてが禁止されており、徐々に許可していきます。次のコードでは、テンプレートの作成者がタグ `{block}`、`{if}`、`{else}`、および `{=}`([変数または式を出力 |tags#出力]するためのタグ)とすべてのフィルタを使用できるようにします: ```php $policy = new Latte\Sandbox\SecurityPolicy; @@ -16,7 +14,7 @@ $policy->allowFilters($policy::All); $latte->setPolicy($policy); ``` -また、グローバルな関数、メソッド、オブジェクトのプロパティへのアクセスも許可することができます。 +さらに、個々の関数、メソッド、またはオブジェクトのプロパティを許可できます: ```php $policy->allowFunctions(['trim', 'strlen']); @@ -24,30 +22,35 @@ $policy->allowMethods(Nette\Security\User::class, ['isLoggedIn', 'isAllowed']); $policy->allowProperties(Nette\Database\Row::class, $policy::All); ``` -これってすごいことだと思いませんか?非常に低いレベルですべてをコントロールすることができるのです。もしテンプレートが未承認の関数を呼び出したり、未承認のメソッドやプロパティにアクセスしようとすると、例外がスローされます`Latte\SecurityViolationException`. +素晴らしいでしょう?非常に低いレベルですべてを制御できます。テンプレートが許可されていない関数を呼び出そうとしたり、許可されていないメソッドやプロパティにアクセスしようとしたりすると、`Latte\SecurityViolationException` 例外が発生します。 -すべてが禁止された状態でゼロからポリシーを作成するのは不便なので、安全な基礎から始めることができます。 +すべてが禁止されているゼロからポリシーを作成するのは便利ではないかもしれないので、安全なベースから始めることができます: ```php $policy = Latte\Sandbox\SecurityPolicy::createSafePolicy(); ``` -これは、`contentType`,`debugbreak`,`dump`,`extends`,`import`,`include`,`layout`,`php`,`sandbox`,`snippet`,`snippetArea`,`templatePrint`,`varPrint`,`widget` を除いて、すべての標準タグが許可されていることを意味します。 -`datastream`,`noescape`,`nocheck` を除くすべての標準的なフィルタも許可されています。最後に、オブジェクト`$iterator` のメソッドとプロパティへのアクセスも許可されています。 +安全なベースとは、`contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget` を除くすべての標準タグが許可されていることを意味します。`datastream`, `noescape`, `nocheck` を除く標準フィルタが許可されています。そして最後に、`$iterator` オブジェクトのメソッドとプロパティへのアクセスが許可されています。 -このルールは、新しい [`{sandbox}` |tags#Including Templates]タグで挿入されるテンプレートにも適用されます。これは`{include}` のようなものですが、サンドボックスモードをオンにし、外部変数を一切渡さないようにしています。 +ポリシーは、[`{sandbox}` |tags#テンプレートの挿入] タグで挿入するテンプレートに自動的に適用されるわけではありません。`{sandbox}` タグは、そのインクルードされたテンプレートに対して独立したサンドボックスインスタンス(デフォルトでは安全なポリシーを使用)を作成します。 ```latte {sandbox 'untrusted.latte'} ``` -したがって、レイアウトと個々のページは、以前のようにすべてのタグと変数を使用することができ、制限はテンプレートにのみ適用されます`untrusted.latte`. +したがって、レイアウトと個々のページはすべてのタグと変数を自由に利用できますが、`untrusted.latte`テンプレートにのみ制限が適用されます。 -禁止されているタグやフィルタの使用など、一部の違反はコンパイル時に検出されます。また、オブジェクトの許可されていないメソッドを呼び出すなどの違反は、実行時に検出されます。 -テンプレートは、その他のバグを含むこともできます。サンドボックス化されたテンプレートから例外が発生し、レンダリング全体が中断するのを防ぐために、[独自の例外ハンドラを |develop#exception handler]定義し、例えば、単にログを記録することができます。 +禁止されたタグやフィルタの使用など、一部の違反はコンパイル時に検出されます。オブジェクトの許可されていないメソッドの呼び出しなど、他の違反は実行時に検出されます。 テンプレートには他のエラーも含まれる可能性があります。サンドボックス化されたテンプレートから例外が発生してレンダリング全体が中断されないように、カスタム[例外ハンドラ |develop#例外ハンドラ]を定義して、例えばそれをログに記録することができます。 -すべてのテンプレートに対して直接サンドボックスモードをオンにしたい場合は、簡単です。 +すべてのテンプレートに対してグローバルにサンドボックスモードを有効にしたい場合は、`setSandboxMode(true)` を呼び出し、`setPolicy()` でポリシーを設定します: ```php $latte->setSandboxMode(); ``` + +ユーザーがページに、構文的には正しいが禁止されておりPHPコンパイルエラーを引き起こすPHPコードを挿入しないことを確認するために、[PHPリンターでテンプレートをチェック |develop#生成されたコードのチェック]することをお勧めします。この機能は `Engine::enablePhpLint()` メソッドで有効にします。チェックにはPHPバイナリを呼び出す必要があるため、そのパスをパラメータとして渡します: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); +``` diff --git a/latte/ja/syntax.texy b/latte/ja/syntax.texy index 46a1ae751b..40916e5b1f 100644 --- a/latte/ja/syntax.texy +++ b/latte/ja/syntax.texy @@ -1,55 +1,53 @@ 構文 -*** +******* .[perex] -Syntax Latteは、ウェブデザイナーの実用的な要求から生まれました。私たちは、最も使いやすい構文を探していました。この構文を使えば、他の方法では本当に難しい構文をエレガントに書くことができます。 -同時に、すべての表現はPHPと全く同じに記述されるので、新しい言語を学ぶ必要はありません。すでに知っていることを最大限に活用すればいいのです。 +Latteの構文は、Webデザイナーの実用的な要件から生まれました。そうでなければ本当に難しい構造でもエレガントに記述できる、最もユーザーフレンドリーな構文を探しました。 同時に、すべての式はPHPとまったく同じように記述されるため、新しい言語を学ぶ必要はありません。すでに知っていることを活用するだけです。 -以下に、基本的な要素であるタグ、n:属性、コメント、フィルタを説明する最小限のテンプレートを示します。 +以下は、いくつかの基本的な要素を示す最小限のテンプレートです:タグ、n:属性、コメント、フィルタ。 ```latte -{* this is a comment *} -
                                                                                    {* n:if is n:atribut *} -{foreach $items as $item} {* tag representing foreach loop *} -
                                                                                  • {$item|capitalize}
                                                                                  • {* tag that prints a variable with a filter *} -{/foreach} {* end of cycle *} +{* これはコメントです *} +
                                                                                      {* n:ifはn:属性です *} +{foreach $items as $item} {* foreachループを表すタグ *} +
                                                                                    • {$item|capitalize}
                                                                                    • {* フィルタ付きで変数を出力するタグ *} +{/foreach} {* ループの終わり *}
                                                                                    ``` -これらの重要な要素を詳しく見て、それらがどのように素晴らしいテンプレートを構築するのに役立つかを見てみましょう。 +これらの重要な要素と、それらが素晴らしいテンプレートを作成するのにどのように役立つかを詳しく見てみましょう。 -タグ .[#toc-tags] -=============== +タグ +==== -テンプレートには、テンプレートのロジック(例えば、*foreach*ループ)や出力式を制御するタグが含まれています。どちらも一つの区切り文字`{ ... }` が使われますので、他のシステムのように、どの場面でどの区切り文字を使うか考える必要はありません。 -また、`{`文字の後に引用符やスペースが続く場合、Latteはそれをタグの先頭とは見なさないので、テンプレート内でJavaScriptの構成要素やJSON、CSSルールを問題なく使用することができます。 +テンプレートには、テンプレートのロジックを制御するタグ(例えば、*foreach* ループ)や式を出力するタグが含まれます。両方に単一のデリミタ `{ ... }` が使用されるため、他のシステムのように、どの状況でどのデリミタを使用するかを考える必要はありません。`{` 文字の後に引用符またはスペースが続く場合、Latteはそれをタグの開始とは見なしません。これにより、テンプレート内でJavaScript構造、JSON、またはCSSルールを問題なく使用できます。 -[全タグの概要を |tags]見るさらに、[カスタムタグを |extending-latte#tags]作成することも可能です。 +[すべてのタグの概要|tags]をご覧ください。さらに、[カスタムタグ|custom-tags]を作成することもできます。 -ラテはPHPを理解する .[#toc-latte-understands-php] -========================================= +LatteはPHPを理解します +=============== -タグの中によく知っているPHPの表現を使うことができます。 +タグ内では、よく知っているPHP式を使用できます: - 変数 -- 文字列 (HEREDOC および NOWDOC を含む), 配列, 数値, など。 +- 文字列(HEREDOCおよびNOWDOCを含む)、配列、数値など - [演算子 |https://www.php.net/manual/en/language.operators.php] -- 関数やメソッドの呼び出し ([サンドボックスで |sandbox]制限されることがあります) -- [マッチ |https://www.php.net/manual/en/control-structures.match.php] -- [無名関数 |https://www.php.net/manual/en/functions.arrow.php] -- [コールバック |https://www.php.net/manual/en/functions.first_class_callable_syntax.php] -- 複数行コメント`/* ... */` -- など +- 関数とメソッドの呼び出し([sandbox|sandbox]で制限可能) +- [match式 |https://www.php.net/manual/en/control-structures.match.php] +- [アロー関数 |https://www.php.net/manual/en/functions.arrow.php] +- [ファーストクラスcallable構文 |https://www.php.net/manual/en/functions.first_class_callable_syntax.php] +- 複数行コメント `/* ... */` +- など… -さらに、LatteはPHPの文法にいくつかの[素晴らしい拡張を加えて |#Syntactic Sugar]います。 +さらに、LatteはPHP構文にいくつかの [便利な拡張機能 |#シンタックスシュガー] を追加します。 -n:属性 .[#toc-n-attributes] -========================= +n:属性 +==== -一つのHTML要素に作用する`{if} … {/if}` のような各ペアタグは、[n:attribute |#n:attribute]記法で書くことができます。例えば、上の例の`{foreach}` は、次のように書くこともできる。 +`{if} … {/if}` などのすべてのペアタグは、単一のHTML要素上で動作する場合、n:属性の形式で書き換えることができます。例えば、冒頭の例の `{foreach}` もこのように記述できます: ```latte
                                                                                      @@ -57,7 +55,7 @@ n:属性 .[#toc-n-attributes]
                                                                                    ``` -この場合、機能は、それが記述されているHTML要素に対応する。 +機能は、それが配置されているHTML要素に適用されます: ```latte {var $items = ['I', '♥', 'Latte']} @@ -65,7 +63,7 @@ n:属性 .[#toc-n-attributes]

                                                                                    {$item}

                                                                                    ``` -プリント +出力: ```latte

                                                                                    I

                                                                                    @@ -73,7 +71,7 @@ n:属性 .[#toc-n-attributes]

                                                                                    Latte

                                                                                    ``` -`inner-` プレフィックスを使用することで、その機能が要素の本体のみに適用されるように動作を変更することができます。 +プレフィックス `inner-` を使用すると、動作を要素の内部部分のみに適用するように変更できます: ```latte
                                                                                    @@ -82,7 +80,7 @@ n:属性 .[#toc-n-attributes]
                                                                                    ``` -印刷します。 +出力: ```latte
                                                                                    @@ -95,178 +93,184 @@ n:属性 .[#toc-n-attributes]
                                                                                    ``` -また、`tag-` という接頭辞をつけると、HTML タグにのみ機能が適用されます。 +または、プレフィックス `tag-` を使用すると、機能はHTMLタグ自体にのみ適用されます: ```latte

                                                                                    Title

                                                                                    ``` -変数`$url` の値によって、このように表示されます。 +これは、変数 `$url` に応じて出力されます: ```latte -// when $url is empty +{* $url が空の場合 *}

                                                                                    Title

                                                                                    -// when $url equals 'https://nette.org' +{* $url が 'https://nette.org' を含む場合 *}

                                                                                    Title

                                                                                    ``` -しかし、n:attributesはペアタグのショートカットであるだけでなく、純粋なn:attributesもあります[。 |tags#n:class] +しかし、n:属性はペアタグの単なる短縮形ではありません。純粋なn:属性も存在します。例えば、[n:href |application:creating-links#Presenterテンプレート内] や、コーダーにとって非常に便利なヘルパー [n:class |tags#n:class] などです。 -フィルター .[#toc-filters] -===================== +フィルタ +==== -[標準フィル |filters]タの概要をご覧ください。 +[標準フィルタ |filters] の概要をご覧ください。 -Latteでは、パイプ記号を使った記法でフィルタを呼び出すことができます(前方にスペースがあっても可)。 +フィルタは縦棒の後ろに記述します(前にスペースがあってもかまいません): ```latte

                                                                                    {$heading|upper}

                                                                                    ``` -フィルターは連結することができ、その場合、左から右の順に適用されます。 +フィルタは連結でき、左から右の順に適用されます: ```latte

                                                                                    {$heading|lower|capitalize}

                                                                                    ``` -パラメータはコロンまたはカンマで区切られたフィルタ名の後に置かれます。 +パラメータはフィルタ名の後にコロンまたはカンマで区切って指定します: ```latte

                                                                                    {$heading|truncate:20,''}

                                                                                    ``` -フィルタは式に対して適用することができる。 +フィルタは式にも適用できます: ```latte {var $name = ($title|upper) . ($subtitle|lower)} ``` -ブロックの場合。 +ブロックに: ```latte

                                                                                    {block |lower}{$heading}{/block}

                                                                                    ``` -または直接値(と組み合わせ [`{=expr}` | https://latte.nette.org/ja/tags#printing]タグ)。 +または直接値に([`{=}` |tags#出力] タグと組み合わせて): ```latte -

                                                                                    {=' Hello world '|trim}

                                                                                    +

                                                                                    {=' Hello world '|trim}

                                                                                    ``` -コメント .[#toc-comments] -===================== +動的HTMLタグ .{data-version:3.0.9} +============================== -コメントはこのように書き、出力には入りません。 +Latteは動的HTMLタグをサポートしており、タグ名に柔軟性が必要な場合に便利です: ```latte -{* this is a comment in Latte *} +Heading ``` -PHPのコメントはタグの中で動作します。 +上記のコードは、例えば変数 `$level` の値に応じて `

                                                                                    Heading

                                                                                    ` または `

                                                                                    Heading

                                                                                    ` を生成できます。Latteの動的HTMLタグは常にペアである必要があります。その代替案は [n:tag |tags#n:tag] です。 + +Latteは安全なテンプレートエンジンであるため、結果のタグ名が有効であり、望ましくないまたは有害な値が含まれていないことをチェックします。さらに、終了タグの名前が常に対応する開始タグの名前と同じであることを保証します。 + + +コメント +==== + +コメントはこのように記述され、出力には含まれません: + +```latte +{* これはLatteのコメントです *} +``` + +タグ内ではPHPコメントが機能します: ```latte {include 'file.info', /* value: 123 */} ``` -構文解析シュガー .[#toc-syntactic-sugar] -================================ +シンタックスシュガー +========== -クォーテーションマークなしの文字列 .[#toc-strings-without-quotation-marks] ---------------------------------------------------------- +引用符なしの文字列 +--------- -単純な文字列では、引用符を省略することができます。 +単純な文字列では引用符を省略できます: ```latte -as in PHP: {var $arr = ['hello', 'btn--default', '€']} +PHPと同様: {var $arr = ['hello', 'btn--default', '€']} -abbreviated: {var $arr = [hello, btn--default, €]} +短縮形: {var $arr = [hello, btn--default, €]} ``` -単純な文字列とは、文字、数字、アンダースコア、ハイフン、ピリオドだけで構成されたものを指します。数字で始まってはならず、ハイフンで始まってはならず、ハイフンで終わってはならない。 -また、大文字とアンダースコアだけで構成されてはいけません。そうすると定数とみなされるからです (例:`PHP_VERSION`)。 -また、キーワード`and`,`array`,`clone`,`default`,`false`,`in`,`instanceof`,`new`,`null`,`or`,`return`,`true`,`xor` と衝突してはいけません。 +単純な文字列とは、文字、数字、アンダースコア、ハイフン、ドットのみで構成されるものです。数字で始まってはならず、ハイフンで始まったり終わったりしてはなりません。 すべて大文字とアンダースコアだけで構成されていてはなりません。その場合、定数と見なされます(例:`PHP_VERSION`)。 そして、キーワードと衝突してはなりません:`and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor`。 -短い三項演算子 .[#toc-short-ternary-operator] --------------------------------------- +定数 +--------- -三項演算子の3番目の値が空であれば、省略可能です。 +単純な文字列では引用符を省略できるため、区別するためにグローバル定数を先頭にバックスラッシュを付けて記述することをお勧めします: ```latte -as in PHP: {$stock ? 'In stock' : ''} - -abbreviated: {$stock ? 'In stock'} +{if \PROJECT_ID === 1} ... {/if} ``` +この記述はPHP自体で完全に有効であり、バックスラッシュは定数がグローバル名前空間にあることを示します。 + -配列における現代的な鍵の表記法 .[#toc-modern-key-notation-in-the-array] --------------------------------------------------------- +短縮三項演算子 +------- -配列のキーは、関数を呼び出すときの名前付きパラメータと同じように書くことができます。 +三項演算子の3番目の値が空の場合、省略できます(エルビス演算子と同様): ```latte -as in PHP: {var $arr = ['one' => 'item 1', 'two' => 'item 2']} +PHPと同様: {$stock ? '在庫あり' : ''} -modern: {var $arr = [one: 'item 1', two: 'item 2']} +短縮形: {$stock ? '在庫あり'} ``` -フィルター .[#toc-filters] ---------------------- +配列のキーのモダンな記述法 +------------- -フィルターはどんな式にも使えます。全体を大括弧で囲んでください。 +配列のキーは、関数呼び出し時の名前付きパラメータと同様に記述できます: ```latte -{var $content = ($text|truncate: 30|upper)} +PHPと同様: {var $arr = ['one' => 'item 1', 'two' => 'item 2']} + +モダンな記述法: {var $arr = [one: 'item 1', two: 'item 2']} ``` -演算子`in` .[#toc-operator-in] ---------------------------- +フィルタ +---- -`in` 演算子は`in_array()` 関数の代わりに使用することができます。比較は常に厳密です。 +フィルタは任意の式に使用でき、全体を括弧で囲むだけです: ```latte -{* like in_array($item, $items, true) *} -{if $item in $items} - ... -{/if} +{var $content = ($text|truncate: 30|upper)} ``` -.{data-version:2.9} -未定義安全演算子によるオプショナルチェーニング .[#toc-optional-chaining-with-undefined-safe-operator] ------------------------------------------------------------------------------- +`in` 演算子 +-------- -未定義安全演算子`??->` は nullsafe 演算子`?->` と似ていますが、変数、プロパティ、インデックスが全く存在しない場合にもエラーを発生させません。 +`in` 演算子は `in_array()` 関数の代わりに使用できます。比較は常に厳密 (`===`) です: ```latte -{$order??->id} +{* in_array($item, $items, true) と同等 *} +{if $item in $items} + ... +{/if} ``` -これは、`$order` が定義されていて null でないときは`$order->id` が計算されるが、`$order` が null か存在しないときは、今やっていることをやめて null を返すだけでよい、ということを意味しています。 - -```latte -{$user??->address??->street} -// roughly means isset($user) && isset($user->address) ? $user->address->street : null -``` +歴史的な背景 +------ -歴史への窓 .[#toc-a-window-into-history] ------------------------------------ +Latteはその歴史の中で、数年後にPHP自体に登場した多くのシンタックスシュガーを導入しました。例えば、Latteでは、PHP自体で可能になるずっと前から、配列を `array(1, 2, 3)` の代わりに `[1, 2, 3]` と書いたり、nullsafe演算子 `$obj?->foo` を使用したりすることができました。Latteはまた、今日のPHPの `...$arr` 演算子と同等の配列展開演算子 `(expand) $arr` を導入しました。 -Latteはその歴史の中で、数年後にPHP自体に登場する構文上のお菓子をいくつも生み出してきました。例えば、Latteでは、配列を の代わりに `[1, 2, 3]`の代わりに`array(1, 2, 3)` と書いたり、ヌルセーフ演算子`$obj?->foo` を使ったりすることが、PHPで可能になるずっと以前から可能でした。また、Latteでは配列展開演算子`(expand) $arr` も導入されており、これは現在のPHPの演算子`...$arr` に相当するものです。 +変数が存在しない場合にエラーを発生させないnullsafe演算子 `?->` の類似物であるUndefined-safe演算子 `??->` は、歴史的な理由から生まれ、今日では標準のPHP演算子 `?->` を使用することをお勧めします。 -LatteにおけるPHPの制限事項 .[#toc-php-limitations-in-latte] -================================================== +LatteにおけるPHPの制限 +=============== -Latteで記述できるのはPHPの式のみです。つまり、クラスを宣言したり、`if`,`foreach`,`switch`,`return`,`try`,`throw` などの[制御 |https://www.php.net/manual/en/language.control-structures.php]構造を使用することはできませんが、Latteはその代わりに[タグを |tags]提供しています。 -また、[属性や |https://www.php.net/manual/en/language.attributes.php] [バックスティック |https://www.php.net/manual/en/language.operators.execution.php]、[マジック定数も |https://www.php.net/manual/en/language.constants.magic.php]使えません。 -`echo`,`include`,`require`,`exit`,`eval`,`unset` は関数ではなく、PHPの特殊な言語構造であり、式ではないので、使うことすらできません。 +LatteではPHP式のみを記述できます。つまり、セミコロンで終了するステートメントは使用できません。クラスを宣言したり、[制御構造 |https://www.php.net/manual/en/language.control-structures.php](例:`if`, `foreach`, `switch`, `return`, `try`, `throw` など)を使用したりすることはできません。代わりにLatteは独自の [タグ|tags] を提供します。 また、[属性 |https://www.php.net/manual/en/language.attributes.php]、[バッククォート(実行演算子) |https://www.php.net/manual/en/language.operators.execution.php]、または一部の [マジック定数 |https://www.php.net/manual/en/language.constants.magic.php] を使用することもできません。`unset`, `echo`, `include`, `require`, `exit`, `eval` も使用できません。これらは関数ではなく、PHPの特別な言語構造であり、したがって式ではないためです。コメントは複数行 `/* ... */` のみがサポートされています。 -しかし、[RawPhpExtension |develop#RawPhpExtension]拡張モジュールを有効にすることで、これらの制限を回避することができます。これにより、テンプレートの作者の責任において`{php ...}` タグで任意の PHP コードを使用することができます。 +ただし、これらの制限は、[RawPhpExtension |develop#RawPhpExtension] 拡張機能を有効にすることで回避できます。これにより、テンプレート作成者の責任において、`{php ...}` タグ内で任意のPHPコードを使用できます。 diff --git a/latte/ja/tags.texy b/latte/ja/tags.texy index 45df7cdf6b..12e32fd2b9 100644 --- a/latte/ja/tags.texy +++ b/latte/ja/tags.texy @@ -1,168 +1,173 @@ -ラテのタグ -***** +Latte タグ +******** .[perex] -Latte内蔵の全タグの概要と説明。 +標準で利用可能な Latte テンプレートシステムの全タグの概要と説明です。 .[table-latte-tags language-latte] -|## 印刷 -|`{$var}`,`{...}` or`{=...}` |[エスケープされた変数や式を印刷します。|#printing] -|`{$var\|filter}` |[フィルタリングして印刷する|#filters] -|`{l}` または`{r}` |`{` or `}` の文字を表示します。 +|## 出力 +| `{$var}`, `{...}` または `{=...}` | [エスケープされた変数または式を出力 |#出力] +| `{$var\|filter}` | [フィルタを使用して出力 |#フィルタ] +| `{l}` または `{r}` | `{` または `}` 文字を出力 .[table-latte-tags language-latte] |## 条件 -|`{if}`...`{elseif}`...`{else}`...`{/if}` | [条件 if |#if-elseif-else] -|`{ifset}`...`{elseifset}`...`{/ifset}` |[条件 ifset |#ifset-elseifset] -|`{ifchanged}`...`{/ifchanged}` |[変化があったかどうかのテスト|#ifchanged] -|`{switch}` `{case}` `{default}` `{/switch}` |[条件スイッチ |#switch-case-default] +| `{if}` … `{elseif}` … `{else}` … `{/if}` | [if 条件 |#if elseif else] +| `{ifset}` … `{elseifset}` … `{/ifset}` | [ifset 条件 |#ifset elseifset] +| `{ifchanged}` … `{/ifchanged}` | [変更があったかどうかのテスト |#ifchanged] +| `{switch}` `{case}` `{default}` `{/switch}` | [switch 条件 |#switch case default] +| `n:else` | [条件の代替コンテンツ |#n:else] .[table-latte-tags language-latte] |## ループ -|`{foreach}`...`{/foreach}` |[foreach |#foreach] -|`{for}`...`{/for}` |[for |#for] -|`{while}`...`{/while}` |[while |#while] -|`{continueIf $cond}` |[次の反復に進む |#continueif-skipif-breakif] -|`{skipIf $cond}` |[現在のループの反復をスキップする|#continueif-skipif-breakif] -|`{breakIf $cond}` |[ループを 中断|#continueif-skipif-breakif] -|`{exitIf $cond}` |[早期終了 |#exitif] -|`{first}`...`{/first}` |[最初のイテレーションか? |#first-last-sep] -|`{last}`...`{/last}` |[それは最後の反復ですか?|#first-last-sep] -|`{sep}`...`{/sep}` |[次の反復が続くか?|#first-last-sep] -|`{iterateWhile}`...`{/iterateWhile}` |[構造化されたforeach |#iterateWhile] -|`$iterator` |[foreachループ内の特殊変数 |#$iterator] +| `{foreach}` … `{/foreach}` | [#foreach] +| `{for}` … `{/for}` | [#for] +| `{while}` … `{/while}` | [#while] +| `{continueIf $cond}` | [次のイテレーションに進む |#continueIf skipIf breakIf] +| `{skipIf $cond}` | [イテレーションをスキップ |#continueIf skipIf breakIf] +| `{breakIf $cond}` | [ループの中断 |#continueIf skipIf breakIf] +| `{exitIf $cond}` | [早期終了 |#exitIf] +| `{first}` … `{/first}` | [最初のパスか? |#first last sep] +| `{last}` … `{/last}` | [最後のパスか? |#first last sep] +| `{sep}` … `{/sep}` | [まだ次のパスがあるか? |#first last sep] +| `{iterateWhile}` … `{/iterateWhile}` | [構造化 foreach |#iterateWhile] +| `$iterator` | [foreach 内の特殊変数 |#iterator] .[table-latte-tags language-latte] -|## 他のテンプレートをインクルードする -|`{include 'file.latte'}` |[他のファイルからテンプレートをインクルードする|#include] -|`{sandbox 'file.latte'}` |[サンドボックスモードでテンプレートをインクルードする|#sandbox] +|## 他のテンプレートの挿入 +| `{include 'file.latte'}` | [別のファイルからテンプレートを読み込む |#include] +| `{sandbox 'file.latte'}` | [sandbox モードでテンプレートを読み込む |#sandbox] .[table-latte-tags language-latte] -|## ブロック、レイアウト、テンプレートの継承 -|`{block}` |[匿名ブロック |#block] -|`{block blockname}` |[ブロックの定義 |template-inheritance#blocks] -|`{define blockname}` |[将来使用するブロックの定義 |template-inheritance#definitions] -|`{include blockname}` |[ブロックを印刷する|template-inheritance#printing-blocks] -|`{include blockname from 'file.latte'}` |[ファイルからのブロックの印刷|template-inheritance#printing-blocks] -|`{import 'file.latte'}` |[別のテンプレートからブロックをロードする|template-inheritance#horizontal-reuse] -|`{layout 'file.latte'}` /`{extends}` |[レイアウトファイルの指定 |template-inheritance#layout-inheritance] -|`{embed}`...`{/embed}` |[テンプレートまたはブロックをロードし、ブロックを上書きすることができます。|template-inheritance#unit-inheritance] -|`{ifset blockname}`...`{/ifset}` |[ブロックが定義されている場合の条件 |template-inheritance#checking-block-existence] +|## ブロック、レイアウト、テンプレート継承 +| `{block}` | [匿名ブロック |#block] +| `{block blockname}` | [ブロックを定義 |template-inheritance#Blocks] +| `{define blockname}` | [後で使用するためにブロックを定義 |template-inheritance#Definitions] +| `{include blockname}` | [ブロックのレンダリング |template-inheritance#Rendering blocks] +| `{include blockname from 'file.latte'}` | [ファイルからブロックをレンダリング |template-inheritance#Rendering blocks] +| `{import 'file.latte'}` | [テンプレートからブロックを読み込む |template-inheritance#Horizontal reuse] +| `{layout 'file.latte'}` / `{extends}` | [レイアウトファイルを指定 |template-inheritance#Layout inheritance] +| `{embed}` … `{/embed}` | [テンプレートまたはブロックを読み込み、ブロックの上書きを許可 |template-inheritance#Unit inheritance] +| `{ifset blockname}` … `{/ifset}` | [ブロックが存在するかどうかの条件 |template-inheritance#Checking block existence] .[table-latte-tags language-latte] |## 例外処理 -|`{try}`...`{else}`...`{/try}` |[例外をキャッチする |#try] -|`{rollback}` |[トライブロックを破棄する|#rollback] +| `{try}` … `{else}` … `{/try}` | [例外のキャッチ |#try] +| `{rollback}` | [try ブロックの破棄 |#rollback] .[table-latte-tags language-latte] |## 変数 -|`{var $foo = value}` |[変数の作成 |#var-default] -|`{default $foo = value}` |[変数が宣言されていないときのデフォルト値 |#var-default] -|`{parameters}` | [変数の宣言、デフォルト値のタイプ|#parameters] -|`{capture}`...`{/capture}` |[セクションを変数に取り込む |#capture] +| `{var $foo = value}` | [変数を作成 |#var default] +| `{default $foo = value}` | [存在しない場合に変数を作成 |#var default] +| `{parameters}` | [変数、型、デフォルト値を宣言 |#parameters] +| `{capture}` … `{/capture}` | [ブロックを変数にキャプチャ |#capture] .[table-latte-tags language-latte] -|## タイプ -|`{varType}` |[変数のタイプを宣言|type-system#varType] -|`{varPrint}` |[変数の種類を提案する|type-system#varPrint] -|`{templateType}` |[クラスを使った変数の型の宣言|type-system#templateType] -|`{templatePrint}` |[プロパティを含むクラスを生成 |type-system#templatePrint] +|## 型 +| `{varType}` | [変数の型を宣言 |type-system#varType] +| `{varPrint}` | [変数の型を提案 |type-system#varPrint] +| `{templateType}` | [クラスに基づいて変数の型を宣言 |type-system#templateType] +| `{templatePrint}` | [変数の型を持つクラスを提案 |type-system#templatePrint] .[table-latte-tags language-latte] |## 翻訳 -|`{_string}` |[翻訳された内容を表示する |#Translation] -|`{translate}`...`{/translate}` |[内容を 翻訳する|#Translation] +| `{_...}` | [翻訳を出力 |#翻訳] +| `{translate}` … `{/translate}` | [コンテンツを翻訳 |#翻訳] .[table-latte-tags language-latte] |## その他 -|`{contentType}` |[エスケープモードを切り替え、HTTPヘッダを送信 します。|#contenttype] -|`{debugbreak}` |[コードにブレークポイントを設定 します。|#debugbreak] -|`{do}` |[式を表示せずに評価 する|#do] -|`{dump}` |[トレイシーバーに変数をダンプ する|#dump] -|`{spaceless}`...`{/spaceless}` |[不要な空白を削除する|#spaceless] -|`{syntax}` |[実行時に構文を切り替えます。|#syntax] -|`{trace}` |[スタックトレースを表示する|#trace] +| `{contentType}` | [エスケープを切り替え、HTTP ヘッダーを送信 |#contentType] +| `{debugbreak}` | [コードにブレークポイントを配置 |#debugbreak] +| `{do}` | [コードを実行するが何も出力しない |#do] +| `{dump}` | [変数を Tracy Bar にダンプ |#dump] +| `{php}` | [任意の PHP コードを実行 |#php] +| `{spaceless}` … `{/spaceless}` | [余分な空白を除去 |#spaceless] +| `{syntax}` | [実行時に構文を変更 |#syntax] +| `{trace}` | [スタックトレースを表示 |#trace] .[table-latte-tags language-latte] -|## HTMLタグヘルパー -|`n:class` |[スマートなクラス属性|#n:class] -|`n:attr` | [スマートなHTML属性 |#n:attr] -|`n:tag` |[HTML 要素の動的な名前 |#n:tag] -|`n:ifcontent` |[空の HTML タグを省略する|#n:ifcontent] +|## HTMLコーダーヘルパー +| `n:class` | [HTML の class 属性の動的記述 |#n:class] +| `n:attr` | [任意の HTML 属性の動的記述 |#n:attr] +| `n:tag` | [HTML 要素名の動的記述 |#n:tag] +| `n:ifcontent` | [空の HTML タグを省略 |#n:ifcontent] .[table-latte-tags language-latte] -|## Nette Framework のみで利用可能 -|`n:href` |[`` HTML要素内のリンク |application:en:creating-links#In the Presenter Template] -|`{link}` |[リンクを表示する|application:en:creating-links#In the Presenter Template] -|`{plink}` |[プレゼンターへのリンクを表示する|application:en:creating-links#In the Presenter Template] -|`{control}` |[コンポーネントを表示します|application:en:components#Rendering] -|`{snippet}`...`{/snippet}` |[AJAXで送信可能なテンプレートスニペット |application:en:ajax#tag-snippet] -|`{snippetArea}` | スニペットの封筒 -|`{cache}`...`{/cache}` |[テンプレートセクションをキャッシュする|caching:en#caching-in-latte] +|## Nette Frameworkでのみ利用可能 +| `n:href` | [HTML 要素 `` で使用されるリンク |application:creating-links#Presenterテンプレート内] +| `{link}` | [リンクを出力 |application:creating-links#Presenterテンプレート内] +| `{plink}` | [Presenter へのリンクを出力 |application:creating-links#Presenterテンプレート内] +| `{control}` | [コンポーネントをレンダリング |application:components#レンダリング] +| `{snippet}` … `{/snippet}` | [AJAX で送信可能なスニペット |application:ajax#Latteのスニペット] +| `{snippetArea}` | [スニペットのラッパー |application:ajax#スニペット領域] +| `{cache}` … `{/cache}` | [テンプレートの一部をキャッシュ |caching:#Latteでのキャッシュ] .[table-latte-tags language-latte] -|## Nette Forms のみで利用可能 -|`{form}`...`{/form}` |[フォームエレメントを表示します。|forms:en:rendering#form] -|`{label}`...`{/label}` |[フォーム入力ラベルを表示します。|forms:en:rendering#label-input] -|`{input}` |[フォーム入力要素を表示します。|forms:en:rendering#label-input] -|`{inputError}` | [フォーム入力要素のエラーメッセージを表示します。|forms:en:rendering#inputError] -|`n:name` |[HTML入力要素をアクティブにする|forms:en:rendering#n:name] -|`{formPrint}` |[ラテ型フォームの青写真を生成する|forms:en:rendering#formPrint] -|`{formPrintClass}` |[フォームデータのための PHP クラスを表示する|forms:en:in-presenter#mapping-to-classes] -|`{formContext}`...`{/formContext}` |[フォームの部分的なレンダリング |forms:en:rendering#special-cases] +|## Nette Formsでのみ利用可能 +| `{form}` … `{/form}` | [フォームタグをレンダリング |forms:rendering#form] +| `{label}` … `{/label}` | [フォームコントロールのラベルをレンダリング |forms:rendering#label input] +| `{input}` | [フォームコントロールをレンダリング |forms:rendering#label input] +| `{inputError}` | [フォームコントロールのエラーメッセージを出力 |forms:rendering#inputError] +| `n:name` | [フォームコントロールを有効化 |forms:rendering#n:name] +| `{formContainer}` … `{/formContainer}` | [フォームコンテナの描画 |forms:rendering#特殊なケース] +.[table-latte-tags language-latte] +|## ネットアセットでのみ利用可能 +|`{asset}` |[HTML 要素または URL としてアセットをレンダリングします |assets:#asset] +|`{preload}` |[パフォーマンス最適化のためのプリロードヒントを生成します |assets:#preload] +|`n:asset` |[HTML 要素にアセット属性を追加します |assets:#n:asset] -印刷 .[#toc-printing] -=================== +出力 +========== `{$var}` `{...}` `{=...}` ------------------------- -Latteでは、`{=...}` タグを使って、任意の式を出力しています。式の先頭が変数や関数呼び出しの場合、等号を書く必要はありません。つまり、実際には、ほとんど書く必要がないのです。 +Latteでは、任意の式を出力するために`{=...}`タグを使用します。Latteはあなたの快適さを重視しているので、式が変数または関数呼び出しで始まる場合、等号を記述する必要はありません。これは実際には、ほとんどの場合、記述する必要がないことを意味します: ```latte -Name: {$name} {$surname}
                                                                                    -Age: {date('Y') - $birth}
                                                                                    +名前: {$name} {$surname}
                                                                                    +年齢: {date('Y') - $birth}
                                                                                    ``` -PHP で知っていることは何でも式として書くことができます。新しい言語を学ぶ必要がないだけです。例えば +式として、PHPから知っているものなら何でも記述できます。新しい言語を学ぶ必要はありません。例えば: ```latte {='0' . ($num ?? $num * 3) . ', ' . PHP_VERSION} ``` -前の例で意味を探さないでください。もしそこで意味を見つけたら、私たちに書いてください :-) +前の例に意味を探さないでください。もし見つけたら、私たちに教えてください :-) -エスケープ出力 .[#toc-escaping-output] -------------------------------- +出力のエスケープ +-------- -テンプレートシステムの最も重要なタスクは何ですか?セキュリティホールを回避することです。そして、それはまさにLatteが出力に何かを印刷するときに行うことです。それは自動的にすべてをエスケープします。 +テンプレートシステムの最も重要なタスクは何ですか?セキュリティホールを防ぐことです。そして、Latteは何かを出力するたびにまさにこれを行います。自動的にエスケープします: ```latte -

                                                                                    {='one < two'}

                                                                                    {* prints: '

                                                                                    one < two

                                                                                    ' *} +

                                                                                    {='one < two'}

                                                                                    {* 出力: '

                                                                                    one < two

                                                                                    ' *} ``` -正確には、Latteは文脈依存のエスケープを行います。これは非常に重要かつユニークな機能なので、[別の章を設けて |safety-first#context-aware-escaping] 解説しています。 +正確には、Latteはコンテキストに応じたエスケープを使用します。これは非常に重要でユニークなことなので、[別の章 |safety-first#コンテキストに応じたエスケープ]を割きました。 -また、HTMLコード化されたコンテンツを信頼できるところから印刷する場合は?それなら簡単にエスケープをオフにすることができます。 +そして、信頼できるソースからのHTMLでエンコードされたコンテンツを出力する場合はどうなりますか?その場合、エスケープを簡単に無効にできます: ```latte {$trustedHtmlString|noescape} ``` .[warning] -`noescape` フィルタを誤用すると、XSS 脆弱性につながる可能性があります!自分が何をしているのか、そして印刷する文字列が信頼できるソースから来たものであることが **絶対に** 確実でない限り、決してこれを使用しないでください。 +`noescape`フィルタの誤った使用は、XSS脆弱性の発生につながる可能性があります!何をしているかを**完全に確信**しており、出力される文字列が信頼できるソースからのものである場合を除き、絶対に使用しないでください。 -JavaScriptで印刷する .[#toc-printing-in-javascript] ----------------------------------------------- +JavaScriptでの出力 +-------------- -文脈依存のエスケープのおかげで、JavaScriptの内部で変数を印刷するのは驚くほど簡単で、Latteはそれらを適切にエスケープします。 +コンテキストに応じたエスケープのおかげで、JavaScript内で変数を非常に出力しやすく、Latteが正しいエスケープを処理します。 -変数は文字列である必要はなく、どんなデータ型にも対応し、JSONとしてエンコードされます。 +変数は文字列である必要はなく、任意のデータ型がサポートされており、JSONとしてエンコードされます: ```latte {var $foo = ['hello', true, 1]} @@ -171,7 +176,7 @@ JavaScriptで印刷する .[#toc-printing-in-javascript] ``` -を生成します。 +生成: ```latte ``` -これは、**変数を引用符で囲まないでください**という理由でもあります。ラテは文字列を引用符で囲みます。また、文字列の変数を別の文字列に入れたい場合は、単純に連結してください。 +これが、変数の周りに**引用符を書かない**理由でもあります:Latteは文字列の場合に自動的に追加します。そして、文字列変数を別の文字列に挿入したい場合は、単にそれらを連結します: ```latte ``` -フィルタ .[#toc-filters] --------------------- +フィルタ +---- -印刷された式は、[フィルターによって |syntax#filters]変更することができます。例えば、この例では文字列を大文字に変換し、最大30文字に短縮しています。 +出力される式は[フィルタ |syntax#フィルタ]で変更できます。例えば、文字列を大文字に変換し、最大30文字に短縮します: ```latte {$string|upper|truncate:30} ``` -また、以下のように式の一部にフィルタを適用することもできます。 +フィルタは、式の一部にもこのように適用できます: ```latte {$left . ($middle|upper) . $right} ``` -条件 .[#toc-conditions] -===================== +条件 +======== `{if}` `{elseif}` `{else}` -------------------------- -条件は、PHP の条件と同じように動作します。PHP でおなじみの表現が使えるので、新しい言語を学ぶ必要はありません。 +条件は、PHPの対応するものと同じように動作します。PHPから知っているのと同じ式を使用でき、新しい言語を学ぶ必要はありません。 ```latte {if $product->inStock > Stock::Minimum} - In stock + 在庫あり {elseif $product->isOnWay()} - On the way + 輸送中 {else} - Not available + 利用不可 {/if} ``` -他のペアタグと同様に、`{if} ... {/ if}` のペアは、例えば[n:attribute |syntax#n:attributes] のように書くことができます。 +すべてのペアタグと同様に、`{if} ... {/if}`のペアも[n:属性 |syntax#n:属性]の形式で記述できます。例として: ```latte -

                                                                                    In stock {$count} items

                                                                                    +

                                                                                    在庫 {$count} 個

                                                                                    ``` -n:attributesに接頭辞`tag-` を付けることができることをご存知でしょうか?この場合、HTMLタグにのみ影響を与え、その間の内容は常に印刷されます。 +n:属性にプレフィックス`tag-`を付けることができることを知っていましたか?そうすると、条件はHTMLタグの出力にのみ適用され、その間のコンテンツは常に表示されます: ```latte
                                                                                    Hello -{* prints 'Hello' when $clickable is falsey *} -{* prints 'Hello' when $clickable is truthy *} +{* $clickable が false の場合 'Hello' を出力 *} +{* $clickable が true の場合 'Hello' を出力 *} ``` -いいですね。 +素晴らしい。 + + +`n:else` .{data-version:3.0.11} +------------------------------- + +条件`{if} ... {/if}`を[n:属性 |syntax#n:属性]の形式で記述する場合、`n:else`を使用して代替分岐を指定するオプションがあります: + +```latte +在庫 {$count} 個 + +利用不可 +``` + +`n:else`属性は、[`n:ifset` |#ifset elseifset]、[`n:foreach` |#foreach]、[`n:try` |#try]、[#`n:ifcontent`]、および[`n:ifchanged` |#ifchanged]とのペアでも使用できます。 `{/if $cond}` ------------- -`{if}` 条件の式も終了タグで指定できることに驚かれたかもしれません。これは、タグが開かれたときに、まだ条件の値がわからないような場合に便利です。遅延判定とでも言いましょうか。 +`{if}`条件内の式を終了タグで指定できることに驚くかもしれません。これは、条件を開くときにその値がまだわからない場合に便利です。これを遅延決定と呼びましょう。 -たとえば、データベースからレコードを取得してテーブルのリストアップを開始し、レポートが完成した後で、データベースにレコードがないことに気がついたとします。そこで、終了タグ`{/if}` に条件を入れて、レコードがなければ、何も印刷されないようにします。 +例えば、データベースからのレコードを含むテーブルの出力を開始し、出力が完了した後にデータベースにレコードがなかったことに気づいたとします。そこで、終了タグ`{/if}`に条件を付け、レコードがない場合は何も出力されません: ```latte {if} -

                                                                                    Printing rows from the database

                                                                                    +

                                                                                    データベースからの行のリスト

                                                                                    {foreach $resultSet as $row} @@ -266,28 +285,28 @@ n:attributesに接頭辞`tag-` を付けることができることをご存知 便利でしょう? -繰延条件では`{else}` も使えますが、`{elseif}` は使えません。 +遅延条件では`{else}`を使用できますが、`{elseif}`は使用できません。 `{ifset}` `{elseifset}` ----------------------- .[note] -参照 [`{ifset block}` |template-inheritance#checking-block-existence] +参照 [`{ifset block}` |template-inheritance#Checking block existence] -変数(あるいは複数の変数)が存在し、かつ非 null 値であるかどうかを調べるには、`{ifset $var}` 条件を使用します。これは、実際にはPHPの`if (isset($var))` と同じです。他のペアタグと同様に、[n:attributeの |syntax#n:attributes]形式で書くことができますので、例で紹介しましょう。 +`{ifset $var}`条件を使用して、変数(または複数の変数)が存在し、*null*以外の値を持っているかどうかを確認します。実際には、PHPの`if (isset($var))`と同じです。すべてのペアタグと同様に、[n:属性 |syntax#n:属性]の形式でも記述できます。例として示しましょう: ```latte - + ``` -`{ifchanged}` .{data-version:2.9} ---------------------------------- +`{ifchanged}` +------------- -`{ifchanged}` は、ループ(foreach、for、while)内の最後の反復から、変数の値が変化したかどうかをチェックします。 +`{ifchanged}`は、ループ(foreach、for、またはwhile)の最後のイテレーション以降に変数の値が変更されたかどうかを確認します。 -タグの中で1つ以上の変数を指定すると、それらの変数のいずれかが変更されたかどうかをチェックし、それに応じて内容を表示します。たとえば、次の例では、名前をリストアップするときに、変更されるたびに名前の最初の文字を見出しとしてプリントしています。 +タグに1つ以上の変数を指定すると、それらのいずれかが変更されたかどうかを確認し、それに応じてコンテンツを出力します。例えば、次の例では、名前のリストを出力するときに名前の最初の文字が変更されるたびに、それをヘッダーとして出力します: ```latte {foreach ($names|sort) as $name} @@ -297,7 +316,7 @@ n:attributesに接頭辞`tag-` を付けることができることをご存知 {/foreach} ``` -しかし、引数が与えられない場合は、レンダリングされた内容そのものが以前の状態と照らし合わされる。つまり、先ほどの例では、タグの引数を省略しても大丈夫ということです。また、もちろん[n:attributeを |syntax#n:attributes]使うこともできます。 +ただし、引数を指定しない場合は、レンダリングされたコンテンツが前の状態と比較されます。つまり、前の例では、タグ内の引数を省略できます。そしてもちろん、[n:属性 |syntax#n:属性]を使用することもできます: ```latte {foreach ($names|sort) as $name} @@ -307,49 +326,49 @@ n:attributesに接頭辞`tag-` を付けることができることをご存知 {/foreach} ``` -の中に`{else}` 節を入れることもできます。`{ifchanged}`. +`{ifchanged}`内では、`{else}`句を指定することもできます。 `{switch}` `{case}` `{default}` ------------------------------- -値を複数のオプションと比較します。これは、PHPでおなじみの`switch` の構造に似ています。しかし、Latteではそれを改良しています。 +値を複数のオプションと比較します。これは、PHPから知っている`switch`条件文に似ています。ただし、Latteはそれを改善します: -- 厳密な比較を行う (`===`) -- は必要ありません。`break` +- 厳密な比較(`===`)を使用します +- `break`は必要ありません -つまり、PHP 8.0が搭載している`match` 構造とまったく同じものです。 +したがって、PHP 8.0で導入された`match`構造と完全に同等です。 ```latte {switch $transport} {case train} - By train + 電車で {case plane} - By plane + 飛行機で {default} - Differently + その他 {/switch} ``` -.{data-version:2.9} -節`{case}` は、カンマで区切られた複数の値を含むことができます。 + +`{case}`句には、カンマで区切られた複数の値を含めることができます: ```latte {switch $status} -{case $status::New}new item -{case $status::Sold, $status::Unknown}not available +{case $status::New}新規項目 +{case $status::Sold, $status::Unknown}利用不可 {/switch} ``` -ループ .[#toc-loops] -================= +ループ +=== -Latteでは、PHPでおなじみのforeach、for、whileといったループが利用可能です。 +Latteには、PHPから知っているすべてのループがあります:foreach、for、while。 `{foreach}` ----------- -サイクルはPHPと全く同じように書きます。 +ループはPHPとまったく同じように記述します: ```latte {foreach $langs as $code => $lang} @@ -357,11 +376,11 @@ Latteでは、PHPでおなじみのforeach、for、whileといったループが {/foreach} ``` -さらに、これから説明するような便利な調整もあるそうです。 +さらに、これから説明するいくつかの便利な機能があります。 -例えば、ラテは作成した変数が誤って同じ名前のグローバル変数を上書きしないようにチェックしています。これは、`$lang` がそのページの現在の言語であると仮定したときに、`foreach $langs as $lang` がその変数を上書きしてしまったことに気づかなかったときの救いになります。 +例えば、Latteは、作成された変数が誤って同じ名前のグローバル変数を上書きしていないかを確認します。これにより、`$lang`に現在のページの言語が含まれていると期待していて、`foreach $langs as $lang`がその変数を上書きしたことに気づかない状況を救います。 -foreachループも[n:attributeを |syntax#n:attributes]使えば非常にエレガントかつ経済的に書くことができる。 +foreachループは、[n:属性 |syntax#n:属性]を使用して非常にエレガントかつ簡潔に記述することもできます: ```latte
                                                                                      @@ -369,7 +388,7 @@ foreachループも[n:attributeを |syntax#n:attributes]使えば非常にエレ
                                                                                    ``` -n:attributeの前に`inner-` というプレフィックスを付けることができることをご存知ですか?これで、ループの中で要素の内側の部分だけが繰り返されるようになります。 +n:属性にプレフィックス`inner-`を付けることができることを知っていましたか?そうすると、ループ内で要素の内部のみが繰り返されます: ```latte
                                                                                    @@ -378,7 +397,7 @@ n:attributeの前に`inner-` というプレフィックスを付けることが
                                                                                    ``` -というわけで、次のような感じでプリントされます。 +したがって、次のようなものが出力されます: ```latte
                                                                                    @@ -390,17 +409,17 @@ n:attributeの前に`inner-` というプレフィックスを付けることが ``` -`{else}` .{data-version:2.9}{toc: foreach-else} ------------------------------------------------ +`{else}` .{toc: foreach-else} +----------------------------- -`foreach` ループでは、オプションで`{else}` 節を指定することができ、そのテキストは与えられた配列が空の場合に表示されます。 +`foreach`ループ内では、`{else}`句を指定できます。その内容は、ループが空の場合に表示されます: ```latte
                                                                                      {foreach $people as $person}
                                                                                    • {$person->name}
                                                                                    • {else} -
                                                                                    • Sorry, no users in this list
                                                                                    • +
                                                                                    • 申し訳ありませんが、このリストにはユーザーがいません
                                                                                    • {/foreach}
                                                                                    ``` @@ -409,17 +428,17 @@ n:attributeの前に`inner-` というプレフィックスを付けることが `$iterator` ----------- -`foreach` ループの内部では、変数`$iterator` が初期化されます。これは、現在のループに関する重要な情報を保持します。 +`foreach`ループ内で、Latteは`$iterator`変数を作成します。これを使用して、進行中のループに関する有用な情報を取得できます: --`$iterator->first` - これは最初の反復処理か? --`$iterator->last` - これは最後の反復処理ですか? --`$iterator->counter` - 反復カウンタ、1 から始まる。 --`$iterator->counter0` - 反復カウンタ、0から開始。 .{data-version:2.9} --`$iterator->odd` - この反復は奇数ですか? --`$iterator->even` - この反復は偶数か? --`$iterator->parent` - 現在のイテレータを囲むイテレータ .{data-version:2.9} --`$iterator->nextValue` - ループ内の次のアイテム --`$iterator->nextKey` - ループ内の次のアイテムのキー +- `$iterator->first` - ループを初めて通過していますか? +- `$iterator->last` - 最後のパスですか? +- `$iterator->counter` - 1から数えて何番目のパスですか? +- `$iterator->counter0` - 0から数えて何番目のパスですか? +- `$iterator->odd` - 奇数番目のパスですか? +- `$iterator->even` - 偶数番目のパスですか? +- `$iterator->parent` - 現在のイテレータを囲むイテレータ +- `$iterator->nextValue` - ループ内の次の項目 +- `$iterator->nextKey` - ループ内の次の項目のキー ```latte @@ -435,20 +454,19 @@ n:attributeの前に`inner-` というプレフィックスを付けることが {/foreach} ``` -ラテは賢く、`$iterator->last` 配列だけでなく、事前に項目数が分からない一般的なイテレータ上でループを実行する場合にも有効です。 +Latteは賢く、`$iterator->last`は配列だけでなく、項目数が事前にわからない一般的なイテレータ上でループが実行される場合にも機能します。 `{first}` `{last}` `{sep}` -------------------------- -これらのタグは、`{foreach}` のループの中で使用することができます。`{first}` のコンテンツは、最初のパスでレンダリングされます。 -`{last}` のコンテンツがレンダリングされる......わかるかな?そう、最後のパスです。これらは実は、`{if $iterator->first}` と`{if $iterator->last}` のショートカットです。 +これらのタグは`{foreach}`ループ内で使用できます。`{first}`の内容は、最初のパスの場合にレンダリングされます。`{last}`の内容は…推測できますか?はい、最後のパスの場合にレンダリングされます。これらは実際には`{if $iterator->first}`と`{if $iterator->last}`の短縮形です。 -タグは[n:attributesと |syntax#n:attributes]書くこともできる. +タグは[n:属性 |syntax#n:属性]としてもエレガントに使用できます: ```latte {foreach $rows as $row} - {first}

                                                                                    List of names

                                                                                    {/first} + {first}

                                                                                    名前のリスト

                                                                                    {/first}

                                                                                    {$row->name}

                                                                                    @@ -456,21 +474,21 @@ n:attributeの前に`inner-` というプレフィックスを付けることが {/foreach} ``` -`{sep}` の内容は、反復が最後でない場合にレンダリングされるので、リストされた項目の間にカンマなどの区切り文字を印刷するのに適しています。 +`{sep}`タグの内容は、パスが最後でない場合にレンダリングされます。したがって、出力される項目の間に区切り文字、例えばカンマをレンダリングするのに役立ちます: ```latte {foreach $items as $item} {$item} {sep}, {/sep} {/foreach} ``` -かなり実用的ですね。 +これはかなり実用的でしょう? -`{iterateWhile}` .{data-version:2.10} -------------------------------------- +`{iterateWhile}` +---------------- -条件を満たす限りネストしたループで反復処理を行うことで、foreachループでの反復処理中の線形データのグループ化を簡略化します。[クックブックの |cookbook/iteratewhile]説明をお読みください。 +foreachループ内で反復処理中に線形データをグループ化するのを簡略化します。条件が満たされている間、ネストされたループで反復処理を実行します。[詳細なチュートリアルを読む|cookbook/grouping]。 -また、上の例の`{first}` と`{last}` をエレガントに置き換えることができます。 +上記の例の`{first}`と`{last}`をエレガントに置き換えることもできます: ```latte {foreach $rows as $row} @@ -487,19 +505,21 @@ n:attributeの前に`inner-` というプレフィックスを付けることが {/foreach} ``` +参照 [batch |filters#batch] および [group |filters#group] フィルタ。 + `{for}` ------- -サイクルはPHPと全く同じように書きます。 +ループはPHPとまったく同じように記述します: ```latte {for $i = 0; $i < 10; $i++} - Item #{$i} + 項目 {$i} {/for} ``` -タグは[n:attributeと |syntax#n:attributes]書くこともできる。 +タグは[n:属性 |syntax#n:属性]としても使用できます: ```latte

                                                                                    {$i}

                                                                                    @@ -509,7 +529,7 @@ n:attributeの前に`inner-` というプレフィックスを付けることが `{while}` --------- -ここでも、PHPと全く同じ方法でサイクルを書きます。 +ループもPHPとまったく同じように記述します: ```latte {while $row = $result->fetch()} @@ -517,7 +537,7 @@ n:attributeの前に`inner-` というプレフィックスを付けることが {/while} ``` -あるいは[n:attributeの |syntax#n:attributes]ように。 +または[n:属性 |syntax#n:属性]として: ```latte @@ -525,7 +545,7 @@ n:attributeの前に`inner-` というプレフィックスを付けることが ``` -終了タグに条件を指定するバリアントは、PHP の do-while ループに相当します。 +終了タグに条件を持つバリアントも可能です。これはPHPのdo-whileループに対応します: ```latte {while} @@ -537,7 +557,7 @@ n:attributeの前に`inner-` というプレフィックスを付けることが `{continueIf}` `{skipIf}` `{breakIf}` ------------------------------------- -ループを制御するために使用できる特別なタグがあります。`{continueIf ?}` と`{breakIf ?}` はそれぞれ、条件が満たされた場合に次の反復処理にジャンプし、ループを終了させるものです。 +任意のループを制御するために、条件が満たされた場合に次の要素に進む`{continueIf ?}`タグと、ループを終了する`{breakIf ?}`タグを使用できます: ```latte {foreach $rows as $row} @@ -547,8 +567,8 @@ n:attributeの前に`inner-` というプレフィックスを付けることが {/foreach} ``` -.{data-version:2.9} -タグ`{skipIf}` は`{continueIf}` と非常によく似ていますが、カウンターをインクリメントしません。したがって、`$iterator->counter` を表示して、いくつかの項目をスキップしても、番号付けに穴が開くことはありません。また、{else}句はすべての項目をスキップするときに表示されます。 + +`{skipIf}`タグは`{continueIf}`と非常によく似ていますが、`$iterator->counter`カウンターをインクリメントしません。したがって、それを表示し、同時にいくつかの項目をスキップすると、番号付けにギャップが生じません。また、すべての項目をスキップした場合に`{else}`句がレンダリングされます。 ```latte
                                                                                      @@ -556,7 +576,7 @@ n:attributeの前に`inner-` というプレフィックスを付けることが {skipIf $person->age < 18}
                                                                                    • {$iterator->counter}. {$person->name}
                                                                                    • {else} -
                                                                                    • Sorry, no adult users in this list
                                                                                    • +
                                                                                    • 申し訳ありませんが、このリストには大人がいません
                                                                                    • {/foreach}
                                                                                    ``` @@ -565,72 +585,68 @@ n:attributeの前に`inner-` というプレフィックスを付けることが `{exitIf}` .{data-version:3.0.5} -------------------------------- -ある条件が満たされたとき、テンプレートまたはブロックのレンダリングを終了します(すなわち、「早期終了」)。 +条件が満たされた場合にテンプレートまたはブロックのレンダリングを終了します(いわゆる「早期終了」)。 ```latte {exitIf !$messages} -

                                                                                    Messages

                                                                                    +

                                                                                    メッセージ

                                                                                    {$message}
                                                                                    ``` -テンプレートを含む .[#toc-including-templates] -===================================== +テンプレートの挿入 +========= `{include 'file.latte'}` .{toc: include} ---------------------------------------- .[note] -参照 [`{include block}` |template-inheritance#printing-blocks] +参照 [`{include block}` |template-inheritance#Rendering blocks] -`{include}` タグは、指定されたテンプレートを読み込み、レンダリングします。私たちの大好きなPHP言語では、次のような感じです。 +`{include}`タグは、指定されたテンプレートを読み込んでレンダリングします。私たちのお気に入りの言語PHPで言えば、次のようなものです: ```php ``` -インクルードされたテンプレートは、アクティブなコンテキストの変数にはアクセスできませんが、グローバル変数にはアクセスできます。 +インクルードされたテンプレートは、アクティブなコンテキストの変数にアクセスできず、グローバル変数にのみアクセスできます。 -この方法で変数を渡すことができます。 +インクルードされたテンプレートに変数を渡すには、次のようにします: ```latte -{* since Latte 2.9 *} {include 'template.latte', foo: 'bar', id: 123} - -{* before Latte 2.9 *} -{include 'template.latte', foo => 'bar', id => 123} ``` -テンプレート名には、任意の PHP 式を指定することができます。 +テンプレート名は、任意のPHP式にすることができます: ```latte {include $someVar} {include $ajax ? 'ajax.latte' : 'not-ajax.latte'} ``` -挿入される内容は、[フィルタを用いて |syntax#filters]変更することができます。次の例は、すべての HTML を削除し、大文字小文字を調整するものです。 +インクルードされたコンテンツは[フィルタ |syntax#フィルタ]を使用して変更できます。次の例では、すべてのHTMLを削除し、大文字小文字を調整します: ```latte {include 'heading.latte' |stripHtml|capitalize} ``` -[テンプレートの継承は |template inheritance]**デフォルトではこれに関与しません**。インクルードされたテンプレートにブロックタグを追加することはできますが、インクルードされたテンプレート内の一致するブロックを置き換えることはありません。インクルードを、ページやモジュールの独立した、シールドされた部分として考えてください。この動作は、モディファイア`with blocks` (Latte 2.9.1以降)を使って変更することができます。 +デフォルトでは、この場合、[テンプレート継承|template-inheritance]はまったく機能しません。インクルードされたテンプレートでブロックを使用できても、インクルード先のテンプレートの対応するブロックは置き換えられません。インクルードされたテンプレートを、ページの独立した分離された部分またはモジュールと考えてください。この動作は、`with blocks`修飾子を使用して変更できます: ```latte {include 'template.latte' with blocks} ``` -タグで指定されたファイル名とディスク上のファイルとの関係は、[ローダーの |extending-latte#Loaders]問題です。 +タグで指定されたファイル名とディスク上のファイルとの関係は、[ローダー|loaders]の問題です。 -`{sandbox}` .{data-version:2.8} -------------------------------- +`{sandbox}` +----------- -エンドユーザーが作成したテンプレートをインクルードする場合、サンドボックス化を検討する必要があります(詳細は[サンドボックスのドキュメントを |sandbox]参照してください)。 +エンドユーザーが作成したテンプレートを挿入する場合は、Sandboxモードを検討する必要があります(詳細については[sandbox ドキュメント |sandbox]を参照): ```latte {sandbox 'untrusted.latte', level: 3, data: $menu} @@ -641,9 +657,9 @@ n:attributeの前に`inner-` というプレフィックスを付けることが ========= .[note] -こちらもご覧ください [`{block name}` |template-inheritance#blocks] +参照 [`{block name}` |template-inheritance#Blocks] -名前のないブロックは、テンプレートの一部に[フィルタを |syntax#filters]適用する機能を提供します。例えば、[ストリップ |filters#strip]フィルターを適用して不要なスペースを削除することができます。 +名前のないブロックは、テンプレートの一部に[フィルタ |syntax#フィルタ]を適用する方法として機能します。例えば、このようにして、不要な空白を除去する[strip |filters#spaceless]フィルタを適用できます: ```latte {block|strip} @@ -654,16 +670,16 @@ n:attributeの前に`inner-` というプレフィックスを付けることが ``` -例外処理 .[#toc-exception-handling] -=============================== +例外処理 +==== -`{try}` .{data-version:2.9} ---------------------------- +`{try}` +------- -このタグを使うと、非常に簡単に堅牢なテンプレートを構築することができます。 +このタグのおかげで、堅牢なテンプレートを非常に簡単に作成できます。 -`{try}` ブロックのレンダリング中に例外が発生した場合、ブロック全体がスローされ、その後にレンダリングが続行されます。 +`{try}`ブロックのレンダリング中に例外が発生した場合、ブロック全体が破棄され、レンダリングはその後に続行されます: ```latte {try} @@ -675,7 +691,7 @@ n:attributeの前に`inner-` というプレフィックスを付けることが {/try} ``` -オプションの節`{else}` の内容は、例外が発生したときだけレンダリングされる。 +オプションの`{else}`句の内容は、例外が発生した場合にのみレンダリングされます: ```latte {try} @@ -685,11 +701,11 @@ n:attributeの前に`inner-` というプレフィックスを付けることが {/foreach} {else} -

                                                                                    Sorry, the tweets could not be loaded.

                                                                                    +

                                                                                    申し訳ありませんが、ツイートを読み込めませんでした。

                                                                                    {/try} ``` -このタグは[n:attributeと |syntax#n:attributes]書くこともできる. +タグは[n:属性 |syntax#n:属性]としても使用できます: ```latte
                                                                                      @@ -697,13 +713,13 @@ n:attributeの前に`inner-` というプレフィックスを付けることが
                                                                                    ``` -また、ロギングなどのための[例外ハンドラを独自 |develop#exception handler]に定義することも可能です。 +例えばロギングのために、カスタム[例外ハンドラ |develop#例外ハンドラ]を定義することも可能です。 -`{rollback}` .{data-version:2.9} --------------------------------- +`{rollback}` +------------ -`{try}` ブロックは、`{rollback}` を使って手動で停止したり、スキップしたりすることもできます。そのため、事前にすべての入力データをチェックする必要はなく、レンダリング中にのみ、オブジェクトをレンダリングすることに意味があるかどうかを判断することができます。 +`{try}`ブロックは、`{rollback}`を使用して手動で停止およびスキップすることもできます。これにより、すべての入力データを事前にチェックする必要がなく、レンダリング中にオブジェクトをまったくレンダリングしないことを決定できます: ```latte {try} @@ -719,30 +735,30 @@ n:attributeの前に`inner-` というプレフィックスを付けることが ``` -変数 .[#toc-variables] -==================== +変数 +======== `{var}` `{default}` ------------------- -`{var}` タグを使用して、テンプレート内に新しい変数を作成することにします。 +新しい変数は、テンプレート内で`{var}`タグを使用して作成します: ```latte {var $name = 'John Smith'} {var $age = 27} -{* Multiple declaration *} +{* 複数宣言 *} {var $name = 'John Smith', $age = 27} ``` -`{default}` タグも同様に動作しますが、変数が存在しない場合にのみ変数を作成する点が異なります。 +`{default}`タグも同様に機能しますが、変数が存在しない場合にのみ作成します。変数がすでに存在し、`null`値を含んでいる場合、上書きされません: ```latte {default $lang = 'cs'} ``` -Latte 2.7では、[変数の種類を |type-system]指定することもできます。Latte 2.7では、変数の型も指定できます。今のところ、これらは情報提供であり、Latteはこれらをチェックしません。 +[変数の型|type-system]を指定することもできます。今のところ、これらは情報提供のみであり、Latteはそれらをチェックしません。 ```latte {var string $name = $article->getTitle()} @@ -750,10 +766,10 @@ Latte 2.7では、[変数の種類を |type-system]指定することもでき ``` -`{parameters}` .{data-version:2.9} ----------------------------------- +`{parameters}` +-------------- -関数がパラメータを宣言するのと同じように,テンプレートもその先頭で変数を宣言することができます. +関数がパラメータを宣言するように、テンプレートも最初に変数を宣言できます: ```latte {parameters @@ -763,15 +779,15 @@ Latte 2.7では、[変数の種類を |type-system]指定することもでき } ``` -デフォルト値のない変数`$a` と`$b` は自動的にデフォルト値`null` を持ちます.宣言された型はまだ参考値であり,Latteはそれをチェックしません. +デフォルト値が指定されていない変数`$a`と`$b`は、自動的にデフォルト値`null`を持ちます。宣言された型は今のところ情報提供のみであり、Latteはそれらをチェックしません。 -その他、宣言された変数はテンプレートに渡されません。この点は`{default}` タグとの違いです。 +宣言された変数以外の変数はテンプレートに渡されません。これは`{default}`タグとの違いです。 `{capture}` ----------- -`{capture}` タグを使用することで、出力を変数に取り込むことができます。 +出力を変数にキャプチャします: ```latte {capture $var} @@ -780,10 +796,10 @@ Latte 2.7では、[変数の種類を |type-system]指定することもでき {/capture} -

                                                                                    Captured: {$var}

                                                                                    +

                                                                                    キャプチャされた内容: {$var}

                                                                                    ``` -タグは[n:attributeと |syntax#n:attributes]書くこともできる。 +すべてのペアタグと同様に、このタグも[n:属性 |syntax#n:属性]として記述できます: ```latte
                                                                                      @@ -791,33 +807,35 @@ Latte 2.7では、[変数の種類を |type-system]指定することもでき
                                                                                    ``` +HTML出力は、出力時に[不要なエスケープが発生しないように |develop#変数の自動エスケープの無効化]、`Latte\Runtime\Html`オブジェクトの形式で変数`$var`に保存されます。 + -その他 .[#toc-others] -================== +その他 +=== `{contentType}` --------------- -タグを使用して、テンプレートが表すコンテンツの種類を指定します。オプションは以下の通りです。 +テンプレートが表すコンテンツのタイプを指定するタグ。オプションは次のとおりです: --`html` (デフォルトのタイプ) --`xml` --`javascript` --`css` --`calendar` (iCal) --`text` +- `html` (デフォルトタイプ) +- `xml` +- `javascript` +- `css` +- `calendar` (iCal) +- `text` -この機能は,[文脈に応じたエスケープを |safety-first#context-aware-escaping]設定し,Latteが正しくエスケープできるようにするために重要です.例えば、`{contentType xml}` はXMLモードに切り替わり、`{contentType text}` はエスケープを完全にオフにします。 +その使用は重要です。なぜなら、[コンテキストに応じたエスケープ |safety-first#コンテキストに応じたエスケープ]を設定し、それによってのみ正しくエスケープできるからです。例えば、`{contentType xml}`はXMLモードに切り替え、`{contentType text}`はエスケープを完全に無効にします。 -パラメータが`application/xml` のようなフル機能の MIME タイプの場合、ブラウザに HTTP ヘッダ`Content-Type` も送信します。 +パラメータが`application/xml`などの完全なMIMEタイプである場合、さらにHTTPヘッダー`Content-Type`をブラウザに送信します: ```latte {contentType application/xml} - RSS feed + RSSフィード ... @@ -829,26 +847,24 @@ Latte 2.7では、[変数の種類を |type-system]指定することもでき `{debugbreak}` -------------- -コードの実行が中断される場所を指定します。プログラマが実行環境を検査し、コードが期待通りに実行されることを確認するためのデバッグ目的で使用されます。[Xdebugに |https://xdebug.org]対応しています。さらに、コードがブレークする条件を指定することができます。 +プログラムの実行が一時停止され、デバッガが起動される場所を示します。これにより、プログラマーは実行環境を検査し、プログラムが期待どおりに機能しているかどうかを確認できます。[Xdebug |https://xdebug.org/]をサポートしています。プログラムを一時停止するタイミングを指定する条件を追加できます。 ```latte -{debugbreak} {* breaks the program *} +{debugbreak} {* プログラムを一時停止 *} -{debugbreak $counter == 1} {* breaks the program if the condition is met *} +{debugbreak $counter == 1} {* 条件が満たされた場合にプログラムを一時停止 *} ``` `{do}` ------ -コードを実行し、何も表示しない。 +PHPコードを実行しますが、何も出力しません。他のすべてのタグと同様に、PHPコードとは単一の式を意味します。参照 [PHP の制限 |syntax#LatteにおけるPHPの制限]。 ```latte {do $num++} ``` -Latte 2.7 以前のバージョンでは、`{php}` を使用していました。 - `{dump}` -------- @@ -856,19 +872,25 @@ Latte 2.7 以前のバージョンでは、`{php}` を使用していました 変数または現在のコンテキストをダンプします。 ```latte -{dump $name} {* dumps the $name variable *} +{dump $name} {* 変数 $name をダンプ *} -{dump} {* dumps all the defined variables *} +{dump} {* 現在定義されているすべての変数をダンプ *} ``` .[caution] -[Tracy |tracy:en] パッケージが必要です。 +ライブラリ[Tracy|tracy:]が必要です。 + + +`{php}` +------- + +任意のPHPコードを実行できます。タグは[RawPhpExtension |develop#RawPhpExtension]拡張機能を使用して有効にする必要があります。 `{spaceless}` ------------- -不要な空白を削除する。[スペースレスフィルタと似て |filters#spaceless]いる。 +出力から不要な空白を除去します。[spaceless |filters#spaceless]フィルタと同様に機能します。 ```latte {spaceless} @@ -878,52 +900,52 @@ Latte 2.7 以前のバージョンでは、`{php}` を使用していました {/spaceless} ``` -出力は +生成: ```latte
                                                                                    • Hello
                                                                                    ``` -タグは、[n:attributeと |syntax#n:attributes]書くこともできる。 +タグは[n:属性 |syntax#n:属性]としても記述できます。 `{syntax}` ---------- -ラテタグは中括弧だけで囲む必要はありません。実行時であっても、別のセパレータを選択することができます。これは、`{syntax…}` 、パラメータにすることができます。 +Latteタグは、単一の波括弧で囲む必要はありません。実行時に別のデリミタを選択することもできます。これには`{syntax …}`を使用し、パラメータとして次を指定できます: -- doubleとする。 `{{...}}` -- off: Latteタグを完全に無効にする +- double: `{{...}}` +- off: Latteタグの処理を完全に無効にします -n:attributeという記法を用いると、JavaScriptのブロックに対してのみLatteを無効にすることができます。 +n:属性を使用すると、例えばJavaScriptの1つのブロックに対してのみLatteを無効にできます: ```latte ``` -JavaScriptでもLatteは快適に使えますが、この例のように`{`の直後に文字が来るような構成は避けてください、詳しくは[JavaScriptやCSSでのLatteを |recipes#Latte inside JavaScript or CSS]ご覧ください。 +LatteはJavaScript内でも非常に快適に使用できます。この例のように、`{`の直後に文字が続く構造を避けるだけで十分です。参照 [JavaScript または CSS 内の Latte |recipes#JavaScriptまたはCSS内のLatte]。 -`{syntax off}` (つまりn:属性ではなくタグ)でLatteをオフにすると、`{/syntax}` までのタグは厳密に無視されます。 +`{syntax off}`(つまり、n:属性ではなくタグ)を使用してLatteを無効にすると、`{/syntax}`までのすべてのタグが一貫して無視されます。 -{trace} .{data-version:2.10} ----------------------------- +{trace} +------- -`Latte\RuntimeException` 例外をスローします。そのスタックトレースはテンプレートの精神に則っています。したがって、関数やメソッドを呼び出す代わりに、ブロックを呼び出したり、テンプレートを挿入することになります。[Tracyの |tracy:en]ようなスローされた例外を明確に表示するツールを使用すると、渡されたすべての引数を含むコールスタックを明確に見ることができます。 +`Latte\RuntimeException`例外をスローします。そのスタックトレースはテンプレートの精神に基づいています。つまり、関数やメソッドの呼び出しの代わりに、ブロックの呼び出しやテンプレートの挿入が含まれます。[Tracy|tracy:]などのスローされた例外を明確に表示するためのツールを使用している場合、すべての渡された引数を含むコールスタックが明確に表示されます。 -HTMLタグヘルパー .[#toc-html-tag-helpers] -=================================== +HTMLコーダーヘルパー +============ -n:class .[#toc-n-class] ------------------------ +n:class +------- -`n:class` のおかげで、必要な HTML 属性`class` を正確に生成することが非常に簡単になりました。 +`n:class`のおかげで、HTML属性`class`をまさに思い通りに非常に簡単に生成できます。 -例を挙げましょう。active 要素に`active` というクラスが必要です。 +例:アクティブな要素にクラス`active`を持たせたい: ```latte {foreach $items as $item} @@ -931,7 +953,7 @@ n:class .[#toc-n-class] {/foreach} ``` -さらに、最初の要素に`first` と`main` というクラスを持たせる必要があります。 +さらに、最初の要素にクラス`first`と`main`を持たせたい: ```latte {foreach $items as $item} @@ -939,7 +961,7 @@ n:class .[#toc-n-class] {/foreach} ``` -そして、すべての要素が`list-item` クラスを持つ必要があります。 +そして、すべての要素にクラス`list-item`を持たせたい: ```latte {foreach $items as $item} @@ -947,13 +969,13 @@ n:class .[#toc-n-class] {/foreach} ``` -驚くほどシンプルでしょう? +驚くほど簡単でしょう? -n:attr .[#toc-n-attr] ---------------------- +n:attr +------ -`n:attr` 属性は[n:class |#n:class] と同じエレガンスを持った任意の HTML 属性を生成することができます。 +`n:attr`属性は、[#n:class]と同じ優雅さで任意のHTML属性を生成できます。 ```latte {foreach $data as $item} @@ -961,7 +983,7 @@ n:attr .[#toc-n-attr] {/foreach} ``` -返された値に応じて、例えば次のように表示します。 +返された値に応じて、例えば次のように出力されます: ```latte @@ -972,26 +994,28 @@ n:attr .[#toc-n-attr] ``` -n:タグ .[#toc-n-tag] ------------------- +n:tag +----- -`n:tag` 属性は、HTML 要素の名前を動的に変更することができます。 +`n:tag`属性は、HTML要素の名前を動的に変更できます。 ```latte

                                                                                    {$title}

                                                                                    ``` -もし`$heading === null` 、その `

                                                                                    `タグは変更されずに表示されます。そうでない場合は、要素名を変数の値に変更するので、`$heading === 'h3'` の場合は、次のように書きます。 +`$heading === null`の場合、タグ`

                                                                                    `が変更なしで出力されます。それ以外の場合、要素の名前が変数の値に変更されます。したがって、`$heading === 'h3'`の場合、次のように出力されます: ```latte

                                                                                    ...

                                                                                    ``` +Latteは安全なテンプレートシステムであるため、新しいタグ名が有効であり、望ましくないまたは有害な値が含まれていないことをチェックします。 -n:ifcontent .[#toc-n-ifcontent] -------------------------------- -空の HTML 要素、つまり空白しか含まない要素が表示されないようにします。 +n:ifcontent +----------- + +空のHTML要素、つまり空白以外の何も含まない要素が出力されるのを防ぎます。 ```latte
                                                                                    @@ -999,7 +1023,7 @@ n:ifcontent .[#toc-n-ifcontent]
                                                                                    ``` -変数`$error` の値によって、印刷されます。 +変数`$error`の値に応じて出力されます: ```latte {* $error = '' *} @@ -1013,10 +1037,10 @@ n:ifcontent .[#toc-n-ifcontent] ``` -翻訳 .[#toc-translation] -====================== +翻訳 +======== -翻訳タグを動作させるには、[トランスレータを |develop#TranslatorExtension]設定する必要があります。また、翻訳用の [`translate` |filters#translate]フィルタを使用して翻訳することもできます。 +翻訳タグが機能するには、[トランスレータを有効化 |develop#TranslatorExtension]する必要があります。翻訳には[`translate` |filters#translate]フィルタを使用することもできます。 `{_...}` @@ -1025,30 +1049,30 @@ n:ifcontent .[#toc-n-ifcontent] 値を他の言語に翻訳します。 ```latte -{_'Basket'} +{_'カート'} {_$item} ``` -他のパラメータもトランスレータに渡すことができます。 +トランスレータには他のパラメータを渡すこともできます: ```latte -{_'Basket', domain: order} +{_'カート', domain: order} ``` `{translate}` ------------- -Překládá části šablony: +テンプレートの一部を翻訳します: ```latte -

                                                                                    {translate}Order{/translate}

                                                                                    +

                                                                                    {translate}注文{/translate}

                                                                                    -{translate domain: order}Lorem ipsum .. .{/translate} +{translate domain: order}Lorem ipsum ...{/translate} ``` -タグは、要素の内部を翻訳するために、[n:attributeと |syntax#n:attributes]記述することも可能です。 +タグは[n:属性 |syntax#n:属性]としても記述でき、要素の内部を翻訳します: ```latte -

                                                                                    Order

                                                                                    +

                                                                                    注文

                                                                                    ``` diff --git a/latte/ja/template-inheritance.texy b/latte/ja/template-inheritance.texy index c94887932e..4e6ce3f851 100644 --- a/latte/ja/template-inheritance.texy +++ b/latte/ja/template-inheritance.texy @@ -2,15 +2,15 @@ ************** .[perex] -テンプレートの再利用と継承の仕組みは、各テンプレートが固有の内容のみを含み、繰り返される要素や構造は再利用されるため、生産性を向上させることができます。ここでは、[レイアウト継承 |#layout inheritance]、[水平再利用 |#horizontal reuse]、[単位継承の |#unit inheritance]3つのコンセプトを紹介します。 +テンプレートの再利用と継承のメカニズムは、各テンプレートが独自のコンテンツのみを含み、繰り返される要素と構造が再利用されるため、生産性を向上させます。3つのコンセプトを紹介します:[レイアウト継承 |#Layout inheritance]、[水平再利用 |#Horizontal reuse]、および[ユニット継承 |#Unit inheritance]。 -Latteのテンプレート継承のコンセプトは、PHPのクラス継承に似ています。親テンプレート**を定義し、他の**子テンプレート**がそれを継承し、親テンプレートの一部をオーバーライドすることができます。これは、要素が共通の構造を持つ場合に非常に有効です。複雑に聞こえますか?心配しないでください、そんなことはありません。 +Latteのテンプレート継承のコンセプトは、PHPのクラス継承に似ています。**親テンプレート**を定義し、他の**子テンプレート**がそれを継承して、親テンプレートの一部を上書きできます。要素が共通の構造を共有する場合に非常にうまく機能します。複雑に聞こえますか?心配しないでください、とても簡単です。 -レイアウトの継承`{layout}` .{toc: Layout Inheritance} -============================================= +レイアウト継承 `{layout}` .{toc:Layout inheritance} +============================================ -レイアウト・テンプレートの継承について、まず例から見ていきましょう。これは親テンプレートで、例えば`layout.latte` と呼ぶことにします。これは HTML のスケルトン・ドキュメントを定義します。 +レイアウトテンプレートの継承、つまりレイアウトを例で見てみましょう。これは親テンプレートで、例えば `layout.latte` と呼び、HTMLドキュメントの骨格を定義します: ```latte @@ -30,9 +30,9 @@ Latteのテンプレート継承のコンセプトは、PHPのクラス継承に ``` -`{block}` タグは、子テンプレートが埋めることのできる 3 つのブロックを定義しています。ブロック・タグが行うのは、子テンプレートが同じ名前の独自のブロックを定義することで、テンプレートのこれらの部分をオーバーライドできることをテンプレート・エンジンに伝えるだけです。 +`{block}` タグは、子テンプレートが埋めることができる3つのブロックを定義します。blockタグは、子テンプレートが同じ名前の独自のブロックを定義することによってこの場所を上書きできることを通知するだけです。 -子テンプレートは次のようなものになります。 +子テンプレートは次のようになります: ```latte {layout 'layout.latte'} @@ -44,11 +44,11 @@ Latteのテンプレート継承のコンセプトは、PHPのクラス継承に {/block} ``` -ここで重要なのは、`{layout}` タグです。このタグは、このテンプレートが他のテンプレートを「拡張」していることをテンプレートエンジンに伝えます。Latteがこのテンプレートをレンダリングするとき、まず親を探します。この場合、`layout.latte` 。 +ここでのキーは `{layout}` タグです。これはLatteに、このテンプレートが別のテンプレートを「拡張」することを伝えます。Latteがこのテンプレートをレンダリングするとき、まず親テンプレート、この場合は `layout.latte` を見つけます。 -このとき、テンプレートエンジンは`layout.latte` にある3つのブロックタグに注目し、それらのブロックを子テンプレートの内容に置き換えます。子テンプレートでは *footer* ブロックが定義されていないため、代わりに親テンプレートのコンテンツが使用されることに注意してください。親テンプレートの`{block}` タグ内のコンテンツは、常にフォールバックとして使用されます。 +この時点で、Latteは `layout.latte` 内の3つのブロックタグに気づき、これらのブロックを子テンプレートのコンテンツで置き換えます。子テンプレートは *footer* ブロックを定義していないため、代わりに親テンプレートのコンテンツが使用されます。親テンプレートの `{block}` タグ内のコンテンツは、常にフォールバックとして使用されます。 -出力は次のようになります。 +出力は次のようになります: ```latte @@ -68,7 +68,7 @@ Latteのテンプレート継承のコンセプトは、PHPのクラス継承に ``` -子テンプレートでは、ブロックはトップレベルか他のブロックの内部にのみ配置できます。 +子テンプレートでは、ブロックは最上位レベルまたは別のブロック内にのみ配置できます。つまり: ```latte {block content} @@ -76,7 +76,7 @@ Latteのテンプレート継承のコンセプトは、PHPのクラス継承に {/block} ``` -また、周囲の`{if}` の条件が true か false かにかかわらず、ブロックは常に内側に作成されます。しかし、このテンプレートはブロックを定義しています。 +また、周囲の `{if}` 条件がtrueまたはfalseに評価されるかに関わらず、ブロックは常に作成されます。したがって、そうは見えなくても、このテンプレートはブロックを定義します。 ```latte {if false} @@ -86,7 +86,7 @@ Latteのテンプレート継承のコンセプトは、PHPのクラス継承に {/if} ``` -ブロックの中の出力を条件付きで表示させたい場合は、次のようにします。 +ブロック内の出力を条件付きで表示したい場合は、代わりに次を使用してください: ```latte {block head} @@ -96,7 +96,7 @@ Latteのテンプレート継承のコンセプトは、PHPのクラス継承に {/block} ``` -子テンプレート内のブロック外のデータは、レイアウトテンプレートがレンダリングされる前に実行されるため、`{var $foo = bar}` のような変数の定義や、継承チェーン全体へのデータの伝搬に使用することができます。 +子テンプレートのブロック外のスペースは、レイアウトテンプレートがレンダリングされる前に実行されるため、`{var $foo = bar}` のような変数を定義し、継承チェーン全体にデータを伝播するために使用できます: ```latte {layout 'layout.latte'} @@ -106,51 +106,50 @@ Latteのテンプレート継承のコンセプトは、PHPのクラス継承に ``` -マルチレベル継承 .[#toc-multilevel-inheritance] ---------------------------------------- -必要なだけ多くのレベルの継承を使用することができます。レイアウト継承の一般的な使用方法として、次のような3レベルの方法があります。 +多階層継承 +----- +必要なだけ多くの継承レベルを使用できます。レイアウト継承を使用する一般的な方法は、次の3レベルのアプローチです: -1) サイトの主要な外観を保持する`layout.latte` テンプレートを作成します。 -2) サイトの各セクションに対応する`layout-SECTIONNAME.latte` テンプレートを作成します。例えば、`layout-news.latte`,`layout-blog.latte` などです。これらのテンプレートはすべて`layout.latte` を拡張し、セクション固有のスタイルやデザインを含んでいます。 -3) ニュース記事やブログ記事など、ページの種類ごとに個別のテンプレートを作成します。これらのテンプレートは、適切なセクションテンプレートを拡張します。 +1) ウェブサイトの主要な外観の骨格を含む `layout.latte` テンプレートを作成します。 +2) ウェブサイトの各セクション用に `layout-SECTIONNAME.latte` テンプレートを作成します。例:`layout-news.latte`、`layout-blog.latte` など。これらのテンプレートはすべて `layout.latte` を拡張し、各セクション固有のスタイルとデザインを含みます。 +3) 各ページタイプ(例:ニュース記事やブログエントリ)用に個別のテンプレートを作成します。これらのテンプレートは、対応するセクションテンプレートを拡張します。 -動的なレイアウトの継承 .[#toc-dynamic-layout-inheritance] ----------------------------------------------- -親テンプレートの名前を変数や任意のPHP式で指定することで、動的な継承を行うことができます。 +動的継承 +---- +親テンプレートの名前として変数または任意のPHP式を使用できるため、継承は動的に動作できます: ```latte {layout $standalone ? 'minimum.latte' : 'layout.latte'} ``` -また、Latte APIを使用して、レイアウトテンプレートを[自動的に |develop#automatic-layout-lookup]選択することもできます。 +Latte APIを使用して、レイアウトテンプレートを[自動的に選択する |develop#自動レイアウト検索]こともできます。 -ヒント .[#toc-tips] ----------------- -ここでは、レイアウトの継承を行う際のTipsを紹介します。 +ヒント +--- +レイアウト継承を扱うためのヒントをいくつか紹介します: -- テンプレート内で`{layout}` を使用する場合、そのテンプレート内の最初のテンプレートタグでなければなりません。 +- テンプレートで `{layout}` を使用する場合、それはそのテンプレートの最初のテンプレートタグでなければなりません。 -- レイアウトは[自動的に検索 |develop#automatic-layout-lookup]することができます([プレゼンターの |application:templates#search-for-templates]ように)。この場合、テンプレートがレイアウトを持つべきではない場合は、`{layout none}` タグでその旨を表示します。 +- レイアウトは[自動的に検索される |develop#自動レイアウト検索]ことがあります(例えば[Presenter |application:templates#テンプレートの検索]のように)。その場合、テンプレートがレイアウトを持つべきでない場合は、`{layout none}` タグで示します。 -- タグ`{layout}` はエイリアス`{extends}` を持ちます。 +- `{layout}` タグにはエイリアス `{extends}` があります。 -- 拡張テンプレートのファイル名は、[テンプレート・ローダーによって |extending-latte#Loaders]異なります。 +- レイアウトファイル名は[loader |loaders]に依存します。 -- ブロックはいくつでも用意できます。子テンプレートはすべての親ブロックを定義する必要はないので、いくつかのブロックに合理的なデフォルトを記入し、後で必要なものだけを定義することができることを覚えておいてください。 +- 好きなだけブロックを持つことができます。子テンプレートはすべての親ブロックを定義する必要はないことを覚えておいてください。そのため、いくつかのブロックに適切なデフォルト値を入力し、後で必要なものだけを定義することができます。 -ブロック`{block}` .{toc: Blocks} -============================ +ブロック `{block}` .{toc: Blocks} +============================= .[note] -匿名も参照 [`{block}` |tags#block] +注釈:匿名 [`{block}` |tags#block] も参照してください。 -ブロックは、テンプレートの特定の部分がどのようにレンダリングされるかを変更する方法を提供しますが、その周りのロジックには何ら干渉しません。次の例で、ブロックがどのように機能するか、そしてより重要なのは、どのように機能しないかを説明します。 +ブロックは、テンプレートの特定の部分のレンダリング方法を変更する方法を提供しますが、その周りのロジックには干渉しません。次の例では、ブロックがどのように機能するか、またどのように機能しないかを示します: -```latte -{* parent.Latte *} +```latte .{file: parent.latte} {foreach $posts as $post} {block post}

                                                                                    {$post->title}

                                                                                    @@ -159,10 +158,9 @@ Latteのテンプレート継承のコンセプトは、PHPのクラス継承に {/foreach} ``` -このテンプレートをレンダリングした場合、ブロックタグがあってもなくても結果はまったく同じになります。ブロックは外部スコープの変数にアクセスすることができます。これは、子テンプレートでオーバーライド可能にするための手段に過ぎません。 +このテンプレートをレンダリングすると、結果は `{block}` タグがあってもなくてもまったく同じになります。ブロックは外部スコープの変数にアクセスできます。子テンプレートによって上書きされる可能性を与えるだけです: -```latte -{* child.Latte *} +```latte .{file: child.latte} {layout 'parent.Latte'} {block post} @@ -173,7 +171,7 @@ Latteのテンプレート継承のコンセプトは、PHPのクラス継承に {/block} ``` -さて、子テンプレートをレンダリングするとき、ループはベーステンプレート`parent.Latte` で定義されたブロックではなく、子テンプレート`child.Latte` で定義されたブロックを使用するようになります。 +これで、子テンプレートをレンダリングすると、ループは `parent.Latte` で定義されたブロックの代わりに `child.Latte` で定義された子テンプレートのブロックを使用します。実行されるテンプレートは次のものと同等です: ```latte {foreach $posts as $post} @@ -184,7 +182,7 @@ Latteのテンプレート継承のコンセプトは、PHPのクラス継承に {/foreach} ``` -しかし,名前付きブロックの中で新しい変数を作成したり,既存の変数の値を置き換えたりすると,その変更はブロックの中だけに表示されます. +ただし、名前付きブロック内で新しい変数を作成したり、既存の変数の値を置き換えたりした場合、変更はそのブロック内でのみ表示されます: ```latte {var $foo = 'foo'} @@ -197,13 +195,13 @@ foo: {$foo} // prints: foo bar: {$bar ?? 'not defined'} // prints: not defined ``` -ブロックの内容は、[フィルターによって |syntax#filters]変更することができます。次の例では、すべてのHTMLを削除し、タイトルケースを付けています。 +ブロックのコンテンツは[フィルタ |syntax#フィルタ]を使用して変更できます。次の例では、すべてのHTMLを削除し、大文字小文字を変更します: ```latte -{block title|stripHtml|capitalize}.. .{/block} +{block title|stripHtml|capitalize}...{/block} ``` -このタグは[n:attributeと |syntax#n:attributes]書くこともできる。 +タグは[n:属性 |syntax#n:属性]として書くこともできます: ```latte
                                                                                    @@ -212,10 +210,10 @@ bar: {$bar ?? 'not defined'} // prints: not defined ``` -ローカルブロック .[#toc-local-blocks] ------------------------------ +ローカルブロック +-------- -すべてのブロックは、同じ名前の親ブロックの内容を上書きします。ただし、ローカルブロックは例外です。これは、クラスのプライベート・メソッドのようなものです。ブロック名の一致により、2つ目のテンプレートで上書きされることを心配することなく、テンプレートを作成することができます。 +各ブロックは、同じ名前の親ブロックのコンテンツを上書きします – ローカルブロックを除きます。クラスでは、これはプライベートメソッドのようなものです。これにより、ブロック名の衝突によって他のテンプレートから上書きされることを心配せずにテンプレートを作成できます。 ```latte {block local helper} @@ -224,13 +222,13 @@ bar: {$bar ?? 'not defined'} // prints: not defined ``` -ブロックの印刷`{include}` .{toc: Printing Blocks} ------------------------------------------- +ブロックのレンダリング `{include}` .{toc: Rendering blocks} +------------------------------------------------ .[note] -参照 [`{include file}` |tags#include] +注釈:[`{include file}` |tags#include] も参照してください。 -ブロックを特定の場所に印刷するには、`{include blockname}` タグを使用します。 +特定の場所でブロックを出力するには、`{include blockname}` タグを使用します: ```latte {block title}{/block} @@ -238,32 +236,28 @@ bar: {$bar ?? 'not defined'} // prints: not defined

                                                                                    {include title}

                                                                                    ``` -また、別のテンプレートからブロックを表示することもできます。 +別のテンプレートからブロックを出力することもできます: ```latte {include footer from 'main.latte'} ``` -印刷されたブロックは、アクティブなコンテキストの変数にアクセスできません。ただし、ブロックがインクルードされているのと同じファイルに定義されている場合は例外です。ただし、グローバル変数へのアクセスは可能です。 +レンダリングされるブロックは、アクティブなコンテキストの変数にアクセスできません。ただし、ブロックが挿入されたのと同じファイルで定義されている場合は除きます。しかし、グローバル変数にはアクセスできます。 -この方法で変数を渡すことができます。 +このようにしてブロックに変数を渡すことができます: ```latte -{* since Latte 2.9 *} {include footer, foo: bar, id: 123} - -{* before Latte 2.9 *} -{include footer, foo => bar, id => 123} ``` -ブロック名として、変数または PHP の任意の式を使用することができます。この場合、変数の前に`block` というキーワードを追加し、コンパイル時にブロックであることがわかるようにします。 +ブロック名として変数または任意のPHP式を使用できます。その場合、コンパイル時にLatteがそれがブロックであり、[テンプレートの挿入 |tags#include](その名前も変数にある可能性がある)ではないことを知るために、変数の前にキーワード `block` を追加します: ```latte {var $name = footer} {include block $name} ``` -ブロックは、それ自身の内部に出力することもできます。これは、たとえば、ツリー構造をレンダリングするときに便利です。 +ブロックはそれ自体の中でレンダリングすることもでき、これは例えばツリー構造をレンダリングするのに役立ちます: ```latte {define menu, $items} @@ -281,19 +275,19 @@ bar: {$bar ?? 'not defined'} // prints: not defined {/define} ``` -`{include menu, ...}` の代わりに`{include this, ...}` と書くこともできます。ここで`this` は現在のブロックを意味します。 +`{include menu, ...}` の代わりに `{include this, ...}` と書くことができます。ここで `this` は現在のブロックを意味します。 -印刷されたコンテンツは、[フィルターによって |syntax#filters]変更することができます。次の例では、すべてのHTMLを削除し、タイトルケースを付けています。 +レンダリングされるブロックは[フィルタ |syntax#フィルタ]を使用して変更できます。次の例では、すべてのHTMLを削除し、大文字小文字を変更します: ```latte {include heading|stripHtml|capitalize} ``` -親ブロック .[#toc-parent-block] --------------------------- +親ブロック +----- -親テンプレートからブロックの内容を出力する必要がある場合は、`{include parent}` ステートメントを使用すると便利です。これは、親ブロックを完全にオーバーライドするのではなく、親ブロックの内容に追加したい場合に便利です。 +親テンプレートからブロックのコンテンツを出力する必要がある場合は、`{include parent}` を使用します。これは、親ブロックのコンテンツを完全に上書きするのではなく、補完したい場合に便利です。 ```latte {block footer} @@ -304,16 +298,16 @@ bar: {$bar ?? 'not defined'} // prints: not defined ``` -定義`{define}` .{toc: Definitions} --------------------------------- +定義 `{define}` .{toc: Definitions} +--------------------------------- -Latteにはブロックの他に「定義」というものがあります.これは、通常のプログラミング言語における関数に相当するものです。テンプレートの断片を再利用して、同じことを繰り返さないようにするために便利です。 +ブロックに加えて、Latteには「定義」もあります。通常のプログラミング言語では、これらを関数と比較します。テンプレートのフラグメントを再利用して繰り返しを避けるのに役立ちます。 -Latteは簡単に物事を進めようとするので、基本的にはブロックと同じで、**ブロックについて語られていることはすべて定義にも当てはまります**。ブロックと違うのは3点だけです。 +Latteは物事をシンプルにしようと努めているため、基本的に定義はブロックと同じであり、**ブロックについて言われていることはすべて定義にも当てはまります**。ブロックとの違いは次の点です: -1) 引数を受け付けることができる -2)[フィルタを |syntax#filters]持つことができない -3) タグで囲まれている`{define}` そして、これらのタグの中のコンテンツは、タグを含むまで出力に送られない。このため、どこでも作成することができます。 +1) `{define}` タグで囲まれている +2) `{include}` を介して挿入された場合にのみレンダリングされる +3) PHPの関数と同様にパラメータを定義できる ```latte {block foo}

                                                                                    Hello

                                                                                    {/block} @@ -326,10 +320,9 @@ Latteは簡単に物事を進めようとするので、基本的にはブロッ {* prints:

                                                                                    World

                                                                                    *} ``` -HTMLフォームのレンダリング方法を定義した汎用的なヘルパーテンプレートがあると想像してください。 +HTMLフォームを描画する方法の定義のコレクションを含むヘルパーテンプレートがあると想像してください。 -```latte -{* forms.latte *} +```latte .{file: forms.latte} {define input, $name, $value, $type = 'text'} {/define} @@ -339,38 +332,36 @@ HTMLフォームのレンダリング方法を定義した汎用的なヘルパ {/define} ``` -定義の引数はデフォルト値が指定されない限り、常にオプションで、デフォルト値は`null` です(ここでは`text` が`$type` のデフォルト値、Latte 2.9.1 以降で可能です)。Latte 2.7では、パラメータの型も宣言できます。`{define input, string $name, ...}`. - -定義は,アクティブなコンテキストの変数にはアクセスできませんが,グローバルな変数にはアクセスすることができます. +引数は常にオプションであり、デフォルト値が指定されていない場合はデフォルト値 `null` になります(ここでは `'text'` が `$type` のデフォルト値です)。パラメータの型も宣言できます:`{define input, string $name, ...}`。 -[ブロックと同じ |#Printing Blocks]ようにインクルードされます。 +定義を含むテンプレートは [`{import}` |#Horizontal reuse] を使用してロードします。定義自体は[ブロックと同じ方法でレンダリングされます |#Rendering blocks]: ```latte

                                                                                    {include input, 'password', null, 'password'}

                                                                                    {include textarea, 'comment'}

                                                                                    ``` +定義はアクティブなコンテキストの変数にアクセスできませんが、グローバル変数にはアクセスできます。 -動的なブロック名 .[#toc-dynamic-block-names] ------------------------------------- -Latte では、ブロック名を任意の PHP 式にすることができるため、非常に柔軟にブロックを定義することができます。この例では、`hi-Peter`,`hi-John`,`hi-Mary` という名前の3つのブロックを定義しています。 +動的ブロック名 +------- -```latte -{* parent.latte *} +Latteはブロックの定義において大きな柔軟性を提供します。なぜなら、ブロック名は任意のPHP式にすることができるからです。この例では、`hi-Peter`、`hi-John`、`hi-Mary` という名前の3つのブロックを定義します: + +```latte .{file: parent.latte} {foreach [Peter, John, Mary] as $name} - {block "hi-$name"}Hi, I am {$name} .{/block} + {block "hi-$name"}Hi, I am {$name}.{/block} {/foreach} ``` -例えば、子テンプレートの中で1つのブロックだけを再定義することができます。 +子テンプレートでは、例えば1つのブロックだけを再定義できます: -```latte -{* child.latte *} -{block hi-John}Hello. I am {$name} .{/block} +```latte .{file: child.latte} +{block hi-John}Hello. I am {$name}.{/block} ``` -ですから、出力は次のようになります。 +したがって、出力は次のようになります: ```latte Hi, I am Peter. @@ -379,13 +370,13 @@ Hi, I am Mary. ``` -ブロックの存在を確認する`{ifset}` .{toc: Checking Block Existence} ------------------------------------------------------- +ブロック存在チェック `{ifset}` .{toc: Checking block existence} +----------------------------------------------------- .[note] -参照 [`{ifset $var}` |tags#ifset-elseifset] +注釈:[`{ifset $var}` |tags#ifset elseifset] も参照してください。 -`{ifset blockname}` テストを使用して、ブロック (または複数のブロック) が現在のコンテキストに存在するかどうかを確認します。 +`{ifset blockname}` テストを使用して、現在のコンテキストにブロック(または複数のブロック)が存在するかどうかを確認します: ```latte {ifset footer} @@ -397,7 +388,7 @@ Hi, I am Mary. {/ifset} ``` -ブロック名として、変数あるいは PHP の任意の式を使用することができます。この場合、変数の前に`block` というキーワードを追加して、チェック対象が[変数 |tags#ifset-elseifset]でないことを明確にします。 +ブロック名として変数または任意のPHP式を使用できます。その場合、それが[変数 |tags#ifset elseifset]の存在テストではないことを明確にするために、変数の前にキーワード `block` を追加します: ```latte {ifset block $name} @@ -405,71 +396,69 @@ Hi, I am Mary. {/ifset} ``` +[`hasBlock()` |functions#hasBlock] 関数もブロックの存在を確認します: -ヒント .[#toc-tips] ----------------- -ブロックを使った作業のコツを紹介します。 - -- 最後のトップレベルブロックには終了タグは必要ありません(ブロックはドキュメントの終わりで終了します)。これによって、1つの主ブロックを持つ子テンプレートの記述が簡単になります。 +```latte +{if hasBlock(header) || hasBlock(footer)} + ... +{/if} +``` -- 読みやすくするために、`{/block}` タグに`{/block footer}` のような名前を付けることができます。ただし、この名前はブロック名と一致させる必要があります。大きなテンプレートでは、このテクニックを使うと、どのブロックタグが閉じられているのかがわかりやすくなります。 -- 同じテンプレートに同じ名前の複数のブロックタグを直接定義することはできません。しかし、[動的なブロック名を |#dynamic block names]使えば、これを実現することができます。 +ヒント +--- +ブロックを扱うためのヒントをいくつか紹介します: --[n:attributesを使って |syntax#n:attributes]、次のようなブロックを定義することができます。 `

                                                                                    Welcome to my awesome homepage

                                                                                    ` +- 最上位レベルの最後のブロックは、終了タグを持つ必要はありません(ブロックはドキュメントの終わりで終了します)。これにより、単一のプライマリブロックを含む子テンプレートの記述が簡素化されます。 -- ブロックは、出力に[フィルタを |syntax#filters]適用するためだけに名前なしで使用することもできます。`{block|strip} hello {/block}` +- 読みやすさを向上させるために、`{/block}` タグにブロック名を指定できます。例:`{/block footer}`。ただし、名前はブロック名と一致する必要があります。大きなテンプレートでは、このテクニックはどのブロックタグが閉じられているかを判断するのに役立ちます。 +- 同じテンプレート内で同じ名前の複数のブロックタグを直接定義することはできません。ただし、これは[#動的ブロック名]を使用して実現できます。 -水平方向の再利用`{import}` .{toc: Horizontal Reuse} -=========================================== +- `

                                                                                    Welcome to my awesome homepage

                                                                                    ` のようにブロックを定義するために[n:属性 |syntax#n:属性]を使用できます。 -水平方向の再利用はLatteの3つ目の再利用性・継承の仕組みです。他のテンプレートからブロックを読み込むことができるようになります。ヘルパー関数やtraitでPHPファイルを作成するのと同じようなものです。 +- ブロックは、[フィルタ |syntax#フィルタ]を適用するためだけに名前なしで使用することもできます:`{block|strip} hello {/block}` -レイアウトテンプレート継承はLatteの最も強力な機能の一つですが、単一継承に限定されており、テンプレートは他のテンプレートを1つだけ拡張することができます。この制限により、テンプレート継承は理解しやすく、デバッグしやすくなっています。 -```latte -{layout 'layout.latte'} +水平再利用 `{import}` .{toc: Horizontal reuse} +========================================= -{block title}.. .{/block} -{block content}.. .{/block} -``` +水平再利用は、Latteにおける再利用と継承のための3番目のメカニズムです。これにより、他のテンプレートからブロックをロードできます。これは、PHPでヘルパー関数のファイルを作成し、それを `require` を使用してロードするのと似ています。 -水平方向の再利用は、多重継承と同じ目的を達成するための方法ですが、それに伴う複雑さは伴わないのです。 +テンプレートのレイアウト継承はLatteの最も強力な機能の1つですが、単純な継承に限定されています – テンプレートは他の1つのテンプレートしか拡張できません。水平再利用は、多重継承を実現する方法です。 -```latte -{layout 'layout.latte'} +ブロック定義を含むファイルがあるとします: -{import 'blocks.latte'} +```latte .{file: blocks.latte} +{block sidebar}...{/block} -{block title}.. .{/block} -{block content}.. .{/block} +{block menu}...{/block} ``` -`{import}` ステートメントは、`blocks.latte` で定義されたすべてのブロックと[定義を |#definitions]現在のテンプレートにインポートするように Latte に指示します。 +`{import}` コマンドを使用して、`blocks.latte` で定義されたすべてのブロックと[定義 |#Definitions]を別のテンプレートにインポートします: -```latte -{* blocks.latte *} +```latte .{file: child.latte} +{import 'blocks.latte'} -{block sidebar}.. .{/block} +{* これで sidebar と menu ブロックを使用できます *} ``` -この例では、`{import}` ステートメントが`sidebar` ブロックをメインテンプレートにインポートしています。 +親テンプレートでブロックをインポートする場合(つまり、`layout.latte` で `{import}` を使用する場合)、ブロックはすべての子テンプレートでも利用可能になり、これは非常に実用的です。 -インポートされたテンプレートは、他のテンプレートを[拡張しては |#Layout Inheritance]ならず、そのボディは空でなければなりま せん。ただし、インポートされたテンプレートは、他のテンプレートをインポートすることができます。 +インポートされることを意図したテンプレート(例:`blocks.latte`)は、別のテンプレートを[拡張 |#Layout inheritance]してはなりません。つまり、`{layout}` を使用してはなりません。ただし、他のテンプレートをインポートすることはできます。 -`{import}` タグは、`{layout}` の後の最初のテンプレート・タグでなければなりません。テンプレート名には、任意の PHP 式を指定することができます。 +`{import}` タグは、`{layout}` の後のテンプレートの最初のタグである必要があります。テンプレート名は任意のPHP式にすることができます: ```latte {import $ajax ? 'ajax.latte' : 'not-ajax.latte'} ``` -`{import}` ステートメントは、任意のテンプレートで好きなだけ使用することができます。インポートされた2つのテンプレートが同じブロックを定義している場合、先に定義されたものが優先されます。ただし、メイン・テンプレートが最も優先され、インポートされたブロックを上書きすることができます。 +テンプレート内で好きなだけ `{import}` コマンドを使用できます。2つのインポートされたテンプレートが同じブロックを定義する場合、最初のものが優先されます。ただし、メインテンプレートが最も高い優先度を持ち、インポートされたブロックを上書きできます。 -上書きされたブロックはすべて、[親ブロックとして |#parent block]挿入することで、徐々に含めることができます。 +上書きされたブロックのコンテンツは、[#親ブロック]を挿入するのと同じ方法でブロックを挿入することによって保持できます: ```latte -{layout 'base.latte'} +{layout 'layout.latte'} {import 'blocks.latte'} @@ -477,21 +466,21 @@ Hi, I am Mary. {include parent} {/block} -{block title}.. .{/block} -{block content}.. .{/block} +{block title}...{/block} +{block content}...{/block} ``` -この例では、`{include parent}` は`blocks.latte` テンプレートから`sidebar` ブロックを正しく呼び出します。 +この例では、`{include parent}` は `blocks.latte` テンプレートから `sidebar` ブロックを呼び出します。 -ユニットの継承`{embed}` .{toc: Unit Inheritance}{data-version:2.9} -=========================================================== +ユニット継承 `{embed}` .{toc: Unit inheritance} +========================================= -ユニット継承は、レイアウト継承の考え方をコンテンツ・フラグメントのレベルまで拡張したものです。レイアウト継承が "ドキュメントスケルトン "で動作し、子テンプレートによって命を吹き込まれるのに対して、ユニット継承はコンテンツの小さなユニットのスケルトンを作成し、好きな場所で再利用することが可能です。 +ユニット継承は、レイアウト継承の考え方をコンテンツフラグメントのレベルに拡張します。レイアウト継承が子テンプレートによって活気づけられる「ドキュメントの骨格」を扱うのに対し、ユニット継承はより小さなコンテンツユニットの骨格を作成し、好きな場所で再利用することを可能にします。 -ユニット継承では、`{embed}` タグがキーとなります。このタグは、`{include}` と`{layout}` の動作を組み合わせたもので、`{include}` のように、他のテンプレートやブロックの内容を取り込んだり、変数を渡したりすることができます。また、`{layout}` のように、インクルードされたテンプレートの内部で定義されたブロックをオーバーライドすることも可能です。 +ユニット継承では、`{embed}` タグがキーです。これは `{include}` と `{layout}` の動作を組み合わせます。これにより、別のテンプレートまたはブロックのコンテンツを挿入し、`{include}` の場合と同様にオプションで変数を渡すことができます。また、`{layout}` を使用する場合と同様に、埋め込まれたテンプレート内で定義された任意のブロックを上書きすることもできます。 -例えば、折りたたみ可能なアコーディオン要素を使用することにします。テンプレート`collapsible.latte` にある要素のスケルトンを見てみましょう。 +例えば、アコーディオン要素を使用します。`collapsible.latte` テンプレートに保存されている要素の骨格を見てみましょう: ```latte
                                                                                    @@ -505,9 +494,9 @@ Hi, I am Mary.
                                                                                    ``` -`{block}` タグは、子テンプレートが埋めることのできる2つのブロックを定義しています。そう、レイアウト継承のテンプレートの親テンプレートの場合と同じです。また、`$modifierClass` 変数がありますね。 +`{block}` タグは、子テンプレートが埋めることができる2つのブロックを定義します。はい、レイアウト継承の親テンプレートの場合と同様です。変数 `$modifierClass` も表示されます。 -テンプレートで私たちの要素を使用してみましょう。ここで、`{embed}` 。これは超強力なキットで、要素のテンプレート・コンテンツをインクルードしたり、変数を追加したり、カスタムHTMLのブロックを追加したりと、あらゆることができるようになります。 +テンプレートで要素を使用しましょう。ここで `{embed}` が登場します。これは非常に強力なタグであり、要素テンプレートのコンテンツを挿入し、それに変数を追加し、独自のHTMLを持つブロックを追加するなど、すべてのことを可能にします: ```latte {embed 'collapsible.latte', modifierClass: my-style} @@ -522,7 +511,7 @@ Hi, I am Mary. {/embed} ``` -出力は次のようになります。 +出力は次のようになります: ```latte
                                                                                    @@ -537,7 +526,7 @@ Hi, I am Mary.
                                                                                    ``` -embedタグ内のブロックは、他のブロックとは独立した別レイヤーを形成しています。したがって、embedタグの外側にあるブロックと同じ名前を持つことができ、何ら影響を受けません。`{embed}` タグ内に[include |#Printing Blocks]タグを使用すると、ここで作成したブロック、埋め込みテンプレートからのブロック([ローカル |#Local Blocks]ではないもの)、メインテンプレートからのブロック(ローカルなもの)を挿入することができます。また、他のファイルから[ブロックをインポート |#Horizontal Reuse]することもできます。 +埋め込みタグ内のブロックは、他のブロックから独立した別のレイヤーを形成します。したがって、埋め込み外のブロックと同じ名前を持つことができ、影響を受けません。`{embed}` タグ内で [include |#Rendering blocks] タグを使用すると、ここで作成されたブロック、埋め込みテンプレートからのブロック([*ローカル* |#ローカルブロック]ではないもの)、および逆に*ローカル*であるメインテンプレートからのブロックを挿入できます。他のファイルから[ブロックをインポート |#Horizontal reuse]することもできます: ```latte {block outer}…{/block} @@ -558,9 +547,9 @@ embedタグ内のブロックは、他のブロックとは独立した別レイ {/embed} ``` -埋め込みテンプレートは、アクティブなコンテキストの変数にアクセスできませんが、グローバル変数にはアクセスできます。 +埋め込みテンプレートはアクティブなコンテキストの変数にアクセスできませんが、グローバル変数にはアクセスできます。 -`{embed}` では、テンプレートだけでなく、他のブロックも挿入することができますので、先ほどの例は次のように記述することができます。 .{data-version:2.10} +`{embed}` を使用すると、テンプレートだけでなく他のブロックも埋め込むことができるため、前の例はこのように書くことができます: ```latte {define collapsible} @@ -581,23 +570,23 @@ embedタグ内のブロックは、他のブロックとは独立した別レイ {/embed} ``` -`{embed}` に式を渡して、それがブロック名なのかファイル名なのかはっきりしない場合は、キーワード`block` または`file` を追加してください。 +`{embed}` に式を渡し、それがブロック名なのかファイル名なのかが不明な場合は、キーワード `block` または `file` を追加します: ```latte {embed block $name} ... {/embed} ``` -使用例 .[#toc-use-cases] -===================== +ユースケース +====== -Latteには様々な種類の継承やコードの再利用があります。より明確にするために、主な概念をまとめてみましょう。 +Latteには、さまざまな種類の継承とコードの再利用があります。より明確にするために、主要なコンセプトを要約しましょう: `{include template}` -------------------- -**使用例:**`header.latte` &`footer.latte` を`layout.latte` の中で使用する場合 . +**ユースケース**: `layout.latte` 内で `header.latte` と `footer.latte` を使用する。 `header.latte` @@ -630,7 +619,7 @@ Latteには様々な種類の継承やコードの再利用があります。よ `{layout}` ---------- -**ユースケース**:`layout.latte` を`homepage.latte` &`about.latte` の内部に拡張する. +**ユースケース**: `homepage.latte` と `about.latte` 内で `layout.latte` を拡張する。 `layout.latte` @@ -666,7 +655,7 @@ Latteには様々な種類の継承やコードの再利用があります。よ `{import}` ---------- -**使用例**:`sidebar.latte` in`single.product.latte` &`single.service.latte`. +**ユースケース**: `single.product.latte` と `single.service.latte` 内の `sidebar.latte`。 `sidebar.latte` @@ -698,7 +687,7 @@ Latteには様々な種類の継承やコードの再利用があります。よ `{define}` ---------- -**使用例**: いくつかの変数を取得し、いくつかのマークアップを出力する関数です。 +**ユースケース**: 変数を渡し、何かをレンダリングする関数。 `form.latte` @@ -724,7 +713,7 @@ Latteには様々な種類の継承やコードの再利用があります。よ `{embed}` --------- -**ユースケース**: `pagination.latte` を`product.table.latte` &`service.table.latte` に埋め込む。 +**ユースケース**: `product.table.latte` と `service.table.latte` に `pagination.latte` を埋め込む。 `pagination.latte` diff --git a/latte/ja/type-system.texy b/latte/ja/type-system.texy index d60c49f52d..0f93ddc900 100644 --- a/latte/ja/type-system.texy +++ b/latte/ja/type-system.texy @@ -1,27 +1,27 @@ -タイプシステム -******* +型システム +***** -
                                                                                    +
                                                                                    -堅牢なアプリケーションを開発するためには、型システムが重要な役割を果たします。Latteはテンプレートに型サポートを導入しています。各変数がどのようなデータ型、オブジェクト型であるかを把握することで、より効率的な開発が可能になります。 +型システムは堅牢なアプリケーション開発の鍵となります。Latteはテンプレートにも型サポートを導入します。各変数にどのようなデータ型またはオブジェクト型が含まれているかがわかるため、以下のことが可能になります。 -- IDEが正しくオートコンプリートを行う([統合とプラグインを |recipes#Editors and IDE]参照) -- 静的解析によるエラーの検出 +- IDEが正しく補完する([統合 |recipes#エディタとIDE]を参照) +- 静的解析でエラーを検出する -の2点が、開発の質と利便性を大きく向上させます。 +これらは両方とも、開発の品質と快適さを大幅に向上させます。
                                                                                    .[note] -申告型は情報提供であり、ラテは現時点ではチェックしていない。 +宣言された型は情報提供のためであり、Latteは現時点ではそれらをチェックしません。 -型はどのように使うのでしょうか?渡されたパラメータを表すテンプレートクラス,例えば`CatalogTemplateParameters` を作成します. +型の使用を開始するにはどうすればよいですか?渡されるパラメータ、その型、そして場合によってはデフォルト値を表すテンプレートクラス、例えば `CatalogTemplateParameters` を作成します。 ```php class CatalogTemplateParameters { public function __construct( - public string $langs, + public string $lang, /** @var ProductEntity[] */ public array $products, public Address $address, @@ -35,19 +35,16 @@ $latte->render('template.latte', new CatalogTemplateParameters( )); ``` -そして、テンプレートの最初に、完全なクラス名(名前空間を含む)を持つ`{templateType}` タグを挿入します。これにより、テンプレート内に変数`$langs` と`$products` が存在し、対応する型があることが定義されます。 -また、ローカル変数の型を指定するために [`{var}` |tags#var-default]`{varType}` および [`{define}` |template-inheritance#definitions]. +次に、テンプレートの先頭に、クラスの完全な名前(名前空間を含む)を持つ `{templateType}` タグを挿入します。これにより、テンプレート内に変数 `$lang` と `$products` が、対応する型とともに定義されます。ローカル変数の型は、[`{var}` |tags#var default]、`{varType}`、[`{define}` |template-inheritance#Definitions] タグを使用して指定できます。 -これで、IDEが正しくオートコンプリートできるようになりました。 +その時点から、IDEは正しく補完できるようになります。 -作業を保存するには?テンプレートクラスや`{varType}` タグをできるだけ簡単に書くにはどうしたらいいでしょうか。それらを生成させましょう。 -`{templatePrint}` と`{varPrint}` というタグはまさにそれを実現するものです。 -これらのタグをテンプレートに配置すると、通常のレンダリングではなく、クラスやテンプレートのコードが表示されます。そして、そのコードを選択し、プロジェクトにコピーするだけです。 +手間を省くにはどうすればよいですか?テンプレートパラメータを持つクラスや `{varType}` タグをできるだけ簡単に書くにはどうすればよいですか?それらを生成させましょう。そのために `{templatePrint}` と `{varPrint}` という2つのタグが存在します。これらをテンプレートに配置すると、通常のレンダリングの代わりに、クラスコードの提案または `{varType}` タグのリストが表示されます。その後、コードをワンクリックで選択し、プロジェクトにコピーするだけです。 `{templateType}` ---------------- -テンプレートに渡されるパラメータの種類は、class.Template.Template()を使って宣言します。 +テンプレートに渡されるパラメータの型は、クラスを使用して宣言します。 ```latte {templateType MyApp\CatalogTemplateParameters} @@ -56,7 +53,7 @@ $latte->render('template.latte', new CatalogTemplateParameters( `{varType}` ----------- -変数の型はどのように宣言するのですか?そのためには、既存の変数に対して、`{varType}` というタグを使うか、あるいは [`{var}` |tags#var-default]: +変数の型を宣言するにはどうすればよいですか?そのために、既存の変数には `{varType}` タグ、または [`{var}` |tags#var default] タグを使用します。 ```latte {varType Nette\Security\User $user} @@ -66,11 +63,11 @@ $latte->render('template.latte', new CatalogTemplateParameters( `{templatePrint}` ----------------- -このクラスは、`{templatePrint}` タグを使用して生成することもできます。これをテンプレートの先頭に配置すると、通常のテンプレートではなく、クラスのコードが表示されます。あとは、そのコードを選択し、プロジェクトにコピーするだけです。 +`{templatePrint}` タグを使用してクラスを生成させることもできます。これをテンプレートの先頭に配置すると、通常のレンダリングの代わりにクラスの提案が表示されます。その後、コードをワンクリックで選択し、プロジェクトにコピーするだけです。 `{varPrint}` ------------ -`{varPrint}` タグは、あなたの時間を節約してくれます。テンプレートに配置すると、通常のレンダリングではなく、`{varType}` タグのリストが表示されます。あとは、コードを選択して、テンプレートにコピーするだけです。 +`{varPrint}` タグは、記述の手間を省きます。これをテンプレートに配置すると、通常のレンダリングの代わりに、ローカル変数用の `{varType}` タグの提案が表示されます。その後、コードをワンクリックで選択し、テンプレートにコピーするだけです。 -`{varPrint}` は、テンプレートのパラメーターでないローカル変数をリストアップします。すべての変数をリストアップしたい場合は、`{varPrint all}` を使用します。 +`{varPrint}` 自体は、テンプレートパラメータではないローカル変数のみを出力します。すべての変数を出力したい場合は、`{varPrint all}` を使用してください。 diff --git a/latte/ja/why-use.texy b/latte/ja/why-use.texy new file mode 100644 index 0000000000..1a6888d734 --- /dev/null +++ b/latte/ja/why-use.texy @@ -0,0 +1,80 @@ +なぜテンプレートを使用するのですか? +****************** + + +なぜPHPでテンプレートエンジンを使用する必要があるのですか? +------------------------------- + +PHP自体がテンプレート言語であるのに、なぜPHPでテンプレートエンジンを使用するのですか? + +まず、興味深い紆余曲折に満ちたこの言語の歴史を簡単に振り返ってみましょう。HTMLページを生成するために使用された最初のプログラミング言語の1つはC言語でした。しかし、すぐにこの目的での使用は非現実的であることが判明しました。そこでRasmus LerdorfはPHPを作成し、バックエンドでC言語を使用して動的HTMLを簡単に生成できるようにしました。したがって、PHPは元々テンプレート言語として設計されましたが、時間の経過とともに他の機能を取得し、本格的なプログラミング言語になりました。 + +それでも、テンプレート言語としても機能します。PHPファイルには、`` などを使用して変数を出力するHTMLページを記述できます。 + +PHPの歴史の初期に、Smartyテンプレートエンジンが登場しました。その目的は、外観(HTML/CSS)をアプリケーションロジックから厳密に分離することでした。つまり、開発者がテンプレートからデータベースクエリを実行できないようにするなど、意図的にPHP自体よりも制限された言語を提供しました。一方で、プロジェクトに追加の依存関係をもたらし、複雑さを増し、プログラマーは新しいSmarty言語を学ぶ必要がありました。そのような利点は議論の余地があり、テンプレートには引き続きプレーンなPHPが使用されました。 + +時間の経過とともに、テンプレートエンジンは役立つようになりました。[継承 |template-inheritance]、[サンドボックスモード |sandbox]、およびその他の多くの機能の概念を導入し、プレーンPHPと比較してテンプレート作成を大幅に簡素化しました。セキュリティのトピック、[XSS のような脆弱性 |safety-first]の存在、および[エスケープ |#エスケープとは何ですか]の必要性が前面に出てきました。テンプレートエンジンは自動エスケープを導入し、プログラマーがそれを忘れて重大なセキュリティホールが発生するリスクを排除しました(これにはいくつかの落とし穴があることをすぐに示します)。 + +今日、テンプレートエンジンの利点は、その導入に関連するコストを大幅に上回っています。したがって、それらを使用することは理にかなっています。 + + +なぜLatteはTwigやBladeよりも優れているのですか? +------------------------------- + +いくつかの理由があります – いくつかは快適で、その他は根本的に役立ちます。Latteは快適さと有用性を兼ね備えています。 + +*まず快適な点:* Latteは[PHP と同じ構文 |syntax#LatteはPHPを理解します]を持っています。タグの表記法だけが異なり、`` の代わりに、より短い `{` と `}` を好みます。これは、新しい言語を学ぶ必要がないことを意味します。トレーニングコストは最小限です。そして最も重要なことは、開発中にPHP言語とテンプレート言語の間で常に「切り替える」必要がないことです。なぜなら、両方とも同じだからです。Python言語を使用するTwigテンプレートとは対照的に、プログラマーは2つの異なる言語を切り替える必要があります。 + +*そして今、非常に役立つ理由*:Twig、Blade、Smartyなどのすべてのテンプレートエンジンは、進化の過程で自動[エスケープ |#エスケープとは何ですか]の形でXSSに対する保護を導入しました。より正確には、`htmlspecialchars()` 関数の自動呼び出しです。しかし、Latteの作成者は、これがまったく正しい解決策ではないことに気づきました。なぜなら、ドキュメントのさまざまな場所で、さまざまな方法でエスケープする必要があるからです。単純な自動エスケープは、誤った安心感を生み出すため、危険な機能です。 + +自動エスケープが機能的で信頼できるものになるためには、データがドキュメントのどの場所に出力されるか(これらをコンテキストと呼びます)を認識し、それに応じてエスケープ関数を選択する必要があります。つまり、[コンテキスト認識型 |safety-first#コンテキストに応じたエスケープ]である必要があります。そして、これこそがLatteができることです。HTMLを理解しています。テンプレートを単なる文字列として認識するのではなく、タグ、属性などが何であるかを理解しています。そのため、HTMLテキスト内、HTMLタグ内、JavaScript内などで異なる方法でエスケープします。 + +Latteは、コンテキスト認識型エスケープを備えたPHPで最初で唯一のテンプレートエンジンです。したがって、唯一の本当に安全なテンプレートエンジンです。 + +*そしてもう1つの快適な理由*:LatteはHTMLを理解しているため、他の非常に快適な機能を提供します。例えば、[n:属性 |syntax#n:属性]です。または、[リンクをチェックする |safety-first#リンクのチェック]機能です。その他多数。 + + +エスケープとは何ですか? +------------ + +エスケープとは、ある文字列を別の文字列に挿入する際に、特殊な意味を持つ文字を対応するシーケンスに置き換えるプロセスであり、望ましくない現象やエラーを防ぐためのものです。例えば、HTMLテキストに文字列を挿入する場合、`<` 文字はタグの開始を示すため特別な意味を持ちます。これを対応するシーケンス、つまりHTMLエンティティ `<` に置き換えます。これにより、ブラウザは `<` 記号を正しく表示します。 + +PHPでコードを直接記述する際のエスケープの簡単な例は、文字列に引用符を挿入する場合で、その前にバックスラッシュを記述します。 + +エスケープについては、[XSS から身を守る方法 |safety-first#XSSからどのように防御しますか] の章で詳しく説明します。 + + +Latteでテンプレートからデータベースクエリを実行できますか? +-------------------------------- + +テンプレートでは、プログラマーが渡したオブジェクトを操作できます。したがって、プログラマーが望むなら、データベースオブジェクトをテンプレートに渡し、それに対してクエリを実行できます。そのような意図がある場合、それを妨げる理由はありません。 + +クライアントや外部のコーダーにテンプレートを編集する機能を提供したい場合は、状況が異なります。その場合、彼らがデータベースにアクセスできることを絶対に望まないでしょう。もちろん、データベースオブジェクトをテンプレートに渡すことはありませんが、別のオブジェクトを介してアクセスできる場合はどうでしょうか?解決策は[サンドボックスモード |sandbox]であり、テンプレートでどのメソッドを呼び出すことができるかを定義できます。これにより、セキュリティ侵害を心配する必要はありません。 + + +Latte、Twig、Bladeなどのテンプレートエンジンの主な違いは何ですか? +---------------------------------------- + +Latte、Twig、Bladeテンプレートエンジンの違いは、主に構文、セキュリティ、およびフレームワークへの統合方法にあります + +- Latte:PHP言語の構文を使用しているため、学習と使用が容易です。XSS攻撃に対する最高レベルの保護を提供します。 +- Twig:PHPとはかなり異なるPython言語の構文を使用します。コンテキストを区別せずにエスケープします。Symfonyフレームワークによく統合されています。 +- Blade:PHPと独自の構文の混合を使用します。コンテキストを区別せずにエスケープします。Laravelの機能とエコシステムに密接に統合されています。 + + +企業にとってテンプレートエンジンを使用する価値はありますか? +------------------------------ + +まず、トレーニング、使用、および全体的な利点に関連するコストは、システムによって大幅に異なります。LatteテンプレートエンジンはPHP構文を使用しているため、この言語に既に精通しているプログラマーの学習を大幅に簡素化します。通常、プログラマーがLatteに十分に慣れるまでには数時間かかります。したがって、トレーニングコストが削減されます。同時に、テクノロジーの習得を加速し、とりわけ日常的な使用における効率を高めます。 + +さらに、Latteは独自のコンテキスト認識型エスケープ技術により、XSS脆弱性に対する高レベルの保護を提供します。この保護は、Webアプリケーションのセキュリティを確保し、ユーザーや企業データを危険にさらす可能性のある攻撃のリスクを最小限に抑えるために不可欠です。Webアプリケーションのセキュリティ保護は、企業の評判を維持するためにも重要です。セキュリティ問題は、顧客からの信頼を失い、市場での企業の評判を損なう可能性があります。 + +Latteを使用すると、開発と保守の両方が容易になるため、アプリケーションの全体的な開発および保守コストも削減されます。したがって、テンプレートエンジンを使用することは間違いなく価値があります。 + + +LatteはWebアプリケーションのパフォーマンスに影響しますか? +--------------------------------- + +Latteテンプレートは高速に処理されますが、この側面は実際には重要ではありません。その理由は、ファイルの解析は最初の表示時に一度だけ行われるためです。その後、PHPコードにコンパイルされ、ディスクに保存され、再コンパイルを実行する必要なく、後続の各リクエストで実行されます。 + +これは本番環境での動作方法です。開発中、Latteテンプレートはコンテンツが変更されるたびに再コンパイルされ、開発者は常に最新バージョンを確認できます。 diff --git a/latte/pl/@home.texy b/latte/pl/@home.texy index dc3331b93c..60a2555a37 100644 --- a/latte/pl/@home.texy +++ b/latte/pl/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Latte - najbezpieczniejsze i naprawdę intuicyjne szablony PHP}} -{{description: Latte jest najbezpieczniejszym systemem templatkowania dla PHP. Zapobiega to wielu lukom w zabezpieczeniach. Docenisz jego intuicyjną składnię i docenisz wiele przydatnych tweaków.}} +{{maintitle: Latte – najbezpieczniejsze & naprawdę intuicyjne szablony dla PHP}} +{{description: Latte to najbezpieczniejszy system szablonów dla PHP. Zapobiega wielu lukom bezpieczeństwa. Docenisz jego intuicyjną składnię i wiele przydatnych funkcji.}} diff --git a/latte/pl/@left-menu.texy b/latte/pl/@left-menu.texy index 961cfb8509..78eaf48a57 100644 --- a/latte/pl/@left-menu.texy +++ b/latte/pl/@left-menu.texy @@ -1,24 +1,24 @@ -- [Zaczynając od Latte |guide] -- Koncepcje +- [Zaczynamy z Latte |guide] +- [Dlaczego używać szablonów? |why-use] +- Koncepty ⚗️ - [Bezpieczeństwo przede wszystkim |safety-first] - [Dziedziczenie szablonów |Template Inheritance] - - [System typu |type-system] - - [Piaskownica |Sandbox] + - [System typów |type-system] + - [Sandbox |Sandbox] -- Dla projektantów +- Dla projektantów 🎨 - [Składnia |syntax] - [Tagi |tags] - [Filtry |filters] - [Funkcje |functions] - - [Porady i wskazówki |recipes] + - [Wskazówki i triki |recipes] -- Dla deweloperów - - [Praktyki deweloperskie |develop] - - [Expanding Latte |extending-latte] - - [Tworzenie rozszerzenia |creating-extension] +- Dla programistów 🧮 + - [Praktyki programistyczne |develop] + - [Rozszerzanie Latte |extending-latte] -- [Samouczki i procedury |cookbook/@home] - - [Migracja z Twig |cookbook/migration-from-twig] - - [... więcej |cookbook/@home] +- [Przewodniki i dobre praktyki 💡|cookbook/@home] + - [Migracja z Twiga |cookbook/migration-from-twig] + - [… kolejne |cookbook/@home] -- "Place zabaw .[link-external]":https://fiddle.nette.org/latte/ .{padding-top:1em} +- "Plac zabaw .[link-external]":https://fiddle.nette.org/latte/ .{padding-top:1em} diff --git a/latte/pl/@menu.texy b/latte/pl/@menu.texy index 96b99b8973..df7bec843f 100644 --- a/latte/pl/@menu.texy +++ b/latte/pl/@menu.texy @@ -4,9 +4,9 @@ - "GitHub .[link-external]":https://github.com/nette/latte diff --git a/latte/pl/@meta.texy b/latte/pl/@meta.texy new file mode 100644 index 0000000000..82ad276beb --- /dev/null +++ b/latte/pl/@meta.texy @@ -0,0 +1 @@ +{{sitename: Dokumentacja Latte}} diff --git a/latte/pl/compiler-passes.texy b/latte/pl/compiler-passes.texy new file mode 100644 index 0000000000..a65ad2adf0 --- /dev/null +++ b/latte/pl/compiler-passes.texy @@ -0,0 +1,555 @@ +Przebiegi kompilacji +******************** + +.[perex] +Przebiegi kompilacji stanowią potężny mechanizm do analizy i modyfikacji szablonów Latte *po* ich sparsowaniu do abstrakcyjnego drzewa składni (AST) i *przed* wygenerowaniem ostatecznego kodu PHP. Umożliwia to zaawansowaną manipulację szablonami, optymalizacje, kontrole bezpieczeństwa (takie jak Sandbox) oraz zbieranie informacji o szablonach. Ten przewodnik poprowadzi Cię przez tworzenie własnych przebiegów kompilacji. + + +Co to jest przebieg kompilacji? +=============================== + +Aby zrozumieć rolę przebiegów kompilacji, spójrz na [proces kompilacji Latte |custom-tags#Zrozumienie procesu kompilacji]. Jak możesz zobaczyć, przebiegi kompilacji działają w kluczowej fazie, umożliwiając głęboką interwencję między początkowym parsowaniem a ostatecznym wyjściem kodu. + +W istocie przebieg kompilacji to po prostu PHP callable (jak funkcja, metoda statyczna lub metoda instancji), który przyjmuje jeden argument: korzeniowy węzeł AST szablonu, który jest zawsze instancją `Latte\Compiler\Nodes\TemplateNode`. + +Głównym celem przebiegu kompilacji jest zwykle jeden lub oba z poniższych: + +- Analiza: Przechodzenie przez AST i zbieranie informacji o szablonie (np. znalezienie wszystkich zdefiniowanych bloków, sprawdzenie użycia specyficznych tagów, zapewnienie spełnienia określonych ograniczeń bezpieczeństwa). +- Modyfikacja: Zmiana struktury AST lub atrybutów węzłów (np. automatyczne dodawanie atrybutów HTML, optymalizacja określonych kombinacji tagów, zastępowanie przestarzałych tagów nowymi, implementacja zasad sandboxu). + + +Rejestracja +=========== + +Przebiegi kompilacji są rejestrowane za pomocą metody [rozszerzenia |extending-latte#getPasses] `getPasses()`. Ta metoda zwraca tablicę asocjacyjną, gdzie klucze są unikalnymi nazwami przebiegów (używanymi wewnętrznie i do sortowania), a wartości to PHP callable implementujące logikę przebiegu. + +```php +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Extension; + +class MyExtension extends Extension +{ + public function getPasses(): array + { + return [ + 'modificationPass' => $this->modifyTemplateAst(...), + // ... inne przebiegi ... + ]; + } + + public function modifyTemplateAst(TemplateNode $templateNode): void + { + // Implementacja... + } +} +``` + +Przebiegi zarejestrowane przez podstawowe rozszerzenia Latte i Twoje własne rozszerzenia są uruchamiane sekwencyjnie. Kolejność może być ważna, szczególnie jeśli jeden przebieg zależy od wyników lub modyfikacji innego. Latte dostarcza pomocniczy mechanizm kontroli tej kolejności, jeśli jest to potrzebne; zobacz dokumentację [`Extension::getPasses()` |extending-latte#getPasses] dla szczegółów. + + +Przykład AST +============ + +Dla lepszego wyobrażenia o AST, dodajemy przykład. To jest szablon źródłowy: + +```latte +{foreach $category->getItems() as $item} +
                                                                                  • {$item->name|upper}
                                                                                  • + {else} + nie znaleziono elementów +{/foreach} +``` + +A to jest jego reprezentacja w formie AST: + +/--pre +Latte\Compiler\Nodes\TemplateNode( + Latte\Compiler\Nodes\FragmentNode( + - Latte\Essential\Nodes\ForeachNode( + expression: Latte\Compiler\Nodes\Php\Expression\MethodCallNode( + object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$category') + name: Latte\Compiler\Nodes\Php\IdentifierNode('getItems') + ) + value: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') + content: Latte\Compiler\Nodes\FragmentNode( + - Latte\Compiler\Nodes\TextNode(' ') + - Latte\Compiler\Nodes\Html\ElementNode('li')( + content: Latte\Essential\Nodes\PrintNode( + expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode( + object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') + name: Latte\Compiler\Nodes\Php\IdentifierNode('name') + ) + modifier: Latte\Compiler\Nodes\Php\ModifierNode( + filters: + - Latte\Compiler\Nodes\Php\FilterNode('upper') + ) + ) + ) + ) + else: Latte\Compiler\Nodes\FragmentNode( + - Latte\Compiler\Nodes\TextNode('nie znaleziono elementów') + ) + ) + ) +) +\-- + + +Przechodzenie przez AST za pomocą `NodeTraverser` +================================================= + +Ręczne pisanie rekurencyjnych funkcji do przechodzenia przez złożoną strukturę AST jest męczące i podatne na błędy. Latte dostarcza specjalne narzędzie do tego celu: [api:Latte\Compiler\NodeTraverser]. Ta klasa implementuje [wzorzec projektowy Visitor |https://en.wikipedia.org/wiki/Visitor_pattern], dzięki któremu przechodzenie przez AST jest systematyczne i łatwe do opanowania. + +Podstawowe użycie obejmuje utworzenie instancji `NodeTraverser` i wywołanie jej metody `traverse()`, przekazanie korzeniowego węzła AST i jednego lub dwóch "visitor" callable: + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; + +(new NodeTraverser)->traverse( + $templateNode, + + // 'enter' visitor: Wywoływany przy wejściu do węzła (przed jego dziećmi) + enter: function (Node $node) { + echo "Wejście do węzła typu: " . $node::class . "\n"; + // Tutaj możesz badać węzeł + if ($node instanceof Nodes\TextNode) { + // echo "Znaleziono tekst: " . $node->content . "\n"; + } + }, + + // 'leave' visitor: Wywoływany przy opuszczeniu węzła (po jego dzieciach) + leave: function (Node $node) { + echo "Opuszczenie węzła typu: " . $node::class . "\n"; + // Tutaj możesz wykonywać akcje po przetworzeniu dzieci + }, +); +``` + +Możesz dostarczyć tylko visitor `enter`, tylko visitor `leave` lub oba, w zależności od Twoich potrzeb. + +**`enter(Node $node)`:** Ta funkcja jest wykonywana dla każdego węzła **przed** tym, jak przechodzący odwiedzi którekolwiek z dzieci tego węzła. Jest przydatna do: + +- Zbierania informacji podczas przechodzenia przez drzewo w dół. +- Podejmowania decyzji *przed* przetworzeniem dzieci (jak decyzja o ich pominięciu, zobacz [#Optymalizacja przechodzenia]). +- Potencjalnej modyfikacji węzła przed odwiedzeniem dzieci (rzadziej). + +**`leave(Node $node)`:** Ta funkcja jest wykonywana dla każdego węzła **po** tym, jak wszystkie jego dzieci (i ich całe poddrzewa) zostały w pełni odwiedzone (zarówno wejście, jak i opuszczenie). Jest to najczęstsze miejsce dla: + +Oba visitory `enter` i `leave` mogą opcjonalnie zwracać wartość w celu wpływu na proces przechodzenia. Zwrócenie `null` (lub niczego) kontynuuje przechodzenie normalnie, zwrócenie instancji `Node` zastępuje bieżący węzeł, a zwrócenie specjalnych stałych takich jak `NodeTraverser::RemoveNode` lub `NodeTraverser::StopTraversal` modyfikuje przepływ, jak wyjaśniono w kolejnych sekcjach. + + +Jak działa przechodzenie +------------------------ + +`NodeTraverser` wewnętrznie używa metody `getIterator()`, którą musi implementować każda klasa `Node` (jak omówiono w [Tworzenie własnych tagów |custom-tags#Implementacja getIterator dla podwęzłów]). Iteruje przez dzieci uzyskane za pomocą `getIterator()`, rekurencyjnie wywołuje na nich `traverse()` i zapewnia, że visitory `enter` i `leave` są wywoływane w prawidłowej kolejności wgłąb (depth-first) dla każdego węzła w drzewie dostępnego przez iteratory. To ponownie podkreśla, dlaczego prawidłowo zaimplementowany `getIterator()` w Twoich własnych węzłach tagów jest absolutnie niezbędny do prawidłowego działania przebiegów kompilacji. + +Napiszmy prosty przebieg, który liczy, ile razy w szablonie użyty jest tag `{do}` (reprezentowany przez `Latte\Essential\Nodes\DoNode`). + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Essential\Nodes\DoNode; + +function countDoTags(TemplateNode $templateNode): void +{ + $count = 0; + (new NodeTraverser)->traverse( + $templateNode, + enter: function (Node $node) use (&$count): void { + if ($node instanceof DoNode) { + $count++; + } + }, + // visitor 'leave' nie jest potrzebny do tego zadania + ); + + echo "Znaleziono tag {do} $count razy.\n"; +} + +$latte = new Latte\Engine; +$ast = $latte->parse($templateSource); +countDoTags($ast); +``` + +W tym przykładzie potrzebowaliśmy tylko visitora `enter`, aby sprawdzić typ każdego odwiedzanego węzła. + +Następnie zbadamy, jak te visitory faktycznie modyfikują AST. + + +Modyfikacja AST +=============== + +Jednym z głównych celów przebiegów kompilacji jest modyfikacja abstrakcyjnego drzewa składni. Umożliwia to potężne transformacje, optymalizacje lub wymuszanie reguł bezpośrednio na strukturze szablonu przed generowaniem kodu PHP. `NodeTraverser` dostarcza kilka sposobów, jak to osiągnąć w ramach visitorów `enter` i `leave`. + +**Ważna uwaga:** Modyfikacja AST wymaga ostrożności. Nieprawidłowe zmiany – jak usunięcie podstawowych węzłów lub zastąpienie węzła niekompatybilnym typem – mogą prowadzić do błędów podczas generowania kodu lub spowodować nieoczekiwane zachowanie podczas działania programu. Zawsze dokładnie testuj swoje przebiegi modyfikacyjne. + + +Zmiana właściwości węzłów +------------------------- + +Najprostszym sposobem modyfikacji drzewa jest bezpośrednia zmiana **publicznych właściwości** węzłów odwiedzanych podczas przechodzenia. Wszystkie węzły przechowują swoje sparsowane argumenty, zawartość lub atrybuty w publicznych właściwościach. + +**Przykład:** Stwórzmy przebieg, który znajduje wszystkie statyczne węzły tekstowe (`TextNode`, reprezentujące zwykły HTML lub tekst poza tagami Latte) i konwertuje ich zawartość na wielkie litery *bezpośrednio w AST*. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\TextNode; + +function uppercaseStaticText(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // Możemy użyć 'enter', ponieważ TextNode nie ma żadnych dzieci do przetworzenia + enter: function (Node $node) { + // Czy ten węzeł jest statycznym blokiem tekstowym? + if ($node instanceof TextNode) { + // Tak! Bezpośrednio modyfikujemy jego publiczną właściwość 'content'. + $node->content = mb_strtoupper(html_entity_decode($node->content)); + } + // Nie ma potrzeby niczego zwracać; zmiana jest stosowana bezpośrednio. + }, + ); +} +``` + +W tym przykładzie visitor `enter` sprawdza, czy bieżący `$node` jest typu `TextNode`. Jeśli tak, bezpośrednio aktualizujemy jego publiczną właściwość `$content` za pomocą `mb_strtoupper()`. To bezpośrednio zmienia zawartość statycznego tekstu przechowywanego w AST *przed* generowaniem kodu PHP. Ponieważ modyfikujemy obiekt bezpośrednio, nie musimy niczego zwracać z visitora. + +Efekt: Jeśli szablon zawierał `

                                                                                    Witaj

                                                                                    {= $var }Świecie`, po tym przebiegu AST będzie reprezentować coś w rodzaju: `

                                                                                    WITAJ

                                                                                    {= $var }ŚWIECIE`. To NIE WPŁYNIE na zawartość $var. + + +Zastępowanie węzłów +------------------- + +Potężniejszą techniką modyfikacji jest całkowite zastąpienie węzła innym. Robi się to przez **zwrócenie nowej instancji `Node`** z visitora `enter` lub `leave`. `NodeTraverser` następnie zastępuje oryginalny węzeł zwróconym w strukturze węzła nadrzędnego. + +**Przykład:** Stwórzmy przebieg, który znajduje wszystkie użycia stałej `PHP_VERSION` (reprezentowanej przez `ConstantFetchNode`) i zastępuje je bezpośrednio literałem ciągu znaków (`StringNode`) zawierającym *rzeczywistą* wersję PHP wykrytą *podczas kompilacji*. Jest to forma optymalizacji w czasie kompilacji. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\Php\Expression\ConstantFetchNode; +use Latte\Compiler\Nodes\Php\Scalar\StringNode; + +function inlinePhpVersion(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // 'leave' jest często używany do zastępowania, zapewnia, że dzieci (jeśli istnieją) + // są przetwarzane najpierw, chociaż tutaj działałby również 'enter'. + leave: function (Node $node) { + // Czy ten węzeł jest dostępem do stałej i nazwa stałej to 'PHP_VERSION'? + if ($node instanceof ConstantFetchNode && (string) $node->name === 'PHP_VERSION') { + // Tworzymy nowy StringNode zawierający aktualną wersję PHP + $newNode = new StringNode(PHP_VERSION); + + // Opcjonalne, ale dobra praktyka: kopiujemy informacje o pozycji + $newNode->position = $node->position; + + // Zwracamy nowy StringNode. Traverser zastąpi + // oryginalny ConstantFetchNode tym $newNode. + return $newNode; + } + // Jeśli nie zwrócimy Node, oryginalny $node jest zachowany. + }, + ); +} +``` + +Tutaj visitor `leave` identyfikuje specyficzny `ConstantFetchNode` dla `PHP_VERSION`. Następnie tworzy całkowicie nowy `StringNode` zawierający wartość stałej `PHP_VERSION` *w czasie kompilacji*. Zwracając ten `$newNode`, mówi traverserowi, aby zastąpił oryginalny `ConstantFetchNode` w AST. + +Efekt: Jeśli szablon zawierał `{= PHP_VERSION }` i kompilacja przebiega na PHP 8.2.1, AST po tym przebiegu będzie efektywnie reprezentować `{= '8.2.1' }`. + +**Wybór `enter` vs. `leave` do zastąpienia:** + +- Użyj `leave`, jeśli utworzenie nowego węzła zależy od wyników przetwarzania dzieci starego węzła, lub jeśli chcesz po prostu zapewnić, że dzieci zostały odwiedzone przed zastąpieniem (powszechna praktyka). +- Użyj `enter`, jeśli chcesz zastąpić węzeł *przed* tym, jak jego dzieci zostaną w ogóle odwiedzone. + + +Usuwanie węzłów +--------------- + +Możesz całkowicie usunąć węzeł z AST, zwracając specjalną stałą `NodeTraverser::RemoveNode` z visitora. + +**Przykład:** Usuńmy wszystkie komentarze szablonu (`{* ... *}`), które są reprezentowane przez `CommentNode` w AST generowanym przez rdzeń Latte (chociaż typowo przetwarzane wcześniej, to służy jako przykład). + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\CommentNode; + +function removeCommentNodes(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // 'enter' jest tutaj w porządku, ponieważ nie potrzebujemy informacji o dzieciach do usunięcia komentarza + enter: function (Node $node) { + if ($node instanceof CommentNode) { + // Sygnalizujemy traverserowi, aby usunął ten węzeł z AST + return NodeTraverser::RemoveNode; + } + }, + ); +} +``` + +**Ostrzeżenie:** Używaj `RemoveNode` ostrożnie. Usunięcie węzła, który zawiera podstawową zawartość lub wpływa na strukturę (jak usunięcie węzła zawartości pętli), może prowadzić do uszkodzonych szablonów lub nieprawidłowego wygenerowanego kodu. Najbezpieczniejsze jest dla węzłów, które są rzeczywiście opcjonalne lub samodzielne (jak komentarze lub tagi debugowania) lub dla pustych węzłów strukturalnych (np. pusty `FragmentNode` może być w niektórych kontekstach bezpiecznie usunięty przez przebieg czyszczący). + +Te trzy metody - modyfikacja właściwości, zastępowanie węzłów i usuwanie węzłów - stanowią podstawowe narzędzia do manipulacji AST w ramach Twoich przebiegów kompilacji. + + +Optymalizacja przechodzenia +=========================== + +AST szablonów może być dość duży, potencjalnie zawierając tysiące węzłów. Przechodzenie przez każdy pojedynczy węzeł może być niepotrzebne i wpływać na wydajność kompilacji, jeśli Twój przebieg interesuje się tylko specyficznymi częściami drzewa. `NodeTraverser` oferuje sposoby optymalizacji przechodzenia: + + +Pomijanie dzieci +---------------- + +Jeśli wiesz, że gdy napotkasz określony typ węzła, żaden z jego potomków nie może zawierać węzłów, których szukasz, możesz powiedzieć traverserowi, aby pominął odwiedzanie jego dzieci. Robi się to przez zwrócenie stałej `NodeTraverser::DontTraverseChildren` z visitora **`enter`**. W ten sposób pomijasz całe gałęzie podczas przechodzenia, co potencjalnie oszczędza znaczący czas, zwłaszcza w szablonach ze złożonymi wyrażeniami PHP wewnątrz tagów. + + +Zatrzymanie przechodzenia +------------------------- + +Jeśli Twój przebieg potrzebuje znaleźć tylko *pierwsze* wystąpienie czegoś (specyficzny typ węzła, spełnienie warunku), możesz całkowicie zatrzymać cały proces przechodzenia, gdy tylko to znajdziesz. Osiąga się to przez zwrócenie stałej `NodeTraverser::StopTraversal` z visitora `enter` lub `leave`. Metoda `traverse()` przestanie odwiedzać jakiekolwiek dalsze węzły. Jest to bardzo efektywne, jeśli potrzebujesz tylko pierwszego dopasowania w potencjalnie bardzo dużym drzewie. + + +Użyteczny pomocnik `NodeHelpers` +================================ + +Podczas gdy `NodeTraverser` oferuje precyzyjną kontrolę, Latte dostarcza również praktyczną klasę pomocniczą, [api:Latte\Compiler\NodeHelpers], która enkapsuluje `NodeTraverser` dla kilku typowych zadań wyszukiwania i analizy, często wymagając mniej kodu przygotowawczego. + + +find(Node $startNode, callable $filter): array .[method] +-------------------------------------------------------- + +Ta metoda statyczna znajduje **wszystkie** węzły w poddrzewie zaczynającym się od `$startNode` (włącznie), które spełniają callback `$filter`. Zwraca tablicę pasujących węzłów. + +**Przykład:** Znajdź wszystkie węzły zmiennych (`VariableNode`) w całym szablonie. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Php\Expression\VariableNode; +use Latte\Compiler\Nodes\TemplateNode; + +function findAllVariables(TemplateNode $templateNode): array +{ + return NodeHelpers::find( + $templateNode, + fn($node) => $node instanceof VariableNode, + ); +} +``` + + +findFirst(Node $startNode, callable $filter): ?Node .[method] +-------------------------------------------------------------- + +Podobne do `find`, ale zatrzymuje przechodzenie natychmiast po znalezieniu **pierwszego** węzła, który spełnia callback `$filter`. Zwraca znaleziony obiekt `Node` lub `null`, jeśli nie znaleziono żadnego pasującego węzła. Jest to w zasadzie praktyczne opakowanie wokół `NodeTraverser::StopTraversal`. + +**Przykład:** Znajdź węzeł `{parameters}` (to samo co ręczny przykład wcześniej, ale krócej). + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Essential\Nodes\ParametersNode; + +function findParametersNodeHelper(TemplateNode $templateNode): ?ParametersNode +{ + return NodeHelpers::findFirst( + $templateNode->head, // Szukaj tylko w głównej sekcji dla efektywności + fn($node) => $node instanceof ParametersNode, + ); +} +``` + + +toValue(ExpressionNode $node, bool $constants = false): mixed .[method] +----------------------------------------------------------------------- + +Ta metoda statyczna próbuje wyewaluować `ExpressionNode` **w czasie kompilacji** i zwrócić jego odpowiadającą wartość PHP. Działa niezawodnie tylko dla prostych węzłów literalnych (`StringNode`, `IntegerNode`, `FloatNode`, `BooleanNode`, `NullNode`) oraz instancji `ArrayNode` zawierających tylko takie ewaluowalne elementy. + +Jeśli `$constants` jest ustawione na `true`, będzie również próbować rozwiązać `ConstantFetchNode` i `ClassConstantFetchNode`, sprawdzając `defined()` i używając `constant()`. + +Jeśli węzeł zawiera zmienne, wywołania funkcji lub inne dynamiczne elementy, nie może być wyewaluowany w czasie kompilacji, a metoda rzuci `InvalidArgumentException`. + +**Przypadek użycia:** Uzyskanie statycznej wartości argumentu tagu podczas kompilacji do podejmowania decyzji w czasie kompilacji. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Php\ExpressionNode; + +function getStaticStringArgument(ExpressionNode $argumentNode): ?string +{ + try { + $value = NodeHelpers::toValue($argumentNode); + return is_string($value) ? $value : null; + } catch (\InvalidArgumentException $e) { + // Argument nie był statycznym literałem ciągu znaków + return null; + } +} +``` + + +toText(?Node $node): ?string .[method] +-------------------------------------- + +Ta metoda statyczna jest przydatna do ekstrakcji zwykłego tekstu z prostych węzłów. Działa głównie z: +- `TextNode`: Zwraca jego `$content`. +- `FragmentNode`: Konkatenuje wynik `toText()` dla wszystkich jego dzieci. Jeśli któreś dziecko nie jest konwertowalne na tekst (np. zawiera `PrintNode`), zwraca `null`. +- `NopNode`: Zwraca pusty ciąg znaków. +- Inne typy węzłów: Zwraca `null`. + +**Przypadek użycia:** Uzyskanie statycznej zawartości tekstowej wartości atrybutu HTML lub prostego elementu HTML do analizy podczas przebiegu kompilacji. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Html\AttributeNode; + +function getStaticAttributeValue(AttributeNode $attr): ?string +{ + // $attr->value jest zazwyczaj AreaNode (jak FragmentNode lub TextNode) + return NodeHelpers::toText($attr->value); +} + +// Przykład użycia w przebiegu: +// if ($node instanceof Html\ElementNode && $node->name === 'meta') { +// $nameAttrValue = getStaticAttributeValue($node->getAttributeNode('name')); +// if ($nameAttrValue === 'description') { ... } +// } +``` + +`NodeHelpers` może uprościć Twoje przebiegi kompilacji, dostarczając gotowych rozwiązań dla typowych zadań przechodzenia i analizy AST. + + +Praktyczne przykłady +==================== + +Zastosujmy koncepcje przechodzenia i modyfikacji AST do rozwiązania niektórych praktycznych problemów. Te przykłady demonstrują typowe wzorce używane w przebiegach kompilacji. + + +Automatyczne dodawanie `loading="lazy"` do `` +-------------------------------------------------- + +Nowoczesne przeglądarki obsługują natywne leniwe ładowanie obrazów za pomocą atrybutu `loading="lazy"`. Stwórzmy przebieg, który automatycznie dodaje ten atrybut do wszystkich tagów ``, które jeszcze nie mają atrybutu `loading`. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; +use Latte\Compiler\Nodes\Html; + +function addLazyLoading(Nodes\TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // Możemy użyć 'enter', ponieważ modyfikujemy węzeł bezpośrednio + // i nie zależy to od dzieci dla tej decyzji. + enter: function (Node $node) { + // Czy to element HTML o nazwie 'img'? + if ($node instanceof Html\ElementNode && $node->name === 'img') { + // Upewniamy się, że węzeł atrybutów istnieje + $node->attributes ??= new Nodes\FragmentNode; + + // Sprawdzamy, czy już istnieje atrybut 'loading' (bez względu na wielkość liter) + foreach ($node->attributes->children as $attrNode) { + if ($attrNode instanceof Html\AttributeNode + && $attrNode->name instanceof Nodes\TextNode // Statyczna nazwa atrybutu + && strtolower($attrNode->name->content) === 'loading' + ) { + return; + } + } + + // Dołączamy spację, jeśli atrybuty nie są puste + if ($node->attributes->children) { + $node->attributes->children[] = new Nodes\TextNode(' '); + } + + // Tworzymy nowy węzeł atrybutu: loading="lazy" + $node->attributes->children[] = new Html\AttributeNode( + name: new Nodes\TextNode('loading'), + value: new Nodes\TextNode('lazy'), + quote: '"', + ); + // Zmiana jest stosowana bezpośrednio w obiekcie, nie trzeba niczego zwracać. + } + }, + ); +} +``` + +Wyjaśnienie: +- Visitor `enter` szuka węzłów `Html\ElementNode` o nazwie `img`. +- Iteruje przez istniejące atrybuty (`$node->attributes->children`) i sprawdza, czy atrybut `loading` jest już obecny. +- Jeśli nie jest znaleziony, tworzy nowy `Html\AttributeNode` reprezentujący `loading="lazy"`. + + +Sprawdzanie wywołań funkcji +--------------------------- + +Przebiegi kompilacji są podstawą Latte Sandbox. Chociaż prawdziwy Sandbox jest zaawansowany, możemy zademonstrować podstawową zasadę sprawdzania zabronionych wywołań funkcji. + +**Cel:** Zapobieganie użyciu potencjalnie niebezpiecznej funkcji `shell_exec` wewnątrz wyrażeń szablonu. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; +use Latte\Compiler\Nodes\Php; +use Latte\SecurityViolationException; + +function checkForbiddenFunctions(Nodes\TemplateNode $templateNode): void +{ + $forbiddenFunctions = ['shell_exec' => true, 'exec' => true]; // Prosta lista + + $traverser = new NodeTraverser; + (new NodeTraverser)->traverse( + $templateNode, + enter: function (Node $node) use ($forbiddenFunctions) { + // Czy to węzeł bezpośredniego wywołania funkcji? + if ($node instanceof Php\Expression\FunctionCallNode + && $node->name instanceof Php\NameNode + && isset($forbiddenFunctions[strtolower((string) $node->name)]) + ) { + throw new SecurityViolationException( + "Funkcja {$node->name}() nie jest dozwolona.", + $node->position, + ); + } + }, + ); +} +``` + +Wyjaśnienie: +- Definiujemy listę zabronionych nazw funkcji. +- Visitor `enter` sprawdza `FunctionCallNode`. +- Jeśli nazwa funkcji (`$node->name`) jest statycznym `NameNode`, sprawdzamy jej reprezentację tekstową w małych literach w naszej zabronionej liście. +- Jeśli zostanie znaleziona zabroniona funkcja, rzucamy `Latte\SecurityViolationException`, która jasno wskazuje naruszenie reguły bezpieczeństwa i zatrzymuje kompilację. + +Te przykłady pokazują, jak przebiegi kompilacji z użyciem `NodeTraverser` mogą być wykorzystane do analizy, automatycznych modyfikacji i egzekwowania ograniczeń bezpieczeństwa poprzez interakcję bezpośrednio ze strukturą AST szablonu. + + +Dobre praktyki +============== + +Podczas pisania przebiegów kompilacji pamiętaj o tych wytycznych, aby tworzyć solidne, łatwe w utrzymaniu i efektywne rozszerzenia: + +- **Kolejność ma znaczenie:** Bądź świadomy kolejności, w jakiej przebiegają przebiegi. Jeśli Twój przebieg zależy od struktury AST utworzonej przez inny przebieg (np. podstawowe przebiegi Latte lub inny własny przebieg), lub jeśli inne przebiegi mogą zależeć od Twoich modyfikacji, użyj mechanizmu sortowania dostarczanego przez `Extension::getPasses()` do definiowania zależności (`before`/`after`). Zobacz dokumentację [`Extension::getPasses()` |extending-latte#getPasses] dla szczegółów. +- **Pojedyncza odpowiedzialność:** Dąż do przebiegów, które wykonują jedno dobrze zdefiniowane zadanie. Dla złożonych transformacji rozważ podzielenie logiki na wiele przebiegów – być może jeden do analizy i drugi do modyfikacji opartej na wynikach analizy. To poprawia przejrzystość i testowalność. +- **Wydajność:** Pamiętaj, że przebiegi kompilacji wydłużają czas kompilacji szablonu (chociaż zazwyczaj dzieje się to tylko raz, dopóki szablon się nie zmieni). Unikaj operacji wymagających dużych zasobów obliczeniowych w swoich przebiegach, jeśli to możliwe. Wykorzystuj optymalizacje przechodzenia, takie jak `NodeTraverser::DontTraverseChildren` i `NodeTraverser::StopTraversal`, gdy tylko wiesz, że nie musisz odwiedzać określonych części AST. +- **Używaj `NodeHelpers`:** Dla typowych zadań, takich jak znajdowanie specyficznych węzłów lub statyczne ewaluowanie prostych wyrażeń, sprawdź, czy `Latte\Compiler\NodeHelpers` nie oferuje odpowiedniej metody przed pisaniem własnej logiki `NodeTraverser`. Może to zaoszczędzić czas i zmniejszyć ilość kodu przygotowawczego. +- **Obsługa błędów:** Jeśli Twój przebieg wykryje błąd lub nieprawidłowy stan w AST szablonu, rzuć `Latte\CompileException` (lub `Latte\SecurityViolationException` dla problemów bezpieczeństwa) z jasną wiadomością i odpowiednim obiektem `Position` (zazwyczaj `$node->position`). To dostarcza użytecznej informacji zwrotnej programiście szablonu. +- **Idempotencja (jeśli to możliwe):** Idealnie, uruchomienie Twojego przebiegu wielokrotnie na tym samym AST powinno dać ten sam wynik, co jego jednorazowe uruchomienie. Nie zawsze jest to wykonalne, ale upraszcza debugowanie i rozumienie interakcji przebiegów, jeśli jest to osiągnięte. Na przykład, upewnij się, że Twój przebieg modyfikacyjny sprawdza, czy modyfikacja została już zastosowana, zanim zastosuje ją ponownie. + +Przestrzegając tych praktyk, możesz efektywnie wykorzystać przebiegi kompilacji do rozszerzenia możliwości Latte w sposób potężny i niezawodny, przyczyniając się do bezpieczniejszego, bardziej zoptymalizowanego lub bogatszego funkcjonalnie przetwarzania szablonów. diff --git a/latte/pl/cookbook/@home.texy b/latte/pl/cookbook/@home.texy index 2f49c82fcc..fb55d74650 100644 --- a/latte/pl/cookbook/@home.texy +++ b/latte/pl/cookbook/@home.texy @@ -1,13 +1,13 @@ -Instrukcje i procedury -********************** +Przewodniki i dobre praktyki +**************************** .[perex] -Przykładowe kody i przepisy na wykonanie typowych zadań z Latte. +Przykłady kodów i przepisów do wykonywania typowych zadań za pomocą Latte. -- [Wszystko, co kiedykolwiek chciałeś wiedzieć o {iterateWhile} |iteratewhile]. +- [Praktyki dla programistów |/develop] +- [Przekazywanie zmiennych między szablonami |passing-variables] +- [Wszystko, co kiedykolwiek chciałeś wiedzieć o grupowaniu |grouping] - [Jak pisać zapytania SQL w Latte? |how-to-write-sql-queries-in-latte] - [Migracja z PHP |migration-from-php] -- [Migracja z Twig |migration-from-twig] -- [Używanie Latte z Slim 4 |slim-framework] - -{{leftbar: /@left-menu}} +- [Migracja z Twiga |migration-from-twig] +- [Użycie Latte ze Slim 4 |slim-framework] diff --git a/latte/pl/cookbook/@meta.texy b/latte/pl/cookbook/@meta.texy new file mode 100644 index 0000000000..53f435e908 --- /dev/null +++ b/latte/pl/cookbook/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Dokumentacja Latte}} +{{leftbar: /@left-menu}} diff --git a/latte/pl/cookbook/grouping.texy b/latte/pl/cookbook/grouping.texy new file mode 100644 index 0000000000..c7fd154b2e --- /dev/null +++ b/latte/pl/cookbook/grouping.texy @@ -0,0 +1,251 @@ +Wszystko, co chcieliście wiedzieć o grupowaniu +********************************************** + +.[perex] +Podczas pracy z danymi w szablonach często można napotkać potrzebę ich grupowania lub specyficznego wyświetlania według określonych kryteriów. Latte oferuje w tym celu kilka potężnych narzędzi. + +Filtr i funkcja `|group` umożliwiają efektywne grupowanie danych według podanego kryterium, filtr `|batch` ułatwia podział danych na ustalone partie, a znacznik `{iterateWhile}` zapewnia możliwość bardziej złożonego sterowania przebiegiem pętli z warunkami. Każdy z tych znaczników oferuje specyficzne możliwości pracy z danymi, co czyni je niezbędnymi narzędziami do dynamicznego i strukturalnego wyświetlania informacji w szablonach Latte. + + +Filtr i funkcja `group` .{data-version:3.0.16} +============================================== + +Wyobraź sobie tabelę bazy danych `items` z pozycjami podzielonymi na kategorie: + +| id | categoryId | name +|------------------ +| 1 | 1 | Apple +| 2 | 1 | Banana +| 3 | 2 | PHP +| 4 | 3 | Green +| 5 | 3 | Red +| 6 | 3 | Blue + +Prosta lista wszystkich pozycji za pomocą szablonu Latte wyglądałaby tak: + +```latte +
                                                                                      +{foreach $items as $item} +
                                                                                    • {$item->name}
                                                                                    • +{/foreach} +
                                                                                    +``` + +Jeśli jednak chcielibyśmy, aby pozycje były uporządkowane w grupy według kategorii, musimy je podzielić tak, aby każda kategoria miała swoją własną listę. Wynik powinien wyglądać następująco: + +```latte +
                                                                                      +
                                                                                    • Apple
                                                                                    • +
                                                                                    • Banana
                                                                                    • +
                                                                                    + +
                                                                                      +
                                                                                    • PHP
                                                                                    • +
                                                                                    + +
                                                                                      +
                                                                                    • Green
                                                                                    • +
                                                                                    • Red
                                                                                    • +
                                                                                    • Blue
                                                                                    • +
                                                                                    +``` + +Zadanie można łatwo i elegancko rozwiązać za pomocą `|group`. Jako parametr podajemy `categoryId`, co oznacza, że pozycje zostaną podzielone na mniejsze tablice według wartości `$item->categoryId` (jeśli `$item` byłoby tablicą, użyłoby się `$item['categoryId']`): + +```latte +{foreach ($items|group: categoryId) as $categoryId => $categoryItems} +
                                                                                      + {foreach $categoryItems as $item} +
                                                                                    • {$item->name}
                                                                                    • + {/foreach} +
                                                                                    +{/foreach} +``` + +Filtr można w Latte użyć również jako funkcję, co daje nam alternatywną składnię: `{foreach group($items, categoryId) ...}`. + +Jeśli chcesz grupować pozycje według bardziej złożonych kryteriów, możesz w parametrze filtra użyć funkcji. Na przykład, grupowanie pozycji według długości nazwy wyglądałoby tak: + +```latte +{foreach ($items|group: fn($item) => strlen($item->name)) as $items} + ... +{/foreach} +``` + +Ważne jest, aby pamiętać, że `$categoryItems` nie jest zwykłą tablicą, ale obiektem, który zachowuje się jak iterator. Aby uzyskać dostęp do pierwszej pozycji grupy, możesz użyć funkcji [`first()` |latte:functions#first]. + +Ta elastyczność w grupowaniu danych czyni `group` wyjątkowo użytecznym narzędziem do prezentacji danych w szablonach Latte. + + +Zagnieżdżone pętle +------------------ + +Wyobraźmy sobie, że mamy tabelę bazy danych z dodatkową kolumną `subcategoryId`, która definiuje podkategorie poszczególnych pozycji. Chcemy wyświetlić każdą główną kategorię w osobnej liście `
                                                                                      ` i każdą podkategorię w osobnej zagnieżdżonej liście `
                                                                                        `: + +```latte +{foreach ($items|group: categoryId) as $categoryItems} +
                                                                                          + {foreach ($categoryItems|group: subcategoryId) as $subcategoryItems} +
                                                                                            + {foreach $subcategoryItems as $item} +
                                                                                          1. {$item->name} + {/foreach} +
                                                                                          + {/foreach} +
                                                                                        +{/foreach} +``` + + +Połączenie z Nette Database +--------------------------- + +Pokażmy, jak efektywnie wykorzystać grupowanie danych w połączeniu z Nette Database. Załóżmy, że pracujemy z tabelą `items` z przykładu wprowadzającego, która jest za pośrednictwem kolumny `categoryId` połączona z tą tabelą `categories`: + +| categoryId | name | +|------------|------------| +| 1 | Fruits | +| 2 | Languages | +| 3 | Colors | + +Dane z tabeli `items` wczytamy za pomocą Nette Database Explorer poleceniem `$items = $db->table('items')`. Podczas iteracji nad tymi danymi mamy możliwość dostępu nie tylko do atrybutów jak `$item->name` i `$item->categoryId`, ale dzięki połączeniu z tabelą `categories` również do powiązanego wiersza w niej przez `$item->category`. Na tym połączeniu można zademonstrować ciekawe wykorzystanie: + +```latte +{foreach ($items|group: category) as $category => $categoryItems} +

                                                                                        {$category->name}

                                                                                        +
                                                                                          + {foreach $categoryItems as $item} +
                                                                                        • {$item->name}
                                                                                        • + {/foreach} +
                                                                                        +{/foreach} +``` + +W tym przypadku używamy filtra `|group` do grupowania według połączonego wiersza `$item->category`, a nie tylko według kolumny `categoryId`. Dzięki temu w zmiennej klucza mamy bezpośrednio `ActiveRow` danej kategorii, co pozwala nam bezpośrednio wypisywać jej nazwę za pomocą `{$category->name}`. Jest to praktyczny przykład, jak grupowanie może uczynić szablony bardziej przejrzystymi i ułatwić pracę z danymi. + + +Filtr `|batch` +============== + +Filtr umożliwia podział listy elementów na grupy o z góry określonej liczbie elementów. Ten filtr jest idealny w sytuacjach, gdy chcesz prezentować dane w wielu mniejszych grupach, na przykład dla lepszej przejrzystości lub wizualnego uporządkowania na stronie. + +Wyobraźmy sobie, że mamy listę pozycji i chcemy je wyświetlić w listach, gdzie każda zawiera maksymalnie trzy pozycje. Użycie filtra `|batch` jest w takim przypadku bardzo praktyczne: + +```latte +
                                                                                          +{foreach ($items|batch: 3) as $batch} + {foreach $batch as $item} +
                                                                                        • {$item->name}
                                                                                        • + {/foreach} +{/foreach} +
                                                                                        +``` + +W tym przykładzie lista `$items` jest podzielona na mniejsze grupy, przy czym każda grupa (`$batch`) zawiera do trzech pozycji. Każda grupa jest następnie wyświetlana w osobnej liście `
                                                                                          `. + +Jeśli ostatnia grupa nie zawiera wystarczającej liczby elementów do osiągnięcia wymaganej liczby, drugi parametr filtra pozwala zdefiniować, czym ta grupa zostanie uzupełniona. Jest to idealne do estetycznego wyrównania elementów tam, gdzie niekompletny rząd mógłby wyglądać nieuporządkowanie. + +```latte +{foreach ($items|batch: 3, '—') as $batch} + ... +{/foreach} +``` + + +Znacznik `{iterateWhile}` +========================= + +Te same zadania, które rozwiązywaliśmy za pomocą filtra `|group`, pokażemy z użyciem znacznika `{iterateWhile}`. Główna różnica między oboma podejściami polega na tym, że `group` najpierw przetwarza i grupuje wszystkie dane wejściowe, podczas gdy `{iterateWhile}` steruje przebiegiem pętli z warunkami, więc iteracja odbywa się stopniowo. + +Najpierw wyrenderujemy tabelę z kategoriami za pomocą iterateWhile: + +```latte +{foreach $items as $item} +
                                                                                            + {iterateWhile} +
                                                                                          • {$item->name}
                                                                                          • + {/iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                                                                                          +{/foreach} +``` + +Podczas gdy `{foreach}` oznacza zewnętrzną część pętli, czyli renderowanie list dla każdej kategorii, znacznik `{iterateWhile}` oznacza wewnętrzną część, czyli poszczególne pozycje. Warunek w końcowym znaczniku mówi, że powtarzanie będzie trwało, dopóki bieżący i następny element należą do tej samej kategorii (`$iterator->nextValue` jest [następnym elementem |/tags#iterator]). + +Gdyby warunek był zawsze spełniony, to w wewnętrznej pętli wyrenderowałyby się wszystkie elementy: + +```latte +{foreach $items as $item} +
                                                                                            + {iterateWhile} +
                                                                                          • {$item->name} + {/iterateWhile true} +
                                                                                          +{/foreach} +``` + +Wynik będzie wyglądał tak: + +```latte +
                                                                                            +
                                                                                          • Apple
                                                                                          • +
                                                                                          • Banana
                                                                                          • +
                                                                                          • PHP
                                                                                          • +
                                                                                          • Green
                                                                                          • +
                                                                                          • Red
                                                                                          • +
                                                                                          • Blue
                                                                                          • +
                                                                                          +``` + +Do czego jest dobre takie użycie iterateWhile? Gdy tabela będzie pusta i nie będzie zawierać żadnych elementów, nie wypisze się puste `
                                                                                            `. + +Jeśli podamy warunek w otwierającym znaczniku `{iterateWhile}`, to zachowanie się zmieni: warunek (i przejście do następnego elementu) wykona się już na początku wewnętrznej pętli, a nie na końcu. Czyli podczas gdy do `{iterateWhile}` bez warunku wejdzie się zawsze, do `{iterateWhile $cond}` tylko przy spełnieniu warunku `$cond`. A jednocześnie do `$item` zapisze się następny element. + +Co przydaje się na przykład w sytuacji, gdy będziemy chcieli pierwszy element w każdej kategorii wyrenderować w inny sposób, na przykład tak: + +```latte +

                                                                                            Apple

                                                                                            +
                                                                                              +
                                                                                            • Banana
                                                                                            • +
                                                                                            + +

                                                                                            PHP

                                                                                            +
                                                                                              +
                                                                                            + +

                                                                                            Green

                                                                                            +
                                                                                              +
                                                                                            • Red
                                                                                            • +
                                                                                            • Blue
                                                                                            • +
                                                                                            +``` + +Oryginalny kod zmodyfikujemy tak, że najpierw wyrenderujemy pierwszą pozycję, a następnie w wewnętrznej pętli `{iterateWhile}` wyrenderujemy kolejne pozycje z tej samej kategorii: + +```latte +{foreach $items as $item} +

                                                                                            {$item->name}

                                                                                            +
                                                                                              + {iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                                                                                            • {$item->name}
                                                                                            • + {/iterateWhile} +
                                                                                            +{/foreach} +``` + +W ramach jednej pętli możemy tworzyć więcej wewnętrznych pętli i nawet je zagnieżdżać. W ten sposób można by grupować na przykład podkategorie itd. + +Załóżmy, że w tabeli będzie jeszcze dodatkowa kolumna `subcategoryId` i oprócz tego, że każda kategoria będzie w osobnym `
                                                                                              `, każda podkategoria w osobnym `
                                                                                                `: + +```latte +{foreach $items as $item} +
                                                                                                  + {iterateWhile} +
                                                                                                    + {iterateWhile} +
                                                                                                  1. {$item->name} + {/iterateWhile $item->subcategoryId === $iterator->nextValue->subcategoryId} +
                                                                                                  + {/iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                                                                                                +{/foreach} +``` diff --git a/latte/pl/cookbook/how-to-write-sql-queries-in-latte.texy b/latte/pl/cookbook/how-to-write-sql-queries-in-latte.texy index 02bb4f0cdf..4173fa5570 100644 --- a/latte/pl/cookbook/how-to-write-sql-queries-in-latte.texy +++ b/latte/pl/cookbook/how-to-write-sql-queries-in-latte.texy @@ -2,9 +2,9 @@ Jak pisać zapytania SQL w Latte? ******************************** .[perex] -Latte może być przydatne do generowania naprawdę złożonych zapytań SQL. +Latte może się przydać również do generowania naprawdę złożonych zapytań SQL. -Jeśli tworzenie zapytania SQL zawiera wiele warunków i zmiennych, to zapisanie go w Latte może być naprawdę bardziej przejrzyste. Bardzo prosty przykład: +Jeśli tworzenie zapytania SQL zawiera wiele warunków i zmiennych, może być naprawdę bardziej przejrzyste napisanie go w Latte. Bardzo prosty przykład: ```latte SELECT users.* FROM users @@ -14,8 +14,7 @@ SELECT users.* FROM users WHERE groups.name = 'Admins' {ifset $country} AND country.name = {$country} {/ifset} ``` -Używając `$latte->setContentType()`, mówimy Latte, aby traktowała treść jako zwykły tekst (nie HTML), a następnie -przygotowuje funkcję ucieczki, która ucieka z łańcuchów bezpośrednio przez sterownik bazy danych: +Za pomocą `$latte->setContentType()` powiemy Latte, aby traktowało zawartość jako zwykły tekst (a nie jako HTML), a następnie przygotujemy funkcję escapującą, która będzie escapować ciągi znaków bezpośrednio przez sterownik bazy danych: ```php $db = new PDO(/* ... */); @@ -27,7 +26,7 @@ $latte->addFilter('escape', fn($val) => match (true) { is_int($val), is_float($val) => (string) $val, is_bool($val) => $val ? '1' : '0', is_null($val) => 'NULL', - default => throw new Exception('Unsupported type'), + default => throw new Exception('Nieobsługiwany typ'), }); ``` @@ -38,6 +37,4 @@ $sql = $latte->renderToString('query.sql.latte', ['country' => $country]); $result = $db->query($sql); ``` -*Powyższy przykład wymaga Latte v3.0.5 lub wyższej. - -{{leftbar: /@left-menu}} +*Podany przykład wymaga Latte w wersji 3.0.5 lub wyższej.* diff --git a/latte/pl/cookbook/iteratewhile.texy b/latte/pl/cookbook/iteratewhile.texy deleted file mode 100644 index f4c86b6b2c..0000000000 --- a/latte/pl/cookbook/iteratewhile.texy +++ /dev/null @@ -1,214 +0,0 @@ -Wszystko, co kiedykolwiek chciałeś wiedzieć o {iterateWhile}. -************************************************************* - -.[perex] -Znacznik `{iterateWhile}` jest przydatny do wszelkiego rodzaju sztuczek w pętlach foreach. - -Załóżmy, że mamy następującą tabelę bazy danych, w której przedmioty są skategoryzowane: - -| id | catId | name -|------------------ -| 1 | 1 | Apple -| 2 | 1 | Banana -| 3 | 2 | PHP -| 4 | 3 | Green -| 5 | 3 | Red -| 6 | 3 | Blue - -Renderowanie elementów w pętli foreach jako listy jest oczywiście proste: - -```latte -
                                                                                                  -{foreach $items as $item} -
                                                                                                • {$item->name}
                                                                                                • -{/foreach} -
                                                                                                -``` - -Ale co jeśli chcielibyśmy, aby każda kategoria była na osobnej liście? Innymi słowy, rozwiązujemy problem, jak pogrupować elementy na liście liniowej w pętli foreach. Dane wyjściowe powinny wyglądać tak: - -```latte -
                                                                                                  -
                                                                                                • Apple
                                                                                                • -
                                                                                                • Banana
                                                                                                • -
                                                                                                - -
                                                                                                  -
                                                                                                • PHP
                                                                                                • -
                                                                                                - -
                                                                                                  -
                                                                                                • Green
                                                                                                • -
                                                                                                • Red
                                                                                                • -
                                                                                                • Blue
                                                                                                • -
                                                                                                -``` - -Zobaczymy jak łatwo i elegancko można rozwiązać to zadanie używając iterateWhile: - -```latte -{foreach $items as $item} -
                                                                                                  - {iterateWhile} -
                                                                                                • {$item->name}
                                                                                                • - {/iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                                                                -{/foreach} -``` - -Podczas gdy `{foreach}` oznacza zewnętrzną część pętli, czyli renderowanie list dla każdej kategorii, znacznik `{iterateWhile}` oznacza część wewnętrzną, czyli poszczególne elementy. -Warunek w znaczniku end mówi, że iteracja będzie trwała tak długo, jak długo bieżący i następny element należą do tej samej kategorii (`$iterator->nextValue` jest [następnym |/tags#iterator] elementem). - -Gdyby warunek był zawsze spełniony, wszystkie elementy byłyby renderowane w wewnętrznej pętli: - -```latte -{foreach $items as $item} -
                                                                                                  - {iterateWhile} -
                                                                                                • {$item->name} - {/iterateWhile true} -
                                                                                                -{/foreach} -``` - -Wynik wyglądałby tak: - -```latte -
                                                                                                  -
                                                                                                • Apple
                                                                                                • -
                                                                                                • Banana
                                                                                                • -
                                                                                                • PHP
                                                                                                • -
                                                                                                • Green
                                                                                                • -
                                                                                                • Red
                                                                                                • -
                                                                                                • Blue
                                                                                                • -
                                                                                                -``` - -Jakie jest zastosowanie iterateWhile? Czym różni się ono od rozwiązania, które pokazaliśmy na samym początku tego tutorialu? Różnica polega na tym, że jeśli tablica jest pusta i nie zawiera żadnych elementów, nie zostanie wypisana pusta tablica `
                                                                                                  `. - - -Rozwiązanie bez `{iterateWhile}` .[#toc-solution-without-iteratewhile] ----------------------------------------------------------------------- - -Gdybyśmy mieli rozwiązać to samo zadanie używając bardzo podstawowych systemów templatowania, na przykład w Twigu, Blade, czy czystym PHP, rozwiązanie wyglądałoby coś takiego: - -```latte -{var $prevCatId = null} -{foreach $items as $item} - {if $item->catId !== $prevCatId} - {* kategoria zmieniona *} - - {* zamknij poprzedni,
                                                                                                    jeśli nie jest pierwszym elementem *} - {if $prevCatId !== null} -
                                                                                                  - {/if} - - {* otwórz nową listę *} -
                                                                                                    - - {do $prevCatId = $item->catId} - {/if} - -
                                                                                                  • {$item->name}
                                                                                                  • -{/foreach} - -{if $prevCatId !== null} - {* zamknij ostatnią listę *} -
                                                                                                  -{/if} -``` - -Jednak ten kod jest niezrozumiały i nieintuicyjny. Związek pomiędzy otwierającymi i zamykającymi znacznikami HTML nie jest wcale jasny. Nie widać na pierwszy rzut oka, czy jest jakiś błąd. I wymaga zmiennych pomocniczych, takich jak `$prevCatId`. - -W przeciwieństwie do tego, rozwiązanie `{iterateWhile}` jest czyste, jasne, nie potrzebuje zmiennych pomocniczych i jest bloatproof. - - -Warunek w tagu otwierającym .[#toc-condition-in-the-closing-tag] ----------------------------------------------------------------- - -Jeśli określisz warunek w znaczniku otwierającym `{iterateWhile}`, to zachowanie się zmieni: warunek (i przejście do następnego elementu) jest wykonywany na początku pętli wewnętrznej, a nie na końcu. -O ile więc `{iterateWhile}` bez warunku jest wpisywany zawsze, to `{iterateWhile $cond}` jest wpisywany dopiero po spełnieniu warunku `$cond`. I w tym samym czasie na stronę `$item` wchodzi kolejny element. - -Co jest przydatne np. jeśli chcemy wyrenderować pierwszy element w każdej kategorii w inny sposób, np: - -```latte -

                                                                                                  Apple

                                                                                                  -
                                                                                                    -
                                                                                                  • Banana
                                                                                                  • -
                                                                                                  - -

                                                                                                  PHP

                                                                                                  -
                                                                                                    -
                                                                                                  - -

                                                                                                  Green

                                                                                                  -
                                                                                                    -
                                                                                                  • Red
                                                                                                  • -
                                                                                                  • Blue
                                                                                                  • -
                                                                                                  -``` - -Zmodyfikuj oryginalny kod, najpierw renderując pierwszy element, a następnie renderując pozostałe elementy z tej samej kategorii w wewnętrznej pętli strony `{iterateWhile}`: - -```latte -{foreach $items as $item} -

                                                                                                  {$item->name}

                                                                                                  -
                                                                                                    - {iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                                                                  • {$item->name}
                                                                                                  • - {/iterateWhile} -
                                                                                                  -{/foreach} -``` - - -Pętle zagnieżdżone .[#toc-nested-loops] ---------------------------------------- - -Możemy tworzyć wiele pętli wewnętrznych w ramach jednej pętli, a nawet je zagnieżdżać. W ten sposób można by grupować np. podkategorie itp. - -Załóżmy, że w tabeli `subCatId` jest jeszcze jedna kolumna i oprócz tego, że każda kategoria jest w osobnej `
                                                                                                    `każda podkategoria w osobnym `
                                                                                                      `: - -```latte -{foreach $items as $item} -
                                                                                                        - {iterateWhile} -
                                                                                                          - {iterateWhile} -
                                                                                                        1. {$item->name} - {/iterateWhile $item->subCatId === $iterator->nextValue->subCatId} -
                                                                                                        - {/iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                                                                      -{/foreach} -``` - - -Filtr |wszystkie .[#toc-filter-batch] -------------------------------------- - -Filtr `batch` obsługuje również grupowanie elementów liniowych w partie o stałej liczbie elementów: - -```latte -
                                                                                                        -{foreach ($items|batch:3) as $batch} - {foreach $batch as $item} -
                                                                                                      • {$item->name}
                                                                                                      • - {/foreach} -{/foreach} -
                                                                                                      -``` - -Można go zastąpić iterateWhile w następujący sposób: - -```latte -
                                                                                                        -{foreach $items as $item} - {iterateWhile} -
                                                                                                      • {$item->name}
                                                                                                      • - {/iterateWhile $iterator->counter0 % 3} -{/foreach} -
                                                                                                      -``` - -{{leftbar: /@left-menu}} diff --git a/latte/pl/cookbook/migration-from-php.texy b/latte/pl/cookbook/migration-from-php.texy index 300eb4e028..7993706c34 100644 --- a/latte/pl/cookbook/migration-from-php.texy +++ b/latte/pl/cookbook/migration-from-php.texy @@ -2,27 +2,27 @@ Migracja z PHP do Latte *********************** .[perex] -Czy migrujesz stary projekt napisany w czystym PHP do Latte? Mamy narzędzie, które ułatwi migrację. [Spróbuj online |https://php2latte.nette.org]. +Konwertujesz stary projekt napisany w czystym PHP do Latte? Mamy dla Ciebie narzędzie, które ułatwi migrację. [Wypróbuj online |https://fiddle.nette.org/php2latte/]. -Możesz pobrać narzędzie z [GitHub |https://github.com/nette/latte-tools] lub zainstalować je za pomocą Composera: +Narzędzie możesz pobrać z [GitHub|https://github.com/nette/latte-tools] lub zainstalować za pomocą Composera: ```shell composer create-project latte/tools ``` -Konwerter nie używa prostych substytucji wyrażeń regularnych, ale zamiast tego używa bezpośrednio parsera PHP, więc może obsługiwać każdą złożoną składnię. +Konwerter nie używa prostych zamian za pomocą wyrażeń regularnych, wręcz przeciwnie, wykorzystuje bezpośrednio parser PHP, dzięki czemu poradzi sobie z dowolnie złożoną składnią. -Skrypt `php-to-latte.php` służy do konwersji z PHP na Latte: +Do konwersji z PHP do Latte służy skrypt `php-to-latte.php`: ```shell -php-to-latte.php input.php [output.latte] +php php-to-latte.php input.php [output.latte] ``` -Przykład .[#toc-example] ------------------------- +Przykład +-------- -Plik wejściowy może wyglądać tak (jest to część kodu forum PunBB): +Plik wejściowy może wyglądać na przykład tak (jest to fragment kodu forum PunBB): ```php

                                                                                                      @@ -48,7 +48,7 @@ foreach ($result as $cur_group) {
                                                                                        ``` -Generuje ten szablon: +Wygeneruje ten szablon: ```latte

                                                                                        {$lang_common['User list']}

                                                                                        @@ -68,5 +68,3 @@ Generuje ten szablon:
                                                                                        ``` - -{{leftbar: /@left-menu}} diff --git a/latte/pl/cookbook/migration-from-twig.texy b/latte/pl/cookbook/migration-from-twig.texy index 71a12258ce..41aa0e395a 100644 --- a/latte/pl/cookbook/migration-from-twig.texy +++ b/latte/pl/cookbook/migration-from-twig.texy @@ -2,37 +2,37 @@ Migracja z Twig do Latte ************************ .[perex] -Czy migrujesz projekt napisany w Twigu do bardziej nowoczesnego Latte? Mamy narzędzie, które ułatwi Ci migrację. [Spróbuj online |https://twig2latte.nette.org]. +Konwertujesz projekt napisany w Twigu do nowocześniejszego Latte? Mamy dla Ciebie narzędzie, które ułatwi migrację. [Wypróbuj online |https://fiddle.nette.org/twig2latte/]. -Możesz pobrać narzędzie z [GitHub |https://github.com/nette/latte-tools] lub zainstalować je za pomocą Composera: +Narzędzie możesz pobrać z [GitHub|https://github.com/nette/latte-tools] lub zainstalować za pomocą Composera: ```shell composer create-project latte/tools ``` -Konwerter nie używa prostych substytucji wyrażeń regularnych, ale zamiast tego używa bezpośrednio parsera Twig, więc może obsługiwać dowolnie złożoną składnię. +Konwerter nie używa prostych zamian za pomocą wyrażeń regularnych, wręcz przeciwnie, wykorzystuje bezpośrednio parser Twiga, dzięki czemu poradzi sobie z dowolnie złożoną składnią. -Skrypt `twig-to-latte.php` służy do konwersji z Twiga na Latte: +Do konwersji z Twiga do Latte służy skrypt `twig-to-latte.php`: ```shell -twig-to-latte.php input.twig.html [output.latte] +php twig-to-latte.php input.twig.html [output.latte] ``` -Konwersja .[#toc-conversion] ----------------------------- +Konwersja +--------- -Konwersja wymaga ręcznej edycji wyniku, gdyż nie można dokonać jednoznacznej konwersji. Twig używa składni kropkowej, gdzie `{{ a.b }}` może oznaczać `$a->b`, `$a['b']` lub `$a->getB()`, których nie można odróżnić w czasie kompilacji. Konwerter zamienia więc wszystko na `$a->b`. +Konwersja zakłada ręczną modyfikację wyniku, ponieważ konwersji nie można przeprowadzić jednoznacznie. Twig używa składni kropkowej, gdzie `{{ a.b }}` może oznaczać `$a->b`, `$a['b']` lub `$a->getB()`, czego nie można rozróżnić podczas kompilacji. Konwerter dlatego wszystko konwertuje na `$a->b`. -Niektóre funkcje, filtry lub tagi nie mają odpowiednika w Latte lub mogą zachowywać się nieco inaczej. +Niektóre funkcje, filtry lub tagi nie mają odpowiednika w Latte, lub mogą zachowywać się nieco inaczej. -Przykład .[#toc-example] ------------------------- +Przykład +-------- -Plik wejściowy może wyglądać tak: +Plik wejściowy może wyglądać na przykład tak: -```latte +```twig {% use "blocks.twig" %} @@ -54,7 +54,7 @@ Plik wejściowy może wyglądać tak: ``` -Po konwersji na Latte otrzymujemy taki oto szablon: +Po konwersji do Latte otrzymamy ten szablon: ```latte {import 'blocks.latte'} @@ -77,5 +77,3 @@ Po konwersji na Latte otrzymujemy taki oto szablon: ``` - -{{leftbar: /@left-menu}} diff --git a/latte/pl/cookbook/passing-variables.texy b/latte/pl/cookbook/passing-variables.texy new file mode 100644 index 0000000000..3ad76a2e53 --- /dev/null +++ b/latte/pl/cookbook/passing-variables.texy @@ -0,0 +1,158 @@ +Przekazywanie zmiennych między szablonami +***************************************** + +Ten przewodnik wyjaśni Ci, jak zmienne są przekazywane między szablonami w Latte za pomocą różnych tagów, takich jak `{include}`, `{import}`, `{embed}`, `{layout}`, `{sandbox}` i innych. Dowiesz się również, jak pracować ze zmiennymi w tagach `{block}` i `{define}`, oraz do czego służy znacznik `{parameters}`. + + +Typy zmiennych +-------------- +Zmienne w Latte możemy podzielić na trzy kategorie w zależności od tego, jak i gdzie są zdefiniowane: + +**Zmienne wejściowe** to te, które są przekazywane do szablonu z zewnątrz, na przykład ze skryptu PHP lub za pomocą tagu jak `{include}`. + +```php +$latte->render('template.latte', ['userName' => 'Jan', 'userAge' => 30]); +``` + +**Zmienne otoczenia** to zmienne istniejące w miejscu danego znacznika. Obejmują wszystkie zmienne wejściowe i inne zmienne utworzone za pomocą tagów jak `{var}`, `{default}` lub w ramach pętli `{foreach}`. + +```latte +{foreach $users as $user} + {include 'userBox.latte', user: $user} +{/foreach} +``` + +**Zmienne jawne** to te, które są bezpośrednio określone wewnątrz tagu i są wysyłane do szablonu docelowego. + +```latte +{include 'userBox.latte', name: $user->name, age: $user->age} +``` + + +`{block}` +--------- +Tag `{block}` służy do definiowania bloków kodu wielokrotnego użytku, które można dostosowywać lub rozszerzać w szablonach dziedziczących. Zmienne otoczenia zdefiniowane przed blokiem są dostępne wewnątrz bloku, ale wszelkie zmiany zmiennych będą widoczne tylko w ramach tego bloku. + +```latte +{var $foo = 'oryginalny'} +{block example} + {var $foo = 'zmieniony'} +{/block} + +{$foo} // wypisze: oryginalny +``` + + +`{define}` +---------- +Tag `{define}` służy do tworzenia bloków, które renderują się dopiero po ich wywołaniu za pomocą `{include}`. Zmienne dostępne wewnątrz tych bloków zależą od tego, czy w definicji podano parametry. Jeśli tak, dostęp mają tylko do tych parametrów. Jeśli nie, dostęp mają do wszystkich zmiennych wejściowych szablonu, w którym bloki są zdefiniowane. + +```latte +{define hello} + {* ma dostęp do wszystkich zmiennych wejściowych szablonu *} +{/define} + +{define hello $name} + {* ma dostęp tylko do parametru $name *} +{/define} +``` + + +`{parameters}` +-------------- +Tag `{parameters}` służy do jawnej deklaracji oczekiwanych zmiennych wejściowych na początku szablonu. W ten sposób można łatwo dokumentować oczekiwane zmienne i ich typy danych. Można również zdefiniować wartości domyślne. + +```latte +{parameters int $age, string $name = 'nieznane'} +

                                                                                        Wiek: {$age}, Imię: {$name}

                                                                                        +``` + + +`{include file}` +---------------- +Tag `{include file}` służy do wstawienia całego szablonu. Do tego szablonu przekazywane są zarówno zmienne wejściowe szablonu, w którym znacznik jest użyty, jak i zmienne w nim jawnie zdefiniowane. Szablon docelowy może jednak ograniczyć zakres za pomocą `{parameters}`. + +```latte +{include 'profile.latte', userId: $user->id} +``` + + +`{include block}` +----------------- +Gdy wstawiasz blok zdefiniowany w tym samym szablonie, przekazywane są do niego wszystkie zmienne otoczenia i jawnie zdefiniowane: + +```latte +{define blockName} +

                                                                                        Imię: {$name}, Wiek: {$age}

                                                                                        +{/define} + +{var $name = 'Jan', $age = 30} +{include blockName} +``` + +W tym przykładzie zmienne `$name` i `$age` zostaną przekazane do bloku `blockName`. W ten sam sposób zachowuje się również `{include parent}`. + +Podczas wstawiania bloku z innego szablonu przekazywane są tylko zmienne wejściowe i jawnie zdefiniowane. Zmienne otoczenia nie są automatycznie dostępne. + +```latte +{include blockInOtherTemplate, name: $name, age: $age} +``` + + +`{layout}` lub `{extends}` +-------------------------- +Te tagi definiują layout, do którego przekazywane są zmienne wejściowe szablonu podrzędnego oraz zmienne utworzone w kodzie przed blokami: + +```latte +{layout 'layout.latte'} +{var $seo = 'index, follow'} +``` + +Szablon `layout.latte`: + +```latte + + + +``` + + +`{embed}` +--------- +Tag `{embed}` jest podobny do tagu `{include}`, ale umożliwia wstawianie bloków do szablonu. W przeciwieństwie do `{include}` przekazywane są tylko jawnie zadeklarowane zmienne: + +```latte +{embed 'menu.latte', items: $menuItems} +{/embed} +``` + +W tym przykładzie szablon `menu.latte` ma dostęp tylko do zmiennej `$items`. + +Natomiast w blokach wewnątrz `{embed}` jest dostęp do wszystkich zmiennych otoczenia: + +```latte +{var $name = 'Jan'} +{embed 'menu.latte', items: $menuItems} + {block foo} + {$name} + {/block} +{/embed} +``` + + +`{import}` +---------- +Tag `{import}` służy do ładowania bloków z innych szablonów. Przekazywane są zarówno zmienne wejściowe, jak i jawnie zadeklarowane do importowanych bloków. + +```latte +{import 'buttons.latte'} +``` + + +`{sandbox}` +----------- +Tag `{sandbox}` izoluje szablon do bezpiecznego przetwarzania. Zmienne są przekazywane wyłącznie jawnie. + +```latte +{sandbox 'secure.latte', data: $secureData} +``` diff --git a/latte/pl/cookbook/slim-framework.texy b/latte/pl/cookbook/slim-framework.texy index bc70d40d2b..9afe988e39 100644 --- a/latte/pl/cookbook/slim-framework.texy +++ b/latte/pl/cookbook/slim-framework.texy @@ -1,8 +1,8 @@ -Używanie Latte z Slim 4 -*********************** +Używanie Latte ze Slim 4 +************************ .[perex] -Ten artykuł, którego autorem jest "Daniel Opitz":https://odan.github.io/2022/04/06/slim4-latte.html, opisuje użycie Latte z Slim Framework. +Ten artykuł, którego autorem jest "Daniel Opitz":https://odan.github.io/2022/04/06/slim4-latte.html, opisuje użycie Latte ze Slim Framework. Najpierw "zainstaluj Slim Framework":https://odan.github.io/2019/11/05/slim4-tutorial.html, a następnie Latte za pomocą Composera: @@ -11,33 +11,33 @@ composer require latte/latte ``` -Konfiguracja .[#toc-configuration] ----------------------------------- +Konfiguracja +------------ -Utwórz nowy katalog `templates` w katalogu głównym projektu. Wszystkie szablony zostaną w nim umieszczone później. +W katalogu głównym projektu utwórz nowy katalog `templates`. Wszystkie szablony zostaną do niego umieszczone później. -Dodaj nowy klucz konfiguracyjny `template` do pliku `config/defaults.php`: +Do pliku `config/defaults.php` dodaj nowy klucz konfiguracyjny `template`: ```php $settings['template'] = __DIR__ . '/../templates'; ``` -Latte kompiluje szablony do natywnego kodu PHP i buforuje je na dysku. Są więc tak szybkie, jakby były napisane w natywnym PHP. +Latte kompiluje szablony do natywnego kodu PHP i zapisuje je w pamięci podręcznej na dysku. Są więc tak samo szybkie, jakby były napisane w natywnym języku PHP. -Dodaj nowy klucz konfiguracyjny `template_temp` do pliku `config/defaults.php`: Upewnij się, że katalog `{project}/tmp/templates` istnieje i ma uprawnienia do odczytu i zapisu. +Do pliku `config/defaults.php` dodaj nowy klucz konfiguracyjny `template_temp`: Upewnij się, że katalog `{project}/tmp/templates` istnieje i ma prawa do odczytu i zapisu. ```php $settings['template_temp'] = __DIR__ . '/../tmp/templates'; ``` -Latte automatycznie regeneruje pamięć podręczną przy każdej zmianie szablonu, co można wyłączyć w środowisku produkcyjnym, aby zaoszczędzić trochę wydajności: +Latte automatycznie regeneruje pamięć podręczną przy każdej zmianie szablonu, co można w środowisku produkcyjnym wyłączyć i zaoszczędzić trochę wydajności: ```php -// zmiana na false w środowisku produkcyjnym +// w środowisku produkcyjnym zmień na false $settings['template_auto_refresh'] = true; ``` -Następnie dodaj definicję kontenera DI dla klasy `Latte\Engine`. +Dalej dodaj definicję kontenera DI dla klasy `Latte\Engine`. ```php +
                                                                                          {foreach $items as $item}
                                                                                        • {$item|capitalize}
                                                                                        • {/foreach}
                                                                                        ``` -Jeśli wszystko jest skonfigurowane poprawnie, powinieneś zobaczyć następujące wyjście: +Jeśli wszystko jest poprawnie skonfigurowane, powinien pojawić się następujący wynik: ```latte One @@ -155,4 +155,3 @@ Three ``` {{priority: -1}} -{{leftbar: /@left-menu}} diff --git a/latte/pl/creating-extension.texy b/latte/pl/creating-extension.texy deleted file mode 100644 index 94df5fd5c9..0000000000 --- a/latte/pl/creating-extension.texy +++ /dev/null @@ -1,579 +0,0 @@ -Tworzenie rozszerzenia -********************** - -.[perex]{data-version:3.0} -Rozszerzenie to klasa wielokrotnego użytku, która może definiować niestandardowe tagi, filtry, funkcje, dostawców itp. - -Tworzymy rozszerzenia, gdy chcemy ponownie użyć naszych dostosowań Latte w różnych projektach lub podzielić się nimi z innymi. -Warto również utworzyć rozszerzenie dla każdego projektu internetowego, które będzie zawierało wszystkie konkretne tagi i filtry, które chcesz użyć w szablonach projektu. - - -Klasa rozszerzona .[#toc-extension-class] -========================================= - -Extension jest klasą dziedziczącą po [api:Latte\Extension]. Jest ona rejestrowana do Latte za pomocą `addExtension()` (lub [pliku konfiguracyjnego |application:configuration#Latte]): - -```php -$latte = new Latte\Engine; -$latte->addExtension(new MyLatteExtension); -``` - -Jeśli zarejestrujesz wiele rozszerzeń i zdefiniują one identycznie nazwane tagi, filtry lub funkcje, wygrywa ostatnio dodane rozszerzenie. Oznacza to również, że twoje rozszerzenia mogą nadpisać natywne tagi / filtry / funkcje. - -Za każdym razem, gdy dokonasz zmiany w klasie i autoodświeżanie nie jest wyłączone, Latte automatycznie przekompiluje twoje szablony. - -Klasa może implementować dowolną z poniższych metod: - -```php -abstract class Extension -{ - /** - * Inicializace před kompilací šablony. - */ - public function beforeCompile(Engine $engine): void; - - /** - * Vrací seznam parserů pro značky Latte. - * @return array - */ - public function getTags(): array; - - /** - * Vrací seznam průchodů kompilátoru. - * @return array - */ - public function getPasses(): array; - - /** - * Vrací seznam |filtrů. - * @return array - */ - public function getFilters(): array; - - /** - * Vrací seznam funkcí použitých v šablonách. - * @return array - */ - public function getFunctions(): array; - - /** - * Vrací seznam providerů. - * @return array - */ - public function getProviders(): array; - - /** - * Vrací hodnotu pro rozlišení více verzí šablony. - */ - public function getCacheKey(Engine $engine): mixed; - - /** - * Inicializace před vykreslením šablony. - */ - public function beforeRender(Template $template): void; -} -``` - -Aby uzyskać pomysł, jak wygląda rozszerzenie, zobacz wbudowany "CoreExtension":https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php. - - -beforeCompile(Latte\Engine $engine): void .[method] ---------------------------------------------------- - -Jest on wywoływany przed kompilacją szablonu. Metoda ta może być używana na przykład do inicjalizacji związanych z kompilacją. - - -getTags(): array .[method] --------------------------- - -Wywoływany podczas kompilacji szablonu. Zwraca tablicę asocjacyjną *nazwa [tagu |#Tag-Parsing-Function]=> callable*, które są [funkcjami parsowania tagów |#Tag-Parsing-Function]. - -```php -public function getTags(): array -{ - return [ - 'foo' => [FooNode::class, 'create'], - 'bar' => [BarNode::class, 'create'], - 'n:baz' => [NBazNode::class, 'create'], - // ... - ]; -} -``` - -Znacznik `n:baz` reprezentuje czysty n:atrybut, czyli jest to znacznik, który może być zapisany tylko jako atrybut. - -W przypadku znaczników `foo` i `bar`, Latte automatycznie rozpozna, czy są one sparowane, a jeśli tak, to automatycznie zapisze je z użyciem n:attributes, w tym warianty z przedrostkami `n:inner-foo` i `n:tag-foo`. - -O kolejności wykonania takich n:atrybutów decyduje ich kolejność w polu zwracanym przez `getTags()`. Tak więc, `n:foo` jest zawsze wykonywany przed `n:bar`, nawet jeśli atrybuty w znaczniku HTML są wymienione w odwrotnej kolejności jako `
                                                                                        `. - -Jeśli musisz określić kolejność n:atrybutów w wielu rozszerzeniach, użyj metody pomocniczej `order()`, gdzie parametr `before` lub `after` określa przed lub po jakich znacznikach znacznik jest uporządkowany. - -```php -public function getTags(): array -{ - return [ - 'foo' => self::order([FooNode::class, 'create'], before: 'bar')] - 'bar' => self::order([BarNode::class, 'create'], after: ['block', 'snippet'])] - ]; -} -``` - - -getPasses(): array .[method] ----------------------------- - -Wywoływany podczas kompilacji szablonu. Zwraca tablicę asocjacyjną *name pass => callable*, która jest funkcją reprezentującą tzw. [przejścia kompilatora |#Compiler-Passes], które przemierzają i modyfikują AST. - -Również w tym przypadku można zastosować metodę pomocniczą `order()`. Wartość parametrów `before` lub `after` może być `'*'` ze znaczeniem before/after all. - -```php -public function getPasses(): array -{ - return [ - 'optimize' => [Passes::class, 'optimizePass'], - 'sandbox' => self::order([$this, 'sandboxPass'], before: '*'), - // ... - ]; -} -``` - - -beforeRender(Latte\Engine $engine): void .[method] --------------------------------------------------- - -Jest on wywoływany przed każdym renderowaniem szablonu. Metoda może być wykorzystana np. do inicjalizacji zmiennych używanych podczas renderowania. - - -getFilters(): array .[method] ------------------------------ - -Jest on wywoływany przed wyrenderowaniem szablonu. Zwraca [filtry |extending-latte#Filters] jako tablicę asocjacyjną *nazwa [filtra |extending-latte#Filters] => callable*. - -```php -public function getFilters(): array -{ - return [ - 'batch' => [$this, 'batchFilter'], - 'trim' => [$this, 'trimFilter'], - // ... - ]; -} -``` - - -getFunctions(): array .[method] -------------------------------- - -Wywoływany przed wyrenderowaniem szablonu. Zwraca [funkcję |extending-latte#Functions] jako tablicę asocjacyjną *nazwa funkcji => callable*. - -```php -public function getFunctions(): array -{ - return [ - 'clamp' => [$this, 'clampFunction'], - 'divisibleBy' => [$this, 'divisibleByFunction'], - // ... - ]; -} -``` - - -getProviders(): array .[method] -------------------------------- - -Wywoływany przed wyrenderowaniem szablonu. Zwraca tablicę dostawców, które są zwykle obiektami, które używają tagów w czasie rzeczywistym. Dostęp do nich uzyskuje się poprzez stronę `$this->global->...`. - -```php -public function getProviders(): array -{ - return [ - 'myFoo' => $this->foo, - 'myBar' => $this->bar, - // ... - ]; -} -``` - - -getCacheKey(Latte\Engine $engine): mixed .[method] --------------------------------------------------- - -Jest on wywoływany przed wyrenderowaniem szablonu. Zwracana wartość staje się częścią klucza, którego hash zawarty jest w nazwie skompilowanego pliku szablonu. Tak więc dla różnych wartości zwrotnych Latte wygeneruje różne pliki pamięci podręcznej. - - -Jak działa Latte? .[#toc-how-does-latte-work] -============================================= - -Aby zrozumieć, jak zdefiniować niestandardowe tagi lub przejścia kompilatora, konieczne jest zrozumienie, jak Latte działa pod maską. - -Kompilacja szablonów w Latte jest uproszczona w następujący sposób: - -- Najpierw **lexer** tokenizuje kod źródłowy szablonu na małe części (tokeny) dla łatwiejszego przetwarzania. -- Następnie **parser** przekształca strumień tokenów w sensowne drzewo węzłów (abstrakcyjne drzewo składniowe, AST). -- Na koniec kompilator **generuje** klasę PHP z AST, która renderuje szablon i buforuje go. - -W rzeczywistości kompilacja jest nieco bardziej skomplikowana. Latte **posiada** dwa lexery i parsery: jeden dla szablonu HTML, a drugi dla kodu podobnego do PHP wewnątrz znaczników. Również parsowanie nie jest uruchamiane po tokenizacji, ale lexer i parser działają równolegle w dwóch "wątkach" i koordynują. To rocket science :-) - -Ponadto wszystkie tagi mają swoje własne procedury parsowania. Gdy parser napotka znacznik, wywołuje swoją funkcję parsującą (zwraca ona [Extension::getTags()) |#getTags]. -Ich zadaniem jest parsowanie argumentów znaczników oraz, w przypadku znaczników sparowanych, wewnętrznej treści. Zwraca *węzeł*, który staje się częścią AST. Zobacz sekcję [Funkcje parsowania znaczników |#Tag-Parsing-Function], aby uzyskać szczegółowe informacje. - -Kiedy parser zakończy swoją pracę, mamy kompletny AST reprezentujący szablon. Węzeł główny to `Latte\Compiler\Nodes\TemplateNode`. Poszczególne węzły wewnątrz drzewa reprezentują więc nie tylko znaczniki, ale także elementy HTML, ich atrybuty, wszelkie wyrażenia użyte wewnątrz znaczników itd. - -Następnie przychodzą tzw. [Compiler Passes |#Compiler-Passes], czyli funkcje (zwracane przez [Extension::getPasses()) |#getPasses], które modyfikują AST. - -Cały proces, od ładowania zawartości szablonu, przez parsowanie, po generowanie ostatecznego pliku, może być sekwencjonowany za pomocą tego kodu, z którym możesz eksperymentować i zrzucać pośrednie kroki: - -```php -$latte = new Latte\Engine; -$source = $latte->getLoader()->getContent($file); -$ast = $latte->parse($source); -$latte->applyPasses($ast); -$code = $latte->generate($ast, $file); -``` - - -Przykład AST .[#toc-example-of-ast] ------------------------------------ - -Aby lepiej zapoznać się z formą AST, dodajemy próbkę. To jest szablon źródłowy: - -```latte -{foreach $category->getItems() as $item} -
                                                                                      1. {$item->name|upper}
                                                                                      2. - {else} - no items found -{/foreach} -``` - -I to jest jego reprezentacja w postaci AST: - -/--pre -Latte\Compiler\Nodes\TemplateNode( - Latte\Compiler\Nodes\FragmentNode( - - Latte\Essential\Nodes\ForeachNode( - expression: Latte\Compiler\Nodes\Php\Expression\MethodCallNode( - object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$category') - name: Latte\Compiler\Nodes\Php\IdentifierNode('getItems') - ) - value: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') - content: Latte\Compiler\Nodes\FragmentNode( - - Latte\Compiler\Nodes\TextNode(' ') - - Latte\Compiler\Nodes\Html\ElementNode('li')( - content: Latte\Essential\Nodes\PrintNode( - expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode( - object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') - name: Latte\Compiler\Nodes\Php\IdentifierNode('name') - ) - modifier: Latte\Compiler\Nodes\Php\ModifierNode( - filters: - - Latte\Compiler\Nodes\Php\FilterNode('upper') - ) - ) - ) - ) - else: Latte\Compiler\Nodes\FragmentNode( - - Latte\Compiler\Nodes\TextNode('no items found') - ) - ) - ) -) -\-- - - -Tagi własne .[#toc-custom-tags] -=============================== - -Do zdefiniowania nowego znacznika wymagane są trzy kroki: - -- Definiowanie [funkcji parsowania tagów |#Tag-Parsing-Function] (odpowiedzialnej za parsowanie tagu do węzła). -- stworzenie klasy node (odpowiedzialnej za [generowanie kodu PHP |#Generating-PHP-Code] i [przemierzanie AST |#AST-Traversing]) -- rejestrowanie tagu za pomocą [Extension::getTags() |#getTags] - - -Funkcja parsowania znaczników .[#toc-tag-parsing-function] ----------------------------------------------------------- - -Parsowanie tagów jest obsługiwane przez funkcję parsującą (tę zwróconą przez [Extension::getTags()) |#getTags]. Jej zadaniem jest parsowanie i sprawdzenie, czy wewnątrz tagu nie ma żadnych argumentów (wykorzystuje do tego TagParser). -Ponadto, jeśli tag jest parą, poprosi TemplateParser o parsowanie i zwrócenie wewnętrznej zawartości. -Funkcja tworzy i zwraca węzeł, który zwykle jest dzieckiem `Latte\Compiler\Nodes\StatementNode`, a ten staje się częścią AST. - -Tworzymy klasę dla każdego węzła, co teraz zrobimy, i zgrabnie umieszczamy w niej funkcję parsowania jako statyczną fabrykę. Jako przykład spróbujmy stworzyć znany nam już znacznik `{foreach}`: - -```php -use Latte\Compiler\Nodes\StatementNode; - -class ForeachNode extends StatementNode -{ - // funkcja parsowania, która na razie tworzy tylko węzeł - public static function create(Latte\Compiler\Tag $tag): self - { - $node = new self; - return $node; - } - - public function print(Latte\Compiler\PrintContext $context): string - { - // kod, który zostanie dodany później - } - - public function &getIterator(): \Generator - { - // kod, który zostanie dodany później - } -} -``` - -Funkcji parsującej `create()` przekazywany jest obiekt [api:Latte\Compiler\Tag], który przenosi podstawowe informacje o znaczniku (czy jest to klasyczny znacznik, czy n:atrybut, w jakiej linii się znajduje itp.), a przede wszystkim udostępnia [api:Latte\Compiler\TagParser] w `$tag->parser`. - -Jeśli znacznik musi mieć argumenty, sprawdzamy ich istnienie wywołując `$tag->expectArguments()`. Do ich parsowania dostępne są metody obiektu `$tag->parser`: - -- `parseExpression(): ExpressionNode` dla wyrażenia podobnego do PHP (np. `10 + 3`) -- `parseUnquotedStringOrExpression(): ExpressionNode` dla wyrażenia lub *unquoted-string*. -- `parseArguments(): ArrayNode` dla zawartości tablicy (np. `10, true, foo => bar`) -- `parseModifier(): ModifierNode` dla modyfikatora (np. `|upper|truncate:10`) -- `parseType(): ExpressionNode` dla typehint (np. `int|string` lub `Foo\Bar[]`) - -a następnie niskopoziomowy [api:Latte\Compiler\TokenStream] działający bezpośrednio na tokenach: - -- `$tag->parser->stream->consume(...): Token` -- `$tag->parser->stream->tryConsume(...): ?Token` - -Latte rozszerza składnię PHP w drobny sposób, na przykład dodając modyfikatory, skrócone operatory trójskładnikowe lub pozwalając na pisanie prostych ciągów alfanumerycznych bez cudzysłowów. Dlatego właśnie używamy określenia *PHP-like* zamiast PHP. W ten sposób metoda `parseExpression()` parsuje np. `foo` jako `'foo'`. -Ponadto *unquoted-string* jest specjalnym przypadkiem ciągu znaków, który również nie musi być cytowany, ale jednocześnie nie musi być alfanumeryczny. Na przykład ścieżka do pliku w znaczniku `{include ../file.latte}`. Do jego parsowania używana jest metoda `parseUnquotedStringOrExpression()`. - -.[note] -Studiowanie klas węzłów, które są częścią Latte, jest najlepszym sposobem na poznanie wszystkich szczegółów procesu parsowania. - -Wróćmy do znacznika `{foreach}`. W nim oczekujemy argumentów o postaci `výraz + 'as' + druhý výraz` i parsujemy je w następujący sposób: - -```php -use Latte\Compiler\Nodes\StatementNode; -use Latte\Compiler\Nodes\Php\ExpressionNode; -use Latte\Compiler\Nodes\AreaNode; - -class ForeachNode extends StatementNode -{ - public ExpressionNode $expression; - public ExpressionNode $value; - - public static function create(Latte\Compiler\Tag $tag): self - { - $tag->expectArguments(); - $node = new self; - $node->expression = $tag->parser->parseExpression(); - $tag->parser->stream->consume('as'); - $node->value = $parser->parseExpression(); - return $node; - } -} -``` - -Wyrażenia, które wpisaliśmy do zmiennych `$expression` i `$value`, reprezentują węzły podrzędne. - -.[tip] -Definiuj zmienne z węzłami podrzędnymi jako **publiczne**, aby można je było modyfikować w [kolejnych |#Compiler-Passes] krokach przetwarzania. Jednocześnie muszą być one **udostępnione** do [przeglądania |#AST-Traversing]. - -Dla sparowanych tagów, takich jak nasze, metoda musi nadal pozwalać TemplateParser parsować wnętrze tagu. Zajmuje się tym `yield`, który zwraca parę ''[zawartość wewnętrzna, tag końcowy]''. Zawartość wewnętrzną przechowujemy w zmiennej `$node->content`. - -```php -public AreaNode $content; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $endTag] = yield; - return $node; -} -``` - -Słowo kluczowe `yield` powoduje przerwanie metody `create()`, kierując ją z powrotem do TemplateParser, który kontynuuje parsowanie treści aż do trafienia na znacznik end. Następnie przekazuje kontrolę z powrotem do `create()`, która kontynuuje od miejsca, w którym się skończyła. Użycie metody `yield` automatycznie zwraca `Generator`. - -Możesz również przekazać tablicę nazw tagów do `yield`, aby zatrzymać przetwarzanie, jeśli wystąpią one przed tagiem końcowym. Dzięki temu będziemy mogli zaimplementować konstrukcję `{foreach}...{else}...{/foreach}`. Jeśli pojawia się `{else}`, to treść po nim parsujemy na `$node->elseContent`: - -```php -public AreaNode $content; -public ?AreaNode $elseContent = null; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $nextTag] = yield ['else']; - if ($nextTag?->name === 'else') { - [$node->elseContent] = yield; - } - - return $node; -} -``` - -Zwrócenie węzła kończy parsowanie znacznika. - - -Generowanie kodu PHP .[#toc-generating-php-code] ------------------------------------------------- - -Każdy węzeł musi implementować metodę `print()`. Zwraca ona kod PHP renderujący podany fragment szablonu (kod runtime). Jako parametr przekazywany jest obiekt [api:Latte\Compiler\PrintContext], który posiada przydatną metodę `format()` upraszczającą kompilację kodu wynikowego. - -Metoda `format(string $mask, ...$args)` akceptuje w masce następujące placeholdery: -- `%node` wymienia węzeł -- `%dump` eksportuje wartość do PHP -- `%raw` wstawia tekst bezpośrednio bez żadnych przekształceń -- `%args` wypisuje ArrayNode jako argumenty do wywołania funkcji -- `%line` wypisuje komentarz z numerem linii -- `%escape(...)` ucieka z treści -- `%modify(...)` stosuje modyfikator -- `%modifyContent(...)` stosuje modyfikator dla bloków - - -Nasza funkcja `print()` może wyglądać tak (dla uproszczenia zaniedbujemy gałąź `else` ): - -```php -public function print(Latte\Compiler\PrintContext $context): string -{ - return $context->format( - <<<'XX' - foreach (%node as %node) %line { - %node - } - - XX, - $this->expression, - $this->value, - $this->position, - $this->content, - ); -} -``` - -Zmienna `$this->position` jest już zdefiniowana przez klasę [api:Latte\Compiler\Node] i ustawiona przez parser. Zawiera obiekt [api:Latte\Compiler\Position] z pozycją znacznika w kodzie źródłowym w postaci numeru wiersza i kolumny. - -Kod runtime może używać zmiennych pomocniczych. Aby uniknąć kolizji ze zmiennymi używanymi przez sam szablon, zwyczajowo poprzedzamy je znakiem `$ʟ__`. - -Może również używać dowolnych wartości w czasie runtime, które przekazuje do szablonu w postaci tzw. providerów za pomocą metody [Extension::getProviders() |#getProviders]. Dostęp do nich uzyskuje się za pomocą `$this->global->...`. - - -Przeglądanie AST .[#toc-ast-traversing] ---------------------------------------- - -W celu dogłębnego przeglądania drzewa AST konieczne jest zaimplementowanie metody `getIterator()`: - -```php -public function &getIterator(): \Generator -{ - yield $this->expression; - yield $this->value; - yield $this->content; - if ($this->elseContent) { - yield $this->elseContent; - } -} -``` - -Zauważ, że `getIterator()` zwraca referencje. Dzięki temu *odwiedzający węzły* mogą zastępować poszczególne węzły innymi. - -.[warning] -Jeśli węzeł ma subnody, konieczne jest zaimplementowanie tej metody i udostępnienie wszystkich subnodów. W przeciwnym razie może powstać dziura w zabezpieczeniach. Na przykład tryb piaskownicy nie byłby w stanie sprawdzić węzłów podrzędnych i zapewnić, że nie są na nich wywoływane nieautoryzowane konstrukcje. - -Ponieważ słowo kluczowe `yield` musi być obecne w ciele metody, nawet jeśli nie ma węzłów dzieci, napisz to w następujący sposób: - -```php -public function &getIterator(): \Generator -{ - if (false) { - yield; - } -} -``` - - -Kompilator przechodzi .[#toc-compiler-passes] -============================================= - -Przejścia kompilatora to funkcje, które modyfikują AST lub zbierają w nich informacje. Są one zwracane przez metodę [Extension::getPasses() |#getPasses]. - - -Przeszukiwanie węzłów .[#toc-node-traverser] --------------------------------------------- - -Najczęstszym sposobem pracy z AST jest użycie [api:Latte\Compiler\NodeTraverser]: - -```php -use Latte\Compiler\Node; -use Latte\Compiler\NodeTraverser; - -$ast = (new NodeTraverser)->traverse( - $ast, - enter: fn(Node $node) => ..., - leave: fn(Node $node) => ..., -); -``` - -Funkcja *enter* (tj. node visitor) jest wywoływana przy pierwszym napotkaniu węzła, zanim zostaną przetworzone jego podwęzły. Funkcja *leave* jest wywoływana po odwiedzeniu wszystkich węzłów podrzędnych. -Powszechną praktyką jest to, że funkcja *enter* służy do zebrania pewnych informacji, a następnie funkcja *leave* dokonuje korekt na podstawie tych informacji. Do czasu wywołania funkcji *leave*, cały kod wewnątrz węzła zostanie odwiedzony i zebrane zostaną niezbędne informacje. - -Jak zmodyfikować AST? Najprostszym sposobem jest po prostu zmodyfikowanie właściwości węzłów. Drugim sposobem jest całkowite zastąpienie węzła poprzez zwrócenie nowego węzła. Przykład: poniższy kod zmieni wszystkie liczby całkowite w AST na łańcuchy (np. 42 zostanie zmienione na `'42'`). - -```php -use Latte\Compiler\Nodes\Php; - -$ast = (new NodeTraverser)->traverse( - $ast, - leave: function (Node $node) { - if ($node instanceof Php\Scalar\IntegerNode) { - return new Php\Scalar\StringNode((string) $node->value); - } - }, -); -``` - -Moduł AST może z łatwością zawierać tysiące węzłów, a przemierzanie ich wszystkich może być powolne. W niektórych przypadkach można uniknąć całkowitego traversal. - -Jeśli przeszukasz drzewo dla wszystkich węzłów `Html\ElementNode`, to wiesz, że gdy zobaczysz węzeł `Php\ExpressionNode`, nie ma sensu sprawdzać również wszystkich jego węzłów dziecięcych, ponieważ HTML nie może być wewnątrz wyrażeń. W tym przypadku możesz powiedzieć traverserowi, aby nie wykonywał rekurencji do węzła klasy: - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Php\ExpressionNode) { - return NodeTraverser::DontTraverseChildren; - } - // ... - }, -); -``` - -Jeśli szukasz tylko jednego konkretnego węzła, możliwe jest również całkowite przerwanie traversal po znalezieniu go. - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Nodes\ParametersNode) { - return NodeTraverser::StopTraversal; - } - // ... - }, -); -``` - - -Pomocnicy dla węzłów .[#toc-node-helpers] ------------------------------------------ - -Klasa [api:Latte\Compiler\NodeHelpers] zapewnia pewne metody, które mogą znaleźć węzły AST, które albo spełniają określony warunek, itp. Kilka przykładów: - -```php -use Latte\Compiler\NodeHelpers; - -// znajduje wszystkie węzły elementów HTML -$elements = NodeHelpers::find($ast, fn(Node $node) => $node instanceof Nodes\Html\ElementNode); - -// znajduje pierwszy węzeł tekstowy -$text = NodeHelpers::findFirst($ast, fn(Node $node) => $node instanceof Nodes\TextNode); - -// konwertuje węzeł PHP na wartość rzeczywistą -$value = NodeHelpers::toValue($node); - -// konwertuje statyczny węzeł tekstowy na ciąg znaków -$text = NodeHelpers::toText($node); -``` diff --git a/latte/pl/custom-filters.texy b/latte/pl/custom-filters.texy new file mode 100644 index 0000000000..f0df0a41c8 --- /dev/null +++ b/latte/pl/custom-filters.texy @@ -0,0 +1,231 @@ +Tworzenie własnych filtrów +************************** + +.[perex] +Filtry są potężnymi narzędziami do formatowania i modyfikacji danych bezpośrednio w szablonach Latte. Oferują czystą składnię za pomocą symbolu potoku (`|`) do transformacji zmiennych lub wyników wyrażeń do pożądanego formatu wyjściowego. + + +Co to są filtry? +================ + +Filtry w Latte to w zasadzie **funkcje PHP zaprojektowane specjalnie do transformacji wartości wejściowej na wartość wyjściową**. Stosuje się je za pomocą zapisu z potokiem (`|`) wewnątrz wyrażeń szablonu (`{...}`). + +**Wygoda:** Filtry pozwalają zamknąć typowe zadania formatowania (jak formatowanie dat, zmiana wielkości liter, skracanie) lub manipulacji danymi w reużywalnych jednostkach. Zamiast powtarzać złożony kod PHP w szablonach, możesz po prostu zastosować filtr: +```latte +{* Zamiast złożonego PHP do skrócenia: *} +{$article->text|truncate:100} + +{* Zamiast kodu do formatowania daty: *} +{$event->startTime|date:'Y-m-d H:i'} + +{* Zastosowanie wielu transformacji: *} +{$product->name|lower|capitalize} +``` + +**Czytelność:** Używanie filtrów sprawia, że szablony są bardziej przejrzyste i skoncentrowane na prezentacji, ponieważ logika transformacji jest przeniesiona do definicji filtra. + +**Wrażliwość kontekstowa:** Kluczową zaletą filtrów w Latte jest ich zdolność do bycia [wrażliwymi kontekstowo |#Filtry kontekstowe]. Oznacza to, że filtr może rozpoznać typ zawartości, z którą pracuje (HTML, JavaScript, zwykły tekst itp.) i zastosować odpowiednią logikę lub escapowanie, co jest kluczowe dla bezpieczeństwa i poprawności, zwłaszcza przy generowaniu HTML. + +**Integracja z logiką aplikacji:** Podobnie jak własne funkcje, PHP callable stojący za filtrem może być domknięciem (closure), metodą statyczną lub metodą instancji. Pozwala to filtrom na dostęp do usług aplikacji lub danych, jeśli jest to potrzebne, chociaż ich głównym celem pozostaje *transformacja wartości wejściowej*. + +Latte domyślnie dostarcza bogaty zestaw [standardowych filtrów |filters]. Własne filtry pozwalają rozszerzyć ten zestaw o formatowanie i transformacje specyficzne dla Twojego projektu. + +Jeśli potrzebujesz wykonywać logikę opartą na *wielu* wejściach lub nie masz pierwotnej wartości do transformacji, prawdopodobnie bardziej odpowiednie jest użycie [własnej funkcji |custom-functions]. Jeśli potrzebujesz generować złożony markup lub sterować przepływem szablonu, rozważ [własny tag |custom-tags]. + + +Tworzenie i rejestracja filtrów +=============================== + +Istnieje kilka sposobów definiowania i rejestrowania własnych filtrów w Latte. + + +Bezpośrednia rejestracja za pomocą `addFilter()` +------------------------------------------------ + +Najprostszym sposobem dodania filtra jest użycie metody `addFilter()` bezpośrednio na obiekcie `Latte\Engine`. Podajesz nazwę filtra (jak będzie używany w szablonie) i odpowiadający PHP callable. + +```php +$latte = new Latte\Engine; + +// Prosty filtr bez argumentów +$latte->addFilter('initial', fn(string $s): string => mb_substr($s, 0, 1) . '.'); + +// Filtr z opcjonalnym argumentem +$latte->addFilter('shortify', function (string $s, int $len = 10): string { + return mb_substr($s, 0, $len); +}); + +// Filtr przetwarzający tablicę +$latte->addFilter('sum', fn(array $numbers): int|float => array_sum($numbers)); +``` + +**Użycie w szablonie:** + +```latte +{$name|initial} {* Wypisze 'J.' jeśli $name to 'John' *} +{$description|shortify} {* Użyje domyślnej długości 10 *} +{$description|shortify:50} {* Użyje długości 50 *} +{$prices|sum} {* Wypisze sumę elementów w tablicy $prices *} +``` + +**Przekazywanie argumentów:** + +Wartość na lewo od potoku (`|`) jest zawsze przekazywana jako *pierwszy* argument funkcji filtra. Wszelkie parametry podane za dwukropkiem (`:`) w szablonie są przekazywane jako kolejne argumenty. + +```latte +{$text|shortify:30} +// Wywołuje funkcję PHP shortify($text, 30) +``` + + +Rejestracja za pomocą rozszerzenia +---------------------------------- + +Dla lepszej organizacji, zwłaszcza przy tworzeniu reużywalnych zestawów filtrów lub udostępnianiu ich jako pakiety, zalecanym sposobem jest rejestrowanie ich w ramach [rozszerzenia Latte |extending-latte#Latte Extension]: + +```php +namespace App\Latte; + +use Latte\Extension; + +class MyLatteExtension extends Extension +{ + public function getFilters(): array + { + return [ + 'initial' => $this->initial(...), + 'shortify' => $this->shortify(...), + ]; + } + + public function initial(string $s): string + { + return mb_substr($s, 0, 1) . '.'; + } + + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// Rejestracja +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +To podejście pozwala utrzymać logikę filtra w kapsułce, a rejestrację prostą. + + +Użycie loadera filtrów +---------------------- + +Latte umożliwia rejestrowanie loadera filtrów za pomocą `addFilterLoader()`. Jest to pojedynczy callable, o który Latte zapyta podczas kompilacji dla każdej nieznanej nazwy filtra. Loader zwraca PHP callable filtra lub `null`. + +```php +$latte = new Latte\Engine; + +// Loader może dynamicznie tworzyć/pobierać callable filtrów +$latte->addFilterLoader(function (string $name): ?callable { + if ($name === 'myLazyFilter') { + // Wyobraź sobie tutaj kosztowną inicjalizację... + $service = get_some_expensive_service(); + return fn($value) => $service->process($value); + } + return null; +}); +``` + +Ta metoda była pierwotnie przeznaczona do leniwego ładowania filtrów z bardzo **kosztowną inicjalizacją**. Jednak nowoczesne praktyki wstrzykiwania zależności (dependency injection) zazwyczaj radzą sobie z leniwymi usługami efektywniej. + +Loadery filtrów dodają złożoność i generalnie nie są zalecane na rzecz bezpośredniej rejestracji za pomocą `addFilter()` lub w ramach rozszerzenia za pomocą `getFilters()`. Używaj loaderów tylko wtedy, gdy masz poważny, specyficzny powód związany z problemami wydajnościowymi przy inicjalizacji filtrów, których nie można rozwiązać inaczej. + + +Filtry używające klasy z atrybutami +----------------------------------- + +Kolejny elegancki sposób definiowania filtrów to użycie metod w Twojej [klasie parametrów szablonu |develop#Parametry jako klasa]. Wystarczy dodać atrybut `#[Latte\Attributes\TemplateFilter]` do metody. + +```php +use Latte\Attributes\TemplateFilter; + +class TemplateParameters +{ + public function __construct( + public string $description, + // inne parametry... + ) {} + + #[TemplateFilter] + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// Przekazanie obiektu do szablonu +$params = new TemplateParameters(description: '...'); +$latte->render('template.latte', $params); +``` + +Latte automatycznie rozpozna i zarejestruje metody oznaczone tym atrybutem, gdy obiekt `TemplateParameters` zostanie przekazany do szablonu. Nazwa filtra w szablonie będzie taka sama jak nazwa metody (`shortify` w tym przypadku). + +```latte +{* Użycie filtra zdefiniowanego w klasie parametrów *} +{$description|shortify:50} +``` + + +Filtry kontekstowe +================== + +Czasami filtr potrzebuje więcej informacji niż tylko wartość wejściowa. Może potrzebować znać **typ zawartości** ciągu znaków, z którym pracuje (np. HTML, JavaScript, zwykły tekst) lub nawet go zmodyfikować. To jest sytuacja dla filtrów kontekstowych. + +Filtr kontekstowy jest definiowany tak samo jak zwykły filtr, ale jego **pierwszy parametr musi być** oznaczony typem `Latte\Runtime\FilterInfo`. Latte automatycznie rozpoznaje ten podpis i przy wywołaniu filtra przekazuje obiekt `FilterInfo`. Kolejne parametry otrzymają argumenty filtra jak zwykle. + +```php +use Latte\Runtime\FilterInfo; +use Latte\ContentType; + +$latte->addFilter('money', function (FilterInfo $info, float $amount): string { + // 1. Sprawdź typ zawartości wejściowej (opcjonalne, ale zalecane) + // Zezwól na null (zmienne wejście) lub zwykły tekst. Odrzuć, jeśli jest stosowany do HTML itp. + if (!in_array($info->contentType, [null, ContentType::Text], true)) { + $actualType = $info->contentType ?? 'mixed'; + throw new \RuntimeException( + "Filtr |money użyty w niekompatybilnym typie zawartości $actualType. Oczekiwano tekstu lub null." + ); + } + + // 2. Wykonaj transformację + $formatted = number_format($amount, 2, '.', ',') . ' EUR'; + $htmlOutput = '' . htmlspecialchars($formatted) . ''; // Zapewnij poprawne escapowanie! + + // 3. Zadeklaruj typ zawartości wyjściowej + $info->contentType = ContentType::Html; + + // 4. Zwróć wynik + return $htmlOutput; +}); +``` + +`$info->contentType` jest stałą tekstową z `Latte\ContentType` (np. `ContentType::Html`, `ContentType::Text`, `ContentType::JavaScript`, itd.) lub `null`, jeśli filtr jest stosowany do zmiennej (`{$var|filter}`). Możesz tę wartość **odczytać**, aby sprawdzić kontekst wejściowy, i **zapisać** do niej, aby zadeklarować typ kontekstu wyjściowego. + +Ustawiając typ zawartości na HTML, informujesz Latte, że ciąg znaków zwrócony przez Twój filtr jest bezpiecznym HTML. Latte następnie **nie będzie** stosować swojego domyślnego automatycznego escapowania do tego wyniku. Jest to kluczowe, jeśli Twój filtr generuje markup HTML. + +.[warning] +Jeśli Twój filtr generuje HTML, **jesteś odpowiedzialny za poprawne escapowanie wszelkich danych wejściowych** użytych w tym HTML (jak w przypadku wywołania `htmlspecialchars($formatted)` powyżej). Pominięcie tego może stworzyć luki XSS. Jeśli Twój filtr zwraca tylko zwykły tekst, nie musisz ustawiać `$info->contentType`. + + +Filtry na blokach +----------------- + +Wszystkie filtry stosowane do [bloków |tags#block] *muszą* być kontekstowe. Dzieje się tak, ponieważ zawartość bloku ma zdefiniowany typ zawartości (zazwyczaj HTML), którego filtr musi być świadomy. + +```latte +{block heading|money}1000{/block} +{* Filtr 'money' otrzyma '1000' jako drugi argument + a $info->contentType będzie ContentType::Html *} +``` + +Filtry kontekstowe zapewniają silną kontrolę nad tym, jak dane są przetwarzane na podstawie ich kontekstu, umożliwiają zaawansowane funkcje i zapewniają poprawne zachowanie escapowania, zwłaszcza przy generowaniu zawartości HTML. diff --git a/latte/pl/custom-functions.texy b/latte/pl/custom-functions.texy new file mode 100644 index 0000000000..adf8100926 --- /dev/null +++ b/latte/pl/custom-functions.texy @@ -0,0 +1,144 @@ +Tworzenie własnych funkcji +************************** + +.[perex] +Łatwo dodaj do szablonów Latte własne funkcje pomocnicze. Wywołuj logikę PHP bezpośrednio w wyrażeniach do obliczeń, dostępu do usług lub generowania dynamicznej zawartości, co pozwoli utrzymać szablony czyste i wydajne. + + +Co to są funkcje? +================= + +Funkcje w Latte pozwalają rozszerzyć zestaw funkcji, które można wywoływać w ramach wyrażeń w szablonach (`{...}`). Możesz je sobie wyobrazić jako **własne funkcje PHP dostępne tylko wewnątrz Twoich szablonów Latte**. Przynosi to kilka korzyści: + +**Wygoda:** Możesz zdefiniować logikę pomocniczą (jak obliczenia, formatowanie lub dostęp do danych aplikacji) i wywoływać ją za pomocą prostej, znanej składni funkcji bezpośrednio w szablonie, tak jakbyś wywoływał `strlen()` lub `date()` w PHP. + +```latte +{var $userInitials = initials($userName)} {* np. 'J. D.' *} + +{if hasPermission('article', 'edit')} + Edytuj +{/if} +``` + +**Bez zanieczyszczania globalnej przestrzeni:** W przeciwieństwie do definiowania prawdziwej globalnej funkcji w PHP, funkcje Latte istnieją tylko w kontekście renderowania szablonu. Nie musisz obciążać globalnej przestrzeni nazw PHP pomocnikami, które są specyficzne tylko dla szablonów. + +**Integracja z logiką aplikacji:** PHP callable stojący za funkcją Latte może być czymkolwiek – funkcją anonimową, metodą statyczną lub metodą instancji. Oznacza to, że Twoje funkcje w szablonach mogą łatwo uzyskiwać dostęp do usług aplikacji, baz danych, konfiguracji lub jakiejkolwiek innej potrzebnej logiki poprzez przechwytywanie zmiennych (w przypadku funkcji anonimowych) lub za pomocą wstrzykiwania zależności (w przypadku obiektów). Powyższy przykład `hasPermission` jasno to demonstruje, gdy prawdopodobnie wywołuje w tle usługę autoryzacji. + +**Nadpisywanie funkcji natywnych (opcjonalnie):** Możesz nawet zdefiniować funkcję Latte o tej samej nazwie co natywna funkcja PHP. W szablonie zostanie wywołana Twoja własna wersja zamiast oryginalnej funkcji. Może to być przydatne do zapewnienia zachowania specyficznego dla szablonu lub zapewnienia spójnego przetwarzania (np. zapewnienie, że `strlen` będzie zawsze bezpieczny dla wielu bajtów). Używaj tej funkcji ostrożnie, aby uniknąć nieporozumień. + +Domyślnie Latte pozwala na wywoływanie *wszystkich* natywnych funkcji PHP (chyba że są ograniczone przez [Sandbox |sandbox]). Własne funkcje rozszerzają tę wbudowaną bibliotekę o specyficzne potrzeby Twojego projektu. + +Jeśli tylko transformujesz pojedynczą wartość, bardziej odpowiednie może być użycie [własnego filtra |custom-filters]. + + +Tworzenie i rejestracja funkcji +=============================== + +Podobnie jak w przypadku filtrów, istnieje kilka sposobów definiowania i rejestrowania własnych funkcji. + + +Bezpośrednia rejestracja za pomocą `addFunction()` +-------------------------------------------------- + +Najprostszą metodą jest użycie `addFunction()` na obiekcie `Latte\Engine`. Podajesz nazwę funkcji (jak będzie wyświetlana w szablonie) i odpowiadający PHP callable. + +```php +$latte = new Latte\Engine; + +// Prosta funkcja pomocnicza +$latte->addFunction('initials', function (string $name): string { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; +}); +``` + +**Użycie w szablonie:** + +```latte +{var $userInitials = initials($userName)} +``` + +Argumenty funkcji w szablonie są przekazywane bezpośrednio do PHP callable w tej samej kolejności. Funkcjonalności PHP takie jak podpowiedzi typów, wartości domyślne i parametry o zmiennej liczbie argumentów (`...`) działają zgodnie z oczekiwaniami. + + +Rejestracja za pomocą rozszerzenia +---------------------------------- + +Dla lepszej organizacji i reużywalności, rejestruj funkcje w ramach [rozszerzenia Latte |extending-latte#Latte Extension]. To podejście jest zalecane dla bardziej złożonych aplikacji lub współdzielonych bibliotek. + +```php +namespace App\Latte; + +use Latte\Extension; +use Nette\Security\Authorizator; + +class MyLatteExtension extends Extension +{ + public function __construct( + // Zakładamy, że usługa Authorizator istnieje + private Authorizator $authorizator, + ) { + } + + public function getFunctions(): array + { + // Rejestracja metod jako funkcji Latte + return [ + 'hasPermission' => $this->hasPermission(...), + ]; + } + + public function hasPermission(string $resource, string $action): bool + { + return $this->authorizator->isAllowed($resource, $action); + } +} + +// Rejestracja (zakładamy, że $container zawiera kontener DI) +$extension = $container->getByType(App\Latte\MyLatteExtension::class); +$latte = new Latte\Engine; +$latte->addExtension($extension); +``` + +To podejście ilustruje, jak funkcje zdefiniowane w Latte mogą być wspierane przez metody obiektów, które mogą mieć swoje własne zależności zarządzane przez kontener wstrzykiwania zależności Twojej aplikacji lub fabrykę. Pozwala to utrzymać logikę szablonów połączoną z rdzeniem aplikacji, zachowując jednocześnie przejrzystą organizację. + + +Funkcje używające klasy z atrybutami +------------------------------------ + +Podobnie jak filtry, funkcje mogą być definiowane jako metody w Twojej [klasie parametrów szablonu |develop#Parametry jako klasa] za pomocą atrybutu `#[Latte\Attributes\TemplateFunction]`. + +```php +use Latte\Attributes\TemplateFunction; + +class TemplateParameters +{ + public function __construct( + public string $userName, + // inne parametry... + ) {} + + // Ta metoda będzie dostępna jako {initials(...)} w szablonie + #[TemplateFunction] + public function initials(string $name): string + { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; + } +} + +// Przekazanie obiektu do szablonu +$params = new TemplateParameters(userName: 'John Doe', /* ... */); +$latte->render('template.latte', $params); +``` + +Latte automatycznie odkryje i zarejestruje metody oznaczone tym atrybutem, gdy obiekt parametrów zostanie przekazany do szablonu. Nazwa funkcji w szablonie odpowiada nazwie metody. + +```latte +{* Użycie funkcji zdefiniowanej w klasie parametrów *} +{var $inits = initials($userName)} +``` + +**Funkcje kontekstowe?** + +W przeciwieństwie do filtrów, nie ma bezpośredniego pojęcia "funkcji kontekstowych", które otrzymałyby obiekt podobny do `FilterInfo`. Funkcje działają w ramach wyrażeń i zazwyczaj nie potrzebują bezpośredniego dostępu do kontekstu renderowania ani informacji o typie zawartości w taki sam sposób, jak filtry stosowane do bloków. diff --git a/latte/pl/custom-tags.texy b/latte/pl/custom-tags.texy new file mode 100644 index 0000000000..50be9f0562 --- /dev/null +++ b/latte/pl/custom-tags.texy @@ -0,0 +1,1135 @@ +Tworzenie własnych tagów +************************ + +.[perex] +Ta strona zawiera kompleksowy przewodnik dotyczący tworzenia własnych tagów w Latte. Omówimy wszystko, od prostych tagów po bardziej złożone scenariusze z zagnieżdżoną zawartością i specyficznymi potrzebami parsowania, opierając się na zrozumieniu, jak Latte kompiluje szablony. + +Własne tagi zapewniają najwyższy poziom kontroli nad składnią szablonu i logiką renderowania, ale są również najbardziej złożonym punktem rozszerzenia. Zanim zdecydujesz się stworzyć własny tag, zawsze rozważ, czy [nie istnieje prostsze rozwiązanie |extending-latte#Sposoby rozszerzania Latte] lub czy odpowiedni tag nie istnieje już w [standardowym zestawie |tags]. Używaj własnych tagów tylko wtedy, gdy prostsze alternatywy nie są wystarczające dla Twoich potrzeb. + + +Zrozumienie procesu kompilacji +============================== + +Aby efektywnie tworzyć własne tagi, warto wyjaśnić, jak Latte przetwarza szablony. Zrozumienie tego procesu wyjaśnia, dlaczego tagi są skonstruowane w ten sposób i jak pasują do szerszego kontekstu. + +Kompilacja szablonu w Latte, w uproszczeniu, obejmuje następujące kluczowe kroki: + +1. **Analiza leksykalna:** Lekser odczytuje kod źródłowy szablonu (plik `.latte`) i dzieli go na sekwencję małych, odrębnych części zwanych **tokenami** (np. `{`, `foreach`, `$variable`, `}`, tekst HTML, itp.). +2. **Parsowanie:** Parser bierze ten strumień tokenów i konstruuje z niego sensowną strukturę drzewiastą reprezentującą logikę i zawartość szablonu. To drzewo nazywa się **abstrakcyjnym drzewem składniowym (AST)**. +3. **Przejścia kompilacji:** Przed wygenerowaniem kodu PHP Latte uruchamia [przejścia kompilacji |compiler passes]. Są to funkcje, które przechodzą przez całe AST i mogą je modyfikować lub zbierać informacje. Ten krok jest kluczowy dla funkcji takich jak bezpieczeństwo ([Sandbox |sandbox]) czy optymalizacje. +4. **Generowanie kodu:** Na koniec kompilator przechodzi przez (potencjalnie zmodyfikowane) AST i generuje odpowiedni kod klasy PHP. Ten kod PHP jest tym, co faktycznie renderuje szablon podczas uruchomienia. +5. **Caching:** Wygenerowany kod PHP jest zapisywany na dysku, co sprawia, że kolejne renderowania są bardzo szybkie, ponieważ kroki 1-4 są pomijane. + +W rzeczywistości kompilacja jest nieco bardziej skomplikowana. Latte **ma dwa** leksery i parsery: jeden dla szablonu HTML i drugi dla kodu podobnego do PHP wewnątrz tagów. A także parsowanie nie odbywa się dopiero po tokenizacji, ale lekser i parser działają równolegle w dwóch "wątkach" i koordynują się. Uwierzcie mi, zaprogramowanie tego było jak lot w kosmos :-) + +Cały proces, od załadowania zawartości szablonu, przez parsowanie, aż po wygenerowanie wynikowego pliku, można zsekwencjonować za pomocą tego kodu, z którym można eksperymentować i wypisywać wyniki pośrednie: + +```php +$latte = new Latte\Engine; +$source = $latte->getLoader()->getContent($file); +$ast = $latte->parse($source); +$latte->applyPasses($ast); +$code = $latte->generate($ast, $file); +``` + + +Anatomia tagu +============= + +Stworzenie w pełni funkcjonalnego własnego tagu w Latte obejmuje kilka powiązanych części. Zanim przejdziemy do implementacji, zrozummy podstawowe koncepcje i terminologię, wykorzystując analogię do HTML i Document Object Model (DOM). + + +Tagi vs. Węzły (Analogia z HTML) +-------------------------------- + +W HTML piszemy **tagi** takie jak `

                                                                                        ` lub `

                                                                                        ...
                                                                                        `. Te tagi są składnią w kodzie źródłowym. Kiedy przeglądarka parsuje ten HTML, tworzy reprezentację w pamięci zwaną **Document Object Model (DOM)**. W DOM tagi HTML są reprezentowane przez **węzły** (konkretnie węzły `Element` w terminologii JavaScriptowego DOM). Z tymi *węzłami* pracujemy programowo (np. za pomocą JavaScriptowego `document.getElementById(...)` zwracany jest węzeł Element). Tag jest tylko tekstową reprezentacją w pliku źródłowym; węzeł jest obiektową reprezentacją w logicznym drzewie. + +Latte działa podobnie: + +- W pliku szablonu `.latte` piszesz **tagi Latte**, takie jak `{foreach ...}` i `{/foreach}`. Jest to składnia, z którą pracujesz jako autor szablonu. +- Kiedy Latte **parsuje** szablon, buduje **Abstract Syntax Tree (AST)**. To drzewo składa się z **węzłów**. Każdy tag Latte, element HTML, fragment tekstu lub wyrażenie w szablonie staje się jednym lub więcej węzłami w tym drzewie. +- Podstawową klasą dla wszystkich węzłów w AST jest `Latte\Compiler\Node`. Podobnie jak DOM ma różne typy węzłów (Element, Text, Comment), AST Latte ma różne typy węzłów. Spotkasz `Latte\Compiler\Nodes\TextNode` dla statycznego tekstu, `Latte\Compiler\Nodes\Html\ElementNode` dla elementów HTML, `Latte\Compiler\Nodes\Php\ExpressionNode` dla wyrażeń wewnątrz tagów i, co kluczowe dla własnych tagów, węzły dziedziczące z `Latte\Compiler\Nodes\StatementNode`. + + +Dlaczego `StatementNode`? +------------------------- + +Elementy HTML (`Html\ElementNode`) głównie reprezentują strukturę i zawartość. Wyrażenia PHP (`Php\ExpressionNode`) reprezentują wartości lub obliczenia. Ale co z tagami Latte takimi jak `{if}`, `{foreach}` lub naszym własnym `{datetime}`? Te tagi *wykonują akcje*, kontrolują przepływ programu lub generują wyjście na podstawie logiki. Są to jednostki funkcjonalne, które czynią Latte potężnym *silnikiem* szablonów, a nie tylko językiem znaczników. + +W programowaniu takie jednostki wykonujące akcje często nazywane są "statements" (instrukcjami). Dlatego węzły reprezentujące te funkcjonalne tagi Latte zazwyczaj dziedziczą z `Latte\Compiler\Nodes\StatementNode`. To odróżnia je od czysto strukturalnych węzłów (jak elementy HTML) lub węzłów reprezentujących wartości (jak wyrażenia). + + +Kluczowe komponenty +=================== + +Przejdźmy przez główne komponenty potrzebne do stworzenia własnego tagu: + + +Funkcja parsowania tagu +----------------------- + +- Ta funkcja PHP typu callable parsuje składnię tagu Latte (`{...}`) w szablonie źródłowym. +- Otrzymuje informacje o tagu (takie jak jego nazwa, pozycja i czy jest to n:atrybut) za pośrednictwem obiektu [api:Latte\Compiler\Tag]. +- Jej głównym narzędziem do parsowania argumentów i wyrażeń wewnątrz ograniczników tagu jest obiekt [api:Latte\Compiler\TagParser], dostępny przez `$tag->parser` (jest to inny parser niż ten, który parsuje cały szablon). +- Dla tagów parzystych używa `yield` do sygnalizowania Latte, aby sparsowało wewnętrzną zawartość między tagiem początkowym a końcowym. +- Ostatecznym celem funkcji parsowania jest utworzenie i zwrócenie instancji **klasy węzła**, która jest dodawana do AST. +- Zwyczajem (choć nie jest to wymagane) jest implementowanie funkcji parsowania jako metody statycznej (często nazywanej `create`) bezpośrednio w odpowiedniej klasie węzła. Utrzymuje to logikę parsowania i reprezentację węzła schludnie w jednym pakiecie, umożliwia dostęp do prywatnych/chronionych elementów klasy, jeśli jest to potrzebne, i poprawia organizację. + + +Klasa węzła +----------- + +- Reprezentuje *logiczną funkcję* Twojego tagu w **Abstract Syntax Tree (AST)**. +- Zawiera sparsowane informacje (takie jak argumenty lub zawartość) jako publiczne właściwości. Te właściwości często zawierają inne instancje `Node` (np. `ExpressionNode` dla sparsowanych argumentów, `AreaNode` dla sparsowanej zawartości). +- Metoda `print(PrintContext $context): string` generuje *kod PHP* (instrukcję lub serię instrukcji), który wykonuje akcję tagu podczas renderowania szablonu. +- Metoda `getIterator(): \Generator` udostępnia węzły potomne (argumenty, zawartość) do przechodzenia przez **przejścia kompilacji**. Musi dostarczać referencje (`&`), aby umożliwić przejściom potencjalne modyfikowanie lub zastępowanie podwęzłów. +- Po tym, jak cały szablon zostanie sparsowany do AST, Latte uruchamia serię [przejść kompilacji |compiler-passes]. Te przejścia przechodzą przez *całe* AST za pomocą metody `getIterator()` dostarczonej przez każdy węzeł. Mogą one sprawdzać węzły, zbierać informacje, a nawet *modyfikować* drzewo (np. zmieniając publiczne właściwości węzłów lub całkowicie zastępując węzły). Ten projekt, wymagający kompleksowego `getIterator()`, jest kluczowy. Umożliwia potężnym funkcjom, takim jak [Sandbox |sandbox], analizowanie i potencjalne zmienianie zachowania *każdej* części szablonu, w tym Twoich własnych tagów, zapewniając bezpieczeństwo i spójność. + + +Rejestracja przez rozszerzenie +------------------------------ + +- Musisz poinformować Latte o swoim nowym tagu i która funkcja parsowania ma być dla niego użyta. Odbywa się to w ramach [rozszerzenia Latte |extending-latte#Latte Extension]. +- Wewnątrz swojej klasy rozszerzenia implementujesz metodę `getTags(): array`. Ta metoda zwraca tablicę asocjacyjną, gdzie klucze są nazwami tagów (np. `'mytag'`, `'n:myattribute'`), a wartości są funkcjami PHP typu callable reprezentującymi ich odpowiednie funkcje parsowania (np. `MyNamespace\DatetimeNode::create(...)`). + +Podsumowanie: **Funkcja parsowania tagu** przekształca *kod źródłowy szablonu* Twojego tagu w **węzeł AST**. **Klasa węzła** następnie potrafi przekształcić *siebie* w wykonywalny *kod PHP* dla skompilowanego szablonu i udostępnia swoje podwęzły dla **przejść kompilacji** przez `getIterator()`. **Rejestracja przez rozszerzenie** łączy nazwę tagu z funkcją parsowania i informuje o nim Latte. + +Teraz zbadamy, jak zaimplementować te komponenty krok po kroku. + + +Tworzenie prostego tagu +======================= + +Zajmijmy się tworzeniem Twojego pierwszego własnego tagu Latte. Zaczniemy od bardzo prostego przykładu: tagu o nazwie `{datetime}`, który wypisuje aktualną datę i czas. **Początkowo ten tag nie będzie przyjmował żadnych argumentów**, ale ulepszymy go później w sekcji [#"Parsowanie argumentów tagu"]. Nie ma również żadnej wewnętrznej zawartości. + +Ten przykład przeprowadzi Cię przez podstawowe kroki: zdefiniowanie klasy węzła, implementację jej metod `print()` i `getIterator()`, utworzenie funkcji parsowania i wreszcie rejestrację tagu. + +**Cel:** Zaimplementować `{datetime}` do wypisywania aktualnej daty i czasu za pomocą funkcji PHP `date()`. + + +Tworzenie klasy węzła +--------------------- + +Najpierw potrzebujemy klasy, która będzie reprezentować nasz tag w Abstract Syntax Tree (AST). Jak omówiono powyżej, dziedziczymy z `Latte\Compiler\Nodes\StatementNode`. + +Utwórz plik (np. `DatetimeNode.php`) i zdefiniuj klasę: + +```php +node = new self; + return $node; + } + + /** + * Generuje kod PHP, który zostanie uruchomiony podczas renderowania szablonu. + */ + public function print(PrintContext $context): string + { + return $context->format( + 'echo date(\'Y-m-d H:i:s\') %line;', + $this->position, + ); + } + + /** + * Zapewnia dostęp do węzłów potomnych dla przejść kompilacji Latte. + */ + public function &getIterator(): \Generator + { + false && yield; + } +} +``` + +Kiedy Latte napotka `{datetime}` w szablonie, wywoła funkcję parsowania `create()`. Jej zadaniem jest zwrócenie instancji `DatetimeNode`. + +Metoda `print()` generuje kod PHP, który zostanie uruchomiony podczas renderowania szablonu. Wywołujemy metodę `$context->format()`, która buduje wynikowy ciąg kodu PHP dla skompilowanego szablonu. Pierwszy argument, `'echo date('Y-m-d H:i:s') %line;'`, jest maską, do której są uzupełniane następujące parametry. Symbol zastępczy `%line` mówi metodzie `format()`, aby użyła drugiego argumentu, którym jest `$this->position`, i wstawiła komentarz jak `/* line 15 */`, który łączy wygenerowany kod PHP z powrotem do oryginalnego wiersza szablonu, co jest kluczowe dla debugowania. + +Właściwość `$this->position` jest dziedziczona z klasy bazowej `Node` i jest automatycznie ustawiana przez parser Latte. Zawiera obiekt [api:Latte\Compiler\Position], który wskazuje, gdzie tag został znaleziony w pliku źródłowym `.latte`. + +Metoda `getIterator()` jest kluczowa dla przejść kompilacji. Musi dostarczać wszystkie węzły potomne, ale nasz prosty `DatetimeNode` obecnie nie ma żadnych argumentów ani zawartości, a więc żadnych węzłów potomnych. Niemniej jednak metoda musi nadal istnieć i być generatorem, tj. słowo kluczowe `yield` musi być w jakiś sposób obecne w ciele metody. + + +Rejestracja przez rozszerzenie +------------------------------ + +Na koniec poinformujmy Latte o nowym tagu. Utwórz [klasę rozszerzenia |extending-latte#Latte Extension] (np. `MyLatteExtension.php`) i zarejestruj tag w jej metodzie `getTags()`. + +```php + Mapa: 'nazwa-tagu' => funkcja-parsowania + */ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + // Później zarejestruj tutaj więcej tagów + ]; + } +} +``` + +Następnie zarejestruj to rozszerzenie w Latte Engine: + +```php +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +Utwórz szablon: + +```latte +

                                                                                        Strona wygenerowana: {datetime}

                                                                                        +``` + +Oczekiwane wyjście: `

                                                                                        Strona wygenerowana: 2023-10-27 11:00:00

                                                                                        ` + + +Podsumowanie tej fazy +--------------------- + +Pomyślnie stworzyliśmy podstawowy własny tag `{datetime}`. Zdefiniowaliśmy jego reprezentację w AST (`DatetimeNode`), obsłużyliśmy jego parsowanie (`create()`), określiliśmy, jak powinien generować kod PHP (`print()`), zapewniliśmy, że jego dzieci są dostępne do przechodzenia (`getIterator()`), i zarejestrowaliśmy go w Latte. + +W następnej sekcji ulepszymy ten tag tak, aby przyjmował argumenty, i pokażemy, jak parsować wyrażenia i zarządzać węzłami potomnymi. + + +Parsowanie argumentów tagu +========================== + +Nasz prosty tag `{datetime}` działa, ale nie jest zbyt elastyczny. Ulepszmy go, aby przyjmował opcjonalny argument: ciąg formatujący dla funkcji `date()`. Wymagana składnia będzie `{datetime $format}`. + +**Cel:** Zmodyfikować `{datetime}` tak, aby przyjmował opcjonalne wyrażenie PHP jako argument, które zostanie użyte jako ciąg formatujący dla `date()`. + + +Wprowadzenie `TagParser` +------------------------ + +Zanim zmodyfikujemy kod, ważne jest, aby zrozumieć narzędzie, którego będziemy używać: [api:Latte\Compiler\TagParser]. Kiedy główny parser Latte (`TemplateParser`) napotka tag Latte, taki jak `{datetime ...}` lub n:atrybut, deleguje parsowanie zawartości *wewnątrz* tagu (część między `{` a `}` lub wartość atrybutu) do wyspecjalizowanego `TagParser`. + +Ten `TagParser` pracuje wyłącznie z **argumentami tagu**. Jego zadaniem jest przetwarzanie tokenów reprezentujących te argumenty. Kluczowe jest, że **musi przetworzyć całą zawartość**, która jest mu dostarczona. Jeśli Twoja funkcja parsowania zakończy się, ale `TagParser` nie osiągnął końca argumentów (sprawdzane przez `$tag->parser->isEnd()`), Latte rzuci wyjątek, ponieważ wskazuje to, że wewnątrz tagu pozostały nieoczekiwane tokeny. Odwrotnie, jeśli tag *wymaga* argumentów, powinieneś na początku swojej funkcji parsowania wywołać `$tag->expectArguments()`. Ta metoda sprawdza, czy argumenty są obecne, i rzuca pomocny wyjątek, jeśli tag został użyty bez żadnych argumentów. + +`TagParser` oferuje przydatne metody do parsowania różnych rodzajów argumentów: + +- `parseExpression(): ExpressionNode`: Parsuje wyrażenie podobne do PHP (zmienne, literały, operatory, wywołania funkcji/metod, itp.). Obsługuje cukier syntaktyczny Latte, taki jak traktowanie prostych ciągów alfanumerycznych jako ciągów w cudzysłowach (np. `foo` jest parsowane, jakby było `'foo'`). +- `parseUnquotedStringOrExpression(): ExpressionNode`: Parsuje albo standardowe wyrażenie, albo *niecytowany ciąg*. Niecytowane ciągi to sekwencje dozwolone przez Latte bez cudzysłowów, często używane do rzeczy takich jak ścieżki plików (np. `{include ../file.latte}`). Jeśli parsuje niecytowany ciąg, zwraca `StringNode`. +- `parseArguments(): ArrayNode`: Parsuje argumenty oddzielone przecinkami, potencjalnie z kluczami, jak `10, name: 'John', true`. +- `parseModifier(): ModifierNode`: Parsuje filtry jak `|upper|truncate:10`. +- `parseType(): ?SuperiorTypeNode`: Parsuje podpowiedzi typów PHP jak `int`, `?string`, `array|Foo`. + +Dla bardziej złożonych lub niższych poziomów potrzeb parsowania, możesz bezpośrednio interagować ze [strumieniem tokenów |api:Latte\Compiler\TokenStream] przez `$tag->parser->stream`. Ten obiekt dostarcza metody do sprawdzania i przetwarzania pojedynczych tokenów: + +- `$tag->parser->stream->is(...): bool`: Sprawdza, czy *bieżący* token odpowiada któremukolwiek z określonych typów (np. `Token::Php_Variable`) lub wartościom literałowym (np. `'as'`) bez jego konsumowania. Przydatne do patrzenia w przód. +- `$tag->parser->stream->consume(...): Token`: Konsumuje *bieżący* token i przesuwa pozycję strumienia do przodu. Jeśli jako argumenty podano oczekiwane typy/wartości tokenów, a bieżący token nie pasuje, rzuca `CompileException`. Użyj tego, gdy *oczekujesz* określonego tokenu. +- `$tag->parser->stream->tryConsume(...): ?Token`: Próbuje skonsumować *bieżący* token *tylko jeśli* pasuje do jednego z określonych typów/wartości. Jeśli pasuje, konsumuje token i zwraca go. Jeśli nie pasuje, pozostawia pozycję strumienia niezmienioną i zwraca `null`. Użyj tego dla opcjonalnych tokenów lub gdy wybierasz między różnymi ścieżkami składniowymi. + + +Aktualizacja funkcji parsowania `create()` +------------------------------------------ + +Z tym zrozumieniem zmodyfikujmy metodę `create()` w `DatetimeNode` tak, aby parsowała opcjonalny argument formatu za pomocą `$tag->parser`. + +```php +node = new self; + + // Sprawdzamy, czy istnieją jakiekolwiek tokeny + if (!$tag->parser->isEnd()) { + // Parsujemy argument jako wyrażenie podobne do PHP za pomocą TagParser. + $node->format = $tag->parser->parseExpression(); + } + + return $node; + } + + // ... metody print() i getIterator() zostaną zaktualizowane dalej ... +} +``` + +Dodaliśmy publiczną właściwość `$format`. W `create()` teraz używamy `$tag->parser->isEnd()` do sprawdzenia, czy *istnieją* argumenty. Jeśli tak, `$tag->parser->parseExpression()` przetwarza tokeny dla wyrażenia. Ponieważ `TagParser` musi przetworzyć wszystkie tokeny wejściowe, Latte automatycznie rzuci błąd, jeśli użytkownik napisze coś nieoczekiwanego po wyrażeniu formatu (np. `{datetime 'Y-m-d', unexpected}`). + + +Aktualizacja metody `print()` +----------------------------- + +Teraz zmodyfikujmy metodę `print()` tak, aby używała sparsowanego wyrażenia formatu zapisanego w `$this->format`. Jeśli nie podano formatu (`$this->format` jest `null`), powinniśmy użyć domyślnego ciągu formatującego, na przykład `'Y-m-d H:i:s'`. + +```php + public function print(PrintContext $context): string + { + $formatNode = $this->format ?? new StringNode('Y-m-d H:i:s'); + + // %node wydrukuje reprezentację kodu PHP $formatNode. + return $context->format( + 'echo date(%node) %line;', + $formatNode, + $this->position + ); + } +``` + +Do zmiennej `$formatNode` zapisujemy węzeł AST reprezentujący ciąg formatujący dla funkcji PHP `date()`. Używamy tutaj operatora koalescencji null (`??`). Jeśli użytkownik podał argument w szablonie (np. `{datetime 'd.m.Y'}`), to właściwość `$this->format` zawiera odpowiedni węzeł (w tym przypadku `StringNode` z wartością `'d.m.Y'`), i ten węzeł jest używany. Jeśli użytkownik nie podał argumentu (napisał tylko `{datetime}`), właściwość `$this->format` jest `null`, i zamiast tego tworzymy nowy `StringNode` z domyślnym formatem `'Y-m-d H:i:s'`. To zapewnia, że `$formatNode` zawsze zawiera prawidłowy węzeł AST dla formatu. + +W masce `'echo date(%node) %line;'` użyto nowego symbolu zastępczego `%node`, który mówi metodzie `format()`, aby wzięła pierwszy następujący argument (którym jest nasz `$formatNode`), wywołała jego metodę `print()` (która zwróci jego reprezentację kodu PHP) i wstawiła wynik na pozycję symbolu zastępczego. + + +Implementacja `getIterator()` dla podwęzłów +------------------------------------------- + +Nasz `DatetimeNode` ma teraz węzeł potomny: wyrażenie `$format`. **Musimy** udostępnić ten węzeł potomny przejściom kompilacji, dostarczając go w metodzie `getIterator()`. Pamiętaj, aby dostarczyć *referencję* (`&`), aby umożliwić przejściom potencjalne zastąpienie węzła. + +```php + public function &getIterator(): \Generator + { + if ($this->format) { + yield $this->format; + } + } +``` + +Dlaczego jest to kluczowe? Wyobraź sobie przejście Sandbox, które musi sprawdzić, czy argument `$format` nie zawiera zabronionego wywołania funkcji (np. `{datetime dangerousFunction()}`). Jeśli `getIterator()` nie dostarczy `$this->format`, przejście Sandbox nigdy nie zobaczyłoby wywołania `dangerousFunction()` wewnątrz argumentu naszego tagu, co stworzyłoby potencjalną lukę bezpieczeństwa. Dostarczając go, umożliwiamy Sandboxowi (i innym przejściom) sprawdzanie i potencjalne modyfikowanie węzła wyrażenia `$format`. + + +Użycie ulepszonego tagu +----------------------- + +Tag teraz poprawnie obsługuje opcjonalny argument: + +```latte +Domyślny format: {datetime} +Własny format: {datetime 'd.m.Y'} +Użycie zmiennej: {datetime $userDateFormatPreference} + +{* To spowodowałoby błąd po sparsowaniu 'd.m.Y', ponieważ ", foo" jest nieoczekiwane *} +{* {datetime 'd.m.Y', foo} *} +``` + +Następnie przyjrzymy się tworzeniu tagów parzystych, które przetwarzają zawartość między nimi. + + +Obsługa tagów parzystych +======================== + +Dotychczas nasz tag `{datetime}` był *samozamykający* (koncepcyjnie). Nie miał żadnej zawartości między tagiem początkowym a końcowym. Wiele przydatnych tagów jednak pracuje z blokiem zawartości szablonu. Nazywa się je **tagami parzystymi**. Przykłady obejmują `{if}...{/if}`, `{block}...{/block}` lub własny tag, który teraz stworzymy: `{debug}...{/debug}`. + +Ten tag pozwoli nam zawrzeć w naszych szablonach informacje debugowania, które powinny być widoczne tylko podczas rozwoju. + +**Cel:** Stworzyć tag parzysty `{debug}`, którego zawartość jest renderowana tylko wtedy, gdy aktywna jest specyficzna flaga "trybu deweloperskiego". + + +Wprowadzenie providerów +----------------------- + +Czasami Twoje tagi potrzebują dostępu do danych lub usług, które nie są przekazywane bezpośrednio jako parametry szablonu. Na przykład określenie, czy aplikacja jest w trybie deweloperskim, dostęp do obiektu użytkownika lub uzyskanie wartości konfiguracyjnych. Latte dostarcza mechanizm zwany **providerami** (Providers) do tego celu. + +Providerzy są rejestrowani w Twoim [rozszerzeniu |extending-latte#Latte Extension] za pomocą metody `getProviders()`. Ta metoda zwraca tablicę asocjacyjną, gdzie klucze są nazwami, pod którymi providerzy będą dostępni w kodzie wykonawczym szablonu, a wartości są rzeczywistymi danymi lub obiektami. + +Wewnątrz kodu PHP generowanego przez metodę `print()` Twojego tagu, możesz uzyskać dostęp do tych providerów za pośrednictwem specjalnej właściwości obiektu `$this->global`. Ponieważ ta właściwość jest współdzielona przez wszystkie rozszerzenia, dobrą praktyką jest **prefikowanie nazw Twoich providerów**, aby zapobiec potencjalnym kolizjom nazw z kluczowymi providerami Latte lub providerami z innych rozszerzeń firm trzecich. Powszechną konwencją jest używanie krótkiego, unikalnego prefiksu związanego z Twoim producentem lub nazwą rozszerzenia. Dla naszego przykładu użyjemy prefiksu `app`, a flaga trybu deweloperskiego będzie dostępna jako `$this->global->appDevMode`. + + +Słowo kluczowe `yield` do parsowania zawartości +----------------------------------------------- + +Jak mówimy parserowi Latte, aby przetworzył zawartość *między* `{debug}` a `{/debug}`? Tutaj wchodzi w grę słowo kluczowe `yield`. + +Kiedy `yield` jest używane w funkcji `create()`, funkcja staje się [generatorem PHP |https://www.php.net/manual/en/language.generators.overview.php]. Jego wykonywanie zostaje wstrzymane, a kontrola wraca do głównego `TemplateParser`. `TemplateParser` następnie kontynuuje parsowanie zawartości szablonu *aż* napotka odpowiadający tag zamykający (`{/debug}` w naszym przypadku). + +Gdy zostanie znaleziony tag zamykający, `TemplateParser` wznawia wykonywanie naszej funkcji `create()` bezpośrednio po instrukcji `yield`. Wartość *zwrócona* przez instrukcję `yield` to tablica zawierająca dwa elementy: + +1. `AreaNode` reprezentujący sparsowaną zawartość między tagiem początkowym a końcowym. +2. Obiekt `Tag` reprezentujący tag zamykający (np. `{/debug}`). + +Stwórzmy klasę `DebugNode` i jej metodę `create` wykorzystującą `yield`. + +```php +node = new self; + + // Wstrzymaj parsowanie, uzyskaj wewnętrzną zawartość i tag końcowy, gdy zostanie znaleziony {/debug} + [$node->content, $endTag] = yield; + + return $node; + } + + // ... print() i getIterator() zostaną zaimplementowane dalej ... +} +``` + +Uwaga: `$endTag` jest `null`, jeśli tag jest używany jako n:atrybut, tj. `
                                                                                        ...
                                                                                        `. + + +Implementacja `print()` dla warunkowego renderowania +---------------------------------------------------- + +Metoda `print()` teraz musi generować kod PHP, który w czasie wykonania sprawdzi providera `appDevMode` i wykona kod dla wewnętrznej zawartości tylko wtedy, gdy flaga jest true. + +```php + public function print(PrintContext $context): string + { + // Wygeneruje instrukcję PHP 'if', która w czasie wykonania sprawdzi providera + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + // Jeśli jest w trybie deweloperskim, wypisze wewnętrzną zawartość + %node + } + + XX, + $this->position, // Dla komentarza %line + $this->content, // Węzeł zawierający AST wewnętrznej zawartości + ); + } +``` + +To jest proste. Używamy `PrintContext::format()` do stworzenia standardowej instrukcji PHP `if`. Wewnątrz `if` umieszczamy symbol zastępczy `%node` dla `$this->content`. Latte rekurencyjnie wywoła `$this->content->print($context)` do wygenerowania kodu PHP dla wewnętrznej części tagu, ale tylko jeśli `$this->global->appDevMode` zostanie ocenione w czasie wykonania jako true. + + +Implementacja `getIterator()` dla zawartości +-------------------------------------------- + +Podobnie jak w przypadku węzła argumentu w poprzednim przykładzie, nasz `DebugNode` ma teraz węzeł potomny: `AreaNode $content`. Musimy go udostępnić, dostarczając go w `getIterator()`: + +```php + public function &getIterator(): \Generator + { + // Dostarcza referencję do węzła zawartości + yield $this->content; + } +``` + +To umożliwia przejściom kompilacji zejście do zawartości naszego tagu `{debug}`, co jest ważne, nawet jeśli zawartość jest renderowana warunkowo. Na przykład Sandbox musi analizować zawartość niezależnie od tego, czy `appDevMode` jest true czy false. + + +Rejestracja i użycie +-------------------- + +Zarejestruj tag i providera w swoim rozszerzeniu: + +```php +class MyLatteExtension extends Extension +{ + // Zakładamy, że $isDevelopmentMode jest określone gdzieś (np. z konfiguracji) + public function __construct( + private bool $isDevelopmentMode, + ) { + } + + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), // Rejestracja nowego tagu + ]; + } + + public function getProviders(): array + { + return [ + 'appDevMode' => $this->isDevelopmentMode, // Rejestracja providera + ]; + } +} + +// Przy rejestracji rozszerzenia: +$isDev = true; // Określ to na podstawie środowiska Twojej aplikacji +$latte->addExtension(new App\Latte\MyLatteExtension($isDev)); +``` + +I jego użycie w szablonie: + +```latte +

                                                                                        Zwykła zawartość widoczna zawsze.

                                                                                        + +{debug} +
                                                                                        + ID aktualnego użytkownika: {$user->id} + Czas żądania: {=time()} +
                                                                                        +{/debug} + +

                                                                                        Kolejna zwykła zawartość.

                                                                                        +``` + + +Integracja n:atrybutów +---------------------- + +Latte oferuje wygodny skrócony zapis dla wielu tagów parzystych: [n:atrybuty |syntax#n:atrybuty]. Jeśli masz tag parzysty jak `{tag}...{/tag}` i chcesz, aby jego efekt zastosował się bezpośrednio do pojedynczego elementu HTML, często możesz zapisać go bardziej zwięźle jako atrybut `n:tag` na tym elemencie. + +Dla większości standardowych tagów parzystych, które definiujesz (jak nasz `{debug}`), Latte automatycznie włączy odpowiadającą wersję atrybutu `n:`. Podczas rejestracji nie musisz robić nic dodatkowego: + +```latte +{* Standardowe użycie tagu parzystego *} +{debug}
                                                                                        Informacje do debugowania
                                                                                        {/debug} + +{* Równoważne użycie z n:atrybutem *} +
                                                                                        Informacje do debugowania
                                                                                        +``` + +Obie wersje wyrenderują `
                                                                                        ` tylko jeśli `$this->global->appDevMode` jest true. Prefiksy `inner-` i `tag-` również działają zgodnie z oczekiwaniami. + +Czasami logika Twojego tagu może potrzebować zachowywać się nieco inaczej w zależności od tego, czy jest używany jako standardowy tag parzysty, czy jako n:atrybut, lub czy użyto prefiksu jak `n:inner-tag` lub `n:tag-tag`. Obiekt `Latte\Compiler\Tag`, przekazany do Twojej funkcji parsowania `create()`, dostarcza te informacje: + +- `$tag->isNAttribute(): bool`: Zwraca `true`, jeśli tag jest parsowany jako n:atrybut +- `$tag->prefix: ?string`: Zwraca prefiks użyty z n:atrybutem, co może być `null` (nie jest n:atrybutem), `Tag::PrefixNone`, `Tag::PrefixInner` lub `Tag::PrefixTag` + +Teraz, gdy rozumiemy proste tagi, parsowanie argumentów, tagi parzyste, providerów i n:atrybuty, zajmijmy się bardziej złożonym scenariuszem obejmującym tagi zagnieżdżone w innych tagach, wykorzystując nasz tag `{debug}` jako punkt wyjścia. + + +Tagi pośrednie +============== + +Niektóre tagi parzyste pozwalają lub nawet wymagają, aby inne tagi pojawiły się *wewnątrz* nich przed końcowym tagiem zamykającym. Nazywa się je **tagami pośrednimi**. Klasyczne przykłady obejmują `{if}...{elseif}...{else}...{/if}` lub `{switch}...{case}...{default}...{/switch}`. + +Rozszerzmy nasz tag `{debug}` o obsługę opcjonalnej klauzuli `{else}`, która będzie renderowana, gdy aplikacja *nie jest* w trybie deweloperskim. + +**Cel:** Zmodyfikować `{debug}` tak, aby obsługiwał opcjonalny tag pośredni `{else}`. Końcowa składnia powinna być `{debug} ... {else} ... {/debug}`. + + +Parsowanie tagów pośrednich za pomocą `yield` +--------------------------------------------- + +Już wiemy, że `yield` wstrzymuje funkcję parsowania `create()` i zwraca sparsowaną zawartość wraz z tagiem końcowym. `yield` jednak oferuje więcej kontroli: możesz mu dostarczyć tablicę *nazw tagów pośrednich*. Kiedy parser napotka którykolwiek z tych określonych tagów **na tym samym poziomie zagnieżdżenia** (tj. jako bezpośrednie dzieci tagu nadrzędnego, nie wewnątrz innych bloków lub tagów wewnątrz niego), również zatrzyma parsowanie. + +Kiedy parsowanie zatrzymuje się z powodu tagu pośredniego, zatrzymuje parsowanie zawartości, wznawia generator `create()` i przekazuje z powrotem częściowo sparsowaną zawartość oraz **tag pośredni** sam w sobie (zamiast końcowego tagu zamykającego). Nasza funkcja `create()` może następnie przetworzyć ten tag pośredni (np. sparsować jego argumenty, jeśli jakieś miał) i ponownie użyć `yield` do parsowania *kolejnej* części zawartości aż do *końcowego* tagu zamykającego lub innego oczekiwanego tagu pośredniego. + +Zmodyfikujmy `DebugNode::create()` tak, aby oczekiwał `{else}`: + +```php +node = new self; + + // yield i oczekuj albo {/debug} albo {else} + [$node->thenContent, $nextTag] = yield ['else']; + + // Sprawdź, czy tag, przy którym się zatrzymaliśmy, był {else} + if ($nextTag?->name === 'else') { + // Yield ponownie, aby sparsować zawartość między {else} a {/debug} + [$node->elseContent, $endTag] = yield; + } + + return $node; + } + + // ... print() i getIterator() zostaną zaktualizowane dalej ... +} +``` + +Teraz `yield ['else']` mówi Latte, aby zatrzymało parsowanie nie tylko dla `{/debug}`, ale także dla `{else}`. Jeśli `{else}` zostanie znaleziony, `$nextTag` będzie zawierał obiekt `Tag` dla `{else}`. Następnie ponownie używamy `yield` bez argumentów, co oznacza, że teraz oczekujemy tylko końcowego tagu `{/debug}`, i zapisujemy wynik w `$node->elseContent`. Jeśli `{else}` nie został znaleziony, `$nextTag` byłby `Tag` dla `{/debug}` (lub `null`, jeśli używany jako n:atrybut), a `$node->elseContent` pozostałby `null`. + + +Implementacja `print()` z `{else}` +---------------------------------- + +Metoda `print()` musi odzwierciedlać nową strukturę. Powinna generować instrukcję PHP `if/else` opartą na providerze `devMode`. + +```php + public function print(PrintContext $context): string + { + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + %node // Kod dla gałęzi 'then' (zawartość {debug}) + } else { + %node // Kod dla gałęzi 'else' (zawartość {else}) + } + + XX, + $this->position, // Numer wiersza dla warunku 'if' + $this->thenContent, // Pierwszy symbol zastępczy %node + $this->elseContent ?? new NopNode, // Drugi symbol zastępczy %node + ); + } +``` + +To jest standardowa struktura PHP `if/else`. Używamy `%node` dwukrotnie; `format()` zastępuje dostarczone węzły po kolei. Używamy `?? new NopNode` do uniknięcia błędów, jeśli `$this->elseContent` jest `null` – `NopNode` po prostu nic nie wydrukuje. + + +Implementacja `getIterator()` dla obu zawartości +------------------------------------------------ + +Teraz mamy potencjalnie dwa węzły potomne zawartości (`$thenContent` i `$elseContent`). Musimy dostarczyć oba, jeśli istnieją: + +```php + public function &getIterator(): \Generator + { + yield $this->thenContent; + if ($this->elseContent) { + yield $this->elseContent; + } + } +``` + + +Użycie ulepszonego tagu +----------------------- + +Tag może być teraz używany z opcjonalną klauzulą `{else}`: + +```latte +{debug} +

                                                                                        Wyświetlanie informacji debugowania, ponieważ devMode jest WŁĄCZONY.

                                                                                        +{else} +

                                                                                        Informacje debugowania są ukryte, ponieważ devMode jest WYŁĄCZONY.

                                                                                        +{/debug} +``` + + +Obsługa stanu i zagnieżdżania +============================= + +Nasze poprzednie przykłady (`{datetime}`, `{debug}`) były stosunkowo bezstanowe w ramach swoich metod `print()`. Albo bezpośrednio wypisywały zawartość, albo przeprowadzały prostą kontrolę warunkową opartą na globalnym providerze. Wiele tagów jednak musi zarządzać jakąś formą **stanu** podczas renderowania lub obejmuje ewaluację wyrażeń użytkownika, które powinny być uruchamiane tylko raz ze względu na wydajność lub poprawność. Dalej musimy rozważyć, co się stanie, gdy nasze własne tagi są **zagnieżdżone**. + +Zilustrujmy te koncepcje, tworząc tag `{repeat $count}...{/repeat}`. Ten tag będzie powtarzał swoją wewnętrzną zawartość `$count`-krotnie. + +**Cel:** Zaimplementować `{repeat $count}`, który powtarza swoją zawartość określoną liczbę razy. + + +Potrzeba tymczasowych i unikalnych zmiennych +-------------------------------------------- + +Wyobraź sobie, że użytkownik napisze: + +```latte +{repeat rand(1, 5)} Zawartość {/repeat} +``` + +Gdybyśmy naiwnie wygenerowali pętlę PHP `for` w ten sposób w naszej metodzie `print()`: + +```php +// Uproszczony, NIEPRAWIDŁOWY wygenerowany kod +for ($i = 0; $i < rand(1, 5); $i++) { + // wypisanie zawartości +} +``` +To byłoby źle! Wyrażenie `rand(1, 5)` byłoby **ponownie ewaluowane przy każdej iteracji pętli**, co prowadziłoby do nieprzewidywalnej liczby powtórzeń. Musimy ewaluować wyrażenie `$count` *raz* przed rozpoczęciem pętli i zapisać jego wynik. + +Wygenerujemy kod PHP, który najpierw ewaluuje wyrażenie liczby i zapisuje je w **tymczasowej zmiennej wykonawczej**. Aby zapobiec kolizjom ze zmiennymi zdefiniowanymi przez użytkownika szablonu *oraz* wewnętrznymi zmiennymi Latte (jak `$ʟ_...`), użyjemy konwencji prefiksu **`$__` (podwójne podkreślenie)** dla naszych tymczasowych zmiennych. + +Wygenerowany kod wyglądałby wtedy tak: + +```php +$__count = rand(1, 5); +for ($__i = 0; $__i < $__count; $__i++) { + // wypisanie zawartości +} +``` + +Teraz rozważmy zagnieżdżanie: + +```latte +{repeat $countA} {* Zewnętrzna pętla *} + {repeat $countB} {* Wewnętrzna pętla *} + ... + {/repeat} +{/repeat} +``` + +Gdyby zarówno zewnętrzny, jak i wewnętrzny tag `{repeat}` generował kod używający *tych samych* nazw zmiennych tymczasowych (np. `$__count` i `$__i`), wewnętrzna pętla nadpisałaby zmienne zewnętrznej pętli, co naruszyłoby logikę. + +Musimy zapewnić, że zmienne tymczasowe generowane dla każdej instancji tagu `{repeat}` są **unikalne**. Osiągniemy to za pomocą `PrintContext::generateId()`. Ta metoda zwraca unikalną liczbę całkowitą podczas fazy kompilacji. Możemy dołączyć ten ID do nazw naszych zmiennych tymczasowych. + +Więc zamiast `$__count` będziemy generować `$__count_1` dla pierwszego tagu repeat, `$__count_2` dla drugiego itd. Podobnie dla licznika pętli użyjemy `$__i_1`, `$__i_2` itd. + + +Implementacja `RepeatNode` +-------------------------- + +Stwórzmy klasę węzła. + +```php +expectArguments(); // upewnia się, że $count jest podany + $node = $tag->node = new self; + // Parsuje wyrażenie liczby + $node->count = $tag->parser->parseExpression(); + // Uzyskanie wewnętrznej zawartości + [$node->content] = yield; + return $node; + } + + /** + * Generuje pętlę PHP 'for' z unikalnymi nazwami zmiennych. + */ + public function print(PrintContext $context): string + { + // Generowanie unikalnych nazw zmiennych + $id = $context->generateId(); + $countVar = '$__count_' . $id; // np. $__count_1, $__count_2, itd. + $iteratorVar = '$__i_' . $id; // np. $__i_1, $__i_2, itd. + + return $context->format( + <<<'XX' + // Ewaluacja wyrażenia liczby *raz* i zapisanie + %raw = (int) (%node); + // Pętla z użyciem zapisanej liczby i unikalnej zmiennej iteracyjnej + for (%raw = 0; %2.raw < %0.raw; %2.raw++) %line { + %node // Renderowanie wewnętrznej zawartości + } + + XX, + $countVar, // %0 - Zmienna do zapisania liczby + $this->count, // %1 - Węzeł wyrażenia dla liczby + $iteratorVar, // %2 - Nazwa zmiennej iteracyjnej pętli + $this->position, // %3 - Komentarz z numerem wiersza dla samej pętli + $this->content // %4 - Węzeł wewnętrznej zawartości + ); + } + + /** + * Dostarcza węzły potomne (wyrażenie liczby i zawartość). + */ + public function &getIterator(): \Generator + { + yield $this->count; + yield $this->content; + } +} +``` + +Metoda `create()` parsuje wymagane wyrażenie `$count` za pomocą `parseExpression()`. Najpierw wywoływane jest `$tag->expectArguments()`. Zapewnia to, że użytkownik podał *coś* po `{repeat}`. Chociaż `$tag->parser->parseExpression()` zawiodłoby, gdyby nic nie zostało podane, komunikat o błędzie mógłby dotyczyć nieoczekiwanej składni. Użycie `expectArguments()` dostarcza znacznie jaśniejszy błąd, konkretnie stwierdzający, że brakuje argumentów dla tagu `{repeat}`. + +Metoda `print()` generuje kod PHP odpowiedzialny za wykonywanie logiki powtarzania w czasie wykonania. Zaczyna od generowania unikalnych nazw dla tymczasowych zmiennych PHP, których będzie potrzebować. + +Metoda `$context->format()` jest wywoływana z nowym symbolem zastępczym `%raw`, który wstawia *surowy ciąg* dostarczony jako odpowiadający argument. Tutaj wstawia unikalną nazwę zmiennej zapisaną w `$countVar` (np. `$__count_1`). A co z `%0.raw` i `%2.raw`? To demonstruje **pozycyjne symbole zastępcze**. Zamiast zwykłego `%raw`, który bierze *następny* dostępny surowy argument, `%2.raw` jawnie bierze argument o indeksie 2 (którym jest `$iteratorVar`) i wstawia jego surową wartość ciągu. Pozwala nam to ponownie użyć ciągu `$iteratorVar` bez wielokrotnego przekazywania go na liście argumentów dla `format()`. + +To starannie skonstruowane wywołanie `format()` generuje wydajną i bezpieczną pętlę PHP, która poprawnie obsługuje wyrażenie liczby i unika kolizji nazw zmiennych, nawet gdy tagi `{repeat}` są zagnieżdżone. + + +Rejestracja i użycie +-------------------- + +Zarejestruj tag w swoim rozszerzeniu: + +```php +use App\Latte\RepeatNode; + +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), // Rejestracja tagu repeat + ]; + } +} +``` + +Użyj go w szablonie, w tym zagnieżdżania: + +```latte +{var $rows = rand(5, 7)} +{var $cols = rand(3, 5)} + +{repeat $rows} +
                                                                                        + {repeat $cols} + + {/repeat} + +{/repeat} +``` + +Ten przykład demonstruje, jak obsługiwać stan (liczniki pętli) i potencjalne problemy z zagnieżdżaniem za pomocą zmiennych tymczasowych z prefiksem `$__` i unikalnych z ID od `PrintContext::generateId()`. + + +Czyste n:atrybuty +----------------- + +Podczas gdy wiele `n:atrybutów` jak `n:if` lub `n:foreach` służy jako wygodne skróty dla ich odpowiedników w tagach parzystych (`{if}...{/if}`, `{foreach}...{/foreach}`), Latte pozwala również definiować tagi, które *istnieją tylko* w formie n:atrybutu. Są one często używane do modyfikowania atrybutów lub zachowania elementu HTML, do którego są dołączone. + +Standardowe przykłady wbudowane w Latte obejmują [`n:class` |tags#n:class], który pomaga dynamicznie budować atrybut `class`, oraz [`n:attr` |tags#n:attr], który może ustawić wiele dowolnych atrybutów. + +Stwórzmy własny czysty n:atrybut: `n:confirm`, który doda dialog potwierdzenia JavaScript przed wykonaniem akcji (jak podążenie za linkiem lub wysłanie formularza). + +**Cel:** Zaimplementować `n:confirm="'Jesteś pewien?'"`, który doda obsługę zdarzenia `onclick` do zapobiegania domyślnej akcji, jeśli użytkownik anuluje dialog potwierdzenia. + + +Implementacja `ConfirmNode` +--------------------------- + +Potrzebujemy klasy Node i funkcji parsowania. + +```php +expectArguments(); + $node = $tag->node = new self; + $node->message = $tag->parser->parseExpression(); + return $node; + } + + /** + * Generuje kod atrybutu 'onclick' z poprawnym escapowaniem. + */ + public function print(PrintContext $context): string + { + // Zapewnia poprawne escapowanie dla kontekstów JavaScript i atrybutu HTML. + return $context->format( + <<<'XX' + echo ' onclick="', LR\Filters::escapeHtmlAttr('return confirm(' . LR\Filters::escapeJs(%node) . ')'), '"' %line; + XX, + $this->message, + $this->position, + ); + } + + public function &getIterator(): \Generator + { + yield $this->message; + } +} +``` + +Metoda `print()` generuje kod PHP, który ostatecznie podczas renderowania szablonu wypisze atrybut HTML `onclick="..."`. Obsługa zagnieżdżonych kontekstów (JavaScript wewnątrz atrybutu HTML) wymaga starannego escapowania. Filtr `LR\Filters::escapeJs(%node)` jest wywoływany w czasie wykonania i escapuje wiadomość poprawnie do użycia wewnątrz JavaScriptu (wyjście byłoby jak `"Sure?"`). Następnie filtr `LR\Filters::escapeHtmlAttr(...)` escapuje znaki, które są specjalne w atrybutach HTML, więc zmieniłoby to wyjście na `return confirm("Sure?")`. To dwustopniowe escapowanie w czasie wykonania zapewnia, że wiadomość jest bezpieczna dla JavaScriptu, a wynikowy kod JavaScript jest bezpieczny do wstawienia do atrybutu HTML `onclick`. + + +Rejestracja i użycie +-------------------- + +Zarejestruj n:atrybut w swoim rozszerzeniu. Pamiętaj o prefiksie `n:` w kluczu: + +```php +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), + 'n:confirm' => ConfirmNode::create(...), // Rejestracja n:confirm + ]; + } +} +``` + +Teraz możesz użyć `n:confirm` na linkach, przyciskach lub elementach formularza: + +```latte +Usuń +``` + +Wygenerowany HTML: + +```html +Usuń +``` + +Kiedy użytkownik kliknie na link, przeglądarka wykona kod `onclick`, wyświetli dialog potwierdzenia i przejdzie do `delete.php` tylko wtedy, gdy użytkownik kliknie "OK". + +Ten przykład demonstruje, jak można stworzyć czysty n:atrybut do modyfikowania zachowania lub atrybutów swojego hostującego elementu HTML, generując odpowiedni kod PHP w jego metodzie `print()`. Pamiętaj o podwójnym escapowaniu, które jest często wymagane: raz dla kontekstu docelowego (JavaScript w tym przypadku) i ponownie dla kontekstu atrybutu HTML. + + +Tematy zaawansowane +=================== + +Podczas gdy poprzednie sekcje obejmują podstawowe koncepcje, oto kilka bardziej zaawansowanych tematów, na które możesz natknąć się podczas tworzenia własnych tagów Latte. + + +Tryby wyjścia tagów +------------------- + +Obiekt `Tag` przekazany do Twojej funkcji `create()` ma właściwość `outputMode`. Ta właściwość wpływa na to, jak Latte traktuje otaczające białe znaki i wcięcia, szczególnie gdy tag jest używany na własnej linii. Możesz zmodyfikować tę właściwość w swojej funkcji `create()`. + +- `Tag::OutputKeepIndentation` (Domyślny dla większości tagów jak `{=...}`): Latte stara się zachować wcięcie przed tagiem. Nowe linie *po* tagu są generalnie zachowywane. Jest to odpowiednie dla tagów, które wypisują zawartość w linii. +- `Tag::OutputRemoveIndentation` (Domyślny dla tagów blokowych jak `{if}`, `{foreach}`): Latte usuwa początkowe wcięcie i potencjalnie jedną następującą nową linię. Pomaga to utrzymać wygenerowany kod PHP czystszym i zapobiega dodatkowym pustym liniom w wyjściu HTML spowodowanym przez sam tag. Użyj tego dla tagów, które reprezentują struktury sterujące lub bloki, które same nie powinny dodawać białych znaków. +- `Tag::OutputNone` (Używany przez tagi jak `{var}`, `{default}`): Podobny do `RemoveIndentation`, ale sygnalizuje silniej, że sam tag nie produkuje bezpośredniego wyjścia, potencjalnie wpływając na przetwarzanie białych znaków wokół niego jeszcze bardziej agresywnie. Odpowiedni dla tagów deklaracyjnych lub ustawiających. + +Wybierz tryb, który najlepiej pasuje do celu Twojego tagu. Dla większości tagów strukturalnych lub sterujących zazwyczaj odpowiedni jest `OutputRemoveIndentation`. + + +Dostęp do tagów nadrzędnych/najbliższych +---------------------------------------- + +Czasami zachowanie tagu musi zależeć od kontekstu, w którym jest używany, konkretnie w którym tagu(tagach) nadrzędnym się znajduje. Obiekt `Tag` przekazany do Twojej funkcji `create()` dostarcza metodę `closestTag(array $classes, ?callable $condition = null): ?Tag` dokładnie do tego celu. + +Ta metoda przeszukuje w górę hierarchię aktualnie otwartych tagów (w tym elementów HTML reprezentowanych wewnętrznie podczas parsowania) i zwraca obiekt `Tag` najbliższego przodka, który odpowiada określonym kryteriom. Jeśli nie zostanie znaleziony żaden pasujący przodek, zwraca `null`. + +Tablica `$classes` określa, jakiego rodzaju tagów przodków szukasz. Sprawdza, czy powiązany węzeł tagu przodka (`$ancestorTag->node`) jest instancją tej klasy. + +```php +function create(Tag $tag) +{ + // Szukanie najbliższego tagu przodka, którego węzeł jest instancją ForeachNode + $foreachTag = $tag->closestTag([ForeachNode::class]); + if ($foreachTag) { + // Możemy uzyskać dostęp do samej instancji ForeachNode: + $foreachNode = $foreachTag->node; + } +} +``` + +Zauważ `$foreachTag->node`: Działa to tylko dlatego, że jest konwencją w rozwoju tagów Latte natychmiastowe przypisanie utworzonego węzła do `$tag->node` w ramach metody `create()`, jak zawsze robiliśmy. + +Czasami samo porównanie typu węzła nie wystarcza. Możesz potrzebować sprawdzić specyficzną właściwość potencjalnego tagu przodka lub jego węzła. Opcjonalny drugi argument dla `closestTag()` to funkcja typu callable, która przyjmuje potencjalny obiekt `Tag` przodka i powinna zwracać, czy jest to prawidłowe dopasowanie. + +```php +function create(Tag $tag) +{ + $dynamicBlockTag = $tag->closestTag( + [BlockNode::class], + // Warunek: blok musi być dynamiczny + fn(Tag $blockTag) => $blockTag->node->block->isDynamic(), + ); +} +``` + +Użycie `closestTag()` pozwala tworzyć tagi, które są świadome kontekstu i wymuszają poprawne użycie w ramach struktury Twojego szablonu, co prowadzi do bardziej solidnych i zrozumiałych szablonów. + + +Symbole zastępcze `PrintContext::format()` +------------------------------------------ + +Często używaliśmy `PrintContext::format()` do generowania kodu PHP w metodach `print()` naszych węzłów. Przyjmuje ona ciąg maski i następujące argumenty, które zastępują symbole zastępcze w masce. Oto podsumowanie dostępnych symboli zastępczych: + +- **`%node`**: Argument musi być instancją `Node`. Wywołuje metodę `print()` węzła i wstawia wynikowy ciąg kodu PHP. +- **`%dump`**: Argument jest dowolną wartością PHP. Eksportuje wartość do prawidłowego kodu PHP. Odpowiedni dla skalarów, tablic, null. + - `$context->format('echo %dump;', 'Hello')` -> `echo 'Hello';` + - `$context->format('$arr = %dump;', [1, 2])` -> `$arr = [1, 2];` +- **`%raw`**: Wstawia argument bezpośrednio do wyjściowego kodu PHP bez żadnego escapowania ani modyfikacji. **Używaj z ostrożnością**, głównie do wstawiania wstępnie wygenerowanych fragmentów kodu PHP lub nazw zmiennych. + - `$context->format('%raw = 1;', '$variableName')` -> `$variableName = 1;` +- **`%args`**: Argument musi być `Expression\ArrayNode`. Wypisuje elementy tablicy sformatowane jako argumenty do wywołania funkcji lub metody (oddzielone przecinkami, obsługuje nazwane argumenty, jeśli są obecne). + - `$argsNode = new ArrayNode([...]);` + - `$context->format('myFunc(%args);', $argsNode)` -> `myFunc(1, name: 'Joe');` +- **`%line`**: Argument musi być obiektem `Position` (zazwyczaj `$this->position`). Wstawia komentarz PHP `/* line X */` wskazujący numer wiersza źródła. + - `$context->format('echo "Hi" %line;', $this->position)` -> `echo "Hi" /* line 42 */;` +- **`%escape(...)`**: Generuje kod PHP, który *w czasie wykonania* escapuje wewnętrzne wyrażenie za pomocą bieżących, świadomych kontekstu reguł escapowania. + - `$context->format('echo %escape(%node);', $variableNode)` +- **`%modify(...)`**: Argument musi być `ModifierNode`. Generuje kod PHP, który stosuje filtry określone w `ModifierNode` do wewnętrznej zawartości, w tym świadome kontekstu escapowanie, jeśli nie jest wyłączone za pomocą `|noescape`. + - `$context->format('%modify(%node);', $modifierNode, $variableNode)` +- **`%modifyContent(...)`**: Podobny do `%modify`, ale przeznaczony do modyfikowania bloków przechwyconej zawartości (często HTML). + +Możesz jawnie odwoływać się do argumentów według ich indeksu (od zera): `%0.node`, `%1.dump`, `%2.raw`, itd. Pozwala to ponownie użyć argumentu kilka razy w masce bez powtarzania go w wywołaniu `format()`. Zobacz przykład tagu `{repeat}`, gdzie użyto `%0.raw` i `%2.raw`. + + +Przykład złożonego parsowania argumentów +---------------------------------------- + +Podczas gdy `parseExpression()`, `parseArguments()`, itp., obejmują wiele przypadków, czasami potrzebujesz bardziej złożonej logiki parsowania używającej niższego poziomu `TokenStream` dostępnego przez `$tag->parser->stream`. + +**Cel:** Stworzyć tag `{embedYoutube $videoID, width: 640, height: 480}`. Chcemy sparsować wymagane ID wideo (ciąg lub zmienną) poprzedzone opcjonalnymi parami klucz-wartość dla wymiarów. + +```php +expectArguments(); + $node = $tag->node = new self; + // Parsowanie wymaganego ID wideo + $node->videoId = $tag->parser->parseExpression(); + + // Parsowanie opcjonalnych par klucz-wartość + $stream = $tag->parser->stream; // Uzyskanie strumienia tokenów + while ($stream->tryConsume(',')) { // Wymaga oddzielenia przecinkiem + // Oczekiwanie identyfikatora 'width' lub 'height' + $keyToken = $stream->consume(Token::Php_Identifier); + $key = strtolower($keyToken->text); + + $stream->consume(':'); // Oczekiwanie separatora dwukropka + + $value = $tag->parser->parseExpression(); // Parsowanie wyrażenia wartości + + if ($key === 'width') { + $node->width = $value; + } elseif ($key === 'height') { + $node->height = $value; + } else { + throw new CompileException("Nieznany argument '$key'. Oczekiwano 'width' lub 'height'.", $keyToken->position); + } + } + + return $node; + } +} +``` + +Ten poziom kontroli pozwala definiować bardzo specyficzne i złożone składnie dla Twoich własnych tagów poprzez bezpośrednią interakcję ze strumieniem tokenów. + + +Użycie `AuxiliaryNode` +---------------------- + +Latte dostarcza ogólne "pomocnicze" węzły dla specjalnych sytuacji podczas generowania kodu lub w ramach przejść kompilacji. Są to `AuxiliaryNode` i `Php\Expression\AuxiliaryNode`. + +Uważaj `AuxiliaryNode` za elastyczny węzeł kontenerowy, który deleguje swoje podstawowe funkcjonalności - generowanie kodu i udostępnianie węzłów potomnych - argumentom dostarczonym w jego konstruktorze: + +- Delegacja `print()`: Pierwszy argument konstruktora to **closure** PHP. Kiedy Latte wywołuje metodę `print()` na `AuxiliaryNode`, uruchamia tę dostarczoną closure. Closure przyjmuje `PrintContext` i wszelkie węzły przekazane w drugim argumencie konstruktora, co pozwala definiować całkowicie własną logikę generowania kodu PHP w czasie wykonania. +- Delegacja `getIterator()`: Drugi argument konstruktora to **tablica obiektów `Node`**. Kiedy Latte potrzebuje przejść przez dzieci `AuxiliaryNode` (np. podczas przejść kompilacji), jego metoda `getIterator()` po prostu dostarcza węzły wymienione w tej tablicy. + +Przykład: + +```php +$node = new AuxiliaryNode( + // 1. Ta closure staje się ciałem print() + fn(PrintContext $context, $arg1, $arg2) => $context->format('...%node...%node...', $arg1, $arg2), + + // 2. Te węzły są dostarczane przez metodę getIterator() i przekazywane do closure powyżej + [$argumentNode1, $argumentNode2] +); +``` + +Latte dostarcza dwa odrębne typy w zależności od tego, gdzie potrzebujesz wstawić wygenerowany kod: + +- `Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode`: Użyj tego, gdy potrzebujesz wygenerować fragment kodu PHP, który reprezentuje **wyrażenie** +- `Latte\Compiler\Nodes\AuxiliaryNode`: Użyj tego do bardziej ogólnych celów, gdy potrzebujesz wstawić blok kodu PHP reprezentujący jedną lub więcej **instrukcji** + +Ważnym powodem użycia `AuxiliaryNode` zamiast standardowych węzłów (jak `StaticMethodCallNode`) w ramach Twojej metody `print()` lub przejścia kompilacji jest **kontrola widoczności dla kolejnych przejść kompilacji**, szczególnie tych związanych z bezpieczeństwem, jak Sandbox. + +Rozważ scenariusz: Twoje przejście kompilacji musi opakować wyrażenie dostarczone przez użytkownika (`$userExpr`) wywołaniem specyficznej, zaufanej funkcji pomocniczej `myInternalSanitize($userExpr)`. Jeśli utworzysz standardowy węzeł `new FunctionCallNode('myInternalSanitize', [$userExpr])`, będzie on w pełni widoczny dla przejścia AST. Jeśli przejście Sandbox działa później i `myInternalSanitize` *nie jest* na jego liście dozwolonych, Sandbox może *zablokować* lub zmodyfikować to wywołanie, potencjalnie naruszając wewnętrzną logikę Twojego tagu, nawet jeśli *Ty*, autor tagu, wiesz, że to konkretne wywołanie jest bezpieczne i niezbędne. Możesz więc generować wywołanie bezpośrednio w ramach closure `AuxiliaryNode`. + +```php +use Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode; + +// ... wewnątrz print() lub przejścia kompilacji ... +$wrappedNode = new AuxiliaryNode( + fn(PrintContext $context, $userExpr) => $context->format( + 'myInternalSanitize(%node)', // Bezpośrednie generowanie kodu PHP + $userExpr, + ), + // WAŻNE: Nadal przekaż oryginalny węzeł wyrażenia użytkownika tutaj! + [$userExpr], +); +``` + +W tym przypadku przejście Sandbox widzi `AuxiliaryNode`, ale **nie analizuje kodu PHP generowanego przez jego closure**. Nie może bezpośrednio zablokować wywołania `myInternalSanitize` generowanego *wewnątrz* closure. + +Podczas gdy sam wygenerowany kod PHP jest ukryty przed przejściami, *wejścia* do tego kodu (węzły reprezentujące dane lub wyrażenia użytkownika) **muszą być nadal możliwe do przejścia**. Dlatego drugi argument konstruktora `AuxiliaryNode` jest kluczowy. **Musisz** przekazać tablicę zawierającą wszystkie oryginalne węzły (jak `$userExpr` w przykładzie powyżej), których używa Twoja closure. `getIterator()` `AuxiliaryNode` **dostarczy te węzły**, umożliwiając przejściom kompilacji, takim jak Sandbox, analizowanie ich pod kątem potencjalnych problemów. + + +Dobre praktyki +============== + +- **Jasny cel:** Upewnij się, że Twój tag ma jasny i niezbędny cel. Nie twórz tagów do zadań, które można łatwo rozwiązać za pomocą [filtrów |custom-filters] lub [funkcji |custom-functions]. +- **Poprawnie zaimplementuj `getIterator()`:** Zawsze implementuj `getIterator()` i dostarczaj *referencje* (`&`) do *wszystkich* węzłów potomnych (argumentów, zawartości), które zostały sparsowane z szablonu. Jest to niezbędne dla przejść kompilacji, bezpieczeństwa (Sandbox) i potencjalnych przyszłych optymalizacji. +- **Publiczne właściwości dla węzłów:** Właściwości zawierające węzły potomne czyń publicznymi, aby przejścia kompilacji mogły je w razie potrzeby modyfikować. +- **Używaj `PrintContext::format()`:** Wykorzystuj metodę `format()` do generowania kodu PHP. Obsługuje cudzysłowy, poprawnie escapuje symbole zastępcze i automatycznie dodaje komentarze z numerem wiersza. +- **Zmienne tymczasowe (`$__`):** Podczas generowania kodu PHP wykonawczego, który potrzebuje zmiennych tymczasowych (np. do przechowywania wyników pośrednich, liczników pętli), używaj konwencji prefiksu `$__`, aby uniknąć kolizji ze zmiennymi użytkownika i wewnętrznymi zmiennymi Latte `$ʟ_`. +- **Zagnieżdżanie i unikalne ID:** Jeśli Twój tag może być zagnieżdżony lub potrzebuje stanu specyficznego dla instancji w czasie wykonania, użyj `$context->generateId()` w ramach swojej metody `print()`, aby utworzyć unikalne sufiksy dla Twoich zmiennych tymczasowych `$__`. +- **Providerzy dla danych zewnętrznych:** Używaj providerów (rejestrowanych przez `Extension::getProviders()`) do dostępu do danych lub usług wykonawczych ($this->global->...) zamiast hardkodowania wartości lub polegania na stanie globalnym. Używaj prefiksów producenta dla nazw providerów. +- **Rozważ n:atrybuty:** Jeśli Twój tag parzysty logicznie operuje na jednym elemencie HTML, Latte prawdopodobnie zapewnia automatyczne wsparcie `n:atrybutu`. Miej to na uwadze dla wygody użytkownika. Jeśli tworzysz tag modyfikujący atrybut, rozważ, czy czysty `n:atrybut` jest najodpowiedniejszą formą. +- **Testowanie:** Pisz testy dla swoich tagów, obejmujące zarówno parsowanie różnych wejść składniowych, jak i poprawność wyjścia generowanego **kodu PHP**. + +Przestrzegając tych wytycznych, możesz tworzyć potężne, solidne i łatwe w utrzymaniu własne tagi, które bezproblemowo integrują się z silnikiem szablonów Latte. + +.[note] +Studiowanie klas węzłów, które są częścią Latte, jest najlepszym sposobem na nauczenie się wszystkich szczegółów procesu parsowania. diff --git a/latte/pl/develop.texy b/latte/pl/develop.texy index d940bb87e5..a7b5347695 100644 --- a/latte/pl/develop.texy +++ b/latte/pl/develop.texy @@ -1,9 +1,9 @@ -Praktyki dla programistów -************************* +Procedury deweloperskie +*********************** -Instalacja .[#toc-installation] -=============================== +Instalacja +========== Najlepszym sposobem na zainstalowanie Latte jest użycie Composera: @@ -11,60 +11,56 @@ Najlepszym sposobem na zainstalowanie Latte jest użycie Composera: composer require latte/latte ``` -Obsługiwane wersje PHP (dotyczy najnowszych wersji Latte z łatką): +Obsługiwane wersje PHP (dotyczy najnowszych wersji Latte): -| wersja | kompatybilna z PHP +| wersja | kompatybilny z PHP |-----------------|------------------- -| Latte 3.0 | PHP 8.0 - 8.2 -| Latte 2.11 | PHP 7.1 - 8.2 -| Latte 2.8 - 2.10| PHP 7.1 - 8.1 +| Latte 3.0 | PHP 8.0 – 8.2 -Jak renderować szablon .[#toc-how-to-render-a-template] -======================================================= +Jak renderować szablon +====================== -Jak wyrenderować szablon? Wystarczy użyć tego prostego kodu: +Jak wyrenderować szablon? Wystarczy ten prosty kod: ```php $latte = new Latte\Engine; -// katalog podręczny +// katalog dla cache $latte->setTempDirectory('/path/to/tempdir'); $params = [ /* zmienne szablonu */ ]; // lub $params = new TemplateParameters(/* ... */); -// renderuj do wyjścia +// renderuj na wyjście $latte->render('template.latte', $params); -// lub renderuj do zmiennej +// renderuj do zmiennej $output = $latte->renderToString('template.latte', $params); ``` -Parametry mogą być tablicą lub jeszcze lepiej [obiektem |#Parameters-as-a-Class], który zapewnia sprawdzanie typu i podpowiadanie w edytorze. +Parametry mogą być tablicą lub, jeszcze lepiej, [obiektem |#Parametry jako klasa], który zapewni kontrolę typów i podpowiadanie w edytorach. .[note] -Możesz również znaleźć przykłady użycia w repozytorium [przykładów Latte |https://github.com/nette-examples/latte]. +Przykłady użycia można znaleźć również w repozytorium [Przykłady Latte |https://github.com/nette-examples/latte]. -Wydajność i buforowanie .[#toc-performance-and-caching] -======================================================= +Wydajność i cache +================= -Szablony Latte są niezwykle szybkie, ponieważ Latte kompiluje je bezpośrednio do kodu PHP i buforuje na dysku. Dzięki temu nie mają one żadnych dodatkowych kosztów ogólnych w porównaniu z szablonami napisanymi w czystym PHP. +Szablony w Latte są niezwykle szybkie, ponieważ Latte kompiluje je bezpośrednio do kodu PHP i zapisuje w pamięci podręcznej na dysku. Dzięki temu nie mają żadnego dodatkowego narzutu w porównaniu do szablonów napisanych w czystym PHP. -Pamięć podręczna jest automatycznie regenerowana przy każdej zmianie pliku źródłowego. Możesz więc wygodnie edytować swoje szablony Latte podczas tworzenia i widzieć zmiany natychmiast w przeglądarce. Możesz wyłączyć tę funkcję w środowisku produkcyjnym i zaoszczędzić trochę wydajności: +Cache jest automatycznie regenerowany za każdym razem, gdy zmienisz plik źródłowy. Podczas rozwoju możesz więc wygodnie edytować szablony w Latte, a zmiany natychmiast widzieć w przeglądarce. Możesz wyłączyć tę funkcję w środowisku produkcyjnym, aby zaoszczędzić trochę wydajności: ```php $latte->setAutoRefresh(false); ``` -Po wdrożeniu na serwerze produkcyjnym początkowe generowanie pamięci podręcznej, zwłaszcza w przypadku większych aplikacji, może, co zrozumiałe, zająć trochę czasu. Latte posiada wbudowane zabezpieczenie przed "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. -Jest to sytuacja, w której serwer otrzymuje dużą liczbę współbieżnych żądań, a ponieważ cache Latte jeszcze nie istnieje, wszystkie generowałyby go w tym samym czasie. Co powoduje kolce CPU. -Latte jest inteligentny, a gdy istnieje wiele współbieżnych żądań, tylko pierwszy wątek generuje pamięć podręczną, pozostałe czekają, a następnie używają go. +Podczas wdrażania na serwerze produkcyjnym, początkowe wygenerowanie cache, zwłaszcza w przypadku większych aplikacji, może oczywiście chwilę potrwać. Latte ma wbudowaną prewencję przed "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Jest to sytuacja, w której pojawia się większa liczba jednoczesnych żądań uruchamiających Latte, a ponieważ cache jeszcze nie istnieje, wszystkie zaczęłyby go generować jednocześnie. Co nieproporcjonalnie obciążyłoby serwer. Latte jest sprytne i przy wielu jednoczesnych żądaniach cache generuje tylko pierwszy wątek, pozostałe czekają, a następnie go wykorzystują. -Parametry jako klasa .[#toc-parameters-as-a-class] -================================================== +Parametry jako klasa +==================== -Lepszym rozwiązaniem niż przekazywanie zmiennych do szablonu jako tablic jest stworzenie klasy. Otrzymujesz [notację bezpieczną dla typu |type-system], [ładną sugestię w IDE |recipes#Editors and IDE] i sposób na [rejestrację filtrów |extending-latte#Filters Using the Class] i [funkcji |extending-latte#Functions Using the Class]. +Lepiej niż przekazywać zmienne do szablonu jako tablicę jest utworzyć klasę. Zyskasz w ten sposób [zapis bezpieczny typowo |type-system], [przyjemne podpowiadanie w IDE |recipes#Edytory i IDE] oraz drogę do [rejestracji filtrów |custom-filters#Filtry używające klasy z atrybutami] i [funkcji |custom-functions#Funkcje używające klasy z atrybutami]. ```php class MailTemplateParameters @@ -88,12 +84,12 @@ $latte->render('mail.latte', new MailTemplateParameters( ``` -Wyłączenie automatycznej ucieczki zmiennych .[#toc-disabling-auto-escaping-of-variable] -======================================================================================= +Wyłączanie automatycznego escapowania zmiennej +============================================== -Jeśli zmienna zawiera łańcuch HTML, możesz ją oznaczyć tak, aby Latte nie wykonywała automatycznego (a więc podwójnego) escape'u. Pozwala to uniknąć konieczności określania `|noescape` w szablonie. +Jeśli zmienna zawiera ciąg znaków w HTML, możesz ją oznaczyć tak, aby Latte automatycznie (a więc podwójnie) jej nie escapowało. Unikniesz w ten sposób konieczności używania w szablonie `|noescape`. -Najprostszym sposobem jest zawinięcie łańcucha w obiekt `Latte\Runtime\Html`: +Najprostszym sposobem jest opakowanie ciągu znaków w obiekt `Latte\Runtime\Html`: ```php $params = [ @@ -101,7 +97,7 @@ $params = [ ]; ``` -Latte nie ucieka również wszystkim obiektom, które implementują interfejs `Latte\HtmlStringable`. Możesz więc stworzyć własną klasę, której metoda `__toString()` będzie zwracała kod HTML, który nie będzie automatycznie escape'owany: +Latte ponadto nie escapuje wszystkich obiektów, które implementują interfejs `Latte\HtmlStringable`. Możesz więc stworzyć własną klasę, której metoda `__toString()` będzie zwracać kod HTML, który nie będzie automatycznie escapowany: ```php class Emphasis extends Latte\HtmlStringable @@ -123,32 +119,85 @@ $params = [ ``` .[warning] -Metoda `__toString` musi zwracać poprawny HTML i zapewniać uciekanie parametrów, w przeciwnym razie może wystąpić luka XSS! +Metoda `__toString` musi zwracać poprawny HTML i zapewniać escapowanie parametrów, w przeciwnym razie może dojść do podatności XSS! -Jak rozszerzyć Latte o filtry, tagi, itp. .[#toc-how-to-extend-latte-with-filters-tags-etc] -=========================================================================================== +Jak rozszerzyć Latte o filtry, znaczniki itp. +============================================= -Jak dodać niestandardowy filtr, funkcję, tag itp. do Latte? Jest to temat rozdziału [Extending Latte |extending-latte]. -Jeśli chcesz ponownie użyć swoich dostosowań w różnych projektach lub podzielić się nimi z innymi, powinieneś [utworzyć rozszerzenie |creating-extension]. +Jak dodać do Latte własny filtr, funkcję, znacznik itp.? O tym traktuje rozdział [rozszerzanie Latte |extending-latte]. Jeśli chcesz ponownie wykorzystać swoje modyfikacje w różnych projektach lub podzielić się nimi z innymi, powinieneś [utworzyć rozszerzenie |extending-latte#Latte Extension]. -Dowolny kod w szablonie `{php ...}` .{data-version:3.0}{toc: RawPhpExtension} -============================================================================= +Dowolny kod w szablonie `{php ...}` .{toc: RawPhpExtension} +=========================================================== -Wewnątrz tagu [`{do}` |tags#do] może zawierać tylko wyrażenia PHP, więc nie możesz wstawiać takich konstrukcji jak `if ... else` czy wyrażenia zakończone średnikiem. +Wewnątrz znacznika [`{do}` |tags#do] można zapisywać tylko wyrażenia PHP, nie można więc na przykład wstawiać konstrukcji takich jak `if ... else` ani instrukcji zakończonych średnikiem. -Można jednak zarejestrować rozszerzenie `RawPhpExtension`, które dodaje znacznik `{php ...}`, za pomocą którego można wstawić dowolny kod PHP na ryzyko autora szablonu. +Można jednak zarejestrować rozszerzenie `RawPhpExtension`, które dodaje znacznik `{php ...}`. Za jego pomocą można wstawiać dowolny kod PHP. Nie obowiązują go żadne zasady trybu sandbox, użycie jest więc na odpowiedzialność autora szablonu. ```php $latte->addExtension(new Latte\Essential\RawPhpExtension); ``` -Tłumaczenie w szablonach .{data-version:3.0}{toc: TranslatorExtension} -====================================================================== +Kontrola wygenerowanego kodu .{data-version:3.0.7} +================================================== + +Latte kompiluje szablony do kodu PHP. Oczywiście dba o to, aby wygenerowany kod był składniowo poprawny. Jednak przy użyciu rozszerzeń stron trzecich lub `RawPhpExtension` Latte nie może zagwarantować poprawności wygenerowanego pliku. Można również napisać w PHP kod, który jest wprawdzie składniowo poprawny, ale jest zabroniony (na przykład przypisanie wartości do zmiennej `$this`) i spowoduje PHP Compile Error. Jeśli taką operację zapiszesz w szablonie, trafi ona również do wygenerowanego kodu PHP. Ponieważ w PHP istnieje ponad dwieście różnych zabronionych operacji, Latte nie ma ambicji ich wykrywać. Zwróci na nie uwagę dopiero samo PHP podczas renderowania, co zazwyczaj niczemu nie szkodzi. -Użyj rozszerzenia `TranslatorExtension`, aby dodać tagi do swojego szablonu [`{_...}` |tags#_], [`{translate}` |tags#translate] i filtr [`translate` |filters#translate]. Służy do tłumaczenia wartości lub części szablonu na inne języki. Jako parametr określ metodę (callable PHP) wykonującą tłumaczenie: +Są jednak sytuacje, w których chcesz wiedzieć już w momencie kompilacji szablonu, że nie zawiera on żadnego PHP Compile Error. Zwłaszcza wtedy, gdy szablony mogą edytować użytkownicy lub używasz [Sandbox |sandbox]. W takim przypadku zlecaj kontrolę szablonów już w czasie kompilacji. Tę funkcjonalność włączysz metodą `Engine::enablePhpLint()`. Ponieważ do kontroli potrzebuje wywołać binarkę PHP, przekaż do niej ścieżkę jako parametr: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); + +try { + $latte->compile('home.latte'); +} catch (Latte\CompileException $e) { + // przechwytuje błędy w Latte, a także Compile Error w PHP + echo 'Błąd: ' . $e->getMessage(); +} +``` + + +Ustawienia regionalne .{data-version:3.0.18}{toc: Locale} +========================================================= + +Latte pozwala ustawić ustawienia regionalne (locale), które wpływają na formatowanie liczb, dat i sortowanie. Ustawia się je za pomocą metody `setLocale()`. Identyfikator ustawień regionalnych jest zgodny ze standardem IETF language tag, który używa rozszerzenie PHP `intl`. Składa się z kodu języka i opcjonalnie kodu kraju, np. `en_US` dla angielskiego w Stanach Zjednoczonych, `de_DE` dla niemieckiego w Niemczech, `pl_PL` dla polskiego w Polsce itp. + +```php +$latte = new Latte\Engine; +$latte->setLocale('pl_PL'); +``` + +Ustawienie locale wpływa na filtry [localDate |filters#localDate], [sort |filters#sort], [number |filters#number] oraz [bytes |filters#bytes]. + +.[note] +Wymaga rozszerzenia PHP `intl`. Ustawienie w Latte nie wpływa na globalne ustawienia locale w PHP. + + +Tryb ścisły .{data-version:3.0.8} +================================= + +W trybie ścisłego parsowania Latte kontroluje, czy nie brakuje zamykających znaczników HTML, a także zabrania używania zmiennej `$this`. Włączysz go w ten sposób: + +```php +$latte = new Latte\Engine; +$latte->setStrictParsing(); +``` + +Generowanie szablonów z nagłówkiem `declare(strict_types=1)` włączysz w ten sposób: + +```php +$latte = new Latte\Engine; +$latte->setStrictTypes(); +``` + + +Tłumaczenie w szablonach .{toc: TranslatorExtension} +==================================================== + +Za pomocą rozszerzenia `TranslatorExtension` dodasz do szablonu znaczniki [`{_...}` |tags#], [`{translate}` |tags#translate] oraz filtr [`translate` |filters#translate]. Służą one do tłumaczenia wartości lub części szablonu na inne języki. Jako parametr podajemy metodę (PHP callable) wykonującą tłumaczenie: ```php class MyTranslator @@ -158,7 +207,7 @@ class MyTranslator public function translate(string $original): string { - // utworzyć $translated z $original zgodnie z $this->lang + // z $original tworzymy $translated zgodnie z $this->lang return $translated; } } @@ -170,7 +219,7 @@ $extension = new Latte\Essential\TranslatorExtension( $latte->addExtension($extension); ``` -Tłumacz jest wywoływany w trybie runtime, gdy szablon jest renderowany. Jednak Latte może tłumaczyć wszystkie statyczne teksty podczas kompilacji szablonu. To oszczędza wydajność, ponieważ każdy ciąg jest tłumaczony tylko raz, a wynikowe tłumaczenie jest zapisywane do skompilowanego pliku. Tworzy to wiele skompilowanych wersji szablonu w katalogu cache, po jednej dla każdego języka. Aby to zrobić, musisz tylko określić język jako drugi parametr: +Translator jest wywoływany w czasie działania podczas renderowania szablonu. Latte potrafi jednak wszystkie statyczne teksty tłumaczyć już podczas kompilacji szablonu. Oszczędza to wydajność, ponieważ każdy ciąg znaków jest tłumaczony tylko raz, a wynikowe tłumaczenie jest zapisywane w skompilowanej postaci. W katalogu z cache powstaje więc więcej skompilowanych wersji szablonu, jedna dla każdego języka. W tym celu wystarczy podać język jako drugi parametr: ```php $extension = new Latte\Essential\TranslatorExtension( @@ -179,9 +228,9 @@ $extension = new Latte\Essential\TranslatorExtension( ); ``` -Przez tekst statyczny rozumiemy na przykład `{_'hello'}` lub `{translate}hello{/translate}`. Tekst niestatyczny, taki jak `{_$foo}`, będzie nadal tłumaczony w trybie runtime. +Tekstem statycznym jest na przykład `{_'hello'}` lub `{translate}hello{/translate}`. Teksty niestatyczne, jak na przykład `{_$foo}`, nadal będą tłumaczone w czasie działania. -Szablon może również przekazać dodatkowe parametry do tłumacza poprzez `{_$original, foo: bar}` lub `{translate foo: bar}`, które otrzymuje jako tablicę `$params`: +Tłumaczowi można z szablonu przekazywać również dodatkowe parametry za pomocą `{_$original, foo: bar}` lub `{translate foo: bar}`, które otrzyma jako tablicę `$params`: ```php public function translate(string $original, ...$params): string @@ -191,66 +240,73 @@ public function translate(string $original, ...$params): string ``` -Debugowanie i Tracy .[#toc-debugging-and-tracy] -=============================================== +Debugowanie i Tracy +=================== -Latte stara się, aby Twój rozwój był jak najbardziej przyjemny. Istnieją trzy znaczniki bezpośrednio do celów debugowania [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak] a [`{trace}` |tags#trace]. +Latte stara się jak najbardziej ułatwić Ci rozwój. Bezpośrednio do celów debugowania istnieje trójka znaczników [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak] oraz [`{trace}` |tags#trace]. -Największą wygodę uzyskasz, jeśli jeszcze zainstalujesz świetny [debugger Tracy |tracy:] i aktywujesz dodatek Latte: +Największy komfort uzyskasz, jeśli zainstalujesz jeszcze świetne [narzędzie do debugowania Tracy |tracy:] i aktywujesz dodatek dla Latte: ```php -// umożliwia Tracy +// włącza Tracy Tracy\Debugger::enable(); $latte = new Latte\Engine; -// aktywuje rozszerzenie Tracy +// aktywuje rozszerzenie dla Tracy $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension); ``` -Teraz zobaczysz wszystkie błędy na wyraźnym czerwonym ekranie, w tym błędy w szablonach z podświetleniem wierszy i kolumn ([wideo |https://github.com/nette/tracy/releases/tag/v2.9.0]). -Jednocześnie w prawym dolnym rogu Tracy Bar pojawia się zakładka Latte, na której wyraźnie widać wszystkie wyrenderowane szablony i ich relacje (w tym możliwość kliknięcia w szablon lub skompilowany kod), a także zmienne: +Teraz wszystkie błędy będą wyświetlane na przejrzystym czerwonym ekranie, w tym błędy w szablonach z podświetleniem wiersza i kolumny ([wideo |https://github.com/nette/tracy/releases/tag/v2.9.0]). Jednocześnie w prawym dolnym rogu, w tzw. Tracy Barze, pojawi się zakładka dla Latte, gdzie przejrzyście widać wszystkie renderowane szablony i ich wzajemne relacje (włącznie z możliwością przejścia do szablonu lub skompilowanego kodu) oraz zmienne: [* latte-debugging.webp *] -Ponieważ Latte kompiluje szablony do czytelnego kodu PHP, możesz wygodnie przejść przez nie w swoim IDE. +Ponieważ Latte kompiluje szablony do przejrzystego kodu PHP, możesz je wygodnie krokować w swoim IDE. -Linter: Sprawdzanie poprawności składni szablonu .{data-version:2.11}{toc: Linter} -================================================================================== +Linter: walidacja składni szablonów .{toc: Linter} +================================================== -Narzędzie Linter pomoże Ci przejść przez wszystkie szablony i sprawdzić błędy składni. Jest ono uruchamiane z konsoli: +Przejrzeć wszystkie szablony i sprawdzić, czy nie zawierają błędów składniowych, pomoże Ci narzędzie Linter. Uruchamia się je z konsoli: ```shell -vendor/bin/latte-lint +vendor/bin/latte-lint <ścieżka> ``` -Jeśli używasz niestandardowych znaczników, utwórz również swój niestandardowy Linter, np. `custom-latte-lint`: +Parametrem `--strict` aktywujesz [#tryb ścisły]. + +Jeśli używasz własnych znaczników, utwórz również własną wersję Lintera, np. `custom-latte-lint`: ```php #!/usr/bin/env php scanDirectory($path); +$path = $argv[1] ?? '.'; -$engine = new Latte\Engine; -// rejestruje poszczególne rozszerzenia tutaj -$engine->addExtension(/* ... */); +$linter = new Latte\Tools\Linter; +$latte = $linter->getEngine(); +// tutaj dodaj swoje poszczególne rozszerzenia +$latte->addExtension(/* ... */); -$path = $argv[1]; -$linter = new Latte\Tools\Linter(engine: $engine); $ok = $linter->scanDirectory($path); exit($ok ? 0 : 1); ``` +Alternatywnie możesz przekazać własny obiekt `Latte\Engine` do Lintera: + +```php +$latte = new Latte\Engine; +// tutaj konfigurujemy obiekt $latte +$linter = new Latte\Tools\Linter(engine: $latte); +``` + -Ładowanie szablonów z łańcucha .[#toc-loading-templates-from-a-string] -====================================================================== +Ładowanie szablonów z ciągu znaków +================================== -Potrzebujesz załadować szablony z ciągów znaków zamiast z plików, być może w celach testowych? [StringLoader |extending-latte#stringloader] pomoże Ci w tym: +Potrzebujesz ładować szablony z ciągów znaków zamiast plików, na przykład do celów testowania? Pomoże Ci [StringLoader |loaders#StringLoader]: ```php $latte->setLoader(new Latte\Loaders\StringLoader([ @@ -262,10 +318,10 @@ $latte->render('main.file', $params); ``` -Exception Handler .[#toc-exception-handler] -=========================================== +Obsługa wyjątków +================ -Możesz zdefiniować swój własny handler dla oczekiwanych wyjątków. Wyjątki zgłaszane wewnątrz [`{try}` |tags#try] i w [piaskownicy |sandbox] są do niego przekazywane. +Możesz zdefiniować własny handler do obsługi oczekiwanych wyjątków. Przekazane zostaną mu wyjątki powstałe wewnątrz [`{try}` |tags#try] oraz w [sandboxie |sandbox]. ```php $loggingHandler = function (Throwable $e, Latte\Runtime\Template $template) use ($logger) { @@ -277,17 +333,17 @@ $latte->setExceptionHandler($loggingHandler); ``` -Automatyczne wyszukiwanie układu .[#toc-automatic-layout-lookup] -================================================================ +Automatyczne wyszukiwanie layoutu +================================= -Używając znacznika [`{layout}` |template-inheritance#layout-inheritance]szablon określa swój szablon nadrzędny. Możliwe jest również automatyczne wyszukiwanie układu, co ułatwi pisanie szablonów, ponieważ nie będą one musiały zawierać znacznika `{layout}`. +Za pomocą znacznika [`{layout}` |template-inheritance#Dziedziczenie layoutu] szablon określa swój szablon nadrzędny. Możliwe jest również automatyczne wyszukiwanie layoutu, co uprości pisanie szablonów, ponieważ nie będzie w nich konieczne używanie znacznika `{layout}`. Osiąga się to w następujący sposób: ```php $finder = function (Latte\Runtime\Template $template) { if (!$template->getReferenceType()) { - // zwraca ścieżkę do nadrzędnego pliku szablonu + // zwraca ścieżkę do pliku layoutu return 'automatic.layout.latte'; } }; @@ -296,4 +352,4 @@ $latte = new Latte\Engine; $latte->addProvider('coreParentFinder', $finder); ``` -Jeśli szablon nie powinien mieć układu, wskaże to za pomocą znacznika `{layout none}`. +Jeśli szablon nie ma mieć layoutu, informuje o tym znacznikiem `{layout none}`. diff --git a/latte/pl/extending-latte.texy b/latte/pl/extending-latte.texy index deb168f196..174b31b92f 100644 --- a/latte/pl/extending-latte.texy +++ b/latte/pl/extending-latte.texy @@ -1,285 +1,227 @@ -Expanding Latte -*************** +Rozszerzanie Latte +****************** .[perex] -Latte jest bardzo elastyczny i może być rozszerzony na wiele sposobów: możesz dodać niestandardowe filtry, funkcje, tagi, loadery itp. Pokażemy ci, jak to zrobić. +Latte zostało zaprojektowane z myślą o rozszerzalności. Chociaż jego standardowy zestaw znaczników, filtrów i funkcji obejmuje wiele przypadków użycia, często trzeba dodać własną, specyficzną logikę lub narzędzia pomocnicze. Ta strona zawiera przegląd sposobów rozszerzenia Latte, aby idealnie pasowało do wymagań Twojego projektu - od prostych pomocników po złożoną nową składnię. -W tym rozdziale opisano różne sposoby rozszerzania Latte. Jeśli chcesz ponownie użyć swoich dostosowań w różnych projektach lub podzielić się nimi z innymi, powinieneś [utworzyć rozszerzenie |creating-extension]. +Sposoby rozszerzania Latte +========================== -Ile dróg prowadzi do Rzymu? .[#toc-how-many-roads-lead-to-rome] -=============================================================== +Oto szybki przegląd głównych sposobów dostosowywania i rozszerzania Latte: -Ponieważ niektóre sposoby przedłużania Latte mogą się łączyć, spróbujmy najpierw wyjaśnić różnice między nimi. Jako przykład spróbujemy zaimplementować generator *Lorem ipsum*, przekazaliśmy liczbę słów do wygenerowania. +- **[Filtry niestandardowe |Custom Filters]:** Do formatowania lub przekształcania danych bezpośrednio w wyjściu szablonu (np. `{$var|myFilter}`). Idealne do zadań takich jak formatowanie dat, modyfikacje tekstu lub stosowanie specyficznego escapowania. Można je również użyć do modyfikacji większych bloków zawartości HTML, opakowując zawartość w anonimowy [`{block}` |tags#block] i stosując na nim własny filtr. +- **[Funkcje niestandardowe |Custom Functions]:** Do dodawania logiki wielokrotnego użytku, którą można wywoływać w wyrażeniach w szablonie (np. `{myFunction($arg1, $arg2)}`). Przydatne do obliczeń, dostępu do funkcji pomocniczych aplikacji lub generowania małych fragmentów treści. +- **[Znaczniki niestandardowe |Custom Tags]:** Do tworzenia zupełnie nowych konstrukcji językowych (`{mytag}...{/mytag}` lub `n:mytag`). Znaczniki oferują najwięcej możliwości, pozwalają definiować własne struktury, kontrolować parsowanie szablonu i implementować złożoną logikę renderowania. +- **[Przejścia kompilatora |Compiler Passes]:** Funkcje, które modyfikują abstrakcyjne drzewo składni (AST) szablonu po parsowaniu, ale przed generowaniem kodu PHP. Używane do zaawansowanych optymalizacji, kontroli bezpieczeństwa (takich jak Sandbox) lub automatycznych modyfikacji kodu. +- **[Niestandardowe loadery |loaders]:** Do zmiany sposobu, w jaki Latte wyszukuje i ładuje pliki szablonów (np. ładowanie z bazy danych, zaszyfrowanego magazynu itp.). -Głównym konstruktem języka Latte jest tag. Możemy zaimplementować generator poprzez rozszerzenie Latte o nowy tag: +Wybór odpowiedniej metody rozszerzenia jest kluczowy. Zanim stworzysz złożony znacznik, zastanów się, czy nie wystarczyłby prostszy filtr lub funkcja. Pokażmy to na przykładzie: implementacja generatora *Lorem ipsum*, który jako argument przyjmuje liczbę słów do wygenerowania. -```latte -{lipsum 40} -``` - -Przywieszka sprawdzi się znakomicie. Jednak generator w postaci znacznika może nie być wystarczająco elastyczny, ponieważ nie można go użyć w wyrażeniu. Przy okazji, w praktyce rzadko trzeba generować tagi; i to jest dobra wiadomość, ponieważ tagi są bardziej skomplikowanym sposobem na rozszerzenie. - -Ok, spróbujmy stworzyć filtr zamiast tagu: - -```latte -{=40|lipsum} -``` - -Ponownie, ważna opcja. Ale filtr powinien przekształcić przekazaną wartość w coś innego. Tutaj używamy wartości `40`, która wskazuje na liczbę wygenerowanych słów, jako argumentu filtra, a nie jako wartości, którą chcemy przekształcić. +- **Jako znacznik?** `{lipsum 40}` - Możliwe, ale znaczniki są bardziej odpowiednie dla struktur sterujących lub generowania złożonych znaczników. Znaczników nie można używać bezpośrednio w wyrażeniach. +- **Jako filtr?** `{=40|lipsum}` - Technicznie to działa, ale filtry są przeznaczone do *transformacji* wartości wejściowej. Tutaj `40` jest *argumentem*, a nie wartością, która jest transformowana. To wydaje się semantycznie niepoprawne. +- **Jako funkcja?** `{lipsum(40)}` - To jest najbardziej naturalne rozwiązanie! Funkcje przyjmują argumenty i zwracają wartości, co jest idealne do użycia w dowolnym wyrażeniu: `{var $text = lipsum(40)}`. -Spróbujmy więc użyć funkcji: +**Ogólne zalecenie:** Używaj funkcji do obliczeń/generowania, filtrów do transformacji i znaczników do nowych konstrukcji językowych lub złożonych znaczników. Przejść używaj do manipulacji AST, a loaderów do pobierania szablonów. -```latte -{lipsum(40)} -``` -To jest to! Dla tego konkretnego przykładu, tworzenie funkcji jest idealnym sposobem na rozszerzenie. Możesz go wywołać wszędzie tam, gdzie akceptowane jest wyrażenie, np: +Bezpośrednia rejestracja +======================== -```latte -{var $text = lipsum(40)} -``` +Dla narzędzi pomocniczych specyficznych dla projektu lub szybkich rozszerzeń, Latte umożliwia bezpośrednią rejestrację filtrów i funkcji w obiekcie `Latte\Engine`. - -Filtry .[#toc-filters] -====================== - -Utwórz filtr, rejestrując jego nazwę i dowolną możliwość wywołania w PHP, np. funkcję: +Do rejestracji filtra użyj metody `addFilter()`. Pierwszym argumentem Twojej funkcji filtrującej będzie wartość przed znakiem `|`, a kolejne argumenty to te, które są przekazywane za dwukropkiem `:`. ```php $latte = new Latte\Engine; -$latte->addFilter('shortify', fn(string $s) => mb_substr($s, 0, 10)); // obcina tekst do 10 liter -``` -W tym przypadku przydałoby się, aby filtr akceptował dodatkowy parametr: +// Definicja filtra (obiekt wywoływalny: funkcja, metoda statyczna itp.) +$myTruncate = fn(string $s, int $length = 50) => mb_substr($s, 0, $length); -```php -$latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); -``` - -W szablonie jest on wtedy nazywany w następujący sposób: +// Rejestracja +$latte->addFilter('truncate', $myTruncate); -```latte -

                                                                                        {$text|shortify}

                                                                                        -

                                                                                        {$text|shortify:100}

                                                                                        +// Użycie w szablonie: {$text|truncate} lub {$text|truncate:100} ``` -Jak widać, funkcja otrzymuje lewą stronę filtra przed fajką `|` jako první argument a argumenty předané filtru za `:` jako dodatkowe argumenty. - -Oczywiście funkcja reprezentująca filtr może przyjąć dowolną liczbę parametrów, obsługiwane są również parametry variadyczne. - - -Filtry z wykorzystaniem klasy .[#toc-filters-using-the-class] -------------------------------------------------------------- - -Drugim sposobem na zdefiniowanie filtra jest [użycie klasy |develop#Parameters-as-a-Class]. Tworzymy metodę z atrybutem `TemplateFilter`: +Możesz również zarejestrować **Filter Loader**, funkcję, która dynamicznie dostarcza obiekty wywoływalne filtrów według żądanej nazwy: ```php -class TemplateParameters -{ - public function __construct( - // parametry - ) {} - - #[Latte\Attributes\TemplateFilter] - public function shortify(string $s, int $len = 10): string - { - return mb_substr($s, 0, $len); - } -} - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); +$latte->addFilterLoader(fn(string $name) => /* zwraca obiekt wywoływalny lub null */); ``` -Jeśli używasz PHP 7.x i Latte 2.x, zamiast atrybutu podaj adnotację `/** @filter */`. - -Ładowarka filtrów .[#toc-filter-loader] ---------------------------------------- - -Zamiast rejestrować poszczególne filtry, można stworzyć tzw. loader, czyli funkcję, która jest wywoływana z nazwą filtra jako argumentem i zwraca jego wywołanie w PHP lub null. +Do rejestracji funkcji użytecznej w wyrażeniach szablonu użyj `addFunction()`. ```php -$latte->addFilterLoader([new Filters, 'load']); +$latte = new Latte\Engine; +// Definicja funkcji +$isWeekend = fn(DateTimeInterface $date) => $date->format('N') >= 6; -class Filters -{ - public function load(string $filter): ?callable - { - if (in_array($filter, get_class_methods($this))) { - return [$this, $filter]; - } - return null; - } - - public function shortify($s, $len = 10) - { - return mb_substr($s, 0, $len); - } - - // ... -} +// Rejestracja +$latte->addFunction('isWeekend', $isWeekend); + +// Użycie w szablonie: {if isWeekend($myDate)}Weekend!{/if} ``` +Więcej informacji znajdziesz w części [Tworzenie niestandardowych filtrów |custom-filters] i [Funkcji |custom-functions]. -Filtry kontekstowe .[#toc-contextual-filters] ---------------------------------------------- -Filtr kontekstowy to taki, który akceptuje obiekt [api:Latte\Runtime\FilterInfo] w pierwszym parametrze, a następnie inne parametry jak w klasycznych filtrach. Jest zarejestrowany w ten sam sposób, Latte samo rozpoznaje, że filtr jest kontekstowy: +Solidny sposób: Rozszerzenie Latte .{toc: Latte Extension} +========================================================== -```php -use Latte\Runtime\FilterInfo; +Chociaż bezpośrednia rejestracja jest prosta, standardowym i zalecanym sposobem pakowania i dystrybucji rozszerzeń Latte jest użycie klas **Extension**. Extension służy jako centralny punkt konfiguracyjny do rejestracji wielu znaczników, filtrów, funkcji, przejść kompilatora i innych elementów. -$latte->addFilter('foo', function (FilterInfo $info, string $str): string { - // ... -}); -``` +Dlaczego używać Extensions? -Filtry kontekstowe mogą wykrywać i modyfikować typ zawartości, który otrzymują w zmiennej `$info->contentType`. Jeśli filtr zostanie wywołany klasycznie nad zmienną (np. `{$var|foo}`), to `$info->contentType` będzie zawierał null. +- **Organizacja:** Utrzymuje powiązane rozszerzenia (znaczniki, filtry itp. dla konkretnej funkcji) razem w jednej klasie. +- **Wielokrotne użycie i udostępnianie:** Łatwo spakujesz swoje rozszerzenia do użytku w innych projektach lub do udostępnienia społeczności (np. przez Composer). +- **Pełna moc:** Niestandardowe znaczniki i przejścia kompilatora *mogą być rejestrowane tylko* za pośrednictwem Extensions. -Filtr powinien najpierw sprawdzić, czy typ zawartości łańcucha wejściowego jest obsługiwany. Może też ją zmienić. Przykład filtra, który przyjmuje tekst (lub null) i zwraca HTML: + +Rejestracja Rozszerzenia +------------------------ + +Extension rejestruje się w Latte za pomocą metody `addExtension()` (lub za pośrednictwem [pliku konfiguracyjnego |application:configuration#Szablony Latte]): ```php -use Latte\Runtime\FilterInfo; - -$latte->addFilter('money', function (FilterInfo $info, float $amount): string { - // nejprve oveříme, zda je vstupem content-type text - if (!in_array($info->contentType, [null, ContentType::Text])) { - throw new Exception("Filtr |money użyty w niekompatybilnym typie zawartości $info->contentType."); - } - - // změníme content-type na HTML - $info->contentType = ContentType::Html; - return "$num Kč"; -}); +$latte = new Latte\Engine; +$latte->addExtension(new MyProjectExtension); ``` -.[note] -W tym przypadku filtr musi zapewnić prawidłową ucieczkę danych. +Jeśli zarejestrujesz wiele rozszerzeń i definiują one tak samo nazwane znaczniki, filtry lub funkcje, pierwszeństwo ma ostatnio dodane rozszerzenie. Oznacza to również, że Twoje rozszerzenia mogą nadpisywać natywne znaczniki/filtry/funkcje. -Wszystkie filtry, które są używane nad [blokami |tags#block] (np. jako `{block|foo}...{/block}`) musi mieć charakter kontekstowy. +Za każdym razem, gdy dokonasz zmiany w klasie i automatyczne odświeżanie nie jest wyłączone, Latte automatycznie przekompiluje Twoje szablony. -Funkcja .[#toc-functions] -========================= +Tworzenie Rozszerzenia +---------------------- -Domyślnie wszystkie natywne funkcje PHP mogą być używane w Latte, chyba że piaskownica to wyłączy. Ale można też zdefiniować własne funkcje. Mogą one nadpisać funkcje natywne. +Aby utworzyć własne rozszerzenie, musisz utworzyć klasę, która dziedziczy z [api:Latte\Extension]. Aby zobaczyć, jak wygląda takie rozszerzenie, spójrz na wbudowane "CoreExtension":https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php. -Tworzysz funkcję, rejestrując jej nazwę i dowolne wywołanie w PHP: +Przyjrzyjmy się metodom, które możesz zaimplementować: -```php -$latte = new Latte\Engine; -$latte->addFunction('random', function (...$args) { - return $args[array_rand($args)]; -}); -``` -Użycie jest wtedy takie samo jak w przypadku wywołania funkcji PHP: +beforeCompile(Latte\Engine $engine): void .[method] +--------------------------------------------------- -```latte -{random(jablko, pomeranč, citron)} // vypíše například: jablko -``` +Wywoływane przed kompilacją szablonu. Metoda może być używana na przykład do inicjalizacji związanych z kompilacją. -Funkcja wykorzystująca klasę .[#toc-functions-using-the-class] --------------------------------------------------------------- +getTags(): array .[method] +-------------------------- -Drugim sposobem na zdefiniowanie funkcji jest [użycie klasy |develop#Parameters-as-a-Class]. Tworzymy metodę z atrybutem `TemplateFunction`: +Wywoływane podczas kompilacji szablonu. Zwraca tablicę asocjacyjną *nazwa znacznika => obiekt wywoływalny*, które są funkcjami do parsowania znaczników. [Więcej informacji |custom-tags]. ```php -class TemplateParameters +public function getTags(): array { - public function __construct( - // parametry - ) {} - - #[Latte\Attributes\TemplateFunction] - public function random(...$args) - { - return $args[array_rand($args)]; - } + return [ + 'foo' => FooNode::create(...), + 'bar' => BarNode::create(...), + 'n:baz' => NBazNode::create(...), + // ... + ]; } - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); ``` -Jeśli używasz PHP 7.x i Latte 2.x, zamiast atrybutu podaj adnotację `/** @function */`. +Znacznik `n:baz` reprezentuje czysty [n:atrybut |syntax#n:atrybuty], czyli znacznik, który można zapisać tylko jako atrybut. +Dla znaczników `foo` i `bar` Latte automatycznie rozpozna, czy są to znaczniki parzyste, a jeśli tak, można je automatycznie zapisywać za pomocą n:atrybutów, w tym wariantów z prefiksami `n:inner-foo` i `n:tag-foo`. -Ładowarki .[#toc-loaders] -========================= +Kolejność wykonywania takich n:atrybutów jest określona przez ich kolejność w tablicy zwróconej przez metodę `getTags()`. Zatem `n:foo` jest zawsze wykonywany przed `n:bar`, nawet jeśli atrybuty w znaczniku HTML są wymienione w odwrotnej kolejności, jak `
                                                                                        `. -Loadery są odpowiedzialne za ładowanie szablonów ze źródła, takiego jak system plików. Ustawia się je metodą `setLoader()`: +Jeśli potrzebujesz określić kolejność n:atrybutów w wielu rozszerzeniach, użyj metody pomocniczej `order()`, gdzie parametr `before` xor `after` określa, które znaczniki są sortowane przed lub za znacznikiem. ```php -$latte->setLoader(new MyLoader); +public function getTags(): array +{ + return [ + 'foo' => self::order(FooNode::create(...), before: 'bar'), + 'bar' => self::order(BarNode::create(...), after: ['block', 'snippet']), + ]; +} ``` -Wbudowane ładowarki to: +getPasses(): array .[method] +---------------------------- -FileLoader .[#toc-fileloader] ------------------------------ +Wywoływane podczas kompilacji szablonu. Zwraca tablicę asocjacyjną *nazwa przejścia => obiekt wywoływalny*, które są funkcjami reprezentującymi tzw. [przejścia kompilatora |compiler-passes], które przechodzą i modyfikują AST. -Default loader. Ładuje szablony z systemu plików. - -Dostęp do plików może być ograniczony poprzez ustawienie katalogu podstawowego: +Tutaj również można użyć metody pomocniczej `order()`. Wartość parametrów `before` lub `after` może być `*` o znaczeniu przed/po wszystkich. ```php -$latte->setLoader(new Latte\Loaders\FileLoader($templateDir)); -$latte->render('test.latte'); +public function getPasses(): array +{ + return [ + 'optimize' => Passes::optimizePass(...), + 'sandbox' => self::order($this->sandboxPass(...), before: '*'), + // ... + ]; +} ``` -StringLoader .[#toc-stringloader] ---------------------------------- +beforeRender(Latte\Engine $engine): void .[method] +-------------------------------------------------- -Ładuje szablony z ciągów znaków. Ten loader jest bardzo przydatny do testów. Może być również używany do małych projektów, w których sensowne może być przechowywanie wszystkich szablonów w jednym pliku PHP. +Wywoływane przed każdym renderowaniem szablonu. Metoda może być używana na przykład do inicjalizacji zmiennych używanych podczas renderowania. -```php -$latte->setLoader(new Latte\Loaders\StringLoader([ - 'main.file' => '{include other.file}', - 'other.file' => '{if true} {$var} {/if}', -])); -$latte->render('main.file'); -``` +getFilters(): array .[method] +----------------------------- -Uproszczone użytkowanie: +Wywoływane przed renderowaniem szablonu. Zwraca filtry jako tablicę asocjacyjną *nazwa filtra => obiekt wywoływalny*. [Więcej informacji |custom-filters]. ```php -$template = '{if true} {$var} {/if}'; -$latte->setLoader(new Latte\Loaders\StringLoader); -$latte->render($template); +public function getFilters(): array +{ + return [ + 'batch' => $this->batchFilter(...), + 'trim' => $this->trimFilter(...), + // ... + ]; +} ``` -Stwórz swój własny program ładujący .[#toc-creating-a-custom-loader] --------------------------------------------------------------------- - -Loader to klasa implementująca interfejs [api:Latte\Loader]. +getFunctions(): array .[method] +------------------------------- +Wywoływane przed renderowaniem szablonu. Zwraca funkcje jako tablicę asocjacyjną *nazwa funkcji => obiekt wywoływalny*. [Więcej informacji |custom-functions]. -Tagi .[#toc-tags] -================= +```php +public function getFunctions(): array +{ + return [ + 'clamp' => $this->clampFunction(...), + 'divisibleBy' => $this->divisibleByFunction(...), + // ... + ]; +} +``` -Jedną z najciekawszych cech jądra templatki jest możliwość definiowania nowych konstrukcji językowych za pomocą znaczników. Jest to również bardziej złożona funkcjonalność i musisz zrozumieć, jak Latte działa wewnętrznie. -W większości przypadków jednak znacznik nie jest potrzebny: -- jeśli ma generować jakieś dane wyjściowe, użyj zamiast tego [funkcji |#Functions] -- jeśli miałby zmodyfikować jakieś dane wejściowe i je zwrócić, użyj zamiast tego [filtra |#Filters] -- jeśli miałby edytować obszar tekstu, owinąć go tagiem [`{block}` |tags#block] i użyj [filtra |#Contextual-Filters] -- jeśli nie miałoby to nic dać, a jedynie wywołać funkcję, wywołaj ją za pomocą [`{do}` |tags#do] +getProviders(): array .[method] +------------------------------- -Jeśli nadal chcesz stworzyć tag, świetnie! Wszystkie najważniejsze informacje znajdziesz w rozdziale [Tworzenie rozszerzenia |creating-extension]. +Wywoływane przed renderowaniem szablonu. Zwraca tablicę dostawców, którzy są zazwyczaj obiektami używanymi przez znaczniki w czasie działania. Dostęp do nich uzyskuje się przez `$this->global->...`. [Więcej informacji |custom-tags#Wprowadzenie providerów]. +```php +public function getProviders(): array +{ + return [ + 'myFoo' => $this->foo, + 'myBar' => $this->bar, + // ... + ]; +} +``` -Kompilator przechodzi .[#toc-compiler-passes] -============================================= -Przejścia kompilatora to funkcje, które modyfikują AST lub zbierają w nich informacje. W Latte, na przykład, piaskownica jest zaimplementowana w ten sposób: przemierza wszystkie węzły AST, znajduje wywołania funkcji i metod i zastępuje je wywołaniami, które kontroluje. +getCacheKey(Latte\Engine $engine): mixed .[method] +-------------------------------------------------- -Podobnie jak w przypadku tagów, jest to bardziej złożona funkcjonalność i musisz zrozumieć, jak Latte działa pod maską. Wszystkie najważniejsze informacje można znaleźć w rozdziale [Tworzenie rozszerzenia |creating-extension]. +Wywoływane przed renderowaniem szablonu. Wartość zwracana staje się częścią klucza, którego hash jest zawarty w nazwie pliku skompilowanego szablonu. Dla różnych wartości zwracanych Latte wygeneruje więc różne pliki cache. diff --git a/latte/pl/filters.texy b/latte/pl/filters.texy index f4375c91d3..eb1827d5a8 100644 --- a/latte/pl/filters.texy +++ b/latte/pl/filters.texy @@ -1,112 +1,114 @@ -Filtry do latte -*************** +Filtry Latte +************ .[perex] -Możemy używać funkcji w szablonach, aby pomóc w edycji lub przeformatowaniu danych do ostatecznej postaci. Nazywamy je *filtrami*. +W szablonach możemy używać funkcji, które pomagają modyfikować lub przeformatować dane do ostatecznej postaci. Nazywamy je *filtrami*. .[table-latte-filters] -|## Transformacja -| `batch` | [wyodrębnić dane liniowe do tabeli |#batch] -| `breakLines` | [Dodaj przerwę w linii HTML przed końcem linii |#breakLines] -| `bytes` | [rozmiar formatu w bajtach |#bytes] -| `clamp` | [ogranicza wartość do podanego zakresu |#clamp] +|## Transformacje +| `batch` | [wyświetlanie danych liniowych w tabeli |#batch] +| `breakLines` | [Dodaje podziały wierszy HTML przed końcami linii |#breakLines] +| `bytes` | [formatuje rozmiar w bajtach |#bytes] +| `clamp` | [ogranicza wartość do danego zakresu |#clamp] | `dataStream` | [konwersja dla protokołu Data URI |#dataStream] -| `date` | [formatuje datę |#date] -| `explode` | [dzieli łańcuch na pola zgodnie z delimiterem |#explode] -| `first` | [zwraca pierwszy element tablicy lub znak łańcucha|#first] -| `implode` | [łączy pola w ciąg znaków|#implode] -| `indent` | [przesuwa tekst od lewej strony o podaną liczbę tabulatorów |#indent] -| `join` | [konkatenuje tablicę w ciąg znaków|#implode] -| `last` | [zwraca ostatni element tablicy lub znak łańcucha|#last] -| `length` | [zwraca długość łańcucha w znakach lub tablicy |#length] -| `number` | [formatuje liczbę |#number] -| `padLeft` | [uzupełnia ciąg od lewej do pożądanej długości |#padLeft] -| `padRight` | [uzupełnia ciąg od prawej do pożądanej długości |#padRight] -| `random` | [zwraca losowy element tablicy lub znak łańcuchowy|#random] -| `repeat` | [powtórzyć ciąg |#repeat] -| `replace` | [Zamień wystąpienia szukanego ciągu znaków|#replace] -| `replaceRE` | [zamienia wystąpienia zgodnie z wyrażeniem regularnym |#replaceRE] -| `reverse` | [odwrócony ciąg lub tablica UTF-8 |#reverse] -| `slice` | [wyciąga część tablicy lub łańcucha|#slice] -| `sort` | [sortuje tablicę |#sort] -| `spaceless` | [usuwa białą |#spaceless] przestrzeń, podobnie jak znacznik [bez spacji |tags] -| `split` | [dzieli łańcuch na tablice według separatora |#explode] -| `strip` | [usuwa białe spacje |#spaceless] -| `stripHtml` | [usuwa znaczniki HTML i zamienia elementy HTML na znaki |#stripHtml] -| `substr` | [zwraca część łańcucha|#substr] -| `trim` | [usuwa wiodące i końcowe spacje lub inne znaki |#trim] -| `translate` | [tłumaczenie na inne języki |#translate] -| `truncate` | [skraca długość, zachowując słowa |#truncate] -| `webalize` | [modyfikuje ciąg UTF-8 do formatu używanego w URL |#webalize] +| `date` | [formatuje datę i czas |#date] +| `explode` | [dzieli ciąg znaków na tablicę według separatora |#explode] +| `first` | [zwraca pierwszy element tablicy lub znak ciągu |#first] +| `group` | [grupuje dane według różnych kryteriów |#group] +| `implode` | [łączy tablicę w ciąg znaków |#implode] +| `indent` | [wcięcie tekstu od lewej o podaną liczbę tabulatorów |#indent] +| `join` | [łączy tablicę w ciąg znaków |#implode] +| `last` | [zwraca ostatni element tablicy lub znak ciągu |#last] +| `length` | [zwraca długość ciągu w znakach lub tablicy |#length] +| `localDate` | [formatuje datę i czas zgodnie z ustawieniami regionalnymi |#localDate] +| `number` | [formatuje liczbę |#number] +| `padLeft` | [uzupełnia ciąg znaków od lewej do wymaganej długości |#padLeft] +| `padRight` | [uzupełnia ciąg znaków od prawej do wymaganej długości |#padRight] +| `random` | [zwraca losowy element tablicy lub znak ciągu |#random] +| `repeat` | [powtarzanie ciągu znaków |#repeat] +| `replace` | [zastępuje wystąpienia szukanego ciągu |#replace] +| `replaceRE` | [zastępuje wystąpienia zgodnie z wyrażeniem regularnym |#replaceRE] +| `reverse` | [odwraca ciąg UTF-8 lub tablicę |#reverse] +| `slice` | [wyodrębnia część tablicy lub ciągu |#slice] +| `sort` | [sortuje tablicę |#sort] +| `spaceless` | [usuwa białe znaki |#spaceless], podobnie jak znacznik [spaceless |tags] +| `split` | [dzieli ciąg znaków na tablicę według separatora |#explode] +| `strip` | [usuwa białe znaki |#spaceless] +| `stripHtml` | [usuwa znaczniki HTML i konwertuje encje HTML na znaki |#stripHtml] +| `substr` | [zwraca część ciągu |#substr] +| `trim` | [usuwa początkowe i końcowe spacje lub inne znaki |#trim] +| `translate` | [tłumaczenie na inne języki |#translate] +| `truncate` | [skraca długość z zachowaniem słów |#truncate] +| `webalize` | [dostosowuje ciąg UTF-8 do formatu używanego w adresach URL |#webalize] .[table-latte-filters] -|Każda sprawa -| `capitalize` | [mała litera, pierwsza litera w słowach wielka litera |#capitalize] -| `firstUpper` | [zamień pierwszą literę na wielką |#firstUpper] -| `lower` | [konwersja na małe litery |#lower] -| `upper` | [konwersja do wielkich liter |#upper] +|## Wielkość liter +| `capitalize` | [małe litery, pierwsza litera w słowach wielka |#capitalize] +| `firstUpper` | [konwertuje pierwszą literę na wielką |#firstUpper] +| `lower` | [konwertuje na małe litery |#lower] +| `upper` | [konwertuje na wielkie litery |#upper] .[table-latte-filters] -|## Zaokrąglenie -| `ceil` | [zaokrągla liczbę do podanej precyzji |#ceil] -| `floor` | [zaokrągla liczbę w dół do podanej precyzji |#floor] -| `round` | [zaokrągla liczbę do podanej precyzji |#round] +|## Zaokrąglanie +| `ceil` | [zaokrągla liczbę w górę do podanej precyzji |#ceil] +| `floor` | [zaokrągla liczbę w dół do podanej precyzji |#floor] +| `round` | [zaokrągla liczbę do podanej precyzji |#round] .[table-latte-filters] -|## Ucieczka -| `escapeUrl` | [ucieka od parametru w URL |#escapeUrl] -| `noescape` | [wypisuje zmienną bez ucieczki |#noescape] -| `query` | [generuje ciąg zapytania w URL |#query] +|## Escapowanie +| `escapeUrl` | [escapuje parametr w URL |#escapeUrl] +| `noescape` | [wyświetla zmienną bez escapowania |#noescape] +| `query` | [generuje query string w URL |#query] -Istnieją również filtry ucieczki dla HTML (`escapeHtml` i `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) i iCalendar (`escapeICal`), które Latte używa samodzielnie dzięki [ucieczce kontekstowej |safety-first#Context-Aware-Escaping] i nie musisz ich pisać. +Ponadto istnieją filtry escapujące dla HTML (`escapeHtml` i `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) i iCalendar (`escapeICal`), których Latte używa samo dzięki [escapowaniu kontekstowemu |safety-first#Escapowanie kontekstowe] i nie trzeba ich zapisywać. .[table-latte-filters] |## Bezpieczeństwo -| `checkUrl` | [traktuje URL z niebezpiecznych wejść |#checkUrl] -| `nocheck` | [zapobiega automatycznemu przetwarzaniu adresów URL |#nocheck] +| `checkUrl` | [oczyszcza adres URL z niebezpiecznych danych wejściowych |#checkUrl] +| `nocheck` | [zapobiega automatycznemu czyszczeniu adresu URL |#nocheck] -Latte [sprawdza automatycznie |safety-first#link-checking] atrybuty `src` i `href`, więc prawie nie trzeba używać filtra `checkUrl`. +Latte [sprawdza automatycznie |safety-first#Sprawdzanie linków] atrybuty `src` i `href`, więc filtra `checkUrl` prawie nie trzeba używać. .[note] -Wszystkie domyślne filtry są dla ciągów zakodowanych w UTF-8. +Wszystkie domyślne filtry są przeznaczone dla ciągów znaków w kodowaniu UTF‑8. -Użycie .[#toc-usage] -==================== +Użycie +====== -Filtry zapisywane są po pionowym pasku (mogą być poprzedzone spacją): +Filtry zapisuje się za pionową kreską (może być przed nią spacja): ```latte

                                                                                        {$heading|upper}

                                                                                        ``` -Filtry (w starszych wersjach pomocnicy) mogą być konkatenowane, a następnie stosowane w kolejności od lewej do prawej: +Filtry (w starszych wersjach helpery) można łączyć w łańcuchy, a następnie są stosowane w kolejności od lewej do prawej: ```latte

                                                                                        {$heading|lower|capitalize}

                                                                                        ``` -Parametry wprowadzane są po nazwie filtra, oddzielone dwukropkami lub przecinkami: +Parametry podaje się za nazwą filtra, oddzielone dwukropkami lub przecinkami: ```latte

                                                                                        {$heading|truncate:20,''}

                                                                                        ``` -Filtry mogą być również stosowane do wyrażenia: +Filtry można stosować również do wyrażeń: ```latte {var $name = ($title|upper) . ($subtitle|lower)} ``` -[Filtry niestandardowe |extending-latte#Filters] można zarejestrować w następujący sposób: +[Filtry niestandardowe|custom-filters] można rejestrować w ten sposób: ```php $latte = new Latte\Engine; $latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -W szablonie jest on wówczas nazywany w następujący sposób: +W szablonie wywołuje się je w następujący sposób: ```latte

                                                                                        {$text|shortify}

                                                                                        @@ -114,13 +116,13 @@ W szablonie jest on wówczas nazywany w następujący sposób: ``` -Filtry .[#toc-filters] -====================== +Filtry +====== -batch(int length, mixed item): array .[filter]{data-version:2.7} ----------------------------------------------------------------- -Filtr, który upraszcza wyprowadzenie danych liniowych do tabeli. Zwraca tablicę pól o określonej liczbie elementów. Jeśli określisz drugi parametr, jest on używany do wypełnienia brakujących elementów w ostatnim wierszu. +batch(int $length, mixed $item): array .[filter] +------------------------------------------------ +Filtr, który upraszcza wyświetlanie danych liniowych w postaci tabeli. Zwraca tablicę tablic o podanej liczbie elementów. Jeśli podasz drugi parametr, zostanie on użyty do uzupełnienia brakujących elementów w ostatnim wierszu. ```latte {var $items = ['a', 'b', 'c', 'd', 'e']} @@ -135,7 +137,7 @@ Filtr, który upraszcza wyprowadzenie danych liniowych do tabeli. Zwraca tablic
                                                                                        Wewnętrzna pętla
                                                                                        ``` -Wydruki: +Wyświetli: ```latte @@ -152,74 +154,76 @@ Wydruki:
                                                                                        ``` +Zobacz także [#group] i znacznik [iterateWhile |tags#iterateWhile]. + breakLines .[filter] -------------------- -Wstawia podziały linii HTML przed wszystkimi liniami nowymi. +Dodaje przed każdym znakiem nowego wiersza znacznik HTML `
                                                                                        `. ```latte {var $s = "Text & with \n newline"} -{$s|breakLines} {* vypíše "Text & with
                                                                                        \n newline" *} +{$s|breakLines} {* wyświetla "Text & with
                                                                                        \n newline" *} ``` -bytes(int precision = 2) .[filter] ----------------------------------- -Formatuje rozmiar w bajtach do postaci czytelnej dla człowieka. +bytes(int $precision=2) .[filter] +--------------------------------- +Formatuje rozmiar w bajtach do postaci czytelnej dla człowieka. Jeśli ustawione są [ustawienia regionalne |develop#Locale], używane są odpowiednie separatory dziesiętne i tysięcy. ```latte -{$size|bytes} 0 B, 1.25 GB, … -{$size|bytes:0} 10 B, 1 GB, … +{$size|bytes} {* 0 B, 1.25 GB, … *} +{$size|bytes:0} {* 10 B, 1 GB, … *} ``` -ceil(int precision = 0) .[filter] ---------------------------------- -Zaokrągla liczbę do określonej precyzji. +ceil(int $precision=0) .[filter] +-------------------------------- +Zaokrągla liczbę w górę do podanej precyzji. ```latte -{=3.4|ceil} {* outputs 4 *} -{=135.22|ceil:1} {* outputs 135.3 *} -{=135.22|ceil:3} {* outputs 135.22 *} +{=3.4|ceil} {* wyświetla 4 *} +{=135.22|ceil:1} {* wyświetla 135.3 *} +{=135.22|ceil:3} {* wyświetla 135.22 *} ``` -Patrz także [podłoga |#floor], [okrągła |#round]. +Zobacz także [#floor], [#round]. capitalize .[filter] -------------------- -Zwraca tytułową wersję wartości. Słowa będą zaczynać się od dużych liter, wszystkie pozostałe znaki są małe. Wymaga rozszerzenia PHP `mbstring`. +Słowa będą zaczynać się wielkimi literami, wszystkie pozostałe znaki będą małe. Wymaga rozszerzenia PHP `mbstring`. ```latte -{='i like LATTE'|capitalize} {* outputs 'I Like Latte' *} +{='i like LATTE'|capitalize} {* wyświetla 'I Like Latte' *} ``` -Zobacz także [firstUpper |#firstUpper], [lower |#lower], [upper |#upper]. +Zobacz także [#firstUpper], [#lower], [#upper]. checkUrl .[filter] ------------------ -Wymusza sanityzację adresów URL. Sprawdza czy zmienna zawiera adres URL strony internetowej (tj. protokół HTTP/HTTPS) i zapobiega zapisywaniu linków, które mogą stanowić zagrożenie bezpieczeństwa. +Wymusza oczyszczenie adresu URL. Sprawdza, czy zmienna zawiera adres URL (tj. protokół HTTP/HTTPS) i zapobiega wyświetlaniu linków, które mogą stanowić zagrożenie bezpieczeństwa. ```latte {var $link = 'javascript:window.close()'} -checked -unchecked +kontrolowane +niekontrolowane ``` -Wydruki: +Wyświetli: ```latte -checked -unchecked +kontrolowane +niekontrolowane ``` -Zobacz również [nocheck |#nocheck]. +Zobacz także [#nocheck]. -clamp(int|float min, int|float max) .[filter]{data-version:2.9} ---------------------------------------------------------------- -Zwraca wartość zaciśniętą na inkluzywnym zakresie min i max. +clamp(int|float $min, int|float $max) .[filter] +----------------------------------------------- +Ogranicza wartość do podanego zakresu włącznie min i max. ```latte {$level|clamp: 0, 255} @@ -228,17 +232,17 @@ Zwraca wartość zaciśniętą na inkluzywnym zakresie min i max. Istnieje również jako [funkcja |functions#clamp]. -dataStream(string mimetype = detect) .[filter] ----------------------------------------------- -Konwertuje zawartość na schemat URI danych. Może być używany do wstawiania obrazów do HTML lub CSS bez potrzeby łączenia plików zewnętrznych. +dataStream(string $mimetype=detect) .[filter] +--------------------------------------------- +Konwertuje zawartość do schematu data URI. Za jego pomocą można wstawiać obrazy do HTML lub CSS bez konieczności linkowania zewnętrznych plików. -Załóżmy, że mamy obrazek w zmiennej `$img = Image::fromFile('obrazek.gif')`, to +Miejmy w zmiennej obrazek `$img = Image::fromFile('obrazek.gif')`, wtedy ```latte - + ``` -Drukuje na przykład: +Wyświetli na przykład: ```latte {$name} ``` -Zobacz również [zapytanie |#query]. +Zobacz także [#query]. -explode(string separator = '') .[filter]{data-version:2.10.2} -------------------------------------------------------------- -Rozdziela łańcuch przez podany delimiter i zwraca tablicę łańcuchów. Alias dla `split`. +explode(string $separator='') .[filter] +--------------------------------------- +Dzieli ciąg znaków na tablicę według separatora. Alias dla `split`. ```latte -{='one,two,three'|explode:','} {* returns ['one', 'two', 'three'] *} +{='one,two,three'|explode:','} {* zwraca ['one', 'two', 'three'] *} ``` -Jeśli delimiterem jest pusty łańcuch (wartość domyślna), to wejście zostanie podzielone na pojedyncze znaki: +Jeśli separator jest pustym ciągiem (wartość domyślna), wejście zostanie podzielone na poszczególne znaki: ```latte -{='123'|explode} {* returns ['1', '2', '3'] *} +{='123'|explode} {* zwraca ['1', '2', '3'] *} ``` -Możesz użyć także aliasu `split`: +Możesz także użyć aliasu `split`: ```latte -{='1,2,3'|split:','} {* returns ['1', '2', '3'] *} +{='1,2,3'|split:','} {* zwraca ['1', '2', '3'] *} ``` -Zobacz również [implode |#implode]. +Zobacz także [#implode]. -first .[filter]{data-version:2.10.2} ------------------------------------- -Zwraca pierwszy element tablicy lub znak łańcucha: +first .[filter] +--------------- +Zwraca pierwszy element tablicy lub znak ciągu: ```latte -{=[1, 2, 3, 4]|first} {* outputs 1 *} -{='abcd'|first} {* outputs 'a' *} +{=[1, 2, 3, 4]|first} {* wyświetla 1 *} +{='abcd'|first} {* wyświetla 'a' *} ``` -Zobacz także [last |#last], [random |#random]. +Zobacz także [#last], [#random]. -floor(int precision = 0) .[filter] ----------------------------------- -Zaokrągla liczbę do określonej dokładności. +floor(int $precision=0) .[filter] +--------------------------------- +Zaokrągla liczbę w dół do podanej precyzji. ```latte -{=3.5|floor} {* outputs 3 *} -{=135.79|floor:1} {* outputs 135.7 *} -{=135.79|floor:3} {* outputs 135.79 *} +{=3.5|floor} {* wyświetla 3 *} +{=135.79|floor:1} {* wyświetla 135.7 *} +{=135.79|floor:3} {* wyświetla 135.79 *} ``` -Zobacz także [ceil |#ceil], [round |#round]. +Zobacz także [#ceil], [#round]. firstUpper .[filter] -------------------- -Konwertuje pierwszą literę wartości na duże. Wymaga rozszerzenia PHP `mbstring`. +Konwertuje pierwszą literę na wielką. Wymaga rozszerzenia PHP `mbstring`. ```latte -{='the latte'|firstUpper} {* outputs 'The latte' *} +{='the latte'|firstUpper} {* wyświetla 'The latte' *} ``` -Zobacz także [capitalize |#capitalize], [lower |#lower], [upper |#upper]. +Zobacz także [#capitalize], [#lower], [#upper]. -implode(string glue = '') .[filter] ------------------------------------ -Zwraca łańcuch będący konkatenacją łańcuchów w tablicy. Alias dla `join`. +group(string|int|\Closure $by): array .[filter]{data-version:3.0.16} +-------------------------------------------------------------------- +Filtr grupuje dane według różnych kryteriów. + +W tym przykładzie wiersze w tabeli są grupowane według kolumny `categoryId`. Wyjściem jest tablica tablic, gdzie kluczem jest wartość w kolumnie `categoryId`. [Przeczytaj szczegółowy przewodnik|cookbook/grouping]. ```latte -{=[1, 2, 3]|implode} {* outputs '123' *} -{=[1, 2, 3]|implode:'|'} {* outputs '1|2|3' *} +{foreach ($items|group: categoryId) as $categoryId => $categoryItems} +
                                                                                          + {foreach $categoryItems as $item} +
                                                                                        • {$item->name}
                                                                                        • + {/foreach} +
                                                                                        +{/foreach} ``` -Możesz również użyć aliasu `join`: .{data-version:2.10.2} +Zobacz także [#batch], funkcja [group |functions#group] i znacznik [iterateWhile |tags#iterateWhile]. + + +implode(string $glue='') .[filter] +---------------------------------- +Zwraca ciąg znaków, który jest połączeniem elementów sekwencji. Alias dla `join`. ```latte -{=[1, 2, 3]|join} {* outputs '123' *} +{=[1, 2, 3]|implode} {* wyświetla '123' *} +{=[1, 2, 3]|implode:'|'} {* wyświetla '1|2|3' *} ``` +Możesz także użyć aliasu `join`: + +```latte +{=[1, 2, 3]|join} {* wyświetla '123' *} +``` -indent(int level = 1, string char = "\t") .[filter] ---------------------------------------------------- -Wcina tekst od lewej strony o określoną liczbę tabulatorów lub innych znaków, które podajemy w drugim opcjonalnym argumencie. Puste linie nie są wcięte. + +indent(int $level=1, string $char="\t") .[filter] +------------------------------------------------- +Wcięcie tekstu od lewej o podaną liczbę tabulatorów lub innych znaków, które można podać w drugim argumencie. Puste wiersze nie są wcięte. ```latte
                                                                                        @@ -358,7 +382,7 @@ Wcina tekst od lewej strony o określoną liczbę tabulatorów lub innych znakó
                                                                                        ``` -Drukuje: +Wyświetli: ```latte
                                                                                        @@ -367,26 +391,26 @@ Drukuje: ``` -last .[filter]{data-version:2.10.2} ------------------------------------ -Zwraca ostatni element tablicy lub znak łańcucha: +last .[filter] +-------------- +Zwraca ostatni element tablicy lub znak ciągu: ```latte -{=[1, 2, 3, 4]|last} {* outputs 4 *} -{='abcd'|last} {* outputs 'd' *} +{=[1, 2, 3, 4]|last} {* wyświetla 4 *} +{='abcd'|last} {* wyświetla 'd' *} ``` -Zobacz także [first |#first], [random |#random]. +Zobacz także [#first], [#random]. length .[filter] ---------------- -Zwraca długość łańcucha lub tablicy. +Zwraca długość ciągu lub tablicy. -- dla łańcuchów, zwróci długość w znakach UTF-8 -- dla tablic, zwróci liczbę elementów -- dla obiektów implementujących interfejs Countable, użyje wartości zwracanej przez count() -- dla obiektów implementujących interfejs IteratorAgregate, użyje wartości zwracanej przez iterator_count() +- dla ciągów zwraca długość w znakach UTF‑8 +- dla tablic zwraca liczbę elementów +- dla obiektów implementujących interfejs Countable, używa wartości zwracanej przez metodę count() +- dla obiektów implementujących interfejs IteratorAggregate, używa wartości zwracanej przez funkcję iterator_count() ```latte @@ -396,101 +420,211 @@ Zwraca długość łańcucha lub tablicy. ``` +localDate(?string $format=null, ?string $date=null, ?string $time=null) .[filter] +--------------------------------------------------------------------------------- +Formatuje datę i czas zgodnie z [ustawieniami regionalnymi |develop#Locale], co zapewnia spójne i zlokalizowane wyświetlanie danych czasowych w różnych językach i regionach. Filtr przyjmuje datę jako UNIX timestamp, ciąg znaków lub obiekt typu `DateTimeInterface`. + +```latte +{$date|localDate} {* 15 kwietnia 2024 *} +{$date|localDate: format: yM} {* 4/2024 *} +{$date|localDate: date: medium} {* 15.04.2024 *} +``` + +Jeśli użyjesz filtra bez parametrów, data zostanie wyświetlona na poziomie `long`, zobacz dalej. + +**a) użycie formatu** + +Parametr `format` opisuje, które składniki czasu mają być wyświetlone. Używa do tego kodów literowych, których liczba powtórzeń wpływa na szerokość wyjścia: + +| rok | `y` / `yy` / `yyyy` | `2024` / `24` / `2024` +| miesiąc | `M` / `MM` / `MMM` / `MMMM` | `8` / `08` / `sie` / `sierpień` +| dzień | `d` / `dd` / `E` / `EEEE` | `1` / `01` / `niedz.` / `niedziela` +| godzina | `j` / `H` / `h` | preferowany / 24-godzinny / 12-godzinny +| minuta | `m` / `mm` | `5` / `05` (2 cyfry w połączeniu z sekundami) +| sekunda | `s` / `ss` | `8` / `08` (2 cyfry w połączeniu z minutami) + +Kolejność kodów w formacie nie ma znaczenia, ponieważ kolejność składników jest wyświetlana zgodnie ze zwyczajami ustawień regionalnych. Format jest więc od nich niezależny. Na przykład format `yyyyMMMMd` w środowisku `en_US` wyświetli `April 15, 2024`, podczas gdy w środowisku `pl_PL` wyświetli `15 kwietnia 2024`: + +| locale: | pl_PL | en_US +|--- +| `format: 'dMy'` | 10.08.2024 | 8/10/2024 +| `format: 'yM'` | 8/2024 | 8/2024 +| `format: 'yyyyMMMM'` | sierpień 2024 | August 2024 +| `format: 'MMMM'` | sierpień | August +| `format: 'jm'` | 17:22 | 5:22 PM +| `format: 'Hm'` | 17:22 | 17:22 +| `format: 'hm'` | 5:22 PM | 5:22 PM + + +**b) użycie predefiniowanych stylów** + +Parametry `date` i `time` określają, jak szczegółowo ma być wyświetlana data i czas. Możesz wybrać spośród kilku poziomów: `full`, `long`, `medium`, `short`. Można wyświetlić tylko datę, tylko czas lub oba: + +| locale: | pl_PL | en_US +|--- +| `date: short` | 23.01.78 | 1/23/78 +| `date: medium` | 23.01.1978 | Jan 23, 1978 +| `date: long` | 23 stycznia 1978 | January 23, 1978 +| `date: full` | poniedziałek, 23 stycznia 1978 | Monday, January 23, 1978 +| `time: short` | 08:30 | 8:30 AM +| `time: medium` | 08:30:59 | 8:30:59 AM +| `time: long` | 08:30:59 CET | 8:30:59 AM GMT+1 +| `date: short, time: short` | 23.01.78 08:30 | 1/23/78, 8:30 AM +| `date: medium, time: short` | 23.01.1978 08:30 | Jan 23, 1978, 8:30 AM +| `date: long, time: short` | 23 stycznia 1978 o 08:30 | January 23, 1978 at 8:30 AM + +Dla daty można dodatkowo użyć prefiksu `relative-` (np. `relative-short`), który dla dat bliskich teraźniejszości wyświetli `wczoraj`, `dzisiaj` lub `jutro`, w przeciwnym razie zostanie wyświetlony w standardowy sposób. + +```latte +{$date|localDate: date: relative-short} {* wczoraj *} +``` + +Zobacz także [#date]. + + lower .[filter] --------------- -Konwertuje wartość na małe litery. Wymaga rozszerzenia PHP `mbstring`. +Konwertuje ciąg znaków na małe litery. Wymaga rozszerzenia PHP `mbstring`. ```latte -{='LATTE'|lower} {* outputs 'latte' *} +{='LATTE'|lower} {* wyświetla 'latte' *} ``` -Zobacz także [capitalize |#capitalize], [firstUpper |#firstUpper], [upper |#upper]. +Zobacz także [#capitalize], [#firstUpper], [#upper]. nocheck .[filter] ----------------- -Zapobiega automatycznej sanityzacji adresów URL. Latte [automatycznie sprawdza |safety-first#Link checking], czy zmienna zawiera adres URL strony internetowej (tj. protokół HTTP/HTTPS) i zapobiega zapisywaniu linków, które mogą stanowić zagrożenie dla bezpieczeństwa. +Zapobiega automatycznemu oczyszczaniu adresu URL. Latte [automatycznie sprawdza |safety-first#Sprawdzanie linków], czy zmienna zawiera adres URL (tj. protokół HTTP/HTTPS) i zapobiega wyświetlaniu linków, które mogą stanowić zagrożenie bezpieczeństwa. -Jeśli link używa innego schematu, takiego jak `javascript:` lub `data:`, i jesteś pewien jego zawartości, możesz wyłączyć sprawdzanie poprzez `|nocheck`. +Jeśli link używa innego schematu, np. `javascript:` lub `data:`, i jesteś pewien jego zawartości, możesz wyłączyć kontrolę za pomocą `|nocheck`. ```latte {var $link = 'javascript:window.close()'} -checked -unchecked +kontrolowane +niekontrolowane ``` -Wydruki: +Wyświetli: ```latte -checked -unchecked +kontrolowane +niekontrolowane ``` -Zobacz także [checkUrl |#checkUrl]. +Zobacz także [#checkUrl]. noescape .[filter] ------------------ -Wyłącza automatyczne uciekanie. +Wyłącza automatyczne escapowanie. ```latte {var $trustedHtmlString = 'hello'} -Escaped: {$trustedHtmlString} -Unescaped: {$trustedHtmlString|noescape} +Escapowany: {$trustedHtmlString} +Neescapowany: {$trustedHtmlString|noescape} ``` -Drukuje: +Wyświetli: ```latte -Escaped: <b>hello</b> -Unescaped: hello +Escapowany: <b>hello</b> +Neescapowany: hello ``` .[warning] -Niewłaściwe użycie filtra `noescape` może prowadzić do luki XSS! Nigdy nie używaj go, jeśli nie jesteś **absolutnie pewien** tego, co robisz i że drukowany ciąg pochodzi z zaufanego źródła. +Nieprawidłowe użycie filtra `noescape` może prowadzić do powstania podatności XSS! Nigdy go nie używaj, jeśli nie jesteś **całkowicie pewien**, co robisz, i że wyświetlany ciąg pochodzi z zaufanego źródła. -number(int decimals = 0, string decPoint = '.', string thousandsSep = ',') .[filter] ------------------------------------------------------------------------------------- -Formatuje liczbę do podanej liczby miejsc dziesiętnych. Można również określić znak kropki dziesiętnej i separatora tysięcy. +number(int $decimals=0, string $decPoint='.', string $thousandsSep=',') .[filter] +--------------------------------------------------------------------------------- +Formatuje liczbę do określonej liczby miejsc dziesiętnych. Jeśli ustawione są [ustawienia regionalne |develop#Locale], używane są odpowiednie separatory dziesiętne i tysięcy. ```latte -{1234.20 |number} 1,234 -{1234.20 |number:1} 1,234.2 -{1234.20 |number:2} 1,234.20 -{1234.20 |number:2, ',', ' '} 1 234,20 +{1234.20|number} {* 1,234 *} +{1234.20|number:1} {* 1,234.2 *} +{1234.20|number:2} {* 1,234.20 *} +{1234.20|number:2, ',', ' '} {* 1 234,20 *} ``` -padLeft(int length, string pad = ' ') .[filter] +number(string $format) .[filter] +-------------------------------- +Parametr `format` pozwala zdefiniować wygląd liczb dokładnie według Twoich potrzeb. Do tego potrzebne jest ustawienie [ustawień regionalnych |develop#Locale]. Format składa się z kilku specjalnych znaków, których pełny opis znajdziesz w dokumentacji "DecimalFormat":https://unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns: + +- `0` obowiązkowa cyfra, zawsze się wyświetli, nawet jeśli to zero +- `#` opcjonalna cyfra, wyświetli się tylko wtedy, gdy na tym miejscu liczba rzeczywiście jest +- `@` cyfra znacząca, pomaga wyświetlić liczbę z określoną liczbą cyfr znaczących +- `.` oznacza, gdzie ma być przecinek dziesiętny (lub kropka, w zależności od kraju) +- `,` służy do oddzielania grup cyfr, najczęściej tysięcy +- `%` mnoży liczbę przez 100× i dodaje znak procenta + +Spójrzmy na przykłady. W pierwszym przykładzie dwa miejsca dziesiętne są obowiązkowe, w drugim opcjonalne. Trzeci przykład pokazuje uzupełnienie zerami z lewej i prawej strony, czwarty wyświetla tylko istniejące cyfry: + +```latte +{1234.5|number: '#,##0.00'} {* 1,234.50 *} +{1234.5|number: '#,##0.##'} {* 1,234.5 *} +{1.23 |number: '000.000'} {* 001.230 *} +{1.2 |number: '##.##'} {* 1.2 *} +``` + +Cyfry znaczące określają, ile cyfr, niezależnie od przecinka dziesiętnego, ma być wyświetlonych, przy czym następuje zaokrąglenie: + +```latte +{1234|number: '@@'} {* 1200 *} +{1234|number: '@@@'} {* 1230 *} +{1234|number: '@@@#'} {* 1234 *} +{1.2345|number: '@@@'} {* 1.23 *} +{0.00123|number: '@@'} {* 0.0012 *} +``` + +Łatwy sposób na wyświetlenie liczby jako procentów. Liczba jest mnożona przez 100× i dodawany jest znak `%`: + +```latte +{0.1234|number: '#.##%'} {* 12.34% *} +``` + +Możemy zdefiniować odrębny format dla liczb dodatnich i ujemnych, oddziela je znak `;`. W ten sposób można na przykład ustawić, że liczby dodatnie mają być wyświetlane ze znakiem `+`: + +```latte +{42|number: '#.##;(#.##)'} {* 42 *} +{-42|number: '#.##;(#.##)'} {* (42) *} +{42|number: '+#.##;-#.##'} {* +42 *} +{-42|number: '+#.##;-#.##'} {* -42 *} +``` + +Pamiętaj, że rzeczywisty wygląd liczb może się różnić w zależności od ustawień kraju. Na przykład w niektórych krajach używa się przecinka zamiast kropki jako separatora miejsc dziesiętnych. Ten filtr automatycznie to uwzględni i nie musisz się o nic martwić. + + +padLeft(int $length, string $pad=' ') .[filter] ----------------------------------------------- -Przekłada łańcuch o określonej długości z innym łańcuchem od lewej. +Uzupełnia ciąg znaków do określonej długości innym ciągiem z lewej strony. ```latte -{='hello'|padLeft: 10, '123'} {* outputs '12312hello' *} +{='hello'|padLeft: 10, '123'} {* wyświetla '12312hello' *} ``` -padRight(int length, string pad = ' ') .[filter] +padRight(int $length, string $pad=' ') .[filter] ------------------------------------------------ -Wyrównuje ciąg do pewnej długości z innym ciągiem od prawej. +Uzupełnia ciąg znaków do określonej długości innym ciągiem z prawej strony. ```latte -{='hello'|padRight: 10, '123'} {* outputs 'hello12312' *} +{='hello'|padRight: 10, '123'} {* wyświetla 'hello12312' *} ``` -query .[filter]{data-version:2.10} ------------------------------------ -Dynamicznie generuje ciąg zapytania w adresie URL: +query .[filter] +--------------- +Dynamicznie generuje query string w URL: ```latte click search ``` -Drukuje: +Wyświetli: ```latte click @@ -499,101 +633,101 @@ Drukuje: Klucze o wartości `null` są pomijane. -Zobacz również [escapeUrl |#escapeUrl]. +Zobacz także [#escapeUrl]. -random .[filter]{data-version:2.10.2} -------------------------------------- -Zwraca losowy element tablicy lub znak łańcucha: +random .[filter] +---------------- +Zwraca losowy element tablicy lub znak ciągu: ```latte -{=[1, 2, 3, 4]|random} {* example output: 3 *} -{='abcd'|random} {* example output: 'b' *} +{=[1, 2, 3, 4]|random} {* wyświetla np.: 3 *} +{='abcd'|random} {* wyświetla np.: 'b' *} ``` -Zobacz również [first |#first], [last |#last]. +Zobacz także [#first], [#last]. -repeat(int count) .[filter] ---------------------------- -Powtarza łańcuch x razy. +repeat(int $count) .[filter] +---------------------------- +Powtarza ciąg znaków x razy. ```latte -{='hello'|repeat: 3} {* outputs 'hellohellohello' *} +{='hello'|repeat: 3} {* wyświetla 'hellohellohello' *} ``` -replace(string|array search, string replace = '') .[filter] +replace(string|array $search, string $replace='') .[filter] ----------------------------------------------------------- -Zastępuje wszystkie wystąpienia szukanego łańcucha łańcuchem zastępczym. +Zastępuje wszystkie wystąpienia szukanego ciągu ciągiem zastępującym. ```latte -{='hello world'|replace: 'world', 'friend'} {* outputs 'hello friend' *} +{='hello world'|replace: 'world', 'friend'} {* wyświetla 'hello friend' *} ``` -Można dokonać wielu zamian jednocześnie: .{data-version:2.10.2} +Można wykonać wiele zamian jednocześnie: ```latte -{='hello world'|replace: [h => l, l => h]} {* outputs 'lehho worhd' *} +{='hello world'|replace: [h => l, l => h]} {* wyświetla 'lehho worhd' *} ``` -replaceRE(string pattern, string replace = '') .[filter] +replaceRE(string $pattern, string $replace='') .[filter] -------------------------------------------------------- -Zastępuje wszystkie wystąpienia zgodnie z wyrażeniem regularnym. +Wykonuje wyszukiwanie wyrażeń regularnych z zastępowaniem. ```latte -{='hello world'|replaceRE: '/l.*/', 'l'} {* outputs 'hel' *} +{='hello world'|replaceRE: '/l.*/', 'l'} {* wyświetla 'hel' *} ``` reverse .[filter] ----------------- -Odwraca podany łańcuch lub tablicę. +Odwraca podany ciąg znaków lub tablicę. ```latte {var $s = 'Nette'} -{$s|reverse} {* outputs 'etteN' *} +{$s|reverse} {* wyświetla 'etteN' *} {var $a = ['N', 'e', 't', 't', 'e']} -{$a|reverse} {* returns ['e', 't', 't', 'e', 'N'] *} +{$a|reverse} {* zwraca ['e', 't', 't', 'e', 'N'] *} ``` -round(int precision = 0) .[filter] ----------------------------------- -Zaokrągla liczbę do określonej precyzji. +round(int $precision=0) .[filter] +--------------------------------- +Zaokrągla liczbę do podanej precyzji. ```latte -{=3.4|round} {* outputs 3 *} -{=3.5|round} {* outputs 4 *} -{=135.79|round:1} {* outputs 135.8 *} -{=135.79|round:3} {* outputs 135.79 *} +{=3.4|round} {* wyświetla 3 *} +{=3.5|round} {* wyświetla 4 *} +{=135.79|round:1} {* wyświetla 135.8 *} +{=135.79|round:3} {* wyświetla 135.79 *} ``` -Zobacz także [ceil |#ceil], [floor |#floor]. +Zobacz także [#ceil], [#floor]. -slice(int start, int length = null, bool preserveKeys = false) .[filter]{data-version:2.10.2} ---------------------------------------------------------------------------------------------- -Wyodrębnia fragment tablicy lub ciągu znaków. +slice(int $start, ?int $length=null, bool $preserveKeys=false) .[filter] +------------------------------------------------------------------------ +Wyodrębnia część tablicy lub ciągu. ```latte -{='hello'|slice: 1, 2} {* outputs 'el' *} -{=['a', 'b', 'c']|slice: 1, 2} {* outputs ['b', 'c'] *} +{='hello'|slice: 1, 2} {* wyświetla 'el' *} +{=['a', 'b', 'c']|slice: 1, 2} {* wyświetla ['b', 'c'] *} ``` -Filtr plasterkowy działa jak funkcja `array_slice` PHP dla tablic i `mb_substr` dla łańcuchów z możliwością powrotu do `iconv_substr` w trybie UTF-8. +Filtr działa jak funkcja PHP `array_slice` dla tablic lub `mb_substr` dla ciągów z fallbackiem na funkcję `iconv_substr` w trybie UTF‑8. -Jeśli początek jest nieujemny, to sekwencja rozpocznie się od tego początku w zmiennej. Jeśli start jest ujemny, sekwencja rozpocznie się tak daleko od końca zmiennej. +Jeśli start jest dodatni, sekwencja zacznie się przesunięta o tę liczbę od początku tablicy/ciągu. Jeśli jest ujemny, sekwencja zacznie się przesunięta o tyle od końca. -Jeśli podana jest długość i jest ona dodatnia, to sekwencja będzie miała do tylu elementów. Jeśli zmienna jest krótsza niż długość, to w sekwencji znajdą się tylko dostępne elementy zmiennej. Jeśli podana jest długość i jest ona ujemna, to sekwencja zatrzyma się o tyle elementów od końca zmiennej. Jeśli jest pominięta, to sekwencja będzie miała wszystko od offsetu aż do końca zmiennej. +Jeśli podany jest parametr length i jest dodatni, sekwencja będzie zawierać tyle elementów. Jeśli do tej funkcji zostanie przekazany ujemny parametr length, sekwencja będzie zawierać wszystkie elementy oryginalnej tablicy, zaczynając od pozycji start i kończąc na pozycji mniejszej o length elementów od końca tablicy. Jeśli ten parametr nie zostanie podany, sekwencja będzie zawierać wszystkie elementy oryginalnej tablicy, zaczynając od pozycji start. -Filtr domyślnie zmieni kolejność i wyzeruje klucze tablicy liczb całkowitych. To zachowanie można zmienić ustawiając preserveKeys na true. Klucze łańcuchowe są zawsze zachowywane, niezależnie od tego parametru. +Domyślnie filtr zmienia kolejność i resetuje klucze całkowite tablicy. To zachowanie można zmienić, ustawiając preserveKeys na true. Klucze ciągów są zawsze zachowywane, niezależnie od tego parametru. -sort .[filter]{data-version:2.9} ---------------------------------- -Filtr, który sortuje tablicę i zachowuje asocjację indeksów. +sort(?Closure $comparison, string|int|\Closure|null $by=null, string|int|\Closure|bool $byKey=false) .[filter] +-------------------------------------------------------------------------------------------------------------- +Filtr sortuje elementy tablicy lub iteratora i zachowuje ich klucze asocjacyjne. Przy ustawionych [ustawieniach regionalnych |develop#Locale] sortowanie odbywa się zgodnie z ich zasadami, chyba że określono własną funkcję porównującą. ```latte {foreach ($names|sort) as $name} @@ -601,7 +735,7 @@ Filtr, który sortuje tablicę i zachowuje asocjację indeksów. {/foreach} ``` -Tablica posortowana w odwrotnej kolejności. +Sortowana tablica w odwrotnej kolejności: ```latte {foreach ($names|sort|reverse) as $name} @@ -609,16 +743,42 @@ Tablica posortowana w odwrotnej kolejności. {/foreach} ``` -Możesz przekazać własną funkcję porównania jako parametr: .{data-version:2.10.2} +Możesz określić własną funkcję porównującą do sortowania (przykład pokazuje, jak odwrócić sortowanie od największego do najmniejszego): ```latte -{var $sorted = ($names|sort: fn($a, $b) => $b <=> $a)} +{var $reverted = ($names|sort: fn($a, $b) => $b <=> $a)} ``` +Filtr `|sort` pozwala również sortować elementy według kluczy: -spaceless .[filter]{data-version:2.10.2} ------------------------------------------ -Usuwa niepotrzebne białe przestrzenie z wyjścia. Możesz również użyć aliasu `strip`. +```latte +{foreach ($names|sort: byKey: true) as $name} + ... +{/foreach} +``` + +Jeśli potrzebujesz posortować tabelę według określonej kolumny, możesz użyć parametru `by`. Wartość `'name'` w przykładzie określa, że sortowanie będzie odbywać się według `$item->name` lub `$item['name']`, w zależności od tego, czy `$item` jest tablicą czy obiektem: + +```latte +{foreach ($items|sort: by: 'name') as $item} + {$item->name} +{/foreach} +``` + +Możesz również zdefiniować funkcję callback, która określi wartość, według której ma się odbywać sortowanie: + +```latte +{foreach ($items|sort: by: fn($items) => $items->category->name) as $item} + {$item->name} +{/foreach} +``` + +W ten sam sposób można wykorzystać parametr `byKey`. + + +spaceless .[filter] +------------------- +Usuwa zbędne białe znaki (spacje) z wyjścia. Możesz także użyć aliasu `strip`. ```latte {block |spaceless} @@ -628,7 +788,7 @@ Usuwa niepotrzebne białe przestrzenie z wyjścia. Możesz również użyć alia {/block} ``` -Drukuje: +Wyświetli: ```latte
                                                                                        • Hello
                                                                                        @@ -637,47 +797,47 @@ Drukuje: stripHtml .[filter] ------------------- -Konwertuje HTML na zwykły tekst. To znaczy, usuwa znaczniki HTML i konwertuje encje HTML na tekst. +Konwertuje HTML na czysty tekst. Czyli usuwa z niego znaczniki HTML i konwertuje encje HTML na tekst. ```latte -{='

                                                                                        one < two

                                                                                        '|stripHtml} {* outputs 'one < two' *} +{='

                                                                                        one < two

                                                                                        '|stripHtml} {* wyświetla 'one < two' *} ``` -Wynikowy zwykły tekst może naturalnie zawierać znaki reprezentujące znaczniki HTML, na przykład `'<p>'|stripHtml` jest konwertowany na `

                                                                                        `. Nigdy nie wyprowadzaj wynikowego tekstu za pomocą `|noescape`, ponieważ może to prowadzić do luki w zabezpieczeniach. +Wynikowy czysty tekst może naturalnie zawierać znaki, które reprezentują znaczniki HTML, na przykład `'<p>'|stripHtml` zostanie przekonwertowane na `

                                                                                        `. W żadnym wypadku nie wyświetlaj tak powstałego tekstu z `|noescape`, ponieważ może to prowadzić do powstania luki bezpieczeństwa. -substr(int offset, int length = null) .[filter] ------------------------------------------------ -Wyodrębnia fragment łańcucha. Ten filtr został zastąpiony przez filtr [slice |#slice]. +substr(int $offset, ?int $length=null) .[filter] +------------------------------------------------ +Wyodrębnia część ciągu. Ten filtr został zastąpiony filtrem [#slice]. ```latte {$string|substr: 1, 2} ``` -translate(string message, ...args) .[filter]{data-version:3.0} --------------------------------------------------------------- -Tłumaczy wyrażenia na inne języki. Aby udostępnić ten filtr, musisz skonfigurować [translator |develop#TranslatorExtension]. Możesz również użyć [tagów do tłumaczenia |tags#Translation]. +translate(...$args) .[filter] +----------------------------- +Tłumaczy wyrażenia na inne języki. Aby filtr był dostępny, należy [ustawić translator |develop#TranslatorExtension]. Możesz także użyć [znaczników do tłumaczenia |tags#Tłumaczenia]. ```latte -{='Baskter'|translate} +{='Koszyk'|translate} {$item|translate} ``` -trim(string charlist = " \t\n\r\0\x0B\u{A0}") .[filter] -------------------------------------------------------- -Strip leading and trailing characters, by default whitespace. +trim(string $charlist=" \t\n\r\0\x0B\u{A0}") .[filter] +------------------------------------------------------ +Usuwa białe znaki (lub inne znaki) z początku i końca ciągu. ```latte -{=' I like Latte. '|trim} {* outputs 'I like Latte.' *} -{=' I like Latte.'|trim: '.'} {* outputs ' I like Latte' *} +{=' I like Latte. '|trim} {* wyświetla 'I like Latte.' *} +{=' I like Latte.'|trim: '.'} {* wyświetla ' I like Latte' *} ``` -truncate(int length, string append = '…') .[filter] +truncate(int $length, string $append='…') .[filter] --------------------------------------------------- -Skraca łańcuch do maksymalnej podanej długości, ale stara się zachować całe słowa. Jeśli łańcuch jest obcięty, dodaje na końcu elipsę (można to zmienić drugim parametrem). +Obcina ciąg znaków do podanej maksymalnej długości, starając się zachować całe słowa. Jeśli ciąg zostanie skrócony, na końcu dodaje wielokropek (można to zmienić drugim parametrem). ```latte {var $title = 'Hello, how are you?'} @@ -689,25 +849,25 @@ Skraca łańcuch do maksymalnej podanej długości, ale stara się zachować ca upper .[filter] --------------- -Konwertuje wartość na duże litery. Wymaga rozszerzenia PHP `mbstring`. +Konwertuje ciąg znaków na wielkie litery. Wymaga rozszerzenia PHP `mbstring`. ```latte -{='latte'|upper} {* outputs 'LATTE' *} +{='latte'|upper} {* wyświetla 'LATTE' *} ``` -Zobacz także [capitalize |#capitalize], [firstUpper |#firstUpper], [lower |#lower]. +Zobacz także [#capitalize], [#firstUpper], [#lower]. webalize .[filter] ------------------ -Konwertuje na ASCII. +Dostosowuje ciąg UTF‑8 do formatu używanego w adresach URL. -Zamienia spacje na myślniki. Usuwa znaki, które nie są alfanumeryczne, podkreślenia lub myślniki. Konwertuje na małe litery. Usuwa również wiodącą i końcową białą przestrzeń. +Konwertuje na ASCII. Konwertuje spacje na myślniki. Usuwa znaki, które nie są alfanumeryczne, podkreśleniami ani myślnikami. Konwertuje na małe litery. Usuwa również początkowe i końcowe spacje. ```latte -{var $s = 'Our 10. product'} -{$s|webalize} {* outputs 'our-10-product' *} +{var $s = 'Nasz 10. produkt'} +{$s|webalize} {* wyświetla 'nasz-10-produkt' *} ``` .[caution] -Wymaga pakietu [nette/utils |utils:]. +Wymaga biblioteki [nette/utils|utils:]. diff --git a/latte/pl/functions.texy b/latte/pl/functions.texy index 9975ece0ec..395fdc8c64 100644 --- a/latte/pl/functions.texy +++ b/latte/pl/functions.texy @@ -1,23 +1,25 @@ -Funkcja Latte +Funkcje Latte ************* .[perex] -Oprócz zwykłych funkcji PHP, w szablonach możemy używać także następujących innych funkcji. +W szablonach możemy oprócz zwykłych funkcji PHP używać również tych dodatkowych. .[table-latte-filters] -| `clamp` | [ogranicza wartość do podanego zakresu |#clamp] +| `clamp` | [ogranicza wartość do danego zakresu |#clamp] | `divisibleBy`| [sprawdza, czy zmienna jest podzielna przez liczbę |#divisibleBy] -| `even` | [sprawdza, czy podana liczba jest parzysta |#even] -| `first` | [zwraca pierwszy element tablicy lub znak łańcucha|#first] -| `last` | [zwraca ostatni element tablicy lub znak łańcucha|#last] -| `odd` | [sprawdza, czy podana liczba jest nieparzysta |#odd] -| `slice` | [wyciąga część tablicy lubłańcucha |#slice] +| `even` | [sprawdza, czy podana liczba jest parzysta |#even] +| `first` | [zwraca pierwszy element tablicy lub znak ciągu |#first] +| `group` | [grupuje dane według różnych kryteriów |#group] +| `hasBlock` | [sprawdza istnienie bloku |#hasBlock] +| `last` | [zwraca ostatni element tablicy lub znak ciągu |#last] +| `odd` | [sprawdza, czy podana liczba jest nieparzysta |#odd] +| `slice` | [wyodrębnia część tablicy lub ciągu |#slice] -Korzystanie z .[#toc-usage] -=========================== +Użycie +====== -Funkcje są używane w taki sam sposób jak regularne funkcje PHP i mogą być używane we wszystkich wyrażeniach: +Funkcje używane są tak samo jak zwykłe funkcje PHP i można ich używać we wszystkich wyrażeniach: ```latte

                                                                                        {clamp($num, 1, 100)}

                                                                                        @@ -25,14 +27,14 @@ Funkcje są używane w taki sam sposób jak regularne funkcje PHP i mogą być u {if odd($num)} ... {/if} ``` -[Funkcje niestandardowe |extending-latte#Functions] można zarejestrować w następujący sposób: +[Funkcje niestandardowe|custom-functions] można rejestrować w ten sposób: ```php $latte = new Latte\Engine; $latte->addFunction('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -W szablonie jest on wtedy nazywany w następujący sposób: +W szablonie wywołuje się je w następujący sposób: ```latte

                                                                                        {shortify($text)}

                                                                                        @@ -40,23 +42,23 @@ W szablonie jest on wtedy nazywany w następujący sposób: ``` -Funkcja .[#toc-functions] -========================= +Funkcje +======= -clamp(int|float $value, int|float $min, int|float $max): int|float .[method]{data-version:2.9} ----------------------------------------------------------------------------------------------- -Ogranicza wartość do danego zakresu włączenia min i max. +clamp(int|float $value, int|float $min, int|float $max): int|float .[method] +---------------------------------------------------------------------------- +Ogranicza wartość do podanego zakresu włącznie min i max. ```latte {=clamp($level, 0, 255)} ``` -Patrz również [zacisk filtra |filters#clamp]. +Zobacz także [filtr clamp |filters#clamp]. -divisibleBy(int $value, int $by): bool .[method]{data-version:2.10.2} ---------------------------------------------------------------------- +divisibleBy(int $value, int $by): bool .[method] +------------------------------------------------ Sprawdza, czy zmienna jest podzielna przez liczbę. ```latte @@ -64,8 +66,8 @@ Sprawdza, czy zmienna jest podzielna przez liczbę. ``` -even(int $value): bool .[method]{data-version:2.10.2} ------------------------------------------------------ +even(int $value): bool .[method] +-------------------------------- Sprawdza, czy podana liczba jest parzysta. ```latte @@ -73,32 +75,62 @@ Sprawdza, czy podana liczba jest parzysta. ``` -first(string|array $value): mixed .[method]{data-version:2.10.2} ----------------------------------------------------------------- -Zwraca pierwszy element tablicy lub znak łańcucha: +first(string|iterable $value): mixed .[method] +---------------------------------------------- +Zwraca pierwszy element tablicy lub znak ciągu: ```latte -{=first([1, 2, 3, 4])} {* drukuje 1 *} -{=first('abcd')} {* drukuje 'a' *} +{=first([1, 2, 3, 4])} {* wyświetla 1 *} +{=first('abcd')} {* wyświetla 'a' *} ``` -Zobacz także [last |#last], [filter first |filters#first]. +Zobacz także [#last], [filtr first |filters#first]. -last(string|array $value): mixed .[method]{data-version:2.10.2} ---------------------------------------------------------------- -Zwraca ostatni element tablicy lub znak łańcucha: +group(iterable $data, string|int|\Closure $by): array .[method]{data-version:3.0.16} +------------------------------------------------------------------------------------ +Funkcja grupuje dane według różnych kryteriów. + +W tym przykładzie wiersze w tabeli są grupowane według kolumny `categoryId`. Wyjściem jest tablica tablic, gdzie kluczem jest wartość w kolumnie `categoryId`. [Przeczytaj szczegółowy przewodnik|cookbook/grouping]. + +```latte +{foreach group($items, categoryId) as $categoryId => $categoryItems} +
                                                                                          + {foreach $categoryItems as $item} +
                                                                                        • {$item->name}
                                                                                        • + {/foreach} +
                                                                                        +{/foreach} +``` + +Zobacz także filtr [group |filters#group]. + + +hasBlock(string $name): bool .[method]{data-version:3.0.10} +----------------------------------------------------------- +Sprawdza, czy blok o podanej nazwie istnieje: + +```latte +{if hasBlock(header)} ... {/if} +``` + +Zobacz także [sprawdzanie istnienia bloków |template-inheritance#Sprawdzanie istnienia bloków]. + + +last(string|array $value): mixed .[method] +------------------------------------------ +Zwraca ostatni element tablicy lub znak ciągu: ```latte -{=last([1, 2, 3, 4])} {* drukuje 4 *} -{=last('abcd')} {* drukuje 'd' *} +{=last([1, 2, 3, 4])} {* wyświetla 4 *} +{=last('abcd')} {* wyświetla 'd' *} ``` -Zobacz także [first |#first], [filter last |filters#last]. +Zobacz także [#first], [filtr last |filters#last]. -odd(int $value): bool .[method]{data-version:2.10.2} ----------------------------------------------------- +odd(int $value): bool .[method] +------------------------------- Sprawdza, czy podana liczba jest nieparzysta. ```latte @@ -106,19 +138,19 @@ Sprawdza, czy podana liczba jest nieparzysta. ``` -slice(string|array $value, int $start, int $length=null, bool $preserveKeys=false): string|array .[method]{data-version:2.10.2} -------------------------------------------------------------------------------------------------------------------------------- -Wyodrębnia część tablicy lub łańcucha. +slice(string|array $value, int $start, ?int $length=null, bool $preserveKeys=false): string|array .[method] +----------------------------------------------------------------------------------------------------------- +Wyodrębnia część tablicy lub ciągu. ```latte -{=slice('hello', 1, 2)} {* drukuje 'el' *} -{=slice(['a', 'b', 'c'], 1, 2)} {* drukuje ['b', 'c'] *} +{=slice('hello', 1, 2)} {* wyświetla 'el' *} +{=slice(['a', 'b', 'c'], 1, 2)} {* wyświetla ['b', 'c'] *} ``` -Filtr działa jak funkcja PHP `array_slice` dla tablic lub `mb_substr` dla łańcuchów z awaryjnym przejściem do funkcji `iconv_substr` w trybie UTF-8. +Funkcja działa jak funkcja PHP `array_slice` dla tablic lub `mb_substr` dla ciągów z fallbackiem na funkcję `iconv_substr` w trybie UTF‑8. -Jeśli start jest dodatni, to sekwencja rozpoczyna się z przesunięciem o ten numer od początku tablicy/łańcucha. Jeśli jest ujemny, to sekwencja zaczyna się z przesunięciem o tyle samo od końca. +Jeśli start jest dodatni, sekwencja zacznie się przesunięta o tę liczbę od początku tablicy/ciągu. Jeśli jest ujemny, sekwencja zacznie się przesunięta o tyle od końca. -Jeśli parametr długość jest podany i jest dodatni, to sekwencja będzie zawierała tyle elementów. Jeśli do funkcji zostanie przekazany ujemny parametr długości, sekwencja będzie zawierała wszystkie elementy oryginalnej tablicy, zaczynając od pozycji startowej i kończąc na pozycji mniejszej niż długość elementów od końca tablicy. Jeśli parametr ten nie zostanie przekazany, sekwencja będzie zawierała wszystkie elementy oryginalnej tablicy począwszy od pozycji startowej. +Jeśli podany jest parametr length i jest dodatni, sekwencja będzie zawierać tyle elementów. Jeśli do tej funkcji zostanie przekazany ujemny parametr length, sekwencja będzie zawierać wszystkie elementy oryginalnej tablicy, zaczynając od pozycji start i kończąc na pozycji mniejszej o length elementów od końca tablicy. Jeśli ten parametr nie zostanie podany, sekwencja będzie zawierać wszystkie elementy oryginalnej tablicy, zaczynając od pozycji start. -Domyślnie filtr zmienia kolejność i resetuje klucz całkowity tablicy. To zachowanie można zmienić ustawiając preserveKeys na true. Klucze łańcuchowe są zawsze zachowywane, niezależnie od tego parametru. +Domyślnie filtr zmienia kolejność i resetuje klucze całkowite tablicy. To zachowanie można zmienić, ustawiając preserveKeys na true. Klucze ciągów są zawsze zachowywane, niezależnie od tego parametru. diff --git a/latte/pl/guide.texy b/latte/pl/guide.texy index c51adb5c8d..69ca47ab92 100644 --- a/latte/pl/guide.texy +++ b/latte/pl/guide.texy @@ -1,47 +1,45 @@ -Zaczynając od Latte -******************* +Zaczynamy z Latte +*****************
                                                                                        -Szablony poprawiają organizację kodu, oddzielają logikę aplikacji od prezentacji i zwiększają bezpieczeństwo. Oferują znacznie lepsze funkcje i możliwości ekspresji w generowaniu HTML niż sam PHP. +Szablony poprawiają organizację kodu, oddzielają logikę aplikacji od prezentacji i zwiększają bezpieczeństwo. Oferują znacznie lepsze funkcje i środki wyrazu do generowania HTML niż samo PHP. -Latte jest najbezpieczniejszym systemem szablonowania dla PHP. Spodoba Ci się jego intuicyjna składnia. Szeroka gama przydatnych funkcji znacznie ułatwi Ci pracę. -Zapewnia najwyższej klasy ochronę przed [krytycznymi podatnościami |safety-first] i pozwala skupić się na tworzeniu wysokiej jakości aplikacji bez obaw o ich bezpieczeństwo. +Latte to najbezpieczniejszy system szablonów dla PHP. Pokochasz jego intuicyjną składnię. Szeroka gama przydatnych funkcji znacznie ułatwi Ci pracę. Zapewnia najwyższą ochronę przed [krytycznymi podatnościami|safety-first] i pozwoli Ci skupić się na tworzeniu wysokiej jakości aplikacji bez obaw o ich bezpieczeństwo. -Jak pisać szablony za pomocą Latte? .[#toc-how-to-write-templates-using-latte] ------------------------------------------------------------------------------- +Jak pisać szablony za pomocą Latte? +----------------------------------- -Latte jest sprytnie zaprojektowany i łatwy do nauczenia dla osób zaznajomionych z PHP, ponieważ mogą one szybko przyjąć jego podstawowe tagi. +Latte jest inteligentnie zaprojektowane i łatwe do nauczenia dla tych, którzy znają PHP i opanują podstawowe znaczniki. -- Najpierw zapoznaj się ze [składnią Lat |syntax] te i wypróbuj [wszystko w sieci |https://fiddle.nette.org/latte/] -- Zapoznaj się z podstawowym zestawem [tagów |tags] i [filtrów |filters] -- Napisz szablony w [edytorze z obsługą Lat |recipes#Editors and IDE]te +- Najpierw zapoznaj się ze [składnią Latte|syntax] i [WYPRÓBUJ ONLINE |https://fiddle.nette.org/latte/#9cc0cf6d89#9cc0cf6d89] +- Spójrz na podstawowy zestaw [znaczników|tags] i [filtrów|filters] +- Pisz szablony w [edytorze z obsługą Latte |recipes#Edytory i IDE] -Jak używać Latte w PHP? .[#toc-how-to-use-latte-in-php] -------------------------------------------------------- +Jak używać Latte w PHP? +----------------------- -Wdrożenie Latte do nowej aplikacji to kwestia kilku minut: +Wdrożenie Latte do Twojej nowej aplikacji to kwestia kilku minut: -- Najpierw [zainstaluj i uruchom Latte |develop#Installation] -- Pozwól, aby [narzędzie do debugowania Tracy Cię rozpieszczało|develop#debugging-and-tracy] -- Rozszerzenie Latte o [niestandardową funkcjonalność |extending-latte] +- Najpierw [zainstaluj i uruchom Latte |develop#Instalacja] +- Daj się rozpieścić [narzędziem do debugowania Tracy |develop#Debugowanie i Tracy] +- Rozszerz Latte o [własną funkcjonalność |extending-latte] -Jeśli konwertujesz stary projekt napisany w zwykłym PHP na Latte, [narzędzie do konwersji kodu PHP na Lat |cookbook/migration-from-php] te ułatwi Ci migrację. A może planujesz przejść na Latte z Twiga? Mamy dla Ciebie [konwerter szablonów Tw |cookbook/migration-from-twig] ig na Latte. +Jeśli konwertujesz stary projekt napisany w czystym PHP na Latte, migrację ułatwi Ci [narzędzie do konwersji kodu PHP na Latte |cookbook/migration-from-php]. A może planujesz przejść na Latte z Twiga? Mamy dla Ciebie [konwerter szablonów Twig na Latte |cookbook/migration-from-twig]. -Co jeszcze może zrobić Latte? .[#toc-what-else-can-latte-do] ------------------------------------------------------------- +Co jeszcze potrafi Latte? +------------------------- -Latte przychodzi w pełni wyposażona, ze wszystkimi niezbędnymi rzeczami w zestawie. +Latte otrzymujesz w pełnym wyposażeniu, ze wszystkim, co ważne, w podstawie. -- Twoją produktywność zwiększą [mechanizmy dziedziczenia |template-inheritance], które ponownie wykorzystują powtarzające się elementy i struktury. -- Pancerna [piaskownica |sandbox] bunkra izoluje szablony z niezaufanych źródeł, na przykład edytowane przez samych użytkowników -- Dla dalszej inspiracji, oto [wskazówki i porady |recipes] +- Twoją produktywność zwiększą [mechanizmy dziedziczenia |template-inheritance], dzięki którym powtarzające się elementy i struktury są ponownie wykorzystywane +- Pancerny bunkier [sandbox] izoluje szablony z niezaufanych źródeł, które na przykład edytują sami użytkownicy +- Dla dalszej inspiracji są tu [wskazówki i triki |recipes]
                                                                                        - -{{description: Latte jest najbezpieczniejszym systemem templatkowania dla PHP. Zapobiega to wielu lukom w zabezpieczeniach. Docenisz jego intuicyjną składnię i docenisz wiele przydatnych tweaków.}} +{{description: Latte to najbezpieczniejszy system szablonów dla PHP. Zapobiega wielu lukom bezpieczeństwa. Docenisz jego intuicyjną składnię i wiele przydatnych funkcji.}} diff --git a/latte/pl/loaders.texy b/latte/pl/loaders.texy new file mode 100644 index 0000000000..3006e44b53 --- /dev/null +++ b/latte/pl/loaders.texy @@ -0,0 +1,198 @@ +Loadery +******* + +.[perex] +Loadery to mechanizm, którego Latte używa do uzyskania kodu źródłowego Twoich szablonów. Najczęściej szablony są przechowywane jako pliki na dysku, ale dzięki elastycznemu systemowi loaderów możesz je ładować praktycznie z dowolnego miejsca lub nawet generować dynamicznie. + + +Co to jest Loader? +================== + +Kiedy pracujesz z szablonami, zazwyczaj wyobrażasz sobie pliki `.latte` umieszczone w strukturze katalogów Twojego projektu. Tym zajmuje się domyślny [#FileLoader] w Latte. Jednak połączenie między nazwą szablonu (jak `'main.latte'` lub `'components/card.latte'`) a jego rzeczywistym kodem źródłowym *nie musi* być bezpośrednim mapowaniem na ścieżkę pliku. + +Właśnie tutaj wchodzą w grę loadery. Loader to obiekt, który ma za zadanie wziąć nazwę szablonu (ciąg identyfikacyjny) i dostarczyć Latte jego kod źródłowy. Latte podczas tego zadania całkowicie polega na skonfigurowanym loaderze. Dotyczy to nie tylko początkowego szablonu żądanego za pomocą `$latte->render('main.latte')`, ale także **każdego szablonu odwołującego się wewnątrz** za pomocą tagów takich jak `{include ...}`, `{layout ...}`, `{embed ...}` lub `{import ...}`. + +Dlaczego używać własnego loadera? + +- **Ładowanie z alternatywnych źródeł:** Pobieranie szablonów przechowywanych w bazie danych, w pamięci podręcznej (jak Redis lub Memcached), w systemie kontroli wersji (jak Git, na podstawie konkretnego commita) lub generowanych dynamicznie. +- **Implementacja własnych konwencji nazewnictwa:** Możesz chcieć używać krótszych aliasów dla szablonów lub zaimplementować specyficzną logikę ścieżek wyszukiwania (np. najpierw szukać w katalogu motywu, potem wrócić do katalogu domyślnego). +- **Dodanie zabezpieczeń lub kontroli dostępu:** Własny loader może przed załadowaniem określonych szablonów zweryfikować uprawnienia użytkownika. +- **Przetwarzanie wstępne:** Chociaż generalnie nie jest to zalecane ([przejścia kompilacji |compiler-passes] są lepsze), loader *teoretycznie mógłby* przetworzyć wstępnie zawartość szablonu, zanim przekaże ją do Latte. + +Loader dla instancji `Latte\Engine` ustawiasz za pomocą metody `setLoader()`: + +```php +$latte = new Latte\Engine; + +// Użycie domyślnego FileLoadera dla plików w '/path/to/templates' +$loader = new Latte\Loaders\FileLoader('/path/to/templates'); +$latte->setLoader($loader); +``` + +Loader musi implementować interfejs `Latte\Loader`. + + +Wbudowane Loadery +================= + +Latte oferuje kilka standardowych loaderów: + + +FileLoader +---------- + +To jest **domyślny loader** używany przez klasę `Latte\Engine`, jeśli nie określono żadnego innego. Ładuje szablony bezpośrednio z systemu plików. + +Opcjonalnie możesz ustawić katalog główny, aby ograniczyć dostęp: + +```php +use Latte\Loaders\FileLoader; + +// Poniższe pozwoli na ładowanie szablonów tylko z katalogu /var/www/html/templates +$loader = new FileLoader('/var/www/html/templates'); +$latte->setLoader($loader); + +// $latte->render('../../../etc/passwd'); // To rzuciłoby wyjątek + +// Renderowanie szablonu znajdującego się w /var/www/html/templates/pages/contact.latte +$latte->render('pages/contact.latte'); +``` + +Podczas używania tagów takich jak `{include}` lub `{layout}` rozwiązuje nazwy szablonów względnie do bieżącego szablonu, jeśli nie podano ścieżki absolutnej. + + +StringLoader +------------ + +Ten loader pobiera zawartość szablonu z tablicy asocjacyjnej, gdzie klucze są nazwami szablonów (identyfikatorami), a wartości są ciągami kodu źródłowego szablonu. Jest szczególnie przydatny do testowania lub małych aplikacji, gdzie szablony mogą być przechowywane bezpośrednio w kodzie PHP. + +```php +use Latte\Loaders\StringLoader; + +$loader = new StringLoader([ + 'main.latte' => 'Hello {$name}, include is below:{include helper.latte}', + 'helper.latte' => '{var $x = 10}Included content: {$x}', + // Dodaj więcej szablonów w razie potrzeby +]); + +$latte->setLoader($loader); + +$latte->render('main.latte', ['name' => 'World']); +// Wyjście: Hello World, include is below:Included content: 10 +``` + +Jeśli potrzebujesz wyrenderować tylko jeden szablon bezpośrednio z ciągu bez potrzeby włączania lub dziedziczenia odwołującego się do innych nazwanych szablonów ciągów, możesz przekazać ciąg bezpośrednio do metody `render()` lub `renderToString()` podczas używania `StringLoader` bez tablicy: + +```php +$loader = new StringLoader; +$latte->setLoader($loader); + +$templateString = 'Hello {$name}!'; +$output = $latte->renderToString($templateString, ['name' => 'Alice']); +// $output zawiera 'Hello Alice!' +``` + + +Tworzenie własnego Loadera +========================== + +Aby stworzyć własny loader (np. do ładowania szablonów z bazy danych, pamięci podręcznej, systemu kontroli wersji lub innego źródła), musisz utworzyć klasę, która implementuje interfejs [api:Latte\Loader]. + +Zobaczmy, co musi robić każda metoda. + + +getContent(string $name): string .[method] +------------------------------------------ +To jest podstawowa metoda loadera. Jej zadaniem jest uzyskanie i zwrócenie pełnego kodu źródłowego szablonu zidentyfikowanego przez `$name` (jak przekazano do metody `$latte->render()` lub zwrócono przez metodę [#getReferredName()]). + +Jeśli szablonu nie można znaleźć lub uzyskać do niego dostępu, ta metoda **musi rzucić wyjątek `Latte\RuntimeException`**. + +```php +public function getContent(string $name): string +{ + // Przykład: Ładowanie z hipotetycznego wewnętrznego magazynu + $content = $this->storage->read($name); + if ($content === null) { + throw new Latte\RuntimeException("Template '$name' cannot be loaded."); + } + return $content; +} +``` + + +getReferredName(string $name, string $referringName): string .[method] +---------------------------------------------------------------------- +Ta metoda zajmuje się tłumaczeniem nazw szablonów używanych wewnątrz tagów takich jak `{include}`, `{layout}` itp. Kiedy Latte napotka na przykład `{include 'partial.latte'}` wewnątrz `main.latte`, wywołuje tę metodę z `$name = 'partial.latte'` i `$referringName = 'main.latte'`. + +Zadaniem metody jest przetłumaczenie `$name` na kanoniczny identyfikator (np. ścieżkę absolutną, unikalny klucz bazy danych), który zostanie użyty podczas wywoływania innych metod loadera, na podstawie kontekstu dostarczonego w `$referringName`. + +```php +public function getReferredName(string $name, string $referringName): string +{ + return ...; +} +``` + + +getUniqueId(string $name): string .[method] +------------------------------------------- +Latte używa pamięci podręcznej skompilowanych szablonów do poprawy wydajności. Każdy skompilowany plik szablonu potrzebuje unikalnej nazwy pochodzącej od identyfikatora szablonu źródłowego. Ta metoda dostarcza ciąg, który **jednoznacznie identyfikuje** szablon `$name`. + +Dla szablonów opartych na plikach może posłużyć ścieżka absolutna. Dla szablonów w bazie danych powszechna jest kombinacja prefiksu i ID bazy danych. + +```php +public function getUniqueId(string $name): string +{ + return ...; +} +``` + + +Przykład: Prosty Loader bazodanowy +---------------------------------- + +Ten przykład pokazuje podstawową strukturę loadera, który ładuje szablony przechowywane w tabeli bazy danych o nazwie `templates` z kolumnami `name` (unikalny identyfikator), `content` i `updated_at`. + +```php +use Latte; + +class DatabaseLoader implements Latte\Loader +{ + public function __construct( + private \PDO $db, + ) { + } + + public function getContent(string $name): string + { + $stmt = $this->db->prepare('SELECT content FROM templates WHERE name = ?'); + $stmt->execute([$name]); + $content = $stmt->fetchColumn(); + if ($content === false) { + throw new Latte\RuntimeException("Template '$name' not found in database."); + } + return $content; + } + + // Ten prosty przykład zakłada, że nazwy szablonów ('homepage', 'article', itp.) + // są unikalnymi ID i szablony nie odwołują się do siebie względnie. + public function getReferredName(string $name, string $referringName): string + { + return $name; + } + + public function getUniqueId(string $name): string + { + // Użycie prefiksu i samej nazwy jest tutaj unikalne i wystarczające + return 'db_' . $name; + } +} + +// Użycie: +$pdo = new \PDO(/* szczegóły połączenia */); +$loader = new DatabaseLoader($pdo); +$latte->setLoader($loader); +$latte->render('homepage'); // Ładuje szablon o nazwie 'homepage' z DB +``` + +Własne loadery dają Ci pełną kontrolę nad tym, skąd pochodzą Twoje szablony Latte, co umożliwia integrację z różnymi systemami przechowywania i przepływami pracy. diff --git a/latte/pl/recipes.texy b/latte/pl/recipes.texy index c95813ae11..5c6adaf92d 100644 --- a/latte/pl/recipes.texy +++ b/latte/pl/recipes.texy @@ -1,45 +1,45 @@ -Porady i wskazówki -****************** +Wskazówki i triki +***************** -Edytory i IDE .[#toc-editors-and-ide] -===================================== +Edytory i IDE +============= -Napisz szablony w edytorze lub IDE, który ma wsparcie dla Latte. Będzie to o wiele przyjemniejsze. +Pisz szablony w edytorze lub IDE, który ma wsparcie dla Latte. Będzie to znacznie przyjemniejsze. -- NetBeans IDE ma wbudowaną obsługę -- PhpStorm: zainstaluj [wtyczkę Latte |https://plugins.jetbrains.com/plugin/7457-latte] w `Settings > Plugins > Marketplace` -- Kod VS: wyszukiwanie w markerplace dla wtyczki "Nette Latte + Neon" +- PhpStorm: zainstaluj w `Settings > Plugins > Marketplace` [wtyczkę Latte|https://plugins.jetbrains.com/plugin/7457-latte] +- VS Code: zainstaluj [Nette Latte + Neon|https://marketplace.visualstudio.com/items?itemName=Kasik96.latte], [Nette Latte templates|https://marketplace.visualstudio.com/items?itemName=smuuf.latte-lang] lub najnowszą [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode] wtyczkę +- NetBeans IDE: natywne wsparcie Latte jest częścią instalacji - Sublime Text 3: w Package Control znajdź i zainstaluj pakiet `Nette` i wybierz Latte w `View > Syntax` -- W starych edytorach użyj podświetlenia Smarty dla plików .latte +- w starych edytorach użyj dla plików .latte podświetlania Smarty -Wtyczka do PhpStorm jest bardzo zaawansowana i może wykonać świetną robotę z podpowiadaniem kodu PHP. Aby działało to optymalnie, użyj [szablonów typowych |type-system]. +Wtyczka dla PhpStorm jest bardzo zaawansowana i potrafi doskonale podpowiadać kod PHP. Aby działała optymalnie, używaj [szablonów typowanych|type-system]. [* latte-phpstorm-plugin.webp *] -Możesz również znaleźć wsparcie dla Latte w zakreślaczu kodu internetowego [Prism.js |https://prismjs.com/#supported-languages] i edytorze [Ace |https://ace.c9.io]. +Wsparcie dla Latte znajdziesz również w webowym podświetlaczu kodu [Prism.js|https://prismjs.com/#supported-languages] i edytorze [Ace|https://ace.c9.io]. -Latte wewnątrz JavaScript lub CSS .[#toc-latte-inside-javascript-or-css] -======================================================================== +Latte wewnątrz JavaScriptu lub CSS +================================== -Latte można bardzo wygodnie używać wewnątrz JavaScript lub CSS. Jak jednak uniknąć sytuacji, w której Latte pomyli kod JavaScript lub styl CSS jako tag Latte? +Latte można bardzo wygodnie używać również wewnątrz JavaScriptu lub CSS. Jak jednak uniknąć sytuacji, w której Latte błędnie uznałoby kod JavaScript lub styl CSS za znacznik Latte? ```latte ``` -**Opcja 1** +**Wariant 1** -Unikaj sytuacji, w której litera następuje bezpośrednio po `{`, na przykład przez umieszczenie przed nią spacji, przerwy w linii lub cytatu: +Unikaj sytuacji, w której litera następuje bezpośrednio po `{`, na przykład wstawiając przed nią spację, znak nowej linii lub cudzysłów: ```latte -

                                                                                        +

                                                                                        ``` -Dwie ścieżki i dwa różne sposoby ucieczki od danych. Wewnątrz elementu ` ``` -Jeśli jednak chcemy wstawić go do atrybutu HTML, nadal musimy uciec od cudzysłowów do encji HTML: +Jeśli jednak chcielibyśmy go wstawić do atrybutu HTML, musimy jeszcze escapować cudzysłowy na encje HTML: ```html
                                                                                        ``` -Zagnieżdżony kontekst nie musi być JS ani CSS. Parametry w adresach URL są escape'owane przez konwersję znaków specjalnych na sekwencje zaczynające się od `%`. Przykład: +Zagnieżdżonym kontekstem nie musi być jednak tylko JS lub CSS. Często jest nim również URL. Parametry w URL escapuje się tak, że znaki o specjalnym znaczeniu konwertuje się na sekwencje zaczynające się od `%`. Przykład: ``` https://example.org/?a=Jazz&b=Rock%27n%27Roll ``` -A kiedy wyprowadzamy ten ciąg w atrybucie, nadal stosujemy escaping zgodnie z tym kontekstem i zastępujemy `&` za `&`: +A kiedy ten ciąg wyświetlimy w atrybucie, jeszcze zastosujemy escapowanie zgodnie z tym kontekstem i zastąpimy `&` na `&`: ```html ``` -Jeśli przeczytałeś to do tej pory, gratuluję, to było wyczerpujące. Teraz masz dobre pojęcie o tym, czym są konteksty i ucieczka. I nie musisz się martwić, że będzie to skomplikowane. Latte robi to za Ciebie automatycznie. +Jeśli doczytałeś aż dotąd, gratulujemy, było to wyczerpujące. Teraz już masz dobre pojęcie o tym, czym są konteksty i escapowanie. I nie musisz się martwić, że to skomplikowane. Latte robi to bowiem za Ciebie automatycznie. -Latte a systemy naiwne .[#toc-latte-vs-naive-systems] -===================================================== +Latte vs naiwne systemy +======================= -Pokazaliśmy, jak prawidłowo stosować escaping w dokumencie HTML i jak kluczowa jest znajomość kontekstu, czyli miejsca, w którym wyprowadzamy dane. Innymi słowy, jak działa ucieczka kontekstowa. -Chociaż jest to warunek wstępny dla funkcjonalnej obrony przed XSS, **Latte jest jedynym systemem szablonów dla PHP, który to robi.** +Pokazaliśmy, jak poprawnie escapuje się w dokumencie HTML i jak kluczowa jest znajomość kontekstu, czyli miejsca, gdzie dane wyświetlamy. Innymi słowy, jak działa escapowanie kontekstowe. Chociaż jest to niezbędny warunek skutecznej obrony przed XSS, **Latte jest jedynym systemem szablonów dla PHP, który to potrafi.** -Jak to możliwe, skoro wszystkie dzisiejsze systemy twierdzą, że mają automatyczną ucieczkę? -Automatyczne uciekanie bez znajomości kontekstu to bzdura, która **tworzy fałszywe poczucie bezpieczeństwa**. +Jak to możliwe, skoro wszystkie systemy dzisiaj twierdzą, że mają automatyczne escapowanie? Automatyczne escapowanie bez znajomości kontekstu to trochę bullshit, który **tworzy fałszywe poczucie bezpieczeństwa**. -Systemy szablonujące takie jak Twig, Laravel Blade i inne nie widzą w szablonie żadnej struktury HTML. Dlatego nie widzą też kontekstów. W porównaniu z Latte są ślepi i naiwni. Obsługują tylko niestandardowe tagi, wszystko inne jest dla nich nieistotnym strumieniem znaków: +Systemy szablonów, takie jak Twig, Laravel Blade i inne, nie widzą w szablonie żadnej struktury HTML. Nie widzą więc również kontekstów. W porównaniu do Latte są ślepe i naiwne. Przetwarzają tylko własne znaczniki, wszystko inne jest dla nich nieistotnym strumieniem znaków:
                                                                                        -```twig .{file:Twig šablona, jak ji vidí samotný Twig} -░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░ -░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░ +```twig .{file:Szablon Twig, jak go widzi sam Twig} +░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░ +░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░ ``` -```twig .{file:Twig šablona, jak ji vidí designer} -- v textu: {{ text }} -- v tagu: -- v atributu: -- v atributu bez uvozovek: -- v atributu obsahujícím URL: -- v atributu obsahujícím JavaScript: -- v atributu obsahujícím CSS: -- v JavaScriptu: -- v CSS: -- v komentáři: +```twig .{file:Szablon Twig, jak go widzi projektant} +- w tekście: {{ foo }} +- w tagu: +- w atrybucie: +- w atrybucie bez cudzysłowów: +- w atrybucie zawierającym URL: +- w atrybucie zawierającym JavaScript: +- w atrybucie zawierającym CSS: +- w JavaScripcie: +- w CSS: +- w komentarzu: ```
                                                                                        -Systemy naiwne po prostu mechanicznie konwertują znaki `< > & ' "` na encje HTML, co jest poprawną metodą ucieczki w większości przypadków użycia, ale daleko od zawsze. Nie mogą więc wykryć ani zapobiec różnym lukom w zabezpieczeniach, co pokażemy poniżej. +Naiwne systemy tylko mechanicznie konwertują znaki `< > & ' "` na encje HTML, co jest wprawdzie w większości przypadków użycia prawidłowym sposobem escapowania, ale zdecydowanie nie zawsze. Nie mogą więc wykryć ani zapobiec powstawaniu różnych luk bezpieczeństwa, jak pokażemy dalej. -Latte widzi szablon tak samo jak ty. Rozumie HTML, XML, rozpoznaje tagi, atrybuty itp. A dzięki temu rozróżnia konteksty i odpowiednio traktuje dane. Oferuje więc naprawdę skuteczną ochronę przed krytyczną podatnością Cross-site Scripting. +Latte widzi szablon tak samo jak Ty. Rozumie HTML, XML, rozpoznaje znaczniki, atrybuty itp. A dzięki temu rozróżnia poszczególne konteksty i zgodnie z nimi oczyszcza dane. Oferuje w ten sposób naprawdę skuteczną ochronę przed krytyczną podatnością Cross-site Scripting. + +
                                                                                        + +```latte .{file:Szablon Latte, jak go widzi Latte} +░░░░░░░░░░░{$foo} +░░░░░░░░░░ +░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░ +░░░░░░░░░ +░░░░░░░░░░░░░░░ +``` + +```latte .{file:Szablon Latte, jak go widzi projektant} +- w tekście: {$foo} +- w tagu: +- w atrybucie: +- w atrybucie bez cudzysłowów: +- w atrybucie zawierającym URL: +- w atrybucie zawierającym JavaScript: +- w atrybucie zawierającym CSS: +- w JavaScripcie: +- w CSS: +- w komentarzu: +``` + +
                                                                                        -Demonstracja na żywo .[#toc-live-demonstration] -=============================================== +Przykład na żywo +================ -Po lewej stronie widać szablon w Latte, po prawej wygenerowany kod HTML. Zmienna `$text` jest wyświetlana kilkakrotnie, za każdym razem w nieco innym kontekście. I w ten sposób uciekł trochę inaczej. Możesz samodzielnie edytować kod szablonu, np. zmienić zawartość zmiennej itp. Spróbuj: +Po lewej stronie widać szablon w Latte, po prawej wygenerowany kod HTML. Kilka razy wyświetlana jest zmienna `$text` i za każdym razem w nieco innym kontekście. A więc i nieco inaczej escapowana. Kod szablonu możesz sam edytować, na przykład zmienić zawartość zmiennej itp. Spróbuj:
                                                                                        ``` .{file:template.latte; min-height: 14em}[fiddle-source] -{* ZKUS UPRAVIT TUTO ŠABLONU *} +{* SPRÓBUJ ZMODYFIKOWAĆ TEN SZABLON *} {var $text = "Rock'n'Roll"} - {$text} - @@ -270,63 +286,61 @@ Po lewej stronie widać szablon w Latte, po prawej wygenerowany kod HTML. Zmienn
                                                                                        -Czyż nie jest to wspaniałe! Latte robi kontekstowe ucieczki automatycznie, więc programista: +Czyż to nie wspaniałe! Latte wykonuje escapowanie kontekstowe automatycznie, więc programista: -- nie musi myśleć ani wiedzieć, jak uciec, gdzie -- nie można się pomylić -- nie można zapomnieć o ucieczce +- nie musi myśleć ani wiedzieć, jak gdzie escapować +- nie może się pomylić +- nie może zapomnieć o escapowaniu -To nawet nie są wszystkie konteksty, które Latte rozróżnia przy wyprowadzaniu i dla których dostosowuje obróbkę danych. Teraz przejdziemy przez ciekawsze przypadki. +To nawet nie wszystkie konteksty, które Latte rozróżnia podczas wyświetlania i dla których dostosowuje oczyszczanie danych. Inne ciekawe przypadki omówimy teraz. -Jak włamać się do systemów naiwnych .[#toc-how-to-hack-naive-systems] -===================================================================== +Jak zhakować naiwne systemy +=========================== -Na kilku praktycznych przykładach pokażemy jak ważne jest rozróżnianie kontekstu i dlaczego naiwne systemy templatek nie zapewniają wystarczającej ochrony przed XSS, w przeciwieństwie do Latte. -W przykładach użyjemy Twiga jako przedstawiciela systemu naiwnego, ale to samo dotyczy innych systemów. +Na kilku praktycznych przykładach pokażemy, jak ważne jest rozróżnianie kontekstów i dlaczego naiwne systemy szablonów nie zapewniają wystarczającej ochrony przed XSS, w przeciwieństwie do Latte. Jako przedstawiciela naiwnego systemu użyjemy w przykładach Twiga, ale to samo dotyczy innych systemów. -Podatność atrybutów .[#toc-attribute-vulnerability] ---------------------------------------------------- +Podatność atrybutu +------------------ -Spróbujemy wstrzyknąć złośliwy kod do strony za pomocą atrybutu HTML, jak pokazaliśmy [powyżej |#How-Does-the-Vulnerability-Arise]. Miejmy na uwadze, że szablon w Twigu renderuje obraz: +Spróbujemy wstrzyknąć do strony złośliwy kod za pomocą atrybutu HTML, jak [pokazaliśmy powyżej |#Jak powstaje podatność]. Miejmy szablon w Twigu renderujący obrazek: ```twig .{file:Twig} {{ ``` -Zauważ, że wokół wartości atrybutów nie ma cudzysłowów. Koder mógł o nich zapomnieć, co po prostu się zdarza. Na przykład w React kod jest napisany tak, bez cytatów, a koder, który zmienia języki, może łatwo zapomnieć o cytatach. +Zwróć uwagę, że wokół wartości atrybutów nie ma cudzysłowów. Koder mógł o nich zapomnieć, co się po prostu zdarza. Na przykład w React kod pisze się w ten sposób, bez cudzysłowów, a koder, który zmienia języki, może łatwo zapomnieć o cudzysłowach. -Atakujący wstawiłby sprytnie skonstruowany ciąg znaków `foo onload=alert('Hacked!')` jako podpis obrazka. Wiemy już, że Twig nie potrafi określić, czy zmienna jest wyprowadzana w strumieniu tekstu HTML, wewnątrz atrybutu, wewnątrz komentarza HTML itd. I po prostu mechanicznie konwertuje znaki `< > & ' "` na jednostki HTML. -Tak więc wynikowy kod będzie wyglądał tak: +Atakujący jako opis obrazka wstawia sprytnie skonstruowany ciąg `foo onload=alert('Hacked!')`. Już wiemy, że Twig nie może rozpoznać, czy zmienna jest wyświetlana w przepływie tekstu HTML, wewnątrz atrybutu, komentarza HTML itp., krótko mówiąc, nie rozróżnia kontekstów. I tylko mechanicznie konwertuje znaki `< > & ' "` na encje HTML. Więc wynikowy kod będzie wyglądał tak: ```html foo ``` -**Powstała dziura w zabezpieczeniach!** +**I powstała luka bezpieczeństwa!** -Fałszywy atrybut `onload` stał się częścią strony i przeglądarka uruchamia go natychmiast po pobraniu obrazu. +Częścią strony stał się podrzucony atrybut `onload`, a przeglądarka natychmiast po pobraniu obrazka go uruchomi. -Teraz zobaczmy, jak Latte radzi sobie z tym samym szablonem: +Teraz zobaczymy, jak z tym samym szablonem poradzi sobie Latte: ```latte .{file:Latte} {$imageAlt} ``` -Latte widzi szablon tak samo jak ty. W przeciwieństwie do Twig, rozumie HTML i wie, że zmienna jest drukowana jako wartość atrybutu, która nie jest w cudzysłowie. Dlatego też dodaje je. Gdy atakujący wstawi tę samą etykietę, wynikowy kod będzie wyglądał tak: +Latte widzi szablon tak samo jak Ty. W przeciwieństwie do Twiga rozumie HTML i wie, że zmienna jest wyświetlana jako wartość atrybutu, który nie jest w cudzysłowach. Dlatego je uzupełni. Kiedy atakujący wstawi ten sam opis, wynikowy kod będzie wyglądał tak: ```html foo onload=alert('Hacked!') ``` -**Latte skutecznie zapobiegła XSS.** +**Latte skutecznie zapobiegło XSS.** -Wypisywanie zmiennej w JavaScript .[#toc-printing-a-variable-in-javascript] ---------------------------------------------------------------------------- +Wyświetlanie zmiennej w JavaScript +---------------------------------- -Dzięki ucieczce kontekstowej, możliwe jest używanie zmiennych PHP natywnie wewnątrz JavaScript. +Dzięki escapowaniu kontekstowemu możliwe jest całkowicie natywne używanie zmiennych PHP wewnątrz JavaScriptu. ```latte

                                                                                        {$movie}

                                                                                        @@ -334,7 +348,7 @@ Dzięki ucieczce kontekstowej, możliwe jest używanie zmiennych PHP natywnie we ``` -Jeśli zmienna `$movie` zawiera ciąg `'Amarcord & 8 1/2'`, to zostanie wygenerowane następujące wyjście. Zauważ, że wewnątrz HTML używane jest inne escaping niż wewnątrz JavaScript, a nawet inne w atrybucie `onclick`: +Jeśli zmienna `$movie` będzie zawierać ciąg `'Amarcord & 8 1/2'`, wygeneruje się następujące wyjście. Zwróć uwagę, że wewnątrz HTML użyje się innego escapowania niż wewnątrz JavaScriptu, a jeszcze innego w atrybucie `onclick`: ```latte

                                                                                        Amarcord & 8 1/2

                                                                                        @@ -343,29 +357,27 @@ Jeśli zmienna `$movie` zawiera ciąg `'Amarcord & 8 1/2'`, to zostanie wygenero ``` -Kontrola połączeń .[#toc-link-checking] ---------------------------------------- +Sprawdzanie linków +------------------ -Latte automatycznie sprawdza, czy zmienna użyta w atrybutach `src` lub `href` zawiera adres URL strony internetowej (tj. protokół HTTP) i zapobiega wypisywaniu linków, które mogą stanowić zagrożenie dla bezpieczeństwa. +Latte automatycznie sprawdza, czy zmienna użyta w atrybutach `src` lub `href` zawiera adres URL (tj. protokół HTTP) i zapobiega wyświetlaniu linków, które mogą stanowić zagrożenie bezpieczeństwa. ```latte {var $link = 'javascript:attack()'} -klikni +kliknij ``` -Wydruki: +Wyświetli: ```latte -klikni +kliknij ``` -Sprawdzanie można wyłączyć za pomocą filtra [nocheck |filters#nocheck]. +Kontrolę można wyłączyć za pomocą filtra [nocheck |filters#nocheck]. -Limity na latte .[#toc-limits-of-latte] -======================================= +Ograniczenia Latte +================== -Latte nie jest kompletną ochroną XSS dla całej aplikacji. Nie chcielibyśmy, abyś przestał myśleć o bezpieczeństwie podczas korzystania z Latte. -Celem Latte jest zapewnienie, że atakujący nie może zmienić struktury strony, ani sfałszować elementów lub atrybutów HTML. Ale nie sprawdza poprawności merytorycznej danych wyjściowych. Albo poprawność zachowania JavaScript. -To wykracza poza zakres systemu szablonowania. Weryfikacja poprawności danych, zwłaszcza wprowadzanych przez użytkownika, a więc niezaufanych, jest ważnym zadaniem dla programisty. +Latte nie jest całkowicie kompletną ochroną przed XSS dla całej aplikacji. Nie chcielibyśmy, abyś przy użyciu Latte przestał myśleć o bezpieczeństwie. Celem Latte jest zapewnienie, aby atakujący nie mógł zmodyfikować struktury strony, podrzucić elementów HTML lub atrybutów. Ale nie kontroluje poprawności treściowej wyświetlanych danych. Ani poprawności działania JavaScriptu. To już wykracza poza kompetencje systemu szablonów. Weryfikacja poprawności danych, zwłaszcza tych wprowadzonych przez użytkownika, a więc niezaufanych, jest ważnym zadaniem programisty. diff --git a/latte/pl/sandbox.texy b/latte/pl/sandbox.texy index 4f10f7dbdf..89ea7ab358 100644 --- a/latte/pl/sandbox.texy +++ b/latte/pl/sandbox.texy @@ -1,12 +1,10 @@ -Piaskownica -*********** +Sandbox +******* -.[perex]{data-version:2.8} -Sandbox zapewnia warstwę bezpieczeństwa, która daje kontrolę nad tym, jakie znaczniki, funkcje PHP, metody itp. mogą być używane w szablonach. Dzięki trybowi piaskownicy możesz bezpiecznie współpracować z klientem lub zewnętrznym koderem przy tworzeniu szablonów bez obaw o kompromitację aplikacji lub niepożądane operacje. +.[perex] +Sandbox zapewnia warstwę bezpieczeństwa, która daje kontrolę nad tym, jakie znaczniki, funkcje PHP, metody itp. mogą być używane w szablonach. Dzięki trybowi sandbox możesz bezpiecznie współpracować z klientem lub zewnętrznym koderem przy tworzeniu szablonów, nie martwiąc się o naruszenie aplikacji lub niepożądane operacje. -Jak to działa? Po prostu definiujemy, co chcemy dopuścić w szablonie. Na początku wszystko jest zabronione i stopniowo nadajemy uprawnienia: - -Za pomocą poniższego kodu pozwalamy autorowi szablonu na użycie znaczników `{block}`, `{if}`, `{else}`, oraz `{=}`, który jest znacznikiem służącym do [wypisania zmiennej lub wyrażenia |tags#Printing] oraz wszystkich filtrów: +Jak to działa? Po prostu definiujemy, na co wszystko pozwalamy szablonowi. Przy czym domyślnie wszystko jest zabronione, a my stopniowo zezwalamy. Poniższym kodem umożliwimy autorowi szablonu używanie znaczników `{block}`, `{if}`, `{else}` i `{=}`, co jest znacznikiem do [wyświetlania zmiennej lub wyrażenia |tags#Wyświetlanie] oraz wszystkich filtrów: ```php $policy = new Latte\Sandbox\SecurityPolicy; @@ -16,7 +14,7 @@ $policy->allowFilters($policy::All); $latte->setPolicy($policy); ``` -Możemy również włączyć poszczególne funkcje, metody lub właściwości obiektu: +Dalej możemy zezwolić na poszczególne funkcje, metody lub właściwości obiektów: ```php $policy->allowFunctions(['trim', 'strlen']); @@ -24,30 +22,35 @@ $policy->allowMethods(Nette\Security\User::class, ['isLoggedIn', 'isAllowed']); $policy->allowProperties(Nette\Database\Row::class, $policy::All); ``` -Czyż to nie wspaniałe? Możesz kontrolować wszystko na bardzo niskim poziomie. Jeśli szablon spróbuje wywołać nielegalną funkcję lub uzyskać dostęp do nielegalnej metody lub właściwości, zakończy się wyjątkiem `Latte\SecurityViolationException`. +Czyż to nie wspaniałe? Możesz na bardzo niskim poziomie kontrolować absolutnie wszystko. Jeśli szablon spróbuje wywołać niedozwoloną funkcję lub uzyskać dostęp do niedozwolonej metody lub właściwości, zakończy się to wyjątkiem `Latte\SecurityViolationException`. -Tworzenie od podstaw polityki, w której wszystko jest zabronione, może nie być wygodne, więc można zacząć od bezpiecznej bazy: +Tworzenie polityki od zera, gdy wszystko jest zabronione, może nie być wygodne, dlatego możesz zacząć od bezpiecznej podstawy: ```php $policy = Latte\Sandbox\SecurityPolicy::createSafePolicy(); ``` -Bezpieczna baza oznacza, że wszystkie standardowe tagi są dozwolone z wyjątkiem `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget`. -Włączone są wszystkie standardowe filtry oprócz `datastream`, `noescape` i `nocheck`. Wreszcie włączony jest dostęp do metod i właściwości obiektu `$iterator`. +Bezpieczna podstawa oznacza, że dozwolone są wszystkie standardowe znaczniki oprócz `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget`. Dozwolone są standardowe filtry oprócz `datastream`, `noescape` i `nocheck`. I na koniec dozwolony jest dostęp do metod i właściwości obiektu `$iterator`. -Reguły są stosowane do szablonu, który wstawiamy z tagiem [`{sandbox}` |tags#including-templates]. Który jest w pewnym sensie podobny do `{include}`, ale włącza tryb bezpieczny i również nie przekazuje żadnych zmiennych: +Zasady stosuje się do szablonu, który wstawiamy znacznikiem [`{sandbox}` |tags#Dołączanie szablonu]. Co jest pewnego rodzaju odpowiednikiem `{include}`, który jednak włącza tryb bezpieczny i nie przekazuje żadnych zmiennych: ```latte {sandbox 'untrusted.latte'} ``` -W ten sposób layout i poszczególne strony mogą bez przeszkód używać wszystkich znaczników i zmiennych, ograniczony będzie jedynie szablon `untrusted.latte`. +Zatem layout i poszczególne strony mogą swobodnie wykorzystywać wszystkie znaczniki i zmienne, jedynie na szablon `untrusted.latte` zostaną zastosowane restrykcje. -Niektóre przekroczenia, takie jak użycie zabronionego znacznika lub filtra, zostaną ujawnione w czasie kompilacji. Inne, takie jak wywoływanie nieautoryzowanych metod obiektu, tylko w czasie biegu. -Szablon może zawierać również inne błędy. Aby zapobiec wyskakiwaniu wyjątku z sandboksowanego szablonu i zakłócaniu całego renderowania, możesz zdefiniować niestandardowy [handler wyjątków |develop#Exception-Handler], aby na przykład rejestrować wyjątek. +Niektóre naruszenia, takie jak użycie zabronionego znacznika lub filtra, zostaną wykryte w czasie kompilacji. Inne, takie jak wywołanie niedozwolonych metod obiektu, dopiero w czasie działania. Szablon może również zawierać dowolne inne błędy. Aby z sandboxowanego szablonu nie mógł wyskoczyć wyjątek, który zakłóci całe renderowanie, można zdefiniować własny [niestandardowy handler wyjątków |develop#Obsługa wyjątków], który go na przykład zaloguje. -Gdybyśmy chcieli włączyć tryb piaskownicy bezpośrednio dla wszystkich szablonów, to łatwo to zrobić: +Jeśli chcielibyśmy włączyć tryb sandbox bezpośrednio dla wszystkich szablonów, jest to łatwe: ```php $latte->setSandboxMode(); ``` + +Aby mieć pewność, że użytkownik nie wstawi do strony kodu PHP, który jest wprawdzie poprawny składniowo, ale zabroniony i spowoduje PHP Compile Error, zalecamy [sprawdzać szablony za pomocą lintera PHP |develop#Kontrola wygenerowanego kodu]. Tę funkcjonalność włączysz metodą `Engine::enablePhpLint()`. Ponieważ do kontroli potrzebuje wywołać binarkę PHP, ścieżkę do niej przekaż jako parametr: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); +``` diff --git a/latte/pl/syntax.texy b/latte/pl/syntax.texy index a6c7722e1e..c0285f0c29 100644 --- a/latte/pl/syntax.texy +++ b/latte/pl/syntax.texy @@ -2,62 +2,60 @@ Składnia ******** .[perex] -Syntax Latte powstał z praktycznych wymagań projektantów stron internetowych. Szukaliśmy najbardziej przyjaznej dla użytkownika składni, z którą można elegancko napisać konstrukcje, które w przeciwnym razie są prawdziwym wyzwaniem. -Jednocześnie wszystkie wyrażenia są napisane dokładnie tak samo jak w PHP, więc nie musisz uczyć się nowego języka. Po prostu wykorzystujesz w pełni to, co już wiesz. +Składnia Latte wywodzi się z praktycznych wymagań webdesignerów. Szukaliśmy najbardziej przyjaznej składni, za pomocą której elegancko zapiszesz nawet konstrukcje, które w innym przypadku stanowią prawdziwe wyzwanie. Jednocześnie wszystkie wyrażenia pisze się dokładnie tak samo jak w PHP, więc nie musisz uczyć się nowego języka. Po prostu wykorzystujesz to, co już dawno umiesz. -Poniżej znajduje się minimalny szablon, który ilustruje kilka podstawowych elementów: tagi, n:attributes, komentarze i filtry. +Poniżej znajduje się minimalny szablon, który ilustruje kilka podstawowych elementów: znaczniki, n:atrybuty, komentarze i filtry. ```latte {* to jest komentarz *} -
                                                                                          {* n:if to n:atrybut *} -{foreach $items as $item} {* znacznik reprezentujący pętlę foreach *} -
                                                                                        • {$item|capitalize}
                                                                                        • {* tag wyprowadzający zmienną filtrującą *} -{/foreach} {* koniec pętli *} +
                                                                                            {* n:if to n:atrybut *} +{foreach $items as $item} {* znacznik reprezentujący pętlę foreach *} +
                                                                                          • {$item|capitalize}
                                                                                          • {* znacznik wyświetlający zmienną z filtrem *} +{/foreach} {* koniec pętli *}
                                                                                          ``` -Przyjrzyjmy się bliżej tym ważnym elementom i temu, jak mogą one pomóc w stworzeniu rewelacyjnego szablonu. +Przyjrzyjmy się bliżej tym ważnym elementom i temu, jak mogą pomóc Ci stworzyć niesamowity szablon. -Tagi .[#toc-tags] -================= +Znaczniki +========= -Szablon zawiera znaczniki, które kontrolują logikę szablonu (na przykład pętle *foreach*) lub wyrażenia wyjściowe. Pojedynczy delimiter jest używany dla obu `{ ... }`, więc nie trzeba się zastanawiać, którego delimitera użyć w danej sytuacji, jak to ma miejsce w innych systemach. -Jeśli po znaku `{` następuje cytat lub spacja, Latte nie uważa go za początek znacznika, więc możesz bez problemu używać konstrukcji JavaScript, JSON lub reguł CSS w szablonach. +Szablon zawiera znaczniki, które sterują logiką szablonu (na przykład pętle *foreach*) lub wyświetlają wyrażenia. Do obu celów używa się jednego ogranicznika `{ ... }`, więc nie musisz myśleć, jakiego ogranicznika użyć w jakiej sytuacji, jak ma to miejsce w innych systemach. Jeśli po znaku `{` następuje cudzysłów lub spacja, Latte nie uważa go za początek znacznika, dzięki czemu możesz w szablonach bez problemu używać również konstrukcji JavaScriptowych, JSON lub reguł w CSS. -Zobacz [przegląd wszystkich tagów |tags]. Dodatkowo można też tworzyć [własne tagi |extending-latte#Tags]. +Zobacz [przegląd wszystkich znaczników|tags]. Oprócz tego możesz tworzyć również [niestandardowe znaczniki|custom tags]. -Latte rozumie PHP .[#toc-latte-understands-php] -=============================================== +Latte rozumie PHP +================= -Możesz używać wyrażeń PHP wewnątrz znaczników, które dobrze znasz: +Wewnątrz znaczników możesz używać wyrażeń PHP, które dobrze znasz: - zmienne -- ciągi (w tym HEREDOC i NOWDOC), tablice, liczby itp. -- [operatorzy |https://www.php.net/manual/en/language.operators.php] -- wywołania funkcji i metod (które mogą być [sandboxowane |sandbox]) -- [mecz |https://www.php.net/manual/en/control-structures.match.php] -- [anonimowe funkcje |https://www.php.net/manual/en/functions.arrow.php] -- [callbacks |https://www.php.net/manual/en/functions.first_class_callable_syntax.php] +- ciągi znaków (w tym HEREDOC i NOWDOC), tablice, liczby itp. +- [operatory |https://www.php.net/manual/en/language.operators.php] +- wywołania funkcji i metod (które można ograniczyć [sandboxem|sandbox]) +- [match |https://www.php.net/manual/en/control-structures.match.php] +- [funkcje anonimowe |https://www.php.net/manual/en/functions.arrow.php] +- [callbacki |https://www.php.net/manual/en/functions.first_class_callable_syntax.php] - komentarze wieloliniowe `/* ... */` -- itp... +- itd… -Latte dodaje również kilka [miłych rozszerzeń |#Syntactic-Sugar] do składni PHP. +Latte dodatkowo uzupełnia składnię PHP o kilka [przyjemnych rozszerzeń |#Cukier syntaktyczny]. -n:atrybuty .[#toc-n-attributes] -=============================== +n:atrybuty +========== -Wszystkie sparowane znaczniki, na przykład `{if} … {/if}`, działające na pojedynczym elemencie HTML, można przepisać jako n:attributes. Na przykład `{foreach}` w przykładzie otwierającym można by napisać w ten sposób: +Wszystkie znaczniki parzyste, na przykład `{if} … {/if}`, operujące na jednym elemencie HTML, można przepisać w postaci n:atrybutów. W ten sposób można by zapisać na przykład również `{foreach}` we wstępnym przykładzie: ```latte -
                                                                                            +
                                                                                            • {$item|capitalize}
                                                                                            ``` -Następnie funkcjonalność jest stosowana do elementu HTML, w którym jest umieszczona: +Funkcjonalność odnosi się wtedy do elementu HTML, w którym jest umieszczona: ```latte {var $items = ['I', '♥', 'Latte']} @@ -65,7 +63,7 @@ Następnie funkcjonalność jest stosowana do elementu HTML, w którym jest umie

                                                                                            {$item}

                                                                                            ``` -wydruki: +wyświetli: ```latte

                                                                                            I

                                                                                            @@ -73,7 +71,7 @@ wydruki:

                                                                                            Latte

                                                                                            ``` -Używając przedrostka `inner-` możemy zmodyfikować zachowanie, aby dotyczyło tylko wewnętrznej części elementu: +Za pomocą prefiksu `inner-` możemy zmodyfikować zachowanie tak, aby odnosiło się tylko do wewnętrznej części elementu: ```latte
                                                                                            @@ -82,7 +80,7 @@ Używając przedrostka `inner-` możemy zmodyfikować zachowanie, aby dotyczyło
                                                                                            ``` -Zostanie wydrukowany: +Wyświetli się: ```latte
                                                                                            @@ -95,178 +93,184 @@ Zostanie wydrukowany:
                                                                                            ``` -Możesz też użyć przedrostka `tag-`, aby zastosować tę funkcjonalność do samych znaczników HTML: +Lub za pomocą prefiksu `tag-` zastosujemy funkcjonalność tylko do samych znaczników HTML: ```latte -

                                                                                            Title

                                                                                            +

                                                                                            Title

                                                                                            ``` -Która wychodzi w zależności od zmiennej `$url`: +Co wyświetli w zależności od zmiennej `$url`: ```latte -{* gdy $url jest pusty *} +{* gdy $url jest puste *}

                                                                                            Title

                                                                                            {* gdy $url zawiera 'https://nette.org' *}

                                                                                            Title

                                                                                            ``` -Jednak n:attributes nie jest tylko skrótem dla znaczników parami. Istnieją również czyste n:atrybuty, takie jak [n:href |application:creating-links#In-the-Presenter-Template] lub podręczny pomocnik kodera [n:class |tags#n-class]. +Jednak n:atrybuty to nie tylko skrót dla znaczników parzystych. Istnieją również czyste n:atrybuty, takie jak [n:href |application:creating-links#W szablonie presentera] lub bardzo przydatny pomocnik kodera [n:class |tags#n:class]. -Filtry .[#toc-filters] -====================== +Filtry +====== Zobacz przegląd [standardowych filtrów |filters]. -Filtry wpisujemy po pionowym pasku (przed nim może być spacja): +Filtry zapisuje się za pionową kreską (może być przed nią spacja): ```latte

                                                                                            {$heading|upper}

                                                                                            ``` -Filtry mogą być konkatenowane, a następnie stosowane w kolejności od lewej do prawej: +Filtry można łączyć w łańcuchy, a następnie są stosowane w kolejności od lewej do prawej: ```latte

                                                                                            {$heading|lower|capitalize}

                                                                                            ``` -Parametry wprowadzane są po nazwie filtra, oddzielone dwukropkami lub przecinkami: +Parametry podaje się za nazwą filtra, oddzielone dwukropkami lub przecinkami: ```latte

                                                                                            {$heading|truncate:20,''}

                                                                                            ``` -Filtry można również zastosować do wyrażenia: +Filtry można stosować również do wyrażeń: ```latte {var $name = ($title|upper) . ($subtitle|lower)} ``` -Do zablokowania: +Na blok: ```latte

                                                                                            {block |lower}{$heading}{/block}

                                                                                            ``` -Lub bezpośrednio na wartość (w połączeniu z tagiem [`{=expr}` | https://latte.nette.org/pl/tags#Vypisování]): +Lub bezpośrednio na wartość (w połączeniu ze znacznikiem [`{=expr}` |tags#Wyświetlanie]): ```latte

                                                                                            {=' Hello world '|trim}

                                                                                            ``` -Uwagi .[#toc-comments] -====================== +Dynamiczne znaczniki HTML .{data-version:3.0.9} +=============================================== -Komentarze są napisane w ten sposób i nie dostają się do wyjścia: +Latte obsługuje dynamiczne znaczniki HTML, które są przydatne, gdy potrzebujesz elastyczności w nazwach znaczników: + +```latte +Heading +``` + +Powyższy kod może na przykład generować `

                                                                                            Heading

                                                                                            ` lub `

                                                                                            Heading

                                                                                            ` w zależności od wartości zmiennej `$level`. Dynamiczne znaczniki HTML w Latte muszą być zawsze parzyste. Ich alternatywą jest [n:tag |tags#n:tag]. + +Ponieważ Latte jest bezpiecznym systemem szablonów, sprawdza, czy wynikowa nazwa znacznika jest prawidłowa i nie zawiera żadnych niepożądanych lub szkodliwych wartości. Ponadto zapewnia, że nazwa znacznika końcowego będzie zawsze taka sama jak nazwa znacznika otwierającego. + + +Komentarze +========== + +Komentarze zapisuje się w ten sposób i nie trafiają one do wyjścia: ```latte {* to jest komentarz w Latte *} ``` -Komentarze PHP działają wewnątrz znaczników: +Wewnątrz znaczników działają komentarze PHP: ```latte {include 'file.info', /* value: 123 */} ``` -Cukier syntaktyczny .[#toc-syntactic-sugar] -=========================================== +Cukier syntaktyczny +=================== -Nienotowane ciągi znaków .[#toc-strings-without-quotation-marks] ----------------------------------------------------------------- +Ciągi znaków bez cudzysłowów +---------------------------- -Możesz pominąć cudzysłowy dla prostych ciągów znaków: +W przypadku prostych ciągów znaków można pominąć cudzysłowy: ```latte -jako v PHP: {var $arr = ['hello', 'btn--default', '€']} +jak w PHP: {var $arr = ['hello', 'btn--default', '€']} -skrócony: {var $arr = [hello, btn--default, €]} +w skrócie: {var $arr = [hello, btn--default, €]} ``` -Proste ciągi znaków to takie, które składają się wyłącznie z liter, cyfr, podkreśleń, myślników i kropek. Nie mogą zaczynać się od cyfry i nie mogą zaczynać się ani kończyć myślnikiem. -Nie może składać się tylko z wielkich liter i podkreślników, bo wtedy jest traktowany jako stała (np. `PHP_VERSION`). -I nie może być sprzeczny z następującymi słowami kluczowymi: `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor`. +Proste ciągi znaków to te, które składają się wyłącznie z liter, cyfr, podkreśleń, myślników i kropek. Nie mogą zaczynać się cyfrą i nie mogą zaczynać się ani kończyć myślnikiem. Nie mogą składać się tylko z wielkich liter i podkreśleń, ponieważ wtedy są uważane za stałą (np. `PHP_VERSION`). I nie mogą kolidować ze słowami kluczowymi: `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor`. -Skrócony operator trójskładnikowy .[#toc-short-ternary-operator] ----------------------------------------------------------------- +Stałe +----- -Jeśli trzecia wartość operatora trójdzielnego jest pusta, można ją pominąć: +Ponieważ w przypadku prostych ciągów znaków można pomijać cudzysłowy, zalecamy dla odróżnienia zapisywanie stałych globalnych z ukośnikiem na początku: ```latte -jako v PHP: {$stock ? 'Skladem' : ''} - -zkráceně: {$stock ? 'Skladem'} +{if \PROJECT_ID === 1} ... {/if} ``` +Ten zapis jest całkowicie prawidłowy w samym PHP, ukośnik mówi, że stała znajduje się w globalnej przestrzeni nazw. + -Nowoczesna notacja klucza tablicowego .[#toc-modern-key-notation-in-the-array] ------------------------------------------------------------------------------- +Skrócony operator trójargumentowy +--------------------------------- -Klucze tablicowe mogą być zapisywane podobnie jak nazwane parametry podczas wywoływania funkcji: +Jeśli trzecia wartość operatora trójargumentowego jest pusta, można ją pominąć: ```latte -jako v PHP: {var $arr = ['one' => 'item 1', 'two' => 'item 2']} +jak w PHP: {$stock ? 'W magazynie' : ''} -moderně: {var $arr = [one: 'item 1', two: 'item 2']} +w skrócie: {$stock ? 'W magazynie'} ``` -Filtry .[#toc-filters] ----------------------- +Nowoczesny zapis kluczy w tablicy +--------------------------------- -Filtry można stosować do dowolnych wyrażeń, wystarczy całość zamknąć w nawiasach: +Klucze w tablicy można zapisywać podobnie jak nazwane parametry przy wywoływaniu funkcji: ```latte -{var $content = ($text|truncate: 30|upper)} +jak w PHP: {var $arr = ['one' => 'item 1', 'two' => 'item 2']} + +nowocześnie: {var $arr = [one: 'item 1', two: 'item 2']} ``` -Operator `in` .[#toc-operator-in] ---------------------------------- +Filtry +------ -Operator `in` może być użyty do zastąpienia funkcji `in_array()` Porównanie jest zawsze ścisłe: +Filtry można stosować do dowolnych wyrażeń, wystarczy całość ująć w nawiasy: ```latte -{* analog in_array($item, $items, true) *} -{if $item in $items} - ... -{/if} +{var $content = ($text|truncate: 30|upper)} ``` -.{data-version:2.9} -Opcjonalne łączenie łańcuchów z operatorem undefined-safe .[#toc-optional-chaining-with-undefined-safe-operator] ----------------------------------------------------------------------------------------------------------------- +Operator `in` +------------- -Operator undefined-safe `??->` jest podobny do operatora nullsafe `?->`, ale nie spowoduje błędu, jeśli zmienna, właściwość lub indeks w ogóle nie istnieje w tablicy. +Operatorem `in` można zastąpić funkcję `in_array()`. Porównanie jest zawsze ścisłe: ```latte -{$order??->id} +{* odpowiednik in_array($item, $items, true) *} +{if $item in $items} + ... +{/if} ``` -Mówimy, że jeśli `$order` istnieje i nie jest null, `$order->id` będzie wyjściem, ale jeśli `$order` jest null lub nie istnieje, przestanie oceniać i po prostu zwróci null. - -```latte -{$user??->address??->street} -// znamená cca isset($user) && isset($user->address) ? $user->address->street : null -``` +Okno historyczne +---------------- -Okno historyczne .[#toc-a-window-into-history] ----------------------------------------------- +Latte wprowadziło w trakcie swojej historii całą gamę cukierków syntaktycznych, które po kilku latach pojawiły się w samym PHP. Na przykład w Latte można było pisać tablice jako `[1, 2, 3]` zamiast `array(1, 2, 3)` lub używać operatora nullsafe `$obj?->foo` na długo przed tym, zanim stało się to możliwe w samym PHP. Latte wprowadziło również operator do rozpakowywania tablicy `(expand) $arr`, który jest odpowiednikiem dzisiejszego operatora `...$arr` z PHP. -Latte w ciągu swojej historii wymyśliło kilka syntaktycznych sugar daddies, które kilka lat później pojawiły się w samym PHP. Na przykład w Latte można było napisać pola typu `[1, 2, 3]` zamiast `array(1, 2, 3)` lub używać operatora nullsafe `$obj?->foo` na długo przed tym, jak było to możliwe w samym PHP. Latte wprowadził również operator rozszerzenia tablicy `(expand) $arr`, który jest odpowiednikiem dzisiejszego operatora `...$arr` z PHP. +Operator undefined-safe `??->`, który jest odpowiednikiem operatora nullsafe `?->`, ale nie wywołuje błędu, jeśli zmienna nie istnieje, powstał z powodów historycznych i dzisiaj zalecamy używanie standardowego operatora PHP `?->`. -Ograniczenia PHP w Latte .[#toc-php-limitations-in-latte] -========================================================= +Ograniczenia PHP w Latte +======================== -W Latte można pisać tylko wyrażenia PHP. To znaczy, nie można deklarować klas ani używać [struktur kontrolnych |https://www.php.net/manual/en/language.control-structures.php], takich jak `if`, `foreach`, `switch`, `return`, `try`, `throw` i innych, zamiast których Latte oferuje własne [znaczniki |tags]. -Nie można też używać [atrybutów |https://www.php.net/manual/en/language.attributes.php], [backticków |https://www.php.net/manual/en/language.operators.execution.php] czy [magicznych stałych |https://www.php.net/manual/en/language.constants.magic.php], bo to nie miałoby sensu. -Nie możesz też użyć `unset`, `echo`, `include`, `require`, `exit`, `eval`, ponieważ nie są to funkcje, lecz specjalne konstrukcje języka PHP, a więc nie są wyrażeniami. +W Latte można zapisywać tylko wyrażenia PHP. Zatem nie można używać instrukcji zakończonych średnikiem. Nie można deklarować klas ani używać [struktur sterujących |https://www.php.net/manual/en/language.control-structures.php], np. `if`, `foreach`, `switch`, `return`, `try`, `throw` i innych, zamiast których Latte oferuje swoje [znaczniki|tags]. Nie można również używać [atrybutów |https://www.php.net/manual/en/language.attributes.php], [backticks |https://www.php.net/manual/en/language.operators.execution.php] czy niektórych [stałych magicznych |https://www.php.net/manual/en/language.constants.magic.php]. Nie można używać również `unset`, `echo`, `include`, `require`, `exit`, `eval`, ponieważ nie są to funkcje, ale specjalne konstrukcje językowe PHP, i nie są to więc wyrażenia. Komentarze są obsługiwane tylko wieloliniowe `/* ... */`. -Można jednak obejść te ograniczenia aktywując [RawPhpExtension |develop#RawPhpExtension], które pozwala wtedy na użycie dowolnego kodu PHP w tagu `{php ...}` na ryzyko autora szablonu. +Te ograniczenia można jednak obejść, aktywując rozszerzenie [RawPhpExtension |develop#RawPhpExtension], dzięki któremu można następnie używać w znaczniku `{php ...}` dowolnego kodu PHP na odpowiedzialność autora szablonu. diff --git a/latte/pl/tags.texy b/latte/pl/tags.texy index ecde43eda1..797729f88b 100644 --- a/latte/pl/tags.texy +++ b/latte/pl/tags.texy @@ -1,168 +1,174 @@ -Tagi Latte -********** +Znaczniki Latte +*************** .[perex] -Przegląd i opis wszystkich znaczników systemu templatek Latte, które są domyślnie dostępne dla użytkownika. +Przegląd i opis wszystkich znaczników systemu szablonów Latte, które są standardowo dostępne. .[table-latte-tags language-latte] -|## Spis treści -| `{$var}`, `{...}` lub `{=...}` | [drukuje wymazaną zmienną lub wyrażenie |#Printing] -| `{$var\|filter}` | [drukuje z użyciem filtrów |#Filters] -| `{l}` lub `{r}` | drukuje znak `{` nebo `}` +|## Wyświetlanie +| `{$var}`, `{...}` lub `{=...}` | [wyświetla escapowaną zmienną lub wyrażenie |#Wyświetlanie] +| `{$var\|filter}` | [wyświetla z użyciem filtrów |#Filtry] +| `{l}` lub `{r}` | wyświetla znak `{` lub `}` .[table-latte-tags language-latte] |## Warunki -| `{if}`... `{elseif}`... `{else}`... `{/if}` | [warunek jeśli |#if-elseif-else] -| `{ifset}`... `{elseifset}`... `{/ifset}` | [ifset condition |#ifset-elseifset] -| `{ifchanged}`... `{/ifchanged}` | [test, czy nastąpiła zmiana |#ifchanged] -| `{switch}` `{case}` `{default}` `{/switch}` | [warunek przełączenia |#switch-case-default] +| `{if}` … `{elseif}` … `{else}` … `{/if}` | [warunek if |#if elseif else] +| `{ifset}` … `{elseifset}` … `{/ifset}` | [warunek ifset |#ifset elseifset] +| `{ifchanged}` … `{/ifchanged}` | [sprawdza, czy nastąpiła zmiana |#ifchanged] +| `{switch}` `{case}` `{default}` `{/switch}` | [warunek switch |#switch case default] +| `n:else` | [alternatywna zawartość dla warunków |#n:else] .[table-latte-tags language-latte] -|## Cykle -| `{foreach}`... `{/foreach}` | [foreach |#foreach] -| `{for}`... `{/for}` | [dla |#for] -| `{while}`... `{/while}` | [while |#while] -| `{continueIf $cond}` | [kontynuuj z następną iteracją |#continueIf-skipIf-breakIf] -| `{skipIf $cond}` | [pomiń iterację |#continueIf-skipIf-breakIf] -| `{breakIf $cond}` | [break loop |#continueIf-skipIf-breakIf] -| `{exitIf $cond}` | [wcześniejsze rozwiązanie umowy|#exitIf] -| `{first}`... `{/first}` | [czy to pierwsze przejście? |#first-last-sep] -| `{last}`... `{/last}` | [czy to ostatnie przejście? |#first-last-sep] -| `{sep}`... `{/sep}` | [czy będzie kolejna przepustka? |#first-last-sep] -| `{iterateWhile}`... `{/iterateWhile}` | [structured foreach |#iterateWhile] -| `$iterator` | [specjalna zmienna wewnątrz foreach |#iterator] +|## Pętle +| `{foreach}` … `{/foreach}` | [#foreach] +| `{for}` … `{/for}` | [#for] +| `{while}` … `{/while}` | [#while] +| `{continueIf $cond}` | [kontynuuj następną iterację |#continueIf skipIf breakIf] +| `{skipIf $cond}` | [pomiń iterację |#continueIf skipIf breakIf] +| `{breakIf $cond}` | [przerwanie pętli |#continueIf skipIf breakIf] +| `{exitIf $cond}` | [wczesne zakończenie |#exitIf] +| `{first}` … `{/first}` | [czy to pierwsze przejście? |#first last sep] +| `{last}` … `{/last}` | [czy to ostatnie przejście? |#first last sep] +| `{sep}` … `{/sep}` | [czy nastąpi kolejne przejście? |#first last sep] +| `{iterateWhile}` … `{/iterateWhile}` | [ustrukturyzowany foreach |#iterateWhile] +| `$iterator` | [specjalna zmienna wewnątrz foreach |#iterator] .[table-latte-tags language-latte] -|## Wstawianie innych szablonów -| `{include 'file.latte'}` | [załaduj szablon z innego pliku |#include] -| `{sandbox 'file.latte'}` | [załaduj szablon w trybie piaskownicy |#sandbox] +|## Dołączanie innych szablonów +| `{include 'file.latte'}` | [wczytuje szablon z innego pliku |#include] +| `{sandbox 'file.latte'}` | [wczytuje szablon w trybie sandbox |#sandbox] .[table-latte-tags language-latte] -|## Bloki, układy, dziedziczenie szablonów -| `{block}` | [anonimowy blok |#block] -| `{block blockname}` | [definiuje blok |template-inheritance#Blocks] -| `{define blockname}` | [definiuje blok do późniejszego wykorzystania |template-inheritance#Definitions] -| `{include blockname}` | [render block |template-inheritance#Printing-Blocks] -| `{include blockname from 'file.latte'}` | [renderuj blok z pliku |template-inheritance#Printing-Blocks] -| `{import 'file.latte'}` | [pobierz bloki z szablonu |template-inheritance#Horizontal-Reuse] -| `{layout 'file.latte'}` / `{extends}` | [definiuje plik układu |template-inheritance#Layout Inheritance] -| `{embed}`... `{/embed}` | [ładuje szablon lub blok i pozwala na nadpisywanie bloków |template-inheritance#Unit-Inheritance] -| `{ifset blockname}`... `{/ifset}` | [czy blok istnieje |template-inheritance#Checking-Block-Existence] +|## Bloki, layouty, dziedziczenie szablonów +| `{block}` | [blok anonimowy |#block] +| `{block blockname}` | [definiuje blok |template-inheritance#Bloki] +| `{define blockname}` | [definiuje blok do późniejszego użycia |template-inheritance#Definicje define] +| `{include blockname}` | [renderowanie bloku |template-inheritance#Renderowanie bloków] +| `{include blockname from 'file.latte'}` | [renderuje blok z pliku |template-inheritance#Renderowanie bloków] +| `{import 'file.latte'}` | [wczytuje bloki z szablonu |template-inheritance#Ponowne wykorzystanie poziome] +| `{layout 'file.latte'}` / `{extends}` | [określa plik z layoutem |template-inheritance#Dziedziczenie layoutu] +| `{embed}` … `{/embed}` | [wczytuje szablon lub blok i pozwala nadpisać bloki |template-inheritance#Dziedziczenie jednostkowe] +| `{ifset blockname}` … `{/ifset}` | [warunek, czy blok istnieje |template-inheritance#Sprawdzanie istnienia bloków] .[table-latte-tags language-latte] |## Obsługa wyjątków -| `{try}`... `{else}`... `{/try}` | [obsługa wyjątków |#try] -| `{rollback}` | [odrzucenie bloku prób |#rollback] +| `{try}` … `{else}` … `{/try}` | [przechwytywanie wyjątków |#try] +| `{rollback}` | [odrzucenie bloku try |#rollback] .[table-latte-tags language-latte] |## Zmienne -| `{var $foo = value}` | [utwórz zmienną |#var-default] -| `{default $foo = value}` | [utwórz zmienną, jeśli nie istnieje |#var-default] -| `{parameters}` | [deklaruje zmienne, typy i wartości domyślne |#parameters] -| `{capture}`... `{/capture}` | [zapisuje blok w zmiennej |#capture] +| `{var $foo = value}` | [tworzy zmienną |#var default] +| `{default $foo = value}` | [tworzy zmienną, jeśli nie istnieje |#var default] +| `{parameters}` | [deklaruje zmienne, typy i wartości domyślne |#parameters] +| `{capture}` … `{/capture}` | [przechwytuje blok do zmiennej |#capture] .[table-latte-tags language-latte] -|## Rodzajów -| `{varType}` | [deklaruje typ zmiennej |type-system#varType] -| `{varPrint}` | [sugeruje typy zmiennych |type-system#varPrint] -| `{templateType}` | [deklaruje typy zmiennych według klas |type-system#templateType] -| `{templatePrint}` | [klasa projektowa ze zmiennymi typami |type-system#templatePrint] +|## Typy +| `{varType}` | [deklaruje typ zmiennej |type-system#varType] +| `{varPrint}` | [proponuje typy zmiennych |type-system#varPrint] +| `{templateType}` | [deklaruje typy zmiennych według klasy |type-system#templateType] +| `{templatePrint}` | [proponuje klasę z typami zmiennych |type-system#templatePrint] .[table-latte-tags language-latte] -|Wykonywanie tłumaczeń -| `{_...}` | [drukuje tłumaczenie |#Translation] -| `{translate}`... `{/translate}` | [tłumaczy treść |#Translation] +|## Tłumaczenia +| `{_...}` | [wyświetla tłumaczenie |#Tłumaczenia] +| `{translate}` … `{/translate}` | [tłumaczy zawartość |#Tłumaczenia] .[table-latte-tags language-latte] |## Inne -| `{contentType}` | [przełączyć ucieczkę i wysłać nagłówek HTTP |#contentType] -| `{debugbreak}` | [Umieść punkt przerwania w kodzie |#debugbreak] -| `{do}` | [Wykonuje kod, ale nic nie drukuje |#do] -| `{dump}` | [Zrzuca zmienne do Tracy Bar |#dump] -| `{spaceless}`... `{/spaceless}` | [usuwa zbędne spacje |#spaceless] -| `{syntax}` | [zmienić składnię w czasie pracy |#Syntax] -| `{trace}` | [wyświetla ślad stosu |#trace] +| `{contentType}` | [przełącza escapowanie i wysyła nagłówek HTTP |#contentType] +| `{debugbreak}` | [umieszcza breakpoint w kodzie |#debugbreak] +| `{do}` | [wykonuje kod, ale nic nie wyświetla |#do] +| `{dump}` | [zrzuca zmienne do Tracy Bar |#dump] +| `{php}` | [wykonuje dowolny kod PHP |#php] +| `{spaceless}` … `{/spaceless}` | [usuwa zbędne spacje |#spaceless] +| `{syntax}` | [zmiana składni w locie |#syntax] +| `{trace}` | [wyświetla ślad stosu |#trace] .[table-latte-tags language-latte] |## Pomocnicy kodera HTML -| `n:class` | [dynamiczne zapisywanie atrybutu klasy HTML |#n-class] -| `n:attr` | [dynamiczne zapisywanie dowolnych atrybutów HTML |#n-attr] -| `n:tag` | [dynamiczny zapis nazwy elementu HTML |#n-tag] -| `n:ifcontent` | [Pomiń pusty tag HTML |#n-ifcontent] +| `n:class` | [dynamiczny zapis atrybutu HTML class |#n:class] +| `n:attr` | [dynamiczny zapis dowolnych atrybutów HTML |#n:attr] +| `n:tag` | [dynamiczny zapis nazwy elementu HTML |#n:tag] +| `n:ifcontent` | [pomija pusty znacznik HTML |#n:ifcontent] .[table-latte-tags language-latte] |## Dostępne tylko w Nette Framework -| `n:href` | [link używany w elementach HTML |application:creating-links#In-the-Presenter-Template] `` -| `{link}` | [drukuje link |application:creating-links#In-the-Presenter-Template] -| `{plink}` | [drukuje link do prezentera |application:creating-links#In-the-Presenter-Template] -| `{control}` | [renderuje komponent |application:components#Rendering] -| `{snippet}`... `{/snippet}` | [snippet, który może być wysłany przez AJAX |application:ajax#Tag-snippet] -| `{snippetArea}` | clipping cover -| `{cache}`... `{/cache}` | [buforuje część szablonu |caching:#Caching-in-Latte] +| `n:href` | [link używany w elementach HTML `` |application:creating-links#W szablonie presentera] +| `{link}` | [wyświetla link |application:creating-links#W szablonie presentera] +| `{plink}` | [wyświetla link do presentera |application:creating-links#W szablonie presentera] +| `{control}` | [renderuje komponent |application:components#Renderowanie] +| `{snippet}` … `{/snippet}` | [fragment, który można wysłać AJAXem |application:ajax#Snippety w Latte] +| `{snippetArea}` | [opakowanie dla fragmentów |application:ajax#Obszary snippetów] +| `{cache}` … `{/cache}` | [buforuje część szablonu |caching:#Buforowanie w Latte] .[table-latte-tags language-latte] |## Dostępne tylko z Nette Forms -| `{form}`... `{/form}` | [renderuje znaczniki formularzy |forms:rendering#form] -| `{label}`... `{/label}` | [renderuje etykietę elementu formularza |forms:rendering#label-input] -| `{input}` | [renderuje element formularza |forms:rendering#label-input] -| `{inputError}` | [drukuje komunikat o błędzie elementu formularza |forms:rendering#inputError] -| `n:name` | [animuje element formularza |forms:rendering#n:name] -| `{formPrint}` | [projekt kod latte dla formularza |forms:rendering#formPrint] -| `{formPrintClass}` | [zaprojektuj kod PHP dla klasy z danymi formularza |forms:in-presenter#Mapping-to-Classes] -| `{formContext}`... `{/formContext}` | [częściowy rysunek formy |forms:rendering#special-cases] +| `{form}` … `{/form}` | [renderuje znaczniki formularza |forms:rendering#form] +| `{label}` … `{/label}` | [renderuje etykietę elementu formularza |forms:rendering#label input] +| `{input}` | [renderuje element formularza |forms:rendering#label input] +| `{inputError}` | [wyświetla komunikat błędu elementu formularza |forms:rendering#inputError] +| `n:name` | [ożywia element formularza |forms:rendering#n:name] +| `{formContainer}` … `{/formContainer}` | [rysowanie kontenera formularza |forms:rendering#Przypadki specjalne] + +.[table-latte-tags language-latte] +|## Dostępne tylko z zasobami Nette +| `{asset}` | [renderuje zasób jako element HTML lub URL |assets:#asset] +| `{preload}` | [generuje wskazówki przed załadowaniem dla optymalizacji wydajności |assets:#preload] +| `n:asset` | [dodaje atrybuty zasobów do elementów HTML |assets:#n:asset] -Pisanie .[#toc-printing] -======================== +Wyświetlanie +============ `{$var}` `{...}` `{=...}` ------------------------- -W Latte znacznik `{=...}` służy do wyprowadzania na wyjście dowolnego wyrażenia. Latte zależy od wygody użytkownika, więc jeśli wyrażenie zaczyna się od zmiennej lub wywołania funkcji, nie ma potrzeby pisania znaku równości. Co w praktyce oznacza, że prawie nigdy nie musisz go pisać: +W Latte używa się znacznika `{=...}` do wyświetlania dowolnego wyrażenia na wyjściu. Latte dba o Twoją wygodę, więc jeśli wyrażenie zaczyna się od zmiennej lub wywołania funkcji, nie trzeba pisać znaku równości. Co w praktyce oznacza, że prawie nigdy nie trzeba go pisać: ```latte -Jméno: {$name} {$surname}
                                                                                            -Věk: {date('Y') - $birth}
                                                                                            +Imię: {$name} {$surname}
                                                                                            +Wiek: {date('Y') - $birth}
                                                                                            ``` -Jako wyrażenie możesz napisać wszystko, co znasz z PHP. Po prostu nie musisz uczyć się nowego języka. Na przykład: +Jako wyrażenie możesz zapisać cokolwiek, co znasz z PHP. Nie musisz po prostu uczyć się nowego języka. Na przykład: ```latte {='0' . ($num ?? $num * 3) . ', ' . PHP_VERSION} ``` -Proszę nie szukać sensu w poprzednim przykładzie, ale jeśli jakiś znajdziecie, to napiszcie do nas :-) +Proszę, nie szukaj w poprzednim przykładzie żadnego sensu, ale gdybyś tam jakiś znalazł, napisz do nas :-) -Ucieczka od wyjścia .[#toc-escaping-output] -------------------------------------------- +Escapowanie wyjścia +------------------- -Jakie jest najważniejsze zadanie systemu templatkowania? Unikanie dziur w zabezpieczeniach. I właśnie to robi Latte za każdym razem, gdy coś wyprowadzasz. Automatycznie z niego ucieka: +Jakie jest najważniejsze zadanie systemu szablonów? Zapobieganie lukom bezpieczeństwa. I dokładnie to robi Latte zawsze, gdy coś wyświetlasz. Automatycznie to escapuje: ```latte -

                                                                                            {='one < two'}

                                                                                            {* vypíše: '

                                                                                            one < two

                                                                                            ' *} +

                                                                                            {='one < two'}

                                                                                            {* wyświetla: '

                                                                                            one < two

                                                                                            ' *} ``` -Dokładnie mówiąc, Latte używa ucieczki kontekstowej, która jest tak ważną i unikalną rzeczą, że poświęciliśmy jej [osobny rozdział |safety-first#Context-Aware-Escaping]. +Aby być precyzyjnym, Latte używa escapowania kontekstowego, co jest tak ważną i unikalną rzeczą, że poświęciliśmy temu [osobny rozdział |safety-first#Escapowanie kontekstowe]. -A co jeśli wyprowadzasz treść zakodowaną w HTML z zaufanego źródła? Wtedy ucieczka może być łatwo wyłączona: +A co jeśli wyświetlasz zawartość zakodowaną w HTML z zaufanego źródła? Wtedy można łatwo wyłączyć escapowanie: ```latte {$trustedHtmlString|noescape} ``` .[warning] -Niewłaściwe wykorzystanie filtra `noescape` może prowadzić do luki XSS! Nigdy nie używaj go, jeśli nie jesteś **całkowicie pewien** co robisz i że łańcuch, który wyprowadzasz, pochodzi z zaufanego źródła. +Nieprawidłowe użycie filtra `noescape` może prowadzić do powstania podatności XSS! Nigdy go nie używaj, jeśli nie jesteś **całkowicie pewien**, co robisz, i że wyświetlany ciąg pochodzi z zaufanego źródła. -Zrzutka w JavaScript .[#toc-printing-in-javascript] ---------------------------------------------------- +Wyświetlanie w JavaScript +------------------------- -Ucieczka kontekstowa sprawia, że cudownie łatwo jest zrzucać zmienne wewnątrz JavaScript, a Latte zajmuje się odpowiednią ucieczką. +Dzięki escapowaniu kontekstowemu jest niezwykle łatwo wyświetlać zmienne wewnątrz JavaScriptu, a poprawne escapowanie załatwi Latte. -Zmienna nie musi być tylko ciągiem, obsługiwany jest dowolny typ danych, który następnie jest kodowany jako JSON: +Zmienna nie musi być tylko ciągiem znaków, obsługiwany jest dowolny typ danych, który następnie zostanie zakodowany jako JSON: ```latte {var $foo = ['hello', true, 1]} @@ -171,7 +177,7 @@ Zmienna nie musi być tylko ciągiem, obsługiwany jest dowolny typ danych, któ ``` -Generuje: +Wygeneruje: ```latte ``` -Jest to również powód, dla którego cudzysłowy **nie są napisane wokół zmiennej**: Latte doda je dla ciągów. A jeśli chcesz umieścić zmienną łańcuchową w innym ciągu, po prostu konkatenuj je: +To jest również powód, dla którego wokół zmiennej **nie pisze się cudzysłowów**: Latte doda je samo dla ciągów znaków. A jeśli chciałbyś wstawić zmienną ciągową do innego ciągu, po prostu je połącz: ```latte ``` -Filtry .[#toc-filtry] ---------------------- +Filtry +------ -Wymienione wyrażenie może być modyfikowane przez [filtr |syntax#Filters]. Na przykład przekonwertuj ciąg na duże litery i skróć go do maksymalnie 30 znaków: +Wyświetlone wyrażenie może być zmodyfikowane [filtrem |syntax#Filtry]. W ten sposób na przykład ciąg znaków przekształcimy na wielkie litery i skrócimy do maksymalnie 30 znaków: ```latte {$string|upper|truncate:30} ``` -W ten sposób można również stosować filtry do podczęści wyrażenia: +Filtry możesz stosować również do częściowych części wyrażenia w ten sposób: ```latte {$left . ($middle|upper) . $right} ``` -Warunki .[#toc-conditions] -========================== +Warunki +======= `{if}` `{elseif}` `{else}` -------------------------- -Warunki zachowują się tak samo jak ich odpowiedniki w PHP. Możesz też używać w nich tych samych wyrażeń, które znasz z PHP, nie musisz uczyć się nowego języka. +Warunki zachowują się tak samo, jak ich odpowiedniki w PHP. Możesz w nich używać tych samych wyrażeń, jakie znasz z PHP, nie musisz uczyć się nowego języka. ```latte {if $product->inStock > Stock::Minimum} - Skladem + W magazynie {elseif $product->isOnWay()} - Na cestě + W drodze {else} - Není dostupné + Niedostępny {/if} ``` -Jak każda para znaczników, para `{if} ... {/if}` może być również zapisana w formie [n:attribute |syntax#n-attributes], np: +Jak każdy znacznik parzysty, tak i parę `{if} ... {/if}` można zapisywać również w postaci [n:atrybutu |syntax#n:atrybuty], na przykład: ```latte -

                                                                                            Skladem {$count} kusů

                                                                                            +

                                                                                            W magazynie {$count} sztuk

                                                                                            ``` -Czy wiesz, że możesz przedrostek `tag-` do n:atrybutów ? Wtedy warunek będzie dotyczył tylko wyjścia znaczników HTML, a treść pomiędzy nimi będzie zawsze wyprowadzana: +Czy wiesz, że do n:atrybutów możesz dołączyć prefiks `tag-`? Wtedy warunek będzie odnosił się tylko do wyświetlenia znaczników HTML, a zawartość między nimi zostanie wyświetlona zawsze: ```latte
                                                                                            Hello -{* vypíše 'Hello' když $clickable je nepravdivá *} -{* vypíše 'Hello' když $clickable je pravdivá *} +{* wyświetla 'Hello' gdy $clickable jest fałszywe *} +{* wyświetla 'Hello' gdy $clickable jest prawdziwe *} ``` -Boże. +Boskie. + + +`n:else` .{data-version:3.0.11} +------------------------------- + +Jeśli warunek `{if} ... {/if}` zapiszesz w postaci [n:atrybutu |syntax#n:atrybuty], masz możliwość podania również alternatywnej gałęzi za pomocą `n:else`: + +```latte +W magazynie {$count} sztuk + +niedostępny +``` + +Atrybut `n:else` można użyć również w parze z [`n:ifset` |#ifset elseifset], [`n:foreach` |#foreach], [`n:try` |#try], [#`n:ifcontent`] i [`n:ifchanged` |#ifchanged]. `{/if $cond}` ------------- -Możesz być zaskoczony, że wyrażenie w warunku `{if}` może być również zawarte w znaczniku kończącym. Jest to przydatne w sytuacjach, gdy nie znamy wartości warunku w momencie jego otwarcia. Nazwijmy to decyzją odroczoną. +Być może Cię zaskoczy, że wyrażenie w warunku `{if}` można podać również w znaczniku zamykającym. Jest to przydatne w sytuacjach, gdy przy otwieraniu warunku jeszcze nie znamy jego wartości. Nazwijmy to odroczoną decyzją. -Na przykład zaczynamy zrzucać tabelę z rekordami z bazy danych i dopiero po zakończeniu zrzutu orientujemy się, że w bazie nie było żadnego rekordu. Więc stawiamy na nim warunek w tagu końcowym `{/if}`, a jeśli nie ma rekordu, nic nie jest wyrzucane: +Na przykład zaczynamy wyświetlać tabelę z rekordami z bazy danych i dopiero po zakończeniu wyświetlania zdajemy sobie sprawę, że w bazie danych nie było żadnego rekordu. Wtedy umieszczamy warunek w znaczniku końcowym `{/if}` i jeśli nie będzie żadnego rekordu, nic z tego się nie wyświetli: ```latte {if} -

                                                                                            Výpis řádků z databáze

                                                                                            +

                                                                                            Lista wierszy z bazy danych

                                                                                            {foreach $resultSet as $row} @@ -264,30 +284,30 @@ Na przykład zaczynamy zrzucać tabelę z rekordami z bazy danych i dopiero po z {/if isset($row)} ``` -Poręczne, prawda? +Sprytne, prawda? -Możesz również użyć `{else}`, ale nie `{elseif}`, w warunku odroczonym. +W odroczonym warunku można użyć również `{else}`, ale nie `{elseif}`. `{ifset}` `{elseifset}` ----------------------- .[note] -Zobacz także. [`{ifset block}` |template-inheritance#Checking-Block-Existence] +Zobacz także [`{ifset block}` |template-inheritance#Sprawdzanie istnienia bloków] -Użyj warunku `{ifset $var}`, aby określić, czy zmienna (lub wiele zmiennych) istnieje i ma wartość non-*null*. Właściwie to jest to samo, co `if (isset($var))` w PHP. Jak każdy znacznik par, może być również napisany jako [n:atrybut |syntax#n-attributes], więc weźmy to jako przykład: +Za pomocą warunku `{ifset $var}` sprawdzimy, czy zmienna (lub więcej zmiennych) istnieje i ma wartość inną niż *null*. Właściwie jest to to samo, co `if (isset($var))` w PHP. Jak każdy znacznik parzysty, można go zapisywać również w postaci [n:atrybutu |syntax#n:atrybuty], więc pokażmy to jako przykład: ```latte - + ``` -`{ifchanged}` .{data-version:2.9} ---------------------------------- +`{ifchanged}` +------------- `{ifchanged}` sprawdza, czy wartość zmiennej zmieniła się od ostatniej iteracji w pętli (foreach, for lub while). -Jeśli w znaczniku określisz jedną lub więcej zmiennych, sprawdzi on, czy któraś z nich uległa zmianie i odpowiednio wypisze jej zawartość. Na przykład, poniższy przykład wydrukuje pierwszą literę nazwy jako tytuł, gdy tylko zmieni się ona podczas wypisywania nazw: +Jeśli w znaczniku podamy jedną lub więcej zmiennych, będzie sprawdzał, czy któraś z nich się zmieniła, i zgodnie z tym wyświetli zawartość. Na przykład poniższy przykład wyświetli pierwszą literę imienia jako nagłówek za każdym razem, gdy podczas wyświetlania imion zmieni się: ```latte {foreach ($names|sort) as $name} @@ -297,7 +317,7 @@ Jeśli w znaczniku określisz jedną lub więcej zmiennych, sprawdzi on, czy kt {/foreach} ``` -Jednakże, jeśli nie podano żadnego argumentu, sprawdzi on wyrenderowaną zawartość względem jej poprzedniego stanu. Oznacza to, że w poprzednim przykładzie możemy bezpiecznie pominąć argument w znaczniku. Oczywiście możemy też użyć [n:attribute |syntax#n-attributes]: +Jeśli jednak nie podamy żadnego argumentu, będzie sprawdzana renderowana zawartość w porównaniu do jej poprzedniego stanu. Oznacza to, że w poprzednim przykładzie możemy spokojnie pominąć argument w znaczniku. I oczywiście możemy również użyć [n:atrybutu |syntax#n:atrybuty]: ```latte {foreach ($names|sort) as $name} @@ -307,49 +327,49 @@ Jednakże, jeśli nie podano żadnego argumentu, sprawdzi on wyrenderowaną zawa {/foreach} ``` -Wewnątrz `{ifchanged}` możemy również umieścić klauzulę `{else}`. +Wewnątrz `{ifchanged}` można również podać klauzulę `{else}`. `{switch}` `{case}` `{default}` ------------------------------- -Porównuje wartość z wieloma opcjami. Jest to podobne do przykładu warunkowego `switch`, który znasz z PHP. Latte jednak poprawia się w tym względzie: +Porównuje wartość z wieloma opcjami. Jest to odpowiednik instrukcji warunkowej `switch`, którą znasz z PHP. Jednak Latte ją ulepsza: - używa ścisłego porównania (`===`) -- nie musi `break` +- nie potrzebuje `break` -Jest to więc dokładny odpowiednik struktury `match`, z którą przychodzi PHP 8.0. +Jest to więc dokładny odpowiednik struktury `match`, która pojawiła się w PHP 8.0. ```latte {switch $transport} {case train} - Vlakem + Pociągiem {case plane} - Letecky + Samolotem {default} - Jinak + Inaczej {/switch} ``` -.{data-version:2.9} + Klauzula `{case}` może zawierać wiele wartości oddzielonych przecinkami: ```latte {switch $status} -{case $status::New}nová položka -{case $status::Sold, $status::Unknown}není dostupná +{case $status::New}nowy element +{case $status::Sold, $status::Unknown}niedostępny {/switch} ``` -Cykle .[#toc-loops] -=================== +Pętle +===== -W Latte znajdziesz wszystkie cykle, które znasz z PHP: foreach, for i while. +W Latte znajdziesz wszystkie pętle, które znasz z PHP: foreach, for i while. `{foreach}` ----------- -Pętlę piszemy w taki sam sposób jak w PHP: +Pętlę zapisujemy dokładnie tak samo jak w PHP: ```latte {foreach $langs as $code => $lang} @@ -357,11 +377,11 @@ Pętlę piszemy w taki sam sposób jak w PHP: {/foreach} ``` -Dodatkowo ma kilka fajnych funkcji, o których teraz porozmawiamy. +Ponadto ma kilka sprytnych ulepszeń, o których teraz opowiemy. -Na przykład Latte sprawdza, czy utworzone zmienne nie nadpisują przypadkiem zmiennych globalnych o tej samej nazwie. To ratuje sytuacje, w których liczysz na to, że `$lang` zawiera aktualny język strony, a nie zdajesz sobie sprawy, że `foreach $langs as $lang` nadpisał tę zmienną. +Latte na przykład sprawdza, czy utworzone zmienne przypadkowo nie nadpisują zmiennych globalnych o tej samej nazwie. Ratuję to sytuacje, gdy liczysz na to, że w `$lang` jest aktualny język strony, i nie zdajesz sobie sprawy, że `foreach $langs as $lang` Ci tę zmienną nadpisało. -Pętla foreach może być również napisana bardzo elegancko i ekonomicznie przy użyciu [atrybutu n:: |syntax#n-attributes] +Pętlę foreach można również bardzo elegancko i oszczędnie zapisać za pomocą [n:atrybutu |syntax#n:atrybuty]: ```latte
                                                                                              @@ -369,7 +389,7 @@ Pętla foreach może być również napisana bardzo elegancko i ekonomicznie prz
                                                                                            ``` -Czy wiesz, że możesz przedrostek n:attributes z `inner-`? Wtedy w pętli będzie powtarzane tylko wnętrze elementu: +Czy wiesz, że do n:atrybutów możesz dołączyć prefiks `inner-`? Wtedy w pętli będzie powtarzane tylko wnętrze elementu: ```latte
                                                                                            @@ -378,7 +398,7 @@ Czy wiesz, że możesz przedrostek n:attributes z `inner-`? Wtedy w pętli będz
                                                                                            ``` -Wydrukuje więc coś w rodzaju: +Więc wyświetli się coś takiego: ```latte
                                                                                            @@ -390,17 +410,17 @@ Wydrukuje więc coś w rodzaju: ``` -`{else}` .{data-version:2.9}{toc: foreach-else} ------------------------------------------------ +`{else}` .{toc: foreach-else} +----------------------------- -Wewnątrz pętli `foreach` może zawrzeć klauzulę `{else}`, której zawartość zostanie wyświetlona, jeśli pętla jest pusta: +Wewnątrz pętli `foreach` można podać klauzulę `{else}`, której zawartość zostanie wyświetlona, jeśli pętla jest pusta: ```latte
                                                                                              {foreach $people as $person}
                                                                                            • {$person->name}
                                                                                            • {else} -
                                                                                            • Litujeme, v tomto seznamu nejsou žádní uživatelé
                                                                                            • +
                                                                                            • Przepraszamy, na tej liście nie ma żadnych użytkowników
                                                                                            • {/foreach}
                                                                                            ``` @@ -409,15 +429,15 @@ Wewnątrz pętli `foreach` może zawrzeć klauzulę `{else}`, której zawartoś `$iterator` ----------- -Wewnątrz pętli `foreach`, Latte tworzy zmienną `$iterator`, którą możemy wykorzystać do uzyskania przydatnych informacji o bieżącej pętli: +Wewnątrz pętli `foreach` Latte tworzy zmienną `$iterator`, za pomocą której możemy sprawdzać przydatne informacje o trwającej pętli: -- `$iterator->first` - czy przechodzi przez cykl po raz pierwszy? -- `$iterator->last` - czy to już ostatnie przejście? -- `$iterator->counter` - ile przejść liczy się od jednego? -- `$iterator->counter0` - ile przejść jest liczonych od zera? .{data-version:2.9} -- `$iterator->odd` - czy to przepustka z numerem nieparzystym? -- `$iterator->even` - czy jest to przepustka o numerach parzystych? -- `$iterator->parent` - iterator otaczający bieżący .{data-version:2.9} +- `$iterator->first` - czy to pierwsze przejście przez pętlę? +- `$iterator->last` - czy to ostatnie przejście? +- `$iterator->counter` - które to przejście liczone od jednego? +- `$iterator->counter0` - które to przejście liczone od zera? +- `$iterator->odd` - czy to nieparzyste przejście? +- `$iterator->even` - czy to parzyste przejście? +- `$iterator->parent` - iterator otaczający bieżący - `$iterator->nextValue` - następny element w pętli - `$iterator->nextKey` - klucz następnego elementu w pętli @@ -435,20 +455,19 @@ Wewnątrz pętli `foreach`, Latte tworzy zmienną `$iterator`, którą możemy w {/foreach} ``` -Latte jest przebiegły i `$iterator->last` działa nie tylko dla tablic, ale także gdy pętla jest uruchamiana nad generycznym iteratorem, w którym liczba elementów nie jest z góry znana. +Latte jest sprytne i `$iterator->last` działa nie tylko dla tablic, ale również gdy pętla przebiega nad ogólnym iteratorem, gdzie liczba elementów nie jest znana z góry. `{first}` `{last}` `{sep}` -------------------------- -Znaczniki te mogą być użyte wewnątrz pętli `{foreach}`. renderowana jest zawartość strony `{first}`, jeśli jest to pierwsze przejście. -Zawartość strony `{last}` zostanie wyrenderowana ... czy można zgadnąć? Tak, jeśli to ostatnie przejście. Są to właściwie skróty dla `{if $iterator->first}` i `{if $iterator->last}`. +Te znaczniki można używać wewnątrz pętli `{foreach}`. Zawartość `{first}` zostanie wyrenderowana, jeśli jest to pierwsze przejście. Zawartość `{last}` zostanie wyrenderowana… czy zgadniesz? Tak, jeśli jest to ostatnie przejście. Są to właściwie skróty dla `{if $iterator->first}` i `{if $iterator->last}`. -Znaczniki mogą być również elegancko użyte jako [n:attributes |syntax#n-attributes]: +Znaczniki można również elegancko użyć jako [n:atrybut |syntax#n:atrybuty]: ```latte {foreach $rows as $row} - {first}

                                                                                            List of names

                                                                                            {/first} + {first}

                                                                                            Lista imion

                                                                                            {/first}

                                                                                            {$row->name}

                                                                                            @@ -456,21 +475,21 @@ Znaczniki mogą być również elegancko użyte jako [n:attributes |syntax#n-att {/foreach} ``` -Zawartość znacznika `{sep}` jest renderowana, jeśli przejście nie jest ostatnim, więc jest przydatna do renderowania delimitatorów, takich jak linie pomiędzy wyszczególnianymi elementami: +Zawartość znacznika `{sep}` zostanie wyrenderowana, jeśli przejście nie jest ostatnie, jest więc przydatna do renderowania separatorów, na przykład przecinków między wyświetlanymi elementami: ```latte {foreach $items as $item} {$item} {sep}, {/sep} {/foreach} ``` -To całkiem poręczne, prawda? +To całkiem praktyczne, prawda? -`{iterateWhile}` .{data-version:2.10} -------------------------------------- +`{iterateWhile}` +---------------- -Upraszcza grupowanie danych liniowych podczas iteracji w pętli foreach poprzez iterację w pętli zagnieżdżonej do momentu spełnienia warunku. [Przeczytaj instrukcję |cookbook/iteratewhile]. +Upraszcza grupowanie danych liniowych podczas iteracji w pętli foreach, wykonując iterację w zagnieżdżonej pętli, dopóki warunek jest spełniony. [Przeczytaj szczegółowy przewodnik|cookbook/grouping]. -Może również elegancko zastąpić `{first}` i `{last}` w powyższym przykładzie: +Może również elegancko zastąpić `{first}` i `{last}` w przykładzie powyżej: ```latte {foreach $rows as $row} @@ -487,19 +506,21 @@ Może również elegancko zastąpić `{first}` i `{last}` w powyższym przykład {/foreach} ``` +Zobacz także filtry [batch |filters#batch] i [group |filters#group]. + `{for}` ------- -Pętla jest napisana w taki sam sposób jak w PHP: +Pętlę zapisujemy dokładnie tak samo jak w PHP: ```latte {for $i = 0; $i < 10; $i++} - Položka {$i} + Element {$i} {/for} ``` -Znacznik ten może być również użyty jako [n:attribut |syntax#n-attributes]: +Znacznik można również użyć jako [n:atrybut |syntax#n:atrybuty]: ```latte

                                                                                            {$i}

                                                                                            @@ -509,7 +530,7 @@ Znacznik ten może być również użyty jako [n:attribut |syntax#n-attributes]: `{while}` --------- -Pętla znów jest napisana w taki sam sposób jak w PHP: +Pętlę znów zapisujemy dokładnie tak samo jak w PHP: ```latte {while $row = $result->fetch()} @@ -517,7 +538,7 @@ Pętla znów jest napisana w taki sam sposób jak w PHP: {/while} ``` -Albo jako [n:atrybut |syntax#n-attributes]: +Lub jako [n:atrybut |syntax#n:atrybuty]: ```latte @@ -525,7 +546,7 @@ Albo jako [n:atrybut |syntax#n-attributes]: ``` -Możliwy jest również wariant z warunkiem w znaczniku końcowym, który odpowiada pętli do-while w PHP: +Możliwa jest również wariacja z warunkiem w znaczniku końcowym, która odpowiada w PHP pętli do-while: ```latte {while} @@ -537,7 +558,7 @@ Możliwy jest również wariant z warunkiem w znaczniku końcowym, który odpowi `{continueIf}` `{skipIf}` `{breakIf}` ------------------------------------- -Do sterowania dowolnym cyklem można użyć znaczników `{continueIf ?}` i `{breakIf ?}`, które odpowiednio przejdą do następnego elementu i zakończą cykl po spełnieniu warunku: +Do sterowania dowolną pętlą można używać znaczników `{continueIf ?}` i `{breakIf ?}`, które przejdą do następnego elementu odpowiednio zakończą pętlę przy spełnieniu warunku: ```latte {foreach $rows as $row} @@ -547,8 +568,8 @@ Do sterowania dowolnym cyklem można użyć znaczników `{continueIf ?}` i `{bre {/foreach} ``` -.{data-version:2.9} -Znacznik `{skipIf}` jest bardzo podobny do `{continueIf}`, ale nie inkrementuje licznika `$iterator->counter`, więc jeśli wymienimy go z pominięciem niektórych pozycji, nie będzie dziur w numeracji. Również klauzula `{else}` jest renderowana, gdy pominiemy wszystkie wpisy. + +Znacznik `{skipIf}` jest bardzo podobny do `{continueIf}`, ale nie zwiększa licznika `$iterator->counter`, więc jeśli go wyświetlamy i jednocześnie pomijamy niektóre elementy, nie będzie dziur w numeracji. A także klauzula `{else}` zostanie wyrenderowana, jeśli pominiemy wszystkie elementy. ```latte
                                                                                              @@ -556,7 +577,7 @@ Znacznik `{skipIf}` jest bardzo podobny do `{continueIf}`, ale nie inkrementuje {skipIf $person->age < 18}
                                                                                            • {$iterator->counter}. {$person->name}
                                                                                            • {else} -
                                                                                            • Litujeme, v tomto seznamu nejsou žádní dospělí
                                                                                            • +
                                                                                            • Przepraszamy, na tej liście nie ma żadnych dorosłych
                                                                                            • {/foreach}
                                                                                            ``` @@ -565,72 +586,68 @@ Znacznik `{skipIf}` jest bardzo podobny do `{continueIf}`, ale nie inkrementuje `{exitIf}` .{data-version:3.0.5} -------------------------------- -Kończy renderowanie szablonu lub bloku, gdy spełniony jest warunek (early exit). +Zakończy renderowanie szablonu lub bloku przy spełnieniu warunku (tzw. "early exit"). ```latte {exitIf !$messages} -

                                                                                            Messages

                                                                                            +

                                                                                            Wiadomości

                                                                                            {$message}
                                                                                            ``` -Wstawianie szablonu .[#toc-including-templates] -=============================================== +Dołączanie szablonu +=================== `{include 'file.latte'}` .{toc: include} ---------------------------------------- .[note] -Zobacz także. [`{include block}` |template-inheritance#Printing-Blocks] +Zobacz także [`{include block}` |template-inheritance#Renderowanie bloków] -Znacznik `{include}` ładuje i renderuje podany szablon. Jeśli mówimy w języku naszego ulubionego języka PHP, to jest to coś w stylu: +Znacznik `{include}` wczytuje i renderuje podany szablon. Jeśli mówilibyśmy w języku naszego ulubionego języka PHP, jest to coś w rodzaju: ```php ``` -Szablony zagnieżdżone nie mają dostępu do aktywnych zmiennych kontekstowych, mają tylko dostęp do zmiennych globalnych. +Dołączone szablony nie mają dostępu do zmiennych aktywnego kontekstu, mają dostęp tylko do zmiennych globalnych. -Możesz przekazać inne zmienne w następujący sposób: +Zmienne do dołączonego szablonu możesz przekazywać w ten sposób: ```latte -{* od Latte 2.9 *} {include 'template.latte', foo: 'bar', id: 123} - -{* před Latte 2.9 *} -{include 'template.latte', foo => 'bar', id => 123} ``` -Nazwa szablonu może być dowolnym wyrażeniem PHP: +Nazwa szablonu może być dowolnym wyrażeniem w PHP: ```latte {include $someVar} {include $ajax ? 'ajax.latte' : 'not-ajax.latte'} ``` -Wstawioną treść można edytować za pomocą [filtrów |syntax#Filters]. Poniższy przykład usuwa cały HTML i dostosowuje wielkość liter: +Dołączoną zawartość można modyfikować za pomocą [filtrów |syntax#Filtry]. Poniższy przykład usunie cały HTML i zmodyfikuje wielkość liter: ```latte {include 'heading.latte' |stripHtml|capitalize} ``` -Domyślnie [dziedziczenie szablonów |template-inheritance] nie jest w tym przypadku skonfigurowane. Mimo, że możemy używać bloków w zalinkowanym szablonie, odpowiadające im bloki w szablonie, do którego jest zalinkowany, nie zostaną zastąpione. Pomyśl o dołączonych szablonach jako o oddzielnych zacienionych częściach stron lub modułów. To zachowanie można zmienić za pomocą modyfikatora `with blocks` (z wersji Latte 2.9.1): +Domyślnie [dziedziczenie szablonów|template-inheritance] w tym przypadku w żaden sposób nie figuruje. Chociaż w dołączonym szablonie możemy używać bloków, nie dojdzie do zastąpienia odpowiadających bloków w szablonie, do którego się dołącza. Myśl o dołączonych szablonach jako o oddzielnych, zacienionych częściach stron lub modułów. To zachowanie można zmienić za pomocą modyfikatora `with blocks`: ```latte {include 'template.latte' with blocks} ``` -Związek między nazwą pliku określoną w znaczniku a plikiem na dysku jest sprawą programu [ładującego |extending-latte#Loaders]. +Relacja między nazwą pliku podaną w znaczniku a plikiem na dysku jest kwestią [loadera|loaders]. -`{sandbox}` .{data-version:2.8} -------------------------------- +`{sandbox}` +----------- -W przypadku wstawiania szablonu utworzonego przez użytkownika końcowego należy rozważyć tryb piaskownicy (więcej informacji w [dokumentacji pias kownicy|sandbox]): +Przy dołączaniu szablonu utworzonego przez użytkownika końcowego powinieneś rozważyć tryb sandbox (więcej informacji w [dokumentacji sandbox |sandbox]): ```latte {sandbox 'untrusted.latte', level: 3, data: $menu} @@ -641,9 +658,9 @@ W przypadku wstawiania szablonu utworzonego przez użytkownika końcowego należ ========= .[note] -Zobacz także. [`{block name}` |template-inheritance#Blocks] +Zobacz także [`{block name}` |template-inheritance#Bloki] -Nienazwane bloki są używane jako sposób na zastosowanie [filtrów |syntax#Filters] do części szablonu. Na przykład filtr [paskowy |filters#strip] można zastosować w ten sposób, aby usunąć niepotrzebne spacje: +Bloki bez nazwy służą jako sposób na zastosowanie [filtrów |syntax#Filtry] do części szablonu. Na przykład w ten sposób można zastosować filtr [strip |filters#spaceless], który usunie zbędne spacje: ```latte {block|strip} @@ -654,16 +671,16 @@ Nienazwane bloki są używane jako sposób na zastosowanie [filtrów |syntax#Fil ``` -Zarządzanie wyjątkami .[#toc-exception-handling] -================================================ +Obsługa wyjątków +================ -`{try}` .{data-version:2.9} ---------------------------- +`{try}` +------- -Marka ta niezwykle ułatwia tworzenie solidnych szablonów. +Dzięki temu znacznikowi jest niezwykle łatwo tworzyć solidne szablony. -Jeśli podczas renderowania bloku `{try}` wystąpi wyjątek, cały blok zostanie wyrzucony, a renderowanie będzie kontynuowane po nim: +Jeśli podczas renderowania bloku `{try}` dojdzie do wyjątku, cały blok zostanie odrzucony, a renderowanie będzie kontynuowane po nim: ```latte {try} @@ -675,7 +692,7 @@ Jeśli podczas renderowania bloku `{try}` wystąpi wyjątek, cały blok zostanie {/try} ``` -Zawartość w opcjonalnej klauzuli `{else}` będzie renderowana tylko w przypadku wystąpienia wyjątku: +Zawartość w opcjonalnej klauzuli `{else}` zostanie wyrenderowana tylko wtedy, gdy wystąpi wyjątek: ```latte {try} @@ -685,11 +702,11 @@ Zawartość w opcjonalnej klauzuli `{else}` będzie renderowana tylko w przypadk {/foreach} {else} -

                                                                                            Je nám líto, nepodařilo se načíst tweety.

                                                                                            +

                                                                                            Przepraszamy, nie udało się załadować tweetów.

                                                                                            {/try} ``` -Znacznik ten może być również użyty jako [n:attribute |syntax#n-attributes]: +Znacznik można również użyć jako [n:atrybut |syntax#n:atrybuty]: ```latte
                                                                                              @@ -697,13 +714,13 @@ Znacznik ten może być również użyty jako [n:attribute |syntax#n-attributes]
                                                                                            ``` -Możliwe jest również zdefiniowanie niestandardowego [handler'a wyjątków |develop#Exception-Handler], na przykład dla logowania. +Możliwe jest również zdefiniowanie własnego [niestandardowego handlera wyjątków |develop#Obsługa wyjątków], na przykład w celu logowania. -`{rollback}` .{data-version:2.9} --------------------------------- +`{rollback}` +------------ -Blok `{try}` można również zatrzymać i pominąć ręcznie za pomocą `{rollback}`. W ten sposób nie trzeba wcześniej sprawdzać wszystkich danych wejściowych i można w trakcie procesu renderowania zdecydować, że obiekt w ogóle nie będzie renderowany: +Blok `{try}` można zatrzymać i pominąć również ręcznie za pomocą `{rollback}`. Dzięki temu nie musisz z góry sprawdzać wszystkich danych wejściowych, a dopiero podczas renderowania możesz zdecydować, że obiektu w ogóle nie chcesz renderować: ```latte {try} @@ -719,30 +736,30 @@ Blok `{try}` można również zatrzymać i pominąć ręcznie za pomocą `{rollb ``` -Zmienne .[#toc-variables] -========================= +Zmienne +======= `{var}` `{default}` ------------------- -Utwórz nowe zmienne w szablonie z tagiem `{var}`: +Nowe zmienne tworzymy w szablonie znacznikiem `{var}`: ```latte {var $name = 'John Smith'} {var $age = 27} -{* Wielokrotne deklaracje *} +{* Deklaracja wielokrotna *} {var $name = 'John Smith', $age = 27} ``` -Znacznik `{default}` działa podobnie, z tym że tworzy zmienne tylko wtedy, gdy nie istnieją: +Znacznik `{default}` działa podobnie, ale tworzy zmienne tylko wtedy, gdy nie istnieją. Jeśli zmienna już istnieje i zawiera wartość `null`, nie zostanie nadpisana: ```latte {default $lang = 'cs'} ``` -Od wersji Latte 2.7 można również określić [typy zmiennych |type-system]. Na razie mają one charakter informacyjny i Latte ich nie sprawdza. +Możesz podawać również [typy zmiennych|type-system]. Na razie są one informacyjne i Latte ich nie kontroluje. ```latte {var string $name = $article->getTitle()} @@ -750,10 +767,10 @@ Od wersji Latte 2.7 można również określić [typy zmiennych |type-system]. N ``` -`{parameters}` .{data-version:2.9} ----------------------------------- +`{parameters}` +-------------- -Podobnie jak funkcja deklaruje swoje parametry, szablon może zadeklarować swoje zmienne na początku: +Tak jak funkcja deklaruje swoje parametry, tak i szablon może na początku zadeklarować swoje zmienne: ```latte {parameters @@ -763,15 +780,15 @@ Podobnie jak funkcja deklaruje swoje parametry, szablon może zadeklarować swoj } ``` -Zmienne `$a` i `$b` bez zadeklarowanej wartości domyślnej automatycznie domyślnie przyjmują wartość `null`. Zadeklarowane typy są nadal informacyjne i nie są sprawdzane przez Latte. +Zmienne `$a` i `$b` bez podanej wartości domyślnej mają automatycznie wartość domyślną `null`. Zadeklarowane typy są na razie informacyjne i Latte ich nie kontroluje. -Zmienne inne niż zadeklarowane nie są przekazywane do szablonu. To jest coś innego niż tag `{default}`. +Inne zmienne niż zadeklarowane nie są przekazywane do szablonu. Tym różni się od znacznika `{default}`. `{capture}` ----------- -Przechwytuje dane wyjściowe do zmiennej: +Przechwytuje wyjście do zmiennej: ```latte {capture $var} @@ -783,7 +800,7 @@ Przechwytuje dane wyjściowe do zmiennej:

                                                                                            Captured: {$var}

                                                                                            ``` -Znacznik może być również zapisany jako [n:attribut |syntax#n-attributes]: +Znacznik można, podobnie jak każdy znacznik parzysty, zapisać również jako [n:atrybut |syntax#n:atrybuty]: ```latte
                                                                                              @@ -791,15 +808,17 @@ Znacznik może być również zapisany jako [n:attribut |syntax#n-attributes]:
                                                                                            ``` +Wyjście HTML zostanie zapisane do zmiennej `$var` w postaci obiektu `Latte\Runtime\Html`, aby [nie doszło do niepożądanego escapowania |develop#Wyłączanie automatycznego escapowania zmiennej] przy wyświetlaniu. -Inne .[#toc-others] -=================== + +Inne +==== `{contentType}` --------------- -Użyj znacznika, aby określić, jaki typ treści reprezentuje szablon. Dostępne opcje to: +Znacznikiem określisz, jaki typ zawartości reprezentuje szablon. Możliwości są: - `html` (typ domyślny) - `xml` @@ -808,9 +827,9 @@ Użyj znacznika, aby określić, jaki typ treści reprezentuje szablon. Dostępn - `calendar` (iCal) - `text` -Jego użycie jest ważne, ponieważ ustawia ucieczkę [kontekstową |safety-first#Context-Aware-Escaping] i tylko wtedy ucieczka może działać poprawnie. Na przykład `{contentType xml}` przełącza się na tryb XML, `{contentType text}` całkowicie wyłącza escaping. +Jego użycie jest ważne, ponieważ ustawia [escapowanie kontekstowe |safety-first#Escapowanie kontekstowe] i tylko tak może escapować poprawnie. Na przykład `{contentType xml}` przełącza do trybu XML, `{contentType text}` całkowicie wyłącza escapowanie. -Jeśli parametr jest pełnym typem MIME, takim jak `application/xml`, wyśle również nagłówek HTTP `Content-Type` do przeglądarki: +Jeśli parametrem jest pełnoprawny typ MIME, taki jak na przykład `application/xml`, to dodatkowo wysyła nagłówek HTTP `Content-Type` do przeglądarki: ```latte {contentType application/xml} @@ -829,46 +848,50 @@ Jeśli parametr jest pełnym typem MIME, takim jak `application/xml`, wyśle ró `{debugbreak}` -------------- -Wskazuje punkt, w którym program jest wstrzymywany i uruchamiany jest debugger, aby programista mógł zbadać środowisko uruchomieniowe i sprawdzić, czy program działa zgodnie z oczekiwaniami. Obsługuje [Xdebug |https://xdebug.org/]. Można dodać warunek określający, kiedy program powinien zostać zawieszony. +Oznacza miejsce, w którym nastąpi zatrzymanie wykonania programu i uruchomienie debuggera, aby programista mógł przeprowadzić inspekcję środowiska wykonawczego i sprawdzić, czy program działa zgodnie z oczekiwaniami. Obsługuje [Xdebug |https://xdebug.org/]. Można dodać warunek, który określa, kiedy program ma zostać zatrzymany. ```latte -{debugbreak} {* zawieszenie programu *} +{debugbreak} {* zatrzymuje program *} -{debugbreak $counter == 1} {* zawieszenie programu po spełnieniu warunku *} +{debugbreak $counter == 1} {* zatrzymuje program przy spełnieniu warunku *} ``` `{do}` ------ -Wykonuje kod i nic nie drukuje. +Wykonuje kod PHP i nic nie wyświetla. Podobnie jak w przypadku wszystkich innych znaczników, kodem PHP rozumie się jedno wyrażenie, zobacz [ograniczenia PHP |syntax#Ograniczenia PHP w Latte]. ```latte {do $num++} ``` -Latte 2.7 i wcześniejsze używały `{php}`. - `{dump}` -------- -Wypisuje zmienną lub bieżący kontekst. +Wyświetla zmienną lub aktualny kontekst. ```latte -{dump $name} {* Zrzuca zmienną $name *} +{dump $name} {* Wyświetla zmienną $name *} -{dump} {* Zrzuca wszystkie aktualnie zdefiniowane zmienne *} +{dump} {* Wyświetla wszystkie aktualnie zdefiniowane zmienne *} ``` .[caution] -Wymaga biblioteki [Tracy |tracy:]. +Wymaga biblioteki [Tracy|tracy:]. + + +`{php}` +------- + +Umożliwia wykonanie dowolnego kodu PHP. Znacznik należy aktywować za pomocą rozszerzenia [RawPhpExtension |develop#RawPhpExtension]. `{spaceless}` ------------- -Usuwa niepotrzebne białe spacje z wyjścia. Działa podobnie jak filtr [bezprzestrzenny |filters#spaceless]. +Usuwa zbędne białe znaki z wyjścia. Działa podobnie jak filtr [spaceless |filters#spaceless]. ```latte {spaceless} @@ -878,50 +901,50 @@ Usuwa niepotrzebne białe spacje z wyjścia. Działa podobnie jak filtr [bezprze {/spaceless} ``` -Generuje +Wygeneruje ```latte
                                                                                            • Hello
                                                                                            ``` -Znacznik może być również zapisany jako [n:attribut |syntax#n-attributes]. +Znacznik można również zapisać jako [n:atrybut |syntax#n:atrybuty]. `{syntax}` ---------- -Znaki latte nie muszą ograniczać się do prostych nawiasów złożonych. Możemy też wybrać inny delimiter, nawet w locie. Można to zrobić na stronie `{syntax …}`, gdzie jako parametr można podać: +Znaczniki Latte nie muszą być ograniczone tylko do pojedynczych nawiasów klamrowych. Możemy wybrać również inny ogranicznik, i to nawet w locie. Służy do tego `{syntax …}`, gdzie jako parametr można podać: -- podwójne: `{{...}}` +- double: `{{...}}` - off: całkowicie wyłącza przetwarzanie znaczników Latte -Używając n:attributes, możesz wyłączyć Latte dla tylko jednego bloku JavaScript: +Z wykorzystaniem n:atrybutów można wyłączyć Latte na przykład tylko dla jednego bloku JavaScriptu: ```latte ``` -Latte może być również używane bardzo wygodnie wewnątrz JavaScript, wystarczy unikać konstrukcji takich jak w tym przykładzie, gdzie litera następuje po `{`, zobacz [Latte wewnątrz JavaScript lub CSS |recipes#Latte-Inside-JavaScript-or-CSS]. +Latte można bardzo wygodnie używać również wewnątrz JavaScriptu, wystarczy unikać konstrukcji jak w tym przykładzie, gdy litera następuje bezpośrednio po `{`, zobacz [Latte wewnątrz JavaScriptu lub CSS |recipes#Latte wewnątrz JavaScriptu lub CSS]. -Jeśli wyłączysz Latte za pomocą `{syntax off}` (tj. za pomocą tagu, a nie atrybutu n:), będzie ona konsekwentnie ignorować wszystkie tagi aż do `{/syntax}` +Jeśli Latte wyłączysz za pomocą `{syntax off}` (tj. znacznikiem, a nie n:atrybutem), będzie konsekwentnie ignorować wszystkie znaczniki aż do `{/syntax}` -{trace} .{data-version:2.10} ----------------------------- +{trace} +------- -Rzuca wyjątek `Latte\RuntimeException`, którego ślad stosu jest zgodny z linią szablonów. To znaczy, zamiast wywoływania funkcji i metod, zawiera wywołania bloków i wstawianie szablonów. Jeśli używasz narzędzia do wyraźnego wyświetlania rzuconych wyjątków, takich jak [Tracy |tracy:], zobaczysz wyraźnie stos wywołań, w tym wszystkie przekazane argumenty. +Wyrzuca wyjątek `Latte\RuntimeException`, którego ślad stosu jest w duchu szablonów. Zatem zamiast wywołań funkcji i metod zawiera wywołania bloków i dołączania szablonów. Jeśli używasz narzędzia do przejrzystego wyświetlania wyrzuconych wyjątków, takiego jak na przykład [Tracy|tracy:], przejrzyście wyświetli się call stack wraz ze wszystkimi przekazywanymi argumentami. -Pomocnicy kodera HTML .[#toc-html-tag-helpers] -============================================== +Pomocnicy kodera HTML +===================== -n:klasa .[#toc-n-class] ------------------------ +n:class +------- -Dzięki `n:class` możesz w łatwy sposób wygenerować atrybut HTML `class` dokładnie taki, jaki chcesz. +Dzięki `n:class` bardzo łatwo wygenerujesz atrybut HTML `class` dokładnie według potrzeb. Przykład: potrzebuję, aby aktywny element miał klasę `active`: @@ -931,7 +954,7 @@ Przykład: potrzebuję, aby aktywny element miał klasę `active`: {/foreach} ``` -I dalej, pierwszy element powinien mieć klasy `first` i `main`: +A ponadto, aby pierwszy element miał klasy `first` i `main`: ```latte {foreach $items as $item} @@ -939,7 +962,7 @@ I dalej, pierwszy element powinien mieć klasy `first` i `main`: {/foreach} ``` -A wszystkie elementy powinny mieć klasę `list-item`: +I wszystkie elementy mają mieć klasę `list-item`: ```latte {foreach $items as $item} @@ -947,13 +970,13 @@ A wszystkie elementy powinny mieć klasę `list-item`: {/foreach} ``` -Zadziwiająco proste, prawda? +Niesamowicie proste, prawda? -n:attr .[#toc-n-attr] ---------------------- +n:attr +------ -Atrybut `n:attr` może generować dowolne atrybuty HTML z taką samą elegancją jak [n:class |#n-class]. +Atrybut `n:attr` potrafi z tą samą elegancją co [#n:class] generować dowolne atrybuty HTML. ```latte {foreach $data as $item} @@ -961,7 +984,7 @@ Atrybut `n:attr` może generować dowolne atrybuty HTML z taką samą elegancją {/foreach} ``` -W zależności od zwróconych wartości wyprowadza np: +W zależności od zwróconych wartości wyświetli np.: ```latte @@ -972,26 +995,28 @@ W zależności od zwróconych wartości wyprowadza np: ``` -n:tag .[#toc-n-tag] -------------------- +n:tag +----- -Atrybut `n:tag` może dynamicznie zmieniać nazwę elementu HTML. +Atrybut `n:tag` potrafi dynamicznie zmieniać nazwę elementu HTML. ```latte

                                                                                            {$title}

                                                                                            ``` -Jeśli jest to `$heading === null`, tag jest drukowany bez zmian. `

                                                                                            `. W przeciwnym razie nazwa elementu jest zmieniana na wartość zmiennej, więc jest drukowana dla `$heading === 'h3'`: +Jeśli `$heading === null`, wyświetli się bez zmian tag `

                                                                                            `. W przeciwnym razie zmieni się nazwa elementu na wartość zmiennej, więc dla `$heading === 'h3'` wyświetli się: ```latte

                                                                                            ...

                                                                                            ``` +Ponieważ Latte jest bezpiecznym systemem szablonów, sprawdza, czy nowa nazwa znacznika jest prawidłowa i nie zawiera żadnych niepożądanych lub szkodliwych wartości. -n:ifcontent .[#toc-n-ifcontent] -------------------------------- -Zapobiega wypisaniu pustego elementu HTML, tj. elementu zawierającego tylko spacje. +n:ifcontent +----------- + +Zapobiega temu, aby wyświetlił się pusty element HTML, tj. element nie zawierający niczego poza spacjami. ```latte
                                                                                            @@ -999,7 +1024,7 @@ Zapobiega wypisaniu pustego elementu HTML, tj. elementu zawierającego tylko spa
                                                                                            ``` -Drukuje w zależności od wartości zmiennej `$error`: +Wyświetli w zależności od wartości zmiennej `$error`: ```latte {* $error = '' *} @@ -1013,10 +1038,10 @@ Drukuje w zależności od wartości zmiennej `$error`: ``` -Tłumaczenia .[#toc-translation] -=============================== +Tłumaczenia +=========== -Aby tagi tłumaczące działały, należy [aktywować translator |develop#TranslatorExtension]. Możesz również użyć filtra do tłumaczenia [`translate` |filters#translate]. +Aby znaczniki do tłumaczenia działały, należy [aktywować translator |develop#TranslatorExtension]. Do tłumaczenia możesz również użyć filtra [`translate` |filters#translate]. `{_...}` @@ -1025,14 +1050,14 @@ Aby tagi tłumaczące działały, należy [aktywować translator |develop#Transl Tłumaczy wartości na inne języki. ```latte -{_'Košík'} +{_'Koszyk'} {_$item} ``` -Inne parametry mogą być przekazane do tłumacza: +Translatorowi można przekazywać również inne parametry: ```latte -{_'Košík', domain: order} +{_'Koszyk', domain: order} ``` @@ -1042,13 +1067,13 @@ Inne parametry mogą być przekazane do tłumacza: Tłumaczy części szablonu: ```latte -

                                                                                            {translate}Objednávka{/translate}

                                                                                            +

                                                                                            {translate}Zamówienie{/translate}

                                                                                            {translate domain: order}Lorem ipsum ...{/translate} ``` -Znacznik może być również zapisany jako [n:attribut |syntax#n-attributes], aby przetłumaczyć wnętrze elementu: +Znacznik można również zapisać jako [n:atrybut |syntax#n:atrybuty], do tłumaczenia wnętrza elementu: ```latte -

                                                                                            Objednávka

                                                                                            +

                                                                                            Zamówienie

                                                                                            ``` diff --git a/latte/pl/template-inheritance.texy b/latte/pl/template-inheritance.texy index 375f7ddd72..043fd8a80f 100644 --- a/latte/pl/template-inheritance.texy +++ b/latte/pl/template-inheritance.texy @@ -1,16 +1,16 @@ -Dziedziczenie i możliwość ponownego wykorzystania szablonów -*********************************************************** +Dziedziczenie i ponowne wykorzystanie szablonów +*********************************************** .[perex] -Mechanizmy ponownego wykorzystania szablonów i dziedziczenia zwiększą Twoją produktywność, ponieważ każdy szablon zawiera tylko swoją unikalną zawartość, a powtarzające się elementy i struktury zostaną ponownie wykorzystane. Wprowadzamy trzy pojęcia: [dziedziczenie układu |#layoutová dědičnost], [ponowne wykorzystanie poziome |#Horizontal-Reuse] oraz [dziedziczenie jednostkowe |#Unit-Inheritance]. +Mechanizmy ponownego wykorzystania i dziedziczenia szablonów zwiększą Twoją produktywność, ponieważ każdy szablon zawiera tylko swoją unikalną treść, a powtarzające się elementy i struktury są ponownie wykorzystywane. Przedstawiamy trzy koncepcje: [#Dziedziczenie layoutu], [#Ponowne wykorzystanie poziome] i [#Dziedziczenie jednostkowe]. -Koncepcja dziedziczenia szablonów Latte jest podobna do dziedziczenia klas w PHP. Definiujesz **szablon nadrzędny**, z którego inne **szablony podrzędne** mogą dziedziczyć i mogą nadpisywać części szablonu nadrzędnego. Działa to świetnie, gdy elementy mają wspólną strukturę. Brzmi skomplikowanie? Nie martw się, to bardzo proste. +Koncepcja dziedziczenia szablonów Latte jest podobna do dziedziczenia klas w PHP. Definiujesz **szablon nadrzędny**, z którego mogą dziedziczyć inne **szablony podrzędne** i mogą nadpisywać części szablonu nadrzędnego. Działa to świetnie, gdy elementy mają wspólną strukturę. Brzmi skomplikowanie? Nie martw się, to bardzo proste. -Dziedziczenie układu `{layout}` .{toc: Layout Inheritance} -========================================================== +Dziedziczenie layoutu `{layout}` .{toc:Dziedziczenie layoutu} +============================================================= -Przyjrzyjmy się na przykładzie dziedziczenia szablonu układu, czyli layoutu. Jest to szablon nadrzędny, który nazwiemy na przykład `layout.latte`, a który definiuje szkielet dokumentu HTML: +Spójrzmy na dziedziczenie szablonu układu, czyli layoutu, od razu na przykładzie. To jest szablon nadrzędny, który będziemy nazywać na przykład `layout.latte` i który definiuje szkielet dokumentu HTML: ```latte @@ -30,9 +30,9 @@ Przyjrzyjmy się na przykładzie dziedziczenia szablonu układu, czyli layoutu. ``` -Znaczniki `{block}` definiują trzy bloki, które mogą wypełnić szablony dziecięce. Znacznik block nie robi nic więcej niż oznajmia, że to miejsce może być nadpisane przez szablon dziecka poprzez zdefiniowanie własnego bloku o tej samej nazwie. +Tagi `{block}` definiują trzy bloki, które mogą wypełnić szablony podrzędne. Tag block robi tylko to, że informuje, że to miejsce może nadpisać szablon podrzędny, definiując własny blok o tej samej nazwie. -Szablon dziecka może wyglądać tak: +Szablon podrzędny może wyglądać tak: ```latte {layout 'layout.latte'} @@ -44,11 +44,11 @@ Szablon dziecka może wyglądać tak: {/block} ``` -Kluczem jest tutaj tag `{layout}`. Latte mówi, że ten szablon "rozszerza" inny szablon. Kiedy Latte renderuje ten szablon, najpierw znajduje rodzica - w tym przypadku `layout.latte`. +Kluczem jest tutaj tag `{layout}`. Mówi on Latte, że ten szablon „rozszerza” inny szablon. Kiedy Latte renderuje ten szablon, najpierw znajduje szablon nadrzędny - w tym przypadku `layout.latte`. -W tym momencie Latte zauważa trzy znaczniki bloków w `layout.latte` i zastępuje te bloki zawartością szablonu dziecka. Ponieważ szablon dziecka nie zdefiniował bloku *footer*, zamiast niego używana jest zawartość z szablonu rodzica. Zawartość w tagu `{block}` w szablonie nadrzędnym jest zawsze używana jako fallback. +W tym momencie Latte zauważa trzy tagi blokowe w `layout.latte` i zastępuje te bloki zawartością szablonu podrzędnego. Ponieważ szablon podrzędny nie zdefiniował bloku *footer*, używana jest zamiast tego zawartość z szablonu nadrzędnego. Zawartość w tagu `{block}` w szablonie nadrzędnym jest zawsze używana jako zapasowa. -Dane wyjściowe mogą wyglądać tak: +Wynik może wyglądać tak: ```latte @@ -68,7 +68,7 @@ Dane wyjściowe mogą wyglądać tak: ``` -W szablonie dziecka bloki mogą być umieszczane tylko na najwyższym poziomie lub wewnątrz innego bloku, tj : +W szablonie podrzędnym bloki mogą być umieszczone tylko na najwyższym poziomie lub wewnątrz innego bloku, tj.: ```latte {block content} @@ -76,7 +76,7 @@ W szablonie dziecka bloki mogą być umieszczane tylko na najwyższym poziomie l {/block} ``` -Ponadto blok zostanie zawsze utworzony niezależnie od tego, czy otaczający go warunek `{if}` oceniany jest jako prawdziwy czy fałszywy. Więc nawet jeśli nie wygląda, to ten szablon zdefiniuje blok. +Również blok zostanie zawsze utworzony bez względu na to, czy otaczający warunek `{if}` jest oceniany jako prawdziwy czy fałszywy. Więc nawet jeśli tak nie wygląda, ten szablon zdefiniuje blok. ```latte {if false} @@ -86,7 +86,7 @@ Ponadto blok zostanie zawsze utworzony niezależnie od tego, czy otaczający go {/if} ``` -Jeśli chcesz, aby wyjście wewnątrz bloku pojawiło się warunkowo, użyj zamiast tego następującego: +Jeśli chcesz, aby wyjście wewnątrz bloku wyświetlało się warunkowo, użyj zamiast tego następującego kodu: ```latte {block head} @@ -96,7 +96,7 @@ Jeśli chcesz, aby wyjście wewnątrz bloku pojawiło się warunkowo, użyj zami {/block} ``` -Rozmieść bloki w szablonie dziecka przed renderowaniem szablonu układu, abyś mógł użyć ich do zdefiniowania zmiennych, takich jak `{var $foo = bar}` i propagować dane do całego łańcucha dziedziczenia: +Przestrzeń poza blokami w szablonie podrzędnym jest wykonywana przed renderowaniem szablonu layoutu, więc możesz jej użyć do definiowania zmiennych jak `{var $foo = bar}` i do propagowania danych w całym łańcuchu dziedziczenia: ```latte {layout 'layout.latte'} @@ -106,51 +106,50 @@ Rozmieść bloki w szablonie dziecka przed renderowaniem szablonu układu, abyś ``` -Dziedziczenie wielopoziomowe .[#toc-multilevel-inheritance] ------------------------------------------------------------ -Możesz użyć tylu poziomów dziedziczenia, ile potrzebujesz. Powszechnym sposobem korzystania z dziedziczenia układu jest następujące trzypoziomowe podejście: +Dziedziczenie wielopoziomowe +---------------------------- +Możesz używać tylu poziomów dziedziczenia, ile potrzebujesz. Powszechnym sposobem użycia dziedziczenia layoutu jest następujące podejście trójpoziomowe: 1) Utwórz szablon `layout.latte`, który zawiera główny szkielet wyglądu strony. -2) Utwórz szablon `layout-SECTIONNAME.latte` dla każdej sekcji swojej strony. Na przykład: `layout-news.latte`, `layout-blog.latte`, itd. Wszystkie te szablony rozszerzają `layout.latte` i zawierają style & design specyficzne dla każdej sekcji. -3) Utwórz indywidualne szablony dla każdego typu strony, np. artykułu prasowego lub wpisu na blogu. Szablony te rozszerzają odpowiedni szablon sekcji. +2) Utwórz szablon `layout-SECTIONNAME.latte` dla każdej sekcji swojej strony. Na przykład `layout-news.latte`, `layout-blog.latte` itp. Wszystkie te szablony rozszerzają `layout.latte` i zawierają style i design specyficzne dla poszczególnych sekcji. +3) Utwórz indywidualne szablony dla każdego typu strony, na przykład artykułu prasowego lub wpisu na blogu. Te szablony rozszerzają odpowiedni szablon sekcji. -Dynamiczne dziedziczenie .[#toc-dynamic-layout-inheritance] ------------------------------------------------------------ -Zmienna lub dowolne wyrażenie PHP może być użyte jako nazwa szablonu nadrzędnego, więc dziedziczenie może zachowywać się dynamicznie: +Dynamiczne dziedziczenie +------------------------ +Jako nazwa szablonu nadrzędnego można użyć zmiennej lub dowolnego wyrażenia PHP, więc dziedziczenie może zachowywać się dynamicznie: ```latte {layout $standalone ? 'minimum.latte' : 'layout.latte'} ``` -Możesz również użyć interfejsu API Latte, aby [automatycznie |develop#Automatic-Layout-Lookup] wybrać szablon układu. +Możesz także użyć Latte API do [automatycznego |develop#Automatyczne wyszukiwanie layoutu] wyboru szablonu layoutu. -Porady .[#toc-tips] -------------------- -Oto kilka wskazówek dotyczących pracy z dziedziczeniem układów: +Wskazówki +--------- +Oto kilka wskazówek dotyczących pracy z dziedziczeniem layoutu: -- Jeśli używasz `{layout}` w szablonie , musi to być pierwszy tag szablonu w tym szablonie. +- Jeśli w szablonie użyjesz `{layout}`, musi to być pierwszy tag szablonu w tym szablonie. -- Układ może być [wyszukiwany automatycznie |develop#automatic-layout-lookup] (jak w [prezenterach |application:templates#search-for-templates]). W takim przypadku, jeśli szablon nie powinien mieć układu, wskaże to za pomocą znacznika `{layout none}`. +- Layout może być [wyszukiwany automatycznie |develop#Automatyczne wyszukiwanie layoutu] (jak na przykład w [presenterach |application:templates#Wyszukiwanie szablonów]). W takim przypadku, jeśli szablon nie ma mieć layoutu, informuje o tym tagiem `{layout none}`. -- Znacznik `{layout}` ma alias `{extends}`. +- Tag `{layout}` ma alias `{extends}`. -- Nazwa pliku układu zależy od programu [ładującego |extending-latte#Loaders]. +- Nazwa pliku layoutu zależy od [loadera |loaders]. -- Możesz mieć tyle bloków, ile chcesz. Zauważ, że szablony dzieci nie muszą definiować wszystkich bloków nadrzędnych, więc możesz wypełnić rozsądne domyślne ustawienia w kilku blokach, a następnie zdefiniować tylko te, których potrzebujesz później. +- Możesz mieć tyle bloków, ile chcesz. Pamiętaj, że szablony podrzędne nie muszą definiować wszystkich bloków nadrzędnych, więc możesz wypełnić rozsądne wartości domyślne w kilku blokach, a następnie zdefiniować tylko te, których potrzebujesz później. -Bloki `{block}` .{toc: Blocks} -============================== +Bloki `{block}` .{toc: Bloki} +============================= .[note] -Zobacz także anonimowość [`{block}` |tags#block] +Zobacz także anonimowy [`{block}` |tags#block] -Blok jest sposobem na zmianę sposobu renderowania pewnej części szablonu, ale nie ingeruje w logikę wokół niego. W poniższym przykładzie pokażemy jak działa blok, ale też jak nie działa: +Blok reprezentuje sposób na zmianę sposobu renderowania określonej części szablonu, ale w żaden sposób nie ingeruje w logikę wokół niego. W poniższym przykładzie pokażemy, jak blok działa, ale także jak nie działa: -```latte -{* parent.Latte *} +```latte .{file: parent.latte} {foreach $posts as $post} {block post}

                                                                                            {$post->title}

                                                                                            @@ -159,10 +158,9 @@ Blok jest sposobem na zmianę sposobu renderowania pewnej części szablonu, ale {/foreach} ``` -Jeśli wyrenderujesz ten szablon, wynik będzie dokładnie taki sam z i bez znaczników `{block}`. Bloki mają dostęp do zmiennych z zewnętrznych zakresów. Po prostu dają opcję do nadpisania przez szablon dziecka: +Jeśli wyrenderujesz ten szablon, wynik będzie dokładnie taki sam z tagami `{block}` i bez nich. Bloki mają dostęp do zmiennych z zewnętrznych zakresów. Dają tylko możliwość nadpisania przez szablon podrzędny: -```latte -{* child.Latte *} +```latte .{file: child.latte} {layout 'parent.Latte'} {block post} @@ -173,7 +171,7 @@ Jeśli wyrenderujesz ten szablon, wynik będzie dokładnie taki sam z i bez znac {/block} ``` -Teraz podczas renderowania szablonu dziecięcego pętla użyje bloku zdefiniowanego w szablonie dziecięcym `child.Latte` zamiast bloku zdefiniowanego w `parent.Latte`; działający szablon ma wtedy odpowiednik: +Teraz podczas renderowania szablonu podrzędnego pętla będzie używać bloku zdefiniowanego w szablonie podrzędnym `child.Latte` zamiast bloku zdefiniowanego w `parent.Latte`; uruchomiony szablon jest wtedy równoważny następującemu: ```latte {foreach $posts as $post} @@ -189,21 +187,21 @@ Jeśli jednak utworzymy nową zmienną wewnątrz nazwanego bloku lub zastąpimy ```latte {var $foo = 'foo'} {block post} - {do $foo = 'new value'} + {do $foo = 'nowa wartość'} {var $bar = 'bar'} {/block} -foo: {$foo} // prints: foo -bar: {$bar ?? 'not defined'} // prints: not defined +foo: {$foo} // drukuje: foo +bar: {$bar ?? 'niezdefiniowane'} // drukuje: niezdefiniowane ``` -Zawartość bloku może być modyfikowana za pomocą [filtrów |syntax#Filters]. Poniższy przykład usuwa cały HTML i zmienia wielkość liter: +Zawartość bloku można modyfikować za pomocą [filtrów |syntax#Filtry]. Poniższy przykład usuwa wszystkie znaczniki HTML i zmienia wielkość liter: ```latte {block title|stripHtml|capitalize}...{/block} ``` -Znacznik może być również zapisany jako [n:attribut |syntax#n-attributes]: +Tag można również zapisać jako [n:atrybut |syntax#n:atrybuty]: ```latte
                                                                                            @@ -212,10 +210,10 @@ Znacznik może być również zapisany jako [n:attribut |syntax#n-attributes]: ``` -Bloki lokalne .{data-version:2.9}[#toc-local-blocks] ----------------------------------------------------- +Bloki lokalne +------------- -Każdy blok nadpisuje zawartość bloku nadrzędnego o tej samej nazwie - z wyjątkiem bloków lokalnych. W klasach byłoby to coś w rodzaju prywatnych metod. Możesz więc stworzyć szablon bez obaw, że z powodu zgodności nazw bloków zostaną one nadpisane z innego szablonu. +Każdy blok nadpisuje zawartość bloku nadrzędnego o tej samej nazwie – z wyjątkiem bloków lokalnych. W klasach byłoby to coś w rodzaju metod prywatnych. Możesz więc tworzyć szablon bez obaw, że z powodu zgodności nazw bloków zostaną one nadpisane z innego szablonu. ```latte {block local helper} @@ -224,13 +222,13 @@ Każdy blok nadpisuje zawartość bloku nadrzędnego o tej samej nazwie - z wyj ``` -Rendering bloków `{include}` .{toc: Printing Blocks} ----------------------------------------------------- +Renderowanie bloków `{include}` .{toc: Renderowanie bloków} +----------------------------------------------------------- .[note] -Zobacz także. [`{include file}` |tags#include] +Zobacz także [`{include file}` |tags#include] -Aby wymienić blok w konkretnym miejscu, należy użyć znacznika `{include blockname}`: +Aby wyświetlić blok w określonym miejscu, użyj tagu `{include blockname}`: ```latte {block title}{/block} @@ -238,32 +236,28 @@ Aby wymienić blok w konkretnym miejscu, należy użyć znacznika `{include bloc

                                                                                            {include title}

                                                                                            ``` -Można również wylistować blok z innego szablonu: +Można również wyświetlić blok z innego szablonu: ```latte {include footer from 'main.latte'} ``` -Wyrenderowany blok nie ma dostępu do aktywnych zmiennych kontekstowych, z wyjątkiem sytuacji, gdy blok jest zdefiniowany w tym samym pliku, w którym został wstawiony. Ma jednak dostęp do zmiennych globalnych. +Renderowany blok nie ma dostępu do zmiennych aktywnego kontekstu, z wyjątkiem przypadków, gdy blok jest zdefiniowany w tym samym pliku, w którym jest dołączany. Ma jednak dostęp do zmiennych globalnych. -Możesz przekazać zmienne w następujący sposób: +Zmienne można przekazywać do bloku w ten sposób: ```latte -{* od Latte 2.9 *} {include footer, foo: bar, id: 123} - -{* před Latte 2.9 *} -{include footer, foo => bar, id => 123} ``` -Jako nazwy bloku możesz użyć zmiennej lub dowolnego wyrażenia PHP. W tym przypadku dodajemy słowo kluczowe `block` przed zmienną, aby w czasie kompilacji Latte wiedziało już, że chodzi o blok, a nie o [wstawienie szablonu |tags#include], którego nazwa również mogłaby znaleźć się w zmiennej: +Jako nazwa bloku można użyć zmiennej lub dowolnego wyrażenia w PHP. W takim przypadku przed zmienną dodajemy jeszcze słowo kluczowe `block`, aby już w czasie kompilacji Latte wiedziało, że chodzi o blok, a nie o [dołączanie szablonu |tags#include], którego nazwa również mogłaby być w zmiennej: ```latte {var $name = footer} {include block $name} ``` -Blok może być również renderowany wewnątrz siebie, co jest przydatne na przykład przy renderowaniu struktury drzewa: +Blok można renderować również wewnątrz siebie, co jest na przykład przydatne przy renderowaniu struktury drzewiastej: ```latte {define menu, $items} @@ -281,19 +275,19 @@ Blok może być również renderowany wewnątrz siebie, co jest przydatne na prz {/define} ``` -Zamiast `{include menu, ...}`, możemy napisać `{include this, ...}`, gdzie `this` oznacza bieżący blok. +Zamiast `{include menu, ...}` możemy wtedy napisać `{include this, ...}`, gdzie `this` oznacza bieżący blok. -Wyrenderowany blok może być modyfikowany za pomocą [filtrów |syntax#Filters]. Poniższy przykład usuwa cały HTML i zmienia wielkość liter: +Renderowany blok można modyfikować za pomocą [filtrów |syntax#Filtry]. Poniższy przykład usuwa wszystkie znaczniki HTML i zmienia wielkość liter: ```latte {include heading|stripHtml|capitalize} ``` -Blok macierzysty .[#toc-parent-block] -------------------------------------- +Blok nadrzędny +-------------- -Jeśli potrzebujesz zrzucić zawartość bloku z szablonu nadrzędnego, użyj `{include parent}`. Jest to przydatne, jeśli chcesz tylko dodać zawartość bloku nadrzędnego zamiast całkowicie go nadpisywać. +Jeśli potrzebujesz wyświetlić zawartość bloku z szablonu nadrzędnego, użyj `{include parent}`. Jest to przydatne, jeśli chcesz tylko uzupełnić zawartość bloku nadrzędnego zamiast jego całkowitego nadpisania. ```latte {block footer} @@ -304,32 +298,31 @@ Jeśli potrzebujesz zrzucić zawartość bloku z szablonu nadrzędnego, użyj `{ ``` -Definicja: `{define}` .{toc: Definitions} ------------------------------------------ +Definicje `{define}` +-------------------- -Oprócz bloków, w Latte są też "definicje". W normalnych językach programowania przyrównalibyśmy je do funkcji. Są one przydatne do ponownego wykorzystania fragmentów szablonów, aby nie powtarzać się. +Oprócz bloków istnieją w Latte również „definicje”. W zwykłych językach programowania porównalibyśmy je do funkcji. Są przydatne do ponownego wykorzystania fragmentów szablonu, aby się nie powtarzać. -Latte stara się zachować prostotę, więc zasadniczo definicje są takie same jak bloki, a **wszystko, co powiedziano o blokach, odnosi się również do definicji**. Od bloków różnią się tylko trzema sposobami: +Latte stara się robić rzeczy prosto, więc w zasadzie definicje są takie same jak bloki i **wszystko, co powiedziano o blokach, dotyczy również definicji**. Różnią się od bloków tym, że: -1) potrafią przyjąć argumenty -2) nie mogą mieć [filtrów |syntax#Filters] -3) są one zamknięte w znacznikach `{define}`, a zawartość wewnątrz tych znaczników nie jest wysyłana na wyjście, dopóki ich nie wstawisz. Dzięki temu można je tworzyć w dowolnym miejscu: +1) są zamknięte w tagach `{define}` +2) renderują się dopiero, gdy je dołączysz przez `{include}` +3) można im definiować parametry podobnie jak funkcjom w PHP ```latte -{block foo}

                                                                                            Hello

                                                                                            {/block} -{* prints:

                                                                                            Hello

                                                                                            *} +{block foo}

                                                                                            Witaj

                                                                                            {/block} +{* drukuje:

                                                                                            Witaj

                                                                                            *} -{define bar}

                                                                                            World

                                                                                            {/define} -{* prints nothing *} +{define bar}

                                                                                            Świecie

                                                                                            {/define} +{* nic nie drukuje *} {include bar} -{* prints:

                                                                                            World

                                                                                            *} +{* drukuje:

                                                                                            Świecie

                                                                                            *} ``` -Wyobraź sobie, że masz ogólny szablon pomocniczy, który określa, jak renderować formularze HTML za pomocą definicji: +Wyobraź sobie, że masz szablon pomocniczy z kolekcją definicji, jak rysować formularze HTML. -```latte -{* forms.latte *} +```latte .{file: forms.latte} {define input, $name, $value, $type = 'text'} {/define} @@ -339,38 +332,36 @@ Wyobraź sobie, że masz ogólny szablon pomocniczy, który określa, jak render {/define} ``` -Argumenty są zawsze opcjonalne z wartością domyślną `null`, chyba że określono wartość domyślną (tutaj `text` jest wartością domyślną dla `$type`, działającą od wersji Latte 2.9.1). Od wersji Latte 2.7 można również deklarować typy parametrów: `{define input, string $name, ...}`. +Argumenty są zawsze opcjonalne z domyślną wartością `null`, chyba że podano wartość domyślną (tutaj `'text'` jest wartością domyślną dla `$type`). Można również zadeklarować typy parametrów: `{define input, string $name, ...}`. -Definicje nie mają dostępu do aktywnych zmiennych kontekstowych, ale mają dostęp do zmiennych globalnych. - -Wstawia się je w [taki sam sposób jak blok |#Printing-Blocks]: +Szablon z definicjami ładujemy za pomocą [`{import}` |#Ponowne wykorzystanie poziome]. Same definicje renderują się [w ten sam sposób co bloki |#Renderowanie bloków]: ```latte

                                                                                            {include input, 'password', null, 'password'}

                                                                                            {include textarea, 'comment'}

                                                                                            ``` +Definicje nie mają dostępu do zmiennych aktywnego kontekstu, ale mają dostęp do zmiennych globalnych. -Dynamiczne nazwy bloków .[#toc-dynamic-block-names] ---------------------------------------------------- -Latte pozwala na dużą elastyczność w definiowaniu bloków, ponieważ nazwa bloku może być dowolnym wyrażeniem PHP. W tym przykładzie zdefiniowano trzy bloki o nazwach `hi-Peter`, `hi-John`, oraz `hi-Mary`: +Dynamiczne nazwy bloków +----------------------- -```latte -{* parent.latte *} +Latte pozwala na dużą elastyczność w definiowaniu bloków, ponieważ nazwa bloku może być dowolnym wyrażeniem PHP. Ten przykład definiuje trzy bloki o nazwach `hi-Peter`, `hi-John` i `hi-Mary`: + +```latte .{file: parent.latte} {foreach [Peter, John, Mary] as $name} {block "hi-$name"}Hi, I am {$name}.{/block} {/foreach} ``` -Możemy wtedy przedefiniować tylko jeden blok w szablonie dziecka, np: +W szablonie podrzędnym możemy wtedy przedefiniować na przykład tylko jeden blok: -```latte -{* child.latte *} +```latte .{file: child.latte} {block hi-John}Hello. I am {$name}.{/block} ``` -Więc wyjście będzie wyglądać tak: +Więc wynik będzie wyglądał tak: ```latte Hi, I am Peter. @@ -379,13 +370,13 @@ Hi, I am Mary. ``` -Sprawdź, czy istnieją bloki `{ifset}` .{toc: Checking Block Existence} ----------------------------------------------------------------------- +Sprawdzanie istnienia bloków `{ifset}` .{toc: Sprawdzanie istnienia bloków} +--------------------------------------------------------------------------- .[note] -Zobacz także. [`{ifset $var}` |tags#ifset-elseifset] +Zobacz także [`{ifset $var}` |tags#ifset elseifset] -Użyj testu `{ifset blockname}`, aby sprawdzić, czy blok (lub wiele bloków) istnieje w bieżącym kontekście: +Za pomocą testu `{ifset blockname}` sprawdzamy, czy w bieżącym kontekście blok (lub więcej bloków) istnieje: ```latte {ifset footer} @@ -397,7 +388,7 @@ Użyj testu `{ifset blockname}`, aby sprawdzić, czy blok (lub wiele bloków) is {/ifset} ``` -Nazwa bloku może być zmienną lub dowolnym wyrażeniem PHP. W tym przypadku dodajemy słowo kluczowe `block` przed zmienną, aby wyjaśnić, że nie jest to test na istnienie [zmiennych |tags#ifset-elseifset]: +Jako nazwa bloku można użyć zmiennej lub dowolnego wyrażenia w PHP. W takim przypadku przed zmienną dodajemy jeszcze słowo kluczowe `block`, aby było jasne, że nie chodzi o test istnienia [zmiennych |tags#ifset elseifset]: ```latte {ifset block $name} @@ -405,71 +396,69 @@ Nazwa bloku może być zmienną lub dowolnym wyrażeniem PHP. W tym przypadku do {/ifset} ``` +Istnienie bloków sprawdza również funkcja [`hasBlock()` |functions#hasBlock]: -Porady .[#toc-tips] -------------------- -Kilka wskazówek dotyczących pracy z klockami: - -- Ostatni blok najwyższego poziomu nie musi mieć znacznika zamykającego (blok kończy się na końcu dokumentu). Upraszcza to pisanie szablonów dzieci, które zawierają jeden blok główny. +```latte +{if hasBlock(header) || hasBlock(footer)} + ... +{/if} +``` -- Dla lepszej czytelności można zawrzeć nazwę bloku w znaczniku `{/block}`, na przykład `{/block footer}`. Nazwa ta musi jednak odpowiadać nazwie bloku. W większych szablonach ta technika pomaga określić, które znaczniki blokowe zamykają. -- Nie można bezpośrednio zdefiniować wielu znaczników blokowych o tej samej nazwie w tym samym szablonie. Można to jednak osiągnąć poprzez zastosowanie [dynamicznych nazw bloków |#Dynamic-Block-Names]. +Wskazówki +--------- +Kilka wskazówek dotyczących pracy z blokami: -- Możesz użyć [n:attributes |syntax#n-attributes] do zdefiniowania bloków jako `

                                                                                            Welcome to my awesome homepage

                                                                                            ` +- Ostatni blok najwyższego poziomu nie musi mieć tagu zamykającego (blok kończy się końcem dokumentu). To upraszcza pisanie szablonów podrzędnych, które zawierają jeden główny blok. -- Możesz również użyć bloków bez nazw tylko do zastosowania [filtrów |syntax#Filters]: `{block|strip} hello {/block}` +- Dla lepszej czytelności możesz podać nazwę bloku w tagu `{/block}`, na przykład `{/block footer}`. Nazwa musi jednak zgadzać się z nazwą bloku. W większych szablonach ta technika pomoże ci ustalić, które tagi bloku się zamykają. +- W tym samym szablonie nie możesz bezpośrednio zdefiniować więcej tagów bloków o tej samej nazwie. Można to jednak osiągnąć za pomocą [dynamicznych nazw bloków |#Dynamiczne nazwy bloków]. -Ponowne wykorzystanie poziome `{import}` .{toc: Horizontal Reuse} -================================================================= +- Możesz użyć [n:atrybutów |syntax#n:atrybuty] do definiowania bloków jak `

                                                                                            Welcome to my awesome homepage

                                                                                            ` -Poziome ponowne użycie jest trzecim mechanizmem wielokrotnego użycia i dziedziczenia w Latte. Pozwala na wczytywanie bloków z innych szablonów. Jest to podobne do tworzenia pliku PHP z funkcjami pomocniczymi. +- Bloki można również używać bez nazw tylko do zastosowania [filtrów |syntax#Filtry]: `{block|strip} hello {/block}` -Chociaż dziedziczenie układów jest jedną z najpotężniejszych funkcji Latte, jest ono ograniczone do prostego dziedziczenia; szablon może rozszerzać tylko jeden inny szablon. To ograniczenie sprawia, że dziedziczenie układu jest łatwe do zrozumienia i łatwe do debugowania: -```latte -{layout 'layout.latte'} +Ponowne wykorzystanie poziome `{import}` .{toc: Ponowne wykorzystanie poziome} +============================================================================== -{block title}...{/block} -{block content}...{/block} -``` +Ponowne wykorzystanie poziome jest w Latte trzecim mechanizmem ponownego wykorzystania i dziedziczenia. Umożliwia ładowanie bloków z innych szablonów. Jest to podobne do sytuacji, gdy w PHP tworzymy plik z funkcjami pomocniczymi, który następnie ładujemy za pomocą `require`. -Ponowne wykorzystanie poziome jest sposobem na osiągnięcie wielokrotnego dziedziczenia, ale bez złożoności: +Chociaż dziedziczenie layoutu szablonu jest jedną z najpotężniejszych funkcji Latte, jest ograniczone do prostego dziedziczenia - szablon może rozszerzać tylko jeden inny szablon. Ponowne wykorzystanie poziome jest sposobem na osiągnięcie wielokrotnego dziedziczenia. -```latte -{layout 'layout.latte'} +Miejmy plik z definicjami bloków: -{import 'blocks.latte'} +```latte .{file: blocks.latte} +{block sidebar}...{/block} -{block title}...{/block} -{block content}...{/block} +{block menu}...{/block} ``` -Polecenie `{import}` nakazuje Latte zaimportować do bieżącego szablonu wszystkie bloki i [definicje |#Definitions] zdefiniowane w `blocks.latte`. +Za pomocą polecenia `{import}` importujemy wszystkie bloki i [Definicje |#Definicje define] zdefiniowane w `blocks.latte` do innego szablonu: -```latte -{* blocks.latte *} +```latte .{file: child.latte} +{import 'blocks.latte'} -{block sidebar}...{/block} +{* teraz można używać bloków sidebar i menu *} ``` -W tym przykładzie polecenie `{import}` importuje do głównego szablonu blok `sidebar`. +Jeśli bloki importujesz w szablonie nadrzędnym (tj. użyjesz `{import}` w `layout.latte`), bloki będą dostępne również we wszystkich szablonach podrzędnych, co jest bardzo praktyczne. -Importowany szablon nie może [rozszerzać |#Layoutová dědičnost] innego szablonu, a jego ciało powinno być puste. Jednak importowany szablon może importować inne szablony. +Szablon przeznaczony do importowania (np. `blocks.latte`) nie może [rozszerzać |#Dziedziczenie layoutu] innego szablonu, tj. używać `{layout}`. Może jednak importować inne szablony. -Znacznik `{import}` powinien być pierwszym znacznikiem szablonu po `{layout}`. Nazwa szablonu może być dowolnym wyrażeniem PHP: +Tag `{import}` powinien być pierwszym tagiem szablonu po `{layout}`. Nazwa szablonu może być dowolnym wyrażeniem PHP: ```latte {import $ajax ? 'ajax.latte' : 'not-ajax.latte'} ``` -W szablonie możesz użyć dowolnej ilości oświadczeń `{import}`. Jeśli dwa importowane szablony definiują ten sam blok, wygrywa pierwszy. Jednak szablon główny ma najwyższy priorytet i może zastąpić każdy zaimportowany blok. +W szablonie możesz użyć tylu poleceń `{import}`, ile chcesz. Jeśli dwa importowane szablony definiują ten sam blok, wygrywa pierwszy z nich. Najwyższy priorytet ma jednak główny szablon, który może nadpisać dowolny importowany blok. -Do wszystkich nadrzędnych bloków można uzyskać dostęp sekwencyjny, wstawiając je jako [blok |#Parent-Block] nadrzędny: +Zawartość nadpisanych bloków można zachować, wstawiając blok tak samo, jak wstawia się [#blok nadrzędny]: ```latte -{layout 'base.latte'} +{layout 'layout.latte'} {import 'blocks.latte'} @@ -481,17 +470,17 @@ Do wszystkich nadrzędnych bloków można uzyskać dostęp sekwencyjny, wstawiaj {block content}...{/block} ``` -W tym przykładzie `{include parent}` wywołuje blok `sidebar` z szablonu `blocks.latte`. +W tym przykładzie `{include parent}` wywoła blok `sidebar` z szablonu `blocks.latte`. -Dziedziczenie jednostek `{embed}` .{toc: Unit Inheritance}{data-version:2.9} -============================================================================ +Dziedziczenie jednostkowe `{embed}` .{toc: Dziedziczenie jednostkowe} +===================================================================== -Dziedziczenie jednostek rozszerza ideę dziedziczenia układu na poziom fragmentów treści. Podczas gdy dziedziczenie układu działa z "szkieletem dokumentu", który jest animowany przez szablony dzieci, dziedziczenie jednostek pozwala na tworzenie szkieletów dla mniejszych jednostek treści i ponowne wykorzystanie ich gdziekolwiek chcesz. +Dziedziczenie jednostkowe rozszerza ideę dziedziczenia layoutu na poziom fragmentów treści. Podczas gdy dziedziczenie layoutu pracuje ze „szkieletem dokumentu”, który ożywiają szablony podrzędne, dziedziczenie jednostkowe pozwala tworzyć szkielety dla mniejszych jednostek treści i ponownie je wykorzystywać gdziekolwiek chcesz. -W dziedziczeniu jednostek kluczem jest znacznik `{embed}`. Łączy w sobie zachowanie `{include}` i `{layout}`. Pozwala na wstawienie treści innego szablonu lub bloku i opcjonalnie przekazanie zmiennych, jak w przypadku `{include}`. Pozwala również na nadpisanie dowolnego bloku zdefiniowanego wewnątrz wstawionego szablonu, tak jak w przypadku użycia `{layout}`. +W dziedziczeniu jednostkowym kluczem jest tag `{embed}`. Łączy zachowanie `{include}` i `{layout}`. Umożliwia osadzenie zawartości innego szablonu lub bloku i opcjonalnie przekazanie zmiennych, tak jak w przypadku `{include}`. Umożliwia również nadpisanie dowolnego bloku zdefiniowanego wewnątrz osadzonego szablonu, tak jak przy użyciu `{layout}`. -Na przykład używamy elementu accordion. Przyjrzyjmy się szkieletowi elementu zapisanego w szablonie `collapsible.latte`: +Na przykład użyjemy elementu akordeon. Spójrzmy na szkielet elementu zapisany w szablonie `collapsible.latte`: ```latte
                                                                                            @@ -505,14 +494,14 @@ Na przykład używamy elementu accordion. Przyjrzyjmy się szkieletowi elementu
                                                                                            ``` -Znaczniki `{block}` definiują dwa bloki, które szablony dziecięce mogą wypełnić. Tak, jak w przypadku szablonu nadrzędnego w dziedziczeniu układu. Można też zobaczyć zmienną `$modifierClass`. +Tagi `{block}` definiują dwa bloki, które mogą wypełnić szablony podrzędne. Tak, jak w przypadku szablonu nadrzędnego w dziedziczeniu layoutu. Widzisz również zmienną `$modifierClass`. -Wykorzystajmy nasz element w szablonie. Tu z pomocą przychodzi `{embed}`. Jest to niezwykle potężny znacznik, który pozwala nam robić różne rzeczy: wstawiać zawartość elementu szablonu, dodawać do niego zmienne oraz dodawać bloki z niestandardowym HTML: +Użyjmy naszego elementu w szablonie. Tutaj do gry wchodzi `{embed}`. Jest to niezwykle potężny tag, który pozwala nam robić wszystko: osadzić zawartość szablonu elementu, dodać do niego zmienne i dodać do niego bloki z własnym HTML: ```latte {embed 'collapsible.latte', modifierClass: my-style} {block title} - Hello World + Witaj Świecie {/block} {block content} @@ -522,12 +511,12 @@ Wykorzystajmy nasz element w szablonie. Tu z pomocą przychodzi `{embed}`. Jest {/embed} ``` -Dane wyjściowe mogą wyglądać tak: +Wynik może wyglądać tak: ```latte

                                                                                            - Hello World + Witaj Świecie

                                                                                            @@ -537,7 +526,7 @@ Dane wyjściowe mogą wyglądać tak:
                                                                                            ``` -Bloki wewnątrz osadzonych znaczników tworzą osobną warstwę niezależną od pozostałych bloków. Dlatego mogą mieć taką samą nazwę jak blok poza wstawką i nie są w żaden sposób dotknięte. Używając znacznika [include |#Printing-Blocks] wewnątrz znaczników `{embed}`, można wstawiać bloki utworzone tutaj, bloki z szablonu osadzonego (które *nie są* [lokalne |#Local-Blocks]) oraz bloki z szablonu głównego, które *są* lokalne. Można również [importować bloki |#Horizontal-Reuse] z innych plików: +Bloki wewnątrz osadzonych tagów tworzą oddzielną warstwę niezależną od innych bloków. Dlatego mogą mieć taką samą nazwę jak blok poza osadzeniem i nie są w żaden sposób przez niego wpływane. Za pomocą tagu [include |#Renderowanie bloków] wewnątrz tagów `{embed}` możesz wstawić bloki tutaj utworzone, bloki z osadzonego szablonu (które *nie są* [lokalne |#Bloki lokalne]) oraz bloki z głównego szablonu, które z kolei *są* lokalne. Możesz również [importować bloki |#Ponowne wykorzystanie poziome] z innych plików: ```latte {block outer}…{/block} @@ -551,16 +540,16 @@ Bloki wewnątrz osadzonych znaczników tworzą osobną warstwę niezależną od {block title} {include inner} {* działa, blok jest zdefiniowany wewnątrz embed *} {include hello} {* działa, blok jest lokalny w tym szablonie *} - {include content} {* działa, blok jest zdefiniowany w szablonie osadzonym *} - {include aBlockDefinedInImportedTemplate} {* works *} + {include content} {* działa, blok jest zdefiniowany w osadzonym szablonie *} + {include aBlockDefinedInImportedTemplate} {* działa *} {include outer} {* nie działa! - blok jest w warstwie zewnętrznej *} {/block} {/embed} ``` -Szablony wbudowane nie mają dostępu do aktywnych zmiennych kontekstowych, ale mają dostęp do zmiennych globalnych. +Osadzone szablony nie mają dostępu do zmiennych aktywnego kontekstu, ale mają dostęp do zmiennych globalnych. -Nie tylko szablony, ale także inne bloki można wstawiać za pomocą `{embed}`, więc poprzedni przykład można było napisać w ten sposób: .{data-version:2.10} +Za pomocą `{embed}` można osadzać nie tylko szablony, ale także inne bloki, a zatem poprzedni przykład można by zapisać w ten sposób: ```latte {define collapsible} @@ -575,23 +564,23 @@ Nie tylko szablony, ale także inne bloki można wstawiać za pomocą `{embed}`, {embed collapsible, modifierClass: my-style} {block title} - Hello World + Witaj Świecie {/block} ... {/embed} ``` -Jeśli do `{embed}` przekazujemy wyrażenie i nie jest jasne, czy chodzi o nazwę bloku czy pliku, dodajemy słowo kluczowe `block` lub `file`: +Jeśli do `{embed}` przekażemy wyrażenie i nie jest jasne, czy chodzi o nazwę bloku czy pliku, dodajemy słowo kluczowe `block` lub `file`: ```latte {embed block $name} ... {/embed} ``` -Przypadki użycia .[#toc-use-cases] -================================== +Przypadki użycia +================ -W Latte istnieją różne rodzaje dziedziczenia i ponownego wykorzystania kodu. Dla jasności podsumujmy główne pojęcia: +W Latte istnieją różne typy dziedziczenia i ponownego wykorzystania kodu. Podsumujmy główne koncepcje dla większej przejrzystości: `{include template}` @@ -698,7 +687,7 @@ W Latte istnieją różne rodzaje dziedziczenia i ponownego wykorzystania kodu. `{define}` ---------- -**Użytek**: Funkcja, do której przekazujemy zmienne i renderuje coś. +**Przypadek użycia**: Funkcje, którym przekazujemy zmienne i coś renderują. `form.latte` @@ -724,7 +713,7 @@ W Latte istnieją różne rodzaje dziedziczenia i ponownego wykorzystania kodu. `{embed}` --------- -**Przypadek użycia**: Wstaw `pagination.latte` do `product.table.latte` i `service.table.latte`. +**Przypadek użycia**: Osadzenie `pagination.latte` w `product.table.latte` i `service.table.latte`. `pagination.latte` diff --git a/latte/pl/type-system.texy b/latte/pl/type-system.texy index 52a9e5f99c..9f19e935d9 100644 --- a/latte/pl/type-system.texy +++ b/latte/pl/type-system.texy @@ -1,27 +1,27 @@ -System typu -*********** +System typów +************ -
                                                                                            +
                                                                                            -System typów jest kluczowy dla rozwoju solidnych aplikacji. Latte wprowadza obsługę typów również do szablonów. Wiedząc, jaki typ danych lub obiektów znajduje się w każdej zmiennej, może +System typów jest kluczowy dla rozwoju solidnych aplikacji. Latte wprowadza wsparcie dla typów również do szablonów. Dzięki temu, że wiemy, jaki typ danych lub obiektowy znajduje się w każdej zmiennej, może -- IDE do poprawnego szeptania (patrz [integracja |recipes#Editors-and-IDE]) -- analiza statyczna w celu wykrycia błędów +- IDE poprawnie podpowiadać (zobacz [integracja |recipes#Edytory i IDE]) +- analiza statyczna wykrywać błędy -Oba zasadniczo poprawiają jakość i wygodę rozwoju. +Oba te elementy w znaczący sposób zwiększają jakość i komfort rozwoju.
                                                                                            .[note] -Deklarowane typy są informacyjne i Latte nie kontroluje ich w tym czasie. +Zadeklarowane typy są informacyjne i Latte w tej chwili ich nie kontroluje. -Jak zacząć używać typów? Utwórz klasę szablonową, np. `CatalogTemplateParameters`, reprezentującą przekazywane parametry, ich typy i ewentualnie wartości domyślne: +Jak zacząć używać typów? Utwórz klasę szablonu, np. `CatalogTemplateParameters`, reprezentującą przekazywane parametry, ich typy i ewentualnie wartości domyślne: ```php class CatalogTemplateParameters { public function __construct( - public string $langs, + public string $lang, /** @var ProductEntity[] */ public array $products, public Address $address, @@ -35,19 +35,16 @@ $latte->render('template.latte', new CatalogTemplateParameters( )); ``` -Następnie na początku szablonu umieść tag `{templateType}` z pełną nazwą klasy (wraz z przestrzenią nazw). To definiuje, że szablon zawiera zmienne `$langs` i `$products` wraz z ich odpowiednimi typami. -Możesz określić typy zmiennych lokalnych używając znaczników [`{var}` |tags#var-default], `{varType}`, [`{define}` |template-inheritance#Definitions]. +A następnie na początku szablonu wstaw tag `{templateType}` z pełną nazwą klasy (włącznie z namespace). To definiuje, że w szablonie są zmienne `$lang` i `$products` wraz z odpowiednimi typami. Typy zmiennych lokalnych możesz podać za pomocą tagów [`{var}` |tags#var default], `{varType}`, [`{define}` |template-inheritance#Definicje define]. -Od tego momentu IDE może Ci prawidłowo szeptać. +Od tego momentu IDE może poprawnie podpowiadać. -Jak zaoszczędzić sobie pracy? Jak najprościej napisać klasę z parametrami szablonu lub tagu `{varType}`? Niech je wygenerują. -W tym celu istnieje para znaczników `{templatePrint}` i `{varPrint}`. -Jeśli umieścisz je w szablonie, zamiast zwykłego renderingu, zobaczysz projekt kodu klasy lub listę znaczników `{varType}`. Następnie wystarczy wybrać kod i skopiować go do projektu jednym kliknięciem. +Jak sobie ułatwić pracę? Jak najłatwiej napisać klasę z parametrami szablonu lub tagi `{varType}`? Pozwól je sobie wygenerować. Do tego służy para tagów `{templatePrint}` i `{varPrint}`. Jeśli umieścisz je w szablonie, zamiast zwykłego renderowania wyświetli się propozycja kodu klasy lub lista tagów `{varType}`. Kod wystarczy jednym kliknięciem zaznaczyć i skopiować do projektu. `{templateType}` ---------------- -Typy parametrów przekazywanych do szablonu są deklarowane za pomocą klasy: +Typy parametrów przekazywanych do szablonu deklarujemy za pomocą klasy: ```latte {templateType MyApp\CatalogTemplateParameters} @@ -56,7 +53,7 @@ Typy parametrów przekazywanych do szablonu są deklarowane za pomocą klasy: `{varType}` ----------- -Jak deklarować typy zmiennych? Aby to zrobić, użyj znaczników `{varType}` dla istniejących zmiennych, lub [`{var}` |tags#var-default]: +Jak zadeklarować typy zmiennych? Do tego służą tagi `{varType}` dla istniejących zmiennych lub [`{var}` |tags#var default]: ```latte {varType Nette\Security\User $user} @@ -66,11 +63,11 @@ Jak deklarować typy zmiennych? Aby to zrobić, użyj znaczników `{varType}` dl `{templatePrint}` ----------------- -Możesz również zlecić wygenerowanie klasy za pomocą znacznika `{templatePrint}` Jeśli umieścisz go na początku szablonu, zobaczysz projekt klasy zamiast normalnego renderingu. Kod może być następnie wybrany i skopiowany do projektu jednym kliknięciem. +Klasę możesz również wygenerować za pomocą tagu `{templatePrint}`. Jeśli umieścisz go na początku szablonu, zamiast zwykłego renderowania wyświetli się propozycja klasy. Kod wystarczy jednym kliknięciem zaznaczyć i skopiować do projektu. `{varPrint}` ------------ -Znacznik `{varPrint}` oszczędza czas przy wpisywaniu. Jeśli umieścisz go w szablonie, zobaczysz sugestię tagu `{varType}` dla zmiennych lokalnych zamiast normalnego renderowania. Możesz wtedy po prostu wybrać kod i skopiować go do szablonu jednym kliknięciem. +Tag `{varPrint}` oszczędzi ci czas na pisaniu. Jeśli umieścisz go w szablonie, zamiast zwykłego renderowania wyświetli się propozycja tagów `{varType}` dla zmiennych lokalnych. Kod wystarczy jednym kliknięciem zaznaczyć i skopiować do szablonu. -Sam `{varPrint}` wymienia tylko zmienne lokalne, które nie są parametrami szablonu. Jeśli chcesz wypisać wszystkie zmienne, użyj `{varPrint all}`. +Samo `{varPrint}` wypisuje tylko zmienne lokalne, które nie są parametrami szablonu. Jeśli chcesz wypisać wszystkie zmienne, użyj `{varPrint all}`. diff --git a/latte/pl/why-use.texy b/latte/pl/why-use.texy new file mode 100644 index 0000000000..c8aebe5ea0 --- /dev/null +++ b/latte/pl/why-use.texy @@ -0,0 +1,80 @@ +Dlaczego używać szablonów? +************************** + + +Dlaczego powinienem używać systemu szablonów w PHP? +--------------------------------------------------- + +Dlaczego używać systemu szablonów w PHP, skoro PHP samo w sobie jest językiem szablonów? + +Najpierw krótko przypomnijmy sobie historię tego języka, która jest pełna interesujących zwrotów akcji. Jednym z pierwszych języków programowania używanych do generowania stron HTML był język C. Wkrótce jednak okazało się, że jego użycie do tego celu jest niepraktyczne. Rasmus Lerdorf stworzył więc PHP, które ułatwiło generowanie dynamicznego HTML z językiem C na backendzie. PHP było więc pierwotnie zaprojektowane jako język szablonów, ale z czasem zyskało dodatkowe funkcje i stało się pełnoprawnym językiem programowania. + +Mimo to nadal działa również jako język szablonów. W pliku PHP może być zapisana strona HTML, w której za pomocą `` wypisuje się zmienne itp. + +Już na początku historii PHP powstał system szablonów Smarty, którego celem było ścisłe oddzielenie wyglądu (HTML/CSS) od logiki aplikacji. Czyli celowo dostarczał bardziej ograniczony język niż samo PHP, aby programista nie mógł na przykład wykonać zapytania do bazy danych z szablonu itp. Z drugiej strony stanowił dodatkową zależność w projektach, zwiększał ich złożoność, a programiści musieli uczyć się nowego języka Smarty. Taka korzyść była wątpliwa i nadal do szablonów używano zwykłego PHP. + +Z biegiem czasu systemy szablonów zaczęły stawać się użyteczne. Przyniosły koncepcję [dziedziczenia |template-inheritance], [trybu sandbox|sandbox] i szereg innych funkcji, które znacząco uprościły tworzenie szablonów w porównaniu do czystego PHP. Na pierwszy plan wysunął się temat bezpieczeństwa, istnienie [podatności takich jak XSS|safety-first] i konieczność [escapowania |#Czym jest escapowanie]. Systemy szablonów wprowadziły autoescapowanie, aby zniknęło ryzyko, że programista o tym zapomni i powstanie poważna luka bezpieczeństwa (za chwilę pokażemy, że ma to pewne pułapki). + +Korzyści płynące z systemów szablonów dzisiaj znacznie przewyższają koszty związane z ich wdrożeniem. Dlatego warto je stosować. + + +Dlaczego Latte jest lepsze niż Twig czy Blade? +---------------------------------------------- + +Powodów jest kilka – niektóre są przyjemne, a inne zasadniczo użyteczne. Latte to połączenie przyjemnego z pożytecznym. + +*Najpierw to przyjemne:* Latte ma tę samą [składnię co PHP |syntax#Latte rozumie PHP]. Różni się tylko zapis tagów, zamiast `` preferuje krótsze `{` i `}`. Oznacza to, że nie musisz uczyć się nowego języka. Koszty szkolenia są minimalne. A co najważniejsze, podczas rozwoju nie musisz ciągle "przełączać się" między językiem PHP a językiem szablonu, ponieważ oba są takie same. W przeciwieństwie do szablonów Twig, które używają języka Python, a programista musi przełączać się między dwoma różnymi językami. + +*A teraz powód niezwykle użyteczny*: Wszystkie systemy szablonów, takie jak Twig, Blade czy Smarty, w trakcie ewolucji wprowadziły ochronę przed XSS w postaci automatycznego [escapowania |#Czym jest escapowanie]. Dokładniej mówiąc, automatycznego wywoływania funkcji `htmlspecialchars()`. Twórcy Latte zdali sobie jednak sprawę, że to wcale nie jest właściwe rozwiązanie. Ponieważ w różnych miejscach dokumentu escapuje się na różne sposoby. Naiwne autoescapowanie jest niebezpieczną funkcją, ponieważ tworzy fałszywe poczucie bezpieczeństwa. + +Aby autoescapowanie było funkcjonalne i niezawodne, musi rozróżniać, w którym miejscu dokumentu dane są wypisywane (nazywamy je kontekstami) i zgodnie z nim wybierać funkcję escapującą. Czyli musi być [kontekstowo-sensytywne |safety-first#Escapowanie kontekstowe]. I to właśnie potrafi Latte. Rozumie HTML. Nie postrzega szablonu tylko jako ciągu znaków, ale rozumie, czym są tagi, atrybuty itp. Dlatego inaczej escapuje w tekście HTML, inaczej wewnątrz tagu HTML, inaczej wewnątrz JavaScriptu itd. + +Latte jest pierwszym i jedynym systemem szablonów w PHP, który ma kontekstowo czułe escapowanie. Stanowi więc jedyny naprawdę bezpieczny system szablonów. + +*I jeszcze jeden przyjemny powód*: Dzięki temu, że Latte rozumie HTML, oferuje inne bardzo przyjemne udogodnienia. Na przykład [n:atrybuty |syntax#n:atrybuty]. Lub zdolność [kontrolowania linków |safety-first#Sprawdzanie linków]. I wiele innych. + + +Czym jest escapowanie? +---------------------- + +Escapowanie to proces polegający na zastępowaniu znaków o specjalnym znaczeniu odpowiednimi sekwencjami podczas wstawiania jednego ciągu do drugiego, aby zapobiec niepożądanym zjawiskom lub błędom. Na przykład, gdy wstawiamy ciąg do tekstu HTML, w którym znak `<` ma specjalne znaczenie, ponieważ oznacza początek tagu, zastępujemy go odpowiednią sekwencją, czyli encją HTML `<`. Dzięki temu przeglądarka poprawnie wyświetli symbol `<`. + +Prostym przykładem escapowania bezpośrednio podczas pisania kodu w PHP jest wstawienie cudzysłowu do ciągu, przed którym piszemy odwrotny ukośnik. + +Szczegółowiej escapowanie omawiamy w rozdziale [Jak bronić się przed XSS |safety-first#Jak bronić się przed XSS]. + + +Czy w Latte można wykonać zapytanie do bazy danych z szablonu? +-------------------------------------------------------------- + +W szablonach można pracować z obiektami, które programista do nich przekaże. Jeśli więc programista chce, może przekazać do szablonu obiekt bazy danych i na nim wykonać zapytanie. Jeśli ma taki zamiar, nie ma powodu mu w tym przeszkadzać. + +Inna sytuacja ma miejsce, gdy chcesz dać możliwość edytowania szablonów klientom lub zewnętrznym koderom. W takim przypadku zdecydowanie nie chcesz, aby mieli dostęp do bazy danych. Oczywiście nie przekażesz szablonowi obiektu bazy danych, ale co jeśli można się do niej dostać przez inny obiekt? Rozwiązaniem jest [tryb sandbox|sandbox], który pozwala zdefiniować, które metody można wywoływać w szablonach. Dzięki temu nie musisz obawiać się naruszenia bezpieczeństwa. + + +Jakie są główne różnice między systemami szablonów takimi jak Latte, Twig i Blade? +---------------------------------------------------------------------------------- + +Różnice między systemami szablonów Latte, Twig i Blade polegają głównie na składni, zabezpieczeniach i sposobie integracji z frameworkami + +- Latte: używa składni języka PHP, co ułatwia naukę i używanie. Zapewnia najwyższą ochronę przed atakami XSS. +- Twig: używa składni języka Python, która znacznie różni się od PHP. Escapuje bez rozróżniania kontekstu. Jest dobrze zintegrowany z frameworkiem Symfony. +- Blade: używa mieszanki PHP i własnej składni. Escapuje bez rozróżniania kontekstu. Jest ściśle zintegrowany z funkcjami i ekosystemem Laravel. + + +Czy firmom opłaca się używać systemu szablonów? +----------------------------------------------- + +Przede wszystkim koszty związane ze szkoleniem, użytkowaniem i ogólną korzyścią znacznie różnią się w zależności od systemu. System szablonów Latte, dzięki temu, że używa składni PHP, bardzo upraszcza naukę dla programistów już zaznajomionych z tym językiem. Zwykle zajmuje kilka godzin, zanim programista wystarczająco zapozna się z Latte. Zmniejsza więc koszty szkolenia. Jednocześnie przyspiesza przyswajanie technologii i przede wszystkim efektywność w codziennym użytkowaniu. + +Ponadto Latte zapewnia wysoki poziom ochrony przed podatnością XSS dzięki unikalnej technologii kontekstowo czułego escapowania. Ta ochrona jest kluczowa dla zapewnienia bezpieczeństwa aplikacji internetowych i minimalizacji ryzyka ataków, które mogłyby zagrozić użytkownikom lub danym firmowym. Ochrona bezpieczeństwa aplikacji internetowych jest ważna również dla utrzymania dobrej reputacji firmy. Problemy z bezpieczeństwem mogą spowodować utratę zaufania ze strony klientów i zaszkodzić reputacji firmy na rynku. + +Użycie Latte zmniejsza również całkowite koszty rozwoju i utrzymania aplikacji, ułatwiając oba te procesy. Użycie systemu szablonów jest więc jednoznacznie opłacalne. + + +Czy Latte wpływa na wydajność aplikacji internetowych? +------------------------------------------------------ + +Chociaż szablony Latte są przetwarzane szybko, ten aspekt właściwie nie ma znaczenia. Powodem jest to, że parsowanie plików odbywa się tylko raz przy pierwszym wyświetleniu. Następnie są kompilowane do kodu PHP, zapisywane na dysku i uruchamiane przy każdym kolejnym żądaniu, bez konieczności ponownej kompilacji. + +Tak działa to w środowisku produkcyjnym. Podczas rozwoju szablony Latte są rekompilowane za każdym razem, gdy ich zawartość ulegnie zmianie, aby programista widział zawsze aktualną postać. diff --git a/latte/pt/@home.texy b/latte/pt/@home.texy index 1861042b34..0b8caac5dc 100644 --- a/latte/pt/@home.texy +++ b/latte/pt/@home.texy @@ -1 +1,2 @@ -{{maintitle: Latte - Os Modelos mais seguros e Verdadeiramente Intuitivos para PHP}} +{{maintitle: Latte – os templates mais seguros & verdadeiramente intuitivos para PHP}} +{{description: Latte é o sistema de templates mais seguro para PHP. Previne muitas vulnerabilidades de segurança. Você apreciará sua sintaxe intuitiva e muitos recursos úteis.}} diff --git a/latte/pt/@left-menu.texy b/latte/pt/@left-menu.texy index bc3950eae9..3dede14430 100644 --- a/latte/pt/@left-menu.texy +++ b/latte/pt/@left-menu.texy @@ -1,24 +1,24 @@ -- [Como Começar |Guide] -- Conceitos - - [Segurança em primeiro lugar |Safety First] - - [Herança do modelo |Template Inheritance] - - [Tipo Sistema |Type System] +- [Começando com Latte |guide] +- [Por que usar templates? |why-use] +- Conceitos ⚗️ + - [Segurança em primeiro lugar |safety-first] + - [Herança de Templates |Template Inheritance] + - [Sistema de Tipos |type-system] - [Sandbox |Sandbox] -- Para projetistas - - [Sintaxe |Syntax] - - [Etiquetas |Tags] - - [Filtros |Filters] - - [Funções |Functions] +- Para designers 🎨 + - [Sintaxe |syntax] + - [Tags |tags] + - [Filtros |filters] + - [Funções |functions] - [Dicas e truques |recipes] -- Para os desenvolvedores - - [Práticas para os desenvolvedores |develop] - - [Ampliação do Latte |Extending Latte] - - [Criando uma extensão |creating-extension] +- Para desenvolvedores 🧮 + - [Práticas de desenvolvimento |develop] + - [Estendendo o Latte |extending-latte] -- [Livro de receitas |cookbook/@home] - - [Migração de galho |cookbook/migration-from-twig] - - [... mais |cookbook/@home] +- [Guias e melhores práticas 💡|cookbook/@home] + - [Migração do Twig |cookbook/migration-from-twig] + - [… outros |cookbook/@home] - "Playground .[link-external]":https://fiddle.nette.org/latte/ .{padding-top:1em} diff --git a/latte/pt/@menu.texy b/latte/pt/@menu.texy index a1a5c6dfcc..894d36ea7a 100644 --- a/latte/pt/@menu.texy +++ b/latte/pt/@menu.texy @@ -1,12 +1,12 @@
                                                                                              -- [Início |@home] -- [Documentação |Guide] +- [Introdução |@home] +- [Documentação |guide] - "GitHub .[link-external]":https://github.com/nette/latte
                                                                                            diff --git a/latte/pt/@meta.texy b/latte/pt/@meta.texy new file mode 100644 index 0000000000..669a1b52fc --- /dev/null +++ b/latte/pt/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentação Latte}} diff --git a/latte/pt/compiler-passes.texy b/latte/pt/compiler-passes.texy new file mode 100644 index 0000000000..76ba694e41 --- /dev/null +++ b/latte/pt/compiler-passes.texy @@ -0,0 +1,555 @@ +Passos de Compilação +******************** + +.[perex] +Os passos de compilação fornecem um mecanismo poderoso para analisar e modificar templates Latte *depois* de serem analisados numa Árvore de Sintaxe Abstrata (AST) e *antes* da geração do código PHP final. Isto permite manipulação avançada de templates, otimizações, verificações de segurança (como o Sandbox) e recolha de informações sobre templates. Este guia irá orientá-lo na criação dos seus próprios passos de compilação. + + +O que é um passo de compilação? +=============================== + +Para entender o papel dos passos de compilação, consulte o [processo de compilação do Latte |custom-tags#Compreendendo o processo de compilação]. Como pode ver, os passos de compilação operam numa fase crucial, permitindo uma intervenção profunda entre a análise inicial e a saída final do código. + +No seu cerne, um passo de compilação é simplesmente um objeto PHP chamável (como uma função, método estático ou método de instância) que aceita um único argumento: o nó raiz da AST do template, que é sempre uma instância de `Latte\Compiler\Nodes\TemplateNode`. + +O objetivo principal de um passo de compilação é geralmente um ou ambos dos seguintes: + +- Análise: Percorrer a AST e recolher informações sobre o template (por exemplo, encontrar todos os blocos definidos, verificar o uso de tags específicas, garantir que certas restrições de segurança são cumpridas). +- Modificação: Alterar a estrutura da AST ou os atributos dos nós (por exemplo, adicionar automaticamente atributos HTML, otimizar certas combinações de tags, substituir tags obsoletas por novas, implementar regras do sandbox). + + +Registo +======= + +Os passos de compilação são registados usando o método [`getPasses()` da extensão |extending-latte#getPasses]. Este método retorna um array associativo onde as chaves são nomes de passos únicos (usados internamente e para ordenação) e os valores são objetos PHP chamáveis que implementam a lógica do passo. + +```php +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Extension; + +class MyExtension extends Extension +{ + public function getPasses(): array + { + return [ + 'modificationPass' => $this->modifyTemplateAst(...), + // ... outros passos ... + ]; + } + + public function modifyTemplateAst(TemplateNode $templateNode): void + { + // Implementação... + } +} +``` + +Os passos registados pelas extensões base do Latte e pelas suas próprias extensões são executados sequencialmente. A ordem pode ser importante, especialmente se um passo depender dos resultados ou modificações de outro. O Latte fornece um mecanismo auxiliar para controlar esta ordem, se necessário; consulte a documentação de [`Extension::getPasses()` |extending-latte#getPasses] para detalhes. + + +Exemplo de AST +============== + +Para ter uma ideia melhor da AST, apresentamos um exemplo. Este é o template de origem: + +```latte +{foreach $category->getItems() as $item} +
                                                                                          • {$item->name|upper}
                                                                                          • + {else} + não foram encontrados itens +{/foreach} +``` + +E esta é a sua representação na forma de AST: + +/--pre +Latte\Compiler\Nodes\TemplateNode( + Latte\Compiler\Nodes\FragmentNode( + - Latte\Essential\Nodes\ForeachNode( + expression: Latte\Compiler\Nodes\Php\Expression\MethodCallNode( + object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$category') + name: Latte\Compiler\Nodes\Php\IdentifierNode('getItems') + ) + value: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') + content: Latte\Compiler\Nodes\FragmentNode( + - Latte\Compiler\Nodes\TextNode(' ') + - Latte\Compiler\Nodes\Html\ElementNode('li')( + content: Latte\Essential\Nodes\PrintNode( + expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode( + object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') + name: Latte\Compiler\Nodes\Php\IdentifierNode('name') + ) + modifier: Latte\Compiler\Nodes\Php\ModifierNode( + filters: + - Latte\Compiler\Nodes\Php\FilterNode('upper') + ) + ) + ) + ) + else: Latte\Compiler\Nodes\FragmentNode( + - Latte\Compiler\Nodes\TextNode('não foram encontrados itens') + ) + ) + ) +) +\-- + + +Percorrendo a AST com `NodeTraverser` +===================================== + +Escrever manualmente funções recursivas para percorrer a estrutura complexa da AST é tedioso e propenso a erros. O Latte fornece uma ferramenta especial para este fim: [api:Latte\Compiler\NodeTraverser]. Esta classe implementa o [padrão de projeto Visitor |https://en.wikipedia.org/wiki/Visitor_pattern], tornando a travessia da AST sistemática e fácil de gerir. + +O uso básico envolve a criação de uma instância de `NodeTraverser` e a chamada do seu método `traverse()`, passando o nó raiz da AST e um ou dois objetos chamáveis "visitor": + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; + +(new NodeTraverser)->traverse( + $templateNode, + + // 'enter' visitor: Chamado ao entrar no nó (antes dos seus filhos) + enter: function (Node $node) { + echo "Entrando no nó do tipo: " . $node::class . "\n"; + // Aqui pode examinar o nó + if ($node instanceof Nodes\TextNode) { + // echo "Texto encontrado: " . $node->content . "\n"; + } + }, + + // 'leave' visitor: Chamado ao sair do nó (depois dos seus filhos) + leave: function (Node $node) { + echo "Saindo do nó do tipo: " . $node::class . "\n"; + // Aqui pode realizar ações após processar os filhos + }, +); +``` + +Pode fornecer apenas o visitor `enter`, apenas o visitor `leave`, ou ambos, dependendo das suas necessidades. + +**`enter(Node $node)`:** Esta função é executada para cada nó **antes** que o traverser visite qualquer um dos filhos deste nó. É útil para: + +- Recolher informações ao percorrer a árvore para baixo. +- Tomar decisões *antes* de processar os filhos (como decidir pulá-los, consulte [#Otimizar a Travessia]). +- Potenciais modificações do nó antes de visitar os filhos (menos comum). + +**`leave(Node $node)`:** Esta função é executada para cada nó **depois** que todos os seus filhos (e suas subárvores inteiras) foram totalmente visitados (tanto entrada quanto saída). É o local mais comum para: + +Ambos os visitors `enter` e `leave` podem opcionalmente retornar um valor para influenciar o processo de travessia. Retornar `null` (ou nada) continua a travessia normalmente, retornar uma instância de `Node` substitui o nó atual, e retornar constantes especiais como `NodeTraverser::RemoveNode` ou `NodeTraverser::StopTraversal` modifica o fluxo, como explicado nas seções seguintes. + + +Como funciona a travessia +------------------------- + +O `NodeTraverser` usa internamente o método `getIterator()`, que cada classe `Node` deve implementar (como discutido em [Criando tags personalizadas |custom-tags#Implementando getIterator para subnós]). Ele itera sobre os filhos obtidos usando `getIterator()`, chama recursivamente `traverse()` neles e garante que os visitors `enter` e `leave` sejam chamados na ordem correta de profundidade primeiro (depth-first) para cada nó na árvore acessível através dos iteradores. Isto reforça novamente por que um `getIterator()` corretamente implementado nos seus próprios nós de tag é absolutamente essencial para o funcionamento correto dos passos de compilação. + +Vamos escrever um passo simples que conta quantas vezes a tag `{do}` (representada por `Latte\Essential\Nodes\DoNode`) é usada no template. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Essential\Nodes\DoNode; + +function countDoTags(TemplateNode $templateNode): void +{ + $count = 0; + (new NodeTraverser)->traverse( + $templateNode, + enter: function (Node $node) use (&$count): void { + if ($node instanceof DoNode) { + $count++; + } + }, + // O visitor 'leave' não é necessário para esta tarefa + ); + + echo "Encontrada a tag {do} $count vezes.\n"; +} + +$latte = new Latte\Engine; +$ast = $latte->parse($templateSource); +countDoTags($ast); +``` + +Neste exemplo, precisávamos apenas do visitor `enter` para verificar o tipo de cada nó visitado. + +A seguir, exploraremos como esses visitors realmente modificam a AST. + + +Modificação da AST +================== + +Um dos principais propósitos dos passos de compilação é modificar a árvore de sintaxe abstrata. Isso permite transformações poderosas, otimizações ou aplicação de regras diretamente na estrutura do template antes de gerar o código PHP. O `NodeTraverser` fornece várias maneiras de conseguir isso dentro dos visitors `enter` e `leave`. + +**Nota importante:** A modificação da AST requer cuidado. Alterações incorretas – como remover nós essenciais ou substituir um nó por um tipo incompatível – podem levar a erros durante a geração de código ou causar comportamento inesperado durante a execução do programa. Sempre teste minuciosamente os seus passos de modificação. + + +Alterando propriedades dos nós +------------------------------ + +A maneira mais simples de modificar a árvore é alterar diretamente as **propriedades públicas** dos nós visitados durante a travessia. Todos os nós armazenam os seus argumentos analisados, conteúdo ou atributos em propriedades públicas. + +**Exemplo:** Vamos criar um passo que encontra todos os nós de texto estático (`TextNode`, representando HTML comum ou texto fora das tags Latte) e converte o seu conteúdo para maiúsculas *diretamente na AST*. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\TextNode; + +function uppercaseStaticText(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // Podemos usar 'enter', pois TextNode não tem filhos para processar + enter: function (Node $node) { + // Este nó é um bloco de texto estático? + if ($node instanceof TextNode) { + // Sim! Modificamos diretamente a sua propriedade pública 'content'. + $node->content = mb_strtoupper(html_entity_decode($node->content)); + } + // Não é necessário retornar nada; a alteração é aplicada diretamente. + }, + ); +} +``` + +Neste exemplo, o visitor `enter` verifica se o `$node` atual é do tipo `TextNode`. Se sim, atualizamos diretamente a sua propriedade pública `$content` usando `mb_strtoupper()`. Isso altera diretamente o conteúdo do texto estático armazenado na AST *antes* de gerar o código PHP. Como estamos modificando o objeto diretamente, não precisamos retornar nada do visitor. + +Efeito: Se o template continha `

                                                                                            Olá

                                                                                            {= $var }Mundo`, após este passo, a AST representará algo como: `

                                                                                            OLÁ

                                                                                            {= $var }MUNDO`. Isso NÃO AFETARÁ o conteúdo de `$var`. + + +Substituindo nós +---------------- + +Uma técnica de modificação mais poderosa é substituir completamente um nó por outro. Isso é feito **retornando uma nova instância de `Node`** do visitor `enter` ou `leave`. O `NodeTraverser` então substitui o nó original pelo retornado na estrutura do nó pai. + +**Exemplo:** Vamos criar um passo que encontra todos os usos da constante `PHP_VERSION` (representada por `ConstantFetchNode`) e os substitui diretamente por um literal de string (`StringNode`) contendo a versão *real* do PHP detetada *durante a compilação*. Esta é uma forma de otimização em tempo de compilação. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\Php\Expression\ConstantFetchNode; +use Latte\Compiler\Nodes\Php\Scalar\StringNode; + +function inlinePhpVersion(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // 'leave' é frequentemente usado para substituição, garantindo que os filhos (se existirem) + // sejam processados primeiro, embora 'enter' também funcionasse aqui. + leave: function (Node $node) { + // Este nó é um acesso a constante e o nome da constante é 'PHP_VERSION'? + if ($node instanceof ConstantFetchNode && (string) $node->name === 'PHP_VERSION') { + // Criamos um novo StringNode contendo a versão atual do PHP + $newNode = new StringNode(PHP_VERSION); + + // Opcional, mas boa prática: copiamos informações de posição + $newNode->position = $node->position; + + // Retornamos o novo StringNode. O Traverser substituirá + // o ConstantFetchNode original por este $newNode. + return $newNode; + } + // Se não retornarmos um Node, o $node original é mantido. + }, + ); +} +``` + +Aqui, o visitor `leave` identifica o `ConstantFetchNode` específico para `PHP_VERSION`. Em seguida, cria um `StringNode` completamente novo contendo o valor da constante `PHP_VERSION` *em tempo de compilação*. Retornando este `$newNode`, ele diz ao traverser para substituir o `ConstantFetchNode` original na AST. + +Efeito: Se o template continha `{= PHP_VERSION }` e a compilação está sendo executada no PHP 8.2.1, a AST após este passo representará efetivamente `{= '8.2.1' }`. + +**Escolhendo `enter` vs. `leave` para substituição:** + +- Use `leave` se a criação do novo nó depender dos resultados do processamento dos filhos do nó antigo, ou se você simplesmente quiser garantir que os filhos sejam visitados antes da substituição (prática comum). +- Use `enter` se você quiser substituir um nó *antes* que os seus filhos sejam visitados. + + +Removendo nós +------------- + +Você pode remover completamente um nó da AST retornando a constante especial `NodeTraverser::RemoveNode` do visitor. + +**Exemplo:** Vamos remover todos os comentários do template (`{* ... *}`), que são representados por `CommentNode` na AST gerada pelo núcleo do Latte (embora tipicamente processados antes, isto serve como exemplo). + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\CommentNode; + +function removeCommentNodes(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // 'enter' está bom aqui, pois não precisamos de informações sobre os filhos para remover o comentário + enter: function (Node $node) { + if ($node instanceof CommentNode) { + // Sinalizamos ao traverser para remover este nó da AST + return NodeTraverser::RemoveNode; + } + }, + ); +} +``` + +**Aviso:** Use `RemoveNode` com cuidado. Remover um nó que contém conteúdo essencial ou afeta a estrutura (como remover o nó de conteúdo de um ciclo) pode levar a templates corrompidos ou código gerado inválido. É mais seguro para nós que são verdadeiramente opcionais ou autónomos (como comentários ou tags de depuração) ou para nós estruturais vazios (por exemplo, um `FragmentNode` vazio pode ser removido com segurança em alguns contextos por um passo de limpeza). + +Estes três métodos - alteração de propriedades, substituição de nós e remoção de nós - fornecem as ferramentas básicas para manipular a AST dentro dos seus passos de compilação. + + +Otimizar a Travessia +==================== + +A AST de um template pode ser bastante grande, potencialmente contendo milhares de nós. Percorrer cada nó individualmente pode ser desnecessário e afetar o desempenho da compilação se o seu passo estiver interessado apenas em partes específicas da árvore. O `NodeTraverser` oferece maneiras de otimizar a travessia: + + +Saltar filhos +------------- + +Se você sabe que, uma vez que encontra um certo tipo de nó, nenhum dos seus descendentes pode conter os nós que você está procurando, pode dizer ao traverser para saltar a visita aos seus filhos. Isso é feito retornando a constante `NodeTraverser::DontTraverseChildren` do visitor **`enter`**. Isto omite ramos inteiros durante a travessia, potencialmente economizando tempo considerável, especialmente em templates com expressões PHP complexas dentro das tags. + + +Parar a travessia +----------------- + +Se o seu passo precisa encontrar apenas a *primeira* ocorrência de algo (um tipo específico de nó, o cumprimento de uma condição), você pode parar completamente todo o processo de travessia assim que o encontrar. Isso é alcançado retornando a constante `NodeTraverser::StopTraversal` do visitor `enter` ou `leave`. O método `traverse()` para de visitar quaisquer outros nós. Isto é altamente eficiente se você precisar apenas da primeira correspondência numa árvore potencialmente muito grande. + + +Auxiliar útil `NodeHelpers` +=========================== + +Embora `NodeTraverser` ofereça controlo refinado, o Latte também fornece uma classe auxiliar prática, [api:Latte\Compiler\NodeHelpers], que encapsula `NodeTraverser` para várias tarefas comuns de busca e análise, muitas vezes exigindo menos código de preparação. + + +find(Node $startNode, callable $filter): array .[method] +-------------------------------------------------------- + +Este método estático encontra **todos** os nós na subárvore começando em `$startNode` (inclusive) que satisfazem o callback `$filter`. Retorna um array dos nós correspondentes. + +**Exemplo:** Encontrar todos os nós de variáveis (`VariableNode`) em todo o template. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Php\Expression\VariableNode; +use Latte\Compiler\Nodes\TemplateNode; + +function findAllVariables(TemplateNode $templateNode): array +{ + return NodeHelpers::find( + $templateNode, + fn($node) => $node instanceof VariableNode, + ); +} +``` + + +findFirst(Node $startNode, callable $filter): ?Node .[method] +-------------------------------------------------------------- + +Semelhante a `find`, mas para a travessia imediatamente após encontrar o **primeiro** nó que satisfaz o callback `$filter`. Retorna o objeto `Node` encontrado ou `null` se nenhum nó correspondente for encontrado. Isto é essencialmente um invólucro prático em torno de `NodeTraverser::StopTraversal`. + +**Exemplo:** Encontrar o nó `{parameters}` (o mesmo que o exemplo manual anterior, mas mais curto). + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Essential\Nodes\ParametersNode; + +function findParametersNodeHelper(TemplateNode $templateNode): ?ParametersNode +{ + return NodeHelpers::findFirst( + $templateNode->head, // Procurar apenas na seção principal para eficiência + fn($node) => $node instanceof ParametersNode, + ); +} +``` + + +toValue(ExpressionNode $node, bool $constants = false): mixed .[method] +----------------------------------------------------------------------- + +Este método estático tenta avaliar um `ExpressionNode` **em tempo de compilação** e retornar o seu valor PHP correspondente. Funciona de forma confiável apenas para nós literais simples (`StringNode`, `IntegerNode`, `FloatNode`, `BooleanNode`, `NullNode`) e instâncias de `ArrayNode` contendo apenas tais itens avaliáveis. + +Se `$constants` for definido como `true`, também tentará resolver `ConstantFetchNode` e `ClassConstantFetchNode` verificando `defined()` e usando `constant()`. + +Se o nó contiver variáveis, chamadas de função ou outros elementos dinâmicos, não pode ser avaliado em tempo de compilação e o método lançará `InvalidArgumentException`. + +**Caso de uso:** Obter o valor estático de um argumento de tag durante a compilação para tomar decisões em tempo de compilação. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Php\ExpressionNode; + +function getStaticStringArgument(ExpressionNode $argumentNode): ?string +{ + try { + $value = NodeHelpers::toValue($argumentNode); + return is_string($value) ? $value : null; + } catch (\InvalidArgumentException $e) { + // O argumento não era uma string literal estática + return null; + } +} +``` + + +toText(?Node $node): ?string .[method] +-------------------------------------- + +Este método estático é útil para extrair conteúdo de texto simples de nós simples. Funciona principalmente com: +- `TextNode`: Retorna o seu `$content`. +- `FragmentNode`: Concatena o resultado de `toText()` para todos os seus filhos. Se algum filho não for conversível em texto (por exemplo, contém `PrintNode`), retorna `null`. +- `NopNode`: Retorna uma string vazia. +- Outros tipos de nós: Retorna `null`. + +**Caso de uso:** Obter o conteúdo de texto estático do valor de um atributo HTML ou de um elemento HTML simples para análise durante um passo de compilação. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Html\AttributeNode; + +function getStaticAttributeValue(AttributeNode $attr): ?string +{ + // $attr->value é tipicamente um AreaNode (como FragmentNode ou TextNode) + return NodeHelpers::toText($attr->value); +} + +// Exemplo de uso num passo: +// if ($node instanceof Html\ElementNode && $node->name === 'meta') { +// $nameAttrValue = getStaticAttributeValue($node->getAttributeNode('name')); +// if ($nameAttrValue === 'description') { ... } +// } +``` + +`NodeHelpers` pode simplificar os seus passos de compilação fornecendo soluções prontas para tarefas comuns de travessia e análise da AST. + + +Exemplos Práticos +================= + +Vamos aplicar os conceitos de travessia e modificação da AST para resolver alguns problemas práticos. Estes exemplos demonstram padrões comuns usados em passos de compilação. + + +Adição automática de `loading="lazy"` a `` +----------------------------------------------- + +Navegadores modernos suportam carregamento lento nativo para imagens usando o atributo `loading="lazy"`. Vamos criar um passo que adiciona automaticamente este atributo a todas as tags `` que ainda não possuem um atributo `loading`. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; +use Latte\Compiler\Nodes\Html; + +function addLazyLoading(Nodes\TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // Podemos usar 'enter', pois modificamos o nó diretamente + // e não dependemos dos filhos para esta decisão. + enter: function (Node $node) { + // É um elemento HTML com o nome 'img'? + if ($node instanceof Html\ElementNode && $node->name === 'img') { + // Garantimos que o nó de atributos exista + $node->attributes ??= new Nodes\FragmentNode; + + // Verificamos se já existe um atributo 'loading' (independente de maiúsculas/minúsculas) + foreach ($node->attributes->children as $attrNode) { + if ($attrNode instanceof Html\AttributeNode + && $attrNode->name instanceof Nodes\TextNode // Nome de atributo estático + && strtolower($attrNode->name->content) === 'loading' + ) { + return; // Atributo 'loading' já existe, não fazemos nada + } + } + + // Anexamos um espaço se os atributos não estiverem vazios + if ($node->attributes->children) { + $node->attributes->children[] = new Nodes\TextNode(' '); + } + + // Criamos um novo nó de atributo: loading="lazy" + $node->attributes->children[] = new Html\AttributeNode( + name: new Nodes\TextNode('loading'), + value: new Nodes\TextNode('lazy'), + quote: '"', + ); + // A alteração é aplicada diretamente no objeto, não é necessário retornar nada. + } + }, + ); +} +``` + +Explicação: +- O visitor `enter` procura por nós `Html\ElementNode` com o nome `img`. +- Itera sobre os atributos existentes (`$node->attributes->children`) e verifica se o atributo `loading` já está presente. +- Se não for encontrado, cria um novo `Html\AttributeNode` representando `loading="lazy"`. + + +Verificação de chamadas de funções +---------------------------------- + +Os passos de compilação são a base do Latte Sandbox. Embora o Sandbox real seja sofisticado, podemos demonstrar o princípio básico de verificar chamadas de funções proibidas. + +**Objetivo:** Impedir o uso da função potencialmente perigosa `shell_exec` dentro das expressões do template. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; +use Latte\Compiler\Nodes\Php; +use Latte\SecurityViolationException; + +function checkForbiddenFunctions(Nodes\TemplateNode $templateNode): void +{ + $forbiddenFunctions = ['shell_exec' => true, 'exec' => true]; // Lista simples + + $traverser = new NodeTraverser; + (new NodeTraverser)->traverse( + $templateNode, + enter: function (Node $node) use ($forbiddenFunctions) { + // É um nó de chamada de função direta? + if ($node instanceof Php\Expression\FunctionCallNode + && $node->name instanceof Php\NameNode + && isset($forbiddenFunctions[strtolower((string) $node->name)]) + ) { + throw new SecurityViolationException( + "A função {$node->name}() não é permitida.", + $node->position, + ); + } + }, + ); +} +``` + +Explicação: +- Definimos uma lista de nomes de funções proibidas. +- O visitor `enter` verifica `FunctionCallNode`. +- Se o nome da função (`$node->name`) for um `NameNode` estático, verificamos a sua representação em string minúscula contra a nossa lista proibida. +- Se uma função proibida for encontrada, lançamos `Latte\SecurityViolationException`, que indica claramente a violação da regra de segurança e interrompe a compilação. + +Estes exemplos mostram como os passos de compilação usando `NodeTraverser` podem ser utilizados para análise, modificações automáticas e aplicação de restrições de segurança interagindo diretamente com a estrutura da AST do template. + + +Melhores Práticas +================= + +Ao escrever passos de compilação, tenha em mente estas diretrizes para criar extensões robustas, sustentáveis e eficientes: + +- **A ordem importa:** Esteja ciente da ordem em que os passos são executados. Se o seu passo depender da estrutura da AST criada por outro passo (por exemplo, passos base do Latte ou outro passo personalizado), ou se outros passos puderem depender das suas modificações, use o mecanismo de ordenação fornecido por `Extension::getPasses()` para definir dependências (`before`/`after`). Consulte a documentação de [`Extension::getPasses()` |extending-latte#getPasses] para detalhes. +- **Responsabilidade única:** Esforce-se por passos que realizem uma única tarefa bem definida. Para transformações complexas, considere dividir a lógica em vários passos – talvez um para análise e outro para modificação com base nos resultados da análise. Isto melhora a clareza e a testabilidade. +- **Desempenho:** Lembre-se que os passos de compilação adicionam tempo à compilação do template (embora isto geralmente ocorra apenas uma vez, até que o template mude). Evite operações computacionalmente intensivas nos seus passos, se possível. Utilize otimizações de travessia como `NodeTraverser::DontTraverseChildren` e `NodeTraverser::StopTraversal` sempre que souber que não precisa visitar certas partes da AST. +- **Use `NodeHelpers`:** Para tarefas comuns como encontrar nós específicos ou avaliar estaticamente expressões simples, verifique se `Latte\Compiler\NodeHelpers` oferece um método adequado antes de escrever a sua própria lógica `NodeTraverser`. Isto pode economizar tempo e reduzir a quantidade de código de preparação. +- **Tratamento de erros:** Se o seu passo detetar um erro ou estado inválido na AST do template, lance `Latte\CompileException` (ou `Latte\SecurityViolationException` para problemas de segurança) com uma mensagem clara e o objeto `Position` relevante (geralmente `$node->position`). Isto fornece feedback útil ao desenvolvedor do template. +- **Idempotência (se possível):** Idealmente, executar o seu passo várias vezes na mesma AST deveria produzir o mesmo resultado que executá-lo uma vez. Isto nem sempre é viável, mas simplifica a depuração e o raciocínio sobre as interações dos passos, se alcançado. Por exemplo, garanta que o seu passo de modificação verifique se a modificação já foi aplicada antes de aplicá-la novamente. + +Seguindo estas práticas, você pode utilizar eficazmente os passos de compilação para estender as capacidades do Latte de forma poderosa e confiável, contribuindo para um processamento de templates mais seguro, otimizado ou funcionalmente mais rico. diff --git a/latte/pt/cookbook/@home.texy b/latte/pt/cookbook/@home.texy index d668db4db1..2f9f3a4baf 100644 --- a/latte/pt/cookbook/@home.texy +++ b/latte/pt/cookbook/@home.texy @@ -1,13 +1,13 @@ -Cookbook -******** +Guias e melhores práticas +************************* .[perex] -Exemplos de códigos e receitas para a realização de tarefas comuns com Latte. +Exemplos de código e receitas para realizar tarefas comuns usando Latte. -- [Tudo o que você sempre quis saber sobre {alfabetizar-assim} |iteratewhile] -- [Como escrever consultas SQL em Latte? |how-to-write-sql-queries-in-latte] +- [Práticas para desenvolvedores |/develop] +- [Passando variáveis entre templates |passing-variables] +- [Tudo o que você sempre quis saber sobre agrupamento |grouping] +- [Como escrever consultas SQL no Latte? |how-to-write-sql-queries-in-latte] - [Migração do PHP |migration-from-php] -- [Migração de galho |migration-from-twig] +- [Migração do Twig |migration-from-twig] - [Usando Latte com Slim 4 |slim-framework] - -{{leftbar: /@left-menu}} diff --git a/latte/pt/cookbook/@meta.texy b/latte/pt/cookbook/@meta.texy new file mode 100644 index 0000000000..45bfac38e1 --- /dev/null +++ b/latte/pt/cookbook/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Documentação Latte}} +{{leftbar: /@left-menu}} diff --git a/latte/pt/cookbook/grouping.texy b/latte/pt/cookbook/grouping.texy new file mode 100644 index 0000000000..1fc19964ca --- /dev/null +++ b/latte/pt/cookbook/grouping.texy @@ -0,0 +1,251 @@ +Tudo o que você sempre quis saber sobre agrupamento +*************************************************** + +.[perex] +Ao trabalhar com dados em templates, você pode frequentemente encontrar a necessidade de agrupá-los ou exibi-los especificamente de acordo com certos critérios. Latte oferece várias ferramentas poderosas para este propósito. + +O filtro e a função `|group` permitem agrupar dados eficientemente de acordo com um critério especificado, o filtro `|batch` facilita a divisão de dados em lotes fixos, e a tag `{iterateWhile}` fornece a capacidade de controlar de forma mais complexa o fluxo de loops com condições. Cada uma dessas tags oferece possibilidades específicas para trabalhar com dados, tornando-as ferramentas indispensáveis para a exibição dinâmica e estruturada de informações nos templates Latte. + + +Filtro e função `group` .{data-version:3.0.16} +============================================== + +Imagine uma tabela de banco de dados `items` com itens divididos em categorias: + +| id | categoryId | name +|------------------ +| 1 | 1 | Apple +| 2 | 1 | Banana +| 3 | 2 | PHP +| 4 | 3 | Green +| 5 | 3 | Red +| 6 | 3 | Blue + +Uma lista simples de todos os itens usando um template Latte seria assim: + +```latte +
                                                                                              +{foreach $items as $item} +
                                                                                            • {$item->name}
                                                                                            • +{/foreach} +
                                                                                            +``` + +No entanto, se quiséssemos que os itens fossem organizados em grupos por categoria, precisaríamos dividi-los de forma que cada categoria tivesse sua própria lista. O resultado deveria então ser o seguinte: + +```latte +
                                                                                              +
                                                                                            • Apple
                                                                                            • +
                                                                                            • Banana
                                                                                            • +
                                                                                            + +
                                                                                              +
                                                                                            • PHP
                                                                                            • +
                                                                                            + +
                                                                                              +
                                                                                            • Green
                                                                                            • +
                                                                                            • Red
                                                                                            • +
                                                                                            • Blue
                                                                                            • +
                                                                                            +``` + +A tarefa pode ser resolvida fácil e elegantemente usando `|group`. Como parâmetro, especificamos `categoryId`, o que significa que os itens serão divididos em arrays menores com base no valor de `$item->categoryId` (se `$item` fosse um array, seria usado `$item['categoryId']`): + +```latte +{foreach ($items|group: categoryId) as $categoryId => $categoryItems} +
                                                                                              + {foreach $categoryItems as $item} +
                                                                                            • {$item->name}
                                                                                            • + {/foreach} +
                                                                                            +{/foreach} +``` + +O filtro também pode ser usado em Latte como uma função, o que nos dá uma sintaxe alternativa: `{foreach group($items, categoryId) ...}`. + +Se você quiser agrupar itens com base em critérios mais complexos, pode usar uma função no parâmetro do filtro. Por exemplo, agrupar itens pelo comprimento do nome seria assim: + +```latte +{foreach ($items|group: fn($item) => strlen($item->name)) as $items} + ... +{/foreach} +``` + +É importante notar que `$categoryItems` não é um array comum, mas um objeto que se comporta como um iterador. Para acessar o primeiro item do grupo, você pode usar a função [`first()` |latte:functions#first]. + +Essa flexibilidade no agrupamento de dados torna `group` uma ferramenta excepcionalmente útil para apresentar dados nos templates Latte. + + +Loops aninhados +--------------- + +Imagine que temos uma tabela de banco de dados com outra coluna `subcategoryId`, que define das subcategorias dos itens individuais. Queremos exibir cada categoria principal em uma lista `
                                                                                              ` separada e cada subcategoria em uma lista aninhada `
                                                                                                ` separada: + +```latte +{foreach ($items|group: categoryId) as $categoryItems} +
                                                                                                  + {foreach ($categoryItems|group: subcategoryId) as $subcategoryItems} +
                                                                                                    + {foreach $subcategoryItems as $item} +
                                                                                                  1. {$item->name} + {/foreach} +
                                                                                                  + {/foreach} +
                                                                                                +{/foreach} +``` + + +Conexão com Nette Database +-------------------------- + +Vamos mostrar como usar eficientemente o agrupamento de dados em combinação com a Nette Database. Suponha que estamos trabalhando com a tabela `items` do exemplo introdutório, que está conectada através da coluna `categoryId` a esta tabela `categories`: + +| categoryId | name | +|------------|------------| +| 1 | Fruits | +| 2 | Languages | +| 3 | Colors | + +Carregamos os dados da tabela `items` usando o Nette Database Explorer com o comando `$items = $db->table('items')`. Durante a iteração sobre esses dados, temos a possibilidade de acessar não apenas atributos como `$item->name` e `$item->categoryId`, mas também, graças à conexão com a tabela `categories`, a linha relacionada nela através de `$item->category`. Nesta conexão, podemos demonstrar um uso interessante: + +```latte +{foreach ($items|group: category) as $category => $categoryItems} +

                                                                                                {$category->name}

                                                                                                +
                                                                                                  + {foreach $categoryItems as $item} +
                                                                                                • {$item->name}
                                                                                                • + {/foreach} +
                                                                                                +{/foreach} +``` + +Neste caso, usamos o filtro `|group` para agrupar pela linha conectada `$item->category`, e não apenas pela coluna `categoryId`. Graças a isso, na variável chave, temos diretamente o `ActiveRow` da categoria dada, o que nos permite exibir diretamente seu nome usando `{$category->name}`. Este é um exemplo prático de como o agrupamento pode tornar os templates mais claros e facilitar o trabalho com dados. + + +Filtro `|batch` +=============== + +O filtro permite dividir uma lista de elementos em grupos com um número predeterminado de elementos. Este filtro é ideal para situações em que você deseja apresentar dados em vários grupos menores, por exemplo, para melhor clareza ou organização visual na página. + +Imagine que temos uma lista de itens e queremos exibi-los em listas, onde cada uma contém no máximo três itens. O uso do filtro `|batch` é muito prático nesse caso: + +```latte +
                                                                                                  +{foreach ($items|batch: 3) as $batch} + {foreach $batch as $item} +
                                                                                                • {$item->name}
                                                                                                • + {/foreach} +{/foreach} +
                                                                                                +``` + +Neste exemplo, a lista `$items` é dividida em grupos menores, onde cada grupo (`$batch`) contém até três itens. Cada grupo é então exibido em uma lista `
                                                                                                  ` separada. + +Se o último grupo não contiver elementos suficientes para atingir o número desejado, o segundo parâmetro do filtro permite definir com o que este grupo será preenchido. Isso é ideal para o alinhamento estético de elementos onde uma linha incompleta poderia parecer desorganizada. + +```latte +{foreach ($items|batch: 3, '—') as $batch} + ... +{/foreach} +``` + + +Tag `{iterateWhile}` +==================== + +As mesmas tarefas que resolvemos com o filtro `|group`, mostraremos usando a tag `{iterateWhile}`. A principal diferença entre as duas abordagens é que `group` primeiro processa e agrupa todos os dados de entrada, enquanto `{iterateWhile}` controla o fluxo dos loops com condições, de modo que a iteração ocorre progressivamente. + +Primeiro, renderizamos a tabela com categorias usando iterateWhile: + +```latte +{foreach $items as $item} +
                                                                                                    + {iterateWhile} +
                                                                                                  • {$item->name}
                                                                                                  • + {/iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                                                                                                  +{/foreach} +``` + +Enquanto `{foreach}` marca a parte externa do loop, ou seja, a renderização das listas para cada categoria, a tag `{iterateWhile}` marca a parte interna, ou seja, os itens individuais. A condição na tag de fechamento diz que a repetição continuará enquanto o elemento atual e o seguinte pertencerem à mesma categoria (`$iterator->nextValue` é o [próximo item |/tags#iterator]). + +Se a condição fosse sempre verdadeira, todos os elementos seriam renderizados no loop interno: + +```latte +{foreach $items as $item} +
                                                                                                    + {iterateWhile} +
                                                                                                  • {$item->name} + {/iterateWhile true} +
                                                                                                  +{/foreach} +``` + +O resultado seria assim: + +```latte +
                                                                                                    +
                                                                                                  • Apple
                                                                                                  • +
                                                                                                  • Banana
                                                                                                  • +
                                                                                                  • PHP
                                                                                                  • +
                                                                                                  • Green
                                                                                                  • +
                                                                                                  • Red
                                                                                                  • +
                                                                                                  • Blue
                                                                                                  • +
                                                                                                  +``` + +Para que serve tal uso de iterateWhile? Se a tabela estiver vazia e não contiver nenhum elemento, o `
                                                                                                    ` vazio não será impresso. + +Se especificarmos a condição na tag de abertura `{iterateWhile}`, o comportamento muda: a condição (e a transição para o próximo elemento) é executada já no início do loop interno, e não no final. Ou seja, enquanto se entra sempre em `{iterateWhile}` sem condição, entra-se em `{iterateWhile $cond}` apenas se a condição `$cond` for atendida. E, ao mesmo tempo, o próximo elemento é atribuído a `$item`. + +Isso é útil, por exemplo, na situação em que queremos renderizar o primeiro elemento de cada categoria de forma diferente, por exemplo, assim: + +```latte +

                                                                                                    Apple

                                                                                                    +
                                                                                                      +
                                                                                                    • Banana
                                                                                                    • +
                                                                                                    + +

                                                                                                    PHP

                                                                                                    +
                                                                                                      +
                                                                                                    + +

                                                                                                    Green

                                                                                                    +
                                                                                                      +
                                                                                                    • Red
                                                                                                    • +
                                                                                                    • Blue
                                                                                                    • +
                                                                                                    +``` + +Modificamos o código original para que primeiro renderizemos o primeiro item e depois, no loop interno `{iterateWhile}`, renderizemos os outros itens da mesma categoria: + +```latte +{foreach $items as $item} +

                                                                                                    {$item->name}

                                                                                                    +
                                                                                                      + {iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                                                                                                    • {$item->name}
                                                                                                    • + {/iterateWhile} +
                                                                                                    +{/foreach} +``` + +Dentro de um único loop, podemos criar vários loops internos e até mesmo aninhá-los. Desta forma, poderíamos agrupar subcategorias, etc. + +Digamos que na tabela haja outra coluna `subcategoryId` e, além de cada categoria estar em um `
                                                                                                      ` separado, cada subcategoria estará em um `
                                                                                                        ` separado: + +```latte +{foreach $items as $item} +
                                                                                                          + {iterateWhile} +
                                                                                                            + {iterateWhile} +
                                                                                                          1. {$item->name} + {/iterateWhile $item->subcategoryId === $iterator->nextValue->subcategoryId} +
                                                                                                          + {/iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                                                                                                        +{/foreach} +``` diff --git a/latte/pt/cookbook/how-to-write-sql-queries-in-latte.texy b/latte/pt/cookbook/how-to-write-sql-queries-in-latte.texy index 057ff0162c..b3a716691e 100644 --- a/latte/pt/cookbook/how-to-write-sql-queries-in-latte.texy +++ b/latte/pt/cookbook/how-to-write-sql-queries-in-latte.texy @@ -1,10 +1,10 @@ -Como Escrever Consultas SQL em Latte? +Como escrever consultas SQL em Latte? ************************************* .[perex] -O Latte também pode ser útil para gerar consultas SQL realmente complexas. +Latte também pode ser útil para gerar consultas SQL realmente complexas. -Se a criação de uma consulta SQL contém muitas condições e variáveis, pode ser realmente mais claro escrevê-la em Latte. Um exemplo muito simples: +Se a criação de uma consulta SQL contiver várias condições e variáveis, pode ser realmente mais claro escrevê-la em Latte. Um exemplo muito simples: ```latte SELECT users.* FROM users @@ -14,8 +14,7 @@ SELECT users.* FROM users WHERE groups.name = 'Admins' {ifset $country} AND country.name = {$country} {/ifset} ``` -Usando `$latte->setContentType()`, dizemos ao Latte para tratar o conteúdo como texto simples (não como HTML) e -então preparamos uma função de fuga que escapa das cordas diretamente pelo motorista do banco de dados: +Usando `$latte->setContentType()`, dizemos ao Latte para tratar o conteúdo como texto simples (não como HTML) e, em seguida, preparamos uma função de escape que escapará as strings diretamente com o driver do banco de dados: ```php $db = new PDO(/* ... */); @@ -27,17 +26,15 @@ $latte->addFilter('escape', fn($val) => match (true) { is_int($val), is_float($val) => (string) $val, is_bool($val) => $val ? '1' : '0', is_null($val) => 'NULL', - default => throw new Exception('Unsupported type'), + default => throw new Exception('Tipo não suportado'), }); ``` -O uso teria este aspecto: +O uso seria assim: ```php $sql = $latte->renderToString('query.sql.latte', ['country' => $country]); $result = $db->query($sql); ``` -*Este exemplo requer Latte v3.0.5 ou superior.* - -{{leftbar: /@left-menu}} +*O exemplo fornecido requer Latte v3.0.5 ou superior.* diff --git a/latte/pt/cookbook/iteratewhile.texy b/latte/pt/cookbook/iteratewhile.texy deleted file mode 100644 index ca62ed411c..0000000000 --- a/latte/pt/cookbook/iteratewhile.texy +++ /dev/null @@ -1,214 +0,0 @@ -Tudo o que você sempre quis saber sobre {alfabetizar-assim} -*********************************************************** - -.[perex] -A etiqueta `{iterateWhile}` é adequada para vários truques em ciclos anteriores. - -Suponha que tenhamos a seguinte tabela de banco de dados, onde os itens são divididos em categorias: - -| id | catId | name -|------------------ -| 1 | 1 | Apple -| 2 | 1 | Banana -| 3 | 2 | PHP -| 4 | 3 | Green -| 5 | 3 | Red -| 6 | 3 | Blue - -Naturalmente, desenhar itens em um laço na frente como uma lista é fácil: - -```latte -
                                                                                                          -{foreach $items as $item} -
                                                                                                        • {$item->name}
                                                                                                        • -{/foreach} -
                                                                                                        -``` - -Mas o que fazer se você quiser apresentar cada categoria em uma lista separada? Em outras palavras, como resolver a tarefa de agrupar itens de uma lista linear em um ciclo foreach. A saída deve se parecer com isto: - -```latte -
                                                                                                          -
                                                                                                        • Apple
                                                                                                        • -
                                                                                                        • Banana
                                                                                                        • -
                                                                                                        - -
                                                                                                          -
                                                                                                        • PHP
                                                                                                        • -
                                                                                                        - -
                                                                                                          -
                                                                                                        • Green
                                                                                                        • -
                                                                                                        • Red
                                                                                                        • -
                                                                                                        • Blue
                                                                                                        • -
                                                                                                        -``` - -Mostraremos a facilidade e a elegância com que a tarefa pode ser resolvida com iteração: - -```latte -{foreach $items as $item} -
                                                                                                          - {iterateWhile} -
                                                                                                        • {$item->name}
                                                                                                        • - {/iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                                                                        -{/foreach} -``` - -Enquanto `{foreach}` marca a parte externa do ciclo, ou seja, o desenho de listas para cada categoria, as tags `{iterateWhile}` indicam a parte interna, ou seja, os itens individuais. -A condição na etiqueta final diz que a repetição continuará enquanto o elemento atual e o próximo elemento pertencerem à mesma categoria (`$iterator->nextValue` é o [próximo item |/tags#$iterator]). - -Se a condição for sempre preenchida, então todos os elementos são desenhados no ciclo interno: - -```latte -{foreach $items as $item} -
                                                                                                          - {iterateWhile} -
                                                                                                        • {$item->name} - {/iterateWhile true} -
                                                                                                        -{/foreach} -``` - -O resultado será o seguinte: - -```latte -
                                                                                                          -
                                                                                                        • Apple
                                                                                                        • -
                                                                                                        • Banana
                                                                                                        • -
                                                                                                        • PHP
                                                                                                        • -
                                                                                                        • Green
                                                                                                        • -
                                                                                                        • Red
                                                                                                        • -
                                                                                                        • Blue
                                                                                                        • -
                                                                                                        -``` - -De que serve tal uso da iteração enquanto? De que forma difere da solução que mostramos logo no início deste tutorial? A diferença é que se a tabela estiver vazia e não contiver nenhum elemento, ela não irá tornar vazia `
                                                                                                          `. - - -Solução Sem `{iterateWhile}` .[#toc-solution-without-iteratewhile] ------------------------------------------------------------------- - -Se resolvêssemos a mesma tarefa com construções completamente básicas de sistemas de modelos, por exemplo em Twig, Blade ou PHP puro, a solução seria algo parecido com isto: - -```latte -{var $prevCatId = null} -{foreach $items as $item} - {if $item->catId !== $prevCatId} - {* the category has changed *} - - {* we close the previous
                                                                                                            , if it is not the first item *} - {if $prevCatId !== null} -
                                                                                                          - {/if} - - {* abriremos uma nova lista *} -
                                                                                                            - - {do $prevCatId = $item->catId} - {/if} - -
                                                                                                          • {$item->name}
                                                                                                          • -{/foreach} - -{if $prevCatId !== null} - {* we close the last list *} -
                                                                                                          -{/if} -``` - -No entanto, este código é incompreensível e pouco intuitivo. A conexão entre as tags HTML de abertura e fechamento não é clara em absoluto. Não é clara à primeira vista, se houver um erro. E requer variáveis auxiliares como `$prevCatId`. - -Em contraste, a solução com `{iterateWhile}` é limpa, clara, não necessita de variáveis auxiliares e é infalível. - - -Condição na Etiqueta de Fechamento .[#toc-condition-in-the-closing-tag] ------------------------------------------------------------------------ - -Se especificarmos uma condição na etiqueta de abertura `{iterateWhile}`, o comportamento muda: a condição (e o avanço para o próximo elemento) é executada no início do ciclo interno, não no final. -Assim, enquanto `{iterateWhile}` sem condição é sempre inserido, `{iterateWhile $cond}` é inserido somente quando a condição `$cond` é cumprida. Ao mesmo tempo, o seguinte elemento é escrito para `$item`. - -Isto é útil, por exemplo, em uma situação em que você quer renderizar o primeiro elemento de cada categoria de uma maneira diferente, como por exemplo: - -```latte -

                                                                                                          Apple

                                                                                                          -
                                                                                                            -
                                                                                                          • Banana
                                                                                                          • -
                                                                                                          - -

                                                                                                          PHP

                                                                                                          -
                                                                                                            -
                                                                                                          - -

                                                                                                          Green

                                                                                                          -
                                                                                                            -
                                                                                                          • Red
                                                                                                          • -
                                                                                                          • Blue
                                                                                                          • -
                                                                                                          -``` - -Vamos modificar o código original, primeiro desenhamos um item e depois itens adicionais da mesma categoria no laço interno `{iterateWhile}`: - -```latte -{foreach $items as $item} -

                                                                                                          {$item->name}

                                                                                                          -
                                                                                                            - {iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                                                                          • {$item->name}
                                                                                                          • - {/iterateWhile} -
                                                                                                          -{/foreach} -``` - - -Laços aninhados .[#toc-nested-loops] ------------------------------------- - -Podemos criar vários loops internos em um ciclo e até mesmo aninhá-los. Desta forma, por exemplo, as subcategorias poderiam ser agrupadas. - -Suponha que haja outra coluna na tabela `subCatId` e que, além de cada categoria estar em uma `
                                                                                                            `cada subcategoria estará em uma subcategoria separada `
                                                                                                              `: - -```latte -{foreach $items as $item} -
                                                                                                                - {iterateWhile} -
                                                                                                                  - {iterateWhile} -
                                                                                                                1. {$item->name} - {/iterateWhile $item->subCatId === $iterator->nextValue->subCatId} -
                                                                                                                - {/iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                                                                              -{/foreach} -``` - - -Filtro |batch .[#toc-filter-batch] ----------------------------------- - -O agrupamento de itens lineares também é fornecido por um filtro `batch`, em lotes com um número fixo de elementos: - -```latte -
                                                                                                                -{foreach ($items|batch:3) as $batch} - {foreach $batch as $item} -
                                                                                                              • {$item->name}
                                                                                                              • - {/foreach} -{/foreach} -
                                                                                                              -``` - -Ela pode ser substituída por iteração, enquanto que, como se segue: - -```latte -
                                                                                                                -{foreach $items as $item} - {iterateWhile} -
                                                                                                              • {$item->name}
                                                                                                              • - {/iterateWhile $iterator->counter0 % 3} -{/foreach} -
                                                                                                              -``` - -{{leftbar: /@left-menu}} diff --git a/latte/pt/cookbook/migration-from-php.texy b/latte/pt/cookbook/migration-from-php.texy index b249e9f039..89c66f574e 100644 --- a/latte/pt/cookbook/migration-from-php.texy +++ b/latte/pt/cookbook/migration-from-php.texy @@ -1,28 +1,28 @@ -Migração do PHP para Latte +Migração de PHP para Latte ************************** .[perex] -Você está migrando um projeto antigo escrito em PHP puro para Latte? Nós temos uma ferramenta para facilitar a migração. [Experimente-o online |https://php2latte.nette.org]. +Está convertendo um projeto antigo escrito em PHP puro para Latte? Temos uma ferramenta para facilitar a migração. [Experimente online |https://fiddle.nette.org/php2latte/]. -Você pode baixar a ferramenta do [GitHub |https://github.com/nette/latte-tools] ou instalá-la usando o Composer: +Você pode baixar a ferramenta do [GitHub|https://github.com/nette/latte-tools] ou instalá-la usando o Composer: ```shell composer create-project latte/tools ``` -O conversor não usa simples substituições de expressões regulares, ao invés disso, ele usa o analisador PHP diretamente, de modo que ele pode lidar com qualquer sintaxe complexa. +O conversor não usa substituições simples por expressões regulares, pelo contrário, utiliza diretamente o parser PHP, por isso lida com qualquer sintaxe complexa. -O script `php-to-latte.php` é usado para converter de PHP para Latte: +Para converter de PHP para Latte, use o script `php-to-latte.php`: ```shell php-to-latte.php input.php [output.latte] ``` -Exemplo .[#toc-example] ------------------------ +Exemplo +------- -O arquivo de entrada pode parecer com isto (faz parte do código do fórum PunBB): +O arquivo de entrada pode parecer assim (é parte do código do fórum PunBB): ```php

                                                                                                              @@ -48,10 +48,10 @@ foreach ($result as $cur_group) {
                                                                                                ``` -Gera este modelo: +Ele gera este template: ```latte -

                                                                                                {$lang_common['User list']}

                                                                                                +

                                                                                                {$lang_common['Lista de Usuários']}

                                                                                                @@ -63,10 +63,8 @@ Gera este modelo: {/if} {/foreach} -

                                                                                                {$lang_ul['User search info']}

                                                                                                +

                                                                                                {$lang_ul['Informações de busca de usuário']}

                                                                                                ``` - -{{leftbar: /@left-menu}} diff --git a/latte/pt/cookbook/migration-from-twig.texy b/latte/pt/cookbook/migration-from-twig.texy index fbad6a60f4..a6c99ab2bb 100644 --- a/latte/pt/cookbook/migration-from-twig.texy +++ b/latte/pt/cookbook/migration-from-twig.texy @@ -1,38 +1,38 @@ -Migração de Galho para Latte -**************************** +Migração de Twig para Latte +*************************** .[perex] -Você está migrando um projeto escrito em Twig para o Latte mais moderno? Nós temos uma ferramenta para facilitar a migração. [Experimente-o online |https://twig2latte.nette.org]. +Está convertendo um projeto escrito em Twig para o mais moderno Latte? Temos uma ferramenta para facilitar a migração. [Experimente online |https://fiddle.nette.org/twig2latte/]. -Você pode baixar a ferramenta do [GitHub |https://github.com/nette/latte-tools] ou instalá-la usando o Composer: +Você pode baixar a ferramenta do [GitHub|https://github.com/nette/latte-tools] ou instalá-la usando o Composer: ```shell composer create-project latte/tools ``` -O conversor não utiliza simples substituições de expressão regular, em vez disso, utiliza diretamente o analisador de Twig, de modo que ele pode lidar com qualquer sintaxe complexa. +O conversor não usa substituições simples por expressões regulares, pelo contrário, utiliza diretamente o parser Twig, por isso lida com qualquer sintaxe complexa. -Um roteiro `twig-to-latte.php` é usado para converter de Galho para Latte: +Para converter de Twig para Latte, use o script `twig-to-latte.php`: ```shell twig-to-latte.php input.twig.html [output.latte] ``` -Conversão .[#toc-conversion] ----------------------------- +Conversão +--------- -A conversão requer a edição manual do resultado, já que a conversão não pode ser feita sem ambigüidade. O galho utiliza a sintaxe dos pontos, onde `{{ a.b }}` pode significar `$a->b`, `$a['b']` ou `$a->getB()`, que não pode ser distinguido durante a compilação. O conversor, portanto, converte tudo para `$a->b`. +A conversão pressupõe ajuste manual do resultado, pois a conversão não pode ser realizada de forma inequívoca. Twig usa a sintaxe de ponto, onde `{{ a.b }}` pode significar `$a->b`, `$a['b']` ou `$a->getB()`, o que não pode ser distinguido durante a compilação. O conversor, portanto, converte tudo para `$a->b`. Algumas funções, filtros ou tags não têm equivalente em Latte, ou podem se comportar de maneira ligeiramente diferente. -Exemplo .[#toc-example] ------------------------ +Exemplo +------- -O arquivo de entrada pode se parecer com isto: +O arquivo de entrada pode parecer assim: -```latte +```twig {% use "blocks.twig" %} @@ -54,7 +54,7 @@ O arquivo de entrada pode se parecer com isto: ``` -Após a conversão para Latte, obtemos este modelo: +Após a conversão para Latte, obtemos este template: ```latte {import 'blocks.latte'} @@ -77,5 +77,3 @@ Após a conversão para Latte, obtemos este modelo: ``` - -{{leftbar: /@left-menu}} diff --git a/latte/pt/cookbook/passing-variables.texy b/latte/pt/cookbook/passing-variables.texy new file mode 100644 index 0000000000..4c6478fda8 --- /dev/null +++ b/latte/pt/cookbook/passing-variables.texy @@ -0,0 +1,158 @@ +Passagem de variáveis entre templates +************************************* + +Este guia explica como as variáveis são passadas entre templates em Latte usando várias tags como `{include}`, `{import}`, `{embed}`, `{layout}`, `{sandbox}` e outras. Você também aprenderá como trabalhar com variáveis na tag `{block}` e `{define}`, e para que serve a tag `{parameters}`. + + +Tipos de variáveis +------------------ +As variáveis em Latte podem ser divididas em três categorias, dependendo de como e onde são definidas: + +**Variáveis de entrada** são aquelas que são passadas para o template de fora, por exemplo, de um script PHP ou usando uma tag como `{include}`. + +```php +$latte->render('template.latte', ['userName' => 'Jan', 'userAge' => 30]); +``` + +**Variáveis de ambiente** são variáveis existentes no local de uma determinada tag. Incluem todas as variáveis de entrada e outras variáveis criadas usando tags como `{var}`, `{default}` ou dentro de um loop `{foreach}`. + +```latte +{foreach $users as $user} + {include 'userBox.latte', user: $user} +{/foreach} +``` + +**Variáveis explícitas** são aquelas que são especificadas diretamente dentro da tag e são enviadas para o template de destino. + +```latte +{include 'userBox.latte', name: $user->name, age: $user->age} +``` + + +`{block}` +--------- +A tag `{block}` é usada para definir blocos de código reutilizáveis que podem ser personalizados ou estendidos em templates herdados. As variáveis de ambiente definidas antes do bloco estão disponíveis dentro do bloco, mas quaisquer alterações nas variáveis se refletirão apenas dentro desse bloco. + +```latte +{var $foo = 'original'} +{block example} + {var $foo = 'alterado'} +{/block} + +{$foo} // imprime: original +``` + + +`{define}` +---------- +A tag `{define}` serve para criar blocos que são renderizados apenas após serem chamados usando `{include}`. As variáveis disponíveis dentro desses blocos dependem se parâmetros são especificados na definição. Se sim, eles têm acesso apenas a esses parâmetros. Se não, eles têm acesso a todas as variáveis de entrada do template no qual os blocos são definidos. + +```latte +{define hello} + {* tem acesso a todas as variáveis de entrada do template *} +{/define} + +{define hello $name} + {* tem acesso apenas ao parâmetro $name *} +{/define} +``` + + +`{parameters}` +-------------- +A tag `{parameters}` serve para declarar explicitamente as variáveis de entrada esperadas no início do template. Desta forma, é fácil documentar as variáveis esperadas e seus tipos de dados. Também é possível definir valores padrão. + +```latte +{parameters int $age, string $name = 'desconhecido'} +

                                                                                                Idade: {$age}, Nome: {$name}

                                                                                                +``` + + +`{include file}` +---------------- +A tag `{include file}` serve para inserir um template inteiro. Tanto as variáveis de entrada do template no qual a tag é usada quanto as variáveis explicitamente definidas nela são passadas para este template. O template de destino, no entanto, pode limitar o escopo usando `{parameters}`. + +```latte +{include 'profile.latte', userId: $user->id} +``` + + +`{include block}` +----------------- +Ao incluir um bloco definido no mesmo template, todas as variáveis de ambiente e explicitamente definidas são passadas para ele: + +```latte +{define blockName} +

                                                                                                Nome: {$name}, Idade: {$age}

                                                                                                +{/define} + +{var $name = 'Jan', $age = 30} +{include blockName} +``` + +Neste exemplo, as variáveis `$name` e `$age` são passadas para o bloco `blockName`. O `{include parent}` se comporta da mesma maneira. + +Ao incluir um bloco de outro template, apenas as variáveis de entrada e explicitamente definidas são passadas. As variáveis de ambiente não estão automaticamente disponíveis. + +```latte +{include blockInOtherTemplate, name: $name, age: $age} +``` + + +`{layout}` ou `{extends}` +------------------------- +Estas tags definem o layout para o qual as variáveis de entrada do template filho e, adicionalmente, as variáveis criadas no código antes dos blocos são passadas: + +```latte +{layout 'layout.latte'} +{var $seo = 'index, follow'} +``` + +Template `layout.latte`: + +```latte + + + +``` + + +`{embed}` +--------- +A tag `{embed}` é semelhante à tag `{include}`, mas permite a inserção de blocos no template. Ao contrário de `{include}`, apenas as variáveis explicitamente declaradas são passadas: + +```latte +{embed 'menu.latte', items: $menuItems} +{/embed} +``` + +Neste exemplo, o template `menu.latte` tem acesso apenas à variável `$items`. + +Por outro lado, nos blocos dentro de `{embed}`, há acesso a todas as variáveis de ambiente: + +```latte +{var $name = 'Jan'} +{embed 'menu.latte', items: $menuItems} + {block foo} + {$name} + {/block} +{/embed} +``` + + +`{import}` +---------- +A tag `{import}` é usada para carregar blocos de outros templates. Tanto as variáveis de entrada quanto as explicitamente declaradas são transferidas para os blocos importados. + +```latte +{import 'buttons.latte'} +``` + + +`{sandbox}` +----------- +A tag `{sandbox}` isola o template para processamento seguro. As variáveis são passadas exclusivamente de forma explícita. + +```latte +{sandbox 'secure.latte', data: $secureData} +``` diff --git a/latte/pt/cookbook/slim-framework.texy b/latte/pt/cookbook/slim-framework.texy index e23a442e2f..cdbf336263 100644 --- a/latte/pt/cookbook/slim-framework.texy +++ b/latte/pt/cookbook/slim-framework.texy @@ -2,42 +2,42 @@ Usando Latte com Slim 4 *********************** .[perex] -Este artigo escrito por "Daniel Opitz":https://odan.github.io/2022/04/06/slim4-latte.html descreve como usar o Latte com a estrutura Slim. +Este artigo, cujo autor é "Daniel Opitz":https://odan.github.io/2022/04/06/slim4-latte.html, descreve o uso de Latte com o Slim Framework. -Primeiro, "instalar o Slim Framework":https://odan.github.io/2019/11/05/slim4-tutorial.html e depois o Latte usando o Composer: +Primeiro, "instale o Slim Framework":https://odan.github.io/2019/11/05/slim4-tutorial.html e depois o Latte usando o Composer: ```shell composer require latte/latte ``` -Configuração .[#toc-configuration] ----------------------------------- +Configuração +------------ -Crie um novo diretório `templates` no diretório raiz de seu projeto. Todos os modelos serão colocados lá mais tarde. +No diretório raiz do projeto, crie um novo diretório `templates`. Todos os templates serão colocados nele posteriormente. -Adicione uma nova chave de configuração `template` em seu arquivo `config/defaults.php`: +No arquivo `config/defaults.php`, adicione uma nova chave de configuração `template`: ```php $settings['template'] = __DIR__ . '/../templates'; ``` -O Latte compila os modelos em código PHP nativo e os armazena em um cache no disco. Portanto, eles são tão rápidos como se tivessem sido escritos em PHP nativo. +Latte compila os templates em código PHP nativo e os armazena em cache no disco. Eles são, portanto, tão rápidos quanto se fossem escritos em PHP nativo. -Adicione uma nova chave de configuração `template_temp` em seu arquivo `config/defaults.php`: Certifique-se de que o diretório `{project}/tmp/templates` existe e tem permissões de acesso de leitura e escrita. +No arquivo `config/defaults.php`, adicione uma nova chave de configuração `template_temp`: Certifique-se de que o diretório `{project}/tmp/templates` exista e tenha permissões de leitura e escrita. ```php $settings['template_temp'] = __DIR__ . '/../tmp/templates'; ``` -O Latte regenera automaticamente o cache cada vez que você muda o modelo, que pode ser desligado no ambiente de produção para economizar um pouco de desempenho: +Latte regenera automaticamente o cache sempre que o template é alterado, o que pode ser desativado no ambiente de produção para economizar um pouco de desempenho: ```php -// mudança para falso no ambiente de produção +// em ambiente de produção, mude para false $settings['template_auto_refresh'] = true; ``` -Em seguida, acrescente uma definição de recipiente DI para a classe `Latte\Engine`. +Em seguida, adicione a definição do contêiner DI para a classe `Latte\Engine`. ```php +
                                                                                                  {foreach $items as $item}
                                                                                                • {$item|capitalize}
                                                                                                • {/foreach}
                                                                                                ``` -Se tudo estiver configurado corretamente, você deve ver a seguinte saída: +Se tudo estiver configurado corretamente, a seguinte saída deve ser exibida: ```latte One @@ -155,4 +155,3 @@ Three ``` {{priority: -1}} -{{leftbar: /@left-menu}} diff --git a/latte/pt/creating-extension.texy b/latte/pt/creating-extension.texy deleted file mode 100644 index 38974b74e5..0000000000 --- a/latte/pt/creating-extension.texy +++ /dev/null @@ -1,579 +0,0 @@ -Criando uma extensão -******************** - -.[perex]{data-version:3.0} -Uma extensão é uma classe reutilizável que pode definir etiquetas personalizadas, filtros, funções, fornecedores, etc. - -Criamos extensões quando queremos reutilizar nossas personalizações de Latte em diferentes projetos ou compartilhá-las com outros. -Também é útil criar uma extensão para cada projeto web que conterá todas as tags e filtros específicos que você deseja usar nos modelos de projeto. - - -Classe de Extensão .[#toc-extension-class] -========================================== - -A extensão é uma classe herdada de [api:Latte\Extension]. É registrada na Latte usando `addExtension()` (ou via [arquivo de configuração |application:configuration#Latte]): - -```php -$latte = new Latte\Engine; -$latte->addExtension(new MyLatteExtension); -``` - -Se você registrar múltiplas extensões e elas definirem tags, filtros ou funções com nomes idênticos, a última extensão adicionada ganha. Isto também implica que suas extensões podem anular as tags/filtros/funções nativas. - -Sempre que você fizer uma mudança em uma classe e a atualização automática não for desligada, o Latte recompilará automaticamente seus modelos. - -Uma classe pode implementar qualquer um dos seguintes métodos: - -```php -abstract class Extension -{ - /** - * Initializes before template is compiler. - */ - public function beforeCompile(Engine $engine): void; - - /** - * Returns a list of parsers for Latte tags. - * @return array - */ - public function getTags(): array; - - /** - * Returns a list of compiler passes. - * @return array - */ - public function getPasses(): array; - - /** - * Returns a list of |filters. - * @return array - */ - public function getFilters(): array; - - /** - * Returns a list of functions used in templates. - * @return array - */ - public function getFunctions(): array; - - /** - * Returns a list of providers. - * @return array - */ - public function getProviders(): array; - - /** - * Returns a value to distinguish multiple versions of the template. - */ - public function getCacheKey(Engine $engine): mixed; - - /** - * Initializes before template is rendered. - */ - public function beforeRender(Template $template): void; -} -``` - -Para ter uma idéia de como é a extensão, dê uma olhada no "CoreExtension":https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php incorporado. - - -beforeCompile(Latte\Engine $engine): void .[method] ---------------------------------------------------- - -Chamado antes que o modelo seja compilado. O método pode ser usado para inicializações relacionadas à compilação, por exemplo. - - -getTags(): array .[method] --------------------------- - -Chamado quando o modelo é compilado. Retorna uma matriz associativa *nome da etiqueta => chamável*, que são [funções de análise da etiqueta |#Tag Parsing Function]. - -```php -public function getTags(): array -{ - return [ - 'foo' => [FooNode::class, 'create'], - 'bar' => [BarNode::class, 'create'], - 'n:baz' => [NBazNode::class, 'create'], - // ... - ]; -} -``` - -A tag `n:baz` representa um atributo n:puro, ou seja, é uma tag que só pode ser escrita como um atributo. - -No caso das tags `foo` e `bar`, Latte reconhecerá automaticamente se são pares, e se for o caso, podem ser escritas automaticamente usando n:attributes, incluindo variantes com os prefixos `n:inner-foo` e `n:tag-foo`. - -A ordem de execução de tais n:atributos é determinada por sua ordem na matriz devolvida por `getTags()`. Assim, `n:foo` é sempre executado antes de `n:bar`, mesmo que os atributos sejam listados em ordem inversa na tag HTML como `
                                                                                                `. - -Se você precisar determinar a ordem de n:atributos através de múltiplas extensões, use o método helper `order()`, onde o parâmetro `before` xou `after` determina quais tags são encomendadas antes ou depois da tag. - -```php -public function getTags(): array -{ - return [ - 'foo' => self::order([FooNode::class, 'create'], before: 'bar')] - 'bar' => self::order([BarNode::class, 'create'], after: ['block', 'snippet'])] - ]; -} -``` - - -getPasses(): array .[method] ----------------------------- - -É chamado quando o modelo é compilado. Retorna um array associativo *name pass => chamado*, que são funções que representam os chamados [passes de compilação |#compiler passes] que atravessam e modificam o AST. - -Mais uma vez, o método helper `order()` pode ser usado. O valor dos parâmetros `before` ou `after` pode ser `*` com o significado antes/depois de tudo. - -```php -public function getPasses(): array -{ - return [ - 'optimize' => [Passes::class, 'optimizePass'], - 'sandbox' => self::order([$this, 'sandboxPass'], before: '*'), - // ... - ]; -} -``` - - -beforeRender(Latte\Engine $engine): void .[method] --------------------------------------------------- - -Ela é chamada antes de cada renderização de modelo. O método pode ser usado, por exemplo, para inicializar as variáveis utilizadas durante a renderização. - - -getFilters(): array .[method] ------------------------------ - -Ela é chamada antes de o modelo ser apresentado. Retorna os [filtros |extending-latte#filters] como uma matriz associativa * nome do filtro => chamável*. - -```php -public function getFilters(): array -{ - return [ - 'batch' => [$this, 'batchFilter'], - 'trim' => [$this, 'trimFilter'], - // ... - ]; -} -``` - - -getFunctions(): array .[method] -------------------------------- - -Ela é chamada antes de o modelo ser apresentado. Retorna [funções |extending-latte#functions] como uma matriz associativa *nome da função => chamável*. - -```php -public function getFunctions(): array -{ - return [ - 'clamp' => [$this, 'clampFunction'], - 'divisibleBy' => [$this, 'divisibleByFunction'], - // ... - ]; -} -``` - - -getProviders(): array .[method] -------------------------------- - -Ela é chamada antes de o modelo ser apresentado. Retorna um conjunto de fornecedores, que geralmente são objetos que usam tags em tempo de execução. Eles são acessados via `$this->global->...`. - -```php -public function getProviders(): array -{ - return [ - 'myFoo' => $this->foo, - 'myBar' => $this->bar, - // ... - ]; -} -``` - - -getCacheKey(Latte\Engine $engine): mixed .[method] --------------------------------------------------- - -Ela é chamada antes de o modelo ser apresentado. O valor de retorno torna-se parte da chave cujo hash está contido no nome do arquivo do modelo compilado. Assim, para diferentes valores de retorno, o Latte gerará diferentes arquivos de cache. - - -Como funciona o Latte? .[#toc-how-does-latte-work] -================================================== - -Para entender como definir etiquetas personalizadas ou passes de compilador, é essencial entender como o Latte trabalha sob o capô. - -A compilação de modelos em Latte simplisticamente funciona assim: - -- Em primeiro lugar, o **lexer** transforma o código fonte do modelo em pequenos pedaços (fichas) para facilitar o processamento -- Então, o **parser** converte o fluxo de fichas em uma árvore significativa de nós (a árvore de sintaxe abstrata, AST) -- Finalmente, o compilador **gera** uma classe PHP da AST que renderiza o modelo e o armazena em cache. - -Na verdade, a compilação é um pouco mais complicada. Latte ** tem dois** lexers e parsers: um para o modelo HTML e outro para o código tipo PHP dentro das tags. Além disso, o analisador não funciona após a tokenization, mas o lexer e o analisador funcionam em paralelo em dois "fios" e coordenados. É a ciência do foguete :-) - -Além disso, todas as etiquetas têm suas próprias rotinas de análise. Quando o analisador encontra uma tag, ele chama sua função de análise (ele retorna [Extensão::getTags()) |#getTags]). -Seu trabalho é analisar os argumentos da tag e, no caso de tags emparelhadas, o conteúdo interno. Ele retorna um *node* que se torna parte do AST. Veja a [função de análise de tags |#Tag parsing function] para detalhes. - -Quando o analisador termina seu trabalho, temos um AST completo representando o modelo. O nó de raiz é `Latte\Compiler\Nodes\TemplateNode`. Os nós individuais dentro da árvore representam então não apenas as tags, mas também os elementos HTML, seus atributos, quaisquer expressões usadas dentro das tags, etc. - -Depois disso, entram em jogo os chamados [Passes do Compilador |#Compiler passes], que são funções (retornadas por [Extensão::getPasses()) |#getPasses] que modificam o AST. - -Todo o processo, desde o carregamento do conteúdo do modelo, passando pela análise, até a geração do arquivo resultante, pode ser sequenciado com este código, que você pode experimentar e descarregar os resultados intermediários: - -```php -$latte = new Latte\Engine; -$source = $latte->getLoader()->getContent($file); -$ast = $latte->parse($source); -$latte->applyPasses($ast); -$code = $latte->generate($ast, $file); -``` - - -Exemplo de AST .[#toc-example-of-ast] -------------------------------------- - -Para se ter uma idéia melhor do AST, adicionamos uma amostra. Este é o modelo da fonte: - -```latte -{foreach $category->getItems() as $item} -
                                                                                              1. {$item->name|upper}
                                                                                              2. - {else} - no items found -{/foreach} -``` - -E esta é sua representação sob a forma de AST: - -/--pre -Latte\Compiler\Nodes\TemplateNode( - Latte\Compiler\Nodes\FragmentNode( - - Latte\Essential\Nodes\ForeachNode( - expression: Latte\Compiler\Nodes\Php\Expression\MethodCallNode( - object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$category') - name: Latte\Compiler\Nodes\Php\IdentifierNode('getItems') - ) - value: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') - content: Latte\Compiler\Nodes\FragmentNode( - - Latte\Compiler\Nodes\TextNode(' ') - - Latte\Compiler\Nodes\Html\ElementNode('li')( - content: Latte\Essential\Nodes\PrintNode( - expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode( - object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') - name: Latte\Compiler\Nodes\Php\IdentifierNode('name') - ) - modifier: Latte\Compiler\Nodes\Php\ModifierNode( - filters: - - Latte\Compiler\Nodes\Php\FilterNode('upper') - ) - ) - ) - ) - else: Latte\Compiler\Nodes\FragmentNode( - - Latte\Compiler\Nodes\TextNode('no items found') - ) - ) - ) -) -\-- - - -Etiquetas personalizadas .[#toc-custom-tags] -============================================ - -Três passos são necessários para definir uma nova etiqueta: - -- definição da [função de análise da etiqueta |#tag parsing function] (responsável pela análise da etiqueta em um nó) -- criação de uma classe de nó (responsável pela [geração de código PHP |#generating PHP code] e [AST traversing |#AST traversing]) -- registrando a etiqueta usando [Extensão::getTags() |#getTags] - - -Função Tag Parsing .[#toc-tag-parsing-function] ------------------------------------------------ - -A análise das tags é tratada por sua função de análise (aquela retornada por [Extensão::getTags()) |#getTags]). Sua função é analisar e verificar quaisquer argumentos dentro da tag (para isso, usa TagParser). -Além disso, se a tag for um par, ele pedirá ao TemplateParser para analisar e retornar o conteúdo interno. -A função cria e retorna um nó, que geralmente é uma criança de `Latte\Compiler\Nodes\StatementNode`, e isto se torna parte do AST. - -Criamos uma classe para cada nó, o que faremos agora, e colocamos elegantemente a função de análise dentro dela como uma fábrica estática. Como exemplo, vamos tentar criar a conhecida etiqueta `{foreach}`: - -```php -use Latte\Compiler\Nodes\StatementNode; - -class ForeachNode extends StatementNode -{ - // a parsing function that just creates a node for now - public static function create(Latte\Compiler\Tag $tag): self - { - $node = new self; - return $node; - } - - public function print(Latte\Compiler\PrintContext $context): string - { - // code will be added later - } - - public function &getIterator(): \Generator - { - // code will be added later - } -} -``` - -A função de análise `create()` passa por um objeto [api:Latte\Compiler\Tag], que traz informações básicas sobre a tag (se é uma tag clássica ou n:atributo, em que linha ela está, etc.) e acessa principalmente o [api:Latte\Compiler\TagParser] em `$tag->parser`. - -Se a etiqueta deve ter argumentos, verifique a existência deles ligando para `$tag->expectArguments()`. Os métodos do objeto `$tag->parser` estão disponíveis para analisá-los: - -- `parseExpression(): ExpressionNode` para uma expressão semelhante a PHP (por exemplo, `10 + 3`) -- `parseUnquotedStringOrExpression(): ExpressionNode` para uma expressão ou fio não-calçado -- `parseArguments(): ArrayNode` conteúdo da matriz (por exemplo `10, true, foo => bar`) -- `parseModifier(): ModifierNode` para um modificador (por exemplo, `|upper|truncate:10`) -- `parseType(): expressionNode` para dactilografia (por exemplo `int|string` ou `Foo\Bar[]`) - -e um baixo nível [api:Latte\Compiler\TokenStream] operando diretamente com fichas: - -- `$tag->parser->stream->consume(...): Token` -- `$tag->parser->stream->tryConsume(...): ?Token` - -Latte estende a sintaxe PHP de pequenas maneiras, por exemplo, adicionando modificadores, operadores ternários encurtados ou permitindo que simples cadeias alfanuméricas sejam escritas sem aspas. É por isso que usamos o termo *PHP-like* em vez de PHP. Assim, o método `parseExpression()` analisa `foo` como `'foo'`, por exemplo. -Além disso, *unquoted-string* é um caso especial de uma string que também não precisa ser citada, mas, ao mesmo tempo, não precisa ser alfanumérica. Por exemplo, é o caminho para um arquivo na tag `{include ../file.latte}`. O método `parseUnquotedStringOrExpression()` é usado para analisá-lo. - -.[note] -Estudar as aulas de nó que fazem parte do Latte é a melhor maneira de aprender todos os detalhes do processo de análise. - -Voltemos à tag `{foreach}`. Nela, esperamos argumentos do formulário `expression + 'as' + second expression`, que analisamos a seguir: - -```php -use Latte\Compiler\Nodes\StatementNode; -use Latte\Compiler\Nodes\Php\ExpressionNode; -use Latte\Compiler\Nodes\AreaNode; - -class ForeachNode extends StatementNode -{ - public ExpressionNode $expression; - public ExpressionNode $value; - - public static function create(Latte\Compiler\Tag $tag): self - { - $tag->expectArguments(); - $node = new self; - $node->expression = $tag->parser->parseExpression(); - $tag->parser->stream->consume('as'); - $node->value = $parser->parseExpression(); - return $node; - } -} -``` - -As expressões que escrevemos nas variáveis `$expression` e `$value` representam subnós. - -.[tip] -Definir variáveis com subnós como **público*** para que elas possam ser modificadas em [etapas de processamento adicionais |#Compiler Passes], se necessário. Também é necessário **fazê-los disponíveis** para a [travessia |#AST Traversing]. - -Para etiquetas pareadas, como a nossa, o método também deve deixar o TemplateParser analisar o conteúdo interno da etiqueta. Isto é tratado por `yield`, que retorna um par ''[conteúdo interno, etiqueta final]''. Nós armazenamos o conteúdo interno na variável `$node->content`. - -```php -public AreaNode $content; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $endTag] = yield; - return $node; -} -``` - -A palavra-chave `yield` faz com que o método `create()` termine, devolvendo o controle de volta ao TemplateParser, que continua analisando o conteúdo até atingir a etiqueta final. Em seguida, ele passa o controle de volta para `create()`, que continua de onde ele parou. Usando o `yield`, o método retorna automaticamente `Generator`. - -Você também pode passar uma série de nomes de tags para `yield` para os quais você quer parar de analisar se eles ocorrem antes da tag final. Isto nos ajuda a implementar o `{foreach}...{else}...{/foreach}` construir. Se `{else}` ocorrer, nós analisaremos o conteúdo depois disso em `$node->elseContent`: - -```php -public AreaNode $content; -public ?AreaNode $elseContent = null; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $nextTag] = yield ['else']; - if ($nextTag?->name === 'else') { - [$node->elseContent] = yield; - } - - return $node; -} -``` - -O nó de retorno completa a análise da etiqueta. - - -Geração de código PHP .[#toc-generating-php-code] -------------------------------------------------- - -Cada nó deve implementar o método `print()`. Retorna o código PHP que torna a parte dada do modelo (código de tempo de execução). É passado um objeto [api:Latte\Compiler\PrintContext] como parâmetro, que tem um método útil `format()` que simplifica a montagem do código resultante. - -O método `format(string $mask, ...$args)` aceita os seguintes lugares na máscara: -- `%node` imprime o Nó -- `%dump` exporta o valor para o PHP -- `%raw` insere o texto diretamente sem qualquer transformação -- `%args` imprime o ArrayNode como argumento para a chamada de função -- `%line` imprime um comentário com um número de linha -- `%escape(...)` escapa do conteúdo -- `%modify(...)` aplica um modificador -- `%modifyContent(...)` aplica um modificador aos blocos - - -Nossa função `print()` pode parecer assim (negligenciamos o ramo `else` por simplicidade): - -```php -public function print(Latte\Compiler\PrintContext $context): string -{ - return $context->format( - <<<'XX' - foreach (%node as %node) %line { - %node - } - - XX, - $this->expression, - $this->value, - $this->position, - $this->content, - ); -} -``` - -A variável `$this->position` já está definida pela classe [api:Latte\Compiler\Node] e é definida pelo analisador. Ela contém um objeto [api:Latte\Compiler\Position] com a posição da tag no código fonte sob a forma de um número de linha e coluna. - -O código de tempo de execução pode usar variáveis auxiliares. Para evitar colisão com as variáveis utilizadas pelo próprio modelo, é convenção prefixá-las com caracteres `$ʟ__`. - -Também pode usar valores arbitrários em tempo de execução, que são passados para o modelo na forma de provedores usando o método [Extension::getProviders() |#getProviders]. Ele os acessa usando `$this->global->...`. - - -AST Traversing .[#toc-ast-traversing] -------------------------------------- - -A fim de atravessar a árvore AST em profundidade, é necessário implementar o método `getIterator()`. Isto dará acesso aos subnós: - -```php -public function &getIterator(): \Generator -{ - yield $this->expression; - yield $this->value; - yield $this->content; - if ($this->elseContent) { - yield $this->elseContent; - } -} -``` - -Note que `getIterator()` retorna uma referência. Isto é o que permite aos visitantes dos nós substituir os nós individuais por outros nós. - -.[warning] -Se um nó tem subnós, é necessário implementar este método e tornar todos os subnós disponíveis. Caso contrário, poderia ser criado um buraco de segurança. Por exemplo, o modo sandbox não seria capaz de controlar os subnós e garantir que construções não permitidas não sejam chamadas neles. - -Como a palavra-chave `yield` deve estar presente no corpo do método, mesmo que não tenha nós de criança, escreva-a da seguinte forma: - -```php -public function &getIterator(): \Generator -{ - if (false) { - yield; - } -} -``` - - -Passes de Compilador .[#toc-compiler-passes] -============================================ - -Os passes de compilador são funções que modificam os ASTs ou coletam informações neles. Eles são devolvidos pelo método [Extension::getPasses() |#getPasses]. - - -Nó Traverser .[#toc-node-traverser] ------------------------------------ - -A forma mais comum de trabalhar com o AST é utilizando um [api:Latte\Compiler\NodeTraverser]: - -```php -use Latte\Compiler\Node; -use Latte\Compiler\NodeTraverser; - -$ast = (new NodeTraverser)->traverse( - $ast, - enter: fn(Node $node) => ..., - leave: fn(Node $node) => ..., -); -``` - -A função *enter* (ou seja, visitante) é chamada quando um nó é encontrado pela primeira vez, antes de seus subnós serem processados. A função *saída* é chamada após todos os subnós terem sido visitados. -Um padrão comum é que o *enter* é usado para coletar algumas informações e depois *leave* realiza modificações com base nisso. No momento em que *saída* é chamada, todo o código dentro do nó já terá sido visitado e as informações necessárias já terão sido coletadas. - -Como modificar a AST? A maneira mais fácil é simplesmente mudar as propriedades dos nós. A segunda maneira é substituir o nó inteiramente retornando um novo nó. Exemplo: o seguinte código mudará todos os inteiros no AST para strings (por exemplo, 42 será mudado para `'42'`). - -```php -use Latte\Compiler\Nodes\Php; - -$ast = (new NodeTraverser)->traverse( - $ast, - leave: function (Node $node) { - if ($node instanceof Php\Scalar\IntegerNode) { - return new Php\Scalar\StringNode((string) $node->value); - } - }, -); -``` - -Um AST pode facilmente conter milhares de nós, e atravessar sobre todos eles pode ser lento. Em alguns casos, é possível evitar uma travessia completa. - -Se você está procurando todos `Html\ElementNode` em uma árvore, você sabe que uma vez que você tenha visto `Php\ExpressionNode`, não adianta verificar também todos os seus nós infantis, porque o HTML não pode estar dentro de expressões. Neste caso, você pode instruir o atravessador a não entrar novamente no nó de classe: - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Php\ExpressionNode) { - return NodeTraverser::DontTraverseChildren; - } - // ... - }, -); -``` - -Se você estiver procurando apenas um nó específico, também é possível abortar a travessia inteiramente após encontrá-lo. - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Nodes\ParametersNode) { - return NodeTraverser::StopTraversal; - } - // ... - }, -); -``` - - -Ajudantes de Nó .[#toc-node-helpers] ------------------------------------- - -A classe [api:Latte\Compiler\NodeHelpers] fornece alguns métodos que podem encontrar nós AST que satisfazem um determinado retorno de chamada, etc. Alguns exemplos são mostrados: - -```php -use Latte\Compiler\NodeHelpers; - -// encontra todos os nós de elementos HTML -$elements = NodeHelpers::find($ast, fn(Node $node) => $node instanceof Nodes\Html\ElementNode); - -// encontra o primeiro nó de texto -$text = NodeHelpers::findFirst($ast, fn(Node $node) => $node instanceof Nodes\TextNode); - -// converte o nó de valor PHP em valor real -$value = NodeHelpers::toValue($node); - -// converte o nó de texto estático em string -$text = NodeHelpers::toText($node); -``` diff --git a/latte/pt/custom-filters.texy b/latte/pt/custom-filters.texy new file mode 100644 index 0000000000..df67c972e9 --- /dev/null +++ b/latte/pt/custom-filters.texy @@ -0,0 +1,231 @@ +Criando filtros personalizados +****************************** + +.[perex] +Filtros são ferramentas poderosas para formatar e modificar dados diretamente nos templates Latte. Eles oferecem uma sintaxe limpa usando o símbolo de pipe (`|`) para transformar variáveis ou resultados de expressões no formato de saída desejado. + + +O que são filtros? +================== + +Filtros no Latte são essencialmente **funções PHP projetadas especificamente para transformar um valor de entrada num valor de saída**. São aplicados usando a notação de pipe (`|`) dentro das expressões do template (`{...}`). + +**Conveniência:** Filtros permitem encapsular tarefas comuns de formatação (como formatação de datas, alteração de maiúsculas/minúsculas, truncagem) ou manipulação de dados em unidades reutilizáveis. Em vez de repetir código PHP complexo nos seus templates, você pode simplesmente aplicar um filtro: +```latte +{* Em vez de PHP complexo para truncar: *} +{$article->text|truncate:100} + +{* Em vez de código para formatar datas: *} +{$event->startTime|date:'Y-m-d H:i'} + +{* Aplicando múltiplas transformações: *} +{$product->name|lower|capitalize} +``` + +**Legibilidade:** Usar filtros torna os templates mais limpos e focados na apresentação, pois a lógica de transformação é movida para a definição do filtro. + +**Sensibilidade ao contexto:** Uma vantagem chave dos filtros no Latte é a sua capacidade de serem [sensíveis ao contexto |#Filtros contextuais]. Isto significa que o filtro pode reconhecer o tipo de conteúdo com o qual está a trabalhar (HTML, JavaScript, texto simples, etc.) e aplicar a lógica ou o escaping correspondente, o que é crucial para a segurança e a correção, especialmente ao gerar HTML. + +**Integração com a lógica da aplicação:** Assim como as funções personalizadas, o PHP callable por trás de um filtro pode ser um closure, um método estático ou um método de instância. Isto permite que os filtros acedam a serviços ou dados da aplicação, se necessário, embora o seu propósito principal permaneça a *transformação do valor de entrada*. + +O Latte fornece por padrão um rico conjunto de [filtros padrão |filters]. Filtros personalizados permitem estender este conjunto com formatações e transformações específicas do seu projeto. + +Se você precisa executar lógica baseada em *múltiplas* entradas ou não tem um valor primário para transformar, provavelmente é mais adequado usar uma [função personalizada |custom-functions]. Se você precisa gerar marcação complexa ou controlar o fluxo do template, considere uma [tag personalizada |custom-tags]. + + +Criando e registando filtros +============================ + +Existem várias maneiras de definir e registar filtros personalizados no Latte. + + +Registo direto via `addFilter()` +-------------------------------- + +A maneira mais simples de adicionar um filtro é usar o método `addFilter()` diretamente no objeto `Latte\Engine`. Você especifica o nome do filtro (como será usado no template) e o PHP callable correspondente. + +```php +$latte = new Latte\Engine; + +// Filtro simples sem argumentos +$latte->addFilter('initial', fn(string $s): string => mb_substr($s, 0, 1) . '.'); + +// Filtro com argumento opcional +$latte->addFilter('shortify', function (string $s, int $len = 10): string { + return mb_substr($s, 0, $len); +}); + +// Filtro processando arrays +$latte->addFilter('sum', fn(array $numbers): int|float => array_sum($numbers)); +``` + +**Uso no template:** + +```latte +{$name|initial} {* Imprime 'J.' se $name for 'John' *} +{$description|shortify} {* Usa o comprimento padrão 10 *} +{$description|shortify:50} {* Usa o comprimento 50 *} +{$prices|sum} {* Imprime a soma dos itens no array $prices *} +``` + +**Passagem de argumentos:** + +O valor à esquerda do pipe (`|`) é sempre passado como o *primeiro* argumento para a função do filtro. Quaisquer parâmetros listados após os dois pontos (`:`) no template são passados como os argumentos seguintes. + +```latte +{$text|shortify:30} +// Chama a função PHP shortify($text, 30) +``` + + +Registo via extensão +-------------------- + +Para melhor organização, especialmente ao criar conjuntos reutilizáveis de filtros ou partilhá-los como pacotes, a maneira recomendada é registá-los dentro de uma [extensão Latte |extending-latte#Latte Extension]: + +```php +namespace App\Latte; + +use Latte\Extension; + +class MyLatteExtension extends Extension +{ + public function getFilters(): array + { + return [ + 'initial' => $this->initial(...), + 'shortify' => $this->shortify(...), + ]; + } + + public function initial(string $s): string + { + return mb_substr($s, 0, 1) . '.'; + } + + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// Registo +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +Esta abordagem mantém a lógica do seu filtro encapsulada e o registo simples. + + +Usando o carregador de filtros +------------------------------ + +O Latte permite registar um carregador de filtros usando `addFilterLoader()`. É um único callable que o Latte solicitará para qualquer nome de filtro desconhecido durante a compilação. O carregador retorna o PHP callable do filtro ou `null`. + +```php +$latte = new Latte\Engine; + +// O carregador pode criar/obter callables de filtro dinamicamente +$latte->addFilterLoader(function (string $name): ?callable { + if ($name === 'myLazyFilter') { + // Imagine aqui uma inicialização custosa... + $service = get_some_expensive_service(); + return fn($value) => $service->process($value); + } + return null; +}); +``` + +Este método foi projetado principalmente para carregamento lento de filtros com inicialização muito **custosa**. No entanto, práticas modernas de injeção de dependência geralmente lidam com serviços lentos de forma mais eficiente. + +Carregadores de filtros adicionam complexidade e geralmente não são recomendados em favor do registo direto usando `addFilter()` ou dentro de uma extensão usando `getFilters()`. Use carregadores apenas se tiver uma razão séria e específica relacionada a problemas de desempenho na inicialização de filtros que não podem ser resolvidos de outra forma. + + +Filtros usando uma classe com atributos +--------------------------------------- + +Outra maneira elegante de definir filtros é usar métodos na sua [classe de parâmetros do template |develop#Parâmetros como classe]. Basta adicionar o atributo `#[Latte\Attributes\TemplateFilter]` ao método. + +```php +use Latte\Attributes\TemplateFilter; + +class TemplateParameters +{ + public function __construct( + public string $description, + // outros parâmetros... + ) {} + + #[TemplateFilter] + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// Passando o objeto para o template +$params = new TemplateParameters(description: '...'); +$latte->render('template.latte', $params); +``` + +O Latte reconhecerá e registará automaticamente os métodos marcados com este atributo quando o objeto `TemplateParameters` for passado para o template. O nome do filtro no template será o mesmo que o nome do método (`shortify` neste caso). + +```latte +{* Usando o filtro definido na classe de parâmetros *} +{$description|shortify:50} +``` + + +Filtros contextuais +=================== + +Às vezes, um filtro precisa de mais informações do que apenas o valor de entrada. Pode precisar saber o **tipo de conteúdo** da string com a qual está a trabalhar (por exemplo, HTML, JavaScript, texto simples) ou até mesmo modificá-lo. Esta é a situação para filtros contextuais. + +Um filtro contextual é definido da mesma forma que um filtro comum, mas o seu **primeiro parâmetro deve ser** tipado como `Latte\Runtime\FilterInfo`. O Latte reconhece automaticamente esta assinatura e, ao chamar o filtro, passa o objeto `FilterInfo`. Os parâmetros seguintes recebem os argumentos do filtro como de costume. + +```php +use Latte\Runtime\FilterInfo; +use Latte\ContentType; + +$latte->addFilter('money', function (FilterInfo $info, float $amount): string { + // 1. Verifique o tipo de conteúdo de entrada (opcional, mas recomendado) + // Permita null (entrada variável) ou texto simples. Rejeite se aplicado a HTML, etc. + if (!in_array($info->contentType, [null, ContentType::Text], true)) { + $actualType = $info->contentType ?? 'mixed'; + throw new \RuntimeException( + "O filtro |money foi usado num tipo de conteúdo incompatível $actualType. Esperado texto ou null." + ); + } + + // 2. Realize a transformação + $formatted = number_format($amount, 2, '.', ',') . ' EUR'; + $htmlOutput = '' . htmlspecialchars($formatted) . ''; // Garanta o escaping correto! + + // 3. Declare o tipo de conteúdo de saída + $info->contentType = ContentType::Html; + + // 4. Retorne o resultado + return $htmlOutput; +}); +``` + +`$info->contentType` é uma constante de string de `Latte\ContentType` (por exemplo, `ContentType::Html`, `ContentType::Text`, `ContentType::JavaScript`, etc.) ou `null` se o filtro for aplicado a uma variável (`{$var|filter}`). Você pode **ler** este valor para verificar o contexto de entrada e **escrever** nele para declarar o tipo do contexto de saída. + +Ao definir o tipo de conteúdo para HTML, você informa ao Latte que a string retornada pelo seu filtro é HTML seguro. O Latte então **não** aplicará o seu escaping automático padrão a este resultado. Isto é crucial se o seu filtro gera marcação HTML. + +.[warning] +Se o seu filtro gera HTML, **você é responsável por escapar corretamente quaisquer dados de entrada** usados neste HTML (como no caso da chamada `htmlspecialchars($formatted)` acima). A omissão pode criar vulnerabilidades XSS. Se o seu filtro retorna apenas texto simples, você não precisa definir `$info->contentType`. + + +Filtros em blocos +----------------- + +Todos os filtros aplicados a [blocos |tags#block] *devem* ser contextuais. Isto ocorre porque o conteúdo do bloco tem um tipo de conteúdo definido (geralmente HTML), do qual o filtro precisa estar ciente. + +```latte +{block heading|money}1000{/block} +{* O filtro 'money' receberá '1000' como segundo argumento + e $info->contentType será ContentType::Html *} +``` + +Filtros contextuais fornecem controlo poderoso sobre como os dados são processados com base no seu contexto, permitem funcionalidades avançadas e garantem o comportamento correto do escaping, especialmente ao gerar conteúdo HTML. diff --git a/latte/pt/custom-functions.texy b/latte/pt/custom-functions.texy new file mode 100644 index 0000000000..ccfb389c77 --- /dev/null +++ b/latte/pt/custom-functions.texy @@ -0,0 +1,144 @@ +Criando funções personalizadas +****************************** + +.[perex] +Adicione facilmente funções auxiliares personalizadas aos templates Latte. Chame a lógica PHP diretamente nas expressões para cálculos, acesso a serviços ou geração de conteúdo dinâmico, mantendo os seus templates limpos e poderosos. + + +O que são funções? +================== + +Funções no Latte permitem estender o conjunto de funções que podem ser chamadas dentro das expressões nos templates (`{...}`). Você pode pensar nelas como **funções PHP personalizadas disponíveis apenas dentro dos seus templates Latte**. Isto traz várias vantagens: + +**Conveniência:** Você pode definir lógica auxiliar (como cálculos, formatação ou acesso a dados da aplicação) e chamá-la usando uma sintaxe de função simples e familiar diretamente no template, assim como chamaria `strlen()` ou `date()` em PHP. + +```latte +{var $userInitials = initials($userName)} {* por exemplo, 'J. D.' *} + +{if hasPermission('article', 'edit')} + Editar +{/if} +``` + +**Sem poluição do espaço global:** Ao contrário da definição de uma função global real em PHP, as funções Latte existem apenas no contexto de renderização do template. Você não precisa sobrecarregar o namespace global do PHP com auxiliares que são específicos apenas para templates. + +**Integração com a lógica da aplicação:** O PHP callable por trás de uma função Latte pode ser qualquer coisa – uma função anónima, um método estático ou um método de instância. Isto significa que as suas funções nos templates podem aceder facilmente aos serviços da aplicação, bases de dados, configuração ou qualquer outra lógica necessária capturando variáveis (no caso de funções anónimas) ou usando injeção de dependência (no caso de objetos). O exemplo `hasPermission` acima demonstra isto claramente, provavelmente chamando um serviço de autorização nos bastidores. + +**Sobrescrita de funções nativas (opcional):** Você pode até definir uma função Latte com o mesmo nome de uma função PHP nativa. No template, a sua versão personalizada será chamada em vez da função original. Isto pode ser útil para fornecer comportamento específico do template ou garantir processamento consistente (por exemplo, garantir que `strlen` seja sempre seguro para multibyte). Use esta funcionalidade com cuidado para evitar mal-entendidos. + +Por padrão, o Latte permite a chamada de *todas* as funções PHP nativas (a menos que restringidas pelo [Sandbox |sandbox]). Funções personalizadas estendem esta biblioteca integrada com as necessidades específicas do seu projeto. + +Se você está apenas a transformar um único valor, pode ser mais apropriado usar um [filtro personalizado |custom-filters]. + + +Criando e registando funções +============================ + +Assim como nos filtros, existem várias maneiras de definir e registar funções personalizadas. + + +Registo direto via `addFunction()` +---------------------------------- + +O método mais simples é usar `addFunction()` no objeto `Latte\Engine`. Você especifica o nome da função (como ela aparecerá no template) e o objeto PHP callable correspondente. + +```php +$latte = new Latte\Engine; + +// Função auxiliar simples +$latte->addFunction('initials', function (string $name): string { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; +}); +``` + +**Uso no template:** + +```latte +{var $userInitials = initials($userName)} +``` + +Os argumentos da função no template são passados diretamente para o objeto PHP callable na mesma ordem. Funcionalidades PHP como dicas de tipo, valores padrão e parâmetros variáveis (`...`) funcionam como esperado. + + +Registo via extensão +-------------------- + +Para melhor organização e reutilização, registe funções dentro de uma [extensão Latte |extending-latte#Latte Extension]. Esta abordagem é recomendada para aplicações mais complexas ou bibliotecas partilhadas. + +```php +namespace App\Latte; + +use Latte\Extension; +use Nette\Security\Authorizator; + +class MyLatteExtension extends Extension +{ + public function __construct( + // Assumindo que o serviço Authorizator existe + private Authorizator $authorizator, + ) { + } + + public function getFunctions(): array + { + // Registro de métodos como funções Latte + return [ + 'hasPermission' => $this->hasPermission(...), + ]; + } + + public function hasPermission(string $resource, string $action): bool + { + return $this->authorizator->isAllowed($resource, $action); + } +} + +// Registo (assumindo que $container contém o Contêiner DI) +$extension = $container->getByType(App\Latte\MyLatteExtension::class); +$latte = new Latte\Engine; +$latte->addExtension($extension); +``` + +Esta abordagem ilustra como as funções definidas no Latte podem ser apoiadas por métodos de objetos, que podem ter as suas próprias dependências geridas pelo contêiner de injeção de dependência da sua aplicação ou por uma fábrica. Isto mantém a lógica dos seus templates conectada ao núcleo da aplicação, mantendo ao mesmo tempo uma organização clara. + + +Funções usando uma classe com atributos +--------------------------------------- + +Assim como os filtros, as funções podem ser definidas como métodos na sua [classe de parâmetros do template |develop#Parâmetros como classe] usando o atributo `#[Latte\Attributes\TemplateFunction]`. + +```php +use Latte\Attributes\TemplateFunction; + +class TemplateParameters +{ + public function __construct( + public string $userName, + // outros parâmetros... + ) {} + + // Este método estará disponível como {initials(...)} no template + #[TemplateFunction] + public function initials(string $name): string + { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; + } +} + +// Passando o objeto para o template +$params = new TemplateParameters(userName: 'John Doe', /* ... */); +$latte->render('template.latte', $params); +``` + +O Latte descobrirá e registará automaticamente os métodos marcados com este atributo quando o objeto de parâmetros for passado para o template. O nome da função no template corresponde ao nome do método. + +```latte +{* Usando a função definida na classe de parâmetros *} +{var $inits = initials($userName)} +``` + +**Funções contextuais?** + +Ao contrário dos filtros, não existe um conceito direto de "funções contextuais" que receberiam um objeto semelhante a `FilterInfo`. As funções operam dentro de expressões e normalmente não precisam de acesso direto ao contexto de renderização ou informações de tipo de conteúdo da mesma forma que os filtros aplicados a blocos. diff --git a/latte/pt/custom-tags.texy b/latte/pt/custom-tags.texy new file mode 100644 index 0000000000..b080e9e20b --- /dev/null +++ b/latte/pt/custom-tags.texy @@ -0,0 +1,1135 @@ +Criando tags personalizadas +*************************** + +.[perex] +Esta página fornece um guia abrangente para criar tags personalizadas no Latte. Discutiremos tudo, desde tags simples até cenários mais complexos com conteúdo aninhado e necessidades específicas de parsing, com base na sua compreensão de como o Latte compila templates. + +Tags personalizadas fornecem o mais alto nível de controle sobre a sintaxe do template e a lógica de renderização, mas também são o ponto de extensão mais complexo. Antes de decidir criar uma tag personalizada, sempre considere se [não existe uma solução mais simples |extending-latte#Formas de estender o Latte] ou se uma tag adequada já existe no [conjunto padrão |tags]. Use tags personalizadas apenas quando alternativas mais simples não forem suficientes para suas necessidades. + + +Compreendendo o processo de compilação +====================================== + +Para criar tags personalizadas de forma eficaz, é útil explicar como o Latte processa templates. Compreender este processo esclarece por que as tags são estruturadas dessa forma e como elas se encaixam no contexto mais amplo. + +A compilação de um template no Latte, de forma simplificada, envolve estes passos principais: + +1. **Análise Léxica:** O lexer lê o código-fonte do template (arquivo `.latte`) e o divide em uma sequência de pequenas partes distintas chamadas **tokens** (por exemplo, `{`, `foreach`, `$variable`, `}`, texto HTML, etc.). +2. **Parsing:** O parser pega esse fluxo de tokens e constrói a partir dele uma estrutura de árvore significativa que representa a lógica e o conteúdo do template. Essa árvore é chamada de **árvore de sintaxe abstrata (AST)**. +3. **Passos de Compilação:** Antes de gerar o código PHP, o Latte executa [passos de compilação |compiler-passes]. São funções que percorrem toda a AST e podem modificá-la ou coletar informações. Este passo é crucial para funcionalidades como segurança ([Sandbox |sandbox]) ou otimização. +4. **Geração de Código:** Finalmente, o compilador percorre a AST (potencialmente modificada) e gera o código da classe PHP correspondente. Este código PHP é o que realmente renderiza o template durante a execução. +5. **Caching:** O código PHP gerado é armazenado em disco, o que torna as renderizações subsequentes muito rápidas, pois os passos 1-4 são pulados. + +Na realidade, a compilação é um pouco mais complexa. O Latte **tem dois** lexers e parsers: um para o template HTML e outro para o código tipo PHP dentro das tags. E também o parsing não ocorre após a tokenização, mas o lexer e o parser rodam em paralelo em duas "threads" e se coordenam. Acredite, programar isso foi ciência de foguetes :-) + +Todo o processo, desde o carregamento do conteúdo do template, passando pelo parsing, até a geração do arquivo final, pode ser sequenciado com este código, com o qual você pode experimentar e exibir resultados intermediários: + +```php +$latte = new Latte\Engine; +$source = $latte->getLoader()->getContent($file); +$ast = $latte->parse($source); +$latte->applyPasses($ast); +$code = $latte->generate($ast, $file); +``` + + +A anatomia de uma tag +===================== + +Criar uma tag personalizada totalmente funcional no Latte envolve várias partes interconectadas. Antes de mergulharmos na implementação, vamos entender os conceitos básicos e a terminologia, usando uma analogia com HTML e o Document Object Model (DOM). + + +Tags vs. Nós (Analogia com HTML) +-------------------------------- + +Em HTML, escrevemos **tags** como `

                                                                                                ` ou `

                                                                                                ...
                                                                                                `. Essas tags são a sintaxe no código-fonte. Quando o navegador analisa este HTML, ele cria uma representação na memória chamada **Document Object Model (DOM)**. No DOM, as tags HTML são representadas por **nós** (especificamente, nós `Element` na terminologia do DOM JavaScript). Trabalhamos programaticamente com esses *nós* (por exemplo, usando `document.getElementById(...)` do JavaScript, que retorna um nó Element). A tag é apenas uma representação textual no arquivo de origem; o nó é uma representação de objeto na árvore lógica. + +O Latte funciona de forma semelhante: + +- No arquivo de template `.latte`, você escreve **tags Latte**, como `{foreach ...}` e `{/foreach}`. Esta é a sintaxe com a qual você, como autor do template, trabalha. +- Quando o Latte **analisa (parses)** o template, ele constrói uma **Árvore de Sintaxe Abstrata (AST)**. Esta árvore é composta por **nós**. Cada tag Latte, elemento HTML, pedaço de texto ou expressão no template se torna um ou mais nós nesta árvore. +- A classe base para todos os nós na AST é `Latte\Compiler\Node`. Assim como o DOM tem diferentes tipos de nós (Element, Text, Comment), a AST do Latte tem diferentes tipos de nós. Você encontrará `Latte\Compiler\Nodes\TextNode` para texto estático, `Latte\Compiler\Nodes\Html\ElementNode` para elementos HTML, `Latte\Compiler\Nodes\Php\ExpressionNode` para expressões dentro de tags e, crucialmente para tags personalizadas, nós que herdam de `Latte\Compiler\Nodes\StatementNode`. + + +Por que `StatementNode`? +------------------------ + +Elementos HTML (`Html\ElementNode`) representam principalmente estrutura e conteúdo. Expressões PHP (`Php\ExpressionNode`) representam valores ou cálculos. Mas e as tags Latte como `{if}`, `{foreach}` ou nossa própria `{datetime}`? Essas tags *executam ações*, controlam o fluxo do programa ou geram saída com base na lógica. São unidades funcionais que tornam o Latte um poderoso *engine* de templates, não apenas uma linguagem de marcação. + +Na programação, essas unidades que executam ações são frequentemente chamadas de "statements" (instruções). Portanto, os nós que representam essas tags Latte funcionais normalmente herdam de `Latte\Compiler\Nodes\StatementNode`. Isso os distingue de nós puramente estruturais (como elementos HTML) ou nós que representam valores (como expressões). + + +Os componentes chave +==================== + +Vamos percorrer os principais componentes necessários para criar uma tag personalizada: + + +Função de parsing da tag +------------------------ + +- Esta função PHP callable analisa a sintaxe da tag Latte (`{...}`) no template de origem. +- Recebe informações sobre a tag (como seu nome, posição e se é um n:attribute) através do objeto [api:Latte\Compiler\Tag]. +- Sua ferramenta principal para analisar argumentos e expressões dentro dos delimitadores da tag é o objeto [api:Latte\Compiler\TagParser], acessível via `$tag->parser` (este é um parser diferente daquele que analisa todo o template). +- Para tags de par, usa `yield` para sinalizar ao Latte para analisar o conteúdo interno entre as tags de abertura e fechamento. +- O objetivo final da função de parsing é criar e retornar uma instância da **classe do nó**, que é adicionada à AST. +- É costume (embora não obrigatório) implementar a função de parsing como um método estático (frequentemente chamado `create`) diretamente na classe do nó correspondente. Isso mantém a lógica de parsing e a representação do nó organizadas em um único pacote, permite o acesso a elementos privados/protegidos da classe, se necessário, e melhora a organização. + + +Classe do nó +------------ + +- Representa a *função lógica* da sua tag na **Árvore de Sintaxe Abstrata (AST)**. +- Contém informações analisadas (como argumentos ou conteúdo) como propriedades públicas. Essas propriedades frequentemente contêm outras instâncias de `Node` (por exemplo, `ExpressionNode` para argumentos analisados, `AreaNode` para conteúdo analisado). +- O método `print(PrintContext $context): string` gera o *código PHP* (uma instrução ou série de instruções) que executa a ação da tag durante a renderização do template. +- O método `getIterator(): \Generator` expõe os nós filhos (argumentos, conteúdo) para travessia pelos **passos de compilação**. Deve fornecer referências (`&`) para permitir que os passos potencialmente modifiquem ou substituam subnós. +- Depois que todo o template é analisado na AST, o Latte executa uma série de [passos de compilação |compiler-passes]. Esses passos percorrem *toda* a AST usando o método `getIterator()` fornecido por cada nó. Eles podem inspecionar nós, coletar informações e até *modificar* a árvore (por exemplo, alterando propriedades públicas de nós ou substituindo nós inteiramente). Este design, que requer um `getIterator()` abrangente, é crucial. Ele permite que funcionalidades poderosas como [Sandbox |sandbox] analisem e potencialmente alterem o comportamento de *qualquer* parte do template, incluindo suas próprias tags personalizadas, garantindo segurança e consistência. + + +Registro via extensão +--------------------- + +- Você precisa informar o Latte sobre sua nova tag e qual função de parsing deve ser usada para ela. Isso é feito dentro de uma [extensão Latte |extending-latte#Latte Extension]. +- Dentro da sua classe de extensão, você implementa o método `getTags(): array`. Este método retorna um array associativo onde as chaves são os nomes das tags (por exemplo, `'mytag'`, `'n:myattribute'`) e os valores são funções PHP callable representando suas respectivas funções de parsing (por exemplo, `MyNamespace\DatetimeNode::create(...)`). + +Resumo: A **função de parsing da tag** transforma o *código-fonte do template* da sua tag em um **nó AST**. A **classe do nó** pode então transformar *a si mesma* em *código PHP* executável para o template compilado e expõe seus subnós para os **passos de compilação** via `getIterator()`. O **registro via extensão** conecta o nome da tag à função de parsing e informa o Latte sobre ela. + +Agora, vamos explorar como implementar esses componentes passo a passo. + + +Criando uma tag simples +======================= + +Vamos começar a criar sua primeira tag Latte personalizada. Começaremos com um exemplo muito simples: uma tag chamada `{datetime}` que exibe a data e hora atuais. **Inicialmente, esta tag não aceitará nenhum argumento**, mas a aprimoraremos posteriormente na seção [#Parsing de argumentos da tag]. Ela também não tem conteúdo interno. + +Este exemplo o guiará pelos passos básicos: definir a classe do nó, implementar seus métodos `print()` e `getIterator()`, criar a função de parsing e, finalmente, registrar a tag. + +**Objetivo:** Implementar `{datetime}` para exibir a data e hora atuais usando a função PHP `date()`. + + +Criação da classe do nó +----------------------- + +Primeiro, precisamos de uma classe que represente nossa tag na Árvore de Sintaxe Abstrata (AST). Conforme discutido acima, herdamos de `Latte\Compiler\Nodes\StatementNode`. + +Crie um arquivo (por exemplo, `DatetimeNode.php`) e defina a classe: + +```php +node = new self; + return $node; + } + + /** + * Gera o código PHP que será executado durante a renderização do template. + */ + public function print(PrintContext $context): string + { + return $context->format( + 'echo date(\'Y-m-d H:i:s\') %line;', + $this->position, + ); + } + + /** + * Fornece acesso aos nós filhos para os passos de compilação do Latte. + */ + public function &getIterator(): \Generator + { + false && yield; + } +} +``` + +Quando o Latte encontra `{datetime}` em um template, ele chama a função de parsing `create()`. Sua tarefa é retornar uma instância de `DatetimeNode`. + +O método `print()` gera o código PHP que será executado durante a renderização do template. Chamamos o método `$context->format()`, que monta a string final do código PHP para o template compilado. O primeiro argumento, `'echo date('Y-m-d H:i:s') %line;'`, é uma máscara na qual os parâmetros seguintes são inseridos. O placeholder `%line` diz ao método `format()` para usar o segundo argumento, que é `$this->position`, e inserir um comentário como `/* line 15 */`, que conecta o código PHP gerado de volta à linha original do template, o que é crucial para a depuração. + +A propriedade `$this->position` é herdada da classe base `Node` e é definida automaticamente pelo parser do Latte. Ela contém um objeto [api:Latte\Compiler\Position] que indica onde a tag foi encontrada no arquivo de origem `.latte`. + +O método `getIterator()` é essencial para os passos de compilação. Ele deve fornecer todos os nós filhos, mas nosso `DatetimeNode` simples atualmente não tem argumentos nem conteúdo, portanto, nenhum nó filho. No entanto, o método ainda deve existir e ser um gerador, ou seja, a palavra-chave `yield` deve estar presente de alguma forma no corpo do método. + + +Registro via extensão +--------------------- + +Finalmente, vamos informar o Latte sobre a nova tag. Crie uma [classe de extensão |extending-latte#Latte Extension] (por exemplo, `MyLatteExtension.php`) e registre a tag em seu método `getTags()`. + +```php + Mapa: 'nome-da-tag' => funcao-de-parsing + */ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + // Registre mais tags aqui posteriormente + ]; + } +} +``` + +Em seguida, registre esta extensão no Latte Engine: + +```php +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +Crie um template: + +```latte +

                                                                                                Página gerada em: {datetime}

                                                                                                +``` + +Saída esperada: `

                                                                                                Página gerada em: 2023-10-27 11:00:00

                                                                                                ` + + +Resumo desta fase +----------------- + +Criamos com sucesso uma tag personalizada básica `{datetime}`. Definimos sua representação na AST (`DatetimeNode`), tratamos seu parsing (`create()`), especificamos como ela deve gerar código PHP (`print()`), garantimos que seus filhos sejam acessíveis para travessia (`getIterator()`) e a registramos no Latte. + +Na próxima seção, aprimoraremos esta tag para aceitar argumentos e demonstraremos como analisar expressões e gerenciar nós filhos. + + +Parsing de argumentos da tag +============================ + +Nossa tag simples `{datetime}` funciona, mas não é muito flexível. Vamos aprimorá-la para aceitar um argumento opcional: uma string de formatação para a função `date()`. A sintaxe desejada será `{datetime $format}`. + +**Objetivo:** Modificar `{datetime}` para aceitar uma expressão PHP opcional como argumento, que será usada como string de formatação para `date()`. + + +Apresentando o `TagParser` +-------------------------- + +Antes de modificarmos o código, é importante entender a ferramenta que usaremos: [api:Latte\Compiler\TagParser]. Quando o parser principal do Latte (`TemplateParser`) encontra uma tag Latte como `{datetime ...}` ou um n:attribute, ele delega o parsing do conteúdo *dentro* da tag (a parte entre `{` e `}` ou o valor do atributo) para um `TagParser` especializado. + +Este `TagParser` trabalha exclusivamente com os **argumentos da tag**. Sua tarefa é processar os tokens que representam esses argumentos. Crucialmente, ele **deve processar todo o conteúdo** que lhe é fornecido. Se sua função de parsing terminar, mas o `TagParser` não tiver alcançado o final dos argumentos (verificado via `$tag->parser->isEnd()`), o Latte lançará uma exceção, pois isso indica que tokens inesperados foram deixados dentro da tag. Por outro lado, se a tag *requer* argumentos, você deve chamar `$tag->expectArguments()` no início da sua função de parsing. Este método verifica se os argumentos estão presentes e lança uma exceção útil se a tag foi usada sem nenhum argumento. + +O `TagParser` oferece métodos úteis para analisar diferentes tipos de argumentos: + +- `parseExpression(): ExpressionNode`: Analisa uma expressão semelhante a PHP (variáveis, literais, operadores, chamadas de função/método, etc.). Lida com o açúcar sintático do Latte, como tratar strings alfanuméricas simples como strings entre aspas (por exemplo, `foo` é analisado como se fosse `'foo'`). +- `parseUnquotedStringOrExpression(): ExpressionNode`: Analisa uma expressão padrão ou uma *string sem aspas*. Strings sem aspas são sequências permitidas pelo Latte sem aspas, frequentemente usadas para coisas como caminhos de arquivo (por exemplo, `{include ../file.latte}`). Se analisar uma string sem aspas, retorna um `StringNode`. +- `parseArguments(): ArrayNode`: Analisa argumentos separados por vírgula, potencialmente com chaves, como `10, name: 'John', true`. +- `parseModifier(): ModifierNode`: Analisa filtros como `|upper|truncate:10`. +- `parseType(): ?SuperiorTypeNode`: Analisa dicas de tipo PHP como `int`, `?string`, `array|Foo`. + +Para necessidades de parsing mais complexas ou de nível inferior, você pode interagir diretamente com o [fluxo de tokens |api:Latte\Compiler\TokenStream] via `$tag->parser->stream`. Este objeto fornece métodos para inspecionar e consumir tokens individuais: + +- `$tag->parser->stream->is(...): bool`: Verifica se o token *atual* corresponde a algum dos tipos especificados (por exemplo, `Token::Php_Variable`) ou valores literais (por exemplo, `'as'`) sem consumi-lo. Útil para olhar à frente. +- `$tag->parser->stream->consume(...): Token`: Consome o token *atual* e avança a posição do fluxo. Se tipos/valores de token esperados forem fornecidos como argumentos e o token atual não corresponder, lança `CompileException`. Use isso quando você *espera* um determinado token. +- `$tag->parser->stream->tryConsume(...): ?Token`: Tenta consumir o token *atual* *apenas se* ele corresponder a um dos tipos/valores especificados. Se corresponder, consome o token e o retorna. Se não corresponder, deixa a posição do fluxo inalterada e retorna `null`. Use isso para tokens opcionais ou ao escolher entre diferentes caminhos sintáticos. + + +Atualizando a função de parsing `create()` +------------------------------------------ + +Com esse entendimento, vamos modificar o método `create()` em `DatetimeNode` para analisar o argumento de formato opcional usando `$tag->parser`. + +```php +node = new self; + + // Verificamos se existem alguns tokens + if (!$tag->parser->isEnd()) { + // Analisamos o argumento como uma expressão semelhante a PHP usando TagParser. + $node->format = $tag->parser->parseExpression(); + } + + return $node; + } + + // ... os métodos print() e getIterator() serão atualizados a seguir ... +} +``` + +Adicionamos uma propriedade pública `$format`. Em `create()`, agora usamos `$tag->parser->isEnd()` para verificar se *existem* argumentos. Se sim, `$tag->parser->parseExpression()` processa os tokens para a expressão. Como o `TagParser` deve processar todos os tokens de entrada, o Latte lançará automaticamente um erro se o usuário escrever algo inesperado após a expressão de formato (por exemplo, `{datetime 'Y-m-d', unexpected}`). + + +Atualizando o método `print()` +------------------------------ + +Agora, vamos modificar o método `print()` para usar a expressão de formato analisada armazenada em `$this->format`. Se nenhum formato foi fornecido (`$this->format` é `null`), devemos usar uma string de formatação padrão, por exemplo, `'Y-m-d H:i:s'`. + +```php + public function print(PrintContext $context): string + { + $formatNode = $this->format ?? new StringNode('Y-m-d H:i:s'); + + // %node imprime a representação do código PHP de $formatNode. + return $context->format( + 'echo date(%node) %line;', + $formatNode, + $this->position + ); + } +``` + +Na variável `$formatNode`, armazenamos o nó AST que representa a string de formatação para a função PHP `date()`. Usamos o operador de coalescência nula (`??`) aqui. Se o usuário forneceu um argumento no template (por exemplo, `{datetime 'd.m.Y'}`), então a propriedade `$this->format` contém o nó correspondente (neste caso, um `StringNode` com o valor `'d.m.Y'`), e este nó é usado. Se o usuário não forneceu um argumento (escreveu apenas `{datetime}`), a propriedade `$this->format` é `null`, e em vez disso criamos um novo `StringNode` com o formato padrão `'Y-m-d H:i:s'`. Isso garante que `$formatNode` sempre contenha um nó AST válido para o formato. + +Na máscara `'echo date(%node) %line;'`, um novo placeholder `%node` é usado, que diz ao método `format()` para pegar o primeiro argumento seguinte (que é nosso `$formatNode`), chamar seu método `print()` (que retornará sua representação de código PHP) e inserir o resultado na posição do placeholder. + + +Implementando `getIterator()` para subnós +----------------------------------------- + +Nosso `DatetimeNode` agora tem um nó filho: a expressão `$format`. **Devemos** expor este nó filho aos passos de compilação, fornecendo-o no método `getIterator()`. Lembre-se de fornecer uma *referência* (`&`) para permitir que os passos potencialmente substituam o nó. + +```php + public function &getIterator(): \Generator + { + if ($this->format) { + yield $this->format; + } + } +``` + +Por que isso é crucial? Imagine um passo Sandbox que precisa verificar se o argumento `$format` não contém uma chamada de função proibida (por exemplo, `{datetime dangerousFunction()}`). Se `getIterator()` não fornecer `$this->format`, o passo Sandbox nunca veria a chamada `dangerousFunction()` dentro do argumento da nossa tag, criando uma potencial falha de segurança. Ao fornecê-lo, permitimos que o Sandbox (e outros passos) inspecionem e potencialmente modifiquem o nó da expressão `$format`. + + +Usando a tag aprimorada +----------------------- + +A tag agora lida corretamente com o argumento opcional: + +```latte +Formato padrão: {datetime} +Formato personalizado: {datetime 'd.m.Y'} +Usando variável: {datetime $userDateFormatPreference} + +{* Isso causaria um erro após analisar 'd.m.Y', pois ", foo" é inesperado *} +{* {datetime 'd.m.Y', foo} *} +``` + +A seguir, veremos a criação de tags de par, que processam o conteúdo entre elas. + + +Tratando tags de par +==================== + +Até agora, nossa tag `{datetime}` era *auto-fechada* (conceitualmente). Ela não tinha conteúdo entre as tags de abertura e fechamento. No entanto, muitas tags úteis operam em um bloco de conteúdo de template. Estas são chamadas de **tags de par**. Exemplos incluem `{if}...{/if}`, `{block}...{/block}` ou uma tag personalizada que criaremos agora: `{debug}...{/debug}`. + +Esta tag nos permitirá incluir informações de depuração em nossos templates que devem ser visíveis apenas durante o desenvolvimento. + +**Objetivo:** Criar uma tag de par `{debug}` cujo conteúdo é renderizado apenas quando um sinalizador específico de "modo de desenvolvimento" está ativo. + + +Apresentando os provedores +-------------------------- + +Às vezes, suas tags precisam acessar dados ou serviços que não são passados diretamente como parâmetros de template. Por exemplo, determinar se a aplicação está em modo de desenvolvimento, acessar o objeto do usuário ou obter valores de configuração. O Latte fornece um mecanismo chamado **provedores** (Providers) para este propósito. + +Os provedores são registrados em sua [extensão |extending-latte#Latte Extension] usando o método `getProviders()`. Este método retorna um array associativo onde as chaves são os nomes pelos quais os provedores serão acessíveis no código de tempo de execução do template, e os valores são os dados ou objetos reais. + +Dentro do código PHP gerado pelo método `print()` da sua tag, você pode acessar esses provedores através de uma propriedade especial do objeto `$this->global`. Como esta propriedade é compartilhada entre todas as extensões, é uma boa prática **prefixar os nomes dos seus provedores** para evitar possíveis conflitos de nomes com provedores principais do Latte ou provedores de outras extensões de terceiros. Uma convenção comum é usar um prefixo curto e único relacionado ao seu fornecedor ou nome da extensão. Para nosso exemplo, usaremos o prefixo `app` e o sinalizador de modo de desenvolvimento estará disponível como `$this->global->appDevMode`. + + +A palavra-chave `yield` para parsing de conteúdo +------------------------------------------------ + +Como dizemos ao parser do Latte para processar o conteúdo *entre* `{debug}` e `{/debug}`? É aqui que entra a palavra-chave `yield`. + +Quando `yield` é usado na função `create()`, a função se torna um [gerador PHP |https://www.php.net/manual/en/language.generators.overview.php]. Sua execução é pausada e o controle retorna ao `TemplateParser` principal. O `TemplateParser` então continua a analisar o conteúdo do template *até* encontrar a tag de fechamento correspondente (`{/debug}` em nosso caso). + +Assim que a tag de fechamento é encontrada, o `TemplateParser` retoma a execução da nossa função `create()` logo após a instrução `yield`. O valor *retornado* pela instrução `yield` é um array contendo dois elementos: + +1. Um `AreaNode` representando o conteúdo analisado entre as tags de abertura e fechamento. +2. Um objeto `Tag` representando a tag de fechamento (por exemplo, `{/debug}`). + +Vamos criar a classe `DebugNode` e seu método `create` utilizando `yield`. + +```php +node = new self; + + // Pausar o parsing, obter o conteúdo interno e a tag final quando {/debug} for encontrado + [$node->content, $endTag] = yield; + + return $node; + } + + // ... print() e getIterator() serão implementados a seguir ... +} +``` + +Nota: `$endTag` é `null` se a tag for usada como um n:attribute, ou seja, `
                                                                                                ...
                                                                                                `. + + +Implementando `print()` para renderização condicional +----------------------------------------------------- + +O método `print()` agora precisa gerar código PHP que, em tempo de execução, verifique o provedor `appDevMode` e execute o código para o conteúdo interno apenas se o sinalizador for verdadeiro. + +```php + public function print(PrintContext $context): string + { + // Gera uma instrução PHP 'if' que verifica o provedor em tempo de execução + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + // Se estiver em modo de desenvolvimento, imprime o conteúdo interno + %node + } + + XX, + $this->position, // Para o comentário %line + $this->content, // O nó contendo a AST do conteúdo interno + ); + } +``` + +Isso é simples. Usamos `PrintContext::format()` para criar uma instrução PHP `if` padrão. Dentro do `if`, colocamos o placeholder `%node` para `$this->content`. O Latte chamará recursivamente `$this->content->print($context)` para gerar o código PHP para a parte interna da tag, mas apenas se `$this->global->appDevMode` avaliar como verdadeiro em tempo de execução. + + +Implementando `getIterator()` para o conteúdo +--------------------------------------------- + +Assim como com o nó de argumento no exemplo anterior, nosso `DebugNode` agora tem um nó filho: `AreaNode $content`. Precisamos expô-lo fornecendo-o em `getIterator()`: + +```php + public function &getIterator(): \Generator + { + // Fornece uma referência ao nó de conteúdo + yield $this->content; + } +``` + +Isso permite que os passos de compilação desçam para o conteúdo da nossa tag `{debug}`, o que é importante mesmo que o conteúdo seja renderizado condicionalmente. Por exemplo, o Sandbox precisa analisar o conteúdo independentemente de `appDevMode` ser verdadeiro ou falso. + + +Registro e uso +-------------- + +Registre a tag e o provedor em sua extensão: + +```php +class MyLatteExtension extends Extension +{ + // Assumimos que $isDevelopmentMode é determinado em algum lugar (por exemplo, da configuração) + public function __construct( + private bool $isDevelopmentMode, + ) { + } + + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), // Registro da nova tag + ]; + } + + public function getProviders(): array + { + return [ + 'appDevMode' => $this->isDevelopmentMode, // Registro do provedor + ]; + } +} + +// Ao registrar a extensão: +$isDev = true; // Determine isso com base no ambiente da sua aplicação +$latte->addExtension(new App\Latte\MyLatteExtension($isDev)); +``` + +E seu uso no template: + +```latte +

                                                                                                Conteúdo normal visível sempre.

                                                                                                + +{debug} +
                                                                                                + ID do usuário atual: {$user->id} + Hora da requisição: {=time()} +
                                                                                                +{/debug} + +

                                                                                                Outro conteúdo normal.

                                                                                                +``` + + +Integração de n:attributes +-------------------------- + +O Latte oferece uma notação abreviada conveniente para muitas tags de par: [n:attributes |syntax#n:atributos]. Se você tem uma tag de par como `{tag}...{/tag}` e deseja que seu efeito seja aplicado diretamente a um único elemento HTML, muitas vezes pode escrevê-la de forma mais concisa como um atributo `n:tag` nesse elemento. + +Para a maioria das tags de par padrão que você define (como nossa `{debug}`), o Latte habilitará automaticamente a versão do atributo `n:` correspondente. Você não precisa fazer nada extra durante o registro: + +```latte +{* Uso padrão da tag de par *} +{debug}
                                                                                                Informação de depuração
                                                                                                {/debug} + +{* Uso equivalente com n:attribute *} +
                                                                                                Informação de depuração
                                                                                                +``` + +Ambas as versões renderizarão o `
                                                                                                ` apenas se `$this->global->appDevMode` for verdadeiro. Os prefixos `inner-` e `tag-` também funcionam como esperado. + +Às vezes, a lógica da sua tag pode precisar se comportar de maneira ligeiramente diferente dependendo se ela é usada como uma tag de par padrão ou como um n:attribute, ou se um prefixo como `n:inner-tag` ou `n:tag-tag` é usado. O objeto `Latte\Compiler\Tag`, passado para sua função de parsing `create()`, fornece essas informações: + +- `$tag->isNAttribute(): bool`: Retorna `true` se a tag estiver sendo analisada como um n:attribute +- `$tag->prefix: ?string`: Retorna o prefixo usado com o n:attribute, que pode ser `null` (não é um n:attribute), `Tag::PrefixNone`, `Tag::PrefixInner` ou `Tag::PrefixTag` + +Agora que entendemos tags simples, parsing de argumentos, tags de par, provedores e n:attributes, vamos abordar um cenário mais complexo envolvendo tags aninhadas dentro de outras tags, usando nossa tag `{debug}` como ponto de partida. + + +Tags intermediárias +=================== + +Algumas tags de par permitem ou até exigem que outras tags apareçam *dentro* delas antes da tag de fechamento final. Estas são chamadas de **tags intermediárias**. Exemplos clássicos incluem `{if}...{elseif}...{else}...{/if}` ou `{switch}...{case}...{default}...{/switch}`. + +Vamos estender nossa tag `{debug}` para suportar uma cláusula opcional `{else}`, que será renderizada quando a aplicação *não* estiver em modo de desenvolvimento. + +**Objetivo:** Modificar `{debug}` para suportar uma tag intermediária opcional `{else}`. A sintaxe final deve ser `{debug} ... {else} ... {/debug}`. + + +Parsing de tags intermediárias com `yield` +------------------------------------------ + +Já sabemos que `yield` pausa a função de parsing `create()` e retorna o conteúdo analisado junto com a tag final. No entanto, `yield` oferece mais controle: você pode fornecer a ele um array de *nomes de tags intermediárias*. Quando o parser encontra qualquer uma dessas tags especificadas **no mesmo nível de aninhamento** (ou seja, como filhos diretos da tag pai, não dentro de outros blocos ou tags dentro dela), ele também para o parsing. + +Quando o parsing para devido a uma tag intermediária, ele para de analisar o conteúdo, retoma o gerador `create()` e passa de volta o conteúdo parcialmente analisado e a **tag intermediária** em si (em vez da tag final). Nossa função `create()` pode então processar esta tag intermediária (por exemplo, analisar seus argumentos, se houver) e usar `yield` novamente para analisar a *próxima* parte do conteúdo até a tag final *ou* outra tag intermediária esperada. + +Vamos modificar `DebugNode::create()` para esperar `{else}`: + +```php +node = new self; + + // yield e esperar por {/debug} ou {else} + [$node->thenContent, $nextTag] = yield ['else']; + + // Verificar se a tag onde paramos era {else} + if ($nextTag?->name === 'else') { + // Yield novamente para analisar o conteúdo entre {else} e {/debug} + [$node->elseContent, $endTag] = yield; + } + + return $node; + } + + // ... print() e getIterator() serão atualizados a seguir ... +} +``` + +Agora, `yield ['else']` diz ao Latte para parar o parsing não apenas para `{/debug}`, mas também para `{else}`. Se `{else}` for encontrado, `$nextTag` conterá o objeto `Tag` para `{else}`. Em seguida, usamos `yield` novamente sem argumentos, o que significa que agora esperamos apenas a tag final `{/debug}`, e armazenamos o resultado em `$node->elseContent`. Se `{else}` não foi encontrado, `$nextTag` seria o `Tag` para `{/debug}` (ou `null`, se usado como n:attribute) e `$node->elseContent` permaneceria `null`. + + +Implementando `print()` com `{else}` +------------------------------------ + +O método `print()` precisa refletir a nova estrutura. Ele deve gerar uma instrução PHP `if/else` baseada no provedor `devMode`. + +```php + public function print(PrintContext $context): string + { + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + %node // Código para o ramo 'then' (conteúdo {debug}) + } else { + %node // Código para o ramo 'else' (conteúdo {else}) + } + + XX, + $this->position, // Número da linha para a condição 'if' + $this->thenContent, // Primeiro placeholder %node + $this->elseContent ?? new NopNode, // Segundo placeholder %node + ); + } +``` + +Esta é uma estrutura PHP `if/else` padrão. Usamos `%node` duas vezes; `format()` substitui os nós fornecidos sequencialmente. Usamos `?? new NopNode` para evitar erros se `$this->elseContent` for `null` – `NopNode` simplesmente não imprime nada. + + +Implementando `getIterator()` para ambos os conteúdos +----------------------------------------------------- + +Agora temos potencialmente dois nós filhos de conteúdo (`$thenContent` e `$elseContent`). Precisamos fornecer ambos, se existirem: + +```php + public function &getIterator(): \Generator + { + yield $this->thenContent; + if ($this->elseContent) { + yield $this->elseContent; + } + } +``` + + +Usando a tag aprimorada +----------------------- + +A tag agora pode ser usada com a cláusula opcional `{else}`: + +```latte +{debug} +

                                                                                                Exibindo informações de depuração, porque devMode está LIGADO.

                                                                                                +{else} +

                                                                                                Informações de depuração estão ocultas, porque devMode está DESLIGADO.

                                                                                                +{/debug} +``` + + +Tratando estado e aninhamento +============================= + +Nossos exemplos anteriores (`{datetime}`, `{debug}`) eram relativamente sem estado dentro de seus métodos `print()`. Eles imprimiam conteúdo diretamente ou realizavam uma verificação condicional simples baseada em um provedor global. No entanto, muitas tags precisam gerenciar alguma forma de **estado** durante a renderização ou envolvem a avaliação de expressões do usuário que devem ser executadas apenas uma vez por desempenho ou correção. Além disso, precisamos considerar o que acontece quando nossas tags personalizadas são **aninhadas**. + +Vamos ilustrar esses conceitos criando uma tag `{repeat $count}...{/repeat}`. Esta tag repetirá seu conteúdo interno `$count` vezes. + +**Objetivo:** Implementar `{repeat $count}`, que repete seu conteúdo um número especificado de vezes. + + +A necessidade de variáveis temporárias e únicas +----------------------------------------------- + +Imagine que o usuário escreva: + +```latte +{repeat rand(1, 5)} Conteúdo {/repeat} +``` + +Se gerássemos ingenuamente um loop `for` PHP desta forma em nosso método `print()`: + +```php +// Código gerado simplificado, INCORRETO +for ($i = 0; $i < rand(1, 5); $i++) { + // imprimir conteúdo +} +``` +Isso estaria errado! A expressão `rand(1, 5)` seria **reavaliada a cada iteração do loop**, levando a um número imprevisível de repetições. Precisamos avaliar a expressão `$count` *uma vez* antes do início do loop e armazenar seu resultado. + +Geraremos código PHP que primeiro avalia a expressão de contagem e a armazena em uma **variável temporária de tempo de execução**. Para evitar colisões com variáveis definidas pelo usuário do template *e* variáveis internas do Latte (como `$ʟ_...`), usaremos a convenção de prefixar nossas variáveis temporárias com **`$__` (sublinhado duplo)**. + +O código gerado ficaria assim: + +```php +$__count = rand(1, 5); +for ($__i = 0; $__i < $__count; $__i++) { + // imprimir conteúdo +} +``` + +Agora, considere o aninhamento: + +```latte +{repeat $countA} {* Loop externo *} + {repeat $countB} {* Loop interno *} + ... + {/repeat} +{/repeat} +``` + +Se as tags `{repeat}` externa e interna gerassem código usando os *mesmos* nomes de variáveis temporárias (por exemplo, `$__count` e `$__i`), o loop interno sobrescreveria as variáveis do loop externo, quebrando a lógica. + +Precisamos garantir que as variáveis temporárias geradas para cada instância da tag `{repeat}` sejam **únicas**. Conseguimos isso usando `PrintContext::generateId()`. Este método retorna um inteiro único durante a fase de compilação. Podemos anexar este ID aos nomes de nossas variáveis temporárias. + +Então, em vez de `$__count`, geraremos `$__count_1` para a primeira tag repeat, `$__count_2` para a segunda, etc. Da mesma forma, para o contador do loop, usaremos `$__i_1`, `$__i_2`, etc. + + +Implementando `RepeatNode` +-------------------------- + +Vamos criar a classe do nó. + +```php +expectArguments(); // garante que $count seja fornecido + $node = $tag->node = new self; + // Analisa a expressão de contagem + $node->count = $tag->parser->parseExpression(); + // Obtém o conteúdo interno + [$node->content] = yield; + return $node; + } + + /** + * Gera um loop 'for' PHP com nomes de variáveis únicos. + */ + public function print(PrintContext $context): string + { + // Geração de nomes de variáveis únicos + $id = $context->generateId(); + $countVar = '$__count_' . $id; // ex. $__count_1, $__count_2, etc. + $iteratorVar = '$__i_' . $id; // ex. $__i_1, $__i_2, etc. + + return $context->format( + <<<'XX' + // Avalia a expressão de contagem *uma vez* e armazena + %raw = (int) (%node); + // Loop usando a contagem armazenada e variável de iteração única + for (%raw = 0; %2.raw < %0.raw; %2.raw++) %line { + %node // Renderiza o conteúdo interno + } + + XX, + $countVar, // %0 - Variável para armazenar a contagem + $this->count, // %1 - Nó da expressão para a contagem + $iteratorVar, // %2 - Nome da variável de iteração do loop + $this->position, // %3 - Comentário com número da linha para o próprio loop + $this->content // %4 - Nó do conteúdo interno + ); + } + + /** + * Fornece os nós filhos (expressão de contagem e conteúdo). + */ + public function &getIterator(): \Generator + { + yield $this->count; + yield $this->content; + } +} +``` + +O método `create()` analisa a expressão `$count` necessária usando `parseExpression()`. Primeiro, `$tag->expectArguments()` é chamado. Isso garante que o usuário forneceu *algo* após `{repeat}`. Embora `$tag->parser->parseExpression()` falhasse se nada fosse fornecido, a mensagem de erro poderia ser sobre sintaxe inesperada. Usar `expectArguments()` fornece um erro muito mais claro, indicando especificamente que faltam argumentos para a tag `{repeat}`. + +O método `print()` gera o código PHP responsável por executar a lógica de repetição em tempo de execução. Ele começa gerando nomes únicos para as variáveis PHP temporárias que precisará. + +O método `$context->format()` é chamado com um novo placeholder `%raw`, que insere a *string bruta* fornecida como o argumento correspondente. Aqui, ele insere o nome da variável única armazenado em `$countVar` (por exemplo, `$__count_1`). E quanto a `%0.raw` e `%2.raw`? Isso demonstra **placeholders posicionais**. Em vez de apenas `%raw`, que pega o *próximo* argumento bruto disponível, `%2.raw` pega explicitamente o argumento no índice 2 (que é `$iteratorVar`) e insere seu valor de string bruto. Isso nos permite reutilizar a string `$iteratorVar` sem passá-la várias vezes na lista de argumentos para `format()`. + +Esta chamada `format()` cuidadosamente construída gera um loop PHP eficiente e seguro que lida corretamente com a expressão de contagem e evita colisões de nomes de variáveis, mesmo quando as tags `{repeat}` estão aninhadas. + + +Registro e uso +-------------- + +Registre a tag em sua extensão: + +```php +use App\Latte\RepeatNode; + +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), // Registro da tag repeat + ]; + } +} +``` + +Use-a no template, incluindo aninhamento: + +```latte +{var $rows = rand(5, 7)} +{var $cols = rand(3, 5)} + +{repeat $rows} +
                                                                                                + {repeat $cols} + + {/repeat} + +{/repeat} +``` + +Este exemplo demonstra como lidar com estado (contadores de loop) e potenciais problemas de aninhamento usando variáveis temporárias prefixadas com `$__` e tornadas únicas com IDs de `PrintContext::generateId()`. + + +n:attributes puros +------------------ + +Enquanto muitos `n:attributes` como `n:if` ou `n:foreach` servem como atalhos convenientes para suas contrapartes em tags de par (`{if}...{/if}`, `{foreach}...{/foreach}`), o Latte também permite definir tags que *existem apenas* na forma de n:attribute. Estes são frequentemente usados para modificar atributos ou comportamento do elemento HTML ao qual estão anexados. + +Exemplos padrão integrados no Latte incluem [`n:class` |tags#n:class], que ajuda a construir dinamicamente o atributo `class`, e [`n:attr` |tags#n:attr], que pode definir múltiplos atributos arbitrários. + +Vamos criar nosso próprio n:attribute puro: `n:confirm`, que adiciona um diálogo de confirmação JavaScript antes de executar uma ação (como seguir um link ou enviar um formulário). + +**Objetivo:** Implementar `n:confirm="'Tem certeza?'"`, que adiciona um manipulador `onclick` para prevenir a ação padrão se o usuário cancelar o diálogo de confirmação. + + +Implementando `ConfirmNode` +--------------------------- + +Precisamos de uma classe Node e uma função de parsing. + +```php +expectArguments(); + $node = $tag->node = new self; + $node->message = $tag->parser->parseExpression(); + return $node; + } + + /** + * Gera o código do atributo 'onclick' com escaping correto. + */ + public function print(PrintContext $context): string + { + // Garante o escaping correto para os contextos de atributo JavaScript e HTML. + return $context->format( + <<<'XX' + echo ' onclick="', LR\Filters::escapeHtmlAttr('return confirm(' . LR\Filters::escapeJs(%node) . ')'), '"' %line; + XX, + $this->message, + $this->position, + ); + } + + public function &getIterator(): \Generator + { + yield $this->message; + } +} +``` + +O método `print()` gera código PHP que, eventualmente, durante a renderização do template, imprimirá o atributo HTML `onclick="..."`. Lidar com contextos aninhados (JavaScript dentro de um atributo HTML) requer escaping cuidadoso. O filtro `LR\Filters::escapeJs(%node)` é chamado em tempo de execução e escapa a mensagem corretamente para uso dentro do JavaScript (a saída seria como `"Sure?"`). Em seguida, o filtro `LR\Filters::escapeHtmlAttr(...)` escapa caracteres que são especiais em atributos HTML, então isso mudaria a saída para `return confirm("Sure?")`. Este escaping de tempo de execução em duas etapas garante que a mensagem seja segura para JavaScript e que o código JavaScript resultante seja seguro para incorporação no atributo HTML `onclick`. + + +Registro e uso +-------------- + +Registre o n:attribute em sua extensão. Não se esqueça do prefixo `n:` na chave: + +```php +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), + 'n:confirm' => ConfirmNode::create(...), // Registro de n:confirm + ]; + } +} +``` + +Agora você pode usar `n:confirm` em links, botões ou elementos de formulário: + +```latte +Excluir +``` + +HTML gerado: + +```html +Excluir +``` + +Quando o usuário clica no link, o navegador executa o código `onclick`, exibe o diálogo de confirmação e só navega para `delete.php` se o usuário clicar em "OK". + +Este exemplo demonstra como um n:attribute puro pode ser criado para modificar o comportamento ou atributos de seu elemento HTML hospedeiro, gerando código PHP apropriado em seu método `print()`. Lembre-se do duplo escaping que muitas vezes é necessário: uma vez para o contexto de destino (JavaScript neste caso) e novamente para o contexto do atributo HTML. + + +Tópicos avançados +================= + +Embora as seções anteriores cubram os conceitos básicos, aqui estão alguns tópicos mais avançados que você pode encontrar ao criar tags Latte personalizadas. + + +Modos de saída de tags +---------------------- + +O objeto `Tag` passado para sua função `create()` has uma propriedade `outputMode`. Esta propriedade influencia como o Latte trata espaços em branco e indentação ao redor, especialmente quando a tag é usada em sua própria linha. Você pode modificar esta propriedade em sua função `create()`. + +- `Tag::OutputKeepIndentation` (Padrão para a maioria das tags como `{=...}`): O Latte tenta preservar a indentação antes da tag. Novas linhas *após* a tag são geralmente preservadas. Isso é adequado para tags que imprimem conteúdo inline. +- `Tag::OutputRemoveIndentation` (Padrão para tags de bloco como `{if}`, `{foreach}`): O Latte remove a indentação inicial e potencialmente uma nova linha seguinte. Isso ajuda a manter o código PHP gerado mais limpo e evita linhas em branco extras na saída HTML causadas pela própria tag. Use isso para tags que representam estruturas de controle ou blocos que não devem adicionar espaços em branco por si só. +- `Tag::OutputNone` (Usado por tags como `{var}`, `{default}`): Semelhante a `RemoveIndentation`, mas sinaliza mais fortemente que a tag em si não produz saída direta, potencialmente afetando o tratamento de espaços em branco ao redor dela de forma ainda mais agressiva. Adequado para tags declarativas ou de configuração. + +Escolha o modo que melhor se adapta ao propósito da sua tag. Para a maioria das tags estruturais ou de controle, `OutputRemoveIndentation` geralmente é apropriado. + + +Acessando tags pai/mais próximas +-------------------------------- + +Às vezes, o comportamento de uma tag precisa depender do contexto em que é usada, especificamente em qual(is) tag(s) pai ela reside. O objeto `Tag` passado para sua função `create()` fornece o método `closestTag(array $classes, ?callable $condition = null): ?Tag` exatamente para este propósito. + +Este método pesquisa para cima na hierarquia das tags atualmente abertas (incluindo elementos HTML representados internamente durante o parsing) e retorna o objeto `Tag` do ancestral mais próximo que corresponde a critérios específicos. Se nenhum ancestral correspondente for encontrado, retorna `null`. + +O array `$classes` especifica que tipo de tags ancestrais você está procurando. Ele verifica se o nó associado da tag ancestral (`$ancestorTag->node`) é uma instância desta classe. + +```php +function create(Tag $tag) +{ + // Procura a tag ancestral mais próxima cujo nó é uma instância de ForeachNode + $foreachTag = $tag->closestTag([ForeachNode::class]); + if ($foreachTag) { + // Podemos acessar a instância ForeachNode em si: + $foreachNode = $foreachTag->node; + } +} +``` + +Observe `$foreachTag->node`: Isso funciona apenas porque é uma convenção no desenvolvimento de tags Latte atribuir imediatamente o nó criado a `$tag->node` dentro do método `create()`, como sempre fizemos. + +Às vezes, apenas comparar o tipo do nó não é suficiente. Você pode precisar verificar uma propriedade específica da tag ancestral potencial ou seu nó. O segundo argumento opcional para `closestTag()` é um callable que recebe o objeto `Tag` ancestral potencial e deve retornar se é uma correspondência válida. + +```php +function create(Tag $tag) +{ + $dynamicBlockTag = $tag->closestTag( + [BlockNode::class], + // Condição: o bloco deve ser dinâmico + fn(Tag $blockTag) => $blockTag->node->block->isDynamic(), + ); +} +``` + +Usar `closestTag()` permite criar tags que são conscientes do contexto e impõem o uso adequado dentro da estrutura do seu template, levando a templates mais robustos e compreensíveis. + + +Placeholders `PrintContext::format()` +------------------------------------- + +Usamos frequentemente `PrintContext::format()` para gerar código PHP nos métodos `print()` de nossos nós. Ele aceita uma string de máscara e argumentos subsequentes que substituem os placeholders na máscara. Aqui está um resumo dos placeholders disponíveis: + +- **`%node`**: O argumento deve ser uma instância de `Node`. Chama o método `print()` do nó e insere a string de código PHP resultante. +- **`%dump`**: O argumento é qualquer valor PHP. Exporta o valor para código PHP válido. Adequado para escalares, arrays, null. + - `$context->format('echo %dump;', 'Hello')` -> `echo 'Hello';` + - `$context->format('$arr = %dump;', [1, 2])` -> `$arr = [1, 2];` +- **`%raw`**: Insere o argumento diretamente no código PHP de saída sem qualquer escaping ou modificação. **Use com cautela**, principalmente para inserir fragmentos de código PHP pré-gerados ou nomes de variáveis. + - `$context->format('%raw = 1;', '$variableName')` -> `$variableName = 1;` +- **`%args`**: O argumento deve ser um `Expression\ArrayNode`. Imprime os itens do array formatados como argumentos para uma chamada de função ou método (separados por vírgula, lida com argumentos nomeados se presentes). + - `$argsNode = new ArrayNode([...]);` + - `$context->format('myFunc(%args);', $argsNode)` -> `myFunc(1, name: 'Joe');` +- **`%line`**: O argumento deve ser um objeto `Position` (geralmente `$this->position`). Insere um comentário PHP `/* line X */` indicando o número da linha da fonte. + - `$context->format('echo "Hi" %line;', $this->position)` -> `echo "Hi" /* line 42 */;` +- **`%escape(...)`**: Gera código PHP que *em tempo de execução* escapa a expressão interna usando as regras de escaping conscientes do contexto atuais. + - `$context->format('echo %escape(%node);', $variableNode)` +- **`%modify(...)`**: O argumento deve ser um `ModifierNode`. Gera código PHP que aplica os filtros especificados no `ModifierNode` ao conteúdo interno, incluindo escaping consciente do contexto, a menos que desabilitado com `|noescape`. + - `$context->format('%modify(%node);', $modifierNode, $variableNode)` +- **`%modifyContent(...)`**: Semelhante a `%modify`, mas destinado a modificar blocos de conteúdo capturado (frequentemente HTML). + +Você pode referenciar explicitamente argumentos por seu índice (baseado em zero): `%0.node`, `%1.dump`, `%2.raw`, etc. Isso permite reutilizar um argumento várias vezes na máscara sem passá-lo repetidamente para `format()`. Veja o exemplo da tag `{repeat}`, onde `%0.raw` e `%2.raw` foram usados. + + +Exemplo de parsing de argumentos complexos +------------------------------------------ + +Embora `parseExpression()`, `parseArguments()`, etc., cubram muitos casos, às vezes você precisa de lógica de parsing mais complexa usando o `TokenStream` de nível inferior disponível via `$tag->parser->stream`. + +**Objetivo:** Criar uma tag `{embedYoutube $videoID, width: 640, height: 480}`. Queremos analisar o ID do vídeo obrigatório (string ou variável) seguido por pares chave-valor opcionais para as dimensões. + +```php +expectArguments(); + $node = $tag->node = new self; + // Analisar o ID do vídeo obrigatório + $node->videoId = $tag->parser->parseExpression(); + + // Analisar pares chave-valor opcionais + $stream = $tag->parser->stream; // Obter o fluxo de tokens + while ($stream->tryConsume(',')) { // Requer separação por vírgula + // Esperar um identificador 'width' ou 'height' + $keyToken = $stream->consume(Token::Php_Identifier); + $key = strtolower($keyToken->text); + + $stream->consume(':'); // Esperar o separador de dois pontos + + $value = $tag->parser->parseExpression(); // Analisar a expressão do valor + + if ($key === 'width') { + $node->width = $value; + } elseif ($key === 'height') { + $node->height = $value; + } else { + throw new CompileException("Argumento desconhecido '$key'. Esperado 'width' ou 'height'.", $keyToken->position); + } + } + + return $node; + } +} +``` + +Este nível de controle permite definir sintaxes muito específicas e complexas para suas tags personalizadas, interagindo diretamente com o fluxo de tokens. + + +Usando `AuxiliaryNode` +---------------------- + +O Latte fornece nós "auxiliares" genéricos para situações especiais durante a geração de código ou dentro de passos de compilação. São eles `AuxiliaryNode` e `Php\Expression\AuxiliaryNode`. + +Considere `AuxiliaryNode` como um nó contêiner flexível que delega suas funcionalidades principais - geração de código e exposição de nós filhos - aos argumentos fornecidos em seu construtor: + +- Delegação de `print()`: O primeiro argumento do construtor é uma **closure** PHP. Quando o Latte chama o método `print()` em um `AuxiliaryNode`, ele executa esta closure fornecida. A closure recebe o `PrintContext` e quaisquer nós passados no segundo argumento do construtor, permitindo que você defina lógica de geração de código PHP completamente personalizada em tempo de execução. +- Delegação de `getIterator()`: O segundo argumento do construtor é um **array de objetos `Node`**. Quando o Latte precisa percorrer os filhos de um `AuxiliaryNode` (por exemplo, durante os passos de compilação), seu método `getIterator()` simplesmente fornece os nós listados neste array. + +Exemplo: + +```php +$node = new AuxiliaryNode( + // 1. Esta closure se torna o corpo de print() + fn(PrintContext $context, $arg1, $arg2) => $context->format('...%node...%node...', $arg1, $arg2), + + // 2. Estes nós são fornecidos pelo método getIterator() e passados para a closure acima + [$argumentNode1, $argumentNode2] +); +``` + +O Latte fornece dois tipos distintos com base em onde você precisa inserir o código gerado: + +- `Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode`: Use isso quando precisar gerar um pedaço de código PHP que representa uma **expressão**. +- `Latte\Compiler\Nodes\AuxiliaryNode`: Use isso para fins mais gerais, quando precisar inserir um bloco de código PHP representando uma ou mais **instruções**. + +Uma razão importante para usar `AuxiliaryNode` em vez de nós padrão (como `StaticMethodCallNode`) dentro do seu método `print()` ou passo de compilação é **controlar a visibilidade para os passos de compilação subsequentes**, especialmente aqueles relacionados à segurança, como o Sandbox. + +Considere um cenário: Seu passo de compilação precisa envolver uma expressão fornecida pelo usuário (`$userExpr`) em uma chamada para uma função auxiliar específica e confiável `myInternalSanitize($userExpr)`. Se você criar um nó padrão `new FunctionCallNode('myInternalSanitize', [$userExpr])`, ele será totalmente visível para a travessia da AST. Se um passo Sandbox for executado posteriormente e `myInternalSanitize` *não* estiver em sua lista de permissões, o Sandbox pode *bloquear* ou modificar essa chamada, potencialmente quebrando a lógica interna da sua tag, mesmo que *você*, o autor da tag, saiba que essa chamada específica é segura e necessária. Você pode, portanto, gerar a chamada diretamente dentro da closure do `AuxiliaryNode`. + +```php +use Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode; + +// ... dentro de print() ou passo de compilação ... +$wrappedNode = new AuxiliaryNode( + fn(PrintContext $context, $userExpr) => $context->format( + 'myInternalSanitize(%node)', // Geração direta de código PHP + $userExpr, + ), + // IMPORTANTE: Ainda passe o nó da expressão do usuário original aqui! + [$userExpr], +); +``` + +Neste caso, o passo Sandbox vê o `AuxiliaryNode`, mas **não analisa o código PHP gerado por sua closure**. Ele não pode bloquear diretamente a chamada `myInternalSanitize` gerada *dentro* da closure. + +Embora o próprio código PHP gerado esteja oculto dos passos, as *entradas* para esse código (nós representando dados ou expressões do usuário) **ainda devem ser atravessáveis**. É por isso que o segundo argumento do construtor `AuxiliaryNode` é crucial. Você **deve** passar um array contendo todos os nós originais (como `$userExpr` no exemplo acima) que sua closure usa. O `getIterator()` do `AuxiliaryNode` **fornecerá esses nós**, permitindo que passos de compilação como o Sandbox os analisem em busca de problemas potenciais. + + +Melhores práticas +================= + +- **Propósito claro:** Certifique-se de que sua tag tenha um propósito claro e necessário. Não crie tags para tarefas que podem ser facilmente resolvidas com [filtros |custom-filters] ou [funções |custom-functions]. +- **Implemente `getIterator()` corretamente:** Sempre implemente `getIterator()` e forneça *referências* (`&`) para *todos* os nós filhos (argumentos, conteúdo) que foram analisados do template. Isso é essencial para os passos de compilação, segurança (Sandbox) e potenciais otimizações futuras. +- **Propriedades públicas para nós:** Torne públicas as propriedades que contêm nós filhos para que os passos de compilação possam modificá-los, se necessário. +- **Use `PrintContext::format()`:** Utilize o método `format()` para gerar código PHP. Ele lida com aspas, escapa corretamente os placeholders e adiciona comentários de número de linha automaticamente. +- **Variáveis temporárias (`$__`):** Ao gerar código PHP de tempo de execução que precisa de variáveis temporárias (por exemplo, para armazenar subtotais, contadores de loop), use a convenção de prefixo `$__` para evitar colisões com variáveis do usuário e variáveis internas do Latte `$ʟ_`. +- **Aninhamento e IDs únicos:** Se sua tag pode ser aninhada ou precisa de estado específico da instância em tempo de execução, use `$context->generateId()` dentro do seu método `print()` para criar sufixos únicos para suas variáveis temporárias `$__`. +- **Provedores para dados externos:** Use provedores (registrados via `Extension::getProviders()`) para acessar dados ou serviços em tempo de execução (`$this->global->...`) em vez de codificar valores ou depender de estado global. Use prefixos de fornecedor para nomes de provedores. +- **Considere n:attributes:** Se sua tag de par opera logicamente em um único elemento HTML, o Latte provavelmente fornecerá suporte automático a `n:attribute`. Tenha isso em mente para conveniência do usuário. Se estiver criando uma tag modificadora de atributo, considere se um `n:attribute` puro é a forma mais apropriada. +- **Testes:** Escreva testes para suas tags, cobrindo tanto o parsing de diferentes entradas sintáticas quanto a correção da saída do **código PHP** gerado. + +Seguindo estas diretrizes, você pode criar tags personalizadas poderosas, robustas e sustentáveis que se integram perfeitamente com o engine de templates Latte. + +.[note] +Estudar as classes de nós que fazem parte do Latte é a melhor maneira de aprender todos os detalhes sobre o processo de parsing. diff --git a/latte/pt/develop.texy b/latte/pt/develop.texy index b3f72c869c..ebd0a0a7f5 100644 --- a/latte/pt/develop.texy +++ b/latte/pt/develop.texy @@ -1,70 +1,66 @@ -Práticas para os desenvolvedores -******************************** +Práticas do desenvolvedor +************************* -Instalação .[#toc-installation] -=============================== +Instalação +========== -A melhor maneira de instalar o Latte é usar um Composer: +A melhor maneira de instalar o Latte é usando o Composer: ```shell composer require latte/latte ``` -Versões PHP suportadas (aplica-se às últimas versões de correção Latte): +Versões do PHP suportadas (aplica-se às últimas versões de patch do Latte): -| versão | compatível com PHP +| versão | compatível com PHP |-----------------|------------------- -| Latte 3.0 | PHP 8.0 - 8.2 -| Latte 2.11 | PHP 7.1 - 8.2 -| Latte 2.8 - 2.10| PHP 7.1 - 8.1 +| Latte 3.0 | PHP 8.0 – 8.2 -Como fazer um modelo .[#toc-how-to-render-a-template] -===================================================== +Como renderizar um template +=========================== -Como fazer um modelo? Basta usar este código simples: +Como renderizar um template? Basta este código simples: ```php $latte = new Latte\Engine; -// diretório de cache +// diretório para cache $latte->setTempDirectory('/path/to/tempdir'); -$params = [ /* template variables */ ]; -// ou $params = novos parâmetros TemplateParameters(/* ... */); +$params = [ /* variáveis do template */ ]; +// ou $params = new TemplateParameters(/* ... */); -// render à saída +// renderiza na saída $latte->render('template.latte', $params); -// ou render a variável +// renderiza para uma variável $output = $latte->renderToString('template.latte', $params); ``` -Os parâmetros podem ser arrays ou até melhor [objeto |#Parameters as a class], o que proporcionará verificação de tipo e sugestão no editor. +Os parâmetros podem ser um array ou, melhor ainda, um [objeto |#Parâmetros como classe], que garante a verificação de tipo e sugestões em editores. .[note] -Você também pode encontrar exemplos de uso no repositório de [exemplos Latte |https://github.com/nette-examples/latte]. +Exemplos de uso também podem ser encontrados no repositório [Latte examples |https://github.com/nette-examples/latte]. -Desempenho e Caching .[#toc-performance-and-caching] -==================================================== +Desempenho e cache +================== -Os modelos Latte são extremamente rápidos, porque o Latte os compila diretamente em código PHP e os armazena em cache em disco. Assim, eles não têm despesas extras em comparação com os modelos escritos em PHP puro. +Os templates em Latte são extremamente rápidos, pois o Latte os compila diretamente em código PHP e os armazena em cache no disco. Portanto, eles não têm nenhuma sobrecarga adicional em comparação com templates escritos em PHP puro. -O cache é automaticamente regenerado cada vez que você muda o arquivo fonte. Assim, você pode editar convenientemente seus modelos Latte durante o desenvolvimento e ver as mudanças imediatamente no navegador. Você pode desativar este recurso em um ambiente de produção e economizar um pouco de desempenho: +O cache é regenerado automaticamente sempre que você altera o arquivo de origem. Durante o desenvolvimento, você pode editar confortavelmente os templates em Latte e ver as alterações imediatamente no navegador. Você pode desativar esta função no ambiente de produção para economizar um pouco de desempenho: ```php $latte->setAutoRefresh(false); ``` -Quando implantada em um servidor de produção, a geração inicial de cache, especialmente para aplicações maiores, pode, compreensivelmente, demorar um pouco. O Latte tem prevenção embutida contra a "debandada do cache:https://en.wikipedia.org/wiki/Cache_stampede". -Esta é uma situação em que o servidor recebe um grande número de solicitações simultâneas e como o cache do Latte ainda não existe, todos eles o gerariam ao mesmo tempo. O que aumenta a CPU. -O Latte é inteligente, e quando há várias solicitações simultâneas, apenas o primeiro tópico gera o cache, os outros esperam e depois o utilizam. +Ao implantar em um servidor de produção, a geração inicial do cache, especialmente para aplicações maiores, pode, compreensivelmente, levar um momento. Latte tem prevenção integrada contra "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Esta é uma situação em que um grande número de requisições simultâneas chega, que iniciam o Latte, e como o cache ainda não existe, todas começariam a gerá-lo simultaneamente. O que sobrecarregaria indevidamente o servidor. Latte é inteligente e, com múltiplas requisições simultâneas, apenas a primeira thread gera o cache, as outras esperam e depois o utilizam. -Parâmetros como classe .[#toc-parameters-as-a-class] -==================================================== +Parâmetros como classe +====================== -Melhor do que passar variáveis para o modelo como matrizes é criar uma classe. Você recebe [notação de segurança de tipo |type-system], [boa sugestão no IDE |recipes#Editors and IDE] e uma forma de [registrar filtros |extending-latte#Filters Using the Class] e [funções |extending-latte#Functions Using the Class]. +Melhor do que passar variáveis para o template como um array é criar uma classe. Você obterá assim uma [escrita type-safe |type-system], [sugestões úteis no IDE |recipes#Editores e IDEs] e um caminho para [registro de filtros |custom-filters#Filtros usando uma classe com atributos] e [funções |custom-functions#Funções usando uma classe com atributos]. ```php class MailTemplateParameters @@ -88,12 +84,12 @@ $latte->render('mail.latte', new MailTemplateParameters( ``` -Desativação do Auto-Escaping da variável .[#toc-disabling-auto-escaping-of-variable] -==================================================================================== +Desativando o auto-escaping de variável +======================================= -Se a variável contém uma string HTML, você pode marcá-la para que Latte não fuja automaticamente (e portanto duplique) dela. Isto evita a necessidade de especificar `|noescape` no modelo. +Se uma variável contiver uma string em HTML, você pode marcá-la para que o Latte não a escape automaticamente (e, portanto, duplamente). Você evitará assim a necessidade de especificar `|noescape` no template. -A maneira mais fácil é enrolar o cordel em um objeto `Latte\Runtime\Html`: +A maneira mais simples é envolver a string em um objeto `Latte\Runtime\Html`: ```php $params = [ @@ -101,7 +97,7 @@ $params = [ ]; ``` -O Latte também não escapa de todos os objetos que implementam a interface `Latte\HtmlStringable`. Assim, você pode criar sua própria classe cujo método `__toString()` retornará o código HTML que não escapará automaticamente: +Latte também não escapa todos os objetos que implementam a interface `Latte\HtmlStringable`. Você pode, assim, criar sua própria classe, cujo método `__toString()` retornará código HTML que não será escapado automaticamente: ```php class Emphasis extends Latte\HtmlStringable @@ -123,32 +119,85 @@ $params = [ ``` .[warning] -O método `__toString` deve retornar o HTML correto e fornecer parâmetros que escapem, caso contrário pode ocorrer uma vulnerabilidade XSS! +O método `__toString` deve retornar HTML correto e garantir o escape de parâmetros, caso contrário, pode ocorrer uma vulnerabilidade XSS! -Como estender o Latte com filtros, etiquetas, etc. .[#toc-how-to-extend-latte-with-filters-tags-etc] -==================================================================================================== +Como estender o Latte com filtros, tags, etc. +============================================= -Como adicionar um filtro personalizado, função, etiqueta, etc. ao Latte? Descubra no capítulo [que estende o Latte |extending Latte]. -Se você quiser reutilizar suas alterações em diferentes projetos ou se quiser compartilhá-las com outros, você deve então [criar uma extensão |creating-extension]. +Como adicionar seu próprio filtro, função, tag, etc. ao Latte? Isso é abordado no capítulo [estendendo o Latte |extending-latte]. Se você deseja reutilizar suas modificações em diferentes projetos ou compartilhá-las com outros, você deve [criar uma extensão |extending-latte#Latte Extension]. -Qualquer código no modelo `{php ...}` .{data-version:3.0}{toc: RawPhpExtension} -=============================================================================== +Código arbitrário no template `{php ...}` .{toc: RawPhpExtension} +================================================================= -Somente expressões PHP podem ser escritas dentro do [`{do}` |tags#do] por isso não é possível, por exemplo, inserir construções como `if ... else` ou declarações em ponto-e-vírgula. +Dentro da tag [`{do}` |tags#do], apenas expressões PHP podem ser escritas, então você não pode, por exemplo, inserir construções como `if ... else` ou declarações terminadas com ponto e vírgula. -Entretanto, você pode registrar a extensão `RawPhpExtension`, que acrescenta a tag `{php ...}`, que pode ser usada para inserir qualquer código PHP por conta e risco do autor do modelo. +No entanto, você pode registrar a extensão `Latte\Essential\RawPhpExtension`, que adiciona a tag `{php ...}`. Com ela, você pode inserir qualquer código PHP. Nenhuma regra do modo sandbox se aplica a ela, portanto, o uso é de responsabilidade do autor do template. ```php $latte->addExtension(new Latte\Essential\RawPhpExtension); ``` -Tradução em Modelos .{data-version:3.0}{toc: TranslatorExtension} -================================================================= +Verificação do código gerado .{data-version:3.0.7} +================================================== + +Latte compila templates em código PHP. Obviamente, ele garante que o código gerado seja sintaticamente válido. No entanto, ao usar extensões de terceiros ou `RawPhpExtension`, Latte não pode garantir a correção do arquivo gerado. Também é possível escrever código em PHP que, embora sintaticamente correto, é proibido (por exemplo, atribuir um valor à variável `$this`) e causa um Erro de Compilação do PHP. Se você escrever tal operação em um template, ela também chegará ao código PHP gerado. Como existem cerca de duzentas operações proibidas diferentes em PHP, Latte não tem a ambição de detectá-las. O próprio PHP as sinalizará apenas durante a renderização, o que geralmente não é um problema. + +No entanto, existem situações em que você deseja saber já no momento da compilação do template que ele não contém nenhum Erro de Compilação do PHP. Especialmente quando os templates podem ser editados por usuários, ou você usa [Sandbox |sandbox]. Nesse caso, faça com que os templates sejam verificados já no momento da compilação. Esta funcionalidade é ativada pelo método `Engine::enablePhpLint()`. Como ele precisa chamar o binário PHP para a verificação, passe o caminho para ele como parâmetro: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/usr/bin/php'); // Exemplo de caminho + +try { + $latte->compile('home.latte'); +} catch (Latte\CompileException $e) { + // captura erros no Latte e também Compile Error no PHP + echo 'Erro: ' . $e->getMessage(); +} +``` + + +Localidade .{data-version:3.0.18}{toc: Locale} +============================================== + +Latte permite definir a localidade, que afeta a formatação de números, datas e ordenação. É definida usando o método `setLocale()`. O identificador de localidade segue o padrão IETF language tag, que usa a extensão PHP `intl`. Consiste no código do idioma e, opcionalmente, no código do país, por exemplo, `en_US` para inglês nos Estados Unidos, `de_DE` para alemão na Alemanha, `pt_BR` para português no Brasil, etc. + +```php +$latte = new Latte\Engine; +$latte->setLocale('pt_BR'); +``` + +A configuração da localidade afeta os filtros [localDate |filters#localDate], [sort |filters#sort], [number |filters#number] e [bytes |filters#bytes]. + +.[note] +Requer a extensão PHP `intl`. A configuração no Latte não afeta a configuração global de localidade no PHP. + -Use a extensão `TranslatorExtension` para adicionar [`{_...}` |tags#_], [`{translate}` |tags#translate] e filtro [`translate` |filters#translate] para o modelo. Eles são usados para traduzir valores ou partes do modelo para outros idiomas. O parâmetro é o método (chamado PHP) que realiza a tradução: +Modo estrito .{data-version:3.0.8} +================================== + +No modo de análise estrito, o Latte verifica se as tags HTML de fechamento não estão faltando e também proíbe o uso da variável `$this`. Você o ativa assim: + +```php +$latte = new Latte\Engine; +$latte->setStrictParsing(); +``` + +A geração de templates com o cabeçalho `declare(strict_types=1)` é ativada assim: + +```php +$latte = new Latte\Engine; +$latte->setStrictTypes(); +``` + + +Tradução em templates .{toc: TranslatorExtension} +================================================= + +Usando a extensão `Latte\Essential\TranslatorExtension`, você adiciona as tags [`{_...}` |tags#], [`{translate}` |tags#translate] e o filtro [`translate` |filters#translate] ao template. Eles servem para traduzir valores ou partes do template para outros idiomas. Como parâmetro, especificamos o método (PHP callable) que realiza a tradução: ```php class MyTranslator @@ -158,19 +207,19 @@ class MyTranslator public function translate(string $original): string { - // criar $translated from $original according to $this->lang + // de $original criamos $translated de acordo com $this->lang return $translated; } } $translator = new MyTranslator($lang); $extension = new Latte\Essential\TranslatorExtension( - $translator->translate(...), // [$translator, 'translate'] in PHP 8.0 + $translator->translate(...), // [$translator, 'translate'] no PHP 8.0 ); $latte->addExtension($extension); ``` -O tradutor é chamado no momento da execução, quando o modelo é renderizado. Entretanto, o Latte pode traduzir todos os textos estáticos durante a compilação do modelo. Isto economiza desempenho porque cada string é traduzida apenas uma vez e a tradução resultante é escrita no arquivo compilado. Isto cria múltiplas versões compiladas do modelo no diretório do cache, uma para cada idioma. Para fazer isto, basta especificar o idioma como segundo parâmetro: +O tradutor é chamado em tempo de execução ao renderizar o template. No entanto, o Latte pode traduzir todos os textos estáticos já durante a compilação do template. Isso economiza desempenho, pois cada string é traduzida apenas uma vez e a tradução resultante é escrita na forma compilada. No diretório de cache, são criadas várias versões compiladas do template, uma para cada idioma. Para isso, basta especificar o idioma como segundo parâmetro: ```php $extension = new Latte\Essential\TranslatorExtension( @@ -179,9 +228,9 @@ $extension = new Latte\Essential\TranslatorExtension( ); ``` -Por texto estático entendemos, por exemplo, `{_'hello'}` ou `{translate}hello{/translate}`. Textos não estáticos, como `{_$foo}`, continuarão a ser traduzidos em tempo de execução. +Texto estático significa, por exemplo, `{_'hello'}` ou `{translate}hello{/translate}`. Textos não estáticos, como `{_$foo}`, continuarão sendo traduzidos em tempo de execução. -O modelo também pode passar parâmetros adicionais para o tradutor via `{_$original, foo: bar}` ou `{translate foo: bar}`, que recebe como a matriz `$params`: +Também é possível passar parâmetros adicionais do template para o tradutor usando `{_$original, foo: bar}` ou `{translate foo: bar}`, que ele recebe como um array `$params`: ```php public function translate(string $original, ...$params): string @@ -191,66 +240,73 @@ public function translate(string $original, ...$params): string ``` -Depuração e Tracy .[#toc-debugging-and-tracy] -============================================= +Depuração e Tracy +================= -Latte tenta tornar o desenvolvimento tão agradável quanto possível. Para fins de depuração, existem três tags [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak] e [`{trace}` |tags#trace]. +Latte tenta tornar o desenvolvimento o mais agradável possível para você. Diretamente para fins de depuração, existem três tags [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak] e [`{trace}` |tags#trace]. -Você terá o maior conforto se instalar a grande [ferramenta de depuração Tracy |tracy:] e ativar o plugin Latte: +Você obterá o maior conforto se instalar também a excelente [ferramenta de depuração Tracy |tracy:] e ativar o complemento para Latte: ```php -// permite que Tracy +// ativa o Tracy Tracy\Debugger::enable(); $latte = new Latte\Engine; -// ativa a extensão da Tracy +// ativa a extensão para Tracy $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension); ``` -Agora você verá todos os erros em uma bela tela vermelha, incluindo erros em modelos com destaque de linha e coluna ([vídeo |https://github.com/nette/tracy/releases/tag/v2.9.0]). -Ao mesmo tempo, no canto inferior direito da chamada Barra Tracy, aparece uma aba para Latte, onde você pode ver claramente todos os modelos renderizados e suas relações (incluindo a possibilidade de clicar no modelo ou código compilado), bem como as variáveis: +Agora, todos os erros serão exibidos em uma tela vermelha clara, incluindo erros nos templates com destaque de linha e coluna ([vídeo |https://github.com/nette/tracy/releases/tag/v2.9.0]). Ao mesmo tempo, no canto inferior direito, na chamada Tracy Bar, aparecerá uma aba para Latte, onde todos os templates renderizados e suas relações mútuas são claramente visíveis (incluindo a possibilidade de clicar para ir ao template ou ao código compilado) e também as variáveis: [* latte-debugging.webp *] -Como o Latte compila os modelos em código PHP legível, você pode passar convenientemente por eles em seu IDE. +Como o Latte compila templates em código PHP claro, você pode depurá-los confortavelmente em seu IDE. -Linter: Validação da sintaxe do gabarito .{data-version:2.11}{toc: Linter} -========================================================================== +Linter: validação da sintaxe dos templates .{toc: Linter} +========================================================= -A ferramenta Linter o ajudará a percorrer todos os modelos e verificar a existência de erros de sintaxe. Ela é lançada a partir do console: +Para percorrer todos os templates e verificar se eles não contêm erros de sintaxe, a ferramenta Linter o ajudará. Ela é executada a partir do console: ```shell -vendor/bin/latte-lint +vendor/bin/latte-lint ``` -Se você usa etiquetas personalizadas, crie também seu Linter personalizado, por exemplo, `custom-latte-lint`: +O parâmetro `--strict` ativa o [#modo estrito]. + +Se você usa tags personalizadas, crie também sua própria versão do Linter, por exemplo, `custom-latte-lint`: ```php #!/usr/bin/env php scanDirectory($path); +$path = $argv[1] ?? '.'; -$engine = new Latte\Engine; -// registra aqui extensões individuais -$engine->addExtension(/* ... */); +$linter = new Latte\Tools\Linter; +$latte = $linter->getEngine(); +// aqui adicione suas extensões individuais +$latte->addExtension(/* ... */); -$path = $argv[1]; -$linter = new Latte\Tools\Linter(engine: $engine); $ok = $linter->scanDirectory($path); exit($ok ? 0 : 1); ``` +Alternativamente, você pode passar seu próprio objeto `Latte\Engine` para o Linter: + +```php +$latte = new Latte\Engine; +// aqui configuramos o objeto $latte +$linter = new Latte\Tools\Linter(engine: $latte); +``` -Carregando modelos a partir de uma corda .[#toc-loading-templates-from-a-string] -================================================================================ -Necessidade de carregar modelos de cordas em vez de arquivos, talvez para fins de teste? [O StringLoader |extending-latte#stringloader] o ajudará: +Carregando templates de string +============================== + +Precisa carregar templates de strings em vez de arquivos, talvez para fins de teste? [StringLoader |loaders#StringLoader] o ajudará: ```php $latte->setLoader(new Latte\Loaders\StringLoader([ @@ -262,10 +318,10 @@ $latte->render('main.file', $params); ``` -Manipulador de Exceções .[#toc-exception-handler] -================================================= +Manipulador de exceção +====================== -Você pode definir seu próprio manipulador para as exceções esperadas. Exceções levantadas no interior [`{try}` |tags#try] e na [caixa de areia |sandbox] são passados para ela. +Você pode definir seu próprio manipulador para exceções esperadas. As exceções que ocorrem dentro de [`{try}` |tags#try] e no [sandbox |sandbox] serão passadas para ele. ```php $loggingHandler = function (Throwable $e, Latte\Runtime\Template $template) use ($logger) { @@ -277,17 +333,17 @@ $latte->setExceptionHandler($loggingHandler); ``` -Procura automática de layout .[#toc-automatic-layout-lookup] -============================================================ +Busca automática de layout +========================== -Usando a etiqueta [`{layout}` |template-inheritance#layout-inheritance] o modelo determina seu modelo pai. Também é possível ter o layout pesquisado automaticamente, o que simplificará a escrita dos modelos, uma vez que eles não precisarão incluir a tag `{layout}`. +Usando a tag [`{layout}` |template-inheritance#Herança de Layout], o template especifica seu template pai. Também é possível fazer com que o layout seja buscado automaticamente, o que simplificará a escrita dos templates, pois não será necessário especificar a tag `{layout}` neles. -Isto é conseguido da seguinte forma: +Isso é alcançado da seguinte maneira: ```php $finder = function (Latte\Runtime\Template $template) { if (!$template->getReferenceType()) { - // ele retorna o caminho para o arquivo de modelo pai + // retorna o caminho para o arquivo com o layout return 'automatic.layout.latte'; } }; @@ -296,4 +352,4 @@ $latte = new Latte\Engine; $latte->addProvider('coreParentFinder', $finder); ``` -Se o modelo não deve ter um layout, ele o indicará com a tag `{layout none}`. +Se o template não deve ter um layout, ele o indica com a tag `{layout none}`. diff --git a/latte/pt/extending-latte.texy b/latte/pt/extending-latte.texy index 16352a0203..0e126b8a3a 100644 --- a/latte/pt/extending-latte.texy +++ b/latte/pt/extending-latte.texy @@ -1,285 +1,227 @@ -Ampliação do Latte +Estendendo o Latte ****************** .[perex] -O Latte é muito flexível e pode ser estendido de várias maneiras: você pode adicionar filtros personalizados, funções, tags, carregadores, etc. Nós lhe mostraremos como fazer isso. +O Latte foi projetado com a extensibilidade em mente. Embora seu conjunto padrão de tags, filtros e funções cubra muitos casos de uso, muitas vezes você precisa adicionar sua própria lógica específica ou ferramentas auxiliares. Esta página fornece uma visão geral das maneiras de estender o Latte para que ele corresponda perfeitamente aos requisitos do seu projeto - desde ajudantes simples até novas sintaxes complexas. -Este capítulo descreve as diferentes maneiras de estender o Latte. Se você quiser reutilizar suas mudanças em diferentes projetos ou se quiser compartilhá-las com outros, você deve então [criar a chamada extensão |creating-extension]. +Formas de estender o Latte +========================== -Quantas Estradas levam a Roma? .[#toc-how-many-roads-lead-to-rome] -================================================================== +Aqui está uma visão geral rápida das principais maneiras de personalizar e estender o Latte: -Como algumas das formas de estender o Latte podem ser misturadas, vamos primeiro tentar explicar as diferenças entre elas. Como exemplo, vamos tentar implementar um gerador *Lorem ipsum*, que é passado o número de palavras a serem geradas. +- **[Filtros personalizados |Custom Filters]:** Para formatar ou transformar dados diretamente na saída do template (por exemplo, `{$var|myFilter}`). Ideal para tarefas como formatação de datas, modificações de texto ou aplicação de escaping específico. Você também pode usá-los para modificar blocos maiores de conteúdo HTML, envolvendo o conteúdo em um [`{block}` |tags#block] anônimo e aplicando um filtro personalizado a ele. +- **[Funções personalizadas |Custom Functions]:** Para adicionar lógica reutilizável que pode ser chamada dentro de expressões no template (por exemplo, `{myFunction($arg1, $arg2)}`). Útil para cálculos, acesso a funções auxiliares do aplicativo ou geração de pequenas partes de conteúdo. +- **[Tags personalizadas |Custom Tags]:** Para criar construções de linguagem totalmente novas (`{mytag}...{/mytag}` ou `n:mytag`). As tags oferecem mais possibilidades, permitem definir estruturas personalizadas, controlar a análise do template e implementar lógica de renderização complexa. +- **[Passes de compilação |Compiler Passes]:** Funções que modificam a árvore de sintaxe abstrata (AST) do template após a análise, mas antes da geração do código PHP. Usadas para otimizações avançadas, verificações de segurança (como Sandbox) ou modificações automáticas de código. +- **[Loaders personalizados |loaders]:** Para alterar a forma como o Latte localiza e carrega arquivos de template (por exemplo, carregar do banco de dados, armazenamento criptografado, etc.). -A principal construção da linguagem latte é a tag. Podemos implementar um gerador, ampliando o Latte com uma nova tag: +Escolher o método de extensão correto é crucial. Antes de criar uma tag complexa, considere se um filtro ou função mais simples seria suficiente. Vamos ilustrar com um exemplo: implementação de um gerador *Lorem ipsum* que recebe o número de palavras a serem geradas como argumento. -```latte -{lipsum 40} -``` - -A etiqueta vai funcionar muito bem. Entretanto, o gerador na forma de uma etiqueta pode não ser suficientemente flexível porque não pode ser usado em uma expressão. A propósito, na prática, raramente é necessário gerar tags; e isso é uma boa notícia, pois as tags são uma maneira mais complicada de se estender. - -Ok, vamos tentar criar um filtro ao invés de uma etiqueta: - -```latte -{=40|lipsum} -``` - -Mais uma vez, uma opção válida. Mas o filtro deve transformar o valor passado em algo mais. Aqui usamos o valor `40`, que indica o número de palavras geradas, como argumento do filtro, e não como o valor que queremos transformar. +- **Como tag?** `{lipsum 40}` - Possível, mas as tags são mais adequadas para estruturas de controle ou geração de marcação complexa. As tags não podem ser usadas diretamente em expressões. +- **Como filtro?** `{=40|lipsum}` - Tecnicamente funciona, mas os filtros são destinados a *transformar* o valor de entrada. Aqui, `40` é um *argumento*, não um valor que está sendo transformado. Isso parece semanticamente incorreto. +- **Como função?** `{lipsum(40)}` - Esta é a solução mais natural! As funções aceitam argumentos e retornam valores, o que é ideal para uso em qualquer expressão: `{var $text = lipsum(40)}`. -Portanto, vamos tentar usar a função: +**Recomendação geral:** Use funções para cálculos/geração, filtros para transformação e tags para novas construções de linguagem ou marcação complexa. Use passes para manipulação de AST e loaders para obter templates. -```latte -{lipsum(40)} -``` -É isso aí! Para este exemplo em particular, criar uma função é o ponto de extensão ideal a ser utilizado. Você pode chamá-la de qualquer lugar onde uma expressão é aceita, por exemplo: +Registro direto +=============== -```latte -{var $text = lipsum(40)} -``` +Para ferramentas auxiliares específicas do projeto ou extensões rápidas, o Latte permite o registro direto de filtros e funções no objeto `Latte\Engine`. - -Filtros .[#toc-filters] -======================= - -Criar um filtro registrando seu nome e qualquer PHP chamável, como por exemplo uma função: +Para registrar um filtro, use o método `addFilter()`. O primeiro argumento da sua função de filtro será o valor antes do caractere `|` e os argumentos seguintes são aqueles passados após os dois pontos `:`. ```php $latte = new Latte\Engine; -$latte->addFilter('shortify', fn(string $s) => mb_substr($s, 0, 10)); // encurta o texto para 10 caracteres -``` -Neste caso, seria melhor para o filtro obter um parâmetro adicional: +// Definição do filtro (objeto chamável: função, método estático, etc.) +$myTruncate = fn(string $s, int $length = 50) => mb_substr($s, 0, $length); -```php -$latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); -``` - -Usamo-lo em um modelo como este: +// Registro +$latte->addFilter('truncate', $myTruncate); -```latte -

                                                                                                {$text|shortify}

                                                                                                -

                                                                                                {$text|shortify:100}

                                                                                                +// Uso no template: {$text|truncate} ou {$text|truncate:100} ``` -Como você pode ver, a função recebe o lado esquerdo do filtro antes do tubo `|` as the first argument and the arguments passed to the filter after `:` como os próximos argumentos. - -Naturalmente, a função que representa o filtro pode aceitar qualquer número de parâmetros, e parâmetros variáveis também são suportados. - - -Filtros que utilizam a classe .[#toc-filters-using-the-class] -------------------------------------------------------------- - -A segunda maneira de definir um filtro é [usar classe |develop#Parameters as a class]. Criamos um método com o atributo `TemplateFilter`: +Você também pode registrar um **Filter Loader**, uma função que fornece dinamicamente objetos chamáveis de filtros com base no nome solicitado: ```php -class TemplateParameters -{ - public function __construct( - // parameters - ) {} - - #[Latte\Attributes\TemplateFilter] - public function shortify(string $s, int $len = 10): string - { - return mb_substr($s, 0, $len); - } -} - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); +$latte->addFilterLoader(fn(string $name) => /* retorna um objeto chamável ou null */); ``` -Se você estiver usando PHP 7.x e Latte 2.x, use a anotação `/** @filter */` ao invés do atributo. - -Carregador de filtros .[#toc-filter-loader] -------------------------------------------- - -Em vez de registrar filtros individuais, você pode criar um chamado carregador, que é uma função que é chamada com o nome do filtro como argumento e retorna seu PHP chamável, ou nulo. +Para registrar uma função utilizável em expressões de template, use `addFunction()`. ```php -$latte->addFilterLoader([new Filters, 'load']); +$latte = new Latte\Engine; +// Definição da função +$isWeekend = fn(DateTimeInterface $date) => $date->format('N') >= 6; -class Filters -{ - public function load(string $filter): ?callable - { - if (in_array($filter, get_class_methods($this))) { - return [$this, $filter]; - } - return null; - } - - public function shortify($s, $len = 10) - { - return mb_substr($s, 0, $len); - } - - // ... -} +// Registro +$latte->addFunction('isWeekend', $isWeekend); + +// Uso no template: {if isWeekend($myDate)}Fim de semana!{/if} ``` +Mais informações podem ser encontradas nas seções [Criando filtros personalizados |custom-filters] e [Funções |custom-functions]. -Filtros contextuais .[#toc-contextual-filters] ----------------------------------------------- -Um filtro contextual é aquele que aceita um objeto [api:Latte\Runtime\FilterInfo] no primeiro parâmetro, seguido por outros parâmetros como no caso dos filtros clássicos. Ele é registrado da mesma forma, o próprio Latte reconhece que o filtro é contextual: +Método robusto: Extensão Latte .{toc: Latte Extension} +====================================================== -```php -use Latte\Runtime\FilterInfo; +Embora o registro direto seja simples, a maneira padrão e recomendada de empacotar e distribuir extensões Latte é através de classes **Extension**. A Extension serve como um ponto de configuração central para registrar múltiplas tags, filtros, funções, passes de compilação e outros elementos. -$latte->addFilter('foo', function (FilterInfo $info, string $str): string { - // ... -}); -``` +Por que usar Extensions? -Os filtros de contexto podem detectar e alterar o tipo de conteúdo que recebem na variável `$info->contentType`. Se o filtro for chamado classicamente sobre uma variável (por exemplo, `{$var|foo}`), o `$info->contentType` conterá nulo. +- **Organização:** Mantém extensões relacionadas (tags, filtros, etc. para uma funcionalidade específica) juntas em uma única classe. +- **Reutilização e compartilhamento:** Empacote facilmente suas extensões para uso em outros projetos ou para compartilhar com a comunidade (por exemplo, via Composer). +- **Poder total:** Tags personalizadas e passes de compilação *só podem ser registrados* através de Extensions. -O filtro deve primeiro verificar se o tipo de conteúdo da cadeia de entrada é suportado. Ele também pode mudá-lo. Exemplo de um filtro que aceita texto (ou nulo) e retorna HTML: + +Registrando uma Extensão +------------------------ + +Uma Extension é registrada no Latte usando o método `addExtension()` (ou através do [arquivo de configuração |application:configuration#Templates Latte]): ```php -use Latte\Runtime\FilterInfo; - -$latte->addFilter('money', function (FilterInfo $info, float $amount): string { - // primeiro verificamos se o tipo de conteúdo da entrada é texto - if (!in_array($info->contentType, [null, ContentType::Text])) { - throw new Exception("Filter |money used in incompatible content type $info->contentType."); - } - - // mudar o tipo de conteúdo para HTML - $info->contentType = ContentType::Html; - return "$num Kč"; -}); +$latte = new Latte\Engine; +$latte->addExtension(new MyProjectExtension); ``` -.[note] -Neste caso, o filtro deve garantir a fuga correta dos dados. +Se você registrar várias extensões e elas definirem tags, filtros ou funções com o mesmo nome, a extensão adicionada por último tem precedência. Isso também significa que suas extensões podem sobrescrever tags/filtros/funções nativas. -Todos os filtros que são utilizados sobre [blocos |tags#block] (por exemplo, como `{block|foo}...{/block}`) deve ser contextual. +Sempre que você fizer uma alteração na classe e a atualização automática não estiver desativada, o Latte recompilará automaticamente seus templates. -Funções .[#toc-functions] -========================= +Criando uma Extensão +-------------------- -Por padrão, todas as funções do PHP nativo podem ser usadas em Latte, a menos que o sandbox o desabilite. Mas você também pode definir suas próprias funções. Elas podem sobrepor-se às funções nativas. +Para criar sua própria extensão, você precisa criar uma classe que herde de [api:Latte\Extension]. Para ter uma ideia de como essa extensão se parece, dê uma olhada na [CoreExtension |https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php] embutida. -Crie uma função registrando seu nome e qualquer PHP que possa ser chamado: +Vamos dar uma olhada nos métodos que você pode implementar: -```php -$latte = new Latte\Engine; -$latte->addFunction('random', function (...$args) { - return $args[array_rand($args)]; -}); -``` -O uso é então o mesmo que quando se chama a função PHP: +beforeCompile(Latte\Engine $engine): void .[method] +--------------------------------------------------- -```latte -{random(apple, orange, lemon)} // prints for example: apple -``` +Chamado antes da compilação do template. O método pode ser usado, por exemplo, para inicializações relacionadas à compilação. -Funções usando a classe .[#toc-functions-using-the-class] ---------------------------------------------------------- +getTags(): array .[method] +-------------------------- -A segunda maneira de definir uma função é [usar a classe |develop#Parameters as a class]. Criamos um método com o atributo `TemplateFunction`: +Chamado durante a compilação do template. Retorna um array associativo *nome da tag => objeto chamável*, que são funções para analisar tags. [Mais informações |custom-tags]. ```php -class TemplateParameters +public function getTags(): array { - public function __construct( - // parameters - ) {} - - #[Latte\Attributes\TemplateFunction] - public function random(...$args) - { - return $args[array_rand($args)]; - } + return [ + 'foo' => FooNode::create(...), + 'bar' => BarNode::create(...), + 'n:baz' => NBazNode::create(...), + // ... + ]; } - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); ``` -Se você estiver usando PHP 7.x e Latte 2.x, use a anotação `/** @function */` ao invés do atributo. +A tag `n:baz` representa um [n:atributo |syntax#n:atributos] puro, ou seja, uma tag que só pode ser escrita como um atributo. +Para as tags `foo` e `bar`, o Latte reconhece automaticamente se são tags pares e, se sim, podem ser escritas automaticamente usando n:atributos, incluindo variantes com os prefixos `n:inner-foo` e `n:tag-foo`. -Carregadeiras .[#toc-loaders] -============================= +A ordem de execução desses n:atributos é determinada pela sua ordem no array retornado pelo método `getTags()`. Assim, `n:foo` é sempre executado antes de `n:bar`, mesmo que os atributos na tag HTML estejam listados na ordem inversa, como `
                                                                                                `. -Os carregadores são responsáveis por carregar modelos a partir de uma fonte, como um sistema de arquivo. Eles são definidos usando o método `setLoader()`: +Se você precisar especificar a ordem dos n:atributos entre várias extensões, use o método auxiliar `order()`, onde o parâmetro `before` ou `after` especifica quais tags são ordenadas antes ou depois da tag. ```php -$latte->setLoader(new MyLoader); +public function getTags(): array +{ + return [ + 'foo' => self::order(FooNode::create(...), before: 'bar'), + 'bar' => self::order(BarNode::create(...), after: ['block', 'snippet']), + ]; +} ``` -Os carregadores embutidos são: +getPasses(): array .[method] +---------------------------- -FileLoader .[#toc-fileloader] ------------------------------ +Chamado durante a compilação do template. Retorna um array associativo *nome do passe => objeto chamável*, que são funções representando os chamados [passes de compilação |compiler-passes], que percorrem e modificam a AST. -Carregador padrão. Carrega modelos do sistema de arquivos. - -O acesso aos arquivos pode ser restringido através da configuração do diretório base: +Aqui também pode ser usado o método auxiliar `order()`. O valor dos parâmetros `before` ou `after` pode ser `*` com o significado de antes/depois de todos. ```php -$latte->setLoader(new Latte\Loaders\FileLoader($templateDir)); -$latte->render('test.latte'); +public function getPasses(): array +{ + return [ + 'optimize' => Passes::optimizePass(...), + 'sandbox' => self::order($this->sandboxPass(...), before: '*'), + // ... + ]; +} ``` -StringLoader .[#toc-stringloader] ---------------------------------- +beforeRender(Latte\Engine $engine): void .[method] +-------------------------------------------------- -Carrega modelos a partir de cordas. Esta carregadeira é muito útil para testes unitários. Também pode ser usado para pequenos projetos onde pode fazer sentido armazenar todos os gabaritos em um único arquivo PHP. +Chamado antes de cada renderização do template. O método pode ser usado, por exemplo, para inicializar variáveis usadas durante a renderização. -```php -$latte->setLoader(new Latte\Loaders\StringLoader([ - 'main.file' => '{include other.file}', - 'other.file' => '{if true} {$var} {/if}', -])); -$latte->render('main.file'); -``` +getFilters(): array .[method] +----------------------------- -Uso simplificado: +Chamado antes da renderização do template. Retorna filtros como um array associativo *nome do filtro => objeto chamável*. [Mais informações |custom-filters]. ```php -$template = '{if true} {$var} {/if}'; -$latte->setLoader(new Latte\Loaders\StringLoader); -$latte->render($template); +public function getFilters(): array +{ + return [ + 'batch' => $this->batchFilter(...), + 'trim' => $this->trimFilter(...), + // ... + ]; +} ``` -Criando um Carregador Personalizado .[#toc-creating-a-custom-loader] --------------------------------------------------------------------- - -Loader é uma classe que implementa a interface [api:Latte\Loader]. +getFunctions(): array .[method] +------------------------------- +Chamado antes da renderização do template. Retorna funções como um array associativo *nome da função => objeto chamável*. [Mais informações |custom-functions]. -Etiquetas .[#toc-tags] -====================== +```php +public function getFunctions(): array +{ + return [ + 'clamp' => $this->clampFunction(...), + 'divisibleBy' => $this->divisibleByFunction(...), + // ... + ]; +} +``` -Uma das características mais interessantes do motor de modelagem é a capacidade de definir novas construções de linguagem usando tags. É também uma funcionalidade mais complexa e você precisa entender como o Latte funciona internamente. -Na maioria dos casos, no entanto, a etiqueta não é necessária: -- se ela deve gerar alguma saída, use a [função |#functions] -- se fosse para modificar alguma entrada e devolvê-la, usar [filtro |#filters] em seu lugar -- se fosse para editar uma área de texto, embrulhe-a com um [`{block}` |tags#block] e usar um [filtro |#Contextual Filters] -- se não era para produzir nada, mas apenas chamar uma função, chame-a com [`{do}` |tags#do] +getProviders(): array .[method] +------------------------------- -Se você ainda quiser criar uma etiqueta, ótimo! Todos os elementos essenciais podem ser encontrados em [Criar uma Extensão |creating-extension]. +Chamado antes da renderização do template. Retorna um array de provedores, que geralmente são objetos usados por tags em tempo de execução. Eles são acessados via `$this->global->...`. [Mais informações |custom-tags#Apresentando os provedores]. +```php +public function getProviders(): array +{ + return [ + 'myFoo' => $this->foo, + 'myBar' => $this->bar, + // ... + ]; +} +``` -Passes de Compilador .[#toc-compiler-passes] -============================================ -Os passes de compilador são funções que modificam os ASTs ou coletam informações neles. Em Latte, por exemplo, uma caixa de areia é implementada desta forma: atravessa todos os nós de um AST, encontra chamadas de funções e métodos, e as substitui por chamadas controladas. +getCacheKey(Latte\Engine $engine): mixed .[method] +-------------------------------------------------- -Assim como as tags, esta é uma funcionalidade mais complexa e você precisa entender como o Latte funciona sob o capô. Todos os elementos essenciais podem ser encontrados no capítulo [Criação de uma Extensão |creating-extension]. +Chamado antes da renderização do template. O valor de retorno se torna parte da chave, cujo hash está contido no nome do arquivo do template compilado. Portanto, para diferentes valores de retorno, o Latte gerará diferentes arquivos de cache. diff --git a/latte/pt/filters.texy b/latte/pt/filters.texy index 6b6a4748c9..f4856a0e71 100644 --- a/latte/pt/filters.texy +++ b/latte/pt/filters.texy @@ -1,112 +1,114 @@ -Filtros de Latte -**************** +Filtros Latte +************* .[perex] -Os filtros são funções que mudam ou formatam os dados para um formulário que desejamos. Este é um resumo dos filtros incorporados que estão disponíveis. +Nos templates, podemos usar funções que ajudam a modificar ou reformatar dados para a forma final. Chamamo-las de *filtros*. .[table-latte-filters] -|## Transformação de corda / matriz -| `batch` | [listando dados lineares em uma tabela |#batch] -| `breakLines` | [Insere quebras de linha HTML antes de todas as novas linhas |#breakLines] -| `bytes` | [formatos em bytes |#bytes] -| `clamp` | [valor dos grampos para a faixa |#clamp] -| `dataStream` | [Conversão do protocolo Data URI |#datastream] -| `date` | [formatos data |#date] -| `explode` | [divide um fio pelo delimitador dado |#explode] -| `first` | [devolve o primeiro elemento de matriz ou caráter de cadeia |#first] -| `implode` | [junta um conjunto a um cordão |#implode] -| `indent` | [traça o texto da esquerda com o número de abas |#indent] -| `join` | [junta um conjunto a um cordão |#implode] -| `last` | [retorna o último elemento de matriz ou caráter de cadeia |#last] -| `length` | [devolve o comprimento de um fio ou matriz |#length] -| `number` | [número de formatos |#number] -| `padLeft` | [completa o cordel a dado comprimento a partir da esquerda |#padLeft] -| `padRight` | [completa o cordel a dado comprimento a partir da direita |#padRight] -| `random` | [retorna elemento aleatório de matriz ou caráter de cadeia |#random] -| `repeat` | [repete o fio |#repeat] -| `replace` | [substitui todas as ocorrências da cadeia de busca com a substituição |#replace] -| `replaceRE` | [substitui todas as ocorrências de acordo com a expressão regular |#replaceRE] -| `reverse` | [inverte uma corda ou matriz UTF-8 |#reverse] -| `slice` | [extrai uma fatia de uma matriz ou um fio |#slice] -| `sort` | [classifica uma série |#sort] -| `spaceless` | [remove o espaço em branco |#spaceless], semelhante à etiqueta [sem espaçamento |tags] -| `split` | [divide um fio pelo delimitador dado |#explode] -| `strip` | [remove o espaço em branco |#spaceless] -| `stripHtml` | [remove as tags HTML e converte entidades HTML em texto |#stripHtml] -| `substr` | [devolve parte da cadeia |#substr] -| `trim` | [tira o espaço em branco da corda |#trim] -| `translate` | [tradução para outros idiomas |#translate] -| `truncate` | [encurta o comprimento preservando palavras inteiras |#truncate] -| `webalize` | [ajusta a corda UTF-8 à forma usada na URL |#webalize] +|## Transformação +| `batch` | [exibição de dados lineares numa tabela |#batch] +| `breakLines` | [Adiciona quebras de linha HTML antes do final da linha |#breakLines] +| `bytes` | [formata o tamanho em bytes |#bytes] +| `clamp` | [limita o valor ao intervalo dado |#clamp] +| `dataStream` | [conversão para o protocolo Data URI |#dataStream] +| `date` | [formata data e hora |#date] +| `explode` | [divide uma string num array por um separador |#explode] +| `first` | [retorna o primeiro elemento de um array ou caractere de uma string |#first] +| `group` | [agrupa dados por vários critérios |#group] +| `implode` | [une um array numa string |#implode] +| `indent` | [indenta o texto da esquerda por um número especificado de tabulações |#indent] +| `join` | [une um array numa string |#implode] +| `last` | [retorna o último elemento de um array ou caractere de uma string |#last] +| `length` | [retorna o comprimento de uma string em caracteres ou de um array |#length] +| `localDate` | [formata data e hora de acordo com a localidade |#localDate] +| `number` | [formata um número |#number] +| `padLeft` | [preenche uma string à esquerda até o comprimento desejado |#padLeft] +| `padRight` | [preenche uma string à direita até o comprimento desejado |#padRight] +| `random` | [retorna um elemento aleatório de um array ou caractere de uma string |#random] +| `repeat` | [repetição de string |#repeat] +| `replace` | [substitui ocorrências da string de pesquisa |#replace] +| `replaceRE` | [substitui ocorrências de acordo com uma expressão regular |#replaceRE] +| `reverse` | [inverte uma string UTF-8 ou um array |#reverse] +| `slice` | [extrai uma parte de um array ou string |#slice] +| `sort` | [ordena um array |#sort] +| `spaceless` | [remove espaços em branco |#spaceless], semelhante à tag [spaceless |tags] +| `split` | [divide uma string num array por um separador |#explode] +| `strip` | [remove espaços em branco |#spaceless] +| `stripHtml` | [remove tags HTML e converte entidades HTML em caracteres |#stripHtml] +| `substr` | [retorna uma parte de uma string |#substr] +| `trim` | [remove espaços em branco ou outros caracteres do início e do fim |#trim] +| `translate` | [tradução para outros idiomas |#translate] +| `truncate` | [reduz o comprimento preservando palavras |#truncate] +| `webalize` | [modifica uma string UTF-8 para a forma usada em URLs |#webalize] .[table-latte-filters] -|## Caixa de papelão -| `capitalize` | [minúscula a primeira letra de cada palavra maiúscula |#capitalize] -| `firstUpper` | [faz a primeira letra maiúscula |#firstUpper] -| `lower` | [faz uma minúscula |#lower] -| `upper` | [faz uma maiúscula de corda |#upper] +|## Maiúsculas/Minúsculas +| `capitalize` | [minúsculas, primeira letra das palavras maiúscula |#capitalize] +| `firstUpper` | [converte a primeira letra para maiúscula |#firstUpper] +| `lower` | [converte para minúsculas |#lower] +| `upper` | [converte para maiúsculas |#upper] .[table-latte-filters] -|### Arredondamento de números -| `ceil` | [arredonda um número até uma determinada precisão |#ceil] -| `floor` | [arredonda um número para uma determinada precisão |#floor] -| `round` | [arredonda um número para uma dada precisão |#round] +|## Arredondamento +| `ceil` | [arredonda um número para cima para a precisão dada |#ceil] +| `floor` | [arredonda um número para baixo para a precisão dada |#floor] +| `round` | [arredonda um número para a precisão dada |#round] .[table-latte-filters] -|## Escapando -| `escapeUrl` | [parâmetro de fuga no URL |#escapeUrl] -| `noescape` | [imprime uma variável sem fugir |#noescape] -| `query` | [gera uma cadeia de consulta na URL |#query] +|## Escaping +| `escapeUrl` | [escapa um parâmetro numa URL |#escapeUrl] +| `noescape` | [imprime uma variável sem escapar |#noescape] +| `query` | [gera uma query string numa URL |#query] -Há também filtros de fuga para HTML (`escapeHtml` e `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) e iCalendar (`escapeICal`), que Latte se utiliza graças à [fuga consciente do contexto |safety-first#Context-aware escaping] e você não precisa escrevê-los. +Além disso, existem filtros de escaping para HTML (`escapeHtml` e `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) e iCalendar (`escapeICal`), que o Latte usa por si só graças ao [escaping sensível ao contexto |safety-first#Escaping sensível ao contexto] e não é necessário escrevê-los. .[table-latte-filters] -|### Segurança -| `checkUrl` | [higieniza cadeia para uso dentro do atributo href |#checkUrl] -| `nocheck` | [previne a higienização automática da URL |#nocheck] +|## Segurança +| `checkUrl` | [trata um endereço URL contra entradas perigosas |#checkUrl] +| `nocheck` | [evita o tratamento automático do endereço URL |#nocheck] -Latte os atributos `src` e `href` [verificam automaticamente |safety-first#link checking], de modo que quase não é necessário usar o filtro `checkUrl`. +Os atributos Latte `src` e `href` [verificam automaticamente |safety-first#Verificação de links], pelo que quase nunca precisa de usar o filtro `checkUrl`. .[note] -Todos os filtros integrados trabalham com cordas codificadas UTF-8. +Todos os filtros padrão são destinados a strings na codificação UTF-8. -Utilização .[#toc-usage] -======================== +Uso +=== -O Latte permite a chamada de filtros utilizando a notação do sinal de canalização (o espaço precedente é permitido): +Os filtros são escritos após uma barra vertical (pode haver um espaço antes dela): ```latte

                                                                                                {$heading|upper}

                                                                                                ``` -Os filtros podem ser acorrentados, nesse caso, eles se aplicam por ordem da esquerda para a direita: +Os filtros (em versões mais antigas, helpers) podem ser encadeados e são aplicados na ordem da esquerda para a direita: ```latte

                                                                                                {$heading|lower|capitalize}

                                                                                                ``` -Os parâmetros são colocados após o nome do filtro separado por dois pontos ou vírgula: +Os parâmetros são inseridos após o nome do filtro, separados por dois pontos ou vírgulas: ```latte

                                                                                                {$heading|truncate:20,''}

                                                                                                ``` -Os filtros podem ser aplicados por expressão: +Os filtros também podem ser aplicados a uma expressão: ```latte {var $name = ($title|upper) . ($subtitle|lower)} ``` -[Os filtros personalizados |extending-latte#filters] podem ser registrados desta forma: +[Filtros personalizados|custom-filters] podem ser registados desta forma: ```php $latte = new Latte\Engine; $latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -Usamo-lo em um modelo como este: +No template, é chamado assim: ```latte

                                                                                                {$text|shortify}

                                                                                                @@ -114,13 +116,13 @@ Usamo-lo em um modelo como este: ``` -Filtros .[#toc-filters] -======================= +Filtros +======= -batch(int length, mixed item): array .[filter]{data-version:2.7} ----------------------------------------------------------------- -Filtro que simplifica a listagem de dados lineares na forma de uma tabela. Ele retorna uma matriz com o determinado número de itens. Se você fornecer um segundo parâmetro, este é usado para preencher os itens em falta na última linha. +batch(int $length, mixed $item): array .[filter] +------------------------------------------------ +Filtro que simplifica a exibição de dados lineares na forma de uma tabela. Retorna um array de arrays com o número especificado de itens. Se fornecer um segundo parâmetro, ele será usado para preencher os itens em falta na última linha. ```latte {var $items = ['a', 'b', 'c', 'd', 'e']} @@ -135,7 +137,7 @@ Filtro que simplifica a listagem de dados lineares na forma de uma tabela. Ele r
                                                                                                Loop interno
                                                                                                ``` -Impressões: +Exibe: ```latte @@ -152,20 +154,22 @@ Impressões:
                                                                                                ``` +Veja também [#group] e a tag [iterateWhile |tags#iterateWhile]. + breakLines .[filter] -------------------- -Insere quebras de linha HTML antes de todas as novas linhas. +Adiciona a tag HTML `
                                                                                                ` antes de cada caractere de nova linha. ```latte {var $s = "Text & with \n newline"} -{$s|breakLines} {* resultados "Text & with
                                                                                                \n newline" *} +{$s|breakLines} {* exibe "Text & with
                                                                                                \n newline" *} ``` -bytes(int precision = 2) .[filter] ----------------------------------- -Formata um tamanho em bytes de forma legível para o ser humano. +bytes(int $precision=2) .[filter] +--------------------------------- +Formata o tamanho em bytes de forma legível por humanos. Se a [localidade |develop#Locale] estiver definida, os separadores decimais e de milhares correspondentes serão usados. ```latte {$size|bytes} 0 B, 1.25 GB, … @@ -173,72 +177,72 @@ Formata um tamanho em bytes de forma legível para o ser humano. ``` -ceil(int precision = 0) .[filter] ---------------------------------- -Arredonda um número até uma determinada precisão. +ceil(int $precision=0) .[filter] +-------------------------------- +Arredonda um número para cima para a precisão dada. ```latte -{=3.4|ceil} {* saídas 4 *} -{=135.22|ceil:1} {* saídas 135,3 *} -{=135.22|ceil:3} {* saídas 135,22 *} +{=3.4|ceil} {* exibe 4 *} +{=135.22|ceil:1} {* exibe 135.3 *} +{=135.22|ceil:3} {* exibe 135.22 *} ``` -Veja também [piso |#floor], [redondo |#round]. +Veja também [#floor], [#round]. capitalize .[filter] -------------------- -Devolve uma versão do valor com base no título. As palavras começarão com letras maiúsculas, todos os caracteres restantes são minúsculos. Requer extensão PHP `mbstring`. +As palavras começarão com letras maiúsculas, todos os caracteres restantes serão minúsculos. Requer a extensão PHP `mbstring`. ```latte -{='i like LATTE'|capitalize} {* output 'I Like Latte' *} +{='i like LATTE'|capitalize} {* exibe 'I Like Latte' *} ``` -Ver também [firstUpper |#firstUpper], [lower |#lower], [upper |#upper]. +Veja também [#firstUpper], [#lower], [#upper]. checkUrl .[filter] ------------------ -Impõe a higienização do URL. Verifica se a variável contém uma URL da web (ou seja, protocolo HTTP/HTTPS) e impede a escrita de links que possam representar um risco de segurança. +Força o tratamento do endereço URL. Verifica se a variável contém uma URL da web (ou seja, protocolo HTTP/HTTPS) e evita a exibição de links que podem representar um risco de segurança. ```latte {var $link = 'javascript:window.close()'} -checked -unchecked +verificado +não verificado ``` -Impressões: +Exibe: ```latte -checked -unchecked +verificado +não verificado ``` -Veja também [nocheck |#nocheck]. +Veja também [#nocheck]. -clamp(int|float min, int|float max) .[filter]{data-version:2.9} ---------------------------------------------------------------- -Retorna o valor fixado para a faixa inclusiva de min e max. +clamp(int|float $min, int|float $max) .[filter] +----------------------------------------------- +Limita o valor ao intervalo inclusivo especificado de min e max. ```latte {$level|clamp: 0, 255} ``` -Também existe como [função |functions#clamp]. +Existe também como [função |functions#clamp]. -dataStream(string mimetype = detect) .[filter] ----------------------------------------------- -Converte o conteúdo para o esquema URI de dados. Pode ser usado para inserir imagens em HTML ou CSS sem a necessidade de vincular arquivos externos. +dataStream(string $mimetype=detect) .[filter] +--------------------------------------------- +Converte o conteúdo para o esquema data URI. Com ele, é possível incorporar imagens em HTML ou CSS sem a necessidade de vincular arquivos externos. -Vamos ter uma imagem em uma variável `$img = Image::fromFile('obrazek.gif')`, então +Tenha na variável a imagem `$img = Image::fromFile('imagem.gif')`, então ```latte - + ``` -Impressões, por exemplo: +Exibe, por exemplo: ```latte {$name} ``` -Veja também [consulta |#query]. +Veja também [#query]. -explode(string separator = '') .[filter]{data-version:2.10.2} -------------------------------------------------------------- -Divide um fio pelo delimitador dado e retorna um conjunto de fios. Alias para `split`. +explode(string $separator='') .[filter] +--------------------------------------- +Divide uma string num array por um separador. Alias para `split`. ```latte -{='um,dois,três'|explodir:','} {* retorna ['um', 'dois', 'três'] *} +{='one,two,three'|explode:','} {* retorna ['one', 'two', 'three'] *} ``` -Se o delimitador for uma cadeia vazia (valor padrão), a entrada será dividida em caracteres individuais: +Se o separador for uma string vazia (valor padrão), a entrada será dividida em caracteres individuais: ```latte -{='123'|explode} {* retorna ['1', '2', '3'] *} +{='123'|explode} {* retorna ['1', '2', '3'] *} ``` -Você também pode usar o pseudônimo `split`: +Também pode usar o alias `split`: ```latte {='1,2,3'|split:','} {* retorna ['1', '2', '3'] *} ``` -Veja também [implodir |#implode]. +Veja também [#implode]. -first .[filter]{data-version:2.10.2} ------------------------------------- -Devolve o primeiro elemento de matriz ou caráter de corda: +first .[filter] +--------------- +Retorna o primeiro elemento de um array ou caractere de uma string: ```latte -{=[1, 2, 3, 4]|first} {* outputs 1 *} -{='abcd'|first} {* outputs 'a' *} +{=[1, 2, 3, 4]|first} {* exibe 1 *} +{='abcd'|first} {* exibe 'a' *} ``` -Ver também [último |#last], [aleatório |#random]. +Veja também [#last], [#random]. -floor(int precision = 0) .[filter] ----------------------------------- -Arredonda um número para uma determinada precisão. +floor(int $precision=0) .[filter] +--------------------------------- +Arredonda um número para baixo para a precisão dada. ```latte -{=3.5|floor} {* outputs 3 *} -{=135.79|floor:1} {* outputs 135.7 *} -{=135.79|floor:3} {* outputs 135.79 *} +{=3.5|floor} {* exibe 3 *} +{=135.79|floor:1} {* exibe 135.7 *} +{=135.79|floor:3} {* exibe 135.79 *} ``` -Veja também [Cessar |#ceil], [redondo |#round]. +Veja também [#ceil], [#round]. firstUpper .[filter] -------------------- -Converte uma primeira letra de valor em maiúsculas. Requer extensão PHP `mbstring`. +Converte a primeira letra para maiúscula. Requer a extensão PHP `mbstring`. ```latte -{='the latte'|firstUpper} {* outputs 'The latte' *} +{='the latte'|firstUpper} {* exibe 'The latte' *} ``` -Veja também [capitalizar |#capitalize], [inferior |#lower], [superior |#upper]. +Veja também [#capitalize], [#lower], [#upper]. -implode(string glue = '') .[filter] ------------------------------------ -Retorna um fio que é a concatenação das cordas na matriz. Alias para `join`. +group(string|int|\Closure $by): array .[filter]{data-version:3.0.16} +-------------------------------------------------------------------- +O filtro agrupa dados por vários critérios. + +Neste exemplo, as linhas da tabela são agrupadas pela coluna `categoryId`. A saída é um array de arrays, onde a chave é o valor na coluna `categoryId`. [Leia o tutorial detalhado|cookbook/grouping]. ```latte -{=[1, 2, 3]|implode} {* outputs '123' *} -{=[1, 2, 3]|implode:'|'} {* outputs '1|2|3' *} +{foreach ($items|group: categoryId) as $categoryId => $categoryItems} +
                                                                                                  + {foreach $categoryItems as $item} +
                                                                                                • {$item->name}
                                                                                                • + {/foreach} +
                                                                                                +{/foreach} ``` -Você também pode usar um pseudônimo `join`: .{data-version:2.10.2} +Veja também [#batch], a função [group |functions#group] e a tag [iterateWhile |tags#iterateWhile]. + + +implode(string $glue='') .[filter] +---------------------------------- +Retorna uma string que é a concatenação dos itens da sequência. Alias para `join`. ```latte -{=[1, 2, 3]|join} {* outputs '123' *} +{=[1, 2, 3]|implode} {* exibe '123' *} +{=[1, 2, 3]|implode:'|'} {* exibe '1|2|3' *} ``` +Também pode usar o alias `join`: + +```latte +{=[1, 2, 3]|join} {* exibe '123' *} +``` -indent(int level = 1, string char = "\t") .[filter] ---------------------------------------------------- -Indica um texto da esquerda por um determinado número de abas ou outros caracteres que especificamos no segundo argumento opcional. As linhas em branco não são indentadas. + +indent(int $level=1, string $char="\t") .[filter] +------------------------------------------------- +Indenta o texto da esquerda por um número especificado de tabulações ou outros caracteres, que podem ser especificados no segundo argumento. Linhas vazias não são indentadas. ```latte
                                                                                                @@ -358,7 +382,7 @@ Indica um texto da esquerda por um determinado número de abas ou outros caracte
                                                                                                ``` -Impressões: +Exibe: ```latte
                                                                                                @@ -367,26 +391,26 @@ Impressões: ``` -last .[filter]{data-version:2.10.2} ------------------------------------ -Retorna o último elemento de matriz ou caráter de corda: +last .[filter] +-------------- +Retorna o último elemento de um array ou caractere de uma string: ```latte -{=[1, 2, 3, 4]|last} {* outputs 4 *} -{='abcd'|last} {* outputs 'd' *} +{=[1, 2, 3, 4]|last} {* exibe 4 *} +{='abcd'|last} {* exibe 'd' *} ``` -Veja também [primeiro |#first], [aleatório |#random]. +Veja também [#first], [#random]. length .[filter] ---------------- -Retorna o comprimento de um fio ou matriz. +Retorna o comprimento de uma string ou array. -- para cordas, ele retornará o comprimento em caracteres UTF-8 -- para arrays, ele retornará a contagem dos itens -- para objetos que implementam a interface Countable, ele usará o valor de retorno da contagem() -- para objetos que implementam a interface IteratorAggregate, ele usará o valor de retorno do iterator_count() +- para strings, retorna o comprimento em caracteres UTF-8 +- para arrays, retorna o número de itens +- para objetos que implementam a interface `Countable`, usa o valor de retorno do método `count()` +- para objetos que implementam a interface `IteratorAggregate`, usa o valor de retorno da função `iterator_count()` ```latte @@ -396,204 +420,314 @@ Retorna o comprimento de um fio ou matriz. ``` +localDate(?string $format=null, ?string $date=null, ?string $time=null) .[filter] +--------------------------------------------------------------------------------- +Formata data e hora de acordo com a [localidade |develop#Locale], o que garante uma exibição consistente e localizada de informações de tempo em diferentes idiomas e regiões. O filtro aceita a data como timestamp UNIX, string ou objeto do tipo `DateTimeInterface`. + +```latte +{$date|localDate} {* 15 de abril de 2024 *} +{$date|localDate: format: yM} {* 4/2024 *} +{$date|localDate: date: medium} {* 15/04/2024 *} +``` + +Se usar o filtro sem parâmetros, a data será exibida no nível `long`, veja abaixo. + +**a) uso do formato** + +O parâmetro `format` descreve quais componentes de tempo devem ser exibidos. Ele usa códigos de letras para eles, cujo número de repetições afeta a largura da saída: + +| ano | `y` / `yy` / `yyyy` | `2024` / `24` / `2024` +| mês | `M` / `MM` / `MMM` / `MMMM` | `8` / `08` / `ago` / `agosto` +| dia | `d` / `dd` / `E` / `EEEE` | `1` / `01` / `dom` / `domingo` +| hora | `j` / `H` / `h` | preferido / 24 horas / 12 horas +| minuto | `m` / `mm` | `5` / `05` (2 dígitos em combinação com segundos) +| segundo | `s` / `ss` | `8` / `08` (2 dígitos em combinação com minutos) + +A ordem dos códigos no formato não importa, pois a ordem dos componentes será exibida de acordo com os costumes da localidade. O formato é, portanto, independente dela. Por exemplo, o formato `yyyyMMMMd` no ambiente `en_US` exibe `April 15, 2024`, enquanto no ambiente `pt_BR` exibe `15 de abril de 2024`: + +| locale: | pt_BR | en_US +|--- +| `format: 'dMy'` | 10/8/2024 | 8/10/2024 +| `format: 'yM'` | 8/2024 | 8/2024 +| `format: 'yyyyMMMM'` | agosto de 2024 | August 2024 +| `format: 'MMMM'` | agosto | August +| `format: 'jm'` | 17:22 | 5:22 PM +| `format: 'Hm'` | 17:22 | 17:22 +| `format: 'hm'` | 5:22 PM | 5:22 PM + + +**b) uso de estilos predefinidos** + +Os parâmetros `date` e `time` determinam com que detalhe a data e a hora devem ser exibidas. Pode escolher entre vários níveis: `full`, `long`, `medium`, `short`. É possível exibir apenas a data, apenas a hora ou ambos: + +| locale: | pt_BR | en_US +|--- +| `date: short` | 23/01/78 | 1/23/78 +| `date: medium` | 23 de jan. de 1978 | Jan 23, 1978 +| `date: long` | 23 de janeiro de 1978 | January 23, 1978 +| `date: full` | segunda-feira, 23 de janeiro de 1978 | Monday, January 23, 1978 +| `time: short` | 08:30 | 8:30 AM +| `time: medium` | 08:30:59 | 8:30:59 AM +| `time: long` | 08:30:59 GMT+1 | 8:30:59 AM GMT+1 +| `date: short, time: short` | 23/01/78 08:30 | 1/23/78, 8:30 AM +| `date: medium, time: short` | 23 de jan. de 1978 08:30 | Jan 23, 1978, 8:30 AM +| `date: long, time: short` | 23 de janeiro de 1978 às 08:30 | January 23, 1978 at 8:30 AM + +Para a data, também pode ser usado o prefixo `relative-` (por exemplo, `relative-short`), que para datas próximas ao presente exibirá `ontem`, `hoje` ou `amanhã`, caso contrário, será exibido da maneira padrão. + +```latte +{$date|localDate: date: relative-short} {* ontem *} +``` + +Veja também [#date]. + + lower .[filter] --------------- -Converte um valor para minúsculas. Requer extensão PHP `mbstring`. +Converte uma string para minúsculas. Requer a extensão PHP `mbstring`. ```latte -{='LATTE'|lower} {* outputs 'latte' *} +{='LATTE'|lower} {* exibe 'latte' *} ``` -Ver também [capitalize |#capitalize], [firstUpper |#firstUpper], [upper |#upper]. +Veja também [#capitalize], [#firstUpper], [#upper]. nocheck .[filter] ----------------- -Impede a higienização automática da URL. Latte [verifica automaticamente |safety-first#Link checking] se a variável contém uma URL da web (ou seja, protocolo HTTP/HTTPS) e impede a escrita de links que possam representar um risco de segurança. +Evita o tratamento automático do endereço URL. O Latte [verifica automaticamente |safety-first#Verificação de links] se a variável contém uma URL da web (ou seja, protocolo HTTP/HTTPS) e evita a exibição de links que podem representar um risco de segurança. -Se o link usa um esquema diferente, como `javascript:` ou `data:`, e você tem certeza de seu conteúdo, você pode desativar a verificação via `|nocheck`. +Se o link usar outro esquema, como `javascript:` ou `data:`, e tiver certeza do seu conteúdo, pode desativar a verificação usando `|nocheck`. ```latte {var $link = 'javascript:window.close()'} -checked -unchecked +verificado +não verificado ``` -Impressões: +Exibe: ```latte -checked -unchecked +verificado +não verificado ``` -Veja também [checkUrl |#checkUrl]. +Veja também [#checkUrl]. noescape .[filter] ------------------ -Desativa a fuga automática. +Desativa o escaping automático. ```latte -{var $trustedHtmlString = 'hello'} -Escaped: {$trustedHtmlString} -Unescaped: {$trustedHtmlString|noescape} +{var $trustedHtmlString = 'olá'} +Escapado: {$trustedHtmlString} +Não escapado: {$trustedHtmlString|noescape} ``` -Impressões: +Exibe: ```latte -Escaped: <b>hello</b> -Unescaped: hello +Escapado: <b>olá</b> +Não escapado: olá ``` .[warning] -O mau uso do filtro `noescape` pode levar a uma vulnerabilidade XSS! Nunca o use a menos que você esteja **absolutamente seguro** do que está fazendo e que o fio que você está imprimindo vem de uma fonte confiável. +O uso incorreto do filtro `noescape` pode levar à vulnerabilidade XSS! Nunca o use a menos que tenha **certeza absoluta** do que está a fazer e que a string exibida vem de uma fonte confiável. -number(int decimals = 0, string decPoint = '.', string thousandsSep = ',') .[filter] ------------------------------------------------------------------------------------- -Formata um número para um determinado número de casas decimais. Você também pode especificar um caractere do separador de casas decimais e de milhares. +number(int $decimals=0, string $decPoint='.', string $thousandsSep=',') .[filter] +--------------------------------------------------------------------------------- +Formata um número para um certo número de casas decimais. Se a [localidade |develop#Locale] estiver definida, os separadores decimais e de milhares correspondentes serão usados. ```latte -{1234.20 |number} 1,234 -{1234.20 |number:1} 1,234.2 -{1234.20 |number:2} 1,234.20 -{1234.20 |number:2, ',', ' '} 1 234,20 +{1234.20|number} 1,234 +{1234.20|number:1} 1,234.2 +{1234.20|number:2} 1,234.20 +{1234.20|number:2, ',', ' '} 1 234,20 ``` -padLeft(int length, string pad = ' ') .[filter] +number(string $format) .[filter] +-------------------------------- +O parâmetro `format` permite definir a aparência dos números exatamente de acordo com as suas necessidades. Para isso, é necessário ter a [localidade |develop#Locale] configurada. O formato consiste em vários caracteres especiais, cuja descrição completa pode ser encontrada na documentação "DecimalFormat":https://unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns: + +- `0` dígito obrigatório, sempre será exibido, mesmo que seja zero +- `#` dígito opcional, será exibido apenas se o número realmente existir neste local +- `@` dígito significativo, ajuda a exibir o número com um certo número de dígitos válidos +- `.` indica onde deve estar a vírgula decimal (ou ponto, dependendo do país) +- `,` serve para separar grupos de dígitos, mais comumente milhares +- `%` multiplica o número por 100× e adiciona o sinal de porcentagem + +Vamos dar uma olhada nos exemplos. No primeiro exemplo, duas casas decimais são obrigatórias, no segundo, opcionais. O terceiro exemplo mostra o preenchimento com zeros à esquerda e à direita, o quarto exibe apenas os dígitos existentes: + +```latte +{1234.5|number: '#,##0.00'} {* 1,234.50 *} +{1234.5|number: '#,##0.##'} {* 1,234.5 *} +{1.23 |number: '000.000'} {* 001.230 *} +{1.2 |number: '##.##'} {* 1.2 *} +``` + +Os dígitos significativos determinam quantos dígitos, independentemente da vírgula decimal, devem ser exibidos, com arredondamento: + +```latte +{1234|number: '@@'} {* 1200 *} +{1234|number: '@@@'} {* 1230 *} +{1234|number: '@@@#'} {* 1234 *} +{1.2345|number: '@@@'} {* 1.23 *} +{0.00123|number: '@@'} {* 0.0012 *} +``` + +Uma maneira fácil de exibir um número como porcentagem. O número é multiplicado por 100× e o sinal `%` é adicionado: + +```latte +{0.1234|number: '#.##%'} {* 12.34% *} +``` + +Podemos definir um formato diferente para números positivos e negativos, separados pelo caractere `;`. Desta forma, por exemplo, pode-se definir que números positivos devem ser exibidos com o sinal `+`: + +```latte +{42|number: '#.##;(#.##)'} {* 42 *} +{-42|number: '#.##;(#.##)'} {* (42) *} +{42|number: '+#.##;-#.##'} {* +42 *} +{-42|number: '+#.##;-#.##'} {* -42 *} +``` + +Lembre-se de que a aparência real dos números pode variar dependendo das configurações do país. Por exemplo, em alguns países, usa-se vírgula em vez de ponto como separador decimal. Este filtro leva isso em consideração automaticamente e não precisa de se preocupar com nada. + + +padLeft(int $length, string $pad=' ') .[filter] ----------------------------------------------- -Coloca um cordel a um certo comprimento com outro cordel da esquerda. +Preenche uma string até um certo comprimento com outra string pela esquerda. ```latte -{='hello'|padLeft: 10, '123'} {* outputs '12312hello' *} +{='hello'|padLeft: 10, '123'} {* exibe '12312hello' *} ``` -padRight(int length, string pad = ' ') .[filter] +padRight(int $length, string $pad=' ') .[filter] ------------------------------------------------ -Coloca um cordel a um certo comprimento com outro cordel da direita. +Preenche uma string até um certo comprimento com outra string pela direita. ```latte -{='hello'|padRight: 10, '123'} {* outputs 'hello12312' *} +{='hello'|padRight: 10, '123'} {* exibe 'hello12312' *} ``` -query .[filter]{data-version:2.10} ------------------------------------ -Dinamicamente, gera uma cadeia de consulta na URL: +query .[filter] +--------------- +Gera dinamicamente uma query string numa URL: ```latte -click -search +clique +pesquisar ``` -Impressões: +Exibe: ```latte -click -search +clique +pesquisar ``` -As chaves com um valor de `null` são omitidas. +Chaves com valor `null` são omitidas. -Veja também [escapeUrl |#escapeUrl]. +Veja também [#escapeUrl]. -random .[filter]{data-version:2.10.2} -------------------------------------- -Devolve elemento aleatório de matriz ou caráter de corda: +random .[filter] +---------------- +Retorna um elemento aleatório de um array ou caractere de uma string: ```latte -{=[1, 2, 3, 4]|random} {* example output: 3 *} -{='abcd'|random} {* example output: 'b' *} +{=[1, 2, 3, 4]|random} {* exibe por ex.: 3 *} +{='abcd'|random} {* exibe por ex.: 'b' *} ``` -Ver também [primeiro |#first], [último |#last]. +Veja também [#first], [#last]. -repeat(int count) .[filter] ---------------------------- -Repete a seqüência x-vezes. +repeat(int $count) .[filter] +---------------------------- +Repete uma string x vezes. ```latte -{='hello'|repeat: 3} {* outputs 'hellohellohello' *} +{='hello'|repeat: 3} {* exibe 'hellohellohello' *} ``` -replace(string|array search, string replace = '') .[filter] +replace(string|array $search, string $replace='') .[filter] ----------------------------------------------------------- -Substitui todas as ocorrências da cadeia de busca pela cadeia de substituição. +Substitui todas as ocorrências da string de pesquisa pela string de substituição. ```latte -{='hello world'|replace: 'world', 'friend'} {* outputs 'hello friend' *} +{='hello world'|replace: 'world', 'friend'} {* exibe 'hello friend' *} ``` -Substituições múltiplas podem ser feitas de uma só vez: .{data-version:2.10.2} +É possível realizar várias substituições de uma vez: ```latte -{='hello world'|replace: [h => l, l => h]} {* outputs 'lehho worhd' *} +{='hello world'|replace: [h => l, l => h]} {* exibe 'lehho worhd' *} ``` -replaceRE(string pattern, string replace = '') .[filter] +replaceRE(string $pattern, string $replace='') .[filter] -------------------------------------------------------- -Substitui todas as ocorrências de acordo com a expressão regular. +Realiza uma pesquisa de expressão regular com substituição. ```latte -{='hello world'|replaceRE: '/l.*/', 'l'} {* outputs 'hel' *} +{='hello world'|replaceRE: '/l.*/', 'l'} {* exibe 'hel' *} ``` reverse .[filter] ----------------- -Inverte seqüência ou matriz dada. +Inverte a string ou array fornecido. ```latte {var $s = 'Nette'} -{$s|reverse} {* outputs 'etteN' *} +{$s|reverse} {* exibe 'etteN' *} {var $a = ['N', 'e', 't', 't', 'e']} -{$a|reverse} {* returns ['e', 't', 't', 'e', 'N'] *} +{$a|reverse} {* retorna ['e', 't', 't', 'e', 'N'] *} ``` -round(int precision = 0) .[filter] ----------------------------------- -Arredonda um número para uma dada precisão. +round(int $precision=0) .[filter] +--------------------------------- +Arredonda um número para a precisão dada. ```latte -{=3.4|round} {* outputs 3 *} -{=3.5|round} {* outputs 4 *} -{=135.79|round:1} {* outputs 135.8 *} -{=135.79|round:3} {* outputs 135.79 *} +{=3.4|round} {* exibe 3 *} +{=3.5|round} {* exibe 4 *} +{=135.79|round:1} {* exibe 135.8 *} +{=135.79|round:3} {* exibe 135.79 *} ``` -Veja também [ceil |#ceil], [chão |#floor]. +Veja também [#ceil], [#floor]. -slice(int start, int length = null, bool preserveKeys = false) .[filter]{data-version:2.10.2} ---------------------------------------------------------------------------------------------- -Extrai uma fatia de uma matriz ou um fio. +slice(int $start, ?int $length=null, bool $preserveKeys=false) .[filter] +------------------------------------------------------------------------ +Extrai uma parte de um array ou string. ```latte -{='hello'|slice: 1, 2} {* outputs 'el' *} -{=['a', 'b', 'c']|slice: 1, 2} {* outputs ['b', 'c'] *} +{='hello'|slice: 1, 2} {* exibe 'el' *} +{=['a', 'b', 'c']|slice: 1, 2} {* exibe ['b', 'c'] *} ``` -O filtro de fatias funciona como a função PHP `array_slice` para arrays e `mb_substr` para strings com um fallback para `iconv_substr` no modo UTF-8. +O filtro funciona como a função PHP `array_slice` para arrays ou `mb_substr` para strings com fallback para a função `iconv_substr` no modo UTF-8. -Se o início for não negativo, a seqüência começará nesse início na variável. Se o início for negativo, a seqüência começará tão longe do final da variável. +Se `start` for positivo, a sequência começará deslocada por este número a partir do início do array/string. Se for negativo, a sequência começará deslocada por tanto a partir do fim. -Se o comprimento for dado e for positivo, então a seqüência terá até muitos elementos nele. Se a variável for menor do que o comprimento, então somente os elementos variáveis disponíveis estarão presentes. Se o comprimento for dado e for negativo, então a seqüência interromperá que muitos elementos do final da variável. Se for omitido, então a seqüência terá tudo desde o offset até o final da variável. +Se o parâmetro `length` for especificado e for positivo, a sequência conterá tantos elementos. Se um parâmetro `length` negativo for passado para esta função, a sequência conterá todos os elementos do array original, começando na posição `start` e terminando na posição `length` elementos antes do fim do array. Se este parâmetro não for especificado, a sequência conterá todos os elementos do array original, começando na posição `start`. -O filtro reordenará e redefinirá as chaves da matriz inteira por padrão. Este comportamento pode ser alterado ajustando preserveKeys para true. As chaves de string são sempre preservadas, independentemente deste parâmetro. +Por padrão, o filtro reordena e redefine as chaves inteiras do array. Este comportamento pode ser alterado definindo `preserveKeys` como `true`. As chaves de string são sempre preservadas, independentemente deste parâmetro. -sort .[filter]{data-version:2.9} ---------------------------------- -Filtro que ordena uma matriz e mantém a associação de índices. +sort(?Closure $comparison, string|int|\Closure|null $by=null, string|int|\Closure|bool $byKey=false) .[filter] +-------------------------------------------------------------------------------------------------------------- +O filtro ordena os elementos de um array ou iterador e preserva as suas chaves associativas. Quando a [localidade |develop#Locale] está definida, a ordenação segue as suas regras, a menos que uma função de comparação personalizada seja especificada. ```latte {foreach ($names|sort) as $name} @@ -601,7 +735,7 @@ Filtro que ordena uma matriz e mantém a associação de índices. {/foreach} ``` -Array ordenados em ordem inversa. +Array ordenado em ordem inversa: ```latte {foreach ($names|sort|reverse) as $name} @@ -609,16 +743,42 @@ Array ordenados em ordem inversa. {/foreach} ``` -Você pode passar sua própria função de comparação como um parâmetro: .{data-version:2.10.2} +Pode especificar uma função de comparação personalizada para ordenação (o exemplo mostra como inverter a ordenação do maior para o menor): ```latte -{var $sorted = ($names|sort: fn($a, $b) => $b <=> $a)} +{var $reverted = ($names|sort: fn($a, $b) => $b <=> $a)} ``` +O filtro `|sort` também permite ordenar elementos por chaves: -spaceless .[filter]{data-version:2.10.2} ------------------------------------------ -Remove espaços em branco desnecessários da produção. Você também pode usar o pseudônimo `strip`. +```latte +{foreach ($names|sort: byKey: true) as $name} + ... +{/foreach} +``` + +Se precisar de ordenar uma tabela por uma coluna específica, pode usar o parâmetro `by`. O valor `'name'` no exemplo especifica que a ordenação será por `$item->name` ou `$item['name']`, dependendo se `$item` é um array ou objeto: + +```latte +{foreach ($items|sort: by: 'name') as $item} + {$item->name} +{/foreach} +``` + +Também pode definir uma função de callback que determinará o valor pelo qual ordenar: + +```latte +{foreach ($items|sort: by: fn($items) => $items->category->name) as $item} + {$item->name} +{/foreach} +``` + +O parâmetro `byKey` pode ser usado da mesma maneira. + + +spaceless .[filter] +------------------- +Remove espaços em branco desnecessários da saída. Também pode usar o alias `strip`. ```latte {block |spaceless} @@ -628,7 +788,7 @@ Remove espaços em branco desnecessários da produção. Você também pode usar {/block} ``` -Impressões: +Exibe: ```latte
                                                                                                • Hello
                                                                                                @@ -637,47 +797,47 @@ Impressões: stripHtml .[filter] ------------------- -Converte HTML em texto simples. Ou seja, remove as tags HTML e converte entidades HTML em texto. +Converte HTML para texto puro. Ou seja, remove as tags HTML e converte as entidades HTML em caracteres. ```latte -{='

                                                                                                one < two

                                                                                                '|stripHtml} {* outputs 'one < two' *} +{='

                                                                                                one < two

                                                                                                '|stripHtml} {* exibe 'one < two' *} ``` -O texto simples resultante pode naturalmente conter caracteres que representam tags HTML, por exemplo `'<p>'|stripHtml` é convertido para `

                                                                                                `. Nunca envie o texto resultante com `|noescape`, pois isso pode levar a uma vulnerabilidade de segurança. +O texto puro resultante pode naturalmente conter caracteres que representam tags HTML, por exemplo, `'<p>'|stripHtml` é convertido para `

                                                                                                `. Em nenhuma circunstância exiba o texto resultante com `|noescape`, pois isso pode levar a uma falha de segurança. -substr(int offset, int length = null) .[filter] ------------------------------------------------ -Extrai uma fatia de um fio. Este filtro foi substituído por uma [fatia de |#slice] filtro. +substr(int $offset, ?int $length=null) .[filter] +------------------------------------------------ +Extrai uma parte de uma string. Este filtro foi substituído pelo filtro [#slice]. ```latte {$string|substr: 1, 2} ``` -translate(string message, ...args) .[filter]{data-version:3.0} --------------------------------------------------------------- -Ela traduz expressões para outros idiomas. Para tornar o filtro disponível, é necessário [instalar um tradutor |develop#TranslatorExtension]. Você também pode usar as [tags para a tradução |tags#Translation]. +translate(...$args) .[filter] +----------------------------- +Traduz expressões para outros idiomas. Para que o filtro esteja disponível, é necessário [configurar o tradutor |develop#TranslatorExtension]. Também pode usar [tags para tradução |tags#Traduções]. ```latte -{='Baskter'|translate} +{='Carrinho'|translate} {$item|translate} ``` -trim(string charlist = " \t\n\r\0\x0B\u{A0}") .[filter] -------------------------------------------------------- -Tire os personagens que lideram e seguem, por padrão, o espaço em branco. +trim(string $charlist=" \t\n\r\0\x0B\u{A0}") .[filter] +------------------------------------------------------ +Remove espaços em branco (ou outros caracteres) do início e do fim de uma string. ```latte -{=' I like Latte. '|trim} {* outputs 'I like Latte.' *} -{=' I like Latte.'|trim: '.'} {* outputs ' I like Latte' *} +{=' I like Latte. '|trim} {* exibe 'I like Latte.' *} +{=' I like Latte.'|trim: '.'} {* exibe ' I like Latte' *} ``` -truncate(int length, string append = '…') .[filter] +truncate(int $length, string $append='…') .[filter] --------------------------------------------------- -Encurta um fio até o comprimento máximo dado, mas tenta preservar palavras inteiras. Se a cadeia for truncada, acrescenta elipses no final (isto pode ser alterado pelo segundo parâmetro). +Corta uma string para o comprimento máximo especificado, tentando preservar palavras inteiras. Se a string for encurtada, adiciona reticências no final (pode ser alterado com o segundo parâmetro). ```latte {var $title = 'Hello, how are you?'} @@ -689,25 +849,25 @@ Encurta um fio até o comprimento máximo dado, mas tenta preservar palavras int upper .[filter] --------------- -Converte um valor em maiúsculas. Requer extensão PHP `mbstring`. +Converte uma string para maiúsculas. Requer a extensão PHP `mbstring`. ```latte -{='latte'|upper} {* outputs 'LATTE' *} +{='latte'|upper} {* exibe 'LATTE' *} ``` -Veja também [capitalizar |#capitalize], [primeiroCima |#firstUpper], [mais baixo |#lower]. +Veja também [#capitalize], [#firstUpper], [#lower]. webalize .[filter] ------------------ -Converte para ASCII. +Modifica uma string UTF-8 para a forma usada em URLs. -Converte espaços em hífens. Remove caracteres que não são alfanuméricos, sublinhados ou hífens. Converte para minúsculas. Também abre e desvia espaços em branco. +Converte para ASCII. Converte espaços em hífens. Remove caracteres que não são alfanuméricos, sublinhados ou hífens. Converte para minúsculas. Também remove espaços iniciais e finais. ```latte -{var $s = 'Our 10. product'} -{$s|webalize} {* outputs 'our-10-product' *} +{var $s = 'Nosso 10º produto'} +{$s|webalize} {* exibe 'nosso-10-produto' *} ``` .[caution] -Requer pacote [nette/utils |utils:]. +Requer a biblioteca [nette/utils|utils:]. diff --git a/latte/pt/functions.texy b/latte/pt/functions.texy index 46df70bdb4..6f03ca69b1 100644 --- a/latte/pt/functions.texy +++ b/latte/pt/functions.texy @@ -1,23 +1,25 @@ -Funções do Latte -**************** +Funções Latte +************* .[perex] -Além das funções comuns do PHP, você também pode usá-las em modelos. +Nos templates, além das funções PHP comuns, podemos usar estas outras funções. .[table-latte-filters] -| `clamp` | [valor dos grampos para a faixa |#clamp] +| `clamp` | [limita o valor ao intervalo dado |#clamp] | `divisibleBy`| [verifica se uma variável é divisível por um número |#divisibleBy] -| `even` | [verifica se o número dado é igual |#even] -| `first` | [devolve o primeiro elemento de matriz ou caráter de cadeia |#first] -| `last` | [retorna o último elemento de matriz ou caráter de cadeia |#last] -| `odd` | [verifica se o número dado é ímpar |#odd] -| `slice` | [extrai uma fatia de uma matriz ou um fio |#slice] +| `even` | [verifica se um número dado é par |#even] +| `first` | [retorna o primeiro elemento de um array ou caractere de uma string |#first] +| `group` | [agrupa dados por vários critérios |#group] +| `hasBlock` | [verifica a existência de um bloco |#hasBlock] +| `last` | [retorna o último elemento de um array ou caractere de uma string |#last] +| `odd` | [verifica se um número dado é ímpar |#odd] +| `slice` | [extrai uma parte de um array ou string |#slice] -Utilização .[#toc-usage] -======================== +Uso +=== -As funções são usadas da mesma forma que as funções comuns do PHP e podem ser usadas em todas as expressões: +As funções são usadas da mesma forma que as funções PHP comuns e podem ser usadas em todas as expressões: ```latte

                                                                                                {clamp($num, 1, 100)}

                                                                                                @@ -25,14 +27,14 @@ As funções são usadas da mesma forma que as funções comuns do PHP e podem s {if odd($num)} ... {/if} ``` -[As funções personalizadas |extending-latte#functions] podem ser registradas desta forma: +[Funções personalizadas|custom-functions] podem ser registadas desta forma: ```php $latte = new Latte\Engine; $latte->addFunction('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -Usamo-lo em um modelo como este: +No template, é chamado assim: ```latte

                                                                                                {shortify($text)}

                                                                                                @@ -40,23 +42,23 @@ Usamo-lo em um modelo como este: ``` -Funções .[#toc-functions] -========================= +Funções +======= -clamp(int|float $value, int|float $min, int|float $max): int|float .[method]{data-version:2.9} ----------------------------------------------------------------------------------------------- -Retorna o valor fixado para a faixa inclusiva de min e max. +clamp(int|float $value, int|float $min, int|float $max): int|float .[method] +---------------------------------------------------------------------------- +Limita o valor ao intervalo inclusivo especificado de min e max. ```latte {=clamp($level, 0, 255)} ``` -Veja também a [braçadeira do filtro |filters#clamp]: +Veja também [o filtro clamp |filters#clamp]. -divisibleBy(int $value, int $by): bool .[method]{data-version:2.10.2} ---------------------------------------------------------------------- +divisibleBy(int $value, int $by): bool .[method] +------------------------------------------------ Verifica se uma variável é divisível por um número. ```latte @@ -64,61 +66,91 @@ Verifica se uma variável é divisível por um número. ``` -even(int $value): bool .[method]{data-version:2.10.2} ------------------------------------------------------ -Verifica se o número dado é igual. +even(int $value): bool .[method] +-------------------------------- +Verifica se um número dado é par. ```latte {if even($num)} ... {/if} ``` -first(string|array $value): mixed .[method]{data-version:2.10.2} ----------------------------------------------------------------- -Devolve o primeiro elemento de matriz ou caráter de corda: +first(string|iterable $value): mixed .[method] +---------------------------------------------- +Retorna o primeiro elemento de um array ou caractere de uma string: ```latte -{=first([1, 2, 3, 4])} {* resultados 1 *} -{=first('abcd')} {* resultados 'a' *} +{=first([1, 2, 3, 4])} {* exibe 1 *} +{=first('abcd')} {* exibe 'a' *} ``` -Ver também [por último |#last], [filtrar primeiro |filters#first]. +Veja também [#last], [o filtro first |filters#first]. -last(string|array $value): mixed .[method]{data-version:2.10.2} ---------------------------------------------------------------- -Retorna o último elemento de matriz ou caráter de corda: +group(iterable $data, string|int|\Closure $by): array .[method]{data-version:3.0.16} +------------------------------------------------------------------------------------ +A função agrupa dados por vários critérios. + +Neste exemplo, as linhas da tabela são agrupadas pela coluna `categoryId`. A saída é um array de arrays, onde a chave é o valor na coluna `categoryId`. [Leia o tutorial detalhado|cookbook/grouping]. + +```latte +{foreach group($items, categoryId) as $categoryId => $categoryItems} +
                                                                                                  + {foreach $categoryItems as $item} +
                                                                                                • {$item->name}
                                                                                                • + {/foreach} +
                                                                                                +{/foreach} +``` + +Veja também o filtro [group |filters#group]. + + +hasBlock(string $name): bool .[method]{data-version:3.0.10} +----------------------------------------------------------- +Verifica se o bloco com o nome especificado existe: + +```latte +{if hasBlock(header)} ... {/if} +``` + +Veja também [verificação da existência de blocos |template-inheritance#Verificando a Existência de Blocos]. + + +last(string|array $value): mixed .[method] +------------------------------------------ +Retorna o último elemento de um array ou caractere de uma string: ```latte -{=last([1, 2, 3, 4])} {* saídas 4 *} -{=last('abcd')} {* saídas 'd' *} +{=last([1, 2, 3, 4])} {* exibe 4 *} +{=last('abcd')} {* exibe 'd' *} ``` -Ver também [primeiro |#first], [filtrar por último |filters#last]. +Veja também [#first], [o filtro last |filters#last]. -odd(int $value): bool .[method]{data-version:2.10.2} ----------------------------------------------------- -Verifica se o número dado é estranho. +odd(int $value): bool .[method] +------------------------------- +Verifica se um número dado é ímpar. ```latte {if odd($num)} ... {/if} ``` -slice(string|array $value, int $start, int $length=null, bool $preserveKeys=false): string|array .[method]{data-version:2.10.2} -------------------------------------------------------------------------------------------------------------------------------- -Extrai uma fatia de uma matriz ou um fio. +slice(string|array $value, int $start, ?int $length=null, bool $preserveKeys=false): string|array .[method] +----------------------------------------------------------------------------------------------------------- +Extrai uma parte de um array ou string. ```latte -{=slice('hello', 1, 2)} {* resultados 'el' *} -{=slice(['a', 'b', 'c'], 1, 2)} {* resultados ['b', 'c'] *} +{=slice('hello', 1, 2)} {* exibe 'el' *} +{=slice(['a', 'b', 'c'], 1, 2)} {* exibe ['b', 'c'] *} ``` -O filtro de fatias funciona como a função PHP `array_slice` para arrays e `mb_substr` para strings com um fallback para `iconv_substr` no modo UTF-8. +O filtro funciona como a função PHP `array_slice` para arrays ou `mb_substr` para strings com fallback para a função `iconv_substr` no modo UTF-8. -Se o início for não negativo, a seqüência começará nesse início na variável. Se o início for negativo, a seqüência começará tão longe do final da variável. +Se `start` for positivo, a sequência começará deslocada por este número a partir do início do array/string. Se for negativo, a sequência começará deslocada por tanto a partir do fim. -Se o comprimento for dado e for positivo, então a seqüência terá até muitos elementos nele. Se a variável for menor do que o comprimento, então somente os elementos variáveis disponíveis estarão presentes. Se o comprimento for dado e for negativo, então a seqüência interromperá que muitos elementos do final da variável. Se for omitido, então a seqüência terá tudo desde o offset até o final da variável. +Se o parâmetro `length` for especificado e for positivo, a sequência conterá tantos elementos. Se um parâmetro `length` negativo for passado para esta função, a sequência conterá todos os elementos do array original, começando na posição `start` e terminando na posição `length` elementos antes do fim do array. Se este parâmetro não for especificado, a sequência conterá todos os elementos do array original, começando na posição `start`. -O filtro reordenará e redefinirá as chaves da matriz inteira por padrão. Este comportamento pode ser alterado ajustando preserveKeys para true. As chaves de string são sempre preservadas, independentemente deste parâmetro. +Por padrão, o filtro reordena e redefine as chaves inteiras do array. Este comportamento pode ser alterado definindo `preserveKeys` como `true`. As chaves de string são sempre preservadas, independentemente deste parâmetro. diff --git a/latte/pt/guide.texy b/latte/pt/guide.texy index 27e6705265..388bf558db 100644 --- a/latte/pt/guide.texy +++ b/latte/pt/guide.texy @@ -1,46 +1,45 @@ -Começando com o Latte -********************* +Começando com Latte +*******************
                                                                                                -Os modelos melhoram a organização do código, separam a lógica de aplicação da apresentação, e aumentam a segurança. Eles oferecem características muito melhores e capacidades expressivas para gerar HTML do que o próprio PHP. +Os templates melhoram a organização do código, separam a lógica da aplicação da apresentação e aumentam a segurança. Oferecem funções e meios de expressão muito melhores para gerar HTML do que o próprio PHP. -Latte é o sistema de modelos mais seguro para PHP. Você vai adorar sua sintaxe intuitiva. Uma ampla gama de recursos úteis simplificará significativamente seu trabalho. -Ele oferece proteção de primeira linha contra [vulnerabilidades críticas |safety-first] e permite que você se concentre na criação de aplicações de alta qualidade sem se preocupar com sua segurança. +O Latte é o sistema de template mais seguro para PHP. Vai adorar a sua sintaxe intuitiva. Uma ampla gama de funções úteis facilitará significativamente o seu trabalho. Fornece proteção de ponta contra [vulnerabilidades críticas|safety-first] e permite que se concentre na criação de aplicações de qualidade sem se preocupar com a sua segurança. -Como escrever modelos usando Latte? .[#toc-how-to-write-templates-using-latte] ------------------------------------------------------------------------------- +Como escrever templates com Latte? +---------------------------------- -O Latte é inteligentemente projetado e fácil de aprender para aqueles familiarizados com PHP, pois eles podem rapidamente adotar suas tags básicas. +O Latte é inteligentemente projetado e fácil de aprender para aqueles que conhecem PHP e dominam as tags básicas. -- Primeiro, familiarize-se com a [sintaxe do Latte |syntax] e [experimente tudo isso online |https://fiddle.nette.org/latte/] -- Dê uma olhada no conjunto básico de [tags |tags] e [filtros |filters] -- Escrever modelos em [editor com suporte de Latte |recipes#Editors and IDE] +- Primeiro, familiarize-se com a [sintaxe Latte|syntax] e [EXPERIMENTE ONLINE |https://fiddle.nette.org/latte/#9cc0cf6d89] +- Dê uma olhada no conjunto básico de [tags|tags] e [filtros|filters] +- Escreva templates num [editor com suporte Latte |recipes#Editores e IDEs] -Como usar o Latte em PHP? .[#toc-how-to-use-latte-in-php] ---------------------------------------------------------- +Como usar o Latte em PHP? +------------------------- -A implementação do Latte em sua nova aplicação é uma questão de minutos: +Implementar o Latte na sua nova aplicação é uma questão de minutos: -- Primeiro, [instale e execute o Latte |develop#Installation] -- Mime-se com a [ferramenta de depuração Tracy |develop#Debugging and Tracy] -- Ampliar o Latte com [funcionalidade personalizada |extending-latte] +- Primeiro, [Instale e execute o Latte |develop#Instalação] +- Deixe-se mimar pela [ferramenta de depuração Tracy |develop#Depuração e Tracy] +- Estenda o Latte com [funcionalidade personalizada |extending-latte] -Se você estiver convertendo um projeto antigo escrito em PHP simples para Latte, a [ferramenta de conversão de código PHP para Latte |cookbook/migration-from-php] tornará a migração mais fácil para você. Ou você está planejando mudar do Twig para o Latte? Nós temos um [conversor de modelo de Twig para Latte |cookbook/migration-from-twig] para você. +Se estiver a converter um projeto antigo escrito em PHP puro para Latte, a migração será facilitada pela [ferramenta para converter código PHP para Latte |cookbook/migration-from-php]. Ou está a planear mudar para o Latte a partir do Twig? Temos para si um [conversor de templates Twig para Latte |cookbook/migration-from-twig]. -O que mais o Latte pode fazer? .[#toc-what-else-can-latte-do] -------------------------------------------------------------- +O que mais o Latte pode fazer? +------------------------------ -O café com leite vem totalmente equipado, com todos os itens essenciais incluídos. +Recebe o Latte totalmente equipado, com tudo o que é importante incluído. -- Sua produtividade será impulsionada pelos [mecanismos de herança |template-inheritance] que reutilizam elementos e estruturas repetidas -- O bunker de armadura [Sandbox |Sandbox] isola modelos de fontes não confiáveis, tais como os editados pelos próprios usuários -- Para maior inspiração, aqui estão [dicas e truques |recipes] +- A sua produtividade será impulsionada pelos [mecanismos de herança |template-inheritance], graças aos quais elementos e estruturas repetidos são reutilizados +- O bunker blindado [sandbox] isola templates de fontes não confiáveis, que, por exemplo, são editados pelos próprios usuários +- Para mais inspiração, existem [dicas e truques |recipes]
                                                                                                -{{description: Latte é o sistema de modelos mais seguro para PHP. Ela evita muitas vulnerabilidades de segurança. Você apreciará sua sintaxe intuitiva e apreciará muitos ajustes úteis.}} +{{description: O Latte é o sistema de template mais seguro para PHP. Previne muitas vulnerabilidades de segurança. Apreciará a sua sintaxe intuitiva e muitos recursos úteis.}} diff --git a/latte/pt/loaders.texy b/latte/pt/loaders.texy new file mode 100644 index 0000000000..312e67567f --- /dev/null +++ b/latte/pt/loaders.texy @@ -0,0 +1,198 @@ +Loaders +******* + +.[perex] +Loaders são o mecanismo que o Latte usa para obter o código-fonte dos seus templates. Mais comumente, os templates são armazenados como arquivos em disco, mas graças ao sistema flexível de loaders, pode carregá-los de praticamente qualquer lugar ou até mesmo gerá-los dinamicamente. + + +O que é um Loader? +================== + +Quando trabalha com templates, geralmente imagina arquivos `.latte` localizados na estrutura de diretórios do seu projeto. O [#FileLoader] padrão no Latte cuida disso. No entanto, a ligação entre o nome do template (como `'main.latte'` ou `'components/card.latte'`) e o seu código-fonte real *não precisa* ser um mapeamento direto para um caminho de arquivo. + +É aqui que entram os loaders. Um loader é um objeto responsável por pegar um nome de template (uma string de identificação) e fornecer ao Latte o seu código-fonte. O Latte depende inteiramente do loader configurado para esta tarefa. Isso aplica-se não apenas ao template inicial solicitado via `$latte->render('main.latte')`, mas também a **cada template referenciado internamente** usando tags como `{include ...}`, `{layout ...}`, `{embed ...}` ou `{import ...}`. + +Porquê usar um loader personalizado? + +- **Carregar de fontes alternativas:** Obter templates armazenados numa base de dados, em cache (como Redis ou Memcached), num sistema de controlo de versão (como Git, com base num commit específico) ou gerados dinamicamente. +- **Implementar convenções de nomenclatura personalizadas:** Pode querer usar aliases mais curtos para templates ou implementar lógica específica de caminho de pesquisa (por exemplo, procurar primeiro no diretório do tema, depois voltar para o diretório padrão). +- **Adicionar segurança ou controlo de acesso:** Um loader personalizado pode verificar as permissões do usuário antes de carregar certos templates. +- **Pré-processamento:** Embora geralmente não recomendado ([passos de compilação |compiler-passes] são melhores), um loader *poderia* teoricamente pré-processar o conteúdo do template antes de passá-lo para o Latte. + +Define o loader para uma instância `Latte\Engine` usando o método `setLoader()`: + +```php +$latte = new Latte\Engine; + +// Usando o FileLoader padrão para arquivos em '/path/to/templates' +$loader = new Latte\Loaders\FileLoader('/path/to/templates'); +$latte->setLoader($loader); +``` + +O loader deve implementar a interface `Latte\Loader`. + + +Loaders Integrados +================== + +O Latte oferece vários loaders padrão: + + +FileLoader +---------- + +Este é o **loader padrão** usado pela classe `Latte\Engine` se nenhum outro for especificado. Ele carrega templates diretamente do sistema de arquivos. + +Opcionalmente, pode definir um diretório raiz para restringir o acesso: + +```php +use Latte\Loaders\FileLoader; + +// O seguinte permitirá carregar templates apenas do diretório /var/www/html/templates +$loader = new FileLoader('/var/www/html/templates'); +$latte->setLoader($loader); + +// $latte->render('../../../etc/passwd'); // Isso lançaria uma exceção + +// Renderizando um template localizado em /var/www/html/templates/pages/contact.latte +$latte->render('pages/contact.latte'); +``` + +Ao usar tags como `{include}` ou `{layout}`, ele resolve os nomes dos templates relativamente ao template atual, a menos que um caminho absoluto seja fornecido. + + +StringLoader +------------ + +Este loader obtém o conteúdo do template de um array associativo, onde as chaves são nomes de template (identificadores) e os valores são strings de código-fonte do template. É particularmente útil para testes ou pequenas aplicações onde os templates podem ser armazenados diretamente no código PHP. + +```php +use Latte\Loaders\StringLoader; + +$loader = new StringLoader([ + 'main.latte' => 'Olá {$name}, o include está abaixo:{include helper.latte}', + 'helper.latte' => '{var $x = 10}Conteúdo incluído: {$x}', + // Adicione mais templates conforme necessário +]); + +$latte->setLoader($loader); + +$latte->render('main.latte', ['name' => 'Mundo']); +// Saída: Olá Mundo, o include está abaixo:Conteúdo incluído: 10 +``` + +Se precisar de renderizar apenas um único template diretamente de uma string sem a necessidade de includes ou herança referenciando outros templates de string nomeados, pode passar a string diretamente para o método `render()` ou `renderToString()` ao usar `StringLoader` sem um array: + +```php +$loader = new StringLoader; +$latte->setLoader($loader); + +$templateString = 'Olá {$name}!'; +$output = $latte->renderToString($templateString, ['name' => 'Alice']); +// $output contém 'Olá Alice!' +``` + + +Criando um Loader Personalizado +=============================== + +Para criar um loader personalizado (por exemplo, para carregar templates de uma base de dados, cache, sistema de controlo de versão ou outra fonte), precisa de criar uma classe que implemente a interface [api:Latte\Loader]. + +Vamos ver o que cada método deve fazer. + + +getContent(string $name): string .[method] +------------------------------------------ +Este é o método principal do loader. A sua tarefa é obter e retornar o código-fonte completo do template identificado por `$name` (conforme passado para o método `$latte->render()` ou retornado pelo método [#getReferredName()]). + +Se o template não puder ser encontrado ou acedido, este método **deve lançar uma exceção `Latte\RuntimeException`**. + +```php +public function getContent(string $name): string +{ + // Exemplo: Carregando de um armazenamento interno hipotético + $content = $this->storage->read($name); + if ($content === null) { + throw new Latte\RuntimeException("Template '$name' não pode ser carregado."); + } + return $content; +} +``` + + +getReferredName(string $name, string $referringName): string .[method] +---------------------------------------------------------------------- +Este método lida com a resolução de nomes de template usados dentro de tags como `{include}`, `{layout}`, etc. Quando o Latte encontra, por exemplo, `{include 'partial.latte'}` dentro de `main.latte`, ele chama este método com `$name = 'partial.latte'` e `$referringName = 'main.latte'`. + +A tarefa do método é traduzir `$name` para um identificador canónico (por exemplo, caminho absoluto, chave de base de dados única) que será usado ao chamar outros métodos do loader, com base no contexto fornecido em `$referringName`. + +```php +public function getReferredName(string $name, string $referringName): string +{ + return ...; +} +``` + + +getUniqueId(string $name): string .[method] +------------------------------------------- +O Latte usa um cache de templates compilados para melhorar o desempenho. Cada arquivo de template compilado precisa de um nome único derivado do identificador do template de origem. Este método fornece uma string que **identifica exclusivamente** o template `$name`. + +Para templates baseados em arquivos, o caminho absoluto pode servir. Para templates numa base de dados, uma combinação de um prefixo e o ID da base de dados é comum. + +```php +public function getUniqueId(string $name): string +{ + return ...; +} +``` + + +Exemplo: Loader de Base de Dados Simples +---------------------------------------- + +Este exemplo mostra a estrutura básica de um loader que carrega templates armazenados numa tabela de base de dados chamada `templates` com colunas `name` (identificador único), `content` e `updated_at`. + +```php +use Latte; + +class DatabaseLoader implements Latte\Loader +{ + public function __construct( + private \PDO $db, + ) { + } + + public function getContent(string $name): string + { + $stmt = $this->db->prepare('SELECT content FROM templates WHERE name = ?'); + $stmt->execute([$name]); + $content = $stmt->fetchColumn(); + if ($content === false) { + throw new Latte\RuntimeException("Template '$name' não encontrado na base de dados."); + } + return $content; + } + + // Este exemplo simples assume que os nomes dos templates ('homepage', 'article', etc.) + // são IDs únicos e os templates não se referenciam relativamente. + public function getReferredName(string $name, string $referringName): string + { + return $name; + } + + public function getUniqueId(string $name): string + { + // Usar um prefixo e o próprio nome é único e suficiente aqui + return 'db_' . $name; + } +} + +// Uso: +$pdo = new \PDO(/* detalhes da conexão */); +$loader = new DatabaseLoader($pdo); +$latte->setLoader($loader); +$latte->render('homepage'); // Carrega o template com o nome 'homepage' da BD +``` + +Loaders personalizados dão-lhe controlo total sobre a origem dos seus templates Latte, permitindo a integração com vários sistemas de armazenamento e fluxos de trabalho. diff --git a/latte/pt/recipes.texy b/latte/pt/recipes.texy index fa24fa5399..8bfc95897f 100644 --- a/latte/pt/recipes.texy +++ b/latte/pt/recipes.texy @@ -2,44 +2,44 @@ Dicas e truques *************** -Editores e IDE .[#toc-editors-and-ide] -====================================== +Editores e IDEs +=============== -Escreva os modelos em um editor ou IDE que tenha suporte para Latte. Será muito mais agradável. +Escreva templates num editor ou IDE que tenha suporte para Latte. Será muito mais agradável. -- NetBeans IDE tem suporte integrado -- PhpStorm: instale o [plugin Latte |https://plugins.jetbrains.com/plugin/7457-latte] `Settings > Plugins > Marketplace` -- Código VS: busca no marcador de busca do plugin "Nette Latte + Neon -- Texto Sublime 3: em Package Control find and install `Nette` e selecione Latte in `View > Syntax` -- em editores antigos usam Smarty highlighting para arquivos .latte +- PhpStorm: instale em `Settings > Plugins > Marketplace` o [plugin Latte|https://plugins.jetbrains.com/plugin/7457-latte] +- VS Code: instale [Nette Latte + Neon|https://marketplace.visualstudio.com/items?itemName=Kasik96.latte], [Nette Latte templates|https://marketplace.visualstudio.com/items?itemName=smuuf.latte-lang] ou o mais recente [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode] plugin +- NetBeans IDE: o suporte nativo ao Latte faz parte da instalação +- Sublime Text 3: no Package Control, encontre e instale o pacote `Nette` e escolha Latte em `View > Syntax` +- em editores antigos, use o realce Smarty para arquivos .latte -O plugin para PhpStorm é muito avançado e pode perfeitamente sugerir código PHP. Para funcionar de forma ideal, use [modelos datilografados |type-system]. +O plugin para PhpStorm é muito avançado e pode sugerir código PHP excelentemente. Para que funcione otimamente, use [templates tipados|type-system]. [* latte-phpstorm-plugin.webp *] -O suporte para o Latte também pode ser encontrado no código de barras [Prism.js |https://prismjs.com/#supported-languages] e no editor [Ace |https://ace.c9.io]. +O suporte para Latte também pode ser encontrado no realçador de código web [Prism.js|https://prismjs.com/#supported-languages] e no editor [Ace|https://ace.c9.io]. -Latte Inside JavaScript ou CSS .[#toc-latte-inside-javascript-or-css] -===================================================================== +Latte dentro de JavaScript ou CSS +================================= -O Latte pode ser usado com muito conforto dentro do JavaScript ou CSS. Mas como evitar que o Latte seja equivocadamente considerado código JavaScript ou estilo CSS como uma tag Latte? +O Latte pode ser usado muito convenientemente dentro de JavaScript ou CSS. Mas como evitar a situação em que o Latte interpretaria erroneamente o código JavaScript ou o estilo CSS como uma tag Latte? ```latte ``` -**Opção 1*** +**Opção 1** -Evite situações em que uma carta siga imediatamente um `{`, seja inserindo um espaço, quebra de linha ou aspas entre eles: +Evite a situação em que uma letra segue imediatamente após `{`, por exemplo, inserindo um espaço, quebra de linha ou aspas antes dela: ```latte -

                                                                                                + +

                                                                                                ``` -Duas formas e dois tipos diferentes de fuga de dados. Dentro do ` ``` -Entretanto, se quisermos inseri-lo em um atributo HTML, ainda precisamos escapar de citações para as entidades HTML: +No entanto, se quiséssemos inseri-lo num atributo HTML, ainda precisaríamos de escapar as aspas para entidades HTML: ```html
                                                                                                ``` -Entretanto, o contexto aninhado não tem que ser apenas JS ou CSS. Também é comumente um URL. Parâmetros em URLs são escapados através da conversão de caracteres especiais em seqüências que começam com `%`. Exemplo: +Mas o contexto aninhado não precisa de ser apenas JS ou CSS. Comumente, também é uma URL. Parâmetros em URLs são escapados convertendo caracteres com significado especial em sequências que começam com `%`. Exemplo: ``` https://example.org/?a=Jazz&b=Rock%27n%27Roll ``` -E quando emitimos esta string em um atributo, ainda aplicamos a fuga de acordo com este contexto e substituímos `&` with `&`: +E quando exibimos esta string num atributo, ainda aplicamos o escaping de acordo com este contexto e substituímos `&` por `&`: ```html ``` -Se você já leu até aqui, parabéns, tem sido exaustivo. Agora você tem uma boa idéia do que são contextos e fugas. E você não precisa se preocupar com o fato de ser complicado. Latte faz isso para você automaticamente. +Se leu até aqui, parabéns, foi exaustivo. Agora tem uma boa ideia do que são contextos e escaping. E não precisa de se preocupar que seja complicado. O Latte faz isso automaticamente por si. -Sistemas Latte vs Naive .[#toc-latte-vs-naive-systems] -====================================================== +Latte vs sistemas ingénuos +========================== -Mostramos como escapar corretamente em um documento HTML e como é crucial conhecer o contexto, ou seja, onde você está produzindo os dados. Em outras palavras, como funciona a fuga sensível ao contexto. -Embora este seja um pré-requisito para a defesa funcional do XSS, **Latte é o único sistema de templates para PHP que faz isso.** +Mostramos como escapar corretamente num documento HTML e como é crucial o conhecimento do contexto, ou seja, o local onde exibimos os dados. Em outras palavras, como funciona o escaping sensível ao contexto. Embora seja um pré-requisito necessário para uma defesa funcional contra XSS, **o Latte é o único sistema de template para PHP que faz isso.** -Como isso é possível quando todos os sistemas de hoje afirmam ter fuga automática? -A fuga automática sem saber o contexto é um pouco de besteira que ** cria uma falsa sensação de segurança***. +Como isso é possível, quando todos os sistemas hoje afirmam ter escaping automático? O escaping automático sem conhecimento do contexto é um pouco bullshit, que **cria uma falsa sensação de segurança**. -Sistemas de gabarito como Twig, Laravel Blade e outros não vêem nenhuma estrutura HTML no gabarito. Portanto, eles também não vêem contextos. Em comparação ao Latte, eles são cegos e ingênuos. Eles só lidam com sua própria marcação, tudo o mais é um fluxo de caracteres irrelevante para eles: +Sistemas de template, como Twig, Laravel Blade e outros, não veem nenhuma estrutura HTML no template. Portanto, eles também não veem contextos. Em comparação com o Latte, eles são cegos e ingénuos. Eles processam apenas as suas próprias tags, todo o resto é para eles um fluxo de caracteres irrelevante:
                                                                                                -```twig .{file:Twig template as seen by Twig himself} -░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░ -░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░{{ text }}░░░░ +```twig .{file:Template Twig, como o próprio Twig o vê} +░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░ +░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░ ``` -```twig .{file:Twig template as the designer sees it} -- in text: {{ text }} -- in tag: -- in attribute: -- in unquoted attribute: -- in attribute containing URL: -- in attribute containing JavaScript: -- in attribute containing CSS: -- in JavaScriptu: -- in CSS: -- in comment: +```twig .{file:Template Twig, como o designer o vê} +- no texto: {{ foo }} +- na tag: +- no atributo: +- no atributo sem aspas: +- no atributo contendo URL: +- no atributo contendo JavaScript: +- no atributo contendo CSS: +- em JavaScript: +- em CSS: +- no comentário: ```
                                                                                                -Sistemas ingênuos apenas convertem mecanicamente `< > & ' "` caracteres em entidades HTML, o que é uma forma válida de escapar na maioria dos usos, mas longe de sempre. Assim, eles não podem detectar ou evitar várias falhas de segurança, como mostraremos a seguir. +Sistemas ingénuos apenas convertem mecanicamente os caracteres `< > & ' "` em entidades HTML, o que, embora seja um método de escaping válido na maioria dos casos de uso, está longe de ser sempre o caso. Eles não podem, portanto, detetar nem prevenir a criação de várias falhas de segurança, como mostraremos a seguir. -Latte vê o modelo da mesma forma que você vê. Ele entende HTML, XML, reconhece tags, atributos, etc. E por causa disso, ele distingue entre contextos e trata os dados de acordo. Portanto, ele oferece uma proteção realmente eficaz contra a vulnerabilidade crítica do Cross-site Scripting. +O Latte vê o template da mesma forma que você. Entende HTML, XML, reconhece tags, atributos, etc. E graças a isso, distingue contextos individuais e trata os dados de acordo com eles. Oferece, assim, uma proteção verdadeiramente eficaz contra a vulnerabilidade crítica Cross-site Scripting. + +
                                                                                                + +```latte .{file:Template Latte, como o Latte o vê} +░░░░░░░░░░░{$foo} +░░░░░░░░░░ +░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░ +░░░░░░░░░ +░░░░░░░░░░░░░░░ +``` + +```latte .{file:Template Latte, como o designer o vê} +- no texto: {$foo} +- na tag: +- no atributo: +- no atributo sem aspas: +- no atributo contendo URL: +- no atributo contendo JavaScript: +- no atributo contendo CSS: +- em JavaScript: +- em CSS: +- no comentário: +``` + +
                                                                                                -Demonstração ao vivo .[#toc-live-demonstration] -=============================================== +Demonstração ao vivo +==================== -À esquerda você pode ver o template em Latte, à direita está o código HTML gerado. A variável `$text` é gerada várias vezes, cada vez em um contexto ligeiramente diferente. E, portanto, escapou um pouco diferente. Você mesmo pode editar o código do template, por exemplo, alterar o conteúdo da variável, etc. Experimente-o: +À esquerda, vê o template em Latte, à direita está o código HTML gerado. A variável `$text` é exibida várias vezes aqui, e cada vez num contexto ligeiramente diferente. E, portanto, também escapada de forma ligeiramente diferente. Pode editar o código do template você mesmo, por exemplo, alterar o conteúdo da variável, etc. Experimente:
                                                                                                ``` .{file:template.latte; min-height: 14em}[fiddle-source] -{* TRY TO EDIT THIS TEMPLATE *} +{* TENTE EDITAR ESTE TEMPLATE *} {var $text = "Rock'n'Roll"} - {$text} - @@ -270,63 +286,61 @@ Demonstração ao vivo .[#toc-live-demonstration]
                                                                                                -Isso não é ótimo? Latte faz a fuga sensível ao contexto automaticamente, por isso o programador: +Não é ótimo! O Latte faz o escaping sensível ao contexto automaticamente, então o programador: -- não tem que pensar ou saber como escapar dos dados -- não pode estar errado -- não pode esquecê-lo +- não precisa de pensar nem saber como escapar em cada lugar +- não pode errar +- não pode esquecer de escapar -Estes não são nem mesmo todos os contextos que Latte distingue ao produzir e para os quais ele personaliza o tratamento de dados. Passaremos agora por casos mais interessantes. +Estes nem são todos os contextos que o Latte distingue ao exibir e para os quais adapta o tratamento dos dados. Vamos percorrer outros casos interessantes agora. -Como Hackear Sistemas Naive .[#toc-how-to-hack-naive-systems] -============================================================= +Como hackear sistemas ingénuos +============================== -Vamos usar alguns exemplos práticos para mostrar como é importante a diferenciação do contexto e por que os sistemas de modelos ingênuos não oferecem proteção suficiente contra o XSS, ao contrário do Latte. -Usaremos Twig como representante de um sistema ingênuo nos exemplos, mas o mesmo se aplica a outros sistemas. +Em vários exemplos práticos, mostraremos como a distinção de contextos é importante e porque sistemas de template ingénuos não fornecem proteção suficiente contra XSS, ao contrário do Latte. Como representante de um sistema ingénuo, usaremos o Twig nos exemplos, mas o mesmo se aplica a outros sistemas. -Atributo Vulnerabilidade .[#toc-attribute-vulnerability] --------------------------------------------------------- +Vulnerabilidade por atributo +---------------------------- -Vamos tentar injetar código malicioso na página usando o atributo HTML, como [mostramos acima |#How does the vulnerability arise]. Vamos ter um template no Twig exibindo uma imagem: +Tentaremos injetar código malicioso na página usando um atributo HTML, como [mostramos acima |#Como a vulnerabilidade surge]. Tenha um template em Twig renderizando uma imagem: ```twig .{file:Twig} {{ ``` -Observe que não há citações em torno dos valores dos atributos. O codificador pode tê-los esquecido, o que simplesmente acontece. Por exemplo, em React, o código é escrito assim, sem aspas, e um codificador que está trocando de idioma pode facilmente esquecer as aspas. +Observe que não há aspas em torno dos valores dos atributos. O codificador pode tê-las esquecido, o que simplesmente acontece. Por exemplo, em React, o código é escrito assim, sem aspas, e um codificador que alterna entre linguagens pode facilmente esquecer as aspas. -O atacante insere uma corda engenhosamente construída `foo onload=alert('Hacked!')` como legenda da imagem. Já sabemos que Twig não pode dizer se uma variável está sendo impressa em um fluxo de texto HTML, dentro de um atributo, dentro de um comentário HTML, etc.; em suma, não faz distinção entre contextos. E ele apenas converte mecanicamente `< > & ' "` caracteres em entidades HTML. -Portanto, o código resultante será parecido com este: +Um atacante insere como legenda da imagem uma string habilmente construída `foo onload=alert('Hacked!')`. Já sabemos que o Twig não pode saber se a variável está a ser exibida no fluxo de texto HTML, dentro de um atributo, comentário HTML, etc., em suma, não distingue contextos. E apenas converte mecanicamente os caracteres `< > & ' "` em entidades HTML. Então, o código resultante será assim: ```html foo ``` -** Foi criado um buraco na segurança!** +**E uma falha de segurança foi criada!** -Um falso atributo `onload` tornou-se parte da página e o navegador o executa imediatamente após o download da imagem. +O atributo forjado `onload` tornou-se parte da página e o navegador o executará imediatamente após o download da imagem. -Agora vamos ver como o Latte lida com o mesmo modelo: +Agora veremos como o Latte lida com o mesmo template: ```latte .{file:Latte} {$imageAlt} ``` -Latte vê o modelo da mesma forma que você vê. Ao contrário do Twig, ele entende HTML e sabe que uma variável é impressa como um valor de atributo que não está entre aspas. É por isso que ele as acrescenta. Quando um atacante insere a mesma legenda, o código resultante será parecido com este: +O Latte vê o template da mesma forma que você. Ao contrário do Twig, ele entende HTML e sabe que a variável está a ser exibida como o valor de um atributo que não está entre aspas. Portanto, ele adiciona-as. Quando o atacante insere a mesma legenda, o código resultante será assim: ```html foo onload=alert('Hacked!') ``` -**Latte impediu com sucesso o XSS.** +**O Latte preveniu com sucesso o XSS.** -Impressão de uma variável em JavaScript .[#toc-printing-a-variable-in-javascript] ---------------------------------------------------------------------------------- +Exibição de variável em JavaScript +---------------------------------- -Graças à fuga sensível ao contexto, é possível usar variáveis PHP nativamente dentro do JavaScript. +Graças ao escaping sensível ao contexto, é totalmente nativo usar variáveis PHP dentro de JavaScript. ```latte

                                                                                                {$movie}

                                                                                                @@ -334,7 +348,7 @@ Graças à fuga sensível ao contexto, é possível usar variáveis PHP nativame ``` -Se `$movie` variável armazena `'Amarcord & 8 1/2'` string, ela gera a seguinte saída. Observe diferentes fugas utilizadas em HTML e JavaScript e também no atributo `onclick`: +Se a variável `$movie` contiver a string `'Amarcord & 8 1/2'`, a seguinte saída será gerada. Observe que dentro do HTML é usado um escaping diferente do que dentro do JavaScript e ainda diferente no atributo `onclick`: ```latte

                                                                                                Amarcord & 8 1/2

                                                                                                @@ -343,29 +357,27 @@ Se `$movie` variável armazena `'Amarcord & 8 1/2'` string, ela gera a seguinte ``` -Verificação do link .[#toc-link-checking] ------------------------------------------ +Verificação de links +-------------------- -Latte verifica automaticamente se a variável utilizada nos atributos `src` ou `href` contém uma URL da web (ou seja, protocolo HTTP) e impede a escrita de links que possam representar um risco de segurança. +O Latte verifica automaticamente se a variável usada nos atributos `src` ou `href` contém uma URL da web (ou seja, protocolo HTTP) e evita a exibição de links que podem representar um risco de segurança. ```latte {var $link = 'javascript:attack()'} -click here +clique ``` -Escreve: +Exibe: ```latte -click here +clique ``` -A verificação pode ser desligada utilizando um [nocheck de |filters#nocheck] filtro. +A verificação pode ser desativada usando o filtro [nocheck |filters#nocheck]. -Limites do Latte .[#toc-limits-of-latte] -======================================== +Limites do Latte +================ -O Latte não é uma proteção XSS completa para toda a aplicação. Ficaríamos infelizes se você parasse para pensar em segurança ao usar o Latte. -O objetivo do Latte é garantir que um atacante não possa alterar a estrutura de uma página, adulterar elementos ou atributos HTML. Mas ele não verifica a exatidão do conteúdo dos dados que estão sendo emitidos. Ou a correção do comportamento do JavaScript. -Isso está além do escopo do sistema de templates. Verificar a exatidão dos dados, especialmente aqueles inseridos pelo usuário e portanto não confiáveis, é uma tarefa importante para o programador. +O Latte não é uma proteção totalmente completa contra XSS para toda a aplicação. Não gostaríamos que deixasse de pensar em segurança ao usar o Latte. O objetivo do Latte é garantir que um atacante não possa modificar a estrutura da página, forjar elementos ou atributos HTML. Mas ele não verifica a correção do conteúdo dos dados exibidos. Ou a correção do comportamento do JavaScript. Isso já está fora da competência do sistema de template. A verificação da correção dos dados, especialmente aqueles inseridos pelo usuário e, portanto, não confiáveis, é uma tarefa importante do programador. diff --git a/latte/pt/sandbox.texy b/latte/pt/sandbox.texy index bfe1ef7683..c5eaf7d0a8 100644 --- a/latte/pt/sandbox.texy +++ b/latte/pt/sandbox.texy @@ -1,12 +1,10 @@ Sandbox ******* -.[perex]{data-version:2.8} -Sandbox fornece uma camada de segurança que lhe dá controle sobre quais tags, funções PHP, métodos, etc. podem ser usados nos modelos. Graças ao modo sandbox, você pode colaborar com segurança com um cliente ou codificador externo na criação de modelos sem se preocupar em comprometer a aplicação ou operações indesejadas. +.[perex] +O Sandbox fornece uma camada de segurança que lhe dá controlo sobre quais tags, funções PHP, métodos, etc., podem ser usados nos templates. Graças ao modo sandbox, pode colaborar com segurança com o cliente ou codificador externo na criação de templates, sem ter que se preocupar com a violação da aplicação ou operações indesejadas. -Como funciona? Nós simplesmente definimos o que queremos permitir no modelo. No início, tudo é proibido e gradualmente concedemos permissões: - -O código a seguir permite que o modelo utilize as tags `{block}`, `{if}`, `{else}` e `{=}` (esta última é uma tag para [imprimir uma variável ou expressão |tags#Printing]) e todos os filtros: +Como funciona? Simplesmente definimos tudo o que permitiremos ao template. Por padrão, tudo é proibido e nós gradualmente permitimos. O código a seguir permite ao autor do template usar as tags `{block}`, `{if}`, `{else}` e `{=}`, que é a tag para [exibição de variável ou expressão |tags#Exibição], e todos os filtros: ```php $policy = new Latte\Sandbox\SecurityPolicy; @@ -16,7 +14,7 @@ $policy->allowFilters($policy::All); $latte->setPolicy($policy); ``` -Também podemos permitir o acesso a funções, métodos ou propriedades globais dos objetos: +Além disso, podemos permitir funções, métodos ou propriedades individuais de objetos: ```php $policy->allowFunctions(['trim', 'strlen']); @@ -24,30 +22,35 @@ $policy->allowMethods(Nette\Security\User::class, ['isLoggedIn', 'isAllowed']); $policy->allowProperties(Nette\Database\Row::class, $policy::All); ``` -Isso não é incrível? Você pode controlar tudo a um nível muito baixo. Se o modelo tentar chamar uma função não autorizada ou acessar um método ou propriedade não autorizada, ele lança a exceção `Latte\SecurityViolationException`. +Não é incrível? Pode controlar absolutamente tudo num nível muito baixo. Se o template tentar chamar uma função não permitida ou aceder a um método ou propriedade não permitido, ele terminará com uma exceção `Latte\SecurityViolationException`. -Criar políticas a partir do zero, quando tudo é proibido, pode não ser conveniente, para que você possa começar a partir de uma base segura: +Criar uma política do zero, onde absolutamente tudo é proibido, pode não ser conveniente, então pode começar de uma base segura: ```php $policy = Latte\Sandbox\SecurityPolicy::createSafePolicy(); ``` -Isto significa que todas as etiquetas padrão são permitidas exceto `contentType`, `debugbreak`, `dump`, `import`, `extends`, , `include`, `layout`, `php`, `sandbox`, `snippet`, , `snippetArea`, `templatePrint`, `varPrint`, `widget`. -Todos os filtros padrão também são permitidos, com exceção de `datastream`, `noescape` e `nocheck`. Finalmente, o acesso aos métodos e propriedades do objeto `$iterator` também é permitido. +A base segura significa que todas as tags padrão são permitidas, exceto `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget`. Os filtros padrão são permitidos, exceto `datastream`, `noescape` e `nocheck`. E, finalmente, o acesso aos métodos e propriedades do objeto `$iterator` é permitido. -As regras se aplicam ao modelo que inserimos com o novo [`{sandbox}` |tags#Including Templates] tag. O que é algo como `{include}`, mas liga o modo sandbox e também não passa nenhuma variável externa: +As regras são aplicadas ao template que inserimos com a tag [`{sandbox}` |tags#Inclusão de template]. Que é uma espécie de análogo ao `{include}`, mas que ativa o modo seguro e também não passa nenhuma variável: ```latte {sandbox 'untrusted.latte'} ``` -Assim, o layout e as páginas individuais podem usar todas as tags e variáveis como antes, as restrições serão aplicadas apenas ao modelo `untrusted.latte`. +Assim, o layout e as páginas individuais podem usar livremente todas as tags e variáveis, apenas o template `untrusted.latte` terá restrições aplicadas. -Algumas violações, tais como o uso de uma etiqueta ou filtro proibido, são detectadas em tempo de compilação. Outras, como a chamada de métodos não permitidos de um objeto, em tempo de execução. -O modelo também pode conter quaisquer outros bugs. A fim de evitar que uma exceção seja lançada a partir do template de caixa de areia, o que perturba toda a renderização, você pode definir seu [próprio manipulador de exceções |develop#exception handler], que, por exemplo, apenas o registra. +Algumas infrações, como o uso de uma tag ou filtro proibido, são detetadas em tempo de compilação. Outras, como chamar métodos de objeto não permitidos, apenas em tempo de execução. O template também pode conter quaisquer outros erros. Para evitar que uma exceção de um template em sandbox interrompa toda a renderização, pode definir um [manipulador de exceções personalizado |develop#Manipulador de exceção], que, por exemplo, a registará. -Se quisermos ligar o modo sandbox diretamente para todos os gabaritos, é fácil: +Se quiséssemos ativar o modo sandbox diretamente para todos os templates, é fácil: ```php $latte->setSandboxMode(); ``` + +Para ter a certeza de que o usuário não inserirá código PHP na página que seja sintaticamente correto, mas proibido e cause um PHP Compile Error, recomendamos deixar os [templates serem verificados pelo linter PHP |develop#Verificação do código gerado]. Ativa essa funcionalidade com o método `Engine::enablePhpLint()`. Como ele precisa de chamar o binário PHP para verificação, passe o caminho para ele como parâmetro: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); +``` diff --git a/latte/pt/syntax.texy b/latte/pt/syntax.texy index 8f8428fe63..ca0751c225 100644 --- a/latte/pt/syntax.texy +++ b/latte/pt/syntax.texy @@ -2,62 +2,60 @@ Sintaxe ******* .[perex] -A sintaxe do Latte nasceu das exigências práticas dos web designers. Estávamos procurando a sintaxe mais fácil de usar, com a qual você pode escrever com elegância construções que de outra forma seriam um verdadeiro desafio. -Ao mesmo tempo, todas as expressões são escritas exatamente da mesma forma que em PHP, para que você não tenha que aprender uma nova linguagem. Você apenas aproveita ao máximo o que já sabe. +A sintaxe do Latte surgiu das necessidades práticas dos web designers. Buscamos a sintaxe mais amigável, com a qual pode escrever elegantemente até mesmo construções que, de outra forma, representam um verdadeiro desafio. Ao mesmo tempo, todas as expressões são escritas exatamente como em PHP, então não precisa de aprender uma nova linguagem. Simplesmente aproveita o que já sabe há muito tempo. -Abaixo está um modelo mínimo que ilustra alguns elementos básicos: tags, n:atributos, comentários e filtros. +Abaixo está um template mínimo que ilustra vários elementos básicos: tags, n:atributos, comentários e filtros. ```latte -{* este é um comentário *} -
                                                                                                  {* n:se é n:atribut *} -{foreach $items as $item} {* etiqueta que representa o laço frontal *} -
                                                                                                • {$item|capitalize}
                                                                                                • {* tag que imprime uma variável com um filtro *} -{/foreach} {* final do ciclo *} +{* este é um comentário em Latte *} +
                                                                                                    {* n:if é um n:atributo *} +{foreach $items as $item} {* tag representando o ciclo foreach *} +
                                                                                                  • {$item|capitalize}
                                                                                                  • {* tag exibindo variável com filtro *} +{/foreach} {* fim do ciclo *}
                                                                                                  ``` -Vamos dar uma olhada mais de perto nestes elementos importantes e como eles podem ajudar você a construir um modelo incrível. +Vamos dar uma olhada mais de perto nesses elementos importantes e como eles podem ajudá-lo a criar um template incrível. -Etiquetas .[#toc-tags] -====================== +Tags +==== -Um modelo contém tags que controlam a lógica do modelo (por exemplo, *para cada* laço) ou expressões de saída. Para ambos, é usado um único delimitador `{ ... }`, de modo que não é preciso pensar em qual delimitador usar em qual situação, como acontece com outros sistemas. -Se o caractere `{` for seguido por uma citação ou espaço, Latte não o considera como sendo o início de uma tag, então você pode utilizar construções JavaScript, JSON, ou regras CSS em seus templates sem problemas. +O template contém tags que controlam a lógica do template (por exemplo, loops *foreach*) ou exibem expressões. Para ambos, é usado um único delimitador `{ ... }`, então não precisa de pensar qual delimitador usar em qual situação, como é o caso em outros sistemas. Se o caractere `{` for seguido por aspas ou espaço, o Latte não o considera o início de uma tag, graças ao qual pode usar construções JavaScript, JSON ou regras CSS nos seus templates sem problemas. -Veja [a visão geral de todas as etiquetas |tags]. Além disso, você também pode criar [etiquetas personalizadas |extending-latte#tags]. +Veja a [visão geral de todas as tags|tags]. Além disso, também pode criar as suas [tags personalizadas|custom tags]. -Latte Entende PHP .[#toc-latte-understands-php] -=============================================== +Latte entende PHP +================= -Você pode usar expressões PHP que você conhece bem dentro das tags: +Dentro das tags, pode usar expressões PHP que conhece bem: - variáveis -- cordas (incluindo HEREDOC e NOWDOC), matrizes, números, etc. +- strings (incluindo HEREDOC e NOWDOC), arrays, números, etc. - [operadores |https://www.php.net/manual/en/language.operators.php] -- função e chamadas de método (que podem ser restringidas pelo [sandbox |sandbox]) -- [jogo |https://www.php.net/manual/en/control-structures.match.php] -- [funções anônimas |https://www.php.net/manual/en/functions.arrow.php] -- [ligações de retorno |https://www.php.net/manual/en/functions.first_class_callable_syntax.php] -- comentários de várias linhas `/* ... */` -- etc.... +- chamadas de funções e métodos (que podem ser restringidas pelo [sandbox|sandbox]) +- [match |https://www.php.net/manual/en/control-structures.match.php] +- [funções anónimas |https://www.php.net/manual/en/functions.arrow.php] +- [callbacks |https://www.php.net/manual/en/functions.first_class_callable_syntax.php] +- comentários de múltiplas linhas `/* ... */` +- etc… -Além disso, Latte acrescenta várias [extensões agradáveis |#Syntactic Sugar] à sintaxe PHP. +Além disso, o Latte complementa a sintaxe do PHP com várias [extensões agradáveis |#Açúcar sintático]. -n:atributos .[#toc-n-attributes] -================================ +n:atributos +=========== -Cada tag de par, tal como `{if} … {/if}`, operando sobre um único elemento HTML pode ser escrita em notação de [atributo n:n |#n:attribute]. Por exemplo, `{foreach}` no exemplo acima também poderia ser escrito desta forma: +Todas as tags pares, como `{if} … {/if}`, operando sobre um único elemento HTML, podem ser reescritas na forma de n:atributos. Assim, seria possível escrever, por exemplo, o `{foreach}` no exemplo inicial: ```latte -
                                                                                                    +
                                                                                                    • {$item|capitalize}
                                                                                                    ``` -A funcionalidade corresponde então ao elemento HTML em que está escrito: +A funcionalidade então aplica-se ao elemento HTML em que está localizada: ```latte {var $items = ['I', '♥', 'Latte']} @@ -65,7 +63,7 @@ A funcionalidade corresponde então ao elemento HTML em que está escrito:

                                                                                                    {$item}

                                                                                                    ``` -Impressões: +exibe: ```latte

                                                                                                    I

                                                                                                    @@ -73,7 +71,7 @@ Impressões:

                                                                                                    Latte

                                                                                                    ``` -Usando o prefixo `inner-`, podemos alterar o comportamento para que a funcionalidade se aplique apenas ao corpo do elemento: +Usando o prefixo `inner-`, podemos modificar o comportamento para que ele se aplique apenas à parte interna do elemento: ```latte
                                                                                                    @@ -82,7 +80,7 @@ Usando o prefixo `inner-`, podemos alterar o comportamento para que a funcionali
                                                                                                    ``` -Impressões: +Será exibido: ```latte
                                                                                                    @@ -95,178 +93,184 @@ Impressões:
                                                                                                    ``` -Ou usando o prefixo `tag-`, a funcionalidade é aplicada apenas nas tags HTML: +Ou usando o prefixo `tag-`, aplicamos a funcionalidade apenas às próprias tags HTML: ```latte -

                                                                                                    Title

                                                                                                    +

                                                                                                    Title

                                                                                                    ``` -Dependendo do valor da variável `$url`, isto será impresso: +O que exibe dependendo da variável `$url`: ```latte -// when $url is empty +{* quando $url está vazio *}

                                                                                                    Title

                                                                                                    -// when $url equals 'https://nette.org' +{* quando $url contém 'https://nette.org' *}

                                                                                                    Title

                                                                                                    ``` -Entretanto, os n:atributos não são apenas um atalho para etiquetas de pares, existem também alguns n:atributos puros, por exemplo, o melhor amigo do codificador [n:classe |tags#n:class]. +No entanto, os n:atributos não são apenas um atalho para tags pares. Existem também n:atributos puros, como [n:href |application:creating-links#No template do presenter] ou o muito útil auxiliar do codificador [n:class |tags#n:class]. -Filtros .[#toc-filters] -======================= +Filtros +======= -Veja o resumo dos [filtros padrão |filters]. +Veja a visão geral dos [filtros padrão |filters]. -O Latte permite a chamada de filtros utilizando a notação do sinal de canalização (o espaço precedente é permitido): +Os filtros são escritos após uma barra vertical (pode haver um espaço antes dela): ```latte

                                                                                                    {$heading|upper}

                                                                                                    ``` -Os filtros podem ser acorrentados, nesse caso, eles se aplicam por ordem da esquerda para a direita: +Os filtros podem ser encadeados e são aplicados na ordem da esquerda para a direita: ```latte

                                                                                                    {$heading|lower|capitalize}

                                                                                                    ``` -Os parâmetros são colocados após o nome do filtro separado por dois pontos ou vírgula: +Os parâmetros são inseridos após o nome do filtro, separados por dois pontos ou vírgulas: ```latte

                                                                                                    {$heading|truncate:20,''}

                                                                                                    ``` -Os filtros podem ser aplicados por expressão: +Os filtros também podem ser aplicados a uma expressão: ```latte {var $name = ($title|upper) . ($subtitle|lower)} ``` -Em bloco: +Num bloco: ```latte

                                                                                                    {block |lower}{$heading}{/block}

                                                                                                    ``` -Ou diretamente sobre o valor (em combinação com [`{=expr}` | https://latte.nette.org/pt/tags#printing] tag): +Ou diretamente no valor (em combinação com a tag [`{=expr}` |tags#Exibição]): ```latte

                                                                                                    {=' Hello world '|trim}

                                                                                                    ``` -Comentários .[#toc-comments] -============================ +Tags HTML dinâmicas .{data-version:3.0.9} +========================================= -Os comentários são escritos desta forma e não entram na produção: +O Latte suporta tags HTML dinâmicas, que são úteis quando precisa de flexibilidade nos nomes das tags: + +```latte +Heading +``` + +O código acima pode, por exemplo, gerar `

                                                                                                    Heading

                                                                                                    ` ou `

                                                                                                    Heading

                                                                                                    ` dependendo do valor da variável `$level`. As tags HTML dinâmicas no Latte devem ser sempre pares. A sua alternativa é [n:tag |tags#n:tag]. + +Como o Latte é um sistema de template seguro, ele verifica se o nome da tag resultante é válido e não contém valores indesejados ou maliciosos. Além disso, garante que o nome da tag de fechamento seja sempre o mesmo que o nome da tag de abertura. + + +Comentários +=========== + +Os comentários são escritos desta forma e não aparecem na saída: ```latte {* este é um comentário em Latte *} ``` -Os comentários PHP funcionam dentro das tags: +Dentro das tags, os comentários PHP funcionam: ```latte {include 'file.info', /* value: 123 */} ``` -Açúcar Syntactic .[#toc-syntactic-sugar] -======================================== +Açúcar sintático +================ -Cordas sem marcas de citação .[#toc-strings-without-quotation-marks] --------------------------------------------------------------------- +Strings sem aspas +----------------- -As aspas podem ser omitidas para cordas simples: +Para strings simples, as aspas podem ser omitidas: ```latte -as in PHP: {var $arr = ['hello', 'btn--default', '€']} +como em PHP: {var $arr = ['hello', 'btn--default', '€']} -abbreviated: {var $arr = [hello, btn--default, €]} +abreviado: {var $arr = [hello, btn--default, €]} ``` -Cordas simples são aquelas que são compostas puramente de letras, dígitos, sublinhados, hífens e pontos. Elas não devem começar com um dígito e não devem começar ou terminar com um hífen. -Não devem ser compostas apenas de letras maiúsculas e sublinhados, pois então são consideradas uma constante (por exemplo, `PHP_VERSION`). -E não deve colidir com as palavras-chave `and`, `array`, `clone`, `false`, `default`, , `in`, `instanceof`, `new`, `null`, `or`, , `return`, `true`, `xor`. +Strings simples são aquelas compostas puramente por letras, dígitos, sublinhados, hífens e pontos. Não devem começar com um dígito e não devem começar ou terminar com um hífen. Não devem ser compostas apenas por letras maiúsculas e sublinhados, porque então são consideradas constantes (por exemplo, `PHP_VERSION`). E não devem colidir com palavras-chave: `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor`. -Operador de Ternary curto .[#toc-short-ternary-operator] --------------------------------------------------------- +Constantes +---------- -Se o terceiro valor do operador ternário estiver vazio, ele pode ser omitido: +Como é possível omitir aspas em strings simples, recomendamos escrever constantes globais com uma barra no início para diferenciação: ```latte -as in PHP: {$stock ? 'In stock' : ''} - -abbreviated: {$stock ? 'In stock'} +{if \PROJECT_ID === 1} ... {/if} ``` +Esta notação é totalmente válida no próprio PHP, a barra diz que a constante está no namespace global. + -Notação Chave Moderna no Array .[#toc-modern-key-notation-in-the-array] ------------------------------------------------------------------------ +Operador ternário abreviado +--------------------------- -As chaves de matriz podem ser escritas de forma semelhante aos parâmetros nomeados ao chamar funções: +Se o terceiro valor do operador ternário estiver vazio, ele pode ser omitido: ```latte -as in PHP: {var $arr = ['one' => 'item 1', 'two' => 'item 2']} +como em PHP: {$stock ? 'Em estoque' : ''} -modern: {var $arr = [one: 'item 1', two: 'item 2']} +abreviado: {$stock ? 'Em estoque'} ``` -Filtros .[#toc-filters] ------------------------ +Notação moderna de chaves em arrays +----------------------------------- -Os filtros podem ser usados para qualquer expressão, basta anexar o todo entre parênteses: +As chaves em arrays podem ser escritas de forma semelhante aos parâmetros nomeados ao chamar funções: ```latte -{var $content = ($text|truncate: 30|upper)} +como em PHP: {var $arr = ['one' => 'item 1', 'two' => 'item 2']} + +moderno: {var $arr = [one: 'item 1', two: 'item 2']} ``` -Operador `in` .[#toc-operator-in] ---------------------------------- +Filtros +------- -O operador `in` pode ser usado para substituir a função `in_array()`. A comparação é sempre rigorosa: +Os filtros podem ser usados para quaisquer expressões, basta envolver o todo em parênteses: ```latte -{* como in_array($item, $items, true) *} -{if $item in $items} - ... -{/if} +{var $content = ($text|truncate: 30|upper)} ``` -.{data-version:2.9} -Encadeamento Opcional com Operador de Segurança Indefinida .[#toc-optional-chaining-with-undefined-safe-operator] ------------------------------------------------------------------------------------------------------------------ +Operador `in` +------------- -O operador indefinido e seguro `??->` é semelhante ao operador nulo e seguro `?->`, mas não levanta um erro se uma variável, propriedade ou índice não existir de forma alguma. +O operador `in` pode substituir a função `in_array()`. A comparação é sempre estrita: ```latte -{$order??->id} +{* análogo a in_array($item, $items, true) *} +{if $item in $items} + ... +{/if} ``` -esta é uma forma de dizer que quando `$order` for definido e não nulo, `$order->id` será computado, mas quando `$order` for nulo ou não existir, pare o que estamos fazendo e simplesmente retorne nulo. - -```latte -{$user??->address??->street} -// roughly means isset($user) && isset($user->address) ? $user->address->street : null -``` +Janela histórica +---------------- -Uma Janela para a História .[#toc-a-window-into-history] --------------------------------------------------------- +O Latte introduziu ao longo da sua história uma série de açúcares sintáticos que, após alguns anos, apareceram no próprio PHP. Por exemplo, no Latte era possível escrever arrays como `[1, 2, 3]` em vez de `array(1, 2, 3)` ou usar o operador nullsafe `$obj?->foo` muito antes de ser possível no próprio PHP. O Latte também introduziu o operador para desempacotar array `(expand) $arr`, que é o equivalente ao operador atual `...$arr` do PHP. -O Latte criou uma série de doces sintáticos ao longo de sua história, que apareceram no próprio PHP alguns anos depois. Por exemplo, em Latte era possível escrever arrays como `[1, 2, 3]` em vez de `array(1, 2, 3)` ou usar o operador nullsafe `$obj?->foo` muito antes de ser possível no próprio PHP. Latte também introduziu o operador de expansão de array `(expand) $arr`, que é o equivalente ao atual operador `...$arr` do PHP. +O operador undefined-safe `??->`, que é análogo ao operador nullsafe `?->`, mas não gera erro se a variável não existir, surgiu por razões históricas e hoje recomendamos usar o operador PHP padrão `?->`. -Limitações do PHP em Latte .[#toc-php-limitations-in-latte] -=========================================================== +Limitações do PHP no Latte +========================== -Somente expressões PHP podem ser escritas em Latte. Ou seja, você não pode declarar classes ou usar [estruturas de controle |https://www.php.net/manual/en/language.control-structures.php], tais como `if`, `foreach`, `switch`, `return`, `try`, `throw` e outras, em vez das quais Latte oferece suas [tags |tags]. -Você também não pode usar [atributos |https://www.php.net/manual/en/language.attributes.php], [backticks |https://www.php.net/manual/en/language.operators.execution.php] ou [constantes mágicas |https://www.php.net/manual/en/language.constants.magic.php], porque isso não faria sentido. -Você não pode sequer usar `echo`, `include`, `require`, `eval`, `exit`, , `unset`, porque não são funções, mas construções especiais da linguagem PHP, e, portanto, não expressões. +No Latte, apenas expressões PHP podem ser escritas. Ou seja, não é possível usar instruções terminadas em ponto e vírgula. Não é possível declarar classes ou usar [estruturas de controlo |https://www.php.net/manual/en/language.control-structures.php], por exemplo, `if`, `foreach`, `switch`, `return`, `try`, `throw` e outras, para as quais o Latte oferece as suas [tags|tags]. Também não é possível usar [atributos |https://www.php.net/manual/en/language.attributes.php], [backticks |https://www.php.net/manual/en/language.operators.execution.php] ou algumas [constantes mágicas |https://www.php.net/manual/en/language.constants.magic.php]. Também não é possível usar `unset`, `echo`, `include`, `require`, `exit`, `eval`, porque não são funções, mas construções especiais da linguagem PHP, e não são, portanto, expressões. Os comentários são suportados apenas em múltiplas linhas `/* ... */`. -Entretanto, você pode contornar essas limitações ativando a extensão [RawPhpExtension |develop#RawPhpExtension], que permite utilizar qualquer código PHP na tag `{php ...}` sob a responsabilidade do autor do modelo. +No entanto, essas limitações podem ser contornadas ativando a extensão [RawPhpExtension |develop#RawPhpExtension], graças à qual é possível usar qualquer código PHP na tag `{php ...}` sob a responsabilidade do autor do template. diff --git a/latte/pt/tags.texy b/latte/pt/tags.texy index b2dbfda1f1..5816091e1f 100644 --- a/latte/pt/tags.texy +++ b/latte/pt/tags.texy @@ -1,168 +1,174 @@ -Latte Tags +Tags Latte ********** .[perex] -Resumo e descrição de todas as etiquetas incorporadas no Latte. +Visão geral e descrição de todas as tags do sistema de templates Latte que estão disponíveis por padrão. .[table-latte-tags language-latte] -|### Impressão -| `{$var}`, `{...}` ou `{=...}` | [imprime uma variável ou expressão fugida |#printing] -| `{$var\|filter}` | [estampas com filtros |#filters] -| `{l}` ou `{r}` | imprime o caracter `{` or `}` +|## Exibição +| `{$var}`, `{...}` ou `{=...}` | [exibe variável ou expressão escapada |#Exibição] +| `{$var\|filter}` | [exibe usando filtros |#Filtros] +| `{l}` ou `{r}` | exibe o caractere `{` ou `}` .[table-latte-tags language-latte] -|### Condições -| `{if}`... `{elseif}`... `{else}`... `{/if}` | [condição se |#if-elseif-else] -| `{ifset}`... `{elseifset}`... `{/ifset}`... | [condição se começar |#ifset-elseifset] -| `{ifchanged}`... `{/ifchanged}` | [testar se houve uma mudança |#ifchanged] -| `{switch}` `{case}` `{default}` `{/switch}` | [interruptor de condição |#switch-case-default] +|## Condições +| `{if}` … `{elseif}` … `{else}` … `{/if}` | [condição if |#if elseif else] +| `{ifset}` … `{elseifset}` … `{/ifset}` | [condição ifset |#ifset elseifset] +| `{ifchanged}` … `{/ifchanged}` | [testa se houve mudança |#ifchanged] +| `{switch}` `{case}` `{default}` `{/switch}` | [condição switch |#switch case default] +| `n:else` | [conteúdo alternativo para condições |#n:else] .[table-latte-tags language-latte] |## Loops -| `{foreach}`... `{/foreach}` | [foreach |#foreach] -| `{for}`... `{/for}` | [para |#for] -| `{while}`... `{/while}` | [enquanto |#while] -| `{continueIf $cond}` | [continuar para a próxima iteração |#continueif-skipif-breakif] -| `{skipIf $cond}` | [pular a atual iteração do loop |#continueif-skipif-breakif] -| `{breakIf $cond}` | [loop de quebra |#continueif-skipif-breakif] -| `{exitIf $cond}` | [saída antecipada |#exitif] -| `{first}`... `{/first}` | [é a primeira iteração? |#first-last-sep] -| `{last}`... `{/last}` | [é a última iteração? |#first-last-sep] -| `{sep}`... `{/sep}`... | [será a próxima iteração? |#first-last-sep] -| `{iterateWhile}`... `{/iterateWhile}`... ... -| `$iterator` | [variável especial dentro do laço frontal |#$iterator] +| `{foreach}` … `{/foreach}` | [#foreach] +| `{for}` … `{/for}` | [#for] +| `{while}` … `{/while}` | [#while] +| `{continueIf $cond}` | [continuar para a próxima iteração |#continueIf skipIf breakIf] +| `{skipIf $cond}` | [pular iteração |#continueIf skipIf breakIf] +| `{breakIf $cond}` | [interrupção do loop |#continueIf skipIf breakIf] +| `{exitIf $cond}` | [término antecipado |#exitIf] +| `{first}` … `{/first}` | [é a primeira passagem? |#first last sep] +| `{last}` … `{/last}` | [é a última passagem? |#first last sep] +| `{sep}` … `{/sep}` | [haverá outra passagem? |#first last sep] +| `{iterateWhile}` … `{/iterateWhile}` | [foreach estruturado |#iterateWhile] +| `$iterator` | [variável especial dentro do foreach |#iterator] .[table-latte-tags language-latte] -|### Incluindo outros Modelos -| `{include 'file.latte'}` | [inclui um modelo de outro arquivo |#include] -| `{sandbox 'file.latte'}` | [inclui um modelo em modo sandbox |#sandbox] +|## Inclusão de outros templates +| `{include 'file.latte'}` | [carrega o template de outro arquivo |#include] +| `{sandbox 'file.latte'}` | [carrega o template em modo sandbox |#sandbox] .[table-latte-tags language-latte] -|## Blocos, layouts, modelo de herança -| `{block}` | [bloco anônimo |#block] -| `{block blockname}` | [definição do bloco |template-inheritance#blocks] -| `{define blockname}` | [definição do bloco para uso futuro |template-inheritance#definitions] -| `{include blockname}` | [bloco de impressões |template-inheritance#printing-blocks] -| `{include blockname from 'file.latte'}` | [imprime um bloco do arquivo |template-inheritance#printing-blocks] -| `{import 'file.latte'}` | [carrega blocos a partir de outro modelo |template-inheritance#horizontal-reuse] -| `{layout 'file.latte'}` / `{extends}` | [especifica um arquivo de layout |template-inheritance#layout-inheritance] -| `{embed}`... `{/embed}` | [carrega o modelo ou bloco e permite que você sobrescreva os blocos |template-inheritance#unit-inheritance] -| `{ifset blockname}`... `{/ifset}` | [condição se o bloco estiver definido |template-inheritance#checking-block-existence] +|## Blocos, layouts, herança de templates +| `{block}` | [bloco anônimo |#block] +| `{block blockname}` | [define um bloco |template-inheritance#Blocos] +| `{define blockname}` | [define um bloco para uso posterior |template-inheritance#Definições] +| `{include blockname}` | [renderização de bloco |template-inheritance#Renderização de Blocos] +| `{include blockname from 'file.latte'}` | [renderiza um bloco de um arquivo |template-inheritance#Renderização de Blocos] +| `{import 'file.latte'}` | [carrega blocos do template |template-inheritance#Reutilização Horizontal] +| `{layout 'file.latte'}` / `{extends}` | [especifica o arquivo de layout |template-inheritance#Herança de Layout] +| `{embed}` … `{/embed}` | [carrega um template ou bloco e permite sobrescrever blocos |template-inheritance#Herança de Unidade] +| `{ifset blockname}` … `{/ifset}` | [condição se um bloco existe |template-inheritance#Verificando a Existência de Blocos] .[table-latte-tags language-latte] -|## Manuseio de exceções -| `{try}`... `{else}`... `{/try}`... | [exceções de captura |#try] -| `{rollback}` | [descartes tentar bloquear |#rollback] +|## Controle de exceções +| `{try}` … `{else}` … `{/try}` | [captura de exceções |#try] +| `{rollback}` | [descarte do bloco try |#rollback] .[table-latte-tags language-latte] -|### Variáveis -| `{var $foo = value}` | [criação de variáveis |#var-default] -| `{default $foo = value}` | [valor padrão quando a variável não é declarada |#var-default] -| `{parameters}` | [declara variáveis, digita valores padrão |#parameters] -| `{capture}`... `{/capture}` | [captura uma seção para uma variável |#capture] +|## Variáveis +| `{var $foo = value}` | [cria uma variável |#var default] +| `{default $foo = value}` | [cria uma variável se ela não existir |#var default] +| `{parameters}` | [declara variáveis, tipos e valores padrão |#parameters] +| `{capture}` … `{/capture}` | [captura um bloco para uma variável |#capture] .[table-latte-tags language-latte] -|### Tipos -| `{varType}` | [declara o tipo de variável |type-system#varType] -| `{varPrint}` | [sugere tipos de variáveis |type-system#varPrint] -| `{templateType}` | [declara tipos de variáveis usando classe |type-system#templateType] -| `{templatePrint}` | [gera classe com propriedades |type-system#templatePrint] +|## Tipos +| `{varType}` | [declara o tipo da variável |type-system#varType] +| `{varPrint}` | [sugere tipos de variáveis |type-system#varPrint] +| `{templateType}` | [declara tipos de variáveis de acordo com a classe |type-system#templateType] +| `{templatePrint}` | [sugere uma classe com tipos de variáveis |type-system#templatePrint] .[table-latte-tags language-latte] -|### Tradução -| `{_string}` | [impressões traduzidas |#Translation] -| `{translate}`... `{/translate}` | [traduz o conteúdo |#Translation] +|## Traduções +| `{_...}` | [exibe a tradução |#Traduções] +| `{translate}` … `{/translate}` | [traduz o conteúdo |#Traduções] .[table-latte-tags language-latte] |## Outros -| `{contentType}` | [muda o modo de fuga e envia o cabeçalho HTTP |#contenttype] -| `{debugbreak}` | [define o ponto de parada para o código |#debugbreak] -| `{do}` | [avalia uma expressão sem imprimi-la |#do] -| `{dump}` | [dump variables to the Tracy Bar |#dump] -| `{spaceless}`... `{/spaceless}` | [remove espaços em branco desnecessários |#spaceless] -| `{syntax}` | [muda a sintaxe em tempo de execução |#syntax] -| `{trace}` | [mostra rastro de pilha |#trace] +| `{contentType}` | [alterna o escaping e envia o cabeçalho HTTP |#contentType] +| `{debugbreak}` | [coloca um breakpoint no código |#debugbreak] +| `{do}` | [executa o código, mas não exibe nada |#do] +| `{dump}` | [faz dump de variáveis para a Tracy Bar |#dump] +| `{php}` | [executa qualquer código PHP |#php] +| `{spaceless}` … `{/spaceless}` | [remove espaços em branco desnecessários |#spaceless] +| `{syntax}` | [mudança de sintaxe em tempo de execução |#syntax] +| `{trace}` | [exibe o stack trace |#trace] .[table-latte-tags language-latte] -|## Ajudantes de tag HTML -| `n:class` | [atributo classe inteligente |#n:class] -| `n:attr` | [atributos HTML inteligentes |#n:attr] -| `n:tag` | [nome dinâmico do elemento HTML |#n:tag] -| `n:ifcontent` | [Omitir tag HTML vazio |#n:ifcontent] +|## Auxiliares do codificador HTML +| `n:class` | [escrita dinâmica do atributo HTML class |#n:class] +| `n:attr` | [escrita dinâmica de quaisquer atributos HTML |#n:attr] +| `n:tag` | [escrita dinâmica do nome do elemento HTML |#n:tag] +| `n:ifcontent` | [omite a tag HTML vazia |#n:ifcontent] .[table-latte-tags language-latte] -|## Disponível apenas em Nette Framework -| `n:href` | [link em `` elementos HTML |application:creating-links#In the Presenter Template] -| `{link}` | [imprime um link |application:creating-links#In the Presenter Template] -| `{plink}` | [imprime um link para um apresentador |application:creating-links#In the Presenter Template] -| `{control}` | [imprime um componente |application:components#Rendering] -| `{snippet}`... `{/snippet}` | [um trecho de modelo que pode ser enviado pela AJAX |application:ajax#tag-snippet] -| `{snippetArea}` | envelope de snippets -| `{cache}`... `{/cache}` | [caches uma seção modelo |caching:#caching-in-latte] +|## Disponível apenas no Nette Framework +| `n:href` | [link usado em elementos HTML `` |application:creating-links#No template do presenter] +| `{link}` | [exibe um link |application:creating-links#No template do presenter] +| `{plink}` | [exibe um link para um presenter |application:creating-links#No template do presenter] +| `{control}` | [renderiza um componente |application:components#Renderização] +| `{snippet}` … `{/snippet}` | [snippet que pode ser enviado via AJAX |application:ajax#Snippets em Latte] +| `{snippetArea}` | [invólucro para snippets |application:ajax#Áreas de Snippets] +| `{cache}` … `{/cache}` | [faz cache de parte do template |caching:#Armazenamento em cache no Latte] .[table-latte-tags language-latte] -|## Disponível apenas com Formulários Nette -| `{form}`... `{/form}` | [imprime um elemento do formulário |forms:rendering#form] -| `{label}`... `{/label}` | [imprime uma etiqueta de entrada de formulário |forms:rendering#label-input] -| `{input}` | [imprime um elemento de entrada do formulário |forms:rendering#label-input] -| `{inputError}` | [imprime mensagem de erro para o elemento de entrada do formulário |forms:rendering#inputError] -| `n:name` | [ativa um elemento de entrada HTML |forms:rendering#n:name] -| `{formPrint}` | [gera o projeto do formulário Latte |forms:rendering#formPrint] -| `{formPrintClass}` | [imprime a classe PHP para dados de formulário |forms:in-presenter#mapping-to-classes] -| `{formContext}`... `{/formContext}` | [versão parcial da forma |forms:rendering#special-cases] +|## Disponível apenas com Nette Forms +| `{form}` … `{/form}` | [renderiza tags de formulário |forms:rendering#form] +| `{label}` … `{/label}` | [renderiza o rótulo do controle de formulário |forms:rendering#label input] +| `{input}` | [renderiza o controle de formulário |forms:rendering#label input] +| `{inputError}` | [exibe a mensagem de erro do controle de formulário |forms:rendering#inputError] +| `n:name` | [ativa o controle de formulário |forms:rendering#n:name] +| `{formContainer}` … `{/formContainer}` | [renderização do contêiner de formulário |forms:rendering#Casos especiais] + +.[table-latte-tags language-latte] +|## Disponível somente com o Nette Assets +| `{asset}` | [renderiza um ativo como elemento HTML ou URL |assets:#asset] +| `{preload}` | [gera dicas de pré-carregamento para otimização do desempenho |assets:#preload] +| `n:asset` | [adiciona atributos de ativos a elementos HTML |assets:#n:asset] -Impressão .[#toc-printing] -========================== +Exibição +======== `{$var}` `{...}` `{=...}` ------------------------- -Latte usa a tag `{=...}` para imprimir qualquer expressão para a saída. Se a expressão começa com uma variável ou chamada de função, não há necessidade de escrever um sinal de igual. O que, na prática, significa que quase nunca precisa ser escrito: +No Latte, a tag `{=...}` é usada para exibir qualquer expressão na saída. O Latte se preocupa com sua conveniência, então se a expressão começa com uma variável ou chamada de função, não é necessário escrever o sinal de igual. O que na prática significa que quase nunca é necessário escrevê-lo: ```latte -Name: {$name} {$surname}
                                                                                                    -Age: {date('Y') - $birth}
                                                                                                    +Nome: {$name} {$surname}
                                                                                                    +Idade: {date('Y') - $birth}
                                                                                                    ``` -Você pode escrever qualquer coisa que você conhece do PHP como uma expressão. Você simplesmente não precisa aprender uma nova linguagem. Por exemplo, não é preciso aprender uma nova linguagem: +Como expressão, você pode escrever qualquer coisa que conheça do PHP. Você simplesmente não precisa aprender uma nova linguagem. Por exemplo: ```latte {='0' . ($num ?? $num * 3) . ', ' . PHP_VERSION} ``` -Por favor, não procure nenhum significado no exemplo anterior, mas se você encontrar um lá, escreva-nos :-) +Por favor, não procure nenhum sentido no exemplo anterior, mas se encontrar algum, escreva para nós :-) -Escapando da saída .[#toc-escaping-output] ------------------------------------------- +Escaping da saída +----------------- -Qual é a tarefa mais importante de um sistema modelo? Evitar buracos na segurança. E é exatamente isso que Latte faz sempre que você imprime algo para produzir. Ele escapa automaticamente de tudo: +Qual é a tarefa mais importante de um sistema de template? Prevenir falhas de segurança. E é exatamente isso que o Latte faz sempre que você exibe algo. Ele escapa automaticamente: ```latte -

                                                                                                    {='one < two'}

                                                                                                    {* prints: '

                                                                                                    one < two

                                                                                                    ' *} +

                                                                                                    {='one < two'}

                                                                                                    {* exibe: '

                                                                                                    one < two

                                                                                                    ' *} ``` -Para ser mais preciso, Latte utiliza o escape sensível ao contexto, que é uma característica tão importante e única que [lhe |safety-first#context-aware-escaping] dedicamos [um capítulo à parte |safety-first#context-aware-escaping]. +Para ser preciso, o Latte usa escaping sensível ao contexto, que é algo tão importante e único que dedicamos um [capítulo separado |safety-first#Escaping sensível ao contexto] a ele. -E se você imprimir conteúdo codificado em HTML a partir de uma fonte confiável? Então, você pode facilmente desativar a fuga: +E se você estiver exibindo conteúdo codificado em HTML de uma fonte confiável? Então, você pode facilmente desativar o escaping: ```latte {$trustedHtmlString|noescape} ``` .[warning] -O mau uso do filtro `noescape` pode levar a uma vulnerabilidade XSS! Nunca o use a menos que você esteja **absolutamente seguro** do que está fazendo e que o fio que você está imprimindo vem de uma fonte confiável. +O uso incorreto do filtro `noescape` pode levar à vulnerabilidade XSS! Nunca o use a menos que tenha **certeza absoluta** do que está fazendo e que a string exibida vem de uma fonte confiável. -Impressão em JavaScript .[#toc-printing-in-javascript] ------------------------------------------------------- +Exibição em JavaScript +---------------------- -Graças à fuga sensível ao contexto, é maravilhosamente fácil imprimir variáveis dentro do JavaScript, e o Latte escapará delas adequadamente. +Graças ao escaping sensível ao contexto, é maravilhosamente fácil exibir variáveis dentro de JavaScript e o Latte cuida do escaping correto. -A variável não tem que ser uma string, qualquer tipo de dado é suportado, que é então codificado como JSON: +A variável não precisa ser apenas uma string, qualquer tipo de dados é suportado, que é então codificado como JSON: ```latte {var $foo = ['hello', true, 1]} @@ -179,7 +185,7 @@ Gera: ``` -Esta é também a razão pela qual **não colocar variável entre aspas***: Latte as adiciona em torno de cordas. E se você quiser colocar uma variável de string em outra string, basta concatená-las: +Essa também é a razão pela qual **não se escrevem aspas** em torno da variável: o Latte as adiciona automaticamente para strings. E se você quiser inserir uma variável de string em outra string, simplesmente concatene-as: ```latte ``` -Filtros .[#toc-filters] ------------------------ +Filtros +------- -A expressão impressa pode ser modificada [por filtros |syntax#filters]. Por exemplo, este exemplo converte a corda em maiúsculas e a encurta para um máximo de 30 caracteres: +A expressão exibida pode ser modificada por um [filtro |syntax#Filtros]. Assim, por exemplo, convertemos uma string para maiúsculas e a encurtamos para um máximo de 30 caracteres: ```latte {$string|upper|truncate:30} ``` -Você também pode aplicar filtros a partes de uma expressão como a seguir: +Você também pode aplicar filtros a partes parciais da expressão desta forma: ```latte {$left . ($middle|upper) . $right} ``` -Condições .[#toc-conditions] -============================ +Condições +========= `{if}` `{elseif}` `{else}` -------------------------- -As condições se comportam da mesma forma que suas contrapartes em PHP. Você pode usar as mesmas expressões que você conhece do PHP, você não precisa aprender uma nova linguagem. +As condições se comportam da mesma forma que suas contrapartes em PHP. Você pode usar as mesmas expressões nelas que conhece do PHP, não precisa aprender uma nova linguagem. ```latte {if $product->inStock > Stock::Minimum} - In stock + Em estoque {elseif $product->isOnWay()} - On the way + A caminho {else} - Not available + Não disponível {/if} ``` -Como qualquer tag de par, um par de `{if} ... {/ if}` pode ser escrito como [n:atributo |syntax#n:attributes], por exemplo: +Como toda tag par, o par `{if} ... {/if}` também pode ser escrito na forma de um [n:atributo |syntax#n:atributos], por exemplo: ```latte -

                                                                                                    In stock {$count} items

                                                                                                    +

                                                                                                    Em estoque {$count} peças

                                                                                                    +``` + +Você sabia que pode adicionar o prefixo `tag-` aos n:atributos? Então a condição se aplicará apenas à exibição das tags HTML e o conteúdo entre elas será sempre exibido: + +```latte +
                                                                                                    Hello + +{* exibe 'Hello' quando $clickable é falso *} +{* exibe 'Hello' quando $clickable é verdadeiro *} ``` -Você sabe que pode adicionar o prefixo `tag-` aos n:attributes? Então a condição afetará apenas as tags HTML e o conteúdo entre elas será sempre impresso: +Incrível. + + +`n:else` .{data-version:3.0.11} +------------------------------- + +Se você escrever a condição `{if} ... {/if}` na forma de um [n:atributo |syntax#n:atributos], você tem a opção de especificar também um ramo alternativo usando `n:else`: ```latte -Olá +Em estoque {$count} peças -{* prints 'Hello' when $clickable is falsey *} -{* prints 'Hello' when $clickable is truthy *} +não disponível ``` -Legal. +O atributo `n:else` também pode ser usado em conjunto com [`n:ifset` |#ifset elseifset], [`n:foreach` |#foreach], [`n:try` |#try], [#`n:ifcontent`] e [`n:ifchanged` |#ifchanged]. `{/if $cond}` ------------- -Você pode se surpreender que a expressão na condição `{if}` também possa ser especificada na etiqueta final. Isto é útil em situações em que ainda não sabemos o valor da condição quando a tag é aberta. Vamos chamar isso de decisão adiada. +Você pode se surpreender que a expressão na condição `{if}` também possa ser especificada na tag de fechamento. Isso é útil em situações em que ainda não conhecemos seu valor ao abrir a condição. Vamos chamar isso de decisão adiada. -Por exemplo, começamos a listar uma tabela com registros do banco de dados, e somente após completar o relatório é que percebemos que não havia nenhum registro no banco de dados. Portanto, colocamos condição na etiqueta final `{/if}` e, se não houver nenhum registro, nenhum deles será impresso: +Por exemplo, começamos a exibir uma tabela com registros de um banco de dados e só depois de terminar a exibição percebemos que não havia nenhum registro no banco de dados. Então, colocamos uma condição na tag de fechamento `{/if}` e, se não houver nenhum registro, nada disso será exibido: ```latte {if} -

                                                                                                    Printing rows from the database

                                                                                                    +

                                                                                                    Lista de linhas do banco de dados

                                                                                                    {foreach $resultSet as $row} @@ -264,30 +284,30 @@ Por exemplo, começamos a listar uma tabela com registros do banco de dados, e s {/if isset($row)} ``` -Prático, não é? +Engenhoso, não é? -Você também pode usar `{else}` na condição diferida, mas não `{elseif}`. +Na condição adiada, também é possível usar `{else}`, mas não `{elseif}`. `{ifset}` `{elseifset}` ----------------------- .[note] -Veja também [`{ifset block}` |template-inheritance#checking-block-existence] +Veja também [`{ifset block}` |template-inheritance#Verificando a Existência de Blocos] -Use a condição `{ifset $var}` para determinar se uma variável (ou múltiplas variáveis) existe e tem um valor não-nulo. Na verdade é o mesmo que `if (isset($var))` em PHP. Como qualquer tag de par, isto pode ser escrito na forma de [n:atributo |syntax#n:attributes], então vamos mostrá-lo no exemplo: +Usando a condição `{ifset $var}`, verificamos se uma variável (ou mais variáveis) existe e tem um valor não-*null*. Na verdade, é o mesmo que `if (isset($var))` em PHP. Como toda tag par, ela também pode ser escrita na forma de um [n:atributo |syntax#n:atributos], então vamos mostrar isso como exemplo: ```latte - + ``` -`{ifchanged}` .{data-version:2.9} ---------------------------------- +`{ifchanged}` +------------- -`{ifchanged}` verifica se o valor de uma variável mudou desde a última iteração no laço (foreach, para, ou enquanto). +`{ifchanged}` verifica se o valor de uma variável mudou desde a última iteração no loop (foreach, for ou while). -Se especificarmos uma ou mais variáveis na etiqueta, ela verificará se alguma delas mudou e imprimirá o conteúdo de acordo. Por exemplo, o exemplo seguinte imprime a primeira letra de um nome como um título cada vez que ele muda ao listar nomes: +Se especificarmos uma ou mais variáveis na tag, ele verificará se alguma delas mudou e exibirá o conteúdo de acordo. Por exemplo, o exemplo a seguir exibe a primeira letra do nome como um título toda vez que ela muda durante a exibição dos nomes: ```latte {foreach ($names|sort) as $name} @@ -297,7 +317,7 @@ Se especificarmos uma ou mais variáveis na etiqueta, ela verificará se alguma {/foreach} ``` -Entretanto, se nenhum argumento for apresentado, o próprio conteúdo apresentado será verificado em relação ao seu estado anterior. Isto significa que, no exemplo anterior, podemos omitir com segurança o argumento na etiqueta. E é claro que também podemos usar [n:attribute |syntax#n:attributes]: +No entanto, se não especificarmos nenhum argumento, o conteúdo renderizado será verificado em relação ao seu estado anterior. Isso significa que, no exemplo anterior, podemos omitir o argumento na tag. E, claro, também podemos usar um [n:atributo |syntax#n:atributos]: ```latte {foreach ($names|sort) as $name} @@ -307,49 +327,49 @@ Entretanto, se nenhum argumento for apresentado, o próprio conteúdo apresentad {/foreach} ``` -Você também pode incluir uma cláusula `{else}` dentro do `{ifchanged}`. +Dentro de `{ifchanged}`, também é possível especificar uma cláusula `{else}`. `{switch}` `{case}` `{default}` ------------------------------- -Compara valor com múltiplas opções. Isto é similar à estrutura `switch` que você conhece do PHP. No entanto, o Latte o melhora: +Compara um valor com várias opções. É análogo à instrução condicional `switch`, que você conhece do PHP. No entanto, o Latte o aprimora: -- utiliza comparação rigorosa (`===`) -- não precisa de um `break` +- usa comparação estrita (`===`) +- não precisa de `break` -Portanto, é o equivalente exato da estrutura `match` que o PHP 8.0 vem com. +É, portanto, o equivalente exato da estrutura `match` que vem com o PHP 8.0. ```latte {switch $transport} {case train} - By train + De trem {case plane} - By plane + De avião {default} - Differently + Outro {/switch} ``` -.{data-version:2.9} -A cláusula `{case}` pode conter múltiplos valores separados por vírgulas: + +A cláusula `{case}` pode conter vários valores separados por vírgulas: ```latte {switch $status} -{case $status::New}new item -{case $status::Sold, $status::Unknown}not available +{case $status::New}novo item +{case $status::Sold, $status::Unknown}não disponível {/switch} ``` -Laços .[#toc-loops] -=================== +Loops +===== -Em Latte, todos os loops que você conhece do PHP estão disponíveis para você: foreach, for and while. +No Latte, você encontrará todos os loops que conhece do PHP: foreach, for e while. `{foreach}` ----------- -Você escreve o ciclo exatamente da mesma forma que em PHP: +Escrevemos o loop exatamente como em PHP: ```latte {foreach $langs as $code => $lang} @@ -357,11 +377,11 @@ Você escreve o ciclo exatamente da mesma forma que em PHP: {/foreach} ``` -Além disso, ele tem alguns ajustes úteis de que falaremos agora. +Além disso, ele tem alguns truques úteis, sobre os quais falaremos agora. -Por exemplo, as verificações Latte que criaram variáveis não sobrepõem acidentalmente variáveis globais com o mesmo nome. Isto o salvará quando você assumir que `$lang` é o idioma atual da página, e não perceber que `foreach $langs as $lang` sobregravou essa variável. +Por exemplo, o Latte verifica se as variáveis criadas não sobrescrevem acidentalmente variáveis globais com o mesmo nome. Isso salva situações em que você conta que `$lang` contém o idioma atual da página e não percebe que `foreach $langs as $lang` sobrescreveu essa variável. -O laço frontal também pode ser escrito de forma muito elegante e econômica com [n:attribute |syntax#n:attributes]: +O loop foreach também pode ser escrito de forma muito elegante e econômica usando um [n:atributo |syntax#n:atributos]: ```latte
                                                                                                      @@ -369,7 +389,7 @@ O laço frontal também pode ser escrito de forma muito elegante e econômica co
                                                                                                    ``` -Você sabia que você pode preender o prefixo `inner-` para n:attributes? Agora, apenas a parte interna do elemento será repetida no laço: +Você sabia que pode adicionar o prefixo `inner-` aos n:atributos? Então, apenas o interior do elemento será repetido no loop: ```latte
                                                                                                    @@ -378,7 +398,7 @@ Você sabia que você pode preender o prefixo `inner-` para n:attributes? Agora,
                                                                                                    ``` -Portanto, imprime algo como: +Então, algo como isto será exibido: ```latte
                                                                                                    @@ -390,17 +410,17 @@ Portanto, imprime algo como: ``` -`{else}` .{data-version:2.9}{toc: foreach-else} ------------------------------------------------ +`{else}` .{toc: foreach-else} +----------------------------- -O laço `foreach` pode levar uma cláusula opcional `{else}` cujo texto é exibido se o conjunto dado estiver vazio: +Dentro do loop `foreach`, você pode especificar uma cláusula `{else}`, cujo conteúdo será exibido se o loop estiver vazio: ```latte
                                                                                                      {foreach $people as $person}
                                                                                                    • {$person->name}
                                                                                                    • {else} -
                                                                                                    • Sorry, no users in this list
                                                                                                    • +
                                                                                                    • Desculpe, não há usuários nesta lista
                                                                                                    • {/foreach}
                                                                                                    ``` @@ -409,17 +429,17 @@ O laço `foreach` pode levar uma cláusula opcional `{else}` cujo texto é exibi `$iterator` ----------- -Dentro do laço `foreach`, a variável `$iterator` é inicializada. Ela contém informações importantes sobre o laço atual. +Dentro do loop `foreach`, o Latte cria a variável `$iterator`, com a qual podemos obter informações úteis sobre o loop em andamento: -- `$iterator->first` - esta é a primeira iteração? -- `$iterator->last` - esta é a última iteração? -- `$iterator->counter` - contador de iteração, a partir de 1 -- `$iterator->counter0` - contador de iteração, a partir de 0 .{data-version:2.9} -- `$iterator->odd` - esta iteração é estranha? -- `$iterator->even` - esta iteração é uniforme? -- `$iterator->parent` - o iterador que envolve o atual .{data-version:2.9} -- `$iterator->nextValue` - o próximo item do laço -- `$iterator->nextKey` - a chave do próximo item do laço +- `$iterator->first` - está passando pelo loop pela primeira vez? +- `$iterator->last` - é a última passagem? +- `$iterator->counter` - qual é a passagem, contado a partir de um? +- `$iterator->counter0` - qual é a passagem, contado a partir de zero? +- `$iterator->odd` - é uma passagem ímpar? +- `$iterator->even` - é uma passagem par? +- `$iterator->parent` - o iterador que envolve o atual +- `$iterator->nextValue` - o próximo item no loop +- `$iterator->nextKey` - a chave do próximo item no loop ```latte @@ -435,20 +455,19 @@ Dentro do laço `foreach`, a variável `$iterator` é inicializada. Ela contém {/foreach} ``` -O latte é inteligente e `$iterator->last` funciona não apenas para arrays, mas também quando o laço passa por cima de um iterador geral onde o número de itens não é conhecido antecipadamente. +O Latte é esperto e `$iterator->last` funciona não apenas para arrays, mas também quando o loop ocorre sobre um iterador geral, onde o número de itens não é conhecido antecipadamente. `{first}` `{last}` `{sep}` -------------------------- -Estas etiquetas podem ser usadas dentro do laço `{foreach}`. O conteúdo de `{first}` é apresentado para o primeiro passe. -O conteúdo de `{last}` é renderizado ... você pode adivinhar? Sim, para o último passe. Na verdade, estes são atalhos para `{if $iterator->first}` e `{if $iterator->last}`. +Estas tags podem ser usadas dentro do loop `{foreach}`. O conteúdo de `{first}` é renderizado se for a primeira passagem. O conteúdo de `{last}` é renderizado … você consegue adivinhar? Sim, se for a última passagem. Na verdade, são atalhos para `{if $iterator->first}` e `{if $iterator->last}`. -As etiquetas também podem ser escritas como [n:atributos |syntax#n:attributes]: +As tags também podem ser usadas elegantemente como um [n:atributo |syntax#n:atributos]: ```latte {foreach $rows as $row} - {first}

                                                                                                    List of names

                                                                                                    {/first} + {first}

                                                                                                    Lista de nomes

                                                                                                    {/first}

                                                                                                    {$row->name}

                                                                                                    @@ -456,19 +475,19 @@ As etiquetas também podem ser escritas como [n:atributos |syntax#n:attributes]: {/foreach} ``` -O conteúdo do `{sep}` é apresentado se a iteração não for a última, portanto é adequado para a impressão de delimitadores, tais como vírgulas entre os itens listados: +O conteúdo da tag `{sep}` é renderizado se a passagem não for a última, sendo útil para renderizar separadores, por exemplo, vírgulas entre os itens exibidos: ```latte {foreach $items as $item} {$item} {sep}, {/sep} {/foreach} ``` -Isso é muito prático, não é? +Isso é bastante prático, não é? -`{iterateWhile}` .{data-version:2.10} -------------------------------------- +`{iterateWhile}` +---------------- -Ele simplifica o agrupamento de dados lineares durante a iteração em um laço frontal, realizando a iteração em um laço aninhado, desde que a condição seja atendida. [Leia as instruções no livro de receitas |cookbook/iteratewhile]. +Simplifica o agrupamento de dados lineares durante a iteração em um loop foreach, realizando a iteração em um loop aninhado enquanto a condição for atendida. [Leia o tutorial detalhado |cookbook/grouping]. Também pode substituir elegantemente `{first}` e `{last}` no exemplo acima: @@ -487,19 +506,21 @@ Também pode substituir elegantemente `{first}` e `{last}` no exemplo acima: {/foreach} ``` +Veja também os filtros [batch |filters#batch] e [group |filters#group]. + `{for}` ------- -Nós escrevemos o ciclo exatamente da mesma forma que em PHP: +Escrevemos o loop exatamente como em PHP: ```latte {for $i = 0; $i < 10; $i++} - Item #{$i} + Item {$i} {/for} ``` -A etiqueta também pode ser escrita como [n:atributo |syntax#n:attributes]: +A tag também pode ser usada como um [n:atributo |syntax#n:atributos]: ```latte

                                                                                                    {$i}

                                                                                                    @@ -509,7 +530,7 @@ A etiqueta também pode ser escrita como [n:atributo |syntax#n:attributes]: `{while}` --------- -Novamente, nós escrevemos o ciclo exatamente da mesma forma que em PHP: +Novamente, escrevemos o loop exatamente como em PHP: ```latte {while $row = $result->fetch()} @@ -517,7 +538,7 @@ Novamente, nós escrevemos o ciclo exatamente da mesma forma que em PHP: {/while} ``` -Ou como [n:atributo |syntax#n:attributes]: +Ou como um [n:atributo |syntax#n:atributos]: ```latte @@ -525,7 +546,7 @@ Ou como [n:atributo |syntax#n:attributes]: ``` -Uma variante com uma condição na etiqueta final corresponde ao loop do do-while em PHP: +Também é possível uma variante com a condição na tag de fechamento, que corresponde ao loop do-while em PHP: ```latte {while} @@ -537,7 +558,7 @@ Uma variante com uma condição na etiqueta final corresponde ao loop do do-whil `{continueIf}` `{skipIf}` `{breakIf}` ------------------------------------- -Há etiquetas especiais que você pode usar para controlar qualquer laço - `{continueIf ?}` e `{breakIf ?}` que saltam para a próxima iteração e terminam o laço, respectivamente, se as condições forem cumpridas: +Para controlar qualquer loop, podem ser usadas as tags `{continueIf ?}` e `{breakIf ?}`, que passam para o próximo elemento ou encerram o loop, respectivamente, se a condição for atendida: ```latte {foreach $rows as $row} @@ -547,8 +568,8 @@ Há etiquetas especiais que você pode usar para controlar qualquer laço - `{co {/foreach} ``` -.{data-version:2.9} -A tag `{skipIf}` é muito semelhante a `{continueIf}`, mas não incrementa o contador. Portanto, não há furos na numeração quando você imprime `$iterator->counter` e pula alguns itens. Também a cláusula {else} será apresentada quando você pular todos os itens. + +A tag `{skipIf}` é muito semelhante a `{continueIf}`, mas não incrementa o contador `$iterator->counter`, então se o exibirmos e ao mesmo tempo pularmos alguns itens, não haverá lacunas na numeração. E também a cláusula `{else}` será renderizada se pularmos todos os itens. ```latte
                                                                                                      @@ -556,7 +577,7 @@ A tag `{skipIf}` é muito semelhante a `{continueIf}`, mas não incrementa o con {skipIf $person->age < 18}
                                                                                                    • {$iterator->counter}. {$person->name}
                                                                                                    • {else} -
                                                                                                    • Sorry, no adult users in this list
                                                                                                    • +
                                                                                                    • Desculpe, não há adultos nesta lista
                                                                                                    • {/foreach}
                                                                                                    ``` @@ -565,72 +586,68 @@ A tag `{skipIf}` é muito semelhante a `{continueIf}`, mas não incrementa o con `{exitIf}` .{data-version:3.0.5} -------------------------------- -Termina a renderização de um modelo ou bloco quando uma condição é atendida (isto é, "saída antecipada"). +Encerra a renderização do template ou bloco se a condição for atendida (o chamado "early exit"). ```latte {exitIf !$messages} -

                                                                                                    Messages

                                                                                                    +

                                                                                                    Mensagens

                                                                                                    {$message}
                                                                                                    ``` -Incluindo os modelos .[#toc-including-templates] -================================================ +Inclusão de template +==================== `{include 'file.latte'}` .{toc: include} ---------------------------------------- .[note] -Veja também [`{include block}` |template-inheritance#printing-blocks] +Veja também [`{include block}` |template-inheritance#Renderização de Blocos] -A tag `{include}` carrega e torna o modelo especificado. Em nossa linguagem PHP favorita, é como: +A tag `{include}` carrega e renderiza o template especificado. Se estivéssemos falando na linguagem do nosso amado PHP, seria algo como: ```php ``` -Os modelos incluídos não têm acesso às variáveis do contexto ativo, mas têm acesso às variáveis globais. +Os templates incluídos não têm acesso às variáveis do contexto ativo, eles têm acesso apenas às variáveis globais. -Você pode passar as variáveis desta forma: +Você pode passar variáveis para o template incluído desta forma: ```latte -{* since Latte 2.9 *} {include 'template.latte', foo: 'bar', id: 123} - -{* antes do Latte 2.9 *} -{include 'template.latte', foo => 'bar', id => 123} ``` -O nome do modelo pode ser qualquer expressão PHP: +O nome do template pode ser qualquer expressão PHP: ```latte {include $someVar} {include $ajax ? 'ajax.latte' : 'not-ajax.latte'} ``` -O conteúdo inserido pode ser modificado usando [filtros |syntax#filters]. O exemplo a seguir remove todo o material HTML e ajusta o estojo: +O conteúdo incluído pode ser modificado usando [filtros |syntax#Filtros]. O exemplo a seguir remove todo o HTML e ajusta a capitalização: ```latte {include 'heading.latte' |stripHtml|capitalize} ``` -A [herança modelo |template inheritance] ** não está envolvida*** nisto, por padrão. Embora você possa adicionar etiquetas de bloco aos modelos que estão incluídos, elas não substituirão os blocos correspondentes no modelo em que estão incluídas. Pense em incluir como partes independentes e blindadas de páginas ou módulos. Este comportamento pode ser alterado usando o modificador `with blocks` (desde Latte 2.9.1): +Por padrão, a [herança de templates |template-inheritance] não desempenha nenhum papel neste caso. Embora possamos usar blocos no template incluído, os blocos correspondentes no template em que ele está sendo incluído não serão substituídos. Pense nos templates incluídos como partes ou módulos isolados e independentes das páginas. Este comportamento pode ser alterado usando o modificador `with blocks`: ```latte {include 'template.latte' with blocks} ``` -A relação entre o nome do arquivo especificado na etiqueta e o arquivo em disco é uma questão de [carregador |extending-latte#Loaders]. +A relação entre o nome do arquivo especificado na tag e o arquivo no disco é assunto do [loader |loaders]. -`{sandbox}` .{data-version:2.8} -------------------------------- +`{sandbox}` +----------- -Ao incluir um modelo criado por um usuário final, você deve considerar o sandboxing (mais informações na [documentação do sandbox |sandbox]): +Ao incluir um template criado pelo usuário final, você deve considerar o modo sandbox (mais informações na [documentação do sandbox |sandbox]): ```latte {sandbox 'untrusted.latte', level: 3, data: $menu} @@ -641,9 +658,9 @@ Ao incluir um modelo criado por um usuário final, você deve considerar o sandb ========= .[note] -Veja também [`{block name}` |template-inheritance#blocks] +Veja também [`{block name}` |template-inheritance#Blocos] -Blocos sem nome servem para a capacidade de aplicar [filtros |syntax#filters] a uma parte do modelo. Por exemplo, você pode aplicar um filtro de [tira |filters#strip] para remover espaços desnecessários: +Blocos sem nome servem como uma maneira de aplicar [filtros |syntax#Filtros] a uma parte do template. Por exemplo, assim pode ser aplicado o filtro [strip |filters#spaceless], que remove espaços em branco desnecessários: ```latte {block|strip} @@ -654,16 +671,16 @@ Blocos sem nome servem para a capacidade de aplicar [filtros |syntax#filters] a ``` -Tratamento de Exceções .[#toc-exception-handling] -================================================= +Controle de exceções +==================== -`{try}` .{data-version:2.9} ---------------------------- +`{try}` +------- -Estas etiquetas tornam extremamente fácil a construção de modelos robustos. +Graças a esta tag, é extremamente fácil criar templates robustos. -Se ocorrer uma exceção durante a renderização do bloco `{try}`, o bloco inteiro é jogado fora e a renderização continuará depois dele: +Se ocorrer uma exceção durante a renderização do bloco `{try}`, todo o bloco será descartado e a renderização continuará após ele: ```latte {try} @@ -675,7 +692,7 @@ Se ocorrer uma exceção durante a renderização do bloco `{try}`, o bloco inte {/try} ``` -O conteúdo da cláusula opcional `{else}` é apresentado somente quando ocorre uma exceção: +O conteúdo na cláusula opcional `{else}` será renderizado apenas se ocorrer uma exceção: ```latte {try} @@ -685,11 +702,11 @@ O conteúdo da cláusula opcional `{else}` é apresentado somente quando ocorre {/foreach} {else} -

                                                                                                    Sorry, the tweets could not be loaded.

                                                                                                    +

                                                                                                    Desculpe, não foi possível carregar os tweets.

                                                                                                    {/try} ``` -A etiqueta também pode ser escrita como [n:atributo |syntax#n:attributes]: +A tag também pode ser usada como um [n:atributo |syntax#n:atributos]: ```latte
                                                                                                      @@ -697,13 +714,13 @@ A etiqueta também pode ser escrita como [n:atributo |syntax#n:attributes]:
                                                                                                    ``` -Também é possível definir o [próprio manipulador de exceções |develop#exception handler], ou seja, a extração de madeira: +Também é possível definir um [manipulador de exceções personalizado |develop#Manipulador de exceção], por exemplo, para logging. -`{rollback}` .{data-version:2.9} --------------------------------- +`{rollback}` +------------ -O bloco `{try}` também pode ser parado e pulado manualmente usando `{rollback}`. Assim, não é necessário verificar todos os dados de entrada com antecedência, e somente durante a renderização você pode decidir se faz sentido renderizar o objeto. +O bloco `{try}` também pode ser interrompido e pulado manualmente usando `{rollback}`. Graças a isso, você não precisa verificar todos os dados de entrada antecipadamente e pode decidir durante a renderização que não deseja renderizar o objeto: ```latte {try} @@ -719,14 +736,14 @@ O bloco `{try}` também pode ser parado e pulado manualmente usando `{rollback}` ``` -Variáveis .[#toc-variables] -=========================== +Variáveis +========= `{var}` `{default}` ------------------- -Vamos criar novas variáveis no modelo com a tag `{var}`: +Criamos novas variáveis no template com a tag `{var}`: ```latte {var $name = 'John Smith'} @@ -736,13 +753,13 @@ Vamos criar novas variáveis no modelo com a tag `{var}`: {var $name = 'John Smith', $age = 27} ``` -A tag `{default}` funciona de forma semelhante, exceto que ela só cria variáveis se elas não existirem: +A tag `{default}` funciona de forma semelhante, mas cria variáveis apenas se elas não existirem. Se a variável já existir e contiver o valor `null`, ela não será sobrescrita: ```latte -{default $lang = 'cs'} +{default $lang = 'pt'} ``` -A partir do Latte 2.7, você também pode especificar [os tipos de variáveis |type-system]. Por enquanto, elas são informativas e o Latte não as verifica. +Você também pode especificar [tipos de variáveis |type-system]. Por enquanto, eles são informativos e o Latte não os verifica. ```latte {var string $name = $article->getTitle()} @@ -750,10 +767,10 @@ A partir do Latte 2.7, você também pode especificar [os tipos de variáveis |t ``` -`{parameters}` .{data-version:2.9} ----------------------------------- +`{parameters}` +-------------- -Assim como uma função declara seus parâmetros, um modelo pode declarar suas variáveis em seu início: +Assim como uma função declara seus parâmetros, um template também pode declarar suas variáveis no início: ```latte {parameters @@ -763,15 +780,15 @@ Assim como uma função declara seus parâmetros, um modelo pode declarar suas v } ``` -As variáveis `$a` e `$b` sem um valor padrão têm automaticamente um valor padrão de `null`. Os tipos declarados ainda são informativos e o Latte não os verifica. +As variáveis `$a` e `$b` sem um valor padrão especificado têm automaticamente o valor padrão `null`. Os tipos declarados são, por enquanto, informativos e o Latte não os verifica. -Além das variáveis declaradas, não são passadas para o modelo. Esta é uma diferença em relação à tag `{default}`. +Outras variáveis além das declaradas não são passadas para o template. Isso difere da tag `{default}`. `{capture}` ----------- -Ao usar a tag `{capture}` você pode capturar a saída para uma variável: +Captura a saída para uma variável: ```latte {capture $var} @@ -780,10 +797,10 @@ Ao usar a tag `{capture}` você pode capturar a saída para uma variável: {/capture} -

                                                                                                    Captured: {$var}

                                                                                                    +

                                                                                                    Capturado: {$var}

                                                                                                    ``` -A etiqueta também pode ser escrita como [n:atributo |syntax#n:attributes]: +A tag pode, como toda tag par, ser escrita também como um [n:atributo |syntax#n:atributos]: ```latte
                                                                                                      @@ -791,15 +808,17 @@ A etiqueta também pode ser escrita como [n:atributo |syntax#n:attributes]:
                                                                                                    ``` +A saída HTML é armazenada na variável `$var` na forma de um objeto `Latte\Runtime\Html`, para que [não ocorra escaping indesejado |develop#Desativando o auto-escaping de variável] ao ser exibida. + -Outros .[#toc-others] -===================== +Outros +====== `{contentType}` --------------- -Use a etiqueta para especificar que tipo de conteúdo o modelo representa. As opções são: +Com esta tag, você especifica qual tipo de conteúdo o template representa. As opções são: - `html` (tipo padrão) - `xml` @@ -808,9 +827,9 @@ Use a etiqueta para especificar que tipo de conteúdo o modelo representa. As op - `calendar` (iCal) - `text` -Seu uso é importante porque define a [fuga sensível ao contexto |safety-first#context-aware-escaping] e só então o Latte pode escapar corretamente. Por exemplo, `{contentType xml}` muda para o modo XML, `{contentType text}` desliga completamente a fuga. +Seu uso é importante porque define o [escaping sensível ao contexto |safety-first#Escaping sensível ao contexto] e só assim pode escapar corretamente. Por exemplo, `{contentType xml}` alterna para o modo XML, `{contentType text}` desativa completamente o escaping. -Se o parâmetro for um tipo MIME completo, como `application/xml`, ele também envia um cabeçalho HTTP `Content-Type` para o navegador: +Se o parâmetro for um MIME type completo, como `application/xml`, ele também enviará o cabeçalho HTTP `Content-Type` para o navegador: ```latte {contentType application/xml} @@ -829,46 +848,50 @@ Se o parâmetro for um tipo MIME completo, como `application/xml`, ele também e `{debugbreak}` -------------- -Especifica o local onde a execução do código irá quebrar. É usado para fins de depuração para o programador inspecionar o ambiente de tempo de execução e para garantir que o código seja executado conforme o esperado. Ele suporta [Xdebug |https://xdebug.org]. Além disso, é possível especificar uma condição quando o código deve quebrar. +Marca um local onde a execução do programa será pausada e o debugger será iniciado, para que o programador possa inspecionar o ambiente de execução e verificar se o programa está funcionando como esperado. Suporta [Xdebug |https://xdebug.org/]. É possível adicionar uma condição que determina quando o programa deve ser pausado. ```latte -{debugbreak} {Quebra o programa *} +{debugbreak} {* pausa o programa *} -{debugbreak $counter == 1} {* quebra o programa se a condição for cumprida *} +{debugbreak $counter == 1} {* pausa o programa se a condição for atendida *} ``` `{do}` ------ -Executa o código e não imprime nada. +Executa código PHP e não exibe nada. Assim como em todas as outras tags, por código PHP entende-se uma única expressão, veja [limitações do PHP |syntax#Limitações do PHP no Latte]. ```latte {do $num++} ``` -No Latte 2.7 e anteriores, foi utilizado o `{php}`. - `{dump}` -------- -Despeja um contexto variável ou atual. +Exibe uma variável ou o contexto atual. ```latte -{dump $name} {* dumps the $name variable *} +{dump $name} {* Exibe a variável $name *} -{dump} {* descarta todas as variáveis definidas *} +{dump} {* Exibe todas as variáveis atualmente definidas *} ``` .[caution] -Requer o pacote [Tracy |tracy:]. +Requer a biblioteca [Tracy |tracy:]. + + +`{php}` +------- + +Permite executar qualquer código PHP. A tag deve ser ativada usando a extensão [RawPhpExtension |develop#RawPhpExtension]. `{spaceless}` ------------- -Elimina espaços em branco desnecessários. É semelhante ao filtro [sem espaçamento |filters#spaceless]. +Remove espaços em branco desnecessários da saída. Funciona de forma semelhante ao filtro [spaceless |filters#spaceless]. ```latte {spaceless} @@ -878,52 +901,52 @@ Elimina espaços em branco desnecessários. É semelhante ao filtro [sem espaça {/spaceless} ``` -Saídas: +Gera ```latte
                                                                                                    • Hello
                                                                                                    ``` -A etiqueta também pode ser escrita como [n:atributo |syntax#n:attributes]: +A tag também pode ser escrita como um [n:atributo |syntax#n:atributos]. `{syntax}` ---------- -As etiquetas de latte não precisam ser fechadas somente em um único aparelho de amarrar. Você pode escolher outro separador, mesmo em tempo de execução. Isto é feito por `{syntax…}`, onde o parâmetro pode ser: +As tags Latte não precisam ser delimitadas apenas por chaves simples. Podemos escolher outro delimitador, e até mesmo em tempo de execução. Para isso serve `{syntax …}`, onde como parâmetro pode ser especificado: -- duplo: `{{...}}` -- desligado: desativa completamente as etiquetas Latte +- double: `{{...}}` +- off: desativa completamente o processamento de tags Latte -Usando a notação n:attribute, podemos desativar o Latte apenas para um bloco JavaScript: +Com o uso de n:atributos, é possível desativar o Latte, por exemplo, apenas para um bloco de JavaScript: ```latte ``` -O Latte pode ser utilizado muito confortavelmente dentro do JavaScript, basta evitar construções como neste exemplo, onde a carta segue imediatamente `\`, veja [Latte dentro do JavaScript ou CSS |recipes#Latte inside JavaScript or CSS]. +O Latte pode ser usado muito convenientemente dentro de JavaScript, basta evitar construções como neste exemplo, onde uma letra segue imediatamente após `{`, veja [Latte dentro de JavaScript ou CSS |recipes#Latte dentro de JavaScript ou CSS]. -Se você desligar o Latte com o `{syntax off}` (ou seja, tag, não o atributo n:), ele irá ignorar estritamente todas as tags até `{/syntax}`. +Se você desativar o Latte usando `{syntax off}` (ou seja, com a tag, não com o n:atributo), ele ignorará consistentemente todas as tags até `{/syntax}` -{trace} .{data-version:2.10} ----------------------------- +{trace} +------- -Lança uma exceção `Latte\RuntimeException`, cujo traço de pilha está no espírito dos gabaritos. Assim, ao invés de chamar funções e métodos, envolve chamar blocos e inserir gabaritos. Se você usar uma ferramenta para exibir claramente as exceções lançadas, como [Tracy |tracy:], você verá claramente a pilha de chamadas, incluindo todos os argumentos passados. +Lança uma exceção `Latte\RuntimeException`, cujo stack trace segue o espírito dos templates. Ou seja, em vez de chamadas de funções e métodos, contém chamadas de blocos e inclusões de templates. Se você usa uma ferramenta para exibição clara de exceções lançadas, como [Tracy |tracy:], o call stack será exibido claramente, incluindo todos os argumentos passados. -Ajudantes de Tag HTML .[#toc-html-tag-helpers] -============================================== +Auxiliares do codificador HTML +============================== -n:classe .[#toc-n-class] ------------------------- +n:class +------- -Graças a `n:class`, é muito fácil gerar o atributo HTML `class` exatamente como você precisa. +Graças a `n:class`, é muito fácil gerar o atributo HTML `class` exatamente como desejado. -Exemplo: Eu preciso do elemento ativo para ter a classe `active`: +Exemplo: preciso que o elemento ativo tenha a classe `active`: ```latte {foreach $items as $item} @@ -931,7 +954,7 @@ Exemplo: Eu preciso do elemento ativo para ter a classe `active`: {/foreach} ``` -E preciso ainda que o primeiro elemento tenha as classes `first` e `main`: +E, além disso, que o primeiro elemento tenha as classes `first` e `main`: ```latte {foreach $items as $item} @@ -947,13 +970,13 @@ E todos os elementos devem ter a classe `list-item`: {/foreach} ``` -Surpreendentemente simples, não é? +Incrivelmente simples, não é? -n:attr .[#toc-n-attr] ---------------------- +n:attr +------ -O atributo `n:attr` pode gerar atributos HTML arbitrários com a mesma elegância que o [n:class |#n:class]. +O atributo `n:attr` pode gerar quaisquer atributos HTML com a mesma elegância que [#n:class]. ```latte {foreach $data as $item} @@ -961,7 +984,7 @@ O atributo `n:attr` pode gerar atributos HTML arbitrários com a mesma elegânci {/foreach} ``` -Dependendo dos valores retornados, ele exibe, por exemplo +Dependendo dos valores retornados, exibe por ex.: ```latte @@ -972,26 +995,28 @@ Dependendo dos valores retornados, ele exibe, por exemplo ``` -n:tag .[#toc-n-tag] -------------------- +n:tag +----- -O atributo `n:tag` pode mudar dinamicamente o nome de um elemento HTML. +O atributo `n:tag` pode alterar dinamicamente o nome do elemento HTML. ```latte

                                                                                                    {$title}

                                                                                                    ``` -Se `$heading === null`, o `

                                                                                                    ` é impresso sem alterações. Caso contrário, o nome do elemento é mudado para o valor da variável, portanto para `$heading === 'h3'` ele escreve: +Se `$heading === null`, a tag `

                                                                                                    ` será exibida sem alterações. Caso contrário, o nome do elemento será alterado para o valor da variável, então para `$heading === 'h3'` será exibido: ```latte

                                                                                                    ...

                                                                                                    ``` +Como o Latte é um sistema de template seguro, ele verifica se o novo nome da tag é válido e não contém valores indesejados ou maliciosos. -n:secontente .[#toc-n-ifcontent] --------------------------------- -Impede que um elemento HTML vazio seja impresso, ou seja, um elemento que não contém nada além de espaço em branco. +n:ifcontent +----------- + +Evita que um elemento HTML vazio seja exibido, ou seja, um elemento que não contém nada além de espaços em branco. ```latte
                                                                                                    @@ -999,7 +1024,7 @@ Impede que um elemento HTML vazio seja impresso, ou seja, um elemento que não c
                                                                                                    ``` -Dependendo dos valores da variável `$error`, isto será impresso: +Exibe dependendo do valor da variável `$error`: ```latte {* $error = '' *} @@ -1013,42 +1038,42 @@ Dependendo dos valores da variável `$error`, isto será impresso: ``` -Tradução .[#toc-translation] -============================ +Traduções +========= -Para que as etiquetas de tradução funcionem, é necessário criar um [tradutor |develop#TranslatorExtension]. Você também pode usar o [`translate` |filters#translate] filtro para tradução. +Para que as tags de tradução funcionem, é necessário [ativar o tradutor |develop#TranslatorExtension]. Para tradução, você também pode usar o filtro [`translate` |filters#translate]. `{_...}` -------- -Traduz valores em outros idiomas. +Traduz valores para outros idiomas. ```latte -{_'Basket'} +{_'Carrinho'} {_$item} ``` -Outros parâmetros também podem ser passados para o tradutor: +Também é possível passar outros parâmetros para o tradutor: ```latte -{_'Basket', domain: order} +{_'Carrinho', domain: order} ``` `{translate}` ------------- -Překládá části části +Traduz partes do template: ```latte -

                                                                                                    {translate}Order{/translate}

                                                                                                    +

                                                                                                    {translate}Pedido{/translate}

                                                                                                    {translate domain: order}Lorem ipsum ...{/translate} ``` -A etiqueta também pode ser escrita como [n:atributo |syntax#n:attributes], para traduzir o interior do elemento: +A tag também pode ser escrita como um [n:atributo |syntax#n:atributos], para traduzir o interior do elemento: ```latte -

                                                                                                    Order

                                                                                                    +

                                                                                                    Pedido

                                                                                                    ``` diff --git a/latte/pt/template-inheritance.texy b/latte/pt/template-inheritance.texy index e9e4598a49..00b512be94 100644 --- a/latte/pt/template-inheritance.texy +++ b/latte/pt/template-inheritance.texy @@ -1,16 +1,16 @@ -Herança e Reutilização de Modelos -********************************* +Herança e Reutilização de Templates +*********************************** .[perex] -Os mecanismos de reutilização e herança dos modelos estão aqui para aumentar sua produtividade porque cada modelo contém apenas seu conteúdo único e os elementos e estruturas repetidos são reutilizados. Introduzimos três conceitos: [herança de layout |#layout inheritance], [reutilização horizontal |#horizontal reuse] e [herança de unidade |#unit inheritance]. +Os mecanismos de reutilização e herança de templates aumentarão sua produtividade, pois cada template contém apenas seu conteúdo único, e elementos e estruturas repetidos são reutilizados. Apresentamos três conceitos: [#Herança de layout], [#Reutilização horizontal] e [#Herança de unidade]. -O conceito de herança de modelos Latte é semelhante ao de herança de classe PHP. Você define um **modelo pai*** que outros **modelos filhos*** podem estender e podem sobrepor-se a partes do modelo pai. Funciona muito bem quando os elementos compartilham uma estrutura comum. Parece complicado? Não se preocupe, não é. +O conceito de herança de templates Latte é semelhante à herança de classes em PHP. Você define um **template pai**, do qual outros **templates filhos** podem herdar e podem substituir partes do template pai. Funciona muito bem quando os elementos compartilham uma estrutura comum. Parece complicado? Não se preocupe, é muito fácil. -Herança do Layout `{layout}` .{toc: Layout Inheritance} -======================================================= +Herança de Layout `{layout}` .{toc: Herança de Layout} +====================================================== -Vamos analisar a herança do modelo de layout começando com um exemplo. Este é um template pai que chamaremos por exemplo `layout.latte` e ele define um documento de esqueleto HTML. +Vamos ver a herança de template de layout com um exemplo. Este é um template pai, que chamaremos, por exemplo, de `layout.latte`, e que define o esqueleto de um documento HTML: ```latte @@ -30,9 +30,9 @@ Vamos analisar a herança do modelo de layout começando com um exemplo. Este é ``` -As etiquetas `{block}` definem três blocos que os modelos infantis podem preencher. Tudo o que a tag do bloco faz é dizer ao motor do modelo que um modelo infantil pode substituir essas partes do modelo, definindo seu próprio bloco com o mesmo nome. +As tags `{block}` definem três blocos que os templates filhos podem preencher. A tag block apenas informa que este local pode ser substituído por um template filho, definindo seu próprio bloco com o mesmo nome. -Um modelo infantil pode ser parecido com este: +Um template filho pode parecer com isto: ```latte {layout 'layout.latte'} @@ -44,11 +44,11 @@ Um modelo infantil pode ser parecido com este: {/block} ``` -A tag `{layout}` é a chave aqui. Ela diz ao motor do modelo que este modelo "estende" outro modelo. Quando Latte renderiza este modelo, primeiro ele localiza o modelo pai - neste caso, `layout.latte`. +A chave aqui é a tag `{layout}`. Ela diz ao Latte que este template "estende" outro template. Quando o Latte renderiza este template, ele primeiro encontra o template pai - neste caso, `layout.latte`. -Nesse momento, o motor do modelo notará as três etiquetas de blocos em `layout.latte` e substituirá esses blocos pelo conteúdo do modelo infantil. Note que, como o modelo criança não definiu o bloco *página*, o conteúdo do modelo pai é usado em seu lugar. O conteúdo dentro de uma tag `{block}` em um modelo pai é sempre usado como um recurso. +Neste ponto, o Latte percebe as três tags de bloco em `layout.latte` e substitui esses blocos pelo conteúdo do template filho. Como o template filho não definiu o bloco *footer*, o conteúdo do template pai é usado em seu lugar. O conteúdo dentro da tag `{block}` no template pai é sempre usado como fallback. -O resultado pode ser parecido: +A saída pode parecer com isto: ```latte @@ -68,7 +68,7 @@ O resultado pode ser parecido: ``` -Em um modelo infantil, os blocos só podem ser localizados no nível superior ou dentro de outro bloco, ou seja +No template filho, os blocos podem ser colocados apenas no nível superior ou dentro de outro bloco, ou seja: ```latte {block content} @@ -76,7 +76,7 @@ Em um modelo infantil, os blocos só podem ser localizados no nível superior ou {/block} ``` -Também será sempre criado um bloco, independentemente de a condição ao redor `{if}` ser avaliada como verdadeira ou falsa. Ao contrário do que você possa pensar, este modelo define um bloco. +Além disso, um bloco sempre será criado, independentemente de a condição `{if}` circundante ser avaliada como verdadeira ou falsa. Portanto, mesmo que não pareça, este template definirá o bloco. ```latte {if false} @@ -86,7 +86,7 @@ Também será sempre criado um bloco, independentemente de a condição ao redor {/if} ``` -Se você quiser que a saída dentro do bloco seja exibida condicionalmente, use ao invés disso o seguinte +Se você quiser que a saída dentro do bloco seja exibida condicionalmente, use o seguinte em vez disso: ```latte {block head} @@ -96,7 +96,7 @@ Se você quiser que a saída dentro do bloco seja exibida condicionalmente, use {/block} ``` -Os dados fora de um bloco em um modelo infantil são executados antes que o modelo de layout seja apresentado, assim você pode usá-lo para definir variáveis como `{var $foo = bar}` e propagar dados para toda a cadeia de herança: +O espaço fora dos blocos no template filho é executado antes da renderização do template de layout, então você pode usá-lo para definir variáveis como `{var $foo = bar}` e propagar dados por toda a cadeia de herança: ```latte {layout 'layout.latte'} @@ -106,51 +106,50 @@ Os dados fora de um bloco em um modelo infantil são executados antes que o mode ``` -Herança Multilevel .[#toc-multilevel-inheritance] -------------------------------------------------- -Você pode usar tantos níveis de herança quantos forem necessários. Uma maneira comum de usar a herança de layout é a seguinte abordagem em três níveis: +Herança Multinível +------------------ +Você pode usar quantos níveis de herança precisar. Uma maneira comum de usar a herança de layout é a seguinte abordagem de três níveis: -1) Crie um modelo `layout.latte` que contenha a aparência principal de seu site. -2) Crie um modelo `layout-SECTIONNAME.latte` para cada seção de seu site. Por exemplo, `layout-news.latte`, `layout-blog.latte` etc. Todos estes modelos estendem `layout.latte` e incluem estilos/design específicos de seção. -3) Crie modelos individuais para cada tipo de página, tais como um artigo de notícia ou entrada no blog. Estes modelos estendem o modelo apropriado da seção. +1) Crie um template `layout.latte` que contenha o esqueleto principal da aparência do site. +2) Crie um template `layout-SECTIONNAME.latte` para cada seção do seu site. Por exemplo, `layout-news.latte`, `layout-blog.latte`, etc. Todos esses templates estendem `layout.latte` e incluem estilos e design específicos para cada seção. +3) Crie templates individuais para cada tipo de página, como um artigo de notícias ou uma postagem de blog. Esses templates estendem o template da seção correspondente. -Hereditariedade de layout dinâmico .[#toc-dynamic-layout-inheritance] ---------------------------------------------------------------------- -Você pode usar uma variável ou qualquer expressão PHP como o nome do modelo pai, assim a herança pode se comportar dinamicamente: +Herança Dinâmica +---------------- +Como nome do template pai, pode-se usar uma variável ou qualquer expressão PHP, de modo que a herança pode se comportar dinamicamente: ```latte {layout $standalone ? 'minimum.latte' : 'layout.latte'} ``` -Você também pode usar o Latte API para escolher o modelo de layout [automaticamente |develop#automatic-layout-lookup]. +Você também pode usar a API Latte para [selecionar automaticamente |develop#Busca automática de layout] o template de layout. -Dicas .[#toc-tips] ------------------- -Aqui estão algumas dicas para trabalhar com a herança de layout: +Dicas +----- +Aqui estão algumas dicas para trabalhar com herança de layout: -- Se você usar `{layout}` em um template, ele deve ser a primeira etiqueta do template nesse template. +- Se você usar `{layout}` em um template, deve ser a primeira tag do template nesse template. -- O layout pode ser [pesquisado automaticamente |develop#automatic-layout-lookup] (como nos [apresentadores |application:templates#search-for-templates]). Nesse caso, se o modelo não tiver um layout, ele indicará isso com a tag `{layout none}`. +- O layout pode ser [pesquisado automaticamente |develop#Busca automática de layout] (como em [presenters |application:templates#Procurando templates]). Nesse caso, se o template não deve ter um layout, ele o indica com a tag `{layout none}`. -- A tag `{layout}` tem o pseudônimo `{extends}`. +- A tag `{layout}` tem um alias `{extends}`. -- O nome do arquivo do modelo estendido depende do [carregador de modelos |extending-latte#Loaders]. +- O nome do arquivo de layout depende do [loader |loaders]. -- Você pode ter tantos blocos quantos quiser. Lembre-se, os modelos infantis não precisam definir todos os blocos dos pais, assim você pode preencher padrões razoáveis em um número de blocos, e depois definir apenas os que você precisa mais tarde. +- Você pode ter quantos blocos quiser. Lembre-se de que os templates filhos não precisam definir todos os blocos pais, então você pode preencher valores padrão razoáveis em vários blocos e, em seguida, definir apenas aqueles que precisar mais tarde. -Blocos `{block}` .{toc: Blocks} +Blocos `{block}` .{toc: Blocos} =============================== .[note] -Veja também anônimo [`{block}` |tags#block] +Veja também o anônimo [`{block}` |tags#block] -Um bloco fornece uma maneira de mudar a forma como uma determinada parte de um modelo é renderizada, mas não interfere de forma alguma com a lógica ao seu redor. Tomemos o seguinte exemplo para ilustrar como um bloco funciona e, mais importante, como ele não funciona: +Um bloco representa uma maneira de alterar como uma parte específica de um template é renderizada, mas não interfere na lógica ao redor dele. No exemplo a seguir, mostraremos como um bloco funciona, mas também como não funciona: -```latte -{* parent.Latte *} +```latte .{file: parent.latte} {foreach $posts as $post} {block post}

                                                                                                    {$post->title}

                                                                                                    @@ -159,10 +158,9 @@ Um bloco fornece uma maneira de mudar a forma como uma determinada parte de um m {/foreach} ``` -Se você renderizar este modelo, o resultado seria exatamente o mesmo com ou sem as etiquetas de bloco. Os blocos têm acesso a variáveis de escopos externos. É apenas uma forma de torná-lo anulável por um modelo infantil: +Se você renderizar este template, o resultado será exatamente o mesmo com ou sem as tags `{block}`. Os blocos têm acesso a variáveis de escopos externos. Eles apenas dão a possibilidade de serem substituídos por um template filho: -```latte -{* child.Latte *} +```latte .{file: child.latte} {layout 'parent.Latte'} {block post} @@ -173,7 +171,7 @@ Se você renderizar este modelo, o resultado seria exatamente o mesmo com ou sem {/block} ``` -Agora, ao renderizar o modelo criança, o laço vai usar o bloco definido no modelo criança `child.Latte` ao invés do definido no modelo base `parent.Latte`; o modelo executado é então equivalente ao seguinte: +Agora, ao renderizar o template filho, o loop usará o bloco definido no template filho `child.Latte` em vez do bloco definido em `parent.Latte`; o template executado é então equivalente ao seguinte: ```latte {foreach $posts as $post} @@ -184,7 +182,7 @@ Agora, ao renderizar o modelo criança, o laço vai usar o bloco definido no mod {/foreach} ``` -Entretanto, se criarmos uma nova variável dentro de um bloco nomeado ou substituirmos um valor de um já existente, a mudança será visível apenas dentro do bloco: +No entanto, se criarmos uma nova variável dentro de um bloco nomeado ou substituirmos o valor de uma existente, a alteração será visível apenas dentro do bloco: ```latte {var $foo = 'foo'} @@ -193,17 +191,17 @@ Entretanto, se criarmos uma nova variável dentro de um bloco nomeado ou substit {var $bar = 'bar'} {/block} -foo: {$foo} // prints: foo -bar: {$bar ?? 'not defined'} // prints: not defined +foo: {$foo} // exibe: foo +bar: {$bar ?? 'not defined'} // exibe: not defined ``` -O conteúdo do bloco pode ser modificado por [filtros |syntax#filters]. O exemplo a seguir remove todo HTML e o título do bloco: +O conteúdo do bloco pode ser modificado usando [filtros |syntax#Filtros]. O exemplo a seguir remove todo o HTML e altera o tamanho das letras: ```latte {block title|stripHtml|capitalize}...{/block} ``` -A etiqueta também pode ser escrita como [n:atributo |syntax#n:attributes]: +A tag também pode ser escrita como um [n:atributo |syntax#n:atributos]: ```latte
                                                                                                    @@ -212,10 +210,10 @@ A etiqueta também pode ser escrita como [n:atributo |syntax#n:attributes]: ``` -Blocos locais .[#toc-local-blocks] ----------------------------------- +Blocos Locais +------------- -Cada bloco substitui o conteúdo do bloco pai com o mesmo nome. Exceto para blocos locais. Eles são algo como métodos privados na classe. Você pode criar um modelo sem se preocupar que - devido à coincidência de nomes de blocos - eles seriam sobregravados por um segundo modelo. +Cada bloco substitui o conteúdo do bloco pai com o mesmo nome - exceto os blocos locais. Em classes, seria algo como métodos privados. Assim, você pode criar o template sem se preocupar que, devido à coincidência de nomes de blocos, eles sejam substituídos por outro template. ```latte {block local helper} @@ -224,13 +222,13 @@ Cada bloco substitui o conteúdo do bloco pai com o mesmo nome. Exceto para bloc ``` -Blocos de Impressão `{include}` .{toc: Printing Blocks} -------------------------------------------------------- +Renderização de Blocos `{include}` .{toc: Renderização de Blocos} +----------------------------------------------------------------- .[note] Veja também [`{include file}` |tags#include] -Para imprimir um bloco em um lugar específico, use a tag `{include blockname}`: +Para exibir um bloco em um local específico, use a tag `{include blockname}`: ```latte {block title}{/block} @@ -238,32 +236,28 @@ Para imprimir um bloco em um lugar específico, use a tag `{include blockname}`:

                                                                                                    {include title}

                                                                                                    ``` -Você também pode exibir bloco a partir de outro modelo: +Também é possível exibir um bloco de outro template: ```latte {include footer from 'main.latte'} ``` -O bloco impresso não tem acesso às variáveis do contexto ativo, exceto se o bloco estiver definido no mesmo arquivo onde está incluído. No entanto, eles têm acesso às variáveis globais. +O bloco renderizado não tem acesso às variáveis do contexto ativo, exceto quando o bloco é definido no mesmo arquivo onde é incluído. No entanto, ele tem acesso às variáveis globais. -Você pode passar as variáveis desta forma: +Você pode passar variáveis para o bloco desta forma: ```latte -{* desde Latte 2.9 *} {include footer, foo: bar, id: 123} - -{* antes do Latte 2.9 *} -{include footer, foo => bar, id => 123} ``` -Você pode usar uma variável ou qualquer expressão em PHP como o nome do bloco. Neste caso, adicione a palavra-chave `block` antes da variável, para que se saiba em tempo de compilação que se trata de um bloco, e não [insira um modelo |tags#include], cujo nome também poderia estar na variável: +Como nome do bloco, pode-se usar uma variável ou qualquer expressão PHP. Nesse caso, adicionamos a palavra-chave `block` antes da variável, para que o Latte saiba em tempo de compilação que se trata de um bloco, e não de uma [inclusão de template |tags#include], cujo nome também poderia estar em uma variável: ```latte {var $name = footer} {include block $name} ``` -O bloco também pode ser impresso dentro dele mesmo, o que é útil, por exemplo, ao renderizar uma estrutura em árvore: +O bloco também pode ser renderizado dentro de si mesmo, o que é útil, por exemplo, ao renderizar uma estrutura em árvore: ```latte {define menu, $items} @@ -281,19 +275,19 @@ O bloco também pode ser impresso dentro dele mesmo, o que é útil, por exemplo {/define} ``` -Em vez de `{include menu, ...}`, também podemos escrever `{include this, ...}` onde `this` significa bloco atual. +Em vez de `{include menu, ...}`, podemos então escrever `{include this, ...}`, onde `this` significa o bloco atual. -O conteúdo impresso pode ser modificado por [filtros |syntax#filters]. O exemplo a seguir remove todo o HTML e o título do arquivo: +O bloco renderizado pode ser modificado usando [filtros |syntax#Filtros]. O exemplo a seguir remove todo o HTML e altera o tamanho das letras: ```latte {include heading|stripHtml|capitalize} ``` -Bloco dos Pais .[#toc-parent-block] ------------------------------------ +Bloco Pai +--------- -Se você precisar imprimir o conteúdo do bloco a partir do modelo pai, a declaração `{include parent}` fará o truque. Isto é útil se você quiser adicionar ao conteúdo de um bloco pai, em vez de substituí-lo completamente. +Se você precisar exibir o conteúdo de um bloco do template pai, use `{include parent}`. Isso é útil se você quiser apenas complementar o conteúdo do bloco pai em vez de substituí-lo completamente. ```latte {block footer} @@ -304,32 +298,31 @@ Se você precisar imprimir o conteúdo do bloco a partir do modelo pai, a declar ``` -Definições `{define}` .{toc: Definitions} ------------------------------------------ +Definições `{define}` .{toc: Definições} +---------------------------------------- -Além dos blocos, há também "definições" em Latte. Elas são comparáveis com funções em linguagens de programação regulares. Elas são úteis para reutilizar fragmentos de modelos para não se repetir. +Além dos blocos, existem também "definições" no Latte. Em linguagens de programação comuns, elas seriam comparadas a funções. São úteis para reutilizar fragmentos de template para não se repetir. -Latte tenta fazer as coisas facilmente, então basicamente as definições são as mesmas dos blocos, e **tudo o que é dito sobre blocos também se aplica às definições***. Ele difere dos blocos apenas de três maneiras: +O Latte tenta simplificar as coisas, então, basicamente, as definições são iguais aos blocos e **tudo o que foi dito sobre blocos também se aplica às definições**. Elas diferem dos blocos porque: -1) eles podem aceitar argumentos -2) eles não podem ter [filtros |syntax#filters] -3) elas são incluídas nas etiquetas `{define}` e o conteúdo dentro destas etiquetas não é enviado para saída até que você as inclua. Graças a isso, você pode criá-las em qualquer lugar: +1) estão contidas nas tags `{define}` +2) são renderizadas apenas quando incluídas via `{include}` +3) podem ter parâmetros definidos de forma semelhante às funções em PHP ```latte {block foo}

                                                                                                    Hello

                                                                                                    {/block} -{* prints:

                                                                                                    Hello

                                                                                                    *} +{* exibe:

                                                                                                    Hello

                                                                                                    *} {define bar}

                                                                                                    World

                                                                                                    {/define} -{* prints nothing *} +{* não exibe nada *} {include bar} -{* prints:

                                                                                                    World

                                                                                                    *} +{* exibe:

                                                                                                    World

                                                                                                    *} ``` -Imagine ter um modelo genérico de helper que define como renderizar formulários HTML através de definições: +Imagine que você tem um template auxiliar com uma coleção de definições sobre como desenhar formulários HTML. -```latte -{* forms.latte *} +```latte .{file: forms.latte} {define input, $name, $value, $type = 'text'} {/define} @@ -339,38 +332,36 @@ Imagine ter um modelo genérico de helper que define como renderizar formulário {/define} ``` -Os argumentos de uma definição são sempre opcionais com valor padrão `null`, a menos que o valor padrão seja especificado (aqui `text` é o valor padrão para `$type`, possível desde Latte 2.9.1). A partir do Latte 2.7, os tipos de parâmetros também podem ser declarados: `{define input, string $name, ...}`. - -As definições não têm acesso às variáveis do contexto ativo, mas elas têm acesso às variáveis globais. +Os argumentos são sempre opcionais com um valor padrão `null`, a menos que um valor padrão seja especificado (aqui `'text'` é o valor padrão para `$type`). Também podem ser declarados tipos de parâmetros: `{define input, string $name, ...}`. -Eles estão incluídos da [mesma forma que o bloco |#Printing Blocks]: +Carregamos o template com definições usando [`{import}` |#Reutilização Horizontal]. As próprias definições são renderizadas [da mesma forma que os blocos |#Renderização de Blocos]: ```latte

                                                                                                    {include input, 'password', null, 'password'}

                                                                                                    {include textarea, 'comment'}

                                                                                                    ``` +As definições não têm acesso às variáveis do contexto ativo, mas têm acesso às variáveis globais. -Nomes de blocos dinâmicos .[#toc-dynamic-block-names] ------------------------------------------------------ -O Latte permite grande flexibilidade na definição de blocos porque o nome do bloco pode ser qualquer expressão PHP. Este exemplo define três blocos chamados `hi-Peter`, `hi-John` e `hi-Mary`: +Nomes de Blocos Dinâmicos +------------------------- -```latte -{* parent.latte *} +O Latte permite grande flexibilidade na definição de blocos, pois o nome do bloco pode ser qualquer expressão PHP. Este exemplo define três blocos com os nomes `hi-Peter`, `hi-John` e `hi-Mary`: + +```latte .{file: parent.latte} {foreach [Peter, John, Mary] as $name} {block "hi-$name"}Hi, I am {$name}.{/block} {/foreach} ``` -Por exemplo, podemos redefinir apenas um bloco em um modelo infantil: +No template filho, podemos então redefinir, por exemplo, apenas um bloco: -```latte -{* child.latte *} +```latte .{file: child.latte} {block hi-John}Hello. I am {$name}.{/block} ``` -Assim, a produção será parecida com esta: +Assim, a saída ficará assim: ```latte Hi, I am Peter. @@ -379,13 +370,13 @@ Hi, I am Mary. ``` -Verificação da existência do bloco `{ifset}` .{toc: Checking Block Existence} ------------------------------------------------------------------------------ +Verificando a Existência de Blocos `{ifset}` .{toc: Verificando a Existência de Blocos} +--------------------------------------------------------------------------------------- .[note] -Veja também [`{ifset $var}` |tags#ifset-elseifset] +Veja também [`{ifset $var}` |tags#ifset elseifset] -Use o teste `{ifset blockname}` para verificar se um bloco (ou mais blocos) existe no contexto atual: +Usando o teste `{ifset blockname}`, verificamos se um bloco (ou vários blocos) existe no contexto atual: ```latte {ifset footer} @@ -397,7 +388,7 @@ Use o teste `{ifset blockname}` para verificar se um bloco (ou mais blocos) exis {/ifset} ``` -Você pode usar uma variável ou qualquer expressão em PHP como o nome do bloco. Neste caso, adicione a palavra-chave `block` antes da variável para deixar claro que não é a [variável |tags#ifset-elseifset] que é verificada: +Como nome do bloco, pode-se usar uma variável ou qualquer expressão PHP. Nesse caso, adicionamos a palavra-chave `block` antes da variável, para deixar claro que não se trata de um teste de existência de [variáveis |tags#ifset elseifset]: ```latte {ifset block $name} @@ -405,71 +396,69 @@ Você pode usar uma variável ou qualquer expressão em PHP como o nome do bloco {/ifset} ``` +A existência de blocos também é verificada pela função [`hasBlock()` |functions#hasBlock]: -Dicas .[#toc-tips] ------------------- -Aqui estão algumas dicas para trabalhar com blocos: - -- O último bloco de nível superior não precisa ter etiqueta de fechamento (o bloco termina com o final do documento). Isto simplifica a escrita de modelos infantis, que é um bloco primário. +```latte +{if hasBlock(header) || hasBlock(footer)} + ... +{/if} +``` -- Para uma legibilidade extra, você pode opcionalmente dar um nome à sua tag `{/block}`, por exemplo `{/block footer}`. Entretanto, o nome deve corresponder ao nome do bloco. Em modelos maiores, esta técnica ajuda você a ver quais etiquetas de bloco estão sendo fechadas. -- Você não pode definir diretamente várias etiquetas de bloco com o mesmo nome no mesmo modelo. Mas isto pode ser conseguido usando [nomes de blocos dinâmicos |#dynamic block names]. +Dicas +----- +Algumas dicas para trabalhar com blocos: -- Você pode usar [n:atributos |syntax#n:attributes] para definir blocos como `

                                                                                                    Welcome to my awesome homepage

                                                                                                    ` +- O último bloco de nível superior não precisa ter uma tag de fechamento (o bloco termina no final do documento). Isso simplifica a escrita de templates filhos que contêm um bloco principal. -- Os blocos também podem ser usados sem nomes apenas para aplicar os [filtros |syntax#filters] à saída: `{block|strip} hello {/block}` +- Para melhor legibilidade, você pode indicar o nome do bloco na tag `{/block}`, por exemplo, `{/block footer}`. No entanto, o nome deve corresponder ao nome do bloco. Em templates maiores, essa técnica ajuda a identificar quais tags de bloco estão sendo fechadas. +- Você não pode definir diretamente várias tags de bloco com o mesmo nome no mesmo template. No entanto, isso pode ser alcançado usando [#nomes de blocos dinâmicos]. -Reutilização Horizontal `{import}` .{toc: Horizontal Reuse} -=========================================================== +- Você pode usar [n:atributos |syntax#n:atributos] para definir blocos como `

                                                                                                    Welcome to my awesome homepage

                                                                                                    ` -A reutilização horizontal é um terceiro mecanismo de reusabilidade e herança em Latte. Ele permite carregar blocos de outros modelos. É semelhante à criação de um arquivo PHP com funções de ajuda ou uma característica. +- Blocos também podem ser usados sem nomes apenas para aplicar [filtros |syntax#Filtros]: `{block|strip} hello {/block}` -Embora a herança de modelos de layout seja uma das características mais poderosas do Latte, ela está limitada a uma única herança; um modelo só pode estender um outro modelo. Esta limitação torna a herança de modelos simples de entender e fácil de depurar: -```latte -{layout 'layout.latte'} +Reutilização Horizontal `{import}` .{toc: Reutilização Horizontal} +================================================================== -{block title}...{/block} -{block content}...{/block} -``` +A reutilização horizontal é o terceiro mecanismo no Latte para reutilização e herança. Permite carregar blocos de outros templates. É semelhante a quando criamos um arquivo com funções auxiliares em PHP e depois o carregamos usando `require`. -A reutilização horizontal é uma forma de alcançar o mesmo objetivo que a herança múltipla, mas sem a complexidade associada: +Embora a herança de layout de template seja uma das características mais poderosas do Latte, ela é limitada à herança simples - um template pode estender apenas um outro template. A reutilização horizontal é uma maneira de alcançar herança múltipla. -```latte -{layout 'layout.latte'} +Vamos ter um arquivo com definições de blocos: -{import 'blocks.latte'} +```latte .{file: blocks.latte} +{block sidebar}...{/block} -{block title}...{/block} -{block content}...{/block} +{block menu}...{/block} ``` -A declaração `{import}` diz à Latte para importar todos os blocos e [definições |#definitions] definidas em `blocks.latte` para o modelo atual. +Usando o comando `{import}`, importamos todos os blocos e [#definições] definidos em `blocks.latte` para outro template: -```latte -{* blocks.latte *} +```latte .{file: child.latte} +{import 'blocks.latte'} -{block sidebar}...{/block} +{* agora os blocos sidebar e menu podem ser usados *} ``` -Neste exemplo, a declaração `{import}` importa o bloco `sidebar` para o modelo principal. +Se você importar blocos no template pai (ou seja, usar `{import}` em `layout.latte`), os blocos também estarão disponíveis em todos os templates filhos, o que é muito prático. -O modelo importado não deve [estender |#Layout Inheritance] outro modelo, e seu corpo deve estar vazio. Entretanto, o gabarito importado pode importar outros gabaritos. +O template destinado à importação (por exemplo, `blocks.latte`) não deve [estender |#Herança de Layout] outro template, ou seja, usar `{layout}`. No entanto, ele pode importar outros templates. -A tag `{import}` deve ser a primeira tag modelo após `{layout}`. O nome do template pode ser qualquer expressão PHP: +A tag `{import}` deve ser a primeira tag do template após `{layout}`. O nome do template pode ser qualquer expressão PHP: ```latte {import $ajax ? 'ajax.latte' : 'not-ajax.latte'} ``` -Você pode usar tantas declarações `{import}` quantas quiser em qualquer modelo dado. Se dois gabaritos importados definirem o mesmo bloco, o primeiro ganha. Entretanto, a maior prioridade é dada ao modelo principal, que pode sobrescrever qualquer bloco importado. +Você pode usar quantos comandos `{import}` quiser no template. Se dois templates importados definirem o mesmo bloco, o primeiro vence. No entanto, o template principal tem a prioridade mais alta e pode substituir qualquer bloco importado. -Todos os blocos sobrepostos podem ser incluídos gradualmente, inserindo-os como [bloco pai |#parent block]: +O conteúdo dos blocos substituídos pode ser preservado incluindo o bloco da mesma forma que o [#bloco pai] é incluído: ```latte -{layout 'base.latte'} +{layout 'layout.latte'} {import 'blocks.latte'} @@ -481,17 +470,17 @@ Todos os blocos sobrepostos podem ser incluídos gradualmente, inserindo-os como {block content}...{/block} ``` -Neste exemplo, `{include parent}` chamará corretamente o bloco `sidebar` a partir do modelo `blocks.latte`. +Neste exemplo, `{include parent}` chama o bloco `sidebar` do template `blocks.latte`. -Herança da Unidade `{embed}` .{toc: Unit Inheritance}{data-version:2.9} -======================================================================= +Herança de Unidade `{embed}` .{toc: Herança de Unidade} +======================================================= -A herança de unidade leva a idéia de herança de layout ao nível de fragmentos de conteúdo. Enquanto o layout inheritance funciona com "esqueletos de documentos", que são trazidos à vida por modelos de crianças, a herança de unidade permite criar esqueletos para unidades menores de conteúdo e reutilizá-los onde você quiser. +A herança de unidade estende a ideia da herança de layout ao nível de fragmentos de conteúdo. Enquanto a herança de layout trabalha com o "esqueleto do documento", que é animado pelos templates filhos, a herança de unidade permite criar esqueletos para unidades menores de conteúdo e reutilizá-los onde quiser. -Na unidade de herança, a chave é a tag `{embed}`. Ela combina o comportamento de `{include}` e `{layout}`. Ela permite incluir outro modelo ou conteúdo de bloco e, opcionalmente, passar variáveis, assim como `{include}` faz. Ela também permite que você substitua qualquer bloco definido dentro do modelo incluído, como o `{layout}` faz. +Na herança de unidade, a chave é a tag `{embed}`. Ela combina o comportamento de `{include}` e `{layout}`. Permite incluir o conteúdo de outro template ou bloco e, opcionalmente, passar variáveis, assim como no caso de `{include}`. Também permite substituir qualquer bloco definido dentro do template incluído, como ao usar `{layout}`. -Por exemplo, vamos utilizar o elemento de acordeão dobrável. Vamos dar uma olhada no esqueleto do elemento no modelo `collapsible.latte`: +Por exemplo, usaremos um elemento acordeão. Vejamos o esqueleto do elemento armazenado no template `collapsible.latte`: ```latte
                                                                                                    @@ -505,9 +494,9 @@ Por exemplo, vamos utilizar o elemento de acordeão dobrável. Vamos dar uma olh
                                                                                                    ``` -As etiquetas `{block}` definem dois blocos que os modelos infantis podem preencher. Sim, como no caso do modelo pai no modelo de herança de layout. Você também pode ver a variável `$modifierClass`. +As tags `{block}` definem dois blocos que os templates filhos podem preencher. Sim, como no caso do template pai na herança de layout. Você também vê a variável `$modifierClass`. -Vamos usar nosso elemento no modelo. É aqui que entra `{embed}`. É um kit super poderoso que nos permite fazer todas as coisas: incluir o conteúdo do elemento no template, adicionar variáveis a ele e adicionar blocos com HTML personalizado a ele: +Vamos usar nosso elemento no template. É aqui que entra `{embed}`. É uma tag extremamente poderosa que nos permite fazer tudo: incluir o conteúdo do template do elemento, adicionar variáveis a ele e adicionar blocos com nosso próprio HTML: ```latte {embed 'collapsible.latte', modifierClass: my-style} @@ -522,7 +511,7 @@ Vamos usar nosso elemento no modelo. É aqui que entra `{embed}`. É um kit supe {/embed} ``` -O resultado pode ser parecido: +A saída pode parecer com isto: ```latte
                                                                                                    @@ -537,7 +526,7 @@ O resultado pode ser parecido:
                                                                                                    ``` -Blocos dentro de etiquetas embutidas formam uma camada separada independente de outros blocos. Portanto, eles podem ter o mesmo nome que o bloco fora do encaixe e não são afetados de forma alguma. Usando a tag [inclua |#Printing Blocks] dentro de `{embed}` tags você pode inserir blocos aqui criados, blocos de modelo embutido (que * não são* [locais |#Local Blocks]), e também blocos de modelo principal que *são* locais. Você também pode [importar blocos |#Horizontal Reuse] de outros arquivos: +Blocos dentro de tags incorporadas formam uma camada separada, independente de outros blocos. Portanto, eles podem ter o mesmo nome que um bloco fora da incorporação e não são afetados de forma alguma. Usando a tag [include |#Renderização de Blocos] dentro das tags `{embed}`, você pode incluir blocos criados aqui, blocos do template incorporado (que *não são* [locais |#Blocos Locais]) e também blocos do template principal que, por outro lado, *são* locais. Você também pode [importar blocos |#Reutilização Horizontal] de outros arquivos: ```latte {block outer}…{/block} @@ -549,18 +538,18 @@ Blocos dentro de etiquetas embutidas formam uma camada separada independente de {block inner}…{/block} {block title} - {include inner} {* funciona, o bloco é definido dentro do encaixe *} - {include hello} {* funciona, o bloco é local neste modelo *} - {include content} {* funciona, o bloco é definido no modelo embutido *} - {include aBlockDefinedInImportedTemplate} {* works *} - {include outer} {* does not work! - block is in outer layer *} + {include inner} {* funciona, bloco definido dentro do embed *} + {include hello} {* funciona, bloco é local neste template *} + {include content} {* funciona, bloco definido no template incorporado *} + {include aBlockDefinedInImportedTemplate} {* funciona *} + {include outer} {* não funciona! - bloco está na camada externa *} {/block} {/embed} ``` -Os modelos incorporados não têm acesso às variáveis do contexto ativo, mas eles têm acesso às variáveis globais. +Templates incorporados não têm acesso às variáveis do contexto ativo, mas têm acesso às variáveis globais. -Com `{embed}` você pode inserir não apenas modelos, mas também outros blocos, de modo que o exemplo anterior poderia ser escrito desta forma: .{data-version:2.10} +Com `{embed}`, é possível incluir não apenas templates, mas também outros blocos, e assim o exemplo anterior poderia ser escrito desta forma: ```latte {define collapsible} @@ -581,23 +570,23 @@ Com `{embed}` você pode inserir não apenas modelos, mas também outros blocos, {/embed} ``` -Se passarmos uma expressão para `{embed}` e não estiver claro se se trata de um bloco ou nome de arquivo, acrescente a palavra-chave `block` ou `file`: +Se passarmos uma expressão para `{embed}` e não estiver claro se é o nome de um bloco ou de um arquivo, adicionamos a palavra-chave `block` ou `file`: ```latte {embed block $name} ... {/embed} ``` -Casos de uso .[#toc-use-cases] -============================== +Casos de Uso +============ -Há vários tipos de herança e reutilização de código em Latte. Vamos resumir os principais conceitos para uma maior liberação: +No Latte, existem diferentes tipos de herança e reutilização de código. Vamos resumir os principais conceitos para maior clareza: `{include template}` -------------------- -**Use Case:** Usando `header.latte` & `footer.latte` dentro de `layout.latte`. +**Caso de uso**: Usar `header.latte` e `footer.latte` dentro de `layout.latte`. `header.latte` @@ -630,7 +619,7 @@ Há vários tipos de herança e reutilização de código em Latte. Vamos resumi `{layout}` ---------- -**Uso caso***: Estendendo `layout.latte` dentro de `homepage.latte` & `about.latte`. +**Caso de uso**: Estender `layout.latte` dentro de `homepage.latte` e `about.latte`. `layout.latte` @@ -666,7 +655,7 @@ Há vários tipos de herança e reutilização de código em Latte. Vamos resumi `{import}` ---------- -**Use Case***: `sidebar.latte` em `single.product.latte` & `single.service.latte`. +**Caso de uso**: `sidebar.latte` em `single.product.latte` e `single.service.latte`. `sidebar.latte` @@ -698,7 +687,7 @@ Há vários tipos de herança e reutilização de código em Latte. Vamos resumi `{define}` ---------- -**Uso caso***: Uma função que obtém algumas variáveis e produz alguma marcação. +**Caso de uso**: Funções que recebem variáveis e renderizam algo. `form.latte` @@ -724,7 +713,7 @@ Há vários tipos de herança e reutilização de código em Latte. Vamos resumi `{embed}` --------- -**Uso caso***: Embutir `pagination.latte` em `product.table.latte` & `service.table.latte`. +**Caso de uso**: Incluir `pagination.latte` em `product.table.latte` e `service.table.latte`. `pagination.latte` diff --git a/latte/pt/type-system.texy b/latte/pt/type-system.texy index 6aa4fd2cde..9baf8bb9f8 100644 --- a/latte/pt/type-system.texy +++ b/latte/pt/type-system.texy @@ -1,27 +1,27 @@ -Tipo Sistema -************ +Sistema de Tipos +**************** -
                                                                                                    +
                                                                                                    -O sistema de tipo é a coisa principal para o desenvolvimento de aplicações robustas. O Latte traz suporte de tipo para os modelos. Para saber quais dados ou tipo de objeto cada variável permite +O sistema de tipos é crucial para o desenvolvimento de aplicações robustas. O Latte traz suporte a tipos também para os templates. Graças ao fato de sabermos qual tipo de dado ou objeto está em cada variável, -- IDE para autocompletar corretamente (ver [integração e plugins |recipes#Editors and IDE]) -- análise estática para detectar erros +- o IDE pode sugerir corretamente (veja [integração |recipes#Editores e IDEs]) +- a análise estática pode detectar erros -Dois pontos que melhoram significativamente a qualidade e a conveniência do desenvolvimento. +Ambos aumentam significativamente a qualidade e o conforto do desenvolvimento.
                                                                                                    .[note] -Os tipos declarados são informativos e o Latte não os verifica neste momento. +Os tipos declarados são informativos e o Latte não os verifica no momento. -Como começar a usar os tipos? Crie uma classe modelo, por exemplo `CatalogTemplateParameters`, representando os parâmetros passados: +Como começar a usar tipos? Crie uma classe de template, por exemplo, `CatalogTemplateParameters`, representando os parâmetros passados, seus tipos e, opcionalmente, seus valores padrão: ```php class CatalogTemplateParameters { public function __construct( - public string $langs, + public string $lang, /** @var ProductEntity[] */ public array $products, public Address $address, @@ -35,19 +35,16 @@ $latte->render('template.latte', new CatalogTemplateParameters( )); ``` -Em seguida, insira a tag `{templateType}` com o nome completo da classe (incluindo o espaço de nomes) no início do modelo. Isto define que há variáveis `$langs` e `$products` no modelo, incluindo os tipos correspondentes. -Você também pode especificar os tipos de variáveis locais usando tags [`{var}` |tags#var-default], `{varType}` e [`{define}` |template-inheritance#definitions]. +E, em seguida, no início do template, insira a tag `{templateType}` com o nome completo da classe (incluindo o namespace). Isso define que no template existem as variáveis `$lang` e `$products`, incluindo os tipos correspondentes. Você pode especificar os tipos de variáveis locais usando as tags [`{var}` |tags#var default], `{varType}`, [`{define}` |template-inheritance#Definições]. -Agora a IDE pode se autocompletar corretamente. +A partir desse momento, o IDE pode sugerir corretamente. -Como salvar o trabalho? Como escrever um modelo de classe ou etiquetas `{varType}` o mais facilmente possível? Faça com que sejam geradas. -É exatamente isso que fazem os pares de tags `{templatePrint}` e `{varPrint}`. -Se você colocar uma dessas tags em um modelo, o código de classe ou modelo será exibido ao invés da renderização normal. Então, basta selecionar e copiar o código em seu projeto. +Como economizar trabalho? Qual é a maneira mais fácil de escrever uma classe com parâmetros de template ou tags `{varType}`? Deixe que sejam gerados para você. Para isso, existem duas tags: `{templatePrint}` e `{varPrint}`. Se você as colocar no template, em vez da renderização normal, será exibida uma sugestão de código da classe ou uma lista de tags `{varType}`. Basta então marcar o código com um clique e copiá-lo para o projeto. `{templateType}` ---------------- -Os tipos de parâmetros passados para o modelo são declarados usando classe: +Declaramos os tipos dos parâmetros passados para o template usando uma classe: ```latte {templateType MyApp\CatalogTemplateParameters} @@ -56,7 +53,7 @@ Os tipos de parâmetros passados para o modelo são declarados usando classe: `{varType}` ----------- -Como declarar os tipos de variáveis? Para este propósito, use a tag `{varType}` para uma variável existente, ou [`{var}` |tags#var-default]: +Como declarar tipos de variáveis? Para isso, servem as tags `{varType}` para variáveis existentes, ou [`{var}` |tags#var default]: ```latte {varType Nette\Security\User $user} @@ -66,11 +63,11 @@ Como declarar os tipos de variáveis? Para este propósito, use a tag `{varType} `{templatePrint}` ----------------- -Você também pode gerar esta classe usando a tag `{templatePrint}`. Se você colocá-la no início do modelo, o código de classe é exibido ao invés do modelo normal. Em seguida, basta selecionar e copiar o código em seu projeto. +Você também pode ter a classe gerada usando a tag `{templatePrint}`. Se você a colocar no início do template, em vez da renderização normal, será exibida uma sugestão de classe. Basta então marcar o código com um clique e copiá-lo para o projeto. `{varPrint}` ------------ -A tag `{varPrint}` economiza seu tempo. Se você colocá-la em um modelo, a lista de tags `{varType}` é exibida ao invés da renderização normal. Em seguida, basta selecionar e copiar o código em seu modelo. +A tag `{varPrint}` economizará seu tempo de escrita. Se você a colocar no template, em vez da renderização normal, será exibida uma sugestão de tags `{varType}` para variáveis locais. Basta então marcar o código com um clique e copiá-lo para o template. -O `{varPrint}` lista variáveis locais que não são parâmetros de modelo. Se você quiser listar todas as variáveis, use `{varPrint all}`. +O próprio `{varPrint}` lista apenas variáveis locais que não são parâmetros do template. Se você quiser listar todas as variáveis, use `{varPrint all}`. diff --git a/latte/pt/why-use.texy b/latte/pt/why-use.texy new file mode 100644 index 0000000000..a989d5557e --- /dev/null +++ b/latte/pt/why-use.texy @@ -0,0 +1,80 @@ +Por que usar templates? +*********************** + + +Por que devo usar um sistema de templates em PHP? +------------------------------------------------- + +Por que usar um sistema de templates em PHP, quando o próprio PHP é uma linguagem de template? + +Vamos primeiro recapitular brevemente a história desta linguagem, que está cheia de reviravoltas interessantes. Uma das primeiras linguagens de programação usadas para gerar páginas HTML foi a linguagem C. No entanto, logo se mostrou que seu uso para esse fim era impraticável. Rasmus Lerdorf então criou o PHP, que facilitou a geração de HTML dinâmico com a linguagem C no backend. O PHP foi, portanto, originalmente projetado como uma linguagem de template, mas com o tempo ganhou recursos adicionais e se tornou uma linguagem de programação completa. + +Apesar disso, ainda funciona como uma linguagem de template. Em um arquivo PHP, pode haver uma página HTML escrita, na qual variáveis são exibidas usando ``, etc. + +Já nos primórdios da história do PHP, surgiu o sistema de templates Smarty, cujo propósito era separar estritamente a aparência (HTML/CSS) da lógica da aplicação. Ou seja, ele fornecia intencionalmente uma linguagem mais limitada que o próprio PHP, para que o desenvolvedor não pudesse, por exemplo, fazer uma consulta ao banco de dados a partir do template, etc. Por outro lado, representava uma dependência adicional nos projetos, aumentava sua complexidade e os programadores tinham que aprender a nova linguagem Smarty. Tal benefício era questionável e o PHP simples continuou a ser usado para templates. + +Com o tempo, os sistemas de templates começaram a se tornar úteis. Eles introduziram o conceito de [herança |template-inheritance], [modo sandbox |sandbox] e uma série de outros recursos que simplificaram significativamente a criação de templates em comparação com o PHP puro. O tema da segurança ganhou destaque, a existência de [vulnerabilidades como XSS |safety-first] e a necessidade de [escaping |#O que é escaping]. Os sistemas de templates introduziram o autoescaping para eliminar o risco de o programador esquecer e criar uma séria falha de segurança (em breve mostraremos que isso tem certas armadilhas). + +Os benefícios dos sistemas de templates hoje superam significativamente os custos associados à sua implementação. Portanto, faz sentido usá-los. + + +Por que Latte é melhor que Twig ou Blade? +----------------------------------------- + +Existem várias razões - algumas são agradáveis e outras fundamentalmente úteis. Latte é uma combinação do agradável com o útil. + +*Primeiro, o agradável:* Latte tem a mesma [sintaxe do PHP |syntax#Latte entende PHP]. Apenas a notação das tags difere, em vez de ``, prefere os mais curtos `{` e `}`. Isso significa que você não precisa aprender uma nova linguagem. Os custos de treinamento são mínimos. E, principalmente, durante o desenvolvimento, você não precisa constantemente "alternar" entre a linguagem PHP e a linguagem do template, pois ambas são iguais. Ao contrário dos templates Twig, que usam a linguagem Python, e o programador precisa alternar entre duas linguagens diferentes. + +*E agora a razão extremamente útil*: Todos os sistemas de templates, como Twig, Blade ou Smarty, evoluíram para incluir proteção contra XSS na forma de [escaping |#O que é escaping] automático. Mais precisamente, a chamada automática da função `htmlspecialchars()`. No entanto, os criadores do Latte perceberam que essa não era a solução correta. Porque em diferentes partes do documento, o escaping é feito de maneiras diferentes. O autoescaping ingênuo é uma função perigosa porque cria uma falsa sensação de segurança. + +Para que o autoescaping seja funcional e confiável, ele deve reconhecer em qual parte do documento os dados estão sendo exibidos (chamamos de contextos) e escolher a função de escaping de acordo. Ou seja, deve ser [sensível ao contexto |safety-first#Escaping sensível ao contexto]. E é exatamente isso que o Latte faz. Ele entende HTML. Ele não vê o template apenas como uma sequência de caracteres, mas entende o que são tags, atributos, etc. E, portanto, escapa de forma diferente no texto HTML, dentro de uma tag HTML, dentro de JavaScript, etc. + +Latte é o primeiro e único sistema de templates em PHP que possui escaping sensível ao contexto. Representa, assim, o único sistema de templates verdadeiramente seguro. + +*E mais uma razão agradável*: Graças ao fato de que o Latte entende HTML, ele oferece outros recursos muito agradáveis. Por exemplo, [n:atributos |syntax#n:atributos]. Ou a capacidade de [verificar links |safety-first#Verificação de links]. E muitos outros. + + +O que é escaping? +----------------- + +Escaping é o processo que consiste em substituir caracteres com significado especial pelas sequências correspondentes ao inserir uma string em outra, para evitar fenômenos indesejados ou erros. Por exemplo, ao inserir uma string em texto HTML, onde o caractere `<` tem um significado especial, pois indica o início de uma tag, substituímo-lo pela sequência correspondente, que é a entidade HTML `<`. Graças a isso, o navegador exibirá corretamente o símbolo `<`. + +Um exemplo simples de escaping diretamente ao escrever código em PHP é inserir aspas em uma string, precedendo-as com uma barra invertida. + +Discutimos o escaping em mais detalhes no capítulo [Como se defender de XSS |safety-first#Como se defender do XSS]. + + +É possível fazer uma consulta ao banco de dados a partir de um template Latte? +------------------------------------------------------------------------------ + +Nos templates, é possível trabalhar com objetos que o programador passa para eles. Portanto, se o programador quiser, ele pode passar um objeto de banco de dados para o template e executar uma consulta nele. Se essa for a intenção, não há razão para impedi-lo. + +Uma situação diferente surge se você quiser dar a possibilidade de editar templates a clientes ou codificadores externos. Nesse caso, você definitivamente não quer que eles tenham acesso ao banco de dados. Claro, você não passará o objeto do banco de dados para o template, mas e se for possível acessá-lo através de outro objeto? A solução é o [modo sandbox |sandbox], que permite definir quais métodos podem ser chamados nos templates. Graças a isso, você não precisa se preocupar com violações de segurança. + + +Quais são as principais diferenças entre sistemas de templates como Latte, Twig e Blade? +---------------------------------------------------------------------------------------- + +As diferenças entre os sistemas de templates Latte, Twig e Blade residem principalmente na sintaxe, segurança e método de integração em frameworks. + +- Latte: usa a sintaxe da linguagem PHP, o que facilita o aprendizado e o uso. Fornece proteção de ponta contra ataques XSS. +- Twig: usa a sintaxe da linguagem Python, que difere bastante do PHP. Escapa sem distinguir o contexto. Está bem integrado ao framework Symfony. +- Blade: usa uma mistura de PHP e sintaxe própria. Escapa sem distinguir o contexto. Está intimamente integrado com as funções e o ecossistema do Laravel. + + +Vale a pena para as empresas usar um sistema de templates? +---------------------------------------------------------- + +Primeiramente, os custos associados ao treinamento, uso e benefício geral variam significativamente dependendo do sistema. O sistema de templates Latte, por usar a sintaxe PHP, simplifica muito o aprendizado para programadores já familiarizados com essa linguagem. Geralmente, leva algumas horas para um programador se familiarizar suficientemente com o Latte. Reduz, portanto, os custos de treinamento. Ao mesmo tempo, acelera a adoção da tecnologia e, principalmente, a eficiência no uso diário. + +Além disso, o Latte fornece um alto nível de proteção contra a vulnerabilidade XSS graças à tecnologia única de escaping sensível ao contexto. Essa proteção é crucial para garantir a segurança das aplicações web e minimizar o risco de ataques que poderiam comprometer usuários ou dados da empresa. A proteção da segurança das aplicações web também é importante para manter a boa reputação da empresa. Problemas de segurança podem causar perda de confiança por parte dos clientes e prejudicar a reputação da empresa no mercado. + +O uso do Latte também reduz os custos gerais de desenvolvimento e manutenção da aplicação, facilitando ambos. Portanto, o uso de um sistema de templates definitivamente vale a pena. + + +O Latte afeta o desempenho das aplicações web? +---------------------------------------------- + +Embora os templates Latte sejam processados rapidamente, esse aspecto na verdade não importa. A razão é que a análise dos arquivos ocorre apenas uma vez na primeira exibição. Posteriormente, eles são compilados em código PHP, armazenados em disco e executados a cada requisição subsequente, sem a necessidade de recompilação. + +Esta é a forma como funciona em um ambiente de produção. Durante o desenvolvimento, os templates Latte são recompilados toda vez que seu conteúdo é alterado, para que o desenvolvedor veja sempre a versão atual. diff --git a/latte/ro/@home.texy b/latte/ro/@home.texy index 0287108e3f..91300fce58 100644 --- a/latte/ro/@home.texy +++ b/latte/ro/@home.texy @@ -1 +1,2 @@ -{{titlu principal: Latte - Cele mai sigure și cu adevărat intuitive șabloane pentru PHP}}. +{{maintitle: Latte – cel mai sigur & cu adevărat intuitiv sistem de șabloane pentru PHP}} +{{description: Latte este cel mai sigur sistem de șabloane pentru PHP. Previne o mulțime de vulnerabilități de securitate. Veți aprecia sintaxa sa intuitivă și veți aprecia o mulțime de caracteristici utile.}} diff --git a/latte/ro/@left-menu.texy b/latte/ro/@left-menu.texy index 4caf1f8632..fe1a1af63f 100644 --- a/latte/ro/@left-menu.texy +++ b/latte/ro/@left-menu.texy @@ -1,24 +1,24 @@ -- [Noțiuni introductive |Guide] -- Concepte - - [Siguranța mai întâi |Safety First] - - [Moștenirea șablonului |Template Inheritance] - - [Sistemul de tipuri |Type System] +- [Începeți cu Latte |guide] +- [De ce să folosiți șabloane? |why-use] +- Concepte ⚗️ + - [Securitatea înainte de toate |safety-first] + - [Moștenirea șabloanelor |Template Inheritance] + - [Sistemul de tipuri |type-system] - [Sandbox |Sandbox] -- Pentru proiectanți - - [Sintaxa |Syntax] - - [Etichete |Tags] - - [Filtre |Filters] - - [Funcții |Functions] +- Pentru designeri 🎨 + - [Sintaxă |syntax] + - [Tag-uri |tags] + - [Filtre |filters] + - [Funcții |functions] - [Sfaturi și trucuri |recipes] -- Pentru dezvoltatori - - [Practici pentru dezvoltatori |develop] - - [Extinderea Latte |Extending Latte] - - [Crearea unei extensii |creating-extension] +- Pentru dezvoltatori 🧮 + - [Proceduri de dezvoltare |develop] + - [Extinderea Latte |extending-latte] -- [Carte de bucate |cookbook/@home] +- [Tutoriale și proceduri 💡|cookbook/@home] - [Migrarea de la Twig |cookbook/migration-from-twig] - - [... mai mult |cookbook/@home] + - [… altele |cookbook/@home] -- "Loc de joacă .[link-external]":https://fiddle.nette.org/latte/ .{padding-top:1em} +- "Teren de joacă .[link-external]":https://fiddle.nette.org/latte/ .{padding-top:1em} diff --git a/latte/ro/@menu.texy b/latte/ro/@menu.texy index 792ad89c64..2f4bc14d04 100644 --- a/latte/ro/@menu.texy +++ b/latte/ro/@menu.texy @@ -1,12 +1,12 @@
                                                                                                      -- [Acasă |@home] -- [Documentație |Guide] +- [Introducere |@home] +- [Documentație |guide] - "GitHub .[link-external]":https://github.com/nette/latte
                                                                                                    diff --git a/latte/ro/@meta.texy b/latte/ro/@meta.texy new file mode 100644 index 0000000000..3130122e97 --- /dev/null +++ b/latte/ro/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentație Latte}} diff --git a/latte/ro/compiler-passes.texy b/latte/ro/compiler-passes.texy new file mode 100644 index 0000000000..f7618ce7ed --- /dev/null +++ b/latte/ro/compiler-passes.texy @@ -0,0 +1,555 @@ +Treceri de compilare +******************** + +.[perex] +Trecerile de compilare oferă un mecanism puternic pentru analiza și modificarea șabloanelor Latte *după* ce acestea sunt parsate într-un arbore sintactic abstract (AST) și *înainte* de generarea codului PHP final. Acest lucru permite manipularea avansată a șabloanelor, optimizări, verificări de securitate (cum ar fi Sandbox) și colectarea de informații despre șabloane. Acest ghid vă va îndruma în crearea propriilor treceri de compilare. + + +Ce este o trecere de compilare? +=============================== + +Pentru a înțelege rolul trecerilor de compilare, consultați [procesul de compilare Latte |custom-tags#Înțelegerea procesului de compilare]. După cum puteți vedea, trecerile de compilare operează într-o fază cheie, permițând o intervenție profundă între parsarea inițială și ieșirea finală a codului. + +În esență, o trecere de compilare este pur și simplu un obiect PHP apelabil (cum ar fi o funcție, o metodă statică sau o metodă de instanță) care acceptă un singur argument: nodul rădăcină al AST-ului șablonului, care este întotdeauna o instanță a `Latte\Compiler\Nodes\TemplateNode`. + +Scopul principal al unei treceri de compilare este de obicei unul sau ambele dintre următoarele: + +- Analiză: Parcurgerea AST-ului și colectarea de informații despre șablon (de exemplu, găsirea tuturor blocurilor definite, verificarea utilizării unor tag-uri specifice, asigurarea îndeplinirii anumitor restricții de securitate). +- Modificare: Schimbarea structurii AST-ului sau a atributelor nodurilor (de exemplu, adăugarea automată a atributelor HTML, optimizarea anumitor combinații de tag-uri, înlocuirea tag-urilor depreciate cu altele noi, implementarea regulilor sandbox). + + +Înregistrare +============ + +Trecerile de compilare sunt înregistrate folosind metoda [extensiei |extending-latte#getPasses] `getPasses()`. Această metodă returnează un array asociativ unde cheile sunt nume unice ale trecerilor (utilizate intern și pentru sortare) iar valorile sunt obiecte PHP apelabile care implementează logica trecerii. + +```php +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Extension; + +class MyExtension extends Extension +{ + public function getPasses(): array + { + return [ + 'modificationPass' => $this->modifyTemplateAst(...), + // ... alte treceri ... + ]; + } + + public function modifyTemplateAst(TemplateNode $templateNode): void + { + // Implementare... + } +} +``` + +Trecerile înregistrate de extensiile de bază Latte și extensiile dvs. proprii rulează secvențial. Ordinea poate fi importantă, mai ales dacă o trecere depinde de rezultatele sau modificările alteia. Latte oferă un mecanism auxiliar pentru controlul acestei ordini, dacă este necesar; consultați documentația pentru [`Extension::getPasses()` |extending-latte#getPasses] pentru detalii. + + +Exemplu AST +=========== + +Pentru o mai bună înțelegere a AST-ului, adăugăm un exemplu. Acesta este șablonul sursă: + +```latte +{foreach $category->getItems() as $item} +
                                                                                                  • {$item->name|upper}
                                                                                                  • + {else} + nu au fost găsite elemente +{/foreach} +``` + +Și aceasta este reprezentarea sa sub formă de AST: + +/--pre +Latte\Compiler\Nodes\TemplateNode( + Latte\Compiler\Nodes\FragmentNode( + - Latte\Essential\Nodes\ForeachNode( + expression: Latte\Compiler\Nodes\Php\Expression\MethodCallNode( + object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$category') + name: Latte\Compiler\Nodes\Php\IdentifierNode('getItems') + ) + value: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') + content: Latte\Compiler\Nodes\FragmentNode( + - Latte\Compiler\Nodes\TextNode(' ') + - Latte\Compiler\Nodes\Html\ElementNode('li')( + content: Latte\Essential\Nodes\PrintNode( + expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode( + object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') + name: Latte\Compiler\Nodes\Php\IdentifierNode('name') + ) + modifier: Latte\Compiler\Nodes\Php\ModifierNode( + filters: + - Latte\Compiler\Nodes\Php\FilterNode('upper') + ) + ) + ) + ) + else: Latte\Compiler\Nodes\FragmentNode( + - Latte\Compiler\Nodes\TextNode('nu au fost găsite elemente') + ) + ) + ) +) +\-- + + +Parcurgerea AST folosind `NodeTraverser` +======================================== + +Scrierea manuală a funcțiilor recursive pentru parcurgerea structurii complexe a AST-ului este obositoare și predispusă la erori. Latte oferă un instrument special pentru acest scop: [api:Latte\Compiler\NodeTraverser]. Această clasă implementează [pattern-ul de design Visitor |https://en.wikipedia.org/wiki/Visitor_pattern], datorită căruia parcurgerea AST-ului este sistematică și ușor de gestionat. + +Utilizarea de bază implică crearea unei instanțe `NodeTraverser` și apelarea metodei sale `traverse()`, transmițând nodul rădăcină al AST-ului și unul sau doi „visitor” apelabili: + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; + +(new NodeTraverser)->traverse( + $templateNode, + + // 'enter' visitor: Apelat la intrarea în nod (înainte de copiii săi) + enter: function (Node $node) { + echo "Intrare în nod de tip: " . $node::class . "\n"; + // Aici puteți examina nodul + if ($node instanceof Nodes\TextNode) { + // echo "Text găsit: " . $node->content . "\n"; + } + }, + + // 'leave' visitor: Apelat la părăsirea nodului (după copiii săi) + leave: function (Node $node) { + echo "Părăsire nod de tip: " . $node::class . "\n"; + // Aici puteți efectua acțiuni după procesarea copiilor + }, +); +``` + +Puteți furniza doar visitor-ul `enter`, doar visitor-ul `leave`, sau ambii, în funcție de nevoile dvs. + +**`enter(Node $node)`:** Această funcție este executată pentru fiecare nod **înainte** ca traverser-ul să viziteze oricare dintre copiii acestui nod. Este utilă pentru: + +- Colectarea informațiilor în timpul parcurgerii arborelui în jos. +- Luarea deciziilor *înainte* de procesarea copiilor (cum ar fi decizia de a-i sări, vezi [#Optimizarea parcurgerii]). +- Modificări potențiale ale nodului înainte de vizitarea copiilor (mai puțin frecvent). + +**`leave(Node $node)`:** Această funcție este executată pentru fiecare nod **după** ce toți copiii săi (și subarborii lor întregi) au fost complet vizitați (atât intrare cât și părăsire). Este cel mai frecvent loc pentru: + +Ambii vizitatori `enter` și `leave` pot returna opțional o valoare pentru a influența procesul de parcurgere. Returnarea `null` (sau nimic) continuă parcurgerea normal, returnarea unei instanțe `Node` înlocuiește nodul curent, iar returnarea constantelor speciale precum `NodeTraverser::RemoveNode` sau `NodeTraverser::StopTraversal` modifică fluxul, așa cum este explicat în secțiunile următoare. + + +Cum funcționează parcurgerea +---------------------------- + +`NodeTraverser` utilizează intern metoda `getIterator()`, pe care trebuie să o implementeze fiecare clasă `Node` (așa cum s-a discutat în [Crearea tag-urilor personalizate |custom-tags#Implementarea getIterator pentru sub-noduri]). Iterează peste copiii obținuți prin `getIterator()`, apelează recursiv `traverse()` pe ei și asigură că vizitatorii `enter` și `leave` sunt apelați în ordinea corectă de parcurgere în adâncime pentru fiecare nod din arbore accesibil prin iteratori. Acest lucru subliniază din nou de ce un `getIterator()` implementat corect în nodurile dvs. de tag-uri personalizate este absolut necesar pentru funcționarea corectă a trecerilor de compilare. + +Să scriem o trecere simplă care numără de câte ori este utilizat tag-ul `{do}` (reprezentat de `Latte\Essential\Nodes\DoNode`) în șablon. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Essential\Nodes\DoNode; + +function countDoTags(TemplateNode $templateNode): void +{ + $count = 0; + (new NodeTraverser)->traverse( + $templateNode, + enter: function (Node $node) use (&$count): void { + if ($node instanceof DoNode) { + $count++; + } + }, + // visitor-ul 'leave' nu este necesar pentru această sarcină + ); + + echo "Tag-ul {do} a fost găsit de $count ori.\n"; +} + +$latte = new Latte\Engine; +$ast = $latte->parse($templateSource); +countDoTags($ast); +``` + +În acest exemplu, am avut nevoie doar de visitor-ul `enter` pentru a verifica tipul fiecărui nod vizitat. + +În continuare, vom explora cum acești vizitatori modifică efectiv AST-ul. + + +Modificarea AST-ului +==================== + +Unul dintre scopurile principale ale trecerilor de compilare este modificarea arborelui sintactic abstract. Acest lucru permite transformări puternice, optimizări sau impunerea regulilor direct asupra structurii șablonului înainte de generarea codului PHP. `NodeTraverser` oferă mai multe moduri de a realiza acest lucru în cadrul vizitatorilor `enter` și `leave`. + +**Notă importantă:** Modificarea AST-ului necesită prudență. Modificările incorecte – cum ar fi eliminarea nodurilor esențiale sau înlocuirea unui nod cu un tip incompatibil – pot duce la erori în timpul generării codului sau pot cauza un comportament neașteptat în timpul rulării programului. Testați întotdeauna temeinic trecerile dvs. de modificare. + + +Modificarea proprietăților nodurilor +------------------------------------ + +Cel mai simplu mod de a modifica arborele este schimbarea directă a **proprietăților publice** ale nodurilor vizitate în timpul parcurgerii. Toate nodurile stochează argumentele, conținutul sau atributele parsate în proprietăți publice. + +**Exemplu:** Să creăm o trecere care găsește toate nodurile de text static (`TextNode`, reprezentând HTML obișnuit sau text în afara tag-urilor Latte) și convertește conținutul lor în majuscule *direct în AST*. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\TextNode; + +function uppercaseStaticText(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // Putem folosi 'enter', deoarece TextNode nu are copii de procesat + enter: function (Node $node) { + // Este acest nod un bloc de text static? + if ($node instanceof TextNode) { + // Da! Modificăm direct proprietatea sa publică 'content'. + $node->content = mb_strtoupper(html_entity_decode($node->content)); + } + // Nu este nevoie să returnăm nimic; modificarea este aplicată direct. + }, + ); +} +``` + +În acest exemplu, visitor-ul `enter` verifică dacă `$node`-ul curent este de tip `TextNode`. Dacă da, actualizăm direct proprietatea sa publică `$content` folosind `mb_strtoupper()`. Acest lucru modifică direct conținutul textului static stocat în AST *înainte* de generarea codului PHP. Deoarece modificăm obiectul direct, nu trebuie să returnăm nimic din visitor. + +Efect: Dacă șablonul conținea `

                                                                                                    Hello

                                                                                                    {= $var }World`, după această trecere, AST-ul va reprezenta ceva de genul: `

                                                                                                    HELLO

                                                                                                    {= $var }WORLD`. Acest lucru NU VA AFECTA conținutul `$var`. + + +Înlocuirea nodurilor +-------------------- + +O tehnică de modificare mai puternică este înlocuirea completă a unui nod cu altul. Acest lucru se realizează prin **returnarea unei noi instanțe `Node`** din visitor-ul `enter` sau `leave`. `NodeTraverser` înlocuiește apoi nodul original cu cel returnat în structura nodului părinte. + +**Exemplu:** Să creăm o trecere care găsește toate utilizările constantei `PHP_VERSION` (reprezentate de `ConstantFetchNode`) și le înlocuiește direct cu un literal de șir (`StringNode`) conținând versiunea PHP *reală* detectată *în timpul compilării*. Aceasta este o formă de optimizare în timpul compilării. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\Php\Expression\ConstantFetchNode; +use Latte\Compiler\Nodes\Php\Scalar\StringNode; + +function inlinePhpVersion(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // 'leave' este adesea folosit pentru înlocuire, asigurând că copiii (dacă există) + // sunt procesați mai întâi, deși și 'enter' ar funcționa aici. + leave: function (Node $node) { + // Este acest nod un acces la constantă și numele constantei este 'PHP_VERSION'? + if ($node instanceof ConstantFetchNode && (string) $node->name === 'PHP_VERSION') { + // Creăm un nou StringNode conținând versiunea PHP curentă + $newNode = new StringNode(PHP_VERSION); + + // Opțional, dar bună practică: copiem informațiile despre poziție + $newNode->position = $node->position; + + // Returnăm noul StringNode. Traverser-ul va înlocui + // ConstantFetchNode original cu acest $newNode. + return $newNode; + } + // Dacă nu returnăm Node, $node-ul original este păstrat. + }, + ); +} +``` + +Aici, visitor-ul `leave` identifică `ConstantFetchNode` specific pentru `PHP_VERSION`. Apoi creează un `StringNode` complet nou conținând valoarea constantei `PHP_VERSION` *în timpul compilării*. Returnând acest `$newNode`, îi spune traverser-ului să înlocuiască `ConstantFetchNode` original în AST. + +Efect: Dacă șablonul conținea `{= PHP_VERSION }` și compilarea rulează pe PHP 8.2.1, AST-ul după această trecere va reprezenta efectiv `{= '8.2.1' }`. + +**Alegerea `enter` vs. `leave` pentru înlocuire:** + +- Utilizați `leave` dacă crearea noului nod depinde de rezultatele procesării copiilor nodului vechi, sau dacă doriți pur și simplu să vă asigurați că copiii sunt vizitați înainte de înlocuire (practică comună). +- Utilizați `enter` dacă doriți să înlocuiți un nod *înainte* ca copiii săi să fie vizitați. + + +Eliminarea nodurilor +-------------------- + +Puteți elimina complet un nod din AST returnând constanta specială `NodeTraverser::RemoveNode` din visitor. + +**Exemplu:** Să eliminăm toate comentariile șablonului (`{* ... *}`), care sunt reprezentate de `CommentNode` în AST-ul generat de nucleul Latte (deși de obicei procesate mai devreme, acest lucru servește ca exemplu). + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\CommentNode; + +function removeCommentNodes(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // 'enter' este în regulă aici, deoarece nu avem nevoie de informații despre copii pentru a elimina comentariul + enter: function (Node $node) { + if ($node instanceof CommentNode) { + // Semnalăm traverser-ului să elimine acest nod din AST + return NodeTraverser::RemoveNode; + } + }, + ); +} +``` + +**Atenție:** Utilizați `RemoveNode` cu prudență. Eliminarea unui nod care conține conținut esențial sau afectează structura (cum ar fi eliminarea nodului de conținut al unui ciclu) poate duce la șabloane corupte sau cod generat invalid. Este cel mai sigur pentru nodurile care sunt cu adevărat opționale sau autonome (cum ar fi comentariile sau tag-urile de depanare) sau pentru nodurile structurale goale (de exemplu, un `FragmentNode` gol poate fi eliminat în siguranță în unele contexte printr-o trecere de curățare). + +Aceste trei metode - modificarea proprietăților, înlocuirea nodurilor și eliminarea nodurilor - oferă instrumentele de bază pentru manipularea AST-ului în cadrul trecerilor dvs. de compilare. + + +Optimizarea parcurgerii +======================= + +AST-ul șabloanelor poate fi destul de mare, conținând potențial mii de noduri. Parcurgerea fiecărui nod individual poate fi inutilă și poate afecta performanța compilării dacă trecerea dvs. este interesată doar de părți specifice ale arborelui. `NodeTraverser` oferă modalități de optimizare a parcurgerii: + + +Sărirea peste copii +------------------- + +Dacă știți că, odată ce întâlniți un anumit tip de nod, niciunul dintre descendenții săi nu poate conține nodurile pe care le căutați, puteți spune traverser-ului să sară peste vizitarea copiilor săi. Acest lucru se realizează returnând constanta `NodeTraverser::DontTraverseChildren` din visitor-ul **`enter`**. Astfel, veți omite ramuri întregi în timpul parcurgerii, economisind potențial timp considerabil, în special în șabloanele cu expresii PHP complexe în interiorul tag-urilor. + + +Oprirea parcurgerii +------------------- + +Dacă trecerea dvs. trebuie să găsească doar *prima* apariție a ceva (un tip specific de nod, îndeplinirea unei condiții), puteți opri complet întregul proces de parcurgere odată ce ați găsit-o. Acest lucru se realizează returnând constanta `NodeTraverser::StopTraversal` din visitor-ul `enter` sau `leave`. Metoda `traverse()` va înceta să viziteze orice alte noduri. Acest lucru este extrem de eficient dacă aveți nevoie doar de prima potrivire într-un arbore potențial very mare. + + +Ajutor util `NodeHelpers` +========================= + +În timp ce `NodeTraverser` oferă un control fin, Latte oferă, de asemenea, o clasă ajutătoare practică, [api:Latte\Compiler\NodeHelpers], care încapsulează `NodeTraverser` pentru mai multe sarcini comune de căutare și analiză, necesitând adesea mai puțin cod de pregătire. + + +find(Node $startNode, callable $filter): array .[method] +-------------------------------------------------------- + +Această metodă statică găsește **toate** nodurile din subarborele care începe la `$startNode` (inclusiv), care îndeplinesc callback-ul `$filter`. Returnează un array de noduri potrivite. + +**Exemplu:** Găsiți toate nodurile de variabile (`VariableNode`) din întregul șablon. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Php\Expression\VariableNode; +use Latte\Compiler\Nodes\TemplateNode; + +function findAllVariables(TemplateNode $templateNode): array +{ + return NodeHelpers::find( + $templateNode, + fn($node) => $node instanceof VariableNode, + ); +} +``` + + +findFirst(Node $startNode, callable $filter): ?Node .[method] +-------------------------------------------------------------- + +Similar cu `find`, dar oprește parcurgerea imediat după găsirea **primului** nod care îndeplinește callback-ul `$filter`. Returnează obiectul `Node` găsit sau `null` dacă nu este găsit niciun nod potrivit. Acesta este în esență un wrapper practic în jurul `NodeTraverser::StopTraversal`. + +**Exemplu:** Găsiți nodul `{parameters}` (la fel ca exemplul manual anterior, dar mai scurt). + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Essential\Nodes\ParametersNode; + +function findParametersNodeHelper(TemplateNode $templateNode): ?ParametersNode +{ + return NodeHelpers::findFirst( + $templateNode->head, // Căutați doar în secțiunea principală pentru eficiență + fn($node) => $node instanceof ParametersNode, + ); +} +``` + + +toValue(ExpressionNode $node, bool $constants = false): mixed .[method] +----------------------------------------------------------------------- + +Această metodă statică încearcă să evalueze `ExpressionNode` **în timpul compilării** și să returneze valoarea sa PHP corespunzătoare. Funcționează fiabil doar pentru noduri literale simple (`StringNode`, `IntegerNode`, `FloatNode`, `BooleanNode`, `NullNode`) și instanțe `ArrayNode` care conțin doar astfel de elemente evaluabile. + +Dacă `$constants` este setat la `true`, va încerca, de asemenea, să rezolve `ConstantFetchNode` și `ClassConstantFetchNode` verificând `defined()` și folosind `constant()`. + +Dacă nodul conține variabile, apeluri de funcții sau alte elemente dinamice, nu poate fi evaluat în timpul compilării și metoda va arunca `InvalidArgumentException`. + +**Caz de utilizare:** Obținerea valorii statice a unui argument de tag în timpul compilării pentru luarea deciziilor în timpul compilării. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Php\ExpressionNode; + +function getStaticStringArgument(ExpressionNode $argumentNode): ?string +{ + try { + $value = NodeHelpers::toValue($argumentNode); + return is_string($value) ? $value : null; + } catch (\InvalidArgumentException $e) { + // Argumentul nu a fost un șir literal static + return null; + } +} +``` + + +toText(?Node $node): ?string .[method] +-------------------------------------- + +Această metodă statică este utilă pentru extragerea conținutului text simplu din noduri simple. Funcționează în principal cu: +- `TextNode`: Returnează `$content`-ul său. +- `FragmentNode`: Concatenează rezultatul `toText()` pentru toți copiii săi. Dacă un copil nu este convertibil în text (de exemplu, conține `PrintNode`), returnează `null`. +- `NopNode`: Returnează un șir gol. +- Alte tipuri de noduri: Returnează `null`. + +**Caz de utilizare:** Obținerea conținutului text static al valorii unui atribut HTML sau al unui element HTML simplu pentru analiză în timpul unei treceri de compilare. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Html\AttributeNode; + +function getStaticAttributeValue(AttributeNode $attr): ?string +{ + // $attr->value este de obicei AreaNode (cum ar fi FragmentNode sau TextNode) + return NodeHelpers::toText($attr->value); +} + +// Exemplu de utilizare într-o trecere: +// if ($node instanceof Html\ElementNode && $node->name === 'meta') { +// $nameAttrValue = getStaticAttributeValue($node->getAttributeNode('name')); +// if ($nameAttrValue === 'description') { ... } +// } +``` + +`NodeHelpers` poate simplifica trecerile dvs. de compilare oferind soluții gata făcute pentru sarcini comune de parcurgere și analiză AST. + + +Exemple practice +================ + +Să aplicăm conceptele de parcurgere și modificare AST pentru a rezolva câteva probleme practice. Aceste exemple demonstrează pattern-uri comune utilizate în trecerile de compilare. + + +Adăugarea automată a `loading="lazy"` la `` +------------------------------------------------ + +Browserele moderne suportă încărcarea leneșă nativă pentru imagini folosind atributul `loading="lazy"`. Să creăm o trecere care adaugă automat acest atribut la toate tag-urile `` care nu au încă atributul `loading`. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; +use Latte\Compiler\Nodes\Html; + +function addLazyLoading(Nodes\TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // Putem folosi 'enter', deoarece modificăm nodul direct + // și nu depindem de copii pentru această decizie. + enter: function (Node $node) { + // Este un element HTML cu numele 'img'? + if ($node instanceof Html\ElementNode && $node->name === 'img') { + // Asigurăm că nodul de atribute există + $node->attributes ??= new Nodes\FragmentNode; + + // Verificăm dacă există deja un atribut 'loading' (indiferent de majuscule/minuscule) + foreach ($node->attributes->children as $attrNode) { + if ($attrNode instanceof Html\AttributeNode + && $attrNode->name instanceof Nodes\TextNode // Nume static de atribut + && strtolower($attrNode->name->content) === 'loading' + ) { + return; // Atributul există deja, nu facem nimic + } + } + + // Adăugăm un spațiu dacă atributele nu sunt goale + if ($node->attributes->children) { + $node->attributes->children[] = new Nodes\TextNode(' '); + } + + // Creăm un nou nod de atribut: loading="lazy" + $node->attributes->children[] = new Html\AttributeNode( + name: new Nodes\TextNode('loading'), + value: new Nodes\TextNode('lazy'), + quote: '"', + ); + // Modificarea este aplicată direct în obiect, nu este nevoie să returnăm nimic. + } + }, + ); +} +``` + +Explicație: +- Visitor-ul `enter` caută noduri `Html\ElementNode` cu numele `img`. +- Iterează peste atributele existente (`$node->attributes->children`) și verifică dacă atributul `loading` este deja prezent. +- Dacă nu este găsit, creează un nou `Html\AttributeNode` reprezentând `loading="lazy"`. + + +Verificarea apelurilor de funcții +--------------------------------- + +Trecerile de compilare stau la baza Latte Sandbox. Deși Sandbox-ul real este sofisticat, putem demonstra principiul de bază al verificării apelurilor de funcții interzise. + +**Scop:** Prevenirea utilizării funcției potențial periculoase `shell_exec` în cadrul expresiilor șablonului. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; +use Latte\Compiler\Nodes\Php; +use Latte\SecurityViolationException; + +function checkForbiddenFunctions(Nodes\TemplateNode $templateNode): void +{ + $forbiddenFunctions = ['shell_exec' => true, 'exec' => true]; // Listă simplă + + $traverser = new NodeTraverser; + (new NodeTraverser)->traverse( + $templateNode, + enter: function (Node $node) use ($forbiddenFunctions) { + // Este un nod de apel direct de funcție? + if ($node instanceof Php\Expression\FunctionCallNode + && $node->name instanceof Php\NameNode + && isset($forbiddenFunctions[strtolower((string) $node->name)]) + ) { + throw new SecurityViolationException( + "Funcția {$node->name}() nu este permisă.", + $node->position, + ); + } + }, + ); +} +``` + +Explicație: +- Definim o listă de nume de funcții interzise. +- Visitor-ul `enter` verifică `FunctionCallNode`. +- Dacă numele funcției (`$node->name`) este un `NameNode` static, verificăm reprezentarea sa de șir în minuscule față de lista noastră interzisă. +- Dacă este găsită o funcție interzisă, aruncăm `Latte\SecurityViolationException`, care indică clar încălcarea regulii de securitate și oprește compilarea. + +Aceste exemple arată cum trecerile de compilare, folosind `NodeTraverser`, pot fi utilizate pentru analiză, modificări automate și impunerea restricțiilor de securitate prin interacțiunea directă cu structura AST a șablonului. + + +Cele mai bune practici +====================== + +La scrierea trecerilor de compilare, țineți cont de aceste linii directoare pentru a crea extensii robuste, mentenabile și eficiente: + +- **Ordinea contează:** Fiți conștienți de ordinea în care rulează trecerile. Dacă trecerea dvs. depinde de structura AST creată de o altă trecere (de exemplu, trecerile de bază Latte sau o altă trecere personalizată), sau dacă alte treceri pot depinde de modificările dvs., utilizați mecanismul de sortare furnizat de `Extension::getPasses()` pentru a defini dependențele (`before`/`after`). Consultați documentația pentru [`Extension::getPasses()` |extending-latte#getPasses] pentru detalii. +- **O singură responsabilitate:** Încercați să aveți treceri care efectuează o singură sarcină bine definită. Pentru transformări complexe, luați în considerare împărțirea logicii în mai multe treceri – poate una pentru analiză și alta pentru modificare bazată pe rezultatele analizei. Acest lucru îmbunătățește claritatea și testabilitatea. +- **Performanță:** Amintiți-vă că trecerile de compilare adaugă timp la compilarea șablonului (deși acest lucru se întâmplă de obicei o singură dată, până când șablonul se schimbă). Evitați operațiunile costisitoare din punct de vedere computațional în trecerile dvs., dacă este posibil. Utilizați optimizări de parcurgere precum `NodeTraverser::DontTraverseChildren` și `NodeTraverser::StopTraversal` ori de câte ori știți că nu trebuie să vizitați anumite părți ale AST-ului. +- **Utilizați `NodeHelpers`:** Pentru sarcini comune precum găsirea unor noduri specifice sau evaluarea statică a expresiilor simple, verificați dacă `Latte\Compiler\NodeHelpers` nu oferă o metodă potrivită înainte de a scrie propria logică `NodeTraverser`. Acest lucru poate economisi timp și reduce cantitatea de cod de pregătire. +- **Gestionarea erorilor:** Dacă trecerea dvs. detectează o eroare sau o stare invalidă în AST-ul șablonului, aruncați `Latte\CompileException` (sau `Latte\SecurityViolationException` pentru probleme de securitate) cu un mesaj clar și obiectul `Position` relevant (de obicei `$node->position`). Acest lucru oferă feedback util dezvoltatorului șablonului. +- **Idempotență (dacă este posibil):** Ideal, rularea trecerii dvs. de mai multe ori pe același AST ar trebui să producă același rezultat ca și rularea sa o singură dată. Acest lucru nu este întotdeauna fezabil, dar simplifică depanarea și raționamentul despre interacțiunile trecerilor, dacă este realizat. De exemplu, asigurați-vă că trecerea dvs. de modificare verifică dacă modificarea a fost deja aplicată înainte de a o aplica din nou. + +Urmând aceste practici, puteți utiliza eficient trecerile de compilare pentru a extinde capacitățile Latte într-un mod puternic și fiabil, contribuind la o procesare a șabloanelor mai sigură, optimizată sau mai bogată funcțional. diff --git a/latte/ro/cookbook/@home.texy b/latte/ro/cookbook/@home.texy index 6eaa0a78b2..f60f9b890a 100644 --- a/latte/ro/cookbook/@home.texy +++ b/latte/ro/cookbook/@home.texy @@ -1,13 +1,13 @@ -Carte de bucate -*************** +Tutoriale și proceduri +********************** .[perex] -Exemple de coduri și rețete pentru realizarea unor sarcini obișnuite cu Latte. +Exemple de cod și rețete pentru efectuarea sarcinilor comune folosind Latte. -- [Tot ce ați vrut întotdeauna să știți despre {iterateWhile} |iteratewhile] +- [Proceduri pentru dezvoltatori |/develop] +- [Transmiterea variabilelor între șabloane |passing-variables] +- [Tot ce ați dorit vreodată să știți despre grupare |grouping] - [Cum să scrieți interogări SQL în Latte? |how-to-write-sql-queries-in-latte] - [Migrarea de la PHP |migration-from-php] - [Migrarea de la Twig |migration-from-twig] - [Utilizarea Latte cu Slim 4 |slim-framework] - -{{leftbar: /@left-menu}} diff --git a/latte/ro/cookbook/@meta.texy b/latte/ro/cookbook/@meta.texy new file mode 100644 index 0000000000..8339242327 --- /dev/null +++ b/latte/ro/cookbook/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Documentație Latte}} +{{leftbar: /@left-menu}} diff --git a/latte/ro/cookbook/grouping.texy b/latte/ro/cookbook/grouping.texy new file mode 100644 index 0000000000..3a88ef65d1 --- /dev/null +++ b/latte/ro/cookbook/grouping.texy @@ -0,0 +1,251 @@ +Tot ce ați dorit vreodată să știți despre grupare +************************************************* + +.[perex] +Când lucrați cu date în șabloane, puteți întâlni adesea nevoia de a le grupa sau de a le afișa specific în funcție de anumite criterii. Latte oferă în acest scop mai multe instrumente puternice. + +Filtrul și funcția `|group` permit gruparea eficientă a datelor conform criteriului specificat, filtrul `|batch` facilitează împărțirea datelor în loturi fixe, iar tag-ul `{iterateWhile}` oferă posibilitatea unui control mai complex al desfășurării buclelor cu condiții. Fiecare dintre aceste tag-uri oferă opțiuni specifice pentru lucrul cu datele, devenind astfel instrumente indispensabile pentru afișarea dinamică și structurată a informațiilor în șabloanele Latte. + + +Filtrul și funcția `group` .{data-version:3.0.16} +================================================= + +Imaginați-vă un tabel de bază de date `items` cu elemente împărțite în categorii: + +| id | categoryId | name +|------------------ +| 1 | 1 | Apple +| 2 | 1 | Banana +| 3 | 2 | PHP +| 4 | 3 | Green +| 5 | 3 | Red +| 6 | 3 | Blue + +O listă simplă a tuturor elementelor folosind un șablon Latte ar arăta astfel: + +```latte +
                                                                                                      +{foreach $items as $item} +
                                                                                                    • {$item->name}
                                                                                                    • +{/foreach} +
                                                                                                    +``` + +Dacă am dori însă ca elementele să fie aranjate în grupuri după categorie, trebuie să le împărțim astfel încât fiecare categorie să aibă propria listă. Rezultatul ar trebui să arate astfel: + +```latte +
                                                                                                      +
                                                                                                    • Apple
                                                                                                    • +
                                                                                                    • Banana
                                                                                                    • +
                                                                                                    + +
                                                                                                      +
                                                                                                    • PHP
                                                                                                    • +
                                                                                                    + +
                                                                                                      +
                                                                                                    • Green
                                                                                                    • +
                                                                                                    • Red
                                                                                                    • +
                                                                                                    • Blue
                                                                                                    • +
                                                                                                    +``` + +Sarcina poate fi rezolvată ușor și elegant folosind `|group`. Ca parametru specificăm `categoryId`, ceea ce înseamnă că elementele vor fi împărțite în array-uri mai mici în funcție de valoarea `$item->categoryId` (dacă `$item` ar fi un array, s-ar folosi `$item['categoryId']`): + +```latte +{foreach ($items|group: categoryId) as $categoryId => $categoryItems} +
                                                                                                      + {foreach $categoryItems as $item} +
                                                                                                    • {$item->name}
                                                                                                    • + {/foreach} +
                                                                                                    +{/foreach} +``` + +Filtrul poate fi folosit în Latte și ca funcție, ceea ce ne oferă o sintaxă alternativă: `{foreach group($items, categoryId) ...}`. + +Dacă doriți să grupați elementele după criterii mai complexe, puteți folosi o funcție în parametrul filtrului. De exemplu, gruparea elementelor după lungimea numelui ar arăta astfel: + +```latte +{foreach ($items|group: fn($item) => strlen($item->name)) as $items} + ... +{/foreach} +``` + +Este important de reținut că `$categoryItems` nu este un array obișnuit, ci un obiect care se comportă ca un iterator. Pentru a accesa primul element al grupului, puteți utiliza funcția [`first()` |latte:functions#first]. + +Această flexibilitate în gruparea datelor face din `group` un instrument excepțional de util pentru prezentarea datelor în șabloanele Latte. + + +Bucle imbricate +--------------- + +Să ne imaginăm că avem un tabel de bază de date cu o coloană suplimentară `subcategoryId`, care definește subcategoriile elementelor individuale. Dorim să afișăm fiecare categorie principală într-o listă separată `
                                                                                                      ` și fiecare subcategorie într-o listă imbricată separată `
                                                                                                        `: + +```latte +{foreach ($items|group: categoryId) as $categoryItems} +
                                                                                                          + {foreach ($categoryItems|group: subcategoryId) as $subcategoryItems} +
                                                                                                            + {foreach $subcategoryItems as $item} +
                                                                                                          1. {$item->name}
                                                                                                          2. + {/foreach} +
                                                                                                          + {/foreach} +
                                                                                                        +{/foreach} +``` + + +Conexiunea cu Nette Database +---------------------------- + +Să vedem cum să utilizăm eficient gruparea datelor în combinație cu Nette Database. Presupunem că lucrăm cu tabelul `items` din exemplul introductiv, care este legat prin coloana `categoryId` de acest tabel `categories`: + +| categoryId | name | +|------------|------------| +| 1 | Fruits | +| 2 | Languages | +| 3 | Colors | + +Datele din tabelul `items` le încărcăm folosind Nette Database Explorer cu comanda `$items = $db->table('items')`. În timpul iterației peste aceste date, avem posibilitatea de a accesa nu numai atribute precum `$item->name` și `$item->categoryId`, ci, datorită legăturii cu tabelul `categories`, și rândul corespunzător din acesta prin `$item->category`. Pe această legătură se poate demonstra o utilizare interesantă: + +```latte +{foreach ($items|group: category) as $category => $categoryItems} +

                                                                                                        {$category->name}

                                                                                                        +
                                                                                                          + {foreach $categoryItems as $item} +
                                                                                                        • {$item->name}
                                                                                                        • + {/foreach} +
                                                                                                        +{/foreach} +``` + +În acest caz, folosim filtrul `|group` pentru a grupa după rândul legat `$item->category`, nu doar după coloana `categoryId`. Datorită acestui fapt, în variabila cheie `$category` avem direct obiectul `ActiveRow` al categoriei respective, ceea ce ne permite să afișăm direct numele său folosind `{$category->name}`. Acesta este un exemplu practic despre cum gruparea poate clarifica șabloanele și facilita lucrul cu datele. + + +Filtrul `|batch` +================ + +Filtrul permite împărțirea unei liste de elemente în grupuri cu un număr predeterminat de elemente. Acest filtru este ideal pentru situațiile în care doriți să prezentați datele în mai multe grupuri mai mici, de exemplu, pentru o mai bună claritate sau aranjare vizuală pe pagină. + +Să ne imaginăm că avem o listă de elemente și dorim să le afișăm în liste, unde fiecare conține maximum trei elemente. Utilizarea filtrului `|batch` este în acest caz foarte practică: + +```latte +
                                                                                                          +{foreach ($items|batch: 3) as $batch} + {foreach $batch as $item} +
                                                                                                        • {$item->name}
                                                                                                        • + {/foreach} +{/foreach} +
                                                                                                        +``` + +În acest exemplu, lista `$items` este împărțită în grupuri mai mici, fiecare grup (`$batch`) conținând până la trei elemente. Fiecare grup este apoi afișat într-o listă `
                                                                                                          ` separată. + +Dacă ultimul grup nu conține suficiente elemente pentru a atinge numărul dorit, al doilea parametrul al filtrului permite definirea cu ce va fi completat acest grup. Acest lucru este ideal pentru alinierea estetică a elementelor acolo unde un rând incomplet ar putea părea dezordonat. + +```latte +{foreach ($items|batch: 3, '—') as $batch} + ... +{/foreach} +``` + + +Tag-ul `{iterateWhile}` +======================= + +Aceleași sarcini pe care le-am rezolvat cu filtrul `|group` le vom arăta folosind tag-ul `{iterateWhile}`. Diferența principală între cele două abordări este că `group` procesează și grupează mai întâi toate datele de intrare, în timp ce `{iterateWhile}` controlează desfășurarea buclelor cu condiții, astfel încât iterația are loc treptat. + +Mai întâi vom randa tabelul cu categoriile folosind `iterateWhile`: + +```latte +{foreach $items as $item} +
                                                                                                            + {iterateWhile} +
                                                                                                          • {$item->name}
                                                                                                          • + {/iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                                                                                                          +{/foreach} +``` + +În timp ce `{foreach}` marchează partea exterioară a buclei, adică randarea listelor pentru fiecare categorie, tag-ul `{iterateWhile}` marchează partea interioară, adică elementele individuale. Condiția din tag-ul de închidere spune că repetarea va continua atâta timp cât elementul curent și cel următor aparțin aceleiași categorii (`$iterator->nextValue` este [elementul următor |/tags#iterator]). + +Dacă condiția ar fi îndeplinită întotdeauna, atunci în bucla interioară s-ar randa toate elementele: + +```latte +{foreach $items as $item} +
                                                                                                            + {iterateWhile} +
                                                                                                          • {$item->name}
                                                                                                          • + {/iterateWhile true} +
                                                                                                          +{/foreach} +``` + +Rezultatul va arăta astfel: + +```latte +
                                                                                                            +
                                                                                                          • Apple
                                                                                                          • +
                                                                                                          • Banana
                                                                                                          • +
                                                                                                          • PHP
                                                                                                          • +
                                                                                                          • Green
                                                                                                          • +
                                                                                                          • Red
                                                                                                          • +
                                                                                                          • Blue
                                                                                                          • +
                                                                                                          +``` + +La ce este bună o astfel de utilizare a `iterateWhile`? Când tabelul va fi gol și nu va conține niciun element, nu se va afișa un `
                                                                                                            ` gol. + +Dacă specificăm condiția în tag-ul de deschidere `{iterateWhile}`, atunci comportamentul se schimbă: condiția (și trecerea la elementul următor) se execută deja la începutul buclei interioare, nu la sfârșit. Deci, în timp ce în `{iterateWhile}` fără condiție se intră întotdeauna, în `{iterateWhile $cond}` se intră doar la îndeplinirea condiției `$cond`. Și, în același timp, în `$item` se scrie elementul următor. + +Ceea ce este util, de exemplu, în situația în care vom dori să randăm primul element din fiecare categorie într-un mod diferit, de exemplu astfel: + +```latte +

                                                                                                            Apple

                                                                                                            +
                                                                                                              +
                                                                                                            • Banana
                                                                                                            • +
                                                                                                            + +

                                                                                                            PHP

                                                                                                            +
                                                                                                              +
                                                                                                            + +

                                                                                                            Green

                                                                                                            +
                                                                                                              +
                                                                                                            • Red
                                                                                                            • +
                                                                                                            • Blue
                                                                                                            • +
                                                                                                            +``` + +Vom modifica codul original astfel încât să randăm mai întâi primul element și apoi, în bucla interioară `{iterateWhile}`, să randăm celelalte elemente din aceeași categorie: + +```latte +{foreach $items as $item} +

                                                                                                            {$item->name}

                                                                                                            +
                                                                                                              + {iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                                                                                                            • {$item->name}
                                                                                                            • + {/iterateWhile} +
                                                                                                            +{/foreach} +``` + +În cadrul unei singure bucle putem crea mai multe bucle interioare și chiar le putem imbrica. Astfel s-ar putea grupa, de exemplu, subcategoriile etc. + +Să presupunem că în tabel va mai exista o coloană `subcategoryId` și, pe lângă faptul că fiecare categorie va fi într-un `
                                                                                                              ` separat, fiecare subcategorie va fi într-un `
                                                                                                                ` separat: + +```latte +{foreach $items as $item} +
                                                                                                                  + {iterateWhile} +
                                                                                                                    + {iterateWhile} +
                                                                                                                  1. {$item->name} + {/iterateWhile $item->subcategoryId === $iterator->nextValue->subcategoryId} +
                                                                                                                  + {/iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                                                                                                                +{/foreach} +``` diff --git a/latte/ro/cookbook/how-to-write-sql-queries-in-latte.texy b/latte/ro/cookbook/how-to-write-sql-queries-in-latte.texy index dbd9085bd7..833a0a15d9 100644 --- a/latte/ro/cookbook/how-to-write-sql-queries-in-latte.texy +++ b/latte/ro/cookbook/how-to-write-sql-queries-in-latte.texy @@ -2,9 +2,9 @@ Cum să scrieți interogări SQL în Latte? *************************************** .[perex] -Latte poate fi util și pentru a genera interogări SQL foarte complexe. +Latte poate fi util și pentru generarea interogărilor SQL cu adevărat complexe. -În cazul în care crearea unei interogări SQL conține multe condiții și variabile, poate fi foarte clar să o scrieți în Latte. Un exemplu foarte simplu: +Dacă crearea unei interogări SQL conține o serie de condiții și variabile, poate fi cu adevărat mai clar să o scrieți în Latte. Un exemplu foarte simplu: ```latte SELECT users.* FROM users @@ -14,8 +14,7 @@ SELECT users.* FROM users WHERE groups.name = 'Admins' {ifset $country} AND country.name = {$country} {/ifset} ``` -Folosind `$latte->setContentType()` îi spunem lui Latte să trateze conținutul ca text simplu (nu ca HTML) și -apoi pregătim o funcție de escapare care să evadeze șirurile de caractere direct de către driverul bazei de date: +Folosind `$latte->setContentType()`, spunem Latte să trateze conținutul ca text simplu (nu ca HTML) și, în continuare, pregătim o funcție de escapare care va escapa șirurile direct prin driverul bazei de date: ```php $db = new PDO(/* ... */); @@ -27,7 +26,7 @@ $latte->addFilter('escape', fn($val) => match (true) { is_int($val), is_float($val) => (string) $val, is_bool($val) => $val ? '1' : '0', is_null($val) => 'NULL', - default => throw new Exception('Unsupported type'), + default => throw new Exception('Tip nesuportat'), }); ``` @@ -38,6 +37,4 @@ $sql = $latte->renderToString('query.sql.latte', ['country' => $country]); $result = $db->query($sql); ``` -*Acest exemplu necesită Latte v3.0.5 sau o versiune mai recentă.* - -{{leftbar: /@left-menu}} +*Exemplul prezentat necesită Latte v3.0.5 sau o versiune ulterioară.* diff --git a/latte/ro/cookbook/iteratewhile.texy b/latte/ro/cookbook/iteratewhile.texy deleted file mode 100644 index 130ce06353..0000000000 --- a/latte/ro/cookbook/iteratewhile.texy +++ /dev/null @@ -1,214 +0,0 @@ -Tot ce ați vrut să știți despre {iterateWhile} -********************************************** - -.[perex] -Eticheta `{iterateWhile}` este potrivită pentru diverse trucuri în ciclurile foreach. - -Să presupunem că avem următorul tabel de bază de date, în care articolele sunt împărțite în categorii: - -| id | catId | name -|------------------ -| 1 | 1 | Apple -| 2 | 1 | Banana -| 3 | 2 | PHP -| 4 | 3 | Green -| 5 | 3 | Red -| 6 | 3 | Blue - -Desigur, este ușor să desenezi elementele dintr-o buclă foreach sub forma unei liste: - -```latte -
                                                                                                                  -{foreach $items as $item} -
                                                                                                                • {$item->name}
                                                                                                                • -{/foreach} -
                                                                                                                -``` - -Dar ce să faceți dacă doriți să redați fiecare categorie într-o listă separată? Cu alte cuvinte, cum să rezolvați sarcina de a grupa elementele dintr-o listă liniară într-un ciclu foreach. Rezultatul ar trebui să arate astfel: - -```latte -
                                                                                                                  -
                                                                                                                • Apple
                                                                                                                • -
                                                                                                                • Banana
                                                                                                                • -
                                                                                                                - -
                                                                                                                  -
                                                                                                                • PHP
                                                                                                                • -
                                                                                                                - -
                                                                                                                  -
                                                                                                                • Green
                                                                                                                • -
                                                                                                                • Red
                                                                                                                • -
                                                                                                                • Blue
                                                                                                                • -
                                                                                                                -``` - -Vă vom arăta cât de ușor și elegant poate fi rezolvată această sarcină cu iterateWhile: - -```latte -{foreach $items as $item} -
                                                                                                                  - {iterateWhile} -
                                                                                                                • {$item->name}
                                                                                                                • - {/iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                                                                                -{/foreach} -``` - -În timp ce `{foreach}` marchează partea exterioară a ciclului, adică întocmirea listelor pentru fiecare categorie, etichetele `{iterateWhile}` indică partea interioară, adică elementele individuale. -Condiția din tag-ul end spune că repetiția va continua atâta timp cât elementul curent și cel următor aparțin aceleiași categorii (`$iterator->nextValue` este [elementul următor |/tags#$iterator]). - -Dacă condiția este întotdeauna îndeplinită, atunci toate elementele sunt desenate în ciclul interior: - -```latte -{foreach $items as $item} -
                                                                                                                  - {iterateWhile} -
                                                                                                                • {$item->name} - {/iterateWhile true} -
                                                                                                                -{/foreach} -``` - -Rezultatul va arăta astfel: - -```latte -
                                                                                                                  -
                                                                                                                • Apple
                                                                                                                • -
                                                                                                                • Banana
                                                                                                                • -
                                                                                                                • PHP
                                                                                                                • -
                                                                                                                • Green
                                                                                                                • -
                                                                                                                • Red
                                                                                                                • -
                                                                                                                • Blue
                                                                                                                • -
                                                                                                                -``` - -La ce este bună o astfel de utilizare a iterateWhile? Prin ce diferă de soluția pe care am arătat-o la începutul acestui tutorial? Diferența constă în faptul că, dacă tabelul este gol și nu conține niciun element, nu se va afișa gol `
                                                                                                                  `. - - -Soluție fără `{iterateWhile}` .[#toc-solution-without-iteratewhile] -------------------------------------------------------------------- - -Dacă am rezolva aceeași sarcină cu sisteme de șabloane complet de bază, de exemplu în Twig, Blade sau PHP pur, soluția ar arăta cam așa: - -```latte -{var $prevCatId = null} -{foreach $items as $item} - {if $item->catId !== $prevCatId} - {* categoria s-a schimbat *} - - {* închidem pagina anterioară
                                                                                                                    , dacă nu este primul element *} - {if $prevCatId !== null} -
                                                                                                                  - {/if} - - {* vom deschide o nouă listă *} -
                                                                                                                    - - {do $prevCatId = $item->catId} - {/if} - -
                                                                                                                  • {$item->name}
                                                                                                                  • -{/foreach} - -{if $prevCatId !== null} - {* închidem ultima listă *} -
                                                                                                                  -{/if} -``` - -Cu toate acestea, acest cod este de neînțeles și neintuitiv. Legătura dintre etichetele HTML de deschidere și închidere nu este deloc clară. Nu este clar la prima vedere dacă este vorba de o greșeală. Și este nevoie de variabile auxiliare precum `$prevCatId`. - -În schimb, soluția cu `{iterateWhile}` este curată, clară, nu are nevoie de variabile auxiliare și este infailibilă. - - -Condiția din eticheta de închidere .[#toc-condition-in-the-closing-tag] ------------------------------------------------------------------------ - -Dacă specificăm o condiție în tag-ul de deschidere `{iterateWhile}`, comportamentul se schimbă: condiția (și avansarea la elementul următor) este executată la începutul ciclului interior, nu la sfârșitul acestuia. -Astfel, în timp ce `{iterateWhile}` fără condiție este introdus întotdeauna, `{iterateWhile $cond}` este introdus doar atunci când este îndeplinită condiția `$cond`. În același timp, următorul element este scris în `$item`. - -Acest lucru este util, de exemplu, în situația în care doriți să redați primul element din fiecare categorie într-un mod diferit, cum ar fi: - -```latte -

                                                                                                                  Apple

                                                                                                                  -
                                                                                                                    -
                                                                                                                  • Banana
                                                                                                                  • -
                                                                                                                  - -

                                                                                                                  PHP

                                                                                                                  -
                                                                                                                    -
                                                                                                                  - -

                                                                                                                  Green

                                                                                                                  -
                                                                                                                    -
                                                                                                                  • Red
                                                                                                                  • -
                                                                                                                  • Blue
                                                                                                                  • -
                                                                                                                  -``` - -Să modificăm codul original, desenăm primul element și apoi elementele suplimentare din aceeași categorie în bucla interioară `{iterateWhile}`: - -```latte -{foreach $items as $item} -

                                                                                                                  {$item->name}

                                                                                                                  -
                                                                                                                    - {iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                                                                                  • {$item->name}
                                                                                                                  • - {/iterateWhile} -
                                                                                                                  -{/foreach} -``` - - -Bucle imbricate .[#toc-nested-loops] ------------------------------------- - -Putem crea mai multe bucle interioare într-un ciclu și chiar le putem anina. În acest fel, de exemplu, se pot grupa subcategorii. - -Să presupunem că există o altă coloană în tabelul `subCatId` și, pe lângă faptul că fiecare categorie se află într-o coloană separată `
                                                                                                                    `, fiecare subcategorie va fi într-o coloană separată `
                                                                                                                      `: - -```latte -{foreach $items as $item} -
                                                                                                                        - {iterateWhile} -
                                                                                                                          - {iterateWhile} -
                                                                                                                        1. {$item->name} - {/iterateWhile $item->subCatId === $iterator->nextValue->subCatId} -
                                                                                                                        - {/iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                                                                                      -{/foreach} -``` - - -Filtru |batch .[#toc-filter-batch] ----------------------------------- - -Gruparea elementelor liniare este, de asemenea, asigurată de un filtru `batch`, în loturi cu un număr fix de elemente: - -```latte -
                                                                                                                        -{foreach ($items|batch:3) as $batch} - {foreach $batch as $item} -
                                                                                                                      • {$item->name}
                                                                                                                      • - {/foreach} -{/foreach} -
                                                                                                                      -``` - -Acesta poate fi înlocuit cu iterateWhile după cum urmează: - -```latte -
                                                                                                                        -{foreach $items as $item} - {iterateWhile} -
                                                                                                                      • {$item->name}
                                                                                                                      • - {/iterateWhile $iterator->counter0 % 3} -{/foreach} -
                                                                                                                      -``` - -{{leftbar: /@left-menu}} diff --git a/latte/ro/cookbook/migration-from-php.texy b/latte/ro/cookbook/migration-from-php.texy index c1c3e2fe44..334754dea4 100644 --- a/latte/ro/cookbook/migration-from-php.texy +++ b/latte/ro/cookbook/migration-from-php.texy @@ -2,27 +2,27 @@ Migrarea de la PHP la Latte *************************** .[perex] -Migrați un proiect vechi scris în PHP pur în Latte? Avem un instrument pentru a face migrarea mai ușoară. [Încercați-l online |https://php2latte.nette.org]. +Convertiți un proiect vechi scris în PHP pur la Latte? Avem pentru dvs. un instrument care vă va facilita migrarea. [Încercați online |https://fiddle.nette.org/php2latte/]. -Puteți descărca instrumentul de pe [GitHub |https://github.com/nette/latte-tools] sau îl puteți instala folosind Composer: +Instrumentul îl puteți descărca de pe [GitHub |https://github.com/nette/latte-tools] sau instala folosind Composer: ```shell composer create-project latte/tools ``` -Convertorul nu utilizează substituții simple de expresii regulate, ci folosește direct parserul PHP, astfel încât poate gestiona orice sintaxă complexă. +Convertorul nu utilizează înlocuiri simple folosind expresii regulate, ci, dimpotrivă, utilizează direct parserul PHP, astfel încât poate gestiona orice sintaxă, oricât de complexă. -Scriptul `php-to-latte.php` este utilizat pentru a converti din PHP în Latte: +Pentru conversia de la PHP la Latte servește scriptul `php-to-latte.php`: ```shell -php-to-latte.php input.php [output.latte] +php php-to-latte.php input.php [output.latte] ``` -Exemplul .[#toc-example] ------------------------- +Exemplu +------- -Fișierul de intrare ar putea arăta astfel (face parte din codul forumului PunBB): +Fișierul de intrare poate arăta, de exemplu, astfel (este o parte din codul forumului PunBB): ```php

                                                                                                                      @@ -68,5 +68,3 @@ Generează acest șablon:
                                                                                                        ``` - -{{leftbar: /@left-menu}} diff --git a/latte/ro/cookbook/migration-from-twig.texy b/latte/ro/cookbook/migration-from-twig.texy index 59b1bd2e04..db1590587a 100644 --- a/latte/ro/cookbook/migration-from-twig.texy +++ b/latte/ro/cookbook/migration-from-twig.texy @@ -2,37 +2,37 @@ Migrarea de la Twig la Latte **************************** .[perex] -Migrați un proiect scris în Twig la Latte, mai modern? Avem un instrument pentru a face migrarea mai ușoară. [Încercați-l online |https://twig2latte.nette.org]. +Convertiți un proiect scris în Twig la Latte, mai modern? Avem pentru dvs. un instrument care vă va facilita migrarea. [Încercați online |https://fiddle.nette.org/twig2latte/]. -Puteți descărca instrumentul de pe [GitHub |https://github.com/nette/latte-tools] sau îl puteți instala folosind Composer: +Instrumentul îl puteți descărca de pe [GitHub |https://github.com/nette/latte-tools] sau instala folosind Composer: ```shell composer create-project latte/tools ``` -Convertorul nu utilizează substituții simple de expresii regulate, ci folosește direct parserul Twig, astfel încât poate gestiona orice sintaxă complexă. +Convertorul nu utilizează înlocuiri simple folosind expresii regulate, ci, dimpotrivă, utilizează direct parserul Twig, astfel încât poate gestiona orice sintaxă, oricât de complexă. -Un script `twig-to-latte.php` este utilizat pentru a converti din Twig în Latte: +Pentru conversia de la Twig la Latte servește scriptul `twig-to-latte.php`: ```shell -twig-to-latte.php input.twig.html [output.latte] +php twig-to-latte.php input.twig.html [output.latte] ``` -Conversia .[#toc-conversion] ----------------------------- +Conversie +--------- -Conversia necesită o editare manuală a rezultatului, deoarece conversia nu poate fi făcută fără ambiguitate. Twig utilizează sintaxa punct, unde `{{ a.b }}` poate însemna `$a->b`, `$a['b']` sau `$a->getB()`, care nu pot fi distinse în timpul compilării. Prin urmare, convertorul convertește totul în `$a->b`. +Conversia presupune ajustarea manuală a rezultatului, deoarece conversia nu poate fi efectuată univoc. Twig utilizează sintaxa cu punct, unde `{{ a.b }}` poate însemna `$a->b`, `$a['b']` sau `$a->getB()`, ceea ce nu poate fi distins la compilare. Convertorul, prin urmare, convertește totul la `$a->b`. -Unele funcții, filtre sau etichete nu au un echivalent în Latte sau se pot comporta ușor diferit. +Unele funcții, filtre sau tag-uri nu au echivalent în Latte sau se pot comporta ușor diferit. -Exemplu .[#toc-example] ------------------------ +Exemplu +------- -Fișierul de intrare ar putea arăta astfel: +Fișierul de intrare poate arăta, de exemplu, astfel: -```latte +```twig {% use "blocks.twig" %} @@ -54,7 +54,7 @@ Fișierul de intrare ar putea arăta astfel: ``` -După conversia în Latte, obținem acest șablon: +După conversia la Latte, obținem acest șablon: ```latte {import 'blocks.latte'} @@ -77,5 +77,3 @@ După conversia în Latte, obținem acest șablon: ``` - -{{leftbar: /@left-menu}} diff --git a/latte/ro/cookbook/passing-variables.texy b/latte/ro/cookbook/passing-variables.texy new file mode 100644 index 0000000000..2db71fcf00 --- /dev/null +++ b/latte/ro/cookbook/passing-variables.texy @@ -0,0 +1,158 @@ +Transmiterea variabilelor între șabloane +**************************************** + +Acest ghid vă va explica cum se transmit variabilele între șabloane în Latte folosind diverse tag-uri precum `{include}`, `{import}`, `{embed}`, `{layout}`, `{sandbox}` și altele. Veți afla, de asemenea, cum să lucrați cu variabile în tag-ul `{block}` și `{define}`, și la ce servește tag-ul `{parameters}`. + + +Tipuri de variabile +------------------- +Variabilele în Latte le putem împărți în trei categorii în funcție de cum și unde sunt definite: + +**Variabilele de intrare** sunt cele care sunt transmise șablonului din exterior, de exemplu dintr-un script PHP sau folosind un tag precum `{include}`. + +```php +$latte->render('template.latte', ['userName' => 'Jan', 'userAge' => 30]); +``` + +**Variabilele ambientale** sunt variabilele existente în locul unui anumit tag. Acestea includ toate variabilele de intrare și alte variabile create folosind tag-uri precum `{var}`, `{default}` sau în cadrul unei bucle `{foreach}`. + +```latte +{foreach $users as $user} + {include 'userBox.latte', user: $user} +{/foreach} +``` + +**Variabilele explicite** sunt cele care sunt specificate direct în interiorul tag-ului și sunt trimise șablonului țintă. + +```latte +{include 'userBox.latte', name: $user->name, age: $user->age} +``` + + +`{block}` +--------- +Tag-ul `{block}` se utilizează pentru a defini blocuri de cod reutilizabile, care pot fi personalizate sau extinse în șabloanele moștenite. Variabilele ambientale definite înainte de bloc sunt disponibile în interiorul blocului, dar orice modificări ale variabilelor se reflectă doar în cadrul acelui bloc. + +```latte +{var $foo = 'original'} +{block example} + {var $foo = 'modificat'} +{/block} + +{$foo} // afișează: original +``` + + +`{define}` +---------- +Tag-ul `{define}` servește la crearea blocurilor care se randează abia după ce sunt apelate folosind `{include}`. Variabilele disponibile în interiorul acestor blocuri depind dacă sunt specificați parametri în definiție. Dacă da, au acces doar la acești parametri. Dacă nu, au acces la toate variabilele de intrare ale șablonului în care sunt definite blocurile. + +```latte +{define hello} + {* are acces la toate variabilele de intrare ale șablonului *} +{/define} + +{define hello $name} + {* are acces doar la parametrul $name *} +{/define} +``` + + +`{parameters}` +-------------- +Tag-ul `{parameters}` servește la declararea explicită a variabilelor de intrare așteptate la începutul șablonului. În acest mod se pot documenta ușor variabilele așteptate și tipurile lor de date. De asemenea, este posibil să se definească valori implicite. + +```latte +{parameters int $age, string $name = 'necunoscut'} +

                                                                                                        Vârstă: {$age}, Nume: {$name}

                                                                                                        +``` + + +`{include file}` +---------------- +Tag-ul `{include file}` servește la inserarea unui șablon întreg. Acestui șablon i se transmit atât variabilele de intrare ale șablonului în care este utilizat tag-ul, cât și variabilele definite explicit în el. Șablonul țintă poate însă limita domeniul folosind `{parameters}`. + +```latte +{include 'profile.latte', userId: $user->id} +``` + + +`{include block}` +----------------- +Când inserați un bloc definit în același șablon, i se transmit toate variabilele ambientale și cele definite explicit: + +```latte +{define blockName} +

                                                                                                        Nume: {$name}, Vârstă: {$age}

                                                                                                        +{/define} + +{var $name = 'Jan', $age = 30} +{include blockName} +``` + +În acest exemplu, variabilele `$name` și `$age` se transmit blocului `blockName`. În același mod se comportă și `{include parent}`. + +La inserarea unui bloc dintr-un alt șablon, sunt transmise doar variabilele de intrare și cele definite explicit. Variabilele ambientale nu sunt disponibile automat. + +```latte +{include blockInOtherTemplate, name: $name, age: $age} +``` + + +`{layout}` sau `{extends}` +-------------------------- +Aceste tag-uri definesc layout-ul, căruia i se transmit variabilele de intrare ale șablonului copil și, în plus, variabilele create în cod înainte de blocuri: + +```latte +{layout 'layout.latte'} +{var $seo = 'index, follow'} +``` + +Șablonul `layout.latte`: + +```latte + + + +``` + + +`{embed}` +--------- +Tag-ul `{embed}` este similar cu tag-ul `{include}`, dar permite inserarea blocurilor în șablon. Spre deosebire de `{include}`, se transmit doar variabilele declarate explicit: + +```latte +{embed 'menu.latte', items: $menuItems} +{/embed} +``` + +În acest exemplu, șablonul `menu.latte` are acces doar la variabila `$items`. + +În schimb, în blocurile din interiorul `{embed}` este acces la toate variabilele ambientale: + +```latte +{var $name = 'Jan'} +{embed 'menu.latte', items: $menuItems} + {block foo} + {$name} + {/block} +{/embed} +``` + + +`{import}` +---------- +Tag-ul `{import}` se utilizează pentru încărcarea blocurilor din alte șabloane. Se transmit atât variabilele de intrare, cât și cele declarate explicit în blocurile importate. + +```latte +{import 'buttons.latte'} +``` + + +`{sandbox}` +----------- +Tag-ul `{sandbox}` izolează șablonul pentru procesare sigură. Variabilele sunt transmise exclusiv explicit. + +```latte +{sandbox 'secure.latte', data: $secureData} +``` diff --git a/latte/ro/cookbook/slim-framework.texy b/latte/ro/cookbook/slim-framework.texy index 9b7adc48d7..1e06b5e764 100644 --- a/latte/ro/cookbook/slim-framework.texy +++ b/latte/ro/cookbook/slim-framework.texy @@ -2,7 +2,7 @@ Utilizarea Latte cu Slim 4 ************************** .[perex] -Acest articol scris de "Daniel Opitz":https://odan.github.io/2022/04/06/slim4-latte.html descrie cum se utilizează Latte cu Slim Framework. +Acest articol, al cărui autor este "Daniel Opitz":https://odan.github.io/2022/04/06/slim4-latte.html, descrie utilizarea Latte cu Slim Framework. Mai întâi, "instalați Slim Framework":https://odan.github.io/2019/11/05/slim4-tutorial.html și apoi Latte folosind Composer: @@ -11,33 +11,33 @@ composer require latte/latte ``` -Configuration .[#toc-configuration] ------------------------------------ +Configurație +------------ -Creați un nou director `templates` în directorul rădăcină al proiectului dumneavoastră. Toate șabloanele vor fi plasate acolo mai târziu. +În directorul rădăcină al proiectului, creați un nou director `templates`. Toate șabloanele vor fi plasate ulterior în el. -Adăugați o nouă cheie de configurare `template` în fișierul `config/defaults.php`: +În fișierul `config/defaults.php`, adăugați o nouă cheie de configurare `template`: ```php $settings['template'] = __DIR__ . '/../templates'; ``` -Latte compilează șabloanele în cod PHP nativ și le stochează într-o memorie cache pe disc. Astfel, acestea sunt la fel de rapide ca și cum ar fi fost scrise în PHP nativ. +Latte compilează șabloanele în cod PHP nativ și le salvează într-un cache pe disc. Prin urmare, sunt la fel de rapide ca și cum ar fi fost scrise în limbaj PHP nativ. -Adăugați o nouă cheie de configurare `template_temp` în fișierul `config/defaults.php`: Asigurați-vă că directorul `{project}/tmp/templates` există și are permisiuni de acces în citire și scriere. +În fișierul `config/defaults.php`, adăugați o nouă cheie de configurare `template_temp`: Asigurați-vă că directorul `{project}/tmp/templates` există și are permisiuni de citire și scriere. ```php $settings['template_temp'] = __DIR__ . '/../tmp/templates'; ``` -Latte regenerează automat memoria cache de fiecare dată când modificați șablonul, ceea ce poate fi dezactivat în mediul de producție pentru a economisi puțină performanță: +Latte regenerează automat cache-ul la fiecare modificare a șablonului, ceea ce poate fi dezactivat în mediul de producție pentru a economisi puțină performanță: ```php -// se modifică la false în mediul de producție +// în mediul de producție, schimbați la false $settings['template_auto_refresh'] = true; ``` -În continuare, adăugați definiții ale unui container DI pentru clasa `Latte\Engine`. +În continuare, adăugați definiția containerului DI pentru clasa `Latte\Engine`. ```php +
                                                                                                          {foreach $items as $item}
                                                                                                        • {$item|capitalize}
                                                                                                        • {/foreach}
                                                                                                        ``` -Dacă totul este configurat corect, ar trebui să vedeți următorul rezultat: +Dacă totul este configurat corect, ar trebui să se afișeze următorul output: ```latte One @@ -155,4 +155,3 @@ Three ``` {{priority: -1}} -{{leftbar: /@left-menu}} diff --git a/latte/ro/creating-extension.texy b/latte/ro/creating-extension.texy deleted file mode 100644 index 20d513099d..0000000000 --- a/latte/ro/creating-extension.texy +++ /dev/null @@ -1,579 +0,0 @@ -Crearea unei extensii -********************* - -.[perex]{data-version:3.0} -O extensie este o clasă reutilizabilă care poate defini etichete, filtre, funcții, furnizori etc. personalizate. - -Creăm extensii atunci când dorim să reutilizăm personalizările noastre Latte în diferite proiecte sau să le împărtășim cu alții. -De asemenea, este util să creați o extensie pentru fiecare proiect web care va conține toate etichetele și filtrele specifice pe care doriți să le utilizați în șabloanele de proiect. - - -Clasa de extensie .[#toc-extension-class] -========================================= - -Extension este o clasă care moștenește din [api:Latte\Extension]. Este înregistrată în Latte utilizând `addExtension()` (sau prin intermediul [fișierului de configurare |application:configuration#Latte]): - -```php -$latte = new Latte\Engine; -$latte->addExtension(new MyLatteExtension); -``` - -Dacă înregistrați mai multe extensii și acestea definesc etichete, filtre sau funcții cu nume identice, ultima extensie adăugată câștigă. Acest lucru implică, de asemenea, faptul că extensiile dvs. pot suprascrie etichetele/filtrele/funcțiile native. - -Ori de câte ori efectuați o modificare într-o clasă și actualizarea automată nu este dezactivată, Latte va recompila automat șabloanele dumneavoastră. - -O clasă poate implementa oricare dintre următoarele metode: - -```php -abstract class Extension -{ - /** - * Initializes before template is compiler. - */ - public function beforeCompile(Engine $engine): void; - - /** - * Returns a list of parsers for Latte tags. - * @return array - */ - public function getTags(): array; - - /** - * Returns a list of compiler passes. - * @return array - */ - public function getPasses(): array; - - /** - * Returns a list of |filters. - * @return array - */ - public function getFilters(): array; - - /** - * Returns a list of functions used in templates. - * @return array - */ - public function getFunctions(): array; - - /** - * Returns a list of providers. - * @return array - */ - public function getProviders(): array; - - /** - * Returns a value to distinguish multiple versions of the template. - */ - public function getCacheKey(Engine $engine): mixed; - - /** - * Initializes before template is rendered. - */ - public function beforeRender(Template $template): void; -} -``` - -Pentru a vă face o idee despre cum arată extensia, aruncați o privire la extensia încorporată "CoreExtension":https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php. - - -beforeCompile(Latte\Engine $engine): void .[method] ---------------------------------------------------- - -Apelată înainte de compilarea șablonului. Metoda poate fi utilizată, de exemplu, pentru inițializări legate de compilare. - - -getTags(): array .[method] --------------------------- - -Apelată atunci când șablonul este compilat. Returnează o matrice asociativă *numele [etichetei |#Tag Parsing Function]=> callable*, care sunt [funcții de analiză a etichetelor |#Tag Parsing Function]. - -```php -public function getTags(): array -{ - return [ - 'foo' => [FooNode::class, 'create'], - 'bar' => [BarNode::class, 'create'], - 'n:baz' => [NBazNode::class, 'create'], - // ... - ]; -} -``` - -Eticheta `n:baz` reprezintă un atribut n:pur, adică este o etichetă care poate fi scrisă numai ca atribut. - -În cazul etichetelor `foo` și `bar`, Latte va recunoaște automat dacă acestea sunt perechi și, în caz afirmativ, pot fi scrise automat folosind n:attributes, inclusiv variantele cu prefixele `n:inner-foo` și `n:tag-foo`. - -Ordinea de execuție a acestor n:attributes este determinată de ordinea lor în matricea returnată de `getTags()`. Astfel, `n:foo` este întotdeauna executat înainte de `n:bar`, chiar dacă atributele sunt enumerate în ordine inversă în tag-ul HTML ca `
                                                                                                        `. - -Dacă aveți nevoie să determinați ordinea celor n:atribute în mai multe extensii, utilizați metoda de ajutor `order()`, unde parametrul `before` xor `after` determină ce etichete sunt ordonate înainte sau după etichetă. - -```php -public function getTags(): array -{ - return [ - 'foo' => self::order([FooNode::class, 'create'], before: 'bar')] - 'bar' => self::order([BarNode::class, 'create'], after: ['block', 'snippet'])] - ]; -} -``` - - -getPasses(): array .[method] ----------------------------- - -Este apelată atunci când șablonul este compilat. Returnează o matrice asociativă *name pass => callable*, care sunt funcții reprezentând așa-numitele [pase de compilare |#compiler passes] care traversează și modifică AST. - -Din nou, poate fi utilizată metoda de ajutor `order()`. Valoarea parametrilor `before` sau `after` poate fi `*` cu semnificația before/after all. - -```php -public function getPasses(): array -{ - return [ - 'optimize' => [Passes::class, 'optimizePass'], - 'sandbox' => self::order([$this, 'sandboxPass'], before: '*'), - // ... - ]; -} -``` - - -beforeRender(Latte\Engine $engine): void .[method] --------------------------------------------------- - -Este apelată înainte de fiecare redare a șablonului. Metoda poate fi utilizată, de exemplu, pentru a inițializa variabilele utilizate în timpul redării. - - -getFilters(): array .[method] ------------------------------ - -Este apelată înainte ca șablonul să fie redat. Returnează [filtrele |extending-latte#filters] sub forma unei matrice asociative *filter name => callable*. - -```php -public function getFilters(): array -{ - return [ - 'batch' => [$this, 'batchFilter'], - 'trim' => [$this, 'trimFilter'], - // ... - ]; -} -``` - - -getFunctions(): array .[method] -------------------------------- - -Se apelează înainte ca șablonul să fie redat. Returnează [funcțiile |extending-latte#functions] sub forma unui tablou asociativ *function name => callable*. - -```php -public function getFunctions(): array -{ - return [ - 'clamp' => [$this, 'clampFunction'], - 'divisibleBy' => [$this, 'divisibleByFunction'], - // ... - ]; -} -``` - - -getProviders(): array .[method] -------------------------------- - -Se apelează înainte ca șablonul să fie redat. Returnează o matrice de furnizori, care sunt, de obicei, obiecte care utilizează etichete în timpul execuției. Acestea sunt accesate prin intermediul `$this->global->...`. - -```php -public function getProviders(): array -{ - return [ - 'myFoo' => $this->foo, - 'myBar' => $this->bar, - // ... - ]; -} -``` - - -getCacheKey(Latte\Engine $engine): mixed .[method] --------------------------------------------------- - -Este apelată înainte ca șablonul să fie redat. Valoarea returnată devine parte a cheii a cărei hash este conținută în numele fișierului șablon compilat. Astfel, pentru valori de returnare diferite, Latte va genera fișiere cache diferite. - - -Cum funcționează Latte? .[#toc-how-does-latte-work] -=================================================== - -Pentru a înțelege cum se definesc etichete personalizate sau pase de compilare, este esențial să înțelegem cum funcționează Latte sub capotă. - -Compilarea șabloanelor în Latte funcționează în mod simplist astfel: - -- În primul rând, **lexer** tokenizează codul sursă al șablonului în bucăți mici (tokens) pentru o procesare mai ușoară -- Apoi, **parserul** convertește fluxul de token-uri într-un arbore de noduri semnificativ (Arborele de sintaxă abstract, AST). -- În cele din urmă, compilatorul **generează** o clasă PHP din AST care redă șablonul și îl pune în cache. - -De fapt, compilarea este un pic mai complicată. Latte **are două** lexere și analizoare: una pentru șablonul HTML și una pentru codul de tip PHP din interiorul etichetelor. De asemenea, parsarea nu se execută după tokenizare, ci lexerul și parserul rulează în paralel în două "fire" și se coordonează. Este o știință a rachetelor :-) - -În plus, toate etichetele au propriile lor rutine de parsare. Atunci când parserul întâlnește o etichetă, apelează funcția de parsare a acesteia (returnează [Extension::getTags() |#getTags]). -Sarcina acestora este de a analiza argumentele tag-ului și, în cazul tag-urilor împerecheate, conținutul interior. Aceasta returnează un *nod* care devine parte din AST. Pentru detalii, consultați [funcția de analiză a etichetelor |#Tag parsing function]. - -Când parserul își termină munca, avem un AST complet care reprezintă șablonul. Nodul rădăcină este `Latte\Compiler\Nodes\TemplateNode`. Nodurile individuale din interiorul arborelui reprezintă nu numai etichetele, ci și elementele HTML, atributele acestora, orice expresii utilizate în interiorul etichetelor etc. - -După aceasta, intră în joc așa-numitele [Compiler passes |#Compiler passes], care sunt funcții (returnate de [Extension::getPasses() |#getPasses]) care modifică AST. - -Întregul proces, de la încărcarea conținutului șablonului, trecând prin parsare, până la generarea fișierului rezultat, poate fi secvențiat cu acest cod, pe care îl puteți experimenta și arunca rezultatele intermediare: - -```php -$latte = new Latte\Engine; -$source = $latte->getLoader()->getContent($file); -$ast = $latte->parse($source); -$latte->applyPasses($ast); -$code = $latte->generate($ast, $file); -``` - - -Exemplu de AST .[#toc-example-of-ast] -------------------------------------- - -Pentru a avea o idee mai bună despre AST, adăugăm un exemplu. Acesta este șablonul sursă: - -```latte -{foreach $category->getItems() as $item} -
                                                                                                      1. {$item->name|upper}
                                                                                                      2. - {else} - no items found -{/foreach} -``` - -Și aceasta este reprezentarea sa sub formă de AST: - -/--pre -Latte\Compiler\Nodes\TemplateNode( - Latte\Compiler\Nodes\FragmentNode( - - Latte\Essential\Nodes\ForeachNode( - expression: Latte\Compiler\Nodes\Php\Expression\MethodCallNode( - object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$category') - name: Latte\Compiler\Nodes\Php\IdentifierNode('getItems') - ) - value: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') - content: Latte\Compiler\Nodes\FragmentNode( - - Latte\Compiler\Nodes\TextNode(' ') - - Latte\Compiler\Nodes\Html\ElementNode('li')( - content: Latte\Essential\Nodes\PrintNode( - expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode( - object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') - name: Latte\Compiler\Nodes\Php\IdentifierNode('name') - ) - modifier: Latte\Compiler\Nodes\Php\ModifierNode( - filters: - - Latte\Compiler\Nodes\Php\FilterNode('upper') - ) - ) - ) - ) - else: Latte\Compiler\Nodes\FragmentNode( - - Latte\Compiler\Nodes\TextNode('no items found') - ) - ) - ) -) -\-- - - -Etichete personalizate .[#toc-custom-tags] -========================================== - -Pentru a defini o nouă etichetă, sunt necesare trei etape: - -- definirea [funcției de |#tag parsing function] analiză a etichetei (responsabilă de analiza etichetei într-un nod) -- crearea unei clase de nod (responsabilă de [generarea codului PHP |#generating PHP code] și de [traversarea AST |#AST traversing]) -- înregistrarea etichetei folosind [Extension::getTags() |#getTags] - - -Funcția de analiză a etichetelor .[#toc-tag-parsing-function] -------------------------------------------------------------- - -Parsarea etichetelor este gestionată de funcția de parsare a acestora (cea returnată de [Extension::getTags() |#getTags]). Sarcina sa este de a analiza și verifica orice argumente din interiorul etichetei (pentru aceasta utilizează TagParser). -În plus, dacă eticheta este o pereche, aceasta va cere TemplateParser să analizeze și să returneze conținutul interior. -Funcția creează și returnează un nod, care este, de obicei, un copil al `Latte\Compiler\Nodes\StatementNode`, iar acesta devine parte din AST. - -Creăm o clasă pentru fiecare nod, lucru pe care îl vom face acum, și plasăm în mod elegant funcția de parsare în ea ca o fabrică statică. Ca exemplu, să încercăm să creăm cunoscuta etichetă `{foreach}`: - -```php -use Latte\Compiler\Nodes\StatementNode; - -class ForeachNode extends StatementNode -{ - // o funcție de parsare care creează doar un nod pentru moment - public static function create(Latte\Compiler\Tag $tag): self - { - $node = new self; - return $node; - } - - public function print(Latte\Compiler\PrintContext $context): string - { - // codul va fi adăugat mai târziu - } - - public function &getIterator(): \Generator - { - // codul va fi adăugat mai târziu - } -} -``` - -Funcției de parsare `create()` i se transmite un obiect [api:Latte\Compiler\Tag], care conține informații de bază despre etichetă (dacă este o etichetă clasică sau un n:attribute, pe ce linie se află etc.) și accesează în principal [api:Latte\Compiler\TagParser] în `$tag->parser`. - -În cazul în care tag-ul trebuie să aibă argumente, se verifică existența acestora prin apelarea `$tag->expectArguments()`. Metodele obiectului `$tag->parser` sunt disponibile pentru analizarea acestora: - -- `parseExpression(): ExpressionNode` pentru o expresie de tip PHP (de exemplu, `10 + 3`). -- `parseUnquotedStringOrExpression(): ExpressionNode` pentru o expresie sau un șir de caractere necotate -- `parseArguments(): ArrayNode` conținutul matricei (de exemplu, `10, true, foo => bar`) -- `parseModifier(): ModifierNode` pentru un modificator (de exemplu, `|upper|truncate:10`) -- `parseType(): expressionNode` pentru un indicator de tip (de exemplu, `int|string` sau `Foo\Bar[]`) - -și un nivel scăzut [api:Latte\Compiler\TokenStream] care operează direct cu token-uri: - -- `$tag->parser->stream->consume(...): Token` -- `$tag->parser->stream->tryConsume(...): ?Token` - -Latte extinde sintaxa PHP în moduri mici, de exemplu prin adăugarea de modificatori, operatori ternari scurtați sau permițând scrierea șirurilor alfanumerice simple fără ghilimele. Acesta este motivul pentru care folosim termenul *PHP-like* în loc de PHP. Astfel, metoda `parseExpression()` analizează `foo` ca `'foo'`, de exemplu. -În plus, *unquoted-string* este un caz special al unui șir de caractere care, de asemenea, nu trebuie să fie citat, dar în același timp nu trebuie să fie alfanumeric. De exemplu, este vorba de calea de acces la un fișier în eticheta `{include ../file.latte}`. Metoda `parseUnquotedStringOrExpression()` este utilizată pentru a-l analiza. - -.[note] -Studierea claselor de noduri care fac parte din Latte este cel mai bun mod de a învăța toate detaliile de detaliu ale procesului de analiză. - -Să ne întoarcem la eticheta `{foreach}`. În ea, așteptăm argumente de forma `expression + 'as' + second expression`, pe care le analizăm după cum urmează: - -```php -use Latte\Compiler\Nodes\StatementNode; -use Latte\Compiler\Nodes\Php\ExpressionNode; -use Latte\Compiler\Nodes\AreaNode; - -class ForeachNode extends StatementNode -{ - public ExpressionNode $expression; - public ExpressionNode $value; - - public static function create(Latte\Compiler\Tag $tag): self - { - $tag->expectArguments(); - $node = new self; - $node->expression = $tag->parser->parseExpression(); - $tag->parser->stream->consume('as'); - $node->value = $parser->parseExpression(); - return $node; - } -} -``` - -Expresiile pe care le-am scris în variabilele `$expression` și `$value` reprezintă subnoduri. - -.[tip] -Definiți variabilele cu subnoduri ca fiind **publice**, astfel încât acestea să poată fi modificate în [etapele ulterioare de procesare |#Compiler Passes], dacă este necesar. De asemenea, este necesar să le **puneți la dispoziție** pentru [traversare |#AST Traversing]. - -Pentru etichetele împerecheate, cum este cazul nostru, metoda trebuie să permită, de asemenea, TemplateParser să analizeze conținutul interior al etichetei. Acest lucru este gestionat de `yield`, care returnează o pereche ''[inner content, end tag]''. Stocăm conținutul interior în variabila `$node->content`. - -```php -public AreaNode $content; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $endTag] = yield; - return $node; -} -``` - -Cuvântul cheie `yield` determină încheierea metodei `create()`, returnând controlul înapoi la TemplateParser, care continuă să analizeze conținutul până când ajunge la tag-ul final. Apoi transmite controlul înapoi la `create()`, care continuă de unde a rămas. Utilizarea metodei `yield`, returnează automat `Generator`. - -Puteți, de asemenea, să transmiteți la `yield` o matrice de nume de etichete pentru care doriți să opriți analizarea dacă apar înainte de eticheta de sfârșit. Acest lucru ne ajută să implementăm metoda `{foreach}...{else}...{/foreach}` construcția. Dacă apare `{else}`, analizăm conținutul de după el în `$node->elseContent`: - -```php -public AreaNode $content; -public ?AreaNode $elseContent = null; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $nextTag] = yield ['else']; - if ($nextTag?->name === 'else') { - [$node->elseContent] = yield; - } - - return $node; -} -``` - -Nodul de întoarcere finalizează analiza etichetelor. - - -Generarea codului PHP .[#toc-generating-php-code] -------------------------------------------------- - -Fiecare nod trebuie să implementeze metoda `print()`. Returnează codul PHP care redă partea dată a șablonului (cod de execuție). I se transmite ca parametru un obiect [api:Latte\Compiler\PrintContext], care are o metodă utilă `format()` care simplifică asamblarea codului rezultat. - -Metoda `format(string $mask, ...$args)` acceptă în mască următoarele spații libere: -- `%node` imprimă Node -- `%dump` exportă valoarea în PHP -- `%raw` inserează textul direct, fără nicio transformare -- `%args` tipărește ArrayNode ca argumente la apelul funcției -- `%line` tipărește un comentariu cu un număr de linie -- `%escape(...)` evadează conținutul -- `%modify(...)` aplică un modificator -- `%modifyContent(...)` aplică un modificator la blocuri - - -Funcția noastră `print()` ar putea arăta astfel (pentru simplificare, neglijăm ramura `else` ): - -```php -public function print(Latte\Compiler\PrintContext $context): string -{ - return $context->format( - <<<'XX' - foreach (%node as %node) %line { - %node - } - - XX, - $this->expression, - $this->value, - $this->position, - $this->content, - ); -} -``` - -Variabila `$this->position` este deja definită de clasa [api:Latte\Compiler\Node] și este setată de parser. Ea conține un obiect [api:Latte\Compiler\Position] cu poziția etichetei în codul sursă sub forma unui număr de rând și coloană. - -Codul de execuție poate utiliza variabile auxiliare. Pentru a evita coliziunea cu variabilele utilizate de șablonul propriu-zis, este o convenție să le prefixeze cu caracterele `$ʟ__`. - -De asemenea, se pot utiliza valori arbitrare în timpul execuției, care sunt transmise șablonului sub formă de furnizori cu ajutorul metodei [Extension::getProviders() |#getProviders]. Acesta le accesează folosind `$this->global->...`. - - -Traversarea AST .[#toc-ast-traversing] --------------------------------------- - -Pentru a parcurge arborele AST în profunzime, este necesar să se implementeze metoda `getIterator()`. Aceasta va permite accesul la subnoduri: - -```php -public function &getIterator(): \Generator -{ - yield $this->expression; - yield $this->value; - yield $this->content; - if ($this->elseContent) { - yield $this->elseContent; - } -} -``` - -Rețineți că `getIterator()` returnează o referință. Aceasta este ceea ce permite vizitatorilor de noduri să înlocuiască nodurile individuale cu alte noduri. - -.[warning] -În cazul în care un nod are subnoduri, este necesar să se implementeze această metodă și să se facă disponibile toate subnodurile. În caz contrar, s-ar putea crea o gaură de securitate. De exemplu, modul "sandbox" nu ar putea controla subnodurile și nu s-ar putea asigura că în acestea nu sunt apelate construcții nepermise. - -Deoarece cuvântul cheie `yield` trebuie să fie prezent în corpul metodei chiar dacă nu are noduri copil, scrieți-o după cum urmează: - -```php -public function &getIterator(): \Generator -{ - if (false) { - yield; - } -} -``` - - -Compilatorul trece .[#toc-compiler-passes] -========================================== - -Compiler Passes sunt funcții care modifică AST-urile sau colectează informații din acestea. Acestea sunt returnate de metoda [Extension::getPasses() |#getPasses]. - - -Traversarea nodurilor .[#toc-node-traverser] --------------------------------------------- - -Cel mai comun mod de a lucra cu AST este prin utilizarea unui server [api:Latte\Compiler\NodeTraverser]: - -```php -use Latte\Compiler\Node; -use Latte\Compiler\NodeTraverser; - -$ast = (new NodeTraverser)->traverse( - $ast, - enter: fn(Node $node) => ..., - leave: fn(Node $node) => ..., -); -``` - -Funcția *enter* (adică vizitator) este apelată atunci când un nod este întâlnit pentru prima dată, înainte ca subnodurile sale să fie procesate. Funcția *leave* este apelată după ce toate subnodurile au fost vizitate. -Un model comun este acela că *enter* este utilizat pentru a colecta anumite informații, iar apoi *leave* efectuează modificări pe baza acestora. În momentul în care *leave* este apelată, tot codul din interiorul nodului a fost deja vizitat și informațiile necesare au fost colectate. - -Cum se modifică AST? Cea mai simplă metodă este de a modifica pur și simplu proprietățile nodurilor. A doua modalitate este de a înlocui în întregime nodul prin returnarea unui nou nod. Exemplu: următorul cod va schimba toate numerele întregi din AST în șiruri de caractere (de exemplu, 42 va fi schimbat în `'42'`). - -```php -use Latte\Compiler\Nodes\Php; - -$ast = (new NodeTraverser)->traverse( - $ast, - leave: function (Node $node) { - if ($node instanceof Php\Scalar\IntegerNode) { - return new Php\Scalar\StringNode((string) $node->value); - } - }, -); -``` - -Un AST poate conține cu ușurință mii de noduri, iar parcurgerea tuturor acestora poate fi lentă. În unele cazuri, este posibil să se evite o parcurgere completă. - -Dacă sunteți în căutarea tuturor `Html\ElementNode` într-un arbore, știți că, odată ce ați văzut `Php\ExpressionNode`, nu are rost să verificați și toate nodurile sale inferioare, deoarece HTML nu poate fi inclus în expresii. În acest caz, puteți instrui traversarea pentru a nu mai intra în nodul de clasă: - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Php\ExpressionNode) { - return NodeTraverser::DontTraverseChildren; - } - // ... - }, -); -``` - -Dacă nu căutați decât un singur nod specific, este de asemenea posibil să întrerupeți complet parcurgerea după ce l-ați găsit. - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Nodes\ParametersNode) { - return NodeTraverser::StopTraversal; - } - // ... - }, -); -``` - - -Ajutoare pentru noduri .[#toc-node-helpers] -------------------------------------------- - -Clasa [api:Latte\Compiler\NodeHelpers] oferă câteva metode care pot găsi noduri AST care satisfac un anumit callback etc. Sunt prezentate câteva exemple: - -```php -use Latte\Compiler\NodeHelpers; - -// găsește toate nodurile elementelor HTML -$elements = NodeHelpers::find($ast, fn(Node $node) => $node instanceof Nodes\Html\ElementNode); - -// găsește primul nod de text -$text = NodeHelpers::findFirst($ast, fn(Node $node) => $node instanceof Nodes\TextNode); - -// convertește nodul de valoare PHP în valoare reală -$value = NodeHelpers::toValue($node); - -// convertește nodul textual static în șir de caractere -$text = NodeHelpers::toText($node); -``` diff --git a/latte/ro/custom-filters.texy b/latte/ro/custom-filters.texy new file mode 100644 index 0000000000..89c593c91d --- /dev/null +++ b/latte/ro/custom-filters.texy @@ -0,0 +1,231 @@ +Crearea filtrelor personalizate +******************************* + +.[perex] +Filtrele sunt instrumente puternice pentru formatarea și modificarea datelor direct în șabloanele Latte. Oferă o sintaxă curată folosind simbolul pipe (`|`) pentru a transforma variabilele sau rezultatele expresiilor în formatul de ieșire dorit. + + +Ce sunt filtrele? +================= + +Filtrele în Latte sunt în esență **funcții PHP concepute special pentru a transforma o valoare de intrare într-o valoare de ieșire**. Se aplică folosind notația cu pipe (`|`) în interiorul expresiilor șablonului (`{...}`). + +**Comoditate:** Filtrele vă permit să încapsulați sarcini comune de formatare (cum ar fi formatarea datelor, schimbarea majusculelor/minusculelor, trunchierea) sau manipularea datelor în unități reutilizabile. În loc să repetați cod PHP complex în șabloanele dvs., puteți aplica pur și simplu un filtru: +```latte +{* În loc de PHP complex pentru trunchiere: *} +{$article->text|truncate:100} + +{* În loc de cod pentru formatarea datei: *} +{$event->startTime|date:'Y-m-d H:i'} + +{* Aplicarea mai multor transformări: *} +{$product->name|lower|capitalize} +``` + +**Lizibilitate:** Utilizarea filtrelor face șabloanele mai clare și mai concentrate pe prezentare, deoarece logica de transformare este mutată în definiția filtrului. + +**Sensibilitate la context:** Un avantaj cheie al filtrelor în Latte este capacitatea lor de a fi [sensibile la context |#Filtre contextuale]. Acest lucru înseamnă că un filtru poate recunoaște tipul de conținut cu care lucrează (HTML, JavaScript, text simplu etc.) și poate aplica logica sau escaparea corespunzătoare, ceea ce este crucial pentru securitate și corectitudine, în special la generarea HTML. + +**Integrare cu logica aplicației:** La fel ca funcțiile personalizate, PHP callable din spatele unui filtru poate fi o închidere (closure), o metodă statică sau o metodă de instanță. Acest lucru permite filtrelor să acceseze serviciile sau datele aplicației, dacă este necesar, deși scopul lor principal rămâne *transformarea valorii de intrare*. + +Latte oferă implicit un set bogat de [filtre standard |filters]. Filtrele personalizate vă permit să extindeți acest set cu formatări și transformări specifice proiectului dvs. + +Dacă trebuie să efectuați logică bazată pe *mai multe* intrări sau nu aveți o valoare primară de transformat, este probabil mai potrivit să utilizați o [funcție personalizată |custom-functions]. Dacă trebuie să generați markup complex sau să controlați fluxul șablonului, luați în considerare un [tag personalizat |custom-tags]. + + +Crearea și înregistrarea filtrelor +================================== + +Există mai multe moduri de a defini și înregistra filtre personalizate în Latte. + + +Înregistrare directă folosind `addFilter()` +------------------------------------------- + +Cel mai simplu mod de a adăuga un filtru este utilizarea metodei `addFilter()` direct pe obiectul `Latte\Engine`. Specificați numele filtrului (cum va fi utilizat în șablon) și PHP callable corespunzător. + +```php +$latte = new Latte\Engine; + +// Filtru simplu fără argumente +$latte->addFilter('initial', fn(string $s): string => mb_substr($s, 0, 1) . '.'); + +// Filtru cu argument opțional +$latte->addFilter('shortify', function (string $s, int $len = 10): string { + return mb_substr($s, 0, $len); +}); + +// Filtru care procesează array-uri +$latte->addFilter('sum', fn(array $numbers): int|float => array_sum($numbers)); +``` + +**Utilizare în șablon:** + +```latte +{$name|initial} {* Afișează 'I.' dacă $name este 'Ion' *} +{$description|shortify} {* Utilizează lungimea implicită 10 *} +{$description|shortify:50} {* Utilizează lungimea 50 *} +{$prices|sum} {* Afișează suma elementelor din array-ul $prices *} +``` + +**Transmiterea argumentelor:** + +Valoarea din stânga pipe-ului (`|`) este întotdeauna transmisă ca *primul* argument funcției filtrului. Orice parametri specificați după două puncte (`:`) în șablon sunt transmiși ca argumente următoare. + +```latte +{$text|shortify:30} +// Apelează funcția PHP shortify($text, 30) +``` + + +Înregistrare prin extensie +-------------------------- + +Pentru o mai bună organizare, în special la crearea de seturi reutilizabile de filtre sau partajarea lor ca pachete, modul recomandat este să le înregistrați în cadrul unei [extensii Latte |extending-latte#Latte Extension]: + +```php +namespace App\Latte; + +use Latte\Extension; + +class MyLatteExtension extends Extension +{ + public function getFilters(): array + { + return [ + 'initial' => $this->initial(...), + 'shortify' => $this->shortify(...), + ]; + } + + public function initial(string $s): string + { + return mb_substr($s, 0, 1) . '.'; + } + + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// Înregistrare +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +Această abordare menține logica filtrului dvs. încapsulată și înregistrarea simplă. + + +Utilizarea încărcătorului de filtre +----------------------------------- + +Latte permite înregistrarea unui încărcător de filtre folosind `addFilterLoader()`. Acesta este un singur callable pe care Latte îl va solicita pentru orice nume de filtru necunoscut în timpul compilării. Încărcătorul returnează PHP callable al filtrului sau `null`. + +```php +$latte = new Latte\Engine; + +// Încărcătorul poate crea/obține dinamic callable-uri de filtre +$latte->addFilterLoader(function (string $name): ?callable { + if ($name === 'myLazyFilter') { + // Imaginați-vă aici o inițializare costisitoare... + $service = get_some_expensive_service(); + return fn($value) => $service->process($value); + } + return null; +}); +``` + +Această metodă a fost destinată în principal încărcării leneșe a filtrelor cu o **inițializare foarte costisitoare**. Cu toate acestea, practicile moderne de injectare a dependențelor (dependency injection) gestionează de obicei serviciile leneșe mai eficient. + +Încărcătoarele de filtre adaugă complexitate și, în general, nu sunt recomandate în favoarea înregistrării directe folosind `addFilter()` sau în cadrul unei extensii folosind `getFilters()`. Utilizați încărcătoarele doar dacă aveți un motiv serios, specific, legat de probleme de performanță la inițializarea filtrelor, care nu pot fi rezolvate altfel. + + +Filtre care utilizează o clasă cu atribute +------------------------------------------ + +Un alt mod elegant de a defini filtre este utilizarea metodelor în [clasa dvs. de parametri de șablon |develop#Parametrii ca o clasă]. Pur și simplu adăugați atributul `#[Latte\Attributes\TemplateFilter]` la metodă. + +```php +use Latte\Attributes\TemplateFilter; + +class TemplateParameters +{ + public function __construct( + public string $description, + // alți parametri... + ) {} + + #[TemplateFilter] + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// Transmiterea obiectului către șablon +$params = new TemplateParameters(description: '...'); +$latte->render('template.latte', $params); +``` + +Latte va recunoaște și înregistra automat metodele marcate cu acest atribut atunci când obiectul `TemplateParameters` este transmis șablonului. Numele filtrului în șablon va fi același cu numele metodei (`shortify` în acest caz). + +```latte +{* Utilizarea filtrului definit în clasa de parametri *} +{$description|shortify:50} +``` + + +Filtre contextuale +================== + +Uneori, un filtru are nevoie de mai multe informații decât doar valoarea de intrare. Poate avea nevoie să cunoască **tipul de conținut** al șirului cu care lucrează (de exemplu, HTML, JavaScript, text simplu) sau chiar să îl modifice. Aceasta este situația pentru filtrele contextuale. + +Un filtru contextual este definit la fel ca un filtru obișnuit, dar **primul său parametru trebuie să fie** tipat ca `Latte\Runtime\FilterInfo`. Latte recunoaște automat această semnătură și, la apelarea filtrului, transmite obiectul `FilterInfo`. Parametrii următori primesc argumentele filtrului ca de obicei. + +```php +use Latte\Runtime\FilterInfo; +use Latte\ContentType; + +$latte->addFilter('money', function (FilterInfo $info, float $amount): string { + // 1. Verificați tipul de conținut de intrare (opțional, dar recomandat) + // Permiteți null (intrare variabilă) sau text simplu. Refuzați dacă este aplicat pe HTML etc. + if (!in_array($info->contentType, [null, ContentType::Text], true)) { + $actualType = $info->contentType ?? 'mixed'; + throw new \RuntimeException( + "Filtrul |money utilizat într-un tip de conținut incompatibil $actualType. Se aștepta text sau null." + ); + } + + // 2. Efectuați transformarea + $formatted = number_format($amount, 2, '.', ',') . ' EUR'; + $htmlOutput = '' . htmlspecialchars($formatted) . ''; // Asigurați escaparea corectă! + + // 3. Declarați tipul de conținut de ieșire + $info->contentType = ContentType::Html; + + // 4. Returnați rezultatul + return $htmlOutput; +}); +``` + +`$info->contentType` este o constantă de șir din `Latte\ContentType` (de exemplu, `ContentType::Html`, `ContentType::Text`, `ContentType::JavaScript`, etc.) sau `null`, dacă filtrul este aplicat pe o variabilă (`{$var|filter}`). Puteți **citi** această valoare pentru a verifica contextul de intrare și **scrie** în ea pentru a declara tipul contextului de ieșire. + +Setând tipul de conținut la HTML, îi spuneți lui Latte că șirul returnat de filtrul dvs. este HTML sigur. Latte **nu va** aplica apoi escaparea sa automată implicită acestui rezultat. Acest lucru este crucial dacă filtrul dvs. generează markup HTML. + +.[warning] +Dacă filtrul dvs. generează HTML, **sunteți responsabil pentru escaparea corectă a oricăror date de intrare** utilizate în acest HTML (ca în cazul apelului `htmlspecialchars($formatted)` de mai sus). Omiterea poate crea vulnerabilități XSS. Dacă filtrul dvs. returnează doar text simplu, nu trebuie să setați `$info->contentType`. + + +Filtre pe blocuri +----------------- + +Toate filtrele aplicate pe [blocuri |tags#block] *trebuie* să fie contextuale. Acest lucru se datorează faptului că conținutul blocului are un tip de conținut definit (de obicei HTML), de care filtrul trebuie să fie conștient. + +```latte +{block heading|money}1000{/block} +{* Filtrul 'money' primește '1000' ca al doilea argument + și $info->contentType va fi ContentType::Html *} +``` + +Filtrele contextuale oferă un control puternic asupra modului în care datele sunt procesate în funcție de contextul lor, permit funcții avansate și asigură un comportament corect de escapare, în special la generarea de conținut HTML. diff --git a/latte/ro/custom-functions.texy b/latte/ro/custom-functions.texy new file mode 100644 index 0000000000..0f20ccc697 --- /dev/null +++ b/latte/ro/custom-functions.texy @@ -0,0 +1,144 @@ +Crearea funcțiilor personalizate +******************************** + +.[perex] +Adăugați cu ușurință funcții ajutătoare personalizate în șabloanele Latte. Apelați logica PHP direct în expresii pentru calcule, acces la servicii sau generarea de conținut dinamic, menținând șabloanele curate și puternice. + + +Ce sunt funcțiile? +================== + +Funcțiile în Latte vă permit să extindeți setul de funcții care pot fi apelate în cadrul expresiilor din șabloane (`{...}`). Le puteți considera ca **funcții PHP personalizate disponibile doar în interiorul șabloanelor dvs. Latte**. Acest lucru aduce mai multe avantaje: + +**Comoditate:** Puteți defini logica ajutătoare (cum ar fi calcule, formatare sau acces la datele aplicației) și o puteți apela folosind o sintaxă simplă și familiară de funcție direct în șablon, la fel cum ați apela `strlen()` sau `date()` în PHP. + +```latte +{var $userInitials = initials($userName)} {* ex. 'I. D.' *} + +{if hasPermission('article', 'edit')} + Editare +{/if} +``` + +**Fără poluarea spațiului global:** Spre deosebire de definirea unei funcții globale reale în PHP, funcțiile Latte există doar în contextul randării șablonului. Nu trebuie să încărcați spațiul de nume global PHP cu ajutoare care sunt specifice doar șabloanelor. + +**Integrare cu logica aplicației:** PHP callable din spatele unei funcții Latte poate fi orice – o funcție anonimă, o metodă statică sau o metodă de instanță. Acest lucru înseamnă că funcțiile dvs. din șabloane pot accesa cu ușurință serviciile aplicației, bazele de date, configurația sau orice altă logică necesară prin capturarea variabilelor (în cazul funcțiilor anonime) sau prin utilizarea dependency injection (în cazul obiectelor). Exemplul `hasPermission` de mai sus demonstrează clar acest lucru, apelând probabil în fundal un serviciu de autorizare. + +**Suprascrierea funcțiilor native (opțional):** Puteți chiar defini o funcție Latte cu același nume ca o funcție PHP nativă. În șablon, versiunea dvs. personalizată va fi apelată în locul funcției originale. Acest lucru poate fi util pentru a oferi un comportament specific șablonului sau pentru a asigura o procesare consecventă (de exemplu, asigurarea că `strlen` este întotdeauna sigură pentru multibyte). Utilizați această funcție cu prudență pentru a evita neînțelegerile. + +Implicit, Latte permite apelarea *tuturor* funcțiilor PHP native (dacă nu sunt restricționate de [Sandbox |sandbox]). Funcțiile personalizate extind această bibliotecă încorporată cu nevoile specifice ale proiectului dvs. + +Dacă transformați doar o singură valoare, ar putea fi mai potrivit să utilizați un [filtru personalizat |custom-filters]. + + +Crearea și înregistrarea funcțiilor +=================================== + +Similar filtrelor, există mai multe moduri de a defini și înregistra funcții personalizate. + + +Înregistrare directă folosind `addFunction()` +--------------------------------------------- + +Cea mai simplă metodă este utilizarea `addFunction()` pe obiectul `Latte\Engine`. Specificați numele funcției (cum va apărea în șablon) și PHP callable corespunzător. + +```php +$latte = new Latte\Engine; + +// Funcție ajutătoare simplă +$latte->addFunction('initials', function (string $name): string { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; +}); +``` + +**Utilizare în șablon:** + +```latte +{var $userInitials = initials($userName)} +``` + +Argumentele funcției din șablon sunt transmise direct PHP callable în aceeași ordine. Funcționalitățile PHP precum type hints, valori implicite și parametri variabili (`...`) funcționează conform așteptărilor. + + +Înregistrare prin extensie +-------------------------- + +Pentru o mai bună organizare și reutilizare, înregistrați funcțiile în cadrul unei [extensii Latte |extending-latte#Latte Extension]. Această abordare este recomandată pentru aplicații mai complexe sau biblioteci partajate. + +```php +namespace App\Latte; + +use Latte\Extension; +use Nette\Security\Authorizator; // Presupunând că utilizați Nette Authorizator + +class MyLatteExtension extends Extension +{ + public function __construct( + // Presupunem că serviciul Authorizator există + private Authorizator $authorizator, + ) { + } + + public function getFunctions(): array + { + // Înregistrarea metodelor ca funcții Latte + return [ + 'hasPermission' => $this->hasPermission(...), + ]; + } + + public function hasPermission(string $resource, string $action): bool + { + return $this->authorizator->isAllowed($resource, $action); + } +} + +// Înregistrare (presupunem că $container conține DIC) +$extension = $container->getByType(App\Latte\MyLatteExtension::class); +$latte = new Latte\Engine; +$latte->addExtension($extension); +``` + +Această abordare ilustrează cum funcțiile definite în Latte pot fi susținute de metode ale obiectelor, care pot avea propriile dependențe gestionate de containerul de dependency injection al aplicației dvs. sau de o fabrică. Acest lucru menține logica șabloanelor dvs. conectată la nucleul aplicației, păstrând în același timp o organizare clară. + + +Funcții care utilizează o clasă cu atribute +------------------------------------------- + +La fel ca filtrele, funcțiile pot fi definite ca metode în [clasa dvs. de parametri de șablon |develop#Parametrii ca o clasă] folosind atributul `#[Latte\Attributes\TemplateFunction]`. + +```php +use Latte\Attributes\TemplateFunction; + +class TemplateParameters +{ + public function __construct( + public string $userName, + // alți parametri... + ) {} + + // Această metodă va fi disponibilă ca {initials(...)} în șablon + #[TemplateFunction] + public function initials(string $name): string + { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; + } +} + +// Transmiterea obiectului către șablon +$params = new TemplateParameters(userName: 'John Doe', /* ... */); +$latte->render('template.latte', $params); +``` + +Latte va descoperi și înregistra automat metodele marcate cu acest atribut atunci când obiectul de parametri este transmis șablonului. Numele funcției în șablon corespunde numelui metodei. + +```latte +{* Utilizarea funcției definite în clasa de parametri *} +{var $inits = initials($userName)} +``` + +**Funcții contextuale?** + +Spre deosebire de filtre, nu există un concept direct de „funcții contextuale” care ar primi un obiect similar cu `FilterInfo`. Funcțiile operează în cadrul expresiilor și, de obicei, nu necesită acces direct la contextul de randare sau la informații despre tipul de conținut în același mod ca filtrele aplicate pe blocuri. diff --git a/latte/ro/custom-tags.texy b/latte/ro/custom-tags.texy new file mode 100644 index 0000000000..bc12920a37 --- /dev/null +++ b/latte/ro/custom-tags.texy @@ -0,0 +1,1135 @@ +Crearea tag-urilor personalizate +******************************** + +.[perex] +Această pagină oferă un ghid cuprinzător pentru crearea tag-urilor personalizate în Latte. Vom discuta totul, de la tag-uri simple la scenarii mai complexe cu conținut imbricat și nevoi specifice de parsare, bazându-ne pe înțelegerea modului în care Latte compilează șabloanele. + +Tag-urile personalizate oferă cel mai înalt nivel de control asupra sintaxei șablonului și a logicii de randare, dar sunt și cel mai complex punct de extensie. Înainte de a decide să creați un tag personalizat, luați întotdeauna în considerare dacă [nu există o soluție mai simplă |extending-latte#Modalități de extindere a Latte] sau dacă un tag potrivit nu există deja în [setul standard |tags]. Utilizați tag-uri personalizate numai atunci când alternativele mai simple nu sunt suficiente pentru nevoile dvs. + + +Înțelegerea procesului de compilare +=================================== + +Pentru a crea eficient tag-uri personalizate, este util să explicăm cum procesează Latte șabloanele. Înțelegerea acestui proces clarifică de ce tag-urile sunt structurate în acest mod și cum se încadrează în contextul mai larg. + +Compilarea unui șablon în Latte, simplificat, implică acești pași cheie: + +1. **Analiza lexicală:** Lexerul citește codul sursă al șablonului (fișierul `.latte`) și îl împarte într-o secvență de părți mici, distincte, numite **token-uri** (de exemplu, `{`, `foreach`, `$variable`, `}`, text HTML, etc.). +2. **Parsarea:** Parserul preia acest flux de token-uri și construiește din el o structură arborescentă semnificativă, reprezentând logica și conținutul șablonului. Acest arbore se numește **arbore sintactic abstract (AST)**. +3. **Trecerea de compilare:** Înainte de a genera codul PHP, Latte rulează [treceri de compilare |compiler passes]. Acestea sunt funcții care parcurg întregul AST și îl pot modifica sau colecta informații. Acest pas este crucial pentru funcții precum securitatea ([Sandbox |sandbox]) sau optimizarea. +4. **Generarea codului:** În final, compilatorul parcurge AST-ul (potențial modificat) și generează codul clasei PHP corespunzător. Acest cod PHP este cel care randează efectiv șablonul la rulare. +5. **Caching:** Codul PHP generat este stocat pe disc, ceea ce face randările ulterioare foarte rapide, deoarece pașii 1-4 sunt săriți. + +În realitate, compilarea este puțin mai complexă. Latte **are două** lexere și parsere: unul pentru șablonul HTML și altul pentru codul PHP-like din interiorul tag-urilor. De asemenea, parsarea nu are loc după tokenizare, ci lexerul și parserul rulează în paralel în două "fire" și se coordonează. Credeți-mă, programarea a fost o știință rachetă :-) + +Întregul proces, de la încărcarea conținutului șablonului, prin parsare, până la generarea fișierului rezultat, poate fi secvențiat cu acest cod, cu care puteți experimenta și afișa rezultatele intermediare: + +```php +$latte = new Latte\Engine; +$source = $latte->getLoader()->getContent($file); +$ast = $latte->parse($source); +$latte->applyPasses($ast); +$code = $latte->generate($ast, $file); +``` + + +Anatomia unui tag +================= + +Crearea unui tag personalizat complet funcțional în Latte implică mai multe părți interconectate. Înainte de a ne arunca în implementare, să înțelegem conceptele și terminologia de bază, folosind o analogie cu HTML și Document Object Model (DOM). + + +Tag-uri vs. Noduri (Analogie cu HTML) +------------------------------------- + +În HTML, scriem **tag-uri** precum `

                                                                                                        ` sau `

                                                                                                        ...
                                                                                                        `. Aceste tag-uri sunt sintaxa din codul sursă. Când browserul parsează acest HTML, creează o reprezentare în memorie numită **Document Object Model (DOM)**. În DOM, tag-urile HTML sunt reprezentate de **noduri** (în mod specific, noduri `Element` în terminologia DOM JavaScript). Lucrăm programatic cu aceste *noduri* (de exemplu, folosind `document.getElementById(...)` din JavaScript se returnează un nod Element). Tag-ul este doar reprezentarea textuală în fișierul sursă; nodul este reprezentarea obiectuală în arborele logic. + +Latte funcționează similar: + +- În fișierul șablon `.latte`, scrieți **tag-uri Latte**, precum `{foreach ...}` și `{/foreach}`. Aceasta este sintaxa cu care lucrați ca autor al șablonului. +- Când Latte **parsează** șablonul, construiește un **Abstract Syntax Tree (AST)**. Acest arbore este compus din **noduri**. Fiecare tag Latte, element HTML, bucată de text sau expresie din șablon devine unul sau mai multe noduri în acest arbore. +- Clasa de bază pentru toate nodurile din AST este `Latte\Compiler\Node`. La fel cum DOM are diferite tipuri de noduri (Element, Text, Comment), AST-ul Latte are diferite tipuri de noduri. Veți întâlni `Latte\Compiler\Nodes\TextNode` pentru text static, `Latte\Compiler\Nodes\Html\ElementNode` pentru elemente HTML, `Latte\Compiler\Nodes\Php\ExpressionNode` pentru expresii în interiorul tag-urilor și, crucial pentru tag-uri personalizate, noduri care moștenesc din `Latte\Compiler\Nodes\StatementNode`. + + +De ce `StatementNode`? +---------------------- + +Elementele HTML (`Html\ElementNode`) reprezintă în principal structura și conținutul. Expresiile PHP (`Php\ExpressionNode`) reprezintă valori sau calcule. Dar ce zicem de tag-urile Latte precum `{if}`, `{foreach}` sau propriul nostru `{datetime}`? Aceste tag-uri *efectuează acțiuni*, controlează fluxul programului sau generează ieșire pe baza logicii. Sunt unități funcționale care fac din Latte un motor de șabloane puternic, nu doar un limbaj de marcare. + +În programare, astfel de unități care efectuează acțiuni sunt adesea numite "statements" (instrucțiuni). De aceea, nodurile care reprezintă aceste tag-uri Latte funcționale moștenesc de obicei din `Latte\Compiler\Nodes\StatementNode`. Acest lucru le diferențiază de nodurile pur structurale (cum ar fi elementele HTML) sau de nodurile care reprezintă valori (cum ar fi expresiile). + + +Componentele cheie +================== + +Să trecem în revistă componentele principale necesare pentru a crea un tag personalizat: + + +Funcția de parsare a tag-ului +----------------------------- + +- Această funcție PHP callable parsează sintaxa tag-ului Latte (`{...}`) în șablonul sursă. +- Primește informații despre tag (cum ar fi numele său, poziția și dacă este un n:atribut) prin intermediul obiectului [api:Latte\Compiler\Tag]. +- Instrumentul său principal pentru parsarea argumentelor și expresiilor din interiorul delimitatorilor tag-ului este obiectul [api:Latte\Compiler\TagParser], accesibil prin `$tag->parser` (acesta este un parser diferit de cel care parsează întregul șablon). +- Pentru tag-urile pereche, utilizează `yield` pentru a semnala Latte să parseze conținutul intern între tag-ul de început și cel de sfârșit. +- Scopul final al funcției de parsare este de a crea și returna o instanță a **clasei de nod**, care este adăugată la AST. +- Este o practică obișnuită (deși nu obligatorie) să implementați funcția de parsare ca o metodă statică (adesea numită `create`) direct în clasa de nod corespunzătoare. Acest lucru menține logica de parsare și reprezentarea nodului ordonat într-un singur pachet, permite accesul la elementele private/protejate ale clasei, dacă este necesar, și îmbunătățește organizarea. + + +Clasa de nod +------------ + +- Reprezintă *funcția logică* a tag-ului dvs. în **Abstract Syntax Tree (AST)**. +- Conține informațiile parsate (cum ar fi argumentele sau conținutul) ca proprietăți publice. Aceste proprietăți conțin adesea alte instanțe `Node` (de exemplu, `ExpressionNode` pentru argumentele parsate, `AreaNode` pentru conținutul parsat). +- Metoda `print(PrintContext $context): string` generează *codul PHP* (instrucțiunea sau seria de instrucțiuni) care execută acțiunea tag-ului în timpul randării șablonului. +- Metoda `getIterator(): \Generator` expune nodurile copil (argumente, conținut) pentru parcurgerea de către **trecerile de compilare**. Trebuie să furnizeze referințe (`&`) pentru a permite trecerilor să modifice sau să înlocuiască potențial sub-nodurile. +- După ce întregul șablon este parsat în AST, Latte rulează o serie de [treceri de compilare |compiler-passes]. Aceste treceri parcurg *întregul* AST folosind metoda `getIterator()` furnizată de fiecare nod. Pot inspecta nodurile, colecta informații și chiar *modifica* arborele (de exemplu, schimbând proprietățile publice ale nodurilor sau înlocuind complet nodurile). Acest design, care necesită un `getIterator()` complex, este esențial. Permite funcțiilor puternice precum [Sandbox |sandbox] să analizeze și să modifice potențial comportamentul *oricărei* părți a șablonului, inclusiv tag-urile dvs. personalizate, asigurând securitatea și consistența. + + +Înregistrarea prin extensie +--------------------------- + +- Trebuie să informați Latte despre noul dvs. tag și ce funcție de parsare trebuie utilizată pentru acesta. Acest lucru se face în cadrul unei [extensii Latte |extending-latte#Latte Extension]. +- În interiorul clasei dvs. de extensie, implementați metoda `getTags(): array`. Această metodă returnează un array asociativ unde cheile sunt numele tag-urilor (de exemplu, `'mytag'`, `'n:myattribute'`) și valorile sunt funcții PHP callable reprezentând funcțiile lor de parsare respective (de exemplu, `MyNamespace\DatetimeNode::create(...)`). + +Rezumat: **Funcția de parsare a tag-ului** transformă *codul sursă al șablonului* tag-ului dvs. într-un **nod AST**. **Clasa de nod** poate apoi să se transforme *pe sine însăși* în *cod PHP* executabil pentru șablonul compilat și expune sub-nodurile sale pentru **trecerile de compilare** prin `getIterator()`. **Înregistrarea prin extensie** leagă numele tag-ului de funcția de parsare și îl face cunoscut Latte. + +Acum vom explora cum să implementăm aceste componente pas cu pas. + + +Crearea unui tag simplu +======================= + +Să ne apucăm de crearea primului dvs. tag Latte personalizat. Vom începe cu un exemplu foarte simplu: un tag numit `{datetime}`, care afișează data și ora curentă. **Inițial, acest tag nu va accepta niciun argument**, dar îl vom îmbunătăți mai târziu în secțiunea [#Parsarea argumentelor tag-ului]. De asemenea, nu are niciun conținut intern. + +Acest exemplu vă va ghida prin pașii de bază: definirea clasei de nod, implementarea metodelor sale `print()` și `getIterator()`, crearea funcției de parsare și, în final, înregistrarea tag-ului. + +**Scop:** Implementarea `{datetime}` pentru a afișa data și ora curentă folosind funcția PHP `date()`. + + +Crearea clasei de nod +--------------------- + +Mai întâi, avem nevoie de o clasă care să reprezinte tag-ul nostru în Abstract Syntax Tree (AST). După cum s-a discutat mai sus, moștenim din `Latte\Compiler\Nodes\StatementNode`. + +Creați un fișier (de exemplu, `DatetimeNode.php`) și definiți clasa: + +```php +node = new self; + return $node; + } + + /** + * Generează codul PHP care va fi executat la randarea șablonului. + */ + public function print(PrintContext $context): string + { + return $context->format( + 'echo date(\'Y-m-d H:i:s\') %line;', + $this->position, + ); + } + + /** + * Oferă acces la nodurile copil pentru trecerile de compilare Latte. + */ + public function &getIterator(): \Generator + { + false && yield; + } +} +``` + +Când Latte întâlnește `{datetime}` într-un șablon, apelează funcția de parsare `create()`. Sarcina sa este să returneze o instanță a `DatetimeNode`. + +Metoda `print()` generează codul PHP care va fi executat la randarea șablonului. Apelăm metoda `$context->format()`, care construiește șirul final de cod PHP pentru șablonul compilat. Primul argument, `'echo date('Y-m-d H:i:s') %line;'`, este o mască în care sunt completați următorii parametri. Placeholder-ul `%line` îi spune metodei `format()` să folosească al doilea argument, care este `$this->position`, și să insereze un comentariu precum `/* line 15 */`, care leagă codul PHP generat înapoi la linia originală a șablonului, ceea ce este crucial pentru depanare. + +Proprietatea `$this->position` este moștenită din clasa de bază `Node` și este setată automat de parserul Latte. Conține un obiect [api:Latte\Compiler\Position], care indică unde a fost găsit tag-ul în fișierul sursă `.latte`. + +Metoda `getIterator()` este esențială pentru trecerile de compilare. Trebuie să furnizeze toate nodurile copil, dar `DatetimeNode`-ul nostru simplu nu are în prezent niciun argument sau conținut, deci niciun nod copil. Cu toate acestea, metoda trebuie să existe în continuare și să fie un generator, adică cuvântul cheie `yield` trebuie să fie prezent într-un fel în corpul metodei. + + +Înregistrarea prin extensie +--------------------------- + +În final, să informăm Latte despre noul tag. Creați o [clasă de extensie |extending-latte#Latte Extension] (de exemplu, `MyLatteExtension.php`) și înregistrați tag-ul în metoda sa `getTags()`. + +```php + Map: 'nume-tag' => functie-parsare + */ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + // Înregistrați mai multe tag-uri aici mai târziu + ]; + } +} +``` + +Apoi, înregistrați această extensie în Latte Engine: + +```php +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +Creați un șablon: + +```latte +

                                                                                                        Pagină generată la: {datetime}

                                                                                                        +``` + +Ieșirea așteptată: `

                                                                                                        Pagină generată la: 2023-10-27 11:00:00

                                                                                                        ` + + +Rezumatul acestei faze +---------------------- + +Am creat cu succes un tag personalizat de bază `{datetime}`. Am definit reprezentarea sa în AST (`DatetimeNode`), am gestionat parsarea sa (`create()`), am specificat cum ar trebui să genereze cod PHP (`print()`), ne-am asigurat că copiii săi sunt accesibili pentru parcurgere (`getIterator()`) și l-am înregistrat în Latte. + +În secțiunea următoare, vom îmbunătăți acest tag pentru a accepta argumente și vom arăta cum să parsăm expresii și să gestionăm nodurile copil. + + +Parsarea argumentelor tag-ului +============================== + +Tag-ul nostru simplu `{datetime}` funcționează, dar nu este foarte flexibil. Să-l îmbunătățim pentru a accepta un argument opțional: un șir de formatare pentru funcția `date()`. Sintaxa dorită va fi `{datetime $format}`. + +**Scop:** Modificarea `{datetime}` pentru a accepta o expresie PHP opțională ca argument, care va fi folosită ca șir de formatare pentru `date()`. + + +Introducerea `TagParser` +------------------------ + +Înainte de a modifica codul, este important să înțelegem instrumentul pe care îl vom folosi: [api:Latte\Compiler\TagParser]. Când parserul principal Latte (`TemplateParser`) întâlnește un tag Latte precum `{datetime ...}` sau un n:atribut, deleagă parsarea conținutului *din interiorul* tag-ului (partea dintre `{` și `}` sau valoarea atributului) unui `TagParser` specializat. + +Acest `TagParser` lucrează exclusiv cu **argumentele tag-ului**. Sarcina sa este să proceseze token-urile care reprezintă aceste argumente. Cheia este că **trebuie să proceseze întregul conținut** care îi este furnizat. Dacă funcția dvs. de parsare se termină, dar `TagParser` nu a ajuns la sfârșitul argumentelor (verificat prin `$tag->parser->isEnd()`), Latte va arunca o excepție, deoarece acest lucru indică faptul că au rămas token-uri neașteptate în interiorul tag-ului. Invers, dacă tag-ul *necesită* argumente, ar trebui să apelați `$tag->expectArguments()` la începutul funcției dvs. de parsare. Această metodă verifică dacă argumentele sunt prezente și aruncă o excepție utilă dacă tag-ul a fost folosit fără niciun argument. + +`TagParser` oferă metode utile pentru parsarea diferitelor tipuri de argumente: + +- `parseExpression(): ExpressionNode`: Parsează o expresie PHP-like (variabile, literali, operatori, apeluri de funcții/metode, etc.). Gestionează sintaxa dulce Latte, cum ar fi tratarea șirurilor alfanumerice simple ca șiruri între ghilimele (de exemplu, `foo` este parsat ca și cum ar fi `'foo'`). +- `parseUnquotedStringOrExpression(): ExpressionNode`: Parsează fie o expresie standard, fie un *șir neghilimat*. Șirurile neghilimate sunt secvențe permise de Latte fără ghilimele, adesea folosite pentru lucruri precum căile de fișiere (de exemplu, `{include ../file.latte}`). Dacă parsează un șir neghilimat, returnează `StringNode`. +- `parseArguments(): ArrayNode`: Parsează argumente separate prin virgulă, potențial cu chei, precum `10, name: 'John', true`. +- `parseModifier(): ModifierNode`: Parsează filtre precum `|upper|truncate:10`. +- `parseType(): ?SuperiorTypeNode`: Parsează type hint-uri PHP precum `int`, `?string`, `array|Foo`. + +Pentru nevoi de parsare mai complexe sau de nivel inferior, puteți interacționa direct cu [fluxul de token-uri |api:Latte\Compiler\TokenStream] prin `$tag->parser->stream`. Acest obiect oferă metode pentru verificarea și procesarea token-urilor individuale: + +- `$tag->parser->stream->is(...): bool`: Verifică dacă token-ul *curent* corespunde unuia dintre tipurile specificate (de exemplu, `Token::Php_Variable`) sau valorilor literale (de exemplu, `'as'`) fără a-l consuma. Util pentru a privi înainte. +- `$tag->parser->stream->consume(...): Token`: Consumă token-ul *curent* și avansează poziția fluxului. Dacă sunt furnizate tipuri/valori de token-uri așteptate ca argumente și token-ul curent nu corespunde, aruncă `CompileException`. Folosiți acest lucru când *așteptați* un anumit token. +- `$tag->parser->stream->tryConsume(...): ?Token`: Încearcă să consume token-ul *curent* *numai dacă* corespunde unuia dintre tipurile/valorile specificate. Dacă corespunde, consumă token-ul și îl returnează. Dacă nu corespunde, lasă poziția fluxului neschimbată și returnează `null`. Folosiți acest lucru pentru token-uri opționale sau când alegeți între diferite căi sintactice. + + +Actualizarea funcției de parsare `create()` +------------------------------------------- + +Cu această înțelegere, să modificăm metoda `create()` în `DatetimeNode` pentru a parsa argumentul de formatare opțional folosind `$tag->parser`. + +```php +node = new self; + + // Verificăm dacă există token-uri + if (!$tag->parser->isEnd()) { + // Parsăm argumentul ca o expresie PHP-like folosind TagParser. + $node->format = $tag->parser->parseExpression(); + } + + return $node; + } + + // ... metodele print() și getIterator() vor fi actualizate în continuare ... +} +``` + +Am adăugat o proprietate publică `$format`. În `create()`, folosim acum `$tag->parser->isEnd()` pentru a verifica dacă *există* argumente. Dacă da, `$tag->parser->parseExpression()` procesează token-urile pentru expresie. Deoarece `TagParser` trebuie să proceseze toate token-urile de intrare, Latte va arunca automat o eroare dacă utilizatorul scrie ceva neașteptat după expresia de format (de exemplu, `{datetime 'Y-m-d', unexpected}`). + + +Actualizarea metodei `print()` +------------------------------ + +Acum să modificăm metoda `print()` pentru a utiliza expresia de format parsat stocată în `$this->format`. Dacă nu a fost furnizat niciun format (`$this->format` este `null`), ar trebui să folosim un șir de formatare implicit, de exemplu `'Y-m-d H:i:s'`. + +```php + public function print(PrintContext $context): string + { + $formatNode = $this->format ?? new StringNode('Y-m-d H:i:s'); + + // %node tipărește reprezentarea codului PHP a $formatNode. + return $context->format( + 'echo date(%node) %line;', + $formatNode, + $this->position + ); + } +``` + +În variabila `$formatNode` stocăm nodul AST care reprezintă șirul de formatare pentru funcția PHP `date()`. Folosim aici operatorul de coalescență nulă (`??`). Dacă utilizatorul a furnizat un argument în șablon (de exemplu, `{datetime 'd.m.Y'}`), atunci proprietatea `$this->format` conține nodul corespunzător (în acest caz, un `StringNode` cu valoarea `'d.m.Y'`), și acest nod este utilizat. Dacă utilizatorul nu a furnizat un argument (a scris doar `{datetime}`), proprietatea `$this->format` este `null`, și în schimb creăm un nou `StringNode` cu formatul implicit `'Y-m-d H:i:s'`. Acest lucru asigură că `$formatNode` conține întotdeauna un nod AST valid pentru format. + +În masca `'echo date(%node) %line;'` este folosit un nou placeholder `%node`, care îi spune metodei `format()` să ia primul argument următor (care este `$formatNode`-ul nostru), să apeleze metoda sa `print()` (care va returna reprezentarea sa în cod PHP) și să insereze rezultatul la poziția placeholder-ului. + + +Implementarea `getIterator()` pentru sub-noduri +----------------------------------------------- + +`DatetimeNode`-ul nostru are acum un nod copil: expresia `$format`. **Trebuie** să facem acest nod copil accesibil trecerilor de compilare furnizându-l în metoda `getIterator()`. Nu uitați să furnizați o *referință* (`&`) pentru a permite trecerilor să înlocuiască potențial nodul. + +```php + public function &getIterator(): \Generator + { + if ($this->format) { + yield $this->format; + } + } +``` + +De ce este acest lucru crucial? Imaginați-vă o trecere Sandbox care trebuie să verifice dacă argumentul `$format` nu conține un apel de funcție interzis (de exemplu, `{datetime dangerousFunction()}`). Dacă `getIterator()` nu furnizează `$this->format`, trecerea Sandbox nu ar vedea niciodată apelul `dangerousFunction()` în interiorul argumentului tag-ului nostru, ceea ce ar crea o potențială gaură de securitate. Furnizându-l, permitem Sandbox-ului (și altor treceri) să verifice și să modifice potențial nodul expresiei `$format`. + + +Utilizarea tag-ului îmbunătățit +------------------------------- + +Tag-ul gestionează acum corect argumentul opțional: + +```latte +Format implicit: {datetime} +Format personalizat: {datetime 'd.m.Y'} +Utilizarea variabilei: {datetime $userDateFormatPreference} + +{* Acest lucru ar cauza o eroare după parsarea 'd.m.Y', deoarece ", foo" este neașteptat *} +{* {datetime 'd.m.Y', foo} *} +``` + +În continuare, vom analiza crearea de tag-uri pereche, care procesează conținutul dintre ele. + + +Gestionarea tag-urilor pereche +============================== + +Până acum, tag-ul nostru `{datetime}` a fost *auto-închizător* (conceptual). Nu are niciun conținut între tag-ul de început și cel de sfârșit. Cu toate acestea, multe tag-uri utile lucrează cu un bloc de conținut de șablon. Acestea se numesc **tag-uri pereche**. Exemple includ `{if}...{/if}`, `{block}...{/block}` sau un tag personalizat pe care îl vom crea acum: `{debug}...{/debug}`. + +Acest tag ne va permite să includem informații de depanare în șabloanele noastre, care ar trebui să fie vizibile numai în timpul dezvoltării. + +**Scop:** Crearea unui tag pereche `{debug}`, al cărui conținut este randat numai atunci când este activ un flag specific "mod dezvoltare". + + +Introducerea furnizorilor +------------------------- + +Uneori, tag-urile dvs. au nevoie de acces la date sau servicii care nu sunt transmise direct ca parametri ai șablonului. De exemplu, determinarea dacă aplicația este în modul de dezvoltare, accesarea obiectului utilizatorului sau obținerea valorilor de configurare. Latte oferă un mecanism numit **furnizori** (Providers) în acest scop. + +Furnizorii sunt înregistrați în [extensia |extending-latte#Latte Extension] dvs. folosind metoda `getProviders()`. Această metodă returnează un array asociativ unde cheile sunt numele sub care furnizorii vor fi accesibili în codul de rulare al șablonului, iar valorile sunt datele sau obiectele reale. + +În interiorul codului PHP generat de metoda `print()` a tag-ului dvs., puteți accesa acești furnizori prin intermediul proprietății speciale a obiectului `$this->global`. Deoarece această proprietate este partajată între toate extensiile, este o bună practică să **prefixați numele furnizorilor dvs.** pentru a preveni potențialele coliziuni de nume cu furnizorii cheie Latte sau furnizorii din alte extensii terțe. O convenție comună este utilizarea unui prefix scurt, unic, legat de producătorul sau numele extensiei dvs. Pentru exemplul nostru, vom folosi prefixul `app` și flag-ul modului de dezvoltare va fi disponibil ca `$this->global->appDevMode`. + + +Cuvântul cheie `yield` pentru parsarea conținutului +--------------------------------------------------- + +Cum îi spunem parserului Latte să proceseze conținutul *între* `{debug}` și `{/debug}`? Aici intervine cuvântul cheie `yield`. + +Când `yield` este folosit în funcția `create()`, funcția devine un [generator PHP |https://www.php.net/manual/en/language.generators.overview.php]. Execuția sa este suspendată și controlul revine la `TemplateParser`-ul principal. `TemplateParser` continuă apoi să parseze conținutul șablonului *până când* întâlnește tag-ul de închidere corespunzător (`{/debug}` în cazul nostru). + +Odată ce tag-ul de închidere este găsit, `TemplateParser` reia execuția funcției noastre `create()` imediat după instrucțiunea `yield`. Valoarea *returnată* de instrucțiunea `yield` este un array care conține două elemente: + +1. Un `AreaNode` reprezentând conținutul parsat între tag-ul de început și cel de sfârșit. +2. Un obiect `Tag` reprezentând tag-ul de închidere (de exemplu, `{/debug}`). + +Să creăm clasa `DebugNode` și metoda sa `create` folosind `yield`. + +```php +node = new self; + + // Suspendă parsarea, obține conținutul intern și tag-ul de sfârșit când este găsit {/debug} + [$node->content, $endTag] = yield; + + return $node; + } + + // ... print() și getIterator() vor fi implementate în continuare ... +} +``` + +Notă: `$endTag` este `null` dacă tag-ul este folosit ca n:atribut, adică `
                                                                                                        ...
                                                                                                        `. + + +Implementarea `print()` pentru randare condiționată +--------------------------------------------------- + +Metoda `print()` trebuie acum să genereze cod PHP care, la rulare, verifică furnizorul `appDevMode` și execută codul pentru conținutul intern numai dacă flag-ul este true. + +```php + public function print(PrintContext $context): string + { + // Generează o instrucțiune PHP 'if' care verifică furnizorul la rulare + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + // Dacă este în modul de dezvoltare, afișează conținutul intern + %node + } + + XX, + $this->position, // Pentru comentariul %line + $this->content, // Nodul care conține AST-ul conținutului intern + ); + } +``` + +Este simplu. Folosim `PrintContext::format()` pentru a crea o instrucțiune PHP `if` standard. În interiorul `if`, plasăm placeholder-ul `%node` pentru `$this->content`. Latte va apela recursiv `$this->content->print($context)` pentru a genera codul PHP pentru partea internă a tag-ului, dar numai dacă `$this->global->appDevMode` evaluează la true la rulare. + + +Implementarea `getIterator()` pentru conținut +--------------------------------------------- + +La fel ca și cu nodul argumentului din exemplul anterior, `DebugNode`-ul nostru are acum un nod copil: `AreaNode $content`. Trebuie să-l facem accesibil furnizându-l în `getIterator()`: + +```php + public function &getIterator(): \Generator + { + // Furnizează o referință la nodul de conținut + yield $this->content; + } +``` + +Acest lucru permite trecerilor de compilare să coboare în conținutul tag-ului nostru `{debug}`, ceea ce este important chiar dacă conținutul este randat condiționat. De exemplu, Sandbox trebuie să analizeze conținutul indiferent dacă `appDevMode` este true sau false. + + +Înregistrare și utilizare +------------------------- + +Înregistrați tag-ul și furnizorul în extensia dvs.: + +```php +class MyLatteExtension extends Extension +{ + // Presupunem că $isDevelopmentMode este determinat undeva (de ex., din configurare) + public function __construct( + private bool $isDevelopmentMode, + ) { + } + + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), // Înregistrarea noului tag + ]; + } + + public function getProviders(): array + { + return [ + 'appDevMode' => $this->isDevelopmentMode, // Înregistrarea furnizorului + ]; + } +} + +// La înregistrarea extensiei: +$isDev = true; // Determinați acest lucru pe baza mediului aplicației dvs. +$latte->addExtension(new App\Latte\MyLatteExtension($isDev)); +``` + +Și utilizarea sa în șablon: + +```latte +

                                                                                                        Conținut normal vizibil întotdeauna.

                                                                                                        + +{debug} +
                                                                                                        + ID-ul utilizatorului curent: {$user->id} + Timpul cererii: {=time()} +
                                                                                                        +{/debug} + +

                                                                                                        Alt conținut normal.

                                                                                                        +``` + + +Integrarea n:atributelor +------------------------ + +Latte oferă o notație prescurtată convenabilă pentru multe tag-uri pereche: [n:atribute |syntax#n:atribute]. Dacă aveți un tag pereche precum `{tag}...{/tag}` și doriți ca efectul său să se aplice direct unui singur element HTML, îl puteți scrie adesea mai concis ca un atribut `n:tag` pe acel element. + +Pentru majoritatea tag-urilor pereche standard pe care le definiți (cum ar fi `{debug}`-ul nostru), Latte va activa automat versiunea `n:` atributului corespunzător. Nu trebuie să faceți nimic suplimentar în timpul înregistrării: + +```latte +{* Utilizarea standard a tag-ului pereche *} +{debug}
                                                                                                        Informații pentru depanare
                                                                                                        {/debug} + +{* Utilizare echivalentă cu n:atribut *} +
                                                                                                        Informații pentru depanare
                                                                                                        +``` + +Ambele versiuni vor randa `
                                                                                                        ` numai dacă `$this->global->appDevMode` este true. Prefixele `inner-` și `tag-` funcționează, de asemenea, conform așteptărilor. + +Uneori, logica tag-ului dvs. poate avea nevoie să se comporte ușor diferit în funcție de dacă este utilizat ca un tag pereche standard sau ca un n:atribut, sau dacă este utilizat un prefix precum `n:inner-tag` sau `n:tag-tag`. Obiectul `Latte\Compiler\Tag`, transmis funcției dvs. de parsare `create()`, furnizează aceste informații: + +- `$tag->isNAttribute(): bool`: Returnează `true` dacă tag-ul este parsat ca n:atribut +- `$tag->prefix: ?string`: Returnează prefixul utilizat cu n:atributul, care poate fi `null` (nu este n:atribut), `Tag::PrefixNone`, `Tag::PrefixInner` sau `Tag::PrefixTag` + +Acum că înțelegem tag-urile simple, parsarea argumentelor, tag-urile pereche, furnizorii și n:atributele, să abordăm un scenariu mai complex care implică tag-uri imbricate în alte tag-uri, folosind tag-ul nostru `{debug}` ca punct de plecare. + + +Tag-uri intermediare +==================== + +Unele tag-uri pereche permit sau chiar necesită ca alte tag-uri să apară *în interiorul* lor înainte de tag-ul de închidere final. Acestea se numesc **tag-uri intermediare**. Exemple clasice includ `{if}...{elseif}...{else}...{/if}` sau `{switch}...{case}...{default}...{/switch}`. + +Să extindem tag-ul nostru `{debug}` pentru a suporta o clauză opțională `{else}`, care va fi randată atunci când aplicația *nu* este în modul de dezvoltare. + +**Scop:** Modificarea `{debug}` pentru a suporta un tag intermediar opțional `{else}`. Sintaxa finală ar trebui să fie `{debug} ... {else} ... {/debug}`. + + +Parsarea tag-urilor intermediare folosind `yield` +------------------------------------------------- + +Știm deja că `yield` suspendă funcția de parsare `create()` și returnează conținutul parsat împreună cu tag-ul de închidere. Cu toate acestea, `yield` oferă mai mult control: îi puteți furniza un array de *nume de tag-uri intermediare*. Când parserul întâlnește oricare dintre aceste tag-uri specificate **la același nivel de imbricare** (adică, ca copii direcți ai tag-ului părinte, nu în interiorul altor blocuri sau tag-uri din interiorul său), oprește, de asemenea, parsarea. + +Când parsarea se oprește din cauza unui tag intermediar, oprește parsarea conținutului, reia generatorul `create()` și returnează conținutul parsat parțial și **tag-ul intermediar** însuși (în loc de tag-ul de închidere final). Funcția noastră `create()` poate apoi procesa acest tag intermediar (de exemplu, parsa argumentele sale, dacă a avut vreunul) și utiliza din nou `yield` pentru a parsa *următoarea* parte a conținutului până la tag-ul de închidere *final* sau un alt tag intermediar așteptat. + +Să modificăm `DebugNode::create()` pentru a aștepta `{else}`: + +```php +node = new self; + + // yield și așteaptă fie {/debug}, fie {else} + [$node->thenContent, $nextTag] = yield ['else']; + + // Verifică dacă tag-ul la care ne-am oprit a fost {else} + if ($nextTag?->name === 'else') { + // Yield din nou pentru a parsa conținutul între {else} și {/debug} + [$node->elseContent, $endTag] = yield; + } + + return $node; + } + + // ... print() și getIterator() vor fi actualizate în continuare ... +} +``` + +Acum `yield ['else']` îi spune Latte să oprească parsarea nu numai pentru `{/debug}`, ci și pentru `{else}`. Dacă `{else}` este găsit, `$nextTag` va conține obiectul `Tag` pentru `{else}`. Apoi folosim din nou `yield` fără argumente, ceea ce înseamnă că acum așteptăm doar tag-ul final `{/debug}`, și stocăm rezultatul în `$node->elseContent`. Dacă `{else}` nu a fost găsit, `$nextTag` ar fi `Tag` pentru `{/debug}` (sau `null`, dacă este folosit ca n:atribut) și `$node->elseContent` ar rămâne `null`. + + +Implementarea `print()` cu `{else}` +----------------------------------- + +Metoda `print()` trebuie să reflecte noua structură. Ar trebui să genereze o instrucțiune PHP `if/else` bazată pe furnizorul `devMode`. + +```php + public function print(PrintContext $context): string + { + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + %node // Cod pentru ramura 'then' (conținut {debug}) + } else { + %node // Cod pentru ramura 'else' (conținut {else}) + } + + XX, + $this->position, // Numărul liniei pentru condiția 'if' + $this->thenContent, // Primul placeholder %node + $this->elseContent ?? new NopNode, // Al doilea placeholder %node + ); + } +``` + +Aceasta este o structură PHP `if/else` standard. Folosim `%node` de două ori; `format()` înlocuiește nodurile furnizate succesiv. Folosim `?? new NopNode` pentru a evita erorile dacă `$this->elseContent` este `null` – `NopNode` pur și simplu nu tipărește nimic. + + +Implementarea `getIterator()` pentru ambele conținuturi +------------------------------------------------------- + +Acum avem potențial două noduri copil de conținut (`$thenContent` și `$elseContent`). Trebuie să le furnizăm pe ambele, dacă există: + +```php + public function &getIterator(): \Generator + { + yield $this->thenContent; + if ($this->elseContent) { + yield $this->elseContent; + } + } +``` + + +Utilizarea tag-ului îmbunătățit +------------------------------- + +Tag-ul poate fi acum utilizat cu clauza opțională `{else}`: + +```latte +{debug} +

                                                                                                        Afișarea informațiilor de depanare, deoarece devMode este ACTIVAT.

                                                                                                        +{else} +

                                                                                                        Informațiile de depanare sunt ascunse, deoarece devMode este DEZACTIVAT.

                                                                                                        +{/debug} +``` + + +Gestionarea stării și a imbricării +================================== + +Exemplele noastre anterioare (`{datetime}`, `{debug}`) au fost relativ fără stare în cadrul metodelor lor `print()`. Fie au afișat direct conținutul, fie au efectuat o verificare condiționată simplă bazată pe un furnizor global. Cu toate acestea, multe tag-uri trebuie să gestioneze o formă de **stare** în timpul randării sau implică evaluarea expresiilor utilizatorului care ar trebui rulate o singură dată pentru performanță sau corectitudine. Mai mult, trebuie să luăm în considerare ce se întâmplă atunci când tag-urile noastre personalizate sunt **imbricate**. + +Să ilustrăm aceste concepte creând un tag `{repeat $count}...{/repeat}`. Acest tag va repeta conținutul său intern de `$count` ori. + +**Scop:** Implementarea `{repeat $count}`, care repetă conținutul său un număr specificat de ori. + + +Nevoia de variabile temporare & unice +------------------------------------- + +Imaginați-vă că utilizatorul scrie: + +```latte +{repeat rand(1, 5)} Conținut {/repeat} +``` + +Dacă am genera naiv un ciclu `for` PHP în acest mod în metoda noastră `print()`: + +```php +// Cod generat simplificat, INCORECT +for ($i = 0; $i < rand(1, 5); $i++) { + // afișare conținut +} +``` +Acest lucru ar fi greșit! Expresia `rand(1, 5)` ar fi **reevaluată la fiecare iterație a ciclului**, ceea ce ar duce la un număr imprevizibil de repetări. Trebuie să evaluăm expresia `$count` *o singură dată* înainte de începerea ciclului și să stocăm rezultatul său. + +Vom genera cod PHP care evaluează mai întâi expresia numărului și o stochează într-o **variabilă temporară de rulare**. Pentru a preveni coliziunile cu variabilele definite de utilizatorul șablonului *și* variabilele interne Latte (cum ar fi `$ʟ_...`), vom folosi convenția prefixului **`$__` (dublu underscore)** pentru variabilele noastre temporare. + +Codul generat ar arăta atunci astfel: + +```php +$__count = rand(1, 5); +for ($__i = 0; $__i < $__count; $__i++) { + // afișare conținut +} +``` + +Acum să luăm în considerare imbricarea: + +```latte +{repeat $countA} {* Ciclu exterior *} + {repeat $countB} {* Ciclu interior *} + ... + {/repeat} +{/repeat} +``` + +Dacă atât tag-ul exterior, cât și cel interior `{repeat}` ar genera cod folosind *aceleași* nume de variabile temporare (de exemplu, `$__count` și `$__i`), ciclul interior ar suprascrie variabilele ciclului exterior, ceea ce ar strica logica. + +Trebuie să ne asigurăm că variabilele temporare generate pentru fiecare instanță a tag-ului `{repeat}` sunt **unice**. Realizăm acest lucru folosind `PrintContext::generateId()`. Această metodă returnează un număr întreg unic în timpul fazei de compilare. Putem anexa acest ID la numele variabilelor noastre temporare. + +Deci, în loc de `$__count`, vom genera `$__count_1` pentru primul tag repeat, `$__count_2` pentru al doilea, etc. Similar, pentru contorul ciclului vom folosi `$__i_1`, `$__i_2`, etc. + + +Implementarea `RepeatNode` +-------------------------- + +Să creăm clasa de nod. + +```php +expectArguments(); // asigură că $count este furnizat + $node = $tag->node = new self; + // Parsează expresia numărului + $node->count = $tag->parser->parseExpression(); + // Obținerea conținutului intern + [$node->content] = yield; + return $node; + } + + /** + * Generează un ciclu PHP 'for' cu nume de variabile unice. + */ + public function print(PrintContext $context): string + { + // Generarea de nume de variabile unice + $id = $context->generateId(); + $countVar = '$__count_' . $id; // de ex. $__count_1, $__count_2, etc. + $iteratorVar = '$__i_' . $id; // de ex. $__i_1, $__i_2, etc. + + return $context->format( + <<<'XX' + // Evaluarea expresiei numărului *o singură dată* și stocarea + %raw = (int) (%node); + // Ciclu folosind numărul stocat și variabila de iterație unică + for (%raw = 0; %2.raw < %0.raw; %2.raw++) %line { + %node // Randarea conținutului intern + } + + XX, + $countVar, // %0 - Variabila pentru stocarea numărului + $this->count, // %1 - Nodul expresiei pentru număr + $iteratorVar, // %2 - Numele variabilei de iterație a ciclului + $this->position, // %3 - Comentariul cu numărul liniei pentru ciclul însuși + $this->content // %4 - Nodul conținutului intern + ); + } + + /** + * Furnizează nodurile copil (expresia numărului și conținutul). + */ + public function &getIterator(): \Generator + { + yield $this->count; + yield $this->content; + } +} +``` + +Metoda `create()` parsează expresia necesară `$count` folosind `parseExpression()`. Mai întâi este apelat `$tag->expectArguments()`. Acest lucru asigură că utilizatorul a furnizat *ceva* după `{repeat}`. În timp ce `$tag->parser->parseExpression()` ar eșua dacă nu s-ar furniza nimic, mesajul de eroare ar putea fi despre sintaxă neașteptată. Utilizarea `expectArguments()` oferă o eroare mult mai clară, specificând în mod specific că argumentele lipsesc pentru tag-ul `{repeat}`. + +Metoda `print()` generează codul PHP responsabil pentru executarea logicii de repetare la rulare. Începe prin generarea de nume unice pentru variabilele PHP temporare de care va avea nevoie. + +Metoda `$context->format()` este apelată cu un nou placeholder `%raw`, care inserează *șirul brut* furnizat ca argument corespunzător. Aici inserează numele unic al variabilei stocat în `$countVar` (de exemplu, `$__count_1`). Și ce zicem de `%0.raw` și `%2.raw`? Aceasta demonstrează **placeholder-uri poziționale**. În loc de doar `%raw`, care ia *următorul* argument brut disponibil, `%2.raw` ia explicit argumentul de la indexul 2 (care este `$iteratorVar`) și inserează valoarea sa brută de șir. Acest lucru ne permite să reutilizăm șirul `$iteratorVar` fără a-l transmite de mai multe ori în lista de argumente pentru `format()`. + +Acest apel `format()` atent construit generează un ciclu PHP eficient și sigur, care gestionează corect expresia numărului și evită coliziunile de nume de variabile chiar și atunci când tag-urile `{repeat}` sunt imbricate. + + +Înregistrare și utilizare +------------------------- + +Înregistrați tag-ul în extensia dvs.: + +```php +use App\Latte\RepeatNode; + +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), // Înregistrarea tag-ului repeat + ]; + } +} +``` + +Utilizați-l în șablon, inclusiv imbricarea: + +```latte +{var $rows = rand(5, 7)} +{var $cols = rand(3, 5)} + +{repeat $rows} +
                                                                                                        + {repeat $cols} + + {/repeat} + +{/repeat} +``` + +Acest exemplu demonstrează cum să gestionați starea (contoarele ciclurilor) și potențialele probleme de imbricare folosind variabile temporare cu prefixul `$__` și unice cu ID-uri de la `PrintContext::generateId()`. + + +n:atribute pure +--------------- + +În timp ce multe `n:atribute` precum `n:if` sau `n:foreach` servesc drept scurtături convenabile pentru omologii lor în tag-uri pereche (`{if}...{/if}`, `{foreach}...{/foreach}`), Latte permite, de asemenea, definirea de tag-uri care *există numai* sub formă de n:atribut. Acestea sunt adesea folosite pentru a modifica atributele sau comportamentul elementului HTML la care sunt atașate. + +Exemple standard încorporate în Latte includ [`n:class` |tags#n:class], care ajută la construirea dinamică a atributului `class`, și [`n:attr` |tags#n:attr], care poate seta mai multe atribute arbitrare. + +Să creăm propriul nostru n:atribut pur: `n:confirm`, care adaugă un dialog de confirmare JavaScript înainte de a efectua o acțiune (cum ar fi urmărirea unui link sau trimiterea unui formular). + +**Scop:** Implementarea `n:confirm="'Sunteți sigur?'"`, care adaugă un handler `onclick` pentru a preveni acțiunea implicită dacă utilizatorul anulează dialogul de confirmare. + + +Implementarea `ConfirmNode` +--------------------------- + +Avem nevoie de o clasă Node și o funcție de parsare. + +```php +expectArguments(); + $node = $tag->node = new self; + $node->message = $tag->parser->parseExpression(); + return $node; + } + + /** + * Generează codul atributului 'onclick' cu escapare corectă. + */ + public function print(PrintContext $context): string + { + // Asigură escaparea corectă pentru contextele JavaScript și atribut HTML. + return $context->format( + <<<'XX' + echo ' onclick="', LR\Filters::escapeHtmlAttr('return confirm(' . LR\Filters::escapeJs(%node) . ')'), '"' %line; + XX, + $this->message, + $this->position, + ); + } + + public function &getIterator(): \Generator + { + yield $this->message; + } +} +``` + +Metoda `print()` generează cod PHP care, în final, în timpul randării șablonului, va afișa atributul HTML `onclick="..."`. Gestionarea contextelor imbricate (JavaScript în interiorul unui atribut HTML) necesită o escapare atentă. Filtrul `LR\Filters::escapeJs(%node)` este apelat la rulare și escapează mesajul corect pentru utilizare în interiorul JavaScript (ieșirea ar fi ca `"Sigur?"`). Apoi, filtrul `LR\Filters::escapeHtmlAttr(...)` escapează caracterele care sunt speciale în atributele HTML, astfel încât ar schimba ieșirea la `return confirm("Sigur?")`. Această escapare în două etape la rulare asigură că mesajul este sigur pentru JavaScript și codul JavaScript rezultat este sigur pentru inserarea într-un atribut HTML `onclick`. + + +Înregistrare și utilizare +------------------------- + +Înregistrați n:atributul în extensia dvs. Nu uitați prefixul `n:` în cheie: + +```php +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), + 'n:confirm' => ConfirmNode::create(...), // Înregistrarea n:confirm + ]; + } +} +``` + +Acum puteți utiliza `n:confirm` pe linkuri, butoane sau elemente de formular: + +```latte +Șterge +``` + +HTML generat: + +```html +Șterge +``` + +Când utilizatorul face clic pe link, browserul execută codul `onclick`, afișează dialogul de confirmare și navighează la `delete.php` numai dacă utilizatorul face clic pe "OK". + +Acest exemplu demonstrează cum se poate crea un n:atribut pur pentru a modifica comportamentul sau atributele elementului său HTML gazdă, generând codul PHP adecvat în metoda sa `print()`. Nu uitați de dubla escapare, care este adesea necesară: o dată pentru contextul țintă (JavaScript în acest caz) și din nou pentru contextul atributului HTML. + + +Subiecte avansate +================= + +În timp ce secțiunile anterioare acoperă conceptele de bază, iată câteva subiecte mai avansate pe care le puteți întâlni la crearea de tag-uri Latte personalizate. + + +Moduri de ieșire a tag-urilor +----------------------------- + +Obiectul `Tag` transmis funcției dvs. `create()` are o proprietate `outputMode`. Această proprietate influențează modul în care Latte tratează spațiile și indentarea înconjurătoare, în special atunci când tag-ul este utilizat pe propria linie. Puteți modifica această proprietate în funcția dvs. `create()`. + +- `Tag::OutputKeepIndentation` (Implicit pentru majoritatea tag-urilor precum `{=...}`): Latte încearcă să păstreze indentarea dinaintea tag-ului. Liniile noi *după* tag sunt în general păstrate. Acest lucru este potrivit pentru tag-urile care afișează conținut în linie. +- `Tag::OutputRemoveIndentation` (Implicit pentru tag-uri de bloc precum `{if}`, `{foreach}`): Latte elimină indentarea inițială și potențial o linie nouă următoare. Acest lucru ajută la menținerea codului PHP generat mai curat și previne liniile goale suplimentare în ieșirea HTML cauzate de tag-ul însuși. Utilizați acest lucru pentru tag-urile care reprezintă structuri de control sau blocuri care nu ar trebui să adauge ele însele spații. +- `Tag::OutputNone` (Utilizat de tag-uri precum `{var}`, `{default}`): Similar cu `RemoveIndentation`, dar semnalează mai puternic că tag-ul însuși nu produce ieșire directă, potențial influențând procesarea spațiilor din jurul său și mai agresiv. Potrivit pentru tag-uri declarative sau de setare. + +Alegeți modul care se potrivește cel mai bine scopului tag-ului dvs. Pentru majoritatea tag-urilor structurale sau de control, `OutputRemoveIndentation` este de obicei potrivit. + + +Accesarea tag-urilor părinte/cele mai apropiate +----------------------------------------------- + +Uneori, comportamentul unui tag trebuie să depindă de contextul în care este utilizat, în mod specific în ce tag(uri) părinte se află. Obiectul `Tag` transmis funcției dvs. `create()` oferă metoda `closestTag(array $classes, ?callable $condition = null): ?Tag` exact în acest scop. + +Această metodă caută în sus în ierarhia tag-urilor deschise în prezent (inclusiv elementele HTML reprezentate intern în timpul parsării) și returnează obiectul `Tag` al celui mai apropiat strămoș care corespunde criteriilor specifice. Dacă nu este găsit niciun strămoș corespunzător, returnează `null`. + +Array-ul `$classes` specifică ce tip de tag-uri strămoșe căutați. Verifică dacă nodul asociat al tag-ului strămoș (`$ancestorTag->node`) este o instanță a acestei clase. + +```php +function create(Tag $tag) +{ + // Căutarea celui mai apropiat tag strămoș al cărui nod este o instanță a ForeachNode + $foreachTag = $tag->closestTag([ForeachNode::class]); + if ($foreachTag) { + // Putem accesa instanța ForeachNode însăși: + $foreachNode = $foreachTag->node; + } +} +``` + +Observați `$foreachTag->node`: Acest lucru funcționează numai pentru că este o convenție în dezvoltarea tag-urilor Latte să atribuiți imediat nodul creat la `$tag->node` în cadrul metodei `create()`, așa cum am făcut întotdeauna. + +Uneori, simpla comparare a tipului de nod nu este suficientă. Este posibil să trebuiască să verificați o proprietate specifică a tag-ului strămoș potențial sau a nodului său. Al doilea argument opțional pentru `closestTag()` este un callable care primește obiectul `Tag` strămoș potențial și ar trebui să returneze dacă este o potrivire validă. + +```php +function create(Tag $tag) +{ + $dynamicBlockTag = $tag->closestTag( + [BlockNode::class], + // Condiție: blocul trebuie să fie dinamic + fn(Tag $blockTag) => $blockTag->node->block->isDynamic(), + ); +} +``` + +Utilizarea `closestTag()` permite crearea de tag-uri care sunt conștiente de context și impun utilizarea corectă în cadrul structurii șablonului dvs., ceea ce duce la șabloane mai robuste și mai ușor de înțeles. + + +Placeholder-uri `PrintContext::format()` +---------------------------------------- + +Am folosit adesea `PrintContext::format()` pentru a genera cod PHP în metodele `print()` ale nodurilor noastre. Acesta acceptă un șir mască și argumente ulterioare care înlocuiesc placeholder-urile din mască. Iată un rezumat al placeholder-urilor disponibile: + +- **`%node`**: Argumentul trebuie să fie o instanță `Node`. Apelează metoda `print()` a nodului și inserează șirul de cod PHP rezultat. +- **`%dump`**: Argumentul este orice valoare PHP. Exportă valoarea în cod PHP valid. Potrivit pentru scalari, array-uri, null. + - `$context->format('echo %dump;', 'Hello')` -> `echo 'Hello';` + - `$context->format('$arr = %dump;', [1, 2])` -> `$arr = [1, 2];` +- **`%raw`**: Inserează argumentul direct în codul PHP de ieșire fără nicio escapare sau modificare. **Utilizați cu precauție**, în principal pentru inserarea de fragmente de cod PHP pregenerate sau nume de variabile. + - `$context->format('%raw = 1;', '$variableName')` -> `$variableName = 1;` +- **`%args`**: Argumentul trebuie să fie `Expression\ArrayNode`. Afișează elementele array-ului formatate ca argumente pentru un apel de funcție sau metodă (separate prin virgulă, gestionează argumentele numite dacă sunt prezente). + - `$argsNode = new ArrayNode([...]);` + - `$context->format('myFunc(%args);', $argsNode)` -> `myFunc(1, name: 'Joe');` +- **`%line`**: Argumentul trebuie să fie un obiect `Position` (de obicei `$this->position`). Inserează un comentariu PHP `/* line X */` indicând numărul liniei sursă. + - `$context->format('echo "Hi" %line;', $this->position)` -> `echo "Hi" /* line 42 */;` +- **`%escape(...)`**: Generează cod PHP care *la rulare* escapează expresia internă folosind regulile de escapare curente conștiente de context. + - `$context->format('echo %escape(%node);', $variableNode)` +- **`%modify(...)`**: Argumentul trebuie să fie `ModifierNode`. Generează cod PHP care aplică filtrele specificate în `ModifierNode` conținutului intern, inclusiv escaparea conștientă de context, dacă nu este dezactivată folosind `|noescape`. + - `$context->format('%modify(%node);', $modifierNode, $variableNode)` +- **`%modifyContent(...)`**: Similar cu `%modify`, dar destinat modificării blocurilor de conținut capturat (adesea HTML). + +Puteți face referire explicită la argumente după indexul lor (începând de la zero): `%0.node`, `%1.dump`, `%2.raw`, etc. Acest lucru permite reutilizarea unui argument de mai multe ori în mască fără a-l transmite în mod repetat la `format()`. Vezi exemplul tag-ului `{repeat}`, unde au fost utilizate `%0.raw` și `%2.raw`. + + +Exemplu de parsare complexă a argumentelor +------------------------------------------ + +În timp ce `parseExpression()`, `parseArguments()`, etc., acoperă multe cazuri, uneori aveți nevoie de o logică de parsare mai complexă folosind `TokenStream`-ul de nivel inferior disponibil prin `$tag->parser->stream`. + +**Scop:** Crearea unui tag `{embedYoutube $videoID, width: 640, height: 480}`. Dorim să parsăm ID-ul video necesar (șir sau variabilă) urmat de perechi opționale cheie-valoare pentru dimensiuni. + +```php +expectArguments(); + $node = $tag->node = new self; + // Parsarea ID-ului video necesar + $node->videoId = $tag->parser->parseExpression(); + + // Parsarea perechilor opționale cheie-valoare + $stream = $tag->parser->stream; // Obținerea fluxului de token-uri + while ($stream->tryConsume(',')) { // Necesită separare prin virgulă + // Așteptarea identificatorului 'width' sau 'height' + $keyToken = $stream->consume(Token::Php_Identifier); + $key = strtolower($keyToken->text); + + $stream->consume(':'); // Așteptarea separatorului două puncte + + $value = $tag->parser->parseExpression(); // Parsarea expresiei valorii + + if ($key === 'width') { + $node->width = $value; + } elseif ($key === 'height') { + $node->height = $value; + } else { + throw new CompileException("Argument necunoscut '$key'. Așteptat 'width' sau 'height'.", $keyToken->position); + } + } + + return $node; + } +} +``` + +Acest nivel de control vă permite să definiți sintaxe foarte specifice și complexe pentru tag-urile dvs. personalizate interacționând direct cu fluxul de token-uri. + + +Utilizarea `AuxiliaryNode` +-------------------------- + +Latte oferă noduri "auxiliare" generice pentru situații speciale în timpul generării codului sau în cadrul trecerilor de compilare. Acestea sunt `AuxiliaryNode` și `Php\Expression\AuxiliaryNode`. + +Considerați `AuxiliaryNode` ca un nod container flexibil care deleagă funcționalitățile sale de bază - generarea codului și expunerea nodurilor copil - argumentelor furnizate în constructorul său: + +- Delegarea `print()`: Primul argument al constructorului este o **closure** PHP. Când Latte apelează metoda `print()` pe `AuxiliaryNode`, execută această closure furnizată. Closure primește `PrintContext` și orice noduri transmise în al doilea argument al constructorului, permițându-vă să definiți o logică complet personalizată de generare a codului PHP la rulare. +- Delegarea `getIterator()`: Al doilea argument al constructorului este un **array de obiecte `Node`**. Când Latte trebuie să parcurgă copiii `AuxiliaryNode` (de exemplu, în timpul trecerilor de compilare), metoda sa `getIterator()` furnizează pur și simplu nodurile listate în acest array. + +Exemplu: + +```php +$node = new AuxiliaryNode( + // 1. Această closure devine corpul print() + fn(PrintContext $context, $arg1, $arg2) => $context->format('...%node...%node...', $arg1, $arg2), + + // 2. Aceste noduri sunt furnizate de metoda getIterator() și transmise closure-ului de mai sus + [$argumentNode1, $argumentNode2] +); +``` + +Latte oferă două tipuri distincte bazate pe locul unde trebuie să inserați codul generat: + +- `Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode`: Utilizați acest lucru când trebuie să generați o bucată de cod PHP care reprezintă o **expresie** +- `Latte\Compiler\Nodes\AuxiliaryNode`: Utilizați acest lucru în scopuri mai generale, când trebuie să inserați un bloc de cod PHP reprezentând una sau mai multe **instrucțiuni** + +Un motiv important pentru a utiliza `AuxiliaryNode` în loc de noduri standard (cum ar fi `StaticMethodCallNode`) în cadrul metodei dvs. `print()` sau a trecerii de compilare este **controlul vizibilității pentru trecerile de compilare ulterioare**, în special cele legate de securitate, cum ar fi Sandbox. + +Luați în considerare un scenariu: Trecerea dvs. de compilare trebuie să încapsuleze o expresie furnizată de utilizator (`$userExpr`) într-un apel la o funcție auxiliară specifică, de încredere `myInternalSanitize($userExpr)`. Dacă creați un nod standard `new FunctionCallNode('myInternalSanitize', [$userExpr])`, acesta va fi complet vizibil pentru parcurgerea AST. Dacă trecerea Sandbox rulează mai târziu și `myInternalSanitize` *nu* este pe lista sa de permise, Sandbox poate *bloca* sau modifica acest apel, potențial perturbând logica internă a tag-ului dvs., chiar dacă *dvs.*, autorul tag-ului, știți că acest apel specific este sigur și necesar. Puteți, prin urmare, genera apelul direct în cadrul closure-ului `AuxiliaryNode`. + +```php +use Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode; + +// ... în interiorul print() sau al trecerii de compilare ... +$wrappedNode = new AuxiliaryNode( + fn(PrintContext $context, $userExpr) => $context->format( + 'myInternalSanitize(%node)', // Generarea directă a codului PHP + $userExpr, + ), + // IMPORTANT: Transmiteți în continuare nodul original al expresiei utilizatorului aici! + [$userExpr], +); +``` + +În acest caz, trecerea Sandbox vede `AuxiliaryNode`, dar **nu analizează codul PHP generat de closure-ul său**. Nu poate bloca direct apelul `myInternalSanitize` generat *în interiorul* closure-ului. + +În timp ce codul PHP generat însuși este ascuns de treceri, *intrările* în acest cod (nodurile reprezentând datele sau expresiile utilizatorului) **trebuie să fie în continuare parcurse**. De aceea, al doilea argument al constructorului `AuxiliaryNode` este esențial. **Trebuie** să transmiteți un array care conține toate nodurile originale (cum ar fi `$userExpr` în exemplul de mai sus) pe care le utilizează closure-ul dvs. `getIterator()` al `AuxiliaryNode` **va furniza aceste noduri**, permițând trecerilor de compilare precum Sandbox să le analizeze pentru potențiale probleme. + + +Cele mai bune practici +====================== + +- **Scop clar:** Asigurați-vă că tag-ul dvs. are un scop clar și necesar. Nu creați tag-uri pentru sarcini care pot fi ușor rezolvate folosind [filtre |custom-filters] sau [funcții |custom-functions]. +- **Implementați corect `getIterator()`:** Implementați întotdeauna `getIterator()` și furnizați *referințe* (`&`) la *toate* nodurile copil (argumente, conținut) care au fost parsate din șablon. Acest lucru este necesar pentru trecerile de compilare, securitate (Sandbox) și potențiale optimizări viitoare. +- **Proprietăți publice pentru noduri:** Faceți publice proprietățile care conțin noduri copil, astfel încât trecerile de compilare să le poată modifica dacă este necesar. +- **Utilizați `PrintContext::format()`:** Folosiți metoda `format()` pentru a genera cod PHP. Gestionează ghilimelele, escapează corect placeholder-urile și adaugă automat comentarii cu numărul liniei. +- **Variabile temporare (`$__`):** La generarea codului PHP de rulare care necesită variabile temporare (de exemplu, pentru stocarea subtotalurilor, contoarelor ciclurilor), utilizați convenția prefixului `$__` pentru a evita coliziunile cu variabilele utilizatorului și variabilele interne Latte `$ʟ_`. +- **Imbricare și ID-uri unice:** Dacă tag-ul dvs. poate fi imbricat sau necesită o stare specifică instanței la rulare, utilizați `$context->generateId()` în cadrul metodei dvs. `print()` pentru a crea sufixe unice pentru variabilele dvs. temporare `$__`. +- **Furnizori pentru date externe:** Utilizați furnizori (înregistrați prin `Extension::getProviders()`) pentru a accesa date sau servicii de rulare ($this->global->...) în loc să hardcodați valori sau să vă bazați pe starea globală. Utilizați prefixe de producător pentru numele furnizorilor. +- **Luați în considerare n:atributele:** Dacă tag-ul dvs. pereche operează logic pe un singur element HTML, Latte oferă probabil suport automat pentru `n:atribut`. Țineți cont de acest lucru pentru confortul utilizatorului. Dacă creați un tag de modificare a atributelor, luați în considerare dacă un `n:atribut` pur este forma cea mai potrivită. +- **Testare:** Scrieți teste pentru tag-urile dvs., acoperind atât parsarea diferitelor intrări sintactice, cât și corectitudinea ieșirii generate de **codul PHP**. + +Urmând aceste instrucțiuni, puteți crea tag-uri personalizate puternice, robuste și sustenabile, care se integrează perfect cu motorul de șabloane Latte. + +.[note] +Studierea claselor de noduri care fac parte din Latte este cel mai bun mod de a învăța toate detaliile procesului de parsare. diff --git a/latte/ro/develop.texy b/latte/ro/develop.texy index dfa5f50b60..219198e6af 100644 --- a/latte/ro/develop.texy +++ b/latte/ro/develop.texy @@ -1,70 +1,66 @@ -Practici pentru dezvoltatori -**************************** +Practici de dezvoltare +********************** -Instalare .[#toc-installation] -============================== +Instalare +========= -Cel mai bun mod de a instala Latte este de a utiliza un Composer: +Cel mai bun mod de a instala Latte este folosind Composer: ```shell composer require latte/latte ``` -Versiuni PHP acceptate (se aplică la cele mai recente versiuni Latte patch): +Versiuni PHP suportate (se aplică pentru ultimele versiuni patch ale Latte): -| versiune | compatibil cu PHP +| versiune | compatibil cu PHP |-----------------|------------------- -| Latte 3.0 | PHP 8.0 - 8.2 -| Latte 2.11 | PHP 7.1 - 8.2 -| Latte 2.8 - 2.10| PHP 7.1 - 8.1 +| Latte 3.0 | PHP 8.0 – 8.2 -Cum se redă un șablon .[#toc-how-to-render-a-template] -====================================================== +Cum se randează un șablon +========================= -Cum se redă un șablon? Folosiți doar acest cod simplu: +Cum se randează un șablon? Este suficient acest cod simplu: ```php $latte = new Latte\Engine; -// director de cache +// director pentru cache $latte->setTempDirectory('/path/to/tempdir'); -$params = [ /* template variables */ ]; +$params = [ /* variabile șablon */ ]; // sau $params = new TemplateParameters(/* ... */); -// redare la ieșire +// randează la ieșire $latte->render('template.latte', $params); -// sau redă în variabilă +// randează într-o variabilă $output = $latte->renderToString('template.latte', $params); ``` -Parametrii pot fi array-uri sau chiar mai bine [obiecte |#Parameters as a class], care vor asigura verificarea tipului și sugestii în editor. +Parametrii pot fi array-uri sau, și mai bine, un [obiect |#Parametrii ca o clasă], care asigură verificarea tipului și sugestii în editori. .[note] -Puteți găsi, de asemenea, exemple de utilizare în depozitul [Latte examples |https://github.com/nette-examples/latte]. +Exemple de utilizare găsiți și în depozitul [Latte examples |https://github.com/nette-examples/latte]. -Performanță și memorare în cache .[#toc-performance-and-caching] -================================================================ +Performanță și cache +==================== -Șabloanele Latte sunt extrem de rapide, deoarece Latte le compilează direct în cod PHP și le stochează pe disc. Astfel, ele nu au niciun fel de costuri suplimentare în comparație cu șabloanele scrise în PHP pur. +Șabloanele în Latte sunt extrem de rapide, deoarece Latte le compilează direct în cod PHP și le salvează în cache pe disc. Prin urmare, nu au nicio suprasarcină suplimentară față de șabloanele scrise în PHP pur. -Memoria cache este regenerată automat de fiecare dată când modificați fișierul sursă. Astfel, puteți edita comod șabloanele Latte în timpul dezvoltării și puteți vedea imediat modificările în browser. Puteți dezactiva această caracteristică într-un mediu de producție și economisi puțină performanță: +Cache-ul se regenerează automat de fiecare dată când modificați fișierul sursă. În timpul dezvoltării, editați confortabil șabloanele în Latte și vedeți imediat modificările în browser. Puteți dezactiva această funcție în mediul de producție pentru a economisi puțină performanță: ```php $latte->setAutoRefresh(false); ``` -Atunci când este implementat pe un server de producție, generarea inițială a cache-ului, în special pentru aplicațiile mari, poate dura, în mod normal, ceva timp. Latte are o prevenire încorporată împotriva "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. -Aceasta este o situație în care serverul primește un număr mare de solicitări simultane și, deoarece memoria cache a Latte nu există încă, toate acestea o vor genera în același timp. Ceea ce face ca CPU să crească brusc. -Latte este inteligent și, atunci când există mai multe cereri concurente, doar primul fir generează memoria cache, celelalte așteaptă și apoi o utilizează. +La implementarea pe serverul de producție, generarea inițială a cache-ului, în special pentru aplicații mai mari, poate dura, desigur, puțin timp. Latte are încorporată prevenirea "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Este o situație în care un număr mare de cereri concurente ajung simultan, care pornesc Latte, și deoarece cache-ul încă nu există, toate ar începe să-l genereze simultan. Ceea ce ar suprasolicita nejustificat serverul. Latte este inteligent și, în cazul mai multor cereri concurente, generează cache-ul doar primul fir de execuție, celelalte așteaptă și apoi îl utilizează. -Parametrii ca o clasă .[#toc-parameters-as-a-class] -=================================================== +Parametrii ca o clasă +===================== -Mai bine decât să transmiteți variabilele șablonului sub formă de array-uri este să creați o clasă. Obțineți o [notație sigură din punct de vedere al tipului |type-system], o [sugestie plăcută în IDE |recipes#Editors and IDE] și o modalitate de a [înregistra filtre |extending-latte#Filters Using the Class] și [funcții |extending-latte#Functions Using the Class]. +Mai bine decât să transmiteți variabilele șablonului ca array este să creați o clasă. Veți obține astfel o [scriere sigură din punct de vedere al tipului|type-system], [sugestii utile în IDE |recipes#Editoare și IDE-uri] și o cale pentru [înregistrarea filtrelor |custom-filters#Filtre care utilizează o clasă cu atribute] și [funcțiilor |custom-functions#Funcții care utilizează o clasă cu atribute]. ```php class MailTemplateParameters @@ -88,12 +84,12 @@ $latte->render('mail.latte', new MailTemplateParameters( ``` -Dezactivarea scăpării automate a variabilelor .[#toc-disabling-auto-escaping-of-variable] -========================================================================================= +Dezactivarea auto-escapării variabilelor +======================================== -Dacă variabila conține un șir de caractere HTML, o puteți marca astfel încât Latte să nu o evadeze automat (și, prin urmare, dublu). Astfel, se evită necesitatea de a specifica `|noescape` în șablon. +Dacă o variabilă conține un șir în HTML, o puteți marca astfel încât Latte să nu o escapeze automat (și deci dublu). Veți evita astfel necesitatea de a specifica în șablon `|noescape`. -Cea mai simplă metodă este să înfășurați șirul într-un obiect `Latte\Runtime\Html`: +Cea mai simplă cale este să înfășurați șirul într-un obiect `Latte\Runtime\Html`: ```php $params = [ @@ -101,7 +97,7 @@ $params = [ ]; ``` -De asemenea, Latte nu evadează toate obiectele care implementează interfața `Latte\HtmlStringable`. Astfel, vă puteți crea propria clasă a cărei metodă `__toString()` va returna codul HTML care nu va fi scăpat automat: +Latte nu mai escapează toate obiectele care implementează interfața `Latte\HtmlStringable`. Puteți astfel crea propria clasă, a cărei metodă `__toString()` va returna cod HTML care nu va fi escapat automat: ```php class Emphasis extends Latte\HtmlStringable @@ -123,32 +119,85 @@ $params = [ ``` .[warning] -Metoda `__toString` trebuie să returneze HTML corect și să asigure scăparea parametrilor, altfel poate apărea o vulnerabilitate XSS! +Metoda `__toString` trebuie să returneze HTML corect și să asigure escaparea parametrilor, altfel poate apărea o vulnerabilitate XSS! -Cum să extindeți Latte cu filtre, etichete etc. .[#toc-how-to-extend-latte-with-filters-tags-etc] -================================================================================================= +Cum să extindeți Latte cu filtre, tag-uri etc. +============================================== -Cum să adăugați un filtru, o funcție, o etichetă etc. personalizat la Latte? Aflați în capitolul [Extinderea Latte |extending Latte]. -Dacă doriți să refolosiți modificările în diferite proiecte sau dacă doriți să le partajați cu alții, ar trebui să [creați |creating-extension] apoi [o extensie |creating-extension]. +Cum să adăugați în Latte propriul filtru, funcție, tag etc? Despre aceasta tratează capitolul [extindem Latte |extending-latte]. Dacă doriți să reutilizați modificările dvs. în diferite proiecte sau să le partajați cu alții, ar trebui să [creați o extensie |extending-latte#Latte Extension]. -Orice cod în șablon `{php ...}` .{data-version:3.0}{toc: RawPhpExtension} -========================================================================= +Cod arbitrar în șablon `{php ...}` .{toc: RawPhpExtension} +========================================================== -Doar expresiile PHP pot fi scrise în interiorul [`{do}` |tags#do] așa că nu puteți, de exemplu, să introduceți construcții precum `if ... else` sau instrucțiuni terminate cu punct și virgulă. +În interiorul tag-ului [`{do}` |tags#do] se pot scrie doar expresii PHP, nu puteți, de exemplu, să inserați construcții precum `if ... else` sau instrucțiuni terminate cu punct și virgulă. -Cu toate acestea, puteți înregistra extensia `RawPhpExtension`, care adaugă tag-ul `{php ...}`, care poate fi utilizat pentru a insera orice cod PHP, pe riscul autorului șablonului. +Puteți însă înregistra extensia `RawPhpExtension`, care adaugă tag-ul `{php ...}`. Folosind acesta, puteți insera orice cod PHP. Nu i se aplică nicio regulă a modului sandbox, utilizarea fiind deci responsabilitatea autorului șablonului. ```php $latte->addExtension(new Latte\Essential\RawPhpExtension); ``` -Traducerea în șabloane .{data-version:3.0}{toc: TranslatorExtension} -==================================================================== +Verificarea codului generat .{data-version:3.0.7} +================================================= + +Latte compilează șabloanele în cod PHP. Desigur, are grijă ca codul generat să fie valid sintactic. Cu toate acestea, la utilizarea extensiilor terțe sau a `RawPhpExtension`, Latte nu poate garanta corectitudinea fișierului generat. De asemenea, se poate scrie în PHP cod care, deși este sintactic corect, este interzis (de exemplu, atribuirea unei valori variabilei `$this`) și provoacă o eroare de compilare PHP (Compile Error). Dacă scrieți o astfel de operație în șablon, aceasta ajunge și în codul PHP generat. Deoarece în PHP există peste două sute de operații interzise diferite, Latte nu are ambiția de a le detecta. Le semnalează abia PHP-ul însuși la randare, ceea ce de obicei nu deranjează. + +Există însă situații în care doriți să știți deja în momentul compilării șablonului că nu conține nicio eroare de compilare PHP. În special atunci când șabloanele pot fi editate de utilizatori sau utilizați [Sandbox |sandbox]. În acest caz, lăsați șabloanele să fie verificate deja în timpul compilării. Activați această funcționalitate cu metoda `Engine::enablePhpLint()`. Deoarece pentru verificare are nevoie să apeleze binarul PHP, transmiteți calea către acesta ca parametru: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); + +try { + $latte->compile('home.latte'); +} catch (Latte\CompileException $e) { + // prinde erorile din Latte și, de asemenea, Compile Error din PHP + echo 'Error: ' . $e->getMessage(); +} +``` + + +Setări regionale .{data-version:3.0.18}{toc: Locale} +==================================================== + +Latte permite setarea setărilor regionale, care influențează formatarea numerelor, datelor și sortarea. Se setează folosind metoda `setLocale()`. Identificatorul de mediu se ghidează după standardul IETF language tag, care utilizează extensia PHP `intl`. Se compune din codul limbii și, eventual, codul țării, de ex. `en_US` pentru engleza din Statele Unite, `de_DE` pentru germana din Germania etc. + +```php +$latte = new Latte\Engine; +$latte->setLocale('ro_RO'); +``` + +Setarea mediului influențează filtrele [localDate |filters#localDate], [sort |filters#sort], [number |filters#number] și [bytes |filters#bytes]. + +.[note] +Necesită extensia PHP `intl`. Setarea în Latte nu influențează setarea globală a locale în PHP. + + +Mod strict .{data-version:3.0.8} +================================ + +În modul strict de parsare, Latte verifică dacă nu lipsesc tag-urile HTML de închidere și, de asemenea, interzice utilizarea variabilei `$this`. Îl activați astfel: + +```php +$latte = new Latte\Engine; +$latte->setStrictParsing(); +``` + +Generarea șabloanelor cu antetul `declare(strict_types=1)` o activați astfel: + +```php +$latte = new Latte\Engine; +$latte->setStrictTypes(); +``` -Utilizați extensia `TranslatorExtension` pentru a adăuga [`{_...}` |tags#_], [`{translate}` |tags#translate] și să filtreze [`translate` |filters#translate] la șablon. Acestea sunt utilizate pentru a traduce valori sau părți ale șablonului în alte limbi. Parametrul este metoda (PHP callable) care efectuează traducerea: + +Traducerea în șabloane .{toc: TranslatorExtension} +================================================== + +Folosind extensia `TranslatorExtension`, adăugați în șablon tag-urile [`{_...}` |tags#], [`{translate}` |tags#translate] și filtrul [`translate` |filters#translate]. Acestea servesc la traducerea valorilor sau a părților șablonului în alte limbi. Ca parametru specificăm metoda (PHP callable) care efectuează traducerea: ```php class MyTranslator @@ -158,7 +207,7 @@ class MyTranslator public function translate(string $original): string { - // creați $translated din $original în conformitate cu $this->lang + // din $original creăm $translated conform $this->lang return $translated; } } @@ -170,7 +219,7 @@ $extension = new Latte\Essential\TranslatorExtension( $latte->addExtension($extension); ``` -Traducătorul este apelat în timpul execuției atunci când șablonul este redat. Cu toate acestea, Latte poate traduce toate textele statice în timpul compilării șablonului. Acest lucru economisește performanță, deoarece fiecare șir de caractere este tradus o singură dată, iar traducerea rezultată este scrisă în fișierul compilat. Astfel, se creează mai multe versiuni compilate ale șablonului în directorul cache, una pentru fiecare limbă. Pentru a face acest lucru, trebuie doar să specificați limba ca al doilea parametru: +Translatorul este apelat în timpul rulării la randarea șablonului. Latte poate însă traduce toate textele statice deja în timpul compilării șablonului. Astfel se economisește performanță, deoarece fiecare șir se traduce o singură dată, iar traducerea rezultată se scrie în forma compilată. În directorul cu cache vor apărea astfel mai multe versiuni compilate ale șablonului, una pentru fiecare limbă. Pentru aceasta este suficient doar să specificați limba ca al doilea parametru: ```php $extension = new Latte\Essential\TranslatorExtension( @@ -179,9 +228,9 @@ $extension = new Latte\Essential\TranslatorExtension( ); ``` -Prin text static înțelegem, de exemplu, `{_'hello'}` sau `{translate}hello{/translate}`. Textul nestatic, cum ar fi `{_$foo}`, va continua să fie tradus în timpul execuției. +Prin text static se înțelege, de exemplu, `{_'hello'}` sau `{translate}hello{/translate}`. Textele nestatice, cum ar fi `{_$foo}`, se vor traduce în continuare în timpul rulării. -Șablonul poate, de asemenea, să transmită parametrii suplimentari traducătorului prin intermediul `{_$original, foo: bar}` sau `{translate foo: bar}`, pe care acesta îi primește sub forma matricei `$params`: +Translatorului i se pot transmite din șablon și parametri suplimentari folosind `{_$original, foo: bar}` sau `{translate foo: bar}`, pe care îi primește ca array `$params` în al doilea argument: ```php public function translate(string $original, ...$params): string @@ -191,40 +240,41 @@ public function translate(string $original, ...$params): string ``` -Depanarea și Tracy .[#toc-debugging-and-tracy] -============================================== +Depanare și Tracy +================= -Latte încearcă să facă dezvoltarea cât mai plăcută posibil. În scopuri de depanare, există trei etichete [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak] și [`{trace}` |tags#trace]. +Latte încearcă să vă facă dezvoltarea cât mai plăcută posibil. Direct în scopuri de depanare există trei tag-uri [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak] și [`{trace}` |tags#trace]. -Veți obține cel mai mult confort dacă instalați instrumentul de depanare excelent [Tracy |tracy:] și activați pluginul Latte: +Cel mai mare confort îl obțineți dacă instalați și excelentul [instrument de depanare Tracy|tracy:] și activați add-on-ul pentru Latte: ```php -// permite Tracy +// pornește Tracy Tracy\Debugger::enable(); $latte = new Latte\Engine; -// activează extensia lui Tracy +// activează extensia pentru Tracy $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension); ``` -Veți vedea acum toate erorile într-un ecran roșu îngrijit, inclusiv erorile din șabloane cu evidențierea rândurilor și coloanelor ([video |https://github.com/nette/tracy/releases/tag/v2.9.0]). -În același timp, în colțul din dreapta jos, în așa-numita Tracy Bar, apare o filă pentru Latte, unde puteți vedea clar toate șabloanele redate și relațiile dintre ele (inclusiv posibilitatea de a face clic în șablon sau în codul compilat), precum și variabilele: +Acum vi se vor afișa toate erorile într-un ecran roșu clar, inclusiv erorile din șabloane cu evidențierea rândului și coloanei ([video|https://github.com/nette/tracy/releases/tag/v2.9.0]). În același timp, în colțul din dreapta jos, în așa-numitul Tracy Bar, va apărea o filă pentru Latte, unde sunt vizibile clar toate șabloanele randate și relațiile lor reciproce (inclusiv posibilitatea de a face clic pentru a ajunge la șablon sau la codul compilat) și, de asemenea, variabilele: [* latte-debugging.webp *] -Deoarece Latte compilează șabloanele în cod PHP lizibil, puteți să le parcurgeți în mod convenabil în IDE-ul dumneavoastră. +Deoarece Latte compilează șabloanele în cod PHP clar, le puteți parcurge pas cu pas confortabil în IDE-ul dvs. -Linter: Validarea sintaxei șablonului .{data-version:2.11}{toc: Linter} -======================================================================= +Linter: validarea sintaxei șabloanelor .{toc: Linter} +===================================================== -Instrumentul Linter vă va ajuta să treceți prin toate șabloanele și să verificați dacă există erori de sintaxă. Acesta se lansează din consolă: +Pentru a parcurge toate șabloanele și a verifica dacă nu conțin erori sintactice, vă ajută instrumentul Linter. Se lansează din consolă: ```shell -vendor/bin/latte-lint +vendor/bin/latte-lint ``` -Dacă utilizați etichete personalizate, creați și Linterul personalizat, de exemplu `custom-latte-lint`: +Parametrul `--strict` activează [modul strict |#Mod strict]. + +Dacă utilizați tag-uri personalizate, creați-vă și propria versiune de Linter, de ex. `custom-latte-lint`: ```php #!/usr/bin/env php @@ -233,24 +283,30 @@ Dacă utilizați etichete personalizate, creați și Linterul personalizat, de e // introduceți calea reală către fișierul autoload.php require __DIR__ . '/vendor/autoload.php'; -$linter = new Latte\Tools\Linter($engine); -$linter->scanDirectory($path); +$path = $argv[1] ?? '.'; -$engine = new Latte\Engine; -// înregistrează extensiile individuale aici -$engine->addExtension(/* ... */); +$linter = new Latte\Tools\Linter; +$latte = $linter->getEngine(); +// adăugați aici extensiile dvs. individuale +$latte->addExtension(/* ... */); -$path = $argv[1]; -$linter = new Latte\Tools\Linter(engine: $engine); $ok = $linter->scanDirectory($path); exit($ok ? 0 : 1); ``` +Alternativ, puteți transmite propriul obiect `Latte\Engine` către Linter: + +```php +$latte = new Latte\Engine; +// aici configurăm obiectul $latte +$linter = new Latte\Tools\Linter(engine: $latte); +``` + -Încărcarea șabloanelor dintr-un șir de caractere .[#toc-loading-templates-from-a-string] -======================================================================================== +Încărcarea șabloanelor dintr-un șir +=================================== -Aveți nevoie să încărcați șabloane din șiruri de caractere în loc de fișiere, poate în scopuri de testare? [StringLoader |extending-latte#stringloader] vă va ajuta: +Aveți nevoie să încărcați șabloane din șiruri în loc de fișiere, de exemplu în scopuri de testare? Vă ajută [StringLoader |loaders#StringLoader]: ```php $latte->setLoader(new Latte\Loaders\StringLoader([ @@ -262,10 +318,10 @@ $latte->render('main.file', $params); ``` -Manipulator de excepții .[#toc-exception-handler] -================================================= +Gestionar de excepții +===================== -Puteți defini propriul gestionar pentru excepțiile așteptate. Excepțiile ridicate în interiorul [`{try}` |tags#try] și în [sandbox |sandbox] sunt transmise către acesta. +Puteți defini propriul handler de deservire pentru excepțiile așteptate. I se vor transmite excepțiile apărute în interiorul [`{try}` |tags#try] și în [sandbox|sandbox]. ```php $loggingHandler = function (Throwable $e, Latte\Runtime\Template $template) use ($logger) { @@ -277,17 +333,17 @@ $latte->setExceptionHandler($loggingHandler); ``` -Căutarea automată a aspectului .[#toc-automatic-layout-lookup] -============================================================== +Căutarea automată a layout-ului +=============================== -Utilizarea tag-ului [`{layout}` |template-inheritance#layout-inheritance], șablonul își determină șablonul părinte. De asemenea, este posibil ca aspectul să fie căutat automat, ceea ce va simplifica scrierea șabloanelor, deoarece acestea nu vor trebui să includă eticheta `{layout}`. +Folosind tag-ul [`{layout}` |template-inheritance#Moștenirea layout-ului layout], șablonul își determină șablonul părinte. Este, de asemenea, posibil să se lase căutarea layout-ului să se facă automat, ceea ce simplifică scrierea șabloanelor, deoarece nu va mai fi necesar să se specifice tag-ul `{layout}` în ele. -Acest lucru se realizează după cum urmează: +Acest lucru se realizează în felul următor: ```php $finder = function (Latte\Runtime\Template $template) { if (!$template->getReferenceType()) { - // returnează calea către fișierul șablon părinte + // returnează calea către fișierul de layout return 'automatic.layout.latte'; } }; @@ -296,4 +352,4 @@ $latte = new Latte\Engine; $latte->addProvider('coreParentFinder', $finder); ``` -În cazul în care șablonul nu trebuie să aibă un aspect, acesta va indica acest lucru cu ajutorul etichetei `{layout none}`. +Dacă șablonul nu trebuie să aibă layout, o anunță prin tag-ul `{layout none}`. diff --git a/latte/ro/extending-latte.texy b/latte/ro/extending-latte.texy index b4e5f547a6..6996b625fc 100644 --- a/latte/ro/extending-latte.texy +++ b/latte/ro/extending-latte.texy @@ -2,284 +2,226 @@ Extinderea Latte **************** .[perex] -Latte este foarte flexibil și poate fi extins în multe feluri: puteți adăuga filtre personalizate, funcții, etichete, încărcătoare etc. Vă vom arăta cum să faceți acest lucru. +Latte este proiectat având în vedere extensibilitatea. Deși setul său standard de tag-uri, filtre și funcții acoperă multe cazuri de utilizare, adesea trebuie să adăugați logica specifică proprie sau instrumente auxiliare. Această pagină oferă o prezentare generală a modurilor în care puteți extinde Latte pentru a se potrivi perfect cerințelor proiectului dvs. - de la simple ajutoare la sintaxe noi complexe. -Acest capitol descrie diferitele modalități de extindere a Latte. Dacă doriți să reutilizați modificările dvs. în diferite proiecte sau dacă doriți să le partajați cu alții, ar trebui să [creați |creating-extension] atunci [așa-numita extensie |creating-extension]. +Modalități de extindere a Latte +=============================== -Câte drumuri duc la Roma? .[#toc-how-many-roads-lead-to-rome] -============================================================= +Iată o prezentare rapidă a principalelor moduri în care puteți personaliza și extinde Latte: -Deoarece unele dintre modalitățile de extindere a Latte pot fi amestecate, să încercăm mai întâi să explicăm diferențele dintre ele. Ca exemplu, să încercăm să implementăm un generator *Lorem ipsum*, căruia i se transmite numărul de cuvinte de generat. +- **[Filtre personalizate |Custom Filters]:** Pentru formatarea sau transformarea datelor direct în ieșirea șablonului (de ex. `{$var|myFilter}`). Ideal pentru sarcini precum formatarea datelor, modificarea textului sau aplicarea unei escapări specifice. Le puteți utiliza și pentru a modifica blocuri mai mari de conținut HTML, înfășurând conținutul într-un [`{block}` |tags#block] anonim și aplicând un filtru personalizat pe acesta. +- **[Funcții personalizate |Custom Functions]:** Pentru adăugarea unei logici reutilizabile care poate fi apelată în cadrul expresiilor din șablon (de ex. `{myFunction($arg1, $arg2)}`). Utile pentru calcule, accesarea funcțiilor auxiliare ale aplicației sau generarea unor mici porțiuni de conținut. +- **[Tag-uri personalizate |Custom Tags]:** Pentru crearea unor construcții lingvistice complet noi (`{mytag}...{/mytag}` sau `n:mytag`). Tag-urile oferă cele mai multe posibilități, permit definirea structurilor personalizate, controlul parsării șablonului și implementarea unei logici complexe de randare. +- **[Pași de compilare |Compiler Passes]:** Funcții care modifică arborele sintactic abstract (AST) al șablonului după parsare, dar înainte de generarea codului PHP. Se utilizează pentru optimizări avansate, verificări de securitate (cum ar fi Sandbox) sau modificări automate ale codului. +- **[Loadere personalizate |loaders]:** Pentru modificarea modului în care Latte caută și încarcă fișierele de șabloane (de ex. încărcarea din baza de date, stocare criptată etc.). -Construcția principală a limbajului Latte este tag-ul. Putem implementa un generator prin extinderea Latte cu un nou tag: +Alegerea metodei corecte de extindere este crucială. Înainte de a crea un tag complex, luați în considerare dacă un filtru sau o funcție mai simplă nu ar fi suficientă. Să ilustrăm acest lucru cu un exemplu: implementarea unui generator *Lorem ipsum* care primește ca argument numărul de cuvinte de generat. -```latte -{lipsum 40} -``` - -Tag-ul va funcționa foarte bine. Cu toate acestea, este posibil ca generatorul sub forma unei etichete să nu fie suficient de flexibil, deoarece nu poate fi utilizat într-o expresie. Apropo, în practică, rareori aveți nevoie să generați etichete; și aceasta este o veste bună, deoarece etichetele reprezintă o modalitate mai complicată de extindere. - -Bine, haideți să încercăm să creăm un filtru în loc de o etichetă: - -```latte -{=40|lipsum} -``` - -Din nou, o opțiune valabilă. Dar filtrul ar trebui să transforme valoarea transmisă în altceva. Aici folosim valoarea `40`, care indică numărul de cuvinte generate, ca argument al filtrului, nu ca valoare pe care dorim să o transformăm. +- **Ca tag?** `{lipsum 40}` - Posibil, dar tag-urile sunt mai potrivite pentru structuri de control sau generarea de marcaje complexe. Tag-urile nu pot fi utilizate direct în expresii. +- **Ca filtru?** `{=40|lipsum}` - Tehnic funcționează, dar filtrele sunt destinate *transformării* valorii de intrare. Aici, `40` este un *argument*, nu o valoare care se transformă. Acest lucru pare semantic incorect. +- **Ca funcție?** `{lipsum(40)}` - Aceasta este soluția cea mai naturală! Funcțiile acceptă argumente și returnează valori, ceea ce este ideal pentru utilizarea în orice expresie: `{var $text = lipsum(40)}`. -Deci, să încercăm să folosim funcția: +**Recomandare generală:** Utilizați funcții pentru calcule/generare, filtre pentru transformare și tag-uri pentru construcții lingvistice noi sau marcaje complexe. Utilizați trecerile de compilare pentru manipularea AST și loaderele pentru obținerea șabloanelor. -```latte -{lipsum(40)} -``` -Asta este! Pentru acest exemplu particular, crearea unei funcții este punctul de extensie ideal de utilizat. Puteți să o apelați oriunde unde este acceptată o expresie, de exemplu: +Înregistrare directă +==================== -```latte -{var $text = lipsum(40)} -``` +Pentru instrumente auxiliare specifice proiectului sau extensii rapide, Latte permite înregistrarea directă a filtrelor și funcțiilor în obiectul `Latte\Engine`. - -Filtre .[#toc-filters] -====================== - -Creați un filtru prin înregistrarea numelui său și a oricărui fișier PHP apelabil, cum ar fi o funcție: +Pentru a înregistra un filtru, utilizați metoda `addFilter()`. Primul argument al funcției dvs. de filtrare va fi valoarea dinaintea caracterului `|`, iar argumentele următoare sunt cele care sunt transmise după două puncte `:`. ```php $latte = new Latte\Engine; -$latte->addFilter('shortify', fn(string $s) => mb_substr($s, 0, 10)); // scurtează textul la 10 caractere -``` -În acest caz, ar fi mai bine ca filtrul să primească un parametru suplimentar: +// Definirea filtrului (obiect apelabil: funcție, metodă statică etc.) +$myTruncate = fn(string $s, int $length = 50) => mb_substr($s, 0, $length); -```php -$latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); -``` - -Îl folosim într-un șablon ca acesta: +// Înregistrare +$latte->addFilter('truncate', $myTruncate); -```latte -

                                                                                                        {$text|shortify}

                                                                                                        -

                                                                                                        {$text|shortify:100}

                                                                                                        +// Utilizare în șablon: {$text|truncate} sau {$text|truncate:100} ``` -După cum puteți vedea, funcția primește partea stângă a filtrului înainte de pipa `|` as the first argument and the arguments passed to the filter after `:` ca argumente următoare. - -Bineînțeles, funcția care reprezintă filtrul poate accepta orice număr de parametri, fiind suportați și parametrii variadici. - - -Filtre care utilizează clasa .[#toc-filters-using-the-class] ------------------------------------------------------------- - -A doua modalitate de a defini un filtru este de a [folosi clasa |develop#Parameters as a class]. Creăm o metodă cu atributul `TemplateFilter`: +Puteți înregistra și un **Filter Loader**, o funcție care furnizează dinamic obiecte apelabile de filtre în funcție de numele solicitat: ```php -class TemplateParameters -{ - public function __construct( - // parametri - ) {} - - #[Latte\Attributes\TemplateFilter] - public function shortify(string $s, int $len = 10): string - { - return mb_substr($s, 0, $len); - } -} - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); +$latte->addFilterLoader(fn(string $name) => /* returnează un obiect apelabil sau null */); ``` -Dacă utilizați PHP 7.x și Latte 2.x, utilizați adnotarea `/** @filter */` în locul atributului. - -Încărcător de filtre .[#toc-filter-loader] ------------------------------------------- - -În loc să înregistrați filtre individuale, puteți crea un așa-numit încărcător, care este o funcție care este apelată cu numele filtrului ca argument și care returnează fișierul PHP care poate fi apelat sau nul. +Pentru a înregistra o funcție utilizabilă în expresiile șablonului, utilizați `addFunction()`. ```php -$latte->addFilterLoader([new Filters, 'load']); +$latte = new Latte\Engine; +// Definirea funcției +$isWeekend = fn(DateTimeInterface $date) => $date->format('N') >= 6; -class Filters -{ - public function load(string $filter): ?callable - { - if (in_array($filter, get_class_methods($this))) { - return [$this, $filter]; - } - return null; - } - - public function shortify($s, $len = 10) - { - return mb_substr($s, 0, $len); - } - - // ... -} +// Înregistrare +$latte->addFunction('isWeekend', $isWeekend); + +// Utilizare în șablon: {if isWeekend($myDate)}Weekend!{/if} ``` +Mai multe informații găsiți în secțiunile [Crearea filtrelor personalizate |custom-filters] și [Funcțiilor |custom-functions]. -Filtre contextuale .[#toc-contextual-filters] ---------------------------------------------- -Un filtru contextual este un filtru care acceptă un obiect [api:Latte\Runtime\FilterInfo] în primul parametru, urmat de alți parametri, ca în cazul filtrelor clasice. Acesta este înregistrat în același mod, Latte însuși recunoaște că filtrul este contextual: +Metoda robustă: Latte Extension .{toc: Latte Extension} +======================================================= -```php -use Latte\Runtime\FilterInfo; +În timp ce înregistrarea directă este simplă, metoda standard și recomandată pentru împachetarea și distribuirea extensiilor Latte este prin intermediul claselor **Extension**. Extension servește ca un punct central de configurare pentru înregistrarea mai multor tag-uri, filtre, funcții, treceri de compilare și alte elemente. -$latte->addFilter('foo', function (FilterInfo $info, string $str): string { - // ... -}); -``` +De ce să folosiți Extensions? -Filtrele contextuale pot detecta și modifica tipul de conținut pe care îl primesc în variabila `$info->contentType`. Dacă filtrul este apelat în mod clasic peste o variabilă (de exemplu, `{$var|foo}`), `$info->contentType` va conține null. +- **Organizare:** Menține extensiile conexe (tag-uri, filtre etc. pentru o funcționalitate specifică) împreună într-o singură clasă. +- **Reutilizabilitate și partajare:** Împachetați ușor extensiile dvs. pentru utilizare în alte proiecte sau pentru partajare cu comunitatea (de ex. prin Composer). +- **Putere deplină:** Tag-urile personalizate și trecerile de compilare *pot fi înregistrate doar* prin intermediul Extensions. -Filtrul trebuie să verifice mai întâi dacă tipul de conținut al șirului de intrare este acceptat. De asemenea, îl poate modifica. Exemplu de filtru care acceptă text (sau null) și returnează HTML: + +Înregistrarea unei Extension +---------------------------- + +O Extension se înregistrează în Latte folosind metoda `addExtension()` (sau prin intermediul [fișierului de configurare |application:configuration#Șabloane Latte]): ```php -use Latte\Runtime\FilterInfo; - -$latte->addFilter('money', function (FilterInfo $info, float $amount): string { - // mai întâi verificăm dacă tipul de conținut al intrării este text - if (!in_array($info->contentType, [null, ContentType::Text])) { - throw new Exception("Filter |money used in incompatible content type $info->contentType."); - } - - // schimbăm tipul de conținut în HTML - $info->contentType = ContentType::Html; - return "$num Kč"; -}); +$latte = new Latte\Engine; +$latte->addExtension(new MyProjectExtension); ``` -.[note] -În acest caz, filtrul trebuie să asigure scăparea corectă a datelor. +Dacă înregistrați mai multe extensii și acestea definesc tag-uri, filtre sau funcții cu același nume, extensia adăugată ultima are prioritate. Acest lucru înseamnă, de asemenea, că extensiile dvs. pot suprascrie tag-urile/filtrele/funcțiile native. -Toate filtrele care sunt utilizate peste [blocuri |tags#block] (de exemplu, ca filtre de tip `{block|foo}...{/block}`) trebuie să fie contextuale. +Oricând efectuați o modificare într-o clasă și reîmprospătarea automată nu este dezactivată, Latte va recompila automat șabloanele dvs. -Funcții .[#toc-functions] -========================= +Crearea unei Extension +---------------------- -În mod implicit, toate funcțiile PHP native pot fi utilizate în Latte, cu excepția cazului în care sandbox-ul dezactivează acest lucru. Dar puteți, de asemenea, să vă definiți propriile funcții. Acestea pot suprascrie funcțiile native. +Pentru a crea propria extensie, trebuie să creați o clasă care moștenește din [api:Latte\Extension]. Pentru a vă face o idee despre cum arată o astfel de extensie, consultați "CoreExtension":https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php încorporată. -Creați o funcție prin înregistrarea numelui său și a oricărui callable PHP: +Să aruncăm o privire la metodele pe care le puteți implementa: -```php -$latte = new Latte\Engine; -$latte->addFunction('random', function (...$args) { - return $args[array_rand($args)]; -}); -``` -În acest caz, utilizarea este identică cu apelarea funcției PHP: +beforeCompile(Latte\Engine $engine): void .[method] +--------------------------------------------------- -```latte -{random(apple, orange, lemon)} // prints for example: apple -``` +Apelată înainte de compilarea șablonului. Metoda poate fi utilizată, de exemplu, pentru inițializări legate de compilare. -Funcții care utilizează clasa .[#toc-functions-using-the-class] ---------------------------------------------------------------- +getTags(): array .[method] +-------------------------- -A doua modalitate de a defini o funcție este [utilizarea clasei |develop#Parameters as a class]. Creăm o metodă cu atributul `TemplateFunction`: +Apelată la compilarea șablonului. Returnează un array asociativ *nume tag => obiect apelabil*, care sunt funcții pentru parsarea tag-urilor. [Mai multe informații |custom-tags]. ```php -class TemplateParameters +public function getTags(): array { - public function __construct( - // parametri - ) {} - - #[Latte\Attributes\TemplateFunction] - public function random(...$args) - { - return $args[array_rand($args)]; - } + return [ + 'foo' => FooNode::create(...), + 'bar' => BarNode::create(...), + 'n:baz' => NBazNode::create(...), + // ... + ]; } - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); ``` -Dacă utilizați PHP 7.x și Latte 2.x, utilizați adnotarea `/** @function */` în locul atributului. +Tag-ul `n:baz` reprezintă un [n:atribut |syntax#n:atribute] pur, adică un tag care poate fi scris doar ca atribut. +Pentru tag-urile `foo` și `bar`, Latte recunoaște automat dacă sunt tag-uri pereche și, dacă da, pot fi scrise automat folosind n:atribute, inclusiv variante cu prefixele `n:inner-foo` și `n:tag-foo`. -Încărcătoare .[#toc-loaders] -============================ +Ordinea de execuție a acestor n:atribute este determinată de ordinea lor în array-ul returnat de metoda `getTags()`. Astfel, `n:foo` este întotdeauna executat înainte de `n:bar`, chiar dacă atributele din tag-ul HTML sunt listate în ordine inversă, ca `
                                                                                                        `. -Încărcătoarele sunt responsabile de încărcarea șabloanelor dintr-o sursă, cum ar fi un sistem de fișiere. Aceștia sunt setați cu ajutorul metodei `setLoader()`: +Dacă trebuie să specificați ordinea n:atributelor între mai multe extensii, utilizați metoda auxiliară `order()`, unde parametrul `before` xor `after` specifică ce tag-uri sunt ordonate înainte sau după tag. ```php -$latte->setLoader(new MyLoader); +public function getTags(): array +{ + return [ + 'foo' => self::order(FooNode::create(...), before: 'bar'), + 'bar' => self::order(BarNode::create(...), after: ['block', 'snippet']), + ]; +} ``` -Încărcătoarele încorporate sunt: +getPasses(): array .[method] +---------------------------- -FileLoader .[#toc-fileloader] ------------------------------ +Apelată la compilarea șablonului. Returnează un array asociativ *nume trecere => obiect apelabil*, care sunt funcții reprezentând așa-numitele [treceri de compilare |compiler-passes], care parcurg și modifică AST-ul. -Încărcător implicit. Încarcă șabloanele din sistemul de fișiere. - -Accesul la fișiere poate fi restricționat prin setarea directorului de bază: +Și aici se poate utiliza metoda auxiliară `order()`. Valoarea parametrilor `before` sau `after` poate fi `*` cu semnificația înainte/după toate. ```php -$latte->setLoader(new Latte\Loaders\FileLoader($templateDir)); -$latte->render('test.latte'); +public function getPasses(): array +{ + return [ + 'optimize' => Passes::optimizePass(...), + 'sandbox' => self::order($this->sandboxPass(...), before: '*'), + // ... + ]; +} ``` -StringLoader .[#toc-stringloader] ---------------------------------- +beforeRender(Latte\Engine $engine): void .[method] +-------------------------------------------------- -Încarcă șabloanele din șiruri de caractere. Acest încărcător este foarte util pentru testarea unitară. De asemenea, poate fi utilizat pentru proiecte mici în care poate fi utilă stocarea tuturor șabloanelor într-un singur fișier PHP. +Apelată înainte de fiecare randare a șablonului. Metoda poate fi utilizată, de exemplu, pentru a inițializa variabilele utilizate în timpul randării. -```php -$latte->setLoader(new Latte\Loaders\StringLoader([ - 'main.file' => '{include other.file}', - 'other.file' => '{if true} {$var} {/if}', -])); -$latte->render('main.file'); -``` +getFilters(): array .[method] +----------------------------- -Utilizare simplificată: +Apelată înainte de randarea șablonului. Returnează filtrele ca un array asociativ *nume filtru => obiect apelabil*. [Mai multe informații |custom-filters]. ```php -$template = '{if true} {$var} {/if}'; -$latte->setLoader(new Latte\Loaders\StringLoader); -$latte->render($template); +public function getFilters(): array +{ + return [ + 'batch' => $this->batchFilter(...), + 'trim' => $this->trimFilter(...), + // ... + ]; +} ``` -Crearea unui încărcător personalizat .[#toc-creating-a-custom-loader] ---------------------------------------------------------------------- - -Loader este o clasă care implementează interfața [api:Latte\Loader]. +getFunctions(): array .[method] +------------------------------- +Apelată înainte de randarea șablonului. Returnează funcțiile ca un array asociativ *nume funcție => obiect apelabil*. [Mai multe informații |custom-functions]. -Etichete .[#toc-tags] -===================== +```php +public function getFunctions(): array +{ + return [ + 'clamp' => $this->clampFunction(...), + 'divisibleBy' => $this->divisibleByFunction(...), + // ... + ]; +} +``` -Una dintre cele mai interesante caracteristici ale motorului de modelare este capacitatea de a defini noi construcții de limbaj folosind etichete. Este, de asemenea, o funcționalitate mai complexă și trebuie să înțelegeți cum funcționează Latte la nivel intern. -În cele mai multe cazuri, însă, tag-ul nu este necesar: -- dacă ar trebui să genereze o anumită ieșire, utilizați în schimb [funcția |#functions] -- dacă ar trebui să modifice unele intrări și să le returneze, utilizați în schimb [filter (filtru |#filters] ) -- dacă ar trebui să editeze o zonă de text, înfășurați-o cu o etichetă [`{block}` |tags#block] și utilizați un [filtru |#Contextual Filters] -- în cazul în care nu ar trebui să producă nimic, ci doar să apeleze o funcție, apelați-o cu [`{do}` |tags#do] +getProviders(): array .[method] +------------------------------- -Dacă doriți în continuare să creați o etichetă, foarte bine! Toate elementele esențiale pot fi găsite în [Crearea unei extensii |creating-extension]. +Apelată înainte de randarea șablonului. Returnează un array de furnizori, care sunt de obicei obiecte utilizate de tag-uri în timpul rulării. Se accesează prin `$this->global->...`. [Mai multe informații |custom-tags#Introducerea furnizorilor]. +```php +public function getProviders(): array +{ + return [ + 'myFoo' => $this->foo, + 'myBar' => $this->bar, + // ... + ]; +} +``` -Trecerile compilatorului .[#toc-compiler-passes] -================================================ -Pasele de compilare sunt funcții care modifică AST-urile sau colectează informații din acestea. În Latte, de exemplu, un sandbox este implementat în acest mod: parcurge toate nodurile unui AST, găsește apelurile de funcții și metode și le înlocuiește cu apeluri controlate. +getCacheKey(Latte\Engine $engine): mixed .[method] +-------------------------------------------------- -Ca și în cazul etichetelor, aceasta este o funcționalitate mai complexă și trebuie să înțelegeți cum funcționează Latte sub capotă. Toate elementele esențiale pot fi găsite în capitolul [Crearea unei extensii |creating-extension]. +Apelată înainte de randarea șablonului. Valoarea returnată devine parte a cheii, al cărei hash este inclus în numele fișierului șablonului compilat. Prin urmare, pentru valori returnate diferite, Latte va genera fișiere cache diferite. diff --git a/latte/ro/filters.texy b/latte/ro/filters.texy index 3465581591..1b7ff42606 100644 --- a/latte/ro/filters.texy +++ b/latte/ro/filters.texy @@ -2,111 +2,113 @@ Filtre Latte ************ .[perex] -Filtrele sunt funcții care modifică sau formatează datele în forma dorită. Acesta este un rezumat al filtrelor încorporate care sunt disponibile. +În șabloane, putem folosi funcții care ajută la modificarea sau reformatarea datelor în forma finală. Le numim *filtre*. .[table-latte-filters] -|## Transformare șiruri / matrice -| `batch` | [listarea datelor liniare într-un tabel |#batch] -| `breakLines` | [inserează întreruperi de linie HTML înainte de toate liniile noi |#breakLines] -| `bytes` | [formatează dimensiunea în bytes |#bytes] -| `clamp` | [fixează valoarea în intervalul |#clamp] -| `dataStream` | [Conversia protocolului URI de date |#datastream] -| `date` | [formatează data |#date] -| `explode` | [separă un șir de caractere prin delimitatorul dat |#explode] -| `first` | [returnează primul element al unui array sau caracterul unui șir de caractere |#first] -| `implode` | [unește un array cu un șir de caractere|#implode] -| `indent` | [indentează textul de la stânga cu un număr de tabulări |#indent] -| `join` | [unește un array cu un șir de caractere|#implode] -| `last` | [returnează ultimul element al unui array sau caracter al unui șir de caractere|#last] -| `length` | [returnează lungimea unui șir sau a unui array |#length] -| `number` | [formatează numere |#number] -| `padLeft` | [completează șirul de caractere la o lungime dată din stânga |#padLeft] -| `padRight` | [completează șirul la lungimea dată dinspre dreapta |#padRight] -| `random` | [returnează un element aleatoriu al unui array sau un caracter al unui șir de caractere|#random] -| `repeat` | [repetă șirul |#repeat] -| `replace` | [înlocuiește toate aparițiile șirului de căutare cu șirul de înlocuire |#replace] -| `replaceRE` | [înlocuiește toate aparițiile în funcție de expresia regulată |#replaceRE] -| `reverse` | [inversează un șir sau o matrice UTF-8 |#reverse] -| `slice` | [extrage o porțiune dintr-un array sau un șir de caractere |#slice] -| `sort` | [sortează un array |#sort] -| `spaceless` | [elimină spațiile albe |#spaceless], similar cu tag-ul [fără spațiu |tags] -| `split` | [împarte un șir de caractere după delimitatorul dat |#explode] -| `strip` | [elimină spațiile albe |#spaceless] -| `stripHtml` | [elimină etichetele HTML și convertește entitățile HTML în text |#stripHtml] -| `substr` | [returnează o parte din șir |#substr] -| `trim` | [elimină spațiile albe din șirul de caractere |#trim] -| `translate` | [traducere în alte limbi |#translate] -| `truncate` | [scurtează lungimea păstrând cuvinte întregi |#truncate] -| `webalize` | [adaptează șirul UTF-8 la forma utilizată în URL |#webalize] +|## Transformare +| `batch` | [afișarea datelor liniare într-un tabel |#batch] +| `breakLines` | [Adaugă întreruperi de linie HTML înainte de sfârșitul rândului |#breakLines] +| `bytes` | [formatează dimensiunea în bytes |#bytes] +| `clamp` | [limitează valoarea la un interval dat |#clamp] +| `dataStream` | [conversie pentru protocolul Data URI |#dataStream] +| `date` | [formatează data și ora |#date] +| `explode` | [împarte șirul într-un array după delimitator |#explode] +| `first` | [returnează primul element al array-ului sau caracterul șirului |#first] +| `group` | [grupează datele după diverse criterii |#group] +| `implode` | [unește array-ul într-un șir |#implode] +| `indent` | [indentează textul de la stânga cu un număr dat de tabulatori |#indent] +| `join` | [unește array-ul într-un șir |#implode] +| `last` | [returnează ultimul element al array-ului sau caracterul șirului |#last] +| `length` | [returnează lungimea șirului în caractere sau a array-ului |#length] +| `localDate` | [formatează data și ora în funcție de setările regionale |#localDate] +| `number` | [formatează numărul |#number] +| `padLeft` | [completează șirul de la stânga la lungimea dorită |#padLeft] +| `padRight` | [completează șirul de la dreapta la lungimea dorită |#padRight] +| `random` | [returnează un element aleatoriu al array-ului sau caracterul șirului |#random] +| `repeat` | [repetarea șirului |#repeat] +| `replace` | [înlocuiește aparițiile șirului căutat |#replace] +| `replaceRE` | [înlocuiește aparițiile conform expresiei regulate |#replaceRE] +| `reverse` | [inversează șirul UTF-8 sau array-ul |#reverse] +| `slice` | [extrage o parte a array-ului sau a șirului |#slice] +| `sort` | [sortează array-ul |#sort] +| `spaceless` | [elimină spațiul alb |#spaceless], similar cu tag-ul [spaceless |tags] +| `split` | [împarte șirul într-un array după delimitator |#explode] +| `strip` | [elimină spațiul alb |#spaceless] +| `stripHtml` | [elimină tag-urile HTML și convertește entitățile HTML în caractere |#stripHtml] +| `substr` | [returnează o parte a șirului |#substr] +| `trim` | [elimină spațiile sau alte caractere de la început și sfârșit |#trim] +| `translate` | [traducere în alte limbi |#translate] +| `truncate` | [scurtează lungimea păstrând cuvintele |#truncate] +| `webalize` | [modifică șirul UTF-8 în forma utilizată în URL |#webalize] .[table-latte-filters] -|## Casetarea literelor -| `capitalize` | [litere minuscule, prima literă a fiecărui cuvânt majusculă |#capitalize] -| `firstUpper` | [face ca prima literă să fie majusculă |#firstUpper] -| `lower` | [face ca un șir de caractere să fie minuscul |#lower] -| `upper` | [face un șir de caractere majuscule |#upper] +|## Capitalizare +| `capitalize` | [litere mici, prima literă a cuvintelor mare |#capitalize] +| `firstUpper` | [convertește prima literă în majusculă |#firstUpper] +| `lower` | [convertește în litere mici |#lower] +| `upper` | [convertește în litere mari |#upper] .[table-latte-filters] -|## Rotunjirea numerelor -| `ceil` | [rotunjește un număr până la o precizie dată |#ceil] -| `floor` | [rotunjește un număr la o precizie dată, în jos |#floor] -| `round` | [rotunjește un număr la o precizie dată |#round] +|## Rotunjire +| `ceil` | [rotunjește numărul în sus la precizia dată |#ceil] +| `floor` | [rotunjește numărul în jos la precizia dată |#floor] +| `round` | [rotunjește numărul la precizia dată |#round] .[table-latte-filters] -|## Escaping -| `escapeUrl` | [evită parametrii în URL |#escapeUrl] -| `noescape` | [tipărește o variabilă fără scăpare |#noescape] -| `query` | [generează un șir de interogare în URL |#query] +|## Escapare +| `escapeUrl` | [escapează parametrul în URL |#escapeUrl] +| `noescape` | [afișează variabila fără escapare |#noescape] +| `query` | [generează query string în URL |#query] -Există, de asemenea, filtre de [scăpare |safety-first#Context-aware escaping] pentru HTML (`escapeHtml` și `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) și iCalendar (`escapeICal`), pe care Latte le utilizează singur datorită [scăpării în funcție de context |safety-first#Context-aware escaping] și pe care nu este nevoie să le scrieți. +Mai există filtre de escapare pentru HTML (`escapeHtml` și `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) și iCalendar (`escapeICal`), pe care Latte le folosește singur datorită [escapării contextuale sensibile |safety-first#Escapare contextuală sensibilă] și nu trebuie să le scrieți. .[table-latte-filters] |## Securitate -| `checkUrl` | [dezinfectează șirul de caractere pentru utilizarea în interiorul atributului href |#checkUrl] -| `nocheck` | [previne dezinfectarea automată a URL-urilor |#nocheck] +| `checkUrl` | [curăță adresa URL de intrări periculoase |#checkUrl] +| `nocheck` | [previne curățarea automată a adresei URL |#nocheck] -Latte [verifică automat |safety-first#link checking] atributele `src` și `href`, astfel încât aproape că nu este nevoie să utilizați filtrul `checkUrl`. +Atributele Latte `src` și `href` [verifică automat |safety-first#Verificarea linkurilor], deci filtrul `checkUrl` aproape că nu trebuie folosit. .[note] -Toate filtrele încorporate funcționează cu șiruri de caractere codificate UTF-8. +Toate filtrele implicite sunt destinate șirurilor în codificarea UTF-8. -Utilizare .[#toc-usage] -======================= +Utilizare +========= -Latte permite apelarea filtrelor folosind notația "pipe" (este permisă folosirea unui spațiu înainte): +Filtrele se scriu după bara verticală (poate fi precedată de un spațiu): ```latte

                                                                                                        {$heading|upper}

                                                                                                        ``` -Filtrele pot fi înlănțuite, caz în care se aplică în ordine de la stânga la dreapta: +Filtrele (în versiunile mai vechi, ajutoarele) pot fi înlănțuite și apoi se aplică în ordine de la stânga la dreapta: ```latte

                                                                                                        {$heading|lower|capitalize}

                                                                                                        ``` -Parametrii se pun după numele filtrului, separați prin două puncte sau virgulă: +Parametrii se specifică după numele filtrului, separați prin două puncte sau virgule: ```latte

                                                                                                        {$heading|truncate:20,''}

                                                                                                        ``` -Filtrele pot fi aplicate pe expresie: +Filtrele pot fi aplicate și pe o expresie: ```latte {var $name = ($title|upper) . ($subtitle|lower)} ``` -[Filtrele personalizate |extending-latte#filters] pot fi înregistrate în acest mod: +[Filtrele personalizate|custom-filters] pot fi înregistrate în acest mod: ```php $latte = new Latte\Engine; $latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -Îl folosim într-un șablon ca acesta: +În șablon, se apelează apoi astfel: ```latte

                                                                                                        {$text|shortify}

                                                                                                        @@ -114,18 +116,18 @@ $latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $ ``` -Filtre .[#toc-filters] -====================== +Filtre +====== -batch(int length, mixed item): array .[filter]{data-version:2.7} ----------------------------------------------------------------- -Filtru care simplifică listarea datelor liniare sub formă de tabel. Acesta returnează o matrice de tablouri cu numărul dat de elemente. Dacă furnizați un al doilea parametru, acesta este utilizat pentru a completa elementele lipsă de pe ultimul rând. +batch(int $length, mixed $item): array .[filter] +------------------------------------------------ +Filtrul care simplifică afișarea datelor liniare sub formă de tabel. Returnează un array de array-uri cu numărul specificat de elemente. Dacă specificați al doilea parametru, acesta va fi folosit pentru a completa elementele lipsă de pe ultimul rând. ```latte {var $items = ['a', 'b', 'c', 'd', 'e']}
                                                                                                        Ciclu interior
                                                                                                        -{foreach ($items|batch: 3, 'No item') as $row} +{foreach ($items|batch: 3, 'Niciun element') as $row} {foreach $row as $column} @@ -135,7 +137,7 @@ Filtru care simplifică listarea datelor liniare sub formă de tabel. Acesta ret
                                                                                                        {$column}
                                                                                                        ``` -Se tipărește: +Afișează: ```latte @@ -147,25 +149,27 @@ Se tipărește: - +
                                                                                                        d eNo itemNiciun element
                                                                                                        ``` +Vezi și [#group] și tag-ul [iterateWhile |tags#iterateWhile]. + breakLines .[filter] -------------------- -Inserează întreruperi de linie HTML înainte de toate liniile noi. +Adaugă tag-ul HTML `
                                                                                                        ` înaintea fiecărui caracter de linie nouă. ```latte {var $s = "Text & with \n newline"} -{$s|breakLines} {* outputs "Text & with
                                                                                                        \n newline" *} +{$s|breakLines} {* afișează "Text & with
                                                                                                        \n newline" *} ``` -bytes(int precision = 2) .[filter] ----------------------------------- -Formatează o dimensiune în bytes în formă lizibilă pentru oameni. +bytes(int $precision=2) .[filter] +--------------------------------- +Formatează dimensiunea în bytes într-o formă lizibilă pentru om. Dacă sunt setate [setările regionale |develop#Locale], se vor folosi separatoarele corespunzătoare pentru zecimale și mii. ```latte {$size|bytes} 0 B, 1.25 GB, … @@ -173,72 +177,72 @@ Formatează o dimensiune în bytes în formă lizibilă pentru oameni. ``` -ceil(int precision = 0) .[filter] ---------------------------------- -Rotunjește un număr până la o precizie dată. +ceil(int $precision=0) .[filter] +-------------------------------- +Rotunjește numărul în sus la precizia dată. ```latte -{=3.4|ceil} {* ieșiri 4 *} -{=135.22|ceil:1} {* ieșiri 135.3 *} -{=135.22|ceil:3} {* ieșiri 135.22 *} +{=3.4|ceil} {* afișează 4 *} +{=135.22|ceil:1} {* afișează 135.3 *} +{=135.22|ceil:3} {* afișează 135.22 *} ``` -A se vedea, de asemenea, [floor |#floor], [round |#round]. +Vezi și [#floor], [#round]. capitalize .[filter] -------------------- -Returnează o versiune a valorii cu titlu. Cuvintele vor începe cu litere majuscule, toate caracterele rămase sunt minuscule. Necesită extensia PHP `mbstring`. +Cuvintele vor începe cu majuscule, toate celelalte caractere vor fi minuscule. Necesită extensia PHP `mbstring`. ```latte -{='i like LATTE'|capitalize} {* outputs 'I Like Latte' *} +{='i like LATTE'|capitalize} {* afișează 'I Like Latte' *} ``` -A se vedea, de asemenea, [firstUpper |#firstUpper], [lower |#lower], [upper |#upper]. +Vezi și [#firstUpper], [#lower], [#upper]. checkUrl .[filter] ------------------ -Aplică igienizarea URL-urilor. Verifică dacă variabila conține un URL web (adică protocolul HTTP/HTTPS) și previne scrierea de linkuri care pot reprezenta un risc de securitate. +Forțează curățarea adresei URL. Verifică dacă variabila conține o adresă URL web (adică protocol HTTP/HTTPS) și previne afișarea linkurilor care pot reprezenta un risc de securitate. ```latte {var $link = 'javascript:window.close()'} -checked -unchecked +verificat +neverificat ``` -Imprimă: +Afișează: ```latte -checked -unchecked +verificat +neverificat ``` -A se vedea și [nocheck |#nocheck]. +Vezi și [#nocheck]. -clamp(int|float min, int|float max) .[filter]{data-version:2.9} ---------------------------------------------------------------- -Returnează valoarea fixată în intervalul inclusiv dintre min și max. +clamp(int|float $min, int|float $max) .[filter] +----------------------------------------------- +Limitează valoarea la intervalul inclusiv dat min și max. ```latte {$level|clamp: 0, 255} ``` -Există, de asemenea, ca [funcție |functions#clamp]. +Există și ca [funcție |functions#clamp]. -dataStream(string mimetype = detect) .[filter] ----------------------------------------------- -Convertește conținutul în schema URI de date. Poate fi utilizat pentru a insera imagini în HTML sau CSS fără a fi nevoie să se facă legătura cu fișiere externe. +dataStream(string $mimetype=detect) .[filter] +--------------------------------------------- +Convertește conținutul în schema data URI. Cu ajutorul acesteia, se pot încorpora imagini în HTML sau CSS fără a fi nevoie de linkuri către fișiere externe. -Să avem o imagine într-o variabilă `$img = Image::fromFile('obrazek.gif')`, atunci +Să avem într-o variabilă imaginea `$img = Image::fromFile('imagine.gif')`, apoi ```latte - + ``` -Se imprimă de exemplu: +Afișează, de exemplu: ```latte {$name} ``` -A se vedea, de asemenea, [query |#query]. +Vezi și [#query]. -explode(string separator = '') .[filter]{data-version:2.10.2} -------------------------------------------------------------- -Împarte un șir de caractere după delimitatorul dat și returnează un tablou de șiruri de caractere. Alias pentru `split`. +explode(string $separator='') .[filter] +--------------------------------------- +Împarte șirul într-un array după delimitator. Alias pentru `split`. ```latte -{='one,two,three'|explode:','} {* returns ['one', 'two', 'three'] *} +{='one,two,three'|explode:','} {* returnează ['one', 'two', 'three'] *} ``` -Dacă delimitatorul este un șir de caractere gol (valoare implicită), datele de intrare vor fi împărțite în caractere individuale: +Dacă delimitatorul este un șir gol (valoarea implicită), intrarea va fi împărțită în caractere individuale: ```latte {='123'|explode} {* returnează ['1', '2', '3'] *} ``` -Puteți utiliza și aliasul `split`: +Puteți folosi și aliasul `split`: ```latte {='1,2,3'|split:','} {* returnează ['1', '2', '3'] *} ``` -A se vedea și [implode |#implode]. +Vezi și [#implode]. -first .[filter]{data-version:2.10.2} ------------------------------------- -Returnează primul element al unui array sau caracterul unui șir de caractere: +first .[filter] +--------------- +Returnează primul element al array-ului sau caracterul șirului: ```latte -{=[1, 2, 3, 4]|first} {* ieșiri 1 *} -{='abcd'|first} {* iese "a" *} +{=[1, 2, 3, 4]|first} {* afișează 1 *} +{='abcd'|first} {* afișează 'a' *} ``` -A se vedea, de asemenea, [last |#last], [random |#random]. +Vezi și [#last], [#random]. -floor(int precision = 0) .[filter] ----------------------------------- -Rotunjește un număr până la o precizie dată. +floor(int $precision=0) .[filter] +--------------------------------- +Rotunjește numărul în jos la precizia dată. ```latte -{=3.5|floor} {* ieșiri 3 *} -{=135.79|floor:1} {* ieșiri 135.7 *} -{=135.79|floor:3} {* ieșiri 135.79 *} +{=3.5|floor} {* afișează 3 *} +{=135.79|floor:1} {* afișează 135.7 *} +{=135.79|floor:3} {* afișează 135.79 *} ``` -A se vedea și [plafon |#ceil], [rotunjire |#round]. +Vezi și [#ceil], [#round]. firstUpper .[filter] -------------------- -Convertește prima literă a unei valori în majusculă. Necesită extensia PHP `mbstring`. +Convertește prima literă în majusculă. Necesită extensia PHP `mbstring`. ```latte -{='the latte'|firstUpper} {* outputs 'The latte' *} +{='the latte'|firstUpper} {* afișează 'The latte' *} ``` -A se vedea, de asemenea, [capitalize |#capitalize], [lower |#lower], [upper |#upper]. +Vezi și [#capitalize], [#lower], [#upper]. -implode(string glue = '') .[filter] ------------------------------------ -Returnează un șir de caractere care este concatenarea șirurilor din matrice. Alias pentru `join`. +group(string|int|\Closure $by): array .[filter]{data-version:3.0.16} +-------------------------------------------------------------------- +Filtrul grupează datele după diverse criterii. + +În acest exemplu, rândurile din tabel sunt grupate după coloana `categoryId`. Ieșirea este un array de array-uri, unde cheia este valoarea din coloana `categoryId`. [Citiți un tutorial detaliat|cookbook/grouping]. ```latte -{=[1, 2, 3]|implode} {* iese "123" *} -{=[1, 2, 3]|implode:'|'} {* iese '1|2|3' *} +{foreach ($items|group: categoryId) as $categoryId => $categoryItems} +
                                                                                                          + {foreach $categoryItems as $item} +
                                                                                                        • {$item->name}
                                                                                                        • + {/foreach} +
                                                                                                        +{/foreach} ``` -Puteți utiliza și un alias `join`: .{data-version:2.10.2} +Vezi și [#batch], funcția [group |functions#group] și tag-ul [iterateWhile |tags#iterateWhile]. + + +implode(string $glue='') .[filter] +---------------------------------- +Returnează un șir care este concatenarea elementelor secvenței. Alias pentru `join`. ```latte -{=[1, 2, 3]|join} {* acest "123" *} +{=[1, 2, 3]|implode} {* afișează '123' *} +{=[1, 2, 3]|implode:'|'} {* afișează '1|2|3' *} ``` +Puteți folosi și aliasul `join`: + +```latte +{=[1, 2, 3]|join} {* afișează '123' *} +``` -indent(int level = 1, string char = "\t") .[filter] ---------------------------------------------------- -Indentează un text de la stânga cu un anumit număr de tabulauri sau alte caractere pe care îl specificăm în al doilea argument opțional. Liniile goale nu sunt indentate. + +indent(int $level=1, string $char="\t") .[filter] +------------------------------------------------- +Indentează textul de la stânga cu un număr dat de tabulatori sau alte caractere, pe care le putem specifica în al doilea argument. Rândurile goale nu sunt indentate. ```latte
                                                                                                        {block |indent} -

                                                                                                        Hello

                                                                                                        +

                                                                                                        Salut

                                                                                                        {/block}
                                                                                                        ``` -Imprimă: +Afișează: ```latte
                                                                                                        -

                                                                                                        Hello

                                                                                                        +

                                                                                                        Salut

                                                                                                        ``` -last .[filter]{data-version:2.10.2} ------------------------------------ -Returnează ultimul element al unui array sau caracterul unui șir de caractere: +last .[filter] +-------------- +Returnează ultimul element al array-ului sau caracterul șirului: ```latte -{=[1, 2, 3, 4]|last} {* ieșiri 4 *} -{='abcd'|last} {* ieșiri 'd' *} +{=[1, 2, 3, 4]|last} {* afișează 4 *} +{='abcd'|last} {* afișează 'd' *} ``` -A se vedea, de asemenea, [first |#first], [random |#random]. +Vezi și [#first], [#random]. length .[filter] ---------------- -Returnează lungimea unui șir de caractere sau a unei matrice. +Returnează lungimea șirului sau a array-ului. -- pentru șiruri de caractere, va returna lungimea în caractere UTF-8 -- pentru array-uri, va returna numărul de elemente. -- pentru obiectele care implementează interfața Countable, se va utiliza valoarea returnată de count(). -- pentru obiectele care implementează interfața IteratorAggregate, se va utiliza valoarea returnată de iterator_count(). +- pentru șiruri, returnează lungimea în caractere UTF-8 +- pentru array-uri, returnează numărul de elemente +- pentru obiecte care implementează interfața Countable, utilizează valoarea returnată de metoda count() +- pentru obiecte care implementează interfața IteratorAggregate, utilizează valoarea returnată de funcția iterator_count() ```latte @@ -396,204 +420,314 @@ Returnează lungimea unui șir de caractere sau a unei matrice. ``` +localDate(?string $format=null, ?string $date=null, ?string $time=null) .[filter] +--------------------------------------------------------------------------------- +Formatează data și ora în funcție de [setările regionale |develop#Locale], asigurând o afișare consistentă și localizată a datelor temporale în diferite limbi și regiuni. Filtrul acceptă data ca UNIX timestamp, șir sau obiect de tip `DateTimeInterface`. + +```latte +{$date|localDate} {* 15 aprilie 2024 *} +{$date|localDate: format: yM} {* 4/2024 *} +{$date|localDate: date: medium} {* 15 apr. 2024 *} +``` + +Dacă utilizați filtrul fără parametri, data va fi afișată la nivelul `long`, vezi mai jos. + +**a) utilizarea formatului** + +Parametrul `format` descrie ce componente temporale trebuie afișate. Utilizează coduri de litere pentru acestea, a căror număr de repetări influențează lățimea ieșirii: + +| an | `y` / `yy` / `yyyy` | `2024` / `24` / `2024` +| lună | `M` / `MM` / `MMM` / `MMMM` | `8` / `08` / `aug.` / `august` +| zi | `d` / `dd` / `E` / `EEEE` | `1` / `01` / `dum.` / `duminică` +| oră | `j` / `H` / `h` | preferat / 24 ore / 12 ore +| minut | `m` / `mm` | `5` / `05` (2 cifre în combinație cu secundele) +| secundă | `s` / `ss` | `8` / `08` (2 cifre în combinație cu minutele) + +Ordinea codurilor în format nu contează, deoarece ordinea componentelor va fi afișată conform convențiilor setărilor regionale. Formatul este, așadar, independent de acestea. De exemplu, formatul `yyyyMMMMd` în mediul `en_US` va afișa `April 15, 2024`, în timp ce în mediul `ro_RO` va afișa `15 august 2024`: + +| locale: | ro_RO | en_US +|--- +| `format: 'dMy'` | 10.8.2024 | 8/10/2024 +| `format: 'yM'` | 8/2024 | 8/2024 +| `format: 'yyyyMMMM'` | august 2024 | August 2024 +| `format: 'MMMM'` | august | August +| `format: 'jm'` | 17:22 | 5:22 PM +| `format: 'Hm'` | 17:22 | 17:22 +| `format: 'hm'` | 5:22 p.m. | 5:22 PM + + +**b) utilizarea stilurilor predefinite** + +Parametrii `date` și `time` determină cât de detaliat trebuie afișată data și ora. Puteți alege dintre mai multe niveluri: `full`, `long`, `medium`, `short`. Se poate afișa doar data, doar ora sau ambele: + +| locale: | ro_RO | en_US +|--- +| `date: short` | 23.01.1978 | 1/23/78 +| `date: medium` | 23 ian. 1978 | Jan 23, 1978 +| `date: long` | 23 ianuarie 1978 | January 23, 1978 +| `date: full` | luni, 23 ianuarie 1978 | Monday, January 23, 1978 +| `time: short` | 08:30 | 8:30 AM +| `time: medium` | 08:30:59 | 8:30:59 AM +| `time: long` | 08:30:59 EET | 8:30:59 AM GMT+2 +| `date: short, time: short` | 23.01.1978, 08:30 | 1/23/78, 8:30 AM +| `date: medium, time: short` | 23 ian. 1978, 08:30 | Jan 23, 1978, 8:30 AM +| `date: long, time: short` | 23 ianuarie 1978, 08:30 | January 23, 1978 at 8:30 AM + +Pentru dată, se poate utiliza în plus prefixul `relative-` (de ex. `relative-short`), care pentru datele apropiate de prezent va afișa `ieri`, `azi` sau `mâine`, altfel se va afișa în mod standard. + +```latte +{$date|localDate: date: relative-short} {* ieri *} +``` + +Vezi și [#date]. + + lower .[filter] --------------- -Convertește o valoare în minuscule. Necesită extensia PHP `mbstring`. +Convertește șirul în litere mici. Necesită extensia PHP `mbstring`. ```latte -{='LATTE'|lower} {* acest "latte" *} +{='LATTE'|lower} {* afișează 'latte' *} ``` -A se vedea, de asemenea, [capitalize |#capitalize], [firstUpper |#firstUpper], [upper |#upper]. +Vezi și [#capitalize], [#firstUpper], [#upper]. nocheck .[filter] ----------------- -Împiedică dezinfectarea automată a URL-urilor. Latte [verifică automat |safety-first#Link checking] dacă variabila conține un URL web (adică protocolul HTTP/HTTPS) și împiedică scrierea de link-uri care pot reprezenta un risc de securitate. +Previne curățarea automată a adresei URL. Latte [verifică automat |safety-first#Verificarea linkurilor] dacă variabila conține o adresă URL web (adică protocol HTTP/HTTPS) și previne afișarea linkurilor care pot reprezenta un risc de securitate. -Dacă link-ul utilizează o schemă diferită, cum ar fi `javascript:` sau `data:`, și sunteți sigur de conținutul acestuia, puteți dezactiva verificarea prin intermediul `|nocheck`. +Dacă linkul utilizează o altă schemă, de ex. `javascript:` sau `data:`, și sunteți sigur de conținutul său, puteți dezactiva verificarea folosind `|nocheck`. ```latte {var $link = 'javascript:window.close()'} -checked -unchecked +verificat +neverificat ``` -Tipărituri: +Afișează: ```latte -checked -unchecked +verificat +neverificat ``` -A se vedea, de asemenea, [checkUrl |#checkUrl]. +Vezi și [#checkUrl]. noescape .[filter] ------------------ -Dezactive scăparea automată. +Dezactivează escaparea automată. ```latte -{var $trustedHtmlString = 'hello'} -Escaped: {$trustedHtmlString} -Unescaped: {$trustedHtmlString|noescape} +{var $trustedHtmlString = 'salut'} +Escapat: {$trustedHtmlString} +Neescapat: {$trustedHtmlString|noescape} ``` -Imprimă: +Afișează: ```latte -Escaped: <b>hello</b> -Unescaped: hello +Escapat: <b>salut</b> +Neescapat: salut ``` .[warning] -Folosirea abuzivă a filtrului `noescape` poate duce la o vulnerabilitate XSS! Nu îl utilizați niciodată decât dacă sunteți **absolut sigur** de ceea ce faceți și dacă șirul pe care îl imprimați provine dintr-o sursă de încredere. +Utilizarea incorectă a filtrului `noescape` poate duce la vulnerabilitatea XSS! Nu-l utilizați niciodată dacă nu sunteți **absolut sigur** de ceea ce faceți și că șirul afișat provine dintr-o sursă de încredere. -number(int decimals = 0, string decPoint = '.', string thousandsSep = ',') .[filter] ------------------------------------------------------------------------------------- -Formatează un număr cu un anumit număr de zecimale. De asemenea, puteți specifica un caracter al punctului zecimal și al separatorului de mii. +number(int $decimals=0, string $decPoint='.', string $thousandsSep=',') .[filter] +--------------------------------------------------------------------------------- +Formatează numărul la un anumit număr de zecimale. Dacă sunt setate [setările regionale |develop#Locale], se vor folosi separatoarele corespunzătoare pentru zecimale și mii. ```latte -{1234.20 |number} 1,234 -{1234.20 |number:1} 1,234.2 -{1234.20 |number:2} 1,234.20 -{1234.20 |number:2, ',', ' '} 1 234,20 +{1234.20|number} 1.234 +{1234.20|number:1} 1.234,2 +{1234.20|number:2} 1.234,20 +{1234.20|number:2, ',', ' '} 1 234,20 ``` -padLeft(int length, string pad = ' ') .[filter] +number(string $format) .[filter] +-------------------------------- +Parametrul `format` permite definirea aspectului numerelor exact conform nevoilor dvs. Pentru aceasta, este necesar să aveți setate [setările regionale |develop#Locale]. Formatul constă din mai multe caractere speciale, a căror descriere completă o găsiți în documentația "DecimalFormat":https://unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns: + +- `0` cifră obligatorie, se afișează întotdeauna, chiar dacă este zero +- `#` cifră opțională, se afișează doar dacă numărul există efectiv în această poziție +- `@` cifră semnificativă, ajută la afișarea numărului cu un anumit număr de cifre valide +- `.` indică unde trebuie să fie virgula zecimală (sau punctul, în funcție de țară) +- `,` servește la separarea grupurilor de cifre, cel mai adesea miilor +- `%` numărul se înmulțește cu 100× și se adaugă semnul procentului + +Să vedem câteva exemple. În primul exemplu, două zecimale sunt obligatorii, în al doilea sunt opționale. Al treilea exemplu arată completarea cu zerouri la stânga și la dreapta, al patrulea afișează doar cifrele existente: + +```latte +{1234.5|number: '#,##0.00'} {* 1.234,50 *} +{1234.5|number: '#,##0.##'} {* 1.234,5 *} +{1.23 |number: '000.000'} {* 001,230 *} +{1.2 |number: '##.##'} {* 1,2 *} +``` + +Cifrele semnificative determină câte cifre, indiferent de virgula zecimală, trebuie afișate, rotunjindu-se: + +```latte +{1234|number: '@@'} {* 1200 *} +{1234|number: '@@@'} {* 1230 *} +{1234|number: '@@@#'} {* 1234 *} +{1.2345|number: '@@@'} {* 1,23 *} +{0.00123|number: '@@'} {* 0,0012 *} +``` + +O modalitate ușoară de a afișa numărul ca procent. Numărul se înmulțește cu 100× și se adaugă semnul `%`: + +```latte +{0.1234|number: '#.##%'} {* 12,34% *} +``` + +Putem defini un format diferit pentru numerele pozitive și negative, separate prin caracterul `;`. În acest mod, de exemplu, se poate seta ca numerele pozitive să fie afișate cu semnul `+`: + +```latte +{42|number: '#.##;(#.##)'} {* 42 *} +{-42|number: '#.##;(#.##)'} {* (42) *} +{42|number: '+#.##;-#.##'} {* +42 *} +{-42|number: '+#.##;-#.##'} {* -42 *} +``` + +Rețineți că aspectul real al numerelor poate varia în funcție de setările țării. De exemplu, în unele țări se folosește virgula în loc de punct ca separator zecimal. Acest filtru ia în considerare automat acest lucru și nu trebuie să vă faceți griji. + + +padLeft(int $length, string $pad=' ') .[filter] ----------------------------------------------- -Pads un șir de caractere la o anumită lungime cu un alt șir de caractere din stânga. +Completează șirul la o anumită lungime cu un alt șir de la stânga. ```latte -{='hello'|padLeft: 10, '123'} {* ieșiri '12312hello' *} +{='salut'|padLeft: 10, '123'} {* afișează '12312salut' *} ``` -padRight(int length, string pad = ' ') .[filter] +padRight(int $length, string $pad=' ') .[filter] ------------------------------------------------ -Adaugă un șir de caractere la o anumită lungime cu un alt șir din dreapta. +Completează șirul la o anumită lungime cu un alt șir de la dreapta. ```latte -{='hello'|padRight: 10, '123'} {* ieșiri 'hello12312' *} +{='salut'|padRight: 10, '123'} {* afișează 'salut12312' *} ``` -query .[filter]{data-version:2.10} ------------------------------------ -Generează dinamic un șir de interogare în URL: +query .[filter] +--------------- +Generează dinamic query string în URL: ```latte -click -search +clic +căutare ``` -Imprimă: +Afișează: ```latte -click -search +clic +căutare ``` -Cheile cu o valoare de `null` sunt omise. +Cheile cu valoarea `null` sunt omise. -A se vedea, de asemenea, [escapeUrl |#escapeUrl]. +Vezi și [#escapeUrl]. -random .[filter]{data-version:2.10.2} -------------------------------------- -Returnează un element aleatoriu din matrice sau un caracter aleatoriu dintr-un șir de caractere: +random .[filter] +---------------- +Returnează un element aleatoriu al array-ului sau caracterul șirului: ```latte -{=[1, 2, 3, 4]|random} {* exemplu de ieșire: 3 *} -{='abcd'|random} {* exemplu de ieșire: 'b' *} +{=[1, 2, 3, 4]|random} {* afișează de ex.: 3 *} +{='abcd'|random} {* afișează de ex.: 'b' *} ``` -A se vedea, de asemenea, [first |#first], [last |#last]. +Vezi și [#first], [#last]. -repeat(int count) .[filter] ---------------------------- +repeat(int $count) .[filter] +---------------------------- Repetă șirul de x ori. ```latte -{='hello'|repeat: 3} {* outputs 'hellohellohello' *} +{='salut'|repeat: 3} {* afișează 'salutsalutsalut' *} ``` -replace(string|array search, string replace = '') .[filter] +replace(string|array $search, string $replace='') .[filter] ----------------------------------------------------------- Înlocuiește toate aparițiile șirului de căutare cu șirul de înlocuire. ```latte -{='hello world'|replace: 'world', 'friend'} {* iese 'hello friend' *} +{='hello world'|replace: 'world', 'friend'} {* afișează 'hello friend' *} ``` -Se pot face mai multe înlocuiri deodată: .{data-version:2.10.2} +Se pot efectua și mai multe înlocuiri simultan: ```latte -{='hello world'|replace: [h => l, l => h]} {* outputs 'lehho worhd' *} +{='hello world'|replace: [h => l, l => h]} {* afișează 'lehho worhd' *} ``` -replaceRE(string pattern, string replace = '') .[filter] +replaceRE(string $pattern, string $replace='') .[filter] -------------------------------------------------------- -Înlocuiește toate ocurențele în funcție de expresia regulată. +Efectuează căutarea expresiilor regulate cu înlocuire. ```latte -{='hello world'|replaceRE: '/l.*/', 'l'} {* outputs 'hel' *} +{='hello world'|replaceRE: '/l.*/', 'l'} {* afișează 'hel' *} ``` reverse .[filter] ----------------- -Inversează șirul sau matricea dată. +Inversează șirul sau array-ul dat. ```latte {var $s = 'Nette'} -{$s|reverse} {* iese "etteN" *} +{$s|reverse} {* afișează 'etteN' *} {var $a = ['N', 'e', 't', 't', 'e']} {$a|reverse} {* returnează ['e', 't', 't', 'e', 'N'] *} ``` -round(int precision = 0) .[filter] ----------------------------------- -Rotunjește un număr la o precizie dată. +round(int $precision=0) .[filter] +--------------------------------- +Rotunjește numărul la precizia dată. ```latte -{=3.4|round} {* ieșiri 3 *} -{=3.5|round} {* ieșiri 4 *} -{=135.79|round:1} {* ieșiri 135.8 *} -{=135.79|round:3} {* ieșiri 135.79 *} +{=3.4|round} {* afișează 3 *} +{=3.5|round} {* afișează 4 *} +{=135.79|round:1} {* afișează 135.8 *} +{=135.79|round:3} {* afișează 135.79 *} ``` -A se vedea, de asemenea, [plafon |#ceil], [podea |#floor]. +Vezi și [#ceil], [#floor]. -slice(int start, int length = null, bool preserveKeys = false) .[filter]{data-version:2.10.2} ---------------------------------------------------------------------------------------------- -Extrage o porțiune dintr-un tablou sau un șir de caractere. +slice(int $start, ?int $length=null, bool $preserveKeys=false) .[filter] +------------------------------------------------------------------------ +Extrage o parte a array-ului sau a șirului. ```latte -{='hello'|slice: 1, 2} {* ieșiri 'el' *} -{=['a', 'b', 'c']|slice: 1, 2} {* ieșiri ['b', 'c'] *} +{='salut'|slice: 1, 2} {* afișează 'al' *} +{=['a', 'b', 'c']|slice: 1, 2} {* afișează ['b', 'c'] *} ``` -Filtrul slice funcționează ca funcția PHP `array_slice` pentru array-uri și `mb_substr` pentru șiruri de caractere, cu o revenire la `iconv_substr` în modul UTF-8. +Filtrul funcționează ca funcția PHP `array_slice` pentru array-uri sau `mb_substr` pentru șiruri, cu fallback la funcția `iconv_substr` în modul UTF-8. -În cazul în care startul nu este negativ, secvența va începe de la acel start în variabilă. Dacă start este negativ, secvența va începe la acea distanță de la sfârșitul variabilei. +Dacă start este pozitiv, secvența va începe deplasată cu acest număr de la începutul array-ului/șirului. Dacă este negativ, secvența va începe deplasată cu atâtea de la sfârșit. -În cazul în care lungimea este dată și este pozitivă, atunci secvența va avea până la numărul de elemente din ea. În cazul în care variabila este mai scurtă decât lungimea, atunci vor fi prezente numai elementele disponibile ale variabilei. În cazul în care lungimea este dată și este negativă, secvența se va opri la atâtea elemente de la sfârșitul variabilei. În cazul în care este omisă, secvența va conține toate elementele de la offset până la sfârșitul variabilei. +Dacă parametrul length este specificat și este pozitiv, secvența va conține atâtea elemente. Dacă în această funcție este transmis un parametru length negativ, secvența va conține toate elementele array-ului original, începând de la poziția start și terminând la poziția mai mică cu length elemente de la sfârșitul array-ului. Dacă nu specificați acest parametru, secvența va conține toate elementele array-ului original, începând de la poziția start. -Filter reordonează și resetează implicit cheile tabloului de numere întregi. Acest comportament poate fi modificat prin setarea preserveKeys la true. Cheile șirurilor de caractere sunt întotdeauna păstrate, indiferent de acest parametru. +În mod implicit, filtrul schimbă ordinea și resetează cheile întregi ale array-ului. Acest comportament poate fi schimbat setând preserveKeys la true. Cheile șir sunt întotdeauna păstrate, indiferent de acest parametru. -sort .[filter]{data-version:2.9} ---------------------------------- -Filtru care sortează o matrice și menține asocierea indicilor. +sort(?Closure $comparison, string|int|\Closure|null $by=null, string|int|\Closure|bool $byKey=false) .[filter] +-------------------------------------------------------------------------------------------------------------- +Filtrul sortează elementele unui array sau iterator și păstrează cheile lor asociative. Când sunt setate [setările regionale |develop#Locale], sortarea respectă regulile acestora, dacă nu este specificată o funcție de comparare personalizată. ```latte {foreach ($names|sort) as $name} @@ -601,7 +735,7 @@ Filtru care sortează o matrice și menține asocierea indicilor. {/foreach} ``` -Array sortat în ordine inversă. +Array sortat în ordine inversă: ```latte {foreach ($names|sort|reverse) as $name} @@ -609,75 +743,101 @@ Array sortat în ordine inversă. {/foreach} ``` -Puteți trece propria funcție de comparație ca parametru: .{data-version:2.10.2} +Puteți specifica o funcție de comparare personalizată pentru sortare (exemplul arată cum să inversați sortarea de la cel mai mare la cel mai mic): ```latte -{var $sorted = ($names|sort: fn($a, $b) => $b <=> $a)} +{var $reverted = ($names|sort: fn($a, $b) => $b <=> $a)} ``` +Filtrul `|sort` permite, de asemenea, sortarea elementelor după chei: -spaceless .[filter]{data-version:2.10.2} ------------------------------------------ -Îndepărtează spațiile albe inutile din rezultat. De asemenea, puteți utiliza aliasul `strip`. +```latte +{foreach ($names|sort: byKey: true) as $name} + ... +{/foreach} +``` + +Dacă trebuie să sortați un tabel după o anumită coloană, puteți utiliza parametrul `by`. Valoarea `'name'` din exemplu specifică faptul că se va sorta după `$item->name` sau `$item['name']`, în funcție de dacă `$item` este un array sau un obiect: + +```latte +{foreach ($items|sort: by: 'name') as $item} + {$item->name} +{/foreach} +``` + +Puteți defini, de asemenea, o funcție callback care determină valoarea după care se va sorta: + +```latte +{foreach ($items|sort: by: fn($items) => $items->category->name) as $item} + {$item->name} +{/foreach} +``` + +În același mod se poate utiliza și parametrul `byKey`. + + +spaceless .[filter] +------------------- +Elimină spațiul alb (spațiile) inutil din ieșire. Puteți folosi și aliasul `strip`. ```latte {block |spaceless}
                                                                                                          -
                                                                                                        • Hello
                                                                                                        • +
                                                                                                        • Salut
                                                                                                        {/block} ``` -Imprimă: +Afișează: ```latte -
                                                                                                        • Hello
                                                                                                        +
                                                                                                        • Salut
                                                                                                        ``` stripHtml .[filter] ------------------- -Convertește HTML în text simplu. Adică, elimină etichetele HTML și convertește entitățile HTML în text. +Convertește HTML în text simplu. Adică elimină tag-urile HTML și convertește entitățile HTML în text. ```latte -{='

                                                                                                        one < two

                                                                                                        '|stripHtml} {* outputs 'one < two' *} +{='

                                                                                                        one < two

                                                                                                        '|stripHtml} {* afișează 'one < two' *} ``` -Textul simplu rezultat poate conține, în mod natural, caractere care reprezintă etichete HTML, de exemplu `'<p>'|stripHtml` este convertit în `

                                                                                                        `. Nu scoateți niciodată textul rezultat cu `|noescape`, deoarece acest lucru poate duce la o vulnerabilitate de securitate. +Textul simplu rezultat poate conține în mod natural caractere care reprezintă tag-uri HTML, de exemplu `'<p>'|stripHtml` se convertește în `

                                                                                                        `. În niciun caz nu afișați textul astfel obținut cu `|noescape`, deoarece acest lucru poate duce la o vulnerabilitate de securitate. -substr(int offset, int length = null) .[filter] ------------------------------------------------ -Extrage o porțiune dintr-un șir de caractere. Acest filtru a fost înlocuit cu un filtru de [felie |#slice]. +substr(int $offset, ?int $length=null) .[filter] +------------------------------------------------ +Extrage o parte a șirului. Acest filtru a fost înlocuit de filtrul [#slice]. ```latte {$string|substr: 1, 2} ``` -translate(string message, ...args) .[filter]{data-version:3.0} --------------------------------------------------------------- -Traduce expresii în alte limbi. Pentru ca filtrul să fie disponibil, trebuie să configurați [translator |develop#TranslatorExtension]. De asemenea, puteți utiliza [etichetele pentru traducere |tags#Translation]. +translate(...$args) .[filter] +----------------------------- +Traduce expresiile în alte limbi. Pentru ca filtrul să fie disponibil, trebuie [setat traducătorul |develop#TranslatorExtension]. Puteți utiliza și [tag-urile pentru traducere |tags#Traduceri]. ```latte -{='Baskter'|translate} +{='Coș'|translate} {$item|translate} ``` -trim(string charlist = " \t\n\r\0\x0B\u{A0}") .[filter] -------------------------------------------------------- -Elimină caracterele de început și de sfârșit, implicit spațiile albe. +trim(string $charlist=" \t\n\r\0\x0B\u{A0}") .[filter] +------------------------------------------------------ +Elimină spațiile albe (sau alte caractere) de la începutul și sfârșitul șirului. ```latte -{=' I like Latte. '|trim} {* outputs 'I like Latte.' *} -{=' I like Latte.'|trim: '.'} {* outputs ' I like Latte' *} +{=' I like Latte. '|trim} {* afișează 'I like Latte.' *} +{=' I like Latte.'|trim: '.'} {* afișează ' I like Latte' *} ``` -truncate(int length, string append = '…') .[filter] +truncate(int $length, string $append='…') .[filter] --------------------------------------------------- -Scurtează un șir de caractere până la lungimea maximă dată, dar încearcă să păstreze cuvintele întregi. În cazul în care șirul este trunchiat, adaugă elipse la sfârșit (acest lucru poate fi modificat prin intermediul celui de-al doilea parametru). +Taie șirul la lungimea maximă specificată, încercând să păstreze cuvintele întregi. Dacă șirul este scurtat, adaugă la sfârșit trei puncte (se poate schimba cu al doilea parametru). ```latte {var $title = 'Hello, how are you?'} @@ -689,25 +849,25 @@ Scurtează un șir de caractere până la lungimea maximă dată, dar încearcă upper .[filter] --------------- -Convertește o valoare în majuscule. Necesită extensia PHP `mbstring`. +Convertește șirul în litere mari. Necesită extensia PHP `mbstring`. ```latte -{='latte'|upper} {* outputs 'LATTE' *} +{='latte'|upper} {* afișează 'LATTE' *} ``` -A se vedea, de asemenea, [capitalize |#capitalize], [firstUpper |#firstUpper], [lower |#lower]. +Vezi și [#capitalize], [#firstUpper], [#lower]. webalize .[filter] ------------------ -Convertește în ASCII. +Modifică șirul UTF-8 în forma utilizată în URL. -Convertește spațiile în cratimă. Îndepărtează caracterele care nu sunt alfanumerice, sublinieri sau cratime. Convertește în minuscule. Îndepărtează, de asemenea, spațiile albe de început și de sfârșit. +Se convertește în ASCII. Convertește spațiile în cratime. Elimină caracterele care nu sunt alfanumerice, underscore sau cratime. Convertește în litere mici. Elimină, de asemenea, spațiile de la început și sfârșit. ```latte -{var $s = 'Our 10. product'} -{$s|webalize} {* outputs 'our-10-product' *} +{var $s = 'Produsul nostru 10'} +{$s|webalize} {* afișează 'produsul-nostru-10' *} ``` .[caution] -Necesită pachetul [nette/utils |utils:]. +Necesită biblioteca [nette/utils|utils:]. diff --git a/latte/ro/functions.texy b/latte/ro/functions.texy index af3271c75b..22137b9903 100644 --- a/latte/ro/functions.texy +++ b/latte/ro/functions.texy @@ -2,22 +2,24 @@ Funcții Latte ************* .[perex] -În plus față de funcțiile PHP obișnuite, le puteți utiliza și în șabloane. +În șabloane, pe lângă funcțiile PHP obișnuite, putem folosi și aceste funcții suplimentare. .[table-latte-filters] -| `clamp` | [fixează valoarea în interval |#clamp] +| `clamp` | [limitează valoarea la un interval dat |#clamp] | `divisibleBy`| [verifică dacă o variabilă este divizibilă cu un număr |#divisibleBy] -| `even` | [verifică dacă numărul dat este par |#even] -| `first` | [returnează primul element al unui array sau caracterul unui șir de caractere|#first] -| `last` | [returnează ultimul element al unui array sau caracter al unui șir de caractere|#last] -| `odd` | [verifică dacă numărul dat este impar |#odd] -| `slice` | [extrage o porțiune dintr-un tablou sau un șir de caractere |#slice] +| `even` | [verifică dacă numărul dat este par |#even] +| `first` | [returnează primul element al array-ului sau caracterul șirului |#first] +| `group` | [grupează datele după diverse criterii |#group] +| `hasBlock` | [verifică existența unui bloc |#hasBlock] +| `last` | [returnează ultimul element al array-ului sau caracterul șirului |#last] +| `odd` | [verifică dacă numărul dat este impar |#odd] +| `slice` | [extrage o parte a array-ului sau a șirului |#slice] -Utilizare .[#toc-usage] -======================= +Utilizare +========= -Funcțiile sunt utilizate în același mod ca și funcțiile obișnuite din PHP și pot fi utilizate în toate expresiile: +Funcțiile se utilizează la fel ca funcțiile PHP obișnuite și pot fi folosite în toate expresiile: ```latte

                                                                                                        {clamp($num, 1, 100)}

                                                                                                        @@ -25,14 +27,14 @@ Funcțiile sunt utilizate în același mod ca și funcțiile obișnuite din PHP {if odd($num)} ... {/if} ``` -[Funcțiile personalizate |extending-latte#functions] pot fi înregistrate în acest mod: +[Funcțiile personalizate|custom-functions] pot fi înregistrate în acest mod: ```php $latte = new Latte\Engine; $latte->addFunction('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -Le folosim într-un șablon ca acesta: +În șablon, se apelează apoi astfel: ```latte

                                                                                                        {shortify($text)}

                                                                                                        @@ -40,23 +42,23 @@ Le folosim într-un șablon ca acesta: ``` -Funcții .[#toc-functions] -========================= +Funcții +======= -clamp(int|float $value, int|float $min, int|float $max): int|float .[method]{data-version:2.9} ----------------------------------------------------------------------------------------------- -Returnează valoarea fixată în intervalul inclusiv dintre min și max. +clamp(int|float $value, int|float $min, int|float $max): int|float .[method] +---------------------------------------------------------------------------- +Limitează valoarea la intervalul inclusiv dat min și max. ```latte {=clamp($level, 0, 255)} ``` -A se vedea, de asemenea, [filter clamp |filters#clamp]: +Vezi și [filtrul clamp |filters#clamp]. -divisibleBy(int $value, int $by): bool .[method]{data-version:2.10.2} ---------------------------------------------------------------------- +divisibleBy(int $value, int $by): bool .[method] +------------------------------------------------ Verifică dacă o variabilă este divizibilă cu un număr. ```latte @@ -64,8 +66,8 @@ Verifică dacă o variabilă este divizibilă cu un număr. ``` -even(int $value): bool .[method]{data-version:2.10.2} ------------------------------------------------------ +even(int $value): bool .[method] +-------------------------------- Verifică dacă numărul dat este par. ```latte @@ -73,32 +75,62 @@ Verifică dacă numărul dat este par. ``` -first(string|array $value): mixed .[method]{data-version:2.10.2} ----------------------------------------------------------------- -Returnează primul element al unui tablou sau caracterul unui șir de caractere: +first(string|iterable $value): mixed .[method] +---------------------------------------------- +Returnează primul element al array-ului sau caracterul șirului: ```latte -{=first([1, 2, 3, 4])} {* ieșiri 1 *} -{=first('abcd')} {* iese "a" *} +{=first([1, 2, 3, 4])} {* afișează 1 *} +{=first('abcd')} {* afișează 'a' *} ``` -A se vedea, de asemenea, [last |#last], [filter first |filters#first]. +Vezi și [#last], [filtrul first |filters#first]. -last(string|array $value): mixed .[method]{data-version:2.10.2} ---------------------------------------------------------------- -Returnează ultimul element al unui array sau caracter al unui șir de caractere: +group(iterable $data, string|int|\Closure $by): array .[method]{data-version:3.0.16} +------------------------------------------------------------------------------------ +Funcția grupează datele după diverse criterii. + +În acest exemplu, rândurile din tabel sunt grupate după coloana `categoryId`. Ieșirea este un array de array-uri, unde cheia este valoarea din coloana `categoryId`. [Citiți un tutorial detaliat|cookbook/grouping]. + +```latte +{foreach group($items, categoryId) as $categoryId => $categoryItems} +
                                                                                                          + {foreach $categoryItems as $item} +
                                                                                                        • {$item->name}
                                                                                                        • + {/foreach} +
                                                                                                        +{/foreach} +``` + +Vezi și filtrul [group |filters#group]. + + +hasBlock(string $name): bool .[method]{data-version:3.0.10} +----------------------------------------------------------- +Verifică dacă blocul cu numele specificat există: + +```latte +{if hasBlock(header)} ... {/if} +``` + +Vezi și [verificarea existenței blocurilor |template-inheritance#Verificarea existenței blocurilor ifset]. + + +last(string|array $value): mixed .[method] +------------------------------------------ +Returnează ultimul element al array-ului sau caracterul șirului: ```latte -{=last([1, 2, 3, 4])} {* ieșiri 4 *} -{=last('abcd')} {* ieșiri 'd' *} +{=last([1, 2, 3, 4])} {* afișează 4 *} +{=last('abcd')} {* afișează 'd' *} ``` -A se vedea, de asemenea, [first |#first], [filter last |filters#last]. +Vezi și [#first], [filtrul last |filters#last]. -odd(int $value): bool .[method]{data-version:2.10.2} ----------------------------------------------------- +odd(int $value): bool .[method] +------------------------------- Verifică dacă numărul dat este impar. ```latte @@ -106,19 +138,19 @@ Verifică dacă numărul dat este impar. ``` -slice(string|array $value, int $start, int $length=null, bool $preserveKeys=false): string|array .[method]{data-version:2.10.2} -------------------------------------------------------------------------------------------------------------------------------- -Extrage o porțiune dintr-un tablou sau un șir de caractere. +slice(string|array $value, int $start, ?int $length=null, bool $preserveKeys=false): string|array .[method] +----------------------------------------------------------------------------------------------------------- +Extrage o parte a array-ului sau a șirului. ```latte -{=slice('hello', 1, 2)} {* ieșiri 'el' *} -{=slice(['a', 'b', 'c'], 1, 2)} {* ieșiri ['b', 'c'] *} +{=slice('salut', 1, 2)} {* afișează 'al' *} +{=slice(['a', 'b', 'c'], 1, 2)} {* afișează ['b', 'c'] *} ``` -Filtrul de felie funcționează ca funcția PHP `array_slice` pentru array-uri și `mb_substr` pentru șiruri de caractere, cu o revenire la `iconv_substr` în modul UTF-8. +Filtrul funcționează ca funcția PHP `array_slice` pentru array-uri sau `mb_substr` pentru șiruri, cu fallback la funcția `iconv_substr` în modul UTF-8. -În cazul în care startul nu este negativ, secvența va începe de la acel start în variabilă. Dacă start este negativ, secvența va începe la acea distanță de la sfârșitul variabilei. +Dacă start este pozitiv, secvența va începe deplasată cu acest număr de la începutul array-ului/șirului. Dacă este negativ, secvența va începe deplasată cu atâtea de la sfârșit. -În cazul în care lungimea este dată și este pozitivă, atunci secvența va avea până la numărul de elemente din ea. În cazul în care variabila este mai scurtă decât lungimea, atunci vor fi prezente numai elementele disponibile ale variabilei. În cazul în care lungimea este dată și este negativă, secvența se va opri la atâtea elemente de la sfârșitul variabilei. În cazul în care este omisă, secvența va conține toate elementele de la offset până la sfârșitul variabilei. +Dacă parametrul length este specificat și este pozitiv, secvența va conține atâtea elemente. Dacă în această funcție este transmis un parametru length negativ, secvența va conține toate elementele array-ului original, începând de la poziția start și terminând la poziția mai mică cu length elemente de la sfârșitul array-ului. Dacă nu specificați acest parametru, secvența va conține toate elementele array-ului original, începând de la poziția start. -Filter reordonează și resetează implicit cheile tabloului de numere întregi. Acest comportament poate fi modificat prin setarea preserveKeys la true. Cheile șirurilor de caractere sunt întotdeauna păstrate, indiferent de acest parametru. +În mod implicit, filtrul schimbă ordinea și resetează cheile întregi ale array-ului. Acest comportament poate fi schimbat setând preserveKeys la true. Cheile șir sunt întotdeauna păstrate, indiferent de acest parametru. diff --git a/latte/ro/guide.texy b/latte/ro/guide.texy index ae47dc8aa4..f57c757c48 100644 --- a/latte/ro/guide.texy +++ b/latte/ro/guide.texy @@ -1,46 +1,45 @@ -Noțiuni de bază pentru a începe cu Latte -**************************************** +Începeți cu Latte +*****************
                                                                                                        -Șabloanele îmbunătățesc organizarea codului, separă logica aplicației de prezentare și sporesc securitatea. Acestea oferă caracteristici și capacități expresive mult mai bune pentru generarea de HTML decât PHP însuși. +Șabloanele îmbunătățesc organizarea codului, separă logica aplicației de prezentare și cresc securitatea. Oferă funcții și mijloace expresive mult mai bune pentru generarea HTML decât PHP în sine. -Latte este cel mai sigur sistem de creare de șabloane pentru PHP. Vă va plăcea sintaxa sa intuitivă. O gamă largă de caracteristici utile vă va simplifica semnificativ munca. -Oferă o protecție de top împotriva [vulnerabilităților critice |safety-first] și vă permite să vă concentrați pe crearea de aplicații de înaltă calitate fără a vă îngrijora de securitatea acestora. +Latte este cel mai sigur sistem de șabloane pentru PHP. Vă veți îndrăgosti de sintaxa sa intuitivă. O gamă largă de funcții utile vă va ușura semnificativ munca. Oferă protecție de top împotriva [vulnerabilităților critice|safety-first] și vă permite să vă concentrați pe crearea de aplicații de calitate fără a vă face griji pentru securitatea lor. -Cum să scrieți șabloane folosind Latte? .[#toc-how-to-write-templates-using-latte] ----------------------------------------------------------------------------------- +Cum să scrieți șabloane folosind Latte? +--------------------------------------- -Latte este inteligent conceput și ușor de învățat pentru cei care sunt familiarizați cu PHP, deoarece pot adopta rapid etichetele sale de bază. +Latte este inteligent proiectat și ușor de învățat de către cei care cunosc PHP și își însușesc tag-urile de bază. -- Mai întâi, familiarizați-vă cu [sintaxa Latte |syntax] și [încercați totul online |https://fiddle.nette.org/latte/] -- Aruncați o privire la setul de bază de [etichete |tags] și [filtre |filters] -- Scrieți șabloane în [editor cu suport Latte |recipes#Editors and IDE] +- Mai întâi, familiarizați-vă cu [sintaxa Latte|syntax] și [ÎNCERCAȚI-O ONLINE |https://fiddle.nette.org/latte/#9cc0cf6d89#9cc0cf6d89] +- Aruncați o privire la setul de bază de [tag-uri|tags] și [filtre|filters] +- Scrieți șabloane într-un [editor cu suport Latte |recipes#Editoare și IDE-uri] -Cum să utilizați Latte în PHP? .[#toc-how-to-use-latte-in-php] --------------------------------------------------------------- +Cum să folosiți Latte în PHP? +----------------------------- -Implementarea Latte în noua dumneavoastră aplicație este o chestiune de minute: +Implementarea Latte în noua dvs. aplicație este o chestiune de câteva minute: -- Mai întâi, [instalați și rulați Latte |develop#Installation] -- Răsfățați-vă cu [instrumentul de depanare Tracy |develop#Debugging and Tracy] +- Mai întâi [instalați și rulați Latte |develop#Instalare] +- Lăsați-vă răsfățați de [instrumentul de depanare Tracy |develop#Depanare și Tracy] - Extindeți Latte cu [funcționalități personalizate |extending-latte] -Dacă convertiți un proiect vechi scris în PHP simplu în [Latte, instrumentul de conversie a codului PHP în Latte |cookbook/migration-from-php] vă va ușura migrarea. Sau plănuiți să treceți la Latte de la Twig? Avem un [convertor de șabloane Twig în Latte |cookbook/migration-from-twig] pentru dumneavoastră. +Dacă convertiți un proiect vechi scris în PHP pur în Latte, migrarea va fi facilitată de [instrumentul pentru conversia codului PHP în Latte |cookbook/migration-from-php]. Sau vă pregătiți să treceți la Latte de la Twig? Avem pentru dvs. un [convertor de șabloane Twig în Latte |cookbook/migration-from-twig]. -Ce mai poate face Latte? .[#toc-what-else-can-latte-do] -------------------------------------------------------- +Ce mai poate face Latte? +------------------------ -Latte vine complet echipat, cu toate elementele esențiale incluse. +Latte vine complet echipat, cu tot ce este important inclus în pachetul de bază. -- Productivitatea dvs. va fi stimulată de [mecanismele de moștenire |template-inheritance] care reutilizează elemente și structuri repetate -- buncărul blindat [Sandbox |Sandbox] izolează șabloanele de sursele nesigure, cum ar fi cele editate chiar de utilizatori -- Pentru mai multă inspirație, iată [sfaturi și trucuri |recipes] +- Productivitatea dvs. va fi impulsionată de [mecanismele de moștenire |template-inheritance], datorită cărora elementele și structurile repetitive sunt reutilizate +- Buncărul blindat [sandbox |sandbox] izolează șabloanele din surse nesigure, care, de exemplu, sunt editate chiar de utilizatori +- Pentru inspirație suplimentară, există [sfaturi și trucuri |recipes]
                                                                                                        -{{description: Latte je nejbezpečnější šablonovací systém pro PHP. Zabraňuje spoustě bezpečnostních zranitelností. Oceníte jeho intuitivní syntaxi a oceníte spoustu užitečných vychytávek.}} +{{description: Latte este cel mai sigur sistem de șabloane pentru PHP. Previne o mulțime de vulnerabilități de securitate. Veți aprecia sintaxa sa intuitivă și o mulțime de caracteristici utile.}} diff --git a/latte/ro/loaders.texy b/latte/ro/loaders.texy new file mode 100644 index 0000000000..f01c6bceb4 --- /dev/null +++ b/latte/ro/loaders.texy @@ -0,0 +1,198 @@ +Loadere +******* + +.[perex] +Loaderele sunt mecanismul pe care Latte îl folosește pentru a obține codul sursă al șabloanelor dvs. Cel mai adesea, șabloanele sunt stocate ca fișiere pe disc, dar datorită sistemului flexibil de loadere, le puteți încărca practic de oriunde sau chiar le puteți genera dinamic. + + +Ce este un Loader? +================== + +Când lucrați cu șabloane, de obicei vă imaginați fișiere `.latte` situate în structura de directoare a proiectului dvs. De acest lucru se ocupă [#FileLoader] implicit în Latte. Cu toate acestea, legătura dintre numele șablonului (cum ar fi `'main.latte'` sau `'components/card.latte'`) și codul său sursă real *nu trebuie* să fie o mapare directă la calea fișierului. + +Aici intervin loaderele. Un loader este un obiect care are sarcina de a lua numele șablonului (un șir de identificare) și de a furniza Latte codul său sursă. Latte se bazează complet pe loaderul configurat pentru această sarcină. Acest lucru este valabil nu numai pentru șablonul inițial solicitat folosind `$latte->render('main.latte')`, ci și pentru **fiecare șablon referit în interior** folosind tag-uri precum `{include ...}`, `{layout ...}`, `{embed ...}` sau `{import ...}`. + +De ce să folosiți un loader personalizat? + +- **Încărcarea din surse alternative:** Obținerea șabloanelor stocate într-o bază de date, în cache (cum ar fi Redis sau Memcached), într-un sistem de control al versiunilor (cum ar fi Git, pe baza unui commit specific) sau generate dinamic. +- **Implementarea convențiilor de denumire personalizate:** Este posibil să doriți să utilizați aliasuri mai scurte pentru șabloane sau să implementați o logică specifică a căilor de căutare (de exemplu, căutați mai întâi în directorul temei, apoi reveniți la directorul implicit). +- **Adăugarea securității sau a controlului accesului:** Un loader personalizat poate verifica permisiunile utilizatorului înainte de a încărca anumite șabloane. +- **Preprocesare:** Deși în general nu se recomandă ([trecerile de compilare |compiler-passes] sunt mai bune), un loader *ar putea* teoretic să preproceseze conținutul șablonului înainte de a-l transmite către Latte. + +Setați loaderul pentru instanța `Latte\Engine` folosind metoda `setLoader()`: + +```php +$latte = new Latte\Engine; + +// Utilizarea FileLoader implicit pentru fișierele din '/path/to/templates' +$loader = new Latte\Loaders\FileLoader('/path/to/templates'); +$latte->setLoader($loader); +``` + +Loaderul trebuie să implementeze interfața `Latte\Loader`. + + +Loadere încorporate +=================== + +Latte oferă mai multe loadere standard: + + +FileLoader +---------- + +Acesta este **loaderul implicit** utilizat de clasa `Latte\Engine`, dacă nu este specificat niciun altul. Încarcă șabloanele direct din sistemul de fișiere. + +Opțional, puteți seta un director rădăcină pentru a restricționa accesul: + +```php +use Latte\Loaders\FileLoader; + +// Următorul va permite încărcarea șabloanelor numai din directorul /var/www/html/templates +$loader = new FileLoader('/var/www/html/templates'); +$latte->setLoader($loader); + +// $latte->render('../../../etc/passwd'); // Acest lucru ar arunca o excepție + +// Randarea unui șablon situat la /var/www/html/templates/pages/contact.latte +$latte->render('pages/contact.latte'); +``` + +La utilizarea tag-urilor precum `{include}` sau `{layout}`, rezolvă numele șabloanelor relativ la șablonul curent, dacă nu este specificată o cale absolută. + + +StringLoader +------------ + +Acest loader obține conținutul șablonului dintr-un array asociativ, unde cheile sunt numele șabloanelor (identificatori) și valorile sunt șiruri de cod sursă al șablonului. Este deosebit de util pentru testare sau aplicații mici, unde șabloanele pot fi stocate direct în codul PHP. + +```php +use Latte\Loaders\StringLoader; + +$loader = new StringLoader([ + 'main.latte' => 'Salut {$name}, include este mai jos:{include helper.latte}', + 'helper.latte' => '{var $x = 10}Conținut inclus: {$x}', + // Adăugați alte șabloane după cum este necesar +]); + +$latte->setLoader($loader); + +$latte->render('main.latte', ['name' => 'Lume']); +// Ieșire: Salut Lume, include este mai jos:Conținut inclus: 10 +``` + +Dacă trebuie să randați doar un singur șablon direct dintr-un șir, fără a fi nevoie de includere sau moștenire care să facă referire la alte șabloane șir numite, puteți transmite șirul direct metodei `render()` sau `renderToString()` atunci când utilizați `StringLoader` fără un array: + +```php +$loader = new StringLoader; +$latte->setLoader($loader); + +$templateString = 'Salut {$name}!'; +$output = $latte->renderToString($templateString, ['name' => 'Alice']); +// $output conține 'Salut Alice!' +``` + + +Crearea unui Loader personalizat +================================ + +Pentru a crea un loader personalizat (de exemplu, pentru a încărca șabloane dintr-o bază de date, cache, sistem de control al versiunilor sau altă sursă), trebuie să creați o clasă care implementează interfața [api:Latte\Loader]. + +Să vedem ce trebuie să facă fiecare metodă. + + +getContent(string $name): string .[method] +------------------------------------------ +Aceasta este metoda de bază a loaderului. Sarcina sa este să obțină și să returneze codul sursă complet al șablonului identificat prin `$name` (așa cum este transmis metodei `$latte->render()` sau returnat de metoda [#getReferredName()]). + +Dacă șablonul nu poate fi găsit sau accesat, această metodă **trebuie să arunce o excepție `Latte\RuntimeException`**. + +```php +public function getContent(string $name): string +{ + // Exemplu: Încărcare dintr-un depozit intern ipotetic + $content = $this->storage->read($name); + if ($content === null) { + throw new Latte\RuntimeException("Șablonul '$name' nu poate fi încărcat."); + } + return $content; +} +``` + + +getReferredName(string $name, string $referringName): string .[method] +---------------------------------------------------------------------- +Această metodă se ocupă de traducerea numelor de șabloane utilizate în interiorul tag-urilor precum `{include}`, `{layout}`, etc. Când Latte întâlnește, de exemplu, `{include 'partial.latte'}` în interiorul `main.latte`, apelează această metodă cu `$name = 'partial.latte'` și `$referringName = 'main.latte'`. + +Sarcina metodei este să traducă `$name` într-un identificator canonic (de exemplu, cale absolută, cheie unică de bază de date), care va fi utilizat la apelarea altor metode ale loaderului, pe baza contextului furnizat în `$referringName`. + +```php +public function getReferredName(string $name, string $referringName): string +{ + return ...; +} +``` + + +getUniqueId(string $name): string .[method] +------------------------------------------- +Latte utilizează un cache de șabloane compilate pentru a îmbunătăți performanța. Fiecare fișier de șablon compilat are nevoie de un nume unic derivat din identificatorul șablonului sursă. Această metodă furnizează un șir care **identifică în mod unic** șablonul `$name`. + +Pentru șabloanele bazate pe fișiere, calea absolută poate servi. Pentru șabloanele din baza de date, o combinație de prefix și ID de bază de date este comună. + +```php +public function getUniqueId(string $name): string +{ + return ...; +} +``` + + +Exemplu: Loader simplu de bază de date +-------------------------------------- + +Acest exemplu arată structura de bază a unui loader care încarcă șabloane stocate într-un tabel de bază de date numit `templates` cu coloanele `name` (identificator unic), `content` și `updated_at`. + +```php +use Latte; + +class DatabaseLoader implements Latte\Loader +{ + public function __construct( + private \PDO $db, + ) { + } + + public function getContent(string $name): string + { + $stmt = $this->db->prepare('SELECT content FROM templates WHERE name = ?'); + $stmt->execute([$name]); + $content = $stmt->fetchColumn(); + if ($content === false) { + throw new Latte\RuntimeException("Șablonul '$name' nu a fost găsit în baza de date."); + } + return $content; + } + + // Acest exemplu simplu presupune că numele șabloanelor ('homepage', 'article', etc.) + // sunt ID-uri unice și șabloanele nu se referă relativ unele la altele. + public function getReferredName(string $name, string $referringName): string + { + return $name; + } + + public function getUniqueId(string $name): string + { + // Utilizarea unui prefix și a numelui însuși este unică și suficientă aici + return 'db_' . $name; + } +} + +// Utilizare: +$pdo = new \PDO(/* detalii conexiune */); +$loader = new DatabaseLoader($pdo); +$latte->setLoader($loader); +$latte->render('homepage'); // Încarcă șablonul cu numele 'homepage' din DB +``` + +Loaderele personalizate vă oferă control complet asupra provenienței șabloanelor dvs. Latte, permițând integrarea cu diverse sisteme de stocare și fluxuri de lucru. diff --git a/latte/ro/recipes.texy b/latte/ro/recipes.texy index 88795d0716..ca0ed178c9 100644 --- a/latte/ro/recipes.texy +++ b/latte/ro/recipes.texy @@ -2,44 +2,44 @@ Sfaturi și trucuri ****************** -Editori și IDE .[#toc-editors-and-ide] -====================================== +Editoare și IDE-uri +=================== Scrieți șabloane într-un editor sau IDE care are suport pentru Latte. Va fi mult mai plăcut. -- NetBeans IDE are suport încorporat -- PhpStorm: instalați [pluginul Latte |https://plugins.jetbrains.com/plugin/7457-latte] în `Settings > Plugins > Marketplace` -- VS Code: căutați în markerplace pentru pluginul "Nette Latte + Neon" -- Sublime Text 3: în Package Control găsiți și instalați pachetul `Nette` și selectați Latte în `View > Syntax` -- în vechile editoare utilizați evidențierea Smarty pentru fișierele .latte +- PhpStorm: instalați în `Settings > Plugins > Marketplace` [pluginul Latte|https://plugins.jetbrains.com/plugin/7457-latte] +- VS Code: instalați [Nette Latte + Neon|https://marketplace.visualstudio.com/items?itemName=Kasik96.latte], [Nette Latte templates|https://marketplace.visualstudio.com/items?itemName=smuuf.latte-lang] sau cel mai recent [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode] plugin +- NetBeans IDE: suportul nativ pentru Latte este inclus în instalare +- Sublime Text 3: în Package Control găsiți și instalați pachetul `Nette` și alegeți Latte în `View > Syntax` +- în editoarele vechi, folosiți evidențierea Smarty pentru fișierele .latte -Plugin-ul pentru PhpStorm este foarte avansat și poate sugera perfect codul PHP. Pentru a funcționa în mod optim, utilizați [șabloane tipizate |type-system]. +Pluginul pentru PhpStorm este foarte avansat și poate sugera excelent cod PHP. Pentru a funcționa optim, utilizați [șabloane tipizate|type-system]. [* latte-phpstorm-plugin.webp *] -Suportul pentru Latte poate fi găsit și în corectorul de cod web [Prism.js |https://prismjs.com/#supported-languages] și în editorul [Ace |https://ace.c9.io]. +Suportul pentru Latte îl găsiți și în evidențiatorul de cod web [Prism.js|https://prismjs.com/#supported-languages] și editorul [Ace|https://ace.c9.io]. -Latte în JavaScript sau CSS .[#toc-latte-inside-javascript-or-css] -================================================================== +Latte în interiorul JavaScript sau CSS +====================================== -Latte poate fi utilizat foarte confortabil în JavaScript sau CSS. Dar cum să evitați ca Latte să considere din greșeală codul JavaScript sau stilul CSS ca fiind un tag Latte? +Latte poate fi folosit foarte convenabil și în interiorul JavaScript sau CSS. Dar cum să evităm situația în care Latte ar considera eronat codul JavaScript sau stilul CSS drept un tag Latte? ```latte ``` -**Opțiunea 1** +**Varianta 1** -Evitați situațiile în care o literă urmează imediat după un `{`, fie prin inserarea unui spațiu, a unei pauze de rând sau a unor ghilimele între ele: +Evitați situația în care o literă urmează imediat după `{`, de exemplu, inserând un spațiu, o linie nouă sau ghilimele înainte de aceasta: ```latte -

                                                                                                        +

                                                                                                        ``` -Două moduri și două tipuri diferite de scăpare a datelor. În cadrul elementului ` ``` -Cu toate acestea, dacă dorim să îl inserăm într-un atribut HTML, trebuie totuși să scăpăm ghilimelele în entități HTML: +Dacă am dori însă să îl inserăm într-un atribut HTML, trebuie să mai escapăm ghilimelele în entități HTML: ```html
                                                                                                        ``` -Cu toate acestea, contextul imbricat nu trebuie să fie doar JS sau CSS. Este, de asemenea, în mod obișnuit un URL. Parametrii din URL-uri sunt evadați prin convertirea caracterelor speciale în secvențe care încep cu `%`. Exemplu: +Dar contextul imbricat nu trebuie să fie doar JS sau CSS. Adesea, acesta este și URL-ul. Parametrii din URL se escapează prin convertirea caracterelor cu semnificație specială în secvențe care încep cu `%`. Exemplu: ``` https://example.org/?a=Jazz&b=Rock%27n%27Roll ``` -Și când ieșim acest șir de caractere într-un atribut, aplicăm în continuare scăparea în funcție de acest context și înlocuim `&` with `&`: +Și când afișăm acest șir într-un atribut, aplicăm și escaparea conform acestui context și înlocuim `&` cu `&`: ```html ``` -Dacă ați citit până aici, felicitări, a fost obositor. Acum aveți o idee bună despre ceea ce sunt contextele și scăpările. Și nu trebuie să vă faceți griji că este complicat. Latte face asta pentru tine în mod automat. +Dacă ați citit până aici, felicitări, a fost epuizant. Acum aveți o idee bună despre ce sunt contextele și escaparea. Și nu trebuie să vă faceți griji că este complicat. Latte face acest lucru automat pentru dvs. -Latte vs. sistemele naive .[#toc-latte-vs-naive-systems] -======================================================== +Latte vs sisteme naive +====================== -Am arătat cum să evadăm în mod corespunzător într-un document HTML și cât de crucial este să cunoaștem contextul, adică locul în care se produc datele. Cu alte cuvinte, cum funcționează escaparea sensibilă la context. -Deși aceasta este o condiție prealabilă pentru o apărare funcțională împotriva XSS, **Latte este singurul sistem de șabloane pentru PHP care face acest lucru.** +Am arătat cum se escapează corect într-un document HTML și cât de esențială este cunoașterea contextului, adică a locului unde afișăm datele. Cu alte cuvinte, cum funcționează escaparea contextuală sensibilă. Deși este o condiție necesară pentru o apărare funcțională împotriva XSS, **Latte este singurul sistem de șabloane pentru PHP care știe să facă acest lucru.** -Cum este posibil acest lucru când toate sistemele actuale pretind că au escaping automat? -Evadarea automată fără a cunoaște contextul este un rahat care **creează un fals sentiment de securitate**. +Cum este posibil, când toate sistemele pretind astăzi că au escapare automată? Escaparea automată fără cunoașterea contextului este un pic de bullshit, care **creează o falsă impresie de securitate**. -Sistemele de șabloane precum Twig, Laravel Blade și altele nu văd nicio structură HTML în șablon. Prin urmare, nu văd nici contextele. În comparație cu Latte, ele sunt oarbe și naive. Ei se ocupă doar de propria marcare, orice altceva este un flux de caractere irelevant pentru ei: +Sistemele de șabloane, cum ar fi Twig, Laravel Blade și altele, nu văd nicio structură HTML în șablon. Prin urmare, nu văd nici contextele. Spre deosebire de Latte, sunt oarbe și naive. Procesează doar propriile tag-uri, tot restul este pentru ele un flux nesemnificativ de caractere:
                                                                                                        -```twig .{file:Twig template as seen by Twig himself} -░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░ -░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░{{ text }}░░░░ +```twig .{file:Șablon Twig, așa cum îl vede Twig însuși} +░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░ +░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░ ``` -```twig .{file:Twig template as the designer sees it} -- in text: {{ text }} -- in tag: -- in attribute: -- in unquoted attribute: -- in attribute containing URL: -- in attribute containing JavaScript: -- in attribute containing CSS: -- in JavaScriptu: -- in CSS: -- in comment: +```twig .{file:Șablon Twig, așa cum îl vede designerul} +- în text: {{ foo }} +- în tag: +- în atribut: +- în atribut fără ghilimele: +- în atribut conținând URL: +- în atribut conținând JavaScript: +- în atribut conținând CSS: +- în JavaScript: +- în CSS: +- în comentariu: ```
                                                                                                        -Sistemele naive doar convertesc mecanic caracterele `< > & ' "` în entități HTML, ceea ce reprezintă o modalitate validă de scăpare în majoritatea utilizărilor, dar nu întotdeauna. Astfel, ele nu pot detecta sau preveni diverse breșe de securitate, după cum vom arăta mai jos. +Sistemele naive doar convertesc mecanic caracterele `< > & ' "` în entități HTML, ceea ce, deși este o metodă validă de escapare în majoritatea cazurilor de utilizare, nu este nici pe departe întotdeauna așa. Nu pot astfel detecta sau preveni apariția diverselor găuri de securitate, așa cum vom arăta în continuare. -Latte vede șablonul în același mod în care îl vedeți dumneavoastră. Înțelege HTML, XML, recunoaște etichetele, atributele etc. Și, datorită acestui fapt, distinge între contexte și tratează datele în consecință. Astfel, oferă o protecție cu adevărat eficientă împotriva vulnerabilității critice Cross-site Scripting. +Latte vede șablonul la fel ca dvs. Înțelege HTML, XML, recunoaște tag-uri, atribute etc. Și datorită acestui fapt, distinge contextele individuale și tratează datele conform acestora. Oferă astfel o protecție cu adevărat eficientă împotriva vulnerabilității critice Cross-site Scripting. + +
                                                                                                        + +```latte .{file:Șablon Latte, așa cum îl vede Latte} +░░░░░░░░░░░{$foo} +░░░░░░░░░░ +░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░ +░░░░░░░░░ +░░░░░░░░░░░░░░░ +``` + +```latte .{file:Șablon Latte, așa cum îl vede designerul} +- în text: {$foo} +- în tag: +- în atribut: +- în atribut fără ghilimele: +- în atribut conținând URL: +- în atribut conținând JavaScript: +- în atribut conținând CSS: +- în JavaScript: +- în CSS: +- în comentariu: +``` + +
                                                                                                        -Demonstrație în direct .[#toc-live-demonstration] -================================================= +Demonstrație live +================= -În stânga puteți vedea șablonul în Latte, iar în dreapta este codul HTML generat. Variabila `$text` este afișată de mai multe ori, de fiecare dată într-un context ușor diferit. Și, prin urmare, scăpată un pic diferit. Puteți edita singur codul șablonului, de exemplu să modificați conținutul variabilei etc. Încercați: +În stânga vedeți șablonul în Latte, în dreapta este codul HTML generat. Variabila `$text` este afișată de mai multe ori aici, și de fiecare dată într-un context ușor diferit. Și, prin urmare, și escapată ușor diferit. Puteți edita singur codul șablonului, de exemplu, schimba conținutul variabilei etc. Încercați:
                                                                                                        ``` .{file:template.latte; min-height: 14em}[fiddle-source] -{* TRY TO EDIT THIS TEMPLATE *} +{* ÎNCERCAȚI SĂ EDITAȚI ACEST ȘABLON *} {var $text = "Rock'n'Roll"} - {$text} - @@ -270,51 +286,49 @@ Demonstrație în direct .[#toc-live-demonstration]
                                                                                                        -Nu-i așa că e grozav! Latte face escape-uri sensibile la context în mod automat, astfel încât programatorul: +Nu este grozav! Latte face escaparea contextuală sensibilă automat, astfel încât programatorul: -- nu trebuie să se gândească sau să știe cum să evadeze datele +- nu trebuie să se gândească sau să știe cum se escapează unde - nu poate greși -- nu poate uita de ele +- nu poate uita de escapare -Acestea nu sunt nici măcar toate contextele pe care Latte le distinge la ieșire și pentru care personalizează tratamentul datelor. Vom trece acum în revistă mai multe cazuri interesante. +Acestea nu sunt nici măcar toate contextele pe care Latte le distinge la afișare și pentru care adaptează tratarea datelor. Vom parcurge acum alte cazuri interesante. -Cum să spargem sistemele naive .[#toc-how-to-hack-naive-systems] -================================================================ +Cum să hackuiți sistemele naive +=============================== -Vom folosi câteva exemple practice pentru a arăta cât de importantă este diferențierea contextului și de ce sistemele de modelare naive nu oferă o protecție suficientă împotriva XSS, spre deosebire de Latte. -În exemple vom folosi Twig ca reprezentant al unui sistem naiv, dar același lucru este valabil și pentru alte sisteme. +Pe câteva exemple practice, vom arăta cât de importantă este distingerea contextelor și de ce sistemele de șabloane naive nu oferă o protecție suficientă împotriva XSS, spre deosebire de Latte. Ca reprezentant al unui sistem naiv, vom folosi Twig în exemple, dar același lucru este valabil și pentru alte sisteme. -Vulnerabilitatea atributelor .[#toc-attribute-vulnerability] ------------------------------------------------------------- +Vulnerabilitatea prin atribut +----------------------------- -Să încercăm să injectăm cod malițios în pagină folosind atributul HTML, așa cum [am arătat mai sus |#How does the vulnerability arise]. Să avem un șablon în Twig care afișează o imagine: +Vom încerca să injectăm cod malițios în pagină folosind un atribut HTML, așa cum am [arătat mai sus |#Cum apare vulnerabilitatea]. Să avem un șablon în Twig care afișează o imagine: ```twig .{file:Twig} {{ ``` -Observați că nu există ghilimele în jurul valorilor atributului. Este posibil ca programatorul să le fi uitat, ceea ce se întâmplă pur și simplu. De exemplu, în React, codul este scris astfel, fără ghilimele, iar un programator care schimbă limbajul poate uita cu ușurință ghilimelele. +Observați că în jurul valorilor atributelor nu există ghilimele. Coderul le-ar fi putut uita, ceea ce se întâmplă pur și simplu. De exemplu, în React, codul se scrie astfel, fără ghilimele, iar un coder care alternează limbajele poate uita ușor de ghilimele. -Atacatorul inserează un șir de caractere inteligent construit, `foo onload=alert('Hacked!')`, drept legendă a imaginii. Știm deja că Twig nu poate spune dacă o variabilă este imprimată într-un flux de text HTML, în interiorul unui atribut, într-un comentariu HTML etc.; pe scurt, nu face distincție între contexte. Și doar convertește mecanic caracterele `< > & ' "` în entități HTML. -Deci, codul rezultat va arăta astfel: +Atacatorul introduce ca descriere a imaginii un șir inteligent construit `foo onload=alert('Hacked!')`. Știm deja că Twig nu poate recunoaște dacă variabila se afișează în fluxul textului HTML, în interiorul unui atribut, comentariu HTML etc., pe scurt, nu distinge contextele. Și doar convertesc mecanic caracterele `< > & ' "` în entități HTML. Deci, codul rezultat va arăta astfel: ```html foo ``` -**A fost creată o gaură de securitate!** +**Și a apărut o gaură de securitate!** -Un atribut fals `onload` a devenit parte a paginii, iar browserul îl execută imediat după descărcarea imaginii. +Un atribut `onload` falsificat a devenit parte a paginii, iar browserul îl execută imediat după descărcarea imaginii. -Acum să vedem cum gestionează Latte același șablon: +Acum să vedem cum se descurcă Latte cu același șablon: ```latte .{file:Latte} {$imageAlt} ``` -Latte vede șablonul în același mod ca și dumneavoastră. Spre deosebire de Twig, acesta înțelege HTML și știe că o variabilă este tipărită ca o valoare de atribut care nu este între ghilimele. De aceea le adaugă. Când un atacator inserează aceeași legendă, codul rezultat va arăta astfel: +Latte vede șablonul la fel ca dvs. Spre deosebire de Twig, înțelege HTML și știe că variabila se afișează ca valoare a unui atribut care nu este între ghilimele. De aceea, le completează. Când atacatorul introduce aceeași descriere, codul rezultat va arăta astfel: ```html foo onload=alert('Hacked!') @@ -323,10 +337,10 @@ Latte vede șablonul în același mod ca și dumneavoastră. Spre deosebire de T **Latte a prevenit cu succes XSS.** -Imprimarea unei variabile în JavaScript .[#toc-printing-a-variable-in-javascript] ---------------------------------------------------------------------------------- +Afișarea variabilei în JavaScript +--------------------------------- -Datorită scăpării sensibile la context, este posibilă utilizarea variabilelor PHP în mod nativ în JavaScript. +Datorită escapării contextuale sensibile, este posibil să utilizați variabile PHP în mod complet nativ în interiorul JavaScriptului. ```latte

                                                                                                        {$movie}

                                                                                                        @@ -334,7 +348,7 @@ Datorită scăpării sensibile la context, este posibilă utilizarea variabilelo ``` -Dacă variabila `$movie` stochează șirul `'Amarcord & 8 1/2'`, se generează următoarea ieșire. Observați că sunt utilizate diferite scăpări în HTML și JavaScript și, de asemenea, în atributul `onclick`: +Dacă variabila `$movie` conține șirul `'Amarcord & 8 1/2'`, se va genera următoarea ieșire. Observați că în interiorul HTML se folosește o escapare diferită decât în interiorul JavaScriptului și încă alta în atributul `onclick`: ```latte

                                                                                                        Amarcord & 8 1/2

                                                                                                        @@ -343,29 +357,27 @@ Dacă variabila `$movie` stochează șirul `'Amarcord & 8 1/2'`, se generează u ``` -Verificarea legăturii .[#toc-link-checking] -------------------------------------------- +Verificarea linkurilor +---------------------- -Latte verifică automat dacă variabila utilizată în atributele `src` sau `href` conține un URL web (adică protocolul HTTP) și previne scrierea de linkuri care pot reprezenta un risc de securitate. +Latte verifică automat dacă variabila utilizată în atributele `src` sau `href` conține o adresă URL web (adică protocol HTTP) și previne afișarea linkurilor care pot reprezenta un risc de securitate. ```latte {var $link = 'javascript:attack()'} -click here +clic ``` -Scrie: +Afișează: ```latte -click here +clic ``` -Verificarea poate fi dezactivată cu ajutorul unui filtru [nocheck |filters#nocheck]. +Verificarea poate fi dezactivată folosind filtrul [nocheck |filters#nocheck]. -Limitele lui Latte .[#toc-limits-of-latte] -========================================== +Limitele Latte +============== -Latte nu este o protecție XSS completă pentru întreaga aplicație. Am fi nefericiți dacă v-ați opri să vă gândiți la securitate atunci când utilizați Latte. -Scopul Latte este de a se asigura că un atacator nu poate modifica structura unei pagini, nu poate manipula elementele sau atributele HTML. Dar nu verifică corectitudinea conținutului datelor care sunt emise. Sau corectitudinea comportamentului JavaScript. -Acest lucru depășește sfera de acțiune a sistemului de modelare. Verificarea corectitudinii datelor, în special a celor introduse de utilizator și, prin urmare, nesigure, este o sarcină importantă pentru programator. +Latte nu este o protecție completă împotriva XSS pentru întreaga aplicație. Nu am dori să încetați să vă gândiți la securitate atunci când utilizați Latte. Scopul Latte este de a asigura că un atacator nu poate modifica structura paginii, falsifica elemente sau atribute HTML. Dar nu controlează corectitudinea conținutului datelor afișate. Sau corectitudinea comportamentului JavaScriptului. Acest lucru depășește competențele sistemului de șabloane. Verificarea corectitudinii datelor, în special a celor introduse de utilizator și, prin urmare, nesigure, este o sarcină importantă a programatorului. diff --git a/latte/ro/sandbox.texy b/latte/ro/sandbox.texy index 5f3c75dbf1..f96fc64d2c 100644 --- a/latte/ro/sandbox.texy +++ b/latte/ro/sandbox.texy @@ -1,12 +1,10 @@ Sandbox ******* -.[perex]{data-version:2.8} -Sandbox oferă un nivel de securitate care vă permite să controlați ce etichete, funcții PHP, metode etc. pot fi utilizate în șabloane. Datorită modului sandbox, puteți colabora în siguranță cu un client sau cu un programator extern la crearea de șabloane, fără a vă face griji cu privire la compromiterea aplicației sau la operațiuni nedorite. +.[perex] +Sandbox oferă un strat de securitate care vă oferă control asupra tag-urilor, funcțiilor PHP, metodelor etc. care pot fi utilizate în șabloane. Datorită modului sandbox, puteți colabora în siguranță cu clientul sau cu un coder extern la crearea șabloanelor, fără a vă teme că aplicația va fi compromisă sau că vor avea loc operațiuni nedorite. -Cum funcționează? Pur și simplu definim ceea ce dorim să permitem în șablon. La început, totul este interzis, iar noi acordăm treptat permisiuni: - -Următorul cod permite șablonului să utilizeze etichetele `{block}`, `{if}`, `{else}` și `{=}` (aceasta din urmă este o etichetă pentru [imprimarea unei variabile sau expresii |tags#Printing]) și toate filtrele: +Cum funcționează? Pur și simplu definim ce îi permitem șablonului. În mod implicit, totul este interzis și noi permitem treptat. Următorul cod permite autorului șablonului să utilizeze tag-urile `{block}`, `{if}`, `{else}` și `{=}`, care este tag-ul pentru [afișarea unei variabile sau expresii |tags#Afișare] și toate filtrele: ```php $policy = new Latte\Sandbox\SecurityPolicy; @@ -16,7 +14,7 @@ $policy->allowFilters($policy::All); $latte->setPolicy($policy); ``` -De asemenea, putem permite accesul la funcții, metode sau proprietăți globale ale obiectelor: +În continuare, putem permite funcții, metode sau proprietăți individuale ale obiectelor: ```php $policy->allowFunctions(['trim', 'strlen']); @@ -24,30 +22,35 @@ $policy->allowMethods(Nette\Security\User::class, ['isLoggedIn', 'isAllowed']); $policy->allowProperties(Nette\Database\Row::class, $policy::All); ``` -Nu-i așa că este uimitor? Puteți controla totul la un nivel foarte scăzut. Dacă șablonul încearcă să apeleze o funcție neautorizată sau să acceseze o metodă sau o proprietate neautorizată, se aruncă excepția `Latte\SecurityViolationException`. +Nu este uimitor? Puteți controla absolut totul la un nivel foarte scăzut. Dacă șablonul încearcă să apeleze o funcție nepermisă sau să acceseze o metodă sau proprietate nepermisă, se va termina cu o excepție `Latte\SecurityViolationException`. -Crearea politicilor de la zero, când totul este interzis, poate să nu fie convenabilă, așa că puteți începe de la o bază sigură: +Crearea unei politici de la zero, când totul este interzis, poate să nu fie convenabilă, așa că puteți începe de la o bază sigură: ```php $policy = Latte\Sandbox\SecurityPolicy::createSafePolicy(); ``` -Aceasta înseamnă că toate etichetele standard sunt permise, cu excepția `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget`. -De asemenea, sunt permise toate filtrele standard, cu excepția `datastream`, `noescape` și `nocheck`. În cele din urmă, este permis și accesul la metodele și proprietățile obiectului `$iterator`. +Baza sigură înseamnă că sunt permise toate tag-urile standard, cu excepția `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget`. Sunt permise filtrele standard, cu excepția `datastream`, `noescape` și `nocheck`. Și, în final, este permis accesul la metodele și proprietățile obiectului `$iterator`. -Regulile se aplică șablonului pe care îl inserăm cu noul [`{sandbox}` |tags#Including Templates] tag. Care este ceva de genul `{include}`, dar activează modul sandbox și, de asemenea, nu trece nicio variabilă externă: +Regulile se aplică pentru șablonul pe care îl inserăm cu tag-ul [`{sandbox}` |tags#Includerea șablonului]. Care este un fel de echivalent al `{include}`, dar care activează modul sigur și, de asemenea, nu transmite nicio variabilă: ```latte {sandbox 'untrusted.latte'} ``` -Astfel, macheta și paginile individuale pot folosi toate etichetele și variabilele ca și până acum, restricțiile vor fi aplicate doar șablonului `untrusted.latte`. +Astfel, layout-ul și paginile individuale pot utiliza nestingherit toate tag-urile și variabilele, doar șablonului `untrusted.latte` i se vor aplica restricții. -Unele încălcări, cum ar fi utilizarea unei etichete sau a unui filtru interzis, sunt detectate în momentul compilării. Altele, cum ar fi apelarea unor metode nepermise ale unui obiect, la momentul execuției. -Șablonul poate conține, de asemenea, orice alte erori. Pentru a preveni lansarea unei excepții din șablonul sandboxed, care perturbă întreaga redare, puteți defini [propriul gestionar de excepții |develop#exception handler], care, de exemplu, doar o înregistrează. +Unele încălcări, cum ar fi utilizarea unui tag sau filtru interzis, sunt detectate în timpul compilării. Altele, cum ar fi apelarea metodelor nepermise ale unui obiect, abia în timpul rulării. Șablonul poate conține, de asemenea, orice alte erori. Pentru ca o excepție din șablonul sandboxat să nu poată ieși și să perturbe întreaga randare, puteți defini propriul [handler de excepții |develop#Gestionar de excepții], care, de exemplu, o va loga. -Dacă dorim să activăm modul sandbox direct pentru toate șabloanele, este simplu: +Dacă am dori să activăm modul sandbox direct pentru toate șabloanele, este ușor: ```php $latte->setSandboxMode(); ``` + +Pentru a vă asigura că utilizatorul nu inserează în pagină cod PHP care este sintactic corect, dar interzis și provoacă o eroare de compilare PHP, vă recomandăm să lăsați [șabloanele verificate de linterul PHP |develop#Verificarea codului generat]. Activați această funcționalitate prin metoda `Engine::enablePhpLint()`. Deoarece pentru verificare trebuie să apeleze binarul PHP, transmiteți calea către acesta ca parametru: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); +``` diff --git a/latte/ro/syntax.texy b/latte/ro/syntax.texy index 5ddb34737d..efa6d7b7e9 100644 --- a/latte/ro/syntax.texy +++ b/latte/ro/syntax.texy @@ -1,63 +1,61 @@ -Sintaxa +Sintaxă ******* .[perex] -Syntax Latte s-a născut din cerințele practice ale designerilor web. Căutam o sintaxă cât mai ușor de utilizat, cu ajutorul căreia să puteți scrie elegant construcții care altfel reprezintă o adevărată provocare. -În același timp, toate expresiile sunt scrise exact la fel ca în PHP, astfel încât nu trebuie să învățați un nou limbaj. Pur și simplu profitați la maximum de ceea ce știți deja. +Sintaxa Latte a apărut din cerințele practice ale webdesignerilor. Am căutat cea mai prietenoasă sintaxă, cu care puteți scrie elegant chiar și construcții care altfel reprezintă o adevărată provocare. În același timp, toate expresiile se scriu exact la fel ca în PHP, așa că nu trebuie să învățați un nou limbaj. Pur și simplu valorificați ceea ce știți deja. -Mai jos este prezentat un șablon minimal care ilustrează câteva elemente de bază: tag-uri, n:attributes, comentarii și filtre. +Mai jos este un șablon minim care ilustrează câteva elemente de bază: tag-uri, n:atribute, comentarii și filtre. ```latte {* acesta este un comentariu *} -
                                                                                                          {* n:if este n:atribut *} +
                                                                                                            {* n:if este un n:atribut *} {foreach $items as $item} {* tag reprezentând bucla foreach *} -
                                                                                                          • {$item|capitalize}
                                                                                                          • {* tag care tipărește o variabilă cu un filtru *} -{/foreach} {* sfârșit de ciclu *} +
                                                                                                          • {$item|capitalize}
                                                                                                          • {* tag afișând variabila cu filtru *} +{/foreach} {* sfârșitul buclei *}
                                                                                                          ``` -Să analizăm mai îndeaproape aceste elemente importante și modul în care acestea vă pot ajuta să construiți un șablon incredibil. +Să aruncăm o privire mai atentă la aceste elemente importante și la modul în care vă pot ajuta să creați un șablon uimitor. -Etichete .[#toc-tags] -===================== +Tag-uri +======= -Un șablon conține etichete care controlează logica șablonului (de exemplu, buclele *foreach*) sau expresiile de ieșire. Pentru ambele, se utilizează un singur delimitator `{ ... }`, astfel încât nu trebuie să vă gândiți ce delimitator să folosiți în fiecare situație, ca în cazul altor sisteme. -Dacă caracterul `{` este urmat de un ghilimele sau de un spațiu, Latte nu îl consideră ca fiind începutul unei etichete, astfel încât puteți folosi construcții JavaScript, JSON sau reguli CSS în șabloanele dvs. fără probleme. +Șablonul conține tag-uri care controlează logica șablonului (de exemplu, bucle *foreach*) sau afișează expresii. Pentru ambele se folosește un singur delimitator `{ ... }`, astfel încât nu trebuie să vă gândiți ce delimitator să folosiți în ce situație, așa cum se întâmplă în alte sisteme. Dacă după caracterul `{` urmează ghilimele sau spațiu, Latte nu îl consideră începutul unui tag, datorită căruia puteți folosi fără probleme în șabloane și construcții JavaScript, JSON sau reguli CSS. -Vedeți [prezentarea generală a tuturor etichetelor |tags]. În plus, puteți crea, de asemenea, [tag-uri personalizate |extending-latte#tags]. +Consultați [prezentarea generală a tuturor tag-urilor|tags]. În plus, puteți crea și [propriile tag-uri|custom tags]. -Latte înțelege PHP .[#toc-latte-understands-php] -================================================ +Latte înțelege PHP +================== -Puteți utiliza expresii PHP pe care le cunoașteți bine în interiorul etichetelor: +În interiorul tag-urilor puteți folosi expresii PHP pe care le cunoașteți bine: - variabile -- șiruri de caractere (inclusiv HEREDOC și NOWDOC), matrice, numere etc. +- șiruri (inclusiv HEREDOC și NOWDOC), array-uri, numere etc. - [operatori |https://www.php.net/manual/en/language.operators.php] -- apeluri de funcții și metode (care pot fi restricționate de [sandbox |sandbox]) -- [potrivire |https://www.php.net/manual/en/control-structures.match.php] +- apeluri de funcții și metode (care pot fi restricționate prin [sandbox|sandbox]) +- [match |https://www.php.net/manual/en/control-structures.match.php] - [funcții anonime |https://www.php.net/manual/en/functions.arrow.php] - [callback-uri |https://www.php.net/manual/en/functions.first_class_callable_syntax.php] -- comentarii pe mai multe rânduri `/* ... */` -- etc... +- comentarii pe mai multe linii `/* ... */` +- etc… -În plus, Latte adaugă câteva [extensii frumoase |#Syntactic Sugar] la sintaxa PHP. +În plus, Latte completează sintaxa PHP cu câteva [extensii plăcute |#Zahăr sintactic]. -n:atribute .[#toc-n-attributes] -=============================== +n:atribute +========== -Fiecare etichetă pereche, cum ar fi `{if} … {/if}`, care acționează asupra unui singur element HTML poate fi scrisă în notația [n:attribute |#n:attribute]. De exemplu, `{foreach}` din exemplul de mai sus poate fi scris și în acest fel: +Toate tag-urile pereche, de exemplu `{if} … {/if}`, care operează asupra unui singur element HTML, pot fi rescrise sub formă de n:atribute. Astfel ar putea fi scris, de exemplu, și `{foreach}` din exemplul introductiv: ```latte -
                                                                                                            +
                                                                                                            • {$item|capitalize}
                                                                                                            ``` -Funcționalitatea corespunde apoi elementului HTML în care este scrisă: +Funcționalitatea se aplică apoi elementului HTML în care este plasat: ```latte {var $items = ['I', '♥', 'Latte']} @@ -65,7 +63,7 @@ Funcționalitatea corespunde apoi elementului HTML în care este scrisă:

                                                                                                            {$item}

                                                                                                            ``` -Imprimă: +afișează: ```latte

                                                                                                            I

                                                                                                            @@ -73,7 +71,7 @@ Imprimă:

                                                                                                            Latte

                                                                                                            ``` -Prin utilizarea prefixului `inner-` putem modifica comportamentul astfel încât funcționalitatea să se aplice doar corpului elementului: +Folosind prefixul `inner-`, putem modifica comportamentul astfel încât să se aplice doar părții interioare a elementului: ```latte
                                                                                                            @@ -82,7 +80,7 @@ Prin utilizarea prefixului `inner-` putem modifica comportamentul astfel încât
                                                                                                            ``` -Imprimă: +Se va afișa: ```latte
                                                                                                            @@ -95,178 +93,184 @@ Imprimă:
                                                                                                            ``` -Sau prin utilizarea prefixului `tag-`, funcționalitatea se aplică numai la etichetele HTML: +Sau folosind prefixul `tag-`, aplicăm funcționalitatea doar asupra tag-urilor HTML în sine: ```latte -

                                                                                                            Title

                                                                                                            +

                                                                                                            Titlu

                                                                                                            ``` -În funcție de valoarea variabilei `$url`, se va imprima: +Ceea ce va afișa în funcție de variabila `$url`: ```latte -// when $url is empty -

                                                                                                            Title

                                                                                                            +{* când $url este gol *} +

                                                                                                            Titlu

                                                                                                            -// when $url equals 'https://nette.org' -

                                                                                                            Title

                                                                                                            +{* când $url conține 'https://nette.org' *} +

                                                                                                            Titlu

                                                                                                            ``` -Cu toate acestea, n:attributes nu sunt doar o prescurtare pentru etichetele pereche, ci există și unele atribute n:pure, de exemplu, cel mai bun prieten al programatorului, [n:class |tags#n:class]. +Cu toate acestea, n:atributele nu sunt doar o prescurtare pentru tag-urile pereche. Există și n:atribute pure, cum ar fi [n:href |application:creating-links#În șablonul presenterului] sau ajutorul extrem de util pentru coderi [n:class |tags#n:class]. -Filtre .[#toc-filters] -====================== +Filtre +====== -A se vedea rezumatul [filtrelor standard |filters]. +Consultați prezentarea generală a [filtrelor standard |filters]. -Latte permite apelarea filtrelor utilizând notația semnului pipe (este permisă folosirea unui spațiu înainte): +Filtrele se scriu după bara verticală (poate fi precedată de un spațiu): ```latte

                                                                                                            {$heading|upper}

                                                                                                            ``` -Filtrele pot fi înlănțuite, caz în care se aplică în ordine de la stânga la dreapta: +Filtrele pot fi înlănțuite și apoi se aplică în ordine de la stânga la dreapta: ```latte

                                                                                                            {$heading|lower|capitalize}

                                                                                                            ``` -Parametrii se pun după numele filtrului, separați prin două puncte sau virgulă: +Parametrii se specifică după numele filtrului, separați prin două puncte sau virgule: ```latte

                                                                                                            {$heading|truncate:20,''}

                                                                                                            ``` -Filtrele pot fi aplicate pe expresie: +Filtrele pot fi aplicate și pe o expresie: ```latte {var $name = ($title|upper) . ($subtitle|lower)} ``` -Pe bloc: +Pe un bloc: ```latte

                                                                                                            {block |lower}{$heading}{/block}

                                                                                                            ``` -Sau direct pe valoare (în combinație cu [`{=expr}` | https://latte.nette.org/ro/tags#printing] tag): +Sau direct pe valoare (în combinație cu tag-ul [`{=expr}` |tags#Afișare]): +```latte +

                                                                                                            {=' Salut lume '|trim}

                                                                                                            +``` + + +Tag-uri HTML dinamice .{data-version:3.0.9} +=========================================== + +Latte suportă tag-uri HTML dinamice, care sunt utile când aveți nevoie de flexibilitate în numele tag-urilor: + ```latte -

                                                                                                            {=' Hello world '|trim}

                                                                                                            +Titlu ``` +Codul de mai sus poate genera, de exemplu, `

                                                                                                            Titlu

                                                                                                            ` sau `

                                                                                                            Titlu

                                                                                                            ` în funcție de valoarea variabilei `$level`. Tag-urile HTML dinamice în Latte trebuie să fie întotdeauna pereche. Alternativa lor este [n:tag |tags#n:tag]. + +Deoarece Latte este un sistem de șabloane sigur, verifică dacă numele tag-ului rezultat este valid și nu conține valori nedorite sau dăunătoare. De asemenea, asigură că numele tag-ului de închidere va fi întotdeauna același cu numele tag-ului de deschidere. + -Comentarii .[#toc-comments] -=========================== +Comentarii +========== -Comentariile sunt scrise în acest mod și nu se introduc în text: +Comentariile se scriu în acest mod și nu ajung în ieșire: ```latte -{* acest lucru este un comentariu în Latte *}} +{* acesta este un comentariu în Latte *} ``` -Comentariile PHP funcționează în interiorul etichetelor: +În interiorul tag-urilor funcționează comentariile PHP: ```latte {include 'file.info', /* value: 123 */} ``` -Zahăr sintactic .[#toc-syntactic-sugar] -======================================= +Zahăr sintactic +=============== -Șiruri de caractere fără ghilimele .[#toc-strings-without-quotation-marks] --------------------------------------------------------------------------- +Șiruri fără ghilimele +--------------------- -Ghilimelele pot fi omise în cazul șirurilor de caractere simple: +Pentru șirurile simple, se pot omite ghilimelele: ```latte -as in PHP: {var $arr = ['hello', 'btn--default', '€']} +ca în PHP: {var $arr = ['hello', 'btn--default', '€']} -abbreviated: {var $arr = [hello, btn--default, €]} +prescurtat: {var $arr = [hello, btn--default, €]} ``` -Șirurile de caractere simple sunt cele care sunt alcătuite exclusiv din litere, cifre, liniuțe, sublinieri, cratime și puncte. Ele nu trebuie să înceapă cu o cifră și nu trebuie să înceapă sau să se termine cu o cratimă. -Nu trebuie să fie compuse numai din litere majuscule și liniuțe de subliniere, pentru că atunci este considerată o constantă (de exemplu, `PHP_VERSION`). -Și nu trebuie să intre în coliziune cu cuvintele-cheie `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor`. +Șirurile simple sunt cele formate exclusiv din litere, cifre, underscore-uri, cratime și puncte. Nu trebuie să înceapă cu o cifră și nu trebuie să înceapă sau să se termine cu o cratimă. Nu trebuie să fie compuse doar din litere mari și underscore-uri, deoarece atunci sunt considerate constante (de ex. `PHP_VERSION`). Și nu trebuie să intre în conflict cu cuvintele cheie: `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor`. -Operator ternar scurt .[#toc-short-ternary-operator] ----------------------------------------------------- +Constante +--------- -În cazul în care a treia valoare a operatorului ternar este goală, aceasta poate fi omisă: +Deoarece la șirurile simple se pot omite ghilimelele, recomandăm pentru diferențiere să scrieți constantele globale cu un slash la început: ```latte -as in PHP: {$stock ? 'In stock' : ''} - -abbreviated: {$stock ? 'In stock'} +{if \PROJECT_ID === 1} ... {/if} ``` +Această scriere este complet validă în PHP însuși, slash-ul indică faptul că constanta este în namespace-ul global. + -Notarea modernă a cheilor în matrice .[#toc-modern-key-notation-in-the-array] ------------------------------------------------------------------------------ +Operator ternar prescurtat +-------------------------- -Cheile din array pot fi scrise în mod similar cu parametrii numiți la apelarea funcțiilor: +Dacă a treia valoare a operatorului ternar este goală, poate fi omisă: ```latte -as in PHP: {var $arr = ['one' => 'item 1', 'two' => 'item 2']} +ca în PHP: {$stock ? 'În stoc' : ''} -modern: {var $arr = [one: 'item 1', two: 'item 2']} +prescurtat: {$stock ? 'În stoc'} ``` -Filtre .[#toc-filters] ----------------------- +Scriere modernă a cheilor în array +---------------------------------- -Filtrele pot fi utilizate pentru orice expresie, trebuie doar să le includeți între paranteze: +Cheile în array pot fi scrise similar cu parametrii numiți la apelarea funcțiilor: ```latte -{var $content = ($text|truncate: 30|upper)} +ca în PHP: {var $arr = ['one' => 'item 1', 'two' => 'item 2']} + +modern: {var $arr = [one: 'item 1', two: 'item 2']} ``` -Operator `in` .[#toc-operator-in] ---------------------------------- +Filtre +------ -Operatorul `in` poate fi utilizat pentru a înlocui funcția `in_array()`. Comparația este întotdeauna strictă: +Filtrele pot fi utilizate pentru orice expresie, este suficient să închideți întregul în paranteze: ```latte -{* like in_array($item, $items, true) *} -{if $item in $items} - ... -{/if} +{var $content = ($text|truncate: 30|upper)} ``` -.{data-version:2.9} -Încatenarea opțională cu operatorul sigur împotriva definițiilor .[#toc-optional-chaining-with-undefined-safe-operator] ------------------------------------------------------------------------------------------------------------------------ +Operatorul `in` +--------------- -Operatorul undefined-safe `??->` este similar cu operatorul nullsafe `?->`, dar nu generează o eroare dacă o variabilă, o proprietate sau un index nu există deloc. +Operatorul `in` poate înlocui funcția `in_array()`. Compararea este întotdeauna strictă: ```latte -{$order??->id} +{* echivalent cu in_array($item, $items, true) *} +{if $item in $items} + ... +{/if} ``` -acesta este un mod de a spune că, atunci când `$order` este definit și nu este nul, `$order->id` va fi calculat, dar când `$order` este nul sau nu există, ne oprim din ceea ce facem și returnăm pur și simplu null. - -```latte -{$user??->address??->street} -// roughly means isset($user) && isset($user->address) ? $user->address->street : null -``` +Fereastră istorică +------------------ -O fereastră în istorie .[#toc-a-window-into-history] ----------------------------------------------------- +Latte a venit de-a lungul istoriei sale cu o serie întreagă de zahăr sintactic, care după câțiva ani au apărut în PHP însuși. De exemplu, în Latte era posibil să scrieți array-uri ca `[1, 2, 3]` în loc de `array(1, 2, 3)` sau să folosiți operatorul nullsafe `$obj?->foo` cu mult înainte ca acest lucru să fie posibil în PHP însuși. Latte a introdus, de asemenea, operatorul pentru despachetarea array-ului `(expand) $arr`, care este echivalentul operatorului actual `...$arr` din PHP. -Latte a venit cu o serie de bomboane sintactice de-a lungul istoriei sale, care au apărut în PHP însuși câțiva ani mai târziu. De exemplu, în Latte era posibil să se scrie array-uri ca `[1, 2, 3]` în loc de `array(1, 2, 3)` sau de a utiliza operatorul nullsafe `$obj?->foo` cu mult înainte ca acest lucru să fie posibil în PHP. Latte a introdus, de asemenea, operatorul de expansiune a tablourilor `(expand) $arr`, care este echivalentul operatorului `...$arr` de astăzi din PHP. +Operatorul undefined-safe `??->`, care este un echivalent al operatorului nullsafe `?->`, dar care nu generează eroare dacă variabila nu există, a apărut din motive istorice și astăzi recomandăm utilizarea operatorului PHP standard `?->`. -Limitări ale PHP în Latte .[#toc-php-limitations-in-latte] -========================================================== +Limitări PHP în Latte +===================== -În Latte pot fi scrise numai expresii PHP. Adică, nu puteți declara clase sau utiliza [structuri de control |https://www.php.net/manual/en/language.control-structures.php], cum ar fi `if`, `foreach`, `switch`, `return`, , `try`, `throw` și altele, în locul cărora Latte oferă [etichetele |tags] sale. -De asemenea, nu puteți folosi [atribute |https://www.php.net/manual/en/language.attributes.php], [backticks |https://www.php.net/manual/en/language.operators.execution.php] sau [constante magice |https://www.php.net/manual/en/language.constants.magic.php], deoarece nu ar avea sens. -Nu puteți folosi nici măcar `echo`, `include`, `require`, , `exit`, `eval`, `unset`, pentru că acestea nu sunt funcții, ci construcții speciale ale limbajului PHP, și deci nu sunt expresii. +În Latte se pot scrie doar expresii PHP. Adică nu se pot folosi instrucțiuni terminate cu punct și virgulă. Nu se pot declara clase sau folosi [structuri de control |https://www.php.net/manual/en/language.control-structures.php], de ex. `if`, `foreach`, `switch`, `return`, `try`, `throw` și altele, în locul cărora Latte oferă [tag-urile|tags] sale. De asemenea, nu se pot folosi [atribute |https://www.php.net/manual/en/language.attributes.php], [backticks |https://www.php.net/manual/en/language.operators.execution.php] sau unele [constante magice |https://www.php.net/manual/en/language.constants.magic.php]. Nu se pot folosi nici `unset`, `echo`, `include`, `require`, `exit`, `eval`, deoarece nu sunt funcții, ci construcții speciale ale limbajului PHP, și nu sunt deci expresii. Comentariile sunt suportate doar cele pe mai multe linii `/* ... */`. -Cu toate acestea, puteți ocoli aceste limitări prin activarea extensiei [RawPhpExtension |develop#RawPhpExtension], care vă permite să utilizați orice cod PHP în tag-ul `{php ...}` pe răspunderea autorului șablonului. +Aceste limitări pot fi totuși ocolite activând extensia [RawPhpExtension |develop#RawPhpExtension], datorită căreia se poate folosi apoi în tag-ul `{php ...}` orice cod PHP pe responsabilitatea autorului șablonului. diff --git a/latte/ro/tags.texy b/latte/ro/tags.texy index 55bf610084..00ab828253 100644 --- a/latte/ro/tags.texy +++ b/latte/ro/tags.texy @@ -1,168 +1,174 @@ -Latte Etichete -************** +Tag-uri Latte +************* .[perex] -Rezumat și descriere a tuturor etichetelor Latte încorporate. +Prezentare generală și descrierea tuturor tag-urilor (sau etichetelor ori macro-urilor) sistemului de șabloane Latte, care vă sunt disponibile în mod standard. .[table-latte-tags language-latte] -|## Imprimare -| `{$var}`, `{...}` sau `{=...}` | [tipărește o variabilă sau o expresie scăpată |#printing]. -| `{$var\|filter}` | [tipărește cu filtre |#filters] -| `{l}` sau `{r}` | tipărește caracterul `{` or `}` +|## Afișare +| `{$var}`, `{...}` sau `{=...}` | [afișează variabila sau expresia escapată |#Afișare] +| `{$var\|filter}` | [afișează folosind filtre |#Filtre] +| `{l}` sau `{r}` | afișează caracterul `{` sau `}` .[table-latte-tags language-latte] |## Condiții -| `{if}`... `{elseif}`... `{else}`... `{/if}` | [condiție dacă |#if-elseif-else] -| `{ifset}`... `{elseifset}`... `{/ifset}` | [condiție ifset |#ifset-elseifset] -| `{ifchanged}`... `{/ifchanged}` | [testează dacă a avut loc o schimbare |#ifchanged] -| `{switch}` `{case}` `{default}` `{/switch}` | [condition switch |#switch-case-default] +| `{if}` … `{elseif}` … `{else}` … `{/if}` | [condiția if |#if elseif else] +| `{ifset}` … `{elseifset}` … `{/ifset}` | [condiția ifset |#ifset elseifset] +| `{ifchanged}` … `{/ifchanged}` | [testează dacă a avut loc o schimbare |#ifchanged] +| `{switch}` `{case}` `{default}` `{/switch}` | [condiția switch |#switch case default] +| `n:else` | [conținut alternativ pentru condiții |#n:else] .[table-latte-tags language-latte] |## Bucle -| `{foreach}`... `{/foreach}` | [foreach |#foreach] -| `{for}`... `{/for}` | [for |#for] -| `{while}`... `{/while}` | [while |#while] -| `{continueIf $cond}` | [continuă la următoarea iterație |#continueif-skipif-breakif] -| `{skipIf $cond}` | [săriți peste iterația curentă a buclei |#continueif-skipif-breakif] -| `{breakIf $cond}` | [întrerupe bucla |#continueif-skipif-breakif] -| `{exitIf $cond}` | [ieșire anticipată |#exitif] -| `{first}`... `{/first}` | [este prima iterație? |#first-last-sep] -| `{last}`... `{/last}` | [este ultima iterație? |#first-last-sep] -| `{sep}`... `{/sep}` | [va urma următoarea iterație? |#first-last-sep] -| `{iterateWhile}`... `{/iterateWhile}` | [structurat foreach |#iterateWhile] -| `$iterator` | [variabilă specială în interiorul buclei foreach |#$iterator] +| `{foreach}` … `{/foreach}` | [#foreach] +| `{for}` … `{/for}` | [#for] +| `{while}` … `{/while}` | [#while] +| `{continueIf $cond}` | [continuă cu următoarea iterație |#continueIf skipIf breakIf] +| `{skipIf $cond}` | [sare peste iterație |#continueIf skipIf breakIf] +| `{breakIf $cond}` | [întreruperea buclei |#continueIf skipIf breakIf] +| `{exitIf $cond}` | [terminare timpurie |#exitIf] +| `{first}` … `{/first}` | [este prima trecere? |#first last sep] +| `{last}` … `{/last}` | [este ultima trecere? |#first last sep] +| `{sep}` … `{/sep}` | [va urma o altă trecere? |#first last sep] +| `{iterateWhile}` … `{/iterateWhile}` | [foreach structurat |#iterateWhile] +| `$iterator` | [variabilă specială în interiorul foreach |#iterator] .[table-latte-tags language-latte] |## Includerea altor șabloane -| `{include 'file.latte'}` | [include un șablon din alt fișier |#include] -| `{sandbox 'file.latte'}` | [include un șablon în modul sandbox |#sandbox] +| `{include 'file.latte'}` | [încarcă șablonul dintr-un alt fișier |#include] +| `{sandbox 'file.latte'}` | [încarcă șablonul în modul sandbox |#sandbox] .[table-latte-tags language-latte] -|## Blocuri, machete, moștenirea șablonului -| `{block}` | [bloc anonim |#block] -| `{block blockname}` | [definiție bloc |template-inheritance#blocks] -| `{define blockname}` | [definiție bloc pentru utilizare viitoare |template-inheritance#definitions] -| `{include blockname}` | [imprimă blocul |template-inheritance#printing-blocks] -| `{include blockname from 'file.latte'}` | [tipărește un bloc din fișier |template-inheritance#printing-blocks] -| `{import 'file.latte'}` | [încarcă blocuri dintr-un alt șablon |template-inheritance#horizontal-reuse] -| `{layout 'file.latte'}` / `{extends}` | [specifică un fișier de machetare |template-inheritance#layout-inheritance] -| `{embed}`... `{/embed}` | [încarcă șablonul sau blocul și vă permite să suprascrieți blocurile |template-inheritance#unit-inheritance] -| `{ifset blockname}`... `{/ifset}` | [condiție dacă blocul este definit |template-inheritance#checking-block-existence] +|## Blocuri, layout-uri, moștenirea șabloanelor +| `{block}` | [bloc anonim |#block] +| `{block blockname}` | [definește un bloc |template-inheritance#Blocuri block] +| `{define blockname}` | [definește un bloc pentru utilizare ulterioară |template-inheritance#Definiții define] +| `{include blockname}` | [randarea blocului |template-inheritance#Redarea blocurilor include] +| `{include blockname from 'file.latte'}` | [randează blocul dintr-un fișier |template-inheritance#Redarea blocurilor include] +| `{import 'file.latte'}` | [încarcă blocurile dintr-un șablon |template-inheritance#Reutilizarea orizontală import] +| `{layout 'file.latte'}` / `{extends}` | [specifică fișierul cu layout-ul |template-inheritance#Moștenirea layout-ului layout] +| `{embed}` … `{/embed}` | [încarcă șablonul sau blocul și permite suprascrierea blocurilor |template-inheritance#Moștenirea unitară embed] +| `{ifset blockname}` … `{/ifset}` | [condiție, dacă există blocul |template-inheritance#Verificarea existenței blocurilor ifset] .[table-latte-tags language-latte] |## Gestionarea excepțiilor -| `{try}`... `{else}`... `{/try}` | [prinderea excepțiilor |#try] -| `{rollback}` | [elimină blocul de încercare |#rollback] +| `{try}` … `{else}` … `{/try}` | [capturarea excepțiilor |#try] +| `{rollback}` | [anularea blocului try |#rollback] .[table-latte-tags language-latte] |## Variabile -| `{var $foo = value}` | [crearea de variabile |#var-default] -| `{default $foo = value}` | [valoare implicită atunci când variabila nu este declarată |#var-default] -| `{parameters}` | [declară variabile, tipuri și valori implicite |#parameters] -| `{capture}`... `{/capture}` | [captează o secțiune într-o variabilă |#capture] +| `{var $foo = value}` | [creează o variabilă |#var default] +| `{default $foo = value}` | [creează o variabilă, dacă nu există |#var default] +| `{parameters}` | [declară variabile, tipuri și valori implicite |#parameters] +| `{capture}` … `{/capture}` | [capturează blocul într-o variabilă |#capture] .[table-latte-tags language-latte] |## Tipuri -| `{varType}` | [declară tipul de variabilă |type-system#varType] -| `{varPrint}` | [sugerează tipuri de variabile |type-system#varPrint] -| `{templateType}` | [declară tipurile de variabile folosind clasa |type-system#templateType] -| `{templatePrint}` | [generează o clasă cu proprietăți |type-system#templatePrint] +| `{varType}` | [declară tipul variabilei |type-system#varType] +| `{varPrint}` | [sugerează tipurile variabilelor |type-system#varPrint] +| `{templateType}` | [declară tipurile variabilelor conform clasei |type-system#templateType] +| `{templatePrint}` | [sugerează clasa cu tipurile variabilelor |type-system#templatePrint] .[table-latte-tags language-latte] -|## Traducere -| `{_string}` | [tipărește traducerea |#Translation] -| `{translate}`... `{/translate}` | [traduce conținutul |#Translation] +|## Traduceri +| `{_...}` | [afișează traducerea |#Traduceri] +| `{translate}` … `{/translate}` | [traduce conținutul |#Traduceri] .[table-latte-tags language-latte] |## Altele -| `{contentType}` | [comută modul de scăpare și trimite antetul HTTP |#contenttype] -| `{debugbreak}` | [setează un punct de întrerupere în cod |#debugbreak] -| `{do}` | [evaluează o expresie fără a o imprima |#do] -| `{dump}` | [transferă variabilele în bara Tracy Bar |#dump] -| `{spaceless}`... `{/spaceless}` | [elimină spațiile albe inutile |#spaceless] -| `{syntax}` | [schimbă sintaxa în timpul execuției |#syntax] -| `{trace}` | [afișează urma de stivă |#trace] +| `{contentType}` | [comută escaparea și trimite antetul HTTP |#contentType] +| `{debugbreak}` | [plasează un breakpoint în cod |#debugbreak] +| `{do}` | [execută codul, dar nu afișează nimic |#do] +| `{dump}` | [afisează variabilele în Tracy Bar |#dump] +| `{php}` | [execută orice cod PHP |#php] +| `{spaceless}` … `{/spaceless}` | [elimină spațiile redundante |#spaceless] +| `{syntax}` | [schimbarea sintaxei în timpul rulării |#syntax] +| `{trace}` | [afișează stack trace |#trace] .[table-latte-tags language-latte] -|## HTML tag helpers -| `n:class` | [atribut de clasă inteligentă |#n:class] -| `n:attr` | [atribute HTML inteligente |#n:attr] -| `n:tag` | [nume dinamic al elementului HTML |#n:tag] -| `n:ifcontent` | [Omite tagul HTML gol |#n:ifcontent] +|## Ajutoare pentru coderul HTML +| `n:class` | [scriere dinamică a atributului HTML class |#n:class] +| `n:attr` | [scriere dinamică a oricăror atribute HTML |#n:attr] +| `n:tag` | [scriere dinamică a numelui elementului HTML |#n:tag] +| `n:ifcontent` | [omite tag-ul HTML gol |#n:ifcontent] .[table-latte-tags language-latte] -|## Disponibil numai în Nette Framework -| `n:href` | [link în elementele HTML `` |application:creating-links#In the Presenter Template] -| `{link}` | [tipărește un link |application:creating-links#In the Presenter Template] -| `{plink}` | [tipărește un link către un prezentator |application:creating-links#In the Presenter Template] -| `{control}` | [tipărește o componentă |application:components#Rendering] -| `{snippet}`... `{/snippet}` | [un fragment de șablon care poate fi trimis prin AJAX |application:ajax#tag-snippet] -| `{snippetArea}` | plic de fragmente -| `{cache}`... `{/cache}` | [pune în cache o secțiune de șablon |caching:en#caching-in-latte] +|## Disponibile doar în Nette Framework +| `n:href` | [link utilizat în elementele HTML `` |application:creating-links#În șablonul presenterului] +| `{link}` | [afișează linkul |application:creating-links#În șablonul presenterului] +| `{plink}` | [afișează linkul către presenter |application:creating-links#În șablonul presenterului] +| `{control}` | [randează componenta |application:components#Redare] +| `{snippet}` … `{/snippet}` | [fragment care poate fi trimis prin AJAX |application:ajax#Snippets în Latte] +| `{snippetArea}` | [înveliș pentru fragmente |application:ajax#Zone de snippets] +| `{cache}` … `{/cache}` | [cachează o parte a șablonului |caching:#Stocarea în cache în Latte] .[table-latte-tags language-latte] -|## Disponibil numai cu Nette Forms -| `{form}`... `{/form}` | [tipărește un element de formular |forms:rendering#form] -| `{label}`... `{/label}` | [tipărește o etichetă de intrare a unui formular |forms:rendering#label-input] -| `{input}` | [tipărește un element de intrare în formular |forms:rendering#label-input] -| `{inputError}` | [tipărește mesajul de eroare pentru elementul de intrare al formularului |forms:rendering#inputError] -| `n:name` | [activează un element de intrare HTML |forms:rendering#n:name] -| `{formPrint}` | [generează schița unui formular Latte |forms:rendering#formPrint] -| `{formPrintClass}` | [tipărește clasa PHP pentru datele formularului |forms:in-presenter#mapping-to-classes] -| `{formContext}`... `{/formContext}` | [redarea parțială a formularului |forms:rendering#special-cases] +|## Disponibile doar cu Nette Forms +| `{form}` … `{/form}` | [randează tag-urile formularului |forms:rendering#form] +| `{label}` … `{/label}` | [randează eticheta elementului de formular |forms:rendering#label input] +| `{input}` | [randează elementul de formular |forms:rendering#label input] +| `{inputError}` | [afișează mesajul de eroare al elementului de formular |forms:rendering#inputError] +| `n:name` | [activează elementul de formular |forms:rendering#n:name] +| `{formContainer}` … `{/formContainer}` | [randarea containerului de formular |forms:rendering#Cazuri speciale] + +.[table-latte-tags language-latte] +|## Disponibil numai cu Nette Assets +| `{asset}` | [redă un activ ca element HTML sau URL |assets:#asset] +| `{preload}` | [generează indicii de preîncărcare pentru optimizarea performanței |assets:#preload] +| `n:asset` | [adaugă atribute ale activelor la elementele HTML |assets:#n:asset] -Imprimare .[#toc-printing] -========================== +Afișare +======= `{$var}` `{...}` `{=...}` ------------------------- -Latte utilizează eticheta `{=...}` pentru a imprima orice expresie la ieșire. Dacă expresia începe cu o variabilă sau cu un apel de funcție, nu este necesar să se scrie un semn egal. Ceea ce, în practică, înseamnă că aproape niciodată nu este necesar să fie scris: +În Latte se folosește tag-ul `{=...}` pentru afișarea oricărei expresii în output. Latte ține la confortul dvs., așa că dacă expresia începe cu o variabilă sau un apel de funcție, nu este nevoie să scrieți semnul egal. Ceea ce în practică înseamnă că aproape niciodată nu este nevoie să îl scrieți: ```latte -Name: {$name} {$surname}
                                                                                                            -Age: {date('Y') - $birth}
                                                                                                            +Nume: {$name} {$surname}
                                                                                                            +Vârstă: {date('Y') - $birth}
                                                                                                            ``` -Puteți scrie ca expresie orice știți din PHP. Pur și simplu nu trebuie să învățați un nou limbaj. De exemplu: +Ca expresie puteți scrie orice cunoașteți din PHP. Nu trebuie, pur și simplu, să învățați un nou limbaj. De exemplu: ```latte {='0' . ($num ?? $num * 3) . ', ' . PHP_VERSION} ``` -Vă rugăm să nu căutați niciun sens în exemplul anterior, dar dacă găsiți vreunul acolo, scrieți-ne :-) +Vă rugăm, nu căutați niciun sens în exemplul anterior, dar dacă găsiți vreunul, scrieți-ne :-) -Ieșire prin evadare .[#toc-escaping-output] -------------------------------------------- +Escaparea outputului +-------------------- -Care este cea mai importantă sarcină a unui sistem de șabloane? Să evite găurile de securitate. Și exact asta face Latte de fiecare dată când imprimați ceva la ieșire. El scapă automat totul: +Care este cea mai importantă sarcină a unui sistem de șabloane? Prevenirea găurilor de securitate. Și exact asta face Latte întotdeauna când afișați ceva. Escapează automat: ```latte -

                                                                                                            {='one < two'}

                                                                                                            {* prints: '

                                                                                                            one < two

                                                                                                            ' *} +

                                                                                                            {='one < two'}

                                                                                                            {* afișează: '

                                                                                                            one < two

                                                                                                            ' *} ``` -Mai exact, Latte folosește escape-ul sensibil la context, care este o caracteristică atât de importantă și unică încât [i-am dedicat un capitol separat |safety-first#context-aware-escaping]. +Pentru a fi exacți, Latte folosește escaparea sensibilă la context, ceea ce este un lucru atât de important și unic, încât i-am dedicat [un capitol separat |safety-first#Escapare contextuală sensibilă]. -Și dacă imprimați conținut codificat HTML dintr-o sursă de încredere? Atunci puteți să dezactivați cu ușurință scăparea: +Și ce se întâmplă dacă afișați conținut codificat în HTML dintr-o sursă de încredere? Atunci puteți dezactiva ușor escaparea: ```latte {$trustedHtmlString|noescape} ``` .[warning] -Utilizarea abuzivă a filtrului `noescape` poate duce la o vulnerabilitate XSS! Nu îl utilizați niciodată decât dacă sunteți **absolut sigur** de ceea ce faceți și dacă șirul pe care îl imprimați provine dintr-o sursă de încredere. +Utilizarea incorectă a filtrului `noescape` poate duce la vulnerabilitatea XSS! Nu-l utilizați niciodată dacă nu sunteți **absolut sigur** de ceea ce faceți și că șirul afișat provine dintr-o sursă de încredere. -Imprimarea în JavaScript .[#toc-printing-in-javascript] -------------------------------------------------------- +Afișarea în JavaScript +---------------------- -Datorită scăpării sensibile la context, este foarte ușor să imprimați variabile în JavaScript, iar Latte le va scăpa în mod corespunzător. +Datorită escapării sensibile la context, este minunat de ușor să afișați variabile în interiorul JavaScriptului, iar Latte se ocupă de escaparea corectă. -Variabila nu trebuie să fie un șir de caractere, este acceptat orice tip de date, care este apoi codificat ca JSON: +Variabila nu trebuie să fie doar un șir, este suportat orice tip de date, care apoi este codificat ca JSON: ```latte {var $foo = ['hello', true, 1]} @@ -179,7 +185,7 @@ Generează: ``` -Acesta este și motivul pentru care **nu puneți variabilele între ghilimele**: Latte le adaugă în jurul șirurilor de caractere. Iar dacă doriți să puneți o variabilă de șir de caractere în alt șir de caractere, pur și simplu le concatenați: +Acesta este și motivul pentru care **nu se scriu ghilimele** în jurul variabilei: Latte le adaugă singur pentru șiruri. Și dacă doriți să inserați o variabilă șir într-un alt șir, pur și simplu le concatenați: ```latte ``` -Filtre .[#toc-filters] ----------------------- +Filtre +------ -Expresia tipărită poate fi modificată [prin filtre |syntax#filters]. De exemplu, acest exemplu convertește șirul în majuscule și îl scurtează la maximum 30 de caractere: +Expresia afișată poate fi modificată prin [filtre |syntax#Filtre]. Astfel, de exemplu, convertim un șir în majuscule și îl scurtăm la maximum 30 de caractere: ```latte {$string|upper|truncate:30} ``` -De asemenea, puteți aplica filtre la părți ale unei expresii, după cum urmează: +Puteți aplica filtre și pe părți parțiale ale expresiei în acest mod: ```latte {$left . ($middle|upper) . $right} ``` -Condiții .[#toc-conditions] -=========================== +Condiții +======== `{if}` `{elseif}` `{else}` -------------------------- -Condițiile se comportă în același mod ca și omologii lor din PHP. Puteți utiliza aceleași expresii pe care le cunoașteți din PHP, nu trebuie să învățați un nou limbaj. +Condițiile se comportă la fel ca omologii lor din PHP. Puteți folosi în ele aceleași expresii pe care le cunoașteți din PHP, nu trebuie să învățați un nou limbaj. ```latte {if $product->inStock > Stock::Minimum} - In stock + În stoc {elseif $product->isOnWay()} - On the way + Pe drum {else} - Not available + Indisponibil {/if} ``` -La fel ca orice tag pair, o pereche de `{if} ... {/ if}` poate fi scrisă ca [n:attribute |syntax#n:attributes], de exemplu: +Ca orice tag pereche, și perechea `{if} ... {/if}` poate fi scrisă și sub formă de [n:atribut |syntax#n:atribute], de exemplu: ```latte -

                                                                                                            In stock {$count} items

                                                                                                            +

                                                                                                            În stoc {$count} bucăți

                                                                                                            ``` -Știați că puteți adăuga prefixul `tag-` la n:attribute? În acest caz, condiția va afecta doar etichetele HTML, iar conținutul dintre ele va fi întotdeauna tipărit: +Știați că la n:atribute puteți adăuga prefixul `tag-`? Atunci condiția se va aplica doar la afișarea tag-urilor HTML, iar conținutul dintre ele se va afișa întotdeauna: ```latte
                                                                                                            Hello -{* prints 'Hello' when $clickable is falsey *} -{* prints 'Hello' when $clickable is truthy *} +{* afișează 'Hello' când $clickable este fals *} +{* afișează 'Hello' când $clickable este adevărat *} +``` + +Grozav. + + +`n:else` .{data-version:3.0.11} +------------------------------- + +Dacă scrieți condiția `{if} ... {/if}` sub formă de [n:atribut |syntax#n:atribute], aveți posibilitatea de a specifica și o ramură alternativă folosind `n:else`: + +```latte +În stoc {$count} bucăți + +indisponibil ``` -Frumos. +Atributul `n:else` poate fi folosit și în pereche cu [`n:ifset` |#ifset elseifset], [`n:foreach` |#foreach], [`n:try` |#try], [#`n:ifcontent`] și [`n:ifchanged` |#ifchanged]. `{/if $cond}` ------------- -S-ar putea să fiți surprins de faptul că expresia din condiția `{if}` poate fi, de asemenea, specificată în eticheta end. Acest lucru este util în situațiile în care nu cunoaștem încă valoarea condiției la deschiderea tag-ului. Să o numim o decizie amânată. +Poate vă va surprinde că expresia din condiția `{if}` poate fi specificată și în tag-ul de închidere. Acest lucru este util în situațiile în care, la deschiderea condiției, încă nu cunoaștem valoarea sa. Să numim asta decizie amânată. -De exemplu, începem să listăm un tabel cu înregistrări din baza de date și abia după ce finalizăm raportul ne dăm seama că nu exista nicio înregistrare în baza de date. Deci, punem condiția în tag-ul de sfârșit `{/if}` și, dacă nu există nicio înregistrare, nu se va imprima nimic: +De exemplu, începem să afișăm un tabel cu înregistrări din baza de date și abia după finalizarea afișării realizăm că în baza de date nu a existat nicio înregistrare. Atunci punem condiția în tag-ul de închidere `{/if}` și dacă nu există nicio înregistrare, nimic din toate acestea nu se va afișa: ```latte {if} -

                                                                                                            Printing rows from the database

                                                                                                            +

                                                                                                            Listarea rândurilor din baza de date

                                                                                                            {foreach $resultSet as $row} @@ -264,30 +284,30 @@ De exemplu, începem să listăm un tabel cu înregistrări din baza de date și {/if isset($row)} ``` -Practic, nu-i așa? +Util, nu-i așa? -De asemenea, puteți utiliza `{else}` în condiția amânată, dar nu și `{elseif}`. +În condiția amânată se poate folosi și `{else}`, dar nu `{elseif}`. `{ifset}` `{elseifset}` ----------------------- .[note] -A se vedea și [`{ifset block}` |template-inheritance#checking-block-existence] +Vezi și [`{ifset block}` |template-inheritance#Verificarea existenței blocurilor ifset] -Utilizați condiția `{ifset $var}` pentru a determina dacă o variabilă (sau mai multe variabile) există și dacă are o valoare diferită de cea nulă. De fapt, este același lucru cu `if (isset($var))` în PHP. La fel ca orice tag pair, aceasta poate fi scrisă sub forma [n:attribute |syntax#n:attributes], așa că haideți să o arătăm în exemplu: +Folosind condiția `{ifset $var}`, verificăm dacă variabila (sau mai multe variabile) există și are o valoare non-*null*. De fapt, este același lucru cu `if (isset($var))` în PHP. Ca orice tag pereche, poate fi scris și sub formă de [n:atribut |syntax#n:atribute], așa că să arătăm asta ca exemplu: ```latte - + ``` -`{ifchanged}` .{data-version:2.9} ---------------------------------- +`{ifchanged}` +------------- -`{ifchanged}` verifică dacă valoarea unei variabile s-a schimbat de la ultima iterație din buclă (foreach, for sau while). +`{ifchanged}` verifică dacă valoarea unei variabile s-a schimbat de la ultima iterație într-o buclă (foreach, for sau while). -Dacă specificăm una sau mai multe variabile în etichetă, aceasta va verifica dacă vreuna dintre ele s-a schimbat și va imprima conținutul în consecință. De exemplu, exemplul următor tipărește prima literă a unui nume ca titlu de fiecare dată când se schimbă atunci când se listează nume: +Dacă specificăm una sau mai multe variabile în tag, va verifica dacă vreuna dintre ele s-a schimbat și va afișa conținutul în consecință. De exemplu, următorul exemplu afișează prima literă a numelui ca titlu de fiecare dată când se schimbă la afișarea numelor: ```latte {foreach ($names|sort) as $name} @@ -297,7 +317,7 @@ Dacă specificăm una sau mai multe variabile în etichetă, aceasta va verifica {/foreach} ``` -Cu toate acestea, dacă nu se furnizează niciun argument, conținutul redat în sine va fi verificat în raport cu starea sa anterioară. Acest lucru înseamnă că, în exemplul anterior, putem omite în siguranță argumentul din tag. Și, bineînțeles, putem folosi și [n:attribute |syntax#n:attributes]: +Dacă însă nu specificăm niciun argument, se va verifica conținutul randat față de starea sa anterioară. Acest lucru înseamnă că, în exemplul anterior, putem omite liniștit argumentul din tag. Și, desigur, putem folosi și [n:atribut |syntax#n:atribute]: ```latte {foreach ($names|sort) as $name} @@ -307,49 +327,49 @@ Cu toate acestea, dacă nu se furnizează niciun argument, conținutul redat în {/foreach} ``` -De asemenea, puteți include o clauză `{else}` în interiorul `{ifchanged}`. +În interiorul `{ifchanged}` se poate specifica și clauza `{else}`. `{switch}` `{case}` `{default}` ------------------------------- -Compară valoarea cu mai multe opțiuni. Aceasta este similară structurii `switch` pe care o cunoașteți din PHP. Cu toate acestea, Latte o îmbunătățește: +Compară valoarea cu mai multe opțiuni. Este un echivalent al instrucțiunii condiționale `switch`, pe care o cunoașteți din PHP. Cu toate acestea, Latte o îmbunătățește: -- utilizează o comparație strictă (`===`) -- nu are nevoie de un `break` +- folosește comparare strictă (`===`) +- nu necesită `break` -Așadar, este echivalentul exact al structurii `match` cu care vine PHP 8.0. +Este deci echivalentul exact al structurii `match` introdusă în PHP 8.0. ```latte {switch $transport} {case train} - By train + Cu trenul {case plane} - By plane + Cu avionul {default} - Differently + Altfel {/switch} ``` -.{data-version:2.9} + Clauza `{case}` poate conține mai multe valori separate prin virgule: ```latte {switch $status} -{case $status::New}new item -{case $status::Sold, $status::Unknown}not available +{case $status::New}element nou +{case $status::Sold, $status::Unknown}indisponibil {/switch} ``` -Bucle .[#toc-loops] -=================== +Bucle +===== -În Latte, toate buclele pe care le cunoașteți din PHP vă sunt disponibile: foreach, for și while. +În Latte găsiți toate buclele pe care le cunoașteți din PHP: foreach, for și while. `{foreach}` ----------- -Ciclul se scrie exact la fel ca în PHP: +Bucla se scrie exact la fel ca în PHP: ```latte {foreach $langs as $code => $lang} @@ -357,11 +377,11 @@ Ciclul se scrie exact la fel ca în PHP: {/foreach} ``` -În plus, el are câteva ajustări utile despre care vom vorbi acum. +În plus, are câteva caracteristici utile despre care vom vorbi acum. -De exemplu, Latte verifică dacă variabilele create nu suprascriu accidental variabilele globale cu același nume. Acest lucru vă va salva atunci când presupuneți că `$lang` este limba curentă a paginii și nu vă dați seama că `foreach $langs as $lang` a suprascris acea variabilă. +Latte, de exemplu, verifică dacă variabilele create nu suprascriu accidental variabile globale cu același nume. Salvează situații în care contați pe faptul că `$lang` conține limba curentă a paginii și nu realizați că `foreach $langs as $lang` v-a suprascris acea variabilă. -Bucla foreach poate fi, de asemenea, scrisă foarte elegant și economic cu [n:attribute |syntax#n:attributes]: +Bucla foreach poate fi scrisă și foarte elegant și concis folosind [n:atribut |syntax#n:atribute]: ```latte
                                                                                                              @@ -369,7 +389,7 @@ Bucla foreach poate fi, de asemenea, scrisă foarte elegant și economic cu [n:a
                                                                                                            ``` -Știați că puteți adăuga prefixul `inner-` la n:attribute? Astfel, numai partea interioară a elementului va fi repetată în buclă: +Știați că la n:atribute puteți adăuga prefixul `inner-`? Atunci doar interiorul elementului se va repeta în buclă: ```latte
                                                                                                            @@ -378,7 +398,7 @@ Bucla foreach poate fi, de asemenea, scrisă foarte elegant și economic cu [n:a
                                                                                                            ``` -Deci, se tipărește ceva de genul: +Deci se va afișa ceva de genul: ```latte
                                                                                                            @@ -390,17 +410,17 @@ Deci, se tipărește ceva de genul: ``` -`{else}` .{data-version:2.9}{toc: foreach-else} ------------------------------------------------ +`{else}` .{toc: foreach-else} +----------------------------- -Bucla `foreach` poate primi o clauză opțională `{else}` al cărei text este afișat dacă matricea dată este goală: +În interiorul buclei `foreach` se poate specifica clauza `{else}`, al cărei conținut se afișează dacă bucla este goală: ```latte
                                                                                                              {foreach $people as $person}
                                                                                                            • {$person->name}
                                                                                                            • {else} -
                                                                                                            • Sorry, no users in this list
                                                                                                            • +
                                                                                                            • Ne pare rău, nu există utilizatori în această listă
                                                                                                            • {/foreach}
                                                                                                            ``` @@ -409,17 +429,17 @@ Bucla `foreach` poate primi o clauză opțională `{else}` al cărei text este a `$iterator` ----------- -În cadrul buclei `foreach` se inițializează variabila `$iterator`. Aceasta conține informații importante despre bucla curentă. +În interiorul buclei `foreach`, Latte creează variabila `$iterator`, cu ajutorul căreia putem afla informații utile despre bucla în curs: -- `$iterator->first` - este aceasta prima iterație? -- `$iterator->last` - este aceasta ultima iterație? -- `$iterator->counter` - contorul de iterații, începe de la 1 -- `$iterator->counter0` - contorul de iterații, începe de la 0 .{data-version:2.9} -- `$iterator->odd` - este această iterație impară? -- `$iterator->even` - este această iterație pară? -- `$iterator->parent` - iteratorul care îl înconjoară pe cel curent .{data-version:2.9} -- `$iterator->nextValue` - următorul element din buclă -- `$iterator->nextKey` - cheia următorului element din buclă +- `$iterator->first` - este prima trecere prin buclă? +- `$iterator->last` - este ultima trecere? +- `$iterator->counter` - a câta trecere este, numărând de la unu? +- `$iterator->counter0` - a câta trecere este, numărând de la zero? +- `$iterator->odd` - este o trecere impară? +- `$iterator->even` - este o trecere pară? +- `$iterator->parent` - iteratorul care îl înconjoară pe cel curent +- `$iterator->nextValue` - elementul următor din buclă +- `$iterator->nextKey` - cheia elementului următor din buclă ```latte @@ -435,20 +455,19 @@ Bucla `foreach` poate primi o clauză opțională `{else}` al cărei text este a {/foreach} ``` -Lapte este inteligent și `$iterator->last` funcționează nu numai pentru array-uri, ci și atunci când bucla se execută peste un iterator general în care numărul de elemente nu este cunoscut dinainte. +Latte este inteligent și `$iterator->last` funcționează nu doar pentru array-uri, ci și atunci când bucla parcurge un iterator general, unde numărul de elemente nu este cunoscut în avans. `{first}` `{last}` `{sep}` -------------------------- -Aceste etichete pot fi utilizate în interiorul buclei `{foreach}`. Conținutul din `{first}` este redat pentru prima trecere. -Conținutul din `{last}` este redat ... puteți ghici? Da, pentru ultima trecere. Acestea sunt, de fapt, prescurtări pentru `{if $iterator->first}` și `{if $iterator->last}`. +Aceste tag-uri pot fi folosite în interiorul buclei `{foreach}`. Conținutul `{first}` se randează dacă este prima trecere. Conținutul `{last}` se randează… ghiciți? Da, dacă este ultima trecere. Sunt de fapt prescurtări pentru `{if $iterator->first}` și `{if $iterator->last}`. -Etichetele pot fi scrise și sub forma [n:attributes |syntax#n:attributes]: +Tag-urile pot fi folosite și elegant ca [n:atribut |syntax#n:atribute]: ```latte {foreach $rows as $row} - {first}

                                                                                                            List of names

                                                                                                            {/first} + {first}

                                                                                                            Lista de nume

                                                                                                            {/first}

                                                                                                            {$row->name}

                                                                                                            @@ -456,7 +475,7 @@ Etichetele pot fi scrise și sub forma [n:attributes |syntax#n:attributes]: {/foreach} ``` -Conținutul `{sep}` este redat dacă iterația nu este ultima, astfel încât este potrivit pentru imprimarea delimitatorilor, cum ar fi virgulele între elementele enumerate: +Conținutul tag-ului `{sep}` se randează dacă trecerea nu este ultima, fiind util pentru randarea separatoarelor, de exemplu, virgule între elementele afișate: ```latte {foreach $items as $item} {$item} {sep}, {/sep} {/foreach} @@ -465,12 +484,12 @@ Conținutul `{sep}` este redat dacă iterația nu este ultima, astfel încât es Este destul de practic, nu-i așa? -`{iterateWhile}` .{data-version:2.10} -------------------------------------- +`{iterateWhile}` +---------------- -Aceasta simplifică gruparea datelor liniare în timpul iterației într-o buclă foreach prin efectuarea iterației într-o buclă imbricata, atâta timp cât condiția este îndeplinită. [Citiți instrucțiunile din cartea de bucate |cookbook/iteratewhile]. +Simplifică gruparea datelor liniare în timpul iterării într-o buclă foreach, efectuând iterația într-o buclă imbricată atâta timp cât condiția este îndeplinită. [Citiți un tutorial detaliat|cookbook/grouping]. -De asemenea, poate înlocui în mod elegant `{first}` și `{last}` în exemplul de mai sus: +Poate înlocui elegant și `{first}` și `{last}` din exemplul de mai sus: ```latte {foreach $rows as $row} @@ -487,19 +506,21 @@ De asemenea, poate înlocui în mod elegant `{first}` și `{last}` în exemplul {/foreach} ``` +Vezi și filtrele [batch |filters#batch] și [group |filters#group]. + `{for}` ------- -Scriem ciclul exact în același mod ca în PHP: +Bucla o scriem exact la fel ca în PHP: ```latte {for $i = 0; $i < 10; $i++} - Item #{$i} + Element {$i} {/for} ``` -Eticheta poate fi scrisă și sub forma [n:attribute |syntax#n:attributes]: +Tag-ul poate fi folosit și ca [n:atribut |syntax#n:atribute]: ```latte

                                                                                                            {$i}

                                                                                                            @@ -509,7 +530,7 @@ Eticheta poate fi scrisă și sub forma [n:attribute |syntax#n:attributes]: `{while}` --------- -Din nou, scriem ciclul exact în același mod ca în PHP: +Bucla o scriem din nou exact la fel ca în PHP: ```latte {while $row = $result->fetch()} @@ -517,7 +538,7 @@ Din nou, scriem ciclul exact în același mod ca în PHP: {/while} ``` -Sau ca [n:attribute |syntax#n:attributes]: +Sau ca [n:atribut |syntax#n:atribute]: ```latte @@ -525,7 +546,7 @@ Sau ca [n:attribute |syntax#n:attributes]: ``` -O variantă cu o condiție în tag-ul final corespunde buclei do-while din PHP: +Este posibilă și varianta cu condiția în tag-ul de închidere, care corespunde în PHP buclei do-while: ```latte {while} @@ -537,7 +558,7 @@ O variantă cu o condiție în tag-ul final corespunde buclei do-while din PHP: `{continueIf}` `{skipIf}` `{breakIf}` ------------------------------------- -Există etichete speciale pe care le puteți utiliza pentru a controla orice buclă - `{continueIf ?}` și `{breakIf ?}` care trec la următoarea iterație și, respectiv, încheie bucla, dacă sunt îndeplinite condițiile: +Pentru controlul oricărei bucle se pot folosi tag-urile `{continueIf ?}` și `{breakIf ?}`, care trec la elementul următor, respectiv încheie bucla la îndeplinirea condiției: ```latte {foreach $rows as $row} @@ -547,8 +568,8 @@ Există etichete speciale pe care le puteți utiliza pentru a controla orice buc {/foreach} ``` -.{data-version:2.9} -Eticheta `{skipIf}` este foarte asemănătoare cu `{continueIf}`, dar nu incrementează contorul. Astfel, nu există găuri în numerotare atunci când tipăriți `$iterator->counter` și săriți peste unele elemente. De asemenea, clauza {else} va fi redată atunci când săriți peste toate elementele. + +Tag-ul `{skipIf}` este foarte similar cu `{continueIf}`, dar nu incrementează contorul `$iterator->counter`, astfel încât dacă îl afișăm și în același timp sărim peste unele elemente, nu vor exista goluri în numerotare. De asemenea, clauza `{else}` se randează dacă sărim peste toate elementele. ```latte
                                                                                                              @@ -556,7 +577,7 @@ Eticheta `{skipIf}` este foarte asemănătoare cu `{continueIf}`, dar nu increme {skipIf $person->age < 18}
                                                                                                            • {$iterator->counter}. {$person->name}
                                                                                                            • {else} -
                                                                                                            • Sorry, no adult users in this list
                                                                                                            • +
                                                                                                            • Ne pare rău, nu există adulți în această listă
                                                                                                            • {/foreach}
                                                                                                            ``` @@ -565,44 +586,40 @@ Eticheta `{skipIf}` este foarte asemănătoare cu `{continueIf}`, dar nu increme `{exitIf}` .{data-version:3.0.5} -------------------------------- -Încheie redarea unui șablon sau a unui bloc atunci când este îndeplinită o condiție (de exemplu, "ieșire anticipată"). +Încheie randarea șablonului sau a blocului la îndeplinirea condiției (așa-numitul "early exit"). ```latte {exitIf !$messages} -

                                                                                                            Messages

                                                                                                            +

                                                                                                            Mesaje

                                                                                                            {$message}
                                                                                                            ``` -Includerea șabloanelor .[#toc-including-templates] -================================================== +Includerea șablonului +===================== `{include 'file.latte'}` .{toc: include} ---------------------------------------- .[note] -A se vedea și [`{include block}` |template-inheritance#printing-blocks] +Vezi și [`{include block}` |template-inheritance#Redarea blocurilor include] -Eticheta `{include}` încarcă și redă șablonul specificat. În limbajul nostru preferat, PHP, este ca și cum ar fi: +Tag-ul `{include}` încarcă și randează șablonul specificat. Dacă am vorbi în limbajul limbii noastre preferate PHP, este ceva de genul: ```php ``` -Șabloanele incluse nu au acces la variabilele contextului activ, dar au acces la variabilele globale. +Șabloanele incluse nu au acces la variabilele contextului activ, au acces doar la variabilele globale. -Puteți transmite variabilele în acest mod: +Puteți transmite variabile în șablonul inclus în acest mod: ```latte -{* de la Latte 2.9 *} {include 'template.latte', foo: 'bar', id: 123} - -{* înainte de Latte 2.9 *} -{include 'template.latte', foo => 'bar', id => 123} ``` Numele șablonului poate fi orice expresie PHP: @@ -612,25 +629,25 @@ Numele șablonului poate fi orice expresie PHP: {include $ajax ? 'ajax.latte' : 'not-ajax.latte'} ``` -Conținutul inserat poate fi modificat cu ajutorul [filtrelor |syntax#filters]. Următorul exemplu elimină toate elementele HTML și ajustează cazul: +Conținutul inclus poate fi modificat folosind [filtre |syntax#Filtre]. Următorul exemplu elimină tot HTML-ul și modifică dimensiunea literelor: ```latte {include 'heading.latte' |stripHtml|capitalize} ``` - [Moștenirea șablonului |template inheritance] **nu este implicată** în acest lucru în mod implicit. Deși puteți adăuga etichete de bloc la șabloanele care sunt incluse, acestea nu vor înlocui blocurile corespunzătoare din șablonul în care sunt incluse. Gândiți-vă la incluziuni ca la părți independente și ecranate ale paginilor sau modulelor. Acest comportament poate fi schimbat cu ajutorul modificatorului `with blocks` (începând cu Latte 2.9.1): +În mod implicit, [moștenirea șabloanelor|template-inheritance] nu figurează în niciun fel în acest caz. Chiar dacă în șablonul inclus putem folosi blocuri, nu se va produce înlocuirea blocurilor corespunzătoare din șablonul în care se include. Gândiți-vă la șabloanele incluse ca la părți separate și izolate ale paginilor sau modulelor. Acest comportament poate fi schimbat folosind modificatorul `with blocks`: ```latte {include 'template.latte' with blocks} ``` -Relația dintre numele de fișier specificat în etichetă și fișierul de pe disc este o chestiune de [încărcător |extending-latte#Loaders]. +Relația dintre numele fișierului specificat în tag și fișierul de pe disc este responsabilitatea [loaderului|loaders]. -`{sandbox}` .{data-version:2.8} -------------------------------- +`{sandbox}` +----------- -Atunci când includeți un șablon creat de un utilizator final, ar trebui să aveți în vedere [sandboxing-ul |sandbox] acestuia (mai multe informații în [documentația sandbox |sandbox]): +La includerea unui șablon creat de utilizatorul final, ar trebui să luați în considerare modul sandbox (mai multe informații în [documentația sandbox |sandbox]): ```latte {sandbox 'untrusted.latte', level: 3, data: $menu} @@ -641,9 +658,9 @@ Atunci când includeți un șablon creat de un utilizator final, ar trebui să a ========= .[note] -A se vedea și [`{block name}` |template-inheritance#blocks] +Vezi și [`{block name}` |template-inheritance#Blocuri block] -Blocurile fără nume servesc la capacitatea de a aplica [filtre |syntax#filters] unei părți din șablon. De exemplu, puteți aplica un filtru de [benzi |filters#strip] pentru a elimina spațiile inutile: +Blocurile fără nume servesc ca o modalitate de a aplica [filtre |syntax#Filtre] pe o parte a șablonului. De exemplu, astfel se poate aplica filtrul [strip |filters#spaceless], care elimină spațiile inutile: ```latte {block|strip} @@ -654,16 +671,16 @@ Blocurile fără nume servesc la capacitatea de a aplica [filtre |syntax#filters ``` -Gestionarea excepțiilor .[#toc-exception-handling] -================================================== +Gestionarea excepțiilor +======================= -`{try}` .{data-version:2.9} ---------------------------- +`{try}` +------- -Aceste etichete facilitează foarte mult crearea de șabloane robuste. +Datorită acestui tag, este extrem de ușor să creați șabloane robuste. -Dacă apare o excepție în timpul redării blocului `{try}`, întregul bloc este aruncat și redarea va continua după el: +Dacă în timpul randării blocului `{try}` apare o excepție, întregul bloc este anulat și randarea va continua după el: ```latte {try} @@ -675,7 +692,7 @@ Dacă apare o excepție în timpul redării blocului `{try}`, întregul bloc est {/try} ``` -Conținutul clauzei opționale `{else}` este redat numai atunci când apare o excepție: +Conținutul din clauza opțională `{else}` se randează doar când apare o excepție: ```latte {try} @@ -685,11 +702,11 @@ Conținutul clauzei opționale `{else}` este redat numai atunci când apare o ex {/foreach} {else} -

                                                                                                            Sorry, the tweets could not be loaded.

                                                                                                            +

                                                                                                            Ne pare rău, nu s-au putut încărca tweet-urile.

                                                                                                            {/try} ``` -Eticheta poate fi scrisă și sub forma [n:attribute |syntax#n:attributes]: +Tag-ul poate fi folosit și ca [n:atribut |syntax#n:atribute]: ```latte
                                                                                                              @@ -697,13 +714,13 @@ Eticheta poate fi scrisă și sub forma [n:attribute |syntax#n:attributes]:
                                                                                                            ``` -De asemenea, este posibil să se definească [propriul gestionar de excepții |develop#exception handler], de exemplu, pentru logare: +Este posibil, de asemenea, să definiți propriul [handler de excepții |develop#Gestionar de excepții], de exemplu, pentru logare. -`{rollback}` .{data-version:2.9} --------------------------------- +`{rollback}` +------------ -Blocul `{try}` poate fi, de asemenea, oprit și sărit manual folosind `{rollback}`. Astfel, nu trebuie să verificați în prealabil toate datele de intrare și numai în timpul redării puteți decide dacă este utilă redarea obiectului. +Blocul `{try}` poate fi oprit și sărit și manual folosind `{rollback}`. Datorită acestui fapt, nu trebuie să verificați în prealabil toate datele de intrare și abia în timpul randării puteți decide că nu doriți să randați deloc obiectul: ```latte {try} @@ -719,14 +736,14 @@ Blocul `{try}` poate fi, de asemenea, oprit și sărit manual folosind `{rollbac ``` -Variabile .[#toc-variables] -=========================== +Variabile +========= `{var}` `{default}` ------------------- -Vom crea noi variabile în șablon cu ajutorul etichetei `{var}`: +Variabile noi creăm în șablon cu tag-ul `{var}`: ```latte {var $name = 'John Smith'} @@ -736,13 +753,13 @@ Vom crea noi variabile în șablon cu ajutorul etichetei `{var}`: {var $name = 'John Smith', $age = 27} ``` -Eticheta `{default}` funcționează în mod similar, cu excepția faptului că creează variabile numai dacă acestea nu există: +Tag-ul `{default}` funcționează similar, dar creează variabile doar dacă nu există. Dacă variabila există deja și conține valoarea `null`, nu va fi suprascrisă: ```latte -{default $lang = 'cs'} +{default $lang = 'ro'} ``` -Începând cu Latte 2.7, puteți specifica și [tipurile de variabile |type-system]. Deocamdată, acestea sunt informative și Latte nu le verifică. +Puteți specifica și [tipurile variabilelor|type-system]. Deocamdată sunt informative și Latte nu le verifică. ```latte {var string $name = $article->getTitle()} @@ -750,10 +767,10 @@ Eticheta `{default}` funcționează în mod similar, cu excepția faptului că c ``` -`{parameters}` .{data-version:2.9} ----------------------------------- +`{parameters}` +-------------- -La fel cum o funcție își declară parametrii, un șablon își poate declara variabilele la începutul său: +Așa cum o funcție își declară parametrii, și un șablon poate declara la început variabilele sale: ```latte {parameters @@ -763,15 +780,15 @@ La fel cum o funcție își declară parametrii, un șablon își poate declara } ``` -Variabilele `$a` și `$b` care nu au o valoare implicită au automat valoarea implicită `null`. Tipurile declarate sunt în continuare informative, iar Latte nu le verifică. +Variabilele `$a` și `$b` fără valoare implicită specificată au automat valoarea implicită `null`. Tipurile declarate sunt deocamdată informative și Latte nu le verifică. -În afară de variabilele declarate, acestea nu sunt transmise în șablon. Aceasta este o diferență față de eticheta `{default}`. +Alte variabile decât cele declarate nu sunt transferate în șablon. Prin aceasta se diferențiază de tag-ul `{default}`. `{capture}` ----------- -Prin utilizarea etichetei `{capture}` puteți captura ieșirea într-o variabilă: +Capturează outputul într-o variabilă: ```latte {capture $var} @@ -780,10 +797,10 @@ Prin utilizarea etichetei `{capture}` puteți captura ieșirea într-o variabil {/capture} -

                                                                                                            Captured: {$var}

                                                                                                            +

                                                                                                            Capturat: {$var}

                                                                                                            ``` -Eticheta poate fi scrisă și sub forma [n:attribute |syntax#n:attributes]: +Tag-ul poate fi, la fel ca orice tag pereche, scris și ca [n:atribut |syntax#n:atribute]: ```latte
                                                                                                              @@ -791,15 +808,17 @@ Eticheta poate fi scrisă și sub forma [n:attribute |syntax#n:attributes]:
                                                                                                            ``` +Outputul HTML se salvează în variabila `$var` sub formă de obiect `Latte\Runtime\Html`, pentru a [preveni escaparea nedorită |develop#Dezactivarea auto-escapării variabilelor] la afișare. -Altele .[#toc-others] -===================== + +Altele +====== `{contentType}` --------------- -Utilizați eticheta pentru a specifica ce tip de conținut reprezintă șablonul. Opțiunile sunt următoarele: +Cu acest tag specificați ce tip de conținut reprezintă șablonul. Opțiunile sunt: - `html` (tip implicit) - `xml` @@ -808,16 +827,16 @@ Utilizați eticheta pentru a specifica ce tip de conținut reprezintă șablonul - `calendar` (iCal) - `text` -Utilizarea sa este importantă, deoarece setează escape-ul [sensibil la context |safety-first#context-aware-escaping] și numai atunci Latte poate evada corect. De exemplu, `{contentType xml}` comută în modul XML, `{contentType text}` dezactive complet scăparea. +Utilizarea sa este importantă, deoarece setează [escaparea sensibilă la context |safety-first#Escapare contextuală sensibilă] și doar așa poate escapa corect. De exemplu, `{contentType xml}` comută în modul XML, `{contentType text}` dezactivează complet escaparea. -În cazul în care parametrul este un tip MIME complet, cum ar fi `application/xml`, se trimite, de asemenea, un antet HTTP `Content-Type` către browser: +Dacă parametrul este un tip MIME complet, cum ar fi `application/xml`, atunci trimite și antetul HTTP `Content-Type` către browser: ```latte {contentType application/xml} - RSS feed + Flux RSS ... @@ -829,46 +848,50 @@ Utilizarea sa este importantă, deoarece setează escape-ul [sensibil la context `{debugbreak}` -------------- -Specifică locul în care se va întrerupe execuția codului. Este utilizat în scopuri de depanare pentru ca programatorul să inspecteze mediul de execuție și să se asigure că codul se execută conform așteptărilor. Este compatibil cu [Xdebug |https://xdebug.org]. În plus, puteți specifica o condiție în care codul trebuie să se întrerupă. +Indică locul unde execuția programului va fi suspendată și debuggerul va fi pornit, pentru ca programatorul să poată inspecta mediul de rulare și să verifice dacă programul funcționează conform așteptărilor. Suportă [Xdebug |https://xdebug.org/]. Se poate adăuga o condiție care determină când programul trebuie suspendat. ```latte -{debugbreak} {* întrerupe programul *} +{debugbreak} {* suspendă programul *} -{debugbreak $counter == 1} {* întrerupe programul dacă este îndeplinită condiția *} +{debugbreak $counter == 1} {* suspendă programul la îndeplinirea condiției *} ``` `{do}` ------ -Execută codul și nu tipărește nimic. +Execută cod PHP și nu afișează nimic. La fel ca la toate celelalte tag-uri, prin cod PHP se înțelege o singură expresie, vezi [limitările PHP |syntax#Limitări PHP în Latte]. ```latte {do $num++} ``` -În Latte 2.7 și versiunile anterioare, se folosea `{php}`. - `{dump}` -------- -Aruncă o variabilă sau contextul curent. +Afișează variabila sau contextul curent. ```latte -{dump $name} {* aruncă variabila $name *} +{dump $name} {* Afișează variabila $name *} -{dump} {* descarcă toate variabilele definite *} +{dump} {* Afișează toate variabilele definite curent *} ``` .[caution] -Necesită pachetul [Tracy |tracy:en]. +Necesită biblioteca [Tracy|tracy:]. + + +`{php}` +------- + +Permite executarea oricărui cod PHP. Tag-ul trebuie activat folosind extensia [RawPhpExtension |develop#RawPhpExtension]. `{spaceless}` ------------- -Elimină spațiile albe inutile. Este similar cu filtrul [fără spațiu |filters#spaceless]. +Elimină spațiul alb inutil din output. Funcționează similar cu filtrul [spaceless |filters#spaceless]. ```latte {spaceless} @@ -878,52 +901,52 @@ Elimină spațiile albe inutile. Este similar cu filtrul [fără spațiu |filter {/spaceless} ``` -Ieșiri: +Generează ```latte
                                                                                                            • Hello
                                                                                                            ``` -Eticheta poate fi scrisă și sub forma [n:attribute |syntax#n:attributes]: +Tag-ul poate fi scris și ca [n:atribut |syntax#n:atribute]. `{syntax}` ---------- -Etichetele Latte nu trebuie să fie incluse doar în acolade simple. Puteți alege un alt separator, chiar și în timpul execuției. Acest lucru se face prin `{syntax…}`, unde parametrul poate fi: +Tag-urile Latte nu trebuie să fie delimitate doar de acolade simple. Putem alege și alt delimitator, chiar și în timpul rulării. Pentru aceasta servește `{syntax …}`, unde ca parametru se poate specifica: - double: `{{...}}` -- off: dezactivează complet etichetele Latte +- off: dezactivează complet procesarea tag-urilor Latte -Prin utilizarea notației n:attribute putem dezactiva Latte doar pentru un bloc JavaScript: +Folosind n:atribute, se poate dezactiva Latte, de exemplu, doar pentru un bloc JavaScript: ```latte ``` - [Latte |recipes#Latte inside JavaScript or CSS] poate fi folosit foarte confortabil în JavaScript, doar evitați construcțiile ca în acest exemplu, unde litera urmează imediat după `{`, vezi [Latte în JavaScript sau CSS |recipes#Latte inside JavaScript or CSS]. +Latte poate fi folosit foarte convenabil și în interiorul JavaScriptului, trebuie doar evitate construcțiile ca în acest exemplu, unde o literă urmează imediat după `{`, vezi [Latte în interiorul JavaScriptului sau CSS |recipes#Latte în interiorul JavaScript sau CSS]. -Dacă dezactivați Latte cu `{syntax off}` (adică tag-ul, nu atributul n:), acesta va ignora cu strictețe toate tag-urile până la `{/syntax}`. +Dacă dezactivați Latte folosind `{syntax off}` (adică prin tag, nu prin n:atribut), va ignora consecvent toate tag-urile până la `{/syntax}` -{trace} .{data-version:2.10} ----------------------------- +{trace} +------- -Aruncă o excepție `Latte\RuntimeException`, a cărei urmă din stivă este în spiritul șabloanelor. Astfel, în loc să apeleze funcții și metode, implică apelarea de blocuri și inserarea de șabloane. Dacă utilizați un instrument pentru afișarea clară a excepțiilor aruncate, cum ar fi [Tracy |tracy:en], veți vedea clar stiva de apelare, inclusiv toate argumentele transmise. +Aruncă o excepție `Latte\RuntimeException`, al cărei stack trace este în spiritul șabloanelor. Adică, în loc de apeluri de funcții și metode, conține apeluri de blocuri și includeri de șabloane. Dacă utilizați un instrument pentru afișarea clară a excepțiilor aruncate, cum ar fi [Tracy|tracy:], vi se va afișa clar call stack-ul, inclusiv toți argumentii transmiși. -Ajutoare pentru etichete HTML .[#toc-html-tag-helpers] -====================================================== +Ajutoare pentru coderul HTML +============================ -n:class .[#toc-n-class] ------------------------ +n:class +------- -Datorită `n:class`, este foarte ușor să generați atributul HTML `class` exact așa cum aveți nevoie. +Datorită `n:class`, generați foarte ușor atributul HTML `class` exact conform așteptărilor. -Exemplu: Am nevoie ca elementul activ să aibă clasa `active`: +Exemplu: am nevoie ca elementul activ să aibă clasa `active`: ```latte {foreach $items as $item} @@ -931,7 +954,7 @@ Exemplu: Am nevoie ca elementul activ să aibă clasa `active`: {/foreach} ``` -Și mai am nevoie ca primul element să aibă clasele `first` și `main`: +Și în plus, ca primul element să aibă clasele `first` și `main`: ```latte {foreach $items as $item} @@ -939,7 +962,7 @@ Exemplu: Am nevoie ca elementul activ să aibă clasa `active`: {/foreach} ``` -Iar toate elementele trebuie să aibă clasa `list-item`: +Și toate elementele trebuie să aibă clasa `list-item`: ```latte {foreach $items as $item} @@ -950,10 +973,10 @@ Iar toate elementele trebuie să aibă clasa `list-item`: Uimitor de simplu, nu-i așa? -n:attr .[#toc-n-attr] ---------------------- +n:attr +------ -Atributul `n:attr` poate genera atribute HTML arbitrare cu aceeași eleganță ca și [n:class |#n:class]. +Atributul `n:attr` poate genera orice atribute HTML cu aceeași eleganță ca [#n:class]. ```latte {foreach $data as $item} @@ -961,7 +984,7 @@ Atributul `n:attr` poate genera atribute HTML arbitrare cu aceeași eleganță c {/foreach} ``` -În funcție de valorile returnate, acesta afișează, de ex: +În funcție de valorile returnate, afișează de ex.: ```latte @@ -972,26 +995,28 @@ Atributul `n:attr` poate genera atribute HTML arbitrare cu aceeași eleganță c ``` -n:tag .[#toc-n-tag] -------------------- +n:tag +----- -Atributul `n:tag` poate schimba în mod dinamic numele unui element HTML. +Atributul `n:tag` poate schimba dinamic numele elementului HTML. ```latte

                                                                                                            {$title}

                                                                                                            ``` -Dacă `$heading === null`, atributul `

                                                                                                            ` este tipărit fără modificări. În caz contrar, numele elementului este schimbat cu valoarea variabilei, astfel încât pentru `$heading === 'h3'` se scrie: +Dacă `$heading === null`, se va afișa neschimbat tag-ul `

                                                                                                            `. Altfel, numele elementului se schimbă în valoarea variabilei, deci pentru `$heading === 'h3'` se va afișa: ```latte

                                                                                                            ...

                                                                                                            ``` +Deoarece Latte este un sistem de șabloane sigur, verifică dacă noul nume al tag-ului este valid și nu conține valori nedorite sau dăunătoare. -n:ifcontent .[#toc-n-ifcontent] -------------------------------- -Împiedică tipărirea unui element HTML gol, adică a unui element care nu conține decât spații albe. +n:ifcontent +----------- + +Previne afișarea unui element HTML gol, adică un element care nu conține nimic altceva decât spații. ```latte
                                                                                                            @@ -999,24 +1024,24 @@ n:ifcontent .[#toc-n-ifcontent]
                                                                                                            ``` -În funcție de valorile variabilei `$error`, se va imprima: +Afișează în funcție de valoarea variabilei `$error`: ```latte {* $error = '' *}
                                                                                                            -{* $error = 'Required' *} +{* $error = 'Necesar' *}
                                                                                                            -
                                                                                                            Required
                                                                                                            +
                                                                                                            Necesar
                                                                                                            ``` -Traducere .[#toc-translation] -============================= +Traduceri +========= -Pentru a face ca etichetele de traducere să funcționeze, trebuie să configurați [translator |develop#TranslatorExtension]. De asemenea, puteți utiliza [`translate` |filters#translate] filtru pentru traducere. +Pentru ca tag-urile de traducere să funcționeze, trebuie [activat traducătorul |develop#TranslatorExtension]. Pentru traducere puteți folosi și filtrul [`translate` |filters#translate]. `{_...}` @@ -1025,30 +1050,30 @@ Pentru a face ca etichetele de traducere să funcționeze, trebuie să configura Traduce valorile în alte limbi. ```latte -{_'Basket'} +{_'Coș'} {_$item} ``` Traducătorului i se pot transmite și alți parametri: ```latte -{_'Basket', domain: order} +{_'Coș', domain: order} ``` `{translate}` ------------- -Překládá části šablony: +Traduce părți ale șablonului: ```latte -

                                                                                                            {translate}Order{/translate}

                                                                                                            +

                                                                                                            {translate}Comandă{/translate}

                                                                                                            {translate domain: order}Lorem ipsum ...{/translate} ``` -Eticheta poate fi scrisă și sub forma [n:attribute |syntax#n:attributes], pentru a traduce interiorul elementului: +Tag-ul poate fi scris și ca [n:atribut |syntax#n:atribute], pentru traducerea interiorului elementului: ```latte -

                                                                                                            Order

                                                                                                            +

                                                                                                            Comandă

                                                                                                            ``` diff --git a/latte/ro/template-inheritance.texy b/latte/ro/template-inheritance.texy index 98cd9105bf..bc95ba6a5b 100644 --- a/latte/ro/template-inheritance.texy +++ b/latte/ro/template-inheritance.texy @@ -2,15 +2,15 @@ Moștenirea și reutilizarea șabloanelor ************************************** .[perex] -Mecanismele de reutilizare și de moștenire a șabloanelor au rolul de a vă spori productivitatea, deoarece fiecare șablon conține doar conținutul său unic, iar elementele și structurile repetate sunt reutilizate. Prezentăm trei concepte: [moștenirea aspectului |#layout inheritance], [reutilizarea orizontală |#horizontal reuse] și [moștenirea unitară |#unit inheritance]. +Mecanismele de reutilizare și moștenire a șabloanelor vă vor crește productivitatea, deoarece fiecare șablon conține doar conținutul său unic, iar elementele și structurile repetitive sunt reutilizate. Vă prezentăm trei concepte: [Moștenirea layout-ului |#Moștenirea layout-ului layout], [Reutilizarea orizontală |#Reutilizarea orizontală import] și [Moștenirea unitară |#Moștenirea unitară embed]. -Conceptul de moștenire a șabloanelor Latte este similar cu moștenirea claselor PHP. Definiți un **șablon părinte** pe care alte **șabloane copii** îl pot extinde și pot suprascrie părți ale șablonului părinte. Funcționează foarte bine atunci când elementele au o structură comună. Sună complicat? Nu vă faceți griji, nu este. +Conceptul de moștenire a șabloanelor Latte este similar cu moștenirea claselor în PHP. Definiți un **șablon părinte**, de la care pot moșteni alte **șabloane copil** și pot suprascrie părți ale șablonului părinte. Funcționează excelent atunci când elementele partajează o structură comună. Sună complicat? Nu vă faceți griji, este foarte ușor. -Moștenirea aspectului `{layout}` .{toc: Layout Inheritance} -=========================================================== +Moștenirea layout-ului `{layout}` +================================= -Să analizăm moștenirea șablonului de aspect pornind de la un exemplu. Acesta este un șablon părinte pe care îl vom numi, de exemplu, `layout.latte` și definește un document HTML schelet. +Să vedem moștenirea șablonului de layout, adică a layout-ului, direct printr-un exemplu. Acesta este șablonul părinte, pe care îl vom numi, de exemplu, `layout.latte` și care definește scheletul documentului HTML: ```latte @@ -30,36 +30,36 @@ Să analizăm moștenirea șablonului de aspect pornind de la un exemplu. Acesta ``` -Etichetele `{block}` definesc trei blocuri pe care șabloanele copil le pot completa. Tot ceea ce face tag-ul block este să spună motorului de șabloane că un șablon copil poate înlocui aceste părți ale șablonului prin definirea propriului bloc cu același nume. +Tag-urile `{block}` definesc trei blocuri pe care șabloanele copil le pot completa. Tag-ul block face doar atât: anunță că acest loc poate fi suprascris de șablonul copil prin definirea propriului bloc cu același nume. -Un șablon copil ar putea arăta astfel: +Un șablon copil poate arăta astfel: ```latte {layout 'layout.latte'} -{block title}My amazing blog{/block} +{block title}Blogul meu uimitor{/block} {block content} -

                                                                                                            Welcome to my awesome homepage.

                                                                                                            +

                                                                                                            Bun venit pe pagina mea minunată.

                                                                                                            {/block} ``` -Eticheta `{layout}` este cheia aici. Aceasta indică motorului de șabloane că acest șablon "extinde" un alt șablon. Când Latte redă acest șablon, mai întâi localizează părintele - în acest caz, `layout.latte`. +Cheia aici este tag-ul `{layout}`. Acesta îi spune lui Latte că acest șablon „extinde” un alt șablon. Când Latte redă acest șablon, mai întâi găsește șablonul părinte - în acest caz `layout.latte`. -În acel moment, motorul șablonului va observa cele trei etichete de bloc din `layout.latte` și va înlocui acele blocuri cu conținutul șablonului copil. Rețineți că, deoarece șablonul copil nu a definit blocul *footer*, se utilizează în schimb conținutul șablonului părinte. Conținutul din cadrul unei etichete `{block}` dintr-un șablon părinte este întotdeauna utilizat ca soluție de rezervă. +În acest moment, Latte observă cele trei tag-uri block în `layout.latte` și înlocuiește aceste blocuri cu conținutul șablonului copil. Deoarece șablonul copil nu a definit blocul *footer*, se va folosi în schimb conținutul din șablonul părinte. Conținutul din tag-ul `{block}` în șablonul părinte este întotdeauna folosit ca alternativă. -Rezultatul ar putea arăta astfel: +Outputul poate arăta astfel: ```latte - My amazing blog + Blogul meu uimitor
                                                                                                            -

                                                                                                            Welcome to my awesome homepage.

                                                                                                            +

                                                                                                            Bun venit pe pagina mea minunată.

                                                                                                            ``` - -{{leftbar: /@left-menu}} diff --git a/latte/ru/cookbook/migration-from-twig.texy b/latte/ru/cookbook/migration-from-twig.texy index 3b42574a70..19ec6e9c89 100644 --- a/latte/ru/cookbook/migration-from-twig.texy +++ b/latte/ru/cookbook/migration-from-twig.texy @@ -2,37 +2,37 @@ ************************ .[perex] -Вы переносите проект, написанный на Twig, на более современный Latte? У нас есть инструмент для облегчения миграции. [Попробуйте его онлайн |https://twig2latte.nette.org]. +Преобразуете проект, написанный на Twig, в более современный Latte? У нас есть инструмент, который облегчит вам миграцию. [Попробуйте его онлайн |https://fiddle.nette.org/twig2latte/]. -Вы можете скачать инструмент с [GitHub |https://github.com/nette/latte-tools] или установить его с помощью Composer: +Инструмент можно скачать с [GitHub|https://github.com/nette/latte-tools] или установить с помощью Composer: ```shell composer create-project latte/tools ``` -Конвертер не использует простые подстановки регулярных выражений, вместо этого он использует непосредственно парсер Twig, поэтому он может обрабатывать любой сложный синтаксис. +Конвертер не использует простые замены с помощью регулярных выражений, наоборот, он использует непосредственно парсер Twig, так что справится с синтаксисом любой сложности. -Для конвертации из Twig в Latte используется скрипт `twig-to-latte.php`: +Для преобразования из Twig в Latte служит скрипт `twig-to-latte.php`: ```shell -twig-to-latte.php input.twig.html [output.latte] +php twig-to-latte.php input.twig.html [output.latte] ``` -Конвертация .[#toc-conversion] ------------------------------- +Конвертация +----------- -Преобразование требует ручного редактирования результата, поскольку преобразование не может быть выполнено однозначно. В Twig используется точечный синтаксис, где `{{ a.b }}` может означать `$a->b`, `$a['b']` или `$a->getB()`, которые невозможно различить во время компиляции. Поэтому конвертер преобразует все в `$a->b`. +Преобразование предполагает ручную правку результата, так как конвертацию нельзя выполнить однозначно. Twig использует точечный синтаксис, где `{{ a.b }}` может означать `$a->b`, `$a['b']` или `$a->getB()`, что нельзя различить при компиляции. Конвертер поэтому все преобразует в `$a->b`. -Некоторые функции, фильтры или теги не имеют эквивалента в Latte или могут вести себя несколько иначе. +Некоторые функции, фильтры или теги не имеют аналога в Latte, или могут вести себя немного иначе. -Пример .[#toc-example] ----------------------- +Пример +------ -Входной файл может выглядеть следующим образом: +Входной файл может выглядеть, например, так: -```latte +```twig {% use "blocks.twig" %} @@ -54,7 +54,7 @@ twig-to-latte.php input.twig.html [output.latte] ``` -После преобразования в Latte мы получим этот шаблон: +После конвертации в Latte мы получим этот шаблон: ```latte {import 'blocks.latte'} @@ -77,5 +77,3 @@ twig-to-latte.php input.twig.html [output.latte] ``` - -{{leftbar: /@left-menu}} diff --git a/latte/ru/cookbook/passing-variables.texy b/latte/ru/cookbook/passing-variables.texy new file mode 100644 index 0000000000..694694f73e --- /dev/null +++ b/latte/ru/cookbook/passing-variables.texy @@ -0,0 +1,158 @@ +Передача переменных между шаблонами +*********************************** + +Это руководство объяснит вам, как переменные передаются между шаблонами в Latte с помощью различных тегов, таких как `{include}`, `{import}`, `{embed}`, `{layout}`, `{sandbox}` и других. Вы также узнаете, как работать с переменными в тегах `{block}` и `{define}`, и для чего служит тег `{parameters}`. + + +Типы переменных +--------------- +Переменные в Latte можно разделить на три категории в зависимости от того, как и где они определены: + +**Входные переменные** — это те, которые передаются в шаблон извне, например, из PHP-скрипта или с помощью тега, такого как `{include}`. + +```php +$latte->render('template.latte', ['userName' => 'Ян', 'userAge' => 30]); +``` + +**Окружающие переменные** — это переменные, существующие в месте определенного тега. Они включают все входные переменные и другие переменные, созданные с помощью тегов, таких как `{var}`, `{default}` или в рамках цикла `{foreach}`. + +```latte +{foreach $users as $user} + {include 'userBox.latte', user: $user} +{/foreach} +``` + +**Явные переменные** — это те, которые прямо указаны внутри тега и отправляются в целевой шаблон. + +```latte +{include 'userBox.latte', name: $user->name, age: $user->age} +``` + + +`{block}` +--------- +Тег `{block}` используется для определения повторно используемых блоков кода, которые можно настроить или расширить в наследуемых шаблонах. Окружающие переменные, определенные перед блоком, доступны внутри блока, но любые изменения переменных проявятся только в рамках этого блока. + +```latte +{var $foo = 'оригинальный'} +{block example} + {var $foo = 'измененный'} +{/block} + +{$foo} // выведет: оригинальный +``` + + +`{define}` +---------- +Тег `{define}` служит для создания блоков, которые рендерятся только после их вызова с помощью `{include}`. Переменные, доступные внутри этих блоков, зависят от того, указаны ли параметры в определении. Если да, доступ есть только к этим параметрам. Если нет, доступ есть ко всем входным переменным шаблона, в котором определены блоки. + +```latte +{define hello} + {* имеет доступ ко всем входным переменным шаблона *} +{/define} + +{define hello $name} + {* имеет доступ только к параметру $name *} +{/define} +``` + + +`{parameters}` +-------------- +Тег `{parameters}` служит для явной декларации ожидаемых входных переменных в начале шаблона. Таким образом можно легко документировать ожидаемые переменные и их типы данных. Также возможно определить значения по умолчанию. + +```latte +{parameters int $age, string $name = 'неизвестно'} +

                                                                                                            Возраст: {$age}, Имя: {$name}

                                                                                                            +``` + + +`{include file}` +---------------- +Тег `{include file}` служит для вставки всего шаблона. Этому шаблону передаются как входные переменные шаблона, в котором используется тег, так и переменные, явно определенные в нем. Целевой шаблон, однако, может ограничить область видимости с помощью `{parameters}`. + +```latte +{include 'profile.latte', userId: $user->id} +``` + + +`{include block}` +----------------- +Когда вы вставляете блок, определенный в том же шаблоне, в него передаются все окружающие и явно определенные переменные: + +```latte +{define blockName} +

                                                                                                            Имя: {$name}, Возраст: {$age}

                                                                                                            +{/define} + +{var $name = 'Ян', $age = 30} +{include blockName} +``` + +В этом примере переменные `$name` и `$age` передаются в блок `blockName`. Таким же образом ведет себя и `{include parent}`. + +При вставке блока из другого шаблона передаются только входные и явно определенные переменные. Окружающие переменные автоматически недоступны. + +```latte +{include blockInOtherTemplate, name: $name, age: $age} +``` + + +`{layout}` или `{extends}` +-------------------------- +Эти теги определяют макет, в который передаются входные переменные дочернего шаблона, а также переменные, созданные в коде перед блоками: + +```latte +{layout 'layout.latte'} +{var $seo = 'index, follow'} +``` + +Шаблон `layout.latte`: + +```latte + + + +``` + + +`{embed}` +--------- +Тег `{embed}` похож на тег `{include}`, но позволяет вставлять блоки в шаблон. В отличие от `{include}`, передаются только явно объявленные переменные: + +```latte +{embed 'menu.latte', items: $menuItems} +{/embed} +``` + +В этом примере шаблон `menu.latte` имеет доступ только к переменной `$items`. + +Наоборот, в блоках внутри `{embed}` есть доступ ко всем окружающим переменным: + +```latte +{var $name = 'Ян'} +{embed 'menu.latte', items: $menuItems} + {block foo} + {$name} + {/block} +{/embed} +``` + + +`{import}` +---------- +Тег `{import}` используется для загрузки блоков из других шаблонов. Передаются как входные, так и явно объявленные переменные в импортированные блоки. + +```latte +{import 'buttons.latte'} +``` + + +`{sandbox}` +----------- +Тег `{sandbox}` изолирует шаблон для безопасной обработки. Переменные передаются исключительно явно. + +```latte +{sandbox 'secure.latte', data: $secureData} +``` diff --git a/latte/ru/cookbook/slim-framework.texy b/latte/ru/cookbook/slim-framework.texy index bdac358bcd..be54426a45 100644 --- a/latte/ru/cookbook/slim-framework.texy +++ b/latte/ru/cookbook/slim-framework.texy @@ -1,8 +1,8 @@ -Использование Latte с Slim 4 -**************************** +Использование Latte со Slim 4 +***************************** .[perex] -Эта статья, написанная "Daniel Opitz":https://odan.github.io/2022/04/06/slim4-latte.html, описывает использование Latte с Slim Framework. +Эта статья, автором которой является "Daniel Opitz":https://odan.github.io/2022/04/06/slim4-latte.html, описывает использование Latte со Slim Framework. Сначала "установите Slim Framework":https://odan.github.io/2019/11/05/slim4-tutorial.html, а затем Latte с помощью Composer: @@ -11,33 +11,33 @@ composer require latte/latte ``` -Конфигурация .[#toc-configuration] ----------------------------------- +Конфигурация +------------ -Создайте новый каталог `templates` в корневом каталоге вашего проекта. Все шаблоны будут помещены туда позже. +В корневом каталоге проекта создайте новый каталог `templates`. Все шаблоны будут размещены в нем позже. -Добавьте новый ключ конфигурации `template` в ваш файл `config/defaults.php`: +В файл `config/defaults.php` добавьте новый ключ конфигурации `template`: ```php $settings['template'] = __DIR__ . '/../templates'; ``` -Latte компилирует шаблоны в собственный PHP-код и хранит их в кэше на диске. Таким образом, они работают так же быстро, как если бы были написаны на родном PHP. +Latte компилирует шаблоны в нативный PHP-код и сохраняет их в кеше на диске. Поэтому они так же быстры, как если бы были написаны на нативном PHP. -Добавьте новый ключ конфигурации `template_temp` в файл `config/defaults.php`: Убедитесь, что каталог `{project}/tmp/templates` существует и имеет права доступа на чтение и запись. +В файл `config/defaults.php` добавьте новый ключ конфигурации `template_temp`: Убедитесь, что каталог `{project}/tmp/templates` существует и имеет права на чтение и запись. ```php $settings['template_temp'] = __DIR__ . '/../tmp/templates'; ``` -Latte автоматически регенерирует кэш при каждом изменении шаблона, что можно отключить в производственной среде, чтобы сэкономить немного производительности: +Latte автоматически регенерирует кеш при каждом изменении шаблона, что можно отключить в production-среде и сэкономить немного производительности: ```php -// change to false in the production environment +// в production-среде измените на false $settings['template_auto_refresh'] = true; ``` -Далее добавьте определения контейнера DI для класса `Latte\Engine`. +Далее добавьте определение DI-контейнера для класса `Latte\Engine`. ```php +
                                                                                                              {foreach $items as $item}
                                                                                                            • {$item|capitalize}
                                                                                                            • {/foreach}
                                                                                                            ``` -Если все настроено правильно, вы должны увидеть следующий результат: +Если все правильно сконфигурировано, должен отобразиться следующий вывод: ```latte One @@ -155,4 +155,3 @@ Three ``` {{priority: -1}} -{{leftbar: /@left-menu}} diff --git a/latte/ru/creating-extension.texy b/latte/ru/creating-extension.texy deleted file mode 100644 index e5b5cbc606..0000000000 --- a/latte/ru/creating-extension.texy +++ /dev/null @@ -1,579 +0,0 @@ -Создание расширения -******************* - -.[perex]{data-version:3.0} -Расширение - это многократно используемый класс, который может определять пользовательские теги, фильтры, функции, провайдеры и т.д. - -Мы создаем расширения, когда хотим повторно использовать наши настройки Latte в различных проектах или поделиться ими с другими. -Также полезно создавать расширение для каждого веб-проекта, которое будет содержать все специфические теги и фильтры, которые вы хотите использовать в шаблонах проекта. - - -Класс расширения .[#toc-extension-class] -======================================== - -Extension - это класс, наследующий от [api:Latte\Extension]. Он регистрируется в Latte с помощью `addExtension()` (или через [конфигурационный файл |application:configuration#Latte]): - -```php -$latte = new Latte\Engine; -$latte->addExtension(new MyLatteExtension); -``` - -Если вы зарегистрировали несколько расширений и они определяют одинаково названные теги, фильтры или функции, побеждает последнее добавленное расширение. Это также подразумевает, что ваши расширения могут переопределять собственные теги/фильтры/функции. - -Всякий раз, когда вы вносите изменения в класс и автообновление не выключено, Latte автоматически перекомпилирует ваши шаблоны. - -Класс может реализовывать любой из следующих методов: - -```php -abstract class Extension -{ - /** - * Initializes before template is compiler. - */ - public function beforeCompile(Engine $engine): void; - - /** - * Returns a list of parsers for Latte tags. - * @return array - */ - public function getTags(): array; - - /** - * Returns a list of compiler passes. - * @return array - */ - public function getPasses(): array; - - /** - * Returns a list of |filters. - * @return array - */ - public function getFilters(): array; - - /** - * Returns a list of functions used in templates. - * @return array - */ - public function getFunctions(): array; - - /** - * Returns a list of providers. - * @return array - */ - public function getProviders(): array; - - /** - * Returns a value to distinguish multiple versions of the template. - */ - public function getCacheKey(Engine $engine): mixed; - - /** - * Initializes before template is rendered. - */ - public function beforeRender(Template $template): void; -} -``` - -Чтобы получить представление о том, как выглядит расширение, посмотрите на встроенное "CoreExtension":https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php. - - -beforeCompile(Latte\Engine $engine): void .[method] ---------------------------------------------------- - -Вызывается перед компиляцией шаблона. Метод может использоваться, например, для инициализации, связанной с компиляцией. - - -getTags(): array .[method] --------------------------- - -Вызывается при компиляции шаблона. Возвращает ассоциативный массив *имя тега => callable*, которые являются [функциями разбора тегов |#Tag-Parsing-Function]. - -```php -public function getTags(): array -{ - return [ - 'foo' => [FooNode::class, 'create'], - 'bar' => [BarNode::class, 'create'], - 'n:baz' => [NBazNode::class, 'create'], - // ... - ]; -} -``` - -Тег `n:baz` представляет собой чистый n:attribute, т.е. это тег, который может быть записан только как атрибут. - -В случае тегов `foo` и `bar` Latte автоматически распознает, являются ли они парами, и если да, то они могут быть автоматически записаны с использованием n:атрибутов, включая варианты с префиксами `n:inner-foo` и `n:tag-foo`. - -Порядок выполнения таких n:атрибутов определяется их порядком в массиве, возвращаемом `getTags()`. Таким образом, `n:foo` всегда выполняется перед `n:bar`, даже если атрибуты перечислены в обратном порядке в HTML-теге как `
                                                                                                            `. - -Если вам нужно определить порядок выполнения n:атрибутов для нескольких расширений, используйте вспомогательный метод `order()`, где параметр `before` xor `after` определяет, какие теги будут упорядочены до или после тега . - -```php -public function getTags(): array -{ - return [ - 'foo' => self::order([FooNode::class, 'create'], before: 'bar')] - 'bar' => self::order([BarNode::class, 'create'], after: ['block', 'snippet'])] - ]; -} -``` - - -getPasses(): array .[method] ----------------------------- - -Вызывается при компиляции шаблона. Возвращает ассоциативный массив *name pass => callable*, которые являются функциями, представляющими так называемые [проходы компилятора |#Compiler-Passes], которые обходят и изменяют AST. - -Опять же, может быть использован вспомогательный метод `order()`. Значением параметров `before` или `after` может быть `*` со значением before/after all. - -```php -public function getPasses(): array -{ - return [ - 'optimize' => [Passes::class, 'optimizePass'], - 'sandbox' => self::order([$this, 'sandboxPass'], before: '*'), - // ... - ]; -} -``` - - -beforeRender(Latte\Engine $engine): void .[method] --------------------------------------------------- - -Вызывается перед каждым рендерингом шаблона. Метод можно использовать, например, для инициализации переменных, используемых во время рендеринга. - - -getFilters(): array .[method] ------------------------------ - -Вызывается перед отрисовкой шаблона. Возвращает [фильтры |extending-latte#Filters] в виде ассоциативного массива *имя фильтра => вызываемый*. - -```php -public function getFilters(): array -{ - return [ - 'batch' => [$this, 'batchFilter'], - 'trim' => [$this, 'trimFilter'], - // ... - ]; -} -``` - - -getFunctions(): array .[method] -------------------------------- - -Вызывается перед отрисовкой шаблона. Возвращает [функции |extending-latte#Functions] в виде ассоциативного массива *имя функции => callable*. - -```php -public function getFunctions(): array -{ - return [ - 'clamp' => [$this, 'clampFunction'], - 'divisibleBy' => [$this, 'divisibleByFunction'], - // ... - ]; -} -``` - - -getProviders(): array .[method] -------------------------------- - -Вызывается перед отрисовкой шаблона. Возвращает массив провайдеров, которые обычно являются объектами, использующими теги во время выполнения. Доступ к ним осуществляется через `$this->global->...`. - -```php -public function getProviders(): array -{ - return [ - 'myFoo' => $this->foo, - 'myBar' => $this->bar, - // ... - ]; -} -``` - - -getCacheKey(Latte\Engine $engine): mixed .[method] --------------------------------------------------- - -Вызывается перед отрисовкой шаблона. Возвращаемое значение становится частью ключа, хэш которого содержится в имени скомпилированного файла шаблона. Таким образом, для разных возвращаемых значений Latte будет генерировать разные файлы кэша. - - -Как работает Latte? .[#toc-how-does-latte-work] -=============================================== - -Чтобы понять, как определить пользовательские теги или передачи компилятора, необходимо понять, как Latte работает под капотом. - -Компиляция шаблонов в Latte упрощенно работает следующим образом: - -- Сначала **лексор** разбивает исходный код шаблона на небольшие фрагменты (лексемы) для более удобной обработки. -- Затем **парсер** преобразует поток лексем в осмысленное дерево узлов (Abstract Syntax Tree, AST). -- Наконец, компилятор **генерирует** класс PHP из AST, который отображает шаблон и сохраняет его в кэше. - -На самом деле, компиляция немного сложнее. У Latte **есть два** лексера и парсера: один для HTML-шаблона, другой для PHP-подобного кода внутри тегов. Кроме того, парсинг не выполняется после токенизации, а лексер и парсер работают параллельно в двух "потоках" и координируются. Это ракетостроение :-) - -Более того, все теги имеют свои собственные процедуры синтаксического анализа. Когда парсер встречает тег, он вызывает свою функцию разбора (она возвращает [Extension::getTags() |#getTags]). -Ее работа заключается в разборе аргументов тега и, в случае парных тегов, внутреннего содержимого. Она возвращает *узел*, который становится частью AST. Подробности см. в разделе [Функция разбора тегов |#Tag-Parsing-Function]. - -Когда парсер завершает свою работу, мы получаем полный AST, представляющий шаблон. Корневым узлом является `Latte\Compiler\Nodes\TemplateNode`. Отдельные узлы внутри дерева представляют не только теги, но и элементы HTML, их атрибуты, любые выражения, используемые внутри тегов, и т. д. - -После этого в игру вступают так называемые [проходы компилятора |#Compiler-Passes], которые представляют собой функции (возвращаемые [Extension::getPasses() |#getPasses]), изменяющие AST. - -Весь процесс, от загрузки содержимого шаблона, парсинга до генерации результирующего файла, может быть упорядочен с помощью этого кода, с которым вы можете экспериментировать и сбрасывать промежуточные результаты: - -```php -$latte = new Latte\Engine; -$source = $latte->getLoader()->getContent($file); -$ast = $latte->parse($source); -$latte->applyPasses($ast); -$code = $latte->generate($ast, $file); -``` - - -Пример AST .[#toc-example-of-ast] ---------------------------------- - -Чтобы получить лучшее представление об AST, мы добавим пример. Это исходный шаблон: - -```latte -{foreach $category->getItems() as $item} -
                                                                                                          • {$item->name|upper}
                                                                                                          • - {else} - no items found -{/foreach} -``` - -А это его представление в виде AST: - -/--pre -Latte\Compiler\Nodes\TemplateNode( - Latte\Compiler\Nodes\FragmentNode( - - Latte\Essential\Nodes\ForeachNode( - expression: Latte\Compiler\Nodes\Php\Expression\MethodCallNode( - object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$category') - name: Latte\Compiler\Nodes\Php\IdentifierNode('getItems') - ) - value: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') - content: Latte\Compiler\Nodes\FragmentNode( - - Latte\Compiler\Nodes\TextNode(' ') - - Latte\Compiler\Nodes\Html\ElementNode('li')( - content: Latte\Essential\Nodes\PrintNode( - expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode( - object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') - name: Latte\Compiler\Nodes\Php\IdentifierNode('name') - ) - modifier: Latte\Compiler\Nodes\Php\ModifierNode( - filters: - - Latte\Compiler\Nodes\Php\FilterNode('upper') - ) - ) - ) - ) - else: Latte\Compiler\Nodes\FragmentNode( - - Latte\Compiler\Nodes\TextNode('no items found') - ) - ) - ) -) -\-- - - -Пользовательские теги .[#toc-custom-tags] -========================================= - -Для определения нового тега необходимо выполнить три шага: - -- определение [функции разбора тега |#Tag-Parsing-Function] (отвечает за разбор тега в узел) -- создание класса узла (отвечает за [генерацию PHP-кода |#Generating-PHP-Code] и [обход AST |#AST-Traversing]) -- регистрация тега с помощью [Extension::getTags() |#getTags] - - -Функция разбора тега .[#toc-tag-parsing-function] -------------------------------------------------- - -Разбор тегов выполняется функцией разбора (та, которая возвращается функцией [Extension::getTags() |#getTags]). Ее задача - разобрать и проверить все аргументы внутри тега (для этого она использует TagParser). -Кроме того, если тег является парой, она попросит TemplateParser разобрать и вернуть внутреннее содержимое. -Функция создает и возвращает узел, который обычно является дочерним узлом `Latte\Compiler\Nodes\StatementNode`, и он становится частью AST. - -Мы создаем класс для каждого узла, что мы сейчас и сделаем, и элегантно помещаем в него функцию парсинга в виде статической фабрики. В качестве примера попробуем создать знакомый тег `{foreach}`: - -```php -use Latte\Compiler\Nodes\StatementNode; - -class ForeachNode extends StatementNode -{ - // a parsing function that just creates a node for now - public static function create(Latte\Compiler\Tag $tag): self - { - $node = new self; - return $node; - } - - public function print(Latte\Compiler\PrintContext $context): string - { - // code will be added later - } - - public function &getIterator(): \Generator - { - // code will be added later - } -} -``` - -Функции парсинга `create()` передается объект [api:Latte\Compiler\Tag], который несет основную информацию о теге (является ли он классическим тегом или n:attribute, на какой строке он находится и т.д.) и в основном обращается к объекту [api:Latte\Compiler\TagParser] в `$tag->parser`. - -Если тег должен иметь аргументы, проверьте их наличие, вызвав `$tag->expectArguments()`. Для их разбора доступны методы объекта `$tag->parser`: - -- `parseExpression(): ExpressionNode` для PHP-подобного выражения (например, `10 + 3`). -- `parseUnquotedStringOrExpression(): ExpressionNode` для выражения или строки без кавычек -- `parseArguments(): ArrayNode` содержимое массива (например, `10, true, foo => bar`) -- `parseModifier(): ModifierNode` для модификатора (например, `|upper|truncate:10`) -- `parseType(): expressionNode` для подсказки типа (например, `int|string` или `Foo\Bar[]`) - -и низкоуровневый [api:Latte\Compiler\TokenStream], работающий непосредственно с лексемами: - -- `$tag->parser->stream->consume(...): Token` -- `$tag->parser->stream->tryConsume(...): ?Token` - -Latte расширяет синтаксис PHP небольшими способами, например, добавляя модификаторы, сокращенные троичные операторы или позволяя записывать простые буквенно-цифровые строки без кавычек. Именно поэтому мы используем термин *PHP-подобный* вместо PHP. Так, например, метод `parseExpression()` анализирует `foo` как `'foo'`. -Кроме того, *unquoted-string* - это особый случай строки, которая также не нуждается в кавычках, но в то же время не обязательно должна быть буквенно-цифровой. Например, это путь к файлу в теге `{include ../file.latte}`. Для его разбора используется метод `parseUnquotedStringOrExpression()`. - -.[note] -Изучение классов узлов, входящих в состав Latte, - лучший способ узнать все тонкости процесса разбора. - -Давайте вернемся к тегу `{foreach}`. В нем мы ожидаем аргументы вида `expression + 'as' + second expression`, которые мы разбираем следующим образом: - -```php -use Latte\Compiler\Nodes\StatementNode; -use Latte\Compiler\Nodes\Php\ExpressionNode; -use Latte\Compiler\Nodes\AreaNode; - -class ForeachNode extends StatementNode -{ - public ExpressionNode $expression; - public ExpressionNode $value; - - public static function create(Latte\Compiler\Tag $tag): self - { - $tag->expectArguments(); - $node = new self; - $node->expression = $tag->parser->parseExpression(); - $tag->parser->stream->consume('as'); - $node->value = $parser->parseExpression(); - return $node; - } -} -``` - -Выражения, которые мы записали в переменные `$expression` и `$value`, представляют собой вложенные узлы. - -.[tip] -Определите переменные с подузлами как **публичные**, чтобы при необходимости их можно было изменить на [последующих этапах обработки |#Compiler-Passes]. Также необходимо **сделать их доступными** для [обхода |#AST-Traversing]. - -Для парных тегов, таких как наш, метод должен также позволить TemplateParser разобрать внутреннее содержимое тега. Этим занимается `yield`, который возвращает пару ''[внутреннее содержимое, конечный тег]''. Мы храним внутреннее содержимое в переменной `$node->content`. - -```php -public AreaNode $content; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $endTag] = yield; - return $node; -} -``` - -Ключевое слово `yield` вызывает завершение метода `create()`, возвращая управление обратно в TemplateParser, который продолжает разбор содержимого, пока не достигнет конечного тега. Затем он передает управление обратно методу `create()`, который продолжает с того места, на котором остановился. Использование метода `yield`, автоматически возвращает `Generator`. - -Вы также можете передать в `yield` массив имен тегов, для которых вы хотите остановить разбор, если они встречаются до конечного тега. Это помогает нам реализовать `{foreach}...{else}...{/foreach}` конструкцию. Если встречается `{else}`, мы разбираем содержимое после него в `$node->elseContent`: - -```php -public AreaNode $content; -public ?AreaNode $elseContent = null; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $nextTag] = yield ['else']; - if ($nextTag?->name === 'else') { - [$node->elseContent] = yield; - } - - return $node; -} -``` - -Возвращающийся узел завершает разбор тега. - - -Генерация PHP-кода .[#toc-generating-php-code] ----------------------------------------------- - -Каждый узел должен реализовать метод `print()`. Возвращает PHP-код, который рендерит заданную часть шаблона (runtime-код). В качестве параметра ему передается объект [api:Latte\Compiler\PrintContext], который имеет полезный метод `format()`, упрощающий сборку результирующего кода. - -Метод `format(string $mask, ...$args)` принимает следующие заполнители в маске: -- `%node` печатает Node -- `%dump` экспортирует значение в PHP -- `%raw` вставляет текст напрямую без каких-либо преобразований -- `%args` печатает ArrayNode в качестве аргументов вызова функции -- `%line` печатает комментарий с номером строки -- `%escape(...)` экранирует содержимое -- `%modify(...)` применяет модификатор -- `%modifyContent(...)` применяет модификатор к блокам - - -Наша функция `print()` может выглядеть следующим образом (для простоты мы пренебрегаем ветвью `else` ): - -```php -public function print(Latte\Compiler\PrintContext $context): string -{ - return $context->format( - <<<'XX' - foreach (%node as %node) %line { - %node - } - - XX, - $this->expression, - $this->value, - $this->position, - $this->content, - ); -} -``` - -Переменная `$this->position` уже определена классом [api:Latte\Compiler\Node] и устанавливается парсером. Она содержит объект [api:Latte\Compiler\Position] с позицией тега в исходном коде в виде номера строки и столбца. - -Код времени выполнения может использовать вспомогательные переменные. Чтобы избежать столкновения с переменными, используемыми самим шаблоном, принято префиксировать их символами `$ʟ__`. - -Также во время выполнения может использоваться произвольные значения, которые передаются шаблону в виде провайдеров с помощью метода [Extension::getProviders() |#getProviders]. Доступ к ним осуществляется с помощью `$this->global->...`. - - -Обход AST .[#toc-ast-traversing] --------------------------------- - -Для того чтобы просмотреть дерево AST вглубь, необходимо реализовать метод `getIterator()`. Это обеспечит доступ к вложенным узлам: - -```php -public function &getIterator(): \Generator -{ - yield $this->expression; - yield $this->value; - yield $this->content; - if ($this->elseContent) { - yield $this->elseContent; - } -} -``` - -Обратите внимание, что `getIterator()` возвращает ссылку. Именно это позволяет посетителям узла заменять отдельные узлы другими узлами. - -.[warning] -Если узел имеет подузлы, необходимо реализовать этот метод и сделать доступными все подузлы. В противном случае может быть создана брешь в безопасности. Например, режим песочницы не сможет контролировать подноды и гарантировать, что в них не будут вызываться неразрешенные конструкции. - -Поскольку ключевое слово `yield` должно присутствовать в теле метода, даже если у него нет дочерних узлов, запишите его следующим образом: - -```php -public function &getIterator(): \Generator -{ - if (false) { - yield; - } -} -``` - - -Компилятор передает .[#toc-compiler-passes] -=========================================== - -Пассы компилятора - это функции, которые изменяют AST или собирают информацию в них. Они возвращаются методом [Extension::getPasses() |#getPasses]. - - -Траверсер узлов .[#toc-node-traverser] --------------------------------------- - -Наиболее распространенным способом работы с AST является использование [api:Latte\Compiler\NodeTraverser]: - -```php -use Latte\Compiler\Node; -use Latte\Compiler\NodeTraverser; - -$ast = (new NodeTraverser)->traverse( - $ast, - enter: fn(Node $node) => ..., - leave: fn(Node $node) => ..., -); -``` - -Функция *enter* (т.е. посетитель) вызывается при первой встрече с узлом, до того, как будут обработаны его подузлы. Функция *leave* вызывается после посещения всех подузлов. -Общим шаблоном является то, что *enter* используется для сбора некоторой информации, а затем *leave* выполняет модификации на основе этой информации. К моменту вызова *leave* весь код внутри узла уже будет посещен и собрана необходимая информация. - -Как модифицировать AST? Самый простой способ - просто изменить свойства узлов. Второй способ - полностью заменить узел, вернув новый узел. Пример: следующий код изменит все целые числа в AST на строки (например, 42 будет заменено на `'42'`). - -```php -use Latte\Compiler\Nodes\Php; - -$ast = (new NodeTraverser)->traverse( - $ast, - leave: function (Node $node) { - if ($node instanceof Php\Scalar\IntegerNode) { - return new Php\Scalar\StringNode((string) $node->value); - } - }, -); -``` - -AST может содержать тысячи узлов, и обход всех узлов может быть медленным. В некоторых случаях можно обойтись без полного обхода. - -Если вы ищете все `Html\ElementNode` в дереве, вы знаете, что после просмотра `Php\ExpressionNode` нет смысла проверять все его дочерние узлы, потому что HTML не может быть внутри выражений. В этом случае вы можете указать обходчику не переходить к узлу класса: - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Php\ExpressionNode) { - return NodeTraverser::DontTraverseChildren; - } - // ... - }, -); -``` - -Если вы ищете только один конкретный узел, можно также полностью прервать обход после его нахождения. - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Nodes\ParametersNode) { - return NodeTraverser::StopTraversal; - } - // ... - }, -); -``` - - -Помощники узлов .[#toc-node-helpers] ------------------------------------- - -Класс [api:Latte\Compiler\NodeHelpers] предоставляет несколько методов, которые могут найти AST-узлы, удовлетворяющие определенному обратному вызову и т.д. Показана пара примеров: - -```php -use Latte\Compiler\NodeHelpers; - -// finds all HTML element nodes -$elements = NodeHelpers::find($ast, fn(Node $node) => $node instanceof Nodes\Html\ElementNode); - -// finds first text node -$text = NodeHelpers::findFirst($ast, fn(Node $node) => $node instanceof Nodes\TextNode); - -// converts PHP value node to real value -$value = NodeHelpers::toValue($node); - -// converts static textual node to string -$text = NodeHelpers::toText($node); -``` diff --git a/latte/ru/custom-filters.texy b/latte/ru/custom-filters.texy new file mode 100644 index 0000000000..2f8bf84bd1 --- /dev/null +++ b/latte/ru/custom-filters.texy @@ -0,0 +1,231 @@ +Создание пользовательских фильтров +********************************** + +.[perex] +Фильтры — это мощные инструменты для форматирования и изменения данных непосредственно в шаблонах Latte. Они предлагают чистый синтаксис с использованием символа пайпа (`|`) для преобразования переменных или результатов выражений в желаемый выходной формат. + + +Что такое фильтры? +================== + +Фильтры в Latte — это, по сути, **PHP-функции, разработанные специально для преобразования входного значения в выходное**. Они применяются с помощью записи с пайпом (`|`) внутри выражений шаблона (`{...}`). + +**Удобство:** Фильтры позволяют инкапсулировать распространенные задачи форматирования (например, форматирование дат, изменение регистра, усечение) или манипуляции с данными в повторно используемые единицы. Вместо повторения сложного PHP-кода в ваших шаблонах вы можете просто применить фильтр: +```latte +{* Вместо сложного PHP для усечения: *} +{$article->text|truncate:100} + +{* Вместо кода для форматирования даты: *} +{$event->startTime|date:'Y-m-d H:i'} + +{* Применение нескольких преобразований: *} +{$product->name|lower|capitalize} +``` + +**Читаемость:** Использование фильтров делает шаблоны более понятными и более ориентированными на представление, поскольку логика преобразования перемещается в определение фильтра. + +**Контекстная чувствительность:** Ключевым преимуществом фильтров в Latte является их способность быть [контекстно-зависимыми |#Контекстные фильтры]. Это означает, что фильтр может распознавать тип содержимого, с которым он работает (HTML, JavaScript, простой текст и т. д.), и применять соответствующую логику или экранирование, что критически важно для безопасности и правильности, особенно при генерации HTML. + +**Интеграция с логикой приложения:** Как и пользовательские функции, вызываемый PHP-объект, стоящий за фильтром, может быть замыканием (closure), статическим методом или методом экземпляра. Это позволяет фильтрам получать доступ к сервисам приложения или данным, если это необходимо, хотя их основной целью остается *преобразование входного значения*. + +Latte по умолчанию предоставляет богатый набор [стандартных фильтров |filters]. Пользовательские фильтры позволяют расширить этот набор форматированием и преобразованиями, специфичными для вашего проекта. + +Если вам нужно выполнять логику, основанную на *нескольких* входах, или у вас нет основного значения для преобразования, вероятно, более подходящим будет использование [пользовательской функции |custom-functions]. Если вам нужно генерировать сложную разметку или управлять потоком шаблона, рассмотрите [пользовательский тег |custom-tags]. + + +Создание и регистрация фильтров +=============================== + +Существует несколько способов определения и регистрации пользовательских фильтров в Latte. + + +Прямая регистрация с помощью `addFilter()` +------------------------------------------ + +Самый простой способ добавить фильтр — использовать метод `addFilter()` непосредственно на объекте `Latte\Engine`. Вы указываете имя фильтра (как он будет использоваться в шаблоне) и соответствующий вызываемый PHP-объект. + +```php +$latte = new Latte\Engine; + +// Простой фильтр без аргументов +$latte->addFilter('initial', fn(string $s): string => mb_substr($s, 0, 1) . '.'); + +// Фильтр с необязательным аргументом +$latte->addFilter('shortify', function (string $s, int $len = 10): string { + return mb_substr($s, 0, $len); +}); + +// Фильтр, обрабатывающий массив +$latte->addFilter('sum', fn(array $numbers): int|float => array_sum($numbers)); +``` + +**Использование в шаблоне:** + +```latte +{$name|initial} {* Выведет 'J.' если $name 'John' *} +{$description|shortify} {* Использует длину по умолчанию 10 *} +{$description|shortify:50} {* Использует длину 50 *} +{$prices|sum} {* Выведет сумму элементов в массиве $prices *} +``` + +**Передача аргументов:** + +Значение слева от пайпа (`|`) всегда передается как *первый* аргумент функции фильтра. Любые параметры, указанные после двоеточия (`:`) в шаблоне, передаются как следующие аргументы. + +```latte +{$text|shortify:30} +// Вызывает PHP-функцию shortify($text, 30) +``` + + +Регистрация с помощью расширения +-------------------------------- + +Для лучшей организации, особенно при создании повторно используемых наборов фильтров или их распространении в виде пакетов, рекомендуемым способом является их регистрация в рамках [расширения Latte |extending-latte#Latte Extension]: + +```php +namespace App\Latte; + +use Latte\Extension; + +class MyLatteExtension extends Extension +{ + public function getFilters(): array + { + return [ + 'initial' => $this->initial(...), + 'shortify' => $this->shortify(...), + ]; + } + + public function initial(string $s): string + { + return mb_substr($s, 0, 1) . '.'; + } + + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// Регистрация +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +Этот подход сохраняет логику вашего фильтра инкапсулированной и упрощает регистрацию. + + +Использование загрузчика фильтров +--------------------------------- + +Latte позволяет регистрировать загрузчик фильтров с помощью `addFilterLoader()`. Это единственный вызываемый объект, который Latte запросит для любого неизвестного имени фильтра во время компиляции. Загрузчик возвращает вызываемый PHP-объект фильтра или `null`. + +```php +$latte = new Latte\Engine; + +// Загрузчик может динамически создавать/получать вызываемые объекты фильтров +$latte->addFilterLoader(function (string $name): ?callable { + if ($name === 'myLazyFilter') { + // Представьте здесь сложную инициализацию... + $service = get_some_expensive_service(); + return fn($value) => $service->process($value); + } + return null; +}); +``` + +Этот метод был в основном предназначен для ленивой загрузки фильтров с очень **сложной инициализацией**. Однако современные практики внедрения зависимостей (dependency injection) обычно справляются с ленивыми сервисами более эффективно. + +Загрузчики фильтров добавляют сложности и, как правило, не рекомендуются в пользу прямой регистрации с помощью `addFilter()` или в рамках расширения с помощью `getFilters()`. Используйте загрузчики только если у вас есть веская, специфическая причина, связанная с проблемами производительности при инициализации фильтров, которые нельзя решить иначе. + + +Фильтры, использующие класс с атрибутами +---------------------------------------- + +Еще один элегантный способ определения фильтров — использование методов в вашем [классе параметров шаблона |develop#Параметры как класс]. Просто добавьте атрибут `#[Latte\Attributes\TemplateFilter]` к методу. + +```php +use Latte\Attributes\TemplateFilter; + +class TemplateParameters +{ + public function __construct( + public string $description, + // другие параметры... + ) {} + + #[TemplateFilter] + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// Передача объекта в шаблон +$params = new TemplateParameters(description: '...'); +$latte->render('template.latte', $params); +``` + +Latte автоматически распознает и зарегистрирует методы, помеченные этим атрибутом, когда объект `TemplateParameters` передается в шаблон. Имя фильтра в шаблоне будет таким же, как имя метода (`shortify` в данном случае). + +```latte +{* Использование фильтра, определенного в классе параметров *} +{$description|shortify:50} +``` + + +Контекстные фильтры +=================== + +Иногда фильтру требуется больше информации, чем просто входное значение. Ему может потребоваться знать **тип содержимого** строки, с которой он работает (например, HTML, JavaScript, простой текст), или даже изменить его. Это ситуация для контекстных фильтров. + +Контекстный фильтр определяется так же, как и обычный фильтр, но его **первый параметр должен быть** типизирован как `Latte\Runtime\FilterInfo`. Latte автоматически распознает эту сигнатуру и при вызове фильтра передаст объект `FilterInfo`. Следующие параметры получат аргументы фильтра как обычно. + +```php +use Latte\Runtime\FilterInfo; +use Latte\ContentType; + +$latte->addFilter('money', function (FilterInfo $info, float $amount): string { + // 1. Проверьте тип входного содержимого (необязательно, но рекомендуется) + // Разрешите null (переменный ввод) или простой текст. Отклоните, если применен к HTML и т. д. + if (!in_array($info->contentType, [null, ContentType::Text], true)) { + $actualType = $info->contentType ?? 'mixed'; + throw new \RuntimeException( + "Фильтр |money используется в несовместимом типе контента $actualType. Ожидался text или null." + ); + } + + // 2. Выполните преобразование + $formatted = number_format($amount, 2, '.', ',') . ' EUR'; + $htmlOutput = '' . htmlspecialchars($formatted) . ''; // Обеспечьте правильное экранирование! + + // 3. Объявите тип выходного содержимого + $info->contentType = ContentType::Html; + + // 4. Верните результат + return $htmlOutput; +}); +``` + +`$info->contentType` — это строковая константа из `Latte\ContentType` (например, `ContentType::Html`, `ContentType::Text`, `ContentType::JavaScript` и т. д.) или `null`, если фильтр применяется к переменной (`{$var|filter}`). Вы можете **читать** это значение, чтобы проверить входной контекст, и **записывать** в него, чтобы объявить тип выходного контекста. + +Устанавливая тип содержимого в HTML, вы сообщаете Latte, что строка, возвращаемая вашим фильтром, является безопасным HTML. Latte тогда **не будет** применять к этому результату свое стандартное автоматическое экранирование. Это критически важно, если ваш фильтр генерирует HTML-разметку. + +.[warning] +Если ваш фильтр генерирует HTML, **вы несете ответственность за правильное экранирование любых входных данных**, используемых в этом HTML (как в случае вызова `htmlspecialchars($formatted)` выше). Пренебрежение этим может создать уязвимости XSS. Если ваш фильтр возвращает только простой текст, вам не нужно устанавливать `$info->contentType`. + + +Фильтры на блоках +----------------- + +Все фильтры, применяемые к [блокам |tags#block], *должны* быть контекстными. Это потому, что содержимое блока имеет определенный тип содержимого (обычно HTML), о котором фильтр должен знать. + +```latte +{block heading|money}1000{/block} +{* Фильтр 'money' получит '1000' как второй аргумент + и $info->contentType будет ContentType::Html *} +``` + +Контекстные фильтры предоставляют мощный контроль над тем, как данные обрабатываются на основе их контекста, позволяют реализовать продвинутые функции и обеспечивают правильное поведение экранирования, особенно при генерации HTML-содержимого. diff --git a/latte/ru/custom-functions.texy b/latte/ru/custom-functions.texy new file mode 100644 index 0000000000..0921080aca --- /dev/null +++ b/latte/ru/custom-functions.texy @@ -0,0 +1,144 @@ +Создание пользовательских функций +********************************* + +.[perex] +Легко добавляйте в шаблоны Latte пользовательские вспомогательные функции. Вызывайте PHP-логику непосредственно в выражениях для вычислений, доступа к сервисам или генерации динамического контента, что сохранит ваши шаблоны чистыми и мощными. + + +Что такое функции? +================== + +Функции в Latte позволяют расширить набор функций, которые можно вызывать в выражениях в шаблонах (`{...}`). Вы можете представить их как **пользовательские PHP-функции, доступные только внутри ваших Latte-шаблонов**. Это дает несколько преимуществ: + +**Удобство:** Вы можете определить вспомогательную логику (например, вычисления, форматирование или доступ к данным приложения) и вызывать ее с помощью простого, знакомого синтаксиса функций прямо в шаблоне, так же, как вы бы вызвали `strlen()` или `date()` в PHP. + +```latte +{var $userInitials = initials($userName)} {* например, 'J. D.' *} + +{if hasPermission('article', 'edit')} + Edit +{/if} +``` + +**Без загрязнения глобального пространства:** В отличие от определения настоящей глобальной функции в PHP, функции Latte существуют только в контексте рендеринга шаблона. Вам не нужно загружать глобальное пространство имен PHP помощниками, специфичными только для шаблонов. + +**Интеграция с логикой приложения:** Вызываемый PHP-объект, стоящий за функцией Latte, может быть чем угодно — анонимной функцией, статическим методом или методом экземпляра. Это означает, что ваши функции в шаблонах могут легко получать доступ к сервисам приложения, базам данных, конфигурации или любой другой необходимой логике, захватывая переменные (в случае анонимных функций) или используя внедрение зависимостей (в случае объектов). Вышеприведенный пример `hasPermission` ясно демонстрирует это, когда, вероятно, вызывает на заднем плане сервис авторизации. + +**Переопределение нативных функций (необязательно):** Вы можете даже определить функцию Latte с тем же именем, что и у нативной PHP-функции. В шаблоне вместо исходной функции будет вызвана ваша собственная версия. Это может быть полезно для предоставления поведения, специфичного для шаблона, или обеспечения согласованной обработки (например, гарантируя, что `strlen` всегда будет многобайтово безопасной). Используйте эту функцию с осторожностью, чтобы избежать недоразумений. + +По умолчанию Latte позволяет вызывать *все* нативные PHP-функции (если они не ограничены [Песочницей |sandbox]). Пользовательские функции расширяют эту встроенную библиотеку специфическими потребностями вашего проекта. + +Если вы просто преобразуете одно значение, может быть более подходящим использовать [пользовательский фильтр |custom-filters]. + + +Создание и регистрация функций +============================== + +Аналогично фильтрам, существует несколько способов определения и регистрации пользовательских функций. + + +Прямая регистрация с помощью `addFunction()` +-------------------------------------------- + +Самый простой метод — использовать `addFunction()` на объекте `Latte\Engine`. Вы указываете имя функции (как оно будет отображаться в шаблоне) и соответствующий вызываемый PHP-объект. + +```php +$latte = new Latte\Engine; + +// Простая вспомогательная функция +$latte->addFunction('initials', function (string $name): string { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; +}); +``` + +**Использование в шаблоне:** + +```latte +{var $userInitials = initials($userName)} +``` + +Аргументы функции в шаблоне передаются непосредственно вызываемому PHP-объекту в том же порядке. Функциональность PHP, такая как типизация, значения по умолчанию и переменное количество параметров (`...`), работает как ожидалось. + + +Регистрация с помощью расширения +-------------------------------- + +Для лучшей организации и повторного использования регистрируйте функции в рамках [расширения Latte |extending-latte#Latte Extension]. Этот подход рекомендуется для более сложных приложений или общих библиотек. + +```php +namespace App\Latte; + +use Latte\Extension; +use Nette\Security\Authorizator; + +class MyLatteExtension extends Extension +{ + public function __construct( + // Предполагаем, что сервис Authorizator существует + private Authorizator $authorizator, + ) { + } + + public function getFunctions(): array + { + // Регистрация методов как функций Latte + return [ + 'hasPermission' => $this->hasPermission(...), + ]; + } + + public function hasPermission(string $resource, string $action): bool + { + return $this->authorizator->isAllowed($resource, $action); + } +} + +// Регистрация (предполагаем, что $container содержит DI-контейнер) +$extension = $container->getByType(App\Latte\MyLatteExtension::class); +$latte = new Latte\Engine; +$latte->addExtension($extension); +``` + +Этот подход наглядно показывает, как функции, определенные в Latte, могут быть подкреплены методами объектов, которые могут иметь свои собственные зависимости, управляемые DI-контейнером вашего приложения или фабрикой. Это поддерживает связь логики ваших шаблонов с ядром приложения, сохраняя при этом четкую организацию. + + +Функции, использующие класс с атрибутами +---------------------------------------- + +Как и фильтры, функции могут быть определены как методы в вашем [классе параметров шаблона |develop#Параметры как класс] с помощью атрибута `#[Latte\Attributes\TemplateFunction]`. + +```php +use Latte\Attributes\TemplateFunction; + +class TemplateParameters +{ + public function __construct( + public string $userName, + // другие параметры... + ) {} + + // Этот метод будет доступен как {initials(...)} в шаблоне + #[TemplateFunction] + public function initials(string $name): string + { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; + } +} + +// Передача объекта в шаблон +$params = new TemplateParameters(userName: 'John Doe', /* ... */); +$latte->render('template.latte', $params); +``` + +Latte автоматически обнаружит и зарегистрирует методы, помеченные этим атрибутом, когда объект параметров передается в шаблон. Имя функции в шаблоне соответствует имени метода. + +```latte +{* Использование функции, определенной в классе параметров *} +{var $inits = initials($userName)} +``` + +**Контекстные функции?** + +В отличие от фильтров, не существует прямого понятия "контекстных функций", которые бы получали объект, подобный `FilterInfo`. Функции работают в рамках выражений и обычно не нуждаются в прямом доступе к контексту рендеринга или информации о типе содержимого так же, как фильтры, применяемые к блокам. diff --git a/latte/ru/custom-tags.texy b/latte/ru/custom-tags.texy new file mode 100644 index 0000000000..dc35fdad01 --- /dev/null +++ b/latte/ru/custom-tags.texy @@ -0,0 +1,1135 @@ +Создание пользовательских тегов +******************************* + +.[perex] +Эта страница содержит исчерпывающее руководство по созданию пользовательских тегов в Latte. Мы рассмотрим всё, от простых тегов до более сложных сценариев с вложенным содержимым и специфическими потребностями парсинга, опираясь на ваше понимание того, как Latte компилирует шаблоны. + +Пользовательские теги обеспечивают наивысший уровень контроля над синтаксисом шаблона и логикой рендеринга, но они также являются самой сложной точкой расширения. Прежде чем решиться на создание пользовательского тега, всегда подумайте, [не существует ли более простого решения |extending-latte#Способы расширения Latte] или не существует ли уже подходящий тег в [стандартном наборе |tags]. Используйте пользовательские теги только тогда, когда более простые альтернативы недостаточны для ваших нужд. + + +Понимание процесса компиляции +============================= + +Для эффективного создания пользовательских тегов полезно объяснить, как Latte обрабатывает шаблоны. Понимание этого процесса проясняет, почему теги структурированы именно так и как они вписываются в более широкий контекст. + +Компиляция шаблона в Latte, упрощенно, включает следующие ключевые шаги: + +1. **Лексический анализ:** Лексер читает исходный код шаблона (файл `.latte`) и разбивает его на последовательность мелких, отдельных частей, называемых **токенами** (например, `{`, `foreach`, `$variable`, `}`, HTML-текст и т.д.). +2. **Парсинг:** Парсер берет этот поток токенов и строит из него осмысленную древовидную структуру, представляющую логику и содержимое шаблона. Это дерево называется **абстрактным синтаксическим деревом (AST)**. +3. **Проходы компиляции:** Перед генерацией PHP-кода Latte запускает [проходы компиляции |compiler-passes]. Это функции, которые проходят по всему AST и могут его изменять или собирать информацию. Этот шаг ключевой для функций, таких как безопасность ([Sandbox |sandbox]) или оптимизация. +4. **Генерация кода:** Наконец, компилятор проходит по (потенциально измененному) AST и генерирует соответствующий код PHP-класса. Этот PHP-код — это то, что фактически рендерит шаблон при запуске. +5. **Кеширование:** Сгенерированный PHP-код сохраняется на диск, что делает последующие рендеринги очень быстрыми, поскольку шаги 1-4 пропускаются. + +На самом деле компиляция немного сложнее. Latte **имеет два** лексера и парсера: один для HTML-шаблона и второй для PHP-подобного кода внутри тегов. А также парсинг не происходит после токенизации, но лексер и парсер работают параллельно в двух "потоках" и координируются. Поверьте мне, программирование этого было ракетостроением :-) + +Весь процесс, от загрузки содержимого шаблона, через парсинг, до генерации результирующего файла, можно последовательно выполнить этим кодом, с которым вы можете экспериментировать и выводить промежуточные результаты: + +```php +$latte = new Latte\Engine; +$source = $latte->getLoader()->getContent($file); +$ast = $latte->parse($source); +$latte->applyPasses($ast); +$code = $latte->generate($ast, $file); +``` + + +Анатомия тега +============= + +Создание полнофункционального пользовательского тега в Latte включает несколько взаимосвязанных частей. Прежде чем приступить к реализации, давайте разберемся с основными концепциями и терминологией, используя аналогию с HTML и Document Object Model (DOM). + + +Теги против Узлов (Аналогия с HTML) +----------------------------------- + +В HTML мы пишем **теги**, такие как `

                                                                                                            ` или `

                                                                                                            ...
                                                                                                            `. Эти теги являются синтаксисом в исходном коде. Когда браузер парсит этот HTML, он создает представление в памяти, называемое **Document Object Model (DOM)**. В DOM HTML-теги представлены **узлами** (конкретно узлами `Element` в терминологии JavaScript DOM). С этими *узлами* мы работаем программно (например, с помощью JavaScript `document.getElementById(...)` возвращается узел Element). Тег — это всего лишь текстовое представление в исходном файле; узел — это объектное представление в логическом дереве. + +Latte работает аналогично: + +- В файле `.latte` шаблона вы пишете **теги Latte**, такие как `{foreach ...}` и `{/foreach}`. Это синтаксис, с которым вы, как автор шаблона, работаете. +- Когда Latte **парсит** шаблон, он строит **Abstract Syntax Tree (AST)**. Это дерево состоит из **узлов**. Каждый тег Latte, HTML-элемент, кусок текста или выражение в шаблоне становится одним или несколькими узлами в этом дереве. +- Базовый класс для всех узлов в AST — это `Latte\Compiler\Node`. Так же, как DOM имеет различные типы узлов (Element, Text, Comment), AST Latte имеет различные типы узлов. Вы столкнетесь с `Latte\Compiler\Nodes\TextNode` для статического текста, `Latte\Compiler\Nodes\Html\ElementNode` для HTML-элементов, `Latte\Compiler\Nodes\Php\ExpressionNode` для выражений внутри тегов и, что ключевое для пользовательских тегов, узлами, наследующими от `Latte\Compiler\Nodes\StatementNode`. + + +Почему `StatementNode`? +----------------------- + +HTML-элементы (`Html\ElementNode`) в первую очередь представляют структуру и содержимое. PHP-выражения (`Php\ExpressionNode`) представляют значения или вычисления. Но что насчет тегов Latte, таких как `{if}`, `{foreach}` или нашего пользовательского `{datetime}`? Эти теги *выполняют действия*, управляют потоком программы или генерируют вывод на основе логики. Это функциональные единицы, которые делают Latte мощным *движком* шаблонов, а не просто языком разметки. + +В программировании такие единицы, выполняющие действия, часто называют "statements" (инструкции/операторы). Поэтому узлы, представляющие эти функциональные теги Latte, обычно наследуют от `Latte\Compiler\Nodes\StatementNode`. Это отличает их от чисто структурных узлов (как HTML-элементы) или узлов, представляющих значения (как выражения). + + +Ключевые компоненты +=================== + +Давайте рассмотрим основные компоненты, необходимые для создания пользовательского тега: + + +Функция для парсинга тега +------------------------- + +- Эта PHP callable функция парсит синтаксис тега Latte (`{...}`) в исходном шаблоне. +- Она получает информацию о теге (например, его имя, позицию и является ли он n:атрибутом) через объект [api:Latte\Compiler\Tag]. +- Ее основным инструментом для парсинга аргументов и выражений внутри разделителей тега является объект [api:Latte\Compiler\TagParser], доступный через `$tag->parser` (это другой парсер, чем тот, который парсит весь шаблон). +- Для парных тегов она использует `yield` для сигнализации Latte о необходимости парсить внутреннее содержимое между начальным и конечным тегами. +- Конечной целью функции парсинга является создание и возврат экземпляра **класса узла**, который добавляется в AST. +- Принято (хотя и не требуется) реализовывать функцию парсинга как статический метод (часто называемый `create`) непосредственно в соответствующем классе узла. Это позволяет аккуратно упаковать логику парсинга и представление узла, дает доступ к приватным/защищенным членам класса, если необходимо, и улучшает организацию. + + +Класс узла +---------- + +- Представляет *логическую функцию* вашего тега в **Abstract Syntax Tree (AST)**. +- Содержит распарсенную информацию (например, аргументы или содержимое) как публичные свойства. Эти свойства часто содержат другие экземпляры `Node` (например, `ExpressionNode` для распарсенных аргументов, `AreaNode` для распарсенного содержимого). +- Метод `print(PrintContext $context): string` генерирует *PHP-код* (инструкцию или серию инструкций), который выполняет действие тега во время рендеринга шаблона. +- Метод `getIterator(): \Generator` предоставляет доступ к дочерним узлам (аргументам, содержимому) для обхода **проходами компиляции**. Он должен предоставлять ссылки (`&`), чтобы позволить проходам потенциально изменять или заменять подузлы. +- После того как весь шаблон распарсен в AST, Latte запускает серию [проходов компиляции |compiler-passes]. Эти проходы обходят *весь* AST с помощью метода `getIterator()`, предоставляемого каждым узлом. Они могут проверять узлы, собирать информацию и даже *изменять* дерево (например, изменяя публичные свойства узлов или полностью заменяя узлы). Этот дизайн, требующий комплексного `getIterator()`, является основополагающим. Он позволяет мощным функциям, таким как [Sandbox |sandbox], анализировать и потенциально изменять поведение *любой* части шаблона, включая ваши пользовательские теги, обеспечивая безопасность и консистентность. + + +Регистрация через расширение +---------------------------- + +- Вам нужно сообщить Latte о вашем новом теге и какая функция парсинга должна быть для него использована. Это делается в рамках [расширения Latte |extending-latte#Latte Extension]. +- Внутри вашего класса расширения вы реализуете метод `getTags(): array`. Этот метод возвращает ассоциативный массив, где ключи — это имена тегов (например, `'mytag'`, `'n:myattribute'`), а значения — это PHP callable функции, представляющие их соответствующие функции парсинга (например, `MyNamespace\DatetimeNode::create(...)`). + +Резюме: **Функция парсинга тега** преобразует *исходный код шаблона* вашего тега в **узел AST**. **Класс узла** затем умеет преобразовывать *себя* в исполняемый *PHP-код* для скомпилированного шаблона и предоставляет доступ к своим подузлам для **проходов компиляции** через `getIterator()`. **Регистрация через расширение** связывает имя тега с функцией парсинга и сообщает о нем Latte. + +Теперь мы рассмотрим, как реализовать эти компоненты шаг за шагом. + + +Создание простого тега +====================== + +Давайте приступим к созданию вашего первого пользовательского тега Latte. Начнем с очень простого примера: тег с именем `{datetime}`, который выводит текущую дату и время. **Изначально этот тег не будет принимать никаких аргументов**, но мы улучшим его позже в разделе [#"Парсинг аргументов тега"]. У него также нет внутреннего содержимого. + +Этот пример проведет вас через основные шаги: определение класса узла, реализация его методов `print()` и `getIterator()`, создание функции парсинга и, наконец, регистрация тега. + +**Цель:** Реализовать `{datetime}` для вывода текущей даты и времени с помощью PHP-функции `date()`. + + +Создание класса узла +-------------------- + +Сначала нам нужен класс, который будет представлять наш тег в Abstract Syntax Tree (AST). Как обсуждалось выше, мы наследуем от `Latte\Compiler\Nodes\StatementNode`. + +Создайте файл (например, `DatetimeNode.php`) и определите класс: + +```php +node = new self; + return $node; + } + + /** + * Генерирует PHP-код, который будет выполнен при рендеринге шаблона. + */ + public function print(PrintContext $context): string + { + return $context->format( + 'echo date(\'Y-m-d H:i:s\') %line;', + $this->position, + ); + } + + /** + * Предоставляет доступ к дочерним узлам для проходов компиляции Latte. + */ + public function &getIterator(): \Generator + { + false && yield; + } +} +``` + +Когда Latte встречает `{datetime}` в шаблоне, он вызывает функцию парсинга `create()`. Ее задача — вернуть экземпляр `DatetimeNode`. + +Метод `print()` генерирует PHP-код, который будет выполнен при рендеринге шаблона. Мы вызываем метод `$context->format()`, который собирает результирующую строку PHP-кода для скомпилированного шаблона. Первый аргумент, `'echo date('Y-m-d H:i:s') %line;'`, — это маска, в которую подставляются следующие параметры. Плейсхолдер `%line` говорит методу `format()`, чтобы он использовал второй аргумент, которым является `$this->position`, и вставил комментарий вроде `/* line 15 */`, который связывает сгенерированный PHP-код обратно с исходной строкой шаблона, что ключевое для отладки. + +Свойство `$this->position` наследуется от базового класса `Node` и автоматически устанавливается парсером Latte. Оно содержит объект [api:Latte\Compiler\Position], который указывает, где был найден тег в исходном файле `.latte`. + +Метод `getIterator()` является основополагающим для проходов компиляции. Он должен предоставлять все дочерние узлы, но наш простой `DatetimeNode` в настоящее время не имеет ни аргументов, ни содержимого, то есть нет дочерних узлов. Тем не менее, метод все равно должен существовать и быть генератором, т.е. ключевое слово `yield` должно каким-то образом присутствовать в теле метода. + + +Регистрация через расширение +---------------------------- + +Наконец, сообщим Latte о новом теге. Создайте [класс расширения |extending-latte#Latte Extension] (например, `MyLatteExtension.php`) и зарегистрируйте тег в его методе `getTags()`. + +```php + Карта: 'имя-тега' => функция-парсинга + */ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + // Позже здесь зарегистрируйте больше тегов + ]; + } +} +``` + +Затем зарегистрируйте это расширение в Latte Engine: + +```php +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +Создайте шаблон: + +```latte +

                                                                                                            Страница сгенерирована: {datetime}

                                                                                                            +``` + +Ожидаемый вывод: `

                                                                                                            Страница сгенерирована: 2023-10-27 11:00:00

                                                                                                            ` + + +Резюме этого этапа +------------------ + +Мы успешно создали базовый пользовательский тег `{datetime}`. Мы определили его представление в AST (`DatetimeNode`), обработали его парсинг (`create()`), указали, как он должен генерировать PHP-код (`print()`), обеспечили доступность его дочерних элементов для обхода (`getIterator()`) и зарегистрировали его в Latte. + +В следующем разделе мы улучшим этот тег, чтобы он принимал аргументы, и покажем, как парсить выражения и управлять дочерними узлами. + + +Парсинг аргументов тега +======================= + +Наш простой тег `{datetime}` работает, но он не очень гибкий. Давайте улучшим его, чтобы он принимал необязательный аргумент: строку форматирования для функции `date()`. Требуемый синтаксис будет `{datetime $format}`. + +**Цель:** Изменить `{datetime}` так, чтобы он принимал необязательное PHP-выражение в качестве аргумента, которое будет использоваться как строка форматирования для `date()`. + + +Представление `TagParser` +------------------------- + +Прежде чем изменять код, важно понять инструмент, который мы будем использовать: [api:Latte\Compiler\TagParser]. Когда основной парсер Latte (`TemplateParser`) встречает тег Latte, такой как `{datetime ...}` или n:атрибут, он делегирует парсинг содержимого *внутри* тега (часть между `{` и `}` или значение атрибута) специализированному `TagParser`. + +Этот `TagParser` работает исключительно с **аргументами тега**. Его задача — обрабатывать токены, представляющие эти аргументы. Ключевое то, что **он должен обработать все предоставленное ему содержимое**. Если ваша функция парсинга завершается, но `TagParser` не достиг конца аргументов (проверяется через `$tag->parser->isEnd()`), Latte выбросит исключение, поскольку это указывает на то, что внутри тега остались неожиданные токены. Наоборот, если тег *требует* аргументы, вы должны в начале вашей функции парсинга вызвать `$tag->expectArguments()`. Этот метод проверяет наличие аргументов и выбрасывает полезное исключение, если тег был использован без каких-либо аргументов. + +`TagParser` предлагает полезные методы для парсинга различных видов аргументов: + +- `parseExpression(): ExpressionNode`: Парсит PHP-подобное выражение (переменные, литералы, операторы, вызовы функций/методов и т.д.). Обрабатывает синтаксический сахар Latte, например, обработку простых буквенно-цифровых строк как строк в кавычках (например, `foo` парсится, как если бы это было `'foo'`). +- `parseUnquotedStringOrExpression(): ExpressionNode`: Парсит либо стандартное выражение, либо *строку без кавычек*. Строки без кавычек — это последовательности, разрешенные Latte без кавычек, часто используемые для таких вещей, как пути к файлам (например, `{include ../file.latte}`). Если парсит строку без кавычек, возвращает `StringNode`. +- `parseArguments(): ArrayNode`: Парсит аргументы, разделенные запятыми, потенциально с ключами, как `10, name: 'John', true`. +- `parseModifier(): ModifierNode`: Парсит фильтры, такие как `|upper|truncate:10`. +- `parseType(): ?SuperiorTypeNode`: Парсит PHP-тайп хинты, такие как `int`, `?string`, `array|Foo`. + +Для более сложных или низкоуровневых потребностей парсинга вы можете напрямую взаимодействовать с [потоком токенов |api:Latte\Compiler\TokenStream] через `$tag->parser->stream`. Этот объект предоставляет методы для проверки и обработки отдельных токенов: + +- `$tag->parser->stream->is(...): bool`: Проверяет, соответствует ли *текущий* токен одному из указанных типов (например, `Token::Php_Variable`) или литеральных значений (например, `'as'`) без его потребления. Полезно для заглядывания вперед. +- `$tag->parser->stream->consume(...): Token`: Потребляет *текущий* токен и сдвигает позицию потока вперед. Если предоставлены ожидаемые типы/значения токенов в качестве аргументов, и текущий токен не соответствует, выбрасывает `CompileException`. Используйте это, когда *ожидаете* определенный токен. +- `$tag->parser->stream->tryConsume(...): ?Token`: Пытается потребить *текущий* токен *только если* он соответствует одному из указанных типов/значений. Если соответствует, потребляет токен и возвращает его. Если не соответствует, оставляет позицию потока неизменной и возвращает `null`. Используйте это для необязательных токенов или при выборе между различными синтаксическими путями. + + +Обновление функции парсинга `create()` +-------------------------------------- + +С этим пониманием изменим метод `create()` в `DatetimeNode` так, чтобы он парсил необязательный аргумент форматирования с помощью `$tag->parser`. + +```php +node = new self; + + // Проверим, существуют ли какие-либо токены + if (!$tag->parser->isEnd()) { + // Парсим аргумент как PHP-подобное выражение с помощью TagParser. + $node->format = $tag->parser->parseExpression(); + } + + return $node; + } + + // ... методы print() и getIterator() будут обновлены далее ... +} +``` + +Мы добавили публичное свойство `$format`. В `create()` мы теперь используем `$tag->parser->isEnd()` для проверки, *существуют* ли аргументы. Если да, `$tag->parser->parseExpression()` обрабатывает токены для выражения. Поскольку `TagParser` должен обработать все входные токены, Latte автоматически выбросит ошибку, если пользователь напишет что-то неожиданное после выражения формата (например, `{datetime 'Y-m-d', unexpected}`). + + +Обновление метода `print()` +--------------------------- + +Теперь изменим метод `print()`, чтобы он использовал распарсенное выражение формата, хранящееся в `$this->format`. Если формат не был предоставлен (`$this->format` равен `null`), мы должны использовать строку форматирования по умолчанию, например, `'Y-m-d H:i:s'`. + +```php + public function print(PrintContext $context): string + { + $formatNode = $this->format ?? new StringNode('Y-m-d H:i:s'); + + // %node выведет представление PHP-кода $formatNode. + return $context->format( + 'echo date(%node) %line;', + $formatNode, + $this->position + ); + } +``` + +В переменную `$formatNode` мы сохраняем узел AST, представляющий строку форматирования для PHP-функции `date()`. Мы используем здесь оператор объединения с null (`??`). Если пользователь предоставил аргумент в шаблоне (например, `{datetime 'd.m.Y'}`), то свойство `$this->format` содержит соответствующий узел (в данном случае `StringNode` со значением `'d.m.Y'`), и этот узел используется. Если пользователь не предоставил аргумент (написал просто `{datetime}`), свойство `$this->format` равно `null`, и вместо этого мы создаем новый `StringNode` с форматом по умолчанию `'Y-m-d H:i:s'`. Это гарантирует, что `$formatNode` всегда содержит действительный узел AST для формата. + +В маске `'echo date(%node) %line;'` используется новый плейсхолдер `%node`, который говорит методу `format()`, чтобы он взял первый следующий аргумент (которым является наш `$formatNode`), вызвал его метод `print()` (который вернет его представление в виде PHP-кода) и вставил результат на место плейсхолдера. + + +Реализация `getIterator()` для подузлов +--------------------------------------- + +Наш `DatetimeNode` теперь имеет дочерний узел: выражение `$format`. **Мы должны** сделать этот дочерний узел доступным для проходов компиляции, предоставив его в методе `getIterator()`. Не забудьте предоставить *ссылку* (`&`), чтобы позволить проходам потенциально заменить узел. + +```php + public function &getIterator(): \Generator + { + if ($this->format) { + yield $this->format; + } + } +``` + +Почему это так важно? Представьте себе проход Sandbox, который должен проверить, не содержит ли аргумент `$format` запрещенный вызов функции (например, `{datetime dangerousFunction()}`). Если `getIterator()` не предоставит `$this->format`, проход Sandbox никогда не увидит вызов `dangerousFunction()` внутри аргумента нашего тега, что создало бы потенциальную дыру в безопасности. Предоставляя его, мы позволяем Sandbox (и другим проходам) проверять и потенциально изменять узел выражения `$format`. + + +Использование улучшенного тега +------------------------------ + +Тег теперь правильно обрабатывает необязательный аргумент: + +```latte +Формат по умолчанию: {datetime} +Пользовательский формат: {datetime 'd.m.Y'} +Использование переменной: {datetime $userDateFormatPreference} + +{* Это вызвало бы ошибку после парсинга 'd.m.Y', так как ", foo" является неожиданным *} +{* {datetime 'd.m.Y', foo} *} +``` + +Далее мы рассмотрим создание парных тегов, которые обрабатывают содержимое между ними. + + +Обработка парных тегов +====================== + +До сих пор наш тег `{datetime}` был *самозакрывающимся* (концептуально). У него не было содержимого между начальным и конечным тегами. Однако многие полезные теги работают с блоком содержимого шаблона. Они называются **парными тегами**. Примеры включают `{if}...{/if}`, `{block}...{/block}` или пользовательский тег, который мы сейчас создадим: `{debug}...{/debug}`. + +Этот тег позволит нам включать в наши шаблоны отладочную информацию, которая должна быть видна только во время разработки. + +**Цель:** Создать парный тег `{debug}`, содержимое которого рендерится только тогда, когда активен специфический флаг "режима разработки". + + +Представление поставщиков +------------------------- + +Иногда вашим тегам нужен доступ к данным или сервисам, которые не передаются напрямую как параметры шаблона. Например, определение, находится ли приложение в режиме разработки, доступ к объекту пользователя или получение конфигурационных значений. Latte предоставляет механизм, называемый **поставщиками** (Providers) для этой цели. + +Поставщики регистрируются в вашем [расширении |extending-latte#Latte Extension] с помощью метода `getProviders()`. Этот метод возвращает ассоциативный массив, где ключи — это имена, под которыми поставщики будут доступны в коде шаблона во время выполнения, а значения — это фактические данные или объекты. + +Внутри PHP-кода, генерируемого методом `print()` вашего тега, вы можете получить доступ к этим поставщикам через специальное свойство объекта `$this->global`. Поскольку это свойство является общим для всех расширений, хорошей практикой является **добавлять префиксы к именам ваших поставщиков**, чтобы избежать потенциальных конфликтов имен с ключевыми поставщиками Latte или поставщиками из других сторонних расширений. Обычной конвенцией является использование короткого, уникального префикса, связанного с вашим производителем или именем расширения. Для нашего примера мы будем использовать префикс `app`, и флаг режима разработки будет доступен как `$this->global->appDevMode`. + + +Ключевое слово `yield` для парсинга содержимого +----------------------------------------------- + +Как мы говорим парсеру Latte обработать содержимое *между* `{debug}` и `{/debug}`? Здесь в игру вступает ключевое слово `yield`. + +Когда `yield` используется в функции `create()`, функция становится [PHP-генератором |https://www.php.net/manual/en/language.generators.overview.php]. Ее выполнение приостанавливается, и управление возвращается к основному `TemplateParser`. `TemplateParser` затем продолжает парсить содержимое шаблона *до тех пор, пока* не встретит соответствующий закрывающий тег (`{/debug}` в нашем случае). + +Как только найден закрывающий тег, `TemplateParser` возобновляет выполнение нашей функции `create()` сразу после инструкции `yield`. Значение, *возвращаемое* инструкцией `yield`, — это массив, содержащий два элемента: + +1. `AreaNode`, представляющий распарсенное содержимое между начальным и конечным тегами. +2. Объект `Tag`, представляющий закрывающий тег (например, `{/debug}`). + +Создадим класс `DebugNode` и его метод `create`, использующий `yield`. + +```php +node = new self; + + // Приостановить парсинг, получить внутреннее содержимое и конечный тег, когда найден {/debug} + [$node->content, $endTag] = yield; + + return $node; + } + + // ... print() и getIterator() будут реализованы далее ... +} +``` + +Примечание: `$endTag` равен `null`, если тег используется как n:атрибут, т.е. `
                                                                                                            ...
                                                                                                            `. + + +Реализация `print()` для условного рендеринга +--------------------------------------------- + +Метод `print()` теперь должен генерировать PHP-код, который во время выполнения проверяет поставщика `appDevMode` и выполняет код для внутреннего содержимого только в том случае, если флаг равен true. + +```php + public function print(PrintContext $context): string + { + // Генерирует PHP-инструкцию 'if', которая во время выполнения проверяет поставщика + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + // Если в режиме разработки, выводит внутреннее содержимое + %node + } + + XX, + $this->position, // Для %line комментария + $this->content, // Узел, содержащий AST внутреннего содержимого + ); + } +``` + +Это просто. Мы используем `PrintContext::format()` для создания стандартной PHP-инструкции `if`. Внутри `if` мы помещаем плейсхолдер `%node` для `$this->content`. Latte рекурсивно вызовет `$this->content->print($context)` для генерации PHP-кода для внутренней части тега, но только если `$this->global->appDevMode` во время выполнения будет оценено как true. + + +Реализация `getIterator()` для содержимого +------------------------------------------ + +Так же, как и с узлом аргумента в предыдущем примере, наш `DebugNode` теперь имеет дочерний узел: `AreaNode $content`. Мы должны сделать его доступным, предоставив его в `getIterator()`: + +```php + public function &getIterator(): \Generator + { + // Предоставляет ссылку на узел содержимого + yield $this->content; + } +``` + +Это позволяет проходам компиляции спускаться в содержимое нашего тега `{debug}`, что важно, даже если содержимое рендерится условно. Например, Sandbox должен анализировать содержимое независимо от того, равен ли `appDevMode` true или false. + + +Регистрация и использование +--------------------------- + +Зарегистрируйте тег и поставщика в вашем расширении: + +```php +class MyLatteExtension extends Extension +{ + // Предполагаем, что $isDevelopmentMode определяется где-то (например, из конфигурации) + public function __construct( + private bool $isDevelopmentMode, + ) { + } + + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), // Регистрация нового тега + ]; + } + + public function getProviders(): array + { + return [ + 'appDevMode' => $this->isDevelopmentMode, // Регистрация поставщика + ]; + } +} + +// При регистрации расширения: +$isDev = true; // Определите это на основе среды вашего приложения +$latte->addExtension(new App\Latte\MyLatteExtension($isDev)); +``` + +И его использование в шаблоне: + +```latte +

                                                                                                            Обычное содержимое, видимое всегда.

                                                                                                            + +{debug} +
                                                                                                            + ID текущего пользователя: {$user->id} + Время запроса: {=time()} +
                                                                                                            +{/debug} + +

                                                                                                            Другое обычное содержимое.

                                                                                                            +``` + + +Интеграция n:атрибутов +---------------------- + +Latte предлагает удобную сокращенную запись для многих парных тегов: [n:атрибуты |syntax#n:атрибуты]. Если у вас есть парный тег, такой как `{tag}...{/tag}`, и вы хотите, чтобы его эффект применялся непосредственно к одному HTML-элементу, вы часто можете записать его более экономно как атрибут `n:tag` на этом элементе. + +Для большинства стандартных парных тегов, которые вы определяете (как наш `{debug}`), Latte автоматически разрешит соответствующую версию `n:` атрибута. Во время регистрации вам не нужно делать ничего дополнительного: + +```latte +{* Стандартное использование парного тега *} +{debug}
                                                                                                            Информация для отладки
                                                                                                            {/debug} + +{* Эквивалентное использование с n:атрибутом *} +
                                                                                                            Информация для отладки
                                                                                                            +``` + +Обе версии рендерят `
                                                                                                            ` только если `$this->global->appDevMode` равно true. Префиксы `inner-` и `tag-` также работают, как ожидалось. + +Иногда логика вашего тега может потребовать немного иного поведения в зависимости от того, используется ли он как стандартный парный тег или как n:атрибут, или используется ли префикс, такой как `n:inner-tag` или `n:tag-tag`. Объект `Latte\Compiler\Tag`, переданный вашей функции парсинга `create()`, предоставляет эту информацию: + +- `$tag->isNAttribute(): bool`: Возвращает `true`, если тег парсится как n:атрибут +- `$tag->prefix: ?string`: Возвращает префикс, использованный с n:атрибутом, который может быть `null` (не n:атрибут), `Tag::PrefixNone`, `Tag::PrefixInner` или `Tag::PrefixTag` + +Теперь, когда мы понимаем простые теги, парсинг аргументов, парные теги, поставщиков и n:атрибуты, давайте рассмотрим более сложный сценарий, включающий теги, вложенные в другие теги, используя наш тег `{debug}` в качестве отправной точки. + + +Промежуточные теги +================== + +Некоторые парные теги позволяют или даже требуют, чтобы другие теги появлялись *внутри* них перед конечным закрывающим тегом. Они называются **промежуточными тегами**. Классические примеры включают `{if}...{elseif}...{else}...{/if}` или `{switch}...{case}...{default}...{/switch}`. + +Давайте расширим наш тег `{debug}` для поддержки необязательной клаузы `{else}`, которая будет рендериться, когда приложение *не* находится в режиме разработки. + +**Цель:** Изменить `{debug}` так, чтобы он поддерживал необязательный промежуточный тег `{else}`. Конечный синтаксис должен быть `{debug} ... {else} ... {/debug}`. + + +Парсинг промежуточных тегов с помощью `yield` +--------------------------------------------- + +Мы уже знаем, что `yield` приостанавливает функцию парсинга `create()` и возвращает распарсенное содержимое вместе с конечным тегом. Однако `yield` предлагает больше контроля: вы можете предоставить ему массив *имен промежуточных тегов*. Когда парсер встречает любой из этих указанных тегов **на том же уровне вложенности** (т.е. как прямые потомки родительского тега, а не внутри других блоков или тегов внутри него), он также останавливает парсинг. + +Когда парсинг останавливается из-за промежуточного тега, он останавливает парсинг содержимого, возобновляет генератор `create()` и передает обратно частично распарсенное содержимое и сам **промежуточный тег** (вместо конечного закрывающего тега). Наша функция `create()` затем может обработать этот промежуточный тег (например, распарсить его аргументы, если они были) и снова использовать `yield` для парсинга *следующей* части содержимого до *конечного* закрывающего тега или другого ожидаемого промежуточного тега. + +Изменим `DebugNode::create()` так, чтобы он ожидал `{else}`: + +```php +node = new self; + + // yield и ожидать либо {/debug}, либо {else} + [$node->thenContent, $nextTag] = yield ['else']; + + // Проверить, был ли тег, на котором мы остановились, {else} + if ($nextTag?->name === 'else') { + // Yield снова для парсинга содержимого между {else} и {/debug} + [$node->elseContent, $endTag] = yield; + } + + return $node; + } + + // ... print() и getIterator() будут обновлены далее ... +} +``` + +Теперь `yield ['else']` говорит Latte остановить парсинг не только для `{/debug}`, но и для `{else}`. Если `{else}` найден, `$nextTag` будет содержать объект `Tag` для `{else}`. Затем мы снова используем `yield` без аргументов, что означает, что теперь мы ожидаем только конечный тег `{/debug}`, и сохраняем результат в `$node->elseContent`. Если `{else}` не был найден, `$nextTag` был бы `Tag` для `{/debug}` (или `null`, если используется как n:атрибут), и `$node->elseContent` остался бы `null`. + + +Реализация `print()` с `{else}` +------------------------------- + +Метод `print()` должен отражать новую структуру. Он должен генерировать PHP-инструкцию `if/else`, основанную на поставщике `appDevMode`. + +```php + public function print(PrintContext $context): string + { + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + %node // Код для ветки 'then' (содержимое {debug}) + } else { + %node // Код для ветки 'else' (содержимое {else}) + } + + XX, + $this->position, // Номер строки для условия 'if' + $this->thenContent, // Первый плейсхолдер %node + $this->elseContent ?? new NopNode, // Второй плейсхолдер %node + ); + } +``` + +Это стандартная PHP-структура `if/else`. Мы используем `%node` дважды; `format()` заменяет предоставленные узлы последовательно. Мы используем `?? new NopNode` для избежания ошибок, если `$this->elseContent` равен `null` – `NopNode` просто ничего не печатает. + + +Реализация `getIterator()` для обоих содержимых +----------------------------------------------- + +Теперь у нас потенциально два дочерних узла содержимого (`$thenContent` и `$elseContent`). Мы должны предоставить оба, если они существуют: + +```php + public function &getIterator(): \Generator + { + yield $this->thenContent; + if ($this->elseContent) { + yield $this->elseContent; + } + } +``` + + +Использование улучшенного тега +------------------------------ + +Тег теперь может быть использован с необязательной клаузой `{else}`: + +```latte +{debug} +

                                                                                                            Отображение отладочной информации, так как devMode ВКЛЮЧЕН.

                                                                                                            +{else} +

                                                                                                            Отладочная информация скрыта, так как devMode ВЫКЛЮЧЕН.

                                                                                                            +{/debug} +``` + + +Обработка состояния и вложенности +================================= + +Наши предыдущие примеры (`{datetime}`, `{debug}`) были относительно без состояния в рамках своих методов `print()`. Они либо напрямую выводили содержимое, либо выполняли простую условную проверку на основе глобального поставщика. Однако многие теги должны управлять некоторой формой **состояния** во время рендеринга или включают вычисление пользовательских выражений, которые должны быть выполнены только один раз для производительности или корректности. Далее мы должны рассмотреть, что происходит, когда наши пользовательские теги **вложены**. + +Проиллюстрируем эти концепции, создав тег `{repeat $count}...{/repeat}`. Этот тег будет повторять свое внутреннее содержимое `$count` раз. + +**Цель:** Реализовать `{repeat $count}`, который повторяет свое содержимое указанное количество раз. + + +Необходимость временных и уникальных переменных +----------------------------------------------- + +Представьте, что пользователь пишет: + +```latte +{repeat rand(1, 5)} Содержимое {/repeat} +``` + +Если бы мы наивно сгенерировали PHP-цикл `for` таким образом в нашем методе `print()`: + +```php +// Упрощенный, НЕПРАВИЛЬНЫЙ сгенерированный код +for ($i = 0; $i < rand(1, 5); $i++) { + // вывод содержимого +} +``` +Это было бы неправильно! Выражение `rand(1, 5)` было бы **перевычислено при каждой итерации цикла**, что привело бы к непредсказуемому количеству повторений. Нам нужно вычислить выражение `$count` *один раз* перед началом цикла и сохранить его результат. + +Мы сгенерируем PHP-код, который сначала вычисляет выражение количества и сохраняет его во **временную переменную времени выполнения**. Чтобы предотвратить конфликты с переменными, определенными пользователем шаблона, *и* внутренними переменными Latte (такими как `$ʟ_...`), мы будем использовать конвенцию префикса **`$__` (двойное подчеркивание)** для наших временных переменных. + +Сгенерированный код тогда выглядел бы так: + +```php +$__count = rand(1, 5); +for ($__i = 0; $__i < $__count; $__i++) { + // вывод содержимого +} +``` + +Теперь рассмотрим вложенность: + +```latte +{repeat $countA} {* Внешний цикл *} + {repeat $countB} {* Внутренний цикл *} + ... + {/repeat} +{/repeat} +``` + +Если бы и внешний, и внутренний теги `{repeat}` генерировали код, использующий *одинаковые* имена временных переменных (например, `$__count` и `$__i`), внутренний цикл перезаписал бы переменные внешнего цикла, что нарушило бы логику. + +Нам нужно обеспечить, чтобы временные переменные, генерируемые для каждого экземпляра тега `{repeat}`, были **уникальными**. Мы достигнем этого с помощью `PrintContext::generateId()`. Этот метод возвращает уникальное целое число во время фазы компиляции. Мы можем добавить этот ID к именам наших временных переменных. + +Так, вместо `$__count` мы будем генерировать `$__count_1` для первого тега repeat, `$__count_2` для второго и т.д. Аналогично для счетчика цикла мы будем использовать `$__i_1`, `$__i_2` и т.д. + + +Реализация `RepeatNode` +----------------------- + +Давайте создадим класс узла. + +```php +expectArguments(); // убеждается, что $count предоставлен + $node = $tag->node = new self; + // Парсит выражение количества + $node->count = $tag->parser->parseExpression(); + // Получение внутреннего содержимого + [$node->content] = yield; + return $node; + } + + /** + * Генерирует PHP-цикл 'for' с уникальными именами переменных. + */ + public function print(PrintContext $context): string + { + // Генерация уникальных имен переменных + $id = $context->generateId(); + $countVar = '$__count_' . $id; // напр. $__count_1, $__count_2, и т.д. + $iteratorVar = '$__i_' . $id; // напр. $__i_1, $__i_2, и т.д. + + return $context->format( + <<<'XX' + // Вычисление выражения количества *один раз* и сохранение + %raw = (int) (%node); + // Цикл с использованием сохраненного количества и уникальной итерационной переменной + for (%raw = 0; %2.raw < %0.raw; %2.raw++) %line { + %node // Рендеринг внутреннего содержимого + } + + XX, + $countVar, // %0 - Переменная для сохранения количества + $this->count, // %1 - Узел выражения для количества + $iteratorVar, // %2 - Имя итерационной переменной цикла + $this->position, // %3 - Комментарий с номером строки для самого цикла + $this->content // %4 - Узел внутреннего содержимого + ); + } + + /** + * Предоставляет дочерние узлы (выражение количества и содержимое). + */ + public function &getIterator(): \Generator + { + yield $this->count; + yield $this->content; + } +} +``` + +Метод `create()` парсит требуемое выражение `$count` с помощью `parseExpression()`. Сначала вызывается `$tag->expectArguments()`. Это гарантирует, что пользователь предоставил *что-то* после `{repeat}`. Хотя `$tag->parser->parseExpression()` потерпело бы неудачу, если бы ничего не было предоставлено, сообщение об ошибке могло бы быть о неожиданном синтаксисе. Использование `expectArguments()` предоставляет гораздо более ясную ошибку, конкретно указывая, что аргументы отсутствуют для тега `{repeat}`. + +Метод `print()` генерирует PHP-код, ответственный за выполнение логики повторения во время выполнения. Он начинается с генерации уникальных имен для временных PHP-переменных, которые ему понадобятся. + +Метод `$context->format()` вызывается с новым плейсхолдером `%raw`, который вставляет *сырую строку*, предоставленную в качестве соответствующего аргумента. Здесь он вставляет уникальное имя переменной, хранящееся в `$countVar` (например, `$__count_1`). А что насчет `%0.raw` и `%2.raw`? Это демонстрирует **позиционные плейсхолдеры**. Вместо простого `%raw`, который берет *следующий* доступный сырой аргумент, `%2.raw` явно берет аргумент с индексом 2 (которым является `$iteratorVar`) и вставляет его сырое строковое значение. Это позволяет нам повторно использовать строку `$iteratorVar` без ее многократной передачи в списке аргументов для `format()`. + +Этот тщательно сконструированный вызов `format()` генерирует эффективный и безопасный PHP-цикл, который правильно обрабатывает выражение количества и избегает конфликтов имен переменных, даже когда теги `{repeat}` вложены. + + +Регистрация и использование +--------------------------- + +Зарегистрируйте тег в вашем расширении: + +```php +use App\Latte\RepeatNode; + +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), // Регистрация тега repeat + ]; + } +} +``` + +Используйте его в шаблоне, включая вложенность: + +```latte +{var $rows = rand(5, 7)} +{var $cols = rand(3, 5)} + +{repeat $rows} +
                                                                                                            + {repeat $cols} + + {/repeat} + +{/repeat} +``` + +Этот пример демонстрирует, как обрабатывать состояние (счетчики циклов) и потенциальные проблемы с вложенностью с помощью временных переменных с префиксом `$__` и уникальных с ID от `PrintContext::generateId()`. + + +Чистые n:атрибуты +----------------- + +В то время как многие `n:атрибуты`, такие как `n:if` или `n:foreach`, служат удобными сокращениями для их аналогов в парных тегах (`{if}...{/if}`, `{foreach}...{/foreach}`), Latte также позволяет определять теги, которые *существуют только* в форме n:атрибута. Они часто используются для изменения атрибутов или поведения HTML-элемента, к которому они присоединены. + +Стандартные примеры, встроенные в Latte, включают [`n:class` |tags#n:class], который помогает динамически собрать атрибут `class`, и [`n:attr` |tags#n:attr], который может установить несколько произвольных атрибутов. + +Давайте создадим наш собственный чистый n:атрибут: `n:confirm`, который добавляет диалоговое окно подтверждения JavaScript перед выполнением действия (например, переходом по ссылке или отправкой формы). + +**Цель:** Реализовать `n:confirm="'Вы уверены?'"`, который добавляет обработчик `onclick` для предотвращения действия по умолчанию, если пользователь отменяет диалоговое окно подтверждения. + + +Реализация `ConfirmNode` +------------------------ + +Нам нужен класс Node и функция парсинга. + +```php +expectArguments(); + $node = $tag->node = new self; + $node->message = $tag->parser->parseExpression(); + return $node; + } + + /** + * Генерирует код атрибута 'onclick' с правильным экранированием. + */ + public function print(PrintContext $context): string + { + // Обеспечивает правильное экранирование для контекстов JavaScript и HTML-атрибута. + return $context->format( + <<<'XX' + echo ' onclick="', LR\Filters::escapeHtmlAttr('return confirm(' . LR\Filters::escapeJs(%node) . ')'), '"' %line; + XX, + $this->message, + $this->position, + ); + } + + public function &getIterator(): \Generator + { + yield $this->message; + } +} +``` + +Метод `print()` генерирует PHP-код, который в конечном итоге во время рендеринга шаблона выводит HTML-атрибут `onclick="..."`. Обработка вложенных контекстов (JavaScript внутри HTML-атрибута) требует тщательного экранирования. Фильтр `LR\Filters::escapeJs(%node)` вызывается во время выполнения и экранирует сообщение правильно для использования внутри JavaScript (вывод был бы как `"Sure?"`). Затем фильтр `LR\Filters::escapeHtmlAttr(...)` экранирует символы, которые являются специальными в HTML-атрибутах, так что это изменило бы вывод на `return confirm("Sure?")`. Это двухступенчатое экранирование во время выполнения гарантирует, что сообщение безопасно для JavaScript, а результирующий JavaScript-код безопасен для вставки в HTML-атрибут `onclick`. + + +Регистрация и использование +--------------------------- + +Зарегистрируйте n:атрибут в вашем расширении. Не забудьте префикс `n:` в ключе: + +```php +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), + 'n:confirm' => ConfirmNode::create(...), // Регистрация n:confirm + ]; + } +} +``` + +Теперь вы можете использовать `n:confirm` на ссылках, кнопках или элементах формы: + +```latte +Удалить +``` + +Сгенерированный HTML: + +```html +Удалить +``` + +Когда пользователь нажимает на ссылку, браузер выполняет код `onclick`, отображает диалоговое окно подтверждения и переходит на `delete.php` только в том случае, если пользователь нажимает "OK". + +Этот пример демонстрирует, как можно создать чистый n:атрибут для изменения поведения или атрибутов своего хост-HTML-элемента путем генерации соответствующего PHP-кода в его методе `print()`. Не забывайте о двойном экранировании, которое часто требуется: один раз для целевого контекста (JavaScript в данном случае) и снова для контекста HTML-атрибута. + + +Продвинутые темы +================ + +В то время как предыдущие разделы охватывают основные концепции, вот несколько более продвинутых тем, с которыми вы можете столкнуться при создании пользовательских тегов Latte. + + +Режимы вывода тегов +------------------- + +Объект `Tag`, переданный вашей функции `create()`, имеет свойство `outputMode`. Это свойство влияет на то, как Latte обрабатывает окружающие пробелы и отступы, особенно когда тег используется на отдельной строке. Вы можете изменить это свойство в вашей функции `create()`. + +- `Tag::OutputKeepIndentation` (По умолчанию для большинства тегов, таких как `{=...}`): Latte пытается сохранить отступ перед тегом. Новые строки *после* тега обычно сохраняются. Это подходит для тегов, которые выводят содержимое в строке. +- `Tag::OutputRemoveIndentation` (По умолчанию для блочных тегов, таких как `{if}`, `{foreach}`): Latte удаляет начальный отступ и потенциально одну следующую новую строку. Это помогает поддерживать чистоту сгенерированного PHP-кода и предотвращает появление дополнительных пустых строк в HTML-выводе, вызванных самим тегом. Используйте это для тегов, представляющих управляющие структуры или блоки, которые сами по себе не должны добавлять пробелы. +- `Tag::OutputNone` (Используется тегами, такими как `{var}`, `{default}`): Похоже на `RemoveIndentation`, но сигнализирует более сильно, что сам тег не производит прямого вывода, потенциально влияя на обработку пробелов вокруг него еще более агрессивно. Подходит для декларативных или установочных тегов. + +Выберите режим, который наилучшим образом соответствует назначению вашего тега. Для большинства структурных или управляющих тегов обычно подходит `OutputRemoveIndentation`. + + +Доступ к родительским/ближайшим тегам +------------------------------------- + +Иногда поведение тега должно зависеть от контекста, в котором он используется, в частности, в каком родительском теге(ах) он находится. Объект `Tag`, переданный вашей функции `create()`, предоставляет метод `closestTag(array $classes, ?callable $condition = null): ?Tag` именно для этой цели. + +Этот метод ищет вверх по иерархии текущих открытых тегов (включая HTML-элементы, представленные внутренне во время парсинга) и возвращает объект `Tag` ближайшего предка, который соответствует специфическим критериям. Если соответствующий предок не найден, возвращает `null`. + +Массив `$classes` указывает, какой тип предковых тегов вы ищете. Он проверяет, является ли связанный узел предкового тега (`$ancestorTag->node`) экземпляром этого класса. + +```php +function create(Tag $tag) +{ + // Поиск ближайшего предкового тега, узел которого является экземпляром ForeachNode + $foreachTag = $tag->closestTag([ForeachNode::class]); + if ($foreachTag) { + // Мы можем получить доступ к самому экземпляру ForeachNode: + $foreachNode = $foreachTag->node; + } +} +``` + +Обратите внимание на `$foreachTag->node`: это работает только потому, что в разработке тегов Latte принято немедленно присваивать созданный узел `$tag->node` внутри метода `create()`, как мы всегда делали. + +Иногда простого сравнения типа узла недостаточно. Вам может потребоваться проверить специфическое свойство потенциального предкового тега или его узла. Необязательный второй аргумент для `closestTag()` — это callable, который принимает потенциальный предковый объект `Tag` и должен возвращать, является ли он действительным совпадением. + +```php +function create(Tag $tag) +{ + $dynamicBlockTag = $tag->closestTag( + [BlockNode::class], + // Условие: блок должен быть динамическим + fn(Tag $blockTag) => $blockTag->node->block->isDynamic(), + ); +} +``` + +Использование `closestTag()` позволяет создавать теги, которые осведомлены о контексте и обеспечивают правильное использование в структуре вашего шаблона, что приводит к более надежным и понятным шаблонам. + + +Плейсхолдеры `PrintContext::format()` +------------------------------------- + +Мы часто использовали `PrintContext::format()` для генерации PHP-кода в методах `print()` наших узлов. Он принимает строку-маску и последующие аргументы, которые заменяют плейсхолдеры в маске. Вот краткое изложение доступных плейсхолдеров: + +- **`%node`**: Аргумент должен быть экземпляром `Node`. Вызывает метод `print()` узла и вставляет результирующую строку PHP-кода. +- **`%dump`**: Аргумент — любое значение PHP. Экспортирует значение в действительный PHP-код. Подходит для скаляров, массивов, null. + - `$context->format('echo %dump;', 'Hello')` -> `echo 'Hello';` + - `$context->format('$arr = %dump;', [1, 2])` -> `$arr = [1, 2];` +- **`%raw`**: Вставляет аргумент непосредственно в выходной PHP-код без какого-либо экранирования или модификации. **Используйте с осторожностью**, в основном для вставки предварительно сгенерированных фрагментов PHP-кода или имен переменных. + - `$context->format('%raw = 1;', '$variableName')` -> `$variableName = 1;` +- **`%args`**: Аргумент должен быть `Expression\ArrayNode`. Выводит элементы массива, отформатированные как аргументы для вызова функции или метода (разделенные запятыми, обрабатывает именованные аргументы, если они присутствуют). + - `$argsNode = new ArrayNode([...]);` + - `$context->format('myFunc(%args);', $argsNode)` -> `myFunc(1, name: 'Joe');` +- **`%line`**: Аргумент должен быть объектом `Position` (обычно `$this->position`). Вставляет PHP-комментарий `/* line X */`, указывающий номер строки источника. + - `$context->format('echo "Hi" %line;', $this->position)` -> `echo "Hi" /* line 42 */;` +- **`%escape(...)`**: Генерирует PHP-код, который *во время выполнения* экранирует внутреннее выражение с использованием текущих контекстно-зависимых правил экранирования. + - `$context->format('echo %escape(%node);', $variableNode)` +- **`%modify(...)`**: Аргумент должен быть `ModifierNode`. Генерирует PHP-код, который применяет фильтры, указанные в `ModifierNode`, к внутреннему содержимому, включая контекстно-зависимое экранирование, если оно не отключено с помощью `|noescape`. + - `$context->format('%modify(%node);', $modifierNode, $variableNode)` +- **`%modifyContent(...)`**: Похоже на `%modify`, но предназначено для модификации блоков захваченного содержимого (часто HTML). + +Вы можете явно ссылаться на аргументы по их индексу (начиная с нуля): `%0.node`, `%1.dump`, `%2.raw` и т.д. Это позволяет повторно использовать аргумент несколько раз в маске без его повторной передачи в `format()`. См. пример тега `{repeat}`, где использовались `%0.raw` и `%2.raw`. + + +Пример комплексного парсинга аргументов +--------------------------------------- + +В то время как `parseExpression()`, `parseArguments()` и т.д. покрывают многие случаи, иногда вам нужна более сложная логика парсинга с использованием низкоуровневого `TokenStream`, доступного через `$tag->parser->stream`. + +**Цель:** Создать тег `{embedYoutube $videoID, width: 640, height: 480}`. Мы хотим распарсить обязательный ID видео (строку или переменную), за которым следуют необязательные пары ключ-значение для размеров. + +```php +expectArguments(); + $node = $tag->node = new self; + // Парсинг обязательного ID видео + $node->videoId = $tag->parser->parseExpression(); + + // Парсинг необязательных пар ключ-значение + $stream = $tag->parser->stream; // Получение потока токенов + while ($stream->tryConsume(',')) { // Требует разделения запятой + // Ожидание идентификатора 'width' или 'height' + $keyToken = $stream->consume(Token::Php_Identifier); + $key = strtolower($keyToken->text); + + $stream->consume(':'); // Ожидание разделителя двоеточия + + $value = $tag->parser->parseExpression(); // Парсинг выражения значения + + if ($key === 'width') { + $node->width = $value; + } elseif ($key === 'height') { + $node->height = $value; + } else { + throw new CompileException("Неизвестный аргумент '$key'. Ожидалось 'width' или 'height'.", $keyToken->position); + } + } + + return $node; + } +} +``` + +Этот уровень контроля позволяет вам определять очень специфические и комплексные синтаксисы для ваших пользовательских тегов путем прямого взаимодействия с потоком токенов. + + +Использование `AuxiliaryNode` +----------------------------- + +Latte предоставляет общие "вспомогательные" узлы для особых ситуаций во время генерации кода или в рамках проходов компиляции. Это `AuxiliaryNode` и `Php\Expression\AuxiliaryNode`. + +Считайте `AuxiliaryNode` гибким узлом-контейнером, который делегирует свои основные функциональности — генерацию кода и предоставление дочерних узлов — аргументам, предоставленным в его конструкторе: + +- Делегирование `print()`: Первый аргумент конструктора — это PHP **closure**. Когда Latte вызывает метод `print()` на `AuxiliaryNode`, он выполняет это предоставленное closure. Closure принимает `PrintContext` и любые узлы, переданные во втором аргументе конструктора, что позволяет вам определить полностью пользовательскую логику генерации PHP-кода во время выполнения. +- Делегирование `getIterator()`: Второй аргумент конструктора — это **массив объектов `Node`**. Когда Latte нужно пройти по дочерним элементам `AuxiliaryNode` (например, во время проходов компиляции), его метод `getIterator()` просто предоставляет узлы, перечисленные в этом массиве. + +Пример: + +```php +$node = new AuxiliaryNode( + // 1. Это closure становится телом print() + fn(PrintContext $context, $arg1, $arg2) => $context->format('...%node...%node...', $arg1, $arg2), + + // 2. Эти узлы предоставляются методом getIterator() и передаются в closure выше + [$argumentNode1, $argumentNode2] +); +``` + +Latte предоставляет два различных типа в зависимости от того, где вам нужно вставить сгенерированный код: + +- `Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode`: Используйте это, когда вам нужно сгенерировать кусок PHP-кода, представляющий **выражение** +- `Latte\Compiler\Nodes\AuxiliaryNode`: Используйте это для более общих целей, когда вам нужно вставить блок PHP-кода, представляющий одну или несколько **инструкций** + +Важной причиной использования `AuxiliaryNode` вместо стандартных узлов (таких как `StaticMethodCallNode`) в вашем методе `print()` или проходе компиляции является **контроль видимости для последующих проходов компиляции**, особенно тех, которые связаны с безопасностью, таких как Sandbox. + +Рассмотрим сценарий: Ваш проход компиляции должен обернуть предоставленное пользователем выражение (`$userExpr`) вызовом специфической, доверенной вспомогательной функции `myInternalSanitize($userExpr)`. Если вы создадите стандартный узел `new FunctionCallNode('myInternalSanitize', [$userExpr])`, он будет полностью виден для прохода AST. Если проход Sandbox запускается позже и `myInternalSanitize` *не* находится в его списке разрешенных, Sandbox может *заблокировать* или изменить этот вызов, потенциально нарушая внутреннюю логику вашего тега, даже если *вы*, автор тега, знаете, что этот специфический вызов безопасен и необходим. Поэтому вы можете генерировать вызов непосредственно внутри closure `AuxiliaryNode`. + +```php +use Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode; + +// ... внутри print() или прохода компиляции ... +$wrappedNode = new AuxiliaryNode( + fn(PrintContext $context, $userExpr) => $context->format( + 'myInternalSanitize(%node)', // Прямая генерация PHP-кода + $userExpr, + ), + // ВАЖНО: Все равно передайте оригинальный узел пользовательского выражения здесь! + [$userExpr], +); +``` + +В этом случае проход Sandbox видит `AuxiliaryNode`, но **не анализирует PHP-код, генерируемый его closure**. Он не может напрямую заблокировать вызов `myInternalSanitize`, генерируемый *внутри* closure. + +Хотя сам сгенерированный PHP-код скрыт от проходов, *входы* в этот код (узлы, представляющие пользовательские данные или выражения) **должны по-прежнему быть проходимыми**. Поэтому второй аргумент конструктора `AuxiliaryNode` является основополагающим. **Вы должны** передать массив, содержащий все оригинальные узлы (такие как `$userExpr` в примере выше), которые использует ваше closure. `getIterator()` `AuxiliaryNode` **предоставит эти узлы**, позволяя проходам компиляции, таким как Sandbox, анализировать их на предмет потенциальных проблем. + + +Лучшие практики +=============== + +- **Ясная цель:** Убедитесь, что ваш тег имеет ясную и необходимую цель. Не создавайте теги для задач, которые легко решаются с помощью [фильтров |custom-filters] или [функций |custom-functions]. +- **Правильно реализуйте `getIterator()`:** Всегда реализуйте `getIterator()` и предоставляйте *ссылки* (`&`) на *все* дочерние узлы (аргументы, содержимое), которые были распарсены из шаблона. Это необходимо для проходов компиляции, безопасности (Sandbox) и потенциальных будущих оптимизаций. +- **Публичные свойства для узлов:** Свойства, содержащие дочерние узлы, делайте публичными, чтобы проходы компиляции могли их при необходимости изменять. +- **Используйте `PrintContext::format()`:** Используйте метод `format()` для генерации PHP-кода. Он обрабатывает кавычки, правильно экранирует плейсхолдеры и автоматически добавляет комментарии с номером строки. +- **Временные переменные (`$__`):** При генерации PHP-кода времени выполнения, которому нужны временные переменные (например, для хранения промежуточных итогов, счетчиков циклов), используйте конвенцию префикса `$__` для избежания конфликтов с пользовательскими переменными и внутренними переменными Latte `$ʟ_`. +- **Вложенность и уникальные ID:** Если ваш тег может быть вложенным или нуждается в состоянии, специфичном для экземпляра во время выполнения, используйте `$context->generateId()` внутри вашего метода `print()` для создания уникальных суффиксов для ваших временных переменных `$__`. +- **Поставщики для внешних данных:** Используйте поставщиков (зарегистрированных через `Extension::getProviders()`) для доступа к данным или сервисам времени выполнения ($this->global->...) вместо хардкодинга значений или опоры на глобальное состояние. Используйте префиксы производителя для имен поставщиков. +- **Рассмотрите n:атрибуты:** Если ваш парный тег логически оперирует на одном HTML-элементе, Latte, вероятно, предоставляет автоматическую поддержку `n:атрибута`. Имейте это в виду для удобства пользователя. Если вы создаете тег, модифицирующий атрибут, рассмотрите, является ли чистый `n:атрибут` наиболее подходящей формой. +- **Тестирование:** Пишите тесты для ваших тегов, охватывающие как парсинг различных синтаксических входов, так и правильность вывода сгенерированного **PHP-кода**. + +Следуя этим рекомендациям, вы можете создавать мощные, надежные и поддерживаемые пользовательские теги, которые без проблем интегрируются с движком шаблонов Latte. + +.[note] +Изучение классов узлов, входящих в состав Latte, — лучший способ узнать все подробности процесса парсинга. diff --git a/latte/ru/develop.texy b/latte/ru/develop.texy index c4d6386dc0..6d70d02ea8 100644 --- a/latte/ru/develop.texy +++ b/latte/ru/develop.texy @@ -1,70 +1,66 @@ -Практика для разработчиков -************************** +Практики разработки +******************* -Установка .[#toc-installation] -============================== +Установка +========= -Лучший способ установки Latte - это использование Composer: +Лучший способ установить Latte — это с помощью Composer: ```shell composer require latte/latte ``` -Поддерживаемые версии PHP (применяется к последним версиям патча Latte): +Поддерживаемые версии PHP (применяется к последним патч-версиям Latte): -| версия | совместимая с PHP +| версия | совместимо с PHP |-----------------|------------------- -| Latte 3.0 | PHP 8.0 - 8.2 -| Latte 2.11 | PHP 7.1 - 8.2 -| Latte 2.8 - 2.10| PHP 7.1 - 8.1 +| Latte 3.0 | PHP 8.0 – 8.2 -Как рендерить шаблон .[#toc-how-to-render-a-template] -===================================================== +Как отобразить шаблон +===================== -Как отобразить шаблон? Просто используйте этот простой код: +Как отобразить шаблон? Для этого достаточно этого простого кода: ```php $latte = new Latte\Engine; -// каталог кэша +// каталог для кеша $latte->setTempDirectory('/path/to/tempdir'); -$params = [ /* template variables */ ]; -// или $params = new TemplateParameters(/* ... */); +$params = [ /* переменные шаблона */ ]; +// or $params = new TemplateParameters(/* ... */); -// рендеринг в вывод +// рендерить на вывод $latte->render('template.latte', $params); -// или вывести в переменную +// рендерить в переменную $output = $latte->renderToString('template.latte', $params); ``` -Параметрами могут быть массивы или еще лучше [объекты |#Parameters as a class], что обеспечит проверку типов и предложение в редакторе. +Параметры могут быть массивом или, еще лучше, [объектом |#Параметры как класс], который обеспечит проверку типов и автодополнение в редакторах. .[note] -Примеры использования вы также можете найти в репозитории [Latte examples |https://github.com/nette-examples/latte]. +Примеры использования вы также найдете в репозитории [Latte examples |https://github.com/nette-examples/latte]. -Производительность и кэширование .[#toc-performance-and-caching] -================================================================ +Производительность и кеш +======================== -Шаблоны Latte чрезвычайно быстры, поскольку Latte компилирует их непосредственно в PHP-код и кэширует на диске. Таким образом, они не имеют дополнительных накладных расходов по сравнению с шаблонами, написанными на чистом PHP. +Шаблоны в Latte чрезвычайно быстры, так как Latte компилирует их прямо в PHP-код и сохраняет в кеше на диск. Таким образом, у них нет никаких дополнительных накладных расходов по сравнению с шаблонами, написанными на чистом PHP. -Кэш автоматически обновляется каждый раз, когда вы изменяете исходный файл. Таким образом, вы можете удобно редактировать шаблоны Latte во время разработки и сразу же видеть изменения в браузере. Вы можете отключить эту функцию в производственной среде и сэкономить немного производительности: +Кеш автоматически регенерируется каждый раз, когда вы изменяете исходный файл. Во время разработки вы можете удобно редактировать шаблоны в Latte, и изменения сразу же видны в браузере. Эту функцию можно отключить в production-среде и сэкономить немного производительности: ```php $latte->setAutoRefresh(false); ``` -При развертывании на рабочем сервере начальная генерация кэша, особенно для больших приложений, может занять некоторое время. Latte имеет встроенную защиту от "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. -Это ситуация, когда сервер получает большое количество одновременных запросов, и поскольку кэш Latte еще не существует, все они будут генерировать его одновременно. Что приводит к скачкам процессора. -Latte умна, и когда есть несколько одновременных запросов, только первый поток генерирует кэш, остальные ждут и затем используют его. +При развертывании на production-сервере первоначальная генерация кеша, особенно для более крупных приложений, может, естественно, занять некоторое время. Latte имеет встроенное предотвращение "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Это ситуация, когда одновременно поступает большое количество параллельных запросов, которые запускают Latte, и поскольку кеш еще не существует, все они начали бы генерировать его одновременно. Что непропорционально нагрузило бы сервер. Latte умный и при нескольких параллельных запросах генерирует кеш только первый поток, остальные ждут и впоследствии используют его. -Параметры как класс .[#toc-parameters-as-a-class] -================================================= +Параметры как класс +=================== -Лучше, чем передавать переменные в шаблон в виде массивов, создать класс. Вы получаете [безопасную для типов нотацию |type-system], [красивое предложение в IDE |recipes#Editors and IDE] и способ [регистрации фильтров |extending-latte#Filters Using the Class] и [функций |extending-latte#Functions Using the Class]. +Лучше, чем передавать переменные в шаблон как массив, — это создать класс. Вы получите [типобезопасную запись |type-system], [приятное автодополнение в IDE |recipes#Редакторы и IDE] и путь для [регистрации фильтров |custom-filters#Фильтры использующие класс с атрибутами] и [функций |custom-functions#Функции использующие класс с атрибутами]. ```php class MailTemplateParameters @@ -88,12 +84,12 @@ $latte->render('mail.latte', new MailTemplateParameters( ``` -Отключение автоматического удаления переменной .[#toc-disabling-auto-escaping-of-variable] -========================================================================================== +Отключение автоэкранирования переменной +======================================= -Если переменная содержит HTML-строку, вы можете пометить ее так, чтобы Latte автоматически (и, следовательно, дважды) не экранировала ее. Это позволяет избежать необходимости указывать `|noescape` в шаблоне. +Если переменная содержит строку в HTML, вы можете пометить ее так, чтобы Latte автоматически (и, следовательно, дважды) ее не экранировал. Вы избежите необходимости указывать в шаблоне `|noescape`. -Самый простой способ - обернуть строку в объект `Latte\Runtime\Html`: +Самый простой способ — обернуть строку в объект `Latte\Runtime\Html`: ```php $params = [ @@ -101,7 +97,7 @@ $params = [ ]; ``` -Latte также не экранирует все объекты, реализующие интерфейс `Latte\HtmlStringable`. Поэтому вы можете создать свой собственный класс, метод которого `__toString()` будет возвращать HTML-код, который не будет экранироваться автоматически: +Latte также не экранирует все объекты, которые реализуют интерфейс `Latte\HtmlStringable`. Вы можете создать собственный класс, метод `__toString()` которого будет возвращать HTML-код, который не будет автоматически экранироваться: ```php class Emphasis extends Latte\HtmlStringable @@ -123,32 +119,85 @@ $params = [ ``` .[warning] -Метод `__toString` должен возвращать корректный HTML и обеспечивать экранирование параметров, иначе может возникнуть XSS-уязвимость! +Метод `__toString` должен возвращать корректный HTML и обеспечивать экранирование параметров, иначе может возникнуть уязвимость XSS! -Как расширить Latte с помощью фильтров, тегов и т.д. .[#toc-how-to-extend-latte-with-filters-tags-etc] -====================================================================================================== +Как расширить Latte фильтрами, тегами и т.д. +============================================ -Как добавить в Latte пользовательский фильтр, функцию, тег и т.д.? Узнайте об этом в главе " [Расширение Latte |extending Latte]". -Если вы хотите повторно использовать свои изменения в различных проектах или поделиться ими с другими, вам следует [создать расширение |creating-extension]. +Как добавить в Latte собственный фильтр, функцию, тег и т.д.? Об этом рассказывается в главе [расширяем Latte |extending-latte]. Если вы хотите повторно использовать свои изменения в разных проектах или поделиться ими с другими, вам следует [создать расширение |extending-latte#Latte Extension]. -Любой код в шаблоне `{php ...}` .{data-version:3.0}{toc: RawPhpExtension} -========================================================================= +Любой код в шаблоне `{php ...}` .{toc: RawPhpExtension} +======================================================= -Внутри тега [`{do}` |tags#do] поэтому вы не можете, например, вставлять такие конструкции, как `if ... else` или утверждения, завершаемые точкой с запятой. +Внутри тега [`{do}` |tags#do] можно записывать только PHP-выражения, вы не можете, например, вставить конструкции типа `if ... else` или операторы, завершенные точкой с запятой. -Однако вы можете зарегистрировать расширение `RawPhpExtension`, которое добавляет тег `{php ...}`, который может быть использован для вставки любого PHP-кода на страх и риск автора шаблона. +Однако вы можете зарегистрировать расширение `RawPhpExtension`, которое добавляет тег `{php ...}`. С его помощью можно вставлять любой PHP-код. На него не распространяются никакие правила режима песочницы, поэтому использование лежит на ответственности автора шаблона. ```php $latte->addExtension(new Latte\Essential\RawPhpExtension); ``` -Перевод в шаблонах .{data-version:3.0}{toc: TranslatorExtension} -================================================================ +Проверка сгенерированного кода .{data-version:3.0.7} +==================================================== -Используйте расширение `TranslatorExtension`, чтобы добавить [`{_...}` |tags#_], [`{translate}` |tags#translate] и фильтр [`translate` |filters#translate] в шаблон. Они используются для перевода значений или частей шаблона на другие языки. Параметр - это метод (вызываемый PHP), который выполняет перевод: +Latte компилирует шаблоны в PHP-код. Разумеется, он следит за тем, чтобы сгенерированный код был синтаксически валидным. Однако при использовании расширений третьих сторон или `RawPhpExtension` Latte не может гарантировать правильность сгенерированного файла. Также в PHP можно записать код, который, хотя и синтаксически правильный, но запрещен (например, присваивание значения переменной `$this`) и вызывает PHP Compile Error. Если вы запишете такую операцию в шаблоне, она попадет и в сгенерированный PHP-код. Поскольку в PHP существует около двух сотен различных запрещенных операций, Latte не ставит перед собой задачу их обнаруживать. На них укажет само PHP при рендеринге, что обычно ничему не мешает. + +Однако бывают ситуации, когда вы хотите знать уже во время компиляции шаблона, что он не содержит никаких PHP Compile Error. Особенно тогда, когда шаблоны могут редактировать пользователи, или вы используете [Sandbox |sandbox]. В таком случае настройте проверку шаблонов уже во время компиляции. Эту функциональность можно включить методом `Engine::enablePhpLint()`. Поскольку для проверки требуется вызывать бинарный файл PHP, передайте путь к нему в качестве параметра: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); + +try { + $latte->compile('home.latte'); +} catch (Latte\CompileException $e) { + // перехватывает ошибки в Latte, а также Compile Error в PHP + echo 'Error: ' . $e->getMessage(); +} +``` + + +Национальная среда .{data-version:3.0.18}{toc: Locale} +====================================================== + +Latte позволяет установить национальную среду (локаль), которая влияет на форматирование чисел, дат и сортировку. Она устанавливается с помощью метода `setLocale()`. Идентификатор среды руководствуется стандартом IETF language tag, который использует расширение PHP `intl`. Он состоит из кода языка и, возможно, кода страны, например, `en_US` для английского языка в Соединенных Штатах, `de_DE` для немецкого языка в Германии и т.д. + +```php +$latte = new Latte\Engine; +$latte->setLocale('ru_RU'); +``` + +Настройка среды влияет на фильтры [localDate |filters#localDate], [sort |filters#sort], [number |filters#number] и [bytes |filters#bytes]. + +.[note] +Требует PHP-расширения `intl`. Настройка в Latte не влияет на глобальную настройку локали в PHP. + + +Строгий режим .{data-version:3.0.8} +=================================== + +В строгом режиме парсинга Latte контролирует, не отсутствуют ли закрывающие HTML-теги, а также запрещает использование переменной `$this`. Включить его можно так: + +```php +$latte = new Latte\Engine; +$latte->setStrictParsing(); +``` + +Генерацию шаблонов с заголовком `declare(strict_types=1)` можно включить так: + +```php +$latte = new Latte\Engine; +$latte->setStrictTypes(); +``` + + +Перевод в шаблонах .{toc: TranslatorExtension} +============================================== + +С помощью расширения `TranslatorExtension` вы добавите в шаблон теги [`{_...}` |tags#], [`{translate}` |tags#translate] и фильтр [`translate` |filters#translate]. Они служат для перевода значений или частей шаблона на другие языки. В качестве параметра мы указываем метод (PHP callable), выполняющий перевод: ```php class MyTranslator @@ -158,7 +207,7 @@ class MyTranslator public function translate(string $original): string { - // создать $translated из $original в соответствии с $this->lang + // из $original создаем $translated в соответствии с $this->lang return $translated; } } @@ -170,7 +219,7 @@ $extension = new Latte\Essential\TranslatorExtension( $latte->addExtension($extension); ``` -Переводчик вызывается во время выполнения, когда шаблон отображается. Однако Latte может перевести все статические тексты во время компиляции шаблона. Это экономит производительность, поскольку каждая строка переводится только один раз, а полученный перевод записывается в скомпилированный файл. Таким образом, в каталоге кэша создается несколько скомпилированных версий шаблона, по одной для каждого языка. Для этого достаточно указать язык в качестве второго параметра: +Переводчик вызывается во время выполнения при рендеринге шаблона. Latte, однако, умеет переводить все статические тексты уже во время компиляции шаблона. Это экономит производительность, так как каждая строка переводится только один раз, и результирующий перевод записывается в скомпилированную форму. В каталоге кеша таким образом создается несколько скомпилированных версий шаблона, одна для каждого языка. Для этого достаточно только указать язык в качестве второго параметра: ```php $extension = new Latte\Essential\TranslatorExtension( @@ -179,9 +228,9 @@ $extension = new Latte\Essential\TranslatorExtension( ); ``` -Под статическим текстом подразумевается, например, `{_'hello'}` или `{translate}hello{/translate}`. Нестатический текст, такой как `{_$foo}`, будет продолжать переводиться во время выполнения. +Под статическим текстом подразумевается, например, `{_'hello'}` или `{translate}hello{/translate}`. Нестатические тексты, например, `{_$foo}`, по-прежнему будут переводиться во время выполнения. -Шаблон также может передавать переводчику дополнительные параметры через `{_$original, foo: bar}` или `{translate foo: bar}`, которые он получает в виде массива `$params`: +Переводчику можно из шаблона передавать и дополнительные параметры с помощью `{_$original, foo: bar}` или `{translate foo: bar}`, которые он получит как массив `$params`: ```php public function translate(string $original, ...$params): string @@ -191,66 +240,73 @@ public function translate(string $original, ...$params): string ``` -Отладка и трассировка .[#toc-debugging-and-tracy] -================================================= +Отладка и Tracy +=============== -Latte старается сделать разработку как можно более приятной. Для целей отладки существует три тега [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak] и [`{trace}` |tags#trace]. +Latte старается сделать разработку как можно более приятной. Непосредственно для целей отладки существует тройка тегов [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak] и [`{trace}` |tags#trace]. -Вы получите максимальный комфорт, если установите замечательный [инструмент отладки Tracy |tracy:] и активируете плагин Latte: +Наибольший комфорт вы получите, если еще установите замечательный [инструмент отладки Tracy|tracy:] и активируете дополнение для Latte: ```php -// включает Трейси +// включает Tracy Tracy\Debugger::enable(); $latte = new Latte\Engine; -// активирует расширение Трейси +// активирует расширение для Tracy $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension); ``` -Теперь вы будете видеть все ошибки на аккуратном красном экране, включая ошибки в шаблонах с подсветкой строк и столбцов[(видео |https://github.com/nette/tracy/releases/tag/v2.9.0]). -В то же время в правом нижнем углу, в так называемой панели Tracy, появится вкладка для Latte, где вы сможете наглядно увидеть все отрисованные шаблоны и их взаимосвязи (включая возможность щелкнуть в шаблон или скомпилированный код), а также переменные: +Теперь все ошибки будут отображаться на наглядном красном экране, включая ошибки в шаблонах с подсветкой строки и столбца ([видео|https://github.com/nette/tracy/releases/tag/v2.9.0]). Одновременно в правом нижнем углу в так называемом Tracy Baru появится вкладка для Latte, где наглядно видны все рендеримые шаблоны и их взаимные связи (включая возможность перейти по ссылке в шаблон или скомпилированный код), а также переменные: [* latte-debugging.webp *] -Поскольку Latte компилирует шаблоны в читаемый PHP-код, вы можете удобно просматривать их в вашей IDE. +Поскольку Latte компилирует шаблоны в понятный PHP-код, вы можете удобно пошагово выполнять их в своей IDE. -Linter: Проверка синтаксиса шаблона .{data-version:2.11}{toc: Linter} -===================================================================== +Linter: валидация синтаксиса шаблонов .{toc: Linter} +==================================================== -Инструмент Linter поможет вам просмотреть все шаблоны и проверить их на наличие синтаксических ошибок. Он запускается из консоли: +Пройти все шаблоны и проверить, не содержат ли они синтаксических ошибок, вам поможет инструмент Linter. Он запускается из консоли: ```shell -vendor/bin/latte-lint +vendor/bin/latte-lint <путь> ``` -Если вы используете пользовательские теги, также создайте свой пользовательский Linter, например, `custom-latte-lint`: +Параметром `--strict` активируется [#строгий режим]. + +Если вы используете собственные теги, создайте также собственную версию Linter, например, `custom-latte-lint`: ```php #!/usr/bin/env php scanDirectory($path); +$path = $argv[1] ?? '.'; -$engine = new Latte\Engine; -// регистрирует отдельные расширения здесь -$engine->addExtension(/* ... */); +$linter = new Latte\Tools\Linter; +$latte = $linter->getEngine(); +// здесь добавьте отдельные свои расширения +$latte->addExtension(/* ... */); -$path = $argv[1]; -$linter = new Latte\Tools\Linter(engine: $engine); $ok = $linter->scanDirectory($path); -exit($ok ? 0: 1); +exit($ok ? 0 : 1); +``` + +Альтернативно вы можете передать собственный объект `Latte\Engine` в Linter: + +```php +$latte = new Latte\Engine; +// здесь конфигурируем объект $latte +$linter = new Latte\Tools\Linter(engine: $latte); ``` -Загрузка шаблонов из строки .[#toc-loading-templates-from-a-string] -=================================================================== +Загрузка шаблонов из строки +=========================== -Вам нужно загрузить шаблоны из строк, а не из файлов, возможно, в целях тестирования? [StringLoader |extending-latte#stringloader] поможет вам: +Нужно загружать шаблоны из строк вместо файлов, например, для целей тестирования? Вам поможет [StringLoader |loaders#StringLoader]: ```php $latte->setLoader(new Latte\Loaders\StringLoader([ @@ -262,10 +318,10 @@ $latte->render('main.file', $params); ``` -Обработчик исключений .[#toc-exception-handler] -=============================================== +Обработчик исключений +===================== -Вы можете определить свой собственный обработчик ожидаемых исключений. Исключения, возникающие внутри [`{try}` |tags#try] и в [песочнице |sandbox], передаются в него. +Вы можете определить собственный обработчик для ожидаемых исключений. Ему будут переданы исключения, возникшие внутри [`{try}` |tags#try] и в [песочнице |sandbox]. ```php $loggingHandler = function (Throwable $e, Latte\Runtime\Template $template) use ($logger) { @@ -277,17 +333,17 @@ $latte->setExceptionHandler($loggingHandler); ``` -Автоматический поиск макета .[#toc-automatic-layout-lookup] -=========================================================== +Автоматический поиск макета +=========================== -Используя тег [`{layout}` |template-inheritance#layout-inheritance]шаблон определяет свой родительский шаблон. Также возможен автоматический поиск макета, что упростит написание шаблонов, так как в них не нужно будет включать тег `{layout}`. +С помощью тега [`{layout}` |template-inheritance#Наследование макета] шаблон определяет свой родительский шаблон. Также возможно настроить автоматический поиск макета, что упростит написание шаблонов, так как в них не нужно будет указывать тег `{layout}`. -Это достигается следующим образом: +Этого можно достичь следующим способом: ```php $finder = function (Latte\Runtime\Template $template) { if (!$template->getReferenceType()) { - // возвращает путь к файлу родительского шаблона + // возвращает путь к файлу с макетом return 'automatic.layout.latte'; } }; @@ -296,4 +352,4 @@ $latte = new Latte\Engine; $latte->addProvider('coreParentFinder', $finder); ``` -Если шаблон не должен иметь макета, он укажет на это с помощью тега `{layout none}`. +Если шаблон не должен иметь макет, он сообщит об этом тегом `{layout none}`. diff --git a/latte/ru/extending-latte.texy b/latte/ru/extending-latte.texy index ec8af1f7db..ca17ab029c 100644 --- a/latte/ru/extending-latte.texy +++ b/latte/ru/extending-latte.texy @@ -1,285 +1,227 @@ -Удлиняющий Latte +Расширение Latte **************** .[perex] -Latte очень гибок и может быть расширен множеством способов: вы можете добавить пользовательские фильтры, функции, теги, загрузчики и т.д. Мы покажем вам, как это сделать. +Latte разработано с учетом возможности расширения. Хотя стандартный набор тегов, фильтров и функций охватывает многие случаи использования, часто возникает необходимость добавить собственную специфическую логику или вспомогательные инструменты. Эта страница предоставляет обзор способов расширения Latte, чтобы оно идеально соответствовало требованиям вашего проекта — от простых помощников до сложного нового синтаксиса. -В этой главе описаны различные способы расширения Latte. Если вы хотите повторно использовать свои изменения в различных проектах или поделиться ими с другими, вам следует [создать так называемое расширение |creating-extension]. +Способы расширения Latte +======================== -Сколько дорог ведет в Рим? .[#toc-how-many-roads-lead-to-rome] -============================================================== +Вот краткий обзор основных способов настройки и расширения Latte: -Поскольку некоторые из способов расширения Latte могут смешиваться, давайте сначала попробуем объяснить различия между ними. В качестве примера попробуем реализовать генератор *Lorem ipsum*, которому передается количество слов для генерации. +- **[Пользовательские фильтры |Custom Filters]:** Для форматирования или преобразования данных непосредственно в выводе шаблона (например, `{$var|myFilter}`). Идеально подходят для таких задач, как форматирование дат, редактирование текста или применение специфического экранирования. Вы также можете использовать их для изменения больших блоков HTML-содержимого, обернув содержимое анонимным [`{block}` |tags#block] и применив к нему пользовательский фильтр. +- **[Пользовательские функции |Custom Functions]:** Для добавления повторно используемой логики, которую можно вызывать в выражениях шаблона (например, `{myFunction($arg1, $arg2)}`). Полезны для вычислений, доступа к вспомогательным функциям приложения или генерации небольших фрагментов контента. +- **[Пользовательские теги |Custom Tags]:** Для создания совершенно новых языковых конструкций (`{mytag}...{/mytag}` или `n:mytag`). Теги предлагают наибольшие возможности, позволяют определять собственные структуры, управлять парсингом шаблона и реализовывать сложную логику рендеринга. +- **[Проходы компиляции |Compiler Passes]:** Функции, которые изменяют абстрактное синтаксическое дерево (AST) шаблона после парсинга, но до генерации PHP-кода. Используются для продвинутых оптимизаций, проверок безопасности (например, Sandbox) или автоматических изменений кода. +- **[Пользовательские загрузчики |loaders]:** Для изменения способа, которым Latte ищет и загружает файлы шаблонов (например, загрузка из базы данных, зашифрованного хранилища и т. д.). -Основной конструкцией языка Latte является тег. Мы можем реализовать генератор, расширив Latte новым тегом: +Выбор правильного метода расширения является ключевым. Прежде чем создавать сложный тег, подумайте, не будет ли достаточно более простого фильтра или функции. Давайте рассмотрим это на примере: реализация генератора *Lorem ipsum*, который в качестве аргумента принимает количество слов для генерации. -```latte -{lipsum 40} -``` - -Тег будет работать отлично. Однако генератор в виде тега может оказаться недостаточно гибким, поскольку его нельзя использовать в выражении. Кстати, на практике вам редко понадобится генерировать теги; и это хорошая новость, потому что теги - это более сложный способ расширения. - -Хорошо, давайте попробуем создать фильтр вместо тега: - -```latte -{=40|lipsum} -``` - -Опять же, приемлемый вариант. Но фильтр должен преобразовать переданное значение во что-то другое. Здесь мы используем значение `40`, которое указывает на количество созданных слов, как аргумент фильтра, а не как значение, которое мы хотим преобразовать. +- **Как тег?** `{lipsum 40}` - Возможно, но теги больше подходят для управляющих структур или генерации сложных тегов. Теги нельзя использовать непосредственно в выражениях. +- **Как фильтр?** `{=40|lipsum}` - Технически это работает, но фильтры предназначены для *преобразования* входного значения. Здесь `40` — это *аргумент*, а не значение, которое преобразуется. Это кажется семантически неправильным. +- **Как функция?** `{lipsum(40)}` - Это самое естественное решение! Функции принимают аргументы и возвращают значения, что идеально подходит для использования в любом выражении: `{var $text = lipsum(40)}`. -Поэтому давайте попробуем использовать функцию: +**Общая рекомендация:** Используйте функции для вычислений/генерации, фильтры для преобразования и теги для новых языковых конструкций или сложных тегов. Проходы используйте для манипуляции AST, а загрузчики для получения шаблонов. -```latte -{lipsum(40)} -``` -Вот и все! Для этого конкретного примера создание функции - идеальная точка расширения. Вы можете вызвать ее в любом месте, где принимается выражение, например: +Прямая регистрация +================== -```latte -{var $text = lipsum(40)} -``` +Для вспомогательных инструментов, специфичных для проекта, или быстрых расширений Latte позволяет напрямую регистрировать фильтры и функции в объекте `Latte\Engine`. - -Фильтры .[#toc-filters] -======================= - -Создайте фильтр, зарегистрировав его имя и любой вызываемый элемент PHP, например, функцию: +Для регистрации фильтра используйте метод `addFilter()`. Первым аргументом вашей функции-фильтра будет значение перед символом `|`, а последующие аргументы — это те, которые передаются после двоеточия `:`. ```php $latte = new Latte\Engine; -$latte->addFilter('shortify', fn(string $s) => mb_substr($s, 0, 10)); // shortens the text to 10 characters -``` -В этом случае было бы лучше, чтобы фильтр получал дополнительный параметр: +// Определение фильтра (вызываемый объект: функция, статический метод и т.д.) +$myTruncate = fn(string $s, int $length = 50) => mb_substr($s, 0, $length); -```php -$latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); -``` - -Мы используем его в шаблоне следующим образом: +// Регистрация +$latte->addFilter('truncate', $myTruncate); -```latte -

                                                                                                            {$text|shortify}

                                                                                                            -

                                                                                                            {$text|shortify:100}

                                                                                                            +// Использование в шаблоне: {$text|truncate} или {$text|truncate:100} ``` -Как видите, функция получает в качестве следующих аргументов левую часть фильтра перед трубой `|` as the first argument and the arguments passed to the filter after `:`. - -Конечно, функция, представляющая фильтр, может принимать любое количество параметров, также поддерживаются переменные параметры. - - -Фильтры, использующие класс .[#toc-filters-using-the-class] ------------------------------------------------------------ - -Второй способ определить фильтр - [использовать класс |develop#Parameters-as-a-Class]. Мы создаем метод с атрибутом `TemplateFilter`: +Вы также можете зарегистрировать **Загрузчик фильтров**, функцию, которая динамически предоставляет вызываемые объекты фильтров по запрошенному имени: ```php -class TemplateParameters -{ - public function __construct( - // parameters - ) {} - - #[Latte\Attributes\TemplateFilter] - public function shortify(string $s, int $len = 10): string - { - return mb_substr($s, 0, $len); - } -} - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); +$latte->addFilterLoader(fn(string $name) => /* возвращает вызываемый объект или null */); ``` -Если вы используете PHP 7.x и Latte 2.x, используйте аннотацию `/** @filter */` вместо атрибута. - -Загрузчик фильтров .{data-version:2.10}[#toc-filter-loader] ------------------------------------------------------------ - -Вместо регистрации отдельных фильтров вы можете создать так называемый загрузчик, который представляет собой функцию, вызываемую с именем фильтра в качестве аргумента и возвращающую его вызываемый PHP-файл или null. +Для регистрации функции, используемой в выражениях шаблона, используйте `addFunction()`. ```php -$latte->addFilterLoader([new Filters, 'load']); +$latte = new Latte\Engine; +// Определение функции +$isWeekend = fn(DateTimeInterface $date) => $date->format('N') >= 6; -class Filters -{ - public function load(string $filter): ?callable - { - if (in_array($filter, get_class_methods($this))) { - return [$this, $filter]; - } - return null; - } - - public function shortify($s, $len = 10) - { - return mb_substr($s, 0, $len); - } - - // ... -} +// Регистрация +$latte->addFunction('isWeekend', $isWeekend); + +// Использование в шаблоне: {if isWeekend($myDate)}Выходной!{/if} ``` +Больше информации вы найдете в разделах [Создание пользовательских фильтров |custom-filters] и [Функций |custom-functions]. -Контекстные фильтры .[#toc-contextual-filters] ----------------------------------------------- -Контекстный фильтр - это фильтр, который принимает объект [api:Latte\Runtime\FilterInfo] в качестве первого параметра, за которым следуют другие параметры, как в случае с классическими фильтрами. Он регистрируется таким же образом, Latte сам распознает, что фильтр контекстный: +Надежный способ: Расширение Latte .{toc: Latte Extension} +========================================================= -```php -use Latte\Runtime\FilterInfo; +Хотя прямая регистрация проста, стандартным и рекомендуемым способом упаковки и распространения расширений Latte является использование классов **Extension**. Extension служит центральной точкой конфигурации для регистрации нескольких тегов, фильтров, функций, проходов компиляции и других элементов. -$latte->addFilter('foo', function (FilterInfo $info, string $str): string { - // ... -}); -``` +Зачем использовать Extensions? -Контекстные фильтры могут определять и изменять тип содержимого, который они получают в переменной `$info->contentType`. Если фильтр вызывается классически через переменную (например, `{$var|foo}`), то `$info->contentType` будет содержать null. +- **Организация:** Сохраняет связанные расширения (теги, фильтры и т. д. для конкретной функции) вместе в одном классе. +- **Повторное использование и обмен:** Легко упаковывать свои расширения для использования в других проектах или для обмена с сообществом (например, через Composer). +- **Полная мощь:** Пользовательские теги и проходы компиляции *можно регистрировать только* через Extensions. -Фильтр должен сначала проверить, поддерживается ли тип содержимого входной строки. Он также может изменить его. Пример фильтра, который принимает текст (или null) и возвращает HTML: + +Регистрация Extension +--------------------- + +Extension регистрируется в Latte с помощью метода `addExtension()` (или через [файл конфигурации |application:configuration#Шаблоны Latte]): ```php -use Latte\Runtime\FilterInfo; - -$latte->addFilter('money', function (FilterInfo $info, float $amount): string { - // first we check if the input's content-type is text - if (!in_array($info->contentType, [null, ContentType::Text])) { - throw new Exception("Filter |money used in incompatible content type $info->contentType."); - } - - // change content-type to HTML - $info->contentType = ContentType::Html; - return "$num Kč"; -}); +$latte = new Latte\Engine; +$latte->addExtension(new MyProjectExtension); ``` -.[note] -В этом случае фильтр должен обеспечить правильную экранировку данных. +Если вы зарегистрируете несколько расширений, и они определяют теги, фильтры или функции с одинаковыми именами, приоритет будет иметь последнее добавленное расширение. Это также означает, что ваши расширения могут переопределять нативные теги/фильтры/функции. -Все фильтры, которые используются поверх [блоков |tags#block] (например, как `{block|foo}...{/block}`), должны быть контекстными. +Каждый раз, когда вы вносите изменения в класс и автоматическое обновление не отключено, Latte автоматически перекомпилирует ваши шаблоны. -Функции .{data-version:2.6}[#toc-functions] -=========================================== +Создание Extension +------------------ -По умолчанию все собственные функции PHP могут использоваться в Latte, если только это не отключено в песочнице. Но вы также можете определить свои собственные функции. Они могут переопределять собственные функции. +Для создания собственного расширения вам нужно создать класс, который наследуется от [api:Latte\Extension]. Чтобы представить, как выглядит такое расширение, посмотрите на встроенное [CoreExtension |https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php]. -Создайте функцию, зарегистрировав ее имя и любую вызываемую функцию PHP: +Рассмотрим методы, которые вы можете реализовать: -```php -$latte = new Latte\Engine; -$latte->addFunction('random', function (...$args) { - return $args[array_rand($args)]; -}); -``` -После этого использование будет таким же, как и при вызове PHP-функции: +beforeCompile(Latte\Engine $engine): void .[method] +--------------------------------------------------- -```latte -{random(apple, orange, lemon)} // prints for example: apple -``` +Вызывается перед компиляцией шаблона. Метод можно использовать, например, для инициализаций, связанных с компиляцией. -Функции, использующие класс .[#toc-functions-using-the-class] -------------------------------------------------------------- +getTags(): array .[method] +-------------------------- -Второй способ определить функцию - [использовать класс |develop#Parameters-as-a-Class]. Мы создаем метод с атрибутом `TemplateFunction`: +Вызывается при компиляции шаблона. Возвращает ассоциативный массив *имя тега => вызываемый объект*, который представляет собой функции для парсинга тегов. [Подробнее |custom-tags]. ```php -class TemplateParameters +public function getTags(): array { - public function __construct( - // parameters - ) {} - - #[Latte\Attributes\TemplateFunction] - public function random(...$args) - { - return $args[array_rand($args)]; - } + return [ + 'foo' => FooNode::create(...), + 'bar' => BarNode::create(...), + 'n:baz' => NBazNode::create(...), + // ... + ]; } - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); ``` -Если вы используете PHP 7.x и Latte 2.x, используйте аннотацию `/** @function */` вместо атрибута. +Тег `n:baz` представляет собой чистый [n:атрибут |syntax#n:атрибуты], то есть тег, который можно записать только как атрибут. +Для тегов `foo` и `bar` Latte автоматически распознает, являются ли они парными, и если да, то их можно автоматически записывать с помощью n:атрибутов, включая варианты с префиксами `n:inner-foo` и `n:tag-foo`. -Загрузчики .[#toc-loaders] -========================== +Порядок выполнения таких n:атрибутов определяется их порядком в массиве, возвращаемом методом `getTags()`. Таким образом, `n:foo` всегда выполняется перед `n:bar`, даже если атрибуты в HTML-теге указаны в обратном порядке, например `
                                                                                                            `. -Загрузчики отвечают за загрузку шаблонов из источника, например, из файловой системы. Они устанавливаются с помощью метода `setLoader()`: +Если вам нужно определить порядок n:атрибутов между несколькими расширениями, используйте вспомогательный метод `order()`, где параметр `before` xor `after` указывает, какие теги сортируются до или после тега. ```php -$latte->setLoader(new MyLoader); +public function getTags(): array +{ + return [ + 'foo' => self::order(FooNode::create(...), before: 'bar'), + 'bar' => self::order(BarNode::create(...), after: ['block', 'snippet']), + ]; +} ``` -Встроенными загрузчиками являются: +getPasses(): array .[method] +---------------------------- -FileLoader .[#toc-fileloader] ------------------------------ +Вызывается при компиляции шаблона. Возвращает ассоциативный массив *имя прохода => вызываемый объект*, который представляет собой функции, представляющие так называемые [проходы компиляции |compiler-passes], которые проходят и изменяют AST. -Загрузчик по умолчанию. Загружает шаблоны из файловой системы. - -Доступ к файлам можно ограничить, задав базовый каталог: +Здесь также можно использовать вспомогательный метод `order()`. Значение параметров `before` или `after` может быть `*`, что означает до/после всех. ```php -$latte->setLoader(new Latte\Loaders\FileLoader($templateDir)); -$latte->render('test.latte'); +public function getPasses(): array +{ + return [ + 'optimize' => Passes::optimizePass(...), + 'sandbox' => self::order($this->sandboxPass(...), before: '*'), + // ... + ]; +} ``` -StringLoader .[#toc-stringloader] ---------------------------------- +beforeRender(Latte\Engine $engine): void .[method] +-------------------------------------------------- -Загружает шаблоны из строк. Этот загрузчик очень полезен для модульного тестирования. Он также может быть использован для небольших проектов, где имеет смысл хранить все шаблоны в одном PHP-файле. +Вызывается перед каждым рендерингом шаблона. Метод может быть использован, например, для инициализации переменных, используемых во время рендеринга. -```php -$latte->setLoader(new Latte\Loaders\StringLoader([ - 'main.file' => '{include other.file}', - 'other.file' => '{if true} {$var} {/if}', -])); -$latte->render('main.file'); -``` +getFilters(): array .[method] +----------------------------- -Упрощенное использование: +Вызывается перед рендерингом шаблона. Возвращает фильтры как ассоциативный массив *имя фильтра => вызываемый объект*. [Подробнее |custom-filters]. ```php -$template = '{if true} {$var} {/if}'; -$latte->setLoader(new Latte\Loaders\StringLoader); -$latte->render($template); +public function getFilters(): array +{ + return [ + 'batch' => $this->batchFilter(...), + 'trim' => $this->trimFilter(...), + // ... + ]; +} ``` -Создание пользовательского загрузчика .[#toc-creating-a-custom-loader] ----------------------------------------------------------------------- - -Loader - это класс, реализующий интерфейс [api:Latte\Loader]. +getFunctions(): array .[method] +------------------------------- +Вызывается перед рендерингом шаблона. Возвращает функции как ассоциативный массив *имя функции => вызываемый объект*. [Подробнее |custom-functions]. -Теги .[#toc-tags] -================= +```php +public function getFunctions(): array +{ + return [ + 'clamp' => $this->clampFunction(...), + 'divisibleBy' => $this->divisibleByFunction(...), + // ... + ]; +} +``` -Одной из самых интересных возможностей шаблонизатора является возможность определять новые языковые конструкции с помощью тегов. Это также более сложная функциональность, и вам необходимо понимать, как внутренне работает Latte. -В большинстве случаев, однако, тег не нужен: -- если он должен генерировать некоторый вывод, используйте вместо него [функцию |#Functions] -- если нужно изменить входные данные и вернуть их, используйте [filter |#Filters] -- если нужно отредактировать участок текста, оберните его тегом [`{block}` |tags#block] тегом и используйте [фильтр |#Contextual-Filters] -- если он не должен был ничего выводить, а только вызывать функцию, вызовите ее с помощью [`{do}` |tags#do] +getProviders(): array .[method] +------------------------------- -Если вы все еще хотите создать тег, отлично! Все самое необходимое можно найти в разделе [Создание расширения |creating-extension]. +Вызывается перед рендерингом шаблона. Возвращает массив поставщиков, которые обычно являются объектами, используемыми тегами во время выполнения. Доступ к ним осуществляется через `$this->global->...`. [Подробнее |custom-tags#Представление поставщиков]. +```php +public function getProviders(): array +{ + return [ + 'myFoo' => $this->foo, + 'myBar' => $this->bar, + // ... + ]; +} +``` -Передачи компилятора .{data-version:3.0}[#toc-compiler-passes] -============================================================== -Пассы компилятора - это функции, которые изменяют AST или собирают в них информацию. В Latte, например, песочница реализована таким образом: она обходит все узлы AST, находит вызовы функций и методов и заменяет их управляемыми вызовами. +getCacheKey(Latte\Engine $engine): mixed .[method] +-------------------------------------------------- -Как и в случае с тегами, это более сложная функциональность, и вам нужно понимать, как Latte работает под капотом. Все самое необходимое можно найти в главе [Создание расширения |creating-extension]. +Вызывается перед рендерингом шаблона. Возвращаемое значение становится частью ключа, хеш которого содержится в имени файла скомпилированного шаблона. Таким образом, для разных возвращаемых значений Latte сгенерирует разные файлы кеша. diff --git a/latte/ru/filters.texy b/latte/ru/filters.texy index 2e9cb6473f..c24194b9fa 100644 --- a/latte/ru/filters.texy +++ b/latte/ru/filters.texy @@ -1,112 +1,114 @@ -Фильтры для Latte -***************** +Фильтры Latte +************* .[perex] -Фильтры - это функции, которые изменяют или форматируют данные в нужную нам форму. Это краткое описание встроенных фильтров, которые доступны. +В шаблонах мы можем использовать функции, которые помогают изменять или переформатировать данные в конечный вид. Мы называем их *фильтрами*. .[table-latte-filters] -|## Преобразование строк / массивов -| `batch` | [Перечисление линейных данных в таблице |#batch] -| `breakLines` | [вставляет разрывы строк HTML перед всеми новыми строками |#breakLines] -| `bytes` | [форматирует размер в байтах |#bytes] -| `clamp` | [ограничивает значение диапазоном |#clamp] -| `dataStream` | [преобразование протокола URI данных |#dataStream] -| `date` | [форматирует дату |#date] -| `explode` | [разделяет строку по заданному разделителю |#explode] -| `first` | [возвращает первый элемент массива или символ строки |#first] -| `implode` | [соединяет массив со строкой |#implode] -| `indent` | [отступы текста слева с количеством табуляций |#indent] -| `join` | [присоединяет массив к строке |#implode] -| `last` | [возвращает последний элемент массива или символ строки |#last] -| `length` | [возвращает длину строки или массива |#length] -| `number` | [форматирует число |#number] -| `padLeft` | [завершает строку до заданной длины слева |#padLeft] -| `padRight` | [завершает строку до заданной длины справа |#padRight] -| `random` | [возвращает случайный элемент массива или символ строки |#random] -| `repeat` | [повторяет строку |#repeat] -| `replace` | [заменяет все вхождения искомой строки заменой |#replace] -| `replaceRE` | [заменяет все вхождения в соответствии с регулярным выражением |#replaceRE] -| `reverse` | [инвертирует строку или массив в формате UTF-8 |#reverse] -| `slice` | [извлекает фрагмент массива или строки |#slice] -| `sort` | [сортирует массив |#sort] -| `spaceless` | [удаляет пробельные символы |#spaceless], аналогично тегу [spaceless |tags] -| `split` | [разделяет строку по заданному разделителю |#explode] -| `strip` | [удаляет пробелы |#spaceless] -| `stripHtml` | [удаляет HTML-теги и преобразует HTML-сущности в текст |#stripHtml] -| `substr` | [возвращает часть строки |#substr] -| `trim` | [удаляет пробелы из строки |#trim] -| `translate` | [перевод на другие языки |#translate] -| `truncate` | [сокращает длину, сохраняя целые слова |#truncate] -| `webalize` | [приводит строку UTF-8 в соответствие с формой, используемой в URL-адресе |#webalize] +|## Преобразование +| `batch` | [вывод линейных данных в таблицу |#batch] +| `breakLines` | [Добавляет HTML-перенос строки перед концами строк |#breakLines] +| `bytes` | [форматирует размер в байтах |#bytes] +| `clamp` | [ограничивает значение заданным диапазоном |#clamp] +| `dataStream` | [преобразование для протокола Data URI |#dataStream] +| `date` | [форматирует дату и время |#date] +| `explode` | [разбивает строку на массив по разделителю |#explode] +| `first` | [возвращает первый элемент массива или символ строки |#first] +| `group` | [группирует данные по различным критериям |#group] +| `implode` | [объединяет массив в строку |#implode] +| `indent` | [делает отступ текста слева на заданное количество табуляций |#indent] +| `join` | [объединяет массив в строку |#implode] +| `last` | [возвращает последний элемент массива или символ строки |#last] +| `length` | [возвращает длину строки в символах или массив |#length] +| `localDate` | [форматирует дату и время в соответствии с локалью |#localDate] +| `number` | [форматирует число |#number] +| `padLeft` | [дополняет строку слева до нужной длины |#padLeft] +| `padRight` | [дополняет строку справа до нужной длины |#padRight] +| `random` | [возвращает случайный элемент массива или символ строки |#random] +| `repeat` | [повторение строки |#repeat] +| `replace` | [заменяет вхождения искомой строки |#replace] +| `replaceRE` | [заменяет вхождения по регулярному выражению |#replaceRE] +| `reverse` | [переворачивает строку UTF-8 или массив |#reverse] +| `slice` | [извлекает часть массива или строки |#slice] +| `sort` | [сортирует массив |#sort] +| `spaceless` | [удаляет пробелы |#spaceless], аналогично тегу [spaceless |tags] +| `split` | [разбивает строку на массив по разделителю |#explode] +| `strip` | [удаляет пробелы |#spaceless] +| `stripHtml` | [удаляет HTML-теги и преобразует HTML-сущности в символы |#stripHtml] +| `substr` | [возвращает часть строки |#substr] +| `trim` | [удаляет начальные и конечные пробелы или другие символы |#trim] +| `translate` | [перевод на другие языки |#translate] +| `truncate` | [сокращает длину с сохранением слов |#truncate] +| `webalize` | [преобразует строку UTF-8 в форму, используемую в URL |#webalize] .[table-latte-filters] -|## Буквенный регистр -| `capitalize` | [нижний регистр, первая буква каждого слова в верхнем регистре |#capitalize] -| `firstUpper` | [делает первую букву в верхнем регистре |#firstUpper] -| `lower` | [делает строку строчной |#lower] -| `upper` | [делает строку в верхнем регистре |#upper] +|## Регистр букв +| `capitalize` | [строчные буквы, первая буква в словах заглавная |#capitalize] +| `firstUpper` | [преобразует первую букву в заглавную |#firstUpper] +| `lower` | [преобразует в строчные буквы |#lower] +| `upper` | [преобразует в заглавные буквы |#upper] .[table-latte-filters] -|## Округление чисел -| `ceil` | [округляет число в большую сторону с заданной точностью |#ceil] -| `floor` | [округляет число в меньшую сторону с заданной точностью |#floor] -| `round` | [округление числа до заданной точности |#round] +|## Округление +| `ceil` | [округляет число вверх до заданной точности |#ceil] +| `floor` | [округляет число вниз до заданной точности |#floor] +| `round` | [округляет число до заданной точности |#round] .[table-latte-filters] -|## Эскапирование -| `escapeUrl` | [экранирует параметр в URL |#escapeUrl] -| `noescape` | [печатает переменную без экранирования |#noescape] -| `query` | [формирует строку запроса в URL |#query] +|## Экранирование +| `escapeUrl` | [экранирует параметр в URL |#escapeUrl] +| `noescape` | [выводит переменную без экранирования |#noescape] +| `query` | [генерирует строку запроса в URL |#query] -Существуют также фильтры экранирования для HTML (`escapeHtml` и `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) и iCalendar (`escapeICal`), которые Latte использует сам благодаря [контекстно-осознанному экранированию |safety-first#Context-aware escaping], и вам не нужно их писать. +Кроме того, существуют фильтры экранирования для HTML (`escapeHtml` и `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) и iCalendar (`escapeICal`), которые Latte использует самостоятельно благодаря [контекстно-зависимому экранированию |safety-first#Контекстно-зависимое экранирование], и вам не нужно их записывать. .[table-latte-filters] |## Безопасность -| `checkUrl` | [Санирует строку для использования внутри атрибута href |#checkUrl] -| `nocheck` | [предотвращает автоматическую санацию URL |#nocheck] +| `checkUrl` | [очищает URL-адрес от опасных входных данных |#checkUrl] +| `nocheck` | [предотвращает автоматическую очистку URL-адреса |#nocheck] -Latte [проверяет |safety-first#Link-Checking] атрибуты `src` и `href` [автоматически |safety-first#Link-Checking], поэтому вам почти не нужно использовать фильтр `checkUrl`. +Latte атрибуты `src` и `href` [проверяет автоматически |safety-first#Проверка ссылок], поэтому фильтр `checkUrl` почти не нужно использовать. .[note] -Все встроенные фильтры работают со строками в кодировке UTF-8. +Все стандартные фильтры предназначены для строк в кодировке UTF‑8. -Использование .[#toc-usage] -=========================== +Использование +============= -Latte позволяет вызывать фильтры с помощью обозначения "труба" (допускается использование пробела): +Фильтры записываются после вертикальной черты (перед ней может быть пробел): ```latte

                                                                                                            {$heading|upper}

                                                                                                            ``` -Фильтры могут быть соединены в цепочку, в этом случае они применяются в порядке слева направо: +Фильтры (в старых версиях хелперы) можно объединять в цепочку, и они применяются в порядке слева направо: ```latte

                                                                                                            {$heading|lower|capitalize}

                                                                                                            ``` -Параметры помещаются после имени фильтра через двоеточие или запятую: +Параметры указываются после имени фильтра, разделенные двоеточиями или запятыми: ```latte

                                                                                                            {$heading|truncate:20,''}

                                                                                                            ``` -Фильтры могут быть применены к выражению: +Фильтры можно применять и к выражению: ```latte {var $name = ($title|upper) . ($subtitle|lower)} ``` -[Пользовательские фильтры |extending-latte#Filters] могут быть зарегистрированы таким образом: +[Пользовательские фильтры |custom-filters] можно зарегистрировать следующим образом: ```php $latte = new Latte\Engine; $latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -Мы используем его в шаблоне следующим образом: +В шаблоне он вызывается так: ```latte

                                                                                                            {$text|shortify}

                                                                                                            @@ -114,13 +116,13 @@ $latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $ ``` -Фильтры .[#toc-filters] -======================= +Фильтры +======= -batch(int length, mixed item): array .[filter]{data-version:2.7} ----------------------------------------------------------------- -Фильтр, упрощающий вывод линейных данных в виде таблицы. Он возвращает массив массивов с заданным количеством элементов. Если указан второй параметр, он используется для заполнения недостающих элементов в последней строке. +batch(int $length, mixed $item): array .[filter] +------------------------------------------------ +Фильтр, упрощающий вывод линейных данных в виде таблицы. Возвращает массив массивов с заданным количеством элементов. Если указан второй параметр, он используется для заполнения недостающих элементов в последней строке. ```latte {var $items = ['a', 'b', 'c', 'd', 'e']} @@ -135,7 +137,7 @@ batch(int length, mixed item): array .[filter]{data-version:2.7}
                                                                                                            Внутренний цикл
                                                                                                            ``` -Печатает: +Выводит: ```latte @@ -152,20 +154,22 @@ batch(int length, mixed item): array .[filter]{data-version:2.7}
                                                                                                            ``` +См. также [#group] и тег [iterateWhile |tags#iterateWhile]. + breakLines .[filter] -------------------- -Вставляет разрывы строк HTML перед всеми новыми строками. +Добавляет перед каждым символом новой строки HTML-тег `
                                                                                                            ` ```latte {var $s = "Text & with \n newline"} -{$s|breakLines} {* выходы "Text & with
                                                                                                            \n newline" *} +{$s|breakLines} {* выводит "Text & with
                                                                                                            \n newline" *} ``` -bytes(int precision = 2) .[filter] ----------------------------------- -Форматирует размер в байтах в человекочитаемую форму. +bytes(int $precision=2) .[filter] +--------------------------------- +Форматирует размер в байтах в удобочитаемый вид. Если установлена [локаль |develop#Locale], используются соответствующие разделители десятичных знаков и тысяч. ```latte {$size|bytes} 0 B, 1.25 GB, … @@ -173,72 +177,72 @@ bytes(int precision = 2) .[filter] ``` -ceil(int precision = 0) .[filter] ---------------------------------- -Округляет число с заданной точностью. +ceil(int $precision=0) .[filter] +-------------------------------- +Округляет число вверх до заданной точности. ```latte -{=3.4|ceil} {* выходы 4 *} -{=135.22|ceil:1} {* выходы 135.3 *} -{=135.22|ceil:3} {* выходы 135.22 *} +{=3.4|ceil} {* выводит 4 *} +{=135.22|ceil:1} {* выводит 135.3 *} +{=135.22|ceil:3} {* выводит 135.22 *} ``` -См. также [floor |#floor], [round |#round]. +См. также [#floor], [#round]. capitalize .[filter] -------------------- -Возвращает версию значения с заглавными буквами. Слова будут начинаться с заглавных букв, все остальные символы - строчные. Требуется расширение PHP `mbstring`. +Слова будут начинаться с заглавных букв, все остальные символы будут строчными. Требуется расширение PHP `mbstring`. ```latte -{='i like LATTE'|capitalize} {* выходы 'I Like Latte' *} +{='i like LATTE'|capitalize} {* выводит 'I Like Latte' *} ``` -См. также [firstUpper |#firstUpper], [lower |#lower], [upper |#upper]. +См. также [#firstUpper], [#lower], [#upper]. checkUrl .[filter] ------------------ -Обеспечивает санитарную обработку URL. Она проверяет, содержит ли переменная веб-адрес (т.е. протокол HTTP/HTTPS) и предотвращает запись ссылок, которые могут представлять угрозу безопасности. +Принудительно очищает URL-адрес. Проверяет, содержит ли переменная веб-URL (т.е. протокол HTTP/HTTPS) и предотвращает вывод ссылок, которые могут представлять угрозу безопасности. ```latte {var $link = 'javascript:window.close()'} -checked -unchecked +проверенный +непроверенный ``` -Печатает: +Выводит: ```latte -checked -unchecked +проверенный +непроверенный ``` -См. также [nocheck |#nocheck]. +См. также [#nocheck]. -clamp(int|float min, int|float max) .[filter]{data-version:2.9} ---------------------------------------------------------------- -Возвращает значение, зажатое в инклюзивный диапазон min и max. +clamp(int|float $min, int|float $max) .[filter] +----------------------------------------------- +Ограничивает значение заданным включительным диапазоном min и max. ```latte {$level|clamp: 0, 255} ``` -Также существует как [clamp |functions#clamp]. +Существует также как [функция |functions#clamp]. -dataStream(string mimetype = detect) .[filter] ----------------------------------------------- -Преобразует содержимое в схему URI данных. Может использоваться для вставки изображений в HTML или CSS без необходимости ссылки на внешние файлы. +dataStream(string $mimetype=detect) .[filter] +--------------------------------------------- +Преобразует содержимое в схему data URI. С его помощью можно вставлять изображения в HTML или CSS без необходимости ссылаться на внешние файлы. -Пусть у вас есть изображение в переменной `$img = Image::fromFile('obrazek.gif')`, тогда +Пусть в переменной есть изображение `$img = Image::fromFile('obrazek.gif')`, тогда ```latte - + ``` -Печатает, например: +Выведет, например: ```latte {$name} ``` -См. также [query |#query]. +См. также [#query]. -explode(string separator = '') .[filter]{data-version:2.10.2} -------------------------------------------------------------- -Разделяет строку по заданному разделителю и возвращает массив строк. Псевдоним для `split`. +explode(string $separator='') .[filter] +--------------------------------------- +Разбивает строку на массив по разделителю. Псевдоним для `split`. ```latte -{='one,two,three'|explode:','} {* returns ['one', 'two', 'three'] *} +{='one,two,three'|explode:','} {* возвращает ['one', 'two', 'three'] *} ``` -Если разделителем является пустая строка (значение по умолчанию), входные данные будут разделены на отдельные символы: +Если разделитель — пустая строка (значение по умолчанию), входные данные будут разделены на отдельные символы: ```latte -{='123'|explode} {* returns ['1', '2', '3'] *} +{='123'|explode} {* возвращает ['1', '2', '3'] *} ``` -Вы можете использовать также псевдоним `split`: +Вы также можете использовать псевдоним `split`: ```latte -{='1,2,3'|split:','} {* returns ['1', '2', '3'] *} +{='1,2,3'|split:','} {* возвращает ['1', '2', '3'] *} ``` -См. также [implode |#implode]. +См. также [#implode]. -first .[filter]{data-version:2.10.2} ------------------------------------- +first .[filter] +--------------- Возвращает первый элемент массива или символ строки: ```latte -{=[1, 2, 3, 4]|first} {* выходы 1 *} -{='abcd'|first} {* выходы 'a' *} +{=[1, 2, 3, 4]|first} {* выводит 1 *} +{='abcd'|first} {* выводит 'a' *} ``` -См. также [last |#last], [random |#random]. +См. также [#last], [#random]. -floor(int precision = 0) .[filter] ----------------------------------- -Округляет число до заданной точности. +floor(int $precision=0) .[filter] +--------------------------------- +Округляет число вниз до заданной точности. ```latte -{=3.5|floor} {* выходы 3 *} -{=135.79|floor:1} {* выходы 135.7 *} -{=135.79|floor:3} {* выходы 135.79 *} +{=3.5|floor} {* выводит 3 *} +{=135.79|floor:1} {* выводит 135.7 *} +{=135.79|floor:3} {* выводит 135.79 *} ``` -См. также [ceil |#ceil], [round |#round]. +См. также [#ceil], [#round]. firstUpper .[filter] -------------------- -Преобразует первую букву значения в верхний регистр. Требуется расширение PHP `mbstring`. +Преобразует первую букву в заглавную. Требуется расширение PHP `mbstring`. ```latte -{='the latte'|firstUpper} {* выходы 'The latte' *} +{='the latte'|firstUpper} {* выводит 'The latte' *} ``` -См. также [capitalize |#capitalize], [lower |#lower] регистр, [upper |#upper] регистр. +См. также [#capitalize], [#lower], [#upper]. -implode(string glue = '') .[filter] ------------------------------------ -Возвращает строку, которая является конкатенацией строк в массиве. Псевдоним для `join`. +group(string|int|\Closure $by): array .[filter]{data-version:3.0.16} +-------------------------------------------------------------------- +Фильтр группирует данные по различным критериям. + +В этом примере строки в таблице группируются по столбцу `categoryId`. Выводом является массив массивов, где ключом является значение в столбце `categoryId`. [Прочитайте подробное руководство |cookbook/grouping]. ```latte -{=[1, 2, 3]|implode} {* выходы '123' *} -{=[1, 2, 3]|implode:'|'} {* выходы '1|2|3' *} +{foreach ($items|group: categoryId) as $categoryId => $categoryItems} +
                                                                                                              + {foreach $categoryItems as $item} +
                                                                                                            • {$item->name}
                                                                                                            • + {/foreach} +
                                                                                                            +{/foreach} ``` -Вы также можете использовать псевдоним `join`: .{data-version:2.10.2} +См. также [#batch], функция [group |functions#group] и тег [iterateWhile |tags#iterateWhile]. + + +implode(string $glue='') .[filter] +---------------------------------- +Возвращает строку, которая является конкатенацией элементов последовательности. Псевдоним для `join`. ```latte -{=[1, 2, 3]|join} {* выходы '123' *} +{=[1, 2, 3]|implode} {* выводит '123' *} +{=[1, 2, 3]|implode:'|'} {* выводит '1|2|3' *} ``` +Вы также можете использовать псевдоним `join`: + +```latte +{=[1, 2, 3]|join} {* выводит '123' *} +``` -indent(int level = 1, string char = "\t") .[filter] ---------------------------------------------------- -Отступы текста слева на заданное количество табуляций или других символов, которые мы указываем во втором необязательном аргументе. Пустые строки не отступают. + +indent(int $level=1, string $char="\t") .[filter] +------------------------------------------------- +Делает отступ текста слева на заданное количество табуляций или других символов, которые можно указать во втором аргументе. Пустые строки не имеют отступа. ```latte
                                                                                                            @@ -358,7 +382,7 @@ indent(int level = 1, string char = "\t") .[filter]
                                                                                                            ``` -Печатает: +Выводит: ```latte
                                                                                                            @@ -367,26 +391,26 @@ indent(int level = 1, string char = "\t") .[filter] ``` -last .[filter]{data-version:2.10.2} ------------------------------------ +last .[filter] +-------------- Возвращает последний элемент массива или символ строки: ```latte -{=[1, 2, 3, 4]|last} {* выходы 4 *} -{='abcd'|last} {* выходы 'd' *} +{=[1, 2, 3, 4]|last} {* выводит 4 *} +{='abcd'|last} {* выводит 'd' *} ``` -См. также [first |#first], [random |#random]. +См. также [#first], [#random]. length .[filter] ---------------- Возвращает длину строки или массива. -- для строк возвращает длину в символах UTF-8 +- для строк возвращает длину в символах UTF‑8 - для массивов возвращает количество элементов -- для объектов, реализующих интерфейс Countable, будет использовано возвращаемое значение функции count() -- для объектов, реализующих интерфейс IteratorAggregate, будет использовано возвращаемое значение iterator_count() +- для объектов, реализующих интерфейс `Countable`, использует возвращаемое значение метода `count()` +- для объектов, реализующих интерфейс `IteratorAggregate`, использует возвращаемое значение функции `iterator_count()` ```latte @@ -396,38 +420,100 @@ length .[filter] ``` +localDate(?string $format=null, ?string $date=null, ?string $time=null) .[filter] +--------------------------------------------------------------------------------- +Форматирует дату и время в соответствии с [локалью |develop#Locale], что обеспечивает согласованное и локализованное отображение данных о времени в разных языках и регионах. Фильтр принимает дату как UNIX timestamp, строку или объект типа `DateTimeInterface`. + +```latte +{$date|localDate} {* 15 апреля 2024 *} +{$date|localDate: format: yM} {* 4/2024 *} +{$date|localDate: date: medium} {* 15. 4. 2024 *} +``` + +Если вы используете фильтр без параметров, дата будет выведена на уровне `long`, см. далее. + +**a) использование формата** + +Параметр `format` описывает, какие компоненты времени должны отображаться. Для них используются буквенные коды, количество повторений которых влияет на ширину вывода: + +| год | `y` / `yy` / `yyyy` | `2024` / `24` / `2024` +| месяц | `M` / `MM` / `MMM` / `MMMM` | `8` / `08` / `авг` / `август` +| день | `d` / `dd` / `E` / `EEEE` | `1` / `01` / `вс` / `воскресенье` +| час | `j` / `H` / `h` | предпочтительный / 24-часовой / 12-часовой +| минута | `m` / `mm` | `5` / `05` (2 цифры в сочетании с секундами) +| секунда | `s` / `ss` | `8` / `08` (2 цифры в сочетании с минутами) + +Порядок кодов в формате не имеет значения, так как порядок компонентов выводится в соответствии с обычаями локали. Таким образом, формат не зависит от нее. Например, формат `yyyyMMMMd` в среде `en_US` выведет `April 15, 2024`, тогда как в среде `ru_RU` выведет `15 апреля 2024`: + +| locale: | ru_RU | en_US +|--- +| `format: 'dMy'` | 10. 8. 2024 | 8/10/2024 +| `format: 'yM'` | 8/2024 | 8/2024 +| `format: 'yyyyMMMM'` | август 2024 | August 2024 +| `format: 'MMMM'` | август | August +| `format: 'jm'` | 17:22 | 5:22 PM +| `format: 'Hm'` | 17:22 | 17:22 +| `format: 'hm'` | 5:22 вечера | 5:22 PM + + +**б) использование предустановленных стилей** + +Параметры `date` и `time` определяют, насколько подробно должны выводиться дата и время. Вы можете выбрать из нескольких уровней: `full`, `long`, `medium`, `short`. Можно вывести только дату, только время или и то, и другое: + +| locale: | ru_RU | en_US +|--- +| `date: short` | 23.01.78 | 1/23/78 +| `date: medium` | 23 янв. 1978 г. | Jan 23, 1978 +| `date: long` | 23 января 1978 г. | January 23, 1978 +| `date: full` | понедельник, 23 января 1978 г. | Monday, January 23, 1978 +| `time: short` | 8:30 | 8:30 AM +| `time: medium` | 8:30:59 | 8:30:59 AM +| `time: long` | 8:30:59 GMT+1 | 8:30:59 AM GMT+1 +| `date: short, time: short` | 23.01.78, 8:30 | 1/23/78, 8:30 AM +| `date: medium, time: short` | 23 янв. 1978 г., 8:30 | Jan 23, 1978, 8:30 AM +| `date: long, time: short` | 23 января 1978 г. в 8:30 | January 23, 1978 at 8:30 AM + +Для даты также можно использовать префикс `relative-` (например, `relative-short`), который для дат, близких к текущей, отобразит `вчера`, `сегодня` или `завтра`, иначе выведет стандартным образом. + +```latte +{$date|localDate: date: relative-short} {* вчера *} +``` + +См. также [#date]. + + lower .[filter] --------------- -Преобразует значение в нижний регистр. Требуется расширение PHP `mbstring`. +Преобразует строку в строчные буквы. Требуется расширение PHP `mbstring`. ```latte -{='LATTE'|lower} {* выходы 'latte' *} +{='LATTE'|lower} {* выводит 'latte' *} ``` -См. также [capitalize |#capitalize], [firstUpper |#firstUpper], [upper |#upper]. +См. также [#capitalize], [#firstUpper], [#upper]. nocheck .[filter] ----------------- -Предотвращает автоматическую санацию URL. Latte [автоматически проверяет |safety-first#Link checking], содержит ли переменная веб-адрес (т.е. протокол HTTP/HTTPS) и предотвращает запись ссылок, которые могут представлять угрозу безопасности. +Предотвращает автоматическую очистку URL-адреса. Latte [автоматически проверяет |safety-first#Проверка ссылок], содержит ли переменная веб-URL (т.е. протокол HTTP/HTTPS) и предотвращает вывод ссылок, которые могут представлять угрозу безопасности. -Если ссылка использует другую схему, например, `javascript:` или `data:`, и вы уверены в ее содержимом, вы можете отключить проверку через `|nocheck`. +Если ссылка использует другую схему, например `javascript:` или `data:`, и вы уверены в ее содержимом, вы можете отключить проверку с помощью `|nocheck`. ```latte {var $link = 'javascript:window.close()'} -checked -unchecked +проверенный +непроверенный ``` -Печатные издания: +Выводит: ```latte -checked -unchecked +проверенный +непроверенный ``` -См. также [checkUrl |#checkUrl]. +См. также [#checkUrl]. noescape .[filter] @@ -436,53 +522,101 @@ noescape .[filter] ```latte {var $trustedHtmlString = 'hello'} -Escaped: {$trustedHtmlString} -Unescaped: {$trustedHtmlString|noescape} +Экранированный: {$trustedHtmlString} +Неэкранированный: {$trustedHtmlString|noescape} ``` -Печатает: +Выводит: ```latte -Escaped: <b>hello</b> -Unescaped: hello +Экранированный: <b>hello</b> +Неэкранированный: hello ``` .[warning] -Неправильное использование фильтра `noescape` может привести к XSS-уязвимости! Никогда не используйте его, если вы не **абсолютно уверены** в том, что вы делаете, и что печатаемая строка получена из надежного источника. +Неправильное использование фильтра `noescape` может привести к уязвимости XSS! Никогда не используйте его, если вы не **абсолютно уверены** в том, что делаете, и что выводимая строка происходит из надежного источника. -number(int decimals = 0, string decPoint = '.', string thousandsSep = ',') .[filter] ------------------------------------------------------------------------------------- -Форматирует число до заданного количества знаков после запятой. Можно также указать символ десятичной точки и разделителя тысяч. +number(int $decimals=0, string $decPoint='.', string $thousandsSep=',') .[filter] +--------------------------------------------------------------------------------- +Форматирует число до определенного количества десятичных знаков. Если установлена [локаль |develop#Locale], используются соответствующие разделители десятичных знаков и тысяч. ```latte -{1234.20 |number} 1,234 -{1234.20 |number:1} 1,234.2 -{1234.20 |number:2} 1,234.20 -{1234.20 |number:2, ',', ' '} 1 234,20 +{1234.20|number} 1,234 +{1234.20|number:1} 1,234.2 +{1234.20|number:2} 1,234.20 +{1234.20|number:2, ',', ' '} 1 234,20 ``` -padLeft(int length, string pad = ' ') .[filter] +number(string $format) .[filter] +-------------------------------- +Параметр `format` позволяет определить внешний вид чисел точно в соответствии с вашими потребностями. Для этого необходимо установить [локаль |develop#Locale]. Формат состоит из нескольких специальных символов, полное описание которых можно найти в документации "DecimalFormat":https://unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns: + +- `0` обязательная цифра, всегда отображается, даже если это ноль +- `#` необязательная цифра, отображается только тогда, когда на этом месте действительно есть число +- `@` значащая цифра, помогает отобразить число с определенным количеством значащих цифр +- `.` указывает, где должна быть десятичная запятая (или точка, в зависимости от страны) +- `,` служит для разделения групп цифр, чаще всего тысяч +- `%` умножает число на 100× и добавляет знак процента + +Давайте рассмотрим примеры. В первом примере два десятичных знака обязательны, во втором — необязательны. Третий пример показывает дополнение нулями слева и справа, четвертый отображает только существующие цифры: + +```latte +{1234.5|number: '#,##0.00'} {* 1,234.50 *} +{1234.5|number: '#,##0.##'} {* 1,234.5 *} +{1.23 |number: '000.000'} {* 001.230 *} +{1.2 |number: '##.##'} {* 1.2 *} +``` + +Значащие цифры определяют, сколько цифр, независимо от десятичной запятой, должно быть отображено, при этом происходит округление: + +```latte +{1234|number: '@@'} {* 1200 *} +{1234|number: '@@@'} {* 1230 *} +{1234|number: '@@@#'} {* 1234 *} +{1.2345|number: '@@@'} {* 1.23 *} +{0.00123|number: '@@'} {* 0.0012 *} +``` + +Простой способ отобразить число в виде процентов. Число умножается на 100× и добавляется знак `%`: + +```latte +{0.1234|number: '#.##%'} {* 12.34% *} +``` + +Мы можем определить разный формат для положительных и отрицательных чисел, их разделяет знак `;`. Таким образом, например, можно настроить, чтобы положительные числа отображались со знаком `+`: + +```latte +{42|number: '#.##;(#.##)'} {* 42 *} +{-42|number: '#.##;(#.##)'} {* (42) *} +{42|number: '+#.##;-#.##'} {* +42 *} +{-42|number: '+#.##;-#.##'} {* -42 *} +``` + +Помните, что фактический вид чисел может отличаться в зависимости от настроек страны. Например, в некоторых странах используется запятая вместо точки в качестве разделителя десятичных знаков. Этот фильтр автоматически учитывает это, и вам не нужно ни о чем беспокоиться. + + +padLeft(int $length, string $pad=' ') .[filter] ----------------------------------------------- -Складывает строку определенной длины с другой строкой слева. +Дополняет строку до определенной длины другой строкой слева. ```latte -{='hello'|padLeft: 10, '123'} {* outputs '12312hello' *} +{='hello'|padLeft: 10, '123'} {* выводит '12312hello' *} ``` -padRight(int length, string pad = ' ') .[filter] +padRight(int $length, string $pad=' ') .[filter] ------------------------------------------------ -Складывает строку определенной длины с другой строкой справа. +Дополняет строку до определенной длины другой строкой справа. ```latte -{='hello'|padRight: 10, '123'} {* выходы 'hello12312' *} +{='hello'|padRight: 10, '123'} {* выводит 'hello12312' *} ``` -query .[filter]{data-version:2.10} ------------------------------------ +query .[filter] +--------------- Динамически генерирует строку запроса в URL: ```latte @@ -490,110 +624,110 @@ query .[filter]{data-version:2.10} search ``` -Печатает: +Выводит: ```latte click search ``` -Ключи со значением `null` опускаются. +Ключи со значением `null` пропускаются. -См. также [escapeUrl |#escapeUrl]. +См. также [#escapeUrl]. -random .[filter]{data-version:2.10.2} -------------------------------------- +random .[filter] +---------------- Возвращает случайный элемент массива или символ строки: ```latte -{=[1, 2, 3, 4]|random} {* example output: 3 *} -{='abcd'|random} {* example output: 'b' *} +{=[1, 2, 3, 4]|random} {* выводит например: 3 *} +{='abcd'|random} {* выводит например: 'b' *} ``` -См. также [first |#first], [last |#last]. +См. также [#first], [#last]. -repeat(int count) .[filter] ---------------------------- +repeat(int $count) .[filter] +---------------------------- Повторяет строку x раз. ```latte -{='hello'|repeat: 3} {* выходы 'hellohellohello' *} +{='hello'|repeat: 3} {* выводит 'hellohellohello' *} ``` -replace(string|array search, string replace = '') .[filter] +replace(string|array $search, string $replace='') .[filter] ----------------------------------------------------------- -Заменяет все вхождения строки поиска строкой замены. +Заменяет все вхождения искомой строки на заменяющую строку. ```latte -{='hello world'|replace: 'world', 'friend'} {* выходы 'hello friend' *} +{='hello world'|replace: 'world', 'friend'} {* выводит 'hello friend' *} ``` -Одновременно может быть произведено несколько замен: .{data-version:2.10.2} +Можно выполнить несколько замен одновременно: ```latte -{='hello world'|replace: [h => l, l => h]} {* выходы 'lehho worhd' *} +{='hello world'|replace: [h => l, l => h]} {* выводит 'lehho worhd' *} ``` -replaceRE(string pattern, string replace = '') .[filter] +replaceRE(string $pattern, string $replace='') .[filter] -------------------------------------------------------- -Заменяет все вхождения в соответствии с регулярным выражением. +Выполняет поиск по регулярному выражению с заменой. ```latte -{='hello world'|replaceRE: '/l.*/', 'l'} {* выходы 'hel' *} +{='hello world'|replaceRE: '/l.*/', 'l'} {* выводит 'hel' *} ``` reverse .[filter] ----------------- -Заменяет заданную строку или массив. +Переворачивает данную строку или массив. ```latte {var $s = 'Nette'} -{$s|reverse} {* выходы 'etteN' *} +{$s|reverse} {* выводит 'etteN' *} {var $a = ['N', 'e', 't', 't', 'e']} -{$a|reverse} {* returns ['e', 't', 't', 'e', 'N'] *} +{$a|reverse} {* возвращает ['e', 't', 't', 'e', 'N'] *} ``` -round(int precision = 0) .[filter] ----------------------------------- -Округляет число с заданной точностью. +round(int $precision=0) .[filter] +--------------------------------- +Округляет число до заданной точности. ```latte -{=3.4|round} {* выходы 3 *} -{=3.5|round} {* выходы 4 *} -{=135.79|round:1} {* выходы 135.8 *} -{=135.79|round:3} {* выходы 135.79 *} +{=3.4|round} {* выводит 3 *} +{=3.5|round} {* выводит 4 *} +{=135.79|round:1} {* выводит 135.8 *} +{=135.79|round:3} {* выводит 135.79 *} ``` -См. также [ceil |#ceil], [floor |#floor]. +См. также [#ceil], [#floor]. -slice(int start, int length = null, bool preserveKeys = false) .[filter]{data-version:2.10.2} ---------------------------------------------------------------------------------------------- -Извлекает фрагмент массива или строки. +slice(int $start, ?int $length=null, bool $preserveKeys=false) .[filter] +------------------------------------------------------------------------ +Извлекает часть массива или строки. ```latte -{='hello'|slice: 1, 2} {* выходы 'el' *} -{=['a', 'b', 'c']|slice: 1, 2} {* выходы ['b', 'c'] *} +{='hello'|slice: 1, 2} {* выводит 'el' *} +{=['a', 'b', 'c']|slice: 1, 2} {* выводит ['b', 'c'] *} ``` -Фильтр срезов работает как функция `array_slice` PHP для массивов и `mb_substr` для строк с возвратом к `iconv_substr` в режиме UTF-8. +Фильтр работает как функция PHP `array_slice` для массивов или `mb_substr` для строк с резервным вариантом на функцию `iconv_substr` в режиме UTF‑8. -Если start неотрицательно, то последовательность начнется с этого начала в переменной. Если start отрицательно, то последовательность начнется на таком-то расстоянии от конца переменной. +Если start положительный, последовательность начнется со смещением на это количество от начала массива/строки. Если отрицательный, последовательность начнется со смещением на столько от конца. -Если задана длина и она положительна, то последовательность будет содержать до этого количества элементов. Если переменная короче длины, то будут присутствовать только доступные элементы переменной. Если длина задана и отрицательна, то последовательность остановится на столько элементов от конца переменной. Если длина не указана, то последовательность будет содержать все элементы от смещения до конца переменной. +Если указан параметр length и он положительный, последовательность будет содержать столько элементов. Если в эту функцию передан отрицательный параметр length, последовательность будет содержать все элементы исходного массива, начиная с позиции start и заканчивая на позиции, меньшей на length элементов от конца массива. Если этот параметр не указан, последовательность будет содержать все элементы исходного массива, начиная с позиции start. -Filter по умолчанию переупорядочивает и сбрасывает ключи целочисленного массива. Это поведение можно изменить, установив preserveKeys в true. Строковые ключи всегда сохраняются, независимо от этого параметра. +По умолчанию фильтр изменяет порядок и сбрасывает целочисленные ключи массива. Это поведение можно изменить, установив preserveKeys в true. Строковые ключи всегда сохраняются, независимо от этого параметра. -sort .[filter]{data-version:2.9} ---------------------------------- -Фильтр, сортирующий массив и сохраняющий ассоциацию индексов. +sort(?Closure $comparison, string|int|\Closure|null $by=null, string|int|\Closure|bool $byKey=false) .[filter] +-------------------------------------------------------------------------------------------------------------- +Фильтр сортирует элементы массива или итератора и сохраняет их ассоциативные ключи. При установленной [локали |develop#Locale] сортировка подчиняется ее правилам, если не указана собственная функция сравнения. ```latte {foreach ($names|sort) as $name} @@ -601,7 +735,7 @@ sort .[filter]{data-version:2.9} {/foreach} ``` -Массив сортируется в обратном порядке. +Сортированный массив в обратном порядке: ```latte {foreach ($names|sort|reverse) as $name} @@ -609,16 +743,42 @@ sort .[filter]{data-version:2.9} {/foreach} ``` -В качестве параметра можно передать собственную функцию сравнения: .{data-version:2.10.2} +Вы можете указать собственную функцию сравнения для сортировки (пример показывает, как обратить сортировку от наибольшего к наименьшему): + +```latte +{var $reverted = ($names|sort: fn($a, $b) => $b <=> $a)} +``` + +Фильтр `|sort` также позволяет сортировать элементы по ключам: + +```latte +{foreach ($names|sort: byKey: true) as $name} + ... +{/foreach} +``` + +Если вам нужно отсортировать таблицу по определенному столбцу, вы можете использовать параметр `by`. Значение `'name'` в примере указывает, что сортировка будет производиться по `$item->name` или `$item['name']`, в зависимости от того, является ли `$item` массивом или объектом: ```latte -{var $sorted = ($names|sort: fn($a, $b) => $b <=> $a)} +{foreach ($items|sort: by: 'name') as $item} + {$item->name} +{/foreach} +``` + +Вы также можете определить callback-функцию, которая определит значение, по которому нужно сортировать: + +```latte +{foreach ($items|sort: by: fn($items) => $items->category->name) as $item} + {$item->name} +{/foreach} ``` +Таким же образом можно использовать и параметр `byKey`. -spaceless .[filter]{data-version:2.10.2} ------------------------------------------ -Удаляет ненужные пробельные символы из вывода. Вы также можете использовать псевдоним `strip`. + +spaceless .[filter] +------------------- +Удаляет ненужные пробелы из вывода. Вы также можете использовать псевдоним `strip`. ```latte {block |spaceless} @@ -628,7 +788,7 @@ spaceless .[filter]{data-version:2.10.2} {/block} ``` -Печатает: +Выводит: ```latte
                                                                                                            • Hello
                                                                                                            @@ -637,47 +797,47 @@ spaceless .[filter]{data-version:2.10.2} stripHtml .[filter] ------------------- -Преобразует HTML в обычный текст. То есть, удаляет HTML-теги и преобразует HTML-сущности в текст. +Преобразует HTML в чистый текст. То есть удаляет из него HTML-теги и преобразует HTML-сущности в текст. ```latte -{='

                                                                                                            one < two

                                                                                                            '|stripHtml} {* выходы 'one < two' *} +{='

                                                                                                            one < two

                                                                                                            '|stripHtml} {* выводит 'one < two' *} ``` -Полученный простой текст может естественно содержать символы, представляющие HTML-теги, например, `'<p>'|stripHtml` преобразуется в `

                                                                                                            `. Никогда не выводите полученный текст с `|noescape`, так как это может привести к уязвимости системы безопасности. +Полученный чистый текст может естественно содержать символы, представляющие HTML-теги, например `'<p>'|stripHtml` преобразуется в `

                                                                                                            `. Ни в коем случае не выводите такой текст с `|noescape`, так как это может привести к уязвимости безопасности. -substr(int offset, int length = null) .[filter] ------------------------------------------------ -Извлекает фрагмент строки. Этот фильтр был заменен фильтром [срезов |#slice]. +substr(int $offset, ?int $length=null) .[filter] +------------------------------------------------ +Извлекает часть строки. Этот фильтр был заменен фильтром [#slice]. ```latte {$string|substr: 1, 2} ``` -translate(string message, ...args) .[filter]{data-version:3.0} --------------------------------------------------------------- -Переводит выражения на другие языки. Чтобы сделать фильтр доступным, необходимо [настроить переводчик |develop#TranslatorExtension]. Вы также можете использовать [теги для перевода |tags#Translation]. +translate(...$args) .[filter] +----------------------------- +Переводит выражения на другие языки. Чтобы фильтр был доступен, необходимо [установить переводчик |develop#TranslatorExtension]. Вы также можете использовать [теги для перевода |tags#Переводы]. ```latte -{='Baskter'|translate} +{='Корзина'|translate} {$item|translate} ``` -trim(string charlist = " \t\n\r\0\x0B\u{A0}") .[filter] -------------------------------------------------------- -Вычеркивать ведущие и последующие символы, по умолчанию пробелы. +trim(string $charlist=" \t\n\r\0\x0B\u{A0}") .[filter] +------------------------------------------------------ +Удаляет пробелы (или другие символы) с начала и конца строки. ```latte -{=' I like Latte. '|trim} {* выходы 'I like Latte.' *} -{=' I like Latte.'|trim: '.'} {* выходы ' I like Latte' *} +{=' I like Latte. '|trim} {* выводит 'I like Latte.' *} +{=' I like Latte.'|trim: '.'} {* выводит ' I like Latte' *} ``` -truncate(int length, string append = '…') .[filter] +truncate(int $length, string $append='…') .[filter] --------------------------------------------------- -Сокращает строку до максимальной заданной длины, но старается сохранить целые слова. Если строка усечена, добавляет многоточие в конце (это можно изменить вторым параметром). +Обрезает строку до указанной максимальной длины, при этом стараясь сохранять целые слова. Если строка укорачивается, в конце добавляется многоточие (можно изменить вторым параметром). ```latte {var $title = 'Hello, how are you?'} @@ -689,25 +849,25 @@ truncate(int length, string append = '…') .[filter] upper .[filter] --------------- -Преобразует значение в верхний регистр. Требуется расширение PHP `mbstring`. +Преобразует строку в заглавные буквы. Требуется расширение PHP `mbstring`. ```latte -{='latte'|upper} {* outputs 'LATTE' *} +{='latte'|upper} {* выводит 'LATTE' *} ``` -См. также [capitalize |#capitalize], [firstUpper |#firstUpper], [lower |#lower]. +См. также [#capitalize], [#firstUpper], [#lower]. webalize .[filter] ------------------ -Преобразование в ASCII. +Преобразует строку UTF‑8 в форму, используемую в URL. -Преобразует пробелы в дефисы. Удаляет символы, не являющиеся алфавитно-цифровыми, подчеркиванием или дефисом. Преобразует в строчные буквы. Также удаляет ведущие и последующие пробельные символы. +Преобразуется в ASCII. Преобразует пробелы в дефисы. Удаляет символы, которые не являются буквенно-цифровыми, подчеркиваниями или дефисами. Преобразует в строчные буквы. Также удаляет начальные и конечные пробелы. ```latte -{var $s = 'Our 10. product'} -{$s|webalize} {* выходы 'our-10-product' *} +{var $s = 'Наш 10. продукт'} +{$s|webalize} {* выводит 'nash-10-produkt' *} ``` .[caution] -Требуется пакет [nette/utils |utils:]. +Требуется библиотека [nette/utils |utils:]. diff --git a/latte/ru/functions.texy b/latte/ru/functions.texy index 4e4cf43081..a6e5dfb38c 100644 --- a/latte/ru/functions.texy +++ b/latte/ru/functions.texy @@ -2,22 +2,24 @@ ************* .[perex] -В дополнение к обычным функциям PHP, вы можете использовать их в шаблонах. +В шаблонах, помимо обычных PHP-функций, мы можем использовать и эти дополнительные функции. .[table-latte-filters] -| `clamp` | [зажимает значение в диапазон |#clamp] +| `clamp` | [ограничивает значение заданным диапазоном |#clamp] | `divisibleBy`| [проверяет, делится ли переменная на число |#divisibleBy] -| `even` | [проверяет, является ли данное число четным |#even] -| `first` | [возвращает первый элемент массива или символ строки |#first] -| `last` | [возвращает последний элемент массива или символ строки |#last] -| `odd` | [проверяет, является ли данное число нечетным |#odd] -| `slice` | [извлекает фрагмент массива или строки |#slice] +| `even` | [проверяет, является ли данное число четным |#even] +| `first` | [возвращает первый элемент массива или символ строки |#first] +| `group` | [группирует данные по различным критериям |#group] +| `hasBlock` | [проверяет существование блока |#hasBlock] +| `last` | [возвращает последний элемент массива или символ строки |#last] +| `odd` | [проверяет, является ли данное число нечетным |#odd] +| `slice` | [извлекает часть массива или строки |#slice] -Использование .[#toc-usage] -=========================== +Использование +============= -Функции используются так же, как и обычные функции PHP, и могут быть использованы во всех выражениях: +Функции используются так же, как обычные PHP-функции, и их можно использовать во всех выражениях: ```latte

                                                                                                            {clamp($num, 1, 100)}

                                                                                                            @@ -25,14 +27,14 @@ {if odd($num)} ... {/if} ``` -[Пользовательские функции |extending-latte#Functions] могут быть зарегистрированы таким образом: +[Пользовательские функции |custom-functions] можно зарегистрировать следующим образом: ```php $latte = new Latte\Engine; $latte->addFunction('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -Мы используем его в шаблоне следующим образом: +В шаблоне она вызывается так: ```latte

                                                                                                            {shortify($text)}

                                                                                                            @@ -40,23 +42,23 @@ $latte->addFunction('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, ``` -Функции .[#toc-functions] -========================= +Функции +======= -clamp(int|float $value, int|float $min, int|float $max): int|float .[method]{data-version:2.9} ----------------------------------------------------------------------------------------------- -Возвращает значение, зажатое во включительно диапазоне min и max. +clamp(int|float $value, int|float $min, int|float $max): int|float .[method] +---------------------------------------------------------------------------- +Ограничивает значение заданным включительным диапазоном min и max. ```latte {=clamp($level, 0, 255)} ``` -См. также [зажим фильтра |filters#clamp]: +См. также [фильтр clamp |filters#clamp]. -divisibleBy(int $value, int $by): bool .[method]{data-version:2.10.2} ---------------------------------------------------------------------- +divisibleBy(int $value, int $by): bool .[method] +------------------------------------------------ Проверяет, делится ли переменная на число. ```latte @@ -64,61 +66,91 @@ divisibleBy(int $value, int $by): bool .[method]{data-version:2.10.2} ``` -even(int $value): bool .[method]{data-version:2.10.2} ------------------------------------------------------ -Проверяет, является ли заданное число четным. +even(int $value): bool .[method] +-------------------------------- +Проверяет, является ли данное число четным. ```latte {if even($num)} ... {/if} ``` -first(string|array $value): mixed .[method]{data-version:2.10.2} ----------------------------------------------------------------- +first(string|iterable $value): mixed .[method] +---------------------------------------------- Возвращает первый элемент массива или символ строки: ```latte -{=first([1, 2, 3, 4])} {* выходы 1 *} -{=first('abcd')} {* выходы 'a' *} +{=first([1, 2, 3, 4])} {* выводит 1 *} +{=first('abcd')} {* выводит 'a' *} ``` -См. также [last |#last], [filter first |filters#first]. +См. также [#last], [фильтр first |filters#first]. -last(string|array $value): mixed .[method]{data-version:2.10.2} ---------------------------------------------------------------- +group(iterable $data, string|int|\Closure $by): array .[method]{data-version:3.0.16} +------------------------------------------------------------------------------------ +Функция группирует данные по различным критериям. + +В этом примере строки в таблице группируются по столбцу `categoryId`. Выводом является массив массивов, где ключом является значение в столбце `categoryId`. [Прочитайте подробное руководство |cookbook/grouping]. + +```latte +{foreach group($items, categoryId) as $categoryId => $categoryItems} +
                                                                                                              + {foreach $categoryItems as $item} +
                                                                                                            • {$item->name}
                                                                                                            • + {/foreach} +
                                                                                                            +{/foreach} +``` + +См. также фильтр [group |filters#group]. + + +hasBlock(string $name): bool .[method]{data-version:3.0.10} +----------------------------------------------------------- +Проверяет, существует ли блок с указанным именем: + +```latte +{if hasBlock(header)} ... {/if} +``` + +См. также [проверка существования блоков |template-inheritance#Проверка существования блоков]. + + +last(string|array $value): mixed .[method] +------------------------------------------ Возвращает последний элемент массива или символ строки: ```latte -{=last([1, 2, 3, 4])} {* выходы 4 *} -{=last('abcd')} {* выходы 'd' *} +{=last([1, 2, 3, 4])} {* выводит 4 *} +{=last('abcd')} {* выводит 'd' *} ``` -См. также [first |#first], [filter last |filters#last]. +См. также [#first], [фильтр last |filters#last]. -odd(int $value): bool .[method]{data-version:2.10.2} ----------------------------------------------------- -Проверяет, является ли заданное число нечетным. +odd(int $value): bool .[method] +------------------------------- +Проверяет, является ли данное число нечетным. ```latte {if odd($num)} ... {/if} ``` -slice(string|array $value, int $start, int $length=null, bool $preserveKeys=false): string|array .[method]{data-version:2.10.2} -------------------------------------------------------------------------------------------------------------------------------- -Извлекает фрагмент массива или строки. +slice(string|array $value, int $start, ?int $length=null, bool $preserveKeys=false): string|array .[method] +----------------------------------------------------------------------------------------------------------- +Извлекает часть массива или строки. ```latte -{=slice('hello', 1, 2)} {* выходы 'el' *} -{=slice(['a', 'b', 'c'], 1, 2)} {* выходы ['b', 'c'] *} +{=slice('hello', 1, 2)} {* выводит 'el' *} +{=slice(['a', 'b', 'c'], 1, 2)} {* выводит ['b', 'c'] *} ``` -Фильтр срезов работает как функция `array_slice` PHP для массивов и `mb_substr` для строк с возвратом к `iconv_substr` в режиме UTF-8. +Функция работает как функция PHP `array_slice` для массивов или `mb_substr` для строк с резервным вариантом на функцию `iconv_substr` в режиме UTF‑8. -Если start неотрицательно, то последовательность начнется с этого начала в переменной. Если start отрицательно, то последовательность начнется на таком-то расстоянии от конца переменной. +Если start положительный, последовательность начнется со смещением на это количество от начала массива/строки. Если отрицательный, последовательность начнется со смещением на столько от конца. -Если задана длина и она положительна, то последовательность будет содержать до этого количества элементов. Если переменная короче длины, то будут присутствовать только доступные элементы переменной. Если длина задана и отрицательна, то последовательность остановится на столько элементов от конца переменной. Если длина не указана, то последовательность будет содержать все элементы от смещения до конца переменной. +Если указан параметр length и он положительный, последовательность будет содержать столько элементов. Если в эту функцию передан отрицательный параметр length, последовательность будет содержать все элементы исходного массива, начиная с позиции start и заканчивая на позиции, меньшей на length элементов от конца массива. Если этот параметр не указан, последовательность будет содержать все элементы исходного массива, начиная с позиции start. -Filter по умолчанию переупорядочивает и сбрасывает ключи целочисленного массива. Это поведение можно изменить, установив preserveKeys в true. Строковые ключи всегда сохраняются, независимо от этого параметра. +По умолчанию функция изменяет порядок и сбрасывает целочисленные ключи массива. Это поведение можно изменить, установив preserveKeys в true. Строковые ключи всегда сохраняются, независимо от этого параметра. diff --git a/latte/ru/guide.texy b/latte/ru/guide.texy index f982e5994e..f27ffd2c96 100644 --- a/latte/ru/guide.texy +++ b/latte/ru/guide.texy @@ -3,44 +3,43 @@
                                                                                                            -Шаблоны улучшают организацию кода, отделяют логику приложения от презентации и повышают безопасность. Они предлагают гораздо лучшие функции и выразительные возможности для генерации HTML, чем сам PHP. +Шаблоны улучшают организацию кода, отделяют логику приложения от представления и повышают безопасность. Они предлагают гораздо лучшие функции и выразительные средства для генерации HTML, чем сам PHP. -Latte - это самая безопасная система шаблонов для PHP. Вам понравится ее интуитивно понятный синтаксис. Широкий набор полезных функций значительно упростит вашу работу. -Она обеспечивает первоклассную защиту от [критических уязвимостей |safety-first] и позволяет вам сосредоточиться на создании высококачественных приложений, не беспокоясь об их безопасности. +Latte — самая безопасная система шаблонов для PHP. Вам понравится ее интуитивно понятный синтаксис. Широкий спектр полезных функций значительно облегчит вашу работу. Она обеспечивает первоклассную защиту от [критических уязвимостей |safety-first] и позволит вам сосредоточиться на создании качественных приложений, не беспокоясь об их безопасности. -Как писать шаблоны с помощью Latte? .[#toc-how-to-write-templates-using-latte] ------------------------------------------------------------------------------- +Как писать шаблоны с помощью Latte? +----------------------------------- -Latte продуманно спроектирован и прост в освоении для тех, кто знаком с PHP, так как они могут быстро освоить его основные теги. +Latte продуманно спроектирован и легко осваивается теми, кто знаком с PHP и освоит основные теги. -- Для начала ознакомьтесь с [синтаксисом Latte |syntax] и [попробуйте все это в режиме онлайн |https://fiddle.nette.org/latte/]. -- Ознакомьтесь с базовым набором [тегов |tags] и [фильтров |filters] -- Напишите шаблоны в [редакторе с поддержкой Latte |recipes#Editors and IDE] +- Сначала ознакомьтесь с [синтаксисом Latte |syntax] и [ПОПРОБУЙТЕ ОНЛАЙН |https://fiddle.nette.org/latte/#9cc0cf6d89#9cc0cf6d89] +- Посмотрите на основной набор [тегов |tags] и [фильтров |filters] +- Пишите шаблоны в [редакторе с поддержкой Latte |recipes#Редакторы и IDE] -Как использовать Latte в PHP? .[#toc-how-to-use-latte-in-php] -------------------------------------------------------------- +Как использовать Latte в PHP? +----------------------------- -Внедрение Latte в ваше новое приложение - дело нескольких минут: +Развернуть Latte в вашем новом приложении — дело нескольких минут: -- Сначала [установите и запустите Latte |develop#Installation] -- Побалуйте себя [инструментом отладки Tracy |develop#Debugging-and-Tracy] -- Расширьте Latte с помощью [пользовательской функциональности |extending-latte] +- Сначала [Установите и запустите Latte |develop#Установка] +- Побалуйте себя [инструментом отладки Tracy |develop#Отладка и Tracy] +- Расширьте Latte [пользовательской функциональностью |extending-latte] -Если вы переводите старый проект, написанный на обычном PHP, в Latte, [инструмент для преобразования PHP-кода в Latte |cookbook/migration-from-php] облегчит вам миграцию. Или вы планируете перейти на Latte с Twig? У нас есть [конвертер шаблонов Twig в Latte |cookbook/migration-from-twig] для вас. +Если вы переносите старый проект, написанный на чистом PHP, в Latte, вам облегчит миграцию [инструмент для преобразования PHP-кода в Latte |cookbook/migration-from-php]. Или вы собираетесь перейти на Latte с Twig? У нас есть для вас [конвертер шаблонов Twig в Latte |cookbook/migration-from-twig]. -Что еще может Latte? .[#toc-what-else-can-latte-do] ---------------------------------------------------- +Что еще умеет Latte? +-------------------- -Latte поставляется в полной комплектации, со всем необходимым. +Latte поставляется в полной комплектации, со всем важным в основе. -- Ваша производительность повысится благодаря [механизмам наследования |template-inheritance], которые повторно используют повторяющиеся элементы и структуры -- Броневой бункер [Sandbox] изолирует шаблоны от ненадежных источников, например, редактируемых самими пользователями -- Для дальнейшего вдохновения вот [советы и рекомендации |recipes] +- Вашу продуктивность повысят [механизмы наследования |template-inheritance], благодаря которым повторяющиеся элементы и структуры используются повторно +- Бронированный бункер [песочница |sandbox] изолирует шаблоны из ненадежных источников, которые, например, редактируют сами пользователи +- Для дальнейшего вдохновения есть [советы и трюки |recipes]
                                                                                                            -{{description: Latte - это самая безопасная система шаблонов для PHP. Это предотвращает многие уязвимости в системе безопасности. Вы оцените его интуитивно понятный синтаксис и множество полезных настроек.}} +{{description: Latte — самая безопасная система шаблонов для PHP. Предотвращает множество уязвимостей безопасности. Вы оцените ее интуитивно понятный синтаксис и множество полезных функций.}} diff --git a/latte/ru/loaders.texy b/latte/ru/loaders.texy new file mode 100644 index 0000000000..ac79b61a2e --- /dev/null +++ b/latte/ru/loaders.texy @@ -0,0 +1,198 @@ +Загрузчики +********** + +.[perex] +Загрузчики — это механизм, который Latte использует для получения исходного кода ваших шаблонов. Чаще всего шаблоны хранятся в виде файлов на диске, но благодаря гибкой системе загрузчиков вы можете загружать их практически откуда угодно или даже генерировать динамически. + + +Что такое Загрузчик? +==================== + +Когда вы работаете с шаблонами, вы обычно представляете себе файлы `.latte`, расположенные в структуре каталогов вашего проекта. Этим занимается загрузчик по умолчанию [#FileLoader] в Latte. Однако связь между именем шаблона (например, `'main.latte'` или `'components/card.latte'`) и его фактическим исходным кодом *не обязательно* должна быть прямым отображением на путь к файлу. + +Именно здесь вступают в игру загрузчики. Загрузчик — это объект, задачей которого является взять имя шаблона (идентифицирующую строку) и предоставить Latte его исходный код. Latte при выполнении этой задачи полностью полагается на настроенный загрузчик. Это относится не только к начальному шаблону, запрошенному с помощью `$latte->render('main.latte')`, но и к **каждому шаблону, на который ссылаются внутри** с помощью тегов, таких как `{include ...}`, `{layout ...}`, `{embed ...}` или `{import ...}`. + +Зачем использовать пользовательский загрузчик? + +- **Загрузка из альтернативных источников:** Получение шаблонов, хранящихся в базе данных, в кеше (например, Redis или Memcached), в системе управления версиями (например, Git, на основе конкретного коммита) или динамически генерируемых. +- **Реализация пользовательских конвенций именования:** Вы можете захотеть использовать более короткие псевдонимы для шаблонов или реализовать специфическую логику путей поиска (например, сначала искать в каталоге темы, затем вернуться к каталогу по умолчанию). +- **Добавление безопасности или контроля доступа:** Пользовательский загрузчик может проверять права пользователя перед загрузкой определенных шаблонов. +- **Предварительная обработка:** Хотя это обычно не рекомендуется ([проходы компиляции |compiler-passes] лучше), загрузчик *теоретически* мог бы предварительно обработать содержимое шаблона, прежде чем передать его в Latte. + +Загрузчик для экземпляра `Latte\Engine` устанавливается с помощью метода `setLoader()`: + +```php +$latte = new Latte\Engine; + +// Использование FileLoader по умолчанию для файлов в '/path/to/templates' +$loader = new Latte\Loaders\FileLoader('/path/to/templates'); +$latte->setLoader($loader); +``` + +Загрузчик должен реализовывать интерфейс `Latte\Loader`. + + +Встроенные Загрузчики +===================== + +Latte предлагает несколько стандартных загрузчиков: + + +FileLoader +---------- + +Это **загрузчик по умолчанию**, используемый классом `Latte\Engine`, если не указан другой. Он загружает шаблоны непосредственно из файловой системы. + +При желании вы можете установить корневой каталог для ограничения доступа: + +```php +use Latte\Loaders\FileLoader; + +// Следующее позволит загружать шаблоны только из каталога /var/www/html/templates +$loader = new FileLoader('/var/www/html/templates'); +$latte->setLoader($loader); + +// $latte->render('../../../etc/passwd'); // Это выбросило бы исключение + +// Рендеринг шаблона, расположенного в /var/www/html/templates/pages/contact.latte +$latte->render('pages/contact.latte'); +``` + +При использовании тегов, таких как `{include}` или `{layout}`, он разрешает имена шаблонов относительно текущего шаблона, если не указан абсолютный путь. + + +StringLoader +------------ + +Этот загрузчик получает содержимое шаблона из ассоциативного массива, где ключи — это имена шаблонов (идентификаторы), а значения — строки исходного кода шаблона. Он особенно полезен для тестирования или небольших приложений, где шаблоны могут храниться непосредственно в PHP-коде. + +```php +use Latte\Loaders\StringLoader; + +$loader = new StringLoader([ + 'main.latte' => 'Hello {$name}, include is below:{include helper.latte}', + 'helper.latte' => '{var $x = 10}Included content: {$x}', + // Добавьте другие шаблоны по мере необходимости +]); + +$latte->setLoader($loader); + +$latte->render('main.latte', ['name' => 'World']); +// Вывод: Hello World, include is below:Included content: 10 +``` + +Если вам нужно отрендерить только один шаблон непосредственно из строки без необходимости включения или наследования, ссылающихся на другие именованные строковые шаблоны, вы можете передать строку напрямую методу `render()` или `renderToString()` при использовании `StringLoader` без массива: + +```php +$loader = new StringLoader; +$latte->setLoader($loader); + +$templateString = 'Hello {$name}!'; +$output = $latte->renderToString($templateString, ['name' => 'Alice']); +// $output содержит 'Hello Alice!' +``` + + +Создание пользовательского Загрузчика +===================================== + +Для создания пользовательского загрузчика (например, для загрузки шаблонов из базы данных, кеша, системы управления версиями или другого источника) вам необходимо создать класс, реализующий интерфейс [api:Latte\Loader]. + +Давайте посмотрим, что должен делать каждый метод. + + +getContent(string $name): string .[method] +------------------------------------------ +Это основной метод загрузчика. Его задача — получить и вернуть полный исходный код шаблона, идентифицированного с помощью `$name` (как передано методу `$latte->render()` или возвращено методом [#getReferredName()]). + +Если шаблон не может быть найден или доступ к нему невозможен, этот метод **должен выбросить исключение `Latte\RuntimeException`**. + +```php +public function getContent(string $name): string +{ + // Пример: Загрузка из гипотетического внутреннего хранилища + $content = $this->storage->read($name); + if ($content === null) { + throw new Latte\RuntimeException("Template '$name' cannot be loaded."); + } + return $content; +} +``` + + +getReferredName(string $name, string $referringName): string .[method] +---------------------------------------------------------------------- +Этот метод отвечает за разрешение имен шаблонов, используемых внутри тегов, таких как `{include}`, `{layout}` и т.д. Когда Latte встречает, например, `{include 'partial.latte'}` внутри `main.latte`, он вызывает этот метод с `$name = 'partial.latte'` и `$referringName = 'main.latte'`. + +Задача метода — преобразовать `$name` в канонический идентификатор (например, абсолютный путь, уникальный ключ базы данных), который будет использоваться при вызове других методов загрузчика, на основе контекста, предоставленного в `$referringName`. + +```php +public function getReferredName(string $name, string $referringName): string +{ + return ...; +} +``` + + +getUniqueId(string $name): string .[method] +------------------------------------------- +Latte использует кеш скомпилированных шаблонов для повышения производительности. Каждый скомпилированный файл шаблона нуждается в уникальном имени, производном от идентификатора исходного шаблона. Этот метод предоставляет строку, которая **однозначно идентифицирует** шаблон `$name`. + +Для шаблонов, основанных на файлах, может служить абсолютный путь. Для шаблонов в базе данных распространена комбинация префикса и ID базы данных. + +```php +public function getUniqueId(string $name): string +{ + return ...; +} +``` + + +Пример: Простой Загрузчик из Базы Данных +---------------------------------------- + +Этот пример показывает базовую структуру загрузчика, который загружает шаблоны, хранящиеся в таблице базы данных с именем `templates` со столбцами `name` (уникальный идентификатор), `content` и `updated_at`. + +```php +use Latte; + +class DatabaseLoader implements Latte\Loader +{ + public function __construct( + private \PDO $db, + ) { + } + + public function getContent(string $name): string + { + $stmt = $this->db->prepare('SELECT content FROM templates WHERE name = ?'); + $stmt->execute([$name]); + $content = $stmt->fetchColumn(); + if ($content === false) { + throw new Latte\RuntimeException("Template '$name' not found in database."); + } + return $content; + } + + // Этот простой пример предполагает, что имена шаблонов ('homepage', 'article', и т.д.) + // являются уникальными ID и шаблоны не ссылаются друг на друга относительно. + public function getReferredName(string $name, string $referringName): string + { + return $name; + } + + public function getUniqueId(string $name): string + { + // Использование префикса и самого имени здесь уникально и достаточно + return 'db_' . $name; + } +} + +// Использование: +$pdo = new \PDO(/* детали подключения */); +$loader = new DatabaseLoader($pdo); +$latte->setLoader($loader); +$latte->render('homepage'); // Загрузит шаблон с именем 'homepage' из БД +``` + +Пользовательские загрузчики дают вам полный контроль над тем, откуда берутся ваши шаблоны Latte, что позволяет интегрироваться с различными системами хранения и рабочими процессами. diff --git a/latte/ru/recipes.texy b/latte/ru/recipes.texy index b978c389a8..7a3eff7774 100644 --- a/latte/ru/recipes.texy +++ b/latte/ru/recipes.texy @@ -1,45 +1,45 @@ -Советы и рекомендации -********************* +Советы и трюки +************** -Редакторы и IDE .[#toc-editors-and-ide] -======================================= +Редакторы и IDE +=============== -Пишите шаблоны в редакторе или IDE, в которых есть поддержка Latte. Это будет намного приятнее. +Пишите шаблоны в редакторе или IDE, который поддерживает Latte. Это будет гораздо приятнее. -- NetBeans IDE имеет встроенную поддержку -- PhpStorm: установите [плагин Latte |https://plugins.jetbrains.com/plugin/7457-latte] в `Settings > Plugins > Marketplace` -- VS Code: найдите в маркерплейсе плагин "Nette Latte + Neon" -- Sublime Text 3: в Package Control найдите и установите пакет `Nette` и выберите Latte in `View > Syntax` -- в старых редакторах используйте подсветку Smarty для файлов .latte +- PhpStorm: установите в `Settings > Plugins > Marketplace` [плагин Latte |https://plugins.jetbrains.com/plugin/7457-latte] +- VS Code: установите [Nette Latte + Neon |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte], [Nette Latte templates |https://marketplace.visualstudio.com/items?itemName=smuuf.latte-lang] или новейший [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode] плагин +- NetBeans IDE: встроенная поддержка Latte является частью установки +- Sublime Text 3: в Package Control найдите и установите пакет `Nette` и выберите Latte в `View > Syntax` +- в старых редакторах используйте для файлов .latte подсветку Smarty -Плагин для PhpStorm очень продвинутый и может отлично подсказывать PHP код. Для оптимальной работы используйте [типизированные шаблоны |type-system]. +Плагин для PhpStorm очень продвинутый и отлично подсказывает PHP-код. Чтобы он работал оптимально, используйте [типизированные шаблоны |type-system]. [* latte-phpstorm-plugin.webp *] -Поддержку Latte также можно найти в веб-выделителе кода [Prism.js |https://prismjs.com/#supported-languages] и редакторе [Ace |https://ace.c9.io]. +Поддержку Latte вы также найдете в веб-подсветчике кода [Prism.js |https://prismjs.com/#supported-languages] и редакторе [Ace |https://ace.c9.io]. -Latte Inside JavaScript или CSS .[#toc-latte-inside-javascript-or-css] -====================================================================== +Latte внутри JavaScript или CSS +=============================== -Latte можно очень удобно использовать внутри JavaScript или CSS. Но как избежать того, чтобы Latte ошибочно считал код JavaScript или стиль CSS тегом Latte? +Latte можно очень удобно использовать и внутри JavaScript или CSS. Но как избежать ситуации, когда Latte ошибочно примет JavaScript-код или CSS-стиль за тег Latte? ```latte ``` **Вариант 1** -Избегайте ситуаций, когда буква следует сразу за `{`, вставляя между ними пробел, перенос строки или кавычки: +Избегайте ситуации, когда буква следует сразу за `{`, например, вставив перед ней пробел, перенос строки или кавычку: ```latte -

                                                                                                            + +

                                                                                                            ``` -Два способа и два разных вида экранирования данных. В пределах ` ``` -Однако если мы хотим вставить его в HTML-атрибут, нам все равно придется экранировать кавычки в HTML-сущности: +Однако, если бы мы хотели вставить его в HTML-атрибут, нужно еще экранировать кавычки в HTML-сущности: ```html
                                                                                                            ``` -Однако вложенный контекст не обязательно должен быть только JS или CSS. Обычно это также URL. Параметры в URL экранируются путем преобразования специальных символов в последовательности, начинающиеся с `%`. Пример: +Вложенным контекстом может быть не только JS или CSS. Обычно им является также URL. Параметры в URL экранируются так, что символы со специальным значением преобразуются в последовательности, начинающиеся с `%`. Пример: ``` https://example.org/?a=Jazz&b=Rock%27n%27Roll ``` -Когда мы выводим эту строку в атрибуте, мы все равно применяем экранирование в соответствии с этим контекстом и заменяем `&` with `&`: +А когда мы выводим эту строку в атрибуте, еще применяем экранирование в соответствии с этим контекстом и заменяем `&` на `&`: ```html ``` -Если вы дочитали до этого места, поздравляю, это было утомительно. Теперь у вас есть хорошее представление о том, что такое контекст и экранирование. И вам не нужно беспокоиться о том, что это сложно. Latte делает это за вас автоматически. +Если вы дочитали до этого места, поздравляем, это было утомительно. Теперь у вас есть хорошее представление о том, что такое контексты и экранирование. И не беспокойтесь, что это сложно. Latte делает это за вас автоматически. -Latte против наивных систем .[#toc-latte-vs-naive-systems] -========================================================== +Latte против наивных систем +=========================== -Мы показали, как правильно выполнять экранирование в HTML-документе и насколько важно знать контекст, т.е. куда вы выводите данные. Другими словами, как работает контекстно-зависимая экранировка. -Хотя это необходимое условие для функциональной защиты от XSS, **Latte - единственная система шаблонов для PHP, которая это делает.**. +Мы показали, как правильно экранировать в HTML-документе и насколько важно знание контекста, то есть места, где мы выводим данные. Другими словами, как работает контекстно-зависимое экранирование. Хотя это необходимое условие для функциональной защиты от XSS, **Latte — единственная система шаблонов для PHP, которая это умеет.** -Как такое возможно, когда все системы сегодня утверждают, что у них есть автоматическое экранирование? -Автоматическое экранирование без знания контекста - это полная чушь, которая **создает ложное чувство безопасности**. +Как это возможно, если все системы сегодня утверждают, что у них есть автоматическое экранирование? Автоматическое экранирование без знания контекста — это немного чушь, которая **создает ложное чувство безопасности**. -Системы шаблонизации, такие как Twig, Laravel Blade и другие, не видят HTML-структуры в шаблоне. Поэтому они не видят и контекста. По сравнению с Latte, они слепы и наивны. Они работают только со своей собственной разметкой, все остальное для них - поток нерелевантных символов: +Системы шаблонов, такие как Twig, Laravel Blade и другие, не видят в шаблоне никакой HTML-структуры. Следовательно, они не видят и контекстов. По сравнению с Latte они слепы и наивны. Они обрабатывают только собственные теги, все остальное для них — несущественный поток символов:
                                                                                                            -```twig .{file:Twig template as seen by Twig himself} -░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░ -░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░{{ text }}░░░░ +```twig .{file:Шаблон Twig, как его видит сам Twig} +░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░ +░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░ ``` -```twig .{file:Twig template as the designer sees it} -- in text: {{ text }} -- in tag: -- in attribute: -- in unquoted attribute: -- in attribute containing URL: -- in attribute containing JavaScript: -- in attribute containing CSS: -- in JavaScriptu: -- in CSS: -- in comment: +```twig .{file:Шаблон Twig, как его видит дизайнер} +- в тексте: {{ foo }} +- в теге: +- в атрибуте: +- в атрибуте без кавычек: +- в атрибуте, содержащем URL: +- в атрибуте, содержащем JavaScript: +- в атрибуте, содержащем CSS: +- в JavaScript: +- в CSS: +- в комментарии: ```
                                                                                                            -Наивные системы просто механически преобразуют символы `< > & ' "` в HTML-сущности, что является правильным способом экранирования в большинстве случаев, но далеко не всегда. Таким образом, они не могут обнаружить или предотвратить различные дыры в безопасности, как мы покажем ниже. +Наивные системы просто механически преобразуют символы `< > & ' "` в HTML-сущности, что хотя и является в большинстве случаев использования действительным способом экранирования, но далеко не всегда. Они не могут ни обнаружить, ни предотвратить возникновение различных дыр в безопасности, как мы покажем далее. -Latte видит шаблон так же, как и вы. Он понимает HTML, XML, распознает теги, атрибуты и т.д. И благодаря этому он различает контексты и обрабатывает данные соответствующим образом. Поэтому он предлагает действительно эффективную защиту от критической уязвимости Cross-site Scripting. +Latte видит шаблон так же, как и вы. Он понимает HTML, XML, распознает теги, атрибуты и т. д. И благодаря этому различает отдельные контексты и в соответствии с ними обрабатывает данные. Таким образом, он предлагает действительно эффективную защиту от критической уязвимости межсайтового скриптинга. + +
                                                                                                            + +```latte .{file:Шаблон Latte, как его видит Latte} +░░░░░░░░░░░{$foo} +░░░░░░░░░░ +░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░ +░░░░░░░░░ +░░░░░░░░░░░░░░░ +``` + +```latte .{file:Шаблон Latte, как его видит дизайнер} +- в тексте: {$foo} +- в теге: +- в атрибуте: +- в атрибуте без кавычек: +- в атрибуте, содержащем URL: +- в атрибуте, содержащем JavaScript: +- в атрибуте, содержащем CSS: +- в JavaScript: +- в CSS: +- в комментарии: +``` + +
                                                                                                            -Живая демонстрация .[#toc-live-demonstration] -============================================= +Живой пример +============ -Слева вы видите шаблон в Latte, справа - сгенерированный HTML-код. Переменная `$text` выводится несколько раз, каждый раз в несколько ином контексте. И поэтому экранируется немного по-разному. Вы можете самостоятельно отредактировать код шаблона, например, изменить содержимое переменной и т.д. Попробуйте: +Слева вы видите шаблон в Latte, справа — сгенерированный HTML-код. Несколько раз здесь выводится переменная `$text`, и каждый раз в немного другом контексте. И, следовательно, немного по-другому экранированная. Код шаблона вы можете редактировать сами, например, изменить содержимое переменной и т. д. Попробуйте:
                                                                                                            ``` .{file:template.latte; min-height: 14em}[fiddle-source] -{* TRY TO EDIT THIS TEMPLATE *} +{* ПОПРОБУЙТЕ ОТРЕДАКТИРОВАТЬ ЭТОТ ШАБЛОН *} {var $text = "Rock'n'Roll"} - {$text} - @@ -270,63 +286,61 @@ Latte видит шаблон так же, как и вы. Он понимает
                                                                                                            -Разве это не здорово! Latte делает контекстно-зависимое экранирование автоматически, так что программист: +Разве это не здорово! Latte делает контекстно-зависимое экранирование автоматически, поэтому программист: -- не нужно думать или знать, как экранировать данные +- не должен думать или знать, как где экранировать - не может ошибиться -- не может забыть об этом +- не может забыть об экранировании -Это даже не все контексты, которые Latte различает при выводе и для которых настраивает обработку данных. Сейчас мы рассмотрим более интересные случаи. +Это даже не все контексты, которые Latte различает при выводе и для которых адаптирует обработку данных. Другие интересные случаи мы рассмотрим сейчас. -Как взломать наивные системы .[#toc-how-to-hack-naive-systems] -============================================================== +Как взломать наивные системы +============================ -На нескольких практических примерах мы покажем, насколько важно контекстное разграничение и почему наивные системы шаблонов не обеспечивают достаточной защиты от XSS, в отличие от Latte. -Мы будем использовать Twig в качестве представителя наивной системы в примерах, но то же самое относится и к другим системам. +На нескольких практических примерах мы покажем, насколько важно различать контексты и почему наивные системы шаблонов не обеспечивают достаточной защиты от XSS, в отличие от Latte. В качестве представителя наивной системы мы будем использовать в примерах Twig, но то же самое относится и к другим системам. -Уязвимость атрибутов .[#toc-attribute-vulnerability] ----------------------------------------------------- +Уязвимость через атрибут +------------------------ -Попробуем внедрить вредоносный код на страницу с помощью атрибута HTML, как мы [показали выше |#How-Does-the-Vulnerability-Arise]. Пусть у нас есть шаблон в Twig, отображающий изображение: +Попытаемся внедрить в страницу вредоносный код с помощью HTML-атрибута, как мы [показывали выше |#Как возникает уязвимость]. Возьмем шаблон в Twig, отображающий изображение: ```twig .{file:Twig} {{ ``` -Обратите внимание, что вокруг значений атрибутов нет кавычек. Возможно, кодер забыл их, что случается сплошь и рядом. Например, в React код пишется вот так, без кавычек, и кодер, переходящий с одного языка на другой, может легко забыть о кавычках. +Обратите внимание, что вокруг значений атрибутов нет кавычек. Кодер мог о них забыть, что просто случается. Например, в React код пишется так, без кавычек, и кодер, который переключается между языками, может легко забыть о кавычках. -Злоумышленник вставляет хитроумно сконструированную строку `foo onload=alert('Hacked!')` в качестве подписи к изображению. Мы уже знаем, что Twig не может определить, выводится ли переменная в потоке HTML-текста, внутри атрибута, внутри HTML-комментария и т.д.; короче говоря, он не различает контексты. И он просто механически преобразует символы `< > & ' "` в HTML-сущности. -Таким образом, результирующий код будет выглядеть следующим образом: +Злоумышленник в качестве описания изображения вставляет хитро составленную строку `foo onload=alert('Hacked!')`. Мы уже знаем, что Twig не может определить, выводится ли переменная в потоке HTML-текста, внутри атрибута, HTML-комментария и т. д., короче говоря, не различает контексты. И просто механически преобразует символы `< > & ' "` в HTML-сущности. Так что результирующий код будет выглядеть так: ```html foo ``` -**Создана брешь в системе безопасности!** +**И возникла дыра в безопасности!** -Поддельный атрибут `onload` стал частью страницы, и браузер выполняет его сразу после загрузки изображения. +Частью страницы стал поддельный атрибут `onload`, и браузер сразу после загрузки изображения его выполнит. -Теперь посмотрим, как Latte обрабатывает тот же шаблон: +Теперь посмотрим, как с тем же шаблоном справится Latte: ```latte .{file:Latte} {$imageAlt} ``` -Latte видит шаблон так же, как и вы. В отличие от Twig, он понимает HTML и знает, что переменная выводится как значение атрибута, которое не заключено в кавычки. Поэтому он добавляет их. Когда злоумышленник вставит такую же кавычку, результирующий код будет выглядеть следующим образом: +Latte видит шаблон так же, как и вы. В отличие от Twig, он понимает HTML и знает, что переменная выводится как значение атрибута, который не заключен в кавычки. Поэтому он их добавит. Когда злоумышленник вставит то же описание, результирующий код будет выглядеть так: ```html foo onload=alert('Hacked!') ``` -**Латт успешно предотвратил XSS.**. +**Latte успешно предотвратил XSS.** -Печать переменной в JavaScript .[#toc-printing-a-variable-in-javascript] ------------------------------------------------------------------------- +Вывод переменной в JavaScript +----------------------------- -Благодаря контекстно-зависимой экранировке можно использовать переменные PHP в JavaScript. +Благодаря контекстно-зависимому экранированию можно совершенно нативно использовать PHP-переменные внутри JavaScript. ```latte

                                                                                                            {$movie}

                                                                                                            @@ -334,7 +348,7 @@ Latte видит шаблон так же, как и вы. В отличие о ``` -Если переменная `$movie` хранит строку `'Amarcord & 8 1/2'`, то она генерирует следующий вывод. Обратите внимание на различное экранирование, используемое в HTML и JavaScript, а также в атрибуте `onclick`: +Если переменная `$movie` будет содержать строку `'Amarcord & 8 1/2'`, сгенерируется следующий вывод. Обратите внимание, что внутри HTML используется другое экранирование, чем внутри JavaScript, и еще другое в атрибуте `onclick`: ```latte

                                                                                                            Amarcord & 8 1/2

                                                                                                            @@ -343,29 +357,27 @@ Latte видит шаблон так же, как и вы. В отличие о ``` -Проверка ссылок .[#toc-link-checking] -------------------------------------- +Проверка ссылок +--------------- -Latte автоматически проверяет, содержит ли переменная, используемая в атрибутах `src` или `href`, веб-адрес (т.е. протокол HTTP), и предотвращает запись ссылок, которые могут представлять угрозу безопасности. +Latte автоматически проверяет, содержит ли переменная, используемая в атрибутах `src` или `href`, веб-URL (т.е. протокол HTTP) и предотвращает вывод ссылок, которые могут представлять угрозу безопасности. ```latte {var $link = 'javascript:attack()'} -click here +кликни ``` -Пишет: +Выводит: ```latte -click here +кликни ``` Проверку можно отключить с помощью фильтра [nocheck |filters#nocheck]. -Пределы Latte .[#toc-limits-of-latte] -===================================== +Ограничения Latte +================= -Latte не является полной XSS-защитой для всего приложения. Мы были бы недовольны, если бы вы перестали думать о безопасности при использовании Latte. -Цель Latte - гарантировать, что злоумышленник не сможет изменить структуру страницы, подделать элементы или атрибуты HTML. Но он не проверяет корректность содержания выводимых данных. Или корректность поведения JavaScript. -Это выходит за рамки системы шаблонов. Проверка корректности данных, особенно введенных пользователем и, следовательно, не вызывающих доверия, является важной задачей для программиста. +Latte не является абсолютно полной защитой от XSS для всего приложения. Мы бы не хотели, чтобы вы, используя Latte, перестали думать о безопасности. Цель Latte — обеспечить, чтобы злоумышленник не мог изменить структуру страницы, подделать HTML-элементы или атрибуты. Но он не контролирует содержательную правильность выводимых данных. Или правильность поведения JavaScript. Это уже выходит за рамки компетенции системы шаблонов. Проверка правильности данных, особенно введенных пользователем и, следовательно, ненадежных, является важной задачей программиста. diff --git a/latte/ru/sandbox.texy b/latte/ru/sandbox.texy index 3b471cb89d..08e3116d44 100644 --- a/latte/ru/sandbox.texy +++ b/latte/ru/sandbox.texy @@ -1,12 +1,10 @@ Песочница ********* -.[perex]{data-version:2.8} -Песочница обеспечивает уровень безопасности, который дает вам контроль над тем, какие теги, функции PHP, методы и т.д. могут быть использованы в шаблонах. Благодаря режиму песочницы вы можете безопасно сотрудничать с клиентом или внешним кодером при создании шаблонов, не беспокоясь о компрометации приложения или нежелательных операциях. +.[perex] +Песочница предоставляет уровень безопасности, который дает вам контроль над тем, какие теги, PHP-функции, методы и т.д. могут быть использованы в шаблонах. Благодаря режиму песочницы вы можете безопасно сотрудничать с клиентом или внешним кодером при создании шаблонов, не опасаясь нарушения работы приложения или нежелательных операций. -Как это работает? Мы просто определяем, что мы хотим разрешить в шаблоне. Вначале все запрещено, и мы постепенно предоставляем разрешения: - -Следующий код разрешает шаблону использовать теги `{block}`, `{if}`, `{else}` и `{=}` (последний - это тег для [печати переменной или выражения |tags#Printing]) и все фильтры: +Как это работает? Мы просто определяем, что все разрешено шаблону. При этом по умолчанию все запрещено, и мы постепенно разрешаем. Следующим кодом мы разрешим автору шаблона использовать теги `{block}`, `{if}`, `{else}` и `{=}`, что является тегом для [вывода переменной или выражения |tags#Вывод], и все фильтры: ```php $policy = new Latte\Sandbox\SecurityPolicy; @@ -16,7 +14,7 @@ $policy->allowFilters($policy::All); $latte->setPolicy($policy); ``` -Мы также можем разрешить доступ к глобальным функциям, методам или свойствам объектов: +Далее мы можем разрешить отдельные функции, методы или свойства объектов: ```php $policy->allowFunctions(['trim', 'strlen']); @@ -24,30 +22,35 @@ $policy->allowMethods(Nette\Security\User::class, ['isLoggedIn', 'isAllowed']); $policy->allowProperties(Nette\Database\Row::class, $policy::All); ``` -Разве это не удивительно? Вы можете контролировать все на очень низком уровне. Если шаблон пытается вызвать неавторизованную функцию или получить доступ к неавторизованному методу или свойству, он выбрасывает исключение `Latte\SecurityViolationException`. +Разве это не замечательно? Вы можете на очень низком уровне контролировать абсолютно все. Если шаблон попытается вызвать запрещенную функцию или получить доступ к запрещенному методу или свойству, это закончится исключением `Latte\SecurityViolationException`. -Создание политик с нуля, когда все запрещено, может быть неудобным, поэтому вы можете начать с безопасного фундамента: +Создавать политику с нуля, когда все запрещено, может быть неудобно, поэтому вы можете начать с безопасной основы: ```php $policy = Latte\Sandbox\SecurityPolicy::createSafePolicy(); ``` -Это означает, что все стандартные теги разрешены, кроме `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget`. -Также разрешены все стандартные фильтры, кроме `datastream`, `noescape` и `nocheck`. Наконец, доступ к методам и свойствам объекта `$iterator` также разрешен. +Безопасная основа означает, что разрешены все стандартные теги, кроме `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget`. Разрешены стандартные фильтры, кроме `datastream`, `noescape` и `nocheck`. И, наконец, разрешен доступ к методам и свойствам объекта `$iterator`. -Эти правила применяются к шаблону, который мы вставляем с новым [`{sandbox}` |tags#Including-Templates] тегом. Это что-то вроде `{include}`, но в нем включен режим песочницы, а также не передаются внешние переменные: +Правила применяются к шаблону, который мы вставляем тегом [`{sandbox}` |tags#Включение шаблона]. Это своего рода аналог `{include}`, который, однако, включает безопасный режим и также не передает никаких переменных: ```latte {sandbox 'untrusted.latte'} ``` -Таким образом, макет и отдельные страницы могут использовать все теги и переменные, как и раньше, ограничения будут накладываться только на шаблон `untrusted.latte`. +Таким образом, макет и отдельные страницы могут беспрепятственно использовать все теги и переменные, только к шаблону `untrusted.latte` будут применены ограничения. -Некоторые нарушения, такие как использование запрещенного тега или фильтра, обнаруживаются во время компиляции. Другие, такие как вызов неразрешенных методов объекта, - во время выполнения. -Шаблон также может содержать любые другие ошибки. Чтобы предотвратить выброс исключения из шаблона, находящегося в песочнице, которое нарушает весь рендеринг, вы можете определить [собственный обработчик исключений |develop#Exception-Handler], который, например, просто регистрирует его. +Некоторые нарушения, такие как использование запрещенного тега или фильтра, обнаруживаются во время компиляции. Другие, например, вызов запрещенных методов объекта, только во время выполнения. Шаблон также может содержать любые другие ошибки. Чтобы из песочницы не могло вылететь исключение, которое нарушит весь рендеринг, можно определить собственный [обработчик исключений |develop#Обработчик исключений], который, например, его залогирует. -Если мы хотим включить режим песочницы непосредственно для всех шаблонов, то это просто: +Если бы мы хотели включить режим песочницы непосредственно для всех шаблонов, это легко сделать: ```php $latte->setSandboxMode(); ``` + +Чтобы быть уверенным, что пользователь не вставит в страницу PHP-код, который хотя и синтаксически правильный, но запрещен и вызовет PHP Compile Error, рекомендуется [проверять шаблоны с помощью PHP-линтера |develop#Проверка сгенерированного кода]. Эту функциональность вы включите методом `Engine::enablePhpLint()`. Поскольку для проверки требуется вызывать бинарный файл PHP, передайте путь к нему в качестве параметра: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); +``` diff --git a/latte/ru/syntax.texy b/latte/ru/syntax.texy index 00039cd361..8f2ef52fea 100644 --- a/latte/ru/syntax.texy +++ b/latte/ru/syntax.texy @@ -2,62 +2,60 @@ ********* .[perex] -Синтаксис Latte родился из практических потребностей веб-дизайнеров. Мы искали наиболее удобный синтаксис, с помощью которого можно элегантно писать конструкции, которые в других случаях представляют собой настоящую проблему. -В то же время, все выражения написаны точно так же, как в PHP, поэтому вам не придется изучать новый язык. Вы просто используете то, что уже знаете. +Синтаксис Latte возник из практических требований веб-дизайнеров. Мы искали самый удобный синтаксис, с помощью которого можно элегантно записывать даже конструкции, которые в противном случае представляют собой настоящую головоломку. В то же время все выражения пишутся точно так же, как в PHP, так что вам не придется учить новый язык. Вы просто используете то, что уже давно умеете. -Ниже приведен минимальный шаблон, иллюстрирующий несколько базовых элементов: теги, n:attributes, комментарии и фильтры. +Ниже приведен минимальный шаблон, иллюстрирующий несколько основных элементов: теги, n:атрибуты, комментарии и фильтры. ```latte -{* this is a comment *} -
                                                                                                              {* n:if is n:atribut *} -{foreach $items as $item} {* tag representing foreach loop *} -
                                                                                                            • {$item|capitalize}
                                                                                                            • {* tag that prints a variable with a filter *} -{/foreach} {* end of cycle *} +{* это комментарий *} +
                                                                                                                {* n:if - это n:атрибут *} +{foreach $items as $item} {* тег, представляющий цикл foreach *} +
                                                                                                              • {$item|capitalize}
                                                                                                              • {* тег, выводящий переменную с фильтром *} +{/foreach} {* конец цикла *}
                                                                                                              ``` -Давайте подробнее рассмотрим эти важные элементы и то, как они могут помочь вам создать невероятный шаблон. +Давайте подробнее рассмотрим эти важные элементы и то, как они могут помочь вам создать потрясающий шаблон. -Теги .[#toc-tags] -================= +Теги +==== -Шаблон содержит теги, которые управляют логикой шаблона (например, циклы *foreach*) или выходными выражениями. Для обоих случаев используется один разделитель `{ ... }`, поэтому вам не нужно думать о том, какой разделитель использовать в той или иной ситуации, как в других системах. -Если за символом `{` следует кавычка или пробел, Latte не считает его началом тега, поэтому вы можете без проблем использовать в своих шаблонах конструкции JavaScript, JSON или правила CSS. +Шаблон содержит теги, которые управляют логикой шаблона (например, циклы *foreach*) или выводят выражения. Для обоих используется единый разделитель `{ ... }`, так что вам не нужно думать, какой разделитель использовать в какой ситуации, как это бывает в других системах. Если за символом `{` следует кавычка или пробел, Latte не считает его началом тега, благодаря чему вы можете без проблем использовать в шаблонах и JavaScript-конструкции, JSON или правила в CSS. -Смотрите [обзор всех тегов |tags]. Кроме того, вы можете создавать [пользовательские теги |extending-latte#Tags]. +Посмотрите [обзор всех тегов |tags]. Кроме того, вы можете создавать и [пользовательские теги |custom tags]. -Latte понимает PHP .[#toc-latte-understands-php] -================================================ +Latte понимает PHP +================== -Внутри тегов можно использовать выражения PHP, которые вы хорошо знаете: +Внутри тегов вы можете использовать PHP-выражения, которые вам хорошо знакомы: - переменные - строки (включая HEREDOC и NOWDOC), массивы, числа и т.д. - [операторы |https://www.php.net/manual/en/language.operators.php] -- вызовы функций и методов (которые могут быть ограничены [песочницей |sandbox]) -- [соответствие |https://www.php.net/manual/en/control-structures.match.php] +- вызовы функций и методов (которые можно ограничить [песочницей |sandbox]) +- [match |https://www.php.net/manual/en/control-structures.match.php] - [анонимные функции |https://www.php.net/manual/en/functions.arrow.php] -- [обратные вызовы |https://www.php.net/manual/en/functions.first_class_callable_syntax.php] +- [коллбэки |https://www.php.net/manual/en/functions.first_class_callable_syntax.php] - многострочные комментарии `/* ... */` -- и т.д. +- и т.д… -Кроме того, Latte добавляет несколько [приятных расширений |#Syntactic-Sugar] к синтаксису PHP. +Кроме того, Latte дополняет синтаксис PHP несколькими [приятными расширениями |#Синтаксический сахар]. -n:attributes .[#toc-n-attributes] -================================= +n:атрибуты +========== -Каждый парный тег, например `{if} … {/if}`, работающий с одним элементом HTML, может быть записан в нотации [n:attribute |#n:attribute]. Например, `{foreach}` в приведенном выше примере можно записать и так: +Все парные теги, например `{if} … {/if}`, оперирующие над одним HTML-элементом, можно переписать в виде n:атрибутов. Так можно было бы записать, например, и `{foreach}` во вводном примере: ```latte -
                                                                                                                +
                                                                                                                • {$item|capitalize}
                                                                                                                ``` -Функциональность тогда соответствует элементу HTML, в котором она записана: +Функциональность тогда относится к HTML-элементу, в который она помещена: ```latte {var $items = ['I', '♥', 'Latte']} @@ -65,7 +63,7 @@ n:attributes .[#toc-n-attributes]

                                                                                                                {$item}

                                                                                                                ``` -Prints: +выводит: ```latte

                                                                                                                I

                                                                                                                @@ -73,7 +71,7 @@ Prints:

                                                                                                                Latte

                                                                                                                ``` -Используя префикс `inner-`, мы можем изменить поведение так, чтобы функциональность применялась только к телу элемента: +С помощью префикса `inner-` мы можем изменить поведение так, чтобы оно относилось только к внутренней части элемента: ```latte
                                                                                                                @@ -82,7 +80,7 @@ Prints:
                                                                                                                ``` -Отпечатки: +Выведется: ```latte
                                                                                                                @@ -95,178 +93,184 @@ Prints:
                                                                                                                ``` -Или с помощью префикса `tag-` функциональность применяется только к HTML-тегам: +Или с помощью префикса `tag-` применим функциональность только к самим HTML-тегам: ```latte -

                                                                                                                Title

                                                                                                                +

                                                                                                                Title

                                                                                                                ``` -В зависимости от значения переменной `$url` будет выведено следующее: +Что выведет в зависимости от переменной `$url`: ```latte -// when $url is empty +{* когда $url пуст *}

                                                                                                                Title

                                                                                                                -// when $url equals 'https://nette.org' +{* когда $url содержит 'https://nette.org' *}

                                                                                                                Title

                                                                                                                ``` -Однако n:attributes - это не только сокращение для парных тегов, есть и некоторые чистые n:attributes, например, лучший друг кодера [n:class |tags#n-class]. +Однако n:атрибуты — это не просто сокращение для парных тегов. Существуют и чистые n:атрибуты, такие как [n:href |application:creating-links#В шаблоне презентера] или очень удобный помощник кодера [n:class |tags#n:class]. -Фильтры .[#toc-filters] -======================= +Фильтры +======= -См. краткое описание [стандартных фильтров |filters]. +Посмотрите обзор [стандартных фильтров |filters]. -Latte позволяет вызывать фильтры с помощью знака трубы (пробел перед фильтром допускается): +Фильтры записываются после вертикальной черты (перед ней может быть пробел): ```latte

                                                                                                                {$heading|upper}

                                                                                                                ``` -Фильтры могут быть соединены в цепочку, в этом случае они применяются в порядке слева направо: +Фильтры можно объединять в цепочку, и они применяются в порядке слева направо: ```latte

                                                                                                                {$heading|lower|capitalize}

                                                                                                                ``` -Параметры помещаются после имени фильтра через двоеточие или запятую: +Параметры указываются после имени фильтра, разделенные двоеточиями или запятыми: ```latte

                                                                                                                {$heading|truncate:20,''}

                                                                                                                ``` -Фильтры могут быть применены к выражению: +Фильтры можно применять и к выражению: ```latte {var $name = ($title|upper) . ($subtitle|lower)} ``` -На блоке: +К блоку: ```latte

                                                                                                                {block |lower}{$heading}{/block}

                                                                                                                ``` -Или непосредственно на значении (в сочетании с [`{=expr}` | https://latte.nette.org/ru/tags#printing] тег): +Или непосредственно к значению (в сочетании с тегом [`{=expr}` |tags#Вывод]): ```latte

                                                                                                                {=' Hello world '|trim}

                                                                                                                ``` -Комментарии .[#toc-comments] -============================ +Динамические HTML-теги .{data-version:3.0.9} +============================================ + +Latte поддерживает динамические HTML-теги, которые полезны, когда вам нужна гибкость в именах тегов: + +```latte +Heading +``` + +Приведенный выше код может, например, генерировать `

                                                                                                                Heading

                                                                                                                ` или `

                                                                                                                Heading

                                                                                                                ` в зависимости от значения переменной `$level`. Динамические HTML-теги в Latte всегда должны быть парными. Их альтернативой является [n:tag |tags#n:tag]. + +Поскольку Latte является безопасной системой шаблонов, он проверяет, является ли результирующее имя тега допустимым и не содержит ли оно нежелательных или вредоносных значений. Кроме того, он гарантирует, что имя закрывающего тега всегда будет таким же, как имя открывающего тега. + -Комментарии пишутся таким образом и не попадают в вывод: +Комментарии +=========== + +Комментарии записываются таким образом и в вывод не попадают: ```latte -{* this is a comment in Latte *} +{* это комментарий в Latte *} ``` -Комментарии PHP работают внутри тегов: +Внутри тегов работают PHP-комментарии: ```latte {include 'file.info', /* value: 123 */} ``` -Синтаксический сахар .[#toc-syntactic-sugar] -============================================ +Синтаксический сахар +==================== -Строки без кавычек .[#toc-strings-without-quotation-marks] ----------------------------------------------------------- +Строки без кавычек +------------------ -Для простых строк кавычки можно не ставить: +Для простых строк можно опускать кавычки: ```latte -as in PHP: {var $arr = ['hello', 'btn--default', '€']} +как в PHP: {var $arr = ['hello', 'btn--default', '€']} -abbreviated: {var $arr = [hello, btn--default, €]} +сокращенно: {var $arr = [hello, btn--default, €]} ``` -Простые строки - это строки, состоящие исключительно из букв, цифр, знаков подчеркивания, дефисов и точек. Они не должны начинаться с цифры и не должны начинаться или заканчиваться дефисом. -Он не должен состоять только из заглавных букв и знаков подчеркивания, потому что тогда он считается константой (например, `PHP_VERSION`). -И он не должен пересекаться с ключевыми словами `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor`. +Простые строки — это те, которые состоят исключительно из букв, цифр, подчеркиваний, дефисов и точек. Они не должны начинаться с цифры и не должны начинаться или заканчиваться дефисом. Они не должны состоять только из заглавных букв и подчеркиваний, потому что тогда они считаются константами (например, `PHP_VERSION`). И они не должны конфликтовать с ключевыми словами: `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor`. -Короткий тернарный оператор .[#toc-short-ternary-operator] ----------------------------------------------------------- +Константы +--------- -Если третье значение тернарного оператора пустое, его можно опустить: +Поскольку для простых строк можно опускать кавычки, рекомендуется для отличия записывать глобальные константы с косой чертой в начале: ```latte -as in PHP: {$stock ? 'In stock' : ''} - -abbreviated: {$stock ? 'In stock'} +{if \PROJECT_ID === 1} ... {/if} ``` +Эта запись полностью валидна в самом PHP, косая черта говорит о том, что константа находится в глобальном пространстве имен. + -Современная ключевая нотация в массиве .[#toc-modern-key-notation-in-the-array] -------------------------------------------------------------------------------- +Сокращенный тернарный оператор +------------------------------ -Ключи массива могут быть записаны аналогично именованным параметрам при вызове функций: +Если третье значение тернарного оператора пустое, его можно опустить: ```latte -as in PHP: {var $arr = ['one' => 'item 1', 'two' => 'item 2']} +как в PHP: {$stock ? 'В наличии' : ''} -modern: {var $arr = [one: 'item 1', two: 'item 2']} +сокращенно: {$stock ? 'В наличии'} ``` -Фильтры .[#toc-filters] ------------------------ +Современная запись ключей в массиве +----------------------------------- -Фильтры можно использовать для любого выражения, просто заключите все в скобки: +Ключи в массиве можно записывать подобно именованным параметрам при вызове функций: ```latte -{var $content = ($text|truncate: 30|upper)} +как в PHP: {var $arr = ['one' => 'item 1', 'two' => 'item 2']} + +современно: {var $arr = [one: 'item 1', two: 'item 2']} ``` -Оператор `in` .[#toc-operator-in] ---------------------------------- +Фильтры +------- -Оператор `in` может быть использован для замены функции `in_array()`. Сравнение всегда строгое: +Фильтры можно применять к любым выражениям, достаточно заключить целое в скобки: ```latte -{* like in_array($item, $items, true) *} -{if $item in $items} - ... -{/if} +{var $content = ($text|truncate: 30|upper)} ``` -.{data-version:2.9} -Опциональная цепочка с неопределенно-безопасным оператором .[#toc-optional-chaining-with-undefined-safe-operator] ------------------------------------------------------------------------------------------------------------------ +Оператор `in` +------------- -Оператор undefined-safe `??->` похож на оператор nullsafe `?->`, но не вызывает ошибку, если переменная, свойство или индекс вообще не существует. +Оператором `in` можно заменить функцию `in_array()`. Сравнение всегда строгое: ```latte -{$order??->id} +{* аналог in_array($item, $items, true) *} +{if $item in $items} + ... +{/if} ``` -Это способ сказать, что когда `$order` определена и не является null, `$order->id` будет вычислена, но когда `$order` является null или не существует, остановите наши действия и просто верните null. - -```latte -{$user??->address??->street} -// roughly means isset($user) && isset($user->address) ? $user->address->street : null -``` +Исторический экскурс +-------------------- -Окно в историю .[#toc-a-window-into-history] --------------------------------------------- +Latte в ходе своей истории представило целый ряд синтаксических улучшений, которые через несколько лет появились в самом PHP. Например, в Latte можно было писать массивы как `[1, 2, 3]` вместо `array(1, 2, 3)` или использовать nullsafe оператор `$obj?->foo` задолго до того, как это стало возможным в самом PHP. Latte также ввело оператор распаковки массива `(expand) $arr`, который является эквивалентом сегодняшнего оператора `...$arr` из PHP. -За свою историю Latte придумал несколько синтаксических конфет, которые появились в самом PHP несколькими годами позже. Например, в Latte можно было записывать массивы в виде `[1, 2, 3]` вместо `array(1, 2, 3)` или использовать оператор nullsafe `$obj?->foo` задолго до того, как это стало возможным в самом PHP. В Latte также появился оператор расширения массива `(expand) $arr`, который является эквивалентом сегодняшнего оператора `...$arr` из PHP. +Undefined-safe оператор `??->`, который является аналогом nullsafe оператора `?->`, но не вызывает ошибку, если переменная не существует, возник по историческим причинам, и сегодня мы рекомендуем использовать стандартный PHP-оператор `?->`. -Ограничения PHP в Latte .[#toc-php-limitations-in-latte] -======================================================== +Ограничения PHP в Latte +======================= -В Latte можно писать только PHP-выражения. То есть, вы не можете объявлять классы или использовать [управляющие структуры |https://www.php.net/manual/en/language.control-structures.php], такие как `if`, `foreach`, `switch`, `return`, `try`, `throw` и другие, вместо которых Latte предлагает свои [теги |tags]. -Вы также не можете использовать [атрибуты |https://www.php.net/manual/en/language.attributes.php], [обратные знаки |https://www.php.net/manual/en/language.operators.execution.php] или [магические константы |https://www.php.net/manual/en/language.constants.magic.php], потому что это бессмысленно. -Вы даже не можете использовать `unset`, `echo`, `include`, `require`, `exit`, `eval`, потому что это не функции, а специальные конструкции языка PHP, а значит, не выражения. +В Latte можно записывать только PHP-выражения. То есть нельзя использовать инструкции, заканчивающиеся точкой с запятой. Нельзя объявлять классы или использовать [управляющие структуры |https://www.php.net/manual/en/language.control-structures.php], например `if`, `foreach`, `switch`, `return`, `try`, `throw` и другие, вместо которых Latte предлагает свои [теги |tags]. Также нельзя использовать [атрибуты |https://www.php.net/manual/en/language.attributes.php], [обратные кавычки |https://www.php.net/manual/en/language.operators.execution.php] или некоторые [магические константы |https://www.php.net/manual/en/language.constants.magic.php]. Нельзя использовать и `unset`, `echo`, `include`, `require`, `exit`, `eval`, потому что это не функции, а специальные языковые конструкции PHP, и они не являются выражениями. Комментарии поддерживаются только многострочные `/* ... */`. -Однако вы можете обойти эти ограничения, активировав расширение [RawPhpExtension |develop#RawPhpExtension], которое позволяет использовать любой PHP-код в теге `{php ...}` под ответственность автора шаблона. +Однако эти ограничения можно обойти, активировав расширение [RawPhpExtension |develop#RawPhpExtension], благодаря которому можно затем использовать в теге `{php ...}` любой PHP-код под ответственность автора шаблона. diff --git a/latte/ru/tags.texy b/latte/ru/tags.texy index c74c039299..a2fcf45546 100644 --- a/latte/ru/tags.texy +++ b/latte/ru/tags.texy @@ -2,167 +2,173 @@ ********** .[perex] -Сводка и описание всех встроенных тегов Latte. +Обзор и описание всех тегов системы шаблонов Latte, которые доступны вам по умолчанию. .[table-latte-tags language-latte] -|## Печать -| `{$var}`, `{...}` или `{=...}` | [печатает экранированную переменную или выражение |#Printing] -| `{$var\|filter}` | [печатает с фильтрами |#Filters] -| `{l}` или `{r}` | печатает символ `{` or `}` +|## Вывод +| `{$var}`, `{...}` или `{=...}` | [выводит экранированную переменную или выражение |#Вывод] +| `{$var\|filter}` | [выводит с использованием фильтров |#Фильтры] +| `{l}` или `{r}` | выводит символ `{` или `}` .[table-latte-tags language-latte] |## Условия -| `{if}`... `{elseif}`... `{else}`... `{/if}` | [условие if |#if-elseif-else] -| `{ifset}`... `{elseifset}`... `{/ifset}` | [условие ifset |#ifset-elseifset] -| `{ifchanged}`... `{/ifchanged}` | [проверка наличия изменений |#ifchanged] -| `{switch}` `{case}` `{default}` `{/switch}` | [условие switch |#switch-case-default] +| `{if}` … `{elseif}` … `{else}` … `{/if}` | [условие if |#if elseif else] +| `{ifset}` … `{elseifset}` … `{/ifset}` | [условие ifset |#ifset elseifset] +| `{ifchanged}` … `{/ifchanged}` | [проверка, произошло ли изменение |#ifchanged] +| `{switch}` `{case}` `{default}` `{/switch}` | [условие switch |#switch case default] +| `n:else` | [альтернативное содержимое для условий |#n:else] .[table-latte-tags language-latte] -|## Loops -| `{foreach}`... `{/foreach}` | [foreach |#foreach] -| `{for}`... `{/for}` | [for |#for] -| `{while}`... `{/while}` | [while |#while] -| `{continueIf $cond}` | [перейти к следующей итерации |#continueIf-skipIf-breakIf] -| `{skipIf $cond}` | [пропустить текущую итерацию цикла |#continueIf-skipIf-breakIf] -| `{breakIf $cond}` | [прервать цикл |#continueIf-skipIf-breakIf] -| `{exitIf $cond}` | [ранний выход |#exitIf] -| `{first}`... `{/first}` | [это первая итерация? |#first-last-sep] -| `{last}`... `{/last}` | [Это последняя итерация? |#first-last-sep] -| `{sep}`... `{/sep}` | [последует ли следующая итерация? |#first-last-sep] -| `{iterateWhile}`... `{/iterateWhile}` | [структурированный foreach |#iterateWhile] -| `$iterator` | [специальная переменная внутри цикла foreach |#iterator] +|## Циклы +| `{foreach}` … `{/foreach}` | [#foreach] +| `{for}` … `{/for}` | [#for] +| `{while}` … `{/while}` | [#while] +| `{continueIf $cond}` | [продолжить следующую итерацию |#continueIf skipIf breakIf] +| `{skipIf $cond}` | [пропустить итерацию |#continueIf skipIf breakIf] +| `{breakIf $cond}` | [прерывание цикла |#continueIf skipIf breakIf] +| `{exitIf $cond}` | [раннее завершение |#exitIf] +| `{first}` … `{/first}` | [это первый проход? |#first last sep] +| `{last}` … `{/last}` | [это последний проход? |#first last sep] +| `{sep}` … `{/sep}` | [будет ли еще один проход? |#first last sep] +| `{iterateWhile}` … `{/iterateWhile}` | [структурированный foreach |#iterateWhile] +| `$iterator` | [специальная переменная внутри foreach |#iterator] .[table-latte-tags language-latte] |## Включение других шаблонов -| `{include 'file.latte'}` | [включает шаблон из другого файла |#include] -| `{sandbox 'file.latte'}` | [включает шаблон в режиме песочницы |#sandbox] +| `{include 'file.latte'}` | [загружает шаблон из другого файла |#include] +| `{sandbox 'file.latte'}` | [загружает шаблон в режиме песочницы |#sandbox] .[table-latte-tags language-latte] |## Блоки, макеты, наследование шаблонов -| `{block}` | [анонимный блок |#block] -| `{block blockname}` | [определение блока |template-inheritance#Blocks] -| `{define blockname}` | [определение блока для будущего использования |template-inheritance#Definitions] -| `{include blockname}` | [печатает блок |template-inheritance#Printing-Blocks] -| `{include blockname from 'file.latte'}` | [печатает блок из файла |template-inheritance#Printing-Blocks] -| `{import 'file.latte'}` | [загружает блоки из другого шаблона |template-inheritance#Horizontal-Reuse] -| `{layout 'file.latte'}` / `{extends}` | [указание файла макета |template-inheritance#Layout-Inheritance] -| `{embed}`... `{/embed}` | [загружает шаблон или блок и позволяет перезаписывать блоки |template-inheritance#Unit-Inheritance] -| `{ifset blockname}`... `{/ifset}` | [условие, если блок определен |template-inheritance#Checking-Block-Existence] +| `{block}` | [анонимный блок |#block] +| `{block blockname}` | [определяет блок |template-inheritance#Блоки] +| `{define blockname}` | [определяет блок для последующего использования |template-inheritance#Определения] +| `{include blockname}` | [рендеринг блока |template-inheritance#Отрисовка блоков] +| `{include blockname from 'file.latte'}` | [рендерит блок из файла |template-inheritance#Отрисовка блоков] +| `{import 'file.latte'}` | [загружает блоки из шаблона |template-inheritance#Горизонтальное повторное использование] +| `{layout 'file.latte'}` / `{extends}` | [указывает файл с макетом |template-inheritance#Наследование макета] +| `{embed}` … `{/embed}` | [загружает шаблон или блок и позволяет переопределять блоки |template-inheritance#Модульное наследование] +| `{ifset blockname}` … `{/ifset}` | [условие, существует ли блок |template-inheritance#Проверка существования блоков] .[table-latte-tags language-latte] -|## Обработка исключений -| `{try}`... `{else}`... `{/try}` | [перехват исключений |#try] -| `{rollback}` | [отбрасывает блок try |#rollback] +|## Управление исключениями +| `{try}` … `{else}` … `{/try}` | [перехват исключений |#try] +| `{rollback}` | [отмена блока try |#rollback] .[table-latte-tags language-latte] |## Переменные -| `{var $foo = value}` | [создание переменных |#var-default] -| `{default $foo = value}` | [значение по умолчанию, когда переменная не объявлена |#var-default] -| `{parameters}` | [Объявление переменных, типы значений по умолчанию |#parameters] -| `{capture}`... `{/capture}` | [Захватывает секцию для переменной |#capture] +| `{var $foo = value}` | [создает переменную |#var default] +| `{default $foo = value}` | [создает переменную, если она не существует |#var default] +| `{parameters}` | [объявляет переменные, типы и значения по умолчанию |#parameters] +| `{capture}` … `{/capture}` | [захватывает блок в переменную |#capture] .[table-latte-tags language-latte] |## Типы -| `{varType}` | [объявляет тип переменной |type-system#varType] -| `{varPrint}` | [предлагает типы переменных |type-system#varPrint] -| `{templateType}` | [объявляет типы переменных с помощью класса |type-system#templateType] -| `{templatePrint}` | [генерирует класс со свойствами |type-system#templatePrint] +| `{varType}` | [объявляет тип переменной |type-system#varType] +| `{varPrint}` | [предлагает типы переменных |type-system#varPrint] +| `{templateType}` | [объявляет типы переменных в соответствии с классом |type-system#templateType] +| `{templatePrint}` | [предлагает класс с типами переменных |type-system#templatePrint] .[table-latte-tags language-latte] -|## Перевод -| `{_string}` | [печатает перевод |#Translation] -| `{translate}`... `{/translate}` | [переводит содержимое |#Translation] +|## Переводы +| `{_...}` | [выводит перевод |#Переводы] +| `{translate}` … `{/translate}` | [переводит содержимое |#Переводы] .[table-latte-tags language-latte] -|## Другие -| `{contentType}` | [переключает режим экранирования и отправляет HTTP-заголовок |#contentType] -| `{debugbreak}` | [устанавливает точку останова в коде |#debugbreak] -| `{do}` | [оценивает выражение, не выводя его на печать |#do] -| `{dump}` | [сбрасывает переменные в Tracy Bar |#dump] -| `{spaceless}`... `{/spaceless}` | [удаляет ненужные пробельные символы |#spaceless] -| `{syntax}` | [переключает синтаксис во время выполнения программы|#Syntax] -| `{trace}` | [показывает трассировку стека |#trace] +|## Прочее +| `{contentType}` | [переключает экранирование и отправляет HTTP-заголовок |#contentType] +| `{debugbreak}` | [помещает точку останова в код |#debugbreak] +| `{do}` | [выполняет код, но ничего не выводит |#do] +| `{dump}` | [выводит переменные в Tracy Bar |#dump] +| `{php}` | [выполняет любой PHP-код |#php] +| `{spaceless}` … `{/spaceless}` | [удаляет лишние пробелы |#spaceless] +| `{syntax}` | [изменение синтаксиса во время выполнения |#syntax] +| `{trace}` | [отображает трассировку стека |#trace] .[table-latte-tags language-latte] -|## Помощники HTML тегов -| `n:class` | [умный атрибут класса |#n-class] -| `n:attr` | [интеллектуальные атрибуты HTML |#n-attr] -| `n:tag` | [динамическое имя элемента HTML |#n-tag] -| `n:ifcontent` | [Опустить пустой HTML-тег |#n-ifcontent] +|## Помощники HTML-кодера +| `n:class` | [динамическая запись HTML-атрибута class |#n:class] +| `n:attr` | [динамическая запись любых HTML-атрибутов |#n:attr] +| `n:tag` | [динамическая запись имени HTML-элемента |#n:tag] +| `n:ifcontent` | [пропускает пустой HTML-тег |#n:ifcontent] .[table-latte-tags language-latte] |## Доступно только в Nette Framework -| `n:href` | [ссылка в HTML-элементах `` |application:creating-links#In-the-Presenter-Template] -| `{link}` | [печатает ссылку |application:creating-links#In-the-Presenter-Template] -| `{plink}` | [печатает ссылку на ведущего |application:creating-links#In-the-Presenter-Template] -| `{control}` | [печатает компонент |application:components#Rendering] -| `{snippet}`... `{/snippet}` | [фрагмент шаблона, который может быть отправлен с помощью AJAX |application:ajax#Tag-snippet] -| `{snippetArea}` | конверт сниппетов -| `{cache}`... `{/cache}` | [кэширует раздел шаблона |caching:#Caching-in-Latte] +| `n:href` | [ссылка, используемая в HTML-элементах `` |application:creating-links#В шаблоне презентера] +| `{link}` | [выводит ссылку |application:creating-links#В шаблоне презентера] +| `{plink}` | [выводит ссылку на презентер |application:creating-links#В шаблоне презентера] +| `{control}` | [рендерит компонент |application:components#Отрисовка] +| `{snippet}` … `{/snippet}` | [сниппет, который можно отправить через AJAX |application:ajax#Сниппеты в Latte] +| `{snippetArea}` | [обертка для сниппетов |application:ajax#Области сниппетов] +| `{cache}` … `{/cache}` | [кеширует часть шаблона |caching:#Кеширование в Latte] .[table-latte-tags language-latte] -|## Доступно только в Nette Forms -| `{form}`... `{/form}` | [печатает элемент формы |forms:rendering#form] -| `{label}`... `{/label}` | [печатает метку ввода формы |forms:rendering#label-input] -| `{input}` | [печатает элемент ввода формы |forms:rendering#label-input] -| `{inputError}` | [печатает сообщение об ошибке для элемента ввода формы |forms:rendering#inputError] -| `n:name` | [активирует элемент ввода HTML |forms:rendering#n:name] -| `{formPrint}` | [генерирует чертеж формы Latte |forms:rendering#formPrint] -| `{formPrintClass}` | [печатает PHP класс для данных формы |forms:in-presenter#Mapping-to-Classes] -| `{formContext}`... `{/formContext}` | [частичный рендеринг формы |forms:rendering#special-cases] - - -Печать .[#toc-printing] -======================= +|## Доступно только с Nette Forms +| `{form}` … `{/form}` | [рендерит теги формы |forms:rendering#form] +| `{label}` … `{/label}` | [рендерит метку элемента формы |forms:rendering#label input] +| `{input}` | [рендерит элемент формы |forms:rendering#label input] +| `{inputError}` | [выводит сообщение об ошибке элемента формы |forms:rendering#inputError] +| `n:name` | [оживляет элемент формы |forms:rendering#n:name] +| `{formContainer}` … `{/formContainer}` | [рендеринг контейнера формы |forms:rendering#Особые случаи] + +.[table-latte-tags language-latte] +|## Доступно только для Nette Assets +| `{asset}` | [рендерит актив как HTML-элемент или URL |assets:#asset] +| `{preload}` | [генерирует подсказки для предварительной загрузки для оптимизации производительности |assets:#preload] +| `n:asset` | [добавляет атрибуты актива к HTML-элементам |assets:#n:asset] + + +Вывод +===== `{$var}` `{...}` `{=...}` ------------------------- -Latte использует тег `{=...}` для печати любого выражения на выходе. Если выражение начинается с переменной или вызова функции, то нет необходимости писать знак равенства. Что на практике означает, что его почти никогда не нужно писать: +В Latte используется тег `{=...}` для вывода любого выражения на выход. Latte заботится о вашем удобстве, поэтому если выражение начинается с переменной или вызова функции, не нужно писать знак равенства. Что на практике означает, что его почти никогда не нужно писать: ```latte -Name: {$name} {$surname}
                                                                                                                -Age: {date('Y') - $birth}
                                                                                                                +Имя: {$name} {$surname}
                                                                                                                +Возраст: {date('Y') - $birth}
                                                                                                                ``` -Вы можете записать в виде выражения все, что знаете из PHP. Вам просто не нужно учить новый язык. Например: +В качестве выражения вы можете записать все, что знаете из PHP. Вам не нужно учить новый язык. Например: ```latte {='0' . ($num ?? $num * 3) . ', ' . PHP_VERSION} ``` -Пожалуйста, не ищите никакого смысла в предыдущем примере, но если вы его там найдете, напишите нам :-) +Пожалуйста, не ищите в предыдущем примере никакого смысла, но если вы его там найдете, напишите нам :-) -Эскейпинг-вывод .[#toc-escaping-output] ---------------------------------------- +Экранирование вывода +-------------------- -Какая самая важная задача системы шаблонов? Избегать дыр в безопасности. И именно это делает Latte, когда вы печатаете что-то на вывод. Он автоматически экранирует все: +Какая самая важная задача системы шаблонов? Предотвратить дыры в безопасности. И именно это делает Latte всегда, когда вы что-то выводите. Он автоматически это экранирует: ```latte -

                                                                                                                {='one < two'}

                                                                                                                {* prints: '

                                                                                                                one < two

                                                                                                                ' *} +

                                                                                                                {='one < two'}

                                                                                                                {* выводит: '

                                                                                                                one < two

                                                                                                                ' *} ``` -Если быть точным, Latte использует контекстно-зависимую экранировку, которая является настолько важной и уникальной особенностью, что мы посвятили ей [отдельную главу|safety-first#context-aware-escaping]. +Чтобы быть точным, Latte использует контекстно-зависимое экранирование, что является настолько важной и уникальной вещью, что мы посвятили этому [отдельную главу |safety-first#Контекстно-зависимое экранирование]. -А если вы печатаете HTML-кодированное содержимое из надежного источника? Тогда вы можете легко отключить экранирование: +А что если вы выводите содержимое, закодированное в HTML, из надежного источника? Тогда можно легко отключить экранирование: ```latte {$trustedHtmlString|noescape} ``` .[warning] -Неправильное использование фильтра `noescape` может привести к XSS-уязвимости! Никогда не используйте его, если вы не **абсолютно уверены** в том, что вы делаете, и что печатаемая вами строка получена из надежного источника. +Неправильное использование фильтра `noescape` может привести к уязвимости XSS! Никогда не используйте его, если вы не **абсолютно уверены** в том, что делаете, и что выводимая строка происходит из надежного источника. -Печать в JavaScript .[#toc-printing-in-javascript] --------------------------------------------------- +Вывод в JavaScript +------------------ -Благодаря контекстно-зависимому экранированию, очень легко печатать переменные в JavaScript, и Latte будет правильно их экранировать. +Благодаря контекстно-зависимому экранированию очень легко выводить переменные внутри JavaScript, а правильное экранирование обеспечит Latte. -Переменная не обязательно должна быть строкой, поддерживается любой тип данных, которые затем кодируются как JSON: +Переменная не обязательно должна быть строкой, поддерживается любой тип данных, который затем кодируется как JSON: ```latte {var $foo = ['hello', true, 1]} @@ -179,7 +185,7 @@ Age: {date('Y') - $birth}
                                                                                                                ``` -Это также причина, по которой **не заключайте переменную в кавычки**: Latte добавляет их вокруг строк. А если вы хотите поместить строковую переменную в другую строку, просто конкатенируйте их: +Это также причина, почему вокруг переменной **не пишутся кавычки**: Latte добавит их для строк самостоятельно. А если вы хотите вставить строковую переменную в другую строку, просто соедините их: ```latte ``` -Фильтры .[#toc-filters] ------------------------ +Фильтры +------- -Печатное выражение может быть изменено с [помощью фильтров |syntax#Filters]. Например, в этом примере строка преобразуется в верхний регистр и сокращается максимум до 30 символов: +Выводимое выражение может быть изменено [фильтром |syntax#Фильтры]. Так, например, строку можно перевести в верхний регистр и укоротить до максимальной длины 30 символов: ```latte {$string|upper|truncate:30} ``` -Вы также можете применять фильтры к частям выражения следующим образом: +Фильтры можно применять и к частям выражения следующим образом: ```latte {$left . ($middle|upper) . $right} ``` -Условия .[#toc-conditions] -========================== +Условия +======= `{if}` `{elseif}` `{else}` -------------------------- -Условия ведут себя так же, как и их аналоги в PHP. Вы можете использовать те же выражения, которые вы знаете из PHP, вам не нужно изучать новый язык. +Условия ведут себя так же, как их аналоги в PHP. Вы можете использовать в них те же выражения, что и в PHP, вам не нужно учить новый язык. ```latte {if $product->inStock > Stock::Minimum} - In stock + В наличии {elseif $product->isOnWay()} - On the way + В пути {else} - Not available + Недоступно {/if} ``` -Как и любой парный тег, пара `{if} ... {/ if}` может быть записана как [n:attribute |syntax#n-attributes], например: +Как и любой парный тег, пару `{if} ... {/if}` можно записывать и в виде [n:атрибута |syntax#n:атрибуты], например: ```latte -

                                                                                                                In stock {$count} items

                                                                                                                +

                                                                                                                В наличии {$count} штук

                                                                                                                ``` -Знаете ли вы, что к n:attributes можно добавить префикс `tag-`? Тогда условие будет затрагивать только HTML-теги, а содержимое между ними всегда будет выводиться: +Знаете ли вы, что к n:атрибутам можно добавить префикс `tag-`? Тогда условие будет относиться только к выводу HTML-тегов, а содержимое между ними будет выводиться всегда: ```latte
                                                                                                                Hello -{* prints 'Hello' when $clickable is falsey *} -{* prints 'Hello' when $clickable is truthy *} +{* выводит 'Hello' когда $clickable ложно *} +{* выводит 'Hello' когда $clickable истинно *} +``` + +Божественно. + + +`n:else` .{data-version:3.0.11} +------------------------------- + +Если вы записываете условие `{if} ... {/if}` в виде [n:атрибута |syntax#n:атрибуты], у вас есть возможность указать и альтернативную ветвь с помощью `n:else`: + +```latte +В наличии {$count} штук + +недоступно ``` -Отлично. +Атрибут `n:else` можно использовать также в паре с [`n:ifset` |#ifset elseifset], [`n:foreach` |#foreach], [`n:try` |#try], [#`n:ifcontent`] и [`n:ifchanged` |#ifchanged]. `{/if $cond}` ------------- -Вас может удивить, что выражение в условии `{if}` также может быть указано в теге end. Это полезно в ситуациях, когда мы еще не знаем значение условия на момент открытия тега. Назовем это отложенным решением. +Возможно, вас удивит, что выражение в условии `{if}` можно указать и в закрывающем теге. Это удобно в ситуациях, когда при открытии условия мы еще не знаем его значения. Назовем это отложенным решением. -Например, мы начинаем выводить таблицу с записями из базы данных, и только после завершения отчета понимаем, что в базе данных не было ни одной записи. Поэтому мы помещаем условие в конечный тег `{/if}`, и если записи нет, то ничего из этого не будет напечатано: +Например, мы начинаем выводить таблицу с записями из базы данных и только после завершения вывода понимаем, что в базе данных не было ни одной записи. Тогда мы ставим условие в закрывающий тег `{/if}`, и если записей не будет, ничего из этого не выведется: ```latte {if} -

                                                                                                                Printing rows from the database

                                                                                                                +

                                                                                                                Вывод строк из базы данных

                                                                                                                {foreach $resultSet as $row} @@ -266,28 +286,28 @@ Age: {date('Y') - $birth}
                                                                                                                Удобно, не правда ли? -Вы также можете использовать `{else}` в отложенном условии, но не `{elseif}`. +В отложенном условии можно использовать и `{else}`, но не `{elseif}`. `{ifset}` `{elseifset}` ----------------------- .[note] -См. также [`{ifset block}` |template-inheritance#Checking-Block-Existence] +См. также [`{ifset block}` |template-inheritance#Проверка существования блоков] -Используйте условие `{ifset $var}`, чтобы определить, существует ли переменная (или несколько переменных) и имеет ли она ненулевое значение. На самом деле это то же самое, что и `if (isset($var))` в PHP. Как и любой парный тег, этот может быть записан в виде [n:attribute |syntax#n-attributes], поэтому покажем его на примере: +С помощью условия `{ifset $var}` мы проверяем, существует ли переменная (или несколько переменных) и имеет ли она значение, отличное от *null*. Фактически, это то же самое, что `if (isset($var))` в PHP. Как и любой парный тег, его можно записывать и в виде [n:атрибута |syntax#n:атрибуты], так что покажем это на примере: ```latte - + ``` -`{ifchanged}` .{data-version:2.9} ---------------------------------- +`{ifchanged}` +------------- -`{ifchanged}` проверяет, изменилось ли значение переменной с момента последней итерации в цикле (foreach, for или while). +`{ifchanged}` проверяет, изменилось ли значение переменной с последней итерации в цикле (foreach, for или while). -Если мы укажем в теге одну или несколько переменных, он проверит, изменилось ли значение любой из них, и напечатает содержимое соответствующим образом. Например, в следующем примере при перечислении имен в качестве заголовка печатается первая буква имени каждый раз, когда она меняется: +Если в теге указать одну или несколько переменных, он будет проверять, изменилась ли какая-либо из них, и в зависимости от этого выведет содержимое. Например, следующий пример выводит первую букву имени как заголовок каждый раз, когда она меняется при выводе имен: ```latte {foreach ($names|sort) as $name} @@ -297,7 +317,7 @@ Age: {date('Y') - $birth}
                                                                                                                {/foreach} ``` -Однако, если аргумент не указан, то будет проверено само содержимое рендеринга в соответствии с его предыдущим состоянием. Это означает, что в предыдущем примере мы можем смело опустить аргумент в теге. И, конечно, мы также можем использовать [n:attribute |syntax#n-attributes]: +Однако, если не указать ни одного аргумента, будет проверяться отрисованное содержимое по сравнению с его предыдущим состоянием. Это означает, что в предыдущем примере мы можем спокойно опустить аргумент в теге. И, конечно же, мы также можем использовать [n:атрибут |syntax#n:атрибуты]: ```latte {foreach ($names|sort) as $name} @@ -307,49 +327,49 @@ Age: {date('Y') - $birth}
                                                                                                                {/foreach} ``` -Вы также можете включить клаузулу `{else}` внутрь `{ifchanged}`. +Внутри `{ifchanged}` можно также указать клаузулу `{else}`. `{switch}` `{case}` `{default}` ------------------------------- -Сравнивает значение с несколькими вариантами. Это похоже на структуру `switch`, известную вам из PHP. Однако Latte улучшает ее: +Сравнивает значение с несколькими вариантами. Это аналог условного оператора `switch`, который вы знаете из PHP. Однако Latte его улучшает: - использует строгое сравнение (`===`) - не требует `break` -Таким образом, это точный эквивалент структуры `match`, с которой поставляется PHP 8.0. +Это точный эквивалент структуры `match`, которая появилась в PHP 8.0. ```latte {switch $transport} {case train} - By train + Поездом {case plane} - By plane + Самолетом {default} - Differently + Иначе {/switch} ``` -.{data-version:2.9} -Пункт `{case}` может содержать несколько значений, разделенных запятыми: + +Клаузула `{case}` может содержать несколько значений, разделенных запятыми: ```latte {switch $status} -{case $status::New}new item -{case $status::Sold, $status::Unknown}not available +{case $status::New}новая позиция +{case $status::Sold, $status::Unknown}недоступно {/switch} ``` -Циклы .[#toc-loops] -=================== +Циклы +===== -В Latte доступны все циклы, знакомые вам по PHP: foreach, for и while. +В Latte вы найдете все циклы, которые знаете из PHP: foreach, for и while. `{foreach}` ----------- -Вы пишете цикл точно так же, как и в PHP: +Цикл записываем точно так же, как в PHP: ```latte {foreach $langs as $code => $lang} @@ -357,11 +377,11 @@ Age: {date('Y') - $birth}
                                                                                                                {/foreach} ``` -Кроме того, у него есть несколько удобных твиков, о которых мы сейчас поговорим. +Кроме того, у него есть несколько удобных фишек, о которых мы сейчас расскажем. -Например, Latte проверяет, чтобы созданные переменные случайно не перезаписали одноименные глобальные переменные. Это спасет вас, когда вы предполагаете, что `$lang` - текущий язык страницы, и не понимаете, что `foreach $langs as $lang` перезаписал эту переменную. +Latte, например, проверяет, не перезаписывают ли созданные переменные случайно глобальные переменные с тем же именем. Это спасает в ситуациях, когда вы рассчитываете, что в `$lang` находится текущий язык страницы, и не осознаете, что `foreach $langs as $lang` вам эту переменную перезаписало. -Цикл foreach также может быть написан очень элегантно и экономично с помощью [n:attribute |syntax#n-attributes]: +Цикл foreach также можно очень элегантно и компактно записать с помощью [n:атрибута |syntax#n:атрибуты]: ```latte
                                                                                                                  @@ -369,7 +389,7 @@ Age: {date('Y') - $birth}
                                                                                                                ``` -Знаете ли вы, что к n:attributes можно добавлять префикс `inner-`? Тогда в цикле будет повторяться только внутренняя часть элемента: +Знаете ли вы, что к n:атрибутам можно добавить префикс `inner-`? Тогда в цикле будет повторяться только внутренняя часть элемента: ```latte
                                                                                                                @@ -378,7 +398,7 @@ Age: {date('Y') - $birth}
                                                                                                                ``` -Таким образом, будет выведено что-то вроде: +Так что выведется что-то вроде: ```latte
                                                                                                                @@ -390,17 +410,17 @@ Age: {date('Y') - $birth}
                                                                                                                ``` -`{else}` .{data-version:2.9}{toc: foreach-else} ------------------------------------------------ +`{else}` .{toc: foreach-else} +----------------------------- -Цикл `foreach` может принимать необязательное предложение `{else}`, текст которого выводится, если заданный массив пуст: +Внутри цикла `foreach` можно указать клаузулу `{else}`, содержимое которой отобразится, если цикл пуст: ```latte
                                                                                                                  {foreach $people as $person}
                                                                                                                • {$person->name}
                                                                                                                • {else} -
                                                                                                                • Sorry, no users in this list
                                                                                                                • +
                                                                                                                • К сожалению, в этом списке нет пользователей
                                                                                                                • {/foreach}
                                                                                                                ``` @@ -409,15 +429,15 @@ Age: {date('Y') - $birth}
                                                                                                                `$iterator` ----------- -Внутри цикла `foreach` инициализируется переменная `$iterator`. В ней хранится важная информация о текущем цикле. +Внутри цикла `foreach` Latte создает переменную `$iterator`, с помощью которой мы можем узнавать полезную информацию о текущем цикле: -- `$iterator->first` - это первая итерация? -- `$iterator->last` - это последняя итерация? -- `$iterator->counter` - счетчик итераций, начинается с 1 -- `$iterator->counter0` - счетчик итераций, начинается с 0 .{data-version:2.9} -- `$iterator->odd` - эта итерация нечетная? -- `$iterator->even` - эта итерация четная? -- `$iterator->parent` - итератор, окружающий текущий итератор. .{data-version:2.9} +- `$iterator->first` - это первый проход цикла? +- `$iterator->last` - это последний проход? +- `$iterator->counter` - какой это проход, считая с единицы? +- `$iterator->counter0` - какой это проход, считая с нуля? +- `$iterator->odd` - это нечетный проход? +- `$iterator->even` - это четный проход? +- `$iterator->parent` - итератор, окружающий текущий - `$iterator->nextValue` - следующий элемент в цикле - `$iterator->nextKey` - ключ следующего элемента в цикле @@ -435,16 +455,15 @@ Age: {date('Y') - $birth}
                                                                                                                {/foreach} ``` -Лата умница и `$iterator->last` работает не только для массивов, но и когда цикл работает над общим итератором, где количество элементов заранее не известно. +Latte хитер, и `$iterator->last` работает не только с массивами, но и когда цикл проходит по общему итератору, где количество элементов заранее неизвестно. `{first}` `{last}` `{sep}` -------------------------- -Эти теги можно использовать внутри цикла `{foreach}`. Содержимое `{first}` отображается при первом проходе. -Содержимое `{last}` отображается ... можете догадаться? Да, для последнего прохода. На самом деле это ярлыки для `{if $iterator->first}` и `{if $iterator->last}`. +Эти теги можно использовать внутри цикла `{foreach}`. Содержимое `{first}` отрисовывается, если это первый проход. Содержимое `{last}` отрисовывается… угадаете ли? Да, если это последний проход. Это фактически сокращения для `{if $iterator->first}` и `{if $iterator->last}`. -Теги также могут быть записаны как [n:attributes |syntax#n-attributes]: +Теги также можно элегантно использовать как [n:атрибут |syntax#n:атрибуты]: ```latte {foreach $rows as $row} @@ -456,21 +475,21 @@ Age: {date('Y') - $birth}
                                                                                                                {/foreach} ``` -Содержимое `{sep}` выводится, если итерация не последняя, поэтому он подходит для печати разделителей, например, запятых между элементами списка: +Содержимое тега `{sep}` отрисовывается, если проход не последний, поэтому он подходит для отрисовки разделителей, например, запятых между выводимыми элементами: ```latte {foreach $items as $item} {$item} {sep}, {/sep} {/foreach} ``` -Это довольно практично, не так ли? +Довольно практично, не так ли? -`{iterateWhile}` .{data-version:2.10} -------------------------------------- +`{iterateWhile}` +---------------- -Упрощает группировку линейных данных во время итерации в цикле foreach, выполняя итерацию во вложенном цикле до тех пор, пока выполняется условие. [Читайте инструкции в книге рецептов |cookbook/iteratewhile]. +Упрощает группировку линейных данных во время итерации в цикле foreach, выполняя итерацию во вложенном цикле, пока выполняется условие. [Прочитайте подробное руководство |cookbook/grouping]. -Он также может элегантно заменить `{first}` и `{last}` в примере выше: +Может также элегантно заменить `{first}` и `{last}` в примере выше: ```latte {foreach $rows as $row} @@ -487,19 +506,21 @@ Age: {date('Y') - $birth}
                                                                                                                {/foreach} ``` +См. также фильтры [batch |filters#batch] и [group |filters#group]. + `{for}` ------- -Мы пишем цикл точно так же, как и в PHP: +Цикл записываем точно так же, как в PHP: ```latte {for $i = 0; $i < 10; $i++} - Item #{$i} + Элемент {$i} {/for} ``` -Тег также может быть записан как [n:attribute |syntax#n-attributes]: +Тег также можно использовать как [n:атрибут |syntax#n:атрибуты]: ```latte

                                                                                                                {$i}

                                                                                                                @@ -509,7 +530,7 @@ Age: {date('Y') - $birth}
                                                                                                                `{while}` --------- -Опять же, мы пишем цикл точно так же, как и в PHP: +Цикл опять же записываем точно так же, как в PHP: ```latte {while $row = $result->fetch()} @@ -517,7 +538,7 @@ Age: {date('Y') - $birth}
                                                                                                                {/while} ``` -Или как [n:attribute |syntax#n-attributes]: +Или как [n:атрибут |syntax#n:атрибуты]: ```latte @@ -525,7 +546,7 @@ Age: {date('Y') - $birth}
                                                                                                                ``` -Вариант с условием в конце тега соответствует циклу do-while в PHP: +Возможен также вариант с условием в закрывающем теге, который соответствует в PHP циклу do-while: ```latte {while} @@ -537,7 +558,7 @@ Age: {date('Y') - $birth}
                                                                                                                `{continueIf}` `{skipIf}` `{breakIf}` ------------------------------------- -Существуют специальные теги, которые можно использовать для управления любым циклом - `{continueIf ?}` и `{breakIf ?}`, которые переходят к следующей итерации и завершают цикл, соответственно, при выполнении условий: +Для управления любым циклом можно использовать теги `{continueIf ?}` и `{breakIf ?}`, которые переходят к следующему элементу соответственно завершают цикл при выполнении условия: ```latte {foreach $rows as $row} @@ -547,8 +568,8 @@ Age: {date('Y') - $birth}
                                                                                                                {/foreach} ``` -.{data-version:2.9} -Тег `{skipIf}` очень похож на `{continueIf}`, но не увеличивает счетчик. Таким образом, при печати `$iterator->counter` и пропуске некоторых элементов в нумерации не будет дыр. Также предложение {else} будет выведено при пропуске всех элементов. + +Тег `{skipIf}` очень похож на `{continueIf}`, но не увеличивает счетчик `$iterator->counter`, так что если мы его выводим и одновременно пропускаем некоторые элементы, в нумерации не будет дыр. А также клаузула `{else}` отрисовывается, когда мы пропускаем все элементы. ```latte
                                                                                                                  @@ -556,7 +577,7 @@ Age: {date('Y') - $birth}
                                                                                                                  {skipIf $person->age < 18}
                                                                                                                • {$iterator->counter}. {$person->name}
                                                                                                                • {else} -
                                                                                                                • Sorry, no adult users in this list
                                                                                                                • +
                                                                                                                • К сожалению, в этом списке нет взрослых
                                                                                                                • {/foreach}
                                                                                                                ``` @@ -565,7 +586,7 @@ Age: {date('Y') - $birth}
                                                                                                                `{exitIf}` .{data-version:3.0.5} -------------------------------- -Завершает отрисовку шаблона или блока при выполнении условия. +Завершает рендеринг шаблона или блока при выполнении условия (так называемый "ранний выход"). ```latte {exitIf !$messages} @@ -577,60 +598,56 @@ Age: {date('Y') - $birth}
                                                                                                                ``` -Включение шаблонов .[#toc-including-templates] -============================================== +Включение шаблона +================= `{include 'file.latte'}` .{toc: include} ---------------------------------------- .[note] -См. также [`{include block}` |template-inheritance#Printing-Blocks] +См. также [`{include block}` |template-inheritance#Отрисовка блоков] -Тег `{include}` загружает и отображает указанный шаблон. На нашем любимом языке PHP это выглядит так: +Тег `{include}` загружает и отрисовывает указанный шаблон. Если говорить на языке нашего любимого PHP, это что-то вроде: ```php ``` -Включенные шаблоны не имеют доступа к переменным активного контекста, но имеют доступ к глобальным переменным. +Включенные шаблоны не имеют доступа к переменным активного контекста, они имеют доступ только к глобальным переменным. -Вы можете передавать переменные таким образом: +Переменные во включенный шаблон можно передавать следующим образом: ```latte -{* since Latte 2.9 *} {include 'template.latte', foo: 'bar', id: 123} - -{* before Latte 2.9 *} -{include 'template.latte', foo => 'bar', id => 123} ``` -Имя шаблона может быть любым выражением PHP: +Имя шаблона может быть любым выражением в PHP: ```latte {include $someVar} {include $ajax ? 'ajax.latte' : 'not-ajax.latte'} ``` -Вставленное содержимое может быть изменено с помощью [фильтров |syntax#Filters]. В следующем примере удаляется весь HTML и корректируется регистр: +Включенное содержимое можно изменять с помощью [фильтров |syntax#Фильтры]. Следующий пример удаляет весь HTML и изменяет регистр букв: ```latte {include 'heading.latte' |stripHtml|capitalize} ``` -[Наследование шаблона |template inheritance] **по умолчанию не участвует** в этом. Хотя вы можете добавлять теги блоков в включаемые шаблоны, они не будут заменять соответствующие блоки в шаблоне, в который они включены. Рассматривайте включаемые блоки как независимые и экранированные части страниц или модулей. Это поведение можно изменить с помощью модификатора `with blocks` (начиная с Latte 2.9.1): +По умолчанию [наследование шаблонов |template-inheritance] в этом случае никак не фигурирует. Хотя во включенном шаблоне можно использовать блоки, не происходит замены соответствующих блоков в шаблоне, в который он включается. Думайте о включенных шаблонах как об отдельных изолированных частях страниц или модулей. Это поведение можно изменить с помощью модификатора `with blocks`: ```latte {include 'template.latte' with blocks} ``` -Связь между именем файла, указанным в теге, и файлом на диске зависит от [загрузчика |extending-latte#Loaders]. +Связь между именем файла, указанным в теге, и файлом на диске — это дело [загрузчика |loaders]. -`{sandbox}` .{data-version:2.8} -------------------------------- +`{sandbox}` +----------- -При включении шаблона, созданного конечным пользователем, следует рассмотреть возможность его "песочницы" (более подробная информация в [документации по "песочнице" |sandbox]): +При включении шаблона, созданного конечным пользователем, следует рассмотреть режим песочницы (больше информации в [документации песочницы |sandbox]): ```latte {sandbox 'untrusted.latte', level: 3, data: $menu} @@ -641,9 +658,9 @@ Age: {date('Y') - $birth}
                                                                                                                ========= .[note] -См. также [`{block name}` |template-inheritance#Blocks] +См. также [`{block name}` |template-inheritance#Блоки] -Блоки без названия служат для возможности применения [фильтров |syntax#Filters] к части шаблона. Например, можно применить фильтр [полосы |filters#strip], чтобы удалить ненужные пробелы: +Блоки без имени служат способом применения [фильтров |syntax#Фильтры] к части шаблона. Например, так можно применить фильтр [strip |filters#spaceless], который удалит лишние пробелы: ```latte {block|strip} @@ -654,16 +671,16 @@ Age: {date('Y') - $birth}
                                                                                                                ``` -Обработка исключений .[#toc-exception-handling] -=============================================== +Управление исключениями +======================= -`{try}` .{data-version:2.9} ---------------------------- +`{try}` +------- -С помощью этих тегов очень легко создавать надежные шаблоны. +Благодаря этому тегу чрезвычайно легко создавать надежные шаблоны. -Если при рендеринге блока `{try}` возникает исключение, весь блок отбрасывается, и рендеринг будет продолжен после него: +Если при рендеринге блока `{try}` произойдет исключение, весь блок будет отброшен, и рендеринг продолжится после него: ```latte {try} @@ -675,7 +692,7 @@ Age: {date('Y') - $birth}
                                                                                                                {/try} ``` -Содержимое необязательного пункта `{else}` выводится только при возникновении исключения: +Содержимое в необязательной клаузуле `{else}` отрисовывается только тогда, когда происходит исключение: ```latte {try} @@ -685,11 +702,11 @@ Age: {date('Y') - $birth}
                                                                                                                {/foreach} {else} -

                                                                                                                Sorry, the tweets could not be loaded.

                                                                                                                +

                                                                                                                К сожалению, не удалось загрузить твиты.

                                                                                                                {/try} ``` -Тег также может быть записан как [n:attribute |syntax#n-attributes]: +Тег также можно использовать как [n:атрибут |syntax#n:атрибуты]: ```latte
                                                                                                                  @@ -697,13 +714,13 @@ Age: {date('Y') - $birth}
                                                                                                                ``` -Также можно определить [собственный обработчик исключений |develop#Exception-Handler] для ведения журнала: +Также возможно определить собственный [обработчик исключений |develop#Обработчик исключений], например, для логирования. -`{rollback}` .{data-version:2.9} --------------------------------- +`{rollback}` +------------ -Блок `{try}` также можно остановить и пропустить вручную с помощью `{rollback}`. Таким образом, вам не нужно проверять все входные данные заранее, и только во время рендеринга вы можете решить, имеет ли смысл рендерить объект. +Блок `{try}` можно остановить и пропустить также вручную с помощью `{rollback}`. Благодаря этому не нужно заранее проверять все входные данные, и только во время рендеринга можно решить, что объект вообще не нужно отрисовывать: ```latte {try} @@ -719,30 +736,30 @@ Age: {date('Y') - $birth}
                                                                                                                ``` -Переменные .[#toc-variables] -============================ +Переменные +========== `{var}` `{default}` ------------------- -Мы создадим новые переменные в шаблоне с помощью тега `{var}`: +Новые переменные мы создаем в шаблоне тегом `{var}`: ```latte {var $name = 'John Smith'} {var $age = 27} -{* Multiple declaration *} +{* Множественное объявление *} {var $name = 'John Smith', $age = 27} ``` -Тег `{default}` работает аналогично, за исключением того, что он создает переменные, только если они не существуют: +Тег `{default}` работает аналогично, но создает переменные только тогда, когда они не существуют. Если переменная уже существует и содержит значение `null`, она не будет перезаписана: ```latte {default $lang = 'cs'} ``` -Начиная с Latte 2.7, вы также можете указывать [типы переменных |type-system]. Пока что они носят информативный характер, и Latte не проверяет их. +Вы можете указывать и [типы переменных |type-system]. Пока они информативны, и Latte их не проверяет. ```latte {var string $name = $article->getTitle()} @@ -750,10 +767,10 @@ Age: {date('Y') - $birth}
                                                                                                                ``` -`{parameters}` .{data-version:2.9} ----------------------------------- +`{parameters}` +-------------- -Подобно тому, как функция объявляет свои параметры, шаблон может объявить свои переменные в самом начале: +Так же, как функция объявляет свои параметры, может и шаблон в начале объявлять свои переменные: ```latte {parameters @@ -763,15 +780,15 @@ Age: {date('Y') - $birth}
                                                                                                                } ``` -Переменные `$a` и `$b` без значения по умолчанию автоматически имеют значение по умолчанию `null`. Объявленные типы остаются информативными, и Latte не проверяет их. +Переменные `$a` и `$b` без указанного значения по умолчанию автоматически имеют значение по умолчанию `null`. Объявленные типы пока информативны, и Latte их не проверяет. -В остальном объявленные переменные не передаются в шаблон. Это отличие от тега `{default}`. +Другие переменные, кроме объявленных, в шаблон не передаются. Этим он отличается от тега `{default}`. `{capture}` ----------- -Используя тег `{capture}`, вы можете захватить вывод в переменную: +Захватывает вывод в переменную: ```latte {capture $var} @@ -783,7 +800,7 @@ Age: {date('Y') - $birth}

                                                                                                                Captured: {$var}

                                                                                                                ``` -Тег также может быть записан как [n:attribute |syntax#n-attributes]: +Тег можно, как и любой парный тег, записать также как [n:атрибут |syntax#n:атрибуты]: ```latte
                                                                                                                  @@ -791,15 +808,17 @@ Age: {date('Y') - $birth}
                                                                                                                ``` +HTML-вывод сохраняется в переменную `$var` в виде объекта `Latte\Runtime\Html`, чтобы [нежелательное экранирование не произошло |develop#Отключение автоэкранирования переменной] при выводе. -Другие .[#toc-others] -===================== + +Прочее +====== `{contentType}` --------------- -Используйте тег, чтобы указать, какой тип содержимого представляет шаблон. Возможны следующие варианты: +Тегом вы указываете, какой тип содержимого представляет шаблон. Возможные варианты: - `html` (тип по умолчанию) - `xml` @@ -808,9 +827,9 @@ Age: {date('Y') - $birth}
                                                                                                                - `calendar` (iCal) - `text` -Его использование важно, поскольку он устанавливает [контекстно-зависимую экранировку |safety-first#Context-Aware-Escaping], и только после этого Latte может правильно экранировать. Например, `{contentType xml}` переключается в режим XML, `{contentType text}` полностью отключает экранирование. +Его использование важно, потому что он устанавливает [контекстно-зависимое экранирование |safety-first#Контекстно-зависимое экранирование] и только так может экранировать правильно. Например, `{contentType xml}` переключает в режим XML, `{contentType text}` полностью отключает экранирование. -Если параметр является полнофункциональным MIME-типом, например, `application/xml`, он также посылает браузеру HTTP-заголовок `Content-Type`: +Если параметром является полноценный MIME-тип, например `application/xml`, то он еще дополнительно отправляет HTTP-заголовок `Content-Type` в браузер: ```latte {contentType application/xml} @@ -829,46 +848,50 @@ Age: {date('Y') - $birth}
                                                                                                                `{debugbreak}` -------------- -Указывает место, где выполнение кода прервется. Используется в целях отладки, чтобы программист мог проверить среду выполнения и убедиться, что код выполняется так, как ожидалось. Поддерживается [Xdebug |https://xdebug.org]. Кроме того, можно указать условие, при котором код должен прерваться. +Обозначает место, где выполнение программы будет приостановлено и запущен отладчик, чтобы программист мог провести инспекцию среды выполнения и выяснить, работает ли программа в соответствии с ожиданиями. Поддерживает [Xdebug |https://xdebug.org/]. Можно добавить условие, которое определяет, когда программа должна быть приостановлена. ```latte -{debugbreak} {* breaks the program *} +{debugbreak} {* приостанавливает программу *} -{debugbreak $counter == 1} {* breaks the program if the condition is met *} +{debugbreak $counter == 1} {* приостанавливает программу при выполнении условия *} ``` `{do}` ------ -Выполняет код и ничего не печатает. +Выполняет PHP-код и ничего не выводит. Как и у всех других тегов, под PHP-кодом понимается одно выражение, см. [ограничения PHP |syntax#Ограничения PHP в Latte]. ```latte {do $num++} ``` -В Latte 2.7 и более ранних версиях использовался `{php}`. - `{dump}` -------- -Выгружает переменную или текущий контекст. +Выводит переменную или текущий контекст. ```latte -{dump $name} {* dumps the $name variable *} +{dump $name} {* Выводит переменную $name *} -{dump} {* dumps all the defined variables *} +{dump} {* Выводит все текущие определенные переменные *} ``` .[caution] -Требуется пакет [Tracy |tracy:]. +Требуется библиотека [Tracy |tracy:]. + + +`{php}` +------- + +Позволяет выполнить любой PHP-код. Тег необходимо активировать с помощью расширения [RawPhpExtension |develop#RawPhpExtension]. `{spaceless}` ------------- -Удаляет ненужные пробельные символы. Аналогичен фильтру [без пробелов |filters#spaceless]. +Удаляет лишние пробелы из вывода. Работает аналогично фильтру [spaceless |filters#spaceless]. ```latte {spaceless} @@ -878,52 +901,52 @@ Age: {date('Y') - $birth}
                                                                                                                {/spaceless} ``` -Выходные данные: +Генерирует ```latte
                                                                                                                • Hello
                                                                                                                ``` -Тег также может быть записан как [n:attribute |syntax#n-attributes]: +Тег также можно записать как [n:атрибут |syntax#n:атрибуты]. `{syntax}` ---------- -Теги Latte не обязательно должны быть заключены только в одинарные фигурные скобки. Вы можете выбрать другой разделитель, даже во время выполнения. Это делается с помощью `{syntax…}`, где параметр может быть: +Теги Latte не обязательно должны быть ограничены только одинарными фигурными скобками. Мы можем выбрать и другой разделитель, и даже во время выполнения. Для этого служит `{syntax …}`, где в качестве параметра можно указать: - double: `{{...}}` -- off: полностью отключает теги Latte +- off: полностью отключает обработку тегов Latte -Используя нотацию n:attribute, мы можем отключить Latte только для блока JavaScript: +С использованием n:атрибутов можно отключить Latte, например, только для одного блока JavaScript: ```latte ``` -Latte можно очень удобно использовать внутри JavaScript, только избегайте конструкций, как в этом примере, где буква сразу следует за `{`, см. [Latte внутри JavaScript или CSS |recipes#Latte-Inside-JavaScript-or-CSS]. +Latte можно очень удобно использовать и внутри JavaScript, достаточно избегать конструкций, как в этом примере, когда буква следует сразу за `{`, см. [Latte внутри JavaScript или CSS |recipes#Latte внутри JavaScript или CSS]. -Если вы отключите Latte с помощью `{syntax off}` (т.е. тега, а не атрибута n:attribute), то он будет строго игнорировать все теги до `{/syntax}`. +Если Latte отключить с помощью `{syntax off}` (т.е. тегом, а не n:атрибутом), он будет последовательно игнорировать все теги до `{/syntax}` -{trace} .{data-version:2.10} ----------------------------- +{trace} +------- -Выбрасывает исключение `Latte\RuntimeException`, стековая трассировка которого выполнена в духе шаблонов. Таким образом, вместо вызова функций и методов, оно включает вызов блоков и вставку шаблонов. Если вы используете инструмент для наглядного отображения брошенных исключений, такой как [Tracy |tracy:], вы будете четко видеть стек вызова, включая все переданные аргументы. +Выбрасывает исключение `Latte\RuntimeException`, трассировка стека которого выполнена в духе шаблонов. То есть вместо вызовов функций и методов содержит вызовы блоков и включения шаблонов. Если вы используете инструмент для наглядного отображения выброшенных исключений, такой как [Tracy |tracy:], вам наглядно отобразится стек вызовов, включая все переданные аргументы. -Помощники тегов HTML .[#toc-html-tag-helpers] -============================================= +Помощники HTML-кодера +===================== -n:класс .[#toc-n-class] ------------------------ +n:class +------- -Благодаря `n:class` очень легко сгенерировать HTML-атрибут `class` именно так, как вам нужно. +Благодаря `n:class` очень легко сгенерировать HTML-атрибут `class` точно по представлению. -Пример: Мне нужно, чтобы активный элемент имел класс `active`: +Пример: мне нужно, чтобы активный элемент имел класс `active`: ```latte {foreach $items as $item} @@ -931,7 +954,7 @@ n:класс .[#toc-n-class] {/foreach} ``` -И еще мне нужно, чтобы первый элемент имел классы `first` и `main`: +И далее, чтобы первый элемент имел классы `first` и `main`: ```latte {foreach $items as $item} @@ -939,7 +962,7 @@ n:класс .[#toc-n-class] {/foreach} ``` -А все элементы должны иметь класс `list-item`: +И все элементы должны иметь класс `list-item`: ```latte {foreach $items as $item} @@ -947,13 +970,13 @@ n:класс .[#toc-n-class] {/foreach} ``` -Удивительно просто, не правда ли? +Удивительно просто, не так ли? -n:attr .[#toc-n-attr] ---------------------- +n:attr +------ -Атрибут `n:attr` может генерировать произвольные HTML-атрибуты с той же элегантностью, что и [n:class |#n-class]. +Атрибут `n:attr` умеет с той же элегантностью, что и [#n:class], генерировать любые HTML-атрибуты. ```latte {foreach $data as $item} @@ -961,7 +984,7 @@ n:attr .[#toc-n-attr] {/foreach} ``` -В зависимости от возвращаемых значений, он отображает, например: +В зависимости от возвращенных значений выведет, например: ```latte @@ -972,26 +995,28 @@ n:attr .[#toc-n-attr] ``` -n:tag .{data-version:2.10} --------------------------- +n:tag +----- -Атрибут `n:tag` может динамически изменять имя элемента HTML. +Атрибут `n:tag` умеет динамически изменять имя HTML-элемента. ```latte

                                                                                                                {$title}

                                                                                                                ``` -Если `$heading === null`, то `

                                                                                                                ` тег выводится без изменений. В противном случае имя элемента изменяется на значение переменной, так что для `$heading === 'h3'` записывается: +Если `$heading === null`, выведется без изменений тег `

                                                                                                                `. Иначе имя элемента изменится на значение переменной, так что для `$heading === 'h3'` выведется: ```latte

                                                                                                                ...

                                                                                                                ``` +Поскольку Latte является безопасной системой шаблонов, он проверяет, является ли новое имя тега допустимым и не содержит ли оно нежелательных или вредоносных значений. -n:ifcontent .[#toc-n-ifcontent] -------------------------------- -Предотвращает печать пустого HTML-элемента, т.е. элемента, не содержащего ничего, кроме пробелов. +n:ifcontent +----------- + +Предотвращает вывод пустого HTML-элемента, т.е. элемента, не содержащего ничего, кроме пробелов. ```latte
                                                                                                                @@ -999,7 +1024,7 @@ n:ifcontent .[#toc-n-ifcontent]
                                                                                                                ``` -В зависимости от значений переменной `$error` будет выводиться: +Выводит в зависимости от значения переменной `$error`: ```latte {* $error = '' *} @@ -1013,10 +1038,10 @@ n:ifcontent .[#toc-n-ifcontent] ``` -Перевод .{data-version:3.0}[#toc-translation] -============================================= +Переводы +======== -Чтобы теги перевода работали, необходимо [настроить переводчик |develop#TranslatorExtension]. Вы также можете использовать [`translate` |filters#translate] фильтр для перевода. +Чтобы теги для перевода работали, необходимо [активировать переводчик |develop#TranslatorExtension]. Для перевода вы также можете использовать фильтр [`translate` |filters#translate]. `{_...}` @@ -1025,30 +1050,30 @@ n:ifcontent .[#toc-n-ifcontent] Переводит значения на другие языки. ```latte -{_'Basket'} +{_'Корзина'} {_$item} ``` -Переводчику могут быть переданы и другие параметры: +Переводчику можно передавать и другие параметры: ```latte -{_'Basket', domain: order} +{_'Корзина', domain: order} ``` `{translate}` ------------- -Překládá části šablony: +Переводит части шаблона: ```latte -

                                                                                                                {translate}Order{/translate}

                                                                                                                +

                                                                                                                {translate}Заказ{/translate}

                                                                                                                {translate domain: order}Lorem ipsum ...{/translate} ``` -Тег также может быть записан как [n:attribute |syntax#n-attributes], чтобы перевести внутреннюю часть элемента: +Тег также можно записать как [n:атрибут |syntax#n:атрибуты], для перевода внутренней части элемента: ```latte -

                                                                                                                Order

                                                                                                                +

                                                                                                                Заказ

                                                                                                                ``` diff --git a/latte/ru/template-inheritance.texy b/latte/ru/template-inheritance.texy index 9ebdb2631f..c5dbfca707 100644 --- a/latte/ru/template-inheritance.texy +++ b/latte/ru/template-inheritance.texy @@ -1,16 +1,16 @@ -Наследование шаблонов и возможность повторного использования -************************************************************ +Наследование и повторное использование шаблонов +*********************************************** .[perex] -Механизмы повторного использования и наследования шаблонов повышают вашу производительность, поскольку каждый шаблон содержит только уникальное содержимое, а повторяющиеся элементы и структуры используются повторно. Мы представляем три концепции: [наследование макета |#Layout-Inheritance], [горизонтальное повторное использование |#Horizontal-Reuse] и [наследование единиц |#Unit-Inheritance]. +Механизмы повторного использования и наследования шаблонов повысят вашу производительность, поскольку каждый шаблон содержит только свое уникальное содержимое, а повторяющиеся элементы и структуры используются повторно. Мы представляем три концепции: [#наследование макета], [#горизонтальное повторное использование] и [#модульное наследование]. -Концепция наследования шаблонов Latte похожа на наследование классов в PHP. Вы определяете **родительский шаблон**, от которого могут отталкиваться другие **потомственные шаблоны** и переопределять части родительского шаблона. Это отлично работает, когда элементы имеют общую структуру. Звучит сложно? Не волнуйтесь, это не так. +Концепция наследования шаблонов Latte похожа на наследование классов в PHP. Вы определяете **родительский шаблон**, от которого могут наследоваться другие **дочерние шаблоны** и переопределять части родительского шаблона. Это отлично работает, когда элементы имеют общую структуру. Звучит сложно? Не волнуйтесь, это очень просто. -Наследование макета `{layout}` .{toc: Layout Inheritance} +Наследование макета `{layout}` .{toc:Наследование макета} ========================================================= -Давайте рассмотрим наследование шаблонов макета на примере. Это родительский шаблон, который мы назовем для примера `layout.latte`, и он определяет HTML-скелет документа. +Рассмотрим наследование шаблона макета, то есть layout, на примере. Это родительский шаблон, который мы назовем, например, `layout.latte` и который определяет каркас HTML-документа: ```latte @@ -30,9 +30,9 @@ ``` -Теги `{block}` определяют три блока, которые дочерние шаблоны могут заполнять. Все, что делает тег block, это сообщает шаблонизатору, что дочерний шаблон может переопределить эти части шаблона, определив свой собственный блок с тем же именем. +Теги `{block}` определяют три блока, которые могут заполнять дочерние шаблоны. Тег block просто сообщает, что это место может быть переопределено дочерним шаблоном путем определения собственного блока с тем же именем. -Дочерний шаблон может выглядеть следующим образом: +Дочерний шаблон может выглядеть так: ```latte {layout 'layout.latte'} @@ -44,11 +44,11 @@ {/block} ``` -Ключевым здесь является тег `{layout}`. Он сообщает шаблонизатору, что этот шаблон "расширяет" другой шаблон. Когда Latte рендерит этот шаблон, сначала он находит родителя - в данном случае `layout.latte`. +Ключевым здесь является тег `{layout}`. Он сообщает Latte, что этот шаблон «расширяет» другой шаблон. Когда Latte отрисовывает этот шаблон, он сначала находит родительский шаблон — в данном случае `layout.latte`. -В этот момент шаблонизатор заметит три блочных тега в `layout.latte` и заменит эти блоки содержимым дочернего шаблона. Обратите внимание, что поскольку дочерний шаблон не определил блок *footer*, вместо него используется содержимое родительского шаблона. Содержимое внутри тега `{block}` в родительском шаблоне всегда используется в качестве запасного варианта. +В этот момент Latte замечает три тега block в `layout.latte` и заменяет эти блоки содержимым дочернего шаблона. Поскольку дочерний шаблон не определил блок *footer*, используется содержимое из родительского шаблона. Содержимое в теге `{block}` в родительском шаблоне всегда используется как резервное. -Вывод может выглядеть следующим образом: +Вывод может выглядеть так: ```latte @@ -68,7 +68,7 @@ ``` -В дочернем шаблоне блоки могут располагаться только либо на верхнем уровне, либо внутри другого блока, т.е: +В дочернем шаблоне блоки могут быть размещены только на верхнем уровне или внутри другого блока, т.е.: ```latte {block content} @@ -76,7 +76,7 @@ {/block} ``` -Кроме того, блок всегда будет создаваться в независимо от того, будет ли окружающее `{if}` условие оценено как true или false. Вопреки тому, что вы можете подумать, этот шаблон действительно определяет блок. +Также блок всегда будет создан независимо от того, является ли окружающее условие `{if}` истинным или ложным. Так что, хотя это может показаться не так, этот шаблон определит блок. ```latte {if false} @@ -86,7 +86,7 @@ {/if} ``` -Если вы хотите, чтобы вывод внутри блока отображался условно, используйте следующее: +Если вы хотите, чтобы вывод внутри блока отображался условно, используйте вместо этого следующее: ```latte {block head} @@ -96,7 +96,7 @@ {/block} ``` -Данные вне блоков в дочернем шаблоне выполняются до отрисовки шаблона макета, поэтому вы можете использовать его для определения переменных типа `{var $foo = bar}` и распространения данных на всю цепочку наследования: +Пространство вне блоков в дочернем шаблоне выполняется перед отрисовкой шаблона макета, поэтому вы можете использовать его для определения переменных, таких как `{var $foo = bar}`, и для распространения данных по всей цепочке наследования: ```latte {layout 'layout.latte'} @@ -106,51 +106,50 @@ ``` -Многоуровневое наследование .[#toc-multilevel-inheritance] ----------------------------------------------------------- -Вы можете использовать столько уровней наследования, сколько необходимо. Одним из распространенных способов использования наследования макетов является следующий трехуровневый подход: +Многоуровневое наследование +--------------------------- +Вы можете использовать столько уровней наследования, сколько вам нужно. Обычный способ использования наследования макета — это следующий трехуровневый подход: -1) Создайте шаблон `layout.latte`, в котором будет храниться основной внешний вид вашего сайта. -2) Создайте шаблон `layout-SECTIONNAME.latte` для каждого раздела вашего сайта. Например, `layout-news.latte`, `layout-blog.latte` и т.д. Все эти шаблоны расширяют `layout.latte` и включают стили/дизайн для каждого раздела. -3) Создайте отдельные шаблоны для каждого типа страницы, например, для новостной статьи или записи в блоге. Эти шаблоны расширяют соответствующий шаблон раздела. +1) Создайте шаблон `layout.latte`, который содержит основной каркас внешнего вида сайта. +2) Создайте шаблон `layout-SECTIONNAME.latte` для каждого раздела вашего сайта. Например, `layout-news.latte`, `layout-blog.latte` и т.д. Все эти шаблоны расширяют `layout.latte` и включают стили и дизайн, специфичные для отдельных разделов. +3) Создайте индивидуальные шаблоны для каждого типа страницы, например, новостной статьи или записи в блоге. Эти шаблоны расширяют соответствующий шаблон раздела. -Динамическое наследование макета .[#toc-dynamic-layout-inheritance] -------------------------------------------------------------------- -Вы можете использовать переменную или любое выражение PHP в качестве имени родительского шаблона, таким образом, наследование может вести себя динамически: +Динамическое наследование +------------------------- +В качестве имени родительского шаблона можно использовать переменную или любое выражение PHP, поэтому наследование может вести себя динамически: ```latte {layout $standalone ? 'minimum.latte' : 'layout.latte'} ``` -Вы также можете использовать Latte API для [автоматического |develop#Automatic-Layout-Lookup] выбора шаблона компоновки. +Вы также можете использовать Latte API для [автоматического |develop#Автоматический поиск макета] выбора шаблона макета. -Советы .[#toc-tips] -------------------- +Советы +------ Вот несколько советов по работе с наследованием макета: -- Если вы используете `{layout}` в шаблоне, он должен быть первым тегом шаблона в этом шаблоне. +- Если вы используете `{layout}` в шаблоне, это должен быть первый тег шаблона в этом шаблоне. -- Макет может [искаться автоматически |develop#automatic-layout-lookup] (как в [презентаторах |application:templates#search-for-templates]). В этом случае, если шаблон не должен иметь макета, он укажет на это с помощью тега `{layout none}`. +- Макет может [искаться автоматически |develop#Автоматический поиск макета] (как, например, в [презентерах |application:templates#Поиск шаблонов]). В таком случае, если шаблон не должен иметь макет, он сообщает об этом тегом `{layout none}`. - Тег `{layout}` имеет псевдоним `{extends}`. -- Имя файла расширенного шаблона зависит от [загрузчика шаблонов |extending-latte#Loaders]. +- Имя файла макета зависит от [загрузчика |loaders]. -- Вы можете иметь столько блоков, сколько хотите. Помните, что дочерние шаблоны не обязаны определять все родительские блоки, поэтому вы можете заполнить разумные значения по умолчанию в нескольких блоках, а затем определить только те, которые вам нужны позже. +- Вы можете иметь столько блоков, сколько хотите. Помните, что дочерние шаблоны не обязаны определять все родительские блоки, поэтому вы можете заполнить разумные значения по умолчанию в нескольких блоках, а затем определить только те, которые вам понадобятся позже. -Блоки `{block}` .{toc: Blocks} -============================== +Блоки `{block}` .{toc: Блоки} +============================= .[note] -См. также анонимные [`{block}` |tags#block] +См. также анонимный [`{block}` |tags#block] -Блок дает возможность изменить отображение определенной части шаблона, но никак не вмешивается в окружающую логику. Давайте рассмотрим следующий пример, чтобы проиллюстрировать, как работает блок и, что более важно, как он не работает: +Блок представляет собой способ изменить способ отрисовки определенной части шаблона, но никак не влияет на логику вокруг него. В следующем примере мы покажем, как блок работает, а также как он не работает: -```latte -{* parent.Latte *} +```latte .{file: parent.latte} {foreach $posts as $post} {block post}

                                                                                                                {$post->title}

                                                                                                                @@ -159,10 +158,9 @@ {/foreach} ``` -Если вы отобразите этот шаблон, результат будет точно таким же с тегами блока или без них. Блоки имеют доступ к переменным из внешних диапазонов. Это просто способ сделать его переопределяемым для дочернего шаблона: +Если вы отрисуете этот шаблон, результат будет точно таким же, как с тегами `{block}`, так и без них. Блоки имеют доступ к переменным из внешних областей видимости. Они просто дают возможность быть переопределенными дочерним шаблоном: -```latte -{* child.Latte *} +```latte .{file: child.latte} {layout 'parent.Latte'} {block post} @@ -173,7 +171,7 @@ {/block} ``` -Теперь при рендеринге дочернего шаблона цикл будет использовать блок, определенный в дочернем шаблоне `child.Latte`, вместо блока, определенного в базовом шаблоне `parent.Latte`; выполненный шаблон будет эквивалентен следующему: +Теперь при отрисовке дочернего шаблона цикл будет использовать блок, определенный в дочернем шаблоне `child.Latte`, вместо блока, определенного в `parent.Latte`; запущенный шаблон тогда эквивалентен следующему: ```latte {foreach $posts as $post} @@ -184,7 +182,7 @@ {/foreach} ``` -Однако, если мы создадим новую переменную внутри именованного блока или заменим значение существующей переменной, изменение будет видно только внутри блока: +Однако, если мы создадим новую переменную внутри именованного блока или заменим значение существующей, изменение будет видно только внутри блока: ```latte {var $foo = 'foo'} @@ -193,17 +191,17 @@ {var $bar = 'bar'} {/block} -foo: {$foo} // prints: foo -bar: {$bar ?? 'not defined'} // prints: not defined +foo: {$foo} // выводит: foo +bar: {$bar ?? 'not defined'} // выводит: not defined ``` -Содержимое блока может быть изменено с помощью [фильтров |syntax#Filters]. В следующем примере удаляется весь HTML и приводится заголовок: +Содержимое блока можно изменить с помощью [фильтров |syntax#Фильтры]. Следующий пример удаляет все HTML и изменяет регистр букв: ```latte {block title|stripHtml|capitalize}...{/block} ``` -Тег также может быть записан как [n:attribute |syntax#n-attributes]: +Тег также можно записать как [n:attribut |syntax#n:атрибуты]: ```latte
                                                                                                                @@ -212,10 +210,10 @@ bar: {$bar ?? 'not defined'} // prints: not defined ``` -Локальные блоки .{data-version:2.9}[#toc-local-blocks] ------------------------------------------------------- +Локальные блоки +--------------- -Каждый блок переопределяет содержимое одноименного родительского блока. За исключением локальных блоков. Они чем-то похожи на приватные методы в классе. Вы можете создать шаблон, не опасаясь, что из-за совпадения имен блоков они будут перезаписаны вторым шаблоном. +Каждый блок переопределяет содержимое родительского блока с тем же именем — кроме локальных блоков. В классах это было бы что-то вроде приватных методов. Таким образом, вы можете создавать шаблон, не беспокоясь о том, что из-за совпадения имен блоков они будут переопределены из другого шаблона. ```latte {block local helper} @@ -224,13 +222,13 @@ bar: {$bar ?? 'not defined'} // prints: not defined ``` -Печать блоков `{include}` .{toc: Printing Blocks} -------------------------------------------------- +Отрисовка блоков `{include}` .{toc: Отрисовка блоков} +----------------------------------------------------- .[note] См. также [`{include file}` |tags#include] -Чтобы напечатать блок в определенном месте, используйте тег `{include blockname}`: +Чтобы вывести блок в определенном месте, используйте тег `{include blockname}`: ```latte {block title}{/block} @@ -238,32 +236,28 @@ bar: {$bar ?? 'not defined'} // prints: not defined

                                                                                                                {include title}

                                                                                                                ``` -Вы также можете вывести блок из другого шаблона: +Можно также вывести блок из другого шаблона: ```latte {include footer from 'main.latte'} ``` -Выводимые блоки не имеют доступа к переменным активного контекста, за исключением случаев, когда блок определен в том же файле, куда он включен. Однако они имеют доступ к глобальным переменным. +Отрисовываемый блок не имеет доступа к переменным активного контекста, за исключением случаев, когда блок определен в том же файле, где он и вставлен. Однако он имеет доступ к глобальным переменным. -Вы можете передавать переменные таким образом: +Переменные можно передавать в блок следующим образом: ```latte -{* since Latte 2.9 *} {include footer, foo: bar, id: 123} - -{* before Latte 2.9 *} -{include footer, foo => bar, id => 123} ``` -Вы можете использовать переменную или любое выражение в PHP в качестве имени блока. В этом случае добавьте ключевое слово `block` перед переменной, чтобы при компиляции было известно, что это блок, а не [вставка шаблона |tags#include], имя которого также может быть в переменной: +В качестве имени блока можно использовать переменную или любое выражение PHP. В таком случае перед переменной мы добавляем ключевое слово `block`, чтобы уже во время компиляции Latte знало, что это блок, а не [вставка шаблона |tags#include], имя которого также могло бы быть в переменной: ```latte {var $name = footer} {include block $name} ``` -Блок также может быть напечатан внутри себя, что полезно, например, при рендеринге древовидной структуры: +Блок можно отрисовать и внутри самого себя, что, например, полезно при отрисовке древовидной структуры: ```latte {define menu, $items} @@ -281,19 +275,19 @@ bar: {$bar ?? 'not defined'} // prints: not defined {/define} ``` -Вместо `{include menu, ...}` можно также написать `{include this, ...}`, где `this` означает текущий блок. +Вместо `{include menu, ...}` мы можем написать `{include this, ...}`, где `this` означает текущий блок. -Выводимое содержимое можно изменять с помощью [фильтров |syntax#Filters]. В следующем примере удаляется весь HTML и ставится заголовок: +Отрисовываемый блок можно изменить с помощью [фильтров |syntax#Фильтры]. Следующий пример удаляет все HTML и изменяет регистр букв: ```latte {include heading|stripHtml|capitalize} ``` -Родительский блок .[#toc-parent-block] --------------------------------------- +Родительский блок +----------------- -Если вам нужно вывести содержимое блока из родительского шаблона, вам поможет оператор `{include parent}`. Это полезно, если вы хотите дополнить содержимое родительского блока, а не полностью его переопределить. +Если вам нужно вывести содержимое блока из родительского шаблона, используйте `{include parent}`. Это полезно, если вы хотите просто дополнить содержимое родительского блока, а не полностью его переопределить. ```latte {block footer} @@ -304,32 +298,31 @@ bar: {$bar ?? 'not defined'} // prints: not defined ``` -Определения `{define}` .{toc: Definitions} +Определения `{define}` .{toc: Определения} ------------------------------------------ -Помимо блоков, в Latte существуют также "определения". Их можно сравнить с функциями в обычных языках программирования. Они полезны для повторного использования фрагментов шаблонов, чтобы не повторяться. +Кроме блоков, в Latte существуют также «определения». В обычных языках программирования мы бы сравнили их с функциями. Они полезны для повторного использования фрагментов шаблона, чтобы не повторяться. -Latte старается делать все просто, поэтому в основном определения - это то же самое, что и блоки, и **все, что сказано о блоках, относится и к определениям**. Он отличается от блоков только тремя способами: +Latte старается делать вещи простыми, поэтому в основном определения такие же, как блоки, и **все, что сказано о блоках, относится и к определениям**. Они отличаются от блоков тем, что: -1) они могут принимать аргументы -2) они не могут иметь [фильтров |syntax#Filters] -3) они заключены в теги `{define}`, и содержимое внутри этих тегов не отправляется на вывод, пока вы их не включите. Благодаря этому вы можете создавать их в любом месте: +1) заключены в теги `{define}` +2) отрисовываются только тогда, когда вы вставляете их через `{include}` +3) им можно определить параметры, подобно функциям в PHP ```latte {block foo}

                                                                                                                Hello

                                                                                                                {/block} -{* prints:

                                                                                                                Hello

                                                                                                                *} +{* выводит:

                                                                                                                Hello

                                                                                                                *} {define bar}

                                                                                                                World

                                                                                                                {/define} -{* prints nothing *} +{* ничего не выводит *} {include bar} -{* prints:

                                                                                                                World

                                                                                                                *} +{* выводит:

                                                                                                                World

                                                                                                                *} ``` -Представьте, что у вас есть общий шаблон-помощник, который определяет, как выводить HTML-формы с помощью определений: +Представьте, что у вас есть вспомогательный шаблон с коллекцией определений, как рисовать HTML-формы. -```latte -{* forms.latte *} +```latte .{file: forms.latte} {define input, $name, $value, $type = 'text'} {/define} @@ -339,38 +332,36 @@ Latte старается делать все просто, поэтому в о {/define} ``` -Аргументы определений всегда необязательны со значением по умолчанию `null`, если не указано значение по умолчанию (здесь `text` - это значение по умолчанию для `$type`, возможное с Latte 2.9.1). Начиная с Latte 2.7, типы параметров также могут быть объявлены: `{define input, string $name, ...}`. - -Определения не имеют доступа к переменным активного контекста, но имеют доступ к глобальным переменным. +Аргументы всегда необязательны со значением по умолчанию `null`, если не указано значение по умолчанию (здесь `'text'` — значение по умолчанию для `$type`). Можно также объявить типы параметров: `{define input, string $name, ...}`. -Они включаются так же, как и [блок |#Printing-Blocks]: +Шаблон с определениями загружаем с помощью [`{import}` |#Горизонтальное повторное использование]. Сами определения отрисовываются [так же, как блоки |#Отрисовка блоков]: ```latte

                                                                                                                {include input, 'password', null, 'password'}

                                                                                                                {include textarea, 'comment'}

                                                                                                                ``` +Определения не имеют доступа к переменным активного контекста, но имеют доступ к глобальным переменным. + -Динамические имена блоков .[#toc-dynamic-block-names] ------------------------------------------------------ +Динамические имена блоков +------------------------- -Latte позволяет очень гибко определять блоки, потому что имя блока может быть любым выражением PHP. В данном примере определены три блока с именами `hi-Peter`, `hi-John` и `hi-Mary`: +Latte допускает большую гибкость при определении блоков, поскольку имя блока может быть любым выражением PHP. Этот пример определяет три блока с именами `hi-Peter`, `hi-John` и `hi-Mary`: -```latte -{* parent.latte *} +```latte .{file: parent.latte} {foreach [Peter, John, Mary] as $name} {block "hi-$name"}Hi, I am {$name}.{/block} {/foreach} ``` -Например, мы можем переопределить только один блок в дочернем шаблоне: +В дочернем шаблоне мы можем переопределить, например, только один блок: -```latte -{* child.latte *} +```latte .{file: child.latte} {block hi-John}Hello. I am {$name}.{/block} ``` -Таким образом, вывод будет выглядеть следующим образом: +Таким образом, вывод будет выглядеть так: ```latte Hi, I am Peter. @@ -379,13 +370,13 @@ Hi, I am Mary. ``` -Проверка существования блока `{ifset}` .{toc: Checking Block Existence} ------------------------------------------------------------------------ +Проверка существования блоков `{ifset}` .{toc: Проверка существования блоков} +----------------------------------------------------------------------------- .[note] -См. также [`{ifset $var}` |tags#ifset-elseifset] +См. также [`{ifset $var}` |tags#ifset elseifset] -Используйте тест `{ifset blockname}`, чтобы проверить, существует ли блок (или несколько блоков) в текущем контексте: +С помощью теста `{ifset blockname}` мы проверяем, существует ли блок (или несколько блоков) в текущем контексте: ```latte {ifset footer} @@ -397,7 +388,7 @@ Hi, I am Mary. {/ifset} ``` -В качестве имени блока вы можете использовать переменную или любое выражение в PHP. В этом случае добавьте ключевое слово `block` перед переменной, чтобы было понятно, что проверяется не [она |tags#ifset-elseifset]: +В качестве имени блока можно использовать переменную или любое выражение PHP. В таком случае перед переменной мы добавляем ключевое слово `block`, чтобы было ясно, что это не проверка существования [переменных |tags#ifset elseifset]: ```latte {ifset block $name} @@ -405,58 +396,56 @@ Hi, I am Mary. {/ifset} ``` +Существование блоков также проверяет функция [`hasBlock()` |functions#hasBlock]: -Советы .[#toc-tips] -------------------- -Вот несколько советов по работе с блоками: - -- Последний блок верхнего уровня не обязательно должен иметь закрывающий тег (блок заканчивается вместе с концом документа). Это упрощает написание дочерних шаблонов, в которых один основной блок. +```latte +{if hasBlock(header) || hasBlock(footer)} + ... +{/if} +``` -- Для повышения удобочитаемости вы можете по желанию дать имя тегу `{/block}`, например `{/block footer}`. Однако имя должно совпадать с именем блока. В больших шаблонах этот прием помогает увидеть, какие теги блоков закрываются. -- Вы не можете напрямую определить несколько блочных тегов с одинаковым именем в одном шаблоне. Но этого можно добиться, используя [динамические имена блоков |#Dynamic-Block-Names]. +Советы +------ +Несколько советов по работе с блоками: -- Вы можете использовать [n:attributes |syntax#n-attributes] для определения таких блоков, как `

                                                                                                                Welcome to my awesome homepage

                                                                                                                ` +- Последний блок верхнего уровня не обязательно должен иметь закрывающий тег (блок заканчивается концом документа). Это упрощает написание дочерних шаблонов, содержащих один основной блок. -- Блоки также можно использовать без имен, только для применения [фильтров |syntax#Filters] к выводу: `{block|strip} hello {/block}` +- Для лучшей читаемости вы можете указать имя блока в теге `{/block}`, например `{/block footer}`. Однако имя должно совпадать с именем блока. В больших шаблонах эта техника поможет вам определить, какие теги блока закрываются. +- Вы не можете напрямую определить несколько тегов блоков с одинаковым именем в одном шаблоне. Однако этого можно достичь с помощью [динамических имен блоков |#Динамические имена блоков]. -Горизонтальное повторное использование `{import}` .{toc: Horizontal Reuse} -========================================================================== +- Вы можете использовать [n:атрибуты |syntax#n:атрибуты] для определения блоков, таких как `

                                                                                                                Welcome to my awesome homepage

                                                                                                                ` -Горизонтальное повторное использование - это третий механизм повторного использования и наследования в Latte. Он позволяет загружать блоки из других шаблонов. Это похоже на создание PHP-файла с вспомогательными функциями или трейтом. +- Блоки также можно использовать без имен только для применения [фильтров |syntax#Фильтры]: `{block|strip} hello {/block}` -Хотя наследование шаблонов компоновки является одной из самых мощных возможностей Latte, оно ограничено однократным наследованием; шаблон может расширять только один другой шаблон. Это ограничение делает наследование шаблонов простым для понимания и легким для отладки: -```latte -{layout 'layout.latte'} +Горизонтальное повторное использование `{import}` .{toc: Горизонтальное повторное использование} +================================================================================================ -{block title}...{/block} -{block content}...{/block} -``` +Горизонтальное повторное использование — это третий механизм повторного использования и наследования в Latte. Он позволяет загружать блоки из других шаблонов. Это похоже на то, как если бы мы создали в PHP файл со вспомогательными функциями, который затем загружали бы с помощью `require`. -Горизонтальное повторное использование - это способ достижения той же цели, что и множественное наследование, но без сопутствующих сложностей: +Хотя наследование макета шаблона является одной из самых мощных функций Latte, оно ограничено простым наследованием — шаблон может расширять только один другой шаблон. Горизонтальное повторное использование — это способ достижения множественного наследования. -```latte -{layout 'layout.latte'} +Допустим, у нас есть файл с определениями блоков: -{import 'blocks.latte'} +```latte .{file: blocks.latte} +{block sidebar}...{/block} -{block title}...{/block} -{block content}...{/block} +{block menu}...{/block} ``` -Оператор `{import}` указывает Latte импортировать все блоки и [определения |#Definitions], определенные в `blocks.latte`, в текущий шаблон. +С помощью команды `{import}` мы импортируем все блоки и [#определения], определенные в `blocks.latte`, в другой шаблон: -```latte -{* blocks.latte *} +```latte .{file: child.latte} +{import 'blocks.latte'} -{block sidebar}...{/block} +{* теперь можно использовать блоки sidebar и menu *} ``` -В этом примере оператор `{import}` импортирует блок `sidebar` в основной шаблон. +Если вы импортируете блоки в родительском шаблоне (т.е. используете `{import}` в `layout.latte`), блоки будут доступны и во всех дочерних шаблонах, что очень удобно. -Импортируемый шаблон не должен [расширять |#Layout-Inheritance] другой шаблон, а его тело должно быть пустым. Однако импортируемый шаблон может импортировать другие шаблоны. +Шаблон, предназначенный для импорта (например, `blocks.latte`), не должен [расширять |#Наследование макета] другой шаблон, т.е. использовать `{layout}`. Однако он может импортировать другие шаблоны. Тег `{import}` должен быть первым тегом шаблона после `{layout}`. Имя шаблона может быть любым выражением PHP: @@ -464,12 +453,12 @@ Hi, I am Mary. {import $ajax ? 'ajax.latte' : 'not-ajax.latte'} ``` -Вы можете использовать столько выражений `{import}`, сколько хотите, в любом данном шаблоне. Если два импортированных шаблона определяют один и тот же блок, побеждает первый. Однако наивысший приоритет отдается главному шаблону, который может перезаписать любой импортированный блок. +В шаблоне можно использовать столько команд `{import}`, сколько хотите. Если два импортированных шаблона определяют один и тот же блок, выигрывает первый из них. Однако наивысший приоритет имеет основной шаблон, который может переопределить любой импортированный блок. -Все переопределенные блоки можно включать постепенно, вставляя их в качестве [родительского блока |#Parent-Block]: +Содержимое переопределенных блоков можно сохранить, вставив блок так же, как вставляется [#родительский блок]: ```latte -{layout 'base.latte'} +{layout 'layout.latte'} {import 'blocks.latte'} @@ -481,17 +470,17 @@ Hi, I am Mary. {block content}...{/block} ``` -В этом примере `{include parent}` будет корректно вызывать блок `sidebar` из шаблона `blocks.latte`. +В этом примере `{include parent}` вызывает блок `sidebar` из шаблона `blocks.latte`. -Наследование блоков `{embed}` .{toc: Unit Inheritance}{data-version:2.9} -======================================================================== +Модульное наследование `{embed}` .{toc: Модульное наследование} +=============================================================== -Наследование блоков переносит идею наследования макетов на уровень фрагментов контента. В то время как наследование макета работает со "скелетами документов", которые оживляются дочерними шаблонами, наследование единиц позволяет создавать скелеты для меньших единиц содержимого и повторно использовать их в любом месте. +Модульное наследование расширяет идею наследования макета на уровень фрагментов контента. В то время как наследование макета работает с «каркасом документа», который оживляют дочерние шаблоны, модульное наследование позволяет создавать каркасы для меньших единиц контента и повторно использовать их где угодно. -В наследовании блоков ключевым является тег `{embed}`. Он сочетает в себе поведение `{include}` и `{layout}`. Он позволяет включать содержимое другого шаблона или блока и, по желанию, передавать переменные, как это делает `{include}`. Он также позволяет переопределять любой блок, определенный внутри включенного шаблона, как это делает `{layout}`. +В модульном наследовании ключом является тег `{embed}`. Он сочетает в себе поведение `{include}` и `{layout}`. Он позволяет вставлять содержимое другого шаблона или блока и опционально передавать переменные, так же как в случае `{include}`. Он также позволяет переопределять любой блок, определенный внутри вставленного шаблона, как при использовании `{layout}`. -Для примера мы будем использовать элемент складного аккордеона. Давайте посмотрим на скелет элемента в шаблоне `collapsible.latte`: +Например, используем элемент аккордеон. Посмотрим на каркас элемента, сохраненный в шаблоне `collapsible.latte`: ```latte
                                                                                                                @@ -505,9 +494,9 @@ Hi, I am Mary.
                                                                                                                ``` -Теги `{block}` определяют два блока, которые могут заполнять дочерние шаблоны. Да, как и в случае с родительским шаблоном в шаблоне наследования макета. Вы также видите переменную `$modifierClass`. +Теги `{block}` определяют два блока, которые могут заполнять дочерние шаблоны. Да, как в случае родительского шаблона в наследовании макета. Вы также видите переменную `$modifierClass`. -Давайте используем наш элемент в шаблоне. Здесь на помощь приходит `{embed}`. Это супер мощный набор, который позволяет нам делать все: включать содержимое шаблона элемента, добавлять к нему переменные и добавлять к нему блоки с пользовательским HTML: +Давайте используем наш элемент в шаблоне. Здесь в игру вступает `{embed}`. Это чрезвычайно мощный тег, который позволяет нам делать все: вставлять содержимое шаблона элемента, добавлять в него переменные и добавлять в него блоки с собственным HTML: ```latte {embed 'collapsible.latte', modifierClass: my-style} @@ -522,7 +511,7 @@ Hi, I am Mary. {/embed} ``` -Вывод может выглядеть следующим образом: +Вывод может выглядеть так: ```latte
                                                                                                                @@ -537,7 +526,7 @@ Hi, I am Mary.
                                                                                                                ``` -Блоки внутри тегов embed образуют отдельный слой, независимый от других блоков. Поэтому они могут иметь то же имя, что и блок вне embed, и никак на них не влияют. Используя тег [include |#Printing-Blocks] внутри тегов `{embed}`, вы можете вставлять созданные здесь блоки, блоки из встроенного шаблона (которые *не являются* [локальными |#Local-Blocks]), а также блоки из основного шаблона, которые *являются* локальными. Вы также можете [импортировать блоки |#Horizontal-Reuse] из других файлов: +Блоки внутри вставленных тегов образуют отдельный слой, независимый от других блоков. Поэтому они могут иметь то же имя, что и блок вне вставки, и никак не затрагиваются. С помощью тега [include |#Отрисовка блоков] внутри тегов `{embed}` вы можете вставлять блоки, созданные здесь, блоки из вставленного шаблона (которые *не являются* [локальными |#Локальные блоки]), а также блоки из основного шаблона, которые, наоборот, *являются* локальными. Вы также можете [импортировать блоки |#Горизонтальное повторное использование] из других файлов: ```latte {block outer}…{/block} @@ -549,18 +538,18 @@ Hi, I am Mary. {block inner}…{/block} {block title} - {include inner} {* works, block is defined inside embed *} - {include hello} {* works, block is local in this template *} - {include content} {* works, block is defined in embedded template *} - {include aBlockDefinedInImportedTemplate} {* works *} - {include outer} {* does not work! - block is in outer layer *} + {include inner} {* работает, блок определен внутри embed *} + {include hello} {* работает, блок локален в этом шаблоне *} + {include content} {* работает, блок определен во вставленном шаблоне *} + {include aBlockDefinedInImportedTemplate} {* работает *} + {include outer} {* не работает! - блок находится во внешнем слое *} {/block} {/embed} ``` -Встроенные шаблоны не имеют доступа к переменным активного контекста, но имеют доступ к глобальным переменным. +Вставленные шаблоны не имеют доступа к переменным активного контекста, но имеют доступ к глобальным переменным. -С помощью `{embed}` можно вставлять не только шаблоны, но и другие блоки, поэтому предыдущий пример можно написать так: .{data-version:2.10} +С помощью `{embed}` можно вставлять не только шаблоны, но и другие блоки, и поэтому предыдущий пример можно было бы записать следующим образом: ```latte {define collapsible} @@ -581,23 +570,23 @@ Hi, I am Mary. {/embed} ``` -Если мы передаем выражение в `{embed}` и не ясно, что это - блок или имя файла, добавьте ключевое слово `block` или `file`: +Если мы передаем в `{embed}` выражение и неясно, является ли это именем блока или файла, мы добавляем ключевое слово `block` или `file`: ```latte {embed block $name} ... {/embed} ``` -Примеры использования .[#toc-use-cases] -======================================= +Примеры использования +===================== -В Latte существуют различные виды наследования и повторного использования кода. Давайте обобщим основные понятия для большей наглядности: +В Latte существуют различные типы наследования и повторного использования кода. Давайте подведем итоги основных концепций для большей ясности: `{include template}` -------------------- -**Use Case:** Использование `header.latte` и `footer.latte` внутри `layout.latte`. +**Пример использования**: Использование `header.latte` и `footer.latte` внутри `layout.latte`. `header.latte` @@ -666,7 +655,7 @@ Hi, I am Mary. `{import}` ---------- -**Пользовательский случай**: `sidebar.latte` в `single.product.latte` и `single.service.latte`. +**Пример использования**: `sidebar.latte` в `single.product.latte` и `single.service.latte`. `sidebar.latte` @@ -698,7 +687,7 @@ Hi, I am Mary. `{define}` ---------- -**Пример использования**: Функция, которая получает некоторые переменные и выводит некоторую разметку. +**Пример использования**: Функции, которым передаем переменные и что-то отрисовываем. `form.latte` @@ -724,7 +713,7 @@ Hi, I am Mary. `{embed}` --------- -**Пример использования**: Встраивание `pagination.latte` в `product.table.latte` и `service.table.latte`. +**Пример использования**: Вставка `pagination.latte` в `product.table.latte` и `service.table.latte`. `pagination.latte` diff --git a/latte/ru/type-system.texy b/latte/ru/type-system.texy index 28c8112202..ab6a5a5d6f 100644 --- a/latte/ru/type-system.texy +++ b/latte/ru/type-system.texy @@ -1,27 +1,27 @@ Система типов ************* -
                                                                                                                +
                                                                                                                -Система типов - это главное для разработки надежных приложений. Latte привносит поддержку типов в шаблоны. Знание того, к какому типу данных или объектов относится каждая переменная, позволяет +Система типов является ключевой для разработки надежных приложений. Latte обеспечивает поддержку типов и в шаблонах. Благодаря тому, что мы знаем, какой тип данных или объекта находится в каждой переменной, -- IDE правильно осуществлять автозаполнение (см. [интеграцию и плагины |recipes#Editors-and-IDE]) -- статический анализ для обнаружения ошибок +- IDE может правильно подсказывать (см. [интеграцию |recipes#Редакторы и IDE]) +- статический анализ может выявить ошибки -Два момента, которые значительно повышают качество и удобство разработки. +Оба этих фактора существенно повышают качество и удобство разработки.
                                                                                                                .[note] -Заявленные типы являются информативными, и Latte не проверяет их в настоящее время. +Объявленные типы являются информативными, и Latte в настоящее время их не проверяет. -Как начать использовать типы? Создайте шаблонный класс, например `CatalogTemplateParameters`, представляющий передаваемые параметры: +Как начать использовать типы? Создайте класс шаблона, например `CatalogTemplateParameters`, представляющий передаваемые параметры, их типы и, возможно, значения по умолчанию: ```php class CatalogTemplateParameters { public function __construct( - public string $langs, + public string $lang, /** @var ProductEntity[] */ public array $products, public Address $address, @@ -35,14 +35,11 @@ $latte->render('template.latte', new CatalogTemplateParameters( )); ``` -Затем вставьте тег `{templateType}` с полным именем класса (включая пространство имен) в начало шаблона. Это определяет, что в шаблоне будут переменные `$langs` и `$products`, включая соответствующие типы. -Вы также можете указать типы локальных переменных с помощью тегов [`{var}` |tags#var-default], `{varType}` и [`{define}` |template-inheritance#Definitions]. +А затем в начало шаблона вставьте тег `{templateType}` с полным именем класса (включая пространство имен). Это определяет, что в шаблоне есть переменные `$lang` и `$products` с соответствующими типами. Типы локальных переменных можно указать с помощью тегов [`{var}` |tags#var default], `{varType}`, [`{define}` |template-inheritance#Определения]. -Теперь IDE может корректно автозаполнять. +С этого момента IDE сможет правильно подсказывать. -Как сохранить работу? Как максимально просто написать шаблонный класс или теги `{varType}`? Сгенерировать их. -Именно это и делает пара тегов `{templatePrint}` и `{varPrint}`. -Если вы поместите один из этих тегов в шаблон, то вместо обычного рендеринга будет отображаться код класса или шаблона. Затем просто выделите и скопируйте код в свой проект. +Как сэкономить работу? Как проще всего написать класс с параметрами шаблона или тегами `{varType}`? Позвольте им сгенерироваться. Для этого существует пара тегов `{templatePrint}` и `{varPrint}`. Если вы разместите их в шаблоне, вместо обычной отрисовки отобразится предложение кода класса или список тегов `{varType}`. Затем достаточно одного клика, чтобы выделить код и скопировать его в проект. `{templateType}` @@ -56,7 +53,7 @@ $latte->render('template.latte', new CatalogTemplateParameters( `{varType}` ----------- -Как объявить типы переменных? Для этого используйте тег `{varType}` для существующей переменной, или [`{var}` |tags#var-default]: +Как объявить типы переменных? Для этого служат теги `{varType}` для существующих переменных или [`{var}` |tags#var default]: ```latte {varType Nette\Security\User $user} @@ -66,11 +63,11 @@ $latte->render('template.latte', new CatalogTemplateParameters( `{templatePrint}` ----------------- -Вы также можете сгенерировать этот класс с помощью тега `{templatePrint}`. Если поместить его в начало шаблона, то вместо обычного шаблона будет отображаться код класса. Затем просто выделите и скопируйте код в свой проект. +Класс также можно сгенерировать с помощью тега `{templatePrint}`. Если вы разместите его в начале шаблона, вместо обычной отрисовки отобразится предложение класса. Затем достаточно одного клика, чтобы выделить код и скопировать его в проект. `{varPrint}` ------------ -Тег `{varPrint}` экономит ваше время. Если поместить его в шаблон, то вместо обычного рендеринга отобразится список тегов `{varType}`. Затем просто выберите и скопируйте код в свой шаблон. +Тег `{varPrint}` сэкономит вам время на написание. Если вы разместите его в шаблоне, вместо обычной отрисовки отобразится предложение тегов `{varType}` для локальных переменных. Затем достаточно одного клика, чтобы выделить код и скопировать его в шаблон. -В `{varPrint}` перечислены локальные переменные, которые не являются параметрами шаблона. Если вы хотите перечислить все переменные, используйте `{varPrint all}`. +Сам `{varPrint}` выводит только локальные переменные, которые не являются параметрами шаблона. Если вы хотите вывести все переменные, используйте `{varPrint all}`. diff --git a/latte/ru/why-use.texy b/latte/ru/why-use.texy new file mode 100644 index 0000000000..d25f8f4714 --- /dev/null +++ b/latte/ru/why-use.texy @@ -0,0 +1,80 @@ +Зачем использовать шаблоны? +*************************** + + +Зачем использовать систему шаблонов в PHP? +------------------------------------------ + +Зачем использовать систему шаблонов в PHP, если сам PHP является языком шаблонов? + +Давайте сначала кратко рассмотрим историю этого языка, которая полна интересных поворотов. Одним из первых языков программирования, используемых для генерации HTML-страниц, был язык C. Однако вскоре стало ясно, что его использование для этой цели непрактично. Поэтому Расмус Лердорф создал PHP, который облегчил генерацию динамического HTML с языком C на бэкенде. Таким образом, PHP изначально был разработан как язык шаблонов, но со временем приобрел дополнительные функции и стал полноценным языком программирования. + +Тем не менее, он по-прежнему функционирует и как язык шаблонов. В файле PHP может быть записана HTML-страница, в которой с помощью `` выводятся переменные и т.д. + +Уже на заре истории PHP возникла система шаблонов Smarty, целью которой было строгое разделение внешнего вида (HTML/CSS) от логики приложения. То есть она намеренно предоставляла более ограниченный язык, чем сам PHP, чтобы разработчик не мог, например, выполнить запрос к базе данных из шаблона и т.п. С другой стороны, она представляла собой дополнительную зависимость в проектах, увеличивала их сложность, и программистам приходилось изучать новый язык Smarty. Такая польза была спорной, и для шаблонов продолжали использовать простой PHP. + +Со временем системы шаблонов стали полезными. Они пришли с концепцией [наследования |template-inheritance], [режимом песочницы|sandbox] и рядом других функций, которые значительно упростили создание шаблонов по сравнению с чистым PHP. На первый план вышла тема безопасности, существование [уязвимостей, таких как XSS|safety-first] и необходимость [экранирования |#Что такое экранирование]. Системы шаблонов пришли с автоэкранированием, чтобы исчез риск того, что программист забудет об этом и возникнет серьезная дыра в безопасности (через мгновение мы покажем, что у этого есть определенные подводные камни). + +Преимущества систем шаблонов сегодня значительно превышают затраты, связанные с их внедрением. Поэтому имеет смысл их использовать. + + +Почему Latte лучше, чем Twig или Blade? +--------------------------------------- + +Причин несколько — некоторые приятные, а другие принципиально полезные. Latte — это сочетание приятного с полезным. + +*Сначала приятное:* Latte имеет тот же [синтаксис, что и PHP |syntax#Latte понимает PHP]. Отличается только запись тегов, вместо `` предпочитает более короткие `{` и `}`. Это означает, что вам не нужно изучать новый язык. Затраты на обучение минимальны. И главное, при разработке вам не нужно постоянно "переключаться" между языком PHP и языком шаблона, поскольку они оба одинаковы. В отличие от шаблонов Twig, которые используют язык Python, и программисту приходится переключаться между двумя разными языками. + +*А теперь чрезвычайно полезная причина*: Все системы шаблонов, такие как Twig, Blade или Smarty, в ходе эволюции пришли к защите от XSS в виде автоматического [экранирования |#Что такое экранирование]. Точнее говоря, автоматического вызова функции `htmlspecialchars()`. Однако создатели Latte поняли, что это вовсе не правильное решение. Потому что в разных местах документа экранирование выполняется по-разному. Наивное автоэкранирование — это опасная функция, поскольку она создает ложное чувство безопасности. + +Чтобы автоэкранирование было функциональным и надежным, оно должно распознавать, в каком месте документа выводятся данные (мы называем их контекстами) и в соответствии с этим выбирать функцию экранирования. То есть оно должно быть [контекстно-зависимым |safety-first#Контекстно-зависимое экранирование]. И именно это умеет Latte. Он понимает HTML. Он не воспринимает шаблон просто как строку символов, а понимает, что такое теги, атрибуты и т.д. И поэтому по-разному экранирует в HTML-тексте, внутри HTML-тега, внутри JavaScript и т.д. + +Latte — это первая и единственная система шаблонов в PHP, которая имеет контекстно-зависимое экранирование. Таким образом, она представляет собой единственную действительно безопасную систему шаблонов. + +*И еще одна приятная причина*: Благодаря тому, что Latte понимает HTML, он предлагает другие очень приятные возможности. Например, [n:атрибуты |syntax#n:атрибуты]. Или способность [проверять ссылки |safety-first#Проверка ссылок]. И многое другое. + + +Что такое экранирование? +------------------------ + +Экранирование — это процесс замены символов со специальным значением соответствующими последовательностями при вставке одной строки в другую, чтобы предотвратить нежелательные явления или ошибки. Например, когда мы вставляем строку в HTML-текст, в котором символ `<` имеет особое значение, поскольку он обозначает начало тега, мы заменяем его соответствующей последовательностью, которая является HTML-сущностью `<`. Благодаря этому браузер правильно отобразит символ `<`. + +Простым примером экранирования непосредственно при написании кода в PHP является вставка кавычки в строку, когда перед ней мы пишем обратную косую черту `\`. + +Подробнее экранирование мы разбираем в главе [Как защититься от XSS |safety-first#Как защититься от XSS]. + + +Можно ли в Latte выполнить запрос к базе данных из шаблона? +----------------------------------------------------------- + +В шаблонах можно работать с объектами, которые передает в них программист. Если программист хочет, он может передать в шаблон объект базы данных и выполнить над ним запрос. Если у него есть такое намерение, нет причин ему в этом мешать. + +Другая ситуация возникает, если вы хотите дать возможность редактировать шаблоны клиентам или внешним верстальщикам. В таком случае вы определенно не хотите, чтобы у них был доступ к базе данных. Конечно, вы не передадите шаблон объекту базы данных, но что, если к ней можно получить доступ через другой объект? Решением является [режим песочницы|sandbox], который позволяет определить, какие методы можно вызывать в шаблонах. Благодаря этому вы можете не беспокоиться о нарушении безопасности. + + +Каковы основные различия между системами шаблонов, такими как Latte, Twig и Blade? +---------------------------------------------------------------------------------- + +Различия между системами шаблонов Latte, Twig и Blade заключаются в основном в синтаксисе, безопасности и способе интеграции во фреймворки. + +- Latte: использует синтаксис языка PHP, что облегчает изучение и использование. Обеспечивает первоклассную защиту от атак XSS. +- Twig: использует синтаксис языка Python, который довольно сильно отличается от PHP. Экранирует без различения контекста. Хорошо интегрирован во фреймворк Symfony. +- Blade: использует смесь PHP и собственного синтаксиса. Экранирует без различения контекста. Тесно интегрирован с функциями и экосистемой Laravel. + + +Выгодно ли компаниям использовать систему шаблонов? +--------------------------------------------------- + +Прежде всего, затраты, связанные с обучением, использованием и общей выгодой, значительно различаются в зависимости от системы. Система шаблонов Latte, благодаря тому, что использует синтаксис PHP, очень упрощает обучение для программистов, уже знакомых с этим языком. Обычно программисту требуется несколько часов, чтобы достаточно ознакомиться с Latte. Таким образом, она снижает затраты на обучение. Одновременно ускоряет освоение технологии и, прежде всего, эффективность при повседневном использовании. + +Кроме того, Latte обеспечивает высокий уровень защиты от уязвимости XSS благодаря уникальной технологии контекстно-зависимого экранирования. Эта защита является ключевой для обеспечения безопасности веб-приложений и минимизации риска атак, которые могли бы угрожать пользователям или корпоративным данным. Защита безопасности веб-приложений важна также для поддержания хорошей репутации компании. Проблемы с безопасностью могут привести к потере доверия со стороны клиентов и нанести ущерб репутации компании на рынке. + +Использование Latte также снижает общие затраты на разработку и обслуживание приложения, облегчая и то, и другое. Таким образом, использование системы шаблонов однозначно выгодно. + + +Влияет ли Latte на производительность веб-приложений? +----------------------------------------------------- + +Хотя шаблоны Latte обрабатываются быстро, этот аспект на самом деле не имеет значения. Причина в том, что парсинг файлов происходит только один раз при первом отображении. Затем они компилируются в PHP-код, сохраняются на диск и запускаются при каждом последующем запросе, без необходимости повторной компиляции. + +Так это работает в производственной среде. Во время разработки шаблоны Latte перекомпилируются каждый раз, когда изменяется их содержимое, чтобы разработчик всегда видел актуальную версию. diff --git a/latte/sl/@home.texy b/latte/sl/@home.texy index b8a665dc00..53206a5040 100644 --- a/latte/sl/@home.texy +++ b/latte/sl/@home.texy @@ -1 +1,2 @@ -{{maintitle: Latte - najvarnejše in resnično intuitivne predloge za PHP}} +{{maintitle: Latte – najvarnejše & resnično intuitivne predloge za PHP}} +{{description: Latte je najvarnejši sistem predlog za PHP. Preprečuje veliko varnostnih ranljivosti. Cenili boste njegovo intuitivno sintakso in veliko uporabnih pripomočkov.}} diff --git a/latte/sl/@left-menu.texy b/latte/sl/@left-menu.texy index 4f1e442445..55bc83ae16 100644 --- a/latte/sl/@left-menu.texy +++ b/latte/sl/@left-menu.texy @@ -1,24 +1,24 @@ -- [Začetek |Guide] -- Pojmi - - [Najprej varnost |Safety First] +- [Začnimo z Latte |guide] +- [Zakaj uporabljati predloge? |why-use] +- Koncepti ⚗️ + - [Varnost na prvem mestu |safety-first] - [Dedovanje predlog |Template Inheritance] - - [Sistem tipov |Type System] + - [Sistem tipov |type-system] - [Peskovnik |Sandbox] -- Za oblikovalce - - [Sintaksa |Syntax] - - [Oznake |Tags] - - [Filtri |Filters] - - [Funkcije |Functions] +- Za oblikovalce 🎨 + - [Sintaksa |syntax] + - [Značke |tags] + - [Filtri |filters] + - [Funkcije |functions] - [Nasveti in triki |recipes] -- Za razvijalce - - [Prakse za razvijalce |develop] - - [Razširitev Latte |Extending Latte] - - [Ustvarjanje razširitve |creating-extension] +- Za razvijalce 🧮 + - [Razvojni postopki |develop] + - [Razširjamo Latte |extending-latte] -- [Kuharska knjiga |cookbook/@home] +- [Navodila in postopki 💡|cookbook/@home] - [Migracija iz Twiga |cookbook/migration-from-twig] - - [... več |cookbook/@home] + - [… nadaljnje |cookbook/@home] - "Igrišče .[link-external]":https://fiddle.nette.org/latte/ .{padding-top:1em} diff --git a/latte/sl/@menu.texy b/latte/sl/@menu.texy index ae20f75dd7..a889e7dd37 100644 --- a/latte/sl/@menu.texy +++ b/latte/sl/@menu.texy @@ -1,12 +1,12 @@
                                                                                                                  -- [Domov |@home] -- [Dokumentacija |Guide] +- [Uvod |@home] +- [Dokumentacija |guide] - "GitHub .[link-external]":https://github.com/nette/latte
                                                                                                                diff --git a/latte/sl/@meta.texy b/latte/sl/@meta.texy new file mode 100644 index 0000000000..581c4735c1 --- /dev/null +++ b/latte/sl/@meta.texy @@ -0,0 +1 @@ +{{sitename: Latte Dokumentacija}} diff --git a/latte/sl/compiler-passes.texy b/latte/sl/compiler-passes.texy new file mode 100644 index 0000000000..c15eb627c5 --- /dev/null +++ b/latte/sl/compiler-passes.texy @@ -0,0 +1,555 @@ +Kompilacijski prehodi +********************* + +.[perex] +Kompilacijski prehodi zagotavljajo zmogljiv mehanizem za analizo in spreminjanje predlog Latte *po* njihovem razčlenjevanju v abstraktno sintaktično drevo (AST) in *pred* generiranjem končne PHP kode. To omogoča napredno manipulacijo s predlogami, optimizacije, varnostne preglede (kot je Sandbox) in zbiranje informacij o predlogah. Ta vodnik vas bo vodil skozi ustvarjanje lastnih kompilacijskih prehodov. + + +Kaj je kompilacijski prehod? +============================ + +Za razumevanje vloge kompilacijskih prehodov si oglejte [Proces kompilacije Latte |custom-tags#Razumevanje postopka prevajanja]. Kot lahko vidite, kompilacijski prehodi delujejo v ključni fazi, kar omogoča globok poseg med začetnim razčlenjevanjem in končnim izpisom kode. + +V jedru je kompilacijski prehod preprosto PHP klicni objekt (kot funkcija, statična metoda ali metoda instance), ki sprejme en argument: korenski vozel AST predloge, ki je vedno instanca `Latte\Compiler\Nodes\TemplateNode`. + +Primarni cilj kompilacijskega prehoda je običajno eden ali oba od naslednjih: + +- Analiza: Prehajati skozi AST in zbirati informacije o predlogi (npr. najti vse definirane bloke, preveriti uporabo specifičnih značk, zagotoviti izpolnjevanje določenih varnostnih omejitev). +- Sprememba: Spremeniti strukturo AST ali atribute vozlov (npr. samodejno dodati HTML atribute, optimizirati določene kombinacije značk, zamenjati zastarele značke z novimi, implementirati pravila peskovnika). + + +Registracija +============ + +Kompilacijski prehodi so registrirani s pomočjo metode [razširitve |extending-latte#getPasses] `getPasses()`. Ta metoda vrača asociativno polje, kjer so ključi edinstvena imena prehodov (uporabljena interno in za razvrščanje) in vrednosti so PHP klicni objekti, ki implementirajo logiko prehoda. + +```php +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Extension; + +class MyExtension extends Extension +{ + public function getPasses(): array + { + return [ + 'modificationPass' => $this->modifyTemplateAst(...), + // ... nadaljnji prehodi ... + ]; + } + + public function modifyTemplateAst(TemplateNode $templateNode): void + { + // Implementacija... + } +} +``` + +Prehodi, registrirani z osnovnimi razširitvami Latte in vašimi lastnimi razširitvami, tečejo zaporedno. Vrstni red je lahko pomemben, še posebej, če en prehod temelji na rezultatih ali spremembah drugega. Latte ponuja pomožni mehanizem za nadzor tega vrstnega reda, če je potrebno; glejte dokumentacijo za [`Extension::getPasses()` |extending-latte#getPasses] za podrobnosti. + + +Primer AST +========== + +Za boljšo predstavo o AST dodajamo primer. To je izvorna predloga: + +```latte +{foreach $category->getItems() as $item} +
                                                                                                              • {$item->name|upper}
                                                                                                              • + {else} + no items found +{/foreach} +``` + +In to je njena predstavitev v obliki AST: + +/--pre +Latte\Compiler\Nodes\TemplateNode( + Latte\Compiler\Nodes\FragmentNode( + - Latte\Essential\Nodes\ForeachNode( + expression: Latte\Compiler\Nodes\Php\Expression\MethodCallNode( + object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$category') + name: Latte\Compiler\Nodes\Php\IdentifierNode('getItems') + ) + value: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') + content: Latte\Compiler\Nodes\FragmentNode( + - Latte\Compiler\Nodes\TextNode(' ') + - Latte\Compiler\Nodes\Html\ElementNode('li')( + content: Latte\Essential\Nodes\PrintNode( + expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode( + object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') + name: Latte\Compiler\Nodes\Php\IdentifierNode('name') + ) + modifier: Latte\Compiler\Nodes\Php\ModifierNode( + filters: + - Latte\Compiler\Nodes\Php\FilterNode('upper') + ) + ) + ) + ) + else: Latte\Compiler\Nodes\FragmentNode( + - Latte\Compiler\Nodes\TextNode('no items found') + ) + ) + ) +) +\-- + + +Prehajanje AST s pomočjo `NodeTraverser` +======================================== + +Ročno pisanje rekurzivnih funkcij za prehajanje kompleksne strukture AST je utrujajoče in nagnjeno k napakam. Latte ponuja posebno orodje za ta namen: [api:Latte\Compiler\NodeTraverser]. Ta razred implementira [načrtovalski vzorec Visitor |https://en.wikipedia.org/wiki/Visitor_pattern], zahvaljujoč kateremu je prehajanje AST sistematično in enostavno obvladljivo. + +Osnovna uporaba vključuje ustvarjanje instance `NodeTraverser` in klic njene metode `traverse()`, posredovanje korenskega vozla AST in enega ali dveh "visitor" klicnih objektov: + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; + +(new NodeTraverser)->traverse( + $templateNode, + + // 'enter' visitor: Klican ob vstopu v vozel (pred njegovimi otroki) + enter: function (Node $node) { + echo "Vstop v vozel tipa: " . $node::class . "\n"; + // Tu lahko preučujete vozel + if ($node instanceof Nodes\TextNode) { + // echo "Najden tekst: " . $node->content . "\n"; + } + }, + + // 'leave' visitor: Klican ob izstopu iz vozla (po njegovih otrocih) + leave: function (Node $node) { + echo "Izstop iz vozla tipa: " . $node::class . "\n"; + // Tu lahko izvajate akcije po obdelavi otrok + }, +); +``` + +Lahko posredujete samo `enter` visitor, samo `leave` visitor, ali oba, odvisno od vaših potreb. + +**`enter(Node $node)`:** Ta funkcija se izvede za vsak vozel **pred** tem, ko obiskovalec obišče kateregakoli od otrok tega vozla. Uporabna je za: + +- Zbiranje informacij med prehajanjem drevesa navzdol. +- Odločanje *pred* obdelavo otrok (kot odločitev, da jih preskočimo, glejte [#Optimizacija prehajanja]). +- Potencialne spremembe vozla pred obiskom otrok (manj pogosto). + +**`leave(Node $node)`:** Ta funkcija se izvede za vsak vozel **po** tem, ko so bili vsi njegovi otroci (in njihova celotna poddrevesa) v celoti obiskani (tako vstop kot izstop). Je najpogostejše mesto za: + +Oba visitorja `enter` in `leave` lahko neobvezno vračata vrednost za vplivanje na proces prehajanja. Vračanje `null` (ali nič) nadaljuje prehajanje normalno, vračanje instance `Node` zamenja trenutni vozel, in vračanje posebnih konstant kot `NodeTraverser::RemoveNode` ali `NodeTraverser::StopTraversal` spremeni tok, kot je razloženo v naslednjih odsekih. + + +Kako prehajanje deluje +---------------------- + +`NodeTraverser` interno uporablja metodo `getIterator()`, ki jo mora implementirati vsak razred `Node` (kot je bilo obravnavano v [Ustvarjanje lastnih značk |custom-tags#Implementacija getIterator za podvozlišča]). Iterira skozi otroke, pridobljene s pomočjo `getIterator()`, rekurzivno kliče `traverse()` na njih in zagotavlja, da sta `enter` in `leave` visitorja klicana v pravilnem globinsko-prvem vrstnem redu za vsak vozel v drevesu, dostopen preko iteratorjev. To ponovno poudarja, zakaj je pravilno implementiran `getIterator()` v vaših lastnih vozlih značk absolutno nujen za pravilno delovanje kompilacijskih prehodov. + +Napišimo preprost prehod, ki šteje, kolikokrat je v predlogi uporabljena značka `{do}` (predstavljena z `Latte\Essential\Nodes\DoNode`). + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Essential\Nodes\DoNode; + +function countDoTags(TemplateNode $templateNode): void +{ + $count = 0; + (new NodeTraverser)->traverse( + $templateNode, + enter: function (Node $node) use (&$count): void { + if ($node instanceof DoNode) { + $count++; + } + }, + // 'leave' visitor ni potreben za to nalogo + ); + + echo "Najdena značka {do} $count krat.\n"; +} + +$latte = new Latte\Engine; +$ast = $latte->parse($templateSource); +countDoTags($ast); +``` + +V tem primeru smo potrebovali samo visitor `enter` za preverjanje tipa vsakega obiskanega vozla. + +Nato bomo preučili, kako ti visitorji dejansko spreminjajo AST. + + +Spreminjanje AST +================ + +Eden od glavnih namenov kompilacijskih prehodov je spreminjanje abstraktnega sintaktičnega drevesa. To omogoča zmogljive transformacije, optimizacije ali uveljavljanje pravil neposredno na strukturi predloge pred generiranjem PHP kode. `NodeTraverser` ponuja več načinov, kako to doseči znotraj visitorjev `enter` in `leave`. + +**Pomembna opomba:** Spreminjanje AST zahteva previdnost. Napačne spremembe – kot odstranitev osnovnih vozlov ali zamenjava vozla z nezdružljivim tipom – lahko vodijo do napak med generiranjem kode ali povzročijo nepričakovano vedenje med izvajanjem programa. Vedno temeljito testirajte svoje modifikacijske prehode. + + +Spreminjanje lastnosti vozlov +----------------------------- + +Najenostavnejši način za spreminjanje drevesa je neposredna sprememba **javnih lastnosti** vozlov, obiskanih med prehajanjem. Vsi vozli shranjujejo svoje razčlenjene argumente, vsebino ali atribute v javnih lastnostih. + +**Primer:** Ustvarimo prehod, ki najde vse statične tekstovne vozle (`TextNode`, ki predstavljajo običajen HTML ali tekst zunaj Latte značk) in pretvori njihovo vsebino v velike črke *neposredno v AST*. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\TextNode; + +function uppercaseStaticText(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // Lahko uporabimo 'enter', ker TextNode nima otrok za obdelavo + enter: function (Node $node) { + // Je ta vozel statični tekstovni blok? + if ($node instanceof TextNode) { + // Da! Neposredno spremenimo njegovo javno lastnost 'content'. + $node->content = mb_strtoupper(html_entity_decode($node->content)); + } + // Ni treba ničesar vračati; sprememba je uporabljena neposredno. + }, + ); +} +``` + +V tem primeru visitor `enter` preverja, ali je trenutni `$node` tipa `TextNode`. Če je, neposredno posodobimo njegovo javno lastnost `$content` s pomočjo `mb_strtoupper()`. To neposredno spremeni vsebino statičnega teksta, shranjenega v AST *pred* generiranjem PHP kode. Ker spreminjamo objekt neposredno, nam ni treba ničesar vračati iz visitorja. + +Učinek: Če je predloga vsebovala `

                                                                                                                Hello

                                                                                                                {= $var }World`, bo po tem prehodu AST predstavljal nekaj takega: `

                                                                                                                HELLO

                                                                                                                {= $var }WORLD`. To NE VPLIVA na vsebino $var. + + +Zamenjava vozlov +---------------- + +Močnejša tehnika spreminjanja je popolna zamenjava vozla z drugim. To se naredi z **vračanjem nove instance `Node`** iz visitorja `enter` ali `leave`. `NodeTraverser` nato zamenja prvotni vozel z vrnjenim v strukturi starševskega vozla. + +**Primer:** Ustvarimo prehod, ki najde vse uporabe konstante `PHP_VERSION` (predstavljene z `ConstantFetchNode`) in jih zamenja neposredno z nizovnim literalom (`StringNode`), ki vsebuje *dejansko* različico PHP, zaznano *med kompilacijo*. To je oblika optimizacije v času kompilacije. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\Php\Expression\ConstantFetchNode; +use Latte\Compiler\Nodes\Php\Scalar\StringNode; + +function inlinePhpVersion(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // 'leave' se pogosto uporablja za zamenjavo, zagotavlja, da so otroci (če obstajajo) + // obdelani najprej, čeprav bi tu deloval tudi 'enter'. + leave: function (Node $node) { + // Je ta vozel dostop do konstante in ime konstante 'PHP_VERSION'? + if ($node instanceof ConstantFetchNode && (string) $node->name === 'PHP_VERSION') { + // Ustvarimo nov StringNode, ki vsebuje trenutno različico PHP + $newNode = new StringNode(PHP_VERSION); + + // Neobvezno, a dobra praksa: kopiramo informacije o poziciji + $newNode->position = $node->position; + + // Vrnemo nov StringNode. Traverser bo zamenjal + // prvotni ConstantFetchNode s tem $newNode. + return $newNode; + } + // Če ne vrnemo Node, se prvotni $node ohrani. + }, + ); +} +``` + +Tu visitor `leave` identificira specifičen `ConstantFetchNode` za `PHP_VERSION`. Nato ustvari popolnoma nov `StringNode`, ki vsebuje vrednost konstante `PHP_VERSION` *v času kompilacije*. Z vračanjem tega `$newNode` pove traverserju, naj zamenja prvotni `ConstantFetchNode` v AST. + +Učinek: Če je predloga vsebovala `{= PHP_VERSION }` in kompilacija teče na PHP 8.2.1, bo AST po tem prehodu učinkovito predstavljal `{= '8.2.1' }`. + +**Izbira `enter` vs. `leave` za zamenjavo:** + +- Uporabite `leave`, če ustvarjanje novega vozla temelji na rezultatih obdelave otrok starega vozla, ali če želite preprosto zagotoviti, da so otroci obiskani pred zamenjavo (pogosta praksa). +- Uporabite `enter`, če želite zamenjati vozel *pred* tem, ko so njegovi otroci sploh obiskani. + + +Odstranjevanje vozlov +--------------------- + +Lahko popolnoma odstranite vozel iz AST z vračanjem posebne konstante `NodeTraverser::RemoveNode` iz visitorja. + +**Primer:** Odstranimo vse komentarje predloge (`{* ... *}`), ki so predstavljeni z `CommentNode` v AST, generiranem s strani jedra Latte (čeprav so običajno obdelani prej, to služi kot primer). + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\CommentNode; + +function removeCommentNodes(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // 'enter' je tu v redu, ker ne potrebujemo informacij o otrocih za odstranitev komentarja + enter: function (Node $node) { + if ($node instanceof CommentNode) { + // Signaliziramo traverserju, naj odstrani ta vozel iz AST + return NodeTraverser::RemoveNode; + } + }, + ); +} +``` + +**Opozorilo:** Uporabljajte `RemoveNode` previdno. Odstranitev vozla, ki vsebuje osnovno vsebino ali vpliva na strukturo (kot odstranitev vsebinskega vozla cikla), lahko vodi do poškodovanih predlog ali neveljavne generirane kode. Najvarnejše je za vozle, ki so resnično neobvezni ali samostojni (kot komentarji ali razhroščevalne značke) ali za prazne strukturne vozle (npr. prazen `FragmentNode` je lahko v nekaterih kontekstih varno odstranjen s prehodom za čiščenje). + +Te tri metode - spreminjanje lastnosti, zamenjava vozlov in odstranjevanje vozlov - zagotavljajo osnovna orodja za manipulacijo z AST znotraj vaših kompilacijskih prehodov. + + +Optimizacija prehajanja +======================= + +AST predlog je lahko precej velik, potencialno vsebujoč tisoče vozlov. Prehajanje vsakega posameznega vozla je lahko nepotrebno in vpliva na zmogljivost kompilacije, če vaš prehod zanima samo specifične dele drevesa. `NodeTraverser` ponuja načine za optimizacijo prehajanja: + + +Preskakovanje otrok +------------------- + +Če veste, da ko naletite na določen tip vozla, nobeden od njegovih potomcev ne more vsebovati vozlov, ki jih iščete, lahko traverserju poveste, naj preskoči obisk njegovih otrok. To se naredi z vračanjem konstante `NodeTraverser::DontTraverseChildren` iz visitorja **`enter`**. S tem izpustite cele veje pri prehodu, kar potencialno prihrani znaten čas, še posebej v predlogah s kompleksnimi PHP izrazi znotraj značk. + + +Ustavitev prehajanja +-------------------- + +Če vaš prehod potrebuje najti samo *prvi* pojav nečesa (specifičen tip vozla, izpolnitev pogoja), lahko popolnoma ustavite celoten proces prehajanja, takoj ko to najdete. To dosežete z vračanjem konstante `NodeTraverser::StopTraversal` iz visitorja `enter` ali `leave`. Metoda `traverse()` preneha obiskovati katerekoli nadaljnje vozle. To je zelo učinkovito, če potrebujete samo prvo ujemanje v potencialno zelo velikem drevesu. + + +Uporaben pomočnik `NodeHelpers` +=============================== + +Medtem ko `NodeTraverser` ponuja fino stopenjsko kontrolo, Latte ponuja tudi praktičen pomožni razred, [api:Latte\Compiler\NodeHelpers], ki enkapsulira `NodeTraverser` za več pogostih nalog iskanja in analize, pogosto zahtevajoč manj pripravljalne kode. + + +find(Node $startNode, callable $filter): array .[method] +-------------------------------------------------------- + +Ta statična metoda najde **vse** vozle v poddrevesu, ki se začne na `$startNode` (vključno), ki izpolnjujejo povratni klic `$filter`. Vrača polje ujemajočih se vozlov. + +**Primer:** Najti vse vozle spremenljivk (`VariableNode`) v celotni predlogi. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Php\Expression\VariableNode; +use Latte\Compiler\Nodes\TemplateNode; + +function findAllVariables(TemplateNode $templateNode): array +{ + return NodeHelpers::find( + $templateNode, + fn($node) => $node instanceof VariableNode, + ); +} +``` + + +findFirst(Node $startNode, callable $filter): ?Node .[method] +-------------------------------------------------------------- + +Podobno kot `find`, vendar ustavi prehajanje takoj po najdbi **prvega** vozla, ki izpolnjuje povratni klic `$filter`. Vrača najden objekt `Node` ali `null`, če ni najden noben ujemajoč se vozel. To je v bistvu praktičen ovoj okoli `NodeTraverser::StopTraversal`. + +**Primer:** Najti vozel `{parameters}` (enako kot ročni primer prej, vendar krajše). + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Essential\Nodes\ParametersNode; + +function findParametersNodeHelper(TemplateNode $templateNode): ?ParametersNode +{ + return NodeHelpers::findFirst( + $templateNode->head, // Iskati samo v glavni sekciji za učinkovitost + fn($node) => $node instanceof ParametersNode, + ); +} +``` + + +toValue(ExpressionNode $node, bool $constants = false): mixed .[method] +----------------------------------------------------------------------- + +Ta statična metoda poskuša ovrednotiti `ExpressionNode` **v času kompilacije** in vrniti njegovo ustrezno PHP vrednost. Deluje zanesljivo samo za preproste literalne vozle (`StringNode`, `IntegerNode`, `FloatNode`, `BooleanNode`, `NullNode`) in instance `ArrayNode`, ki vsebujejo samo take ovrednotljive elemente. + +Če je `$constants` nastavljen na `true`, bo poskušal rešiti tudi `ConstantFetchNode` in `ClassConstantFetchNode` s preverjanjem `defined()` in uporabo `constant()`. + +Če vozel vsebuje spremenljivke, klice funkcij ali druge dinamične elemente, ga ni mogoče ovrednotiti v času kompilacije in metoda vrže `InvalidArgumentException`. + +**Primer uporabe:** Pridobivanje statične vrednosti argumenta značke med kompilacijo za odločanje v času kompilacije. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Php\ExpressionNode; + +function getStaticStringArgument(ExpressionNode $argumentNode): ?string +{ + try { + $value = NodeHelpers::toValue($argumentNode); + return is_string($value) ? $value : null; + } catch (\InvalidArgumentException $e) { + // Argument ni bil statični literalni niz + return null; + } +} +``` + + +toText(?Node $node): ?string .[method] +-------------------------------------- + +Ta statična metoda je uporabna za ekstrakcijo preproste tekstovne vsebine iz preprostih vozlov. Deluje primarno z: +- `TextNode`: Vrača njegov `$content`. +- `FragmentNode`: Združi rezultat `toText()` za vse njegove otroke. Če kateri otrok ni pretvorljiv v tekst (npr. vsebuje `PrintNode`), vrne `null`. +- `NopNode`: Vrača prazen niz. +- Drugi tipi vozlov: Vrača `null`. + +**Primer uporabe:** Pridobivanje statične tekstovne vsebine vrednosti HTML atributa ali preprostega HTML elementa za analizo med kompilacijskim prehodom. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Html\AttributeNode; + +function getStaticAttributeValue(AttributeNode $attr): ?string +{ + // $attr->value je tipično AreaNode (kot FragmentNode ali TextNode) + return NodeHelpers::toText($attr->value); +} + +// Primer uporabe v prehodu: +// if ($node instanceof Html\ElementNode && $node->name === 'meta') { +// $nameAttrValue = getStaticAttributeValue($node->getAttributeNode('name')); +// if ($nameAttrValue === 'description') { ... } +// } +``` + +`NodeHelpers` lahko poenostavi vaše kompilacijske prehode z zagotavljanjem pripravljenih rešitev za pogoste naloge prehajanja in analize AST. + + +Praktični primeri +================= + +Uporabimo koncepte prehajanja in spreminjanja AST za reševanje nekaterih praktičnih problemov. Ti primeri prikazujejo pogoste vzorce, uporabljene v kompilacijskih prehodih. + + +Samodejno dodajanje `loading="lazy"` k `` +---------------------------------------------- + +Sodobni brskalniki podpirajo nativno leno nalaganje za slike s pomočjo atributa `loading="lazy"`. Ustvarimo prehod, ki samodejno doda ta atribut vsem značkam ``, ki še nimajo atributa `loading`. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; +use Latte\Compiler\Nodes\Html; + +function addLazyLoading(Nodes\TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // Lahko uporabimo 'enter', ker spreminjamo vozel neposredno + // in nismo odvisni od otrok za to odločitev. + enter: function (Node $node) { + // Je to HTML element z imenom 'img'? + if ($node instanceof Html\ElementNode && $node->name === 'img') { + // Zagotovimo, da vozel atributov obstaja + $node->attributes ??= new Nodes\FragmentNode; + + // Preverimo, ali že obstaja atribut 'loading' (ne glede na velikost črk) + foreach ($node->attributes->children as $attrNode) { + if ($attrNode instanceof Html\AttributeNode + && $attrNode->name instanceof Nodes\TextNode // Statično ime atributa + && strtolower($attrNode->name->content) === 'loading' + ) { + return; + } + } + + // Dodamo presledek, če atributi niso prazni + if ($node->attributes->children) { + $node->attributes->children[] = new Nodes\TextNode(' '); + } + + // Ustvarimo nov vozel atributa: loading="lazy" + $node->attributes->children[] = new Html\AttributeNode( + name: new Nodes\TextNode('loading'), + value: new Nodes\TextNode('lazy'), + quote: '"', + ); + // Sprememba je uporabljena neposredno v objektu, ni treba ničesar vračati. + } + }, + ); +} +``` + +Pojasnilo: +- Visitor `enter` išče vozle `Html\ElementNode` z imenom `img`. +- Iterira skozi obstoječe atribute (`$node->attributes->children`) in preverja, ali je atribut `loading` že prisoten. +- Če ni najden, ustvari nov `Html\AttributeNode`, ki predstavlja `loading="lazy"`. + + +Preverjanje klicev funkcij +-------------------------- + +Kompilacijski prehodi so osnova Latte Sandboxa. Čeprav je dejanski Sandbox sofisticiran, lahko demonstriramo osnovni princip preverjanja prepovedanih klicev funkcij. + +**Cilj:** Preprečiti uporabo potencialno nevarne funkcije `shell_exec` znotraj izrazov predloge. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; +use Latte\Compiler\Nodes\Php; +use Latte\SecurityViolationException; + +function checkForbiddenFunctions(Nodes\TemplateNode $templateNode): void +{ + $forbiddenFunctions = ['shell_exec' => true, 'exec' => true]; // Preprost seznam + + $traverser = new NodeTraverser; + (new NodeTraverser)->traverse( + $templateNode, + enter: function (Node $node) use ($forbiddenFunctions) { + // Je to vozel neposrednega klica funkcije? + if ($node instanceof Php\Expression\FunctionCallNode + && $node->name instanceof Php\NameNode + && isset($forbiddenFunctions[strtolower((string) $node->name)]) + ) { + throw new SecurityViolationException( + "Funkcija {$node->name}() ni dovoljena.", + $node->position, + ); + } + }, + ); +} +``` + +Pojasnilo: +- Definiramo seznam prepovedanih imen funkcij. +- Visitor `enter` preverja `FunctionCallNode`. +- Če je ime funkcije (`$node->name`) statičen `NameNode`, preverjamo njegovo nizovno predstavitev v malih črkah proti našemu prepovedanemu seznamu. +- Če je najdena prepovedana funkcija, vržemo `Latte\SecurityViolationException`, ki jasno označuje kršitev varnostnega pravila in ustavi kompilacijo. + +Ti primeri prikazujejo, kako lahko kompilacijske prehode z uporabo `NodeTraverser` izkoristimo za analizo, samodejne spremembe in uveljavljanje varnostnih omejitev interakcij neposredno s strukturo AST predloge. + + +Najboljše prakse +================ + +Pri pisanju kompilacijskih prehodov imejte v mislih te smernice za ustvarjanje robustnih, vzdržljivih in učinkovitih razširitev: + +- **Vrstni red je pomemben:** Zavedajte se vrstnega reda, v katerem tečejo prehodi. Če vaš prehod temelji na strukturi AST, ustvarjeni z drugim prehodom (npr. osnovni prehodi Latte ali drug lasten prehod), ali če drugi prehodi lahko temeljijo na vaših spremembah, uporabite mehanizem razvrščanja, ki ga ponuja `Extension::getPasses()`, za definiranje odvisnosti (`before`/`after`). Glejte dokumentacijo za [`Extension::getPasses()` |extending-latte#getPasses] za podrobnosti. +- **Ena odgovornost:** Prizadevajte si za prehode, ki opravljajo eno dobro definirano nalogo. Za kompleksne transformacije razmislite o razdelitvi logike na več prehodov – morda enega za analizo in drugega za spremembo na podlagi rezultatov analize. To izboljšuje preglednost in testabilnost. +- **Zmogljivost:** Ne pozabite, da kompilacijski prehodi dodajajo čas kompilacije predloge (čeprav se to običajno zgodi samo enkrat, dokler se predloga ne spremeni). Izogibajte se računsko zahtevnim operacijam v vaših prehodih, če je le mogoče. Izkoristite optimizacije prehajanja kot `NodeTraverser::DontTraverseChildren` in `NodeTraverser::StopTraversal`, kadarkoli veste, da vam ni treba obiskati določenih delov AST. +- **Uporabljajte `NodeHelpers`:** Za pogoste naloge, kot je iskanje specifičnih vozlov ali statično vrednotenje preprostih izrazov, preverite, ali `Latte\Compiler\NodeHelpers` ponuja primerno metodo, preden pišete lastno logiko `NodeTraverser`. To lahko prihrani čas in zmanjša količino pripravljalne kode. +- **Obravnavanje napak:** Če vaš prehod zazna napako ali neveljavno stanje v AST predloge, vržite `Latte\CompileException` (ali `Latte\SecurityViolationException` za varnostne težave) z jasnim sporočilom in ustreznim objektom `Position` (običajno `$node->position`). To zagotavlja uporabno povratno informacijo razvijalcu predloge. +- **Idempotenca (če je mogoče):** Idealno bi bilo, če bi zagon vašega prehoda večkrat na istem AST proizvedel enak rezultat kot njegov enkratni zagon. To ni vedno izvedljivo, vendar poenostavlja razhroščevanje in razmišljanje o interakcijah prehodov, če je to doseženo. Na primer, zagotovite, da vaš modifikacijski prehod preveri, ali je bila sprememba že uporabljena, preden jo ponovno uporabi. + +Z upoštevanjem teh praks lahko učinkovito izkoristite kompilacijske prehode za razširitev zmogljivosti Latte na zmogljiv in zanesljiv način, kar prispeva k varnejšemu, optimiziranemu ali funkcionalno bogatejšemu obdelovanju predlog. diff --git a/latte/sl/cookbook/@home.texy b/latte/sl/cookbook/@home.texy index d6d749e0e4..78376d6b75 100644 --- a/latte/sl/cookbook/@home.texy +++ b/latte/sl/cookbook/@home.texy @@ -1,13 +1,13 @@ -Kuharska knjiga -*************** +Navodila in postopki +******************** .[perex] -Primeri kod in receptov za opravljanje običajnih opravil s programom Latte. +Primeri kod in receptov za izvajanje pogostih nalog s pomočjo Latte. -- [Vse, kar ste vedno želeli vedeti o {iterateWhile} |iteratewhile] -- [Kako pisati poizvedbe SQL v Latte |how-to-write-sql-queries-in-latte]? +- [Postopki za razvijalce |/develop] +- [Posredovanje spremenljivk med predlogami |passing-variables] +- [Vse, kar ste kdaj želeli vedeti o združevanju |grouping] +- [Kako pisati SQL poizvedbe v Latte? |how-to-write-sql-queries-in-latte] - [Migracija iz PHP |migration-from-php] -- [Migracija iz sistema Twig |migration-from-twig] +- [Migracija iz Twiga |migration-from-twig] - [Uporaba Latte s Slim 4 |slim-framework] - -{{leftbar: /@left-menu}} diff --git a/latte/sl/cookbook/@meta.texy b/latte/sl/cookbook/@meta.texy new file mode 100644 index 0000000000..b8c17ced54 --- /dev/null +++ b/latte/sl/cookbook/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Latte Dokumentacija}} +{{leftbar: /@left-menu}} diff --git a/latte/sl/cookbook/grouping.texy b/latte/sl/cookbook/grouping.texy new file mode 100644 index 0000000000..fdd016f1e9 --- /dev/null +++ b/latte/sl/cookbook/grouping.texy @@ -0,0 +1,251 @@ +Vse, kar ste kdaj želeli vedeti o združevanju +********************************************* + +.[perex] +Pri delu s podatki v predlogah lahko pogosto naletite na potrebo po njihovem združevanju ali specifičnem prikazu glede na določena merila. Latte za ta namen ponuja več močnih orodij. + +Filter in funkcija `|group` omogočata učinkovito združevanje podatkov glede na določen kriterij, filter `|batch` pa olajša razdelitev podatkov v fiksne serije, značka `{iterateWhile}` pa omogoča bolj zapleteno upravljanje poteka zank s pogoji. Vsaka od teh značk ponuja specifične možnosti za delo s podatki, s čimer postanejo nepogrešljiva orodja za dinamičen in strukturiran prikaz informacij v predlogah Latte. + + +Filter in funkcija `group` .{data-version:3.0.16} +================================================= + +Predstavljajte si podatkovno tabelo `items` s postavkami, razdeljenimi v kategorije: + +| id | categoryId | name +|------------------ +| 1 | 1 | Apple +| 2 | 1 | Banana +| 3 | 2 | PHP +| 4 | 3 | Green +| 5 | 3 | Red +| 6 | 3 | Blue + +Preprost seznam vseh postavk s pomočjo predloge Latte bi izgledal takole: + +```latte +
                                                                                                                  +{foreach $items as $item} +
                                                                                                                • {$item->name}
                                                                                                                • +{/foreach} +
                                                                                                                +``` + +Če pa bi želeli, da so postavke urejene v skupine glede na kategorijo, jih moramo razdeliti tako, da bo vsaka kategorija imela svoj seznam. Rezultat bi moral izgledati takole: + +```latte +
                                                                                                                  +
                                                                                                                • Apple
                                                                                                                • +
                                                                                                                • Banana
                                                                                                                • +
                                                                                                                + +
                                                                                                                  +
                                                                                                                • PHP
                                                                                                                • +
                                                                                                                + +
                                                                                                                  +
                                                                                                                • Green
                                                                                                                • +
                                                                                                                • Red
                                                                                                                • +
                                                                                                                • Blue
                                                                                                                • +
                                                                                                                +``` + +Nalogo je mogoče enostavno in elegantno rešiti s pomočjo `|group`. Kot parameter navedemo `categoryId`, kar pomeni, da se postavke razdelijo v manjša polja glede na vrednost `$item->categoryId` (če bi bil `$item` polje, se uporabi `$item['categoryId']`): + +```latte +{foreach ($items|group: categoryId) as $categoryId => $categoryItems} +
                                                                                                                  + {foreach $categoryItems as $item} +
                                                                                                                • {$item->name}
                                                                                                                • + {/foreach} +
                                                                                                                +{/foreach} +``` + +Filter lahko v Latte uporabimo tudi kot funkcijo, kar nam daje alternativno sintakso: `{foreach group($items, categoryId) ...}`. + +Če želite združevati postavke glede na bolj zapletena merila, lahko v parametru filtra uporabite funkcijo. Na primer, združevanje postavk glede na dolžino imena bi izgledalo takole: + +```latte +{foreach ($items|group: fn($item) => strlen($item->name)) as $items} + ... +{/foreach} +``` + +Pomembno je vedeti, da `$categoryItems` ni običajno polje, ampak objekt, ki se obnaša kot iterator. Za dostop do prve postavke skupine lahko uporabite funkcijo [`first()` |latte:functions#first]. + +Ta fleksibilnost pri združevanju podatkov naredi `group` izjemno uporabno orodje za predstavitev podatkov v predlogah Latte. + + +Gnezdene zanke +-------------- + +Predstavljajmo si, da imamo podatkovno tabelo z dodatnim stolpcem `subcategoryId`, ki definira podkategorije posameznih postavk. Želimo prikazati vsako glavno kategorijo v ločenem seznamu `
                                                                                                                  ` in vsako podkategorijo v ločenem gnezdenem seznamu `
                                                                                                                    `: + +```latte +{foreach ($items|group: categoryId) as $categoryItems} +
                                                                                                                      + {foreach ($categoryItems|group: subcategoryId) as $subcategoryItems} +
                                                                                                                        + {foreach $subcategoryItems as $item} +
                                                                                                                      1. {$item->name} + {/foreach} +
                                                                                                                      + {/foreach} +
                                                                                                                    +{/foreach} +``` + + +Povezava z Nette Database +------------------------- + +Poglejmo si, kako učinkovito izkoristiti združevanje podatkov v kombinaciji z Nette Database. Predpostavimo, da delamo s tabelo `items` iz uvodnega primera, ki je preko stolpca `categoryId` povezana s to tabelo `categories`: + +| categoryId | name | +|------------|------------| +| 1 | Fruits | +| 2 | Languages | +| 3 | Colors | + +Podatke iz tabele `items` naložimo s pomočjo Nette Database Explorer ukaza `$items = $db->table('items')`. Med iteracijo nad temi podatki imamo možnost dostopati ne le do atributov kot sta `$item->name` in `$item->categoryId`, ampak zaradi povezave s tabelo `categories` tudi do povezane vrstice v njej preko `$item->category`. Na tej povezavi lahko demonstriramo zanimivo uporabo: + +```latte +{foreach ($items|group: category) as $category => $categoryItems} +

                                                                                                                    {$category->name}

                                                                                                                    +
                                                                                                                      + {foreach $categoryItems as $item} +
                                                                                                                    • {$item->name}
                                                                                                                    • + {/foreach} +
                                                                                                                    +{/foreach} +``` + +V tem primeru uporabljamo filter `|group` za združevanje glede na povezano vrstico `$item->category`, ne le glede na stolpec `categoryId`. Zahvaljujoč temu imamo v spremenljivki ključa neposredno `ActiveRow` dane kategorije, kar nam omogoča neposredno izpisovanje njenega imena s pomočjo `{$category->name}`. To je praktičen primer, kako lahko združevanje naredi predloge bolj pregledne in olajša delo s podatki. + + +Filter `|batch` +=============== + +Filter omogoča razdelitev seznama elementov v skupine s predhodno določenim številom elementov. Ta filter je idealen za situacije, ko želite podatke predstaviti v več manjših skupinah, na primer za boljšo preglednost ali vizualno ureditev na strani. + +Predstavljajmo si, da imamo seznam postavk in jih želimo prikazati v seznamih, kjer vsak vsebuje največ tri postavke. Uporaba filtra `|batch` je v takem primeru zelo praktična: + +```latte +
                                                                                                                      +{foreach ($items|batch: 3) as $batch} + {foreach $batch as $item} +
                                                                                                                    • {$item->name}
                                                                                                                    • + {/foreach} +{/foreach} +
                                                                                                                    +``` + +V tem primeru je seznam `$items` razdeljen v manjše skupine, pri čemer vsaka skupina (`$batch`) vsebuje do tri postavke. Vsaka skupina je nato prikazana v ločenem `
                                                                                                                      ` seznamu. + +Če zadnja skupina ne vsebuje dovolj elementov za dosego želenega števila, drugi parameter filtra omogoča definiranje, s čim bo ta skupina dopolnjena. To je idealno za estetsko poravnavo elementov tam, kjer bi nepopolna vrsta lahko delovala neurejeno. + +```latte +{foreach ($items|batch: 3, '—') as $batch} + ... +{/foreach} +``` + + +Značka `{iterateWhile}` +======================= + +Enake naloge, kot smo jih reševali s filtrom `|group`, si bomo ogledali z uporabo značke `{iterateWhile}`. Glavna razlika med obema pristopoma je v tem, da `group` najprej obdela in združi vse vhodne podatke, medtem ko `{iterateWhile}` nadzoruje potek zank s pogoji, tako da iteracija poteka postopoma. + +Najprej izrišemo tabelo s kategorijami s pomočjo iterateWhile: + +```latte +{foreach $items as $item} +
                                                                                                                        + {iterateWhile} +
                                                                                                                      • {$item->name}
                                                                                                                      • + {/iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                                                                                                                      +{/foreach} +``` + +Medtem ko `{foreach}` označuje zunanji del zanke, torej izrisovanje seznamov za vsako kategorijo, značka `{iterateWhile}` označuje notranji del, torej posamezne postavke. Pogoj v končni znački pravi, da se bo ponavljanje izvajalo, dokler trenutni in naslednji element pripadata isti kategoriji (`$iterator->nextValue` je [naslednji element |/tags#iterator]). + +Če bi bil pogoj vedno izpolnjen, bi se v notranji zanki izrisali vsi elementi: + +```latte +{foreach $items as $item} +
                                                                                                                        + {iterateWhile} +
                                                                                                                      • {$item->name} + {/iterateWhile true} +
                                                                                                                      +{/foreach} +``` + +Rezultat bo izgledal takole: + +```latte +
                                                                                                                        +
                                                                                                                      • Apple
                                                                                                                      • +
                                                                                                                      • Banana
                                                                                                                      • +
                                                                                                                      • PHP
                                                                                                                      • +
                                                                                                                      • Green
                                                                                                                      • +
                                                                                                                      • Red
                                                                                                                      • +
                                                                                                                      • Blue
                                                                                                                      • +
                                                                                                                      +``` + +Za kaj je taka uporaba iterateWhile dobra? Ko bo tabela prazna in ne bo vsebovala nobenih elementov, se ne bo izpisalo prazno `
                                                                                                                        `. + +Če navedemo pogoj v odpiralni znački `{iterateWhile}`, se vedenje spremeni: pogoj (in prehod na naslednji element) se izvede že na začetku notranje zanke, ne na koncu. Torej, medtem ko se v `{iterateWhile}` brez pogoja vedno vstopi, se v `{iterateWhile $cond}` vstopi le ob izpolnitvi pogoja `$cond`. In hkrati se s tem v `$item` zapiše naslednji element. + +Kar pride prav na primer v situaciji, ko bomo želeli prvi element v vsaki kategoriji izrisati na drugačen način, na primer takole: + +```latte +

                                                                                                                        Apple

                                                                                                                        +
                                                                                                                          +
                                                                                                                        • Banana
                                                                                                                        • +
                                                                                                                        + +

                                                                                                                        PHP

                                                                                                                        +
                                                                                                                          +
                                                                                                                        + +

                                                                                                                        Green

                                                                                                                        +
                                                                                                                          +
                                                                                                                        • Red
                                                                                                                        • +
                                                                                                                        • Blue
                                                                                                                        • +
                                                                                                                        +``` + +Prvotno kodo prilagodimo tako, da najprej izrišemo prvo postavko in nato v notranji zanki `{iterateWhile}` izrišemo nadaljnje postavke iz iste kategorije: + +```latte +{foreach $items as $item} +

                                                                                                                        {$item->name}

                                                                                                                        +
                                                                                                                          + {iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                                                                                                                        • {$item->name}
                                                                                                                        • + {/iterateWhile} +
                                                                                                                        +{/foreach} +``` + +V okviru ene zanke lahko ustvarjamo več notranjih zank in jih celo gnezdnimo. Tako bi se dale združevati na primer podkategorije itd. + +Recimo, da bo v tabeli še en stolpec `subcategoryId` in poleg tega, da bo vsaka kategorija v ločenem `
                                                                                                                          `, bo vsaka podkategorija v ločenem `
                                                                                                                            `: + +```latte +{foreach $items as $item} +
                                                                                                                              + {iterateWhile} +
                                                                                                                                + {iterateWhile} +
                                                                                                                              1. {$item->name} + {/iterateWhile $item->subcategoryId === $iterator->nextValue->subcategoryId} +
                                                                                                                              + {/iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                                                                                                                            +{/foreach} +``` diff --git a/latte/sl/cookbook/how-to-write-sql-queries-in-latte.texy b/latte/sl/cookbook/how-to-write-sql-queries-in-latte.texy index 845a5e8408..465de2a83f 100644 --- a/latte/sl/cookbook/how-to-write-sql-queries-in-latte.texy +++ b/latte/sl/cookbook/how-to-write-sql-queries-in-latte.texy @@ -1,10 +1,10 @@ -Kako napisati poizvedbe SQL v Latte? -************************************ +Kako pisati SQL poizvedbe v Latte? +********************************** .[perex] -Latte je lahko uporaben tudi za ustvarjanje zelo zapletenih poizvedb SQL. +Latte se lahko izkaže za koristno tudi pri generiranju resnično zapletenih SQL poizvedb. -Če ustvarjanje poizvedbe SQL vsebuje veliko pogojev in spremenljivk, je lahko pisanje poizvedbe v Latte res preglednejše. Zelo preprost primer: +Če ustvarjanje SQL poizvedbe vključuje vrsto pogojev in spremenljivk, je lahko resnično bolj pregledno, če jo napišete v Latte. Zelo preprost primer: ```latte SELECT users.* FROM users @@ -14,8 +14,7 @@ SELECT users.* FROM users WHERE groups.name = 'Admins' {ifset $country} AND country.name = {$country} {/ifset} ``` -S spletno stranjo `$latte->setContentType()` povemo Latte, naj vsebino obravnava kot navadno besedilo (ne kot HTML) in -nato pripravimo funkcijo escaping, ki escapira nize neposredno z gonilnikom podatkovne zbirke: +S pomočjo `$latte->setContentType()` povemo Latteju, naj vsebino obravnava kot navadno besedilo (ne kot HTML) in nato pripravimo funkcijo za ubežanje znakov, ki bo nize ubežala neposredno z gonilnikom baze podatkov: ```php $db = new PDO(/* ... */); @@ -27,17 +26,15 @@ $latte->addFilter('escape', fn($val) => match (true) { is_int($val), is_float($val) => (string) $val, is_bool($val) => $val ? '1' : '0', is_null($val) => 'NULL', - default => throw new Exception('Unsupported type'), + default => throw new Exception('Nepodprt tip'), }); ``` -Uporaba bi bila videti takole: +Uporaba bi izgledala takole: ```php $sql = $latte->renderToString('query.sql.latte', ['country' => $country]); $result = $db->query($sql); ``` -*Ta primer zahteva Latte v3.0.5 ali novejšo različico.* - -{{leftbar: /@left-menu}} +*Navedeni primer zahteva Latte v3.0.5 ali višjo.* diff --git a/latte/sl/cookbook/iteratewhile.texy b/latte/sl/cookbook/iteratewhile.texy deleted file mode 100644 index 16f20568ff..0000000000 --- a/latte/sl/cookbook/iteratewhile.texy +++ /dev/null @@ -1,214 +0,0 @@ -Vse, kar ste vedno želeli vedeti o {iterateWhile} -************************************************* - -.[perex] -Oznaka `{iterateWhile}` je primerna za različne trike v ciklih foreach. - -Recimo, da imamo naslednjo tabelo podatkovne zbirke, v kateri so predmeti razdeljeni v kategorije: - -| id | catId | name -|------------------ -| 1 | 1 | Apple -| 2 | 1 | Banana -| 3 | 2 | PHP -| 4 | 3 | Green -| 5 | 3 | Red -| 6 | 3 | Blue - -Seveda je risanje elementov v zanki foreach kot seznam enostavno: - -```latte -
                                                                                                                              -{foreach $items as $item} -
                                                                                                                            • {$item->name}
                                                                                                                            • -{/foreach} -
                                                                                                                            -``` - -Kaj pa storiti, če želite vsako kategorijo prikazati na ločenem seznamu? Z drugimi besedami, kako rešiti nalogo združevanja elementov z linearnega seznama v ciklu foreach. Rezultat mora biti videti takole: - -```latte -
                                                                                                                              -
                                                                                                                            • Apple
                                                                                                                            • -
                                                                                                                            • Banana
                                                                                                                            • -
                                                                                                                            - -
                                                                                                                              -
                                                                                                                            • PHP
                                                                                                                            • -
                                                                                                                            - -
                                                                                                                              -
                                                                                                                            • Green
                                                                                                                            • -
                                                                                                                            • Red
                                                                                                                            • -
                                                                                                                            • Blue
                                                                                                                            • -
                                                                                                                            -``` - -Pokazali vam bomo, kako enostavno in elegantno lahko to nalogo rešimo z iterateWhile: - -```latte -{foreach $items as $item} -
                                                                                                                              - {iterateWhile} -
                                                                                                                            • {$item->name}
                                                                                                                            • - {/iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                                                                                            -{/foreach} -``` - -Medtem ko `{foreach}` označuje zunanji del cikla, tj. izris seznamov za vsako kategorijo, oznake `{iterateWhile}` označujejo notranji del, tj. posamezne elemente. -Pogoj v končni oznaki pove, da se bo ponavljanje nadaljevalo, dokler trenutni in naslednji element pripadata isti kategoriji (`$iterator->nextValue` je [naslednji element |/tags#$iterator]). - -Če je pogoj vedno izpolnjen, so v notranjem ciklu narisani vsi elementi: - -```latte -{foreach $items as $item} -
                                                                                                                              - {iterateWhile} -
                                                                                                                            • {$item->name} - {/iterateWhile true} -
                                                                                                                            -{/foreach} -``` - -Rezultat bo videti takole: - -```latte -
                                                                                                                              -
                                                                                                                            • Apple
                                                                                                                            • -
                                                                                                                            • Banana
                                                                                                                            • -
                                                                                                                            • PHP
                                                                                                                            • -
                                                                                                                            • Green
                                                                                                                            • -
                                                                                                                            • Red
                                                                                                                            • -
                                                                                                                            • Blue
                                                                                                                            • -
                                                                                                                            -``` - -Kaj dobrega prinaša takšna uporaba iterateWhile? Kako se razlikuje od rešitve, ki smo jo prikazali na samem začetku tega učbenika? Razlika je v tem, da če je tabela prazna in ne vsebuje nobenih elementov, se ne bo prikazala prazna `
                                                                                                                              `. - - -Rešitev brez spletne strani `{iterateWhile}` .[#toc-solution-without-iteratewhile] ----------------------------------------------------------------------------------- - -Če bi isto nalogo reševali s povsem osnovnimi konstrukcijami sistemov predlog, na primer v Twigu, Bladeu ali čistem PHP, bi bila rešitev videti nekako takole: - -```latte -{var $prevCatId = null} -{foreach $items as $item} - {if $item->catId !== $prevCatId} - {* kategorija se je spremenila *} - - {* zapremo prejšnji
                                                                                                                                , če to ni prvi element *} - {if $prevCatId !== null} -
                                                                                                                              - {/if} - - {* odpremo nov seznam *} -
                                                                                                                                - - {do $prevCatId = $item->catId} - {/if} - -
                                                                                                                              • {$item->name}
                                                                                                                              • -{/foreach} - -{if $prevCatId !== null} - {* zapremo zadnji seznam *} -
                                                                                                                              -{/if} -``` - -Vendar je ta koda nerazumljiva in neintuitivna. Povezava med začetnimi in zaključnimi oznakami HTML sploh ni jasna. Na prvi pogled ni jasno, ali gre za napako. In zahteva pomožne spremenljivke, kot je `$prevCatId`. - -V nasprotju s tem je rešitev s `{iterateWhile}` čista, jasna, ne potrebuje pomožnih spremenljivk in je zanesljiva. - - -Pogoj v zaključni oznaki .[#toc-condition-in-the-closing-tag] -------------------------------------------------------------- - -Če določimo pogoj v začetni oznaki `{iterateWhile}`, se obnašanje spremeni: pogoj (in prehod na naslednji element) se izvede na začetku notranjega cikla in ne na koncu. -Tako se `{iterateWhile}` brez pogoja vedno vnese, `{iterateWhile $cond}` pa se vnese šele, ko je izpolnjen pogoj `$cond`. Hkrati se v `$item` zapiše naslednji element. - -To je uporabno na primer v primeru, ko želite prvi element v vsaki kategoriji prikazati na drugačen način, npr: - -```latte -

                                                                                                                              Apple

                                                                                                                              -
                                                                                                                                -
                                                                                                                              • Banana
                                                                                                                              • -
                                                                                                                              - -

                                                                                                                              PHP

                                                                                                                              -
                                                                                                                                -
                                                                                                                              - -

                                                                                                                              Green

                                                                                                                              -
                                                                                                                                -
                                                                                                                              • Red
                                                                                                                              • -
                                                                                                                              • Blue
                                                                                                                              • -
                                                                                                                              -``` - -Spremenimo prvotno kodo, v notranji zanki izrišemo prvi element in nato dodatne elemente iz iste kategorije `{iterateWhile}`: - -```latte -{foreach $items as $item} -

                                                                                                                              {$item->name}

                                                                                                                              -
                                                                                                                                - {iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                                                                                              • {$item->name}
                                                                                                                              • - {/iterateWhile} -
                                                                                                                              -{/foreach} -``` - - -Vgnezdene zanke .[#toc-nested-loops] ------------------------------------- - -V enem ciklu lahko ustvarimo več notranjih zank in jih celo ugnezdimo. Na ta način lahko na primer združimo podkategorije. - -Recimo, da je v preglednici še en stolpec `subCatId` in poleg tega, da je vsaka kategorija v ločenem `
                                                                                                                                `, bo vsaka podkategorija v ločenem `
                                                                                                                                  `: - -```latte -{foreach $items as $item} -
                                                                                                                                    - {iterateWhile} -
                                                                                                                                      - {iterateWhile} -
                                                                                                                                    1. {$item->name} - {/iterateWhile $item->subCatId === $iterator->nextValue->subCatId} -
                                                                                                                                    - {/iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                                                                                                  -{/foreach} -``` - - -Filtriranje `|batch` .[#toc-filter-batch] ------------------------------------------ - -Združevanje linearnih elementov v skupine omogoča tudi filter `batch`, in sicer v serije s fiksnim številom elementov: - -```latte -
                                                                                                                                    -{foreach ($items|batch:3) as $batch} - {foreach $batch as $item} -
                                                                                                                                  • {$item->name}
                                                                                                                                  • - {/foreach} -{/foreach} -
                                                                                                                                  -``` - -Zamenjamo ga lahko z iterateWhile, kot sledi: - -```latte -
                                                                                                                                    -{foreach $items as $item} - {iterateWhile} -
                                                                                                                                  • {$item->name}
                                                                                                                                  • - {/iterateWhile $iterator->counter0 % 3} -{/foreach} -
                                                                                                                                  -``` - -{{leftbar: /@left-menu}} diff --git a/latte/sl/cookbook/migration-from-php.texy b/latte/sl/cookbook/migration-from-php.texy index b978d1fe6d..d3482bc5b6 100644 --- a/latte/sl/cookbook/migration-from-php.texy +++ b/latte/sl/cookbook/migration-from-php.texy @@ -2,27 +2,27 @@ Migracija iz PHP v Latte ************************ .[perex] -Ali prenašate star projekt, napisan v čistem jeziku PHP, na Latte? Pripravili smo orodje, ki vam bo olajšalo migracijo. [Preizkusite ga na spletu |https://php2latte.nette.org]. +Prevajate star projekt, napisan v čistem PHP, v Latte? Imamo orodje za vas, ki vam bo olajšalo migracijo. [Preizkusite na spletu |https://fiddle.nette.org/php2latte/]. -Orodje lahko prenesete s [spletišča GitHub |https://github.com/nette/latte-tools] ali pa ga namestite s programom Composer: +Orodje si lahko prenesete z [GitHubu|https://github.com/nette/latte-tools] ali namestite s pomočjo Composerja: ```shell composer create-project latte/tools ``` -Pretvornik ne uporablja preprostih zamenjav regularnih izrazov, temveč neposredno uporablja razčlenjevalnik PHP, zato lahko obdela vsako zapleteno sintakso. +Pretvornik ne uporablja preprostih zamenjav s pomočjo regularnih izrazov, nasprotno, uporablja neposredno PHP parser, tako da se spopade s kakršno koli zapleteno sintakso. -Za pretvorbo iz PHP v Latte se uporablja skripta `php-to-latte.php`: +Za pretvorbo iz PHP v Latte služi skript `php-to-latte.php`: ```shell php-to-latte.php input.php [output.latte] ``` -Primer .[#toc-example] ----------------------- +Primer +------ -Vhodna datoteka je lahko videti takole (je del kode foruma PunBB): +Vhodna datoteka lahko izgleda na primer takole (gre za del kode foruma PunBB): ```php

                                                                                                                                  @@ -48,7 +48,7 @@ foreach ($result as $cur_group) {
                                                                                                                    ``` -generira to predlogo: +Generira to predlogo: ```latte

                                                                                                                    {$lang_common['User list']}

                                                                                                                    @@ -68,5 +68,3 @@ generira to predlogo:
                                                                                                                    ``` - -{{leftbar: /@left-menu}} diff --git a/latte/sl/cookbook/migration-from-twig.texy b/latte/sl/cookbook/migration-from-twig.texy index cbf7007a55..50a45b3e58 100644 --- a/latte/sl/cookbook/migration-from-twig.texy +++ b/latte/sl/cookbook/migration-from-twig.texy @@ -1,38 +1,38 @@ -Migracija iz sistema Twig v sistem Latte -**************************************** +Migracija iz Twiga v Latte +************************** .[perex] -Migrirate projekt, napisan v Twigu, na sodobnejši Latte? Pripravili smo orodje za lažjo migracijo. [Preizkusite ga na spletu |https://twig2latte.nette.org]. +Prevajate projekt, napisan v Twigu, v sodobnejši Latte? Imamo orodje za vas, ki vam bo olajšalo migracijo. [Preizkusite na spletu |https://fiddle.nette.org/twig2latte/]. -Orodje lahko prenesete s [spletišča GitHub |https://github.com/nette/latte-tools] ali ga namestite z uporabo programa Composer: +Orodje si lahko prenesete z [GitHubu|https://github.com/nette/latte-tools] ali namestite s pomočjo Composerja: ```shell composer create-project latte/tools ``` -Pretvornik ne uporablja preprostih zamenjav regularnih izrazov, temveč neposredno uporablja razčlenjevalnik Twig, zato lahko obdela vsako zapleteno sintakso. +Pretvornik ne uporablja preprostih zamenjav s pomočjo regularnih izrazov, nasprotno, uporablja neposredno Twig parser, tako da se spopade s kakršno koli zapleteno sintakso. -Za pretvorbo iz Twiga v Latte se uporablja skripta `twig-to-latte.php`: +Za pretvorbo iz Twiga v Latte služi skript `twig-to-latte.php`: ```shell twig-to-latte.php input.twig.html [output.latte] ``` -Pretvorba .[#toc-conversion] ----------------------------- +Pretvorba +--------- -Pretvorba zahteva ročno urejanje rezultata, saj pretvorbe ni mogoče izvesti nedvoumno. Twig uporablja sintakso s piko, kjer `{{ a.b }}` lahko pomeni `$a->b`, `$a['b']` ali `$a->getB()`, česar med sestavljanjem ni mogoče razlikovati. Pretvornik zato vse pretvori v `$a->b`. +Pretvorba predpostavlja ročno urejanje rezultata, ker pretvorbe ni mogoče izvesti enoznačno. Twig uporablja pikčasto sintakso, kjer `{{ a.b }}` lahko pomeni `$a->b`, `$a['b']` ali `$a->getB()`, česar ni mogoče razlikovati pri kompilaciji. Pretvornik zato vse pretvori v `$a->b`. -Nekatere funkcije, filtri ali oznake nimajo ekvivalenta v Latte ali pa se lahko obnašajo nekoliko drugače. +Nekatere funkcije, filtri ali oznake nimajo ustreznika v Latteju ali pa se lahko obnašajo nekoliko drugače. -Primer .[#toc-example] ----------------------- +Primer +------ -Vhodna datoteka je lahko videti takole: +Vhodna datoteka lahko izgleda na primer takole: -```latte +```twig {% use "blocks.twig" %} @@ -77,5 +77,3 @@ Po pretvorbi v Latte dobimo to predlogo: ``` - -{{leftbar: /@left-menu}} diff --git a/latte/sl/cookbook/passing-variables.texy b/latte/sl/cookbook/passing-variables.texy new file mode 100644 index 0000000000..7fe561de3f --- /dev/null +++ b/latte/sl/cookbook/passing-variables.texy @@ -0,0 +1,158 @@ +Prenos spremenljivk med predlogami +********************************** + +Ta vodnik vam bo pojasnil, kako se spremenljivke prenašajo med predlogami v Latte s pomočjo različnih oznak, kot so `{include}`, `{import}`, `{embed}`, `{layout}`, `{sandbox}` in druge. Izvedeli boste tudi, kako delati s spremenljivkami v oznakah `{block}` in `{define}` ter čemu služi oznaka `{parameters}`. + + +Tipi spremenljivk +----------------- +Spremenljivke v Latte lahko razdelimo v tri kategorije glede na to, kako in kje so definirane: + +**Vhodne spremenljivke** so tiste, ki so v predlogo prenesene od zunaj, na primer iz PHP skripta ali s pomočjo oznake, kot je `{include}`. + +```php +$latte->render('template.latte', ['userName' => 'Jan', 'userAge' => 30]); +``` + +**Okoljske spremenljivke** so spremenljivke, ki obstajajo na mestu določene oznake. Vključujejo vse vhodne spremenljivke in druge spremenljivke, ustvarjene s pomočjo oznak, kot so `{var}`, `{default}` ali v okviru zanke `{foreach}`. + +```latte +{foreach $users as $user} + {include 'userBox.latte', user: $user} +{/foreach} +``` + +**Eksplicitne spremenljivke** so tiste, ki so neposredno specificirane znotraj oznake in so poslane v ciljno predlogo. + +```latte +{include 'userBox.latte', name: $user->name, age: $user->age} +``` + + +`{block}` +--------- +Oznaka `{block}` se uporablja za definiranje ponovno uporabnih blokov kode, ki jih je mogoče v podedovanih predlogah prilagoditi ali razširiti. Okoljske spremenljivke, definirane pred blokom, so dostopne znotraj bloka, vendar se kakršne koli spremembe spremenljivk odražajo le znotraj tega bloka. + +```latte +{var $foo = 'izvirni'} +{block example} + {var $foo = 'spremenjen'} +{/block} + +{$foo} // izpiše: izvirni +``` + + +`{define}` +---------- +Oznaka `{define}` služi za ustvarjanje blokov, ki se renderirajo šele po njihovem klicu s pomočjo `{include}`. Spremenljivke, dostopne znotraj teh blokov, so odvisne od tega, ali so v definiciji navedeni parametri. Če so, imajo dostop le do teh parametrov. Če ne, imajo dostop do vseh vhodnih spremenljivk predloge, v kateri so bloki definirani. + +```latte +{define hello} + {* ima dostop do vseh vhodnih spremenljivk predloge *} +{/define} + +{define hello $name} + {* ima dostop le do parametra $name *} +{/define} +``` + + +`{parameters}` +-------------- +Oznaka `{parameters}` služi za eksplicitno deklaracijo pričakovanih vhodnih spremenljivk na začetku predloge. Na ta način je mogoče enostavno dokumentirati pričakovane spremenljivke in njihove podatkovne tipe. Prav tako je mogoče definirati privzete vrednosti. + +```latte +{parameters int $age, string $name = 'neznano'} +

                                                                                                                    Starost: {$age}, Ime: {$name}

                                                                                                                    +``` + + +`{include file}` +---------------- +Oznaka `{include file}` služi za vstavljanje celotne predloge. Tej predlogi se prenašajo tako vhodne spremenljivke predloge, v kateri je oznaka uporabljena, kot tudi spremenljivke, ki so v njej eksplicitno definirane. Ciljna predloga pa lahko obseg omeji s pomočjo `{parameters}`. + +```latte +{include 'profile.latte', userId: $user->id} +``` + + +`{include block}` +----------------- +Ko vstavljate blok, definiran v isti predlogi, se vanj prenašajo vse okoljske in eksplicitno definirane spremenljivke: + +```latte +{define blockName} +

                                                                                                                    Ime: {$name}, Starost: {$age}

                                                                                                                    +{/define} + +{var $name = 'Jan', $age = 30} +{include blockName} +``` + +V tem primeru se spremenljivki `$name` in `$age` preneseta v blok `blockName`. Na enak način se obnaša tudi `{include parent}`. + +Pri vstavljanju bloka iz druge predloge se prenašajo le vhodne in eksplicitno definirane spremenljivke. Okoljske spremenljivke niso samodejno dostopne. + +```latte +{include blockInOtherTemplate, name: $name, age: $age} +``` + + +`{layout}` ali `{extends}` +-------------------------- +Te oznake definirajo postavitev, v katero se prenašajo vhodne spremenljivke podrejene predloge in nadalje spremenljivke, ustvarjene v kodi pred bloki: + +```latte +{layout 'layout.latte'} +{var $seo = 'index, follow'} +``` + +Predloga `layout.latte`: + +```latte + + + +``` + + +`{embed}` +--------- +Oznaka `{embed}` je podobna oznaki `{include}`, vendar omogoča vstavljanje blokov v predlogo. Za razliko od `{include}` se prenašajo le eksplicitno deklarirane spremenljivke: + +```latte +{embed 'menu.latte', items: $menuItems} +{/embed} +``` + +V tem primeru ima predloga `menu.latte` dostop le do spremenljivke `$items`. + +Nasprotno pa je v blokih znotraj `{embed}` dostop do vseh okoljskih spremenljivk: + +```latte +{var $name = 'Jan'} +{embed 'menu.latte', items: $menuItems} + {block foo} + {$name} + {/block} +{/embed} +``` + + +`{import}` +---------- +Oznaka `{import}` se uporablja za nalaganje blokov iz drugih predlog. Prenašajo se tako vhodne kot eksplicitno deklarirane spremenljivke v uvožene bloke. + +```latte +{import 'buttons.latte'} +``` + + +`{sandbox}` +----------- +Oznaka `{sandbox}` izolira predlogo za varno obdelavo. Spremenljivke se prenašajo izključno eksplicitno. + +```latte +{sandbox 'secure.latte', data: $secureData} +``` diff --git a/latte/sl/cookbook/slim-framework.texy b/latte/sl/cookbook/slim-framework.texy index 33e802240b..bac9b71ab0 100644 --- a/latte/sl/cookbook/slim-framework.texy +++ b/latte/sl/cookbook/slim-framework.texy @@ -2,19 +2,19 @@ Uporaba Latte s Slim 4 ********************** .[perex] -Ta članek, ki ga je napisal Daniel Opitz opisuje, kako uporabljati Latte z ogrodjem Slim. +Ta članek, katerega avtor je "Daniel Opitz":https://odan.github.io/2022/04/06/slim4-latte.html, opisuje uporabo Latte s Slim Frameworkom. -Najprej "namestite Slim Framework":https://odan.github.io/2019/11/05/slim4-tutorial.html in nato Latte z uporabo Composerja: +Najprej si "namestite Slim Framework":https://odan.github.io/2019/11/05/slim4-tutorial.html in nato Latte s pomočjo Composerja: ```shell composer require latte/latte ``` -Konfiguracija .[#toc-configuration] ------------------------------------ +Konfiguracija +------------- -V korenskem imeniku projekta ustvarite nov imenik `templates`. Vanj bodo kasneje nameščene vse predloge. +V korenskem imeniku projekta ustvarite nov imenik `templates`. Vse predloge bodo vanj nameščene kasneje. V datoteko `config/defaults.php` dodajte nov konfiguracijski ključ `template`: @@ -22,22 +22,22 @@ V datoteko `config/defaults.php` dodajte nov konfiguracijski ključ `template`: $settings['template'] = __DIR__ . '/../templates'; ``` -Latte predloge sestavi v izvirno kodo PHP in jih shrani v predpomnilnik na disku. Zato so tako hitre, kot če bi bile napisane v izvornem jeziku PHP. +Latte prevede predloge v izvorno kodo PHP in jih shrani v predpomnilnik na disku. So torej enako hitre, kot če bi bile napisane v izvornem jeziku PHP. -V datoteko `config/defaults.php` dodajte nov konfiguracijski ključ `template_temp`: Prepričajte se, da imenik `{project}/tmp/templates` obstaja ter ima dovoljenja za branje in pisanje. +V datoteko `config/defaults.php` dodajte nov konfiguracijski ključ `template_temp`: Prepričajte se, da imenik `{project}/tmp/templates` obstaja in ima pravice za branje in pisanje. ```php $settings['template_temp'] = __DIR__ . '/../tmp/templates'; ``` -Latte samodejno regenerira predpomnilnik ob vsaki spremembi predloge, kar lahko v produkcijskem okolju izklopite, da prihranite nekaj zmogljivosti: +Latte samodejno regenerira predpomnilnik ob vsaki spremembi predloge, kar je mogoče v produkcijskem okolju izklopiti in prihraniti malo zmogljivosti: ```php -// v produkcijskem okolju spremenite v false. +// v produkcijskem okolju spremenite na false $settings['template_auto_refresh'] = true; ``` -Nato dodajte definicije vsebnika DI za razred `Latte\Engine`. +Nato dodajte definicijo DI vsebnika za razred `Latte\Engine`. ```php +
                                                                                                                      {foreach $items as $item}
                                                                                                                    • {$item|capitalize}
                                                                                                                    • {/foreach}
                                                                                                                    ``` -Če je vse pravilno konfigurirano, bi morali videti naslednji rezultat: +Če je vse pravilno konfigurirano, bi se moral prikazati naslednji izpis: ```latte One @@ -155,4 +155,3 @@ Three ``` {{priority: -1}} -{{leftbar: /@left-menu}} diff --git a/latte/sl/creating-extension.texy b/latte/sl/creating-extension.texy deleted file mode 100644 index e8414bbf5d..0000000000 --- a/latte/sl/creating-extension.texy +++ /dev/null @@ -1,579 +0,0 @@ -Ustvarjanje razširitve -********************** - -.[perex]{data-version:3.0} -Razširitev je razred za večkratno uporabo, ki lahko določa oznake, filtre, funkcije, ponudnike itd. po meri. - -Razširitve ustvarimo, kadar želimo svoje prilagoditve Latte ponovno uporabiti v različnih projektih ali jih deliti z drugimi. -Koristno je tudi, da za vsak spletni projekt ustvarite razširitev, ki bo vsebovala vse posebne oznake in filtre, ki jih želite uporabiti v predlogah projekta. - - -Razred razširitve .[#toc-extension-class] -========================================= - -Razširitev je razred, ki deduje od [api:Latte\Extension]. Registrira se v Latte z uporabo `addExtension()` (ali prek [konfiguracijske datoteke |application:configuration#Latte]): - -```php -$latte = new Latte\Engine; -$latte->addExtension(new MyLatteExtension); -``` - -Če registrirate več razširitev in opredeljujejo enako poimenovane oznake, filtre ali funkcije, zmaga zadnja dodana razširitev. To tudi pomeni, da lahko vaše razširitve razveljavijo izvirne oznake/filtre/funkcije. - -Kadar koli spremenite razred in samodejno osveževanje ni izklopljeno, bo Latte samodejno ponovno sestavil vaše predloge. - -Razred lahko implementira katero koli od naslednjih metod: - -```php -abstract class Extension -{ - /** - * Initializes before template is compiler. - */ - public function beforeCompile(Engine $engine): void; - - /** - * Returns a list of parsers for Latte tags. - * @return array - */ - public function getTags(): array; - - /** - * Returns a list of compiler passes. - * @return array - */ - public function getPasses(): array; - - /** - * Returns a list of |filters. - * @return array - */ - public function getFilters(): array; - - /** - * Returns a list of functions used in templates. - * @return array - */ - public function getFunctions(): array; - - /** - * Returns a list of providers. - * @return array - */ - public function getProviders(): array; - - /** - * Returns a value to distinguish multiple versions of the template. - */ - public function getCacheKey(Engine $engine): mixed; - - /** - * Initializes before template is rendered. - */ - public function beforeRender(Template $template): void; -} -``` - -Za predstavo, kako je videti razširitev, si oglejte vgrajeno razširitev "CoreExtension:https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php". - - -beforeCompile(Latte\Engine $engine): void .[method] ---------------------------------------------------- - -Pokliče se, preden se sestavi predloga. Metoda se lahko uporablja na primer za inicializacije, povezane s sestavljanjem. - - -getTags(): array .[method] --------------------------- - -Pokliče se, ko je predloga sestavljena. Vrne asociativno polje *imena značk => klicni*, ki so [funkcije za razčlenjevanje značk |#Tag Parsing Function]. - -```php -public function getTags(): array -{ - return [ - 'foo' => [FooNode::class, 'create'], - 'bar' => [BarNode::class, 'create'], - 'n:baz' => [NBazNode::class, 'create'], - // ... - ]; -} -``` - -Oznaka `n:baz` predstavlja čisti n:atribut, tj. je oznaka, ki se lahko zapiše samo kot atribut. - -V primeru oznak `foo` in `bar` bo Latte samodejno prepoznal, ali gre za pare, in če je tako, jih je mogoče samodejno zapisati z uporabo n:atributov, vključno z različicami s predponama `n:inner-foo` in `n:tag-foo`. - -Vrstni red izvajanja takih n:atributov je določen z njihovim vrstnim redom v polju, ki ga vrne `getTags()`. Tako se `n:foo` vedno izvede pred `n:bar`, tudi če so atributi v oznaki HTML navedeni v obratnem vrstnem redu kot `
                                                                                                                    `. - -Če morate določiti vrstni red n:atributov v več razširitvah, uporabite pomožno metodo `order()`, kjer parameter `before` xor `after` določa, katere oznake se razvrstijo pred ali za oznako. - -```php -public function getTags(): array -{ - return [ - 'foo' => self::order([FooNode::class, 'create'], before: 'bar')] - 'bar' => self::order([BarNode::class, 'create'], after: ['block', 'snippet'])] - ]; -} -``` - - -getPasses(): array .[method] ----------------------------- - -Ta metoda se kliče, ko je predloga sestavljena. Vrne asociativno polje *name pass => callable*, ki so funkcije, ki predstavljajo tako imenovane [prevoze sestavljavca |#compiler passes], ki prečkajo in spreminjajo AST. - -Spet lahko uporabimo pomožno metodo `order()`. Vrednost parametrov `before` ali `after` je lahko `*` s pomenom pred/po vseh. - -```php -public function getPasses(): array -{ - return [ - 'optimize' => [Passes::class, 'optimizePass'], - 'sandbox' => self::order([$this, 'sandboxPass'], before: '*'), - // ... - ]; -} -``` - - -beforeRender(Latte\Engine $engine): void .[method] --------------------------------------------------- - -Pokliče se pred vsakim izrisom predloge. Metoda se lahko uporablja na primer za inicializacijo spremenljivk, ki se uporabljajo med upodabljanjem. - - -getFilters(): array .[method] ------------------------------ - -Pokliče se, preden se predloga izriše. Vrne [filtre |extending-latte#filters] kot asociativno polje *imena filtrov => klicni*. - -```php -public function getFilters(): array -{ - return [ - 'batch' => [$this, 'batchFilter'], - 'trim' => [$this, 'trimFilter'], - // ... - ]; -} -``` - - -getFunctions(): array .[method] -------------------------------- - -Pokliče se pred izrisom predloge. Vrne [funkcije |extending-latte#functions] kot asociativno polje *naslov funkcije => klicno*. - -```php -public function getFunctions(): array -{ - return [ - 'clamp' => [$this, 'clampFunction'], - 'divisibleBy' => [$this, 'divisibleByFunction'], - // ... - ]; -} -``` - - -getProviders(): array .[method] -------------------------------- - -Pokliče se, preden se predloga prikaže. Vrne polje ponudnikov, ki so običajno predmeti, ki uporabljajo oznake med izvajanjem. Dostop do njih je mogoč prek `$this->global->...`. - -```php -public function getProviders(): array -{ - return [ - 'myFoo' => $this->foo, - 'myBar' => $this->bar, - // ... - ]; -} -``` - - -getCacheKey(Latte\Engine $engine): mixed .[method] --------------------------------------------------- - -Pokliče se pred izrisom predloge. Vrnjena vrednost postane del ključa, katerega hash je vsebovan v imenu sestavljene datoteke predloge. Tako bo Latte za različne vrnjene vrednosti ustvaril različne datoteke predpomnilnika. - - -Kako deluje Latte? .[#toc-how-does-latte-work] -============================================== - -Da bi razumeli, kako opredeliti oznake po meri ali prehode za sestavljanje, je treba razumeti, kako Latte deluje pod pokrovom. - -Sestavljanje predlog v Latte poenostavljeno deluje takole: - -- Najprej **lekser** označi izvorno kodo predloge na majhne dele (žetone) za lažjo obdelavo. -- nato **parser** pretvori tok žetonov v smiselno drevo vozlišč (Abstract Syntax Tree, AST) -- na koncu prevajalnik iz AST ustvari** razred PHP, ki upodobi predlogo in jo shrani v predpomnilnik. - -Pravzaprav je sestavljanje nekoliko bolj zapleteno. Latte ima dva** leksikatorja in razčlenjevalnika: enega za predlogo HTML in drugega za kodo PHP znotraj oznak. Prav tako se razčlenjevanje ne izvaja po tokenizaciji, temveč leksikator in razčlenjevalnik delujeta vzporedno v dveh "nitih" in se usklajujeta. To je raketna znanost :-) - -Poleg tega imajo vse oznake svoje rutine za razčlenjevanje. Ko razčlenjevalnik naleti na oznako, pokliče njeno funkcijo za razčlenjevanje (vrne funkcijo [Extension::getTags() |#getTags]). -Njihova naloga je razčleniti argumente oznake in v primeru parnih oznak tudi notranjo vsebino. Vrne *vozlišče*, ki postane del AST. Za podrobnosti glejte [Funkcija za razčlenjevanje oznak |#Tag parsing function]. - -Ko razčlenjevalnik konča svoje delo, imamo popoln AST, ki predstavlja predlogo. Korensko vozlišče je `Latte\Compiler\Nodes\TemplateNode`. Posamezna vozlišča znotraj drevesa nato predstavljajo ne le oznake, temveč tudi elemente HTML, njihove atribute, vse izraze, uporabljene znotraj oznak, itd. - -Nato pridejo na vrsto tako imenovani [Compiler passes |#Compiler passes], ki so funkcije (vrne jih [Extension::getPasses() |#getPasses]), ki spreminjajo AST. - -Celoten postopek, od nalaganja vsebine predloge prek razčlenjevanja do generiranja končne datoteke, je mogoče zaporedoma izvajati s to kodo, s katero lahko eksperimentirate in izpisujete vmesne rezultate: - -```php -$latte = new Latte\Engine; -$source = $latte->getLoader()->getContent($file); -$ast = $latte->parse($source); -$latte->applyPasses($ast); -$code = $latte->generate($ast, $file); -``` - - -Primer AST .[#toc-example-of-ast] ---------------------------------- - -Za boljšo predstavo o AST dodamo primer. To je izvorna predloga: - -```latte -{foreach $category->getItems() as $item} -
                                                                                                                  1. {$item->name|upper}
                                                                                                                  2. - {else} - no items found -{/foreach} -``` - -To je njena predstavitev v obliki AST: - -/--pre -Latte\Compiler\Nodes\TemplateNode( - Latte\Compiler\Nodes\FragmentNode( - - Latte\Essential\Nodes\ForeachNode( - expression: Latte\Compiler\Nodes\Php\Expression\MethodCallNode( - object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$category') - name: Latte\Compiler\Nodes\Php\IdentifierNode('getItems') - ) - value: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') - content: Latte\Compiler\Nodes\FragmentNode( - - Latte\Compiler\Nodes\TextNode(' ') - - Latte\Compiler\Nodes\Html\ElementNode('li')( - content: Latte\Essential\Nodes\PrintNode( - expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode( - object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') - name: Latte\Compiler\Nodes\Php\IdentifierNode('name') - ) - modifier: Latte\Compiler\Nodes\Php\ModifierNode( - filters: - - Latte\Compiler\Nodes\Php\FilterNode('upper') - ) - ) - ) - ) - else: Latte\Compiler\Nodes\FragmentNode( - - Latte\Compiler\Nodes\TextNode('no items found') - ) - ) - ) -) -\-- - - -Oznake po meri .[#toc-custom-tags] -================================== - -Za opredelitev nove oznake so potrebni trije koraki: - -- definiranje [funkcije za razčlenitev oznake |#tag parsing function] (odgovorna za razčlenitev oznake v vozlišče) -- ustvarjanje razreda vozlišča (odgovoren za [generiranje kode PHP |#generating PHP code] in [pregledovanje AST |#AST traversing]) -- registracija oznake z uporabo funkcije [Extension::getTags() |#getTags] - - -Funkcija za razčlenjevanje oznak .[#toc-tag-parsing-function] -------------------------------------------------------------- - -Za razčlenjevanje oznak skrbi funkcija za razčlenjevanje (tista, ki jo vrne [Extension::getTags( |#getTags])). Njena naloga je razčleniti in preveriti vse argumente znotraj oznake (za to uporablja TagParser). -Poleg tega, če je oznaka par, bo od TemplateParserja zahtevala razčlenitev in vrnitev notranje vsebine. -Funkcija ustvari in vrne vozlišče, ki je običajno otrok `Latte\Compiler\Nodes\StatementNode`, to pa postane del AST. - -Za vsako vozlišče ustvarimo razred, kar bomo storili zdaj, in vanj kot statično tovarno elegantno umestimo funkcijo za razčlenjevanje. Kot primer poskusimo ustvariti znano oznako `{foreach}`: - -```php -use Latte\Compiler\Nodes\StatementNode; - -class ForeachNode extends StatementNode -{ - // funkcija razčlenjevanja, ki za zdaj samo ustvari vozlišče - public static function create(Latte\Compiler\Tag $tag): self - { - $node = new self; - return $node; - } - - public function print(Latte\Compiler\PrintContext $context): string - { - // koda bo dodana pozneje - } - - public function &getIterator(): \Generator - { - // koda bo dodana pozneje - } -} -``` - -Funkciji za razčlenjevanje `create()` se posreduje objekt [api:Latte\Compiler\Tag], ki nosi osnovne informacije o znački (ali gre za klasično značko ali n:atribut, v kateri vrstici je itd.), v glavnem pa dostopa do [api:Latte\Compiler\TagParser] v `$tag->parser`. - -Če mora imeti oznaka argumente, preveri njihov obstoj tako, da pokliče `$tag->expectArguments()`. Za njihovo analizo so na voljo metode predmeta `$tag->parser`: - -- `parseExpression(): ExpressionNode` za izraz, podoben PHP (npr. `10 + 3`) -- `parseUnquotedStringOrExpression(): ExpressionNode` za izraz ali niz brez citatov -- `parseArguments(): ArrayNode` vsebina polja (npr. `10, true, foo => bar`) -- `parseModifier(): ModifierNode` za modifikator (npr. `|upper|truncate:10`) -- `parseType(): expressionNode` za namig tipa (npr. `int|string` ali `Foo\Bar[]`) - -in nizkonivojski [api:Latte\Compiler\TokenStream], ki deluje neposredno z žetoni: - -- `$tag->parser->stream->consume(...): Token` -- `$tag->parser->stream->tryConsume(...): ?Token` - -Latte razširi sintakso PHP na majhne načine, na primer z dodajanjem modifikatorjev, skrajšanih ternarnih operatorjev ali omogočanjem zapisa preprostih alfanumeričnih nizov brez narekovajev. Zato uporabljamo izraz *PHP-like* namesto PHP. Tako na primer metoda `parseExpression()` razčleni `foo` kot `'foo'`. -Poleg tega je *neocitni niz* poseben primer niza, ki ga prav tako ni treba navajati z narekovaji, hkrati pa ni treba, da je alfanumerični. To je na primer pot do datoteke v oznaki `{include ../file.latte}`. Za njegovo razčlenjevanje se uporablja metoda `parseUnquotedStringOrExpression()`. - -.[note] -Preučevanje razredov vozlišč, ki so del Latte, je najboljši način za spoznavanje vseh podrobnosti postopka razčlenjevanja. - -Vrnimo se k oznaki `{foreach}`. V njej pričakujemo argumente v obliki `expression + 'as' + second expression`, ki jih razčlenimo na naslednji način: - -```php -use Latte\Compiler\Nodes\StatementNode; -use Latte\Compiler\Nodes\Php\ExpressionNode; -use Latte\Compiler\Nodes\AreaNode; - -class ForeachNode extends StatementNode -{ - public ExpressionNode $expression; - public ExpressionNode $value; - - public static function create(Latte\Compiler\Tag $tag): self - { - $tag->expectArguments(); - $node = new self; - $node->expression = $tag->parser->parseExpression(); - $tag->parser->stream->consume('as'); - $node->value = $parser->parseExpression(); - return $node; - } -} -``` - -Izrazi, ki smo jih zapisali v spremenljivki `$expression` in `$value`, predstavljajo podmene. - -.[tip] -Spremenljivke s podvozli opredelite kot **javne**, tako da jih lahko po potrebi spremenite v [nadaljnjih korakih obdelave |#Compiler Passes]. Prav tako jih je treba **dati na voljo** za [prečkanje |#AST Traversing]. - -Za parne oznake, kot je naša, mora metoda omogočiti, da TemplateParser razčleni tudi notranjo vsebino oznake. Za to poskrbi `yield`, ki vrne par ''[notranja vsebina, končna oznaka]''. Notranjo vsebino shranimo v spremenljivko `$node->content`. - -```php -public AreaNode $content; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $endTag] = yield; - return $node; -} -``` - -Ključna beseda `yield` povzroči, da se metoda `create()` zaključi in vrne nadzor nazaj v TemplateParser, ki nadaljuje z razčlenjevanjem vsebine, dokler ne naleti na končno oznako. Nato preda krmiljenje nazaj metodi `create()`, ki nadaljuje z delom, ki ga je končala. Uporaba metode `yield`, samodejno vrne `Generator`. - -Na naslov `yield` lahko posredujete tudi polje imen oznak, za katere želite ustaviti razčlenjevanje, če se pojavijo pred končno oznako. To nam pomaga pri izvajanju metode `{foreach}...{else}...{/foreach}` konstrukcijo. Če se pojavi `{else}`, vsebino za njo razčlenimo v `$node->elseContent`: - -```php -public AreaNode $content; -public ?AreaNode $elseContent = null; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $nextTag] = yield ['else']; - if ($nextTag?->name === 'else') { - [$node->elseContent] = yield; - } - - return $node; -} -``` - -Vračanje vozlišča zaključi razčlenjevanje oznak. - - -Ustvarjanje kode PHP .[#toc-generating-php-code] ------------------------------------------------- - -Vsako vozlišče mora izvajati metodo `print()`. Vrne kodo PHP, ki upodablja dani del predloge (izvajalna koda). Kot parameter se ji posreduje objekt [api:Latte\Compiler\PrintContext], ki ima uporabno metodo `format()`, ki poenostavi sestavljanje dobljene kode. - -Metoda `format(string $mask, ...$args)` v maski sprejema naslednje nadomestne znake: -- `%node` izpiše vozlišče -- `%dump` izvozi vrednost v PHP -- `%raw` vstavi besedilo neposredno brez preoblikovanja -- `%args` izpiše ArrayNode kot argumente za klic funkcije -- `%line` izpiše komentar s številko vrstice -- `%escape(...)` izriše vsebino -- `%modify(...)` uporabi modifikator -- `%modifyContent(...)` uporabi modifikator za bloke - - -Naša funkcija `print()` je lahko videti takole (zaradi preprostosti zanemarjamo vejo `else` ): - -```php -public function print(Latte\Compiler\PrintContext $context): string -{ - return $context->format( - <<<'XX' - foreach (%node as %node) %line { - %node - } - - XX, - $this->expression, - $this->value, - $this->position, - $this->content, - ); -} -``` - -Spremenljivka `$this->position` je že opredeljena v razredu [api:Latte\Compiler\Node] in jo določi razčlenjevalnik. Vsebuje objekt [api:Latte\Compiler\Position] s položajem oznake v izvorni kodi v obliki številke vrstice in stolpca. - -Izvedbena koda lahko uporablja pomožne spremenljivke. Da bi se izognili trku s spremenljivkami, ki jih uporablja sama predloga, je običajno, da jim dodamo predpono z znaki `$ʟ__`. - -V času izvajanja lahko uporablja tudi poljubne vrednosti, ki se predlogi posredujejo v obliki ponudnikov z metodo [Extension::getProviders() |#getProviders]. Do njih dostopa z uporabo `$this->global->...`. - - -Prehajanje po AST .[#toc-ast-traversing] ----------------------------------------- - -Za poglobljeno potovanje po drevesu AST je treba implementirati metodo `getIterator()`. To bo omogočilo dostop do podvozij: - -```php -public function &getIterator(): \Generator -{ - yield $this->expression; - yield $this->value; - yield $this->content; - if ($this->elseContent) { - yield $this->elseContent; - } -} -``` - -Upoštevajte, da `getIterator()` vrne referenco. To obiskovalcem vozlišč omogoča zamenjavo posameznih vozlišč z drugimi vozlišči. - -.[warning] -Če ima vozlišče podvozlišča, je treba implementirati to metodo in dati na voljo vsa podvozlišča. V nasprotnem primeru lahko nastane varnostna luknja. Na primer, način peskovnika ne bi mogel nadzorovati podvozij in zagotoviti, da se v njih ne kličejo nedovoljene konstrukcije. - -Ker mora biti ključna beseda `yield` prisotna v telesu metode, tudi če ta nima podrejenih vozlišč, jo zapišite na naslednji način: - -```php -public function &getIterator(): \Generator -{ - if (false) { - yield; - } -} -``` - - -Prevajalnik prenese .[#toc-compiler-passes] -=========================================== - -Compiler Passes so funkcije, ki spreminjajo AST ali zbirajo informacije v njih. Vrne jih metoda [Extension::getPasses(). |#getPasses] - - -Premikanje vozlišč .[#toc-node-traverser] ------------------------------------------ - -Najpogostejši način za delo z AST je uporaba [api:Latte\Compiler\NodeTraverser]: - -```php -use Latte\Compiler\Node; -use Latte\Compiler\NodeTraverser; - -$ast = (new NodeTraverser)->traverse( - $ast, - enter: fn(Node $node) => ..., - leave: fn(Node $node) => ..., -); -``` - -Funkcija *enter* (tj. obiskovalec) se pokliče ob prvem srečanju z vozliščem, preden se obdelajo njegova podvozja. Funkcija *leave* se pokliče, ko so obiskana vsa podvozja. -Pogost vzorec je, da se funkcija *enter* uporablja za zbiranje nekaterih informacij, nato pa funkcija *leave* na podlagi teh informacij izvede spremembe. Ko se pokliče funkcija *leave*, bo vsa koda v vozlišču že obiskana in zbrane bodo potrebne informacije. - -Kako spremeniti AST? Najlažje je preprosto spremeniti lastnosti vozlišč. Drugi način je, da vozlišče v celoti zamenjamo tako, da vrnemo novo vozlišče. Primer: naslednja koda bo vsa cela števila v AST spremenila v nize (npr. 42 bo spremenjeno v `'42'`). - -```php -use Latte\Compiler\Nodes\Php; - -$ast = (new NodeTraverser)->traverse( - $ast, - leave: function (Node $node) { - if ($node instanceof Php\Scalar\IntegerNode) { - return new Php\Scalar\StringNode((string) $node->value); - } - }, -); -``` - -AST lahko zlahka vsebuje na tisoče vozlišč, premikanje po vseh vozliščih pa je lahko počasno. V nekaterih primerih se je mogoče izogniti celotnemu obhodu. - -Če iščete vse `Html\ElementNode` v drevesu, veste, da ko enkrat vidite `Php\ExpressionNode`, nima smisla preverjati tudi vseh njegovih podrejenih vozlišč, saj HTML ne more biti znotraj v izrazih. V tem primeru lahko potovalniku naročite, naj ne seže v vozlišče razreda: - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Php\ExpressionNode) { - return NodeTraverser::DontTraverseChildren; - } - // ... - }, -); -``` - -Če iščete samo eno določeno vozlišče, lahko po tem, ko ga najdete, tudi v celoti prekinete potovanje. - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Nodes\ParametersNode) { - return NodeTraverser::StopTraversal; - } - // ... - }, -); -``` - - -Pomočniki vozlišč .[#toc-node-helpers] --------------------------------------- - -Razred [api:Latte\Compiler\NodeHelpers] ponuja nekaj metod, s katerimi lahko poiščemo vozlišča AST, ki izpolnjujejo določene povratne klice itd. Prikazanih je nekaj primerov: - -```php -use Latte\Compiler\NodeHelpers; - -// najde vsa vozlišča elementov HTML -$elements = NodeHelpers::find($ast, fn(Node $node) => $node instanceof Nodes\Html\ElementNode); - -// najde prvo vozlišče besedila -$text = NodeHelpers::findFirst($ast, fn(Node $node) => $node instanceof Nodes\TextNode); - -// pretvori vozlišče vrednosti PHP v realno vrednost -$value = NodeHelpers::toValue($node); - -// pretvori statično besedilno vozlišče v niz -$text = NodeHelpers::toText($node); -``` diff --git a/latte/sl/custom-filters.texy b/latte/sl/custom-filters.texy new file mode 100644 index 0000000000..2e4c130eb2 --- /dev/null +++ b/latte/sl/custom-filters.texy @@ -0,0 +1,231 @@ +Ustvarjanje lastnih filtrov +*************************** + +.[perex] +Filtri so zmogljiva orodja za formatiranje in urejanje podatkov neposredno v predlogah Latte. Ponujajo čisto sintakso s simbolom cevi (`|`) za transformacijo spremenljivk ali rezultatov izrazov v želeni izhodni format. + + +Kaj so filtri? +============== + +Filtri v Latte so v bistvu **PHP funkcije, zasnovane posebej za transformacijo vhodne vrednosti v izhodno vrednost**. Uporabljajo se s pomočjo zapisa s cevjo (`|`) znotraj izrazov predloge (`{...}`). + +**Priročnost:** Filtri vam omogočajo enkapsulacijo pogostih nalog formatiranja (kot je formatiranje datumov, sprememba velikosti črk, krajšanje) ali manipulacije s podatki v ponovno uporabne enote. Namesto ponavljanja zapletene PHP kode v vaših predlogah lahko preprosto uporabite filter: +```latte +{* Namesto zapletenega PHP za krajšanje: *} +{$article->text|truncate:100} + +{* Namesto kode za formatiranje datuma: *} +{$event->startTime|date:'Y-m-d H:i'} + +{* Uporaba več transformacij: *} +{$product->name|lower|capitalize} +``` + +**Čitljivost:** Uporaba filtrov naredi predloge preglednejše in bolj osredotočene na predstavitev, saj je transformacijska logika premaknjena v definicijo filtra. + +**Kontekstna občutljivost:** Ključna prednost filtrov v Latte je njihova sposobnost biti [kontekstno občutljivi |#Kontekstni filtri]. To pomeni, da lahko filter prepozna tip vsebine, s katero dela (HTML, JavaScript, navaden tekst itd.), in uporabi ustrezno logiko ali ubežanje znakov, kar je ključno za varnost in pravilnost, še posebej pri generiranju HTML. + +**Integracija z aplikacijsko logiko:** Enako kot lastne funkcije je lahko PHP klicni objekt za filtrom zaprtje (closure), statična metoda ali metoda instance. To omogoča filtrom dostop do aplikacijskih storitev ali podatkov, če je potrebno, čeprav njihov glavni namen ostaja *transformacija vhodne vrednosti*. + +Latte privzeto ponuja bogat nabor [standardnih filtrov |filters]. Lastni filtri vam omogočajo razširitev tega nabora s formatiranjem in transformacijami, specifičnimi za vaš projekt. + +Če potrebujete izvajati logiko, temelječo na *več* vhodih, ali nimate primarne vrednosti za transformacijo, je verjetno primernejše uporabiti [lastno funkcijo |custom-functions]. Če potrebujete generirati zapleten markup ali nadzorovati tok predloge, razmislite o [lastni znački |custom-tags]. + + +Ustvarjanje in registracija filtrov +=================================== + +Obstaja več načinov, kako definirati in registrirati lastne filtre v Latte. + + +Neposredna registracija s pomočjo `addFilter()` +----------------------------------------------- + +Najenostavnejši način za dodajanje filtra je uporaba metode `addFilter()` neposredno na objektu `Latte\Engine`. Določite ime filtra (kot bo uporabljen v predlogi) in ustrezen PHP klicni objekt. + +```php +$latte = new Latte\Engine; + +// Preprost filter brez argumentov +$latte->addFilter('initial', fn(string $s): string => mb_substr($s, 0, 1) . '.'); + +// Filter z neobveznim argumentom +$latte->addFilter('shortify', function (string $s, int $len = 10): string { + return mb_substr($s, 0, $len); +}); + +// Filter, ki obdeluje polja +$latte->addFilter('sum', fn(array $numbers): int|float => array_sum($numbers)); +``` + +**Uporaba v predlogi:** + +```latte +{$name|initial} {* Izpiše 'J.', če je $name 'John' *} +{$description|shortify} {* Uporabi privzeto dolžino 10 *} +{$description|shortify:50} {* Uporabi dolžino 50 *} +{$prices|sum} {* Izpiše vsoto elementov v polju $prices *} +``` + +**Posredovanje argumentov:** + +Vrednost levo od cevi (`|`) je vedno posredovana kot *prvi* argument funkciji filtra. Kakršnikoli parametri, navedeni za dvopičjem (`:`) v predlogi, so posredovani kot naslednji argumenti. + +```latte +{$text|shortify:30} +// Kliče PHP funkcijo shortify($text, 30) +``` + + +Registracija s pomočjo razširitve +--------------------------------- + +Za boljšo organizacijo, še posebej pri ustvarjanju ponovno uporabnih naborov filtrov ali njihovem deljenju kot paketi, je priporočen način registracija znotraj [razširitve Latte |extending-latte#Latte Extension]: + +```php +namespace App\Latte; + +use Latte\Extension; + +class MyLatteExtension extends Extension +{ + public function getFilters(): array + { + return [ + 'initial' => $this->initial(...), + 'shortify' => $this->shortify(...), + ]; + } + + public function initial(string $s): string + { + return mb_substr($s, 0, 1) . '.'; + } + + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// Registracija +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +Ta pristop ohranja logiko vašega filtra enkapsulirano in registracijo enostavno. + + +Uporaba nalagalnika filtrov +--------------------------- + +Latte omogoča registracijo nalagalnika filtrov s pomočjo `addFilterLoader()`. Gre za en sam klicni objekt, ki ga Latte vpraša za katerokoli neznano ime filtra med kompilacijo. Nalagalnik vrne PHP klicni objekt filtra ali `null`. + +```php +$latte = new Latte\Engine; + +// Nalagalnik lahko dinamično ustvarja/pridobiva klicne objekte filtrov +$latte->addFilterLoader(function (string $name): ?callable { + if ($name === 'myLazyFilter') { + // Predstavljajte si tu zahtevno inicializacijo... + $service = get_some_expensive_service(); + return fn($value) => $service->process($value); + } + return null; +}); +``` + +Ta metoda je bila primarno namenjena za leno nalaganje filtrov z zelo **zahtevno inicializacijo**. Vendar sodobne prakse vstavljanja odvisnosti (dependency injection) običajno upravljajo lene storitve učinkoviteje. + +Nalagalniki filtrov dodajajo kompleksnost in se na splošno ne priporočajo v korist neposredne registracije s pomočjo `addFilter()` ali znotraj razširitve s pomočjo `getFilters()`. Uporabljajte nalagalnike samo, če imate resen, specifičen razlog, povezan z zmogljivostnimi težavami pri inicializaciji filtrov, ki jih ni mogoče rešiti drugače. + + +Filtri, ki uporabljajo razred z atributi +---------------------------------------- + +Še en eleganten način za definiranje filtrov je uporaba metod v vašem [razredu parametrov predloge |develop#Parametri kot razred]. Dovolj je dodati atribut `#[Latte\Attributes\TemplateFilter]` k metodi. + +```php +use Latte\Attributes\TemplateFilter; + +class TemplateParameters +{ + public function __construct( + public string $description, + // nadaljnji parametri... + ) {} + + #[TemplateFilter] + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// Posredovanje objekta v predlogo +$params = new TemplateParameters(description: '...'); +$latte->render('template.latte', $params); +``` + +Latte samodejno prepozna in registrira metode, označene s tem atributom, ko je objekt `TemplateParameters` posredovan v predlogo. Ime filtra v predlogi bo enako imenu metode (`shortify` v tem primeru). + +```latte +{* Uporaba filtra, definiranega v razredu parametrov *} +{$description|shortify:50} +``` + + +Kontekstni filtri +================= + +Včasih filter potrebuje več informacij kot samo vhodno vrednost. Morda mora poznati **tip vsebine** niza, s katerim dela (npr. HTML, JavaScript, navaden tekst) ali ga celo spremeniti. To je situacija za kontekstne filtre. + +Kontekstni filter je definiran enako kot običajen filter, vendar mora biti njegov **prvi parameter** tipsko označen kot `Latte\Runtime\FilterInfo`. Latte samodejno prepozna ta podpis in pri klicu filtra posreduje objekt `FilterInfo`. Naslednji parametri prejmejo argumente filtra kot običajno. + +```php +use Latte\Runtime\FilterInfo; +use Latte\ContentType; + +$latte->addFilter('money', function (FilterInfo $info, float $amount): string { + // 1. Preverite vhodni tip vsebine (neobvezno, a priporočljivo) + // Dovolite null (spremenljiv vhod) ali navaden tekst. Zavrnite, če je uporabljen na HTML itd. + if (!in_array($info->contentType, [null, ContentType::Text], true)) { + $actualType = $info->contentType ?? 'mixed'; + throw new \RuntimeException( + "Filter |money uporabljen v nezdružljivem tipu vsebine $actualType. Pričakovan tekst ali null." + ); + } + + // 2. Izvedite transformacijo + $formatted = number_format($amount, 2, '.', ',') . ' EUR'; + $htmlOutput = '' . htmlspecialchars($formatted) . ''; // Zagotovite pravilno ubežanje znakov! + + // 3. Deklarirajte izhodni tip vsebine + $info->contentType = ContentType::Html; + + // 4. Vrnite rezultat + return $htmlOutput; +}); +``` + +`$info->contentType` je nizovna konstanta iz `Latte\ContentType` (npr. `ContentType::Html`, `ContentType::Text`, `ContentType::JavaScript`, itd.) ali `null`, če je filter uporabljen na spremenljivki (`{$var|filter}`). To vrednost lahko **berete**, da preverite vhodni kontekst, in **pišete** vanjo, da deklarirate tip izhodnega konteksta. + +Z nastavitvijo tipa vsebine na HTML sporočate Latte, da je niz, vrnjen s strani vašega filtra, varen HTML. Latte potem na ta rezultat **ne bo** uporabil svojega privzetega samodejnega ubežanja znakov. To je ključno, če vaš filter generira HTML markup. + +.[warning] +Če vaš filter generira HTML, **ste odgovorni za pravilno ubežanje znakov kakršnihkoli vhodnih podatkov**, uporabljenih v tem HTML (kot v primeru klica `htmlspecialchars($formatted)` zgoraj). Opustitev lahko ustvari XSS ranljivosti. Če vaš filter vrača samo navaden tekst, vam ni treba nastavljati `$info->contentType`. + + +Filtri na blokih +---------------- + +Vsi filtri, uporabljeni na [blokih |tags#block] *morajo* biti kontekstni. To je zato, ker ima vsebina bloka definiran tip vsebine (običajno HTML), katerega se mora filter zavedati. + +```latte +{block heading|money}1000{/block} +{* Filter 'money' prejme '1000' kot drugi argument + in $info->contentType bo ContentType::Html *} +``` + +Kontekstni filtri zagotavljajo močan nadzor nad tem, kako so podatki obdelani na podlagi njihovega konteksta, omogočajo napredne funkcije in zagotavljajo pravilno vedenje ubežanja znakov, še posebej pri generiranju HTML vsebine. diff --git a/latte/sl/custom-functions.texy b/latte/sl/custom-functions.texy new file mode 100644 index 0000000000..fd9d36e690 --- /dev/null +++ b/latte/sl/custom-functions.texy @@ -0,0 +1,144 @@ +Ustvarjanje lastnih funkcij +*************************** + +.[perex] +Enostavno dodajte v predloge Latte lastne pomožne funkcije. Kličite PHP logiko neposredno v izrazih za izračune, dostop do storitev ali generiranje dinamične vsebine, kar ohranja vaše predloge čiste in zmogljive. + + +Kaj so funkcije? +================ + +Funkcije v Latte vam omogočajo razširitev nabora funkcij, ki jih je mogoče klicati znotraj izrazov v predlogah (`{...}`). Lahko si jih predstavljate kot **lastne PHP funkcije, dostopne samo znotraj vaših Latte predlog**. To prinaša več prednosti: + +**Priročnost:** Lahko definirate pomožno logiko (kot izračune, formatiranje ali dostop do podatkov aplikacije) in jo kličete s preprosto, znano sintakso funkcij neposredno v predlogi, enako kot bi klicali `strlen()` ali `date()` v PHP. + +```latte +{var $userInitials = initials($userName)} {* npr. 'J. D.' *} + +{if hasPermission('article', 'edit')} + Uredi +{/if} +``` + +**Brez onesnaževanja globalnega prostora:** Za razliko od definiranja dejanske globalne funkcije v PHP obstajajo funkcije Latte samo v kontekstu izrisovanja predloge. Ni vam treba obremenjevati globalnega imenskega prostora PHP s pomočniki, ki so specifični samo za predloge. + +**Integracija z logiko aplikacije:** PHP klicni objekt, ki stoji za funkcijo Latte, je lahko karkoli – anonimna funkcija, statična metoda ali instančna metoda. To pomeni, da lahko vaše funkcije v predlogah enostavno dostopajo do aplikacijskih storitev, podatkovnih baz, konfiguracije ali katerekoli druge potrebne logike z zajemanjem spremenljivk (v primeru anonimnih funkcij) ali s pomočjo dependency injection (v primeru objektov). Zgornji primer `hasPermission` to jasno prikazuje, ko verjetno v ozadju kliče avtorizacijsko storitev. + +**Prepisovanje nativnih funkcij (neobvezno):** Lahko celo definirate funkcijo Latte z enakim imenom kot nativna PHP funkcija. V predlogi bo namesto prvotne funkcije klicana vaša lastna različica. To je lahko uporabno za zagotavljanje vedenja, specifičnega za predlogo, ali zagotavljanje dosledne obdelave (npr. zagotavljanje, da bo `strlen` vedno večbajtno varen). To funkcijo uporabljajte previdno, da preprečite nesporazume. + +Privzeto Latte omogoča klicanje *vseh* nativnih PHP funkcij (če niso omejene s [Peskovnikom |sandbox]). Lastne funkcije razširjajo to vgrajeno knjižnico s specifičnimi potrebami vašega projekta. + +Če samo transformirate eno samo vrednost, je morda primernejše uporabiti [lasten filter |custom-filters]. + + +Ustvarjanje in registracija funkcij +=================================== + +Podobno kot pri filtrih obstaja več načinov, kako definirati in registrirati lastne funkcije. + + +Neposredna registracija s pomočjo `addFunction()` +------------------------------------------------- + +Najenostavnejša metoda je uporaba `addFunction()` na objektu `Latte\Engine`. Določite ime funkcije (kot se bo prikazovalo v predlogi) in ustrezen PHP klicni objekt. + +```php +$latte = new Latte\Engine; + +// Preprosta pomožna funkcija +$latte->addFunction('initials', function (string $name): string { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; +}); +``` + +**Uporaba v predlogi:** + +```latte +{var $userInitials = initials($userName)} +``` + +Argumenti funkcije v predlogi so posredovani neposredno PHP klicnemu objektu v istem vrstnem redu. PHP funkcionalnosti kot so tipski namigi, privzete vrednosti in variabilni parametri (`...`) delujejo po pričakovanjih. + + +Registracija s pomočjo razširitve +--------------------------------- + +Za boljšo organizacijo in ponovno uporabnost registrirajte funkcije znotraj [razširitve Latte |extending-latte#Latte Extension]. Ta pristop je priporočen za bolj zapletene aplikacije ali deljene knjižnice. + +```php +namespace App\Latte; + +use Latte\Extension; +use Nette\Security\Authorizator; + +class MyLatteExtension extends Extension +{ + public function __construct( + // Predpostavljamo, da storitev Authorizator obstaja + private Authorizator $authorizator, + ) { + } + + public function getFunctions(): array + { + // Registracija metod kot Latte funkcij + return [ + 'hasPermission' => $this->hasPermission(...), + ]; + } + + public function hasPermission(string $resource, string $action): bool + { + return $this->authorizator->isAllowed($resource, $action); + } +} + +// Registracija (predpostavljamo, da $container vsebuje DI vsebnik) +$extension = $container->getByType(App\Latte\MyLatteExtension::class); +$latte = new Latte\Engine; +$latte->addExtension($extension); +``` + +Ta pristop nazorno prikazuje, kako so lahko funkcije, definirane v Latte, podprte z metodami objektov, ki imajo lahko svoje lastne odvisnosti, upravljane s strani DI vsebnika vaše aplikacije ali tovarne. To ohranja logiko vaših predlog povezano z jedrom aplikacije in hkrati ohranja pregledno organizacijo. + + +Funkcije, ki uporabljajo razred z atributi +------------------------------------------ + +Enako kot filtri, funkcije lahko definiramo kot metode v vašem [razredu parametrov predloge |develop#Parametri kot razred] s pomočjo atributa `#[Latte\Attributes\TemplateFunction]`. + +```php +use Latte\Attributes\TemplateFunction; + +class TemplateParameters +{ + public function __construct( + public string $userName, + // nadaljnji parametri... + ) {} + + // Ta metoda bo dostopna kot {initials(...)} v predlogi + #[TemplateFunction] + public function initials(string $name): string + { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; + } +} + +// Posredovanje objekta v predlogo +$params = new TemplateParameters(userName: 'John Doe', /* ... */); +$latte->render('template.latte', $params); +``` + +Latte samodejno odkrije in registrira metode, označene s tem atributom, ko je objekt parametrov posredovan v predlogo. Ime funkcije v predlogi ustreza imenu metode. + +```latte +{* Uporaba funkcije, definirane v razredu parametrov *} +{var $inits = initials($userName)} +``` + +**Kontekstne funkcije?** + +Za razliko od filtrov ne obstaja neposreden koncept "kontekstnih funkcij", ki bi prejele objekt, podoben `FilterInfo`. Funkcije delujejo znotraj izrazov in tipično ne potrebujejo neposrednega dostopa do konteksta izrisovanja ali informacij o tipu vsebine na enak način kot filtri, uporabljeni na blokih. diff --git a/latte/sl/custom-tags.texy b/latte/sl/custom-tags.texy new file mode 100644 index 0000000000..1066b5eb86 --- /dev/null +++ b/latte/sl/custom-tags.texy @@ -0,0 +1,1135 @@ +Ustvarjanje lastnih značk +************************* + +.[perex] +Ta stran ponuja obsežen vodnik za ustvarjanje lastnih značk v Latte. Obravnavali bomo vse od preprostih značk do bolj zapletenih scenarijev z gnezdeno vsebino in specifičnimi potrebami razčlenjevanja, pri čemer bomo gradili na vašem razumevanju, kako Latte prevaja predloge. + +Lastne značke zagotavljajo najvišjo raven nadzora nad sintakso predloge in logiko izrisovanja, vendar so tudi najbolj zapletena točka razširitve. Preden se odločite ustvariti lastno značko, vedno razmislite, ali [ne obstaja enostavnejša rešitev |extending-latte#Načini razširitve Latte] ali če ustrezna značka že ne obstaja v [standardnem naboru |tags]. Lastne značke uporabljajte le, če enostavnejše alternative ne zadostujejo za vaše potrebe. + + +Razumevanje postopka prevajanja +=============================== + +Za učinkovito ustvarjanje lastnih značk je koristno pojasniti, kako Latte obdeluje predloge. Razumevanje tega postopka pojasnjuje, zakaj so značke strukturirane ravno tako in kako se prilegajo širšemu kontekstu. + +Prevajanje predloge v Latte, poenostavljeno, vključuje te ključne korake: + +1. **Leksična analiza:** Lekser bere izvorno kodo predloge (datoteko `.latte`) in jo razdeli na zaporedje majhnih, ločenih delov, imenovanih **žetoni** (npr. `{`, `foreach`, `$variable`, `}`, besedilo HTML itd.). +2. **Razčlenjevanje:** Razčlenjevalnik vzame ta tok žetonov in iz njega zgradi smiselno drevesno strukturo, ki predstavlja logiko in vsebino predloge. To drevo se imenuje **abstraktno sintaktično drevo (AST)**. +3. **Prevajalski prehodi:** Pred generiranjem PHP kode Latte zažene [prevajalske prehode |compiler-passes]. To so funkcije, ki prehajajo celoten AST in ga lahko spreminjajo ali zbirajo informacije. Ta korak je ključen za funkcije, kot sta varnost ([Sandbox |sandbox]) ali optimizacija. +4. **Generiranje kode:** Na koncu prevajalnik preide (potencialno spremenjen) AST in generira ustrezno kodo PHP razreda. Ta PHP koda je tisto, kar dejansko izriše predlogo ob zagonu. +5. **Predpomnjenje:** Generirana PHP koda se shrani na disk, kar naredi nadaljnja izrisovanja zelo hitra, saj so koraki 1-4 preskočeni. + +V resnici je prevajanje nekoliko bolj zapleteno. Latte **ima dva** lekserja in razčlenjevalnika: enega za HTML predlogo in drugega za PHP-podobno kodo znotraj značk. Prav tako se razčlenjevanje ne zgodi šele po tokenizaciji, ampak lekser in razčlenjevalnik tečeta vzporedno v dveh "nitih" in se usklajujeta. Verjemite mi, programiranje tega je bila raketna znanost :-) + +Celoten postopek, od nalaganja vsebine predloge, preko razčlenjevanja, do generiranja končne datoteke, je mogoče zaporedno izvesti s to kodo, s katero lahko eksperimentirate in izpisujete vmesne rezultate: + +```php +$latte = new Latte\Engine; +$source = $latte->getLoader()->getContent($file); +$ast = $latte->parse($source); +$latte->applyPasses($ast); +$code = $latte->generate($ast, $file); +``` + + +Anatomija značke +================ + +Ustvarjanje popolnoma delujoče lastne značke v Latte vključuje več medsebojno povezanih delov. Preden se lotimo implementacije, razumimo osnovne koncepte in terminologijo z uporabo analogije s HTML in Document Object Model (DOM). + + +Značke proti Vozliščem (Analogija s HTML) +----------------------------------------- + +V HTML pišemo **oznake** kot `

                                                                                                                    ` ali `

                                                                                                                    ...
                                                                                                                    `. Te oznake so sintaksa v izvorni kodi. Ko brskalnik razčleni ta HTML, ustvari pomnilniško predstavitev, imenovano **Document Object Model (DOM)**. V DOM so HTML oznake predstavljene z **vozlišči** (natančneje vozlišči `Element` v terminologiji JavaScript DOM). S temi *vozlišči* programsko delamo (npr. z JavaScript `document.getElementById(...)` se vrne vozlišče Element). Oznaka je samo besedilna predstavitev v izvorni datoteki; vozlišče je objektna predstavitev v logičnem drevesu. + +Latte deluje podobno: + +- V datoteki `.latte` predloge pišete **Latte značke**, kot sta `{foreach ...}` in `{/foreach}`. To je sintaksa, s katero delate kot avtor predloge. +- Ko Latte **razčleni** predlogo, zgradi **Abstract Syntax Tree (AST)**. To drevo je sestavljeno iz **vozlišč**. Vsaka Latte značka, HTML element, kos besedila ali izraz v predlogi postane eno ali več vozlišč v tem drevesu. +- Osnovni razred za vsa vozlišča v AST je `Latte\Compiler\Node`. Tako kot ima DOM različne vrste vozlišč (Element, Text, Comment), ima AST Latte različne vrste vozlišč. Srečali se boste z `Latte\Compiler\Nodes\TextNode` za statično besedilo, `Latte\Compiler\Nodes\Html\ElementNode` za HTML elemente, `Latte\Compiler\Nodes\Php\ExpressionNode` za izraze znotraj značk in ključno za lastne značke, vozlišči, ki dedujejo iz `Latte\Compiler\Nodes\StatementNode`. + + +Zakaj `StatementNode`? +---------------------- + +HTML elementi (`Html\ElementNode`) primarno predstavljajo strukturo in vsebino. PHP izrazi (`Php\ExpressionNode`) predstavljajo vrednosti ali izračune. Kaj pa Latte značke kot `{if}`, `{foreach}` ali naša lastna `{datetime}`? Te značke *izvajajo dejanja*, nadzorujejo tok programa ali generirajo izpis na podlagi logike. So funkcionalne enote, ki naredijo Latte močan mehanizem za predloge, ne le označevalni jezik. + +V programiranju se takšne enote, ki izvajajo dejanja, pogosto imenujejo "statements" (stavki). Zato vozlišča, ki predstavljajo te funkcionalne Latte značke, tipično dedujejo iz `Latte\Compiler\Nodes\StatementNode`. To jih loči od čisto strukturnih vozlišč (kot so HTML elementi) ali vozlišč, ki predstavljajo vrednosti (kot so izrazi). + + +Ključne komponente +================== + +Poglejmo glavne komponente, potrebne za ustvarjanje lastne značke: + + +Funkcija za razčlenjevanje značke +--------------------------------- + +- Ta PHP klicna funkcija (callable) razčleni sintakso Latte značke (`{...}`) v izvorni predlogi. +- Prejme informacije o znački (kot so njeno ime, položaj in ali gre za n:atribut) preko objekta [api:Latte\Compiler\Tag]. +- Njeno primarno orodje za razčlenjevanje argumentov in izrazov znotraj ločil značke je objekt [api:Latte\Compiler\TagParser], dostopen preko `$tag->parser` (to je drug razčlenjevalnik kot tisti, ki razčleni celotno predlogo). +- Za parne značke uporablja `yield` za signaliziranje Latteju, naj razčleni notranjo vsebino med začetno in končno značko. +- Končni cilj funkcije za razčlenjevanje je ustvariti in vrniti instanco **razreda vozlišča**, ki je dodana v AST. +- Navada je (čeprav ni zahtevano) implementirati funkcijo za razčlenjevanje kot statično metodo (pogosto imenovano `create`) neposredno v ustreznem razredu vozlišča. To ohranja logiko razčlenjevanja in predstavitev vozlišča lepo v enem paketu, omogoča dostop do zasebnih/zaščitenih elementov razreda, če je potrebno, in izboljšuje organizacijo. + + +Razred vozlišča +--------------- + +- Predstavlja *logično funkcijo* vaše značke v **Abstract Syntax Tree (AST)**. +- Vsebuje razčlenjene informacije (kot so argumenti ali vsebina) kot javne lastnosti. Te lastnosti pogosto vsebujejo druge instance `Node` (npr. `ExpressionNode` za razčlenjene argumente, `AreaNode` za razčlenjeno vsebino). +- Metoda `print(PrintContext $context): string` generira *PHP kodo* (stavek ali serijo stavkov), ki izvaja dejanje značke med izrisovanjem predloge. +- Metoda `getIterator(): \Generator` omogoča dostop do podrejenih vozlišč (argumentov, vsebine) za prehod **prevajalskih prehodov**. Mora zagotavljati reference (`&`), da omogoči prehodom potencialno spreminjanje ali zamenjavo podvozlišč. +- Ko je celotna predloga razčlenjena v AST, Latte zažene vrsto [prevajalskih prehodov |compiler-passes]. Ti prehodi prehajajo *celoten* AST z uporabo metode `getIterator()`, ki jo zagotavlja vsako vozlišče. Lahko pregledujejo vozlišča, zbirajo informacije in celo *spreminjajo* drevo (npr. s spreminjanjem javnih lastnosti vozlišč ali popolno zamenjavo vozlišč). Ta zasnova, ki zahteva celovit `getIterator()`, je temeljna. Omogoča močnim funkcijam, kot je [Sandbox |sandbox], da analizirajo in potencialno spremenijo obnašanje *katerega koli* dela predloge, vključno z vašimi lastnimi značkami, kar zagotavlja varnost in doslednost. + + +Registracija preko razširitve +----------------------------- + +- Latte morate obvestiti o vaši novi znački in katera funkcija za razčlenjevanje naj se zanjo uporabi. To se zgodi znotraj [razširitve Latte |extending-latte#Latte Extension]. +- Znotraj vašega razreda razširitve implementirate metodo `getTags(): array`. Ta metoda vrne asociativno polje, kjer so ključi imena značk (npr. `'mytag'`, `'n:myattribute'`), vrednosti pa so PHP klicne funkcije (callable), ki predstavljajo njihove ustrezne funkcije za razčlenjevanje (npr. `MyNamespace\DatetimeNode::create(...)`). + +Povzetek: **Funkcija za razčlenjevanje značke** pretvori *izvorno kodo predloge* vaše značke v **vozlišče AST**. **Razred vozlišča** nato zna pretvoriti *samega sebe* v izvedljivo *PHP kodo* za prevedeno predlogo in omogoča dostop do svojih podvozlišč za **prevajalske prehode** preko `getIterator()`. **Registracija preko razširitve** poveže ime značke s funkcijo za razčlenjevanje in o njej obvesti Latte. + +Zdaj bomo raziskali, kako implementirati te komponente korak za korakom. + + +Ustvarjanje preproste značke +============================ + +Lotimo se ustvarjanja vaše prve lastne Latte značke. Začeli bomo z zelo preprostim primerom: značko z imenom `{datetime}`, ki izpiše trenutni datum in čas. **Sprva ta značka ne bo sprejemala nobenih argumentov**, vendar jo bomo izboljšali kasneje v razdelku [#Razčlenjevanje argumentov značke]. Prav tako nima nobene notranje vsebine. + +Ta primer vas bo vodil skozi osnovne korake: definiranje razreda vozlišča, implementacija njegovih metod `print()` in `getIterator()`, ustvarjanje funkcije za razčlenjevanje in končno registracija značke. + +**Cilj:** Implementirati `{datetime}` za izpis trenutnega datuma in časa z uporabo PHP funkcije `date()`. + + +Ustvarjanje razreda vozlišča +---------------------------- + +Najprej potrebujemo razred, ki bo predstavljal našo značko v Abstract Syntax Tree (AST). Kot smo že omenili, dedujemo iz `Latte\Compiler\Nodes\StatementNode`. + +Ustvarite datoteko (npr. `DatetimeNode.php`) in definirajte razred: + +```php .{file: DatetimeNode.php} +node = new self; + return $node; + } + + /** + * Generira PHP kodo, ki se bo izvedla pri izrisovanju predloge. + */ + public function print(PrintContext $context): string + { + return $context->format( + 'echo date(\'Y-m-d H:i:s\') %line;', + $this->position, + ); + } + + /** + * Omogoča dostop do podrejenih vozlišč za prevajalske prehode Latte. + */ + public function &getIterator(): \Generator + { + false && yield; + } +} +``` + +Ko Latte naleti na `{datetime}` v predlogi, pokliče funkcijo za razčlenjevanje `create()`. Njena naloga je vrniti instanco `DatetimeNode`. + +Metoda `print()` generira PHP kodo, ki se bo izvedla pri izrisovanju predloge. Kličemo metodo `$context->format()`, ki sestavi končni niz PHP kode za prevedeno predlogo. Prvi argument, `'echo date('Y-m-d H:i:s') %line;'`, je maska, v katero se dopolnijo naslednji parametri. Nadomestni znak `%line` pove metodi `format()`, naj uporabi drugi argument, ki je `$this->position`, in vstavi komentar kot `/* line 15 */`, ki povezuje generirano PHP kodo nazaj na izvirno vrstico predloge, kar je ključno za razhroščevanje. + +Lastnost `$this->position` je podedovana iz osnovnega razreda `Node` in jo samodejno nastavi razčlenjevalnik Latte. Vsebuje objekt [api:Latte\Compiler\Position], ki označuje, kje je bila značka najdena v izvorni datoteki `.latte`. + +Metoda `getIterator()` je temeljna za prevajalske prehode. Mora zagotoviti vsa podrejena vozlišča, vendar naš preprost `DatetimeNode` trenutno nima nobenih argumentov ali vsebine, torej nobenih podrejenih vozlišč. Kljub temu mora metoda še vedno obstajati in biti generator, tj. ključna beseda `yield` mora biti nekako prisotna v telesu metode. + + +Registracija preko razširitve +----------------------------- + +Končno obvestimo Latte o novi znački. Ustvarite [razred razširitve |extending-latte#Latte Extension] (npr. `MyLatteExtension.php`) in registrirajte značko v njeni metodi `getTags()`. + +```php .{file: MyLatteExtension.php} + Mapa: 'ime-značke' => funkcija-za-razčlenjevanje + */ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + // Kasneje tukaj registrirajte več značk + ]; + } +} +``` + +Nato registrirajte to razširitev v Latte Engine: + +```php +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +Ustvarite predlogo: + +```latte +

                                                                                                                    Stran generirana: {datetime}

                                                                                                                    +``` + +Pričakovan izpis: `

                                                                                                                    Stran generirana: 2023-10-27 11:00:00

                                                                                                                    ` + + +Povzetek te faze +---------------- + +Uspešno smo ustvarili osnovno lastno značko `{datetime}`. Definirali smo njeno predstavitev v AST (`DatetimeNode`), obdelali njeno razčlenjevanje (`create()`), določili, kako naj generira PHP kodo (`print()`), zagotovili, da so njeni otroci dostopni za prehod (`getIterator()`), in jo registrirali v Latte. + +V naslednjem razdelku bomo to značko izboljšali tako, da bo sprejemala argumente, in pokazali, kako razčlenjevati izraze ter upravljati podrejena vozlišča. + + +Razčlenjevanje argumentov značke +================================ + +Naša preprosta značka `{datetime}` deluje, vendar ni zelo prilagodljiva. Izboljšajmo jo, da bo sprejemala neobvezen argument: formatni niz za funkcijo `date()`. Zahtevana sintaksa bo `{datetime $format}`. + +**Cilj:** Prilagoditi `{datetime}` tako, da sprejema neobvezen PHP izraz kot argument, ki bo uporabljen kot formatni niz za `date()`. + + +Predstavitev `TagParser` +------------------------ + +Preden prilagodimo kodo, je pomembno razumeti orodje, ki ga bomo uporabljali: [api:Latte\Compiler\TagParser]. Ko glavni razčlenjevalnik Latte (`TemplateParser`) naleti na Latte značko, kot je `{datetime ...}` ali n:atribut, prenese razčlenjevanje vsebine *znotraj* značke (del med `{` in `}` ali vrednost atributa) na specializiran `TagParser`. + +Ta `TagParser` deluje izključno z **argumenti značke**. Njegova naloga je obdelati žetone, ki predstavljajo te argumente. Ključno je, da **mora obdelati celotno vsebino**, ki mu je na voljo. Če se vaša funkcija za razčlenjevanje konča, vendar `TagParser` ni dosegel konca argumentov (preverjeno preko `$tag->parser->isEnd()`), bo Latte vrgel izjemo, saj to kaže, da so znotraj značke ostali nepričakovani žetoni. Nasprotno, če značka *zahteva* argumente, morate na začetku vaše funkcije za razčlenjevanje poklicati `$tag->expectArguments()`. Ta metoda preveri, ali so argumenti prisotni, in vrže koristno izjemo, če je bila značka uporabljena brez kakršnih koli argumentov. + +`TagParser` ponuja uporabne metode za razčlenjevanje različnih vrst argumentov: + +- `parseExpression(): ExpressionNode`: Razčleni PHP-podoben izraz (spremenljivke, literale, operatorje, klice funkcij/metod itd.). Obravnava sintaktični sladkor Latte, kot je na primer obravnavanje preprostih alfanumeričnih nizov kot nizov v narekovajih (npr. `foo` se razčleni, kot da bi bilo `'foo'`). +- `parseUnquotedStringOrExpression(): ExpressionNode`: Razčleni bodisi standardni izraz bodisi *nenarekovajni niz*. Nenarekovajni nizi so zaporedja, ki jih Latte dovoljuje brez narekovajev, pogosto uporabljena za stvari, kot so poti do datotek (npr. `{include ../file.latte}`). Če razčleni nenarekovajni niz, vrne `StringNode`. +- `parseArguments(): ArrayNode`: Razčleni argumente, ločene z vejicami, potencialno s ključi, kot `10, name: 'John', true`. +- `parseModifier(): ModifierNode`: Razčleni filtre kot `|upper|truncate:10`. +- `parseType(): ?SuperiorTypeNode`: Razčleni PHP namige tipov kot `int`, `?string`, `array|Foo`. + +Za bolj zapletene ali nižje ravni potreb po razčlenjevanju lahko neposredno komunicirate s [tokom žetonov |api:Latte\Compiler\TokenStream] preko `$tag->parser->stream`. Ta objekt ponuja metode za preverjanje in obdelavo posameznih žetonov: + +- `$tag->parser->stream->is(...): bool`: Preveri, ali *trenutni* žeton ustreza kateri od določenih vrst (npr. `Token::Php_Variable`) ali literalnih vrednosti (npr. `'as'`), ne da bi ga porabil. Uporabno za pogled naprej. +- `$tag->parser->stream->consume(...): Token`: Porabi *trenutni* žeton in premakne položaj toka naprej. Če so kot argumenti podane pričakovane vrste/vrednosti žetonov in trenutni žeton ne ustreza, vrže `CompileException`. Uporabite to, ko *pričakujete* določen žeton. +- `$tag->parser->stream->tryConsume(...): ?Token`: Poskusi porabiti *trenutni* žeton *samo če* ustreza eni od določenih vrst/vrednosti. Če ustreza, porabi žeton in ga vrne. Če ne ustreza, pusti položaj toka nespremenjen in vrne `null`. Uporabite to za neobvezne žetone ali ko izbirate med različnimi sintaktičnimi potmi. + + +Posodobitev funkcije za razčlenjevanje `create()` +------------------------------------------------- + +S tem razumevanjem prilagodimo metodo `create()` v `DatetimeNode` tako, da razčleni neobvezen formatni argument z uporabo `$tag->parser`. + +```php +node = new self; + + // Preverimo, ali obstajajo kakšni žetoni + if (!$tag->parser->isEnd()) { + // Razčlenimo argument kot PHP-podoben izraz z uporabo TagParser. + $node->format = $tag->parser->parseExpression(); + } + + return $node; + } + + // ... metodi print() in getIterator() bosta posodobljeni kasneje ... +} +``` + +Dodali smo javno lastnost `$format`. V `create()` zdaj uporabljamo `$tag->parser->isEnd()` za preverjanje, ali *obstajajo* argumenti. Če obstajajo, `$tag->parser->parseExpression()` obdela žetone za izraz. Ker mora `TagParser` obdelati vse vhodne žetone, bo Latte samodejno vrgel napako, če uporabnik napiše nekaj nepričakovanega za formatnim izrazom (npr. `{datetime 'Y-m-d', unexpected}`). + + +Posodobitev metode `print()` +---------------------------- + +Zdaj prilagodimo metodo `print()` tako, da uporablja razčlenjen formatni izraz, shranjen v `$this->format`. Če format ni bil podan (`$this->format` je `null`), moramo uporabiti privzeti formatni niz, na primer `'Y-m-d H:i:s'`. + +```php + public function print(PrintContext $context): string + { + $formatNode = $this->format ?? new StringNode('Y-m-d H:i:s'); + + // %node izpiše PHP kodno predstavitev $formatNode. + return $context->format( + 'echo date(%node) %line;', + $formatNode, + $this->position + ); + } +``` + +V spremenljivko `$formatNode` shranimo vozlišče AST, ki predstavlja formatni niz za PHP funkcijo `date()`. Tukaj uporabljamo operator ničnega združevanja (`??`). Če je uporabnik podal argument v predlogi (npr. `{datetime 'd.m.Y'}`), potem lastnost `$this->format` vsebuje ustrezno vozlišče (v tem primeru `StringNode` z vrednostjo `'d.m.Y'`), in to vozlišče se uporabi. Če uporabnik ni podal argumenta (napisal je samo `{datetime}`), je lastnost `$this->format` `null`, in namesto tega ustvarimo nov `StringNode` s privzetim formatom `'Y-m-d H:i:s'`. To zagotavlja, da `$formatNode` vedno vsebuje veljavno vozlišče AST za format. + +V maski `'echo date(%node) %line;'` je uporabljen nov nadomestni znak `%node`, ki pove metodi `format()`, naj vzame prvi naslednji argument (kar je naš `$formatNode`), pokliče njegovo metodo `print()` (ki vrne njegovo PHP kodno predstavitev) in vstavi rezultat na mesto nadomestnega znaka. + + +Implementacija `getIterator()` za podvozlišča +--------------------------------------------- + +Naš `DatetimeNode` ima zdaj podrejeno vozlišče: izraz `$format`. **Moramo** to podrejeno vozlišče omogočiti dostop prevajalskim prehodom tako, da ga zagotovimo v metodi `getIterator()`. Ne pozabite zagotoviti *reference* (`&`), da omogočite prehodom potencialno zamenjavo vozlišča. + +```php + public function &getIterator(): \Generator + { + if ($this->format) { + yield $this->format; + } + } +``` + +Zakaj je to ključno? Predstavljajte si prehod Sandbox, ki mora preveriti, ali argument `$format` ne vsebuje prepovedanega klica funkcije (npr. `{datetime dangerousFunction()}`). Če `getIterator()` ne zagotovi `$this->format`, prehod Sandbox nikoli ne bi videl klica `dangerousFunction()` znotraj argumenta naše značke, kar bi ustvarilo potencialno varnostno luknjo. Z zagotavljanjem mu omogočimo Sandboxu (in drugim prehodom), da preverijo in potencialno spremenijo vozlišče izraza `$format`. + + +Uporaba izboljšane značke +------------------------- + +Značka zdaj pravilno obravnava neobvezen argument: + +```latte +Privzeti format: {datetime} +Lastni format: {datetime 'd.m.Y'} +Uporaba spremenljivke: {datetime $userDateFormatPreference} + +{* To bi povzročilo napako po razčlenjevanju 'd.m.Y', ker je ", foo" nepričakovano *} +{* {datetime 'd.m.Y', foo} *} +``` + +Nato si bomo ogledali ustvarjanje parnih značk, ki obdelujejo vsebino med njimi. + + +Obravnavanje parnih značk +========================= + +Do sedaj je bila naša značka `{datetime}` *samozapirajoča* (konceptualno). Nima nobene vsebine med začetno in končno značko. Vendar pa veliko uporabnih značk deluje z blokom vsebine predloge. Te se imenujejo **parne značke**. Primeri vključujejo `{if}...{/if}`, `{block}...{/block}` ali lastno značko, ki jo bomo zdaj ustvarili: `{debug}...{/debug}`. + +Ta značka nam bo omogočila vključitev informacij za razhroščevanje v naše predloge, ki naj bi bile vidne samo med razvojem. + +**Cilj:** Ustvariti parno značko `{debug}`, katere vsebina se izriše samo, če je aktivna specifična zastavica "razvojnega načina". + + +Predstavitev ponudnikov +----------------------- + +Včasih vaše značke potrebujejo dostop do podatkov ali storitev, ki niso posredovane neposredno kot parametri predloge. Na primer, določanje, ali je aplikacija v razvojnem načinu, dostop do objekta uporabnika ali pridobivanje konfiguracijskih vrednosti. Latte zagotavlja mehanizem, imenovan **ponudniki** (Providers) za ta namen. + +Ponudniki so registrirani v vaši [razširitvi |extending-latte#Latte Extension] z uporabo metode `getProviders()`. Ta metoda vrne asociativno polje, kjer so ključi imena, pod katerimi bodo ponudniki dostopni v izvajalni kodi predloge, vrednosti pa so dejanski podatki ali objekti. + +Znotraj PHP kode, ki jo generira metoda `print()` vaše značke, lahko do teh ponudnikov dostopate preko posebne lastnosti objekta `$this->global`. Ker je ta lastnost deljena med vsemi razširitvami, je dobra praksa **predpona imen vaših ponudnikov** za preprečevanje potencialnih kolizij imen s ključnimi ponudniki Latte ali ponudniki iz drugih razširitev tretjih oseb. Običajna konvencija je uporaba kratke, edinstvene predpone, povezane z vašim proizvajalcem ali imenom razširitve. Za naš primer bomo uporabili predpono `app` in zastavica razvojnega načina bo dostopna kot `$this->global->appDevMode`. + + +Ključna beseda `yield` za razčlenjevanje vsebine +------------------------------------------------ + +Kako povemo razčlenjevalniku Latte, naj obdela vsebino *med* `{debug}` in `{/debug}`? Tukaj pride v poštev ključna beseda `yield`. + +Ko se `yield` uporabi v funkciji `create()`, funkcija postane [PHP generator |https://www.php.net/manual/en/language.generators.overview.php]. Njegovo izvajanje se zaustavi in nadzor se vrne glavnemu `TemplateParser`. `TemplateParser` nato nadaljuje z razčlenjevanjem vsebine predloge, *dokler* ne naleti na ustrezno zapiralno značko (`{/debug}` v našem primeru). + +Ko je najdena zapiralna značka, `TemplateParser` nadaljuje izvajanje naše funkcije `create()` takoj za stavkom `yield`. Vrednost, ki jo *vrne* stavek `yield`, je polje, ki vsebuje dva elementa: + +1. `AreaNode`, ki predstavlja razčlenjeno vsebino med začetno in končno značko. +2. Objekt `Tag`, ki predstavlja zapiralno značko (npr. `{/debug}`). + +Ustvarimo razred `DebugNode` in njegovo metodo `create`, ki uporablja `yield`. + +```php .{file: DebugNode.php} +node = new self; + + // Zaustavi razčlenjevanje, pridobi notranjo vsebino in končno značko, ko je najden {/debug} + [$node->content, $endTag] = yield; + + return $node; + } + + // ... print() in getIterator() bosta implementirani kasneje ... +} +``` + +Opomba: `$endTag` je `null`, če je značka uporabljena kot n:atribut, tj. `
                                                                                                                    ...
                                                                                                                    `. + + +Implementacija `print()` za pogojno izrisovanje +----------------------------------------------- + +Metoda `print()` mora zdaj generirati PHP kodo, ki ob izvajanju preveri ponudnika `appDevMode` in izvede kodo za notranjo vsebino le, če je zastavica true. + +```php + public function print(PrintContext $context): string + { + // Generira PHP stavek 'if', ki ob izvajanju preveri ponudnika + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + // Če je v razvojnem načinu, izpiše notranjo vsebino + %node + } + + XX, + $this->position, // Za %line komentar + $this->content, // Vozlišče, ki vsebuje AST notranje vsebine + ); + } +``` + +To je preprosto. Uporabljamo `PrintContext::format()` za ustvarjanje standardnega PHP stavka `if`. Znotraj `if` postavimo nadomestni znak `%node` za `$this->content`. Latte rekurzivno pokliče `$this->content->print($context)` za generiranje PHP kode za notranji del značke, vendar le, če `$this->global->appDevMode` ob izvajanju vrne true. + + +Implementacija `getIterator()` za vsebino +----------------------------------------- + +Tako kot pri vozlišču argumenta v prejšnjem primeru, ima naš `DebugNode` zdaj podrejeno vozlišče: `AreaNode $content`. Moramo ga omogočiti dostop tako, da ga zagotovimo v `getIterator()`: + +```php + public function &getIterator(): \Generator + { + // Zagotavlja referenco na vozlišče vsebine + yield $this->content; + } +``` + +To omogoča prevajalskim prehodom, da se spustijo v vsebino naše značke `{debug}`, kar je pomembno, tudi če je vsebina pogojno izrisana. Na primer, Sandbox mora analizirati vsebino ne glede na to, ali je `appDevMode` true ali false. + + +Registracija in uporaba +----------------------- + +Registrirajte značko in ponudnika v vaši razširitvi: + +```php .{file: MyLatteExtension.php} +class MyLatteExtension extends Extension +{ + // Predpostavljamo, da je $isDevelopmentMode določen nekje (npr. iz konfiguracije) + public function __construct( + private bool $isDevelopmentMode, + ) { + } + + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), // Registracija nove značke + ]; + } + + public function getProviders(): array + { + return [ + 'appDevMode' => $this->isDevelopmentMode, // Registracija ponudnika + ]; + } +} + +// Pri registraciji razširitve: +$isDev = true; // Določite to na podlagi okolja vaše aplikacije +$latte->addExtension(new App\Latte\MyLatteExtension($isDev)); +``` + +In njegova uporaba v predlogi: + +```latte +

                                                                                                                    Običajna vsebina, vedno vidna.

                                                                                                                    + +{debug} +
                                                                                                                    + ID trenutnega uporabnika: {$user->id} + Čas zahteve: {=time()} +
                                                                                                                    +{/debug} + +

                                                                                                                    Druga običajna vsebina.

                                                                                                                    +``` + + +Integracija n:atributov +----------------------- + +Latte ponuja priročen skrajšan zapis za mnoge parne značke: [n:atributi |syntax#n:atributi]. Če imate parno značko, kot je `{tag}...{/tag}`, in želite, da se njen učinek uporabi neposredno na enem samem HTML elementu, jo lahko pogosto zapišete bolj jedrnato kot atribut `n:tag` na tem elementu. + +Za večino standardnih parnih značk, ki jih definirate (kot je naša `{debug}`), bo Latte samodejno omogočil ustrezno različico `n:` atributa. Med registracijo vam ni treba storiti ničesar dodatnega: + +```latte +{* Standardna uporaba parne značke *} +{debug}
                                                                                                                    Informacije za razhroščevanje
                                                                                                                    {/debug} + +{* Enakovredna uporaba z n:atributom *} +
                                                                                                                    Informacije za razhroščevanje
                                                                                                                    +``` + +Obe različici bosta izrisali `
                                                                                                                    ` samo, če je `$this->global->appDevMode` true. Predpone `inner-` in `tag-` prav tako delujejo po pričakovanjih. + +Včasih se mora logika vaše značke obnašati nekoliko drugače, odvisno od tega, ali je uporabljena kot standardna parna značka ali kot n:atribut, ali če je uporabljena predpona kot `n:inner-tag` ali `n:tag-tag`. Objekt `Latte\Compiler\Tag`, posredovan vaši funkciji za razčlenjevanje `create()`, zagotavlja te informacije: + +- `$tag->isNAttribute(): bool`: Vrne `true`, če je značka razčlenjena kot n:atribut. +- `$tag->prefix: ?string`: Vrne predpono, uporabljeno z n:atributom, kar je lahko `null` (ni n:atribut), `Tag::PrefixNone`, `Tag::PrefixInner` ali `Tag::PrefixTag`. + +Zdaj, ko razumemo preproste značke, razčlenjevanje argumentov, parne značke, ponudnike in n:atribute, se lotimo bolj zapletenega scenarija, ki vključuje značke, gnezdenih v drugih značkah, z uporabo naše značke `{debug}` kot izhodišča. + + +Vmesne značke +============= + +Nekatere parne značke omogočajo ali celo zahtevajo, da se druge značke pojavijo *znotraj* njih pred končno zapiralno značko. Te se imenujejo **vmesne značke**. Klasični primeri vključujejo `{if}...{elseif}...{else}...{/if}` ali `{switch}...{case}...{default}...{/switch}`. + +Razširimo našo značko `{debug}` tako, da podpira neobvezno klavzulo `{else}`, ki bo izrisana, ko aplikacija *ni* v razvojnem načinu. + +**Cilj:** Prilagoditi `{debug}` tako, da podpira neobvezno vmesno značko `{else}`. Končna sintaksa naj bi bila `{debug} ... {else} ... {/debug}`. + + +Razčlenjevanje vmesnih značk z `yield` +-------------------------------------- + +Že vemo, da `yield` zaustavi funkcijo za razčlenjevanje `create()` in vrne razčlenjeno vsebino skupaj s končno značko. Vendar `yield` ponuja več nadzora: lahko mu posredujete polje *imen vmesnih značk*. Ko razčlenjevalnik naleti na katero koli od teh določenih značk **na isti ravni gnezdenja** (tj. kot neposredne otroke starševske značke, ne znotraj drugih blokov ali značk znotraj nje), prav tako ustavi razčlenjevanje. + +Ko se razčlenjevanje ustavi zaradi vmesne značke, ustavi razčlenjevanje vsebine, nadaljuje generator `create()` in preda nazaj delno razčlenjeno vsebino in **vmesno značko** samo (namesto končne zapiralne značke). Naša funkcija `create()` lahko nato obdela to vmesno značko (npr. razčleni njene argumente, če jih je imela) in ponovno uporabi `yield` za razčlenjevanje *naslednjega* dela vsebine do *končne* zapiralne značke ali druge pričakovane vmesne značke. + +Prilagodimo `DebugNode::create()` tako, da pričakuje `{else}`: + +```php .{file: DebugNode.php} +node = new self; + + // yield in pričakovati bodisi {/debug} ali {else} + [$node->thenContent, $nextTag] = yield ['else']; + + // Preveriti, ali je bila značka, pri kateri smo se ustavili, {else} + if ($nextTag?->name === 'else') { + // Yield ponovno za razčlenjevanje vsebine med {else} in {/debug} + [$node->elseContent, $endTag] = yield; + } + + return $node; + } + + // ... print() in getIterator() bosta posodobljeni kasneje ... +} +``` + +Zdaj `yield ['else']` pove Latteju, naj ustavi razčlenjevanje ne samo za `{/debug}`, ampak tudi za `{else}`. Če je `{else}` najden, bo `$nextTag` vseboval objekt `Tag` za `{else}`. Nato ponovno uporabimo `yield` brez argumentov, kar pomeni, da zdaj pričakujemo samo končno značko `{/debug}`, in shranimo rezultat v `$node->elseContent`. Če `{else}` ni bil najden, bi bil `$nextTag` `Tag` za `{/debug}` (ali `null`, če je uporabljen kot n:atribut) in `$node->elseContent` bi ostal `null`. + + +Implementacija `print()` z `{else}` +----------------------------------- + +Metoda `print()` mora odražati novo strukturo. Morala bi generirati PHP stavek `if/else`, ki temelji na ponudniku `devMode`. + +```php + public function print(PrintContext $context): string + { + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + %node // Koda za vejo 'then' (vsebina {debug}) + } else { + %node // Koda za vejo 'else' (vsebina {else}) + } + + XX, + $this->position, // Številka vrstice za pogoj 'if' + $this->thenContent, // Prvi nadomestni znak %node + $this->elseContent ?? new NopNode, // Drugi nadomestni znak %node + ); + } +``` + +To je standardna PHP struktura `if/else`. Uporabljamo `%node` dvakrat; `format()` zaporedno zamenja podana vozlišča. Uporabljamo `?? new NopNode` za izogibanje napakam, če je `$this->elseContent` `null` – `NopNode` preprosto ne izpiše ničesar. + + +Implementacija `getIterator()` za obe vsebini +--------------------------------------------- + +Zdaj imamo potencialno dve podrejeni vozlišči vsebine (`$thenContent` in `$elseContent`). Zagotoviti moramo obe, če obstajata: + +```php + public function &getIterator(): \Generator + { + yield $this->thenContent; + if ($this->elseContent) { + yield $this->elseContent; + } + } +``` + + +Uporaba izboljšane značke +------------------------- + +Značka se zdaj lahko uporablja z neobvezno klavzulo `{else}`: + +```latte +{debug} +

                                                                                                                    Prikazovanje informacij za razhroščevanje, ker je devMode VKLOPLJEN.

                                                                                                                    +{else} +

                                                                                                                    Informacije za razhroščevanje so skrite, ker je devMode IZKLOPLJEN.

                                                                                                                    +{/debug} +``` + + +Obravnavanje stanja in gnezdenja +================================ + +Naši prejšnji primeri (`{datetime}`, `{debug}`) so bili relativno brez stanja znotraj svojih metod `print()`. Bodisi so neposredno izpisovali vsebino bodisi izvajali preprosto pogojno preverjanje na podlagi globalnega ponudnika. Vendar pa morajo mnoge značke upravljati neko obliko **stanja** med izrisovanjem ali vključujejo vrednotenje uporabniških izrazov, ki naj bi se zaradi učinkovitosti ali pravilnosti zagnali samo enkrat. Poleg tega moramo razmisliti, kaj se zgodi, ko so naše lastne značke **gnezdenje**. + +Ilustrirajmo te koncepte z ustvarjanjem značke `{repeat $count}...{/repeat}`. Ta značka bo ponovila svojo notranjo vsebino `$count`-krat. + +**Cilj:** Implementirati `{repeat $count}`, ki ponovi svojo vsebino določeno število krat. + + +Potreba po začasnih & edinstvenih spremenljivkah +------------------------------------------------ + +Predstavljajte si, da uporabnik napiše: + +```latte +{repeat rand(1, 5)} Vsebina {/repeat} +``` + +Če bi naivno generirali PHP `for` zanko na ta način v naši metodi `print()`: + +```php +// Poenostavljena, NAPAČNA generirana koda +for ($i = 0; $i < rand(1, 5); $i++) { + // izpis vsebine +} +``` +To bi bilo narobe! Izraz `rand(1, 5)` bi bil **ponovno ovrednoten pri vsaki iteraciji zanke**, kar bi vodilo do nepredvidljivega števila ponovitev. Izraz `$count` moramo ovrednotiti *enkrat* pred začetkom zanke in shraniti njegov rezultat. + +Generirali bomo PHP kodo, ki najprej ovrednoti izraz števila in ga shrani v **začasno izvajalno spremenljivko**. Da bi preprečili kolizije s spremenljivkami, ki jih definira uporabnik predloge, *in* notranjimi spremenljivkami Latte (kot je `$ʟ_...`), bomo uporabili konvencijo predpone **`$__` (dvojno podčrtaj)** za naše začasne spremenljivke. + +Generirana koda bi potem izgledala takole: + +```php +$__count = rand(1, 5); +for ($__i = 0; $__i < $__count; $__i++) { + // izpis vsebine +} +``` + +Zdaj razmislimo o gnezdenju: + +```latte +{repeat $countA} {* Zunanja zanka *} + {repeat $countB} {* Notranja zanka *} + ... + {/repeat} +{/repeat} +``` + +Če bi tako zunanja kot notranja značka `{repeat}` generirali kodo, ki uporablja *ista* imena začasnih spremenljivk (npr. `$__count` in `$__i`), bi notranja zanka prepisala spremenljivke zunanje zanke, kar bi porušilo logiko. + +Zagotoviti moramo, da so začasne spremenljivke, generirane za vsako instanco značke `{repeat}`, **edinstvene**. To dosežemo z uporabo `PrintContext::generateId()`. Ta metoda vrne edinstveno celo število med fazo prevajanja. To ID lahko pripnemo k imenom naših začasnih spremenljivk. + +Torej namesto `$__count` bomo generirali `$__count_1` za prvo značko repeat, `$__count_2` za drugo itd. Podobno bomo za števec zanke uporabili `$__i_1`, `$__i_2` itd. + + +Implementacija `RepeatNode` +--------------------------- + +Ustvarimo razred vozlišča. + +```php .{file: RepeatNode.php} +expectArguments(); // zagotovi, da je $count podan + $node = $tag->node = new self; + // Razčleni izraz števila + $node->count = $tag->parser->parseExpression(); + // Pridobivanje notranje vsebine + [$node->content] = yield; + return $node; + } + + /** + * Generira PHP 'for' zanko z edinstvenimi imeni spremenljivk. + */ + public function print(PrintContext $context): string + { + // Generiranje edinstvenih imen spremenljivk + $id = $context->generateId(); + $countVar = '$__count_' . $id; // npr. $__count_1, $__count_2, itd. + $iteratorVar = '$__i_' . $id; // npr. $__i_1, $__i_2, itd. + + return $context->format( + <<<'XX' + // Ovrednotenje izraza števila *enkrat* in shranjevanje + %raw = (int) (%node); + // Zanka z uporabo shranjenega števila in edinstvene iteracijske spremenljivke + for (%raw = 0; %2.raw < %0.raw; %2.raw++) %line { + %node // Izrisovanje notranje vsebine + } + + XX, + $countVar, // %0 - Spremenljivka za shranjevanje števila + $this->count, // %1 - Vozlišče izraza za število + $iteratorVar, // %2 - Ime iteracijske spremenljivke zanke + $this->position, // %3 - Komentar s številko vrstice za samo zanko + $this->content // %4 - Vozlišče notranje vsebine + ); + } + + /** + * Zagotavlja podrejena vozlišča (izraz števila in vsebina). + */ + public function &getIterator(): \Generator + { + yield $this->count; + yield $this->content; + } +} +``` + +Metoda `create()` razčleni zahtevani izraz `$count` z uporabo `parseExpression()`. Najprej se pokliče `$tag->expectArguments()`. To zagotavlja, da je uporabnik podal *nekaj* za `{repeat}`. Medtem ko bi `$tag->parser->parseExpression()` spodletel, če nič ne bi bilo podano, bi sporočilo o napaki lahko bilo o nepričakovani sintaksi. Uporaba `expectArguments()` zagotavlja veliko jasnejšo napako, ki posebej navaja, da manjkajo argumenti za značko `{repeat}`. + +Metoda `print()` generira PHP kodo, odgovorno za izvajanje logike ponavljanja ob izvajanju. Začne z generiranjem edinstvenih imen za začasne PHP spremenljivke, ki jih bo potrebovala. + +Metoda `$context->format()` je klicana z novim nadomestnim znakom `%raw`, ki vstavi *surov niz*, podan kot ustrezen argument. Tukaj vstavi edinstveno ime spremenljivke, shranjeno v `$countVar` (npr. `$__count_1`). Kaj pa `%0.raw` in `%2.raw`? To prikazuje **pozicijske nadomestne znake**. Namesto samo `%raw`, ki vzame *naslednji* razpoložljiv surovi argument, `%2.raw` eksplicitno vzame argument na indeksu 2 (kar je `$iteratorVar`) in vstavi njegovo surovo nizovno vrednost. To nam omogoča ponovno uporabo niza `$iteratorVar`, ne da bi ga večkrat posredovali v seznamu argumentov za `format()`. + +Ta skrbno sestavljen klic `format()` generira učinkovito in varno PHP zanko, ki pravilno obravnava izraz števila in se izogiba kolizijam imen spremenljivk, tudi ko so značke `{repeat}` gnezdenje. + + +Registracija in uporaba +----------------------- + +Registrirajte značko v vaši razširitvi: + +```php +use App\Latte\RepeatNode; + +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), // Registracija značke repeat + ]; + } +} +``` + +Uporabite jo v predlogi, vključno z gnezdenjem: + +```latte +{var $rows = rand(5, 7)} +{var $cols = rand(3, 5)} + +{repeat $rows} +
                                                                                                                    + {repeat $cols} + + {/repeat} + +{/repeat} +``` + +Ta primer prikazuje, kako obravnavati stanje (števce zank) in potencialne težave z gnezdenjem z uporabo začasnih spremenljivk s predpono `$__` in edinstvenih z ID-jem od `PrintContext::generateId()`. + + +Čisti n:atributi +---------------- + +Medtem ko mnogi `n:atributi`, kot sta `n:if` ali `n:foreach`, služijo kot priročne okrajšave za njihove ustrezne parne značke (`{if}...{/if}`, `{foreach}...{/foreach}`), Latte omogoča tudi definiranje značk, ki *obstajajo samo* v obliki n:atributa. Te se pogosto uporabljajo za spreminjanje atributov ali obnašanja HTML elementa, na katerega so pripeti. + +Standardni primeri, vgrajeni v Latte, vključujejo [`n:class` |tags#n:class], ki pomaga dinamično sestaviti atribut `class`, in [`n:attr` |tags#n:attr], ki lahko nastavi več poljubnih atributov. + +Ustvarimo si lasten čisti n:atribut: `n:confirm`, ki doda JavaScript potrditveno pogovorno okno pred izvedbo dejanja (kot je sledenje povezavi ali pošiljanje obrazca). + +**Cilj:** Implementirati `n:confirm="'Ste prepričani?'"`, ki doda obravnavnik `onclick` za preprečitev privzetega dejanja, če uporabnik prekliče potrditveno pogovorno okno. + + +Implementacija `ConfirmNode` +---------------------------- + +Potrebujemo razred Node in funkcijo za razčlenjevanje. + +```php .{file: ConfirmNode.php} +expectArguments(); + $node = $tag->node = new self; + $node->message = $tag->parser->parseExpression(); + return $node; + } + + /** + * Generira kodo atributa 'onclick' s pravilnim ubežanjem znakov. + */ + public function print(PrintContext $context): string + { + // Zagotavlja pravilno ubežanje znakov za kontekste JavaScript in HTML atributa. + return $context->format( + <<<'XX' + echo ' onclick="', LR\Filters::escapeHtmlAttr('return confirm(' . LR\Filters::escapeJs(%node) . ')'), '"' %line; + XX, + $this->message, + $this->position, + ); + } + + public function &getIterator(): \Generator + { + yield $this->message; + } +} +``` + +Metoda `print()` generira PHP kodo, ki na koncu med izrisovanjem predloge izpiše HTML atribut `onclick="..."`. Obravnavanje gnezdenih kontekstov (JavaScript znotraj HTML atributa) zahteva skrbno ubežanje znakov. Filter `LR\Filters::escapeJs(%node)` se pokliče ob izvajanju in pravilno ubeži sporočilo za uporabo znotraj JavaScripta (izpis bi bil kot `"Ste prepričani?"`). Nato filter `LR\Filters::escapeHtmlAttr(...)` ubeži znake, ki so posebni v HTML atributih, tako da bi to spremenilo izpis v `return confirm("Ste prepričani?")`. To dvostopenjsko ubežanje znakov ob izvajanju zagotavlja, da je sporočilo varno za JavaScript in da je nastala JavaScript koda varna za vstavljanje v HTML atribut `onclick`. + + +Registracija in uporaba +----------------------- + +Registrirajte n:atribut v vaši razširitvi. Ne pozabite na predpono `n:` v ključu: + +```php .{file: MyLatteExtension.php} +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), + 'n:confirm' => ConfirmNode::create(...), // Registracija n:confirm + ]; + } +} +``` + +Zdaj lahko uporabite `n:confirm` na povezavah, gumbih ali elementih obrazca: + +```latte +Izbriši +``` + +Generirana HTML koda: + +```html +Izbriši +``` + +Ko uporabnik klikne na povezavo, brskalnik izvede kodo `onclick`, prikaže potrditveno pogovorno okno in preide na `delete.php` samo, če uporabnik klikne "OK". + +Ta primer prikazuje, kako je mogoče ustvariti čisti n:atribut za spreminjanje obnašanja ali atributov svojega gostiteljskega HTML elementa z generiranjem ustrezne PHP kode v njegovi metodi `print()`. Ne pozabite na dvojno ubežanje znakov, ki je pogosto zahtevano: enkrat za ciljni kontekst (JavaScript v tem primeru) in ponovno za kontekst HTML atributa. + + +Napredne teme +============= + +Medtem ko prejšnji razdelki pokrivajo osnovne koncepte, je tukaj nekaj naprednejših tem, na katere lahko naletite pri ustvarjanju lastnih Latte značk. + + +Načini izpisa značk +------------------- + +Objekt `Tag`, posredovan vaši funkciji `create()`, ima lastnost `outputMode`. Ta lastnost vpliva na to, kako Latte obravnava okoliške presledke in zamike, zlasti ko je značka uporabljena v svoji vrstici. To lastnost lahko spremenite v vaši funkciji `create()`. + +- `Tag::OutputKeepIndentation` (Privzeto za večino značk kot `{=...}`): Latte poskuša ohraniti zamik pred značko. Nove vrstice *po* znački so na splošno ohranjene. To je primerno za značke, ki izpisujejo vsebino v vrstici. +- `Tag::OutputRemoveIndentation` (Privzeto za blokovne značke kot `{if}`, `{foreach}`): Latte odstrani začetni zamik in potencialno eno naslednjo novo vrstico. To pomaga ohranjati generirano PHP kodo čistejšo in preprečuje dodatne prazne vrstice v HTML izpisu, ki jih povzroči sama značka. Uporabite to za značke, ki predstavljajo kontrolne strukture ali bloke, ki sami ne bi smeli dodajati presledkov. +- `Tag::OutputNone` (Uporabljajo ga značke kot `{var}`, `{default}`): Podobno kot `RemoveIndentation`, vendar močneje signalizira, da značka sama ne proizvaja neposrednega izpisa, kar potencialno še bolj agresivno vpliva na obdelavo presledkov okoli nje. Primerno za deklarativne ali nastavitvene značke. + +Izberite način, ki najbolje ustreza namenu vaše značke. Za večino strukturnih ali kontrolnih značk je običajno primeren `OutputRemoveIndentation`. + + +Dostop do starševskih/najbližjih značk +-------------------------------------- + +Včasih mora obnašanje značke biti odvisno od konteksta, v katerem se uporablja, natančneje, v kateri starševski znački(ah) se nahaja. Objekt `Tag`, posredovan vaši funkciji `create()`, zagotavlja metodo `closestTag(array $classes, ?callable $condition = null): ?Tag` natančno za ta namen. + +Ta metoda išče navzgor po hierarhiji trenutno odprtih značk (vključno s HTML elementi, ki so interno predstavljeni med razčlenjevanjem) in vrne objekt `Tag` najbližjega prednika, ki ustreza specifičnim kriterijem. Če ni najden noben ustrezen prednik, vrne `null`. + +Polje `$classes` določa, kakšno vrsto predniških značk iščete. Preverja, ali je povezano vozlišče predniške značke (`$ancestorTag->node`) instanca tega razreda. + +```php +function create(Tag $tag) +{ + // Iskanje najbližje predniške značke, katere vozlišče je instanca ForeachNode + $foreachTag = $tag->closestTag([ForeachNode::class]); + if ($foreachTag) { + // Lahko dostopamo do instance ForeachNode same: + $foreachNode = $foreachTag->node; + } +} +``` + +Opazite `$foreachTag->node`: To deluje samo zato, ker je konvencija pri razvoju Latte značk takoj dodeliti ustvarjeno vozlišče k `$tag->node` znotraj metode `create()`, kot smo vedno počeli. + +Včasih samo primerjava tipa vozlišča ni dovolj. Morda boste morali preveriti specifično lastnost potencialne predniške značke ali njenega vozlišča. Neobvezni drugi argument za `closestTag()` je klicna funkcija (callable), ki prejme potencialni predniški objekt `Tag` in mora vrniti, ali je veljavno ujemanje. + +```php +function create(Tag $tag) +{ + $dynamicBlockTag = $tag->closestTag( + [BlockNode::class], + // Pogoj: blok mora biti dinamičen + fn(Tag $blockTag) => $blockTag->node->block->isDynamic(), + ); +} +``` + +Uporaba `closestTag()` omogoča ustvarjanje značk, ki so kontekstno zavedne in uveljavljajo pravilno uporabo znotraj strukture vaše predloge, kar vodi do bolj robustnih in razumljivih predlog. + + +Nadomestni znaki `PrintContext::format()` +----------------------------------------- + +Pogosto smo uporabljali `PrintContext::format()` za generiranje PHP kode v metodah `print()` naših vozlišč. Sprejme niz maske in naslednje argumente, ki nadomestijo nadomestne znake v maski. Tukaj je povzetek razpoložljivih nadomestnih znakov: + +- **`%node`**: Argument mora biti instanca `Node`. Pokliče metodo `print()` vozlišča in vstavi nastali niz PHP kode. +- **`%dump`**: Argument je katera koli PHP vrednost. Izvozi vrednost v veljavno PHP kodo. Primerno za skalarje, polja, null. + - `$context->format('echo %dump;', 'Hello')` -> `echo 'Hello';` + - `$context->format('$arr = %dump;', [1, 2])` -> `$arr = [1, 2];` +- **`%raw`**: Vstavi argument neposredno v izhodno PHP kodo brez kakršnega koli ubežanja znakov ali prilagoditev. **Uporabljajte previdno**, predvsem za vstavljanje predgeneriranih fragmentov PHP kode ali imen spremenljivk. + - `$context->format('%raw = 1;', '$variableName')` -> `$variableName = 1;` +- **`%args`**: Argument mora biti `Expression\ArrayNode`. Izpiše elemente polja, formatirane kot argumenti za klic funkcije ali metode (ločeni z vejicami, obravnava poimenovane argumente, če so prisotni). + - `$argsNode = new ArrayNode([...]);` + - `$context->format('myFunc(%args);', $argsNode)` -> `myFunc(1, name: 'Joe');` +- **`%line`**: Argument mora biti objekt `Position` (običajno `$this->position`). Vstavi PHP komentar `/* line X */`, ki označuje številko vrstice vira. + - `$context->format('echo "Hi" %line;', $this->position)` -> `echo "Hi" /* line 42 */;` +- **`%escape(...)`**: Generira PHP kodo, ki *ob izvajanju* ubeži notranji izraz z uporabo trenutnih kontekstno zavednih pravil ubežanja znakov. + - `$context->format('echo %escape(%node);', $variableNode)` +- **`%modify(...)`**: Argument mora biti `ModifierNode`. Generira PHP kodo, ki uporabi filtre, določene v `ModifierNode`, na notranji vsebini, vključno s kontekstno zavednim ubežanjem znakov, če ni onemogočeno z `|noescape`. + - `$context->format('%modify(%node);', $modifierNode, $variableNode)` +- **`%modifyContent(...)`**: Podobno kot `%modify`, vendar namenjeno za spreminjanje blokov zajete vsebine (pogosto HTML). + +Lahko eksplicitno sklicujete na argumente po njihovem indeksu (od nič): `%0.node`, `%1.dump`, `%2.raw`, itd. To omogoča ponovno uporabo argumenta večkrat v maski, ne da bi ga ponovno posredovali v `format()`. Glejte primer značke `{repeat}`, kjer sta bila uporabljena `%0.raw` in `%2.raw`. + + +Primer kompleksnega razčlenjevanja argumentov +--------------------------------------------- + +Medtem ko `parseExpression()`, `parseArguments()`, itd., pokrivajo mnoge primere, včasih potrebujete bolj zapleteno logiko razčlenjevanja z uporabo nižje ravni `TokenStream`, ki je na voljo preko `$tag->parser->stream`. + +**Cilj:** Ustvariti značko `{embedYoutube $videoID, width: 640, height: 480}`. Želimo razčleniti zahtevani ID videa (niz ali spremenljivko), ki mu sledijo neobvezni pari ključ-vrednost za dimenzije. + +```php .{file: YoutubeNode.php} +expectArguments(); + $node = $tag->node = new self; + // Razčlenjevanje zahtevanega ID-ja videa + $node->videoId = $tag->parser->parseExpression(); + + // Razčlenjevanje neobveznih parov ključ-vrednost + $stream = $tag->parser->stream; // Pridobivanje toka žetonov + while ($stream->tryConsume(',')) { // Zahteva ločitev z vejico + // Pričakovanje identifikatorja 'width' ali 'height' + $keyToken = $stream->consume(Token::Php_Identifier); + $key = strtolower($keyToken->text); + + $stream->consume(':'); // Pričakovanje ločila dvopičja + + $value = $tag->parser->parseExpression(); // Razčlenjevanje izraza vrednosti + + if ($key === 'width') { + $node->width = $value; + } elseif ($key === 'height') { + $node->height = $value; + } else { + throw new CompileException("Neznan argument '$key'. Pričakovano 'width' ali 'height'.", $keyToken->position); + } + } + + return $node; + } +} +``` + +Ta raven nadzora vam omogoča definiranje zelo specifičnih in kompleksnih sintaks za vaše lastne značke z neposredno interakcijo s tokom žetonov. + + +Uporaba `AuxiliaryNode` +----------------------- + +Latte ponuja splošna "pomožna" vozlišča za posebne situacije med generiranjem kode ali znotraj prevajalskih prehodov. To sta `AuxiliaryNode` in `Php\Expression\AuxiliaryNode`. + +Predstavljajte si `AuxiliaryNode` kot prilagodljivo vsebniško vozlišče, ki svoje osnovne funkcionalnosti - generiranje kode in izpostavljanje podrejenih vozlišč - prenese na argumente, podane v njegovem konstruktorju: + +- Delegacija `print()`: Prvi argument konstruktorja je PHP **closure**. Ko Latte pokliče metodo `print()` na `AuxiliaryNode`, izvede to podano closure. Closure prejme `PrintContext` in katera koli vozlišča, posredovana v drugem argumentu konstruktorja, kar vam omogoča definiranje popolnoma lastne logike generiranja PHP kode ob izvajanju. +- Delegacija `getIterator()`: Drugi argument konstruktorja je **polje objektov `Node`**. Ko mora Latte preiti otroke `AuxiliaryNode` (npr. med prevajalskimi prehodi), njegova metoda `getIterator()` preprosto zagotovi vozlišča, navedena v tem polju. + +Primer: + +```php +$node = new AuxiliaryNode( + // 1. Ta closure postane telo print() + fn(PrintContext $context, $arg1, $arg2) => $context->format('...%node...%node...', $arg1, $arg2), + + // 2. Ta vozlišča so zagotovljena z metodo getIterator() in posredovana zgornji closure + [$argumentNode1, $argumentNode2] +); +``` + +Latte ponuja dva različna tipa, ki temeljita na tem, kje morate vstaviti generirano kodo: + +- `Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode`: Uporabite to, ko morate generirati kos PHP kode, ki predstavlja **izraz**. +- `Latte\Compiler\Nodes\AuxiliaryNode`: Uporabite to za bolj splošne namene, ko morate vstaviti blok PHP kode, ki predstavlja enega ali več **stavkov**. + +Pomemben razlog za uporabo `AuxiliaryNode` namesto standardnih vozlišč (kot je `StaticMethodCallNode`) znotraj vaše metode `print()` ali prevajalskega prehoda je **nadzor vidnosti za naslednje prevajalske prehode**, zlasti tiste, povezane z varnostjo, kot je Sandbox. + +Razmislite o scenariju: Vaš prevajalski prehod mora oviti izraz, ki ga je podal uporabnik (`$userExpr`), s klicem specifične, zaupanja vredne pomožne funkcije `myInternalSanitize($userExpr)`. Če ustvarite standardno vozlišče `new FunctionCallNode('myInternalSanitize', [$userExpr])`, bo popolnoma vidno za prehod AST. Če prehod Sandbox teče kasneje in `myInternalSanitize` *ni* na njegovem seznamu dovoljenih, lahko Sandbox ta klic *blokira* ali spremeni, kar potencialno poruši notranjo logiko vaše značke, čeprav *vi*, avtor značke, veste, da je ta specifičen klic varen in potreben. Zato lahko klic generirate neposredno znotraj closure `AuxiliaryNode`. + +```php +use Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode; + +// ... znotraj print() ali prevajalskega prehoda ... +$wrappedNode = new AuxiliaryNode( + fn(PrintContext $context, $userExpr) => $context->format( + 'myInternalSanitize(%node)', // Neposredno generiranje PHP kode + $userExpr, + ), + // POMEMBNO: Še vedno tukaj posredujte izvirno vozlišče uporabniškega izraza! + [$userExpr], +); +``` + +V tem primeru prehod Sandbox vidi `AuxiliaryNode`, vendar **ne analizira PHP kode, ki jo generira njegova closure**. Ne more neposredno blokirati klica `myInternalSanitize`, generiranega *znotraj* closure. + +Medtem ko je generirana PHP koda sama skrita pred prehodi, morajo biti *vhodi* v to kodo (vozlišča, ki predstavljajo uporabniške podatke ali izraze) **še vedno prehodni**. Zato je drugi argument konstruktorja `AuxiliaryNode` ključen. **Morate** posredovati polje, ki vsebuje vsa izvirna vozlišča (kot je `$userExpr` v zgornjem primeru), ki jih vaša closure uporablja. `getIterator()` `AuxiliaryNode` **bo zagotovil ta vozlišča**, kar omogoča prevajalskim prehodom, kot je Sandbox, da jih analizirajo za potencialne težave. + + +Najboljše prakse +================ + +- **Jasen namen:** Zagotovite, da ima vaša značka jasen in nujen namen. Ne ustvarjajte značk za naloge, ki jih je mogoče enostavno rešiti z uporabo [filtrov |custom-filters] ali [funkcij |custom-functions]. +- **Pravilno implementirajte `getIterator()`:** Vedno implementirajte `getIterator()` in zagotovite *reference* (`&`) na *vsa* podrejena vozlišča (argumente, vsebino), ki so bila razčlenjena iz predloge. To je nujno za prevajalske prehode, varnost (Sandbox) in potencialne prihodnje optimizacije. +- **Javne lastnosti za vozlišča:** Lastnosti, ki vsebujejo podrejena vozlišča, naredite javne, da jih lahko prevajalski prehodi po potrebi spreminjajo. +- **Uporabljajte `PrintContext::format()`:** Izkoristite metodo `format()` za generiranje PHP kode. Obravnava narekovaje, pravilno ubeži nadomestne znake in samodejno dodaja komentarje s številko vrstice. +- **Začasne spremenljivke (`$__`):** Pri generiranju izvajalne PHP kode, ki potrebuje začasne spremenljivke (npr. za shranjevanje vmesnih vsot, števce zank), uporabljajte konvencijo predpone `$__` za izogibanje kolizijam z uporabniškimi spremenljivkami in notranjimi spremenljivkami Latte `$ʟ_`. +- **Gnezdenje in edinstveni ID-ji:** Če je vaša značka lahko gnezdena ali potrebuje stanje, specifično za instanco ob izvajanju, uporabite `$context->generateId()` znotraj vaše metode `print()` za ustvarjanje edinstvenih pripon za vaše začasne spremenljivke `$__`. +- **Ponudniki za zunanje podatke:** Uporabljajte ponudnike (registrirane preko `Extension::getProviders()`) za dostop do izvajalnih podatkov ali storitev ($this->global->...) namesto trdega kodiranja vrednosti ali zanašanja na globalno stanje. Uporabljajte predpone proizvajalca za imena ponudnikov. +- **Razmislite o n:atributih:** Če vaša parna značka logično deluje na enem samem HTML elementu, Latte verjetno zagotavlja samodejno podporo `n:atributu`. Imejte to v mislih za udobje uporabnika. Če ustvarjate značko, ki spreminja atribut, razmislite, ali je čisti `n:atribut` najprimernejša oblika. +- **Testiranje:** Pišite teste za vaše značke, ki pokrivajo tako razčlenjevanje različnih sintaktičnih vnosov kot pravilnost izpisa generirane **PHP kode**. + +Z upoštevanjem teh smernic lahko ustvarite močne, robustne in vzdržljive lastne značke, ki se brezhibno integrirajo z mehanizmom predlog Latte. + +.[note] +Študij razredov vozlišč, ki so del Latte, je najboljši način za učenje vseh podrobnosti o postopku razčlenjevanja. diff --git a/latte/sl/develop.texy b/latte/sl/develop.texy index 0b3315ea6f..4438aca6b6 100644 --- a/latte/sl/develop.texy +++ b/latte/sl/develop.texy @@ -1,70 +1,66 @@ -Prakse za razvijalce -******************** +Razvojni postopki +***************** -Namestitev .[#toc-installation] -=============================== +Namestitev +========== -Najboljši način za namestitev Latteja je uporaba Composerja: +Najboljši način za namestitev Latte je s pomočjo Composerja: ```shell composer require latte/latte ``` -Podprte različice PHP (velja za najnovejše različice popravkov Latte): +Podprte različice PHP (velja za zadnje stotinske različice Latte): -| različica | združljiva s PHP +| različica | združljivo s PHP |-----------------|------------------- -| Latte 3.0 | PHP 8.0 - 8.2 -| Latte 2.11 | PHP 7.1 - 8.2 -| Latte 2.8 - 2.10| PHP 7.1 - 8.1 +| Latte 3.0 | PHP 8.0 – 8.2 -Kako izrisati predlogo .[#toc-how-to-render-a-template] -======================================================= +Kako izrisati predlogo +====================== -Kako izrisati predlogo? Uporabite to preprosto kodo: +Kako izrisati predlogo? Dovolj je ta preprosta koda: ```php $latte = new Latte\Engine; -// imenik predpomnilnika +// imenik za predpomnilnik $latte->setTempDirectory('/path/to/tempdir'); -$params = [ /* template variables */ ]; +$params = [ /* spremenljivke predloge */ ]; // ali $params = new TemplateParameters(/* ... */); -// izrisovanje v izpis +// izriši na izhod $latte->render('template.latte', $params); -// ali izriši v spremenljivko +// izriši v spremenljivko $output = $latte->renderToString('template.latte', $params); ``` -Parametri so lahko polja ali še bolje [predmeti |#Parameters as a class], kar bo omogočilo preverjanje tipa in predlaganje v urejevalniku. +Parametri so lahko polja ali še bolje [objekt |#Parametri kot razred], ki zagotavlja tipsko kontrolo in predlaganje v urejevalnikih. .[note] -Primere uporabe lahko najdete tudi v skladišču [Latte examples |https://github.com/nette-examples/latte]. +Primeri uporabe najdete tudi v repozitoriju [Latte examples |https://github.com/nette-examples/latte]. -Zmogljivost in predpomnilnik .[#toc-performance-and-caching] -============================================================ +Zmogljivost in predpomnilnik +============================ -Predloge Latte so izjemno hitre, saj jih Latte sestavi neposredno v kodo PHP in jih predpomni na disku. Tako nimajo dodatnih režijskih stroškov v primerjavi s predlogami, napisanimi v čistem jeziku PHP. +Predloge v Latte so izjemno hitre, Latte jih namreč prevede neposredno v PHP kodo in shrani v predpomnilnik na disk. Nimajo torej nobene dodatne režije v primerjavi s predlogami, napisanimi v čistem PHP. -Predpomnilnik se samodejno obnovi vsakič, ko spremenite izvorno datoteko. Tako lahko med razvojem priročno urejate predloge Latte in spremembe takoj vidite v brskalniku. V produkcijskem okolju lahko to funkcijo onemogočite in prihranite nekaj zmogljivosti: +Predpomnilnik se samodejno regenerira vsakič, ko spremenite izvorno datoteko. Med razvojem si torej udobno urejate predloge v Latte in spremembe takoj vidite v brskalniku. To funkcijo lahko v produkcijskem okolju izklopite in prihranite malo zmogljivosti: ```php $latte->setAutoRefresh(false); ``` -Ko se namestite v produkcijski strežnik, lahko začetno ustvarjanje predpomnilnika, zlasti pri večjih aplikacijah, razumljivo traja nekaj časa. Latte ima vgrajeno preprečevanje "stampede predpomnilnika":https://en.wikipedia.org/wiki/Cache_stampede. -To je situacija, ko strežnik prejme veliko število hkratnih zahtevkov in ker predpomnilnik Latte še ne obstaja, bi ga vsi ustvarili hkrati. Zaradi tega se poveča število procesorjev. -Latte je pameten in kadar je več hkratnih zahtevkov, samo prva nit ustvari predpomnilnik, druge počakajo in ga nato uporabijo. +Pri uvajanju na produkcijski strežnik lahko prvotno generiranje predpomnilnika, zlasti pri obsežnejših aplikacijah, seveda traja nekaj časa. Latte ima vgrajeno preprečevanje pred "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Gre za situacijo, ko se zbere večje število sočasnih zahtev, ki sprožijo Latte, in ker predpomnilnik še ne obstaja, bi ga vse začele generirati hkrati. Kar bi nesorazmerno obremenilo strežnik. Latte je pameten in pri več sočasnih zahtevah generira predpomnilnik samo prva nit, ostale čakajo in ga nato uporabijo. -Parametri kot razred .[#toc-parameters-as-a-class] -================================================== +Parametri kot razred +==================== -Bolje kot posredovati spremenljivke predlogi kot polja je ustvariti razred. Pridobite [tipsko varen zapis |type-system], [lepo sugestijo v IDE |recipes#Editors and IDE] ter način za [registracijo filtrov |extending-latte#Filters Using the Class] in [funkcij |extending-latte#Functions Using the Class]. +Bolje kot predajati spremenljivke v predlogo kot polje je ustvariti si razred. Dobite tako [tipsko varen zapis|type-system], [prijetno predlaganje v IDE |recipes#Urejevalniki in IDE] in pot za [registracijo filtrov |custom-filters#Filtri ki uporabljajo razred z atributi] in [funkcij |custom-functions#Funkcije ki uporabljajo razred z atributi]. ```php class MailTemplateParameters @@ -88,12 +84,12 @@ $latte->render('mail.latte', new MailTemplateParameters( ``` -Onemogočanje samodejnega pobega spremenljivke .[#toc-disabling-auto-escaping-of-variable] -========================================================================================= +Izklop samodejnega ubežanja spremenljivke +========================================= -Če spremenljivka vsebuje niz HTML, jo lahko označite tako, da je Latte ne bo samodejno (in s tem dvakratno) izločil. S tem se izognete potrebi po navedbi `|noescape` v predlogi. +Če spremenljivka vsebuje niz v HTML, jo lahko označite tako, da je Latte samodejno (in torej dvakrat) ne ubeži. Izognete se tako potrebi po navajanju `|noescape` v predlogi. -Najlažji način je, da niz zapakirate v predmet `Latte\Runtime\Html`: +Najenostavnejša pot je niz zaviti v objekt `Latte\Runtime\Html`: ```php $params = [ @@ -101,7 +97,7 @@ $params = [ ]; ``` -Latte prav tako ne pobegne vsem objektom, ki implementirajo vmesnik `Latte\HtmlStringable`. Tako lahko ustvarite svoj razred, katerega metoda `__toString()` bo vrnila kodo HTML, ki ne bo samodejno pobegnila: +Latte nadalje ne ubeži vseh objektov, ki implementirajo vmesnik `Latte\HtmlStringable`. Lahko si tako ustvarite lasten razred, katerega metoda `__toString()` bo vračala HTML kodo, ki se ne bo samodejno ubežala: ```php class Emphasis extends Latte\HtmlStringable @@ -123,32 +119,85 @@ $params = [ ``` .[warning] -Metoda `__toString` mora vrniti pravilno kodo HTML in zagotoviti eskapiranje parametrov, sicer lahko pride do ranljivosti XSS! +Metoda `__toString` mora vrniti korekten HTML in zagotoviti ubežanje parametrov, sicer lahko pride do ranljivosti XSS! -Kako razširiti Latte s filtri, oznakami itd. .[#toc-how-to-extend-latte-with-filters-tags-etc] -============================================================================================== +Kako razširiti Latte s filtri, značkami itd. +============================================ -Kako v Latte dodati filter, funkcijo, oznako itd. po meri? To izveste v poglavju [Razširitev sistema Latte |extending Latte]. -Če želite svoje spremembe ponovno uporabiti v različnih projektih ali če jih želite deliti z drugimi, morate nato [ustvariti razširitev |creating-extension]. +Kako v Latte dodati lasten filter, funkcijo, značko itd.? O tem govori poglavje [razširjamo Latte |extending-latte]. Če želite svoje prilagoditve ponovno uporabiti v različnih projektih ali jih deliti z drugimi, bi morali [ustvariti razširitev |extending-latte#Latte Extension]. -Katera koli koda v predlogi `{php ...}` .{data-version:3.0}{toc: RawPhpExtension} -================================================================================= +Poljubna koda v predlogi `{php ...}` .{toc: RawPhpExtension} +============================================================ -Znotraj okenca lahko zapišete samo izraze PHP [`{do}` |tags#do] značko, zato ne morete na primer vstaviti konstrukcij, kot so `if ... else` ali izjave, zaključene s podpičjem. +Znotraj značke [`{do}` |tags#do] je mogoče zapisovati le PHP izraze, ne morete pa na primer vstaviti konstrukcij, kot sta `if ... else` ali stavkov, zaključenih s podpičjem. -Lahko pa registrirate razširitev `RawPhpExtension`, ki doda oznako `{php ...}`, s katero lahko avtor predloge na lastno odgovornost vstavi poljubno kodo PHP. +Lahko pa si registrirate razširitev `RawPhpExtension`, ki dodaja značko `{php ...}`. S pomočjo te je mogoče vstavljati kakršno koli PHP kodo. Zanjo ne veljajo nobena pravila peskovniškega načina, uporaba je torej na odgovornost avtorja predloge. ```php $latte->addExtension(new Latte\Essential\RawPhpExtension); ``` -Prevajanje v predlogah .{data-version:3.0}{toc: TranslatorExtension} -==================================================================== +Preverjanje generirane kode .{data-version:3.0.7} +================================================= + +Latte prevaja predloge v PHP kodo. Seveda skrbi za to, da je generirana koda sintaktično veljavna. Vendar pri uporabi razširitev tretjih oseb ali `RawPhpExtension` Latte ne more zagotoviti pravilnosti generirane datoteke. Prav tako je mogoče v PHP zapisati kodo, ki je sicer sintaktično pravilna, vendar je prepovedana (na primer dodelitev vrednosti spremenljivki `$this`) in povzroči PHP Compile Error. Če tako operacijo zapišete v predlogi, pride tudi v generirano PHP kodo. Ker v PHP obstaja na dvesto različnih prepovedanih operacij, Latte nima ambicije jih odkrivati. Nanjo opozori šele sam PHP pri izrisovanju, kar običajno ničesar ne ovira. + +So pa situacije, ko želite vedeti že v času prevajanja predloge, da ne vsebuje nobenega PHP Compile Error. Zlasti takrat, ko lahko predloge urejajo uporabniki ali uporabljate [Sandbox|sandbox]. V takem primeru si pustite predloge preverjati že v času prevajanja. To funkcionalnost vklopite z metodo `Engine::enablePhpLint()`. Ker za preverjanje potrebuje klicati PHP binarno datoteko, pot do nje predajte kot parameter: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); + +try { + $latte->compile('home.latte'); +} catch (Latte\CompileException $e) { + // prestreže napake v Latte in tudi Compile Error v PHP + echo 'Napaka: ' . $e->getMessage(); +} +``` + + +Nacionalno okolje .{data-version:3.0.18}{toc: Locale} +===================================================== + +Latte omogoča nastavitev nacionalnega okolja, ki vpliva na formatiranje števil, datumov in razvrščanje. Nastavlja se s pomočjo metode `setLocale()`. Identifikator okolja sledi standardu IETF language tag, ki ga uporablja PHP razširitev `intl`. Sestavljen je iz kode jezika in po potrebi kode države, npr. `en_US` za angleščino v Združenih državah, `de_DE` za nemščino v Nemčiji itd. + +```php +$latte = new Latte\Engine; +$latte->setLocale('sl'); +``` + +Nastavitev okolja vpliva na filtre [localDate |filters#localDate], [sort |filters#sort], [number |filters#number] in [bytes |filters#bytes]. + +.[note] +Zahteva PHP razširitev `intl`. Nastavitev v Latte ne vpliva na globalno nastavitev locale v PHP. + + +Strogi način .{data-version:3.0.8} +================================== + +V strogem načinu razčlenjevanja Latte preverja, ali manjkajo zaključne HTML oznake in tudi prepoveduje uporabo spremenljivke `$this`. Vklopite ga takole: + +```php +$latte = new Latte\Engine; +$latte->setStrictParsing(); +``` + +Generiranje predlog z glavo `declare(strict_types=1)` vklopite takole: + +```php +$latte = new Latte\Engine; +$latte->setStrictTypes(); +``` -Uporabite razširitev `TranslatorExtension` za dodajanje [`{_...}` |tags#_], [`{translate}` |tags#translate] in filtrirajte [`translate` |filters#translate] v predlogo. Uporabljata se za prevajanje vrednosti ali delov predloge v druge jezike. Parameter je metoda (klic PHP), ki izvede prevod: + +Prevajanje v predlogah .{toc: TranslatorExtension} +================================================== + +S pomočjo razširitve `TranslatorExtension` dodate v predlogo značke [`{_...}` |tags#], [`{translate}` |tags#translate] in filter [`translate` |filters#translate]. Služijo za prevajanje vrednosti ali delov predloge v druge jezike. Kot parameter navedemo metodo (PHP callable), ki izvaja prevod: ```php class MyTranslator @@ -158,7 +207,7 @@ class MyTranslator public function translate(string $original): string { - // ustvarite $translated iz $original glede na $this->lang + // iz $original ustvarimo $translated glede na $this->lang return $translated; } } @@ -170,7 +219,7 @@ $extension = new Latte\Essential\TranslatorExtension( $latte->addExtension($extension); ``` -Prevajalnik se pokliče med izvajanjem, ko se predloga prikaže. Vendar lahko Latte vsa statična besedila prevede med sestavljanjem predloge. To prihrani zmogljivost, saj se vsak niz prevede samo enkrat, dobljeni prevod pa se zapiše v sestavljeno datoteko. Tako se v imeniku predpomnilnika ustvari več sestavljenih različic predloge, po ena za vsak jezik. Za to morate kot drugi parameter navesti le jezik: +Prevajalnik se kliče med izvajanjem pri izrisovanju predloge. Latte pa zna vse statične tekste prevajati že med prevajanjem predloge. S tem se prihrani zmogljivost, ker se vsak niz prevede le enkrat in končni prevod se zapiše v prevedeno obliko. V imeniku s predpomnilnikom tako nastane več prevedenih različic predloge, ena za vsak jezik. Za to je dovolj le navesti jezik kot drugi parameter: ```php $extension = new Latte\Essential\TranslatorExtension( @@ -179,9 +228,9 @@ $extension = new Latte\Essential\TranslatorExtension( ); ``` -S statičnim besedilom mislimo na primer na `{_'hello'}` ali `{translate}hello{/translate}`. Nestatično besedilo, kot je `{_$foo}`, se bo med izvajanjem še naprej prevajalo. +Statični tekst je mišljeno na primer `{_'hello'}` ali `{translate}hello{/translate}`. Nestatični teksti, kot na primer `{_$foo}`, se še naprej prevajajo med izvajanjem. -Predloga lahko prevajalniku prek `{_$original, foo: bar}` ali `{translate foo: bar}` posreduje tudi dodatne parametre, ki jih ta prejme kot polje `$params`: +Prevajalniku lahko iz predloge predajate tudi dopolnilne parametre s pomočjo `{_$original, foo: bar}` ali `{translate foo: bar}`, ki jih dobi kot polje `$params`: ```php public function translate(string $original, ...$params): string @@ -191,66 +240,73 @@ public function translate(string $original, ...$params): string ``` -Razhroščevanje in Tracy .[#toc-debugging-and-tracy] -=================================================== +Razhroščevanje in Tracy +======================= -Latte se trudi, da bi bil razvoj čim bolj prijeten. Za namene razhroščevanja so na voljo tri oznake [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak] in [`{trace}` |tags#trace]. +Latte vam skuša razvoj čim bolj olajšati. Neposredno za namene razhroščevanja obstaja trojica značk [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak] in [`{trace}` |tags#trace]. -Največ udobja boste imeli, če namestite odlično [orodje za razhroščevanje Tracy |tracy:] in aktivirate vtičnik Latte: +Največje udobje dobite, če si še namestite odlično [orodje za razhroščevanje Tracy|tracy:] in aktivirate dodatek za Latte: ```php -// omogoča Tracy +// vklopi Tracy Tracy\Debugger::enable(); $latte = new Latte\Engine; -// aktivira Tracyjev podaljšek. +// aktivira razširitev za Tracy $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension); ``` -Vse napake bodo zdaj vidne v preglednem rdečem zaslonu, vključno z napakami v predlogah s poudarjenimi vrsticami in stolpci ([videoposnetek |https://github.com/nette/tracy/releases/tag/v2.9.0]). -Hkrati se v spodnjem desnem kotu v tako imenovani Tracyjevi vrstici prikaže zavihek za Latte, kjer lahko jasno vidite vse izrisane predloge in njihova razmerja (vključno z možnostjo klika v predlogo ali sestavljeno kodo) ter spremenljivke: +Zdaj se vam bodo vse napake prikazovale v preglednem rdečem zaslonu, vključno z napakami v predlogah s poudarkom vrstice in stolpca ([video|https://github.com/nette/tracy/releases/tag/v2.9.0]). Hkrati se v desnem spodnjem kotu v t.i. Tracy Baru pojavi zavihek za Latte, kjer so pregledno vidne vse izrisane predloge in njihove medsebojne povezave (vključno z možnostjo klikanja v predlogo ali prevedeno kodo) ter tudi spremenljivke: [* latte-debugging.webp *] -Ker Latte predloge sestavi v berljivo kodo PHP, jih lahko priročno pregledujete v svojem IDE. +Ker Latte prevaja predloge v pregledno PHP kodo, jih lahko udobno korakate v svojem IDE. -Linter: Preverjanje sintakse predloge: Linter: preverjanje sintakse predloge .{data-version:2.11}{toc: Linter} -============================================================================================================== +Linter: validacija sintakse predlog .{toc: Linter} +================================================== -Orodje Linter vam bo pomagalo pregledati vse predloge in preveriti napake v sintaksi. Začne se iz konzole: +Pregledati vse predloge in preveriti, ali ne vsebujejo sintaktičnih napak, vam pomaga orodje Linter. Zažene se iz konzole: ```shell -vendor/bin/latte-lint +vendor/bin/latte-lint ``` -Če uporabljate oznake po meri, ustvarite tudi svoj prilagojeni Linter, npr. `custom-latte-lint`: +S parametrom `--strict` aktivirate [#strogi način]. + +Če uporabljate lastne značke, si ustvarite tudi lastno različico Linterja, npr. `custom-latte-lint`: ```php #!/usr/bin/env php scanDirectory($path); +$path = $argv[1] ?? '.'; -$engine = new Latte\Engine; -// registrira posamezne razširitve tukaj -$engine->addExtension(/* ... */); +$linter = new Latte\Tools\Linter; +$latte = $linter->getEngine(); +// tukaj dodajte posamezne svoje razširitve +$latte->addExtension(/* ... */); -$path = $argv[1]; -$linter = new Latte\Tools\Linter(engine: $engine); $ok = $linter->scanDirectory($path); exit($ok ? 0 : 1); ``` +Alternativno lahko lasten objekt `Latte\Engine` predate v Linter: -Nalaganje predlog iz niza .[#toc-loading-templates-from-a-string] -================================================================= +```php +$latte = new Latte\Engine; +// tukaj konfiguriramo objekt $latte +$linter = new Latte\Tools\Linter(engine: $latte); +``` + + +Nalaganje predlog iz niza +========================= -Potrebujete naložiti predloge iz nizov namesto iz datotek, morda za namene testiranja? [StringLoader |extending-latte#stringloader] vam bo pomagal: +Potrebujete nalagati predloge iz nizov namesto datotek, na primer za namene testiranja? Pomagal vam bo [StringLoader |loaders#StringLoader]: ```php $latte->setLoader(new Latte\Loaders\StringLoader([ @@ -262,10 +318,10 @@ $latte->render('main.file', $params); ``` -Obvladovalec izjem .[#toc-exception-handler] -============================================ +Obravnavalnik izjem +=================== -Za pričakovane izjeme lahko določite svoj lasten izvajalec. Izjeme, ki se pojavijo znotraj [`{try}` |tags#try] in v [peskovniku |sandbox], se mu posredujejo. +Lahko si definirate lasten obravnavalni handler za pričakovane izjeme. Predale se mu bodo izjeme, nastale znotraj [`{try}` |tags#try] in v [peskovniku|sandbox]. ```php $loggingHandler = function (Throwable $e, Latte\Runtime\Template $template) use ($logger) { @@ -277,17 +333,17 @@ $latte->setExceptionHandler($loggingHandler); ``` -Samodejno iskanje postavitve .[#toc-automatic-layout-lookup] -============================================================ +Samodejno iskanje postavitve +============================ -Uporaba oznake [`{layout}` |template-inheritance#layout-inheritance] predloga določi svojo nadrejeno predlogo. Možno je tudi, da se postavitev išče samodejno, kar bo poenostavilo pisanje predlog, saj jim ne bo treba vključevati oznake `{layout}`. +S pomočjo značke [`{layout}` |template-inheritance#Dedovanje postavitve] predloga določa svojo starševsko predlogo. Mogoče je tudi pustiti samodejno iskanje postavitve, kar poenostavi pisanje predlog, saj v njih ne bo treba navajati značke `{layout}`. To dosežemo na naslednji način: ```php $finder = function (Latte\Runtime\Template $template) { if (!$template->getReferenceType()) { - // vrne pot do nadrejene datoteke predloge + // vrača pot do datoteke s postavitvijo return 'automatic.layout.latte'; } }; @@ -296,4 +352,4 @@ $latte = new Latte\Engine; $latte->addProvider('coreParentFinder', $finder); ``` -Če predloga ne sme imeti postavitve, bo to označeno z oznako `{layout none}`. +Če predloga ne sme imeti postavitve, to sporoči z značko `{layout none}`. diff --git a/latte/sl/extending-latte.texy b/latte/sl/extending-latte.texy index b2a475b0ae..71a4c6947d 100644 --- a/latte/sl/extending-latte.texy +++ b/latte/sl/extending-latte.texy @@ -1,285 +1,227 @@ -Podaljšanje Latte +Razširjanje Latte ***************** .[perex] -Latte je zelo prilagodljiv in ga je mogoče razširiti na več načinov: dodajate lahko filtre, funkcije, oznake, nalagalnike itd. Pokazali vam bomo, kako to storiti. +Latte je zasnovan z mislijo na razširljivost. Čeprav njegov standardni nabor značk, filtrov in funkcij pokriva številne primere uporabe, pogosto potrebujete dodati lastno specifično logiko ali pomožna orodja. Ta stran ponuja pregled načinov, kako razširiti Latte, da bo popolnoma ustrezal zahtevam vašega projekta - od preprostih pomočnikov do kompleksne nove sintakse. -V tem poglavju so opisani različni načini razširitve sistema Latte. Če želite svoje spremembe ponovno uporabiti v različnih projektih ali če jih želite deliti z drugimi, morate nato [ustvariti tako imenovano razširitev. |creating-extension] +Načini razširitve Latte +======================= -Koliko poti vodi v Rim? .[#toc-how-many-roads-lead-to-rome] -=========================================================== +Tukaj je hiter pregled glavnih načinov, kako lahko prilagodite in razširite Latte: -Ker se lahko nekateri načini podaljševanja latte mešajo, poskušajmo najprej razložiti razlike med njimi. Kot primer poskusimo implementirati generator *Lorem ipsum*, ki mu posredujemo število besed za generiranje. +- **[Filtri po meri |Custom Filters]:** Za formatiranje ali preoblikovanje podatkov neposredno v izpisu predloge (npr. `{$var|myFilter}`). Idealno za naloge, kot so formatiranje datumov, urejanje besedila ali uporaba specifičnega ubežanja znakov. Uporabite jih lahko tudi za urejanje večjih blokov vsebine HTML tako, da vsebino ovijete v anonimni [`{block}` |tags#block] in nanjo uporabite filter po meri. +- **[Funkcije po meri |Custom Functions]:** Za dodajanje ponovno uporabne logike, ki jo je mogoče klicati znotraj izrazov v predlogi (npr. `{myFunction($arg1, $arg2)}`). Uporabno za izračune, dostop do pomožnih funkcij aplikacije ali generiranje majhnih delov vsebine. +- **[Oznake po meri |Custom Tags]:** Za ustvarjanje popolnoma novih jezikovnih konstruktov (`{mytag}...{/mytag}` ali `n:mytag`). Oznake ponujajo največ možnosti, omogočajo definiranje lastnih struktur, nadzor nad razčlenjevanjem predloge in implementacijo kompleksne logike izrisa. +- **[Prevajalni prehodi |Compiler Passes]:** Funkcije, ki spreminjajo abstraktno sintaktično drevo (AST) predloge po razčlenjevanju, vendar pred generiranjem kode PHP. Uporabljajo se za napredne optimizacije, varnostne preglede (kot je Sandbox) ali samodejne spremembe kode. +- **[Nalagatelji po meri |loaders]:** Za spremembo načina, kako Latte išče in nalaga datoteke predlog (npr. nalaganje iz baze podatkov, šifriranega pomnilnika itd.). -Glavni konstrukt jezika Latte je oznaka. Generator lahko implementiramo tako, da razširimo program Latte z novo značko: +Izbira prave metode razširitve je ključna. Preden ustvarite zapleteno značko, razmislite, ali bi zadostoval preprostejši filter ali funkcija. Poglejmo si primer: implementacija generatorja *Lorem ipsum*, ki kot argument sprejme število besed za generiranje. -```latte -{lipsum 40} -``` - -Oznaka bo delovala odlično. Vendar generator v obliki oznake morda ne bo dovolj prilagodljiv, saj ga ne bo mogoče uporabiti v izrazu. Mimogrede, v praksi redko potrebujete generiranje značk; in to je dobra novica, saj so značke bolj zapleten način razširjanja. - -Ok, poskusite ustvariti filter namesto oznake: - -```latte -{=40|lipsum} -``` - -Tudi to je veljavna možnost. Toda filter mora posredovano vrednost spremeniti v nekaj drugega. V tem primeru uporabimo vrednost `40`, ki označuje število ustvarjenih besed, kot argument filtra in ne kot vrednost, ki jo želimo pretvoriti. +- **Kot značka?** `{lipsum 40}` - Možno, vendar so značke primernejše za krmilne strukture ali generiranje zapletenih oznak. Značk ni mogoče uporabiti neposredno v izrazih. +- **Kot filter?** `{=40|lipsum}` - Tehnično deluje, vendar so filtri namenjeni *preoblikovanju* vhodne vrednosti. Tukaj je `40` *argument*, ne vrednost, ki se preoblikuje. To se zdi semantično napačno. +- **Kot funkcija?** `{lipsum(40)}` - To je najbolj naravna rešitev! Funkcije sprejemajo argumente in vračajo vrednosti, kar je idealno za uporabo v katerem koli izrazu: `{var $text = lipsum(40)}`. -Zato poskusimo uporabiti funkcijo: +**Splošno priporočilo:** Uporabljajte funkcije za izračune/generiranje, filtre za preoblikovanje in značke za nove jezikovne konstrukte ali zapletene oznake. Prehode uporabljajte za manipulacijo z AST in nalagatelje za pridobivanje predlog. -```latte -{lipsum(40)} -``` -To je to! Za ta primer je ustvarjanje funkcije idealna razširitvena točka, ki jo lahko uporabimo. Pokličete jo lahko povsod, kjer je na primer sprejet izraz: +Neposredna registracija +======================= -```latte -{var $text = lipsum(40)} -``` +Za pomožna orodja, specifična za projekt, ali hitre razširitve Latte omogoča neposredno registracijo filtrov in funkcij v objekt `Latte\Engine`. - -Filtri .[#toc-filters] -====================== - -Filter ustvarite tako, da registrirate njegovo ime in katerikoli klic PHP, na primer funkcijo: +Za registracijo filtra uporabite metodo `addFilter()`. Prvi argument vaše filtrirne funkcije bo vrednost pred znakom `|`, naslednji argumenti pa so tisti, ki se posredujejo za dvopičjem `:`. ```php $latte = new Latte\Engine; -$latte->addFilter('shortify', fn(string $s) => mb_substr($s, 0, 10)); // skrajša besedilo na 10 znakov -``` -V tem primeru bi bilo bolje, če bi filter dobil dodaten parameter: +// Definicija filtra (klicni objekt: funkcija, statična metoda itd.) +$myTruncate = fn(string $s, int $length = 50) => mb_substr($s, 0, $length); -```php -$latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); -``` - -Uporabljamo ga v predlogi, kot je ta: +// Registracija +$latte->addFilter('truncate', $myTruncate); -```latte -

                                                                                                                    {$text|shortify}

                                                                                                                    -

                                                                                                                    {$text|shortify:100}

                                                                                                                    +// Uporaba v predlogi: {$text|truncate} ali {$text|truncate:100} ``` -Kot lahko vidite, funkcija kot naslednje argumente prejme levo stran filtra pred cevjo `|` as the first argument and the arguments passed to the filter after `:`. - -Seveda lahko funkcija, ki predstavlja filter, sprejme poljubno število parametrov, podprti pa so tudi variabilni parametri. - - -Filtri z uporabo razreda .[#toc-filters-using-the-class] --------------------------------------------------------- - -Drugi način za opredelitev filtra je [uporaba razreda |develop#Parameters as a class]. Ustvarimo metodo z atributom `TemplateFilter`: +Lahko registrirate tudi **Nalagatelj filtrov (Filter Loader)**, funkcijo, ki dinamično zagotavlja klicne objekte filtrov glede na zahtevano ime: ```php -class TemplateParameters -{ - public function __construct( - // parametri - ) {} - - #[Latte\Attributes\TemplateFilter] - public function shortify(string $s, int $len = 10): string - { - return mb_substr($s, 0, $len); - } -} - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); +$latte->addFilterLoader(fn(string $name) => /* vrne klicni objekt ali null */); ``` -Če uporabljate PHP 7.x in Latte 2.x, namesto atributa uporabite opombo `/** @filter */`. - -Nalaganje filtrov .[#toc-filter-loader] ---------------------------------------- - -Namesto registracije posameznih filtrov lahko ustvarite tako imenovani nalagalnik, ki je funkcija, ki se pokliče z imenom filtra kot argumentom in vrne njegov klicni parameter PHP ali nič. +Za registracijo funkcije, uporabne v izrazih predloge, uporabite `addFunction()`. ```php -$latte->addFilterLoader([new Filters, 'load']); +$latte = new Latte\Engine; +// Definicija funkcije +$isWeekend = fn(DateTimeInterface $date) => $date->format('N') >= 6; -class Filters -{ - public function load(string $filter): ?callable - { - if (in_array($filter, get_class_methods($this))) { - return [$this, $filter]; - } - return null; - } - - public function shortify($s, $len = 10) - { - return mb_substr($s, 0, $len); - } - - // ... -} +// Registracija +$latte->addFunction('isWeekend', $isWeekend); + +// Uporaba v predlogi: {if isWeekend($myDate)}Vikend!{/if} ``` +Več informacij najdete v razdelku [Ustvarjanje filtrov po meri |custom-filters] in [Funkcij |custom-functions]. -Kontekstualni filtri .[#toc-contextual-filters] ------------------------------------------------ -Kontekstualni filter je filter, ki v prvem parametru sprejme predmet [api:Latte\Runtime\FilterInfo], nato pa mu sledijo drugi parametri kot pri klasičnih filtrih. Registrira se na enak način, Latte sam prepozna, da je filter kontekstualen: +Robusten način: Razširitev Latte .{toc: Latte Extension} +======================================================== -```php -use Latte\Runtime\FilterInfo; +Medtem ko je neposredna registracija preprosta, je standardni in priporočeni način za pakiranje in distribucijo razširitev Latte prek razredov **Extension**. Razširitev služi kot osrednja konfiguracijska točka za registracijo več značk, filtrov, funkcij, prevajalnih prehodov in drugih elementov. -$latte->addFilter('foo', function (FilterInfo $info, string $str): string { - // ... -}); -``` +Zakaj uporabljati razširitve? -Kontekstni filtri lahko zaznajo in spremenijo vrsto vsebine, ki jo prejmejo v spremenljivki `$info->contentType`. Če je filter klican klasično nad spremenljivko (npr. `{$var|foo}`), bo `$info->contentType` vsebovala ničlo. +- **Organizacija:** Ohranja povezane razširitve (značke, filtri itd. za določeno funkcijo) skupaj v enem razredu. +- **Ponovna uporabnost in deljenje:** Enostavno zapakirate svoje razširitve za uporabo v drugih projektih ali za deljenje s skupnostjo (npr. prek Composerja). +- **Polna moč:** Značke po meri in prevajalne prehode *lahko registrirate samo* prek razširitev. -Filter mora najprej preveriti, ali je vrsta vsebine vhodnega niza podprta. Lahko ga tudi spremeni. Primer filtra, ki sprejme besedilo (ali ničlo) in vrne HTML: + +Registracija razširitve +----------------------- + +Razširitev se registrira v Latte z metodo `addExtension()` (ali prek [konfiguracijske datoteke |application:configuration#Predloge Latte]): ```php -use Latte\Runtime\FilterInfo; - -$latte->addFilter('money', function (FilterInfo $info, float $amount): string { - // najprej preverimo, ali je tip vsebine vnosa besedilo. - if (!in_array($info->contentType, [null, ContentType::Text])) { - throw new Exception("Filter |money used in incompatible content type $info->contentType."); - } - - // spremenimo tip vsebine v HTML - $info->contentType = ContentType::Html; - return "$num Kč"; -}); +$latte = new Latte\Engine; +$latte->addExtension(new MyProjectExtension); ``` -.[note] -V tem primeru mora filter zagotoviti pravilno escapiranje podatkov. +Če registrirate več razširitev in te definirajo enako poimenovane značke, filtre ali funkcije, ima prednost zadnja dodana razširitev. To tudi pomeni, da lahko vaše razširitve prepišejo izvorne značke/filtre/funkcije. -Vsi filtri, ki se uporabljajo nad [bloki |tags#block] (npr. kot `{block|foo}...{/block}`) morajo biti kontekstualni. +Kadar koli naredite spremembo v razredu in samodejno osveževanje ni izklopljeno, bo Latte samodejno ponovno prevedel vaše predloge. -Funkcije .[#toc-functions] -========================== +Ustvarjanje razširitve +---------------------- -Privzeto lahko v Latte uporabljate vse izvorne funkcije PHP, razen če je to v peskovniku onemogočeno. Lahko pa določite tudi lastne funkcije. Te lahko prekrijejo izvorne funkcije. +Za ustvarjanje lastne razširitve morate ustvariti razred, ki deduje od [api:Latte\Extension]. Za predstavo, kako taka razširitev izgleda, si oglejte vgrajeno [CoreExtension](https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php). -Funkcijo ustvarite tako, da vpišete njeno ime in kateri koli klic PHP: +Poglejmo si metode, ki jih lahko implementirate: -```php -$latte = new Latte\Engine; -$latte->addFunction('random', function (...$args) { - return $args[array_rand($args)]; -}); -``` -Uporaba je nato enaka kot pri klicanju funkcije PHP: +beforeCompile(Latte\Engine $engine): void .[method] +--------------------------------------------------- -```latte -{random(apple, orange, lemon)} // prints for example: apple -``` +Pokliče se pred prevajanjem predloge. Metodo lahko uporabite na primer za inicializacije, povezane s prevajanjem. -Funkcije, ki uporabljajo razred .[#toc-functions-using-the-class] ------------------------------------------------------------------ +getTags(): array .[method] +-------------------------- -Drugi način opredelitve funkcije je [uporaba razreda |develop#Parameters as a class]. Ustvarimo metodo z atributom `TemplateFunction`: +Pokliče se med prevajanjem predloge. Vrne asociativno polje *ime značke => klicni objekt*, kar so funkcije za razčlenjevanje značk. [Več informacij |custom-tags]. ```php -class TemplateParameters +public function getTags(): array { - public function __construct( - // parametri - ) {} - - #[Latte\Attributes\TemplateFunction] - public function random(...$args) - { - return $args[array_rand($args)]; - } + return [ + 'foo' => FooNode::create(...), + 'bar' => BarNode::create(...), + 'n:baz' => NBazNode::create(...), + // ... + ]; } - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); ``` -Če uporabljate PHP 7.x in Latte 2.x, namesto atributa uporabite opombo `/** @function */`. +Značka `n:baz` predstavlja čisti [n:atribut |syntax#n:atributi], torej značko, ki jo je mogoče zapisati samo kot atribut. +Pri značkah `foo` in `bar` Latte samodejno prepozna, ali gre za parne značke, in če da, jih je mogoče samodejno zapisati z n:atributi, vključno z različicami s predponami `n:inner-foo` in `n:tag-foo`. -Nalagalniki .[#toc-loaders] -=========================== +Vrstni red izvajanja takšnih n:atributov je določen z njihovim vrstnim redom v polju, ki ga vrne metoda `getTags()`. Tako se `n:foo` vedno izvede pred `n:bar`, tudi če so atributi v HTML oznaki navedeni v obratnem vrstnem redu kot `
                                                                                                                    `. -Nalagalniki so odgovorni za nalaganje predlog iz vira, kot je datotečni sistem. Nastavijo se z metodo `setLoader()`: +Če morate določiti vrstni red n:atributov med več razširitvami, uporabite pomožno metodo `order()`, kjer parameter `before` xor `after` določa, katere značke so razvrščene pred ali za značko. ```php -$latte->setLoader(new MyLoader); +public function getTags(): array +{ + return [ + 'foo' => self::order(FooNode::create(...), before: 'bar'), + 'bar' => self::order(BarNode::create(...), after: ['block', 'snippet']), + ]; +} ``` -Vgrajeni nalagalniki so: +getPasses(): array .[method] +---------------------------- -FileLoader .[#toc-fileloader] ------------------------------ +Pokliče se med prevajanjem predloge. Vrne asociativno polje *ime prehoda => klicni objekt*, kar so funkcije, ki predstavljajo t.i. [prevajalni prehodi |compiler-passes], ki prehajajo in spreminjajo AST. -Privzeta naprava za nalaganje. Naloži predloge iz datotečnega sistema. - -Dostop do datotek lahko omejite z nastavitvijo osnovnega imenika: +Tudi tukaj lahko uporabite pomožno metodo `order()`. Vrednost parametrov `before` ali `after` je lahko `*` s pomenom pred/po vseh. ```php -$latte->setLoader(new Latte\Loaders\FileLoader($templateDir)); -$latte->render('test.latte'); +public function getPasses(): array +{ + return [ + 'optimize' => Passes::optimizePass(...), + 'sandbox' => self::order($this->sandboxPass(...), before: '*'), + // ... + ]; +} ``` -StringLoader .[#toc-stringloader] ---------------------------------- +beforeRender(Latte\Engine $engine): void .[method] +-------------------------------------------------- -Naloži predloge iz nizov. Ta nalagalnik je zelo uporaben za testiranje enot. Uporablja se lahko tudi pri manjših projektih, kjer je morda smiselno vse predloge shraniti v eno samo datoteko PHP. +Pokliče se pred vsakim izrisom predloge. Metodo lahko uporabite na primer za inicializacijo spremenljivk, ki se uporabljajo med izrisom. -```php -$latte->setLoader(new Latte\Loaders\StringLoader([ - 'main.file' => '{include other.file}', - 'other.file' => '{if true} {$var} {/if}', -])); -$latte->render('main.file'); -``` +getFilters(): array .[method] +----------------------------- -Poenostavljena uporaba: +Pokliče se pred izrisom predloge. Vrne filtre kot asociativno polje *ime filtra => klicni objekt*. [Več informacij |custom-filters]. ```php -$template = '{if true} {$var} {/if}'; -$latte->setLoader(new Latte\Loaders\StringLoader); -$latte->render($template); +public function getFilters(): array +{ + return [ + 'batch' => $this->batchFilter(...), + 'trim' => $this->trimFilter(...), + // ... + ]; +} ``` -Ustvarjanje polnilnika po meri .[#toc-creating-a-custom-loader] ---------------------------------------------------------------- - -Loader je razred, ki implementira vmesnik [api:Latte\Loader]. +getFunctions(): array .[method] +------------------------------- +Pokliče se pred izrisom predloge. Vrne funkcije kot asociativno polje *ime funkcije => klicni objekt*. [Več informacij |custom-functions]. -Oznake .[#toc-tags] -=================== +```php +public function getFunctions(): array +{ + return [ + 'clamp' => $this->clampFunction(...), + 'divisibleBy' => $this->divisibleByFunction(...), + // ... + ]; +} +``` -Ena od najzanimivejših funkcij mehanizma za oblikovanje predlog je možnost opredelitve novih jezikovnih konstrukcij z uporabo oznak. To je tudi bolj zapletena funkcionalnost, zato morate razumeti, kako Latte notranje deluje. -Vendar pa v večini primerov uporaba značk ni potrebna: -- Če naj bi ustvarila kakšen izhod, namesto tega uporabite [funkcijo |#functions] -- če naj bi spremenila nek vhodni podatek in ga vrnila, namesto tega uporabite [filter |#filters] -- če naj bi urejala območje besedila, ga ovijte z [`{block}` |tags#block] in uporabite [filter |#Contextual Filters] -- če ne bi smel ničesar izpisati, ampak samo poklicati funkcijo, jo pokličite z [`{do}` |tags#do] +getProviders(): array .[method] +------------------------------- -Če še vedno želite ustvariti oznako, odlično! Vse bistvene informacije so na voljo v poglavju [Ustvarjanje razširitve |creating-extension]. +Pokliče se pred izrisom predloge. Vrne polje ponudnikov, ki so običajno objekti, ki jih značke uporabljajo med izvajanjem. Dostopa se jim prek `$this->global->...`. [Več informacij |custom-tags#Predstavitev ponudnikov]. +```php +public function getProviders(): array +{ + return [ + 'myFoo' => $this->foo, + 'myBar' => $this->bar, + // ... + ]; +} +``` -Prehodi za prevajalnik .[#toc-compiler-passes] -============================================== -Prehodi za prevajalnik so funkcije, ki spreminjajo AST ali v njih zbirajo informacije. V programu Latte je na primer peskovnik izveden na ta način: prečka vsa vozlišča AST, poišče klice funkcij in metod ter jih nadomesti z nadzorovanimi klici. +getCacheKey(Latte\Engine $engine): mixed .[method] +-------------------------------------------------- -Tako kot pri oznakah gre za bolj zapleteno funkcionalnost, zato morate razumeti, kako Latte deluje pod pokrovom. Vse bistvene informacije najdete v poglavju [Ustvarjanje razširitve |creating-extension]. +Pokliče se pred izrisom predloge. Vračana vrednost postane del ključa, katerega zgoščena vrednost je vsebovana v imenu datoteke prevedene predloge. Za različne vračane vrednosti torej Latte generira različne datoteke predpomnilnika. diff --git a/latte/sl/filters.texy b/latte/sl/filters.texy index 34b8b58835..39036516ec 100644 --- a/latte/sl/filters.texy +++ b/latte/sl/filters.texy @@ -1,112 +1,114 @@ -Filtri za latte -*************** +Filtri Latte +************ .[perex] -Filtri so funkcije, ki spremenijo ali oblikujejo podatke v želeno obliko. To je povzetek vgrajenih filtrov, ki so na voljo. +V predlogah lahko uporabljamo funkcije, ki pomagajo urediti ali preoblikovati podatke v končno obliko. Imenujemo jih *filtri*. .[table-latte-filters] -|## Preoblikovanje nizov / polj -| `batch` | [izpis linearnih podatkov v tabeli |#batch] -| `breakLines` | [Vstavi prelome vrstic HTML pred vse nove vrstice |#breakLines] -| `bytes` | [oblikuje velikost v bajtih |#bytes] -| `clamp` | [vpne vrednost v območje |#clamp] -| `dataStream` | [Pretvorba protokola URI podatkov |#datastream] -| `date` | [oblikuje datum |#date] -| `explode` | [razdeli niz z danim ločilom |#explode] -| `first` | [vrne prvi element polja ali znak niza |#first] -| `implode` | [združi polje z nizom |#implode] -| `indent` | [besedilo z leve strani odriva s številom tabulatorjev |#indent] -| `join` | [združi polje z nizom |#implode] -| `last` | [vrne zadnji element polja ali znak niza |#last] -| `length` | [vrne dolžino niza ali polja |#length] -| `number` | [oblikovanje števila |#number] -| `padLeft` | [dopolni niz do dane dolžine z leve |#padLeft] -| `padRight` | [dopolni niz do dane dolžine od desne |#padRight] -| `random` | [vrne naključni element polja ali znak niza |#random] -| `repeat` | [ponovi niz |#repeat] -| `replace` | [nadomesti vse pojavitve iskanega niza z zamenjavo |#replace] -| `replaceRE` | [nadomesti vse pojavitve v skladu z regularnim izrazom |#replaceRE] -| `reverse` | [obrne niz ali polje UTF-8 |#reverse] -| `slice` | [izvleče rezino polja ali niza |#slice] -| `sort` | [razvrsti polje |#sort] -| `spaceless` | [odstrani bele presledke |#spaceless], podobno kot pri oznaki [brez presledkov |tags] -| `split` | [razdeli niz z danim ločilom |#explode] -| `strip` | [odstrani bele prostore |#spaceless] -| `stripHtml` | [odstrani oznake HTML in pretvori entitete HTML v besedilo |#stripHtml] -| `substr` | [vrne del niza |#substr] -| `trim` | [odstrani bele prostore iz niza |#trim] -| `translate` | [prevajanje v druge jezike |#translate] -| `truncate` | [skrajša dolžino, pri čemer ohrani cele besede |#truncate] -| `webalize` | [prilagodi niz UTF-8 obliki, ki se uporablja v naslovu URL |#webalize] +|## Transformacije +| `batch` | [izpis linearnih podatkov v tabelo |#batch] +| `breakLines` | [Pred konce vrstic doda HTML prelom vrstice |#breakLines] +| `bytes` | [formatira velikost v bajtih |#bytes] +| `clamp` | [omeji vrednost na dano območje |#clamp] +| `dataStream` | [pretvorba za protokol Data URI |#dataStream] +| `date` | [formatira datum in čas |#date] +| `explode` | [razdeli niz na polje glede na ločilo |#explode] +| `first` | [vrne prvi element polja ali znak niza |#first] +| `group` | [združi podatke po različnih kriterijih |#group] +| `implode` | [združi polje v niz |#implode] +| `indent` | [zamika besedilo z leve za dano število tabulatorjev |#indent] +| `join` | [združi polje v niz |#implode] +| `last` | [vrne zadnji element polja ali znak niza |#last] +| `length` | [vrne dolžino niza v znakih ali polje |#length] +| `localDate` | [formatira datum in čas glede na lokalne nastavitve |#localDate] +| `number` | [formatira število |#number] +| `padLeft` | [dopolni niz z leve na zahtevano dolžino |#padLeft] +| `padRight` | [dopolni niz z desne na zahtevano dolžino |#padRight] +| `random` | [vrne naključni element polja ali znak niza |#random] +| `repeat` | [ponavljanje niza |#repeat] +| `replace` | [zamenja pojavitve iskanega niza |#replace] +| `replaceRE` | [zamenja pojavitve po regularnem izrazu |#replaceRE] +| `reverse` | [obrne niz UTF‑8 ali polje |#reverse] +| `slice` | [izvleče del polja ali niza |#slice] +| `sort` | [razvrsti polje |#sort] +| `spaceless` | [odstrani presledke |#spaceless], podobno kot značka [spaceless |tags] +| `split` | [razdeli niz na polje glede na ločilo |#explode] +| `strip` | [odstrani presledke |#spaceless] +| `stripHtml` | [odstrani oznake HTML in entitete HTML pretvori v znake |#stripHtml] +| `substr` | [vrne del niza |#substr] +| `trim` | [odstrani začetne in končne presledke ali druge znake |#trim] +| `translate` | [prevod v druge jezike |#translate] +| `truncate` | [skrajša dolžino z ohranjanjem besed |#truncate] +| `webalize` | [prilagodi niz UTF‑8 v obliko, ki se uporablja v URL-jih |#webalize] .[table-latte-filters] -|### Oblikovanje črk -| `capitalize` | [male črke, prva črka vsake besede velika črka |#capitalize] -| `firstUpper` | [prva črka je velika |#firstUpper] -| `lower` | [niz postane mala črka|#lower] -| `upper` | [spremeni niz v velike črke |#upper] +|## Velikost črk +| `capitalize` | [male črke, prva črka v besedah velika |#capitalize] +| `firstUpper` | [pretvori prvo črko v veliko |#firstUpper] +| `lower` | [pretvori v male črke |#lower] +| `upper` | [pretvori v velike črke |#upper] .[table-latte-filters] -|## Zaokroževanje številk -| `ceil` | [zaokroži število do določene natančnosti |#ceil] -| `floor` | [zaokroži število navzdol na določeno natančnost |#floor] -| `round` | [zaokroži število na določeno natančnost |#round] +|## Zaokroževanje +| `ceil` | [zaokroži število navzgor na dano natančnost |#ceil] +| `floor` | [zaokroži število navzdol na dano natančnost |#floor] +| `round` | [zaokroži število na dano natančnost |#round] .[table-latte-filters] -|### Escaping -| `escapeUrl` | [pobegne parameter v naslovu URL |#escapeUrl] -| `noescape` | [natisne spremenljivko brez umika |#noescape] -| `query` | [ustvari poizvedbeni niz v naslovu URL |#query] +|## Ubežanje znakov +| `escapeUrl` | [ubeža parameter v URL |#escapeUrl] +| `noescape` | [izpiše spremenljivko brez ubežanja |#noescape] +| `query` | [generira poizvedbeni niz v URL |#query] -Na voljo so tudi filtri za eskapiranje za HTML (`escapeHtml` in `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) in iCalendar (`escapeICal`), ki jih Latte uporablja sam zaradi [kontekstnega eskapiranja |safety-first#Context-aware escaping] in vam jih ni treba pisati. +Poleg tega obstajajo filtri za ubežanje znakov za HTML (`escapeHtml` in `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) in iCalendar (`escapeICal`), ki jih Latte uporablja samo zaradi [kontekstno občutljivega ubežanja |safety-first#Kontekstno občutljivo ubežanje] in jih ni treba zapisovati. .[table-latte-filters] |## Varnost -| `checkUrl` | [odpravi niz za uporabo znotraj atributa href |#checkUrl] -| `nocheck` | [preprečuje samodejno čiščenje URL |#nocheck] +| `checkUrl` | [obdela naslov URL pred nevarnimi vnosi |#checkUrl] +| `nocheck` | [prepreči samodejno obdelavo naslova URL |#nocheck] -Latte atributa `src` in `href` [preverja samodejno |safety-first#link checking], zato vam skoraj ni treba uporabljati filtra `checkUrl`. +Latte atributa `src` in `href` [samodejno preverja |safety-first#Preverjanje povezav], zato filtra `checkUrl` skoraj ni treba uporabljati. .[note] -Vsi vgrajeni filtri delujejo z nizi, kodiranimi v UTF-8. +Vsi privzeti filtri so namenjeni nizom v kodiranju UTF‑8. -Uporaba .[#toc-usage] -===================== +Uporaba +======= -Latte omogoča klicanje filtrov z uporabo zapisa z znakom pipe (pred njim je dovoljen presledek): +Filtri se zapisujejo za navpičnico (pred njo je lahko presledek): ```latte

                                                                                                                    {$heading|upper}

                                                                                                                    ``` -Filtri se lahko verižijo, v tem primeru se uporabljajo po vrstnem redu od leve proti desni: +Filtre (v starejših različicah helperje) je mogoče verižiti, nato pa se uporabljajo v vrstnem redu od leve proti desni: ```latte

                                                                                                                    {$heading|lower|capitalize}

                                                                                                                    ``` -Parametri so navedeni za imenom filtra, ločeni z dvopičjem ali vejico: +Parametri se vnašajo za imenom filtra, ločeni z dvopičji ali vejicami: ```latte

                                                                                                                    {$heading|truncate:20,''}

                                                                                                                    ``` -Filtre je mogoče uporabiti na izrazu: +Filtre je mogoče uporabiti tudi na izrazu: ```latte {var $name = ($title|upper) . ($subtitle|lower)} ``` -[Filtre po meri |extending-latte#filters] je mogoče registrirati na ta način: +[Filtre po meri |custom-filters] se lahko registrirajo na ta način: ```php $latte = new Latte\Engine; $latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -Uporabimo ga v predlogi, kot je ta: +V predlogi se nato kliče takole: ```latte

                                                                                                                    {$text|shortify}

                                                                                                                    @@ -114,13 +116,13 @@ Uporabimo ga v predlogi, kot je ta: ``` -Filtri .[#toc-filters] -====================== +Filtri +====== -batch(int length, mixed item): array .[filter]{data-version:2.7} ----------------------------------------------------------------- -Filter, ki poenostavi navajanje linearnih podatkov v obliki tabele. Vrne polje z danim številom elementov. Če navedete drugi parameter, se ta uporabi za dopolnitev manjkajočih elementov v zadnji vrstici. +batch(int $length, mixed $item): array .[filter] +------------------------------------------------ +Filter, ki poenostavlja izpis linearnih podatkov v obliki tabele. Vrne polje polj z določenim številom elementov. Če podate drugi parameter, se uporabi za dopolnitev manjkajočih elementov v zadnji vrstici. ```latte {var $items = ['a', 'b', 'c', 'd', 'e']} @@ -135,7 +137,7 @@ Filter, ki poenostavi navajanje linearnih podatkov v obliki tabele. Vrne polje z
                                                                                                                    Notranja zanka
                                                                                                                    ``` -Izpisuje: +Izpiše: ```latte @@ -152,74 +154,76 @@ Izpisuje:
                                                                                                                    ``` +Glejte tudi [#group] in značko [iterateWhile |tags#iterateWhile]. + breakLines .[filter] -------------------- -Pred vse nove vrstice vstavi prelome vrstic HTML. +Doda pred vsak znak nove vrstice oznako HTML `
                                                                                                                    `. ```latte {var $s = "Text & with \n newline"} -{$s|breakLines} {* outputs "Text & with
                                                                                                                    \n newline" *} +{$s|breakLines} {* izpiše "Text & with
                                                                                                                    \n newline" *} ``` -bytes(int precision = 2) .[filter] ----------------------------------- -Oblikuje velikost v bajtih v človeku berljivo obliko. +bytes(int $precision=2) .[filter] +--------------------------------- +Formatira velikost v bajtih v človeku berljivo obliko. Če so nastavljene [lokalne nastavitve |develop#Locale], se uporabijo ustrezna ločila za decimalna mesta in tisočice. ```latte -{$size|bytes} 0 B, 1.25 GB, … -{$size|bytes:0} 10 B, 1 GB, … +{$size|bytes} {* 0 B, 1.25 GB, … *} +{$size|bytes:0} {* 10 B, 1 GB, … *} ``` -ceil(int precision = 0) .[filter] ---------------------------------- -Zaokroži število do določene natančnosti. +ceil(int $precision=0) .[filter] +-------------------------------- +Zaokroži število navzgor na dano natančnost. ```latte -{=3.4|ceil} {* izhodi 4 *} -{=135.22|ceil:1} {* izhodi 135.3 *} -{=135.22|ceil:3} {* izhodi 135.22 *} +{=3.4|ceil} {* izpiše 4 *} +{=135.22|ceil:1} {* izpiše 135.3 *} +{=135.22|ceil:3} {* izpiše 135.22 *} ``` -Glej tudi [floor |#floor], [round |#round]. +Glejte tudi [#floor], [#round]. capitalize .[filter] -------------------- -Vrne različico vrednosti, ki je označena z naslovi. Besede se začnejo z velikimi črkami, vsi preostali znaki so mali. Zahteva razširitev PHP `mbstring`. +Besede se bodo začele z velikimi črkami, vsi preostali znaki bodo mali. Zahteva razširitev PHP `mbstring`. ```latte -{='i like LATTE'|capitalize} {* outputs 'I Like Latte' *} +{='i like LATTE'|capitalize} {* izpiše 'I Like Latte' *} ``` -Glej tudi [firstUpper |#firstUpper], [lower |#lower], [upper |#upper]. +Glejte tudi [#firstUpper], [#lower], [#upper]. checkUrl .[filter] ------------------ -Uveljavlja čiščenje URL-jev. Preveri, ali spremenljivka vsebuje spletni naslov URL (tj. protokol HTTP/HTTPS), in prepreči zapis povezav, ki bi lahko predstavljale varnostno tveganje. +Vsili obdelavo naslova URL. Preverja, ali spremenljivka vsebuje spletni URL (tj. protokol HTTP/HTTPS) in preprečuje izpis povezav, ki lahko predstavljajo varnostno tveganje. ```latte {var $link = 'javascript:window.close()'} -checked -unchecked +preverjeno +nepreverjeno ``` -Izpisuje: +Izpiše: ```latte -checked -unchecked +preverjeno +nepreverjeno ``` -Glej tudi [nocheck |#nocheck]. +Glejte tudi [#nocheck]. -clamp(int|float min, int|float max) .[filter]{data-version:2.9} ---------------------------------------------------------------- -Vrne vrednost, ki je omejena na vključujoče območje min in max. +clamp(int|float $min, int|float $max) .[filter] +----------------------------------------------- +Omeji vrednost na dano vključno območje min in max. ```latte {$level|clamp: 0, 255} @@ -228,17 +232,17 @@ Vrne vrednost, ki je omejena na vključujoče območje min in max. Obstaja tudi kot [funkcija |functions#clamp]. -dataStream(string mimetype = detect) .[filter] ----------------------------------------------- -Pretvori vsebino v podatkovno shemo URI. Uporablja se lahko za vstavljanje slik v HTML ali CSS, ne da bi bilo treba povezovati zunanje datoteke. +dataStream(string $mimetype=detect) .[filter] +--------------------------------------------- +Pretvori vsebino v shemo data URI. Z njo je mogoče v HTML ali CSS vstavljati slike brez potrebe po povezovanju zunanjih datotek. Imejmo sliko v spremenljivki `$img = Image::fromFile('obrazek.gif')`, potem ```latte - + ``` -natisne na primer: +Izpiše na primer: ```latte {$name} ``` -Glej tudi [poizvedbo |#query]. +Glejte tudi [#query]. -explode(string separator = '') .[filter]{data-version:2.10.2} -------------------------------------------------------------- -Razdeli niz z danim ločilom in vrne polje nizov. Vzdevek za `split`. +explode(string $separator='') .[filter] +--------------------------------------- +Razdeli niz na polje glede na ločilo. Alias za `split`. ```latte -{='one,two,three'|explode:','} {* returns ['one', 'two', 'three'] *} +{='one,two,three'|explode:','} {* vrne ['one', 'two', 'three'] *} ``` -Če je ločilo prazen niz (privzeta vrednost), se vhodni niz razdeli na posamezne znake: +Če je ločilo prazen niz (privzeta vrednost), bo vhod razdeljen na posamezne znake: ```latte {='123'|explode} {* vrne ['1', '2', '3'] *} ``` -Uporabite lahko tudi vzdevek `split`: +Lahko uporabite tudi alias `split`: ```latte {='1,2,3'|split:','} {* vrne ['1', '2', '3'] *} ``` -Glej tudi [implode |#implode]. +Glejte tudi [#implode]. -first .[filter]{data-version:2.10.2} ------------------------------------- +first .[filter] +--------------- Vrne prvi element polja ali znak niza: ```latte -{=[1, 2, 3, 4]|first} {* izhodi 1 *} -{='abcd'|first} {* izhodi 'a' *} +{=[1, 2, 3, 4]|first} {* izpiše 1 *} +{='abcd'|first} {* izpiše 'a' *} ``` -Glej tudi [last |#last], [random |#random]. +Glejte tudi [#last], [#random]. -floor(int precision = 0) .[filter] ----------------------------------- -Zaokroži število do določene natančnosti. +floor(int $precision=0) .[filter] +--------------------------------- +Zaokroži število navzdol na dano natančnost. ```latte -{=3.5|floor} {* izhodi 3 *} -{=135.79|floor:1} {* izhodi 135.7 *} -{=135.79|floor:3} {* izhodi 135.79 *} +{=3.5|floor} {* izpiše 3 *} +{=135.79|floor:1} {* izpiše 135.7 *} +{=135.79|floor:3} {* izpiše 135.79 *} ``` -Glejte tudi [strop |#ceil], [zaokrožitev |#round]. +Glejte tudi [#ceil], [#round]. firstUpper .[filter] -------------------- -Prvo črko vrednosti pretvori v veliko črko. Zahteva razširitev PHP `mbstring`. +Pretvori prvo črko v veliko. Zahteva razširitev PHP `mbstring`. ```latte -{='the latte'|firstUpper} {* outputs 'The latte' *} +{='the latte'|firstUpper} {* izpiše 'The latte' *} ``` -Oglejte si tudi [velika začetnica |#capitalize], [mala |#lower] [začetnica |#capitalize], [velika |#upper] začetnica. +Glejte tudi [#capitalize], [#lower], [#upper]. -implode(string glue = '') .[filter] ------------------------------------ -Vrne niz, ki je sestavljen iz nizov v polju. Vzdevek za `join`. +group(string|int|\Closure $by): array .[filter]{data-version:3.0.16} +-------------------------------------------------------------------- +Filter združi podatke po različnih kriterijih. + +V tem primeru se vrstice v tabeli združujejo po stolpcu `categoryId`. Izpis je polje polj, kjer je ključ vrednost v stolpcu `categoryId`. [Preberite podrobna navodila |cookbook/grouping]. ```latte -{=[1, 2, 3]|implode} {* izhodi '123' *} -{=[1, 2, 3]|implode:'|'} {* izhodi '1|2|3' *} +{foreach ($items|group: categoryId) as $categoryId => $categoryItems} +
                                                                                                                      + {foreach $categoryItems as $item} +
                                                                                                                    • {$item->name}
                                                                                                                    • + {/foreach} +
                                                                                                                    +{/foreach} ``` -Uporabite lahko tudi vzdevek `join`: .{data-version:2.10.2} +Glejte tudi [#batch], funkcijo [group |functions#group] in značko [iterateWhile |tags#iterateWhile]. + + +implode(string $glue='') .[filter] +---------------------------------- +Vrne niz, ki je združitev elementov zaporedja. Alias za `join`. ```latte -{=[1, 2, 3]|join} {* izhodi '123' *} +{=[1, 2, 3]|implode} {* izpiše '123' *} +{=[1, 2, 3]|implode:'|'} {* izpiše '1|2|3' *} ``` +Lahko uporabite tudi alias `join`: + +```latte +{=[1, 2, 3]|join} {* izpiše '123' *} +``` -indent(int level = 1, string char = "\t") .[filter] ---------------------------------------------------- -Odmakne besedilo od leve strani za določeno število tabulatorjev ali drugih znakov, ki jih določimo v drugem neobveznem argumentu. Prazne vrstice se ne odrivajo. + +indent(int $level=1, string $char="\t") .[filter] +------------------------------------------------- +Zamika besedilo z leve za dano število tabulatorjev ali drugih znakov, ki jih lahko navedemo v drugem argumentu. Prazne vrstice niso zamaknjene. ```latte
                                                                                                                    @@ -358,7 +382,7 @@ Odmakne besedilo od leve strani za določeno število tabulatorjev ali drugih zn
                                                                                                                    ``` -Natisne: +Izpiše: ```latte
                                                                                                                    @@ -367,26 +391,26 @@ Natisne: ``` -last .[filter]{data-version:2.10.2} ------------------------------------ +last .[filter] +-------------- Vrne zadnji element polja ali znak niza: ```latte -{=[1, 2, 3, 4]|last} {* izhodi 4 *} -{='abcd'|last} {* izhodi 'd' *} +{=[1, 2, 3, 4]|last} {* izpiše 4 *} +{='abcd'|last} {* izpiše 'd' *} ``` -Glej tudi [first |#first], [random |#random]. +Glejte tudi [#first], [#random]. length .[filter] ---------------- Vrne dolžino niza ali polja. -- za nize vrne dolžino v znakih UTF-8 +- za nize vrne dolžino v znakih UTF‑8 - za polja vrne število elementov -- za predmete, ki implementirajo vmesnik Countable, bo uporabil povratno vrednost funkcije count() -- za predmete, ki implementirajo vmesnik IteratorAggregate, uporabi povratno vrednost iterator_count() +- za objekte, ki implementirajo vmesnik `Countable`, uporabi vračano vrednost metode `count()` +- za objekte, ki implementirajo vmesnik `IteratorAggregate`, uporabi vračano vrednost funkcije `iterator_count()` ```latte @@ -396,154 +420,264 @@ Vrne dolžino niza ali polja. ``` +localDate(?string $format=null, ?string $date=null, ?string $time=null) .[filter] +--------------------------------------------------------------------------------- +Formatira datum in čas glede na [lokalne nastavitve |develop#Locale], kar zagotavlja dosleden in lokaliziran prikaz časovnih podatkov med različnimi jeziki in regijami. Filter sprejema datum kot UNIX timestamp, niz ali objekt tipa `DateTimeInterface`. + +```latte +{$date|localDate} {* 15. april 2024 *} +{$date|localDate: format: yM} {* 4/2024 *} +{$date|localDate: date: medium} {* 15. 4. 2024 *} +``` + +Če uporabite filter brez parametrov, se izpiše datum na ravni `long`, glejte spodaj. + +**a) uporaba formata** + +Parameter `format` opisuje, katere časovne komponente naj se prikažejo. Uporablja črkovne kode, katerih število ponovitev vpliva na širino izpisa: + +| leto | `y` / `yy` / `yyyy` | `2024` / `24` / `2024` +| mesec | `M` / `MM` / `MMM` / `MMMM` | `8` / `08` / `avg` / `avgust` +| dan | `d` / `dd` / `E` / `EEEE` | `1` / `01` / `ned` / `nedelja` +| ura | `j` / `H` / `h` | prednostno / 24-urno / 12-urno +| minuta | `m` / `mm` | `5` / `05` (2 števki v kombinaciji s sekundami) +| sekunda | `s` / `ss` | `8` / `08` (2 števki v kombinaciji z minutami) + +Vrstni red kod v formatu ni pomemben, saj se vrstni red komponent izpiše glede na navade lokalnih nastavitev. Format je torej od njega neodvisen. Na primer, format `yyyyMMMMd` v okolju `en_US` izpiše `April 15, 2024`, medtem ko v okolju `sl_SI` izpiše `15. april 2024`: + +| locale: | sl_SI | en_US +|--- +| `format: 'dMy'` | 10. 8. 2024 | 8/10/2024 +| `format: 'yM'` | 8/2024 | 8/2024 +| `format: 'yyyyMMMM'` | avgust 2024 | August 2024 +| `format: 'MMMM'` | avgust | August +| `format: 'jm'` | 17:22 | 5:22 PM +| `format: 'Hm'` | 17:22 | 17:22 +| `format: 'hm'` | 5:22 pop. | 5:22 PM + + +**b) uporaba prednastavljenih stilov** + +Parametra `date` in `time` določata, kako podrobno naj se izpišeta datum in čas. Izbirate lahko med več ravnmi: `full`, `long`, `medium`, `short`. Lahko pustite izpisati samo datum, samo čas ali oboje: + +| locale: | sl_SI | en_US +|--- +| `date: short` | 23. 01. 78 | 1/23/78 +| `date: medium` | 23. 1. 1978 | Jan 23, 1978 +| `date: long` | 23. januar 1978 | January 23, 1978 +| `date: full` | ponedeljek, 23. januar 1978 | Monday, January 23, 1978 +| `time: short` | 8:30 | 8:30 AM +| `time: medium` | 8:30:59 | 8:30:59 AM +| `time: long` | 8:30:59 SEČ | 8:30:59 AM GMT+1 +| `date: short, time: short` | 23. 01. 78 8:30 | 1/23/78, 8:30 AM +| `date: medium, time: short` | 23. 1. 1978 8:30 | Jan 23, 1978, 8:30 AM +| `date: long, time: short` | 23. januar 1978 ob 8:30 | January 23, 1978 at 8:30 AM + +Pri datumu lahko dodatno uporabite predpono `relative-` (npr. `relative-short`), ki za datume blizu sedanjosti prikaže `včeraj`, `danes` ali `jutri`, sicer se izpiše na standardni način. + +```latte +{$date|localDate: date: relative-short} {* včeraj *} +``` + +Glejte tudi [#date]. + + lower .[filter] --------------- -Pretvori vrednost v male črke. Zahteva razširitev PHP `mbstring`. +Pretvori niz v male črke. Zahteva razširitev PHP `mbstring`. ```latte -{='LATTE'|lower} {* izhodi 'latte' *} +{='LATTE'|lower} {* izpiše 'latte' *} ``` -Glej tudi [capitalize |#capitalize], [firstUpper |#firstUpper], [upper |#upper]. +Glejte tudi [#capitalize], [#firstUpper], [#upper]. nocheck .[filter] ----------------- -Preprečuje samodejno prečiščevanje URL. Latte [samodejno preveri, |safety-first#Link checking] ali spremenljivka vsebuje spletni naslov URL (tj. protokol HTTP/HTTPS), in prepreči zapis povezav, ki bi lahko predstavljale varnostno tveganje. +Prepreči samodejno obdelavo naslova URL. Latte [samodejno preverja |safety-first#Preverjanje povezav], ali spremenljivka vsebuje spletni URL (tj. protokol HTTP/HTTPS) in preprečuje izpis povezav, ki lahko predstavljajo varnostno tveganje. -Če povezava uporablja drugo shemo, na primer `javascript:` ali `data:`, in ste prepričani o njeni vsebini, lahko preverjanje onemogočite prek `|nocheck`. +Če povezava uporablja drugo shemo, npr. `javascript:` ali `data:`, in ste prepričani o njeni vsebini, lahko preverjanje izklopite s pomočjo `|nocheck`. ```latte {var $link = 'javascript:window.close()'} -checked -unchecked +preverjeno +nepreverjeno ``` -Izpisi: +Izpiše: ```latte -checked -unchecked +preverjeno +nepreverjeno ``` -Glej tudi [checkUrl |#checkUrl]. +Glejte tudi [#checkUrl]. noescape .[filter] ------------------ -Onemogoči samodejno izrivanje. +Onemogoči samodejno ubežanje znakov. ```latte {var $trustedHtmlString = 'hello'} -Escaped: {$trustedHtmlString} -Unescaped: {$trustedHtmlString|noescape} +Ubežano: {$trustedHtmlString} +Neubežano: {$trustedHtmlString|noescape} ``` -Natisne: +Izpiše: ```latte -Escaped: <b>hello</b> -Unescaped: hello +Ubežano: <b>hello</b> +Neubežano: hello ``` .[warning] -Zloraba filtra `noescape` lahko privede do ranljivosti XSS! Nikoli ga ne uporabljajte, razen če ste **trdno prepričani**, kaj počnete in da niz, ki ga tiskate, prihaja iz zaupanja vrednega vira. +Napačna uporaba filtra `noescape` lahko vodi do nastanka ranljivosti XSS! Nikoli ga ne uporabljajte, če niste **popolnoma prepričani**, kaj počnete, in da izpisani niz prihaja iz zaupanja vrednega vira. -number(int decimals = 0, string decPoint = '.', string thousandsSep = ',') .[filter] ------------------------------------------------------------------------------------- -Oblikuje število na določeno število decimalnih mest. Določite lahko tudi znak za decimalno vejico in ločilo tisočic. +number(int $decimals=0, string $decPoint='.', string $thousandsSep=',') .[filter] +--------------------------------------------------------------------------------- +Formatira število na določeno število decimalnih mest. Če so nastavljene [lokalne nastavitve |develop#Locale], se uporabijo ustrezna ločila za decimalna mesta in tisočice. ```latte -{1234.20 |number} 1,234 -{1234.20 |number:1} 1,234.2 -{1234.20 |number:2} 1,234.20 -{1234.20 |number:2, ',', ' '} 1 234,20 +{1234.20|number} {* 1,234 *} +{1234.20|number:1} {* 1,234.2 *} +{1234.20|number:2} {* 1,234.20 *} +{1234.20|number:2, ',', ' '} {* 1 234,20 *} ``` -padLeft(int length, string pad = ' ') .[filter] +number(string $format) .[filter] +-------------------------------- +Parameter `format` omogoča definiranje videza števil natančno po vaših potrebah. Za to je treba imeti nastavljene [lokalne nastavitve |develop#Locale]. Format je sestavljen iz več posebnih znakov, katerih celoten opis najdete v dokumentaciji "DecimalFormat":https://unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns: + +- `0` obvezna števka, vedno se prikaže, tudi če je ničla +- `#` neobvezna števka, prikaže se samo, če na tem mestu število dejansko obstaja +- `@` pomembna števka, pomaga prikazati število z določenim številom veljavnih števk +- `.` označuje, kje naj bo decimalna vejica (ali pika, odvisno od države) +- `,` služi za ločevanje skupin števk, najpogosteje tisočic +- `%` število pomnoži s 100× in doda znak za odstotek + +Poglejmo si primere. V prvem primeru sta dve decimalni mesti obvezni, v drugem neobvezni. Tretji primer prikazuje dopolnjevanje z ničlami z leve in desne, četrti prikazuje samo obstoječe števke: + +```latte +{1234.5|number: '#,##0.00'} {* 1,234.50 *} +{1234.5|number: '#,##0.##'} {* 1,234.5 *} +{1.23 |number: '000.000'} {* 001.230 *} +{1.2 |number: '##.##'} {* 1.2 *} +``` + +Pomembne števke določajo, koliko števk, ne glede na decimalno vejico, naj bo prikazanih, pri čemer se zaokrožuje: + +```latte +{1234|number: '@@'} {* 1200 *} +{1234|number: '@@@'} {* 1230 *} +{1234|number: '@@@#'} {* 1234 *} +{1.2345|number: '@@@'} {* 1.23 *} +{0.00123|number: '@@'} {* 0.0012 *} +``` + +Enostaven način za prikaz števila kot odstotka. Število se pomnoži s 100× in doda se znak `%`: + +```latte +{0.1234|number: '#.##%'} {* 12.34% *} +``` + +Lahko definiramo različen format za pozitivna in negativna števila, loči jih znak `;`. Na ta način lahko na primer nastavimo, da se pozitivna števila prikazujejo z znakom `+`: + +```latte +{42|number: '#.##;(#.##)'} {* 42 *} +{-42|number: '#.##;(#.##)'} {* (42) *} +{42|number: '+#.##;-#.##'} {* +42 *} +{-42|number: '+#.##;-#.##'} {* -42 *} +``` + +Ne pozabite, da se dejanski videz števil lahko razlikuje glede na nastavitve države. Na primer, v nekaterih državah se uporablja vejica namesto pike kot ločilo decimalnih mest. Ta filter to samodejno upošteva in vam ni treba skrbeti za nič. + + +padLeft(int $length, string $pad=' ') .[filter] ----------------------------------------------- -Podaljša niz do določene dolžine z drugim nizom z leve. +Dopolni niz do določene dolžine z drugim nizom z leve. ```latte -{='hello'|padLeft: 10, '123'} {* izhodi '12312hello' *} +{='hello'|padLeft: 10, '123'} {* izpiše '12312hello' *} ``` -padRight(int length, string pad = ' ') .[filter] +padRight(int $length, string $pad=' ') .[filter] ------------------------------------------------ -Podloži niz na določeno dolžino z drugim nizom z desne. +Dopolni niz do določene dolžine z drugim nizom z desne. ```latte -{='hello'|padRight: 10, '123'} {* izpisi 'hello12312' *} +{='hello'|padRight: 10, '123'} {* izpiše 'hello12312' *} ``` -query .[filter]{data-version:2.10} ------------------------------------ -Dinamično ustvari poizvedbeni niz v naslovu URL: +query .[filter] +--------------- +Dinamično generira poizvedbeni niz v URL: ```latte -click -search +klikni +išči ``` -Natisne: +Izpiše: ```latte -click -search +klikni +išči ``` -Ključi z vrednostjo `null` so izpuščeni. +Ključi z vrednostjo `null` se izpustijo. -Glej tudi [escapeUrl |#escapeUrl]. +Glejte tudi [#escapeUrl]. -random .[filter]{data-version:2.10.2} -------------------------------------- +random .[filter] +---------------- Vrne naključni element polja ali znak niza: ```latte -{=[1, 2, 3, 4]|random} {* izhodni primer: 3 *} -{='abcd'|random} {* izhodni primer: 'b' *} +{=[1, 2, 3, 4]|random} {* izpiše npr.: 3 *} +{='abcd'|random} {* izpiše npr.: 'b' *} ``` -Glej tudi [first |#first], [last |#last]. +Glejte tudi [#first], [#last]. -repeat(int count) .[filter] ---------------------------- -Ponovi niz x-krat. +repeat(int $count) .[filter] +---------------------------- +Ponavlja niz x-krat. ```latte -{='hello'|repeat: 3} {* izhodi 'hellohellohello' *} +{='hello'|repeat: 3} {* izpiše 'hellohellohello' *} ``` -replace(string|array search, string replace = '') .[filter] +replace(string|array $search, string $replace='') .[filter] ----------------------------------------------------------- -Zamenja vse pojavitve iskanega niza z nadomestnim nizom. +Nadomesti vse pojavitve iskalnega niza z nadomestnim nizom. ```latte -{='hello world'|replace: 'world', 'friend'} {* outputs 'hello friend' *} +{='hello world'|replace: 'world', 'friend'} {* izpiše 'hello friend' *} ``` -Izvede se lahko več zamenjav naenkrat: .{data-version:2.10.2} +Lahko izvedemo tudi več zamenjav hkrati: ```latte -{='hello world'|replace: [h => l, l => h]} {* izhodi 'lehho worhd' *} +{='hello world'|replace: [h => l, l => h]} {* izpiše 'lehho worhd' *} ``` -replaceRE(string pattern, string replace = '') .[filter] +replaceRE(string $pattern, string $replace='') .[filter] -------------------------------------------------------- -Zamenja vse pojavitve v skladu z regularnim izrazom. +Izvede iskanje regularnih izrazov z zamenjavo. ```latte -{='hello world'|replaceRE: '/l.*/', 'l'} {* izhodi 'hel' *} +{='hello world'|replaceRE: '/l.*/', 'l'} {* izpiše 'hel' *} ``` @@ -553,47 +687,47 @@ Obrne dani niz ali polje. ```latte {var $s = 'Nette'} -{$s|reverse} {* izhodi 'etteN' *} +{$s|reverse} {* izpiše 'etteN' *} {var $a = ['N', 'e', 't', 't', 'e']} {$a|reverse} {* vrne ['e', 't', 't', 'e', 'N'] *} ``` -round(int precision = 0) .[filter] ----------------------------------- -Zaokroži število na določeno natančnost. +round(int $precision=0) .[filter] +--------------------------------- +Zaokroži število na dano natančnost. ```latte -{=3.4|round} {* izhodi 3 *} -{=3.5|round} {* izhodi 4 *} -{=135.79|round:1} {* izhodi 135.8 *} -{=135.79|round:3} {* izhodi 135.79 *} +{=3.4|round} {* izpiše 3 *} +{=3.5|round} {* izpiše 4 *} +{=135.79|round:1} {* izpiše 135.8 *} +{=135.79|round:3} {* izpiše 135.79 *} ``` -Glej tudi [strop |#ceil], [dno |#floor]. +Glejte tudi [#ceil], [#floor]. -slice(int start, int length = null, bool preserveKeys = false) .[filter]{data-version:2.10.2} ---------------------------------------------------------------------------------------------- -Izvleče rezino polja ali niza. +slice(int $start, ?int $length=null, bool $preserveKeys=false) .[filter] +------------------------------------------------------------------------ +Izvleče del polja ali niza. ```latte -{='hello'|slice: 1, 2} {* izhodi 'el' *} -{=['a', 'b', 'c']|slice: 1, 2} {* izhodi ['b', 'c'] *} +{='hello'|slice: 1, 2} {* izpiše 'el' *} +{=['a', 'b', 'c']|slice: 1, 2} {* izpiše ['b', 'c'] *} ``` -Filter rezine deluje kot funkcija PHP `array_slice` za polja in `mb_substr` za nize s povratno funkcijo `iconv_substr` v načinu UTF-8. +Filter deluje kot funkcija PHP `array_slice` za polja ali `mb_substr` za nize z rezervno funkcijo `iconv_substr` v načinu UTF‑8. -Če je začetek nenegativen, se zaporedje v spremenljivki začne s tem začetkom. Če je start negativen, se zaporedje začne tako daleč od konca spremenljivke. +Če je `start` pozitiven, se bo zaporedje začelo zamaknjeno za to število od začetka polja/niza. Če je negativen, se bo zaporedje začelo zamaknjeno za toliko od konca. -Če je podana dolžina in je pozitivna, bo imelo zaporedje do toliko elementov. Če je spremenljivka krajša od dolžine, bodo prisotni samo razpoložljivi elementi spremenljivke. Če je podana dolžina in je negativna, se bo zaporedje ustavilo toliko elementov od konca spremenljivke. Če je dolžina izpuščena, bo zaporedje vsebovalo vse od odmika do konca spremenljivke. +Če je podan parameter `length` in je pozitiven, bo zaporedje vsebovalo toliko elementov. Če je tej funkciji posredovan negativen parameter `length`, bo zaporedje vsebovalo vse elemente prvotnega polja, začenši na poziciji `start` in končavši na poziciji, manjši za `length` elementov od konca polja. Če tega parametra ne podate, bo zaporedje vsebovalo vse elemente prvotnega polja, začenši na poziciji `start`. -Filter bo privzeto spremenil vrstni red in ponastavil ključe celoštevilskega polja. To obnašanje lahko spremenite z nastavitvijo vrednosti preserveKeys na true. Vrstični ključi se vedno ohranijo, ne glede na ta parameter. +Privzeto filter spremeni vrstni red in ponastavi celoštevilske ključe polja. To vedenje lahko spremenite z nastavitvijo `preserveKeys` na `true`. Nizovni ključi se vedno ohranijo, ne glede na ta parameter. -sort .[filter]{data-version:2.9} ---------------------------------- -Filter, ki razvrsti polje in ohrani povezavo indeksov. +sort(?Closure $comparison, string|int|\Closure|null $by=null, string|int|\Closure|bool $byKey=false) .[filter] +-------------------------------------------------------------------------------------------------------------- +Filter razvrsti elemente polja ali iteratorja in ohrani njihove asociativne ključe. Pri nastavljenih [lokalnih nastavitvah |develop#Locale] se razvrščanje ravna po njegovih pravilih, če ni specificirana lastna primerjalna funkcija. ```latte {foreach ($names|sort) as $name} @@ -601,7 +735,7 @@ Filter, ki razvrsti polje in ohrani povezavo indeksov. {/foreach} ``` -Polje je razvrščeno v obratnem vrstnem redu. +Razvrščeno polje v obratnem vrstnem redu: ```latte {foreach ($names|sort|reverse) as $name} @@ -609,16 +743,42 @@ Polje je razvrščeno v obratnem vrstnem redu. {/foreach} ``` -Kot parameter lahko posredujete svojo primerjalno funkcijo: .{data-version:2.10.2} +Lahko specificirate lastno primerjalno funkcijo za razvrščanje (primer prikazuje, kako obrniti razvrščanje od največjega do najmanjšega): ```latte -{var $sorted = ($names|sort: fn($a, $b) => $b <=> $a)} +{var $reverted = ($names|sort: fn($a, $b) => $b <=> $a)} ``` +Filter `|sort` omogoča tudi razvrščanje elementov po ključih: -spaceless .[filter]{data-version:2.10.2} ------------------------------------------ -Odstrani nepotrebne bele lise iz izpisa. Uporabite lahko tudi vzdevek `strip`. +```latte +{foreach ($names|sort: byKey: true) as $name} + ... +{/foreach} +``` + +Če morate razvrstiti tabelo po določenem stolpcu, lahko uporabite parameter `by`. Vrednost `'name'` v primeru določa, da se bo razvrščalo po `$item->name` ali `$item['name']`, odvisno od tega, ali je `$item` polje ali objekt: + +```latte +{foreach ($items|sort: by: 'name') as $item} + {$item->name} +{/foreach} +``` + +Lahko tudi definirate povratno funkcijo, ki določi vrednost, po kateri naj se razvršča: + +```latte +{foreach ($items|sort: by: fn($items) => $items->category->name) as $item} + {$item->name} +{/foreach} +``` + +Na enak način lahko uporabite tudi parameter `byKey`. + + +spaceless .[filter] +------------------- +Odstrani nepotrebne presledke iz izpisa. Lahko uporabite tudi alias `strip`. ```latte {block |spaceless} @@ -628,7 +788,7 @@ Odstrani nepotrebne bele lise iz izpisa. Uporabite lahko tudi vzdevek `strip`. {/block} ``` -Natisne: +Izpiše: ```latte
                                                                                                                    • Hello
                                                                                                                    @@ -637,47 +797,47 @@ Natisne: stripHtml .[filter] ------------------- -Pretvarja HTML v navadno besedilo. To pomeni, da odstrani oznake HTML in pretvori entitete HTML v besedilo. +Pretvori HTML v čisto besedilo. Torej odstrani iz njega oznake HTML in entitete HTML pretvori v besedilo. ```latte -{='

                                                                                                                    one < two

                                                                                                                    '|stripHtml} {* outputs 'one < two' *} +{='

                                                                                                                    one < two

                                                                                                                    '|stripHtml} {* izpiše 'one < two' *} ``` -Dobljeno navadno besedilo lahko seveda vsebuje znake, ki predstavljajo oznake HTML, na primer `'<p>'|stripHtml` se pretvori v `

                                                                                                                    `. Dobljenega besedila nikoli ne izpišite s `|noescape`, saj to lahko povzroči varnostno ranljivost. +Rezultatno čisto besedilo lahko naravno vsebuje znake, ki predstavljajo oznake HTML, na primer `'<p>'|stripHtml` se pretvori v `

                                                                                                                    `. V nobenem primeru ne izpisujte tako nastalega besedila z `|noescape`, ker lahko to vodi do nastanka varnostne luknje. -substr(int offset, int length = null) .[filter] ------------------------------------------------ -Izvleče rezino niza. Ta filter je bil nadomeščen s filtrom za [rezine |#slice]. +substr(int $offset, ?int $length=null) .[filter] +------------------------------------------------ +Izvleče del niza. Ta filter je bil nadomeščen s filtrom [#slice]. ```latte {$string|substr: 1, 2} ``` -translate(string message, ...args) .[filter]{data-version:3.0} --------------------------------------------------------------- -Prevaja izraze v druge jezike. Če želite, da je filter na voljo, morate [nastaviti prevajalnik |develop#TranslatorExtension]. Za [prevajanje |tags#Translation] lahko uporabite tudi [oznake |tags#Translation]. +translate(...$args) .[filter] +----------------------------- +Prevaja izraze v druge jezike. Da bi bil filter na voljo, je treba [nastaviti prevajalnik |develop#TranslatorExtension]. Lahko uporabite tudi [oznake za prevajanje |tags#Prevodi]. ```latte -{='Baskter'|translate} +{='Košarica'|translate} {$item|translate} ``` -trim(string charlist = " \t\n\r\0\x0B\u{A0}") .[filter] -------------------------------------------------------- -Odstrani vodilne in končne znake, privzeto beli prostor. +trim(string $charlist=" \t\n\r\0\x0B\u{A0}") .[filter] +------------------------------------------------------ +Odstrani prazne znake (ali druge znake) z začetka in konca niza. ```latte -{=' I like Latte. '|trim} {* outputs 'I like Latte.' *} -{=' I like Latte.'|trim: '.'} {* outputs ' I like Latte' *} +{=' I like Latte. '|trim} {* izpiše 'I like Latte.' *} +{=' I like Latte.'|trim: '.'} {* izpiše ' I like Latte' *} ``` -truncate(int length, string append = '…') .[filter] +truncate(int $length, string $append='…') .[filter] --------------------------------------------------- -Skrajša niz na največjo dovoljeno dolžino, vendar poskuša ohraniti cele besede. Če je niz skrajšan, na koncu doda elipso (to lahko spremenite z drugim parametrom). +Obreže niz na navedeno največjo dolžino, pri čemer poskuša ohraniti cele besede. Če pride do skrajšanja niza, na koncu doda tri pike (lahko spremenite z drugim parametrom). ```latte {var $title = 'Hello, how are you?'} @@ -689,25 +849,25 @@ Skrajša niz na največjo dovoljeno dolžino, vendar poskuša ohraniti cele bese upper .[filter] --------------- -Pretvori vrednost v velike črke. Zahteva razširitev PHP `mbstring`. +Pretvori niz v velike črke. Zahteva razširitev PHP `mbstring`. ```latte -{='latte'|upper} {* izhodi 'LATTE' *} +{='latte'|upper} {* izpiše 'LATTE' *} ``` -Glej tudi [capitalize |#capitalize], [firstUpper |#firstUpper], [lower |#lower]. +Glejte tudi [#capitalize], [#firstUpper], [#lower]. webalize .[filter] ------------------ -Pretvarja v ASCII. +Prilagodi niz UTF‑8 v obliko, ki se uporablja v URL-jih. -Prevede presledke v pomišljaje. Odstrani znake, ki niso alfanumerični znaki, podčrtaji ali pomišljaji. Pretvarja v male črke. Odstrani tudi začetni in končni beli presledek. +Pretvarja se v ASCII. Pretvori presledke v pomišljaje. Odstrani znake, ki niso alfanumerični, podčrtaji ali pomišljaji. Pretvori v male črke. Prav tako odstrani začetne in končne presledke. ```latte -{var $s = 'Our 10. product'} -{$s|webalize} {* izhodi 'our-10-product' *} +{var $s = 'Naš 10. produkt'} +{$s|webalize} {* izpiše 'nas-10-produkt' *} ``` .[caution] -Zahteva paket [nette/utils |utils:]. +Zahteva knjižnico [nette/utils |utils:]. diff --git a/latte/sl/functions.texy b/latte/sl/functions.texy index 3568d5121f..69fc187be3 100644 --- a/latte/sl/functions.texy +++ b/latte/sl/functions.texy @@ -2,22 +2,24 @@ Funkcije Latte ************** .[perex] -Poleg običajnih funkcij PHP lahko v predlogah uporabljate tudi te. +V predlogah lahko poleg običajnih funkcij PHP uporabljamo tudi te dodatne. .[table-latte-filters] -| `clamp` | [vpne vrednost v območje |#clamp] +| `clamp` | [omeji vrednost na dano območje |#clamp] | `divisibleBy`| [preveri, ali je spremenljivka deljiva s številom |#divisibleBy] -| `even` | [preveri, ali je dano število sodo |#even] -| `first` | [vrne prvi element polja ali znak niza |#first] -| `last` | [vrne zadnji element polja ali znak niza |#last] -| `odd` | [preveri, ali je podano število liho |#odd] -| `slice` | [izvleče rezino polja ali niza |#slice] +| `even` | [preveri, ali je dano število sodo |#even] +| `first` | [vrne prvi element polja ali znak niza |#first] +| `group` | [združi podatke po različnih kriterijih |#group] +| `hasBlock` | [ugotovi obstoj bloka |#hasBlock] +| `last` | [vrne zadnji element polja ali znak niza |#last] +| `odd` | [preveri, ali je dano število liho |#odd] +| `slice` | [izvleče del polja ali niza |#slice] -Uporaba .[#toc-usage] -===================== +Uporaba +======= -Funkcije se uporabljajo na enak način kot običajne funkcije PHP in se lahko uporabljajo v vseh izrazih: +Funkcije se uporabljajo enako kot običajne funkcije PHP in jih je mogoče uporabiti v vseh izrazih: ```latte

                                                                                                                    {clamp($num, 1, 100)}

                                                                                                                    @@ -25,14 +27,14 @@ Funkcije se uporabljajo na enak način kot običajne funkcije PHP in se lahko up {if odd($num)} ... {/if} ``` -[Funkcije po meri |extending-latte#functions] lahko registrirate na ta način: +[Funkcije po meri |custom-functions] se lahko registrirajo na ta način: ```php $latte = new Latte\Engine; $latte->addFunction('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -Uporabimo jo v predlogi, kot je ta: +V predlogi se nato kliče takole: ```latte

                                                                                                                    {shortify($text)}

                                                                                                                    @@ -40,23 +42,23 @@ Uporabimo jo v predlogi, kot je ta: ``` -Funkcije .[#toc-functions] -========================== +Funkcije +======== -clamp(int|float $value, int|float $min, int|float $max): int|float .[method]{data-version:2.9} ----------------------------------------------------------------------------------------------- -Vrne vrednost, ki je vpeta v območje min in max. +clamp(int|float $value, int|float $min, int|float $max): int|float .[method] +---------------------------------------------------------------------------- +Omeji vrednost na dano vključno območje min in max. ```latte {=clamp($level, 0, 255)} ``` -Glej tudi [filter clamp |filters#clamp]: +Glejte tudi [filter clamp |filters#clamp]. -divisibleBy(int $value, int $by): bool .[method]{data-version:2.10.2} ---------------------------------------------------------------------- +divisibleBy(int $value, int $by): bool .[method] +------------------------------------------------ Preveri, ali je spremenljivka deljiva s številom. ```latte @@ -64,61 +66,91 @@ Preveri, ali je spremenljivka deljiva s številom. ``` -even(int $value): bool .[method]{data-version:2.10.2} ------------------------------------------------------ -Preveri, ali je podano število sodo. +even(int $value): bool .[method] +-------------------------------- +Preveri, ali je dano število sodo. ```latte {if even($num)} ... {/if} ``` -first(string|array $value): mixed .[method]{data-version:2.10.2} ----------------------------------------------------------------- +first(string|iterable $value): mixed .[method] +---------------------------------------------- Vrne prvi element polja ali znak niza: ```latte -{=first([1, 2, 3, 4])} {* izhodi 1 *} -{=first('abcd')} {* izhodi 'a' *} +{=first([1, 2, 3, 4])} {* izpiše 1 *} +{=first('abcd')} {* izpiše 'a' *} ``` -Glej tudi [last |#last], [filter first |filters#first]. +Glejte tudi [#last], [filter first |filters#first]. -last(string|array $value): mixed .[method]{data-version:2.10.2} ---------------------------------------------------------------- +group(iterable $data, string|int|\Closure $by): array .[method]{data-version:3.0.16} +------------------------------------------------------------------------------------ +Funkcija združi podatke po različnih kriterijih. + +V tem primeru se vrstice v tabeli združujejo po stolpcu `categoryId`. Izpis je polje polj, kjer je ključ vrednost v stolpcu `categoryId`. [Preberite podrobna navodila |cookbook/grouping]. + +```latte +{foreach group($items, categoryId) as $categoryId => $categoryItems} +
                                                                                                                      + {foreach $categoryItems as $item} +
                                                                                                                    • {$item->name}
                                                                                                                    • + {/foreach} +
                                                                                                                    +{/foreach} +``` + +Glejte tudi filter [group |filters#group]. + + +hasBlock(string $name): bool .[method]{data-version:3.0.10} +----------------------------------------------------------- +Ugotovi, ali blok z navedenim imenom obstaja: + +```latte +{if hasBlock(header)} ... {/if} +``` + +Glejte tudi [preverjanje obstoja blokov |template-inheritance#Preverjanje obstoja blokov]. + + +last(string|array $value): mixed .[method] +------------------------------------------ Vrne zadnji element polja ali znak niza: ```latte -{=last([1, 2, 3, 4])} {* izhodi 4 *} -{=last('abcd')} {* izhodi 'd' *} +{=last([1, 2, 3, 4])} {* izpiše 4 *} +{=last('abcd')} {* izpiše 'd' *} ``` -Glej tudi [first |#first], [filter last |filters#last]. +Glejte tudi [#first], [filter last |filters#last]. -odd(int $value): bool .[method]{data-version:2.10.2} ----------------------------------------------------- -Preveri, ali je podano število liho. +odd(int $value): bool .[method] +------------------------------- +Preveri, ali je dano število liho. ```latte {if odd($num)} ... {/if} ``` -slice(string|array $value, int $start, int $length=null, bool $preserveKeys=false): string|array .[method]{data-version:2.10.2} -------------------------------------------------------------------------------------------------------------------------------- -Izvleče rezino polja ali niza. +slice(string|array $value, int $start, ?int $length=null, bool $preserveKeys=false): string|array .[method] +----------------------------------------------------------------------------------------------------------- +Izvleče del polja ali niza. ```latte -{=slice('hello', 1, 2)} {* izhodi 'el' *} -{=slice(['a', 'b', 'c'], 1, 2)} {* izhodi ['b', 'c'] *} +{=slice('hello', 1, 2)} {* izpiše 'el' *} +{=slice(['a', 'b', 'c'], 1, 2)} {* izpiše ['b', 'c'] *} ``` -Filter rezine deluje kot funkcija PHP `array_slice` za polja in `mb_substr` za nize s povratno funkcijo `iconv_substr` v načinu UTF-8. +Funkcija deluje kot funkcija PHP `array_slice` za polja ali `mb_substr` za nize z rezervno funkcijo `iconv_substr` v načinu UTF‑8. -Če je začetek nenegativen, se zaporedje v spremenljivki začne s tem začetkom. Če je start negativen, se zaporedje začne tako daleč od konca spremenljivke. +Če je `start` pozitiven, se bo zaporedje začelo zamaknjeno za to število od začetka polja/niza. Če je negativen, se bo zaporedje začelo zamaknjeno za toliko od konca. -Če je podana dolžina in je pozitivna, bo imelo zaporedje do toliko elementov. Če je spremenljivka krajša od dolžine, bodo prisotni samo razpoložljivi elementi spremenljivke. Če je podana dolžina in je negativna, se bo zaporedje ustavilo toliko elementov od konca spremenljivke. Če je dolžina izpuščena, bo zaporedje vsebovalo vse od odmika do konca spremenljivke. +Če je podan parameter `length` in je pozitiven, bo zaporedje vsebovalo toliko elementov. Če je tej funkciji posredovan negativen parameter `length`, bo zaporedje vsebovalo vse elemente prvotnega polja, začenši na poziciji `start` in končavši na poziciji, manjši za `length` elementov od konca polja. Če tega parametra ne podate, bo zaporedje vsebovalo vse elemente prvotnega polja, začenši na poziciji `start`. -Filter bo privzeto spremenil vrstni red in ponastavil ključe celoštevilskega polja. To obnašanje lahko spremenite z nastavitvijo vrednosti preserveKeys na true. Vrstični ključi se vedno ohranijo, ne glede na ta parameter. +Privzeto funkcija spremeni vrstni red in ponastavi celoštevilske ključe polja. To vedenje lahko spremenite z nastavitvijo `preserveKeys` na `true`. Nizovni ključi se vedno ohranijo, ne glede na ta parameter. diff --git a/latte/sl/guide.texy b/latte/sl/guide.texy index 423da1bf92..676dd0931f 100644 --- a/latte/sl/guide.texy +++ b/latte/sl/guide.texy @@ -1,46 +1,45 @@ -Začetek dela z Lattejem -*********************** +Začenjamo z Latte +*****************
                                                                                                                    -Predloge izboljšajo organizacijo kode, ločijo logiko aplikacije od predstavitve in povečajo varnost. Ponujajo veliko boljše funkcije in izrazne zmožnosti za ustvarjanje HTML kot sam PHP. +Predloge izboljšujejo organizacijo kode, ločujejo logiko aplikacije od predstavitve in povečujejo varnost. Ponujajo veliko boljše funkcije in izrazna sredstva za generiranje HTML kot sam PHP. -Latte je najvarnejši sistem predlog za PHP. Všeč vam bo njegova intuitivna sintaksa. Širok nabor uporabnih funkcij vam bo bistveno olajšal delo. -Zagotavlja vrhunsko zaščito pred [kritičnimi ranljivostmi |safety-first] in vam omogoča, da se osredotočite na ustvarjanje visokokakovostnih aplikacij brez skrbi glede njihove varnosti. +Latte je najvarnejši sistem predlog za PHP. Njegova intuitivna sintaksa vas bo navdušila. Širok nabor uporabnih funkcij vam bo znatno olajšal delo. Zagotavlja vrhunsko zaščito pred [kritičnimi ranljivostmi |safety-first] in vam omogoča, da se osredotočite na ustvarjanje kakovostnih aplikacij brez skrbi za njihovo varnost. -Kako napisati predloge z uporabo programa Latte? .[#toc-how-to-write-templates-using-latte] -------------------------------------------------------------------------------------------- +Kako pisati predloge z Latte? +----------------------------- -Latte je pametno zasnovan in enostaven za učenje za tiste, ki poznajo PHP, saj lahko hitro sprejmejo njegove osnovne oznake. +Latte je pametno zasnovan in ga zlahka osvojijo tisti, ki poznajo PHP in se naučijo osnovnih oznak. -- Najprej se seznanite s [sintakso Latte |syntax] in [vse skupaj preizkusite na spletu |https://fiddle.nette.org/latte/] +- Najprej se seznanite s [sintakso Latte |syntax] in jo [PREIZKUSITE NA SPLETU |https://fiddle.nette.org/latte/#9cc0cf6d89] - Oglejte si osnovni nabor [oznak |tags] in [filtrov |filters] -- Napišite predloge v [urejevalniku s podporo za Latte |recipes#Editors and IDE] +- Pišite predloge v [urejevalniku s podporo za Latte |recipes#Urejevalniki in IDE] -Kako uporabljati Latte v PHP? .[#toc-how-to-use-latte-in-php] -------------------------------------------------------------- +Kako uporabiti Latte v PHP? +--------------------------- -Implementacija sistema Latte v novo aplikacijo je vprašanje nekaj minut: +Uvesti Latte v vašo novo aplikacijo je vprašanje nekaj minut: -- Najprej [namestite in zaženite Latte |develop#Installation] -- Razvajajte se z [orodjem za razhroščevanje Tracy |develop#Debugging and Tracy] -- Razširite Latte s [funkcionalnostjo po meri |extending-latte] +- Najprej [namestite in zaženite Latte |develop#Namestitev] +- Pustite se razvajati z [orodjem za razhroščevanje Tracy |develop#Razhroščevanje in Tracy] +- Razširite Latte z [lastno funkcionalnostjo |extending-latte] -Če star projekt, napisan v navadnem jeziku PHP, pretvarjate v Latte, vam bo [orodje za pretvarjanje kode PHP v Latte |cookbook/migration-from-php] olajšalo prehod. Ali pa nameravate preiti na Latte iz Twiga? Za vas imamo orodje za [pretvorbo predlog Twig v Latte |cookbook/migration-from-twig]. +Če prenašate star projekt, napisan v čistem PHP, v Latte, vam bo migracijo olajšalo [orodje za pretvorbo kode PHP v Latte |cookbook/migration-from-php]. Ali pa se pripravljate na prehod na Latte iz Twiga? Imamo za vas [pretvornik predlog Twig v Latte |cookbook/migration-from-twig]. -Kaj še zna Latte? .[#toc-what-else-can-latte-do] ------------------------------------------------- +Kaj še zmore Latte? +------------------- -Latte je popolnoma opremljen, saj so vključene vse osnovne stvari. +Latte prejmete v polni opremi, z vsem pomembnim v osnovi. -- Vašo produktivnost bodo povečali [mehanizmi dedovanja |template-inheritance], ki omogočajo ponovno uporabo ponavljajočih se elementov in struktur -- Oklepni bunker [Sandbox |Sandbox] izolira predloge od nezaupljivih virov, kot so tisti, ki jih urejajo uporabniki sami -- Za dodaten navdih so tu [nasveti in triki |recipes] +- Vašo produktivnost bodo povečali [mehanizmi dedovanja |template-inheritance], zahvaljujoč katerim se ponavljajoči elementi in strukture ponovno uporabijo +- Oklepni bunker [sandbox] izolira predloge iz nezaupljivih virov, ki jih na primer urejajo sami uporabniki +- Za nadaljnji navdih so tu [nasveti in triki |recipes]
                                                                                                                    -{{description: Latte je najbezpečnejši šablonovací sistem za PHP. Zabraňuje spoustě varnostních ranitelností. Oceníte njegovo intuitivní syntaxi a oceníte spoustu užitečných vychytávek.}} +{{description: Latte je najvarnejši sistem predlog za PHP. Preprečuje številne varnostne ranljivosti. Cenili boste njegovo intuitivno sintakso in številne uporabne funkcije.}} diff --git a/latte/sl/loaders.texy b/latte/sl/loaders.texy new file mode 100644 index 0000000000..424dde2524 --- /dev/null +++ b/latte/sl/loaders.texy @@ -0,0 +1,198 @@ +Nalagatelji (Loaders) +********************* + +.[perex] +Nalagatelji so mehanizem, ki ga Latte uporablja za pridobivanje izvorne kode vaših predlog. Najpogosteje so predloge shranjene kot datoteke na disku, vendar jih lahko zaradi prilagodljivega sistema nalagateljev nalagate praktično od koder koli ali jih celo dinamično generirate. + + +Kaj je Nalagatelj? +================== + +Ko delate s predlogami, si običajno predstavljate datoteke `.latte`, ki se nahajajo v strukturi imenikov vašega projekta. Za to skrbi privzeti [#FileLoader] v Latte. Vendar pa povezava med imenom predloge (kot je `'main.latte'` ali `'components/card.latte'`) in njeno dejansko izvorno kodo *ni nujno* neposredno preslikava na pot do datoteke. + +Prav tukaj pridejo na vrsto nalagatelji. Nalagatelj je objekt, ki ima nalogo vzeti ime predloge (identifikacijski niz) in Latteju zagotoviti njeno izvorno kodo. Latte se pri tej nalogi popolnoma zanaša na konfiguriran nalagatelj. To velja ne samo za začetno predlogo, zahtevano z `$latte->render('main.latte')`, ampak tudi za **vsako predlogo, na katero se sklicuje znotraj** z uporabo značk, kot so `{include ...}`, `{layout ...}`, `{embed ...}` ali `{import ...}`. + +Zakaj uporabiti lasten nalagatelj? + +- **Nalaganje iz alternativnih virov:** Pridobivanje predlog, shranjenih v podatkovni bazi, v predpomnilniku (kot sta Redis ali Memcached), v sistemu za upravljanje različic (kot je Git, na podlagi določenega commita) ali dinamično generiranih. +- **Implementacija lastnih konvencij poimenovanja:** Morda želite uporabljati krajše vzdevke za predloge ali implementirati specifično logiko iskalnih poti (npr. najprej iskati v imeniku teme, nato se vrniti v privzeti imenik). +- **Dodajanje varnosti ali nadzora dostopa:** Lasten nalagatelj lahko pred nalaganjem določenih predlog preveri uporabniška dovoljenja. +- **Predobdelava:** Čeprav se to na splošno ne priporoča ([prevajalski prehodi |compiler-passes] so boljši), bi nalagatelj *teoretično* lahko predobdelal vsebino predloge, preden jo preda Latteju. + +Nalagatelj za instanco `Latte\Engine` nastavite z metodo `setLoader()`: + +```php +$latte = new Latte\Engine; + +// Uporaba privzetega FileLoaderja za datoteke v '/path/to/templates' +$loader = new Latte\Loaders\FileLoader('/path/to/templates'); +$latte->setLoader($loader); +``` + +Nalagatelj mora implementirati vmesnik `Latte\Loader`. + + +Vgrajeni Nalagatelji +==================== + +Latte ponuja več standardnih nalagateljev: + + +FileLoader +---------- + +To je **privzeti nalagatelj**, ki ga uporablja razred `Latte\Engine`, če ni določen noben drug. Nalaga predloge neposredno iz datotečnega sistema. + +Po želji lahko nastavite korenski imenik za omejitev dostopa: + +```php +use Latte\Loaders\FileLoader; + +// Naslednje bo omogočilo nalaganje predlog samo iz imenika /var/www/html/templates +$loader = new FileLoader('/var/www/html/templates'); +$latte->setLoader($loader); + +// $latte->render('../../../etc/passwd'); // To bi vrglo izjemo + +// Izrisovanje predloge, ki se nahaja na /var/www/html/templates/pages/contact.latte +$latte->render('pages/contact.latte'); +``` + +Pri uporabi značk, kot sta `{include}` ali `{layout}`, razrešuje imena predlog relativno glede na trenutno predlogo, če ni podana absolutna pot. + + +StringLoader +------------ + +Ta nalagatelj pridobi vsebino predloge iz asociativnega polja, kjer so ključi imena predlog (identifikatorji), vrednosti pa nizi izvorne kode predloge. Je še posebej uporaben za testiranje ali majhne aplikacije, kjer so lahko predloge shranjene neposredno v PHP kodi. + +```php +use Latte\Loaders\StringLoader; + +$loader = new StringLoader([ + 'main.latte' => 'Hello {$name}, include is below:{include helper.latte}', + 'helper.latte' => '{var $x = 10}Included content: {$x}', + // Dodajte druge predloge po potrebi +]); + +$latte->setLoader($loader); + +$latte->render('main.latte', ['name' => 'World']); +// Izpis: Hello World, include is below:Included content: 10 +``` + +Če morate izrisati samo eno predlogo neposredno iz niza brez potrebe po vključevanju ali dedovanju, ki se sklicuje na druge poimenovane nizovne predloge, lahko niz posredujete neposredno metodi `render()` ali `renderToString()` pri uporabi `StringLoader` brez polja: + +```php +$loader = new StringLoader; +$latte->setLoader($loader); + +$templateString = 'Hello {$name}!'; +$output = $latte->renderToString($templateString, ['name' => 'Alice']); +// $output vsebuje 'Hello Alice!' +``` + + +Ustvarjanje lastnega Nalagatelja +================================ + +Za ustvarjanje lastnega nalagatelja (npr. za nalaganje predlog iz podatkovne baze, predpomnilnika, sistema za upravljanje različic ali drugega vira) morate ustvariti razred, ki implementira vmesnik [api:Latte\Loader]. + +Poglejmo, kaj mora vsaka metoda narediti. + + +getContent(string $name): string .[method] +------------------------------------------ +To je osnovna metoda nalagatelja. Njena naloga je pridobiti in vrniti celotno izvorno kodo predloge, identificirane z `$name` (kot je posredovano metodi `$latte->render()` ali vrnjeno z metodo [#getReferredName()]). + +Če predloge ni mogoče najti ali dostopati do nje, mora ta metoda **vrniti izjemo `Latte\RuntimeException`**. + +```php +public function getContent(string $name): string +{ + // Primer: Nalaganje iz hipotetičnega notranjega shrambe + $content = $this->storage->read($name); + if ($content === null) { + throw new Latte\RuntimeException("Predloga '$name' ni mogoče naložiti."); + } + return $content; +} +``` + + +getReferredName(string $name, string $referringName): string .[method] +---------------------------------------------------------------------- +Ta metoda rešuje prevajanje imen predlog, uporabljenih znotraj značk, kot so `{include}`, `{layout}` itd. Ko Latte naleti na primer na `{include 'partial.latte'}` znotraj `main.latte`, pokliče to metodo z `$name = 'partial.latte'` in `$referringName = 'main.latte'`. + +Naloga metode je prevesti `$name` v kanonični identifikator (npr. absolutno pot, edinstven ključ podatkovne baze), ki bo uporabljen pri klicanju drugih metod nalagatelja, na podlagi konteksta, podanega v `$referringName`. + +```php +public function getReferredName(string $name, string $referringName): string +{ + return ...; +} +``` + + +getUniqueId(string $name): string .[method] +------------------------------------------- +Latte uporablja za izboljšanje učinkovitosti predpomnilnik prevedenih predlog. Vsaka prevedena datoteka predloge potrebuje edinstveno ime, izpeljano iz identifikatorja izvorne predloge. Ta metoda zagotavlja niz, ki **nedvoumno identificira** predlogo `$name`. + +Za predloge, ki temeljijo na datotekah, lahko služi absolutna pot. Za predloge v podatkovni bazi je običajna kombinacija predpone in ID-ja podatkovne baze. + +```php +public function getUniqueId(string $name): string +{ + return ...; +} +``` + + +Primer: Preprost Nalagatelj iz Podatkovne Baze +---------------------------------------------- + +Ta primer prikazuje osnovno strukturo nalagatelja, ki nalaga predloge, shranjene v tabeli podatkovne baze, imenovani `templates`, s stolpci `name` (edinstven identifikator), `content` in `updated_at`. + +```php +use Latte; + +class DatabaseLoader implements Latte\Loader +{ + public function __construct( + private \PDO $db, + ) { + } + + public function getContent(string $name): string + { + $stmt = $this->db->prepare('SELECT content FROM templates WHERE name = ?'); + $stmt->execute([$name]); + $content = $stmt->fetchColumn(); + if ($content === false) { + throw new Latte\RuntimeException("Predloga '$name' ni najdena v podatkovni bazi."); + } + return $content; + } + + // Ta preprost primer predpostavlja, da so imena predlog ('homepage', 'article', itd.) + // edinstveni ID-ji in se predloge ne sklicujejo relativno druga na drugo. + public function getReferredName(string $name, string $referringName): string + { + return $name; + } + + public function getUniqueId(string $name): string + { + // Uporaba predpone in samega imena je tukaj edinstvena in zadostna + return 'db_' . $name; + } +} + +// Uporaba: +$pdo = new \PDO(/* podrobnosti povezave */); +$loader = new DatabaseLoader($pdo); +$latte->setLoader($loader); +$latte->render('homepage'); // Naloži predlogo z imenom 'homepage' iz PB +``` + +Lastni nalagatelji vam dajejo popoln nadzor nad tem, od kod prihajajo vaše Latte predloge, kar omogoča integracijo z različnimi sistemi shranjevanja in delovnimi postopki. diff --git a/latte/sl/recipes.texy b/latte/sl/recipes.texy index f9b3d43c7d..f8e9e08efd 100644 --- a/latte/sl/recipes.texy +++ b/latte/sl/recipes.texy @@ -2,44 +2,44 @@ Nasveti in triki **************** -Urejevalniki in IDE .[#toc-editors-and-ide] -=========================================== +Urejevalniki in IDE +=================== -Predloge pišite v urejevalniku ali IDE, ki podpira Latte. Bilo bo veliko bolj prijetno. +Pišite predloge v urejevalniku ali IDE, ki ima podporo za Latte. Bo veliko prijetneje. -- V okolju NetBeans IDE je vgrajena podpora -- PhpStorm: namestite [vtičnik Latte |https://plugins.jetbrains.com/plugin/7457-latte] v `Settings > Plugins > Marketplace` -- Koda VS: iskanje v markerplaceu za vtičnik "Nette Latte + Neon" -- Sublime Text 3: v Upravljanju paketov poiščite in namestite paket `Nette` ter izberite Latte v `View > Syntax` -- V starih urejevalnikih uporabite Smartyjevo označevanje za datoteke .latte +- PhpStorm: namestite v `Settings > Plugins > Marketplace` [plugin Latte |https://plugins.jetbrains.com/plugin/7457-latte] +- VS Code: namestite [Nette Latte + Neon |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte], [Nette Latte templates |https://marketplace.visualstudio.com/items?itemName=smuuf.latte-lang] ali najnovejši [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode] plugin +- NetBeans IDE: izvorna podpora Latte je del namestitve +- Sublime Text 3: v Package Control poiščite in namestite paket `Nette` ter izberite Latte v `View > Syntax` +- v starih urejevalnikih uporabite za datoteke .latte poudarjanje Smarty -Vtičnik za program PhpStorm je zelo napreden in omogoča odlično delo pri namigovanju kode PHP. Za optimalno delovanje uporabite [tipizirane predloge |type-system]. +Plugin za PhpStorm je zelo napreden in zna odlično predlagati kodo PHP. Da bi deloval optimalno, uporabljajte [tipizirane predloge |type-system]. [* latte-phpstorm-plugin.webp *] -Podporo za Latte najdete tudi v spletnem označevalniku kode [Prism.js |https://prismjs.com/#supported-languages] in urejevalniku [Ace |https://ace.c9.io]. +Podporo za Latte najdete tudi v spletnem poudarjalniku kode [Prism.js |https://prismjs.com/#supported-languages] in urejevalniku [Ace |https://ace.c9.io]. -Latte znotraj JavaScript ali CSS .[#toc-latte-inside-javascript-or-css] -======================================================================= +Latte znotraj JavaScripta ali CSS +================================= -Latte lahko zelo priročno uporabljate v programih JavaScript ali CSS. Toda kako se izogniti situaciji, ko Latte napačno uporabi kodo JavaScript ali slog CSS kot oznako Latte? +Latte je mogoče zelo udobno uporabljati tudi znotraj JavaScripta ali CSS. Kako pa se izogniti situaciji, ko bi Latte napačno razumel JavaScript kodo ali CSS stil kot oznako Latte? ```latte ``` -**Opcija 1** +**Varianta 1** -Izognite se situaciji, ko črka sledi takoj za `{`, na primer tako, da pred njo naredite presledek, prelom vrstice ali narekovaj: +Izognite se situaciji, ko sledi črka takoj za `{`, na primer tako, da pred njo vstavite presledek, prelom vrstice ali narekovaj: ```latte -

                                                                                                                    +

                                                                                                                    ``` -Dva načina in dve različni vrsti pobegov podatkov. Znotraj elementa ` ``` -Če pa jo želimo vstaviti v atribut HTML, moramo še vedno eskapirati narekovaje v entitete HTML: +Če pa bi jo želeli vstaviti v atribut HTML, moramo še ubežati narekovaje v HTML entitete: ```html
                                                                                                                    ``` -Vendar ni nujno, da je vgnezdeni kontekst samo JS ali CSS. Pogosto je tudi URL. Parametri v naslovih URL se izognejo tako, da se posebni znaki pretvorijo v zaporedja, ki se začnejo z `%`. Primer: +Vgnezden kontekst pa ni nujno samo JS ali CSS. Pogosto je to tudi URL. Parametri v URL se ubežajo tako, da se znaki s posebnim pomenom pretvorijo v zaporedja, ki se začnejo z `%`. Primer: ``` https://example.org/?a=Jazz&b=Rock%27n%27Roll ``` -Ko ta niz izpišemo v atributu, še vedno uporabimo izogibanje v skladu s tem kontekstom in nadomestimo `&` with `&`: +In ko ta niz izpišemo v atributu, še uporabimo ubežanje po tem kontekstu in zamenjamo `&` za `&`: ```html ``` -Če ste prebrali vse do sem, vam čestitam, bilo je naporno. Zdaj imate dobro predstavo o tem, kaj so konteksti in escapiranje. In ni vam treba skrbeti, da bi bilo to zapleteno. Latte to stori namesto vas samodejno. +Če ste prebrali do sem, čestitamo, bilo je izčrpno. Zdaj imate dobro predstavo o tem, kaj so konteksti in ubežanje. In ni vam treba skrbeti, da je zapleteno. Latte to namreč dela za vas samodejno. -Latte proti naivnim sistemom .[#toc-latte-vs-naive-systems] -=========================================================== +Latte proti naivnim sistemom +============================ -Pokazali smo, kako pravilno pobegniti v dokumentu HTML in kako pomembno je poznati kontekst, tj. kje izpisujete podatke. Z drugimi besedami, kako deluje kontekstno občutljivo eskapiranje. -Čeprav je to predpogoj za funkcionalno obrambo pred XSS, je **Latte edini šablonski sistem za PHP, ki to omogoča.** +Pokazali smo si, kako se pravilno ubeža v dokumentu HTML in kako bistveno je poznavanje konteksta, torej mesta, kjer podatke izpisujemo. Z drugimi besedami, kako deluje kontekstno občutljivo ubežanje. Čeprav gre za nujen predpogoj funkcionalne obrambe pred XSS, **je Latte edini sistem predlog za PHP, ki to zna.** -Kako je to mogoče, ko pa vsi današnji sistemi trdijo, da imajo samodejno eskapiranje? -Samodejno izogibanje brez poznavanja konteksta je neumnost, ki **ustvarja lažen občutek varnosti**. +Kako je to mogoče, ko pa vsi sistemi danes trdijo, da imajo samodejno ubežanje? Samodejno ubežanje brez poznavanja konteksta je malce bullshit, ki **ustvarja lažen občutek varnosti**. -Sistemi za izdelavo predlog, kot so Twig, Laravel Blade in drugi, ne vidijo nobene strukture HTML v predlogi. Zato tudi ne vidijo kontekstov. V primerjavi z Lattejem so slepi in naivni. Obravnavajo samo svoje lastne oznake, vse drugo je zanje nepomemben tok znakov: +Sistemi predlog, kot so Twig, Laravel Blade in drugi, v predlogi ne vidijo nobene strukture HTML. Zato ne vidijo niti kontekstov. V primerjavi z Latte so slepi in naivni. Obdelujejo samo lastne oznake, vse ostalo je zanje nepomemben tok znakov:
                                                                                                                    -```twig .{file:Twig template as seen by Twig himself} -░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░ -░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░{{ text }}░░░░ +```twig .{file:Predloga Twig, kot jo vidi sam Twig} +░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░ +░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░ ``` -```twig .{file:Twig template as the designer sees it} -- in text: {{ text }} -- in tag: -- in attribute: -- in unquoted attribute: -- in attribute containing URL: -- in attribute containing JavaScript: -- in attribute containing CSS: -- in JavaScriptu: -- in CSS: -- in comment: +```twig .{file:Predloga Twig, kot jo vidi oblikovalec} +- v besedilu: {{ foo }} +- v oznaki: +- v atributu: +- v atributu brez narekovajev: +- v atributu, ki vsebuje URL: +- v atributu, ki vsebuje JavaScript: +- v atributu, ki vsebuje CSS: +- v JavaScriptu: +- v CSS: +- v komentarju: ```
                                                                                                                    -Naivni sistemi samo mehansko pretvorijo znake `< > & ' "` v entitete HTML, kar je v večini primerov ustrezen način pobega, vendar še zdaleč ne vedno. Zato ne morejo odkriti ali preprečiti različnih varnostnih lukenj, kot bomo pokazali v nadaljevanju. +Naivni sistemi samo mehansko pretvarjajo znake `< > & ' "` v HTML entitete, kar je sicer v večini primerov uporabe veljaven način ubežanja, vendar še zdaleč ne vedno. Ne morejo tako odkriti niti preprečiti nastanka različnih varnostnih lukenj, kot bomo pokazali dalje. -Latte vidi predlogo enako kot vi. Razume HTML, XML, prepozna oznake, atribute itd. In zaradi tega razlikuje med konteksti in ustrezno obravnava podatke. Zato ponuja resnično učinkovito zaščito pred kritično ranljivostjo Cross-site Scripting. +Latte predlogo vidi enako kot vi. Razume HTML, XML, prepoznava oznake, atribute itd. In zahvaljujoč temu razlikuje posamezne kontekste in po njih obdeluje podatke. Ponuja tako resnično učinkovito zaščito pred kritično ranljivostjo Cross-site Scripting. + +
                                                                                                                    + +```latte .{file:Predloga Latte, kot jo vidi Latte} +░░░░░░░░░░░{$foo} +░░░░░░░░░░ +░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░ +░░░░░░░░░ +░░░░░░░░░░░░░░░ +``` + +```latte .{file:Predloga Latte, kot jo vidi oblikovalec} +- v besedilu: {$foo} +- v oznaki: +- v atributu: +- v atributu brez narekovajev: +- v atributu, ki vsebuje URL: +- v atributu, ki vsebuje JavaScript: +- v atributu, ki vsebuje CSS: +- v JavaScriptu: +- v CSS: +- v komentarju: +``` + +
                                                                                                                    -Prikaz v živo .[#toc-live-demonstration] -======================================== +Primer v živo +============= -Na levi strani si lahko ogledate predlogo v Latte, na desni pa ustvarjeno kodo HTML. Spremenljivka `$text` se izpiše večkrat, vsakič v nekoliko drugačnem kontekstu. Zato je tudi izpisana nekoliko drugače. Kodo predloge lahko urejate sami, na primer spremenite vsebino spremenljivke itd. Poskusite: +Na levi vidite predlogo v Latte, na desni je generirana koda HTML. Večkrat se tu izpisuje spremenljivka `$text` in vsakič v malce drugačnem kontekstu. In torej tudi malce drugače ubežana. Kodo predloge lahko sami urejate, na primer spremenite vsebino spremenljivke itd. Poskusite si:
                                                                                                                    ``` .{file:template.latte; min-height: 14em}[fiddle-source] -{* TRY TO EDIT THIS TEMPLATE *} +{* POSKUSITE UREDITI TO PREDLOGO *} {var $text = "Rock'n'Roll"} - {$text} - @@ -270,51 +286,49 @@ Na levi strani si lahko ogledate predlogo v Latte, na desni pa ustvarjeno kodo H
                                                                                                                    -Ali ni to super! Latte samodejno izvaja kontekstno občutljivo eskapiranje, tako da programer: +Ni to super! Latte dela kontekstno občutljivo ubežanje samodejno, tako da programer: -- mu ni treba razmišljati ali vedeti, kako pobegniti podatke +- ni treba razmišljati niti vedeti, kako se kje ubeža - se ne more zmotiti -- ne more pozabiti nanj +- ne more pozabiti na ubežanje -To niti niso vsi konteksti, ki jih Latte loči pri izpisu in za katere prilagodi obdelavo podatkov. Zdaj bomo pregledali več zanimivih primerov. +To celo niso vsi konteksti, ki jih Latte pri izpisovanju razlikuje in za katere prilagaja obdelavo podatkov. Druge zanimive primere si bomo ogledali zdaj. -Kako vdreti v naivne sisteme .[#toc-how-to-hack-naive-systems] -============================================================== +Kako vdreti v naivne sisteme +============================ -Z nekaj praktičnimi primeri bomo pokazali, kako pomembno je razlikovanje konteksta in zakaj naivni sistemi za oblikovanje predlog za razliko od sistema Latte ne zagotavljajo zadostne zaščite pred XSS. -V primerih bomo kot predstavnika naivnega sistema uporabili Twig, vendar enako velja tudi za druge sisteme. +Na nekaj praktičnih primerih si bomo pokazali, kako pomembno je razlikovanje kontekstov in zakaj naivni sistemi predlog ne zagotavljajo zadostne zaščite pred XSS, za razliko od Latte. Kot predstavnika naivnega sistema bomo v primerih uporabili Twig, vendar isto velja tudi za druge sisteme. -Ranljivost atributov .[#toc-attribute-vulnerability] ----------------------------------------------------- +Ranljivost atributa +------------------- -Poskusimo v stran vnesti zlonamerno kodo s pomočjo atributa HTML, kot smo [pokazali zgoraj |#How does the vulnerability arise]. Imejmo predlogo v Twigu, ki prikazuje sliko: +Poskusili bomo v stran injicirati škodljivo kodo s pomočjo atributa HTML, kot smo si [prikazali zgoraj |#Kako nastane ranljivost]. Imejmo predlogo v Twigu, ki izrisuje sliko: ```twig .{file:Twig} {{ ``` -Upoštevajte, da okoli vrednosti atributa ni narekovajev. Koder jih je morda pozabil, kar se pač zgodi. Na primer, v jeziku React je koda zapisana takole, brez narekovajev, in koder, ki menja jezike, lahko zlahka pozabi na narekovaje. +Opazite, da okoli vrednosti atributov ni narekovajev. Koder jih je lahko pozabil, kar se pač dogaja. Na primer v Reactu se koda piše tako, brez narekovajev, in koder, ki menja jezike, lahko nato na narekovaje zlahka pozabi. -Napadalec vstavi spretno sestavljen niz `foo onload=alert('Hacked!')` kot napis slike. Vemo že, da Twig ne more ugotoviti, ali se spremenljivka izpiše v toku besedila HTML, znotraj atributa, znotraj komentarja HTML itd; skratka, ne razlikuje med konteksti. In samo mehanično pretvori znake `< > & ' "` v entitete HTML. -Tako bo nastala koda videti takole: +Napadalec kot opis slike vstavi spretno sestavljen niz `foo onload=alert('Hacked!')`. Že vemo, da Twig ne more prepoznati, ali se spremenljivka izpisuje v toku besedila HTML, znotraj atributa, komentarja HTML, itd., skratka ne razlikuje kontekstov. In samo mehansko pretvarja znake `< > & ' "` v HTML entitete. Torej bo rezultatna koda izgledala takole: ```html foo ``` -**Vzpostavljena je bila varnostna luknja!** +**In nastala je varnostna luknja!** -Ponarejeni atribut `onload` je postal del strani in brskalnik ga izvede takoj po prenosu slike. +Del strani je postal podtaknjen atribut `onload` in brskalnik ga takoj po prenosu slike zažene. -Zdaj si oglejmo, kako Latte obravnava isto predlogo: +Zdaj si poglejmo, kako se z enako predlogo spopade Latte: ```latte .{file:Latte} {$imageAlt} ``` -Latte vidi predlogo enako kot vi. Za razliko od Twiga razume HTML in ve, da se spremenljivka izpiše kot vrednost atributa, ki ni v narekovajih. Zato jih doda. Ko napadalec vstavi enak napis, bo nastala koda videti takole: +Latte vidi predlogo enako kot vi. Za razliko od Twiga razume HTML in ve, da se spremenljivka izpisuje kot vrednost atributa, ki ni v narekovajih. Zato jih dopolni. Ko napadalec vstavi enak opis, bo rezultatna koda izgledala takole: ```html foo onload=alert('Hacked!') @@ -323,10 +337,10 @@ Latte vidi predlogo enako kot vi. Za razliko od Twiga razume HTML in ve, da se s **Latte je uspešno preprečil XSS.** -Tiskanje spremenljivke v jeziku JavaScript .[#toc-printing-a-variable-in-javascript] ------------------------------------------------------------------------------------- +Izpis spremenljivke v JavaScriptu +--------------------------------- -Zaradi kontekstno občutljivega escapiranja je mogoče spremenljivke PHP uporabljati v jeziku JavaScript. +Zahvaljujoč kontekstno občutljivemu ubežanju je mogoče popolnoma izvorno uporabljati spremenljivke PHP znotraj JavaScripta. ```latte

                                                                                                                    {$movie}

                                                                                                                    @@ -334,7 +348,7 @@ Zaradi kontekstno občutljivega escapiranja je mogoče spremenljivke PHP uporabl ``` -Če spremenljivka `$movie` hrani niz `'Amarcord & 8 1/2'`, se ustvari naslednji rezultat. Opazite različno eskapiranje, ki se uporablja v HTML in JavaScript ter tudi v atributu `onclick`: +Če bo spremenljivka `$movie` vsebovala niz `'Amarcord & 8 1/2'`, se bo generiral naslednji izpis. Opazite, da se znotraj HTML uporabi drugačno ubežanje kot znotraj JavaScripta in še drugačno v atributu `onclick`: ```latte

                                                                                                                    Amarcord & 8 1/2

                                                                                                                    @@ -343,29 +357,27 @@ Zaradi kontekstno občutljivega escapiranja je mogoče spremenljivke PHP uporabl ``` -Preverjanje povezav .[#toc-link-checking] ------------------------------------------ +Preverjanje povezav +------------------- -Latte samodejno preveri, ali spremenljivka, uporabljena v atributih `src` ali `href`, vsebuje spletni naslov URL (tj. protokol HTTP), in prepreči pisanje povezav, ki bi lahko predstavljale varnostno tveganje. +Latte samodejno preverja, ali spremenljivka, uporabljena v atributih `src` ali `href`, vsebuje spletni URL (tj. protokol HTTP) in preprečuje izpis povezav, ki lahko predstavljajo varnostno tveganje. ```latte {var $link = 'javascript:attack()'} -click here +klikni ``` -Napiše: +Izpiše: ```latte -click here +klikni ``` -Preverjanje je mogoče izklopiti s filtrom [nocheck |filters#nocheck]. +Preverjanje se da izklopiti s pomočjo filtra [nocheck |filters#nocheck]. -Omejitve Latte .[#toc-limits-of-latte] -====================================== +Omejitve Latte +============== -Latte ni popolna zaščita XSS za celotno aplikacijo. Bili bi nezadovoljni, če bi pri uporabi aplikacije Latte nehali razmišljati o varnosti. -Cilj sistema Latte je zagotoviti, da napadalec ne more spreminjati strukture strani, posegati v elemente HTML ali atribute. Ne preverja pa vsebinske pravilnosti izpisanih podatkov. Prav tako ne preverja pravilnosti obnašanja JavaScripta. -To presega področje uporabe sistema za oblikovanje predlog. Preverjanje pravilnosti podatkov, zlasti tistih, ki jih vnese uporabnik in so zato nezaupljivi, je pomembna naloga programerja. +Latte ni popolnoma celovita zaščita pred XSS za celotno aplikacijo. Ne bi želeli, da bi ob uporabi Latte prenehali razmišljati o varnosti. Cilj Latte je zagotoviti, da napadalec ne more spremeniti strukture strani, podtakniti elementov HTML ali atributov. Vendar ne preverja vsebinske pravilnosti izpisanih podatkov. Ali pravilnosti delovanja JavaScripta. To že presega pristojnosti sistema predlog. Preverjanje pravilnosti podatkov, zlasti tistih, ki jih vnese uporabnik in so torej nezaupljivi, je pomembna naloga programerja. diff --git a/latte/sl/sandbox.texy b/latte/sl/sandbox.texy index ab0d266e0d..f0c9ae7d4f 100644 --- a/latte/sl/sandbox.texy +++ b/latte/sl/sandbox.texy @@ -1,12 +1,10 @@ -Peskovnik -********* +Peskovnik (Sandbox) +******************* -.[perex]{data-version:2.8} -Peskovnik zagotavlja varnostno plast, ki omogoča nadzor nad tem, katere oznake, funkcije PHP, metode itd. se lahko uporabljajo v predlogah. Zahvaljujoč načinu peskovnika lahko pri ustvarjanju predlog varno sodelujete s stranko ali zunanjim programerjem, ne da bi vas skrbelo ogrožanje aplikacije ali neželene operacije. +.[perex] +Peskovnik zagotavlja varnostno plast, ki vam daje nadzor nad tem, katere oznake, funkcije PHP, metode ipd. se lahko uporabljajo v predlogah. Zahvaljujoč načinu peskovnika lahko varno sodelujete s stranko ali zunanjim kodirnikom pri ustvarjanju predlog, ne da bi se morali bati, da bo prišlo do vdora v aplikacijo ali neželenih operacij. -Kako deluje? Preprosto določimo, kaj želimo dovoliti v predlogi. Na začetku je vse prepovedano in postopoma dodeljujemo dovoljenja: - -Naslednja koda dovoli predlogi uporabo oznak `{block}`, `{if}`, `{else}` in `{=}` (slednja je oznaka za [izpis spremenljivke ali izraza |tags#Printing]) in vseh filtrov: +Kako deluje? Enostavno definiramo, kaj vse bomo predlogi dovolili. Pri čemer je v osnovi vse prepovedano in postopoma dovoljujemo. Z naslednjo kodo omogočimo avtorju predloge uporabo oznak `{block}`, `{if}`, `{else}` in `{=}`, kar je oznaka za [izpis spremenljivke ali izraza |tags#Izpisovanje] in vse filtre: ```php $policy = new Latte\Sandbox\SecurityPolicy; @@ -16,7 +14,7 @@ $policy->allowFilters($policy::All); $latte->setPolicy($policy); ``` -Dovolimo lahko tudi dostop do globalnih funkcij, metod ali lastnosti predmetov: +Nadalje lahko dovolimo posamezne funkcije, metode ali lastnosti objektov: ```php $policy->allowFunctions(['trim', 'strlen']); @@ -24,30 +22,35 @@ $policy->allowMethods(Nette\Security\User::class, ['isLoggedIn', 'isAllowed']); $policy->allowProperties(Nette\Database\Row::class, $policy::All); ``` -Ali ni to neverjetno? Vse lahko nadzorujete na zelo nizki ravni. Če predloga poskuša poklicati nedovoljeno funkcijo ali dostopati do nedovoljene metode ali lastnosti, vrže izjemo `Latte\SecurityViolationException`. +Ni to čudovito? Lahko na zelo nizki ravni nadzorujete popolnoma vse. Če predloga poskuša poklicati nedovoljeno funkcijo ali dostopati do nedovoljene metode ali lastnosti, se konča z izjemo `Latte\SecurityViolationException`. -Ustvarjanje politik od začetka, ko je vse prepovedano, morda ni primerno, zato lahko začnete z varnimi temelji: +Ustvarjati politiko od točke nič, ko je prepovedano popolnoma vse, morda ni udobno, zato lahko začnete od varne osnove: ```php $policy = Latte\Sandbox\SecurityPolicy::createSafePolicy(); ``` -To pomeni, da so dovoljene vse standardne oznake, razen `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget`. -Prav tako so dovoljeni vsi standardni filtri, razen `datastream`, `noescape` in `nocheck`. Končno je dovoljen tudi dostop do metod in lastnosti objekta `$iterator`. +Varna osnova pomeni, da so dovoljene vse standardne oznake razen `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget`. Dovoljeni so standardni filtri razen `datastream`, `noescape` in `nocheck`. In končno je dovoljen dostop do metod in lastnosti objekta `$iterator`. -Pravila veljajo za predlogo, ki jo vstavimo z novim [`{sandbox}` |tags#Including Templates] oznako. Ki je nekaj podobnega kot `{include}`, vendar vklopi način peskovnika in tudi ne posreduje nobenih zunanjih spremenljivk: +Pravila se uporabljajo za predlogo, ki jo vstavimo z oznako [`{sandbox}` |tags#Vstavljanje predloge]. Kar je nekakšna analogija `{include}`, ki pa vklopi varni način in tudi ne posreduje nobenih spremenljivk: ```latte {sandbox 'untrusted.latte'} ``` -Tako lahko postavitev in posamezne strani uporabljajo vse oznake in spremenljivke kot prej, omejitve pa bodo veljale le za predlogo `untrusted.latte`. +Torej lahko postavitev in posamezne strani nemoteno uporabljajo vse oznake in spremenljivke, samo za predlogo `untrusted.latte` bodo veljale omejitve. -Nekatere kršitve, kot je uporaba prepovedane oznake ali filtra, se zaznajo v času sestavljanja. Druge, kot je klicanje nedovoljenih metod objekta, pa ob izvajanju. -Predloga lahko vsebuje tudi katere koli druge napake. Če želite preprečiti, da bi se iz predloge v peskovniku vrgla izjema, ki bi motila celotno izrisovanje, lahko določite [svoj lasten obdelovalec izjem |develop#exception handler], ki jo na primer samo zabeleži. +Nekatere kršitve, kot je uporaba prepovedane oznake ali filtra, se odkrijejo v času prevajanja. Druge, kot na primer klicanje nedovoljenih metod objekta, šele med izvajanjem. Predloga lahko vsebuje tudi kakršne koli druge napake. Da vam iz peskovniške predloge ne bi mogla skočiti izjema, ki bi prekinila celotno izrisovanje, lahko definirate lasten [obdelovalnik izjem po meri |develop#Obravnavalnik izjem], ki jo na primer zabeleži. -Če želimo način peskovnika vklopiti neposredno za vse predloge, je to preprosto: +Če bi želeli način peskovnika vklopiti neposredno za vse predloge, je to enostavno: ```php $latte->setSandboxMode(); ``` + +Da bi bili prepričani, da uporabnik v stran ne bo vstavil kode PHP, ki je sicer sintaktično pravilna, vendar prepovedana in povzroči PHP Compile Error, priporočamo, da pustite [predloge preverjati s PHP linterjem |develop#Preverjanje generirane kode]. To funkcionalnost vklopite z metodo `Engine::enablePhpLint()`. Ker za preverjanje potrebuje klicati binarno datoteko PHP, pot do nje posredujte kot parameter: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); +``` diff --git a/latte/sl/syntax.texy b/latte/sl/syntax.texy index 94e890c1df..28f6e058b2 100644 --- a/latte/sl/syntax.texy +++ b/latte/sl/syntax.texy @@ -2,62 +2,60 @@ Sintaksa ******** .[perex] -Syntax Latte je nastal zaradi praktičnih zahtev spletnih oblikovalcev. Iskali smo uporabniku najbolj prijazno sintakso, s katero lahko elegantno zapišete konstrukcije, ki so sicer pravi izziv. -Hkrati so vsi izrazi zapisani popolnoma enako kot v jeziku PHP, zato se vam ni treba učiti novega jezika. Preprosto kar najbolje izkoristite tisto, kar že poznate. +Sintaksa Latte je izšla iz praktičnih zahtev spletnih oblikovalcev. Iskali smo najbolj prijazno sintakso, s katero elegantno zapišete tudi konstrukte, ki sicer predstavljajo pravi oreh. Hkrati se vsi izrazi pišejo popolnoma enako kot v PHP, tako da se vam ni treba učiti novega jezika. Preprosto izkoristite tisto, kar že dolgo znate. -Spodaj je minimalna predloga, ki ponazarja nekaj osnovnih elementov: oznake, n:atribute, komentarje in filtre. +Spodaj je navedena minimalna predloga, ki ilustrira nekaj osnovnih elementov: oznake, n:atribute, komentarje in filtre. ```latte {* to je komentar *} -
                                                                                                                      {* n:if je n:atribut *} +
                                                                                                                        {* n:if je n:atribut *} {foreach $items as $item} {* oznaka, ki predstavlja zanko foreach *} -
                                                                                                                      • {$item|capitalize}
                                                                                                                      • {* oznaka, ki izpiše spremenljivko s filtrom *} -{/foreach} {* konec cikla *} +
                                                                                                                      • {$item|capitalize}
                                                                                                                      • {* oznaka, ki izpisuje spremenljivko s filtrom *} +{/foreach} {* konec zanke *}
                                                                                                                      ``` -Podrobneje si oglejmo te pomembne elemente in kako vam lahko pomagajo zgraditi neverjetno predlogo. +Poglejmo si podrobneje te pomembne elemente in kako vam lahko pomagajo ustvariti čudovito predlogo. -Oznake .[#toc-tags] -=================== +Oznake +====== -Predloga vsebuje oznake, ki nadzorujejo logiko predloge (na primer zanke *foreach*) ali izhodne izraze. Za oboje se uporablja en sam razmejitveni znak `{ ... }`, zato vam ni treba razmišljati o tem, kateri razmejitveni znak uporabiti v kateri situaciji, kot je to v drugih sistemih. -Če znaku `{` sledi narekovaj ali presledek, ga Latte ne obravnava kot začetek oznake, zato lahko v predlogah brez težav uporabljate konstrukcije JavaScript, JSON ali pravila CSS. +Predloga vsebuje oznake, ki krmilijo logiko predloge (na primer zanke *foreach*) ali izpisujejo izraze. Za oboje se uporablja en sam ločevalnik `{ ... }`, tako da vam ni treba razmišljati, kateri ločevalnik v kateri situaciji uporabiti, kot je to pri drugih sistemih. Če za znakom `{` sledi narekovaj ali presledek, ga Latte ne šteje za začetek oznake, zahvaljujoč čemur lahko v predlogah brez težav uporabljate tudi JavaScript konstrukte, JSON ali pravila v CSS. -Oglejte si [pregled vseh oznak |tags]. Poleg tega lahko ustvarite tudi [oznake po meri |extending-latte#tags]. +Oglejte si [pregled vseh oznak |tags]. Poleg tega si lahko ustvarite tudi [oznake po meri |custom tags]. -Latte razume PHP .[#toc-latte-understands-php] -============================================== +Latte razume PHP +================ -Znotraj oznak lahko uporabite izraze PHP, ki jih dobro poznate: +Znotraj oznak lahko uporabljate izraze PHP, ki jih dobro poznate: - spremenljivke -- nizov (vključno s HEREDOC in NOWDOC), polj, števil itd. -- [operatorji |https://www.php.net/manual/en/language.operators.php] -- klice funkcij in metod (ki so lahko omejeni s [peskovnikom |sandbox]) -- [ujemanje |https://www.php.net/manual/en/control-structures.match.php] +- nize (vključno s HEREDOC in NOWDOC), polja, števila ipd. +- [operatorje |https://www.php.net/manual/en/language.operators.php] +- klice funkcij in metod (ki jih je mogoče omejiti s [peskovnikom |sandbox]) +- [match |https://www.php.net/manual/en/control-structures.match.php] - [anonimne funkcije |https://www.php.net/manual/en/functions.arrow.php] -- [povratni klici |https://www.php.net/manual/en/functions.first_class_callable_syntax.php] -- večvrstični komentarji `/* ... */` -- itd. +- [povratne klice |https://www.php.net/manual/en/functions.first_class_callable_syntax.php] +- večvrstične komentarje `/* ... */` +- itd… -Poleg tega Latte dodaja več [lepih razširitev |#Syntactic Sugar] sintakse PHP. +Latte poleg tega sintakso PHP dopolnjuje z nekaj [prijetnih razširitev |#Sintaktični sladkor]. -n:atributi .[#toc-n-attributes] -=============================== +n:atributi +========== -Vsako parno oznako, kot je `{if} … {/if}`, ki deluje na posamezen element HTML, lahko zapišemo v zapisu [n:atribut |#n:attribute]. Na primer, `{foreach}` iz zgornjega primera je mogoče zapisati tudi na ta način: +Vse parne oznake, na primer `{if} … {/if}`, ki delujejo nad enim elementom HTML, se dajo prepisati v obliko n:atributov. Tako bi bilo mogoče zapisati na primer tudi `{foreach}` v uvodnem primeru: ```latte -
                                                                                                                        +
                                                                                                                        • {$item|capitalize}
                                                                                                                        ``` -Funkcija nato ustreza elementu HTML, v katerem je zapisana: +Funkcionalnost se nato nanaša na element HTML, v katerega je umeščen: ```latte {var $items = ['I', '♥', 'Latte']} @@ -65,7 +63,7 @@ Funkcija nato ustreza elementu HTML, v katerem je zapisana:

                                                                                                                        {$item}

                                                                                                                        ``` -Izpisi: +izpiše: ```latte

                                                                                                                        I

                                                                                                                        @@ -73,7 +71,7 @@ Izpisi:

                                                                                                                        Latte

                                                                                                                        ``` -Z uporabo predpone `inner-` lahko spremenimo obnašanje tako, da funkcionalnost velja samo za telo elementa: +S pomočjo predpone `inner-` lahko vedenje prilagodimo tako, da se nanaša samo na notranji del elementa: ```latte
                                                                                                                        @@ -82,7 +80,7 @@ Z uporabo predpone `inner-` lahko spremenimo obnašanje tako, da funkcionalnost
                                                                                                                        ``` -Izpisi: +Izpiše se: ```latte
                                                                                                                        @@ -95,49 +93,49 @@ Izpisi:
                                                                                                                        ``` -S predpono `tag-` se funkcionalnost uporablja samo za oznake HTML: +Ali pa s pomočjo predpone `tag-` uporabimo funkcionalnost samo na samih oznakah HTML: ```latte -

                                                                                                                        Title

                                                                                                                        +

                                                                                                                        Title

                                                                                                                        ``` -Glede na vrednost spremenljivke `$url` se izpiše: +Kar izpiše glede na spremenljivko `$url`: ```latte -// when $url is empty +{* ko je $url prazen *}

                                                                                                                        Title

                                                                                                                        -// when $url equals 'https://nette.org' +{* ko $url vsebuje 'https://nette.org' *}

                                                                                                                        Title

                                                                                                                        ``` -Vendar pa n:atributi niso le bližnjica za parne oznake, temveč obstajajo tudi nekateri čisti n:atributi, na primer koderjev najboljši prijatelj [n:class |tags#n:class]. +Vendar n:atributi niso samo bližnjica za parne oznake. Obstajajo tudi čisti n:atributi, kot na primer [n:href |application:creating-links#V predlogi presenterja] ali zelo priročen pomočnik kodirnika [n:class |tags#n:class]. -Filtri .[#toc-filters] -====================== +Filtri +====== -Oglejte si povzetek [standardnih filtrov |filters]. +Oglejte si pregled [standardnih filtrov |filters]. -Latte omogoča klicanje filtrov z uporabo zapisa z znakom pipe (pred njim je dovoljen presledek): +Filtri se zapisujejo za navpičnico (pred njo je lahko presledek): ```latte

                                                                                                                        {$heading|upper}

                                                                                                                        ``` -Filtri se lahko verižijo, v tem primeru se uporabljajo po vrstnem redu od leve proti desni: +Filtre je mogoče verižiti, nato pa se uporabljajo v vrstnem redu od leve proti desni: ```latte

                                                                                                                        {$heading|lower|capitalize}

                                                                                                                        ``` -Parametri so navedeni za imenom filtra, ločeni z dvopičjem ali vejico: +Parametri se vnašajo za imenom filtra, ločeni z dvopičji ali vejicami: ```latte

                                                                                                                        {$heading|truncate:20,''}

                                                                                                                        ``` -Filtre je mogoče uporabiti na izrazu: +Filtre je mogoče uporabiti tudi na izrazu: ```latte {var $name = ($title|upper) . ($subtitle|lower)} @@ -149,124 +147,130 @@ Na bloku:

                                                                                                                        {block |lower}{$heading}{/block}

                                                                                                                        ``` -Ali neposredno na vrednosti (v kombinaciji z [`{=expr}` | https://latte.nette.org/sl/tags#printing] oznako): +Ali neposredno na vrednosti (v kombinaciji z oznako [`{=expr}` |tags#Izpisovanje]): ```latte

                                                                                                                        {=' Hello world '|trim}

                                                                                                                        ``` -Komentarji .[#toc-comments] -=========================== +Dinamične oznake HTML .{data-version:3.0.9} +=========================================== + +Latte podpira dinamične oznake HTML, ki so uporabne, ko potrebujete fleksibilnost v imenih oznak: + +```latte +Heading +``` + +Zgornja koda lahko na primer generira `

                                                                                                                        Heading

                                                                                                                        ` ali `

                                                                                                                        Heading

                                                                                                                        ` glede na vrednost spremenljivke `$level`. Dinamične oznake HTML v Latte morajo biti vedno parne. Njihova alternativa je [n:tag |tags#n:tag]. + +Ker je Latte varen sistem predlog, preverja, ali je rezultatno ime oznake veljavno in ne vsebuje nobenih neželenih ali škodljivih vrednosti. Poleg tega zagotovi, da bo ime končne oznake vedno enako imenu začetne oznake. + -Komentarji so zapisani na ta način in se ne vključijo v izpis: +Komentarji +========== + +Komentarji se zapisujejo na ta način in se v izpis ne prenesejo: ```latte {* to je komentar v Latte *} ``` -Komentarji PHP delujejo znotraj oznak: +Znotraj oznak delujejo komentarji PHP: ```latte {include 'file.info', /* value: 123 */} ``` -Sintaktični sladkor .[#toc-syntactic-sugar] -=========================================== +Sintaktični sladkor +=================== -Nizi brez narekovajev .[#toc-strings-without-quotation-marks] -------------------------------------------------------------- +Nizi brez narekovajev +--------------------- -Za preproste nize lahko izpustite narekovaje: +Pri preprostih nizih lahko izpustite narekovaje: ```latte -as in PHP: {var $arr = ['hello', 'btn--default', '€']} +kot v PHP: {var $arr = ['hello', 'btn--default', '€']} -abbreviated: {var $arr = [hello, btn--default, €]} +skrajšano: {var $arr = [hello, btn--default, €]} ``` -Enostavni nizi so tisti, ki so sestavljeni izključno iz črk, številk, podčrtank, pomišljajev in pik. Ne smejo se začeti s številko in ne smejo se začeti ali končati s pomišljajem. -Ne smejo biti sestavljeni samo iz velikih črk in podčrtajev, ker se potem štejejo za konstanto (npr. `PHP_VERSION`). -In ne sme biti v koliziji s ključnimi besedami `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor`. +Preprosti nizi so tisti, ki so sestavljeni izključno iz črk, števk, podčrtajev, pomišljajev in pik. Ne smejo se začeti s števko in ne smejo se začeti ali končati s pomišljajem. Ne smejo biti sestavljeni samo iz velikih črk in podčrtajev, ker se potem štejejo za konstanto (npr. `PHP_VERSION`). In ne smejo biti v konfliktu s ključnimi besedami: `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor`. -Kratek terminarni operator .[#toc-short-ternary-operator] ---------------------------------------------------------- +Konstante +--------- -Če je tretja vrednost ternarnega operatorja prazna, jo lahko izpustite: +Ker je pri preprostih nizih mogoče izpuščati narekovaje, priporočamo, da za razlikovanje zapisujete globalne konstante s poševnico na začetku: ```latte -as in PHP: {$stock ? 'In stock' : ''} - -abbreviated: {$stock ? 'In stock'} +{if \PROJECT_ID === 1} ... {/if} ``` +Ta zapis je popolnoma veljaven v samem PHP, poševnica pove, da je konstanta v globalnem imenskem prostoru. -Sodobni zapis ključev v polju .[#toc-modern-key-notation-in-the-array] ----------------------------------------------------------------------- -Ključe polja lahko zapišete podobno kot poimenovane parametre pri klicanju funkcij: +Skrajšani ternarni operator +--------------------------- + +Če je tretja vrednost ternarnega operatorja prazna, jo lahko izpustite: ```latte -as in PHP: {var $arr = ['one' => 'item 1', 'two' => 'item 2']} +kot v PHP: {$stock ? 'Na zalogi' : ''} -modern: {var $arr = [one: 'item 1', two: 'item 2']} +skrajšano: {$stock ? 'Na zalogi'} ``` -Filtri .[#toc-filters] ----------------------- +Sodoben zapis ključev v polju +----------------------------- -Filtre lahko uporabite za katerikoli izraz, samo celoten izraz zapišite v oklepaj: +Ključe v polju lahko zapisujete podobno kot poimenovane parametre pri klicanju funkcij: ```latte -{var $content = ($text|truncate: 30|upper)} +kot v PHP: {var $arr = ['one' => 'item 1', 'two' => 'item 2']} + +sodobno: {var $arr = [one: 'item 1', two: 'item 2']} ``` -Operator `in` .[#toc-operator-in] ---------------------------------- +Filtri +------ -Operater `in` se lahko uporablja za zamenjavo funkcije `in_array()`. Primerjava je vedno stroga: +Filtre lahko uporabite za katere koli izraze, celoto je treba le zapreti v oklepaje: ```latte -{* like in_array($item, $items, true) *} -{if $item in $items} - ... -{/if} +{var $content = ($text|truncate: 30|upper)} ``` -.{data-version:2.9} -Izbirno veriženje z operatorjem z nedefinirano varnostjo .[#toc-optional-chaining-with-undefined-safe-operator] ---------------------------------------------------------------------------------------------------------------- +Operator `in` +------------- -Operator undefined-safe `??->` je podoben operatorju nullsafe `?->`, vendar ne sproži napake, če spremenljivka, lastnost ali indeks sploh ne obstajajo. +Z operatorjem `in` lahko nadomestite funkcijo `in_array()`. Primerjava je vedno stroga: ```latte -{$order??->id} +{* analogno in_array($item, $items, true) *} +{if $item in $items} + ... +{/if} ``` -to je način, kako povedati, da ko je `$order` definiran in ni null, se bo izračunal `$order->id`, ko pa je `$order` null ali ne obstaja, prenehamo s tem, kar počnemo, in preprosto vrnemo null. - -```latte -{$user??->address??->street} -// roughly means isset($user) && isset($user->address) ? $user->address->street : null -``` +Zgodovinsko okno +---------------- -Okno v zgodovino .[#toc-a-window-into-history] ----------------------------------------------- +Latte je v teku svoje zgodovine prišel s celo vrsto sintaktičnih sladkorčkov, ki so se po nekaj letih pojavili v samem PHP. Na primer, v Latte je bilo mogoče pisati polja kot `[1, 2, 3]` namesto `array(1, 2, 3)` ali uporabljati nullsafe operator `$obj?->foo` dolgo preden je bilo to mogoče v samem PHP. Latte je tudi uvedel operator za razširitev polja `(expand) $arr`, ki je ekvivalent današnjega operatorja `...$arr` iz PHP. -Latte je v svoji zgodovini ponudil številne sintaktične bonbončke, ki so se nekaj let pozneje pojavili tudi v samem jeziku PHP. V Latte je bilo na primer mogoče zapisati polja kot `[1, 2, 3]` namesto `array(1, 2, 3)` ali uporabiti operator nullsafe `$obj?->foo` veliko prej, preden je bilo to mogoče v samem PHP. Latte je uvedel tudi operator za razširitev polj `(expand) $arr`, ki je enakovreden današnjemu operatorju `...$arr` iz PHP. +Undefined-safe operator `??->`, kar je analogija nullsafe operatorja `?->`, ki pa ne sproži napake, če spremenljivka ne obstaja, je nastal iz zgodovinskih razlogov in danes priporočamo uporabo standardnega PHP operatorja `?->`. -Omejitve PHP v Latte .[#toc-php-limitations-in-latte] -===================================================== +Omejitve PHP v Latte +==================== -V Latte je mogoče zapisati samo izraze PHP. To pomeni, da ne morete deklarirati razredov ali uporabljati [kontrolnih struktur |https://www.php.net/manual/en/language.control-structures.php], kot so `if`, `foreach`, `switch`, `return`, `try`, `throw` in druge, namesto katerih Latte ponuja svoje [oznake |tags]. -Prav tako ne morete uporabljati [atributov |https://www.php.net/manual/en/language.attributes.php], [zaklepajev |https://www.php.net/manual/en/language.operators.execution.php] ali [čarobnih konstant |https://www.php.net/manual/en/language.constants.magic.php], ker to ne bi bilo smiselno. -Ne morete uporabljati niti `unset`, `echo`, `include`, `require`, `exit`, `eval`, ker to niso funkcije, temveč posebni konstrukti jezika PHP in zato niso izrazi. +V Latte je mogoče zapisovati samo izraze PHP. Torej ni mogoče uporabljati stavkov, zaključenih s podpičjem. Ni mogoče deklarirati razredov ali uporabljati [krmilnih struktur |https://www.php.net/manual/en/language.control-structures.php], npr. `if`, `foreach`, `switch`, `return`, `try`, `throw` in druge, namesto katerih Latte ponuja svoje [oznake |tags]. Prav tako ni mogoče uporabljati [atributov |https://www.php.net/manual/en/language.attributes.php], [backticks |https://www.php.net/manual/en/language.operators.execution.php] ali nekaterih [magičnih konstant |https://www.php.net/manual/en/language.constants.magic.php]. Ni mogoče uporabljati niti `unset`, `echo`, `include`, `require`, `exit`, `eval`, ker ne gre za funkcije, ampak posebne jezikovne konstrukte PHP, in torej niso izrazi. Komentarji so podprti samo večvrstični `/* ... */`. -Vendar lahko te omejitve obidete tako, da aktivirate razširitev [RawPhpExtension |develop#RawPhpExtension], ki vam omogoča uporabo katere koli kode PHP v oznaki `{php ...}` na odgovornost avtorja predloge. +Te omejitve je sicer mogoče zaobiti tako, da aktivirate razširitev [RawPhpExtension |develop#RawPhpExtension], zahvaljujoč kateri lahko nato uporabljate v oznaki `{php ...}` kakršno koli kodo PHP na odgovornost avtorja predloge. diff --git a/latte/sl/tags.texy b/latte/sl/tags.texy index a063850161..c8f3388f30 100644 --- a/latte/sl/tags.texy +++ b/latte/sl/tags.texy @@ -1,168 +1,174 @@ -Latte Tags -********** +Oznake Latte +************ .[perex] -Povzetek in opis vseh vgrajenih oznak Latte. +Pregled in opis vseh oznak sistema predlog Latte, ki so vam standardno na voljo. .[table-latte-tags language-latte] -|## Tiskanje -| `{$var}`, `{...}` ali `{=...}` | [natisne pobeglo spremenljivko ali izraz |#printing] -| `{$var\|filter}` | [natisne s filtri |#filters] -| `{l}` ali `{r}` | natisne znak `{` or `}` +|## Izpisovanje +| `{$var}`, `{...}` ali `{=...}` | [izpiše ubežano spremenljivko ali izraz |#Izpisovanje] +| `{$var\|filter}` | [izpiše z uporabo filtrov |#Filtri] +| `{l}` ali `{r}` | izpiše znak `{` ali `}` .[table-latte-tags language-latte] |## Pogoji -| `{if}`... `{elseif}`... `{else}`... `{/if}` | [pogoj če |#if-elseif-else] -| `{ifset}`... `{elseifset}`... `{/ifset}` | [pogoj ifset |#ifset-elseifset] -| `{ifchanged}`... `{/ifchanged}` | [testiranje, ali je prišlo do spremembe |#ifchanged] -| `{switch}` `{case}` `{default}` `{/switch}` | [pogoj switch |#switch-case-default] +| `{if}` … `{elseif}` … `{else}` … `{/if}` | [pogoj if |#if elseif else] +| `{ifset}` … `{elseifset}` … `{/ifset}` | [pogoj ifset |#ifset elseifset] +| `{ifchanged}` … `{/ifchanged}` | [preizkus, ali je prišlo do spremembe |#ifchanged] +| `{switch}` `{case}` `{default}` `{/switch}` | [pogoj switch |#switch case default] +| `n:else` | [alternativna vsebina za pogoje |#n:else] .[table-latte-tags language-latte] -|### zanke -| `{foreach}`... `{/foreach}` | [foreach |#foreach] -| `{for}`... `{/for}` | [for |#for] -| `{while}`... `{/while}` | [while |#while] -| `{continueIf $cond}` | [nadaljevanje na naslednjo iteracijo |#continueif-skipif-breakif] -| `{skipIf $cond}` | [preskočite iteracijo trenutne zanke |#continueif-skipif-breakif] -| `{breakIf $cond}` | [prekine zanko |#continueif-skipif-breakif] -| `{exitIf $cond}` | [predčasni izhod |#exitif] -| `{first}`... `{/first}` | [je to prva iteracija |#first-last-sep]? -| `{last}`... `{/last}` | [je to zadnja iteracija |#first-last-sep]? -| `{sep}`... `{/sep}` | [bo sledila naslednja iteracija |#first-last-sep]? -| `{iterateWhile}`... `{/iterateWhile}` | [strukturirano foreach |#iterateWhile] -| `$iterator` | [posebna spremenljivka znotraj zanke foreach |#$iterator] +|## Zanke +| `{foreach}` … `{/foreach}` | [#foreach] +| `{for}` … `{/for}` | [#for] +| `{while}` … `{/while}` | [#while] +| `{continueIf $cond}` | [nadaljuj z naslednjo iteracijo |#continueIf skipIf breakIf] +| `{skipIf $cond}` | [preskoči iteracijo |#continueIf skipIf breakIf] +| `{breakIf $cond}` | [prekinitev zanke |#continueIf skipIf breakIf] +| `{exitIf $cond}` | [zgodnji izhod |#exitIf] +| `{first}` … `{/first}` | [ali je to prvi prehod? |#first last sep] +| `{last}` … `{/last}` | [ali je to zadnji prehod? |#first last sep] +| `{sep}` … `{/sep}` | [ali bo sledil še en prehod? |#first last sep] +| `{iterateWhile}` … `{/iterateWhile}` | [strukturiran foreach |#iterateWhile] +| `$iterator` | [posebna spremenljivka znotraj foreach |#iterator] .[table-latte-tags language-latte] -|## Vključevanje drugih predlog -| `{include 'file.latte'}` | [vključitev predloge iz druge datoteke |#include] -| `{sandbox 'file.latte'}` | [vključitev predloge v načinu peskovnika |#sandbox] +|## Vstavljanje drugih predlog +| `{include 'file.latte'}` | [naloži predlogo iz druge datoteke |#include] +| `{sandbox 'file.latte'}` | [naloži predlogo v načinu peskovnika |#sandbox] .[table-latte-tags language-latte] -|### Bloki, postavitve, dedovanje predlog -| `{block}` | [anonimni blok |#block] -| `{block blockname}` | [opredelitev bloka |template-inheritance#blocks] -| `{define blockname}` | [opredelitev bloka za prihodnjo uporabo |template-inheritance#definitions] -| `{include blockname}` | [izpisi bloka |template-inheritance#printing-blocks] -| `{include blockname from 'file.latte'}` | [izpis bloka iz datoteke |template-inheritance#printing-blocks] -| `{import 'file.latte'}` | [nalaganje blokov iz druge predloge |template-inheritance#horizontal-reuse] -| `{layout 'file.latte'}` / `{extends}` | [določa datoteko za postavitev |template-inheritance#layout-inheritance] -| `{embed}`... `{/embed}` | [naloži predlogo ali blok in omogoča prepisovanje blokov |template-inheritance#unit-inheritance] -| `{ifset blockname}`... `{/ifset}` | [pogoj, če je blok definiran |template-inheritance#checking-block-existence] +|## Bloki, postavitve, dedovanje predlog +| `{block}` | [anonimni blok |#block] +| `{block blockname}` | [definira blok |template-inheritance#Bloki] +| `{define blockname}` | [definira blok za kasnejšo uporabo |template-inheritance#Definicije] +| `{include blockname}` | [izris bloka |template-inheritance#Izrisovanje blokov] +| `{include blockname from 'file.latte'}` | [izriše blok iz datoteke |template-inheritance#Izrisovanje blokov] +| `{import 'file.latte'}` | [naloži bloke iz predloge |template-inheritance#Horizontalna ponovna uporabnost] +| `{layout 'file.latte'}` / `{extends}` | [določa datoteko s postavitvijo |template-inheritance#Dedovanje postavitve] +| `{embed}` … `{/embed}` | [naloži predlogo ali blok in omogoča prepisovanje blokov |template-inheritance#Dedovanje enot] +| `{ifset blockname}` … `{/ifset}` | [pogoj, ali blok obstaja |template-inheritance#Preverjanje obstoja blokov] .[table-latte-tags language-latte] -|### Obravnava izjem -| `{try}`... `{else}`... `{/try}` | [lovljenje izjem |#try] -| `{rollback}` | [zavrže blok poskusov |#rollback] +|## Upravljanje izjem +| `{try}` … `{else}` … `{/try}` | [lovljenje izjem |#try] +| `{rollback}` | [zavrnitev bloka try |#rollback] .[table-latte-tags language-latte] |## Spremenljivke -| `{var $foo = value}` | [ustvarjanje spremenljivk |#var-default] -| `{default $foo = value}` | [privzeta vrednost, če spremenljivka ni deklarirana |#var-default] -| `{parameters}` | [deklaracija spremenljivk, tipi in privzete vrednosti |#parameters] -| `{capture}`... `{/capture}` | [zajem odseka v spremenljivko |#capture] +| `{var $foo = value}` | [ustvari spremenljivko |#var default] +| `{default $foo = value}` | [ustvari spremenljivko, če ne obstaja |#var default] +| `{parameters}` | [deklarira spremenljivke, tipe in privzete vrednosti |#parameters] +| `{capture}` … `{/capture}` | [zajame blok v spremenljivko |#capture] .[table-latte-tags language-latte] |## Tipi -| `{varType}` | [deklarira vrsto spremenljivke |type-system#varType] -| `{varPrint}` | [predlaga vrste spremenljivk |type-system#varPrint] -| `{templateType}` | [deklarira vrste spremenljivk z uporabo razreda |type-system#templateType] -| `{templatePrint}` | [ustvari razred z lastnostmi |type-system#templatePrint] +| `{varType}` | [deklarira tip spremenljivke |type-system#varType] +| `{varPrint}` | [predlaga tipe spremenljivk |type-system#varPrint] +| `{templateType}` | [deklarira tipe spremenljivk glede na razred |type-system#templateType] +| `{templatePrint}` | [predlaga razred s tipi spremenljivk |type-system#templatePrint] .[table-latte-tags language-latte] -|## Prevod -| `{_string}` | [izpiše prevedeno |#Translation] -| `{translate}`... `{/translate}` | [prevede vsebino |#Translation] +|## Prevodi +| `{_...}` | [izpiše prevod |#Prevodi] +| `{translate}` … `{/translate}` | [prevede vsebino |#Prevodi] .[table-latte-tags language-latte] -|## Drugo -| `{contentType}` | [preklopi način escapiranja in pošlje glavo HTTP |#contenttype] -| `{debugbreak}` | [nastavi točko prekinitve kode |#debugbreak] -| `{do}` | [ovrednoti izraz, ne da bi ga natisnil |#do] -| `{dump}` | [izpiše spremenljivke v Tracy Bar |#dump] -| `{spaceless}`... `{/spaceless}` | [odstrani nepotrebne bele prostore |#spaceless] -| `{syntax}` | [preklopi sintakso med izvajanjem |#syntax] -| `{trace}` | [prikaže sled zastoja |#trace] +|## Ostalo +| `{contentType}` | [preklopi ubežanje in pošlje glavo HTTP |#contentType] +| `{debugbreak}` | [postavi prelomno točko v kodo |#debugbreak] +| `{do}` | [izvede kodo, vendar ničesar ne izpiše |#do] +| `{dump}` | [izpiše spremenljivke v Tracy Bar |#dump] +| `{php}` | [izvede poljubno kodo PHP |#php] +| `{spaceless}` … `{/spaceless}` | [odstrani odvečne presledke |#spaceless] +| `{syntax}` | [sprememba sintakse med izvajanjem |#syntax] +| `{trace}` | [prikaže sledenje sklada |#trace] .[table-latte-tags language-latte] -|## Pomočniki za oznake HTML -| `n:class` | [atribut pametnega razreda |#n:class] -| `n:attr` | [pametni atributi HTML |#n:attr] -| `n:tag` | [dinamično ime elementa HTML |#n:tag] -| `n:ifcontent` | [Opustitev prazne oznake HTML |#n:ifcontent] +|## Pomočniki kodirnika HTML +| `n:class` | [dinamični zapis atributa HTML class |#n:class] +| `n:attr` | [dinamični zapis poljubnih atributov HTML |#n:attr] +| `n:tag` | [dinamični zapis imena elementa HTML |#n:tag] +| `n:ifcontent` | [izpusti prazno oznako HTML |#n:ifcontent] .[table-latte-tags language-latte] -|## Na voljo samo v okolju Nette Framework -| `n:href` | [povezava v elementih HTML `` |application:creating-links#In the Presenter Template] -| `{link}` | [izpiše povezavo |application:creating-links#In the Presenter Template] -| `{plink}` | [natisne povezavo do predvajalnika |application:creating-links#In the Presenter Template] -| `{control}` | [natisne komponento |application:components#Rendering] -| `{snippet}`... `{/snippet}` | [odlomek predloge, ki se lahko pošlje z AJAXom |application:ajax#tag-snippet] -| `{snippetArea}` | ovojnica snippets -| `{cache}`... `{/cache}` | [predpomnilnik oddelka predloge |caching:en#caching-in-latte] +|## Na voljo samo v Nette Frameworku +| `n:href` | [povezava, uporabljena v elementih HTML `` |application:creating-links#V predlogi presenterja] +| `{link}` | [izpiše povezavo |application:creating-links#V predlogi presenterja] +| `{plink}` | [izpiše povezavo na presenter |application:creating-links#V predlogi presenterja] +| `{control}` | [izriše komponento |application:components#Izrisovanje] +| `{snippet}` … `{/snippet}` | [odrezek, ki ga je mogoče poslati z AJAXom |application:ajax#Odrezki v Latte] +| `{snippetArea}` | [ovoj za odrezke |application:ajax#Območja odrezkov] +| `{cache}` … `{/cache}` | [predpomni del predloge |caching:#Predpomnjenje v Latte] .[table-latte-tags language-latte] |## Na voljo samo z Nette Forms -| `{form}`... `{/form}` | [natisne element obrazca |forms:rendering#form] -| `{label}`... `{/label}` | [natisne vhodno oznako obrazca |forms:rendering#label-input] -| `{input}` | [natisne vhodni element obrazca |forms:rendering#label-input] -| `{inputError}` | [izpiše sporočilo o napaki za vhodni element obrazca |forms:rendering#inputError] -| `n:name` | [aktivira vnosni element HTML |forms:rendering#n:name] -| `{formPrint}` | [ustvari načrt obrazca Latte |forms:rendering#formPrint] -| `{formPrintClass}` | [izpiše razred PHP za podatke obrazca |forms:in-presenter#mapping-to-classes] -| `{formContext}`... `{/formContext}` | [delni prikaz obrazca |forms:rendering#special-cases] +| `{form}` … `{/form}` | [izriše oznake obrazca |forms:rendering#form] +| `{label}` … `{/label}` | [izriše oznako elementa obrazca |forms:rendering#label input] +| `{input}` | [izriše element obrazca |forms:rendering#label input] +| `{inputError}` | [izpiše sporočilo o napaki elementa obrazca |forms:rendering#inputError] +| `n:name` | [oživi element obrazca |forms:rendering#n:name] +| `{formContainer}` … `{/formContainer}` | [risanje vsebnika obrazca |forms:rendering#Posebni primeri] + +.[table-latte-tags language-latte] +|### Na voljo samo z Nette Assets +| `{asset}` | [upodobi sredstvo kot element HTML ali URL |assets:#asset] +| `{preload}` | [ustvari namige pred nalaganjem za optimizacijo zmogljivosti |assets:#preload] +| `n:asset` | [dodaja atribute sredstev elementom HTML |assets:#n:asset] -Tiskanje .[#toc-printing] -========================= +Izpisovanje +=========== `{$var}` `{...}` `{=...}` ------------------------- -Latte uporablja oznako `{=...}` za izpis poljubnega izraza na izhod. Če se izraz začne s spremenljivko ali klicem funkcije, ni treba napisati znaka enakosti. Kar v praksi pomeni, da ga skoraj nikoli ni treba zapisati: +V Latte se uporablja oznaka `{=...}` za izpis katerega koli izraza na izhod. Latte skrbi za vaše udobje, zato če se izraz začne s spremenljivko ali klicem funkcije, ni treba pisati enačaja. Kar v praksi pomeni, da ga skoraj nikoli ni treba pisati: ```latte -Name: {$name} {$surname}
                                                                                                                        -Age: {date('Y') - $birth}
                                                                                                                        +Ime: {$name} {$surname}
                                                                                                                        +Starost: {date('Y') - $birth}
                                                                                                                        ``` -Kot izraz lahko zapišete karkoli, kar poznate iz PHP. Ni se vam treba učiti novega jezika. Na primer: +Kot izraz lahko zapišete karkoli, kar poznate iz PHP. Ni vam treba učiti novega jezika. Tako na primer: ```latte {='0' . ($num ?? $num * 3) . ', ' . PHP_VERSION} ``` -Če pa boste v prejšnjem primeru našli kakšen pomen, nam pišite :-) +Prosim, ne iščite v prejšnjem primeru nobenega smisla, a če bi ga tam našli, nam pišite :-) -Pobegli izpis .[#toc-escaping-output] -------------------------------------- +Ubežanje izpisa +--------------- -Katera je najpomembnejša naloga sistema predlog? Izogibanje varnostnim luknjam. In prav to stori Latte, kadar koli nekaj natisnete na izhod. Vse samodejno pobegne: +Kaj je najpomembnejša naloga sistema predlog? Preprečiti varnostne luknje. In natanko to počne Latte vedno, ko nekaj izpisujete. Samodejno to ubeži: ```latte -

                                                                                                                        {='one < two'}

                                                                                                                        {* prints: '

                                                                                                                        one < two

                                                                                                                        ' *} +

                                                                                                                        {='one < two'}

                                                                                                                        {* izpiše: '

                                                                                                                        one < two

                                                                                                                        ' *} ``` -To je tako pomembna in edinstvena lastnost, da smo [ji |safety-first#context-aware-escaping] posvetili [posebno poglavje |safety-first#context-aware-escaping]. +Da bi bili natančni, Latte uporablja kontekstno občutljivo ubežanje, kar je tako pomembna in edinstvena stvar, da smo ji posvetili [ločeno poglavje |safety-first#Kontekstno občutljivo ubežanje]. -In če iz zanesljivega vira natisnete vsebino, kodirano v HTML? Potem lahko pobege preprosto izklopite: +In kaj če izpisujete vsebino, kodirano v HTML, iz zaupanja vrednega vira? Potem lahko enostavno izklopite ubežanje: ```latte {$trustedHtmlString|noescape} ``` .[warning] -Zloraba filtra `noescape` lahko privede do ranljivosti XSS! Nikoli ga ne uporabljajte, razen če ste **trdno prepričani**, kaj počnete in da niz, ki ga tiskate, prihaja iz zaupanja vrednega vira. +Napačna uporaba filtra `noescape` lahko vodi do nastanka ranljivosti XSS! Nikoli ga ne uporabljajte, če niste **popolnoma prepričani**, kaj počnete, in da izpisani niz prihaja iz zaupanja vrednega vira. -Tiskanje v jeziku JavaScript .[#toc-printing-in-javascript] ------------------------------------------------------------ +Izpis v JavaScriptu +------------------- -Zaradi kontekstno občutljivega eskapiranja je tiskanje spremenljivk v javascriptu zelo enostavno in Latte jih bo pravilno eskaliral. +Zahvaljujoč kontekstno občutljivemu ubežanju je čudovito enostavno izpisovati spremenljivke znotraj JavaScripta in pravilno ubežanje uredi Latte. -Ni nujno, da je spremenljivka niz, podprta je katerakoli podatkovna vrsta, ki se nato kodira kot JSON: +Spremenljivka ni nujno samo niz, podprt je kateri koli podatkovni tip, ki se nato zakodira kot JSON: ```latte {var $foo = ['hello', true, 1]} @@ -179,7 +185,7 @@ Generira: ``` -To je tudi razlog, zakaj spremenljivke ne postavljajte v narekovaje**: Latte jih doda okoli nizov. Če želite spremenljivko v nizu vstaviti v drug niz, ju preprosto združite: +To je tudi razlog, zakaj se okoli spremenljivke **ne pišejo narekovaji**: Latte jih pri nizih doda sam. In če bi želeli nizovno spremenljivko vstaviti v drug niz, jih preprosto združite: ```latte ``` -Filtri .[#toc-filters] ----------------------- +Filtri +------ -Izpisani izraz lahko spremenite s [filtri |syntax#filters]. Ta primer na primer pretvori niz v velike črke in ga skrajša na največ 30 znakov: +Izpisani izraz je mogoče spremeniti s [filtrom |syntax#Filtri]. Tako na primer niz pretvorimo v velike črke in skrajšamo na največ 30 znakov: ```latte {$string|upper|truncate:30} ``` -Filtre lahko uporabite tudi za dele izraza, kot sledi: +Filtre lahko uporabljate tudi na delnih delih izraza na ta način: ```latte {$left . ($middle|upper) . $right} ``` -Pogoji .[#toc-conditions] -========================= +Pogoji +====== `{if}` `{elseif}` `{else}` -------------------------- -Pogoji se obnašajo enako kot njihovi ustrezniki v jeziku PHP. Uporabljate lahko enake izraze, kot jih poznate iz PHP, ni se vam treba učiti novega jezika. +Pogoji se obnašajo enako kot njihovi ustrezni v PHP. V njih lahko uporabljate tudi enake izraze, kot jih poznate iz PHP, ni vam treba učiti novega jezika. ```latte {if $product->inStock > Stock::Minimum} - In stock + Na zalogi {elseif $product->isOnWay()} - On the way + Na poti {else} - Not available + Ni na voljo {/if} ``` -Tako kot katero koli drugo parno oznako lahko par `{if} ... {/ if}` zapišete kot [n:atribut |syntax#n:attributes], na primer: +Kot vsako parno oznako, tako tudi dvojico `{if} ... {/if}` lahko zapišemo tudi v obliki [n:attributu |syntax#n:atributi], na primer: ```latte -

                                                                                                                        In stock {$count} items

                                                                                                                        +

                                                                                                                        Na zalogi {$count} kosov

                                                                                                                        ``` -Ali veste, da lahko n:atributom dodate predpono `tag-`? Potem bo pogoj vplival samo na oznake HTML, vsebina med njimi pa bo vedno izpisana: +Veste, da lahko n:atributom dodate predpono `tag-`? Potem se bo pogoj nanašal samo na izpis oznak HTML in vsebina med njimi se bo izpisala vedno: ```latte
                                                                                                                        Hello -{* prints 'Hello' when $clickable is falsey *} -{* prints 'Hello' when $clickable is truthy *} +{* izpiše 'Hello', ko je $clickable neresničen *} +{* izpiše 'Hello', ko je $clickable resničen *} ``` -Lepo. +Božansko. + + +`n:else` .{data-version:3.0.11} +------------------------------- + +Če pogoj `{if} ... {/if}` zapišete v obliki [n:attributu |syntax#n:atributi], imate možnost navesti tudi alternativno vejo s pomočjo `n:else`: + +```latte +Na zalogi {$count} kosov + +ni na voljo +``` + +Atribut `n:else` lahko uporabite tudi v dvojici z [`n:ifset` |#ifset elseifset], [`n:foreach` |#foreach], [`n:try` |#try], [#`n:ifcontent`] in [`n:ifchanged` |#ifchanged]. `{/if $cond}` ------------- -Morda vas bo presenetilo, da lahko izraz v pogoju `{if}` določite tudi v končni oznaki. To je uporabno v primerih, ko ob odprtju oznake še ne poznamo vrednosti pogoja. Temu pravimo odložena odločitev. +Morda vas bo presenetilo, da lahko izraz v pogoju `{if}` navedete tudi v zaključni oznaki. To pride prav v situacijah, ko ob odpiranju pogoja njegove vrednosti še ne poznamo. Recimo temu odložena odločitev. -Na primer, začnemo izpisovati tabelo z zapisi iz podatkovne zbirke in šele po končanem izpisu ugotovimo, da v podatkovni zbirki ni bilo nobenega zapisa. Zato na koncu oznake `{/if}` postavimo pogoj in če ni nobenega zapisa, se ne bo izpisal noben: +Na primer, začnemo izpisovati tabelo z zapisi iz baze podatkov in šele po končanem izpisu ugotovimo, da v bazi podatkov ni bilo nobenega zapisa. Tako damo na to pogoj v končno oznako `{/if}` in če nobenega zapisa ne bo, se nič od tega ne bo izpisalo: ```latte {if} -

                                                                                                                        Printing rows from the database

                                                                                                                        +

                                                                                                                        Izpis vrstic iz baze podatkov

                                                                                                                        {foreach $resultSet as $row} @@ -266,28 +286,28 @@ Na primer, začnemo izpisovati tabelo z zapisi iz podatkovne zbirke in šele po Priročno, kajne? -V odloženem pogoju lahko uporabite tudi `{else}`, ne pa tudi `{elseif}`. +V odloženem pogoju lahko uporabite tudi `{else}`, vendar ne `{elseif}`. `{ifset}` `{elseifset}` ----------------------- .[note] -Glej tudi [`{ifset block}` |template-inheritance#checking-block-existence] +Glejte tudi [`{ifset block}` |template-inheritance#Preverjanje obstoja blokov] -Pogoj `{ifset $var}` uporabite za ugotavljanje, ali spremenljivka (ali več spremenljivk) obstaja in ima vrednost, ki ni ničelna. To je pravzaprav enako kot `if (isset($var))` v PHP. Kot vsako parno oznako lahko tudi to zapišemo v obliki [n:atribut |syntax#n:attributes], zato jo prikažimo na primeru: +S pomočjo pogoja `{ifset $var}` ugotovimo, ali spremenljivka (ali več spremenljivk) obstaja in ima vrednost, ki ni *null*. Pravzaprav gre za isto kot `if (isset($var))` v PHP. Kot vsako parno oznako jo lahko zapišemo tudi v obliki [n:attributu |syntax#n:atributi], zato si to poglejmo kot primer: ```latte - + ``` -`{ifchanged}` .{data-version:2.9} ---------------------------------- +`{ifchanged}` +------------- `{ifchanged}` preveri, ali se je vrednost spremenljivke spremenila od zadnje iteracije v zanki (foreach, for ali while). -Če v oznaki navedemo eno ali več spremenljivk, bo preveril, ali se je katera od njih spremenila, in ustrezno izpiše vsebino. Naslednji primer na primer pri naštevanju imen ob vsaki spremembi izpiše prvo črko imena kot naslov: +Če v oznaki navedemo eno ali več spremenljivk, bo preverjal, ali se je katera od njih spremenila, in glede na to izpisal vsebino. Na primer, naslednji primer izpiše prvo črko imena kot naslov vsakič, ko se pri izpisu imen spremeni: ```latte {foreach ($names|sort) as $name} @@ -297,7 +317,7 @@ Pogoj `{ifset $var}` uporabite za ugotavljanje, ali spremenljivka (ali več spre {/foreach} ``` -Če pa ni podan noben argument, se bo sama izpisana vsebina preverila glede na prejšnje stanje. To pomeni, da lahko v prejšnjem primeru argument v oznaki mirno izpustimo. Seveda lahko uporabimo tudi [n:attribute |syntax#n:attributes]: +Če pa ne navedemo nobenega argumenta, se bo preverjala izrisana vsebina v primerjavi z njenim prejšnjim stanjem. To pomeni, da lahko v prejšnjem primeru mirno izpustimo argument v oznaki. In seveda lahko uporabimo tudi [n:attribut |syntax#n:atributi]: ```latte {foreach ($names|sort) as $name} @@ -307,49 +327,49 @@ Pogoj `{ifset $var}` uporabite za ugotavljanje, ali spremenljivka (ali več spre {/foreach} ``` -V stavek `{else}` lahko vključite tudi stavek znotraj stavka `{ifchanged}`. +Znotraj `{ifchanged}` lahko navedemo tudi klavzulo `{else}`. `{switch}` `{case}` `{default}` ------------------------------- -Primerja vrednost z več možnostmi. To je podobno strukturi `switch`, ki jo poznate iz PHP. Vendar jo Latte izboljša: +Primerja vrednost z več možnostmi. Gre za analogijo pogojnemu stavku `switch`, ki ga poznate iz PHP. Vendar ga Latte izboljšuje: - uporablja strogo primerjavo (`===`) - ne potrebuje `break` -Tako je to natančen ekvivalent strukture `match`, ki jo vsebuje PHP 8.0. +Je torej natančen ekvivalent strukture `match`, s katero prihaja PHP 8.0. ```latte {switch $transport} {case train} - By train + Z vlakom {case plane} - By plane + Z letalom {default} - Differently + Drugače {/switch} ``` -.{data-version:2.9} + Klavzula `{case}` lahko vsebuje več vrednosti, ločenih z vejicami: ```latte {switch $status} -{case $status::New}new item -{case $status::Sold, $status::Unknown}not available +{case $status::New}nova postavka +{case $status::Sold, $status::Unknown}ni na voljo {/switch} ``` -Zanke .[#toc-loops] -=================== +Zanke +===== -V Latte so vam na voljo vse zanke, ki jih poznate iz PHP: foreach, for in while. +V Latte najdete vse zanke, ki jih poznate iz PHP: foreach, for in while. `{foreach}` ----------- -Cikel zapišete na popolnoma enak način kot v PHP: +Zanko zapišemo popolnoma enako kot v PHP: ```latte {foreach $langs as $code => $lang} @@ -357,11 +377,11 @@ Cikel zapišete na popolnoma enak način kot v PHP: {/foreach} ``` -Poleg tega ima nekaj priročnih izboljšav, o katerih bomo govorili zdaj. +Poleg tega ima nekaj priročnih izboljšav, o katerih bomo zdaj govorili. -Latte na primer preverja, da ustvarjene spremenljivke ne prepišejo globalnih spremenljivk z istim imenom. To vas bo rešilo, ko boste domnevali, da je `$lang` trenutni jezik strani, in se ne boste zavedali, da je `foreach $langs as $lang` prepisala to spremenljivko. +Latte na primer preverja, ali ustvarjene spremenljivke pomotoma ne prepišejo globalnih spremenljivk istega imena. To reši situacije, ko računate na to, da je v `$lang` trenutni jezik strani, in se ne zavedate, da vam je `foreach $langs as $lang` to spremenljivko prepisalo. -Z [n:atributom |syntax#n:attributes] lahko zelo elegantno in ekonomično zapišete tudi zanko foreach: +Zanko foreach lahko tudi zelo elegantno in varčno zapišemo s pomočjo [n:attributu |syntax#n:atributi]: ```latte
                                                                                                                          @@ -369,7 +389,7 @@ Z [n:atributom |syntax#n:attributes] lahko zelo elegantno in ekonomično zapiše
                                                                                                                        ``` -Ali ste vedeli, da lahko predpono `inner-` dodamo predponi n:attributes? Zdaj se bo v zanki ponovil samo notranji del elementa: +Veste, da lahko n:atributom dodate predpono `inner-`? Potem se bo v zanki ponavljala samo notranjost elementa: ```latte
                                                                                                                        @@ -390,17 +410,17 @@ Tako se izpiše nekaj takega: ``` -`{else}` .{data-version:2.9}{toc: foreach-else} ------------------------------------------------ +`{else}` .{toc: foreach-else} +----------------------------- -Zanka `foreach` lahko sprejme neobvezno `{else}` določilo, katerega besedilo se prikaže, če je podano polje prazno: +Znotraj zanke `foreach` lahko navedemo klavzulo `{else}`, katere vsebina se prikaže, če je zanka prazna: ```latte
                                                                                                                          {foreach $people as $person}
                                                                                                                        • {$person->name}
                                                                                                                        • {else} -
                                                                                                                        • Sorry, no users in this list
                                                                                                                        • +
                                                                                                                        • Žal na tem seznamu ni uporabnikov
                                                                                                                        • {/foreach}
                                                                                                                        ``` @@ -409,17 +429,17 @@ Zanka `foreach` lahko sprejme neobvezno `{else}` določilo, katerega besedilo se `$iterator` ----------- -Znotraj zanke `foreach` se inicializira spremenljivka `$iterator`. V njej so shranjene pomembne informacije o trenutni zanki. +Znotraj zanke `foreach` Latte ustvari spremenljivko `$iterator`, s pomočjo katere lahko ugotavljamo koristne informacije o potekajoči zanki: -- `$iterator->first` - ali je to prva iteracija? -- `$iterator->last` - ali je to zadnja iteracija? -- `$iterator->counter` - števec iteracij, začne se od 1 -- `$iterator->counter0` - števec iteracij, začne se z 0 .{data-version:2.9} -- `$iterator->odd` - ali je ta iteracija liha? -- `$iterator->even` - ali je ta iteracija soda? -- `$iterator->parent` - iterator, ki obkroža trenutni iterator .{data-version:2.9} -- `$iterator->nextValue` - naslednji element v zanki -- `$iterator->nextKey` - ključ naslednjega elementa v zanki +- `$iterator->first` - ali gre za prvi prehod skozi zanko? +- `$iterator->last` - ali gre za zadnji prehod? +- `$iterator->counter` - kateri prehod je to, šteto od ena? +- `$iterator->counter0` - kateri prehod je to, šteto od nič? +- `$iterator->odd` - ali gre za lih prehod? +- `$iterator->even` - ali gre za sod prehod? +- `$iterator->parent` - iterator, ki obdaja trenutnega +- `$iterator->nextValue` - naslednja postavka v zanki +- `$iterator->nextKey` - ključ naslednje postavke v zanki ```latte @@ -435,20 +455,19 @@ Znotraj zanke `foreach` se inicializira spremenljivka `$iterator`. V njej so shr {/foreach} ``` -Latte je pametna in `$iterator->last` ne deluje le za polja, temveč tudi takrat, ko zanka teče nad splošnim iteratorjem, kjer število elementov ni vnaprej znano. +Latte je pameten in `$iterator->last` deluje ne samo pri poljih, ampak tudi, ko zanka poteka nad splošnim iteratorjem, kjer število postavk ni vnaprej znano. `{first}` `{last}` `{sep}` -------------------------- -Te oznake lahko uporabite znotraj zanke `{foreach}`. Vsebina `{first}` se pri prvem prehodu izriše. -Vsebina `{last}` se prikaže ... lahko uganete? Da, pri zadnjem prehodu. To so pravzaprav bližnjice za `{if $iterator->first}` in `{if $iterator->last}`. +Te oznake lahko uporabljamo znotraj zanke `{foreach}`. Vsebina `{first}` se izriše, če gre za prvi prehod. Vsebina `{last}` se izriše … ali uganete? Da, če gre za zadnji prehod. Pravzaprav gre za okrajšave za `{if $iterator->first}` in `{if $iterator->last}`. -Oznake se lahko zapišejo tudi kot [n:attributes |syntax#n:attributes]: +Oznake lahko tudi elegantno uporabimo kot [n:attribut |syntax#n:atributi]: ```latte {foreach $rows as $row} - {first}

                                                                                                                        List of names

                                                                                                                        {/first} + {first}

                                                                                                                        Seznam imen

                                                                                                                        {/first}

                                                                                                                        {$row->name}

                                                                                                                        @@ -456,7 +475,7 @@ Oznake se lahko zapišejo tudi kot [n:attributes |syntax#n:attributes]: {/foreach} ``` -Vsebina `{sep}` se izriše, če iteracija ni zadnja, zato je primerna za izpis razmejitvenih elementov, kot so vejice med naštetimi elementi: +Vsebina oznake `{sep}` se izriše, če prehod ni zadnji, zato je primerna za izrisovanje ločil, na primer vejic med izpisanimi postavkami: ```latte {foreach $items as $item} {$item} {sep}, {/sep} {/foreach} @@ -465,12 +484,12 @@ Vsebina `{sep}` se izriše, če iteracija ni zadnja, zato je primerna za izpis r To je precej praktično, kajne? -`{iterateWhile}` .{data-version:2.10} -------------------------------------- +`{iterateWhile}` +---------------- -Poenostavlja grupiranje linearnih podatkov med iteracijo v zanki foreach tako, da izvaja iteracijo v ugnezdeni zanki, dokler je pogoj izpolnjen. [Preberite navodila v kuharski knjigi |cookbook/iteratewhile]. +Poenostavlja združevanje linearnih podatkov med iteriranjem v zanki foreach tako, da iteracijo izvaja v vgnezdeni zanki, dokler je pogoj izpolnjen. [Preberite podrobna navodila|cookbook/grouping]. -Elegantno lahko nadomesti tudi `{first}` in `{last}` v zgornjem primeru: +Lahko tudi elegantno nadomesti `{first}` in `{last}` v zgornjem primeru: ```latte {foreach $rows as $row} @@ -487,19 +506,21 @@ Elegantno lahko nadomesti tudi `{first}` in `{last}` v zgornjem primeru: {/foreach} ``` +Glejte tudi filtre [batch |filters#batch] in [group |filters#group]. + `{for}` ------- -Cikel zapišemo na popolnoma enak način kot v PHP: +Zanko zapišemo popolnoma enako kot v PHP: ```latte {for $i = 0; $i < 10; $i++} - Item #{$i} + Postavka {$i} {/for} ``` -Oznaka se lahko zapiše tudi kot [n:atribut |syntax#n:attributes]: +Oznako lahko uporabimo tudi kot [n:attribut |syntax#n:atributi]: ```latte

                                                                                                                        {$i}

                                                                                                                        @@ -509,7 +530,7 @@ Oznaka se lahko zapiše tudi kot [n:atribut |syntax#n:attributes]: `{while}` --------- -Ponovno zapišemo cikel na popolnoma enak način kot v PHP: +Zanko spet zapišemo popolnoma enako kot v PHP: ```latte {while $row = $result->fetch()} @@ -517,7 +538,7 @@ Ponovno zapišemo cikel na popolnoma enak način kot v PHP: {/while} ``` -ali kot [n:atribut |syntax#n:attributes]: +Ali kot [n:attribut |syntax#n:atributi]: ```latte @@ -525,7 +546,7 @@ ali kot [n:atribut |syntax#n:attributes]: ``` -Varianta s pogojem v končni oznaki ustreza zanki do-while v PHP: +Možna je tudi varianta s pogojem v končni oznaki, ki ustreza v PHP zanki do-while: ```latte {while} @@ -537,7 +558,7 @@ Varianta s pogojem v končni oznaki ustreza zanki do-while v PHP: `{continueIf}` `{skipIf}` `{breakIf}` ------------------------------------- -Obstajajo posebne oznake, ki jih lahko uporabite za nadzor katere koli zanke - `{continueIf ?}` in `{breakIf ?}`, s katerima preskočite na naslednjo iteracijo oziroma končate zanko, če so izpolnjeni pogoji: +Za krmiljenje katere koli zanke lahko uporabljamo oznake `{continueIf ?}` in `{breakIf ?}`, ki preidejo na naslednji element oz. končajo zanko ob izpolnitvi pogoja: ```latte {foreach $rows as $row} @@ -547,8 +568,8 @@ Obstajajo posebne oznake, ki jih lahko uporabite za nadzor katere koli zanke - ` {/foreach} ``` -.{data-version:2.9} -Oznaka `{skipIf}` je zelo podobna oznaki `{continueIf}`, vendar ne povečuje števca. Tako ni lukenj v številčenju, če izpišete `$iterator->counter` in preskočite nekatere elemente. Tudi določilo {else} bo izpisano, če preskočite vse elemente. + +Oznaka `{skipIf}` je zelo podobna kot `{continueIf}`, vendar ne povečuje števca `$iterator->counter`, tako da če ga izpisujemo in hkrati preskočimo nekatere postavke, v številčenju ne bo lukenj. In tudi klavzula `{else}` se izriše, ko preskočimo vse postavke. ```latte
                                                                                                                          @@ -556,7 +577,7 @@ Oznaka `{skipIf}` je zelo podobna oznaki `{continueIf}`, vendar ne povečuje št {skipIf $person->age < 18}
                                                                                                                        • {$iterator->counter}. {$person->name}
                                                                                                                        • {else} -
                                                                                                                        • Sorry, no adult users in this list
                                                                                                                        • +
                                                                                                                        • Žal na tem seznamu ni odraslih
                                                                                                                        • {/foreach}
                                                                                                                        ``` @@ -565,72 +586,68 @@ Oznaka `{skipIf}` je zelo podobna oznaki `{continueIf}`, vendar ne povečuje št `{exitIf}` .{data-version:3.0.5} -------------------------------- -Zaključi upodabljanje predloge ali bloka, ko je izpolnjen pogoj (tj. "zgodnji izhod"). +Konča izrisovanje predloge ali bloka ob izpolnitvi pogoja (t.i. "early exit"). ```latte {exitIf !$messages} -

                                                                                                                        Messages

                                                                                                                        +

                                                                                                                        Sporočila

                                                                                                                        {$message}
                                                                                                                        ``` -Vključevanje predlog .[#toc-including-templates] -================================================ +Vstavljanje predloge +==================== `{include 'file.latte'}` .{toc: include} ---------------------------------------- .[note] -Glej tudi [`{include block}` |template-inheritance#printing-blocks] +Glejte tudi [`{include block}` |template-inheritance#Izrisovanje blokov] -Oznaka `{include}` naloži in prikaže določeno predlogo. V našem priljubljenem jeziku PHP je to tako: +Oznaka `{include}` naloži in izriše navedeno predlogo. Če bi govorili v jeziku našega priljubljenega jezika PHP, je to nekaj takega kot: ```php ``` -Vključene predloge nimajo dostopa do spremenljivk aktivnega konteksta, imajo pa dostop do globalnih spremenljivk. +Vstavljene predloge nimajo dostopa do spremenljivk aktivnega konteksta, imajo dostop samo do globalnih spremenljivk. -Spremenljivke lahko posredujete na ta način: +Spremenljivke lahko v vstavljeno predlogo posredujete na ta način: ```latte -{* od različice Latte 2.9 *} {include 'template.latte', foo: 'bar', id: 123} - -{* pred Latte 2.9 *} -{include 'template.latte', foo => 'bar', id => 123} ``` -Ime predloge je lahko kateri koli izraz PHP: +Ime predloge je lahko kateri koli izraz v PHP: ```latte {include $someVar} {include $ajax ? 'ajax.latte' : 'not-ajax.latte'} ``` -Vstavljeno vsebino je mogoče spremeniti s [filtri |syntax#filters]. Naslednji primer odstrani vse elemente HTML in prilagodi primer: +Vstavljeno vsebino lahko uredite s pomočjo [filtrov |syntax#Filtri]. Naslednji primer odstrani ves HTML in prilagodi velikost črk: ```latte {include 'heading.latte' |stripHtml|capitalize} ``` - [Dedovanje predloge |template inheritance] **pri tem privzeto ne sodeluje**. Čeprav lahko v vključene predloge dodate oznake blokov, te ne bodo nadomestile ustreznih blokov v predlogi, v katero so vključene. Vključitve si predstavljajte kot neodvisne in zaščitene dele strani ali modulov. To obnašanje lahko spremenite z uporabo modifikatorja `with blocks` (od različice Latte 2.9.1): +Privzeto [dedovanje predlog|template-inheritance] v tem primeru nikakor ne figurira. Čeprav lahko v vključeni predlogi uporabljamo bloke, ne pride do zamenjave ustreznih blokov v predlogi, v katero se vključuje. Razmišljajte o vključenih predlogah kot o samostojnih zasenčenih delih strani ali modulov. To vedenje se da spremeniti s pomočjo modifikatorja `with blocks`: ```latte {include 'template.latte' with blocks} ``` -Razmerje med imenom datoteke, navedenim v oznaki, in datoteko na disku je stvar [nalagalnika |extending-latte#Loaders]. +Razmerje med imenom datoteke, navedenim v oznaki, in datoteko na disku je stvar [nalagatelja|loaders]. -`{sandbox}` .{data-version:2.8} -------------------------------- +`{sandbox}` +----------- -Če vključite predlogo, ki jo je ustvaril končni uporabnik, morate razmisliti o tem, da jo vključite v peskovnik (več informacij v [dokumentaciji o peskovniku |sandbox]): +Pri vstavljanju predloge, ki jo je ustvaril končni uporabnik, bi morali razmisliti o načinu peskovnika (več informacij v [dokumentaciji peskovnika |sandbox]): ```latte {sandbox 'untrusted.latte', level: 3, data: $menu} @@ -641,9 +658,9 @@ Razmerje med imenom datoteke, navedenim v oznaki, in datoteko na disku je stvar ========= .[note] -Glej tudi [`{block name}` |template-inheritance#blocks] +Glejte tudi [`{block name}` |template-inheritance#Bloki] -Bloki brez imena omogočajo uporabo [filtrov |syntax#filters] za del predloge. Uporabite lahko na primer filter za [odstranjevanje |filters#strip] nepotrebnih presledkov: +Bloki brez imena služijo kot način za uporabo [filtrov |syntax#Filtri] na delu predloge. Na primer, tako lahko uporabimo filter [strip |filters#spaceless], ki odstrani nepotrebne presledke: ```latte {block|strip} @@ -654,16 +671,16 @@ Bloki brez imena omogočajo uporabo [filtrov |syntax#filters] za del predloge. U ``` -Obravnava izjem .[#toc-exception-handling] -========================================== +Upravljanje izjem +================= -`{try}` .{data-version:2.9} ---------------------------- +`{try}` +------- -S temi oznakami je izredno enostavno izdelati robustne predloge. +Zahvaljujoč tej oznaki je izjemno enostavno ustvarjati robustne predloge. -Če se med izrisovanjem bloka `{try}` pojavi izjema, se celoten blok zavrže in izrisovanje se nadaljuje po njem: +Če pri izrisovanju bloka `{try}` pride do izjeme, se celoten blok zavrže in izrisovanje se bo nadaljevalo šele po njem: ```latte {try} @@ -675,7 +692,7 @@ S temi oznakami je izredno enostavno izdelati robustne predloge. {/try} ``` -Vsebina neobveznega stavka `{else}` se izriše le, če pride do izjeme: +Vsebina v neobvezni klavzuli `{else}` se izriše samo, če nastane izjema: ```latte {try} @@ -685,11 +702,11 @@ Vsebina neobveznega stavka `{else}` se izriše le, če pride do izjeme: {/foreach} {else} -

                                                                                                                        Sorry, the tweets could not be loaded.

                                                                                                                        +

                                                                                                                        Žal nam je, ni uspelo naložiti tvitov.

                                                                                                                        {/try} ``` -Oznaka je lahko zapisana tudi kot [n:atribut |syntax#n:attributes]: +Oznako lahko uporabimo tudi kot [n:attribut |syntax#n:atributi]: ```latte
                                                                                                                          @@ -697,13 +714,13 @@ Oznaka je lahko zapisana tudi kot [n:atribut |syntax#n:attributes]:
                                                                                                                        ``` -Prav tako je mogoče definirati [lastne izvajalce izjem za |develop#exception handler] npr. beleženje: +Možno je tudi definirati lasten [obdelovalnik izjem po meri |develop#Obravnavalnik izjem], na primer zaradi beleženja. -`{rollback}` .{data-version:2.9} --------------------------------- +`{rollback}` +------------ -Blok `{try}` lahko ustavite in preskočite tudi ročno z uporabo `{rollback}`. Tako vam ni treba vnaprej preveriti vseh vhodnih podatkov, šele med izrisovanjem pa se lahko odločite, ali je smiselno objekt izrisati. +Blok `{try}` lahko ustavimo in preskočimo tudi ročno s pomočjo `{rollback}`. Zahvaljujoč temu ni treba vnaprej preverjati vseh vhodnih podatkov in se šele med izrisovanjem lahko odločite, da objekta sploh ne želite izrisati: ```latte {try} @@ -719,14 +736,14 @@ Blok `{try}` lahko ustavite in preskočite tudi ročno z uporabo `{rollback}`. T ``` -Spremenljivke .[#toc-variables] -=============================== +Spremenljivke +============= `{var}` `{default}` ------------------- -V predlogi bomo ustvarili nove spremenljivke z oznako `{var}`: +Nove spremenljivke ustvarimo v predlogi z oznako `{var}`: ```latte {var $name = 'John Smith'} @@ -736,13 +753,13 @@ V predlogi bomo ustvarili nove spremenljivke z oznako `{var}`: {var $name = 'John Smith', $age = 27} ``` -Značka `{default}` deluje podobno, le da ustvarja spremenljivke le, če ne obstajajo: +Oznaka `{default}` deluje podobno, vendar ustvarja spremenljivke samo takrat, ko ne obstajajo. Če spremenljivka že obstaja in vsebuje vrednost `null`, ne bo prepisana: ```latte -{default $lang = 'cs'} +{default $lang = 'sl'} ``` -Od različice Latte 2.7 lahko določite tudi [vrste spremenljivk |type-system]. Za zdaj so informativne in jih Latte ne preverja. +Lahko navajate tudi [tipe spremenljivk|type-system]. Zaenkrat so informativni in Latte jih ne preverja. ```latte {var string $name = $article->getTitle()} @@ -750,8 +767,8 @@ Od različice Latte 2.7 lahko določite tudi [vrste spremenljivk |type-system]. ``` -`{parameters}` .{data-version:2.9} ----------------------------------- +`{parameters}` +-------------- Tako kot funkcija deklarira svoje parametre, lahko tudi predloga na začetku deklarira svoje spremenljivke: @@ -763,15 +780,15 @@ Tako kot funkcija deklarira svoje parametre, lahko tudi predloga na začetku dek } ``` -Spremenljivki `$a` in `$b` brez privzete vrednosti imata samodejno privzeto vrednost `null`. Deklarirane vrste so še vedno informativne in jih Latte ne preverja. +Spremenljivki `$a` in `$b` brez navedene privzete vrednosti imata samodejno privzeto vrednost `null`. Deklarirani tipi so zaenkrat informativni in Latte jih ne preverja. -Druge kot deklarirane spremenljivke se ne posredujejo v predlogo. To je razlika v primerjavi z oznako `{default}`. +Druge spremenljivke kot deklarirane se v predlogo ne prenašajo. S tem se razlikuje od oznake `{default}`. `{capture}` ----------- -Z uporabo oznake `{capture}` lahko izhodne podatke zajamete v spremenljivko: +Zajame izpis v spremenljivko: ```latte {capture $var} @@ -780,10 +797,10 @@ Z uporabo oznake `{capture}` lahko izhodne podatke zajamete v spremenljivko: {/capture} -

                                                                                                                        Captured: {$var}

                                                                                                                        +

                                                                                                                        Zajeto: {$var}

                                                                                                                        ``` -Oznaka se lahko zapiše tudi kot [n:atribut |syntax#n:attributes]: +Oznako lahko, podobno kot vsako parno oznako, zapišemo tudi kot [n:attribut |syntax#n:atributi]: ```latte
                                                                                                                          @@ -791,33 +808,35 @@ Oznaka se lahko zapiše tudi kot [n:atribut |syntax#n:attributes]:
                                                                                                                        ``` +Izhod HTML se v spremenljivko `$var` shrani v obliki objekta `Latte\Runtime\Html`, da [ne pride do neželenega ubežanja |develop#Izklop samodejnega ubežanja spremenljivke] pri izpisu. -Drugo .[#toc-others] -==================== + +Ostalo +====== `{contentType}` --------------- -Z oznako določite, katero vrsto vsebine predstavlja predloga. Možnosti so: +Z oznako določite, katero vrsto vsebine predloga predstavlja. Možnosti so: -- `html` (privzeta vrsta) +- `html` (privzeti tip) - `xml` - `javascript` - `css` - `calendar` (iCal) - `text` -Njegova uporaba je pomembna, ker nastavi [kontekstno občutljivo eskapiranje |safety-first#context-aware-escaping] in le tako lahko Latte pravilno eskapira. Na primer, `{contentType xml}` preklopi v način XML, `{contentType text}` pa popolnoma izklopi escapiranje. +Njena uporaba je pomembna, ker nastavi [kontekstno občutljivo ubežanje |safety-first#Kontekstno občutljivo ubežanje] in samo tako lahko pravilno ubeži. Na primer `{contentType xml}` preklopi v način XML, `{contentType text}` ubežanje popolnoma izklopi. -Če je parameter polnopravna vrsta MIME, kot je `application/xml`, se brskalniku pošlje tudi glavička HTTP `Content-Type`: +Če je parameter polnopravni MIME type, kot na primer `application/xml`, potem še dodatno pošlje HTTP glavo `Content-Type` v brskalnik: ```latte {contentType application/xml} - RSS feed + RSS vir ... @@ -829,46 +848,50 @@ Njegova uporaba je pomembna, ker nastavi [kontekstno občutljivo eskapiranje |sa `{debugbreak}` -------------- -Določa mesto, kjer se izvajanje kode prekine. Uporablja se za namene razhroščevanja, da lahko programer preveri okolje izvajanja in zagotovi, da se koda izvaja v skladu s pričakovanji. Podpira [Xdebug |https://xdebug.org]. Poleg tega lahko določite pogoj, kdaj naj se koda prekine. +Označuje mesto, kjer pride do zaustavitve izvajanja programa in zagona razhroščevalnika, da lahko programer pregleda okolje izvajanja in ugotovi, ali program deluje po pričakovanjih. Podpira [Xdebug |https://xdebug.org/]. Lahko dopolnite pogoj, ki določa, kdaj naj se program zaustavi. ```latte -{debugbreak} {* prekinitev programa *} +{debugbreak} {* zaustavi program *} -{debugbreak $counter == 1} {* prekine program, če je pogoj izpolnjen *} +{debugbreak $counter == 1} {* zaustavi program ob izpolnitvi pogoja *} ``` `{do}` ------ -Izvede kodo in ne izpiše ničesar. +Izvede kodo PHP in ničesar ne izpisuje. Enako kot pri vseh drugih oznakah se s kodo PHP razume en izraz, glejte [omejitve PHP |syntax#Omejitve PHP v Latte]. ```latte {do $num++} ``` -V različici Latte 2.7 in prejšnjih je bil uporabljen naslov `{php}`. - `{dump}` -------- -Izbriše spremenljivko ali trenutni kontekst. +Izpiše spremenljivko ali trenutni kontekst. ```latte -{dump $name} {* odvrže spremenljivko $name *} +{dump $name} {* Izpiše spremenljivko $name *} -{dump} {* izprazni vse definirane spremenljivke *} +{dump} {* Izpiše vse trenutno definirane spremenljivke *} ``` .[caution] -Zahteva paket [Tracy |tracy:en]. +Zahteva knjižnico [Tracy|tracy:]. + + +`{php}` +------- + +Omogoča izvedbo katere koli kode PHP. Oznako je treba aktivirati s pomočjo razširitve [RawPhpExtension |develop#RawPhpExtension]. `{spaceless}` ------------- -Odstrani nepotrebne bele lise. Podoben je filtru [brez presledkov |filters#spaceless]. +Odstrani nepotrebne presledke iz izpisa. Deluje podobno kot filter [spaceless |filters#spaceless]. ```latte {spaceless} @@ -878,52 +901,52 @@ Odstrani nepotrebne bele lise. Podoben je filtru [brez presledkov |filters#space {/spaceless} ``` -Rezultati: +Generira ```latte
                                                                                                                        • Hello
                                                                                                                        ``` -Izpisi: Oznaka se lahko zapiše tudi kot [n:attribute |syntax#n:attributes]: +Oznako lahko zapišemo tudi kot [n:attribut |syntax#n:atributi]. `{syntax}` ---------- -Oznake Latte ni treba zapirati samo v enojne oglate oklepaje. Tudi med izvajanjem lahko izberete drugo ločilo. To storite s pomočjo spletne strani `{syntax…}`, kjer je lahko parameter: +Oznake Latte ni treba omejiti samo na enojne zavite oklepaje. Lahko si izberemo tudi drug ločevalnik in to celo med izvajanjem. Za to služi `{syntax …}`, kjer kot parameter lahko navedemo: -- dvojno: `{{...}}` -- off: popolnoma onemogoči oznake Latte +- double: `{{...}}` +- off: popolnoma izklopi obdelavo oznak Latte -Z uporabo zapisa n:attribute lahko onemogočimo Latte samo za blok JavaScript: +Z uporabo n:atributov lahko izklopimo Latte na primer samo za en blok JavaScripta: ```latte ``` -Latte se lahko zelo udobno uporablja znotraj JavaScripta, le izogibajte se konstrukcijam kot v tem primeru, kjer črka sledi takoj za `{`, glejte [Latte znotraj JavaScripta ali CSS |recipes#Latte inside JavaScript or CSS]. +Latte je mogoče zelo udobno uporabljati tudi znotraj JavaScripta, le izogniti se je treba konstruktom kot v tem primeru, ko sledi črka takoj za `{`, glejte [Latte znotraj JavaScripta ali CSS |recipes#Latte znotraj JavaScripta ali CSS]. -Če Latte izklopite s `{syntax off}` (tj. z oznako, ne z atributom n:), bo strogo ignoriral vse oznake do `{/syntax}`. +Če Latte izklopite s pomočjo `{syntax off}` (tj. z oznako, ne z n:atributom), bo dosledno ignoriral vse oznake do `{/syntax}` -{trace} .{data-version:2.10} ----------------------------- +{trace} +------- -Vrže izjemo `Latte\RuntimeException`, katere zasledovanje je v duhu predlog. Tako namesto klicanja funkcij in metod vključuje klicanje blokov in vstavljanje predlog. Če uporabljate orodje za jasen prikaz vrženih izjem, kot je [Tracy |tracy:en], boste jasno videli klicni niz, vključno z vsemi posredovanimi argumenti. +Sproži izjemo `Latte\RuntimeException`, katere sledenje sklada (stack trace) je v duhu predlog. Torej namesto klicev funkcij in metod vsebuje klice blokov in vstavljanje predlog. Če uporabljate orodje za pregledno prikazovanje sproženih izjem, kot je na primer [Tracy|tracy:], se vam pregledno prikaže klicni sklad (call stack) vključno z vsemi posredovanimi argumenti. -Pomočniki za oznake HTML .[#toc-html-tag-helpers] -================================================= +Pomočniki kodirnika HTML +======================== -n:class .[#toc-n-class] ------------------------ +n:class +------- -Zahvaljujoč `n:class`, je zelo enostavno ustvariti atribut HTML `class` točno tako, kot potrebujete. +Zahvaljujoč `n:class` zelo enostavno generirate atribut HTML `class` natančno po predstavah. -Primer: Potrebujem, da ima element active razred `active`: +Primer: potrebujem, da ima aktivni element razred `active`: ```latte {foreach $items as $item} @@ -931,7 +954,7 @@ Primer: Potrebujem, da ima element active razred `active`: {/foreach} ``` -Poleg tega potrebujem, da ima prvi element razreda `first` in `main`: +In nadalje, da ima prvi element razreda `first` in `main`: ```latte {foreach $items as $item} @@ -939,7 +962,7 @@ Poleg tega potrebujem, da ima prvi element razreda `first` in `main`: {/foreach} ``` -Vsi elementi pa morajo imeti razred `list-item`: +In vsi elementi naj imajo razred `list-item`: ```latte {foreach $items as $item} @@ -950,10 +973,10 @@ Vsi elementi pa morajo imeti razred `list-item`: Neverjetno preprosto, kajne? -n:attr .[#toc-n-attr] ---------------------- +n:attr +------ -Atribut `n:attr` lahko ustvari poljubne atribute HTML z enako eleganco kot [n:class |#n:class]. +Atribut `n:attr` zna z enako eleganco kot ima [#n:class] generirati poljubne atribute HTML. ```latte {foreach $data as $item} @@ -961,7 +984,7 @@ Atribut `n:attr` lahko ustvari poljubne atribute HTML z enako eleganco kot [n:cl {/foreach} ``` -Glede na vrnjene vrednosti prikaže npr: +Glede na vrnjene vrednosti izpiše npr.: ```latte @@ -972,26 +995,28 @@ Glede na vrnjene vrednosti prikaže npr: ``` -n:tag .[#toc-n-tag] -------------------- +n:tag +----- -Atribut `n:tag` lahko dinamično spremeni ime elementa HTML. +Atribut `n:tag` zna dinamično spreminjati ime elementa HTML. ```latte

                                                                                                                        {$title}

                                                                                                                        ``` -Če je `$heading === null`, je `

                                                                                                                        ` se oznaka izpiše brez sprememb. V nasprotnem primeru se ime elementa spremeni v vrednost spremenljivke, tako da se za `$heading === 'h3'` zapiše: +Če je `$heading === null`, se izpiše nespremenjena oznaka `

                                                                                                                        `. Sicer se spremeni ime elementa na vrednost spremenljivke, tako da se za `$heading === 'h3'` izpiše: ```latte

                                                                                                                        ...

                                                                                                                        ``` +Ker je Latte varen sistem predlog, preverja, ali je novo ime oznake veljavno in ne vsebuje nobenih neželenih ali škodljivih vrednosti. -n:ifcontent .[#toc-n-ifcontent] -------------------------------- -Preprečuje izpis praznega elementa HTML, tj. elementa, ki vsebuje le bele prostore. +n:ifcontent +----------- + +Preprečuje, da bi se izpisal prazen element HTML, tj. element, ki ne vsebuje ničesar razen presledkov. ```latte
                                                                                                                        @@ -999,7 +1024,7 @@ Preprečuje izpis praznega elementa HTML, tj. elementa, ki vsebuje le bele prost
                                                                                                                        ``` -Odvisno od vrednosti spremenljivke `$error` se bo to natisnilo: +Izpiše glede na vrednost spremenljivke `$error`: ```latte {* $error = '' *} @@ -1013,10 +1038,10 @@ Odvisno od vrednosti spremenljivke `$error` se bo to natisnilo: ``` -Prevod .[#toc-translation] -========================== +Prevodi +======= -Za delovanje prevajalskih oznak morate nastaviti [prevajalnik |develop#TranslatorExtension]. Uporabite lahko tudi [`translate` |filters#translate] filter za prevajanje. +Da bi oznake za prevod delovale, je treba [aktivirati prevajalnik |develop#TranslatorExtension]. Za prevod lahko uporabite tudi filter [`translate` |filters#translate]. `{_...}` @@ -1025,30 +1050,30 @@ Za delovanje prevajalskih oznak morate nastaviti [prevajalnik |develop#Translato Prevaja vrednosti v druge jezike. ```latte -{_'Basket'} +{_'Košarica'} {_$item} ``` Prevajalniku lahko posredujete tudi druge parametre: ```latte -{_'Basket', domain: order} +{_'Košarica', domain: order} ``` `{translate}` ------------- -Překládá část šablony: +Prevaja dele predloge: ```latte -

                                                                                                                        {translate}Order{/translate}

                                                                                                                        +

                                                                                                                        {translate}Naročilo{/translate}

                                                                                                                        {translate domain: order}Lorem ipsum ...{/translate} ``` -Za prevajanje notranjosti elementa se lahko oznaka zapiše tudi kot [n:attribute |syntax#n:attributes]: +Oznako lahko zapišemo tudi kot [n:attribut |syntax#n:atributi], za prevod notranjosti elementa: ```latte -

                                                                                                                        Order

                                                                                                                        +

                                                                                                                        Naročilo

                                                                                                                        ``` diff --git a/latte/sl/template-inheritance.texy b/latte/sl/template-inheritance.texy index b75a7b2e0b..1e52b00211 100644 --- a/latte/sl/template-inheritance.texy +++ b/latte/sl/template-inheritance.texy @@ -1,16 +1,16 @@ -Dedovanje predlog in ponovna uporabnost +Dedovanje in ponovna uporabnost predlog *************************************** .[perex] -Mehanizmi ponovne uporabnosti in dedovanja predlog povečujejo vašo produktivnost, saj vsaka predloga vsebuje le svojo edinstveno vsebino, ponavljajoči se elementi in strukture pa so ponovno uporabljeni. Predstavljamo tri koncepte: [dedovanje postavitve |#layout inheritance], [horizontalno ponovno uporabo |#horizontal reuse] in [dedovanje enot |#unit inheritance]. +Mehanizmi ponovne uporabe in dedovanja predlog bodo povečali vašo produktivnost, saj vsaka predloga vsebuje le svojo edinstveno vsebino, ponavljajoči se elementi in strukture pa se ponovno uporabijo. Predstavljamo tri koncepte: [#Dedovanje postavitve], [#Horizontalna ponovna uporabnost] in [#Dedovanje enot]. -Koncept dedovanja predlog Latte je podoben dedovanju razredov PHP. Določite **starševsko predlogo**, ki jo lahko druge **družinske predloge** razširijo in prekrijejo dele starševske predloge. To se odlično obnese, kadar si elementi delijo skupno strukturo. Se vam zdi zapleteno? Brez skrbi, ni. +Koncept dedovanja predlog Latte je podoben dedovanju razredov v PHP. Definirate **nadrejeno predlogo**, od katere lahko dedujejo druge **podrejene predloge** in lahko prepišejo dele nadrejene predloge. To deluje odlično, ko elementi delijo skupno strukturo. Zveni zapleteno? Ne skrbite, zelo enostavno je. -Dedovanje postavitve `{layout}` .{toc: Layout Inheritance} -========================================================== +Dedovanje postavitve `{layout}` .{toc:Dedovanje postavitve} +=========================================================== -Oglejmo si dedovanje predlog postavitve s primerom. To je nadrejena predloga, ki jo bomo na primer poimenovali `layout.latte` in opredeljuje skeletni dokument HTML. +Poglejmo si dedovanje predloge postavitve, torej layouta, kar s primerom. To je nadrejena predloga, ki jo bomo imenovali na primer `layout.latte` in ki definira okostje HTML dokumenta: ```latte @@ -30,9 +30,9 @@ Oglejmo si dedovanje predlog postavitve s primerom. To je nadrejena predloga, ki ``` -Oznake `{block}` opredeljujejo tri bloke, ki jih lahko zapolnijo podrejene predloge. Vse, kar naredi oznaka bloka, je, da pove gonilniku predloge, da lahko podrejena predloga prekrije te dele predloge z opredelitvijo lastnega bloka z istim imenom. +Značke `{block}` definirajo tri bloke, ki jih lahko podrejene predloge izpolnijo. Značka block naredi le to, da sporoči, da lahko podrejena predloga to mesto prepiše z definiranjem lastnega bloka z enakim imenom. -Otroška predloga je lahko videti takole: +Podrejena predloga lahko izgleda takole: ```latte {layout 'layout.latte'} @@ -44,11 +44,11 @@ Otroška predloga je lahko videti takole: {/block} ``` -Pri tem je ključna oznaka `{layout}`. Ta sporoča mehanizmu predlog, da ta predloga "razširja" drugo predlogo. Ko Latte upodablja to predlogo, najprej poišče nadrejeno - v tem primeru `layout.latte`. +Ključna je tukaj značka `{layout}`. Sporoča Latte, da ta predloga »razširja« drugo predlogo. Ko Latte izrisuje to predlogo, najprej najde nadrejeno predlogo - v tem primeru `layout.latte`. -Takrat bo mehanizem predloge opazil tri blokovne oznake v `layout.latte` in te bloke nadomestil z vsebino podrejene predloge. Upoštevajte, da ker otroška predloga ni opredelila bloka *footer*, se namesto tega uporabi vsebina iz starševske predloge. Vsebina znotraj oznake `{block}` v nadrejeni predlogi se vedno uporabi kot nadomestna možnost. +V tem trenutku si Latte opazi tri blokovne značke v `layout.latte` in te bloke nadomesti z vsebino podrejene predloge. Ker podrejena predloga ni definirala bloka *footer*, se namesto tega uporabi vsebina iz nadrejene predloge. Vsebina v znački `{block}` v nadrejeni predlogi se vedno uporablja kot rezervna. -Rezultat je lahko videti takole: +Izpis lahko izgleda takole: ```latte @@ -68,7 +68,7 @@ Rezultat je lahko videti takole: ``` -V predlogi otroka se bloki lahko nahajajo samo na zgornji ravni ali znotraj drugega bloka, tj: +V podrejeni predlogi so lahko bloki postavljeni le na najvišji ravni ali znotraj drugega bloka, tj.: ```latte {block content} @@ -76,7 +76,7 @@ V predlogi otroka se bloki lahko nahajajo samo na zgornji ravni ali znotraj drug {/block} ``` -Prav tako bo blok vedno ustvarjen ne glede na to, ali je okoliški pogoj `{if}` ovrednoten kot true ali false. V nasprotju s tem, kar morda mislite, ta predloga opredeljuje blok. +Prav tako bo blok vedno ustvarjen, ne glede na to, ali je okoliška `{if}` pogoj ocenjen kot resničen ali neresničen. Torej, čeprav ne izgleda tako, ta predloga blok definira. ```latte {if false} @@ -86,7 +86,7 @@ Prav tako bo blok vedno ustvarjen ne glede na to, ali je okoliški pogoj `{if}` {/if} ``` -Če želite, da se izpis znotraj bloka prikaže pogojno, namesto tega uporabite naslednje: +Če želite, da se izpis znotraj bloka prikazuje pogojno, uporabite namesto tega naslednje: ```latte {block head} @@ -96,7 +96,7 @@ Prav tako bo blok vedno ustvarjen ne glede na to, ali je okoliški pogoj `{if}` {/block} ``` -Podatki zunaj blokov v podrejeni predlogi se izvedejo pred prikazom predloge za postavitev, zato jo lahko uporabite za opredelitev spremenljivk, kot je `{var $foo = bar}`, in širjenje podatkov v celotno dedno verigo: +Prostor izven blokov v podrejeni predlogi se izvaja pred izrisovanjem predloge layouta, zato ga lahko uporabite za definiranje spremenljivk kot `{var $foo = bar}` in za širjenje podatkov po celotni verigi dedovanja: ```latte {layout 'layout.latte'} @@ -106,51 +106,50 @@ Podatki zunaj blokov v podrejeni predlogi se izvedejo pred prikazom predloge za ``` -Večnivojsko dedovanje .[#toc-multilevel-inheritance] ----------------------------------------------------- -Uporabite lahko toliko ravni dedovanja, kolikor jih potrebujete. Eden od pogostih načinov uporabe dedovanja postavitve je naslednji tristopenjski pristop: +Večnivojsko dedovanje +--------------------- +Uporabite lahko toliko ravni dedovanja, kolikor jih potrebujete. Pogost način uporabe dedovanja postavitve je naslednji tristopenjski pristop: -1) Ustvarite predlogo `layout.latte`, ki vsebuje glavni videz vašega spletnega mesta. -2) Ustvarite predlogo `layout-SECTIONNAME.latte` za vsak razdelek svojega spletnega mesta. Na primer `layout-news.latte`, `layout-blog.latte` itd. Vse te predloge razširjajo spletno stran `layout.latte` in vključujejo sloge/oblikovanje, značilne za posamezno poglavje. -3) Ustvarite posamezne predloge za vsako vrsto strani, na primer za novičarski članek ali zapis na blogu. Te predloge razširjajo ustrezno predlogo za razdelek. +1) Ustvarite predlogo `layout.latte`, ki vsebuje glavno okostje videza spletnega mesta. +2) Ustvarite predlogo `layout-SECTIONNAME.latte` za vsak odsek vašega spletnega mesta. Na primer `layout-news.latte`, `layout-blog.latte` itd. Vse te predloge razširjajo `layout.latte` in vključujejo sloge & dizajn, specifične za posamezne odseke. +3) Ustvarite posamezne predloge za vsak tip strani, na primer novičarski članek ali vnos v blog. Te predloge razširjajo ustrezno predlogo odseka. -Dinamično dedovanje postavitve .[#toc-dynamic-layout-inheritance] ------------------------------------------------------------------ +Dinamično dedovanje +------------------- Kot ime nadrejene predloge lahko uporabite spremenljivko ali kateri koli izraz PHP, zato se lahko dedovanje obnaša dinamično: ```latte {layout $standalone ? 'minimum.latte' : 'layout.latte'} ``` -Uporabite lahko tudi API Latte za [samodejno |develop#automatic-layout-lookup] izbiro predloge za postavitev. +Lahko uporabite tudi Latte API za [samodejno |develop#Samodejno iskanje postavitve] izbiro predloge layouta. -Nasveti .[#toc-tips] --------------------- +Nasveti +------- Tukaj je nekaj nasvetov za delo z dedovanjem postavitve: -- Če v predlogi uporabite `{layout}`, mora biti to prva oznaka v predlogi. +- Če v predlogi uporabite `{layout}`, mora biti to prva značka predloge v tej predlogi. -- Postavitev lahko [iščete samodejno |develop#automatic-layout-lookup] (kot v [predstavitvah |application:templates#search-for-templates]). Če v tem primeru predloga ne sme imeti postavitve, se to označi z oznako `{layout none}`. +- Layout se lahko [samodejno poišče |develop#Samodejno iskanje postavitve] (kot na primer v [presenterjih |application:templates#Iskanje predlog]). V tem primeru, če predloga ne sme imeti layouta, to sporoči z značko `{layout none}`. -- Oznaka `{layout}` ima vzdevek `{extends}`. +- Značka `{layout}` ima alias `{extends}`. -- Ime datoteke razširjene predloge je odvisno od programa [za nalaganje predlog |extending-latte#Loaders]. +- Ime datoteke layouta je odvisno od [loaderja |loaders]. -- Na voljo je lahko poljubno število blokov. Ne pozabite, da podrejenim predlogam ni treba opredeliti vseh nadrejenih blokov, zato lahko v več blokov vnesete razumne privzete nastavitve, pozneje pa opredelite le tiste, ki jih potrebujete. +- Lahko imate toliko blokov, kolikor želite. Ne pozabite, da podrejene predloge ne rabijo definirati vseh nadrejenih blokov, zato lahko izpolnite primerne privzete vrednosti v več blokih in nato definirate le tiste, ki jih potrebujete kasneje. -Bloki `{block}` .{toc: Blocks} -============================== +Bloki `{block}` .{toc: Bloki} +============================= .[note] -Glej tudi anonimni [`{block}` |tags#block] +Glejte tudi anonimni [`{block}` |tags#block] -Blok omogoča spreminjanje načina upodabljanja določenega dela predloge, vendar na noben način ne posega v okoliško logiko. Z naslednjim primerom ponazorimo, kako blok deluje in, kar je še pomembneje, kako ne deluje: +Blok predstavlja način, kako spremeniti način izrisovanja določenega dela predloge, vendar nikakor ne posega v logiko okoli njega. V naslednjem primeru si bomo pokazali, kako blok deluje, pa tudi kako ne deluje: -```latte -{* parent.Latte *} +```latte .{file: parent.latte} {foreach $posts as $post} {block post}

                                                                                                                        {$post->title}

                                                                                                                        @@ -159,10 +158,9 @@ Blok omogoča spreminjanje načina upodabljanja določenega dela predloge, venda {/foreach} ``` -Če to predlogo prikažete, bo rezultat popolnoma enak z oznakami bloka ali brez njih. Bloki imajo dostop do spremenljivk iz zunanjih obsegov. To je le način, da ga lahko podrejena predloga prekrije: +Če to predlogo izrišete, bo rezultat popolnoma enak z značkami `{block}` ali brez njih. Bloki imajo dostop do spremenljivk iz zunanjih obsegov. Le dajejo možnost, da jih prepiše podrejena predloga: -```latte -{* child.Latte *} +```latte .{file: child.latte} {layout 'parent.Latte'} {block post} @@ -173,7 +171,7 @@ Blok omogoča spreminjanje načina upodabljanja določenega dela predloge, venda {/block} ``` -Sedaj bo zanka pri upodabljanju podrejene predloge uporabila blok, opredeljen v podrejeni predlogi `child.Latte`, namesto bloka, opredeljenega v osnovni predlogi `parent.Latte`; izvedena predloga je nato enakovredna naslednji: +Zdaj bo pri izrisovanju podrejene predloge zanka uporabljala blok, definiran v podrejeni predlogi `child.Latte`, namesto bloka, definiranega v `parent.Latte`; zagnana predloga je potem ekvivalentna naslednji: ```latte {foreach $posts as $post} @@ -184,7 +182,7 @@ Sedaj bo zanka pri upodabljanju podrejene predloge uporabila blok, opredeljen v {/foreach} ``` -Če pa znotraj poimenovanega bloka ustvarimo novo spremenljivko ali zamenjamo vrednost obstoječe, bo sprememba vidna samo znotraj bloka: +Če pa ustvarimo novo spremenljivko znotraj poimenovanega bloka ali nadomestimo vrednost obstoječe, bo sprememba vidna le znotraj bloka: ```latte {var $foo = 'foo'} @@ -193,17 +191,17 @@ Sedaj bo zanka pri upodabljanju podrejene predloge uporabila blok, opredeljen v {var $bar = 'bar'} {/block} -foo: {$foo} // prints: foo -bar: {$bar ?? 'not defined'} // prints: not defined +foo: {$foo} // izpiše: foo +bar: {$bar ?? 'not defined'} // izpiše: ni definirano ``` -Vsebino bloka lahko spremenimo s [filtri |syntax#filters]. Naslednji primer odstrani ves HTML in ga naslovi: +Vsebino bloka lahko uredite s pomočjo [filtrov |syntax#Filtri]. Naslednji primer odstrani ves HTML in spremeni velikost črk: ```latte {block title|stripHtml|capitalize}...{/block} ``` -Oznaka je lahko zapisana tudi kot [n:attribute |syntax#n:attributes]: +Značko lahko zapišete tudi kot [n:atribut |syntax#n:atributi]: ```latte
                                                                                                                        @@ -212,10 +210,10 @@ Oznaka je lahko zapisana tudi kot [n:attribute |syntax#n:attributes]: ``` -Lokalni bloki .[#toc-local-blocks] ----------------------------------- +Lokalni bloki +------------- -Vsak blok prekrije vsebino nadrejenega bloka z istim imenom. Razen lokalnih blokov. Ti so nekaj podobnega kot zasebne metode v razredu. Ustvarite lahko predlogo, ne da bi vas skrbelo, da bi jih - zaradi naključja imen blokov - druga predloga prepisala. +Vsak blok prepiše vsebino nadrejenega bloka z enakim imenom – razen lokalnih blokov. V razredih bi šlo za nekaj podobnega kot zasebne metode. Predlogo lahko tako ustvarjate brez skrbi, da bi bili zaradi ujemanja imen blokov prepisani iz druge predloge. ```latte {block local helper} @@ -224,13 +222,13 @@ Vsak blok prekrije vsebino nadrejenega bloka z istim imenom. Razen lokalnih blok ``` -Tiskanje blokov `{include}` .{toc: Printing Blocks} ---------------------------------------------------- +Izrisovanje blokov `{include}` .{toc: Izrisovanje blokov} +--------------------------------------------------------- .[note] -Glej tudi [`{include file}` |tags#include] +Glejte tudi [`{include file}` |tags#include] -Če želite blok natisniti na določenem mestu, uporabite oznako `{include blockname}`: +Če želite blok izpisati na določenem mestu, uporabite značko `{include blockname}`: ```latte {block title}{/block} @@ -238,32 +236,28 @@ Glej tudi [`{include file}` |tags#include]

                                                                                                                        {include title}

                                                                                                                        ``` -Prikažete lahko tudi blok iz druge predloge: +Lahko izpišete tudi blok iz druge predloge: ```latte {include footer from 'main.latte'} ``` -Izpisani blok nima dostopa do spremenljivk aktivnega konteksta, razen če je blok definiran v isti datoteki, v katero je vključen. Imajo pa dostop do globalnih spremenljivk. +Izrisovani blok nima dostopa do spremenljivk aktivnega konteksta, razen če je blok definiran v isti datoteki, kjer je tudi vstavljen. Ima pa dostop do globalnih spremenljivk. -Spremenljivke lahko posredujete na ta način: +Spremenljivke lahko v blok posredujete na ta način: ```latte -{* od različice Latte 2.9 *} {include footer, foo: bar, id: 123} - -{* pred Latte 2.9 *} -{include footer, foo => bar, id => 123} ``` -Kot ime bloka lahko uporabite spremenljivko ali kateri koli izraz v PHP. V tem primeru pred spremenljivko dodajte ključno besedo `block`, da se ob sestavljanju ve, da gre za blok, in ne za [vstavljanje predloge, |tags#include] katere ime je lahko tudi v spremenljivki: +Kot ime bloka lahko uporabite spremenljivko ali kateri koli izraz v PHP. V tem primeru pred spremenljivko še dopolnimo ključno besedo `block`, da bo že v času prevajanja Latte vedelo, da gre za blok, in ne za [vstavljanje predloge |tags#include], katere ime bi prav tako lahko bilo v spremenljivki: ```latte {var $name = footer} {include block $name} ``` -Blok se lahko izpiše tudi znotraj samega sebe, kar je uporabno na primer pri upodabljanju drevesne strukture: +Blok lahko izrišete tudi znotraj sebe, kar je na primer koristno pri izrisovanju drevesne strukture: ```latte {define menu, $items} @@ -281,19 +275,19 @@ Blok se lahko izpiše tudi znotraj samega sebe, kar je uporabno na primer pri up {/define} ``` -Namesto `{include menu, ...}` lahko zapišemo tudi `{include this, ...}`, kjer `this` pomeni trenutni blok. +Namesto `{include menu, ...}` lahko potem napišemo `{include this, ...}`, kjer `this` pomeni trenutni blok. -Natisnjeno vsebino lahko spreminjamo s [filtri |syntax#filters]. Naslednji primer odstrani ves HTML in ga označi z naslovi: +Izrisovani blok lahko uredite s pomočjo [filtrov |syntax#Filtri]. Naslednji primer odstrani ves HTML in spremeni velikost črk: ```latte {include heading|stripHtml|capitalize} ``` -Nadrejeni blok .[#toc-parent-block] ------------------------------------ +Starševski blok +--------------- -Če morate vsebino bloka izpisati iz nadrejene predloge, bo to storila izjava `{include parent}`. To je uporabno, če želite dodati vsebino nadrejenega bloka, namesto da bi ga v celoti prepisali. +Če potrebujete izpisati vsebino bloka iz nadrejene predloge, uporabite `{include parent}`. To je koristno, če želite le dopolniti vsebino nadrejenega bloka namesto njegovega popolnega prepisa. ```latte {block footer} @@ -304,32 +298,31 @@ Nadrejeni blok .[#toc-parent-block] ``` -Definicije `{define}` .{toc: Definitions} ------------------------------------------ +Definicije `{define}` .{toc: Definicije} +---------------------------------------- -Poleg blokov so v programu Latte na voljo tudi "definicije". Te so primerljive s funkcijami v običajnih programskih jezikih. Uporabne so za ponovno uporabo fragmentov predlog, da se ne ponavljamo. +Poleg blokov obstajajo v Latte tudi »definicije«. V običajnih programskih jezikih bi jih primerjali s funkcijami. Koristne so za ponovno uporabo fragmentov predloge, da se ne ponavljate. -Latte poskuša delati stvari enostavno, zato so definicije v bistvu enake blokom in **vse, kar je povedano o blokih, velja tudi za definicije**. Od blokov se razlikuje le na tri načine: +Latte si prizadeva narediti stvari enostavne, zato so v osnovi definicije enake kot bloki in **vse, kar je povedano o blokih, velja tudi za definicije**. Od blokov se razlikujejo po tem, da: -1) sprejemajo lahko argumente -2) ne morejo imeti [filtrov |syntax#filters] -3) zaprte so v oznake `{define}` in vsebina znotraj teh oznak se ne pošlje na izhod, dokler jih ne vključite. Zaradi tega jih lahko ustvarite kjer koli: +1) so zaprte v značkah `{define}` +2) se izrišejo šele, ko jih vstavite preko `{include}` +3) jim lahko definirate parametre podobno kot funkcijam v PHP ```latte {block foo}

                                                                                                                        Hello

                                                                                                                        {/block} -{* prints:

                                                                                                                        Hello

                                                                                                                        *} +{* izpiše:

                                                                                                                        Hello

                                                                                                                        *} {define bar}

                                                                                                                        World

                                                                                                                        {/define} -{* ne natisne ničesar *} +{* ne izpiše ničesar *} {include bar} -{* prints:

                                                                                                                        World

                                                                                                                        *} +{* izpiše:

                                                                                                                        World

                                                                                                                        *} ``` -Predstavljajte si, da imate generično pomožno predlogo, ki določa, kako z definicijami prikazati obrazce HTML: +Predstavljajte si, da imate pomožno predlogo z zbirko definicij, kako risati HTML obrazce. -```latte -{* forms.latte *} +```latte .{file: forms.latte} {define input, $name, $value, $type = 'text'} {/define} @@ -339,38 +332,36 @@ Predstavljajte si, da imate generično pomožno predlogo, ki določa, kako z def {/define} ``` -Argumenti definicij so vedno neobvezni s privzeto vrednostjo `null`, razen če je določena privzeta vrednost (tukaj `text` je privzeta vrednost za `$type`, ki je mogoča od različice Latte 2.9.1). Od različice Latte 2.7 je mogoče deklarirati tudi vrste parametrov: `{define input, string $name, ...}`. - -Definicije nimajo dostopa do spremenljivk aktivnega konteksta, imajo pa dostop do globalnih spremenljivk. +Argumenti so vedno izbirni s privzeto vrednostjo `null`, če ni navedena privzeta vrednost (tukaj je `'text'` privzeta vrednost za `$type`). Lahko deklarirate tudi tipe parametrov: `{define input, string $name, ...}`. -Vključene so na [enak način kot blok |#Printing Blocks]: +Predlogo z definicijami naložimo s pomočjo [`{import}` |#Horizontalna ponovna uporabnost]. Same definicije se izrisujejo [na enak način kot bloki |#Izrisovanje blokov]: ```latte

                                                                                                                        {include input, 'password', null, 'password'}

                                                                                                                        {include textarea, 'comment'}

                                                                                                                        ``` +Definicije nimajo dostopa do spremenljivk aktivnega konteksta, imajo pa dostop do globalnih spremenljivk. + -Dinamična imena blokov .[#toc-dynamic-block-names] --------------------------------------------------- +Dinamična imena blokov +---------------------- -Latte omogoča veliko prilagodljivost pri opredeljevanju blokov, saj je lahko ime bloka poljuben izraz PHP. Ta primer opredeljuje tri bloke z imeni `hi-Peter`, `hi-John` in `hi-Mary`: +Latte dovoljuje veliko fleksibilnost pri definiranju blokov, saj je lahko ime bloka kateri koli izraz PHP. Ta primer definira tri bloke z imeni `hi-Peter`, `hi-John` in `hi-Mary`: -```latte -{* parent.latte *} +```latte .{file: parent.latte} {foreach [Peter, John, Mary] as $name} {block "hi-$name"}Hi, I am {$name}.{/block} {/foreach} ``` -Na primer, v podrejeni predlogi lahko na novo opredelimo samo en blok: +V podrejeni predlogi lahko potem ponovno definiramo na primer le en blok: -```latte -{* child.latte *} +```latte .{file: child.latte} {block hi-John}Hello. I am {$name}.{/block} ``` -Tako bo rezultat videti takole: +Torej bo izpis izgledal takole: ```latte Hi, I am Peter. @@ -379,13 +370,13 @@ Hi, I am Mary. ``` -Preverjanje obstoja blokov `{ifset}` .{toc: Checking Block Existence} ---------------------------------------------------------------------- +Preverjanje obstoja blokov `{ifset}` .{toc: Preverjanje obstoja blokov} +----------------------------------------------------------------------- .[note] -Glej tudi [`{ifset $var}` |tags#ifset-elseifset] +Glejte tudi [`{ifset $var}` |tags#ifset elseifset] -S testom `{ifset blockname}` preverite, ali v trenutnem kontekstu obstaja blok (ali več blokov): +S pomočjo testa `{ifset blockname}` preverimo, ali v trenutnem kontekstu blok (ali več blokov) obstaja: ```latte {ifset footer} @@ -397,7 +388,7 @@ S testom `{ifset blockname}` preverite, ali v trenutnem kontekstu obstaja blok ( {/ifset} ``` -Kot ime bloka lahko uporabite spremenljivko ali kateri koli izraz v jeziku PHP. V tem primeru pred spremenljivko dodajte ključno besedo `block`, da bo jasno, da se ne preverja [spremenljivka |tags#ifset-elseifset]: +Kot ime bloka lahko uporabite spremenljivko ali kateri koli izraz v PHP. V tem primeru pred spremenljivko še dopolnimo ključno besedo `block`, da bo jasno, da ne gre za test obstoja [spremenljivk |tags#ifset elseifset]: ```latte {ifset block $name} @@ -405,71 +396,69 @@ Kot ime bloka lahko uporabite spremenljivko ali kateri koli izraz v jeziku PHP. {/ifset} ``` +Obstoj blokov preverja tudi funkcija [`hasBlock()` |functions#hasBlock]: -Nasveti .[#toc-tips] --------------------- -Tukaj je nekaj nasvetov za delo z bloki: - -- Zadnjemu bloku najvišje ravni ni treba imeti zaključne oznake (blok se konča s koncem dokumenta). To poenostavi pisanje podrejenih predlog, ki imajo en primarni blok. +```latte +{if hasBlock(header) || hasBlock(footer)} + ... +{/if} +``` -- Za večjo berljivost lahko po želji dodate ime oznaki `{/block}`, na primer `{/block footer}`. Vendar se mora ime ujemati z imenom bloka. V večjih predlogah vam ta tehnika pomaga videti, katere blokovne oznake so zaprte. -- V isti predlogi ne morete neposredno opredeliti več blokovnih oznak z istim imenom. To pa lahko dosežete z uporabo [dinamičnih imen blokov |#dynamic block names]. +Nasveti +------- +Nekaj nasvetov za delo z bloki: -- Z uporabo [n:atributov |syntax#n:attributes] lahko opredelite bloke, kot so `

                                                                                                                        Welcome to my awesome homepage

                                                                                                                        ` +- Zadnji blok najvišje ravni ne rabi imeti zaključne značke (blok se konča s koncem dokumenta). To poenostavlja pisanje podrejenih predlog, ki vsebujejo en primarni blok. -- Bloki se lahko uporabljajo tudi brez imen samo za uporabo [filtrov |syntax#filters] na izhodu: `{block|strip} hello {/block}` +- Za boljšo berljivost lahko ime bloka navedete v znački `{/block}`, na primer `{/block footer}`. Vendar se mora ime ujemati z imenom bloka. V večjih predlogah vam ta tehnika pomaga ugotoviti, katere značke bloka se zapirajo. +- V isti predlogi ne morete neposredno definirati več značk blokov z enakim imenom. To pa lahko dosežete s pomočjo [dinamičnih imen blokov |#Dinamična imena blokov]. -Vodoravna ponovna uporaba `{import}` .{toc: Horizontal Reuse} -============================================================= +- Lahko uporabite [n:atribute |syntax#n:atributi] za definiranje blokov kot `

                                                                                                                        Welcome to my awesome homepage

                                                                                                                        ` -Horizontalna ponovna uporaba je tretji mehanizem ponovne uporabe in dedovanja v Latte. Omogoča nalaganje blokov iz drugih predlog. To je podobno ustvarjanju datoteke PHP s pomožnimi funkcijami ali lastnostmi. +- Bloke lahko uporabite tudi brez imen samo za uporabo [filtrov |syntax#Filtri]: `{block|strip} hello {/block}` -Čeprav je dedovanje postavitvene predloge ena najmočnejših funkcij sistema Latte, je omejena na enojno dedovanje; predloga lahko razširi le eno drugo predlogo. Zaradi te omejitve je dedovanje predlog preprosto za razumevanje in enostavno za odpravljanje napak: -```latte -{layout 'layout.latte'} +Horizontalna ponovna uporabnost `{import}` .{toc: Horizontalna ponovna uporabnost} +================================================================================== -{block title}...{/block} -{block content}...{/block} -``` +Horizontalna ponovna uporabnost je v Latte tretji mehanizem za ponovno uporabo in dedovanje. Omogoča nalaganje blokov iz drugih predlog. Podobno je, kot če si v PHP ustvarimo datoteko s pomožnimi funkcijami, ki jo nato naložimo s pomočjo `require`. -Horizontalna ponovna uporaba je način za doseganje istega cilja kot večkratno dedovanje, vendar brez s tem povezane zapletenosti: +Čeprav je dedovanje postavitve predloge ena najmočnejših funkcij Latte, je omejena na enostavno dedovanje - predloga lahko razširi le eno drugo predlogo. Horizontalna ponovna uporabnost je način, kako doseči večkratno dedovanje. -```latte -{layout 'layout.latte'} +Imejmo datoteko z definicijami blokov: -{import 'blocks.latte'} +```latte .{file: blocks.latte} +{block sidebar}...{/block} -{block title}...{/block} -{block content}...{/block} +{block menu}...{/block} ``` -Izjava `{import}` pove, da Latte uvozi vse bloke in [definicije |#definitions], opredeljene v `blocks.latte`, v trenutno predlogo. +S pomočjo ukaza `{import}` uvozimo vse bloke in [#Definicije] definirane v `blocks.latte` v drugo predlogo: -```latte -{* blocks.latte *} +```latte .{file: child.latte} +{import 'blocks.latte'} -{block sidebar}...{/block} +{* zdaj je mogoče uporabiti bloka sidebar in menu *} ``` -V tem primeru izjava `{import}` uvozi blok `sidebar` v glavno predlogo. +Če bloke uvozite v nadrejeni predlogi (tj. uporabite `{import}` v `layout.latte`), bodo bloki na voljo tudi v vseh podrejenih predlogah, kar je zelo praktično. -Uvožena predloga ne sme [razširiti |#Layout Inheritance] druge predloge, njeno telo pa mora biti prazno. Uvožena predloga pa lahko uvozi druge predloge. +Predloga, ki je namenjena uvozu (npr. `blocks.latte`), ne sme [razširjati |#Dedovanje postavitve] druge predloge, tj. uporabljati `{layout}`. Lahko pa uvaža druge predloge. -Oznaka `{import}` mora biti prva oznaka predloge za `{layout}`. Ime predloge je lahko kateri koli izraz PHP: +Značka `{import}` bi morala biti prva značka predloge po `{layout}`. Ime predloge je lahko kateri koli izraz PHP: ```latte {import $ajax ? 'ajax.latte' : 'not-ajax.latte'} ``` -V posamezni predlogi lahko uporabite poljubno število izrazov `{import}`. Če dve uvoženi predlogi definirata isti blok, zmaga prva. Vendar ima največjo prednost glavna predloga, ki lahko prepiše kateri koli uvoženi blok. +V predlogi lahko uporabite toliko `{import}` ukazov, kolikor želite. Če dve uvoženi predlogi definirata isti blok, zmaga prva. Najvišjo prioriteto pa ima glavna predloga, ki lahko prepiše kateri koli uvožen blok. -Vse prepisane bloke lahko postopoma vključite tako, da jih vstavite kot [nadrejeni blok |#parent block]: +Vsebino prepisanih blokov lahko ohranite tako, da blok vstavimo enako, kot se vstavlja [#Starševski blok]: ```latte -{layout 'base.latte'} +{layout 'layout.latte'} {import 'blocks.latte'} @@ -481,17 +470,17 @@ Vse prepisane bloke lahko postopoma vključite tako, da jih vstavite kot [nadrej {block content}...{/block} ``` -V tem primeru bo `{include parent}` pravilno poklical blok `sidebar` iz predloge `blocks.latte`. +V tem primeru `{include parent}` pokliče blok `sidebar` iz predloge `blocks.latte`. -Dedovanje enot `{embed}` .{toc: Unit Inheritance}{data-version:2.9} -=================================================================== +Dedovanje enot `{embed}` .{toc: Dedovanje enot} +=============================================== -Dedovanje enot prenaša idejo dedovanja postavitve na raven vsebinskih fragmentov. Medtem ko dedovanje postavitve deluje s "skeleti dokumentov", ki jih oživijo predloge otrok, dedovanje enot omogoča, da ustvarite skelete za manjše enote vsebine in jih ponovno uporabite kjerkoli želite. +Dedovanje enot razširja idejo dedovanja postavitve na raven fragmentov vsebine. Medtem ko dedovanje postavitve dela z »okostjem dokumenta«, ki ga oživijo podrejene predloge, vam dedovanje enot omogoča ustvarjanje okostij za manjše enote vsebine in jih ponovno uporabljate kjerkoli želite. -Pri dedovanju enot je ključna oznaka `{embed}`. Združuje obnašanje oznak `{include}` in `{layout}`. Z njo lahko vključite vsebino druge predloge ali bloka in po želji posredujete spremenljivke, tako kot to počne `{include}`. Omogoča tudi prekrivanje katerega koli bloka, opredeljenega znotraj vključene predloge, kot to počne `{layout}`. +V dedovanju enot je ključna značka `{embed}`. Kombinira obnašanje `{include}` in `{layout}`. Omogoča vstavljanje vsebine druge predloge ali bloka in izbirno posredovanje spremenljivk, enako kot v primeru `{include}`. Omogoča tudi prepis katerega koli bloka, definiranega znotraj vstavljene predloge, kot pri uporabi `{layout}`. -Za primer bomo uporabili element zložljive harmonike. Oglejmo si ogrodje elementa v predlogi `collapsible.latte`: +Na primer, uporabimo element harmonika. Poglejmo si okostje elementa, shranjeno v predlogi `collapsible.latte`: ```latte
                                                                                                                        @@ -505,9 +494,9 @@ Za primer bomo uporabili element zložljive harmonike. Oglejmo si ogrodje elemen
                                                                                                                        ``` -Oznake `{block}` določajo dva bloka, ki ju lahko izpolnijo podrejene predloge. Da, tako kot v primeru starševske predloge v predlogi za dedovanje postavitve. Vidite tudi spremenljivko `$modifierClass`. +Značke `{block}` definirajo dva bloka, ki jih lahko podrejene predloge izpolnijo. Da, kot v primeru nadrejene predloge pri dedovanju postavitve. Vidite tudi spremenljivko `$modifierClass`. -Uporabimo naš element v predlogi. Tu pride na vrsto `{embed}`. To je izjemno zmogljiv pripomoček, ki nam omogoča vse: vključiti vsebino predloge elementa, mu dodati spremenljivke in dodati bloke s prilagojenim HTML-jem: +Uporabimo naš element v predlogi. Tukaj pride v poštev `{embed}`. Gre za izjemno zmogljivo značko, ki nam omogoča narediti vse stvari: vstaviti vsebino predloge elementa, dodati vanjo spremenljivke in dodati vanjo bloke z lastnim HTML: ```latte {embed 'collapsible.latte', modifierClass: my-style} @@ -522,7 +511,7 @@ Uporabimo naš element v predlogi. Tu pride na vrsto `{embed}`. To je izjemno zm {/embed} ``` -Rezultat je lahko videti takole: +Izpis lahko izgleda takole: ```latte
                                                                                                                        @@ -537,7 +526,7 @@ Rezultat je lahko videti takole:
                                                                                                                        ``` -Bloki znotraj oznak embed tvorijo ločeno plast, neodvisno od drugih blokov. Zato imajo lahko enako ime kot blok zunaj vgradne oznake in nanje to nima nobenega vpliva. Z uporabo oznake [include |#Printing Blocks] znotraj oznak `{embed}` lahko vstavite tukaj ustvarjene bloke, bloke iz vstavljene predloge (ki *ne so* [lokalni |#Local Blocks]) in tudi bloke iz glavne predloge, ki *so* lokalni. Prav tako lahko [uvozite bloke |#Horizontal Reuse] iz drugih datotek: +Bloki znotraj vstavljenih značk tvorijo samostojno plast, neodvisno od drugih blokov. Zato lahko imajo enako ime kot blok izven vstavljanja in niso nikakor vplivani. S pomočjo značke [include |#Izrisovanje blokov] znotraj značk `{embed}` lahko vstavite bloke, tukaj ustvarjene, bloke iz vstavljene predloge (ki *niso* [lokalni |#Lokalni bloki]) in tudi bloke iz glavne predloge, ki pa nasprotno *so* lokalni. Lahko tudi [uvažate bloke |#Horizontalna ponovna uporabnost] iz drugih datotek: ```latte {block outer}…{/block} @@ -550,17 +539,17 @@ Bloki znotraj oznak embed tvorijo ločeno plast, neodvisno od drugih blokov. Zat {block title} {include inner} {* deluje, blok je definiran znotraj embed *} - {include hello} {* deluje, blok je lokalni v tej predlogi *} - {include content} {* deluje, blok je opredeljen v vdelani predlogi *} + {include hello} {* deluje, blok je lokalen v tej predlogi *} + {include content} {* deluje, blok je definiran v vstavljeni predlogi *} {include aBlockDefinedInImportedTemplate} {* deluje *} {include outer} {* ne deluje! - blok je v zunanji plasti *} {/block} {/embed} ``` -Vgrajene predloge nimajo dostopa do spremenljivk aktivnega konteksta, imajo pa dostop do globalnih spremenljivk. +Vstavljene predloge nimajo dostopa do spremenljivk aktivnega konteksta, imajo pa dostop do globalnih spremenljivk. -S spletno stranjo `{embed}` lahko poleg predlog vstavite tudi druge bloke, zato bi lahko prejšnji primer zapisali takole: .{data-version:2.10} +S pomočjo `{embed}` lahko vstavljate ne le predloge, ampak tudi druge bloke, in torej bi se prejšnji primer lahko zapisal na ta način: ```latte {define collapsible} @@ -581,23 +570,23 @@ S spletno stranjo `{embed}` lahko poleg predlog vstavite tudi druge bloke, zato {/embed} ``` -Če posredujemo izraz `{embed}` in ni jasno, ali gre za ime bloka ali datoteke, dodamo ključno besedo `block` ali `file`: +Če v `{embed}` posredujemo izraz in ni očitno, ali gre za ime bloka ali datoteke, dopolnimo ključno besedo `block` ali `file`: ```latte {embed block $name} ... {/embed} ``` -Primeri uporabe .[#toc-use-cases] -================================= +Primeri uporabe +=============== -V sistemu Latte obstajajo različne vrste dedovanja in ponovne uporabe kode. Za večjo jasnost povzemimo glavne koncepte: +V Latte obstajajo različni tipi dedovanja in ponovne uporabe kode. Povzemimo glavne koncepte za večjo razumljivost: `{include template}` -------------------- -**Primer uporabe:** Uporaba `header.latte` in `footer.latte` znotraj `layout.latte`. +**Primer uporabe**: Uporaba `header.latte` in `footer.latte` znotraj `layout.latte`. `header.latte` @@ -698,7 +687,7 @@ V sistemu Latte obstajajo različne vrste dedovanja in ponovne uporabe kode. Za `{define}` ---------- -**Primer uporabe**: Funkcija, ki dobi nekaj spremenljivk in izpiše nekaj oznak. +**Primer uporabe**: Funkcije, ki jim posredujemo spremenljivke in nekaj izrišejo. `form.latte` diff --git a/latte/sl/type-system.texy b/latte/sl/type-system.texy index 35ac62a740..e814b63fd0 100644 --- a/latte/sl/type-system.texy +++ b/latte/sl/type-system.texy @@ -1,27 +1,27 @@ -Sistem tipov -************ +Tipski sistem +************* -
                                                                                                                        +
                                                                                                                        -Sistem tipov je glavna stvar pri razvoju robustnih aplikacij. Latte prinaša podporo za tipe v predloge. Vedeti, kakšen tip podatkov ali predmetov je vsaka spremenljivka, omogoča +Tipski sistem je ključen za razvoj robustnih aplikacij. Latte prinaša podporo tipov tudi v predloge. Zahvaljujoč temu, da vemo, kateri podatkovni ali objektni tip je v vsaki spremenljivki, lahko -- IDE pravilno samodejno dopolnjevanje (glejte [integracijo in vtičnike |recipes#Editors and IDE]) -- statična analiza za odkrivanje napak +- IDE pravilno predlaga (glej [integracija |recipes#Urejevalniki in IDE]) +- statična analiza odkrije napake -Dve točki, ki bistveno izboljšata kakovost in udobje razvoja. +Oboje bistveno povečuje kakovost in udobje razvoja.
                                                                                                                        .[note] -Deklarirane vrste so informativne in jih Latte trenutno ne preverja. +Deklarirani tipi so informativni in Latte jih trenutno ne preverja. -Kako začeti uporabljati tipe? Ustvarite razred predloge, npr. `CatalogTemplateParameters`, ki predstavlja posredovane parametre: +Kako začeti uporabljati tipe? Ustvarite si razred predloge, npr. `CatalogTemplateParameters`, ki predstavlja posredovane parametre, njihove tipe in morebiti tudi privzete vrednosti: ```php class CatalogTemplateParameters { public function __construct( - public string $langs, + public string $lang, /** @var ProductEntity[] */ public array $products, public Address $address, @@ -35,19 +35,16 @@ $latte->render('template.latte', new CatalogTemplateParameters( )); ``` -Nato na začetek predloge vstavite oznako `{templateType}` s polnim imenom razreda (vključno z imenskim prostorom). To določa, da sta v predlogi spremenljivki `$langs` in `$products`, vključno z ustreznimi tipi. -Tipe lokalnih spremenljivk lahko določite tudi z uporabo oznak [`{var}` |tags#var-default], `{varType}` in [`{define}` |template-inheritance#definitions]. +Nato na začetek predloge vstavite značko `{templateType}` s polnim imenom razreda (vključno z imenskim prostorom). To definira, da so v predlogi spremenljivke `$lang` in `$products` vključno z ustreznimi tipi. Tipe lokalnih spremenljivk lahko navedete s pomočjo značk [`{var}` |tags#var default], `{varType}`, [`{define}` |template-inheritance#Definicije]. -Zdaj lahko IDE pravilno samodejno dopolni spremenljivke. +Od takrat vam lahko IDE pravilno predlaga. -Kako shraniti delo? Kako čim lažje napisati razred predloge ali oznake `{varType}`? Pridobite jih generirane. -Prav to počne par oznak `{templatePrint}` in `{varPrint}`. -Če eno od teh oznak namestite v predlogo, se namesto običajnega prikazovanja prikaže koda razreda ali predloge. Nato preprosto izberite in kopirajte kodo v svoj projekt. +Kako si prihraniti delo? Kako čim lažje napisati razred s parametri predloge ali značke `{varType}`? Pustite, da se generirajo. Za to obstaja par značk `{templatePrint}` in `{varPrint}`. Če jih postavite v predlogo, se namesto običajnega izrisovanja prikaže predlog kode razreda oz. seznam značk `{varType}`. Kodo potem samo z enim klikom označite in kopirate v projekt. `{templateType}` ---------------- -Vrste parametrov, ki se posredujejo predlogi, so deklarirane z uporabo razreda: +Tipe parametrov, posredovanih v predlogo, deklariramo s pomočjo razreda: ```latte {templateType MyApp\CatalogTemplateParameters} @@ -56,7 +53,7 @@ Vrste parametrov, ki se posredujejo predlogi, so deklarirane z uporabo razreda: `{varType}` ----------- -Kako deklarirati vrste spremenljivk? V ta namen uporabite oznako `{varType}` za obstoječo spremenljivko ali [`{var}` |tags#var-default]: +Kako deklarirati tipe spremenljivk? Za to služijo značke `{varType}` za obstoječe spremenljivke, ali [`{var}` |tags#var default]: ```latte {varType Nette\Security\User $user} @@ -66,11 +63,11 @@ Kako deklarirati vrste spremenljivk? V ta namen uporabite oznako `{varType}` za `{templatePrint}` ----------------- -Ta razred lahko ustvarite tudi z uporabo oznake `{templatePrint}`. Če jo postavite na začetek predloge, se namesto običajne predloge prikaže koda razreda. Nato preprosto izberite in kopirajte kodo v svoj projekt. +Razred si lahko tudi pustite generirati s pomočjo značke `{templatePrint}`. Če jo postavite na začetek predloge, se namesto običajnega izrisovanja prikaže predlog razreda. Kodo potem samo z enim klikom označite in kopirate v projekt. `{varPrint}` ------------ -Oznaka `{varPrint}` vam prihrani čas. Če jo vstavite v predlogo, se namesto običajnega prikazovanja prikaže seznam oznak `{varType}`. Nato preprosto izberite in kopirajte kodo v predlogo. +Značka `{varPrint}` vam bo prihranila čas s pisanjem. Če jo postavite v predlogo, se namesto običajnega izrisovanja prikaže predlog značk `{varType}` za lokalne spremenljivke. Kodo potem samo z enim klikom označite in kopirate v predlogo. -Oznaka `{varPrint}` navaja lokalne spremenljivke, ki niso parametri predloge. Če želite seznam vseh spremenljivk, uporabite `{varPrint all}`. +Samo `{varPrint}` izpisuje le lokalne spremenljivke, ki niso parametri predloge. Če želite izpisati vse spremenljivke, uporabite `{varPrint all}`. diff --git a/latte/sl/why-use.texy b/latte/sl/why-use.texy new file mode 100644 index 0000000000..9dadebc64d --- /dev/null +++ b/latte/sl/why-use.texy @@ -0,0 +1,80 @@ +Zakaj uporabljati predloge? +*************************** + + +Zakaj bi moral uporabljati sistem predlog v PHP? +------------------------------------------------ + +Zakaj uporabljati sistem predlog v PHP, ko je PHP sam po sebi jezik predlog? + +Najprej na kratko povzemimo zgodovino tega jezika, ki je polna zanimivih preobratov. Eden prvih programskih jezikov, uporabljenih za generiranje HTML strani, je bil jezik C. Kmalu pa se je izkazalo, da je njegova uporaba za ta namen nepraktična. Rasmus Lerdorf je zato ustvaril PHP, ki je olajšal generiranje dinamičnega HTML z jezikom C v zaledju. PHP je bil torej prvotno zasnovan kot jezik predlog, vendar je sčasoma pridobil dodatne funkcije in postal polnopraven programski jezik. + +Kljub temu še vedno deluje tudi kot jezik predlog. V datoteki PHP je lahko zapisana HTML stran, v kateri se s pomočjo `` izpisujejo spremenljivke itd. + +Že v začetkih zgodovine PHP je nastal sistem predlog Smarty, katerega namen je bil strogo ločiti videz (HTML/CSS) od aplikacijske logike. Torej je namerno zagotavljal bolj omejen jezik kot sam PHP, da razvijalec ne bi mogel na primer izvesti poizvedbe v bazo podatkov iz predloge ipd. Po drugi strani pa je predstavljal dodatno odvisnost v projektih, povečeval njihovo kompleksnost in programerji so se morali učiti novega jezika Smarty. Takšna korist je bila sporna in še naprej se je za predloge uporabljal navaden PHP. + +Sčasoma so sistemi predlog postajali koristni. Prišli so s konceptom [dedovanja |template-inheritance], [peskovniškim načinom|sandbox] in vrsto drugih funkcij, ki so znatno poenostavile ustvarjanje predlog v primerjavi s čistim PHP. V ospredje je prišla tema varnosti, obstoj [ranljivosti kot XSS|safety-first] in nujnost [ubežanja znakov |#Kaj je ubežanje znakov escaping]. Sistemi predlog so prišli s samodejnim ubežanjem znakov, da bi izginilo tveganje, da bi programer na to pozabil in bi nastala resna varnostna luknja (čez trenutek si bomo pokazali, da ima to določene pasti). + +Koristi sistemov predlog danes znatno presegajo stroške, povezane z njihovo uvedbo. Zato jih je smiselno uporabljati. + + +Zakaj je Latte boljši od na primer Twiga ali Bladea? +---------------------------------------------------- + +Razlogov je kar več – nekateri so prijetni in drugi bistveno koristni. Latte je kombinacija prijetnega s koristnim. + +*Najprej tisti prijeten:* Latte ima enako [sintakso kot PHP |syntax#Latte razume PHP]. Razlikuje se le zapis značk, namesto `` preferira krajša `{` in `}`. To pomeni, da se vam ni treba učiti novega jezika. Stroški usposabljanja so minimalni. In predvsem se med razvojem ni treba nenehno "preklapljati" med jezikom PHP in jezikom predloge, saj sta oba enaka. Za razliko od predlog Twig, ki uporabljajo jezik Python, in se mora programer tako preklapljati med dvema različnima jezikoma. + +*In zdaj razlog izjemno koristen*: Vsi sistemi predlog, kot so Twig, Blade ali Smarty, so v teku evolucije prišli z zaščito pred XSS v obliki samodejnega [ubežanja znakov |#Kaj je ubežanje znakov escaping]. Natančneje rečeno samodejnega klicanja funkcije `htmlspecialchars()`. Ustvarjalci Latte pa so se zavedali, da to sploh ni pravilna rešitev. Ker se na različnih mestih dokumenta ubežijo znaki na različne načine. Naivno samodejno ubežanje znakov je nevarna funkcija, ker ustvarja lažen občutek varnosti. + +Da bi bilo samodejno ubežanje znakov funkcionalno in zanesljivo, mora prepoznavati, na katerem mestu v dokumentu se podatki izpisujejo (imenujemo jih konteksti) in glede na to izbrati funkcijo za ubežanje znakov. Torej mora biti [kontekstno občutljivo |safety-first#Kontekstno občutljivo ubežanje]. In točno to Latte zna. Razume HTML. Predloge ne dojema le kot niz znakov, ampak razume, kaj so značke, atributi itd. In zato drugače ubeži znake v HTML besedilu, drugače znotraj HTML značke, drugače znotraj JavaScripta itd. + +Latte je prvi in edini sistem predlog v PHP, ki ima kontekstno občutljivo ubežanje znakov. Predstavlja tako edini resnično varen sistem predlog. + +*In še en prijeten razlog*: Zahvaljujoč temu, da Latte razume HTML, ponuja druge zelo prijetne izboljšave. Na primer [n:atribute |syntax#n:atributi]. Ali sposobnost [preverjanja povezav |safety-first#Preverjanje povezav]. In mnogo drugih. + + +Kaj je ubežanje znakov / escaping? +---------------------------------- + +Ubežanje znakov je proces, ki temelji na zamenjavi znakov s posebnim pomenom z ustreznimi zaporedji pri vstavljanju enega niza v drugega, da se preprečijo nezaželeni pojavi ali napake. Na primer, ko vstavljamo niz v HTML besedilo, v katerem ima znak `<` poseben pomen, saj označuje začetek značke, ga nadomestimo z ustreznim zaporedjem, kar je HTML entiteta `<`. Zahvaljujoč temu brskalnik pravilno prikaže simbol `<`. + +Enostaven primer ubežanja znakov neposredno pri pisanju kode v PHP je vstavljanje narekovaja v niz, ko pred njim napišemo poševnico nazaj. + +Podrobneje ubežanje znakov razčlenjujemo v poglavju [Kako se braniti pred XSS |safety-first#Kako se braniti pred XSS]. + + +Ali je mogoče v Latte izvesti poizvedbo v bazo podatkov iz predloge? +-------------------------------------------------------------------- + +V predlogah je mogoče delati z objekti, ki jih programer posreduje vanje. Če torej programer želi, lahko v predlogo posreduje objekt baze podatkov in nad njim izvede poizvedbo. Če ima takšen namen, ni razloga, da bi mu v tem preprečevali. + +Druga situacija nastane, če želite dati možnost urejanja predlog strankam ali zunanjim koderjem. V takem primeru vsekakor nočete, da bi imeli dostop do baze podatkov. Seveda predlogi ne boste posredovali objekta baze podatkov, kaj pa, če je mogoče do nje priti preko drugega objekta? Rešitev je [peskovniški način|sandbox], ki omogoča definiranje, katere metode je mogoče klicati v predlogah. Zahvaljujoč temu se vam ni treba bati kršitve varnosti. + + +Kakšne so glavne razlike med sistemi predlog, kot so Latte, Twig in Blade? +-------------------------------------------------------------------------- + +Razlike med sistemi predlog Latte, Twig in Blade temeljijo predvsem na sintaksi, varnosti in načinu integracije v ogrodja + +- Latte: uporablja sintakso jezika PHP, kar olajšuje učenje in uporabo. Zagotavlja vrhunsko zaščito pred XSS napadi. +- Twig: uporablja sintakso jezika Python, ki se precej razlikuje od PHP. Ubeži znake brez razlikovanja konteksta. Je dobro integriran v ogrodje Symfony. +- Blade: uporablja mešanico PHP in lastne sintakse. Ubeži znake brez razlikovanja konteksta. Je tesno integriran s funkcijami Laravel in ekosistemom. + + +Ali se podjetjem splača uporabljati sistem predlog? +--------------------------------------------------- + +Najprej, stroški, povezani z usposabljanjem, uporabo in skupno koristjo, se znatno razlikujejo glede na sistem. Sistem predlog Latte zahvaljujoč temu, da uporablja sintakso PHP, zelo poenostavlja učenje za programerje, ki so že seznanjeni s tem jezikom. Običajno traja nekaj ur, da se programer z Latte dovolj seznani. Zmanjšuje torej stroške usposabljanja. Hkrati pospešuje usvajanje tehnologije in predvsem učinkovitost pri vsakodnevni uporabi. + +Nadalje Latte zagotavlja visoko raven zaščite pred ranljivostjo XSS zahvaljujoč edinstveni tehnologiji kontekstno občutljivega ubežanja znakov. Ta zaščita je ključna za zagotavljanje varnosti spletnih aplikacij in minimizacijo tveganja napadov, ki bi lahko ogrozili uporabnike ali podatke podjetja. Zaščita varnosti spletnih aplikacij je pomembna tudi za ohranjanje dobrega ugleda podjetja. Varnostni problemi lahko povzročijo izgubo zaupanja s strani strank in škodujejo ugledu podjetja na trgu. + +Uporaba Latte zmanjšuje tudi skupne stroške razvoja in vzdrževanja aplikacije s tem, da oboje olajšuje. Uporaba sistema predlog se torej nedvoumno splača. + + +Ali Latte vpliva na zmogljivost spletnih aplikacij? +--------------------------------------------------- + +Čeprav se predloge Latte hitro obdelujejo, ta vidik pravzaprav ni pomemben. Razlog je, da se razčlenjevanje datotek zgodi samo enkrat ob prvem prikazu. Nato se prevedejo v PHP kodo, shranijo na disk in poganjajo ob vsaki naslednji zahtevi, ne da bi bilo treba ponovno izvajati prevajanje. + +To je način delovanja v produkcijskem okolju. Med razvojem se predloge Latte ponovno prevedejo vsakič, ko pride do spremembe njihove vsebine, da razvijalec vidi vedno trenutno obliko. diff --git a/latte/tr/@home.texy b/latte/tr/@home.texy index 7ca9b4a75a..193c89eb36 100644 --- a/latte/tr/@home.texy +++ b/latte/tr/@home.texy @@ -1 +1,2 @@ -{{maintitle: Latte - PHP için En Güvenli ve Gerçekten Sezgisel Şablonlar}} +{{maintitle: Latte – PHP için en güvenli & gerçekten sezgisel şablonlar}} +{{description: Latte, PHP için en güvenli şablonlama sistemidir. Birçok güvenlik açığını önler. Sezgisel sözdizimini ve birçok kullanışlı özelliğini takdir edeceksiniz.}} diff --git a/latte/tr/@left-menu.texy b/latte/tr/@left-menu.texy index 83a4693ecc..bbae4e53f8 100644 --- a/latte/tr/@left-menu.texy +++ b/latte/tr/@left-menu.texy @@ -1,24 +1,24 @@ -- [Başlarken |Guide] -- Kavramlar - - [Önce Güvenlik |Safety First] +- [Latte ile Başlarken |guide] +- [Neden Şablon Kullanmalı? |why-use] +- Kavramlar ⚗️ + - [Önce Güvenlik |safety-first] - [Şablon Kalıtımı |Template Inheritance] - - [Tip Sistem |Type System] + - [Tip Sistemi |type-system] - [Sandbox |Sandbox] -- Tasarımcılar için - - [Sözdizimi |Syntax] - - [Etiketler |Tags] - - [Filtreler |Filters] - - [Fonksiyonlar |Functions] - - [İpuçları ve Püf Noktaları|recipes] +- Tasarımcılar İçin 🎨 + - [Sözdizimi |syntax] + - [Etiketler |tags] + - [Filtreler |filters] + - [Fonksiyonlar |functions] + - [İpuçları ve Püf Noktaları |recipes] -- Geliştiriciler için - - [Geliştiriciler için Uygulamalar |develop] - - [Uzayan Latte |Extending Latte] - - [Uzantı Oluşturma |creating-extension] +- Geliştiriciler İçin 🧮 + - [Geliştirici Yöntemleri |develop] + - [Latte'yi Genişletme |extending-latte] -- [Yemek Kitabı |cookbook/@home] - - [Twig'den geçiş |cookbook/migration-from-twig] - - [... daha fazla |cookbook/@home] +- [Kılavuzlar ve Yöntemler 💡|cookbook/@home] + - [Twig'den Geçiş |cookbook/migration-from-twig] + - [… diğerleri |cookbook/@home] - "Oyun Alanı .[link-external]":https://fiddle.nette.org/latte/ .{padding-top:1em} diff --git a/latte/tr/@menu.texy b/latte/tr/@menu.texy index 02a290048e..296875b7e0 100644 --- a/latte/tr/@menu.texy +++ b/latte/tr/@menu.texy @@ -1,12 +1,12 @@
                                                                                                                          -- [Ev |@home] -- [Dokümantasyon |Guide] +- [Giriş |@home] +- [Dokümantasyon |guide] - "GitHub .[link-external]":https://github.com/nette/latte
                                                                                                                        diff --git a/latte/tr/@meta.texy b/latte/tr/@meta.texy new file mode 100644 index 0000000000..2aa1de2903 --- /dev/null +++ b/latte/tr/@meta.texy @@ -0,0 +1 @@ +{{sitename: Latte Dokümantasyonu}} diff --git a/latte/tr/compiler-passes.texy b/latte/tr/compiler-passes.texy new file mode 100644 index 0000000000..9cec9553c7 --- /dev/null +++ b/latte/tr/compiler-passes.texy @@ -0,0 +1,555 @@ +Derleme Geçişleri +***************** + +.[perex] +Derleme geçişleri, Latte şablonlarını soyut sözdizimi ağacına (AST) ayrıştırıldıktan *sonra* ve son PHP kodu oluşturulmadan *önce* analiz etmek ve değiştirmek için güçlü bir mekanizma sağlar. Bu, şablonların gelişmiş manipülasyonunu, optimizasyonları, güvenlik kontrollerini (Sandbox gibi) ve şablonlar hakkında bilgi toplamayı mümkün kılar. Bu kılavuz, kendi derleme geçişlerinizi oluşturma konusunda size yol gösterecektir. + + +Derleme Geçişi Nedir? +===================== + +Derleme geçişlerinin rolünü anlamak için [Latte derleme sürecine |custom-tags#Derleme Sürecini Anlama] göz atın. Gördüğünüz gibi, derleme geçişleri kritik bir aşamada çalışır ve ilk ayrıştırma ile son kod çıktısı arasında derin müdahaleye izin verir. + +Özünde, bir derleme geçişi basitçe tek bir argüman alan bir PHP çağrılabilir nesnesidir (fonksiyon, statik metot veya örnek metodu gibi): şablonun kök AST düğümü, ki bu her zaman `Latte\Compiler\Nodes\TemplateNode` örneğidir. + +Bir derleme geçişinin birincil amacı genellikle aşağıdakilerden biri veya her ikisidir: + +- Analiz: AST'yi dolaşarak şablon hakkında bilgi toplamak (örneğin, tanımlanmış tüm blokları bulmak, belirli etiketlerin kullanımını kontrol etmek, belirli güvenlik kısıtlamalarının karşılandığından emin olmak). +- Değişiklik: AST'nin yapısını veya düğümlerin niteliklerini değiştirmek (örneğin, otomatik olarak HTML nitelikleri eklemek, belirli etiket kombinasyonlarını optimize etmek, kullanımdan kaldırılmış etiketleri yenileriyle değiştirmek, Sandbox kurallarını uygulamak). + + +Kayıt +===== + +Derleme geçişleri, [uzantının |extending-latte#getPasses] `getPasses()` yöntemi kullanılarak kaydedilir. Bu yöntem, anahtarların benzersiz geçiş adları (dahili olarak ve sıralama için kullanılır) ve değerlerin geçiş mantığını uygulayan PHP çağrılabilir nesneleri olduğu ilişkisel bir dizi döndürür. + +```php +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Extension; + +class MyExtension extends Extension +{ + public function getPasses(): array + { + return [ + 'modificationPass' => $this->modifyTemplateAst(...), + // ... diğer geçişler ... + ]; + } + + public function modifyTemplateAst(TemplateNode $templateNode): void + { + // Uygulama... + } +} +``` + +Latte'nin temel uzantıları ve kendi uzantılarınız tarafından kaydedilen geçişler sırayla çalışır. Sıra önemli olabilir, özellikle bir geçiş başka bir geçişin sonuçlarına veya değişikliklerine bağlıysa. Latte, gerekirse bu sırayı kontrol etmek için yardımcı bir mekanizma sağlar; ayrıntılar için [`Extension::getPasses()` |extending-latte#getPasses] dokümantasyonuna bakın. + + +AST Örneği +========== + +AST hakkında daha iyi bir fikir vermek için bir örnek ekliyoruz. Bu kaynak şablondur: + +```latte +{foreach $category->getItems() as $item} +
                                                                                                                      • {$item->name|upper}
                                                                                                                      • + {else} + öğe bulunamadı +{/foreach} +``` + +Ve bu onun AST biçimindeki temsilidir: + +/--pre +Latte\Compiler\Nodes\TemplateNode( + Latte\Compiler\Nodes\FragmentNode( + - Latte\Essential\Nodes\ForeachNode( + expression: Latte\Compiler\Nodes\Php\Expression\MethodCallNode( + object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$category') + name: Latte\Compiler\Nodes\Php\IdentifierNode('getItems') + ) + value: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') + content: Latte\Compiler\Nodes\FragmentNode( + - Latte\Compiler\Nodes\TextNode(' ') + - Latte\Compiler\Nodes\Html\ElementNode('li')( + content: Latte\Essential\Nodes\PrintNode( + expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode( + object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') + name: Latte\Compiler\Nodes\Php\IdentifierNode('name') + ) + modifier: Latte\Compiler\Nodes\Php\ModifierNode( + filters: + - Latte\Compiler\Nodes\Php\FilterNode('upper') + ) + ) + ) + ) + else: Latte\Compiler\Nodes\FragmentNode( + - Latte\Compiler\Nodes\TextNode('öğe bulunamadı') + ) + ) + ) +) +\-- + + +`NodeTraverser` ile AST'yi Dolaşma +================================== + +Karmaşık AST yapısını dolaşmak için özyinelemeli fonksiyonları manuel olarak yazmak sıkıcı ve hataya açıktır. Latte bu amaç için özel bir araç sağlar: [api:Latte\Compiler\NodeTraverser]. Bu sınıf, [Ziyaretçi tasarım desenini |https://en.wikipedia.org/wiki/Visitor_pattern] uygular, bu da AST dolaşımını sistematik ve yönetimi kolay hale getirir. + +Temel kullanım, bir `NodeTraverser` örneği oluşturmayı ve `traverse()` yöntemini çağırmayı, AST'nin kök düğümünü ve bir veya iki "ziyaretçi" çağrılabilir nesnesini geçirmeyi içerir: + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; + +(new NodeTraverser)->traverse( + $templateNode, + + // 'enter' ziyaretçisi: Düğüme girerken çağrılır (çocuklarından önce) + enter: function (Node $node) { + echo "Düğüm türüne giriş: " . $node::class . "\n"; + // Burada düğümü inceleyebilirsiniz + if ($node instanceof Nodes\TextNode) { + // echo "Metin bulundu: " . $node->content . "\n"; + } + }, + + // 'leave' ziyaretçisi: Düğümden çıkarken çağrılır (çocuklarından sonra) + leave: function (Node $node) { + echo "Düğüm türünden çıkış: " . $node::class . "\n"; + // Burada çocukları işledikten sonra eylemler gerçekleştirebilirsiniz + }, +); +``` + +İhtiyaçlarınıza bağlı olarak yalnızca `enter` ziyaretçisini, yalnızca `leave` ziyaretçisini veya her ikisini de sağlayabilirsiniz. + +**`enter(Node $node)`:** Bu fonksiyon, dolaşan bu düğümün herhangi bir çocuğunu ziyaret etmeden **önce** her düğüm için yürütülür. Şunlar için kullanışlıdır: + +- Ağaçta aşağı doğru ilerlerken bilgi toplama. +- Çocukları işlemeden *önce* karar verme (onları atlama kararı gibi, bkz. [#Dolaşımı Optimize Etme]). +- Çocukları ziyaret etmeden önce düğümü potansiyel olarak değiştirme (daha az yaygın). + +**`leave(Node $node)`:** Bu fonksiyon, tüm çocukları (ve tüm alt ağaçları) tamamen ziyaret edildikten (hem giriş hem de çıkış) **sonra** her düğüm için yürütülür. + +Hem `enter` hem de `leave` ziyaretçileri, dolaşım sürecini etkilemek için isteğe bağlı olarak bir değer döndürebilir. `null` (veya hiçbir şey) döndürmek dolaşıma normal şekilde devam eder, bir `Node` örneği döndürmek geçerli düğümü değiştirir ve `NodeTraverser::RemoveNode` veya `NodeTraverser::StopTraversal` gibi özel sabitleri döndürmek, sonraki bölümlerde açıklandığı gibi akışı değiştirir. + + +Dolaşım Nasıl Çalışır +--------------------- + +`NodeTraverser` dahili olarak, her `Node` sınıfının uygulaması gereken `getIterator()` yöntemini kullanır ([Özel Etiketler Oluşturma |custom-tags#Alt Düğümler İçin getIterator Uygulama] bölümünde tartışıldığı gibi). `getIterator()` aracılığıyla elde edilen çocuklar üzerinde yinelenir, üzerlerinde özyinelemeli olarak `traverse()` çağırır ve `enter` ve `leave` ziyaretçilerinin, yineleyiciler aracılığıyla erişilebilen ağaçtaki her düğüm için doğru derinlik öncelikli sırada çağrılmasını sağlar. Bu, özel etiket düğümlerinizde doğru şekilde uygulanan `getIterator()`'ın derleme geçişlerinin doğru çalışması için neden kesinlikle gerekli olduğunu tekrar vurgular. + +Şablonda `{do}` etiketinin (`Latte\Essential\Nodes\DoNode` ile temsil edilir) kaç kez kullanıldığını sayan basit bir geçiş yazalım. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Essential\Nodes\DoNode; + +function countDoTags(TemplateNode $templateNode): void +{ + $count = 0; + (new NodeTraverser)->traverse( + $templateNode, + enter: function (Node $node) use (&$count): void { + if ($node instanceof DoNode) { + $count++; + } + }, + // 'leave' ziyaretçisi bu görev için gerekli değil + ); + + echo "{do} etiketi $count kez bulundu.\n"; +} + +$latte = new Latte\Engine; +$ast = $latte->parse($templateSource); +countDoTags($ast); +``` + +Bu örnekte, ziyaret edilen her düğümün türünü kontrol etmek için yalnızca `enter` ziyaretçisine ihtiyacımız vardı. + +Ardından, bu ziyaretçilerin AST'yi gerçekte nasıl değiştirdiğini inceleyeceğiz. + + +AST'yi Değiştirme +================= + +Derleme geçişlerinin ana amaçlarından biri soyut sözdizimi ağacını değiştirmektir. Bu, PHP kodu oluşturulmadan önce doğrudan şablon yapısı üzerinde güçlü dönüşümler, optimizasyonlar veya kural uygulamalarına olanak tanır. `NodeTraverser`, `enter` ve `leave` ziyaretçileri içinde bunu başarmanın birkaç yolunu sunar. + +**Önemli Not:** AST'yi değiştirmek dikkat gerektirir. Yanlış değişiklikler – temel düğümleri kaldırmak veya bir düğümü uyumsuz bir türle değiştirmek gibi – kod oluşturma sırasında hatalara yol açabilir veya çalışma zamanında beklenmedik davranışlara neden olabilir. Değişiklik geçişlerinizi her zaman kapsamlı bir şekilde test edin. + + +Düğüm Özelliklerini Değiştirme +------------------------------ + +Ağacı değiştirmenin en basit yolu, dolaşım sırasında ziyaret edilen düğümlerin **genel özelliklerini** doğrudan değiştirmektir. Tüm düğümler, ayrıştırılmış argümanlarını, içeriklerini veya niteliklerini genel özelliklerde saklar. + +**Örnek:** Tüm statik metin düğümlerini (`TextNode`, Latte etiketleri dışındaki normal HTML veya metni temsil eder) bulan ve içeriklerini *doğrudan AST'de* büyük harflere dönüştüren bir geçiş oluşturalım. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\TextNode; + +function uppercaseStaticText(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // TextNode'un işlenecek çocuğu olmadığı için 'enter' kullanabiliriz + enter: function (Node $node) { + // Bu düğüm statik bir metin bloğu mu? + if ($node instanceof TextNode) { + // Evet! Doğrudan genel 'content' özelliğini değiştiriyoruz. + $node->content = mb_strtoupper(html_entity_decode($node->content)); + } + // Hiçbir şey döndürmeye gerek yok; değişiklik doğrudan uygulanır. + }, + ); +} +``` + +Bu örnekte, `enter` ziyaretçisi geçerli `$node`'un `TextNode` türünde olup olmadığını kontrol eder. Eğer öyleyse, genel `$content` özelliğini doğrudan `mb_strtoupper()` kullanarak güncelleriz. Bu, PHP kodu oluşturulmadan *önce* AST'de depolanan statik metnin içeriğini doğrudan değiştirir. Nesneyi doğrudan değiştirdiğimiz için ziyaretçiden hiçbir şey döndürmemiz gerekmez. + +Etki: Şablon `

                                                                                                                        Merhaba

                                                                                                                        {= $var }Dünya` içeriyorsa, bu geçişten sonra AST şuna benzer bir şeyi temsil edecektir: `

                                                                                                                        MERHABA

                                                                                                                        {= $var }DÜNYA`. Bu, $var'ın içeriğini ETKİLEMEZ. + + +Düğümleri Değiştirme +-------------------- + +Daha güçlü bir değişiklik tekniği, bir düğümü tamamen başka bir düğümle değiştirmektir. Bu, `enter` veya `leave` ziyaretçisinden **yeni bir `Node` örneği döndürerek** yapılır. `NodeTraverser` daha sonra orijinal düğümü, ebeveyn düğümünün yapısında döndürülen düğümle değiştirir. + +**Örnek:** `PHP_VERSION` sabitinin (`ConstantFetchNode` ile temsil edilir) tüm kullanımlarını bulan ve bunları *derleme sırasında* algılanan *gerçek* PHP sürümünü içeren bir dize literali (`StringNode`) ile doğrudan değiştiren bir geçiş oluşturalım. Bu, derleme zamanı optimizasyonunun bir biçimidir. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\Php\Expression\ConstantFetchNode; +use Latte\Compiler\Nodes\Php\Scalar\StringNode; + +function inlinePhpVersion(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // 'leave' genellikle değiştirme için kullanılır, çocukların (varsa) + // önce işlenmesini sağlar, ancak burada 'enter' da çalışırdı. + leave: function (Node $node) { + // Bu düğüm bir sabit erişimi mi ve sabit adı 'PHP_VERSION' mu? + if ($node instanceof ConstantFetchNode && (string) $node->name === 'PHP_VERSION') { + // Geçerli PHP sürümünü içeren yeni bir StringNode oluşturuyoruz + $newNode = new StringNode(PHP_VERSION); + + // İsteğe bağlı, ancak iyi bir uygulama: konum bilgilerini kopyalayın + $newNode->position = $node->position; + + // Yeni StringNode'u döndürüyoruz. Traverser, + // orijinal ConstantFetchNode'u bu $newNode ile değiştirecektir. + return $newNode; + } + // Eğer bir Node döndürmezsek, orijinal $node korunur. + }, + ); +} +``` + +Burada `leave` ziyaretçisi, `PHP_VERSION` için belirli bir `ConstantFetchNode`'u tanımlar. Ardından, *derleme zamanında* `PHP_VERSION` sabitinin değerini içeren tamamen yeni bir `StringNode` oluşturur. Bu `$newNode`'u döndürerek, traverser'a orijinal `ConstantFetchNode`'u AST'de değiştirmesini söyler. + +Etki: Şablon `{= PHP_VERSION }` içeriyorsa ve derleme PHP 8.2.1 üzerinde çalışıyorsa, bu geçişten sonraki AST etkili bir şekilde `{= '8.2.1' }` temsil edecektir. + +**Değiştirme için `enter` vs. `leave` seçimi:** + +- Yeni düğümü oluşturmak, eski düğümün çocuklarını işlemenin sonuçlarına bağlıysa veya çocukların değiştirilmeden önce ziyaret edilmesini basitçe sağlamak istiyorsanız (yaygın uygulama) `leave` kullanın. +- Bir düğümü çocukları hiç ziyaret edilmeden *önce* değiştirmek istiyorsanız `enter` kullanın. + + +Düğümleri Kaldırma +------------------ + +Ziyaretçiden özel `NodeTraverser::RemoveNode` sabitini döndürerek bir düğümü AST'den tamamen kaldırabilirsiniz. + +**Örnek:** Latte çekirdeği tarafından oluşturulan AST'de `CommentNode` ile temsil edilen tüm şablon yorumlarını (`{* ... *}`) kaldıralım (genellikle daha önce işlense de, bu bir örnek olarak hizmet eder). + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\CommentNode; + +function removeCommentNodes(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // Yorumu kaldırmak için çocuk bilgisine ihtiyacımız olmadığı için 'enter' burada uygundur + enter: function (Node $node) { + if ($node instanceof CommentNode) { + // Traverser'a bu düğümü AST'den kaldırmasını işaret ediyoruz + return NodeTraverser::RemoveNode; + } + }, + ); +} +``` + +**Uyarı:** `RemoveNode`'u dikkatli kullanın. Temel içeriği içeren veya yapıyı etkileyen bir düğümü kaldırmak (bir döngünün içerik düğümünü kaldırmak gibi), bozuk şablonlara veya geçersiz oluşturulmuş koda yol açabilir. Gerçekten isteğe bağlı veya bağımsız olan düğümler (yorumlar veya hata ayıklama etiketleri gibi) veya boş yapısal düğümler (örneğin, boş bir `FragmentNode` bazı bağlamlarda bir temizleme geçişi tarafından güvenle kaldırılabilir) için en güvenlidir. + +Bu üç yöntem - özellikleri değiştirme, düğümleri değiştirme ve düğümleri kaldırma - derleme geçişleriniz içinde AST'yi manipüle etmek için temel araçları sağlar. + + +Dolaşımı Optimize Etme +====================== + +Şablonların AST'si oldukça büyük olabilir ve potansiyel olarak binlerce düğüm içerebilir. Geçişiniz yalnızca ağacın belirli bölümleriyle ilgileniyorsa, her bir düğümü dolaşmak gereksiz olabilir ve derleme performansını etkileyebilir. `NodeTraverser`, dolaşımı optimize etmenin yollarını sunar: + + +Çocukları Atlama +---------------- + +Belirli bir düğüm türüne rastladığınızda, torunlarından hiçbirinin aradığınız düğümleri içeremeyeceğini biliyorsanız, traverser'a çocuklarını ziyaret etmeyi atlamasını söyleyebilirsiniz. Bu, **`enter`** ziyaretçisinden `NodeTraverser::DontTraverseChildren` sabitini döndürerek yapılır. Bu, dolaşım sırasında tüm dalları atlar, potansiyel olarak önemli ölçüde zaman kazandırır, özellikle etiketler içinde karmaşık PHP ifadeleri olan şablonlarda. + + +Dolaşımı Durdurma +----------------- + +Geçişinizin yalnızca bir şeyin *ilk* örneğini bulması gerekiyorsa (belirli bir düğüm türü, bir koşulun karşılanması), bulduğunuz anda tüm dolaşım sürecini tamamen durdurabilirsiniz. Bu, `enter` veya `leave` ziyaretçisinden `NodeTraverser::StopTraversal` sabitini döndürerek başarılır. `traverse()` yöntemi başka herhangi bir düğümü ziyaret etmeyi durdurur. Potansiyel olarak çok büyük bir ağaçta yalnızca ilk eşleşmeye ihtiyacınız varsa bu son derece etkilidir. + + +Kullanışlı Yardımcı `NodeHelpers` +================================= + +`NodeTraverser` ince ayarlı kontrol sunarken, Latte ayrıca birkaç yaygın arama ve analiz görevi için `NodeTraverser`'ı kapsayan pratik bir yardımcı sınıf olan [api:Latte\Compiler\NodeHelpers]'ı da sağlar ve genellikle daha az standart kod gerektirir. + + +find(Node $startNode, callable $filter): array .[method] +-------------------------------------------------------- + +Bu statik yöntem, `$startNode`'dan başlayan (dahil) alt ağaçtaki `$filter` geri çağrısını karşılayan **tüm** düğümleri bulur. Eşleşen düğümlerin bir dizisini döndürür. + +**Örnek:** Tüm şablondaki tüm değişken düğümlerini (`VariableNode`) bulun. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Php\Expression\VariableNode; +use Latte\Compiler\Nodes\TemplateNode; + +function findAllVariables(TemplateNode $templateNode): array +{ + return NodeHelpers::find( + $templateNode, + fn($node) => $node instanceof VariableNode, + ); +} +``` + + +findFirst(Node $startNode, callable $filter): ?Node .[method] +-------------------------------------------------------------- + +`find`'a benzer, ancak `$filter` geri çağrısını karşılayan **ilk** düğümü bulduktan hemen sonra dolaşımı durdurur. Bulunan `Node` nesnesini veya eşleşen düğüm bulunamazsa `null` döndürür. Bu, esasen `NodeTraverser::StopTraversal` etrafında pratik bir sarmalayıcıdır. + +**Örnek:** `{parameters}` düğümünü bulun (daha önceki manuel örnekle aynı, ancak daha kısa). + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Essential\Nodes\ParametersNode; + +function findParametersNodeHelper(TemplateNode $templateNode): ?ParametersNode +{ + return NodeHelpers::findFirst( + $templateNode->head, // Verimlilik için yalnızca ana bölümde arama yapın + fn($node) => $node instanceof ParametersNode, + ); +} +``` + + +toValue(ExpressionNode $node, bool $constants = false): mixed .[method] +----------------------------------------------------------------------- + +Bu statik yöntem, bir `ExpressionNode`'u **derleme zamanında** değerlendirmeye çalışır ve karşılık gelen PHP değerini döndürür. Yalnızca basit literal düğümler (`StringNode`, `IntegerNode`, `FloatNode`, `BooleanNode`, `NullNode`) ve yalnızca bu tür değerlendirilebilir öğeleri içeren `ArrayNode` örnekleri için güvenilir bir şekilde çalışır. + +Eğer `$constants` `true` olarak ayarlanırsa, `defined()` kontrol ederek ve `constant()` kullanarak `ConstantFetchNode` ve `ClassConstantFetchNode`'u çözmeye de çalışacaktır. + +Düğüm değişkenler, fonksiyon çağrıları veya diğer dinamik öğeler içeriyorsa, derleme zamanında değerlendirilemez ve yöntem bir `InvalidArgumentException` fırlatır. + +**Kullanım Durumu:** Derleme zamanı kararları için derleme sırasında bir etiket argümanının statik değerini elde etme. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Php\ExpressionNode; + +function getStaticStringArgument(ExpressionNode $argumentNode): ?string +{ + try { + $value = NodeHelpers::toValue($argumentNode); + return is_string($value) ? $value : null; + } catch (\InvalidArgumentException $e) { + // Argüman statik bir literal dize değildi + return null; + } +} +``` + + +toText(?Node $node): ?string .[method] +-------------------------------------- + +Bu statik yöntem, basit düğümlerden düz metin içeriğini çıkarmak için kullanışlıdır. Öncelikle şunlarla çalışır: +- `TextNode`: `$content`'ini döndürür. +- `FragmentNode`: Tüm çocukları için `toText()` sonucunu birleştirir. Herhangi bir çocuk metne dönüştürülemezse (örneğin, bir `PrintNode` içeriyorsa), `null` döndürür. +- `NopNode`: Boş bir dize döndürür. +- Diğer düğüm türleri: `null` döndürür. + +**Kullanım Durumu:** Bir derleme geçişi sırasında analiz için bir HTML niteliğinin veya basit bir HTML öğesinin statik metin içeriğini elde etme. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Html\AttributeNode; + +function getStaticAttributeValue(AttributeNode $attr): ?string +{ + // $attr->value tipik olarak bir AreaNode'dur (FragmentNode veya TextNode gibi) + return NodeHelpers::toText($attr->value); +} + +// Geçişte kullanım örneği: +// if ($node instanceof Html\ElementNode && $node->name === 'meta') { +// $nameAttrValue = getStaticAttributeValue($node->getAttributeNode('name')); +// if ($nameAttrValue === 'description') { ... } +// } +``` + +`NodeHelpers`, yaygın AST dolaşım ve analiz görevleri için hazır çözümler sunarak derleme geçişlerinizi basitleştirebilir. + + +Pratik Örnekler +=============== + +Bazı pratik sorunları çözmek için AST dolaşım ve değişiklik kavramlarını uygulayalım. Bu örnekler, derleme geçişlerinde kullanılan yaygın desenleri gösterir. + + +``'ye Otomatik Olarak `loading="lazy"` Ekleme +-------------------------------------------------- + +Modern tarayıcılar, `loading="lazy"` niteliğini kullanarak görüntüler için yerel geç yüklemeyi destekler. Henüz bir `loading` niteliği olmayan tüm `` etiketlerine bu niteliği otomatik olarak ekleyen bir geçiş oluşturalım. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; +use Latte\Compiler\Nodes\Html; + +function addLazyLoading(Nodes\TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // Düğümü doğrudan değiştirdiğimiz ve bu karar için çocuklara + // bağlı olmadığımız için 'enter' kullanabiliriz. + enter: function (Node $node) { + // Bu 'img' adlı bir HTML öğesi mi? + if ($node instanceof Html\ElementNode && $node->name === 'img') { + // Nitelik düğümünün var olduğundan emin olun + $node->attributes ??= new Nodes\FragmentNode; + + // Zaten bir 'loading' niteliğinin olup olmadığını kontrol edin (büyük/küçük harf duyarsız) + foreach ($node->attributes->children as $attrNode) { + if ($attrNode instanceof Html\AttributeNode + && $attrNode->name instanceof Nodes\TextNode // Statik nitelik adı + && strtolower($attrNode->name->content) === 'loading' + ) { + return; // Zaten var, hiçbir şey yapma + } + } + + // Nitelikler boş değilse bir boşluk ekleyin + if ($node->attributes->children) { + $node->attributes->children[] = new Nodes\TextNode(' '); + } + + // Yeni bir nitelik düğümü oluşturun: loading="lazy" + $node->attributes->children[] = new Html\AttributeNode( + name: new Nodes\TextNode('loading'), + value: new Nodes\TextNode('lazy'), + quote: '"', + ); + // Değişiklik doğrudan nesnede uygulanır, hiçbir şey döndürmeye gerek yoktur. + } + }, + ); +} +``` + +Açıklama: +- `enter` ziyaretçisi, `img` adlı `Html\ElementNode` düğümlerini arar. +- Mevcut nitelikleri (`$node->attributes->children`) yineler ve `loading` niteliğinin zaten mevcut olup olmadığını kontrol eder. +- Bulunamazsa, `loading="lazy"` temsil eden yeni bir `Html\AttributeNode` oluşturur. + + +Fonksiyon Çağrılarını Kontrol Etme +---------------------------------- + +Derleme geçişleri, Latte Sandbox'ın temelidir. Gerçek Sandbox karmaşık olsa da, yasaklanmış fonksiyon çağrılarını kontrol etmenin temel prensibini gösterebiliriz. + +**Amaç:** Şablon ifadeleri içinde potansiyel olarak tehlikeli `shell_exec` fonksiyonunun kullanımını önlemek. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; +use Latte\Compiler\Nodes\Php; +use Latte\SecurityViolationException; + +function checkForbiddenFunctions(Nodes\TemplateNode $templateNode): void +{ + $forbiddenFunctions = ['shell_exec' => true, 'exec' => true]; // Basit liste + + $traverser = new NodeTraverser; + (new NodeTraverser)->traverse( + $templateNode, + enter: function (Node $node) use ($forbiddenFunctions) { + // Bu doğrudan bir fonksiyon çağrı düğümü mü? + if ($node instanceof Php\Expression\FunctionCallNode + && $node->name instanceof Php\NameNode + && isset($forbiddenFunctions[strtolower((string) $node->name)]) + ) { + throw new SecurityViolationException( + "{$node->name}() fonksiyonuna izin verilmiyor.", + $node->position, + ); + } + }, + ); +} +``` + +Açıklama: +- Yasaklanmış fonksiyon adlarının bir listesini tanımlıyoruz. +- `enter` ziyaretçisi `FunctionCallNode`'u kontrol eder. +- Fonksiyon adı (`$node->name`) statik bir `NameNode` ise, küçük harfli dize temsilini yasaklı listemizle karşılaştırırız. +- Yasaklanmış bir fonksiyon bulunursa, güvenlik kuralı ihlalini açıkça belirten ve derlemeyi durduran bir `Latte\SecurityViolationException` fırlatırız. + +Bu örnekler, `NodeTraverser` kullanarak derleme geçişlerinin, doğrudan şablonun AST yapısıyla etkileşime girerek analiz, otomatik değişiklikler ve güvenlik kısıtlamalarının uygulanması için nasıl kullanılabileceğini gösterir. + + +En İyi Uygulamalar +================== + +Derleme geçişleri yazarken, sağlam, sürdürülebilir ve verimli uzantılar oluşturmak için bu yönergeleri aklınızda bulundurun: + +- **Sıra Önemlidir:** Geçişlerin çalıştığı sıranın farkında olun. Geçişiniz başka bir geçiş tarafından oluşturulan AST yapısına (örneğin, Latte'nin temel geçişleri veya başka bir özel geçiş) bağlıysa veya diğer geçişler sizin değişikliklerinize bağlı olabiliyorsa, bağımlılıkları tanımlamak için `Extension::getPasses()` tarafından sağlanan sıralama mekanizmasını kullanın (`before`/`after`). Ayrıntılar için [`Extension::getPasses()` |extending-latte#getPasses] dokümantasyonuna bakın. +- **Tek Sorumluluk:** İyi tanımlanmış tek bir görevi yerine getiren geçişler hedefleyin. Karmaşık dönüşümler için, mantığı birden fazla geçişe bölmeyi düşünün – belki biri analiz için, diğeri analiz sonuçlarına dayalı değişiklik için. Bu, netliği ve test edilebilirliği artırır. +- **Performans:** Derleme geçişlerinin şablon derleme süresine ekleme yaptığını unutmayın (genellikle şablon değişene kadar yalnızca bir kez gerçekleşse de). Mümkünse geçişlerinizde hesaplama açısından pahalı işlemlerden kaçının. AST'nin belirli bölümlerini ziyaret etmeniz gerekmediğini bildiğinizde `NodeTraverser::DontTraverseChildren` ve `NodeTraverser::StopTraversal` gibi dolaşım optimizasyonlarından yararlanın. +- **`NodeHelpers` Kullanın:** Belirli düğümleri bulmak veya basit ifadeleri statik olarak değerlendirmek gibi yaygın görevler için, kendi `NodeTraverser` mantığınızı yazmadan önce `Latte\Compiler\NodeHelpers`'ın uygun bir yöntem sunup sunmadığını kontrol edin. Bu, zaman kazandırabilir ve standart kod miktarını azaltabilir. +- **Hata İşleme:** Geçişiniz şablon AST'sinde bir hata veya geçersiz durum algılarsa, net bir mesaj ve ilgili `Position` nesnesiyle (genellikle `$node->position`) bir `Latte\CompileException` (veya güvenlik sorunları için `Latte\SecurityViolationException`) fırlatın. Bu, şablon geliştiricisine yararlı geri bildirim sağlar. +- **İdempotans (Mümkünse):** İdeal olarak, geçişinizi aynı AST üzerinde birden çok kez çalıştırmak, bir kez çalıştırmakla aynı sonucu üretmelidir. Bu her zaman mümkün değildir, ancak başarıldığında hata ayıklamayı ve geçiş etkileşimleri hakkında akıl yürütmeyi basitleştirir. Örneğin, değişiklik geçişinizin, değişikliği tekrar uygulamadan önce zaten uygulanıp uygulanmadığını kontrol ettiğinden emin olun. + +Bu uygulamaları takip ederek, Latte'nin yeteneklerini güçlü ve güvenilir bir şekilde genişletmek için derleme geçişlerini etkili bir şekilde kullanabilir, daha güvenli, daha optimize edilmiş veya işlevsel olarak daha zengin şablon işlemeye katkıda bulunabilirsiniz. diff --git a/latte/tr/cookbook/@home.texy b/latte/tr/cookbook/@home.texy index 8ffcd3e435..1d1a94cb9b 100644 --- a/latte/tr/cookbook/@home.texy +++ b/latte/tr/cookbook/@home.texy @@ -1,13 +1,13 @@ -Yemek Kitabı -************ +Kılavuzlar ve yöntemler +*********************** .[perex] -Latte ile ortak görevleri gerçekleştirmek için örnek kodlar ve tarifler. +Latte kullanarak yaygın görevleri gerçekleştirmek için kod örnekleri ve tarifler. -- [{iterateWhile} Hakkında Her Zaman Bilmek İstediğiniz Her Şey |iteratewhile] +- [Geliştiriciler için yöntemler |/develop] +- [Şablonlar arasında değişkenleri geçirme |passing-variables] +- [Gruplama hakkında bilmek istediğiniz her şey |grouping] - [Latte'de SQL sorguları nasıl yazılır? |how-to-write-sql-queries-in-latte] -- [PHP'den geçiş |migration-from-php] -- [Twig'den geçiş |migration-from-twig] -- [Latte'yi Slim 4 ile kullanma |slim-framework] - -{{leftbar: /@left-menu}} +- [PHP'den Geçiş |migration-from-php] +- [Twig'den Geçiş |migration-from-twig] +- [Latte'yi Slim 4 ile Kullanma |slim-framework] diff --git a/latte/tr/cookbook/@meta.texy b/latte/tr/cookbook/@meta.texy new file mode 100644 index 0000000000..24e8dea278 --- /dev/null +++ b/latte/tr/cookbook/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Latte Dokümantasyonu}} +{{leftbar: /@left-menu}} diff --git a/latte/tr/cookbook/grouping.texy b/latte/tr/cookbook/grouping.texy new file mode 100644 index 0000000000..0115d141f1 --- /dev/null +++ b/latte/tr/cookbook/grouping.texy @@ -0,0 +1,251 @@ +Gruplama Hakkında Bilmek İstediğiniz Her Şey +******************************************** + +.[perex] +Şablonlardaki verilerle çalışırken, bunları belirli kriterlere göre gruplama veya özel olarak görüntüleme ihtiyacıyla sık sık karşılaşabilirsiniz. Latte bu amaçla birkaç güçlü araç sunar. + +`|group` filtresi ve fonksiyonu, verileri belirtilen kritere göre verimli bir şekilde gruplamayı sağlar, `|batch` filtresi verileri sabit boyutlu gruplara ayırmayı kolaylaştırır ve `{iterateWhile}` etiketi, koşullarla döngülerin ilerleyişini daha karmaşık bir şekilde kontrol etme imkanı sunar. Bu etiketlerin her biri, verilerle çalışmak için özel seçenekler sunar, bu da onları Latte şablonlarında bilgilerin dinamik ve yapılandırılmış bir şekilde görüntülenmesi için vazgeçilmez araçlar haline getirir. + + +`group` Filtresi ve Fonksiyonu .{data-version:3.0.16} +===================================================== + +Kategorilere ayrılmış öğeleri içeren bir `items` veritabanı tablosu hayal edin: + +| id | categoryId | name +|------------------ +| 1 | 1 | Apple +| 2 | 1 | Banana +| 3 | 2 | PHP +| 4 | 3 | Green +| 5 | 3 | Red +| 6 | 3 | Blue + +Latte şablonu kullanarak tüm öğelerin basit bir listesi şöyle görünür: + +```latte +
                                                                                                                          +{foreach $items as $item} +
                                                                                                                        • {$item->name}
                                                                                                                        • +{/foreach} +
                                                                                                                        +``` + +Ancak öğelerin kategoriye göre gruplar halinde düzenlenmesini isteseydik, her kategorinin kendi listesine sahip olacak şekilde onları bölmemiz gerekirdi. Sonuç şöyle görünmelidir: + +```latte +
                                                                                                                          +
                                                                                                                        • Apple
                                                                                                                        • +
                                                                                                                        • Banana
                                                                                                                        • +
                                                                                                                        + +
                                                                                                                          +
                                                                                                                        • PHP
                                                                                                                        • +
                                                                                                                        + +
                                                                                                                          +
                                                                                                                        • Green
                                                                                                                        • +
                                                                                                                        • Red
                                                                                                                        • +
                                                                                                                        • Blue
                                                                                                                        • +
                                                                                                                        +``` + +Görev, `|group` kullanarak kolayca ve zarif bir şekilde çözülebilir. Parametre olarak `categoryId` belirtiriz, bu da öğelerin `$item->categoryId` değerine göre daha küçük dizilere bölüneceği anlamına gelir (eğer `$item` bir dizi olsaydı, `$item['categoryId']` kullanılır): + +```latte +{foreach ($items|group: categoryId) as $categoryId => $categoryItems} +
                                                                                                                          + {foreach $categoryItems as $item} +
                                                                                                                        • {$item->name}
                                                                                                                        • + {/foreach} +
                                                                                                                        +{/foreach} +``` + +Filtre Latte'de bir fonksiyon olarak da kullanılabilir, bu da bize alternatif bir sözdizimi sunar: `{foreach group($items, categoryId) ...}`. + +Öğeleri daha karmaşık kriterlere göre gruplamak istiyorsanız, filtre parametresinde bir fonksiyon kullanabilirsiniz. Örneğin, öğeleri ad uzunluğuna göre gruplamak şöyle görünür: + +```latte +{foreach ($items|group: fn($item) => strlen($item->name)) as $items} + ... +{/foreach} +``` + +`$categoryItems`'ın sıradan bir dizi değil, bir iteratör gibi davranan bir nesne olduğunu unutmamak önemlidir. Grubun ilk öğesine erişmek için [`first()` |latte:functions#first] fonksiyonunu kullanabilirsiniz. + +Verileri gruplamadaki bu esneklik, `group`'u Latte şablonlarında verileri sunmak için son derece kullanışlı bir araç haline getirir. + + +İç İçe Döngüler +--------------- + +Tek tek öğelerin alt kategorilerini tanımlayan ek bir `subcategoryId` sütununa sahip bir veritabanı tablomuz olduğunu varsayalım. Her ana kategoriyi ayrı bir `
                                                                                                                          ` listesinde ve her alt kategoriyi ayrı bir iç içe `
                                                                                                                            ` listesinde görüntülemek istiyoruz: + +```latte +{foreach ($items|group: categoryId) as $categoryItems} +
                                                                                                                              + {foreach ($categoryItems|group: subcategoryId) as $subcategoryItems} +
                                                                                                                                + {foreach $subcategoryItems as $item} +
                                                                                                                              1. {$item->name} + {/foreach} +
                                                                                                                              + {/foreach} +
                                                                                                                            +{/foreach} +``` + + +Nette Database ile Bağlantı +--------------------------- + +Veri gruplamayı Nette Database ile birlikte nasıl verimli bir şekilde kullanacağımızı gösterelim. Giriş örneğindeki `items` tablosuyla çalıştığımızı varsayalım, bu tablo `categoryId` sütunu aracılığıyla şu `categories` tablosuna bağlıdır: + +| categoryId | name | +|------------|------------| +| 1 | Fruits | +| 2 | Languages | +| 3 | Colors | + +`items` tablosundaki verileri Nette Database Explorer kullanarak `$items = $db->table('items')` komutuyla okuruz. Bu veriler üzerinde iterasyon yaparken, `$item->name` ve `$item->categoryId` gibi niteliklere erişmenin yanı sıra, `categories` tablosuyla olan bağlantı sayesinde ilgili satıra `$item->category` üzerinden de erişebiliriz. Bu bağlantı üzerinde ilginç bir kullanım gösterilebilir: + +```latte +{foreach ($items|group: category) as $category => $categoryItems} +

                                                                                                                            {$category->name}

                                                                                                                            +
                                                                                                                              + {foreach $categoryItems as $item} +
                                                                                                                            • {$item->name}
                                                                                                                            • + {/foreach} +
                                                                                                                            +{/foreach} +``` + +Bu durumda, `|group` filtresini yalnızca `categoryId` sütununa göre değil, bağlantılı `$item->category` satırına göre gruplamak için kullanıyoruz. Bu sayede, anahtar değişkeninde doğrudan ilgili kategorinin `ActiveRow`'unu alırız, bu da bize doğrudan `{$category->name}` kullanarak adını yazdırmamızı sağlar. Bu, gruplamanın şablonları nasıl netleştirebileceğine ve verilerle çalışmayı nasıl kolaylaştırabileceğine dair pratik bir örnektir. + + +`|batch` Filtresi +================= + +Filtre, bir öğe listesini önceden belirlenmiş sayıda öğe içeren gruplara ayırmayı sağlar. Bu filtre, verileri daha iyi netlik veya sayfada görsel düzenleme gibi nedenlerle birden fazla küçük grupta sunmak istediğiniz durumlar için idealdir. + +Bir öğe listemiz olduğunu ve bunları her biri en fazla üç öğe içeren listelerde görüntülemek istediğimizi varsayalım. `|batch` filtresinin kullanımı bu durumda çok pratiktir: + +```latte +
                                                                                                                              +{foreach ($items|batch: 3) as $batch} + {foreach $batch as $item} +
                                                                                                                            • {$item->name}
                                                                                                                            • + {/foreach} +{/foreach} +
                                                                                                                            +``` + +Bu örnekte, `$items` listesi daha küçük gruplara ayrılmıştır ve her grup (`$batch`) en fazla üç öğe içerir. Her grup daha sonra ayrı bir `
                                                                                                                              ` listesinde görüntülenir. + +Son grup istenen sayıya ulaşmak için yeterli öğe içermiyorsa, filtrenin ikinci parametresi bu grubun neyle tamamlanacağını tanımlamanıza olanak tanır. Bu, eksik bir sıranın düzensiz görünebileceği yerlerde öğeleri estetik olarak hizalamak için idealdir. + +```latte +{foreach ($items|batch: 3, '—') as $batch} + ... +{/foreach} +``` + + +`{iterateWhile}` Etiketi +======================== + +`|group` filtresiyle çözdüğümüz aynı görevleri `{iterateWhile}` etiketini kullanarak göstereceğiz. İki yaklaşım arasındaki temel fark, `group`'un önce tüm giriş verilerini işlemesi ve gruplaması, `{iterateWhile}`'ın ise koşullarla döngülerin ilerleyişini kontrol etmesi, böylece iterasyonun aşamalı olarak gerçekleşmesidir. + +Önce iterateWhile kullanarak kategorilerle tabloyu oluşturalım: + +```latte +{foreach $items as $item} +
                                                                                                                                + {iterateWhile} +
                                                                                                                              • {$item->name}
                                                                                                                              • + {/iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                                                                                                                              +{/foreach} +``` + +`{foreach}` döngünün dış kısmını, yani her kategori için listelerin oluşturulmasını işaret ederken, `{iterateWhile}` etiketi iç kısmı, yani tek tek öğeleri işaret eder. Bitiş etiketindeki koşul, tekrarın geçerli ve sonraki öğe aynı kategoriye ait olduğu sürece (`$iterator->nextValue` [sonraki öğe |/tags#iterator]) devam edeceğini söyler. + +Koşul her zaman karşılanırsa, iç döngüde tüm öğeler oluşturulur: + +```latte +{foreach $items as $item} +
                                                                                                                                + {iterateWhile} +
                                                                                                                              • {$item->name} + {/iterateWhile true} +
                                                                                                                              +{/foreach} +``` + +Sonuç şöyle görünecektir: + +```latte +
                                                                                                                                +
                                                                                                                              • Apple
                                                                                                                              • +
                                                                                                                              • Banana
                                                                                                                              • +
                                                                                                                              • PHP
                                                                                                                              • +
                                                                                                                              • Green
                                                                                                                              • +
                                                                                                                              • Red
                                                                                                                              • +
                                                                                                                              • Blue
                                                                                                                              • +
                                                                                                                              +``` + +Böyle bir iterateWhile kullanımının ne faydası var? Tablo boşsa ve hiçbir öğe içermiyorsa, boş `
                                                                                                                                ` yazdırılmaz. + +Açılış etiketinde `{iterateWhile}` bir koşul belirtirsek, davranış değişir: koşul (ve sonraki öğeye geçiş) iç döngünün sonunda değil, başında zaten yürütülür. Yani, koşulsuz `{iterateWhile}`'a her zaman girilirken, `{iterateWhile $cond}`'a yalnızca `$cond` koşulu karşılandığında girilir. Ve aynı zamanda, sonraki öğe `$item`'a yazılır. + +Bu, örneğin her kategorideki ilk öğeyi farklı bir şekilde, örneğin şöyle oluşturmak istediğimiz bir durumda kullanışlıdır: + +```latte +

                                                                                                                                Apple

                                                                                                                                +
                                                                                                                                  +
                                                                                                                                • Banana
                                                                                                                                • +
                                                                                                                                + +

                                                                                                                                PHP

                                                                                                                                +
                                                                                                                                  +
                                                                                                                                + +

                                                                                                                                Green

                                                                                                                                +
                                                                                                                                  +
                                                                                                                                • Red
                                                                                                                                • +
                                                                                                                                • Blue
                                                                                                                                • +
                                                                                                                                +``` + +Orijinal kodu, önce ilk öğeyi oluşturacak ve ardından iç `{iterateWhile}` döngüsünde aynı kategoriden diğer öğeleri oluşturacak şekilde değiştiririz: + +```latte +{foreach $items as $item} +

                                                                                                                                {$item->name}

                                                                                                                                +
                                                                                                                                  + {iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                                                                                                                                • {$item->name}
                                                                                                                                • + {/iterateWhile} +
                                                                                                                                +{/foreach} +``` + +Tek bir döngü içinde birden fazla iç döngü oluşturabilir ve hatta bunları iç içe geçirebiliriz. Bu şekilde alt kategoriler vb. gruplandırılabilir. + +Tabloda ek bir `subcategoryId` sütunu olduğunu ve her kategorinin ayrı bir `
                                                                                                                                  ` içinde olmasının yanı sıra, her alt kategorinin ayrı bir `
                                                                                                                                    ` içinde olacağını varsayalım: + +```latte +{foreach $items as $item} +
                                                                                                                                      + {iterateWhile} +
                                                                                                                                        + {iterateWhile} +
                                                                                                                                      1. {$item->name} + {/iterateWhile $item->subcategoryId === $iterator->nextValue->subcategoryId} +
                                                                                                                                      + {/iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                                                                                                                                    +{/foreach} +``` diff --git a/latte/tr/cookbook/how-to-write-sql-queries-in-latte.texy b/latte/tr/cookbook/how-to-write-sql-queries-in-latte.texy index ce139aaf43..29269805e0 100644 --- a/latte/tr/cookbook/how-to-write-sql-queries-in-latte.texy +++ b/latte/tr/cookbook/how-to-write-sql-queries-in-latte.texy @@ -2,9 +2,9 @@ Latte'de SQL Sorguları Nasıl Yazılır? ************************************* .[perex] -Latte, gerçekten karmaşık SQL sorguları oluşturmak için de yararlı olabilir. +Latte, gerçekten karmaşık SQL sorguları oluşturmak için de kullanışlı olabilir. -Bir SQL sorgusunun oluşturulması birçok koşul ve değişken içeriyorsa, bunu Latte'de yazmak gerçekten daha net olabilir. Çok basit bir örnek: +Bir SQL sorgusunun oluşturulması bir dizi koşul ve değişken içeriyorsa, Latte'de yazmak gerçekten daha net olabilir. Çok basit bir örnek: ```latte SELECT users.* FROM users @@ -14,8 +14,7 @@ SELECT users.* FROM users WHERE groups.name = 'Admins' {ifset $country} AND country.name = {$country} {/ifset} ``` -`$latte->setContentType()` adresini kullanarak Latte'ye içeriği düz metin (HTML olarak değil) olarak ele almasını ve -ardından, dizeleri doğrudan veritabanı sürücüsü tarafından kaçan bir kaçış işlevi hazırlarız: +`$latte->setContentType()` kullanarak Latte'ye içeriği düz metin olarak (HTML olarak değil) ele almasını söyleriz ve ayrıca dizeleri doğrudan veritabanı sürücüsüyle kaçıracak bir kaçış fonksiyonu hazırlarız: ```php $db = new PDO(/* ... */); @@ -27,17 +26,15 @@ $latte->addFilter('escape', fn($val) => match (true) { is_int($val), is_float($val) => (string) $val, is_bool($val) => $val ? '1' : '0', is_null($val) => 'NULL', - default => throw new Exception('Unsupported type'), + default => throw new Exception('Desteklenmeyen tür'), }); ``` -Kullanım şu şekilde olacaktır: +Kullanım şöyle görünür: ```php $sql = $latte->renderToString('query.sql.latte', ['country' => $country]); $result = $db->query($sql); ``` -*Bu örnek Latte v3.0.5 veya üstünü gerektirmektedir.* - -{{leftbar: /@left-menu}} +*Belirtilen örnek Latte v3.0.5 veya üstünü gerektirir.* diff --git a/latte/tr/cookbook/iteratewhile.texy b/latte/tr/cookbook/iteratewhile.texy deleted file mode 100644 index 97e04321c0..0000000000 --- a/latte/tr/cookbook/iteratewhile.texy +++ /dev/null @@ -1,214 +0,0 @@ -iterateWhile} Hakkında Her Zaman Bilmek İstediğiniz Her Şey -*********************************************************** - -.[perex] -`{iterateWhile}` etiketi foreach döngülerinde çeşitli hileler için uygundur. - -Öğelerin kategorilere ayrıldığı aşağıdaki veritabanı tablosuna sahip olduğumuzu varsayalım: - -| id | catId | name -|------------------ -| 1 | 1 | Apple -| 2 | 1 | Banana -| 3 | 2 | PHP -| 4 | 3 | Green -| 5 | 3 | Red -| 6 | 3 | Blue - -Elbette, bir foreach döngüsündeki öğeleri liste olarak çizmek kolaydır: - -```latte -
                                                                                                                                      -{foreach $items as $item} -
                                                                                                                                    • {$item->name}
                                                                                                                                    • -{/foreach} -
                                                                                                                                    -``` - -Ancak her kategoriyi ayrı bir listede işlemek istiyorsanız ne yapmalısınız? Başka bir deyişle, doğrusal bir listedeki öğeleri bir foreach döngüsü içinde gruplama görevi nasıl çözülür? Çıktı şöyle görünmelidir: - -```latte -
                                                                                                                                      -
                                                                                                                                    • Apple
                                                                                                                                    • -
                                                                                                                                    • Banana
                                                                                                                                    • -
                                                                                                                                    - -
                                                                                                                                      -
                                                                                                                                    • PHP
                                                                                                                                    • -
                                                                                                                                    - -
                                                                                                                                      -
                                                                                                                                    • Green
                                                                                                                                    • -
                                                                                                                                    • Red
                                                                                                                                    • -
                                                                                                                                    • Blue
                                                                                                                                    • -
                                                                                                                                    -``` - -iterateWhile ile bu görevin ne kadar kolay ve zarif bir şekilde çözülebileceğini göstereceğiz: - -```latte -{foreach $items as $item} -
                                                                                                                                      - {iterateWhile} -
                                                                                                                                    • {$item->name}
                                                                                                                                    • - {/iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                                                                                                    -{/foreach} -``` - -`{foreach}` döngünün dış kısmını, yani her kategori için listelerin çizilmesini işaret ederken, `{iterateWhile}` etiketleri iç kısmı, yani tek tek öğeleri gösterir. -Bitiş etiketindeki koşul, mevcut ve bir sonraki [öğe |/tags#$iterator] aynı kategoriye ait olduğu sürece tekrarın devam edeceğini söyler (`$iterator->nextValue` bir sonraki öğedir). - -Koşul her zaman karşılanırsa, tüm öğeler iç döngüde çizilir: - -```latte -{foreach $items as $item} -
                                                                                                                                      - {iterateWhile} -
                                                                                                                                    • {$item->name} - {/iterateWhile true} -
                                                                                                                                    -{/foreach} -``` - -Sonuç şu şekilde görünecektir: - -```latte -
                                                                                                                                      -
                                                                                                                                    • Apple
                                                                                                                                    • -
                                                                                                                                    • Banana
                                                                                                                                    • -
                                                                                                                                    • PHP
                                                                                                                                    • -
                                                                                                                                    • Green
                                                                                                                                    • -
                                                                                                                                    • Red
                                                                                                                                    • -
                                                                                                                                    • Blue
                                                                                                                                    • -
                                                                                                                                    -``` - -iterateWhile'ın böyle bir kullanımı ne işe yarar? Bu eğitimin en başında gösterdiğimiz çözümden ne farkı var? Aradaki fark, tablo boşsa ve herhangi bir öğe içermiyorsa, boş olarak işlenmeyecektir `
                                                                                                                                      `. - - -`{iterateWhile}` Olmadan Çözüm .[#toc-solution-without-iteratewhile] --------------------------------------------------------------------- - -Aynı görevi şablon sistemlerinin tamamen temel yapılarıyla, örneğin Twig, Blade veya saf PHP ile çözmüş olsaydık, çözüm şuna benzer bir şey olurdu: - -```latte -{var $prevCatId = null} -{foreach $items as $item} - {if $item->catId !== $prevCatId} - {* kategori değişti *} - - {*
                                                                                                                                        ilk öğe değilse
                                                                                                                                      bir öncekini kapatıyoruz
                                                                                                                                        *} - {if $prevCatId !== null} -
                                                                                                                                      - {/if} - - {* we will open a new list *} -
                                                                                                                                        - - {do $prevCatId = $item->catId} - {/if} - -
                                                                                                                                      • {$item->name}
                                                                                                                                      • -{/foreach} - -{if $prevCatId !== null} - {* we close the last list *} -
                                                                                                                                      -{/if} -``` - -Ancak, bu kod anlaşılmaz ve sezgisel değildir. Açılış ve kapanış HTML etiketleri arasındaki bağlantı hiç açık değildir. Bir hata olup olmadığı ilk bakışta anlaşılmıyor. Ve `$prevCatId` gibi yardımcı değişkenler gerektirir. - -Buna karşılık, `{iterateWhile}` ile çözüm temiz, net, yardımcı değişkenlere ihtiyaç duymaz ve hatasızdır. - - -Kapanış Etiketindeki Koşul .[#toc-condition-in-the-closing-tag] ---------------------------------------------------------------- - -Açılış etiketinde bir koşul belirtirsek `{iterateWhile}`, davranış değişir: koşul (ve bir sonraki öğeye ilerleme) iç döngünün sonunda değil başında yürütülür. -Böylece, koşul olmadan `{iterateWhile}` her zaman girilirken, `{iterateWhile $cond}` yalnızca `$cond` koşulu karşılandığında girilir. Aynı zamanda, aşağıdaki eleman `$item` adresine yazılır. - -Bu, örneğin, her kategorideki ilk öğeyi farklı bir şekilde oluşturmak istediğiniz bir durumda kullanışlıdır: - -```latte -

                                                                                                                                      Apple

                                                                                                                                      -
                                                                                                                                        -
                                                                                                                                      • Banana
                                                                                                                                      • -
                                                                                                                                      - -

                                                                                                                                      PHP

                                                                                                                                      -
                                                                                                                                        -
                                                                                                                                      - -

                                                                                                                                      Green

                                                                                                                                      -
                                                                                                                                        -
                                                                                                                                      • Red
                                                                                                                                      • -
                                                                                                                                      • Blue
                                                                                                                                      • -
                                                                                                                                      -``` - -Orijinal kodu değiştirelim, `{iterateWhile}` iç döngüsünde ilk öğeyi ve ardından aynı kategoriden ek öğeleri çizelim: - -```latte -{foreach $items as $item} -

                                                                                                                                      {$item->name}

                                                                                                                                      -
                                                                                                                                        - {iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                                                                                                      • {$item->name}
                                                                                                                                      • - {/iterateWhile} -
                                                                                                                                      -{/foreach} -``` - - -İç İçe Döngüler .[#toc-nested-loops] ------------------------------------- - -Bir döngüde birden fazla iç döngü oluşturabilir ve hatta bunları iç içe yerleştirebiliriz. Bu şekilde, örneğin alt kategoriler gruplandırılabilir. - -Tabloda başka bir sütun olduğunu varsayalım `subCatId` ve her kategorinin ayrı bir kategoride olmasına ek olarak `
                                                                                                                                        `her bir alt kategori ayrı bir `
                                                                                                                                          `: - -```latte -{foreach $items as $item} -
                                                                                                                                            - {iterateWhile} -
                                                                                                                                              - {iterateWhile} -
                                                                                                                                            1. {$item->name} - {/iterateWhile $item->subCatId === $iterator->nextValue->subCatId} -
                                                                                                                                            - {/iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                                                                                                          -{/foreach} -``` - - -Filtre | yığın .[#toc-filter-batch] ------------------------------------ - -Doğrusal öğelerin gruplandırılması da bir filtre tarafından sağlanır `batch`, sabit sayıda öğeye sahip gruplar halinde: - -```latte -
                                                                                                                                            -{foreach ($items|batch:3) as $batch} - {foreach $batch as $item} -
                                                                                                                                          • {$item->name}
                                                                                                                                          • - {/foreach} -{/foreach} -
                                                                                                                                          -``` - -Aşağıdaki gibi iterateWhile ile değiştirilebilir: - -```latte -
                                                                                                                                            -{foreach $items as $item} - {iterateWhile} -
                                                                                                                                          • {$item->name}
                                                                                                                                          • - {/iterateWhile $iterator->counter0 % 3} -{/foreach} -
                                                                                                                                          -``` - -{{leftbar: /@left-menu}} diff --git a/latte/tr/cookbook/migration-from-php.texy b/latte/tr/cookbook/migration-from-php.texy index 73e7ff6eff..e63f8cb9c6 100644 --- a/latte/tr/cookbook/migration-from-php.texy +++ b/latte/tr/cookbook/migration-from-php.texy @@ -1,28 +1,28 @@ -PHP'den Latte'ye geçiş +PHP'den Latte'ye Geçiş ********************** .[perex] -Saf PHP ile yazılmış eski bir projeyi Latte'ye mi taşıyorsunuz? Geçişi kolaylaştırmak için bir aracımız var. [Çevrimiçi deneyin |https://php2latte.nette.org]. +Saf PHP ile yazılmış eski bir projeyi Latte'ye mi dönüştürüyorsunuz? Geçişi kolaylaştıracak bir aracımız var. [Çevrimiçi deneyin |https://fiddle.nette.org/php2latte/]. -Aracı [GitHub |https://github.com/nette/latte-tools] 'dan indirebilir veya Composer'ı kullanarak yükleyebilirsiniz: +Aracı [GitHub|https://github.com/nette/latte-tools]'dan indirebilir veya Composer kullanarak yükleyebilirsiniz: ```shell composer create-project latte/tools ``` -Dönüştürücü basit düzenli ifade ikamelerini kullanmaz, bunun yerine doğrudan PHP ayrıştırıcısını kullanır, böylece herhangi bir karmaşık sözdizimini işleyebilir. +Dönüştürücü, düzenli ifadeler kullanarak basit değiştirmeler kullanmaz, aksine doğrudan PHP ayrıştırıcısını kullanır, bu nedenle ne kadar karmaşık olursa olsun herhangi bir sözdizimiyle başa çıkabilir. -`php-to-latte.php` betiği PHP'den Latte'ye dönüştürmek için kullanılır: +PHP'den Latte'ye dönüştürmek için `php-to-latte.php` betiği kullanılır: ```shell php-to-latte.php input.php [output.latte] ``` -Örnek .[#toc-example] ---------------------- +Örnek +----- -Giriş dosyası şu şekilde görünebilir (PunBB forum kodunun bir parçasıdır): +Giriş dosyası şöyle görünebilir (bu, PunBB forum kodunun bir parçasıdır): ```php

                                                                                                                                          @@ -68,5 +68,3 @@ Bu şablonu oluşturur:
                                                                                                                            ``` - -{{leftbar: /@left-menu}} diff --git a/latte/tr/cookbook/migration-from-twig.texy b/latte/tr/cookbook/migration-from-twig.texy index b380e239f2..12e2bf8efb 100644 --- a/latte/tr/cookbook/migration-from-twig.texy +++ b/latte/tr/cookbook/migration-from-twig.texy @@ -1,16 +1,16 @@ -Twig'den Latte'ye geçiş +Twig'den Latte'ye Geçiş *********************** .[perex] -Twig'de yazılmış bir projeyi daha modern Latte'ye mi taşıyorsunuz? Geçişi kolaylaştırmak için bir aracımız var. [Çevrimiçi deneyin |https://twig2latte.nette.org]. +Twig ile yazılmış bir projeyi daha modern Latte'ye mi dönüştürüyorsunuz? Geçişi kolaylaştıracak bir aracımız var. [Çevrimiçi deneyin |https://fiddle.nette.org/twig2latte/]. -Aracı [GitHub |https://github.com/nette/latte-tools] 'dan indirebilir veya Composer'ı kullanarak yükleyebilirsiniz: +Aracı [GitHub|https://github.com/nette/latte-tools]'dan indirebilir veya Composer kullanarak yükleyebilirsiniz: ```shell composer create-project latte/tools ``` -Dönüştürücü basit düzenli ifade ikamelerini kullanmaz, bunun yerine doğrudan Twig ayrıştırıcısını kullanır, böylece herhangi bir karmaşık sözdizimini işleyebilir. +Dönüştürücü, düzenli ifadeler kullanarak basit değiştirmeler kullanmaz, aksine doğrudan Twig ayrıştırıcısını kullanır, bu nedenle ne kadar karmaşık olursa olsun herhangi bir sözdizimiyle başa çıkabilir. Twig'den Latte'ye dönüştürmek için `twig-to-latte.php` betiği kullanılır: @@ -19,20 +19,20 @@ twig-to-latte.php input.twig.html [output.latte] ``` -Dönüşüm .[#toc-conversion] --------------------------- +Dönüşüm +------- -Dönüşüm kesin olarak yapılamadığından, dönüşüm sonucun manuel olarak düzenlenmesini gerektirir. Twig nokta sözdizimini kullanır, burada `{{ a.b }}` `$a->b` anlamına gelebilir, `$a['b']` ya da `$a->getB()` gibi, derleme sırasında ayırt edilemeyen kodlar. Bu nedenle dönüştürücü her şeyi `$a->b` adresine dönüştürür. +Dönüşüm, sonucun manuel olarak düzenlenmesini varsayar, çünkü dönüşüm tek anlamlı bir şekilde gerçekleştirilemez. Twig, `{{ a.b }}`'nin `$a->b`, `$a['b']` veya `$a->getB()` anlamına gelebileceği nokta sözdizimini kullanır, bu da derleme sırasında ayırt edilemez. Bu nedenle dönüştürücü her şeyi `$a->b`'ye dönüştürür. -Bazı işlevlerin, filtrelerin veya etiketlerin Latte'de karşılığı yoktur veya biraz farklı davranabilir. +Bazı fonksiyonlar, filtreler veya etiketlerin Latte'de karşılığı yoktur veya biraz farklı davranabilirler. -Örnek .[#toc-example] ---------------------- +Örnek +----- -Giriş dosyası aşağıdaki gibi görünebilir: +Giriş dosyası şöyle görünebilir: -```latte +```twig {% use "blocks.twig" %} @@ -54,7 +54,7 @@ Giriş dosyası aşağıdaki gibi görünebilir: ``` -Latte'ye dönüştürdükten sonra bu şablonu elde ederiz: +Latte'ye dönüştürdükten sonra şu şablonu elde ederiz: ```latte {import 'blocks.latte'} @@ -77,5 +77,3 @@ Latte'ye dönüştürdükten sonra bu şablonu elde ederiz: ``` - -{{leftbar: /@left-menu}} diff --git a/latte/tr/cookbook/passing-variables.texy b/latte/tr/cookbook/passing-variables.texy new file mode 100644 index 0000000000..30d1277e88 --- /dev/null +++ b/latte/tr/cookbook/passing-variables.texy @@ -0,0 +1,158 @@ +Şablonlar Arasında Değişken Aktarma +*********************************** + +Bu kılavuz, `{include}`, `{import}`, `{embed}`, `{layout}`, `{sandbox}` ve diğerleri gibi çeşitli etiketler kullanarak Latte'de değişkenlerin şablonlar arasında nasıl aktarıldığını açıklayacaktır. Ayrıca `{block}` ve `{define}` etiketlerinde değişkenlerle nasıl çalışılacağını ve `{parameters}` etiketinin ne işe yaradığını da öğreneceksiniz. + + +Değişken Türleri +---------------- +Latte'deki değişkenleri, nasıl ve nerede tanımlandıklarına göre üç kategoriye ayırabiliriz: + +**Giriş Değişkenleri**, şablona dışarıdan aktarılanlardır, örneğin PHP betiğinden veya `{include}` gibi bir etiket kullanılarak. + +```php +$latte->render('template.latte', ['userName' => 'Jan', 'userAge' => 30]); +``` + +**Çevre Değişkenleri**, belirli bir etiketin yerinde var olan değişkenlerdir. Tüm giriş değişkenlerini ve `{var}`, `{default}` gibi etiketlerle veya `{foreach}` döngüsü içinde oluşturulan diğer değişkenleri içerirler. + +```latte +{foreach $users as $user} + {include 'userBox.latte', user: $user} +{/foreach} +``` + +**Açık Değişkenler**, doğrudan etiket içinde belirtilen ve hedef şablona gönderilenlerdir. + +```latte +{include 'userBox.latte', name: $user->name, age: $user->age} +``` + + +`{block}` +--------- +`{block}` etiketi, kalıtım şablonlarında özelleştirilebilen veya genişletilebilen yeniden kullanılabilir kod blokları tanımlamak için kullanılır. Bloktan önce tanımlanan çevre değişkenleri blok içinde kullanılabilir, ancak değişkenlerdeki herhangi bir değişiklik yalnızca o blok içinde geçerli olur. + +```latte +{var $foo = 'orijinal'} +{block example} + {var $foo = 'değiştirilmiş'} +{/block} + +{$foo} // çıktılar: orijinal +``` + + +`{define}` +---------- +`{define}` etiketi, `{include}` ile çağrıldıktan sonra oluşturulan bloklar oluşturmak için kullanılır. Bu bloklar içinde kullanılabilen değişkenler, tanımda parametrelerin belirtilip belirtilmediğine bağlıdır. Eğer belirtilmişse, yalnızca bu parametrelere erişimleri vardır. Eğer belirtilmemişse, blokların tanımlandığı şablonun tüm giriş değişkenlerine erişimleri vardır. + +```latte +{define hello} + {* şablonun tüm giriş değişkenlerine erişimi vardır *} +{/define} + +{define hello $name} + {* yalnızca $name parametresine erişimi vardır *} +{/define} +``` + + +`{parameters}` +-------------- +`{parameters}` etiketi, şablonun başında beklenen giriş değişkenlerini açıkça bildirmek için kullanılır. Bu şekilde, beklenen değişkenleri ve veri türlerini kolayca belgeleyebilirsiniz. Ayrıca varsayılan değerleri de tanımlayabilirsiniz. + +```latte +{parameters int $age, string $name = 'bilinmiyor'} +

                                                                                                                            Yaş: {$age}, İsim: {$name}

                                                                                                                            +``` + + +`{include file}` +---------------- +`{include file}` etiketi, tüm bir şablonu dahil etmek için kullanılır. Bu şablona, etiketin kullanıldığı şablonun giriş değişkenleri ve içinde açıkça tanımlanan değişkenler aktarılır. Ancak hedef şablon, `{parameters}` kullanarak kapsamı sınırlayabilir. + +```latte +{include 'profile.latte', userId: $user->id} +``` + + +`{include block}` +----------------- +Aynı şablonda tanımlanmış bir bloğu dahil ettiğinizde, tüm çevre ve açıkça tanımlanmış değişkenler ona aktarılır: + +```latte +{define blockName} +

                                                                                                                            İsim: {$name}, Yaş: {$age}

                                                                                                                            +{/define} + +{var $name = 'Jan', $age = 30} +{include blockName} +``` + +Bu örnekte, `$name` ve `$age` değişkenleri `blockName` bloğuna aktarılır. `{include parent}` de aynı şekilde davranır. + +Başka bir şablondan bir blok dahil ederken, yalnızca giriş değişkenleri ve açıkça tanımlananlar aktarılır. Çevre değişkenleri otomatik olarak kullanılamaz. + +```latte +{include blockInOtherTemplate, name: $name, age: $age} +``` + + +`{layout}` veya `{extends}` +--------------------------- +Bu etiketler, alt şablonun giriş değişkenlerinin ve ayrıca bloklardan önceki kodda oluşturulan değişkenlerin aktarıldığı düzeni tanımlar: + +```latte +{layout 'layout.latte'} +{var $seo = 'index, follow'} +``` + +`layout.latte` şablonu: + +```latte + + + +``` + + +`{embed}` +--------- +`{embed}` etiketi `{include}` etiketine benzer, ancak blokları şablona dahil etmeyi sağlar. `{include}`'den farklı olarak, yalnızca açıkça bildirilen değişkenler aktarılır: + +```latte +{embed 'menu.latte', items: $menuItems} +{/embed} +``` + +Bu örnekte, `menu.latte` şablonunun yalnızca `$items` değişkenine erişimi vardır. + +Tersine, `{embed}` içindeki bloklarda tüm çevre değişkenlerine erişim vardır: + +```latte +{var $name = 'Jan'} +{embed 'menu.latte', items: $menuItems} + {block foo} + {$nam} + {/block} +{/embed} +``` + + +`{import}` +---------- +`{import}` etiketi, diğer şablonlardan blokları yüklemek için kullanılır. Hem giriş hem de açıkça bildirilen değişkenler içe aktarılan bloklara aktarılır. + +```latte +{import 'buttons.latte'} +``` + + +`{sandbox}` +----------- +`{sandbox}` etiketi, güvenli işleme için şablonu izole eder. Değişkenler yalnızca açıkça aktarılır. + +```latte +{sandbox 'secure.latte', data: $secureData} +``` diff --git a/latte/tr/cookbook/slim-framework.texy b/latte/tr/cookbook/slim-framework.texy index 25492d91fa..f84e21afd4 100644 --- a/latte/tr/cookbook/slim-framework.texy +++ b/latte/tr/cookbook/slim-framework.texy @@ -1,43 +1,43 @@ -Latte'yi Slim 4 ile kullanma -**************************** +Slim 4 ile Latte Kullanımı +************************** .[perex] -"Daniel Opitz":https://odan.github.io/2022/04/06/slim4-latte.html tarafından yazılan bu makalede Latte'nin Slim Framework ile nasıl kullanılacağı anlatılmaktadır. +Yazarı "Daniel Opitz":https://odan.github.io/2022/04/06/slim4-latte.html olan bu makale, Latte'nin Slim Framework ile kullanımını açıklamaktadır. -Önce "Slim Framework'ü yükleyin":https://odan.github.io/2019/11/05/slim4-tutorial.html ve ardından Composer'ı kullanarak Latte'yi yükleyin:https://odan.github.io/2019/11/05/slim4-tutorial.html: +Önce "Slim Framework'ü yükleyin":https://odan.github.io/2019/11/05/slim4-tutorial.html ve ardından Latte'yi Composer kullanarak yükleyin: ```shell composer require latte/latte ``` -Konfigürasyon .[#toc-configuration] ------------------------------------ +Yapılandırma +------------ -Proje kök dizininizde yeni bir `templates` dizini oluşturun. Tüm şablonlar daha sonra buraya yerleştirilecektir. +Projenin kök dizininde yeni bir `templates` dizini oluşturun. Tüm şablonlar daha sonra buraya yerleştirilecektir. -`config/defaults.php` dosyanıza yeni bir `template` yapılandırma anahtarı ekleyin: +`config/defaults.php` dosyasına yeni bir yapılandırma anahtarı `template` ekleyin: ```php $settings['template'] = __DIR__ . '/../templates'; ``` -Latte, şablonları yerel PHP koduna derler ve bunları diskteki bir önbellekte saklar. Böylece yerel PHP ile yazılmış kadar hızlıdırlar. +Latte, şablonları yerel PHP koduna derler ve bunları diskte bir önbellekte saklar. Bu nedenle, yerel PHP dilinde yazılmış olsalardı olacağı kadar hızlıdırlar. -`config/defaults.php` dosyanıza yeni bir `template_temp` yapılandırma anahtarı ekleyin: `{project}/tmp/templates` dizininin var olduğundan ve okuma ve yazma erişim izinlerine sahip olduğundan emin olun. +`config/defaults.php` dosyasına yeni bir yapılandırma anahtarı `template_temp` ekleyin: `{project}/tmp/templates` dizininin var olduğundan ve okuma ve yazma izinlerine sahip olduğundan emin olun. ```php $settings['template_temp'] = __DIR__ . '/../tmp/templates'; ``` -Latte, şablonu her değiştirdiğinizde önbelleği otomatik olarak yeniden oluşturur; bu, biraz performans tasarrufu sağlamak için üretim ortamında kapatılabilir: +Latte, şablonda her değişiklik yapıldığında önbelleği otomatik olarak yeniden oluşturur, bu da üretim ortamında kapatılarak biraz performans tasarrufu sağlanabilir: ```php // üretim ortamında false olarak değiştirin $settings['template_auto_refresh'] = true; ``` -Ardından, `Latte\Engine` sınıfı için bir DI konteyner tanımları ekleyin. +Ardından, `Latte\Engine` sınıfı için DI konteyner tanımını ekleyin. ```php +
                                                                                                                              {foreach $items as $item}
                                                                                                                            • {$item|capitalize}
                                                                                                                            • {/foreach}
                                                                                                                            ``` -Her şey doğru yapılandırılmışsa aşağıdaki çıktıyı görmeniz gerekir: +Her şey doğru yapılandırılmışsa, aşağıdaki çıktı görüntülenmelidir: ```latte One @@ -155,4 +155,3 @@ Three ``` {{priority: -1}} -{{leftbar: /@left-menu}} diff --git a/latte/tr/creating-extension.texy b/latte/tr/creating-extension.texy deleted file mode 100644 index 3d10d0e2c6..0000000000 --- a/latte/tr/creating-extension.texy +++ /dev/null @@ -1,579 +0,0 @@ -Uzantı Oluşturma -**************** - -.[perex]{data-version:3.0} -Uzantı, özel etiketler, filtreler, işlevler, sağlayıcılar vb. tanımlayabilen yeniden kullanılabilir bir sınıftır. - -Latte özelleştirmelerimizi farklı projelerde yeniden kullanmak veya başkalarıyla paylaşmak istediğimizde uzantılar oluştururuz. -Her web projesi için proje şablonlarında kullanmak istediğiniz tüm özel etiketleri ve filtreleri içerecek bir uzantı oluşturmak da yararlıdır. - - -Uzatma Sınıfı .[#toc-extension-class] -===================================== - -Uzantı, [api:Latte\Extension] adresinden miras alınan bir sınıftır. `addExtension()` kullanılarak (veya [yapılandırma dosyası |application:configuration#Latte] aracılığıyla) Latte'ye kaydedilir: - -```php -$latte = new Latte\Engine; -$latte->addExtension(new MyLatteExtension); -``` - -Birden fazla uzantı kaydederseniz ve bunlar aynı adlı etiketleri, filtreleri veya işlevleri tanımlarsa, son eklenen uzantı kazanır. Bu aynı zamanda uzantılarınızın yerel etiketleri/filtreleri/işlevleri geçersiz kılabileceği anlamına gelir. - -Bir sınıfta değişiklik yaptığınızda ve otomatik yenileme kapatılmadığında, Latte şablonlarınızı otomatik olarak yeniden derleyecektir. - -Bir sınıf aşağıdaki yöntemlerden herhangi birini uygulayabilir: - -```php -abstract class Extension -{ - /** - * Initializes before template is compiler. - */ - public function beforeCompile(Engine $engine): void; - - /** - * Returns a list of parsers for Latte tags. - * @return array - */ - public function getTags(): array; - - /** - * Returns a list of compiler passes. - * @return array - */ - public function getPasses(): array; - - /** - * Returns a list of |filters. - * @return array - */ - public function getFilters(): array; - - /** - * Returns a list of functions used in templates. - * @return array - */ - public function getFunctions(): array; - - /** - * Returns a list of providers. - * @return array - */ - public function getProviders(): array; - - /** - * Returns a value to distinguish multiple versions of the template. - */ - public function getCacheKey(Engine $engine): mixed; - - /** - * Initializes before template is rendered. - */ - public function beforeRender(Template $template): void; -} -``` - -Uzantının neye benzediğine dair bir fikir edinmek için yerleşik "CoreExtension:https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php"a bir göz atın. - - -beforeCompile(Latte\Engine $engine): void .[method] ---------------------------------------------------- - -Şablon derlenmeden önce çağrılır. Bu yöntem, örneğin derlemeyle ilgili başlatmalar için kullanılabilir. - - -getTags(): array .[method] --------------------------- - -Şablon derlendiğinde çağrılır. [Etiket ayrıştırma işlevleri |#Tag Parsing Function] olan *etiket adı => çağrılabilir* bir ilişkisel dizi döndürür. - -```php -public function getTags(): array -{ - return [ - 'foo' => [FooNode::class, 'create'], - 'bar' => [BarNode::class, 'create'], - 'n:baz' => [NBazNode::class, 'create'], - // ... - ]; -} -``` - -`n:baz` etiketi saf bir n:özniteliği temsil eder, yani yalnızca bir öznitelik olarak yazılabilen bir etikettir. - -`foo` ve `bar` etiketleri söz konusu olduğunda, Latte bunların çift olup olmadığını otomatik olarak tanıyacaktır ve eğer öyleyse, `n:inner-foo` ve `n:tag-foo` öneklerine sahip varyantlar da dahil olmak üzere n:attributes kullanılarak otomatik olarak yazılabilirler. - -Bu tür n:özniteliklerin yürütülme sırası `getTags()` tarafından döndürülen dizideki sıralarına göre belirlenir. Bu nedenle, nitelikler HTML etiketinde ters sırada listelenmiş olsa bile `n:foo` her zaman `n:bar` adresinden önce yürütülür. `
                                                                                                                            `. - -Birden fazla uzantıda n:niteliklerinin sırasını belirlemeniz gerekiyorsa, `order()` yardımcı yöntemini kullanın; burada `before` xor `after` parametresi hangi etiketlerin etiketten önce veya sonra sıralanacağını belirler. - -```php -public function getTags(): array -{ - return [ - 'foo' => self::order([FooNode::class, 'create'], before: 'bar')] - 'bar' => self::order([BarNode::class, 'create'], after: ['block', 'snippet'])] - ]; -} -``` - - -getPasses(): array .[method] ----------------------------- - -Şablon derlendiğinde çağrılır. AST'yi dolaşan ve değiştiren sözde [derleyici geçişlerini |#compiler passes] temsil eden işlevler olan *name pass => callable* ilişkisel bir dizi döndürür. - -Yine `order()` yardımcı yöntemi kullanılabilir. `before` veya `after` parametrelerinin değeri before/after all anlamında `*` olabilir. - -```php -public function getPasses(): array -{ - return [ - 'optimize' => [Passes::class, 'optimizePass'], - 'sandbox' => self::order([$this, 'sandboxPass'], before: '*'), - // ... - ]; -} -``` - - -beforeRender(Latte\Engine $engine): void .[method] --------------------------------------------------- - -Her şablon render işleminden önce çağrılır. Yöntem, örneğin oluşturma sırasında kullanılan değişkenleri başlatmak için kullanılabilir. - - -getFilters(): array .[method] ------------------------------ - -Şablon işlenmeden önce çağrılır. [Filtreleri |extending-latte#filters] ilişkisel bir dizi olarak döndürür *filter name => callable*. - -```php -public function getFilters(): array -{ - return [ - 'batch' => [$this, 'batchFilter'], - 'trim' => [$this, 'trimFilter'], - // ... - ]; -} -``` - - -getFunctions(): array .[method] -------------------------------- - -Şablon işlenmeden önce çağrılır. [İşlevleri |extending-latte#functions] bir ilişkisel dizi olarak döndürür *işlev adı => çağrılabilir*. - -```php -public function getFunctions(): array -{ - return [ - 'clamp' => [$this, 'clampFunction'], - 'divisibleBy' => [$this, 'divisibleByFunction'], - // ... - ]; -} -``` - - -getProviders(): array .[method] -------------------------------- - -Şablon işlenmeden önce çağrılır. Genellikle çalışma zamanında etiketleri kullanan nesneler olan sağlayıcılardan oluşan bir dizi döndürür. Bunlara `$this->global->...` üzerinden erişilir. - -```php -public function getProviders(): array -{ - return [ - 'myFoo' => $this->foo, - 'myBar' => $this->bar, - // ... - ]; -} -``` - - -getCacheKey(Latte\Engine $engine): mixed .[method] --------------------------------------------------- - -Şablon oluşturulmadan önce çağrılır. Dönüş değeri, hash'i derlenen şablon dosyasının adında bulunan anahtarın bir parçası haline gelir. Böylece, farklı geri dönüş değerleri için Latte farklı önbellek dosyaları oluşturacaktır. - - -Latte Nasıl Çalışır? .[#toc-how-does-latte-work] -================================================ - -Özel etiketlerin veya derleyici geçişlerinin nasıl tanımlanacağını anlamak için, Latte'nin kaputun altında nasıl çalıştığını anlamak önemlidir. - -Latte'de şablon derleme basitçe şu şekilde çalışır: - -- İlk olarak, **lexer** şablon kaynak kodunu daha kolay işlenebilmesi için küçük parçalara (token) ayırır -- Daha sonra, **ayrıştırıcı** belirteç akışını anlamlı bir düğüm ağacına (Soyut Sözdizimi Ağacı, AST) dönüştürür -- Son olarak, derleyici AST'den şablonu işleyen ve önbelleğe alan bir PHP sınıfı **oluşturur**. - -Aslında, derleme biraz daha karmaşıktır. Latte **iki** sözlükleyici ve ayrıştırıcıya sahiptir: biri HTML şablonu için diğeri de etiketlerin içindeki PHP benzeri kod için. Ayrıca, ayrıştırma tokenizasyondan sonra çalışmaz, ancak lexer ve parser iki "thread" içinde paralel olarak çalışır ve koordine olur. Bu roket bilimi :-) - -Ayrıca, tüm etiketlerin kendi ayrıştırma rutinleri vardır. Ayrıştırıcı bir etiketle karşılaştığında, ayrıştırma işlevini çağırır ( [Extension::getTags() |#getTags] döndürür). -Görevleri, etiket argümanlarını ve eşleştirilmiş etiketler söz konusu olduğunda iç içeriği ayrıştırmaktır. AST'nin bir parçası haline gelen bir *node* döndürür. Ayrıntılar için [Etiket ayrıştırma işlevine |#Tag parsing function] bakın. - -Ayrıştırıcı işini bitirdiğinde, şablonu temsil eden eksiksiz bir AST'ye sahip oluruz. Kök düğüm `Latte\Compiler\Nodes\TemplateNode`'dur. Ağacın içindeki tek tek düğümler yalnızca etiketleri değil, aynı zamanda HTML öğelerini, bunların niteliklerini, etiketlerin içinde kullanılan ifadeleri vb. temsil eder. - -Bundan sonra, AST'yi değiştiren fonksiyonlar ( [Extension::getPasses() |#getPasses] tarafından döndürülen) olan [Derleyici geçişleri |#Compiler passes] devreye girer. - -Şablon içeriğinin yüklenmesinden ayrıştırılmasına ve sonuç dosyasının oluşturulmasına kadar tüm süreç, deneyebileceğiniz ve ara sonuçları dökebileceğiniz bu kodla sıralanabilir: - -```php -$latte = new Latte\Engine; -$source = $latte->getLoader()->getContent($file); -$ast = $latte->parse($source); -$latte->applyPasses($ast); -$code = $latte->generate($ast, $file); -``` - - -AST örneği .[#toc-example-of-ast] ---------------------------------- - -AST hakkında daha iyi bir fikir edinmek için bir örnek ekliyoruz. Bu kaynak şablonudur: - -```latte -{foreach $category->getItems() as $item} -
                                                                                                                          1. {$item->name|upper}
                                                                                                                          2. - {else} - no items found -{/foreach} -``` - -Bu da onun AST biçimindeki temsilidir: - -/--pre -Latte\Compiler\Nodes\TemplateNode( - Latte\Compiler\Nodes\FragmentNode( - - Latte\Essential\Nodes\ForeachNode( - expression: Latte\Compiler\Nodes\Php\Expression\MethodCallNode( - object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$category') - name: Latte\Compiler\Nodes\Php\IdentifierNode('getItems') - ) - value: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') - content: Latte\Compiler\Nodes\FragmentNode( - - Latte\Compiler\Nodes\TextNode(' ') - - Latte\Compiler\Nodes\Html\ElementNode('li')( - content: Latte\Essential\Nodes\PrintNode( - expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode( - object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') - name: Latte\Compiler\Nodes\Php\IdentifierNode('name') - ) - modifier: Latte\Compiler\Nodes\Php\ModifierNode( - filters: - - Latte\Compiler\Nodes\Php\FilterNode('upper') - ) - ) - ) - ) - else: Latte\Compiler\Nodes\FragmentNode( - - Latte\Compiler\Nodes\TextNode('no items found') - ) - ) - ) -) -\-- - - -Özel Etiketler .[#toc-custom-tags] -================================== - -Yeni bir etiket tanımlamak için üç adım gereklidir: - -- etiket ayrıştırma [işlevini |#tag parsing function] tanımlama (etiketi bir düğüme ayrıştırmaktan sorumlu) -- bir düğüm sınıfı oluşturma ( [PHP kodu üretmekten |#generating PHP code] ve [AST geçişinden |#AST traversing] sorumlu) -- [Extension::getTags() |#getTags] kullanarak etiketi kaydetme - - -Etiket Ayrıştırma İşlevi .[#toc-tag-parsing-function] ------------------------------------------------------ - -Etiketlerin ayrıştırılması kendi ayrıştırma fonksiyonu ( [Extension::getTags() |#getTags] tarafından döndürülen) tarafından gerçekleştirilir. Görevi, etiket içindeki tüm argümanları ayrıştırmak ve kontrol etmektir (bunu yapmak için TagParser kullanır). -Ayrıca, etiket bir çift ise, TemplateParser'dan iç içeriği ayrıştırmasını ve döndürmesini isteyecektir. -Fonksiyon, genellikle `Latte\Compiler\Nodes\StatementNode`'un bir çocuğu olan bir düğüm oluşturur ve döndürür ve bu AST'nin bir parçası olur. - -Şimdi yapacağımız gibi her düğüm için bir sınıf oluşturacağız ve ayrıştırma işlevini statik bir fabrika olarak zarif bir şekilde içine yerleştireceğiz. Örnek olarak, tanıdık `{foreach}` etiketini oluşturmayı deneyelim: - -```php -use Latte\Compiler\Nodes\StatementNode; - -class ForeachNode extends StatementNode -{ - // a parsing function that just creates a node for now - public static function create(Latte\Compiler\Tag $tag): self - { - $node = new self; - return $node; - } - - public function print(Latte\Compiler\PrintContext $context): string - { - // kod daha sonra eklenecektir - } - - public function &getIterator(): \Generator - { - // kod daha sonra eklenecektir - } -} -``` - -Ayrıştırma işlevi `create()`, etiketle ilgili temel bilgileri (klasik bir etiket mi yoksa n:attribute mu olduğu, hangi satırda olduğu, vb) taşıyan ve esas olarak `$tag->parser` içindeki [api:Latte\Compiler\TagParser] adresine erişen bir [api:Latte\Compiler\Tag] nesnesi aktarır. - -Etiketin argümanlara sahip olması gerekiyorsa, `$tag->expectArguments()` adresini çağırarak bunların varlığını kontrol edin. `$tag->parser` nesnesinin yöntemleri bunları ayrıştırmak için kullanılabilir: - -- PHP benzeri bir ifade için `parseExpression(): ExpressionNode` (örn. `10 + 3`) -- `parseUnquotedStringOrExpression(): ExpressionNode` bir ifade veya tırnaksız dize için -- `parseArguments(): ArrayNode` dizinin içeriği (örn. `10, true, foo => bar`) -- `parseModifier(): ModifierNode` bir değiştirici için (örn. `|upper|truncate:10`) -- typehint için `parseType(): expressionNode` (örn. `int|string` veya `Foo\Bar[]`) - -ve doğrudan jetonlarla çalışan düşük seviyeli bir [api:Latte\Compiler\TokenStream]: - -- `$tag->parser->stream->consume(...): Token` -- `$tag->parser->stream->tryConsume(...): ?Token` - -Latte, PHP sözdizimini küçük yollarla genişletir, örneğin değiştiriciler, kısaltılmış üçlü operatörler ekleyerek veya basit alfanümerik dizelerin tırnak işaretleri olmadan yazılmasına izin vererek. Bu nedenle PHP yerine *PHP benzeri* terimini kullanıyoruz. Bu nedenle, örneğin `parseExpression()` yöntemi `foo` adresini `'foo'` olarak ayrıştırır. -Ayrıca, *unquoted-string* tırnak içine alınması gerekmeyen, ancak aynı zamanda alfanümerik olması da gerekmeyen bir dizginin özel bir durumudur. Örneğin, `{include ../file.latte}` etiketindeki bir dosyanın yoludur. Bunu ayrıştırmak için `parseUnquotedStringOrExpression()` yöntemi kullanılır. - -.[note] -Latte'nin bir parçası olan düğüm sınıflarını incelemek, ayrıştırma sürecinin tüm incelikli ayrıntılarını öğrenmenin en iyi yoludur. - -`{foreach}` etiketine geri dönelim. İçinde, aşağıdaki gibi ayrıştırdığımız `expression + 'as' + second expression` biçiminde argümanlar bekliyoruz: - -```php -use Latte\Compiler\Nodes\StatementNode; -use Latte\Compiler\Nodes\Php\ExpressionNode; -use Latte\Compiler\Nodes\AreaNode; - -class ForeachNode extends StatementNode -{ - public ExpressionNode $expression; - public ExpressionNode $value; - - public static function create(Latte\Compiler\Tag $tag): self - { - $tag->expectArguments(); - $node = new self; - $node->expression = $tag->parser->parseExpression(); - $tag->parser->stream->consume('as'); - $node->value = $parser->parseExpression(); - return $node; - } -} -``` - -`$expression` ve `$value` değişkenlerine yazdığımız ifadeler alt düğümleri temsil etmektedir. - -.[tip] -Alt düğümleri olan değişkenleri **public** olarak tanımlayın, böylece gerekirse [sonraki işlem adımlarında |#Compiler Passes] değiştirilebilirler. Ayrıca, bunları [çaprazlama |#AST Traversing] için **ulaşılabilir** kılmak da gereklidir. - -Bizimki gibi eşleştirilmiş etiketler için, yöntem TemplateParser'ın etiketin iç içeriğini ayrıştırmasına da izin vermelidir. Bu işlem, ''[iç içerik, son etiket]'' çiftini döndüren `yield` tarafından gerçekleştirilir. İç içeriği `$node->content` değişkeninde saklıyoruz. - -```php -public AreaNode $content; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $endTag] = yield; - return $node; -} -``` - -`yield` anahtar sözcüğü `create()` yönteminin sonlanmasına neden olarak kontrolü TemplateParser'a geri döndürür ve TemplateParser end etiketine ulaşana kadar içeriği ayrıştırmaya devam eder. Daha sonra kontrolü, kaldığı yerden devam eden `create()` yöntemine geri aktarır. `yield` , yöntemi kullanıldığında otomatik olarak `Generator` döndürülür. - -Ayrıca `yield` adresine, son etiketten önce gelirlerse ayrıştırmayı durdurmak istediğiniz bir dizi etiket adı da iletebilirsiniz. Bu bize şu yöntemi uygulamamıza yardımcı olur `{foreach}...{else}...{/foreach}` yapı. Eğer `{else}` oluşursa, ondan sonraki içeriği `$node->elseContent` olarak ayrıştırırız: - -```php -public AreaNode $content; -public ?AreaNode $elseContent = null; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $nextTag] = yield ['else']; - if ($nextTag?->name === 'else') { - [$node->elseContent] = yield; - } - - return $node; -} -``` - -Dönen düğüm etiket ayrıştırmasını tamamlar. - - -PHP Kodu Oluşturma .[#toc-generating-php-code] ----------------------------------------------- - -Her düğüm `print()` yöntemini uygulamalıdır. Şablonun verilen bölümünü işleyen PHP kodunu döndürür (çalışma zamanı kodu). Parametre olarak bir [api:Latte\Compiler\PrintContext] nesnesi aktarılır; bu nesne, elde edilen kodun montajını basitleştiren kullanışlı bir `format()` yöntemine sahiptir. - -`format(string $mask, ...$args)` yöntemi maskede aşağıdaki yer tutucuları kabul eder: -- `%node` düğüm yazdırır -- `%dump` değeri PHP'ye aktarır -- `%raw` metni herhangi bir dönüştürme yapmadan doğrudan ekler -- `%args` fonksiyon çağrısına argüman olarak ArrayNode yazdırır -- `%line` satır numarasıyla birlikte bir yorum yazdırır -- `%escape(...)` içerikten kaçar -- `%modify(...)` bir değiştirici uygular -- `%modifyContent(...)` bloklara bir değiştirici uygular - - -`print()` fonksiyonumuz aşağıdaki gibi görünebilir (basitlik için `else` dalını ihmal ediyoruz): - -```php -public function print(Latte\Compiler\PrintContext $context): string -{ - return $context->format( - <<<'XX' - foreach (%node as %node) %line { - %node - } - - XX, - $this->expression, - $this->value, - $this->position, - $this->content, - ); -} -``` - -`$this->position` değişkeni [api:Latte\Compiler\Node] sınıfı tarafından zaten tanımlanmıştır ve ayrıştırıcı tarafından ayarlanır. Etiketin kaynak koddaki konumunu satır ve sütun numarası şeklinde gösteren bir [api:Latte\Compiler\Position] nesnesi içerir. - -Çalışma zamanı kodu yardımcı değişkenler kullanabilir. Şablonun kendisi tarafından kullanılan değişkenlerle çakışmayı önlemek için, bunların önüne `$ʟ__` karakterlerinin eklenmesi gelenekseldir. - -Ayrıca, çalışma zamanında [Extension::getProviders() |#getProviders] yöntemi kullanılarak şablona sağlayıcılar şeklinde aktarılan rastgele değerler de kullanabilir. Bunlara `$this->global->...` adresini kullanarak erişir. - - -AST Çaprazlama .[#toc-ast-traversing] -------------------------------------- - -AST ağacını derinlemesine dolaşmak için `getIterator()` yöntemini uygulamak gerekir. Bu, alt düğümlere erişim sağlayacaktır: - -```php -public function &getIterator(): \Generator -{ - yield $this->expression; - yield $this->value; - yield $this->content; - if ($this->elseContent) { - yield $this->elseContent; - } -} -``` - -`getIterator()` adresinin bir referans döndürdüğüne dikkat edin. Bu, düğüm ziyaretçilerinin tek tek düğümleri diğer düğümlerle değiştirmesini sağlar. - -.[warning] -Bir düğümün alt düğümleri varsa, bu yöntemi uygulamak ve tüm alt düğümleri kullanılabilir hale getirmek gerekir. Aksi takdirde bir güvenlik açığı oluşabilir. Örneğin, sandbox modu alt düğümleri kontrol edemeyecek ve izin verilmeyen yapıların içlerinde çağrılmamasını sağlayamayacaktır. - -Alt düğümleri olmasa bile `yield` anahtar sözcüğünün yöntem gövdesinde bulunması gerektiğinden, aşağıdaki gibi yazın: - -```php -public function &getIterator(): \Generator -{ - if (false) { - yield; - } -} -``` - - -Derleyici Geçişleri .[#toc-compiler-passes] -=========================================== - -Derleyici Geçişleri, AST'leri değiştiren veya içlerindeki bilgileri toplayan işlevlerdir. [Extension::getPasses() |#getPasses] yöntemi tarafından döndürülürler. - - -Düğüm Çaprazlayıcı .[#toc-node-traverser] ------------------------------------------ - -AST ile çalışmanın en yaygın yolu bir [api:Latte\Compiler\NodeTraverser] kullanmaktır: - -```php -use Latte\Compiler\Node; -use Latte\Compiler\NodeTraverser; - -$ast = (new NodeTraverser)->traverse( - $ast, - enter: fn(Node $node) => ..., - leave: fn(Node $node) => ..., -); -``` - -Enter* fonksiyonu (yani ziyaretçi) bir düğümle ilk karşılaşıldığında, alt düğümleri işlenmeden önce çağrılır. Tüm alt düğümler ziyaret edildikten sonra *leave* fonksiyonu çağrılır. -Yaygın bir model, *enter* işlevinin bazı bilgileri toplamak için kullanılması ve ardından *leave* işlevinin buna dayalı değişiklikler yapmasıdır. leave* çağrıldığında, düğüm içindeki tüm kodlar zaten ziyaret edilmiş ve gerekli bilgiler toplanmış olacaktır. - -AST nasıl değiştirilir? En kolay yol basitçe düğümlerin özelliklerini değiştirmektir. İkinci yol ise yeni bir düğüm döndürerek düğümü tamamen değiştirmektir. Örnek: Aşağıdaki kod AST'deki tüm tamsayıları string olarak değiştirecektir (örneğin 42, `'42'` olarak değiştirilecektir). - -```php -use Latte\Compiler\Nodes\Php; - -$ast = (new NodeTraverser)->traverse( - $ast, - leave: function (Node $node) { - if ($node instanceof Php\Scalar\IntegerNode) { - return new Php\Scalar\StringNode((string) $node->value); - } - }, -); -``` - -Bir AST kolayca binlerce düğüm içerebilir ve hepsinin üzerinden geçmek yavaş olabilir. Bazı durumlarda, tam bir çaprazlamadan kaçınmak mümkündür. - -Bir ağaçta tüm `Html\ElementNode` adreslerini arıyorsanız, `Php\ExpressionNode` adresini gördükten sonra tüm alt düğümlerini de kontrol etmenin bir anlamı olmadığını bilirsiniz, çünkü HTML ifadelerin içinde olamaz. Bu durumda, traverser'a sınıf düğümüne geri dönmemesi talimatını verebilirsiniz: - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Php\ExpressionNode) { - return NodeTraverser::DontTraverseChildren; - } - // ... - }, -); -``` - -Yalnızca belirli bir düğümü arıyorsanız, bulduktan sonra geçişi tamamen iptal etmek de mümkündür. - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Nodes\ParametersNode) { - return NodeTraverser::StopTraversal; - } - // ... - }, -); -``` - - -Düğüm Yardımcıları .[#toc-node-helpers] ---------------------------------------- - -[api:Latte\Compiler\NodeHelpers] sınıfı, belirli bir geri çağrıyı vb. karşılayan AST düğümlerini bulabilen bazı yöntemler sağlar. Birkaç örnek gösterilmiştir: - -```php -Latte\Compiler\NodeHelpers kullanın; - -// tüm HTML öğesi düğümlerini bulur -$elements = NodeHelpers::find($ast, fn(Node $node) => $node instanceof Nodes\Html\ElementNode); - -// ilk metin düğümünü bulur -$text = NodeHelpers::findFirst($ast, fn(Node $node) => $node instanceof Nodes\TextNode); - -// PHP değer düğümünü gerçek değere dönüştürür -$value = NodeHelpers::toValue($node); - -// statik metinsel düğümü dizeye dönüştürür -$text = NodeHelpers::toText($node); -``` diff --git a/latte/tr/custom-filters.texy b/latte/tr/custom-filters.texy new file mode 100644 index 0000000000..d8751da017 --- /dev/null +++ b/latte/tr/custom-filters.texy @@ -0,0 +1,231 @@ +Özel Filtreler Oluşturma +************************ + +.[perex] +Filtreler, verileri doğrudan Latte şablonlarında biçimlendirmek ve değiştirmek için güçlü araçlardır. Değişkenleri veya ifade sonuçlarını istenen çıktı biçimine dönüştürmek için boru (`|`) sembolüyle temiz bir sözdizimi sunarlar. + + +Filtreler Nedir? +================ + +Latte'deki filtreler esasen **giriş değerini çıktı değerine dönüştürmek için özel olarak tasarlanmış PHP fonksiyonlarıdır**. Şablon ifadeleri (`{...}`) içinde boru (`|`) gösterimi kullanılarak uygulanırlar. + +**Kolaylık:** Filtreler, yaygın biçimlendirme görevlerini (tarihleri biçimlendirme, büyük/küçük harf değiştirme, kısaltma gibi) veya veri manipülasyonunu yeniden kullanılabilir birimlerde kapsüllemenizi sağlar. Şablonlarınızda karmaşık PHP kodunu tekrarlamak yerine, basitçe bir filtre uygulayabilirsiniz: +```latte +{* Kısaltma için karmaşık PHP yerine: *} +{$article->text|truncate:100} + +{* Tarih biçimlendirme kodu yerine: *} +{$event->startTime|date:'Y-m-d H:i'} + +{* Birden fazla dönüşüm uygulama: *} +{$product->name|lower|capitalize} +``` + +**Okunabilirlik:** Filtreleri kullanmak, dönüşüm mantığı filtre tanımına taşındığı için şablonları daha temiz ve sunuma daha odaklı hale getirir. + +**Bağlama Duyarlılık:** Latte'deki filtrelerin önemli bir avantajı, [bağlama duyarlı |#Bağlamsal Filtreler] olma yetenekleridir. Bu, bir filtrenin çalıştığı içerik türünü (HTML, JavaScript, düz metin vb.) tanıyabileceği ve karşılık gelen mantığı veya kaçışı uygulayabileceği anlamına gelir; bu, özellikle HTML oluştururken güvenlik ve doğruluk için çok önemlidir. + +**Uygulama Mantığı ile Entegrasyon:** Özel fonksiyonlar gibi, bir filtrenin arkasındaki PHP çağrılabilir nesnesi bir kapanış (closure), statik metot veya örnek metodu olabilir. Bu, filtrelerin gerekirse uygulama servislerine veya verilerine erişmesine olanak tanır, ancak ana amaçları *giriş değerini dönüştürmek* olarak kalır. + +Latte varsayılan olarak zengin bir [standart filtre |filters] seti sağlar. Özel filtreler, projenize özgü biçimlendirme ve dönüşümlerle bu seti genişletmenizi sağlar. + +Eğer *birden fazla* girişe dayalı mantık gerçekleştirmeniz gerekiyorsa veya dönüştürülecek birincil bir değeriniz yoksa, muhtemelen [özel bir fonksiyon |custom-functions] kullanmak daha uygundur. Karmaşık işaretleme oluşturmanız veya şablon akışını kontrol etmeniz gerekiyorsa, [özel bir etiket |custom-tags] düşünün. + + +Filtre Oluşturma ve Kaydetme +============================ + +Latte'de özel filtreleri tanımlamanın ve kaydetmenin birkaç yolu vardır. + + +`addFilter()` ile Doğrudan Kayıt +-------------------------------- + +Filtre eklemenin en basit yolu, doğrudan `Latte\Engine` nesnesinde `addFilter()` yöntemini kullanmaktır. Filtre adını (şablonda nasıl kullanılacağını) ve karşılık gelen PHP çağrılabilir nesnesini belirtirsiniz. + +```php +$latte = new Latte\Engine; + +// Argümansız basit filtre +$latte->addFilter('initial', fn(string $s): string => mb_substr($s, 0, 1) . '.'); + +// İsteğe bağlı argümanlı filtre +$latte->addFilter('shortify', function (string $s, int $len = 10): string { + return mb_substr($s, 0, $len); +}); + +// Dizi işleyen filtre +$latte->addFilter('sum', fn(array $numbers): int|float => array_sum($numbers)); +``` + +**Şablonda Kullanım:** + +```latte +{$name|initial} {* $name 'John' ise 'J.' yazdırır *} +{$description|shortify} {* Varsayılan uzunluk 10 kullanılır *} +{$description|shortify:50} {* Uzunluk 50 kullanılır *} +{$prices|sum} {* $prices dizisindeki öğelerin toplamını yazdırır *} +``` + +**Argümanları Geçirme:** + +Borunun (`|`) solundaki değer her zaman filtre fonksiyonuna *ilk* argüman olarak geçirilir. Şablonda iki nokta üst üste (`:`) işaretinden sonra belirtilen herhangi bir parametre, sonraki argümanlar olarak geçirilir. + +```latte +{$text|shortify:30} +// shortify($text, 30) PHP fonksiyonunu çağırır +``` + + +Uzantı ile Kayıt +---------------- + +Daha iyi organizasyon için, özellikle yeniden kullanılabilir filtre setleri oluştururken veya bunları paket olarak paylaşırken, önerilen yol bunları bir [Latte uzantısı |extending-latte#Latte Extension] içinde kaydetmektir: + +```php +namespace App\Latte; + +use Latte\Extension; + +class MyLatteExtension extends Extension +{ + public function getFilters(): array + { + return [ + 'initial' => $this->initial(...), + 'shortify' => $this->shortify(...), + ]; + } + + public function initial(string $s): string + { + return mb_substr($s, 0, 1) . '.'; + } + + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// Kayıt +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +Bu yaklaşım, filtre mantığınızı kapsüllenmiş ve kaydı basit tutar. + + +Filtre Yükleyici Kullanımı +-------------------------- + +Latte, `addFilterLoader()` kullanarak bir filtre yükleyici kaydetmenize olanak tanır. Bu, Latte'nin derleme sırasında herhangi bir bilinmeyen filtre adı için isteyeceği tek bir çağrılabilir callable'dır. Yükleyici, filtrenin PHP çağrılabilir nesnesini veya `null` döndürür. + +```php +$latte = new Latte\Engine; + +// Yükleyici, filtre çağrılabilirlerini dinamik olarak oluşturabilir/alabilir +$latte->addFilterLoader(function (string $name): ?callable { + if ($name === 'myLazyFilter') { + // Burada pahalı bir başlatma hayal edin... + $service = get_some_expensive_service(); + return fn($value) => $service->process($value); + } + return null; +}); +``` + +Bu yöntem öncelikle çok **pahalı başlatmaya** sahip filtrelerin geç yüklenmesi için tasarlanmıştır. Ancak, modern bağımlılık enjeksiyonu uygulamaları genellikle geç servisleri daha verimli bir şekilde yönetir. + +Filtre yükleyicileri karmaşıklık ekler ve genellikle `addFilter()` ile doğrudan kayıt veya `getFilters()` kullanarak bir uzantı içinde kayıt lehine önerilmez. Filtre başlatmadaki performans sorunlarıyla ilgili, başka türlü çözülemeyen ciddi, özel bir nedeniniz olmadıkça yükleyicileri kullanın. + + +Niteliklere Sahip Bir Sınıf Kullanan Filtreler +---------------------------------------------- + +Filtreleri tanımlamanın başka bir zarif yolu, [şablon parametre sınıfınızdaki |develop#Parametreler Olarak Sınıf] yöntemleri kullanmaktır. Yönteme sadece `#[Latte\Attributes\TemplateFilter]` niteliğini ekleyin. + +```php +use Latte\Attributes\TemplateFilter; + +class TemplateParameters +{ + public function __construct( + public string $description, + // diğer parametreler... + ) {} + + #[TemplateFilter] + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// Nesneyi şablona geçirme +$params = new TemplateParameters(description: '...'); +$latte->render('template.latte', $params); +``` + +Latte, `TemplateParameters` nesnesi şablona geçirildiğinde bu nitelikle işaretlenmiş yöntemleri otomatik olarak tanır ve kaydeder. Şablondaki filtre adı, yöntem adıyla aynı olacaktır (bu durumda `shortify`). + +```latte +{* Parametre sınıfında tanımlanan filtreyi kullanma *} +{$description|shortify:50} +``` + + +Bağlamsal Filtreler +=================== + +Bazen bir filtre, yalnızca giriş değerinden daha fazla bilgiye ihtiyaç duyar. Çalıştığı dizenin **içerik türünü** (örneğin HTML, JavaScript, düz metin) bilmesi veya hatta değiştirmesi gerekebilir. Bu, bağlamsal filtrelerin devreye girdiği yerdir. + +Bağlamsal bir filtre, normal bir filtreyle aynı şekilde tanımlanır, ancak **ilk parametresi** `Latte\Runtime\FilterInfo` olarak tür ipucu verilmelidir. Latte bu imzayı otomatik olarak tanır ve filtreyi çağırırken `FilterInfo` nesnesini geçirir. Sonraki parametreler, filtre argümanlarını normal şekilde alır. + +```php +use Latte\Runtime\FilterInfo; +use Latte\ContentType; + +$latte->addFilter('money', function (FilterInfo $info, float $amount): string { + // 1. Giriş içerik türünü kontrol edin (isteğe bağlı, ancak önerilir) + // Null (değişken giriş) veya düz metne izin verin. HTML vb. üzerine uygulanırsa reddedin. + if (!in_array($info->contentType, [null, ContentType::Text], true)) { + $actualType = $info->contentType ?? 'mixed'; + throw new \RuntimeException( + "|money filtresi uyumsuz içerik türü $actualType içinde kullanıldı. Beklenen metin veya null." + ); + } + + // 2. Dönüşümü gerçekleştirin + $formatted = number_format($amount, 2, '.', ',') . ' EUR'; + $htmlOutput = '' . htmlspecialchars($formatted) . ''; // Doğru kaçışı sağlayın! + + // 3. Çıktı içerik türünü bildirin + $info->contentType = ContentType::Html; + + // 4. Sonucu döndürün + return $htmlOutput; +}); +``` + +`$info->contentType`, `Latte\ContentType`'dan bir dize sabitidir (örneğin `ContentType::Html`, `ContentType::Text`, `ContentType::JavaScript`, vb.) veya filtre bir değişkene (`{$var|filter}`) uygulanırsa `null` olur. Bu değeri giriş bağlamını kontrol etmek için **okuyabilir** ve çıktı bağlamının türünü bildirmek için ona **yazabilirsiniz**. + +İçerik türünü HTML olarak ayarlayarak, Latte'ye filtreniz tarafından döndürülen dizenin güvenli HTML olduğunu söylersiniz. Latte daha sonra bu sonuca varsayılan otomatik kaçışını **uygulamaz**. Bu, filtreniz HTML işaretlemesi oluşturuyorsa çok önemlidir. + +.[warning] +Filtreniz HTML oluşturuyorsa, bu HTML'de kullanılan **herhangi bir giriş verisini doğru şekilde kaçmaktan siz sorumlusunuz** (yukarıdaki `htmlspecialchars($formatted)` çağrısında olduğu gibi). Bunu yapmamak XSS güvenlik açıkları oluşturabilir. Filtreniz yalnızca düz metin döndürüyorsa, `$info->contentType` ayarlamanız gerekmez. + + +Bloklardaki Filtreler +--------------------- + +[Bloklara |tags#block] uygulanan tüm filtreler *bağlamsal olmalıdır*. Bunun nedeni, bloğun içeriğinin, filtrenin farkında olması gereken tanımlanmış bir içerik türüne (genellikle HTML) sahip olmasıdır. + +```latte +{block heading|money}1000{/block} +{* 'money' filtresi ikinci argüman olarak '1000' alır + ve $info->contentType ContentType::Html olacaktır *} +``` + +Bağlamsal filtreler, verilerin bağlamlarına göre nasıl işlendiği üzerinde güçlü bir kontrol sağlar, gelişmiş işlevlere olanak tanır ve özellikle HTML içeriği oluştururken doğru kaçış davranışını sağlar. diff --git a/latte/tr/custom-functions.texy b/latte/tr/custom-functions.texy new file mode 100644 index 0000000000..2d94b9c341 --- /dev/null +++ b/latte/tr/custom-functions.texy @@ -0,0 +1,144 @@ +Özel Fonksiyonlar Oluşturma +*************************** + +.[perex] +Latte şablonlarına kolayca özel yardımcı fonksiyonlar ekleyin. Hesaplamalar, servislere erişim veya dinamik içerik oluşturma için ifadeler içinde doğrudan PHP mantığını çağırın, bu da şablonlarınızı temiz ve güçlü tutar. + + +Fonksiyonlar Nedir? +=================== + +Latte'deki fonksiyonlar, şablonlardaki ifadeler (`{...}`) içinde çağrılabilecek fonksiyon setini genişletmenizi sağlar. Bunları **yalnızca Latte şablonlarınızın içinde kullanılabilen özel PHP fonksiyonları** olarak düşünebilirsiniz. Bu birkaç avantaj sağlar: + +**Kolaylık:** Yardımcı mantığı (hesaplamalar, biçimlendirme veya uygulama verilerine erişim gibi) tanımlayabilir ve PHP'de `strlen()` veya `date()` çağırır gibi, şablonda doğrudan basit, tanıdık fonksiyon sözdizimiyle çağırabilirsiniz. + +```latte +{var $userInitials = initials($userName)} {* örn. 'J. D.' *} + +{if hasPermission('article', 'edit')} + Düzenle +{/if} +``` + +**Küresel Alanı Kirletmeme:** PHP'de gerçek bir küresel fonksiyon tanımlamanın aksine, Latte fonksiyonları yalnızca şablon oluşturma bağlamında bulunur. PHP küresel ad alanını yalnızca şablonlara özgü yardımcılarla doldurmanız gerekmez. + +**Uygulama Mantığı ile Entegrasyon:** Bir Latte fonksiyonunun arkasındaki PHP çağrılabilir nesnesi herhangi bir şey olabilir – anonim bir fonksiyon, statik bir metot veya bir örnek metodu. Bu, şablonlarınızdaki fonksiyonların, değişkenleri yakalayarak (anonim fonksiyonlar durumunda) veya bağımlılık enjeksiyonu kullanarak (nesneler durumunda) uygulama servislerine, veritabanlarına, yapılandırmaya veya başka herhangi bir gerekli mantığa kolayca erişebileceği anlamına gelir. Yukarıdaki `hasPermission` örneği, muhtemelen arka planda bir yetkilendirme servisini çağırdığında bunu açıkça gösterir. + +**Yerel Fonksiyonları Geçersiz Kılma (İsteğe Bağlı):** Hatta yerel bir PHP fonksiyonuyla aynı ada sahip bir Latte fonksiyonu tanımlayabilirsiniz. Şablonda, orijinal fonksiyon yerine kendi sürümünüz çağrılır. Bu, şablona özgü davranış sağlamak veya tutarlı işlemeyi sağlamak için yararlı olabilir (örneğin, `strlen`'in her zaman çok baytlı güvenli olmasını sağlamak). Yanlış anlaşılmaları önlemek için bu özelliği dikkatli kullanın. + +Varsayılan olarak Latte, *tüm* yerel PHP fonksiyonlarının çağrılmasına izin verir ([Sandbox |sandbox] tarafından kısıtlanmadıkça). Özel fonksiyonlar, projenizin özel ihtiyaçları için bu yerleşik kütüphaneyi genişletir. + +Yalnızca tek bir değeri dönüştürüyorsanız, [özel bir filtre |custom-filters] kullanmak daha uygun olabilir. + + +Fonksiyon Oluşturma ve Kaydetme +=============================== + +Filtrelerde olduğu gibi, özel fonksiyonları tanımlamanın ve kaydetmenin birkaç yolu vardır. + + +`addFunction()` ile Doğrudan Kayıt +---------------------------------- + +En basit yöntem, `Latte\Engine` nesnesinde `addFunction()` kullanmaktır. Fonksiyon adını (şablonda nasıl görüneceğini) ve karşılık gelen PHP çağrılabilir nesnesini belirtirsiniz. + +```php +$latte = new Latte\Engine; + +// Basit yardımcı fonksiyon +$latte->addFunction('initials', function (string $name): string { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; +}); +``` + +**Şablonda Kullanım:** + +```latte +{var $userInitials = initials($userName)} +``` + +Şablondaki fonksiyon argümanları, doğrudan PHP çağrılabilir nesnesine aynı sırada geçirilir. Tip ipuçları, varsayılan değerler ve değişken parametreler (`...`) gibi PHP işlevleri beklendiği gibi çalışır. + + +Uzantı ile Kayıt +---------------- + +Daha iyi organizasyon ve yeniden kullanılabilirlik için, fonksiyonları bir [Latte uzantısı |extending-latte#Latte Extension] içinde kaydedin. Bu yaklaşım, daha karmaşık uygulamalar veya paylaşılan kütüphaneler için önerilir. + +```php +namespace App\Latte; + +use Latte\Extension; +use Nette\Security\Authorizator; + +class MyLatteExtension extends Extension +{ + public function __construct( + // Authorizator servisinin var olduğunu varsayıyoruz + private Authorizator $authorizator, + ) { + } + + public function getFunctions(): array + { + // Metotları Latte fonksiyonları olarak kaydetme + return [ + 'hasPermission' => $this->hasPermission(...), + ]; + } + + public function hasPermission(string $resource, string $action): bool + { + return $this->authorizator->isAllowed($resource, $action); + } +} + +// Kayıt (konteynerin DIC içerdiğini varsayıyoruz) +$extension = $container->getByType(App\Latte\MyLatteExtension::class); +$latte = new Latte\Engine; +$latte->addExtension($extension); +``` + +Bu yaklaşım, Latte'de tanımlanan fonksiyonların, uygulamanızın bağımlılık enjeksiyon konteyneri veya bir fabrika tarafından yönetilen kendi bağımlılıklarına sahip olabilen nesne metotları tarafından nasıl desteklenebileceğini gösterir. Bu, şablonlarınızın mantığını uygulamanın çekirdeğiyle bağlantılı tutarken temiz bir organizasyon sağlar. + + +Niteliklere Sahip Bir Sınıf Kullanan Fonksiyonlar +------------------------------------------------- + +Filtreler gibi, fonksiyonlar da [şablon parametre sınıfınızdaki |develop#Parametreler Olarak Sınıf] yöntemler olarak `#[Latte\Attributes\TemplateFunction]` niteliği kullanılarak tanımlanabilir. + +```php +use Latte\Attributes\TemplateFunction; + +class TemplateParameters +{ + public function __construct( + public string $userName, + // diğer parametreler... + ) {} + + // Bu metot şablonda {initials(...)} olarak kullanılabilir olacak + #[TemplateFunction] + public function initials(string $name): string + { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; + } +} + +// Nesneyi şablona geçirme +$params = new TemplateParameters(userName: 'John Doe', /* ... */); +$latte->render('template.latte', $params); +``` + +Latte, parametre nesnesi şablona geçirildiğinde bu nitelikle işaretlenmiş yöntemleri otomatik olarak keşfeder ve kaydeder. Şablondaki fonksiyon adı, yöntem adıyla eşleşir. + +```latte +{* Parametre sınıfında tanımlanan fonksiyonu kullanma *} +{var $inits = initials($userName)} +``` + +**Bağlamsal Fonksiyonlar?** + +Filtrelerin aksine, `FilterInfo` benzeri bir nesne alacak "bağlamsal fonksiyonlar" için doğrudan bir kavram yoktur. Fonksiyonlar ifadeler içinde çalışır ve genellikle bloklara uygulanan filtreler gibi oluşturma bağlamına veya içerik türü bilgilerine doğrudan erişime ihtiyaç duymazlar. diff --git a/latte/tr/custom-tags.texy b/latte/tr/custom-tags.texy new file mode 100644 index 0000000000..402e361e28 --- /dev/null +++ b/latte/tr/custom-tags.texy @@ -0,0 +1,1135 @@ +Kendi Etiketlerinizi Oluşturma +****************************** + +.[perex] +Bu sayfa, Latte'de özel etiketler oluşturmak için kapsamlı bir kılavuz sağlar. Latte'nin şablonları nasıl derlediğini anlamanız üzerine inşa ederek, basit etiketlerden iç içe geçmiş içerik ve özel ayrıştırma ihtiyaçları olan daha karmaşık senaryolara kadar her şeyi ele alacağız. + +Özel etiketler, şablon sözdizimi ve oluşturma mantığı üzerinde en yüksek düzeyde kontrol sağlar, ancak aynı zamanda en karmaşık genişletme noktasıdır. Özel bir etiket oluşturmaya karar vermeden önce, her zaman [daha basit bir çözüm olup olmadığını |extending-latte#Latte yi Genişletme Yolları] veya [standart sette |tags] uygun bir etiketin zaten mevcut olup olmadığını düşünün. Özel etiketleri yalnızca daha basit alternatifler ihtiyaçlarınız için yeterli olmadığında kullanın. + + +Derleme Sürecini Anlama +======================= + +Özel etiketleri etkili bir şekilde oluşturmak için Latte'nin şablonları nasıl işlediğini açıklamak faydalıdır. Bu süreci anlamak, etiketlerin neden bu şekilde yapılandırıldığını ve daha geniş bağlama nasıl uyduklarını açıklığa kavuşturur. + +Latte'de şablon derlemesi, basitleştirilmiş olarak şu temel adımları içerir: + +1. **Sözcüksel Analiz:** Lexer, şablon kaynak kodunu (`.latte` dosyası) okur ve onu **token** adı verilen küçük, farklı parçalara ayırır (ör. `{`, `foreach`, `$variable`, `}`, HTML metni, vb.). +2. **Ayrıştırma:** Parser, bu token akışını alır ve şablonun mantığını ve içeriğini temsil eden anlamlı bir ağaç yapısı oluşturur. Bu ağaca **Soyut Sözdizimi Ağacı (AST)** denir. +3. **Derleme Geçişleri:** PHP kodu oluşturmadan önce Latte, [derleme geçişlerini |compiler-passes] çalıştırır. Bunlar, tüm AST'yi dolaşan ve onu değiştirebilen veya bilgi toplayabilen fonksiyonlardır. Bu adım, güvenlik ([Sandbox |sandbox]) veya optimizasyon gibi özellikler için çok önemlidir. +4. **Kod Oluşturma:** Son olarak, derleyici (potansiyel olarak değiştirilmiş) AST'yi dolaşır ve karşılık gelen PHP sınıf kodunu oluşturur. Bu PHP kodu, çalıştırıldığında şablonu gerçekten oluşturan şeydir. +5. **Önbellekleme:** Oluşturulan PHP kodu diske kaydedilir, bu da sonraki oluşturmaları çok hızlı hale getirir, çünkü 1-4 adımları atlanır. + +Aslında, derleme biraz daha karmaşıktır. Latte'nin **iki** lexer'ı ve parser'ı vardır: biri HTML şablonu için, diğeri etiketler içindeki PHP benzeri kod için. Ayrıca ayrıştırma, tokenizasyondan sonra gerçekleşmez, ancak lexer ve parser paralel olarak iki "iş parçacığında" çalışır ve koordine olur. İnanın bana, bunu programlamak roket bilimiydi :-) + +Şablon içeriğinin yüklenmesinden, ayrıştırılmasına ve sonuç dosyasının oluşturulmasına kadar tüm süreci, deneyebileceğiniz ve ara sonuçları yazdırabileceğiniz bu kodla sıralayabilirsiniz: + +```php +$latte = new Latte\Engine; +$source = $latte->getLoader()->getContent($file); +$ast = $latte->parse($source); +$latte->applyPasses($ast); +$code = $latte->generate($ast, $file); +``` + + +Bir Etiketin Anatomisi +====================== + +Latte'de tam işlevsel bir özel etiket oluşturmak, birkaç birbirine bağlı parçayı içerir. Uygulamaya geçmeden önce, HTML ve Belge Nesne Modeli (DOM) ile bir analoji kullanarak temel kavramları ve terminolojiyi anlayalım. + + +Etiketler ve Düğümler (HTML ile Analoji) +---------------------------------------- + +HTML'de `

                                                                                                                            ` veya `

                                                                                                                            ...
                                                                                                                            ` gibi **etiketler** yazarız. Bu etiketler kaynak kodundaki sözdizimidir. Tarayıcı bu HTML'yi ayrıştırdığında, **Belge Nesne Modeli (DOM)** adı verilen bir bellek temsili oluşturur. DOM'da, HTML etiketleri **düğümlerle** temsil edilir (özellikle JavaScript DOM terminolojisinde `Element` düğümleri). Programatik olarak bu *düğümlerle* çalışırız (örneğin, JavaScript `document.getElementById(...)` bir Element düğümü döndürür). Etiket, kaynak dosyadaki yalnızca metinsel bir temsildir; düğüm, mantıksal ağaçtaki nesne temsilidir. + +Latte benzer şekilde çalışır: + +- `.latte` şablon dosyasında `{foreach ...}` ve `{/foreach}` gibi **Latte etiketleri** yazarsınız. Bu, sizin şablon yazarı olarak çalıştığınız sözdizimidir. +- Latte şablonu **ayrıştırdığında**, bir **Soyut Sözdizimi Ağacı (AST)** oluşturur. Bu ağaç **düğümlerden** oluşur. Şablondaki her Latte etiketi, HTML öğesi, metin parçası veya ifade, bu ağaçta bir veya daha fazla düğüm haline gelir. +- AST'deki tüm düğümler için temel sınıf `Latte\Compiler\Node`'dur. Tıpkı DOM'un farklı düğüm türlerine (Element, Text, Comment) sahip olması gibi, Latte AST'sinin de farklı düğüm türleri vardır. Statik metin için `Latte\Compiler\Nodes\TextNode`, HTML öğeleri için `Latte\Compiler\Nodes\Html\ElementNode`, etiketler içindeki ifadeler için `Latte\Compiler\Nodes\Php\ExpressionNode` ve özel etiketler için kritik olarak `Latte\Compiler\Nodes\StatementNode`'dan miras alan düğümlerle karşılaşacaksınız. + + +Neden `StatementNode`? +---------------------- + +HTML öğeleri (`Html\ElementNode`) öncelikle yapıyı ve içeriği temsil eder. PHP ifadeleri (`Php\ExpressionNode`) değerleri veya hesaplamaları temsil eder. Peki ya `{if}`, `{foreach}` veya özel `{datetime}` etiketimiz gibi Latte etiketleri? Bu etiketler *eylemler gerçekleştirir*, program akışını kontrol eder veya mantığa dayalı çıktı üretir. Bunlar, Latte'yi sadece bir işaretleme dili değil, güçlü bir şablonlama *motoru* yapan işlevsel birimlerdir. + +Programlamada, eylemleri gerçekleştiren bu tür birimlere genellikle "statements" (deyimler) denir. Bu nedenle, bu işlevsel Latte etiketlerini temsil eden düğümler tipik olarak `Latte\Compiler\Nodes\StatementNode`'dan miras alır. Bu, onları tamamen yapısal düğümlerden (HTML öğeleri gibi) veya değerleri temsil eden düğümlerden (ifadeler gibi) ayırır. + + +Anahtar Bileşenler +================== + +Özel bir etiket oluşturmak için gereken ana bileşenleri gözden geçirelim: + + +Etiket Ayrıştırma Fonksiyonu +---------------------------- + +- Bu PHP çağrılabilir fonksiyonu, kaynak şablondaki Latte etiketi sözdizimini (`{...}`) ayrıştırır. +- Etiket hakkındaki bilgileri (adı, konumu ve n:nitelik olup olmadığı gibi) [api:Latte\Compiler\Tag] nesnesi aracılığıyla alır. +- Etiket ayırıcıları içindeki argümanları ve ifadeleri ayrıştırmak için birincil aracı, `$tag->parser` aracılığıyla erişilebilen [api:Latte\Compiler\TagParser] nesnesidir (bu, tüm şablonu ayrıştıran parser'dan farklıdır). +- Eşli etiketler için, başlangıç ve bitiş etiketleri arasındaki iç içeriği ayrıştırması için Latte'ye sinyal vermek üzere `yield` kullanır. +- Ayrıştırma fonksiyonunun nihai hedefi, AST'ye eklenen bir **düğüm sınıfı** örneği oluşturmak ve döndürmektir. +- Ayrıştırma fonksiyonunu doğrudan ilgili düğüm sınıfında statik bir metot (genellikle `create` olarak adlandırılır) olarak uygulamak gelenekseldir (zorunlu olmasa da). Bu, ayrıştırma mantığını ve düğüm temsilini tek bir pakette düzgün bir şekilde tutar, gerekirse sınıfın özel/korumalı üyelerine erişime izin verir ve organizasyonu iyileştirir. + + +Düğüm Sınıfı +------------ + +- Etiketinizin **Soyut Sözdizimi Ağacı (AST)** içindeki *mantıksal işlevini* temsil eder. +- Ayrıştırılmış bilgileri (argümanlar veya içerik gibi) genel özellikler olarak içerir. Bu özellikler genellikle diğer `Node` örneklerini içerir (ör. ayrıştırılmış argümanlar için `ExpressionNode`, ayrıştırılmış içerik için `AreaNode`). +- `print(PrintContext $context): string` metodu, şablon oluşturma sırasında etiketin eylemini gerçekleştiren *PHP kodunu* (bir deyim veya bir dizi deyim) oluşturur. +- `getIterator(): \Generator` metodu, **derleme geçişleri** tarafından gezinme için alt düğümleri (argümanlar, içerik) erişilebilir kılar. Geçişlerin potansiyel olarak alt düğümleri değiştirmesine veya değiştirmesine izin vermek için referanslar (`&`) sağlamalıdır. +- Tüm şablon AST'ye ayrıştırıldıktan sonra, Latte bir dizi [derleme geçişi |compiler-passes] çalıştırır. Bu geçişler, her düğüm tarafından sağlanan `getIterator()` metodunu kullanarak *tüm* AST'yi dolaşır. Düğümleri inceleyebilir, bilgi toplayabilir ve hatta ağacı *değiştirebilirler* (ör. düğümlerin genel özelliklerini değiştirerek veya düğümleri tamamen değiştirerek). Kapsamlı bir `getIterator()` gerektiren bu tasarım çok önemlidir. [Sandbox |sandbox] gibi güçlü özelliklerin, özel etiketleriniz de dahil olmak üzere şablonun *herhangi bir* bölümünün davranışını analiz etmesine ve potansiyel olarak değiştirmesine olanak tanıyarak güvenlik ve tutarlılık sağlar. + + +Bir Uzantı Aracılığıyla Kayıt +----------------------------- + +- Latte'ye yeni etiketiniz hakkında ve bunun için hangi ayrıştırma fonksiyonunun kullanılacağını bildirmeniz gerekir. Bu, bir [Latte uzantısı |extending-latte#Latte Extension] içinde yapılır. +- Uzantı sınıfınızın içinde `getTags(): array` metodunu uygularsınız. Bu metot, anahtarların etiket adları (ör. `'mytag'`, `'n:myattribute'`) ve değerlerin ilgili ayrıştırma fonksiyonlarını temsil eden PHP çağrılabilir fonksiyonları (ör. `MyNamespace\DatetimeNode::create(...)`) olduğu ilişkisel bir dizi döndürür. + +Özet: **Etiket ayrıştırma fonksiyonu**, etiketinizin *şablon kaynak kodunu* bir **AST düğümüne** dönüştürür. **Düğüm sınıfı** daha sonra *kendisini* derlenmiş şablon için yürütülebilir *PHP koduna* dönüştürebilir ve alt düğümlerini `getIterator()` aracılığıyla **derleme geçişleri** için erişilebilir kılar. **Uzantı aracılığıyla kayıt**, etiket adını ayrıştırma fonksiyonuyla ilişkilendirir ve Latte'ye bildirir. + +Şimdi bu bileşenleri adım adım nasıl uygulayacağımızı inceleyelim. + + +Basit Bir Etiket Oluşturma +========================== + +İlk özel Latte etiketinizi oluşturmaya başlayalım. Çok basit bir örnekle başlayacağız: mevcut tarih ve saati yazdıran `{datetime}` adlı bir etiket. **Başlangıçta bu etiket hiçbir argüman kabul etmeyecek**, ancak daha sonra [#"Etiket Argümanlarını Ayrıştırma"] bölümünde geliştireceğiz. Ayrıca iç içeriği de yoktur. + +Bu örnek size temel adımları gösterecektir: düğüm sınıfını tanımlama, `print()` ve `getIterator()` metotlarını uygulama, ayrıştırma fonksiyonunu oluşturma ve son olarak etiketi kaydetme. + +**Hedef:** PHP `date()` fonksiyonunu kullanarak mevcut tarih ve saati çıktılamak için `{datetime}`'i uygulamak. + + +Düğüm Sınıfının Oluşturulması +----------------------------- + +Öncelikle, Soyut Sözdizimi Ağacı'nda (AST) etiketimizi temsil edecek bir sınıfa ihtiyacımız var. Yukarıda tartışıldığı gibi, `Latte\Compiler\Nodes\StatementNode`'dan miras alıyoruz. + +Bir dosya oluşturun (ör. `DatetimeNode.php`) ve sınıfı tanımlayın: + +```php +node = new self; + return $node; + } + + /** + * Şablon oluşturulurken çalıştırılacak PHP kodunu oluşturur. + */ + public function print(PrintContext $context): string + { + return $context->format( + 'echo date(\'Y-m-d H:i:s\') %line;', + $this->position, + ); + } + + /** + * Latte derleme geçişleri için alt düğümlere erişim sağlar. + */ + public function &getIterator(): \Generator + { + false && yield; + } +} +``` + +Latte bir şablonda `{datetime}` ile karşılaştığında, `create()` ayrıştırma fonksiyonunu çağırır. Görevi, bir `DatetimeNode` örneği döndürmektir. + +`print()` metodu, şablon oluşturulurken çalıştırılacak PHP kodunu oluşturur. Derlenmiş şablon için sonuç PHP kod dizesini oluşturan `$context->format()` metodunu çağırıyoruz. İlk argüman, `'echo date('Y-m-d H:i:s') %line;'`, sonraki parametrelerin eklendiği bir maskedir. `%line` yer tutucusu, `format()` metoduna ikinci argümanı, yani `$this->position`'ı kullanmasını ve oluşturulan PHP kodunu orijinal şablon satırına geri bağlayan `/* line 15 */` gibi bir yorum eklemesini söyler, bu da hata ayıklama için çok önemlidir. + +`$this->position` özelliği, temel `Node` sınıfından miras alınır ve Latte parser tarafından otomatik olarak ayarlanır. Etiketin kaynak `.latte` dosyasında nerede bulunduğunu gösteren bir [api:Latte\Compiler\Position] nesnesi içerir. + +`getIterator()` metodu, derleme geçişleri için çok önemlidir. Tüm alt düğümleri sağlamalıdır, ancak basit `DatetimeNode`'umuzun şu anda hiçbir argümanı veya içeriği yoktur, dolayısıyla alt düğümü yoktur. Ancak, metot yine de mevcut olmalı ve bir üreteç olmalıdır, yani `yield` anahtar kelimesi metot gövdesinde bir şekilde bulunmalıdır. + + +Bir Uzantı Aracılığıyla Kayıt +----------------------------- + +Son olarak, Latte'ye yeni etiket hakkında bilgi verelim. Bir [uzantı sınıfı |extending-latte#Latte Extension] oluşturun (ör. `MyLatteExtension.php`) ve etiketi `getTags()` metodunda kaydedin. + +```php + Harita: 'etiket-adı' => ayrıştırma-fonksiyonu + */ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + // Daha sonra buraya daha fazla etiket kaydedin + ]; + } +} +``` + +Ardından bu uzantıyı Latte Motoru'nda kaydedin: + +```php +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +Bir şablon oluşturun: + +```latte +

                                                                                                                            Sayfa oluşturuldu: {datetime}

                                                                                                                            +``` + +Beklenen çıktı: `

                                                                                                                            Sayfa oluşturuldu: 2023-10-27 11:00:00

                                                                                                                            ` + + +Bu Aşamanın Özeti +----------------- + +Temel bir özel `{datetime}` etiketi başarıyla oluşturduk. AST'deki temsilini (`DatetimeNode`) tanımladık, ayrıştırmasını (`create()`) ele aldık, nasıl PHP kodu oluşturması gerektiğini (`print()`) belirttik, alt öğelerinin geçiş için erişilebilir olmasını (`getIterator()`) sağladık ve Latte'de kaydettik. + +Bir sonraki bölümde, bu etiketi argümanları kabul edecek şekilde geliştireceğiz ve ifadeleri nasıl ayrıştıracağımızı ve alt düğümleri nasıl yöneteceğimizi göstereceğiz. + + +Etiket Argümanlarını Ayrıştırma +=============================== + +Basit `{datetime}` etiketimiz çalışıyor, ancak çok esnek değil. `date()` fonksiyonu için isteğe bağlı bir argüman kabul edecek şekilde geliştirelim: bir biçimlendirme dizesi. Gerekli sözdizimi `{datetime $format}` olacaktır. + +**Hedef:** `{datetime}`'i, `date()` için biçimlendirme dizesi olarak kullanılacak isteğe bağlı bir PHP ifadesini argüman olarak kabul edecek şekilde değiştirmek. + + +`TagParser` ile Tanışma +----------------------- + +Kodu değiştirmeden önce, kullanacağımız aracı anlamak önemlidir: [api:Latte\Compiler\TagParser]. Ana Latte parser'ı (`TemplateParser`), `{datetime ...}` veya bir n:nitelik gibi bir Latte etiketiyle karşılaştığında, etiketin *içindeki* içeriğin ayrıştırılmasını ( `{` ve `}` arasındaki kısım veya nitelik değeri) özel bir `TagParser`'a devreder. + +Bu `TagParser` yalnızca **etiket argümanları** ile çalışır. Görevi, bu argümanları temsil eden tokenları işlemektir. Anahtar nokta, kendisine sağlanan **tüm içeriği işlemesi gerektiğidir**. Ayrıştırma fonksiyonunuz biterse ancak `TagParser` argümanların sonuna ulaşmadıysa (`$tag->parser->isEnd()` aracılığıyla kontrol edilir), Latte bir istisna fırlatır, çünkü bu, etiket içinde beklenmeyen tokenların kaldığını gösterir. Tersine, etiket argümanları *gerektiriyorsa*, ayrıştırma fonksiyonunuzun başında `$tag->expectArguments()`'i çağırmalısınız. Bu metot, argümanların mevcut olup olmadığını kontrol eder ve etiket herhangi bir argüman olmadan kullanıldıysa yardımcı bir istisna fırlatır. + +`TagParser`, çeşitli türde argümanları ayrıştırmak için kullanışlı metotlar sunar: + +- `parseExpression(): ExpressionNode`: PHP benzeri bir ifadeyi (değişkenler, değişmezler, operatörler, fonksiyon/metot çağrıları vb.) ayrıştırır. Latte'nin sözdizimsel şekerlemesini, örneğin basit alfanümerik dizeleri tırnak içine alınmış dizeler gibi ele almasını (ör. `foo`, `'foo'` gibi ayrıştırılır) yönetir. +- `parseUnquotedStringOrExpression(): ExpressionNode`: Standart bir ifadeyi veya *tırnaksız bir dizeyi* ayrıştırır. Tırnaksız dizeler, Latte tarafından tırnak işaretleri olmadan izin verilen dizilerdir, genellikle dosya yolları gibi şeyler için kullanılır (ör. `{include ../file.latte}`). Tırnaksız bir dize ayrıştırırsa, bir `StringNode` döndürür. +- `parseArguments(): ArrayNode`: `10, name: 'John', true` gibi potansiyel olarak anahtarlarla virgülle ayrılmış argümanları ayrıştırır. +- `parseModifier(): ModifierNode`: `|upper|truncate:10` gibi filtreleri ayrıştırır. +- `parseType(): ?SuperiorTypeNode`: `int`, `?string`, `array|Foo` gibi PHP tür ipuçlarını ayrıştırır. + +Daha karmaşık veya daha düşük seviyeli ayrıştırma ihtiyaçları için, [token akışı |api:Latte\Compiler\TokenStream] ile `$tag->parser->stream` aracılığıyla doğrudan etkileşim kurabilirsiniz. Bu nesne, tek tek tokenları kontrol etmek ve işlemek için metotlar sağlar: + +- `$tag->parser->stream->is(...): bool`: *Mevcut* tokenın belirtilen türlerden (ör. `Token::Php_Variable`) veya değişmez değerlerden (ör. `'as'`) herhangi biriyle eşleşip eşleşmediğini tüketmeden kontrol eder. İleriye bakmak için kullanışlıdır. +- `$tag->parser->stream->consume(...): Token`: *Mevcut* tokenı tüketir ve akış konumunu ileri taşır. Argüman olarak beklenen token türleri/değerleri sağlanırsa ve mevcut token eşleşmezse, bir `CompileException` fırlatır. Belirli bir tokenı *beklediğinizde* bunu kullanın. +- `$tag->parser->stream->tryConsume(...): ?Token`: *Mevcut* tokenı *yalnızca* belirtilen türlerden/değerlerden biriyle eşleşiyorsa tüketmeye çalışır. Eşleşirse, tokenı tüketir ve döndürür. Eşleşmezse, akış konumunu değiştirmeden bırakır ve `null` döndürür. İsteğe bağlı tokenlar için veya farklı sözdizimsel yollar arasında seçim yaparken bunu kullanın. + + +`create()` Ayrıştırma Fonksiyonunu Güncelleme +--------------------------------------------- + +Bu anlayışla, `DatetimeNode`'daki `create()` metodunu, `$tag->parser` kullanarak isteğe bağlı biçimlendirme argümanını ayrıştıracak şekilde değiştirelim. + +```php +node = new self; + + // Herhangi bir token olup olmadığını kontrol edin + if (!$tag->parser->isEnd()) { + // Argümanı TagParser kullanarak PHP benzeri bir ifade olarak ayrıştırın. + $node->format = $tag->parser->parseExpression(); + } + + return $node; + } + + // ... print() ve getIterator() metotları daha sonra güncellenecektir ... +} +``` + +Genel bir `$format` özelliği ekledik. `create()` içinde şimdi argümanların *var olup olmadığını* kontrol etmek için `$tag->parser->isEnd()` kullanıyoruz. Varsa, `$tag->parser->parseExpression()` ifade için tokenları işler. `TagParser` tüm giriş tokenlarını işlemesi gerektiğinden, kullanıcı biçim ifadesinden sonra beklenmeyen bir şey yazarsa (ör. `{datetime 'Y-m-d', unexpected}`) Latte otomatik olarak bir hata fırlatır. + + +`print()` Metodunu Güncelleme +----------------------------- + +Şimdi `print()` metodunu, `$this->format` içinde saklanan ayrıştırılmış biçim ifadesini kullanacak şekilde değiştirelim. Hiçbir biçim sağlanmadıysa (`$this->format` `null` ise), varsayılan bir biçimlendirme dizesi kullanmalıyız, örneğin `'Y-m-d H:i:s'`. + +```php + public function print(PrintContext $context): string + { + $formatNode = $this->format ?? new StringNode('Y-m-d H:i:s'); + + // %node, $formatNode'un PHP kod temsilini yazdırır. + return $context->format( + 'echo date(%node) %line;', + $formatNode, + $this->position + ); + } +``` + +`$formatNode` değişkeninde, PHP `date()` fonksiyonu için biçimlendirme dizesini temsil eden AST düğümünü saklıyoruz. Burada null birleştirme operatörünü (`??`) kullanıyoruz. Kullanıcı şablonda bir argüman sağladıysa (ör. `{datetime 'd.m.Y'}`), o zaman `$this->format` özelliği karşılık gelen düğümü içerir (bu durumda değeri `'d.m.Y'` olan bir `StringNode`) ve bu düğüm kullanılır. Kullanıcı bir argüman sağlamadıysa (sadece `{datetime}` yazdıysa), `$this->format` özelliği `null` olur ve bunun yerine varsayılan `'Y-m-d H:i:s'` biçimiyle yeni bir `StringNode` oluştururuz. Bu, `$formatNode`'un her zaman biçim için geçerli bir AST düğümü içermesini sağlar. + +`'echo date(%node) %line;'` maskesinde, `format()` metoduna bir sonraki argümanı (bizim `$formatNode`'umuz) almasını, `print()` metodunu çağırmasını (PHP kod temsilini döndürecektir) ve sonucu yer tutucunun konumuna eklemesini söyleyen yeni bir `%node` yer tutucusu kullanılır. + + +Alt Düğümler İçin `getIterator()` Uygulama +------------------------------------------ + +`DatetimeNode`'umuzun şimdi bir alt düğümü var: `$format` ifadesi. Bu alt düğümü, `getIterator()` metodunda sağlayarak derleme geçişlerine erişilebilir kılmalıyız. Geçişlerin potansiyel olarak düğümü değiştirmesine izin vermek için bir *referans* (`&`) sağlamayı unutmayın. + +```php + public function &getIterator(): \Generator + { + if ($this->format) { + yield $this->format; + } + } +``` + +Bu neden çok önemli? `$format` argümanının yasaklanmış bir fonksiyon çağrısı içerip içermediğini kontrol etmesi gereken bir Sandbox geçişi düşünün (ör. `{datetime dangerousFunction()}`). `getIterator()` `$this->format`'ı sağlamazsa, Sandbox geçişi etiketimizin argümanı içindeki `dangerousFunction()` çağrısını asla görmezdi, bu da potansiyel bir güvenlik açığı yaratırdı. Sağlayarak, Sandbox'ın (ve diğer geçişlerin) `$format` ifade düğümünü kontrol etmesine ve potansiyel olarak değiştirmesine izin veririz. + + +Geliştirilmiş Etiketi Kullanma +------------------------------ + +Etiket şimdi isteğe bağlı argümanı doğru şekilde işliyor: + +```latte +Varsayılan biçim: {datetime} +Özel biçim: {datetime 'd.m.Y'} +Değişken kullanımı: {datetime $userDateFormatPreference} + +{* Bu, 'd.m.Y' ayrıştırıldıktan sonra bir hataya neden olur, çünkü ", foo" beklenmiyor *} +{* {datetime 'd.m.Y', foo} *} +``` + +Ardından, aralarındaki içeriği işleyen eşli etiketler oluşturmaya bakacağız. + + +Eşli Etiketleri İşleme +====================== + +Şimdiye kadar, `{datetime}` etiketimiz *kendiliğinden kapanan* (kavramsal olarak) idi. Başlangıç ve bitiş etiketleri arasında hiçbir içeriği yoktu. Ancak, birçok kullanışlı etiket bir şablon içeriği bloğuyla çalışır. Bunlara **eşli etiketler** denir. Örnekler arasında `{if}...{/if}`, `{block}...{/block}` veya şimdi oluşturacağımız özel bir etiket bulunur: `{debug}...{/debug}`. + +Bu etiket, şablonlarımıza yalnızca geliştirme sırasında görünür olması gereken hata ayıklama bilgilerini eklememize olanak tanır. + +**Hedef:** İçeriği yalnızca belirli bir "geliştirme modu" bayrağı etkin olduğunda oluşturulan eşli bir `{debug}` etiketi oluşturmak. + + +Sağlayıcılarla Tanışma +---------------------- + +Bazen etiketlerinizin, doğrudan şablon parametreleri olarak iletilmeyen verilere veya hizmetlere erişmesi gerekir. Örneğin, uygulamanın geliştirme modunda olup olmadığını belirlemek, kullanıcı nesnesine erişmek veya yapılandırma değerlerini almak. Latte bu amaçla **sağlayıcılar** (Providers) adı verilen bir mekanizma sağlar. + +Sağlayıcılar, [uzantınızda |extending-latte#Latte Extension] `getProviders()` metodu kullanılarak kaydedilir. Bu metot, anahtarların sağlayıcıların şablon çalışma zamanı kodunda erişilebilir olacağı adlar olduğu ve değerlerin gerçek veriler veya nesneler olduğu ilişkisel bir dizi döndürür. + +Etiketinizin `print()` metodu tarafından oluşturulan PHP kodu içinde, bu sağlayıcılara özel `$this->global` nesne özelliği aracılığıyla erişebilirsiniz. Bu özellik tüm uzantılar arasında paylaşıldığından, Latte'nin temel sağlayıcıları veya diğer üçüncü taraf uzantılarından gelen sağlayıcılarla olası ad çakışmalarını önlemek için **sağlayıcı adlarınıza önek eklemek** iyi bir uygulamadır. Yaygın bir kural, üreticinizle veya uzantı adınızla ilgili kısa, benzersiz bir önek kullanmaktır. Örneğimiz için `app` önekini kullanacağız ve geliştirme modu bayrağı `$this->global->appDevMode` olarak erişilebilir olacaktır. + + +İçerik Ayrıştırma İçin `yield` Anahtar Kelimesi +----------------------------------------------- + +Latte parser'ına `{debug}` ve `{/debug}` *arasındaki* içeriği işlemesini nasıl söyleriz? İşte burada `yield` anahtar kelimesi devreye girer. + +`yield` bir `create()` fonksiyonunda kullanıldığında, fonksiyon bir [PHP üreteci |https://www.php.net/manual/en/language.generators.overview.php] haline gelir. Yürütmesi duraklatılır ve kontrol ana `TemplateParser`'a geri döner. `TemplateParser` daha sonra şablon içeriğini *karşılık gelen kapatma etiketine* (`{/debug}` bizim durumumuzda) rastlayana kadar ayrıştırmaya devam eder. + +Kapatma etiketi bulunduğunda, `TemplateParser` `create()` fonksiyonumuzun yürütmesini `yield` deyiminden hemen sonra devam ettirir. `yield` deyimi tarafından *döndürülen* değer, iki öğe içeren bir dizidir: + +1. Başlangıç ve bitiş etiketleri arasındaki ayrıştırılmış içeriği temsil eden bir `AreaNode`. +2. Kapatma etiketini temsil eden bir `Tag` nesnesi (ör. `{/debug}`). + +`yield` kullanan bir `DebugNode` sınıfı ve `create` metodunu oluşturalım. + +```php +node = new self; + + // Ayrıştırmayı duraklatın, iç içeriği ve {/debug} bulunduğunda bitiş etiketini alın + [$node->content, $endTag] = yield; + + return $node; + } + + // ... print() ve getIterator() daha sonra uygulanacaktır ... +} +``` + +Not: Etiket bir n:nitelik olarak kullanılıyorsa, yani `
                                                                                                                            ...
                                                                                                                            ` ise `$endTag` `null` olur. + + +Koşullu Oluşturma İçin `print()` Uygulama +----------------------------------------- + +`print()` metodu şimdi çalışma zamanında `appDevMode` sağlayıcısını kontrol eden ve bayrak true ise yalnızca iç içerik için kodu yürüten PHP kodu oluşturmalıdır. + +```php + public function print(PrintContext $context): string + { + // Çalışma zamanında sağlayıcıyı kontrol eden bir PHP 'if' deyimi oluşturur + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + // Geliştirme modundaysa, iç içeriği yazdırın + %node + } + + XX, + $this->position, // %line yorumu için + $this->content, // İç içeriğin AST'sini içeren düğüm + ); + } +``` + +Bu basittir. Standart bir PHP `if` deyimi oluşturmak için `PrintContext::format()` kullanıyoruz. `if` içine `$this->content` için `%node` yer tutucusunu yerleştiriyoruz. Latte, etiketin iç kısmı için PHP kodunu oluşturmak üzere `$this->content->print($context)`'i özyinelemeli olarak çağırır, ancak yalnızca `$this->global->appDevMode` çalışma zamanında true olarak değerlendirilirse. + + +İçerik İçin `getIterator()` Uygulama +------------------------------------ + +Önceki örnekteki argüman düğümünde olduğu gibi, `DebugNode`'umuzun şimdi bir alt düğümü var: `AreaNode $content`. `getIterator()` içinde sağlayarak erişilebilir kılmalıyız: + +```php + public function &getIterator(): \Generator + { + // İçerik düğümüne bir referans sağlar + yield $this->content; + } +``` + +Bu, derleme geçişlerinin `{debug}` etiketimizin içeriğine inmesini sağlar, bu da içerik koşullu olarak oluşturulsa bile önemlidir. Örneğin, Sandbox'ın içeriği `appDevMode`'un true veya false olmasına bakılmaksızın analiz etmesi gerekir. + + +Kayıt ve Kullanım +----------------- + +Etiketi ve sağlayıcıyı uzantınızda kaydedin: + +```php +class MyLatteExtension extends Extension +{ + // $isDevelopmentMode'un bir yerde belirlendiğini varsayıyoruz (ör. yapılandırmadan) + public function __construct( + private bool $isDevelopmentMode, + ) { + } + + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), // Yeni etiketi kaydet + ]; + } + + public function getProviders(): array + { + return [ + 'appDevMode' => $this->isDevelopmentMode, // Sağlayıcıyı kaydet + ]; + } +} + +// Uzantıyı kaydederken: +$isDev = true; // Bunu uygulamanızın ortamına göre belirleyin +$latte->addExtension(new App\Latte\MyLatteExtension($isDev)); +``` + +Ve şablonda kullanımı: + +```latte +

                                                                                                                            Her zaman görünen normal içerik.

                                                                                                                            + +{debug} +
                                                                                                                            + Mevcut kullanıcının ID'si: {$user->id} + İstek zamanı: {=time()} +
                                                                                                                            +{/debug} + +

                                                                                                                            Diğer normal içerik.

                                                                                                                            +``` + + +n:Nitelik Entegrasyonu +---------------------- + +Latte, birçok eşli etiket için kullanışlı bir kısaltılmış gösterim sunar: [n:nitelikler |syntax#n:nitelikler]. `{tag}...{/tag}` gibi eşli bir etiketiniz varsa ve etkisinin doğrudan tek bir HTML öğesine uygulanmasını istiyorsanız, genellikle bu öğe üzerinde bir `n:tag` niteliği olarak daha kısa bir şekilde yazabilirsiniz. + +Tanımladığınız çoğu standart eşli etiket için (bizim `{debug}` gibi), Latte karşılık gelen `n:` nitelik sürümünü otomatik olarak etkinleştirir. Kayıt sırasında ek bir şey yapmanız gerekmez: + +```latte +{* Standart eşli etiket kullanımı *} +{debug}
                                                                                                                            Hata ayıklama bilgisi
                                                                                                                            {/debug} + +{* n:nitelik ile eşdeğer kullanım *} +
                                                                                                                            Hata ayıklama bilgisi
                                                                                                                            +``` + +Her iki sürüm de `
                                                                                                                            `'i yalnızca `$this->global->appDevMode` true ise oluşturur. `inner-` ve `tag-` önekleri de beklendiği gibi çalışır. + +Bazen etiketinizin mantığının, standart bir eşli etiket olarak mı yoksa bir n:nitelik olarak mı kullanıldığına veya `n:inner-tag` veya `n:tag-tag` gibi bir önek kullanılıp kullanılmadığına bağlı olarak biraz farklı davranması gerekebilir. `create()` ayrıştırma fonksiyonunuza iletilen `Latte\Compiler\Tag` nesnesi şu bilgileri sağlar: + +- `$tag->isNAttribute(): bool`: Etiket bir n:nitelik olarak ayrıştırılıyorsa `true` döndürür +- `$tag->prefix: ?string`: n:nitelik ile kullanılan öneki döndürür, bu `null` (n:nitelik değil), `Tag::PrefixNone`, `Tag::PrefixInner` veya `Tag::PrefixTag` olabilir + +Şimdi basit etiketleri, argüman ayrıştırmayı, eşli etiketleri, sağlayıcıları ve n:nitelikleri anladığımıza göre, `{debug}` etiketimizi başlangıç noktası olarak kullanarak diğer etiketlerin içine yerleştirilmiş etiketleri içeren daha karmaşık bir senaryoyu ele alalım. + + +Ara Etiketler +============= + +Bazı eşli etiketler, son kapatma etiketinden önce *içlerinde* başka etiketlerin görünmesine izin verir veya hatta gerektirir. Bunlara **ara etiketler** denir. Klasik örnekler arasında `{if}...{elseif}...{else}...{/if}` veya `{switch}...{case}...{default}...{/switch}` bulunur. + +`{debug}` etiketimizi, uygulama geliştirme modunda *olmadığında* oluşturulacak isteğe bağlı bir `{else}` yan tümcesini destekleyecek şekilde genişletelim. + +**Hedef:** `{debug}`'i isteğe bağlı bir ara `{else}` etiketini destekleyecek şekilde değiştirmek. Nihai sözdizimi `{debug} ... {else} ... {/debug}` olmalıdır. + + +`yield` ile Ara Etiketleri Ayrıştırma +------------------------------------- + +`yield`'in `create()` ayrıştırma fonksiyonunu duraklattığını ve ayrıştırılmış içeriği bitiş etiketiyle birlikte döndürdüğünü zaten biliyoruz. Ancak `yield` daha fazla kontrol sunar: ona *ara etiket adları* dizisi sağlayabilirsiniz. Parser, bu belirtilen etiketlerden herhangi birine **aynı iç içe geçme seviyesinde** (yani, üst etiketin doğrudan alt öğeleri olarak, içindeki diğer blokların veya etiketlerin içinde değil) rastladığında, ayrıştırmayı da durdurur. + +Ayrıştırma bir ara etiket nedeniyle durduğunda, içerik ayrıştırmayı durdurur, `create()` üretecini devam ettirir ve kısmen ayrıştırılmış içeriği ve **ara etiketin** kendisini (son bitiş etiketi yerine) geri iletir. `create()` fonksiyonumuz daha sonra bu ara etiketi işleyebilir (ör. varsa argümanlarını ayrıştırabilir) ve içeriğin *bir sonraki* bölümünü *son* bitiş etiketine veya başka bir beklenen ara etikete kadar ayrıştırmak için `yield`'i tekrar kullanabilir. + +`{else}`'i beklemek için `DebugNode::create()`'i değiştirelim: + +```php +node = new self; + + // yield ve {/debug} veya {else}'i bekleyin + [$node->thenContent, $nextTag] = yield ['else']; + + // Durduğumuz etiketin {else} olup olmadığını kontrol edin + if ($nextTag?->name === 'else') { + // {else} ve {/debug} arasındaki içeriği ayrıştırmak için tekrar yield + [$node->elseContent, $endTag] = yield; + } + + return $node; + } + + // ... print() ve getIterator() daha sonra güncellenecektir ... +} +``` + +Şimdi `yield ['else']` Latte'ye sadece `{/debug}` için değil, aynı zamanda `{else}` için de ayrıştırmayı durdurmasını söyler. `{else}` bulunursa, `$nextTag` `{else}` için `Tag` nesnesini içerir. Sonra argümansız `yield`'i tekrar kullanırız, bu da şimdi yalnızca son `{/debug}` etiketini beklediğimiz anlamına gelir ve sonucu `$node->elseContent`'e kaydederiz. `{else}` bulunmazsa, `$nextTag` `{/debug}` için `Tag` olurdu (veya n:nitelik olarak kullanılıyorsa `null`) ve `$node->elseContent` `null` kalırdı. + + +`{else}` ile `print()` Uygulama +------------------------------- + +`print()` metodu yeni yapıyı yansıtmalıdır. `devMode` sağlayıcısına dayalı bir PHP `if/else` deyimi oluşturmalıdır. + +```php + public function print(PrintContext $context): string + { + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + %node // 'then' dalı için kod ({debug} içeriği) + } else { + %node // 'else' dalı için kod ({else} içeriği) + } + + XX, + $this->position, // 'if' koşulu için satır numarası + $this->thenContent, // İlk %node yer tutucusu + $this->elseContent ?? new NopNode, // İkinci %node yer tutucusu + ); + } +``` + +Bu standart bir PHP `if/else` yapısıdır. `%node`'u iki kez kullanıyoruz; `format()` sağlanan düğümleri sırayla değiştirir. `$this->elseContent` `null` ise hatalardan kaçınmak için `?? new NopNode` kullanıyoruz – `NopNode` basitçe hiçbir şey yazdırmaz. + + +Her İki İçerik İçin `getIterator()` Uygulama +-------------------------------------------- + +Şimdi potansiyel olarak iki alt içerik düğümümüz var (`$thenContent` ve `$elseContent`). Varsa her ikisini de sağlamalıyız: + +```php + public function &getIterator(): \Generator + { + yield $this->thenContent; + if ($this->elseContent) { + yield $this->elseContent; + } + } +``` + + +Geliştirilmiş Etiketi Kullanma +------------------------------ + +Etiket şimdi isteğe bağlı `{else}` yan tümcesiyle kullanılabilir: + +```latte +{debug} +

                                                                                                                            Hata ayıklama bilgileri gösteriliyor, çünkü devMode AÇIK.

                                                                                                                            +{else} +

                                                                                                                            Hata ayıklama bilgileri gizli, çünkü devMode KAPALI.

                                                                                                                            +{/debug} +``` + + +Durum ve İç İçe Geçmeyi İşleme +============================== + +Önceki örneklerimiz (`{datetime}`, `{debug}`) `print()` metotları içinde nispeten durumsuzdu. Ya doğrudan içerik yazdırıyorlardı ya da genel bir sağlayıcıya dayalı basit bir koşullu kontrol gerçekleştiriyorlardı. Ancak birçok etiket, oluşturma sırasında bir tür **durum** yönetmeyi gerektirir veya performans veya doğruluk nedeniyle yalnızca bir kez çalıştırılması gereken kullanıcı ifadelerinin değerlendirilmesini içerir. Ayrıca, özel etiketlerimiz **iç içe** geçtiğinde ne olacağını düşünmeliyiz. + +Bu kavramları, `{repeat $count}...{/repeat}` etiketini oluşturarak gösterelim. Bu etiket, iç içeriğini `$count` kez tekrarlayacaktır. + +**Hedef:** İçeriğini belirtilen sayıda tekrarlayan `{repeat $count}`'i uygulamak. + + +Geçici ve Benzersiz Değişkenlere Duyulan İhtiyaç +------------------------------------------------ + +Kullanıcının şunu yazdığını hayal edin: + +```latte +{repeat rand(1, 5)} İçerik {/repeat} +``` + +`print()` metodumuzda safça bir PHP `for` döngüsünü şu şekilde oluşturursak: + +```php +// Basitleştirilmiş, YANLIŞ oluşturulan kod +for ($i = 0; $i < rand(1, 5); $i++) { + // içerik çıktısı +} +``` +Bu yanlış olurdu! `rand(1, 5)` ifadesi **döngünün her iterasyonunda yeniden değerlendirilir**, bu da öngörülemeyen sayıda tekrara yol açar. `$count` ifadesini döngü başlamadan *bir kez* değerlendirmemiz ve sonucunu saklamamız gerekir. + +Önce sayı ifadesini değerlendiren ve onu **geçici bir çalışma zamanı değişkeninde** saklayan PHP kodu oluşturacağız. Şablon kullanıcısı tarafından tanımlanan değişkenlerle *ve* Latte'nin dahili değişkenleriyle ( `$ʟ_...` gibi) çakışmaları önlemek için, geçici değişkenlerimiz için **`$__` (çift alt çizgi)** önek kuralını kullanacağız. + +Oluşturulan kod daha sonra şöyle görünür: + +```php +$__count = rand(1, 5); +for ($__i = 0; $__i < $__count; $__i++) { + // içerik çıktısı +} +``` + +Şimdi iç içe geçmeyi düşünelim: + +```latte +{repeat $countA} {* Dış döngü *} + {repeat $countB} {* İç döngü *} + ... + {/repeat} +{/repeat} +``` + +Hem dış hem de iç `{repeat}` etiketi *aynı* geçici değişken adlarını (ör. `$__count` ve `$__i`) kullanarak kod oluşturursa, iç döngü dış döngünün değişkenlerinin üzerine yazar ve mantığı bozar. + +Her `{repeat}` etiketi örneği için oluşturulan geçici değişkenlerin **benzersiz** olmasını sağlamalıyız. Bunu `PrintContext::generateId()` kullanarak başarırız. Bu metot, derleme aşamasında benzersiz bir tamsayı döndürür. Bu ID'yi geçici değişkenlerimizin adlarına ekleyebiliriz. + +Yani `$__count` yerine, ilk repeat etiketi için `$__count_1`, ikincisi için `$__count_2` vb. oluşturacağız. Benzer şekilde, döngü sayacı için `$__i_1`, `$__i_2` vb. kullanacağız. + + +`RepeatNode` Uygulama +--------------------- + +Düğüm sınıfını oluşturalım. + +```php +expectArguments(); // $count'un sağlandığından emin olun + $node = $tag->node = new self; + // Sayı ifadesini ayrıştırın + $node->count = $tag->parser->parseExpression(); + // İç içeriği alın + [$node->content] = yield; + return $node; + } + + /** + * Benzersiz değişken adlarıyla PHP 'for' döngüsü oluşturur. + */ + public function print(PrintContext $context): string + { + // Benzersiz değişken adları oluşturun + $id = $context->generateId(); + $countVar = '$__count_' . $id; // ör. $__count_1, $__count_2, vb. + $iteratorVar = '$__i_' . $id; // ör. $__i_1, $__i_2, vb. + + return $context->format( + <<<'XX' + // Sayı ifadesini *bir kez* değerlendirin ve saklayın + %raw = (int) (%node); + // Saklanan sayıyı ve benzersiz iterasyon değişkenini kullanarak döngü yapın + for (%raw = 0; %2.raw < %0.raw; %2.raw++) %line { + %node // İç içeriği oluşturun + } + + XX, + $countVar, // %0 - Sayıyı saklamak için değişken + $this->count, // %1 - Sayı için ifade düğümü + $iteratorVar, // %2 - Döngü iterasyon değişkeninin adı + $this->position, // %3 - Döngünün kendisi için satır numarası yorumu + $this->content // %4 - İç içerik düğümü + ); + } + + /** + * Alt düğümleri (sayı ifadesi ve içerik) sağlar. + */ + public function &getIterator(): \Generator + { + yield $this->count; + yield $this->content; + } +} +``` + +`create()` metodu, `parseExpression()` kullanarak gerekli `$count` ifadesini ayrıştırır. Önce `$tag->expectArguments()` çağrılır. Bu, kullanıcının `{repeat}`'ten sonra *bir şey* sağladığından emin olur. `$tag->parser->parseExpression()` hiçbir şey sağlanmazsa başarısız olsa da, hata mesajı beklenmeyen sözdizimi hakkında olabilir. `expectArguments()` kullanmak, özellikle `{repeat}` etiketi için argümanların eksik olduğunu belirten çok daha net bir hata sağlar. + +`print()` metodu, çalışma zamanında tekrarlama mantığını yürütmekten sorumlu PHP kodunu oluşturur. İhtiyaç duyacağı geçici PHP değişkenleri için benzersiz adlar oluşturarak başlar. + +`$context->format()` metodu, karşılık gelen argüman olarak sağlanan *ham dizeyi* ekleyen yeni bir `%raw` yer tutucusu ile çağrılır. Burada, `$countVar` içinde saklanan benzersiz değişken adını (ör. `$__count_1`) ekler. Peki ya `%0.raw` ve `%2.raw`? Bu, **konumsal yer tutucuları** gösterir. Yalnızca *bir sonraki* mevcut ham argümanı alan `%raw` yerine, `%2.raw` açıkça dizin 2'deki argümanı ( `$iteratorVar` olan) alır ve ham dize değerini ekler. Bu, `$iteratorVar` dizesini `format()` için argüman listesinde birden çok kez iletmeden yeniden kullanmamızı sağlar. + +Bu dikkatlice oluşturulmuş `format()` çağrısı, sayı ifadesini doğru şekilde işleyen ve `{repeat}` etiketleri iç içe geçtiğinde bile değişken adı çakışmalarını önleyen verimli ve güvenli bir PHP döngüsü oluşturur. + + +Kayıt ve Kullanım +----------------- + +Etiketi uzantınızda kaydedin: + +```php +use App\Latte\RepeatNode; + +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), // repeat etiketini kaydet + ]; + } +} +``` + +İç içe geçme dahil şablonda kullanın: + +```latte +{var $rows = rand(5, 7)} +{var $cols = rand(3, 5)} + +{repeat $rows} +
                                                                                                                            + {repeat $cols} + + {/repeat} + +{/repeat} +``` + +Bu örnek, `$__` önekli geçici değişkenler ve `PrintContext::generateId()`'den alınan benzersiz ID'ler kullanarak durumun (döngü sayaçları) ve potansiyel iç içe geçme sorunlarının nasıl ele alınacağını gösterir. + + +Saf n:Nitelikler +---------------- + +`n:if` veya `n:foreach` gibi birçok `n:nitelik`, eşli etiket karşılıkları (`{if}...{/if}`, `{foreach}...{/foreach}`) için kullanışlı kısaltmalar olarak hizmet ederken, Latte ayrıca *yalnızca* n:nitelik biçiminde var olan etiketleri tanımlamanıza da olanak tanır. Bunlar genellikle eklendikleri HTML öğesinin niteliklerini veya davranışını değiştirmek için kullanılır. + +Latte'de yerleşik standart örnekler arasında, `class` niteliğini dinamik olarak oluşturmaya yardımcı olan [`n:class` |tags#n:class] ve birden çok rastgele nitelik ayarlayabilen [`n:attr` |tags#n:attr] bulunur. + +Kendi saf n:niteliğimizi oluşturalım: `n:confirm`, bir eylem gerçekleştirmeden önce (bir bağlantıyı takip etmek veya bir form göndermek gibi) bir JavaScript onay iletişim kutusu ekler. + +**Hedef:** Kullanıcı onay iletişim kutusunu iptal ederse varsayılan eylemi önlemek için bir `onclick` işleyicisi ekleyen `n:confirm="'Emin misiniz?'"` uygulamak. + + +`ConfirmNode` Uygulama +---------------------- + +Bir Node sınıfına ve bir ayrıştırma fonksiyonuna ihtiyacımız var. + +```php +expectArguments(); + $node = $tag->node = new self; + $node->message = $tag->parser->parseExpression(); + return $node; + } + + /** + * Doğru kaçış ile 'onclick' nitelik kodunu oluşturur. + */ + public function print(PrintContext $context): string + { + // Hem JavaScript hem de HTML nitelik bağlamları için doğru kaçışı sağlar. + return $context->format( + <<<'XX' + echo ' onclick="', LR\Filters::escapeHtmlAttr('return confirm(' . LR\Filters::escapeJs(%node) . ')'), '"' %line; + XX, + $this->message, + $this->position, + ); + } + + public function &getIterator(): \Generator + { + yield $this->message; + } +} +``` + +`print()` metodu, şablon oluşturma sırasında sonunda `onclick="..."` HTML niteliğini yazdıracak PHP kodunu oluşturur. İç içe geçmiş bağlamların (HTML niteliği içindeki JavaScript) işlenmesi dikkatli kaçış gerektirir. `LR\Filters::escapeJs(%node)` filtresi çalışma zamanında çağrılır ve mesajı JavaScript içinde kullanım için doğru şekilde kaçar (çıktı `"Emin misiniz?"` gibi olurdu). Ardından `LR\Filters::escapeHtmlAttr(...)` filtresi, HTML niteliklerinde özel olan karakterleri kaçar, böylece çıktıyı `return confirm("Emin misiniz?")` olarak değiştirir. Bu iki aşamalı çalışma zamanı kaçışı, mesajın JavaScript için güvenli olmasını ve sonuçtaki JavaScript kodunun bir HTML `onclick` niteliğine eklenmek için güvenli olmasını sağlar. + + +Kayıt ve Kullanım +----------------- + +n:niteliğini uzantınızda kaydedin. Anahtardaki `n:` önekini unutmayın: + +```php +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), + 'n:confirm' => ConfirmNode::create(...), // n:confirm'i kaydet + ]; + } +} +``` + +Şimdi `n:confirm`'i bağlantılarda, düğmelerde veya form öğelerinde kullanabilirsiniz: + +```latte +Sil +``` + +Oluşturulan HTML: + +```html +Sil +``` + +Kullanıcı bağlantıya tıkladığında, tarayıcı `onclick` kodunu yürütür, onay iletişim kutusunu görüntüler ve yalnızca kullanıcı "Tamam"ı tıklarsa `delete.php`'ye gider. + +Bu örnek, `print()` metodunda uygun PHP kodunu oluşturarak ana HTML öğesinin davranışını veya niteliklerini değiştirmek için saf bir n:niteliğin nasıl oluşturulabileceğini gösterir. Genellikle gerekli olan çift kaçışı unutmayın: biri hedef bağlam için (bu durumda JavaScript) ve diğeri HTML nitelik bağlamı için. + + +Gelişmiş Konular +================ + +Önceki bölümler temel kavramları kapsarken, özel Latte etiketleri oluştururken karşılaşabileceğiniz birkaç gelişmiş konu aşağıdadır. + + +Etiket Çıktı Modları +-------------------- + +`create()` fonksiyonunuza iletilen `Tag` nesnesinin bir `outputMode` özelliği vardır. Bu özellik, Latte'nin çevreleyen boşlukları ve girintiyi nasıl ele aldığını etkiler, özellikle etiket kendi satırında kullanıldığında. Bu özelliği `create()` fonksiyonunuzda değiştirebilirsiniz. + +- `Tag::OutputKeepIndentation` ( `{=...}` gibi çoğu etiket için varsayılan): Latte, etiketten önceki girintiyi korumaya çalışır. Etiketten *sonraki* yeni satırlar genellikle korunur. Bu, satır içi içerik yazdıran etiketler için uygundur. +- `Tag::OutputRemoveIndentation` ( `{if}`, `{foreach}` gibi blok etiketleri için varsayılan): Latte, baştaki girintiyi ve potansiyel olarak bir sonraki yeni satırı kaldırır. Bu, oluşturulan PHP kodunu daha temiz tutmaya yardımcı olur ve etiketin kendisinden kaynaklanan HTML çıktısındaki ek boş satırları önler. Kontrol yapılarını veya kendileri boşluk eklememesi gereken blokları temsil eden etiketler için bunu kullanın. +- `Tag::OutputNone` ( `{var}`, `{default}` gibi etiketler tarafından kullanılır): `RemoveIndentation`'a benzer, ancak etiketin kendisinin doğrudan çıktı üretmediğini daha güçlü bir şekilde işaret eder, potansiyel olarak etrafındaki boşlukların işlenmesini daha agresif bir şekilde etkiler. Bildirimsel veya ayarlama etiketleri için uygundur. + +Etiketinizin amacına en uygun modu seçin. Çoğu yapısal veya kontrol etiketi için `OutputRemoveIndentation` genellikle uygundur. + + +Üst/En Yakın Etiketlere Erişim +------------------------------ + +Bazen bir etiketin davranışı, kullanıldığı bağlama, özellikle içinde bulunduğu üst etikete/etiketlere bağlı olması gerekir. `create()` fonksiyonunuza iletilen `Tag` nesnesi, tam olarak bu amaç için bir `closestTag(array $classes, ?callable $condition = null): ?Tag` metodu sağlar. + +Bu metot, mevcut açık etiketlerin hiyerarşisinde (ayrıştırma sırasında dahili olarak temsil edilen HTML öğeleri dahil) yukarı doğru arama yapar ve belirli ölçütlere uyan en yakın atanın `Tag` nesnesini döndürür. Eşleşen bir ata bulunmazsa `null` döndürür. + +`$classes` dizisi, ne tür ata etiketleri aradığınızı belirtir. Ata etiketin ilişkili düğümünün (`$ancestorTag->node`) bu sınıfın bir örneği olup olmadığını kontrol eder. + +```php +function create(Tag $tag) +{ + // Düğümü ForeachNode örneği olan en yakın ata etiketini arayın + $foreachTag = $tag->closestTag([ForeachNode::class]); + if ($foreachTag) { + // ForeachNode örneğine kendisi erişebiliriz: + $foreachNode = $foreachTag->node; + } +} +``` + +`$foreachTag->node`'a dikkat edin: Bu yalnızca, Latte etiket geliştirmede, oluşturulan düğümü `create()` metodu içinde hemen `$tag->node`'a atamanın bir kuralı olduğu için çalışır, her zaman yaptığımız gibi. + +Bazen yalnızca düğüm türünü karşılaştırmak yeterli olmaz. Potansiyel bir ata etiketin veya düğümünün belirli bir özelliğini kontrol etmeniz gerekebilir. `closestTag()` için isteğe bağlı ikinci argüman, potansiyel ata `Tag` nesnesini kabul eden ve geçerli bir eşleşme olup olmadığını döndürmesi gereken bir çağrılabilirdir. + +```php +function create(Tag $tag) +{ + $dynamicBlockTag = $tag->closestTag( + [BlockNode::class], + // Koşul: blok dinamik olmalıdır + fn(Tag $blockTag) => $blockTag->node->block->isDynamic(), + ); +} +``` + +`closestTag()` kullanmak, bağlama duyarlı olan ve şablon yapınız içinde doğru kullanımı zorlayan etiketler oluşturmanıza olanak tanır, bu da daha sağlam ve anlaşılır şablonlara yol açar. + + +`PrintContext::format()` Yer Tutucuları +--------------------------------------- + +Düğümlerimizin `print()` metotlarında PHP kodu oluşturmak için sık sık `PrintContext::format()` kullandık. Bir maske dizesi ve maske içindeki yer tutucuları değiştiren sonraki argümanları kabul eder. Mevcut yer tutucuların bir özeti aşağıdadır: + +- **`%node`**: Argüman bir `Node` örneği olmalıdır. Düğümün `print()` metodunu çağırır ve sonuçtaki PHP kod dizesini ekler. +- **`%dump`**: Argüman herhangi bir PHP değeridir. Değeri geçerli PHP koduna dışa aktarır. Skalerler, diziler, null için uygundur. + - `$context->format('echo %dump;', 'Hello')` -> `echo 'Hello';` + - `$context->format('$arr = %dump;', [1, 2])` -> `$arr = [1, 2];` +- **`%raw`**: Argümanı herhangi bir kaçış veya değişiklik olmadan doğrudan çıktı PHP koduna ekler. **Dikkatli kullanın**, öncelikle önceden oluşturulmuş PHP kod parçacıklarını veya değişken adlarını eklemek için. + - `$context->format('%raw = 1;', '$variableName')` -> `$variableName = 1;` +- **`%args`**: Argüman bir `Expression\ArrayNode` olmalıdır. Dizi öğelerini bir fonksiyon veya metot çağrısı için argümanlar olarak biçimlendirilmiş şekilde yazdırır (virgülle ayrılmış, varsa adlandırılmış argümanları işler). + - `$argsNode = new ArrayNode([...]);` + - `$context->format('myFunc(%args);', $argsNode)` -> `myFunc(1, name: 'Joe');` +- **`%line`**: Argüman bir `Position` nesnesi olmalıdır (genellikle `$this->position`). Kaynak satır numarasını gösteren bir PHP yorumu `/* line X */` ekler. + - `$context->format('echo "Hi" %line;', $this->position)` -> `echo "Hi" /* line 42 */;` +- **`%escape(...)`**: *Çalışma zamanında* iç ifadeyi mevcut bağlama duyarlı kaçış kurallarını kullanarak kaçan PHP kodu oluşturur. + - `$context->format('echo %escape(%node);', $variableNode)` +- **`%modify(...)`**: Argüman bir `ModifierNode` olmalıdır. `ModifierNode`'da belirtilen filtreleri iç içeriğe uygulayan PHP kodu oluşturur, `|noescape` ile devre dışı bırakılmadıkça bağlama duyarlı kaçış dahil. + - `$context->format('%modify(%node);', $modifierNode, $variableNode)` +- **`%modifyContent(...)`**: `%modify`'a benzer, ancak yakalanan içerik bloklarını (genellikle HTML) değiştirmek için tasarlanmıştır. + +Argümanlara dizinlerine göre (sıfırdan başlayarak) açıkça başvurabilirsiniz: `%0.node`, `%1.dump`, `%2.raw`, vb. Bu, bir argümanı `format()`'a tekrar tekrar iletmeden maske içinde birkaç kez yeniden kullanmanızı sağlar. `%0.raw` ve `%2.raw`'ın kullanıldığı `{repeat}` etiketi örneğine bakın. + + +Karmaşık Argüman Ayrıştırma Örneği +---------------------------------- + +`parseExpression()`, `parseArguments()` vb. birçok durumu kapsarken, bazen `$tag->parser->stream` aracılığıyla erişilebilen daha düşük seviyeli `TokenStream` kullanan daha karmaşık ayrıştırma mantığına ihtiyacınız olur. + +**Hedef:** `{embedYoutube $videoID, width: 640, height: 480}` etiketini oluşturmak. Gerekli video ID'sini (dize veya değişken) ve ardından boyutlar için isteğe bağlı anahtar-değer çiftlerini ayrıştırmak istiyoruz. + +```php +expectArguments(); + $node = $tag->node = new self; + // Gerekli video ID'sini ayrıştırın + $node->videoId = $tag->parser->parseExpression(); + + // İsteğe bağlı anahtar-değer çiftlerini ayrıştırın + $stream = $tag->parser->stream; // Token akışını alın + while ($stream->tryConsume(',')) { // Virgülle ayırma gerektirir + // 'width' veya 'height' tanımlayıcısını bekleyin + $keyToken = $stream->consume(Token::Php_Identifier); + $key = strtolower($keyToken->text); + + $stream->consume(':'); // İki nokta üst üste ayırıcısını bekleyin + + $value = $tag->parser->parseExpression(); // Değer ifadesini ayrıştırın + + if ($key === 'width') { + $node->width = $value; + } elseif ($key === 'height') { + $node->height = $value; + } else { + throw new CompileException("Bilinmeyen argüman '$key'. 'width' veya 'height' bekleniyordu.", $keyToken->position); + } + } + + return $node; + } +} +``` + +Bu kontrol seviyesi, token akışıyla doğrudan etkileşim kurarak özel etiketleriniz için çok özel ve karmaşık sözdizimleri tanımlamanıza olanak tanır. + + +`AuxiliaryNode` Kullanma +------------------------ + +Latte, kod oluşturma sırasında veya derleme geçişleri içinde özel durumlar için genel "yardımcı" düğümler sağlar. Bunlar `AuxiliaryNode` ve `Php\Expression\AuxiliaryNode`'dur. + +`AuxiliaryNode`'u, temel işlevlerini - kod oluşturma ve alt düğümleri açığa çıkarma - yapıcısında sağlanan argümanlara devreden esnek bir kapsayıcı düğüm olarak düşünün: + +- `print()` delegasyonu: Yapıcının ilk argümanı bir PHP **closure**'dır. Latte, `AuxiliaryNode` üzerinde `print()` metodunu çağırdığında, bu sağlanan closure'ı yürütür. Closure, bir `PrintContext` ve yapıcının ikinci argümanında iletilen herhangi bir düğümü kabul eder, bu da çalışma zamanında tamamen özel PHP kod oluşturma mantığı tanımlamanıza olanak tanır. +- `getIterator()` delegasyonu: Yapıcının ikinci argümanı **`Node` nesneleri dizisidir**. Latte'nin `AuxiliaryNode`'un alt öğelerini (ör. derleme geçişleri sırasında) dolaşması gerektiğinde, `getIterator()` metodu basitçe bu dizide listelenen düğümleri sağlar. + +Örnek: + +```php +$node = new AuxiliaryNode( + // 1. Bu closure, print() gövdesi olur + fn(PrintContext $context, $arg1, $arg2) => $context->format('...%node...%node...', $arg1, $arg2), + + // 2. Bu düğümler getIterator() metodu tarafından sağlanır ve yukarıdaki closure'a iletilir + [$argumentNode1, $argumentNode2] +); +``` + +Latte, oluşturulan kodu nereye eklemeniz gerektiğine bağlı olarak iki farklı tür sağlar: + +- `Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode`: Bir **ifadeyi** temsil eden bir PHP kod parçası oluşturmanız gerektiğinde bunu kullanın +- `Latte\Compiler\Nodes\AuxiliaryNode`: Bir veya daha fazla **deyimi** temsil eden bir PHP kod bloğu eklemeniz gerektiğinde daha genel amaçlar için bunu kullanın + +`print()` metodunuz veya derleme geçişiniz içinde standart düğümler ( `StaticMethodCallNode` gibi) yerine `AuxiliaryNode` kullanmanın önemli bir nedeni, **sonraki derleme geçişleri için görünürlük kontrolüdür**, özellikle Sandbox gibi güvenlikle ilgili olanlar. + +Bir senaryo düşünün: Derleme geçişinizin, kullanıcı tarafından sağlanan bir ifadeyi (`$userExpr`) belirli, güvenilir bir yardımcı fonksiyon `myInternalSanitize($userExpr)` çağrısıyla sarmalaması gerekir. Standart bir `new FunctionCallNode('myInternalSanitize', [$userExpr])` düğümü oluşturursanız, AST geçişine tamamen görünür olacaktır. Sandbox geçişi daha sonra çalışırsa ve `myInternalSanitize` izin verilenler listesinde *değilse*, Sandbox bu çağrıyı *engelleyebilir* veya değiştirebilir, potansiyel olarak etiketinizin dahili mantığını bozabilir, siz, etiket yazarı, bu özel çağrının güvenli ve gerekli olduğunu bilseniz bile. Bu nedenle, çağrıyı doğrudan `AuxiliaryNode` closure'ı içinde oluşturabilirsiniz. + +```php +use Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode; + +// ... print() veya derleme geçişi içinde ... +$wrappedNode = new AuxiliaryNode( + fn(PrintContext $context, $userExpr) => $context->format( + 'myInternalSanitize(%node)', // Doğrudan PHP kodu oluşturun + $userExpr, + ), + // ÖNEMLİ: Orijinal kullanıcı ifadesi düğümünü hala buraya iletin! + [$userExpr], +); +``` + +Bu durumda, Sandbox geçişi `AuxiliaryNode`'u görür, ancak **closure'ı tarafından oluşturulan PHP kodunu analiz etmez**. Closure *içinde* oluşturulan `myInternalSanitize` çağrısını doğrudan engelleyemez. + +Oluşturulan PHP kodunun kendisi geçişlerden gizlenirken, bu koda *girdiler* (kullanıcı verilerini veya ifadelerini temsil eden düğümler) **hala geçilebilir olmalıdır**. Bu nedenle, `AuxiliaryNode` yapıcısının ikinci argümanı çok önemlidir. Closure'ınızın kullandığı tüm orijinal düğümleri (yukarıdaki örnekteki `$userExpr` gibi) içeren diziyi **iletmelisiniz**. `AuxiliaryNode`'un `getIterator()`'ı **bu düğümleri sağlayacaktır**, Sandbox gibi derleme geçişlerinin potansiyel sorunlar için bunları analiz etmesine olanak tanır. + + +En İyi Uygulamalar +================== + +- **Net Amaç:** Etiketinizin net ve gerekli bir amacı olduğundan emin olun. [Filtreler |custom-filters] veya [fonksiyonlar |custom-functions] ile kolayca çözülebilecek görevler için etiketler oluşturmayın. +- **`getIterator()`'ı Doğru Şekilde Uygulayın:** Her zaman `getIterator()`'ı uygulayın ve şablondan ayrıştırılan *tüm* alt düğümlere (argümanlar, içerik) *referanslar* (`&`) sağlayın. Bu, derleme geçişleri, güvenlik (Sandbox) ve potansiyel gelecek optimizasyonları için gereklidir. +- **Düğümler İçin Genel Özellikler:** Alt düğümleri içeren özellikleri genel yapın, böylece derleme geçişleri gerekirse bunları değiştirebilir. +- **`PrintContext::format()` Kullanın:** PHP kodu oluşturmak için `format()` metodunu kullanın. Tırnak işaretlerini işler, yer tutucuları doğru şekilde kaçar ve satır numarası yorumlarını otomatik olarak ekler. +- **Geçici Değişkenler (`$__`):** Geçici değişkenlere ihtiyaç duyan çalışma zamanı PHP kodu oluştururken (ör. ara toplamları, döngü sayaçlarını saklamak için), kullanıcı değişkenleri ve Latte'nin dahili `$ʟ_` değişkenleriyle çakışmaları önlemek için `$__` önek kuralını kullanın. +- **İç İçe Geçme ve Benzersiz ID'ler:** Etiketiniz iç içe geçebiliyorsa veya çalışma zamanında örneğe özgü duruma ihtiyaç duyuyorsa, geçici `$__` değişkenleriniz için benzersiz sonekler oluşturmak üzere `print()` metodunuz içinde `$context->generateId()` kullanın. +- **Harici Veriler İçin Sağlayıcılar:** Çalışma zamanı verilerine veya hizmetlerine ($this->global->...) erişmek için değerleri sabit kodlamak veya genel duruma güvenmek yerine sağlayıcıları ( `Extension::getProviders()` aracılığıyla kaydedilir) kullanın. Sağlayıcı adları için üretici önekleri kullanın. +- **n:Nitelikleri Düşünün:** Eşli etiketiniz mantıksal olarak tek bir HTML öğesi üzerinde çalışıyorsa, Latte muhtemelen otomatik `n:nitelik` desteği sağlar. Kullanıcı rahatlığı için bunu aklınızda bulundurun. Bir nitelik değiştirme etiketi oluşturuyorsanız, saf bir `n:nitelik`'in en uygun biçim olup olmadığını düşünün. +- **Test Etme:** Etiketleriniz için testler yazın, hem farklı sözdizimsel girdilerin ayrıştırılmasını hem de oluşturulan **PHP kodunun** çıktısının doğruluğunu kapsayın. + +Bu yönergeleri izleyerek, Latte şablonlama motoruyla sorunsuz bir şekilde bütünleşen güçlü, sağlam ve sürdürülebilir özel etiketler oluşturabilirsiniz. + +.[note] +Latte'nin bir parçası olan düğüm sınıflarını incelemek, ayrıştırma sürecinin tüm ayrıntılarını öğrenmenin en iyi yoludur. diff --git a/latte/tr/develop.texy b/latte/tr/develop.texy index fe2c9178bc..1e66f64a2d 100644 --- a/latte/tr/develop.texy +++ b/latte/tr/develop.texy @@ -1,70 +1,66 @@ -Geliştiriciler için Uygulamalar -******************************* +Geliştirici Uygulamaları +************************ -Kurulum .[#toc-installation] -============================ +Kurulum +======= -Latte'yi kurmanın en iyi yolu bir Composer kullanmaktır: +Latte'yi kurmanın en iyi yolu Composer kullanmaktır: ```shell composer require latte/latte ``` -Desteklenen PHP sürümleri (en son yama Latte sürümleri için geçerlidir): +Desteklenen PHP sürümleri (son küçük Latte sürümleri için geçerlidir): -| sürüm | PHP ile uyumlu +| sürüm | PHP ile uyumlu |-----------------|------------------- -| Latte 3.0 | PHP 8.0 - 8.2 -| Latte 2.11 | PHP 7.1 - 8.2 -| Latte 2.8 - 2.10 | PHP 7.1 - 8.1 +| Latte 3.0 | PHP 8.0 – 8.2 -Bir Şablon Nasıl Oluşturulur .[#toc-how-to-render-a-template] -============================================================= +Bir Şablon Nasıl Oluşturulur +============================ -Bir şablon nasıl oluşturulur? Sadece bu basit kodu kullanın: +Bir şablon nasıl oluşturulur? Sadece şu basit kod yeterlidir: ```php $latte = new Latte\Engine; // önbellek dizini $latte->setTempDirectory('/path/to/tempdir'); -$params = [ /* template variables */ ]; +$params = [ /* şablon değişkenleri */ ]; // veya $params = new TemplateParameters(/* ... */); -// çıktıya render et +// çıktıya oluştur $latte->render('template.latte', $params); -// veya değişkene dönüştürme +// değişkene oluştur $output = $latte->renderToString('template.latte', $params); ``` -Parametreler diziler veya daha da iyisi [nesne |#Parameters as a class] olabilir, bu da editörde tür denetimi ve öneri sağlayacaktır. +Parametreler bir dizi veya daha iyisi, tip kontrolü ve editörlerde ipucu sağlayan [bir sınıf |#Parametreler Olarak Sınıf] olabilir. .[note] -Kullanım örneklerini [Latte examples |https://github.com/nette-examples/latte] deposunda da bulabilirsiniz. +Kullanım örneklerini ayrıca [Latte örnekleri |https://github.com/nette-examples/latte] deposunda bulabilirsiniz. -Performans ve Önbellekleme .[#toc-performance-and-caching] -========================================================== +Performans ve Önbellek +====================== -Latte şablonları son derece hızlıdır, çünkü Latte bunları doğrudan PHP koduna derler ve diskte önbelleğe alır. Bu nedenle, saf PHP ile yazılmış şablonlara kıyasla ekstra ek yükleri yoktur. +Latte'deki şablonlar son derece hızlıdır, çünkü Latte onları doğrudan PHP koduna derler ve diskte önbelleğe alır. Bu nedenle, saf PHP ile yazılmış şablonlara göre ek bir yükleri yoktur. -Kaynak dosyayı her değiştirdiğinizde önbellek otomatik olarak yeniden oluşturulur. Böylece geliştirme sırasında Latte şablonlarınızı rahatça düzenleyebilir ve değişiklikleri hemen tarayıcıda görebilirsiniz. Bu özelliği üretim ortamında devre dışı bırakabilir ve performanstan biraz tasarruf edebilirsiniz: +Önbellek, kaynak dosyasını her değiştirdiğinizde otomatik olarak yeniden oluşturulur. Böylece geliştirme sırasında Latte şablonlarınızı rahatça düzenleyebilir ve değişiklikleri tarayıcıda anında görebilirsiniz. Bu özelliği üretim ortamında kapatabilir ve böylece biraz performans tasarrufu sağlayabilirsiniz: ```php $latte->setAutoRefresh(false); ``` -Bir üretim sunucusuna yerleştirildiğinde, özellikle büyük uygulamalar için ilk önbellek oluşturma işlemi anlaşılabilir bir şekilde biraz zaman alabilir. Latte "önbellek izdihamına":https://en.wikipedia.org/wiki/Cache_stampede karşı yerleşik bir önleme sahiptir. -Bu, sunucunun çok sayıda eş zamanlı istek aldığı bir durumdur ve Latte'nin önbelleği henüz mevcut olmadığından, hepsi aynı anda oluşturacaktır. Bu da CPU'yu yükseltir. -Latte akıllıdır ve birden fazla eşzamanlı istek olduğunda, yalnızca ilk iş parçacığı önbelleği oluşturur, diğerleri bekler ve sonra kullanır. +Üretim sunucusuna dağıtım yaparken, özellikle daha büyük uygulamalarda önbelleğin ilk oluşturulması elbette biraz zaman alabilir. Latte, "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede'ye karşı yerleşik bir önleme sahiptir. Bu, Latte'yi başlatan daha fazla sayıda eşzamanlı isteğin bir araya geldiği ve önbellek henüz mevcut olmadığı için hepsinin aynı anda oluşturmaya başlayacağı bir durumdur. Bu da sunucuyu aşırı yükler. Latte akıllıdır ve birden fazla eşzamanlı istek olduğunda önbelleği yalnızca ilk iş parçacığı oluşturur, diğerleri bekler ve ardından onu kullanır. -Sınıf Olarak Parametreler .[#toc-parameters-as-a-class] -======================================================= +Parametreler Olarak Sınıf +========================= -Değişkenleri şablona diziler olarak geçirmekten daha iyisi bir sınıf oluşturmaktır. [Tip güvenli gösterim |type-system], [IDE'de güzel öneri |recipes#Editors and IDE] ve [filtreler |extending-latte#Filters Using the Class] ile [fonksiyonları |extending-latte#Functions Using the Class] [kaydetmek |extending-latte#Filters Using the Class] için bir yol elde edersiniz. +Şablona değişkenleri dizi olarak iletmek yerine bir sınıf oluşturmak daha iyidir. Bu size [tip güvenli yazım |type-system], [IDE'de hoş ipucu |recipes#Düzenleyiciler ve IDE ler] ve [filtrelerin kaydı |custom-filters#Niteliklere Sahip Bir Sınıf Kullanan Filtreler] ve [fonksiyonlar |custom-functions#Niteliklere Sahip Bir Sınıf Kullanan Fonksiyonlar] için bir yol sağlar. ```php class MailTemplateParameters @@ -88,12 +84,12 @@ $latte->render('mail.latte', new MailTemplateParameters( ``` -Değişkenin Otomatik Çıkmasını Devre Dışı Bırakma .[#toc-disabling-auto-escaping-of-variable] -============================================================================================ +Değişken Otomatik Kaçışını Devre Dışı Bırakma +============================================= -Değişken bir HTML dizesi içeriyorsa, Latte'nin otomatik olarak (ve bu nedenle çift) kaçış yapmaması için onu işaretleyebilirsiniz. Bu, şablonda `|noescape` belirtme ihtiyacını ortadan kaldırır. +Bir değişken HTML içinde bir dize içeriyorsa, Latte'nin onu otomatik olarak (ve dolayısıyla çift olarak) kaçırmaması için onu işaretleyebilirsiniz. Bu, şablonda `|noescape` belirtme ihtiyacını ortadan kaldırır. -En kolay yol, dizeyi bir `Latte\Runtime\Html` nesnesine sarmaktır: +En basit yol, dizeyi bir `Latte\Runtime\Html` nesnesine sarmaktır: ```php $params = [ @@ -101,7 +97,7 @@ $params = [ ]; ``` -Latte ayrıca `Latte\HtmlStringable` arayüzünü uygulayan tüm nesnelerden kaçış yapmaz. Böylece, `__toString()` yöntemi otomatik olarak kaçılmayacak HTML kodu döndürecek olan kendi sınıfınızı oluşturabilirsiniz: +Latte ayrıca `Latte\HtmlStringable` arayüzünü uygulayan tüm nesneleri kaçırmaz. Böylece, `__toString()` metodu otomatik olarak kaçırılmayacak HTML kodu döndüren kendi sınıfınızı oluşturabilirsiniz: ```php class Emphasis extends Latte\HtmlStringable @@ -123,32 +119,85 @@ $params = [ ``` .[warning] - `__toString` yöntemi doğru HTML döndürmeli ve parametre kaçışını sağlamalıdır, aksi takdirde bir XSS güvenlik açığı oluşabilir! +`__toString` metodu doğru HTML döndürmeli ve parametrelerin kaçışını sağlamalıdır, aksi takdirde bir XSS güvenlik açığı oluşabilir! -Latte Filtreler, Etiketler vb. ile Nasıl Genişletilir? .[#toc-how-to-extend-latte-with-filters-tags-etc] -======================================================================================================== +Latte'yi Filtreler, Etiketler vb. ile Nasıl Genişletirsiniz? +============================================================ -Latte'ye özel bir filtre, fonksiyon, etiket vb. nasıl eklenir? Latte'yi [genişletme |extending Latte] bölümünde bulabilirsiniz. -Yaptığınız değişiklikleri farklı projelerde yeniden kullanmak veya başkalarıyla paylaşmak istiyorsanız, [bir uzantı oluşturmalısınız |creating-extension]. +Latte'ye kendi filtrenizi, fonksiyonunuzu, etiketinizi vb. nasıl eklersiniz? Bu konu [Latte'yi genişletme |extending-latte] bölümünde ele alınmaktadır. Değişikliklerinizi farklı projelerde yeniden kullanmak veya başkalarıyla paylaşmak istiyorsanız, [bir uzantı oluşturmalısınız |extending-latte#Latte Extension]. -Şablondaki Herhangi Bir Kod `{php ...}` .{data-version:3.0}{toc: RawPhpExtension} -================================================================================= +Şablonda Herhangi Bir Kod `{php ...}` .{toc: RawPhpExtension} +============================================================= -İçerisine sadece PHP ifadeleri yazılabilir. [`{do}` |tags#do] etiketini kullanamazsınız, bu nedenle örneğin `if ... else` gibi yapılar veya noktalı virgülle sonlandırılmış ifadeler ekleyemezsiniz. +[`{do}` |tags#do] etiketi içinde yalnızca PHP ifadeleri yazılabilir, bu nedenle örneğin `if ... else` gibi yapılar veya noktalı virgülle biten deyimler ekleyemezsiniz. -Ancak, `{php ...}` etiketini ekleyen `RawPhpExtension` uzantısını kaydedebilirsiniz; bu etiket, şablon yazarının riski altında herhangi bir PHP kodu eklemek için kullanılabilir. +Ancak, `{php ...}` etiketini ekleyen `RawPhpExtension` uzantısını kaydedebilirsiniz. Bunu kullanarak herhangi bir PHP kodu ekleyebilirsiniz. Sandbox modu kurallarının hiçbiri geçerli değildir, bu nedenle kullanım şablon yazarının sorumluluğundadır. ```php $latte->addExtension(new Latte\Essential\RawPhpExtension); ``` -Şablonlarda Çeviri .{data-version:3.0}{toc: TranslatorExtension} -================================================================ +Oluşturulan Kodu Kontrol Etme .{data-version:3.0.7} +=================================================== + +Latte, şablonları PHP koduna derler. Elbette, oluşturulan kodun sözdizimsel olarak geçerli olmasını sağlar. Ancak, üçüncü taraf uzantıları veya `RawPhpExtension` kullanırken Latte, oluşturulan dosyanın doğruluğunu garanti edemez. Ayrıca, PHP'de sözdizimsel olarak doğru ancak yasaklanmış (örneğin, `$this` değişkenine değer atama) ve PHP Compile Error'a neden olan kod yazmak mümkündür. Böyle bir işlemi şablonda yazarsanız, oluşturulan PHP koduna da girer. PHP'de iki yüzden fazla farklı yasaklanmış işlem olduğundan, Latte'nin bunları tespit etme iddiası yoktur. Yalnızca PHP'nin kendisi oluşturma sırasında bunlara dikkat çeker, bu da genellikle bir sorun teşkil etmez. + +Ancak, şablonun derlenmesi sırasında zaten hiçbir PHP Compile Error içermediğini bilmek istediğiniz durumlar vardır. Özellikle, şablonlar kullanıcılar tarafından düzenlenebiliyorsa veya [Sandbox] kullanıyorsanız. Bu durumda, şablonları derleme sırasında kontrol ettirin. Bu işlevselliği `Engine::enablePhpLint()` metoduyla etkinleştirirsiniz. Kontrol için PHP ikili dosyasını çağırması gerektiğinden, yolunu parametre olarak iletin: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); + +try { + $latte->compile('home.latte'); +} catch (Latte\CompileException $e) { + // Latte'deki hataları ve ayrıca PHP'deki Compile Error'ı yakalar + echo 'Hata: ' . $e->getMessage(); +} +``` + + +Yerel Ayarlar .{data-version:3.0.18}{toc: Locale} +================================================= + +Latte, sayıların, tarihlerin biçimlendirilmesini ve sıralamayı etkileyen yerel ayarları ayarlamanıza olanak tanır. `setLocale()` metodu kullanılarak ayarlanır. Yerel ayar tanımlayıcısı, PHP `intl` uzantısı tarafından kullanılan IETF dil etiketi standardını takip eder. Dil kodundan ve isteğe bağlı olarak ülke kodundan oluşur, ör. Amerika Birleşik Devletleri'nde İngilizce için `en_US`, Almanya'da Almanca için `de_DE` vb. + +```php +$latte = new Latte\Engine; +$latte->setLocale('tr_TR'); +``` + +Yerel ayar ayarı [localDate |filters#localDate], [sort |filters#sort], [number |filters#number] ve [bytes |filters#bytes] filtrelerini etkiler. + +.[note] +PHP `intl` uzantısını gerektirir. Latte'deki ayar, PHP'deki genel yerel ayar ayarını etkilemez. + + +Katı Mod .{data-version:3.0.8} +============================== -Eklemek için `TranslatorExtension` uzantısını kullanın [`{_...}` |tags#_], [`{translate}` |tags#translate] ve filtre [`translate` |filters#translate] şablona eklenir. Şablonun değerlerini veya bölümlerini diğer dillere çevirmek için kullanılırlar. Değiştirge, çeviriyi gerçekleştiren yöntemdir (PHP çağrılabiliri): +Katı ayrıştırma modunda Latte, kapanış HTML etiketlerinin eksik olup olmadığını kontrol eder ve ayrıca `$this` değişkeninin kullanımını yasaklar. Şu şekilde etkinleştirirsiniz: + +```php +$latte = new Latte\Engine; +$latte->setStrictParsing(); +``` + +`declare(strict_types=1)` başlığıyla şablon oluşturmayı şu şekilde etkinleştirirsiniz: + +```php +$latte = new Latte\Engine; +$latte->setStrictTypes(); +``` + + +Şablonlarda Çeviri .{toc: TranslatorExtension} +============================================== + +`TranslatorExtension` uzantısını kullanarak şablona [`{_...}` |tags#], [`{translate}` |tags#translate] etiketlerini ve [`translate` |filters#translate] filtresini eklersiniz. Değerleri veya şablonun bölümlerini diğer dillere çevirmek için kullanılırlar. Parametre olarak çeviriyi gerçekleştiren metodu (PHP çağrılabilir) belirtiriz: ```php class MyTranslator @@ -158,7 +207,7 @@ class MyTranslator public function translate(string $original): string { - // $this->lang'a göre $original'den $translated oluşturun + // $original'dan $this->lang'a göre $translated oluşturacağız return $translated; } } @@ -170,7 +219,7 @@ $extension = new Latte\Essential\TranslatorExtension( $latte->addExtension($extension); ``` -Çevirmen, şablon işlendiğinde çalışma zamanında çağrılır. Ancak Latte, şablon derlemesi sırasında tüm statik metinleri çevirebilir. Bu performans tasarrufu sağlar çünkü her dize yalnızca bir kez çevrilir ve sonuçta ortaya çıkan çeviri derlenmiş dosyaya yazılır. Bu, önbellek dizininde şablonun her dil için bir tane olmak üzere birden fazla derlenmiş sürümünü oluşturur. Bunu yapmak için sadece ikinci parametre olarak dili belirtmeniz gerekir: +Çevirmen, şablon oluşturulurken çalışma zamanında çağrılır. Latte ovšem umí všechny statické texty překládat už během kompilace šablony. Tím se ušetří výkon, protože každý řetězec se přeloží jen jednou a výsledný překlad se zapíše do zkompilované podoby. V adresáři s cache tak vznikne více zkompilovaných verzí šablony, jedna pro každý jazyk. K tomu stačí pouze uvést jazyk jako druhý parametr: ```php $extension = new Latte\Essential\TranslatorExtension( @@ -179,9 +228,9 @@ $extension = new Latte\Essential\TranslatorExtension( ); ``` -Statik metin ile örneğin `{_'hello'}` veya `{translate}hello{/translate}` kastedilmektedir. `{_$foo}` gibi statik olmayan metinler çalışma zamanında çevrilmeye devam edecektir. +Statik metin, örneğin `{_'merhaba'}` veya `{translate}merhaba{/translate}` anlamına gelir. Statik olmayan metinler, örneğin `{_$foo}`, çalışma zamanında çevrilmeye devam edecektir. -Şablon, `$params` dizisi olarak aldığı `{_$original, foo: bar}` veya `{translate foo: bar}` aracılığıyla çevirmene ek parametreler de iletebilir: +Çevirmene şablondan `{_$original, foo: bar}` veya `{translate foo: bar}` kullanarak ek parametreler de iletilebilir, bunlar `$params` dizisi olarak alınır: ```php public function translate(string $original, ...$params): string @@ -191,66 +240,73 @@ public function translate(string $original, ...$params): string ``` -Hata Ayıklama ve Tracy .[#toc-debugging-and-tracy] -================================================== +Hata Ayıklama ve Tracy +====================== -Latte, geliştirmeyi mümkün olduğunca keyifli hale getirmeye çalışır. Hata ayıklama amacıyla, üç etiket vardır [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak] ve [`{trace}` |tags#trace]. +Latte, geliştirmeyi sizin için olabildiğince keyifli hale getirmeye çalışır. Doğrudan hata ayıklama amacıyla üç etiket vardır: [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak] ve [`{trace}` |tags#trace]. -Harika [hata ayıklama aracı Tracy |tracy:] 'yi yüklerseniz ve Latte eklentisini etkinleştirirseniz en fazla konforu elde edersiniz: +En büyük rahatlığı, harika [hata ayıklama aracı Tracy |tracy:]'yi de yükleyip Latte için eklentiyi etkinleştirdiğinizde elde edersiniz: ```php -// Tracy'nin +// Tracy'yi açar Tracy\Debugger::enable(); $latte = new Latte\Engine; -// Tracy'nin uzantısını etkinleştirir +// Tracy için uzantıyı etkinleştirir $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension); ``` -Artık satır ve sütun vurgulamalı şablonlardaki hatalar da dahil olmak üzere tüm hataları düzgün bir kırmızı ekranda göreceksiniz[(video |https://github.com/nette/tracy/releases/tag/v2.9.0]). -Aynı zamanda, Tracy Bar olarak adlandırılan sağ alt köşede, tüm işlenmiş şablonları ve ilişkilerini (şablona veya derlenmiş koda tıklama imkanı dahil) ve değişkenleri açıkça görebileceğiniz Latte için bir sekme görünür: +Artık tüm hatalar, satır ve sütun vurgulamasıyla birlikte şablonlardaki hatalar da dahil olmak üzere net kırmızı ekranda görüntülenecektir ([video|https://github.com/nette/tracy/releases/tag/v2.9.0]). Aynı zamanda, sağ alt köşedeki Tracy Bar'da Latte için bir sekme görünecektir, burada oluşturulan tüm şablonlar ve karşılıklı ilişkileri (şablona veya derlenmiş koda tıklama seçeneği dahil) ve ayrıca değişkenler açıkça görülebilir: [* latte-debugging.webp *] -Latte şablonları okunabilir PHP koduna derlediğinden, IDE'nizde rahatça adım atabilirsiniz. +Latte şablonları anlaşılır PHP koduna derlediğinden, IDE'nizde rahatça adım adım ilerleyebilirsiniz. -Linter: Şablon Sözdizimini Doğrulama .{data-version:2.11}{toc: Linter} -====================================================================== +Linter: Şablon Sözdizimi Doğrulaması .{toc: Linter} +=================================================== -Linter aracı tüm şablonları gözden geçirmenize ve sözdizimi hatalarını kontrol etmenize yardımcı olacaktır. Konsoldan başlatılır: +Tüm şablonları gözden geçirmek ve sözdizimsel hata içerip içermediklerini kontrol etmek için Linter aracı size yardımcı olacaktır. Konsoldan çalıştırılır: ```shell -vendor/bin/latte-lint +vendor/bin/latte-lint ``` -Özel etiketler kullanıyorsanız, özelleştirilmiş Linter'ınızı da oluşturun, örneğin `custom-latte-lint`: +`--strict` parametresi [katı modu |#Katı Mod] etkinleştirir. + +Kendi özel etiketlerinizi kullanıyorsanız, kendi Linter sürümünüzü de oluşturun, ör. `custom-latte-lint`: ```php #!/usr/bin/env php scanDirectory($path); +$path = $argv[1] ?? '.'; -$engine = new Latte\Engine; -// bireysel uzantıları buraya kaydeder -$engine->addExtension(/* ... */); +$linter = new Latte\Tools\Linter; +$latte = $linter->getEngine(); +// buraya kendi uzantılarınızı ekleyin +$latte->addExtension(/* ... */); -$path = $argv[1]; -$linter = new Latte\Tools\Linter(engine: $engine); $ok = $linter->scanDirectory($path); exit($ok ? 0 : 1); ``` +Alternatif olarak, kendi `Latte\Engine` nesnenizi Linter'a iletebilirsiniz: + +```php +$latte = new Latte\Engine; +// burada $latte nesnesini yapılandıracağız +$linter = new Latte\Tools\Linter(engine: $latte); +``` + -Dizeden Şablon Yükleme .[#toc-loading-templates-from-a-string] -============================================================== +Şablonları Dizeden Yükleme +========================== -Belki de test amacıyla şablonları dosyalar yerine dizelerden yüklemeniz mi gerekiyor? [StringLoader |extending-latte#stringloader] size yardımcı olacaktır: +Test amacıyla dosyalar yerine dizelerden şablon yüklemeniz mi gerekiyor? [StringLoader |loaders#StringLoader] size yardımcı olacaktır: ```php $latte->setLoader(new Latte\Loaders\StringLoader([ @@ -262,10 +318,10 @@ $latte->render('main.file', $params); ``` -İstisna İşleyici .[#toc-exception-handler] -========================================== +İstisna İşleyici +================ -Beklenen istisnalar için kendi işleyicinizi tanımlayabilirsiniz. İçinde yükseltilen istisnalar [`{try}` |tags#try] ve [sandbox |sandbox] 'ta ona aktarılır. +Beklenen istisnalar için kendi özel işleyici işleyicinizi tanımlayabilirsiniz. [`{try}` |tags#try] içinde ve [sandbox|sandbox] içinde oluşan istisnalar ona iletilir. ```php $loggingHandler = function (Throwable $e, Latte\Runtime\Template $template) use ($logger) { @@ -277,17 +333,17 @@ $latte->setExceptionHandler($loggingHandler); ``` -Otomatik Yerleşim Arama .[#toc-automatic-layout-lookup] -======================================================= +Otomatik Düzen Arama +==================== -Etiketi kullanma [`{layout}` |template-inheritance#layout-inheritance]'de, şablon kendi üst şablonunu belirler. Düzenin otomatik olarak aranmasını sağlamak da mümkündür, bu da `{layout}` etiketini içermeleri gerekmeyeceğinden şablon yazmayı basitleştirecektir. +[`{layout}` |template-inheritance#Düzen Kalıtımı] etiketiyle şablon, üst şablonunu belirtir. Düzenin otomatik olarak aranmasını sağlamak da mümkündür, bu da şablon yazmayı basitleştirir, çünkü içlerinde `{layout}` etiketini belirtmek gerekmeyecektir. -Bu, aşağıdaki şekilde gerçekleştirilir: +Bu şu şekilde elde edilir: ```php $finder = function (Latte\Runtime\Template $template) { if (!$template->getReferenceType()) { - // üst şablon dosyasının yolunu döndürür + // düzen dosyasının yolunu döndürür return 'automatic.layout.latte'; } }; @@ -296,4 +352,4 @@ $latte = new Latte\Engine; $latte->addProvider('coreParentFinder', $finder); ``` -Şablonun bir düzene sahip olmaması gerekiyorsa, bunu `{layout none}` etiketiyle belirtecektir. +Şablonun bir düzeni olmaması gerekiyorsa, bunu `{layout none}` etiketiyle bildirir. diff --git a/latte/tr/extending-latte.texy b/latte/tr/extending-latte.texy index 26874d334b..41076fe3ac 100644 --- a/latte/tr/extending-latte.texy +++ b/latte/tr/extending-latte.texy @@ -1,285 +1,227 @@ -Uzayan Latte -************ +Latte'yi Genişletme +******************* .[perex] -Latte çok esnektir ve birçok şekilde genişletilebilir: özel filtreler, işlevler, etiketler, yükleyiciler vb. ekleyebilirsiniz. Size nasıl yapılacağını göstereceğiz. +Latte, genişletilebilirlik göz önünde bulundurularak tasarlanmıştır. Standart etiket, filtre ve fonksiyon seti birçok kullanım durumunu kapsasa da, genellikle kendi özel mantığınızı veya yardımcı araçlarınızı eklemeniz gerekir. Bu sayfa, Latte'yi projenizin gereksinimlerine mükemmel şekilde uyacak şekilde genişletmenin yollarına genel bir bakış sunar - basit yardımcılardan karmaşık yeni sözdizimine kadar. -Bu bölümde Latte'yi genişletmenin farklı yolları açıklanmaktadır. Yaptığınız değişiklikleri farklı projelerde yeniden kullanmak veya başkalarıyla paylaşmak istiyorsanız, [sözde uzantı oluşturmalısınız |creating-extension]. +Latte'yi Genişletme Yolları +=========================== -Roma'ya Kaç Yol Çıkar? .[#toc-how-many-roads-lead-to-rome] -========================================================== +Latte'yi özelleştirebileceğiniz ve genişletebileceğiniz ana yolların hızlı bir özeti aşağıdadır: -Latte'yi genişletmenin bazı yolları harmanlanabildiğinden, önce aralarındaki farkları açıklamaya çalışalım. Örnek olarak, üretilecek kelime sayısı verilen bir *Lorem ipsum* üretecini uygulamaya çalışalım. +- **[Özel Filtreler |Custom Filters]:** Şablon çıktısındaki verileri doğrudan biçimlendirmek veya dönüştürmek için (ör. `{$var|myFilter}`). Tarihleri biçimlendirme, metin düzenleme veya belirli kaçış işlemleri uygulama gibi görevler için idealdir. İçeriği anonim bir [`{block}` |tags#block] içine alıp üzerine özel bir filtre uygulayarak daha büyük HTML içerik bloklarını değiştirmek için de kullanabilirsiniz. +- **[Özel Fonksiyonlar |Custom Functions]:** Şablon içindeki ifadelerde çağrılabilecek yeniden kullanılabilir mantık eklemek için (ör. `{myFunction($arg1, $arg2)}`). Hesaplamalar, uygulama yardımcı fonksiyonlarına erişim veya küçük içerik parçaları oluşturmak için kullanışlıdır. +- **[Özel Etiketler |Custom Tags]:** Tamamen yeni dil yapıları oluşturmak için (`{mytag}...{/mytag}` veya `n:mytag`). Etiketler en fazla esnekliği sunar, özel yapılar tanımlamanıza, şablon ayrıştırmasını kontrol etmenize ve karmaşık oluşturma mantığı uygulamanıza olanak tanır. +- **[Derleyici Geçişleri |Compiler Passes]:** Ayrıştırmadan sonra ancak PHP kodu oluşturulmadan önce şablonun soyut sözdizimi ağacını (AST) değiştiren fonksiyonlar. Gelişmiş optimizasyonlar, güvenlik kontrolleri (Sandbox gibi) veya otomatik kod düzenlemeleri için kullanılır. +- **[Özel Yükleyiciler |loaders]:** Latte'nin şablon dosyalarını bulma ve yükleme şeklini değiştirmek için (ör. veritabanından, şifreli depolamadan vb. yükleme). -Latte dilinin ana yapısı etikettir. Latte'yi yeni bir etiketle genişleterek bir üreteç uygulayabiliriz: +Doğru genişletme yöntemini seçmek çok önemlidir. Karmaşık bir etiket oluşturmadan önce, daha basit bir filtre veya fonksiyonun yeterli olup olmayacağını düşünün. Bunu bir örnekle gösterelim: oluşturulacak kelime sayısını argüman olarak alan bir *Lorem ipsum* üreteci uygulama. -```latte -{lipsum 40} -``` - -Etiket harika çalışacaktır. Ancak, etiket biçimindeki oluşturucu bir ifadede kullanılamayacağı için yeterince esnek olmayabilir. Bu arada, pratikte etiket oluşturmaya nadiren ihtiyaç duyarsınız; ve bu iyi bir haber, çünkü etiketler genişletmek için daha karmaşık bir yoldur. - -Tamam, etiket yerine bir filtre oluşturmayı deneyelim: - -```latte -{=40|lipsum} -``` - -Yine geçerli bir seçenek. Ancak filtre geçirilen değeri başka bir şeye dönüştürmelidir. Burada, dönüştürmek istediğimiz değer olarak değil, filtre argümanı olarak üretilen kelime sayısını gösteren `40` değerini kullanıyoruz. +- **Etiket olarak mı?** `{lipsum 40}` - Mümkün, ancak etiketler kontrol yapıları veya karmaşık işaretleme oluşturmak için daha uygundur. Etiketler doğrudan ifadelerde kullanılamaz. +- **Filtre olarak mı?** `{=40|lipsum}` - Teknik olarak çalışır, ancak filtreler girdi değerini *dönüştürmek* için tasarlanmıştır. Burada `40` dönüştürülen bir değer değil, bir *argümandır*. Bu anlamsal olarak yanlış hissettirir. +- **Fonksiyon olarak mı?** `{lipsum(40)}` - Bu en doğal çözümdür! Fonksiyonlar argüman alır ve değer döndürür, bu da herhangi bir ifadede kullanım için idealdir: `{var $text = lipsum(40)}`. -Öyleyse fonksiyon kullanmayı deneyelim: +**Genel öneri:** Hesaplamalar/üretim için fonksiyonları, dönüştürme için filtreleri ve yeni dil yapıları veya karmaşık işaretleme için etiketleri kullanın. AST manipülasyonu için geçişleri ve şablonları almak için yükleyicileri kullanın. -```latte -{lipsum(40)} -``` -İşte bu kadar! Bu özel örnek için, bir fonksiyon oluşturmak kullanılacak ideal uzantı noktasıdır. Örneğin, bir ifadenin kabul edildiği her yerde onu çağırabilirsiniz: +Doğrudan Kayıt +============== -```latte -{var $text = lipsum(40)} -``` +Projeye özgü yardımcı araçlar veya hızlı genişletmeler için Latte, filtrelerin ve fonksiyonların doğrudan `Latte\Engine` nesnesine kaydedilmesine olanak tanır. - -Filtreler .[#toc-filters] -========================= - -Adını ve işlev gibi herhangi bir PHP çağrılabilirini kaydederek bir süzgeç oluşturun: +Bir filtre kaydetmek için `addFilter()` metodunu kullanın. Filtre fonksiyonunuzun ilk argümanı `|` karakterinden önceki değer olacak ve sonraki argümanlar iki nokta üst üste `:` işaretinden sonra iletilenler olacaktır. ```php $latte = new Latte\Engine; -$latte->addFilter('shortify', fn(string $s) => mb_substr($s, 0, 10)); // metni 10 karaktere kısaltır -``` -Bu durumda filtrenin ek bir parametre alması daha iyi olacaktır: +// Filtre tanımı (çağrılabilir nesne: fonksiyon, statik metot vb.) +$myTruncate = fn(string $s, int $length = 50) => mb_substr($s, 0, $length); -```php -$latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); -``` - -Bunun gibi bir şablonda kullanırız: +// Kayıt +$latte->addFilter('truncate', $myTruncate); -```latte -

                                                                                                                            {$text|shortify}

                                                                                                                            -

                                                                                                                            {$text|shortify:100}

                                                                                                                            +// Şablonda kullanım: {$text|truncate} veya {$text|truncate:100} ``` -Gördüğünüz gibi, fonksiyon `|` as the first argument and the arguments passed to the filter after `:` borusundan önce filtrenin sol tarafını sonraki argümanlar olarak alır. - -Elbette, filtreyi temsil eden fonksiyon herhangi bir sayıda parametre kabul edebilir ve değişken parametreler de desteklenir. - - -Sınıfı Kullanan Filtreler .[#toc-filters-using-the-class] ---------------------------------------------------------- - -Bir filtre tanımlamanın ikinci yolu [sınıf kullanmaktır |develop#Parameters as a class]. `TemplateFilter` niteliği ile bir yöntem oluşturuyoruz: +Ayrıca, istenen ada göre dinamik olarak çağrılabilir filtre nesneleri sağlayan bir **Filtre Yükleyici** de kaydedebilirsiniz: ```php -class TemplateParameters -{ - public function __construct( - // parametreler - ) {} - - #[Latte\Attributes\TemplateFilter] - public function shortify(string $s, int $len = 10): string - { - return mb_substr($s, 0, $len); - } -} - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); +$latte->addFilterLoader(fn(string $name) => /* çağrılabilir nesne veya null döndürür */); ``` -PHP 7.x ve Latte 2.x kullanıyorsanız, öznitelik yerine `/** @filter */` ek açıklamasını kullanın. - -Filtre Yükleyici .[#toc-filter-loader] --------------------------------------- - -Süzgeçleri tek tek kaydetmek yerine, süzgeç adını argüman olarak kullanarak çağrılan ve PHP çağrılabilirini veya null döndüren bir işlev olan yükleyici adı verilen bir işlev oluşturabilirsiniz. +Şablon ifadelerinde kullanılabilir bir fonksiyon kaydetmek için `addFunction()` metodunu kullanın. ```php -$latte->addFilterLoader([new Filters, 'load']); +$latte = new Latte\Engine; +// Fonksiyon tanımı +$isWeekend = fn(DateTimeInterface $date) => $date->format('N') >= 6; -class Filters -{ - public function load(string $filter): ?callable - { - if (in_array($filter, get_class_methods($this))) { - return [$this, $filter]; - } - return null; - } - - public function shortify($s, $len = 10) - { - return mb_substr($s, 0, $len); - } - - // ... -} +// Kayıt +$latte->addFunction('isWeekend', $isWeekend); + +// Şablonda kullanım: {if isWeekend($myDate)}Hafta sonu!{/if} ``` +Daha fazla bilgi için [Özel Filtreler Oluşturma |custom-filters] ve [Fonksiyonlar |custom-functions] bölümlerine bakın. -Bağlamsal Filtreler .[#toc-contextual-filters] ----------------------------------------------- -Bağlamsal bir filtre, ilk parametrede [api:Latte\Runtime\FilterInfo] nesnesini kabul eden ve ardından klasik filtrelerde olduğu gibi diğer parametreleri kabul eden bir filtredir. Aynı şekilde kaydedilir, Latte filtrenin bağlamsal olduğunu kendisi tanır: +Sağlam Yöntem: Latte Uzantısı .{toc: Latte Extension} +===================================================== -```php -use Latte\Runtime\FilterInfo; +Doğrudan kayıt basit olsa da, Latte uzantılarını paketlemek ve dağıtmak için standart ve önerilen yol **Extension** sınıfları aracılığıyladır. Extension, birden çok etiket, filtre, fonksiyon, derleyici geçişi ve diğer öğeleri kaydetmek için merkezi bir yapılandırma noktası olarak hizmet eder. -$latte->addFilter('foo', function (FilterInfo $info, string $str): string { - // ... -}); -``` +Neden Uzantıları Kullanmalısınız? -Bağlam filtreleri, `$info->contentType` değişkeninde aldıkları içerik türünü algılayabilir ve değiştirebilir. Filtre klasik olarak bir değişken üzerinden çağrılırsa (örneğin `{$var|foo}`), `$info->contentType` null içerecektir. +- **Organizasyon:** İlgili uzantıları (belirli bir işlev için etiketler, filtreler vb.) tek bir sınıfta bir arada tutar. +- **Yeniden Kullanılabilirlik ve Paylaşım:** Uzantılarınızı diğer projelerde kullanmak veya toplulukla paylaşmak (ör. Composer aracılığıyla) için kolayca paketleyin. +- **Tam Güç:** Özel etiketler ve derleyici geçişleri *yalnızca* Uzantılar aracılığıyla kaydedilebilir. -Filtre öncelikle girdi dizesinin içerik türünün desteklenip desteklenmediğini kontrol etmelidir. Ayrıca bunu değiştirebilir. Metin (veya null) kabul eden ve HTML döndüren bir filtre örneği: + +Uzantı Kaydı +------------ + +Uzantı, Latte'ye `addExtension()` metoduyla (veya [yapılandırma dosyası |application:configuration#Latte Şablonları] aracılığıyla) kaydedilir: ```php -use Latte\Runtime\FilterInfo; - -$latte->addFilter('money', function (FilterInfo $info, float $amount): string { - // önce girdinin içerik türünün metin olup olmadığını kontrol ederiz - if (!in_array($info->contentType, [null, ContentType::Text])) { - throw new Exception("Filter |money used in incompatible content type $info->contentType."); - } - - // içerik türünü HTML olarak değiştir - $info->contentType = ContentType::Html; - return "$num Kč"; -}); +$latte = new Latte\Engine; +$latte->addExtension(new MyProjectExtension); ``` -.[note] -Bu durumda, filtre verilerin doğru şekilde kaçmasını sağlamalıdır. +Birden fazla uzantı kaydederseniz ve bunlar aynı ada sahip etiketler, filtreler veya fonksiyonlar tanımlarsa, en son eklenen uzantı öncelikli olur. Bu aynı zamanda uzantılarınızın yerel etiketleri/filtreleri/fonksiyonları geçersiz kılabileceği anlamına gelir. -[Bloklar |tags#block] üzerinde kullanılan tüm filtreler (örn. `{block|foo}...{/block}`) bağlamsal olmalıdır. +Sınıfta bir değişiklik yaptığınızda ve otomatik yenileme kapalı değilse, Latte şablonlarınızı otomatik olarak yeniden derleyecektir. -Fonksiyonlar .[#toc-functions] -============================== +Uzantı Oluşturma +---------------- -Öntanımlı olarak, sandbox devre dışı bırakmadığı sürece tüm yerel PHP işlevleri Latte'de kullanılabilir. Ancak kendi işlevlerinizi de tanımlayabilirsiniz. Bunlar yerel işlevleri geçersiz kılabilir. +Özel bir uzantı oluşturmak için [api:Latte\Extension] sınıfından miras alan bir sınıf oluşturmanız gerekir. Böyle bir uzantının nasıl göründüğüne dair bir fikir edinmek için yerleşik "CoreExtension":https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php 'e göz atın. -Adını ve herhangi bir PHP çağrılabilirini kaydederek bir işlev oluşturun: +Uygulayabileceğiniz metotlara bakalım: -```php -$latte = new Latte\Engine; -$latte->addFunction('random', function (...$args) { - return $args[array_rand($args)]; -}); -``` -Bu durumda kullanım PHP fonksiyonunun çağrılmasıyla aynıdır: +beforeCompile(Latte\Engine $engine): void .[method] +--------------------------------------------------- -```latte -{random(apple, orange, lemon)} // prints for example: apple -``` +Şablon derlenmeden önce çağrılır. Metot, örneğin derlemeyle ilgili başlatmalar için kullanılabilir. -Sınıfı Kullanan İşlevler .[#toc-functions-using-the-class] ----------------------------------------------------------- +getTags(): array .[method] +-------------------------- -Bir fonksiyon tanımlamanın ikinci yolu [sınıf kullanmaktır |develop#Parameters as a class]. `TemplateFunction` niteliği ile bir metot oluşturuyoruz: +Şablon derlenirken çağrılır. Etiket ayrıştırma fonksiyonları olan *etiket adı => çağrılabilir nesne* şeklinde ilişkisel bir dizi döndürür. [Daha fazla bilgi |custom-tags]. ```php -class TemplateParameters +public function getTags(): array { - public function __construct( - // parametreler - ) {} - - #[Latte\Attributes\TemplateFunction] - public function random(...$args) - { - return $args[array_rand($args)]; - } + return [ + 'foo' => FooNode::create(...), + 'bar' => BarNode::create(...), + 'n:baz' => NBazNode::create(...), + // ... + ]; } - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); ``` -PHP 7.x ve Latte 2.x kullanıyorsanız, öznitelik yerine `/** @function */` ek açıklamasını kullanın. +`n:baz` etiketi, saf bir [n:nitelik |syntax#n:nitelikler]'i temsil eder, yani yalnızca nitelik olarak yazılabilen bir etikettir. +`foo` ve `bar` etiketleri için Latte, bunların çift etiket olup olmadığını otomatik olarak tanır ve eğer öyleyse, `n:inner-foo` ve `n:tag-foo` önekli varyantlar da dahil olmak üzere n:nitelikler kullanılarak otomatik olarak yazılabilirler. -Yükleyiciler .[#toc-loaders] -============================ +Bu tür n:niteliklerin yürütme sırası, `getTags()` metodu tarafından döndürülen dizideki sıralarına göre belirlenir. Bu nedenle, `n:foo` her zaman `n:bar`'dan önce yürütülür, HTML etiketindeki nitelikler ters sırada listelenmiş olsa bile (`
                                                                                                                            `). -Yükleyiciler, şablonları dosya sistemi gibi bir kaynaktan yüklemekten sorumludur. `setLoader()` yöntemi kullanılarak ayarlanırlar: +Birden fazla uzantı arasında n:niteliklerin sırasını belirlemeniz gerekiyorsa, `order()` yardımcı metodunu kullanın; burada `before` xor `after` parametresi, hangi etiketlerin etiketten önce veya sonra sıralandığını belirtir. ```php -$latte->setLoader(new MyLoader); +public function getTags(): array +{ + return [ + 'foo' => self::order(FooNode::create(...), before: 'bar'), + 'bar' => self::order(BarNode::create(...), after: ['block', 'snippet']), + ]; +} ``` -Yerleşik yükleyiciler şunlardır: +getPasses(): array .[method] +---------------------------- -FileLoader .[#toc-fileloader] ------------------------------ +Şablon derlenirken çağrılır. AST'yi dolaşan ve değiştiren sözde [derleyici geçişleri |compiler-passes]'ni temsil eden fonksiyonlar olan *geçiş adı => çağrılabilir nesne* şeklinde ilişkisel bir dizi döndürür. -Varsayılan yükleyici. Şablonları dosya sisteminden yükler. - -Temel dizin ayarlanarak dosyalara erişim kısıtlanabilir: +Burada da `order()` yardımcı metodu kullanılabilir. `before` veya `after` parametrelerinin değeri, hepsinden önce/sonra anlamına gelen `*` olabilir. ```php -$latte->setLoader(new Latte\Loaders\FileLoader($templateDir)); -$latte->render('test.latte'); +public function getPasses(): array +{ + return [ + 'optimize' => Passes::optimizePass(...), + 'sandbox' => self::order($this->sandboxPass(...), before: '*'), + // ... + ]; +} ``` -StringLoader .[#toc-stringloader] ---------------------------------- +beforeRender(Latte\Engine $engine): void .[method] +-------------------------------------------------- -Şablonları dizelerden yükler. Bu yükleyici birim testi için çok kullanışlıdır. Ayrıca tüm şablonları tek bir PHP dosyasında saklamanın mantıklı olabileceği küçük projeler için de kullanılabilir. +Her şablon oluşturulmadan önce çağrılır. Metot, örneğin oluşturma sırasında kullanılan değişkenleri başlatmak için kullanılabilir. -```php -$latte->setLoader(new Latte\Loaders\StringLoader([ - 'main.file' => '{include other.file}', - 'other.file' => '{if true} {$var} {/if}', -])); -$latte->render('main.file'); -``` +getFilters(): array .[method] +----------------------------- -Basitleştirilmiş kullanım: +Şablon oluşturulmadan önce çağrılır. Filtreleri *filtre adı => çağrılabilir nesne* şeklinde ilişkisel bir dizi olarak döndürür. [Daha fazla bilgi |custom-filters]. ```php -$template = '{if true} {$var} {/if}'; -$latte->setLoader(new Latte\Loaders\StringLoader); -$latte->render($template); +public function getFilters(): array +{ + return [ + 'batch' => $this->batchFilter(...), + 'trim' => $this->trimFilter(...), + // ... + ]; +} ``` -Özel Yükleyici Oluşturma .[#toc-creating-a-custom-loader] ---------------------------------------------------------- - -Yükleyici, [api:Latte\Loader] arayüzünü uygulayan bir sınıftır. +getFunctions(): array .[method] +------------------------------- +Şablon oluşturulmadan önce çağrılır. Fonksiyonları *fonksiyon adı => çağrılabilir nesne* şeklinde ilişkisel bir dizi olarak döndürür. [Daha fazla bilgi |custom-functions]. -Etiketler .[#toc-tags] -====================== +```php +public function getFunctions(): array +{ + return [ + 'clamp' => $this->clampFunction(...), + 'divisibleBy' => $this->divisibleByFunction(...), + // ... + ]; +} +``` -Şablonlama motorunun en ilginç özelliklerinden biri, etiketleri kullanarak yeni dil yapıları tanımlama yeteneğidir. Bu aynı zamanda daha karmaşık bir işlevdir ve Latte'nin dahili olarak nasıl çalıştığını anlamanız gerekir. -Ancak çoğu durumda etikete gerek yoktur: -- eğer bir çıktı üretmesi gerekiyorsa, bunun yerine [fonksiyon |#functions] kullanın -- bazı girdileri değiştirip döndürmek içinse, bunun yerine [filtre |#filters] kullanın -- bir metin alanını düzenlemek olsaydı, onu bir [`{block}` |tags#block] etiketi ve bir [filtre kullanın|#Contextual Filters] -- eğer herhangi bir çıktı vermeyip sadece bir fonksiyon çağırması gerekiyorsa [`{do}` |tags#do] +getProviders(): array .[method] +------------------------------- -Eğer hala bir etiket oluşturmak istiyorsanız, harika! Tüm temel bilgileri [Uzantı Oluşturma |creating-extension] bölümünde bulabilirsiniz. +Şablon oluşturulmadan önce çağrılır. Genellikle çalışma zamanında etiketler tarafından kullanılan sağlayıcılar dizisini döndürür. Bunlara `$this->global->...` aracılığıyla erişilir. [Daha fazla bilgi |custom-tags#Sağlayıcılarla Tanışma]. +```php +public function getProviders(): array +{ + return [ + 'myFoo' => $this->foo, + 'myBar' => $this->bar, + // ... + ]; +} +``` -Derleyici Geçişleri .[#toc-compiler-passes] -=========================================== -Derleyici geçişleri AST'leri değiştiren veya içlerindeki bilgileri toplayan işlevlerdir. Örneğin Latte'de bir sandbox şu şekilde uygulanır: AST'nin tüm düğümlerini dolaşır, işlev ve yöntem çağrılarını bulur ve bunları kontrollü çağrılarla değiştirir. +getCacheKey(Latte\Engine $engine): mixed .[method] +-------------------------------------------------- -Etiketlerde olduğu gibi, bu daha karmaşık bir işlevdir ve Latte'nin kaputun altında nasıl çalıştığını anlamanız gerekir. Tüm temel bilgileri [Uzantı Oluşturma |creating-extension] bölümünde bulabilirsiniz. +Şablon oluşturulmadan önce çağrılır. Dönüş değeri, hash'i derlenmiş şablon dosyasının adında bulunan anahtarın bir parçası haline gelir. Bu nedenle, farklı dönüş değerleri için Latte farklı önbellek dosyaları oluşturur. diff --git a/latte/tr/filters.texy b/latte/tr/filters.texy index 47414f851f..586926a494 100644 --- a/latte/tr/filters.texy +++ b/latte/tr/filters.texy @@ -2,111 +2,113 @@ Latte Filtreleri **************** .[perex] -Filtreler, verileri istediğimiz biçimde değiştiren veya biçimlendiren işlevlerdir. Bu, mevcut olan yerleşik filtrelerin bir özetidir. +Şablonlarda, verileri nihai biçimine ayarlamaya veya yeniden biçimlendirmeye yardımcı olan fonksiyonları kullanabiliriz. Bunlara *filtreler* diyoruz. .[table-latte-filters] -|## Dize / dizi dönüşümü -| `batch` | [doğrusal verilerin bir tabloda listelenmesi |#batch] -| `breakLines` | [Tüm yeni satırlardan önce HTML satır sonlarını ekler |#breakLines] -| `bytes` | [bayt cinsinden biçim boyutu |#bytes] -| `clamp` | [değeri aralığa sıkıştırır |#clamp] -| `dataStream` | [Veri URI protokol dönüşümü |#datastream] -| `date` | [tarihi biçimlendirir |#date] -| `explode` | [bir dizeyi verilen sınırlayıcıya göre böler |#explode] -| `first` | [dizinin ilk elemanını veya karakter dizisini döndürür |#first] -| `implode` | [bir diziyi bir dizeye birleştirir|#implode] -| `indent` | [metni soldan sekme sayısı kadar girintiler |#indent] -| `join` | [bir diziyi bir dizeye birleştirir|#implode] -| `last` | [dizinin son elemanını veya karakter dizisini döndürür |#last] -| `length` | [bir dize veya dizinin uzunluğunu döndürür |#length] -| `number` | [format numarası |#number] -| `padLeft` | [dizeyi soldan verilen uzunluğa tamamlar |#padLeft] -| `padRight` | [dizeyi sağdan verilen uzunluğa tamamlar |#padRight] -| `random` | [dizinin rastgele elemanını veya karakter dizisini döndürür |#random] -| `repeat` | [dizeyi tekrarlar |#repeat] -| `replace` | [arama dizesinin tüm geçtiği yerleri değiştirme ile değiştirir |#replace] -| `replaceRE` | [düzenli ifadeye göre tüm oluşumları değiştirir |#replaceRE] -| `reverse` | [bir UTF-8 dizesini veya dizisini tersine çevirir |#reverse] -| `slice` | [bir dizi veya dizenin bir dilimini çıkarır |#slice] -| `sort` | [bir diziyi sıralar |#sort] -| `spaceless` | [boşluksuz |tags] etikete benzer şekilde boşlukları [kaldırır |#spaceless] -| `split` | [bir dizeyi verilen sınırlayıcıya göre böler |#explode] -| `strip` | [boşlukları kaldırır |#spaceless] -| `stripHtml` | [HTML etiketlerini kaldırır ve HTML varlıklarını metne dönüştürür |#stripHtml] -| `substr` | [dizenin bir kısmını döndürür |#substr] -| `trim` | [dizeden boşlukları çıkarır |#trim] -| `translate` | [diğer dillere çeviri |#translate] -| `truncate` | [tüm kelimeleri koruyarak uzunluğu kısaltır |#truncate] -| `webalize` | [UTF-8 dizesini URL'de kullanılan şekle göre ayarlar |#webalize] +|## Dönüşüm +| `batch` | [doğrusal verilerin tabloya dökümü |#batch] +| `breakLines` | [Satır sonlarına HTML satır sonu ekler |#breakLines] +| `bytes` | [boyutu bayt cinsinden biçimlendirir |#bytes] +| `clamp` | [değeri belirtilen aralıkla sınırlar |#clamp] +| `dataStream` | [Data URI protokolü için dönüştürme |#dataStream] +| `date` | [tarih ve saati biçimlendirir |#date] +| `explode` | [karakter dizisini ayırıcıya göre diziye böler |#explode] +| `first` | [dizinin ilk öğesini veya karakter dizisinin ilk karakterini döndürür |#first] +| `group` | [verileri çeşitli kriterlere göre gruplandırır |#group] +| `implode` | [diziyi bir karakter dizisi olarak birleştirir |#implode] +| `indent` | [metni soldan belirtilen sayıda sekme kadar girintiler |#indent] +| `join` | [diziyi bir karakter dizisi olarak birleştirir |#implode] +| `last` | [dizinin son öğesini veya karakter dizisinin son karakterini döndürür |#last] +| `length` | [karakter dizisinin karakter cinsinden veya dizinin uzunluğunu döndürür |#length] +| `localDate` | [tarih ve saati yerel ayara göre biçimlendirir |#localDate] +| `number` | [sayıyı biçimlendirir |#number] +| `padLeft` | [karakter dizisini soldan istenen uzunluğa tamamlar |#padLeft] +| `padRight` | [karakter dizisini sağdan istenen uzunluğa tamamlar |#padRight] +| `random` | [dizinin rastgele bir öğesini veya karakter dizisinin rastgele bir karakterini döndürür |#random] +| `repeat` | [karakter dizisini tekrarlar |#repeat] +| `replace` | [aranan karakter dizisinin geçtiği yerleri değiştirir |#replace] +| `replaceRE` | [düzenli ifadeye göre geçtiği yerleri değiştirir |#replaceRE] +| `reverse` | [UTF-8 karakter dizisini veya diziyi ters çevirir |#reverse] +| `slice` | [dizinin veya karakter dizisinin bir bölümünü çıkarır |#slice] +| `sort` | [diziyi sıralar |#sort] +| `spaceless` | [boşlukları kaldırır |#spaceless], [spaceless |tags] etiketi gibi +| `split` | [karakter dizisini ayırıcıya göre diziye böler |#explode] +| `strip` | [boşlukları kaldırır |#spaceless] +| `stripHtml` | [HTML etiketlerini kaldırır ve HTML varlıklarını karakterlere dönüştürür |#stripHtml] +| `substr` | [karakter dizisinin bir bölümünü döndürür |#substr] +| `trim` | [başlangıç ve sondaki boşlukları veya diğer karakterleri kaldırır |#trim] +| `translate` | [diğer dillere çeviri |#translate] +| `truncate` | [kelimeleri koruyarak uzunluğu kısaltır |#truncate] +| `webalize` | [UTF-8 karakter dizisini URL'de kullanılan biçime dönüştürür |#webalize] .[table-latte-filters] -|## Harf muhafazası -| `capitalize` | [küçük harf, her kelimenin ilk harfi büyük harf |#capitalize] -| `firstUpper` | [ilk harfi büyük yapar |#firstUpper] -| `lower` | [bir dizeyi küçük harf yapar |#lower] -| `upper` | [bir dizeyi büyük harf yapar |#upper] +|## Harf Büyüklüğü +| `capitalize` | [küçük harf, kelimelerin ilk harfi büyük |#capitalize] +| `firstUpper` | [ilk harfi büyük harfe dönüştürür |#firstUpper] +| `lower` | [küçük harfe dönüştürür |#lower] +| `upper` | [büyük harfe dönüştürür |#upper] .[table-latte-filters] -|## Sayıları yuvarlama -| `ceil` | [bir sayıyı belirli bir hassasiyete kadar yuvarlar |#ceil] -| `floor` | [bir sayıyı belirli bir hassasiyete yuvarlar |#floor] -| `round` | [bir sayıyı belirli bir hassasiyete yuvarlar |#round] +|## Yuvarlama +| `ceil` | [sayıyı belirtilen hassasiyete göre yukarı yuvarlar |#ceil] +| `floor` | [sayıyı belirtilen hassasiyete göre aşağı yuvarlar |#floor] +| `round` | [sayıyı belirtilen hassasiyete göre yuvarlar |#round] .[table-latte-filters] -|## Kaçış -| `escapeUrl` | [URL'deki parametreyi kaçar |#escapeUrl] -| `noescape` | [bir değişkeni kaçış yapmadan yazdırır |#noescape] -| `query` | [URL'de bir sorgu dizesi oluşturur |#query] +|## Kaçış (Escaping) +| `escapeUrl` | [URL'deki parametreyi kaçış işlemine tabi tutar |#escapeUrl] +| `noescape` | [değişkeni kaçış işlemi yapmadan yazdırır |#noescape] +| `query` | [URL'de sorgu dizesi oluşturur |#query] -Ayrıca HTML (`escapeHtml` ve `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) ve iCalendar (`escapeICal`) için Latte'nin [bağlama duyarlı kaç |safety-first#Context-aware escaping] ış sayesinde kendi kullandığı kaçış filtreleri vardır ve bunları yazmanıza gerek yoktur. +Ayrıca, Latte'nin [bağlama duyarlı kaçış |safety-first#Bağlama Duyarlı Kaçış] sayesinde kendisinin kullandığı ve sizin yazmanıza gerek olmayan HTML (`escapeHtml` ve `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) ve iCalendar (`escapeICal`) için kaçış filtreleri de vardır. .[table-latte-filters] |## Güvenlik -| `checkUrl` | [href niteliği içinde kullanılmak üzere dizeyi sterilize eder|#checkUrl] -| `nocheck` | [otomatik URL sanitizasyonunu engeller |#nocheck] +| `checkUrl` | [URL adresini tehlikeli girdilerden temizler |#checkUrl] +| `nocheck` | [URL adresinin otomatik temizlenmesini önler |#nocheck] -Latte `src` ve `href` özniteliklerini [otomatik olarak kontrol |safety-first#link checking] eder, böylece `checkUrl` filtresini kullanmanıza neredeyse gerek kalmaz. +Latte `src` ve `href` niteliklerini [otomatik olarak kontrol eder |safety-first#Bağlantı Kontrolü], bu nedenle `checkUrl` filtresini neredeyse hiç kullanmanız gerekmez. .[note] -Tüm yerleşik filtreler UTF-8 kodlu dizelerle çalışır. +Tüm varsayılan filtreler UTF-8 kodlamasındaki karakter dizileri için tasarlanmıştır. -Kullanım .[#toc-usage] -====================== +Kullanım +======== -Latte, boru işareti gösterimini kullanarak filtreleri çağırmaya izin verir (önceki boşluğa izin verilir): +Filtreler dikey çubuktan sonra yazılır (önünde bir boşluk olabilir): ```latte

                                                                                                                            {$heading|upper}

                                                                                                                            ``` -Filtreler zincirleme olabilir, bu durumda soldan sağa doğru sırayla uygulanırlar: +Filtreler (eski sürümlerde yardımcılar) zincirlenebilir ve ardından soldan sağa doğru sırayla uygulanır: ```latte

                                                                                                                            {$heading|lower|capitalize}

                                                                                                                            ``` -Parametreler filtre adından sonra iki nokta üst üste veya virgülle ayrılmış olarak konur: +Parametreler, filtre adından sonra iki nokta üst üste veya virgülle ayrılarak girilir: ```latte

                                                                                                                            {$heading|truncate:20,''}

                                                                                                                            ``` -İfade üzerinde filtreler uygulanabilir: +Filtreler bir ifadeye de uygulanabilir: ```latte {var $name = ($title|upper) . ($subtitle|lower)} ``` -[Özel filtreler |extending-latte#filters] bu şekilde kaydedilebilir: +[Özel filtreler|custom-filters] şu şekilde kaydedilebilir: ```php $latte = new Latte\Engine; $latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -Bunun gibi bir şablonda kullanırız: +Şablonda daha sonra şu şekilde çağrılır: ```latte

                                                                                                                            {$text|shortify}

                                                                                                                            @@ -114,18 +116,18 @@ Bunun gibi bir şablonda kullanırız: ``` -Filtreler .[#toc-filters] -========================= +Filtreler +========= -batch(int length, mixed item): array .[filter]{data-version:2.7} ----------------------------------------------------------------- -Doğrusal verilerin bir tablo biçiminde listelenmesini basitleştiren filtre. Verilen sayıda öğe içeren bir dizi döndürür. İkinci bir parametre sağlarsanız, bu son satırdaki eksik öğeleri doldurmak için kullanılır. +batch(int $length, mixed $item): array .[filter] +------------------------------------------------ +Doğrusal verilerin tablo şeklinde listelenmesini kolaylaştıran bir filtre. Belirtilen sayıda öğe içeren bir dizi dizisi döndürür. İkinci bir parametre belirtirseniz, son satırdaki eksik öğeleri tamamlamak için kullanılır. ```latte {var $items = ['a', 'b', 'c', 'd', 'e']}
                                                                                                                            İç döngü
                                                                                                                            -{foreach ($items|batch: 3, 'No item') as $row} +{foreach ($items|batch: 3, 'Öğe yok') as $row} {foreach $row as $column} @@ -135,7 +137,7 @@ Doğrusal verilerin bir tablo biçiminde listelenmesini basitleştiren filtre. V
                                                                                                                            {$column}
                                                                                                                            ``` -Baskılar: +Yazdırır: ```latte @@ -147,25 +149,27 @@ Baskılar: - +
                                                                                                                            d eNo itemÖğe yok
                                                                                                                            ``` +Ayrıca bkz. [#group] ve [iterateWhile |tags#iterateWhile] etiketi. + breakLines .[filter] -------------------- -Tüm yeni satırlardan önce HTML satır sonlarını ekler. +Her yeni satır karakterinden önce HTML `
                                                                                                                            ` etiketini ekler. ```latte -{var $s = "Text & with \n newline"} -{$s|breakLines} {* çıkışlar "Text & with
                                                                                                                            \n newline" *} +{var $s = "Metin & ile \n yeni satır"} +{$s|breakLines} {* "Metin & ile
                                                                                                                            \n yeni satır" yazdırır *} ``` -bytes(int precision = 2) .[filter] ----------------------------------- -Bayt cinsinden bir boyutu insan tarafından okunabilir biçimde biçimlendirir. +bytes(int $precision=2) .[filter] +--------------------------------- +Boyutu bayt cinsinden insan tarafından okunabilir bir biçime dönüştürür. Eğer [yerel ayar |develop#Locale] ayarlanmışsa, ilgili ondalık ve binlik ayırıcılar kullanılır. ```latte {$size|bytes} 0 B, 1.25 GB, … @@ -173,72 +177,72 @@ Bayt cinsinden bir boyutu insan tarafından okunabilir biçimde biçimlendirir. ``` -ceil(int precision = 0) .[filter] ---------------------------------- -Bir sayıyı belirli bir hassasiyete kadar yuvarlar. +ceil(int $precision=0) .[filter] +-------------------------------- +Sayıyı belirtilen hassasiyete göre yukarı yuvarlar. ```latte -{=3.4|ceil} {* çıkışlar 4 *} -{=135.22|ceil:1} {* çıktılar 135.3 *} -{=135.22|ceil:3} {* çıktılar 135.22 *} +{=3.4|ceil} {* 4 yazdırır *} +{=135.22|ceil:1} {* 135.3 yazdırır *} +{=135.22|ceil:3} {* 135.22 yazdırır *} ``` -Ayrıca bkz. [zemin |#floor], [yuvarlak |#round]. +Ayrıca bkz. [#floor], [#round]. capitalize .[filter] -------------------- -Değerin baş harfli bir sürümünü döndürür. Kelimeler büyük harflerle başlar, geri kalan tüm karakterler küçüktür. PHP eklentisi gerektirir `mbstring`. +Kelimeler büyük harfle başlar, geri kalan tüm karakterler küçük harf olur. `mbstring` PHP eklentisini gerektirir. ```latte -{='i like LATTE'|capitalize} {* çıktılar 'I Like Latte' *} +{='LATTE severim'|capitalize} {* 'Latte Severim' yazdırır *} ``` -Ayrıca bkz. [firstUpper |#firstUpper], [lower |#lower], [upper |#upper]. +Ayrıca bkz. [#firstUpper], [#lower], [#upper]. checkUrl .[filter] ------------------ -URL sanitizasyonunu zorlar. Değişkenin bir web URL'si (yani HTTP/HTTPS protokolü) içerip içermediğini kontrol eder ve güvenlik riski oluşturabilecek bağlantıların yazılmasını engeller. +URL adresinin temizlenmesini zorlar. Değişkenin bir web URL'si (yani HTTP/HTTPS protokolü) içerip içermediğini kontrol eder ve güvenlik riski oluşturabilecek bağlantıların yazdırılmasını önler. ```latte {var $link = 'javascript:window.close()'} -checked -unchecked +kontrollü +kontrolsüz ``` -Baskılar: +Yazdırır: ```latte -checked -unchecked +kontrollü +kontrolsüz ``` -Ayrıca bkz. [nocheck |#nocheck]. +Ayrıca bkz. [#nocheck]. -clamp(int|float min, int|float max) .[filter]{data-version:2.9} ---------------------------------------------------------------- -Min ve maks. dahil aralığına sıkıştırılmış değeri döndürür. +clamp(int|float $min, int|float $max) .[filter] +----------------------------------------------- +Değeri verilen kapsayıcı min ve max aralığıyla sınırlar. ```latte {$level|clamp: 0, 255} ``` -[Fonksiyon |functions#clamp] olarak da mevcuttur. +Ayrıca [fonksiyon |functions#clamp] olarak da mevcuttur. -dataStream(string mimetype = detect) .[filter] ----------------------------------------------- -İçeriği veri URI şemasına dönüştürür. Harici dosyalara bağlantı vermeye gerek kalmadan HTML veya CSS'ye görüntü eklemek için kullanılabilir. +dataStream(string $mimetype=detect) .[filter] +--------------------------------------------- +İçeriği data URI scheme'ye dönüştürür. Bu sayede harici dosyalara bağlantı vermeden HTML veya CSS'e resimler eklenebilir. -`$img = Image::fromFile('obrazek.gif')` değişkeninde bir görüntüye sahip olalım, sonra +Değişkende bir resmimiz olsun `$img = Image::fromFile('resim.gif')`, o zaman ```latte - + ``` -Örneğin baskılar: +Örneğin şunu yazdırır: ```latte {$name} ``` -Ayrıca [sorguya |#query] bakın. +Ayrıca bkz. [#query]. -explode(string separator = '') .[filter]{data-version:2.10.2} -------------------------------------------------------------- -Bir dizeyi verilen sınırlayıcıya göre böler ve bir dizeler dizisi döndürür. İçin takma ad `split`. +explode(string $separator='') .[filter] +--------------------------------------- +Karakter dizisini ayırıcıya göre diziye böler. `split` için takma ad. ```latte -{='one,two,three'|explode:','} {* returns ['one', 'two', 'three'] *} +{='bir,iki,üç'|explode:','} {* ['bir', 'iki', 'üç'] döndürür *} ``` -Sınırlayıcı boş bir dizeyse (varsayılan değer), girdi ayrı karakterlere bölünecektir: +Ayırıcı boş bir karakter dizisi ise (varsayılan değer), girdi tek tek karakterlere bölünecektir: ```latte -{='123'|explode} {* returns ['1', '2', '3'] *} +{='123'|explode} {* ['1', '2', '3'] döndürür *} ``` Ayrıca `split` takma adını da kullanabilirsiniz: ```latte -{='1,2,3'|split:','} {* returns ['1', '2', '3'] *} +{='1,2,3'|split:','} {* ['1', '2', '3'] döndürür *} ``` -Ayrıca bkz. [implode |#implode]. +Ayrıca bkz. [#implode]. -first .[filter]{data-version:2.10.2} ------------------------------------- -Dizinin ilk elemanını veya karakter dizisini döndürür: +first .[filter] +--------------- +Dizinin ilk öğesini veya karakter dizisinin ilk karakterini döndürür: ```latte -{=[1, 2, 3, 4]|first} {* çıktılar 1 *} -{='abcd'|first} {* çıktılar 'a' *} +{=[1, 2, 3, 4]|first} {* 1 yazdırır *} +{='abcd'|first} {* 'a' yazdırır *} ``` -Ayrıca bkz. [son |#last], [rastgele |#random]. +Ayrıca bkz. [#last], [#random]. -floor(int precision = 0) .[filter] ----------------------------------- -Bir sayıyı belirli bir hassasiyete yuvarlar. +floor(int $precision=0) .[filter] +--------------------------------- +Sayıyı belirtilen hassasiyete göre aşağı yuvarlar. ```latte -{=3.5|floor} {* çıkışlar 3 *} -{=135.79|floor:1} {* çıkışlar 135.7 *} -{=135.79|floor:3} {* çıkışlar 135.79 *} +{=3.5|floor} {* 3 yazdırır *} +{=135.79|floor:1} {* 135.7 yazdırır *} +{=135.79|floor:3} {* 135.79 yazdırır *} ``` -Ayrıca bkz. [tavan |#ceil], [yuvarlak |#round]. +Ayrıca bkz. [#ceil], [#round]. firstUpper .[filter] -------------------- -Değerin ilk harfini büyük harfe dönüştürür. PHP eklentisi gerektirir `mbstring`. +İlk harfi büyük harfe dönüştürür. `mbstring` PHP eklentisini gerektirir. ```latte -{='the latte'|firstUpper} {* çıktılar 'The latte' *} +{='latte'|firstUpper} {* 'Latte' yazdırır *} ``` -Ayrıca bkz. [büyük harf |#capitalize], [alt |#lower], [üst |#upper]. +Ayrıca bkz. [#capitalize], [#lower], [#upper]. -implode(string glue = '') .[filter] ------------------------------------ -Dizideki dizelerin birleştirilmesinden oluşan bir dizge döndürür. İçin takma ad `join`. +group(string|int|\Closure $by): array .[filter]{data-version:3.0.16} +-------------------------------------------------------------------- +Filtre, verileri çeşitli kriterlere göre gruplandırır. + +Bu örnekte, tablodaki satırlar `categoryId` sütununa göre gruplandırılır. Çıktı, anahtarın `categoryId` sütunundaki değer olduğu bir dizi dizisidir. [Detaylı kılavuzu okuyun|cookbook/grouping]. ```latte -{=[1, 2, 3]|implode} {* outputs '123' *} -{=[1, 2, 3]|implode:'|'} {* outputs '1|2|3' *} +{foreach ($items|group: categoryId) as $categoryId => $categoryItems} +
                                                                                                                              + {foreach $categoryItems as $item} +
                                                                                                                            • {$item->name}
                                                                                                                            • + {/foreach} +
                                                                                                                            +{/foreach} ``` -Ayrıca `join` takma adını da kullanabilirsiniz: .{data-version:2.10.2} +Ayrıca bkz. [#batch], [group |functions#group] fonksiyonu ve [iterateWhile |tags#iterateWhile] etiketi. + + +implode(string $glue='') .[filter] +---------------------------------- +Bir dizinin öğelerinin birleştirilmesiyle oluşan bir karakter dizisi döndürür. `join` için takma ad. ```latte -{=[1, 2, 3]|join} {* outputs '123' *} +{=[1, 2, 3]|implode} {* '123' yazdırır *} +{=[1, 2, 3]|implode:'|'} {* '1|2|3' yazdırır *} ``` +Ayrıca `join` takma adını da kullanabilirsiniz: + +```latte +{=[1, 2, 3]|join} {* '123' yazdırır *} +``` -indent(int level = 1, string char = "\t") .[filter] ---------------------------------------------------- -Bir metni soldan belirli sayıda sekme veya ikinci isteğe bağlı bağımsız değişkende belirttiğimiz diğer karakterler kadar girintiler. Boş satırlar girintilenmez. + +indent(int $level=1, string $char="\t") .[filter] +------------------------------------------------- +Metni soldan belirtilen sayıda sekme veya ikinci argümanda belirtebileceğimiz diğer karakterlerle girintiler. Boş satırlar girintilenmez. ```latte
                                                                                                                            {block |indent} -

                                                                                                                            Hello

                                                                                                                            +

                                                                                                                            Merhaba

                                                                                                                            {/block}
                                                                                                                            ``` -Baskılar: +Yazdırır: ```latte
                                                                                                                            -

                                                                                                                            Hello

                                                                                                                            +

                                                                                                                            Merhaba

                                                                                                                            ``` -last .[filter]{data-version:2.10.2} ------------------------------------ -Dizinin son elemanını veya karakter dizisini döndürür: +last .[filter] +-------------- +Dizinin son öğesini veya karakter dizisinin son karakterini döndürür: ```latte -{=[1, 2, 3, 4]|last} {* outputs 4 *} -{='abcd'|last} {* outputs 'd' *} +{=[1, 2, 3, 4]|last} {* 4 yazdırır *} +{='abcd'|last} {* 'd' yazdırır *} ``` -Ayrıca bkz. [ilk |#first], [rastgele |#random]. +Ayrıca bkz. [#first], [#random]. length .[filter] ---------------- -Bir dize veya dizinin uzunluğunu döndürür. +Bir karakter dizisinin veya dizinin uzunluğunu döndürür. -- dizeler için, uzunluğu UTF-8 karakterleri cinsinden döndürecektir -- diziler için, öğe sayısını döndürür -- Countable arayüzünü uygulayan nesneler için count() işlevinin dönüş değerini kullanacaktır. -- IteratorAggregate arayüzünü uygulayan nesneler için, iterator_count() işlevinin dönüş değerini kullanacaktır. +- karakter dizileri için UTF-8 karakter cinsinden uzunluğu döndürür +- diziler için öğe sayısını döndürür +- Countable arayüzünü uygulayan nesneler için count() metodunun dönüş değerini kullanır +- IteratorAggregate arayüzünü uygulayan nesneler için iterator_count() fonksiyonunun dönüş değerini kullanır ```latte @@ -396,318 +420,454 @@ Bir dize veya dizinin uzunluğunu döndürür. ``` +localDate(?string $format=null, ?string $date=null, ?string $time=null) .[filter] +--------------------------------------------------------------------------------- +Tarih ve saati [yerel ayara |develop#Locale] göre biçimlendirir, bu da farklı diller ve bölgeler arasında zaman verilerinin tutarlı ve yerelleştirilmiş bir şekilde görüntülenmesini sağlar. Filtre, tarihi UNIX zaman damgası, karakter dizisi veya `DateTimeInterface` türünde bir nesne olarak kabul eder. + +```latte +{$date|localDate} {* 15 Nisan 2024 *} +{$date|localDate: format: yM} {* 4/2024 *} +{$date|localDate: date: medium} {* 15.04.2024 *} +``` + +Filtreyi parametresiz kullanırsanız, tarih `long` seviyesinde yazdırılır, bkz. aşağıda. + +**a) biçim kullanımı** + +`format` parametresi, hangi zaman bileşenlerinin görüntüleneceğini açıklar. Bunun için harf kodları kullanır ve bunların tekrar sayısı çıktının genişliğini etkiler: + +| yıl | `y` / `yy` / `yyyy` | `2024` / `24` / `2024` +| ay | `M` / `MM` / `MMM` / `MMMM` | `8` / `08` / `Ağu` / `Ağustos` +| gün | `d` / `dd` / `E` / `EEEE` | `1` / `01` / `Paz` / `Pazar` +| saat | `j` / `H` / `h` | tercih edilen / 24 saatlik / 12 saatlik +| dakika | `m` / `mm` | `5` / `05` (saniyelerle kombinasyonda 2 basamak) +| saniye | `s` / `ss` | `8` / `08` (dakikalarla kombinasyonda 2 basamak) + +Biçimdeki kodların sırası önemli değildir, çünkü bileşenlerin sırası yerel ayarın geleneklerine göre yazdırılır. Bu nedenle biçim ondan bağımsızdır. Örneğin, `yyyyMMMMd` biçimi `en_US` ortamında `April 15, 2024` yazdırırken, `tr_TR` ortamında `15 Nisan 2024` yazdırır: + +| locale: | tr_TR | en_US +|--- +| `format: 'dMy'` | 10.08.2024 | 8/10/2024 +| `format: 'yM'` | 8/2024 | 8/2024 +| `format: 'yyyyMMMM'` | Ağustos 2024 | August 2024 +| `format: 'MMMM'` | Ağustos | August +| `format: 'jm'` | 17:22 | 5:22 PM +| `format: 'Hm'` | 17:22 | 17:22 +| `format: 'hm'` | 5:22 ös | 5:22 PM + + +**b) önceden ayarlanmış stillerin kullanımı** + +`date` ve `time` parametreleri, tarih ve saatin ne kadar ayrıntılı yazdırılacağını belirler. Birkaç seviye arasından seçim yapabilirsiniz: `full`, `long`, `medium`, `short`. Yalnızca tarihi, yalnızca saati veya her ikisini de yazdırmayı seçebilirsiniz: + +| locale: | tr_TR | en_US +|--- +| `date: short` | 23.01.1978 | 1/23/78 +| `date: medium` | 23 Oca 1978 | Jan 23, 1978 +| `date: long` | 23 Ocak 1978 | January 23, 1978 +| `date: full` | 23 Ocak 1978 Pazartesi | Monday, January 23, 1978 +| `time: short` | 08:30 | 8:30 AM +| `time: medium` | 08:30:59 | 8:30:59 AM +| `time: long` | 08:30:59 GMT+03:00 | 8:30:59 AM GMT+1 +| `date: short, time: short` | 23.01.1978 08:30 | 1/23/78, 8:30 AM +| `date: medium, time: short` | 23 Oca 1978 08:30 | Jan 23, 1978, 8:30 AM +| `date: long, time: short` | 23 Ocak 1978 08:30 | January 23, 1978 at 8:30 AM + +Tarih için ayrıca `relative-` önekini kullanabilirsiniz (ör. `relative-short`), bu, günümüze yakın tarihler için `dün`, `bugün` veya `yarın` gösterir, aksi takdirde standart şekilde yazdırılır. + +```latte +{$date|localDate: date: relative-short} {* dün *} +``` + +Ayrıca bkz. [#date]. + + lower .[filter] --------------- -Bir değeri küçük harfe dönüştürür. PHP eklentisi gerektirir `mbstring`. +Karakter dizisini küçük harfe dönüştürür. `mbstring` PHP eklentisini gerektirir. ```latte -{='LATTE'|lower} {* outputs 'latte' *} +{='LATTE'|lower} {* 'latte' yazdırır *} ``` -Ayrıca bkz. [capitalize |#capitalize], [firstUpper |#firstUpper], [upper |#upper]. +Ayrıca bkz. [#capitalize], [#firstUpper], [#upper]. nocheck .[filter] ----------------- -Otomatik URL sanitizasyonunu önler. Latte, değişkenin bir web URL'si (yani HTTP/HTTPS protokolü) içerip içermediğini [otomatik olarak kontrol |safety-first#Link checking] eder ve güvenlik riski oluşturabilecek bağlantıların yazılmasını önler. +URL adresinin otomatik temizlenmesini önler. Latte [otomatik olarak kontrol eder |safety-first#Bağlantı Kontrolü], değişkenin bir web URL'si (yani HTTP/HTTPS protokolü) içerip içermediğini ve güvenlik riski oluşturabilecek bağlantıların yazdırılmasını önler. -Bağlantı `javascript:` veya `data:` gibi farklı bir şema kullanıyorsa ve içeriğinden eminseniz, kontrolü `|nocheck` üzerinden devre dışı bırakabilirsiniz. +Bağlantı `javascript:` veya `data:` gibi başka bir şema kullanıyorsa ve içeriğinden eminseniz, `|nocheck` kullanarak kontrolü kapatabilirsiniz. ```latte {var $link = 'javascript:window.close()'} -checked -unchecked +kontrollü +kontrolsüz ``` -Baskılar: +Yazdırır: ```latte -checked -unchecked +kontrollü +kontrolsüz ``` -Ayrıca bkz. [checkUrl |#checkUrl]. +Ayrıca bkz. [#checkUrl]. noescape .[filter] ------------------ -Otomatik kaçışı devre dışı bırakır. +Otomatik kaçış işlemini devre dışı bırakır. ```latte -{var $trustedHtmlString = 'hello'} -Escaped: {$trustedHtmlString} -Unescaped: {$trustedHtmlString|noescape} +{var $trustedHtmlString = 'merhaba'} +Kaçışlı: {$trustedHtmlString} +Kaçışsız: {$trustedHtmlString|noescape} ``` -Baskılar: +Yazdırır: ```latte -Escaped: <b>hello</b> -Unescaped: hello +Kaçışlı: <b>merhaba</b> +Kaçışsız: merhaba ``` .[warning] -`noescape` filtresinin yanlış kullanımı bir XSS güvenlik açığına yol açabilir! Ne yaptığınızdan ve yazdırdığınız dizenin güvenilir bir kaynaktan geldiğinden **kesinlikle emin** değilseniz asla kullanmayın. +`noescape` filtresinin yanlış kullanımı XSS güvenlik açığına yol açabilir! Ne yaptığınızdan ve yazdırılan karakter dizisinin güvenilir bir kaynaktan geldiğinden **tamamen emin** değilseniz asla kullanmayın. -number(int decimals = 0, string decPoint = '.', string thousandsSep = ',') .[filter] ------------------------------------------------------------------------------------- -Bir sayıyı verilen ondalık basamak sayısına göre biçimlendirir. Ayrıca ondalık nokta ve binlik ayırıcının bir karakterini de belirtebilirsiniz. +number(int $decimals=0, string $decPoint='.', string $thousandsSep=',') .[filter] +--------------------------------------------------------------------------------- +Sayıyı belirli sayıda ondalık basamağa göre biçimlendirir. Eğer [yerel ayar |develop#Locale] ayarlanmışsa, ilgili ondalık ve binlik ayırıcılar kullanılır. ```latte -{1234.20 |number} 1,234 -{1234.20 |number:1} 1,234.2 -{1234.20 |number:2} 1,234.20 -{1234.20 |number:2, ',', ' '} 1 234,20 +{1234.20|number} 1,234 +{1234.20|number:1} 1,234.2 +{1234.20|number:2} 1,234.20 +{1234.20|number:2, ',', ' '} 1 234,20 ``` -padLeft(int length, string pad = ' ') .[filter] +number(string $format) .[filter] +-------------------------------- +`format` parametresi, sayıların görünümünü tam olarak ihtiyaçlarınıza göre tanımlamanıza olanak tanır. Bunun için [yerel ayarın |develop#Locale] ayarlanmış olması gerekir. Biçim, tam açıklaması "DecimalFormat":https://unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns belgelerinde bulunan birkaç özel karakterden oluşur: + +- `0` zorunlu basamak, sıfır olsa bile her zaman görüntülenir +- `#` isteğe bağlı basamak, yalnızca bu konumda gerçekten bir sayı varsa görüntülenir +- `@` anlamlı basamak, sayıyı belirli sayıda geçerli basamakla görüntülemeye yardımcı olur +- `.` ondalık virgülünün (veya ülkeye göre noktanın) nerede olması gerektiğini belirtir +- `,` basamak gruplarını, en sık binlikleri ayırmak için kullanılır +- `%` sayıyı 100 ile çarpar ve yüzde işaretini ekler + +Örneklere bakalım. İlk örnekte iki ondalık basamak zorunludur, ikincisinde isteğe bağlıdır. Üçüncü örnek soldan ve sağdan sıfırlarla tamamlamayı gösterir, dördüncü sadece var olan basamakları gösterir: + +```latte +{1234.5|number: '#,##0.00'} {* 1,234.50 *} +{1234.5|number: '#,##0.##'} {* 1,234.5 *} +{1.23 |number: '000.000'} {* 001.230 *} +{1.2 |number: '##.##'} {* 1.2 *} +``` + +Anlamlı basamaklar, ondalık virgüle bakılmaksızın kaç basamağın görüntüleneceğini belirler ve yuvarlama yapılır: + +```latte +{1234|number: '@@'} {* 1200 *} +{1234|number: '@@@'} {* 1230 *} +{1234|number: '@@@#'} {* 1234 *} +{1.2345|number: '@@@'} {* 1.23 *} +{0.00123|number: '@@'} {* 0.0012 *} +``` + +Bir sayıyı yüzde olarak göstermenin kolay yolu. Sayı 100 ile çarpılır ve `%` işareti eklenir: + +```latte +{0.1234|number: '#.##%'} {* 12.34% *} +``` + +Pozitif ve negatif sayılar için farklı bir biçim tanımlayabiliriz, bunlar `;` işaretiyle ayrılır. Bu şekilde, örneğin pozitif sayıların `+` işaretiyle gösterilmesini ayarlayabiliriz: + +```latte +{42|number: '#.##;(#.##)'} {* 42 *} +{-42|number: '#.##;(#.##)'} {* (42) *} +{42|number: '+#.##;-#.##'} {* +42 *} +{-42|number: '+#.##;-#.##'} {* -42 *} +``` + +Unutmayın, sayıların gerçek görünümü ülke ayarlarına göre değişebilir. Örneğin, bazı ülkelerde ondalık ayırıcı olarak nokta yerine virgül kullanılır. Bu filtre bunu otomatik olarak dikkate alır ve sizin hiçbir şey yapmanıza gerek kalmaz. + + +padLeft(int $length, string $pad=' ') .[filter] ----------------------------------------------- -Bir dizeyi soldan başka bir dizeyle belirli bir uzunluğa kadar doldurur. +Karakter dizisini soldan başka bir karakter dizisiyle belirli bir uzunluğa tamamlar. ```latte -{='hello'|padLeft: 10, '123'} {* outputs '12312hello' *} +{='merhaba'|padLeft: 10, '123'} {* '12312merhaba' yazdırır *} ``` -padRight(int length, string pad = ' ') .[filter] +padRight(int $length, string $pad=' ') .[filter] ------------------------------------------------ -Bir dizeyi sağdan başka bir dizeyle belirli bir uzunlukta doldurur. +Karakter dizisini sağdan başka bir karakter dizisiyle belirli bir uzunluğa tamamlar. ```latte -{='hello'|padRight: 10, '123'} {* outputs 'hello12312' *} +{='merhaba'|padRight: 10, '123'} {* 'merhaba12312' yazdırır *} ``` -query .[filter]{data-version:2.10} ------------------------------------ -URL'de dinamik olarak bir sorgu dizesi oluşturur: +query .[filter] +--------------- +URL'de dinamik olarak sorgu dizesi oluşturur: ```latte -click -search +tıkla +ara ``` -Baskılar: +Yazdırır: ```latte -click -search +tıkla +ara ``` -Değeri `null` olan anahtarlar atlanır. +`null` değerli anahtarlar atlanır. -Ayrıca bkz. [escapeUrl |#escapeUrl]. +Ayrıca bkz. [#escapeUrl]. -random .[filter]{data-version:2.10.2} -------------------------------------- -Dizinin rastgele elemanını veya karakter dizisini döndürür: +random .[filter] +---------------- +Dizinin rastgele bir öğesini veya karakter dizisinin rastgele bir karakterini döndürür: ```latte -{=[1, 2, 3, 4]|random} {* example output: 3 *} -{='abcd'|random} {* example output: 'b' *} +{=[1, 2, 3, 4]|random} {* örn.: 3 yazdırır *} +{='abcd'|random} {* örn.: 'b' yazdırır *} ``` -Ayrıca bkz. [ilk |#first], [son |#last]. +Ayrıca bkz. [#first], [#last]. -repeat(int count) .[filter] ---------------------------- -Dizeyi x kez tekrarlar. +repeat(int $count) .[filter] +---------------------------- +Karakter dizisini x kez tekrarlar. ```latte -{='hello'|repeat: 3} {* outputs 'hellohellohello' *} +{='merhaba'|repeat: 3} {* 'merhabamerhabamerhaba' yazdırır *} ``` -replace(string|array search, string replace = '') .[filter] +replace(string|array $search, string $replace='') .[filter] ----------------------------------------------------------- -Arama dizesinin tüm geçtiği yerleri değiştirme dizesiyle değiştirir. +Arama karakter dizisinin tüm geçtiği yerleri değiştirme karakter dizisiyle değiştirir. ```latte -{='hello world'|replace: 'world', 'friend'} {* outputs 'hello friend' *} +{='merhaba dünya'|replace: 'dünya', 'arkadaş'} {* 'merhaba arkadaş' yazdırır *} ``` -Aynı anda birden fazla değiştirme yapılabilir: .{data-version:2.10.2} +Aynı anda birden fazla değiştirme de yapılabilir: ```latte -{='hello world'|replace: [h => l, l => h]} {* outputs 'lehho worhd' *} +{='merhaba dünya'|replace: [m => d, d => m]} {* 'derhaba münma' yazdırır *} ``` -replaceRE(string pattern, string replace = '') .[filter] +replaceRE(string $pattern, string $replace='') .[filter] -------------------------------------------------------- -Tüm oluşumları düzenli ifadeye göre değiştirir. +Değiştirme ile düzenli ifade araması yapar. ```latte -{='hello world'|replaceRE: '/l.*/', 'l'} {* outputs 'hel' *} +{='merhaba dünya'|replaceRE: '/d.*/', 'd'} {* 'merhabad' yazdırır *} ``` reverse .[filter] ----------------- -Verilen dizeyi veya diziyi tersine çevirir. +Verilen karakter dizisini veya diziyi ters çevirir. ```latte {var $s = 'Nette'} -{$s|reverse} {* outputs 'etteN' *} +{$s|reverse} {* 'etteN' yazdırır *} {var $a = ['N', 'e', 't', 't', 'e']} -{$a|reverse} {* returns ['e', 't', 't', 'e', 'N'] *} +{$a|reverse} {* ['e', 't', 't', 'e', 'N'] döndürür *} ``` -round(int precision = 0) .[filter] ----------------------------------- -Bir sayıyı belirli bir hassasiyete yuvarlar. +round(int $precision=0) .[filter] +--------------------------------- +Sayıyı belirtilen hassasiyete göre yuvarlar. ```latte -{=3.4|round} {* outputs 3 *} -{=3.5|round} {* outputs 4 *} -{=135.79|round:1} {* outputs 135.8 *} -{=135.79|round:3} {* outputs 135.79 *} +{=3.4|round} {* 3 yazdırır *} +{=3.5|round} {* 4 yazdırır *} +{=135.79|round:1} {* 135.8 yazdırır *} +{=135.79|round:3} {* 135.79 yazdırır *} ``` -Ayrıca bkz. [tavan |#ceil], [zemin |#floor]. +Ayrıca bkz. [#ceil], [#floor]. -slice(int start, int length = null, bool preserveKeys = false) .[filter]{data-version:2.10.2} ---------------------------------------------------------------------------------------------- -Bir dizinin veya dizginin bir dilimini çıkarır. +slice(int $start, ?int $length=null, bool $preserveKeys=false) .[filter] +------------------------------------------------------------------------ +Bir dizinin veya karakter dizisinin bir bölümünü çıkarır. ```latte -{='hello'|slice: 1, 2} {* outputs 'el' *} -{=['a', 'b', 'c']|slice: 1, 2} {* outputs ['b', 'c'] *} +{='merhaba'|slice: 1, 2} {* 'er' yazdırır *} +{=['a', 'b', 'c']|slice: 1, 2} {* ['b', 'c'] yazdırır *} ``` -Dilim süzgeci, diziler için `array_slice` PHP işlevi ve dizeler için `mb_substr` işlevi olarak çalışır ve UTF-8 kipinde `iconv_substr` işlevine geri döner. +Filtre, diziler için PHP `array_slice` fonksiyonu gibi veya karakter dizileri için `mb_substr` fonksiyonu gibi çalışır ve UTF-8 modunda `iconv_substr` fonksiyonuna geri döner. -Başlangıç negatif değilse, dizi değişkendeki o başlangıçtan başlayacaktır. Başlangıç negatifse, dizi değişkenin sonundan o kadar uzakta başlayacaktır. +Eğer başlangıç pozitifse, dizi/karakter dizisinin başından itibaren bu kadar kaydırılarak başlar. Eğer negatifse, sondan bu kadar kaydırılarak başlar. -Uzunluk verilmişse ve pozitifse, dizinin içinde o kadar eleman olacaktır. Değişken uzunluktan daha kısaysa, yalnızca mevcut değişken öğeleri mevcut olacaktır. Uzunluk verilirse ve negatifse, dizi değişkenin sonundan itibaren o kadar elemanla duracaktır. Atlanırsa, dizi ofsetten değişkenin sonuna kadar her şeye sahip olacaktır. +Eğer length parametresi belirtilmişse ve pozitifse, dizi bu kadar öğe içerecektir. Eğer bu fonksiyona negatif bir length parametresi geçirilirse, dizi başlangıç pozisyonundan başlayıp dizinin sonundan length kadar öğe öncesinde biten tüm öğeleri içerecektir. Eğer bu parametre belirtilmezse, dizi başlangıç pozisyonundan itibaren orijinal dizinin tüm öğelerini içerecektir. -Filtre, varsayılan olarak tamsayı dizi anahtarlarını yeniden sıralar ve sıfırlar. Bu davranış preserveKeys öğesi true olarak ayarlanarak değiştirilebilir. Dize anahtarları bu parametreden bağımsız olarak her zaman korunur. +Varsayılan olarak, filtre sırayı değiştirir ve tamsayı dizi anahtarlarını sıfırlar. Bu davranış, preserveKeys'i true olarak ayarlayarak değiştirilebilir. Karakter dizisi anahtarları, bu parametreden bağımsız olarak her zaman korunur. -sort .[filter]{data-version:2.9} ---------------------------------- -Bir diziyi sıralayan ve dizin ilişkisini koruyan süzgeç. +sort(?Closure $comparison, string|int|\Closure|null $by=null, string|int|\Closure|bool $byKey=false) .[filter] +-------------------------------------------------------------------------------------------------------------- +Filtre, bir dizinin veya yineleyicinin öğelerini sıralar ve ilişkisel anahtarlarını korur. Eğer [yerel ayar |develop#Locale] ayarlanmışsa, özel bir karşılaştırma fonksiyonu belirtilmediği sürece sıralama onun kurallarına göre yapılır. ```latte -{foreach ($names|sort) as $name} +{foreach ($isimler|sort) as $isim} ... {/foreach} ``` -Dizi ters sırada sıralanır. +Sıralanmış dizi ters sırada: ```latte -{foreach ($names|sort|reverse) as $name} +{foreach ($isimler|sort|reverse) as $isim} ... {/foreach} ``` -Kendi karşılaştırma fonksiyonunuzu parametre olarak aktarabilirsiniz: .{data-version:2.10.2} +Sıralama için özel bir karşılaştırma fonksiyonu belirleyebilirsiniz (örnek, en büyükten en küçüğe ters sıralamanın nasıl yapılacağını gösterir): ```latte -{var $sorted = ($names|sort: fn($a, $b) => $b <=> $a)} +{var $tersCevrilmis = ($isimler|sort: fn($a, $b) => $b <=> $a)} ``` +`|sort` filtresi ayrıca öğeleri anahtarlara göre sıralamaya da olanak tanır: -spaceless .[filter]{data-version:2.10.2} ------------------------------------------ -Gereksiz boşlukları çıktıdan kaldırır. Ayrıca `strip` takma adını da kullanabilirsiniz. +```latte +{foreach ($isimler|sort: byKey: true) as $isim} + ... +{/foreach} +``` + +Bir tabloyu belirli bir sütuna göre sıralamanız gerekiyorsa, `by` parametresini kullanabilirsiniz. Örnekteki `'name'` değeri, `$item`'ın dizi mi yoksa nesne mi olduğuna bağlı olarak `$item->name` veya `$item['name']`'e göre sıralanacağını belirtir: + +```latte +{foreach ($items|sort: by: 'name') as $item} + {$item->name} +{/foreach} +``` + +Ayrıca, neye göre sıralanacağını belirleyen bir geri çağırma fonksiyonu da tanımlayabilirsiniz: + +```latte +{foreach ($items|sort: by: fn($items) => $items->category->name) as $item} + {$item->name} +{/foreach} +``` + +`byKey` parametresi de aynı şekilde kullanılabilir. + + +spaceless .[filter] +------------------- +Çıktıdan gereksiz boşlukları (boşluk karakterlerini) kaldırır. Ayrıca `strip` takma adını da kullanabilirsiniz. ```latte {block |spaceless}
                                                                                                                              -
                                                                                                                            • Hello
                                                                                                                            • +
                                                                                                                            • Merhaba
                                                                                                                            {/block} ``` -Baskılar: +Yazdırır: ```latte -
                                                                                                                            • Hello
                                                                                                                            +
                                                                                                                            • Merhaba
                                                                                                                            ``` stripHtml .[filter] ------------------- -HTML'yi düz metne dönüştürür. Yani, HTML etiketlerini kaldırır ve HTML varlıklarını metne dönüştürür. +HTML'i düz metne dönüştürür. Yani HTML etiketlerini kaldırır ve HTML varlıklarını metne dönüştürür. ```latte -{='

                                                                                                                            one < two

                                                                                                                            '|stripHtml} {* outputs 'one < two' *} +{='

                                                                                                                            bir < iki

                                                                                                                            '|stripHtml} {* 'bir < iki' yazdırır *} ``` -Ortaya çıkan düz metin doğal olarak HTML etiketlerini temsil eden karakterler içerebilir, örneğin `'<p>'|stripHtml` şu şekilde dönüştürülür `

                                                                                                                            `. Bir güvenlik açığına yol açabileceğinden, ortaya çıkan metni asla `|noescape` ile çıktı olarak vermeyin. +Sonuçtaki düz metin doğal olarak HTML etiketlerini temsil eden karakterler içerebilir, örneğin `'<p>'|stripHtml` `

                                                                                                                            `'ye dönüştürülür. Bu şekilde oluşturulan metni asla `|noescape` ile yazdırmayın, çünkü bu bir güvenlik açığına yol açabilir. -substr(int offset, int length = null) .[filter] ------------------------------------------------ -Bir dizenin bir dilimini çıkarır. Bu filtre bir [dilim |#slice] filtresi ile değiştirilmiştir. +substr(int $offset, ?int $length=null) .[filter] +------------------------------------------------ +Karakter dizisinin bir bölümünü çıkarır. Bu filtre [#slice] filtresiyle değiştirilmiştir. ```latte {$string|substr: 1, 2} ``` -translate(string message, ...args) .[filter]{data-version:3.0} --------------------------------------------------------------- -İfadeleri diğer dillere çevirir. Filtreyi kullanılabilir hale getirmek için [çevirmen kur |develop#TranslatorExtension]manız gerekir. [Çeviri için etiketleri |tags#Translation] de kullanabilirsiniz. +translate(...$args) .[filter] +----------------------------- +İfadeleri diğer dillere çevirir. Filtrenin kullanılabilir olması için [çevirmenin ayarlanması |develop#TranslatorExtension] gerekir. Ayrıca [çeviri için etiketleri |tags#Çeviriler] de kullanabilirsiniz. ```latte -{='Baskter'|translate} +{='Sepet'|translate} {$item|translate} ``` -trim(string charlist = " \t\n\r\0\x0B\u{A0}") .[filter] -------------------------------------------------------- -Baştaki ve sondaki karakterleri, varsayılan olarak boşlukları soyun. +trim(string $charlist=" \t\n\r\0\x0B\u{A0}") .[filter] +------------------------------------------------------ +Karakter dizisinin başından ve sonundan boşluk karakterlerini (veya diğer karakterleri) kaldırır. ```latte -{=' I like Latte. '|trim} {* outputs 'I like Latte.' *} -{=' I like Latte.'|trim: '.'} {* outputs ' I like Latte' *} +{=' Latte severim. '|trim} {* 'Latte severim.' yazdırır *} +{=' Latte severim.'|trim: '.'} {* ' Latte severim' yazdırır *} ``` -truncate(int length, string append = '…') .[filter] +truncate(int $length, string $append='…') .[filter] --------------------------------------------------- -Bir dizeyi verilen maksimum uzunluğa kısaltır, ancak tüm kelimeleri korumaya çalışır. Dize kesilmişse sonuna üç nokta ekler (bu ikinci parametre ile değiştirilebilir). +Bir karakter dizisini belirtilen maksimum uzunluğa kırpar, bu sırada tam kelimeleri korumaya çalışır. Karakter dizisi kısaltılırsa, sonuna üç nokta ekler (ikinci parametre ile değiştirilebilir). ```latte -{var $title = 'Hello, how are you?'} -{$title|truncate:5} {* Hell… *} -{$title|truncate:17} {* Hello, how are… *} -{$title|truncate:30} {* Hello, how are you? *} +{var $title = 'Merhaba, nasılsınız?'} +{$title|truncate:5} {* Merh… *} +{$title|truncate:17} {* Merhaba, nasıl… *} +{$title|truncate:30} {* Merhaba, nasılsınız? *} ``` upper .[filter] --------------- -Bir değeri büyük harfe dönüştürür. PHP eklentisi gerektirir `mbstring`. +Karakter dizisini büyük harfe dönüştürür. `mbstring` PHP eklentisini gerektirir. ```latte -{='latte'|upper} {* outputs 'LATTE' *} +{='latte'|upper} {* 'LATTE' yazdırır *} ``` -Ayrıca bkz. [capitalize |#capitalize], [firstUpper |#firstUpper], [lower |#lower]. +Ayrıca bkz. [#capitalize], [#firstUpper], [#lower]. webalize .[filter] ------------------ -ASCII'ye dönüştürür. +UTF-8 karakter dizisini URL'de kullanılan biçime dönüştürür. -Boşlukları tire işaretine dönüştürür. Alfanümerik, alt çizgi veya kısa çizgi olmayan karakterleri kaldırır. Küçük harfe dönüştürür. Ayrıca baştaki ve sondaki boşlukları da siler. +ASCII'ye dönüştürülür. Boşlukları tireye dönüştürür. Alfanümerik olmayan, alt çizgi veya tire olmayan karakterleri kaldırır. Küçük harfe dönüştürür. Ayrıca baştaki ve sondaki boşlukları kaldırır. ```latte -{var $s = 'Our 10. product'} -{$s|webalize} {* outputs 'our-10-product' *} +{var $s = 'Bizim 10. ürünümüz'} +{$s|webalize} {* 'bizim-10-urunumuz' yazdırır *} ``` .[caution] - [nette/utils |utils:] paketini gerektirir. +[nette/utils|utils:] kütüphanesini gerektirir. diff --git a/latte/tr/functions.texy b/latte/tr/functions.texy index 1aa138ec38..c63413164d 100644 --- a/latte/tr/functions.texy +++ b/latte/tr/functions.texy @@ -2,22 +2,24 @@ Latte Fonksiyonları ******************* .[perex] -Yaygın PHP işlevlerine ek olarak, bunları şablonlarda da kullanabilirsiniz. +Şablonlarda normal PHP fonksiyonlarının yanı sıra bu ek fonksiyonları da kullanabiliriz. .[table-latte-filters] -| `clamp` | [değeri aralığa sıkıştırır |#clamp] -| `divisibleBy`| [bir değişkenin bir sayıya bölünebilir olup olmadığını kontroleder|#divisibleBy] -| `even` | [verilen sayının çift olup olmadığını kontroleder |#even] -| `first` | [dizinin ilk elemanını veya karakter dizisini döndürür |#first] -| `last` | [dizinin son elemanını veya karakter dizisini döndürür |#last] -| `odd` | [verilen sayının tek olup olmadığını kontroleder |#odd] -| `slice` | [bir dizi veya dizenin bir dilimini çıkarır |#slice] +| `clamp` | [değeri belirtilen aralıkla sınırlar |#clamp] +| `divisibleBy`| [bir değişkenin bir sayıya bölünüp bölünemediğini kontrol eder |#divisibleBy] +| `even` | [verilen sayının çift olup olmadığını kontrol eder |#even] +| `first` | [dizinin ilk öğesini veya karakter dizisinin ilk karakterini döndürür |#first] +| `group` | [verileri çeşitli kriterlere göre gruplandırır |#group] +| `hasBlock` | [bir bloğun varlığını kontrol eder |#hasBlock] +| `last` | [dizinin son öğesini veya karakter dizisinin son karakterini döndürür |#last] +| `odd` | [verilen sayının tek olup olmadığını kontrol eder |#odd] +| `slice` | [dizinin veya karakter dizisinin bir bölümünü çıkarır |#slice] -Kullanım .[#toc-usage] -====================== +Kullanım +======== -İşlevler, yaygın PHP işlevleriyle aynı şekilde kullanılır ve tüm ifadelerde kullanılabilir: +Fonksiyonlar normal PHP fonksiyonları gibi kullanılır ve tüm ifadelerde kullanılabilir: ```latte

                                                                                                                            {clamp($num, 1, 100)}

                                                                                                                            @@ -25,14 +27,14 @@ Kullanım .[#toc-usage] {if odd($num)} ... {/if} ``` -[Özel fonksiyonlar |extending-latte#functions] bu şekilde kaydedilebilir: +[Özel fonksiyonlar|custom-functions] şu şekilde kaydedilebilir: ```php $latte = new Latte\Engine; $latte->addFunction('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -Bunun gibi bir şablonda kullanırız: +Şablonda daha sonra şu şekilde çağrılır: ```latte

                                                                                                                            {shortify($text)}

                                                                                                                            @@ -40,32 +42,32 @@ Bunun gibi bir şablonda kullanırız: ``` -Fonksiyonlar .[#toc-functions] -============================== +Fonksiyonlar +============ -clamp(int|float $value, int|float $min, int|float $max): int|float .[method]{data-version:2.9} ----------------------------------------------------------------------------------------------- -Min ve maks. dahil aralığına sıkıştırılmış değeri döndürür. +clamp(int|float $value, int|float $min, int|float $max): int|float .[method] +---------------------------------------------------------------------------- +Değeri verilen kapsayıcı min ve max aralığıyla sınırlar. ```latte {=clamp($level, 0, 255)} ``` -Ayrıca bkz. [filtre kelepçesi |filters#clamp]: +Ayrıca bkz. [clamp filtresi |filters#clamp]. -divisibleBy(int $value, int $by): bool .[method]{data-version:2.10.2} ---------------------------------------------------------------------- -Bir değişkenin bir sayıya bölünebilir olup olmadığını kontrol eder. +divisibleBy(int $value, int $by): bool .[method] +------------------------------------------------ +Bir değişkenin bir sayıya bölünüp bölünemediğini kontrol eder. ```latte {if divisibleBy($num, 5)} ... {/if} ``` -even(int $value): bool .[method]{data-version:2.10.2} ------------------------------------------------------ +even(int $value): bool .[method] +-------------------------------- Verilen sayının çift olup olmadığını kontrol eder. ```latte @@ -73,32 +75,62 @@ Verilen sayının çift olup olmadığını kontrol eder. ``` -first(string|array $value): mixed .[method]{data-version:2.10.2} ----------------------------------------------------------------- -Dizinin ilk elemanını veya karakter dizisini döndürür: +first(string|iterable $value): mixed .[method] +---------------------------------------------- +Dizinin ilk öğesini veya karakter dizisinin ilk karakterini döndürür: ```latte -{=first([1, 2, 3, 4])} {* çıktılar 1 *} -{=first('abcd')} {* çıktılar 'a' *} +{=first([1, 2, 3, 4])} {* 1 yazdırır *} +{=first('abcd')} {* 'a' yazdırır *} ``` -Ayrıca bkz. [last |#last], [filter first |filters#first]. +Ayrıca bkz. [#last], [first filtresi |filters#first]. -last(string|array $value): mixed .[method]{data-version:2.10.2} ---------------------------------------------------------------- -Dizinin son elemanını veya karakter dizisini döndürür: +group(iterable $data, string|int|\Closure $by): array .[method]{data-version:3.0.16} +------------------------------------------------------------------------------------ +Fonksiyon, verileri çeşitli kriterlere göre gruplandırır. + +Bu örnekte, tablodaki satırlar `categoryId` sütununa göre gruplandırılır. Çıktı, anahtarın `categoryId` sütunundaki değer olduğu bir dizi dizisidir. [Detaylı kılavuzu okuyun|cookbook/grouping]. + +```latte +{foreach group($items, categoryId) as $categoryId => $categoryItems} +
                                                                                                                              + {foreach $categoryItems as $item} +
                                                                                                                            • {$item->name}
                                                                                                                            • + {/foreach} +
                                                                                                                            +{/foreach} +``` + +Ayrıca bkz. [group |filters#group] filtresi. + + +hasBlock(string $name): bool .[method]{data-version:3.0.10} +----------------------------------------------------------- +Belirtilen addaki bloğun var olup olmadığını kontrol eder: + +```latte +{if hasBlock(header)} ... {/if} +``` + +Ayrıca bkz. [blokların varlığını kontrol etme |template-inheritance#Blok Varlığını Kontrol Etme]. + + +last(string|array $value): mixed .[method] +------------------------------------------ +Dizinin son öğesini veya karakter dizisinin son karakterini döndürür: ```latte -{=last([1, 2, 3, 4])} {* çıkışlar 4 *} -{=last('abcd')} {* çıktılar 'd' *} +{=last([1, 2, 3, 4])} {* 4 yazdırır *} +{=last('abcd')} {* 'd' yazdırır *} ``` -Ayrıca bkz. [ilk |#first], [son filtre |filters#last]. +Ayrıca bkz. [#first], [last filtresi |filters#last]. -odd(int $value): bool .[method]{data-version:2.10.2} ----------------------------------------------------- +odd(int $value): bool .[method] +------------------------------- Verilen sayının tek olup olmadığını kontrol eder. ```latte @@ -106,19 +138,19 @@ Verilen sayının tek olup olmadığını kontrol eder. ``` -slice(string|array $value, int $start, int $length=null, bool $preserveKeys=false): string|array .[method]{data-version:2.10.2} -------------------------------------------------------------------------------------------------------------------------------- -Bir dizinin veya dizginin bir dilimini çıkarır. +slice(string|array $value, int $start, ?int $length=null, bool $preserveKeys=false): string|array .[method] +----------------------------------------------------------------------------------------------------------- +Bir dizinin veya karakter dizisinin bir bölümünü çıkarır. ```latte -{=slice('hello', 1, 2)} {* çıktılar 'el' *} -{=slice(['a', 'b', 'c'], 1, 2)} {* çıktılar ['b', 'c'] *} +{=slice('merhaba', 1, 2)} {* 'er' yazdırır *} +{=slice(['a', 'b', 'c'], 1, 2)} {* ['b', 'c'] yazdırır *} ``` -Dilim süzgeci, diziler için `array_slice` PHP işlevi ve dizeler için `mb_substr` işlevi olarak çalışır ve UTF-8 kipinde `iconv_substr` işlevine geri döner. +Filtre, diziler için PHP `array_slice` fonksiyonu gibi veya karakter dizileri için `mb_substr` fonksiyonu gibi çalışır ve UTF-8 modunda `iconv_substr` fonksiyonuna geri döner. -Başlangıç negatif değilse, dizi değişkendeki o başlangıçtan başlayacaktır. Başlangıç negatifse, dizi değişkenin sonundan o kadar uzakta başlayacaktır. +Eğer başlangıç pozitifse, dizi/karakter dizisinin başından itibaren bu kadar kaydırılarak başlar. Eğer negatifse, sondan bu kadar kaydırılarak başlar. -Uzunluk verilmişse ve pozitifse, dizinin içinde o kadar eleman olacaktır. Değişken uzunluktan daha kısaysa, yalnızca mevcut değişken öğeleri mevcut olacaktır. Uzunluk verilirse ve negatifse, dizi değişkenin sonundan itibaren o kadar elemanla duracaktır. Atlanırsa, dizi ofsetten değişkenin sonuna kadar her şeye sahip olacaktır. +Eğer length parametresi belirtilmişse ve pozitifse, dizi bu kadar öğe içerecektir. Eğer bu fonksiyona negatif bir length parametresi geçirilirse, dizi başlangıç pozisyonundan başlayıp dizinin sonundan length kadar öğe öncesinde biten tüm öğeleri içerecektir. Eğer bu parametre belirtilmezse, dizi başlangıç pozisyonundan itibaren orijinal dizinin tüm öğelerini içerecektir. -Filtre, varsayılan olarak tamsayı dizi anahtarlarını yeniden sıralar ve sıfırlar. Bu davranış preserveKeys öğesi true olarak ayarlanarak değiştirilebilir. Dize anahtarları bu parametreden bağımsız olarak her zaman korunur. +Varsayılan olarak, filtre sırayı değiştirir ve tamsayı dizi anahtarlarını sıfırlar. Bu davranış, preserveKeys'i true olarak ayarlayarak değiştirilebilir. Karakter dizisi anahtarları, bu parametreden bağımsız olarak her zaman korunur. diff --git a/latte/tr/guide.texy b/latte/tr/guide.texy index 8a86b8a3e3..345d8e7e4a 100644 --- a/latte/tr/guide.texy +++ b/latte/tr/guide.texy @@ -1,46 +1,45 @@ -Latte ile Başlarken -******************* +Latte'ye Başlarken +******************
                                                                                                                            -Şablonlar kod organizasyonunu geliştirir, uygulama mantığını sunumdan ayırır ve güvenliği artırır. HTML oluşturmak için PHP'nin kendisinden çok daha iyi özellikler ve ifade yetenekleri sunarlar. +Şablonlar kod organizasyonunu iyileştirir, uygulama mantığını sunumdan ayırır ve güvenliği artırır. HTML oluşturmak için PHP'nin kendisinden çok daha iyi fonksiyonlar ve ifade yetenekleri sunarlar. -Latte, PHP için en güvenli şablonlama sistemidir. Sezgisel sözdizimini seveceksiniz. Çok çeşitli kullanışlı özellikler işinizi önemli ölçüde kolaylaştıracaktır. - [Kritik |safety-first] güvenlik [açıklarına |safety-first] karşı birinci sınıf koruma sağlar ve güvenlikleri hakkında endişelenmeden yüksek kaliteli uygulamalar oluşturmaya odaklanmanıza olanak tanır. +Latte, PHP için en güvenli şablonlama sistemidir. Sezgisel sözdizimini seveceksiniz. Geniş yelpazedeki kullanışlı fonksiyonlar işinizi önemli ölçüde kolaylaştıracaktır. [Kritik güvenlik açıklarına|safety-first] karşı birinci sınıf koruma sağlar ve uygulamalarınızın güvenliği konusunda endişelenmeden kaliteli uygulamalar oluşturmaya odaklanmanızı sağlar. -Latte kullanarak şablonlar nasıl yazılır? .[#toc-how-to-write-templates-using-latte] ------------------------------------------------------------------------------------- +Latte ile Şablonlar Nasıl Yazılır? +---------------------------------- -Latte akıllıca tasarlanmıştır ve PHP'ye aşina olanlar için öğrenmesi kolaydır, çünkü temel etiketlerini hızlı bir şekilde benimseyebilirler. +Latte akıllıca tasarlanmıştır ve PHP bilen ve temel etiketleri öğrenenler için öğrenmesi kolaydır. -- Öncelikle [Latte sözdizimine |syntax] aşina olun ve [hepsini çevrimiçi deneyin |https://fiddle.nette.org/latte/] -- Temel [etiket |tags] ve [filtre |filters]setine bir göz atın -- [Latte desteği ile editörde |recipes#Editors and IDE] şablon yazma +- Önce [Latte sözdizimi|syntax] ile tanışın ve [ONLINE DENEYİN |https://fiddle.nette.org/latte/#9cc0cf6d89] +- Temel [etiketler|tags] ve [filtreler|filters] setine göz atın +- Şablonları [Latte destekli düzenleyici |recipes#Düzenleyiciler ve IDE ler] içinde yazın -PHP'de Latte Nasıl Kullanılır? .[#toc-how-to-use-latte-in-php] --------------------------------------------------------------- +Latte PHP'de Nasıl Kullanılır? +------------------------------ -Latte'yi yeni uygulamanıza uygulamak birkaç dakika sürer: +Latte'yi yeni uygulamanıza dağıtmak birkaç dakika meselesidir: -- İlk olarak, [Latte'yi kurun ve çalıştırın |develop#Installation] -- [Tracy hata ayıklama aracı |develop#Debugging and Tracy] ile kendinizi şımartın -- Latte'yi [özel işlevlerle |extending-latte] genişletin +- Önce [Latte'yi kurun ve çalıştırın |develop#Kurulum] +- [Hata ayıklama aracı Tracy |develop#Hata Ayıklama ve Tracy] ile kendinizi şımartın +- Latte'yi [özel işlevsellik |extending-latte] ile genişletin -Düz PHP ile yazılmış eski bir projeyi Latte'ye dönüştürüyorsanız, [PHP kodunu Latte'ye dönüştürme aracı |cookbook/migration-from-php] geçişi sizin için kolaylaştıracaktır. Ya da Twig'den Latte'ye geçmeyi mi planlıyorsunuz? Sizin için Latte'ye bir Twig [şablon dönüştürücümüz |cookbook/migration-from-twig] var. +Saf PHP ile yazılmış eski bir projeyi Latte'ye dönüştürüyorsanız, [PHP kodunu Latte'ye dönüştürme aracı |cookbook/migration-from-php] geçişi kolaylaştıracaktır. Veya Twig'den Latte'ye geçmeyi mi planlıyorsunuz? Sizin için [Twig şablonlarını Latte'ye dönüştürücü |cookbook/migration-from-twig] var. -Latte Başka Ne Yapabilir? .[#toc-what-else-can-latte-do] --------------------------------------------------------- +Latte Başka Neler Yapabilir? +---------------------------- -Latte, tüm temel ihtiyaçlar dahil olmak üzere tam donanımlı olarak gelir. +Latte, tüm önemli özellikler temelinde tam donanımlı olarak gelir. -- Tekrarlanan öğeleri ve yapıları yeniden kullanan [kalıtım mekanizmaları |template-inheritance] sayesinde üretkenliğiniz artacak -- [Sandbox |Sandbox] zırh sığınağı, şablonları kullanıcıların kendileri tarafından düzenlenenler gibi güvenilmeyen kaynaklardan izole eder -- Daha fazla ilham almak için işte [ipuçları ve |recipes] püf noktaları +- Tekrarlanan öğelerin ve yapıların yeniden kullanılmasını sağlayan [kalıtım mekanizmaları |template-inheritance] sayesinde üretkenliğiniz artacaktır +- Zırhlı sığınak [sandbox], örneğin kullanıcıların kendilerinin düzenlediği güvenilmeyen kaynaklardan gelen şablonları izole eder +- Daha fazla ilham için [ipuçları ve püf noktaları |recipes] var
                                                                                                                            -{{description: Latte, PHP için en güvenli şablonlama sistemidir. Birçok güvenlik açığını önler. Sezgisel sözdizimini ve birçok yararlı ince ayarı takdir edeceksiniz.}} +{{description: Latte, PHP için en güvenli şablonlama sistemidir. Birçok güvenlik açığını önler. Sezgisel sözdizimini ve birçok kullanışlı özelliğini takdir edeceksiniz.}} diff --git a/latte/tr/loaders.texy b/latte/tr/loaders.texy new file mode 100644 index 0000000000..70396a1055 --- /dev/null +++ b/latte/tr/loaders.texy @@ -0,0 +1,198 @@ +Yükleyiciler +************ + +.[perex] +Yükleyiciler, Latte'nin şablonlarınızın kaynak kodunu almak için kullandığı mekanizmadır. Çoğu zaman şablonlar diskteki dosyalar olarak saklanır, ancak esnek yükleyici sistemi sayesinde bunları pratikte her yerden yükleyebilir veya hatta dinamik olarak oluşturabilirsiniz. + + +Yükleyici Nedir? +================ + +Şablonlarla çalışırken, genellikle projenizin dizin yapısında bulunan `.latte` dosyalarını hayal edersiniz. Latte'deki varsayılan [#FileLoader] bununla ilgilenir. Ancak, bir şablon adı ( `'main.latte'` veya `'components/card.latte'` gibi) ile gerçek kaynak kodu arasındaki bağlantı, bir dosya yoluna doğrudan bir eşleme *olmayabilir*. + +İşte burada yükleyiciler devreye girer. Yükleyici, bir şablon adını (tanımlayıcı bir dize) alıp Latte'ye kaynak kodunu sağlamakla görevli bir nesnedir. Latte bu görev için tamamen yapılandırılmış yükleyiciye güvenir. Bu yalnızca `$latte->render('main.latte')` ile istenen başlangıç şablonu için değil, aynı zamanda `{include ...}`, `{layout ...}`, `{embed ...}` veya `{import ...}` gibi etiketler kullanılarak **içeride başvurulan her şablon** için de geçerlidir. + +Neden özel bir yükleyici kullanmalı? + +- **Alternatif kaynaklardan yükleme:** Veritabanında, önbellekte (Redis veya Memcached gibi), sürüm kontrol sisteminde (Git gibi, belirli bir commite dayalı olarak) saklanan veya dinamik olarak oluşturulan şablonları alma. +- **Özel adlandırma kurallarını uygulama:** Şablonlar için daha kısa takma adlar kullanmak veya belirli bir arama yolu mantığı uygulamak isteyebilirsiniz (ör. önce tema dizininde arama, sonra varsayılan dizine geri dönme). +- **Güvenlik veya erişim kontrolü ekleme:** Özel bir yükleyici, belirli şablonları yüklemeden önce kullanıcı izinlerini doğrulayabilir. +- **Ön işleme:** Genellikle önerilmese de ([derleme geçişleri |compiler-passes] daha iyidir), bir yükleyici *teorik olarak* şablon içeriğini Latte'ye iletmeden önce ön işleyebilir. + +Bir `Latte\Engine` örneği için yükleyiciyi `setLoader()` metoduyla ayarlarsınız: + +```php +$latte = new Latte\Engine; + +// '/path/to/templates' içindeki dosyalar için varsayılan FileLoader'ı kullanma +$loader = new Latte\Loaders\FileLoader('/path/to/templates'); +$latte->setLoader($loader); +``` + +Yükleyici, `Latte\Loader` arayüzünü uygulamalıdır. + + +Yerleşik Yükleyiciler +===================== + +Latte birkaç standart yükleyici sunar: + + +FileLoader +---------- + +Bu, başka bir tane belirtilmediği sürece `Latte\Engine` sınıfı tarafından kullanılan **varsayılan yükleyicidir**. Şablonları doğrudan dosya sisteminden yükler. + +İsteğe bağlı olarak, erişimi kısıtlamak için bir kök dizin ayarlayabilirsiniz: + +```php +use Latte\Loaders\FileLoader; + +// Aşağıdakiler yalnızca /var/www/html/templates dizininden şablon yüklemeye izin verir +$loader = new FileLoader('/var/www/html/templates'); +$latte->setLoader($loader); + +// $latte->render('../../../etc/passwd'); // Bu bir istisna fırlatırdı + +// /var/www/html/templates/pages/contact.latte konumundaki şablonu oluşturma +$latte->render('pages/contact.latte'); +``` + +`{include}` veya `{layout}` gibi etiketleri kullanırken, mutlak bir yol belirtilmedikçe şablon adlarını mevcut şablona göre çözer. + + +StringLoader +------------ + +Bu yükleyici, şablon içeriğini, anahtarların şablon adları (tanımlayıcılar) ve değerlerin şablon kaynak kodu dizeleri olduğu ilişkisel bir diziden alır. Özellikle test etme veya şablonların doğrudan PHP kodunda saklanabileceği küçük uygulamalar için kullanışlıdır. + +```php +use Latte\Loaders\StringLoader; + +$loader = new StringLoader([ + 'main.latte' => 'Merhaba {$name}, include aşağıdadır:{include helper.latte}', + 'helper.latte' => '{var $x = 10}Dahil edilen içerik: {$x}', + // Gerektiğinde daha fazla şablon ekleyin +]); + +$latte->setLoader($loader); + +$latte->render('main.latte', ['name' => 'Dünya']); +// Çıktı: Merhaba Dünya, include aşağıdadır:Dahil edilen içerik: 10 +``` + +Diğer adlandırılmış dize şablonlarına başvuran ekleme veya kalıtıma ihtiyaç duymadan yalnızca bir şablonu doğrudan bir dizeden oluşturmanız gerekiyorsa, bir dizi olmadan `StringLoader` kullanırken dizeyi doğrudan `render()` veya `renderToString()` metoduna iletebilirsiniz: + +```php +$loader = new StringLoader; +$latte->setLoader($loader); + +$templateString = 'Merhaba {$name}!'; +$output = $latte->renderToString($templateString, ['name' => 'Alice']); +// $output 'Merhaba Alice!' içerir +``` + + +Özel Bir Yükleyici Oluşturma +============================ + +Özel bir yükleyici oluşturmak için (ör. şablonları veritabanından, önbellekten, sürüm kontrol sisteminden veya başka bir kaynaktan yüklemek için), [api:Latte\Loader] arayüzünü uygulayan bir sınıf oluşturmanız gerekir. + +Her metodun ne yapması gerektiğine bakalım. + + +getContent(string $name): string .[method] +------------------------------------------ +Bu, yükleyicinin temel metodudur. Görevi, `$name` ile tanımlanan şablonun tam kaynak kodunu almak ve döndürmektir (`$latte->render()` metoduna iletildiği veya [#getReferredName()] metodu tarafından döndürüldüğü gibi). + +Şablon bulunamazsa veya erişilemezse, bu metot **bir `Latte\RuntimeException` istisnası fırlatmalıdır**. + +```php +public function getContent(string $name): string +{ + // Örnek: Varsayımsal bir dahili depodan yükleme + $content = $this->storage->read($name); + if ($content === null) { + throw new Latte\RuntimeException("'$name' şablonu yüklenemiyor."); + } + return $content; +} +``` + + +getReferredName(string $name, string $referringName): string .[method] +---------------------------------------------------------------------- +Bu metot, `{include}`, `{layout}` vb. etiketler içinde kullanılan şablon adlarının çözümlenmesini yönetir. Latte örneğin `main.latte` içinde `{include 'partial.latte'}` ile karşılaştığında, bu metodu `$name = 'partial.latte'` ve `$referringName = 'main.latte'` ile çağırır. + +Metodun görevi, `$referringName` içinde sağlanan bağlama dayanarak, diğer yükleyici metotları çağrılırken kullanılacak olan `$name`'i kanonik bir tanımlayıcıya (ör. mutlak yol, benzersiz veritabanı anahtarı) çevirmektir. + +```php +public function getReferredName(string $name, string $referringName): string +{ + return ...; +} +``` + + +getUniqueId(string $name): string .[method] +------------------------------------------- +Latte, performansı artırmak için derlenmiş şablonların bir önbelleğini kullanır. Her derlenmiş şablon dosyası, kaynak şablon tanımlayıcısından türetilen benzersiz bir ada ihtiyaç duyar. Bu metot, `$name` şablonunu **benzersiz olarak tanımlayan** bir dize sağlar. + +Dosya tabanlı şablonlar için mutlak yol işe yarayabilir. Veritabanındaki şablonlar için, bir önek ve veritabanı ID'sinin birleşimi yaygındır. + +```php +public function getUniqueId(string $name): string +{ + return ...; +} +``` + + +Örnek: Basit Veritabanı Yükleyicisi +----------------------------------- + +Bu örnek, `name` (benzersiz tanımlayıcı), `content` ve `updated_at` sütunlarına sahip `templates` adlı bir veritabanı tablosunda saklanan şablonları yükleyen bir yükleyicinin temel yapısını gösterir. + +```php +use Latte; + +class DatabaseLoader implements Latte\Loader +{ + public function __construct( + private \PDO $db, + ) { + } + + public function getContent(string $name): string + { + $stmt = $this->db->prepare('SELECT content FROM templates WHERE name = ?'); + $stmt->execute([$name]); + $content = $stmt->fetchColumn(); + if ($content === false) { + throw new Latte\RuntimeException("'$name' şablonu veritabanında bulunamadı."); + } + return $content; + } + + // Bu basit örnek, şablon adlarının ('homepage', 'article', vb.) + // benzersiz ID'ler olduğunu ve şablonların birbirine göreceli olarak başvurmadığını varsayar. + public function getReferredName(string $name, string $referringName): string + { + return $name; + } + + public function getUniqueId(string $name): string + { + // Bir önek ve adın kendisini kullanmak burada benzersiz ve yeterlidir + return 'db_' . $name; + } +} + +// Kullanım: +$pdo = new \PDO(/* bağlantı detayları */); +$loader = new DatabaseLoader($pdo); +$latte->setLoader($loader); +$latte->render('homepage'); // DB'den 'homepage' adlı şablonu yükler +``` + +Özel yükleyiciler, Latte şablonlarınızın nereden geldiği üzerinde tam kontrol sahibi olmanızı sağlar, bu da çeşitli depolama sistemleri ve iş akışlarıyla entegrasyona olanak tanır. diff --git a/latte/tr/recipes.texy b/latte/tr/recipes.texy index 45c99eaa74..b38315264b 100644 --- a/latte/tr/recipes.texy +++ b/latte/tr/recipes.texy @@ -2,44 +2,44 @@ ************************* -Editörler ve IDE .[#toc-editors-and-ide] -======================================== +Düzenleyiciler ve IDE'ler +========================= -Şablonları Latte desteği olan bir editör veya IDE'de yazın. Çok daha keyifli olacaktır. +Şablonları Latte desteği olan bir düzenleyicide veya IDE'de yazın. Çok daha keyifli olacaktır. -- NetBeans IDE yerleşik desteğe sahiptir -- PhpStorm: [Latte eklentisini |https://plugins.jetbrains.com/plugin/7457-latte] şuraya yükleyin `Settings > Plugins > Marketplace` -- VS Code: "Nette Latte + Neon" eklentisi için markerplace'te arama yapın -- Sublime Text 3: Paket Kontrolünde `Nette` paketini bulun ve yükleyin ve Latte'yi seçin `View > Syntax` +- PhpStorm: `Settings > Plugins > Marketplace` içinden [Latte eklentisi|https://plugins.jetbrains.com/plugin/7457-latte]'ni kurun +- VS Code: [Nette Latte + Neon|https://marketplace.visualstudio.com/items?itemName=Kasik96.latte], [Nette Latte templates|https://marketplace.visualstudio.com/items?itemName=smuuf.latte-lang] veya en yeni [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode] eklentisini kurun +- NetBeans IDE: yerel Latte desteği kurulumun bir parçasıdır +- Sublime Text 3: Package Control'de `Nette` paketini bulun ve kurun ve `View > Syntax` içinden Latte'yi seçin - eski düzenleyicilerde .latte dosyaları için Smarty vurgulamasını kullanın -PhpStorm eklentisi çok gelişmiştir ve PHP kodunu mükemmel bir şekilde önerebilir. En iyi şekilde çalışmak için, [yazılan şablonları |type-system] kullanın. +PhpStorm için eklenti çok gelişmiştir ve PHP kodunu mükemmel bir şekilde önerebilir. Optimum şekilde çalışması için [türlenmiş şablonlar|type-system] kullanın. [* latte-phpstorm-plugin.webp *] -Latte desteği, web kod vurgulayıcı [Prism.js |https://prismjs.com/#supported-languages] ve editör [Ace'de |https://ace.c9.io] de bulunabilir. +Latte desteğini ayrıca web kod vurgulayıcısı [Prism.js|https://prismjs.com/#supported-languages] ve düzenleyici [Ace|https://ace.c9.io]'de de bulabilirsiniz. -JavaScript veya CSS İçinde Latte .[#toc-latte-inside-javascript-or-css] -======================================================================= +JavaScript veya CSS İçinde Latte +================================ -Latte, JavaScript veya CSS içinde çok rahat bir şekilde kullanılabilir. Ancak Latte'nin yanlışlıkla JavaScript kodunu veya CSS stilini bir Latte etiketi olarak görmesi nasıl önlenir? +Latte, JavaScript veya CSS içinde de çok rahat bir şekilde kullanılabilir. Ancak Latte'nin yanlışlıkla JavaScript kodunu veya CSS stilini Latte etiketi olarak değerlendireceği durumdan nasıl kaçınılır? ```latte ``` **Seçenek 1** -Aralarına boşluk, satır sonu veya tırnak işareti koyarak bir harfin `{` harfinden hemen sonra geldiği durumlardan kaçının: +`{` işaretinden hemen sonra bir harfin geldiği durumdan kaçının, örneğin önüne bir boşluk, satır sonu veya tırnak işareti ekleyerek: ```latte -

                                                                                                                            + +

                                                                                                                            ``` -İki yol ve iki farklı türde veri kaçışı. İçinde ` ``` -Ancak, bunu bir HTML niteliğine eklemek istiyorsak, yine de tırnak işaretlerini HTML varlıklarına kaçmamız gerekir: +Ancak bunu bir HTML niteliğine eklemek istersek, tırnak işaretlerini de HTML varlıklarına kaçış işlemine tabi tutmamız gerekir: ```html
                                                                                                                            ``` -Ancak, iç içe geçmiş bağlam sadece JS veya CSS olmak zorunda değildir. Genellikle bir URL de olabilir. URL'lerdeki parametreler, özel karakterler `%` ile başlayan dizilere dönüştürülerek kaçılır. Örnek: +Ancak iç içe geçmiş bağlam yalnızca JS veya CSS olmak zorunda değildir. Genellikle URL de olabilir. URL'deki parametreler, özel anlamı olan karakterlerin `%` ile başlayan dizilere dönüştürülmesiyle kaçış işlemine tabi tutulur. Örnek: ``` https://example.org/?a=Jazz&b=Rock%27n%27Roll ``` -Ve bu dizeyi bir öznitelikte çıktıladığımızda, yine bu bağlama göre kaçış uygularız ve `&` with `&` yerine koyarız: +Ve bu dizeyi bir nitelikte yazdırdığımızda, bu bağlama göre kaçış işlemini de uygular ve `&` yerine `&` yazarız: ```html ``` -Buraya kadar okuduysanız, tebrikler, yorucu oldu. Artık bağlamların ve kaçışın ne olduğu hakkında iyi bir fikriniz var. Ve karmaşık olması konusunda endişelenmenize gerek yok. Latte bunu sizin için otomatik olarak yapıyor. +Buraya kadar okuduysanız tebrikler, yorucuydu. Artık bağlamların ve kaçış işleminin ne olduğu hakkında iyi bir fikriniz var. Ve karmaşık olduğu konusunda endişelenmenize gerek yok. Latte bunu sizin için otomatik olarak yapar. -Latte vs Naif Sistemler .[#toc-latte-vs-naive-systems] -====================================================== +Latte vs Saf Sistemler +====================== -Bir HTML belgesinde kaçış işleminin nasıl düzgün bir şekilde yapılacağını ve bağlamı, yani verinin çıktısını nerede aldığınızı bilmenin ne kadar önemli olduğunu gösterdik. Başka bir deyişle, bağlama duyarlı kaçış nasıl çalışır. -Bu, işlevsel XSS savunması için bir ön koşul olsa da, **Latte, PHP için bunu yapan tek şablonlama sistemidir.** +Bir HTML belgesinde nasıl doğru bir şekilde kaçış yapılacağını ve bağlamın, yani verileri yazdırdığımız yerin bilgisinin ne kadar önemli olduğunu gösterdik. Başka bir deyişle, bağlama duyarlı kaçışın nasıl çalıştığını. XSS'e karşı işlevsel bir savunma için gerekli bir ön koşul olmasına rağmen, **Latte bunu yapabilen PHP için tek şablonlama sistemidir.** -Günümüzde tüm sistemler otomatik kaçış özelliğine sahip olduğunu iddia ederken bu nasıl mümkün olabilir? -Bağlamı bilmeden otomatik kaçış, **yanlış bir güvenlik duygusu** yaratan bir saçmalıktır. +Bugün tüm sistemler otomatik kaçışa sahip olduklarını iddia ederken bu nasıl mümkün olabilir? Bağlam bilgisi olmadan otomatik kaçış, **yanlış bir güvenlik hissi yaratan** biraz saçmalıktır. -Twig, Laravel Blade ve diğerleri gibi şablonlama sistemleri şablonda herhangi bir HTML yapısı görmez. Bu nedenle, bağlamları da görmezler. Latte ile karşılaştırıldığında, kör ve naiftirler. Sadece kendi işaretlemelerini ele alırlar, diğer her şey onlar için alakasız bir karakter akışıdır: +Twig, Laravel Blade ve diğerleri gibi şablonlama sistemleri, şablonda herhangi bir HTML yapısı görmezler. Dolayısıyla bağlamları da görmezler. Latte'ye kıyasla kör ve saftırlar. Yalnızca kendi etiketlerini işlerler, diğer her şey onlar için önemsiz bir karakter akışıdır:
                                                                                                                            -```twig .{file:Twig template as seen by Twig himself} -░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░ -░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░{{ text }}░░░░ +```twig .{file:Twig şablonu, Twig'in kendisinin gördüğü gibi} +░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░ +░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░ ``` -```twig .{file:Twig template as the designer sees it} -- in text: {{ text }} -- in tag: -- in attribute: -- in unquoted attribute: -- in attribute containing URL: -- in attribute containing JavaScript: -- in attribute containing CSS: -- in JavaScriptu: -- in CSS: -- in comment: +```twig .{file:Twig şablonu, tasarımcının gördüğü gibi} +- metinde: {{ foo }} +- etikette: +- nitelikte: +- tırnaksız nitelikte: +- URL içeren nitelikte: +- JavaScript içeren nitelikte: +- CSS içeren nitelikte: +- JavaScript'te: +- CSS'te: +- yorumda: ```
                                                                                                                            -Naif sistemler sadece mekanik olarak `< > & ' "` karakterlerini HTML varlıklarına dönüştürür, bu da çoğu kullanımda geçerli bir kaçış yoludur, ancak her zaman değil. Bu nedenle, aşağıda göstereceğimiz gibi çeşitli güvenlik açıklarını tespit edemez veya önleyemezler. +Saf sistemler yalnızca `< > & ' "` karakterlerini mekanik olarak HTML varlıklarına dönüştürür, bu da çoğu kullanım durumunda geçerli bir kaçış yöntemi olsa da, her zaman geçerli değildir. Bu nedenle, aşağıda göstereceğimiz gibi çeşitli güvenlik açıklarının oluşumunu ne tespit edebilir ne de önleyebilirler. -Latte şablonu sizinle aynı şekilde görür. HTML ve XML'i anlar, etiketleri, nitelikleri vb. tanır. Ve bu nedenle, bağlamlar arasında ayrım yapar ve verileri buna göre ele alır. Böylece kritik Siteler Arası Komut Dosyası Açığına karşı gerçekten etkili bir koruma sunar. +Latte şablonu sizin gördüğünüz gibi görür. HTML, XML'i anlar, etiketleri, nitelikleri vb. tanır. Ve bu sayede bireysel bağlamları ayırt eder ve verilere bunlara göre işlem yapar. Böylece kritik Siteler Arası Komut Dosyası Çalıştırma güvenlik açığına karşı gerçekten etkili bir koruma sunar. + +
                                                                                                                            + +```latte .{file:Latte şablonu, Latte'nin gördüğü gibi} +░░░░░░░░░░░{$foo} +░░░░░░░░░░ +░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░ +░░░░░░░░░ +░░░░░░░░░░░░░░░ +``` + +```latte .{file:Latte şablonu, tasarımcının gördüğü gibi} +- metinde: {$foo} +- etikette: +- nitelikte: +- tırnaksız nitelikte: +- URL içeren nitelikte: +- JavaScript içeren nitelikte: +- CSS içeren nitelikte: +- JavaScript'te: +- CSS'te: +- yorumda: +``` + +
                                                                                                                            -Canlı Gösteri .[#toc-live-demonstration] -======================================== +Canlı Gösterim +============== -Solda Latte'deki şablonu, sağda ise oluşturulan HTML kodunu görebilirsiniz. `$text` değişkeni, her seferinde biraz farklı bir bağlamda olmak üzere birkaç kez çıktılanır. Ve bu nedenle biraz farklı şekilde öncelenmiştir. Şablon kodunu kendiniz düzenleyebilirsiniz, örneğin değişkenin içeriğini vb. değiştirebilirsiniz. Deneyin: +Solda Latte'deki şablonu, sağda ise oluşturulan HTML kodunu görüyorsunuz. `$text` değişkeni burada birkaç kez ve her seferinde biraz farklı bir bağlamda yazdırılıyor. Ve dolayısıyla biraz farklı bir şekilde kaçış işlemine tabi tutuluyor. Şablon kodunu kendiniz düzenleyebilirsiniz, örneğin değişkenin içeriğini değiştirebilirsiniz vb. Deneyin:
                                                                                                                            ``` .{file:template.latte; min-height: 14em}[fiddle-source] -{* TRY TO EDIT THIS TEMPLATE *} +{* BU ŞABLONU DÜZENLEMEYİ DENEYİN *} {var $text = "Rock'n'Roll"} - {$text} - @@ -270,63 +286,61 @@ Solda Latte'deki şablonu, sağda ise oluşturulan HTML kodunu görebilirsiniz.
                                                                                                                            -Bu harika değil mi! Latte bağlama duyarlı kaçışı otomatik olarak yapar, böylece programcı: +Harika değil mi! Latte bağlama duyarlı kaçış işlemini otomatik olarak yapar, böylece programcı: -- düşünmek ya da veriden nasıl kaçacağını bilmek zorunda değildir -- Yanlış olamaz. -- bunu unutamam +- nerede nasıl kaçış yapılacağını düşünmek veya bilmek zorunda kalmaz +- hata yapamaz +- kaçış işlemini unutamaz -Bunlar Latte'nin çıktı alırken ayırt ettiği ve veri işlemeyi özelleştirdiği tüm bağlamlar bile değildir. Şimdi daha ilginç durumların üzerinden geçeceğiz. +Bunlar bile Latte'nin yazdırırken ayırt ettiği ve verilere işlemi uyarladığı tüm bağlamlar değildir. Şimdi diğer ilginç durumları ele alacağız. -Naif Sistemler Nasıl Hacklenir .[#toc-how-to-hack-naive-systems] -================================================================ +Saf Sistemler Nasıl Hacklenir +============================= -Bağlam farklılaştırmasının ne kadar önemli olduğunu ve Latte'nin aksine naif şablonlama sistemlerinin neden XSS'ye karşı yeterli koruma sağlamadığını göstermek için birkaç pratik örnek kullanacağız. -Örneklerde Twig'i naif bir sistemin temsilcisi olarak kullanacağız, ancak aynı şey diğer sistemler için de geçerlidir. +Birkaç pratik örnekle, bağlamları ayırt etmenin ne kadar önemli olduğunu ve saf şablonlama sistemlerinin neden Latte'nin aksine XSS'e karşı yeterli koruma sağlamadığını göstereceğiz. Saf sistem temsilcisi olarak örneklerde Twig kullanacağız, ancak aynı şey diğer sistemler için de geçerlidir. -Öznitelik Güvenlik Açığı .[#toc-attribute-vulnerability] --------------------------------------------------------- +Nitelik Güvenlik Açığı +---------------------- -[Yukarıda gösterdiğimiz |#How does the vulnerability arise] gibi HTML özniteliğini kullanarak sayfaya kötü amaçlı kod enjekte etmeye çalışalım. Twig'de bir resim görüntüleyen bir şablonumuz olsun: +[Yukarıda gösterdiğimiz gibi |#Güvenlik Açığı Nasıl Oluşur], bir HTML niteliği kullanarak sayfaya kötü amaçlı kod enjekte etmeye çalışacağız. Twig'de bir resim oluşturan bir şablonumuz olsun: ```twig .{file:Twig} {{ ``` -Öznitelik değerlerinin etrafında tırnak işareti olmadığına dikkat edin. Kodlayıcı bunları unutmuş olabilir, ki bu sadece olur. Örneğin, React'te kod tırnak işaretleri olmadan bu şekilde yazılır ve dil değiştiren bir kodlayıcı tırnak işaretlerini kolayca unutabilir. +Nitelik değerlerinin etrafında tırnak işareti olmadığına dikkat edin. Kodlayıcı bunları unutmuş olabilir, bu basitçe olur. Örneğin, React'te kod bu şekilde, tırnak işaretleri olmadan yazılır ve diller arasında geçiş yapan bir kodlayıcı daha sonra tırnak işaretlerini kolayca unutabilir. -Saldırgan, akıllıca oluşturulmuş bir dizeyi `foo onload=alert('Hacked!')` resim başlığı olarak ekler. Twig'in bir değişkenin HTML metin akışı içinde mi, bir niteliğin içinde mi, bir HTML yorumunun içinde mi, vb. mi yazdırıldığını söyleyemediğini zaten biliyoruz; kısacası, bağlamlar arasında ayrım yapmaz. Ve sadece mekanik olarak `< > & ' "` karakterlerini HTML varlıklarına dönüştürür. -Böylece ortaya çıkan kod şöyle görünecektir: +Saldırgan, resim açıklaması olarak akıllıca oluşturulmuş bir karakter dizisi `foo onload=alert('Hacklendiniz!')` ekler. Twig'in değişkenin HTML metin akışında mı, bir nitelik içinde mi, HTML yorumunda mı vb. yazdırıldığını anlayamayacağını, kısacası bağlamları ayırt etmediğini zaten biliyoruz. Ve yalnızca `< > & ' "` karakterlerini mekanik olarak HTML varlıklarına dönüştürür. Yani sonuç kodu şöyle görünecektir: ```html -foo +foo ``` -**Bir güvenlik açığı oluşturuldu!** +**Ve bir güvenlik açığı oluştu!** -Sahte bir `onload` özelliği sayfanın bir parçası haline gelmiştir ve tarayıcı resmi indirdikten hemen sonra bu özelliği çalıştırır. +Sahte `onload` niteliği sayfanın bir parçası haline geldi ve tarayıcı resmi indirir indirmez onu çalıştırır. -Şimdi Latte'nin aynı şablonu nasıl ele aldığını görelim: +Şimdi Latte'nin aynı şablonla nasıl başa çıktığına bakalım: ```latte .{file:Latte} {$imageAlt} ``` -Latte şablonu sizinle aynı şekilde görür. Twig'in aksine, HTML'yi anlar ve bir değişkenin tırnak içinde olmayan bir öznitelik değeri olarak yazdırıldığını bilir. Bu yüzden onları ekler. Bir saldırgan aynı başlığı eklediğinde, ortaya çıkan kod aşağıdaki gibi görünecektir: +Latte şablonu sizin gördüğünüz gibi görür. Twig'in aksine, HTML'i anlar ve değişkenin tırnak içinde olmayan bir niteliğin değeri olarak yazdırıldığını bilir. Bu yüzden onları ekler. Saldırgan aynı açıklamayı eklediğinde, sonuç kodu şöyle görünecektir: ```html -foo onload=alert('Hacked!') +foo onload=alert('Hacklendiniz!') ``` -**Latte XSS'yi başarıyla engelledi.** +**Latte XSS'i başarıyla önledi.** -JavaScript'te Değişken Yazdırma .[#toc-printing-a-variable-in-javascript] -------------------------------------------------------------------------- +JavaScript'te Değişken Yazdırma +------------------------------- -Bağlama duyarlı kaçış sayesinde PHP değişkenlerini JavaScript içinde yerel olarak kullanmak mümkündür. +Bağlama duyarlı kaçış sayesinde, JavaScript içinde PHP değişkenlerini tamamen yerel olarak kullanmak mümkündür. ```latte

                                                                                                                            {$movie}

                                                                                                                            @@ -334,7 +348,7 @@ Bağlama duyarlı kaçış sayesinde PHP değişkenlerini JavaScript içinde yer ``` -`$movie` değişkeni `'Amarcord & 8 1/2'` dizesini depolarsa aşağıdaki çıktıyı üretir. HTML ve JavaScript'te ve ayrıca `onclick` niteliğinde kullanılan farklı kaçışlara dikkat edin: +Eğer `$movie` değişkeni `'Amarcord & 8 1/2'` dizesini içeriyorsa, aşağıdaki çıktı üretilir. HTML içinde farklı bir kaçışın kullanıldığına, JavaScript içinde farklı ve `onclick` niteliğinde yine farklı olduğuna dikkat edin: ```latte

                                                                                                                            Amarcord & 8 1/2

                                                                                                                            @@ -343,29 +357,27 @@ Bağlama duyarlı kaçış sayesinde PHP değişkenlerini JavaScript içinde yer ``` -Bağlantı Kontrolü .[#toc-link-checking] ---------------------------------------- +Bağlantı Kontrolü +----------------- -Latte, `src` veya `href` niteliklerinde kullanılan değişkenin bir web URL'si (yani HTTP protokolü) içerip içermediğini otomatik olarak kontrol eder ve güvenlik riski oluşturabilecek bağlantıların yazılmasını engeller. +Latte, `src` veya `href` niteliklerinde kullanılan değişkenin bir web URL'si (yani HTTP protokolü) içerip içermediğini otomatik olarak kontrol eder ve güvenlik riski oluşturabilecek bağlantıların yazdırılmasını önler. ```latte {var $link = 'javascript:attack()'} -click here +tıkla ``` -Yazıyor: +Yazdırır: ```latte -click here +tıkla ``` Kontrol, [nocheck |filters#nocheck] filtresi kullanılarak kapatılabilir. -Latte'nin Sınırları .[#toc-limits-of-latte] -=========================================== +Latte Sınırları +=============== -Latte, tüm uygulama için eksiksiz bir XSS koruması değildir. Latte'yi kullanırken güvenlik hakkında düşünmeyi bırakırsanız mutsuz oluruz. -Latte'nin amacı, bir saldırganın bir sayfanın yapısını değiştirememesini, HTML öğelerini veya niteliklerini kurcalayamamasını sağlamaktır. Ancak çıktısı alınan verilerin içerik doğruluğunu kontrol etmez. Ya da JavaScript davranışının doğruluğunu. -Bu, şablonlama sisteminin kapsamı dışındadır. Özellikle kullanıcı tarafından girilen ve dolayısıyla güvenilmeyen verilerin doğruluğunu kontrol etmek programcı için önemli bir görevdir. +Latte, tüm uygulama için XSS'e karşı tamamen eksiksiz bir koruma değildir. Latte kullanırken güvenlik hakkında düşünmeyi bırakmanızı istemeyiz. Latte'nin amacı, saldırganın sayfanın yapısını değiştirememesini, sahte HTML öğeleri veya nitelikleri ekleyememesini sağlamaktır. Ancak yazdırılan verilerin içerik doğruluğunu kontrol etmez. Veya JavaScript davranışının doğruluğunu. Bu zaten şablonlama sisteminin yetkinliğinin dışına çıkar. Verilerin doğruluğunu doğrulamak, özellikle kullanıcı tarafından girilen ve dolayısıyla güvenilmeyen verileri doğrulamak, programcının önemli bir görevidir. diff --git a/latte/tr/sandbox.texy b/latte/tr/sandbox.texy index fa8b78e2c4..8fe565b0a3 100644 --- a/latte/tr/sandbox.texy +++ b/latte/tr/sandbox.texy @@ -1,12 +1,10 @@ Sandbox ******* -.[perex]{data-version:2.8} -Sandbox, şablonlarda hangi etiketlerin, PHP işlevlerinin, yöntemlerin vb. kullanılabileceğini kontrol etmenizi sağlayan bir güvenlik katmanı sağlar. Sandbox modu sayesinde, uygulamayı tehlikeye atma veya istenmeyen işlemler konusunda endişelenmeden şablon oluşturma konusunda bir müşteri veya harici kodlayıcı ile güvenle işbirliği yapabilirsiniz. +.[perex] +Sandbox, şablonlarda hangi etiketlerin, PHP fonksiyonlarının, metotlarının vb. kullanılabileceğini kontrol etmenizi sağlayan bir güvenlik katmanı sağlar. Sandbox modu sayesinde, uygulamanın bozulması veya istenmeyen işlemler konusunda endişelenmeden şablon oluşturma konusunda müşteriyle veya harici kodlayıcıyla güvenli bir şekilde işbirliği yapabilirsiniz. -Nasıl çalışır? Basitçe şablonda neye izin vermek istediğimizi tanımlıyoruz. Başlangıçta her şey yasaktır ve kademeli olarak izinler veririz: - -Aşağıdaki kod, şablonun `{block}`, `{if}`, `{else}` ve `{=}` etiketlerini (ikincisi [bir değişken veya ifadeyi yazdırmak |tags#Printing] için kullanılan bir etikettir) ve tüm filtreleri kullanmasını sağlar: +Nasıl çalışır? Basitçe şablona neye izin vereceğimizi tanımlarız. Varsayılan olarak her şey yasaktır ve biz yavaş yavaş izin veririz. Aşağıdaki kod, şablon yazarının `{block}`, `{if}`, `{else}` ve `{=}` etiketlerini (bu [değişken veya ifade yazdırma |tags#Yazdırma] etiketidir) ve tüm filtreleri kullanmasına izin verir: ```php $policy = new Latte\Sandbox\SecurityPolicy; @@ -16,7 +14,7 @@ $policy->allowFilters($policy::All); $latte->setPolicy($policy); ``` -Nesnelerin global fonksiyonlarına, metotlarına veya özelliklerine erişime de izin verebiliriz: +Ayrıca, bireysel fonksiyonlara, metotlara veya nesnelerin özelliklerine izin verebiliriz: ```php $policy->allowFunctions(['trim', 'strlen']); @@ -24,30 +22,35 @@ $policy->allowMethods(Nette\Security\User::class, ['isLoggedIn', 'isAllowed']); $policy->allowProperties(Nette\Database\Row::class, $policy::All); ``` -İnanılmaz değil mi? Her şeyi çok düşük bir seviyede kontrol edebilirsiniz. Şablon yetkisiz bir işlevi çağırmaya veya yetkisiz bir yönteme ya da özelliğe erişmeye çalışırsa `Latte\SecurityViolationException` istisnasını atar. +Harika değil mi? Her şeyi çok düşük bir seviyede tamamen kontrol edebilirsiniz. Şablon izin verilmeyen bir fonksiyonu çağırmaya veya izin verilmeyen bir metoda veya özelliğe erişmeye çalışırsa, `Latte\SecurityViolationException` istisnasıyla sonuçlanır. -Her şey yasakken sıfırdan politika oluşturmak uygun olmayabilir, bu nedenle güvenli bir temelden başlayabilirsiniz: +Politikayı her şeyin tamamen yasak olduğu sıfır noktasından oluşturmak uygun olmayabilir, bu nedenle güvenli bir temelden başlayabilirsiniz: ```php $policy = Latte\Sandbox\SecurityPolicy::createSafePolicy(); ``` -Bu, `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget` hariç tüm standart etiketlere izin verildiği anlamına gelir. -`datastream`, `noescape` ve `nocheck` hariç tüm standart filtrelere de izin verilmektedir. Son olarak, `$iterator` nesnesinin yöntemlerine ve özelliklerine erişime de izin verilir. +Güvenli temel, `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget` dışındaki tüm standart etiketlere izin verildiği anlamına gelir. `datastream`, `noescape` ve `nocheck` dışındaki standart filtrelere izin verilir. Ve son olarak, `$iterator` nesnesinin metotlarına ve özelliklerine erişime izin verilir. -Kurallar, yeni şablonla birlikte eklediğimiz şablon için geçerlidir [`{sandbox}` |tags#Including Templates] etiketi. Bu `{include}` gibi bir şeydir, ancak sandbox modunu açar ve ayrıca herhangi bir harici değişken geçmez: +Kurallar, [`{sandbox}` |tags#Şablon Ekleme] etiketiyle eklediğimiz şablon için geçerlidir. Bu, `{include}`'in bir tür benzeridir, ancak güvenli modu açar ve ayrıca hiçbir değişken aktarmaz: ```latte -{sandbox 'untrusted.latte'} +{sandbox 'guvenilmeyen.latte'} ``` -Böylece, düzen ve tek tek sayfalar daha önce olduğu gibi tüm etiketleri ve değişkenleri kullanabilir, kısıtlamalar yalnızca `untrusted.latte` şablonuna uygulanacaktır. +Yani düzen ve bireysel sayfalar tüm etiketleri ve değişkenleri rahatsız edilmeden kullanabilir, yalnızca `guvenilmeyen.latte` şablonuna kısıtlamalar uygulanır. -Yasak etiket veya filtre kullanımı gibi bazı ihlaller derleme zamanında tespit edilir. Bir nesnenin izin verilmeyen yöntemlerinin çağrılması gibi diğerleri ise çalışma zamanında tespit edilir. -Şablon başka hatalar da içerebilir. Sandbox'lı şablondan bir istisnanın fırlatılmasını önlemek için, örneğin sadece günlüğe kaydeden [kendi |develop#exception handler] istisna [işleyicinizi |develop#exception handler] tanımlayabilirsiniz. +Yasaklanmış bir etiket veya filtre kullanımı gibi bazı ihlaller derleme zamanında ortaya çıkar. Nesnenin izin verilmeyen metotlarının çağrılması gibi diğerleri ise çalışma zamanına kadar ortaya çıkmaz. Şablon ayrıca başka herhangi bir hata da içerebilir. Sandbox'lı şablondan tüm oluşturmayı bozacak bir istisnanın çıkmasını önlemek için, örneğin onu günlüğe kaydedecek özel bir [özel istisna işleyici |develop#İstisna İşleyici] tanımlayabilirsiniz. -Tüm şablonlar için doğrudan sandbox modunu açmak istiyorsak, bu çok kolay: +Sandbox modunu doğrudan tüm şablonlar için açmak istersek, bu kolaydır: ```php $latte->setSandboxMode(); ``` + +Kullanıcının sayfaya sözdizimsel olarak doğru ancak yasaklanmış ve PHP Derleme Hatasına neden olacak PHP kodu eklemediğinden emin olmak için, [şablonları PHP linter ile kontrol etme |develop#Oluşturulan Kodu Kontrol Etme] öneririz. Bu işlevselliği `Engine::enablePhpLint()` metoduyla açarsınız. Kontrol için PHP ikili dosyasını çağırması gerektiğinden, yolunu parametre olarak iletin: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); +``` diff --git a/latte/tr/syntax.texy b/latte/tr/syntax.texy index a844bd06db..9902790245 100644 --- a/latte/tr/syntax.texy +++ b/latte/tr/syntax.texy @@ -2,78 +2,76 @@ Sözdizimi ********* .[perex] -Syntax Latte, web tasarımcılarının pratik gereksinimlerinden doğdu. Aksi takdirde gerçek bir meydan okuma olan yapıları zarif bir şekilde yazabileceğiniz en kullanıcı dostu sözdizimini arıyorduk. -Aynı zamanda, tüm ifadeler PHP'deki ile tamamen aynı şekilde yazılır, bu nedenle yeni bir dil öğrenmeniz gerekmez. Sadece zaten bildiklerinizden en iyi şekilde faydalanırsınız. +Latte sözdizimi, web tasarımcılarının pratik gereksinimlerinden doğmuştur. Aksi takdirde gerçek bir baş ağrısı olan yapıları bile zarif bir şekilde yazabileceğiniz en kullanıcı dostu sözdizimini aradık. Aynı zamanda tüm ifadeler tıpkı PHP'deki gibi yazılır, bu yüzden yeni bir dil öğrenmenize gerek kalmaz. Sadece zaten bildiklerinizi kullanırsınız. -Aşağıda birkaç temel öğeyi gösteren minimal bir şablon bulunmaktadır: etiketler, n:attributes, yorumlar ve filtreler. +Aşağıda, birkaç temel öğeyi gösteren minimal bir şablon bulunmaktadır: etiketler, n:nitelikler, yorumlar ve filtreler. ```latte {* bu bir yorumdur *} -
                                                                                                                              {* n:if is n:atribut *} -{foreach $items as $item} {* foreach döngüsünü temsil eden etiket *} -
                                                                                                                            • {$item|capitalize}
                                                                                                                            • {* bir değişkeni bir filtre ile yazdıran etiket *} -{/foreach} {* döngünün sonu *} +
                                                                                                                                {* n:if bir n:niteliktir *} +{foreach $items as $item} {* foreach döngüsünü temsil eden etiket *} +
                                                                                                                              • {$item|capitalize}
                                                                                                                              • {* filtreli bir değişkeni yazdıran etiket *} +{/foreach} {* döngünün sonu *}
                                                                                                                              ``` -Şimdi bu önemli unsurlara ve inanılmaz bir şablon oluşturmanıza nasıl yardımcı olabileceklerine daha yakından bakalım. +Bu önemli öğelere ve harika bir şablon oluşturmanıza nasıl yardımcı olabileceklerine daha yakından bakalım. -Etiketler .[#toc-tags] -====================== +Etiketler +========= -Bir şablon, şablon mantığını (örneğin, *foreach* döngüleri) veya çıktı ifadelerini kontrol eden etiketler içerir. Her ikisi için de tek bir sınırlayıcı `{ ... }` kullanılır, böylece diğer sistemlerde olduğu gibi hangi durumda hangi sınırlayıcıyı kullanacağınızı düşünmeniz gerekmez. -Eğer `{` karakterinin ardından bir tırnak işareti veya boşluk gelirse, Latte bunu bir etiketin başlangıcı olarak kabul etmez, böylece şablonlarınızda JavaScript yapılarını, JSON'u veya CSS kurallarını sorunsuz bir şekilde kullanabilirsiniz. +Şablon, şablonun mantığını kontrol eden (örneğin *foreach* döngüleri) veya ifadeleri yazdıran etiketler içerir. Her ikisi için de tek bir sınırlayıcı `{ ... }` kullanılır, böylece diğer sistemlerde olduğu gibi hangi durumda hangi sınırlayıcıyı kullanacağınızı düşünmenize gerek kalmaz. Eğer `{` karakterini bir tırnak işareti veya boşluk takip ederse, Latte bunu bir etiketin başlangıcı olarak kabul etmez, bu sayede şablonlarda JavaScript yapılarını, JSON'u veya CSS kurallarını sorunsuz bir şekilde kullanabilirsiniz. -[Tüm etiketlere genel bak |tags] ışı görün. Ayrıca, [özel etiketler |extending-latte#tags] de oluşturabilirsiniz. +[Tüm etiketlerin özeti|tags]'ne göz atın. Ayrıca, [özel etiketler|custom tags] de oluşturabilirsiniz. -Latte PHP'yi Anlıyor .[#toc-latte-understands-php] -================================================== +Latte PHP'yi Anlar +================== -Etiketlerin içinde iyi bildiğiniz PHP ifadelerini kullanabilirsiniz: +Etiketlerin içinde, iyi bildiğiniz PHP ifadelerini kullanabilirsiniz: - değişkenler -- dizeler (HEREDOC ve NOWDOC dahil), diziler, sayılar, vb. +- karakter dizileri (HEREDOC ve NOWDOC dahil), diziler, sayılar vb. - [operatörler |https://www.php.net/manual/en/language.operators.php] -- fonksiyon ve metot çağrıları ( [sandbox |sandbox] tarafından kısıtlanabilir) -- [maç |https://www.php.net/manual/en/control-structures.match.php] +- fonksiyon ve metot çağrıları ([sandbox|sandbox] ile kısıtlanabilir) +- [match |https://www.php.net/manual/en/control-structures.match.php] - [anonim fonksiyonlar |https://www.php.net/manual/en/functions.arrow.php] -- [geri aramalar |https://www.php.net/manual/en/functions.first_class_callable_syntax.php] +- [callback'ler |https://www.php.net/manual/en/functions.first_class_callable_syntax.php] - çok satırlı yorumlar `/* ... */` -- vs... +- vb… -Buna ek olarak, Latte PHP sözdizimine birkaç [güzel uzantı |#Syntactic Sugar] ekler. +Ayrıca Latte, PHP sözdizimini birkaç [güzel uzantılar |#Sözdizimsel Şeker] ile tamamlar. -n:öznitelikler .[#toc-n-attributes] -=================================== +n:nitelikler +============ -Tek bir HTML öğesi üzerinde çalışan `{if} … {/if}` gibi her bir çift etiket [n:attribute |#n:attribute] notasyonunda yazılabilir. Örneğin, yukarıdaki örnekte yer alan `{foreach}` bu şekilde de yazılabilir: +Tek bir HTML öğesi üzerinde çalışan `{if} … {/if}` gibi tüm çift etiketler, n:nitelik biçiminde yeniden yazılabilir. Örneğin, giriş örneğindeki `{foreach}` de bu şekilde yazılabilir: ```latte -
                                                                                                                                +
                                                                                                                                • {$item|capitalize}
                                                                                                                                ``` -İşlevsellik daha sonra yazıldığı HTML öğesine karşılık gelir: +İşlevsellik daha sonra yerleştirildiği HTML öğesine uygulanır: ```latte -{var $items = ['I', '♥', 'Latte']} +{var $items = ['Ben', '♥', 'Latte']}

                                                                                                                                {$item}

                                                                                                                                ``` -Baskılar: +yazdırır: ```latte -

                                                                                                                                I

                                                                                                                                +

                                                                                                                                Ben

                                                                                                                                Latte

                                                                                                                                ``` -`inner-` önekini kullanarak davranışı değiştirebilir, böylece işlevselliğin yalnızca öğenin gövdesine uygulanmasını sağlayabiliriz: +`inner-` önekini kullanarak, davranışı yalnızca öğenin iç kısmına uygulanacak şekilde ayarlayabiliriz: ```latte
                                                                                                                                @@ -82,11 +80,11 @@ Baskılar:
                                                                                                                                ``` -Baskılar: +Yazdırılacak: ```latte
                                                                                                                                -

                                                                                                                                I

                                                                                                                                +

                                                                                                                                Ben



                                                                                                                                @@ -95,178 +93,184 @@ Baskılar:
                                                                                                                                ``` -Veya `tag-` önekini kullanarak işlevsellik yalnızca HTML etiketlerine uygulanır: +Veya `tag-` önekini kullanarak işlevselliği yalnızca HTML etiketlerinin kendisine uygularız: ```latte -

                                                                                                                                Title

                                                                                                                                +

                                                                                                                                Başlık

                                                                                                                                ``` -`$url` değişkeninin değerine bağlı olarak bu yazdırılacaktır: +Bu, `$url` değişkenine bağlı olarak yazdırılır: ```latte -// when $url is empty -

                                                                                                                                Title

                                                                                                                                +{* $url boş olduğunda *} +

                                                                                                                                Başlık

                                                                                                                                -// when $url equals 'https://nette.org' -

                                                                                                                                Title

                                                                                                                                +{* $url 'https://nette.org' içerdiğinde *} +

                                                                                                                                Başlık

                                                                                                                                ``` -Bununla birlikte, n:attributes sadece çift etiketler için bir kısayol değildir, bazı saf n:attribute'lar da vardır, örneğin kodlayıcının en iyi arkadaşı n: [class |tags#n:class]. +Ancak, n:nitelikler yalnızca çift etiketler için bir kısayol değildir. [n:href |application:creating-links#Presenter şablonunda] veya kodlayıcının çok kullanışlı yardımcısı [n:class |tags#n:class] gibi saf n:nitelikler de vardır. -Filtreler .[#toc-filters] -========================= +Filtreler +========= -[Standart filtrelerin |filters] özetine bakın. +[Standart filtreler |filters] özetine bakın. -Latte, boru işareti gösterimini kullanarak filtreleri çağırmaya izin verir (önceki boşluğa izin verilir): +Filtreler dikey çubuktan sonra yazılır (önünde bir boşluk olabilir): ```latte

                                                                                                                                {$heading|upper}

                                                                                                                                ``` -Filtreler zincirleme olabilir, bu durumda soldan sağa doğru sırayla uygulanırlar: +Filtreler zincirlenebilir ve ardından soldan sağa doğru sırayla uygulanır: ```latte

                                                                                                                                {$heading|lower|capitalize}

                                                                                                                                ``` -Parametreler filtre adından sonra iki nokta üst üste veya virgülle ayrılmış olarak konur: +Parametreler, filtre adından sonra iki nokta üst üste veya virgülle ayrılarak girilir: ```latte

                                                                                                                                {$heading|truncate:20,''}

                                                                                                                                ``` -İfade üzerinde filtreler uygulanabilir: +Filtreler bir ifadeye de uygulanabilir: ```latte {var $name = ($title|upper) . ($subtitle|lower)} ``` -Blokta: +Bir bloğa: ```latte

                                                                                                                                {block |lower}{$heading}{/block}

                                                                                                                                ``` -Veya doğrudan değer üzerinde (ile birlikte [`{=expr}` | https://latte.nette.org/tr/tags#printing] etiketi): +Veya doğrudan değere ([`{=expr}` |tags#Yazdırma] etiketiyle kombinasyon halinde): ```latte -

                                                                                                                                {=' Hello world '|trim}

                                                                                                                                +

                                                                                                                                {=' Merhaba dünya '|trim}

                                                                                                                                ``` -Yorumlar .[#toc-comments] -========================= +Dinamik HTML Etiketleri .{data-version:3.0.9} +============================================= -Yorumlar bu şekilde yazılır ve çıktıya girmez: +Latte, etiket adlarında esnekliğe ihtiyaç duyduğunuzda kullanışlı olan dinamik HTML etiketlerini destekler: ```latte -{* Bu Latte dilinde bir yorumdur *} +Başlık ``` -PHP yorumları etiketlerin içinde çalışır: +Yukarıdaki kod, örneğin `$level` değişkeninin değerine bağlı olarak `

                                                                                                                                Başlık

                                                                                                                                ` veya `

                                                                                                                                Başlık

                                                                                                                                ` oluşturabilir. Latte'deki dinamik HTML etiketleri her zaman çift olmalıdır. Alternatifleri [n:tag |tags#n:tag]'dır. -```latte -{include 'file.info', /* value: 123 */} -``` +Latte güvenli bir şablonlama sistemi olduğundan, sonuçtaki etiket adının geçerli olup olmadığını ve istenmeyen veya zararlı değerler içermediğini kontrol eder. Ayrıca, bitiş etiketinin adının her zaman başlangıç etiketinin adıyla aynı olmasını sağlar. -Sözdizimsel Şeker .[#toc-syntactic-sugar] -========================================= +Yorumlar +======== +Yorumlar bu şekilde yazılır ve çıktıya dahil edilmez: -Tırnak İşareti Olmayan Dizeler .[#toc-strings-without-quotation-marks] ----------------------------------------------------------------------- +```latte +{* bu Latte'de bir yorumdur *} +``` -Basit dizeler için tırnak işaretleri atlanabilir: +Etiketlerin içinde PHP yorumları çalışır: ```latte -as in PHP: {var $arr = ['hello', 'btn--default', '€']} - -abbreviated: {var $arr = [hello, btn--default, €]} +{include 'file.info', /* value: 123 */} ``` -Basit dizeler yalnızca harflerden, rakamlardan, alt çizgilerden, tire işaretlerinden ve noktalardan oluşan dizelerdir. Bir rakamla başlamamalı ve bir tire ile başlamamalı veya bitmemelidir. -Yalnızca büyük harflerden ve alt çizgilerden oluşmamalıdır, çünkü o zaman bir sabit olarak kabul edilir (örn. `PHP_VERSION`). -Ve `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor` anahtar kelimeleriyle çakışmamalıdır. + +Sözdizimsel Şeker +================= -Kısa Üçlü Operatör .[#toc-short-ternary-operator] -------------------------------------------------- +Tırnaksız Karakter Dizileri +--------------------------- -Üçlü işlecin üçüncü değeri boşsa, atlanabilir: +Basit karakter dizileri için tırnak işaretleri atlanabilir: ```latte -as in PHP: {$stock ? 'In stock' : ''} +PHP'deki gibi: {var $arr = ['merhaba', 'btn--default', '€']} -abbreviated: {$stock ? 'In stock'} +kısaltılmış: {var $arr = [merhaba, btn--default, €]} ``` +Basit karakter dizileri, yalnızca harfler, rakamlar, alt çizgiler, tireler ve noktalardan oluşanlardır. Rakamla başlayamazlar ve tire ile başlayamaz veya bitemezler. Yalnızca büyük harfler ve alt çizgilerden oluşamazlar, çünkü o zaman bir sabit olarak kabul edilirler (ör. `PHP_VERSION`). Ve anahtar kelimelerle çakışamazlar: `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor`. -Dizide Modern Anahtar Notasyonu .[#toc-modern-key-notation-in-the-array] ------------------------------------------------------------------------- -Dizi anahtarları, fonksiyonlar çağrılırken adlandırılmış parametrelere benzer şekilde yazılabilir: +Sabitler +-------- -```latte -as in PHP: {var $arr = ['one' => 'item 1', 'two' => 'item 2']} +Basit karakter dizilerinde tırnak işaretleri atlanabildiğinden, ayırt etmek için genel sabitleri başında eğik çizgi ile yazmanızı öneririz: -modern: {var $arr = [one: 'item 1', two: 'item 2']} +```latte +{if \PROJECT_ID === 1} ... {/if} ``` +Bu yazım PHP'nin kendisinde tamamen geçerlidir, eğik çizgi sabitin genel ad alanında olduğunu söyler. + -Filtreler .[#toc-filters] +Kısaltılmış Üçlü Operatör ------------------------- -Filtreler herhangi bir ifade için kullanılabilir, sadece bütünü parantez içine alın: +Üçlü operatörün üçüncü değeri boşsa, atlanabilir: ```latte -{var $content = ($text|truncate: 30|upper)} +PHP'deki gibi: {$stock ? 'Stokta' : ''} + +kısaltılmış: {$stock ? 'Stokta'} ``` -Operatör `in` .[#toc-operator-in] ---------------------------------- +Dizide Modern Anahtar Yazımı +---------------------------- -`in` operatörü `in_array()` fonksiyonunun yerine kullanılabilir. Karşılaştırma her zaman katıdır: +Dizideki anahtarlar, fonksiyon çağrılarındaki adlandırılmış parametrelere benzer şekilde yazılabilir: ```latte -{* gibi in_array($item, $items, true) *} -{if $item in $items} - ... -{/if} +PHP'deki gibi: {var $arr = ['one' => 'öğe 1', 'two' => 'öğe 2']} + +modern: {var $arr = [one: 'öğe 1', two: 'öğe 2']} ``` -.{data-version:2.9} -Tanımsız-Güvenli Operatör ile İsteğe Bağlı Zincirleme .[#toc-optional-chaining-with-undefined-safe-operator] ------------------------------------------------------------------------------------------------------------- +Filtreler +--------- -Undefined-safe işleci `??->` nullsafe işlecine benzer `?->`, ancak bir değişken, özellik veya dizin mevcut değilse hata oluşturmaz. +Filtreler herhangi bir ifade için kullanılabilir, bütünü parantez içine almak yeterlidir: ```latte -{$order??->id} +{var $content = ($text|truncate: 30|upper)} ``` -bu, `$order` tanımlandığında ve null olmadığında, `$order->id` 'un hesaplanacağını söylemenin bir yoludur, ancak `$order` null olduğunda veya mevcut olmadığında, yaptığımız şeyi durdurun ve sadece null döndürün. + +`in` Operatörü +-------------- + +`in` operatörü `in_array()` fonksiyonunun yerini alabilir. Karşılaştırma her zaman katıdır: ```latte -{$user??->address??->street} -// roughly means isset($user) && isset($user->address) ? $user->address->street : null +{* in_array($item, $items, true) benzeri *} +{if $item in $items} + ... +{/if} ``` -Tarihe Açılan Bir Pencere .[#toc-a-window-into-history] -------------------------------------------------------- +Tarihsel Pencere +---------------- + +Latte, tarihi boyunca birkaç yıl sonra PHP'nin kendisinde ortaya çıkan bir dizi sözdizimsel şekerleme ile geldi. Örneğin, Latte'de dizileri `array(1, 2, 3)` yerine `[1, 2, 3]` olarak yazmak veya nullsafe operatörü `$obj?->foo` kullanmak, PHP'nin kendisinde mümkün olmadan çok önce mümkündü. Latte ayrıca, PHP'deki bugünkü `...$arr` operatörünün eşdeğeri olan dizi açma operatörü `(expand) $arr`'ı da tanıttı. -Latte, tarihi boyunca, birkaç yıl sonra PHP'nin kendisinde ortaya çıkan bir dizi sözdizimsel şekerleme ile ortaya çıkmıştır. Örneğin, Latte'de dizileri şu şekilde yazmak mümkündü `[1, 2, 3]` yerine `array(1, 2, 3)` veya PHP'de mümkün olmadan çok önce nullsafe operatörünü `$obj?->foo` kullanabilir. Latte ayrıca PHP'nin bugünkü `...$arr` operatörüne eşdeğer olan `(expand) $arr` dizi genişletme operatörünü de tanıttı. +Değişkenin var olmaması durumunda hata vermeyen nullsafe operatörü `?->`'nun bir benzeri olan tanımsız-güvenli operatör `??->`, tarihsel nedenlerden dolayı ortaya çıktı ve bugün standart PHP operatörü `?->`'yu kullanmanızı öneririz. -Latte'deki PHP Sınırlamaları .[#toc-php-limitations-in-latte] -============================================================= +Latte'de PHP Kısıtlamaları +========================== -Latte'de sadece PHP ifadeleri yazılabilir. Yani, Latte'nin [etiketlerini |tags] sunduğu `if`, `foreach`, `switch`, `return`, `try`, `throw` ve diğerleri gibi sınıfları bildiremez veya [kontrol yapılarını |https://www.php.net/manual/en/language.control-structures.php] kullanamazsınız. -Ayrıca [nitelikler |https://www.php.net/manual/en/language.attributes.php], [geri |https://www.php.net/manual/en/language.operators.execution.php] işaretleri veya [sihirli sabitler |https://www.php.net/manual/en/language.constants.magic.php] de kullanamazsınız, çünkü bu mantıklı olmaz. -Hatta `unset`, `echo`, `include`, `require`, `exit`, `eval` bile kullanamazsınız, çünkü bunlar fonksiyon değil, özel PHP dili yapılarıdır ve dolayısıyla ifade değildirler. +Latte'de yalnızca PHP ifadeleri yazılabilir. Yani noktalı virgülle biten deyimler kullanılamaz. Sınıflar bildirilemez veya [kontrol yapıları |https://www.php.net/manual/en/language.control-structures.php] kullanılamaz, örneğin `if`, `foreach`, `switch`, `return`, `try`, `throw` ve diğerleri, bunların yerine Latte kendi [etiketler|tags]'ini sunar. Ayrıca [nitelikler |https://www.php.net/manual/en/language.attributes.php], [backtick'ler |https://www.php.net/manual/en/language.operators.execution.php] veya bazı [sihirli sabitler |https://www.php.net/manual/en/language.constants.magic.php] kullanılamaz. `unset`, `echo`, `include`, `require`, `exit`, `eval` de kullanılamaz, çünkü bunlar fonksiyonlar değil, özel PHP dil yapılarıdır ve dolayısıyla ifadeler değildir. Yorumlar yalnızca çok satırlı `/* ... */` desteklenir. -Ancak, şablon yazarının sorumluluğunda `{php ...}` etiketinde herhangi bir PHP kodunu kullanmanıza olanak tanıyan [RawPhpExtension |develop#RawPhpExtension] uzantısını etkinleştirerek bu sınırlamaları aşabilirsiniz. +Ancak bu kısıtlamalar, [RawPhpExtension |develop#RawPhpExtension] uzantısını etkinleştirerek aşılabilir, bu sayede `{php ...}` etiketinde şablon yazarının sorumluluğunda herhangi bir PHP kodu kullanılabilir. diff --git a/latte/tr/tags.texy b/latte/tr/tags.texy index d480ad396a..285a04546e 100644 --- a/latte/tr/tags.texy +++ b/latte/tr/tags.texy @@ -2,167 +2,173 @@ Latte Etiketleri **************** .[perex] -Tüm Latte yerleşik etiketlerinin özeti ve açıklaması. +Size standart olarak sunulan Latte şablonlama sisteminin tüm etiketlerinin özeti ve açıklaması. .[table-latte-tags language-latte] -|## Baskı -| `{$var}`, `{...}` veya `{=...}` | [öncelenmiş bir değişken veya ifade yazdırır |#printing] -| `{$var\|filter}` | [filtrelerle yazdırır |#filters] -| `{l}` veya `{r}` | `{` or `}` karakterini yazdırır +|## Yazdırma +| `{$var}`, `{...}` veya `{=...}` | [kaçış işlemine tabi tutulmuş değişkeni veya ifadeyi yazdırır |#Yazdırma] +| `{$var\|filter}` | [filtreler kullanarak yazdırır |#Filtreler] +| `{l}` veya `{r}` | `{` veya `}` karakterini yazdırır .[table-latte-tags language-latte] |## Koşullar -| `{if}`... `{elseif}`... `{else}`... `{/if}` | [eğer koşulu |#if-elseif-else] -| `{ifset}`... `{elseifset}`... `{/ifset}` | [koşul ifset |#ifset-elseifset] -| `{ifchanged}`... `{/ifchanged}` | [bir değişiklik olup olmadığını test edin|#ifchanged] -| `{switch}` `{case}` `{default}` `{/switch}` | [koşul anahtarı |#switch-case-default] +| `{if}` … `{elseif}` … `{else}` … `{/if}` | [if koşulu |#if elseif else] +| `{ifset}` … `{elseifset}` … `{/ifset}` | [ifset koşulu |#ifset elseifset] +| `{ifchanged}` … `{/ifchanged}` | [değişiklik olup olmadığını test et |#ifchanged] +| `{switch}` `{case}` `{default}` `{/switch}` | [switch koşulu |#switch case default] +| `n:else` | [koşullar için alternatif içerik |#n:else] .[table-latte-tags language-latte] |## Döngüler -| `{foreach}`... `{/foreach}` | [foreach |#foreach] -| `{for}`... `{/for}` | [için |#for] -| `{while}`... `{/while}` | [while |#while] -| `{continueIf $cond}` | [sonraki yinelemeye devam et|#continueif-skipif-breakif] -| `{skipIf $cond}` | [geçerli döngü yinelemesini atla |#continueif-skipif-breakif] -| `{breakIf $cond}` | [döngüyü kırar |#continueif-skipif-breakif] -| `{exitIf $cond}` | [erken çıkış |#exitif] -| `{first}`... `{/first}` | [bu ilk y ineleme mi?|#first-last-sep] -| `{last}`... `{/last}` | [bu son yineleme mi?|#first-last-sep] -| `{sep}`... `{/sep}` | [bir sonraki yineleme takip edecek mi?|#first-last-sep] -| `{iterateWhile}`... `{/iterateWhile}` | [yapılandırılmış foreach |#iterateWhile] -| `$iterator` | [foreach döngüsü içinde özel değişken |#$iterator] +| `{foreach}` … `{/foreach}` | [#foreach] +| `{for}` … `{/for}` | [#for] +| `{while}` … `{/while}` | [#while] +| `{continueIf $cond}` | [sonraki iterasyonla devam et |#continueIf skipIf breakIf] +| `{skipIf $cond}` | [iterasyonu atla |#continueIf skipIf breakIf] +| `{breakIf $cond}` | [döngüyü kır |#continueIf skipIf breakIf] +| `{exitIf $cond}` | [erken çıkış |#exitIf] +| `{first}` … `{/first}` | [ilk geçiş mi? |#first last sep] +| `{last}` … `{/last}` | [son geçiş mi? |#first last sep] +| `{sep}` … `{/sep}` | [başka bir geçiş takip edecek mi? |#first last sep] +| `{iterateWhile}` … `{/iterateWhile}` | [yapılandırılmış foreach |#iterateWhile] +| `$iterator` | [foreach içindeki özel değişken |#iterator] .[table-latte-tags language-latte] -|## Diğer Şablonları Dahil Etme -| `{include 'file.latte'}` | [başka bir dosyadan şablon içerir |#include] -| `{sandbox 'file.latte'}` | [sandbox modunda bir şablon içerir |#sandbox] +|## Diğer Şablonları Ekleme +| `{include 'file.latte'}` | [başka bir dosyadan şablonu yükler |#include] +| `{sandbox 'file.latte'}` | [şablonu sandbox modunda yükler |#sandbox] .[table-latte-tags language-latte] -|## Bloklar, düzenler, şablon kalıtımı -| `{block}` | [anonim blok |#block] -| `{block blockname}` | [blok tanımı |template-inheritance#blocks] -| `{define blockname}` | [gelecekteki kullanım için blok tanımı |template-inheritance#definitions] -| `{include blockname}` | [blok yazdırır |template-inheritance#printing-blocks] -| `{include blockname from 'file.latte'}` | [dosyadan bir blok yazdırır |template-inheritance#printing-blocks] -| `{import 'file.latte'}` | [blokları başka bir şablondan yükler |template-inheritance#horizontal-reuse] -| `{layout 'file.latte'}` / `{extends}` | [bir düzen dosyası belirtir |template-inheritance#layout-inheritance] -| `{embed}`... `{/embed}` | [şablonu veya bloğu yükler ve blokların üzerine yazmanıza izin verir |template-inheritance#unit-inheritance] -| `{ifset blockname}`... `{/ifset}` | [blok tanımlanmışsa koşul |template-inheritance#checking-block-existence] +|## Bloklar, Düzenler, Şablon Kalıtımı +| `{block}` | [anonim blok |#block] +| `{block blockname}` | [bir blok tanımlar |template-inheritance#Bloklar] +| `{define blockname}` | [daha sonra kullanmak üzere bir blok tanımlar |template-inheritance#Tanımlar] +| `{include blockname}` | [bloğu oluşturma |template-inheritance#Blokları İşleme] +| `{include blockname from 'file.latte'}` | [dosyadan bloğu oluşturur |template-inheritance#Blokları İşleme] +| `{import 'file.latte'}` | [şablondan blokları yükler |template-inheritance#Yatay Yeniden Kullanım] +| `{layout 'file.latte'}` / `{extends}` | [düzen dosyasını belirtir |template-inheritance#Düzen Kalıtımı] +| `{embed}` … `{/embed}` | [şablonu veya bloğu yükler ve blokların üzerine yazılmasına izin verir |template-inheritance#Birim Kalıtımı] +| `{ifset blockname}` … `{/ifset}` | [bloğun var olup olmadığı koşulu |template-inheritance#Blok Varlığını Kontrol Etme] .[table-latte-tags language-latte] -|## İstisna işleme -| `{try}`... `{else}`... `{/try}` | [istisnaları yakalama |#try] -| `{rollback}` | [deneme bloğunu atar |#rollback] +|## İstisna Yönetimi +| `{try}` … `{else}` … `{/try}` | [istisnaları yakalama |#try] +| `{rollback}` | [try bloğunu atma |#rollback] .[table-latte-tags language-latte] |## Değişkenler -| `{var $foo = value}` | [değişken oluşturma |#var-default] -| `{default $foo = value}` | [değişken bildirilmediğinde varsayılan değer |#var-default] -| `{parameters}` | [değişkenleri bildirir, varsayılan değerleri yazar|#parameters] -| `{capture}`... `{/capture}` | [bir bölümü bir değişkene kaydeder|#capture] +| `{var $foo = value}` | [değişken oluşturur |#var default] +| `{default $foo = value}` | [eğer yoksa değişken oluşturur |#var default] +| `{parameters}` | [değişkenleri, türleri ve varsayılan değerleri bildirir |#parameters] +| `{capture}` … `{/capture}` | [bloğu değişkene yakalar |#capture] .[table-latte-tags language-latte] -|## Türleri -| `{varType}` | [değişkenin türünü bildirir |type-system#varType] -| `{varPrint}` | [değişken türlerini önerir |type-system#varPrint] -| `{templateType}` | [sınıf kullanarak değişken türlerini bildirir |type-system#templateType] -| `{templatePrint}` | [özellikli sınıf oluşturur |type-system#templatePrint] +|## Tipler +| `{varType}` | [değişken tipini bildirir |type-system#varType] +| `{varPrint}` | [değişken tiplerini önerir |type-system#varPrint] +| `{templateType}` | [sınıfa göre değişken tiplerini bildirir |type-system#templateType] +| `{templatePrint}` | [değişken tipleriyle sınıf önerir |type-system#templatePrint] .[table-latte-tags language-latte] -|## Çeviri -| `{_string}` | [çevrilmiş olarak yazdırır |#Translation] -| `{translate}`... `{/translate}` | [içeriği çevirir |#Translation] +|## Çeviriler +| `{_...}` | [çeviriyi yazdırır |#Çeviriler] +| `{translate}` … `{/translate}` | [içeriği çevirir |#Çeviriler] .[table-latte-tags language-latte] -|## Diğerleri -| `{contentType}` | [kaçış modunu değiştirir ve HTTP başlığı gönderir |#contenttype] -| `{debugbreak}` | [koda kesme noktası koyar |#debugbreak] -| `{do}` | [bir ifadeyi yazdırmadan değerlendirir |#do] -| `{dump}` | [değişkenleri Tracy Bar'a döker |#dump] -| `{spaceless}`... `{/spaceless}` | [gereksiz boşlukları kaldırır |#spaceless] -| `{syntax}` | [çalışma zamanında sözdizimini değiştirir |#syntax] -| `{trace}` | [yığın izini gösterir |#trace] +|## Diğer +| `{contentType}` | [kaçış işlemini değiştirir ve HTTP başlığını gönderir |#contentType] +| `{debugbreak}` | [koda bir kesme noktası yerleştirir |#debugbreak] +| `{do}` | [kodu yürütür, ancak hiçbir şey yazdırmaz |#do] +| `{dump}` | [değişkenleri Tracy Bar'a döker |#dump] +| `{php}` | [herhangi bir PHP kodunu yürütür |#php] +| `{spaceless}` … `{/spaceless}` | [gereksiz boşlukları kaldırır |#spaceless] +| `{syntax}` | [çalışma zamanında sözdizimi değişikliği |#syntax] +| `{trace}` | [yığın izini gösterir |#trace] .[table-latte-tags language-latte] -|## HTML etiketi yardımcıları -| `n:class` | [akıllı sınıf niteliği |#n:class] -| `n:attr` | [akıllı HTML nitelikleri |#n:attr] -| `n:tag` | [HTML öğesinin dinamik adı |#n:tag] -| `n:ifcontent` | [Boş HTML etiketini atlayın |#n:ifcontent] +|## HTML Kodlayıcı Yardımcıları +| `n:class` | [dinamik HTML class niteliği yazımı |#n:class] +| `n:attr` | [herhangi bir HTML niteliğinin dinamik yazımı |#n:attr] +| `n:tag` | [HTML öğe adının dinamik yazımı |#n:tag] +| `n:ifcontent` | [boş HTML etiketini atlar |#n:ifcontent] .[table-latte-tags language-latte] -|## Yalnızca Nette Framework'te kullanılabilir -| `n:href` | [`` HTML öğelerindeki bağlantı |application:creating-links#In the Presenter Template] -| `{link}` | [bir bağlantı yazdırır |application:creating-links#In the Presenter Template] -| `{plink}` | [sunum yapan kişiye bir bağlantı yazdırır |application:creating-links#In the Presenter Template] -| `{control}` | [bir bileşen yazdırır |application:components#Rendering] -| `{snippet}`... `{/snippet}` | [AJAX ile gönderilebilecek bir şablon parçacığı |application:ajax#tag-snippet] -| `{snippetArea}` | snippets zarfı -| `{cache}`... `{/cache}` | [bir şablon bölümünü önbelleğe alır|caching:#caching-in-latte] +|## Yalnızca Nette Framework'te Mevcut +| `n:href` | [`` HTML öğelerinde kullanılan bağlantı |application:creating-links#Presenter şablonunda] +| `{link}` | [bağlantıyı yazdırır |application:creating-links#Presenter şablonunda] +| `{plink}` | [presenter'a bağlantıyı yazdırır |application:creating-links#Presenter şablonunda] +| `{control}` | [bileşeni oluşturur |application:components#Oluşturma] +| `{snippet}` … `{/snippet}` | [AJAX ile gönderilebilen snippet |application:ajax#Latte de Snippet ler] +| `{snippetArea}` | [snippet'ler için sarmalayıcı |application:ajax#Snippet Alanları] +| `{cache}` … `{/cache}` | [şablonun bir bölümünü önbelleğe alır |caching:#Latte de Önbelleğe Alma] .[table-latte-tags language-latte] -|## Sadece Nette Forms ile kullanılabilir -| `{form}`... `{/form}` | [bir form öğesi yazdırır |forms:rendering#form] -| `{label}`... `{/label}` | [bir form giriş etiketi yazdırır |forms:rendering#label-input] -| `{input}` | [bir form giriş öğesi yazdırır |forms:rendering#label-input] -| `{inputError}` | [form giriş öğesi için hata mesajı yazdırır |forms:rendering#inputError] -| `n:name` | [bir HTML giriş öğesini etkinleştirir |forms:rendering#n:name] -| `{formPrint}` | [Latte form planını oluşturur |forms:rendering#formPrint] -| `{formPrintClass}` | [form verileri için PHP sınıfını yazdırır |forms:in-presenter#mapping-to-classes] -| `{formContext}`... `{/formContext}` | [kısmi form oluşturma |forms:rendering#special-cases] +|## Yalnızca Nette Forms ile Mevcut +| `{form}` … `{/form}` | [form etiketlerini oluşturur |forms:rendering#form] +| `{label}` … `{/label}` | [form öğesinin etiketini oluşturur |forms:rendering#label input] +| `{input}` | [form öğesini oluşturur |forms:rendering#label input] +| `{inputError}` | [form öğesinin hata mesajını yazdırır |forms:rendering#inputError] +| `n:name` | [form öğesini canlandırır |forms:rendering#n:name] +| `{formContainer}` … `{/formContainer}` | [form konteynerini çizme |forms:rendering#Özel Durumlar] + +.[table-latte-tags language-latte] +|## Sadece Nette Assets ile kullanılabilir +| `{asset}` | [bir varlığı HTML öğesi veya URL olarak işler |assets:#asset] +| `{preload}` | [performans optimizasyonu için ön yükleme ipuçları oluşturur |assets:#preload] +| `n:asset` | [HTML öğelerine varlık nitelikleri ekler |assets:#n:asset] -Baskı .[#toc-printing] -====================== +Yazdırma +======== `{$var}` `{...}` `{=...}` ------------------------- -Latte, herhangi bir ifadeyi çıktıya yazdırmak için `{=...}` etiketini kullanır. Eğer ifade bir değişken veya fonksiyon çağrısı ile başlıyorsa, eşittir işareti yazmaya gerek yoktur. Bu da pratikte neredeyse hiçbir zaman yazılmasına gerek olmadığı anlamına gelir: +Latte'de herhangi bir ifadeyi çıktıya yazdırmak için `{=...}` etiketi kullanılır. Latte rahatlığınızı önemser, bu nedenle ifade bir değişkenle veya fonksiyon çağrısıyla başlıyorsa, eşittir işareti yazmaya gerek yoktur. Bu da pratikte neredeyse hiç yazmaya gerek olmadığı anlamına gelir: ```latte -Name: {$name} {$surname}
                                                                                                                                -Age: {date('Y') - $birth}
                                                                                                                                +İsim: {$name} {$surname}
                                                                                                                                +Yaş: {date('Y') - $birth}
                                                                                                                                ``` -PHP'den bildiğiniz her şeyi bir ifade olarak yazabilirsiniz. Sadece yeni bir dil öğrenmek zorunda değilsiniz. Örneğin: +İfade olarak PHP'den bildiğiniz her şeyi yazabilirsiniz. Yeni bir dil öğrenmenize gerek yok. Örneğin: ```latte {='0' . ($num ?? $num * 3) . ', ' . PHP_VERSION} ``` -Lütfen önceki örnekte herhangi bir anlam aramayın, ancak orada bir anlam bulursanız bize yazın :-) +Lütfen önceki örnekte herhangi bir anlam aramayın, ancak bulursanız bize yazın :-) -Çıkıştan Kaçış .[#toc-escaping-output] --------------------------------------- +Çıktı Kaçış İşlemi +------------------ -Bir şablon sisteminin en önemli görevi nedir? Güvenlik açıklarını önlemek. Ve çıktıya bir şey yazdırdığınızda Latte tam olarak bunu yapar. Her şeyi otomatik olarak kaçar: +Bir şablonlama sisteminin en önemli görevi nedir? Güvenlik açıklarını önlemek. Ve Latte tam olarak bunu bir şey yazdırdığınızda her zaman yapar. Otomatik olarak kaçış işlemine tabi tutar: ```latte -

                                                                                                                                {='one < two'}

                                                                                                                                {* prints: '

                                                                                                                                one < two

                                                                                                                                ' *} +

                                                                                                                                {='one < two'}

                                                                                                                                {* yazdırır: '

                                                                                                                                one < two

                                                                                                                                ' *} ``` -Kesin olmak gerekirse, Latte bağlama duyarlı kaçış kullanır, bu o kadar önemli ve benzersiz bir özelliktir ki [ona ayrı bir bölüm |safety-first#context-aware-escaping] ayırdık. +Kesin olmak gerekirse, Latte bağlama duyarlı kaçış kullanır, bu o kadar önemli ve benzersiz bir şeydir ki, buna [ayrı bölüm |safety-first#Bağlama Duyarlı Kaçış] ayırdık. -Peki ya güvenilir bir kaynaktan HTML kodlu içerik yazdırıyorsanız? O zaman kaçış özelliğini kolayca kapatabilirsiniz: +Peki ya güvenilir bir kaynaktan HTML olarak kodlanmış içeriği yazdırıyorsanız? O zaman kaçış işlemini kolayca kapatabilirsiniz: ```latte {$trustedHtmlString|noescape} ``` .[warning] -`noescape` filtresinin yanlış kullanımı bir XSS güvenlik açığına yol açabilir! Ne yaptığınızdan ve yazdırdığınız dizenin güvenilir bir kaynaktan geldiğinden **kesinlikle emin** değilseniz asla kullanmayın. +`noescape` filtresinin yanlış kullanımı XSS güvenlik açığına yol açabilir! Ne yaptığınızdan ve yazdırılan karakter dizisinin güvenilir bir kaynaktan geldiğinden **tamamen emin** değilseniz asla kullanmayın. -JavaScript'te Yazdırma .[#toc-printing-in-javascript] ------------------------------------------------------ +JavaScript'te Yazdırma +---------------------- -Bağlama duyarlı kaçış sayesinde, JavaScript içindeki değişkenleri yazdırmak son derece kolaydır ve Latte bunları düzgün bir şekilde kaçacaktır. +Bağlama duyarlı kaçış sayesinde, JavaScript içinde değişkenleri yazdırmak harika bir şekilde kolaydır ve doğru kaçış işlemini Latte halleder. -Değişken bir dize olmak zorunda değildir, herhangi bir veri türü desteklenir ve daha sonra JSON olarak kodlanır: +Değişken yalnızca bir karakter dizisi olmak zorunda değildir, desteklenen herhangi bir veri türü daha sonra JSON olarak kodlanır: ```latte {var $foo = ['hello', true, 1]} @@ -171,7 +177,7 @@ Değişken bir dize olmak zorunda değildir, herhangi bir veri türü destekleni ``` -Üretir: +Oluşturur: ```latte ``` -Bu aynı zamanda **değişkeni tırnak içine almayın** nedenidir: Latte bunları dizelerin etrafına ekler. Ve bir dize değişkenini başka bir dizenin içine koymak istiyorsanız, bunları birleştirmeniz yeterlidir: +Bu aynı zamanda değişkenin etrafına **tırnak işareti yazılmamasının** nedenidir: Latte bunları karakter dizileri için kendisi ekler. Ve eğer bir karakter dizisi değişkenini başka bir karakter dizisine eklemek isterseniz, bunları basitçe birleştirin: ```latte ``` -Filtreler .[#toc-filters] -------------------------- +Filtreler +--------- -Yazdırılan ifade [filtrelerle |syntax#filters] değiştirilebilir. Örneğin, bu örnek dizeyi büyük harfe dönüştürür ve en fazla 30 karakter olacak şekilde kısaltır: +Yazdırılan ifade [filtre |syntax#Filtreler] ile değiştirilebilir. Örneğin, bir karakter dizisini büyük harfe dönüştürür ve maksimum 30 karaktere kısaltırız: ```latte {$string|upper|truncate:30} ``` -Ayrıca, bir ifadenin bölümlerine aşağıdaki gibi filtreler uygulayabilirsiniz: +Filtreleri ifadenin kısmi bölümlerine de bu şekilde uygulayabilirsiniz: ```latte {$left . ($middle|upper) . $right} ``` -Koşullar .[#toc-conditions] -=========================== +Koşullar +======== `{if}` `{elseif}` `{else}` -------------------------- -Koşullar PHP'deki benzerleriyle aynı şekilde davranır. PHP'den bildiğiniz ifadeleri kullanabilirsiniz, yeni bir dil öğrenmek zorunda değilsiniz. +Koşullar, PHP'deki karşılıklarıyla aynı şekilde davranır. İçlerinde PHP'den bildiğiniz aynı ifadeleri de kullanabilirsiniz, yeni bir dil öğrenmenize gerek yoktur. ```latte {if $product->inStock > Stock::Minimum} - In stock + Stokta {elseif $product->isOnWay()} - On the way + Yolda {else} - Not available + Mevcut değil {/if} ``` -Herhangi bir çift etiketi gibi, bir `{if} ... {/ if}` çifti de örneğin [n:attribute |syntax#n:attributes] şeklinde yazılabilir: +Her çift etiket gibi, `{if} ... {/if}` çifti de [n:niteliği |syntax#n:nitelikler] biçiminde yazılabilir, örneğin: ```latte -

                                                                                                                                In stock {$count} items

                                                                                                                                +

                                                                                                                                Stokta {$count} adet

                                                                                                                                ``` -n:attributes öğesine `tag-` önekini ekleyebileceğinizi biliyor musunuz? Bu durumda koşul yalnızca HTML etiketlerini etkileyecek ve aralarındaki içerik her zaman yazdırılacaktır: +n:niteliklere `tag-` önekini ekleyebileceğinizi biliyor muydunuz? O zaman koşul yalnızca HTML etiketlerinin yazdırılmasına uygulanır ve aralarındaki içerik her zaman yazdırılır: ```latte -
                                                                                                                                Hello +Merhaba -{* prints 'Hello' when $clickable is falsey *} -{* prints 'Hello' when $clickable is truthy *} +{* $clickable yanlış olduğunda 'Merhaba' yazdırır *} +{* $clickable doğru olduğunda 'Merhaba' yazdırır *} ``` -Güzel. +Harika. + + +`n:else` .{data-version:3.0.11} +------------------------------- + +Eğer `{if} ... {/if}` koşulunu [n:niteliği |syntax#n:nitelikler] biçiminde yazarsanız, `n:else` kullanarak alternatif bir dal belirtme seçeneğiniz de vardır: + +```latte +Stokta {$count} adet + +mevcut değil +``` + +`n:else` niteliği ayrıca [`n:ifset` |#ifset elseifset], [`n:foreach` |#foreach], [`n:try` |#try], [#`n:ifcontent`] ve [`n:ifchanged` |#ifchanged] ile çift olarak da kullanılabilir. `{/if $cond}` ------------- -`{if}` koşulundaki ifadenin end etiketinde de belirtilebilmesi sizi şaşırtabilir. Bu, etiket açıldığında koşulun değerini henüz bilmediğimiz durumlarda kullanışlıdır. Buna ertelenmiş karar diyelim. +Belki şaşıracaksınız ama `{if}` koşulundaki ifade bitiş etiketinde de belirtilebilir. Bu, koşulu açarken değerini henüz bilmediğimiz durumlarda kullanışlıdır. Buna ertelenmiş karar diyelim. -Örneğin, veritabanından kayıtları içeren bir tabloyu listelemeye başlıyoruz ve ancak raporu tamamladıktan sonra veritabanında kayıt olmadığını fark ediyoruz. Bu yüzden `{/if}` son etiketine koşul koyarız ve eğer kayıt yoksa hiçbiri yazdırılmaz: +Örneğin, veritabanından kayıtları içeren bir tabloyu yazdırmaya başlarız ve yalnızca yazdırmayı bitirdikten sonra veritabanında hiç kayıt olmadığını fark ederiz. O zaman `{/if}` bitiş etiketine bunun için bir koşul koyarız ve eğer hiç kayıt yoksa, bunların hiçbiri yazdırılmaz: ```latte {if} -

                                                                                                                                Printing rows from the database

                                                                                                                                +

                                                                                                                                Veritabanı satırlarının listesi

                                                                                                                                {foreach $resultSet as $row} @@ -266,28 +286,28 @@ Güzel. Kullanışlı, değil mi? -Ertelenmiş koşulda `{else}` adresini de kullanabilirsiniz, ancak `{elseif}` adresini kullanamazsınız. +Ertelenmiş koşulda `{else}` de kullanılabilir, ancak `{elseif}` kullanılamaz. `{ifset}` `{elseifset}` ----------------------- .[note] -Ayrıca bakınız [`{ifset block}` |template-inheritance#checking-block-existence] +Ayrıca bkz. [`{ifset block}` |template-inheritance#Blok Varlığını Kontrol Etme] -Bir değişkenin (veya birden fazla değişkenin) var olup olmadığını ve boş olmayan bir değere sahip olup olmadığını belirlemek için `{ifset $var}` koşulunu kullanın. Aslında PHP'deki `if (isset($var))` ile aynıdır. Herhangi bir çift etiketi gibi, bu da [n:attribute |syntax#n:attributes] şeklinde yazılabilir, bu yüzden örnek olarak gösterelim: +`{ifset $var}` koşuluyla, bir değişkenin (veya birden çok değişkenin) var olup olmadığını ve *null* olmayan bir değere sahip olup olmadığını kontrol ederiz. Aslında bu, PHP'deki `if (isset($var))` ile aynıdır. Her çift etiket gibi, [n:niteliği |syntax#n:nitelikler] biçiminde de yazılabilir, o zaman bunu bir örnek olarak gösterelim: ```latte - + ``` -`{ifchanged}` .{data-version:2.9} ---------------------------------- +`{ifchanged}` +------------- -`{ifchanged}` Döngüdeki (foreach, for veya while) son yinelemeden bu yana bir değişkenin değerinin değişip değişmediğini kontrol eder. +`{ifchanged}`, bir değişkenin değerinin döngüdeki (foreach, for veya while) son iterasyondan bu yana değişip değişmediğini kontrol eder. -Etikette bir veya daha fazla değişken belirtirsek, bunlardan herhangi birinin değişip değişmediğini kontrol eder ve içeriği buna göre yazdırır. Örneğin, aşağıdaki örnek isimleri listelerken her değiştiğinde bir ismin ilk harfini başlık olarak yazdırır: +Etikette bir veya daha fazla değişken belirtirsek, bunlardan herhangi birinin değişip değişmediğini kontrol eder ve buna göre içeriği yazdırır. Örneğin, aşağıdaki örnek, isimleri yazdırırken her değiştiğinde ismin ilk harfini başlık olarak yazdırır: ```latte {foreach ($names|sort) as $name} @@ -297,7 +317,7 @@ Etikette bir veya daha fazla değişken belirtirsek, bunlardan herhangi birinin {/foreach} ``` -Ancak, herhangi bir argüman verilmezse, işlenen içeriğin kendisi önceki durumuna göre kontrol edilecektir. Bu, önceki örnekte etiketteki argümanı güvenli bir şekilde atlayabileceğimiz anlamına gelir. Ve tabii ki [n:attribute |syntax#n:attributes] da kullanabiliriz: +Ancak, hiçbir argüman belirtmezsek, oluşturulan içerik önceki durumuyla karşılaştırılır. Bu, önceki örnekte etiketteki argümanı tamamen atlayabileceğimiz anlamına gelir. Ve tabii ki [n:niteliğini |syntax#n:nitelikler] de kullanabiliriz: ```latte {foreach ($names|sort) as $name} @@ -307,49 +327,49 @@ Ancak, herhangi bir argüman verilmezse, işlenen içeriğin kendisi önceki dur {/foreach} ``` -Ayrıca `{ifchanged}` içine bir `{else}` cümlesi de ekleyebilirsiniz. +`{ifchanged}` içinde `{else}` ifadesi de belirtilebilir. `{switch}` `{case}` `{default}` ------------------------------- -Değeri birden fazla seçenekle karşılaştırır. Bu, PHP'den bildiğiniz `switch` yapısına benzer. Ancak Latte bunu geliştirir: +Bir değeri birden çok seçenekle karşılaştırır. Bu, PHP'den bildiğiniz `switch` koşullu ifadesine benzer. Ancak Latte bunu geliştirir: -- katı karşılaştırma kullanır (`===`) -- ihtiyaç duymaz `break` +- katı karşılaştırma (`===`) kullanır +- `break`'e ihtiyaç duymaz -Yani PHP 8.0 ile birlikte gelen `match` yapısının tam karşılığıdır. +Yani bu, PHP 8.0 ile gelen `match` yapısının tam eşdeğeridir. ```latte {switch $transport} {case train} - By train + Trenle {case plane} - By plane + Uçakla {default} - Differently + Diğer {/switch} ``` -.{data-version:2.9} -Madde `{case}` virgülle ayrılmış birden fazla değer içerebilir: + +`{case}` ifadesi virgülle ayrılmış birden çok değer içerebilir: ```latte {switch $status} -{case $status::New}new item -{case $status::Sold, $status::Unknown}not available +{case $status::New}yeni öğe +{case $status::Sold, $status::Unknown}mevcut değil {/switch} ``` -Döngüler .[#toc-loops] -====================== +Döngüler +======== -Latte'de PHP'den bildiğiniz tüm döngüler mevcuttur: foreach, for ve while. +Latte'de PHP'den bildiğiniz tüm döngüleri bulacaksınız: foreach, for ve while. `{foreach}` ----------- -Döngüyü PHP'deki ile tamamen aynı şekilde yazarsınız: +Döngüyü tıpkı PHP'deki gibi yazarız: ```latte {foreach $langs as $code => $lang} @@ -357,11 +377,11 @@ Döngüyü PHP'deki ile tamamen aynı şekilde yazarsınız: {/foreach} ``` -Buna ek olarak, şimdi bahsedeceğimiz bazı kullanışlı ince ayarlar var. +Ayrıca, şimdi bahsedeceğimiz birkaç kullanışlı özelliği vardır. -Örneğin, Latte oluşturulan değişkenlerin yanlışlıkla aynı isimli global değişkenlerin üzerine yazılmadığını kontrol eder. Bu, `$lang` adresinin sayfanın geçerli dili olduğunu varsaydığınızda ve `foreach $langs as $lang` adresinin bu değişkenin üzerine yazıldığını fark etmediğinizde sizi kurtaracaktır. +Örneğin Latte, oluşturulan değişkenlerin yanlışlıkla aynı addaki genel değişkenlerin üzerine yazıp yazmadığını kontrol eder. Bu, `$lang`'da sayfanın geçerli dilinin olduğunu varsaydığınız ve `foreach $langs as $lang`'ın bu değişkeni üzerine yazdığını fark etmediğiniz durumları kurtarır. -foreach döngüsü de [n:attribute |syntax#n:attributes] ile çok zarif ve ekonomik bir şekilde yazılabilir: +Foreach döngüsü ayrıca [n:niteliği |syntax#n:nitelikler] kullanılarak çok zarif ve özlü bir şekilde yazılabilir: ```latte
                                                                                                                                  @@ -369,7 +389,7 @@ foreach döngüsü de [n:attribute |syntax#n:attributes] ile çok zarif ve ekono
                                                                                                                                ``` -`inner-` ön ekini n:attributes öğesinin önüne ekleyebileceğinizi biliyor muydunuz? Artık sadece elemanın iç kısmı döngüde tekrarlanacaktır: +n:niteliklere `inner-` önekini ekleyebileceğinizi biliyor muydunuz? O zaman döngüde yalnızca öğenin içi tekrarlanır: ```latte
                                                                                                                                @@ -378,7 +398,7 @@ foreach döngüsü de [n:attribute |syntax#n:attributes] ile çok zarif ve ekono
                                                                                                                                ``` -Yani şöyle bir şey yazdırıyor: +Yani şunun gibi bir şey yazdırılır: ```latte
                                                                                                                                @@ -390,17 +410,17 @@ Yani şöyle bir şey yazdırıyor: ``` -`{else}` .{data-version:2.9}{toc: foreach-else} ------------------------------------------------ +`{else}` .{toc: foreach-else} +----------------------------- -`foreach` döngüsü, verilen dizi boşsa metni görüntülenen isteğe bağlı bir `{else}` cümlesi alabilir: +`foreach` döngüsü içinde, döngü boşsa içeriği görüntülenecek olan `{else}` ifadesini belirtebilirsiniz: ```latte
                                                                                                                                  {foreach $people as $person}
                                                                                                                                • {$person->name}
                                                                                                                                • {else} -
                                                                                                                                • Sorry, no users in this list
                                                                                                                                • +
                                                                                                                                • Üzgünüz, bu listede hiç kullanıcı yok
                                                                                                                                • {/foreach}
                                                                                                                                ``` @@ -409,17 +429,17 @@ Yani şöyle bir şey yazdırıyor: `$iterator` ----------- -`foreach` döngüsü içinde `$iterator` değişkeni başlatılır. Mevcut döngü hakkında önemli bilgiler tutar. +`foreach` döngüsü içinde Latte, devam eden döngü hakkında yararlı bilgiler öğrenmemizi sağlayan `$iterator` değişkenini oluşturur: -- `$iterator->first` - bu ilk yineleme mi? -- `$iterator->last` - bu son yineleme mi? -- `$iterator->counter` - yineleme sayacı, 1'den başlar -- `$iterator->counter0` - yineleme sayacı, 0'dan başlar .{data-version:2.9} -- `$iterator->odd` - bu yineleme tek mi? -- `$iterator->even` - bu yineleme çift mi? -- `$iterator->parent` - mevcut olanı çevreleyen yineleyici .{data-version:2.9} -- `$iterator->nextValue` - döngüdeki bir sonraki öğe -- `$iterator->nextKey` - döngüdeki bir sonraki öğenin anahtarı +- `$iterator->first` - döngüden ilk kez mi geçiliyor? +- `$iterator->last` - bu son geçiş mi? +- `$iterator->counter` - birden başlayarak kaçıncı geçiş? +- `$iterator->counter0` - sıfırdan başlayarak kaçıncı geçiş? +- `$iterator->odd` - bu tek sayılı bir geçiş mi? +- `$iterator->even` - bu çift sayılı bir geçiş mi? +- `$iterator->parent` - geçerli olanı çevreleyen yineleyici +- `$iterator->nextValue` - döngüdeki sonraki öğe +- `$iterator->nextKey` - sonraki öğenin anahtarı ```latte @@ -435,20 +455,19 @@ Yani şöyle bir şey yazdırıyor: {/foreach} ``` -Latte akıllıdır ve `$iterator->last` yalnızca diziler için değil, aynı zamanda döngü öğe sayısının önceden bilinmediği genel bir yineleyici üzerinde çalıştığında da çalışır. +Latte akıllıdır ve `$iterator->last` yalnızca dizilerde değil, döngü öğe sayısının önceden bilinmediği genel bir yineleyici üzerinde çalıştığında da çalışır. `{first}` `{last}` `{sep}` -------------------------- -Bu etiketler `{foreach}` döngüsü içinde kullanılabilir. İlk geçiş için `{first}` içeriği işlenir. -`{last}` içeriği ... tahmin edebilir misiniz? Evet, son geçiş için. Bunlar aslında `{if $iterator->first}` ve `{if $iterator->last}` için kısayollardır. +Bu etiketler `{foreach}` döngüsü içinde kullanılabilir. `{first}` içeriği ilk geçişse oluşturulur. `{last}` içeriği oluşturulur … tahmin edebilir misiniz? Evet, son geçişse. Bunlar aslında `{if $iterator->first}` ve `{if $iterator->last}` için kısayollardır. -Etiketler [n:attributes |syntax#n:attributes] şeklinde de yazılabilir: +Etiketler ayrıca zarif bir şekilde [n:niteliği |syntax#n:nitelikler] olarak da kullanılabilir: ```latte {foreach $rows as $row} - {first}

                                                                                                                                List of names

                                                                                                                                {/first} + {first}

                                                                                                                                İsim listesi

                                                                                                                                {/first}

                                                                                                                                {$row->name}

                                                                                                                                @@ -456,7 +475,7 @@ Etiketler [n:attributes |syntax#n:attributes] şeklinde de yazılabilir: {/foreach} ``` -Yineleme son değilse `{sep}` içeriği işlenir, bu nedenle listelenen öğeler arasındaki virgüller gibi sınırlayıcıları yazdırmak için uygundur: +`{sep}` etiketinin içeriği, geçiş son değilse oluşturulur, bu nedenle yazdırılan öğeler arasına virgül gibi ayırıcılar oluşturmak için kullanışlıdır: ```latte {foreach $items as $item} {$item} {sep}, {/sep} {/foreach} @@ -465,12 +484,12 @@ Yineleme son değilse `{sep}` içeriği işlenir, bu nedenle listelenen öğeler Bu oldukça pratik, değil mi? -`{iterateWhile}` .{data-version:2.10} -------------------------------------- +`{iterateWhile}` +---------------- -Koşul karşılandığı sürece yinelemeyi iç içe bir döngüde gerçekleştirerek foreach döngüsünde yineleme sırasında doğrusal verilerin gruplanmasını basitleştirir. [Yemek kitabındaki talimatları okuyun |cookbook/iteratewhile]. +Koşul karşılandığı sürece iç içe bir döngüde yineleme yaparak foreach döngüsünde yinelenirken doğrusal verilerin gruplandırılmasını basitleştirir. [Detaylı kılavuzu okuyun|cookbook/grouping]. -Ayrıca yukarıdaki örnekte `{first}` ve `{last}` adreslerini zarif bir şekilde değiştirebilir: +Ayrıca yukarıdaki örnekte `{first}` ve `{last}`'ı zarif bir şekilde değiştirebilir: ```latte {foreach $rows as $row} @@ -487,19 +506,21 @@ Ayrıca yukarıdaki örnekte `{first}` ve `{last}` adreslerini zarif bir şekild {/foreach} ``` +Ayrıca bkz. [batch |filters#batch] ve [group |filters#group] filtreleri. + `{for}` ------- -Döngüyü PHP'deki ile tamamen aynı şekilde yazıyoruz: +Döngüyü tıpkı PHP'deki gibi yazarız: ```latte {for $i = 0; $i < 10; $i++} - Item #{$i} + Öğe {$i} {/for} ``` -Etiket [n:attribute |syntax#n:attributes] şeklinde de yazılabilir: +Etiket ayrıca [n:niteliği |syntax#n:nitelikler] olarak da kullanılabilir: ```latte

                                                                                                                                {$i}

                                                                                                                                @@ -509,7 +530,7 @@ Etiket [n:attribute |syntax#n:attributes] şeklinde de yazılabilir: `{while}` --------- -Yine, döngüyü PHP'deki ile tamamen aynı şekilde yazıyoruz: +Döngüyü yine tıpkı PHP'deki gibi yazarız: ```latte {while $row = $result->fetch()} @@ -517,7 +538,7 @@ Yine, döngüyü PHP'deki ile tamamen aynı şekilde yazıyoruz: {/while} ``` -Ya da [n:attribute |syntax#n:attributes] olarak: +Veya [n:niteliği |syntax#n:nitelikler] olarak: ```latte @@ -525,7 +546,7 @@ Ya da [n:attribute |syntax#n:attributes] olarak: ``` -Son etiketinde bir koşul bulunan bir varyant PHP'deki do-while döngüsüne karşılık gelir: +PHP'deki do-while döngüsüne karşılık gelen bitiş etiketindeki koşulla bir varyant da mümkündür: ```latte {while} @@ -537,7 +558,7 @@ Son etiketinde bir koşul bulunan bir varyant PHP'deki do-while döngüsüne kar `{continueIf}` `{skipIf}` `{breakIf}` ------------------------------------- -Herhangi bir döngüyü kontrol etmek için kullanabileceğiniz özel etiketler vardır - `{continueIf ?}` ve `{breakIf ?}`, koşullar karşılandığında sırasıyla bir sonraki yinelemeye atlar ve döngüyü sonlandırır: +Herhangi bir döngüyü kontrol etmek için, koşul karşılandığında sonraki öğeye geçen veya döngüyü sonlandıran `{continueIf ?}` ve `{breakIf ?}` etiketlerini kullanabilirsiniz: ```latte {foreach $rows as $row} @@ -547,8 +568,8 @@ Herhangi bir döngüyü kontrol etmek için kullanabileceğiniz özel etiketler {/foreach} ``` -.{data-version:2.9} -`{skipIf}` etiketi `{continueIf}` etiketine çok benzer, ancak sayacı artırmaz. Böylece `$iterator->counter` adresini yazdırdığınızda ve bazı öğeleri atladığınızda numaralandırmada delikler oluşmaz. Ayrıca tüm öğeleri atladığınızda {else} cümlesi oluşturulacaktır. + +`{skipIf}` etiketi `{continueIf}`'e çok benzer, ancak `$iterator->counter` sayacını artırmaz, bu nedenle onu yazdırırsak ve aynı zamanda bazı öğeleri atlarsak, numaralandırmada boşluklar olmaz. Ve ayrıca `{else}` ifadesi tüm öğeleri atlarsak oluşturulur. ```latte
                                                                                                                                  @@ -556,7 +577,7 @@ Herhangi bir döngüyü kontrol etmek için kullanabileceğiniz özel etiketler {skipIf $person->age < 18}
                                                                                                                                • {$iterator->counter}. {$person->name}
                                                                                                                                • {else} -
                                                                                                                                • Sorry, no adult users in this list
                                                                                                                                • +
                                                                                                                                • Üzgünüz, bu listede hiç yetişkin yok
                                                                                                                                • {/foreach}
                                                                                                                                ``` @@ -565,44 +586,40 @@ Herhangi bir döngüyü kontrol etmek için kullanabileceğiniz özel etiketler `{exitIf}` .{data-version:3.0.5} -------------------------------- -Bir koşul karşılandığında bir şablonun veya bloğun oluşturulmasını sonlandırır (yani "erken çıkış"). +Koşul karşılandığında şablonun veya bloğun oluşturulmasını sonlandırır ("erken çıkış"). ```latte {exitIf !$messages} -

                                                                                                                                Messages

                                                                                                                                +

                                                                                                                                Mesajlar

                                                                                                                                {$message}
                                                                                                                                ``` -Şablonlar Dahil .[#toc-including-templates] -=========================================== +Şablon Ekleme +============= `{include 'file.latte'}` .{toc: include} ---------------------------------------- .[note] -Ayrıca bakınız [`{include block}` |template-inheritance#printing-blocks] +Ayrıca bkz. [`{include block}` |template-inheritance#Blokları İşleme] -`{include}` etiketi belirtilen şablonu yükler ve işler. Favori PHP dilimizde bu şuna benzer: +`{include}` etiketi belirtilen şablonu yükler ve oluşturur. Sevdiğimiz dilimiz PHP'nin dilinde konuşursak, bu şuna benzer: ```php ``` -Dahil edilen şablonların aktif bağlamın değişkenlerine erişimi yoktur, ancak global değişkenlere erişimi vardır. +Eklenen şablonlar etkin bağlamın değişkenlerine erişemez, yalnızca genel değişkenlere erişebilirler. -Değişkenleri bu şekilde aktarabilirsiniz: +Değişkenleri eklenen şablona şu şekilde aktarabilirsiniz: ```latte -{* Latte 2.9'dan beri *} {include 'template.latte', foo: 'bar', id: 123} - -{* Latte 2.9'dan önce *} -{include 'template.latte', foo => 'bar', id => 123} ``` Şablon adı herhangi bir PHP ifadesi olabilir: @@ -612,25 +629,25 @@ Değişkenleri bu şekilde aktarabilirsiniz: {include $ajax ? 'ajax.latte' : 'not-ajax.latte'} ``` -Eklenen içerik [filtreler |syntax#filters] kullanılarak değiştirilebilir. Aşağıdaki örnek tüm HTML öğelerini kaldırır ve durumu ayarlar: +Eklenen içerik [filtreler |syntax#Filtreler] kullanılarak değiştirilebilir. Aşağıdaki örnek tüm HTML'i kaldırır ve harf boyutunu ayarlar: ```latte {include 'heading.latte' |stripHtml|capitalize} ``` -[Şablon kalıtımı |template inheritance] **varsayılan olarak buna dahil değildir**. Dahil edilen şablonlara blok etiketleri ekleyebilseniz de, bunlar dahil edildikleri şablondaki eşleşen blokların yerini almayacaktır. İçerikleri, sayfaların veya modüllerin bağımsız ve korumalı parçaları olarak düşünün. Bu davranış `with blocks` değiştiricisi kullanılarak değiştirilebilir (Latte 2.9.1'den beri): +Varsayılan olarak [şablon kalıtımı|template-inheritance] bu durumda hiçbir rol oynamaz. Dahil edilen şablonda bloklar kullanabilsek de, dahil edildiği şablondaki karşılık gelen blokların yerini almaz. Dahil edilen şablonları sayfaların veya modüllerin ayrı, korumalı parçaları olarak düşünün. Bu davranış `with blocks` değiştiricisi kullanılarak değiştirilebilir: ```latte {include 'template.latte' with blocks} ``` -Etikette belirtilen dosya adı ile diskteki dosya arasındaki ilişki bir [yükleyici |extending-latte#Loaders] meselesidir. +Etikette belirtilen dosya adı ile diskteki dosya arasındaki ilişki [yükleyicinin|loaders] işidir. -`{sandbox}` .{data-version:2.8} -------------------------------- +`{sandbox}` +----------- -Bir son kullanıcı tarafından oluşturulan bir şablonu eklerken, bunu korumalı alana almayı düşünmelisiniz (daha fazla bilgi korumalı alan [belgelerinde |sandbox] bulunmaktadır): +Son kullanıcı tarafından oluşturulan bir şablonu eklerken, sandbox modunu düşünmelisiniz (daha fazla bilgi için [sandbox dokümantasyonu |sandbox]): ```latte {sandbox 'untrusted.latte', level: 3, data: $menu} @@ -641,9 +658,9 @@ Bir son kullanıcı tarafından oluşturulan bir şablonu eklerken, bunu korumal ========= .[note] -Ayrıca bakınız [`{block name}` |template-inheritance#blocks] +Ayrıca bkz. [`{block name}` |template-inheritance#Bloklar] -Adı olmayan bloklar, şablonun bir bölümüne [filtre |syntax#filters] uygulama olanağı sunar. Örneğin, gereksiz boşlukları kaldırmak için bir [şerit |filters#strip] filtresi uygulayabilirsiniz: +Adsız bloklar, [filtreleri |syntax#Filtreler] şablonun bir kısmına uygulamanın bir yolu olarak hizmet eder. Örneğin, bu şekilde gereksiz boşlukları kaldıran [strip |filters#spaceless] filtresini uygulayabilirsiniz: ```latte {block|strip} @@ -654,16 +671,16 @@ Adı olmayan bloklar, şablonun bir bölümüne [filtre |syntax#filters] uygulam ``` -İstisna İşleme .[#toc-exception-handling] -========================================= +İstisna Yönetimi +================ -`{try}` .{data-version:2.9} ---------------------------- +`{try}` +------- -Bu etiketler, sağlam şablonlar oluşturmayı son derece kolaylaştırır. +Bu etiket sayesinde sağlam şablonlar oluşturmak son derece kolaydır. -`{try}` bloğu işlenirken bir istisna oluşursa, tüm blok atılır ve işleme bundan sonra devam eder: +`{try}` bloğunu oluştururken bir istisna oluşursa, tüm blok atılır ve oluşturma ondan sonra devam eder: ```latte {try} @@ -675,7 +692,7 @@ Bu etiketler, sağlam şablonlar oluşturmayı son derece kolaylaştırır. {/try} ``` -İsteğe bağlı `{else}` cümlesinin içeriği yalnızca bir istisna oluştuğunda işlenir: +İsteğe bağlı `{else}` ifadesindeki içerik yalnızca bir istisna oluştuğunda oluşturulur: ```latte {try} @@ -685,11 +702,11 @@ Bu etiketler, sağlam şablonlar oluşturmayı son derece kolaylaştırır. {/foreach} {else} -

                                                                                                                                Sorry, the tweets could not be loaded.

                                                                                                                                +

                                                                                                                                Üzgünüz, tweet'ler yüklenemedi.

                                                                                                                                {/try} ``` -Etiket [n:attribute |syntax#n:attributes] şeklinde de yazılabilir: +Etiket ayrıca [n:niteliği |syntax#n:nitelikler] olarak da kullanılabilir: ```latte
                                                                                                                                  @@ -697,13 +714,13 @@ Etiket [n:attribute |syntax#n:attributes] şeklinde de yazılabilir:
                                                                                                                                ``` -Günlük kaydı için [kendi istisna işleyicisini |develop#exception handler] tanımlamak da mümkündür: +Ayrıca, örneğin günlükleme için özel bir [istisna işleyici |develop#İstisna İşleyici] tanımlamak da mümkündür. -`{rollback}` .{data-version:2.9} --------------------------------- +`{rollback}` +------------ -`{try}` bloğu `{rollback}` kullanılarak manuel olarak da durdurulabilir ve atlanabilir. Böylece tüm girdi verilerini önceden kontrol etmeniz gerekmez ve yalnızca render sırasında nesneyi render etmenin mantıklı olup olmadığına karar verebilirsiniz. +`{try}` bloğu ayrıca `{rollback}` kullanılarak manuel olarak durdurulabilir ve atlanabilir. Bu sayede tüm girdi verilerini önceden kontrol etmeniz gerekmez ve yalnızca oluşturma sırasında nesneyi hiç oluşturmak istemediğinize karar verebilirsiniz: ```latte {try} @@ -719,30 +736,30 @@ Günlük kaydı için [kendi istisna işleyicisini |develop#exception handler] t ``` -Değişkenler .[#toc-variables] -============================= +Değişkenler +=========== `{var}` `{default}` ------------------- -Şablonda `{var}` etiketi ile yeni değişkenler oluşturacağız: +Şablonda `{var}` etiketiyle yeni değişkenler oluştururuz: ```latte {var $name = 'John Smith'} {var $age = 27} -{* Çoklu beyan *} +{* Çoklu bildirim *} {var $name = 'John Smith', $age = 27} ``` -`{default}` etiketi de benzer şekilde çalışır, ancak değişkenleri yalnızca mevcut değillerse oluşturur: +`{default}` etiketi benzer şekilde çalışır, ancak değişkenleri yalnızca mevcut değillerse oluşturur. Değişken zaten mevcutsa ve `null` değerini içeriyorsa, üzerine yazılmaz: ```latte -{default $lang = 'cs'} +{default $lang = 'tr'} ``` -Latte 2.7'den itibaren [değişkenlerin türlerini |type-system] de belirtebilirsiniz. Şimdilik, bunlar bilgilendiricidir ve Latte bunları kontrol etmez. +Ayrıca [değişken tiplerini|type-system] de belirtebilirsiniz. Şimdilik bunlar bilgilendiricidir ve Latte bunları kontrol etmez. ```latte {var string $name = $article->getTitle()} @@ -750,10 +767,10 @@ Latte 2.7'den itibaren [değişkenlerin türlerini |type-system] de belirtebilir ``` -`{parameters}` .{data-version:2.9} ----------------------------------- +`{parameters}` +-------------- -Tıpkı bir fonksiyonun parametrelerini bildirmesi gibi, bir şablon da değişkenlerini başlangıçta bildirebilir: +Fonksiyonların parametrelerini bildirdiği gibi, bir şablon da başlangıçta değişkenlerini bildirebilir: ```latte {parameters @@ -763,15 +780,15 @@ Tıpkı bir fonksiyonun parametrelerini bildirmesi gibi, bir şablon da değişk } ``` -Varsayılan değeri olmayan `$a` ve `$b` değişkenleri otomatik olarak `null` varsayılan değerine sahip olur. Bildirilen türler hala bilgilendiricidir ve Latte bunları kontrol etmez. +Varsayılan değeri belirtilmeyen `$a` ve `$b` değişkenleri otomatik olarak `null` varsayılan değerine sahiptir. Bildirilen tipler şimdilik bilgilendiricidir ve Latte bunları kontrol etmez. -Bildirilen değişkenler dışında hiçbir değişken şablona aktarılmaz. Bu, `{default}` etiketinden bir farktır. +Bildirilenler dışındaki değişkenler şablona aktarılmaz. Bu, onu `{default}` etiketinden ayırır. `{capture}` ----------- -`{capture}` etiketini kullanarak çıktıyı bir değişkene yakalayabilirsiniz: +Çıktıyı bir değişkene yakalar: ```latte {capture $var} @@ -780,10 +797,10 @@ Bildirilen değişkenler dışında hiçbir değişken şablona aktarılmaz. Bu, {/capture} -

                                                                                                                                Captured: {$var}

                                                                                                                                +

                                                                                                                                Yakalanan: {$var}

                                                                                                                                ``` -Etiket [n:attribute |syntax#n:attributes] şeklinde de yazılabilir: +Etiket, her çift etiket gibi, [n:niteliği |syntax#n:nitelikler] olarak da yazılabilir: ```latte
                                                                                                                                  @@ -791,15 +808,17 @@ Etiket [n:attribute |syntax#n:attributes] şeklinde de yazılabilir:
                                                                                                                                ``` +HTML çıktısı `$var` değişkenine `Latte\Runtime\Html` nesnesi biçiminde kaydedilir, böylece yazdırırken [istenmeyen kaçış işleminin gerçekleşmemesi |develop#Değişken Otomatik Kaçışını Devre Dışı Bırakma] sağlanır. + -Diğerleri .[#toc-others] -======================== +Diğer +===== `{contentType}` --------------- -Şablonun ne tür bir içeriği temsil ettiğini belirtmek için etiketi kullanın. Seçenekler şunlardır: +Etiketle, şablonun hangi içerik türünü temsil ettiğini belirtirsiniz. Seçenekler şunlardır: - `html` (varsayılan tip) - `xml` @@ -808,9 +827,9 @@ Diğerleri .[#toc-others] - `calendar` (iCal) - `text` -Kullanımı önemlidir çünkü [bağlama duyarlı |safety-first#context-aware-escaping] kaçış ayarlar ve ancak o zaman Latte doğru şekilde kaçabilir. Örneğin, `{contentType xml}` XML moduna geçer, `{contentType text}` kaçışı tamamen kapatır. +Kullanımı önemlidir, çünkü [bağlama duyarlı kaçış |safety-first#Bağlama Duyarlı Kaçış]'ı ayarlar ve yalnızca bu şekilde doğru şekilde kaçış yapabilir. Örneğin, `{contentType xml}` XML moduna geçer, `{contentType text}` kaçış işlemini tamamen kapatır. -Parametre `application/xml` gibi tam özellikli bir MIME türüyse, tarayıcıya `Content-Type` şeklinde bir HTTP başlığı da gönderir: +Parametre `application/xml` gibi tam bir MIME türü ise, ayrıca tarayıcıya `Content-Type` HTTP başlığını da gönderir: ```latte {contentType application/xml} @@ -829,26 +848,24 @@ Parametre `application/xml` gibi tam özellikli bir MIME türüyse, tarayıcıya `{debugbreak}` -------------- -Kod yürütmesinin kesileceği yeri belirtir. Programcının çalışma zamanı ortamını incelemesi ve kodun beklendiği gibi çalıştığından emin olması için hata ayıklama amacıyla kullanılır. [Xdebug'ı |https://xdebug.org] destekler. Ek olarak, kodun ne zaman kırılması gerektiğini bir koşul olarak belirtebilirsiniz. +Programcının çalışma zamanı ortamını inceleyebilmesi ve programın beklendiği gibi çalışıp çalışmadığını belirleyebilmesi için programın çalışmasının duraklatılacağı ve hata ayıklayıcının başlatılacağı yeri belirtir. [Xdebug |https://xdebug.org/]'ı destekler. Programın ne zaman duraklatılacağını belirleyen bir koşul eklenebilir. ```latte -{debugbreak} {* programı bozar *} +{debugbreak} {* programı duraklatır *} -{debugbreak $counter == 1} {* koşul yerine getirilirse programı keser *} +{debugbreak $counter == 1} {* koşul karşılandığında programı duraklatır *} ``` `{do}` ------ -Kodu çalıştırır ve hiçbir şey yazdırmaz. +PHP kodunu yürütür ve hiçbir şey yazdırmaz. Diğer tüm etiketlerde olduğu gibi, PHP kodu tek bir ifade anlamına gelir, bkz. [PHP kısıtlamaları |syntax#Latte de PHP Kısıtlamaları]. ```latte {do $num++} ``` -Latte 2.7 ve önceki sürümlerde `{php}` kullanılıyordu. - `{dump}` -------- @@ -858,17 +875,23 @@ Bir değişkeni veya geçerli bağlamı döker. ```latte {dump $name} {* $name değişkenini döker *} -{dump} {* tanımlanmış tüm değişkenleri döker *} +{dump} {* Şu anda tanımlanmış tüm değişkenleri döker *} ``` .[caution] -[Tracy |tracy:] paketi gerektirir. +[Tracy|tracy:] kütüphanesini gerektirir. + + +`{php}` +------- + +Herhangi bir PHP kodunu yürütmeye izin verir. Etiketin [RawPhpExtension |develop#RawPhpExtension] uzantısı kullanılarak etkinleştirilmesi gerekir. `{spaceless}` ------------- -Gereksiz boşlukları kaldırır. [Boşluksuz |filters#spaceless] filtreye benzer. +Çıktıdan gereksiz boşlukları kaldırır. [spaceless |filters#spaceless] filtresine benzer şekilde çalışır. ```latte {spaceless} @@ -878,52 +901,52 @@ Gereksiz boşlukları kaldırır. [Boşluksuz |filters#spaceless] filtreye benze {/spaceless} ``` -Çıkışlar: +Oluşturur ```latte
                                                                                                                                • Hello
                                                                                                                                ``` -Etiket [n:attribute |syntax#n:attributes] şeklinde de yazılabilir: +Etiket ayrıca [n:niteliği |syntax#n:nitelikler] olarak da yazılabilir. `{syntax}` ---------- -Latte etiketleri yalnızca tek küme parantezi içine alınmak zorunda değildir. Çalışma zamanında bile başka bir ayırıcı seçebilirsiniz. Bu, parametrenin olabileceği `{syntax…}` ile yapılır: +Latte etiketlerinin yalnızca tek küme parantezleriyle sınırlanması gerekmez. Çalışma zamanında bile başka bir sınırlayıcı seçebiliriz. Bunun için `{syntax …}` kullanılır, burada parametre olarak şunlar belirtilebilir: -- Çifte: `{{...}}` -- off: Latte etiketlerini tamamen devre dışı bırakır +- double: `{{...}}` +- off: Latte etiketlerinin işlenmesini tamamen kapatır -n:attribute notasyonunu kullanarak Latte'yi yalnızca bir JavaScript bloğu için devre dışı bırakabiliriz: +n:nitelikleri kullanarak, Latte'yi örneğin yalnızca bir JavaScript bloğu için kapatabilirsiniz: ```latte ``` -Latte JavaScript içinde çok rahat bir şekilde kullanılabilir, sadece bu örnekteki gibi harfin `{` harfinden hemen sonra geldiği yapılardan kaçının, [JavaScript veya CSS içinde Lat |recipes#Latte inside JavaScript or CSS] te bölümüne bakın. +Latte, JavaScript içinde de çok rahat bir şekilde kullanılabilir, sadece bu örnekteki gibi `{` işaretinden hemen sonra bir harfin geldiği yapılardan kaçınmak yeterlidir, bkz. [JavaScript veya CSS İçinde Latte |recipes#JavaScript veya CSS İçinde Latte]. -Latte'yi `{syntax off}` ile kapatırsanız (yani etiket, n:niteliği değil), `{/syntax}` adresine kadar olan tüm etiketleri kesinlikle yok sayacaktır. +Latte'yi `{syntax off}` kullanarak (yani etiketle, n:niteliğiyle değil) kapatırsanız, `{/syntax}`'a kadar tüm etiketleri kesinlikle yok sayacaktır. -{trace} .{data-version:2.10} ----------------------------- +{trace} +------- -Yığın izi şablonların ruhuna uygun olan bir `Latte\RuntimeException` istisnası atar. Bu nedenle, işlevleri ve yöntemleri çağırmak yerine, blokları çağırmayı ve şablonları eklemeyi içerir. [Tracy |tracy:] gibi atılan istisnaları açıkça görüntülemek için bir araç kullanırsanız, geçirilen tüm argümanlar da dahil olmak üzere çağrı yığınını açıkça görürsünüz. +Yığın izi şablonlar ruhunda olan bir `Latte\RuntimeException` istisnası fırlatır. Yani fonksiyon ve metot çağrıları yerine blok çağrıları ve şablon eklemeleri içerir. Fırlatılan istisnaları net bir şekilde görüntülemek için [Tracy|tracy:] gibi bir araç kullanıyorsanız, tüm aktarılan parametreler dahil olmak üzere çağrı yığını net bir şekilde görüntülenir. -HTML Etiket Yardımcıları .[#toc-html-tag-helpers] -================================================= +HTML Kodlayıcı Yardımcıları +=========================== -n:sınıf .[#toc-n-class] ------------------------ +n:class +------- -`n:class` sayesinde, `class` HTML özniteliğini tam olarak ihtiyaç duyduğunuz şekilde oluşturmak çok kolaydır. +`n:class` sayesinde, HTML `class` niteliğini tam olarak istediğiniz gibi çok kolay bir şekilde oluşturabilirsiniz. -Örnek: Aktif öğenin `active` sınıfına sahip olmasını istiyorum: +Örnek: etkin öğenin `active` sınıfına sahip olmasını istiyorum: ```latte {foreach $items as $item} @@ -931,7 +954,7 @@ n:sınıf .[#toc-n-class] {/foreach} ``` -Ayrıca ilk öğenin `first` ve `main` sınıflarına sahip olması gerekiyor: +Ayrıca, ilk öğenin `first` ve `main` sınıflarına sahip olması gerekir: ```latte {foreach $items as $item} @@ -939,7 +962,7 @@ Ayrıca ilk öğenin `first` ve `main` sınıflarına sahip olması gerekiyor: {/foreach} ``` -Ve tüm öğeler `list-item` sınıfına sahip olmalıdır: +Ve tüm öğelerin `list-item` sınıfına sahip olması gerekir: ```latte {foreach $items as $item} @@ -947,13 +970,13 @@ Ve tüm öğeler `list-item` sınıfına sahip olmalıdır: {/foreach} ``` -İnanılmaz derecede basit, değil mi? +Harika derecede basit, değil mi? -n:attr .[#toc-n-attr] ---------------------- +n:attr +------ -`n:attr` niteliği, [n:class |#n:class] ile aynı zarafete sahip rastgele HTML nitelikleri oluşturabilir. +`n:attr` niteliği, [#n:class]'ın sahip olduğu aynı zarafetle herhangi bir HTML niteliğini oluşturabilir. ```latte {foreach $data as $item} @@ -961,7 +984,7 @@ n:attr .[#toc-n-attr] {/foreach} ``` -Dönen değerlere bağlı olarak, örn: +Döndürülen değerlere bağlı olarak, örneğin şunu yazdırır: ```latte @@ -972,8 +995,8 @@ Dönen değerlere bağlı olarak, örn: ``` -n:etiket .[#toc-n-tag] ----------------------- +n:tag +----- `n:tag` niteliği, bir HTML öğesinin adını dinamik olarak değiştirebilir. @@ -981,17 +1004,19 @@ n:etiket .[#toc-n-tag]

                                                                                                                                {$title}

                                                                                                                                ``` -Eğer `$heading === null`, ise `

                                                                                                                                ` etiketi değiştirilmeden yazdırılır. Aksi takdirde, öğe adı değişkenin değeriyle değiştirilir, bu nedenle `$heading === 'h3'` için yazar: +Eğer `$heading === null` ise, `

                                                                                                                                ` etiketi değişmeden yazdırılır. Aksi takdirde, öğenin adı değişkenin değerine değiştirilir, yani `$heading === 'h3'` için şu yazdırılır: ```latte

                                                                                                                                ...

                                                                                                                                ``` +Latte güvenli bir şablonlama sistemi olduğundan, yeni etiket adının geçerli olup olmadığını ve istenmeyen veya zararlı değerler içermediğini kontrol eder. -n:ifcontent .[#toc-n-ifcontent] -------------------------------- -Boş bir HTML öğesinin, yani boşluktan başka bir şey içermeyen bir öğenin yazdırılmasını önler. +n:ifcontent +----------- + +Boş bir HTML öğesinin, yani boşluklardan başka bir şey içermeyen bir öğenin yazdırılmasını önler. ```latte
                                                                                                                                @@ -999,7 +1024,7 @@ Boş bir HTML öğesinin, yani boşluktan başka bir şey içermeyen bir öğeni
                                                                                                                                ``` -`$error` değişkeninin değerlerine bağlı olarak bu yazdırılacaktır: +`$error` değişkeninin değerine bağlı olarak yazdırır: ```latte {* $error = '' *} @@ -1013,10 +1038,10 @@ Boş bir HTML öğesinin, yani boşluktan başka bir şey içermeyen bir öğeni ``` -Çeviri .[#toc-translation] -========================== +Çeviriler +========= -Çeviri etiketlerinin çalışmasını sağlamak için [çevirmen kurmanız |develop#TranslatorExtension] gerekir. Ayrıca şunları da kullanabilirsiniz [`translate` |filters#translate] çeviri için filtre. +Çeviri etiketlerinin çalışması için [çevirmenin etkinleştirilmesi |develop#TranslatorExtension] gerekir. Çeviri için ayrıca [`translate` |filters#translate] filtresini de kullanabilirsiniz. `{_...}` @@ -1025,30 +1050,30 @@ Boş bir HTML öğesinin, yani boşluktan başka bir şey içermeyen bir öğeni Değerleri diğer dillere çevirir. ```latte -{_'Basket'} +{_'Sepet'} {_$item} ``` -Çevirmene başka parametreler de aktarılabilir: +Çevirmene ek parametreler de aktarılabilir: ```latte -{_'Basket', domain: order} +{_'Sepet', domain: order} ``` `{translate}` ------------- -Překládá části šablony: +Şablonun bölümlerini çevirir: ```latte -

                                                                                                                                {translate}Order{/translate}

                                                                                                                                +

                                                                                                                                {translate}Sipariş{/translate}

                                                                                                                                {translate domain: order}Lorem ipsum ...{/translate} ``` -Elemanın içini çevirmek için etiket [n:attribute |syntax#n:attributes] olarak da yazılabilir: +Etiket ayrıca [n:niteliği |syntax#n:nitelikler] olarak da yazılabilir, öğenin içini çevirmek için: ```latte -

                                                                                                                                Order

                                                                                                                                +

                                                                                                                                Sipariş

                                                                                                                                ``` diff --git a/latte/tr/template-inheritance.texy b/latte/tr/template-inheritance.texy index 38a30ed87b..dd93508724 100644 --- a/latte/tr/template-inheritance.texy +++ b/latte/tr/template-inheritance.texy @@ -1,16 +1,16 @@ -Şablon Kalıtımı ve Yeniden Kullanılabilirlik -******************************************** +Şablon Kalıtımı ve Yeniden Kullanılabilirliği +********************************************* .[perex] -Şablon yeniden kullanılabilirliği ve kalıtım mekanizmaları üretkenliğinizi artırmak için burada, çünkü her şablon yalnızca benzersiz içerikler içeriyor ve tekrarlanan öğeler ve yapılar yeniden kullanılıyor. Üç kavramı tanıtıyoruz: [düzen kalı |#layout inheritance] tımı, [yatay yeniden |#horizontal reuse] kullanım ve [birim |#unit inheritance] kalıtımı. +Şablon yeniden kullanım ve kalıtım mekanizmaları verimliliğinizi artıracaktır, çünkü her şablon yalnızca benzersiz içeriğini içerir ve tekrarlanan öğeler ve yapılar yeniden kullanılır. Üç kavramı tanıtıyoruz: [#Düzen Kalıtımı], [#Yatay Yeniden Kullanım] ve [#Birim Kalıtımı]. -Latte şablon kalıtımı kavramı PHP sınıf kalıtımına benzer. Diğer **çocuk şablonlarının** genişletebileceği ve ana şablonun bazı kısımlarını geçersiz kılabileceği bir **ana şablon** tanımlarsınız. Öğeler ortak bir yapıyı paylaştığında harika çalışır. Kulağa karmaşık mı geliyor? Merak etmeyin, değil. +Latte şablon kalıtımı kavramı, PHP'deki sınıf kalıtımına benzer. Diğer **alt şablonların** miras alabileceği ve üst şablonun bölümlerini geçersiz kılabileceği bir **üst şablon** tanımlarsınız. Öğeler ortak bir yapıyı paylaştığında harika çalışır. Kulağa karmaşık mı geliyor? Endişelenmeyin, çok kolay. -Düzen Kalıtımı `{layout}` .{toc: Layout Inheritance} -==================================================== +Düzen Kalıtımı `{layout}` .{toc:Düzen Kalıtımı} +=============================================== -Bir örnekle başlayarak düzen şablonu kalıtımına bakalım. Bu, örneğin `layout.latte` olarak adlandıracağımız bir üst şablon ve bir HTML iskelet belgesini tanımlıyor. +Düzen şablonu kalıtımına, yani düzene bir örnekle bakalım. Bu, örneğin `layout.latte` olarak adlandıracağımız ve bir HTML belgesinin iskeletini tanımlayan üst şablondur: ```latte @@ -30,9 +30,9 @@ Bir örnekle başlayarak düzen şablonu kalıtımına bakalım. Bu, örneğin ` ``` -`{block}` etiketleri, alt şablonların doldurabileceği üç blok tanımlar. Blok etiketinin tek yaptığı, şablon motoruna bir alt şablonun aynı isimde kendi bloğunu tanımlayarak şablonun bu bölümlerini geçersiz kılabileceğini söylemektir. +`{block}` etiketleri, alt şablonların doldurabileceği üç blok tanımlar. `block` etiketi yalnızca, alt şablonun aynı ada sahip kendi bloğunu tanımlayarak bu yeri geçersiz kılabileceğini bildirir. -Bir çocuk şablonu şu şekilde görünebilir: +Bir alt şablon şöyle görünebilir: ```latte {layout 'layout.latte'} @@ -44,11 +44,11 @@ Bir çocuk şablonu şu şekilde görünebilir: {/block} ``` -Burada anahtar `{layout}` etiketidir. Şablon motoruna bu şablonun başka bir şablonu "genişlettiğini" söyler. Latte bu şablonu işlediğinde, önce üst şablonu bulur - bu durumda `layout.latte`. +Buradaki anahtar `{layout}` etiketidir. Latte'ye bu şablonun başka bir şablonu „genişlettiğini“ söyler. Latte bu şablonu işlediğinde, önce üst şablonu bulur - bu durumda `layout.latte`. -Bu noktada, şablon motoru `layout.latte` adresindeki üç blok etiketini fark edecek ve bu blokları alt şablonun içeriğiyle değiştirecektir. Alt şablon *footer* bloğunu tanımlamadığından, bunun yerine üst şablondaki içeriğin kullanıldığını unutmayın. Ana şablondaki bir `{block}` etiketi içindeki içerik her zaman yedek olarak kullanılır. +Bu noktada Latte, `layout.latte` içindeki üç blok etiketini fark eder ve bu blokları alt şablonun içeriğiyle değiştirir. Alt şablon *footer* bloğunu tanımlamadığı için, bunun yerine üst şablondaki içerik kullanılır. Üst şablondaki `{block}` etiketindeki içerik her zaman yedek olarak kullanılır. -Çıktı aşağıdaki gibi görünebilir: +Çıktı şöyle görünebilir: ```latte @@ -68,7 +68,7 @@ Bu noktada, şablon motoru `layout.latte` adresindeki üç blok etiketini fark e ``` -Bir alt şablonda, bloklar yalnızca üst seviyede veya başka bir bloğun içinde yer alabilir, örn: +Alt şablonda, bloklar yalnızca en üst düzeyde veya başka bir bloğun içinde yerleştirilebilir, yani: ```latte {block content} @@ -76,7 +76,7 @@ Bir alt şablonda, bloklar yalnızca üst seviyede veya başka bir bloğun için {/block} ``` -Ayrıca, çevreleyen `{if}` koşulunun doğru veya yanlış olarak değerlendirilip değerlendirilmediğine bakılmaksızın her zaman bir blok oluşturulacaktır. Düşündüğünüzün aksine, bu şablon bir blok tanımlamaktadır. +Ayrıca, çevreleyen `{if}` koşulunun doğru veya yanlış olarak değerlendirilip değerlendirilmediğine bakılmaksızın her zaman bir blok oluşturulur. Yani öyle görünmese bile, bu şablon bloğu tanımlar. ```latte {if false} @@ -86,7 +86,7 @@ Ayrıca, çevreleyen `{if}` koşulunun doğru veya yanlış olarak değerlendiri {/if} ``` -Blok içindeki çıktının koşullu olarak görüntülenmesini istiyorsanız, bunun yerine aşağıdakileri kullanın: +Bloğun içindeki çıktının koşullu olarak görüntülenmesini istiyorsanız, bunun yerine aşağıdakini kullanın: ```latte {block head} @@ -96,7 +96,7 @@ Blok içindeki çıktının koşullu olarak görüntülenmesini istiyorsanız, b {/block} ``` -Bir alt şablondaki blokların dışındaki veriler, düzen şablonu oluşturulmadan önce yürütülür, böylece `{var $foo = bar}` gibi değişkenleri tanımlamak ve verileri tüm kalıtım zincirine yaymak için kullanabilirsiniz: +Alt şablondaki blokların dışındaki alan, düzen şablonu işlenmeden önce yürütülür, böylece bunları `{var $foo = bar}` gibi değişkenleri tanımlamak ve verileri tüm kalıtım zincirine yaymak için kullanabilirsiniz: ```latte {layout 'layout.latte'} @@ -106,51 +106,50 @@ Bir alt şablondaki blokların dışındaki veriler, düzen şablonu oluşturulm ``` -Çok Düzeyli Kalıtım .[#toc-multilevel-inheritance] --------------------------------------------------- -Gerektiği kadar çok sayıda kalıtım düzeyi kullanabilirsiniz. Düzen kalıtımını kullanmanın yaygın bir yolu aşağıdaki üç seviyeli yaklaşımdır: +Çok Seviyeli Kalıtım +-------------------- +İhtiyaç duyduğunuz kadar çok kalıtım seviyesi kullanabilirsiniz. Düzen kalıtımını kullanmanın yaygın bir yolu aşağıdaki üç seviyeli yaklaşımdır: -1) Sitenizin ana görünümünü ve hissini içeren bir `layout.latte` şablonu oluşturun. -2) Sitenizin her bölümü için bir `layout-SECTIONNAME.latte` şablonu oluşturun. Örneğin, `layout-news.latte`, `layout-blog.latte` vb. Bu şablonların tümü `layout.latte` adresini genişletir ve bölüme özgü stilleri/tasarımı içerir. -3) Haber makalesi veya blog girişi gibi her sayfa türü için ayrı şablonlar oluşturun. Bu şablonlar uygun bölüm şablonunu genişletir. +1) Sitenizin ana görünüm iskeletini içeren bir `layout.latte` şablonu oluşturun. +2) Sitenizin her bölümü için bir `layout-SECTIONNAME.latte` şablonu oluşturun. Örneğin, `layout-news.latte`, `layout-blog.latte` vb. Tüm bu şablonlar `layout.latte`'yi genişletir ve her bölüme özgü stilleri ve tasarımı içerir. +3) Her sayfa türü için ayrı şablonlar oluşturun, örneğin bir haber makalesi veya bir blog gönderisi. Bu şablonlar ilgili bölüm şablonunu genişletir. -Dinamik Yerleşim Kalıtımı .[#toc-dynamic-layout-inheritance] ------------------------------------------------------------- -Üst şablonun adı olarak bir değişken veya herhangi bir PHP ifadesi kullanabilirsiniz, böylece kalıtım dinamik olarak davranabilir: +Dinamik Kalıtım +--------------- +Üst şablonun adı olarak bir değişken veya herhangi bir PHP ifadesi kullanılabilir, böylece kalıtım dinamik olarak davranabilir: ```latte {layout $standalone ? 'minimum.latte' : 'layout.latte'} ``` -Düzen şablonunu [otomatik |develop#automatic-layout-lookup] olarak seçmek için Latte API'sini de kullanabilirsiniz. +Düzen şablonunu [otomatik olarak |develop#Otomatik Düzen Arama] seçmek için Latte API'sini de kullanabilirsiniz. -İpuçları .[#toc-tips] ---------------------- -Düzen kalıtımı ile çalışmak için bazı ipuçları: +İpuçları +-------- +Düzen kalıtımıyla çalışmak için bazı ipuçları: -- Bir şablonda `{layout}` kullanıyorsanız, bu şablondaki ilk şablon etiketi olmalıdır. +- Bir şablonda `{layout}` kullanırsanız, bu şablondaki ilk şablon etiketi olmalıdır. -- Düzen [otomatik olarak aranabilir |develop#automatic-layout-lookup] ( [sunumlarda |application:templates#search-for-templates] olduğu gibi). Bu durumda, şablonun bir düzene sahip olmaması gerekiyorsa, bunu `{layout none}` etiketi ile belirtecektir. +- Düzen [otomatik olarak bulunabilir |develop#Otomatik Düzen Arama] (örneğin [Presenter'larda |application:templates#Şablonları Bulma] olduğu gibi). Bu durumda, şablonun bir düzeni olmaması gerekiyorsa, bunu `{layout none}` etiketiyle bildirir. -- `{layout}` etiketinin `{extends}` takma adı vardır. +- `{layout}` etiketinin bir takma adı vardır: `{extends}`. -- Genişletilmiş şablonun dosya adı şablon [yükleyiciye |extending-latte#Loaders] bağlıdır. +- Düzen dosyasının adı [yükleyiciye |loaders] bağlıdır. -- İstediğiniz kadar bloğa sahip olabilirsiniz. Unutmayın, alt şablonlar tüm üst blokları tanımlamak zorunda değildir, bu nedenle bir dizi blokta makul varsayılanları doldurabilir ve daha sonra yalnızca ihtiyacınız olanları tanımlayabilirsiniz. +- İstediğiniz kadar blok sahibi olabilirsiniz. Unutmayın, alt şablonların tüm üst blokları tanımlaması gerekmez, bu nedenle birkaç blokta makul varsayılan değerler doldurabilir ve ardından yalnızca daha sonra ihtiyacınız olanları tanımlayabilirsiniz. -Bloklar `{block}` .{toc: Blocks} -================================ +Bloklar `{block}` .{toc: Bloklar} +================================= .[note] -Ayrıca bakınız anonim [`{block}` |tags#block] +Ayrıca bkz. [anonim `{block}` |tags#block] -Bir blok, bir şablonun belirli bir bölümünün nasıl işleneceğini değiştirmenin bir yolunu sağlar, ancak etrafındaki mantığa hiçbir şekilde müdahale etmez. Bir bloğun nasıl çalıştığını ve daha da önemlisi nasıl çalışmadığını göstermek için aşağıdaki örneği ele alalım: +Bir blok, bir şablonun belirli bir bölümünün nasıl işleneceğini değiştirmenin bir yoludur, ancak etrafındaki mantığa müdahale etmez. Aşağıdaki örnekte, bir bloğun nasıl çalıştığını ve ayrıca nasıl çalışmadığını göstereceğiz: -```latte -{* parent.Latte *} +```latte .{file: parent.latte} {foreach $posts as $post} {block post}

                                                                                                                                {$post->title}

                                                                                                                                @@ -159,10 +158,9 @@ Bir blok, bir şablonun belirli bir bölümünün nasıl işleneceğini değişt {/foreach} ``` -Bu şablonu işlerseniz, sonuç blok etiketleri olsun ya da olmasın tamamen aynı olacaktır. Blokların dış kapsamlardaki değişkenlere erişimi vardır. Bu sadece bir alt şablon tarafından geçersiz kılınabilir hale getirmenin bir yoludur: +Bu şablonu işlerseniz, sonuç `{block}` etiketleriyle veya etiketleri olmadan tamamen aynı olacaktır. Bloklar, dış kapsamlardaki değişkenlere erişebilir. Sadece bir alt şablon tarafından geçersiz kılınma olasılığını verirler: -```latte -{* child.Latte *} +```latte .{file: child.latte} {layout 'parent.Latte'} {block post} @@ -173,7 +171,7 @@ Bu şablonu işlerseniz, sonuç blok etiketleri olsun ya da olmasın tamamen ayn {/block} ``` -Şimdi, alt şablon işlenirken, döngü `parent.Latte` temel şablonunda tanımlanan blok yerine `child.Latte` alt şablonunda tanımlanan bloğu kullanacaktır; bu durumda çalıştırılan şablon aşağıdakine eşdeğer olacaktır: +Şimdi, alt şablonu işlerken, döngü `parent.Latte`'de tanımlanan blok yerine `child.Latte` alt şablonunda tanımlanan bloğu kullanacaktır; çalıştırılan şablon daha sonra aşağıdakine eşdeğerdir: ```latte {foreach $posts as $post} @@ -184,7 +182,7 @@ Bu şablonu işlerseniz, sonuç blok etiketleri olsun ya da olmasın tamamen ayn {/foreach} ``` -Ancak, adlandırılmış bir blok içinde yeni bir değişken oluşturursak veya mevcut bir değişkenin değerini değiştirirsek, değişiklik yalnızca blok içinde görünür olacaktır: +Ancak, adlandırılmış bir bloğun içinde yeni bir değişken oluşturursak veya mevcut bir değişkenin değerini değiştirirsek, değişiklik yalnızca bloğun içinde görünür olacaktır: ```latte {var $foo = 'foo'} @@ -193,17 +191,17 @@ Ancak, adlandırılmış bir blok içinde yeni bir değişken oluşturursak veya {var $bar = 'bar'} {/block} -foo: {$foo} // prints: foo -bar: {$bar ?? 'not defined'} // prints: not defined +foo: {$foo} // yazdırır: foo +bar: {$bar ?? 'not defined'} // yazdırır: not defined ``` -Blok içeriği [filtreler |syntax#filters] tarafından değiştirilebilir. Aşağıdaki örnek, tüm HTML'yi kaldırır ve başlık atar: +Bloğun içeriği [filtreler |syntax#Filtreler] kullanılarak değiştirilebilir. Aşağıdaki örnek tüm HTML'yi kaldırır ve harf boyutunu değiştirir: ```latte {block title|stripHtml|capitalize}...{/block} ``` -Etiket [n:attribute |syntax#n:attributes] şeklinde de yazılabilir: +Etiket ayrıca [n:niteliği |syntax#n:nitelikler] olarak da yazılabilir: ```latte
                                                                                                                                @@ -212,10 +210,10 @@ Etiket [n:attribute |syntax#n:attributes] şeklinde de yazılabilir: ``` -Yerel Bloklar .[#toc-local-blocks] ----------------------------------- +Yerel Bloklar +------------- -Her blok aynı isimli üst bloğun içeriğini geçersiz kılar. Yerel bloklar hariç. Bunlar sınıftaki özel yöntemler gibi bir şeydir. Blok adlarının çakışması nedeniyle ikinci şablon tarafından üzerlerine yazılacağından endişe etmeden bir şablon oluşturabilirsiniz. +Her blok, aynı ada sahip üst bloğun içeriğini geçersiz kılar - yerel bloklar hariç. Sınıflarda, bu özel metotlar gibi bir şey olurdu. Böylece, blok adlarının çakışması nedeniyle başka bir şablondan geçersiz kılınacağından endişe etmeden şablonu oluşturabilirsiniz. ```latte {block local helper} @@ -224,13 +222,13 @@ Her blok aynı isimli üst bloğun içeriğini geçersiz kılar. Yerel bloklar h ``` -Baskı Blokları `{include}` .{toc: Printing Blocks} --------------------------------------------------- +Blokları İşleme `{include}` .{toc: Blokları İşleme} +--------------------------------------------------- .[note] -Ayrıca bakınız [`{include file}` |tags#include] +Ayrıca bkz. [`{include file}` |tags#include] -Bir bloğu belirli bir yere yazdırmak için `{include blockname}` etiketini kullanın: +Belirli bir yerde bir blok yazdırmak için `{include blockname}` etiketini kullanın: ```latte {block title}{/block} @@ -238,32 +236,28 @@ Bir bloğu belirli bir yere yazdırmak için `{include blockname}` etiketini kul

                                                                                                                                {include title}

                                                                                                                                ``` -Bloğu başka bir şablondan da görüntüleyebilirsiniz: +Başka bir şablondan bir blok da yazdırılabilir: ```latte {include footer from 'main.latte'} ``` -Yazdırılan blok, bloğun dahil edildiği aynı dosyada tanımlanmış olması dışında, etkin bağlamın değişkenlerine erişemez. Ancak global değişkenlere erişimleri vardır. +İşlenen blok, bloğun dahil edildiği aynı dosyada tanımlanmadığı sürece etkin bağlamın değişkenlerine erişemez. Ancak, genel değişkenlere erişebilir. -Değişkenleri bu şekilde aktarabilirsiniz: +Değişkenleri bloğa şu şekilde iletebilirsiniz: ```latte -{* Latte 2.9'dan beri *} {include footer, foo: bar, id: 123} - -{* Latte 2.9'dan önce *} -{include footer, foo => bar, id => 123} ``` -PHP'de blok adı olarak bir değişken veya herhangi bir ifade kullanabilirsiniz. Bu durumda, değişkenden önce `block` anahtar sözcüğünü ekleyin, böylece derleme zamanında bunun bir blok olduğu ve adı değişkende de olabilecek [şablon |tags#include] eklemediği bilinir: +Blok adı olarak bir değişken veya herhangi bir PHP ifadesi kullanılabilir. Bu durumda, değişkenin önüne `block` anahtar kelimesini ekleriz, böylece Latte derleme zamanında bunun bir blok olduğunu ve adı da bir değişkende olabilecek [şablon dahil etme |tags#include] olmadığını bilir: ```latte {var $name = footer} {include block $name} ``` -Blok kendi içinde de yazdırılabilir, bu da örneğin bir ağaç yapısı oluştururken kullanışlıdır: +Blok kendi içinde de işlenebilir, bu örneğin bir ağaç yapısını işlerken kullanışlıdır: ```latte {define menu, $items} @@ -281,19 +275,19 @@ Blok kendi içinde de yazdırılabilir, bu da örneğin bir ağaç yapısı olu {/define} ``` -`{include menu, ...}` yerine `{include this, ...}` de yazabiliriz, burada `this` geçerli blok anlamına gelir. +`{include menu, ...}` yerine `{include this, ...}` yazabiliriz, burada `this` geçerli blok anlamına gelir. -Yazdırılan içerik [filtrelerle |syntax#filters] değiştirilebilir. Aşağıdaki örnek tüm HTML'yi kaldırır ve başlığını büyük harfle yazar: +İşlenen blok [filtreler |syntax#Filtreler] kullanılarak değiştirilebilir. Aşağıdaki örnek tüm HTML'yi kaldırır ve harf boyutunu değiştirir: ```latte {include heading|stripHtml|capitalize} ``` -Ebeveyn Bloğu .[#toc-parent-block] ----------------------------------- +Üst Blok +-------- -Bloğun içeriğini üst şablondan yazdırmanız gerekiyorsa, `{include parent}` deyimi işinizi görecektir. Bu, bir üst bloğu tamamen geçersiz kılmak yerine içeriğine ekleme yapmak istediğinizde kullanışlıdır. +Üst şablondan bir bloğun içeriğini yazdırmanız gerekiyorsa, `{include parent}` kullanın. Bu, üst bloğun içeriğini tamamen geçersiz kılmak yerine yalnızca tamamlamak istediğinizde kullanışlıdır. ```latte {block footer} @@ -304,32 +298,31 @@ Bloğun içeriğini üst şablondan yazdırmanız gerekiyorsa, `{include parent} ``` -Tanımlar `{define}` .{toc: Definitions} ---------------------------------------- +Tanımlar `{define}` .{toc: Tanımlar} +------------------------------------ -Bloklara ek olarak, Latte'de "tanımlar" da vardır. Bunlar normal programlama dillerindeki fonksiyonlarla karşılaştırılabilir. Kendinizi tekrar etmemek için şablon parçalarını yeniden kullanmak için kullanışlıdırlar. +Bloklara ek olarak, Latte'de "tanımlar" da vardır. Yaygın programlama dillerinde, bunları fonksiyonlarla karşılaştırırdık. Kendinizi tekrar etmemek için şablon parçalarını yeniden kullanmak için kullanışlıdırlar. -Latte işleri kolayca yapmaya çalışır, bu nedenle temel olarak tanımlar bloklarla aynıdır ve **bloklar hakkında söylenen her şey tanımlar için de geçerlidir**. Bloklardan sadece üç şekilde farklıdır: +Latte işleri basit tutmaya çalışır, bu nedenle temelde tanımlar bloklarla aynıdır ve **bloklar hakkında söylenen her şey tanımlar için de geçerlidir**. Bloklardan şu şekilde farklıdırlar: -1) argümanları kabul edebilirler -2) [filtreleri olamaz|syntax#filters] -3) `{define}` etiketleri içine alınmışlardır ve bu etiketlerin içindeki içerik siz onları dahil edene kadar çıktıya gönderilmez. Bu sayede onları her yerde oluşturabilirsiniz: +1) `{define}` etiketleri içine alınırlar +2) Yalnızca `{include}` aracılığıyla dahil edildiklerinde işlenirler +3) PHP'deki fonksiyonlara benzer şekilde parametreler tanımlanabilir ```latte -{block foo}

                                                                                                                                Hello

                                                                                                                                {/block} -{* prints:

                                                                                                                                Hello

                                                                                                                                *} +{block foo}

                                                                                                                                Merhaba

                                                                                                                                {/block} +{* yazdırır:

                                                                                                                                Merhaba

                                                                                                                                *} -{define bar}

                                                                                                                                World

                                                                                                                                {/define} -{* prints nothing *} +{define bar}

                                                                                                                                Dünya

                                                                                                                                {/define} +{* hiçbir şey yazdırmaz *} {include bar} -{* prints:

                                                                                                                                World

                                                                                                                                *} +{* yazdırır:

                                                                                                                                Dünya

                                                                                                                                *} ``` -HTML formlarının tanımlar aracılığıyla nasıl işleneceğini tanımlayan genel bir yardımcı şablona sahip olduğunuzu düşünün: +HTML formlarının nasıl çizileceğine dair bir tanım koleksiyonu içeren bir yardımcı şablonunuz olduğunu hayal edin. -```latte -{* forms.latte *} +```latte .{file: forms.latte} {define input, $name, $value, $type = 'text'} {/define} @@ -339,38 +332,36 @@ HTML formlarının tanımlar aracılığıyla nasıl işleneceğini tanımlayan {/define} ``` -Bir tanımın argümanları, varsayılan değer belirtilmediği sürece, varsayılan değer `null` ile her zaman isteğe bağlıdır (burada `text`, Latte 2.9.1'den beri mümkün olan `$type` için varsayılan değerdir). Latte 2.7'den itibaren parametre türleri de bildirilebilir: `{define input, string $name, ...}`. - -Tanımların aktif bağlamın değişkenlerine erişimi yoktur, ancak global değişkenlere erişimleri vardır. +Argümanlar her zaman isteğe bağlıdır ve varsayılan bir değer belirtilmezse varsayılan değer `null` olur (burada `'text'`, `$type` için varsayılan değerdir). Parametre tipleri de bildirilebilir: `{define input, string $name, ...}`. -[Blok ile aynı şekilde |#Printing Blocks] dahil edilirler: +Tanımları içeren şablonu [`{import}` |#Yatay Yeniden Kullanım] kullanarak yükleriz. Tanımların kendileri [bloklarla aynı şekilde |#Blokları İşleme] işlenir: ```latte

                                                                                                                                {include input, 'password', null, 'password'}

                                                                                                                                {include textarea, 'comment'}

                                                                                                                                ``` +Tanımlar, etkin bağlamın değişkenlerine erişemez, ancak genel değişkenlere erişebilir. -Dinamik Blok Adları .[#toc-dynamic-block-names] ------------------------------------------------ -Latte blokların tanımlanmasında büyük esneklik sağlar çünkü blok adı herhangi bir PHP ifadesi olabilir. Bu örnekte `hi-Peter`, `hi-John` ve `hi-Mary` adında üç blok tanımlanmıştır: +Dinamik Blok Adları +------------------- -```latte -{* parent.latte *} +Latte, blokları tanımlarken büyük esneklik sağlar, çünkü blok adı herhangi bir PHP ifadesi olabilir. Bu örnek, `hi-Peter`, `hi-John` ve `hi-Mary` adlarıyla üç blok tanımlar: + +```latte .{file: parent.latte} {foreach [Peter, John, Mary] as $name} {block "hi-$name"}Hi, I am {$name}.{/block} {/foreach} ``` -Örneğin, bir alt şablonda yalnızca bir bloğu yeniden tanımlayabiliriz: +Alt şablonda, örneğin yalnızca bir bloğu yeniden tanımlayabiliriz: -```latte -{* child.latte *} +```latte .{file: child.latte} {block hi-John}Hello. I am {$name}.{/block} ``` -Böylece çıktı şu şekilde görünecektir: +Böylece çıktı şöyle görünecektir: ```latte Hi, I am Peter. @@ -379,13 +370,13 @@ Hi, I am Mary. ``` -Blok Varlığını Kontrol Etme `{ifset}` .{toc: Checking Block Existence} ----------------------------------------------------------------------- +Blok Varlığını Kontrol Etme `{ifset}` .{toc: Blok Varlığını Kontrol Etme} +------------------------------------------------------------------------- .[note] -Ayrıca bakınız [`{ifset $var}` |tags#ifset-elseifset] +Ayrıca bkz. [`{ifset $var}` |tags#ifset elseifset] -Geçerli bağlamda bir bloğun (veya daha fazla bloğun) var olup olmadığını kontrol etmek için `{ifset blockname}` testini kullanın: +`{ifset blockname}` testini kullanarak, geçerli bağlamda bir bloğun (veya birden çok bloğun) var olup olmadığını kontrol ederiz: ```latte {ifset footer} @@ -397,7 +388,7 @@ Geçerli bağlamda bir bloğun (veya daha fazla bloğun) var olup olmadığını {/ifset} ``` -PHP'de blok adı olarak bir değişken veya herhangi bir ifade kullanabilirsiniz. Bu durumda, kontrol edilenin değişken olmadığını açıkça belirtmek için [değişkenden |tags#ifset-elseifset] önce `block` anahtar sözcüğünü ekleyin: +Blok adı olarak bir değişken veya herhangi bir PHP ifadesi kullanılabilir. Bu durumda, değişkenin önüne `block` anahtar kelimesini ekleriz, böylece bunun [değişkenlerin |tags#ifset elseifset] varlığını test etmek olmadığını açıkça belirtiriz: ```latte {ifset block $name} @@ -405,71 +396,69 @@ PHP'de blok adı olarak bir değişken veya herhangi bir ifade kullanabilirsiniz {/ifset} ``` +Blokların varlığı ayrıca [`hasBlock()` |functions#hasBlock] fonksiyonu tarafından da doğrulanır: -İpuçları .[#toc-tips] ---------------------- -İşte bloklarla çalışmak için bazı ipuçları: +```latte +{if hasBlock(header) || hasBlock(footer)} + ... +{/if} +``` -- Son üst düzey bloğun kapanış etiketine sahip olması gerekmez (blok belgenin sonuyla biter). Bu, bir ana blok olan alt şablonların yazılmasını kolaylaştırır. -- Daha fazla okunabilirlik için, isteğe bağlı olarak `{/block}` etiketinize bir ad verebilirsiniz, örneğin `{/block footer}`. Ancak, ad blok adıyla eşleşmelidir. Daha büyük şablonlarda bu teknik, hangi blok etiketlerinin kapatıldığını görmenize yardımcı olur. +İpuçları +-------- +Bloklarla çalışmak için birkaç ipucu: -- Aynı şablonda aynı ada sahip birden fazla blok etiketini doğrudan tanımlayamazsınız. Ancak [dinamik blok adları |#dynamic block names] kullanılarak bu sağlanabilir. +- Son en üst düzey bloğun bir kapanış etiketi olması gerekmez (blok belgenin sonunda biter). Bu, tek bir birincil blok içeren alt şablonların yazılmasını basitleştirir. -- gibi blokları tanımlamak için [n:attributes |syntax#n:attributes] kullanabilirsiniz `

                                                                                                                                Welcome to my awesome homepage

                                                                                                                                ` +- Daha iyi okunabilirlik için, blok adını `{/block}` etiketinde belirtebilirsiniz, örneğin `{/block footer}`. Ancak, ad blok adıyla eşleşmelidir. Daha büyük şablonlarda, bu teknik hangi blok etiketlerinin kapandığını görmenize yardımcı olur. -- Bloklar, yalnızca [filtreleri |syntax#filters] çıktıya uygulamak için isimsiz olarak da kullanılabilir: `{block|strip} hello {/block}` +- Aynı şablonda aynı ada sahip birden çok blok etiketini doğrudan tanımlayamazsınız. Ancak, bu [#dinamik blok adları] kullanılarak başarılabilir. +- Blokları tanımlamak için [n:nitelikleri |syntax#n:nitelikler] kullanabilirsiniz, örneğin `

                                                                                                                                Welcome to my awesome homepage

                                                                                                                                ` -Yatay Yeniden Kullanım `{import}` .{toc: Horizontal Reuse} -========================================================== +- Bloklar ayrıca yalnızca [filtreleri |syntax#Filtreler] kullanmak için adsız olarak da kullanılabilir: `{block|strip} hello {/block}` -Yatay yeniden kullanım, Latte'deki üçüncü bir yeniden kullanılabilirlik ve kalıtım mekanizmasıdır. Diğer şablonlardan bloklar yüklemenizi sağlar. Yardımcı fonksiyonlar veya bir özellik içeren bir PHP dosyası oluşturmaya benzer. -Düzen şablonu kalıtımı Latte'nin en güçlü özelliklerinden biri olmasına rağmen, tek kalıtımla sınırlıdır; bir şablon yalnızca bir başka şablonu genişletebilir. Bu sınırlama, şablon kalıtımının anlaşılmasını ve hata ayıklamasını kolaylaştırır: +Yatay Yeniden Kullanım `{import}` .{toc: Yatay Yeniden Kullanım} +================================================================ -```latte -{layout 'layout.latte'} +Yatay yeniden kullanım, Latte'deki yeniden kullanım ve kalıtım için üçüncü mekanizmadır. Diğer şablonlardan blokları yüklemenizi sağlar. Bu, PHP'de yardımcı fonksiyonlar içeren bir dosya oluşturup ardından `require` ile yüklemeye benzer. -{block title}...{/block} -{block content}...{/block} -``` +Şablon düzeni kalıtımı Latte'nin en güçlü özelliklerinden biri olsa da, basit kalıtımla sınırlıdır - bir şablon yalnızca başka bir şablonu genişletebilir. Yatay yeniden kullanım, çoklu kalıtım elde etmenin bir yoludur. -Yatay yeniden kullanım, çoklu kalıtım ile aynı hedefe ulaşmanın bir yoludur, ancak ilişkili karmaşıklık olmadan: +Blok tanımları içeren bir dosyamız olsun: -```latte -{layout 'layout.latte'} - -{import 'blocks.latte'} +```latte .{file: blocks.latte} +{block sidebar}...{/block} -{block title}...{/block} -{block content}...{/block} +{block menu}...{/block} ``` -`{import}` deyimi Latte'ye `blocks.latte` adresinde tanımlanan tüm blokları ve [tanımları |#definitions] geçerli şablona aktarmasını söyler. +`{import}` komutunu kullanarak, `blocks.latte`'de tanımlanan tüm blokları ve [#Tanımlar] başka bir şablona aktarırız: -```latte -{* blocks.latte *} +```latte .{file: child.latte} +{import 'blocks.latte'} -{block sidebar}...{/block} +{* şimdi sidebar ve menu blokları kullanılabilir *} ``` -Bu örnekte, `{import}` deyimi `sidebar` bloğunu ana şablona aktarır. +Blokları üst şablonda içe aktarırsanız (yani `{import}`'u `layout.latte`'de kullanırsanız), bloklar tüm alt şablonlarda da kullanılabilir olacaktır, bu çok pratiktir. -İçe aktarılan şablon başka bir şablonu [genişletmemeli |#Layout Inheritance] ve gövdesi boş olmalıdır. Ancak, içe aktarılan şablon diğer şablonları içe aktarabilir. +İçe aktarılacak şablon (örneğin `blocks.latte`), başka bir şablonu [genişletmemelidir |#Düzen Kalıtımı], yani `{layout}` kullanmamalıdır. Ancak, diğer şablonları içe aktarabilir. -`{import}` etiketi, `{layout}` adresinden sonraki ilk şablon etiketi olmalıdır. Şablon adı herhangi bir PHP ifadesi olabilir: +`{import}` etiketi, `{layout}`'tan sonraki ilk şablon etiketi olmalıdır. Şablon adı herhangi bir PHP ifadesi olabilir: ```latte {import $ajax ? 'ajax.latte' : 'not-ajax.latte'} ``` -Herhangi bir şablonda istediğiniz kadar `{import}` ifadesi kullanabilirsiniz. İki içe aktarılan şablon aynı bloğu tanımlarsa, ilki kazanır. Ancak, en yüksek öncelik, içe aktarılan herhangi bir bloğun üzerine yazabilen ana şablona verilir. +Bir şablonda istediğiniz kadar `{import}` komutu kullanabilirsiniz. İki içe aktarılan şablon aynı bloğu tanımlarsa, ilki kazanır. Ancak, en yüksek öncelik ana şablondadır ve herhangi bir içe aktarılan bloğu geçersiz kılabilir. -Geçersiz kılınan tüm bloklar, [üst |#parent block] blok olarak eklenerek kademeli olarak dahil edilebilir: +Geçersiz kılınan blokların içeriği, bloğu [#üst blok] dahil edildiği gibi dahil ederek korunabilir: ```latte -{layout 'base.latte'} +{layout 'layout.latte'} {import 'blocks.latte'} @@ -481,17 +470,17 @@ Geçersiz kılınan tüm bloklar, [üst |#parent block] blok olarak eklenerek ka {block content}...{/block} ``` -Bu örnekte `{include parent}`, `blocks.latte` şablonundan `sidebar` bloğunu doğru şekilde çağıracaktır. +Bu örnekte, `{include parent}`, `blocks.latte` şablonundan `sidebar` bloğunu çağırır. -Birim Kalıtımı `{embed}` .{toc: Unit Inheritance}{data-version:2.9} -=================================================================== +Birim Kalıtımı `{embed}` .{toc: Birim Kalıtımı} +=============================================== -Birim kalıtımı, düzen kalıtımı fikrini içerik parçaları düzeyine taşır. Düzen kalıtımı, alt şablonlar tarafından hayata geçirilen "belge iskeletleri" ile çalışırken, birim kalıtımı daha küçük içerik birimleri için iskeletler oluşturmanıza ve bunları istediğiniz yerde yeniden kullanmanıza olanak tanır. +Birim kalıtımı, düzen kalıtımı fikrini içerik parçaları düzeyine genişletir. Düzen kalıtımı, alt şablonlar tarafından canlandırılan "belge iskeleti" ile çalışırken, birim kalıtımı daha küçük içerik birimleri için iskeletler oluşturmanıza ve bunları istediğiniz yerde yeniden kullanmanıza olanak tanır. -Birim kalıtımında `{embed}` etiketi anahtardır. `{include}` ve `{layout}` etiketlerinin davranışlarını birleştirir. `{include}` etiketinde olduğu gibi, başka bir şablonun veya bloğun içeriğini dahil etmenize ve isteğe bağlı olarak değişkenleri aktarmanıza olanak tanır. Ayrıca, `{layout}` 'un yaptığı gibi, dahil edilen şablonun içinde tanımlanan herhangi bir bloğu geçersiz kılmanıza da olanak tanır. +Birim kalıtımında anahtar `{embed}` etiketidir. `{include}` ve `{layout}` davranışlarını birleştirir. Başka bir şablonun veya bloğun içeriğini gömmenize ve isteğe bağlı olarak değişkenleri iletmenize olanak tanır, tıpkı `{include}` durumunda olduğu gibi. Ayrıca, `{layout}` kullanırken olduğu gibi, gömülü şablon içinde tanımlanan herhangi bir bloğu geçersiz kılmanıza da olanak tanır. -Örneğin biz katlanabilir akordeon elemanını kullanacağız. `collapsible.latte` şablonundaki eleman iskeletine bir göz atalım: +Örneğin, bir akordeon öğesi kullanacağız. `collapsible.latte` şablonunda saklanan öğenin iskeletine bakalım: ```latte
                                                                                                                                @@ -505,14 +494,14 @@ Birim kalıtımında `{embed}` etiketi anahtardır. `{include}` ve `{layout}` et
                                                                                                                                ``` -`{block}` etiketleri, alt şablonların doldurabileceği iki blok tanımlar. Evet, düzen kalıtım şablonundaki ana şablonda olduğu gibi. Ayrıca `$modifierClass` değişkenini de görüyorsunuz. +`{block}` etiketleri, alt şablonların doldurabileceği iki blok tanımlar. Evet, düzen kalıtımındaki üst şablon durumunda olduğu gibi. Ayrıca `$modifierClass` değişkenini de görüyorsunuz. -Elemanımızı şablon içinde kullanalım. İşte burada `{embed}` devreye giriyor. Her şeyi yapmamıza izin veren süper güçlü bir kit parçasıdır: öğenin şablon içeriğini dahil edin, ona değişkenler ekleyin ve ona özel HTML içeren bloklar ekleyin: +Öğemizi bir şablonda kullanalım. İşte burada `{embed}` devreye giriyor. Bu, bize her şeyi yapmamızı sağlayan son derece güçlü bir etikettir: öğe şablonunun içeriğini gömmek, ona değişkenler eklemek ve ona kendi HTML'mizle bloklar eklemek: ```latte {embed 'collapsible.latte', modifierClass: my-style} {block title} - Hello World + Merhaba Dünya {/block} {block content} @@ -522,12 +511,12 @@ Elemanımızı şablon içinde kullanalım. İşte burada `{embed}` devreye giri {/embed} ``` -Çıktı aşağıdaki gibi görünebilir: +Çıktı şöyle görünebilir: ```latte

                                                                                                                                - Hello World + Merhaba Dünya

                                                                                                                                @@ -537,7 +526,7 @@ Elemanımızı şablon içinde kullanalım. İşte burada `{embed}` devreye giri
                                                                                                                                ``` -Gömme etiketlerinin içindeki bloklar diğer bloklardan bağımsız ayrı bir katman oluşturur. Bu nedenle, gömme dışındaki blokla aynı ada sahip olabilirler ve hiçbir şekilde etkilenmezler. `{embed}` etiketleri içindeki [include |#Printing Blocks] etiketini kullanarak burada oluşturulan blokları, gömülü şablondan ( [yerel |#Local Blocks] olmayan) blokları ve ayrıca ana şablondan [*yerel* |#Local Blocks] olan blokları ekleyebilirsiniz. Blokları başka dosyalardan da [içe aktarabilirsiniz |#Horizontal Reuse]: +Gömülü etiketlerin içindeki bloklar, diğer bloklardan bağımsız ayrı bir katman oluşturur. Bu nedenle, gömme dışındaki bir blokla aynı ada sahip olabilirler ve hiçbir şekilde etkilenmezler. `{embed}` etiketlerinin içinde [include |#Blokları İşleme] etiketini kullanarak, burada oluşturulan blokları, gömülü şablondaki blokları (*değil* [yerel |#Yerel Bloklar]) ve ayrıca ana şablondaki *olan* yerel blokları dahil edebilirsiniz. Ayrıca diğer dosyalardan [blokları içe aktarabilirsiniz |#Yatay Yeniden Kullanım]: ```latte {block outer}…{/block} @@ -549,18 +538,18 @@ Gömme etiketlerinin içindeki bloklar diğer bloklardan bağımsız ayrı bir k {block inner}…{/block} {block title} - {include inner} {* çalışır, blok gömme içinde tanımlanır *} - {include hello} {* çalışır, blok bu şablonda yereldir *} - {include content} {* çalışır, blok gömülü şablonda tanımlanmıştır *} + {include inner} {* çalışır, blok embed içinde tanımlandı *} + {include hello} {* çalışır, blok bu şablonda yerel *} + {include content} {* çalışır, blok gömülü şablonda tanımlandı *} {include aBlockDefinedInImportedTemplate} {* çalışır *} - {include outer} {* çalışmıyor! - blok dış katmanda *} + {include outer} {* çalışmaz! - blok dış katmanda *} {/block} {/embed} ``` -Gömülü şablonların etkin bağlamın değişkenlerine erişimi yoktur, ancak küresel değişkenlere erişimleri vardır. +Gömülü şablonlar, etkin bağlamın değişkenlerine erişemez, ancak genel değişkenlere erişebilir. -`{embed}` ile sadece şablonları değil diğer blokları da ekleyebilirsiniz, bu nedenle önceki örnek şu şekilde yazılabilir: .{data-version:2.10} +`{embed}` kullanarak yalnızca şablonları değil, diğer blokları da gömebiliriz ve bu nedenle önceki örnek şu şekilde yazılabilir: ```latte {define collapsible} @@ -575,36 +564,36 @@ Gömülü şablonların etkin bağlamın değişkenlerine erişimi yoktur, ancak {embed collapsible, modifierClass: my-style} {block title} - Hello World + Merhaba Dünya {/block} ... {/embed} ``` -`{embed}` adresine bir ifade iletirsek ve bunun bir blok mu yoksa dosya adı mı olduğu net değilse, `block` veya `file` anahtar sözcüğünü ekleyin: +`{embed}`'e bir ifade iletirsek ve bunun bir blok adı mı yoksa dosya adı mı olduğu açık değilse, `block` veya `file` anahtar kelimesini ekleriz: ```latte {embed block $name} ... {/embed} ``` -Kullanım Örnekleri .[#toc-use-cases] -==================================== +Kullanım Durumları +================== -Latte'de çeşitli kalıtım ve kod yeniden kullanımı türleri vardır. Daha fazla açıklık için ana kavramları özetleyelim: +Latte'de farklı kalıtım ve kod yeniden kullanım türleri vardır. Daha fazla netlik için ana kavramları özetleyelim: `{include template}` -------------------- -**Kullanım Örneği:** `layout.latte` içinde `header.latte` & `footer.latte` kullanılması. +**Kullanım durumu**: `layout.latte` içinde `header.latte` ve `footer.latte` kullanma. `header.latte` ```latte ``` @@ -612,7 +601,7 @@ Latte'de çeşitli kalıtım ve kod yeniden kullanımı türleri vardır. Daha f ```latte
                                                                                                                                -
                                                                                                                                Copyright
                                                                                                                                +
                                                                                                                                Telif Hakkı
                                                                                                                                ``` @@ -630,7 +619,7 @@ Latte'de çeşitli kalıtım ve kod yeniden kullanımı türleri vardır. Daha f `{layout}` ---------- -**Kullanım Örneği**: `layout.latte` 'u `homepage.latte` & `about.latte` içinde genişletme. +**Kullanım durumu**: `homepage.latte` ve `about.latte` içinde `layout.latte`'yi genişletme. `layout.latte` @@ -648,7 +637,7 @@ Latte'de çeşitli kalıtım ve kod yeniden kullanımı türleri vardır. Daha f {layout 'layout.latte'} {block main} -

                                                                                                                                Homepage

                                                                                                                                +

                                                                                                                                Ana Sayfa

                                                                                                                                {/block} ``` @@ -658,7 +647,7 @@ Latte'de çeşitli kalıtım ve kod yeniden kullanımı türleri vardır. Daha f {layout 'layout.latte'} {block main} -

                                                                                                                                About page

                                                                                                                                +

                                                                                                                                Hakkında Sayfası

                                                                                                                                {/block} ``` @@ -666,12 +655,12 @@ Latte'de çeşitli kalıtım ve kod yeniden kullanımı türleri vardır. Daha f `{import}` ---------- -**Kullanım Örneği**: `sidebar.latte` içinde `single.product.latte` & `single.service.latte`. +**Kullanım durumu**: `single.product.latte` ve `single.service.latte` içinde `sidebar.latte`. `sidebar.latte` ```latte -{block sidebar}{/block} +{block sidebar}{/block} ``` `single.product.latte` @@ -681,7 +670,7 @@ Latte'de çeşitli kalıtım ve kod yeniden kullanımı türleri vardır. Daha f {import 'sidebar.latte'} -{block main}
                                                                                                                                Product page
                                                                                                                                {/block} +{block main}
                                                                                                                                Ürün sayfası
                                                                                                                                {/block} ``` `single.service.latte` @@ -691,14 +680,14 @@ Latte'de çeşitli kalıtım ve kod yeniden kullanımı türleri vardır. Daha f {import 'sidebar.latte'} -{block main}
                                                                                                                                Service page
                                                                                                                                {/block} +{block main}
                                                                                                                                Hizmet sayfası
                                                                                                                                {/block} ``` `{define}` ---------- -**Kullanım Örneği**: Bazı değişkenleri alan ve bazı biçimlendirmeleri çıktı olarak veren bir işlev. +**Kullanım durumu**: Değişkenleri ilettiğimiz ve bir şeyler işleyen fonksiyonlar. `form.latte` @@ -716,7 +705,7 @@ Latte'de çeşitli kalıtım ve kod yeniden kullanımı türleri vardır. Daha f
                                                                                                                                {include form-input, username}
                                                                                                                                {include form-input, password}
                                                                                                                                -
                                                                                                                                {include form-input, submit, Submit, submit}
                                                                                                                                +
                                                                                                                                {include form-input, submit, Gönder, submit}
                                                                                                                                ``` @@ -724,7 +713,7 @@ Latte'de çeşitli kalıtım ve kod yeniden kullanımı türleri vardır. Daha f `{embed}` --------- -**Kullanım Örneği**: `pagination.latte` adresini `product.table.latte` & `service.table.latte` adreslerine gömme. +**Kullanım durumu**: `product.table.latte` ve `service.table.latte` içine `pagination.latte` gömme. `pagination.latte` @@ -744,8 +733,8 @@ Latte'de çeşitli kalıtım ve kod yeniden kullanımı türleri vardır. Daha f ```latte {embed 'pagination.latte', min: 1, max: $products->count} - {block first}First Product Page{/block} - {block last}Last Product Page{/block} + {block first}İlk Ürün Sayfası{/block} + {block last}Son Ürün Sayfası{/block} {/embed} ``` @@ -753,7 +742,7 @@ Latte'de çeşitli kalıtım ve kod yeniden kullanımı türleri vardır. Daha f ```latte {embed 'pagination.latte', min: 1, max: $services->count} - {block first}First Service Page{/block} - {block last}Last Service Page{/block} + {block first}İlk Hizmet Sayfası{/block} + {block last}Son Hizmet Sayfası{/block} {/embed} ``` diff --git a/latte/tr/type-system.texy b/latte/tr/type-system.texy index dc84504e45..eaf4424ee5 100644 --- a/latte/tr/type-system.texy +++ b/latte/tr/type-system.texy @@ -1,27 +1,27 @@ -Tip Sistem -********** +Tip Sistemi +*********** -
                                                                                                                                +
                                                                                                                                -Tip sistemi, sağlam uygulamaların geliştirilmesi için ana unsurdur. Latte şablonlara tip desteği getiriyor. Her değişkenin hangi veri veya nesne türünde olduğunu bilmek +Tip sistemi, sağlam uygulamalar geliştirmek için anahtardır. Latte, şablonlara da tip desteği getirir. Her değişkende hangi veri veya nesne tipinin olduğunu bildiğimiz için, -- IDE'nin otomatik tamamlamayı doğru şekilde yapması (bkz. [entegrasyon ve eklentiler |recipes#Editors and IDE]) -- hataları tespit etmek için statik analiz +- IDE doğru şekilde önerilerde bulunabilir (bkz. [entegrasyon |recipes#Düzenleyiciler ve IDE ler]) +- statik analiz hataları ortaya çıkarabilir -Gelişimin kalitesini ve rahatlığını önemli ölçüde artıran iki nokta. +Her ikisi de geliştirme kalitesini ve rahatlığını önemli ölçüde artırır.
                                                                                                                                .[note] -Beyan edilen tipler bilgilendiricidir ve Latte şu anda bunları kontrol etmemektedir. +Bildirilen tipler bilgilendiricidir ve Latte şu anda bunları kontrol etmemektedir. -Türleri kullanmaya nasıl başlanır? İletilen parametreleri temsil eden bir şablon sınıf oluşturun, örneğin `CatalogTemplateParameters`: +Tipleri kullanmaya nasıl başlanır? İletilen parametreleri, tiplerini ve muhtemelen varsayılan değerlerini temsil eden bir şablon sınıfı oluşturun, örneğin `CatalogTemplateParameters`: ```php class CatalogTemplateParameters { public function __construct( - public string $langs, + public string $lang, /** @var ProductEntity[] */ public array $products, public Address $address, @@ -35,19 +35,16 @@ $latte->render('template.latte', new CatalogTemplateParameters( )); ``` -Ardından, şablonun başına tam sınıf adını (ad alanı dahil) içeren `{templateType}` etiketini ekleyin. Bu, şablonda ilgili türler de dahil olmak üzere `$langs` ve `$products` değişkenleri olduğunu tanımlar. -Yerel değişkenlerin türlerini etiketleri kullanarak da belirtebilirsiniz [`{var}` |tags#var-default], `{varType}` ve [`{define}` |template-inheritance#definitions]. +Ve ardından şablonun başına `{templateType}` etiketini sınıfın tam adıyla (ad alanı dahil) ekleyin. Bu, şablonda `$lang` ve `$products` değişkenlerinin ilgili tipleriyle birlikte bulunduğunu tanımlar. Yerel değişkenlerin tiplerini [`{var}` |tags#var default], `{varType}`, [`{define}` |template-inheritance#Tanımlar] etiketlerini kullanarak belirtebilirsiniz. -Artık IDE doğru şekilde otomatik tamamlama yapabilir. +O andan itibaren IDE size doğru önerilerde bulunabilir. -İş nasıl kaydedilir? Bir şablon sınıfı veya `{varType}` etiketleri mümkün olduğunca kolay nasıl yazılır? Bunların oluşturulmasını sağlayın. -`{templatePrint}` ve `{varPrint}` etiket çiftleri tam olarak bunu yapar. -Bu etiketlerden birini bir şablona yerleştirirseniz, normal oluşturma yerine sınıfın veya şablonun kodu görüntülenir. Daha sonra kodu seçip projenize kopyalamanız yeterlidir. +İşinizi nasıl kolaylaştırırsınız? Şablon parametreleri veya `{varType}` etiketleri içeren bir sınıfı en kolay nasıl yazarsınız? Oluşturulmalarını sağlayın. Bunun için `{templatePrint}` ve `{varPrint}` etiket çifti vardır. Bunları bir şablona yerleştirirseniz, normal işleme yerine sınıfın kod önerisi veya `{varType}` etiketlerinin listesi görüntülenir. Ardından kodu tek bir tıklamayla işaretleyip projenize kopyalamanız yeterlidir. `{templateType}` ---------------- -Şablona aktarılan parametrelerin türleri class kullanılarak bildirilir: +Şablona iletilen parametrelerin tiplerini bir sınıf kullanarak bildiririz: ```latte {templateType MyApp\CatalogTemplateParameters} @@ -56,7 +53,7 @@ Bu etiketlerden birini bir şablona yerleştirirseniz, normal oluşturma yerine `{varType}` ----------- -Değişken türleri nasıl bildirilir? Bu amaçla mevcut bir değişken için `{varType}` etiketini kullanın veya [`{var}` |tags#var-default]: +Değişkenlerin tipleri nasıl bildirilir? Bunun için mevcut değişkenler için `{varType}` etiketleri veya [`{var}` |tags#var default] kullanılır: ```latte {varType Nette\Security\User $user} @@ -66,11 +63,11 @@ Değişken türleri nasıl bildirilir? Bu amaçla mevcut bir değişken için `{ `{templatePrint}` ----------------- -Bu sınıfı `{templatePrint}` etiketini kullanarak da oluşturabilirsiniz. Bunu şablonun başına yerleştirirseniz, normal şablon yerine sınıfın kodu görüntülenir. Daha sonra kodu seçip projenize kopyalamanız yeterlidir. +Sınıfı `{templatePrint}` etiketini kullanarak da oluşturabilirsiniz. Bunu şablonun başına yerleştirirseniz, normal işleme yerine sınıfın önerisi görüntülenir. Ardından kodu tek bir tıklamayla işaretleyip projenize kopyalamanız yeterlidir. `{varPrint}` ------------ -`{varPrint}` etiketi size zaman kazandırır. Bir şablona yerleştirirseniz, normal oluşturma yerine `{varType}` etiketlerinin listesi görüntülenir. Ardından kodu seçip şablonunuza kopyalamanız yeterlidir. +`{varPrint}` etiketi size yazma zamanından tasarruf ettirir. Bunu şablona yerleştirirseniz, normal işleme yerine yerel değişkenler için `{varType}` etiketlerinin önerisi görüntülenir. Ardından kodu tek bir tıklamayla işaretleyip şablona kopyalamanız yeterlidir. -`{varPrint}` şablon parametreleri olmayan yerel değişkenleri listeler. Tüm değişkenleri listelemek istiyorsanız, `{varPrint all}` adresini kullanın. +`{varPrint}`'in kendisi yalnızca şablon parametreleri olmayan yerel değişkenleri yazdırır. Tüm değişkenleri yazdırmak istiyorsanız, `{varPrint all}` kullanın. diff --git a/latte/tr/why-use.texy b/latte/tr/why-use.texy new file mode 100644 index 0000000000..42a09457e0 --- /dev/null +++ b/latte/tr/why-use.texy @@ -0,0 +1,80 @@ +Neden Şablon Kullanmalı? +************************ + + +PHP'de Neden Bir Şablon Sistemi Kullanmalıyım? +---------------------------------------------- + +PHP kendisi bir şablon diliyken neden PHP'de bir şablon sistemi kullanmalıyım? + +Öncelikle, ilginç dönemeçlerle dolu bu dilin tarihini kısaca özetleyelim. HTML sayfaları oluşturmak için kullanılan ilk programlama dillerinden biri C diliydi. Ancak kısa sürede bu amaçla kullanımının pratik olmadığı anlaşıldı. Bu nedenle Rasmus Lerdorf, arka uçta C dili ile dinamik HTML oluşturmayı kolaylaştıran PHP'yi yarattı. Dolayısıyla PHP başlangıçta bir şablon dili olarak tasarlanmıştı, ancak zamanla ek özellikler kazandı ve tam teşekküllü bir programlama dili haline geldi. + +Yine de hala bir şablon dili olarak da çalışıyor. Bir PHP dosyasında, `` kullanılarak değişkenlerin yazdırıldığı vb. bir HTML sayfası yazılabilir. + +PHP tarihinin başlarında, görünümü (HTML/CSS) uygulama mantığından kesin olarak ayırma amacı taşıyan Smarty şablon sistemi ortaya çıktı. Yani, geliştiricinin örneğin bir şablondan veritabanı sorgusu yapmasını vb. engellemek için kasıtlı olarak PHP'nin kendisinden daha sınırlı bir dil sağladı. Öte yandan, projelerde ek bir bağımlılık oluşturdu, karmaşıklıklarını artırdı ve programcıların yeni bir Smarty dili öğrenmesi gerekiyordu. Böyle bir fayda tartışmalıydı ve şablonlar için basit PHP kullanılmaya devam edildi. + +Zamanla şablon sistemleri kullanışlı hale gelmeye başladı. Saf PHP'ye kıyasla şablon oluşturmayı önemli ölçüde basitleştiren [kalıtım |template-inheritance], [sandbox modu |sandbox] kavramı ve bir dizi başka özellikle geldiler. Güvenlik konusu, [XSS gibi güvenlik açıkları |safety-first]'nın varlığı ve [kaçış |#Kaçış Escaping Nedir] gerekliliği ön plana çıktı. Şablon sistemleri, programcının bunu unutması ve ciddi bir güvenlik açığı oluşması riskini ortadan kaldırmak için otomatik kaçış ile geldi (birazdan bunun bazı tuzakları olduğunu göstereceğiz). + +Günümüzde şablon sistemlerinin faydaları, dağıtımlarıyla ilişkili maliyetleri önemli ölçüde aşmaktadır. Bu nedenle bunları kullanmak mantıklıdır. + + +Neden Latte, Twig veya Blade'den Daha İyidir? +--------------------------------------------- + +Bunun birkaç nedeni var - bazıları hoş, bazıları ise temel olarak kullanışlı. Latte, hoş olanla kullanışlı olanın birleşimidir. + +*Önce hoş olan:* Latte, [PHP ile aynı sözdizimine |syntax#Latte PHP yi Anlar] sahiptir. Yalnızca etiketlerin yazımı farklıdır, `` yerine daha kısa olan `{` ve `}` tercih edilir. Bu, yeni bir dil öğrenmeniz gerekmediği anlamına gelir. Eğitim maliyetleri minimumdur. Ve en önemlisi, geliştirme sırasında sürekli olarak PHP dili ile şablon dili arasında "geçiş yapmak" zorunda kalmazsınız, çünkü ikisi de aynıdır. Python dilini kullanan Twig şablonlarının aksine, programcı iki farklı dil arasında geçiş yapmak zorundadır. + +*Ve şimdi son derece kullanışlı neden*: Twig, Blade veya Smarty gibi tüm şablon sistemleri, evrim sürecinde otomatik [kaçış |#Kaçış Escaping Nedir] şeklinde XSS'e karşı koruma ile geldi. Daha doğrusu, `htmlspecialchars()` fonksiyonunun otomatik çağrılması. Ancak Latte'nin yaratıcıları bunun hiç de doğru bir çözüm olmadığını fark ettiler. Çünkü belgenin farklı yerlerinde farklı şekillerde kaçış yapılır. Saf otomatik kaçış tehlikeli bir fonksiyondur, çünkü yanlış bir güvenlik hissi yaratır. + +Otomatik kaçışın işlevsel ve güvenilir olması için, verilerin belgenin hangi bölümünde yazdırıldığını (bunlara bağlam diyoruz) tanıması ve buna göre kaçış fonksiyonunu seçmesi gerekir. Yani, [bağlama duyarlı |safety-first#Bağlama Duyarlı Kaçış] olmalıdır. Ve işte Latte bunu yapabilir. HTML'yi anlar. Şablonu yalnızca bir karakter dizisi olarak algılamaz, etiketlerin, niteliklerin vb. ne olduğunu anlar. Ve bu nedenle HTML metninde farklı, HTML etiketi içinde farklı, JavaScript içinde farklı vb. şekilde kaçış yapar. + +Latte, PHP'de bağlama duyarlı kaçışa sahip ilk ve tek şablon sistemidir. Bu nedenle gerçekten güvenli tek şablon sistemini temsil eder. + +*Ve bir hoş neden daha*: Latte'nin HTML'yi anlaması sayesinde, başka çok hoş özellikler sunar. Örneğin, [n:nitelikleri |syntax#n:nitelikler]. Veya [bağlantıları kontrol etme |safety-first#Bağlantı Kontrolü] yeteneği. Ve daha birçokları. + + +Kaçış (Escaping) Nedir? +----------------------- + +Kaçış, istenmeyen olayları veya hataları önlemek için bir karakter dizisini diğerine eklerken özel anlamı olan karakterleri karşılık gelen dizilerle değiştirme işlemidir. Örneğin, `<` karakterinin özel bir anlamı olduğu (etiketin başlangıcını belirttiği için) bir HTML metnine bir karakter dizisi eklediğimizde, onu karşılık gelen diziyle, yani HTML varlığı `<` ile değiştiririz. Bu sayede tarayıcı `<` sembolünü doğru şekilde görüntüler. + +Doğrudan PHP'de kod yazarken kaçışın basit bir örneği, önüne ters eğik çizgi yazdığımız bir karakter dizisine tırnak işareti eklemektir. + +Kaçışı [XSS'e Karşı Nasıl Savunulur |safety-first#XSS ten Nasıl Korunulur] bölümünde daha ayrıntılı olarak ele alıyoruz. + + +Latte'de Şablondan Veritabanı Sorgusu Yapılabilir mi? +----------------------------------------------------- + +Şablonlarda, programcının onlara ilettiği nesnelerle çalışılabilir. Dolayısıyla, programcı isterse, şablona bir veritabanı nesnesi iletebilir ve üzerinde bir sorgu gerçekleştirebilir. Böyle bir niyeti varsa, onu engellemek için bir neden yoktur. + +Şablonları müşterilere veya harici kodlayıcılara düzenleme imkanı vermek istediğinizde durum farklıdır. Bu durumda, kesinlikle veritabanına erişmelerini istemezsiniz. Elbette şablona veritabanı nesnesini iletmezsiniz, ancak ya başka bir nesne aracılığıyla erişilebilirse? Çözüm, şablonlarda hangi metotların çağrılabileceğini tanımlamanıza olanak tanıyan [sandbox modu |sandbox]'dur. Bu sayede güvenlik ihlali konusunda endişelenmenize gerek kalmaz. + + +Latte, Twig ve Blade Gibi Şablon Sistemleri Arasındaki Temel Farklar Nelerdir? +------------------------------------------------------------------------------ + +Latte, Twig ve Blade şablon sistemleri arasındaki farklar temel olarak sözdizimi, güvenlik ve framework'lere entegrasyon şeklinde yatmaktadır + +- Latte: PHP dilinin sözdizimini kullanır, bu da öğrenmeyi ve kullanmayı kolaylaştırır. XSS saldırılarına karşı birinci sınıf koruma sağlar. +- Twig: PHP'den oldukça farklı olan Python dilinin sözdizimini kullanır. Bağlamı ayırt etmeden kaçış yapar. Symfony framework'ü ile iyi entegre edilmiştir. +- Blade: PHP ve kendi sözdiziminin bir karışımını kullanır. Bağlamı ayırt etmeden kaçış yapar. Laravel fonksiyonları ve ekosistemi ile sıkı bir şekilde entegre edilmiştir. + + +Şirketler İçin Şablon Sistemi Kullanmak Mantıklı mı? +---------------------------------------------------- + +Öncelikle, eğitim, kullanım ve genel fayda ile ilişkili maliyetler sisteme göre önemli ölçüde farklılık gösterir. Latte şablon sistemi, PHP sözdizimini kullandığı için, bu dile zaten aşina olan programcılar için öğrenmeyi büyük ölçüde basitleştirir. Genellikle bir programcının Latte'yi yeterince tanıması birkaç saat sürer. Bu nedenle eğitim maliyetlerini düşürür. Aynı zamanda teknolojinin benimsenmesini ve özellikle günlük kullanımda verimliliği hızlandırır. + +Ayrıca Latte, benzersiz bağlama duyarlı kaçış teknolojisi sayesinde XSS güvenlik açığına karşı yüksek düzeyde koruma sağlar. Bu koruma, web uygulamalarının güvenliğini sağlamak ve kullanıcıları veya şirket verilerini tehlikeye atabilecek saldırı riskini en aza indirmek için anahtardır. Web uygulamalarının güvenliğini korumak, şirketin iyi itibarını korumak için de önemlidir. Güvenlik sorunları, müşteriler tarafında güven kaybına neden olabilir ve şirketin pazardaki itibarını zedeleyebilir. + +Latte kullanımı ayrıca, her ikisini de kolaylaştırarak uygulamanın genel geliştirme ve bakım maliyetlerini düşürür. Bu nedenle bir şablon sistemi kullanmak kesinlikle mantıklıdır. + + +Latte Web Uygulamalarının Performansını Etkiler mi? +--------------------------------------------------- + +Latte şablonları hızlı bir şekilde işlense de, bu yön aslında önemli değildir. Bunun nedeni, dosyaların ayrıştırılmasının yalnızca ilk görüntülemede bir kez gerçekleşmesidir. Ardından PHP koduna derlenirler, diske kaydedilirler ve her sonraki istekte yeniden derleme yapmaya gerek kalmadan çalıştırılırlar. + +Bu, üretim ortamında çalışma şeklidir. Geliştirme sırasında, geliştiricinin her zaman güncel sürümü görmesi için Latte şablonları içerikleri her değiştiğinde yeniden derlenir. diff --git a/latte/uk/@home.texy b/latte/uk/@home.texy index 9e93e4c93a..3e21ce3053 100644 --- a/latte/uk/@home.texy +++ b/latte/uk/@home.texy @@ -1 +1,2 @@ -{{maintitle: Latte - Найбезпечніші та по-справжньому інтуїтивні шаблони для PHP}} +{{maintitle: Latte – найбезпечніші та дійсно інтуїтивні шаблони для PHP}} +{{description: Latte — це найбезпечніша система шаблонів для PHP. Вона запобігає багатьом вразливостям безпеки. Ви оціните її інтуїтивно зрозумілий синтаксис та безліч корисних функцій.}} diff --git a/latte/uk/@left-menu.texy b/latte/uk/@left-menu.texy index 38cf9d16af..f8a8c8ca12 100644 --- a/latte/uk/@left-menu.texy +++ b/latte/uk/@left-menu.texy @@ -1,24 +1,24 @@ -- [Початок роботи |Guide] -- Концепції - - [Безпека понад усе |Safety First] - - Успадкування [шаблонів |Template Inheritance] - - [Система типів |Type System] - - [Пісочниця |Sandbox] +- [Починаємо з Latte |guide] +- [Чому варто використовувати шаблони? |why-use] +- Концепції ⚗️ + - [Безпека перш за все |safety-first] + - [Спадкування шаблонів |Template Inheritance] + - [Система типів |type-system] + - [Sandbox |Sandbox] -- Для дизайнерів - - [Синтаксис |Syntax] - - [Теги |Tags] - - [Фільтри |Filters] - - [Функції |Functions] +- Для дизайнерів 🎨 + - [Синтаксис |syntax] + - [Теги |tags] + - [Фільтри |filters] + - [Функції |functions] - [Поради та хитрощі |recipes] -- Для розробників - - [Практика для розробників |develop] - - [Розширення Latte |Extending Latte] - - [Створення розширення |creating-extension] +- Для розробників 🧮 + - [Практики розробки |develop] + - [Розширення Latte |extending-latte] -- [Куховарська книга |cookbook/@home] +- [Посібники та практики 💡|cookbook/@home] - [Міграція з Twig |cookbook/migration-from-twig] - - [... докладніше |cookbook/@home] + - [… інші |cookbook/@home] -- "Ігровий майданчик .[link-external]":https://fiddle.nette.org/latte/ .{padding-top:1em} +- "Майданчик .[link-external]":https://fiddle.nette.org/latte/ .{padding-top:1em} diff --git a/latte/uk/@menu.texy b/latte/uk/@menu.texy index dc3c46750f..4300fe4f02 100644 --- a/latte/uk/@menu.texy +++ b/latte/uk/@menu.texy @@ -1,12 +1,12 @@
                                                                                                                                  -- [Головна сторінка|@home] -- [Документація |Guide] +- [Вступ |@home] +- [Документація |guide] - "GitHub .[link-external]":https://github.com/nette/latte
                                                                                                                                diff --git a/latte/uk/@meta.texy b/latte/uk/@meta.texy new file mode 100644 index 0000000000..984f2b2309 --- /dev/null +++ b/latte/uk/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документація Latte}} diff --git a/latte/uk/compiler-passes.texy b/latte/uk/compiler-passes.texy new file mode 100644 index 0000000000..a23dd7d120 --- /dev/null +++ b/latte/uk/compiler-passes.texy @@ -0,0 +1,555 @@ +Проходи компіляції +****************** + +.[perex] +Проходи компіляції надають потужний механізм для аналізу та модифікації шаблонів Latte *після* їх розбору в абстрактне синтаксичне дерево (AST) і *перед* генерацією фінального PHP-коду. Це дозволяє здійснювати розширену маніпуляцію шаблонами, оптимізацію, перевірки безпеки (наприклад, Sandbox) та збір інформації про шаблони. Цей посібник проведе вас через створення власних проходів компіляції. + + +Що таке прохід компіляції? +========================== + +Для розуміння ролі проходів компіляції зверніться до [процесу компіляції Latte |custom-tags#Розуміння процесу компіляції]. Як ви можете бачити, проходи компіляції діють на ключовому етапі, дозволяючи глибоке втручання між початковим розбором та фінальним виведенням коду. + +По суті, прохід компіляції — це просто PHP-об'єкт, що викликається (наприклад, функція, статичний метод або метод екземпляра), який приймає один аргумент: кореневий вузол AST шаблону, який завжди є екземпляром `Latte\Compiler\Nodes\TemplateNode`. + +Основною метою проходу компіляції зазвичай є одна або обидві з наступних: + +- Аналіз: Проходити AST та збирати інформацію про шаблон (наприклад, знайти всі визначені блоки, перевірити використання специфічних тегів, забезпечити дотримання певних обмежень безпеки). +- Модифікація: Змінити структуру AST або атрибути вузлів (наприклад, автоматично додати HTML-атрибути, оптимізувати певні комбінації тегів, замінити застарілі теги новими, реалізувати правила sandbox). + + +Реєстрація +========== + +Проходи компіляції реєструються за допомогою методу [розширення |extending-latte#getPasses] `getPasses()`. Цей метод повертає асоціативний масив, де ключі є унікальними назвами проходів (використовуються внутрішньо та для сортування), а значення — це PHP-об'єкти, що викликаються, які реалізують логіку проходу. + +```php +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Extension; + +class MyExtension extends Extension +{ + public function getPasses(): array + { + return [ + 'modificationPass' => $this->modifyTemplateAst(...), + // ... інші проходи ... + ]; + } + + public function modifyTemplateAst(TemplateNode $templateNode): void + { + // Реалізація... + } +} +``` + +Проходи, зареєстровані базовими розширеннями Latte та вашими власними розширеннями, виконуються послідовно. Порядок може бути важливим, особливо якщо один прохід залежить від результатів або модифікацій іншого. Latte надає допоміжний механізм для контролю цього порядку, якщо це необхідно; див. документацію до [`Extension::getPasses()` |extending-latte#getPasses] для деталей. + + +Приклад AST +=========== + +Для кращого уявлення про AST, додаємо приклад. Це вихідний шаблон: + +```latte +{foreach $category->getItems() as $item} +
                                                                                                                              • {$item->name|upper}
                                                                                                                              • + {else} + no items found +{/foreach} +``` + +А це його представлення у вигляді AST: + +/--pre +Latte\Compiler\Nodes\TemplateNode( + Latte\Compiler\Nodes\FragmentNode( + - Latte\Essential\Nodes\ForeachNode( + expression: Latte\Compiler\Nodes\Php\Expression\MethodCallNode( + object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$category') + name: Latte\Compiler\Nodes\Php\IdentifierNode('getItems') + ) + value: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') + content: Latte\Compiler\Nodes\FragmentNode( + - Latte\Compiler\Nodes\TextNode(' ') + - Latte\Compiler\Nodes\Html\ElementNode('li')( + content: Latte\Essential\Nodes\PrintNode( + expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode( + object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') + name: Latte\Compiler\Nodes\Php\IdentifierNode('name') + ) + modifier: Latte\Compiler\Nodes\Php\ModifierNode( + filters: + - Latte\Compiler\Nodes\Php\FilterNode('upper') + ) + ) + ) + ) + else: Latte\Compiler\Nodes\FragmentNode( + - Latte\Compiler\Nodes\TextNode('no items found') + ) + ) + ) +) +\-- + + +Обхід AST за допомогою `NodeTraverser` +====================================== + +Ручне написання рекурсивних функцій для обходу складної структури AST є втомливим і схильним до помилок. Latte надає спеціальний інструмент для цієї мети: [api:Latte\Compiler\NodeTraverser]. Цей клас реалізує [патерн проектування Visitor |https://en.wikipedia.org/wiki/Visitor_pattern], завдяки якому обхід AST стає систематичним та легко керованим. + +Базове використання включає створення екземпляра `NodeTraverser` та виклик його методу `traverse()`, передаючи кореневий вузол AST та один або два "visitor" об'єкти, що викликаються: + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; + +(new NodeTraverser)->traverse( + $templateNode, + + // 'enter' visitor: Викликається при вході до вузла (перед його дочірніми вузлами) + enter: function (Node $node) { + echo "Вхід до вузла типу: " . $node::class . "\n"; + // Тут ви можете досліджувати вузол + if ($node instanceof Nodes\TextNode) { + // echo "Знайдено текст: " . $node->content . "\n"; + } + }, + + // 'leave' visitor: Викликається при виході з вузла (після його дочірніх вузлів) + leave: function (Node $node) { + echo "Вихід з вузла типу: " . $node::class . "\n"; + // Тут ви можете виконувати дії після обробки дочірніх вузлів + }, +); +``` + +Ви можете надати лише `enter` visitor, лише `leave` visitor, або обидва, залежно від ваших потреб. + +**`enter(Node $node)`:** Ця функція виконується для кожного вузла **перед** тим, як обхідник відвідає будь-яких дочірніх вузлів цього вузла. Вона корисна для: + +- Збору інформації під час обходу деревом вниз. +- Прийняття рішень *перед* обробкою дочірніх вузлів (наприклад, рішення про їх пропуск, див. [#Оптимізація обходу]). +- Потенційних змін вузла перед відвідуванням дочірніх вузлів (менш поширене). + +**`leave(Node $node)`:** Ця функція виконується для кожного вузла **після** того, як усі його дочірні вузли (та їхні цілі піддерева) були повністю відвідані (як вхід, так і вихід). Це найпоширеніше місце для: + +Обидва візитори `enter` та `leave` можуть опціонально повертати значення для впливу на процес обходу. Повернення `null` (або нічого) продовжує обхід нормально, повернення екземпляра `Node` замінює поточний вузол, а повернення спеціальних констант, таких як `NodeTraverser::RemoveNode` або `NodeTraverser::StopTraversal`, змінює потік, як пояснено в наступних розділах. + + +Як працює обхід +--------------- + +`NodeTraverser` внутрішньо використовує метод `getIterator()`, який повинен реалізувати кожен клас `Node` (як обговорювалося в [Створення власних тегів |custom-tags#Реалізація getIterator для підвузлів]). Він ітерує по дочірніх вузлах, отриманих за допомогою `getIterator()`, рекурсивно викликає `traverse()` на них і забезпечує, що `enter` та `leave` візитори викликаються в правильному порядку обходу в глибину для кожного вузла в дереві, доступного через ітератори. Це знову підкреслює, чому правильно реалізований `getIterator()` у ваших власних вузлах тегів є абсолютно необхідним для правильної роботи проходів компіляції. + +Давайте напишемо простий прохід, який підраховує, скільки разів у шаблоні використано тег `{do}` (представлений `Latte\Essential\Nodes\DoNode`). + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Essential\Nodes\DoNode; + +function countDoTags(TemplateNode $templateNode): void +{ + $count = 0; + (new NodeTraverser)->traverse( + $templateNode, + enter: function (Node $node) use (&$count): void { + if ($node instanceof DoNode) { + $count++; + } + }, + // 'leave' visitor не потрібен для цього завдання + ); + + echo "Знайдено тег {do} $count разів.\n"; +} + +$latte = new Latte\Engine; +$ast = $latte->parse($templateSource); +countDoTags($ast); +``` + +У цьому прикладі нам знадобився лише visitor `enter` для перевірки типу кожного відвіданого вузла. + +Далі ми дослідимо, як ці візитори фактично модифікують AST. + + +Модифікація AST +=============== + +Однією з основних цілей проходів компіляції є модифікація абстрактного синтаксичного дерева. Це дозволяє здійснювати потужні перетворення, оптимізації або застосування правил безпосередньо до структури шаблону перед генерацією PHP-коду. `NodeTraverser` надає кілька способів досягнення цього в рамках візиторів `enter` та `leave`. + +**Важлива примітка:** Модифікація AST вимагає обережності. Неправильні зміни — такі як видалення основних вузлів або заміна вузла несумісним типом — можуть призвести до помилок під час генерації коду або спричинити неочікувану поведінку під час виконання програми. Завжди ретельно тестуйте свої модифікаційні проходи. + + +Зміна властивостей вузлів +------------------------- + +Найпростіший спосіб модифікувати дерево — це пряма зміна **публічних властивостей** вузлів, відвіданих під час обходу. Всі вузли зберігають свої розібрані аргументи, вміст або атрибути у публічних властивостях. + +**Приклад:** Створимо прохід, який знаходить усі статичні текстові вузли (`TextNode`, що представляють звичайний HTML або текст поза тегами Latte) і перетворює їхній вміст на великі літери *безпосередньо в AST*. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\TextNode; + +function uppercaseStaticText(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // Ми можемо використовувати 'enter', оскільки TextNode не має дочірніх вузлів для обробки + enter: function (Node $node) { + // Чи є цей вузол статичним текстовим блоком? + if ($node instanceof TextNode) { + // Так! Безпосередньо змінюємо його публічну властивість 'content'. + $node->content = mb_strtoupper(html_entity_decode($node->content)); + } + // Не потрібно нічого повертати; зміна застосовується безпосередньо. + }, + ); +} +``` + +У цьому прикладі visitor `enter` перевіряє, чи є поточний `$node` типу `TextNode`. Якщо так, ми безпосередньо оновлюємо його публічну властивість `$content` за допомогою `mb_strtoupper()`. Це безпосередньо змінює вміст статичного тексту, збереженого в AST *перед* генерацією PHP-коду. Оскільки ми модифікуємо об'єкт безпосередньо, нам не потрібно нічого повертати з візитора. + +Ефект: Якщо шаблон містив `

                                                                                                                                Hello

                                                                                                                                {= $var }World`, після цього проходу AST буде представляти щось на зразок: `

                                                                                                                                HELLO

                                                                                                                                {= $var }WORLD`. Це НЕ ВПЛИНЕ на вміст $var. + + +Заміна вузлів +------------- + +Більш потужною технікою модифікації є повна заміна вузла іншим. Це робиться **поверненням нового екземпляра `Node`** з візитора `enter` або `leave`. `NodeTraverser` потім замінює початковий вузол повернутим у структурі батьківського вузла. + +**Приклад:** Створимо прохід, який знаходить усі використання константи `PHP_VERSION` (представлені `ConstantFetchNode`) і замінює їх безпосередньо рядковим літералом (`StringNode`), що містить *фактичну* версію PHP, виявлену *під час компіляції*. Це форма оптимізації на етапі компіляції. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\Php\Expression\ConstantFetchNode; +use Latte\Compiler\Nodes\Php\Scalar\StringNode; + +function inlinePhpVersion(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // 'leave' часто використовується для заміни, забезпечуючи, що дочірні вузли (якщо є) + // обробляються першими, хоча тут також спрацював би 'enter'. + leave: function (Node $node) { + // Чи є цей вузол доступом до константи і чи ім'я константи 'PHP_VERSION'? + if ($node instanceof ConstantFetchNode && (string) $node->name === 'PHP_VERSION') { + // Створюємо новий StringNode, що містить поточну версію PHP + $newNode = new StringNode(PHP_VERSION); + + // Необов'язково, але хороша практика: скопіюємо інформацію про позицію + $newNode->position = $node->position; + + // Повертаємо новий StringNode. Traverser замінить + // початковий ConstantFetchNode цим $newNode. + return $newNode; + } + // Якщо ми не повертаємо Node, початковий $node зберігається. + }, + ); +} +``` + +Тут visitor `leave` ідентифікує специфічний `ConstantFetchNode` для `PHP_VERSION`. Потім він створює абсолютно новий `StringNode`, що містить значення константи `PHP_VERSION` *на момент компіляції*. Повертаючи цей `$newNode`, він повідомляє обхіднику замінити початковий `ConstantFetchNode` в AST. + +Ефект: Якщо шаблон містив `{= PHP_VERSION }` і компіляція виконується на PHP 8.2.1, AST після цього проходу буде ефективно представляти `{= '8.2.1' }`. + +**Вибір `enter` проти `leave` для заміни:** + +- Використовуйте `leave`, якщо створення нового вузла залежить від результатів обробки дочірніх вузлів старого вузла, або якщо ви просто хочете переконатися, що дочірні вузли відвідані перед заміною (поширена практика). +- Використовуйте `enter`, якщо ви хочете замінити вузол *перед* тим, як його дочірні вузли взагалі будуть відвідані. + + +Видалення вузлів +---------------- + +Ви можете повністю видалити вузол з AST, повернувши спеціальну константу `NodeTraverser::RemoveNode` з візитора. + +**Приклад:** Видалимо всі коментарі шаблону (`{* ... *}`), які представлені `CommentNode` в AST, згенерованому ядром Latte (хоча зазвичай вони обробляються раніше, це служить прикладом). + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Compiler\Nodes\CommentNode; + +function removeCommentNodes(TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // 'enter' тут підходить, оскільки нам не потрібна інформація про дочірні вузли для видалення коментаря + enter: function (Node $node) { + if ($node instanceof CommentNode) { + // Сигналізуємо обхіднику видалити цей вузол з AST + return NodeTraverser::RemoveNode; + } + }, + ); +} +``` + +**Застереження:** Використовуйте `RemoveNode` обережно. Видалення вузла, який містить основний вміст або впливає на структуру (наприклад, видалення вузла вмісту циклу), може призвести до пошкоджених шаблонів або недійсного згенерованого коду. Найбезпечніше це робити для вузлів, які дійсно є необов'язковими або самодостатніми (як коментарі або теги налагодження) або для порожніх структурних вузлів (наприклад, порожній `FragmentNode` може бути безпечно видалений у деяких контекстах проходом для очищення). + +Ці три методи — зміна властивостей, заміна вузлів та видалення вузлів — надають основні інструменти для маніпуляції AST у межах ваших проходів компіляції. + + +Оптимізація обходу +================== + +AST шаблонів може бути досить великим, потенційно містячи тисячі вузлів. Обхід кожного окремого вузла може бути зайвим і вплинути на швидкість компіляції, якщо ваш прохід цікавиться лише специфічними частинами дерева. `NodeTraverser` пропонує способи оптимізації обходу: + + +Пропуск дочірніх вузлів +----------------------- + +Якщо ви знаєте, що як тільки ви натрапите на певний тип вузла, жоден з його нащадків не може містити вузли, які ви шукаєте, ви можете вказати обхіднику пропустити відвідування його дочірніх вузлів. Це робиться поверненням константи `NodeTraverser::DontTraverseChildren` з візитора **`enter`**. Це дозволяє пропустити цілі гілки під час обходу, потенційно заощаджуючи значний час, особливо в шаблонах зі складними PHP-виразами всередині тегів. + + +Зупинка обходу +-------------- + +Якщо ваш прохід потребує знайти лише *перше* входження чогось (специфічний тип вузла, виконання умови), ви можете повністю зупинити весь процес обходу, як тільки його знайдете. Це досягається поверненням константи `NodeTraverser::StopTraversal` з візитора `enter` або `leave`. Метод `traverse()` припинить відвідування будь-яких подальших вузлів. Це дуже ефективно, якщо вам потрібна лише перша відповідність у потенційно дуже великому дереві. + + +Корисний помічник `NodeHelpers` +=============================== + +Хоча `NodeTraverser` пропонує детальний контроль, Latte також надає зручний допоміжний клас, [api:Latte\Compiler\NodeHelpers], який інкапсулює `NodeTraverser` для кількох поширених завдань пошуку та аналізу, часто вимагаючи менше підготовчого коду. + + +find(Node $startNode, callable $filter): array .[method] +-------------------------------------------------------- + +Цей статичний метод знаходить **усі** вузли в піддереві, що починається з `$startNode` (включно), які задовольняють callback `$filter`. Повертає масив відповідних вузлів. + +**Приклад:** Знайти всі вузли змінних (`VariableNode`) у всьому шаблоні. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Php\Expression\VariableNode; +use Latte\Compiler\Nodes\TemplateNode; + +function findAllVariables(TemplateNode $templateNode): array +{ + return NodeHelpers::find( + $templateNode, + fn($node) => $node instanceof VariableNode, + ); +} +``` + + +findFirst(Node $startNode, callable $filter): ?Node .[method] +-------------------------------------------------------------- + +Подібно до `find`, але зупиняє обхід негайно після знаходження **першого** вузла, який задовольняє callback `$filter`. Повертає знайдений об'єкт `Node` або `null`, якщо не знайдено жодного відповідного вузла. Це, по суті, зручна обгортка навколо `NodeTraverser::StopTraversal`. + +**Приклад:** Знайти вузол `{parameters}` (те саме, що й ручний приклад раніше, але коротше). + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\TemplateNode; +use Latte\Essential\Nodes\ParametersNode; + +function findParametersNodeHelper(TemplateNode $templateNode): ?ParametersNode +{ + return NodeHelpers::findFirst( + $templateNode->head, // Шукати лише в головній секції для ефективності + fn($node) => $node instanceof ParametersNode, + ); +} +``` + + +toValue(ExpressionNode $node, bool $constants = false): mixed .[method] +----------------------------------------------------------------------- + +Цей статичний метод намагається обчислити `ExpressionNode` **на етапі компіляції** і повернути його відповідне PHP-значення. Він надійно працює лише для простих літеральних вузлів (`StringNode`, `IntegerNode`, `FloatNode`, `BooleanNode`, `NullNode`) та екземплярів `ArrayNode`, що містять лише такі обчислювані елементи. + +Якщо `$constants` встановлено на `true`, він також намагатиметься розв'язати `ConstantFetchNode` та `ClassConstantFetchNode`, перевіряючи `defined()` та використовуючи `constant()`. + +Якщо вузол містить змінні, виклики функцій або інші динамічні елементи, він не може бути обчислений на етапі компіляції, і метод викине `InvalidArgumentException`. + +**Приклад використання:** Отримання статичного значення аргументу тегу під час компіляції для прийняття рішень на етапі компіляції. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Php\ExpressionNode; + +function getStaticStringArgument(ExpressionNode $argumentNode): ?string +{ + try { + $value = NodeHelpers::toValue($argumentNode); + return is_string($value) ? $value : null; + } catch (\InvalidArgumentException $e) { + // Аргумент не був статичним літеральним рядком + return null; + } +} +``` + + +toText(?Node $node): ?string .[method] +-------------------------------------- + +Цей статичний метод корисний для вилучення простого текстового вмісту з простих вузлів. Він працює переважно з: +- `TextNode`: Повертає його `$content`. +- `FragmentNode`: З'єднує результат `toText()` для всіх його дочірніх вузлів. Якщо якийсь дочірній вузол не може бути перетворений на текст (наприклад, містить `PrintNode`), повертає `null`. +- `NopNode`: Повертає порожній рядок. +- Інші типи вузлів: Повертає `null`. + +**Приклад використання:** Отримання статичного текстового вмісту значення HTML-атрибуту або простого HTML-елемента для аналізу під час компіляційного проходу. + +```php +use Latte\Compiler\NodeHelpers; +use Latte\Compiler\Nodes\Html\AttributeNode; + +function getStaticAttributeValue(AttributeNode $attr): ?string +{ + // $attr->value зазвичай є AreaNode (як FragmentNode або TextNode) + return NodeHelpers::toText($attr->value); +} + +// Приклад використання в проході: +// if ($node instanceof Html\ElementNode && $node->name === 'meta') { +// $nameAttrValue = getStaticAttributeValue($node->getAttributeNode('name')); +// if ($nameAttrValue === 'description') { ... } +// } +``` + +`NodeHelpers` може спростити ваші компіляційні проходи, надаючи готові рішення для поширених завдань обходу та аналізу AST. + + +Практичні приклади +================== + +Застосуємо концепції обходу та модифікації AST для вирішення деяких практичних проблем. Ці приклади демонструють поширені патерни, що використовуються в компіляційних проходах. + + +Автоматичне додавання `loading="lazy"` до `` +------------------------------------------------- + +Сучасні браузери підтримують нативне ліниве завантаження для зображень за допомогою атрибута `loading="lazy"`. Створимо прохід, який автоматично додає цей атрибут до всіх тегів ``, які ще не мають атрибута `loading`. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; +use Latte\Compiler\Nodes\Html; + +function addLazyLoading(Nodes\TemplateNode $templateNode): void +{ + (new NodeTraverser)->traverse( + $templateNode, + // Ми можемо використовувати 'enter', оскільки модифікуємо вузол безпосередньо + // і не залежимо від дочірніх вузлів для цього рішення. + enter: function (Node $node) { + // Чи це HTML-елемент з назвою 'img'? + if ($node instanceof Html\ElementNode && $node->name === 'img') { + // Переконуємося, що вузол атрибутів існує + $node->attributes ??= new Nodes\FragmentNode; + + // Перевіряємо, чи вже існує атрибут 'loading' (незалежно від регістру) + foreach ($node->attributes->children as $attrNode) { + if ($attrNode instanceof Html\AttributeNode + && $attrNode->name instanceof Nodes\TextNode // Статична назва атрибута + && strtolower($attrNode->name->content) === 'loading' + ) { + return; + } + } + + // Додаємо пробіл, якщо атрибути не порожні + if ($node->attributes->children) { + $node->attributes->children[] = new Nodes\TextNode(' '); + } + + // Створюємо новий вузол атрибута: loading="lazy" + $node->attributes->children[] = new Html\AttributeNode( + name: new Nodes\TextNode('loading'), + value: new Nodes\TextNode('lazy'), + quote: '"', + ); + // Зміна застосовується безпосередньо в об'єкті, не потрібно нічого повертати. + } + }, + ); +} +``` + +Пояснення: +- Visitor `enter` шукає вузли `Html\ElementNode` з назвою `img`. +- Він ітерує по існуючих атрибутах (`$node->attributes->children`) і перевіряє, чи атрибут `loading` вже присутній. +- Якщо не знайдено, створює новий `Html\AttributeNode`, що представляє `loading="lazy"`. + + +Перевірка викликів функцій +-------------------------- + +Компіляційні проходи є основою Latte Sandbox. Хоча справжній Sandbox є складним, ми можемо продемонструвати базовий принцип перевірки заборонених викликів функцій. + +**Мета:** Запобігти використанню потенційно небезпечної функції `shell_exec` у межах виразів шаблону. + +```php +use Latte\Compiler\Node; +use Latte\Compiler\NodeTraverser; +use Latte\Compiler\Nodes; +use Latte\Compiler\Nodes\Php; +use Latte\SecurityViolationException; + +function checkForbiddenFunctions(Nodes\TemplateNode $templateNode): void +{ + $forbiddenFunctions = ['shell_exec' => true, 'exec' => true]; // Простий список + + $traverser = new NodeTraverser; + (new NodeTraverser)->traverse( + $templateNode, + enter: function (Node $node) use ($forbiddenFunctions) { + // Чи це вузол прямого виклику функції? + if ($node instanceof Php\Expression\FunctionCallNode + && $node->name instanceof Php\NameNode + && isset($forbiddenFunctions[strtolower((string) $node->name)]) + ) { + throw new SecurityViolationException( + "Функція {$node->name}() не дозволена.", + $node->position, + ); + } + }, + ); +} +``` + +Пояснення: +- Ми визначаємо список заборонених назв функцій. +- Visitor `enter` перевіряє `FunctionCallNode`. +- Якщо назва функції (`$node->name`) є статичним `NameNode`, ми перевіряємо її рядкове представлення в нижньому регістрі проти нашого забороненого списку. +- Якщо знайдено заборонену функцію, ми викидаємо `Latte\SecurityViolationException`, яка чітко вказує на порушення правила безпеки та зупиняє компіляцію. + +Ці приклади показують, як компіляційні проходи з використанням `NodeTraverser` можуть бути використані для аналізу, автоматичних модифікацій та застосування обмежень безпеки шляхом взаємодії безпосередньо зі структурою AST шаблону. + + +Найкращі практики +================= + +При написанні компіляційних проходів пам'ятайте про ці рекомендації для створення надійних, підтримуваних та ефективних розширень: + +- **Порядок важливий:** Будьте свідомі порядку, в якому виконуються проходи. Якщо ваш прохід залежить від структури AST, створеної іншим проходом (наприклад, базові проходи Latte або інший власний прохід), або якщо інші проходи можуть залежати від ваших модифікацій, використовуйте механізм сортування, наданий `Extension::getPasses()` для визначення залежностей (`before`/`after`). Див. документацію до [`Extension::getPasses()` |extending-latte#getPasses] для деталей. +- **Одна відповідальність:** Намагайтеся створювати проходи, які виконують одне добре визначене завдання. Для складних перетворень розгляньте можливість розділення логіки на кілька проходів — можливо, один для аналізу та інший для модифікації на основі результатів аналізу. Це покращує зрозумілість та тестованість. +- **Продуктивність:** Пам'ятайте, що компіляційні проходи збільшують час компіляції шаблону (хоча це зазвичай відбувається лише один раз, доки шаблон не зміниться). Уникайте обчислювально складних операцій у ваших проходах, якщо це можливо. Використовуйте оптимізації обходу, такі як `NodeTraverser::DontTraverseChildren` та `NodeTraverser::StopTraversal`, коли ви знаєте, що вам не потрібно відвідувати певні частини AST. +- **Використовуйте `NodeHelpers`:** Для поширених завдань, таких як пошук специфічних вузлів або статичне обчислення простих виразів, перевірте, чи `Latte\Compiler\NodeHelpers` не пропонує відповідний метод перед написанням власної логіки `NodeTraverser`. Це може заощадити час та зменшити кількість підготовчого коду. +- **Обробка помилок:** Якщо ваш прохід виявляє помилку або недійсний стан в AST шаблону, викиньте `Latte\CompileException` (або `Latte\SecurityViolationException` для проблем безпеки) з чітким повідомленням та відповідним об'єктом `Position` (зазвичай `$node->position`). Це надає корисний зворотний зв'язок розробнику шаблону. +- **Ідемпотентність (якщо можливо):** В ідеалі, виконання вашого проходу кілька разів на тому самому AST має давати той самий результат, що й одноразове виконання. Це не завжди можливо, але спрощує налагодження та роздуми про взаємодію проходів, якщо цього досягнуто. Наприклад, переконайтеся, що ваш модифікаційний прохід перевіряє, чи модифікація вже була застосована, перш ніж застосовувати її знову. + +Дотримуючись цих практик, ви можете ефективно використовувати компіляційні проходи для розширення можливостей Latte потужним та надійним способом, що сприяє безпечнішій, оптимізованішій або функціонально багатшій обробці шаблонів. diff --git a/latte/uk/cookbook/@home.texy b/latte/uk/cookbook/@home.texy index 5d60e3b64a..65cd916958 100644 --- a/latte/uk/cookbook/@home.texy +++ b/latte/uk/cookbook/@home.texy @@ -1,13 +1,13 @@ -Кулінарна книга -*************** +Посібники та практики +********************* .[perex] -Приклади кодів і рецептів для виконання типових завдань за допомогою Latte. +Приклади коду та рецептів для виконання поширених завдань за допомогою Latte. -- [Все, що ви завжди хотіли знати про {iterateWhile} |iteratewhile] +- [Практики для розробників |/develop] +- [Передача змінних між шаблонами |passing-variables] +- [Все, що ви хотіли знати про групування |grouping] - [Як писати SQL-запити в Latte? |how-to-write-sql-queries-in-latte] - [Міграція з PHP |migration-from-php] - [Міграція з Twig |migration-from-twig] - [Використання Latte зі Slim 4 |slim-framework] - -{{leftbar: /@left-menu}} diff --git a/latte/uk/cookbook/@meta.texy b/latte/uk/cookbook/@meta.texy new file mode 100644 index 0000000000..80e1df686d --- /dev/null +++ b/latte/uk/cookbook/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Документація Latte}} +{{leftbar: /@left-menu}} diff --git a/latte/uk/cookbook/grouping.texy b/latte/uk/cookbook/grouping.texy new file mode 100644 index 0000000000..e9a47c8a1e --- /dev/null +++ b/latte/uk/cookbook/grouping.texy @@ -0,0 +1,251 @@ +Все, що ви хотіли знати про групування +************************************** + +.[perex] +При роботі з даними в шаблонах ви часто можете зіткнутися з необхідністю їх групування або специфічного відображення за певними критеріями. Latte для цієї мети пропонує відразу кілька потужних інструментів. + +Фільтр і функція `|group` дозволяють ефективно групувати дані за заданим критерієм, фільтр `|batch` полегшує розбиття даних на фіксовані партії, а тег `{iterateWhile}` надає можливість складнішого керування ходом циклів з умовами. Кожен з цих тегів пропонує специфічні можливості для роботи з даними, що робить їх незамінними інструментами для динамічного та структурованого відображення інформації в шаблонах Latte. + + +Фільтр і функція `group` .{data-version:3.0.16} +=============================================== + +Уявіть собі таблицю бази даних `items` з елементами, розділеними на категорії: + +| id | categoryId | name +|------------------ +| 1 | 1 | Apple +| 2 | 1 | Banana +| 3 | 2 | PHP +| 4 | 3 | Green +| 5 | 3 | Red +| 6 | 3 | Blue + +Простий список усіх елементів за допомогою шаблону Latte виглядав би так: + +```latte +
                                                                                                                                  +{foreach $items as $item} +
                                                                                                                                • {$item->name}
                                                                                                                                • +{/foreach} +
                                                                                                                                +``` + +Однак, якби ми хотіли, щоб елементи були впорядковані в групи за категоріями, нам потрібно було б розділити їх так, щоб кожна категорія мала свій власний список. Результат тоді мав би виглядати наступним чином: + +```latte +
                                                                                                                                  +
                                                                                                                                • Apple
                                                                                                                                • +
                                                                                                                                • Banana
                                                                                                                                • +
                                                                                                                                + +
                                                                                                                                  +
                                                                                                                                • PHP
                                                                                                                                • +
                                                                                                                                + +
                                                                                                                                  +
                                                                                                                                • Green
                                                                                                                                • +
                                                                                                                                • Red
                                                                                                                                • +
                                                                                                                                • Blue
                                                                                                                                • +
                                                                                                                                +``` + +Завдання можна легко та елегантно вирішити за допомогою `|group`. Як параметр вкажемо `categoryId`, що означає, що елементи розділяться на менші масиви за значенням `$item->categoryId` (якщо `$item` було б масивом, використовується `$item['categoryId']`): + +```latte +{foreach ($items|group: categoryId) as $categoryId => $categoryItems} +
                                                                                                                                  + {foreach $categoryItems as $item} +
                                                                                                                                • {$item->name}
                                                                                                                                • + {/foreach} +
                                                                                                                                +{/foreach} +``` + +Фільтр можна в Latte використовувати і як функцію, що дає нам альтернативний синтаксис: `{foreach group($items, categoryId) ...}`. + +Якщо ви хочете групувати елементи за складнішими критеріями, ви можете в параметрі фільтра використовувати функцію. Наприклад, групування елементів за довжиною назви виглядало б так: + +```latte +{foreach ($items|group: fn($item) => strlen($item->name)) as $items} + ... +{/foreach} +``` + +Важливо усвідомлювати, що `$categoryItems` — це не звичайний масив, а об'єкт, який поводиться як ітератор. Для доступу до першого елемента групи ви можете використовувати функцію [`first()` |latte:functions#first]. + +Ця гнучкість у групуванні даних робить `group` винятково корисним інструментом для представлення даних у шаблонах Latte. + + +Вкладені цикли +-------------- + +Уявімо, що ми маємо таблицю бази даних з додатковим стовпцем `subcategoryId`, який визначає підкатегорії окремих елементів. Ми хочемо відобразити кожну основну категорію в окремому списку `
                                                                                                                                  ` і кожну підкатегорію в окремому вкладеному списку `
                                                                                                                                    `: + +```latte +{foreach ($items|group: categoryId) as $categoryItems} +
                                                                                                                                      + {foreach ($categoryItems|group: subcategoryId) as $subcategoryItems} +
                                                                                                                                        + {foreach $subcategoryItems as $item} +
                                                                                                                                      1. {$item->name} + {/foreach} +
                                                                                                                                      + {/foreach} +
                                                                                                                                    +{/foreach} +``` + + +Зв'язок з Nette Database +------------------------ + +Давайте покажемо, як ефективно використовувати групування даних у поєднанні з Nette Database. Припустимо, що ми працюємо з таблицею `items` з вступного прикладу, яка через стовпець `categoryId` пов'язана з цією таблицею `categories`: + +| categoryId | name | +|------------|------------| +| 1 | Fruits | +| 2 | Languages | +| 3 | Colors | + +Дані з таблиці `items` завантажимо за допомогою Nette Database Explorer командою `$items = $db->table('items')`. Під час ітерації над цими даними ми маємо можливість доступу не лише до атрибутів, таких як `$item->name` та `$item->categoryId`, але завдяки зв'язку з таблицею `categories` також до пов'язаного рядка в ній через `$item->category`. На цьому зв'язку можна продемонструвати цікаве використання: + +```latte +{foreach ($items|group: category) as $category => $categoryItems} +

                                                                                                                                    {$category->name}

                                                                                                                                    +
                                                                                                                                      + {foreach $categoryItems as $item} +
                                                                                                                                    • {$item->name}
                                                                                                                                    • + {/foreach} +
                                                                                                                                    +{/foreach} +``` + +У цьому випадку ми використовуємо фільтр `|group` для групування за пов'язаним рядком `$item->category`, а не лише за стовпцем `categoryId`. Завдяки цьому в змінній-ключі безпосередньо `ActiveRow` даної категорії, що дозволяє нам прямо виводити її назву за допомогою `{$category->name}`. Це практичний приклад того, як групування може зробити шаблони зрозумілішими та полегшити роботу з даними. + + +Фільтр `|batch` +=============== + +Фільтр дозволяє розділити список елементів на групи із заздалегідь визначеною кількістю елементів. Цей фільтр ідеально підходить для ситуацій, коли ви хочете представити дані у кількох менших групах, наприклад, для кращої оглядовості або візуального впорядкування на сторінці. + +Уявімо, що ми маємо список елементів і хочемо відобразити їх у списках, де кожен містить максимум три елементи. Використання фільтра `|batch` у такому випадку дуже практичне: + +```latte +
                                                                                                                                      +{foreach ($items|batch: 3) as $batch} + {foreach $batch as $item} +
                                                                                                                                    • {$item->name}
                                                                                                                                    • + {/foreach} +{/foreach} +
                                                                                                                                    +``` + +У цьому прикладі список `$items` розділений на менші групи, причому кожна група (`$batch`) містить до трьох елементів. Кожна група потім відображається в окремому списку `
                                                                                                                                      `. + +Якщо остання група не містить достатньо елементів для досягнення бажаної кількості, другий параметр фільтра дозволяє визначити, чим буде доповнена ця група. Це ідеально підходить для естетичного вирівнювання елементів там, де неповний ряд міг би виглядати невпорядковано. + +```latte +{foreach ($items|batch: 3, '—') as $batch} + ... +{/foreach} +``` + + +Тег `{iterateWhile}` +==================== + +Ті самі завдання, які ми вирішували за допомогою фільтра `|group`, покажемо з використанням тегу `{iterateWhile}`. Основна відмінність між обома підходами полягає в тому, що `group` спочатку обробляє та групує всі вхідні дані, тоді як `{iterateWhile}` керує ходом циклів з умовами, тому ітерація відбувається послідовно. + +Спочатку відобразимо таблицю з категоріями за допомогою iterateWhile: + +```latte +{foreach $items as $item} +
                                                                                                                                        + {iterateWhile} +
                                                                                                                                      • {$item->name}
                                                                                                                                      • + {/iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                                                                                                                                      +{/foreach} +``` + +Тоді як `{foreach}` позначає зовнішню частину циклу, тобто відображення списків для кожної категорії, тег `{iterateWhile}` позначає внутрішню частину, тобто окремі елементи. Умова в закриваючому тегу говорить, що повторення триватиме доти, доки поточний і наступний елемент належать до тієї ж категорії (`$iterator->nextValue` — це [наступний елемент |/tags#iterator]). + +Якби умова була виконана завжди, то у внутрішньому циклі відобразилися б усі елементи: + +```latte +{foreach $items as $item} +
                                                                                                                                        + {iterateWhile} +
                                                                                                                                      • {$item->name} + {/iterateWhile true} +
                                                                                                                                      +{/foreach} +``` + +Результат виглядатиме так: + +```latte +
                                                                                                                                        +
                                                                                                                                      • Apple
                                                                                                                                      • +
                                                                                                                                      • Banana
                                                                                                                                      • +
                                                                                                                                      • PHP
                                                                                                                                      • +
                                                                                                                                      • Green
                                                                                                                                      • +
                                                                                                                                      • Red
                                                                                                                                      • +
                                                                                                                                      • Blue
                                                                                                                                      • +
                                                                                                                                      +``` + +Для чого корисне таке використання iterateWhile? Коли таблиця буде порожньою і не міститиме жодних елементів, не виведеться порожній `
                                                                                                                                        `. + +Якщо ми вкажемо умову у відкриваючому тегу `{iterateWhile}`, то поведінка зміниться: умова (і перехід до наступного елемента) виконається вже на початку внутрішнього циклу, а не в кінці. Тобто, тоді як до `{iterateWhile}` без умови вхід відбувається завжди, до `{iterateWhile $cond}` — лише при виконанні умови `$cond`. І водночас до `$item` записується наступний елемент. + +Це корисно, наприклад, у ситуації, коли ми хочемо перший елемент у кожній категорії відобразити іншим способом, наприклад, так: + +```latte +

                                                                                                                                        Apple

                                                                                                                                        +
                                                                                                                                          +
                                                                                                                                        • Banana
                                                                                                                                        • +
                                                                                                                                        + +

                                                                                                                                        PHP

                                                                                                                                        +
                                                                                                                                          +
                                                                                                                                        + +

                                                                                                                                        Green

                                                                                                                                        +
                                                                                                                                          +
                                                                                                                                        • Red
                                                                                                                                        • +
                                                                                                                                        • Blue
                                                                                                                                        • +
                                                                                                                                        +``` + +Початковий код змінимо так, що спочатку відобразимо перший елемент, а потім у внутрішньому циклі `{iterateWhile}` відобразимо інші елементи з тієї ж категорії: + +```latte +{foreach $items as $item} +

                                                                                                                                        {$item->name}

                                                                                                                                        +
                                                                                                                                          + {iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                                                                                                                                        • {$item->name}
                                                                                                                                        • + {/iterateWhile} +
                                                                                                                                        +{/foreach} +``` + +У рамках одного циклу ми можемо створювати кілька внутрішніх циклів і навіть вкладати їх. Таким чином можна було б групувати, наприклад, підкатегорії тощо. + +Припустимо, що в таблиці буде ще один стовпець `subcategoryId`, і крім того, що кожна категорія буде в окремому `
                                                                                                                                          `, кожна підкатегорія — в окремому `
                                                                                                                                            `: + +```latte +{foreach $items as $item} +
                                                                                                                                              + {iterateWhile} +
                                                                                                                                                + {iterateWhile} +
                                                                                                                                              1. {$item->name} + {/iterateWhile $item->subcategoryId === $iterator->nextValue->subcategoryId} +
                                                                                                                                              + {/iterateWhile $item->categoryId === $iterator->nextValue->categoryId} +
                                                                                                                                            +{/foreach} +``` diff --git a/latte/uk/cookbook/how-to-write-sql-queries-in-latte.texy b/latte/uk/cookbook/how-to-write-sql-queries-in-latte.texy index cae6787617..4998decb92 100644 --- a/latte/uk/cookbook/how-to-write-sql-queries-in-latte.texy +++ b/latte/uk/cookbook/how-to-write-sql-queries-in-latte.texy @@ -2,9 +2,9 @@ ***************************** .[perex] -Latte також може бути корисним для створення дійсно складних SQL-запитів. +Latte може бути корисним і для генерування дійсно складних SQL-запитів. -Якщо створюваний SQL-запит містить безліч умов і змінних, то його написання в Latte може бути дійсно більш зрозумілим. Дуже простий приклад: +Якщо створення SQL-запиту містить ряд умов і змінних, може бути справді зрозуміліше написати його в Latte. Дуже простий приклад: ```latte SELECT users.* FROM users @@ -14,8 +14,7 @@ SELECT users.* FROM users WHERE groups.name = 'Admins' {ifset $country} AND country.name = {$country} {/ifset} ``` -Використовуючи `$latte->setContentType()`, ми вказуємо Latte обробляти вміст як звичайний текст (не як HTML) і -потім ми готуємо функцію екранування, яка екранує рядки безпосередньо драйвером бази даних: +За допомогою `$latte->setContentType()` скажемо Latte, щоб воно розглядало вміст як простий текст (а не як HTML), а далі підготуємо функцію екранування, яка буде екранувати рядки безпосередньо драйвером бази даних: ```php $db = new PDO(/* ... */); @@ -31,13 +30,11 @@ $latte->addFilter('escape', fn($val) => match (true) { }); ``` -Використання буде виглядати наступним чином: +Використання виглядало б так: ```php $sql = $latte->renderToString('query.sql.latte', ['country' => $country]); $result = $db->query($sql); ``` -*Для цього прикладу потрібен Latte v3.0.5 або вище. - -{{leftbar: /@left-menu}} +*Наведений приклад вимагає Latte v3.0.5 або вище.* diff --git a/latte/uk/cookbook/iteratewhile.texy b/latte/uk/cookbook/iteratewhile.texy deleted file mode 100644 index 79a6d83f6e..0000000000 --- a/latte/uk/cookbook/iteratewhile.texy +++ /dev/null @@ -1,214 +0,0 @@ -Все, що ви завжди хотіли знати про {iterateWhile} -************************************************* - -.[perex] -Тег `{iterateWhile}` підходить для різних трюків у циклах foreach. - -Припустимо, у нас є така таблиця бази даних, у якій елементи розділені на категорії: - -| id | catId | name -|------------------ -| 1 | 1 | Apple -| 2 | 1 | Banana -| 3 | 2 | PHP -| 4 | 3 | Green -| 5 | 3 | Red -| 6 | 3 | Blue - -Звичайно, вивести елементи в циклі foreach у вигляді списку дуже просто: - -```latte -
                                                                                                                                              -{foreach $items as $item} -
                                                                                                                                            • {$item->name}
                                                                                                                                            • -{/foreach} -
                                                                                                                                            -``` - -Але що робити, якщо ви хочете вивести кожну категорію в окремий список? Інакше кажучи, як розв'язати задачу групування елементів із лінійного списку в циклі foreach. Виведення має мати такий вигляд: - -```latte -
                                                                                                                                              -
                                                                                                                                            • Apple
                                                                                                                                            • -
                                                                                                                                            • Banana
                                                                                                                                            • -
                                                                                                                                            - -
                                                                                                                                              -
                                                                                                                                            • PHP
                                                                                                                                            • -
                                                                                                                                            - -
                                                                                                                                              -
                                                                                                                                            • Green
                                                                                                                                            • -
                                                                                                                                            • Red
                                                                                                                                            • -
                                                                                                                                            • Blue
                                                                                                                                            • -
                                                                                                                                            -``` - -Ми покажемо вам, як легко й елегантно можна вирішити це завдання за допомогою iterateWhile: - -```latte -{foreach $items as $item} -
                                                                                                                                              - {iterateWhile} -
                                                                                                                                            • {$item->name}
                                                                                                                                            • - {/iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                                                                                                            -{/foreach} -``` - -Якщо `{foreach}` позначає зовнішню частину циклу, тобто складання списків для кожної категорії, то теги `{iterateWhile}` вказують на внутрішню частину, тобто на окремі елементи. -Умова в тезі end говорить, що повторення триватиме доти, доки поточний і наступний елемент належать до однієї категорії (`$iterator->nextValue` - [наступний елемент |/tags#iterator]). - -Якщо умова завжди виконується, то у внутрішньому циклі малюються всі елементи: - -```latte -{foreach $items as $item} -
                                                                                                                                              - {iterateWhile} -
                                                                                                                                            • {$item->name} - {/iterateWhile true} -
                                                                                                                                            -{/foreach} -``` - -Результат матиме такий вигляд: - -```latte -
                                                                                                                                              -
                                                                                                                                            • Apple
                                                                                                                                            • -
                                                                                                                                            • Banana
                                                                                                                                            • -
                                                                                                                                            • PHP
                                                                                                                                            • -
                                                                                                                                            • Green
                                                                                                                                            • -
                                                                                                                                            • Red
                                                                                                                                            • -
                                                                                                                                            • Blue
                                                                                                                                            • -
                                                                                                                                            -``` - -Чим корисне таке використання iterateWhile? Чим воно відрізняється від рішення, яке ми показали на самому початку цього посібника? Різниця в тому, що якщо таблиця порожня і не містить елементів, то вона не буде виводитися порожньою `
                                                                                                                                              `. - - -Рішення без `{iterateWhile}` .[#toc-solution-without-iteratewhile] ------------------------------------------------------------------- - -Якби ми вирішували ту саму задачу за допомогою абсолютно базових конструкцій систем шаблонів, наприклад, у Twig, Blade або чистому PHP, то рішення виглядало б приблизно так: - -```latte -{var $prevCatId = null} -{foreach $items as $item} - {if $item->catId !== $prevCatId} - {* the category has changed *} - - {* we close the previous
                                                                                                                                                , if it is not the first item *} - {if $prevCatId !== null} -
                                                                                                                                              - {/if} - - {* we will open a new list *} -
                                                                                                                                                - - {do $prevCatId = $item->catId} - {/if} - -
                                                                                                                                              • {$item->name}
                                                                                                                                              • -{/foreach} - -{if $prevCatId !== null} - {* we close the last list *} -
                                                                                                                                              -{/if} -``` - -Однак цей код незрозумілий і неінтуїтивний. Зв'язок між HTML-тегами, що відкривають і закривають, абсолютно не зрозумілий. З першого погляду не зрозуміло, чи є помилка. І для цього потрібні допоміжні змінні, такі як `$prevCatId`. - -На відміну від цього, рішення з `{iterateWhile}` чисте, зрозуміле, не потребує допоміжних змінних і є надійним. - - -Умова в закриваючому тезі .[#toc-condition-in-the-closing-tag] --------------------------------------------------------------- - -Якщо вказати умову у відкриваючому тезі `{iterateWhile}`, то поведінка змінюється: умова (і перехід до наступного елемента) виконується на початку внутрішнього циклу, а не в кінці. -Таким чином, якщо `{iterateWhile}` без умови вводиться завжди, то `{iterateWhile $cond}` вводиться тільки при виконанні умови `$cond`. Водночас, наступний елемент записується в `$item`. - -Це корисно, наприклад, у ситуації, коли потрібно по-різному відобразити перший елемент у кожній категорії, наприклад: - -```latte -

                                                                                                                                              Apple

                                                                                                                                              -
                                                                                                                                                -
                                                                                                                                              • Banana
                                                                                                                                              • -
                                                                                                                                              - -

                                                                                                                                              PHP

                                                                                                                                              -
                                                                                                                                                -
                                                                                                                                              - -

                                                                                                                                              Green

                                                                                                                                              -
                                                                                                                                                -
                                                                                                                                              • Red
                                                                                                                                              • -
                                                                                                                                              • Blue
                                                                                                                                              • -
                                                                                                                                              -``` - -Змінимо вихідний код, ми відтворюємо перший елемент, а потім додаткові елементи з тієї ж категорії у внутрішньому циклі `{iterateWhile}`: - -```latte -{foreach $items as $item} -

                                                                                                                                              {$item->name}

                                                                                                                                              -
                                                                                                                                                - {iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                                                                                                              • {$item->name}
                                                                                                                                              • - {/iterateWhile} -
                                                                                                                                              -{/foreach} -``` - - -Вкладені цикли .[#toc-nested-loops] ------------------------------------ - -Ми можемо створити кілька внутрішніх циклів в одному циклі і навіть вкласти їх один в одного. Таким чином, наприклад, можна згрупувати підкатегорії. - -Припустімо, що в таблиці `subCatId` є ще один стовпчик, і на додаток до того, що кожна категорія перебуватиме в окремому стовпчику, кожна підкатегорія перебуватиме в окремому стовпчику. `
                                                                                                                                                `, кожна підкатегорія буде знаходитися в окремій колонці `
                                                                                                                                                  `: - -```latte -{foreach $items as $item} -
                                                                                                                                                    - {iterateWhile} -
                                                                                                                                                      - {iterateWhile} -
                                                                                                                                                    1. {$item->name} - {/iterateWhile $item->subCatId === $iterator->nextValue->subCatId} -
                                                                                                                                                    - {/iterateWhile $item->catId === $iterator->nextValue->catId} -
                                                                                                                                                  -{/foreach} -``` - - -Фільтр | пакетний .[#toc-filter-batch] --------------------------------------- - -Групування лінійних елементів також забезпечується фільтром `batch`, у партії з фіксованою кількістю елементів: - -```latte -
                                                                                                                                                    -{foreach ($items|batch:3) as $batch} - {foreach $batch as $item} -
                                                                                                                                                  • {$item->name}
                                                                                                                                                  • - {/foreach} -{/foreach} -
                                                                                                                                                  -``` - -Його можна замінити на iterateWhile таким чином: - -```latte -
                                                                                                                                                    -{foreach $items as $item} - {iterateWhile} -
                                                                                                                                                  • {$item->name}
                                                                                                                                                  • - {/iterateWhile $iterator->counter0 % 3} -{/foreach} -
                                                                                                                                                  -``` - -{{leftbar: /@left-menu}} diff --git a/latte/uk/cookbook/migration-from-php.texy b/latte/uk/cookbook/migration-from-php.texy index fcb393b0f7..8dfa1c1701 100644 --- a/latte/uk/cookbook/migration-from-php.texy +++ b/latte/uk/cookbook/migration-from-php.texy @@ -1,28 +1,28 @@ -Перехід з PHP на Latte -********************** +Міграція з PHP на Latte +*********************** .[perex] -Ви переносите старий проект, написаний на чистому PHP, на Latte? У нас є інструмент для полегшення міграції. [Спробуйте його онлайн |https://php2latte.nette.org]. +Переносите старий проєкт, написаний на чистому PHP, на Latte? Ми маємо для вас інструмент, який полегшить міграцію. [Спробуйте онлайн |https://fiddle.nette.org/php2latte/]. -Ви можете завантажити інструмент з [GitHub |https://github.com/nette/latte-tools] або встановити його за допомогою Composer: +Інструмент можна завантажити з [GitHub|https://github.com/nette/latte-tools] або встановити за допомогою Composer: ```shell composer create-project latte/tools ``` -Конвертер не використовує прості підстановки регулярних виразів, замість цього він використовує безпосередньо парсер PHP, тому він може обробляти будь-який складний синтаксис. +Конвертер не використовує прості заміни за допомогою регулярних виразів, навпаки, використовує безпосередньо PHP-парсер, тому впорається з будь-яким складним синтаксисом. -Для конвертації з PHP у Latte використовується скрипт `php-to-latte.php`: +Для перетворення з PHP на Latte служить скрипт `php-to-latte.php`: ```shell -php-to-latte.php input.php [output.latte] +php php-to-latte.php input.php [output.latte] ``` -Приклад .[#toc-example] ------------------------ +Приклад +------- -Вхідний файл може мати такий вигляд (це частина коду форуму PunBB): +Вхідний файл може виглядати, наприклад, так (це частина коду форуму PunBB): ```php

                                                                                                                                                  @@ -48,7 +48,7 @@ foreach ($result as $cur_group) {
                                                                                                                                    ``` -Генерує цей шаблон: +Згенеровано цей шаблон: ```latte

                                                                                                                                    {$lang_common['User list']}

                                                                                                                                    @@ -68,5 +68,3 @@ foreach ($result as $cur_group) {
                                                                                                                                    ``` - -{{leftbar: /@left-menu}} diff --git a/latte/uk/cookbook/migration-from-twig.texy b/latte/uk/cookbook/migration-from-twig.texy index 26f61764ed..a0748c5f81 100644 --- a/latte/uk/cookbook/migration-from-twig.texy +++ b/latte/uk/cookbook/migration-from-twig.texy @@ -2,37 +2,37 @@ ************************ .[perex] -Ви переносите проєкт, написаний на Twig, на більш сучасний Latte? У нас є інструмент для полегшення міграції. [Спробуйте його онлайн |https://twig2latte.nette.org]. +Переносите проєкт, написаний на Twig, на сучасніший Latte? Ми маємо для вас інструмент, який полегшить міграцію. [Спробуйте онлайн |https://fiddle.nette.org/twig2latte/]. -Ви можете завантажити інструмент з [GitHub |https://github.com/nette/latte-tools] або встановити його за допомогою Composer: +Інструмент можна завантажити з [GitHub|https://github.com/nette/latte-tools] або встановити за допомогою Composer: ```shell composer create-project latte/tools ``` -Конвертер не використовує прості підстановки регулярних виразів, замість цього він використовує безпосередньо парсер Twig, тому він може обробляти будь-який складний синтаксис. +Конвертер не використовує прості заміни за допомогою регулярних виразів, навпаки, використовує безпосередньо Twig-парсер, тому впорається з будь-яким складним синтаксисом. -Для конвертації з Twig у Latte використовується скрипт `twig-to-latte.php`: +Для перетворення з Twig на Latte служить скрипт `twig-to-latte.php`: ```shell -twig-to-latte.php input.twig.html [output.latte] +php twig-to-latte.php input.twig.html [output.latte] ``` -Конвертація .[#toc-conversion] ------------------------------- +Конвертація +----------- -Перетворення вимагає ручного редагування результату, оскільки перетворення не може бути виконано однозначно. У Twig використовується точковий синтаксис, де `{{ a.b }}` може означати `$a->b`, `$a['b']` або `$a->getB()`, які неможливо розрізнити під час компіляції. Тому конвертер перетворює все в `$a->b`. +Перетворення передбачає ручне редагування результату, оскільки конвертацію неможливо виконати однозначно. Twig використовує крапковий синтаксис, де `{{ a.b }}` може означати `$a->b`, `$a['b']` або `$a->getB()`, що неможливо розрізнити під час компіляції. Тому конвертер все перетворює на `$a->b`. -Деякі функції, фільтри або теги не мають еквівалента в Latte або можуть поводитися дещо інакше. +Деякі функції, фільтри або теги не мають аналогів у Latte, або можуть поводитися трохи інакше. -Приклад .[#toc-example] ------------------------ +Приклад +------- -Вхідний файл може мати такий вигляд: +Вхідний файл може виглядати, наприклад, так: -```latte +```twig {% use "blocks.twig" %} @@ -54,7 +54,7 @@ twig-to-latte.php input.twig.html [output.latte] ``` -Після перетворення в Latte ми отримаємо цей шаблон: +Після конвертації в Latte ми отримаємо цей шаблон: ```latte {import 'blocks.latte'} @@ -77,5 +77,3 @@ twig-to-latte.php input.twig.html [output.latte] ``` - -{{leftbar: /@left-menu}} diff --git a/latte/uk/cookbook/passing-variables.texy b/latte/uk/cookbook/passing-variables.texy new file mode 100644 index 0000000000..12c833c36f --- /dev/null +++ b/latte/uk/cookbook/passing-variables.texy @@ -0,0 +1,158 @@ +Передача змінних між шаблонами +****************************** + +Цей посібник пояснить вам, як змінні передаються між шаблонами в Latte за допомогою різних тегів, таких як `{include}`, `{import}`, `{embed}`, `{layout}`, `{sandbox}` та інших. Ви також дізнаєтеся, як працювати зі змінними в тегах `{block}` та `{define}`, і для чого служить тег `{parameters}`. + + +Типи змінних +------------ +Змінні в Latte можна розділити на три категорії залежно від того, як і де вони визначені: + +**Вхідні змінні** — це ті, які передаються до шаблону ззовні, наприклад, з PHP-скрипта або за допомогою тегу, як `{include}`. + +```php +$latte->render('template.latte', ['userName' => 'Jan', 'userAge' => 30]); +``` + +**Оточуючі змінні** — це змінні, що існують у місці певного тегу. Вони включають усі вхідні змінні та інші змінні, створені за допомогою тегів, таких як `{var}`, `{default}` або в рамках циклу `{foreach}`. + +```latte +{foreach $users as $user} + {include 'userBox.latte', user: $user} +{/foreach} +``` + +**Явні змінні** — це ті, які безпосередньо вказані всередині тегу і надсилаються до цільового шаблону. + +```latte +{include 'userBox.latte', name: $user->name, age: $user->age} +``` + + +`{block}` +--------- +Тег `{block}` використовується для визначення блоків коду для повторного використання, які можна налаштувати або розширити в успадкованих шаблонах. Оточуючі змінні, визначені перед блоком, доступні всередині блоку, але будь-які зміни змінних відображаються лише в межах цього блоку. + +```latte +{var $foo = 'оригінальний'} +{block example} + {var $foo = 'змінений'} +{/block} + +{$foo} // виведе: оригінальний +``` + + +`{define}` +---------- +Тег `{define}` служить для створення блоків, які відображаються лише після їх виклику за допомогою `{include}`. Змінні, доступні всередині цих блоків, залежать від того, чи вказані параметри у визначенні. Якщо так, доступ мають лише до цих параметрів. Якщо ні, доступ мають до всіх вхідних змінних шаблону, в якому визначені блоки. + +```latte +{define hello} + {* має доступ до всіх вхідних змінних шаблону *} +{/define} + +{define hello $name} + {* має доступ лише до параметра $name *} +{/define} +``` + + +`{parameters}` +-------------- +Тег `{parameters}` служить для явного оголошення очікуваних вхідних змінних на початку шаблону. Таким чином можна легко документувати очікувані змінні та їхні типи даних. Також можна визначити значення за замовчуванням. + +```latte +{parameters int $age, string $name = 'невідомий'} +

                                                                                                                                    Вік: {$age}, Ім'я: {$name}

                                                                                                                                    +``` + + +`{include file}` +---------------- +Тег `{include file}` служить для включення цілого шаблону. Цьому шаблону передаються як вхідні змінні шаблону, в якому використано тег, так і змінні, явно визначені в ньому. Однак цільовий шаблон може обмежити область видимості за допомогою `{parameters}`. + +```latte +{include 'profile.latte', userId: $user->id} +``` + + +`{include block}` +----------------- +Коли ви включаєте блок, визначений у тому ж шаблоні, до нього передаються всі оточуючі та явно визначені змінні: + +```latte +{define blockName} +

                                                                                                                                    Ім'я: {$name}, Вік: {$age}

                                                                                                                                    +{/define} + +{var $name = 'Jan', $age = 30} +{include blockName} +``` + +У цьому прикладі змінні `$name` та `$age` передаються до блоку `blockName`. Так само поводиться і `{include parent}`. + +При включенні блоку з іншого шаблону передаються лише вхідні та явно визначені змінні. Оточуючі змінні не доступні автоматично. + +```latte +{include blockInOtherTemplate, name: $name, age: $age} +``` + + +`{layout}` або `{extends}` +-------------------------- +Ці теги визначають layout, до якого передаються вхідні змінні дочірнього шаблону, а також змінні, створені в коді перед блоками: + +```latte +{layout 'layout.latte'} +{var $seo = 'index, follow'} +``` + +Шаблон `layout.latte`: + +```latte + + + +``` + + +`{embed}` +--------- +Тег `{embed}` схожий на тег `{include}`, але дозволяє вбудовувати блоки в шаблон. На відміну від `{include}`, передаються лише явно оголошені змінні: + +```latte +{embed 'menu.latte', items: $menuItems} +{/embed} +``` + +У цьому прикладі шаблон `menu.latte` має доступ лише до змінної `$items`. + +Навпаки, у блоках всередині `{embed}` є доступ до всіх оточуючих змінних: + +```latte +{var $name = 'Jan'} +{embed 'menu.latte', items: $menuItems} + {block foo} + {$name} + {/block} +{/embed} +``` + + +`{import}` +---------- +Тег `{import}` використовується для завантаження блоків з інших шаблонів. Передаються як вхідні, так і явно оголошені змінні до імпортованих блоків. + +```latte +{import 'buttons.latte'} +``` + + +`{sandbox}` +----------- +Тег `{sandbox}` ізолює шаблон для безпечної обробки. Змінні передаються виключно явно. + +```latte +{sandbox 'secure.latte', data: $secureData} +``` diff --git a/latte/uk/cookbook/slim-framework.texy b/latte/uk/cookbook/slim-framework.texy index fe19181415..48edc7e12a 100644 --- a/latte/uk/cookbook/slim-framework.texy +++ b/latte/uk/cookbook/slim-framework.texy @@ -2,7 +2,7 @@ **************************** .[perex] -Ця стаття, написана "Daniel Opitz":https://odan.github.io/2022/04/06/slim4-latte.html, описує використання Latte з Slim Framework. +Ця стаття, автором якої є "Daniel Opitz":https://odan.github.io/2022/04/06/slim4-latte.html, описує використання Latte зі Slim Framework. Спочатку "встановіть Slim Framework":https://odan.github.io/2019/11/05/slim4-tutorial.html, а потім Latte за допомогою Composer: @@ -11,33 +11,33 @@ composer require latte/latte ``` -Конфігурація .[#toc-configuration] ----------------------------------- +Конфігурація +------------ -Створіть новий каталог `templates` у кореневому каталозі вашого проекту. Усі шаблони будуть поміщені туди пізніше. +У кореневому каталозі проєкту створіть новий каталог `templates`. Усі шаблони будуть розміщені в ньому пізніше. -Додайте новий ключ конфігурації `template` у ваш файл `config/defaults.php`: +До файлу `config/defaults.php` додайте новий конфігураційний ключ `template`: ```php $settings['template'] = __DIR__ . '/../templates'; ``` -Latte компілює шаблони у власний PHP-код і зберігає їх у кеші на диску. Таким чином, вони працюють так само швидко, як якщо б були написані на рідному PHP. +Latte компілює шаблони в нативний PHP-код і зберігає їх у кеші на диску. Тому вони такі ж швидкі, якби вони були написані нативною мовою PHP. -Додайте новий ключ конфігурації `template_temp` до файлу `config/defaults.php`: Переконайтеся, що каталог `{project}/tmp/templates` існує і має права доступу на читання і запис. +До файлу `config/defaults.php` додайте новий конфігураційний ключ `template_temp`: Переконайтеся, що каталог `{project}/tmp/templates` існує та має права на читання та запис. ```php $settings['template_temp'] = __DIR__ . '/../tmp/templates'; ``` -Latte автоматично регенерує кеш при кожній зміні шаблону, що можна відключити у виробничому середовищі, щоб заощадити трохи продуктивності: +Latte автоматично регенерує кеш при кожній зміні шаблону, що можна вимкнути в робочому середовищі та заощадити трохи продуктивності: ```php -// змінити на false у виробничому середовищі +// в робочому середовищі змініть на false $settings['template_auto_refresh'] = true; ``` -Далі додайте визначення контейнера DI для класу `Latte\Engine`. +Далі додайте визначення DI-контейнера для класу `Latte\Engine`. ```php +
                                                                                                                                      {foreach $items as $item}
                                                                                                                                    • {$item|capitalize}
                                                                                                                                    • {/foreach}
                                                                                                                                    ``` -Якщо все налаштовано правильно, ви маєте побачити такий результат: +Якщо все правильно налаштовано, має відобразитися наступний вивід: ```latte One @@ -155,4 +155,3 @@ Three ``` {{priority: -1}} -{{leftbar: /@left-menu}} diff --git a/latte/uk/creating-extension.texy b/latte/uk/creating-extension.texy deleted file mode 100644 index 5fb784a9ed..0000000000 --- a/latte/uk/creating-extension.texy +++ /dev/null @@ -1,579 +0,0 @@ -Створення розширення -******************** - -.[perex]{data-version:3.0} -Розширення - це багаторазово використовуваний клас, який може визначати користувацькі теги, фільтри, функції, провайдери тощо. - -Ми створюємо розширення, коли хочемо повторно використати наші налаштування Latte в різних проєктах або поділитися ними з іншими. -Також корисно створювати розширення для кожного веб-проєкту, яке міститиме всі специфічні теги та фільтри, які ви хочете використовувати в шаблонах проєкту. - - -Клас розширення .[#toc-extension-class] -======================================= - -Extension - це клас, що успадковує від [api:Latte\Extension]. Він реєструється в Latte за допомогою `addExtension()` (або через [конфігураційний файл |application:configuration#Latte]): - -```php -$latte = new Latte\Engine; -$latte->addExtension(new MyLatteExtension); -``` - -Якщо ви зареєстрували кілька розширень і вони визначають однаково названі теги, фільтри або функції, перемагає останнє додане розширення. Це також передбачає, що ваші розширення можуть перевизначати власні теги/фільтри/функції. - -Щоразу, коли ви вносите зміни в клас і автооновлення не вимкнено, Latte автоматично перекомпілює ваші шаблони. - -Клас може реалізовувати будь-який із таких методів: - -```php -abstract class Extension -{ - /** - * Initializes before template is compiler. - */ - public function beforeCompile(Engine $engine): void; - - /** - * Returns a list of parsers for Latte tags. - * @return array - */ - public function getTags(): array; - - /** - * Returns a list of compiler passes. - * @return array - */ - public function getPasses(): array; - - /** - * Returns a list of |filters. - * @return array - */ - public function getFilters(): array; - - /** - * Returns a list of functions used in templates. - * @return array - */ - public function getFunctions(): array; - - /** - * Returns a list of providers. - * @return array - */ - public function getProviders(): array; - - /** - * Returns a value to distinguish multiple versions of the template. - */ - public function getCacheKey(Engine $engine): mixed; - - /** - * Initializes before template is rendered. - */ - public function beforeRender(Template $template): void; -} -``` - -Щоб отримати уявлення про те, як виглядає розширення, подивіться на вбудоване "CoreExtension":https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php. - - -beforeCompile(Latte\Engine $engine): void .[method] ---------------------------------------------------- - -Викликається перед компіляцією шаблону. Метод може використовуватися, наприклад, для ініціалізації, пов'язаної з компіляцією. - - -getTags(): array .[method] --------------------------- - -Викликається під час компіляції шаблону. Повертає асоціативний масив *ім'я тега => callable*, які є [функціями розбору тегів |#Tag-Parsing-Function]. - -```php -public function getTags(): array -{ - return [ - 'foo' => [FooNode::class, 'create'], - 'bar' => [BarNode::class, 'create'], - 'n:baz' => [NBazNode::class, 'create'], - // ... - ]; -} -``` - -Тег `n:baz` являє собою чистий n:attribute, тобто це тег, який може бути записаний тільки як атрибут. - -У разі тегів `foo` і `bar` Latte автоматично розпізнає, чи є вони парами, і якщо так, то їх можна автоматично записати з використанням n:атрибутів, включно з варіантами з префіксами `n:inner-foo` і `n:tag-foo`. - -Порядок виконання таких n:атрибутів визначається їхнім порядком у масиві, що повертається `getTags()`. Таким чином, `n:foo` завжди виконується перед `n:bar`, навіть якщо атрибути перераховані у зворотному порядку в HTML-тезі як `
                                                                                                                                    `. - -Якщо вам потрібно визначити порядок виконання n:атрибутів для декількох розширень, використовуйте допоміжний метод `order()`, де параметр `before` xor `after` визначає, які теги будуть упорядковані до або після тега . - -```php -public function getTags(): array -{ - return [ - 'foo' => self::order([FooNode::class, 'create'], before: 'bar')] - 'bar' => self::order([BarNode::class, 'create'], after: ['block', 'snippet'])] - ]; -} -``` - - -getPasses(): array .[method] ----------------------------- - -Викликається під час компіляції шаблону. Повертає асоціативний масив *name pass => callable*, які є функціями, що представляють так звані [проходи компілятора |#Compiler-Passes], що обходять і змінюють AST. - -Знову ж таки, може бути використаний допоміжний метод `order()`. Значенням параметрів `before` або `after` може бути `*` зі значенням before/after all. - -```php -public function getPasses(): array -{ - return [ - 'optimize' => [Passes::class, 'optimizePass'], - 'sandbox' => self::order([$this, 'sandboxPass'], before: '*'), - // ... - ]; -} -``` - - -beforeRender(Latte\Engine $engine): void .[method] --------------------------------------------------- - -Викликається перед кожним рендерингом шаблону. Метод можна використовувати, наприклад, для ініціалізації змінних, що використовуються під час рендерингу. - - -getFilters(): array .[method] ------------------------------ - -Викликається перед відтворенням шаблону. Повертає [фільтри |extending-latte#Filters] у вигляді асоціативного масиву *ім'я фільтра => фільтр, що викликається*. - -```php -public function getFilters(): array -{ - return [ - 'batch' => [$this, 'batchFilter'], - 'trim' => [$this, 'trimFilter'], - // ... - ]; -} -``` - - -getFunctions(): array .[method] -------------------------------- - -Викликається перед відтворенням шаблону. Повертає [функції |extending-latte#Functions] у вигляді асоціативного масиву *ім'я функції => callable*. - -```php -public function getFunctions(): array -{ - return [ - 'clamp' => [$this, 'clampFunction'], - 'divisibleBy' => [$this, 'divisibleByFunction'], - // ... - ]; -} -``` - - -getProviders(): array .[method] -------------------------------- - -Викликається перед відтворенням шаблону. Повертає масив провайдерів, які зазвичай є об'єктами, що використовують теги під час виконання. Доступ до них здійснюється через `$this->global->...`. - -```php -public function getProviders(): array -{ - return [ - 'myFoo' => $this->foo, - 'myBar' => $this->bar, - // ... - ]; -} -``` - - -getCacheKey(Latte\Engine $engine): mixed .[method] --------------------------------------------------- - -Викликається перед відтворенням шаблону. Значення, що повертається, стає частиною ключа, хеш якого міститься в імені скомпільованого файлу шаблону. Таким чином, для різних значень, що повертаються, Latte генеруватиме різні файли кешу. - - -Як працює Latte? .[#toc-how-does-latte-work] -============================================ - -Щоб зрозуміти, як визначити користувацькі теги або передачі компілятора, необхідно зрозуміти, як Latte працює під капотом. - -Компіляція шаблонів у Latte спрощено працює таким чином: - -- Спочатку **лексор** розбиває вихідний код шаблону на невеликі фрагменти (лексеми) для зручнішого опрацювання. -- Потім **парсер** перетворює потік лексем в осмислене дерево вузлів (Abstract Syntax Tree, AST). -- Нарешті, компілятор **генерує** клас PHP з AST, який відображає шаблон і зберігає його в кеші. - -Насправді, компіляція трохи складніша. У Latte **є два** лексера і парсера: один для HTML-шаблону, інший для PHP-подібного коду всередині тегів. Крім того, парсинг не виконується після токенізації, а лексер і парсер працюють паралельно у двох "потоках" і координуються. Це ракетобудування :-) - -Ба більше, усі теги мають свої власні процедури синтаксичного аналізу. Коли парсер зустрічає тег, він викликає свою функцію розбору (вона повертає [Extension::getTags() |#getTags]). -Її робота полягає в розборі аргументів тега і, в разі парних тегів, внутрішнього вмісту. Вона повертає *вузол*, який стає частиною AST. Подробиці див. у розділі [Функція розбору те гів|#Tag-Parsing-Function]. - -Коли парсер завершує свою роботу, ми отримуємо повний AST, що представляє шаблон. Кореневим вузлом є `Latte\Compiler\Nodes\TemplateNode`. Окремі вузли всередині дерева представляють не тільки теги, а й елементи HTML, їхні атрибути, будь-які вирази, що використовуються всередині тегів, тощо. - -Після цього в гру вступають так звані [проходи компілятора |#Compiler-Passes], які являють собою функції (повертаються [Extension::getPasses( |#getPasses])), що змінюють AST. - -Весь процес, від завантаження вмісту шаблону, парсингу до генерації результуючого файлу, може бути впорядкований за допомогою цього коду, з яким ви можете експериментувати і скидати проміжні результати: - -```php -$latte = new Latte\Engine; -$source = $latte->getLoader()->getContent($file); -$ast = $latte->parse($source); -$latte->applyPasses($ast); -$code = $latte->generate($ast, $file); -``` - - -Приклад AST .[#toc-example-of-ast] ----------------------------------- - -Щоб отримати краще уявлення про AST, ми додамо приклад. Це вихідний шаблон: - -```latte -{foreach $category->getItems() as $item} -
                                                                                                                                  1. {$item->name|upper}
                                                                                                                                  2. - {else} - no items found -{/foreach} -``` - -А це його подання у вигляді AST: - -/--pre -Latte\Compiler\Nodes\TemplateNode( - Latte\Compiler\Nodes\FragmentNode( - - Latte\Essential\Nodes\ForeachNode( - expression: Latte\Compiler\Nodes\Php\Expression\MethodCallNode( - object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$category') - name: Latte\Compiler\Nodes\Php\IdentifierNode('getItems') - ) - value: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') - content: Latte\Compiler\Nodes\FragmentNode( - - Latte\Compiler\Nodes\TextNode(' ') - - Latte\Compiler\Nodes\Html\ElementNode('li')( - content: Latte\Essential\Nodes\PrintNode( - expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode( - object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') - name: Latte\Compiler\Nodes\Php\IdentifierNode('name') - ) - modifier: Latte\Compiler\Nodes\Php\ModifierNode( - filters: - - Latte\Compiler\Nodes\Php\FilterNode('upper') - ) - ) - ) - ) - else: Latte\Compiler\Nodes\FragmentNode( - - Latte\Compiler\Nodes\TextNode('no items found') - ) - ) - ) -) -\-- - - -Користувацькі теги .[#toc-custom-tags] -====================================== - -Для визначення нового тега необхідно виконати три кроки: - -- визначення [функції розбору тега |#Tag-Parsing-Function] (відповідає за розбір тега у вузол) -- створення класу вузла (відповідає за [генерацію PHP-коду |#Generating-PHP-Code] і [обхід AST |#AST-Traversing]) -- реєстрація тега за допомогою [Extension::getTags() |#getTags] - - -Функція розбору тега .[#toc-tag-parsing-function] -------------------------------------------------- - -Розбір тегів виконується функцією розбору (та, яка повертається функцією [Extension::getTags() |#getTags]). Її завдання - розібрати і перевірити всі аргументи всередині тега (для цього вона використовує TagParser). -Крім того, якщо тег є парою, вона попросить TemplateParser розібрати і повернути внутрішній вміст. -Функція створює і повертає вузол, який зазвичай є дочірнім вузлом `Latte\Compiler\Nodes\StatementNode`, і він стає частиною AST. - -Ми створюємо клас для кожного вузла, що ми зараз і зробимо, і елегантно поміщаємо в нього функцію парсингу у вигляді статичної фабрики. Як приклад спробуємо створити знайомий тег `{foreach}`: - -```php -use Latte\Compiler\Nodes\StatementNode; - -class ForeachNode extends StatementNode -{ - // функція розбору, яка поки що просто створює вузол - public static function create(Latte\Compiler\Tag $tag): self - { - $node = new self; - return $node; - } - - public function print(Latte\Compiler\PrintContext $context): string - { - // код буде додано пізніше - } - - public function &getIterator(): \Generator - { - // код буде додано пізніше - } -} -``` - -Функції парсингу `create()` передається об'єкт [api:Latte\Compiler\Tag], який несе основну інформацію про тег (чи є він класичним тегом, чи n:attribute, на якому рядку він знаходиться тощо) та здебільшого звертається до об'єкта [api:Latte\Compiler\TagParser] в `$tag->parser`. - -Якщо тег повинен мати аргументи, перевірте їх наявність, викликавши `$tag->expectArguments()`. Для їх розбору доступні методи об'єкта `$tag->parser`: - -- `parseExpression(): ExpressionNode` для PHP-подібного виразу (наприклад, `10 + 3`). -- `parseUnquotedStringOrExpression(): ExpressionNode` для виразу або рядка без лапок -- `parseArguments(): ArrayNode` вміст масиву (наприклад, `10, true, foo => bar`) -- `parseModifier(): ModifierNode` для модифікатора (наприклад, `|upper|truncate:10`) -- `parseType(): expressionNode` для підказки типу (наприклад, `int|string` або `Foo\Bar[]`) - -і низькорівневий [api:Latte\Compiler\TokenStream], що працює безпосередньо з лексемами: - -- `$tag->parser->stream->consume(...): Token` -- `$tag->parser->stream->tryConsume(...): ?Token` - -Latte розширює синтаксис PHP невеликими способами, наприклад, додаючи модифікатори, скорочені трійкові оператори або дозволяючи записувати прості літерно-цифрові рядки без лапок. Саме тому ми використовуємо термін *PHP-подібний* замість PHP. Так, наприклад, метод `parseExpression()` аналізує `foo` як `'foo'`. -Крім того, *unquoted-string* - це особливий випадок рядка, який також не потребує лапок, але в той самий час не обов'язково має бути буквено-цифровим. Наприклад, це шлях до файлу в тезі `{include ../file.latte}`. Для його розбору використовується метод `parseUnquotedStringOrExpression()`. - -.[note] -Вивчення класів вузлів, що входять до складу Latte, - найкращий спосіб дізнатися всі тонкощі процесу розбору. - -Давайте повернемося до тегу `{foreach}`. У ньому ми очікуємо аргументи виду `expression + 'as' + second expression`, які ми розбираємо наступним чином: - -```php -use Latte\Compiler\Nodes\StatementNode; -use Latte\Compiler\Nodes\Php\ExpressionNode; -use Latte\Compiler\Nodes\AreaNode; - -class ForeachNode extends StatementNode -{ - public ExpressionNode $expression; - public ExpressionNode $value; - - public static function create(Latte\Compiler\Tag $tag): self - { - $tag->expectArguments(); - $node = new self; - $node->expression = $tag->parser->parseExpression(); - $tag->parser->stream->consume('as'); - $node->value = $parser->parseExpression(); - return $node; - } -} -``` - -Вирази, які ми записали у змінні `$expression` і `$value`, являють собою вкладені вузли. - -.[tip] -Визначте змінні з підвузлами як **публічні**, щоб за необхідності їх можна було змінити на [наступних етапах обробки |#Compiler-Passes]. Також необхідно **зробити їх доступними** для [обходу |#AST-Traversing]. - -Для парних тегів, таких як наш, метод повинен також дозволити TemplateParser розібрати внутрішній вміст тега. Цим займається `yield`, який повертає пару ''[внутрішній вміст, кінцевий тег]''. Ми зберігаємо внутрішній вміст у змінній `$node->content`. - -```php -public AreaNode $content; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $endTag] = yield; - return $node; -} -``` - -Ключове слово `yield` викликає завершення методу `create()`, повертаючи керування назад у TemplateParser, який продовжує розбір вмісту, поки не досягне кінцевого тега. Потім він передає управління назад методу `create()`, який продовжує з того місця, на якому зупинився. Використання методу `yield`, автоматично повертає `Generator`. - -Ви також можете передати в `yield` масив імен тегів, для яких ви хочете зупинити розбір, якщо вони зустрічаються до кінцевого тега. Це допомагає нам реалізувати `{foreach}...{else}...{/foreach}` конструкцію. Якщо зустрічається `{else}`, ми розбираємо вміст після нього в `$node->elseContent`: - -```php -public AreaNode $content; -public ?AreaNode $elseContent = null; - -public static function create(Latte\Compiler\Tag $tag): \Generator -{ - // ... - [$node->content, $nextTag] = yield ['else']; - if ($nextTag?->name === 'else') { - [$node->elseContent] = yield; - } - - return $node; -} -``` - -Вузол, що повертається, завершує розбір тега. - - -Генерація PHP-коду .[#toc-generating-php-code] ----------------------------------------------- - -Кожен вузол повинен реалізувати метод `print()`. Повертає PHP-код, який рендерить задану частину шаблону (runtime-код). Як параметр йому передається об'єкт [api:Latte\Compiler\PrintContext], який має корисний метод `format()`, що спрощує складання результуючого коду. - -Метод `format(string $mask, ...$args)` приймає такі заповнювачі в масці: -- `%node` друкує Node -- `%dump` експортує значення в PHP -- `%raw` вставляє текст безпосередньо без будь-яких перетворень -- `%args` друкує ArrayNode в якості аргументів виклику функції -- `%line` друкує коментар із номером рядка -- `%escape(...)` екранує вміст -- `%modify(...)` застосовує модифікатор -- `%modifyContent(...)` застосовує модифікатор до блоків - - -Наша функція `print()` може мати такий вигляд (для простоти ми нехтуємо гілкою `else` ): - -```php -public function print(Latte\Compiler\PrintContext $context): string -{ - return $context->format( - <<<'XX' - foreach (%node as %node) %line { - %node - } - - XX, - $this->expression, - $this->value, - $this->position, - $this->content, - ); -} -``` - -Змінна `$this->position` вже визначена класом [api:Latte\Compiler\Node] і встановлюється парсером. Вона містить об'єкт [api:Latte\Compiler\Position] з позицією тега у вихідному коді у вигляді номера рядка і стовпця. - -Код часу виконання може використовувати допоміжні змінні. Щоб уникнути зіткнення зі змінними, використовуваними самим шаблоном, заведено префіксувати їх символами `$ʟ__`. - -Також під час виконання можуть використовуватися довільні значення, які передаються шаблону у вигляді провайдерів за допомогою методу [Extension::getProviders() |#getProviders]. Доступ до них здійснюється за допомогою `$this->global->...`. - - -Обхід AST .[#toc-ast-traversing] --------------------------------- - -Для того щоб переглянути дерево AST углиб, необхідно реалізувати метод `getIterator()`. Це забезпечить доступ до вкладених вузлів: - -```php -public function &getIterator(): \Generator -{ - yield $this->expression; - yield $this->value; - yield $this->content; - if ($this->elseContent) { - yield $this->elseContent; - } -} -``` - -Зверніть увагу, що `getIterator()` повертає посилання. Саме це дозволяє відвідувачам вузла замінювати окремі вузли іншими вузлами. - -.[warning] -Якщо вузол має підвузли, необхідно реалізувати цей метод і зробити доступними всі підвузли. В іншому разі може бути створено пролом у безпеці. Наприклад, режим пісочниці не зможе контролювати підноди і гарантувати, що в них не будуть викликатися недозволені конструкції. - -Оскільки ключове слово `yield` має бути присутнім у тілі методу, навіть якщо в нього немає дочірніх вузлів, запишіть його таким чином: - -```php -public function &getIterator(): \Generator -{ - if (false) { - yield; - } -} -``` - - -Компілятор передає .[#toc-compiler-passes] -========================================== - -Паси компілятора - це функції, які змінюють AST або збирають інформацію в них. Вони повертаються методом [Extension::getPasses() |#getPasses]. - - -Траверсер вузлів .[#toc-node-traverser] ---------------------------------------- - -Найпоширенішим способом роботи з AST є використання [api:Latte\Compiler\NodeTraverser]: - -```php -use Latte\Compiler\Node; -use Latte\Compiler\NodeTraverser; - -$ast = (new NodeTraverser)->traverse( - $ast, - enter: fn(Node $node) => ..., - leave: fn(Node $node) => ..., -); -``` - -Функція *enter* (тобто відвідувач) викликається при першій зустрічі з вузлом, до того, як будуть оброблені його підвузли. Функція *leave* викликається після відвідування всіх підвузлів. -Загальним шаблоном є те, що *enter* використовується для збору деякої інформації, а потім *leave* виконує модифікації на основі цієї інформації. До моменту виклику *leave* весь код усередині вузла вже буде відвідано і зібрано необхідну інформацію. - -Як модифікувати AST? Найпростіший спосіб - просто змінити властивості вузлів. Другий спосіб - повністю замінити вузол, повернувши новий вузол. Приклад: наступний код змінить усі цілі числа в AST на рядки (наприклад, 42 буде замінено на `'42'`). - -```php -use Latte\Compiler\Nodes\Php; - -$ast = (new NodeTraverser)->traverse( - $ast, - leave: function (Node $node) { - if ($node instanceof Php\Scalar\IntegerNode) { - return new Php\Scalar\StringNode((string) $node->value); - } - }, -); -``` - -AST може містити тисячі вузлів, і обхід усіх вузлів може бути повільним. У деяких випадках можна обійтися без повного обходу. - -Якщо ви шукаєте всі `Html\ElementNode` у дереві, ви знаєте, що після перегляду `Php\ExpressionNode` немає сенсу перевіряти всі його дочірні вузли, тому що HTML не може бути всередині виразів. У цьому випадку ви можете вказати обхіднику не переходити до вузла класу: - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Php\ExpressionNode) { - return NodeTraverser::DontTraverseChildren; - } - // ... - }, -); -``` - -Якщо ви шукаєте тільки один конкретний вузол, можна також повністю перервати обхід після його знаходження. - -```php -$ast = (new NodeTraverser)->traverse( - $ast, - enter: function (Node $node) { - if ($node instanceof Nodes\ParametersNode) { - return NodeTraverser::StopTraversal; - } - // ... - }, -); -``` - - -Помічники вузлів .[#toc-node-helpers] -------------------------------------- - -Клас [api:Latte\Compiler\NodeHelpers] надає кілька методів, які можуть знайти AST-вузли, що задовольняють певному зворотному виклику тощо. Показано кілька прикладів: - -```php -use Latte\Compiler\NodeHelpers; - -// знаходить усі вузли HTML-елементів -$elements = NodeHelpers::find($ast, fn(Node $node) => $node instanceof Nodes\Html\ElementNode); - -// знаходимо перший текстовий вузол -$text = NodeHelpers::findFirst($ast, fn(Node $node) => $node instanceof Nodes\TextNode); - -// перетворює PHP-значення node в дійсне значення -$value = NodeHelpers::toValue($node); - -// перетворює статичний текстовий вузол node в рядок -$text = NodeHelpers::toText($node); -``` diff --git a/latte/uk/custom-filters.texy b/latte/uk/custom-filters.texy new file mode 100644 index 0000000000..73c95c56a0 --- /dev/null +++ b/latte/uk/custom-filters.texy @@ -0,0 +1,231 @@ +Створення власних фільтрів +************************** + +.[perex] +Фільтри — це потужні інструменти для форматування та модифікації даних безпосередньо в шаблонах Latte. Вони пропонують чистий синтаксис за допомогою символу вертикальної риски (`|`) для перетворення змінних або результатів виразів у бажаний вихідний формат. + + +Що таке фільтри? +================ + +Фільтри в Latte — це, по суті, **PHP-функції, розроблені спеціально для перетворення вхідного значення на вихідне значення**. Вони застосовуються за допомогою запису з вертикальною рискою (`|`) всередині виразів шаблону (`{...}`). + +**Зручність:** Фільтри дозволяють інкапсулювати поширені завдання форматування (наприклад, форматування дат, зміна регістру, скорочення) або маніпуляції з даними в багаторазові одиниці. Замість повторення складного PHP-коду у ваших шаблонах, ви можете просто застосувати фільтр: +```latte +{* Замість складного PHP для скорочення: *} +{$article->text|truncate:100} + +{* Замість коду для форматування дати: *} +{$event->startTime|date:'Y-m-d H:i'} + +{* Застосування кількох перетворень: *} +{$product->name|lower|capitalize} +``` + +**Читабельність:** Використання фільтрів робить шаблони зрозумілішими та більш орієнтованими на презентацію, оскільки логіка перетворення переміщується до визначення фільтра. + +**Контекстна чутливість:** Ключовою перевагою фільтрів у Latte є їхня здатність бути [контекстно-чутливими |#Контекстні фільтри]. Це означає, що фільтр може розпізнавати тип вмісту, з яким він працює (HTML, JavaScript, простий текст тощо), і застосовувати відповідну логіку або екранування, що є критично важливим для безпеки та правильності, особливо при генерації HTML. + +**Інтеграція з логікою застосунку:** Так само, як і власні функції, PHP-об'єкт, що викликається, за фільтром може бути замиканням (closure), статичним методом або методом екземпляра. Це дозволяє фільтрам отримувати доступ до сервісів застосунку або даних, якщо це необхідно, хоча їхньою основною метою залишається *перетворення вхідного значення*. + +Latte за замовчуванням надає багатий набір [стандартних фільтрів |filters]. Власні фільтри дозволяють розширити цей набір форматуваннями та перетвореннями, специфічними для вашого проекту. + +Якщо вам потрібно виконати логіку, засновану на *кількох* входах, або у вас немає основного значення для перетворення, ймовірно, краще використовувати [власну функцію |custom-functions]. Якщо вам потрібно згенерувати складну розмітку або керувати потоком шаблону, розгляньте [власний тег |custom-tags]. + + +Створення та реєстрація фільтрів +================================ + +Існує кілька способів визначення та реєстрації власних фільтрів у Latte. + + +Пряма реєстрація за допомогою `addFilter()` +------------------------------------------- + +Найпростіший спосіб додати фільтр — це використання методу `addFilter()` безпосередньо на об'єкті `Latte\Engine`. Ви вказуєте назву фільтра (як він буде використовуватися в шаблоні) та відповідний PHP-об'єкт, що викликається. + +```php +$latte = new Latte\Engine; + +// Простий фільтр без аргументів +$latte->addFilter('initial', fn(string $s): string => mb_substr($s, 0, 1) . '.'); + +// Фільтр з необов'язковим аргументом +$latte->addFilter('shortify', function (string $s, int $len = 10): string { + return mb_substr($s, 0, $len); +}); + +// Фільтр, що обробляє масиви +$latte->addFilter('sum', fn(array $numbers): int|float => array_sum($numbers)); +``` + +**Використання в шаблоні:** + +```latte +{$name|initial} {* Виведе 'J.' якщо $name 'John' *} +{$description|shortify} {* Використає стандартну довжину 10 *} +{$description|shortify:50} {* Використає довжину 50 *} +{$prices|sum} {* Виведе суму елементів у масиві $prices *} +``` + +**Передача аргументів:** + +Значення ліворуч від вертикальної риски (`|`) завжди передається як *перший* аргумент функції фільтра. Будь-які параметри, вказані після двокрапки (`:`) в шаблоні, передаються як наступні аргументи. + +```latte +{$text|shortify:30} +// Викликає PHP-функцію shortify($text, 30) +``` + + +Реєстрація за допомогою розширення +---------------------------------- + +Для кращої організації, особливо при створенні багаторазових наборів фільтрів або їх поширенні як пакетів, рекомендованим способом є реєстрація їх у межах [розширення Latte |extending-latte#Latte Extension]: + +```php +namespace App\Latte; + +use Latte\Extension; + +class MyLatteExtension extends Extension +{ + public function getFilters(): array + { + return [ + 'initial' => $this->initial(...), + 'shortify' => $this->shortify(...), + ]; + } + + public function initial(string $s): string + { + return mb_substr($s, 0, 1) . '.'; + } + + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// Реєстрація +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +Цей підхід збереже логіку вашого фільтра інкапсульованою, а реєстрацію — простою. + + +Використання завантажувача фільтрів +----------------------------------- + +Latte дозволяє реєструвати завантажувач фільтрів за допомогою `addFilterLoader()`. Це єдиний об'єкт, що викликається, який Latte запитає про будь-яку невідому назву фільтра під час компіляції. Завантажувач повертає PHP-об'єкт, що викликається, фільтра або `null`. + +```php +$latte = new Latte\Engine; + +// Завантажувач може динамічно створювати/отримувати викликані фільтри +$latte->addFilterLoader(function (string $name): ?callable { + if ($name === 'myLazyFilter') { + // Уявіть тут складну ініціалізацію... + $service = get_some_expensive_service(); + return fn($value) => $service->process($value); + } + return null; +}); +``` + +Цей метод був переважно призначений для лінивого завантаження фільтрів з дуже **складною ініціалізацією**. Однак сучасні практики впровадження залежностей (dependency injection) зазвичай ефективніше справляються з лінивими сервісами. + +Завантажувачі фільтрів додають складності і загалом не рекомендуються на користь прямої реєстрації за допомогою `addFilter()` або в межах розширення за допомогою `getFilters()`. Використовуйте завантажувачі лише якщо у вас є серйозна, специфічна причина, пов'язана з проблемами продуктивності при ініціалізації фільтрів, які неможливо вирішити інакше. + + +Фільтри, що використовують клас з атрибутами +-------------------------------------------- + +Ще один елегантний спосіб визначення фільтрів — це використання методів у вашому [класі параметрів шаблону |develop#Параметри як клас]. Достатньо додати атрибут `#[Latte\Attributes\TemplateFilter]` до методу. + +```php +use Latte\Attributes\TemplateFilter; + +class TemplateParameters +{ + public function __construct( + public string $description, + // інші параметри... + ) {} + + #[TemplateFilter] + public function shortify(string $s, int $len = 10): string + { + return mb_substr($s, 0, $len); + } +} + +// Передача об'єкта до шаблону +$params = new TemplateParameters(description: '...'); +$latte->render('template.latte', $params); +``` + +Latte автоматично розпізнає та зареєструє методи, позначені цим атрибутом, коли об'єкт `TemplateParameters` передається до шаблону. Назва фільтра в шаблоні буде такою ж, як назва методу (`shortify` у цьому випадку). + +```latte +{* Використання фільтра, визначеного в класі параметрів *} +{$description|shortify:50} +``` + + +Контекстні фільтри +================== + +Іноді фільтр потребує більше інформації, ніж просто вхідне значення. Йому може знадобитися знати **тип вмісту** рядка, з яким він працює (наприклад, HTML, JavaScript, простий текст), або навіть змінити його. Це ситуація для контекстних фільтрів. + +Контекстний фільтр визначається так само, як звичайний фільтр, але його **перший параметр повинен бути** типізований як `Latte\Runtime\FilterInfo`. Latte автоматично розпізнає цей підпис і при виклику фільтра передає об'єкт `FilterInfo`. Наступні параметри отримують аргументи фільтра як зазвичай. + +```php +use Latte\Runtime\FilterInfo; +use Latte\ContentType; + +$latte->addFilter('money', function (FilterInfo $info, float $amount): string { + // 1. Перевірте вхідний тип вмісту (необов'язково, але рекомендовано) + // Дозвольте null (змінний вхід) або простий текст. Відхиліть, якщо застосовано до HTML тощо. + if (!in_array($info->contentType, [null, ContentType::Text], true)) { + $actualType = $info->contentType ?? 'mixed'; + throw new \RuntimeException( + "Фільтр |money використано в несумісному типі вмісту $actualType. Очікується text або null." + ); + } + + // 2. Виконайте перетворення + $formatted = number_format($amount, 2, '.', ',') . ' EUR'; + $htmlOutput = '' . htmlspecialchars($formatted) . ''; // Забезпечте правильне екранування! + + // 3. Декларуйте вихідний тип вмісту + $info->contentType = ContentType::Html; + + // 4. Поверніть результат + return $htmlOutput; +}); +``` + +`$info->contentType` — це рядкова константа з `Latte\ContentType` (наприклад, `ContentType::Html`, `ContentType::Text`, `ContentType::JavaScript` тощо) або `null`, якщо фільтр застосовано до змінної (`{$var|filter}`). Ви можете **читати** це значення, щоб перевірити вхідний контекст, і **записувати** в нього, щоб оголосити тип вихідного контексту. + +Встановлюючи тип вмісту на HTML, ви повідомляєте Latte, що рядок, повернутий вашим фільтром, є безпечним HTML. Latte тоді **не буде** застосовувати до цього результату своє стандартне автоматичне екранування. Це критично важливо, якщо ваш фільтр генерує HTML-розмітку. + +.[warning] +Якщо ваш фільтр генерує HTML, **ви несете відповідальність за правильне екранування будь-яких вхідних даних**, використаних у цьому HTML (як у випадку виклику `htmlspecialchars($formatted)` вище). Нехтування цим може створити XSS-вразливості. Якщо ваш фільтр повертає лише простий текст, вам не потрібно встановлювати `$info->contentType`. + + +Фільтри на блоках +----------------- + +Усі фільтри, застосовані до [блоків |tags#block], *повинні* бути контекстними. Це тому, що вміст блоку має визначений тип вмісту (зазвичай HTML), про який фільтр повинен знати. + +```latte +{block heading|money}1000{/block} +{* Фільтр 'money' отримає '1000' як другий аргумент + і $info->contentType буде ContentType::Html *} +``` + +Контекстні фільтри надають потужний контроль над тим, як дані обробляються на основі їхнього контексту, дозволяють розширені функції та забезпечують правильну поведінку екранування, особливо при генерації HTML-вмісту. diff --git a/latte/uk/custom-functions.texy b/latte/uk/custom-functions.texy new file mode 100644 index 0000000000..07deed2849 --- /dev/null +++ b/latte/uk/custom-functions.texy @@ -0,0 +1,144 @@ +Створення власних функцій +************************* + +.[perex] +Легко додавайте до шаблонів Latte власні допоміжні функції. Викликайте PHP-логіку безпосередньо у виразах для обчислень, доступу до сервісів або генерації динамічного вмісту, що збереже ваші шаблони чистими та потужними. + + +Що таке функції? +================ + +Функції в Latte дозволяють розширити набір функцій, які можна викликати в межах виразів у шаблонах (`{...}`). Ви можете уявити їх як **власні PHP-функції, доступні лише всередині ваших Latte-шаблонів**. Це надає кілька переваг: + +**Зручність:** Ви можете визначити допоміжну логіку (наприклад, обчислення, форматування або доступ до даних застосунку) і викликати її за допомогою простого, знайомого синтаксису функцій безпосередньо в шаблоні, так само, як ви б викликали `strlen()` або `date()` в PHP. + +```latte +{var $userInitials = initials($userName)} {* напр. 'J. D.' *} + +{if hasPermission('article', 'edit')} + Edit +{/if} +``` + +**Без забруднення глобального простору:** На відміну від визначення справжньої глобальної функції в PHP, функції Latte існують лише в контексті рендерингу шаблону. Вам не потрібно обтяжувати глобальний простір імен PHP допоміжними функціями, які специфічні лише для шаблонів. + +**Інтеграція з логікою застосунку:** PHP-об'єкт, що викликається, який стоїть за функцією Latte, може бути чим завгодно — анонімною функцією, статичним методом або методом екземпляра. Це означає, що ваші функції в шаблонах можуть легко отримувати доступ до сервісів застосунку, баз даних, конфігурації або будь-якої іншої необхідної логіки шляхом захоплення змінних (у випадку анонімних функцій) або за допомогою впровадження залежностей (у випадку об'єктів). Наведений вище приклад `hasPermission` це чітко демонструє, коли він, ймовірно, викликає на фоні сервіс авторизації. + +**Перевизначення нативних функцій (необов'язково):** Ви можете навіть визначити функцію Latte з такою ж назвою, як у нативної PHP-функції. У шаблоні замість оригінальної функції буде викликана ваша власна версія. Це може бути корисно для надання поведінки, специфічної для шаблону, або забезпечення послідовної обробки (наприклад, забезпечення того, що `strlen` завжди буде багатобайтово безпечною). Використовуйте цю функцію обережно, щоб уникнути непорозумінь. + +За замовчуванням Latte дозволяє викликати *всі* нативні PHP-функції (якщо вони не обмежені [Sandbox |sandbox]). Власні функції розширюють цю вбудовану бібліотеку специфічними потребами вашого проекту. + +Якщо ви лише перетворюєте одне значення, може бути доцільніше використовувати [власний фільтр |custom-filters]. + + +Створення та реєстрація функцій +=============================== + +Подібно до фільтрів, існує кілька способів визначення та реєстрації власних функцій. + + +Пряма реєстрація за допомогою `addFunction()` +--------------------------------------------- + +Найпростіший метод — це використання `addFunction()` на об'єкті `Latte\Engine`. Ви вказуєте назву функції (як вона буде відображатися в шаблоні) та відповідний PHP-об'єкт, що викликається. + +```php +$latte = new Latte\Engine; + +// Проста допоміжна функція +$latte->addFunction('initials', function (string $name): string { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; +}); +``` + +**Використання в шаблоні:** + +```latte +{var $userInitials = initials($userName)} +``` + +Аргументи функції в шаблоні передаються безпосередньо PHP-об'єкту, що викликається, в тому ж порядку. Функціональність PHP, така як підказки типів, значення за замовчуванням та змінні параметри (`...`), працює як очікувалося. + + +Реєстрація за допомогою розширення +---------------------------------- + +Для кращої організації та повторного використання реєструйте функції в межах [розширення Latte |extending-latte#Latte Extension]. Цей підхід рекомендований для складніших застосунків або спільних бібліотек. + +```php +namespace App\Latte; + +use Latte\Extension; +use Nette\Security\Authorizator; + +class MyLatteExtension extends Extension +{ + public function __construct( + // Припускаємо, що сервіс Authorizator існує + private Authorizator $authorizator, + ) { + } + + public function getFunctions(): array + { + // Реєстрація методів як функцій Latte + return [ + 'hasPermission' => $this->hasPermission(...), + ]; + } + + public function hasPermission(string $resource, string $action): bool + { + return $this->authorizator->isAllowed($resource, $action); + } +} + +// Реєстрація (припускаємо, що $container містить DI-контейнер) +$extension = $container->getByType(App\Latte\MyLatteExtension::class); +$latte = new Latte\Engine; +$latte->addExtension($extension); +``` + +Цей підхід наочно демонструє, як функції, визначені в Latte, можуть бути підтримані методами об'єктів, які можуть мати власні залежності, керовані контейнером для впровадження залежностей вашого застосунку або фабрикою. Це підтримує логіку ваших шаблонів пов'язаною з ядром застосунку, зберігаючи при цьому чітку організацію. + + +Функції, що використовують клас з атрибутами +-------------------------------------------- + +Так само, як і фільтри, функції можуть бути визначені як методи у вашому [класі параметрів шаблону |develop#Параметри як клас] за допомогою атрибута `#[Latte\Attributes\TemplateFunction]`. + +```php +use Latte\Attributes\TemplateFunction; + +class TemplateParameters +{ + public function __construct( + public string $userName, + // інші параметри... + ) {} + + // Цей метод буде доступний як {initials(...)} у шаблоні + #[TemplateFunction] + public function initials(string $name): string + { + preg_match_all('#\b\w#u', $name, $m); + return implode('. ', $m[0]) . '.'; + } +} + +// Передача об'єкта до шаблону +$params = new TemplateParameters(userName: 'John Doe', /* ... */); +$latte->render('template.latte', $params); +``` + +Latte автоматично виявить та зареєструє методи, позначені цим атрибутом, коли об'єкт параметрів передається до шаблону. Назва функції в шаблоні відповідає назві методу. + +```latte +{* Використання функції, визначеної в класі параметрів *} +{var $inits = initials($userName)} +``` + +**Контекстні функції?** + +На відміну від фільтрів, не існує прямого поняття "контекстних функцій", які б отримували об'єкт, подібний до `FilterInfo`. Функції працюють у межах виразів і зазвичай не потребують прямого доступу до контексту рендерингу або інформації про тип вмісту так само, як фільтри, застосовані до блоків. diff --git a/latte/uk/custom-tags.texy b/latte/uk/custom-tags.texy new file mode 100644 index 0000000000..96af747f17 --- /dev/null +++ b/latte/uk/custom-tags.texy @@ -0,0 +1,1135 @@ +Створення власних тегів +*********************** + +.[perex] +Ця сторінка надає вичерпний посібник зі створення власних тегів у Latte. Ми розглянемо все, від простих тегів до складніших сценаріїв із вкладеним вмістом та специфічними потребами парсингу, спираючись на ваше розуміння того, як Latte компілює шаблони. + +Власні теги надають найвищий рівень контролю над синтаксисом шаблону та логікою візуалізації, але вони також є найскладнішою точкою розширення. Перш ніж вирішити створити власний тег, завжди зважте, чи [не існує простішого рішення |extending-latte#Способи розширення Latte] або чи вже не існує відповідного тегу в [стандартному наборі |tags]. Використовуйте власні теги лише тоді, коли простіші альтернативи недостатні для ваших потреб. + + +Розуміння процесу компіляції +============================ + +Для ефективного створення власних тегів корисно пояснити, як Latte обробляє шаблони. Розуміння цього процесу пояснює, чому теги структуровані саме так і як вони вписуються в ширший контекст. + +Компіляція шаблону в Latte, спрощено, включає такі ключові кроки: + +1. **Лексичний аналіз:** Лексер читає вихідний код шаблону (файл `.latte`) і розбиває його на послідовність малих, окремих частин, що називаються **токенами** (наприклад, `{`, `foreach`, `$variable`, `}`, HTML-текст тощо). +2. **Парсинг:** Парсер бере цей потік токенів і конструює з нього осмислену деревоподібну структуру, що представляє логіку та вміст шаблону. Це дерево називається **абстрактним синтаксичним деревом (AST)**. +3. **Компіляційні проходи:** Перед генерацією PHP-коду Latte запускає [компіляційні проходи |compiler passes]. Це функції, які проходять по всьому AST і можуть його модифікувати або збирати інформацію. Цей крок є ключовим для функцій, таких як безпека ([Sandbox |sandbox]) або оптимізація. +4. **Генерація коду:** Нарешті, компілятор проходить по (потенційно зміненому) AST і генерує відповідний код PHP-класу. Цей PHP-код є тим, що фактично візуалізує шаблон під час виконання. +5. **Кешування:** Згенерований PHP-код зберігається на диску, що робить наступні візуалізації дуже швидкими, оскільки кроки 1-4 пропускаються. + +Насправді компіляція дещо складніша. Latte **має два** лексери та парсери: один для HTML-шаблону, а другий для PHP-подібного коду всередині тегів. А також парсинг не відбувається аж після токенізації, але лексер і парсер працюють паралельно у двох "потоках" і координуються. Повірте мені, запрограмувати це було ракетною наукою :-) + +Весь процес, від завантаження вмісту шаблону, через парсинг, до генерації кінцевого файлу, можна послідовно виконати за допомогою цього коду, з яким ви можете експериментувати та виводити проміжні результати: + +```php +$latte = new Latte\Engine; +$source = $latte->getLoader()->getContent($file); +$ast = $latte->parse($source); +$latte->applyPasses($ast); +$code = $latte->generate($ast, $file); +``` + + +Анатомія тегу +============= + +Створення повнофункціонального власного тегу в Latte включає кілька пов'язаних частин. Перш ніж перейти до реалізації, давайте розберемося з основними концепціями та термінологією, використовуючи аналогію з HTML та Document Object Model (DOM). + + +Теги проти Вузлів (Аналогія з HTML) +----------------------------------- + +У HTML ми пишемо **теги**, такі як `

                                                                                                                                    ` або `

                                                                                                                                    ...
                                                                                                                                    `. Ці теги є синтаксисом у вихідному коді. Коли браузер парсить цей HTML, він створює представлення в пам'яті, що називається **Document Object Model (DOM)**. У DOM HTML-теги представлені **вузлами** (зокрема, вузлами `Element` у термінології JavaScript DOM). З цими *вузлами* ми працюємо програмно (наприклад, за допомогою JavaScript `document.getElementById(...)` повертається вузол Element). Тег — це лише текстове представлення у вихідному файлі; вузол — це об'єктне представлення в логічному дереві. + +Latte працює схожим чином: + +- У файлі шаблону `.latte` ви пишете **теги Latte**, такі як `{foreach ...}` та `{/foreach}`. Це синтаксис, з яким ви, як автор шаблону, працюєте. +- Коли Latte **парсить** шаблон, він будує **Abstract Syntax Tree (AST)**. Це дерево складається з **вузлів**. Кожен тег Latte, HTML-елемент, частина тексту або вираз у шаблоні стає одним або кількома вузлами в цьому дереві. +- Базовим класом для всіх вузлів в AST є `Latte\Compiler\Node`. Так само, як DOM має різні типи вузлів (Element, Text, Comment), AST Latte має різні типи вузлів. Ви зустрінете `Latte\Compiler\Nodes\TextNode` для статичного тексту, `Latte\Compiler\Nodes\Html\ElementNode` для HTML-елементів, `Latte\Compiler\Nodes\Php\ExpressionNode` для виразів усередині тегів, і, що ключове для власних тегів, вузли, що успадковуються від `Latte\Compiler\Nodes\StatementNode`. + + +Чому `StatementNode`? +--------------------- + +HTML-елементи (`Html\ElementNode`) переважно представляють структуру та вміст. PHP-вирази (`Php\ExpressionNode`) представляють значення або обчислення. Але що щодо тегів Latte, таких як `{if}`, `{foreach}` або наш власний `{datetime}`? Ці теги *виконують дії*, керують потоком програми або генерують вивід на основі логіки. Це функціональні одиниці, які роблять Latte потужним шаблонізатором, а не просто мовою розмітки. + +У програмуванні такі одиниці, що виконують дії, часто називають "statements" (операторами). Тому вузли, що представляють ці функціональні теги Latte, зазвичай успадковуються від `Latte\Compiler\Nodes\StatementNode`. Це відрізняє їх від чисто структурних вузлів (як HTML-елементи) або вузлів, що представляють значення (як вирази). + + +Ключові компоненти +================== + +Розглянемо основні компоненти, необхідні для створення власного тегу: + + +Функція для парсингу тегу +------------------------- + +- Ця PHP callable функція парсить синтаксис тегу Latte (`{...}`) у вихідному шаблоні. +- Вона отримує інформацію про тег (наприклад, його назву, позицію та чи є він n:атрибутом) через об'єкт [api:Latte\Compiler\Tag]. +- Її основним інструментом для парсингу аргументів та виразів усередині роздільників тегу є об'єкт [api:Latte\Compiler\TagParser], доступний через `$tag->parser` (це інший парсер, ніж той, що парсить весь шаблон). +- Для парних тегів вона використовує `yield` для сигналізації Latte, щоб парсити внутрішній вміст між початковим та кінцевим тегом. +- Кінцевою метою функції парсингу є створення та повернення екземпляра **класу вузла**, який додається до AST. +- Зазвичай (хоча це не обов'язково) функцію парсингу реалізують як статичний метод (часто називається `create`) безпосередньо у відповідному класі вузла. Це зберігає логіку парсингу та представлення вузла акуратно в одному пакеті, дозволяє доступ до приватних/захищених елементів класу, якщо потрібно, та покращує організацію. + + +Клас вузла +---------- + +- Представляє *логічну функцію* вашого тегу в **Abstract Syntax Tree (AST)**. +- Містить розпарсену інформацію (наприклад, аргументи або вміст) як публічні властивості. Ці властивості часто містять інші екземпляри `Node` (наприклад, `ExpressionNode` для розпарсених аргументів, `AreaNode` для розпарсеного вмісту). +- Метод `print(PrintContext $context): string` генерує *PHP-код* (оператор або серію операторів), який виконує дію тегу під час візуалізації шаблону. +- Метод `getIterator(): \Generator` надає доступ до дочірніх вузлів (аргументів, вмісту) для проходження **компіляційними проходами**. Він повинен надавати посилання (`&`), щоб дозволити проходам потенційно модифікувати або замінювати підвузли. +- Після того, як весь шаблон розпарсено в AST, Latte запускає ряд [компіляційних проходів |compiler-passes]. Ці проходи обходять *весь* AST за допомогою методу `getIterator()`, наданого кожним вузлом. Вони можуть перевіряти вузли, збирати інформацію і навіть *модифікувати* дерево (наприклад, змінюючи публічні властивості вузлів або повністю замінюючи вузли). Цей дизайн, що вимагає комплексного `getIterator()`, є фундаментальним. Він дозволяє потужним функціям, таким як [Sandbox |sandbox], аналізувати та потенційно змінювати поведінку *будь-якої* частини шаблону, включаючи ваші власні теги, забезпечуючи безпеку та консистентність. + + +Реєстрація через розширення +--------------------------- + +- Вам потрібно повідомити Latte про ваш новий тег і яку функцію парсингу слід для нього використовувати. Це робиться в рамках [розширення Latte |extending-latte#Latte Extension]. +- Усередині вашого класу розширення ви реалізуєте метод `getTags(): array`. Цей метод повертає асоціативний масив, де ключі — це назви тегів (наприклад, `'mytag'`, `'n:myattribute'`), а значення — це PHP callable функції, що представляють їхні відповідні функції парсингу (наприклад, `MyNamespace\DatetimeNode::create(...)`). + +Резюме: **Функція парсингу тегу** перетворює *вихідний код шаблону* вашого тегу на **вузол AST**. **Клас вузла** потім може перетворити *себе* на виконуваний *PHP-код* для скомпільованого шаблону та надає доступ до своїх підвузлів для **компіляційних проходів** через `getIterator()`. **Реєстрація через розширення** пов'язує назву тегу з функцією парсингу та повідомляє про неї Latte. + +Тепер розглянемо, як реалізувати ці компоненти крок за кроком. + + +Створення простого тегу +======================= + +Давайте приступимо до створення вашого першого власного тегу Latte. Почнемо з дуже простого прикладу: тег з назвою `{datetime}`, який виводить поточну дату та час. **Спочатку цей тег не прийматиме жодних аргументів**, але ми вдосконалимо його пізніше в розділі [#"Парсинг аргументів тегу"]. Він також не має внутрішнього вмісту. + +Цей приклад проведе вас через основні кроки: визначення класу вузла, реалізація його методів `print()` та `getIterator()`, створення функції парсингу та, нарешті, реєстрація тегу. + +**Мета:** Реалізувати `{datetime}` для виведення поточної дати та часу за допомогою PHP-функції `date()`. + + +Створення класу вузла +--------------------- + +Спочатку нам потрібен клас, який представлятиме наш тег в Abstract Syntax Tree (AST). Як обговорювалося вище, ми успадковуємося від `Latte\Compiler\Nodes\StatementNode`. + +Створіть файл (наприклад, `DatetimeNode.php`) та визначте клас: + +```php +node = new self; + return $node; + } + + /** + * Генерує PHP-код, який буде виконано під час візуалізації шаблону. + */ + public function print(PrintContext $context): string + { + return $context->format( + 'echo date(\'Y-m-d H:i:s\') %line;', + $this->position, + ); + } + + /** + * Надає доступ до дочірніх вузлів для компіляційних проходів Latte. + */ + public function &getIterator(): \Generator + { + false && yield; + } +} +``` + +Коли Latte зустрічає `{datetime}` у шаблоні, він викликає функцію парсингу `create()`. Її завдання — повернути екземпляр `DatetimeNode`. + +Метод `print()` генерує PHP-код, який буде виконано під час візуалізації шаблону. Ми викликаємо метод `$context->format()`, який складає кінцевий рядок PHP-коду для скомпільованого шаблону. Перший аргумент, `'echo date('Y-m-d H:i:s') %line;'`, є маскою, в яку підставляються наступні параметри. Плейсхолдер `%line` вказує методу `format()`, щоб використати другий аргумент, яким є `$this->position`, і вставити коментар типу `/* line 15 */`, який пов'язує згенерований PHP-код назад до оригінального рядка шаблону, що є ключовим для налагодження. + +Властивість `$this->position` успадковується від базового класу `Node` і автоматично встановлюється парсером Latte. Вона містить об'єкт [api:Latte\Compiler\Position], який вказує, де тег був знайдений у вихідному файлі `.latte`. + +Метод `getIterator()` є фундаментальним для компіляційних проходів. Він повинен надавати всі дочірні вузли, але наш простий `DatetimeNode` наразі не має жодних аргументів чи вмісту, отже, жодних дочірніх вузлів. Однак метод все ще повинен існувати і бути генератором, тобто ключове слово `yield` має бути якимось чином присутнє в тілі методу. + + +Реєстрація через розширення +--------------------------- + +Нарешті, повідомимо Latte про новий тег. Створіть [клас розширення |extending-latte#Latte Extension] (наприклад, `MyLatteExtension.php`) та зареєструйте тег у його методі `getTags()`. + +```php + Мапа: 'назва-тегу' => функція-парсингу + */ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + // Пізніше тут зареєструйте більше тегів + ]; + } +} +``` + +Потім зареєструйте це розширення в Latte Engine: + +```php +$latte = new Latte\Engine; +$latte->addExtension(new App\Latte\MyLatteExtension); +``` + +Створіть шаблон: + +```latte +

                                                                                                                                    Сторінку згенеровано: {datetime}

                                                                                                                                    +``` + +Очікуваний вивід: `

                                                                                                                                    Сторінку згенеровано: 2023-10-27 11:00:00

                                                                                                                                    ` + + +Резюме цього етапу +------------------ + +Ми успішно створили базовий власний тег `{datetime}`. Ми визначили його представлення в AST (`DatetimeNode`), обробили його парсинг (`create()`), вказали, як він повинен генерувати PHP-код (`print()`), забезпечили доступність його дочірніх елементів для проходження (`getIterator()`) та зареєстрували його в Latte. + +У наступному розділі ми вдосконалимо цей тег, щоб він приймав аргументи, та покажемо, як парсити вирази та керувати дочірніми вузлами. + + +Парсинг аргументів тегу +======================= + +Наш простий тег `{datetime}` працює, але він не дуже гнучкий. Вдосконалимо його, щоб він приймав необов'язковий аргумент: рядок форматування для функції `date()`. Необхідний синтаксис буде `{datetime $format}`. + +**Мета:** Змінити `{datetime}` так, щоб він приймав необов'язковий PHP-вираз як аргумент, який буде використано як рядок форматування для `date()`. + + +Представлення `TagParser` +------------------------- + +Перш ніж змінювати код, важливо зрозуміти інструмент, який ми будемо використовувати: [api:Latte\Compiler\TagParser]. Коли основний парсер Latte (`TemplateParser`) зустрічає тег Latte, такий як `{datetime ...}` або n:атрибут, він делегує парсинг вмісту *всередині* тегу (частини між `{` та `}` або значення атрибута) спеціалізованому `TagParser`. + +Цей `TagParser` працює виключно з **аргументами тегу**. Його завдання — обробляти токени, що представляють ці аргументи. Ключовим є те, що **він повинен обробити весь вміст**, який йому надано. Якщо ваша функція парсингу завершується, але `TagParser` не досяг кінця аргументів (перевіряється через `$tag->parser->isEnd()`), Latte викине виняток, оскільки це вказує на те, що всередині тегу залишилися неочікувані токени. Навпаки, якщо тег *вимагає* аргументи, ви повинні на початку вашої функції парсингу викликати `$tag->expectArguments()`. Цей метод перевіряє, чи присутні аргументи, і викидає допоміжний виняток, якщо тег був використаний без будь-яких аргументів. + +`TagParser` пропонує корисні методи для парсингу різних видів аргументів: + +- `parseExpression(): ExpressionNode`: Парсить PHP-подібний вираз (змінні, літерали, оператори, виклики функцій/методів тощо). Обробляє синтаксичний цукор Latte, такий як обробка простих буквено-цифрових рядків як рядків у лапках (наприклад, `foo` парситься, ніби це було `'foo'`). +- `parseUnquotedStringOrExpression(): ExpressionNode`: Парсить або стандартний вираз, або *рядок без лапок*. Рядки без лапок — це послідовності, дозволені Latte без лапок, часто використовуються для таких речей, як шляхи до файлів (наприклад, `{include ../file.latte}`). Якщо парсить рядок без лапок, повертає `StringNode`. +- `parseArguments(): ArrayNode`: Парсить аргументи, розділені комами, потенційно з ключами, як `10, name: 'John', true`. +- `parseModifier(): ModifierNode`: Парсить фільтри, такі як `|upper|truncate:10`. +- `parseType(): ?SuperiorTypeNode`: Парсить PHP-підказки типів, такі як `int`, `?string`, `array|Foo`. + +Для складніших або низькорівневих потреб парсингу ви можете безпосередньо взаємодіяти з [потоком токенів |api:Latte\Compiler\TokenStream] через `$tag->parser->stream`. Цей об'єкт надає методи для перевірки та обробки окремих токенів: + +- `$tag->parser->stream->is(...): bool`: Перевіряє, чи *поточний* токен відповідає будь-якому із зазначених типів (наприклад, `Token::Php_Variable`) або літеральних значень (наприклад, `'as'`) без його споживання. Корисно для погляду наперед. +- `$tag->parser->stream->consume(...): Token`: Споживає *поточний* токен і пересуває позицію потоку вперед. Якщо надані очікувані типи/значення токенів як аргументи, а поточний токен не відповідає, викидає `CompileException`. Використовуйте це, коли *очікуєте* певний токен. +- `$tag->parser->stream->tryConsume(...): ?Token`: Намагається спожити *поточний* токен *лише якщо* він відповідає одному із зазначених типів/значень. Якщо відповідає, споживає токен і повертає його. Якщо не відповідає, залишає позицію потоку незмінною і повертає `null`. Використовуйте це для необов'язкових токенів або коли вибираєте між різними синтаксичними шляхами. + + +Оновлення функції парсингу `create()` +------------------------------------- + +З цим розумінням змінимо метод `create()` у `DatetimeNode` так, щоб він парсив необов'язковий аргумент формату за допомогою `$tag->parser`. + +```php +node = new self; + + // Перевіримо, чи існують якісь токени + if (!$tag->parser->isEnd()) { + // Парсимо аргумент як PHP-подібний вираз за допомогою TagParser. + $node->format = $tag->parser->parseExpression(); + } + + return $node; + } + + // ... методи print() та getIterator() будуть оновлені далі ... +} +``` + +Ми додали публічну властивість `$format`. У `create()` тепер використовуємо `$tag->parser->isEnd()` для перевірки, чи *існують* аргументи. Якщо так, `$tag->parser->parseExpression()` обробляє токени для виразу. Оскільки `TagParser` повинен обробити всі вхідні токени, Latte автоматично викине помилку, якщо користувач напише щось неочікуване після виразу формату (наприклад, `{datetime 'Y-m-d', unexpected}`). + + +Оновлення методу `print()` +-------------------------- + +Тепер змінимо метод `print()` так, щоб він використовував розпарсений вираз формату, збережений у `$this->format`. Якщо формат не був наданий (`$this->format` є `null`), ми повинні використовувати стандартний рядок форматування, наприклад, `'Y-m-d H:i:s'`. + +```php + public function print(PrintContext $context): string + { + $formatNode = $this->format ?? new StringNode('Y-m-d H:i:s'); + + // %node виведе PHP-кодове представлення $formatNode. + return $context->format( + 'echo date(%node) %line;', + $formatNode, + $this->position + ); + } +``` + +У змінну `$formatNode` ми зберігаємо вузол AST, що представляє рядок форматування для PHP-функції `date()`. Ми використовуємо тут оператор нульового злиття (`??`). Якщо користувач надав аргумент у шаблоні (наприклад, `{datetime 'd.m.Y'}`), то властивість `$this->format` містить відповідний вузол (у цьому випадку `StringNode` зі значенням `'d.m.Y'`), і цей вузол використовується. Якщо користувач не надав аргумент (написав лише `{datetime}`), властивість `$this->format` є `null`, і замість цього ми створюємо новий `StringNode` зі стандартним форматом `'Y-m-d H:i:s'`. Це гарантує, що `$formatNode` завжди містить дійсний вузол AST для формату. + +У масці `'echo date(%node) %line;'` використовується новий плейсхолдер `%node`, який вказує методу `format()`, щоб взяти перший наступний аргумент (яким є наш `$formatNode`), викликати його метод `print()` (який поверне його PHP-кодове представлення) та вставити результат на позицію плейсхолдера. + + +Реалізація `getIterator()` для підвузлів +---------------------------------------- + +Наш `DatetimeNode` тепер має дочірній вузол: вираз `$format`. **Ми повинні** зробити цей дочірній вузол доступним для компіляційних проходів, надавши його в методі `getIterator()`. Не забудьте надати *посилання* (`&`), щоб дозволити проходам потенційно замінити вузол. + +```php + public function &getIterator(): \Generator + { + if ($this->format) { + yield $this->format; + } + } +``` + +Чому це так важливо? Уявіть собі прохід Sandbox, який повинен перевірити, чи аргумент `$format` не містить забороненого виклику функції (наприклад, `{datetime dangerousFunction()}`). Якщо `getIterator()` не надасть `$this->format`, прохід Sandbox ніколи не побачить виклик `dangerousFunction()` всередині аргументу нашого тегу, що створить потенційну діру в безпеці. Надавши його, ми дозволяємо Sandbox (та іншим проходам) перевіряти та потенційно модифікувати вузол виразу `$format`. + + +Використання вдосконаленого тегу +-------------------------------- + +Тег тепер правильно обробляє необов'язковий аргумент: + +```latte +Стандартний формат: {datetime} +Власний формат: {datetime 'd.m.Y'} +Використання змінної: {datetime $userDateFormatPreference} + +{* Це спричинило б помилку після парсингу 'd.m.Y', оскільки ", foo" є неочікуваним *} +{* {datetime 'd.m.Y', foo} *} +``` + +Далі ми розглянемо створення парних тегів, які обробляють вміст між ними. + + +Обробка парних тегів +==================== + +Досі наш тег `{datetime}` був *самозакривним* (концептуально). Він не мав вмісту між початковим та кінцевим тегом. Однак багато корисних тегів працюють з блоком вмісту шаблону. Вони називаються **парними тегами**. Приклади включають `{if}...{/if}`, `{block}...{/block}` або власний тег, який ми зараз створимо: `{debug}...{/debug}`. + +Цей тег дозволить нам включати в наші шаблони налагоджувальну інформацію, яка повинна бути видимою лише під час розробки. + +**Мета:** Створити парний тег `{debug}`, вміст якого візуалізується лише тоді, коли активний специфічний прапорець "режиму розробки". + + +Представлення провайдерів +------------------------- + +Іноді ваші теги потребують доступу до даних або сервісів, які не передаються безпосередньо як параметри шаблону. Наприклад, визначення, чи знаходиться програма в режимі розробки, доступ до об'єкта користувача або отримання конфігураційних значень. Latte надає механізм, що називається **провайдерами** (Providers) для цієї мети. + +Провайдери реєструються у вашому [розширенні |extending-latte#Latte Extension] за допомогою методу `getProviders()`. Цей метод повертає асоціативний масив, де ключі — це назви, під якими провайдери будуть доступні в коді шаблону під час виконання, а значення — це фактичні дані або об'єкти. + +Усередині PHP-коду, згенерованого методом `print()` вашого тегу, ви можете отримати доступ до цих провайдерів через спеціальну властивість об'єкта `$this->global`. Оскільки ця властивість спільна для всіх розширень, хорошою практикою є **префіксування назв ваших провайдерів** для запобігання потенційних колізій імен з ключовими провайдерами Latte або провайдерами з інших розширень третіх сторін. Загальною конвенцією є використання короткого, унікального префікса, пов'язаного з вашим виробником або назвою розширення. Для нашого прикладу ми використаємо префікс `app`, і прапорець режиму розробки буде доступний як `$this->global->appDevMode`. + + +Ключове слово `yield` для парсингу вмісту +----------------------------------------- + +Як ми кажемо парсеру Latte, щоб він обробив вміст *між* `{debug}` та `{/debug}`? Тут на допомогу приходить ключове слово `yield`. + +Коли `yield` використовується у функції `create()`, функція стає [PHP-генератором |https://www.php.net/manual/en/language.generators.overview.php]. Її виконання призупиняється, і керування повертається до основного `TemplateParser`. `TemplateParser` потім продовжує парсити вміст шаблону *доки* не зустріне відповідний закриваючий тег (`{/debug}` у нашому випадку). + +Як тільки знайдено закриваючий тег, `TemplateParser` відновлює виконання нашої функції `create()` безпосередньо після оператора `yield`. Значення, *повернене* оператором `yield`, є масивом, що містить два елементи: + +1. `AreaNode`, що представляє розпарсений вміст між початковим та кінцевим тегом. +2. Об'єкт `Tag`, що представляє закриваючий тег (наприклад, `{/debug}`). + +Створимо клас `DebugNode` та його метод `create`, що використовує `yield`. + +```php +node = new self; + + // Призупинити парсинг, отримати внутрішній вміст та кінцевий тег, коли знайдено {/debug} + [$node->content, $endTag] = yield; + + return $node; + } + + // ... print() та getIterator() будуть реалізовані далі ... +} +``` + +Примітка: `$endTag` є `null`, якщо тег використовується як n:атрибут, тобто `
                                                                                                                                    ...
                                                                                                                                    `. + + +Реалізація `print()` для умовної візуалізації +--------------------------------------------- + +Метод `print()` тепер повинен генерувати PHP-код, який під час виконання перевіряє провайдера `appDevMode` і виконує код для внутрішнього вмісту лише тоді, коли прапорець встановлено в true. + +```php + public function print(PrintContext $context): string + { + // Генерує PHP-оператор 'if', який під час виконання перевіряє провайдера + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + // Якщо в режимі розробки, виводить внутрішній вміст + %node + } + + XX, + $this->position, // Для %line коментаря + $this->content, // Вузол, що містить AST внутрішнього вмісту + ); + } +``` + +Це просто. Ми використовуємо `PrintContext::format()` для створення стандартного PHP-оператора `if`. Усередині `if` ми розміщуємо плейсхолдер `%node` для `$this->content`. Latte рекурсивно викличе `$this->content->print($context)` для генерації PHP-коду для внутрішньої частини тегу, але лише якщо `$this->global->appDevMode` під час виконання оцінюється як true. + + +Реалізація `getIterator()` для вмісту +------------------------------------- + +Так само, як і з вузлом аргументу в попередньому прикладі, наш `DebugNode` тепер має дочірній вузол: `AreaNode $content`. Ми повинні зробити його доступним, надавши його в `getIterator()`: + +```php + public function &getIterator(): \Generator + { + // Надає посилання на вузол вмісту + yield $this->content; + } +``` + +Це дозволяє компіляційним проходам спускатися до вмісту нашого тегу `{debug}`, що важливо, навіть якщо вміст візуалізується умовно. Наприклад, Sandbox повинен аналізувати вміст незалежно від того, чи є `appDevMode` true чи false. + + +Реєстрація та використання +-------------------------- + +Зареєструйте тег та провайдера у вашому розширенні: + +```php +class MyLatteExtension extends Extension +{ + // Припускаємо, що $isDevelopmentMode визначається десь (наприклад, з конфігурації) + public function __construct( + private bool $isDevelopmentMode, + ) { + } + + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), // Реєстрація нового тегу + ]; + } + + public function getProviders(): array + { + return [ + 'appDevMode' => $this->isDevelopmentMode, // Реєстрація провайдера + ]; + } +} + +// Під час реєстрації розширення: +$isDev = true; // Визначте це на основі середовища вашої програми +$latte->addExtension(new App\Latte\MyLatteExtension($isDev)); +``` + +І його використання в шаблоні: + +```latte +

                                                                                                                                    Звичайний вміст, видимий завжди.

                                                                                                                                    + +{debug} +
                                                                                                                                    + ID поточного користувача: {$user->id} + Час запиту: {=time()} +
                                                                                                                                    +{/debug} + +

                                                                                                                                    Інший звичайний вміст.

                                                                                                                                    +``` + + +Інтеграція n:атрибутів +---------------------- + +Latte пропонує зручний скорочений запис для багатьох парних тегів: [n:атрибути |syntax#n:атрибути]. Якщо у вас є парний тег, такий як `{tag}...{/tag}`, і ви хочете, щоб його ефект застосовувався безпосередньо до одного HTML-елемента, ви часто можете записати його більш лаконічно як атрибут `n:tag` на цьому елементі. + +Для більшості стандартних парних тегів, які ви визначаєте (як наш `{debug}`), Latte автоматично дозволить відповідну версію `n:` атрибута. Під час реєстрації вам не потрібно робити нічого додаткового: + +```latte +{* Стандартне використання парного тегу *} +{debug}
                                                                                                                                    Інформація для налагодження
                                                                                                                                    {/debug} + +{* Еквівалентне використання з n:атрибутом *} +
                                                                                                                                    Інформація для налагодження
                                                                                                                                    +``` + +Обидві версії візуалізують `
                                                                                                                                    ` лише якщо `$this->global->appDevMode` є true. Префікси `inner-` та `tag-` також працюють, як очікувалося. + +Іноді логіка вашого тегу може потребувати трохи іншої поведінки залежно від того, чи використовується він як стандартний парний тег чи як n:атрибут, або чи використовується префікс, такий як `n:inner-tag` чи `n:tag-tag`. Об'єкт `Latte\Compiler\Tag`, переданий вашій функції парсингу `create()`, надає цю інформацію: + +- `$tag->isNAttribute(): bool`: Повертає `true`, якщо тег парситься як n:атрибут +- `$tag->prefix: ?string`: Повертає префікс, використаний з n:атрибутом, який може бути `null` (не n:атрибут), `Tag::PrefixNone`, `Tag::PrefixInner` або `Tag::PrefixTag` + +Тепер, коли ми розуміємо прості теги, парсинг аргументів, парні теги, провайдерів та n:атрибути, давайте розглянемо складніший сценарій, що включає теги, вкладені в інші теги, використовуючи наш тег `{debug}` як вихідну точку. + + +Проміжні теги +============= + +Деякі парні теги дозволяють або навіть вимагають, щоб інші теги з'являлися *всередині* них перед кінцевим закриваючим тегом. Вони називаються **проміжними тегами**. Класичні приклади включають `{if}...{elseif}...{else}...{/if}` або `{switch}...{case}...{default}...{/switch}`. + +Розширимо наш тег `{debug}`, щоб він підтримував необов'язкову клаузулу `{else}`, яка буде візуалізована, коли програма *не* перебуває в режимі розробки. + +**Мета:** Змінити `{debug}` так, щоб він підтримував необов'язковий проміжний тег `{else}`. Кінцевий синтаксис повинен бути `{debug} ... {else} ... {/debug}`. + + +Парсинг проміжних тегів за допомогою `yield` +-------------------------------------------- + +Ми вже знаємо, що `yield` призупиняє функцію парсингу `create()` і повертає розпарсений вміст разом із кінцевим тегом. Однак `yield` пропонує більше контролю: ви можете надати йому масив *назв проміжних тегів*. Коли парсер зустрічає будь-який із цих зазначених тегів **на тому ж рівні вкладеності** (тобто як прямих нащадків батьківського тегу, а не всередині інших блоків або тегів усередині нього), він також зупиняє парсинг. + +Коли парсинг зупиняється через проміжний тег, він зупиняє парсинг вмісту, відновлює генератор `create()` і передає назад частково розпарсений вміст та **проміжний тег** сам (замість кінцевого закриваючого тегу). Наша функція `create()` потім може обробити цей проміжний тег (наприклад, розпарсити його аргументи, якщо вони були) і знову використати `yield` для парсингу *наступної* частини вмісту аж до *кінцевого* закриваючого тегу або іншого очікуваного проміжного тегу. + +Змінимо `DebugNode::create()` так, щоб він очікував `{else}`: + +```php +node = new self; + + // yield та очікувати або {/debug}, або {else} + [$node->thenContent, $nextTag] = yield ['else']; + + // Перевірити, чи тег, на якому ми зупинилися, був {else} + if ($nextTag?->name === 'else') { + // Yield знову для парсингу вмісту між {else} та {/debug} + [$node->elseContent, $endTag] = yield; + } + + return $node; + } + + // ... print() та getIterator() будуть оновлені далі ... +} +``` + +Тепер `yield ['else']` каже Latte зупинити парсинг не тільки для `{/debug}`, але й для `{else}`. Якщо `{else}` знайдено, `$nextTag` міститиме об'єкт `Tag` для `{else}`. Потім ми знову використовуємо `yield` без аргументів, що означає, що тепер ми очікуємо лише кінцевий тег `{/debug}`, і зберігаємо результат у `$node->elseContent`. Якщо `{else}` не було знайдено, `$nextTag` був би `Tag` для `{/debug}` (або `null`, якщо використовується як n:атрибут), а `$node->elseContent` залишився б `null`. + + +Реалізація `print()` з `{else}` +------------------------------- + +Метод `print()` повинен відображати нову структуру. Він повинен генерувати PHP-оператор `if/else` на основі провайдера `appDevMode`. + +```php + public function print(PrintContext $context): string + { + return $context->format( + <<<'XX' + if ($this->global->appDevMode) %line { + %node // Код для гілки 'then' (вміст {debug}) + } else { + %node // Код для гілки 'else' (вміст {else}) + } + + XX, + $this->position, // Номер рядка для умови 'if' + $this->thenContent, // Перший плейсхолдер %node + $this->elseContent ?? new NopNode, // Другий плейсхолдер %node + ); + } +``` + +Це стандартна PHP-структура `if/else`. Ми використовуємо `%node` двічі; `format()` замінює надані вузли послідовно. Ми використовуємо `?? new NopNode` для уникнення помилок, якщо `$this->elseContent` є `null` – `NopNode` просто нічого не виведе. + + +Реалізація `getIterator()` для обох вмістів +------------------------------------------- + +Тепер ми маємо потенційно два дочірні вузли вмісту (`$thenContent` та `$elseContent`). Ми повинні надати обидва, якщо вони існують: + +```php + public function &getIterator(): \Generator + { + yield $this->thenContent; + if ($this->elseContent) { + yield $this->elseContent; + } + } +``` + + +Використання вдосконаленого тегу +-------------------------------- + +Тег тепер може бути використаний з необов'язковою клаузулою `{else}`: + +```latte +{debug} +

                                                                                                                                    Відображення налагоджувальної інформації, оскільки devMode УВІМКНЕНО.

                                                                                                                                    +{else} +

                                                                                                                                    Налагоджувальна інформація прихована, оскільки devMode ВИМКНЕНО.

                                                                                                                                    +{/debug} +``` + + +Обробка стану та вкладеності +============================ + +Наші попередні приклади (`{datetime}`, `{debug}`) були відносно безстановими в межах своїх методів `print()`. Вони або безпосередньо виводили вміст, або виконували просту умовну перевірку на основі глобального провайдера. Однак багато тегів потребують керування якоюсь формою **стану** під час візуалізації або включають оцінку виразів користувача, які повинні бути виконані лише один раз для продуктивності або коректності. Далі ми повинні розглянути, що відбувається, коли наші власні теги **вкладені**. + +Проілюструємо ці концепції, створивши тег `{repeat $count}...{/repeat}`. Цей тег повторюватиме свій внутрішній вміст `$count` разів. + +**Мета:** Реалізувати `{repeat $count}`, який повторює свій вміст зазначену кількість разів. + + +Потреба в тимчасових та унікальних змінних +------------------------------------------ + +Уявіть, що користувач пише: + +```latte +{repeat rand(1, 5)} Вміст {/repeat} +``` + +Якщо б ми наївно згенерували PHP `for` цикл таким чином у нашому методі `print()`: + +```php +// Спрощений, НЕПРАВИЛЬНИЙ згенерований код +for ($i = 0; $i < rand(1, 5); $i++) { + // вивід вмісту +} +``` +Це було б неправильно! Вираз `rand(1, 5)` був би **переоцінений при кожній ітерації циклу**, що призвело б до непередбачуваної кількості повторень. Нам потрібно оцінити вираз `$count` *один раз* перед початком циклу та зберегти його результат. + +Ми згенеруємо PHP-код, який спочатку оцінює вираз кількості та зберігає його в **тимчасовій змінній виконання**. Щоб запобігти колізіям зі змінними, визначеними користувачем шаблону, *та* внутрішніми змінними Latte (такими як `$ʟ_...`), ми використаємо конвенцію префікса **`$__` (подвійне підкреслення)** для наших тимчасових змінних. + +Згенерований код тоді виглядав би так: + +```php +$__count = rand(1, 5); +for ($__i = 0; $__i < $__count; $__i++) { + // вивід вмісту +} +``` + +Тепер розглянемо вкладеність: + +```latte +{repeat $countA} {* Зовнішній цикл *} + {repeat $countB} {* Внутрішній цикл *} + ... + {/repeat} +{/repeat} +``` + +Якщо б і зовнішній, і внутрішній тег `{repeat}` генерували код, що використовує *однакові* назви тимчасових змінних (наприклад, `$__count` та `$__i`), внутрішній цикл перезаписав би змінні зовнішнього циклу, що порушило б логіку. + +Нам потрібно забезпечити, щоб тимчасові змінні, згенеровані для кожного екземпляра тегу `{repeat}`, були **унікальними**. Цього ми досягнемо за допомогою `PrintContext::generateId()`. Цей метод повертає унікальне ціле число під час фази компіляції. Ми можемо додати цей ID до назв наших тимчасових змінних. + +Отже, замість `$__count` ми будемо генерувати `$__count_1` для першого тегу repeat, `$__count_2` для другого тощо. Аналогічно для лічильника циклу ми використаємо `$__i_1`, `$__i_2` тощо. + + +Реалізація `RepeatNode` +----------------------- + +Давайте створимо клас вузла. + +```php +expectArguments(); // переконується, що $count надано + $node = $tag->node = new self; + // Парсує вираз кількості + $node->count = $tag->parser->parseExpression(); + // Отримання внутрішнього вмісту + [$node->content] = yield; + return $node; + } + + /** + * Генерує PHP 'for' цикл з унікальними назвами змінних. + */ + public function print(PrintContext $context): string + { + // Генерування унікальних назв змінних + $id = $context->generateId(); + $countVar = '$__count_' . $id; // напр. $__count_1, $__count_2, тощо. + $iteratorVar = '$__i_' . $id; // напр. $__i_1, $__i_2, тощо. + + return $context->format( + <<<'XX' + // Оцінка виразу кількості *один раз* та збереження + %raw = (int) (%node); + // Цикл з використанням збереженої кількості та унікальної ітераційної змінної + for (%raw = 0; %2.raw < %0.raw; %2.raw++) %line { + %node // Візуалізація внутрішнього вмісту + } + + XX, + $countVar, // %0 - Змінна для збереження кількості + $this->count, // %1 - Вузол виразу для кількості + $iteratorVar, // %2 - Назва ітераційної змінної циклу + $this->position, // %3 - Коментар з номером рядка для самого циклу + $this->content // %4 - Вузол внутрішнього вмісту + ); + } + + /** + * Надає дочірні вузли (вираз кількості та вміст). + */ + public function &getIterator(): \Generator + { + yield $this->count; + yield $this->content; + } +} +``` + +Метод `create()` парсить необхідний вираз `$count` за допомогою `parseExpression()`. Спочатку викликається `$tag->expectArguments()`. Це гарантує, що користувач надав *щось* після `{repeat}`. Хоча `$tag->parser->parseExpression()` зазнало б невдачі, якби нічого не було надано, повідомлення про помилку могло б бути про неочікуваний синтаксис. Використання `expectArguments()` надає набагато чіткішу помилку, конкретно вказуючи, що аргументи відсутні для тегу `{repeat}`. + +Метод `print()` генерує PHP-код, відповідальний за виконання логіки повторення під час виконання. Він починається з генерування унікальних назв для тимчасових PHP-змінних, які йому знадобляться. + +Метод `$context->format()` викликається з новим плейсхолдером `%raw`, який вставляє *сирий рядок*, наданий як відповідний аргумент. Тут він вставляє унікальну назву змінної, збережену в `$countVar` (наприклад, `$__count_1`). А що щодо `%0.raw` та `%2.raw`? Це демонструє **позиційні плейсхолдери**. Замість простого `%raw`, який бере *наступний* доступний сирий аргумент, `%2.raw` явно бере аргумент з індексом 2 (яким є `$iteratorVar`) і вставляє його сире рядкове значення. Це дозволяє нам повторно використовувати рядок `$iteratorVar` без його багаторазової передачі в списку аргументів для `format()`. + +Цей ретельно сконструйований виклик `format()` генерує ефективний та безпечний PHP-цикл, який правильно обробляє вираз кількості та уникає колізій назв змінних, навіть коли теги `{repeat}` вкладені. + + +Реєстрація та використання +-------------------------- + +Зареєструйте тег у вашому розширенні: + +```php +use App\Latte\RepeatNode; + +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), // Реєстрація тегу repeat + ]; + } +} +``` + +Використовуйте його в шаблоні, включаючи вкладеність: + +```latte +{var $rows = rand(5, 7)} +{var $cols = rand(3, 5)} + +{repeat $rows} +
                                                                                                                                    + {repeat $cols} + + {/repeat} + +{/repeat} +``` + +Цей приклад демонструє, як обробляти стан (лічильники циклів) та потенційні проблеми з вкладеністю за допомогою тимчасових змінних з префіксом `$__` та унікальних ID від `PrintContext::generateId()`. + + +Чисті n:атрибути +---------------- + +Хоча багато `n:атрибутів`, таких як `n:if` або `n:foreach`, служать зручними скороченнями для їхніх відповідників у парних тегах (`{if}...{/if}`, `{foreach}...{/foreach}`), Latte також дозволяє визначати теги, які *існують лише* у формі n:атрибута. Вони часто використовуються для модифікації атрибутів або поведінки HTML-елемента, до якого вони прикріплені. + +Стандартні приклади, вбудовані в Latte, включають [`n:class` |tags#n:class], який допомагає динамічно зібрати атрибут `class`, та [`n:attr` |tags#n:attr], який може встановлювати кілька довільних атрибутів. + +Створимо власний чистий n:атрибут: `n:confirm`, який додасть JavaScript діалогове вікно підтвердження перед виконанням дії (наприклад, переходом за посиланням або відправкою форми). + +**Мета:** Реалізувати `n:confirm="'Ви впевнені?'"`, який додасть обробник `onclick` для запобігання стандартній дії, якщо користувач скасує діалогове вікно підтвердження. + + +Реалізація `ConfirmNode` +------------------------ + +Нам потрібен клас Node та функція парсингу. + +```php +expectArguments(); + $node = $tag->node = new self; + $node->message = $tag->parser->parseExpression(); + return $node; + } + + /** + * Генерує код атрибута 'onclick' з правильним екрануванням. + */ + public function print(PrintContext $context): string + { + // Забезпечує правильне екранування для контекстів JavaScript та HTML атрибута. + return $context->format( + <<<'XX' + echo ' onclick="', LR\Filters::escapeHtmlAttr('return confirm(' . LR\Filters::escapeJs(%node) . ')'), '"' %line; + XX, + $this->message, + $this->position, + ); + } + + public function &getIterator(): \Generator + { + yield $this->message; + } +} +``` + +Метод `print()` генерує PHP-код, який зрештою під час візуалізації шаблону виведе HTML-атрибут `onclick="..."`. Обробка вкладених контекстів (JavaScript всередині HTML-атрибута) вимагає ретельного екранування. Фільтр `LR\Filters::escapeJs(%node)` викликається під час виконання та екранує повідомлення правильно для використання всередині JavaScript (вивід був би як `"Sure?"`). Потім фільтр `LR\Filters::escapeHtmlAttr(...)` екранує символи, які є спеціальними в HTML-атрибутах, так що це змінило б вивід на `return confirm("Sure?")`. Це двоступеневе екранування під час виконання гарантує, що повідомлення безпечне для JavaScript, а результуючий JavaScript-код безпечний для вставки в HTML-атрибут `onclick`. + + +Реєстрація та використання +-------------------------- + +Зареєструйте n:атрибут у вашому розширенні. Не забудьте про префікс `n:` у ключі: + +```php +class MyLatteExtension extends Extension +{ + public function getTags(): array + { + return [ + 'datetime' => DatetimeNode::create(...), + 'debug' => DebugNode::create(...), + 'repeat' => RepeatNode::create(...), + 'n:confirm' => ConfirmNode::create(...), // Реєстрація n:confirm + ]; + } +} +``` + +Тепер ви можете використовувати `n:confirm` на посиланнях, кнопках або елементах форми: + +```latte +Видалити +``` + +Згенерований HTML: + +```html +Видалити +``` + +Коли користувач натискає на посилання, браузер виконує код `onclick`, відображає діалогове вікно підтвердження і переходить на `delete.php` лише якщо користувач натискає "OK". + +Цей приклад демонструє, як можна створити чистий n:атрибут для модифікації поведінки або атрибутів свого хост-елемента HTML, генеруючи відповідний PHP-код у його методі `print()`. Не забувайте про подвійне екранування, яке часто потрібне: один раз для цільового контексту (JavaScript у цьому випадку) і знову для контексту HTML-атрибута. + + +Розширені теми +============== + +Хоча попередні розділи охоплюють основні концепції, ось кілька більш розширених тем, з якими ви можете зіткнутися при створенні власних тегів Latte. + + +Режими виводу тегів +------------------- + +Об'єкт `Tag`, переданий вашій функції `create()`, має властивість `outputMode`. Ця властивість впливає на те, як Latte обробляє навколишні пробіли та відступи, особливо коли тег використовується на власному рядку. Ви можете змінити цю властивість у вашій функції `create()`. + +- `Tag::OutputKeepIndentation` (Стандартно для більшості тегів, таких як `{=...}`): Latte намагається зберегти відступ перед тегом. Нові рядки *після* тегу зазвичай зберігаються. Це підходить для тегів, які виводять вміст у рядку. +- `Tag::OutputRemoveIndentation` (Стандартно для блокових тегів, таких як `{if}`, `{foreach}`): Latte видаляє початковий відступ та потенційно один наступний новий рядок. Це допомагає зберегти згенерований PHP-код чистішим і запобігає додатковим порожнім рядкам у HTML-виводі, спричиненим самим тегом. Використовуйте це для тегів, які представляють керуючі структури або блоки, які самі по собі не повинні додавати пробіли. +- `Tag::OutputNone` (Використовується тегами, такими як `{var}`, `{default}`): Подібно до `RemoveIndentation`, але сигналізує сильніше, що сам тег не виробляє прямого виводу, потенційно впливаючи на обробку пробілів навколо нього ще агресивніше. Підходить для декларативних або налаштовувальних тегів. + +Виберіть режим, який найкраще відповідає призначенню вашого тегу. Для більшості структурних або керуючих тегів зазвичай підходить `OutputRemoveIndentation`. + + +Доступ до батьківських/найближчих тегів +--------------------------------------- + +Іноді поведінка тегу повинна залежати від контексту, в якому він використовується, зокрема, в якому батьківському тегу(тегах) він знаходиться. Об'єкт `Tag`, переданий вашій функції `create()`, надає метод `closestTag(array $classes, ?callable $condition = null): ?Tag` саме для цієї мети. + +Цей метод шукає вгору по ієрархії поточних відкритих тегів (включаючи HTML-елементи, представлені внутрішньо під час парсингу) і повертає об'єкт `Tag` найближчого предка, який відповідає специфічним критеріям. Якщо не знайдено відповідного предка, повертає `null`. + +Масив `$classes` вказує, який тип предкових тегів ви шукаєте. Він перевіряє, чи є пов'язаний вузол предкового тегу (`$ancestorTag->node`) екземпляром цього класу. + +```php +function create(Tag $tag) +{ + // Пошук найближчого предкового тегу, вузол якого є екземпляром ForeachNode + $foreachTag = $tag->closestTag([ForeachNode::class]); + if ($foreachTag) { + // Ми можемо отримати доступ до екземпляра ForeachNode самого: + $foreachNode = $foreachTag->node; + } +} +``` + +Зверніть увагу на `$foreachTag->node`: Це працює лише тому, що є конвенцією в розробці тегів Latte негайно призначати створений вузол до `$tag->node` в межах методу `create()`, як ми завжди робили. + +Іноді простого порівняння типу вузла недостатньо. Вам може знадобитися перевірити специфічну властивість потенційного предкового тегу або його вузла. Необов'язковий другий аргумент для `closestTag()` — це callable, який приймає потенційний предковий об'єкт `Tag` і повинен повертати, чи є він дійсною відповідністю. + +```php +function create(Tag $tag) +{ + $dynamicBlockTag = $tag->closestTag( + [BlockNode::class], + // Умова: блок повинен бути динамічним + fn(Tag $blockTag) => $blockTag->node->block->isDynamic(), + ); +} +``` + +Використання `closestTag()` дозволяє створювати теги, які є контекстно-усвідомленими та забезпечують правильне використання в межах структури вашого шаблону, що призводить до більш надійних та зрозумілих шаблонів. + + +Плейсхолдери `PrintContext::format()` +------------------------------------- + +Ми часто використовували `PrintContext::format()` для генерації PHP-коду в методах `print()` наших вузлів. Він приймає рядок маски та наступні аргументи, які замінюють плейсхолдери в масці. Ось резюме доступних плейсхолдерів: + +- **`%node`**: Аргумент повинен бути екземпляром `Node`. Викликає метод `print()` вузла та вставляє результуючий рядок PHP-коду. +- **`%dump`**: Аргумент — будь-яке PHP-значення. Експортує значення в дійсний PHP-код. Підходить для скалярів, масивів, null. + - `$context->format('echo %dump;', 'Hello')` -> `echo 'Hello';` + - `$context->format('$arr = %dump;', [1, 2])` -> `$arr = [1, 2];` +- **`%raw`**: Вставляє аргумент безпосередньо у вихідний PHP-код без будь-якого екранування чи модифікації. **Використовуйте з обережністю**, переважно для вставки попередньо згенерованих фрагментів PHP-коду або назв змінних. + - `$context->format('%raw = 1;', '$variableName')` -> `$variableName = 1;` +- **`%args`**: Аргумент повинен бути `Expression\ArrayNode`. Виводить елементи масиву, відформатовані як аргументи для виклику функції або методу (розділені комами, обробляє іменовані аргументи, якщо вони присутні). + - `$argsNode = new ArrayNode([...]);` + - `$context->format('myFunc(%args);', $argsNode)` -> `myFunc(1, name: 'Joe');` +- **`%line`**: Аргумент повинен бути об'єктом `Position` (зазвичай `$this->position`). Вставляє PHP-коментар `/* line X */`, що вказує номер рядка джерела. + - `$context->format('echo "Hi" %line;', $this->position)` -> `echo "Hi" /* line 42 */;` +- **`%escape(...)`**: Генерує PHP-код, який *під час виконання* екранує внутрішній вираз за допомогою поточних контекстно-усвідомлених правил екранування. + - `$context->format('echo %escape(%node);', $variableNode)` +- **`%modify(...)`**: Аргумент повинен бути `ModifierNode`. Генерує PHP-код, який застосовує фільтри, зазначені в `ModifierNode`, до внутрішнього вмісту, включаючи контекстно-усвідомлене екранування, якщо не вимкнено за допомогою `|noescape`. + - `$context->format('%modify(%node);', $modifierNode, $variableNode)` +- **`%modifyContent(...)`**: Подібно до `%modify`, але призначений для модифікації блоків захопленого вмісту (часто HTML). + +Ви можете явно посилатися на аргументи за їхнім індексом (від нуля): `%0.node`, `%1.dump`, `%2.raw` тощо. Це дозволяє повторно використовувати аргумент кілька разів у масці без його повторної передачі до `format()`. Дивіться приклад тегу `{repeat}`, де використовувалися `%0.raw` та `%2.raw`. + + +Приклад комплексного парсингу аргументів +---------------------------------------- + +Хоча `parseExpression()`, `parseArguments()` тощо охоплюють багато випадків, іноді вам потрібна складніша логіка парсингу, що використовує нижчий рівень `TokenStream`, доступний через `$tag->parser->stream`. + +**Мета:** Створити тег `{embedYoutube $videoID, width: 640, height: 480}`. Ми хочемо розпарсити необхідний ID відео (рядок або змінну), за яким слідують необов'язкові пари ключ-значення для розмірів. + +```php +expectArguments(); + $node = $tag->node = new self; + // Парсування необхідного ID відео + $node->videoId = $tag->parser->parseExpression(); + + // Парсування необов'язкових пар ключ-значення + $stream = $tag->parser->stream; // Отримання потоку токенів + while ($stream->tryConsume(',')) { // Вимагає розділення комою + // Очікування ідентифікатора 'width' або 'height' + $keyToken = $stream->consume(Token::Php_Identifier); + $key = strtolower($keyToken->text); + + $stream->consume(':'); // Очікування роздільника двокрапки + + $value = $tag->parser->parseExpression(); // Парсування виразу значення + + if ($key === 'width') { + $node->width = $value; + } elseif ($key === 'height') { + $node->height = $value; + } else { + throw new CompileException("Невідомий аргумент '$key'. Очікувалося 'width' або 'height'.", $keyToken->position); + } + } + + return $node; + } +} +``` + +Цей рівень контролю дозволяє вам визначати дуже специфічні та комплексні синтаксиси для ваших власних тегів, безпосередньо взаємодіючи з потоком токенів. + + +Використання `AuxiliaryNode` +---------------------------- + +Latte надає загальні "допоміжні" вузли для спеціальних ситуацій під час генерації коду або в межах компіляційних проходів. Це `AuxiliaryNode` та `Php\Expression\AuxiliaryNode`. + +Вважайте `AuxiliaryNode` гнучким контейнерним вузлом, який делегує свої основні функціональності — генерацію коду та надання дочірніх вузлів — аргументам, наданим у його конструкторі: + +- Делегація `print()`: Перший аргумент конструктора — це PHP **closure**. Коли Latte викликає метод `print()` на `AuxiliaryNode`, він виконує цю надану closure. Closure приймає `PrintContext` та будь-які вузли, передані в другому аргументі конструктора, що дозволяє вам визначати повністю власну логіку генерації PHP-коду під час виконання. +- Делегація `getIterator()`: Другий аргумент конструктора — це **масив об'єктів `Node`**. Коли Latte потрібно пройти по дочірніх елементах `AuxiliaryNode` (наприклад, під час компіляційних проходів), його метод `getIterator()` просто надає вузли, перелічені в цьому масиві. + +Приклад: + +```php +$node = new AuxiliaryNode( + // 1. Ця closure стає тілом print() + fn(PrintContext $context, $arg1, $arg2) => $context->format('...%node...%node...', $arg1, $arg2), + + // 2. Ці вузли надаються методом getIterator() та передаються closure вище + [$argumentNode1, $argumentNode2] +); +``` + +Latte надає два різних типи залежно від того, де вам потрібно вставити згенерований код: + +- `Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode`: Використовуйте це, коли вам потрібно згенерувати фрагмент PHP-коду, який представляє **вираз** +- `Latte\Compiler\Nodes\AuxiliaryNode`: Використовуйте це для більш загальних цілей, коли вам потрібно вставити блок PHP-коду, що представляє один або більше **операторів** + +Важливою причиною використання `AuxiliaryNode` замість стандартних вузлів (таких як `StaticMethodCallNode`) у вашому методі `print()` або компіляційному проході є **контроль видимості для наступних компіляційних проходів**, особливо тих, що пов'язані з безпекою, таких як Sandbox. + +Розглянемо сценарій: Ваш компіляційний прохід повинен обернути наданий користувачем вираз (`$userExpr`) викликом специфічної, довіреної допоміжної функції `myInternalSanitize($userExpr)`. Якщо ви створите стандартний вузол `new FunctionCallNode('myInternalSanitize', [$userExpr])`, він буде повністю видимий для проходу AST. Якщо прохід Sandbox запускається пізніше, і `myInternalSanitize` *не* в його списку дозволених, Sandbox може *заблокувати* або змінити цей виклик, потенційно порушуючи внутрішню логіку вашого тегу, навіть якщо *ви*, автор тегу, знаєте, що цей специфічний виклик безпечний і необхідний. Тому ви можете генерувати виклик безпосередньо в межах closure `AuxiliaryNode`. + +```php +use Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode; + +// ... всередині print() або компіляційного проходу ... +$wrappedNode = new AuxiliaryNode( + fn(PrintContext $context, $userExpr) => $context->format( + 'myInternalSanitize(%node)', // Пряма генерація PHP-коду + $userExpr, + ), + // ВАЖЛИВО: Все одно передайте оригінальний вузол виразу користувача тут! + [$userExpr], +); +``` + +У цьому випадку прохід Sandbox бачить `AuxiliaryNode`, але **не аналізує PHP-код, згенерований його closure**. Він не може безпосередньо заблокувати виклик `myInternalSanitize`, згенерований *всередині* closure. + +Хоча сам згенерований PHP-код прихований від проходів, *входи* до цього коду (вузли, що представляють дані користувача або вирази) **повинні все ще бути доступними для проходження**. Тому другий аргумент конструктора `AuxiliaryNode` є фундаментальним. **Ви повинні** передати масив, що містить усі оригінальні вузли (як `$userExpr` у прикладі вище), які використовує ваша closure. `getIterator()` `AuxiliaryNode` **надасть ці вузли**, дозволяючи компіляційним проходам, таким як Sandbox, аналізувати їх на предмет потенційних проблем. + + +Найкращі практики +================= + +- **Чітке призначення:** Переконайтеся, що ваш тег має чітке та необхідне призначення. Не створюйте теги для завдань, які легко вирішуються за допомогою [фільтрів |custom-filters] або [функцій |custom-functions]. +- **Правильно реалізуйте `getIterator()`:** Завжди реалізуйте `getIterator()` та надавайте *посилання* (`&`) на *всі* дочірні вузли (аргументи, вміст), які були розпарсені з шаблону. Це необхідно для компіляційних проходів, безпеки (Sandbox) та потенційних майбутніх оптимізацій. +- **Публічні властивості для вузлів:** Робіть властивості, що містять дочірні вузли, публічними, щоб компіляційні проходи могли їх за потреби модифікувати. +- **Використовуйте `PrintContext::format()`:** Використовуйте метод `format()` для генерації PHP-коду. Він обробляє лапки, правильно екранує плейсхолдери та автоматично додає коментарі з номером рядка. +- **Тимчасові змінні (`$__`):** При генерації PHP-коду під час виконання, який потребує тимчасових змінних (наприклад, для зберігання проміжних підсумків, лічильників циклів), використовуйте конвенцію префікса `$__` для уникнення колізій з користувацькими змінними та внутрішніми змінними Latte `$ʟ_`. +- **Вкладеність та унікальні ID:** Якщо ваш тег може бути вкладеним або потребує стану, специфічного для екземпляра під час виконання, використовуйте `$context->generateId()` у вашому методі `print()` для створення унікальних суфіксів для ваших тимчасових змінних `$__`. +- **Провайдери для зовнішніх даних:** Використовуйте провайдерів (зареєстрованих через `Extension::getProviders()`) для доступу до даних або сервісів під час виконання ($this->global->...) замість жорсткого кодування значень або покладання на глобальний стан. Використовуйте префікси виробника для назв провайдерів. +- **Розгляньте n:атрибути:** Якщо ваш парний тег логічно оперує на одному HTML-елементі, Latte, ймовірно, надає автоматичну підтримку `n:атрибута`. Майте це на увазі для зручності користувача. Якщо ви створюєте тег, що модифікує атрибут, розгляньте, чи є чистий `n:атрибут` найвідповіднішою формою. +- **Тестування:** Пишіть тести для ваших тегів, охоплюючи як парсинг різних синтаксичних входів, так і коректність виводу згенерованого **PHP-коду**. + +Дотримуючись цих вказівок, ви можете створювати потужні, надійні та підтримувані власні теги, які бездоганно інтегруються з шаблонізатором Latte. + +.[note] +Вивчення класів вузлів, що входять до складу Latte, є найкращим способом дізнатися всі деталі процесу парсингу. diff --git a/latte/uk/develop.texy b/latte/uk/develop.texy index 60eb02a497..d0bcb2f109 100644 --- a/latte/uk/develop.texy +++ b/latte/uk/develop.texy @@ -1,70 +1,66 @@ -Практика для розробників -************************ +Практики розробки +***************** -Інсталяція .[#toc-installation] -=============================== +Встановлення +============ -Найкращим способом встановлення Latte є використання Composer: +Найкращий спосіб встановити Latte — за допомогою Composer: ```shell composer require latte/latte ``` -Підтримувані версії PHP (стосується останніх версій Latte з патчем): +Підтримувані версії PHP (стосується останніх мінорних версій Latte): -| версія | сумісна з PHP +| версія | сумісна з PHP |-----------------|------------------- -| Latte 3.0 | PHP 8.0 - 8.2 -| Latte 2.11 | PHP 7.1 - 8.2 -| Latte 2.8 - 2.10 | PHP 7.1 - 8.1 +| Latte 3.0 | PHP 8.0 – 8.2 -Як відрендерити шаблон .[#toc-how-to-render-a-template] -======================================================= +Як відобразити шаблон +===================== -Як відрендерити шаблон? Просто використовуйте цей простий код: +Як відобразити шаблон? Для цього достатньо цього простого коду: ```php $latte = new Latte\Engine; -// каталог кешу +// каталог для кешу $latte->setTempDirectory('/path/to/tempdir'); -$params = [ /* template variables */ ]; -// або $params = new TemplateParameters(/* ... */); +$params = [ /* змінні шаблону */ ]; +// or $params = new TemplateParameters(/* ... */); -// рендерити у вивід +// відобразити у вивід $latte->render('template.latte', $params); -// або рендерити в змінну +// відобразити у змінну $output = $latte->renderToString('template.latte', $params); ``` -Параметри можуть бути масивами, а ще краще [об'єктами |#Parameters as a class], що забезпечить перевірку типів і підказки в редакторі. +Параметри можуть бути масивом або, ще краще, [об'єктом |#Параметри як клас], який забезпечить перевірку типів та підказки в редакторах. .[note] -Ви також можете знайти приклади використання у репозиторії [Latte examples |https://github.com/nette-examples/latte]. +Приклади використання ви знайдете також у репозиторії [Latte examples |https://github.com/nette-examples/latte]. -Продуктивність та кешування .[#toc-performance-and-caching] -=========================================================== +Продуктивність та кеш +===================== -Шаблони Latte надзвичайно швидкі, тому що Latte компілює їх безпосередньо в PHP-код і кешує на диску. Таким чином, вони не мають додаткових накладних витрат у порівнянні з шаблонами, написаними на чистому PHP. +Шаблони в Latte надзвичайно швидкі, оскільки Latte компілює їх безпосередньо в PHP-код і зберігає в кеші на диску. Тому вони не мають жодних додаткових витрат порівняно з шаблонами, написаними на чистому PHP. -Кеш автоматично регенерується щоразу, коли ви змінюєте вихідний файл. Таким чином, ви можете зручно редагувати свої Latte-шаблони під час розробки і бачити зміни одразу в браузері. У виробничому середовищі ви можете вимкнути цю функцію і заощадити трохи продуктивності: +Кеш автоматично регенерується щоразу, коли ви змінюєте вихідний файл. Таким чином, під час розробки ви зручно редагуєте шаблони в Latte, і зміни одразу бачите в браузері. Цю функцію можна вимкнути в робочому середовищі та заощадити трохи продуктивності: ```php $latte->setAutoRefresh(false); ``` -При розгортанні на виробничому сервері початкова генерація кешу, особливо для великих додатків, зі зрозумілих причин може зайняти деякий час. Latte має вбудований захист від "переповнення кешу:https://en.wikipedia.org/wiki/Cache_stampede". -Це ситуація, коли сервер отримує велику кількість одночасних запитів, і оскільки кеш Latte ще не існує, всі вони будуть генерувати його одночасно. Що призводить до перевантаження процесора. -Latte розумний, і коли є кілька одночасних запитів, тільки перший потік генерує кеш, інші чекають, а потім використовують його. +При розгортанні на робочому сервері початкове генерування кешу, особливо для великих програм, зрозуміло, може зайняти деякий час. Latte має вбудований захист від "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Це ситуація, коли збігається велика кількість одночасних запитів, які запускають Latte, і оскільки кеш ще не існує, всі вони почали б генерувати його одночасно. Що непропорційно навантажило б сервер. Latte розумне і при кількох одночасних запитах генерує кеш лише перший потік, інші чекають, а потім використовують його. -Параметри як клас .[#toc-parameters-as-a-class] -=============================================== +Параметри як клас +================= -Краще, ніж передавати змінні в шаблон у вигляді масивів, створити клас. Ви отримуєте [типо-безпечну нотацію |type-system], гарну [пропозицію в IDE |recipes#Editors and IDE] та спосіб [реєстрації фільтрів |extending-latte#Filters Using the Class] і [функцій |extending-latte#Functions Using the Class]. +Краще, ніж передавати змінні до шаблону як масив, — створити клас. Ви отримаєте [типобезпечний запис|type-system], [зручні підказки в IDE |recipes#Редактори та IDE] та спосіб [реєстрації фільтрів |custom-filters#Фільтри що використовують клас з атрибутами] і [функцій |custom-functions#Функції що використовують клас з атрибутами]. ```php class MailTemplateParameters @@ -88,12 +84,12 @@ $latte->render('mail.latte', new MailTemplateParameters( ``` -Вимкнення автоматичного екранування змінних .[#toc-disabling-auto-escaping-of-variable] -======================================================================================= +Вимкнення автоекранування змінної +================================= -Якщо змінна містить HTML-рядок, ви можете позначити її так, щоб Latte автоматично (і, відповідно, двічі) не екранував її. Це дозволяє уникнути необхідності вказувати `|noescape` у шаблоні. +Якщо змінна містить рядок у HTML, ви можете позначити її так, щоб Latte автоматично (і, отже, подвійно) не екранував її. Таким чином ви уникнете необхідності вказувати в шаблоні `|noescape`. -Найпростіший спосіб - обернути рядок в об'єкт `Latte\Runtime\Html`: +Найпростіший спосіб — обернути рядок в об'єкт `Latte\Runtime\Html`: ```php $params = [ @@ -101,7 +97,7 @@ $params = [ ]; ``` -Latte також не екранує всі об'єкти, які реалізують інтерфейс `Latte\HtmlStringable`. Таким чином, ви можете створити власний клас, метод `__toString()` якого повертатиме HTML-код, який не буде екрановано автоматично: +Latte далі не екранує всі об'єкти, які реалізують інтерфейс `Latte\HtmlStringable`. Ви можете створити власний клас, метод `__toString()` якого повертатиме HTML-код, який не буде автоматично екрануватися: ```php class Emphasis extends Latte\HtmlStringable @@ -123,32 +119,85 @@ $params = [ ``` .[warning] -Метод `__toString` повинен повертати коректний HTML і забезпечувати екранування параметрів, інакше може виникнути XSS-уразливість! +Метод `__toString` повинен повертати коректний HTML і забезпечувати екранування параметрів, інакше може виникнути вразливість XSS! -Як розширити латте за допомогою фільтрів, тегів і т.д. .[#toc-how-to-extend-latte-with-filters-tags-etc] -======================================================================================================== +Як розширити Latte фільтрами, тегами тощо. +========================================== -Як додати до Latte власний фільтр, функцію, тег тощо? Дізнайтеся у розділі про [розширення Latte |extending Latte]. -Якщо ви хочете повторно використовувати ваші зміни у різних проектах або поділитися ними з іншими, вам слід [створити розширення |creating-extension]. +Як додати до Latte власний фільтр, функцію, тег тощо? Про це йдеться в розділі [розширюємо Latte |extending-latte]. Якщо ви хочете повторно використовувати свої зміни в різних проєктах або ділитися ними з іншими, вам слід [створити розширення |extending-latte#Latte Extension]. -Будь-який код у шаблоні `{php ...}` .{data-version:3.0}{toc: RawPhpExtension} -============================================================================= +Довільний код у шаблоні `{php ...}` .{toc: RawPhpExtension} +=========================================================== -Усередині тегу PHP можна писати тільки PHP-вирази, тому ви не можете вставляти в нього конструкції, що закінчуються комою. [`{do}` |tags#do] тому ви не можете, наприклад, вставляти конструкції на кшталт `if ... else` або оператори, що закінчуються крапкою з комою. +Всередині тегу [`{do}` |tags#do] можна записувати лише PHP-вирази, тому ви не можете, наприклад, вставити конструкції типу `if ... else` або оператори, що закінчуються крапкою з комою. -Однак ви можете зареєструвати розширення `RawPhpExtension`, яке додає тег `{php ...}`, який можна використовувати для вставки будь-якого PHP-коду на ризик автора шаблону. +Однак ви можете зареєструвати розширення `RawPhpExtension`, яке додає тег `{php ...}`. За допомогою нього можна вставляти будь-який PHP-код. На нього не поширюються жодні правила режиму sandbox, тому використання здійснюється на відповідальність автора шаблону. ```php $latte->addExtension(new Latte\Essential\RawPhpExtension); ``` -Переклад у шаблонах .{data-version:3.0}{toc: TranslatorExtension} -================================================================= +Перевірка згенерованого коду .{data-version:3.0.7} +================================================== + +Latte компілює шаблони в PHP-код. Звичайно, він дбає про те, щоб згенерований код був синтаксично валідним. Однак при використанні розширень третіх сторін або `RawPhpExtension` Latte не може гарантувати правильність згенерованого файлу. Також можна написати в PHP код, який хоч і синтаксично правильний, але заборонений (наприклад, присвоєння значення змінній `$this`) і спричинить PHP Compile Error. Якщо ви запишете таку операцію в шаблоні, вона потрапить і в згенерований PHP-код. Оскільки в PHP існує близько двох сотень різних заборонених операцій, Latte не має на меті їх виявляти. На них вкаже саме PHP лише під час відображення, що зазвичай нічому не заважає. + +Але є ситуації, коли ви хочете знати вже під час компіляції шаблону, що він не містить жодної PHP Compile Error. Особливо якщо шаблони можуть редагувати користувачі, або ви використовуєте [Sandbox |sandbox]. У такому випадку перевіряйте шаблони вже під час компіляції. Цю функціональність вмикаєте методом `Engine::enablePhpLint()`. Оскільки для перевірки потрібно викликати бінарний файл PHP, передайте шлях до нього як параметр: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); + +try { + $latte->compile('home.latte'); +} catch (Latte\CompileException $e) { + // перехопить помилки в Latte, а також Compile Error в PHP + echo 'Error: ' . $e->getMessage(); +} +``` + + +Національне середовище .{data-version:3.0.18}{toc: Locale} +========================================================== + +Latte дозволяє встановити національне середовище, яке впливає на форматування чисел, дат та сортування. Воно налаштовується за допомогою методу `setLocale()`. Ідентифікатор середовища відповідає стандарту IETF language tag, який використовує розширення PHP `intl`. Він складається з коду мови та, можливо, коду країни, напр. `en_US` для англійської в Сполучених Штатах, `de_DE` для німецької в Німеччині тощо. + +```php +$latte = new Latte\Engine; +$latte->setLocale('uk_UA'); +``` + +Налаштування середовища впливає на фільтри [localDate |filters#localDate], [sort |filters#sort], [number |filters#number] та [bytes |filters#bytes]. + +.[note] +Вимагає PHP-розширення `intl`. Налаштування в Latte не впливає на глобальні налаштування locale в PHP. + + +Строгий режим .{data-version:3.0.8} +=================================== + +У строгому режимі парсингу Latte перевіряє, чи не відсутні закриваючі HTML-теги, а також забороняє використання змінної `$this`. Вмикаєте його так: + +```php +$latte = new Latte\Engine; +$latte->setStrictParsing(); +``` + +Генерування шаблонів із заголовком `declare(strict_types=1)` вмикаєте так: + +```php +$latte = new Latte\Engine; +$latte->setStrictTypes(); +``` -Використовуйте розширення `TranslatorExtension`, щоб додати [`{_...}` |tags#_], [`{translate}` |tags#translate] і фільтр [`translate` |filters#translate] до шаблону. Вони використовуються для перекладу значень або частин шаблону на інші мови. Параметром є метод (PHP-скрипт), який виконує переклад: + +Переклад у шаблонах .{toc: TranslatorExtension} +=============================================== + +За допомогою розширення `TranslatorExtension` ви додасте до шаблону теги [`{_...}` |tags#], [`{translate}` |tags#translate] та фільтр [`translate` |filters#translate]. Вони служать для перекладу значень або частин шаблону на інші мови. Як параметр вказуємо метод (PHP callable), що виконує переклад: ```php class MyTranslator @@ -158,19 +207,19 @@ class MyTranslator public function translate(string $original): string { - // створити $translated з $original відповідно до $this->lang + // з $original створимо $translated відповідно до $this->lang return $translated; } } $translator = new MyTranslator($lang); $extension = new Latte\Essential\TranslatorExtension( - $translator->translate(...), // [$translator, 'translate'] у PHP 8.0 + $translator->translate(...), // [$translator, 'translate'] в PHP 8.0 ); $latte->addExtension($extension); ``` -Перекладач викликається під час виконання, коли шаблон рендериться. Однак Latte може перекладати всі статичні тексти під час компіляції шаблону. Це економить продуктивність, оскільки кожен рядок перекладається лише один раз, а результат перекладу записується до скомпільованого файлу. Це створює кілька скомпільованих версій шаблону в кеш-пам'яті, по одній для кожної мови. Для цього вам потрібно лише вказати мову як другий параметр: +Перекладач викликається під час виконання при відображенні шаблону. Однак Latte може перекладати всі статичні тексти вже під час компіляції шаблону. Це заощаджує продуктивність, оскільки кожен рядок перекладається лише один раз, і результат перекладу записується в скомпільовану форму. У каталозі кешу таким чином створюється кілька скомпільованих версій шаблону, по одній для кожної мови. Для цього достатньо лише вказати мову як другий параметр: ```php $extension = new Latte\Essential\TranslatorExtension( @@ -179,9 +228,9 @@ $extension = new Latte\Essential\TranslatorExtension( ); ``` -Під статичним текстом мається на увазі, наприклад, `{_'hello'}` або `{translate}hello{/translate}`. Нестатичний текст, наприклад, `{_$foo}`, буде продовжувати перекладатися під час виконання. +Статичним текстом мається на увазі, наприклад, `{_'hello'}` або `{translate}hello{/translate}`. Нестатичні тексти, наприклад, `{_$foo}`, надалі перекладатимуться під час виконання. -Шаблон також може передавати додаткові параметри перекладачеві через `{_$original, foo: bar}` або `{translate foo: bar}`, які він отримує у вигляді масиву `$params`: +Перекладачу можна також передавати додаткові параметри з шаблону за допомогою `{_$original, foo: bar}` або `{translate foo: bar}`, які він отримає як масив `$params`: ```php public function translate(string $original, ...$params): string @@ -191,66 +240,73 @@ public function translate(string $original, ...$params): string ``` -Налагодження та трейси .[#toc-debugging-and-tracy] -================================================== +Налагодження та Tracy +===================== -Latte намагається зробити розробку якомога приємнішою. Для налагодження існують три теги [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak] і [`{trace}` |tags#trace]. +Latte намагається зробити розробку якомога приємнішою. Безпосередньо для цілей налагодження існує трійка тегів [`{dump}` |tags#dump], [`{debugbreak}` |tags#debugbreak] та [`{trace}` |tags#trace]. -Ви отримаєте максимальний комфорт, якщо встановите чудовий [інструмент для налагодження Tracy |tracy:] і активуєте плагін Latte: +Найбільший комфорт ви отримаєте, якщо ще встановите чудовий [інструмент налагодження Tracy|tracy:] та активуєте доповнення для Latte: ```php // вмикає Tracy Tracy\Debugger::enable(); $latte = new Latte\Engine; -// активує розширення Tracy +// активує розширення для Tracy $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension); ``` -Тепер ви побачите всі помилки на акуратному червоному екрані, включаючи помилки в шаблонах з підсвічуванням рядків і стовпців[(відео |https://github.com/nette/tracy/releases/tag/v2.9.0]). -При цьому в правому нижньому куті в так званому Tracy Bar з'являється вкладка для Latte, де ви можете чітко бачити всі відрендеринговані шаблони і їх взаємозв'язки (в тому числі з можливістю кліка в шаблон або скомпільований код), а також змінні: +Тепер усі помилки відображатимуться на зрозумілому червоному екрані, включаючи помилки в шаблонах з підсвічуванням рядка та стовпця ([відео|https://github.com/nette/tracy/releases/tag/v2.9.0]). Водночас у правому нижньому куті в так званому Tracy Bar з'явиться вкладка для Latte, де чітко видно всі відображувані шаблони та їхні взаємні зв'язки (включаючи можливість перейти до шаблону або скомпільованого коду), а також змінні: [* latte-debugging.webp *] -Оскільки Latte компілює шаблони в читабельний PHP-код, ви можете зручно переміщатися по ним у вашій IDE. +Оскільки Latte компілює шаблони в зрозумілий PHP-код, ви можете зручно крокувати по них у своєму IDE. -Linter: Перевірка синтаксису шаблону .{data-version:2.11}{toc: Linter} -====================================================================== +Linter: валідація синтаксису шаблонів .{toc: Linter} +==================================================== -Інструмент Linter допоможе вам переглянути всі шаблони і перевірити їх на наявність синтаксичних помилок. Він запускається з консолі: +Пройти всі шаблони та перевірити, чи не містять вони синтаксичних помилок, вам допоможе інструмент Linter. Він запускається з консолі: ```shell -vendor/bin/latte-lint +vendor/bin/latte-lint <шлях> ``` -Якщо ви використовуєте кастомні теги, також створіть свій власний Linter, наприклад, `custom-latte-lint`: +Параметр `--strict` активує [#строгий режим]. + +Якщо ви використовуєте власні теги, створіть також власну версію Linter, напр. `custom-latte-lint`: ```php #!/usr/bin/env php scanDirectory($path); +$path = $argv[1] ?? '.'; -$engine = new Latte\Engine; -// прописує тут окремі розширення -$engine->addExtension(/* ... */); +$linter = new Latte\Tools\Linter; +$latte = $linter->getEngine(); +// тут додайте окремі свої розширення +$latte->addExtension(/* ... */); -$path = $argv[1]; -$linter = new Latte\Tools\Linter(engine: $engine); $ok = $linter->scanDirectory($path); exit($ok ? 0 : 1); ``` +Альтернативно ви можете передати власний об'єкт `Latte\Engine` в Linter: + +```php +$latte = new Latte\Engine; +// тут ми конфігуруємо об'єкт $latte +$linter = new Latte\Tools\Linter(engine: $latte); +``` -Завантаження шаблонів з рядка .[#toc-loading-templates-from-a-string] -===================================================================== -Потрібно завантажити шаблони з рядків, а не з файлів, можливо, для тестування? [StringLoader |extending-latte#stringloader] допоможе вам: +Завантаження шаблонів з рядка +============================= + +Вам потрібно завантажувати шаблони з рядків замість файлів, наприклад, для цілей тестування? Вам допоможе [StringLoader |loaders#StringLoader]: ```php $latte->setLoader(new Latte\Loaders\StringLoader([ @@ -262,10 +318,10 @@ $latte->render('main.file', $params); ``` -Обробник винятків .[#toc-exception-handler] -=========================================== +Обробник винятків +================= -Ви можете визначити власний обробник для очікуваних винятків. Йому передаються винятки, згенеровані всередині [`{try}` |tags#try] і в [пісочниці |sandbox], передаються до нього. +Ви можете визначити власний обробник для очікуваних винятків. Йому передаються винятки, що виникли всередині [`{try}` |tags#try] та в [sandbox|sandbox]. ```php $loggingHandler = function (Throwable $e, Latte\Runtime\Template $template) use ($logger) { @@ -277,17 +333,17 @@ $latte->setExceptionHandler($loggingHandler); ``` -Автоматичний пошук макетів .[#toc-automatic-layout-lookup] -========================================================== +Автоматичний пошук layout +========================= -За допомогою тегу [`{layout}` |template-inheritance#layout-inheritance]шаблон визначає свій батьківський шаблон. Також можна налаштувати автоматичний пошук макета, що спростить написання шаблонів, оскільки їм не потрібно буде включати тег `{layout}`. +За допомогою тегу [`{layout}` |template-inheritance#Успадкування layout ів] шаблон визначає свій батьківський шаблон. Також можливо дозволити автоматичний пошук layout, що спростить написання шаблонів, оскільки в них не потрібно буде вказувати тег `{layout}`. -Це досягається наступним чином: +Цього можна досягти наступним чином: ```php $finder = function (Latte\Runtime\Template $template) { if (!$template->getReferenceType()) { - // повертає шлях до батьківського файлу шаблону + // повертає шлях до файлу з layout return 'automatic.layout.latte'; } }; @@ -296,4 +352,4 @@ $latte = new Latte\Engine; $latte->addProvider('coreParentFinder', $finder); ``` -Якщо шаблон не повинен мати макет, він вказує на це тегом `{layout none}`. +Якщо шаблон не повинен мати layout, він повідомить про це тегом `{layout none}`. diff --git a/latte/uk/extending-latte.texy b/latte/uk/extending-latte.texy index e5bd492fca..e6996e27cc 100644 --- a/latte/uk/extending-latte.texy +++ b/latte/uk/extending-latte.texy @@ -1,285 +1,227 @@ -Подовжуючий Latte -***************** +Розширення Latte +**************** .[perex] -Latte дуже гнучкий і може бути розширений безліччю способів: ви можете додати користувацькі фільтри, функції, теги, завантажувачі тощо. Ми покажемо вам, як це зробити. +Latte розроблено з урахуванням можливості розширення. Хоча його стандартний набір тегів, фільтрів і функцій охоплює багато випадків використання, часто вам потрібно додати власну специфічну логіку або допоміжні інструменти. Ця сторінка надає огляд способів розширення Latte, щоб він ідеально відповідав вимогам вашого проекту - від простих помічників до складного нового синтаксису. -У цьому розділі описано різні способи розширення Latte. Якщо ви хочете повторно використовувати свої зміни в різних проєктах або поділитися ними з іншими, вам слід [створити так зване розширення |creating-extension]. +Способи розширення Latte +======================== -Скільки доріг веде до Риму? .[#toc-how-many-roads-lead-to-rome] -=============================================================== +Ось короткий огляд основних способів, якими ви можете налаштувати та розширити Latte: -Оскільки деякі зі способів розширення Latte можуть змішуватися, давайте спочатку спробуємо пояснити відмінності між ними. Як приклад спробуємо реалізувати генератор *Lorem ipsum*, якому передається кількість слів для генерації. +- **[Власні фільтри |custom-filters]:** Для форматування або перетворення даних безпосередньо у виводі шаблону (наприклад, `{$var|myFilter}`). Ідеально підходить для таких завдань, як форматування дат, редагування тексту або застосування специфічного екранування. Ви також можете використовувати їх для редагування великих блоків HTML-вмісту, обернувши вміст анонімним [`{block}` |tags#block] і застосувавши до нього власний фільтр. +- **[Власні функції |custom-functions]:** Для додавання логіки, що повторно використовується, яку можна викликати в межах виразів у шаблоні (наприклад, `{myFunction($arg1, $arg2)}`). Корисно для обчислень, доступу до допоміжних функцій програми або генерації невеликих частин вмісту. +- **[Власні теги |custom-tags]:** Для створення абсолютно нових мовних конструкцій (`{mytag}...{/mytag}` або `n:mytag`). Теги пропонують найбільше можливостей, дозволяють визначати власні структури, керувати розбором шаблону та реалізовувати складну логіку рендерингу. +- **[Проходи компіляції |compiler-passes]:** Функції, які змінюють абстрактне синтаксичне дерево (AST) шаблону після розбору, але перед генерацією PHP-коду. Використовуються для розширених оптимізацій, перевірок безпеки (наприклад, Sandbox) або автоматичних змін коду. +- **[Власні завантажувачі |loaders]:** Для зміни способу, яким Latte шукає та завантажує файли шаблонів (наприклад, завантаження з бази даних, зашифрованого сховища тощо). -Основною конструкцією мови Latte є тег. Ми можемо реалізувати генератор, розширивши Latte новим тегом: +Вибір правильного методу розширення є ключовим. Перш ніж створювати складний тег, подумайте, чи не вистачить простішого фільтра або функції. Покажемо це на прикладі: реалізація генератора *Lorem ipsum*, який як аргумент приймає кількість слів для генерації. -```latte -{lipsum 40} -``` - -Тег працюватиме чудово. Однак генератор у вигляді тега може виявитися недостатньо гнучким, оскільки його не можна використовувати у виразі. До речі, на практиці вам рідко знадобиться генерувати теги; і це хороша новина, тому що теги - це складніший спосіб розширення. - -Добре, давайте спробуємо створити фільтр замість тега: - -```latte -{=40|lipsum} -``` - -Знову ж таки, прийнятний варіант. Але фільтр повинен перетворити передане значення на щось інше. Тут ми використовуємо значення `40`, яке вказує на кількість створених слів, як аргумент фільтра, а не як значення, яке ми хочемо перетворити. +- **Як тег?** `{lipsum 40}` - Можливо, але теги краще підходять для керуючих структур або генерації складних розміток. Теги не можна використовувати безпосередньо у виразах. +- **Як фільтр?** `{=40|lipsum}` - Технічно це працює, але фільтри призначені для *перетворення* вхідного значення. Тут `40` є *аргументом*, а не значенням, яке перетворюється. Це виглядає семантично неправильно. +- **Як функція?** `{lipsum(40)}` - Це найбільш природне рішення! Функції приймають аргументи та повертають значення, що ідеально підходить для використання в будь-якому виразі: `{var $text = lipsum(40)}`. -Тому давайте спробуємо використати функцію: +**Загальна рекомендація:** Використовуйте функції для обчислень/генерації, фільтри для перетворення та теги для нових мовних конструкцій або складних розміток. Проходи використовуйте для маніпуляції з AST, а завантажувачі для отримання шаблонів. -```latte -{lipsum(40)} -``` -Ось і все! Для цього конкретного прикладу створення функції - ідеальна точка розширення. Ви можете викликати її в будь-якому місці, де приймається вираз, наприклад: +Пряма реєстрація +================ -```latte -{var $text = lipsum(40)} -``` +Для допоміжних інструментів, специфічних для проекту, або швидких розширень Latte дозволяє пряму реєстрацію фільтрів і функцій в об'єкті `Latte\Engine`. - -Фільтри .[#toc-filters] -======================= - -Створіть фільтр, зареєструвавши його ім'я і будь-який елемент PHP, що викликається, наприклад, функцію: +Для реєстрації фільтра використовуйте метод `addFilter()`. Першим аргументом вашої функції фільтра буде значення перед знаком `|`, а наступні аргументи - ті, що передаються після двокрапки `:`. ```php $latte = new Latte\Engine; -$latte->addFilter('shortify', fn(string $s) => mb_substr($s, 0, 10)); // скорочує текст до 10 символів -``` -У цьому випадку було б краще, щоб фільтр отримував додатковий параметр: +// Визначення фільтра (callable об'єкт: функція, статичний метод тощо) +$myTruncate = fn(string $s, int $length = 50) => mb_substr($s, 0, $length); -```php -$latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); -``` - -Ми використовуємо його в шаблоні таким чином: +// Реєстрація +$latte->addFilter('truncate', $myTruncate); -```latte -

                                                                                                                                    {$text|shortify}

                                                                                                                                    -

                                                                                                                                    {$text|shortify:100}

                                                                                                                                    +// Використання в шаблоні: {$text|truncate} або {$text|truncate:100} ``` -Як бачите, функція отримує як такі аргументи ліву частину фільтра перед трубою `|` as the first argument and the arguments passed to the filter after `:`. - -Звичайно, функція, що представляє фільтр, може приймати будь-яку кількість параметрів, також підтримуються змінні параметри. - - -Фільтри, що використовують клас .[#toc-filters-using-the-class] ---------------------------------------------------------------- - -Другий спосіб визначити фільтр - [використовувати клас |develop#Parameters-as-a-Class]. Ми створюємо метод з атрибутом `TemplateFilter`: +Ви також можете зареєструвати **Filter Loader**, функцію, яка динамічно надає callable об'єкти фільтрів за потрібною назвою: ```php -class TemplateParameters -{ - public function __construct( - // параметри - ) {} - - #[Latte\Attributes\TemplateFilter] - public function shortify(string $s, int $len = 10): string - { - return mb_substr($s, 0, $len); - } -} - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); +$latte->addFilterLoader(fn(string $name) => /* повертає callable об'єкт або null */); ``` -Якщо ви використовуєте PHP 7.x і Latte 2.x, використовуйте анотацію `/** @filter */` замість атрибута. - -Завантажувач фільтрів .{data-version:2.10}[#toc-filter-loader] --------------------------------------------------------------- - -Замість реєстрації окремих фільтрів ви можете створити так званий завантажувач, який являє собою функцію, що викликається з ім'ям фільтра як аргумент і повертає його викликаний PHP-файл або null. +Для реєстрації функції, що використовується у виразах шаблону, використовуйте `addFunction()`. ```php -$latte->addFilterLoader([new Filters, 'load']); +$latte = new Latte\Engine; +// Визначення функції +$isWeekend = fn(DateTimeInterface $date) => $date->format('N') >= 6; -class Filters -{ - public function load(string $filter): ?callable - { - if (in_array($filter, get_class_methods($this))) { - return [$this, $filter]; - } - return null; - } - - public function shortify($s, $len = 10) - { - return mb_substr($s, 0, $len); - } - - // ... -} +// Реєстрація +$latte->addFunction('isWeekend', $isWeekend); + +// Використання в шаблоні: {if isWeekend($myDate)}Вихідний!{/if} ``` +Більше інформації знайдете в розділі [Створення власних фільтрів |custom-filters] та [Функцій |custom-functions]. -Контекстні фільтри .[#toc-contextual-filters] ---------------------------------------------- -Контекстний фільтр - це фільтр, який приймає об'єкт [api:Latte\Runtime\FilterInfo] як перший параметр, за яким слідують інші параметри, як у випадку з класичними фільтрами. Він реєструється так само, Latte сам розпізнає, що фільтр контекстний: +Надійний спосіб: Latte Extension .{toc: Latte Extension} +======================================================== -```php -use Latte\Runtime\FilterInfo; +Хоча пряма реєстрація проста, стандартним і рекомендованим способом пакування та розповсюдження розширень Latte є використання класів **Extension**. Extension служить центральною точкою конфігурації для реєстрації кількох тегів, фільтрів, функцій, проходів компіляції та інших елементів. -$latte->addFilter('foo', function (FilterInfo $info, string $str): string { - // ... -}); -``` +Чому використовувати Extensions? -Контекстні фільтри можуть визначати і змінювати тип вмісту, який вони отримують у змінній `$info->contentType`. Якщо фільтр викликається класично через змінну (наприклад, `{$var|foo}`), то `$info->contentType` буде містити null. +- **Організація:** Зберігає пов'язані розширення (теги, фільтри тощо для конкретної функції) разом в одному класі. +- **Повторне використання та спільне використання:** Легко пакуйте свої розширення для використання в інших проектах або для спільного використання зі спільнотою (наприклад, через Composer). +- **Повна сила:** Власні теги та проходи компіляції *можна реєструвати лише* через Extensions. -Фільтр повинен спочатку перевірити, чи підтримується тип вмісту вхідного рядка. Він також може змінити його. Приклад фільтра, який приймає текст (або null) і повертає HTML: + +Реєстрація Extension +-------------------- + +Extension реєструється в Latte за допомогою методу `addExtension()` (або через [конфігураційний файл |application:configuration#Шаблони Latte]): ```php -use Latte\Runtime\FilterInfo; - -$latte->addFilter('money', function (FilterInfo $info, float $amount): string { }) - // спочатку перевіряємо, чи тип вводу текстовий - if (!in_array($info->contentType, [null, ContentType::Text])) { - throw new Exception("Filter |money used in incompatible content type $info->contentType."); - } - - // змінюємо тип контенту на HTML - $info->contentType = ContentType::Html; - return "$num Kč"; -}); +$latte = new Latte\Engine; +$latte->addExtension(new MyProjectExtension); ``` -.[note] -У цьому випадку фільтр повинен забезпечити правильне екранування даних. +Якщо ви зареєструєте кілька розширень, і вони визначають теги, фільтри або функції з однаковими назвами, перевагу має останнє додане розширення. Це також означає, що ваші розширення можуть перевизначати нативні теги/фільтри/функції. -Усі фільтри, які використовуються поверх [блоків |tags#block] (наприклад, як `{block|foo}...{/block}`), мають бути контекстними. +Щоразу, коли ви вносите зміни до класу і автоматичне оновлення не вимкнено, Latte автоматично перекомпілює ваші шаблони. -Функції .{data-version:2.6}[#toc-functions] -=========================================== +Створення Extension +------------------- -За замовчуванням усі власні функції PHP можуть використовуватися в Latte, якщо тільки це не вимкнено в пісочниці. Але ви також можете визначити свої власні функції. Вони можуть перевизначати власні функції. +Для створення власного розширення вам потрібно створити клас, який успадковує від [api:Latte\Extension]. Щоб уявити, як виглядає таке розширення, подивіться на вбудоване "CoreExtension":https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php. -Створіть функцію, зареєструвавши її ім'я та будь-яку викликану функцію PHP: +Розглянемо методи, які ви можете реалізувати: -```php -$latte = new Latte\Engine; -$latte->addFunction('random', function (...$args) { - return $args[array_rand($args)]; -}); -``` -Після цього використання буде таким же, як і при виклику PHP-функції: +beforeCompile(Latte\Engine $engine): void .[method] +--------------------------------------------------- -```latte -{random(apple, orange, lemon)} // prints for example: apple -``` +Викликається перед компіляцією шаблону. Метод можна використовувати, наприклад, для ініціалізації, пов'язаної з компіляцією. -Функції, що використовують клас .[#toc-functions-using-the-class] ------------------------------------------------------------------ +getTags(): array .[method] +-------------------------- -Другий спосіб визначити функцію - [використовувати клас |develop#Parameters-as-a-Class]. Ми створюємо метод з атрибутом `TemplateFunction`: +Викликається під час компіляції шаблону. Повертає асоціативний масив *назва тегу => callable об'єкт*, що є функціями для парсингу тегів. [Більше інформації |custom-tags]. ```php -class TemplateParameters +public function getTags(): array { - public function __construct( - // параметри - ) {} - - #[Latte\Attributes\TemplateFunction] - public function random(...$args) - { - return $args[array_rand($args)]; - } + return [ + 'foo' => FooNode::create(...), + 'bar' => BarNode::create(...), + 'n:baz' => NBazNode::create(...), + // ... + ]; } - -$params = new TemplateParameters(/* ... */); -$latte->render('template.latte', $params); ``` -Якщо ви використовуєте PHP 7.x і Latte 2.x, використовуйте анотацію `/** @function */` замість атрибута. +Тег `n:baz` представляє чистий [n:атрибут |syntax#n:атрибути], тобто тег, який можна записати лише як атрибут. +Для тегів `foo` та `bar` Latte автоматично розпізнає, чи є вони парними тегами, і якщо так, їх можна автоматично записувати за допомогою n:атрибутів, включаючи варіанти з префіксами `n:inner-foo` та `n:tag-foo`. -Завантажувачі .[#toc-loaders] -============================= +Порядок виконання таких n:атрибутів визначається їхнім порядком у масиві, повернутому методом `getTags()`. Отже, `n:foo` завжди виконується перед `n:bar`, навіть якщо атрибути в HTML-тезі вказані в зворотному порядку, як `
                                                                                                                                    `. -Завантажувачі відповідають за завантаження шаблонів із джерела, наприклад, із файлової системи. Вони встановлюються за допомогою методу `setLoader()`: +Якщо вам потрібно визначити порядок n:атрибутів між кількома розширеннями, використовуйте допоміжний метод `order()`, де параметр `before` xor `after` визначає, які теги сортуються перед або після тегу. ```php -$latte->setLoader(new MyLoader); +public function getTags(): array +{ + return [ + 'foo' => self::order(FooNode::create(...), before: 'bar'), + 'bar' => self::order(BarNode::create(...), after: ['block', 'snippet']), + ]; +} ``` -Вбудованими завантажувачами є: +getPasses(): array .[method] +---------------------------- -FileLoader .[#toc-fileloader] ------------------------------ +Викликається під час компіляції шаблону. Повертає асоціативний масив *назва проходу => callable об'єкт*, що є функціями, які представляють так звані [компіляційні проходи |compiler-passes], що проходять і змінюють AST. -Завантажувач за замовчуванням. Завантажує шаблони з файлової системи. - -Доступ до файлів можна обмежити, задавши базовий каталог: +Тут також можна використовувати допоміжний метод `order()`. Значення параметрів `before` або `after` може бути `*` зі значенням перед/після всіх. ```php -$latte->setLoader(new Latte\Loaders\FileLoader($templateDir)); -$latte->render('test.latte'); +public function getPasses(): array +{ + return [ + 'optimize' => Passes::optimizePass(...), + 'sandbox' => self::order($this->sandboxPass(...), before: '*'), + // ... + ]; +} ``` -StringLoader .[#toc-stringloader] ---------------------------------- +beforeRender(Latte\Engine $engine): void .[method] +-------------------------------------------------- -Завантажує шаблони з рядків. Цей завантажувач дуже корисний для модульного тестування. Він також може бути використаний для невеликих проектів, де має сенс зберігати всі шаблони в одному PHP-файлі. +Викликається перед кожним рендерингом шаблону. Метод може бути використаний, наприклад, для ініціалізації змінних, що використовуються під час рендерингу. -```php -$latte->setLoader(new Latte\Loaders\StringLoader([ - 'main.file' => '{include other.file}', - 'other.file' => '{if true} {$var} {/if}', -])); -$latte->render('main.file'); -``` +getFilters(): array .[method] +----------------------------- -Спрощене використання: +Викликається перед рендерингом шаблону. Повертає фільтри як асоціативний масив *назва фільтра => callable об'єкт*. [Більше інформації |custom-filters]. ```php -$template = '{if true} {$var} {/if}'; -$latte->setLoader(new Latte\Loaders\StringLoader); -$latte->render($template); +public function getFilters(): array +{ + return [ + 'batch' => $this->batchFilter(...), + 'trim' => $this->trimFilter(...), + // ... + ]; +} ``` -Створення користувацького завантажувача .[#toc-creating-a-custom-loader] ------------------------------------------------------------------------- - -Loader - це клас, що реалізує інтерфейс [api:Latte\Loader]. +getFunctions(): array .[method] +------------------------------- +Викликається перед рендерингом шаблону. Повертає функції як асоціативний масив *назва функції => callable об'єкт*. [Більше інформації |custom-functions]. -Теги .[#toc-tags] -================= +```php +public function getFunctions(): array +{ + return [ + 'clamp' => $this->clampFunction(...), + 'divisibleBy' => $this->divisibleByFunction(...), + // ... + ]; +} +``` -Однією з найцікавіших можливостей шаблонізатора є можливість визначати нові мовні конструкції за допомогою тегів. Це також складніша функціональність, і вам необхідно розуміти, як внутрішньо працює Latte. -У більшості випадків, однак, тег не потрібен: -- якщо він має генерувати певний висновок, використовуйте замість нього [функцію |#Functions] -- якщо потрібно змінити вхідні дані та повернути їх, використовуйте [filter |#Filters] -- якщо потрібно відредагувати ділянку тексту, оберніть її тегом [`{block}` |tags#block] тегом і використовуйте [фільтр |#Contextual-Filters] -- якщо він не повинен був нічого виводити, а тільки викликати функцію, викличте її за допомогою [`{do}` |tags#do] +getProviders(): array .[method] +------------------------------- -Якщо ви все ще хочете створити тег, чудово! Все найнеобхідніше можна знайти в розділі [Створення розширення |creating-extension]. +Викликається перед рендерингом шаблону. Повертає масив провайдерів, які зазвичай є об'єктами, що використовують теги під час виконання. Доступ до них здійснюється через `$this->global->...`. [Більше інформації |custom-tags#Представлення провайдерів]. +```php +public function getProviders(): array +{ + return [ + 'myFoo' => $this->foo, + 'myBar' => $this->bar, + // ... + ]; +} +``` -Передачі компілятора .{data-version:3.0}[#toc-compiler-passes] -============================================================== -Паси компілятора - це функції, які змінюють AST або збирають у них інформацію. У Latte, наприклад, пісочниця реалізована таким чином: вона обходить усі вузли AST, знаходить виклики функцій і методів і замінює їх керованими викликами. +getCacheKey(Latte\Engine $engine): mixed .[method] +-------------------------------------------------- -Як і у випадку з тегами, це складніша функціональність, і вам потрібно розуміти, як Latte працює під капотом. Усе найнеобхідніше можна знайти в розділі [Створення розширення |creating-extension]. +Викликається перед рендерингом шаблону. Повернене значення стає частиною ключа, хеш якого міститься в назві файлу скомпільованого шаблону. Отже, для різних повернених значень Latte згенерує різні файли кешу. diff --git a/latte/uk/filters.texy b/latte/uk/filters.texy index 205f7d8e1d..ea1864c9fd 100644 --- a/latte/uk/filters.texy +++ b/latte/uk/filters.texy @@ -1,112 +1,114 @@ -Фільтри для Latte -***************** +Фільтри Latte +************* .[perex] -Фільтри - це функції, які змінюють або форматують дані до потрібної нам форми. Це короткий огляд вбудованих фільтрів, які доступні. +У шаблонах ми можемо використовувати функції, які допомагають редагувати або переформатовувати дані до кінцевого вигляду. Ми називаємо їх *фільтрами*. .[table-latte-filters] -|## Перетворення рядків / масивів -| `batch` | [Перерахування лінійних даних у таблиці |#batch] -| `breakLines` | [вставляє розриви рядків HTML перед усіма новими рядками |#breakLines] -| `bytes` | [форматує розмір у байтах |#bytes] -| `clamp` | [обмежує значення діапазоном |#clamp] -| `dataStream` | [перетворення протоколу URI даних |#dataStream] -| `date` | [форматує дату |#date] -| `explode` | [розділяє рядок за заданим роздільником |#explode] -| `first` | [повертає перший елемент масиву або символ рядка |#first] -| `implode` | [з'єднує масив із ряд ком|#implode] -| `indent` | [відступи тексту зліва з кількістю табуляцій |#indent] -| `join` | [приєднує масив до ряд ка|#implode] -| `last` | [повертає останній елемент масиву або символ рядка|#last] -| `length` | [повертає довжину рядка або масиву |#length] -| `number` | [форматує число |#number] -| `padLeft` | [завершує рядок до заданої довжини зліва |#padLeft] -| `padRight` | [завершує рядок до заданої довжини справа |#padRight] -| `random` | [повертає випадковий елемент масиву або символ рядка|#random] -| `repeat` | [повторює рядок|#repeat] -| `replace` | [замінює всі входження шуканого рядка заміною |#replace] -| `replaceRE` | [замінює всі входження відповідно до регулярного виразу |#replaceRE] -| `reverse` | [інвертує рядок або масив у форматі UTF-8 |#reverse] -| `slice` | [витягує фрагмент масиву або рядка |#slice] -| `sort` | [сортує масив |#sort] -| `spaceless` | [видаляє пробільні символи |#spaceless], аналогічно тегу [spaceless |tags] -| `split` | [розділяє рядок за заданим роздільником |#explode] -| `strip` | [видаляє пробіли |#spaceless] -| `stripHtml` | [видаляє HTML-теги і перетворює HTML-сутності в текст |#stripHtml] -| `substr` | [повертає частину рядка |#substr] -| `trim` | [видаляє пробіли з рядка |#trim] -| `translate` | [переклад іншими мовами |#translate] -| `truncate` | [скорочує довжину, зберігаючи цілі слова |#truncate] -| `webalize` | [приводить рядок UTF-8 у відповідність до форми, що використовується в URL-адресі |#webalize] +|## Трансформація +| `batch` | [виведення лінійних даних у таблицю |#batch] +| `breakLines` | [Перед кінцем рядка додає HTML перенесення рядка |#breakLines] +| `bytes` | [форматує розмір у байтах |#bytes] +| `clamp` | [обмежує значення в заданому діапазоні |#clamp] +| `dataStream` | [конверсія для протоколу Data URI |#dataStream] +| `date` | [форматує дату та час |#date] +| `explode` | [розділяє рядок на масив за роздільником |#explode] +| `first` | [повертає перший елемент масиву або символ рядка |#first] +| `group` | [групує дані за різними критеріями |#group] +| `implode` | [об'єднує масив у рядок |#implode] +| `indent` | [відступає текст зліва на задану кількість табуляцій |#indent] +| `join` | [об'єднує масив у рядок |#implode] +| `last` | [повертає останній елемент масиву або символ рядка |#last] +| `length` | [повертає довжину рядка в символах або масиву |#length] +| `localDate` | [форматує дату та час відповідно до локалі |#localDate] +| `number` | [форматує число |#number] +| `padLeft` | [доповнює рядок зліва до потрібної довжини |#padLeft] +| `padRight` | [доповнює рядок справа до потрібної довжини |#padRight] +| `random` | [повертає випадковий елемент масиву або символ рядка |#random] +| `repeat` | [повторення рядка |#repeat] +| `replace` | [замінює входження шуканого рядка |#replace] +| `replaceRE` | [замінює входження за регулярним виразом |#replaceRE] +| `reverse` | [обертає UTF‑8 рядок або масив |#reverse] +| `slice` | [витягує частину масиву або рядка |#slice] +| `sort` | [сортує масив |#sort] +| `spaceless` | [видаляє пробіли |#spaceless], подібно до тегу [spaceless |tags] +| `split` | [розділяє рядок на масив за роздільником |#explode] +| `strip` | [видаляє пробіли |#spaceless] +| `stripHtml` | [видаляє HTML теги та перетворює HTML сутності на символи |#stripHtml] +| `substr` | [повертає частину рядка |#substr] +| `trim` | [видаляє початкові та кінцеві пробіли або інші символи |#trim] +| `translate` | [переклад на інші мови |#translate] +| `truncate` | [скорочує довжину зі збереженням слів |#truncate] +| `webalize` | [змінює UTF‑8 рядок до форми, що використовується в URL |#webalize] .[table-latte-filters] -|## Буквений регістр -| `capitalize` | [нижній регістр, перша буква кожного слова у верхньому регістрі |#capitalize] -| `firstUpper` | [робить першу букву у верхньому регістрі |#firstUpper] -| `lower` | [робить рядок рядковим |#lower] -| `upper` | [робить рядок у верхньому регістрі |#upper] +|## Регістр літер +| `capitalize` | [малі літери, перша літера у словах велика |#capitalize] +| `firstUpper` | [перетворює першу літеру на велику |#firstUpper] +| `lower` | [перетворює на малі літери |#lower] +| `upper` | [перетворює на великі літери |#upper] .[table-latte-filters] -|## Округлення чисел -| `ceil` | [округлює число в більший бік із заданою точністю |#ceil] -| `floor` | [округлює число в менший бік із заданою точністю |#floor] -| `round` | [округлення числа до заданої точності |#round] +|## Округлення +| `ceil` | [округлює число вгору до заданої точності |#ceil] +| `floor` | [округлює число вниз до заданої точності |#floor] +| `round` | [округлює число до заданої точності |#round] .[table-latte-filters] -|## Ескапування -| `escapeUrl` | [екранує параметр в URL |#escapeUrl] -| `noescape` | [друкує змінну без екранування |#noescape] -| `query` | [формує рядок запиту в URL |#query] +|## Екранування +| `escapeUrl` | [екранує параметр в URL |#escapeUrl] +| `noescape` | [виводить змінну без екранування |#noescape] +| `query` | [генерує рядок запиту в URL |#query] -Існують також фільтри екранування для HTML (`escapeHtml` і `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) і iCalendar (`escapeICal`), які Latte використовує сам завдяки [контекстно-залежному екрануванню |safety-first#Context-Aware-Escaping], і вам не потрібно їх писати. +Крім того, існують фільтри екранування для HTML (`escapeHtml` та `escapeHtmlComment`), XML (`escapeXml`), JavaScript (`escapeJs`), CSS (`escapeCss`) та iCalendar (`escapeICal`), які Latte використовує самостійно завдяки [контекстно-залежному екрануванню |safety-first#Контекстно-залежне екранування], і вам не потрібно їх записувати. .[table-latte-filters] |## Безпека -| `checkUrl` | [Санує рядок для використання всередині атрибута href |#checkUrl] -| `nocheck` | [запобігає автоматичній санації URL |#nocheck] +| `checkUrl` | [обробляє URL-адресу від небезпечних входжень |#checkUrl] +| `nocheck` | [запобігає автоматичній обробці URL-адреси |#nocheck] -Latte [перевіряє |safety-first#Link-Checking] атрибути `src` і `href` [автоматично |safety-first#Link-Checking], тому вам майже не потрібно використовувати фільтр `checkUrl`. +Latte атрибути `src` та `href` [перевіряє автоматично |safety-first#Перевірка посилань], тому фільтр `checkUrl` майже не потрібно використовувати. .[note] -Всі вбудовані фільтри працюють з рядками в кодуванні UTF-8. +Усі стандартні фільтри призначені для рядків у кодуванні UTF‑8. -Використання .[#toc-usage] -========================== +Використання +============ -Latte дозволяє викликати фільтри, використовуючи нотацію знака труби (пробіл перед ним допускається): +Фільтри записуються після вертикальної риски (перед нею може бути пробіл): ```latte

                                                                                                                                    {$heading|upper}

                                                                                                                                    ``` -Фільтри можуть бути з'єднані ланцюжком, тоді вони застосовуються в порядку зліва направо: +Фільтри (у старих версіях хелпери) можна об'єднувати в ланцюжок, і вони застосовуються в порядку зліва направо: ```latte

                                                                                                                                    {$heading|lower|capitalize}

                                                                                                                                    ``` -Параметри вказуються після назви фільтра через двокрапку або кому: +Параметри вказуються після назви фільтра, розділені двокрапками або комами: ```latte

                                                                                                                                    {$heading|truncate:20,''}

                                                                                                                                    ``` -Фільтри можна застосовувати до виразу: +Фільтри можна застосовувати і до виразу: ```latte {var $name = ($title|upper) . ($subtitle|lower)} ``` -Таким чином можна зареєструвати[власні фільтри |extending-latte#filters]: +[Власні фільтри |custom-filters] можна зареєструвати таким чином: ```php $latte = new Latte\Engine; $latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -Ми використовуємо його в такому шаблоні: +У шаблоні потім викликається так: ```latte

                                                                                                                                    {$text|shortify}

                                                                                                                                    @@ -114,13 +116,13 @@ $latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $ ``` -Фільтри .[#toc-filters] -======================= +Фільтри +======= -batch(int length, mixed item): array .[filter]{data-version:2.7} ----------------------------------------------------------------- -Фільтр, який спрощує виведення лінійних даних у вигляді таблиці. Повертає масив із заданою кількістю елементів. Якщо ви вкажете другий параметр, він буде використаний для заповнення пропущених елементів в останньому рядку. +batch(int $length, mixed $item): array .[filter] +------------------------------------------------ +Фільтр, який спрощує виведення лінійних даних у вигляді таблиці. Повертає масив масивів із заданою кількістю елементів. Якщо ви вкажете другий параметр, він буде використаний для доповнення відсутніх елементів в останньому рядку. ```latte {var $items = ['a', 'b', 'c', 'd', 'e']} @@ -135,7 +137,7 @@ batch(int length, mixed item): array .[filter]{data-version:2.7}
                                                                                                                                    Внутрішній цикл
                                                                                                                                    ``` -Виводить на друк: +Виведе: ```latte @@ -152,20 +154,22 @@ batch(int length, mixed item): array .[filter]{data-version:2.7}
                                                                                                                                    ``` +Див. також [#group] та тег [iterateWhile |tags#iterateWhile]. + breakLines .[filter] -------------------- -Вставляє розриви рядків HTML перед усіма новими рядками. +Додає перед кожним символом нового рядка HTML-тег `
                                                                                                                                    ` ```latte {var $s = "Text & with \n newline"} -{$s|breakLines} {* результати "Text & with
                                                                                                                                    \n newline" *} +{$s|breakLines} {* виведе "Text & with
                                                                                                                                    \n newline" *} ``` -bytes(int precision = 2) .[filter] ----------------------------------- -Форматує розмір у байтах у зручну для читання форму. +bytes(int $precision=2) .[filter] +--------------------------------- +Форматує розмір у байтах у зручному для читання вигляді. Якщо встановлено [локаль |develop#Locale], використовуються відповідні роздільники десяткових знаків та тисяч. ```latte {$size|bytes} 0 B, 1.25 GB, … @@ -173,72 +177,72 @@ bytes(int precision = 2) .[filter] ``` -ceil(int precision = 0) .[filter] ---------------------------------- -Округлює число до заданої точності. +ceil(int $precision=0) .[filter] +-------------------------------- +Округлює число вгору до заданої точності. ```latte -{=3.4|ceil} {* виводиться 4 *} -{=135.22|ceil:1} {* виводиться 135.3 *} -{=135.22|ceil:3} {* виводиться 135.22 *} +{=3.4|ceil} {* виведе 4 *} +{=135.22|ceil:1} {* виведе 135.3 *} +{=135.22|ceil:3} {* виведе 135.22 *} ``` -Див. також [floor |#floor], [round |#round]. +Див. також [#floor], [#round]. capitalize .[filter] -------------------- -Повертає версію значення у вигляді заголовка. Слова починаються з великих літер, всі інші символи - з малих. Потрібне розширення PHP `mbstring`. +Слова починатимуться з великих літер, усі інші символи будуть малими. Потребує PHP розширення `mbstring`. ```latte -{='i like LATTE'|capitalize} {* виводить 'I Like Latte' *} +{='i like LATTE'|capitalize} {* виведе 'I Like Latte' *} ``` -Див. також [firstUpper |#firstUpper], [lower |#lower], [upper |#upper]. +Див. також [#firstUpper], [#lower], [#upper]. checkUrl .[filter] ------------------ -Застосовує санітарну обробку URL-адрес. Перевіряє, чи містить змінна веб-адресу (тобто протокол HTTP/HTTPS) і запобігає запису посилань, які можуть становити загрозу безпеці. +Примусово обробляє URL-адресу. Перевіряє, чи містить змінна веб-URL (тобто протокол HTTP/HTTPS) та запобігає виведенню посилань, які можуть становити загрозу безпеці. ```latte {var $link = 'javascript:window.close()'} -checked -unchecked +перевірене +неперевірене ``` -Виводить на друк: +Виведе: ```latte -checked -unchecked +перевірене +неперевірене ``` -Див. також [nocheck |#nocheck]. +Див. також [#nocheck]. -clamp(int|float min, int|float max) .[filter]{data-version:2.9} ---------------------------------------------------------------- -Повертає значення, обмежене діапазоном min та max. +clamp(int|float $min, int|float $max) .[filter] +----------------------------------------------- +Обмежує значення в заданому інклюзивному діапазоні min та max. ```latte {$level|clamp: 0, 255} ``` -Існує також у вигляді [функції |functions#clamp]. +Існує також як [функція |functions#clamp]. -dataStream(string mimetype = detect) .[filter] ----------------------------------------------- -Перетворює вміст на схему URI даних. Його можна використовувати для вставки зображень в HTML або CSS без необхідності посилання на зовнішні файли. +dataStream(string $mimetype=detect) .[filter] +--------------------------------------------- +Конвертує вміст у схему data URI. За допомогою нього можна вставляти зображення в HTML або CSS без необхідності посилатися на зовнішні файли. -Нехай у змінній є зображення `$img = Image::fromFile('obrazek.gif')`, тоді +Маємо в змінній зображення `$img = Image::fromFile('obrazek.gif')`, тоді ```latte - + ``` -Виводить, наприклад: +Виведе, наприклад: ```latte {$name} ``` -Див. також [query |#query]. +Див. також [#query]. -explode(string separator = '') .[filter]{data-version:2.10.2} -------------------------------------------------------------- -Розділяє рядок за заданим роздільником і повертає масив рядків. Псевдонім для `split`. +explode(string $separator='') .[filter] +--------------------------------------- +Розділяє рядок на масив за роздільником. Аліас для `split`. ```latte -{='one,two,three'|explode:','} {* returns ['one', 'two', 'three'] *} +{='one,two,three'|explode:','} {* повертає ['one', 'two', 'three'] *} ``` -Якщо роздільник є порожнім рядком (значення за замовчуванням), вхідні дані буде розділено на окремі символи: +Якщо роздільник є порожнім рядком (значення за замовчуванням), вхід буде розділено на окремі символи: ```latte -{='123'|explode} {* returns ['1', '2', '3'] *} +{='123'|explode} {* повертає ['1', '2', '3'] *} ``` -Ви також можете використовувати псевдонім `split`: +Ви також можете використовувати аліас `split`: ```latte -{='1,2,3'|split:','} {* returns ['1', '2', '3'] *} +{='1,2,3'|split:','} {* повертає ['1', '2', '3'] *} ``` -Див. також [implode |#implode]. +Див. також [#implode]. -first .[filter]{data-version:2.10.2} ------------------------------------- +first .[filter] +--------------- Повертає перший елемент масиву або символ рядка: ```latte -{=[1, 2, 3, 4]|first} {* outputs 1 *} -{='abcd'|first} {* outputs 'a' *} +{=[1, 2, 3, 4]|first} {* виведе 1 *} +{='abcd'|first} {* виведе 'a' *} ``` -Див. також [last |#last], [random |#random]. +Див. також [#last], [#random]. -floor(int precision = 0) .[filter] ----------------------------------- -Округлює число до заданої точності. +floor(int $precision=0) .[filter] +--------------------------------- +Округлює число вниз до заданої точності. ```latte -{=3.5|floor} {* outputs 3 *} -{=135.79|floor:1} {* outputs 135.7 *} -{=135.79|floor:3} {* outputs 135.79 *} +{=3.5|floor} {* виведе 3 *} +{=135.79|floor:1} {* виведе 135.7 *} +{=135.79|floor:3} {* виведе 135.79 *} ``` -Див. також [ceil |#ceil], [round |#round]. +Див. також [#ceil], [#round]. firstUpper .[filter] -------------------- -Перетворює першу літеру значення у верхній регістр. Потрібне розширення PHP `mbstring`. +Перетворює першу літеру на велику. Потребує PHP розширення `mbstring`. ```latte -{='the latte'|firstUpper} {* outputs 'The latte' *} +{='the latte'|firstUpper} {* виведе 'The latte' *} ``` -Див. також [capitalize |#capitalize], [lower |#lower] регістр, [upper |#upper] регістр. +Див. також [#capitalize], [#lower], [#upper]. + +group(string|int|\Closure $by): array .[filter]{data-version:3.0.16} +-------------------------------------------------------------------- +Фільтр групує дані за різними критеріями. -implode(string glue = '') .[filter] ------------------------------------ -Повертає рядок, який є конкатенацією рядків у масиві. Псевдонім для `join`. +У цьому прикладі рядки в таблиці групуються за стовпцем `categoryId`. Виходом є масив масивів, де ключем є значення у стовпці `categoryId`. [Прочитайте детальний посібник |cookbook/grouping]. ```latte -{=[1, 2, 3]|implode} {* outputs '123' *} -{=[1, 2, 3]|implode:'|'} {* outputs '1|2|3' *} +{foreach ($items|group: categoryId) as $categoryId => $categoryItems} +
                                                                                                                                      + {foreach $categoryItems as $item} +
                                                                                                                                    • {$item->name}
                                                                                                                                    • + {/foreach} +
                                                                                                                                    +{/foreach} ``` -Ви також можете використовувати псевдонім `join`: .{data-version:2.10.2} +Див. також [#batch], функція [group |functions#group] та тег [iterateWhile |tags#iterateWhile]. + + +implode(string $glue='') .[filter] +---------------------------------- +Повертає рядок, який є конкатенацією елементів послідовності. Аліас для `join`. ```latte -{=[1, 2, 3]|join} {* outputs '123' *} +{=[1, 2, 3]|implode} {* виведе '123' *} +{=[1, 2, 3]|implode:'|'} {* виведе '1|2|3' *} ``` +Ви також можете використовувати аліас `join`: -indent(int level = 1, string char = "\t") .[filter] ---------------------------------------------------- -Відступає текст зліва на задану кількість табуляцій або інших символів, які ми вказуємо у другому необов'язковому аргументі. Порожні рядки не відступаються. +```latte +{=[1, 2, 3]|join} {* виведе '123' *} +``` + + +indent(int $level=1, string $char="\t") .[filter] +------------------------------------------------- +Відступає текст зліва на задану кількість табуляцій або інших символів, які можна вказати у другому аргументі. Порожні рядки не відступаються. ```latte
                                                                                                                                    @@ -358,7 +382,7 @@ indent(int level = 1, string char = "\t") .[filter]
                                                                                                                                    ``` -Друкує: +Виведе: ```latte
                                                                                                                                    @@ -367,26 +391,26 @@ indent(int level = 1, string char = "\t") .[filter] ``` -last .[filter]{data-version:2.10.2} ------------------------------------ +last .[filter] +-------------- Повертає останній елемент масиву або символ рядка: ```latte -{=[1, 2, 3, 4]|last} {* outputs 4 *} -{='abcd'|last} {* outputs 'd' *} +{=[1, 2, 3, 4]|last} {* виведе 4 *} +{='abcd'|last} {* виведе 'd' *} ``` -Див. також [first |#first], [random |#random]. +Див. також [#first], [#random]. length .[filter] ---------------- Повертає довжину рядка або масиву. -- для рядків повертає довжину в символах UTF-8 -- для масивів - кількість елементів -- для об'єктів, що реалізують інтерфейс Countable, буде використовувати значення, що повертається функцією count() -- для об'єктів, що реалізують інтерфейс IteratorAggregate, буде використовувати значення, що повертається функцією iterator_count() +- для рядків повертає довжину в UTF‑8 символах +- для масивів повертає кількість елементів +- для об'єктів, які реалізують інтерфейс `Countable`, використовує повернене значення методу `count()` +- для об'єктів, які реалізують інтерфейс `IteratorAggregate`, використовує повернене значення функції `iterator_count()` ```latte @@ -396,93 +420,203 @@ length .[filter] ``` +localDate(?string $format=null, ?string $date=null, ?string $time=null) .[filter] +--------------------------------------------------------------------------------- +Форматує дату та час відповідно до [локалі |develop#Locale], що забезпечує послідовне та локалізоване відображення часових даних у різних мовах та регіонах. Фільтр приймає дату як UNIX timestamp, рядок або об'єкт типу `DateTimeInterface`. + +```latte +{$date|localDate} {* 15 квітня 2024 *} +{$date|localDate: format: yM} {* 4/2024 *} +{$date|localDate: date: medium} {* 15. 4. 2024 *} +``` + +Якщо ви використовуєте фільтр без параметрів, дата буде виведена на рівні `long`, див. далі. + +**a) використання формату** + +Параметр `format` описує, які часові компоненти мають бути відображені. Він використовує для них літерні коди, кількість повторень яких впливає на ширину виводу: + +| рік | `y` / `yy` / `yyyy` | `2024` / `24` / `2024` +| місяць | `M` / `MM` / `MMM` / `MMMM` | `8` / `08` / `сер` / `серпень` +| день | `d` / `dd` / `E` / `EEEE` | `1` / `01` / `нд` / `неділя` +| година | `j` / `H` / `h` | бажаний / 24-годинний / 12-годинний +| хвилина | `m` / `mm` | `5` / `05` (2 цифри в комбінації з секундами) +| секунда | `s` / `ss` | `8` / `08` (2 цифри в комбінації з хвилинами) + +Порядок кодів у форматі не має значення, оскільки порядок компонентів виводиться відповідно до звичаїв локалі. Таким чином, формат не залежить від неї. Наприклад, формат `yyyyMMMMd` у середовищі `en_US` виведе `April 15, 2024`, тоді як у середовищі `uk_UA` виведе `15 квітня 2024`: + +| locale: | uk_UA | en_US +|--- +| `format: 'dMy'` | 10. 8. 2024 | 8/10/2024 +| `format: 'yM'` | 8/2024 | 8/2024 +| `format: 'yyyyMMMM'` | серпень 2024 | August 2024 +| `format: 'MMMM'` | серпень | August +| `format: 'jm'` | 17:22 | 5:22 PM +| `format: 'Hm'` | 17:22 | 17:22 +| `format: 'hm'` | 5:22 вечора | 5:22 PM + + +**b) використання попередньо встановлених стилів** + +Параметри `date` та `time` визначають, наскільки детально має бути виведена дата та час. Ви можете вибрати з кількох рівнів: `full`, `long`, `medium`, `short`. Можна вивести лише дату, лише час або обидва: + +| locale: | uk_UA | en_US +|--- +| `date: short` | 23.01.78 | 1/23/78 +| `date: medium` | 23 січ. 1978 | Jan 23, 1978 +| `date: long` | 23 січня 1978 р. | January 23, 1978 +| `date: full` | понеділок, 23 січня 1978 р. | Monday, January 23, 1978 +| `time: short` | 8:30 | 8:30 AM +| `time: medium` | 8:30:59 | 8:30:59 AM +| `time: long` | 8:30:59 EET | 8:30:59 AM GMT+1 +| `date: short, time: short` | 23.01.78, 8:30 | 1/23/78, 8:30 AM +| `date: medium, time: short` | 23 січ. 1978 р., 8:30 | Jan 23, 1978, 8:30 AM +| `date: long, time: short` | 23 січня 1978 р. о 8:30 | January 23, 1978 at 8:30 AM + +Для дати можна додатково використовувати префікс `relative-` (наприклад, `relative-short`), який для дат, близьких до поточної, відобразить `вчора`, `сьогодні` або `завтра`, інакше виведеться стандартним способом. + +```latte +{$date|localDate: date: relative-short} {* вчора *} +``` + +Див. також [#date]. + + lower .[filter] --------------- -Перетворює значення в нижній регістр. Потрібне розширення PHP `mbstring`. +Перетворює рядок на малі літери. Потребує PHP розширення `mbstring`. ```latte -{='LATTE'|lower} {* outputs 'latte' *} +{='LATTE'|lower} {* виведе 'latte' *} ``` -Див. також [capitalize |#capitalize], [firstUpper |#firstUpper], [upper |#upper]. +Див. також [#capitalize], [#firstUpper], [#upper]. nocheck .[filter] ----------------- -Запобігає автоматичній санітарній обробці URL-адрес. Latte [автоматично перевіряє |safety-first#Link checking], чи містить змінна веб-адресу (тобто протокол HTTP/HTTPS) і запобігає запису посилань, які можуть становити загрозу безпеці. +Запобігає автоматичній обробці URL-адреси. Latte [автоматично перевіряє |safety-first#Перевірка посилань], чи містить змінна веб-URL (тобто протокол HTTP/HTTPS) та запобігає виведенню посилань, які можуть становити загрозу безпеці. -Якщо посилання використовує іншу схему, наприклад, `javascript:` або `data:`, і ви впевнені в його вмісті, ви можете відключити перевірку через `|nocheck`. +Якщо посилання використовує іншу схему, наприклад `javascript:` або `data:`, і ви впевнені в його вмісті, ви можете вимкнути перевірку за допомогою `|nocheck`. ```latte {var $link = 'javascript:window.close()'} -checked -unchecked +перевірене +неперевірене ``` -Відбитки: +Виведе: ```latte -checked -unchecked +перевірене +неперевірене ``` -Див. також [checkUrl |#checkUrl]. +Див. також [#checkUrl]. noescape .[filter] ------------------ -Вимикає автоматичне екранування. +Забороняє автоматичне екранування. ```latte {var $trustedHtmlString = 'hello'} -Escaped: {$trustedHtmlString} -Unescaped: {$trustedHtmlString|noescape} +Екранований: {$trustedHtmlString} +Неекранований: {$trustedHtmlString|noescape} ``` -Друкує: +Виведе: ```latte -Escaped: <b>hello</b> -Unescaped: hello +Екранований: <b>hello</b> +Неекранований: hello ``` .[warning] -Неправильне використання фільтра `noescape` може призвести до XSS-уразливості! Ніколи не використовуйте його, якщо ви не впевнені в тому, що ви робите, і що рядок, який ви друкуєте, походить з надійного джерела. +Неправильне використання фільтра `noescape` може призвести до виникнення уразливості XSS! Ніколи не використовуйте його, якщо ви не **повністю впевнені** в тому, що робите, і що рядок, який виводиться, походить з надійного джерела. + + +number(int $decimals=0, string $decPoint='.', string $thousandsSep=',') .[filter] +--------------------------------------------------------------------------------- +Форматує число до певної кількості десяткових знаків. Якщо встановлено [локаль |develop#Locale], використовуються відповідні роздільники десяткових знаків та тисяч. +```latte +{1234.20|number} 1,234 +{1234.20|number:1} 1,234.2 +{1234.20|number:2} 1,234.20 +{1234.20|number:2, ',', ' '} 1 234,20 +``` + + +number(string $format) .[filter] +-------------------------------- +Параметр `format` дозволяє визначити вигляд чисел точно відповідно до ваших потреб. Для цього потрібно мати встановлену [локаль |develop#Locale]. Формат складається з кількох спеціальних символів, повний опис яких ви знайдете в документації "DecimalFormat":https://unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns: + +- `0` обов'язкова цифра, завжди відображається, навіть якщо це нуль +- `#` необов'язкова цифра, відображається лише тоді, коли на цьому місці число дійсно є +- `@` значуща цифра, допомагає відобразити число з певною кількістю значущих цифр +- `.` вказує, де має бути десяткова кома (або крапка, залежно від країни) +- `,` служить для розділення груп цифр, найчастіше тисяч +- `%` число множиться на 100× і додається знак відсотка + +Розглянемо приклади. У першому прикладі два десяткові знаки є обов'язковими, у другому - необов'язковими. Третій приклад показує доповнення нулями зліва та справа, четвертий відображає лише існуючі цифри: -number(int decimals = 0, string decPoint = '.', string thousandsSep = ',') .[filter] ------------------------------------------------------------------------------------- -Форматує число до заданої кількості десяткових знаків. Ви також можете вказати символ десяткової крапки та роздільник тисяч. +```latte +{1234.5|number: '#,##0.00'} {* 1,234.50 *} +{1234.5|number: '#,##0.##'} {* 1,234.5 *} +{1.23 |number: '000.000'} {* 001.230 *} +{1.2 |number: '##.##'} {* 1.2 *} +``` + +Значущі цифри визначають, скільки цифр, незалежно від десяткової коми, має бути відображено, при цьому відбувається округлення: ```latte -{1234.20 |number} 1,234 -{1234.20 |number:1} 1,234.2 -{1234.20 |number:2} 1,234.20 -{1234.20 |number:2, ',', ' '} 1 234,20 +{1234|number: '@@'} {* 1200 *} +{1234|number: '@@@'} {* 1230 *} +{1234|number: '@@@#'} {* 1234 *} +{1.2345|number: '@@@'} {* 1.23 *} +{0.00123|number: '@@'} {* 0.0012 *} ``` +Легкий спосіб відобразити число як відсотки. Число множиться на 100× і додається знак `%`: + +```latte +{0.1234|number: '#.##%'} {* 12.34% *} +``` -padLeft(int length, string pad = ' ') .[filter] +Ми можемо визначити різний формат для додатних і від'ємних чисел, розділяючи їх знаком `;`. Таким чином, наприклад, можна налаштувати, щоб додатні числа відображалися зі знаком `+`: + +```latte +{42|number: '#.##;(#.##)'} {* 42 *} +{-42|number: '#.##;(#.##)'} {* (42) *} +{42|number: '+#.##;-#.##'} {* +42 *} +{-42|number: '+#.##;-#.##'} {* -42 *} +``` + +Пам'ятайте, що фактичний вигляд чисел може відрізнятися залежно від налаштувань країни. Наприклад, у деяких країнах використовується кома замість крапки як роздільник десяткових знаків. Цей фільтр це автоматично враховує, і вам не потрібно ні про що турбуватися. + + +padLeft(int $length, string $pad=' ') .[filter] ----------------------------------------------- Доповнює рядок до певної довжини іншим рядком зліва. ```latte -{='hello'|padLeft: 10, '123'} {* outputs '12312hello' *} +{='hello'|padLeft: 10, '123'} {* виведе '12312hello' *} ``` -padRight(int length, string pad = ' ') .[filter] +padRight(int $length, string $pad=' ') .[filter] ------------------------------------------------ -Заміщає рядок до певної довжини іншим рядком праворуч. +Доповнює рядок до певної довжини іншим рядком справа. ```latte -{='hello'|padRight: 10, '123'} {* outputs 'hello12312' *} +{='hello'|padRight: 10, '123'} {* виведе 'hello12312' *} ``` -query .[filter]{data-version:2.10} ------------------------------------ +query .[filter] +--------------- Динамічно генерує рядок запиту в URL: ```latte @@ -490,110 +624,110 @@ query .[filter]{data-version:2.10} search ``` -Друкує: +Виведе: ```latte click search ``` -Ключі зі значенням `null` пропущено. +Ключі зі значенням `null` пропускаються. -Дивіться також [escapeUrl |#escapeUrl]. +Див. також [#escapeUrl]. -random .[filter]{data-version:2.10.2} -------------------------------------- +random .[filter] +---------------- Повертає випадковий елемент масиву або символ рядка: ```latte -{=[1, 2, 3, 4]|random} {* example output: 3 *} -{='abcd'|random} {* example output: 'b' *} +{=[1, 2, 3, 4]|random} {* виведе напр.: 3 *} +{='abcd'|random} {* виведе напр.: 'b' *} ``` -Дивіться також [first |#first], [last |#last]. +Див. також [#first], [#last]. -repeat(int count) .[filter] ---------------------------- +repeat(int $count) .[filter] +---------------------------- Повторює рядок x разів. ```latte -{='hello'|repeat: 3} {* outputs 'hellohellohello' *} +{='hello'|repeat: 3} {* виведе 'hellohellohello' *} ``` -replace(string|array search, string replace = '') .[filter] +replace(string|array $search, string $replace='') .[filter] ----------------------------------------------------------- -Замінює всі входження пошукового рядка на рядок заміни. +Замінює всі входження шуканого рядка на рядок заміни. ```latte -{='hello world'|replace: 'world', 'friend'} {* outputs 'hello friend' *} +{='hello world'|replace: 'world', 'friend'} {* виведе 'hello friend' *} ``` -Можна зробити кілька замін одночасно: .{data-version:2.10.2} +Можна виконати кілька замін одночасно: ```latte -{='hello world'|replace: [h => l, l => h]} {* outputs 'lehho worhd' *} +{='hello world'|replace: [h => l, l => h]} {* виведе 'lehho worhd' *} ``` -replaceRE(string pattern, string replace = '') .[filter] +replaceRE(string $pattern, string $replace='') .[filter] -------------------------------------------------------- -Замінює всі входження відповідно до регулярного виразу. +Виконує пошук за регулярними виразами із заміною. ```latte -{='hello world'|replaceRE: '/l.*/', 'l'} {* outputs 'hel' *} +{='hello world'|replaceRE: '/l.*/', 'l'} {* виведе 'hel' *} ``` reverse .[filter] ----------------- -Реверсує заданий рядок або масив. +Обертає заданий рядок або масив. ```latte {var $s = 'Nette'} -{$s|reverse} {* outputs 'etteN' *} +{$s|reverse} {* виведе 'etteN' *} {var $a = ['N', 'e', 't', 't', 'e']} -{$a|reverse} {* returns ['e', 't', 't', 'e', 'N'] *} +{$a|reverse} {* повертає ['e', 't', 't', 'e', 'N'] *} ``` -round(int precision = 0) .[filter] ----------------------------------- -Округляє число до заданої точності. +round(int $precision=0) .[filter] +--------------------------------- +Округлює число до заданої точності. ```latte -{=3.4|round} {* outputs 3 *} -{=3.5|round} {* outputs 4 *} -{=135.79|round:1} {* outputs 135.8 *} -{=135.79|round:3} {* outputs 135.79 *} +{=3.4|round} {* виведе 3 *} +{=3.5|round} {* виведе 4 *} +{=135.79|round:1} {* виведе 135.8 *} +{=135.79|round:3} {* виведе 135.79 *} ``` -Див. також [ceil |#ceil], [floor |#floor]. +Див. також [#ceil], [#floor]. -slice(int start, int length = null, bool preserveKeys = false) .[filter]{data-version:2.10.2} ---------------------------------------------------------------------------------------------- -Витягує фрагмент масиву або рядка. +slice(int $start, ?int $length=null, bool $preserveKeys=false) .[filter] +------------------------------------------------------------------------ +Витягує частину масиву або рядка. ```latte -{='hello'|slice: 1, 2} {* outputs 'el' *} -{=['a', 'b', 'c']|slice: 1, 2} {* outputs ['b', 'c'] *} +{='hello'|slice: 1, 2} {* виведе 'el' *} +{=['a', 'b', 'c']|slice: 1, 2} {* виведе ['b', 'c'] *} ``` -Фільтр фрагментів працює як PHP-функція `array_slice` для масивів і `mb_substr` для рядків з переходом на `iconv_substr` в режимі UTF-8. +Фільтр працює як функція PHP `array_slice` для масивів або `mb_substr` для рядків з fallback на функцію `iconv_substr` у режимі UTF‑8. -Якщо початок невід'ємний, послідовність почнеться з цього початку у змінній. Якщо start від'ємне, послідовність почнеться на відстані від кінця змінної. +Якщо `start` додатний, послідовність почнеться зі зміщенням на цю кількість від початку масиву/рядка. Якщо від'ємний, послідовність почнеться зі зміщенням на стільки від кінця. -Якщо задано довжину і вона додатна, то послідовність міститиме стільки елементів, скільки вказано у змінній. Якщо довжина змінної менша за довжину, то в послідовності будуть присутні лише доступні елементи змінної. Якщо задано від'ємне значення довжини, то послідовність зупиниться за стільки-то елементів від кінця змінної. Якщо довжину не вказано, то послідовність міститиме всі елементи від зсуву до кінця змінної. +Якщо вказаний параметр `length` і він додатний, послідовність міститиме стільки елементів. Якщо до цієї функції передано від'ємний параметр `length`, послідовність міститиме всі елементи вихідного масиву, починаючи з позиції `start` і закінчуючи на позиції, меншій на `length` елементів від кінця масиву. Якщо цей параметр не вказано, послідовність міститиме всі елементи вихідного масиву, починаючи з позиції `start`. -За замовчуванням фільтр перевпорядковує і скидає ключі цілочисельних масивів. Цю поведінку можна змінити, встановивши preserveKeys у true. Рядкові ключі завжди зберігаються, незалежно від цього параметра. +За замовчуванням фільтр змінює порядок і скидає цілочисельні ключі масиву. Цю поведінку можна змінити, встановивши `preserveKeys` на `true`. Рядкові ключі завжди зберігаються, незалежно від цього параметра. -sort .[filter]{data-version:2.9} ---------------------------------- -Фільтр, який сортує масив і зберігає зв'язок індексів. +sort(?Closure $comparison, string|int|\Closure|null $by=null, string|int|\Closure|bool $byKey=false) .[filter] +-------------------------------------------------------------------------------------------------------------- +Фільтр сортує елементи масиву або ітератора та зберігає їх асоціативні ключі. При встановленій [локалі |develop#Locale] сортування керується її правилами, якщо не вказана власна функція порівняння. ```latte {foreach ($names|sort) as $name} @@ -601,7 +735,7 @@ sort .[filter]{data-version:2.9} {/foreach} ``` -Масив відсортовано у зворотному порядку. +Відсортований масив у зворотному порядку: ```latte {foreach ($names|sort|reverse) as $name} @@ -609,16 +743,42 @@ sort .[filter]{data-version:2.9} {/foreach} ``` -Ви можете передати власну функцію порівняння як параметр: .{data-version:2.10.2} +Ви можете вказати власну функцію порівняння для сортування (приклад показує, як змінити порядок сортування від найбільшого до найменшого): + +```latte +{var $reverted = ($names|sort: fn($a, $b) => $b <=> $a)} +``` + +Фільтр `|sort` також дозволяє сортувати елементи за ключами: + +```latte +{foreach ($names|sort: byKey: true) as $name} + ... +{/foreach} +``` + +Якщо вам потрібно відсортувати таблицю за певним стовпцем, ви можете використовувати параметр `by`. Значення `'name'` у прикладі вказує, що сортування буде відбуватися за `$item->name` або `$item['name']`, залежно від того, чи є `$item` масивом чи об'єктом: ```latte -{var $sorted = ($names|sort: fn($a, $b) => $b <=> $a)} +{foreach ($items|sort: by: 'name') as $item} + {$item->name} +{/foreach} +``` + +Ви також можете визначити функцію зворотного виклику, яка визначить значення, за яким слід сортувати: + +```latte +{foreach ($items|sort: by: fn($items) => $items->category->name) as $item} + {$item->name} +{/foreach} ``` +Таким же чином можна використовувати і параметр `byKey`. -spaceless .[filter]{data-version:2.10.2} ------------------------------------------ -Видаляє непотрібні пробіли з виводу. Також можна використовувати псевдонім `strip`. + +spaceless .[filter] +------------------- +Видаляє зайві пробіли з виводу. Ви також можете використовувати аліас `strip`. ```latte {block |spaceless} @@ -628,7 +788,7 @@ spaceless .[filter]{data-version:2.10.2} {/block} ``` -Друкує: +Виведе: ```latte
                                                                                                                                    • Hello
                                                                                                                                    @@ -637,47 +797,47 @@ spaceless .[filter]{data-version:2.10.2} stripHtml .[filter] ------------------- -Перетворює HTML на звичайний текст. Тобто видаляє HTML-теги і перетворює HTML-об'єкти на текст. +Перетворює HTML на чистий текст. Тобто видаляє з нього HTML-теги та перетворює HTML-сутності на текст. ```latte -{='

                                                                                                                                    one < two

                                                                                                                                    '|stripHtml} {* outputs 'one < two' *} +{='

                                                                                                                                    one < two

                                                                                                                                    '|stripHtml} {* виведе 'one < two' *} ``` -Отриманий в результаті звичайний текст може містити символи, які представляють теги HTML, наприклад, `'<p>'|stripHtml` буде перетворено на `

                                                                                                                                    `. Ніколи не виводьте отриманий текст у вигляді `|noescape`, оскільки це може призвести до уразливості. +Отриманий чистий текст може природно містити символи, які представляють HTML-теги, наприклад `'<p>'|stripHtml` перетвориться на `

                                                                                                                                    `. У жодному разі не виводьте так отриманий текст з `|noescape`, оскільки це може призвести до виникнення діри в безпеці. -substr(int offset, int length = null) .[filter] ------------------------------------------------ -Витягує фрагмент рядка. Цей фільтр було замінено на фільтр [фрагментів |#slice]. +substr(int $offset, ?int $length=null) .[filter] +------------------------------------------------ +Витягує частину рядка. Цей фільтр було замінено фільтром [#slice]. ```latte {$string|substr: 1, 2} ``` -translate(string message, ...args) .[filter]{data-version:3.0} --------------------------------------------------------------- -Перекладає вирази на інші мови. Щоб зробити фільтр доступним, вам потрібно [налаштувати пере |develop#TranslatorExtension]кладач. Ви також можете використовувати [теги для перекладу |tags#Translation]. +translate(...$args) .[filter] +----------------------------- +Перекладає вирази на інші мови. Щоб фільтр був доступний, потрібно [налаштувати перекладач |develop#TranslatorExtension]. Ви також можете використовувати [теги для перекладу |tags#Переклади]. ```latte -{='Baskter'|translate} +{='Кошик'|translate} {$item|translate} ``` -trim(string charlist = " \t\n\r\0\x0B\u{A0}") .[filter] -------------------------------------------------------- -Видаляти початкові та кінцеві символи, за замовчуванням пробіли. +trim(string $charlist=" \t\n\r\0\x0B\u{A0}") .[filter] +------------------------------------------------------ +Видаляє пробіли (або інші символи) з початку та кінця рядка. ```latte -{=' I like Latte. '|trim} {* outputs 'I like Latte.' *} -{=' I like Latte.'|trim: '.'} {* outputs ' I like Latte' *} +{=' I like Latte. '|trim} {* виведе 'I like Latte.' *} +{=' I like Latte.'|trim: '.'} {* виведе ' I like Latte' *} ``` -truncate(int length, string append = '…') .[filter] +truncate(int $length, string $append='…') .[filter] --------------------------------------------------- -Скорочує рядок до максимально заданої довжини, але намагається зберегти цілі слова. Якщо рядок укорочено, додає в кінці багатокрапку (це можна змінити за допомогою другого параметра). +Обрізає рядок до вказаної максимальної довжини, при цьому намагаючись зберігати цілі слова. Якщо рядок скорочується, в кінці додається три крапки (можна змінити другим параметром). ```latte {var $title = 'Hello, how are you?'} @@ -689,25 +849,25 @@ truncate(int length, string append = '…') .[filter] upper .[filter] --------------- -Перетворює значення у верхній регістр. Потрібне розширення PHP `mbstring`. +Перетворює рядок на великі літери. Потребує PHP розширення `mbstring`. ```latte -{='latte'|upper} {* outputs 'LATTE' *} +{='latte'|upper} {* виведе 'LATTE' *} ``` -Дивіться також [capitalize |#capitalize], [firstUpper |#firstUpper], [lower |#lower]. +Див. також [#capitalize], [#firstUpper], [#lower]. webalize .[filter] ------------------ -Перетворює в ASCII. +Змінює UTF‑8 рядок до форми, що використовується в URL. -Перетворює пробіли на дефіси. Видаляє символи, які не є алфавітно-цифровими, підкресленнями або дефісами. Перетворює на малі літери. Також видаляє пробіли на початку та в кінці тексту. +Перетворюється на ASCII. Перетворює пробіли на дефіси. Видаляє символи, які не є буквено-цифровими, підкресленнями або дефісами. Перетворює на малі літери. Також видаляє початкові та кінцеві пробіли. ```latte -{var $s = 'Our 10. product'} -{$s|webalize} {* outputs 'our-10-product' *} +{var $s = 'Наш 10-й продукт'} +{$s|webalize} {* виведе 'nash-10-j-produkt' *} ``` .[caution] -Потрібен пакунок [nette/utils |utils:]. +Потребує бібліотеку [nette/utils |utils:]. diff --git a/latte/uk/functions.texy b/latte/uk/functions.texy index ceeb334904..1c7c2a5828 100644 --- a/latte/uk/functions.texy +++ b/latte/uk/functions.texy @@ -2,22 +2,24 @@ ************* .[perex] -На додаток до звичайних функцій PHP, ви можете використовувати їх у шаблонах. +У шаблонах, крім звичайних функцій PHP, ми можемо використовувати також ці додаткові функції. .[table-latte-filters] -| `clamp` | [затискає значення в діапазон |#clamp] +| `clamp` | [обмежує значення в заданому діапазоні |#clamp] | `divisibleBy`| [перевіряє, чи ділиться змінна на число |#divisibleBy] -| `even` | [перевіряє, чи є дане число парним |#even] -| `first` | [повертає перший елемент масиву або символ рядка |#first] -| `last` | [pповертає останній елемент масиву або символ рядка |#last] -| `odd` | [перевіряє, чи є дане число непарним |#odd] -| `slice` | [витягує фрагмент масиву або рядка |#slice] +| `even` | [перевіряє, чи є дане число парним |#even] +| `first` | [повертає перший елемент масиву або символ рядка |#first] +| `group` | [групує дані за різними критеріями |#group] +| `hasBlock` | [перевіряє існування блоку |#hasBlock] +| `last` | [повертає останній елемент масиву або символ рядка |#last] +| `odd` | [перевіряє, чи є дане число непарним |#odd] +| `slice` | [витягує частину масиву або рядка |#slice] -Використання .[#toc-usage] -========================== +Використання +============ -Функції використовуються так само, як і звичайні функції PHP, і можуть бути використані у всіх виразах: +Функції використовуються так само, як звичайні функції PHP, і їх можна використовувати у всіх виразах: ```latte

                                                                                                                                    {clamp($num, 1, 100)}

                                                                                                                                    @@ -25,14 +27,14 @@ {if odd($num)} ... {/if} ``` -[Користувацькі функції |extending-latte#Functions] можуть бути зареєстровані таким чином: +[Власні функції |custom-functions] можна зареєструвати таким чином: ```php $latte = new Latte\Engine; $latte->addFunction('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len)); ``` -Ми використовуємо його в шаблоні таким чином: +У шаблоні потім викликається так: ```latte

                                                                                                                                    {shortify($text)}

                                                                                                                                    @@ -40,23 +42,23 @@ $latte->addFunction('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, ``` -Функції .[#toc-functions] -========================= +Функції +======= -clamp(int|float $value, int|float $min, int|float $max): int|float .[method]{data-version:2.9} ----------------------------------------------------------------------------------------------- -Повертає значення, затиснуте у включно діапазоні min і max. +clamp(int|float $value, int|float $min, int|float $max): int|float .[method] +---------------------------------------------------------------------------- +Обмежує значення в заданому інклюзивному діапазоні min та max. ```latte {=clamp($level, 0, 255)} ``` -Див. також [затиск фільтра |filters#clamp]: +Див. також [фільтр clamp |filters#clamp]. -divisibleBy(int $value, int $by): bool .[method]{data-version:2.10.2} ---------------------------------------------------------------------- +divisibleBy(int $value, int $by): bool .[method] +------------------------------------------------ Перевіряє, чи ділиться змінна на число. ```latte @@ -64,61 +66,91 @@ divisibleBy(int $value, int $by): bool .[method]{data-version:2.10.2} ``` -even(int $value): bool .[method]{data-version:2.10.2} ------------------------------------------------------ -Перевіряє, чи є задане число парним. +even(int $value): bool .[method] +-------------------------------- +Перевіряє, чи є дане число парним. ```latte {if even($num)} ... {/if} ``` -first(string|array $value): mixed .[method]{data-version:2.10.2} ----------------------------------------------------------------- +first(string|iterable $value): mixed .[method] +---------------------------------------------- Повертає перший елемент масиву або символ рядка: ```latte -{=first([1, 2, 3, 4])} {* виводить 1 *} -{=first('abcd')} {* виводиться 'a' *} +{=first([1, 2, 3, 4])} {* виведе 1 *} +{=first('abcd')} {* виведе 'a' *} ``` -Див. також [last |#last], [filter first |filters#first]. +Див. також [#last], [фільтр first |filters#first]. -last(string|array $value): mixed .[method]{data-version:2.10.2} ---------------------------------------------------------------- +group(iterable $data, string|int|\Closure $by): array .[method]{data-version:3.0.16} +------------------------------------------------------------------------------------ +Функція групує дані за різними критеріями. + +У цьому прикладі рядки в таблиці групуються за стовпцем `categoryId`. Виходом є масив масивів, де ключем є значення у стовпці `categoryId`. [Прочитайте детальний посібник |cookbook/grouping]. + +```latte +{foreach group($items, categoryId) as $categoryId => $categoryItems} +
                                                                                                                                      + {foreach $categoryItems as $item} +
                                                                                                                                    • {$item->name}
                                                                                                                                    • + {/foreach} +
                                                                                                                                    +{/foreach} +``` + +Див. також фільтр [group |filters#group]. + + +hasBlock(string $name): bool .[method]{data-version:3.0.10} +----------------------------------------------------------- +Перевіряє, чи існує блок із зазначеною назвою: + +```latte +{if hasBlock(header)} ... {/if} +``` + +Див. також [перевірка існування блоків |template-inheritance#Перевірка існування блоків]. + + +last(string|array $value): mixed .[method] +------------------------------------------ Повертає останній елемент масиву або символ рядка: ```latte -{=last([1, 2, 3, 4])} {* виводиться 4 *} -{=last('abcd')} {* виводиться 'd' *} +{=last([1, 2, 3, 4])} {* виведе 4 *} +{=last('abcd')} {* виведе 'd' *} ``` -Див. також [first |#first], [filter last |filters#last]. +Див. також [#first], [фільтр last |filters#last]. -odd(int $value): bool .[method]{data-version:2.10.2} ----------------------------------------------------- -Перевіряє, чи є задане число непарним. +odd(int $value): bool .[method] +------------------------------- +Перевіряє, чи є дане число непарним. ```latte {if odd($num)} ... {/if} ``` -slice(string|array $value, int $start, int $length=null, bool $preserveKeys=false): string|array .[method]{data-version:2.10.2} -------------------------------------------------------------------------------------------------------------------------------- -Витягує фрагмент масиву або рядка. +slice(string|array $value, int $start, ?int $length=null, bool $preserveKeys=false): string|array .[method] +----------------------------------------------------------------------------------------------------------- +Витягує частину масиву або рядка. ```latte -{=slice('hello', 1, 2)} {* виводиться 'el' *} -{=slice(['a', 'b', 'c'], 1, 2)} {* виводить ['b', 'c'] *} +{=slice('hello', 1, 2)} {* виведе 'el' *} +{=slice(['a', 'b', 'c'], 1, 2)} {* виведе ['b', 'c'] *} ``` -Фільтр зрізів працює як функція `array_slice` PHP для масивів і `mb_substr` для рядків із поверненням до `iconv_substr` у режимі UTF-8. +Функція працює як функція PHP `array_slice` для масивів або `mb_substr` для рядків з fallback на функцію `iconv_substr` у режимі UTF‑8. -Якщо start невід'ємний, то послідовність почнеться з цього початку в змінній. Якщо start від'ємний, то послідовність почнеться на такій-то відстані від кінця змінної. +Якщо `start` додатний, послідовність почнеться зі зміщенням на цю кількість від початку масиву/рядка. Якщо від'ємний, послідовність почнеться зі зміщенням на стільки від кінця. -Якщо задано довжину і вона позитивна, то послідовність міститиме до цієї кількості елементів. Якщо змінна коротша за довжину, то будуть присутні тільки доступні елементи змінної. Якщо довжина задана і від'ємна, то послідовність зупиниться на стільки елементів від кінця змінної. Якщо довжина не вказана, то послідовність міститиме всі елементи від зміщення до кінця змінної. +Якщо вказаний параметр `length` і він додатний, послідовність міститиме стільки елементів. Якщо до цієї функції передано від'ємний параметр `length`, послідовність міститиме всі елементи вихідного масиву, починаючи з позиції `start` і закінчуючи на позиції, меншій на `length` елементів від кінця масиву. Якщо цей параметр не вказано, послідовність міститиме всі елементи вихідного масиву, починаючи з позиції `start`. -Filter за замовчуванням упорядковує і скидає ключі цілочисельного масиву. Цю поведінку можна змінити, встановивши preserveKeys в true. Рядкові ключі завжди зберігаються, незалежно від цього параметра. +За замовчуванням функція змінює порядок і скидає цілочисельні ключі масиву. Цю поведінку можна змінити, встановивши `preserveKeys` на `true`. Рядкові ключі завжди зберігаються, незалежно від цього параметра. diff --git a/latte/uk/guide.texy b/latte/uk/guide.texy index afd76d12c6..e3e340d72e 100644 --- a/latte/uk/guide.texy +++ b/latte/uk/guide.texy @@ -1,46 +1,45 @@ -Початок роботи з Latte -********************** +Починаємо з Latte +*****************
                                                                                                                                    -Шаблони покращують організацію коду, відокремлюють логіку програми від презентації та підвищують безпеку. Вони пропонують набагато кращі функції та виразні можливості для створення HTML, ніж сам PHP. +Шаблони покращують організацію коду, відокремлюють логіку програми від представлення та підвищують безпеку. Вони пропонують набагато кращі функції та виразні засоби для генерації HTML, ніж сам PHP. -Latte - найбезпечніша система шаблонів для PHP. Вам сподобається її інтуїтивно зрозумілий синтаксис. Широкий спектр корисних функцій значно спростить вашу роботу. -Вона забезпечує першокласний захист від [критичних вразливостей |safety-first] і дозволяє зосередитися на створенні високоякісних додатків, не турбуючись про їхню безпеку. +Latte є найбезпечнішою системою шаблонів для PHP. Ви полюбите її інтуїтивно зрозумілий синтаксис. Широкий спектр корисних функцій значно полегшить вашу роботу. Вона забезпечує найвищий рівень захисту від [критичних уразливостей |safety-first] і дозволить вам зосередитися на створенні якісних програм, не турбуючись про їхню безпеку. -Як писати шаблони за допомогою Latte? .[#toc-how-to-write-templates-using-latte] --------------------------------------------------------------------------------- +Як писати шаблони за допомогою Latte? +------------------------------------- -Latte добре продуманий і простий у вивченні для тих, хто знайомий з PHP, оскільки вони можуть швидко освоїти його основні теги. +Latte розумно розроблений, і його легко вивчать ті, хто знає PHP і засвоїть основні теги. -- Спочатку ознайомтеся з [синтаксисом Lat |syntax] te і [спробуйте все це онлайн |https://fiddle.nette.org/latte/] -- Ознайомтеся з базовим набором [тегів |tags] і [фільтрів |filters] -- Пишіть шаблони в [редакторі з підтримкою Latte |recipes#Editors and IDE] +- Спочатку ознайомтеся з [синтаксисом Latte |syntax] та [СПРОБУЙТЕ ОНЛАЙН |https://fiddle.nette.org/latte/#9cc0cf6d89#9cc0cf6d89] +- Перегляньте базовий набір [тегів |tags] та [фільтрів |filters] +- Пишіть шаблони в [редакторі з підтримкою Latte |recipes#Редактори та IDE] -Як використовувати Latte в PHP? .[#toc-how-to-use-latte-in-php] ---------------------------------------------------------------- +Як використовувати Latte в PHP? +------------------------------- -Впровадження Latte у ваш новий додаток - справа кількох хвилин: +Впровадити Latte у вашу нову програму - це питання кількох хвилин: -- Спочатку [встановіть і запустіть Latte |develop#Installation] -- Побалуйте себе [інструментом налагодження Tracy |develop#Debugging-and-Tracy] -- Розширте Latte за допомогою [користувацької функціональності |extending-latte] +- Спочатку [встановіть і запустіть Latte |develop#Встановлення] +- Насолоджуйтесь [інструментом налагодження Tracy |develop#Налагодження та Tracy] +- Розширте Latte за допомогою [власного функціоналу |extending-latte] -Якщо ви конвертуєте старий проект, написаний на звичайному PHP, в Latte, [інструмент для конвертації PHP-коду в Latte |cookbook/migration-from-php] полегшить вам міграцію. Або ви плануєте перейти на Latte з Twig? У нас є [конвертер шаблонів Twig в Latte |cookbook/migration-from-twig] для вас. +Якщо ви переводите старий проект, написаний на чистому PHP, на Latte, вам полегшить міграцію [інструмент для перетворення PHP коду в Latte |cookbook/migration-from-php]. Або ви збираєтеся перейти на Latte з Twig? У нас є для вас [конвертер шаблонів Twig у Latte |cookbook/migration-from-twig]. -Що ще може Latte? .[#toc-what-else-can-latte-do] ------------------------------------------------- +Що ще вміє Latte? +----------------- -Latte поставляється в повній комплектації, з усім необхідним. +Latte ви отримуєте в повній комплектації, з усім важливим у базовій версії. -- Ваша продуктивність підвищиться завдяки [механізмам успадкування |template-inheritance], які повторно використовують повторювані елементи та структури -- Броньовий бункер [Sandbox |Sandbox] ізолює шаблони від ненадійних джерел, наприклад, тих, що редагуються самими користувачами -- Для подальшого натхнення ось [поради та рекомендації |recipes] +- Вашу продуктивність підвищать [механізми успадкування |template-inheritance], завдяки яким повторювані елементи та структури використовуються повторно +- Броньований бункер [sandbox |sandbox] ізолює шаблони з недовірених джерел, які, наприклад, редагують самі користувачі +- Для подальшого натхнення є [поради та підказки |recipes]
                                                                                                                                    -{{description: Latte - найбезпечніша система шаблонів для PHP. Він запобігає багатьом вразливим місцям у системі безпеки. Ви оціните його інтуїтивно зрозумілий синтаксис і безліч корисних налаштувань.}} +{{description: Latte - це найбезпечніша система шаблонів для PHP. Вона запобігає багатьом уразливостям безпеки. Ви оціните її інтуїтивно зрозумілий синтаксис та безліч корисних функцій.}} diff --git a/latte/uk/loaders.texy b/latte/uk/loaders.texy new file mode 100644 index 0000000000..b54d4395fa --- /dev/null +++ b/latte/uk/loaders.texy @@ -0,0 +1,198 @@ +Завантажувачі +************* + +.[perex] +Завантажувачі — це механізм, який Latte використовує для отримання вихідного коду ваших шаблонів. Найчастіше шаблони зберігаються як файли на диску, але завдяки гнучкій системі завантажувачів ви можете завантажувати їх практично звідки завгодно або навіть динамічно генерувати. + + +Що таке Завантажувач? +===================== + +Коли ви працюєте з шаблонами, зазвичай уявляєте собі файли `.latte`, розташовані в структурі каталогів вашого проекту. Цим займається стандартний [#FileLoader] в Latte. Однак зв'язок між назвою шаблону (наприклад, `'main.latte'` або `'components/card.latte'`) та його фактичним вихідним кодом *не обов'язково* повинен бути прямим відображенням на шлях до файлу. + +Саме тут на допомогу приходять завантажувачі. Завантажувач — це об'єкт, завданням якого є взяти назву шаблону (ідентифікаційний рядок) та надати Latte його вихідний код. Latte повністю покладається на налаштований завантажувач для цього завдання. Це стосується не тільки початкового шаблону, запитаного за допомогою `$latte->render('main.latte')`, але й **кожного шаблону, на який посилаються всередині** за допомогою тегів, таких як `{include ...}`, `{layout ...}`, `{embed ...}` або `{import ...}`. + +Навіщо використовувати власний завантажувач? + +- **Завантаження з альтернативних джерел:** Отримання шаблонів, збережених у базі даних, у кеші (наприклад, Redis або Memcached), у системі керування версіями (наприклад, Git, на основі конкретного коміту) або динамічно згенерованих. +- **Реалізація власних конвенцій іменування:** Ви можете захотіти використовувати коротші псевдоніми для шаблонів або реалізувати специфічну логіку шляхів пошуку (наприклад, спочатку шукати в каталозі теми, потім повертатися до стандартного каталогу). +- **Додавання безпеки або контролю доступу:** Власний завантажувач може перевіряти права користувача перед завантаженням певних шаблонів. +- **Попередня обробка:** Хоча це загалом не рекомендується ([компіляційні проходи |compiler-passes] краще), завантажувач *теоретично міг би* попередньо обробити вміст шаблону, перш ніж передати його до Latte. + +Завантажувач для екземпляра `Latte\Engine` встановлюється за допомогою методу `setLoader()`: + +```php +$latte = new Latte\Engine; + +// Використання стандартного FileLoader для файлів у '/path/to/templates' +$loader = new Latte\Loaders\FileLoader('/path/to/templates'); +$latte->setLoader($loader); +``` + +Завантажувач повинен реалізовувати інтерфейс `Latte\Loader`. + + +Вбудовані Завантажувачі +======================= + +Latte пропонує кілька стандартних завантажувачів: + + +FileLoader +---------- + +Це **стандартний завантажувач**, що використовується класом `Latte\Engine`, якщо не вказано іншого. Він завантажує шаблони безпосередньо з файлової системи. + +За бажанням ви можете встановити кореневий каталог для обмеження доступу: + +```php +use Latte\Loaders\FileLoader; + +// Наступне дозволить завантаження шаблонів лише з каталогу /var/www/html/templates +$loader = new FileLoader('/var/www/html/templates'); +$latte->setLoader($loader); + +// $latte->render('../../../etc/passwd'); // Це викинуло б виняток + +// Візуалізація шаблону, розташованого на /var/www/html/templates/pages/contact.latte +$latte->render('pages/contact.latte'); +``` + +При використанні тегів, таких як `{include}` або `{layout}`, він розв'язує назви шаблонів відносно поточного шаблону, якщо не вказано абсолютний шлях. + + +StringLoader +------------ + +Цей завантажувач отримує вміст шаблону з асоціативного масиву, де ключі — це назви шаблонів (ідентифікатори), а значення — рядки вихідного коду шаблону. Він особливо корисний для тестування або невеликих програм, де шаблони можуть зберігатися безпосередньо в PHP-коді. + +```php +use Latte\Loaders\StringLoader; + +$loader = new StringLoader([ + 'main.latte' => 'Hello {$name}, include is below:{include helper.latte}', + 'helper.latte' => '{var $x = 10}Included content: {$x}', + // Додайте інші шаблони за потребою +]); + +$latte->setLoader($loader); + +$latte->render('main.latte', ['name' => 'World']); +// Вивід: Hello World, include is below:Included content: 10 +``` + +Якщо вам потрібно візуалізувати лише один шаблон безпосередньо з рядка без необхідності включення або успадкування, що посилається на інші іменовані рядкові шаблони, ви можете передати рядок безпосередньо методу `render()` або `renderToString()` при використанні `StringLoader` без масиву: + +```php +$loader = new StringLoader; +$latte->setLoader($loader); + +$templateString = 'Hello {$name}!'; +$output = $latte->renderToString($templateString, ['name' => 'Alice']); +// $output містить 'Hello Alice!' +``` + + +Створення власного Завантажувача +================================ + +Для створення власного завантажувача (наприклад, для завантаження шаблонів з бази даних, кешу, системи керування версіями або іншого джерела) вам потрібно створити клас, який реалізує інтерфейс [api:Latte\Loader]. + +Розглянемо, що повинен робити кожен метод. + + +getContent(string $name): string .[method] +------------------------------------------ +Це основний метод завантажувача. Його завдання — отримати та повернути повний вихідний код шаблону, ідентифікованого за допомогою `$name` (як передано методу `$latte->render()` або повернуто методом [#getReferredName()]). + +Якщо шаблон неможливо знайти або отримати до нього доступ, цей метод **повинен викинути виняток `Latte\RuntimeException`**. + +```php +public function getContent(string $name): string +{ + // Приклад: Завантаження з гіпотетичного внутрішнього сховища + $content = $this->storage->read($name); + if ($content === null) { + throw new Latte\RuntimeException("Template '$name' cannot be loaded."); + } + return $content; +} +``` + + +getReferredName(string $name, string $referringName): string .[method] +---------------------------------------------------------------------- +Цей метод відповідає за перетворення назв шаблонів, що використовуються в тегах, таких як `{include}`, `{layout}` тощо. Коли Latte зустрічає, наприклад, `{include 'partial.latte'}` всередині `main.latte`, він викликає цей метод з `$name = 'partial.latte'` та `$referringName = 'main.latte'`. + +Завдання методу — перетворити `$name` на канонічний ідентифікатор (наприклад, абсолютний шлях, унікальний ключ бази даних), який буде використано при виклику інших методів завантажувача, на основі контексту, наданого в `$referringName`. + +```php +public function getReferredName(string $name, string $referringName): string +{ + return ...; +} +``` + + +getUniqueId(string $name): string .[method] +------------------------------------------- +Latte використовує кеш скомпільованих шаблонів для покращення продуктивності. Кожен скомпільований файл шаблону потребує унікальної назви, похідної від ідентифікатора вихідного шаблону. Цей метод надає рядок, який **однозначно ідентифікує** шаблон `$name`. + +Для шаблонів на основі файлів може слугувати абсолютний шлях. Для шаблонів у базі даних поширеною є комбінація префікса та ID бази даних. + +```php +public function getUniqueId(string $name): string +{ + return ...; +} +``` + + +Приклад: Простий Завантажувач з бази даних +------------------------------------------ + +Цей приклад показує базову структуру завантажувача, який завантажує шаблони, збережені в таблиці бази даних під назвою `templates` зі стовпцями `name` (унікальний ідентифікатор), `content` та `updated_at`. + +```php +use Latte; + +class DatabaseLoader implements Latte\Loader +{ + public function __construct( + private \PDO $db, + ) { + } + + public function getContent(string $name): string + { + $stmt = $this->db->prepare('SELECT content FROM templates WHERE name = ?'); + $stmt->execute([$name]); + $content = $stmt->fetchColumn(); + if ($content === false) { + throw new Latte\RuntimeException("Template '$name' not found in database."); + } + return $content; + } + + // Цей простий приклад припускає, що назви шаблонів ('homepage', 'article', тощо) + // є унікальними ID, і шаблони не посилаються один на одного відносно. + public function getReferredName(string $name, string $referringName): string + { + return $name; + } + + public function getUniqueId(string $name): string + { + // Використання префікса та самої назви тут є унікальним і достатнім + return 'db_' . $name; + } +} + +// Використання: +$pdo = new \PDO(/* деталі підключення */); +$loader = new DatabaseLoader($pdo); +$latte->setLoader($loader); +$latte->render('homepage'); // Завантажить шаблон з назвою 'homepage' з БД +``` + +Власні завантажувачі дають вам повний контроль над тим, звідки походять ваші шаблони Latte, що дозволяє інтеграцію з різними системами зберігання та робочими процесами. diff --git a/latte/uk/recipes.texy b/latte/uk/recipes.texy index 29537ce07e..32907f0f6b 100644 --- a/latte/uk/recipes.texy +++ b/latte/uk/recipes.texy @@ -1,45 +1,45 @@ -Поради та рекомендації -********************** +Поради та підказки +****************** -Редактори та IDE .[#toc-editors-and-ide] -======================================== +Редактори та IDE +================ -Пишіть шаблони в редакторі або IDE, в яких є підтримка Latte. Це буде набагато приємніше. +Пишіть шаблони в редакторі або IDE, який має підтримку Latte. Це буде набагато приємніше. -- NetBeans IDE має вбудовану підтримку -- PhpStorm: встановіть [плагін Latte |https://plugins.jetbrains.com/plugin/7457-latte] в `Settings > Plugins > Marketplace` -- VS Code: знайдіть у маркерплейсі плагін "Nette Latte + Neon" -- Sublime Text 3: у Package Control знайдіть і встановіть пакет `Nette` і виберіть Latte in `View > Syntax` -- у старих редакторах використовуйте підсвічування Smarty для файлів .latte +- PhpStorm: встановіть у `Settings > Plugins > Marketplace` [plugin Latte|https://plugins.jetbrains.com/plugin/7457-latte] +- VS Code: встановіть [Nette Latte + Neon|https://marketplace.visualstudio.com/items?itemName=Kasik96.latte], [Nette Latte templates|https://marketplace.visualstudio.com/items?itemName=smuuf.latte-lang] або найновіший [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode] плагін +- NetBeans IDE: нативна підтримка Latte є частиною інсталяції +- Sublime Text 3: у Package Control знайдіть та встановіть пакет `Nette` та виберіть Latte у `View > Syntax` +- у старих редакторах використовуйте для файлів .latte підсвічування Smarty -Плагін для PhpStorm дуже просунутий і може відмінно підказувати PHP код. Для оптимальної роботи використовуйте [типізовані шаблони |type-system]. +Плагін для PhpStorm дуже просунутий і вміє чудово підказувати PHP код. Щоб він працював оптимально, використовуйте [типізовані шаблони |type-system]. [* latte-phpstorm-plugin.webp *] -Підтримку Latte також можна знайти у веб-виділювачі коду [Prism.js |https://prismjs.com/#supported-languages] і редакторі [Ace |https://ace.c9.io]. +Підтримку Latte ви знайдете також у веб-підсвічувачі коду [Prism.js|https://prismjs.com/#supported-languages] та редакторі [Ace|https://ace.c9.io]. -Latte Inside JavaScript або CSS .[#toc-latte-inside-javascript-or-css] -====================================================================== +Latte всередині JavaScript або CSS +================================== -Latte можна дуже зручно використовувати всередині JavaScript або CSS. Але як уникнути того, щоб Latte помилково вважав код JavaScript або стиль CSS тегом Latte? +Latte можна дуже зручно використовувати і всередині JavaScript або CSS. Але як уникнути ситуації, коли Latte помилково вважатиме JavaScript код або CSS стиль за Latte тег? ```latte ``` **Варіант 1** -Уникайте ситуацій, коли буква слідує одразу за `{`, вставляючи між ними пробіл, перенесення рядка або лапки: +Уникайте ситуації, коли літера йде одразу за `{`, наприклад, вставивши перед нею пробіл, перенесення рядка або лапку: ```latte -

                                                                                                                                    + +

                                                                                                                                    ``` -Два способи і два різні види екранування даних. У межах ` ``` -Однак якщо ми хочемо вставити його в HTML-атрибут, нам все одно доведеться екранувати лапки в HTML-сутності: +Однак, якби ми хотіли вставити його в HTML-атрибут, ми повинні ще екранувати лапки на HTML-сутності: ```html
                                                                                                                                    ``` -Однак вкладений контекст не обов'язково має бути лише JS або CSS. Зазвичай це також URL. Параметри в URL екрануються шляхом перетворення спеціальних символів у послідовності, що починаються з `%`. Приклад: +Але вкладеним контекстом не обов'язково має бути лише JS або CSS. Зазвичай ним є також URL. Параметри в URL екрануються так, що символи зі спеціальним значенням перетворюються на послідовності, що починаються з `%`. Приклад: ``` https://example.org/?a=Jazz&b=Rock%27n%27Roll ``` -Коли ми виводимо цей рядок в атрибуті, ми все одно застосовуємо екранування відповідно до цього контексту і замінюємо `&` with `&`: +І коли ми виводимо цей рядок в атрибуті, ще застосовуємо екранування відповідно до цього контексту і замінюємо `&` на `&`: ```html ``` -Якщо ви дочитали до цього місця, вітаю, це було виснажливо. Тепер у вас є гарне уявлення про те, що таке контекст та екранування. І вам не потрібно турбуватися про те, що це складно. Latte робить це за вас автоматично. +Якщо ви дочитали до цього місця, вітаємо, це було виснажливо. Тепер ви маєте добре уявлення про те, що таке контексти та екранування. І не хвилюйтеся, що це складно. Latte це робить за вас автоматично. -Latte проти наївних систем .[#toc-latte-vs-naive-systems] -========================================================= +Latte проти наївних систем +========================== -Ми показали, як правильно виконувати екранування в HTML-документі і наскільки важливо знати контекст, тобто куди ви виводите дані. Іншими словами, як працює контекстно-залежне екранування. -Хоча це необхідна умова для функціонального захисту від XSS, **Latte - єдина система шаблонів для PHP, яка це робить.**. +Ми показали, як правильно екранувати в HTML-документі та наскільки важливим є знання контексту, тобто місця, де ми виводимо дані. Іншими словами, як працює контекстно-залежне екранування. Хоча це необхідна передумова функціонального захисту від XSS, **Latte - єдина система шаблонів для PHP, яка це вміє.** -Як таке можливо, коли всі системи сьогодні стверджують, що у них є автоматичне екранування? -Автоматичне екранування без знання контексту - це повна нісенітниця, яка **створює хибне відчуття безпеки**. +Як це можливо, коли всі системи сьогодні стверджують, що мають автоматичне екранування? Автоматичне екранування без знання контексту - це трохи фігня, яка **створює хибне відчуття безпеки**. -Системи шаблонізації, такі як Twig, Laravel Blade та інші, не бачать HTML-структури в шаблоні. Тому вони не бачать і контексту. Порівняно з Latte, вони сліпі та наївні. Вони працюють тільки зі своєю власною розміткою, все інше для них - потік нерелевантних символів: +Системи шаблонів, такі як Twig, Laravel Blade та інші, не бачать у шаблоні жодної HTML-структури. Отже, вони не бачать і контекстів. На відміну від Latte, вони сліпі та наївні. Вони обробляють лише власні теги, все інше для них - несуттєвий потік символів:
                                                                                                                                    -```twig .{file:Twig template as seen by Twig himself} -░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░ -░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░░░ -░░░░░░░░░░░░░░░░░░░{{ text }}░░░░ +```twig .{file:Шаблон Twig, як його бачить сам Twig} +░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░ +░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░ ``` -```twig .{file:Twig template as the designer sees it} -- in text: {{ text }} -- in tag: -- in attribute: -- in unquoted attribute: -- in attribute containing URL: -- in attribute containing JavaScript: -- in attribute containing CSS: -- in JavaScriptu: -- in CSS: -- in comment: +```twig .{file:Шаблон Twig, як його бачить дизайнер} +- у тексті: {{ foo }} +- у тезі: +- в атрибуті: +- в атрибуті без лапок: +- в атрибуті, що містить URL: +- в атрибуті, що містить JavaScript: +- в атрибуті, що містить CSS: +- у JavaScript: +- у CSS: +- у коментарі: ```
                                                                                                                                    -Наївні системи просто механічно перетворюють символи `< > & ' "` на HTML-сутності, що є правильним способом екранування в більшості випадків, але далеко не завжди. Таким чином, вони не можуть виявити або запобігти різним дірам у безпеці, як ми покажемо нижче. +Наївні системи лише механічно перетворюють символи `< > & ' "` на HTML-сутності, що хоч і є дійсним способом екранування в більшості випадків використання, але далеко не завжди. Вони не можуть виявити або запобігти виникненню різних дір у безпеці, як ми покажемо далі. -Latte бачить шаблон так само, як і ви. Він розуміє HTML, XML, розпізнає теги, атрибути тощо. І завдяки цьому він розрізняє контексти та обробляє дані відповідним чином. Тому він пропонує дійсно ефективний захист від критичної вразливості Cross-site Scripting. +Latte бачить шаблон так само, як і ви. Розуміє HTML, XML, розпізнає теги, атрибути тощо. І завдяки цьому розрізняє окремі контексти та відповідно до них обробляє дані. Таким чином, пропонує дійсно ефективний захист від критичної вразливості Cross-site Scripting. + +
                                                                                                                                    + +```latte .{file:Шаблон Latte, як його бачить Latte} +░░░░░░░░░░░{$foo} +░░░░░░░░░░ +░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░ +░░░░░░░░░ +░░░░░░░░░░░░░░░ +``` + +```latte .{file:Шаблон Latte, як його бачить дизайнер} +- у тексті: {$foo} +- у тезі: +- в атрибуті: +- в атрибуті без лапок: +- в атрибуті, що містить URL: +- в атрибуті, що містить JavaScript: +- в атрибуті, що містить CSS: +- у JavaScript: +- у CSS: +- у коментарі: +``` + +
                                                                                                                                    -Жива демонстрація .[#toc-live-demonstration] -============================================ +Живий приклад +============= -Зліва ви бачите шаблон у Latte, праворуч - згенерований HTML-код. Змінна `$text` виводиться кілька разів, щоразу в дещо іншому контексті. І тому екранується трохи по-різному. Ви можете самостійно відредагувати код шаблону, наприклад, змінити вміст змінної тощо. Спробуйте: +Зліва ви бачите шаблон у Latte, справа - згенерований HTML-код. Кілька разів тут виводиться змінна `$text`, і щоразу в трохи іншому контексті. А отже, і трохи по-іншому екранована. Код шаблону ви можете редагувати самі, наприклад, змінити вміст змінної тощо. Спробуйте:
                                                                                                                                    ``` .{file:template.latte; min-height: 14em}[fiddle-source] -{* TRY TO EDIT THIS TEMPLATE *} +{* СПРОБУЙТЕ РЕДАГУВАТИ ЦЕЙ ШАБЛОН *} {var $text = "Rock'n'Roll"} - {$text} - @@ -270,63 +286,61 @@ Latte бачить шаблон так само, як і ви. Він розум
                                                                                                                                    -Хіба це не чудово! Latte робить контекстно-залежне екранування автоматично, так що програміст: +Хіба це не чудово! Latte робить контекстно-залежне екранування автоматично, тому програміст: -- не потрібно думати або знати, як екранувати дані +- не повинен думати або знати, як де екранувати - не може помилитися -- не може забути про це +- не може забути про екранування -Це навіть не всі контексти, які Latte розрізняє під час виведення і для яких налаштовує обробку даних. Зараз ми розглянемо цікавіші випадки. +Це навіть не всі контексти, які Latte розрізняє при виведенні та для яких пристосовує обробку даних. Інші цікаві випадки ми розглянемо зараз. -Як зламати наївні системи .[#toc-how-to-hack-naive-systems] -=========================================================== +Як зламати наївні системи +========================= -На кількох практичних прикладах ми покажемо, наскільки важливе контекстне розмежування і чому наївні системи шаблонів не забезпечують достатнього захисту від XSS, на відміну від Latte. -Ми використовуватимемо Twig як представника наївної системи в прикладах, але те ж саме стосується й інших систем. +На кількох практичних прикладах ми покажемо, наскільки важливим є розрізнення контекстів і чому наївні системи шаблонів не забезпечують достатнього захисту від XSS, на відміну від Latte. Як представника наївної системи ми використаємо в прикладах Twig, але те саме стосується й інших систем. -Уразливість атрибутів .[#toc-attribute-vulnerability] ------------------------------------------------------ +Уразливість через атрибут +------------------------- -Спробуємо впровадити шкідливий код на сторінку за допомогою атрибута HTML, як ми [показали вище |#How-Does-the-Vulnerability-Arise]. Нехай у нас є шаблон у Twig, що відображає зображення: +Спробуємо впровадити в сторінку шкідливий код за допомогою HTML-атрибута, як ми [показували вище |#Як виникає вразливість]. Маємо шаблон у Twig, що відображає зображення: ```twig .{file:Twig} {{ ``` -Зверніть увагу, що навколо значень атрибутів немає лапок. Можливо, кодер забув їх, що трапляється часто-густо. Наприклад, у React код пишеться ось так, без лапок, і кодер, який переходить з однієї мови на іншу, може легко забути про лапки. +Зверніть увагу, що навколо значень атрибутів немає лапок. Кодер міг про них забути, що просто трапляється. Наприклад, у React код пишеться так, без лапок, і кодер, який змінює мови, потім може легко забути про лапки. -Зловмисник вставляє хитромудро сконструйований рядок `foo onload=alert('Hacked!')` як підпис до зображення. Ми вже знаємо, що Twig не може визначити, чи виводиться змінна в потоці HTML-тексту, усередині атрибута, усередині HTML-коментаря тощо; коротше кажучи, він не розрізняє контексти. І він просто механічно перетворює символи `< > & ' "` на HTML-сутності. -Таким чином, результуючий код матиме такий вигляд: +Зловмисник як опис зображення вставляє хитро складений рядок `foo onload=alert('Hacked!')`. Ми вже знаємо, що Twig не може розпізнати, чи виводиться змінна в потоці HTML-тексту, всередині атрибута, HTML-коментаря тощо, коротше кажучи, не розрізняє контексти. І лише механічно перетворює символи `< > & ' "` на HTML-сутності. Отже, кінцевий код виглядатиме так: ```html foo ``` -**Створено пролом у системі безпеки!** +**І виникла дірка в безпеці!** -Підроблений атрибут `onload` став частиною сторінки, і браузер виконує його відразу після завантаження зображення. +Частиною сторінки став підроблений атрибут `onload`, і браузер одразу після завантаження зображення його запустить. -Тепер подивимося, як Latte обробляє той самий шаблон: +Тепер подивимося, як із тим самим шаблоном впорається Latte: ```latte .{file:Latte} {$imageAlt} ``` -Latte бачить шаблон так само, як і ви. На відміну від Twig, він розуміє HTML і знає, що змінна виводиться як значення атрибута, яке не укладено в лапки. Тому він додає їх. Коли зловмисник вставить таку саму лапку, результуючий код матиме такий вигляд: +Latte бачить шаблон так само, як і ви. На відміну від Twig, він розуміє HTML і знає, що змінна виводиться як значення атрибута, який не взятий у лапки. Тому він їх доповнить. Коли зловмисник вставить той самий опис, кінцевий код виглядатиме так: ```html foo onload=alert('Hacked!') ``` -**Латт успішно запобіг XSS.**. +**Latte успішно запобіг XSS.** -Друк змінної в JavaScript .[#toc-printing-a-variable-in-javascript] -------------------------------------------------------------------- +Виведення змінної в JavaScript +------------------------------ -Завдяки контекстно-залежному екрануванню можна використовувати змінні PHP у JavaScript. +Завдяки контекстно-залежному екрануванню можна абсолютно нативно використовувати PHP-змінні всередині JavaScript. ```latte

                                                                                                                                    {$movie}

                                                                                                                                    @@ -334,7 +348,7 @@ Latte бачить шаблон так само, як і ви. На відмін ``` -Якщо змінна `$movie` зберігає рядок `'Amarcord & 8 1/2'`, то вона генерує такий висновок. Зверніть увагу на різне екранування, що використовується в HTML і JavaScript, а також в атрибуті `onclick`: +Якщо змінна `$movie` міститиме рядок `'Amarcord & 8 1/2'`, буде згенеровано наступний вивід. Зверніть увагу, що всередині HTML використовується інше екранування, ніж всередині JavaScript, і ще інше в атрибуті `onclick`: ```latte

                                                                                                                                    Amarcord & 8 1/2

                                                                                                                                    @@ -343,29 +357,27 @@ Latte бачить шаблон так само, як і ви. На відмін ``` -Перевірка посилань .[#toc-link-checking] ----------------------------------------- +Перевірка посилань +------------------ -Latte автоматично перевіряє, чи містить змінна, яка використовується в атрибутах `src` або `href`, веб-адресу (тобто протокол HTTP), і запобігає запису посилань, які можуть становити загрозу безпеці. +Latte автоматично перевіряє, чи містить змінна, використана в атрибутах `src` або `href`, веб-URL (тобто протокол HTTP) і запобігає виведенню посилань, які можуть становити загрозу безпеці. ```latte {var $link = 'javascript:attack()'} -click here +клікніть ``` -Пише: +Виведе: ```latte -click here +клікніть ``` -Перевірку можна відключити за допомогою фільтра [nocheck |filters#nocheck]. +Перевірку можна вимкнути за допомогою фільтра [nocheck |filters#nocheck]. -Межі Latte .[#toc-limits-of-latte] -================================== +Обмеження Latte +=============== -Latte не є повним XSS-захистом для всієї програми. Ми були б незадоволені, якби ви перестали думати про безпеку під час використання Latte. -Мета Latte - гарантувати, що зловмисник не зможе змінити структуру сторінки, підробити елементи або атрибути HTML. Але він не перевіряє коректність змісту даних, що виводяться. Або коректність поведінки JavaScript. -Це виходить за рамки системи шаблонів. Перевірка коректності даних, особливо введених користувачем і, отже, таких, що не викликають довіри, є важливим завданням для програміста. +Latte не є повністю комплексним захистом від XSS для всієї програми. Ми б не хотіли, щоб ви, використовуючи Latte, перестали думати про безпеку. Мета Latte - забезпечити, щоб зловмисник не міг змінити структуру сторінки, підробити HTML-елементи або атрибути. Але він не контролює правильність вмісту виведених даних. Або правильність поведінки JavaScript. Це вже виходить за межі компетенції системи шаблонів. Перевірка правильності даних, особливо тих, що введені користувачем і тому є недовіреними, є важливим завданням програміста. diff --git a/latte/uk/sandbox.texy b/latte/uk/sandbox.texy index b70703ce0d..367c0b1a60 100644 --- a/latte/uk/sandbox.texy +++ b/latte/uk/sandbox.texy @@ -1,12 +1,10 @@ -Пісочниця -********* +Sandbox +******* -.[perex]{data-version:2.8} -Пісочниця забезпечує рівень безпеки, який дає вам контроль над тим, які теги, функції PHP, методи тощо можна використовувати в шаблонах. Завдяки режиму пісочниці ви можете безпечно співпрацювати з клієнтом або зовнішнім розробником над створенням шаблону, не турбуючись про компрометацію програми або небажані операції. +.[perex] +Sandbox надає шар безпеки, який дає вам контроль над тим, які теги, функції PHP, методи тощо можуть бути використані в шаблонах. Завдяки режиму sandbox ви можете безпечно співпрацювати з клієнтом або зовнішнім кодером над створенням шаблонів, не турбуючись про порушення роботи програми або небажані операції. -Як це працює? Ми просто визначаємо, що ми хочемо дозволити в шаблоні. На початку все заборонено, і ми поступово надаємо дозволи: - -Наступний код дозволяє шаблону використовувати теги `{block}`, `{if}`, `{else}` і `{=}` (останній - це тег для [друку змінної або виразу |tags#Printing]) і всі фільтри: +Як це працює? Ми просто визначаємо, що все дозволено шаблону. Причому за замовчуванням все заборонено, і ми поступово дозволяємо. Наступним кодом ми дозволимо автору шаблону використовувати теги `{block}`, `{if}`, `{else}` та `{=}`, що є тегом для [виведення змінної або виразу |tags#Виведення], та всі фільтри: ```php $policy = new Latte\Sandbox\SecurityPolicy; @@ -16,7 +14,7 @@ $policy->allowFilters($policy::All); $latte->setPolicy($policy); ``` -Ми також можемо дозволити доступ до глобальних функцій, методів або властивостей об'єктів: +Далі ми можемо дозволити окремі функції, методи або властивості об'єктів: ```php $policy->allowFunctions(['trim', 'strlen']); @@ -24,30 +22,35 @@ $policy->allowMethods(Nette\Security\User::class, ['isLoggedIn', 'isAllowed']); $policy->allowProperties(Nette\Database\Row::class, $policy::All); ``` -Хіба це не дивно? Ви можете контролювати все на дуже низькому рівні. Якщо шаблон намагається викликати несанкціоновану функцію або отримати доступ до несанкціонованого методу чи властивості, він викидає виняток `Latte\SecurityViolationException`. +Хіба це не чудово? Ви можете контролювати абсолютно все на дуже низькому рівні. Якщо шаблон спробує викликати недозволену функцію або отримати доступ до недозволеного методу чи властивості, це призведе до винятку `Latte\SecurityViolationException`. -Створення політик з нуля, коли все заборонено, може бути незручним, тому ви можете почати з безпечного фундаменту: +Створювати політику з нуля, коли все заборонено, може бути незручно, тому ви можете почати з безпечної основи: ```php $policy = Latte\Sandbox\SecurityPolicy::createSafePolicy(); ``` -Це означає, що всі стандартні теги дозволені, крім `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget`. -Також дозволені всі стандартні фільтри, окрім `datastream`, `noescape` і `nocheck`. Нарешті, доступ до методів і властивостей об'єкта `$iterator` також дозволено. +Безпечна основа означає, що дозволені всі стандартні теги, крім `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget`. Дозволені стандартні фільтри, крім `datastream`, `noescape` та `nocheck`. І нарешті, дозволений доступ до методів та властивостей об'єкта `$iterator`. -Ці правила застосовуються до шаблону, який ми вставляємо з новим [`{sandbox}` |tags#Including-Templates] тегом. Це щось на кшталт `{include}`, але в ньому ввімкнено режим пісочниці, а також не передаються зовнішні змінні: +Правила застосовуються до шаблону, який ми вставляємо тегом [`{sandbox}` |tags#Включення шаблону]. Це щось на зразок `{include}`, але він вмикає безпечний режим і також не передає жодних змінних: ```latte {sandbox 'untrusted.latte'} ``` -Таким чином, макет і окремі сторінки можуть використовувати всі теги і змінні, як і раніше, обмеження накладатимуться тільки на шаблон `untrusted.latte`. +Таким чином, layout та окремі сторінки можуть безперешкодно використовувати всі теги та змінні, лише до шаблону `untrusted.latte` будуть застосовані обмеження. -Деякі порушення, як-от використання забороненого тега або фільтра, виявляються під час компіляції. Інші, такі як виклик недозволених методів об'єкта, - під час виконання. -Шаблон також може містити будь-які інші помилки. Щоб запобігти викиду винятку з шаблону, що знаходиться в пісочниці, який порушує весь рендеринг, ви можете визначити [власний обробник винятків |develop#Exception-Handler], який, наприклад, просто реєструє його. +Деякі порушення, як-от використання забороненого тегу або фільтра, виявляються під час компіляції. Інші, наприклад, виклик недозволених методів об'єкта, лише під час виконання. Шаблон також може містити будь-які інші помилки. Щоб із sandbox-шаблону не міг вискочити виняток, який порушить весь рендеринг, можна визначити власний [обробник винятків |develop#Обробник винятків], який, наприклад, його залогує. -Якщо ми хочемо ввімкнути режим пісочниці безпосередньо для всіх шаблонів, то це просто: +Якщо ми хочемо увімкнути режим sandbox безпосередньо для всіх шаблонів, це легко зробити: ```php $latte->setSandboxMode(); ``` + +Щоб бути впевненими, що користувач не вставить у сторінку PHP-код, який хоч і синтаксично правильний, але заборонений і спричинить PHP Compile Error, рекомендуємо [перевіряти шаблони за допомогою PHP linter |develop#Перевірка згенерованого коду]. Цю функціональність ви вмикаєте методом `Engine::enablePhpLint()`. Оскільки для перевірки потрібно викликати бінарний файл PHP, передайте шлях до нього як параметр: + +```php +$latte = new Latte\Engine; +$latte->enablePhpLinter('/path/to/php'); +``` diff --git a/latte/uk/syntax.texy b/latte/uk/syntax.texy index 6b24fb5b96..a662adb3cb 100644 --- a/latte/uk/syntax.texy +++ b/latte/uk/syntax.texy @@ -2,62 +2,60 @@ ********* .[perex] -Синтаксис Latte народився з практичних потреб веб-дизайнерів. Ми шукали найзручніший синтаксис, за допомогою якого можна елегантно писати конструкції, які в інших випадках являють собою справжню проблему. -Водночас усі вирази написані точно так само, як у PHP, тому вам не доведеться вивчати нову мову. Ви просто використовуєте те, що вже знаєте. +Синтаксис Latte виник з практичних потреб веб-дизайнерів. Ми шукали найзручніший синтаксис, за допомогою якого можна елегантно записати навіть конструкції, які зазвичай становлять справжню головоломку. Водночас усі вирази пишуться абсолютно так само, як у PHP, тому вам не потрібно вивчати нову мову. Ви просто використовуєте те, що вже давно вмієте. -Нижче наведено мінімальний шаблон, що ілюструє кілька базових елементів: теги, n:attributes, коментарі та фільтри. +Нижче наведено мінімальний шаблон, який ілюструє кілька основних елементів: теги, n:атрибути, коментарі та фільтри. ```latte {* це коментар *} -
                                                                                                                                      {* n:if is n:atribut *} -{foreach $items as $item} {* тег, що представляє цикл foreach *} -
                                                                                                                                    • {$item|capitalize}
                                                                                                                                    • {* тег, що виводить змінну з фільтром *} -{/foreach} {* кінець циклу *} +
                                                                                                                                        {* n:if - це n:атрибут *} +{foreach $items as $item} {* тег, що представляє цикл foreach *} +
                                                                                                                                      • {$item|capitalize}
                                                                                                                                      • {* тег, що виводить змінну з фільтром *} +{/foreach} {* кінець циклу *}
                                                                                                                                      ``` -Давайте докладніше розглянемо ці важливі елементи і те, як вони можуть допомогти вам створити неймовірний шаблон. +Розглянемо детальніше ці важливі елементи та те, як вони можуть допомогти вам створити чудовий шаблон. -Теги .[#toc-tags] -================= +Теги +==== -Шаблон містить теги, які керують логікою шаблону (наприклад, цикли *foreach*) або вихідними виразами. Для обох випадків використовується один роздільник `{ ... }`, тому вам не потрібно думати про те, який роздільник використовувати в тій чи іншій ситуації, як в інших системах. -Якщо за символом `{` слідує лапка або пробіл, Latte не вважає його початком тега, тому ви можете без проблем використовувати у своїх шаблонах конструкції JavaScript, JSON або правила CSS. +Шаблон містить теги, які керують логікою шаблону (наприклад, цикли *foreach*) або виводять вирази. Для обох використовується єдиний роздільник `{ ... }`, тому вам не потрібно думати, який роздільник використовувати в якій ситуації, як це буває в інших системах. Якщо за символом `{` слідує лапка або пробіл, Latte не вважає це початком тегу, завдяки чому ви можете без проблем використовувати в шаблонах також JavaScript-конструкції, JSON або правила CSS. -Дивіться [огляд усіх тегів |tags]. Крім того, ви можете створювати [власні теги |extending-latte#Tags]. +Перегляньте [огляд усіх тегів |tags]. Крім того, ви можете створювати й [власні теги |custom tags]. -Latte розуміє PHP .[#toc-latte-understands-php] -=============================================== +Latte розуміє PHP +================= -Усередині тегів можна використовувати вирази PHP, які ви добре знаєте: +Всередині тегів ви можете використовувати PHP-вирази, які добре знаєте: - змінні -- рядки (включно з HEREDOC і NOWDOC), масиви, числа тощо. +- рядки (включаючи HEREDOC та NOWDOC), масиви, числа тощо. - [оператори |https://www.php.net/manual/en/language.operators.php] -- виклики функцій і методів (які можуть бути обмежені [пісочницею |sandbox]) -- [відповідність |https://www.php.net/manual/en/control-structures.match.php] +- виклики функцій та методів (які можна обмежити [sandbox |sandbox]) +- [match |https://www.php.net/manual/en/control-structures.match.php] - [анонімні функції |https://www.php.net/manual/en/functions.arrow.php] -- [зворотні виклики |https://www.php.net/manual/en/functions.first_class_callable_syntax.php] +- [callback-функції |https://www.php.net/manual/en/functions.first_class_callable_syntax.php] - багаторядкові коментарі `/* ... */` -- і т.д. +- тощо… -Крім того, Latte додає кілька [приємних розширень |#Syntactic-Sugar] до синтаксису PHP. +Крім того, Latte доповнює синтаксис PHP кількома [приємними розширеннями |#Синтаксичний цукор]. -n:attributes .[#toc-n-attributes] -================================= +n:атрибути +========== -Кожен парний тег, наприклад `{if} … {/if}`, що працює з одним елементом HTML, може бути записаний у нотації [n:attribute |#n:attribute]. Наприклад, `{foreach}` у наведеному вище прикладі можна записати і так: +Усі парні теги, наприклад `{if} … {/if}`, що діють над одним HTML-елементом, можна переписати у вигляді n:атрибутів. Так можна було б записати, наприклад, і `{foreach}` у вступному прикладі: ```latte -
                                                                                                                                        +
                                                                                                                                        • {$item|capitalize}
                                                                                                                                        ``` -Функціональність тоді відповідає елементу HTML, у якому вона записана: +Функціональність тоді стосується HTML-елемента, в який вона поміщена: ```latte {var $items = ['I', '♥', 'Latte']} @@ -65,7 +63,7 @@ n:attributes .[#toc-n-attributes]

                                                                                                                                        {$item}

                                                                                                                                        ``` -Prints: +виведе: ```latte

                                                                                                                                        I

                                                                                                                                        @@ -73,7 +71,7 @@ Prints:

                                                                                                                                        Latte

                                                                                                                                        ``` -Використовуючи префікс `inner-`, ми можемо змінити поведінку так, щоб функціональність застосовувалася тільки до тіла елемента: +За допомогою префікса `inner-` ми можемо змінити поведінку так, щоб вона стосувалася лише внутрішньої частини елемента: ```latte
                                                                                                                                        @@ -82,7 +80,7 @@ Prints:
                                                                                                                                        ``` -Відбитки: +Виведеться: ```latte
                                                                                                                                        @@ -95,178 +93,184 @@ Prints:
                                                                                                                                        ``` -Або за допомогою префікса `tag-` функціональність застосовується тільки до HTML-тегів: +Або за допомогою префікса `tag-` застосуємо функціональність лише до самих HTML-тегів: ```latte -

                                                                                                                                        Title

                                                                                                                                        +

                                                                                                                                        Title

                                                                                                                                        ``` -Залежно від значення змінної `$url` буде виведено таке: +Що виведе залежно від змінної `$url`: ```latte -// when $url is empty +{* коли $url порожній *}

                                                                                                                                        Title

                                                                                                                                        -// when $url equals 'https://nette.org' +{* коли $url містить 'https://nette.org' *}

                                                                                                                                        Title

                                                                                                                                        ``` -Однак n:attributes - це не тільки скорочення для парних тегів, є й деякі чисті n:attributes, наприклад, найкращий друг кодера [n:class |tags#n-class]. +Однак n:атрибути - це не лише скорочення для парних тегів. Існують і чисті n:атрибути, як-от [n:href |application:creating-links#У шаблоні presenter а] або надзвичайно зручний помічник кодера [n:class |tags#n:class]. -Фільтри .[#toc-filters] -======================= +Фільтри +======= -Див. короткий опис [стандартних фільтрів |filters]. +Перегляньте огляд [стандартних фільтрів |filters]. -Latte дозволяє викликати фільтри за допомогою знака труби (пробіл перед фільтром допускається): +Фільтри записуються після вертикальної риски (перед нею може бути пробіл): ```latte

                                                                                                                                        {$heading|upper}

                                                                                                                                        ``` -Фільтри можуть бути з'єднані в ланцюжок, у цьому разі вони застосовуються в порядку зліва направо: +Фільтри можна об'єднувати в ланцюжок, і вони застосовуються в порядку зліва направо: ```latte

                                                                                                                                        {$heading|lower|capitalize}

                                                                                                                                        ``` -Параметри поміщаються після імені фільтра через двокрапку або кому: +Параметри вказуються після назви фільтра, розділені двокрапками або комами: ```latte

                                                                                                                                        {$heading|truncate:20,''}

                                                                                                                                        ``` -Фільтри можуть бути застосовані до виразу: +Фільтри можна застосовувати і до виразу: ```latte {var $name = ($title|upper) . ($subtitle|lower)} ``` -На блоці: +До блоку: ```latte

                                                                                                                                        {block |lower}{$heading}{/block}

                                                                                                                                        ``` -Або безпосередньо на значенні (у поєднанні з [`{=expr}` | https://latte.nette.org/uk/tags#printing] тег): +Або безпосередньо до значення (у комбінації з тегом [`{=expr}` |tags#Виведення]): ```latte

                                                                                                                                        {=' Hello world '|trim}

                                                                                                                                        ``` -Коментарі .[#toc-comments] -========================== +Динамічні HTML теги .{data-version:3.0.9} +========================================= -Коментарі пишуться таким чином і не потрапляють у виведення: +Latte підтримує динамічні HTML-теги, які корисні, коли вам потрібна гнучкість у назвах тегів: ```latte -{* це коментар у Latte *} +Heading ``` -Коментарі PHP працюють усередині тегів: +Наведений вище код може, наприклад, генерувати `

                                                                                                                                        Heading

                                                                                                                                        ` або `

                                                                                                                                        Heading

                                                                                                                                        ` залежно від значення змінної `$level`. Динамічні HTML-теги в Latte завжди мають бути парними. Їхньою альтернативою є [n:tag |tags#n:tag]. + +Оскільки Latte є безпечною системою шаблонів, вона перевіряє, чи є кінцева назва тегу дійсною і не містить жодних небажаних або шкідливих значень. Крім того, вона забезпечує, щоб назва кінцевого тегу завжди була такою ж, як назва відкриваючого тегу. + + +Коментарі +========= + +Коментарі записуються таким чином і не потрапляють у вивід: + +```latte +{* це коментар в Latte *} +``` + +Всередині тегів працюють PHP-коментарі: ```latte {include 'file.info', /* value: 123 */} ``` -Синтаксичний цукор .[#toc-syntactic-sugar] -========================================== +Синтаксичний цукор +================== -Рядки без лапок .[#toc-strings-without-quotation-marks] -------------------------------------------------------- +Рядки без лапок +--------------- -Для простих рядків лапки можна не ставити: +Для простих рядків можна опустити лапки: ```latte -as in PHP: {var $arr = ['hello', 'btn--default', '€']} +як у PHP: {var $arr = ['hello', 'btn--default', '€']} -abbreviated: {var $arr = [hello, btn--default, €]} +скорочено: {var $arr = [hello, btn--default, €]} ``` -Прості рядки - це рядки, які складаються виключно з літер, цифр, підкреслень, дефісів і крапок. Вони не повинні починатися з цифри і не повинні починатися або закінчуватися дефісом. -Він не повинен складатися тільки із великих літер і знаків підкреслення, тому що тоді він вважається константою (наприклад, `PHP_VERSION`). -І він не повинен перетинатися з ключовими словами `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor`. +Прості рядки - це ті, що складаються виключно з літер, цифр, підкреслень, дефісів та крапок. Вони не повинні починатися з цифри і не повинні починатися або закінчуватися дефісом. Вони не повинні складатися лише з великих літер та підкреслень, оскільки тоді вважаються константою (напр. `PHP_VERSION`). І не повинні конфліктувати з ключовими словами: `and`, `array`, `clone`, `default`, `false`, `in`, `instanceof`, `new`, `null`, `or`, `return`, `true`, `xor`. -Короткий тернарний оператор .[#toc-short-ternary-operator] ----------------------------------------------------------- +Константи +--------- -Якщо третє значення тернарного оператора порожнє, його можна опустити: +Оскільки для простих рядків можна опускати лапки, рекомендуємо для розрізнення записувати глобальні константи з похилою рискою на початку: ```latte -as in PHP: {$stock ? 'In stock' : ''} - -abbreviated: {$stock ? 'In stock'} +{if \PROJECT_ID === 1} ... {/if} ``` +Цей запис цілком валідний у самому PHP, похила риска вказує, що константа знаходиться в глобальному просторі імен. + -Сучасна ключова нотація в масиві .[#toc-modern-key-notation-in-the-array] -------------------------------------------------------------------------- +Скорочений тернарний оператор +----------------------------- -Ключі масиву можуть бути записані аналогічно іменованим параметрам при виклику функцій: +Якщо третє значення тернарного оператора порожнє, його можна опустити: ```latte -as in PHP: {var $arr = ['one' => 'item 1', 'two' => 'item 2']} +як у PHP: {$stock ? 'В наявності' : ''} -modern: {var $arr = [one: 'item 1', two: 'item 2']} +скорочено: {$stock ? 'В наявності'} ``` -Фільтри .[#toc-filters] ------------------------ +Сучасний запис ключів у масиві +------------------------------ -Фільтри можна використовувати для будь-якого виразу, просто помістіть все в дужки: +Ключі в масиві можна записувати подібно до іменованих параметрів при виклику функцій: ```latte -{var $content = ($text|truncate: 30|upper)} +як у PHP: {var $arr = ['one' => 'item 1', 'two' => 'item 2']} + +сучасно: {var $arr = [one: 'item 1', two: 'item 2']} ``` -Оператор `in` .[#toc-operator-in] ---------------------------------- +Фільтри +------- -Оператор `in` може бути використаний для заміни функції `in_array()`. Порівняння завжди суворе: +Фільтри можна застосовувати до будь-яких виразів, достатньо взяти ціле в дужки: ```latte -{* like in_array($item, $items, true) *} -{if $item in $items} - ... -{/if} +{var $content = ($text|truncate: 30|upper)} ``` -.{data-version:2.9} -Опціональний ланцюжок із невизначено-безпечним оператором .[#toc-optional-chaining-with-undefined-safe-operator] ----------------------------------------------------------------------------------------------------------------- +Оператор `in` +------------- -Оператор undefined-safe `??->` схожий на оператор nullsafe `?->`, але не викликає помилки, якщо змінна, властивість або індекс взагалі не існує. +Оператором `in` можна замінити функцію `in_array()`. Порівняння завжди строге: ```latte -{$order??->id} +{* аналог in_array($item, $items, true) *} +{if $item in $items} + ... +{/if} ``` -Це спосіб сказати, що коли `$order` визначена і не є null, `$order->id` буде обчислено, але коли `$order` є null або не існує, зупиніть наші дії і просто поверніть null. - -```latte -{$user??->address??->street} -// roughly means isset($user) && isset($user->address) ? $user->address->street : null -``` +Історичне вікно +--------------- -Вікно в історію .[#toc-a-window-into-history] ---------------------------------------------- +Latte протягом своєї історії впровадило цілу низку синтаксичних цукрів, які через кілька років з'явилися в самому PHP. Наприклад, у Latte можна було писати масиви як `[1, 2, 3]` замість `array(1, 2, 3)` або використовувати nullsafe оператор `$obj?->foo` задовго до того, як це стало можливим у самому PHP. Latte також запровадило оператор для розпакування масиву `(expand) $arr`, який є еквівалентом сьогоднішнього оператора `...$arr` з PHP. -За свою історію Latte придумав кілька синтаксичних цукерок, які з'явилися в самому PHP кількома роками пізніше. Наприклад, у Latte можна було записувати масиви у вигляді `[1, 2, 3]` замість `array(1, 2, 3)` або використовувати оператор nullsafe `$obj?->foo` задовго до того, як це стало можливим у самому PHP. У Latte також з'явився оператор розширення масиву `(expand) $arr`, який є еквівалентом сьогоднішнього оператора `...$arr` з PHP. +Undefined-safe оператор `??->`, що є аналогом nullsafe оператора `?->`, але не викликає помилки, якщо змінна не існує, виник з історичних причин, і сьогодні ми рекомендуємо використовувати стандартний PHP оператор `?->`. -Обмеження PHP у Latte .[#toc-php-limitations-in-latte] -====================================================== +Обмеження PHP в Latte +===================== -У Latte можна писати тільки PHP-вирази. Тобто, ви не можете оголошувати класи або використовувати [керуючі структури |https://www.php.net/manual/en/language.control-structures.php], такі як `if`, `foreach`, `switch`, `return`, `try`, `throw` та інші, замість яких Latte пропонує свої [теги |tags]. -Ви також не можете використовувати [атрибути |https://www.php.net/manual/en/language.attributes.php], [зворотні знаки |https://www.php.net/manual/en/language.operators.execution.php] або [магічні константи |https://www.php.net/manual/en/language.constants.magic.php], тому що це безглуздо. -Ви навіть не можете використовувати `unset`, `echo`, `include`, `require`, `exit`, `eval`, тому що це не функції, а спеціальні конструкції мови PHP, а отже, не вирази. +У Latte можна записувати лише PHP-вирази. Тобто не можна використовувати оператори, що закінчуються крапкою з комою. Не можна оголошувати класи або використовувати [керуючі структури |https://www.php.net/manual/en/language.control-structures.php], напр. `if`, `foreach`, `switch`, `return`, `try`, `throw` та інші, замість яких Latte пропонує свої [теги |tags]. Також не можна використовувати [атрибути |https://www.php.net/manual/en/language.attributes.php], [зворотні апострофи |https://www.php.net/manual/en/language.operators.execution.php] чи деякі [магічні константи |https://www.php.net/manual/en/language.constants.magic.php]. Не можна використовувати й `unset`, `echo`, `include`, `require`, `exit`, `eval`, оскільки це не функції, а спеціальні мовні конструкції PHP, і вони не є виразами. Коментарі підтримуються лише багаторядкові `/* ... */`. -Однак ви можете обійти ці обмеження, активувавши розширення [RawPhpExtension |develop#RawPhpExtension], яке дає змогу використовувати будь-який PHP-код у тезі `{php ...}` під відповідальність автора шаблону. +Ці обмеження, однак, можна обійти, активувавши розширення [RawPhpExtension |develop#RawPhpExtension], завдяки якому потім можна використовувати в тезі `{php ...}` будь-який PHP-код на відповідальність автора шаблону. diff --git a/latte/uk/tags.texy b/latte/uk/tags.texy index 709bf58088..534a65e011 100644 --- a/latte/uk/tags.texy +++ b/latte/uk/tags.texy @@ -2,167 +2,173 @@ ********** .[perex] -Зведення та опис усіх вбудованих тегів Latte. +Огляд та опис усіх тегів системи шаблонів Latte, які вам стандартно доступні. .[table-latte-tags language-latte] -|## Друк -| `{$var}`, `{...}` або `{=...}` | [друкує екрановану змінну або вираз |#Printing] -| `{$var\|filter}` | [друкує з фільтрами |#Filters] -| `{l}` або `{r}` | друкує символ `{` or `}` +|## Виведення +| `{$var}`, `{...}` або `{=...}` | [виводить екрановану змінну або вираз |#Виведення] +| `{$var\|filter}` | [виводить із використанням фільтрів |#Фільтри] +| `{l}` або `{r}` | виводить символ `{` або `}` .[table-latte-tags language-latte] |## Умови -| `{if}`... `{elseif}`... `{else}`... `{/if}` | [умова if |#if-elseif-else] -| `{ifset}`... `{elseifset}`... `{/ifset}` | [умова ifset |#ifset-elseifset] -| `{ifchanged}`... `{/ifchanged}` | [перевірка наявності змін |#ifchanged] -| `{switch}` `{case}` `{default}` `{/switch}` | [умова switch |#switch-case-default] +| `{if}` … `{elseif}` … `{else}` … `{/if}` | [умова if |#if elseif else] +| `{ifset}` … `{elseifset}` … `{/ifset}` | [умова ifset |#ifset elseifset] +| `{ifchanged}` … `{/ifchanged}` | [перевірка, чи відбулася зміна |#ifchanged] +| `{switch}` `{case}` `{default}` `{/switch}` | [умова switch |#switch case default] +| `n:else` | [альтернативний вміст для умов |#n:else] .[table-latte-tags language-latte] -|## Loops -| `{foreach}`... `{/foreach}` | [foreach |#foreach] -| `{for}`... `{/for}` | [for |#for] -| `{while}`... `{/while}` | [while |#while] -| `{continueIf $cond}` | [перейти до наступної ітерації |#continueIf-skipIf-breakIf] -| `{skipIf $cond}` | [пропустити поточну ітерацію циклу |#continueIf-skipIf-breakIf] -| `{breakIf $cond}` | [перервати цикл |#continueIf-skipIf-breakIf] -| `{exitIf $cond}` | [ранній вихід |#exitIf] -| `{first}`... `{/first}` | [це перша ітерація? |#first-last-sep] -| `{last}`... `{/last}` | [Це остання ітерація? |#first-last-sep] -| `{sep}`... `{/sep}` | [чи буде наступна ітерація? |#first-last-sep] -| `{iterateWhile}`... `{/iterateWhile}` | [структурований foreach |#iterateWhile] -| `$iterator` | [спеціальна змінна всередині циклу foreach |#iterator] +|## Цикли +| `{foreach}` … `{/foreach}` | [#foreach] +| `{for}` … `{/for}` | [#for] +| `{while}` … `{/while}` | [#while] +| `{continueIf $cond}` | [продовжити наступною ітерацією |#continueIf skipIf breakIf] +| `{skipIf $cond}` | [пропустити ітерацію |#continueIf skipIf breakIf] +| `{breakIf $cond}` | [переривання циклу |#continueIf skipIf breakIf] +| `{exitIf $cond}` | [передчасне завершення |#exitIf] +| `{first}` … `{/first}` | [це перший прохід? |#first last sep] +| `{last}` … `{/last}` | [це останній прохід? |#first last sep] +| `{sep}` … `{/sep}` | [чи буде ще наступний прохід? |#first last sep] +| `{iterateWhile}` … `{/iterateWhile}` | [структурований foreach |#iterateWhile] +| `$iterator` | [спеціальна змінна всередині foreach |#iterator] .[table-latte-tags language-latte] |## Включення інших шаблонів -| `{include 'file.latte'}` | [включає шаблон з іншого файлу |#include] -| `{sandbox 'file.latte'}` | [вмикає шаблон у режимі пісочниці |#sandbox] +| `{include 'file.latte'}` | [завантажує шаблон з іншого файлу |#include] +| `{sandbox 'file.latte'}` | [завантажує шаблон у режимі sandbox |#sandbox] .[table-latte-tags language-latte] -|## Блоки, макети, успадкування шаблонів -| `{block}` | [анонімний блок |#block] -| `{block blockname}` | [визначення блоку |template-inheritance#Blocks] -| `{define blockname}` | [визначення блоку для майбутнього використання |template-inheritance#Definitions] -| `{include blockname}` | [друкує блок |template-inheritance#Printing-Blocks] -| `{include blockname from 'file.latte'}` | [друкує блок із файлу |template-inheritance#Printing-Blocks] -| `{import 'file.latte'}` | [завантажує блоки з іншого шаблону |template-inheritance#Horizontal-Reuse] -| `{layout 'file.latte'}` / `{extends}` | [вказівка файлу макета |template-inheritance#Layout-Inheritance] -| `{embed}`... `{/embed}` | [завантажує шаблон або блок і дозволяє перезаписувати блоки |template-inheritance#Unit-Inheritance] -| `{ifset blockname}`... `{/ifset}` | [умова, якщо блок визначено |template-inheritance#Checking-Block-Existence] +|## Блоки, layout'и, успадкування шаблонів +| `{block}` | [анонімний блок |#block] +| `{block blockname}` | [визначає блок |template-inheritance#Блоки] +| `{define blockname}` | [визначає блок для подальшого використання |template-inheritance#Визначення] +| `{include blockname}` | [рендеринг блоку |template-inheritance#Рендеринг блоків] +| `{include blockname from 'file.latte'}` | [рендерить блок із файлу |template-inheritance#Рендеринг блоків] +| `{import 'file.latte'}` | [завантажує блоки з шаблону |template-inheritance#Горизонтальне повторне використання] +| `{layout 'file.latte'}` / `{extends}` | [визначає файл з layout'ом |template-inheritance#Успадкування layout ів] +| `{embed}` … `{/embed}` | [завантажує шаблон чи блок і дозволяє перевизначити блоки |template-inheritance#Успадкування одиниць embed] +| `{ifset blockname}` … `{/ifset}` | [умова, чи існує блок |template-inheritance#Перевірка існування блоків] .[table-latte-tags language-latte] |## Обробка винятків -| `{try}`... `{else}`... `{/try}` | [перехоплення виключень |#try] -| `{rollback}` | [відкидає блок try |#rollback] +| `{try}` … `{else}` … `{/try}` | [перехоплення винятків |#try] +| `{rollback}` | [відкидання блоку try |#rollback] .[table-latte-tags language-latte] |## Змінні -| `{var $foo = value}` | [створення змінних |#var-default] -| `{default $foo = value}` | [значення за замовчуванням, коли змінна не оголошена |#var-default] -| `{parameters}` | [Оголошення змінних, типи значень за замовчуванням |#parameters] -| `{capture}`... `{/capture}` | [Захоплює секцію для змінної |#capture] +| `{var $foo = value}` | [створює змінну |#var default] +| `{default $foo = value}` | [створює змінну, якщо вона не існує |#var default] +| `{parameters}` | [декларує змінні, типи та значення за замовчуванням |#parameters] +| `{capture}` … `{/capture}` | [захоплює блок у змінну |#capture] .[table-latte-tags language-latte] |## Типи -| `{varType}` | [оголошує тип змін ної|type-system#varType] -| `{varPrint}` | [пропонує типи змін них|type-system#varPrint] -| `{templateType}` | [оголошує типи змінних за допомогою класу |type-system#templateType] -| `{templatePrint}` | [генерує клас із властивостями |type-system#templatePrint] +| `{varType}` | [декларує тип змінної |type-system#varType] +| `{varPrint}` | [пропонує типи змінних |type-system#varPrint] +| `{templateType}` | [декларує типи змінних за класом |type-system#templateType] +| `{templatePrint}` | [пропонує клас з типами змінних |type-system#templatePrint] .[table-latte-tags language-latte] -|## Переклад -| `{_string}` | [друкує переклад |#Translation] -| `{translate}`... `{/translate}` | [перекладає вміст |#Translation] +|## Переклади +| `{_...}` | [виводить переклад |#Переклади] +| `{translate}` … `{/translate}` | [перекладає вміст |#Переклади] .[table-latte-tags language-latte] -|### Інші -| `{contentType}` | [перемикає режим екранування і відправляє HTTP-заголовок |#contentType] -| `{debugbreak}` | [встановлює точку зупинки в коді |#debugbreak] -| `{do}` | [оцінює вираз, не виводячи його на друк |#do] -| `{dump}` | [скидає змінні в Tracy Bar |#dump] -| `{spaceless}`... `{/spaceless}` | [видаляє непотрібні пробільні символи |#spaceless] -| `{syntax}` | [перемикає синтаксис під час виконання програми |#Syntax] -| `{trace}` | [показує трасування стека |#trace] +|## Інше +| `{contentType}` | [перемикає екранування та надсилає HTTP заголовок |#contentType] +| `{debugbreak}` | [розміщує в коді точку зупинки |#debugbreak] +| `{do}` | [виконує код, але нічого не виводить |#do] +| `{dump}` | [виводить змінні в Tracy Bar |#dump] +| `{php}` | [виконує будь-який PHP-код |#php] +| `{spaceless}` … `{/spaceless}` | [видаляє зайві пробіли |#spaceless] +| `{syntax}` | [зміна синтаксису під час виконання |#syntax] +| `{trace}` | [відображає стек викликів |#trace] .[table-latte-tags language-latte] -|## Помічники HTML тегів -| `n:class` | [розумний атрибут класу |#n-class] -| `n:attr` | [інтелектуальні атрибути HTML |#n-attr] -| `n:tag` | [динамічне ім'я елемента HTML |#n-tag] -| `n:ifcontent` | [Опустити порожній HTML-тег |#n-ifcontent] +|## Помічники HTML-кодера +| `n:class` | [динамічний запис HTML-атрибута class |#n:class] +| `n:attr` | [динамічний запис будь-яких HTML-атрибутів |#n:attr] +| `n:tag` | [динамічний запис імені HTML-елемента |#n:tag] +| `n:ifcontent` | [пропускає порожній HTML-тег |#n:ifcontent] .[table-latte-tags language-latte] -|## Доступно тільки в Nette Framework -| `n:href` | [посилання в HTML-елементах |application:creating-links#In-the-Presenter-Template] `` -| `{link}` | [друкує посилання |application:creating-links#In-the-Presenter-Template] -| `{plink}` | [друкує посилання на ведучого |application:creating-links#In-the-Presenter-Template] -| `{control}` | [друкує компонент |application:components#Rendering] -| `{snippet}`... `{/snippet}` | [фрагмент шаблону, який може бути відправлений за допомогою AJAX |application:ajax#Tag-snippet] -| `{snippetArea}` | конверт сніпетів -| `{cache}`... `{/cache}` | [кешує розділ шаблону |caching:#Caching-in-Latte] +|## Доступно лише в Nette Framework +| `n:href` | [посилання, що використовується в HTML-елементах `` |application:creating-links#У шаблоні presenter а] +| `{link}` | [виводить посилання |application:creating-links#У шаблоні presenter а] +| `{plink}` | [виводить посилання на presenter |application:creating-links#У шаблоні presenter а] +| `{control}` | [рендерить компонент |application:components#Відображення] +| `{snippet}` … `{/snippet}` | [фрагмент, який можна надіслати AJAX-ом |application:ajax#Сніпети в Latte] +| `{snippetArea}` | [обгортка для фрагментів |application:ajax#Області сніпетів] +| `{cache}` … `{/cache}` | [кешує частину шаблону |caching:#Кешування в Latte] .[table-latte-tags language-latte] -|## Доступно тільки в Nette Forms -| `{form}`... `{/form}` | [друкує елемент форми |forms:rendering#form] -| `{label}`... `{/label}` | [друкує мітку введення форми |forms:rendering#label-input] -| `{input}` | [друкує елемент введення форми |forms:rendering#label-input] -| `{inputError}` | [друкує повідомлення про помилку для елемента введення форми |forms:rendering#inputError] -| `n:name` | [активує елемент введення HTML |forms:rendering#n:name] -| `{formPrint}` | [генерує креслення форми Latte |forms:rendering#formPrint] -| `{formPrintClass}` | [друкує PHP клас для даних форми |forms:in-presenter#Mapping-to-Classes] -| `{formContext}`... `{/formContext}` | [частковий рендеринг форми |forms:rendering#special-cases] - - -Друк .[#toc-printing] -===================== +|## Доступно лише з Nette Forms +| `{form}` … `{/form}` | [рендерить теги форми |forms:rendering#form] +| `{label}` … `{/label}` | [рендерить мітку елемента форми |forms:rendering#label input] +| `{input}` | [рендерить елемент форми |forms:rendering#label input] +| `{inputError}` | [виводить повідомлення про помилку елемента форми |forms:rendering#inputError] +| `n:name` | [оживляє елемент форми |forms:rendering#n:name] +| `{formContainer}` … `{/formContainer}` | [рендеринг контейнера форми |forms:rendering#Спеціальні випадки] + +.[table-latte-tags language-latte] +|## Доступно лише з Nette Assets +| `{asset}` | [відображає ресурс як елемент HTML або URL-адресу |assets:#asset] +| `{preload}` | [генерує підказки щодо попереднього завантаження для оптимізації продуктивності |assets:#preload] +| `n:asset` | [додає атрибути ресурсу до HTML-елементів |assets:#n:asset] + + +Виведення +========= `{$var}` `{...}` `{=...}` ------------------------- -Latte використовує тег `{=...}` для друку будь-якого виразу на виході. Якщо вираз починається зі змінної або виклику функції, то немає необхідності писати знак рівності. Що на практиці означає, що його майже ніколи не потрібно писати: +У Latte використовується тег `{=...}` для виведення будь-якого виразу. Latte дбає про вашу зручність, тому якщо вираз починається зі змінної або виклику функції, не потрібно писати знак рівності. Що на практиці означає, що його майже ніколи не потрібно писати: ```latte -Name: {$name} {$surname}
                                                                                                                                        -Age: {date('Y') - $birth}
                                                                                                                                        +Ім'я: {$name} {$surname}
                                                                                                                                        +Вік: {date('Y') - $birth}
                                                                                                                                        ``` -Ви можете записати у вигляді виразу все, що знаєте з PHP. Вам просто не потрібно вчити нову мову. Наприклад: +Як вираз ви можете записати все, що знаєте з PHP. Вам просто не потрібно вивчати нову мову. Наприклад: ```latte {='0' . ($num ?? $num * 3) . ', ' . PHP_VERSION} ``` -Будь ласка, не шукайте жодного сенсу в попередньому прикладі, але якщо ви його там знайдете, напишіть нам :-) +Будь ласка, не шукайте в попередньому прикладі жодного сенсу, але якби ви його там знайшли, напишіть нам :-) -Ескейпінг-виведення .[#toc-escaping-output] -------------------------------------------- +Екранування виведення +--------------------- -Яке найважливіше завдання системи шаблонів? Уникати дірок у безпеці. І саме це робить Latte, коли ви друкуєте щось на виведення. Він автоматично екранує все: +Яке найважливіше завдання системи шаблонів? Запобігти діркам у безпеці. І саме це робить Latte завжди, коли ви щось виводите. Автоматично це екранує: ```latte -

                                                                                                                                        {='one < two'}

                                                                                                                                        {* prints: '

                                                                                                                                        one < two

                                                                                                                                        ' *} +

                                                                                                                                        {='one < two'}

                                                                                                                                        {* виведе: '

                                                                                                                                        one < two

                                                                                                                                        ' *} ``` -Якщо бути точним, Latte використовує контекстно-залежне екранування, яке є настільки важливою та унікальною функцією, що ми присвятили їй окрему главу [безпека в першу чергу|safety-first#context-aware-escaping]. +Щоб бути точними, Latte використовує контекстно-залежне екранування, що є настільки важливою та унікальною річчю, що ми присвятили цьому [окремий розділ |safety-first#Контекстно-залежне екранування]. -А якщо ви друкуєте HTML-кодований вміст із надійного джерела? Тоді ви можете легко вимкнути екранування: +А що, якщо ви виводите вміст, закодований у HTML з надійного джерела? Тоді можна легко вимкнути екранування: ```latte {$trustedHtmlString|noescape} ``` .[warning] -Неправильне використання фільтра `noescape` може призвести до XSS-вразливості! Ніколи не використовуйте його, якщо ви не **абсолютно впевнені** у тому, що ви робите, і що рядок, який ви друкуєте, отримано з надійного джерела. +Неправильне використання фільтра `noescape` може призвести до виникнення уразливості XSS! Ніколи не використовуйте його, якщо ви не **повністю впевнені** в тому, що робите, і що рядок, який виводиться, походить з надійного джерела. -Друк у JavaScript .[#toc-printing-in-javascript] ------------------------------------------------- +Виведення в JavaScript +---------------------- -Завдяки контекстно-залежному екрануванню, дуже легко друкувати змінні в JavaScript, і Latte буде правильно їх екранувати. +Завдяки контекстно-залежному екрануванню надзвичайно легко виводити змінні всередині JavaScript, а правильне екранування забезпечить Latte. -Змінна не обов'язково повинна бути рядком, підтримується будь-який тип даних, які потім кодуються як JSON: +Змінна не обов'язково має бути рядком, підтримується будь-який тип даних, який потім кодується як JSON: ```latte {var $foo = ['hello', true, 1]} @@ -171,7 +177,7 @@ Age: {date('Y') - $birth}
                                                                                                                                        ``` -Генерує: +Згенерує: ```latte ``` -Це також причина, чому **не укладайте змінну в лапки**: Latte додає їх навколо рядків. А якщо ви хочете помістити строкову змінну в інший рядок, просто конкатеніруйте їх: +Це також причина, чому навколо змінної **не пишуться лапки**: Latte додасть їх для рядків самостійно. А якщо ви захочете вставити рядкову змінну в інший рядок, просто об'єднайте їх: ```latte ``` -Фільтри .[#toc-filters] ------------------------ +Фільтри +------- -Друкований вираз може бути змінено за [допомогою фільтрів |syntax#Filters]. Наприклад, у цьому прикладі рядок перетворюється у верхній регістр і скорочується максимум до 30 символів: +Виведений вираз може бути змінений [фільтром |syntax#Фільтри]. Так, наприклад, рядок переведемо у верхній регістр і скоротимо до максимум 30 символів: ```latte {$string|upper|truncate:30} ``` -Ви також можете застосовувати фільтри до частин виразу таким чином: +Фільтри можна використовувати і для окремих частин виразу таким чином: ```latte {$left . ($middle|upper) . $right} ``` -Умови .[#toc-conditions] -======================== +Умови +===== `{if}` `{elseif}` `{else}` -------------------------- -Умови поводяться так само, як і їхні аналоги в PHP. Ви можете використовувати ті самі вирази, які ви знаєте з PHP, вам не потрібно вивчати нову мову. +Умови поводяться так само, як їхні аналоги в PHP. Ви можете використовувати в них ті самі вирази, які знаєте з PHP, вам не потрібно вивчати нову мову. ```latte {if $product->inStock > Stock::Minimum} - In stock + В наявності {elseif $product->isOnWay()} - On the way + В дорозі {else} - Not available + Недоступно {/if} ``` -Як і будь-який парний тег, пара `{if} ... {/ if}` може бути записана як [n:attribute |syntax#n-attributes], наприклад: +Як і будь-який парний тег, так і пару `{if} ... {/if}` можна записувати у вигляді [n:атрибуту |syntax#n:атрибути], наприклад: ```latte -

                                                                                                                                        In stock {$count} items

                                                                                                                                        +

                                                                                                                                        В наявності {$count} штук

                                                                                                                                        ``` -Чи знаєте ви, що до n:attributes можна додати префікс `tag-`? Тоді умова зачіпатиме тільки HTML-теги, а вміст між ними завжди виводитиметься: +Чи знаєте ви, що до n:атрибутів можна додати префікс `tag-`? Тоді умова стосуватиметься лише виведення HTML-тегів, а вміст між ними виводитиметься завжди: ```latte
                                                                                                                                        Hello -{* prints 'Hello' when $clickable is falsey *} -{* prints 'Hello' when $clickable is truthy *} +{* виведе 'Hello' коли $clickable є false *} +{* виведе 'Hello' коли $clickable є true *} ``` Чудово. +`n:else` .{data-version:3.0.11} +------------------------------- + +Якщо умову `{if} ... {/if}` записати у вигляді [n:атрибуту |syntax#n:атрибути], ви маєте можливість вказати й альтернативну гілку за допомогою `n:else`: + +```latte +В наявності {$count} штук + +недоступно +``` + +Атрибут `n:else` можна використовувати також у парі з [`n:ifset` |#ifset elseifset], [`n:foreach` |#foreach], [`n:try` |#try], [#`n:ifcontent`] та [`n:ifchanged` |#ifchanged]. + + `{/if $cond}` ------------- -Вас може здивувати, що вираз в умові `{if}` також може бути вказано в тезі end. Це корисно в ситуаціях, коли ми ще не знаємо значення умови на момент відкриття тега. Назвемо це відкладеним рішенням. +Можливо, вас здивує, що вираз в умові `{if}` можна вказати також у закриваючому тезі. Це зручно в ситуаціях, коли при відкритті умови ми ще не знаємо її значення. Назвемо це відкладеним рішенням. -Наприклад, ми починаємо виводити таблицю із записами з бази даних, і тільки після завершення звіту розуміємо, що в базі даних не було жодного запису. Тому ми поміщаємо умову в кінцевий тег `{/if}`, і якщо запису немає, то нічого з цього не буде надруковано: +Наприклад, ми починаємо виводити таблицю із записами з бази даних і лише після завершення виведення розуміємо, що в базі даних не було жодного запису. Тоді поставимо умову на це в кінцевому тезі `{/if}`, і якщо жодного запису не буде, нічого з цього не виведеться: ```latte {if} -

                                                                                                                                        Printing rows from the database

                                                                                                                                        +

                                                                                                                                        Виведення рядків з бази даних

                                                                                                                                        {foreach $resultSet as $row} @@ -266,28 +286,28 @@ Age: {date('Y') - $birth}
                                                                                                                                        Зручно, чи не так? -Ви також можете використовувати `{else}` у відкладеній умові, але не `{elseif}`. +У відкладеній умові можна використовувати й `{else}`, але не `{elseif}`. `{ifset}` `{elseifset}` ----------------------- .[note] -Див. також [`{ifset block}` |template-inheritance#Checking-Block-Existence] +Див. також [`{ifset block}` |template-inheritance#Перевірка існування блоків] -Використовуйте умову `{ifset $var}`, щоб визначити, чи існує змінна (або кілька змінних) і чи має вона ненульове значення. Насправді це те саме, що й `if (isset($var))` у PHP. Як і будь-який парний тег, цей може бути записаний у вигляді [n:attribute |syntax#n-attributes], тому покажемо його на прикладі: +За допомогою умови `{ifset $var}` ми перевіряємо, чи існує змінна (або кілька змінних) і має значення, відмінне від *null*. Власне, це те саме, що `if (isset($var))` у PHP. Як і будь-який парний тег, його можна записувати й у вигляді [n:атрибуту |syntax#n:атрибути], тож покажемо це як приклад: ```latte - + ``` -`{ifchanged}` .{data-version:2.9} ---------------------------------- +`{ifchanged}` +------------- -`{ifchanged}` перевіряє, чи змінилося значення змінної з моменту останньої ітерації в циклі (foreach, for або while). +`{ifchanged}` перевіряє, чи змінилося значення змінної з останньої ітерації в циклі (foreach, for або while). -Якщо ми вкажемо в тезі одну або кілька змінних, він перевірить, чи змінилося значення будь-якої з них, і надрукує вміст відповідним чином. Наприклад, у наступному прикладі під час перерахування імен як заголовок друкується перша буква імені щоразу, коли вона змінюється: +Якщо в тезі вказати одну або кілька змінних, він перевірятиме, чи змінилася якась із них, і відповідно виведе вміст. Наприклад, наступний приклад виведе першу літеру імені як заголовок щоразу, коли вона зміниться під час виведення імен: ```latte {foreach ($names|sort) as $name} @@ -297,7 +317,7 @@ Age: {date('Y') - $birth}
                                                                                                                                        {/foreach} ``` -Однак, якщо аргумент не вказано, то буде перевірено сам вміст рендерингу відповідно до його попереднього стану. Це означає, що в попередньому прикладі ми можемо сміливо опустити аргумент у тезі. І, звичайно, ми також можемо використовувати [n:attribute |syntax#n-attributes]: +Однак, якщо не вказати жодного аргументу, перевірятиметься відрендерений вміст порівняно з його попереднім станом. Це означає, що в попередньому прикладі ми можемо спокійно опустити аргумент у тезі. І, звичайно, також можемо використовувати [n:атрибут |syntax#n:атрибути]: ```latte {foreach ($names|sort) as $name} @@ -307,49 +327,49 @@ Age: {date('Y') - $birth}
                                                                                                                                        {/foreach} ``` -Ви також можете включити клаузулу `{else}` всередину `{ifchanged}`. +Всередині `{ifchanged}` можна також вказати клаузулу `{else}`. `{switch}` `{case}` `{default}` ------------------------------- -Порівнює значення з кількома варіантами. Це схоже на структуру `switch`, відому вам із PHP. Однак Latte покращує її: +Порівнює значення з кількома варіантами. Це аналог умовного оператора `switch`, який ви знаєте з PHP. Однак Latte його вдосконалює: -- використовує суворе порівняння (`===`) -- не вимагає `break` +- використовує строге порівняння (`===`) +- не потребує `break` -Таким чином, це точний еквівалент структури `match`, з якою поставляється PHP 8.0. +Це, отже, точний еквівалент структури `match`, яка з'явилася в PHP 8.0. ```latte {switch $transport} {case train} - By train + Поїздом {case plane} - By plane + Літаком {default} - Differently + Інакше {/switch} ``` -.{data-version:2.9} -Пункт `{case}` може містити кілька значень, розділених комами: + +Клаузула `{case}` може містити кілька значень, розділених комами: ```latte {switch $status} -{case $status::New}new item -{case $status::Sold, $status::Unknown}not available +{case $status::New}новий елемент +{case $status::Sold, $status::Unknown}недоступний {/switch} ``` -Цикли .[#toc-loops] -=================== +Цикли +===== -У Latte доступні всі цикли, знайомі вам із PHP: foreach, for і while. +У Latte ви знайдете всі цикли, які знаєте з PHP: foreach, for та while. `{foreach}` ----------- -Ви пишете цикл точно так само, як і в PHP: +Цикл записуємо абсолютно так само, як у PHP: ```latte {foreach $langs as $code => $lang} @@ -357,11 +377,11 @@ Age: {date('Y') - $birth}
                                                                                                                                        {/foreach} ``` -Крім того, у нього є кілька зручних твіків, про які ми зараз поговоримо. +Крім того, він має кілька зручних функцій, про які ми зараз розповімо. -Наприклад, Latte перевіряє, щоб створені змінні випадково не перезаписали однойменні глобальні змінні. Це врятує вас, коли ви припускаєте, що `$lang` - поточна мова сторінки, і не розумієте, що `foreach $langs as $lang` перезаписав цю змінну. +Latte, наприклад, перевіряє, чи створені змінні випадково не перезаписують глобальні змінні з тією ж назвою. Це рятує ситуації, коли ви розраховуєте, що в `$lang` є поточна мова сторінки, і не усвідомлюєте, що `foreach $langs as $lang` вам цю змінну перезаписало. -Цикл foreach також може бути написаний дуже елегантно й економічно за допомогою [n:attribute |syntax#n-attributes]: +Цикл foreach також можна дуже елегантно та економно записати за допомогою [n:атрибуту |syntax#n:атрибути]: ```latte
                                                                                                                                          @@ -369,7 +389,7 @@ Age: {date('Y') - $birth}
                                                                                                                                        ``` -Чи знаєте ви, що до n:attributes можна додавати префікс `inner-`? Тоді в циклі повторюватиметься тільки внутрішня частина елемента: +Чи знаєте ви, що до n:атрибутів можна додати префікс `inner-`? Тоді в циклі повторюватиметься лише вміст елемента: ```latte
                                                                                                                                        @@ -378,7 +398,7 @@ Age: {date('Y') - $birth}
                                                                                                                                        ``` -Таким чином, буде виведено щось на кшталт: +Отже, виведеться щось на зразок: ```latte
                                                                                                                                        @@ -390,17 +410,17 @@ Age: {date('Y') - $birth}
                                                                                                                                        ``` -`{else}` .{data-version:2.9}{toc: foreach-else} ------------------------------------------------ +`{else}` .{toc: foreach-else} +----------------------------- -Цикл `foreach` може приймати необов'язкове речення `{else}`, текст якого виводиться, якщо заданий масив порожній: +Всередині циклу `foreach` можна вказати клаузулу `{else}`, вміст якої відобразиться, якщо цикл порожній: ```latte
                                                                                                                                          {foreach $people as $person}
                                                                                                                                        • {$person->name}
                                                                                                                                        • {else} -
                                                                                                                                        • Sorry, no users in this list
                                                                                                                                        • +
                                                                                                                                        • На жаль, у цьому списку немає користувачів
                                                                                                                                        • {/foreach}
                                                                                                                                        ``` @@ -409,15 +429,15 @@ Age: {date('Y') - $birth}
                                                                                                                                        `$iterator` ----------- -Усередині циклу `foreach` ініціалізується змінна `$iterator`. У ній зберігається важлива інформація про поточний цикл. +Всередині циклу `foreach` Latte створює змінну `$iterator`, за допомогою якої ми можемо дізнаватися корисну інформацію про поточний цикл: -- `$iterator->first` - це перша ітерація? -- `$iterator->last` - це остання ітерація? -- `$iterator->counter` - лічильник ітерацій, починається з 1 -- `$iterator->counter0` - лічильник ітерацій, починається з 0 .{data-version:2.9} -- `$iterator->odd` - ця ітерація непарна? -- `$iterator->even` - ця ітерація парна? -- `$iterator->parent` - ітератор, що оточує поточний ітератор. .{data-version:2.9} +- `$iterator->first` - чи це перший прохід циклу? +- `$iterator->last` - чи це останній прохід? +- `$iterator->counter` - який це прохід, рахуючи від одиниці? +- `$iterator->counter0` - який це прохід, рахуючи від нуля? +- `$iterator->odd` - чи це непарний прохід? +- `$iterator->even` - чи це парний прохід? +- `$iterator->parent` - ітератор, що оточує поточний - `$iterator->nextValue` - наступний елемент у циклі - `$iterator->nextKey` - ключ наступного елемента в циклі @@ -435,20 +455,19 @@ Age: {date('Y') - $birth}
                                                                                                                                        {/foreach} ``` -Лата розумниця і `$iterator->last` працює не тільки для масивів, а й коли цикл працює над загальним ітератором, де кількість елементів заздалегідь не відома. +Latte розумний, і `$iterator->last` працює не лише з масивами, а й коли цикл проходить над загальним ітератором, де заздалегідь невідома кількість елементів. `{first}` `{last}` `{sep}` -------------------------- -Ці теги можна використовувати всередині циклу `{foreach}`. Вміст `{first}` відображається під час першого проходу. -Вміст `{last}` відображається ... можете здогадатися? Так, для останнього проходу. Насправді це ярлики для `{if $iterator->first}` і `{if $iterator->last}`. +Ці теги можна використовувати всередині циклу `{foreach}`. Вміст `{first}` відрендериться, якщо це перший прохід. Вміст `{last}` відрендериться … чи вгадаєте ви? Так, якщо це останній прохід. Це фактично скорочення для `{if $iterator->first}` та `{if $iterator->last}`. -Теги також можуть бути записані як [n:attributes |syntax#n-attributes]: +Теги також можна елегантно використовувати як [n:атрибут |syntax#n:атрибути]: ```latte {foreach $rows as $row} - {first}

                                                                                                                                        List of names

                                                                                                                                        {/first} + {first}

                                                                                                                                        Список імен

                                                                                                                                        {/first}

                                                                                                                                        {$row->name}

                                                                                                                                        @@ -456,7 +475,7 @@ Age: {date('Y') - $birth}
                                                                                                                                        {/foreach} ``` -Вміст `{sep}` виводиться, якщо ітерація не остання, тому він підходить для друку роздільників, наприклад, ком між елементами списку: +Вміст тегу `{sep}` відрендериться, якщо прохід не є останнім, тому він зручний для рендерингу роздільників, наприклад, ком між виведеними елементами: ```latte {foreach $items as $item} {$item} {sep}, {/sep} {/foreach} @@ -465,12 +484,12 @@ Age: {date('Y') - $birth}
                                                                                                                                        Це досить практично, чи не так? -`{iterateWhile}` .{data-version:2.10} -------------------------------------- +`{iterateWhile}` +---------------- -Спрощує групування лінійних даних під час ітерації в циклі foreach, виконуючи ітерацію у вкладеному циклі доти, доки виконується умова. [Читайте інструкції в книзі рецептів |cookbook/iteratewhile]. +Спрощує групування лінійних даних під час ітерації в циклі foreach, виконуючи ітерацію у вкладеному циклі, доки умова виконана. [Прочитайте детальний посібник |cookbook/grouping]. -Він також може елегантно замінити `{first}` і `{last}` у прикладі вище: +Може також елегантно замінити `{first}` та `{last}` у прикладі вище: ```latte {foreach $rows as $row} @@ -487,19 +506,21 @@ Age: {date('Y') - $birth}
                                                                                                                                        {/foreach} ``` +Див. також фільтри [batch |filters#batch] та [group |filters#group]. + `{for}` ------- -Ми пишемо цикл точно так само, як і в PHP: +Цикл записуємо абсолютно так само, як у PHP: ```latte {for $i = 0; $i < 10; $i++} - Item #{$i} + Елемент {$i} {/for} ``` -Тег також може бути записаний як [n:attribute |syntax#n-attributes]: +Тег також можна використовувати як [n:атрибут |syntax#n:атрибути]: ```latte

                                                                                                                                        {$i}

                                                                                                                                        @@ -509,7 +530,7 @@ Age: {date('Y') - $birth}
                                                                                                                                        `{while}` --------- -Знову ж таки, ми пишемо цикл точно так само, як і в PHP: +Цикл знову записуємо абсолютно так само, як у PHP: ```latte {while $row = $result->fetch()} @@ -517,7 +538,7 @@ Age: {date('Y') - $birth}
                                                                                                                                        {/while} ``` -Або як [n:attribute |syntax#n-attributes]: +Або як [n:атрибут |syntax#n:атрибути]: ```latte @@ -525,7 +546,7 @@ Age: {date('Y') - $birth}
                                                                                                                                        ``` -Варіант з умовою наприкінці тега відповідає циклу do-while у PHP: +Можливий також варіант з умовою в кінцевому тезі, що відповідає в PHP циклу do-while: ```latte {while} @@ -537,7 +558,7 @@ Age: {date('Y') - $birth}
                                                                                                                                        `{continueIf}` `{skipIf}` `{breakIf}` ------------------------------------- -Існують спеціальні теги, які можна використовувати для управління будь-яким циклом - `{continueIf ?}` і `{breakIf ?}`, які переходять до наступної ітерації та завершують цикл, відповідно, при виконанні умов: +Для керування будь-яким циклом можна використовувати теги `{continueIf ?}` та `{breakIf ?}`, які переходять до наступного елемента відповідно або завершують цикл при виконанні умови: ```latte {foreach $rows as $row} @@ -547,8 +568,8 @@ Age: {date('Y') - $birth}
                                                                                                                                        {/foreach} ``` -.{data-version:2.9} -Тег `{skipIf}` дуже схожий на `{continueIf}`, але не збільшує лічильник. Таким чином, під час друку `$iterator->counter` і пропуску деяких елементів у нумерації не буде дірок. Також речення {else} буде виведено при пропуску всіх елементів. + +Тег `{skipIf}` дуже схожий на `{continueIf}`, але не збільшує лічильник `$iterator->counter`, тому якщо ми його виводимо і водночас пропускаємо деякі елементи, в нумерації не буде пропусків. А також клаузула `{else}` відрендериться, якщо ми пропустимо всі елементи. ```latte
                                                                                                                                          @@ -556,7 +577,7 @@ Age: {date('Y') - $birth}
                                                                                                                                          {skipIf $person->age < 18}
                                                                                                                                        • {$iterator->counter}. {$person->name}
                                                                                                                                        • {else} -
                                                                                                                                        • Sorry, no adult users in this list
                                                                                                                                        • +
                                                                                                                                        • На жаль, у цьому списку немає дорослих
                                                                                                                                        • {/foreach}
                                                                                                                                        ``` @@ -565,72 +586,68 @@ Age: {date('Y') - $birth}
                                                                                                                                        `{exitIf}` .{data-version:3.0.5} -------------------------------- -Завершує відмальовування шаблону або блоку при виконанні умови. +Завершує рендеринг шаблону або блоку при виконанні умови (так званий "early exit"). ```latte {exitIf !$messages} -

                                                                                                                                        Messages

                                                                                                                                        +

                                                                                                                                        Повідомлення

                                                                                                                                        {$message}
                                                                                                                                        ``` -Увімкнення шаблонів .[#toc-including-templates] -=============================================== +Включення шаблону +================= `{include 'file.latte'}` .{toc: include} ---------------------------------------- .[note] -Див. також [`{include block}` |template-inheritance#Printing-Blocks] +Див. також [`{include block}` |template-inheritance#Рендеринг блоків] -Тег `{include}` завантажує і відображає вказаний шаблон. На нашій улюбленій мові PHP це виглядає так: +Тег `{include}` завантажує та рендерить вказаний шаблон. Якщо говорити мовою нашої улюбленої мови PHP, це щось на зразок: ```php ``` -Увімкнені шаблони не мають доступу до змінних активного контексту, але мають доступ до глобальних змінних. +Включені шаблони не мають доступу до змінних активного контексту, вони мають доступ лише до глобальних змінних. -Ви можете передавати змінні таким чином: +Змінні до включеного шаблону можна передавати таким чином: ```latte -{* починаючи з Latte 2.9 *} {include 'template.latte', foo: 'bar', id: 123} - -{* до Latte 2.9 *} -{include 'template.latte', foo => 'bar', id => 123} ``` -Ім'я шаблону може бути будь-яким виразом PHP: +Назва шаблону може бути будь-яким виразом у PHP: ```latte {include $someVar} {include $ajax ? 'ajax.latte' : 'not-ajax.latte'} ``` -Вставлений вміст може бути змінено за допомогою [фільтрів |syntax#Filters]. У наступному прикладі видаляється весь HTML і коригується регістр: +Включений вміст можна змінити за допомогою [фільтрів |syntax#Фільтри]. Наступний приклад видаляє весь HTML та змінює регістр літер: ```latte {include 'heading.latte' |stripHtml|capitalize} ``` -[Спадкоємство шаблону |template inheritance] **за замовчуванням не бере участі** у цьому. Хоча ви можете додавати теги блоків до шаблонів, що включаються, вони не замінюватимуть відповідні блоки в шаблоні, до якого вони включені. Розглядайте блоки, що включаються, як незалежні й екрановані частини сторінок або модулів. Цю поведінку можна змінити за допомогою модифікатора `with blocks` (починаючи з Latte 2.9.1): +За замовчуванням [успадкування шаблонів |template-inheritance] у цьому випадку ніяк не фігурує. Хоча у включеному шаблоні ми можемо використовувати блоки, не відбувається заміни відповідних блоків у шаблоні, до якого включається. Думайте про включені шаблони як про окремі ізольовані частини сторінок або модулів. Цю поведінку можна змінити за допомогою модифікатора `with blocks`: ```latte {include 'template.latte' with blocks} ``` -Зв'язок між ім'ям файлу, зазначеним у тезі, і файлом на диску залежить від [завантажувача |extending-latte#Loaders]. +Зв'язок між назвою файлу, вказаною в тезі, та файлом на диску є справою [завантажувача |loaders]. -`{sandbox}` .{data-version:2.8} -------------------------------- +`{sandbox}` +----------- -Під час увімкнення шаблону, створеного кінцевим користувачем, слід розглянути можливість його "пісочниці" (докладніша інформація в [документації щодо "пісочниці" |sandbox]): +При включенні шаблону, створеного кінцевим користувачем, вам слід розглянути режим sandbox (більше інформації в [документації sandbox |sandbox]): ```latte {sandbox 'untrusted.latte', level: 3, data: $menu} @@ -641,9 +658,9 @@ Age: {date('Y') - $birth}
                                                                                                                                        ========= .[note] -Див. також [`{block name}` |template-inheritance#Blocks] +Див. також [`{block name}` |template-inheritance#Блоки] -Блоки без назви слугують для можливості застосування [фільтрів |syntax#Filters] до частини шаблону. Наприклад, можна застосувати фільтр [смуги |filters#strip], щоб видалити непотрібні пробіли: +Блоки без імені служать як спосіб застосування [фільтрів |syntax#Фільтри] до частини шаблону. Наприклад, так можна застосувати фільтр [strip |filters#spaceless], який видаляє зайві пробіли: ```latte {block|strip} @@ -654,16 +671,16 @@ Age: {date('Y') - $birth}
                                                                                                                                        ``` -Обробка винятків .[#toc-exception-handling] -=========================================== +Обробка винятків +================ -`{try}` .{data-version:2.9} ---------------------------- +`{try}` +------- -За допомогою цих тегів дуже легко створювати надійні шаблони. +Завдяки цьому тегу надзвичайно легко створювати надійні шаблони. -Якщо під час рендерингу блоку `{try}` виникає виняток, увесь блок відкидається, і рендеринг буде продовжено після нього: +Якщо під час рендерингу блоку `{try}` виникає виняток, весь блок відкидається, і рендеринг продовжується після нього: ```latte {try} @@ -675,7 +692,7 @@ Age: {date('Y') - $birth}
                                                                                                                                        {/try} ``` -Вміст необов'язкового пункту `{else}` виводиться тільки в разі виникнення винятку: +Вміст у необов'язковій клаузулі `{else}` рендериться лише тоді, коли виникає виняток: ```latte {try} @@ -685,11 +702,11 @@ Age: {date('Y') - $birth}
                                                                                                                                        {/foreach} {else} -

                                                                                                                                        Sorry, the tweets could not be loaded.

                                                                                                                                        +

                                                                                                                                        На жаль, не вдалося завантажити твіти.

                                                                                                                                        {/try} ``` -Тег також може бути записаний як [n:attribute |syntax#n-attributes]: +Тег також можна використовувати як [n:атрибут |syntax#n:атрибути]: ```latte
                                                                                                                                          @@ -697,13 +714,13 @@ Age: {date('Y') - $birth}
                                                                                                                                        ``` -Також можна визначити [власний обробник винятків |develop#Exception-Handler] для ведення журналу: +Також можна визначити власний [обробник винятків |develop#Обробник винятків], наприклад, для логування. -`{rollback}` .{data-version:2.9} --------------------------------- +`{rollback}` +------------ -Блок `{try}` також можна зупинити і пропустити вручну за допомогою `{rollback}`. Таким чином, вам не потрібно перевіряти всі вхідні дані заздалегідь, і тільки під час візуалізації ви можете вирішити, чи є сенс рендерити об'єкт. +Блок `{try}` можна зупинити та пропустити також вручну за допомогою `{rollback}`. Завдяки цьому вам не потрібно заздалегідь перевіряти всі вхідні дані, і лише під час рендерингу ви можете вирішити, що об'єкт взагалі не хочете рендерити: ```latte {try} @@ -719,30 +736,30 @@ Age: {date('Y') - $birth}
                                                                                                                                        ``` -Змінні .[#toc-variables] -======================== +Змінні +====== `{var}` `{default}` ------------------- -Ми створимо нові змінні в шаблоні за допомогою тега `{var}`: +Нові змінні ми створюємо в шаблоні тегом `{var}`: ```latte {var $name = 'John Smith'} {var $age = 27} -{* Множинне оголошення *} +{* Множинна декларація *} {var $name = 'John Smith', $age = 27} ``` -Тег `{default}` працює аналогічно, за винятком того, що він створює змінні, тільки якщо вони не існують: +Тег `{default}` працює подібно, але створює змінні лише тоді, коли вони не існують. Якщо змінна вже існує і містить значення `null`, вона не буде перезаписана: ```latte -{default $lang = 'cs'} +{default $lang = 'uk'} ``` -Починаючи з Latte 2.7, ви також можете вказувати [типи змінних |type-system]. Поки що вони носять інформативний характер, і Latte не перевіряє їх. +Ви можете вказувати й [типи змінних |type-system]. Поки що вони інформативні, і Latte їх не перевіряє. ```latte {var string $name = $article->getTitle()} @@ -750,10 +767,10 @@ Age: {date('Y') - $birth}
                                                                                                                                        ``` -`{parameters}` .{data-version:2.9} ----------------------------------- +`{parameters}` +-------------- -Подібно до того, як функція оголошує свої параметри, шаблон може оголосити свої змінні на самому початку: +Так само, як функція оголошує свої параметри, може й шаблон на початку оголосити свої змінні: ```latte {parameters @@ -763,15 +780,15 @@ Age: {date('Y') - $birth}
                                                                                                                                        } ``` -Змінні `$a` і `$b` без значення за замовчуванням автоматично мають значення за замовчуванням `null`. Оголошені типи залишаються інформативними, і Latte не перевіряє їх. +Змінні `$a` та `$b` без вказаного значення за замовчуванням автоматично мають значення за замовчуванням `null`. Оголошені типи поки що інформативні, і Latte їх не перевіряє. -В іншому оголошені змінні не передаються в шаблон. Це відмінність від тега `{default}`. +Інші змінні, крім оголошених, до шаблону не передаються. Цим він відрізняється від тегу `{default}`. `{capture}` ----------- -Використовуючи тег `{capture}`, ви можете захопити виведення у змінну: +Захоплює виведення у змінну: ```latte {capture $var} @@ -780,10 +797,10 @@ Age: {date('Y') - $birth}
                                                                                                                                        {/capture} -

                                                                                                                                        Captured: {$var}

                                                                                                                                        +

                                                                                                                                        Захоплено: {$var}

                                                                                                                                        ``` -Тег також може бути записаний як [n:attribute |syntax#n-attributes]: +Тег можна, подібно до будь-якого парного тегу, записати також як [n:атрибут |syntax#n:атрибути]: ```latte
                                                                                                                                          @@ -791,15 +808,17 @@ Age: {date('Y') - $birth}
                                                                                                                                        ``` +HTML-виведення зберігається в змінну `$var` у вигляді об'єкта `Latte\Runtime\Html`, щоб [уникнути небажаного екранування |develop#Вимкнення автоекранування змінної] при виведенні. -Інші .[#toc-others] -=================== + +Інше +==== `{contentType}` --------------- -Використовуйте тег, щоб вказати, який тип вмісту представляє шаблон. Можливі такі варіанти: +Тегом ви визначаєте, який тип вмісту представляє шаблон. Можливості: - `html` (тип за замовчуванням) - `xml` @@ -808,9 +827,9 @@ Age: {date('Y') - $birth}
                                                                                                                                        - `calendar` (iCal) - `text` -Його використання важливе, оскільки він встановлює [контекстно-залежне екранування |safety-first#Context-Aware-Escaping], і тільки після цього Latte може правильно екранувати. Наприклад, `{contentType xml}` перемикається в режим XML, `{contentType text}` повністю вимикає екранування. +Його використання важливе, оскільки він встановлює [контекстно-залежне екранування |safety-first#Контекстно-залежне екранування] і лише так може екранувати правильно. Наприклад, `{contentType xml}` перемикає в режим XML, `{contentType text}` екранування повністю вимикає. -Якщо параметр є повнофункціональним MIME-типом, наприклад, `application/xml`, він також надсилає браузеру HTTP-заголовок `Content-Type`: +Якщо параметром є повноцінний MIME-тип, наприклад `application/xml`, то він ще й надсилає HTTP-заголовок `Content-Type` до браузера: ```latte {contentType application/xml} @@ -829,46 +848,50 @@ Age: {date('Y') - $birth}
                                                                                                                                        `{debugbreak}` -------------- -Вказує місце, де виконання коду перерветься. Використовується з метою налагодження, щоб програміст міг перевірити середовище виконання і переконатися, що код виконується так, як очікувалося. Підтримується [Xdebug |https://xdebug.org]. Крім того, можна вказати умову, за якої код має перерватися. +Позначає місце, де відбудеться призупинення виконання програми та запуск налагоджувача, щоб програміст міг провести інспекцію середовища виконання та з'ясувати, чи працює програма відповідно до очікувань. Підтримує [Xdebug |https://xdebug.org/]. Можна додати умову, яка визначає, коли програму слід призупинити. ```latte -{debugbreak} {* перериває програму *} +{debugbreak} {* призупиняє програму *} -{debugbreak $counter == 1} {* перериває програму, якщо виконується умова *} +{debugbreak $counter == 1} {* призупиняє програму при виконанні умови *} ``` `{do}` ------ -Виконує код і нічого не друкує. +Виконує PHP-код і нічого не виводить. Так само, як і для всіх інших тегів, під PHP-кодом розуміється один вираз, див. [обмеження PHP |syntax#Обмеження PHP в Latte]. ```latte {do $num++} ``` -У Latte 2.7 і більш ранніх версіях використовувався `{php}`. - `{dump}` -------- -Вивантажує змінну або поточний контекст. +Виводить змінну або поточний контекст. ```latte -{dump $name} {* виводить змінну $name *} +{dump $name} {* Виводить змінну $name *} -{dump} {* виводить усі визначені змінні *} +{dump} {* Виводить усі поточні визначені змінні *} ``` .[caution] -Потрібен пакет [Tracy |tracy:]. +Потребує бібліотеку [Tracy |tracy:]. + + +`{php}` +------- + +Дозволяє виконати будь-який PHP-код. Тег необхідно активувати за допомогою розширення [RawPhpExtension |develop#RawPhpExtension]. `{spaceless}` ------------- -Видаляє непотрібні пробільні символи. Аналогічний фільтру [без пробілів |filters#spaceless]. +Видаляє зайві пробіли з виведення. Працює подібно до фільтра [spaceless |filters#spaceless]. ```latte {spaceless} @@ -878,52 +901,52 @@ Age: {date('Y') - $birth}
                                                                                                                                        {/spaceless} ``` -Вихідні дані: +Згенерує ```latte
                                                                                                                                        • Hello
                                                                                                                                        ``` -Тег також може бути записаний як [n:attribute |syntax#n-attributes]: +Тег також можна записати як [n:атрибут |syntax#n:атрибути]. `{syntax}` ---------- -Теги Latte не обов'язково повинні бути укладені тільки в одинарні фігурні дужки. Ви можете вибрати інший роздільник, навіть під час виконання. Це робиться за допомогою `{syntax…}`, де параметр може бути: +Теги Latte не обов'язково повинні бути обмежені лише простими фігурними дужками. Ми можемо вибрати й інший роздільник, і навіть під час виконання. Для цього служить `{syntax …}`, де як параметр можна вказати: - double: `{{...}}` -- off: повністю відключає теги Latte +- off: повністю вимикає обробку тегів Latte -Використовуючи нотацію n:attribute, ми можемо вимкнути Latte тільки для блоку JavaScript: +З використанням n:атрибутів можна вимкнути Latte, наприклад, лише для одного блоку JavaScript: ```latte ``` -Latte можна дуже зручно використовувати всередині JavaScript, тільки уникайте конструкцій, як у цьому прикладі, де буква одразу йде за `{`, див. [Latte всередині JavaScript або CSS |recipes#Latte-Inside-JavaScript-or-CSS]. +Latte можна дуже зручно використовувати і всередині JavaScript, достатньо уникати конструкцій, як у цьому прикладі, коли літера йде одразу за `{`, див. [Latte всередині JavaScript або CSS |recipes#Latte всередині JavaScript або CSS]. -Якщо ви вимкнете Latte за допомогою `{syntax off}` (тобто тега, а не атрибута n:attribute), то він буде строго ігнорувати всі теги до `{/syntax}`. +Якщо Latte вимкнути за допомогою `{syntax off}` (тобто тегом, а не n:атрибутом), він буде послідовно ігнорувати всі теги до `{/syntax}` -{trace} .{data-version:2.10} ----------------------------- +{trace} +------- -Викидає виняток `Latte\RuntimeException`, стекове трасування якого виконано в дусі шаблонів. Таким чином, замість виклику функцій і методів, воно включає виклик блоків і вставку шаблонів. Якщо ви використовуєте інструмент для наочного відображення кинутих винятків, такий як [Tracy |tracy:], ви чітко бачитимете стек виклику, включно з усіма переданими аргументами. +Викидає виняток `Latte\RuntimeException`, стек викликів якого відповідає духу шаблонів. Тобто замість викликів функцій та методів містить виклики блоків та включення шаблонів. Якщо ви використовуєте інструмент для наочного відображення викинутих винятків, як-от [Tracy |tracy:], вам наочно відобразиться стек викликів, включаючи всі передані аргументи. -Помічники тегів HTML .[#toc-html-tag-helpers] -============================================= +Помічники HTML-кодера +===================== -n:клас .[#toc-n-class] ----------------------- +n:class +------- -Завдяки `n:class` дуже легко згенерувати HTML-атрибут `class` саме так, як вам потрібно. +Завдяки `n:class` дуже легко згенерувати HTML-атрибут `class` точно за задумом. -Приклад: Мені потрібно, щоб активний елемент мав клас `active`: +Приклад: потрібно, щоб активний елемент мав клас `active`: ```latte {foreach $items as $item} @@ -931,7 +954,7 @@ n:клас .[#toc-n-class] {/foreach} ``` -І ще мені потрібно, щоб перший елемент мав класи `first` і `main`: +А далі, щоб перший елемент мав класи `first` та `main`: ```latte {foreach $items as $item} @@ -939,7 +962,7 @@ n:клас .[#toc-n-class] {/foreach} ``` -А всі елементи повинні мати клас `list-item`: +І всі елементи повинні мати клас `list-item`: ```latte {foreach $items as $item} @@ -947,13 +970,13 @@ n:клас .[#toc-n-class] {/foreach} ``` -Дивно просто, чи не так? +Дивовижно просто, чи не так? -n:attr .[#toc-n-attr] ---------------------- +n:attr +------ -Атрибут `n:attr` може генерувати довільні HTML-атрибути з тією ж елегантністю, що й [n:class |#n-class]. +Атрибут `n:attr` вміє з такою ж елегантністю, як [#n:class], генерувати будь-які HTML-атрибути. ```latte {foreach $data as $item} @@ -961,7 +984,7 @@ n:attr .[#toc-n-attr] {/foreach} ``` -Залежно від значень, що повертаються, він відображає, наприклад: +Залежно від повернутих значень виведе, напр.: ```latte @@ -972,26 +995,28 @@ n:attr .[#toc-n-attr] ``` -n:tag .[#toc-n-tag] -------------------- +n:tag +----- -Атрибут `n:tag` може динамічно змінювати ім'я елемента HTML. +Атрибут `n:tag` вміє динамічно змінювати назву HTML-елемента. ```latte

                                                                                                                                        {$title}

                                                                                                                                        ``` -Якщо `$heading === null`, то `

                                                                                                                                        ` тег виводиться без змін. В іншому випадку ім'я елемента змінюється на значення змінної, так що для `$heading === 'h3'` записується: +Якщо `$heading === null`, виведеться без змін тег `

                                                                                                                                        `. Інакше назва елемента зміниться на значення змінної, тож для `$heading === 'h3'` виведеться: ```latte

                                                                                                                                        ...

                                                                                                                                        ``` +Оскільки Latte є безпечною системою шаблонів, вона перевіряє, чи є нова назва тегу дійсною і не містить жодних небажаних або шкідливих значень. -n:ifcontent .[#toc-n-ifcontent] -------------------------------- -Запобігає друку порожнього HTML-елемента, тобто елемента, що не містить нічого, крім пробілів. +n:ifcontent +----------- + +Запобігає виведенню порожнього HTML-елемента, тобто елемента, що не містить нічого, крім пробілів. ```latte
                                                                                                                                        @@ -999,7 +1024,7 @@ n:ifcontent .[#toc-n-ifcontent]
                                                                                                                                        ``` -Залежно від значень змінної `$error` буде виводитися: +Виведе залежно від значення змінної `$error`: ```latte {* $error = '' *} @@ -1013,42 +1038,42 @@ n:ifcontent .[#toc-n-ifcontent] ``` -Переклад .{data-version:3.0}[#toc-translation] -============================================== +Переклади +========= -Щоб теги перекладу працювали, необхідно [налаштувати перекладач |develop#TranslatorExtension]. Ви також можете використовувати [`translate` |filters#translate] фільтр для перекладу. +Щоб теги для перекладу працювали, потрібно [активувати перекладач |develop#TranslatorExtension]. Для перекладу ви також можете використовувати фільтр [`translate` |filters#translate]. `{_...}` -------- -Перекладає значення іншими мовами. +Перекладає значення на інші мови. ```latte -{_'Basket'} +{_'Кошик'} {_$item} ``` -Перекладачеві можуть бути передані й інші параметри: +Перекладачу можна передавати й інші параметри: ```latte -{_'Basket', domain: order} +{_'Кошик', domain: order} ``` `{translate}` ------------- -Překládá části šablony: +Перекладає частини шаблону: ```latte -

                                                                                                                                        {translate}Order{/translate}

                                                                                                                                        +

                                                                                                                                        {translate}Замовлення{/translate}

                                                                                                                                        {translate domain: order}Lorem ipsum ...{/translate} ``` -Тег також може бути записаний як [n:attribute |syntax#n-attributes], щоб перекласти внутрішню частину елемента: +Тег також можна записати як [n:атрибут |syntax#n:атрибути], для перекладу вмісту елемента: ```latte -

                                                                                                                                        Order

                                                                                                                                        +

                                                                                                                                        Замовлення

                                                                                                                                        ``` diff --git a/latte/uk/template-inheritance.texy b/latte/uk/template-inheritance.texy index 54f311aff4..26afd47e08 100644 --- a/latte/uk/template-inheritance.texy +++ b/latte/uk/template-inheritance.texy @@ -1,16 +1,16 @@ -Успадкування шаблонів і можливість повторного використання -********************************************************** +Успадкування та повторне використання шаблонів +********************************************** .[perex] -Механізми повторного використання та успадкування шаблонів підвищують вашу продуктивність, оскільки кожен шаблон містить тільки унікальний вміст, а повторювані елементи та структури використовуються повторно. Ми представляємо три концепції: успадкування [макета |#Layout-Inheritance], [горизонтальне повторне використання |#Horizontal-Reuse] і [успадкування одиниць |#Unit-Inheritance]. +Механізми повторного використання та успадкування шаблонів підвищать вашу продуктивність, оскільки кожен шаблон містить лише свій унікальний контент, а повторювані елементи та структури використовуються повторно. Представляємо три концепції: [#успадкування layout'ів], [#горизонтальне повторне використання] та [успадкування одиниць |#Успадкування одиниць embed]. -Концепція успадкування шаблонів Latte схожа на успадкування класів у PHP. Ви визначаєте **батьківський шаблон**, від якого можуть відштовхуватися інші **спадкові шаблони** і перевизначати частини батьківського шаблону. Це чудово працює, коли елементи мають спільну структуру. Звучить складно? Не хвилюйтеся, це не так. +Концепція успадкування шаблонів Latte схожа на успадкування класів у PHP. Ви визначаєте **батьківський шаблон**, від якого можуть успадковуватися інші **дочірні шаблони** і можуть перезаписувати частини батьківського шаблону. Це чудово працює, коли елементи мають спільну структуру. Звучить складно? Не хвилюйтеся, це дуже просто. -Успадкування макета `{layout}` .{toc: Layout Inheritance} -========================================================= +Успадкування layout'ів `{layout}` .{toc: Успадкування layout'ів} +================================================================ -Давайте розглянемо успадкування шаблонів макета на прикладі. Це батьківський шаблон, який ми назвемо для прикладу `layout.latte`, і він визначає HTML-скелет документа. +Розглянемо успадкування шаблону layout'у, тобто макета, на прикладі. Це батьківський шаблон, який ми назвемо, наприклад, `layout.latte`, і який визначає каркас HTML-документа: ```latte @@ -30,9 +30,9 @@ ``` -Теги `{block}` визначають три блоки, які дочірні шаблони можуть заповнювати. Усе, що робить тег block, це повідомляє шаблонізатору, що дочірній шаблон може перевизначити ці частини шаблону, визначивши свій власний блок із тим самим ім'ям. +Теги `{block}` визначають три блоки, які дочірні шаблони можуть заповнити. Тег block лише повідомляє, що це місце може бути перезаписане дочірнім шаблоном шляхом визначення власного блоку з такою самою назвою. -Дочірній шаблон може мати такий вигляд: +Дочірній шаблон може виглядати так: ```latte {layout 'layout.latte'} @@ -44,11 +44,11 @@ {/block} ``` -Ключовим тут є тег `{layout}`. Він повідомляє шаблонізатору, що цей шаблон "розширює" інший шаблон. Коли Latte рендерить цей шаблон, спершу він знаходить батька - у цьому випадку `layout.latte`. +Ключовим тут є тег `{layout}`. Він повідомляє Latte, що цей шаблон «розширює» інший шаблон. Коли Latte рендерить цей шаблон, він спочатку знаходить батьківський шаблон — у цьому випадку `layout.latte`. -У цей момент шаблонізатор помітить три блокові теги в `layout.latte` і замінить ці блоки вмістом дочірнього шаблону. Зверніть увагу, що оскільки дочірній шаблон не визначив блок *footer*, замість нього використовується вміст батьківського шаблону. Вміст усередині тега `{block}` у батьківському шаблоні завжди використовується як запасний варіант. +У цей момент Latte помічає три теги block у `layout.latte` і замінює ці блоки вмістом дочірнього шаблону. Оскільки дочірній шаблон не визначив блок *footer*, замість нього використовується вміст з батьківського шаблону. Вміст у тегу `{block}` у батьківському шаблоні завжди використовується як резервний. -Виведення може мати такий вигляд: +Виведення може виглядати так: ```latte @@ -68,7 +68,7 @@ ``` -У дочірньому шаблоні блоки можуть розташовуватися тільки або на верхньому рівні, або всередині іншого блоку, тобто: +У дочірньому шаблоні блоки можуть бути розміщені лише на найвищому рівні або всередині іншого блоку, тобто: ```latte {block content} @@ -76,7 +76,7 @@ {/block} ``` -Крім того, блок завжди буде створюватися в незалежно від того, чи буде оточуюча `{if}` умова оцінена як true або false. Всупереч тому, що ви можете подумати, цей шаблон дійсно визначає блок. +Також блок завжди буде створено незалежно від того, чи буде навколишня умова `{if}` оцінена як true чи false. Тож, навіть якщо це не виглядає так, цей шаблон визначає блок. ```latte {if false} @@ -86,7 +86,7 @@ {/if} ``` -Якщо ви хочете, щоб виведення всередині блоку відображалося умовно, використовуйте наступне: +Якщо ви хочете, щоб виведення всередині блоку відображалося умовно, використовуйте замість цього наступне: ```latte {block head} @@ -96,7 +96,7 @@ {/block} ``` -Дані поза блоками в дочірньому шаблоні виконуються до відтворення шаблону макета, тому ви можете використати його для визначення змінних типу `{var $foo = bar}` і поширення даних на весь ланцюжок успадкування: +Простір поза блоками в дочірньому шаблоні виконується перед рендерингом шаблону layout'у, тому ви можете використовувати його для визначення змінних, таких як `{var $foo = bar}`, і для поширення даних по всьому ланцюжку успадкування: ```latte {layout 'layout.latte'} @@ -106,51 +106,50 @@ ``` -Багаторівневе успадкування .[#toc-multilevel-inheritance] ---------------------------------------------------------- -Ви можете використовувати стільки рівнів успадкування, скільки необхідно. Одним із поширених способів використання успадкування макетів є наступний трирівневий підхід: +Багаторівневе успадкування +-------------------------- +Ви можете використовувати стільки рівнів успадкування, скільки вам потрібно. Поширеним способом використання успадкування layout'ів є наступний трирівневий підхід: -1) Створіть шаблон `layout.latte`, у якому зберігатиметься основний зовнішній вигляд вашого сайту. -2) Створіть шаблон `layout-SECTIONNAME.latte` для кожного розділу вашого сайту. Наприклад, `layout-news.latte`, `layout-blog.latte` тощо. Усі ці шаблони розширюють `layout.latte` і включають стилі/дизайн для кожного розділу. -3) Створіть окремі шаблони для кожного типу сторінки, наприклад, для новинної статті або запису в блозі. Ці шаблони розширюють відповідний шаблон розділу. +1) Створіть шаблон `layout.latte`, який містить основний каркас зовнішнього вигляду сайту. +2) Створіть шаблон `layout-SECTIONNAME.latte` для кожної секції вашого сайту. Наприклад, `layout-news.latte`, `layout-blog.latte` тощо. Усі ці шаблони розширюють `layout.latte` і включають стилі та дизайн, специфічні для окремих секцій. +3) Створіть індивідуальні шаблони для кожного типу сторінки, наприклад, для статті новин або запису в блозі. Ці шаблони розширюють відповідний шаблон секції. -Динамічне успадкування макета .[#toc-dynamic-layout-inheritance] ----------------------------------------------------------------- -Ви можете використовувати змінну або будь-який вираз PHP як ім'я батьківського шаблону, отже, успадкування може поводитися динамічно: +Динамічне успадкування +---------------------- +Як назву батьківського шаблону можна використовувати змінну або будь-який вираз PHP, тому успадкування може поводитися динамічно: ```latte {layout $standalone ? 'minimum.latte' : 'layout.latte'} ``` -Ви також можете використовувати Latte API для [автоматичного |develop#Automatic-Layout-Lookup] вибору шаблону компонування. +Ви також можете використовувати Latte API для [автоматичного |develop#Автоматичний пошук layout] вибору шаблону layout'у. -Поради .[#toc-tips] -------------------- -Ось кілька порад щодо роботи зі спадкуванням макета: +Поради +------ +Ось кілька порад щодо роботи з успадкуванням layout'ів: -- Якщо ви використовуєте `{layout}` у шаблоні, він має бути першим тегом шаблону в цьому шаблоні. +- Якщо ви використовуєте `{layout}` у шаблоні, це має бути перший тег шаблону в цьому шаблоні. -- Макет можна [шукати автоматично |develop#automatic-layout-lookup] (як у [презентаторах |application:templates#search-for-templates]). У цьому випадку, якщо шаблон не повинен мати макет, він вкаже на це тегом `{layout none}`. +- Layout може [автоматично знаходитись |develop#Автоматичний пошук layout] (як, наприклад, у [presenter'ах |application:templates#Пошук шаблонів]). У такому випадку, якщо шаблон не повинен мати layout, він повідомляє про це тегом `{layout none}`. - Тег `{layout}` має псевдонім `{extends}`. -- Ім'я файлу розширеного шаблону залежить від [завантажувача шаблонів |extending-latte#Loaders]. +- Назва файлу layout'у залежить від [завантажувача |loaders]. -- Ви можете мати стільки блоків, скільки хочете. Пам'ятайте, що дочірні шаблони не зобов'язані визначати всі батьківські блоки, тому ви можете заповнити розумні значення за замовчуванням у кількох блоках, а потім визначити лише ті, які вам потрібні пізніше. +- Ви можете мати скільки завгодно блоків. Пам'ятайте, що дочірні шаблони не зобов'язані визначати всі батьківські блоки, тому ви можете заповнити розумні значення за замовчуванням у кількох блоках, а потім визначити лише ті, які вам потрібні пізніше. -Блоки `{block}` .{toc: Blocks} -============================== +Блоки `{block}` .{toc: Блоки} +============================= .[note] -Див. також анонімні [`{block}` |tags#block] +Див. також анонімний [`{block}` |tags#block] -Блок дає можливість змінити відображення певної частини шаблону, але ніяк не втручається в навколишню логіку. Давайте розглянемо наступний приклад, щоб проілюструвати, як працює блок і, що важливіше, як він не працює: +Блок представляє спосіб змінити рендеринг певної частини шаблону, але жодним чином не втручається в логіку навколо нього. У наступному прикладі ми покажемо, як блок працює, а також як він не працює: -```latte -{* parent.Latte *} +```latte .{file: parent.latte} {foreach $posts as $post} {block post}

                                                                                                                                        {$post->title}

                                                                                                                                        @@ -159,10 +158,9 @@ {/foreach} ``` -Якщо ви відобразите цей шаблон, результат буде точно таким самим із тегами блоку або без них. Блоки мають доступ до змінних із зовнішніх діапазонів. Це просто спосіб зробити його перевизначеним для дочірнього шаблону: +Якщо ви відрендерите цей шаблон, результат буде точно таким же, як з тегами `{block}`, так і без них. Блоки мають доступ до змінних із зовнішніх областей видимості. Вони лише дають можливість бути перезаписаними дочірнім шаблоном: -```latte -{* child.Latte *} +```latte .{file: child.latte} {layout 'parent.Latte'} {block post} @@ -173,7 +171,7 @@ {/block} ``` -Тепер під час візуалізації дочірнього шаблону цикл використовуватиме блок, визначений у дочірньому шаблоні `child.Latte`, замість блоку, визначеного в базовому шаблоні `parent.Latte`; виконаний шаблон буде еквівалентний такому: +Тепер при рендерингу дочірнього шаблону цикл буде використовувати блок, визначений у дочірньому шаблоні `child.Latte`, замість блоку, визначеного в `parent.Latte`; запущений шаблон тоді еквівалентний наступному: ```latte {foreach $posts as $post} @@ -184,7 +182,7 @@ {/foreach} ``` -Однак, якщо ми створимо нову змінну всередині іменованого блоку або замінимо значення існуючої змінної, зміну буде видно тільки всередині блоку: +Однак, якщо ми створимо нову змінну всередині іменованого блоку або замінимо значення існуючої, зміна буде видима лише всередині блоку: ```latte {var $foo = 'foo'} @@ -193,17 +191,17 @@ {var $bar = 'bar'} {/block} -foo: {$foo} // prints: foo -bar: {$bar ?? 'not defined'} // prints: not defined +foo: {$foo} // виводить: foo +bar: {$bar ?? 'не визначено'} // виводить: не визначено ``` -Вміст блоку може бути змінено за допомогою [фільтрів |syntax#Filters]. У наступному прикладі видаляється весь HTML і наводиться заголовок: +Вміст блоку можна змінити за допомогою [фільтрів |syntax#Фільтри]. Наступний приклад видаляє весь HTML і змінює регістр літер: ```latte {block title|stripHtml|capitalize}...{/block} ``` -Тег також може бути записаний як [n:attribute |syntax#n-attributes]: +Тег також можна записати як [n:атрибут |syntax#n:атрибути]: ```latte
                                                                                                                                        @@ -212,10 +210,10 @@ bar: {$bar ?? 'not defined'} // prints: not defined ``` -Локальні блоки .{data-version:2.9}[#toc-local-blocks] ------------------------------------------------------ +Локальні блоки +-------------- -Кожен блок перевизначає вміст однойменного батьківського блоку. За винятком локальних блоків. Вони чимось схожі на приватні методи в класі. Ви можете створити шаблон, не побоюючись, що через збіг імен блоків вони будуть перезаписані другим шаблоном. +Кожен блок перезаписує вміст батьківського блоку з такою самою назвою — крім локальних блоків. У класах це було б щось на зразок приватних методів. Таким чином, ви можете створювати шаблон, не турбуючись, що через збіг імен блоків вони будуть перезаписані з іншого шаблону. ```latte {block local helper} @@ -224,13 +222,13 @@ bar: {$bar ?? 'not defined'} // prints: not defined ``` -Друк блоків `{include}` .{toc: Printing Blocks} ------------------------------------------------ +Рендеринг блоків `{include}` .{toc: Рендеринг блоків} +----------------------------------------------------- .[note] Див. також [`{include file}` |tags#include] -Щоб надрукувати блок у певному місці, використовуйте тег `{include blockname}`: +Щоб вивести блок у певному місці, використовуйте тег `{include blockname}`: ```latte {block title}{/block} @@ -238,32 +236,28 @@ bar: {$bar ?? 'not defined'} // prints: not defined

                                                                                                                                        {include title}

                                                                                                                                        ``` -Ви також можете вивести блок з іншого шаблону: +Також можна вивести блок з іншого шаблону: ```latte {include footer from 'main.latte'} ``` -Блоки, що виводяться, не мають доступу до змінних активного контексту, за винятком випадків, коли блок визначено в тому самому файлі, куди його ввімкнено. Однак вони мають доступ до глобальних змінних. +Блок, що рендериться, не має доступу до змінних активного контексту, за винятком випадків, коли блок визначено в тому ж файлі, де він і вставлений. Однак він має доступ до глобальних змінних. -Ви можете передавати змінні таким чином: +Ви можете передавати змінні в блок таким чином: ```latte -{* починаючи з Latte 2.9 *} {include footer, foo: bar, id: 123} - -{* до Latte 2.9 *} -{include footer, foo => bar, id => 123} ``` -Ви можете використовувати змінну або будь-який вираз у PHP як ім'я блоку. У цьому випадку додайте ключове слово `block` перед змінною, щоб під час компіляції було відомо, що це блок, а не [вставка шаблону |tags#include], ім'я якого також може бути у змінній: +Як назву блоку можна використовувати змінну або будь-який вираз PHP. У такому випадку перед змінною ми додаємо ключове слово `block`, щоб Latte вже під час компіляції знало, що це блок, а не [включення шаблону |tags#include], назва якого також могла б бути у змінній: ```latte {var $name = footer} {include block $name} ``` -Блок також може бути надрукований усередині себе, що корисно, наприклад, під час рендерінгу деревоподібної структури: +Блок можна рендерити і всередині самого себе, що, наприклад, корисно при рендерингу деревовидної структури: ```latte {define menu, $items} @@ -281,19 +275,19 @@ bar: {$bar ?? 'not defined'} // prints: not defined {/define} ``` -Замість `{include menu, ...}` можна також написати `{include this, ...}`, де `this` означає поточний блок. +Замість `{include menu, ...}` ми можемо написати `{include this, ...}`, де `this` означає поточний блок. -Виведений вміст можна змінювати за допомогою [фільтрів |syntax#Filters]. У наступному прикладі видаляється весь HTML і ставиться заголовок: +Блок, що рендериться, можна змінити за допомогою [фільтрів |syntax#Фільтри]. Наступний приклад видаляє весь HTML і змінює регістр літер: ```latte {include heading|stripHtml|capitalize} ``` -Батьківський блок .[#toc-parent-block] --------------------------------------- +Батьківський блок +----------------- -Якщо вам потрібно вивести вміст блоку з батьківського шаблону, вам допоможе оператор `{include parent}`. Це корисно, якщо ви хочете доповнити вміст батьківського блоку, а не повністю його перевизначити. +Якщо вам потрібно вивести вміст блоку з батьківського шаблону, використовуйте `{include parent}`. Це корисно, якщо ви хочете лише доповнити вміст батьківського блоку, а не повністю його перезаписати. ```latte {block footer} @@ -304,32 +298,31 @@ bar: {$bar ?? 'not defined'} // prints: not defined ``` -Визначення `{define}` .{toc: Definitions} ------------------------------------------ +Визначення `{define}` .{toc: Визначення} +---------------------------------------- -Крім блоків, у Latte існують також "визначення". Їх можна порівняти з функціями у звичайних мовах програмування. Вони корисні для повторного використання фрагментів шаблонів, щоб не повторюватися. +Крім блоків, у Latte також існують «визначення». У звичайних мовах програмування ми б порівняли їх із функціями. Вони корисні для повторного використання фрагментів шаблону, щоб уникнути повторень. -Latte намагається робити все просто, тому здебільшого визначення - це те саме, що й блоки, і **все, що сказано про блоки, стосується і визначень**. Він відрізняється від блоків тільки трьома способами: +Latte намагається робити речі простими, тому, по суті, визначення такі ж, як і блоки, і **все, що сказано про блоки, стосується також і визначень**. Вони відрізняються від блоків тим, що: -1) вони можуть приймати аргументи -2) вони не можуть мати [фільтрів |syntax#Filters] -3) вони укладені в теги `{define}`, і вміст усередині цих тегів не надсилається на виведення, поки ви їх не ввімкнете. Завдяки цьому ви можете створювати їх у будь-якому місці: +1) вони укладені в теги `{define}` +2) вони рендеряться лише тоді, коли ви їх вставляєте через `{include}` +3) їм можна визначати параметри, подібно до функцій у PHP ```latte {block foo}

                                                                                                                                        Hello

                                                                                                                                        {/block} -{* prints:

                                                                                                                                        Hello

                                                                                                                                        *} +{* виводить:

                                                                                                                                        Hello

                                                                                                                                        *} {define bar}

                                                                                                                                        World

                                                                                                                                        {/define} -{* prints nothing *} +{* нічого не виводить *} {include bar} -{* prints:

                                                                                                                                        World

                                                                                                                                        *} +{* виводить:

                                                                                                                                        World

                                                                                                                                        *} ``` -Уявіть, що у вас є загальний шаблон-помічник, який визначає, як виводити HTML-форми за допомогою визначень: +Уявіть, що у вас є допоміжний шаблон із колекцією визначень, як малювати HTML-форми. -```latte -{* forms.latte *} +```latte .{file: forms.latte} {define input, $name, $value, $type = 'text'} {/define} @@ -339,38 +332,36 @@ Latte намагається робити все просто, тому здеб {/define} ``` -Аргументи визначень завжди необов'язкові зі значенням за замовчуванням `null`, якщо не вказано значення за замовчуванням (тут `text` - це значення за замовчуванням для `$type`, можливе з Latte 2.9.1). Починаючи з Latte 2.7, типи параметрів також можуть бути оголошені: `{define input, string $name, ...}`. - -Визначення не мають доступу до змінних активного контексту, але мають доступ до глобальних змінних. +Аргументи завжди необов'язкові зі значенням за замовчуванням `null`, якщо не вказано значення за замовчуванням (тут `'text'` є значенням за замовчуванням для `$type`). Також можна оголошувати типи параметрів: `{define input, string $name, ...}`. -Вони включаються так само, як і [блок |#Printing-Blocks]: +Шаблон з визначеннями завантажується за допомогою [`{import}` |#Горизонтальне повторне використання]. Самі визначення рендеряться [так само, як і блоки |#Рендеринг блоків]: ```latte

                                                                                                                                        {include input, 'password', null, 'password'}

                                                                                                                                        {include textarea, 'comment'}

                                                                                                                                        ``` +Визначення не мають доступу до змінних активного контексту, але мають доступ до глобальних змінних. -Динамічні імена блоків .[#toc-dynamic-block-names] --------------------------------------------------- -Latte дає змогу дуже гнучко визначати блоки, тому що ім'я блоку може бути будь-яким виразом PHP. У цьому прикладі визначено три блоки з іменами `hi-Peter`, `hi-John` і `hi-Mary`: +Динамічні назви блоків +---------------------- -```latte -{* parent.latte *} +Latte дозволяє велику гнучкість при визначенні блоків, оскільки назва блоку може бути будь-яким виразом PHP. Цей приклад визначає три блоки з назвами `hi-Peter`, `hi-John` та `hi-Mary`: + +```latte .{file: parent.latte} {foreach [Peter, John, Mary] as $name} {block "hi-$name"}Hi, I am {$name}.{/block} {/foreach} ``` -Наприклад, ми можемо перевизначити тільки один блок у дочірньому шаблоні: +У дочірньому шаблоні ми можемо пере-визначити, наприклад, лише один блок: -```latte -{* child.latte *} +```latte .{file: child.latte} {block hi-John}Hello. I am {$name}.{/block} ``` -Таким чином, виведення матиме такий вигляд: +Тож виведення виглядатиме так: ```latte Hi, I am Peter. @@ -379,13 +370,13 @@ Hi, I am Mary. ``` -Перевірка існування блоку `{ifset}` .{toc: Checking Block Existence} --------------------------------------------------------------------- +Перевірка існування блоків `{ifset}` .{toc: Перевірка існування блоків} +----------------------------------------------------------------------- .[note] -Див. також [`{ifset $var}` |tags#ifset-elseifset] +Див. також [`{ifset $var}` |tags#ifset elseifset] -Використовуйте тест `{ifset blockname}`, щоб перевірити, чи існує блок (або кілька блоків) у поточному контексті: +За допомогою тесту `{ifset blockname}` ми перевіряємо, чи існує блок (або кілька блоків) у поточному контексті: ```latte {ifset footer} @@ -397,7 +388,7 @@ Hi, I am Mary. {/ifset} ``` -Як ім'я блоку ви можете використовувати змінну або будь-який вираз у PHP. У цьому випадку додайте ключове слово `block` перед змінною, щоб було зрозуміло, що перевіряється не [вона |tags#ifset-elseifset]: +Як назву блоку можна використовувати змінну або будь-який вираз PHP. У такому випадку перед змінною ми додаємо ключове слово `block`, щоб було зрозуміло, що це не тест на існування [змінних |tags#ifset elseifset]: ```latte {ifset block $name} @@ -405,71 +396,69 @@ Hi, I am Mary. {/ifset} ``` +Існування блоків також перевіряє функція [`hasBlock()` |functions#hasBlock]: -Поради .[#toc-tips] -------------------- -Ось кілька порад щодо роботи з блоками: - -- Останній блок верхнього рівня не обов'язково повинен мати закриваючий тег (блок закінчується разом із кінцем документа). Це спрощує написання дочірніх шаблонів, у яких один основний блок. +```latte +{if hasBlock(header) || hasBlock(footer)} + ... +{/if} +``` -- Для підвищення читабельності ви можете за бажанням дати ім'я тегу `{/block}`, наприклад `{/block footer}`. Однак ім'я має збігатися з ім'ям блоку. У великих шаблонах цей прийом допомагає побачити, які теги блоків закриваються. -- Ви не можете безпосередньо визначити кілька блокових тегів з однаковим ім'ям в одному шаблоні. Але цього можна домогтися, використовуючи [динамічні імена блоків |#Dynamic-Block-Names]. +Поради +------ +Кілька порад щодо роботи з блоками: -- Ви можете використовувати [n:attributes |syntax#n-attributes] для визначення таких блоків, як `

                                                                                                                                        Welcome to my awesome homepage

                                                                                                                                        ` +- Останній блок верхнього рівня не обов'язково повинен мати закриваючий тег (блок закінчується кінцем документа). Це спрощує написання дочірніх шаблонів, які містять один основний блок. -- Блоки також можна використовувати без імен, тільки для застосування [фільтрів |syntax#Filters] до висновку: `{block|strip} hello {/block}` +- Для кращої читабельності ви можете вказати назву блоку в тегу `{/block}`, наприклад `{/block footer}`. Однак назва повинна збігатися з назвою блоку. У великих шаблонах ця техніка допоможе вам визначити, які теги блоків закриваються. +- Ви не можете безпосередньо визначити кілька тегів блоків з однаковою назвою в одному шаблоні. Однак цього можна досягти за допомогою [динамічних назв блоків |#Динамічні назви блоків]. -Горизонтальне повторне використання `{import}` .{toc: Horizontal Reuse} -======================================================================= +- Ви можете використовувати [n:атрибути |syntax#n:атрибути] для визначення блоків як `

                                                                                                                                        Welcome to my awesome homepage

                                                                                                                                        ` -Горизонтальне повторне використання - це третій механізм повторного використання та успадкування в Latte. Він дозволяє завантажувати блоки з інших шаблонів. Це схоже на створення PHP-файлу з допоміжними функціями або трейтом. +- Блоки також можна використовувати без назв лише для застосування [фільтрів |syntax#Фільтри]: `{block|strip} hello {/block}` -Хоча успадкування шаблонів компонування є однією з найпотужніших можливостей Latte, воно обмежене одноразовим успадкуванням; шаблон може розширювати тільки один інший шаблон. Це обмеження робить успадкування шаблонів простим для розуміння і легким для налагодження: -```latte -{layout 'layout.latte'} +Горизонтальне повторне використання `{import}` .{toc: Горизонтальне повторне використання} +========================================================================================== -{block title}...{/block} -{block content}...{/block} -``` +Горизонтальне повторне використання є третім механізмом повторного використання та успадкування в Latte. Воно дозволяє завантажувати блоки з інших шаблонів. Це схоже на те, як ми створюємо файл із допоміжними функціями в PHP, який потім завантажуємо за допомогою `require`. -Горизонтальне повторне використання - це спосіб досягти тієї самої мети, що й множинне успадкування, але без супутніх складнощів: +Хоча успадкування layout'ів шаблонів є однією з найпотужніших функцій Latte, воно обмежене простим успадкуванням — шаблон може розширювати лише один інший шаблон. Горизонтальне повторне використання — це спосіб досягти множинного успадкування. -```latte -{layout 'layout.latte'} +Маємо файл з визначеннями блоків: -{import 'blocks.latte'} +```latte .{file: blocks.latte} +{block sidebar}...{/block} -{block title}...{/block} -{block content}...{/block} +{block menu}...{/block} ``` -Оператор `{import}` вказує Latte імпортувати всі блоки та [визначення |#Definitions], визначені в `blocks.latte`, у поточний шаблон. +За допомогою команди `{import}` ми імпортуємо всі блоки та [#визначення], визначені в `blocks.latte`, до іншого шаблону: -```latte -{* blocks.latte *} +```latte .{file: child.latte} +{import 'blocks.latte'} -{block sidebar}...{/block} +{* тепер можна використовувати блоки sidebar та menu *} ``` -У цьому прикладі оператор `{import}` імпортує блок `sidebar` в основний шаблон. +Якщо ви імпортуєте блоки в батьківському шаблоні (тобто використовуєте `{import}` у `layout.latte`), блоки будуть доступні також у всіх дочірніх шаблонах, що дуже практично. -Імпортований шаблон не повинен [розширювати |#Layout-Inheritance] інший шаблон, а його тіло має бути порожнім. Однак імпортований шаблон може імпортувати інші шаблони. +Шаблон, призначений для імпорту (наприклад, `blocks.latte`), не повинен [розширювати |#Успадкування layout ів] інший шаблон, тобто використовувати `{layout}`. Однак він може імпортувати інші шаблони. -Тег `{import}` має бути першим тегом шаблону після `{layout}`. Ім'я шаблону може бути будь-яким виразом PHP: +Тег `{import}` повинен бути першим тегом шаблону після `{layout}`. Назва шаблону може бути будь-яким виразом PHP: ```latte {import $ajax ? 'ajax.latte' : 'not-ajax.latte'} ``` -Ви можете використовувати стільки виразів `{import}`, скільки хочете, у будь-якому даному шаблоні. Якщо два імпортованих шаблони визначають один і той самий блок, перемагає перший. Однак найвищий пріоритет віддається головному шаблону, який може перезаписати будь-який імпортований блок. +Ви можете використовувати стільки команд `{import}` у шаблоні, скільки забажаєте. Якщо два імпортованих шаблони визначають один і той самий блок, перемагає перший. Однак найвищий пріоритет має головний шаблон, який може перезаписати будь-який імпортований блок. -Усі перевизначені блоки можна включати поступово, вставляючи їх як [батьківський блок |#Parent-Block]: +Вміст перезаписаних блоків можна зберегти, вставивши блок так само, як вставляється [#батьківський блок]: ```latte -{layout 'base.latte'} +{layout 'layout.latte'} {import 'blocks.latte'} @@ -481,17 +470,17 @@ Hi, I am Mary. {block content}...{/block} ``` -У цьому прикладі `{include parent}` буде коректно викликати блок `sidebar` із шаблону `blocks.latte`. +У цьому прикладі `{include parent}` викликає блок `sidebar` із шаблону `blocks.latte`. -Успадкування блоків `{embed}` .{toc: Unit Inheritance}{data-version:2.9} -======================================================================== +Успадкування одиниць `{embed}` +============================== -Спадкування блоків переносить ідею успадкування макетів на рівень фрагментів контенту. У той час як успадкування макета працює зі "скелетами документів", які пожвавлюються дочірніми шаблонами, успадкування одиниць дає змогу створювати скелети для менших одиниць вмісту і повторно використовувати їх у будь-якому місці. +Успадкування одиниць розширює ідею успадкування layout'ів до рівня фрагментів контенту. Тоді як успадкування layout'ів працює з «каркасом документа», який оживляють дочірні шаблони, успадкування одиниць дозволяє створювати каркаси для менших одиниць контенту та повторно використовувати їх де завгодно. -У успадкуванні блоків ключовим є тег `{embed}`. Він поєднує в собі поведінку `{include}` і `{layout}`. Він дозволяє включати вміст іншого шаблону або блоку і, за бажанням, передавати змінні, як це робить `{include}`. Він також дозволяє перевизначати будь-який блок, визначений всередині включеного шаблону, як це робить `{layout}`. +У спадкуванні одиниць ключовим є тег `{embed}`. Він поєднує поведінку `{include}` та `{layout}`. Дозволяє вставляти вміст іншого шаблону чи блоку та необов'язково передавати змінні, так само як у випадку `{include}`. Також дозволяє перезаписувати будь-який блок, визначений усередині вбудованого шаблону, як при використанні `{layout}`. -Для прикладу ми будемо використовувати елемент складного акордеона. Давайте подивимося на скелет елемента в шаблоні `collapsible.latte`: +Наприклад, використаємо елемент акордеон. Розглянемо каркас елемента, збережений у шаблоні `collapsible.latte`: ```latte
                                                                                                                                        @@ -505,9 +494,9 @@ Hi, I am Mary.
                                                                                                                                        ``` -Теги `{block}` визначають два блоки, які можуть заповнювати дочірні шаблони. Так, як і у випадку з батьківським шаблоном у шаблоні успадкування макета. Ви також бачите змінну `$modifierClass`. +Теги `{block}` визначають два блоки, які дочірні шаблони можуть заповнити. Так, як у випадку батьківського шаблону при успадкуванні layout'ів. Ви також бачите змінну `$modifierClass`. -Давайте використаємо наш елемент у шаблоні. Тут на допомогу приходить `{embed}`. Це супер потужний набір, який дає нам змогу робити все: включати вміст шаблону елемента, додавати до нього змінні та додавати до нього блоки з користувацьким HTML: +Давайте використаємо наш елемент у шаблоні. Тут на сцену виходить `{embed}`. Це надзвичайно потужний тег, який дозволяє нам робити все: вставляти вміст шаблону елемента, додавати до нього змінні та додавати до нього блоки з власним HTML: ```latte {embed 'collapsible.latte', modifierClass: my-style} @@ -522,7 +511,7 @@ Hi, I am Mary. {/embed} ``` -Висновок може мати такий вигляд: +Виведення може виглядати так: ```latte
                                                                                                                                        @@ -537,7 +526,7 @@ Hi, I am Mary.
                                                                                                                                        ``` -Блоки всередині тегів embed утворюють окремий шар, незалежний від інших блоків. Тому вони можуть мати те саме ім'я, що й блок поза embed, і ніяк на них не впливають. Використовуючи тег [include |#Printing-Blocks] всередині тегів `{embed}`, ви можете вставляти створені тут блоки, блоки з вбудованого шаблону (які *не є* [локальними |#Local-Blocks]), а також блоки з основного шаблону, які *є* локальними. Ви також можете [імпортувати блоки |#Horizontal-Reuse] з інших файлів: +Блоки всередині вбудованих тегів утворюють окремий шар, незалежний від інших блоків. Тому вони можуть мати таку ж назву, як і блок поза вбудовуванням, і на них це ніяк не впливає. За допомогою тегу [include |#Рендеринг блоків] всередині тегів `{embed}` ви можете вставляти блоки, створені тут, блоки з вбудованого шаблону (які *не є* [локальними |#Локальні блоки]), а також блоки з головного шаблону, які, навпаки, *є* локальними. Ви також можете [імпортувати блоки |#Горизонтальне повторне використання] з інших файлів: ```latte {block outer}…{/block} @@ -549,9 +538,9 @@ Hi, I am Mary. {block inner}…{/block} {block title} - {include inner} {* працює, якщо блок визначено всередині embed *} + {include inner} {* працює, блок визначений всередині embed *} {include hello} {* працює, блок є локальним у цьому шаблоні *} - {include content} {* працює, блок визначено у вбудованому шаблоні *} + {include content} {* працює, блок визначений у вбудованому шаблоні *} {include aBlockDefinedInImportedTemplate} {* працює *} {include outer} {* не працює! - блок знаходиться у зовнішньому шарі *} {/block} @@ -560,7 +549,7 @@ Hi, I am Mary. Вбудовані шаблони не мають доступу до змінних активного контексту, але мають доступ до глобальних змінних. -За допомогою `{embed}` можна вставляти не тільки шаблони, а й інші блоки, тому попередній приклад можна написати так: .{data-version:2.10} +За допомогою `{embed}` можна вставляти не тільки шаблони, але й інші блоки, і, отже, попередній приклад можна було б записати таким чином: ```latte {define collapsible} @@ -581,23 +570,23 @@ Hi, I am Mary. {/embed} ``` -Якщо ми передаємо вираз у `{embed}` і не ясно, що це - блок чи ім'я файлу, додайте ключове слово `block` або `file`: +Якщо ми передаємо вираз у `{embed}` і незрозуміло, чи це назва блоку чи файлу, ми додаємо ключове слово `block` або `file`: ```latte {embed block $name} ... {/embed} ``` -Приклади використання .[#toc-use-cases] -======================================= +Випадки використання +==================== -У Latte існують різні види успадкування та повторного використання коду. Давайте узагальнимо основні поняття для більшої наочності: +У Latte існують різні типи успадкування та повторного використання коду. Давайте підсумуємо основні концепції для більшої ясності: `{include template}` -------------------- -**Use Case:** Використання `header.latte` і `footer.latte` всередині `layout.latte`. +**Випадок використання**: Використання `header.latte` та `footer.latte` всередині `layout.latte`. `header.latte` @@ -630,7 +619,7 @@ Hi, I am Mary. `{layout}` ---------- -**Приклад використання**: Розширення `layout.latte` всередині `homepage.latte` і `about.latte`. +**Випадок використання**: Розширення `layout.latte` всередині `homepage.latte` та `about.latte`. `layout.latte` @@ -666,12 +655,12 @@ Hi, I am Mary. `{import}` ---------- -**Користувацький випадок**: `sidebar.latte` в `single.product.latte` і `single.service.latte`. +**Випадок використання**: `sidebar.latte` у `single.product.latte` та `single.service.latte`. `sidebar.latte` ```latte -{block sidebar}{/block} +{block sidebar}{/block} ``` `single.product.latte` @@ -681,7 +670,7 @@ Hi, I am Mary. {import 'sidebar.latte'} -{block main}
                                                                                                                                        Product page
                                                                                                                                        {/block} +{block main}
                                                                                                                                        Сторінка продукту
                                                                                                                                        {/block} ``` `single.service.latte` @@ -691,14 +680,14 @@ Hi, I am Mary. {import 'sidebar.latte'} -{block main}
                                                                                                                                        Service page
                                                                                                                                        {/block} +{block main}
                                                                                                                                        Сторінка сервісу
                                                                                                                                        {/block} ``` `{define}` ---------- -**Приклад використання**: Функція, яка отримує деякі змінні та виводить деяку розмітку. +**Випадок використання**: Функції, яким ми передаємо змінні і щось рендеримо. `form.latte` @@ -724,7 +713,7 @@ Hi, I am Mary. `{embed}` --------- -**Приклад використання**: Вбудовування `pagination.latte` в `product.table.latte` і `service.table.latte`. +**Випадок використання**: Вставка `pagination.latte` у `product.table.latte` та `service.table.latte`. `pagination.latte` @@ -744,8 +733,8 @@ Hi, I am Mary. ```latte {embed 'pagination.latte', min: 1, max: $products->count} - {block first}First Product Page{/block} - {block last}Last Product Page{/block} + {block first}Перша сторінка продукту{/block} + {block last}Остання сторінка продукту{/block} {/embed} ``` @@ -753,7 +742,7 @@ Hi, I am Mary. ```latte {embed 'pagination.latte', min: 1, max: $services->count} - {block first}First Service Page{/block} - {block last}Last Service Page{/block} + {block first}Перша сторінка сервісу{/block} + {block last}Остання сторінка сервісу{/block} {/embed} ``` diff --git a/latte/uk/type-system.texy b/latte/uk/type-system.texy index 916404b310..14ea265b99 100644 --- a/latte/uk/type-system.texy +++ b/latte/uk/type-system.texy @@ -1,27 +1,27 @@ Система типів ************* -
                                                                                                                                        +
                                                                                                                                        -Система типів - це головне для розробки надійних додатків. Latte привносить підтримку типів у шаблони. Знання того, до якого типу даних або об'єктів належить кожна змінна, дає змогу +Система типів є ключовою для розробки надійних додатків. Latte привносить підтримку типів і в шаблони. Завдяки тому, що ми знаємо, який тип даних або об'єктний тип міститься в кожній змінній, може -- IDE правильно здійснювати автозаповнення (див. [інтеграцію та плагіни |recipes#Editors-and-IDE]) -- статичний аналіз для виявлення помилок +- IDE правильно підказувати (див. [інтеграція |recipes#Редактори та IDE]) +- статичний аналіз виявляти помилки -Два моменти, які значно підвищують якість і зручність розробки. +Обидва фактори суттєво підвищують якість та зручність розробки.
                                                                                                                                        .[note] -Заявлені типи є інформативними, і Latte не перевіряє їх наразі. +Оголошені типи є інформативними, і Latte на даний момент їх не перевіряє. -Як почати використовувати типи? Створіть шаблонний клас, наприклад `CatalogTemplateParameters`, що представляє передані параметри: +Як почати використовувати типи? Створіть клас шаблону, наприклад `CatalogTemplateParameters`, що представляє передані параметри, їх типи та, за необхідності, значення за замовчуванням: ```php class CatalogTemplateParameters { public function __construct( - public string $langs, + public string $lang, /** @var ProductEntity[] */ public array $products, public Address $address, @@ -35,19 +35,16 @@ $latte->render('template.latte', new CatalogTemplateParameters( )); ``` -Потім вставте тег `{templateType}` з повним ім'ям класу (включно із простором імен) на початок шаблону. Це визначає, що в шаблоні будуть змінні `$langs` і `$products`, включаючи відповідні типи. -Ви також можете вказати типи локальних змінних за допомогою тегів [`{var}` |tags#var-default], `{varType}` і [`{define}` |template-inheritance#Definitions]. +А далі на початку шаблону вставте тег `{templateType}` з повною назвою класу (включаючи простір імен). Це визначає, що в шаблоні є змінні `$lang` та `$products` з відповідними типами. Типи локальних змінних можна вказати за допомогою тегів [`{var}` |tags#var default], `{varType}`, [`{define}` |template-inheritance#Визначення]. -Тепер IDE може коректно автозаповнювати. +З цього моменту ваше IDE може правильно підказувати. -Як зберегти роботу? Як максимально просто написати шаблонний клас або теги `{varType}`? Згенерувати їх. -Саме це і робить пара тегів `{templatePrint}` і `{varPrint}`. -Якщо ви помістите один із цих тегів у шаблон, то замість звичайного рендерінгу відображатиметься код класу або шаблону. Потім просто виділіть і скопіюйте код у свій проєкт. +Як заощадити роботу? Як найпростіше написати клас з параметрами шаблону або теги `{varType}`? Дозвольте їм згенеруватися. Для цього існує пара тегів `{templatePrint}` та `{varPrint}`. Якщо ви розмістите їх у шаблоні, замість звичайного рендерингу відобразиться пропозиція коду класу або список тегів `{varType}` відповідно. Потім код достатньо одним кліком виділити та скопіювати до проекту. `{templateType}` ---------------- -Типи параметрів, що передаються в шаблон, оголошуються за допомогою класу: +Типи параметрів, що передаються в шаблон, оголошуємо за допомогою класу: ```latte {templateType MyApp\CatalogTemplateParameters} @@ -56,7 +53,7 @@ $latte->render('template.latte', new CatalogTemplateParameters( `{varType}` ----------- -Як оголосити типи змінних? Для цього використовуйте тег `{varType}` для існуючої змінної, або [`{var}` |tags#var-default]: +Як оголосити типи змінних? Для цього служать теги `{varType}` для існуючих змінних, або [`{var}` |tags#var default]: ```latte {varType Nette\Security\User $user} @@ -66,11 +63,11 @@ $latte->render('template.latte', new CatalogTemplateParameters( `{templatePrint}` ----------------- -Ви також можете згенерувати цей клас за допомогою тега `{templatePrint}`. Якщо помістити його на початок шаблону, то замість звичайного шаблону відображатиметься код класу. Потім просто виділіть і скопіюйте код у свій проєкт. +Клас також можна згенерувати за допомогою тегу `{templatePrint}`. Якщо ви розмістите його на початку шаблону, замість звичайного рендерингу відобразиться пропозиція класу. Потім код достатньо одним кліком виділити та скопіювати до проекту. `{varPrint}` ------------ -Тег `{varPrint}` економить ваш час. Якщо помістити його в шаблон, то замість звичайного рендерингу відобразиться список тегів `{varType}`. Потім просто виберіть і скопіюйте код у свій шаблон. +Тег `{varPrint}` заощадить вам час на написання. Якщо ви розмістите його в шаблоні, замість звичайного рендерингу з'явиться пропозиція тегів `{varType}` для локальних змінних. Потім код достатньо одним кліком виділити та скопіювати до шаблону. -У `{varPrint}` перераховані локальні змінні, які не є параметрами шаблону. Якщо ви хочете перерахувати всі змінні, використовуйте `{varPrint all}`. +Сам `{varPrint}` виводить лише локальні змінні, які не є параметрами шаблону. Якщо ви хочете вивести всі змінні, використовуйте `{varPrint all}`. diff --git a/latte/uk/why-use.texy b/latte/uk/why-use.texy new file mode 100644 index 0000000000..e723d52d6a --- /dev/null +++ b/latte/uk/why-use.texy @@ -0,0 +1,80 @@ +Навіщо використовувати шаблони? +******************************* + + +Навіщо мені використовувати систему шаблонів у PHP? +--------------------------------------------------- + +Навіщо використовувати систему шаблонів у PHP, якщо PHP сам по собі є мовою шаблонів? + +Давайте спочатку коротко розглянемо історію цієї мови, яка сповнена цікавих поворотів. Однією з перших мов програмування, що використовувалися для генерації HTML-сторінок, була мова C. Однак незабаром з'ясувалося, що її використання для цієї мети є непрактичним. Тому Расмус Лердорф створив PHP, який полегшив генерацію динамічного HTML з мовою C на бекенді. Отже, PHP спочатку був розроблений як мова шаблонів, але з часом отримав додаткові функції і став повноцінною мовою програмування. + +Тим не менш, він все ще функціонує і як мова шаблонів. У файлі PHP може бути записана HTML-сторінка, в якій за допомогою `` виводяться змінні тощо. + +Вже на початку історії PHP виникла система шаблонів Smarty, метою якої було суворе розділення вигляду (HTML/CSS) від логіки додатка. Тобто вона навмисно надавала обмеженішу мову, ніж сам PHP, щоб розробник не міг, наприклад, виконати запит до бази даних із шаблону тощо. З іншого боку, вона представляла додаткову залежність у проектах, збільшувала їх складність, і програмістам доводилося вивчати нову мову Smarty. Така користь була сумнівною, і для шаблонів продовжували використовувати простий PHP. + +З часом системи шаблонів почали ставати корисними. Вони прийшли з концепцією [успадкування |template-inheritance], [режимом sandbox |sandbox] та низкою інших функцій, які значно спростили створення шаблонів порівняно з чистим PHP. На перший план вийшла тема безпеки, існування [вразливостей, таких як XSS |safety-first] та необхідність [екранування |#Що таке екранування]. Системи шаблонів прийшли з автоекрануванням, щоб зник ризик того, що програміст про це забуде і виникне серйозна діра в безпеці (за мить ми покажемо, що це має певні підводні камені). + +Переваги систем шаблонів сьогодні значно перевищують витрати, пов'язані з їх впровадженням. Тому має сенс їх використовувати. + + +Чому Latte кращий за, наприклад, Twig або Blade? +------------------------------------------------ + +Причин одразу кілька — деякі приємні, а інші надзвичайно корисні. Latte — це поєднання приємного з корисним. + +*Спочатку приємна:* Latte має такий самий [синтаксис, як у PHP |syntax#Latte розуміє PHP]. Відрізняється лише запис тегів, замість `` перевага надається коротшим `{` та `}`. Це означає, що вам не потрібно вивчати нову мову. Витрати на навчання мінімальні. І головне, під час розробки вам не потрібно постійно "перемикатися" між мовою PHP та мовою шаблону, оскільки вони обидві однакові. На відміну від шаблонів Twig, які використовують синтаксис, схожий на Python, і програмісту доводиться перемикатися між двома різними мовами. + +*А тепер надзвичайно корисна причина*: Усі системи шаблонів, такі як Twig, Blade або Smarty, в процесі еволюції прийшли із захистом від XSS у вигляді автоматичного [екранування |#Що таке екранування]. Точніше кажучи, автоматичного виклику функції `htmlspecialchars()`. Однак творці Latte усвідомили, що це зовсім не правильне рішення. Тому що в різних місцях документа екранування відбувається різними способами. Наївне автоекранування — це небезпечна функція, оскільки створює хибне відчуття безпеки. + +Щоб автоекранування було функціональним та надійним, воно повинно розрізняти, в якому місці документа виводяться дані (ми називаємо їх контекстами) і відповідно до цього вибирати функцію екранування. Тобто воно має бути [контекстно-чутливим |safety-first#Контекстно-залежне екранування]. І саме це вміє Latte. Він розуміє HTML. Він сприймає шаблон не просто як рядок символів, а розуміє, що таке теги, атрибути тощо. І тому інакше екранує в HTML-тексті, інакше всередині HTML-тегу, інакше всередині JavaScript тощо. + +Latte — це перша і єдина система шаблонів у PHP, яка має контекстно-чутливе екранування. Таким чином, вона представляє єдину дійсно безпечну систему шаблонів. + +*І ще одна приємна причина*: Завдяки тому, що Latte розуміє HTML, він пропонує інші дуже приємні можливості. Наприклад, [n:атрибути |syntax#n:атрибути]. Або здатність [перевіряти посилання |safety-first#Перевірка посилань]. І багато інших. + + +Що таке екранування? +-------------------- + +Екранування — це процес, який полягає в заміні символів зі спеціальним значенням відповідними послідовностями при вставці одного рядка в інший, щоб запобігти небажаним явищам або помилкам. Наприклад, коли ми вставляємо рядок в HTML-текст, в якому символ `<` має особливе значення, оскільки позначає початок тегу, ми замінюємо його відповідною послідовністю, якою є HTML-сутність `<`. Завдяки цьому браузер правильно відобразить символ `<`. + +Простим прикладом екранування безпосередньо при написанні коду в PHP є вставка лапки в рядок, коли перед нею ми пишемо зворотний слеш. + +Детальніше екранування розглядаємо в розділі [Як захиститися від XSS |safety-first#Як захиститися від XSS]. + + +Чи можна в Latte виконати запит до бази даних із шаблону? +--------------------------------------------------------- + +У шаблонах можна працювати з об'єктами, які до них передає програміст. Якщо програміст хоче, він може передати в шаблон об'єкт бази даних і виконати над ним запит. Якщо у нього є такий намір, немає причин йому в цьому заважати. + +Інша ситуація виникає, якщо ви хочете надати можливість редагувати шаблони клієнтам або зовнішнім кодерам. У такому випадку ви точно не хочете, щоб вони мали доступ до бази даних. Звичайно, ви не передасте об'єкт бази даних шаблону, але що, якщо до неї можна отримати доступ через інший об'єкт? Рішенням є [режим sandbox |sandbox], який дозволяє визначити, які методи можна викликати в шаблонах. Завдяки цьому вам не потрібно турбуватися про порушення безпеки. + + +Які основні відмінності між системами шаблонів, такими як Latte, Twig та Blade? +------------------------------------------------------------------------------- + +Відмінності між системами шаблонів Latte, Twig та Blade полягають головним чином у синтаксисі, безпеці та способі інтеграції у фреймворки + +- Latte: використовує синтаксис мови PHP, що полегшує навчання та використання. Надає найкращий захист від XSS-атак завдяки контекстно-чутливому екрануванню. +- Twig: використовує синтаксис, схожий на Python, який відрізняється від PHP. Екранує без розрізнення контексту. Добре інтегрований у фреймворк Symfony. +- Blade: використовує суміш PHP та власного синтаксису. Екранує без розрізнення контексту. Тісно інтегрований з функціями та екосистемою Laravel. + + +Чи вигідно компаніям використовувати систему шаблонів? +------------------------------------------------------ + +Перш за все, витрати, пов'язані з навчанням, використанням та загальною користю, значно відрізняються залежно від системи. Система шаблонів Latte, завдяки тому, що використовує синтаксис PHP, дуже спрощує навчання для програмістів, вже знайомих з цією мовою. Зазвичай потрібно кілька годин, щоб програміст достатньо ознайомився з Latte. Таким чином, вона знижує витрати на навчання. Водночас прискорює освоєння технології та, перш за все, ефективність при щоденному використанні. + +Крім того, Latte забезпечує високий рівень захисту від вразливості XSS завдяки унікальній технології контекстно-чутливого екранування. Цей захист є ключовим для забезпечення безпеки веб-додатків та мінімізації ризику атак, які могли б загрожувати користувачам або корпоративним даним. Захист безпеки веб-додатків також важливий для підтримки доброї репутації компанії. Проблеми з безпекою можуть спричинити втрату довіри з боку клієнтів та пошкодити репутацію компанії на ринку. + +Використання Latte також знижує загальні витрати на розробку та підтримку додатка, полегшуючи обидва процеси. Тому використання системи шаблонів однозначно вигідно. + + +Чи впливає Latte на продуктивність веб-додатків? +------------------------------------------------ + +Хоча шаблони Latte обробляються швидко, цей аспект насправді не має значення. Причина в тому, що парсинг файлів відбувається лише один раз при першому відображенні. Потім вони компілюються в PHP-код, зберігаються на диску і запускаються при кожному наступному запиті, без необхідності повторної компіляції. + +Це спосіб роботи в робочому середовищі. Під час розробки шаблони Latte перекомпілюються щоразу, коли змінюється їх вміст, щоб розробник завжди бачив актуальний вигляд. diff --git a/mail/bg/@home.texy b/mail/bg/@home.texy index e9cf39c551..9c9eecc237 100644 --- a/mail/bg/@home.texy +++ b/mail/bg/@home.texy @@ -1,60 +1,60 @@ -Изпращане на имейли -******************* +Nette Mail +**********
                                                                                                                                        -Ще изпращате ли имейли, например бюлетини или потвърждения на поръчки? Рамката Nette предоставя необходимите инструменти с много лесен за използване API. Ние ви показваме: +Предстои ви да изпращате имейли, например бюлетини или потвърждения на поръчки? Nette Framework предоставя необходимите инструменти с много приятен API. Ще ви покажем: -- как да създадете имейл, включващ прикачени файлове -- как да изпратите -- как да комбинирате имейли и шаблони +- как да създадете имейл, включително прикачени файлове +- как да го изпратите +- как да свържете имейли и шаблони
                                                                                                                                        -Настройка .[#toc-installation] -============================== +Инсталация +========== -Изтеглете и инсталирайте пакета с помощта на [Composer |best-practices:composer]: +Можете да изтеглите и инсталирате библиотеката с помощта на инструмента [Composer|best-practices:composer]: ```shell composer require nette/mail ``` -Създаване на имейли .[#toc-creating-emails] -=========================================== +Създаване на имейл +================== -Електронната поща е обект на [api:Nette\Mail\Message]: +Имейлът е обект от класа [api:Nette\Mail\Message]. Нека го създадем по следния начин: ```php $mail = new Nette\Mail\Message; -$mail->setFrom('John ') - ->addTo('peter@example.com') - ->addTo('jack@example.com') - ->setSubject('Подтверждение заказа') - ->setBody("Здравствуйте. Ваш заказ принят."); +$mail->setFrom('Franta ') + ->addTo('petr@example.com') + ->addTo('jirka@example.com') + ->setSubject('Потвърждение на поръчката') + ->setBody("Здравейте,\nвашата поръчка е приета."); ``` -Всички параметри трябва да са кодирани в UTF-8. +Всички въведени параметри трябва да са в UTF-8. -Освен че можете да посочите получатели по метода `addTo()`, можете да посочите и получател на копие по метода `addCc()`, или получател на скрито копие по метода `addBcc()`. Всички тези методи, включително `setFrom()`, приемат дестинацията по три начина: +Освен посочването на получател с метода `addTo()`, можете да посочите и получател на копие `addCc()` или получател на скрито копие `addBcc()`. Във всички тези методи, включително `setFrom()`, можем да запишем адресата по три начина: ```php -$mail->setFrom('john.doe@example.com'); -$mail->setFrom('john.doe@example.com', 'John Doe'); -$mail->setFrom('John Doe '); +$mail->setFrom('franta@example.com'); +$mail->setFrom('franta@example.com', 'Franta'); +$mail->setFrom('Franta '); ``` -Тялото на имейла, написано в HTML формат, се изпраща чрез метода `setHtmlBody()`: +Тялото на имейла, написано в HTML, се предава чрез метода `setHtmlBody()`: ```php -$mail->setHtmlBody('

                                                                                                                                        Hello,

                                                                                                                                        Ваш заказ принят.

                                                                                                                                        '); +$mail->setHtmlBody('

                                                                                                                                        Здравейте,

                                                                                                                                        вашата поръчка е приета.

                                                                                                                                        '); ``` -Не е необходимо да създавате текстов вариант на имейла, Nette ще го генерира автоматично за вас. А ако нямате тема, тя ще бъде взета от ``. +Не е необходимо да създавате текстова алтернатива, Nette ще я генерира автоматично за вас. И ако имейлът няма зададена тема, той ще се опита да я вземе от елемента `<title>`. -Изображенията могат да бъдат вмъкнати много лесно в HTML тялото на имейла. Просто подайте пътя, по който изображенията се намират физически, като втори параметър и Nette автоматично ще ги включи в имейла: +Също така е изключително лесно да вмъквате изображения в тялото на HTML. Просто предайте пътя, където изображенията се намират физически, като втори параметър и Nette автоматично ще ги включи в имейла: ```php // автоматично добавя /path/to/images/background.gif към имейла @@ -64,34 +64,35 @@ $mail->setHtmlBody( ); ``` -Алгоритъмът за вграждане на изображения поддържа следните шаблони: `<img src=...>`, `<body background=...>`, `url(...)` в атрибута на HTML `style` и специалния синтаксис `[[...]]`. +Алгоритъмът, който вмъква изображения, търси следните шаблони: `<img src=...>`, `<body background=...>`, `url(...)` вътре в HTML атрибута `style` и специалния синтаксис `[[...]]`. -Възможно ли е изпращането на имейли да стане още по-лесно? +Може ли изпращането на имейли да бъде още по-лесно? -Имейлите са като пощенски картички. Никога не изпращайте пароли или други идентификационни данни по имейл. .[tip] +.[tip] +Имейлът е като пощенска картичка. Никога не изпращайте пароли или други данни за достъп по имейл. -Прикачени файлове .[#toc-attachments] -------------------------------------- +Прикачени файлове +----------------- -Разбира се, можете да прикачвате прикачени файлове към имейли. Използвайте командата `addAttachment(string $file, string $content = null, string $contentType = null)`. +Разбира се, към имейла могат да се добавят прикачени файлове. За тази цел се използва методът `addAttachment(string $file, ?string $content = null, ?string $contentType = null)`. ```php -//вмъкнете файла /path/to/example.zip в имейла като example.zip +// вмъква в имейла файл /path/to/example.zip под името example.zip $mail->addAttachment('/path/to/example.zip'); -//вмъкнете файла /path/to/example.zip в имейла като info.zip +// вмъква в имейла файл /path/to/example.zip с име info.zip $mail->addAttachment('info.zip', file_get_contents('/path/to/example.zip')); -//вмъкнете файла example.txt с текста "Здравей, Джон! +// вмъква в имейла файл example.txt със съдържание "Hello John!" $mail->addAttachment('example.txt', 'Hello John!'); ``` -Шаблони .[#toc-templates] -------------------------- +Шаблони +------- -Ако изпращате имейли в HTML формат, е чудесна идея да ги напишете в системата за шаблони [Latte |latte:]. Как да го направим? +Ако изпращате HTML имейли, е съвсем естествено да ги пишете в системата за шаблони [Latte|latte:]. Как да го направите? ```php $latte = new Latte\Engine; @@ -100,8 +101,8 @@ $params = [ ]; $mail = new Nette\Mail\Message; -$mail->setFrom('John <john@example.com>') - ->addTo('jack@example.com') +$mail->setFrom('Franta <franta@example.com>') + ->addTo('petr@example.com') ->setHtmlBody( $latte->renderToString('/path/to/email.latte', $params), '/path/to/images', @@ -114,7 +115,7 @@ $mail->setFrom('John <john@example.com>') <html> <head> <meta charset="utf-8"> - <title>Подтверждение заказа + Потвърждение на поръчката -

                                                                                                                                        Здравствуйте!

                                                                                                                                        +

                                                                                                                                        Здравейте,

                                                                                                                                        -

                                                                                                                                        Ваш заказ под номером {$orderId} был принят.

                                                                                                                                        +

                                                                                                                                        Вашата поръчка номер {$orderId} е приета.

                                                                                                                                        ``` -Nette автоматично вмъква всички изображения, задава темата в зависимост от елемента `` и генерира алтернативен текст за тялото на HTML. +Nette автоматично ще вмъкне всички изображения, ще зададе темата според елемента `<title>` и ще генерира текстова алтернатива на HTML. -Използване в приложението Nette .[#toc-using-in-nette-application] ------------------------------------------------------------------- +Използване в Nette Application +------------------------------ -Ако използвате електронна поща заедно с Nette Application, т.е. презентатори, може да искате да създадете връзки в шаблоните, като използвате атрибута `n:href` или тага `{link}`. Latte не ги познава по принцип, но те са много лесни за добавяне. Създаването на връзки може да се обработва от обекта `Nette\Application\LinkGenerator`, който получавате чрез предаването му с помощта на [вграждане на зависимости |dependency-injection:passing-dependencies]. +Ако използвате имейли заедно с Nette Application, т.е. с презентери, може да искате да създавате връзки в шаблоните с помощта на атрибута `n:href` или тага `{link}`. Latte не ги познава по подразбиране, но е много лесно да ги добавите. Обектът `Nette\Application\LinkGenerator` може да създава връзки, до които можете да получите достъп, като го предадете чрез [dependency injection |dependency-injection:passing-dependencies]: ```php use Nette; @@ -168,38 +169,38 @@ class MailSender } ``` -В шаблон връзката се създава както в нормален шаблон. Всички връзки, създадени с LinkGenerator, са абсолютни: +След това създаваме връзки в шаблона, както сме свикнали. Всички връзки, създадени чрез LinkGenerator, ще бъдат абсолютни. ```latte -<a n:href="Presenter:action">Link</a> +<a n:href="Presenter:action">Връзка</a> ``` -Изпращане на имейли .[#toc-sending-emails] -========================================== +Изпращане на имейл +================== -Mailer е класът, който отговаря за изпращането на имейли. Той реализира интерфейса [api:Nette\Mail\Mailer] и предлага няколко готови пощенски програми, които ще представим. +Mailer е клас, който осигурява изпращането на имейли. Той имплементира интерфейса [api:Nette\Mail\Mailer] и има няколко предварително подготвени мейлъра, които ще представим. -Рамката автоматично добавя услугата `Nette\Mail\Mailer`, [базирана на конфигурация, |#Configuring] към контейнера DI, който получавате, като го предавате чрез [инжектиране на зависимости |dependency-injection:passing-dependencies]. +Framework автоматично добавя сървис от тип `Nette\Mail\Mailer` към DI контейнера, изграден въз основа на [конфигурацията |#Конфигурация], до който можете да получите достъп, като го предадете чрез [dependency injection |dependency-injection:passing-dependencies]. -SendmailMailer .[#toc-sendmailmailer] -------------------------------------- +SendmailMailer +-------------- -По подразбиране се използва SendmailMailer, който използва функцията на PHP [php:mail]. Пример за използване: +Мейлърът по подразбиране е SendmailMailer, който използва PHP функцията [php:mail]. Пример за използване: ```php $mailer = new Nette\Mail\SendmailMailer; $mailer->send($mail); ``` -Ако искате да зададете `returnPath`, но сървърът все пак го презаписва, използвайте `$mailer->commandArgs = '-fmy@email.com'`. +Ако искате да зададете `returnPath` и сървърът ви продължава да го презаписва, използвайте `$mailer->commandArgs = '-fMuj@email.cz'`. -SmtpMailer .[#toc-smtpmailer] ------------------------------ +SmtpMailer +---------- -За изпращане на поща чрез SMTP сървър използвайте `SmtpMailer`. +За изпращане на поща чрез SMTP сървър се използва `SmtpMailer`. ```php $mailer = new Nette\Mail\SmtpMailer( @@ -211,19 +212,19 @@ $mailer = new Nette\Mail\SmtpMailer( $mailer->send($mail); ``` -Следните допълнителни параметри могат да бъдат предадени на конструктора: +На конструктора могат да бъдат предадени следните допълнителни параметри: -* `port` - ако не е посочена, ще се използва стойността по подразбиране от 25 или 465 за `ssl`. -* `timeout` - таймаут за SMTP връзка -* `persistent` - използвайте постоянна връзка -* `clientHost` - възлагане на клиента -* `streamOptions` - позволява ви да зададете "SSL контекстни опции" за:https://www.php.net/manual/ru/context.ssl.php връзката +* `port` - ако не е зададен, се използва порт по подразбиране 25 или 465 за `ssl` +* `timeout` - timeout за SMTP връзка +* `persistent` - използване на постоянна връзка +* `clientHost` - настройка на хедъра Host на клиента +* `streamOptions` - позволява настройка на "SSL context options":https://www.php.net/manual/en/context.ssl.php за връзката -FallbackMailer .[#toc-fallbackmailer] -------------------------------------- +FallbackMailer +-------------- -Той не изпраща имейли, а ги изпраща чрез набор от списъци за изпращане. Ако едно изпращане е неуспешно, то се опитва отново да изпрати следващото. Ако последният не успее, се започва отначало с първия. +Той не изпраща имейли директно, а посредничи при изпращането чрез набор от мейлъри. В случай, че един мейлър се провали, той повтаря опита със следващия. Ако и последният се провали, започва отново от първия. ```php $mailer = new Nette\Mail\FallbackMailer([ @@ -234,16 +235,15 @@ $mailer = new Nette\Mail\FallbackMailer([ $mailer->send($mail); ``` -Другите параметри в конструктора включват броя на повторенията и времето за изчакване в милисекунди. +Като допълнителни параметри в конструктора можем да посочим броя на повторенията и времето за изчакване в милисекунди. -DKIM .[#toc-dkim] -================= +DKIM +==== -DKIM (DomainKeys Identified Mail) е технология за сигурна електронна поща, която също така помага за откриване на фалшиви съобщения. Изпратеното съобщение се подписва с частния ключ на домейна на изпращача и този подпис се съхранява в заглавието на имейла. -Сървърът на получателя сравнява този подпис с публичния ключ, съхранен в DNS записите на домейна. С проверката на подписа може да се докаже, че имейлът действително е дошъл от домейна на подателя и че съобщението не е било модифицирано по време на предаването. +DKIM (DomainKeys Identified Mail) е технология за повишаване на достоверността на имейлите, която също помага за разкриването на фалшифицирани съобщения. Изпратеното съобщение се подписва с частния ключ на домейна на изпращача и този подпис се съхранява в хедъра на имейла. Сървърът на получателя сравнява този подпис с публичния ключ, съхранен в DNS записите на домейна. Съответствието на подписа доказва, че имейлът действително произхожда от домейна на изпращача и че не е имало промяна в съобщението по време на преноса му. -Можете да [конфигурирате |#Configuring] пощенския оператор за подписване на имейли в [конфигурацията |#Configuring]. Ако не използвате налагане на зависимости, тя се задава по следния начин: +Можете да настроите подписването на имейли за мейлъра директно в [конфигурацията |#Конфигурация]. Ако не използвате dependency injection, се използва по следния начин: ```php $signer = new Nette\Mail\DkimSigner( @@ -259,16 +259,16 @@ $mailer->send($mail); ``` -Конфигурация .[#toc-configuring] -================================ +Конфигурация +============ -Преглед на конфигурационните настройки за Nette Mail. Ако не използвате цялата рамка, а само тази библиотека, прочетете [Как да изтеглите конфигурационния файл |bootstrap:]. +Преглед на конфигурационните опции за Nette Mail. Ако не използвате целия framework, а само тази библиотека, прочетете [как да заредите конфигурацията |bootstrap:]. -По подразбиране за изпращане на имейли се използва пощенският оператор `Nette\Mail\SendmailMailer`, който вече не може да се конфигурира. Можем обаче да го превключим на `Nette\Mail\SmtpMailer`: +За изпращане на имейли стандартно се използва мейлърът `Nette\Mail\SendmailMailer`, който не се конфигурира допълнително. Можем обаче да го превключим на `Nette\Mail\SmtpMailer`: ```neon mail: - # използвайте SmtpMailer + # използва SmtpMailer smtp: true # (bool) по подразбиране е false host: ... # (string) @@ -276,23 +276,23 @@ mail: username: ... # (string) password: ... # (string) timeout: ... # (int) - encryption: ... # (ssl|tls|null) по подразбиране е null - clientHost: ... # (string) по подразбиране $_SERVER['HTTP_HOST'] + encryption: ... # (ssl|tls|null) по подразбиране е null (има псевдоним 'secure') + clientHost: ... # (string) по подразбиране е $_SERVER['HTTP_HOST'] persistent: ... # (bool) по подразбиране е false - # контекст за свързване към SMTP сървъра, по подразбиране stream_context_get_default() + # контекст за връзка със SMTP сървъра, по подразбиране е stream_context_get_default() context: - ssl: # всички опции на https://www.php.net/manual/en/context.ssl.php + ssl: # преглед на опциите на https://www.php.net/manual/en/context.ssl.php allow_self_signed: ... ... - http: # всички опции в https://www.php.net/manual/en/context.http.php - заглавие: ... + http: # преглед на опциите на https://www.php.net/manual/en/context.http.php + header: ... ... ``` -Можете да деактивирате удостоверяването на SSL сертификат, като използвате опцията `context › ssl › verify_peer: false`. Силно се препоръчва **да не правите това**, тъй като това ще направи приложението уязвимо. Вместо това "добавете сертификати в хранилището за доверие":https://www.php.net/manual/en/openssl.configuration.php. +С помощта на опцията `context › ssl › verify_peer: false` може да се изключи проверката на SSL сертификати. **Силно не препоръчваме** да правите това, защото приложението ще стане уязвимо. Вместо това "добавете сертификати в хранилището":https://www.php.net/manual/en/openssl.configuration.php. -За да повишим доверието, можем да подписваме имейли с помощта на [технологията DKIM |https://blog.nette.org/bg/podpisvane-na-imejli-s-dkim]: +За повишаване на достоверността можем да подписваме имейлите с помощта на [технологията DKIM |https://blog.nette.org/bg/sign-emails-with-dkim]: ```neon mail: @@ -304,4 +304,12 @@ mail: ``` -{{leftbar: nette:@menu-topics}} +DI Сървиси +========== + +Тези сървиси се добавят към DI контейнера: + +| Име | Тип | Описание +|----------------------------------------------------- +| `mail.mailer` | [api:Nette\Mail\Mailer] | [клас, изпращащ имейли |#Изпращане на имейл] +| `mail.signer` | [api:Nette\Mail\Signer] | [DKIM подписване |#DKIM] diff --git a/mail/bg/@meta.texy b/mail/bg/@meta.texy new file mode 100644 index 0000000000..794cbc8522 --- /dev/null +++ b/mail/bg/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Документация на Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/mail/cs/@home.texy b/mail/cs/@home.texy index 9f6c8e3033..c2db2cff9a 100644 --- a/mail/cs/@home.texy +++ b/mail/cs/@home.texy @@ -1,5 +1,5 @@ -Odesílání e-mailů -***************** +Nette Mail +********** <div class=perex> @@ -68,13 +68,14 @@ Algoritmus vkládající obrázky vyhledává tyto vzory: `<img src=...>`, `<bod Může být odesílání e-mailů ještě jednodušší? -E-mail je něco jako pohlednice. Nikdy e-mailem neposílejte hesla ani jiné přístupové údaje. .[tip] +.[tip] +E-mail je něco jako pohlednice. Nikdy e-mailem neposílejte hesla ani jiné přístupové údaje. Přílohy ------- -Do e-mailu lze samozřejmě vkládat přílohy. Slouží k tomu metoda `addAttachment(string $file, string $content = null, string $contentType = null)`. +Do e-mailu lze samozřejmě vkládat přílohy. Slouží k tomu metoda `addAttachment(string $file, ?string $content = null, ?string $contentType = null)`. ```php // vloží do e-mailu soubor /path/to/example.zip pod názvem example.zip @@ -240,10 +241,9 @@ Jako další parametry v konstruktoru můžeme uvést počet opakování a čeka DKIM ==== -DKIM (DomainKeys Identified Mail) je technologie pro zvýšení důvěryhodnosti e-mailů, která také napomáhá odhalení podvržených zpráv. Odeslaná zpráva je podepsána privátním klíčem domény odesílatele a tento podpis je uložen v hlavičce e-mailu. -Server příjemce porovná tento podpis s veřejným klíčem uloženým v DNS záznamech domény. Tím, že podpis odpovídá, je prokázáno, že e-mail skutečně pochází z odesílatelovy domény a že během přenosu zprávy nedošlo k její úpravě. +DKIM (DomainKeys Identified Mail) je technologie pro zvýšení důvěryhodnosti e-mailů, která také napomáhá odhalení podvržených zpráv. Odeslaná zpráva je podepsána privátním klíčem domény odesílatele a tento podpis je uložen v hlavičce e-mailu. Server příjemce porovná tento podpis s veřejným klíčem uloženým v DNS záznamech domény. Tím, že podpis odpovídá, je prokázáno, že e-mail skutečně pochází z odesílatelovy domény a že během přenosu zprávy nedošlo k její úpravě. -Podepisování e-mailů můžete maileru nastavit přímo v [konfiguraci|#Konfigurace]. Pokud nepoužíváte dependency injection, používá se tímto způsobem: +Podepisování e-mailů můžete maileru nastavit přímo v [konfiguraci |#Konfigurace]. Pokud nepoužíváte dependency injection, používá se tímto způsobem: ```php $signer = new Nette\Mail\DkimSigner( @@ -304,4 +304,12 @@ mail: ``` -{{leftbar: nette:@menu-topics}} +Služby DI +========= + +Tyto služby se přidávají do DI kontejneru: + +| Název | Typ | Popis +|----------------------------------------------------- +| `mail.mailer` | [api:Nette\Mail\Mailer] | [třída odesílající e-maily |#Odeslání e-mailu] +| `mail.signer` | [api:Nette\Mail\Signer] | [DKIM podepisování |#DKIM] diff --git a/mail/cs/@meta.texy b/mail/cs/@meta.texy new file mode 100644 index 0000000000..08edde785b --- /dev/null +++ b/mail/cs/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Dokumentace}} +{{leftbar: nette:@menu-topics}} diff --git a/mail/de/@home.texy b/mail/de/@home.texy index 40fab04e85..b0596dca44 100644 --- a/mail/de/@home.texy +++ b/mail/de/@home.texy @@ -1,97 +1,98 @@ -Versenden von E-Mails -********************* +Nette Mail +********** <div class=perex> -Wollen Sie E-Mails wie Newsletter oder Auftragsbestätigungen versenden? Nette Framework bietet die notwendigen Werkzeuge mit einer sehr schönen API. Wir werden es zeigen: +Möchten Sie E-Mails versenden, zum Beispiel Newsletter oder Bestellbestätigungen? Das Nette Framework bietet die notwendigen Werkzeuge mit einer sehr angenehmen API. Wir zeigen Ihnen: -- wie man eine E-Mail erstellt, einschließlich Anhängen +- wie man eine E-Mail inklusive Anhängen erstellt - wie man sie versendet -- wie man E-Mails und Vorlagen kombiniert +- wie man E-Mails und Templates verbindet </div> -Einrichtung .[#toc-installation] -================================ +Installation +============ -Laden Sie das Paket herunter und installieren Sie es mit [Composer |best-practices:composer]: +Sie können die Bibliothek mit dem Werkzeug [Composer|best-practices:composer] herunterladen und installieren: ```shell composer require nette/mail ``` -Emails erstellen .[#toc-creating-emails] -======================================== +E-Mail erstellen +================ -E-Mail ist ein [api:Nette\Mail\Message] Objekt: +Eine E-Mail ist ein Objekt der Klasse [api:Nette\Mail\Message]. Wir erstellen sie zum Beispiel so: ```php $mail = new Nette\Mail\Message; -$mail->setFrom('John <john@example.com>') - ->addTo('peter@example.com') - ->addTo('jack@example.com') - ->setSubject('Order Confirmation') - ->setBody("Hello, Your order has been accepted."); +$mail->setFrom('Franta <franta@example.com>') + ->addTo('petr@example.com') + ->addTo('jirka@example.com') + ->setSubject('Bestellbestätigung') + ->setBody("Guten Tag,\nIhre Bestellung wurde angenommen."); ``` -Alle Parameter müssen in UTF-8 kodiert sein. +Alle eingegebenen Parameter müssen in UTF-8 sein. -Neben der Angabe von Empfängern mit der Methode `addTo()` können Sie auch den Empfänger einer Kopie mit `addCc()` oder den Empfänger einer Blindkopie mit `addBcc()` angeben. Alle diese Methoden, einschließlich `setFrom()`, akzeptieren den Adressaten auf drei Arten: +Neben der Angabe des Empfängers mit der Methode `addTo()` kann auch ein Kopie-Empfänger `addCc()` oder ein Blindkopie-Empfänger `addBcc()` angegeben werden. In all diesen Methoden, einschließlich `setFrom()`, können wir den Adressaten auf drei Arten schreiben: ```php -$mail->setFrom('john.doe@example.com'); -$mail->setFrom('john.doe@example.com', 'John Doe'); -$mail->setFrom('John Doe <john.doe@example.com>'); +$mail->setFrom('franta@example.com'); +$mail->setFrom('franta@example.com', 'Franta'); +$mail->setFrom('Franta <franta@example.com>'); ``` -Der Textkörper einer in HTML geschriebenen E-Mail wird mit der Methode `setHtmlBody()` übergeben: +Der in HTML geschriebene E-Mail-Textkörper wird mit der Methode `setHtmlBody()` übergeben: ```php -$mail->setHtmlBody('<p>Hello,</p><p>Your order has been accepted.</p>'); +$mail->setHtmlBody('<p>Guten Tag,</p><p>Ihre Bestellung wurde angenommen.</p>'); ``` -Sie müssen keine Textalternative erstellen, Nette generiert sie automatisch für Sie. Und wenn die E-Mail keinen Betreff hat, wird dieser aus dem `<title>` Element. +Sie müssen keine Textalternative erstellen, Nette generiert sie automatisch für Sie. Und wenn die E-Mail keinen Betreff hat, versucht Nette, ihn aus dem `<title>`-Element zu übernehmen. -Auch Bilder lassen sich sehr einfach in den HTML-Text einer E-Mail einfügen. Übergeben Sie einfach den Pfad, in dem sich die Bilder physisch befinden, als zweiten Parameter, und Nette fügt sie automatisch in die E-Mail ein: +Bilder können auch außergewöhnlich einfach in den HTML-Textkörper eingefügt werden. Es genügt, als zweiten Parameter den Pfad anzugeben, wo sich die Bilder physisch befinden, und Nette fügt sie automatisch in die E-Mail ein: ```php -// fügt automatisch /path/to/images/background.gif in die E-Mail ein +// fügt automatisch /path/to/images/background.gif zur E-Mail hinzu $mail->setHtmlBody( '<b>Hello</b> <img src="background.gif">', '/path/to/images', ); ``` -Der Algorithmus zum Einbetten von Bildern unterstützt die folgenden Muster: `<img src=...>`, `<body background=...>`, `url(...)` innerhalb des HTML-Attributs `style` und die spezielle Syntax `[[...]]`. +Der Algorithmus, der Bilder einfügt, sucht nach diesen Mustern: `<img src=...>`, `<body background=...>`, `url(...)` innerhalb des HTML-Attributs `style` und der speziellen Syntax `[[...]]`. -Kann das Versenden von E-Mails noch einfacher sein? +Kann das Senden von E-Mails noch einfacher sein? -E-Mails sind wie Postkarten. Senden Sie niemals Passwörter oder andere Anmeldeinformationen per E-Mail. .[tip] +.[tip] +Eine E-Mail ist wie eine Postkarte. Senden Sie niemals Passwörter oder andere Zugangsdaten per E-Mail. -Anhänge .[#toc-attachments] ---------------------------- +Anhänge +------- -Sie können natürlich auch Anhänge an E-Mails anhängen. Verwenden Sie dazu die `addAttachment(string $file, string $content = null, string $contentType = null)`. +Natürlich können Anhänge in die E-Mail eingefügt werden. Dazu dient die Methode `addAttachment(string $file, ?string $content = null, ?string $contentType = null)`. ```php -// fügt die Datei /path/to/example.zip in die E-Mail unter dem Namen example.zip ein +// fügt die Datei /path/to/example.zip unter dem Namen example.zip in die E-Mail ein $mail->addAttachment('/path/to/example.zip'); -// fügt die Datei /path/to/example.zip in die E-Mail mit dem Namen info.zip ein +// fügt die Datei /path/to/example.zip mit dem Namen info.zip in die E-Mail ein $mail->addAttachment('info.zip', file_get_contents('/path/to/example.zip')); -// Hängt den Inhalt der neuen Datei example.txt an "Hello John!" +// fügt die Datei example.txt mit dem Inhalt "Hello John!" in die E-Mail ein $mail->addAttachment('example.txt', 'Hello John!'); ``` -Schablonen .[#toc-templates] ----------------------------- +Templates +--------- -Wenn Sie HTML-E-Mails versenden, ist es eine gute Idee, diese im [Latte-Vorlagensystem |latte:] zu schreiben. Wie man das macht? +Wenn Sie HTML-E-Mails senden, bietet es sich an, diese im Templating-System [Latte|latte:] zu schreiben. Wie geht das? ```php $latte = new Latte\Engine; @@ -100,21 +101,21 @@ $params = [ ]; $mail = new Nette\Mail\Message; -$mail->setFrom('John <john@example.com>') - ->addTo('jack@example.com') +$mail->setFrom('Franta <franta@example.com>') + ->addTo('petr@example.com') ->setHtmlBody( $latte->renderToString('/path/to/email.latte', $params), '/path/to/images', ); ``` -Datei `email.latte`: +Die Datei `email.latte`: ```latte <html> <head> <meta charset="utf-8"> - <title>Order Confirmation + Bestellbestätigung -

                                                                                                                                        Hello,

                                                                                                                                        +

                                                                                                                                        Guten Tag,

                                                                                                                                        -

                                                                                                                                        Your order number {$orderId} has been accepted.

                                                                                                                                        +

                                                                                                                                        Ihre Bestellung Nummer {$orderId} wurde angenommen.

                                                                                                                                        ``` -Nette fügt automatisch alle Bilder ein, setzt den Betreff entsprechend dem `` Element ein und erzeugt einen Alternativtext für den HTML-Body. +Nette fügt automatisch alle Bilder ein, setzt den Betreff gemäß dem `<title>`-Element und generiert eine Textalternative zum HTML. -Verwendung in der Nette-Anwendung .[#toc-using-in-nette-application] --------------------------------------------------------------------- +Verwendung in Nette Application +------------------------------- -Wenn Sie E-Mails zusammen mit Nette Application verwenden, z.B. Moderatoren, möchten Sie vielleicht Links in Vorlagen mit dem `n:href` Attribut oder dem `{link}` Tag erstellen. Latte kennt diese grundsätzlich nicht, aber es ist sehr einfach, sie hinzuzufügen. Das Erstellen von Links kann das Objekt `Nette\Application\LinkGenerator` tun, das Sie durch Übergabe mittels [Dependency Injection |dependency-injection:passing-dependencies] erhalten. +Wenn Sie E-Mails zusammen mit Nette Application, d.h. mit Presentern, verwenden, möchten Sie möglicherweise Links in Templates mit dem Attribut `n:href` oder dem Tag `{link}` erstellen. Latte kennt diese standardmäßig nicht, aber es ist sehr einfach, sie hinzuzufügen. Das Erstellen von Links übernimmt das Objekt `Nette\Application\LinkGenerator`, das Sie erhalten, indem Sie es sich mittels [Dependency Injection |dependency-injection:passing-dependencies] übergeben lassen: ```php use Nette; @@ -168,38 +169,38 @@ class MailSender } ``` -In der Vorlage wird der Link wie in einer normalen Vorlage erstellt. Alle über LinkGenerator erstellten Links sind absolut: +Im Template erstellen wir dann Links wie gewohnt. Alle über LinkGenerator erstellten Links sind absolut. ```latte <a n:href="Presenter:action">Link</a> ``` -Emails verschicken .[#toc-sending-emails] -========================================= +E-Mail senden +============= -Mailer ist die Klasse, die für den Versand von E-Mails zuständig ist. Sie implementiert die Schnittstelle [api:Nette\Mail\Mailer] und es gibt mehrere fertige Mailer, die wir vorstellen werden. +Mailer ist eine Klasse, die für das Senden von E-Mails zuständig ist. Sie implementiert die Schnittstelle [api:Nette\Mail\Mailer] und es stehen mehrere vorbereitete Mailer zur Verfügung, die wir vorstellen werden. -Das Framework fügt dem DI-Container automatisch einen auf der [Konfiguration |#Configuring] basierenden `Nette\Mail\Mailer` -Dienst hinzu, den Sie durch Übergabe mittels [Dependency Injection |dependency-injection:passing-dependencies] erhalten. +Das Framework fügt automatisch einen Dienst vom Typ `Nette\Mail\Mailer` zum DI-Container hinzu, der auf Basis der [#Konfiguration] erstellt wird und den Sie erhalten, indem Sie ihn sich mittels [Dependency Injection |dependency-injection:passing-dependencies] übergeben lassen. -SendmailMailer .[#toc-sendmailmailer] -------------------------------------- +SendmailMailer +-------------- -Der Standard-Mailer ist SendmailMailer, der die PHP-Funktion [php:mail] verwendet. Beispiel für die Verwendung: +Der Standard-Mailer ist SendmailMailer, der die PHP-Funktion [php:mail] verwendet. Anwendungsbeispiel: ```php $mailer = new Nette\Mail\SendmailMailer; $mailer->send($mail); ``` -Wenn Sie `returnPath` setzen wollen und der Server es trotzdem überschreibt, verwenden Sie `$mailer->commandArgs = '-fmy@email.com'`. +Wenn Sie `returnPath` setzen möchten und Ihr Server ihn immer überschreibt, verwenden Sie `$mailer->commandArgs = '-fMeine@email.de'`. -SmtpMailer .[#toc-smtpmailer] ------------------------------ +SmtpMailer +---------- -Um E-Mails über den SMTP-Server zu versenden, verwenden Sie `SmtpMailer`. +Zum Senden von E-Mails über einen SMTP-Server dient `SmtpMailer`. ```php $mailer = new Nette\Mail\SmtpMailer( @@ -211,19 +212,19 @@ $mailer = new Nette\Mail\SmtpMailer( $mailer->send($mail); ``` -Die folgenden zusätzlichen Parameter können an den Konstruktor übergeben werden: +Dem Konstruktor können diese weiteren Parameter übergeben werden: -* `port` - wenn nicht gesetzt, wird der Standardwert 25 oder 465 für `ssl` verwendet -* `timeout` - Timeout für SMTP-Verbindung +* `port` - wenn nicht gesetzt, wird der Standardport 25 oder 465 für `ssl` verwendet +* `timeout` - Timeout für die SMTP-Verbindung * `persistent` - persistente Verbindung verwenden -* `clientHost` - Client-Bezeichnung -* `streamOptions` - ermöglicht die Einstellung von "SSL-Kontextoptionen":https://www.php.net/manual/en/context.ssl.php für die Verbindung +* `clientHost` - Einstellung des Host-Headers des Clients +* `streamOptions` - ermöglicht die Einstellung von "SSL context options":https://www.php.net/manual/en/context.ssl.php für die Verbindung -FallbackMailer .[#toc-fallbackmailer] -------------------------------------- +FallbackMailer +-------------- -Der FallbackMailer versendet keine E-Mails, sondern sendet sie über eine Reihe von Mailern. Wenn ein Mailer fehlschlägt, wiederholt er den Versuch beim nächsten Mailer. Wenn der letzte fehlschlägt, beginnt er wieder mit dem ersten. +Sendet E-Mails nicht direkt, sondern vermittelt den Versand über eine Reihe von Mailern. Wenn ein Mailer fehlschlägt, wiederholt er den Versuch mit dem nächsten. Wenn auch der letzte fehlschlägt, beginnt er wieder beim ersten. ```php $mailer = new Nette\Mail\FallbackMailer([ @@ -234,16 +235,15 @@ $mailer = new Nette\Mail\FallbackMailer([ $mailer->send($mail); ``` -Weitere Parameter im Konstruktor sind die Anzahl der Wiederholungen und die Wartezeit in Millisekunden. +Als weitere Parameter im Konstruktor können wir die Anzahl der Wiederholungen und die Wartezeit in Millisekunden angeben. -DKIM .[#toc-dkim] -================= +DKIM +==== -DKIM (DomainKeys Identified Mail) ist eine vertrauenswürdige E-Mail-Technologie, die auch hilft, gefälschte Nachrichten zu erkennen. Die gesendete Nachricht wird mit dem privaten Schlüssel der Domäne des Absenders signiert, und diese Signatur wird in der Kopfzeile der E-Mail gespeichert. -Der Server des Empfängers vergleicht diese Signatur mit dem öffentlichen Schlüssel, der in den DNS-Einträgen der Domäne gespeichert ist. Durch den Abgleich der Signatur wird nachgewiesen, dass die E-Mail tatsächlich von der Domäne des Absenders stammt und dass die Nachricht während der Übertragung nicht verändert wurde. +DKIM (DomainKeys Identified Mail) ist eine Technologie zur Erhöhung der Vertrauenswürdigkeit von E-Mails, die auch zur Erkennung gefälschter Nachrichten beiträgt. Die gesendete Nachricht wird mit dem privaten Schlüssel der Absenderdomäne signiert und diese Signatur wird im Header der E-Mail gespeichert. Der Empfängerserver vergleicht diese Signatur mit dem öffentlichen Schlüssel, der in den DNS-Einträgen der Domäne gespeichert ist. Wenn die Signatur übereinstimmt, ist nachgewiesen, dass die E-Mail tatsächlich von der Absenderdomäne stammt und während der Übertragung nicht verändert wurde. -Sie können den Mailer in der [Konfiguration |#Configuring] so einrichten, dass er E-Mails signiert. Wenn Sie die Dependency Injection nicht verwenden, wird sie wie folgt eingesetzt: +Sie können das Signieren von E-Mails für den Mailer direkt in der [#Konfiguration] einstellen. Wenn Sie keine Dependency Injection verwenden, wird es auf diese Weise verwendet: ```php $signer = new Nette\Mail\DkimSigner( @@ -259,40 +259,40 @@ $mailer->send($mail); ``` -Konfigurieren von .[#toc-configuring] -===================================== +Konfiguration +============= -Überblick über die Konfigurationsmöglichkeiten für Nette Mail. Wenn Sie nicht das gesamte Framework, sondern nur diese Bibliothek verwenden, lesen Sie [, wie Sie die Konfiguration laden |bootstrap:]. +Übersicht über die Konfigurationsoptionen für Nette Mail. Wenn Sie nicht das gesamte Framework, sondern nur diese Bibliothek verwenden, lesen Sie, [wie die Konfiguration geladen wird|bootstrap:]. -Standardmäßig wird zum Versenden von E-Mails der Mailer `Nette\Mail\SendmailMailer` verwendet, der nicht weiter konfiguriert ist. Wir können ihn jedoch auf `Nette\Mail\SmtpMailer` umstellen: +Zum Senden von E-Mails wird standardmäßig der Mailer `Nette\Mail\SendmailMailer` verwendet, der nicht weiter konfiguriert wird. Wir können ihn jedoch auf `Nette\Mail\SmtpMailer` umschalten: ```neon mail: - # SmtpMailer verwenden - smtp: true # (bool) ist standardmäßig auf false eingestellt + # verwendet SmtpMailer + smtp: true # (bool) Standard ist false host: ... # (string) port: ... # (int) username: ... # (string) - password: ... # (Zeichenkette) + password: ... # (string) timeout: ... # (int) - encryption: ... # (ssl|tls|null) Standardwert ist null (hat den Alias 'secure') - clientHost: ... # (string) Standardwert ist $_SERVER['HTTP_HOST'] - persistent: ... # (bool) Standardwert ist false + encryption: ... # (ssl|tls|null) Standard ist null (hat den Alias 'secure') + clientHost: ... # (string) Standard ist $_SERVER['HTTP_HOST'] + persistent: ... # (bool) Standard ist false - # Kontext für die Verbindung zum SMTP-Server, Standardwert ist stream_context_get_default() - context : - ssl: # alle Optionen unter https://www.php.net/manual/en/context.ssl.php + # Kontext für die Verbindung zum SMTP-Server, Standard ist stream_context_get_default() + context: + ssl: # Übersicht der Optionen auf https://www.php.net/manual/en/context.ssl.php allow_self_signed: ... - ... - http: # alle Optionen unter https://www.php.net/manual/en/context.http.php + # ... + http: # Übersicht der Optionen auf https://www.php.net/manual/en/context.http.php header: ... - ... + # ... ``` -Sie können die Authentifizierung von SSL-Zertifikaten mit der Option `context › ssl › verify_peer: false` deaktivieren. Es wird **strengstens empfohlen**, dies nicht zu tun, da es die Anwendung angreifbar macht. Fügen Sie stattdessen "Zertifikate zum Vertrauensspeicher hinzu":https://www.php.net/manual/en/openssl.configuration.php. +Mit der Option `context › ssl › verify_peer: false` kann die Überprüfung von SSL-Zertifikaten deaktiviert werden. **Wir raten dringend davon ab**, dies zu tun, da die Anwendung dadurch anfällig wird. Fügen Sie stattdessen [Zertifikate zum Trust Store hinzu](https://www.php.net/manual/en/openssl.configuration.php). -Um die Vertrauenswürdigkeit zu erhöhen, können wir E-Mails mit der [DKIM-Technologie |https://blog.nette.org/de/e-mails-mit-dkim-signieren] signieren: +Zur Erhöhung der Vertrauenswürdigkeit können wir E-Mails mit der [DKIM-Technologie |https://blog.nette.org/de/signieren-sie-e-mails-mit-dkim] signieren: ```neon mail: @@ -304,4 +304,12 @@ mail: ``` -{{leftbar: nette:@menu-topics}} +DI-Dienste +========== + +Diese Dienste werden dem DI-Container hinzugefügt: + +| Name | Typ | Beschreibung +|----------------------------------------------------- +| `mail.mailer` | [api:Nette\Mail\Mailer] | [Klasse zum Senden von E-Mails |#E-Mail senden] +| `mail.signer` | [api:Nette\Mail\Signer] | [DKIM-Signierung |#DKIM] diff --git a/mail/de/@meta.texy b/mail/de/@meta.texy new file mode 100644 index 0000000000..2cf383a5cf --- /dev/null +++ b/mail/de/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Dokumentation}} +{{leftbar: nette:@menu-topics}} diff --git a/mail/el/@home.texy b/mail/el/@home.texy index 541f064bac..5e575f6691 100644 --- a/mail/el/@home.texy +++ b/mail/el/@home.texy @@ -1,60 +1,60 @@ -Αποστολή emails -*************** +Nette Mail +********** <div class=perex> -Σκοπεύετε να στέλνετε μηνύματα ηλεκτρονικού ταχυδρομείου, όπως ενημερωτικά δελτία ή επιβεβαιώσεις παραγγελιών; Το Nette Framework παρέχει τα απαραίτητα εργαλεία με ένα πολύ ωραίο API. Θα σας δείξουμε: +Ετοιμάζεστε να στείλετε μηνύματα ηλεκτρονικού ταχυδρομείου, όπως ενημερωτικά δελτία ή επιβεβαιώσεις παραγγελιών; Το Nette Framework παρέχει τα απαραίτητα εργαλεία με ένα πολύ φιλικό API. Θα δείξουμε: -- πώς να δημιουργήσετε ένα email, συμπεριλαμβανομένων των συνημμένων αρχείων +- πώς να δημιουργήσετε ένα μήνυμα ηλεκτρονικού ταχυδρομείου, συμπεριλαμβανομένων των συνημμένων - πώς να το στείλετε -- πώς να συνδυάζετε μηνύματα ηλεκτρονικού ταχυδρομείου και πρότυπα +- πώς να συνδυάσετε μηνύματα ηλεκτρονικού ταχυδρομείου και πρότυπα </div> -Εγκατάσταση .[#toc-installation] -================================ +Εγκατάσταση +=========== -Κατεβάστε και εγκαταστήστε το πακέτο χρησιμοποιώντας το [Composer |best-practices:composer]: +Μπορείτε να κατεβάσετε και να εγκαταστήσετε τη βιβλιοθήκη χρησιμοποιώντας το [Composer|best-practices:composer]: ```shell composer require nette/mail ``` -Composer: Δημιουργία μηνυμάτων ηλεκτρονικού ταχυδρομείου .[#toc-creating-emails] -================================================================================ +Δημιουργία μηνύματος ηλεκτρονικού ταχυδρομείου +============================================== -Το ηλεκτρονικό ταχυδρομείο είναι ένα αντικείμενο [api:Nette\Mail\Message]: +Το μήνυμα ηλεκτρονικού ταχυδρομείου είναι ένα αντικείμενο της κλάσης [api:Nette\Mail\Message]. Ας το δημιουργήσουμε ως εξής: ```php $mail = new Nette\Mail\Message; -$mail->setFrom('John <john@example.com>') - ->addTo('peter@example.com') - ->addTo('jack@example.com') - ->setSubject('Order Confirmation') - ->setBody("Hello, Your order has been accepted."); +$mail->setFrom('Franta <franta@example.com>') + ->addTo('petr@example.com') + ->addTo('jirka@example.com') + ->setSubject('Επιβεβαίωση παραγγελίας') + ->setBody("Γεια σας,\nη παραγγελία σας έγινε αποδεκτή."); ``` -Όλες οι παράμετροι πρέπει να είναι κωδικοποιημένες σε UTF-8. +Όλες οι παρεχόμενες παράμετροι πρέπει να είναι σε UTF-8. -Εκτός από τον προσδιορισμό των παραληπτών με τη μέθοδο `addTo()`, μπορείτε επίσης να προσδιορίσετε τον παραλήπτη της αντιγραφής με τη μέθοδο `addCc()` ή τον παραλήπτη της τυφλής αντιγραφής με τη μέθοδο `addBcc()`. Όλες αυτές οι μέθοδοι, συμπεριλαμβανομένου του `setFrom()`, δέχονται τον παραλήπτη με τρεις τρόπους: +Εκτός από τον καθορισμό του παραλήπτη με τη μέθοδο `addTo()`, μπορείτε επίσης να καθορίσετε έναν παραλήπτη κοινοποίησης (CC) `addCc()` ή έναν παραλήπτη κρυφής κοινοποίησης (BCC) `addBcc()`. Σε όλες αυτές τις μεθόδους, συμπεριλαμβανομένης της `setFrom()`, μπορούμε να γράψουμε τον παραλήπτη με τρεις τρόπους: ```php -$mail->setFrom('john.doe@example.com'); -$mail->setFrom('john.doe@example.com', 'John Doe'); -$mail->setFrom('John Doe <john.doe@example.com>'); +$mail->setFrom('franta@example.com'); +$mail->setFrom('franta@example.com', 'Franta'); +$mail->setFrom('Franta <franta@example.com>'); ``` -Το σώμα ενός ηλεκτρονικού ταχυδρομείου γραμμένου σε HTML περνάει με τη μέθοδο `setHtmlBody()`: +Το σώμα του μηνύματος ηλεκτρονικού ταχυδρομείου γραμμένο σε HTML περνιέται χρησιμοποιώντας τη μέθοδο `setHtmlBody()`: ```php -$mail->setHtmlBody('<p>Hello,</p><p>Your order has been accepted.</p>'); +$mail->setHtmlBody('<p>Γεια σας,</p><p>η παραγγελία σας έγινε αποδεκτή.</p>'); ``` -Η Nette θα το δημιουργήσει αυτόματα για εσάς. Και αν το μήνυμα ηλεκτρονικού ταχυδρομείου δεν έχει οριστεί θέμα, αυτό θα ληφθεί από το αρχείο `<title>` στοιχείο. +Δεν χρειάζεται να δημιουργήσετε μια εναλλακτική λύση κειμένου, το Nette θα τη δημιουργήσει αυτόματα για εσάς. Και αν το μήνυμα ηλεκτρονικού ταχυδρομείου δεν έχει ορισμένο θέμα, θα προσπαθήσει να το πάρει από το στοιχείο `<title>`. -Οι εικόνες μπορούν επίσης να εισαχθούν εξαιρετικά εύκολα στο σώμα HTML ενός email. Απλώς περάστε τη διαδρομή όπου βρίσκονται φυσικά οι εικόνες ως δεύτερη παράμετρο και η Nette θα τις συμπεριλάβει αυτόματα στο email: +Μπορείτε επίσης να εισαγάγετε εικόνες στο σώμα HTML εξαιρετικά εύκολα. Απλώς περάστε τη διαδρομή όπου βρίσκονται φυσικά οι εικόνες ως δεύτερη παράμετρο, και το Nette θα τις συμπεριλάβει αυτόματα στο μήνυμα ηλεκτρονικού ταχυδρομείου: ```php // προσθέτει αυτόματα το /path/to/images/background.gif στο email @@ -64,34 +64,35 @@ $mail->setHtmlBody( ); ``` -Ο αλγόριθμος ενσωμάτωσης εικόνων υποστηρίζει τα ακόλουθα πρότυπα: `<img src=...>`, `<body background=...>`, `url(...)` μέσα στο χαρακτηριστικό HTML `style` και ειδική σύνταξη `[[...]]`. +Ο αλγόριθμος που εισάγει εικόνες αναζητά αυτά τα πρότυπα: `<img src=...>`, `<body background=...>`, `url(...)` μέσα στο attribute HTML `style`, και την ειδική σύνταξη `[[...]]`. -Μπορεί η αποστολή μηνυμάτων ηλεκτρονικού ταχυδρομείου να γίνει ακόμη πιο εύκολη; +Μπορεί η αποστολή μηνυμάτων ηλεκτρονικού ταχυδρομείου να γίνει ακόμα πιο εύκολη; -Τα ηλεκτρονικά μηνύματα είναι σαν τις καρτ ποστάλ. Ποτέ μην στέλνετε κωδικούς πρόσβασης ή άλλα διαπιστευτήρια μέσω ηλεκτρονικού ταχυδρομείου. .[tip] +.[tip] +Ένα μήνυμα ηλεκτρονικού ταχυδρομείου είναι σαν μια ταχυδρομική κάρτα. Ποτέ μην στέλνετε κωδικούς πρόσβασης ή άλλα διαπιστευτήρια μέσω μηνύματος ηλεκτρονικού ταχυδρομείου. -Συνημμένα αρχεία .[#toc-attachments] ------------------------------------- +Συνημμένα +--------- -Μπορείτε, φυσικά, να επισυνάπτετε συνημμένα αρχεία στο ηλεκτρονικό ταχυδρομείο. Χρησιμοποιήστε το `addAttachment(string $file, string $content = null, string $contentType = null)`. +Φυσικά, μπορείτε να επισυνάψετε συνημμένα στο μήνυμα ηλεκτρονικού ταχυδρομείου. Η μέθοδος `addAttachment(string $file, ?string $content = null, ?string $contentType = null)` χρησιμοποιείται για αυτόν τον σκοπό. ```php // εισάγει το αρχείο /path/to/example.zip στο email με το όνομα example.zip $mail->addAttachment('/path/to/example.zip'); -// εισάγει το αρχείο /path/to/example.zip στο email με το όνομα info.zip +// εισάγει το αρχείο /path/to/example.zip με το όνομα info.zip στο email $mail->addAttachment('info.zip', file_get_contents('/path/to/example.zip')); -// επισυνάπτει τα περιεχόμενα του νέου αρχείου example.txt "Hello John!" +// εισάγει το αρχείο example.txt με το περιεχόμενο "Hello John!" στο email $mail->addAttachment('example.txt', 'Hello John!'); ``` -Πρότυπα .[#toc-templates] -------------------------- +Πρότυπα +------- -Εάν στέλνετε μηνύματα ηλεκτρονικού ταχυδρομείου HTML, είναι μια πολύ καλή ιδέα να τα γράψετε στο σύστημα προτύπων [Latte |latte:]. Πώς να το κάνετε; +Αν στέλνετε μηνύματα ηλεκτρονικού ταχυδρομείου HTML, είναι φυσικό να τα γράφετε στο σύστημα προτύπων [Latte|latte:]. Πώς να το κάνετε; ```php $latte = new Latte\Engine; @@ -100,8 +101,8 @@ $params = [ ]; $mail = new Nette\Mail\Message; -$mail->setFrom('John <john@example.com>') - ->addTo('jack@example.com') +$mail->setFrom('Franta <franta@example.com>') + ->addTo('petr@example.com') ->setHtmlBody( $latte->renderToString('/path/to/email.latte', $params), '/path/to/images', @@ -114,7 +115,7 @@ $mail->setFrom('John <john@example.com>') <html> <head> <meta charset="utf-8"> - <title>Order Confirmation + Επιβεβαίωση παραγγελίας -

                                                                                                                                        Hello,

                                                                                                                                        +

                                                                                                                                        Γεια σας,

                                                                                                                                        -

                                                                                                                                        Your order number {$orderId} has been accepted.

                                                                                                                                        +

                                                                                                                                        Η παραγγελία σας με αριθμό {$orderId} έγινε αποδεκτή.

                                                                                                                                        ``` -Η Nette εισάγει αυτόματα όλες τις εικόνες, θέτει το θέμα σύμφωνα με το `` στοιχείο, και δημιουργεί εναλλακτικό κείμενο για το σώμα της HTML. +Το Nette εισάγει αυτόματα όλες τις εικόνες, ορίζει το θέμα σύμφωνα με το στοιχείο `<title>` και δημιουργεί μια εναλλακτική λύση κειμένου για το HTML. -Χρήση στην εφαρμογή Nette .[#toc-using-in-nette-application] ------------------------------------------------------------- +Χρήση στην εφαρμογή Nette +------------------------- -Εάν χρησιμοποιείτε e-mail μαζί με το Nette Application, δηλαδή παρουσιαστές, ίσως θελήσετε να δημιουργήσετε συνδέσμους σε πρότυπα χρησιμοποιώντας το χαρακτηριστικό `n:href` ή την ετικέτα `{link}`. Το Latte βασικά δεν τα γνωρίζει, αλλά είναι πολύ εύκολο να τα προσθέσετε. Η δημιουργία συνδέσμων μπορεί να κάνει το αντικείμενο `Nette\Application\LinkGenerator`, το οποίο παίρνετε περνώντας το με τη χρήση [dependency injection |dependency-injection:passing-dependencies]. +Αν χρησιμοποιείτε μηνύματα ηλεκτρονικού ταχυδρομείου μαζί με την Nette Application, δηλαδή με presenters, μπορεί να θέλετε να δημιουργήσετε συνδέσμους στα πρότυπα χρησιμοποιώντας το attribute `n:href` ή το tag `{link}`. Το Latte δεν τα γνωρίζει από προεπιλογή, αλλά είναι πολύ εύκολο να τα προσθέσετε. Το αντικείμενο `Nette\Application\LinkGenerator` μπορεί να δημιουργήσει συνδέσμους, και μπορείτε να το αποκτήσετε περνώντας το χρησιμοποιώντας [dependency injection |dependency-injection:passing-dependencies]: ```php use Nette; @@ -168,38 +169,38 @@ class MailSender } ``` -Στο πρότυπο, ο σύνδεσμος δημιουργείται όπως σε ένα κανονικό πρότυπο. Όλοι οι σύνδεσμοι που δημιουργούνται μέσω του LinkGenerator είναι απόλυτοι: +Στο πρότυπο, δημιουργούμε συνδέσμους όπως έχουμε συνηθίσει. Όλοι οι σύνδεσμοι που δημιουργούνται μέσω του LinkGenerator θα είναι απόλυτοι. ```latte -<a n:href="Presenter:action">Link</a> +<a n:href="Presenter:action">Σύνδεσμος</a> ``` -Αποστολή Emails .[#toc-sending-emails] -====================================== +Αποστολή μηνύματος ηλεκτρονικού ταχυδρομείου +============================================ -Ο Mailer είναι η κατηγορία που είναι υπεύθυνη για την αποστολή emails. Υλοποιεί τη διεπαφή [api:Nette\Mail\Mailer] και υπάρχουν αρκετοί έτοιμοι mailers τους οποίους θα παρουσιάσουμε. +Ο Mailer είναι μια κλάση που χειρίζεται την αποστολή μηνυμάτων ηλεκτρονικού ταχυδρομείου. Υλοποιεί το interface [api:Nette\Mail\Mailer] και υπάρχουν αρκετοί προκατασκευασμένοι mailers διαθέσιμοι, τους οποίους θα παρουσιάσουμε. -Το πλαίσιο προσθέτει αυτόματα μια υπηρεσία `Nette\Mail\Mailer` με βάση τη [διαμόρφωση |#Configuring] στο DI container, την οποία παίρνετε περνώντας την χρησιμοποιώντας [dependency injection |dependency-injection:passing-dependencies]. +Το Framework προσθέτει αυτόματα μια υπηρεσία τύπου `Nette\Mail\Mailer` στο DI container, διαμορφωμένη βάσει της [διαμόρφωσης |#Διαμόρφωση], στην οποία μπορείτε να αποκτήσετε πρόσβαση περνώντας την χρησιμοποιώντας [dependency injection |dependency-injection:passing-dependencies]. -SendmailMailer .[#toc-sendmailmailer] -------------------------------------- +SendmailMailer +-------------- -Ο προεπιλεγμένος αποστολέας είναι ο SendmailMailer που χρησιμοποιεί τη συνάρτηση PHP [php:mail]. Παράδειγμα χρήσης: +Ο προεπιλεγμένος mailer είναι ο SendmailMailer, ο οποίος χρησιμοποιεί τη συνάρτηση PHP [php:mail]. Παράδειγμα χρήσης: ```php $mailer = new Nette\Mail\SendmailMailer; $mailer->send($mail); ``` -Αν θέλετε να ορίσετε το `returnPath` και ο διακομιστής εξακολουθεί να το αντικαθιστά, χρησιμοποιήστε το `$mailer->commandArgs = '-fmy@email.com'`. +Αν θέλετε να ορίσετε το `returnPath` και ο διακομιστής σας το αντικαθιστά συνεχώς, χρησιμοποιήστε `$mailer->commandArgs = '-fmy@email.com'`. -SmtpMailer .[#toc-smtpmailer] ------------------------------ +SmtpMailer +---------- -Για να στείλετε μήνυμα μέσω του διακομιστή SMTP, χρησιμοποιήστε το `SmtpMailer`. +Ο `SmtpMailer` χρησιμοποιείται για την αποστολή αλληλογραφίας μέσω ενός διακομιστή SMTP. ```php $mailer = new Nette\Mail\SmtpMailer( @@ -213,17 +214,17 @@ $mailer->send($mail); Οι ακόλουθες πρόσθετες παράμετροι μπορούν να περάσουν στον κατασκευαστή: -* `port` - εάν δεν οριστεί, θα χρησιμοποιηθεί η προεπιλεγμένη τιμή 25 ή 465 για το `ssl`. -* `timeout` - χρονικό όριο για τη σύνδεση SMTP +* `port` - αν δεν οριστεί, χρησιμοποιείται η προεπιλογή 25 ή 465 για `ssl` +* `timeout` - timeout για τη σύνδεση SMTP * `persistent` - χρήση μόνιμης σύνδεσης -* `clientHost` - ονομασία πελάτη -* `streamOptions` - σας επιτρέπει να ορίσετε "SSL context options":https://www.php.net/manual/en/context.ssl.php για τη σύνδεση +* `clientHost` - ρύθμιση της κεφαλίδας Host του client +* `streamOptions` - επιτρέπει τη ρύθμιση των "SSL context options":https://www.php.net/manual/en/context.ssl.php για τη σύνδεση -FallbackMailer .[#toc-fallbackmailer] -------------------------------------- +FallbackMailer +-------------- -Δεν στέλνει μηνύματα ηλεκτρονικού ταχυδρομείου αλλά τα αποστέλλει μέσω ενός συνόλου αποστολέων αλληλογραφίας. Εάν ένας αποστολέας αποτύχει, επαναλαμβάνει την προσπάθεια στον επόμενο. Εάν ο τελευταίος αποτύχει, ξεκινά ξανά από τον πρώτο. +Δεν στέλνει απευθείας μηνύματα ηλεκτρονικού ταχυδρομείου, αλλά μεσολαβεί στην αποστολή μέσω ενός συνόλου mailers. Αν ένας mailer αποτύχει, επαναλαμβάνει την προσπάθεια με τον επόμενο. Αν και ο τελευταίος αποτύχει, ξεκινά ξανά από τον πρώτο. ```php $mailer = new Nette\Mail\FallbackMailer([ @@ -234,16 +235,15 @@ $mailer = new Nette\Mail\FallbackMailer([ $mailer->send($mail); ``` -Άλλες παράμετροι στον κατασκευαστή περιλαμβάνουν τον αριθμό των επαναλήψεων και το χρόνο αναμονής σε χιλιοστά του δευτερολέπτου. +Ως πρόσθετες παράμετροι στον κατασκευαστή, μπορούμε να καθορίσουμε τον αριθμό των επαναλήψεων και τον χρόνο αναμονής σε χιλιοστά του δευτερολέπτου. -DKIM .[#toc-dkim] -================= +DKIM +==== -Το DKIM (DomainKeys Identified Mail) είναι μια αξιόπιστη τεχνολογία ηλεκτρονικού ταχυδρομείου που βοηθά επίσης στον εντοπισμό παραποιημένων μηνυμάτων. Το μήνυμα που αποστέλλεται υπογράφεται με το ιδιωτικό κλειδί του τομέα του αποστολέα και η υπογραφή αυτή αποθηκεύεται στην επικεφαλίδα του ηλεκτρονικού ταχυδρομείου. -Ο διακομιστής του παραλήπτη συγκρίνει αυτή την υπογραφή με το δημόσιο κλειδί που είναι αποθηκευμένο στις εγγραφές DNS του τομέα. Με την αντιστοίχιση της υπογραφής αποδεικνύεται ότι το μήνυμα ηλεκτρονικού ταχυδρομείου προέρχεται πράγματι από τον τομέα του αποστολέα και ότι το μήνυμα δεν τροποποιήθηκε κατά τη διάρκεια της μετάδοσης του μηνύματος. +Το DKIM (DomainKeys Identified Mail) είναι μια τεχνολογία για την αύξηση της αξιοπιστίας των μηνυμάτων ηλεκτρονικού ταχυδρομείου, η οποία βοηθά επίσης στην ανίχνευση πλαστών μηνυμάτων. Το απεσταλμένο μήνυμα υπογράφεται με το ιδιωτικό κλειδί του τομέα του αποστολέα και αυτή η υπογραφή αποθηκεύεται στην κεφαλίδα του μηνύματος ηλεκτρονικού ταχυδρομείου. Ο διακομιστής του παραλήπτη συγκρίνει αυτήν την υπογραφή με το δημόσιο κλειδί που είναι αποθηκευμένο στις εγγραφές DNS του τομέα. Εάν η υπογραφή ταιριάζει, αποδεικνύεται ότι το μήνυμα ηλεκτρονικού ταχυδρομείου προέρχεται πράγματι από τον τομέα του αποστολέα και ότι δεν υπήρξε τροποποίηση κατά τη μεταφορά του μηνύματος. -Μπορείτε να ρυθμίσετε το mailer να υπογράφει τα μηνύματα ηλεκτρονικού ταχυδρομείου στη [ρύθμιση παραμέτρων |#Configuring]. Εάν δεν χρησιμοποιείτε την έγχυση εξάρτησης, χρησιμοποιείται ως εξής: +Μπορείτε να ρυθμίσετε την υπογραφή μηνυμάτων ηλεκτρονικού ταχυδρομείου για τον mailer απευθείας στη [#διαμόρφωση]. Αν δεν χρησιμοποιείτε dependency injection, χρησιμοποιείται με αυτόν τον τρόπο: ```php $signer = new Nette\Mail\DkimSigner( @@ -259,40 +259,40 @@ $mailer->send($mail); ``` -Διαμόρφωση του .[#toc-configuring] -================================== +Διαμόρφωση +========== -Επισκόπηση των επιλογών διαμόρφωσης για το Nette Mail. Αν δεν χρησιμοποιείτε ολόκληρο το πλαίσιο, αλλά μόνο αυτή τη βιβλιοθήκη, διαβάστε [πώς να φορτώσετε τη διαμόρφωση |bootstrap:]. +Επισκόπηση των επιλογών διαμόρφωσης για το Nette Mail. Αν δεν χρησιμοποιείτε ολόκληρο το framework, αλλά μόνο αυτή τη βιβλιοθήκη, διαβάστε [πώς να φορτώσετε τη διαμόρφωση|bootstrap:]. -Από προεπιλογή, για την αποστολή μηνυμάτων ηλεκτρονικού ταχυδρομείου χρησιμοποιείται ο αποστολέας `Nette\Mail\SendmailMailer`, ο οποίος δεν ρυθμίζεται περαιτέρω. Ωστόσο, μπορούμε να το αλλάξουμε σε `Nette\Mail\SmtpMailer`: +Για την αποστολή μηνυμάτων ηλεκτρονικού ταχυδρομείου, χρησιμοποιείται από προεπιλογή ο mailer `Nette\Mail\SendmailMailer`, ο οποίος δεν διαμορφώνεται περαιτέρω. Ωστόσο, μπορούμε να τον αλλάξουμε σε `Nette\Mail\SmtpMailer`: ```neon mail: - # χρήση SmtpMailer - smtp: true # (bool) προεπιλογή σε false + # χρησιμοποιεί SmtpMailer + smtp: true # (bool) προεπιλογή είναι false host: ... # (string) port: ... # (int) username: ... # (string) password: ... # (string) timeout: ... # (int) - encryption: ... # (ssl|tls|null) προεπιλογή null (έχει ψευδώνυμο 'secure') - clientHost: ... # (string) προεπιλογή σε $_SERVER['HTTP_HOST'] - persistent: ... # (bool) προεπιλογή σε false + encryption: ... # (ssl|tls|null) προεπιλογή είναι null (έχει ψευδώνυμο 'secure') + clientHost: ... # (string) προεπιλογή είναι $_SERVER['HTTP_HOST'] + persistent: ... # (bool) προεπιλογή είναι false - # πλαίσιο για τη σύνδεση με τον διακομιστή SMTP, προεπιλογή stream_context_get_default() + # context για σύνδεση στον SMTP server, προεπιλογή είναι stream_context_get_default() context: - ssl: # όλες οι επιλογές στη διεύθυνση https://www.php.net/manual/en/context.ssl.php + ssl: # επισκόπηση επιλογών στο https://www.php.net/manual/en/context.ssl.php allow_self_signed: ... ... - http: # Όλες οι επιλογές στη διεύθυνση https://www.php.net/manual/en/context.http.php + http: # επισκόπηση επιλογών στο https://www.php.net/manual/en/context.http.php header: ... ... ``` -Μπορείτε να απενεργοποιήσετε τον έλεγχο ταυτότητας πιστοποιητικού SSL χρησιμοποιώντας την επιλογή `context › ssl › verify_peer: false`. Συνιστάται **απολύτως να μην το κάνετε** αυτό, καθώς θα καταστήσει την εφαρμογή ευάλωτη. Αντ' αυτού, "προσθέστε πιστοποιητικά στο κατάστημα εμπιστοσύνης":https://www.php.net/manual/en/openssl.configuration.php. +Χρησιμοποιώντας την επιλογή `context › ssl › verify_peer: false`, μπορείτε να απενεργοποιήσετε την επαλήθευση των πιστοποιητικών SSL. **Συνιστούμε έντονα να μην** το κάνετε αυτό, καθώς η εφαρμογή θα γίνει ευάλωτη. Αντ' αυτού, "προσθέστε τα πιστοποιητικά στην αποθήκη":https://www.php.net/manual/en/openssl.configuration.php. -Για να αυξήσουμε την αξιοπιστία, μπορούμε να υπογράψουμε τα μηνύματα ηλεκτρονικού ταχυδρομείου χρησιμοποιώντας την [τεχνολογία DKIM |https://blog.nette.org/el/ypographe-menymaton-elektronikou-tachydromeiou-me-dkim]: +Για να αυξήσουμε την αξιοπιστία, μπορούμε να υπογράφουμε τα μηνύματα ηλεκτρονικού ταχυδρομείου χρησιμοποιώντας την [τεχνολογία DKIM |https://blog.nette.org/en/how-to-sign-emails-using-dkim]: ```neon mail: @@ -304,4 +304,12 @@ mail: ``` -{{leftbar: nette:@menu-topics}} +Υπηρεσίες DI +============ + +Αυτές οι υπηρεσίες προστίθενται στο DI container: + +| Όνομα | Τύπος | Περιγραφή +|----------------------------------------------------- +| `mail.mailer` | [api:Nette\Mail\Mailer] | [κλάση που στέλνει μηνύματα ηλεκτρονικού ταχυδρομείου |#Αποστολή μηνύματος ηλεκτρονικού ταχυδρομείου] +| `mail.signer` | [api:Nette\Mail\Signer] | [Υπογραφή DKIM |#DKIM] diff --git a/mail/el/@meta.texy b/mail/el/@meta.texy new file mode 100644 index 0000000000..a09ce5fe0d --- /dev/null +++ b/mail/el/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Τεκμηρίωση}} +{{leftbar: nette:@menu-topics}} diff --git a/mail/en/@home.texy b/mail/en/@home.texy index 70f0da1fb6..97ed765b81 100644 --- a/mail/en/@home.texy +++ b/mail/en/@home.texy @@ -1,9 +1,9 @@ -Sending Emails -************** +Nette Mail +********** <div class=perex> -Are you going to send emails such as newsletters or order confirmations? Nette Framework provides the necessary tools with a very nice API. We will show: +Are you planning to send emails, such as newsletters or order confirmations? Nette Framework provides the necessary tools with a very user-friendly API. We will show you: - how to create an email, including attachments - how to send it @@ -15,7 +15,7 @@ Are you going to send emails such as newsletters or order confirmations? Nette F Installation ============ -Download and install the package using [Composer|best-practices:composer]: +Download and install the library using [Composer|best-practices:composer]: ```shell composer require nette/mail @@ -25,7 +25,7 @@ composer require nette/mail Creating Emails =============== -Email is a [api:Nette\Mail\Message] object: +An email is a [api:Nette\Mail\Message] object. Let's create one like this: ```php $mail = new Nette\Mail\Message; @@ -33,12 +33,12 @@ $mail->setFrom('John <john@example.com>') ->addTo('peter@example.com') ->addTo('jack@example.com') ->setSubject('Order Confirmation') - ->setBody("Hello, Your order has been accepted."); + ->setBody("Hello,\nYour order has been accepted."); ``` -All parameters must be encoded in UTF-8. +All specified parameters must be in UTF-8 encoding. -In addition to specifying recipients with the `addTo()` method, you can also specify the recipient of copy with `addCc()`, or the recipient of blind copy with `addBcc()`. All these methods, including `setFrom()`, accepts addressee in three ways: +In addition to specifying recipients with `addTo()`, you can also specify recipients for a copy with `addCc()`, or recipients for a blind copy with `addBcc()`. All these methods, including `setFrom()`, accept the addressee in three ways: ```php $mail->setFrom('john.doe@example.com'); @@ -52,9 +52,9 @@ The body of an email written in HTML is passed using the `setHtmlBody()` method: $mail->setHtmlBody('<p>Hello,</p><p>Your order has been accepted.</p>'); ``` -You don't have to create a text alternative, Nette will generate it automatically for you. And if the email does not have a subject set, it will be taken from the `<title>` element. +You don't need to create a text alternative; Nette will generate it automatically for you. And if the email doesn't have a subject set, it will try to take it from the `<title>` element. -Images can also be extremely easily inserted into the HTML body of an email. Just pass the path where the images are physically located as the second parameter, and Nette will automatically include them in the email: +Images can also be embedded into the HTML body exceptionally easily. Just pass the path where the images are physically located as the second parameter, and Nette will automatically include them in the email: ```php // automatically adds /path/to/images/background.gif to the email @@ -64,26 +64,27 @@ $mail->setHtmlBody( ); ``` -The image embedding algorithm supports the following patterns: `<img src=...>`, `<body background=...>`, `url(...)` inside the HTML attribute `style` and special syntax `[[...]]`. +The image embedding algorithm searches for these patterns: `<img src=...>`, `<body background=...>`, `url(...)` inside the HTML `style` attribute, and the special syntax `[[...]]`. -Can sending emails be even easier? +Could sending emails be even easier? -Emails are like postcards. Never send passwords or other credentials via email. .[tip] +.[tip] +Emails are like postcards. Never send passwords or other credentials via email. Attachments ----------- -You can, of course, attach attachments to email. Use the `addAttachment(string $file, string $content = null, string $contentType = null)`. +You can, of course, attach files to emails. Use the `addAttachment(string $file, ?string $content = null, ?string $contentType = null)` method for this. ```php -// inserts the file /path/to/example.zip into the email under the name example.zip +// attaches the file /path/to/example.zip to the email with the name example.zip $mail->addAttachment('/path/to/example.zip'); -// inserts the file /path/to/example.zip into the email under the name info.zip +// attaches the file /path/to/example.zip named info.zip $mail->addAttachment('info.zip', file_get_contents('/path/to/example.zip')); -// attaches new example.txt file contents "Hello John!" +// attaches the file example.txt with the content "Hello John!" $mail->addAttachment('example.txt', 'Hello John!'); ``` @@ -91,7 +92,7 @@ $mail->addAttachment('example.txt', 'Hello John!'); Templates --------- -If you send HTML emails, it's a great idea to write them in the [Latte|latte:] template system. How to do it? +If you send HTML emails, writing them in the [Latte|latte:] templating system is a great option. How to do it? ```php $latte = new Latte\Engine; @@ -129,13 +130,13 @@ File `email.latte`: </html> ``` -Nette automatically inserts all images, sets the subject according to the `<title>` element, and generates text alternative for HTML body. +Nette automatically embeds all images, sets the subject based on the `<title>` element, and generates a text alternative for the HTML. -Using in Nette Application +Usage in Nette Application -------------------------- -If you use e-mails together with Nette Application, ie presenters, you may want to create links in templates using the `n:href` attribute or the `{link}` tag. Latte basically does not know them, but it's very easy to add them. Creating links can do object `Nette\Application\LinkGenerator`, which you get by passing it using [dependency injection |dependency-injection:passing-dependencies]. +If you use emails together with Nette Application, i.e., with presenters, you might want to create links in templates using the `n:href` attribute or the `{link}` tag. Latte doesn't know these by default, but it's very easy to add them. The `Nette\Application\LinkGenerator` object can create links, and you can get it by passing it using [dependency injection |dependency-injection:passing-dependencies]: ```php use Nette; @@ -168,7 +169,7 @@ class MailSender } ``` -In the template, link is created like in a normal template. All links create over LinkGenerator are absolute: +In the template, you then create links as you are used to. All links created via LinkGenerator will be absolute. ```latte <a n:href="Presenter:action">Link</a> @@ -178,52 +179,52 @@ In the template, link is created like in a normal template. All links create ove Sending Emails ============== -Mailer is class responsible for sending emails. It implements the [api:Nette\Mail\Mailer] interface and several ready-made mailers are available which we will introduce. +Mailer is a class responsible for sending emails. It implements the [api:Nette\Mail\Mailer] interface, and several pre-made mailers are available, which we will introduce. -The framework automatically adds a `Nette\Mail\Mailer` service based on [configuration|#Configuring] to the DI container, which you get by passing it using [dependency injection |dependency-injection:passing-dependencies]. +The framework automatically adds a `Nette\Mail\Mailer` service based on the [#configuration] to the DI container, which you get by passing it using [dependency injection |dependency-injection:passing-dependencies]. SendmailMailer -------------- -The default mailer is SendmailMailer which uses PHP function [php:mail]. Example of use: +The default mailer is SendmailMailer, which uses the PHP function [php:mail]. Example usage: ```php $mailer = new Nette\Mail\SendmailMailer; $mailer->send($mail); ``` -If you want to set `returnPath` and the server still overwrites it, use `$mailer->commandArgs = '-fmy@email.com'`. +If you want to set the `returnPath` and your server still overwrites it, use `$mailer->commandArgs = '-fmy@email.com'`. SmtpMailer ---------- -To send mail via the SMTP server, use `SmtpMailer`. +To send mail via an SMTP server, use `SmtpMailer`. ```php $mailer = new Nette\Mail\SmtpMailer( host: 'smtp.gmail.com', - username: 'franta@gmail.com', - password: '*****', - encryption: 'ssl', + username: 'john@gmail.com', + password: '*****', // your password + encryption: 'ssl', // or 'tls' ); $mailer->send($mail); ``` The following additional parameters can be passed to the constructor: -* `port` - if not set, the default 25 or 465 for `ssl` will be used -* `timeout` - timeout for SMTP connection -* `persistent` - use persistent connection -* `clientHost` - client designation -* `streamOptions` - allows you to set "SSL context options":https://www.php.net/manual/en/context.ssl.php for connection +* `port` - if not set, the default 25 or 465 for `ssl` (or 587 for `tls`) will be used +* `timeout` - timeout for the SMTP connection +* `persistent` - use a persistent connection +* `clientHost` - specify the client's host header +* `streamOptions` - allows setting "SSL context options":https://www.php.net/manual/en/context.ssl.php for the connection FallbackMailer -------------- -It does not send email but sends them through a set of mailers. If one mailer fails, it repeats the attempt at the next one. If the last one fails, it starts again from the first one. +This mailer does not send emails directly but mediates sending through a set of mailers. If one mailer fails, it retries with the next one. If the last one fails, it starts again from the first one. ```php $mailer = new Nette\Mail\FallbackMailer([ @@ -234,23 +235,22 @@ $mailer = new Nette\Mail\FallbackMailer([ $mailer->send($mail); ``` -Other parameters in the constructor include the number of repeat and waiting time in milliseconds. +Other parameters in the constructor include the number of retries and the waiting time in milliseconds. DKIM ==== -DKIM (DomainKeys Identified Mail) is a trustworthy email technology that also helps detect spoofed messages. The sent message is signed with the private key of the sender's domain and this signature is stored in the email header. -The recipient's server compares this signature with the public key stored in the domain's DNS records. By matching the signature, it is shown that the email actually originated from the sender's domain and that the message was not modified during the transmission of the message. +DKIM (DomainKeys Identified Mail) is a technology for increasing email trustworthiness, which also helps detect spoofed messages. The sent message is signed with the private key of the sender's domain, and this signature is stored in the email header. The recipient's server compares this signature with the public key stored in the domain's DNS records. If the signature matches, it proves that the email actually originated from the sender's domain and that the message was not modified during transmission. -You can set up mailer to sign email in [configuration|#Configuring]. If you do not use dependency injection, it is used as follows: +You can set up the mailer to sign emails directly in the [#configuration]. If you do not use dependency injection, it is used as follows: ```php $signer = new Nette\Mail\DkimSigner( - domain: 'nette.org', - selector: 'dkim', - privateKey: file_get_contents('../dkim/dkim.key'), - passPhrase: '****', + domain: 'yourdomain.com', + selector: 'dkim', // selector from DNS record + privateKey: file_get_contents('/path/to/dkim.key'), // path to your private key + passPhrase: 'your_passphrase', // passphrase for the private key, if any ); $mailer = new Nette\Mail\SendmailMailer; // or SmtpMailer @@ -259,49 +259,57 @@ $mailer->send($mail); ``` -Configuring -=========== +Configuration +============= -Overview of configuration options for the Nette Mail. If you are not using the whole framework, but only this library, read [how to load the configuration|bootstrap:]. +Overview of configuration options for Nette Mail. If you are not using the entire framework but only this library, read [how to load the configuration|bootstrap:]. -By default, the mailer `Nette\Mail\SendmailMailer` is used to send emails, which is not further configured. However, we can switch it to `Nette\Mail\SmtpMailer`: +By default, the `Nette\Mail\SendmailMailer` is used for sending emails, which requires no further configuration. However, we can switch it to `Nette\Mail\SmtpMailer`: ```neon mail: # use SmtpMailer smtp: true # (bool) defaults to false - host: ... # (string) - port: ... # (int) - username: ... # (string) - password: ... # (string) - timeout: ... # (int) - encryption: ... # (ssl|tls|null) defaults to null (has alias 'secure') - clientHost: ... # (string) defaults to $_SERVER['HTTP_HOST'] - persistent: ... # (bool) defaults to false + host: ... # (string) SMTP server hostname + port: ... # (int) SMTP server port + username: ... # (string) username for SMTP authentication + password: ... # (string) password for SMTP authentication + timeout: ... # (int) timeout for SMTP connection + encryption: ... # (ssl|tls|null) defaults to null (alias 'secure' means 'ssl') + clientHost: ... # (string) client hostname, defaults to $_SERVER['HTTP_HOST'] or 'localhost' + persistent: ... # (bool) use persistent connection, defaults to false - # context for connecting to the SMTP server, defaults to stream_context_get_default() + # stream context options for the SMTP connection, defaults to stream_context_get_default() context: ssl: # all options at https://www.php.net/manual/en/context.ssl.php allow_self_signed: ... ... - http: # all options at https://www.php.net/manual/en/context.http.php + http: # options list at https://www.php.net/manual/en/context.http.php header: ... ... ``` -You can disable SSL certificate authentication using the `context › ssl › verify_peer: false` option. It is **strongly recommended not to do** this as it will make the application vulnerable. Instead, "add certificates to trust store":https://www.php.net/manual/en/openssl.configuration.php. +You can disable SSL certificate verification using the `context › ssl › verify_peer: false` option. **We strongly recommend against doing this** as it makes the application vulnerable. Instead, "add certificates to the trust store":https://www.php.net/manual/en/openssl.configuration.php. -To increase trustfulness, we can sign emails using [DKIM technology |https://blog.nette.org/en/sign-emails-with-dkim]: +To increase trustworthiness, we can sign emails using [DKIM technology |https://blog.nette.org/en/sign-emails-with-dkim]: ```neon mail: dkim: - domain: myweb.com - selector: lovenette - privateKey: %appDir%/cert/dkim.priv - passPhrase: ... + domain: myweb.com # your domain + selector: lovenette # DKIM selector + privateKey: %appDir%/cert/dkim.key # path to your private key file + passPhrase: ... # passphrase for the private key, if needed ``` -{{leftbar: nette:@menu-topics}} +DI Services +=========== + +These services are added to the DI container: + +| Name | Type | Description +|----------------------------------------------------- +| `mail.mailer` | [api:Nette\Mail\Mailer] | [email sending class |#Sending Emails] +| `mail.signer` | [api:Nette\Mail\Signer] | [DKIM signing |#DKIM] diff --git a/mail/en/@meta.texy b/mail/en/@meta.texy new file mode 100644 index 0000000000..91205786e5 --- /dev/null +++ b/mail/en/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Documentation}} +{{leftbar: nette:@menu-topics}} diff --git a/mail/es/@home.texy b/mail/es/@home.texy index b7ca8a8153..5a9feb1fee 100644 --- a/mail/es/@home.texy +++ b/mail/es/@home.texy @@ -1,97 +1,98 @@ -Envío de correos electrónicos -***************************** +Nette Mail +********** <div class=perex> -¿Va a enviar correos electrónicos como boletines o confirmaciones de pedidos? Nette Framework proporciona las herramientas necesarias con una API muy agradable. Se lo mostraremos: +¿Va a enviar correos electrónicos, como boletines o confirmaciones de pedidos? Nette Framework proporciona las herramientas necesarias con una API muy agradable. Le mostraremos: -- cómo crear un correo electrónico, incluyendo archivos adjuntos +- cómo crear un correo electrónico, incluidos los archivos adjuntos - cómo enviarlo - cómo combinar correos electrónicos y plantillas </div> -Instalación .[#toc-installation] -================================ +Instalación +=========== -Descargue e instale el paquete utilizando [Composer |best-practices:composer]: +Descargue e instale la librería usando [Composer|best-practices:composer]: ```shell composer require nette/mail ``` -Creación de correos electrónicos .[#toc-creating-emails] -======================================================== +Creación de un correo electrónico +================================= -El correo electrónico es un objeto [api:Nette\Mail\Message]: +El correo electrónico es un objeto de la clase [api:Nette\Mail\Message]. Lo creamos así: ```php $mail = new Nette\Mail\Message; -$mail->setFrom('John <john@example.com>') - ->addTo('peter@example.com') - ->addTo('jack@example.com') - ->setSubject('Order Confirmation') - ->setBody("Hello, Your order has been accepted."); +$mail->setFrom('Franta <franta@example.com>') + ->addTo('petr@example.com') + ->addTo('jirka@example.com') + ->setSubject('Confirmación de pedido') + ->setBody("Hola,\nsu pedido ha sido recibido."); ``` -Todos los parámetros deben estar codificados en UTF-8. +Todos los parámetros especificados deben estar en UTF-8. -Además de especificar destinatarios con el método `addTo()`, también puede especificar el destinatario de la copia con `addCc()`, o el destinatario de la copia oculta con `addBcc()`. Todos estos métodos, incluido `setFrom()`, aceptan destinatarios de tres formas: +Además de especificar el destinatario con el método `addTo()`, también puede especificar el destinatario de la copia `addCc()`, o el destinatario de la copia oculta `addBcc()`. En todos estos métodos, incluido `setFrom()`, podemos escribir la dirección de tres maneras: ```php -$mail->setFrom('john.doe@example.com'); -$mail->setFrom('john.doe@example.com', 'John Doe'); -$mail->setFrom('John Doe <john.doe@example.com>'); +$mail->setFrom('franta@example.com'); +$mail->setFrom('franta@example.com', 'Franta'); +$mail->setFrom('Franta <franta@example.com>'); ``` -El cuerpo de un correo electrónico escrito en HTML se pasa mediante el método `setHtmlBody()`: +El cuerpo del correo electrónico escrito en HTML se pasa usando el método `setHtmlBody()`: ```php -$mail->setHtmlBody('<p>Hello,</p><p>Your order has been accepted.</p>'); +$mail->setHtmlBody('<p>Hola,</p><p>su pedido ha sido recibido.</p>'); ``` -No tiene que crear un texto alternativo, Nette lo generará automáticamente por usted. Y si el correo electrónico no tiene un asunto definido, se tomará del elemento `<title>` elemento. +No necesita crear la alternativa de texto, Nette la generará automáticamente por usted. Y si el correo electrónico no tiene un asunto establecido, intentará tomarlo del elemento `<title>`. -Las imágenes también pueden insertarse muy fácilmente en el cuerpo HTML de un correo electrónico. Basta con pasar la ruta donde se encuentran físicamente las imágenes como segundo parámetro, y Nette las incluirá automáticamente en el correo electrónico: +También es extraordinariamente fácil insertar imágenes en el cuerpo HTML. Simplemente pase la ruta donde se encuentran físicamente las imágenes como segundo parámetro, y Nette las incluirá automáticamente en el correo electrónico: ```php -// automatically adds /path/to/images/background.gif to the email +// agrega automáticamente /path/to/images/background.gif al correo electrónico $mail->setHtmlBody( - '<b>Hello</b> <img src="background.gif">', + '<b>Hola</b> <img src="background.gif">', '/path/to/images', ); ``` -El algoritmo de incrustación de imágenes admite los siguientes patrones: `<img src=...>`, `<body background=...>`, `url(...)` dentro del atributo HTML `style` y la sintaxis especial `[[...]]`. +El algoritmo que inserta imágenes busca estos patrones: `<img src=...>`, `<body background=...>`, `url(...)` dentro del atributo HTML `style` y la sintaxis especial `[[...]]`. ¿Puede ser aún más fácil enviar correos electrónicos? -Los correos electrónicos son como postales. Nunca envíes contraseñas u otras credenciales por correo electrónico. .[tip] +.[tip] +Un correo electrónico es como una postal. Nunca envíe contraseñas u otras credenciales por correo electrónico. -Archivos adjuntos .[#toc-attachments] -------------------------------------- +Adjuntos +-------- -Por supuesto, puedes adjuntar archivos al correo electrónico. Utilice la dirección `addAttachment(string $file, string $content = null, string $contentType = null)`. +Por supuesto, se pueden adjuntar archivos al correo electrónico. El método `addAttachment(string $file, ?string $content = null, ?string $contentType = null)` se utiliza para esto. ```php -// inserts the file /path/to/example.zip into the email under the name example.zip +// inserta el archivo /path/to/example.zip en el correo electrónico con el nombre example.zip $mail->addAttachment('/path/to/example.zip'); -// inserts the file /path/to/example.zip into the email under the name info.zip +// inserta el archivo /path/to/example.zip en el correo electrónico llamado info.zip $mail->addAttachment('info.zip', file_get_contents('/path/to/example.zip')); -// attaches new example.txt file contents "Hello John!" +// inserta el archivo example.txt en el correo electrónico con el contenido "Hola John!" $mail->addAttachment('example.txt', 'Hello John!'); ``` -Plantillas .[#toc-templates] ----------------------------- +Plantillas +---------- -Si envías correos electrónicos HTML, es una gran idea escribirlos en el sistema de plantillas [Latte |latte:]. ¿Cómo hacerlo? +Si envía correos electrónicos HTML, es natural escribirlos en el sistema de plantillas [Latte|latte:]. ¿Cómo hacerlo? ```php $latte = new Latte\Engine; @@ -100,8 +101,8 @@ $params = [ ]; $mail = new Nette\Mail\Message; -$mail->setFrom('John <john@example.com>') - ->addTo('jack@example.com') +$mail->setFrom('Franta <franta@example.com>') + ->addTo('petr@example.com') ->setHtmlBody( $latte->renderToString('/path/to/email.latte', $params), '/path/to/images', @@ -114,7 +115,7 @@ Archivo `email.latte`: <html> <head> <meta charset="utf-8"> - <title>Order Confirmation + Confirmación de pedido -

                                                                                                                                        Hello,

                                                                                                                                        +

                                                                                                                                        Hola,

                                                                                                                                        -

                                                                                                                                        Your order number {$orderId} has been accepted.

                                                                                                                                        +

                                                                                                                                        Su pedido número {$orderId} ha sido recibido.

                                                                                                                                        ``` -Nette inserta automáticamente todas las imágenes, establece el asunto según el elemento `` y genera un texto alternativo para el cuerpo HTML. +Nette inserta automáticamente todas las imágenes, establece el asunto según el elemento `<title>` y genera una alternativa de texto al HTML. -Uso en la aplicación Nette .[#toc-using-in-nette-application] -------------------------------------------------------------- +Uso en Nette Application +------------------------ -Si utiliza correos electrónicos junto con Nette Application, es decir, presentadores, es posible que desee crear enlaces en las plantillas utilizando el atributo `n:href` o la etiqueta `{link}`. Latte básicamente no los conoce, pero es muy fácil añadirlos. La creación de enlaces puede hacer objeto `Nette\Application\LinkGenerator`, que se obtiene al pasarlo usando [inyección de dependencia |dependency-injection:passing-dependencies]. +Si utiliza correos electrónicos junto con Nette Application, es decir, con presenters, es posible que desee crear enlaces en las plantillas utilizando el atributo `n:href` o la etiqueta `{link}`. Latte no los conoce de forma nativa, pero es muy fácil agregarlos. El objeto `Nette\Application\LinkGenerator` puede crear enlaces, y puede obtenerlo pidiéndolo mediante [inyección de dependencias |dependency-injection:passing-dependencies]: ```php use Nette; @@ -168,38 +169,38 @@ class MailSender } ``` -En la plantilla, el enlace se crea como en una plantilla normal. Todos los enlaces creados sobre LinkGenerator son absolutos: +En la plantilla, creamos enlaces como estamos acostumbrados. Todos los enlaces creados a través de LinkGenerator serán absolutos. ```latte -<a n:href="Presenter:action">Link</a> +<a n:href="Presenter:action">Enlace</a> ``` -Envío de correos electrónicos .[#toc-sending-emails] -==================================================== +Envío de un correo electrónico +============================== -Mailer es la clase responsable del envío de correos electrónicos. Implementa la interfaz [api:Nette\Mail\Mailer] y existen varios mailers listos para usar que presentaremos a continuación. +Mailer es una clase que se encarga de enviar correos electrónicos. Implementa la interfaz [api:Nette\Mail\Mailer] y hay varios mailers predefinidos disponibles, que presentaremos. -El framework añade automáticamente un servicio `Nette\Mail\Mailer` basado en la [configuración |#Configuring] al contenedor DI, que se obtiene pasándolo mediante [inyección de dependencias |dependency-injection:passing-dependencies]. +El framework agrega automáticamente un servicio de tipo `Nette\Mail\Mailer` al contenedor DI basado en la [#configuración], al que puede acceder pidiéndolo mediante [inyección de dependencias |dependency-injection:passing-dependencies]. -SendmailMailer .[#toc-sendmailmailer] -------------------------------------- +SendmailMailer +-------------- -El mailer por defecto es SendmailMailer que utiliza la función PHP [php:mail]. Ejemplo de uso: +El mailer por defecto es SendmailMailer, que utiliza la función PHP [php:mail]. Ejemplo de uso: ```php $mailer = new Nette\Mail\SendmailMailer; $mailer->send($mail); ``` -Si desea configurar `returnPath` y el servidor sigue sobrescribiéndolo, utilice `$mailer->commandArgs = '-fmy@email.com'`. +Si desea establecer `returnPath` y su servidor sigue sobrescribiéndolo, use `$mailer->commandArgs = '-fmi@email.cz'`. -SmtpMailer .[#toc-smtpmailer] ------------------------------ +SmtpMailer +---------- -Para enviar correo a través del servidor SMTP, utilice `SmtpMailer`. +Para enviar correo a través de un servidor SMTP, se utiliza `SmtpMailer`. ```php $mailer = new Nette\Mail\SmtpMailer( @@ -213,17 +214,17 @@ $mailer->send($mail); Se pueden pasar los siguientes parámetros adicionales al constructor: -* `port` - si no se establece, se usará el valor por defecto 25 o 465 para `ssl` +* `port` - si no se establece, se utiliza el por defecto 25 o 465 para `ssl` * `timeout` - tiempo de espera para la conexión SMTP -* `persistent` - utilizar conexión persistente -* `clientHost` - designación del cliente +* `persistent` - usar conexión persistente +* `clientHost` - establecer la cabecera Host del cliente * `streamOptions` - permite establecer "opciones de contexto SSL":https://www.php.net/manual/en/context.ssl.php para la conexión -FallbackMailer .[#toc-fallbackmailer] -------------------------------------- +FallbackMailer +-------------- -No envía correos electrónicos, sino que los envía a través de un conjunto de mailers. Si un mailer falla, repite el intento en el siguiente. Si falla el último, vuelve a empezar desde el primero. +No envía correos electrónicos directamente, sino que media el envío a través de un conjunto de mailers. Si un mailer falla, repite el intento con el siguiente. Si el último también falla, comienza de nuevo desde el primero. ```php $mailer = new Nette\Mail\FallbackMailer([ @@ -234,16 +235,15 @@ $mailer = new Nette\Mail\FallbackMailer([ $mailer->send($mail); ``` -Otros parámetros del constructor incluyen el número de repeticiones y el tiempo de espera en milisegundos. +Como parámetros adicionales en el constructor, podemos especificar el número de reintentos y el tiempo de espera en milisegundos. -DKIM .[#toc-dkim] -================= +DKIM +==== -DKIM (DomainKeys Identified Mail) es una tecnología de correo electrónico de confianza que también ayuda a detectar mensajes falsificados. El mensaje enviado se firma con la clave privada del dominio del remitente y esta firma se almacena en la cabecera del correo electrónico. -El servidor del destinatario compara esta firma con la clave pública almacenada en los registros DNS del dominio. Al cotejar la firma, se demuestra que el correo electrónico procede realmente del dominio del remitente y que el mensaje no ha sido modificado durante su transmisión. +DKIM (DomainKeys Identified Mail) es una tecnología para aumentar la confiabilidad de los correos electrónicos, que también ayuda a detectar mensajes falsificados. El mensaje enviado se firma con la clave privada del dominio del remitente y esta firma se almacena en la cabecera del correo electrónico. El servidor del destinatario compara esta firma con la clave pública almacenada en los registros DNS del dominio. Si la firma coincide, se demuestra que el correo electrónico realmente proviene del dominio del remitente y que no se modificó durante la transmisión del mensaje. -Puede configurar el mailer para firmar el correo electrónico en [la configuración |#Configuring]. Si no utiliza la inyección de dependencia, se utiliza de la siguiente manera: +Puede configurar la firma de correos electrónicos para el mailer directamente en la [#configuración]. Si no utiliza la inyección de dependencias, se utiliza de esta manera: ```php $signer = new Nette\Mail\DkimSigner( @@ -259,40 +259,40 @@ $mailer->send($mail); ``` -Configuración de .[#toc-configuring] -==================================== +Configuración +============= -Visión general de las opciones de configuración para el Nette Mail. Si no está utilizando todo el framework, sino sólo esta librería, lea [cómo cargar la |bootstrap:] configuración. +Resumen de las opciones de configuración para Nette Mail. Si no utiliza todo el framework, sino solo esta librería, lea [cómo cargar la configuración|bootstrap:]. -Por defecto, el mailer `Nette\Mail\SendmailMailer` se utiliza para enviar correos electrónicos, que no se configura más. Sin embargo, podemos cambiarlo a `Nette\Mail\SmtpMailer`: +Para enviar correos electrónicos, se utiliza de forma predeterminada el mailer `Nette\Mail\SendmailMailer`, que no se configura más. Sin embargo, podemos cambiarlo a `Nette\Mail\SmtpMailer`: ```neon mail: - # use SmtpMailer - smtp: true # (bool) defaults to false + # usa SmtpMailer + smtp: true # (bool) por defecto es false host: ... # (string) port: ... # (int) username: ... # (string) password: ... # (string) timeout: ... # (int) - encryption: ... # (ssl|tls|null) defaults to null (has alias 'secure') - clientHost: ... # (string) defaults to $_SERVER['HTTP_HOST'] - persistent: ... # (bool) defaults to false + encryption: ... # (ssl|tls|null) por defecto es null (tiene alias 'secure') + clientHost: ... # (string) por defecto es $_SERVER['HTTP_HOST'] + persistent: ... # (bool) por defecto es false - # context for connecting to the SMTP server, defaults to stream_context_get_default() + # contexto para la conexión al servidor SMTP, por defecto es stream_context_get_default() context: - ssl: # all options at https://www.php.net/manual/en/context.ssl.php + ssl: # resumen de opciones en https://www.php.net/manual/en/context.ssl.php allow_self_signed: ... ... - http: # all options at https://www.php.net/manual/en/context.http.php + http: # resumen de opciones en https://www.php.net/manual/en/context.http.php header: ... ... ``` -Puede desactivar la autenticación de certificados SSL utilizando la opción `context › ssl › verify_peer: false`. Se **recomienda encarecidamente** no hacer esto ya que hará que la aplicación sea vulnerable. En su lugar, "añadir certificados al almacén de confianza":https://www.php.net/manual/en/openssl.configuration.php. +Con la opción `context › ssl › verify_peer: false`, puede desactivar la verificación de certificados SSL. **Desaconsejamos encarecidamente** hacer esto, ya que la aplicación se volverá vulnerable. En su lugar, "agregue los certificados al almacén":https://www.php.net/manual/en/openssl.configuration.php. -Para aumentar la confianza, podemos firmar los correos electrónicos utilizando [la tecnología DKIM |https://blog.nette.org/es/firmar-correos-electronicos-con-dkim]: +Para aumentar la confiabilidad, podemos firmar los correos electrónicos utilizando la [tecnología DKIM |https://blog.nette.org/es/sign-emails-with-dkim]: ```neon mail: @@ -304,4 +304,12 @@ mail: ``` -{{leftbar: nette:@menu-topics}} +Servicios DI +============ + +Estos servicios se agregan al contenedor DI: + +| Nombre | Tipo | Descripción +|----------------------------------------------------- +| `mail.mailer` | [api:Nette\Mail\Mailer] | [clase que envía correos electrónicos |#Envío de un correo electrónico] +| `mail.signer` | [api:Nette\Mail\Signer] | [firma DKIM |#DKIM] diff --git a/mail/es/@meta.texy b/mail/es/@meta.texy new file mode 100644 index 0000000000..25d506cde9 --- /dev/null +++ b/mail/es/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Documentación}} +{{leftbar: nette:@menu-topics}} diff --git a/mail/fr/@home.texy b/mail/fr/@home.texy index 7f208b5429..91be1a064c 100644 --- a/mail/fr/@home.texy +++ b/mail/fr/@home.texy @@ -1,97 +1,98 @@ -Envoi de courriels -****************** +Nette Mail +********** <div class=perex> -Allez-vous envoyer des courriels tels que des bulletins d'information ou des confirmations de commande ? Nette Framework fournit les outils nécessaires avec une très belle API. Nous allons vous montrer : +Vous vous apprêtez à envoyer des e-mails, par exemple des newsletters ou des confirmations de commande ? Nette Framework fournit les outils nécessaires avec une API très agréable. Nous allons vous montrer : -- comment créer un email, y compris les pièces jointes +- comment créer un e-mail, y compris les pièces jointes - comment l'envoyer -- comment combiner des e-mails et des modèles +- comment combiner e-mails et templates </div> -Installation .[#toc-installation] -================================= +Installation +============ -Téléchargez et installez le paquet en utilisant [Composer |best-practices:composer]: +Vous pouvez télécharger et installer la bibliothèque à l'aide de [Composer|best-practices:composer] : ```shell composer require nette/mail ``` -Création de courriels .[#toc-creating-emails] -============================================= +Création d'un e-mail +==================== -Le courrier électronique est un objet [api:Nette\Mail\Message]: +L'e-mail est un objet de la classe [api:Nette\Mail\Message]. Créons-le comme ceci : ```php $mail = new Nette\Mail\Message; -$mail->setFrom('John <john@example.com>') - ->addTo('peter@example.com') - ->addTo('jack@example.com') - ->setSubject('Order Confirmation') - ->setBody("Hello, Your order has been accepted."); +$mail->setFrom('Franta <franta@example.com>') + ->addTo('petr@example.com') + ->addTo('jirka@example.com') + ->setSubject('Confirmation de commande') + ->setBody("Bonjour,\nvotre commande a été acceptée."); ``` -Tous les paramètres doivent être encodés en UTF-8. +Tous les paramètres saisis doivent être en UTF-8. -En plus de spécifier les destinataires avec la méthode `addTo()`, vous pouvez également spécifier le destinataire de la copie avec `addCc()`, ou le destinataire de la copie cachée avec `addBcc()`. Toutes ces méthodes, y compris `setFrom()`, acceptent le destinataire de trois manières : +En plus d'indiquer le destinataire avec la méthode `addTo()`, vous pouvez également indiquer le destinataire en copie `addCc()`, ou le destinataire en copie cachée `addBcc()`. Dans toutes ces méthodes, y compris `setFrom()`, nous pouvons écrire l'adresse de trois manières : ```php -$mail->setFrom('john.doe@example.com'); -$mail->setFrom('john.doe@example.com', 'John Doe'); -$mail->setFrom('John Doe <john.doe@example.com>'); +$mail->setFrom('franta@example.com'); +$mail->setFrom('franta@example.com', 'Franta'); +$mail->setFrom('Franta <franta@example.com>'); ``` -Le corps d'un courriel écrit en HTML est transmis à l'aide de la méthode `setHtmlBody()`: +Le corps de l'e-mail écrit en HTML est transmis via la méthode `setHtmlBody()` : ```php -$mail->setHtmlBody('<p>Hello,</p><p>Your order has been accepted.</p>'); +$mail->setHtmlBody('<p>Bonjour,</p><p>votre commande a été acceptée.</p>'); ``` -Vous n'avez pas besoin de créer une alternative textuelle, Nette la génère automatiquement pour vous. Et si l'email n'a pas de sujet défini, celui-ci sera pris dans l'élément `<title>` élément. +Vous n'avez pas besoin de créer l'alternative texte, Nette la générera automatiquement pour vous. Et si l'e-mail n'a pas de sujet défini, il essaiera de le récupérer à partir de l'élément `<title>`. -Les images peuvent également être insérées très facilement dans le corps HTML d'un e-mail. Il suffit de passer le chemin où les images sont physiquement situées comme deuxième paramètre, et Nette les inclura automatiquement dans l'e-mail : +Il est également extraordinairement facile d'insérer des images dans le corps HTML. Il suffit de passer le chemin où se trouvent physiquement les images comme deuxième paramètre, et Nette les inclura automatiquement dans l'e-mail : ```php -// ajoute automatiquement /path/to/images/background.gif à l'email +// ajoute automatiquement /path/to/images/background.gif à l'e-mail $mail->setHtmlBody( - '<b>Hello</b> <img src="background.gif">', + '<b>Bonjour</b> <img src="background.gif">', '/path/to/images', ); ``` -L'algorithme d'incorporation d'images prend en charge les modèles suivants : `<img src=...>`, `<body background=...>`, `url(...)` à l'intérieur de l'attribut HTML `style` et la syntaxe spéciale `[[...]]`. +L'algorithme d'insertion d'images recherche ces motifs : `<img src=...>`, `<body background=...>`, `url(...)` à l'intérieur de l'attribut HTML `style` et la syntaxe spéciale `[[...]]`. -L'envoi d'e-mails peut-il être encore plus facile ? +L'envoi d'e-mails peut-il être encore plus simple ? -Les courriels sont comme des cartes postales. N'envoyez jamais de mots de passe ou d'autres informations d'identification par courrier électronique. .[tip] +.[tip] +Un e-mail est comme une carte postale. N'envoyez jamais de mots de passe ou d'autres informations d'identification par e-mail. -Pièces jointes .[#toc-attachments] ----------------------------------- +Pièces jointes +-------------- -Vous pouvez, bien entendu, joindre des pièces jointes à un courriel. Utilisez l'adresse `addAttachment(string $file, string $content = null, string $contentType = null)`. +Il est bien sûr possible d'ajouter des pièces jointes à l'e-mail. La méthode `addAttachment(string $file, ?string $content = null, ?string $contentType = null)` est utilisée à cet effet. ```php -// insère le fichier /path/to/example.zip dans l'email sous le nom example.zip +// insère le fichier /path/to/example.zip dans l'e-mail sous le nom example.zip $mail->addAttachment('/path/to/example.zip'); -// insère le fichier /path/to/example.zip dans l'e-mail sous le nom info.zip +// insère le fichier /path/to/example.zip nommé info.zip dans l'e-mail $mail->addAttachment('info.zip', file_get_contents('/path/to/example.zip')); -// joint le contenu du nouveau fichier exemple.txt "Bonjour John !" -$mail->addAttachment('example.txt', 'Hello John!'); +// insère le fichier example.txt avec le contenu "Bonjour John !" dans l'e-mail +$mail->addAttachment('example.txt', 'Bonjour John !'); ``` -Modèles .[#toc-templates] -------------------------- +Templates +--------- -Si vous envoyez des e-mails en HTML, c'est une excellente idée de les rédiger dans le système de modèles [Latte |latte:]. Comment faire ? +Si vous envoyez des e-mails HTML, il est naturel de les écrire dans le système de templates [Latte|latte:]. Comment faire ? ```php $latte = new Latte\Engine; @@ -100,21 +101,21 @@ $params = [ ]; $mail = new Nette\Mail\Message; -$mail->setFrom('John <john@example.com>') - ->addTo('jack@example.com') +$mail->setFrom('Franta <franta@example.com>') + ->addTo('petr@example.com') ->setHtmlBody( $latte->renderToString('/path/to/email.latte', $params), '/path/to/images', ); ``` -Fichier `email.latte`: +Fichier `email.latte` : ```latte <html> <head> <meta charset="utf-8"> - <title>Order Confirmation + Confirmation de commande -

                                                                                                                                        Hello,

                                                                                                                                        +

                                                                                                                                        Bonjour,

                                                                                                                                        -

                                                                                                                                        Your order number {$orderId} has been accepted.

                                                                                                                                        +

                                                                                                                                        Votre commande numéro {$orderId} a été acceptée.

                                                                                                                                        ``` -Nette insère automatiquement toutes les images, définit le sujet en fonction de l'élément `` et génère un texte alternatif pour le corps du HTML. +Nette insère automatiquement toutes les images, définit le sujet en fonction de l'élément `<title>` et génère une alternative texte au HTML. -Utilisation dans l'application Nette .[#toc-using-in-nette-application] ------------------------------------------------------------------------ +Utilisation dans Nette Application +---------------------------------- -Si vous utilisez des e-mails avec Nette Application, c'est-à-dire des présentateurs, vous voudrez peut-être créer des liens dans les modèles en utilisant l'attribut `n:href` ou la balise `{link}`. En principe, Nette Application ne les connaît pas, mais il est très facile de les ajouter. La création de liens peut se faire à l'aide de l'objet `Nette\Application\LinkGenerator`, que vous obtenez en le passant à l'aide de l'[injection de dépendances |dependency-injection:passing-dependencies]. +Si vous utilisez les e-mails conjointement avec Nette Application, c'est-à-dire avec des presenters, vous voudrez peut-être créer des liens dans les templates en utilisant l'attribut `n:href` ou la balise `{link}`. Latte ne les connaît pas par défaut, mais il est très facile de les ajouter. L'objet `Nette\Application\LinkGenerator`, capable de créer des liens, peut être obtenu en le demandant via [l'injection de dépendances |dependency-injection:passing-dependencies] : ```php use Nette; @@ -168,38 +169,38 @@ class MailSender } ``` -Dans le modèle, le lien est créé comme dans un modèle normal. Tous les liens créés par LinkGenerator sont absolus : +Dans le template, nous créons ensuite les liens comme nous en avons l'habitude. Tous les liens créés via LinkGenerator seront absolus. ```latte -<a n:href="Presenter:action">Link</a> +<a n:href="Presenter:action">Lien</a> ``` -Envoi de courriels .[#toc-sending-emails] -========================================= +Envoi de l'e-mail +================= -Mailer est la classe responsable de l'envoi des e-mails. Elle implémente l'interface [api:Nette\Mail\Mailer] et il existe plusieurs mailers prêts à l'emploi que nous allons présenter. +Mailer est la classe responsable de l'envoi des e-mails. Elle implémente l'interface [api:Nette\Mail\Mailer] et plusieurs mailers pré-faits sont disponibles, que nous allons présenter. -Le framework ajoute automatiquement un service `Nette\Mail\Mailer` basé sur la [configuration |#Configuring] au conteneur DI, que vous obtenez en le passant en utilisant l'[injection de dépendances |dependency-injection:passing-dependencies]. +Le framework ajoute automatiquement au conteneur DI un service de type `Nette\Mail\Mailer` construit sur la base de la [#configuration], auquel vous pouvez accéder en le demandant via [l'injection de dépendances |dependency-injection:passing-dependencies]. -SendmailMailer .[#toc-sendmailmailer] -------------------------------------- +SendmailMailer +-------------- -Le mailer par défaut est SendmailMailer qui utilise la fonction PHP [php:mail]. Exemple d'utilisation : +Le mailer par défaut est SendmailMailer, qui utilise la fonction PHP [php:mail]. Exemple d'utilisation : ```php $mailer = new Nette\Mail\SendmailMailer; $mailer->send($mail); ``` -Si vous voulez définir `returnPath` et que le serveur l'écrase quand même, utilisez `$mailer->commandArgs = '-fmy@email.com'`. +Si vous souhaitez définir le `returnPath` et que votre serveur le réécrit constamment, utilisez `$mailer->commandArgs = '-fMuj@email.cz'`. -SmtpMailer .[#toc-smtpmailer] ------------------------------ +SmtpMailer +---------- -Pour envoyer du courrier via le serveur SMTP, utilisez `SmtpMailer`. +Pour envoyer du courrier via un serveur SMTP, utilisez `SmtpMailer`. ```php $mailer = new Nette\Mail\SmtpMailer( @@ -213,17 +214,17 @@ $mailer->send($mail); Les paramètres supplémentaires suivants peuvent être passés au constructeur : -* `port` - s'il n'est pas défini, les valeurs par défaut 25 ou 465 pour `ssl` seront utilisées. -* `timeout` - délai d'attente pour la connexion SMTP -* `persistent` - utilisation d'une connexion persistante -* `clientHost` - désignation du client -* `streamOptions` - vous permet de définir les "options du contexte SSL":https://www.php.net/manual/en/context.ssl.php pour la connexion. +* `port` - s'il n'est pas défini, le port par défaut 25 ou 465 pour `ssl` sera utilisé +* `timeout` - timeout pour la connexion SMTP +* `persistent` - utiliser une connexion persistante +* `clientHost` - définir l'en-tête Host du client +* `streamOptions` - permet de définir les "options de contexte SSL":https://www.php.net/manual/en/context.ssl.php pour la connexion -FallbackMailer .[#toc-fallbackmailer] -------------------------------------- +FallbackMailer +-------------- -Il n'envoie pas de courrier électronique mais les fait transiter par un ensemble de mailers. Si un mailer échoue, il répète la tentative au suivant. Si le dernier échoue, il recommence à partir du premier. +Il n'envoie pas directement les e-mails, mais relaie l'envoi via un ensemble de mailers. Si un mailer échoue, il réessaie avec le suivant. Si le dernier échoue également, il recommence depuis le premier. ```php $mailer = new Nette\Mail\FallbackMailer([ @@ -234,16 +235,15 @@ $mailer = new Nette\Mail\FallbackMailer([ $mailer->send($mail); ``` -Les autres paramètres du constructeur comprennent le nombre de répétitions et le temps d'attente en millisecondes. +Comme paramètres supplémentaires dans le constructeur, nous pouvons indiquer le nombre de tentatives et le temps d'attente en millisecondes. -DKIM .[#toc-dkim] -================= +DKIM +==== -DKIM (DomainKeys Identified Mail) est une technologie de messagerie électronique fiable qui permet également de détecter les messages usurpés. Le message envoyé est signé avec la clé privée du domaine de l'expéditeur et cette signature est stockée dans l'en-tête du courriel. -Le serveur du destinataire compare cette signature avec la clé publique stockée dans les enregistrements DNS du domaine. En faisant correspondre la signature, il est démontré que l'e-mail provient effectivement du domaine de l'expéditeur et que le message n'a pas été modifié pendant sa transmission. +DKIM (DomainKeys Identified Mail) est une technologie visant à accroître la fiabilité des e-mails, qui aide également à détecter les messages falsifiés. Le message envoyé est signé avec la clé privée du domaine de l'expéditeur et cette signature est stockée dans l'en-tête de l'e-mail. Le serveur du destinataire compare cette signature avec la clé publique stockée dans les enregistrements DNS du domaine. Si la signature correspond, cela prouve que l'e-mail provient réellement du domaine de l'expéditeur et qu'il n'a pas été modifié pendant la transmission. -Vous pouvez [configurer |#Configuring] le mailer pour qu'il signe les e-mails dans la [configuration |#Configuring]. Si vous n'utilisez pas l'injection de dépendances, elle est utilisée comme suit : +Vous pouvez configurer la signature des e-mails pour le mailer directement dans la [#configuration]. Si vous n'utilisez pas l'injection de dépendances, elle est utilisée de cette manière : ```php $signer = new Nette\Mail\DkimSigner( @@ -259,40 +259,40 @@ $mailer->send($mail); ``` -Configuration de .[#toc-configuring] -==================================== +Configuration +============= -Aperçu des options de configuration du courrier Nette. Si vous n'utilisez pas l'ensemble du framework, mais seulement cette bibliothèque, lisez [comment charger la configuration |bootstrap:]. +Aperçu des options de configuration pour Nette Mail. Si vous n'utilisez pas l'ensemble du framework, mais seulement cette bibliothèque, lisez [comment charger la configuration|bootstrap:]. -Par défaut, le mailer `Nette\Mail\SendmailMailer` est utilisé pour envoyer des emails, ce qui n'est pas plus configuré. Cependant, nous pouvons le changer pour `Nette\Mail\SmtpMailer`: +Pour l'envoi d'e-mails, le mailer `Nette\Mail\SendmailMailer` est utilisé par défaut, qui n'est pas configuré davantage. Cependant, nous pouvons le basculer vers `Nette\Mail\SmtpMailer` : ```neon mail: # utilise SmtpMailer - smtp: true # (bool) defaults to false + smtp: true # (bool) la valeur par défaut est false host: ... # (string) port: ... # (int) username: ... # (string) password: ... # (string) timeout: ... # (int) - encryption: ... # (ssl|tls|null) par défaut null (a un alias 'secure') - clientHost: ... # (string) Valeur par défaut: $_SERVER['HTTP_HOST']. - persistent: ... # (bool) a pour valeur par défaut false + encryption: ... # (ssl|tls|null) la valeur par défaut est null (a l'alias 'secure') + clientHost: ... # (string) la valeur par défaut est $_SERVER['HTTP_HOST'] + persistent: ... # (bool) la valeur par défaut est false # contexte pour la connexion au serveur SMTP, la valeur par défaut est stream_context_get_default() context: - ssl: # toutes les options sur https://www.php.net/manual/en/context.ssl.php + ssl: # aperçu des options sur https://www.php.net/manual/en/context.ssl.php allow_self_signed: ... ... - http: # toutes les options sur https://www.php.net/manual/en/context.http.php + http: # aperçu des options sur https://www.php.net/manual/en/context.http.php header: ... ... ``` -Vous pouvez désactiver l'authentification du certificat SSL en utilisant l'option `context › ssl › verify_peer: false`. Il est **strès fortement recommandé de ne pas le faire** car cela rendra l'application vulnérable. Au lieu de cela, "ajoutez les certificats au magasin de confiance":https://www.php.net/manual/en/openssl.configuration.php. +Avec l'option `context › ssl › verify_peer: false`, vous pouvez désactiver la vérification des certificats SSL. **Nous déconseillons fortement** de le faire, car l'application deviendrait vulnérable. Ajoutez plutôt les "certificats au magasin":https://www.php.net/manual/en/openssl.configuration.php. -Pour augmenter la confiance, nous pouvons signer les courriels en utilisant la [technologie DKIM |https://blog.nette.org/fr/signer-les-e-mails-avec-dkim]: +Pour augmenter la fiabilité, nous pouvons signer les e-mails en utilisant la [technologie DKIM |https://blog.nette.org/fr/signer-les-emails-avec-dkim] : ```neon mail: @@ -304,4 +304,12 @@ mail: ``` -{{leftbar: nette:@menu-topics}} +Services DI +=========== + +Ces services sont ajoutés au conteneur DI : + +| Nom | Type | Description +|----------------------------------------------------- +| `mail.mailer` | [api:Nette\Mail\Mailer] | [classe envoyant les e-mails |#Envoi de l e-mail] +| `mail.signer` | [api:Nette\Mail\Signer] | [signature DKIM |#DKIM] diff --git a/mail/fr/@meta.texy b/mail/fr/@meta.texy new file mode 100644 index 0000000000..95ec8a4ef6 --- /dev/null +++ b/mail/fr/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Documentation Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/mail/hu/@home.texy b/mail/hu/@home.texy index 2925fbeaac..def26a2a4d 100644 --- a/mail/hu/@home.texy +++ b/mail/hu/@home.texy @@ -1,97 +1,98 @@ -E-mailek küldése -**************** +Nette Mail +********** <div class=perex> -E-maileket fog küldeni, például hírleveleket vagy rendelés visszaigazolásokat? A Nette Framework egy nagyon szép API-val biztosítja a szükséges eszközöket. Megmutatjuk: +E-maileket szeretne küldeni, például hírleveleket vagy megrendelés-visszaigazolásokat? A Nette Framework biztosítja a szükséges eszközöket egy nagyon kellemes API-val. Megmutatjuk: -- hogyan hozhatunk létre e-mailt, csatolmányokkal együtt -- hogyan kell elküldeni -- hogyan lehet e-maileket és sablonokat kombinálni +- hogyan hozzon létre e-mailt mellékletekkel együtt +- hogyan küldje el +- hogyan kapcsolja össze az e-maileket és a sablonokat </div> -Telepítés .[#toc-installation] -============================== +Telepítés +========= -Töltse le és telepítse a csomagot a [Composer |best-practices:composer] segítségével: +A könyvtárat a [Composer|best-practices:composer] segítségével töltheti le és telepítheti: ```shell composer require nette/mail ``` -E-mailek létrehozása .[#toc-creating-emails] -============================================ +E-mail létrehozása +================== -Az e-mail egy [api:Nette\Mail\Message] objektum: +Az e-mail a [api:Nette\Mail\Message] osztály objektuma. Hozzuk létre például így: ```php $mail = new Nette\Mail\Message; -$mail->setFrom('John <john@example.com>') - ->addTo('peter@example.com') - ->addTo('jack@example.com') - ->setSubject('Order Confirmation') - ->setBody("Hello, Your order has been accepted."); +$mail->setFrom('Franta <franta@example.com>') + ->addTo('petr@example.com') + ->addTo('jirka@example.com') + ->setSubject('Megrendelés visszaigazolása') + ->setBody("Jó napot,\na megrendelését elfogadtuk."); ``` -Minden paramétert UTF-8 kódolásban kell megadni. +Minden megadott paraméternek UTF-8 kódolásúnak kell lennie. -A `addTo()` módszerrel megadott címzettek mellett a `addCc()` módszerrel a másolat címzettjét, a `addBcc()` módszerrel pedig a vakmásolat címzettjét is megadhatja. Mindezek a módszerek, beleértve a `setFrom()`-t is, háromféleképpen fogadják el a címzettet: +A címzett `addTo()` metódussal történő megadása mellett megadhatunk másolatot kapó címzettet (`addCc()`) vagy rejtett másolatot kapó címzettet (`addBcc()`) is. Mindezekben a metódusokban, beleértve a `setFrom()` metódust is, a címzettet háromféleképpen adhatjuk meg: ```php -$mail->setFrom('john.doe@example.com'); -$mail->setFrom('john.doe@example.com', 'John Doe'); -$mail->setFrom('John Doe <john.doe@example.com>'); +$mail->setFrom('franta@example.com'); +$mail->setFrom('franta@example.com', 'Franta'); +$mail->setFrom('Franta <franta@example.com>'); ``` -A HTML-ben írt e-mail testét a `setHtmlBody()` módszerrel adja át: +A HTML formátumú e-mail törzsét a `setHtmlBody()` metódussal adjuk át: ```php -$mail->setHtmlBody('<p>Hello,</p><p>Your order has been accepted.</p>'); +$mail->setHtmlBody('<p>Jó napot,</p><p>a megrendelését elfogadtuk.</p>'); ``` -Nem kell szöveges alternatívát létrehoznia, a Nette automatikusan generálja Önnek. Ha pedig az e-mailnek nincs beállított tárgya, akkor azt a Nette a `<title>` elemet. +Nem kell szöveges alternatívát létrehoznia, a Nette automatikusan legenerálja Ön helyett. És ha az e-mailnek nincs beállított tárgya, megpróbálja átvenni a `<title>` elemből. -A képek is rendkívül egyszerűen beilleszthetők az e-mail HTML testébe. Csak adja meg második paraméterként azt az elérési utat, ahol a képek fizikailag találhatók, és a Nette automatikusan beilleszti őket az e-mailbe: +Képeket is rendkívül egyszerűen beilleszthet a HTML törzsbe. Csak adja át második paraméterként az elérési utat, ahol a képek fizikailag találhatók, és a Nette automatikusan belefoglalja őket az e-mailbe: ```php -// automatikusan hozzáadja az /path/to/images/background.gif fájlt az e-mailhez +// automatikusan hozzáadja a /path/to/images/background.gif fájlt az e-mailhez $mail->setHtmlBody( '<b>Hello</b> <img src="background.gif">', '/path/to/images', ); ``` -A képbeágyazási algoritmus a következő mintákat támogatja: `<img src=...>`, `<body background=...>`, `url(...)` a HTML-attribútumon belül `style` és speciális szintaxis `[[...]]`. +A képeket beillesztő algoritmus ezeket a mintákat keresi: `<img src=...>`, `<body background=...>`, `url(...)` a HTML `style` attribútumon belül és a speciális `[[...]]` szintaxist. -Lehet még egyszerűbb az e-mailek küldése? +Lehet még ennél is egyszerűbb az e-mailek küldése? -Az e-mailek olyanok, mint a képeslapok. Soha ne küldjön jelszavakat vagy más hitelesítő adatokat e-mailben. .[tip] +.[tip] +Az e-mail olyan, mint egy képeslap. Soha ne küldjön jelszavakat vagy más hozzáférési adatokat e-mailben. -Csatolmányok .[#toc-attachments] --------------------------------- +Mellékletek +----------- -Természetesen csatolhat csatolmányokat is az e-mailhez. Használja a `addAttachment(string $file, string $content = null, string $contentType = null)`. +Természetesen mellékleteket is csatolhatunk az e-mailhez. Erre szolgál az `addAttachment(string $file, ?string $content = null, ?string $contentType = null)` metódus. ```php -// beilleszti a /path/to/example.zip fájlt az e-mailbe a example.zip név alatt. +// beilleszti az e-mailbe a /path/to/example.zip fájlt example.zip néven $mail->addAttachment('/path/to/example.zip'); -// beszúrja a /path/to/example.zip fájlt az e-mailbe info.zip néven. +// beilleszti az e-mailbe a /path/to/example.zip fájlt info.zip néven $mail->addAttachment('info.zip', file_get_contents('/path/to/example.zip')); -// csatolja az új example.txt fájl tartalmát "Hello John!" +// beilleszti az e-mailbe az example.txt fájlt "Hello John!" tartalommal $mail->addAttachment('example.txt', 'Hello John!'); ``` -Sablonok .[#toc-templates] --------------------------- +Sablonok +-------- -Ha HTML e-maileket küldesz, érdemes a [Latte |latte:] sablonrendszerben megírni őket. Hogyan kell ezt megtenni? +Ha HTML e-maileket küld, kézenfekvő, hogy a [Latte|latte:] sablonrendszerben írja meg őket. Hogyan csináljuk? ```php $latte = new Latte\Engine; @@ -100,21 +101,21 @@ $params = [ ]; $mail = new Nette\Mail\Message; -$mail->setFrom('John <john@example.com>') - ->addTo('jack@example.com') +$mail->setFrom('Franta <franta@example.com>') + ->addTo('petr@example.com') ->setHtmlBody( $latte->renderToString('/path/to/email.latte', $params), - '/path/to/images', + '/path/to/images', // elérési út a képekhez ); ``` -Fájl `email.latte`: +Az `email.latte` fájl: ```latte <html> <head> <meta charset="utf-8"> - <title>Order Confirmation + Megrendelés visszaigazolása -

                                                                                                                                        Hello,

                                                                                                                                        +

                                                                                                                                        Jó napot,

                                                                                                                                        -

                                                                                                                                        Your order number {$orderId} has been accepted.

                                                                                                                                        +

                                                                                                                                        A(z) {$orderId} számú megrendelését elfogadtuk.

                                                                                                                                        ``` -A Nette automatikusan beilleszti az összes képet, beállítja a témát a megadott `` elemnek megfelelően, és alternatív szöveget generál a HTML-szövegtesthez. +A Nette automatikusan beilleszti az összes képet, beállítja a tárgyat a `<title>` elem alapján, és legenerálja a szöveges alternatívát a HTML-hez. -Használat a Nette alkalmazásban .[#toc-using-in-nette-application] ------------------------------------------------------------------- +Használat a Nette Applicationben +-------------------------------- -Ha e-maileket használ a Nette alkalmazással együtt, azaz előadókat, akkor a sablonokban linkeket hozhat létre a `n:href` attribútum vagy a `{link}` címke használatával. A Nette alapvetően nem ismeri ezeket, de nagyon könnyen hozzáadhatja őket. A linkek létrehozásához képes objektum `Nette\Application\LinkGenerator`, amelyet a [függőségi injektálással |dependency-injection:passing-dependencies] történő átadással kapunk. +Ha az e-maileket a Nette Applicationnel együtt használja, azaz presenterekkel, akkor lehet, hogy a sablonokban linkeket szeretne létrehozni az `n:href` attribútummal vagy a `{link}` taggel. Ezeket a Latte alapból nem ismeri, de nagyon könnyű hozzáadni őket. A linkeket a `Nette\Application\LinkGenerator` objektum tudja létrehozni, amelyhez úgy juthat hozzá, hogy [dependency injection |dependency-injection:passing-dependencies] segítségével átadja magának: ```php use Nette; @@ -168,38 +169,38 @@ class MailSender } ``` -A sablonban a linket úgy hozzuk létre, mint egy normál sablonban. Minden LinkGeneratoron keresztül létrehozott link abszolút: +A sablonban ezután a megszokott módon hozunk létre linkeket. A LinkGeneratoron keresztül létrehozott összes link abszolút lesz. ```latte <a n:href="Presenter:action">Link</a> ``` -E-mail küldése .[#toc-sending-emails] -===================================== +E-mail küldése +============== -A Mailer osztály felelős az e-mailek küldéséért. A [api:Nette\Mail\Mailer] interfészt valósítja meg, és számos kész mailer áll rendelkezésre, amelyeket be fogunk mutatni. +A Mailer egy osztály, amely biztosítja az e-mailek küldését. Implementálja a [api:Nette\Mail\Mailer] interfészt, és több előre elkészített mailer áll rendelkezésre, amelyeket bemutatunk. -A keretrendszer automatikusan hozzáad egy `Nette\Mail\Mailer` szolgáltatást a [konfiguráció |#Configuring] alapján a DI konténerhez, amit [függőségi injektálással |dependency-injection:passing-dependencies] átadva kapunk meg. +A keretrendszer automatikusan hozzáad egy `Nette\Mail\Mailer` típusú szolgáltatást a DI konténerhez, amely a [#konfiguráció] alapján van összeállítva, és amelyhez úgy juthat hozzá, hogy [dependency injection |dependency-injection:passing-dependencies] segítségével átadja magának. -SendmailMailer .[#toc-sendmailmailer] -------------------------------------- +SendmailMailer +-------------- -Az alapértelmezett levelező a SendmailMailer, amely a [php:mail] PHP függvényt használja. Használati példa: +Az alapértelmezett mailer a SendmailMailer, amely a PHP [php:mail] függvényét használja. Példa a használatra: ```php $mailer = new Nette\Mail\SendmailMailer; $mailer->send($mail); ``` -Ha a `returnPath` címet szeretné beállítani, és a szerver mégis felülírja, használja a `$mailer->commandArgs = '-fmy@email.com'` címet. +Ha be szeretné állítani a `returnPath`-t, és a szerver folyamatosan felülírja, használja a `$mailer->commandArgs = '-fmy@email.com'` parancsot. -SmtpMailer .[#toc-smtpmailer] ------------------------------ +SmtpMailer +---------- -Az SMTP-kiszolgálón keresztül történő levélküldéshez használja a `SmtpMailer` címet. +Az `SmtpMailer` az e-mailek SMTP szerveren keresztüli küldésére szolgál. ```php $mailer = new Nette\Mail\SmtpMailer( @@ -211,19 +212,19 @@ $mailer = new Nette\Mail\SmtpMailer( $mailer->send($mail); ``` -A következő további paraméterek adhatók át a konstruktornak: +A konstruktornak a következő további paramétereket lehet átadni: -* `port` - ha nincs megadva, akkor az alapértelmezett 25 vagy 465 lesz használva a `ssl` esetében. -* `timeout` - az SMTP-kapcsolat időkorlátja. -* `persistent` - állandó kapcsolat használata -* `clientHost` - ügyfélkijelölés -* `streamOptions` - lehetővé teszi a kapcsolat "SSL-kontextus beállításait":https://www.php.net/manual/en/context.ssl.php. +* `port` - ha nincs beállítva, az alapértelmezett 25 vagy 465 lesz használva `ssl` esetén +* `timeout` - időtúllépés az SMTP kapcsolathoz +* `persistent` - perzisztens kapcsolat használata +* `clientHost` - a kliens Host fejlécének beállítása +* `streamOptions` - lehetővé teszi az "SSL context options":https://www.php.net/manual/en/context.ssl.php beállítását a kapcsolathoz -FallbackMailer .[#toc-fallbackmailer] -------------------------------------- +FallbackMailer +-------------- -Nem küld e-maileket, hanem egy sor levelezőn keresztül küldi azokat. Ha az egyik levelező nem sikerül, megismétli a kísérletet a következővel. Ha az utolsó is sikertelen, akkor az elsőtől kezdi újra. +Nem küld közvetlenül e-maileket, hanem egy mailer készleten keresztül közvetíti a küldést. Ha az egyik mailer meghibásodik, megismétli a próbálkozást a következővel. Ha az utolsó is meghibásodik, újra kezdi az elsőtől. ```php $mailer = new Nette\Mail\FallbackMailer([ @@ -234,16 +235,15 @@ $mailer = new Nette\Mail\FallbackMailer([ $mailer->send($mail); ``` -A konstruktor további paraméterei közé tartozik az ismétlés száma és a várakozási idő milliszekundumban. +További paraméterként a konstruktorban megadhatjuk az ismétlések számát és a várakozási időt milliszekundumban. -DKIM .[#toc-dkim] -================= +DKIM +==== -A DKIM (DomainKeys Identified Mail) egy megbízható e-mail technológia, amely segít a hamisított üzenetek felismerésében is. Az elküldött üzenetet a feladó tartományának magánkulcsával írják alá, és ez az aláírás az e-mail fejlécében tárolódik. -A címzett szervere ezt az aláírást összehasonlítja a tartomány DNS-bejegyzésében tárolt nyilvános kulccsal. Az aláírás összevetésével kimutatható, hogy az e-mail valóban a feladó tartományából származik, és hogy az üzenetet nem módosították az üzenet továbbítása során. +A DKIM (DomainKeys Identified Mail) egy technológia az e-mailek hitelességének növelésére, amely segít a hamisított üzenetek felderítésében is. Az elküldött üzenetet a feladó domainjének privát kulcsával írják alá, és ezt az aláírást az e-mail fejlécében tárolják. A címzett szervere összehasonlítja ezt az aláírást a domain DNS rekordjaiban tárolt nyilvános kulccsal. Az aláírás egyezése bizonyítja, hogy az e-mail valóban a feladó domainjéből származik, és hogy az üzenet továbbítása során nem módosították. -A [konfigurációban |#Configuring] beállíthatja, hogy a mailer aláírja az e-maileket. Ha nem használja a függőségi injektálást, akkor a következőképpen használja: +Az e-mailek aláírását beállíthatja a mailernek közvetlenül a [konfigurációt |#Konfiguráció]. Ha nem használ dependency injectiont, akkor így használatos: ```php $signer = new Nette\Mail\DkimSigner( @@ -253,46 +253,46 @@ $signer = new Nette\Mail\DkimSigner( passPhrase: '****', ); -$mailer = new Nette\Mail\SendmailMailer; // or SmtpMailer +$mailer = new Nette\Mail\SendmailMailer; // vagy SmtpMailer $mailer->setSigner($signer); $mailer->send($mail); ``` -A konfigurálása. .[#toc-configuring] -===================================== +Konfiguráció +============ -A Nette Mail konfigurációs lehetőségeinek áttekintése. Ha nem a teljes keretrendszert, hanem csak ezt a könyvtárat használja, olvassa el [, hogyan töltse be a konfigurációt |bootstrap:]. +A Nette Mail konfigurációs opcióinak áttekintése. Ha nem a teljes keretrendszert használja, csak ezt a könyvtárat, olvassa el, [hogyan kell betölteni a konfigurációt|bootstrap:]. -Alapértelmezés szerint a `Nette\Mail\SendmailMailer` levelezőt használja az e-mailek küldésére, amely nincs tovább konfigurálva. Azonban átállíthatjuk a `Nette\Mail\SmtpMailer` címre: +Az e-mailek küldéséhez alapértelmezés szerint a `Nette\Mail\SendmailMailer` mailert használjuk, amelyet nem kell tovább konfigurálni. Átkapcsolhatjuk azonban a `Nette\Mail\SmtpMailer`-re: ```neon mail: - # use SmtpMailer - smtp: true # (bool) alapértelmezett értéke false + # SmtpMailer használata + smtp: true # (bool) alapértelmezett: false host: ... # (string) port: ... # (int) username: ... # (string) password: ... # (string) timeout: ... # (int) - encryption: ... # (ssl|tls|null) alapértelmezés szerint null (alias 'secure') - clientHost: ... # (string) alapértelmezett értéke $_SERVER['HTTP_HOST'] - persistent: ... # (bool) alapértelmezés szerint false + encryption: ... # (ssl|tls|null) alapértelmezett: null (alias: 'secure') + clientHost: ... # (string) alapértelmezett: $_SERVER['HTTP_HOST'] + persistent: ... # (bool) alapértelmezett: false - # kontextus az SMTP-kiszolgálóhoz való csatlakozáshoz, alapértelmezés szerint stream_context_get_default() + # kontextus az SMTP szerverhez való csatlakozáshoz, alapértelmezett: stream_context_get_default() context: - ssl: # all options at https://www.php.net/manual/en/context.ssl.php + ssl: # opciók áttekintése: https://www.php.net/manual/en/context.ssl.php allow_self_signed: ... ... - http: # minden opció a https://www.php.net/manual/en/context.http.php oldalon + http: # opciók áttekintése: https://www.php.net/manual/en/context.http.php header: ... ... ``` -A `context › ssl › verify_peer: false` opcióval kikapcsolhatjuk az SSL-tanúsítvány hitelesítését. Ezt **határozottan nem ajánlott** megtenni, mivel sebezhetővé teszi az alkalmazást. Ehelyett a "tanúsítványok hozzáadása a bizalmi tárolóhoz":https://www.php.net/manual/en/openssl.configuration.php. +A `context › ssl › verify_peer: false` opcióval kikapcsolható az SSL tanúsítványok ellenőrzése. **Erősen nem ajánljuk** ezt tenni, mert az alkalmazás sebezhetővé válik. Ehelyett "adja hozzá a tanúsítványokat a tárolóhoz":https://www.php.net/manual/en/openssl.configuration.php. -A megbízhatóság növelése érdekében aláírhatjuk az e-maileket a [DKIM technológia |https://blog.nette.org/hu/e-mailek-alairasa-dkim-mel] segítségével: +A hitelesség növelése érdekében az e-maileket aláírhatjuk [DKIM technológiával |https://blog.nette.org/en/how-to-sign-emails-using-dkim]: ```neon mail: @@ -304,4 +304,12 @@ mail: ``` -{{leftbar: nette:@menu-topics}} +DI Szolgáltatások +================= + +Ezek a szolgáltatások kerülnek hozzáadásra a DI konténerhez: + +| Név | Típus | Leírás +|----------------------------------------------------- +| `mail.mailer` | [api:Nette\Mail\Mailer] | [e-maileket küldő osztály |#E-mail küldése] +| `mail.signer` | [api:Nette\Mail\Signer] | [DKIM aláírás |#DKIM] diff --git a/mail/hu/@meta.texy b/mail/hu/@meta.texy new file mode 100644 index 0000000000..c00a2158aa --- /dev/null +++ b/mail/hu/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette dokumentáció}} +{{leftbar: nette:@menu-topics}} diff --git a/mail/it/@home.texy b/mail/it/@home.texy index c8d15ea6b6..85ed958601 100644 --- a/mail/it/@home.texy +++ b/mail/it/@home.texy @@ -1,97 +1,98 @@ -Invio di e-mail -*************** +Nette Mail +********** <div class=perex> -Avete intenzione di inviare e-mail come newsletter o conferme d'ordine? Nette Framework fornisce gli strumenti necessari con un'API molto bella. Vi mostreremo: +State per inviare e-mail, ad esempio newsletter o conferme d'ordine? Nette Framework fornisce gli strumenti necessari con un'API molto piacevole. Vedremo: -- come creare un'e-mail, compresi gli allegati +- come creare un'e-mail, incluse le allegate - come inviarla -- come combinare e-mail e modelli +- come collegare e-mail e template </div> -Installazione .[#toc-installation] -================================== +Installazione +============= -Scaricare e installare il pacchetto utilizzando [Composer |best-practices:composer]: +Scaricate e installate la libreria utilizzando lo strumento [Composer|best-practices:composer]: ```shell composer require nette/mail ``` -Creazione di e-mail .[#toc-creating-emails] -=========================================== +Creazione di un'e-mail +====================== -L'e-mail è un oggetto [api:Nette\Mail\Message]: +L'e-mail è un oggetto della classe [api:Nette\Mail\Message]. Lo creiamo ad esempio così: ```php $mail = new Nette\Mail\Message; -$mail->setFrom('John <john@example.com>') - ->addTo('peter@example.com') - ->addTo('jack@example.com') - ->setSubject('Order Confirmation') - ->setBody("Hello, Your order has been accepted."); +$mail->setFrom('Franta <franta@example.com>') + ->addTo('petr@example.com') + ->addTo('jirka@example.com') + ->setSubject('Conferma ordine') + ->setBody("Buongiorno,\nil suo ordine è stato ricevuto."); ``` -Tutti i parametri devono essere codificati in UTF-8. +Tutti i parametri inseriti devono essere in UTF-8. -Oltre a specificare i destinatari con il metodo `addTo()`, è possibile specificare il destinatario della copia con `addCc()`, o il destinatario della copia cieca con `addBcc()`. Tutti questi metodi, compreso `setFrom()`, accettano il destinatario in tre modi: +Oltre a specificare il destinatario con il metodo `addTo()`, è possibile specificare anche il destinatario della copia `addCc()`, o il destinatario della copia nascosta `addBcc()`. In tutti questi metodi, incluso `setFrom()`, possiamo scrivere l'indirizzo in tre modi: ```php -$mail->setFrom('john.doe@example.com'); -$mail->setFrom('john.doe@example.com', 'John Doe'); -$mail->setFrom('John Doe <john.doe@example.com>'); +$mail->setFrom('franta@example.com'); +$mail->setFrom('franta@example.com', 'Franta'); +$mail->setFrom('Franta <franta@example.com>'); ``` -Il corpo di un messaggio di posta elettronica scritto in HTML viene passato con il metodo `setHtmlBody()`: +Il corpo dell'e-mail scritto in HTML viene passato con il metodo `setHtmlBody()`: ```php -$mail->setHtmlBody('<p>Hello,</p><p>Your order has been accepted.</p>'); +$mail->setHtmlBody('<p>Buongiorno,</p><p>il suo ordine è stato ricevuto.</p>'); ``` -Non è necessario creare un testo alternativo, Nette lo genererà automaticamente per voi. E se l'email non ha un oggetto impostato, questo verrà preso dall'elemento `<title>` elemento. +Non è necessario creare l'alternativa testuale, Nette la genererà automaticamente per voi. E se l'e-mail non ha un oggetto impostato, cercherà di prenderlo dall'elemento `<title>`. -Anche le immagini possono essere inserite con estrema facilità nel corpo HTML di un'e-mail. Basta passare il percorso in cui si trovano fisicamente le immagini come secondo parametro e Nette le includerà automaticamente nell'e-mail: +È anche straordinariamente facile inserire immagini nel corpo HTML. Basta passare come secondo parametro il percorso dove si trovano fisicamente le immagini, e Nette le includerà automaticamente nell'e-mail: ```php -// aggiunge automaticamente /path/to/images/background.gif all'email +// aggiunge automaticamente /path/to/images/background.gif all'e-mail $mail->setHtmlBody( - '<b>Ciao</b> <img src="background.gif">', + '<b>Hello</b> <img src="background.gif">', '/path/to/images', ); ``` -L'algoritmo di incorporamento delle immagini supporta i seguenti pattern: `<img src=...>`, `<body background=...>`, `url(...)` all'interno dell'attributo HTML `style` e la sintassi speciale `[[...]]`. +L'algoritmo che inserisce le immagini cerca questi pattern: `<img src=...>`, `<body background=...>`, `url(...)` all'interno dell'attributo HTML `style` e la sintassi speciale `[[...]]`. L'invio di e-mail può essere ancora più semplice? -Le e-mail sono come cartoline. Non inviate mai password o altre credenziali via e-mail. .[tip] +.[tip] +L'e-mail è come una cartolina. Non inviate mai password o altri dati di accesso via e-mail. -Allegati .[#toc-attachments] ----------------------------- +Allegati +-------- -Naturalmente è possibile allegare allegati alle e-mail. Utilizzate l'indirizzo `addAttachment(string $file, string $content = null, string $contentType = null)`. +Naturalmente, è possibile inserire allegati nell'e-mail. A questo serve il metodo `addAttachment(string $file, ?string $content = null, ?string $contentType = null)`. ```php -// inserisce il file /path/to/example.zip nella mail con il nome example.zip +// inserisce il file /path/to/example.zip nell'e-mail con il nome example.zip $mail->addAttachment('/path/to/example.zip'); -// inserisce il file /path/to/example.zip nell'e-mail con il nome info.zip +// inserisce il file /path/to/example.zip nell'e-mail chiamato info.zip $mail->addAttachment('info.zip', file_get_contents('/path/to/example.zip')); -// Allega il nuovo contenuto del file example.txt "Hello John!". +// inserisce il file example.txt nell'e-mail con il contenuto "Hello John!" $mail->addAttachment('example.txt', 'Hello John!'); ``` -Modelli .[#toc-templates] -------------------------- +Template +-------- -Se inviate e-mail in HTML, è un'ottima idea scriverle nel sistema di template [Latte |latte:]. Come fare? +Se inviate e-mail HTML, è naturale scriverle nel sistema di template [Latte|latte:]. Come fare? ```php $latte = new Latte\Engine; @@ -100,8 +101,8 @@ $params = [ ]; $mail = new Nette\Mail\Message; -$mail->setFrom('John <john@example.com>') - ->addTo('jack@example.com') +$mail->setFrom('Franta <franta@example.com>') + ->addTo('petr@example.com') ->setHtmlBody( $latte->renderToString('/path/to/email.latte', $params), '/path/to/images', @@ -114,7 +115,7 @@ File `email.latte`: <html> <head> <meta charset="utf-8"> - <title>Order Confirmation + Conferma ordine -

                                                                                                                                        Hello,

                                                                                                                                        +

                                                                                                                                        Buongiorno,

                                                                                                                                        -

                                                                                                                                        Your order number {$orderId} has been accepted.

                                                                                                                                        +

                                                                                                                                        Il suo ordine numero {$orderId} è stato ricevuto.

                                                                                                                                        ``` -Nette inserisce automaticamente tutte le immagini, imposta l'oggetto in base all'elemento `` e genera un testo alternativo per il corpo dell'HTML. +Nette inserirà automaticamente tutte le immagini, imposterà l'oggetto secondo l'elemento `<title>` e genererà l'alternativa testuale all'HTML. -Utilizzo nell'applicazione Nette .[#toc-using-in-nette-application] -------------------------------------------------------------------- +Utilizzo in Nette Application +----------------------------- -Se utilizzate i messaggi di posta elettronica insieme all'applicazione Nette, ad esempio i presentatori, potreste voler creare dei collegamenti nei modelli utilizzando l'attributo `n:href` o il tag `{link}`. Latte fondamentalmente non li conosce, ma è molto facile aggiungerli. La creazione di collegamenti può essere fatta con l'oggetto `Nette\Application\LinkGenerator`, che si ottiene passandoglielo con la [dependency injection |dependency-injection:passing-dependencies]. +Se usate le e-mail insieme a Nette Application, cioè con i presenter, potreste voler creare link nei template usando l'attributo `n:href` o il tag `{link}`. Latte non li conosce di base, ma è molto facile aggiungerli. L'oggetto `Nette\Application\LinkGenerator` può creare link, e potete ottenerlo facendovelo passare tramite [dependency injection |dependency-injection:passing-dependencies]: ```php use Nette; @@ -168,38 +169,38 @@ class MailSender } ``` -Nel template, il collegamento viene creato come in un normale template. Tutti i collegamenti creati con LinkGenerator sono assoluti: +Nel template, creiamo quindi i link come siamo abituati. Tutti i link creati tramite LinkGenerator saranno assoluti. ```latte <a n:href="Presenter:action">Link</a> ``` -Invio di e-mail .[#toc-sending-emails] -====================================== +Invio dell'e-mail +================= -Mailer è la classe responsabile dell'invio delle e-mail. Implementa l'interfaccia [api:Nette\Mail\Mailer] e sono disponibili diversi mailer già pronti, che presenteremo. +Mailer è la classe che si occupa dell'invio delle e-mail. Implementa l'interfaccia [api:Nette\Mail\Mailer] e sono disponibili diversi mailer predefiniti che presenteremo. -Il framework aggiunge automaticamente al contenitore DI un servizio `Nette\Mail\Mailer` basato sulla [configurazione |#Configuring], che si ottiene passandoglielo tramite [dependency injection |dependency-injection:passing-dependencies]. +Il framework aggiunge automaticamente al container DI un servizio di tipo `Nette\Mail\Mailer` costruito sulla base della [#configurazione], a cui potete accedere facendovelo passare tramite [dependency injection |dependency-injection:passing-dependencies]. -SendmailMailer .[#toc-sendmailmailer] -------------------------------------- +SendmailMailer +-------------- -Il mailer predefinito è SendmailMailer che utilizza la funzione PHP [php:mail]. Esempio di utilizzo: +Il mailer predefinito è SendmailMailer, che utilizza la funzione PHP [php:mail]. Esempio di utilizzo: ```php $mailer = new Nette\Mail\SendmailMailer; $mailer->send($mail); ``` -Se si vuole impostare `returnPath` e il server lo sovrascrive comunque, usare `$mailer->commandArgs = '-fmy@email.com'`. +Se volete impostare `returnPath` e il server continua a sovrascriverlo, usate `$mailer->commandArgs = '-fMio@email.it'`. -SmtpMailer .[#toc-smtpmailer] ------------------------------ +SmtpMailer +---------- -Per inviare posta tramite il server SMTP, utilizzare `SmtpMailer`. +Per inviare posta tramite un server SMTP si usa `SmtpMailer`. ```php $mailer = new Nette\Mail\SmtpMailer( @@ -211,19 +212,19 @@ $mailer = new Nette\Mail\SmtpMailer( $mailer->send($mail); ``` -Al costruttore possono essere passati i seguenti parametri aggiuntivi: +Al costruttore possono essere passati questi altri parametri: -* `port` - se non impostato, verrà utilizzato il valore predefinito 25 o 465 per `ssl` +* `port` - se non impostato, viene utilizzato il predefinito 25 o 465 per `ssl` * `timeout` - timeout per la connessione SMTP -* `persistent` - utilizzare una connessione persistente -* `clientHost` - designazione del client -* `streamOptions` - permette di impostare le "opzioni del contesto SSL":https://www.php.net/manual/en/context.ssl.php per la connessione +* `persistent` - usare una connessione persistente +* `clientHost` - impostazione dell'header Host del client +* `streamOptions` - consente di impostare le "SSL context options":https://www.php.net/manual/en/context.ssl.php per la connessione -FallbackMailer .[#toc-fallbackmailer] -------------------------------------- +FallbackMailer +-------------- -Non invia i messaggi di posta elettronica, ma li invia attraverso un insieme di mailer. Se un mailer fallisce, ripete il tentativo con quello successivo. Se l'ultimo fallisce, ricomincia dal primo. +Non invia direttamente le e-mail, ma ne gestisce l'invio tramite un set di mailer. Nel caso in cui un mailer fallisca, ripete il tentativo con il successivo. Se fallisce anche l'ultimo, ricomincia dal primo. ```php $mailer = new Nette\Mail\FallbackMailer([ @@ -234,16 +235,15 @@ $mailer = new Nette\Mail\FallbackMailer([ $mailer->send($mail); ``` -Altri parametri del costruttore sono il numero di ripetizioni e il tempo di attesa in millisecondi. +Come altri parametri nel costruttore possiamo specificare il numero di ripetizioni e il tempo di attesa in millisecondi. -DKIM .[#toc-dkim] -================= +DKIM +==== -DKIM (DomainKeys Identified Mail) è una tecnologia di posta elettronica affidabile che aiuta anche a rilevare i messaggi falsificati. Il messaggio inviato viene firmato con la chiave privata del dominio del mittente e questa firma viene memorizzata nell'intestazione dell'e-mail. -Il server del destinatario confronta questa firma con la chiave pubblica memorizzata nei record DNS del dominio. La corrispondenza della firma dimostra che l'e-mail proviene effettivamente dal dominio del mittente e che il messaggio non è stato modificato durante la trasmissione. +DKIM (DomainKeys Identified Mail) è una tecnologia per aumentare l'affidabilità delle e-mail, che aiuta anche a rilevare messaggi contraffatti. Il messaggio inviato è firmato con la chiave privata del dominio del mittente e questa firma è memorizzata nell'intestazione dell'e-mail. Il server del destinatario confronta questa firma con la chiave pubblica memorizzata nei record DNS del dominio. Se la firma corrisponde, viene dimostrato che l'e-mail proviene effettivamente dal dominio del mittente e che durante la trasmissione del messaggio non è stata apportata alcuna modifica. -È possibile impostare il mailer per firmare le e-mail nella [configurazione |#Configuring]. Se non si usa l'iniezione di dipendenza, si usa come segue: +Potete impostare la firma delle e-mail per il mailer direttamente nella [#configurazione]. Se non usate la dependency injection, si usa in questo modo: ```php $signer = new Nette\Mail\DkimSigner( @@ -253,46 +253,46 @@ $signer = new Nette\Mail\DkimSigner( passPhrase: '****', ); -$mailer = new Nette\Mail\SendmailMailer; // or SmtpMailer +$mailer = new Nette\Mail\SendmailMailer; // o SmtpMailer $mailer->setSigner($signer); $mailer->send($mail); ``` -Configurazione di .[#toc-configuring] -===================================== +Configurazione +============== -Panoramica delle opzioni di configurazione di Nette Mail. Se non si utilizza l'intero framework, ma solo questa libreria, leggere [come caricare la configurazione |bootstrap:]. +Panoramica delle opzioni di configurazione per Nette Mail. Se non utilizzate l'intero framework, ma solo questa libreria, leggete [come caricare la configurazione|bootstrap:]. -Per impostazione predefinita, per l'invio delle e-mail viene utilizzato il mailer `Nette\Mail\SendmailMailer`, che non viene ulteriormente configurato. Tuttavia, è possibile passare a `Nette\Mail\SmtpMailer`: +Per l'invio di e-mail si utilizza di default il mailer `Nette\Mail\SendmailMailer`, che non viene ulteriormente configurato. Possiamo però passare a `Nette\Mail\SmtpMailer`: ```neon mail: # usa SmtpMailer - smtp: true # (bool) predefinito a false + smtp: true # (bool) predefinito è false host: ... # (string) port: ... # (int) username: ... # (string) password: ... # (string) timeout: ... # (int) - encryption: ... # (ssl|tls|null) predefinito a null (ha l'alias 'secure') - clientHost: ... # (string) predefinito a $_SERVER['HTTP_HOST'] - persistent: ... # (bool) predefinito a false + encryption: ... # (ssl|tls|null) predefinito è null (ha alias 'secure') + clientHost: ... # (string) predefinito è $_SERVER['HTTP_HOST'] + persistent: ... # (bool) predefinito è false - # contesto per la connessione al server SMTP, predefinito con stream_context_get_default() + # contesto per la connessione al server SMTP, predefinito è stream_context_get_default() context: - ssl: # tutte le opzioni a https://www.php.net/manual/en/context.ssl.php + ssl: # panoramica delle opzioni su https://www.php.net/manual/en/context.ssl.php allow_self_signed: ... ... - http: # tutte le opzioni in https://www.php.net/manual/en/context.http.php + http: # panoramica delle opzioni su https://www.php.net/manual/en/context.http.php header: ... ... ``` -È possibile disabilitare l'autenticazione del certificato SSL utilizzando l'opzione `context › ssl › verify_peer: false`. Si consiglia vivamente di non farlo**, in quanto renderebbe l'applicazione vulnerabile. Invece, "aggiungere certificati all'archivio di fiducia":https://www.php.net/manual/en/openssl.configuration.php. +Con l'opzione `context › ssl › verify_peer: false` è possibile disattivare la verifica dei certificati SSL. **Sconsigliamo vivamente** di farlo, perché l'applicazione diventerebbe vulnerabile. Invece, "aggiungete i certificati allo store":https://www.php.net/manual/en/openssl.configuration.php. -Per aumentare l'affidabilità, è possibile firmare le e-mail utilizzando la [tecnologia DKIM |https://blog.nette.org/it/firmare-le-e-mail-con-dkim]: +Per aumentare l'affidabilità, possiamo firmare le e-mail utilizzando la [tecnologia DKIM |https://blog.nette.org/it/sign-emails-with-dkim]: ```neon mail: @@ -304,4 +304,12 @@ mail: ``` -{{leftbar: nette:@menu-topics}} +Servizi DI +========== + +Questi servizi vengono aggiunti al container DI: + +| Nome | Tipo | Descrizione +|------------------------------------------------------ +| `mail.mailer` | [api:Nette\Mail\Mailer] | [classe che invia le e-mail |#Invio dell e-mail] +| `mail.signer` | [api:Nette\Mail\Signer] | [Firma DKIM |#DKIM] diff --git a/mail/it/@meta.texy b/mail/it/@meta.texy new file mode 100644 index 0000000000..9d19e7312c --- /dev/null +++ b/mail/it/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Documentazione Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/mail/ja/@home.texy b/mail/ja/@home.texy new file mode 100644 index 0000000000..1039b83f85 --- /dev/null +++ b/mail/ja/@home.texy @@ -0,0 +1,315 @@ +Nette Mail +********** + +<div class=perex> + +ニュースレターや注文確認などのメールを送信しようとしていますか? Nette Frameworkは、非常に使いやすいAPIを備えた必要なツールを提供します。以下に示します: + +- 添付ファイルを含むメールの作成方法 +- 送信方法 +- メールとテンプレートの結合方法 + +</div> + + +インストール +====== + +ライブラリは[Composer|best-practices:composer]ツールを使用してダウンロードおよびインストールします: + +```shell +composer require nette/mail +``` + + +メールの作成 +====== + +メールは[api:Nette\Mail\Message]クラスのオブジェクトです。次のように作成します: + +```php +$mail = new Nette\Mail\Message; +$mail->setFrom('Franta <franta@example.com>') + ->addTo('petr@example.com') + ->addTo('jirka@example.com') + ->setSubject('注文確認') + ->setBody("こんにちは、\nご注文は受け付けられました。"); +``` + +入力されるすべてのパラメータはUTF-8である必要があります。 + +`addTo()`メソッドで受信者を指定する以外に、コピーの受信者を`addCc()`で、またはブラインドコピーの受信者を`addBcc()`で指定することもできます。`setFrom()`を含むこれらすべてのメソッドで、受信者を3つの方法で記述できます: + +```php +$mail->setFrom('franta@example.com'); +$mail->setFrom('franta@example.com', 'Franta'); +$mail->setFrom('Franta <franta@example.com>'); +``` + +HTMLで記述されたメール本文は`setHtmlBody()`メソッドで渡されます: + +```php +$mail->setHtmlBody('<p>こんにちは、</p><p>ご注文は受け付けられました。</p>'); +``` + +テキスト版を作成する必要はありません。Netteが自動的に生成します。また、メールに件名が設定されていない場合、`<title>`要素から取得しようとします。 + +HTML本文に画像を埋め込むことも非常に簡単です。画像が物理的に存在するパスを2番目のパラメータとして渡すだけで、Netteは自動的にメールに含めます: + +```php +// 自動的に /path/to/images/background.gif をメールに追加します +$mail->setHtmlBody( + '<b>Hello</b> <img src="background.gif">', + '/path/to/images', +); +``` + +画像を埋め込むアルゴリズムは、次のパターンを検索します:`<img src=...>`、`<body background=...>`、HTML属性`style`内の`url(...)`、および特別な構文`[[...]]`。 + +メール送信はもっと簡単になるでしょうか? + +.[tip] +メールははがきのようなものです。メールでパスワードやその他のアクセス情報を送信しないでください。 + + +添付ファイル +------ + +もちろん、メールに添付ファイルを追加することもできます。これには`addAttachment(string $file, ?string $content = null, ?string $contentType = null)`メソッドを使用します。 + +```php +// /path/to/example.zip ファイルを example.zip という名前でメールに挿入します +$mail->addAttachment('/path/to/example.zip'); + +// /path/to/example.zip ファイルを info.zip という名前でメールに挿入します +$mail->addAttachment('info.zip', file_get_contents('/path/to/example.zip')); + +// "Hello John!" という内容の example.txt ファイルをメールに挿入します +$mail->addAttachment('example.txt', 'Hello John!'); +``` + + +テンプレート +------ + +HTMLメールを送信する場合、[Latte|latte:]テンプレートシステムで記述するのが自然です。どうすればよいでしょうか? + +```php +$latte = new Latte\Engine; +$params = [ + 'orderId' => 123, +]; + +$mail = new Nette\Mail\Message; +$mail->setFrom('Franta <franta@example.com>') + ->addTo('petr@example.com') + ->setHtmlBody( + $latte->renderToString('/path/to/email.latte', $params), + '/path/to/images', + ); +``` + +`email.latte`ファイル: + +```latte +<html> +<head> + <meta charset="utf-8"> + <title>注文確認 + + + +

                                                                                                                                        こんにちは、

                                                                                                                                        + +

                                                                                                                                        注文番号 {$orderId} のご注文は受け付けられました。

                                                                                                                                        + + +``` + +Netteは自動的にすべての画像を挿入し、``要素に基づいて件名を設定し、HTMLのテキスト代替を生成します。 + + +Nette Applicationでの使用 +--------------------- + +Nette Application、つまりPresenterと一緒にメールを使用する場合、テンプレート内で`n:href`属性または`{link}`タグを使用してリンクを作成したい場合があります。Latteはデフォルトではこれらを知りませんが、追加するのは非常に簡単です。リンクの作成は`Nette\Application\LinkGenerator`オブジェクトが行い、[依存性注入 |dependency-injection:passing-dependencies]を使用して渡してもらうことでアクセスできます: + +```php +use Nette; + +class MailSender +{ + public function __construct( + private Nette\Application\LinkGenerator $linkGenerator, + private Nette\Bridges\ApplicationLatte\TemplateFactory $templateFactory, + ) { + } + + private function createTemplate(): Nette\Application\UI\Template + { + $template = $this->templateFactory->createTemplate(); + $template->getLatte()->addProvider('uiControl', $this->linkGenerator); + return $template; + } + + public function createEmail(): Nette\Mail\Message + { + $template = $this->createTemplate(); + $html = $template->renderToString('/path/to/email.latte', $params); + + $mail = new Nette\Mail\Message; + $mail->setHtmlBody($html); + // ... + return $mail; + } +} +``` + +テンプレートでは、慣れている方法でリンクを作成します。LinkGeneratorを介して作成されたすべてのリンクは絶対リンクになります。 + +```latte +<a n:href="Presenter:action">リンク</a> +``` + + +メールの送信 +====== + +Mailerはメール送信を担当するクラスです。[api:Nette\Mail\Mailer]インターフェースを実装しており、いくつかの事前に準備されたメーラーが利用可能です。これらを紹介します。 + +フレームワークは、[#設定]に基づいて構築された`Nette\Mail\Mailer`型のサービスをDIコンテナに自動的に追加します。このサービスには、[依存性注入 |dependency-injection:passing-dependencies]を使用して渡してもらうことでアクセスできます。 + + +SendmailMailer +-------------- + +デフォルトのメーラーはSendmailMailerで、PHP関数[php:mail]を使用します。使用例: + +```php +$mailer = new Nette\Mail\SendmailMailer; +$mailer->send($mail); +``` + +`returnPath`を設定したいが、サーバーがそれを上書きし続ける場合は、`$mailer->commandArgs = '-fMuj@email.cz'`を使用します。 + + +SmtpMailer +---------- + +SMTPサーバー経由でメールを送信するには、`SmtpMailer`を使用します。 + +```php +$mailer = new Nette\Mail\SmtpMailer( + host: 'smtp.gmail.com', + username: 'franta@gmail.com', + password: '*****', + encryption: 'ssl', +); +$mailer->send($mail); +``` + +コンストラクタには、次の追加パラメータを渡すことができます: + +* `port` - 設定されていない場合、デフォルトの25または`ssl`の場合は465が使用されます +* `timeout` - SMTP接続のタイムアウト +* `persistent` - 永続的な接続を使用する +* `clientHost` - クライアントのHostヘッダーを設定する +* `streamOptions` - 接続のための"SSLコンテキストオプション":https://www.php.net/manual/en/context.ssl.phpを設定できます + + +FallbackMailer +-------------- + +メールを直接送信するのではなく、一連のメーラーを介して送信を仲介します。1つのメーラーが失敗した場合、次のメーラーで試行を繰り返します。最後のメーラーも失敗した場合、最初のメーラーから再度開始します。 + +```php +$mailer = new Nette\Mail\FallbackMailer([ + $smtpMailer, + $backupSmtpMailer, + $sendmailMailer, +]); +$mailer->send($mail); +``` + +コンストラクタの追加パラメータとして、試行回数とミリ秒単位の待機時間を指定できます。 + + +DKIM +==== + +DKIM(DomainKeys Identified Mail)は、メールの信頼性を高める技術であり、偽装されたメッセージの検出にも役立ちます。送信されたメッセージは送信者のドメインの秘密鍵で署名され、この署名はメールのヘッダーに保存されます。 受信者のサーバーはこの署名を、ドメインのDNSレコードに保存されている公開鍵と比較します。署名が一致することで、メールが実際に送信者のドメインから来たものであり、メッセージの転送中に変更されていないことが証明されます。 + +メールの署名は、[#設定]でメーラーに直接設定できます。依存性注入を使用しない場合は、次のように使用します: + +```php +$signer = new Nette\Mail\DkimSigner( + domain: 'nette.org', + selector: 'dkim', + privateKey: file_get_contents('../dkim/dkim.key'), + passPhrase: '****', +); + +$mailer = new Nette\Mail\SendmailMailer; // または SmtpMailer +$mailer->setSigner($signer); +$mailer->send($mail); +``` + + +設定 +=========== + +Nette Mailの設定オプションの概要。フレームワーク全体ではなく、このライブラリのみを使用している場合は、[設定の読み込み方法|bootstrap:]をお読みください。 + +メール送信には、デフォルトで`Nette\Mail\SendmailMailer`メーラーが使用され、これはさらに設定されません。ただし、`Nette\Mail\SmtpMailer`に切り替えることができます: + +```neon +mail: + # SmtpMailer を使用します + smtp: true # (bool) デフォルトは false + + host: ... # (string) + port: ... # (int) + username: ... # (string) + password: ... # (string) + timeout: ... # (int) + encryption: ... # (ssl|tls|null) デフォルトは null ('secure' というエイリアスがあります) + clientHost: ... # (string) デフォルトは $_SERVER['HTTP_HOST'] + persistent: ... # (bool) デフォルトは false + + # SMTP サーバーへの接続コンテキスト、デフォルトは stream_context_get_default() + context: + ssl: # オプションの概要は https://www.php.net/manual/en/context.ssl.php + allow_self_signed: ... + ... + http: # オプションの概要は https://www.php.net/manual/en/context.http.php + header: ... + ... +``` + +`context › ssl › verify_peer: false`オプションを使用すると、SSL証明書の検証を無効にできます。アプリケーションが脆弱になるため、**これを行うことは強くお勧めしません**。代わりに、"証明書をストアに追加してください":https://www.php.net/manual/en/openssl.configuration.php。 + +信頼性を高めるために、[DKIM テクノロジー |https://blog.nette.org/en/sign-emails-with-dkim]を使用してメールに署名できます: + +```neon +mail: + dkim: + domain: myweb.com + selector: lovenette + privateKey: %appDir%/cert/dkim.priv + passPhrase: ... +``` + + +DIサービス +====== + +これらのサービスはDIコンテナに追加されます: + +| 名前 | 型 | 説明 +|----------------------------------------------------- +| `mail.mailer` | [api:Nette\Mail\Mailer] | [メールを送信するクラス |#メールの送信] +| `mail.signer` | [api:Nette\Mail\Signer] | [DKIM 署名 |#DKIM] diff --git a/mail/ja/@meta.texy b/mail/ja/@meta.texy new file mode 100644 index 0000000000..7d67dcb7b8 --- /dev/null +++ b/mail/ja/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette ドキュメンテーション}} +{{leftbar: nette:@menu-topics}} diff --git a/mail/pl/@home.texy b/mail/pl/@home.texy index 1ea4a78fbe..8ad5efa7bd 100644 --- a/mail/pl/@home.texy +++ b/mail/pl/@home.texy @@ -1,44 +1,44 @@ -Wysyłanie e-maili -***************** +Nette Mail +********** <div class=perex> -Czy zamierzasz wysyłać maile takie jak newslettery czy potwierdzenia zamówień? Nette Framework dostarcza niezbędnych narzędzi z bardzo ładnym API. Zobaczmy: +Chcesz wysyłać e-maile, na przykład newslettery lub potwierdzenia zamówień? Nette Framework dostarcza niezbędnych narzędzi z bardzo przyjaznym API. Pokażemy: -- jak stworzyć wiadomość e-mail zawierającą załączniki +- jak utworzyć e-mail wraz z załącznikami - jak go wysłać -- jak łączyć e-maile i szablony +- jak połączyć e-maile i szablony </div> -Instalacja .[#toc-installation] -=============================== +Instalacja +========== -Pobierz i zainstaluj bibliotekę za pomocą [Composera |best-practices:composer]: +Bibliotekę pobierzesz i zainstalujesz za pomocą narzędzia [Composer|best-practices:composer]: ```shell composer require nette/mail ``` -Tworzenie wiadomości e-mail .[#toc-creating-emails] -=================================================== +Tworzenie e-maila +================= -E-mail jest obiektem klasy [api:Nette\Mail\Message]. Możemy go utworzyć w taki sposób: +E-mail jest obiektem klasy [api:Nette\Mail\Message]. Utworzymy go na przykład tak: ```php $mail = new Nette\Mail\Message; $mail->setFrom('Franta <franta@example.com>') ->addTo('petr@example.com') ->addTo('jirka@example.com') - ->setSubject('Potvrzení objednávky') - ->setBody("Dobrý den,\nvaše objednávka byla přijata."); + ->setSubject('Potwierdzenie zamówienia') + ->setBody("Dzień dobry,\nTwoje zamówienie zostało przyjęte."); ``` -Wszystkie parametry muszą być w języku UTF-8. +Wszystkie podawane parametry muszą być w UTF-8. -Oprócz określenia odbiorcy za pomocą metody `addTo()`, można również określić odbiorcę kopii `addCc()`, lub odbiorcę ukrytej kopii `addBcc()`. We wszystkich tych metodach, łącznie z `setFrom()`, można określić odbiorcę na trzy sposoby: +Oprócz podania odbiorcy metodą `addTo()`, można również podać odbiorcę kopii `addCc()` lub odbiorcę ukrytej kopii `addBcc()`. We wszystkich tych metodach, włącznie z `setFrom()`, adresata możemy zapisać na trzy sposoby: ```php $mail->setFrom('franta@example.com'); @@ -46,52 +46,53 @@ $mail->setFrom('franta@example.com', 'Franta'); $mail->setFrom('Franta <franta@example.com>'); ``` -Ciało maila zapisane w HTML jest przekazywane za pomocą metody `setHtmlBody()`: +Treść e-maila zapisaną w HTML przekazuje się metodą `setHtmlBody()`: ```php -$mail->setHtmlBody('<p>Dobrý den,</p><p>vaše objednávka byla přijata.</p>'); +$mail->setHtmlBody('<p>Dzień dobry,</p><p>Twoje zamówienie zostało przyjęte.</p>'); ``` -Nie musisz tworzyć tekstu alternatywnego, Nette wygeneruje go automatycznie za Ciebie. A jeśli mail nie ma ustawionego tematu, to spróbuje go pobrać z elementu `<title>`. +Nie musisz tworzyć alternatywy tekstowej, Nette wygeneruje ją automatycznie za Ciebie. A jeśli e-mail nie ma ustawionego tematu, spróbuje go pobrać z elementu `<title>`. -Niezwykle łatwe jest również wstawianie obrazów do ciała HTML. Wystarczy przekazać ścieżkę, gdzie fizycznie znajdują się obrazki jako drugi parametr, a Nette automatycznie dołączy je do maila: +W treści HTML można również niezwykle łatwo wstawiać obrazy. Wystarczy jako drugi parametr przekazać ścieżkę, gdzie obrazy fizycznie się znajdują, a Nette automatycznie dołączy je do e-maila: ```php -// automatycznie dodaje /path/to/images/background.gif do wiadomości e-mail +// automatycznie doda /path/to/images/background.gif do e-maila $mail->setHtmlBody( - '<b>Witam</b> <img src="background.gif">', + '<b>Hello</b> <img src="background.gif">', '/path/to/images', ); ``` -Algorytm wstawiający obrazy szuka tych wzorców: `<img src=...>`, `<body background=...>`, `url(...)` wewnątrz atrybutu HTML `style` oraz specjalna składnia `[[...]]`. +Algorytm wstawiający obrazy wyszukuje następujące wzorce: `<img src=...>`, `<body background=...>`, `url(...)` wewnątrz atrybutu HTML `style` oraz specjalną składnię `[[...]]`. -Czy wysyłanie maili może być jeszcze prostsze? +Czy wysyłanie e-maili może być jeszcze prostsze? -E-mail jest jak pocztówka. Nigdy nie wysyłaj haseł ani innych danych dostępowych pocztą elektroniczną. .[tip] +.[tip] +E-mail jest jak pocztówka. Nigdy nie wysyłaj e-mailem haseł ani innych danych dostępowych. -Załączniki .[#toc-attachments] ------------------------------- +Załączniki +---------- -Do maila można oczywiście dodać załączniki. Odbywa się to za pomocą metody `addAttachment(string $file, string $content = null, string $contentType = null)`. +Do e-maila można oczywiście wstawiać załączniki. Służy do tego metoda `addAttachment(string $file, ?string $content = null, ?string $contentType = null)`. ```php -// wstawia do maila plik /path/to/example.zip pod nazwą example.zip +// wstawia plik /path/to/example.zip do e-maila pod nazwą example.zip $mail->addAttachment('/path/to/example.zip'); -// wstawia do maila plik /path/to/example.zip o nazwie info.zip +// wstawia plik /path/to/example.zip do e-maila o nazwie info.zip $mail->addAttachment('info.zip', file_get_contents('/path/to/example.zip')); -// wstawia do maila plik example.txt o treści "Witaj John!". +// wstawia plik example.txt do e-maila z zawartością "Hello John!" $mail->addAttachment('example.txt', 'Hello John!'); ``` -Szablony .[#toc-templates] --------------------------- +Szablony +-------- -Jeśli wysyłasz e-maile w formacie HTML, warto napisać je w systemie szablonów [Latte |latte:]. Jak to zrobić? +Jeśli wysyłasz e-maile HTML, naturalnym rozwiązaniem jest zapisywanie ich w systemie szablonów [Latte|latte:]. Jak to zrobić? ```php $latte = new Latte\Engine; @@ -114,7 +115,7 @@ Plik `email.latte`: <html> <head> <meta charset="utf-8"> - <title>Potvrzení objednávky + Potwierdzenie zamówienia -

                                                                                                                                        Dobrý den,

                                                                                                                                        +

                                                                                                                                        Dzień dobry,

                                                                                                                                        -

                                                                                                                                        Vaše objednávka číslo {$orderId} byla přijata.

                                                                                                                                        +

                                                                                                                                        Twoje zamówienie numer {$orderId} zostało przyjęte.

                                                                                                                                        ``` -Nette automatycznie osadza wszystkie zdjęcia, ustawia temat według elementu `` i generuje tekstową alternatywę dla HTML. +Nette automatycznie wstawi wszystkie obrazy, ustawi temat według elementu `<title>` i wygeneruje alternatywę tekstową dla HTML. -Zastosowanie w aplikacji Nette .[#toc-using-in-nette-application] ------------------------------------------------------------------ +Użycie w Nette Application +-------------------------- -Jeśli używasz e-maili w połączeniu z Nette Application, tj. prezenterami, możesz chcieć tworzyć linki w szablonach za pomocą atrybutu `n:href` lub tagu `{link}`. Nie są one domyślnie znane przez Latte, ale są bardzo łatwe do dodania. Obiekt `Nette\Application\LinkGenerator` może tworzyć linki, do których możesz uzyskać dostęp, przekazując je za pomocą [zastrzyku zależności |dependency-injection:passing-dependencies]: +Jeśli używasz e-maili razem z Nette Application, tj. z presenterami, możesz chcieć tworzyć linki w szablonach za pomocą atrybutu `n:href` lub znacznika `{link}`. Latte domyślnie ich nie zna, ale bardzo łatwo je dodać. Tworzyć linki potrafi obiekt `Nette\Application\LinkGenerator`, do którego można uzyskać dostęp, prosząc o jego przekazanie za pomocą [wstrzykiwania zależności |dependency-injection:passing-dependencies]: ```php use Nette; @@ -168,23 +169,23 @@ class MailSender } ``` -Następnie tworzymy linki w szablonie tak jak jesteśmy do tego przyzwyczajeni. Wszystkie linki utworzone za pośrednictwem LinkGeneratora będą miały charakter bezwzględny. +W szablonie tworzymy linki tak, jak jesteśmy przyzwyczajeni. Wszystkie linki utworzone przez LinkGenerator będą absolutne. ```latte -<a n:href="Presenter:action">Odkaz</a> +<a n:href="Presenter:action">Link</a> ``` -Wysyłanie wiadomości e-mail .[#toc-sending-emails] -================================================== +Wysyłanie e-maila +================= -Mailer to klasa służąca do wysyłania wiadomości e-mail. Implementuje on interfejs [api:Nette\Mail\Mailer], a także istnieje kilka gotowych mailerów, które przedstawimy. +Mailer to klasa zapewniająca wysyłanie e-maili. Implementuje interfejs [api:Nette\Mail\Mailer] i dostępnych jest kilka gotowych mailerów, które przedstawimy. -Framework automatycznie dodaje usługę taką jak `Nette\Mail\Mailer` zbudowaną na podstawie [konfiguracji |#Configuring] do kontenera DI, i do której możesz uzyskać dostęp, mając ją przekazaną do ciebie za pomocą [zastrzyku zależności |dependency-injection:passing-dependencies]. +Framework automatycznie dodaje do kontenera DI usługę typu `Nette\Mail\Mailer` skonfigurowaną na podstawie [Konfiguracji |#Konfiguracja], do której można uzyskać dostęp, prosząc o jej przekazanie za pomocą [wstrzykiwania zależności |dependency-injection:passing-dependencies]. -SendmailMailer .[#toc-sendmailmailer] -------------------------------------- +SendmailMailer +-------------- Domyślnym mailerem jest SendmailMailer, który używa funkcji PHP [php:mail]. Przykład użycia: @@ -193,13 +194,13 @@ $mailer = new Nette\Mail\SendmailMailer; $mailer->send($mail); ``` -Jeśli chcesz ustawić `returnPath`, a serwer ciągle go nadpisuje, użyj `$mailer->commandArgs = '-fMuj@email.cz'`. +Jeśli chcesz ustawić `returnPath`, a serwer ciągle go nadpisuje, użyj `$mailer->commandArgs = '-fMoj@email.cz'`. -SmtpMailer .[#toc-smtpmailer] ------------------------------ +SmtpMailer +---------- -Aby wysłać pocztę przez serwer SMTP, należy użyć `SmtpMailer`. +Do wysyłania poczty przez serwer SMTP służy `SmtpMailer`. ```php $mailer = new Nette\Mail\SmtpMailer( @@ -211,19 +212,19 @@ $mailer = new Nette\Mail\SmtpMailer( $mailer->send($mail); ``` -Do konstruktora można przekazać następujące dodatkowe parametry: +Konstruktorowi można przekazać następujące dodatkowe parametry: -* `port` - jeśli nie jest ustawiony, to domyślnie 25 lub 465 jest używany dla `ssl` +* `port` - jeśli nie jest ustawiony, użyje się domyślnego 25 lub 465 dla `ssl` * `timeout` - timeout dla połączenia SMTP -* `persistent` - użyj trwałego połączenia -* `clientHost` - ustawienie nagłówka Host Client -* `streamOptions` - pozwala na ustawienie "opcji kontekstu SSL":https://www.php.net/manual/en/context.ssl.php dla połączenia +* `persistent` - użyj połączenia trwałego +* `clientHost` - ustawienie nagłówka Host klienta +* `streamOptions` - umożliwia ustawienie "SSL context options":https://www.php.net/manual/en/context.ssl.php dla połączenia -FallbackMailer .[#toc-fallbackmailer] -------------------------------------- +FallbackMailer +-------------- -Nie wysyła maili bezpośrednio, ale pośredniczy w wysyłce poprzez zestaw mailerów. Jeśli jeden mailer się nie powiedzie, ponawia próbę następnego. Jeśli ostatni się nie powiedzie, zaczyna się od pierwszego. +Nie wysyła e-maili bezpośrednio, ale pośredniczy w wysyłaniu przez zestaw mailerów. W przypadku, gdy jeden mailer zawiedzie, ponawia próbę u następnego. Jeśli zawiedzie i ostatni, zaczyna ponownie od pierwszego. ```php $mailer = new Nette\Mail\FallbackMailer([ @@ -234,16 +235,15 @@ $mailer = new Nette\Mail\FallbackMailer([ $mailer->send($mail); ``` -Jako dodatkowe parametry w konstruktorze możemy podać liczbę retries oraz czas oczekiwania w milisekundach. +Jako dodatkowe parametry w konstruktorze możemy podać liczbę powtórzeń i czas oczekiwania w milisekundach. -DKIM .[#toc-dkim] -================= +DKIM +==== -DKIM (DomainKeys Identified Mail) to technologia zwiększania wiarygodności e-maili, która pomaga również w wykrywaniu fałszywych wiadomości. Wysłana wiadomość jest podpisywana kluczem prywatnym domeny nadawcy, a podpis ten jest przechowywany w nagłówku wiadomości e-mail. -Serwer odbiorcy dopasowuje ten podpis do klucza publicznego przechowywanego w rekordach DNS domeny. Dzięki dopasowaniu podpisu udowadnia się, że e-mail rzeczywiście pochodzi z domeny nadawcy i że wiadomość nie została zmodyfikowana podczas transmisji. +DKIM (DomainKeys Identified Mail) to technologia zwiększająca wiarygodność e-maili, która również pomaga w wykrywaniu sfałszowanych wiadomości. Wysłana wiadomość jest podpisywana prywatnym kluczem domeny nadawcy, a ten podpis jest przechowywany w nagłówku e-maila. Serwer odbiorcy porównuje ten podpis z publicznym kluczem przechowywanym w rekordach DNS domeny. Zgodność podpisu dowodzi, że e-mail rzeczywiście pochodzi z domeny nadawcy i że podczas przesyłania wiadomości nie doszło do jej modyfikacji. -Możesz skonfigurować podpisywanie wiadomości e-mail bezpośrednio w [konfiguracji |#Configuring] mailera. Jeśli nie używasz wtrysku zależności, jest on używany w ten sposób: +Podpisywanie e-maili można ustawić w mailerze bezpośrednio w [konfiguracji |#Konfiguracja]. Jeśli nie używasz wstrzykiwania zależności, używa się go w ten sposób: ```php $signer = new Nette\Mail\DkimSigner( @@ -253,22 +253,22 @@ $signer = new Nette\Mail\DkimSigner( passPhrase: '****', ); -$mailer = new Nette\Mail\SendmailMailer; // nebo SmtpMailer +$mailer = new Nette\Mail\SendmailMailer; // lub SmtpMailer $mailer->setSigner($signer); $mailer->send($mail); ``` -Konfiguracja .[#toc-configuring] -================================ +Konfiguracja +============ -Przegląd opcji konfiguracyjnych dla Nette Mail. Jeśli nie używasz całego frameworka, a jedynie tej biblioteki, przeczytaj [jak załadować konfigurację |bootstrap:]. +Przegląd opcji konfiguracyjnych dla Nette Mail. Jeśli nie używasz całego frameworka, a tylko tej biblioteki, przeczytaj, [jak wczytać konfigurację|bootstrap:]. -Domyślnie do wysyłania e-maili używany jest mailer `Nette\Mail\SendmailMailer`, który nie jest dalej konfigurowany. Możemy jednak przełączyć ją na `Nette\Mail\SmtpMailer`: +Do wysyłania e-maili standardowo używa się mailera `Nette\Mail\SendmailMailer`, który nie wymaga dalszej konfiguracji. Możemy go jednak przełączyć na `Nette\Mail\SmtpMailer`: ```neon mail: - # use SmtpMailer + # użyje SmtpMailer smtp: true # (bool) domyślnie false host: ... # (string) @@ -277,22 +277,22 @@ mail: password: ... # (string) timeout: ... # (int) encryption: ... # (ssl|tls|null) domyślnie null (ma alias 'secure') - clientHost: ... # (string) domyślnie jest $_SERVER['HTTP_HOST'] - persistent: ... # (bool) domyślnie jest false + clientHost: ... # (string) domyślnie $_SERVER['HTTP_HOST'] + persistent: ... # (bool) domyślnie false - # kontekst do połączenia z serwerem SMTP, domyślnie jest to stream_context_get_default() + # kontekst do połączenia z serwerem SMTP, domyślnie stream_context_get_default() context: - ssl: # przegląd opcji na stronie https://www.php.net/manual/en/context.ssl.php + ssl: # przegląd opcji na https://www.php.net/manual/en/context.ssl.php allow_self_signed: ... ... - http: # podsumowanie wyborów na stronie https://www.php.net/manual/en/context.http.php + http: # przegląd opcji na https://www.php.net/manual/en/context.http.php header: ... ... ``` -Za pomocą opcji `context › ssl › verify_peer: false` możemy wyłączyć uwierzytelnianie certyfikatu SSL. **Silnie radzimy**, aby tego nie robić, ponieważ spowoduje to, że aplikacja będzie podatna na uszkodzenia. Zamiast tego "dodaj certyfikaty do repozytorium":https://www.php.net/manual/en/openssl.configuration.php. +Za pomocą opcji `context › ssl › verify_peer: false` można wyłączyć weryfikację certyfikatów SSL. **Zdecydowanie odradzamy** tego robić, ponieważ aplikacja stanie się podatna na ataki. Zamiast tego "dodaj certyfikaty do magazynu":https://www.php.net/manual/en/openssl.configuration.php. -Aby zwiększyć wiarygodność, możemy podpisywać e-maile za pomocą [technologii DKIM |https://blog.nette.org/pl/podpisuj-wiadomosci-e-mail-za-pomoca-dkim]: +Aby zwiększyć wiarygodność, możemy podpisywać e-maile za pomocą [technologii DKIM |https://blog.nette.org/pl/sign-emails-with-dkim]: ```neon mail: @@ -304,4 +304,12 @@ mail: ``` -{{leftbar: nette:@menu-topics}} +Usługi DI +========= + +Te usługi są dodawane do kontenera DI: + +| Nazwa | Typ | Opis +|----------------------------------------------------- +| `mail.mailer` | [api:Nette\Mail\Mailer] | [klasa wysyłająca e-maile |#Wysyłanie e-maila] +| `mail.signer` | [api:Nette\Mail\Signer] | [Podpisywanie DKIM |#DKIM] diff --git a/mail/pl/@meta.texy b/mail/pl/@meta.texy new file mode 100644 index 0000000000..08f2227fb5 --- /dev/null +++ b/mail/pl/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Dokumentacja Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/mail/pt/@home.texy b/mail/pt/@home.texy index 8ac6006723..12ea217584 100644 --- a/mail/pt/@home.texy +++ b/mail/pt/@home.texy @@ -1,97 +1,98 @@ -Envio de e-mails -**************** +Nette Mail +********** <div class=perex> -Você vai enviar e-mails como boletins informativos ou confirmações de pedidos? Nette Framework fornece as ferramentas necessárias com uma API muito boa. Vamos mostrar: +Você vai enviar e-mails, como newsletters ou confirmações de pedidos? O Nette Framework fornece as ferramentas necessárias com uma API muito agradável. Vamos mostrar: -- como criar um e-mail, incluindo os anexos +- como criar um e-mail, incluindo anexos - como enviá-lo -- como combinar e-mails e modelos +- como conectar e-mails e templates </div> -Instalação .[#toc-installation] -=============================== +Instalação +========== -Baixe e instale o pacote usando [o Composer |best-practices:composer]: +Baixe e instale a biblioteca usando o [Composer |best-practices:composer]: ```shell composer require nette/mail ``` -Criando e-mails .[#toc-creating-emails] -======================================= +Criando um e-mail +================= -O e-mail é um objeto [api:Nette\Mail\Message]: +Um e-mail é um objeto da classe [api:Nette\Mail\Message]. Vamos criá-lo assim: ```php $mail = new Nette\Mail\Message; -$mail->setFrom('John <john@example.com>') - ->addTo('peter@example.com') - ->addTo('jack@example.com') - ->setSubject('Order Confirmation') - ->setBody("Hello, Your order has been accepted."); +$mail->setFrom('Franta <franta@example.com>') + ->addTo('petr@example.com') + ->addTo('jirka@example.com') + ->setSubject('Confirmação do pedido') + ->setBody("Olá,\nseu pedido foi recebido."); ``` -Todos os parâmetros devem ser codificados em UTF-8. +Todos os parâmetros inseridos devem estar em UTF-8. -Além de especificar os destinatários com o método `addTo()`, você também pode especificar o destinatário da cópia com `addCc()`, ou o destinatário da cópia cega com `addBcc()`. Todos esses métodos, incluindo `setFrom()`, aceitam o destinatário de três maneiras: +Além de especificar o destinatário com o método `addTo()`, também é possível especificar o destinatário da cópia `addCc()`, ou o destinatário da cópia oculta `addBcc()`. Em todos esses métodos, incluindo `setFrom()`, podemos escrever o destinatário de três maneiras: ```php -$mail->setFrom('john.doe@example.com'); -$mail->setFrom('john.doe@example.com', 'John Doe'); -$mail->setFrom('John Doe <john.doe@example.com>'); +$mail->setFrom('franta@example.com'); +$mail->setFrom('franta@example.com', 'Franta'); +$mail->setFrom('Franta <franta@example.com>'); ``` -O corpo de um e-mail escrito em HTML é passado usando o método `setHtmlBody()`: +O corpo do e-mail escrito em HTML é passado pelo método `setHtmlBody()`: ```php -$mail->setHtmlBody('<p>Hello,</p><p>Your order has been accepted.</p>'); +$mail->setHtmlBody('<p>Olá,</p><p>seu pedido foi recebido.</p>'); ``` -Você não tem que criar uma alternativa de texto, Nette a gerará automaticamente para você. E se o e-mail não tiver um conjunto de assuntos, ele será retirado do `<title>` elemento. +Você não precisa criar a alternativa de texto, o Nette a gerará automaticamente para você. E se o e-mail não tiver um assunto definido, ele tentará pegá-lo do elemento `<title>`. -As imagens também podem ser inseridas com extrema facilidade no corpo HTML de um e-mail. Basta passar o caminho onde as imagens estão fisicamente localizadas como segundo parâmetro, e a Nette as incluirá automaticamente no e-mail: +Também é extraordinariamente fácil inserir imagens no corpo HTML. Basta passar o caminho onde as imagens estão fisicamente localizadas como segundo parâmetro, e o Nette as incluirá automaticamente no e-mail: ```php -// adiciona automaticamente /caminho/para/imagens/background.gif ao e-mail +// adiciona automaticamente /path/to/images/background.gif ao e-mail $mail->setHtmlBody( - <b>Hello</b> <img src="background.gif">", - /caminho/para/imagens', + '<b>Olá</b> <img src="background.gif">', + '/path/to/images', ); ``` -O algoritmo de incorporação de imagem suporta os seguintes padrões: `<img src=...>`, `<body background=...>`, `url(...)` dentro do atributo HTML `style` e sintaxe especial `[[...]]`. +O algoritmo que insere imagens procura por estes padrões: `<img src=...>`, `<body background=...>`, `url(...)` dentro do atributo HTML `style` e a sintaxe especial `[[...]]`. -O envio de e-mails pode ser ainda mais fácil? +Enviar e-mails pode ser ainda mais fácil? -Os e-mails são como cartões postais. Nunca envie senhas ou outras credenciais por e-mail. .[tip] +.[tip] +Um e-mail é como um cartão postal. Nunca envie senhas ou outras credenciais por e-mail. -Anexos .[#toc-attachments] --------------------------- +Anexos +------ -Você pode, é claro, anexar anexos ao e-mail. Use o site `addAttachment(string $file, string $content = null, string $contentType = null)`. +Claro, anexos podem ser inseridos no e-mail. O método `addAttachment(string $file, ?string $content = null, ?string $contentType = null)` é usado para isso. ```php -// insere o arquivo /caminho/para/exemplo.zip no e-mail com o nome exemplo.zip -$mail->addAttachment('/caminho/para/exemplo.zip'); +// insere o arquivo /path/to/example.zip no e-mail com o nome example.zip +$mail->addAttachment('/path/to/example.zip'); -// insere o arquivo /caminho/para/exemplo.zip no e-mail sob o nome info.zip +// insere o arquivo /path/to/example.zip no e-mail chamado info.zip $mail->addAttachment('info.zip', file_get_contents('/path/to/example.zip')); -// anexa novo conteúdo do arquivo exemplo.txt "Olá John! +// insere o arquivo example.txt no e-mail com o conteúdo "Olá John!" $mail->addAttachment('example.txt', 'Olá John!'); ``` -Modelos .[#toc-templates] -------------------------- +Templates +--------- -Se você enviar e-mails HTML, é uma ótima idéia escrevê-los no sistema de modelos [Latte |latte:]. Como fazer isso? +Se você envia e-mails HTML, é natural escrevê-los no sistema de templates [Latte |latte:]. Como fazer isso? ```php $latte = new Latte\Engine; @@ -100,8 +101,8 @@ $params = [ ]; $mail = new Nette\Mail\Message; -$mail->setFrom('John <john@example.com>') - ->addTo('jack@example.com') +$mail->setFrom('Franta <franta@example.com>') + ->addTo('petr@example.com') ->setHtmlBody( $latte->renderToString('/path/to/email.latte', $params), '/path/to/images', @@ -114,7 +115,7 @@ Arquivo `email.latte`: <html> <head> <meta charset="utf-8"> - <title>Order Confirmation + Confirmação do pedido -

                                                                                                                                        Hello,

                                                                                                                                        +

                                                                                                                                        Olá,

                                                                                                                                        -

                                                                                                                                        Your order number {$orderId} has been accepted.

                                                                                                                                        +

                                                                                                                                        Seu pedido número {$orderId} foi recebido.

                                                                                                                                        ``` -Nette insere automaticamente todas as imagens, define o assunto de acordo com o `` e gera uma alternativa de texto para o corpo HTML. +O Nette insere automaticamente todas as imagens, define o assunto de acordo com o elemento `<title>` e gera uma alternativa de texto para o HTML. -Utilização na aplicação Nette .[#toc-using-in-nette-application] ----------------------------------------------------------------- +Uso no Nette Application +------------------------ -Se você utiliza e-mails juntamente com a Aplicação Nette, ou seja, apresentadores, você pode querer criar links em modelos usando o atributo `n:href` ou a tag `{link}`. O Latte basicamente não os conhece, mas é muito fácil adicioná-los. A criação de links pode fazer o objeto `Nette\Application\LinkGenerator`, que você obtém passando por ele usando a [injeção de dependência |dependency-injection:passing-dependencies]. +Se você usa e-mails em conjunto com o Nette Application, ou seja, com presenters, pode querer criar links nos templates usando o atributo `n:href` ou a tag `{link}`. O Latte não os conhece por padrão, mas é muito fácil adicioná-los. O objeto `Nette\Application\LinkGenerator` pode criar links, e você pode acessá-lo pedindo para ser passado via [injeção de dependência |dependency-injection:passing-dependencies]: ```php use Nette; @@ -168,38 +169,38 @@ class MailSender } ``` -No modelo, o link é criado como em um modelo normal. Todos os links criados sobre o LinkGenerator são absolutos: +No template, criamos links da maneira que estamos acostumados. Todos os links criados através do LinkGenerator serão absolutos. ```latte <a n:href="Presenter:action">Link</a> ``` -Envio de e-mails .[#toc-sending-emails] -======================================= +Enviando e-mail +=============== -O Mailer é a classe responsável pelo envio de e-mails. Ele implementa a interface [api:Nette\Mail\Mailer] e vários mailers prontos estão disponíveis que apresentaremos. +Mailer é uma classe que garante o envio de e-mails. Implementa a interface [api:Nette\Mail\Mailer] e existem vários mailers pré-preparados disponíveis, que apresentaremos. -A estrutura adiciona automaticamente um serviço `Nette\Mail\Mailer` baseado na [configuração |#Configuring] do recipiente DI, que você obtém passando-o usando a [injeção de dependência |dependency-injection:passing-dependencies]. +O framework adiciona automaticamente um serviço do tipo `Nette\Mail\Mailer` ao contêiner DI, construído com base na [#configuração], e que você pode acessar pedindo para ser passado via [injeção de dependência |dependency-injection:passing-dependencies]. -SendmailMailer .[#toc-sendmailmailer] -------------------------------------- +SendmailMailer +-------------- -O mailer padrão é o SendmailMailer que usa a função PHP [php:mail]. Exemplo de uso: +O mailer padrão é o SendmailMailer, que usa a função PHP [php:mail]. Exemplo de uso: ```php $mailer = new Nette\Mail\SendmailMailer; $mailer->send($mail); ``` -Se você quiser configurar `returnPath` e o servidor ainda o sobrescreve, use `$mailer->commandArgs = '-fmy@email.com'`. +Se você quiser definir o `returnPath` e o servidor ainda o sobrescrever, use `$mailer->commandArgs = '-fMeu@email.cz'`. -SmtpMailer .[#toc-smtpmailer] ------------------------------ +SmtpMailer +---------- -Para enviar correio através do servidor SMTP, use `SmtpMailer`. +Para enviar e-mails através de um servidor SMTP, use `SmtpMailer`. ```php $mailer = new Nette\Mail\SmtpMailer( @@ -213,17 +214,17 @@ $mailer->send($mail); Os seguintes parâmetros adicionais podem ser passados para o construtor: -* `port` - se não estiver definido, será utilizado o padrão 25 ou 465 para `ssl` -* `timeout` - tempo limite para conexão SMTP +* `port` - se não definido, o padrão 25 ou 465 para `ssl` será usado +* `timeout` - timeout para a conexão SMTP * `persistent` - usar conexão persistente -* `clientHost` - designação do cliente -* `streamOptions` - permite definir "opções de contexto SSL":https://www.php.net/manual/en/context.ssl.php para conexão +* `clientHost` - definir o cabeçalho Host do cliente +* `streamOptions` - permite definir "SSL context options":https://www.php.net/manual/en/context.ssl.php para a conexão -FallbackMailer .[#toc-fallbackmailer] -------------------------------------- +FallbackMailer +-------------- -Ele não envia e-mails, mas os envia através de um conjunto de remetentes. Se um remetente falhar, ele repete a tentativa no próximo. Se o último falhar, ele começa novamente a partir do primeiro. +Não envia e-mails diretamente, mas medeia o envio através de um conjunto de mailers. Se um mailer falhar, ele repete a tentativa com o próximo. Se o último também falhar, ele começa novamente do primeiro. ```php $mailer = new Nette\Mail\FallbackMailer([ @@ -234,16 +235,15 @@ $mailer = new Nette\Mail\FallbackMailer([ $mailer->send($mail); ``` -Outros parâmetros no construtor incluem o número de repetições e tempo de espera em milissegundos. +Como parâmetros adicionais no construtor, podemos especificar o número de repetições e o tempo de espera em milissegundos. -DKIM .[#toc-dkim] -================= +DKIM +==== -O DKIM (DomainKeys Identified Mail) é uma tecnologia de e-mail confiável que também ajuda a detectar mensagens falsificadas. A mensagem enviada é assinada com a chave privada do domínio do remetente e esta assinatura é armazenada no cabeçalho do e-mail. -O servidor do destinatário compara esta assinatura com a chave pública armazenada nos registros DNS do domínio. Combinando a assinatura, é mostrado que o e-mail realmente originou-se do domínio do remetente e que a mensagem não foi modificada durante a transmissão da mensagem. +DKIM (DomainKeys Identified Mail) é uma tecnologia para aumentar a confiabilidade dos e-mails, que também ajuda a detectar mensagens falsificadas. A mensagem enviada é assinada com a chave privada do domínio do remetente e esta assinatura é armazenada no cabeçalho do e-mail. O servidor do destinatário compara esta assinatura com a chave pública armazenada nos registros DNS do domínio. Se a assinatura corresponder, prova-se que o e-mail realmente veio do domínio do remetente e que não foi modificado durante a transmissão da mensagem. -Você pode configurar o mailer para assinar o e-mail na [configuração |#Configuring]. Se você não usa injeção de dependência, ela é usada da seguinte forma: +Você pode configurar a assinatura de e-mail para o mailer diretamente na [#configuração]. Se você não estiver usando injeção de dependência, ele é usado desta forma: ```php $signer = new Nette\Mail\DkimSigner( @@ -253,46 +253,46 @@ $signer = new Nette\Mail\DkimSigner( passPhrase: '****', ); -$mailer = new Nette\Mail\SendmailMailer; // or SmtpMailer +$mailer = new Nette\Mail\SendmailMailer; // ou SmtpMailer $mailer->setSigner($signer); $mailer->send($mail); ``` -Configurando .[#toc-configuring] -================================ +Configuração +============ -Visão geral das opções de configuração para o Nette Mail. Se você não estiver usando toda a estrutura, mas apenas esta biblioteca, leia [como carregar a configuração |bootstrap:]. +Visão geral das opções de configuração para Nette Mail. Se você não estiver usando todo o framework, mas apenas esta biblioteca, leia [como carregar a configuração |bootstrap:]. -Por padrão, o mailer `Nette\Mail\SendmailMailer` é usado para enviar e-mails, que não está mais configurado. Entretanto, podemos mudá-lo para `Nette\Mail\SmtpMailer`: +Para enviar e-mails, o mailer `Nette\Mail\SendmailMailer` é usado por padrão, que não é configurado posteriormente. No entanto, podemos mudá-lo para `Nette\Mail\SmtpMailer`: ```neon mail: - # usar SmtpMailer - smtp: true # (bool) default to false + # usa SmtpMailer + smtp: true # (bool) o padrão é false host: ... # (string) port: ... # (int) username: ... # (string) password: ... # (string) timeout: ... # (int) - encryption: ... # (ssl|tls|null) padrão a nulo (tem pseudônimo 'seguro') - clientHost: ... # (string) padrão de $_SERVER['HTTP_HOST'] - persistent: ... # (bool) falha em falso + encryption: ... # (ssl|tls|null) o padrão é null (tem alias 'secure') + clientHost: ... # (string) o padrão é $_SERVER['HTTP_HOST'] + persistent: ... # (bool) o padrão é false - # contexto para conexão com o servidor SMTP, padrão para stream_context_get_default() + # contexto para conexão com o servidor SMTP, o padrão é stream_context_get_default() context: - ssl: # todas as opções em https://www.php.net/manual/en/context.ssl.php - permit_signed_signed: ... + ssl: # visão geral das opções em https://www.php.net/manual/en/context.ssl.php + allow_self_signed: ... ... - http: # todas as opções em https://www.php.net/manual/en/context.http.php + http: # visão geral das opções em https://www.php.net/manual/en/context.http.php header: ... ... ``` -Você pode desativar a autenticação do certificado SSL usando a opção `context › ssl › verify_peer: false`. É ** fortemente recomendado não fazer** isso, pois isso tornará a aplicação vulnerável. Em vez disso, "adicionar certificados à loja de confiança":https://www.php.net/manual/en/openssl.configuration.php. +Usando a opção `context › ssl › verify_peer: false`, você pode desativar a verificação do certificado SSL. **É altamente recomendável não fazer isso**, pois a aplicação se tornará vulnerável. Em vez disso, "adicione os certificados ao armazenamento":https://www.php.net/manual/en/openssl.configuration.php. -Para aumentar a confiança, podemos assinar e-mails usando [a tecnologia DKIM |https://blog.nette.org/pt/assine-e-mails-com-dkim]: +Para aumentar a confiabilidade, podemos assinar e-mails usando a [tecnologia DKIM |https://blog.nette.org/en/sign-emails-using-dkim]: ```neon mail: @@ -304,4 +304,12 @@ mail: ``` -{{leftbar: nette:@menu-topics}} +Serviços DI +=========== + +Estes serviços são adicionados ao contêiner DI: + +| Nome | Tipo | Descrição +|------------------------------------------------------ +| `mail.mailer` | [api:Nette\Mail\Mailer] | [classe que envia e-mails |#Enviando e-mail] +| `mail.signer` | [api:Nette\Mail\Signer] | [Assinatura DKIM |#DKIM] diff --git a/mail/pt/@meta.texy b/mail/pt/@meta.texy new file mode 100644 index 0000000000..e2566bcb44 --- /dev/null +++ b/mail/pt/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Documentação Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/mail/ro/@home.texy b/mail/ro/@home.texy index 0a20bd94c7..065fdee9b2 100644 --- a/mail/ro/@home.texy +++ b/mail/ro/@home.texy @@ -1,9 +1,9 @@ -Trimiterea de e-mailuri -*********************** +Nette Mail +********** <div class=perex> -Aveți de gând să trimiteți e-mailuri, cum ar fi buletine informative sau confirmări de comenzi? Nette Framework oferă instrumentele necesare cu un API foarte bun. Vom arăta: +Sunteți pe cale să trimiteți e-mailuri, de exemplu, newslettere sau confirmări de comandă? Nette Framework oferă instrumentele necesare cu o API foarte prietenoasă. Vă vom arăta: - cum să creați un e-mail, inclusiv atașamente - cum să îl trimiteți @@ -12,86 +12,87 @@ Aveți de gând să trimiteți e-mailuri, cum ar fi buletine informative sau con </div> -Instalare .[#toc-installation] -============================== +Instalare +========= -Descărcați și instalați pachetul folosind [Composer |best-practices:composer]: +Descărcați și instalați biblioteca folosind [Composer|best-practices:composer]: ```shell composer require nette/mail ``` -Crearea de e-mailuri .[#toc-creating-emails] -============================================ +Crearea unui e-mail +=================== -E-mailul este un obiect [api:Nette\Mail\Message]: +E-mailul este un obiect al clasei [api:Nette\Mail\Message]. Îl creăm, de exemplu, astfel: ```php $mail = new Nette\Mail\Message; -$mail->setFrom('John <john@example.com>') - ->addTo('peter@example.com') - ->addTo('jack@example.com') - ->setSubject('Order Confirmation') - ->setBody("Hello, Your order has been accepted."); +$mail->setFrom('Franta <franta@example.com>') + ->addTo('petr@example.com') + ->addTo('jirka@example.com') + ->setSubject('Confirmare comandă') + ->setBody("Bună ziua,\ncomanda dvs. a fost primită."); ``` -Toți parametrii trebuie să fie codificați în UTF-8. +Toți parametrii introduși trebuie să fie în UTF-8. -Pe lângă specificarea destinatarilor cu metoda `addTo()`, puteți specifica și destinatarul copiei cu `addCc()`, sau destinatarul copiei oarbe cu `addBcc()`. Toate aceste metode, inclusiv `setFrom()`, acceptă destinatarii în trei moduri: +Pe lângă specificarea destinatarului cu metoda `addTo()`, puteți specifica și destinatarul copiei `addCc()`, sau destinatarul copiei ascunse `addBcc()`. În toate aceste metode, inclusiv `setFrom()`, putem scrie adresa în trei moduri: ```php -$mail->setFrom('john.doe@example.com'); -$mail->setFrom('john.doe@example.com', 'John Doe'); -$mail->setFrom('John Doe <john.doe@example.com>'); +$mail->setFrom('franta@example.com'); +$mail->setFrom('franta@example.com', 'Franta'); +$mail->setFrom('Franta <franta@example.com>'); ``` -Corpul unui e-mail scris în HTML este transmis cu ajutorul metodei `setHtmlBody()`: +Corpul e-mailului scris în HTML se transmite prin metoda `setHtmlBody()`: ```php -$mail->setHtmlBody('<p>Hello,</p><p>Your order has been accepted.</p>'); +$mail->setHtmlBody('<p>Bună ziua,</p><p>comanda dvs. a fost primită.</p>'); ``` -Nu trebuie să creați o alternativă de text, Nette o va genera automat pentru dumneavoastră. Iar dacă e-mailul nu are un subiect setat, acesta va fi preluat din fișierul `<title>` element. +Nu trebuie să creați alternativa text, Nette o va genera automat pentru dvs. Și dacă e-mailul nu are setat un subiect, va încerca să îl preia din elementul `<title>`. -Imaginile pot fi, de asemenea, extrem de ușor de inserat în corpul HTML al unui e-mail. Trebuie doar să treceți ca al doilea parametru calea în care se află fizic imaginile, iar Nette le va include automat în e-mail: +De asemenea, puteți insera imagini în corpul HTML extrem de ușor. Este suficient să transmiteți calea unde se află fizic imaginile ca al doilea parametru, iar Nette le va include automat în e-mail: ```php -// adaugă automat /path/to/images/background.gif la e-mail +// adaugă automat /path/to/images/background.gif în e-mail $mail->setHtmlBody( '<b>Hello</b> <img src="background.gif">', '/path/to/images', ); ``` -Algoritmul de încorporare a imaginilor acceptă următoarele modele: `<img src=...>`, `<body background=...>`, `url(...)` în interiorul atributului HTML `style` și sintaxa specială `[[...]]`. +Algoritmul care inserează imagini caută aceste modele: `<img src=...>`, `<body background=...>`, `url(...)` în interiorul atributului HTML `style` și sintaxa specială `[[...]]`. -Poate trimiterea de e-mailuri să fie și mai ușoară? +Poate fi trimiterea de e-mailuri și mai simplă? -E-mailurile sunt ca niște cărți poștale. Nu trimiteți niciodată parole sau alte credențiale prin e-mail. .[tip] +.[tip] +E-mailul este ca o carte poștală. Nu trimiteți niciodată parole sau alte date de acces prin e-mail. -Atașamente .[#toc-attachments] ------------------------------- +Atașamente +---------- -Puteți, desigur, să atașați atașamente la e-mail. Folosiți opțiunea `addAttachment(string $file, string $content = null, string $contentType = null)`. +Desigur, puteți atașa fișiere la e-mail. Metoda `addAttachment(string $file, ?string $content = null, ?string $contentType = null)` este folosită pentru aceasta. ```php // inserează fișierul /path/to/example.zip în e-mail sub numele example.zip $mail->addAttachment('/path/to/example.zip'); -// inserează fișierul /path/to/example.zip în e-mail sub numele info.zip +// inserează fișierul /path/to/example.zip în e-mail numit info.zip $mail->addAttachment('info.zip', file_get_contents('/path/to/example.zip')); -// atașează noul conținut al fișierului example.txt "Hello John!" +// inserează fișierul example.txt în e-mail cu conținutul "Hello John!" $mail->addAttachment('example.txt', 'Hello John!'); ``` -Șabloane .[#toc-templates] --------------------------- +Șabloane +-------- -Dacă trimiteți e-mailuri HTML, este o idee excelentă să le scrieți în sistemul de șabloane [Latte |latte:]. Cum să o faceți? +Dacă trimiteți e-mailuri HTML, este firesc să le scrieți în sistemul de șabloane [Latte|latte:]. Cum se face? ```php $latte = new Latte\Engine; @@ -100,21 +101,21 @@ $params = [ ]; $mail = new Nette\Mail\Message; -$mail->setFrom('John <john@example.com>') - ->addTo('jack@example.com') +$mail->setFrom('Franta <franta@example.com>') + ->addTo('petr@example.com') ->setHtmlBody( $latte->renderToString('/path/to/email.latte', $params), '/path/to/images', ); ``` -Fișier `email.latte`: +Fișierul `email.latte`: ```latte <html> <head> <meta charset="utf-8"> - <title>Order Confirmation + Confirmare comandă -

                                                                                                                                        Hello,

                                                                                                                                        +

                                                                                                                                        Bună ziua,

                                                                                                                                        -

                                                                                                                                        Your order number {$orderId} has been accepted.

                                                                                                                                        +

                                                                                                                                        Comanda dvs. numărul {$orderId} a fost primită.

                                                                                                                                        ``` -Nette inserează automat toate imaginile, stabilește subiectul în funcție de `` element, și generează o alternativă de text pentru corpul HTML. +Nette inserează automat toate imaginile, setează subiectul conform elementului `<title>` și generează alternativa text la HTML. -Utilizare în aplicația Nette .[#toc-using-in-nette-application] ---------------------------------------------------------------- +Utilizare în Nette Application +------------------------------ -Dacă utilizați e-mailuri împreună cu Nette Application, adică prezentatori, este posibil să doriți să creați legături în șabloane utilizând atributul `n:href` sau eticheta `{link}`. Practic, Latte nu le cunoaște, dar este foarte ușor să le adăugați. Crearea de link-uri poate face obiectul `Nette\Application\LinkGenerator`, pe care îl obțineți prin trecerea acestuia folosind [injecția de dependență |dependency-injection:passing-dependencies]. +Dacă utilizați e-mailurile împreună cu Nette Application, adică cu presenteri, este posibil să doriți să creați linkuri în șabloane folosind atributul `n:href` sau tag-ul `{link}`. Latte nu le cunoaște implicit, dar este foarte ușor să le adăugați. Crearea linkurilor este gestionată de obiectul `Nette\Application\LinkGenerator`, la care puteți ajunge solicitându-l prin [injecția de dependențe |dependency-injection:passing-dependencies]: ```php use Nette; @@ -168,38 +169,38 @@ class MailSender } ``` -În șablon, legătura este creată ca într-un șablon normal. Toate legăturile create prin intermediul LinkGenerator sunt absolute: +În șablon, creăm apoi linkurile așa cum suntem obișnuiți. Toate linkurile create prin LinkGenerator vor fi absolute. ```latte <a n:href="Presenter:action">Link</a> ``` -Trimiterea de e-mailuri .[#toc-sending-emails] -============================================== +Trimiterea e-mailului +===================== -Mailer este clasa responsabilă pentru trimiterea de e-mailuri. Ea implementează interfața [api:Nette\Mail\Mailer] și sunt disponibile mai multe clase de maileri gata făcute, pe care le vom prezenta. +Mailer este clasa care asigură trimiterea e-mailurilor. Implementează interfața [api:Nette\Mail\Mailer] și sunt disponibili mai mulți maileri predefiniți, pe care îi vom prezenta. -Cadrul de lucru adaugă automat un serviciu `Nette\Mail\Mailer` bazat pe [configurație |#Configuring] la containerul DI, pe care îl obțineți trecându-l folosind [injecția de dependență |dependency-injection:passing-dependencies]. +Framework-ul adaugă automat în containerul DI un serviciu de tip `Nette\Mail\Mailer` construit pe baza [#Configurație], la care puteți ajunge solicitându-l prin [injecția de dependențe |dependency-injection:passing-dependencies]. -SendmailMailer .[#toc-sendmailmailer] -------------------------------------- +SendmailMailer +-------------- -Mailerul implicit este SendmailMailer care folosește funcția PHP [php:mail]. Exemplu de utilizare: +Mailerul implicit este SendmailMailer, care utilizează funcția PHP [php:mail]. Exemplu de utilizare: ```php $mailer = new Nette\Mail\SendmailMailer; $mailer->send($mail); ``` -Dacă doriți să setați `returnPath` și serverul încă îl suprascrie, utilizați `$mailer->commandArgs = '-fmy@email.com'`. +Dacă doriți să setați `returnPath` și serverul îl suprascrie constant, utilizați `$mailer->commandArgs = '-fMy@email.com'`. -SmtpMailer .[#toc-smtpmailer] ------------------------------ +SmtpMailer +---------- -Pentru a trimite mesaje prin intermediul serverului SMTP, utilizați `SmtpMailer`. +Pentru a trimite e-mailuri prin serverul SMTP, se utilizează `SmtpMailer`. ```php $mailer = new Nette\Mail\SmtpMailer( @@ -211,19 +212,19 @@ $mailer = new Nette\Mail\SmtpMailer( $mailer->send($mail); ``` -Următorii parametri suplimentari pot fi trecuți în constructor: +Constructorului i se pot transmite acești parametri suplimentari: -* `port` - dacă nu este setat, se va utiliza valoarea implicită 25 sau 465 pentru `ssl` +* `port` - dacă nu este setat, se utilizează implicit 25 sau 465 pentru `ssl` * `timeout` - timeout pentru conexiunea SMTP -* `persistent` - utilizează o conexiune persistentă -* `clientHost` - desemnarea clientului -* `streamOptions` - vă permite să setați "SSL context options":https://www.php.net/manual/en/context.ssl.php pentru conexiune +* `persistent` - utilizează conexiune persistentă +* `clientHost` - setarea antetului Host al clientului +* `streamOptions` - permite setarea "SSL context options":https://www.php.net/manual/en/context.ssl.php pentru conexiune -FallbackMailer .[#toc-fallbackmailer] -------------------------------------- +FallbackMailer +-------------- -Nu trimite e-mailuri, ci le trimite prin intermediul unui set de expeditori. În cazul în care un maililer eșuează, repetă încercarea la următorul. Dacă ultimul eșuează, o ia de la capăt de la primul. +Nu trimite e-mailuri direct, ci mediază trimiterea printr-un set de maileri. În cazul în care un mailer eșuează, repetă încercarea cu următorul. Dacă și ultimul eșuează, începe din nou de la primul. ```php $mailer = new Nette\Mail\FallbackMailer([ @@ -234,16 +235,15 @@ $mailer = new Nette\Mail\FallbackMailer([ $mailer->send($mail); ``` -Alți parametri din constructor includ numărul de repetări și timpul de așteptare în milisecunde. +Ca parametri suplimentari în constructor, putem specifica numărul de repetări și timpul de așteptare în milisecunde. -DKIM .[#toc-dkim] -================= +DKIM +==== -DKIM (DomainKeys Identified Mail) este o tehnologie de e-mail de încredere care ajută la detectarea mesajelor false. Mesajul trimis este semnat cu cheia privată a domeniului expeditorului, iar această semnătură este stocată în antetul e-mailului. -Serverul destinatarului compară această semnătură cu cheia publică stocată în înregistrările DNS ale domeniului. Prin compararea semnăturii, se demonstrează că mesajul electronic provine de fapt din domeniul expeditorului și că mesajul nu a fost modificat în timpul transmiterii acestuia. +DKIM (DomainKeys Identified Mail) este o tehnologie pentru creșterea credibilității e-mailurilor, care ajută și la detectarea mesajelor falsificate. Mesajul trimis este semnat cu cheia privată a domeniului expeditorului, iar această semnătură este stocată în antetul e-mailului. Serverul destinatarului compară această semnătură cu cheia publică stocată în înregistrările DNS ale domeniului. Faptul că semnătura corespunde demonstrează că e-mailul provine într-adevăr din domeniul expeditorului și că nu a fost modificat în timpul transmiterii mesajului. -Puteți seta mailer pentru a semna e-mailurile în [configurare |#Configuring]. Dacă nu utilizați injecția de dependență, aceasta este utilizată după cum urmează: +Puteți seta semnarea e-mailurilor pentru mailer direct în [#configurație]. Dacă nu utilizați injecția de dependențe, se utilizează în acest mod: ```php $signer = new Nette\Mail\DkimSigner( @@ -259,40 +259,40 @@ $mailer->send($mail); ``` -Configurarea .[#toc-configuring] -================================ +Configurație +============ -Prezentare generală a opțiunilor de configurare pentru Nette Mail. Dacă nu utilizați întregul cadru, ci doar această bibliotecă, citiți [cum se încarcă configurația |bootstrap:]. +Prezentare generală a opțiunilor de configurare pentru Nette Mail. Dacă nu utilizați întregul framework, ci doar această bibliotecă, citiți [cum să încărcați configurația|bootstrap:]. -În mod implicit, pentru trimiterea de e-mailuri se utilizează aplicația de poștă electronică `Nette\Mail\SendmailMailer`, care nu este configurată suplimentar. Cu toate acestea, îl putem comuta la `Nette\Mail\SmtpMailer`: +Pentru trimiterea e-mailurilor se utilizează în mod standard mailerul `Nette\Mail\SendmailMailer`, care nu se configurează ulterior. Cu toate acestea, îl putem comuta la `Nette\Mail\SmtpMailer`: ```neon mail: - # utilizați SmtpMailer - smtp: true # (bool) valoarea implicită este false + # utilizează SmtpMailer + smtp: true # (bool) implicit este false host: ... # (string) port: ... # (int) username: ... # (string) password: ... # (string) timeout: ... # (int) - encryption: ... # (ssl|tls|null) valoarea implicită este null (are aliasul "secure") - clientHost: ... # (string) valoarea implicită este $_SERVER['HTTP_HOST']] - persistent: ... # (bool) valoarea implicită este false + encryption: ... # (ssl|tls|null) implicit este null (are alias 'secure') + clientHost: ... # (string) implicit este $_SERVER['HTTP_HOST'] + persistent: ... # (bool) implicit este false - # context pentru conectarea la serverul SMTP, valoarea implicită este stream_context_get_default() + # context pentru conectarea la serverul SMTP, implicit este stream_context_get_default() context: - ssl: # toate opțiunile la https://www.php.net/manual/en/context.ssl.php + ssl: # prezentare generală a opțiunilor la https://www.php.net/manual/en/context.ssl.php allow_self_signed: ... ... - http: # toate opțiunile la https://www.php.net/manual/en/context.http.php + http: # prezentare generală a opțiunilor la https://www.php.net/manual/en/context.http.php header: ... ... ``` -Puteți dezactiva autentificarea certificatului SSL folosind opțiunea `context › ssl › verify_peer: false`. Se **recomandă cu tărie să nu faceți** acest lucru, deoarece va face aplicația vulnerabilă. În schimb, "add certificates to trust store":https://www.php.net/manual/en/openssl.configuration.php. +Folosind opțiunea `context › ssl › verify_peer: false`, puteți dezactiva verificarea certificatelor SSL. **Nu recomandăm insistent** să faceți acest lucru, deoarece aplicația va deveni vulnerabilă. În schimb, "adăugați certificatele în depozit":https://www.php.net/manual/en/openssl.configuration.php. -Pentru a crește gradul de încredere, putem semna e-mailurile folosind [tehnologia DKIM |https://blog.nette.org/ro/semnati-e-mailurile-cu-dkim]: +Pentru a crește credibilitatea, putem semna e-mailurile folosind [tehnologia DKIM |https://blog.nette.org/ro/sign-emails-with-dkim]: ```neon mail: @@ -304,4 +304,12 @@ mail: ``` -{{leftbar: nette:@menu-topics}} +Servicii DI +=========== + +Aceste servicii sunt adăugate în containerul DI: + +| Nume | Tip | Descriere +|----------------------------------------------------- +| `mail.mailer` | [api:Nette\Mail\Mailer] | [clasa care trimite e-mailuri |#Trimiterea e-mailului] +| `mail.signer` | [api:Nette\Mail\Signer] | [semnătură DKIM |#DKIM] diff --git a/mail/ro/@meta.texy b/mail/ro/@meta.texy new file mode 100644 index 0000000000..6554692600 --- /dev/null +++ b/mail/ro/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Documentație Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/mail/ru/@home.texy b/mail/ru/@home.texy index 8b009ce2c6..0230dfd1f5 100644 --- a/mail/ru/@home.texy +++ b/mail/ru/@home.texy @@ -1,97 +1,98 @@ -Отправка электронных писем -************************** +Nette Mail +********** <div class=perex> -Собираетесь ли вы отправлять электронные письма, например, информационные бюллетени или подтверждения заказов? Фреймворк Nette предоставляет необходимые инструменты с очень удобным API. Мы покажем: +Собираетесь отправлять электронные письма, например, рассылки или подтверждения заказов? Фреймворк Nette предоставляет необходимые инструменты с очень удобным API. Мы покажем: - как создать электронное письмо, включая вложения -- как отправить -- как объединить электронные письма и шаблоны +- как его отправить +- как связать электронные письма и шаблоны </div> -Установка .[#toc-installation] -============================== +Установка +========= -Загрузите и установите пакет с помощью [Composer|best-practices:composer]: +Вы можете скачать и установить библиотеку с помощью [Composer|best-practices:composer]: ```shell composer require nette/mail ``` -Создание электронных писем .[#toc-creating-emails] -================================================== +Создание электронного письма +============================ -Email - это объект [api:Nette\Mail\Message]: +Электронное письмо — это объект класса [api:Nette\Mail\Message]. Создадим его, например, так: ```php $mail = new Nette\Mail\Message; -$mail->setFrom('John <john@example.com>') - ->addTo('peter@example.com') - ->addTo('jack@example.com') +$mail->setFrom('Franta <franta@example.com>') + ->addTo('petr@example.com') + ->addTo('jirka@example.com') ->setSubject('Подтверждение заказа') - ->setBody("Здравствуйте. Ваш заказ принят."); + ->setBody("Добрый день,\nваш заказ принят."); ``` -Все параметры должны быть закодированы в UTF-8. +Все вводимые параметры должны быть в кодировке UTF-8. -Помимо указания получателей с помощью метода `addTo()`, вы также можете указать получателя копии с помощью `addCc()`, или получателя скрытой копии с помощью `addBcc()`. Все эти методы, включая `setFrom()`, принимают адресата тремя способами: +Помимо указания получателя с помощью метода `addTo()`, можно указать получателя копии `addCc()` или получателя скрытой копии `addBcc()`. Во всех этих методах, включая `setFrom()`, мы можем записать адрес тремя способами: ```php -$mail->setFrom('john.doe@example.com'); -$mail->setFrom('john.doe@example.com', 'John Doe'); -$mail->setFrom('John Doe <john.doe@example.com>'); +$mail->setFrom('franta@example.com'); +$mail->setFrom('franta@example.com', 'Franta'); +$mail->setFrom('Franta <franta@example.com>'); ``` -Тело письма, написанное в формате HTML, передается с помощью метода `setHtmlBody()`: +Тело электронного письма, написанное в HTML, передается с помощью метода `setHtmlBody()`: ```php -$mail->setHtmlBody('<p>Hello,</p><p>Ваш заказ принят.</p>'); +$mail->setHtmlBody('<p>Добрый день,</p><p>ваш заказ принят.</p>'); ``` -Вам не нужно создавать текстовый вариант письма, Nette сгенерирует его автоматически за вас. А если у письма нет темы, она будет взята из элемента `<title>`. +Текстовую альтернативу создавать не нужно, Nette сгенерирует её автоматически за вас. И если у письма не установлена тема, она попытается взять её из элемента `<title>`. -Изображения также можно очень легко вставить в HTML-тело письма. Просто передайте путь, где физически находятся изображения, в качестве второго параметра, и Nette автоматически включит их в письмо: +В HTML-тело также можно очень легко вставлять изображения. Достаточно передать вторым параметром путь, где физически находятся изображения, и Nette автоматически включит их в письмо: ```php -// автоматически добавляет /path/to/images/background.gif к письму +// автоматически добавит /path/to/images/background.gif в письмо $mail->setHtmlBody( - '<b>Привет</b> <img src="background.gif">', + '<b>Hello</b> <img src="background.gif">', '/path/to/images', ); ``` -Алгоритм встраивания изображений поддерживает следующие шаблоны: `<img src=...>`, `<body background=...>`, `url(...)` внутри HTML-атрибута `style` и специальный синтаксис `[[...]]`. +Алгоритм вставки изображений ищет следующие шаблоны: `<img src=...>`, `<body background=...>`, `url(...)` внутри HTML-атрибута `style` и специальный синтаксис `[[...]]`. -Можно ли сделать отправку электронных писем ещё проще? +Может ли отправка писем быть еще проще? -Электронные письма — это как открытки. Никогда не отправляйте пароли или другие учётные данные по электронной почте. .[tip] +.[tip] +Электронное письмо — это что-то вроде открытки. Никогда не отправляйте пароли или другие учетные данные по электронной почте. -Вложения .[#toc-attachments] ----------------------------- +Вложения +-------- -Разумеется, вы можете прикреплять вложения к электронным письмам. Используйте команду `addAttachment(string $file, string $content = null, string $contentType = null)`. +В электронное письмо, конечно, можно вставлять вложения. Для этого служит метод `addAttachment(string $file, ?string $content = null, ?string $contentType = null)`. ```php -// вставляет файл /path/to/example.zip в электронное письмо под именем example.zip +// вставляет в письмо файл /path/to/example.zip под именем example.zip $mail->addAttachment('/path/to/example.zip'); -// вставляет файл /path/to/example.zip в электронное письмо под именем info.zip +// вставляет в письмо файл /path/to/example.zip с именем info.zip $mail->addAttachment('info.zip', file_get_contents('/path/to/example.zip')); -// вставляет файл example.txt с текстом "Hello John!" +// вставляет в письмо файл example.txt с содержимым "Hello John!" $mail->addAttachment('example.txt', 'Hello John!'); ``` -Шаблоны .[#toc-templates] -------------------------- +Шаблоны +------- -Если вы отправляете электронные письма в формате HTML, отличная идея — писать их в системе шаблонов [Latte|latte:]. Как это сделать? +Если вы отправляете HTML-письма, очень удобно писать их в системе шаблонов [Latte|latte:]. Как это сделать? ```php $latte = new Latte\Engine; @@ -100,8 +101,8 @@ $params = [ ]; $mail = new Nette\Mail\Message; -$mail->setFrom('John <john@example.com>') - ->addTo('jack@example.com') +$mail->setFrom('Franta <franta@example.com>') + ->addTo('petr@example.com') ->setHtmlBody( $latte->renderToString('/path/to/email.latte', $params), '/path/to/images', @@ -122,20 +123,20 @@ $mail->setFrom('John <john@example.com>') </style> </head> <body> - <p>Здравствуйте!</p> + <p>Добрый день,</p> - <p>Ваш заказ под номером {$orderId} был принят.</p> + <p>Ваш заказ номер {$orderId} был принят.</p> </body> </html> ``` -Nette автоматически вставляет все изображения, устанавливает тему в соответствии с элементом `<title>` и генерирует текстовую альтернативу для тела HTML. +Nette автоматически вставит все изображения, установит тему согласно элементу `<title>` и сгенерирует текстовую альтернативу для HTML. -Использование в приложении Nette .[#toc-using-in-nette-application] -------------------------------------------------------------------- +Использование в Nette Application +--------------------------------- -Если вы используете электронную почту вместе с Nette Application, т. е. презентерами, вы можете захотеть создать ссылки в шаблонах, используя атрибут `n:href` или тег `{link}`. Latte в принципе их не знает, но их очень легко добавить. Созданием ссылок может заниматься объект `Nette\Application\LinkGenerator`, который вы получите, передав его с помощью [внедрения зависимостей |dependency-injection:passing-dependencies]. +Если вы используете электронные письма совместно с Nette Application, то есть с презентерами, вы можете захотеть создавать ссылки в шаблонах с помощью атрибута `n:href` или тега `{link}`. Latte по умолчанию их не знает, но их очень легко добавить. Создавать ссылки умеет объект `Nette\Application\LinkGenerator`, к которому можно получить доступ, запросив его через [dependency injection |dependency-injection:passing-dependencies]: ```php use Nette; @@ -168,38 +169,38 @@ class MailSender } ``` -В шаблоне ссылка создается как в обычном шаблоне. Все ссылки, созданные с помощью LinkGenerator, являются абсолютными: +В шаблоне затем создаем ссылки так, как мы привыкли. Все ссылки, созданные через LinkGenerator, будут абсолютными. ```latte -<a n:href="Presenter:action">Link</a> +<a n:href="Presenter:action">Ссылка</a> ``` -Отправка электронных писем .[#toc-sending-emails] -================================================= +Отправка электронного письма +============================ -Mailer - это класс, отвечающий за отправку электронных писем. Он реализует интерфейс [api:Nette\Mail\Mailer] и предлагает несколько готовых почтовых программ, которые мы представим. +Mailer — это класс, обеспечивающий отправку электронных писем. Он реализует интерфейс [api:Nette\Mail\Mailer], и доступно несколько готовых мейлеров, которые мы рассмотрим. -Фреймворк автоматически добавляет сервис `Nette\Mail\Mailer` на основе [configuration|#Configuring] в контейнер DI, который вы получаете, передавая его с помощью [внедрения зависимостей |dependency-injection:passing-dependencies]. +Фреймворк автоматически добавляет в DI-контейнер сервис типа `Nette\Mail\Mailer`, собранный на основе [конфигурации |#Конфигурация], и к которому можно получить доступ, запросив его через [dependency injection |dependency-injection:passing-dependencies]. SendmailMailer -------------- -По умолчанию используется SendmailMailer, который использует функцию PHP [php:mail]. Пример использования: +Мейлер по умолчанию — это SendmailMailer, который использует PHP-функцию [php:mail]. Пример использования: ```php $mailer = new Nette\Mail\SendmailMailer; $mailer->send($mail); ``` -Если вы хотите установить `returnPath`, но сервер всё равно перезаписывает его, используйте `$mailer->commandArgs = '-fmy@email.com'`. +Если вы хотите установить `returnPath`, а сервер его постоянно перезаписывает, используйте `$mailer->commandArgs = '-fMuj@email.cz'`. SmtpMailer ---------- -Для отправки почты через SMTP-сервер используйте `SmtpMailer`. +Для отправки почты через SMTP-сервер служит `SmtpMailer`. ```php $mailer = new Nette\Mail\SmtpMailer( @@ -211,19 +212,19 @@ $mailer = new Nette\Mail\SmtpMailer( $mailer->send($mail); ``` -В конструктор могут быть переданы следующие дополнительные параметры: +Конструктору можно передать следующие дополнительные параметры: -* `port` - если не задан, будет использоваться стандартное значение 25 или 465 для `ssl`. +* `port` - если не установлен, используется по умолчанию 25 или 465 для `ssl` * `timeout` - тайм-аут для SMTP-соединения * `persistent` - использовать постоянное соединение -* `clientHost` - назначение клиента -* `streamOptions` - позволяет установить "опции контекста SSL":https://www.php.net/manual/ru/context.ssl.php для соединения +* `clientHost` - установка заголовка Host клиента +* `streamOptions` - позволяет установить "SSL context options":https://www.php.net/manual/en/context.ssl.php для соединения FallbackMailer -------------- -Он не отправляет письма по электронной почте, а рассылает их через набор рассылок. Если одна рассылка не удалась, она повторяет попытку на следующей. Если последний из них не работает, то всё начинается заново с первого. +Не отправляет электронные письма напрямую, а осуществляет отправку через набор мейлеров. В случае, если один мейлер выходит из строя, повторяет попытку со следующим. Если и последний выходит из строя, начинает снова с первого. ```php $mailer = new Nette\Mail\FallbackMailer([ @@ -234,16 +235,15 @@ $mailer = new Nette\Mail\FallbackMailer([ $mailer->send($mail); ``` -Другие параметры в конструкторе включают число повторов и время ожидания в милисекундах. +В качестве дополнительных параметров в конструкторе можно указать количество повторов и время ожидания в миллисекундах. DKIM ==== -DKIM (DomainKeys Identified Mail) - это технология надежной электронной почты, которая также помогает обнаружить поддельные сообщения. Отправленное сообщение подписывается закрытым ключом домена отправителя, и эта подпись сохраняется в заголовке электронной почты. -Сервер получателя сравнивает эту подпись с открытым ключом, хранящимся в DNS-записях домена. Сверяя подпись, можно доказать, что электронное письмо действительно пришло из домена отправителя и что сообщение не было изменено во время его передачи. +DKIM (DomainKeys Identified Mail) — это технология для повышения доверия к электронным письмам, которая также помогает выявлять поддельные сообщения. Отправленное сообщение подписывается приватным ключом домена отправителя, и эта подпись сохраняется в заголовке письма. Сервер получателя сравнивает эту подпись с публичным ключом, хранящимся в DNS-записях домена. Соответствие подписи доказывает, что письмо действительно пришло из домена отправителя и что во время передачи сообщение не было изменено. -Вы можете настроить mailer для подписи электронной почты в [конфигурации|#Configuring]. Если вы не используете внедрение зависимостей, она задается следующим образом: +Подписание писем можно настроить для мейлера прямо в [конфигурации |#Конфигурация]. Если вы не используете dependency injection, это делается следующим образом: ```php $signer = new Nette\Mail\DkimSigner( @@ -259,16 +259,16 @@ $mailer->send($mail); ``` -Конфигурация .[#toc-configuring] -================================ +Конфигурация +============ -Обзор параметров конфигурации для Nette Mail. Если вы используете не весь фреймворк, а только эту библиотеку, прочтите [Как загрузить файл конфигурации|bootstrap:]. +Обзор опций конфигурации для Nette Mail. Если вы не используете весь фреймворк, а только эту библиотеку, прочитайте, [как загрузить конфигурацию|bootstrap:]. -По умолчанию для отправки писем используется почтовая программа `Nette\Mail\SendmailMailer`, которая больше не настраивается. Однако мы можем переключить его на `Nette\Mail\SmtpMailer`: +Для отправки электронных писем по умолчанию используется мейлер `Nette\Mail\SendmailMailer`, который далее не конфигурируется. Однако мы можем переключить его на `Nette\Mail\SmtpMailer`: ```neon mail: - # используем SmtpMailer + # использовать SmtpMailer smtp: true # (bool) по умолчанию false host: ... # (string) @@ -276,23 +276,23 @@ mail: username: ... # (string) password: ... # (string) timeout: ... # (int) - encryption: ... # (ssl|tls|null) по умолчанию null + encryption: ... # (ssl|tls|null) по умолчанию null (имеет псевдоним 'secure') clientHost: ... # (string) по умолчанию $_SERVER['HTTP_HOST'] persistent: ... # (bool) по умолчанию false - # контекст для подключения к SMTP-серверу, по умолчанию используется stream_context_get_default() + # контекст для подключения к SMTP-серверу, по умолчанию stream_context_get_default() context: - ssl: # все варианты на https://www.php.net/manual/en/context.ssl.php + ssl: # обзор опций на https://www.php.net/manual/en/context.ssl.php allow_self_signed: ... ... - http: # все варианты на https://www.php.net/manual/en/context.http.php + http: # обзор опций на https://www.php.net/manual/en/context.http.php header: ... ... ``` -Вы можете отключить проверку подлинности SSL-сертификата с помощью опции `context › ssl › verify_peer: false`. Настоятельно рекомендуется **не делать этого**, так как это сделает приложение уязвимым. Вместо этого, "добавьте сертификаты в хранилище доверия":https://www.php.net/manual/en/openssl.configuration.php. +С помощью опции `context › ssl › verify_peer: false` можно отключить проверку SSL-сертификатов. **Настоятельно не рекомендуем** это делать, так как приложение станет уязвимым. Вместо этого "добавьте сертификаты в хранилище":https://www.php.net/manual/en/openssl.configuration.php. -Чтобы повысить доверие, мы можем подписывать электронные письма с помощью [технологии DKIM |https://blog.nette.org/ru/podpisyvajte-elektronnye-pis-ma-s-pomos-yu-dkim]: +Для повышения доверия мы можем подписывать электронные письма с помощью [технологии DKIM |https://blog.nette.org/ru/sign-emails-with-dkim]: ```neon mail: @@ -304,4 +304,12 @@ mail: ``` -{{leftbar: nette:@menu-topics}} +Сервисы DI +========== + +Эти сервисы добавляются в DI-контейнер: + +| Имя | Тип | Описание +|----------------------------------------------------- +| `mail.mailer` | [api:Nette\Mail\Mailer] | [класс, отправляющий электронные письма |#Отправка электронного письма] +| `mail.signer` | [api:Nette\Mail\Signer] | [Подписание DKIM |#DKIM] diff --git a/mail/ru/@meta.texy b/mail/ru/@meta.texy new file mode 100644 index 0000000000..61577d6323 --- /dev/null +++ b/mail/ru/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Документация Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/mail/sl/@home.texy b/mail/sl/@home.texy index 59c8e279e7..578969c758 100644 --- a/mail/sl/@home.texy +++ b/mail/sl/@home.texy @@ -1,60 +1,60 @@ -Pošiljanje e-poštnih sporočil -***************************** +Nette Mail +********** <div class=perex> -Ali boste pošiljali e-poštna sporočila, kot so glasila ali potrditve naročil? Nette Framework zagotavlja potrebna orodja z zelo dobrim vmesnikom API. Prikazali bomo: +Ali nameravate pošiljati e-pošto, na primer novice ali potrditve naročil? Nette Framework ponuja potrebna orodja z zelo prijetnim API-jem. Pokazali bomo: -- kako ustvariti e-poštno sporočilo, vključno s priponkami -- kako ga poslati -- kako združiti e-poštna sporočila in predloge +- kako ustvariti e-pošto, vključno s prilogami +- kako jo poslati +- kako povezati e-pošto in predloge </div> -Namestitev .[#toc-installation] -=============================== +Namestitev +========== -Prenesite in namestite paket s [programom Composer |best-practices:composer]: +Knjižnico prenesete in namestite z orodjem [Composer|best-practices:composer]: ```shell composer require nette/mail ``` -Ustvarjanje e-poštnih sporočil .[#toc-creating-emails] -====================================================== +Ustvarjanje e-pošte +=================== -E-pošta je objekt [api:Nette\Mail\Message]: +E-pošta je objekt razreda [api:Nette\Mail\Message]. Ustvarimo jo na primer tako: ```php $mail = new Nette\Mail\Message; -$mail->setFrom('John <john@example.com>') - ->addTo('peter@example.com') - ->addTo('jack@example.com') - ->setSubject('Order Confirmation') - ->setBody("Hello, Your order has been accepted."); +$mail->setFrom('Franta <franta@example.com>') + ->addTo('petr@example.com') + ->addTo('jirka@example.com') + ->setSubject('Potrditev naročila') + ->setBody("Dober dan,\nvaše naročilo je bilo sprejeto."); ``` -Vsi parametri morajo biti kodirani v UTF-8. +Vsi vneseni parametri morajo biti v UTF-8. -Poleg določitve prejemnikov z metodo `addTo()` lahko določite tudi prejemnika kopije z metodo `addCc()` ali prejemnika slepe kopije z metodo `addBcc()`. Vse te metode, vključno s `setFrom()`, sprejemajo naslovnika na tri načine: +Poleg navedbe prejemnika z metodo `addTo()` lahko navedemo tudi prejemnika kopije `addCc()` ali prejemnika skrite kopije `addBcc()`. V vseh teh metodah, vključno z `setFrom()`, lahko naslovnika zapišemo na tri načine: ```php -$mail->setFrom('john.doe@example.com'); -$mail->setFrom('john.doe@example.com', 'John Doe'); -$mail->setFrom('John Doe <john.doe@example.com>'); +$mail->setFrom('franta@example.com'); +$mail->setFrom('franta@example.com', 'Franta'); +$mail->setFrom('Franta <franta@example.com>'); ``` -Telo elektronskega sporočila, napisanega v jeziku HTML, se posreduje z metodo `setHtmlBody()`: +Telo e-pošte, zapisano v HTML, se preda z metodo `setHtmlBody()`: ```php -$mail->setHtmlBody('<p>Hello,</p><p>Your order has been accepted.</p>'); +$mail->setHtmlBody('<p>Dober dan,</p><p>vaše naročilo je bilo sprejeto.</p>'); ``` -Ni vam treba ustvariti alternativnega besedila, Nette ga bo ustvaril samodejno namesto vas. In če e-poštno sporočilo nima nastavljenega predmeta, bo ta prevzet iz `<title>` elementa. +Besedilne alternative vam ni treba ustvarjati, Nette jo bo samodejno ustvaril za vas. In če e-pošta nima nastavljenega subjekta, ga bo poskusil prevzeti iz elementa `<title>`. -Slike lahko tudi izjemno enostavno vstavite v telo HTML elektronskega sporočila. Samo kot drugi parameter podajte pot, kjer se slike fizično nahajajo, in Nette jih bo samodejno vključil v e-poštno sporočilo: +V telo HTML lahko tudi izjemno enostavno vstavljate slike. Dovolj je, da kot drugi parameter predate pot, kjer se slike fizično nahajajo, in Nette jih bo samodejno vključil v e-pošto: ```php // samodejno doda /path/to/images/background.gif v e-pošto @@ -64,34 +64,35 @@ $mail->setHtmlBody( ); ``` -Algoritem za vstavljanje slik podpira naslednje vzorce: `<img src=...>`, `<body background=...>`, `url(...)` znotraj atributa HTML `style` in posebno sintakso `[[...]]`. +Algoritem za vstavljanje slik išče te vzorce: `<img src=...>`, `<body background=...>`, `url(...)` znotraj atributa HTML `style` in posebno sintakso `[[...]]`. -Ali je lahko pošiljanje e-poštnih sporočil še lažje? +Ali je lahko pošiljanje e-pošte še enostavnejše? -Elektronska sporočila so kot razglednice. Nikoli ne pošiljajte gesel ali drugih poverilnic po e-pošti. .[tip] +.[tip] +E-pošta je kot razglednica. Nikoli ne pošiljajte gesel ali drugih poverilnic po e-pošti. -Priponke .[#toc-attachments] ----------------------------- +Priloge +------- -Elektronskemu sporočilu lahko seveda priložite priponke. Uporabite `addAttachment(string $file, string $content = null, string $contentType = null)`. +V e-pošto lahko seveda vstavljate priloge. Za to služi metoda `addAttachment(string $file, ?string $content = null, ?string $contentType = null)`. ```php // vstavi datoteko /path/to/example.zip v e-pošto pod imenom example.zip $mail->addAttachment('/path/to/example.zip'); -// vstavi datoteko /path/to/example.zip v e-pošto pod imenom info.zip +// vstavi datoteko /path/to/example.zip v e-pošto z imenom info.zip $mail->addAttachment('info.zip', file_get_contents('/path/to/example.zip')); -// priloži novo vsebino datoteke example.txt "Hello John!" +// vstavi datoteko example.txt z vsebino "Hello John!" v e-pošto $mail->addAttachment('example.txt', 'Hello John!'); ``` -Predloge .[#toc-templates] --------------------------- +Predloge +-------- -Če pošiljate e-poštna sporočila HTML, jih je dobro napisati v sistemu predlog [Latte |latte:]. Kako to storite? +Če pošiljate HTML e-pošto, je naravno, da jo zapišete v sistemu predlog [Latte|latte:]. Kako to storiti? ```php $latte = new Latte\Engine; @@ -100,8 +101,8 @@ $params = [ ]; $mail = new Nette\Mail\Message; -$mail->setFrom('John <john@example.com>') - ->addTo('jack@example.com') +$mail->setFrom('Franta <franta@example.com>') + ->addTo('petr@example.com') ->setHtmlBody( $latte->renderToString('/path/to/email.latte', $params), '/path/to/images', @@ -114,7 +115,7 @@ Datoteka `email.latte`: <html> <head> <meta charset="utf-8"> - <title>Order Confirmation + Potrditev naročila -

                                                                                                                                        Hello,

                                                                                                                                        +

                                                                                                                                        Dober dan,

                                                                                                                                        -

                                                                                                                                        Your order number {$orderId} has been accepted.

                                                                                                                                        +

                                                                                                                                        Vaše naročilo številka {$orderId} je bilo sprejeto.

                                                                                                                                        ``` -Nette samodejno vstavi vse slike, nastavi predmet glede na `` elementa in ustvari alternativno besedilo za telo HTML. +Nette samodejno vstavi vse slike, nastavi subjekt glede na element `<title>` in ustvari besedilno alternativo HTML. -Uporaba v aplikaciji Nette .[#toc-using-in-nette-application] -------------------------------------------------------------- +Uporaba v Nette Application +--------------------------- -Če uporabljate e-pošto skupaj z aplikacijo Nette Application, tj. predavatelji, boste morda želeli ustvariti povezave v predlogah z uporabo atributa `n:href` ali oznake `{link}`. Latte jih v osnovi ne pozna, vendar jih je zelo enostavno dodati. Ustvarjanje povezav lahko opravi objekt `Nette\Application\LinkGenerator`, ki ga dobite s posredovanjem z uporabo [vbrizgavanja odvisnosti |dependency-injection:passing-dependencies]. +Če e-pošto uporabljate skupaj z Nette Application, tj. s presenterji, boste morda želeli v predlogah ustvarjati povezave z atributom `n:href` ali oznako `{link}`. Teh Latte privzeto ne pozna, vendar jih je zelo enostavno dodati. Povezave zna ustvarjati objekt `Nette\Application\LinkGenerator`, do katerega pridete tako, da si ga pustite predati z [dependency injection |dependency-injection:passing-dependencies]: ```php use Nette; @@ -168,38 +169,38 @@ class MailSender } ``` -V predlogi je povezava ustvarjena tako kot v običajni predlogi. Vse povezave, ustvarjene prek LinkGeneratorja, so absolutne: +V predlogi nato ustvarjamo povezave, kot smo navajeni. Vse povezave, ustvarjene prek LinkGeneratorja, bodo absolutne. ```latte -<a n:href="Presenter:action">Link</a> +<a n:href="Presenter:action">Povezava</a> ``` -Pošiljanje e-pošte .[#toc-sending-emails] -========================================= +Pošiljanje e-pošte +================== -Pošiljatelj je razred, odgovoren za pošiljanje e-pošte. Implementira vmesnik [api:Nette\Mail\Mailer] in na voljo je več pripravljenih poštnih pošiljateljev, ki jih bomo predstavili. +Mailer je razred, ki skrbi za pošiljanje e-pošte. Implementira vmesnik [api:Nette\Mail\Mailer] in na voljo je več predpripravljenih mailerjev, ki jih bomo predstavili. -Ogrodje samodejno doda storitev `Nette\Mail\Mailer` na podlagi [konfiguracije |#Configuring] v vsebnik DI, ki ga dobite s posredovanjem z uporabo [vbrizgavanja odvisnosti |dependency-injection:passing-dependencies]. +Framework samodejno doda v DI vsebnik storitev tipa `Nette\Mail\Mailer`, sestavljeno na podlagi [Konfiguracije |#Konfiguracija], do katere pridete tako, da si jo pustite predati z [dependency injection |dependency-injection:passing-dependencies]. -SendmailMailer .[#toc-sendmailmailer] -------------------------------------- +SendmailMailer +-------------- -Privzeta poštna pošta je SendmailMailer, ki uporablja funkcijo PHP [php:mail]. Primer uporabe: +Privzeti mailer je SendmailMailer, ki uporablja PHP funkcijo [php:mail]. Primer uporabe: ```php $mailer = new Nette\Mail\SendmailMailer; $mailer->send($mail); ``` -Če želite nastaviti `returnPath` in ga strežnik še vedno prepiše, uporabite `$mailer->commandArgs = '-fmy@email.com'`. +Če želite nastaviti `returnPath` in ga strežnik še vedno prepiše, uporabite `$mailer->commandArgs = '-fMoj@email.si'`. -SmtpMailer .[#toc-smtpmailer] ------------------------------ +SmtpMailer +---------- -Za pošiljanje pošte prek strežnika SMTP uporabite `SmtpMailer`. +Za pošiljanje pošte prek strežnika SMTP služi `SmtpMailer`. ```php $mailer = new Nette\Mail\SmtpMailer( @@ -211,19 +212,19 @@ $mailer = new Nette\Mail\SmtpMailer( $mailer->send($mail); ``` -Konstruktorju lahko posredujete naslednje dodatne parametre: +Konstruktorju lahko predate te dodatne parametre: - `port` * Če ni nastavljen, se uporabi privzeta vrednost 25 ali 465 za `ssl`. -* `timeout` - časovna omejitev za povezavo SMTP -* `persistent` - uporaba trajne povezave -* `clientHost` - oznaka odjemalca -* `streamOptions` - omogoča nastavitev "možnosti konteksta SSL":https://www.php.net/manual/en/context.ssl.php za povezavo +* `port` - če ni nastavljen, se uporabi privzeti 25 ali 465 za `ssl` +* `timeout` - časovna omejitev za SMTP povezavo +* `persistent` - uporabi trajno povezavo +* `clientHost` - nastavitev glave Host odjemalca +* `streamOptions` - omogoča nastavitev "SSL context options":https://www.php.net/manual/en/context.ssl.php za povezavo -FallbackMailer .[#toc-fallbackmailer] -------------------------------------- +FallbackMailer +-------------- -Ne pošilja e-poštnih sporočil, temveč jih pošilja prek niza poštnih pošiljateljev. Če en poštni nabiralnik ne uspe, ponovi poskus pri naslednjem. Če je neuspešen tudi zadnji, začne znova s prvim. +E-pošte ne pošilja neposredno, ampak pošiljanje posreduje prek niza mailerjev. V primeru, da en mailer odpove, ponovi poskus pri naslednjem. Če odpove tudi zadnji, začne znova od prvega. ```php $mailer = new Nette\Mail\FallbackMailer([ @@ -234,16 +235,15 @@ $mailer = new Nette\Mail\FallbackMailer([ $mailer->send($mail); ``` -Drugi parametri v konstruktorju vključujejo število ponovitev in čakalni čas v milisekundah. +Kot dodatne parametre v konstruktorju lahko navedemo število ponovitev in čakalni čas v milisekundah. -DKIM .[#toc-dkim] -================= +DKIM +==== -DKIM (DomainKeys Identified Mail) je zanesljiva tehnologija elektronske pošte, ki pomaga tudi pri odkrivanju lažnih sporočil. Poslano sporočilo je podpisano z zasebnim ključem pošiljateljeve domene, ta podpis pa je shranjen v glavi e-pošte. -Prejemnikov strežnik primerja ta podpis z javnim ključem, shranjenim v zapisih DNS domene. Z ujemanjem podpisa se dokaže, da elektronsko sporočilo dejansko izvira iz pošiljateljeve domene in da med prenosom sporočila ni bilo spremenjeno. +DKIM (DomainKeys Identified Mail) je tehnologija za povečanje verodostojnosti e-pošte, ki prav tako pomaga pri odkrivanju ponarejenih sporočil. Poslano sporočilo je podpisano z zasebnim ključem domene pošiljatelja in ta podpis je shranjen v glavi e-pošte. Strežnik prejemnika primerja ta podpis z javnim ključem, shranjenim v DNS zapisih domene. S tem, ko se podpis ujema, je dokazano, da e-pošta dejansko izvira iz pošiljateljeve domene in da med prenosom sporočila ni prišlo do njegove spremembe. -Pošiljatelja lahko za podpisovanje e-pošte nastavite v [konfiguraciji |#Configuring]. Če ne uporabljate vbrizgavanja odvisnosti, se uporablja na naslednji način: +Podpisovanje e-pošte lahko mailerju nastavite neposredno v [konfiguraciji |#Konfiguracija]. Če ne uporabljate dependency injection, se uporablja na ta način: ```php $signer = new Nette\Mail\DkimSigner( @@ -259,40 +259,40 @@ $mailer->send($mail); ``` -Konfiguracija .[#toc-configuring] -================================= +Konfiguracija +============= -Pregled možnosti konfiguracije za Nette Mail. Če ne uporabljate celotnega ogrodja, temveč samo to knjižnico, preberite, [kako naložiti konfiguracijo |bootstrap:]. +Pregled konfiguracijskih možnosti za Nette Mail. Če ne uporabljate celotnega ogrodja, ampak samo to knjižnico, preberite, [kako naložiti konfiguracijo|bootstrap:]. -Privzeto se za pošiljanje e-pošte uporablja poštni program `Nette\Mail\SendmailMailer`, ki ni dodatno konfiguriran. Vendar ga lahko preklopimo na `Nette\Mail\SmtpMailer`: +Za pošiljanje e-pošte se standardno uporablja mailer `Nette\Mail\SendmailMailer`, ki se nadalje ne konfigurira. Lahko pa ga preklopimo na `Nette\Mail\SmtpMailer`: ```neon mail: - # uporaba SmtpMailer - smtp: true # (bool) privzeto false + # uporabi SmtpMailer + smtp: true # (bool) privzeto je false - host: ... # (niz) + host: ... # (string) port: ... # (int) username: ... # (string) password: ... # (string) timeout: ... # (int) - encryption: ... # (ssl|tls|null) privzeta vrednost je nič (ima vzdevek 'secure') + encryption: ... # (ssl|tls|null) privzeto je null (ima alias 'secure') clientHost: ... # (string) privzeto je $_SERVER['HTTP_HOST'] persistent: ... # (bool) privzeto je false - # kontekst za povezovanje s strežnikom SMTP, privzeto je stream_context_get_default() + # kontekst za povezavo s SMTP strežnikom, privzeto je stream_context_get_default() context: - ssl: # vse možnosti na https://www.php.net/manual/en/context.ssl.php + ssl: # pregled možnosti na https://www.php.net/manual/en/context.ssl.php allow_self_signed: ... ... - http: # vse možnosti na https://www.php.net/manual/en/context.http.php + http: # pregled možnosti na https://www.php.net/manual/en/context.http.php header: ... ... ``` -Preverjanje pristnosti potrdila SSL lahko onemogočite z uporabo možnosti `context › ssl › verify_peer: false`. To je **strogo priporočljivo, da tega ne storite**, saj bo aplikacija zaradi tega ranljiva. Namesto tega "dodajte potrdila v shrambo zaupanja":https://www.php.net/manual/en/openssl.configuration.php. +Z možnostjo `context › ssl › verify_peer: false` lahko izklopite preverjanje SSL certifikatov. **Močno odsvetujemo**, da to storite, ker bo aplikacija postala ranljiva. Namesto tega "dodajte certifikate v shrambo":https://www.php.net/manual/en/openssl.configuration.php. -Za povečanje zaupanja lahko elektronska sporočila podpišemo s [tehnologijo DKIM |https://blog.nette.org/sl/podpisovanje-e-postnih-sporocil-z-dkim]: +Za povečanje verodostojnosti lahko e-pošto podpisujemo s [tehnologijo DKIM |https://blog.nette.org/sl/sign-emails-with-dkim]: ```neon mail: @@ -304,4 +304,12 @@ mail: ``` -{{leftbar: nette:@menu-topics}} +Storitve DI +=========== + +Te storitve se dodajo v DI vsebnik: + +| Ime | Tip | Opis +|----------------------------------------------------- +| `mail.mailer` | [api:Nette\Mail\Mailer] | [razred, ki pošilja e-pošto |#Pošiljanje e-pošte] +| `mail.signer` | [api:Nette\Mail\Signer] | [DKIM podpisovanje |#DKIM] diff --git a/mail/sl/@meta.texy b/mail/sl/@meta.texy new file mode 100644 index 0000000000..282883a3d6 --- /dev/null +++ b/mail/sl/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Dokumentacija}} +{{leftbar: nette:@menu-topics}} diff --git a/mail/tr/@home.texy b/mail/tr/@home.texy index 55a8704884..4daf386805 100644 --- a/mail/tr/@home.texy +++ b/mail/tr/@home.texy @@ -1,97 +1,98 @@ -E-posta Gönderme -**************** +Nette Mail +********** <div class=perex> -Haber bültenleri veya sipariş onayları gibi e-postalar mı göndereceksiniz? Nette Framework çok güzel bir API ile gerekli araçları sağlar. Biz göstereceğiz: +Örneğin bültenler veya sipariş onayları gibi e-postalar göndermek mi istiyorsunuz? Nette Framework, çok hoş bir API ile gerekli araçları sağlar. Göstereceğiz: -- ekler de dahil olmak üzere bir e-posta nasıl oluşturulur +- ekler dahil e-posta nasıl oluşturulur - nasıl gönderilir - e-postalar ve şablonlar nasıl birleştirilir </div> -Kurulum .[#toc-installation] -============================ +Kurulum +======= -[Composer'ı |best-practices:composer] kullanarak paketi indirin ve yükleyin: +Kütüphaneyi [Composer|best-practices:composer] aracını kullanarak indirip kurun: ```shell composer require nette/mail ``` -E-posta Oluşturma .[#toc-creating-emails] -========================================= +E-posta Oluşturma +================= -E-posta bir [api:Nette\Mail\Message] nesnesidir: +E-posta, [api:Nette\Mail\Message] sınıfının bir nesnesidir. Örneğin şöyle oluşturalım: ```php $mail = new Nette\Mail\Message; -$mail->setFrom('John <john@example.com>') - ->addTo('peter@example.com') - ->addTo('jack@example.com') - ->setSubject('Order Confirmation') - ->setBody("Hello, Your order has been accepted."); +$mail->setFrom('Franta <franta@example.com>') + ->addTo('petr@example.com') + ->addTo('jirka@example.com') + ->setSubject('Sipariş Onayı') + ->setBody("Merhaba,\nsiparişiniz alındı."); ``` -Tüm parametreler UTF-8 olarak kodlanmalıdır. +Girilen tüm parametreler UTF-8 olmalıdır. -Alıcıları `addTo()` yöntemiyle belirtmenin yanı sıra, `addCc()` ile kopyanın alıcısını veya `addBcc()` ile kör kopyanın alıcısını da belirtebilirsiniz. `setFrom()` dahil olmak üzere tüm bu yöntemler muhatabı üç şekilde kabul eder: +Alıcıyı `addTo()` metoduyla belirtmenin yanı sıra, kopya alıcısını `addCc()` veya gizli kopya alıcısını `addBcc()` ile de belirtebilirsiniz. `setFrom()` dahil tüm bu metotlarda, alıcıyı üç şekilde yazabiliriz: ```php -$mail->setFrom('john.doe@example.com'); -$mail->setFrom('john.doe@example.com', 'John Doe'); -$mail->setFrom('John Doe <john.doe@example.com>'); +$mail->setFrom('franta@example.com'); +$mail->setFrom('franta@example.com', 'Franta'); +$mail->setFrom('Franta <franta@example.com>'); ``` -HTML ile yazılmış bir e-postanın gövdesi `setHtmlBody()` yöntemi kullanılarak aktarılır: +HTML olarak yazılan e-posta gövdesi `setHtmlBody()` metoduyla iletilir: ```php -$mail->setHtmlBody('<p>Hello,</p><p>Your order has been accepted.</p>'); +$mail->setHtmlBody('<p>Merhaba,</p><p>siparişiniz alındı.</p>'); ``` -Metin alternatifi oluşturmak zorunda değilsiniz, Nette bunu sizin için otomatik olarak oluşturacaktır. Ve eğer e-postanın belirlenmiş bir konusu yoksa, konu `<title>` element. +Metin alternatifini oluşturmanıza gerek yok, Nette sizin için otomatik olarak oluşturacaktır. Ve e-postanın bir konusu yoksa, onu `<title>` öğesinden almaya çalışacaktır. -Görüntüler ayrıca bir e-postanın HTML gövdesine son derece kolay bir şekilde eklenebilir. Görüntülerin fiziksel olarak bulunduğu yolu ikinci parametre olarak geçmeniz yeterlidir; Nette bunları otomatik olarak e-postaya dahil edecektir: +HTML gövdesine resim eklemek de son derece kolaydır. İkinci parametre olarak resimlerin fiziksel olarak bulunduğu yolu iletmeniz yeterlidir ve Nette bunları otomatik olarak e-postaya dahil edecektir: ```php -// e-postaya otomatik olarak /path/to/images/background.gif ekler +// /path/to/images/background.gif dosyasını otomatik olarak e-postaya ekler $mail->setHtmlBody( '<b>Merhaba</b> <img src="background.gif">', '/path/to/images', ); ``` -Görüntü gömme algoritması aşağıdaki kalıpları destekler: `<img src=...>`, `<body background=...>`, `url(...)` HTML özniteliğinin içinde `style` ve özel sözdizimi `[[...]]`. +Resimleri ekleyen algoritma şu kalıpları arar: `<img src=...>`, `<body background=...>`, HTML `style` niteliği içindeki `url(...)` ve özel sözdizimi `[[...]]`. -E-posta göndermek daha da kolay olabilir mi? +E-posta göndermek daha kolay olabilir mi? -E-postalar kartpostal gibidir. Parolaları veya diğer kimlik bilgilerini asla e-posta yoluyla göndermeyin. .[tip] +.[tip] +E-posta bir kartpostal gibidir. Asla e-posta ile şifre veya diğer erişim bilgilerini göndermeyin. -Ekler .[#toc-attachments] -------------------------- +Ekler +----- -Elbette e-postaya ekler de ekleyebilirsiniz. `addAttachment(string $file, string $content = null, string $contentType = null)` adresini kullanın. +Elbette e-postaya ekler eklenebilir. Bunun için `addAttachment(string $file, ?string $content = null, ?string $contentType = null)` metodu kullanılır. ```php -// /path/to/example.zip dosyasını example.zip adı altında e-postaya ekler +// /path/to/example.zip dosyasını example.zip adıyla e-postaya ekler $mail->addAttachment('/path/to/example.zip'); -// /path/to/example.zip dosyasını info.zip adı altında e-postaya ekler +// /path/to/example.zip dosyasını info.zip olarak adlandırılmış şekilde e-postaya ekler $mail->addAttachment('info.zip', file_get_contents('/path/to/example.zip')); -// yeni example.txt dosya içeriğini ekler "Merhaba John!" +// "Merhaba John!" içeriğiyle example.txt dosyasını e-postaya ekler $mail->addAttachment('example.txt', 'Merhaba John!'); ``` -Şablonlar .[#toc-templates] ---------------------------- +Şablonlar +--------- -HTML e-postaları gönderiyorsanız, bunları [Latte |latte:] şablon sisteminde yazmak harika bir fikirdir. Nasıl Yapılır? +HTML e-postaları gönderiyorsanız, bunları [Latte|latte:] şablonlama sisteminde yazmak doğaldır. Nasıl yapılır? ```php $latte = new Latte\Engine; @@ -100,21 +101,21 @@ $params = [ ]; $mail = new Nette\Mail\Message; -$mail->setFrom('John <john@example.com>') - ->addTo('jack@example.com') +$mail->setFrom('Franta <franta@example.com>') + ->addTo('petr@example.com') ->setHtmlBody( $latte->renderToString('/path/to/email.latte', $params), '/path/to/images', ); ``` -Dosya `email.latte`: +`email.latte` dosyası: ```latte <html> <head> <meta charset="utf-8"> - <title>Order Confirmation + Sipariş Onayı -

                                                                                                                                        Hello,

                                                                                                                                        +

                                                                                                                                        Merhaba,

                                                                                                                                        -

                                                                                                                                        Your order number {$orderId} has been accepted.

                                                                                                                                        +

                                                                                                                                        Sipariş numaranız {$orderId} alındı.

                                                                                                                                        ``` -Nette tüm görüntüleri otomatik olarak ekler, konuyu `` öğesine dönüştürür ve HTML gövdesi için metin alternatifi oluşturur. +Nette tüm resimleri otomatik olarak ekler, konuyu `<title>` öğesine göre ayarlar ve HTML'ye bir metin alternatifi oluşturur. -Nette Uygulamasında Kullanım .[#toc-using-in-nette-application] ---------------------------------------------------------------- +Nette Uygulamasında Kullanım +---------------------------- -Nette Uygulaması ile birlikte e-posta kullanıyorsanız, yani sunum yapıyorsanız, `n:href` özniteliğini veya `{link}` etiketini kullanarak şablonlarda bağlantılar oluşturmak isteyebilirsiniz. Latte temelde bunları bilmez, ancak bunları eklemek çok kolaydır. Bağlantılar oluşturmak, [bağımlılık enjeksiyonu |dependency-injection:passing-dependencies] kullanarak geçirerek elde ettiğiniz nesne `Nette\Application\LinkGenerator` yapabilir. +E-postaları Nette Uygulaması ile birlikte, yani presenter'larla kullanıyorsanız, şablonlarda `n:href` niteliği veya `{link}` etiketi kullanarak bağlantılar oluşturmak isteyebilirsiniz. Latte bunları varsayılan olarak bilmez, ancak eklemek çok kolaydır. Bağlantıları oluşturabilen nesne `Nette\Application\LinkGenerator`'dır, buna [bağımlılık enjeksiyonu |dependency-injection:passing-dependencies] kullanarak iletilmesini isteyerek erişebilirsiniz: ```php use Nette; @@ -168,38 +169,38 @@ class MailSender } ``` -Şablonda, normal bir şablonda olduğu gibi bağlantı oluşturulur. LinkGenerator üzerinden oluşturulan tüm linkler mutlaktır: +Şablonda daha sonra bağlantıları alıştığımız gibi oluştururuz. LinkGenerator aracılığıyla oluşturulan tüm bağlantılar mutlak olacaktır. ```latte -<a n:href="Presenter:action">Link</a> +<a n:href="Presenter:action">Bağlantı</a> ``` -E-posta Gönderme .[#toc-sending-emails] -======================================= +E-posta Gönderme +================ -Mailer, e-posta göndermekten sorumlu sınıftır. [api:Nette\Mail\Mailer] arayüzünü uygular ve tanıtacağımız birkaç hazır mailer mevcuttur. +Mailer, e-postaların gönderilmesini sağlayan bir sınıftır. [api:Nette\Mail\Mailer] arayüzünü uygular ve tanıtacağımız birkaç önceden hazırlanmış mailer mevcuttur. -Çerçeve, DI konteynerine [yapılandırmaya |#Configuring] dayalı olarak otomatik olarak bir `Nette\Mail\Mailer` hizmeti ekler; bu hizmeti [bağımlılık enjeksiyonu |dependency-injection:passing-dependencies] kullanarak geçirirsiniz. +Framework, [yapılandırmaya |#Yapılandırma] dayalı olarak oluşturulan `Nette\Mail\Mailer` türünde bir hizmeti DI konteynerine otomatik olarak ekler ve buna [bağımlılık enjeksiyonu |dependency-injection:passing-dependencies] kullanarak iletilmesini isteyerek erişebilirsiniz. -SendmailMailer .[#toc-sendmailmailer] -------------------------------------- +SendmailMailer +-------------- -Varsayılan posta göndericisi, [php:mail] PHP işlevini kullanan SendmailMailer'dir. Kullanım örneği: +Varsayılan mailer, PHP [php:mail] fonksiyonunu kullanan SendmailMailer'dır. Kullanım örneği: ```php $mailer = new Nette\Mail\SendmailMailer; $mailer->send($mail); ``` -Eğer `returnPath` adresini ayarlamak istiyorsanız ve sunucu hala üzerine yazıyorsa, `$mailer->commandArgs = '-fmy@email.com'` adresini kullanın. +`returnPath`'i ayarlamak istiyorsanız ve sunucunuz onu sürekli olarak üzerine yazıyorsa, `$mailer->commandArgs = '-fMuj@email.cz'` kullanın. -SmtpMailer .[#toc-smtpmailer] ------------------------------ +SmtpMailer +---------- -SMTP sunucusu üzerinden posta göndermek için `SmtpMailer` adresini kullanın. +Postayı bir SMTP sunucusu üzerinden göndermek için `SmtpMailer` kullanılır. ```php $mailer = new Nette\Mail\SmtpMailer( @@ -211,19 +212,19 @@ $mailer = new Nette\Mail\SmtpMailer( $mailer->send($mail); ``` -Aşağıdaki ek parametreler kurucuya aktarılabilir: +Yapıcıya şu ek parametreler iletilebilir: -* `port` - ayarlanmamışsa, `ssl` için varsayılan 25 veya 465 kullanılacaktır +* `port` - ayarlanmazsa, `ssl` için varsayılan 25 veya 465 kullanılır * `timeout` - SMTP bağlantısı için zaman aşımı -* `persistent` - kalıcı bağlantı kullanın -* `clientHost` - müşteri tanımı -* `streamOptions` - bağlantı için "SSL bağlam seçeneklerini":https://www.php.net/manual/en/context.ssl.php ayarlamanızı sağlar +* `persistent` - kalıcı bağlantı kullan +* `clientHost` - istemci Host başlığını ayarla +* `streamOptions` - bağlantı için "SSL context options":https://www.php.net/manual/en/context.ssl.php ayarlamanıza olanak tanır -FallbackMailer .[#toc-fallbackmailer] -------------------------------------- +FallbackMailer +-------------- -E-posta göndermez, ancak bunları bir dizi postacı aracılığıyla gönderir. Bir postacı başarısız olursa, bir sonraki denemeyi tekrarlar. Sonuncusu başarısız olursa, ilkinden yeniden başlar. +E-postaları doğrudan göndermez, ancak bir dizi mailer aracılığıyla göndermeyi aracılık eder. Bir mailer başarısız olursa, bir sonrakiyle denemeyi tekrarlar. Sonuncusu da başarısız olursa, baştan tekrar başlar. ```php $mailer = new Nette\Mail\FallbackMailer([ @@ -234,16 +235,15 @@ $mailer = new Nette\Mail\FallbackMailer([ $mailer->send($mail); ``` -Yapıcıdaki diğer parametreler tekrar sayısını ve milisaniye cinsinden bekleme süresini içerir. +Yapıcıda ek parametreler olarak tekrar deneme sayısını ve milisaniye cinsinden bekleme süresini belirtebiliriz. -DKIM .[#toc-dkim] -================= +DKIM +==== -DKIM (DomainKeys Identified Mail), sahte mesajların tespit edilmesine de yardımcı olan güvenilir bir e-posta teknolojisidir. Gönderilen mesaj, gönderenin etki alanının özel anahtarı ile imzalanır ve bu imza e-posta başlığında saklanır. -Alıcının sunucusu bu imzayı alan adının DNS kayıtlarında saklanan açık anahtarla karşılaştırır. İmzanın eşleştirilmesiyle, e-postanın gerçekten gönderenin etki alanından kaynaklandığı ve mesajın iletimi sırasında değiştirilmediği gösterilir. +DKIM (DomainKeys Identified Mail), sahte mesajların tespit edilmesine de yardımcı olan e-postaların güvenilirliğini artırmaya yönelik bir teknolojidir. Gönderilen mesaj, gönderenin alan adının özel anahtarıyla imzalanır ve bu imza e-posta başlığında saklanır. Alıcı sunucu, bu imzayı alan adının DNS kayıtlarında saklanan genel anahtarla karşılaştırır. İmzanın eşleşmesiyle, e-postanın gerçekten gönderenin alan adından geldiği ve mesajın iletimi sırasında değiştirilmediği kanıtlanır. -[Yapılandırmada |#Configuring] e-postayı imzalamak için mailer'ı ayarlayabilirsiniz. Eğer dependency injection kullanmıyorsanız aşağıdaki gibi kullanılır: +E-postaların imzalanmasını mailer'a doğrudan [yapılandırmada |#Yapılandırma] ayarlayabilirsiniz. Bağımlılık enjeksiyonu kullanmıyorsanız, bu şekilde kullanılır: ```php $signer = new Nette\Mail\DkimSigner( @@ -253,46 +253,46 @@ $signer = new Nette\Mail\DkimSigner( passPhrase: '****', ); -$mailer = new Nette\Mail\SendmailMailer; // or SmtpMailer +$mailer = new Nette\Mail\SendmailMailer; // veya SmtpMailer $mailer->setSigner($signer); $mailer->send($mail); ``` -Yapılandırma .[#toc-configuring] -================================ +Yapılandırma +============ -Nette Mail için yapılandırma seçeneklerine genel bakış. Tüm çerçeveyi değil, yalnızca bu kütüphaneyi kullanıyorsanız, [yapılandırmayı nasıl yükleyeceğinizi |bootstrap:] okuyun. +Nette Mail için yapılandırma seçeneklerine genel bakış. Tüm framework'ü değil, yalnızca bu kütüphaneyi kullanıyorsanız, [yapılandırmanın nasıl yükleneceğini |bootstrap:] okuyun. -Varsayılan olarak, e-posta göndermek için `Nette\Mail\SendmailMailer` mailer kullanılır ve daha fazla yapılandırılmaz. Ancak, bunu `Nette\Mail\SmtpMailer` olarak değiştirebiliriz: +E-postaları göndermek için varsayılan olarak `Nette\Mail\SendmailMailer` mailer'ı kullanılır ve bu daha fazla yapılandırılmaz. Ancak, onu `Nette\Mail\SmtpMailer` olarak değiştirebiliriz: ```neon mail: - # use SmtpMailer - smtp: true # (bool) varsayılan değer false + # SmtpMailer kullanır + smtp: true # (bool) varsayılan false'dur host: ... # (string) port: ... # (int) username: ... # (string) password: ... # (string) timeout: ... # (int) - encryption: ... # (ssl|tls|null) varsayılan olarak null ('secure' takma adına sahiptir) - clientHost: ... # (string) varsayılan olarak $_SERVER['HTTP_HOST'] - persistent: ... # (bool) varsayılan değer false + encryption: ... # (ssl|tls|null) varsayılan null'dur ('secure' takma adı vardır) + clientHost: ... # (string) varsayılan $_SERVER['HTTP_HOST']'dur + persistent: ... # (bool) varsayılan false'dur - # SMTP sunucusuna bağlanmak için bağlam, varsayılan olarak stream_context_get_default() + # SMTP sunucusuna bağlanmak için bağlam, varsayılan stream_context_get_default()'dur context: - ssl: # https://www.php.net/manual/en/context.ssl.php adresindeki tüm seçenekler + ssl: # seçeneklerin özeti https://www.php.net/manual/en/context.ssl.php adresinde allow_self_signed: ... ... - http: # tüm seçenekler https://www.php.net/manual/en/context.http.php adresinde + http: # seçeneklerin özeti https://www.php.net/manual/en/context.http.php adresinde header: ... ... ``` -`context › ssl › verify_peer: false` seçeneğini kullanarak SSL sertifikası kimlik doğrulamasını devre dışı bırakabilirsiniz. Uygulamayı savunmasız hale getireceği için bunu yapmamanız **şiddetle tavsiye edilir**. Bunun yerine, "sertifikaları güven deposuna ekle":https://www.php.net/manual/en/openssl.configuration.php. +`context › ssl › verify_peer: false` seçeneği ile SSL sertifika doğrulamasını kapatabilirsiniz. **Kesinlikle tavsiye etmiyoruz** bunu yapmanızı, çünkü uygulama savunmasız hale gelir. Bunun yerine "sertifikaları depoya ekleyin":https://www.php.net/manual/en/openssl.configuration.php. -Güvenilirliği artırmak için [DKIM teknolojisini |https://blog.nette.org/tr/e-postalari-dkim-ile-imzalayin] kullanarak e-postaları imzalayabiliriz: +Güvenilirliği artırmak için e-postaları [DKIM teknolojisi |https://blog.nette.org/tr/sign-emails-with-dkim] kullanarak imzalayabiliriz: ```neon mail: @@ -304,4 +304,12 @@ mail: ``` -{{leftbar: nette:@menu-topics}} +DI Hizmetleri +============= + +Bu hizmetler DI konteynerine eklenir: + +| Ad | Tip | Açıklama +|----------------------------------------------------- +| `mail.mailer` | [api:Nette\Mail\Mailer] | [e-postaları gönderen sınıf |#E-posta Gönderme] +| `mail.signer` | [api:Nette\Mail\Signer] | [DKIM imzalama |#DKIM] diff --git a/mail/tr/@meta.texy b/mail/tr/@meta.texy new file mode 100644 index 0000000000..e5c5cea355 --- /dev/null +++ b/mail/tr/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Dokümantasyonu}} +{{leftbar: nette:@menu-topics}} diff --git a/mail/uk/@home.texy b/mail/uk/@home.texy index 89a9f430df..5c143dbc2e 100644 --- a/mail/uk/@home.texy +++ b/mail/uk/@home.texy @@ -1,97 +1,98 @@ -Надсилання електронних листів -***************************** +Nette Mail +********** <div class=perex> -Чи збираєтеся ви надсилати електронні листи, наприклад, інформаційні бюлетені або підтвердження замовлень? Фреймворк Nette надає необхідні інструменти з дуже зручним API. Ми покажемо: +Ви збираєтеся надсилати електронні листи, наприклад, інформаційні бюлетені або підтвердження замовлень? Nette Framework надає необхідні інструменти з дуже зручним API. Ми покажемо вам: -- як створити електронний лист, включно з вкладеннями -- як відправити -- як об'єднати електронні листи та шаблони +- як створити електронний лист, включаючи вкладення +- як його надіслати +- як поєднати електронні листи та шаблони </div> -Встановлення .[#toc-installation] -================================= +Встановлення +============ -Завантажте та встановіть пакет за допомогою [Composer |best-practices:composer]: +Завантажте та встановіть бібліотеку за допомогою [Composer |best-practices:composer]: ```shell composer require nette/mail ``` -Створення електронних листів .[#toc-creating-emails] -==================================================== +Створення електронного листа +============================ -Email - це об'єкт [api:Nette\Mail\Message]: +Електронний лист є об'єктом класу [api:Nette\Mail\Message]. Створимо його, наприклад, так: ```php $mail = new Nette\Mail\Message; -$mail->setFrom('John <john@example.com>') - ->addTo('peter@example.com') - ->addTo('jack@example.com') - ->setSubject('Подтверждение заказа') - ->setBody("Здравствуйте. Ваш заказ принят."); +$mail->setFrom('Franta <franta@example.com>') + ->addTo('petr@example.com') + ->addTo('jirka@example.com') + ->setSubject('Підтвердження замовлення') + ->setBody("Добрий день,\nваше замовлення було прийнято."); ``` -Усі параметри мають бути закодовані в UTF-8. +Усі введені параметри мають бути в кодуванні UTF-8. -Крім зазначення одержувачів за допомогою методу `addTo()`, ви також можете вказати одержувача копії за допомогою `addCc()`, або одержувача прихованої копії за допомогою `addBcc()`. Усі ці методи, включно з `setFrom()`, приймають адресата трьома способами: +Окрім зазначення одержувача за допомогою методу `addTo()`, можна також вказати одержувача копії `addCc()` або одержувача прихованої копії `addBcc()`. У всіх цих методах, включаючи `setFrom()`, ми можемо записати адресу трьома способами: ```php -$mail->setFrom('john.doe@example.com'); -$mail->setFrom('john.doe@example.com', 'John Doe'); -$mail->setFrom('John Doe <john.doe@example.com>'); +$mail->setFrom('franta@example.com'); +$mail->setFrom('franta@example.com', 'Franta'); +$mail->setFrom('Franta <franta@example.com>'); ``` -Тіло листа, написане у форматі HTML, передається за допомогою методу `setHtmlBody()`: +Тіло електронного листа, написане в HTML, передається за допомогою методу `setHtmlBody()`: ```php -$mail->setHtmlBody('<p>Hello,</p><p>Ваш заказ принят.</p>'); +$mail->setHtmlBody('<p>Добрий день,</p><p>ваше замовлення було прийнято.</p>'); ``` -Вам не потрібно створювати текстовий варіант листа, Nette згенерує його автоматично за вас. А якщо лист не має теми, її буде взято з елемента `<title>`. +Текстову альтернативу створювати не потрібно, Nette згенерує її автоматично за вас. А якщо електронний лист не має встановленої теми, спробує взяти її з елемента `<title>`. -Зображення також можна дуже легко вставити в HTML-тіло листа. Просто передайте шлях, де фізично розташовані зображення, як другий параметр, і Nette автоматично включить їх до листа: +До HTML-тіла також можна надзвичайно легко вставляти зображення. Достатньо передати шлях, де фізично знаходяться зображення, як другий параметр, і Nette автоматично включить їх до електронного листа: ```php -// автоматично додає /path/to/images/background.gif до листа +// автоматично додасть /path/to/images/background.gif до електронного листа $mail->setHtmlBody( '<b>Привіт</b> <img src="background.gif">', '/path/to/images', ); ``` -Алгоритм вбудовування зображень підтримує такі шаблони: `<img src=...>`, `<body background=...>`, `url(...)` всередині HTML-атрибута `style` і спеціальний синтаксис `[[...]]`. +Алгоритм вставки зображень шукає такі шаблони: `<img src=...>`, `<body background=...>`, `url(...)` всередині HTML-атрибута `style` та спеціальний синтаксис `[[...]]`. -Чи можна зробити надсилання електронних листів ще простішим? +Чи може надсилання електронних листів бути ще простішим? -Електронні листи - це як листівки. Ніколи не надсилайте паролі або інші облікові дані електронною поштою. .[tip] +.[tip] +Електронний лист — це щось на зразок листівки. Ніколи не надсилайте електронною поштою паролі чи інші дані доступу. -Вкладення .[#toc-attachments] ------------------------------ +Вкладення +--------- -Зрозуміло, ви можете прикріплювати вкладення до електронних листів. Використовуйте команду `addAttachment(string $file, string $content = null, string $contentType = null)`. +До електронного листа, звісно, можна вкладати файли. Для цього служить метод `addAttachment(string $file, ?string $content = null, ?string $contentType = null)`. ```php -// вставляє файл /path/to/example.zip в електронний лист під ім'ям example.zip +// вставить до електронного листа файл /path/to/example.zip під назвою example.zip $mail->addAttachment('/path/to/example.zip'); -// вставляє файл /path/to/example.zip в електронний лист під іменем info.zip +// вставить до електронного листа файл /path/to/example.zip під назвою info.zip $mail->addAttachment('info.zip', file_get_contents('/path/to/example.zip')); -// вставляє файл example.txt із текстом "Hello John!" -$mail->addAttachment('example.txt', 'Hello John!'); +// вставить до електронного листа файл example.txt з вмістом "Привіт, John!" +$mail->addAttachment('example.txt', 'Привіт, John!'); ``` -Шаблони .[#toc-templates] -------------------------- +Шаблони +------- -Якщо ви надсилаєте електронні листи у форматі HTML, чудова ідея - писати їх у системі шаблонів [Latte |latte:]. Як це зробити? +Якщо ви надсилаєте HTML-листи, дуже зручно писати їх у системі шаблонів [Latte |latte:]. Як це зробити? ```php $latte = new Latte\Engine; @@ -100,8 +101,8 @@ $params = [ ]; $mail = new Nette\Mail\Message; -$mail->setFrom('John <john@example.com>') - ->addTo('jack@example.com') +$mail->setFrom('Franta <franta@example.com>') + ->addTo('petr@example.com') ->setHtmlBody( $latte->renderToString('/path/to/email.latte', $params), '/path/to/images', @@ -114,7 +115,7 @@ $mail->setFrom('John <john@example.com>') <html> <head> <meta charset="utf-8"> - <title>Подтверждение заказа + Підтвердження замовлення -

                                                                                                                                        Здравствуйте!

                                                                                                                                        +

                                                                                                                                        Добрий день,

                                                                                                                                        -

                                                                                                                                        Ваш заказ под номером {$orderId} был принят.

                                                                                                                                        +

                                                                                                                                        Ваше замовлення номер {$orderId} було прийнято.

                                                                                                                                        ``` -Nette автоматично вставляє всі зображення, встановлює тему відповідно до елемента `` і генерує текстову альтернативу для тіла HTML. +Nette автоматично вставить усі зображення, встановить тему відповідно до елемента `<title>` та згенерує текстову альтернативу до HTML. -Використання в додатку Nette .[#toc-using-in-nette-application] ---------------------------------------------------------------- +Використання в Nette Application +-------------------------------- -Якщо ви використовуєте електронну пошту разом із Nette Application, тобто презентерами, ви можете захотіти створити посилання в шаблонах, використовуючи атрибут `n:href` або тег `{link}`. Latte в принципі їх не знає, але їх дуже легко додати. Створенням посилань може займатися об'єкт `Nette\Application\LinkGenerator`, який ви отримаєте, передавши його за допомогою [впровадження залежностей |dependency-injection:passing-dependencies]. +Якщо ви використовуєте електронні листи разом з Nette Application, тобто з presenter'ами, ви можете захотіти створювати посилання в шаблонах за допомогою атрибута `n:href` або тегу `{link}`. Latte за замовчуванням їх не знає, але їх дуже легко додати. Створювати посилання вміє об'єкт `Nette\Application\LinkGenerator`, до якого ви можете отримати доступ, передавши його за допомогою [dependency injection |dependency-injection:passing-dependencies]: ```php use Nette; @@ -168,38 +169,38 @@ class MailSender } ``` -У шаблоні посилання створюється як у звичайному шаблоні. Усі посилання, створені за допомогою LinkGenerator, є абсолютними: +У шаблоні ми потім створюємо посилання так, як звикли. Усі посилання, створені через LinkGenerator, будуть абсолютними. ```latte -<a n:href="Presenter:action">Link</a> +<a n:href="Presenter:action">Посилання</a> ``` -Надсилання електронних листів .[#toc-sending-emails] -==================================================== +Відправлення електронного листа +=============================== -Mailer - це клас, який відповідає за надсилання електронних листів. Він реалізує інтерфейс [api:Nette\Mail\Mailer] і пропонує кілька готових поштових програм, які ми представимо. +Mailer — це клас, який забезпечує відправлення електронних листів. Він реалізує інтерфейс [api:Nette\Mail\Mailer], і доступно кілька готових мейлерів, які ми розглянемо. -Фреймворк автоматично додає сервіс `Nette\Mail\Mailer` на основі [configuration |#Configuring] в контейнер DI, який ви отримуєте, передаючи його за допомогою [впровадження залежностей |dependency-injection:passing-dependencies]. +Framework автоматично додає до DI-контейнера сервіс типу `Nette\Mail\Mailer`, зібраний на основі [конфігурації |#Конфігурація], до якого ви можете отримати доступ, передавши його за допомогою [dependency injection |dependency-injection:passing-dependencies]. -SendmailMailer .[#toc-sendmailmailer] -------------------------------------- +SendmailMailer +-------------- -За замовчуванням використовується SendmailMailer, який використовує функцію PHP [php:mail]. Приклад використання: +Мейлер за замовчуванням — це SendmailMailer, який використовує PHP-функцію [php:mail]. Приклад використання: ```php $mailer = new Nette\Mail\SendmailMailer; $mailer->send($mail); ``` -Якщо ви хочете встановити `returnPath`, але сервер все одно перезаписує його, використовуйте `$mailer->commandArgs = '-fmy@email.com'`. +Якщо ви хочете встановити `returnPath`, а сервер постійно його перезаписує, використовуйте `$mailer->commandArgs = '-fMy@email.cz'`. -SmtpMailer .[#toc-smtpmailer] ------------------------------ +SmtpMailer +---------- -Для надсилання пошти через SMTP-сервер використовуйте `SmtpMailer`. +Для надсилання пошти через SMTP-сервер використовується `SmtpMailer`. ```php $mailer = new Nette\Mail\SmtpMailer( @@ -211,19 +212,19 @@ $mailer = new Nette\Mail\SmtpMailer( $mailer->send($mail); ``` -У конструктор можуть бути передані такі додаткові параметри: +Конструктору можна передати такі додаткові параметри: -* `port` - якщо не задано, використовуватиметься стандартне значення 25 або 465 для `ssl`. -* `timeout` - тайм-аут для SMTP-з'єднання +* `port` - якщо не встановлено, використовується стандартний 25 або 465 для `ssl` +* `timeout` - таймаут для SMTP-з'єднання * `persistent` - використовувати постійне з'єднання -* `clientHost` - призначення клієнта -* `streamOptions` - дозволяє встановити "опції контексту SSL":https://www.php.net/manual/ru/context.ssl.php для з'єднання +* `clientHost` - налаштування заголовка Host клієнта +* `streamOptions` - дозволяє налаштувати [SSL context options](https://www.php.net/manual/en/context.ssl.php) для з'єднання -FallbackMailer .[#toc-fallbackmailer] -------------------------------------- +FallbackMailer +-------------- -Він не надсилає листи електронною поштою, а розсилає їх через набір розсилок. Якщо одна розсилка не вдалася, вона повторює спробу на наступній. Якщо остання з них не працює, то все починається заново з першої. +Електронні листи безпосередньо не надсилає, але забезпечує надсилання через набір мейлерів. У випадку, якщо один мейлер зазнає невдачі, він повторює спробу з наступним. Якщо зазнає невдачі й останній, починає знову з першого. ```php $mailer = new Nette\Mail\FallbackMailer([ @@ -234,16 +235,15 @@ $mailer = new Nette\Mail\FallbackMailer([ $mailer->send($mail); ``` -Інші параметри в конструкторі включають число повторів і час очікування в мілісекундах. +Як додаткові параметри в конструкторі можна вказати кількість повторень та час очікування в мілісекундах. -DKIM .[#toc-dkim] -================= +DKIM +==== -DKIM (DomainKeys Identified Mail) - це технологія надійної електронної пошти, яка також допомагає виявити підроблені повідомлення. Відправлене повідомлення підписується закритим ключем домену відправника, і цей підпис зберігається в заголовку електронної пошти. -Сервер одержувача порівнює цей підпис із відкритим ключем, що зберігається в DNS-записах домену. Звіряючи підпис, можна довести, що електронний лист справді надійшов із домену відправника і що повідомлення не було змінено під час його передавання. +DKIM (DomainKeys Identified Mail) — це технологія для підвищення довіри до електронних листів, яка також допомагає виявляти підроблені повідомлення. Надіслане повідомлення підписується приватним ключем домену відправника, і цей підпис зберігається в заголовку електронного листа. Сервер одержувача порівнює цей підпис з публічним ключем, збереженим у DNS-записах домену. Якщо підпис відповідає, це доводить, що електронний лист дійсно надійшов з домену відправника і що під час передачі повідомлення не було змінено. -Ви можете налаштувати mailer для підпису електронної пошти в [конфігурації |#Configuring]. Якщо ви не використовуєте впровадження залежностей, він задається таким чином: +Підписування електронних листів можна налаштувати для мейлера безпосередньо в [конфігурації |#Конфігурація]. Якщо ви не використовуєте dependency injection, це робиться таким чином: ```php $signer = new Nette\Mail\DkimSigner( @@ -253,22 +253,22 @@ $signer = new Nette\Mail\DkimSigner( passPhrase: '****', ); -$mailer = new Nette\Mail\SendmailMailer; // или SmtpMailer +$mailer = new Nette\Mail\SendmailMailer; // або SmtpMailer $mailer->setSigner($signer); $mailer->send($mail); ``` -Конфігурація .[#toc-configuring] -================================ +Конфігурація +============ -Огляд параметрів конфігурації для Nette Mail. Якщо ви використовуєте не весь фреймворк, а тільки цю бібліотеку, прочитайте [Як завантажити файл конфігурації |bootstrap:]. +Огляд параметрів конфігурації для Nette Mail. Якщо ви використовуєте не весь фреймворк, а лише цю бібліотеку, прочитайте, [як завантажити конфігурацію |bootstrap:]. -За замовчуванням для надсилання листів використовується поштова програма `Nette\Mail\SendmailMailer`, яка більше не налаштовується. Однак ми можемо переключити його на `Nette\Mail\SmtpMailer`: +Для надсилання електронних листів стандартно використовується мейлер `Nette\Mail\SendmailMailer`, який далі не конфігурується. Однак ми можемо переключити його на `Nette\Mail\SmtpMailer`: ```neon mail: - # використовуємо SmtpMailer + # використовує SmtpMailer smtp: true # (bool) за замовчуванням false host: ... # (string) @@ -276,23 +276,23 @@ mail: username: ... # (string) password: ... # (string) timeout: ... # (int) - encryption: ... # (ssl|tls|null) за замовчуванням null + encryption: ... # (ssl|tls|null) за замовчуванням null (має псевдонім 'secure') clientHost: ... # (string) за замовчуванням $_SERVER['HTTP_HOST'] persistent: ... # (bool) за замовчуванням false - # контекст для підключення до SMTP-сервера, за замовчуванням використовується stream_context_get_default() + # контекст для підключення до SMTP-сервера, за замовчуванням stream_context_get_default() context: - ssl: # усі варіанти на https://www.php.net/manual/en/context.ssl.php + ssl: # огляд параметрів на https://www.php.net/manual/en/context.ssl.php allow_self_signed: ... ... - http: # усі варіанти на https://www.php.net/manual/en/context.http.php + http: # огляд параметрів на https://www.php.net/manual/en/context.http.php header: ... ... ``` -Ви можете вимкнути перевірку автентичності SSL-сертифіката за допомогою опції `context › ssl › verify_peer: false`. Настійно рекомендується **не робити цього**, оскільки це зробить додаток вразливим. Натомість, "додайте сертифікати до сховища довіри":https://www.php.net/manual/en/openssl.configuration.php. +За допомогою опції `context › ssl › verify_peer: false` можна вимкнути перевірку SSL-сертифікатів. **Наполегливо не рекомендуємо** цього робити, оскільки програма стане вразливою. Замість цього "додайте сертифікати до сховища":https://www.php.net/manual/en/openssl.configuration.php. -Щоб підвищити довіру, ми можемо підписувати електронні листи за допомогою [технології DKIM |https://blog.nette.org/uk/pidpisujte-elektronni-listi-za-dopomogoyu-dkim]: +Для підвищення довіри ми можемо підписувати електронні листи за допомогою [технології DKIM |https://blog.nette.org/uk/sign-emails-with-dkim]: ```neon mail: @@ -304,4 +304,12 @@ mail: ``` -{{leftbar: nette:@menu-topics}} +Сервіси DI +========== + +Ці сервіси додаються до DI-контейнера: + +| Назва | Тип | Опис +|----------------------------------------------------- +| `mail.mailer` | [api:Nette\Mail\Mailer] | [клас, що надсилає електронні листи |#Відправлення електронного листа] +| `mail.signer` | [api:Nette\Mail\Signer] | [DKIM підписування |#DKIM] diff --git a/mail/uk/@meta.texy b/mail/uk/@meta.texy new file mode 100644 index 0000000000..083a8ab9f7 --- /dev/null +++ b/mail/uk/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Документація Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/migrations/cs/@home.texy b/migrations/cs/@home.texy index 9ef31ca6f9..18e175c7d9 100644 --- a/migrations/cs/@home.texy +++ b/migrations/cs/@home.texy @@ -1,6 +1,7 @@ Přechod na novější verze ************************ +- [z Nette 3.1 na 3.2 |to-3-2] - [z Nette 3.0 na 3.1 |to-3-1] - [z Nette 2.4 na 3.0 |to-3-0] - [z Nette 2.3 na 2.4 |to-2-4] diff --git a/migrations/cs/@left-menu.texy b/migrations/cs/@left-menu.texy index 82c0d7e4a7..6f807d7d72 100644 --- a/migrations/cs/@left-menu.texy +++ b/migrations/cs/@left-menu.texy @@ -1,6 +1,7 @@ Přechod na novější verze ************************ - [Úvod |@home] +- [Z verze 3.1 na 3.2 |to-3-2] - [Z verze 3.0 na 3.1 |to-3-1] - [Z verze 2.4 na 3.0 |to-3-0] - [Z verze 2.3 na 2.4 |to-2-4] diff --git a/migrations/cs/@meta.texy b/migrations/cs/@meta.texy new file mode 100644 index 0000000000..462d9add80 --- /dev/null +++ b/migrations/cs/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokumentace}} diff --git a/migrations/cs/to-2-1.texy b/migrations/cs/to-2-1.texy index 838fbac0c8..f103f577ca 100644 --- a/migrations/cs/to-2-1.texy +++ b/migrations/cs/to-2-1.texy @@ -2,7 +2,7 @@ Přechod na verzi 2.1 ******************** .[perex] -Nová verze přináší [#nové vlastnosti] a některé [nekompatibility|#nekompatibility], které je třeba projít a kód otestovat před nasazením nové verze. +Nová verze přináší [#nové vlastnosti] a některé [#nekompatibility], které je třeba projít a kód otestovat před nasazením nové verze. Nové vlastnosti diff --git a/migrations/cs/to-2-2.texy b/migrations/cs/to-2-2.texy index a226618867..17a08abff0 100644 --- a/migrations/cs/to-2-2.texy +++ b/migrations/cs/to-2-2.texy @@ -3,8 +3,7 @@ Přechod na verzi 2.2 Verze 2.2 přichází s úplně novou infrastrukturou. Původní repozitář "Nette":https://github.com/nette/nette byl rozdělen do nových samostatných komponent. .[perex] -Nette bylo rozděleno do samostatných komponent "Application":https://github.com/nette/application, "Bootstrap":https://github.com/nette/bootstrap, "Caching":https://github.com/nette/caching, "ComponentModel":https://github.com/nette/component-model, "Nette Database":https://github.com/nette/database, "DI":https://github.com/nette/di, "Finder":https://github.com/nette/finder, "Forms":https://github.com/nette/forms, "Http":https://github.com/nette/http, "Latte":https://github.com/nette/latte, "Mail":https://github.com/nette/mail, "Neon":https://github.com/nette/neon, "PhpGenerator":https://github.com/nette/php-generator, "Reflection":https://github.com/nette/reflection, "RobotLoader":https://github.com/nette/robot-loader, "SafeStream":https://github.com/nette/safe-stream, "Security":https://github.com/nette/security, "Tokenizer":https://github.com/nette/tokenizer, "Tracy":https://github.com/nette/tracy a "Utils":https://github.com/nette/utils. -Každá komponenta má vlastní repozitář, issue tracker a číslování verzí. +Nette bylo rozděleno do samostatných komponent "Application":https://github.com/nette/application, "Bootstrap":https://github.com/nette/bootstrap, "Caching":https://github.com/nette/caching, "ComponentModel":https://github.com/nette/component-model, "Nette Database":https://github.com/nette/database, "DI":https://github.com/nette/di, "Finder":https://github.com/nette/finder, "Forms":https://github.com/nette/forms, "Http":https://github.com/nette/http, "Latte":https://github.com/nette/latte, "Mail":https://github.com/nette/mail, "Neon":https://github.com/nette/neon, "PhpGenerator":https://github.com/nette/php-generator, "Reflection":https://github.com/nette/reflection, "RobotLoader":https://github.com/nette/robot-loader, "SafeStream":https://github.com/nette/safe-stream, "Security":https://github.com/nette/security, "Tokenizer":https://github.com/nette/tokenizer, "Tracy":https://github.com/nette/tracy a "Utils":https://github.com/nette/utils. Každá komponenta má vlastní repozitář, issue tracker a číslování verzí. Stále si však můžete stáhnout celý framework nebo jej nainstalovat pomocí [Composeru |best-practices:composer] příkazem `composer require nette/nette`. diff --git a/migrations/cs/to-2-3.texy b/migrations/cs/to-2-3.texy index 2678fce526..bff8ab9808 100644 --- a/migrations/cs/to-2-3.texy +++ b/migrations/cs/to-2-3.texy @@ -1,14 +1,12 @@ Přechod na verzi 2.3 ******************** -Verzí Nette 2.3 se rozumí, že máte tyto balíčky nainstalované ve verze 2.3.*. Ostatní balíčky mohou mít vyšší nebo nižší čísla verzí, kompatibilitu hlídá Composer. +Verzí Nette 2.3 se rozumí, že máte tyto balíčky nainstalované ve verzi 2.3.*: ```json "require": { "nette/application": "2.3.*", "nette/bootstrap": "2.3.*", - "nette/caching": "2.3.*", - "nette/database": "2.3.*", "nette/di": "2.3.*", "nette/forms": "2.3.*", "nette/http": "2.3.*", diff --git a/migrations/cs/to-2-4.texy b/migrations/cs/to-2-4.texy index 7e9857cc16..4a12f4a9a9 100644 --- a/migrations/cs/to-2-4.texy +++ b/migrations/cs/to-2-4.texy @@ -3,14 +3,12 @@ Přechod na verzi 2.4 Minimální požadovaná verze PHP je 5.6 (pro Latte a Tracy 5.4). -Verzí Nette 2.4 se rozumí, že máte tyto balíčky nainstalované ve verze 2.4.* (resp. 2.5.*). Ostatní balíčky mohou mít vyšší nebo nižší čísla verzí, kompatibilitu hlídá Composer. +Verzí Nette 2.4 se rozumí, že máte tyto balíčky nainstalované ve verzi 2.4.*: ```json "require": { "nette/application": "2.4.*", "nette/bootstrap": "2.4.*", - "nette/caching": "2.5.*", - "nette/database": "2.4.*", "nette/di": "2.4.*", "nette/forms": "2.4.*", "nette/http": "2.4.*", @@ -18,6 +16,8 @@ Verzí Nette 2.4 se rozumí, že máte tyto balíčky nainstalované ve verze 2. }, ``` +Pozor: u ostatních balíčků nainstalujte nejnovější verzi, kterou vám dovolí Composer! Mohou mít totiž jiné verzování. Zkuste uvést `"*"` a podívejte se, jakou verzi Composer nainstaluje. + Deprecated ========== diff --git a/migrations/cs/to-3-0.texy b/migrations/cs/to-3-0.texy index b0f3168f36..b464c6d7fe 100644 --- a/migrations/cs/to-3-0.texy +++ b/migrations/cs/to-3-0.texy @@ -3,14 +3,12 @@ Přechod na verzi 3.0 Minimální požadovaná verze PHP je 7.1. -Verzí Nette 3.0 se rozumí, že máte tyto balíčky nainstalované ve verze 3.0.*. Ostatní balíčky mohou mít vyšší nebo nižší čísla verzí, kompatibilitu hlídá Composer. +Verzí Nette 3.0 se rozumí, že máte tyto balíčky nainstalované ve verzi 3.0.*: ```json "require": { "nette/application": "3.0.*", "nette/bootstrap": "3.0.*", - "nette/caching": "3.0.*", - "nette/database": "3.0.*", "nette/di": "3.0.*", "nette/forms": "3.0.*", "nette/http": "3.0.*", @@ -18,6 +16,8 @@ Verzí Nette 3.0 se rozumí, že máte tyto balíčky nainstalované ve verze 3. }, ``` +Pozor: u ostatních balíčků nainstalujte nejnovější verzi, kterou vám dovolí Composer! Mohou mít totiž jiné verzování. Zkuste uvést `"*"` a podívejte se, jakou verzi Composer nainstaluje. + PHP 7.1 type hints ------------------ @@ -57,11 +57,9 @@ Presentery & komponenty Konstruktor `Nette\ComponentModel\Component` nebyl roky používán a byl odstraněn ve verzi 3.0. Je to BC break. Pokud voláte rodičovský konstruktor ve vaší komponentě nebo presenteru zděděném od `Nette\Application\UI\Presenter`, musíte volání odstranit. -Rozhraní `Nette\Application\IRouter` bylo změněno, viz "původní":https://github.com/nette/application/blob/v2.4.0/src/Application/IRouter.php a "nové":https://github.com/nette/routing/blob/v3.0.0/src/Routing/Router.php. -Nyní metoda `match()` vrací a `constructUrl()` přijímá pole parameterů namísto objektu `Nette\Application\Request`. +Rozhraní `Nette\Application\IRouter` bylo změněno, viz "původní":https://github.com/nette/application/blob/v2.4.0/src/Application/IRouter.php a "nové":https://github.com/nette/routing/blob/v3.0.0/src/Routing/Router.php. Nyní metoda `match()` vrací a `constructUrl()` přijímá pole parameterů namísto objektu `Nette\Application\Request`. -Nette nyní zkontroluje, zda každý signal je odeslán ze stejného *originu* (tj. ze stejné domény a subdomény). Same-origin policy je kritický bezpečnostní mechanismus, který pomáhá redukovat možné vektory útoku. -Pokud chcete povolit další *původy*, přidejte k metodě obsluhující signál anotaci `@crossOrigin`: +Nette nyní zkontroluje, zda každý signal je odeslán ze stejného *originu* (tj. ze stejné domény a subdomény). Same-origin policy je kritický bezpečnostní mechanismus, který pomáhá redukovat možné vektory útoku. Pokud chcete povolit další *původy*, přidejte k metodě obsluhující signál anotaci `@crossOrigin`: ```php /** diff --git a/migrations/cs/to-3-1.texy b/migrations/cs/to-3-1.texy index 87cb17b6d9..0b3fd295ef 100644 --- a/migrations/cs/to-3-1.texy +++ b/migrations/cs/to-3-1.texy @@ -3,22 +3,22 @@ Přechod na verzi 3.1 Minimální požadovaná verze PHP je 7.2. -Všechny změny názvů uvedené v tomto dokumentu znamenají, že původní název samozřejmě nadále existuje a funguje, jen je označený jako deprecated. Můžete se setkat s tím, že vám je bude IDE vizuálně označovat jako deprecated. - -Verzí Nette 3.1 se rozumí, že máte tyto balíčky nainstalované ve verze 3.1.*. Ostatní balíčky mohou mít vyšší nebo nižší čísla verzí, kompatibilitu hlídá Composer. +Verzí Nette 3.1 se rozumí, že máte tyto balíčky nainstalované ve verze 3.1.*: ```json "require": { "nette/application": "3.1.*", "nette/bootstrap": "3.1.*", - "nette/caching": "3.1.*", - "nette/database": "3.1.*", "nette/forms": "3.1.*", "nette/http": "3.1.*", "nette/security": "3.1.*", }, ``` +Pozor: u ostatních balíčků nainstalujte nejnovější verzi, kterou vám dovolí Composer! Mohou mít totiž jiné verzování. Zkuste uvést `"*"` a podívejte se, jakou verzi Composer nainstaluje. + +Všemi změnami názvů uvedenými v tomto dokumentu se myslí, že původní názvy samozřejmě nadále existují a fungují, jen jsou označené jako deprecated. Můžete se setkat s tím, že vám je bude IDE vizuálně označovat jako deprecated. + Názvy rozhraní -------------- diff --git a/migrations/cs/to-3-2.texy b/migrations/cs/to-3-2.texy new file mode 100644 index 0000000000..d8ed7422e5 --- /dev/null +++ b/migrations/cs/to-3-2.texy @@ -0,0 +1,28 @@ +Přechod na verzi 3.2 +******************** + +Minimální požadovaná verze PHP je 8.1. + +Verzí Nette 3.2 se rozumí, že máte tyto balíčky nainstalované ve verze 3.2.*: + +```json +"require": { + "nette/application": "3.2.*", + "nette/bootstrap": "3.2.*", + "nette/forms": "3.2.*", + "nette/http": "3.3.*", + "nette/security": "3.2.*", +}, +``` + +Pozor: u ostatních balíčků nainstalujte nejnovější verzi, kterou vám dovolí Composer! Mohou mít totiž jiné verzování. Zkuste uvést `"*"` a podívejte se, jakou verzi Composer nainstaluje. + + +Novinky +------- + +Tato verze využívají všech vymožeností PHP 8. Kód tedy obsahuje všechny nové typehinty zavedené v PHP 8, typy u properties, nové jazykové featury a nativní funkce. Pokusil jsem se maximálně zachovat zpětnou kompatibilitu a tam, kde by přidání typu mohlo něco rozbít, jsem ho zatím nepřidával. + +Dále třídy až na výjimky přestaly využívat traitu Nette\SmartObject, protože její vylepšení po dlouhých letech proniklo do samotného PHP a už tak není potřeba. + +Finder se přestěhoval do balíčku `nette/utils`, odstraňte jej z composeru. diff --git a/migrations/en/@home.texy b/migrations/en/@home.texy index d49961c0f8..33b6c35c23 100644 --- a/migrations/en/@home.texy +++ b/migrations/en/@home.texy @@ -1,6 +1,7 @@ Upgrade Guide ************* +- [from Nette 3.1 to 3.2 |to-3-2] - [from Nette 3.0 to 3.1 |to-3-1] - [from Nette 2.4 to 3.0 |to-3-0] - [from Nette 2.3 to 2.4 |to-2-4] diff --git a/migrations/en/@left-menu.texy b/migrations/en/@left-menu.texy index af1cd6e312..15d695bbc4 100644 --- a/migrations/en/@left-menu.texy +++ b/migrations/en/@left-menu.texy @@ -1,6 +1,7 @@ Upgrade Guide ************* - [Overview |@home] +- [From 3.1 to 3.2 |to-3-2] - [From 3.0 to 3.1 |to-3-1] - [From 2.4 to 3.0 |to-3-0] - [From 2.3 to 2.4 |to-2-4] diff --git a/migrations/en/@meta.texy b/migrations/en/@meta.texy new file mode 100644 index 0000000000..42471908b0 --- /dev/null +++ b/migrations/en/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Documentation}} diff --git a/migrations/en/to-2-1.texy b/migrations/en/to-2-1.texy index c32e704d3f..b37f66ce19 100644 --- a/migrations/en/to-2-1.texy +++ b/migrations/en/to-2-1.texy @@ -2,7 +2,7 @@ Migrating to Version 2.1 ************************ .[perex] -There are [#new features] and some [incompatibilities|#Backward Incompatible Changes] that should be considered, and code should be tested before switching to new Nette Framework version. +There are [#new features] and some [incompatibilities |#Dependency Injection DI] that should be considered, and code should be tested before switching to new Nette Framework version. New Features diff --git a/migrations/en/to-2-2.texy b/migrations/en/to-2-2.texy index f1be604983..dc57660ae7 100644 --- a/migrations/en/to-2-2.texy +++ b/migrations/en/to-2-2.texy @@ -26,8 +26,7 @@ The main repository has been physically split into the following repositories: "Tracy":[https://github.com/nette/tracy] and "Utils":[https://github.com/nette/utils]. Each repository represents one component and has its own history, releases, issues, pull-requests, and, of course, `composer.json`. -You can still download the distribution package or install the whole framework using [best-practices:Composer] by requiring `nette/nette` in your `composer.json`. -In addition, you can use each of the components on its own, without installing the rest of the framework. +You can still download the distribution package or install the whole framework using [best-practices:Composer] by requiring `nette/nette` in your `composer.json`. In addition, you can use each of the components on its own, without installing the rest of the framework. Latte diff --git a/migrations/en/to-2-3.texy b/migrations/en/to-2-3.texy index 0abe9dcacd..ede630debb 100644 --- a/migrations/en/to-2-3.texy +++ b/migrations/en/to-2-3.texy @@ -1,14 +1,12 @@ Migrating to Version 2.3 ************************ -Nette version 2.3 means that you have these packages installed in version 2.3.*. Other packages may have higher or lower version numbers, compatibility is ensured by Composer. +Nette 2.3 means that you have these packages installed in version 2.3.*: ```json "require": { "nette/application": "2.3.*", "nette/bootstrap": "2.3.*", - "nette/caching": "2.3.*", - "nette/database": "2.3.*", "nette/di": "2.3.*", "nette/forms": "2.3.*", "nette/http": "2.3.*", diff --git a/migrations/en/to-2-4.texy b/migrations/en/to-2-4.texy index 40945dd76e..0f7a9257a7 100644 --- a/migrations/en/to-2-4.texy +++ b/migrations/en/to-2-4.texy @@ -3,14 +3,12 @@ Migrating to Version 2.4 Minimum required PHP version is 5.6 (for Latte and Tracy 5.4). -Nette version 2.4 means that you have these packages installed in version 2.4.* (or 2.5.*). Other packages may have higher or lower version numbers, compatibility is ensured by Composer. +Nette version 2.4 means that you have these packages installed in version 2.4.*: ```json "require": { "nette/application": "2.4.*", "nette/bootstrap": "2.4.*", - "nette/caching": "2.5.*", - "nette/database": "2.4.*", "nette/di": "2.4.*", "nette/forms": "2.4.*", "nette/http": "2.4.*", @@ -18,6 +16,8 @@ Nette version 2.4 means that you have these packages installed in version 2.4.* }, ``` +Warning: for other packages, install the latest version that Composer will allow! They may have different versioning. Try `"*"` and see what version Composer installs. + Deprecated Stuff ================ diff --git a/migrations/en/to-3-0.texy b/migrations/en/to-3-0.texy index ebec235b37..9aff6d785e 100644 --- a/migrations/en/to-3-0.texy +++ b/migrations/en/to-3-0.texy @@ -3,14 +3,12 @@ Migrating to Version 3.0 Minimum required PHP version is 7.1. -Nette version 3.0 means that you have these packages installed in version 3.0.*. Other packages may have higher or lower version numbers, compatibility is ensured by Composer. +Nette 3.0 means that you have these packages installed in version 3.0.*: ```json "require": { "nette/application": "3.0.*", "nette/bootstrap": "3.0.*", - "nette/caching": "3.0.*", - "nette/database": "3.0.*", "nette/di": "3.0.*", "nette/forms": "3.0.*", "nette/http": "3.0.*", @@ -18,6 +16,8 @@ Nette version 3.0 means that you have these packages installed in version 3.0.*. }, ``` +Warning: for other packages, install the latest version that Composer will allow! They may have different versioning. Try `"*"` and see what version Composer installs. + PHP 7.1 Type Hints ------------------ @@ -57,11 +57,9 @@ Presenters & Components Constructor of `Nette\ComponentModel\Component` has not been used for years and was removed in version 3.0. It's a BC break. If you call parent constructor in your component or presenter inheriting from `Nette\Application\UI\Presenter`, you must remove it. -Interface `Nette\Application\IRouter` has been changed, see "old":https://github.com/nette/application/blob/v2.4.0/src/Application/IRouter.php and "new":https://github.com/nette/routing/blob/v3.0.0/src/Routing/Router.php. -Now method `match()` returns and `constructUrl()` accepts array of parameters instead of object `Nette\Application\Request`. +Interface `Nette\Application\IRouter` has been changed, see "old":https://github.com/nette/application/blob/v2.4.0/src/Application/IRouter.php and "new":https://github.com/nette/routing/blob/v3.0.0/src/Routing/Router.php. Now method `match()` returns and `constructUrl()` accepts array of parameters instead of object `Nette\Application\Request`. -Nette now checks if each signal is sent from the same origin (ie. from the same domain and subdomain). The same-origin policy is a critical security mechanism that helps reduce possible attack vectors. -If you want to allow another origins, add the annotation `@crossOrigin` to the handle method: +Nette now checks if each signal is sent from the same origin (ie. from the same domain and subdomain). The same-origin policy is a critical security mechanism that helps reduce possible attack vectors. If you want to allow another origins, add the annotation `@crossOrigin` to the handle method: ```php /** diff --git a/migrations/en/to-3-1.texy b/migrations/en/to-3-1.texy index c0e53998ee..f651b897da 100644 --- a/migrations/en/to-3-1.texy +++ b/migrations/en/to-3-1.texy @@ -3,22 +3,22 @@ Migrating to Version 3.1 Minimum required PHP version is 7.2. -All name changes mentioned in this document mean that the original name obviously still exists and works, it is just marked as deprecated. You may encounter the IDE visually marking them as deprecated. - -Nette version 3.1 means that you have these packages installed in version 3.1.*. Other packages may have higher or lower version numbers, compatibility is ensured by Composer. +Nette version 3.1 means that you have these packages installed in version 3.1.*: ```json "require": { "nette/application": "3.1.*", "nette/bootstrap": "3.1.*", - "nette/caching": "3.1.*", - "nette/database": "3.1.*", "nette/forms": "3.1.*", "nette/http": "3.1.*", "nette/security": "3.1.*", }, ``` +Warning: for other packages, install the latest version that Composer will allow! They may have different versioning. Try `"*"` and see what version Composer installs. + +All the name changes mentioned in this document mean that the original names still exist and work, of course, they are just marked as deprecated. You may encounter the IDE visually marking them as deprecated. + Interface Names --------------- @@ -65,7 +65,7 @@ Http - `Nette\Http\Request::getFile()` accepts array of keys and returns FileUpload|null - `Nette\Http\Session::getCookieParameters()` is deprecated - `Nette\Http\FileUpload::getName()` renamed to `getUntrustedName()` -- `Nette\Http\Url`: deprecated getBasePath(), getBaseUrl(), getRelativeUrl() (tyto metody jsou součástí `UrlScript`) +- `Nette\Http\Url`: deprecated `getBasePath()`, `getBaseUrl()`, `getRelativeUrl()` (these methods are part of `UrlScript`) - `Nette\Http\Response::$cookieHttpOnly` is deprecated - `Nette\Http\FileUpload::getImageSize()` returns pair `[width, height]` diff --git a/migrations/en/to-3-2.texy b/migrations/en/to-3-2.texy new file mode 100644 index 0000000000..2e33147c1e --- /dev/null +++ b/migrations/en/to-3-2.texy @@ -0,0 +1,28 @@ +Migrating to Version 3.2 +************************ + +Minimum required PHP version is 8.1. + +Nette version 3.2 means that you have these packages installed in version 3.2.*: + +```json +"require": { + "nette/application": "3.2.*", + "nette/bootstrap": "3.2.*", + "nette/forms": "3.2.*", + "nette/http": "3.3.*", + "nette/security": "3.2.*", +}, +``` + +Warning: for other packages, install the latest version that Composer will allow! They may have different versioning. Try `"*"` and see what version Composer installs. + + +News +---- + +This version takes full advantage of all the features of PHP 8. So the code contains all the new typehints introduced in PHP 8, types for properties, new language features and native functions. I have tried to maintain backward compatibility as much as possible and where adding a type might break something, I have not added it yet. + +Furthermore, classes have, with a few exceptions, no longer use the Nette\SmartObject trait, as its enhancements after many years have made their way into PHP itself and are no longer needed. + +Finder has moved to the package `nette/utils`, remove it from composer. diff --git a/neon/bg/@home.texy b/neon/bg/@home.texy index 7825a8cde7..c25a22d34b 100644 --- a/neon/bg/@home.texy +++ b/neon/bg/@home.texy @@ -1,54 +1,54 @@ -Функции на NEON -*************** +Nette NEON +********** <div class=perex> -NEON е лесен за използване език за сериализация на данни. Той се използва в Nette за конфигурационни файлове. [api:Nette\Neon\Neon] - е статичен клас за работа с NEON. +NEON е разбираем за човека език за сериализация на данни. Използва се в Nette за конфигурационни файлове. [api:Nette\Neon\Neon] е статичен клас за работа с NEON. -Запознайте се с [формата NEON |format] и [го из |https://ne-on.org] пробвайте. +Запознайте се с [формата NEON |format] и [изпробвайте го |https://ne-on.org]. </div> -Следващите примери използват тези псевдоними: +Всички примери предполагат създаден псевдоним: ```php use Nette\Neon\Neon; ``` -Настройка .[#toc-installation] ------------------------------- +Инсталация +---------- -Изтеглете и инсталирайте пакета с помощта на [Composer |best-practices:composer]: +Можете да изтеглите и инсталирате библиотеката с помощта на инструмента [Composer |best-practices:composer]: ```shell composer require nette/neon ``` -Можете да проверите за синтактични грешки във файловете `*.neon`, като използвате конзолната команда `neon-lint`: +Можете да проверите за синтактични грешки във файловете `*.neon` с помощта на конзолната команда `neon-lint`: ```shell vendor/bin/neon-lint <path> ``` -encode(mixed $value, bool $blockMode=false): string .[method] -------------------------------------------------------------- +encode(mixed $value, bool $blockMode=false, string $indentation="\t"): string .[method] +--------------------------------------------------------------------------------------- -Връща `$value`, преобразуван в NEON. Параметърът `$blockMode` може да бъде подаден като true, което ще създаде многоредов изход. Параметърът `$indentation` задава символите, използвани за отстъпите (по подразбиране е tab). +Връща `$value`, преобразувано в NEON. Като параметър `$blockMode` можете да предадете true, което ще създаде многоредов изход. Параметърът `$indentation` определя знаците, използвани за индентация (по подразбиране е табулатор). ```php -Neon::encode($value); // Връща стойността на $, конвертирана в NEON -Neon::encode($value, true); // Връща стойността на $, конвертирана в многоредов NEON +Neon::encode($value); // Връща $value, преобразувано в NEON +Neon::encode($value, true); // Връща $value, преобразувано в многоредов NEON ``` -Методът `encode()` ще хвърли `Nette\Neon\Exception` при грешка. +Методът `encode()` при грешка хвърля `Nette\Neon\Exception`. ```php try { $neon = Neon::encode($value); } catch (Nette\Neon\Exception $e) { - // Обработка на изключения + // обработка на изключението } ``` @@ -56,21 +56,21 @@ try { decode(string $neon): mixed .[method] ------------------------------------- -Преобразува зададения NEON в стойност на PHP. +Преобразува низ от NEON в PHP. -Връща скалари, масиви, [дата |format#Dates] като обекти DateTimeImmutable и [същности |format#Entities] като обекти [api:Nette\Neon\Entity]. +Връща скалари, масиви, [данни |format#Дата] като обекти DateTimeImmutable и [същности |format#Ентитети] като обекти [api:Nette\Neon\Entity]. ```php Neon::decode('hello: world'); // Връща масив ['hello' => 'world'] ``` -Методът `decode()` хвърля `Nette\Neon\Exception` при грешка. +Методът `decode()` при грешка хвърля `Nette\Neon\Exception`. ```php try { $value = Neon::decode($neon); } catch (Nette\Neon\Exception $e) { - // Обработка на изключения + // обработка на изключението } ``` @@ -78,13 +78,10 @@ try { decodeFile(string $file): mixed .[method] ----------------------------------------- -Конвертира съдържанието на файла от NEON в PHP и премахва всички BOM. +Преобразува съдържанието на файл от NEON в PHP и премахва евентуален BOM. ```php Neon::decodeFile('config.neon'); ``` -Методът `decodeFile()` хвърля `Nette\Neon\Exception` при грешка . - - -{{leftbar: utils:@left-menu}} +Методът `decodeFile()` при грешка хвърля `Nette\Neon\Exception`. diff --git a/neon/bg/@meta.texy b/neon/bg/@meta.texy new file mode 100644 index 0000000000..104d043cab --- /dev/null +++ b/neon/bg/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Документация на Nette}} +{{leftbar: utils:@left-menu}} diff --git a/neon/bg/format.texy b/neon/bg/format.texy index 64c41d184d..5b31151ace 100644 --- a/neon/bg/format.texy +++ b/neon/bg/format.texy @@ -2,40 +2,40 @@ *********** .[perex] -NEON е формат за структурирани данни, който може да се чете от човек. В Nette той се използва за конфигурационни файлове. Той се използва и за структурирани данни, като настройки, езикови преводи и др. [Изпробвайте го в пясъчника |https://ne-on.org]. +NEON е лесно четим структуриран формат за данни. В Nette се използва за конфигурационни файлове. Използва се и за структурирани данни, като настройки, езикови преводи и т.н. [Опитайте го|https://ne-on.org]. -NEON е съкращение от *Nette Object Notation*. Той не е толкова сложен и тромав като XML или JSON, но предоставя подобни функции. Той е много подобен на YAML. Основното предимство е, че NEON притежава така наречените [единици, |#Entities] които правят конфигурацията DI толкова секси. Тя позволява да използвате табулатори за отстъпление. +NEON е съкращение от *Nette Object Notation*. Той е по-малко сложен и тромав от XML или JSON, но предоставя подобни функции. Много е подобен на YAML. Основното предимство е, че NEON има така наречените [#ентитети], благодарение на които конфигурацията на DI сървисите е [също толкова секси |https://gist.github.com/dg/26baf3ce8f29d0f751e9dddfaa06504f]. И позволява индентация с табулации. -NEON е проектиран от самото начало, за да бъде лесен за използване. +NEON е създаден от основи, за да бъде лесен за използване. -Интеграция .[#toc-integration] -============================== +Интеграция +========== - NetBeans (има вградена поддръжка) -- PhpStorm ([приставка |https://plugins.jetbrains.com/plugin/7060?pr]) -- Visual Studio Code ([приставка |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) -- Sublime Text 3 ([приставка |https://github.com/FilipStryk/Nette-Latte-Neon-for-Sublime-Text-3]) -- Sublime Text 2 ([приставка |https://github.com/Michal-Mikolas/Nette-package-for-Sublime-Text-2]) -- VIM ([приставка |https://github.com/fpob/nette.vim]) -- Emacs ([приставка |https://github.com/Fuco1/neon-mode]) +- PhpStorm ([плъгин |https://plugins.jetbrains.com/plugin/7060?pr]) +- Visual Studio Code ([Nette Latte + Neon |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) или [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode]) +- Sublime Text 3 ([плъгин |https://github.com/FilipStryk/Nette-Latte-Neon-for-Sublime-Text-3]) +- Sublime Text 2 ([плъгин |https://github.com/Michal-Mikolas/Nette-package-for-Sublime-Text-2]) +- VIM ([плъгин |https://github.com/fpob/nette.vim]) +- Emacs ([плъгин |https://github.com/Fuco1/neon-mode]) - Prism.js ([интегриран език |https://prismjs.com/#supported-languages]) -- [NEON за PHP |@home] -- [NEON за JavaScript |https://github.com/matej21/neon-js] -- [NEON за Python |https://github.com/paveldedik/neon-py]. +- [NEON for PHP |@home] +- [NEON for JavaScript |https://github.com/matej21/neon-js] +- [NEON for Python |https://github.com/paveldedik/neon-py]. -Синтаксис .[#toc-syntax] -======================== +Синтаксис +========= -Файлът, написан в NEON, обикновено се състои от последователност или съпоставяне. +Файл, написан в NEON, обикновено представлява масив или асоциативен масив. -Сравнения .[#toc-mappings] --------------------------- -Съпоставянето е набор от двойки ключ-стойност, който в PHP се нарича асоциативен масив. Всяка двойка се записва като `key: value`, като интервалът след `:` е задължителен. Стойността може да бъде всякаква: низ, число, булева, нула, последователност или друго съпоставяне. +Асоциативни масиви +------------------ +Асоциативният масив е набор от двойки ключ-стойност, в PHP би се нарекъл асоциативен масив. Всяка двойка се записва като `key: value`, интервалът след `:` е задължителен. Стойността може да бъде всичко: низ, число, булева стойност, null, последователност или друг асоциативен масив. ```neon street: 742 Evergreen Terrace @@ -43,7 +43,7 @@ city: Springfield country: USA ``` -В PHP същата структура ще бъде записана по следния начин: +В PHP същата структура би се записала като: ```php [ // PHP @@ -53,13 +53,13 @@ country: USA ] ``` -Този запис се нарича блоков запис, защото всички елементи са на отделен ред и са отстъпени по един и същи начин (в този случай няма отстъпление). NEON също така поддържа представяне по редове за показване, което е затворено в скоби, отстъпите не играят роля, а всеки елемент се отделя със запетая или нов ред: +Този запис се нарича блоков, тъй като всички елементи са на отделен ред и имат еднаква индентация (в този случай никаква). NEON поддържа и инлайн представяне на асоциативни масиви, което е затворено в скоби, индентацията не играе никаква роля, а разделител на отделните елементи е запетая или нов ред: ```neon {street: 742 Evergreen Terrace, city: Springfield, country: USA} ``` -Това е едно и също нещо, написано на няколко реда (отстъпът е без значение): +Същото, записано на няколко реда (индентацията няма значение): ```neon { @@ -68,16 +68,16 @@ country: USA } ``` -Вместо това можете да използвате `=` вместо <code>: </code>, както в блоков, така и във вграден запис: +Вместо `: ` може алтернативно да се използва `=` както в блоков, така и в инлайн запис: ```neon {street=742 Evergreen Terrace, city=Springfield, country=USA} ``` -Последователности .[#toc-sequences] ------------------------------------ -Последователностите са индексирани масиви в PHP. Те се записват като низове, започващи с дефис `-`, последван от интервал. Стойността може да бъде всякаква: низ, число, булева, нула, последователност или друго съпоставяне. +Последователности +----------------- +Последователностите в PHP са индексирани масиви. Записват се като редове, започващи с тире `-`, последвано от интервал. Стойността отново може да бъде всичко: низ, число, булева стойност, null, последователност или друг асоциативен масив. ```neon - Cat @@ -85,7 +85,7 @@ country: USA - Goldfish ``` -В PHP същата структура би изглеждала по следния начин: +В PHP същата структура би се записала като: ```php [ // PHP @@ -95,13 +95,13 @@ country: USA ] ``` -Този запис се нарича блоков запис, защото всички елементи са на отделен ред и имат едно и също отстъпление (в този случай няма такова). NEON също така поддържа инлайн представяне на последователности, които са затворени в скоби, отстъпите не играят роля и всеки елемент се отделя със запетая или нов ред: +Този запис се нарича блоков, тъй като всички елементи са на отделен ред и имат еднаква индентация (в този случай никаква). NEON поддържа и инлайн представяне на последователности, което е затворено в скоби, индентацията не играе никаква роля, а разделител на отделните елементи е запетая или нов ред: ```neon [Cat, Dog, Goldfish] ``` -Това е едно и също нещо, написано на няколко реда (отстъпът е без значение): +Същото, записано на няколко реда (индентацията няма значение): ```neon [ @@ -110,23 +110,23 @@ country: USA ] ``` -Не могат да се използват тирета в инлайн представяне. +В инлайн представянето не могат да се използват водещи тирета. -Комбинацията .[#toc-combination] --------------------------------- -Стойностите на съпоставянията и последователностите могат да бъдат други съпоставяния и последователности. Степента на врязване играе важна роля. В следващия пример тирето, използвано за елементите на последователността, е отстъпено повече от ключа `pets`, така че елементите стават стойности на първия ред: +Комбинации +---------- +Стойностите на асоциативните масиви и последователностите могат да бъдат други асоциативни масиви и последователности. Основна роля играе нивото на индентация. В следващия пример тирето, използвано за обозначаване на елементите на последователността, има по-голяма индентация от ключа `pets`, така че елементите стават стойност на първия ред: ```neon pets: - - Cat - - Dog + - Cat + - Dog cars: - - Volvo - - Skoda + - Volvo + - Skoda ``` -В PHP същата структура ще бъде записана по следния начин: +В PHP същата структура би се записала като: ```php [ // PHP @@ -141,7 +141,7 @@ cars: ] ``` -Възможно е да комбинирате блоково и инлайн записване: +Може да се комбинират блоков и инлайн запис: ```neon pets: [Cat, Dog] @@ -151,17 +151,39 @@ cars: [ ] ``` -Блоковата нотация вече не може да се използва в рамките на инлайн нотация, тя не работи: +Вътре в инлайн записа вече не може да се използва блоков запис, това не работи: ```neon item: [ pets: - - Cat # THIS IS NOT POSSIBLE!!! + - Cat # ТОВА НЕ Е ВЪЗМОЖНО!!! - Dog ] ``` -Тъй като PHP използва една и съща структура за съпоставки и последователности, т.е. масиви, двете могат да се комбинират. Този път отстъпите са същите: +В предишния случай записахме асоциативен масив, чиито елементи бяха последователности, сега ще опитаме обратното и ще създадем последователност, съдържаща асоциативни масиви: + +```neon +- + name: John + age: 35 +- + name: Peter + age: 28 +``` + +Не е необходимо тиретата да са на отделни редове, могат да се поставят и по този начин: + +```neon +- name: John + age: 35 +- name: Peter + age: 28 +``` + +От вас зависи дали ще подравните ключовете в колона с помощта на интервали или ще използвате табулация. + +Тъй като в PHP се използва една и съща структура както за асоциативни масиви, така и за последователности, т.е. масив, и двете могат да бъдат обединени. Индентацията този път е същата: ```neon - Cat @@ -169,7 +191,7 @@ street: 742 Evergreen Terrace - Goldfish ``` -На PHP същата структура ще бъде записана като: +В PHP същата структура би се записала като: ```php [ // PHP @@ -180,55 +202,55 @@ street: 742 Evergreen Terrace ``` -Редове .[#toc-strings] ----------------------- -Низовете в NEON могат да бъдат оградени с единични или двойни кавички. Но, както виждате, те могат да бъдат и без кавички. +Низове +------ +Низовете в NEON могат да бъдат затворени в единични или двойни кавички. Но както виждате, те могат да бъдат и без кавички. ```neon -- A unquoted string in NEON -- 'A singled-quoted string in NEON' -- "A double-quoted string in NEON" +- Низ в NEON без кавички +- 'Низ в NEON в единични кавички' +- "Низ в NEON в двойни кавички" ``` -Ако даден низ съдържа символи `# " ' , : = - [ ] { } ( )` които могат да бъдат объркани със синтаксиса на NEON, трябва да бъдат поставени в кавички. Препоръчваме ви да използвате единични кавички, тъй като при тях не се използва ескапиране. Ако трябва да поставите кавички в такъв низ, удвоете ги: +Ако низът съдържа знаците `# " ' , : = - [ ] { } ( )`, които могат да бъдат объркани със синтаксиса на NEON, е необходимо да го затворите в кавички. Препоръчваме да използвате единични кавички, тъй като в тях не се използва екраниране. Ако трябва да запишете кавичка в такъв низ, удвоете я: ```neon -'A single quote '' inside a single-quoted string' +'Кавичка '' вътре в низ в единични кавички' ``` -Двойните кавички ви позволяват да използвате escape последователности за записване на специални символи с помощта на обратни наклонени черти `\`. All escape sequences as in the JSON format are supported, plus `\_`, които са неразбиваеми интервали, т.е. `\u00A0`. +Двойните кавички позволяват използването на escape последователности за запис на специални знаци с помощта на обратни наклонени черти `\`. Поддържат се всички escape последователности като при формата JSON, плюс `\_`, което е неразделящ интервал, т.е. `\u00A0`. ```neon - "\t \n \r \f \b \" \\ \/ \_" - "\u00A9" ``` -Съществуват и други случаи, в които е необходимо низовете да се поставят в кавички: +Съществуват и други случаи, когато е необходимо низовете да се затворят в кавички: - започват или завършват с интервали -- изглеждат като числа, булеви стойности или null. -- NEON ще ги разбира като [дати |#Dates] +- изглеждат като числа, булеви стойности или null +- NEON би ги разбрал като [#дата] -Многоредови низове .[#toc-multiline-strings] --------------------------------------------- +Многоредови низове +------------------ -Многоредов низ започва и завършва с тройна кавичка на отделни редове. Отстъпът на първия ред се пренебрегва за всички редове: +Многоредовият низ започва и завършва с тройни кавички на отделни редове. Индентацията на първия ред се игнорира за всички редове: ```neon ''' - first line - second line - third line + първи ред + втори ред + трети ред ''' ``` -На PHP бихме написали същото: +В PHP бихме написали същото като: ```php -"first line\n\tsecond line\nthird line" // PHP +"първи ред\n\tвтори ред\nтрети ред" // PHP ``` -Ескейп последователностите работят само за низове, затворени в двойни кавички вместо в апострофи: +Escape последователностите работят само при низове, затворени в двойни кавички вместо апострофи: ```neon """ @@ -237,24 +259,24 @@ street: 742 Evergreen Terrace ``` -Числа .[#toc-numbers] ---------------------- -NEON разбира числата, записани в т.нар. научен запис, както и числата в двоичен, осмичен и шестнадесетичен запис: +Числа +----- +NEON разбира числа, записани в т.нар. научна нотация, както и числа в двоична, осмична и шестнадесетична бройна система: ```neon -- 12 # цяло число -- 12.3 # плаващо число -- +1.2e-34 # експоненциално число +- 12 # цяло число +- 12.3 # float +- +1.2e-34 # експоненциално число -- 0b11010 # двоично число -- 0o666 # осмично число -- 0x7A # шестнадесетично число +- 0b11010 # двоично число +- 0o666 # осмично число +- 0x7A # шестнадесетично число ``` -Нули .[#toc-nulls] ------------------- -Нулите могат да бъдат изразени в NEON със или без `null`. Позволени са и главни букви или всички главни букви. +Null стойности +-------------- +Null може да се изрази в NEON с помощта на `null` или чрез пропускане на стойността. Позволени са и варианти с главна първа буква или всички главни букви. ```neon a: null @@ -262,50 +284,50 @@ b: ``` -Булеви .[#toc-booleans] ------------------------ -Булевите стойности се изразяват в NEON с помощта на `true` / `false` или `yes` / `no`. Позволени са също така главни букви или изцяло главни букви. +Булеви стойности +---------------- +Логическите стойности се изразяват в NEON с помощта на `true` / `false` или `yes` / `no`. Позволени са и варианти с главна първа буква или всички главни букви. ```neon [true, TRUE, True, false, yes, no] ``` -Дати .[#toc-dates] ------------------- -NEON използва следните формати за изразяване на данни и автоматично ги преобразува в обекти `DateTimeImmutable`: +Дата +---- +NEON използва следните формати за изразяване на дати и автоматично ги преобразува в обекти `DateTimeImmutable`: ```neon -- 2016-06-03 # дата -- 2016-06-03 19:00:00 # дата и час -- 2016-06-03 19:00:00.1234 # дата и микровреме -- 2016-06-03 19:00:00 +0200 # дата и час и часова зона -- 2016-06-03 19:00:00 +02:00 # дата и час и часова зона +- 2016-06-03 # дата +- 2016-06-03 19:00:00 # дата и час +- 2016-06-03 19:00:00.1234 # дата и микросекунди +- 2016-06-03 19:00:00 +0200 # дата и час и зона +- 2016-06-03 19:00:00 +02:00 # дата и час и зона ``` -Субекти .[#toc-entities] ------------------------- -Същност е структура, наподобяваща извикване на функция: +Ентитети +-------- +Ентитетът е структура, която прилича на извикване на функция: ```neon Column(type: int, nulls: yes) ``` -В PHP той се анализира като обект [api:Nette\Neon\Entity]: +В PHP се парсира като обект [api:Nette\Neon\Entity]: ```php // PHP new Nette\Neon\Entity('Column', ['type' => 'int', 'nulls' => true]) ``` -Субектите могат да се свързват и верижно: +Ентитетите могат и да се верижат: ```neon Column(type: int, nulls: yes) Field(id: 1) ``` -Това се анализира в PHP по следния начин: +Което в PHP се парсира по следния начин: ```php // PHP @@ -315,7 +337,7 @@ new Nette\Neon\Entity(Nette\Neon\Neon::Chain, [ ]) ``` -Вътре в скобите се прилагат правилата за бележки в ред, използвани за картографиране и последователности, така че те могат да бъдат разделени на няколко реда и не е необходимо да се добавят запетаи: +Вътре в скобите важат правилата за инлайн запис, използван при асоциативни масиви и последователности, т.е. може да бъде и многоредов и тогава не е необходимо да се поставят запетаи: ```neon Column( @@ -325,21 +347,21 @@ Column( ``` -Коментар .[#toc-comments] -------------------------- -Коментарите започват с `#` и всички следващи символи надясно се игнорират: +Коментари +--------- +Коментарите започват със знака `#` и всички следващи знаци надясно се игнорират: ```neon # този ред ще бъде игнориран от интерпретатора -улица: 742 Evergreen Terrace -град: Спрингфийлд # този текст също се игнорира -държава: САЩ +street: 742 Evergreen Terrace +city: Springfield # това също се игнорира +country: USA ``` -NEON срещу JSON .[#toc-neon-versus-json] -======================================== -JSON е подмножество на NEON. Затова всеки JSON може да бъде анализиран като NEON: +Neon срещу JSON +=============== +JSON е подмножество на NEON. Следователно всеки JSON може да бъде парсиран като NEON: ```neon { @@ -358,7 +380,7 @@ JSON е подмножество на NEON. Затова всеки JSON мож } ``` -Ами ако можем да пропуснем кавичките? +Ами ако пропуснем кавичките? ```neon { @@ -377,7 +399,7 @@ users: [ } ``` -Какво ще кажете за скобите и запетайките? +А къдравите скоби и запетаите? ```neon php: @@ -394,7 +416,7 @@ users: [ ] ``` -По-четливи ли са куршумите? +Не са ли списъците с тирета по-четими? ```neon php: @@ -412,14 +434,14 @@ users: - Rimmer ``` -Какво ще кажете за коментарите? +Да добавим коментари? ```neon -# my web application config +# конфигурация на моето уеб приложение php: date.timezone: Europe/Prague - zlib.output_compression: true # use gzip + zlib.output_compression: true # използвай gzip database: driver: mysql @@ -432,8 +454,7 @@ users: - Rimmer ``` -Намерили сте синтаксиса NEON! +Ура, вече познавате синтаксиса на NEON! -{{description: NEON е удобен за човека език за сериализация на данни. Той е подобен на YAML. Основната разлика е, че NEON поддържа "същности" и знаци табулация за отстъпление.}} -{{leftbar: utils:@left-menu}} +{{description: NEON е лесно четим формат за сериализация на данни. Подобен е на YAML. Основната разлика е, че NEON поддържа "ентитети" и за индентация можем да използваме както интервали, така и табулации.}} diff --git a/neon/cs/@home.texy b/neon/cs/@home.texy index 2f04820c6a..5375b2e479 100644 --- a/neon/cs/@home.texy +++ b/neon/cs/@home.texy @@ -1,5 +1,5 @@ -Práce s NEON -************ +Nette NEON +********** <div class=perex> @@ -32,8 +32,8 @@ vendor/bin/neon-lint <cesta> ``` -encode(mixed $value, bool $blockMode=false): string .[method] -------------------------------------------------------------- +encode(mixed $value, bool $blockMode=false, string $indentation="\t"): string .[method] +--------------------------------------------------------------------------------------- Vrací `$value` převedenou na NEON. Jako parametr `$blockMode` můžete předat true, čímž se vytvoří víceřádkový výstup. Parametr `$indentation` určuje znaky použité pro odsazení (výchozí je tabulátor). @@ -58,7 +58,7 @@ decode(string $neon): mixed .[method] Převede řetězec z NEONu do PHP. -Vrací skaláry, pole, [data|format#datum] jako objekty DateTimeImmutable a [entity|format#entity] jako objekty [api:Nette\Neon\Entity]. +Vrací skaláry, pole, [data |format#Datum] jako objekty DateTimeImmutable a [entity |format#Entity] jako objekty [api:Nette\Neon\Entity]. ```php Neon::decode('hello: world'); // Vrátí pole ['hello' => 'world'] @@ -85,6 +85,3 @@ Neon::decodeFile('config.neon'); ``` Metoda `decodeFile()` při chybě vyhodí `Nette\Neon\Exception`. - - -{{leftbar: utils:@left-menu}} diff --git a/neon/cs/@meta.texy b/neon/cs/@meta.texy new file mode 100644 index 0000000000..b0a5494fc2 --- /dev/null +++ b/neon/cs/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Dokumentace}} +{{leftbar: utils:@left-menu}} diff --git a/neon/cs/format.texy b/neon/cs/format.texy index 1fcd560d72..1f7f06bfcf 100644 --- a/neon/cs/format.texy +++ b/neon/cs/format.texy @@ -4,7 +4,7 @@ Formát NEON .[perex] NEON je v lidsky čitelný strukturovaný datový formát. V Nette se používá pro konfigurační soubory. Také se používá pro strukturovaná data, jako jsou nastavení, jazykové překlady atd. [Vyzkoušejte si jej|https://ne-on.org]. -NEON je zkratka pro *Nette Object Notation*. Je méně složitý a nemotorný než XML nebo JSON, ale poskytuje podobné funkce. Je velmi podobný YAML. Hlavní přednost je tom, že NEON má takzvané [#entity], díky kterým je konfigurace DI služeb taky sexy. A umožňuje odsazovat tabulátory. +NEON je zkratka pro *Nette Object Notation*. Je méně složitý a nemotorný než XML nebo JSON, ale poskytuje podobné funkce. Je velmi podobný YAML. Hlavní přednost je tom, že NEON má takzvané [#entity], díky kterým je konfigurace DI služeb [taky sexy |https://gist.github.com/dg/26baf3ce8f29d0f751e9dddfaa06504f]. A umožňuje odsazovat tabulátory. NEON je postaven od základů tak, aby byl snadno použitelný. @@ -14,7 +14,7 @@ Integrace - NetBeans (má vestavěnou podporu) - PhpStorm ([plugin |https://plugins.jetbrains.com/plugin/7060?pr]) -- Visual Studio Code ([plugin |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) +- Visual Studio Code ([Nette Latte + Neon |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) nebo [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode]) - Sublime Text 3 ([plugin |https://github.com/FilipStryk/Nette-Latte-Neon-for-Sublime-Text-3]) - Sublime Text 2 ([plugin |https://github.com/Michal-Mikolas/Nette-package-for-Sublime-Text-2]) - VIM ([plugin |https://github.com/fpob/nette.vim]) @@ -119,11 +119,11 @@ Hodnotami mapování a sekvencí mohou být jiné mapování a sekvence. Hlavní ```neon pets: - - Cat - - Dog + - Cat + - Dog cars: - - Volvo - - Skoda + - Volvo + - Skoda ``` V PHP by se stejná struktura zapsala jako: @@ -161,6 +161,28 @@ item: [ ] ``` +V předchozím případě jsme zapsali mapovaní, jehož prvky byly sekvence, teď to zkusíme obráceně a vytvoříme sekvenci obsahující mapování: + +```neon +- + name: John + age: 35 +- + name: Peter + age: 28 +``` + +Není nutné, aby odrážky byly na samostatných řádcích, lze je umístit i tímto způsobem: + +```neon +- name: John + age: 35 +- name: Peter + age: 28 +``` + +Je na vás, jestli klíče zarovnáte do sloupce pomocí mezer nebo použijete tabulátor. + Protože v PHP se používá pro mapování i sekvence stejná struktura, tedy pole, lze obojí sloučit. Odsazení je tentokrát stejné: ```neon @@ -436,4 +458,3 @@ Hurá, teď znáte syntaxi NEONu! {{description: NEON je snadno čitelný formát pro serializaci dat. Je podobný YAMLu. Hlavní rozdíl je v tom, že NEON podporuje „entity“ a k odsazování můžeme použít jak mezery, tak tabulátory.}} -{{leftbar: utils:@left-menu}} diff --git a/neon/de/@home.texy b/neon/de/@home.texy index 8ff52bc8dd..158c387de2 100644 --- a/neon/de/@home.texy +++ b/neon/de/@home.texy @@ -1,54 +1,54 @@ -NEON-Funktionen -*************** +Nette NEON +********** <div class=perex> -NEON ist eine menschenfreundliche Sprache zur Serialisierung von Daten. Sie wird in Nette für Konfigurationsdateien verwendet. [api:Nette\Neon\Neon] ist eine statische Klasse für die Arbeit mit NEON. +NEON ist eine für Menschen verständliche Sprache zur Serialisierung von Daten. Sie wird in Nette für Konfigurationsdateien verwendet. [api:Nette\Neon\Neon] ist eine statische Klasse für die Arbeit mit NEON. -Lernen Sie das [NEON-Format |format] kennen und [probieren Sie es aus |https://ne-on.org]. +Machen Sie sich mit dem [NEON-Format|format] vertraut und [probieren Sie es aus |https://ne-on.org]. </div> -Die folgenden Beispiele verwenden diese Aliasnamen: +Alle Beispiele setzen voraus, dass ein Alias erstellt wurde: ```php use Nette\Neon\Neon; ``` -Installation .[#toc-installation] ---------------------------------- +Installation +------------ -Laden Sie das Paket herunter und installieren Sie es mit [Composer |best-practices:composer]: +Sie können die Bibliothek mit dem Werkzeug [Composer|best-practices:composer] herunterladen und installieren: ```shell composer require nette/neon ``` -Mit dem Konsolenbefehl `neon-lint` können Sie die Dateien von `*.neon` auf Syntaxfehler überprüfen: +Syntaxfehler in `*.neon`-Dateien können mit dem Konsolenbefehl `neon-lint` überprüft werden: ```shell -vendor/bin/neon-lint <path> +vendor/bin/neon-lint <Pfad> ``` -encode(mixed $value, bool $blockMode=false): string .[method] -------------------------------------------------------------- +encode(mixed $value, bool $blockMode=false, string $indentation="\t"): string .[method] +--------------------------------------------------------------------------------------- -Gibt `$value` in NEON konvertiert zurück. Als Parameter `$blockMode` können Sie true übergeben, was eine mehrzeilige Ausgabe erzeugt. Der Parameter `$indentation` gibt die für die Einrückung verwendeten Zeichen an (Standard ist tab). +Gibt `$value` konvertiert in NEON zurück. Als Parameter `$blockMode` können Sie true übergeben, wodurch eine mehrzeilige Ausgabe erstellt wird. Der Parameter `$indentation` gibt die für die Einrückung verwendeten Zeichen an (Standard ist Tabulator). ```php -Neon::encode($value); // Liefert $value umgewandelt in NEON -Neon::encode($value, true); // Gibt $value umgewandelt in mehrzeiliges NEON zurück +Neon::encode($value); // Gibt $value konvertiert in NEON zurück +Neon::encode($value, true); // Gibt $value konvertiert in mehrzeiliges NEON zurück ``` -Die Methode `encode()` löst im Fehlerfall `Nette\Neon\Exception` aus. +Die Methode `encode()` löst bei einem Fehler eine `Nette\Neon\Exception` aus. ```php try { $neon = Neon::encode($value); } catch (Nette\Neon\Exception $e) { - // Behandlung von Ausnahmen + // Ausnahmebehandlung } ``` @@ -56,21 +56,21 @@ try { decode(string $neon): mixed .[method] ------------------------------------- -Konvertiert den angegebenen NEON-Wert in einen PHP-Wert. +Konvertiert eine Zeichenkette von NEON nach PHP. -Gibt Skalare, Arrays, [Datum |format#dates] als DateTimeImmutable Objekte und [Entitäten |format#Entities] als [api:Nette\Neon\Entity] Objekte zurück. +Gibt Skalare, Arrays, [Daten |format#Datum] als DateTimeImmutable-Objekte und [Entitäten |format#Entitäten] als [api:Nette\Neon\Entity]-Objekte zurück. ```php -Neon::decode('hello: world'); // Liefert ein Array ['hello' => 'world'] +Neon::decode('hello: world'); // Gibt Array ['hello' => 'world'] zurück ``` -Die Methode `decode()` löst im Fehlerfall `Nette\Neon\Exception` aus. +Die Methode `decode()` löst bei einem Fehler eine `Nette\Neon\Exception` aus. ```php try { $value = Neon::decode($neon); } catch (Nette\Neon\Exception $e) { - // Behandlung von Ausnahmen + // Ausnahmebehandlung } ``` @@ -78,13 +78,10 @@ try { decodeFile(string $file): mixed .[method] ----------------------------------------- -Konvertiert den Inhalt der Datei von NEON nach PHP und entfernt alle BOM. +Konvertiert den Inhalt einer Datei von NEON nach PHP und entfernt einen eventuellen BOM. ```php Neon::decodeFile('config.neon'); ``` -Die Methode `decodeFile()` löst im Fehlerfall `Nette\Neon\Exception` aus. - - -{{leftbar: utils:@left-menu}} +Die Methode `decodeFile()` löst bei einem Fehler eine `Nette\Neon\Exception` aus. diff --git a/neon/de/@meta.texy b/neon/de/@meta.texy new file mode 100644 index 0000000000..b7b54eecc7 --- /dev/null +++ b/neon/de/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Dokumentation}} +{{leftbar: utils:@left-menu}} diff --git a/neon/de/format.texy b/neon/de/format.texy index bfe9b4e9c9..bd78f27538 100644 --- a/neon/de/format.texy +++ b/neon/de/format.texy @@ -1,41 +1,41 @@ -NEON-Format -*********** +Das NEON-Format +*************** .[perex] -NEON ist ein von Menschen lesbares strukturiertes Datenformat. In Nette wird es für Konfigurationsdateien verwendet. Es wird auch für strukturierte Daten wie Einstellungen, Sprachübersetzungen usw. verwendet. [Probieren Sie es in der Sandbox |https://ne-on.org] aus. +NEON ist ein menschenlesbares strukturiertes Datenformat. In Nette wird es für Konfigurationsdateien verwendet. Es wird auch für strukturierte Daten wie Einstellungen, Sprachübersetzungen usw. verwendet. [Probieren Sie es aus|https://ne-on.org]. -NEON steht für *Nette Object Notation*. Es ist weniger komplex und unhandlich als XML oder JSON, bietet aber ähnliche Möglichkeiten. Sie ist YAML sehr ähnlich. Der Hauptvorteil ist, dass NEON sogenannte [Entitäten |#entities] hat, dank derer die Konfiguration von DI-Diensten so sexy ist. Und es erlaubt Tabs für die Einrückung. +NEON ist die Abkürzung für *Nette Object Notation*. Es ist weniger komplex und sperrig als XML oder JSON, bietet aber ähnliche Funktionen. Es ist YAML sehr ähnlich. Der Hauptvorteil ist, dass NEON sogenannte [#Entitäten] hat, dank derer die Konfiguration von DI-Diensten [auch sexy ist |https://gist.github.com/dg/26baf3ce8f29d0f751e9dddfaa06504f]. Und es erlaubt die Einrückung mit Tabulatoren. -NEON ist von Grund auf so aufgebaut, dass es einfach zu benutzen ist. +NEON wurde von Grund auf so konzipiert, dass es einfach zu verwenden ist. -Einbindung .[#toc-integration] -============================== +Integration +=========== -- NetBeans (hat integrierte Unterstützung) +- NetBeans (hat eingebaute Unterstützung) - PhpStorm ([Plugin |https://plugins.jetbrains.com/plugin/7060?pr]) -- Visual Studio Code ([Erweiterungsmodul |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) -- Sublime Text 3 ([Erweiterungsmodul |https://github.com/FilipStryk/Nette-Latte-Neon-for-Sublime-Text-3]) -- Sublime Text 2 ([Erweiterungsmodul |https://github.com/Michal-Mikolas/Nette-package-for-Sublime-Text-2]) -- VIM ([Zusatzmodul |https://github.com/fpob/nette.vim]) -- Emacs ([Zusatz programm|https://github.com/Fuco1/neon-mode]) +- Visual Studio Code ([Nette Latte + Neon |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) oder [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode]) +- Sublime Text 3 ([Plugin |https://github.com/FilipStryk/Nette-Latte-Neon-for-Sublime-Text-3]) +- Sublime Text 2 ([Plugin |https://github.com/Michal-Mikolas/Nette-package-for-Sublime-Text-2]) +- VIM ([Plugin |https://github.com/fpob/nette.vim]) +- Emacs ([Plugin |https://github.com/Fuco1/neon-mode]) - Prism.js ([integrierte Sprache |https://prismjs.com/#supported-languages]) -- [NEON für PHP |@home] -- [NEON für JavaScript |https://github.com/matej21/neon-js] -- [NEON für Python |https://github.com/paveldedik/neon-py]. +- [NEON for PHP |@home] +- [NEON for JavaScript |https://github.com/matej21/neon-js] +- [NEON for Python |https://github.com/paveldedik/neon-py]. -Syntax .[#toc-syntax] -===================== +Syntax +====== -Eine in NEON geschriebene Datei besteht normalerweise aus einer Sequenz oder einem Mapping. +Eine in NEON geschriebene Datei stellt normalerweise ein Array oder eine Zuordnung dar. -Mappings .[#toc-mappings] -------------------------- -Mapping ist eine Menge von Schlüssel-Wert-Paaren, in PHP würde man es ein assoziatives Array nennen. Jedes Paar wird als `key: value` geschrieben, ein Leerzeichen nach `:` ist erforderlich. Der Wert kann alles sein: String, Zahl, Boolean, Null, Sequenz oder ein anderes Mapping. +Zuordnung (Mapping) +------------------- +Eine Zuordnung ist eine Menge von Schlüssel-Wert-Paaren, in PHP würde man sagen assoziatives Array. Jedes Paar wird als `key: value` geschrieben, ein Leerzeichen nach `:` ist erforderlich. Der Wert kann alles sein: Zeichenkette, Zahl, Boolean, Null, Sequenz oder eine andere Zuordnung. ```neon street: 742 Evergreen Terrace @@ -43,7 +43,7 @@ city: Springfield country: USA ``` -In PHP würde die gleiche Struktur wie folgt geschrieben werden: +In PHP würde dieselbe Struktur wie folgt geschrieben werden: ```php [ // PHP @@ -53,13 +53,13 @@ In PHP würde die gleiche Struktur wie folgt geschrieben werden: ] ``` -Diese Notation wird als Blocknotation bezeichnet, da alle Elemente in einer separaten Zeile stehen und die gleiche Einrückung haben (in diesem Fall keine). NEON unterstützt auch die Inline-Darstellung für das Mapping, die in Klammern eingeschlossen ist, wobei die Einrückung keine Rolle spielt und das Trennzeichen jedes Elements entweder ein Komma oder ein Zeilenumbruch ist: +Diese Schreibweise wird als Block-Schreibweise bezeichnet, da alle Elemente auf einer separaten Zeile stehen und dieselbe Einrückung haben (in diesem Fall keine). NEON unterstützt auch eine Inline-Darstellung für Zuordnungen, die in Klammern `{}` eingeschlossen ist, die Einrückung spielt keine Rolle und das Trennzeichen für einzelne Elemente ist entweder ein Komma oder ein Zeilenumbruch: ```neon {street: 742 Evergreen Terrace, city: Springfield, country: USA} ``` -Dies ist das Gleiche in mehreren Zeilen geschrieben (Einrückung spielt keine Rolle): +Dasselbe auf mehrere Zeilen verteilt (Einrückung spielt keine Rolle): ```neon { @@ -68,16 +68,16 @@ Dies ist das Gleiche in mehreren Zeilen geschrieben (Einrückung spielt keine Ro } ``` -Alternativ kann auch `=" anstelle von verwendet werden <code>: </code> sowohl in Block- als auch in Inline-Schreibweise verwendet werden: +Anstelle von <code>: </code> kann alternativ `=` verwendet werden, sowohl in der Block- als auch in der Inline-Schreibweise: ```neon {street=742 Evergreen Terrace, city=Springfield, country=USA} ``` -Sequenzen .[#toc-sequences] ---------------------------- -Sequenzen sind indizierte Arrays in PHP. Sie werden als Zeilen geschrieben, die mit dem Bindestrich `-` beginnen, gefolgt von einem Leerzeichen. Auch hier kann der Wert alles sein: String, Zahl, Boolean, Null, Sequenz oder eine andere Zuordnung. +Sequenz +------- +Sequenzen sind in PHP indizierte Arrays. Sie werden als Zeilen geschrieben, die mit einem Bindestrich `-` gefolgt von einem Leerzeichen beginnen. Der Wert kann wiederum alles sein: Zeichenkette, Zahl, Boolean, Null, Sequenz oder eine andere Zuordnung. ```neon - Cat @@ -85,7 +85,7 @@ Sequenzen sind indizierte Arrays in PHP. Sie werden als Zeilen geschrieben, die - Goldfish ``` -In PHP würde die gleiche Struktur wie folgt geschrieben werden: +In PHP würde dieselbe Struktur wie folgt geschrieben werden: ```php [ // PHP @@ -95,13 +95,13 @@ In PHP würde die gleiche Struktur wie folgt geschrieben werden: ] ``` -Diese Notation wird als Blocknotation bezeichnet, weil alle Elemente in einer separaten Zeile stehen und die gleiche Einrückung haben (in diesem Fall keine). NEON unterstützt auch die Inline-Darstellung für Sequenzen, die in eckige Klammern eingeschlossen ist, wobei die Einrückung keine Rolle spielt und das Trennzeichen jedes Elements entweder ein Komma oder ein Zeilenumbruch ist: +Diese Schreibweise wird als Block-Schreibweise bezeichnet, da alle Elemente auf einer separaten Zeile stehen und dieselbe Einrückung haben (in diesem Fall keine). NEON unterstützt auch eine Inline-Darstellung für Sequenzen, die in Klammern `[]` eingeschlossen ist, die Einrückung spielt keine Rolle und das Trennzeichen für einzelne Elemente ist entweder ein Komma oder ein Zeilenumbruch: ```neon [Cat, Dog, Goldfish] ``` -Dies ist das Gleiche auf mehreren Zeilen geschrieben (Einrückung spielt keine Rolle): +Dasselbe auf mehrere Zeilen verteilt (Einrückung spielt keine Rolle): ```neon [ @@ -110,23 +110,23 @@ Dies ist das Gleiche auf mehreren Zeilen geschrieben (Einrückung spielt keine R ] ``` -Bindestriche können in einer Inline-Darstellung nicht verwendet werden. +In der Inline-Darstellung können keine einrückenden Aufzählungszeichen (`-`) verwendet werden. -Kombination .[#toc-combination] -------------------------------- -Werte von Mappings und Sequenzen können andere Mappings und Sequenzen sein. Der Grad der Einrückung spielt eine wichtige Rolle. Im folgenden Beispiel hat der Bindestrich zur Kennzeichnung von Sequenzelementen einen größeren Einzug als die Taste `pets`, so dass die Elemente zum Wert der ersten Zeile werden: +Kombinationen +------------- +Die Werte von Zuordnungen und Sequenzen können andere Zuordnungen und Sequenzen sein. Die Einrückungsebene spielt die Hauptrolle. Im folgenden Beispiel hat der Bindestrich, der zur Kennzeichnung von Sequenzelementen verwendet wird, eine größere Einrückung als der Schlüssel `pets`, sodass die Elemente zum Wert der ersten Zeile werden: ```neon pets: - - Cat - - Dog + - Cat + - Dog cars: - - Volvo - - Skoda + - Volvo + - Skoda ``` -In PHP würde die gleiche Struktur wie folgt geschrieben werden: +In PHP würde dieselbe Struktur wie folgt geschrieben werden: ```php [ // PHP @@ -141,7 +141,7 @@ In PHP würde die gleiche Struktur wie folgt geschrieben werden: ] ``` -Es ist möglich, Block- und Inline-Schreibweise zu kombinieren: +Es ist möglich, Block- und Inline-Schreibweisen zu kombinieren: ```neon pets: [Cat, Dog] @@ -151,17 +151,39 @@ cars: [ ] ``` -Die Blockschreibweise kann nicht mehr innerhalb einer Inline-Schreibweise verwendet werden, dies funktioniert nicht: +Innerhalb der Inline-Schreibweise kann keine Block-Schreibweise mehr verwendet werden, dies funktioniert nicht: ```neon item: [ pets: - - Cat # THIS IS NOT POSSIBLE!!! + - Cat # DAS IST NICHT MÖGLICH!!! - Dog ] ``` -Da PHP die gleiche Struktur für Mapping und Sequenzen verwendet, nämlich Arrays, können beide zusammengeführt werden. Die Einrückung ist dieses Mal die gleiche: +Im vorherigen Fall haben wir eine Zuordnung geschrieben, deren Elemente Sequenzen waren, jetzt versuchen wir es umgekehrt und erstellen eine Sequenz, die Zuordnungen enthält: + +```neon +- + name: John + age: 35 +- + name: Peter + age: 28 +``` + +Es ist nicht notwendig, dass die Aufzählungszeichen auf separaten Zeilen stehen, sie können auch wie folgt platziert werden: + +```neon +- name: John + age: 35 +- name: Peter + age: 28 +``` + +Es liegt an Ihnen, ob Sie die Schlüssel mit Leerzeichen in eine Spalte ausrichten oder einen Tabulator verwenden. + +Da in PHP für Zuordnungen und Sequenzen dieselbe Struktur verwendet wird, nämlich Arrays, können beide zusammengeführt werden. Die Einrückung ist diesmal dieselbe: ```neon - Cat @@ -169,7 +191,7 @@ street: 742 Evergreen Terrace - Goldfish ``` -In PHP würde die gleiche Struktur wie folgt geschrieben werden: +In PHP würde dieselbe Struktur wie folgt geschrieben werden: ```php [ // PHP @@ -180,55 +202,55 @@ In PHP würde die gleiche Struktur wie folgt geschrieben werden: ``` -Zeichenketten .[#toc-strings] ------------------------------ +Zeichenketten +------------- Zeichenketten in NEON können in einfache oder doppelte Anführungszeichen eingeschlossen werden. Aber wie Sie sehen, können sie auch ohne Anführungszeichen sein. ```neon -- A unquoted string in NEON -- 'A singled-quoted string in NEON' -- "A double-quoted string in NEON" +- Zeichenkette in NEON ohne Anführungszeichen +- 'Zeichenkette in NEON in einfachen Anführungszeichen' +- "Zeichenkette in NEON in doppelten Anführungszeichen" ``` -Wenn die Zeichenkette Zeichen enthält `# " ' , : = - [ ] { } ( )` enthält, die mit der NEON-Syntax verwechselt werden können, muss sie in Anführungszeichen gesetzt werden. Es wird empfohlen, einfache Anführungszeichen zu verwenden, da diese keine Umgehungszeichen verwenden. Wenn Sie ein Anführungszeichen in eine solche Zeichenfolge einschließen müssen, verdoppeln Sie es: +Wenn die Zeichenkette Zeichen wie `# " ' , : = - [ ] { } ( )` enthält, die mit der NEON-Syntax verwechselt werden könnten, muss sie in Anführungszeichen eingeschlossen werden. Wir empfehlen die Verwendung einfacher Anführungszeichen (`'...'`), da darin kein Escaping verwendet wird. Wenn Sie in einer solchen Zeichenkette ein einfaches Anführungszeichen schreiben müssen, verdoppeln Sie es (`''`): ```neon -'A single quote '' inside a single-quoted string' +'Anführungszeichen '' innerhalb einer Zeichenkette in einfachen Anführungszeichen' ``` -Mit doppelten Anführungszeichen können Sie Escape-Sequenzen verwenden, um Sonderzeichen mit Backslashes `\`. All escape sequences as in the JSON format are supported, plus `\_` zu schreiben, d. h. mit einem nicht umbrechenden Leerzeichen `\u00A0`. +Doppelte Anführungszeichen (`"..."`) ermöglichen die Verwendung von Escape-Sequenzen zur Darstellung von Sonderzeichen mit Backslashes `\`. Alle Escape-Sequenzen wie im JSON-Format werden unterstützt, zusätzlich `\_`, was ein geschütztes Leerzeichen ist, also `\u00A0`. ```neon - "\t \n \r \f \b \" \\ \/ \_" - "\u00A9" ``` -Es gibt noch andere Fälle, in denen Sie Zeichenketten in Anführungszeichen einschließen müssen: -- wenn sie mit Leerzeichen beginnen oder enden -- sie sehen aus wie Zahlen, Boolesche Werte oder Null -- NEON würde sie als [Datenverstehen |#dates] +Es gibt weitere Fälle, in denen Zeichenketten in Anführungszeichen eingeschlossen werden müssen: +- sie beginnen oder enden mit Leerzeichen +- sie sehen aus wie Zahlen, Booleans oder null +- NEON würde sie als [#Datum] verstehen -Mehrzeilige Zeichenketten .[#toc-multiline-strings] ---------------------------------------------------- +Mehrzeilige Zeichenketten +------------------------- -Eine mehrzeilige Zeichenkette beginnt und endet mit einem dreifachen Anführungszeichen in separaten Zeilen. Der Einzug der ersten Zeile wird für alle Zeilen ignoriert: +Eine mehrzeilige Zeichenkette beginnt und endet mit dreifachen Anführungszeichen (`'''` oder `"""`) auf separaten Zeilen. Die Einrückung der ersten Zeile (nach den öffnenden Anführungszeichen) bestimmt die Basiseinrückung, die von allen folgenden Zeilen entfernt wird: ```neon ''' - first line - second line - third line + erste Zeile + zweite Zeile + dritte Zeile ''' ``` -In PHP würden wir das Gleiche wie folgt schreiben: +In PHP würde dies als folgende Zeichenkette interpretiert: ```php -"first line\n\tsecond line\nthird line" // PHP +"Erste Zeile\n\tZweite Zeile mit Einrückung\nDritte Zeile" // PHP ``` -Escaping-Sequenzen funktionieren nur für Strings, die in doppelte Anführungszeichen statt in Apostrophe eingeschlossen sind: +Escape-Sequenzen funktionieren nur bei Zeichenketten, die in doppelten Anführungszeichen statt Apostrophen eingeschlossen sind: ```neon """ @@ -237,24 +259,24 @@ Escaping-Sequenzen funktionieren nur für Strings, die in doppelte Anführungsze ``` -Zahlen .[#toc-numbers] ----------------------- -NEON versteht Zahlen in der so genannten wissenschaftlichen Notation sowie Zahlen in binärer, oktaler und hexadezimaler Schreibweise: +Zahlen +------ +NEON versteht Zahlen, die in sogenannter wissenschaftlicher Notation geschrieben sind, sowie Zahlen im Binär-, Oktal- und Hexadezimalsystem: ```neon -- 12 # eine ganze Zahl -- 12.3 # eine Gleitkommazahl -- +1.2e-34 # eine Exponentialzahl +- 12 # ganze Zahl +- 12.3 # float +- +1.2e-34 # Exponentialzahl -- 0b11010 # Binärzahl -- 0o666 # Oktalzahl -- 0x7A # Hexa-Zahl +- 0b11010 # Binärzahl +- 0o666 # Oktalzahl +- 0x7A # Hexadezimalzahl ``` -Nullen .[#toc-nulls] --------------------- -Nullen können in NEON durch die Verwendung von `null` oder durch die Nichtangabe eines Wertes ausgedrückt werden. Varianten mit einem großen Anfangsbuchstaben oder nur Großbuchstaben sind ebenfalls erlaubt. +Nulls +----- +Null kann in NEON mit `null` ausgedrückt werden oder durch Weglassen des Werts nach dem Doppelpunkt. Varianten mit großem Anfangsbuchstaben (`Null`) oder komplett in Großbuchstaben (`NULL`) sind ebenfalls erlaubt. ```neon a: null @@ -262,37 +284,37 @@ b: ``` -Boolesche Werte .[#toc-booleans] --------------------------------- -Boolesche Werte werden in NEON durch `true` / `false` oder `yes` / `no` ausgedrückt. Varianten mit einem großen Anfangsbuchstaben oder Großbuchstaben sind ebenfalls zulässig. +Booleans +-------- +Logische Werte werden in NEON mit `true` / `false` oder `yes` / `no` ausgedrückt. Varianten mit großem Anfangsbuchstaben oder komplett in Großbuchstaben sind ebenfalls erlaubt. ```neon [true, TRUE, True, false, yes, no] ``` -Daten .[#toc-dates] -------------------- -NEON verwendet die folgenden Formate, um Daten auszudrücken und konvertiert sie automatisch in `DateTimeImmutable` Objekte: +Datum +----- +NEON verwendet die folgenden Formate zur Darstellung von Daten und konvertiert sie automatisch in `DateTimeImmutable`-Objekte: ```neon -- 2016-06-03 # Datum -- 2016-06-03 19:00:00 # Datum & Uhrzeit -- 2016-06-03 19:00:00.1234 # Datum & Mikrozeit -- 2016-06-03 19:00:00 +0200 # Datum & Uhrzeit & Zeitzone -- 2016-06-03 19:00:00 +02:00 # Datum & Uhrzeit & Zeitzone +- 2016-06-03 # Datum +- 2016-06-03 19:00:00 # Datum & Zeit +- 2016-06-03 19:00:00.1234 # Datum & Mikrozeit +- 2016-06-03 19:00:00 +0200 # Datum & Zeit & Zeitzone +- 2016-06-03 19:00:00 +02:00 # Datum & Zeit & Zeitzone ``` -Entitäten .[#toc-entities] --------------------------- +Entitäten +--------- Eine Entität ist eine Struktur, die einem Funktionsaufruf ähnelt: ```neon Column(type: int, nulls: yes) ``` -In PHP wird sie als Objekt geparst [api:Nette\Neon\Entity]: +In PHP wird dies als Objekt [api:Nette\Neon\Entity] geparst: ```php // PHP @@ -305,7 +327,7 @@ Entitäten können auch verkettet werden: Column(type: int, nulls: yes) Field(id: 1) ``` -Dies wird in PHP wie folgt geparst: +Was in PHP auf diese Weise geparst wird: ```php // PHP @@ -315,7 +337,7 @@ new Nette\Neon\Entity(Nette\Neon\Neon::Chain, [ ]) ``` -Innerhalb der Klammern gelten die Regeln für die Inline-Notation, die für Mapping und Sequenzen verwendet werden, so dass sie in mehrere Zeilen aufgeteilt werden können und keine Kommas hinzugefügt werden müssen: +Innerhalb der Klammern gelten die Regeln für die Inline-Schreibweise, die bei Zuordnungen und Sequenzen verwendet wird, d. h., sie kann auch mehrzeilig sein, und dann müssen keine Kommas angegeben werden: ```neon Column( @@ -325,21 +347,21 @@ Column( ``` -Kommentare .[#toc-comments] ---------------------------- -Kommentare beginnen mit `#` und alle folgenden Zeichen auf der rechten Seite werden ignoriert: +Kommentare +---------- +Kommentare beginnen mit dem Zeichen `#`, und alle folgenden Zeichen rechts davon werden ignoriert: ```neon -# diese Zeile wird vom Dolmetscher ignoriert +# diese Zeile wird vom Interpreter ignoriert street: 742 Evergreen Terrace -city: Springfield # auch dies wird ignoriert +city: Springfield # dies wird ebenfalls ignoriert country: USA ``` -NEON Versus JSON .[#toc-neon-versus-json] -========================================= -JSON ist eine Untermenge von NEON. Jedes JSON kann daher als NEON geparst werden: +Neon versus JSON +================ +JSON ist eine Teilmenge von NEON. Jedes JSON kann daher als NEON geparst werden: ```neon { @@ -358,7 +380,7 @@ JSON ist eine Untermenge von NEON. Jedes JSON kann daher als NEON geparst werden } ``` -Was wäre, wenn wir die Anführungszeichen weglassen könnten? +Was wäre, wenn wir die Anführungszeichen weglassen würden? ```neon { @@ -377,7 +399,7 @@ users: [ } ``` -Wie wäre es mit Klammern und Kommas? +Und die geschweiften Klammern und Kommas? ```neon php: @@ -394,7 +416,7 @@ users: [ ] ``` -Sind Aufzählungszeichen besser lesbar? +Sind Listen mit Aufzählungszeichen nicht besser lesbar? ```neon php: @@ -412,14 +434,14 @@ users: - Rimmer ``` -Wie sieht es mit Kommentaren aus? +Fügen wir Kommentare hinzu? ```neon # meine Webanwendungskonfiguration php: date.timezone: Europe/Prague - zlib.output_compression: true # use gzip + zlib.output_compression: true # gzip verwenden database: driver: mysql @@ -432,8 +454,7 @@ users: - Rimmer ``` -Sie haben die NEON-Syntax gefunden! +Hurra, jetzt kennen Sie die Syntax von NEON! -{{description: NEON ist eine menschenfreundliche Sprache zur Serialisierung von Daten. Sie ist ähnlich wie YAML. Der Hauptunterschied besteht darin, dass NEON "Entitäten" und Tabulatorzeichen für die Einrückung unterstützt.}} -{{leftbar: utils:@left-menu}} +{{description: NEON ist ein leicht lesbares Format zur Datenserialisierung. Es ähnelt YAML. Der Hauptunterschied besteht darin, dass NEON „Entitäten“ unterstützt und wir sowohl Leerzeichen als auch Tabulatoren zur Einrückung verwenden können.}} diff --git a/neon/el/@home.texy b/neon/el/@home.texy index 38e047f559..18c47bc4a6 100644 --- a/neon/el/@home.texy +++ b/neon/el/@home.texy @@ -1,54 +1,54 @@ -Λειτουργίες NEON -**************** +Nette NEON +********** <div class=perex> -Η NEON είναι μια φιλική προς τον άνθρωπο γλώσσα σειριοποίησης δεδομένων. Χρησιμοποιείται στο Nette για αρχεία ρυθμίσεων. [api:Nette\Neon\Neon] είναι μια στατική κλάση για την εργασία με τη NEON. +Το NEON είναι μια γλώσσα σειριοποίησης δεδομένων κατανοητή από τον άνθρωπο. Χρησιμοποιείται στο Nette για αρχεία διαμόρφωσης. Η [api:Nette\Neon\Neon] είναι μια στατική κλάση για την εργασία με το NEON. -Γνωρίστε τη [μορφή NEON |format] και [δοκιμάστε την |https://ne-on.org]. +Εξοικειωθείτε με [τη μορφή NEON|format] και [δοκιμάστε την |https://ne-on.org]. </div> -Τα ακόλουθα παραδείγματα χρησιμοποιούν αυτά τα ψευδώνυμα: +Όλα τα παραδείγματα προϋποθέτουν τη δημιουργία ενός ψευδωνύμου: ```php use Nette\Neon\Neon; ``` -Εγκατάσταση .[#toc-installation] --------------------------------- +Εγκατάσταση +----------- -Κατεβάστε και εγκαταστήστε το πακέτο χρησιμοποιώντας το [Composer |best-practices:composer]: +Μπορείτε να κατεβάσετε και να εγκαταστήσετε τη βιβλιοθήκη χρησιμοποιώντας το [Composer|best-practices:composer]: ```shell composer require nette/neon ``` -Μπορείτε να ελέγξετε για συντακτικά σφάλματα στα αρχεία `*.neon` χρησιμοποιώντας την εντολή `neon-lint` της κονσόλας: +Μπορείτε να ελέγξετε για σφάλματα σύνταξης στα αρχεία `*.neon` χρησιμοποιώντας την εντολή κονσόλας `neon-lint`: ```shell vendor/bin/neon-lint <path> ``` -encode(mixed $value, bool $blockMode=false): string .[method] -------------------------------------------------------------- +encode(mixed $value, bool $blockMode=false, string $indentation="\t"): string .[method] +--------------------------------------------------------------------------------------- -Επιστρέφει το `$value` σε NEON. Ως παράμετρος `$blockMode` μπορείτε να περάσετε την true, η οποία θα δημιουργήσει έξοδο πολλαπλών γραμμών. Η παράμετρος `$indentation` καθορίζει τους χαρακτήρες που χρησιμοποιούνται για την εσοχή (προεπιλογή είναι το tab). +Επιστρέφει το `$value` μετατραπέν σε NEON. Ως παράμετρο `$blockMode`, μπορείτε να περάσετε true, το οποίο θα δημιουργήσει έξοδο πολλαπλών γραμμών. Η παράμετρος `$indentation` καθορίζει τους χαρακτήρες που χρησιμοποιούνται για την εσοχή (η προεπιλογή είναι tab). ```php -Neon::encode($value); // Επιστρέφει την τιμή $value μετατραπείσα σε NEON -Neon::encode($value, true); // Επιστρέφει την τιμή $value μετατραπείσα σε πολυγραμμικό NEON +Neon::encode($value); // Επιστρέφει το $value μετατραπέν σε NEON +Neon::encode($value, true); // Επιστρέφει το $value μετατραπέν σε NEON πολλαπλών γραμμών ``` -Η μέθοδος `encode()` πετάει το `Nette\Neon\Exception` σε περίπτωση σφάλματος. +Η μέθοδος `encode()` ρίχνει μια `Nette\Neon\Exception` σε περίπτωση σφάλματος. ```php try { $neon = Neon::encode($value); } catch (Nette\Neon\Exception $e) { - // Χειρισμός εξαιρέσεων + // χειρισμός εξαίρεσης } ``` @@ -56,21 +56,21 @@ try { decode(string $neon): mixed .[method] ------------------------------------- -Μετατρέπει το δεδομένο NEON σε τιμή PHP. +Μετατρέπει ένα string από NEON σε PHP. -Επιστρέφει κλιμάκια, πίνακες, [ημερομηνία |format#dates] ως αντικείμενα DateTimeImmutable και [οντότητες |format#Entities] ως αντικείμενα [api:Nette\Neon\Entity]. +Επιστρέφει scalars, arrays, [ημερομηνίες |format#Ημερομηνία] ως αντικείμενα DateTimeImmutable και [entities |format#Entities] ως αντικείμενα [api:Nette\Neon\Entity]. ```php -Neon::decode('hello: world'); // Επιστρέφει έναν πίνακα ['hello' => 'world'] +Neon::decode('hello: world'); // Επιστρέφει τον πίνακα ['hello' => 'world'] ``` -Η μέθοδος `decode()` πετάει `Nette\Neon\Exception` σε περίπτωση σφάλματος. +Η μέθοδος `decode()` ρίχνει μια `Nette\Neon\Exception` σε περίπτωση σφάλματος. ```php try { $value = Neon::decode($neon); } catch (Nette\Neon\Exception $e) { - // Χειρισμός εξαιρέσεων + // χειρισμός εξαίρεσης } ``` @@ -78,13 +78,10 @@ try { decodeFile(string $file): mixed .[method] ----------------------------------------- -Μετατρέπει τα περιεχόμενα του αρχείου από NEON σε PHP και αφαιρεί τυχόν BOM. +Μετατρέπει το περιεχόμενο ενός αρχείου από NEON σε PHP και αφαιρεί οποιοδήποτε BOM. ```php Neon::decodeFile('config.neon'); ``` -Η μέθοδος `decodeFile()` εκπέμπει `Nette\Neon\Exception` σε περίπτωση σφάλματος. - - -{{leftbar: utils:@left-menu}} +Η μέθοδος `decodeFile()` ρίχνει μια `Nette\Neon\Exception` σε περίπτωση σφάλματος. diff --git a/neon/el/@meta.texy b/neon/el/@meta.texy new file mode 100644 index 0000000000..2e88e535cb --- /dev/null +++ b/neon/el/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Τεκμηρίωση}} +{{leftbar: utils:@left-menu}} diff --git a/neon/el/format.texy b/neon/el/format.texy index 2b89b64c18..11d6436156 100644 --- a/neon/el/format.texy +++ b/neon/el/format.texy @@ -2,40 +2,40 @@ ********** .[perex] -Το NEON είναι μια μορφή δομημένων δεδομένων με δυνατότητα ανάγνωσης από τον άνθρωπο. Στο Nette, χρησιμοποιείται για αρχεία διαμόρφωσης. Χρησιμοποιείται επίσης για δομημένα δεδομένα όπως ρυθμίσεις, μεταφράσεις γλωσσών κ.λπ. [Δοκιμάστε το στο sandbox |https://ne-on.org]. +Το NEON είναι μια ευανάγνωστη μορφή δομημένων δεδομένων. Στο Nette χρησιμοποιείται για αρχεία διαμόρφωσης. Χρησιμοποιείται επίσης για δομημένα δεδομένα, όπως ρυθμίσεις, γλωσσικές μεταφράσεις κ.λπ. [Δοκιμάστε το|https://ne-on.org]. -Το NEON σημαίνει *Nette Object Notation*. Είναι λιγότερο πολύπλοκο και δυσκίνητο από την XML ή το JSON, αλλά παρέχει παρόμοιες δυνατότητες. Μοιάζει πολύ με το YAML. Το κύριο πλεονέκτημα είναι ότι το NEON έχει τις λεγόμενες [οντότητες |#entities], χάρη στις οποίες η διαμόρφωση των υπηρεσιών DI είναι τόσο σέξι. Και επιτρέπει tabs για εσοχή. +Το NEON είναι ακρωνύμιο του *Nette Object Notation*. Είναι λιγότερο περίπλοκο και αδέξιο από το XML ή το JSON, αλλά παρέχει παρόμοιες λειτουργίες. Είναι πολύ παρόμοιο με το YAML. Το κύριο πλεονέκτημα είναι ότι το NEON διαθέτει τις λεγόμενες [οντότητες |#Entities], χάρη στις οποίες η διαμόρφωση των υπηρεσιών DI είναι [επίσης σέξι |https://gist.github.com/dg/26baf3ce8f29d0f751e9dddfaa06504f]. Και επιτρέπει την εσοχή με tabs. -Το NEON έχει κατασκευαστεί από την αρχή για να είναι απλό στη χρήση. +Το NEON είναι χτισμένο από την αρχή ώστε να είναι εύκολο στη χρήση. -Ενσωμάτωση .[#toc-integration] -============================== +Ενσωμάτωση +========== - NetBeans (έχει ενσωματωμένη υποστήριξη) -- PhpStorm ([πρόσθετο |https://plugins.jetbrains.com/plugin/7060?pr]) -- Visual Studio Code ([πρόσθετο |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) -- Sublime Text 3 ([πρόσθετο |https://github.com/FilipStryk/Nette-Latte-Neon-for-Sublime-Text-3]) -- Sublime Text 2 ([πρόσθετο |https://github.com/Michal-Mikolas/Nette-package-for-Sublime-Text-2]) -- VIM ([πρόσθετο |https://github.com/fpob/nette.vim]) -- Emacs ([πρόσθετο |https://github.com/Fuco1/neon-mode]) +- PhpStorm ([plugin |https://plugins.jetbrains.com/plugin/7060?pr]) +- Visual Studio Code ([Nette Latte + Neon |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) ή [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode]) +- Sublime Text 3 ([plugin |https://github.com/FilipStryk/Nette-Latte-Neon-for-Sublime-Text-3]) +- Sublime Text 2 ([plugin |https://github.com/Michal-Mikolas/Nette-package-for-Sublime-Text-2]) +- VIM ([plugin |https://github.com/fpob/nette.vim]) +- Emacs ([plugin |https://github.com/Fuco1/neon-mode]) - Prism.js ([ενσωματωμένη γλώσσα |https://prismjs.com/#supported-languages]) -- [NEON για PHP |@home] -- [NEON για JavaScript |https://github.com/matej21/neon-js] -- [NEON για Python |https://github.com/paveldedik/neon-py]. +- [NEON for PHP |@home] +- [NEON for JavaScript |https://github.com/matej21/neon-js] +- [NEON for Python |https://github.com/paveldedik/neon-py]. -Σύνταξη .[#toc-syntax] -====================== +Σύνταξη +======= -Ένα αρχείο γραμμένο σε NEON αποτελείται συνήθως από μια ακολουθία ή χαρτογράφηση. +Ένα αρχείο γραμμένο σε NEON αντιπροσωπεύει συνήθως έναν πίνακα ή μια αντιστοίχιση. -Χαρτογραφήσεις .[#toc-mappings] -------------------------------- -Η χαρτογράφηση είναι ένα σύνολο ζευγών κλειδιού-τιμής, στην PHP θα ονομαζόταν συσχετιστικός πίνακας. Κάθε ζεύγος γράφεται ως `key: value`, ένα κενό μετά το `:` είναι απαραίτητο. Η τιμή μπορεί να είναι οτιδήποτε: συμβολοσειρά, αριθμός, boolean, null, ακολουθία ή άλλη αντιστοίχιση. +Αντιστοίχιση +------------ +Η αντιστοίχιση είναι ένα σύνολο ζευγών κλειδιού-τιμής, στην PHP θα λέγαμε ένας συσχετιστικός πίνακας. Κάθε ζεύγος γράφεται ως `key: value`, το κενό μετά το `:` είναι απαραίτητο. Η τιμή μπορεί να είναι οτιδήποτε: string, αριθμός, boolean, null, ακολουθία ή άλλη αντιστοίχιση. ```neon street: 742 Evergreen Terrace @@ -43,7 +43,7 @@ city: Springfield country: USA ``` -Στην PHP, η ίδια δομή θα γραφόταν ως εξής: +Στην PHP, η ίδια δομή θα γραφόταν ως: ```php [ // PHP @@ -53,13 +53,13 @@ country: USA ] ``` -Αυτή η γραφή ονομάζεται γραφή μπλοκ επειδή όλα τα στοιχεία βρίσκονται σε ξεχωριστή γραμμή και έχουν την ίδια εσοχή (καμία σε αυτή την περίπτωση). Ο NEON υποστηρίζει επίσης αναπαράσταση inline για την αντιστοίχιση, η οποία περικλείεται σε αγκύλες, η εσοχή δεν παίζει κανένα ρόλο και το διαχωριστικό κάθε στοιχείου είναι είτε ένα κόμμα είτε μια νέα γραμμή: +Αυτή η γραφή ονομάζεται μπλοκ, επειδή όλα τα στοιχεία βρίσκονται σε ξεχωριστή γραμμή και έχουν την ίδια εσοχή (σε αυτή την περίπτωση, καμία). Το NEON υποστηρίζει επίσης την inline αναπαράσταση αντιστοιχίσεων, η οποία περικλείεται σε αγκύλες `{}`, η εσοχή δεν παίζει κανένα ρόλο και ο διαχωριστής των μεμονωμένων στοιχείων είναι είτε το κόμμα, είτε η νέα γραμμή: ```neon {street: 742 Evergreen Terrace, city: Springfield, country: USA} ``` -Αυτό είναι το ίδιο γραμμένο σε πολλές γραμμές (η εσοχή δεν παίζει ρόλο): +Το ίδιο γραμμένο σε πολλές γραμμές (η εσοχή δεν έχει σημασία): ```neon { @@ -68,16 +68,16 @@ country: USA } ``` -Εναλλακτικά, το `=` μπορεί να χρησιμοποιηθεί αντί του <code>: </code>, τόσο σε σημειογραφία μπλοκ όσο και σε γραμμική σημειογραφία: +Αντί για <code>: </code>, μπορείτε εναλλακτικά να χρησιμοποιήσετε το `=` τόσο στη γραφή μπλοκ όσο και στην inline γραφή: ```neon {street=742 Evergreen Terrace, city=Springfield, country=USA} ``` -Ακολουθίες .[#toc-sequences] ----------------------------- -Οι ακολουθίες είναι ευρετηριασμένοι πίνακες στην PHP. Γράφονται ως γραμμές που αρχίζουν με την παύλα `-` ακολουθούμενες από ένα διάστημα. Και πάλι, η τιμή μπορεί να είναι οτιδήποτε: συμβολοσειρά, αριθμός, boolean, null, ακολουθία ή άλλη αντιστοίχιση. +Ακολουθίες +---------- +Οι ακολουθίες είναι στην PHP πίνακες με δείκτες. Γράφονται ως γραμμές που ξεκινούν με παύλα `-` ακολουθούμενη από κενό. Η τιμή μπορεί πάλι να είναι οτιδήποτε: string, αριθμός, boolean, null, ακολουθία ή άλλη αντιστοίχιση. ```neon - Cat @@ -85,7 +85,7 @@ country: USA - Goldfish ``` -Στην PHP, η ίδια δομή θα γραφόταν ως εξής: +Στην PHP, η ίδια δομή θα γραφόταν ως: ```php [ // PHP @@ -95,13 +95,13 @@ country: USA ] ``` -Αυτή η γραφή ονομάζεται γραφή μπλοκ επειδή όλα τα στοιχεία βρίσκονται σε ξεχωριστή γραμμή και έχουν την ίδια εσοχή (καμία σε αυτή την περίπτωση). Ο NEON υποστηρίζει επίσης την αναπαράσταση inline για ακολουθίες, η οποία περικλείεται σε αγκύλες, η εσοχή δεν παίζει κανένα ρόλο και ο διαχωριστής κάθε στοιχείου είναι είτε ένα κόμμα είτε μια νέα γραμμή: +Αυτή η γραφή ονομάζεται μπλοκ, επειδή όλα τα στοιχεία βρίσκονται σε ξεχωριστή γραμμή και έχουν την ίδια εσοχή (σε αυτή την περίπτωση, καμία). Το NEON υποστηρίζει επίσης την inline αναπαράσταση ακολουθιών, η οποία περικλείεται σε αγκύλες `[]`, η εσοχή δεν παίζει κανένα ρόλο και ο διαχωριστής των μεμονωμένων στοιχείων είναι είτε το κόμμα είτε η νέα γραμμή: ```neon [Cat, Dog, Goldfish] ``` -Αυτό είναι το ίδιο γραμμένο σε πολλές γραμμές (η εσοχή δεν παίζει ρόλο): +Το ίδιο γραμμένο σε πολλές γραμμές (η εσοχή δεν έχει σημασία): ```neon [ @@ -110,23 +110,23 @@ country: USA ] ``` -Οι παύλες δεν μπορούν να χρησιμοποιηθούν σε παράθεση εντός γραμμής. +Στην inline αναπαράσταση δεν μπορούν να χρησιμοποιηθούν εσοχές με παύλες. -Συνδυασμός .[#toc-combination] ------------------------------- -Οι τιμές των αντιστοιχίσεων και ακολουθιών μπορούν να είναι άλλες αντιστοιχίσεις και ακολουθίες. Το επίπεδο εσοχής παίζει σημαντικό ρόλο. Στο ακόλουθο παράδειγμα, η παύλα που χρησιμοποιείται για να υποδείξει στοιχεία ακολουθίας έχει μεγαλύτερη εσοχή από το κλειδί `pets`, οπότε τα στοιχεία γίνονται η τιμή της πρώτης γραμμής: +Συνδυασμοί +---------- +Οι τιμές των αντιστοιχίσεων και των ακολουθιών μπορούν να είναι άλλες αντιστοιχίσεις και ακολουθίες. Ο κύριος ρόλος παίζεται από το επίπεδο εσοχής. Στο παρακάτω παράδειγμα, η παύλα που χρησιμοποιείται για τη σήμανση των στοιχείων της ακολουθίας έχει μεγαλύτερη εσοχή από το κλειδί `pets`, οπότε τα στοιχεία γίνονται η τιμή της πρώτης γραμμής: ```neon pets: - - Cat - - Dog + - Cat + - Dog cars: - - Volvo - - Skoda + - Volvo + - Skoda ``` -Στην PHP, η ίδια δομή θα γραφόταν ως εξής: +Στην PHP, η ίδια δομή θα γραφόταν ως: ```php [ // PHP @@ -141,7 +141,7 @@ cars: ] ``` -Είναι δυνατό να συνδυάσετε την σημειογραφία block και inline: +Μπορείτε να συνδυάσετε τη γραφή μπλοκ και την inline γραφή: ```neon pets: [Cat, Dog] @@ -151,17 +151,39 @@ cars: [ ] ``` -Αυτό δεν λειτουργεί: +Μέσα στην inline γραφή δεν μπορείτε πλέον να χρησιμοποιήσετε τη γραφή μπλοκ, αυτό δεν λειτουργεί: ```neon item: [ pets: - - Cat # ΑΥΤΌ ΔΕΝ ΕΊΝΑΙ ΔΥΝΑΤΌΝ!!! + - Cat # ΑΥΤΟ ΔΕΝ ΕΙΝΑΙ ΔΥΝΑΤΟ!!! - Dog ] ``` -Επειδή η PHP χρησιμοποιεί την ίδια δομή για την αντιστοίχιση και τις ακολουθίες, δηλαδή πίνακες, και οι δύο μπορούν να συγχωνευθούν. Η εσοχή είναι η ίδια αυτή τη φορά: +Στην προηγούμενη περίπτωση, γράψαμε μια αντιστοίχιση της οποίας τα στοιχεία ήταν ακολουθίες, τώρα θα προσπαθήσουμε το αντίστροφο και θα δημιουργήσουμε μια ακολουθία που περιέχει αντιστοιχίσεις: + +```neon +- + name: John + age: 35 +- + name: Peter + age: 28 +``` + +Δεν είναι απαραίτητο οι παύλες να βρίσκονται σε ξεχωριστές γραμμές, μπορούν να τοποθετηθούν και με αυτόν τον τρόπο: + +```neon +- name: John + age: 35 +- name: Peter + age: 28 +``` + +Εξαρτάται από εσάς αν θα ευθυγραμμίσετε τα κλειδιά σε μια στήλη χρησιμοποιώντας κενά ή θα χρησιμοποιήσετε ένα tab. + +Επειδή στην PHP χρησιμοποιείται η ίδια δομή τόσο για τις αντιστοιχίσεις όσο και για τις ακολουθίες, δηλαδή οι πίνακες, και τα δύο μπορούν να συγχωνευθούν. Η εσοχή είναι αυτή τη φορά η ίδια: ```neon - Cat @@ -169,7 +191,7 @@ street: 742 Evergreen Terrace - Goldfish ``` -Στην PHP, η ίδια δομή θα γραφόταν ως εξής: +Στην PHP, η ίδια δομή θα γραφόταν ως: ```php [ // PHP @@ -180,55 +202,55 @@ street: 742 Evergreen Terrace ``` -Strings .[#toc-strings] ------------------------ -Οι συμβολοσειρές στο NEON μπορούν να περικλείονται σε απλά ή διπλά εισαγωγικά. Αλλά όπως μπορείτε να δείτε, μπορούν επίσης να είναι χωρίς εισαγωγικά. +Strings +------- +Τα strings στο NEON μπορούν να περικλείονται σε απλά ή διπλά εισαγωγικά. Αλλά όπως βλέπετε, μπορούν να είναι και χωρίς εισαγωγικά. ```neon -- A unquoted string in NEON -- 'A singled-quoted string in NEON' -- "A double-quoted string in NEON" +- String σε NEON χωρίς εισαγωγικά +- 'String σε NEON σε απλά εισαγωγικά' +- "String σε NEON σε διπλά εισαγωγικά" ``` -Εάν η συμβολοσειρά περιέχει χαρακτήρες `# " ' , : = - [ ] { } ( )` που μπορούν να συγχέονται με τη σύνταξη του NEON, πρέπει να περικλείονται σε εισαγωγικά. Συνιστούμε τη χρήση μονών εισαγωγικών επειδή δεν χρησιμοποιούν διαφυγή. Εάν πρέπει να περικλείσετε ένα εισαγωγικό σε μια τέτοια συμβολοσειρά, διπλασιάστε το: +Αν ένα string περιέχει τους χαρακτήρες `# " ' , : = - [ ] { } ( )`, οι οποίοι μπορούν να συγχέονται με τη σύνταξη NEON, πρέπει να περικλείεται σε εισαγωγικά. Συνιστούμε τη χρήση απλών εισαγωγικών, επειδή δεν χρησιμοποιούν διαφυγή. Αν χρειαστεί να γράψετε ένα εισαγωγικό σε ένα τέτοιο string, διπλασιάστε το: ```neon -'A single quote '' inside a single-quoted string' +'Εισαγωγικό '' μέσα σε string σε απλά εισαγωγικά' ``` -Τα διπλά εισαγωγικά σας επιτρέπουν να χρησιμοποιήσετε ακολουθίες διαφυγής για να γράψετε ειδικούς χαρακτήρες χρησιμοποιώντας backslashes `\`. All escape sequences as in the JSON format are supported, plus `\_`, το οποίο είναι ένα μη διακεκομμένο διάστημα, δηλαδή `\u00A0`. +Τα διπλά εισαγωγικά επιτρέπουν τη χρήση ακολουθιών διαφυγής για τη γραφή ειδικών χαρακτήρων χρησιμοποιώντας ανάποδες καθέτους `\`. Υποστηρίζονται όλες οι ακολουθίες διαφυγής όπως στη μορφή JSON και επιπλέον το `\_`, το οποίο είναι ένα αδιάσπαστο κενό, δηλαδή `\u00A0`. ```neon - "\t \n \r \f \b \" \\ \/ \_" - "\u00A9" ``` -Υπάρχουν και άλλες περιπτώσεις όπου πρέπει να περικλείετε συμβολοσειρές σε εισαγωγικά: +Υπάρχουν και άλλες περιπτώσεις όπου είναι απαραίτητο να περικλείονται τα strings σε εισαγωγικά: - αρχίζουν ή τελειώνουν με κενά - μοιάζουν με αριθμούς, booleans ή null -- Ο NEON θα τα καταλάβαινε ως [ημερομηνίες |#dates] +- το NEON θα τα καταλάβαινε ως [#ημερομηνία] -Συμβολοσειρές πολλών γραμμών .[#toc-multiline-strings] ------------------------------------------------------- +Πολυγραμμικά strings +-------------------- -Μια συμβολοσειρά πολλαπλών γραμμών αρχίζει και τελειώνει με τριπλό εισαγωγικό σε ξεχωριστές γραμμές. Η εσοχή της πρώτης γραμμής αγνοείται για όλες τις γραμμές: +Ένα πολυγραμμικό string αρχίζει και τελειώνει με τριπλά εισαγωγικά σε ξεχωριστές γραμμές. Η εσοχή της πρώτης γραμμής αγνοείται και αυτό ισχύει για όλες τις γραμμές: ```neon ''' - first line - second line - third line + πρώτη γραμμή + δεύτερη γραμμή + τρίτη γραμμή ''' ``` -Στην PHP θα γράφαμε το ίδιο ως εξής: +Στην PHP θα γράφαμε το ίδιο ως: ```php -"first line\n\tsecond line\nthird line" // PHP +"πρώτη γραμμή\n\tdεύτερη γραμμή\nτρίτη γραμμή" // PHP ``` -Οι ακολουθίες διαφυγής λειτουργούν μόνο για συμβολοσειρές που περικλείονται σε διπλά εισαγωγικά αντί για απόστροφα: +Οι ακολουθίες διαφυγής λειτουργούν μόνο για strings που περικλείονται σε διπλά εισαγωγικά αντί για αποστρόφους: ```neon """ @@ -237,14 +259,14 @@ Strings .[#toc-strings] ``` -Αριθμοί .[#toc-numbers] ------------------------ -Ο NEON κατανοεί αριθμούς γραμμένους στη λεγόμενη επιστημονική σημειογραφία, καθώς και αριθμούς σε δυαδικό, οκταδικό και δεκαεξαδικό σύστημα: +Αριθμοί +------- +Το NEON κατανοεί αριθμούς γραμμένους στη λεγόμενη επιστημονική σημειογραφία καθώς και αριθμούς σε δυαδικό, οκταδικό και δεκαεξαδικό σύστημα: ```neon -- 12 # έναν ακέραιο αριθμό -- 12.3 # μια κινητή μονάδα -- +1.2e-34 # έναν εκθετικό αριθμό +- 12 # ακέραιος +- 12.3 # float +- +1.2e-34 # εκθετικός αριθμός - 0b11010 # δυαδικός αριθμός - 0o666 # οκταδικός αριθμός @@ -252,9 +274,9 @@ Strings .[#toc-strings] ``` -Μηδενικά .[#toc-nulls] ----------------------- -Το null μπορεί να εκφραστεί στο NEON με τη χρήση του `null` ή με τον μη προσδιορισμό τιμής. Επιτρέπονται επίσης παραλλαγές με κεφαλαίο πρώτο γράμμα ή όλα τα κεφαλαία γράμματα. +Nulls +----- +Το Null μπορεί να εκφραστεί στο NEON χρησιμοποιώντας `null` ή παραλείποντας την τιμή. Επιτρέπονται επίσης παραλλαγές με κεφαλαίο πρώτο γράμμα ή όλα τα γράμματα κεφαλαία (`Null`, `NULL`). ```neon a: null @@ -262,30 +284,30 @@ b: ``` -Booleans .[#toc-booleans] -------------------------- -Οι τιμές Boolean εκφράζονται στο NEON χρησιμοποιώντας `true` / `false` ή `yes` / `no`. Επιτρέπονται επίσης παραλλαγές με κεφαλαίο πρώτο γράμμα ή όλα τα κεφαλαία γράμματα. +Booleans +-------- +Οι λογικές τιμές εκφράζονται στο NEON χρησιμοποιώντας `true` / `false` ή `yes` / `no`. Επιτρέπονται επίσης παραλλαγές με κεφαλαίο πρώτο γράμμα ή όλα τα γράμματα κεφαλαία (`True`, `TRUE`, `False`, `FALSE`, `Yes`, `YES`, `No`, `NO`). ```neon [true, TRUE, True, false, yes, no] ``` -Ημερομηνίες .[#toc-dates] -------------------------- -Ο NEON χρησιμοποιεί τις ακόλουθες μορφές για να εκφράσει δεδομένα και τις μετατρέπει αυτόματα σε αντικείμενα `DateTimeImmutable`: +Ημερομηνία +---------- +Το NEON χρησιμοποιεί τις ακόλουθες μορφές για την έκφραση ημερομηνιών και τις μετατρέπει αυτόματα σε αντικείμενα `DateTimeImmutable`: ```neon - 2016-06-03 # ημερομηνία - 2016-06-03 19:00:00 # ημερομηνία & ώρα -- 2016-06-03 19:00:00.1234 # ημερομηνία & μικροώρα -- 2016-06-03 19:00:00 +0200 # ημερομηνία & ώρα & ζώνη ώρας -- 2016-06-03 19:00:00 +02:00 # ημερομηνία & ώρα & ζώνη ώρας +- 2016-06-03 19:00:00.1234 # ημερομηνία & μικροχρόνος +- 2016-06-03 19:00:00 +0200 # ημερομηνία & ώρα & ζώνη +- 2016-06-03 19:00:00 +02:00 # ημερομηνία & ώρα & ζώνη ``` -Οντότητες .[#toc-entities] --------------------------- +Entities +-------- Μια οντότητα είναι μια δομή που μοιάζει με κλήση συνάρτησης: ```neon @@ -299,13 +321,13 @@ Column(type: int, nulls: yes) new Nette\Neon\Entity('Column', ['type' => 'int', 'nulls' => true]) ``` -Οι οντότητες μπορούν επίσης να αλυσιδωθούν: +Οι οντότητες μπορούν επίσης να συνδεθούν σε αλυσίδα: ```neon Column(type: int, nulls: yes) Field(id: 1) ``` -Η οποία αναλύεται στην PHP ως εξής: +Το οποίο αναλύεται στην PHP με αυτόν τον τρόπο: ```php // PHP @@ -315,7 +337,7 @@ new Nette\Neon\Entity(Nette\Neon\Neon::Chain, [ ]) ``` -Εντός των παρενθέσεων, ισχύουν οι κανόνες για τον εν σειρά συμβολισμό που χρησιμοποιούνται για την αντιστοίχιση και τις ακολουθίες, οπότε μπορεί να χωριστεί σε πολλές γραμμές και δεν είναι απαραίτητο να προστεθούν κόμματα: +Μέσα στις παρενθέσεις ισχύουν οι κανόνες για την inline γραφή που χρησιμοποιείται για αντιστοιχίσεις και ακολουθίες, δηλαδή μπορεί να είναι και πολυγραμμική και τότε δεν είναι απαραίτητο να αναφέρονται κόμματα: ```neon Column( @@ -325,21 +347,21 @@ Column( ``` -Σχόλια .[#toc-comments] ------------------------ -Τα σχόλια ξεκινούν με `#` και όλοι οι επόμενοι χαρακτήρες στα δεξιά αγνοούνται: +Σχόλια +------ +Τα σχόλια ξεκινούν με τον χαρακτήρα `#` και όλοι οι επόμενοι χαρακτήρες στα δεξιά αγνοούνται: ```neon # αυτή η γραμμή θα αγνοηθεί από τον διερμηνέα street: 742 Evergreen Terrace -city: Springfield # Και αυτή αγνοείται +city: Springfield # αυτό αγνοείται επίσης country: USA ``` -NEON έναντι JSON .[#toc-neon-versus-json] -========================================= -Το JSON είναι ένα υποσύνολο του NEON. Συνεπώς, κάθε JSON μπορεί να αναλυθεί ως NEON: +Neon έναντι JSON +================ +Το JSON είναι υποσύνολο του NEON. Κάθε JSON μπορεί επομένως να αναλυθεί ως NEON: ```neon { @@ -358,7 +380,7 @@ NEON έναντι JSON .[#toc-neon-versus-json] } ``` -Τι θα γινόταν αν μπορούσαμε να παραλείψουμε τα εισαγωγικά; +Τι θα γινόταν αν παραλείπαμε τα εισαγωγικά; ```neon { @@ -377,7 +399,7 @@ users: [ } ``` -Τι θα λέγατε για αγκύλες και κόμματα; +Και τις αγκύλες `{}` και τα κόμματα; ```neon php: @@ -394,7 +416,7 @@ users: [ ] ``` -Είναι πιο ευανάγνωστα τα bullets; +Δεν είναι οι λίστες με παύλες πιο ευανάγνωστες; ```neon php: @@ -412,10 +434,10 @@ users: - Rimmer ``` -Τι γίνεται με τα σχόλια; +Να προσθέσουμε σχόλια; ```neon -# my web application config +# η διαμόρφωση της διαδικτυακής μου εφαρμογής php: date.timezone: Europe/Prague @@ -432,8 +454,7 @@ users: - Rimmer ``` -Βρήκατε τη σύνταξη NEON! +Ουρά, τώρα γνωρίζετε τη σύνταξη του NEON! -{{description: NEON είναι μια φιλική προς τον άνθρωπο γλώσσα σειριοποίησης δεδομένων. Είναι παρόμοια με την YAML. Η κύρια διαφορά είναι ότι η NEON υποστηρίζει "οντότητες" και χαρακτήρες tab για εσοχή.}} -{{leftbar: utils:@left-menu}} +{{description: Το NEON είναι μια ευανάγνωστη μορφή για τη σειριοποίηση δεδομένων. Είναι παρόμοιο με το YAML. Η κύρια διαφορά είναι ότι το NEON υποστηρίζει "οντότητες" και μπορούμε να χρησιμοποιήσουμε τόσο κενά όσο και tabs για εσοχές.}} diff --git a/neon/en/@home.texy b/neon/en/@home.texy index c560bffaf2..d162c54f3f 100644 --- a/neon/en/@home.texy +++ b/neon/en/@home.texy @@ -1,15 +1,15 @@ -NEON Functions -************** +Nette NEON +********** <div class=perex> NEON is a human-friendly data serialization language. It is used in Nette for configuration files. [api:Nette\Neon\Neon] is a static class for working with NEON. -Get to know [NEON format|format] and [try it out |https://ne-on.org]. +Get to know the [NEON format|format] and [try it out |https://ne-on.org]. </div> -The following examples use these aliases: +All examples assume the following alias is defined: ```php use Nette\Neon\Neon; @@ -32,17 +32,17 @@ vendor/bin/neon-lint <path> ``` -encode(mixed $value, bool $blockMode=false): string .[method] -------------------------------------------------------------- +encode(mixed $value, bool $blockMode=false, string $indentation="\t"): string .[method] +--------------------------------------------------------------------------------------- -Returns `$value` converted to NEON. As the parameter `$blockMode` you can pass true, which will create multiline output. The parameter `$indentation` specifies the characters used for indentation (default is tab). +Returns `$value` converted to NEON. You can pass `true` to the `$blockMode` parameter to create multiline output. The `$indentation` parameter specifies the characters used for indentation (default is tab). ```php Neon::encode($value); // Returns $value converted to NEON Neon::encode($value, true); // Returns $value converted to multiline NEON ``` -Method `encode()` throws `Nette\Neon\Exception` on error. +The `encode()` method throws `Nette\Neon\Exception` on error. ```php try { @@ -56,15 +56,15 @@ try { decode(string $neon): mixed .[method] ------------------------------------- -Converts given NEON to PHP value. +Converts the given NEON string to a PHP value. -Returns scalars, arrays, [date|format#dates] as DateTimeImmutable objects, and [entities|format#Entities] as [api:Nette\Neon\Entity] objects. +Returns scalars, arrays, [dates |format#Dates] as DateTimeImmutable objects, and [entities |format#Entities] as [api:Nette\Neon\Entity] objects. ```php Neon::decode('hello: world'); // Returns an array ['hello' => 'world'] ``` -Method `decode()` throws `Nette\Neon\Exception` on error. +The `decode()` method throws `Nette\Neon\Exception` on error. ```php try { @@ -78,13 +78,10 @@ try { decodeFile(string $file): mixed .[method] ----------------------------------------- -Converts the contents of the file from NEON to PHP and removes any BOM. +Converts the contents of a file from NEON to PHP and removes any BOM. ```php Neon::decodeFile('config.neon'); ``` -Method `decodeFile()` throws `Nette\Neon\Exception` on error. - - -{{leftbar: utils:@left-menu}} +The `decodeFile()` method throws `Nette\Neon\Exception` on error. diff --git a/neon/en/@meta.texy b/neon/en/@meta.texy new file mode 100644 index 0000000000..10ac86a352 --- /dev/null +++ b/neon/en/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Documentation}} +{{leftbar: utils:@left-menu}} diff --git a/neon/en/format.texy b/neon/en/format.texy index 9656952652..5f6a51d2c8 100644 --- a/neon/en/format.texy +++ b/neon/en/format.texy @@ -4,9 +4,9 @@ NEON Format .[perex] NEON is a human-readable structured data format. In Nette, it is used for configuration files. It is also used for structured data such as settings, language translations, etc. [Try it on the sandbox |https://ne-on.org]. -NEON stands for *Nette Object Notation*. It is less complex and ungainly than XML or JSON, but provides similar capabilities. It is very similar to YAML. The main advantage is that NEON has so-called [#entities], thanks to which the configuration of DI services is so sexy. And allowes tabs for indentation. +NEON stands for *Nette Object Notation*. It is less complex and cumbersome than XML or JSON, but provides similar capabilities. It is very similar to YAML. The main advantage is that NEON has so-called [#entities], thanks to which the configuration of DI services is [so sexy |https://gist.github.com/dg/26baf3ce8f29d0f751e9dddfaa06504f]. And it allows tabs for indentation. -NEON is built from the ground up to be simple to use. +NEON is built from the ground up to be easy to use. Integration @@ -14,7 +14,7 @@ Integration - NetBeans (has built-in support) - PhpStorm ([plugin |https://plugins.jetbrains.com/plugin/7060?pr]) -- Visual Studio Code ([plugin |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) +- Visual Studio Code ([Nette Latte + Neon |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte] or [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode]) - Sublime Text 3 ([plugin |https://github.com/FilipStryk/Nette-Latte-Neon-for-Sublime-Text-3]) - Sublime Text 2 ([plugin |https://github.com/Michal-Mikolas/Nette-package-for-Sublime-Text-2]) - VIM ([plugin |https://github.com/fpob/nette.vim]) @@ -30,12 +30,12 @@ Integration Syntax ====== -A file written in NEON usually consists of a sequence or mapping. +A file written in NEON usually represents a sequence or a mapping. Mappings -------- -Mapping is a set of key-value pairs, in PHP it would be called an associative array. Each pair is written as `key: value`, a space after `:` is required. The value can be anything: string, number, boolean, null, sequence, or other mapping. +A mapping is a set of key-value pairs; in PHP, it would be called an associative array. Each pair is written as `key: value`, a space after `:` is required. The value can be anything: string, number, boolean, null, sequence, or another mapping. ```neon street: 742 Evergreen Terrace @@ -53,13 +53,13 @@ In PHP, the same structure would be written as: ] ``` -This notation is called a block notation because all items are on a separate line and have the same indentation (none in this case). NEON also supports inline representation for mapping, which is enclosed in brackets, indentation plays no role, and the separator of each element is either a comma or a newline: +This notation is called block notation because all items are on separate lines and have the same indentation (none in this case). NEON also supports an inline representation for mapping, which is enclosed in brackets, indentation plays no role, and the separator for elements is either a comma or a newline: ```neon {street: 742 Evergreen Terrace, city: Springfield, country: USA} ``` -This is the same written on multiple lines (indentation does not matter): +The same written on multiple lines (indentation does not matter): ```neon { @@ -77,7 +77,7 @@ Alternatively, `=` can be used instead of <code>: </code>, both in block and inl Sequences --------- -Sequences are indexed arrays in PHP. They are written as lines starting with the hyphen `-` followed by a space. Again, the value can be anything: string, number, boolean, null, sequence, or other mapping. +Sequences are indexed arrays in PHP. They are written as lines starting with a hyphen `-` followed by a space. Again, the value can be anything: string, number, boolean, null, sequence, or another mapping. ```neon - Cat @@ -95,13 +95,13 @@ In PHP, the same structure would be written as: ] ``` -This notation is called a block notation because all items are on a separate line and have the same indentation (none in this case). NEON also supports inline representation for sequences, which is enclosed in brackets, indentation plays no role, and the separator of each element is either a comma or a newline: +This notation is called block notation because all items are on separate lines and have the same indentation (none in this case). NEON also supports an inline representation for sequences, which is enclosed in brackets, indentation plays no role, and the separator for elements is either a comma or a newline: ```neon [Cat, Dog, Goldfish] ``` -This is the same written on multiple lines (indentation does not matter): +The same written on multiple lines (indentation does not matter): ```neon [ @@ -110,20 +110,20 @@ This is the same written on multiple lines (indentation does not matter): ] ``` -Hyphens cannot be used in an inline representation. +Hyphens (bullets) cannot be used in the inline representation. -Combination ------------ -Values of mappings and sequences may be other mappings and sequences. The level of indentation plays a major role. In the following example, the hyphen used to indicate sequence items has a greater indent than the `pets` key, so the items become the value of the first line: +Combinations +------------ +Values of mappings and sequences can be other mappings and sequences. The level of indentation plays a major role. In the following example, the hyphen used to indicate sequence items has a greater indentation than the `pets` key, so the items become the value of the first line: ```neon pets: - - Cat - - Dog + - Cat + - Dog cars: - - Volvo - - Skoda + - Volvo + - Skoda ``` In PHP, the same structure would be written as: @@ -151,7 +151,7 @@ cars: [ ] ``` -Block notation can no longer be used inside an inline notation, this does not work: +Block notation cannot be used inside an inline notation; this does not work: ```neon item: [ @@ -161,7 +161,29 @@ item: [ ] ``` -Because PHP uses the same structure for mapping and sequences, that is, arrays, both can be merged. The indentation is the same this time: +In the previous case, we wrote a mapping whose elements were sequences. Now, let's try it the other way around and create a sequence containing mappings: + +```neon +- + name: John + age: 35 +- + name: Peter + age: 28 +``` + +It's not necessary for the hyphens to be on separate lines; they can also be placed like this: + +```neon +- name: John + age: 35 +- name: Peter + age: 28 +``` + +It's up to you whether you align the keys in a column using spaces or use a tab character. + +Because PHP uses the same structure for mappings and sequences (i.e., arrays), both can be merged. The indentation is the same this time: ```neon - Cat @@ -185,34 +207,34 @@ Strings Strings in NEON can be enclosed in single or double quotes. But as you can see, they can also be without quotes. ```neon -- A unquoted string in NEON -- 'A singled-quoted string in NEON' +- An unquoted string in NEON +- 'A single-quoted string in NEON' - "A double-quoted string in NEON" ``` -If the string contains characters `# " ' , : = - [ ] { } ( )` that can be confused with NEON syntax, it must be enclosed in quotation marks. We recommend using single quotes because they do not use escaping. If you need to enclose a quotation mark in such a string, double it: +If the string contains characters `# " ' , : = - [ ] { } ( )` that could be confused with NEON syntax, it must be enclosed in quotes. We recommend using single quotes because they do not use escaping. If you need to include a quote character in such a string, double it: ```neon 'A single quote '' inside a single-quoted string' ``` -Double quotes allow you to use escape sequences to write special characters using backslashes `\`. All escape sequences as in the JSON format are supported, plus `\_`, which is an non-breaking space, ie `\u00A0`. +Double quotes allow you to use escape sequences to write special characters using backslashes `\`. All escape sequences supported by the JSON format are supported, plus `\_`, which represents a non-breaking space, i.e., `\u00A0`. ```neon - "\t \n \r \f \b \" \\ \/ \_" - "\u00A9" ``` -There are other cases where you need to enclose strings in quotation marks: -- they begin or end with spaces -- look like numbers, booleans, or null -- NEON would understand them as [#dates] +There are other cases where you need to enclose strings in quotes: +- they start or end with spaces +- they look like numbers, booleans, or null +- NEON would interpret them as [#dates] Multiline Strings ----------------- -A multiline string begins and ends with a triple quotation mark on separate lines. The indent of the first line is ignored for all lines: +A multiline string begins and ends with triple quotes on separate lines. The indentation of the first line is ignored for all lines: ```neon ''' @@ -222,13 +244,13 @@ A multiline string begins and ends with a triple quotation mark on separate line ''' ``` -In PHP we would write the same as: +In PHP, we would write the same as: ```php "first line\n\tsecond line\nthird line" // PHP ``` -Escaping sequences only work for strings enclosed in double quotes instead of apostrophes: +Escape sequences work only for strings enclosed in double quotes instead of apostrophes: ```neon """ @@ -239,22 +261,22 @@ Escaping sequences only work for strings enclosed in double quotes instead of ap Numbers ------- -NEON understands numbers written in so-called scientific notation and also numbers in binary, octal and hexadecimal: +NEON understands numbers written in scientific notation and also numbers in binary, octal, and hexadecimal bases: ```neon -- 12 # an integer -- 12.3 # a float -- +1.2e-34 # an exponential number +- 12 # integer +- 12.3 # float +- +1.2e-34 # exponential number - 0b11010 # binary number - 0o666 # octal number -- 0x7A # hexa number +- 0x7A # hexadecimal number ``` Nulls ----- -Null can be expressed in NEON by using `null` or by not specifying a value. Variants with a capital first or all uppercase letters are also allowed. +Null can be expressed in NEON using `null` or by omitting the value. Variants with a capital first letter or all uppercase letters are also allowed (`Null`, `NULL`). ```neon a: null @@ -264,7 +286,7 @@ b: Booleans -------- -Boolean values are expressed in NEON using `true` / `false` or `yes` / `no`. Variants with a capital first or all uppercase letters are also allowed. +Boolean values are expressed in NEON using `true` / `false` or `yes` / `no`. Variants with a capital first letter or all uppercase letters are also allowed (`True`, `TRUE`, `False`, `FALSE`, `Yes`, `YES`, `No`, `NO`). ```neon [true, TRUE, True, false, yes, no] @@ -273,7 +295,7 @@ Boolean values are expressed in NEON using `true` / `false` or `yes` / `no`. Var Dates ----- -NEON uses the following formats to express data and automatically converts them to `DateTimeImmutable` objects: +NEON uses the following formats to express dates and automatically converts them to `DateTimeImmutable` objects: ```neon - 2016-06-03 # date @@ -292,7 +314,7 @@ An entity is a structure that resembles a function call: Column(type: int, nulls: yes) ``` -In PHP, it is parsed as an object [api:Nette\Neon\Entity]: +In PHP, it is parsed as a [api:Nette\Neon\Entity] object: ```php // PHP @@ -315,7 +337,7 @@ new Nette\Neon\Entity(Nette\Neon\Neon::Chain, [ ]) ``` -Inside the parentheses, the rules for inline notation used for mapping and sequences apply, so it can be divided into several lines and it is not necessary to add commas: +Inside the parentheses, the rules for inline notation used for mappings and sequences apply, so it can be multiline, and commas are not necessary: ```neon Column( @@ -327,7 +349,7 @@ Column( Comments -------- -Comments start with `#` and all of the following characters on the right are ignored: +Comments start with `#` and all subsequent characters to the right are ignored: ```neon # this line will be ignored by the interpreter @@ -337,9 +359,9 @@ country: USA ``` -NEON Versus JSON +NEON versus JSON ================ -JSON is a subset of NEON. Each JSON can therefore be parsed as NEON: +JSON is a subset of NEON. Therefore, any JSON can be parsed as NEON: ```neon { @@ -350,7 +372,7 @@ JSON is a subset of NEON. Each JSON can therefore be parsed as NEON: "database": { "driver": "mysql", "username": "root", - "password": "beruska92" + "password": "password123" }, "users": [ "Dave", "Kryten", "Rimmer" @@ -358,7 +380,7 @@ JSON is a subset of NEON. Each JSON can therefore be parsed as NEON: } ``` -What if we could omit quotes? +What if we omitted the quotes? ```neon { @@ -369,7 +391,7 @@ php: { database: { driver: mysql, username: root, - password: beruska92 + password: password123 }, users: [ Dave, Kryten, Rimmer @@ -387,14 +409,14 @@ php: database: driver: mysql username: root - password: beruska92 + password: password123 users: [ Dave, Kryten, Rimmer ] ``` -Are bullets more legible? +Aren't lists with bullets more readable? ```neon php: @@ -404,7 +426,7 @@ php: database: driver: mysql username: root - password: beruska92 + password: password123 users: - Dave @@ -412,7 +434,7 @@ users: - Rimmer ``` -How about comments? +Shall we add comments? ```neon # my web application config @@ -424,7 +446,7 @@ php: database: driver: mysql username: root - password: beruska92 + password: password123 users: - Dave @@ -432,8 +454,7 @@ users: - Rimmer ``` -You found NEON syntax! +Hooray, now you know the NEON syntax! -{{description: NEON is a human-friendly data serialization language. It is similar to YAML. The main difference is that the NEON supports “entities” and tab characters for indentation.}} -{{leftbar: utils:@left-menu}} +{{description: NEON is a human-friendly data serialization language. It is similar to YAML. The main difference is that NEON supports "entities" and allows tab characters for indentation.}} diff --git a/neon/es/@home.texy b/neon/es/@home.texy index be13729413..ee418ef146 100644 --- a/neon/es/@home.texy +++ b/neon/es/@home.texy @@ -1,45 +1,45 @@ -Funciones NEON -************** +Nette NEON +********** <div class=perex> -NEON es un lenguaje de serialización de datos amigable. Se utiliza en Nette para los archivos de configuración. [api:Nette\Neon\Neon] es una clase estática para trabajar con NEON. +NEON es un lenguaje legible por humanos para la serialización de datos. Se utiliza en Nette para archivos de configuración. [api:Nette\Neon\Neon] es una clase estática para trabajar con NEON. -Conozca el [formato NEON |format] y [pruébelo |https://ne-on.org]. +Familiarícese con el [formato NEON|format] y [pruébelo |https://ne-on.org]. </div> -Los siguientes ejemplos utilizan estos alias: +Todos los ejemplos asumen que se ha creado un alias: ```php use Nette\Neon\Neon; ``` -Instalación .[#toc-installation] --------------------------------- +Instalación +----------- -Descargue e instale el paquete utilizando [Composer |best-practices:composer]: +Descargue e instale la biblioteca usando [Composer|best-practices:composer]: ```shell composer require nette/neon ``` -Puede comprobar si hay errores de sintaxis en los archivos `*.neon` utilizando el comando de consola `neon-lint`: +Puede verificar los errores de sintaxis en los archivos `*.neon` usando el comando de consola `neon-lint`: ```shell -vendor/bin/neon-lint <path> +vendor/bin/neon-lint <ruta> ``` -encode(mixed $value, bool $blockMode=false): string .[method] -------------------------------------------------------------- +encode(mixed $value, bool $blockMode=false, string $indentation="\t"): string .[method] +--------------------------------------------------------------------------------------- -Devuelve `$value` convertido a NEON. Como parámetro `$blockMode` puede pasar true, que creará una salida multilínea. El parámetro `$indentation` especifica los caracteres utilizados para la sangría (por defecto es tabulador). +Devuelve `$value` convertido a NEON. Como parámetro `$blockMode`, puede pasar true, lo que creará una salida multilínea. El parámetro `$indentation` especifica los caracteres utilizados para la indentación (el valor predeterminado es tabulador). ```php -Neon::encode($value); // Returns $value converted to NEON -Neon::encode($value, true); // Returns $value converted to multiline NEON +Neon::encode($value); // Devuelve $value convertido a NEON +Neon::encode($value, true); // Devuelve $value convertido a NEON multilínea ``` El método `encode()` lanza `Nette\Neon\Exception` en caso de error. @@ -48,7 +48,7 @@ El método `encode()` lanza `Nette\Neon\Exception` en caso de error. try { $neon = Neon::encode($value); } catch (Nette\Neon\Exception $e) { - // Exception handling + // manejo de excepciones } ``` @@ -56,12 +56,12 @@ try { decode(string $neon): mixed .[method] ------------------------------------- -Convierte el NEON dado a un valor PHP. +Convierte una cadena de NEON a PHP. -Devuelve escalares, matrices, [fecha |format#dates] como objetos DateTimeImmutable y [entidades |format#Entities] como objetos [api:Nette\Neon\Entity]. +Devuelve escalares, arrays, [fechas |format#Fecha] como objetos DateTimeImmutable y [entidades |format#Entidades] como objetos [api:Nette\Neon\Entity]. ```php -Neon::decode('hello: world'); // Returns an array ['hello' => 'world'] +Neon::decode('hello: world'); // Devuelve el array ['hello' => 'world'] ``` El método `decode()` lanza `Nette\Neon\Exception` en caso de error. @@ -70,7 +70,7 @@ El método `decode()` lanza `Nette\Neon\Exception` en caso de error. try { $value = Neon::decode($neon); } catch (Nette\Neon\Exception $e) { - // Exception handling + // manejo de excepciones } ``` @@ -78,13 +78,10 @@ try { decodeFile(string $file): mixed .[method] ----------------------------------------- -Convierte el contenido del fichero de NEON a PHP y elimina cualquier BOM. +Convierte el contenido de un archivo de NEON a PHP y elimina cualquier BOM. ```php Neon::decodeFile('config.neon'); ``` El método `decodeFile()` lanza `Nette\Neon\Exception` en caso de error. - - -{{leftbar: utils:@left-menu}} diff --git a/neon/es/@meta.texy b/neon/es/@meta.texy new file mode 100644 index 0000000000..016d7efddc --- /dev/null +++ b/neon/es/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Documentación}} +{{leftbar: utils:@left-menu}} diff --git a/neon/es/format.texy b/neon/es/format.texy index 19212e130a..8503a12140 100644 --- a/neon/es/format.texy +++ b/neon/es/format.texy @@ -2,40 +2,40 @@ Formato NEON ************ .[perex] -NEON es un formato de datos estructurados legibles por humanos. En Nette, se utiliza para archivos de configuración. También se utiliza para datos estructurados como configuraciones, traducciones de idiomas, etc. [Pruébalo en el sandbox |https://ne-on.org]. +NEON es un formato de datos estructurados legible por humanos. En Nette se utiliza para archivos de configuración. También se utiliza para datos estructurados, como configuraciones, traducciones de idiomas, etc. [Pruébelo|https://ne-on.org]. -NEON son las siglas de *Nette Object Notation*. Es menos complejo y desgarbado que XML o JSON, pero proporciona capacidades similares. Es muy similar a YAML. La principal ventaja es que NEON tiene las llamadas [entidades |#entities], gracias a las cuales la configuración de los servicios DI es tan sexy. Y permite tabuladores para la indentación. +NEON es la abreviatura de *Nette Object Notation*. Es menos complejo y torpe que XML o JSON, pero proporciona funciones similares. Es muy similar a YAML. La principal ventaja es que NEON tiene las llamadas [#entidades], gracias a las cuales la configuración de los servicios DI es [tan sexy |https://gist.github.com/dg/26baf3ce8f29d0f751e9dddfaa06504f]. Y permite la indentación con tabuladores. NEON está construido desde cero para ser fácil de usar. -Integración .[#toc-integration] -=============================== +Integración +=========== -- NetBeans (tiene soporte integrado) -- PhpStorm ([complemento |https://plugins.jetbrains.com/plugin/7060?pr]) -- Visual Studio Code ([complemento |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) -- Sublime Text 3 ([complemento |https://github.com/FilipStryk/Nette-Latte-Neon-for-Sublime-Text-3]) -- Sublime Text 2 ([complemento |https://github.com/Michal-Mikolas/Nette-package-for-Sublime-Text-2]) -- VIM ([complemento |https://github.com/fpob/nette.vim]) -- Emacs ([complemento |https://github.com/Fuco1/neon-mode]) +- NetBeans (tiene soporte incorporado) +- PhpStorm ([plugin |https://plugins.jetbrains.com/plugin/7060?pr]) +- Visual Studio Code ([Nette Latte + Neon |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) o [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode]) +- Sublime Text 3 ([plugin |https://github.com/FilipStryk/Nette-Latte-Neon-for-Sublime-Text-3]) +- Sublime Text 2 ([plugin |https://github.com/Michal-Mikolas/Nette-package-for-Sublime-Text-2]) +- VIM ([plugin |https://github.com/fpob/nette.vim]) +- Emacs ([plugin |https://github.com/Fuco1/neon-mode]) - Prism.js ([lenguaje integrado |https://prismjs.com/#supported-languages]) -- [NEON para PHP|@home] -- [NEON para JavaScript|https://github.com/matej21/neon-js] -- [NEON para Python |https://github.com/paveldedik/neon-py] +- [NEON for PHP |@home] +- [NEON for JavaScript |https://github.com/matej21/neon-js] +- [NEON for Python |https://github.com/paveldedik/neon-py]. -Sintaxis .[#toc-syntax] -======================= +Sintaxis +======== -Un archivo escrito en NEON consiste normalmente en una secuencia o mapeo. +Un archivo escrito en NEON generalmente representa un array o un mapeo. -Asignaciones .[#toc-mappings] ------------------------------ -El mapeo es un conjunto de pares clave-valor, en PHP se llamaría una matriz asociativa. Cada par se escribe como `key: value`, se requiere un espacio después de `:`. El valor puede ser cualquier cosa: cadena, número, booleano, null, secuencia u otro mapeo. +Mapeo +----- +Un mapeo es un conjunto de pares clave-valor, en PHP se diría un array asociativo. Cada par se escribe como `key: value`, el espacio después de `:` es necesario. El valor puede ser cualquier cosa: cadena, número, booleano, null, secuencia u otro mapeo. ```neon street: 742 Evergreen Terrace @@ -53,13 +53,13 @@ En PHP, la misma estructura se escribiría como: ] ``` -Esta notación se llama notación de bloque porque todos los elementos están en una línea separada y tienen la misma sangría (ninguna en este caso). NEON también admite la representación en línea para la asignación, que se encierra entre corchetes, la sangría no juega ningún papel, y el separador de cada elemento es una coma o una nueva línea: +Esta notación se llama de bloque, porque todos los elementos están en una línea separada y tienen la misma indentación (en este caso, ninguna). NEON también admite una representación en línea para mapeos, que está encerrada entre llaves, la indentación no juega ningún papel y el separador de elementos individuales es una coma o una nueva línea: ```neon {street: 742 Evergreen Terrace, city: Springfield, country: USA} ``` -Esto es lo mismo escrito en varias líneas (la sangría no importa): +Lo mismo escrito en varias líneas (la indentación no importa): ```neon { @@ -68,16 +68,16 @@ Esto es lo mismo escrito en varias líneas (la sangría no importa): } ``` -Como alternativa, puede utilizarse `=` en lugar de <code>: </code> tanto en la notación en bloque como en la notación en línea: +En lugar de <code>: </code> también se puede usar `=` alternativamente, tanto en la notación de bloque como en línea: ```neon {street=742 Evergreen Terrace, city=Springfield, country=USA} ``` -Secuencias .[#toc-sequences] ----------------------------- -Las secuencias son matrices indexadas en PHP. Se escriben como líneas que comienzan con el guión `-` seguido de un espacio. De nuevo, el valor puede ser cualquier cosa: cadena, número, booleano, null, secuencia, u otro mapeo. +Secuencia +--------- +Las secuencias son arrays indexados en PHP. Se escriben como líneas que comienzan con un guión `-` seguido de un espacio. El valor nuevamente puede ser cualquier cosa: cadena, número, booleano, null, secuencia u otro mapeo. ```neon - Cat @@ -95,13 +95,13 @@ En PHP, la misma estructura se escribiría como: ] ``` -Esta notación se llama notación de bloque porque todos los elementos están en una línea separada y tienen la misma sangría (ninguna en este caso). NEON también soporta la representación en línea para secuencias, que se encierran entre corchetes, la sangría no juega ningún papel, y el separador de cada elemento es una coma o una nueva línea: +Esta notación se llama de bloque, porque todos los elementos están en una línea separada y tienen la misma indentación (en este caso, ninguna). NEON también admite una representación en línea para secuencias, que está encerrada entre corchetes, la indentación no juega ningún papel y el separador de elementos individuales es una coma o una nueva línea: ```neon [Cat, Dog, Goldfish] ``` -Esto es lo mismo escrito en varias líneas (la sangría no importa): +Lo mismo escrito en varias líneas (la indentación no importa): ```neon [ @@ -110,20 +110,20 @@ Esto es lo mismo escrito en varias líneas (la sangría no importa): ] ``` -Los guiones no pueden utilizarse en una representación en línea. +En la representación en línea no se pueden usar guiones de indentación. -Combinación .[#toc-combination] -------------------------------- -Los valores de las correspondencias y secuencias pueden ser otras correspondencias y secuencias. El nivel de sangría desempeña un papel importante. En el siguiente ejemplo, el guión utilizado para indicar los elementos de la secuencia tiene una sangría mayor que la tecla `pets`, por lo que los elementos se convierten en el valor de la primera línea: +Combinaciones +------------- +Los valores de mapeos y secuencias pueden ser otros mapeos y secuencias. El nivel de indentación juega un papel principal. En el siguiente ejemplo, el guión utilizado para indicar los elementos de la secuencia tiene una indentación mayor que la clave `pets`, por lo que los elementos se convierten en el valor de la primera línea: ```neon pets: - - Cat - - Dog + - Cat + - Dog cars: - - Volvo - - Skoda + - Volvo + - Skoda ``` En PHP, la misma estructura se escribiría como: @@ -141,7 +141,7 @@ En PHP, la misma estructura se escribiría como: ] ``` -Es posible combinar la notación en bloque y en línea: +Se puede combinar la notación de bloque y en línea: ```neon pets: [Cat, Dog] @@ -151,17 +151,39 @@ cars: [ ] ``` -La notación en bloque ya no puede utilizarse dentro de una notación en línea, esto no funciona: +Dentro de la notación en línea ya no se puede usar la notación de bloque, esto no funciona: ```neon item: [ pets: - - Cat # THIS IS NOT POSSIBLE!!! + - Cat # ¡¡¡ESTO NO SE PUEDE!!! - Dog ] ``` -Debido a que PHP utiliza la misma estructura para mapeo y secuencias, es decir, arrays, ambos pueden ser fusionados. La indentación es la misma esta vez: +En el caso anterior, escribimos un mapeo cuyos elementos eran secuencias, ahora intentaremos lo contrario y crearemos una secuencia que contenga mapeos: + +```neon +- + name: John + age: 35 +- + name: Peter + age: 28 +``` + +No es necesario que los guiones estén en líneas separadas, también se pueden colocar de esta manera: + +```neon +- name: John + age: 35 +- name: Peter + age: 28 +``` + +Depende de usted si alinea las claves en una columna usando espacios o usa un tabulador. + +Dado que en PHP se utiliza la misma estructura para mapeos y secuencias, es decir, arrays, se pueden fusionar ambos. La indentación esta vez es la misma: ```neon - Cat @@ -180,23 +202,23 @@ En PHP, la misma estructura se escribiría como: ``` -Cadenas .[#toc-strings] ------------------------ -Las cadenas en NEON pueden ir entre comillas simples o dobles. Pero como puede ver, también pueden ir sin comillas. +Cadenas +------- +Las cadenas en NEON se pueden encerrar entre comillas simples o dobles. Pero como puede ver, también pueden estar sin comillas. ```neon -- A unquoted string in NEON -- 'A singled-quoted string in NEON' -- "A double-quoted string in NEON" +- Cadena en NEON sin comillas +- 'Cadena en NEON en comillas simples' +- "Cadena en NEON en comillas dobles" ``` -Si la cadena contiene caracteres `# " ' , : = - [ ] { } ( )` que puedan confundirse con la sintaxis NEON, debe ir entre comillas. Recomendamos utilizar comillas simples porque no utilizan escapes. Si necesita encerrar una comilla en una cadena de este tipo, duplíquela: +Si la cadena contiene los caracteres `# " ' , : = - [ ] { } ( )`, que pueden confundirse con la sintaxis NEON, debe encerrarse entre comillas. Recomendamos usar comillas simples, ya que en ellas no se usa el escapado. Si necesita escribir una comilla en dicha cadena, duplíquela: ```neon -'A single quote '' inside a single-quoted string' +'Comilla '' dentro de una cadena en comillas simples' ``` -Las comillas dobles le permiten utilizar secuencias de escape para escribir caracteres especiales mediante barras invertidas `\`. All escape sequences as in the JSON format are supported, plus `\_`, que es un espacio que no se rompe, es decir, `\u00A0`. +Las comillas dobles permiten usar secuencias de escape para escribir caracteres especiales usando barras invertidas `\`. Se admiten todas las secuencias de escape como en el formato JSON y además `\_`, que es un espacio indivisible, es decir, `\u00A0`. ```neon - "\t \n \r \f \b \" \\ \/ \_" @@ -204,31 +226,31 @@ Las comillas dobles le permiten utilizar secuencias de escape para escribir cara ``` Hay otros casos en los que es necesario encerrar las cadenas entre comillas: -- empiezan o acaban con espacios -- parecen números, booleanos o nulos -- NEON las entendería como [fechas |#dates] +- comienzan o terminan con espacios +- parecen números, booleanos o null +- NEON las entendería como [#fecha] -Cadenas multilínea .[#toc-multiline-strings] --------------------------------------------- +Cadenas multilínea +------------------ -Una cadena multilínea comienza y termina con una triple comilla en líneas separadas. La sangría de la primera línea se ignora para todas las líneas: +Una cadena multilínea comienza y termina con comillas triples en líneas separadas. La indentación de la primera línea se ignora para todas las líneas: ```neon ''' - first line - second line - third line + primera línea + segunda línea + tercera línea ''' ``` -En PHP escribiríamos lo mismo que: +En PHP escribiríamos lo mismo como: ```php -"first line\n\tsecond line\nthird line" // PHP +"primera línea\n\tsegunda línea\ntercera línea" // PHP ``` -Las secuencias de escape sólo funcionan para cadenas encerradas entre comillas dobles en lugar de apóstrofes: +Las secuencias de escape solo funcionan en cadenas encerradas entre comillas dobles en lugar de apóstrofes: ```neon """ @@ -237,24 +259,24 @@ Las secuencias de escape sólo funcionan para cadenas encerradas entre comillas ``` -Números .[#toc-numbers] ------------------------ -NEON entiende los números escritos en la llamada notación científica y también los números en binario, octal y hexadecimal: +Números +------- +NEON entiende los números escritos en la llamada notación científica y también los números en sistemas binario, octal y hexadecimal: ```neon -- 12 # an integer -- 12.3 # a float -- +1.2e-34 # an exponential number +- 12 # entero +- 12.3 # float +- +1.2e-34 # número exponencial -- 0b11010 # binary number -- 0o666 # octal number -- 0x7A # hexa number +- 0b11010 # número binario +- 0o666 # número octal +- 0x7A # número hexadecimal ``` -Nulos .[#toc-nulls] -------------------- -Los nulos pueden expresarse en NEON utilizando `null` o no especificando un valor. También se permiten variantes con la primera mayúscula o todo en mayúsculas. +Nulos +----- +Null se puede expresar en NEON usando `null` o no especificando un valor. También se permiten variantes con la primera letra en mayúscula o todas las letras en mayúscula. ```neon a: null @@ -262,50 +284,50 @@ b: ``` -Booleanos .[#toc-booleans] --------------------------- -Los valores booleanos se expresan en NEON utilizando `true` / `false` o `yes` / `no`. También se admiten variantes con la primera mayúscula o todo en mayúsculas. +Booleanos +--------- +Los valores lógicos se expresan en NEON usando `true` / `false` o `yes` / `no`. También se permiten variantes con la primera letra en mayúscula o todas las letras en mayúscula. ```neon [true, TRUE, True, false, yes, no] ``` -Fechas .[#toc-dates] --------------------- -NEON utiliza los siguientes formatos para expresar datos y los convierte automáticamente en objetos de `DateTimeImmutable`: +Fecha +----- +NEON utiliza los siguientes formatos para expresar fechas y los convierte automáticamente en objetos `DateTimeImmutable`: ```neon -- 2016-06-03 # date -- 2016-06-03 19:00:00 # date & time -- 2016-06-03 19:00:00.1234 # date & microtime -- 2016-06-03 19:00:00 +0200 # date & time & timezone -- 2016-06-03 19:00:00 +02:00 # date & time & timezone +- 2016-06-03 # fecha +- 2016-06-03 19:00:00 # fecha y hora +- 2016-06-03 19:00:00.1234 # fecha y microsegundos +- 2016-06-03 19:00:00 +0200 # fecha y hora y zona +- 2016-06-03 19:00:00 +02:00 # fecha y hora y zona ``` -Entidades .[#toc-entities] --------------------------- -Una entidad es una estructura que se asemeja a una llamada de función: +Entidades +--------- +Una entidad es una estructura que se asemeja a una llamada a función: ```neon Column(type: int, nulls: yes) ``` -En PHP, se interpreta como un objeto [api:Nette\Neon\Entity]: +En PHP se analiza como un objeto [api:Nette\Neon\Entity]: ```php // PHP new Nette\Neon\Entity('Column', ['type' => 'int', 'nulls' => true]) ``` -Las entidades también pueden encadenarse: +Las entidades también se pueden encadenar: ```neon Column(type: int, nulls: yes) Field(id: 1) ``` -Que se analiza en PHP de la siguiente manera: +Lo cual se analiza en PHP de esta manera: ```php // PHP @@ -315,7 +337,7 @@ new Nette\Neon\Entity(Nette\Neon\Neon::Chain, [ ]) ``` -Dentro de los paréntesis, se aplican las reglas de notación inline utilizadas para mapeo y secuencias, por lo que se puede dividir en varias líneas y no es necesario añadir comas: +Dentro de los paréntesis se aplican las reglas para la notación en línea utilizada para mapeos y secuencias, por lo que también puede ser multilínea y entonces no es necesario indicar comas: ```neon Column( @@ -325,21 +347,21 @@ Column( ``` -Comentarios .[#toc-comments] ----------------------------- -Los comentarios empiezan por `#` y se ignoran todos los caracteres siguientes a la derecha: +Comentarios +----------- +Los comentarios comienzan con el carácter `#` y todos los caracteres siguientes a la derecha son ignorados: ```neon -# this line will be ignored by the interpreter +# esta línea será ignorada por el intérprete street: 742 Evergreen Terrace -city: Springfield # this is ignored too +city: Springfield # esto también se ignora country: USA ``` -NEON frente a JSON .[#toc-neon-versus-json] -=========================================== -JSON es un subconjunto de NEON. Por lo tanto, cada JSON puede ser analizado como NEON: +Neon versus JSON +================ +JSON es un subconjunto de NEON. Por lo tanto, cada JSON se puede analizar como NEON: ```neon { @@ -358,7 +380,7 @@ JSON es un subconjunto de NEON. Por lo tanto, cada JSON puede ser analizado como } ``` -¿Y si pudiéramos omitir las comillas? +¿Qué pasaría si omitiéramos las comillas? ```neon { @@ -394,7 +416,7 @@ users: [ ] ``` -¿Son más legibles las viñetas? +¿No son las listas con guiones más legibles? ```neon php: @@ -412,14 +434,14 @@ users: - Rimmer ``` -¿Y los comentarios? +¿Añadimos comentarios? ```neon -# my web application config +# configuración de mi aplicación web php: date.timezone: Europe/Prague - zlib.output_compression: true # use gzip + zlib.output_compression: true # usar gzip database: driver: mysql @@ -432,8 +454,7 @@ users: - Rimmer ``` -¡Has encontrado la sintaxis NEON! +¡Hurra, ahora conoces la sintaxis de NEON! -{{description: NEON es un lenguaje de serialización de datos fácil de usar. Es similar a YAML. La principal diferencia es que NEON admite "entidades" y caracteres de tabulación para la sangría.}} -{{leftbar: utils:@left-menu}} +{{description: NEON es un formato fácil de leer para la serialización de datos. Es similar a YAML. La principal diferencia es que NEON admite "entidades" y podemos usar tanto espacios como tabuladores para la indentación.}} diff --git a/neon/fr/@home.texy b/neon/fr/@home.texy index 8813535de9..280177a817 100644 --- a/neon/fr/@home.texy +++ b/neon/fr/@home.texy @@ -1,54 +1,54 @@ -Fonctions NEON -************** +Nette NEON +********** <div class=perex> -NEON est un langage de sérialisation de données convivial. Il est utilisé dans Nette pour les fichiers de configuration. [api:Nette\Neon\Neon] est une classe statique pour travailler avec NEON. +NEON est un langage de sérialisation de données lisible par l'homme. Il est utilisé dans Nette pour les fichiers de configuration. [api:Nette\Neon\Neon] est une classe statique pour travailler avec NEON. -Apprenez à connaître le [format NEON |format] et [essayez-le |https://ne-on.org]. +Familiarisez-vous avec le [format NEON|format] et [essayez-le |https://ne-on.org]. </div> -Les exemples suivants utilisent ces alias : +Tous les exemples supposent la création d'un alias : ```php use Nette\Neon\Neon; ``` -Installation .[#toc-installation] ---------------------------------- +Installation +------------ -Téléchargez et installez le paquet en utilisant [Composer |best-practices:composer]: +Vous pouvez télécharger et installer la bibliothèque à l'aide de [Composer|best-practices:composer] : ```shell composer require nette/neon ``` -Vous pouvez vérifier la présence d'erreurs de syntaxe dans les fichiers `*.neon` à l'aide de la commande console `neon-lint`: +Vous pouvez vérifier les erreurs de syntaxe dans les fichiers `*.neon` à l'aide de la commande console `neon-lint` : ```shell -vendor/bin/neon-lint <path> +vendor/bin/neon-lint <chemin> ``` -encode(mixed $value, bool $blockMode=false): string .[method] -------------------------------------------------------------- +encode(mixed $value, bool $blockMode=false, string $indentation="\t"): string .[method] +--------------------------------------------------------------------------------------- -Renvoie `$value` converti en NEON. Comme paramètre `$blockMode` vous pouvez passer true, ce qui créera une sortie multiligne. Le paramètre `$indentation` spécifie les caractères utilisés pour l'indentation (la tabulation par défaut). +Retourne `$value` convertie en NEON. Comme paramètre `$blockMode`, vous pouvez passer `true`, ce qui créera une sortie multiligne. Le paramètre `$indentation` spécifie les caractères utilisés pour l'indentation (la valeur par défaut est une tabulation). ```php -Neon::encode($value); // Retourne $value converti en NEON -Neon::encode($value, true); // Retourne $value converti en NEON multiligne +Neon::encode($value); // Retourne $value convertie en NEON +Neon::encode($value, true); // Retourne $value convertie en NEON multiligne ``` -La méthode `encode()` lance `Nette\Neon\Exception` en cas d'erreur. +La méthode `encode()` lève une `Nette\Neon\Exception` en cas d'erreur. ```php try { $neon = Neon::encode($value); } catch (Nette\Neon\Exception $e) { - // Gestion des exceptions + // traitement de l'exception } ``` @@ -56,21 +56,21 @@ try { decode(string $neon): mixed .[method] ------------------------------------- -Convertit le NEON donné en valeur PHP. +Convertit une chaîne NEON en PHP. -Renvoie des scalaires, des tableaux, des [dates |format#dates] sous forme d'objets DateTimeImmutable, et des [entités |format#Entities] sous forme d'objets [api:Nette\Neon\Entity]. +Retourne des scalaires, des tableaux, des [dates |format#Date] comme objets `DateTimeImmutable` et des [entités |format#Entités] comme objets [api:Nette\Neon\Entity]. ```php -Neon::decode('hello: world'); // Retourne un tableau ['hello' => 'world']. +Neon::decode('hello: world'); // Retourne le tableau ['hello' => 'world'] ``` -La méthode `decode()` renvoie `Nette\Neon\Exception` en cas d'erreur. +La méthode `decode()` lève une `Nette\Neon\Exception` en cas d'erreur. ```php try { $value = Neon::decode($neon); } catch (Nette\Neon\Exception $e) { - // Gestion des exceptions + // traitement de l'exception } ``` @@ -78,13 +78,10 @@ try { decodeFile(string $file): mixed .[method] ----------------------------------------- -Convertit le contenu du fichier de NEON en PHP et supprime toute nomenclature. +Convertit le contenu d'un fichier NEON en PHP et supprime un éventuel BOM. ```php Neon::decodeFile('config.neon'); ``` -La méthode `decodeFile()` renvoie `Nette\Neon\Exception` en cas d'erreur. - - -{{leftbar: utils:@left-menu}} +La méthode `decodeFile()` lève une `Nette\Neon\Exception` en cas d'erreur. diff --git a/neon/fr/@meta.texy b/neon/fr/@meta.texy new file mode 100644 index 0000000000..5515cce921 --- /dev/null +++ b/neon/fr/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Documentation Nette}} +{{leftbar: utils:@left-menu}} diff --git a/neon/fr/format.texy b/neon/fr/format.texy index 7612b621a1..87c410844f 100644 --- a/neon/fr/format.texy +++ b/neon/fr/format.texy @@ -2,19 +2,19 @@ Format NEON *********** .[perex] -NEON est un format de données structuré lisible par l'homme. Dans Nette, il est utilisé pour les fichiers de configuration. Il est également utilisé pour les données structurées telles que les paramètres, les traductions linguistiques, etc. [Essayez-le dans le bac à sable |https://ne-on.org]. +NEON est un format de données structurées lisible par l'homme. Dans Nette, il est utilisé pour les fichiers de configuration. Il est également utilisé pour les données structurées, telles que les paramètres, les traductions linguistiques, etc. [Essayez-le|https://ne-on.org]. -NEON est l'abréviation de *Nette Object Notation*. Ce langage est moins complexe et moins disgracieux que XML ou JSON, mais offre des possibilités similaires. Il est très similaire à YAML. Le principal avantage est que NEON possède ce qu'on appelle des [entités |#entities], grâce auxquelles la configuration des services DI est si sexy. Et il autorise les tabulations pour l'indentation. +NEON est l'acronyme de *Nette Object Notation*. Il est moins complexe et lourd que XML ou JSON, mais offre des fonctionnalités similaires. Il est très similaire à YAML. Le principal avantage est que NEON possède ce qu'on appelle des [#entités], grâce auxquelles la configuration des services DI est [aussi sexy |https://gist.github.com/dg/26baf3ce8f29d0f751e9dddfaa06504f]. Et il permet d'indenter avec des tabulations. -NEON a été conçu dès le départ pour être simple à utiliser. +NEON est construit dès le départ pour être facile à utiliser. -Intégration .[#toc-integration] -=============================== +Intégration +=========== -- NetBeans (a un support intégré) +- NetBeans (support intégré) - PhpStorm ([plugin |https://plugins.jetbrains.com/plugin/7060?pr]) -- Visual Studio Code ([plugin |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) +- Visual Studio Code ([Nette Latte + Neon |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) ou [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode]) - Sublime Text 3 ([plugin |https://github.com/FilipStryk/Nette-Latte-Neon-for-Sublime-Text-3]) - Sublime Text 2 ([plugin |https://github.com/Michal-Mikolas/Nette-package-for-Sublime-Text-2]) - VIM ([plugin |https://github.com/fpob/nette.vim]) @@ -27,15 +27,15 @@ Intégration .[#toc-integration] - [NEON pour Python |https://github.com/paveldedik/neon-py]. -Syntaxe .[#toc-syntax] -====================== +Syntaxe +======= -Un fichier écrit en NEON se compose généralement d'une séquence ou d'un mappage. +Un fichier écrit en NEON représente généralement un tableau ou un mapping. -Mappages .[#toc-mappings] -------------------------- -Le mappage est un ensemble de paires clé-valeur, en PHP on l'appellerait un tableau associatif. Chaque paire est écrite comme `key: value`, un espace après `:` est nécessaire. La valeur peut être n'importe quoi : chaîne de caractères, nombre, booléen, null, séquence, ou tout autre mappage. +Mapping +------- +Un mapping est un ensemble de paires clé-valeur, en PHP on dirait un tableau associatif. Chaque paire est écrite comme `key: value`, l'espace après `:` est nécessaire. La valeur peut être n'importe quoi : chaîne, nombre, booléen, null, séquence ou autre mapping. ```neon street: 742 Evergreen Terrace @@ -43,7 +43,7 @@ city: Springfield country: USA ``` -En PHP, la même structure serait écrite comme suit : +En PHP, la même structure serait écrite comme : ```php [ // PHP @@ -53,13 +53,13 @@ En PHP, la même structure serait écrite comme suit : ] ``` -Cette notation est appelée notation en bloc car tous les éléments sont sur une ligne séparée et ont la même indentation (aucune dans ce cas). NEON prend également en charge la représentation en ligne pour la cartographie, qui est placée entre parenthèses, l'indentation ne joue aucun rôle, et le séparateur de chaque élément est soit une virgule, soit un saut de ligne : +Cette notation est appelée bloc, car tous les éléments sont sur une ligne distincte et ont la même indentation (dans ce cas, aucune). NEON prend également en charge une représentation en ligne pour le mapping, qui est enfermée entre accolades `{}`, l'indentation ne joue aucun rôle et le séparateur des éléments individuels est soit une virgule, soit un saut de ligne : ```neon {street: 742 Evergreen Terrace, city: Springfield, country: USA} ``` -Il s'agit de la même chose écrite sur plusieurs lignes (l'indentation ne joue aucun rôle) : +La même chose écrite sur plusieurs lignes (l'indentation n'a pas d'importance) : ```neon { @@ -68,16 +68,16 @@ Il s'agit de la même chose écrite sur plusieurs lignes (l'indentation ne joue } ``` -Alternativement, `=` peut être utilisé à la place de <code>: </code> dans la notation en bloc et en ligne : +Au lieu de <code>: </code>, on peut alternativement utiliser `=` et ce, aussi bien en notation bloc qu'en ligne : ```neon {street=742 Evergreen Terrace, city=Springfield, country=USA} ``` -Séquences .[#toc-sequences] ---------------------------- -Les séquences sont des tableaux indexés en PHP. Elles sont écrites comme des lignes commençant par le trait d'union `-` suivi d'un espace. Encore une fois, la valeur peut être n'importe quoi : chaîne de caractères, nombre, booléen, null, séquence, ou autre correspondance. +Séquence +-------- +Les séquences sont des tableaux indexés en PHP. Elles sont écrites comme des lignes commençant par un tiret `-` suivi d'un espace. La valeur peut à nouveau être n'importe quoi : chaîne, nombre, booléen, null, séquence ou autre mapping. ```neon - Cat @@ -85,7 +85,7 @@ Les séquences sont des tableaux indexés en PHP. Elles sont écrites comme des - Goldfish ``` -En PHP, la même structure serait écrite comme suit : +En PHP, la même structure serait écrite comme : ```php [ // PHP @@ -95,13 +95,13 @@ En PHP, la même structure serait écrite comme suit : ] ``` -Cette notation est appelée notation en bloc car tous les éléments sont sur une ligne séparée et ont la même indentation (aucune dans ce cas). NEON prend également en charge la représentation en ligne des séquences, qui sont placées entre parenthèses, l'indentation ne joue aucun rôle, et le séparateur de chaque élément est soit une virgule, soit un saut de ligne : +Cette notation est appelée bloc, car tous les éléments sont sur une ligne distincte et ont la même indentation (dans ce cas, aucune). NEON prend également en charge une représentation en ligne pour la séquence, qui est enfermée entre crochets `[]`, l'indentation ne joue aucun rôle et le séparateur des éléments individuels est soit une virgule, soit un saut de ligne : ```neon [Cat, Dog, Goldfish] ``` -Il s'agit de la même chose écrite sur plusieurs lignes (l'indentation ne joue aucun rôle) : +La même chose écrite sur plusieurs lignes (l'indentation n'a pas d'importance) : ```neon [ @@ -110,23 +110,23 @@ Il s'agit de la même chose écrite sur plusieurs lignes (l'indentation ne joue ] ``` -Les traits d'union ne peuvent pas être utilisés dans une représentation en ligne. +Dans la représentation en ligne, on ne peut pas utiliser les tirets d'indentation. -Combinaison .[#toc-combination] -------------------------------- -Les valeurs des mappings et des séquences peuvent être d'autres mappings et séquences. Le niveau d'indentation joue un rôle important. Dans l'exemple suivant, le trait d'union utilisé pour indiquer les éléments de la séquence a une indentation supérieure à celle de la touche `pets`, de sorte que les éléments deviennent la valeur de la première ligne : +Combinaisons +------------ +Les valeurs des mappings et des séquences peuvent être d'autres mappings et séquences. Le niveau d'indentation joue un rôle principal. Dans l'exemple suivant, le tiret utilisé pour marquer les éléments de la séquence a une indentation plus grande que la clé `pets`, de sorte que les éléments deviennent la valeur de la première ligne : ```neon pets: - - Cat - - Dog + - Cat + - Dog cars: - - Volvo - - Skoda + - Volvo + - Skoda ``` -En PHP, la même structure s'écrirait comme suit : +En PHP, la même structure serait écrite comme : ```php [ // PHP @@ -141,7 +141,7 @@ En PHP, la même structure s'écrirait comme suit : ] ``` -Il est possible de combiner la notation en bloc et en ligne : +Il est possible de combiner la notation bloc et en ligne : ```neon pets: [Cat, Dog] @@ -151,17 +151,39 @@ cars: [ ] ``` -La notation en bloc ne peut plus être utilisée à l'intérieur d'une notation en ligne, cela ne fonctionne pas : +À l'intérieur de la notation en ligne, on ne peut plus utiliser la notation bloc, ceci ne fonctionne pas : ```neon item: [ pets: - - Cat # THIS IS NOT POSSIBLE!!! + - Cat # CECI N'EST PAS POSSIBLE !!! - Dog ] ``` -Parce que PHP utilise la même structure pour le mapping et les séquences, c'est-à-dire les tableaux, les deux peuvent être fusionnés. L'indentation est la même cette fois : +Dans le cas précédent, nous avons écrit un mapping dont les éléments étaient des séquences, essayons maintenant l'inverse et créons une séquence contenant des mappings : + +```neon +- + name: John + age: 35 +- + name: Peter + age: 28 +``` + +Il n'est pas nécessaire que les tirets soient sur des lignes distinctes, on peut aussi les placer de cette manière : + +```neon +- name: John + age: 35 +- name: Peter + age: 28 +``` + +C'est à vous de décider si vous alignez les clés en colonne à l'aide d'espaces ou si vous utilisez une tabulation. + +Comme PHP utilise la même structure pour les mappings et les séquences, c'est-à-dire les tableaux, les deux peuvent être fusionnés. L'indentation est cette fois la même : ```neon - Cat @@ -169,7 +191,7 @@ street: 742 Evergreen Terrace - Goldfish ``` -En PHP, la même structure s'écrirait comme suit : +En PHP, la même structure serait écrite comme : ```php [ // PHP @@ -180,55 +202,55 @@ En PHP, la même structure s'écrirait comme suit : ``` -Chaînes de caractères .[#toc-strings] -------------------------------------- -Les chaînes de caractères dans NEON peuvent être entourées de guillemets simples ou doubles. Mais comme vous pouvez le voir, elles peuvent aussi être sans guillemets. +Chaînes de caractères +--------------------- +Les chaînes en NEON peuvent être enfermées entre guillemets simples ou doubles. Mais comme vous pouvez le voir, elles peuvent aussi être sans guillemets. ```neon -- A unquoted string in NEON -- 'A singled-quoted string in NEON' -- "A double-quoted string in NEON" +- Chaîne en NEON sans guillemets +- 'Chaîne en NEON entre guillemets simples' +- "Chaîne en NEON entre guillemets doubles" ``` -Si la chaîne contient des caractères `# " ' , : = - [ ] { } ( )` qui peuvent être confondus avec la syntaxe NEON, elle doit être placée entre guillemets. Nous vous recommandons d'utiliser des guillemets simples car ils n'utilisent pas d'échappement. Si vous devez placer un guillemet dans une telle chaîne, doublez-le : +Si la chaîne contient les caractères `# " ' , : = - [ ] { } ( )`, qui peuvent être confondus avec la syntaxe NEON, il faut l'enfermer entre guillemets. Nous recommandons d'utiliser des guillemets simples, car ils n'utilisent pas d'échappement. Si vous avez besoin d'écrire un guillemet dans une telle chaîne, doublez-le : ```neon -'A single quote '' inside a single-quoted string' +'Guillemet '' à l''intérieur d''une chaîne entre guillemets simples' ``` -Les guillemets doubles vous permettent d'utiliser des séquences d'échappement pour écrire des caractères spéciaux à l'aide de barres obliques inversées `\`. All escape sequences as in the JSON format are supported, plus `\_`, qui est un espace insécable, c'est-à-dire `\u00A0`. +Les guillemets doubles permettent d'utiliser des séquences d'échappement pour écrire des caractères spéciaux à l'aide de barres obliques inverses `\`. Toutes les séquences d'échappement comme pour le format JSON sont prises en charge, ainsi que `\_`, qui est une espace insécable, c'est-à-dire `\u00A0`. ```neon - "\t \n \r \f \b \" \\ \/ \_" - "\u00A9" ``` -Il existe d'autres cas où vous devez mettre des chaînes de caractères entre guillemets : +Il existe d'autres cas où il est nécessaire d'enfermer les chaînes entre guillemets : - elles commencent ou se terminent par des espaces -- ressemblent à des nombres, des booléens ou des nullités -- NEON les comprendrait comme des [dates |#dates] +- elles ressemblent à des nombres, des booléens ou null +- NEON les comprendrait comme une [#date] -Chaînes de caractères multilignes .[#toc-multiline-strings] ------------------------------------------------------------ +Chaînes multilignes +------------------- -Une chaîne de caractères multiligne commence et se termine par un guillemet triple sur des lignes séparées. L'indentation de la première ligne est ignorée pour toutes les lignes : +Une chaîne multiligne commence et se termine par trois guillemets sur des lignes distinctes. L'indentation de la première ligne est ignorée, et ce pour toutes les lignes : ```neon ''' - first line - second line - third line + première ligne + deuxième ligne + troisième ligne ''' ``` -En PHP, nous écririons la même chose que : +En PHP, nous écririons la même chose comme : ```php -"first line\n\tsecond line\nthird line" // PHP +"première ligne\n\tdeuxième ligne\ntroisième ligne" // PHP ``` -Les séquences d'échappement ne fonctionnent que pour les chaînes de caractères entourées de guillemets doubles au lieu d'apostrophes : +Les séquences d'échappement ne fonctionnent que pour les chaînes entourées de guillemets doubles au lieu d'apostrophes : ```neon """ @@ -237,24 +259,24 @@ Les séquences d'échappement ne fonctionnent que pour les chaînes de caractèr ``` -Chiffres .[#toc-numbers] ------------------------- +Nombres +------- NEON comprend les nombres écrits en notation dite scientifique ainsi que les nombres en binaire, octal et hexadécimal : ```neon -- 12 # un nombre entier -- 12.3 # un flottant -- +1.2e-34 # un nombre exponentiel +- 12 # entier +- 12.3 # float +- +1.2e-34 # nombre exponentiel -- 0b11010 # nombre binaire -- 0o666 # nombre octal -- 0x7A # nombre hexa +- 0b11010 # nombre binaire +- 0o666 # nombre octal +- 0x7A # nombre hexa ``` -Nulls .[#toc-nulls] -------------------- -Les nuls peuvent être exprimés dans NEON en utilisant `null` ou en ne spécifiant pas de valeur. Les variantes avec une première majuscule ou toutes les lettres majuscules sont également autorisées. +Nulls +----- +Null peut être exprimé en NEON à l'aide de `null` ou en n'indiquant pas de valeur. Les variantes avec la première lettre en majuscule ou toutes les lettres en majuscules sont également autorisées. ```neon a: null @@ -262,50 +284,50 @@ b: ``` -Booléens .[#toc-booleans] -------------------------- -Les valeurs booléennes sont exprimées dans NEON en utilisant `true` / `false` ou `yes` / `no`. Les variantes avec une première majuscule ou toutes les lettres majuscules sont également autorisées. +Booléens +-------- +Les valeurs logiques sont exprimées en NEON à l'aide de `true` / `false` ou `yes` / `no`. Les variantes avec la première lettre en majuscule ou toutes les lettres en majuscules sont également autorisées. ```neon [true, TRUE, True, false, yes, no] ``` -Dates .[#toc-dates] -------------------- -NEON utilise les formats suivants pour exprimer les données et les convertit automatiquement en objets `DateTimeImmutable`: +Date +---- +NEON utilise les formats suivants pour exprimer les dates et les convertit automatiquement en objets `DateTimeImmutable` : ```neon -- 2016-06-03 # date -- 2016-06-03 19:00:00 # date et heure -- 2016-06-03 19:00:00.1234 # date & microtime -- 2016-06-03 19:00:00 +0200 # date & heure & fuseau horaire -- 2016-06-03 19:00:00 +02:00 # date & heure & fuseau horaire +- 2016-06-03 # date +- 2016-06-03 19:00:00 # date & heure +- 2016-06-03 19:00:00.1234 # date & microtemps +- 2016-06-03 19:00:00 +0200 # date & heure & fuseau +- 2016-06-03 19:00:00 +02:00 # date & heure & fuseau ``` -Entités .[#toc-entities] ------------------------- +Entités +------- Une entité est une structure qui ressemble à un appel de fonction : ```neon Column(type: int, nulls: yes) ``` -En PHP, elle est analysée comme un objet [api:Nette\Neon\Entity]: +En PHP, elle est analysée comme un objet [api:Nette\Neon\Entity] : ```php // PHP new Nette\Neon\Entity('Column', ['type' => 'int', 'nulls' => true]) ``` -Les entités peuvent également être enchaînées : +Les entités peuvent également être chaînées : ```neon Column(type: int, nulls: yes) Field(id: 1) ``` -Ce qui est analysé en PHP comme suit : +Ce qui est analysé en PHP de cette manière : ```php // PHP @@ -315,7 +337,7 @@ new Nette\Neon\Entity(Nette\Neon\Neon::Chain, [ ]) ``` -À l'intérieur des parenthèses, les règles de notation en ligne utilisées pour le mappage et les séquences s'appliquent, il peut donc être divisé en plusieurs lignes et il n'est pas nécessaire d'ajouter des virgules : +À l'intérieur des parenthèses, les règles de la notation en ligne utilisées pour les mappings et les séquences s'appliquent, c'est-à-dire qu'elle peut être sur plusieurs lignes et il n'est alors pas nécessaire d'indiquer les virgules : ```neon Column( @@ -325,21 +347,21 @@ Column( ``` -Commentaires .[#toc-comments] ------------------------------ -Les commentaires commencent par `#` et tous les caractères suivants à droite sont ignorés : +Commentaires +------------ +Les commentaires commencent par le caractère `#` et tous les caractères suivants à droite sont ignorés : ```neon # cette ligne sera ignorée par l'interpréteur street: 742 Evergreen Terrace -city: Springfield # cette ligne est également ignorée +city: Springfield # ceci est également ignoré country: USA ``` -NEON Versus JSON .[#toc-neon-versus-json] -========================================= -JSON est un sous-ensemble de NEON. Chaque JSON peut donc être analysé comme du NEON : +Neon versus JSON +================ +JSON est un sous-ensemble de NEON. Chaque JSON peut donc être analysé comme NEON : ```neon { @@ -358,7 +380,7 @@ JSON est un sous-ensemble de NEON. Chaque JSON peut donc être analysé comme du } ``` -Et si nous pouvions omettre les guillemets ? +Et si nous omettions les guillemets ? ```neon { @@ -377,7 +399,7 @@ users: [ } ``` -Pourquoi pas des accolades et des virgules ? +Et les accolades et les virgules ? ```neon php: @@ -394,7 +416,7 @@ users: [ ] ``` -Les puces sont-elles plus lisibles ? +Les listes avec des tirets ne sont-elles pas plus lisibles ? ```neon php: @@ -412,14 +434,14 @@ users: - Rimmer ``` -Et les commentaires ? +Ajoutons des commentaires ? ```neon -# la configuration de mon application web +# configuration de mon application web php: date.timezone: Europe/Prague - zlib.output_compression: true # use gzip + zlib.output_compression: true # utiliser gzip database: driver: mysql @@ -432,8 +454,7 @@ users: - Rimmer ``` -Vous avez trouvé la syntaxe NEON ! +Hourra, vous connaissez maintenant la syntaxe NEON ! -{{description: NEON est un langage de sérialisation des données convivial. Il est similaire à YAML. La principale différence est que NEON prend en charge les "entités" et les caractères de tabulation pour l'indentation.}} -{{leftbar: utils:@left-menu}} +{{description: NEON est un format facilement lisible pour la sérialisation des données. Il est similaire à YAML. La principale différence est que NEON prend en charge les "entités" et que nous pouvons utiliser des espaces ou des tabulations pour l'indentation.}} diff --git a/neon/hu/@home.texy b/neon/hu/@home.texy index de60f22bed..2b63918f98 100644 --- a/neon/hu/@home.texy +++ b/neon/hu/@home.texy @@ -1,54 +1,54 @@ -NEON funkciók -************* +Nette NEON +********** <div class=perex> -A NEON egy emberbarát adatszerializációs nyelv. A Nette-ben a konfigurációs fájlokhoz használják. [api:Nette\Neon\Neon] egy statikus osztály a NEON-nal való munkához. +A NEON egy ember által olvasható adatszerializációs nyelv. A Nette-ben konfigurációs fájlokhoz használják. A [api:Nette\Neon\Neon] egy statikus osztály a NEON-nal való munkához. -Ismerje meg a [NEON formátumot |format] és [próbálja ki |https://ne-on.org]. +Ismerkedjen meg a [NEON formátummal|format] és [próbálja ki |https://ne-on.org]. </div> -A következő példák ezeket az álneveket használják: +Minden példa feltételezi egy alias létrehozását: ```php use Nette\Neon\Neon; ``` -Telepítés .[#toc-installation] ------------------------------- +Telepítés +--------- -Töltse le és telepítse a csomagot a [Composer |best-practices:composer] segítségével: +A könyvtárat a [Composer|best-practices:composer] segítségével töltheti le és telepítheti: ```shell composer require nette/neon ``` -A `neon-lint` konzolparanccsal ellenőrizheti a `*.neon` fájlokban található szintaxis hibákat: +A `*.neon` fájlok szintaktikai hibáit a `neon-lint` konzol paranccsal ellenőrizheti: ```shell -vendor/bin/neon-lint <path> +vendor/bin/neon-lint <útvonal> ``` -encode(mixed $value, bool $blockMode=false): string .[method] -------------------------------------------------------------- +encode(mixed $value, bool $blockMode=false, string $indentation="\t"): string .[method] +--------------------------------------------------------------------------------------- -Visszaadja a `$value` fájlt NEON-ra konvertálva. A `$blockMode` paraméterként átadható a true, ami többsoros kimenetet hoz létre. A `$indentation` paraméter megadja a behúzáshoz használt karaktereket (alapértelmezett a tabulátor). +Visszaadja a `$value` értéket NEON formátumba konvertálva. A `$blockMode` paraméterként `true` értéket adhat át, ami többsoros kimenetet hoz létre. Az `$indentation` paraméter határozza meg a behúzáshoz használt karaktereket (alapértelmezés szerint tabulátor). ```php -Neon::encode($value); // Visszaadja a NEON-ra konvertált $value-t. -Neon::encode($value, true); // Visszaadja a többsoros NEON-ba konvertált $value-t +Neon::encode($value); // Visszaadja a $value értéket NEON-ba konvertálva +Neon::encode($value, true); // Visszaadja a $value értéket többsoros NEON-ba konvertálva ``` -A `encode()` metódus hiba esetén a `Nette\Neon\Exception` értéket dobja. +Az `encode()` metódus hiba esetén `Nette\Neon\Exception` kivételt dob. ```php try { $neon = Neon::encode($value); } catch (Nette\Neon\Exception $e) { - // Kivételkezelés + // kivétel kezelése } ``` @@ -56,21 +56,21 @@ try { decode(string $neon): mixed .[method] ------------------------------------- -Átalakítja a megadott NEON-t PHP-értékké. +Átalakítja a stringet NEON-ból PHP-ra. -Visszaad skalárokat, tömböket, [dátumot |format#dates] DateTimeImmutable objektumként, [entitásokat |format#Entities] pedig [api:Nette\Neon\Entity] objektumként. +Visszaad skalárokat, tömböket, [dátumokat |format#Dátum] DateTimeImmutable objektumként és [entitásokat |format#Entitások] [api:Nette\Neon\Entity] objektumként. ```php -Neon::decode('hello: world'); // Visszaad egy tömböt ['hello' => 'world'] +Neon::decode('hello: world'); // Visszaadja a ['hello' => 'world'] tömböt ``` -A `decode()` metódus hiba esetén `Nette\Neon\Exception` dob. +A `decode()` metódus hiba esetén `Nette\Neon\Exception` kivételt dob. ```php try { $value = Neon::decode($neon); } catch (Nette\Neon\Exception $e) { - // Kivételkezelés + // kivétel kezelése } ``` @@ -78,13 +78,10 @@ try { decodeFile(string $file): mixed .[method] ----------------------------------------- -Átalakítja a fájl tartalmát NEON-ról PHP-ra, és eltávolítja a BOM-ot. +Átalakítja egy fájl tartalmát NEON-ból PHP-ra és eltávolítja az esetleges BOM-ot. ```php Neon::decodeFile('config.neon'); ``` -A `decodeFile()` módszer hiba esetén a `Nette\Neon\Exception` értéket dobja. - - -{{leftbar: utils:@left-menu}} +A `decodeFile()` metódus hiba esetén `Nette\Neon\Exception` kivételt dob. diff --git a/neon/hu/@meta.texy b/neon/hu/@meta.texy new file mode 100644 index 0000000000..08f12d5cfb --- /dev/null +++ b/neon/hu/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette dokumentáció}} +{{leftbar: utils:@left-menu}} diff --git a/neon/hu/format.texy b/neon/hu/format.texy index 34eb733cc4..a47bd628d7 100644 --- a/neon/hu/format.texy +++ b/neon/hu/format.texy @@ -1,20 +1,20 @@ -NEON formátum +NEON Formátum ************* .[perex] -A NEON egy ember által olvasható strukturált adatformátum. A Nette-ben konfigurációs fájlokra használják. Strukturált adatok, például beállítások, nyelvi fordítások stb. tárolására is használják. [Próbálja ki a homokozóban |https://ne-on.org]. +A NEON egy ember által olvasható, strukturált adatformátum. A Nette-ben konfigurációs fájlokhoz használják. Strukturált adatokhoz is használják, mint például beállítások, nyelvi fordítások stb. [Próbálja ki|https://ne-on.org]. -A NEON a *Nette Object Notation* rövidítése. Kevésbé bonyolult és nehézkes, mint az XML vagy a JSON, de hasonló képességekkel rendelkezik. Nagyon hasonlít a YAML-hez. A fő előnye, hogy a NEON rendelkezik úgynevezett [entitásokkal |#entities], aminek köszönhetően a DI szolgáltatások konfigurálása olyan szexi. És lehetővé teszi a tabulátorokat a behúzáshoz. +A NEON a *Nette Object Notation* rövidítése. Kevésbé bonyolult és nehézkes, mint az XML vagy a JSON, de hasonló funkciókat biztosít. Nagyon hasonlít a YAML-hez. A fő előnye az, hogy a NEON-nak vannak úgynevezett [entity |#Entitások] entitásai, amelyeknek köszönhetően a DI szolgáltatások konfigurálása [is szexi |https://gist.github.com/dg/26baf3ce8f29d0f751e9dddfaa06504f]. És lehetővé teszi a tabulátorokkal történő behúzást. -A NEON az alapoktól kezdve úgy lett felépítve, hogy egyszerűen használható legyen. +A NEON-t az alapoktól kezdve úgy tervezték, hogy könnyen használható legyen. -Integráció .[#toc-integration] -============================== +Integráció +========== - NetBeans (beépített támogatással rendelkezik) - PhpStorm ([plugin |https://plugins.jetbrains.com/plugin/7060?pr]) -- Visual Studio Code ([plugin |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) +- Visual Studio Code ([Nette Latte + Neon |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) vagy [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode]) - Sublime Text 3 ([plugin |https://github.com/FilipStryk/Nette-Latte-Neon-for-Sublime-Text-3]) - Sublime Text 2 ([plugin |https://github.com/Michal-Mikolas/Nette-package-for-Sublime-Text-2]) - VIM ([plugin |https://github.com/fpob/nette.vim]) @@ -23,19 +23,19 @@ Integráció .[#toc-integration] - [NEON for PHP |@home] -- [NEON JavaScripthez |https://github.com/matej21/neon-js] +- [NEON for JavaScript |https://github.com/matej21/neon-js] - [NEON for Python |https://github.com/paveldedik/neon-py]. -Szintaxis .[#toc-syntax] -======================== +Szintaxis +========= -Egy NEON-ban írt fájl általában egy szekvenciából vagy leképezésből áll. +Egy NEON-ban írt fájl általában egy tömböt vagy egy leképezést (mapping) reprezentál. -Leképezések .[#toc-mappings] ----------------------------- -A leképezés kulcs-érték párok halmaza, PHP-ben asszociatív tömbnek neveznénk. Minden pár a `key: value` formában íródik, a `:` után szóköz szükséges. Az érték bármi lehet: string, szám, boolean, null, szekvencia vagy más leképezés. +Leképezés (Mapping) +------------------- +A leképezés kulcs-érték párok halmaza, PHP-ban asszociatív tömbnek mondanánk. Minden pár `key: value` formában van írva, a `:` utáni szóköz kötelező. Az érték bármi lehet: string, szám, boolean, null, szekvencia vagy másik leképezés. ```neon street: 742 Evergreen Terrace @@ -43,7 +43,7 @@ city: Springfield country: USA ``` -PHP-ben ugyanez a struktúra a következőképpen lenne leírva: +PHP-ban ugyanez a struktúra így íródna: ```php [ // PHP @@ -53,13 +53,13 @@ PHP-ben ugyanez a struktúra a következőképpen lenne leírva: ] ``` -Ezt a jelölést blokkjelölésnek nevezzük, mivel minden elem külön sorban van, és ugyanolyan behúzással rendelkezik (ebben az esetben nincs). A NEON támogatja az inline ábrázolást is a leképezéshez, amely zárójelekbe van zárva, a behúzás nem játszik szerepet, és az egyes elemek elválasztója vagy egy vessző vagy egy újsor: +Ezt a jelölést blokk jelölésnek nevezik, mert minden elem külön sorban van, és ugyanazzal a behúzással rendelkezik (ebben az esetben nincs behúzás). A NEON támogatja a leképezések inline reprezentációját is, amely zárójelekbe van zárva, a behúzás nem játszik szerepet, és az egyes elemek elválasztója vagy vessző, vagy új sor: ```neon {street: 742 Evergreen Terrace, city: Springfield, country: USA} ``` -Ez több sorba írva ugyanaz (a behúzás nem számít): +Ugyanez több sorba írva (a behúzás nem számít): ```neon { @@ -68,16 +68,16 @@ Ez több sorba írva ugyanaz (a behúzás nem számít): } ``` -Alternatívaként a `=` is használható a <code>: </code>, mind blokkos, mind soron belüli jelölésben: +A <code>: </code> helyett alternatívaként használható az `=` is, mind a blokk, mind az inline jelölésben: ```neon {street=742 Evergreen Terrace, city=Springfield, country=USA} ``` -Sequences .[#toc-sequences] ---------------------------- -A szekvenciák indexelt tömbök a PHP-ban. A szekvenciák a `-` kötőjellel kezdődő sorok, majd egy szóköz követi őket. Az érték itt is bármi lehet: karakterlánc, szám, boolean, null, szekvencia vagy más leképezés. +Szekvencia +---------- +A szekvenciák PHP-ban indexelt tömbök. Kötőjellel `-` kezdődő sorokként íródnak, amelyet szóköz követ. Az érték ismét bármi lehet: string, szám, boolean, null, szekvencia vagy másik leképezés. ```neon - Cat @@ -85,7 +85,7 @@ A szekvenciák indexelt tömbök a PHP-ban. A szekvenciák a `-` kötőjellel ke - Goldfish ``` -PHP-ben ugyanez a struktúra a következőképpen lenne leírva: +PHP-ban ugyanez a struktúra így íródna: ```php [ // PHP @@ -95,13 +95,13 @@ PHP-ben ugyanez a struktúra a következőképpen lenne leírva: ] ``` -Ezt a jelölést blokkjelölésnek nevezzük, mivel minden elem külön sorban van, és ugyanolyan behúzással rendelkezik (ebben az esetben nincs). A NEON támogatja a sorozatok inline ábrázolását is, amely zárójelekbe van zárva, a behúzás nem játszik szerepet, és az egyes elemek elválasztója vagy egy vessző vagy egy újsor: +Ezt a jelölést blokk jelölésnek nevezik, mert minden elem külön sorban van, és ugyanazzal a behúzással rendelkezik (ebben az esetben nincs behúzás). A NEON támogatja a szekvenciák inline reprezentációját is, amely zárójelekbe van zárva, a behúzás nem játszik szerepet, és az egyes elemek elválasztója vagy vessző, vagy új sor: ```neon [Cat, Dog, Goldfish] ``` -Ez több sorba írva ugyanaz (a behúzás nem számít): +Ugyanez több sorba írva (a behúzás nem számít): ```neon [ @@ -110,23 +110,23 @@ Ez több sorba írva ugyanaz (a behúzás nem számít): ] ``` -Kötőjelek nem használhatók soron belüli ábrázolásban. +Az inline reprezentációban nem használhatók behúzó kötőjelek (`-`). -Kombináció .[#toc-combination] ------------------------------- -A leképezések és szekvenciák értékei lehetnek más leképezések és szekvenciák. A behúzás szintje nagy szerepet játszik. A következő példában a szekvenciaelemek jelölésére használt kötőjel nagyobb behúzással rendelkezik, mint a `pets` kulcs, így az elemek az első sor értékévé válnak: +Kombinációk +----------- +A leképezések és szekvenciák értékei lehetnek más leképezések és szekvenciák. A fő szerepet a behúzás szintje játssza. A következő példában a szekvencia elemeinek jelölésére használt kötőjel nagyobb behúzással rendelkezik, mint a `pets` kulcs, így az elemek az első sor értékévé válnak: ```neon pets: - - Cat - - Dog + - Cat + - Dog cars: - - Volvo - - Skoda + - Volvo + - Skoda ``` -PHP-ben ugyanezt a struktúrát így írnánk le: +PHP-ban ugyanez a struktúra így íródna: ```php [ // PHP @@ -141,7 +141,7 @@ PHP-ben ugyanezt a struktúrát így írnánk le: ] ``` -Lehetőség van a blokk és az inline jelölés kombinálására: +Lehet kombinálni a blokk és az inline jelölést: ```neon pets: [Cat, Dog] @@ -151,17 +151,39 @@ cars: [ ] ``` -Ez már nem működik, a blokk jelölés nem használható inline jelölésen belül: +Az inline jelölésen belül már nem használható blokk jelölés, ez nem működik: ```neon item: [ pets: - - Cat # THIS IS NOT POSSIBLE!!! + - Cat # EZ NEM LEHET!!! - Dog ] ``` -Mivel a PHP ugyanazt a struktúrát használja a leképezéshez és a szekvenciákhoz, azaz a tömböket, mindkettő összevonható. A behúzás ezúttal is ugyanaz: +Az előző esetben egy leképezést írtunk, amelynek elemei szekvenciák voltak, most próbáljuk meg fordítva, és hozzunk létre egy szekvenciát, amely leképezéseket tartalmaz: + +```neon +- + name: John + age: 35 +- + name: Peter + age: 28 +``` + +Nem szükséges, hogy a kötőjelek külön sorokban legyenek, így is elhelyezhetők: + +```neon +- name: John + age: 35 +- name: Peter + age: 28 +``` + +Ön dönti el, hogy a kulcsokat szóközökkel igazítja-e oszlopba, vagy tabulátort használ. + +Mivel a PHP-ban a leképezésekhez és a szekvenciákhoz is ugyanazt a struktúrát, azaz a tömböt használják, mindkettőt össze lehet vonni. A behúzás ezúttal ugyanaz: ```neon - Cat @@ -169,7 +191,7 @@ street: 742 Evergreen Terrace - Goldfish ``` -PHP-ben ugyanazt a struktúrát így írnánk: +PHP-ban ugyanez a struktúra így íródna: ```php [ // PHP @@ -180,55 +202,55 @@ PHP-ben ugyanazt a struktúrát így írnánk: ``` -Strings .[#toc-strings] ------------------------ -A karakterláncok a NEON-ban szimpla vagy dupla idézőjelek közé zárhatók. De mint láthatjuk, idézőjelek nélkül is lehetnek. +Stringek +-------- +A NEON stringeket szimpla és dupla idézőjelek közé lehet zárni. De ahogy látja, idézőjelek nélkül is lehetnek. ```neon -- A unquoted string in NEON -- 'A singled-quoted string in NEON' -- "A double-quoted string in NEON" +- String NEON-ban idézőjelek nélkül +- 'String NEON-ban szimpla idézőjelekben' +- "String NEON-ban dupla idézőjelekben" ``` -Ha a karakterlánc karaktereket tartalmaz `# " ' , : = - [ ] { } ( )` amelyek összetéveszthetők a NEON szintaxissal, akkor idézőjelek közé kell zárni. Javasoljuk az egyszeres idézőjelek használatát, mivel ezek nem használnak eszkábálást. Ha ilyen karakterláncba idézőjelet kell zárni, akkor duplázza meg azt: +Ha a string tartalmaz `# " ' , : = - [ ] { } ( )` karaktereket, amelyeket össze lehet téveszteni a NEON szintaxissal, akkor idézőjelek közé kell zárni. Javasoljuk a szimpla idézőjelek használatát, mert azokban nem használatos az escapelés. Ha egy ilyen stringben idézőjelet kell írnia, duplázza meg: ```neon -'A single quote '' inside a single-quoted string' +'Idézőjel '' egy stringen belül szimpla idézőjelekben' ``` -A kettős idézőjelek lehetővé teszik, hogy escape szekvenciákat használjon speciális karakterek írásához, a backslashes `\`. All escape sequences as in the JSON format are supported, plus `\_`, ami egy nem szaggatott szóköz, azaz `\u00A0`. +A dupla idézőjelek lehetővé teszik escape szekvenciák használatát speciális karakterek írására fordított perjelek `\` segítségével. Minden escape szekvencia támogatott, mint a JSON formátumban, plusz a `\_`, ami egy nem törhető szóköz, azaz `\u00A0`. ```neon - "\t \n \r \f \b \" \\ \/ \_" - "\u00A9" ``` -Vannak más esetek is, amikor karakterláncokat kell idézőjelekbe zárni: -- szóközzel kezdődnek vagy végződnek -- számoknak, booléknak vagy nullának tűnnek -- A NEON [dátumként |#dates] értelmezné őket +Vannak további esetek, amikor a stringeket idézőjelek közé kell zárni: +- szóközökkel kezdődnek vagy végződnek +- számoknak, booleaneknek vagy nullnak tűnnek +- a NEON [dátumként |#Dátum] értelmezné őket -Többsoros karakterláncok .[#toc-multiline-strings] --------------------------------------------------- +Többsoros stringek +------------------ -A többsoros karakterlánc háromszoros idézőjellel kezdődik és végződik külön sorokban. Az első sor behúzását az összes sor esetében figyelmen kívül hagyjuk: +A többsoros string háromszoros idézőjellel kezdődik és végződik külön sorokban. Az első sor behúzása figyelmen kívül marad, és ez minden sorra vonatkozik: ```neon ''' - first line - second line - third line + első sor + második sor + harmadik sor ''' ``` -PHP-ben ugyanezt így írnánk: +PHP-ban ugyanezt így írnánk: ```php -"first line\n\tsecond line\nthird line" // PHP +"első sor\n\tmásodik sor\nharmadik sor" // PHP ``` -A szekvenciák csak aposztrófok helyett dupla idézőjelekbe zárt karakterláncok esetében működnek: +Az escapelési szekvenciák csak az aposztrófok helyett dupla idézőjelekkel (`"""`) bevezetett stringeknél működnek: ```neon """ @@ -237,24 +259,24 @@ A szekvenciák csak aposztrófok helyett dupla idézőjelekbe zárt karakterlán ``` -Számok .[#toc-numbers] ----------------------- -A NEON megérti az úgynevezett tudományos jelöléssel írt számokat, valamint a bináris, oktális és hexadecimális számokat is: +Számok +------ +A NEON érti a tudományos jelöléssel írt számokat, valamint a bináris, oktális és hexadecimális rendszerben írt számokat: ```neon -- 12 # egész szám -- 12.3 # egy lebegő -- +1.2e-34 # exponenciális szám +- 12 # egész szám +- 12.3 # float +- +1.2e-34 # exponenciális szám -- 0b11010 # bináris szám -- 0o666 # oktális szám -- 0x7A # hexa szám +- 0b11010 # bináris szám +- 0o666 # oktális szám +- 0x7A # hexa szám ``` -Nullák .[#toc-nulls] --------------------- -A null a NEON-ban a `null` használatával vagy érték meg nem adásával fejezhető ki. A nagy kezdőbetűvel vagy csupa nagybetűvel írt változatok is megengedettek. +Null értékek +------------ +A null értéket a NEON-ban `null`-lal vagy az érték megadásának elhagyásával lehet kifejezni. Engedélyezettek a nagy kezdőbetűs vagy csupa nagybetűs változatok is. ```neon a: null @@ -262,37 +284,37 @@ b: ``` -Booleans .[#toc-booleans] -------------------------- -A Boolean értékeket a NEON-ban a `true` / `false` vagy a `yes` / `no` segítségével fejezzük ki. A nagy kezdőbetűvel vagy csupa nagybetűvel írt változatok is megengedettek. +Boole értékek +------------- +A logikai értékeket a NEON-ban `true` / `false` vagy `yes` / `no` segítségével fejezzük ki. Engedélyezettek a nagy kezdőbetűs vagy csupa nagybetűs változatok is. ```neon [true, TRUE, True, false, yes, no] ``` -Dátumok .[#toc-dates] ---------------------- -A NEON a következő formátumokat használja az adatok kifejezésére, és automatikusan átalakítja őket `DateTimeImmutable` objektumokká: +Dátum +----- +A NEON a következő formátumokat használja a dátumok kifejezésére, és automatikusan `DateTimeImmutable` objektumokká konvertálja őket: ```neon -- 2016-06-03 # dátum -- 2016-06-03 19:00:00 # dátum és idő -- 2016-06-03 19:00:00.1234 # dátum és mikroidő -- 2016-06-03 19:00:00 +0200 # dátum & idő & időzóna -- 2016-06-03 19:00:00 +02:00 # dátum & idő & időzóna +- 2016-06-03 # dátum +- 2016-06-03 19:00:00 # dátum & idő +- 2016-06-03 19:00:00.1234 # dátum & mikro idő +- 2016-06-03 19:00:00 +0200 # dátum & idő & zóna +- 2016-06-03 19:00:00 +02:00 # dátum & idő & zóna ``` -Entitások .[#toc-entities] --------------------------- -Az entitás olyan struktúra, amely hasonlít egy függvényhívásra: +Entitások +--------- +Az entitás egy olyan struktúra, amely egy függvényhívásra emlékeztet: ```neon Column(type: int, nulls: yes) ``` -A PHP-ben objektumként elemzik [api:Nette\Neon\Entity]: +PHP-ban ez [api:Nette\Neon\Entity] objektumként kerül elemzésre: ```php // PHP @@ -305,7 +327,7 @@ Az entitások láncolhatók is: Column(type: int, nulls: yes) Field(id: 1) ``` -Amit a PHP a következőképpen elemez: +Ami PHP-ban így kerül elemzésre: ```php // PHP @@ -315,7 +337,7 @@ new Nette\Neon\Entity(Nette\Neon\Neon::Chain, [ ]) ``` -A zárójeleken belül a leképezéshez és a szekvenciákhoz használt inline jelölés szabályai érvényesek, így több sorra osztható, és nem szükséges vesszőt tenni: +A zárójeleken belül az inline jelölés szabályai érvényesek, amelyeket a leképezéseknél és szekvenciáknál használnak, tehát lehet többsoros is, és akkor nem szükséges vesszőket megadni: ```neon Column( @@ -325,21 +347,21 @@ Column( ``` -Megjegyzések .[#toc-comments] ------------------------------ -A megjegyzések `#`-gal kezdődnek, és a jobb oldalt következő karaktereket figyelmen kívül hagyjuk: +Kommentek +--------- +A kommentek `#` jellel kezdődnek, és az összes következő karakter jobbra figyelmen kívül marad: ```neon -# ezt a sort az értelmező figyelmen kívül hagyja. -utca: 742 Evergreen Terrace -város: # ezt is figyelmen kívül hagyja -ország: USA +# ezt a sort az értelmező figyelmen kívül hagyja +street: 742 Evergreen Terrace +city: Springfield # ezt is figyelmen kívül hagyja +country: USA ``` -NEON Versus JSON .[#toc-neon-versus-json] -========================================= -A JSON a NEON egy részhalmaza. Ezért minden JSON elemezhető NEON-ként: +Neon versus JSON +================ +A JSON a NEON részhalmaza. Minden JSON ezért NEON-ként is elemezhető: ```neon { @@ -358,7 +380,7 @@ A JSON a NEON egy részhalmaza. Ezért minden JSON elemezhető NEON-ként: } ``` -Mi lenne, ha elhagyhatnánk az idézőjeleket? +Mi lenne, ha elhagynánk az idézőjeleket? ```neon { @@ -377,7 +399,7 @@ users: [ } ``` -Mit szólnánk a zárójelekhez és a vesszőkhöz? +És a kapcsos zárójeleket és vesszőket? ```neon php: @@ -394,7 +416,7 @@ users: [ ] ``` -A felsorolásjelek olvashatóbbak? +Nem olvashatóbbak a listák kötőjelekkel? ```neon php: @@ -412,14 +434,14 @@ users: - Rimmer ``` -Mi a helyzet a megjegyzésekkel? +Hozzáadunk kommenteket? ```neon -# my web application config +# a webalkalmazásom konfigurációja php: date.timezone: Europe/Prague - zlib.output_compression: true # use gzip + zlib.output_compression: true # gzip használata database: driver: mysql @@ -432,8 +454,7 @@ users: - Rimmer ``` -Megtaláltad a NEON szintaxist! +Hurrá, most már ismeri a NEON szintaxisát! -{{description: A NEON egy emberbarát adatszerializációs nyelv. Hasonló a YAML-hez. A fő különbség az, hogy a NEON támogatja az "entitásokat" és a tabulátor karaktereket a behúzáshoz.}} -{{leftbar: utils:@left-menu}} +{{description: A NEON egy könnyen olvasható formátum az adatok szerializálására. Hasonló a YAML-hez. A fő különbség az, hogy a NEON támogatja az „entitásokat”, és a behúzáshoz használhatunk szóközöket és tabulátorokat is.}} diff --git a/neon/it/@home.texy b/neon/it/@home.texy index cca2db9ae2..3358f69547 100644 --- a/neon/it/@home.texy +++ b/neon/it/@home.texy @@ -1,41 +1,41 @@ -Funzioni NEON -************* +Nette NEON +********** <div class=perex> -NEON è un linguaggio di serializzazione dei dati facile da usare. Viene utilizzato in Nette per i file di configurazione. [api:Nette\Neon\Neon] è una classe statica per lavorare con NEON. +NEON è un linguaggio leggibile dall'uomo per la serializzazione dei dati. Viene utilizzato in Nette per i file di configurazione. [api:Nette\Neon\Neon] è una classe statica per lavorare con NEON. -Conoscete il [formato NEON |format] e [provatelo |https://ne-on.org]. +Scopri il [formato NEON|format] e [provalo |https://ne-on.org]. </div> -I seguenti esempi utilizzano questi alias: +Tutti gli esempi presuppongono la creazione di un alias: ```php use Nette\Neon\Neon; ``` -Installazione .[#toc-installation] ----------------------------------- +Installazione +------------- -Scaricare e installare il pacchetto utilizzando [Composer |best-practices:composer]: +Scarica e installa la libreria utilizzando [Composer|best-practices:composer]: ```shell composer require nette/neon ``` -È possibile verificare la presenza di errori di sintassi nei file `*.neon` usando il comando di console `neon-lint`: +Puoi controllare gli errori di sintassi nei file `*.neon` utilizzando il comando da console `neon-lint`: ```shell -vendor/bin/neon-lint <path> +vendor/bin/neon-lint <percorso> ``` -encode(mixed $value, bool $blockMode=false): string .[method] -------------------------------------------------------------- +encode(mixed $value, bool $blockMode=false, string $indentation="\t"): string .[method] +--------------------------------------------------------------------------------------- -Restituisce `$value` convertito in NEON. Come parametro `$blockMode` si può passare true, che creerà un output multilinea. Il parametro `$indentation` specifica i caratteri utilizzati per l'indentazione (l'impostazione predefinita è tab). +Restituisce `$value` convertito in NEON. Come parametro `$blockMode`, puoi passare `true`, creando così un output multilinea. Il parametro `$indentation` specifica i caratteri utilizzati per l'indentazione (il predefinito è il tabulatore). ```php Neon::encode($value); // Restituisce $value convertito in NEON @@ -48,7 +48,7 @@ Il metodo `encode()` lancia `Nette\Neon\Exception` in caso di errore. try { $neon = Neon::encode($value); } catch (Nette\Neon\Exception $e) { - // Gestione delle eccezioni + // gestione dell'eccezione } ``` @@ -56,21 +56,21 @@ try { decode(string $neon): mixed .[method] ------------------------------------- -Converte il NEON dato in un valore PHP. +Converte una stringa da NEON a PHP. -Restituisce scalari, array, [date |format#dates] come oggetti DateTimeImmutable ed [entità |format#Entities] come oggetti [api:Nette\Neon\Entity]. +Restituisce scalari, array, [date |format#Data] come oggetti DateTimeImmutable ed [entità |format#Entità] come oggetti [api:Nette\Neon\Entity]. ```php -Neon::decode('hello: world'); // Restituisce un array ['hello' => 'world']. +Neon::decode('hello: world'); // Restituisce l'array ['hello' => 'world'] ``` -Il metodo `decode()` lancia `Nette\Neon\Exception` un errore. +Il metodo `decode()` lancia `Nette\Neon\Exception` in caso di errore. ```php try { $value = Neon::decode($neon); } catch (Nette\Neon\Exception $e) { - // Gestione delle eccezioni + // gestione dell'eccezione } ``` @@ -78,13 +78,10 @@ try { decodeFile(string $file): mixed .[method] ----------------------------------------- -Converte il contenuto del file da NEON a PHP e rimuove qualsiasi BOM. +Converte il contenuto di un file da NEON a PHP e rimuove eventuali BOM. ```php Neon::decodeFile('config.neon'); ``` Il metodo `decodeFile()` lancia `Nette\Neon\Exception` in caso di errore. - - -{{leftbar: utils:@left-menu}} diff --git a/neon/it/@meta.texy b/neon/it/@meta.texy new file mode 100644 index 0000000000..f4a992f2ee --- /dev/null +++ b/neon/it/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Documentazione Nette}} +{{leftbar: utils:@left-menu}} diff --git a/neon/it/format.texy b/neon/it/format.texy index 0e1e505561..347d561bc0 100644 --- a/neon/it/format.texy +++ b/neon/it/format.texy @@ -2,19 +2,19 @@ Formato NEON ************ .[perex] -NEON è un formato di dati strutturati leggibili dall'uomo. In Nette viene utilizzato per i file di configurazione. Viene utilizzato anche per i dati strutturati, come le impostazioni, le traduzioni delle lingue, ecc. [Provatelo nella sandbox |https://ne-on.org]. +NEON è un formato di dati strutturati leggibile dall'uomo. In Nette viene utilizzato per i file di configurazione. Viene utilizzato anche per dati strutturati come impostazioni, traduzioni linguistiche, ecc. [Provatelo|https://ne-on.org]. -NEON sta per *Nette Object Notation*. È meno complesso e sgraziato di XML o JSON, ma offre funzionalità simili. È molto simile a YAML. Il vantaggio principale è che NEON ha le cosiddette [entità |#entities], grazie alle quali la configurazione dei servizi DI è così sexy. E permette di usare le tabulazioni per l'indentazione. +NEON è l'acronimo di *Nette Object Notation*. È meno complesso e goffo di XML o JSON, ma fornisce funzionalità simili. È molto simile a YAML. Il vantaggio principale è che NEON ha le cosiddette [#entità], grazie alle quali la configurazione dei servizi DI è [così sexy |https://gist.github.com/dg/26baf3ce8f29d0f751e9dddfaa06504f]. E consente l'indentazione con tabulazioni. -NEON è costruito da zero per essere semplice da usare. +NEON è costruito da zero per essere facile da usare. -Integrazione .[#toc-integration] -================================ +Integrazione +============ -- NetBeans (ha un supporto integrato) +- NetBeans (ha supporto integrato) - PhpStorm ([plugin |https://plugins.jetbrains.com/plugin/7060?pr]) -- Visual Studio Code ([plugin |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) +- Visual Studio Code ([Nette Latte + Neon |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) o [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode]) - Sublime Text 3 ([plugin |https://github.com/FilipStryk/Nette-Latte-Neon-for-Sublime-Text-3]) - Sublime Text 2 ([plugin |https://github.com/Michal-Mikolas/Nette-package-for-Sublime-Text-2]) - VIM ([plugin |https://github.com/fpob/nette.vim]) @@ -22,20 +22,20 @@ Integrazione .[#toc-integration] - Prism.js ([linguaggio integrato |https://prismjs.com/#supported-languages]) -- [NEON per PHP |@home] -- [NEON per JavaScript |https://github.com/matej21/neon-js] -- [NEON per Python |https://github.com/paveldedik/neon-py]. +- [NEON for PHP |@home] +- [NEON for JavaScript |https://github.com/matej21/neon-js] +- [NEON for Python |https://github.com/paveldedik/neon-py]. -Sintassi .[#toc-syntax] -======================= +Sintassi +======== -Un file scritto in NEON consiste solitamente in una sequenza o in una mappatura. +Un file scritto in NEON di solito rappresenta un array o una mappa. -Mappature .[#toc-mappings] --------------------------- -La mappatura è un insieme di coppie chiave-valore, in PHP si chiamerebbe array associativo. Ogni coppia è scritta come `key: value`, è richiesto uno spazio dopo `:`. Il valore può essere qualsiasi cosa: stringa, numero, booleano, null, sequenza o altra mappatura. +Mapping +------- +Un mapping è un insieme di coppie chiave-valore, in PHP si direbbe array associativo. Ogni coppia è scritta come `key: value`, lo spazio dopo `:` è necessario. Il valore può essere qualsiasi cosa: stringa, numero, booleano, null, sequenza o un altro mapping. ```neon street: 742 Evergreen Terrace @@ -53,13 +53,13 @@ In PHP, la stessa struttura sarebbe scritta come: ] ``` -Questa notazione è detta a blocchi perché tutti gli elementi sono su una riga separata e hanno la stessa indentazione (nessuna in questo caso). NEON supporta anche la rappresentazione in linea per la mappatura, che è racchiusa tra parentesi, l'indentazione non gioca alcun ruolo e il separatore di ogni elemento è una virgola o una newline: +Questa notazione è detta a blocchi, perché tutti gli elementi sono su righe separate e hanno la stessa indentazione (in questo caso nessuna). NEON supporta anche una rappresentazione inline dei mapping, che è racchiusa tra parentesi graffe, l'indentazione non gioca alcun ruolo e il separatore tra i singoli elementi è una virgola o un ritorno a capo: ```neon {street: 742 Evergreen Terrace, city: Springfield, country: USA} ``` -Si tratta dello stesso testo scritto su più righe (l'indentazione non ha importanza): +Lo stesso scritto su più righe (l'indentazione non ha importanza): ```neon { @@ -68,16 +68,16 @@ Si tratta dello stesso testo scritto su più righe (l'indentazione non ha import } ``` -In alternativa, `=` può essere usato al posto di <code>: </code>, sia nella notazione a blocchi che in quella inline: +In alternativa a <code>: </code> si può usare `=` sia nella notazione a blocchi che in quella inline: ```neon {street=742 Evergreen Terrace, city=Springfield, country=USA} ``` -Sequenze .[#toc-sequences] --------------------------- -Le sequenze sono array indicizzati in PHP. Sono scritte come righe che iniziano con il trattino `-` seguito da uno spazio. Anche in questo caso, il valore può essere qualsiasi cosa: stringa, numero, booleano, null, sequenza o altra mappatura. +Sequenze +-------- +Le sequenze sono array indicizzati in PHP. Sono scritte come righe che iniziano con un trattino `-` seguito da uno spazio. Il valore può essere di nuovo qualsiasi cosa: stringa, numero, booleano, null, sequenza o un altro mapping. ```neon - Cat @@ -95,13 +95,13 @@ In PHP, la stessa struttura sarebbe scritta come: ] ``` -Questa notazione è detta a blocchi perché tutti gli elementi sono su una riga separata e hanno la stessa indentazione (nessuna in questo caso). NEON supporta anche la rappresentazione in linea per le sequenze, che sono racchiuse tra parentesi, l'indentazione non gioca alcun ruolo e il separatore di ogni elemento è una virgola o una newline: +Questa notazione è detta a blocchi, perché tutti gli elementi sono su righe separate e hanno la stessa indentazione (in questo caso nessuna). NEON supporta anche una rappresentazione inline delle sequenze, che è racchiusa tra parentesi quadre, l'indentazione non gioca alcun ruolo e il separatore tra i singoli elementi è una virgola o un ritorno a capo: ```neon [Cat, Dog, Goldfish] ``` -Si tratta della stessa scrittura su più righe (l'indentazione non ha importanza): +Lo stesso scritto su più righe (l'indentazione non ha importanza): ```neon [ @@ -110,20 +110,20 @@ Si tratta della stessa scrittura su più righe (l'indentazione non ha importanza ] ``` -I trattini non possono essere usati in una rappresentazione in linea. +Nella rappresentazione inline non è possibile utilizzare trattini indentati. -Combinazione .[#toc-combination] --------------------------------- -I valori delle mappature e delle sequenze possono essere altre mappature e sequenze. Il livello di rientro gioca un ruolo importante. Nell'esempio seguente, il trattino usato per indicare gli elementi della sequenza ha un rientro maggiore rispetto al tasto `pets`, quindi gli elementi diventano il valore della prima riga: +Combinazioni +------------ +I valori dei mapping e delle sequenze possono essere altri mapping e sequenze. Il ruolo principale è giocato dal livello di indentazione. Nell'esempio seguente, il trattino utilizzato per indicare gli elementi della sequenza ha un'indentazione maggiore rispetto alla chiave `pets`, quindi gli elementi diventano il valore della prima riga: ```neon pets: - - Cat - - Dog + - Cat + - Dog cars: - - Volvo - - Skoda + - Volvo + - Skoda ``` In PHP, la stessa struttura sarebbe scritta come: @@ -141,7 +141,7 @@ In PHP, la stessa struttura sarebbe scritta come: ] ``` -È possibile combinare la notazione a blocchi e quella in linea: +È possibile combinare la notazione a blocchi e inline: ```neon pets: [Cat, Dog] @@ -151,17 +151,39 @@ cars: [ ] ``` -La notazione a blocchi non può più essere utilizzata all'interno di una notazione in linea, non funziona: +All'interno della notazione inline non è più possibile utilizzare la notazione a blocchi, questo non funziona: ```neon item: [ pets: - - Cat # THIS IS NOT POSSIBLE!!! + - Cat # QUESTO NON È POSSIBILE!!! - Dog ] ``` -Poiché PHP utilizza la stessa struttura per le mappature e le sequenze, cioè gli array, entrambi possono essere uniti. L'indentazione è la stessa questa volta: +Nel caso precedente abbiamo scritto un mapping i cui elementi erano sequenze, ora proviamo il contrario e creiamo una sequenza contenente mapping: + +```neon +- + name: John + age: 35 +- + name: Peter + age: 28 +``` + +Non è necessario che i trattini siano su righe separate, possono essere posizionati anche in questo modo: + +```neon +- name: John + age: 35 +- name: Peter + age: 28 +``` + +Sta a voi decidere se allineare le chiavi in colonne usando spazi o usare una tabulazione. + +Poiché in PHP si usa la stessa struttura sia per i mapping che per le sequenze, cioè gli array, è possibile unire entrambi. L'indentazione questa volta è la stessa: ```neon - Cat @@ -180,23 +202,23 @@ In PHP, la stessa struttura sarebbe scritta come: ``` -Stringhe .[#toc-strings] ------------------------- -Le stringhe in NEON possono essere racchiuse tra apici singoli o doppi. Ma, come si può vedere, possono anche essere senza apici. +Stringhe +-------- +Le stringhe in NEON possono essere racchiuse tra virgolette singole o doppie. Ma come potete vedere, possono anche essere senza virgolette. ```neon -- A unquoted string in NEON -- 'A singled-quoted string in NEON' -- "A double-quoted string in NEON" +- Stringa in NEON senza virgolette +- 'Stringa in NEON tra virgolette singole' +- "Stringa in NEON tra virgolette doppie" ``` -Se la stringa contiene caratteri `# " ' , : = - [ ] { } ( )` che possono essere confusi con la sintassi NEON, deve essere racchiusa tra virgolette. Si consiglia di utilizzare le virgolette singole perché non utilizzano l'escape. Se è necessario racchiudere una virgoletta in una stringa di questo tipo, raddoppiarla: +Se una stringa contiene i caratteri `# " ' , : = - [ ] { } ( )`, che possono essere confusi con la sintassi NEON, è necessario racchiuderla tra virgolette. Si consiglia di utilizzare le virgolette singole, perché in esse non viene utilizzato l'escaping. Se è necessario scrivere una virgoletta in una tale stringa, raddoppiatela: ```neon -'A single quote '' inside a single-quoted string' +'Virgoletta '' all\'interno di una stringa tra virgolette singole' ``` -Le virgolette doppie consentono di utilizzare le sequenze di escape per scrivere caratteri speciali utilizzando i backslash `\`. All escape sequences as in the JSON format are supported, plus `\_`, ovvero uno spazio non spezzato, cioè `\u00A0`. +Le virgolette doppie consentono di utilizzare sequenze di escape per scrivere caratteri speciali usando le barre rovesciate `\`. Sono supportate tutte le sequenze di escape come nel formato JSON e inoltre `\_`, che è uno spazio indivisibile, cioè `\u00A0`. ```neon - "\t \n \r \f \b \" \\ \/ \_" @@ -204,31 +226,31 @@ Le virgolette doppie consentono di utilizzare le sequenze di escape per scrivere ``` Ci sono altri casi in cui è necessario racchiudere le stringhe tra virgolette: -- iniziano o finiscono con spazi -- assomigliano a numeri, booleani o null -- NEON le interpreta come [date |#dates] +- iniziano o terminano con spazi +- sembrano numeri, booleani o null +- NEON li interpreterebbe come [#data] -Stringhe multilinea .[#toc-multiline-strings] ---------------------------------------------- +Stringhe multiriga +------------------ -Una stringa multilinea inizia e termina con una tripla virgoletta su righe separate. Il rientro della prima riga viene ignorato per tutte le righe: +Una stringa multiriga inizia e finisce con tre virgolette su righe separate. L'indentazione della prima riga viene ignorata per tutte le righe: ```neon ''' - first line - second line - third line + prima riga + seconda riga + terza riga ''' ``` -In PHP si scriverebbe come: +In PHP scriveremmo lo stesso come: ```php -"first line\n\tsecond line\nthird line" // PHP +"prima riga\n\tseconda riga\nterza riga" // PHP ``` -Le sequenze di escape funzionano solo per le stringhe racchiuse tra doppi apici anziché tra apostrofi: +Le sequenze di escape funzionano solo per le stringhe racchiuse tra virgolette doppie invece che apostrofi: ```neon """ @@ -237,24 +259,24 @@ Le sequenze di escape funzionano solo per le stringhe racchiuse tra doppi apici ``` -Numeri .[#toc-numbers] ----------------------- -NEON comprende i numeri scritti nella cosiddetta notazione scientifica e anche i numeri in binario, ottale ed esadecimale: +Numeri +------ +NEON comprende i numeri scritti nella cosiddetta notazione scientifica e anche i numeri in base binaria, ottale ed esadecimale: ```neon -- 12 # un intero -- 12.3 # un float -- +1.2e-34 # un numero esponenziale +- 12 # intero +- 12.3 # float +- +1.2e-34 # numero esponenziale -- 0b11010 # numero binario -- 0o666 # numero ottale -- 0x7A # numero esadecimale +- 0b11010 # numero binario +- 0o666 # numero ottale +- 0x7A # numero esadecimale ``` -Nulli .[#toc-nulls] -------------------- -I null possono essere espressi in NEON utilizzando `null` o non specificando un valore. Sono ammesse anche varianti con la prima lettera maiuscola o con tutte le lettere maiuscole. +Nulls +----- +Null può essere espresso in NEON usando `null` o non specificando un valore. Sono consentite anche varianti con la prima lettera maiuscola o tutte le lettere maiuscole. ```neon a: null @@ -262,37 +284,37 @@ b: ``` -Booleani .[#toc-booleans] -------------------------- -I valori booleani sono espressi in NEON utilizzando `true` / `false` o `yes` / `no`. Sono ammesse anche varianti con la prima lettera maiuscola o con tutte le lettere maiuscole. +Booleans +-------- +I valori logici sono espressi in NEON usando `true` / `false` o `yes` / `no`. Sono consentite anche varianti con la prima lettera maiuscola o tutte le lettere maiuscole. ```neon [true, TRUE, True, false, yes, no] ``` -Date .[#toc-dates] ------------------- -NEON utilizza i seguenti formati per esprimere i dati e li converte automaticamente in oggetti `DateTimeImmutable`: +Data +---- +NEON utilizza i seguenti formati per esprimere le date e li converte automaticamente in oggetti `DateTimeImmutable`: ```neon -- 2016-06-03 # data -- 2016-06-03 19:00:00 # data e ora -- 2016-06-03 19:00:00.1234 # data e microtempo -- 2016-06-03 19:00:00 +0200 # data e ora e fuso orario -- 2016-06-03 19:00:00 +02:00 # data, ora e fuso orario +- 2016-06-03 # data +- 2016-06-03 19:00:00 # data & ora +- 2016-06-03 19:00:00.1234 # data & microtempo +- 2016-06-03 19:00:00 +0200 # data & ora & zona +- 2016-06-03 19:00:00 +02:00 # data & ora & zona ``` -Entità .[#toc-entities] ------------------------ +Entità +------ Un'entità è una struttura che assomiglia a una chiamata di funzione: ```neon Column(type: int, nulls: yes) ``` -In PHP, viene analizzata come un oggetto [api:Nette\Neon\Entity]: +In PHP viene analizzata come un oggetto [api:Nette\Neon\Entity]: ```php // PHP @@ -305,7 +327,7 @@ Le entità possono anche essere concatenate: Column(type: int, nulls: yes) Field(id: 1) ``` -Che viene analizzato in PHP come segue: +Il che viene analizzato in PHP in questo modo: ```php // PHP @@ -315,7 +337,7 @@ new Nette\Neon\Entity(Nette\Neon\Neon::Chain, [ ]) ``` -All'interno delle parentesi, si applicano le regole di notazione inline utilizzate per le mappature e le sequenze, quindi può essere suddiviso in più righe e non è necessario aggiungere virgole: +All'interno delle parentesi valgono le regole per la notazione inline utilizzata per mapping e sequenze, quindi può essere anche multiriga e in tal caso non è necessario specificare le virgole: ```neon Column( @@ -325,20 +347,20 @@ Column( ``` -Commenti .[#toc-comments] -------------------------- -I commenti iniziano con `#` e tutti i caratteri successivi a destra vengono ignorati: +Commenti +-------- +I commenti iniziano con il carattere `#` e tutti i caratteri successivi a destra vengono ignorati: ```neon -# questa riga sarà ignorata dall'interprete -via: 742 Evergreen Terrace -città: Springfield # anche questa viene ignorata -paese: USA +# this line will be ignored by the interpreter +street: 742 Evergreen Terrace +city: Springfield # this is ignored too +country: USA ``` -NEON contro JSON .[#toc-neon-versus-json] -========================================= +Neon versus JSON +================ JSON è un sottoinsieme di NEON. Ogni JSON può quindi essere analizzato come NEON: ```neon @@ -358,7 +380,7 @@ JSON è un sottoinsieme di NEON. Ogni JSON può quindi essere analizzato come NE } ``` -E se potessimo omettere le virgolette? +E se omettessimo le virgolette? ```neon { @@ -377,7 +399,7 @@ users: [ } ``` -Che ne dite di parentesi e virgole? +E le parentesi graffe e le virgole? ```neon php: @@ -394,7 +416,7 @@ users: [ ] ``` -I punti sono più leggibili? +Le liste con i trattini non sono più leggibili? ```neon php: @@ -412,7 +434,7 @@ users: - Rimmer ``` -E i commenti? +Aggiungiamo i commenti? ```neon # my web application config @@ -432,8 +454,7 @@ users: - Rimmer ``` -Avete trovato la sintassi NEON! +Evvai, ora conoscete la sintassi di NEON! -{{description: NEON è un linguaggio di serializzazione dei dati facile da usare. È simile a YAML. La differenza principale è che NEON supporta le "entità" e i caratteri di tabulazione per l'indentazione.}} -{{leftbar: utils:@left-menu}} +{{description: NEON è un formato facilmente leggibile per la serializzazione dei dati. È simile a YAML. La differenza principale è che NEON supporta le "entità" e per l'indentazione possiamo usare sia spazi che tabulazioni.}} diff --git a/neon/ja/@home.texy b/neon/ja/@home.texy new file mode 100644 index 0000000000..c1646c3b4d --- /dev/null +++ b/neon/ja/@home.texy @@ -0,0 +1,87 @@ +Nette NEON +********** + +<div class=perex> + +NEONは、人間が理解しやすいデータシリアライズ言語です。Netteでは設定ファイルに使用されます。[api:Nette\Neon\Neon]は、NEONを操作するための静的クラスです。 + +[NEON 形式|format]について学び、[試してみてください |https://ne-on.org]。 + +</div> + +すべての例は、エイリアスが作成されていることを前提としています: + +```php +use Nette\Neon\Neon; +``` + + +インストール +------ + +ライブラリは[Composer|best-practices:composer]ツールを使用してダウンロードおよびインストールします: + +```shell +composer require nette/neon +``` + +`*.neon`ファイルの構文エラーは、コンソールコマンド`neon-lint`を使用して確認できます: + +```shell +vendor/bin/neon-lint <path> +``` + + +encode(mixed $value, bool $blockMode=false, string $indentation="\t"): string .[method] +--------------------------------------------------------------------------------------- + +`$value`をNEONに変換して返します。パラメータ`$blockMode`にtrueを渡すと、複数行の出力が作成されます。パラメータ`$indentation`は、インデントに使用される文字を指定します(デフォルトはタブ)。 + +```php +Neon::encode($value); // $value を NEON に変換して返します +Neon::encode($value, true); // $value を複数行の NEON に変換して返します +``` + +`encode()`メソッドは、エラー時に`Nette\Neon\Exception`をスローします。 + +```php +try { + $neon = Neon::encode($value); +} catch (Nette\Neon\Exception $e) { + // 例外処理 +} +``` + + +decode(string $neon): mixed .[method] +------------------------------------- + +NEON形式の文字列をPHPに変換します。 + +スカラー、配列、[日付 |format#日付]をDateTimeImmutableオブジェクトとして、[エンティティ |format#エンティティ]を[api:Nette\Neon\Entity]オブジェクトとして返します。 + +```php +Neon::decode('hello: world'); // 配列 ['hello' => 'world'] を返します +``` + +`decode()`メソッドは、エラー時に`Nette\Neon\Exception`をスローします。 + +```php +try { + $value = Neon::decode($neon); +} catch (Nette\Neon\Exception $e) { + // 例外処理 +} +``` + + +decodeFile(string $file): mixed .[method] +----------------------------------------- + +ファイルの内容をNEONからPHPに変換し、BOMがあれば削除します。 + +```php +Neon::decodeFile('config.neon'); +``` + +`decodeFile()`メソッドは、エラー時に`Nette\Neon\Exception`をスローします。 diff --git a/neon/ja/@meta.texy b/neon/ja/@meta.texy new file mode 100644 index 0000000000..11ef7c8697 --- /dev/null +++ b/neon/ja/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette ドキュメンテーション}} +{{leftbar: utils:@left-menu}} diff --git a/neon/ja/format.texy b/neon/ja/format.texy new file mode 100644 index 0000000000..4d668d9660 --- /dev/null +++ b/neon/ja/format.texy @@ -0,0 +1,460 @@ +NEONフォーマット +********** + +.[perex] +NEONは、人間が判読可能な構造化データ形式です。Netteでは設定ファイルに使用されます。また、設定、言語翻訳などの構造化データにも使用されます。[試してみてください |https://ne-on.org]。 + +NEONは *Nette Object Notation* の略です。XMLやJSONよりも複雑でなく、扱いにくくありませんが、同様の機能を提供します。YAMLに非常に似ています。主な利点は、NEONにはいわゆる[#エンティティ]があり、これによりDIサービスの[設定 |https://gist.github.com/dg/26baf3ce8f29d0f751e9dddfaa06504f]が非常に洗練されることです。そして、タブによるインデントが可能です。 + +NEONは、使いやすいようにゼロから構築されています。 + + +統合 +========= + +- NetBeans(組み込みサポートあり) +- PhpStorm([プラグイン |https://plugins.jetbrains.com/plugin/7060?pr]) +- Visual Studio Code([Nette Latte + Neon |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte])または([Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode]) +- Sublime Text 3([プラグイン |https://github.com/FilipStryk/Nette-Latte-Neon-for-Sublime-Text-3]) +- Sublime Text 2([プラグイン |https://github.com/Michal-Mikolas/Nette-package-for-Sublime-Text-2]) +- VIM([プラグイン |https://github.com/fpob/nette.vim]) +- Emacs([プラグイン |https://github.com/Fuco1/neon-mode]) +- Prism.js([統合言語 |https://prismjs.com/#supported-languages]) + + +- [NEON for PHP |@home] +- [NEON for JavaScript |https://github.com/matej21/neon-js] +- [NEON for Python |https://github.com/paveldedik/neon-py]。 + + +構文 +======= + +NEONで書かれたファイルは、通常、配列またはマッピングを表します。 + + +マッピング +----- +マッピングはキーと値のペアのセットであり、PHPでは連想配列と呼ばれます。各ペアは `key: value` として記述され、`:` の後のスペースが必要です。値は、文字列、数値、ブール値、null、シーケンス、または他のマッピングなど、何でもかまいません。 + +```neon +street: 742 Evergreen Terrace +city: Springfield +country: USA +``` + +PHPでは、同じ構造は次のように記述されます: + +```php +[ // PHP + 'street' => '742 Evergreen Terrace', + 'city' => 'Springfield', + 'country' => 'USA', +] +``` + +この表記法はブロック表記と呼ばれます。すべての項目が別々の行にあり、同じインデント(この場合はなし)を持つためです。NEONは、括弧で囲まれたマッピングのインライン表現もサポートしています。インデントは役割を果たさず、個々の要素の区切り文字はカンマまたは改行のいずれかです: + +```neon +{street: 742 Evergreen Terrace, city: Springfield, country: USA} +``` + +同じものを複数行で記述(インデントは関係ありません): + +```neon +{ + street: 742 Evergreen Terrace + city: Springfield, country: USA +} +``` + +<code>: </code> の代わりに `=` を使用することもできます。これはブロック表記とインライン表記の両方で可能です: + +```neon +{street=742 Evergreen Terrace, city=Springfield, country=USA} +``` + + +シーケンス +----- +シーケンスはPHPのインデックス付き配列です。ハイフン `-` とそれに続くスペースで始まる行として記述されます。値は再び、文字列、数値、ブール値、null、シーケンス、または他のマッピングなど、何でもかまいません。 + +```neon +- Cat +- Dog +- Goldfish +``` + +PHPでは、同じ構造は次のように記述されます: + +```php +[ // PHP + 'Cat', + 'Dog', + 'Goldfish', +] +``` + +この表記法はブロック表記と呼ばれます。すべての項目が別々の行にあり、同じインデント(この場合はなし)を持つためです。NEONは、括弧で囲まれたシーケンスのインライン表現もサポートしています。インデントは役割を果たさず、個々の要素の区切り文字はカンマまたは改行のいずれかです: + +```neon +[Cat, Dog, Goldfish] +``` + +同じものを複数行で記述(インデントは関係ありません): + +```neon +[ + Cat, Dog + Goldfish +] +``` + +インライン表現ではインデントされた箇条書きは使用できません。 + + +組み合わせ +----- +マッピングとシーケンスの値は、他のマッピングやシーケンスにすることができます。インデントレベルが重要な役割を果たします。次の例では、シーケンス項目を示すために使用されるハイフンは、キー `pets` よりも大きなインデントを持っているため、項目は最初の行の値になります: + +```neon +pets: + - Cat + - Dog +cars: + - Volvo + - Skoda +``` + +PHPでは、同じ構造は次のように記述されます: + +```php +[ // PHP + 'pets' => [ + 'Cat', + 'Dog', + ], + 'cars' => [ + 'Volvo', + 'Skoda', + ], +] +``` + +ブロック表記とインライン表記を組み合わせることができます: + +```neon +pets: [Cat, Dog] +cars: [ + Volvo, + Skoda, +] +``` + +インライン表記内では、ブロック表記を使用することはできません。これは機能しません: + +```neon +item: [ + pets: + - Cat # これはできません!!! + - Dog +] +``` + +前のケースでは、要素がシーケンスであるマッピングを記述しました。今度は逆にして、マッピングを含むシーケンスを作成してみましょう: + +```neon +- + name: John + age: 35 +- + name: Peter + age: 28 +``` + +箇条書きが別々の行にある必要はありません。このように配置することもできます: + +```neon +- name: John + age: 35 +- name: Peter + age: 28 +``` + +キーをスペースで列に揃えるか、タブを使用するかはあなた次第です。 + +PHPではマッピングとシーケンスの両方に同じ構造、つまり配列が使用されるため、両方をマージできます。今回はインデントは同じです: + +```neon +- Cat +street: 742 Evergreen Terrace +- Goldfish +``` + +PHPでは、同じ構造は次のように記述されます: + +```php +[ // PHP + 'Cat', + 'street' => '742 Evergreen Terrace', + 'Goldfish', +] +``` + + +文字列 +--- +NEONの文字列は、単一引用符または二重引用符で囲むことができます。しかし、ご覧のとおり、引用符なしにすることもできます。 + +```neon +- 引用符なしの NEON 文字列 +- '単一引用符で囲まれた NEON 文字列' +- "二重引用符で囲まれた NEON 文字列" +``` + +文字列にNEON構文と混同される可能性のある文字 `# " ' , : = - [ ] { } ( )` が含まれている場合は、引用符で囲む必要があります。エスケープが使用されないため、単一引用符を使用することをお勧めします。そのような文字列に引用符を記述する必要がある場合は、二重にします: + +```neon +'単一引用符で囲まれた文字列内の引用符 '' ' +``` + +二重引用符を使用すると、バックスラッシュ `\` を使用して特殊文字を記述するためのエスケープシーケンスを使用できます。JSON形式と同様のすべてのエスケープシーケンスがサポートされており、さらに `\_`(ノーブレークスペース、つまり `\u00A0`)もサポートされています。 + +```neon +- "\t \n \r \f \b \" \\ \/ \_" +- "\u00A9" +``` + +文字列を引用符で囲む必要があるその他のケースがあります: +- スペースで始まるか終わる +- 数値、ブール値、またはnullのように見える +- NEONがそれらを[#日付]として解釈する + + +複数行文字列 +------ + +複数行文字列は、別々の行にある3つの引用符で始まり、終わります。最初の行のインデントは、すべての行で無視されます: + +```neon +''' + 最初の行 + 2行目 + 3行目 + ''' +``` + +PHPでは、同じことを次のように記述します: + +```php +"最初の行\n\t2行目\n3行目" // PHP +``` + +エスケープシーケンスは、アポストロフィの代わりに二重引用符で囲まれた文字列でのみ機能します: + +```neon +""" + Copyright \u00A9 +""" +``` + + +数値 +----- +NEONは、いわゆる科学表記法で記述された数値、および2進数、8進数、16進数の数値を理解します: + +```neon +- 12 # 整数 +- 12.3 # 浮動小数点数 +- +1.2e-34 # 指数表記の数値 + +- 0b11010 # 2進数 +- 0o666 # 8進数 +- 0x7A # 16進数 +``` + + +Nulls +----- +Nullは、NEONでは `null` を使用するか、値を指定しないことによって表現できます。最初の文字が大文字またはすべて大文字のバリアントも許可されます。 + +```neon +a: null +b: +``` + + +ブール値 +---- +論理値は、NEONでは `true` / `false` または `yes` / `no` を使用して表現されます。最初の文字が大文字またはすべて大文字のバリアントも許可されます。 + +```neon +[true, TRUE, True, false, yes, no] +``` + + +日付 +----- +NEONは、日付を表現するために次の形式を使用し、それらを自動的に `DateTimeImmutable` オブジェクトに変換します: + +```neon +- 2016-06-03 # 日付 +- 2016-06-03 19:00:00 # 日付と時刻 +- 2016-06-03 19:00:00.1234 # 日付とマイクロ秒 +- 2016-06-03 19:00:00 +0200 # 日付、時刻、タイムゾーン +- 2016-06-03 19:00:00 +02:00 # 日付、時刻、タイムゾーン +``` + + +エンティティ +------ +エンティティは、関数呼び出しに似た構造です: + +```neon +Column(type: int, nulls: yes) +``` + +PHPでは、[api:Nette\Neon\Entity] オブジェクトとして解析されます: + +```php +// PHP +new Nette\Neon\Entity('Column', ['type' => 'int', 'nulls' => true]) +``` + +エンティティは連結することもできます: + +```neon +Column(type: int, nulls: yes) Field(id: 1) +``` + +これはPHPで次のように解析されます: + +```php +// PHP +new Nette\Neon\Entity(Nette\Neon\Neon::Chain, [ + new Nette\Neon\Entity('Column', ['type' => 'int', 'nulls' => true]), + new Nette\Neon\Entity('Field', ['id' => 1]), +]) +``` + +括弧内では、マッピングとシーケンスで使用されるインライン表記のルールが適用されます。つまり、複数行にすることもでき、その場合はカンマを指定する必要はありません: + +```neon +Column( + type: int + nulls: yes +) +``` + + +コメント +---- +コメントは `#` 文字で始まり、右側の後続のすべての文字は無視されます: + +```neon +# この行はインタプリタによって無視されます +street: 742 Evergreen Terrace +city: Springfield # これも無視されます +country: USA +``` + + +Neon 対 JSON +=========== +JSONはNEONのサブセットです。したがって、すべてのJSONはNEONとして解析できます: + +```neon +{ +"php": { + "date.timezone": "Europe\/Prague", + "zlib.output_compression": true +}, +"database": { + "driver": "mysql", + "username": "root", + "password": "beruska92" +}, +"users": [ + "Dave", "Kryten", "Rimmer" +] +} +``` + +引用符を省略したらどうなるでしょうか? + +```neon +{ +php: { + date.timezone: Europe/Prague, + zlib.output_compression: true +}, +database: { + driver: mysql, + username: root, + password: beruska92 +}, +users: [ + Dave, Kryten, Rimmer +] +} +``` + +そして波括弧とカンマは? + +```neon +php: + date.timezone: Europe/Prague + zlib.output_compression: true + +database: + driver: mysql + username: root + password: beruska92 + +users: [ + Dave, Kryten, Rimmer +] +``` + +箇条書きリストの方が読みやすいのではないでしょうか? + +```neon +php: + date.timezone: Europe/Prague + zlib.output_compression: true + +database: + driver: mysql + username: root + password: beruska92 + +users: + - Dave + - Kryten + - Rimmer +``` + +コメントを追加しますか? + +```neon +# 私のウェブアプリケーション設定 + +php: + date.timezone: Europe/Prague + zlib.output_compression: true # gzip を使用 + +database: + driver: mysql + username: root + password: beruska92 + +users: + - Dave + - Kryten + - Rimmer +``` + +やった!これでNEONの構文がわかりました! + + +{{description: NEONは、データシリアライゼーションのための読みやすい形式です。YAMLに似ています。主な違いは、NEONが「エンティティ」をサポートし、インデントにスペースとタブの両方を使用できることです。}} diff --git a/neon/pl/@home.texy b/neon/pl/@home.texy index cb6acdb3fb..133d536c91 100644 --- a/neon/pl/@home.texy +++ b/neon/pl/@home.texy @@ -1,11 +1,11 @@ -Praca z NEON -************ +Nette NEON +********** <div class=perex> -NEON jest czytelnym dla człowieka językiem serializacji danych. Jest on używany w Nette do plików konfiguracyjnych. [api:Nette\Neon\Neon] jest statyczną klasą do pracy z NEONem. +NEON to czytelny dla człowieka język do serializacji danych. Jest używany w Nette do plików konfiguracyjnych. [api:Nette\Neon\Neon] to statyczna klasa do pracy z NEONem. -Poznaj [format NEON |format] i [wypróbuj go |https://ne-on.org]. +Zapoznaj się z [formatem NEON|format] i [wypróbuj go |https://ne-on.org]. </div> @@ -16,39 +16,39 @@ use Nette\Neon\Neon; ``` -Instalacja .[#toc-installation] -------------------------------- +Instalacja +---------- -Pobierz i zainstaluj bibliotekę za pomocą [Composera |best-practices:composer]: +Bibliotekę pobierzesz i zainstalujesz za pomocą narzędzia [Composer|best-practices:composer]: ```shell composer require nette/neon ``` -Możesz sprawdzić błędy składni w plikach `*.neon` używając polecenia konsoli `neon-lint`: +Błędy składni w plikach `*.neon` możesz sprawdzić za pomocą polecenia konsolowego `neon-lint`: ```shell -vendor/bin/neon-lint <cesta> +vendor/bin/neon-lint <ścieżka> ``` -encode(mixed $value, bool $blockMode=false): string .[method] -------------------------------------------------------------- +encode(mixed $value, bool $blockMode=false, string $indentation="\t"): string .[method] +--------------------------------------------------------------------------------------- -Zwraca `$value` przekonwertowany na NEON. Możesz przekazać true jako parametr `$blockMode`, aby utworzyć wyjście wieloliniowe. Parametr `$indentation` określa znaki używane do wcięć (domyślnie jest to tabulator). +Zwraca `$value` przekonwertowane na NEON. Jako parametr `$blockMode` możesz przekazać `true`, co utworzy wyjście wieloliniowe. Parametr `$indentation` określa znak używany do wcięcia (domyślnie tabulator). ```php -Neon::encode($value); // Zwraca $value przekonwertowaną na NEON -Neon::encode($value, true); // Zwraca $value przekonwertowaną na wielowierszowy NEON +Neon::encode($value); // Zwraca $value przekonwertowane na NEON +Neon::encode($value, true); // Zwraca $value przekonwertowane na wieloliniowy NEON ``` -Metoda `encode()` rzuca `Nette\Neon\Exception` na błąd. +Metoda `encode()` w przypadku błędu rzuca `Nette\Neon\Exception`. ```php try { $neon = Neon::encode($value); } catch (Nette\Neon\Exception $e) { - // obsługa wyjątków + // obsługa wyjątku } ``` @@ -56,21 +56,21 @@ try { decode(string $neon): mixed .[method] ------------------------------------- -Konwertuje ciąg znaków z NEON na PHP. +Konwertuje ciąg znaków z NEON do PHP. -Zwraca skalary, tablice, [daty |format#Dates] jako obiekty DateTimeImmutable i [encje |format#Entities] jako obiekty [api:Nette\Neon\Entity]. +Zwraca skalary, tablice, [daty |format#Daty] jako obiekty DateTimeImmutable i [encje |format#Encje] jako obiekty [api:Nette\Neon\Entity]. ```php -Neon::decode('hello: world'); // Vrátí pole ['hello' => 'world'] +Neon::decode('hello: world'); // Zwraca tablicę ['hello' => 'world'] ``` -Metoda `decode()` rzuca `Nette\Neon\Exception` na błąd. +Metoda `decode()` w przypadku błędu rzuca `Nette\Neon\Exception`. ```php try { $value = Neon::decode($neon); } catch (Nette\Neon\Exception $e) { - // obsługa wyjątków + // obsługa wyjątku } ``` @@ -78,13 +78,10 @@ try { decodeFile(string $file): mixed .[method] ----------------------------------------- -Konwertuje zawartość pliku z NEON na PHP i usuwa wszelkie BOM. +Konwertuje zawartość pliku z NEON do PHP i usuwa ewentualny BOM. ```php Neon::decodeFile('config.neon'); ``` -Metoda `decodeFile()` rzuca `Nette\Neon\Exception` na błąd. - - -{{leftbar: utils:@left-menu}} +Metoda `decodeFile()` w przypadku błędu rzuca `Nette\Neon\Exception`. diff --git a/neon/pl/@meta.texy b/neon/pl/@meta.texy new file mode 100644 index 0000000000..8f17999909 --- /dev/null +++ b/neon/pl/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Dokumentacja Nette}} +{{leftbar: utils:@left-menu}} diff --git a/neon/pl/format.texy b/neon/pl/format.texy index 20655bc29e..98a2ecc681 100644 --- a/neon/pl/format.texy +++ b/neon/pl/format.texy @@ -2,40 +2,40 @@ Format NEON *********** .[perex] -NEON jest formatem danych strukturalnych czytelnym dla człowieka. W Nette jest on wykorzystywany do plików konfiguracyjnych. Jest również używany do danych strukturalnych, takich jak ustawienia, tłumaczenia językowe itp. [Spróbuj |https://ne-on.org]. +NEON to czytelny dla człowieka format danych strukturalnych. W Nette jest używany do plików konfiguracyjnych. Jest również używany do danych strukturalnych, takich jak ustawienia, tłumaczenia językowe itp. [Wypróbuj go|https://ne-on.org]. -NEON to skrót od *Nette Object Notation*. Jest mniej skomplikowany i nieporęczny niż XML lub JSON, ale zapewnia podobną funkcjonalność. Jest bardzo podobny do YAML, główną zaletą jest to, że NEON ma tak zwane [encje |#Entities], które sprawiają, że konfigurowanie usług DI jest również sexy. I pozwala na wcięcie tabulatorów. +NEON to skrót od *Nette Object Notation*. Jest mniej skomplikowany i nieporęczny niż XML czy JSON, ale zapewnia podobne funkcje. Jest bardzo podobny do YAML. Główną zaletą jest to, że NEON ma tak zwane [#encje], dzięki którym konfiguracja usług DI jest [również seksowna |https://gist.github.com/dg/26baf3ce8f29d0f751e9dddfaa06504f]. I pozwala na wcięcia za pomocą tabulatorów. -NEON jest zbudowany od podstaw tak, aby był łatwy w użyciu. +NEON został zbudowany od podstaw tak, aby był łatwy w użyciu. -Integracja .[#toc-integration] -============================== +Integracja +========== - NetBeans (ma wbudowane wsparcie) - PhpStorm ([plugin |https://plugins.jetbrains.com/plugin/7060?pr]) -- Visual Studio Code ([plugin |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) +- Visual Studio Code ([Nette Latte + Neon |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) lub [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode]) - Sublime Text 3 ([plugin |https://github.com/FilipStryk/Nette-Latte-Neon-for-Sublime-Text-3]) - Sublime Text 2 ([plugin |https://github.com/Michal-Mikolas/Nette-package-for-Sublime-Text-2]) - VIM ([plugin |https://github.com/fpob/nette.vim]) - Emacs ([plugin |https://github.com/Fuco1/neon-mode]) -- Prism.js ([język zintegrowany |https://prismjs.com/#supported-languages]) +- Prism.js ([zintegrowany język |https://prismjs.com/#supported-languages]) -- [NEON dla PHP |@home] -- [NEON dla JavaScript |https://github.com/matej21/neon-js] -- [NEON dla Pythona |https://github.com/paveldedik/neon-py]. +- [NEON for PHP |@home] +- [NEON for JavaScript |https://github.com/matej21/neon-js] +- [NEON for Python |https://github.com/paveldedik/neon-py]. -Składnia .[#toc-syntax] -======================= +Składnia +======== -Plik napisany w NEONie zwykle reprezentuje tablicę lub odwzorowanie. +Plik napisany w NEON zwykle reprezentuje tablicę lub mapowanie. -Mapowanie .[#toc-mappings] --------------------------- -Mapowanie to zestaw par klucz-wartość, w PHP byłoby to nazywane tablicą asocjacyjną. Każda para zapisana jest jako `key: value`, wymagana jest spacja po `:`. Wartość może być czymkolwiek: ciągiem, liczbą, booleanem, null, ciągiem lub dowolnym innym odwzorowaniem. +Mapowanie +--------- +Mapowanie to zbiór par klucz-wartość, w PHP powiedzielibyśmy tablica asocjacyjna. Każda para jest zapisana jako `key: value`, spacja po `:` jest wymagana. Wartością może być cokolwiek: ciąg znaków, liczba, boolean, null, sekwencja lub inne mapowanie. ```neon street: 742 Evergreen Terrace @@ -53,13 +53,13 @@ W PHP ta sama struktura zostałaby zapisana jako: ] ``` -Notacja ta jest określana jako notacja blokowa, ponieważ wszystkie elementy znajdują się w osobnej linii i mają takie samo wcięcie (w tym przypadku brak). NEON obsługuje również reprezentację inline mapowania, która jest zamknięta w nawiasach, wcięcie nie odgrywa żadnej roli, a separatorem dla każdego elementu jest przecinek lub nowa linia: +Ten zapis nazywa się blokowym, ponieważ wszystkie elementy znajdują się w osobnych wierszach i mają takie samo wcięcie (w tym przypadku żadne). NEON obsługuje również reprezentację inline mapowania, która jest zamknięta w nawiasach, wcięcie nie odgrywa żadnej roli, a separatorem poszczególnych elementów jest przecinek lub nowy wiersz: ```neon {street: 742 Evergreen Terrace, city: Springfield, country: USA} ``` -To samo wpisane w wielu liniach (wcięcie nie ma znaczenia): +To samo zapisane w wielu wierszach (wcięcie nie ma znaczenia): ```neon { @@ -68,16 +68,16 @@ To samo wpisane w wielu liniach (wcięcie nie ma znaczenia): } ``` -Alternatywnie, `=` może być używany zamiast <code>: </code> zarówno w notacji blokowej jak i inline: +Zamiast <code>: </code> można alternatywnie używać `=` zarówno w zapisie blokowym, jak i inline: ```neon {street=742 Evergreen Terrace, city=Springfield, country=USA} ``` -Sekwencja .[#toc-sequences] ---------------------------- -Sekwencje są w PHP tablicami indeksowanymi. Są one zapisane jako linie zaczynające się od myślnika `-`, po którym następuje spacja. Ponownie, wartość może być czymkolwiek: ciągiem, liczbą, booleanem, null, ciągiem lub dowolnym innym odwzorowaniem. +Sekwencje +--------- +Sekwencje to w PHP tablice indeksowane. Zapisuje się je jako wiersze zaczynające się od myślnika `-` poprzedzonego spacją. Wartością ponownie może być cokolwiek: ciąg znaków, liczba, boolean, null, sekwencja lub inne mapowanie. ```neon - Cat @@ -95,13 +95,13 @@ W PHP ta sama struktura zostałaby zapisana jako: ] ``` -Notacja ta jest określana jako notacja blokowa, ponieważ wszystkie elementy znajdują się w osobnej linii i mają takie samo wcięcie (w tym przypadku brak). NEON obsługuje również reprezentację inline sekwencji, która jest zamknięta w nawiasach, wcięcie nie odgrywa żadnej roli, a separatorem dla każdego elementu jest przecinek lub nowa linia: +Ten zapis nazywa się blokowym, ponieważ wszystkie elementy znajdują się w osobnych wierszach i mają takie samo wcięcie (w tym przypadku żadne). NEON obsługuje również reprezentację inline sekwencji, która jest zamknięta w nawiasach, wcięcie nie odgrywa żadnej roli, a separatorem poszczególnych elementów jest przecinek lub nowy wiersz: ```neon [Cat, Dog, Goldfish] ``` -To samo wpisane w wielu liniach (wcięcie nie ma znaczenia): +To samo zapisane w wielu wierszach (wcięcie nie ma znaczenia): ```neon [ @@ -110,20 +110,20 @@ To samo wpisane w wielu liniach (wcięcie nie ma znaczenia): ] ``` -Nie można używać wcięcia w reprezentacji inline. +W reprezentacji inline nie można używać myślników z wcięciem. -Kombinacja .[#toc-combination] ------------------------------- -Wartości mapowania i sekwencji mogą być innymi mapowaniami i sekwencjami. Dużą rolę odgrywa poziom wcięcia. W poniższym przykładzie myślnik użyty do wskazania wpisów sekwencji ma większe wcięcie niż klawisz `pets`, więc wpisy te stają się wartościami pierwszego wiersza: +Kombinacje +---------- +Wartościami mapowań i sekwencji mogą być inne mapowania i sekwencje. Główną rolę odgrywa poziom wcięcia. W poniższym przykładzie myślnik użyty do oznaczenia elementów sekwencji ma większe wcięcie niż klucz `pets`, więc elementy stają się wartością pierwszego wiersza: ```neon pets: - - Cat - - Dog + - Cat + - Dog cars: - - Volvo - - Skoda + - Volvo + - Skoda ``` W PHP ta sama struktura zostałaby zapisana jako: @@ -141,7 +141,7 @@ W PHP ta sama struktura zostałaby zapisana jako: ] ``` -Notacja blokowa i inline mogą być łączone: +Można łączyć zapis blokowy i inline: ```neon pets: [Cat, Dog] @@ -151,17 +151,39 @@ cars: [ ] ``` -Nie można już używać notacji blokowej wewnątrz notacji inline, to nie działa: +Wewnątrz zapisu inline nie można już używać zapisu blokowego, to nie zadziała: ```neon item: [ pets: - - Cat # TOHLE NELZE!!! + - Cat # TO JEST NIEDOZWOLONE!!! - Dog ] ``` -Ponieważ PHP używa tej samej struktury dla mapowania i sekwencji, czyli tablic, można je połączyć. Tym razem wcięcie jest takie samo: +W poprzednim przypadku zapisaliśmy mapowanie, którego elementami były sekwencje, teraz spróbujemy odwrotnie i utworzymy sekwencję zawierającą mapowania: + +```neon +- + name: John + age: 35 +- + name: Peter + age: 28 +``` + +Nie jest konieczne, aby myślniki znajdowały się w osobnych wierszach, można je umieścić również w ten sposób: + +```neon +- name: John + age: 35 +- name: Peter + age: 28 +``` + +To od ciebie zależy, czy wyrównasz klucze w kolumnie za pomocą spacji, czy użyjesz tabulatora. + +Ponieważ w PHP używa się tej samej struktury zarówno dla mapowań, jak i sekwencji, czyli tablic, można je obie połączyć. Wcięcie jest tym razem takie samo: ```neon - Cat @@ -180,55 +202,55 @@ W PHP ta sama struktura zostałaby zapisana jako: ``` -Struny .[#toc-strings] ----------------------- -Łańcuchy NEON mogą być ujęte w pojedyncze lub podwójne cudzysłowy. Ale jak widać, mogą być też nienotowane. +Ciągi / Stringi +--------------- +Ciągi znaków w NEON można umieszczać w pojedynczych lub podwójnych cudzysłowach. Ale jak widać, mogą być również bez cudzysłowów. ```neon -- Řetězec v NEON bez uvozovek -- 'Řetězec v NEON v jednoduchých uvozovkách' -- "Řetězec v NEON ve dvojitých uvozovkách" +- Ciąg w NEON bez cudzysłowów +- 'Ciąg w NEON w pojedynczych cudzysłowach' +- "Ciąg w NEON w podwójnych cudzysłowach" ``` -Jeśli ciąg zawiera znaki `# " ' , : = - [ ] { } ( )`który może być mylony ze składnią NEON, musi być zamknięty w cudzysłowie. Zaleca się używanie pojedynczych cytatów, ponieważ nie używają one ucieczki. Jeśli w takim ciągu trzeba zawrzeć cudzysłów, należy go podwoić: +Jeśli ciąg zawiera znaki `# " ' , : = - [ ] { } ( )`, które można pomylić ze składnią NEON, należy go umieścić w cudzysłowach. Zalecamy użycie pojedynczych cudzysłowów, ponieważ w nich nie stosuje się escapowania. Jeśli potrzebujesz zapisać cudzysłów w takim ciągu, podwój go: ```neon -'Uvozovka '' uvnitř řetezce v jednoduchých uvozovkách' +'Cudzysłów '' wewnątrz ciągu w pojedynczych cudzysłowach' ``` -Podwójne cudzysłowy pozwalają na używanie sekwencji ucieczki do pisania znaków specjalnych za pomocą backslashes `\`. Podporovány jsou všechny escape sekvence jako u formátu JSON a navíc `\_`, czyli niepodzielonej spacji, czyli `\u00A0`. +Podwójne cudzysłowy umożliwiają używanie sekwencji ucieczki do zapisu znaków specjalnych za pomocą odwrotnych ukośników `\`. Obsługiwane są wszystkie sekwencje ucieczki jak w formacie JSON oraz dodatkowo `\_`, co jest spacją niełamliwą, czyli `\u00A0`. ```neon - "\t \n \r \f \b \" \\ \/ \_" - "\u00A9" ``` -Istnieją inne przypadki, w których musisz zamknąć ciągi w cudzysłowach: -- zaczynające się lub kończące spacjami -- wyglądają jak liczby, booleans, lub null -- NEON zinterpretowałby je jako [datę |#Dates] +Istnieją inne przypadki, w których należy umieścić ciągi znaków w cudzysłowach: +- zaczynają się lub kończą spacjami +- wyglądają jak liczby, wartości logiczne lub null +- NEON zrozumiałby je jako [#Daty] -Ciągi wieloliniowe .[#toc-multiline-strings] --------------------------------------------- +Ciągi wieloliniowe +------------------ -Łańcuch wieloliniowy zaczyna się i kończy potrójnym cytatem w oddzielnych liniach. Wcięcie pierwszej linii jest ignorowane we wszystkich liniach: +Ciąg wieloliniowy zaczyna się i kończy potrójnym cudzysłowem w osobnych wierszach. Wcięcie pierwszego wiersza jest ignorowane dla wszystkich wierszy: ```neon ''' - první řádek - druhý řádek - třetí řádek + pierwsza linia + druga linia + trzecia linia ''' ``` -W PHP napisalibyśmy to samo jako: +W PHP to samo napisalibyśmy jako: ```php -"první řádek\n\tdruhý řádek\ntřetí řádek" // PHP +"pierwsza linia\n\tdruga linia\ntrzecia linia" // PHP ``` -Sekwencje ucieczki działają tylko dla łańcuchów ujętych w podwójne cudzysłowy zamiast apostrofów: +Sekwencje ucieczki działają tylko w ciągach ujętych w podwójne cudzysłowy zamiast apostrofów: ```neon """ @@ -237,24 +259,24 @@ Sekwencje ucieczki działają tylko dla łańcuchów ujętych w podwójne cudzys ``` -Numery .[#toc-numbers] ----------------------- -NEON rozumie liczby zapisane w notacji naukowej, a także liczby w notacji binarnej, ósemkowej i szesnastkowej: +Liczby +------ +NEON rozumie liczby zapisane w tzw. notacji naukowej, a także liczby w systemie binarnym, ósemkowym i szesnastkowym: ```neon -- 12 # liczba całkowita -- 12.3 # float -- +1.2e-34 # liczba wykładnicza +- 12 # liczba całkowita +- 12.3 # float +- +1.2e-34 # liczba wykładnicza -- 0b11010 # liczba binarna -- 0o666 # liczba oktalna -- 0x7A # liczba heksadecymalna +- 0b11010 # liczba binarna +- 0o666 # liczba ósemkowa +- 0x7A # liczba szesnastkowa ``` -Nulle .[#toc-nulls] -------------------- -Null może być wyrażony w NEON za pomocą `null` lub poprzez nieokreślenie wartości. Dozwolone są również warianty z wielką literą lub pierwszą literą. +Null +---- +Null można w NEON wyrazić za pomocą `null` lub przez pominięcie wartości. Dozwolone są również warianty z wielką pierwszą literą lub wszystkimi wielkimi literami. ```neon a: null @@ -262,50 +284,50 @@ b: ``` -Booleans .[#toc-booleans] -------------------------- -Wyrażenia logiczne są wyrażane w NEON za pomocą `true` / `false` lub `yes` / `no`. Dozwolone są również warianty z dużymi lub wszystkimi literami. +Wartości logiczne +----------------- +Wartości logiczne są w NEON wyrażane za pomocą `true` / `false` lub `yes` / `no`. Dozwolone są również warianty z wielką pierwszą literą lub wszystkimi wielkimi literami. ```neon [true, TRUE, True, false, yes, no] ``` -Data .[#toc-dates] ------------------- -NEON używa następujących formatów do wyrażania danych i automatycznie konwertuje je na obiekty `DateTimeImmutable`: +Daty +---- +NEON używa do wyrażania dat następujących formatów i automatycznie konwertuje je na obiekty `DateTimeImmutable`: ```neon -- 2016-06-03 # data -- 2016-06-03 19:00:00 # data i czas -- 2016-06-03 19:00:00.1234 # data i czas -- 2016-06-03 19:00:00 +0200 # data & czas & strefa -- 2016-06-03 19:00:00 +02:00 # data & czas & strefa +- 2016-06-03 # data +- 2016-06-03 19:00:00 # data i czas +- 2016-06-03 19:00:00.1234 # data i mikroczas +- 2016-06-03 19:00:00 +0200 # data i czas i strefa +- 2016-06-03 19:00:00 +02:00 # data i czas i strefa ``` -Podmioty .[#toc-entities] -------------------------- +Encje +----- Encja to struktura przypominająca wywołanie funkcji: ```neon Column(type: int, nulls: yes) ``` -W PHP jest on zapisany jako obiekt [api:Nette\Neon\Entity]: +W PHP jest parsowana jako obiekt [api:Nette\Neon\Entity]: ```php // PHP new Nette\Neon\Entity('Column', ['type' => 'int', 'nulls' => true]) ``` -Podmioty mogą być również łączone w łańcuchy: +Encje można również łączyć w łańcuchy: ```neon Column(type: int, nulls: yes) Field(id: 1) ``` -Który w PHP jest parsowany w ten sposób: +Co w PHP jest parsowane w ten sposób: ```php // PHP @@ -315,7 +337,7 @@ new Nette\Neon\Entity(Nette\Neon\Neon::Chain, [ ]) ``` -Wewnątrz nawiasów obowiązują zasady notacji inline stosowane dla mapek i sekwencji, a więc może być ona wielowierszowa i wtedy nie ma potrzeby dołączania przecinków: +Wewnątrz nawiasów obowiązują zasady zapisu inline używane dla mapowań i sekwencji, więc może być również wieloliniowy i wtedy nie trzeba podawać przecinków: ```neon Column( @@ -325,21 +347,21 @@ Column( ``` -Uwagi .[#toc-comments] ----------------------- -Komentarze zaczynają się od `#` i wszystkie kolejne znaki po prawej stronie są ignorowane: +Komentarze +---------- +Komentarze zaczynają się od znaku `#`, a wszystkie następujące znaki po prawej stronie są ignorowane: ```neon -# ta linia będzie ignorowana przez tłumacza -ulica: 742 Evergreen Terrace -miasto: Springfield # to też jest ignorowane -kraj: USA +# ta linia zostanie zignorowana przez interpreter +street: 742 Evergreen Terrace +city: Springfield # to również jest ignorowane +country: USA ``` -Neon kontra JSON .[#toc-neon-versus-json] -========================================= -JSON jest podzbiorem NEON. Dlatego każdy JSON może być parsowany jako NEON: +Neon kontra JSON +================ +JSON jest podzbiorem NEONu. Każdy JSON można więc sparsować jako NEON: ```neon { @@ -358,7 +380,7 @@ JSON jest podzbiorem NEON. Dlatego każdy JSON może być parsowany jako NEON: } ``` -A co jeśli pominiemy cudzysłów? +Co gdybyśmy pominęli cudzysłowy? ```neon { @@ -377,7 +399,7 @@ users: [ } ``` -A nawiasy złożone i przecinki? +A nawiasy klamrowe i przecinki? ```neon php: @@ -394,7 +416,7 @@ users: [ ] ``` -Czy listy wypunktowane nie są łatwiejsze do czytania? +Czy listy z myślnikami nie są bardziej czytelne? ```neon php: @@ -412,14 +434,14 @@ users: - Rimmer ``` -Czy mamy dodawać komentarze? +Dodamy komentarze? ```neon -# my web application config +# konfiguracja mojej aplikacji internetowej php: date.timezone: Europe/Prague - zlib.output_compression: true # use gzip + zlib.output_compression: true # użyj gzip database: driver: mysql @@ -432,8 +454,7 @@ users: - Rimmer ``` -Hura, teraz znasz składnię NEON! +Hurra, teraz znasz składnię NEONu! -{{description: NEON to łatwy do odczytania format do serializacji danych. Jest on podobny do YAML. Główną różnicą jest to, że NEON obsługuje "encje" i możemy używać zarówno spacji, jak i tabulatorów do wcięcia.}} -{{leftbar: utils:@left-menu}} +{{description: NEON to łatwy do odczytania format serializacji danych. Jest podobny do YAML. Główna różnica polega na tym, że NEON obsługuje „encje” i do wcięć możemy używać zarówno spacji, jak i tabulatorów.}} diff --git a/neon/pt/@home.texy b/neon/pt/@home.texy index 5b2111ba20..a15233b262 100644 --- a/neon/pt/@home.texy +++ b/neon/pt/@home.texy @@ -1,54 +1,54 @@ -Funções NEON -************ +Nette NEON +********** <div class=perex> -NEON é uma linguagem de serialização de dados amigável ao ser humano. É utilizada em Nette para arquivos de configuração. [api:Nette\Neon\Neon] é uma classe estática para trabalhar com NEON. +NEON é uma linguagem de serialização de dados legível por humanos. É usado no Nette para arquivos de configuração. [api:Nette\Neon\Neon] é uma classe estática para trabalhar com NEON. -Conheça o [formato NEON |format] e [experimente-o |https://ne-on.org]. +Familiarize-se com o [formato NEON |format] e [experimente |https://ne-on.org]. </div> -Os exemplos a seguir utilizam estes pseudônimos: +Todos os exemplos assumem que um alias foi criado: ```php use Nette\Neon\Neon; ``` -Instalação .[#toc-installation] -------------------------------- +Instalação +---------- -Baixe e instale o pacote usando [o Composer |best-practices:composer]: +Baixe e instale a biblioteca usando o [Composer|best-practices:composer]: ```shell composer require nette/neon ``` -Você pode verificar se há erros de sintaxe nos arquivos `*.neon` usando o comando do console `neon-lint`: +Você pode verificar erros de sintaxe em arquivos `*.neon` usando o comando de console `neon-lint`: ```shell -vendor/bin/neon-lint <path> +vendor/bin/neon-lint <caminho> ``` -encode(mixed $value, bool $blockMode=false): string .[method] -------------------------------------------------------------- +encode(mixed $value, bool $blockMode=false, string $indentation="\t"): string .[method] +--------------------------------------------------------------------------------------- -Retorna `$value` convertido para NEON. Como o parâmetro `$blockMode` você pode passar verdadeiro, o que criará uma saída multilinha. O parâmetro `$indentation` especifica os caracteres usados para indentação (o padrão é tabulação). +Retorna `$value` convertido para NEON. Como parâmetro `$blockMode`, você pode passar true, o que criará uma saída multilinha. O parâmetro `$indentation` especifica os caracteres usados para indentação (o padrão é tabulação). ```php -Neon::encode($value); // Devolve $value convertido em NEON -Neon::encode($value, true); // Retorna $value convertido em NEON multilinha +Neon::encode($value); // Retorna $value convertido para NEON +Neon::encode($value, true); // Retorna $value convertido para NEON multilinha ``` -O método `encode()` lança `Nette\Neon\Exception` sobre erro. +O método `encode()` lança `Nette\Neon\Exception` em caso de erro. ```php try { $neon = Neon::encode($value); -catch (Nette\Neon\Exception $e) { - // Tratamento de exceções +} catch (Nette\Neon\Exception $e) { + // tratamento da exceção } ``` @@ -56,21 +56,21 @@ catch (Nette\Neon\Exception $e) { decode(string $neon): mixed .[method] ------------------------------------- -Converte o valor NEON dado para PHP. +Converte uma string de NEON para PHP. -Retorna escalares, arrays, [data |format#dates] como DateTime Objetos imutáveis, e [entidades |format#Entities] como [api:Nette\Neon\Entity] objetos. +Retorna escalares, arrays, [datas |format#Datas] como objetos DateTimeImmutable e [entidades |format#Entidades] como objetos [api:Nette\Neon\Entity]. ```php -Neon::decode('hello: world'); // Devolve um array ['hello' => 'world'] +Neon::decode('hello: world'); // Retorna o array ['hello' => 'world'] ``` -O método `decode()` lança `Nette\Neon\Exception` sobre erro. +O método `decode()` lança `Nette\Neon\Exception` em caso de erro. ```php try { $value = Neon::decode($neon); } catch (Nette\Neon\Exception $e) { - // Tratamento de exceções + // tratamento da exceção } ``` @@ -78,13 +78,10 @@ try { decodeFile(string $file): mixed .[method] ----------------------------------------- -Converte o conteúdo do arquivo de NEON para PHP e remove qualquer lista técnica. +Converte o conteúdo de um arquivo de NEON para PHP e remove qualquer BOM. ```php Neon::decodeFile('config.neon'); ``` -O método `decodeFile()` lança `Nette\Neon\Exception` sobre erro. - - -{{leftbar: utils:@left-menu}} +O método `decodeFile()` lança `Nette\Neon\Exception` em caso de erro. diff --git a/neon/pt/@meta.texy b/neon/pt/@meta.texy new file mode 100644 index 0000000000..2ae02089c3 --- /dev/null +++ b/neon/pt/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Documentação Nette}} +{{leftbar: utils:@left-menu}} diff --git a/neon/pt/format.texy b/neon/pt/format.texy index 08ee2e82e5..6de60e563b 100644 --- a/neon/pt/format.texy +++ b/neon/pt/format.texy @@ -2,40 +2,40 @@ Formato NEON ************ .[perex] -NEON é um formato de dados estruturados legível por humanos. Em Nette, ele é usado para arquivos de configuração. Também é usado para dados estruturados, tais como configurações, traduções de idiomas, etc. [Experimente-o na caixa de areia |https://ne-on.org]. +NEON é um formato de dados estruturados legível por humanos. No Nette, é usado para arquivos de configuração. Também é usado para dados estruturados, como configurações, traduções de idiomas, etc. [Experimente |https://ne-on.org]. -NEON significa *Nette Object Notation*. É menos complexo e pouco atraente do que XML ou JSON, mas oferece capacidades semelhantes. É muito semelhante ao YAML. A principal vantagem é que a NEON tem as chamadas [entidades |#entities], graças às quais a configuração dos serviços de DI é tão sexy. E permite tabulações para indentação. +NEON é a abreviação de *Nette Object Notation*. É menos complexo e desajeitado que XML ou JSON, mas fornece recursos semelhantes. É muito semelhante ao YAML. A principal vantagem é que o NEON possui as chamadas [#entidades], graças às quais a configuração dos serviços de DI é [tão sexy |https://gist.github.com/dg/26baf3ce8f29d0f751e9dddfaa06504f]. E permite indentação com tabulações. -NEON é construído desde o início para ser simples de usar. +O NEON foi construído desde o início para ser fácil de usar. -Integração .[#toc-integration] -============================== +Integração +========== -- NetBeans (tem suporte integrado) +- NetBeans (possui suporte integrado) - PhpStorm ([plugin |https://plugins.jetbrains.com/plugin/7060?pr]) -- Visual Studio Code ([plugin |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) -- Texto Sublime 3 ([plugin |https://github.com/FilipStryk/Nette-Latte-Neon-for-Sublime-Text-3]) -- Texto Sublime 2 ([plugin |https://github.com/Michal-Mikolas/Nette-package-for-Sublime-Text-2]) +- Visual Studio Code ([Nette Latte + Neon |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) ou [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode]) +- Sublime Text 3 ([plugin |https://github.com/FilipStryk/Nette-Latte-Neon-for-Sublime-Text-3]) +- Sublime Text 2 ([plugin |https://github.com/Michal-Mikolas/Nette-package-for-Sublime-Text-2]) - VIM ([plugin |https://github.com/fpob/nette.vim]) - Emacs ([plugin |https://github.com/Fuco1/neon-mode]) - Prism.js ([linguagem integrada |https://prismjs.com/#supported-languages]) -- [NEON para PHP |@home] -- [NEON para JavaScript |https://github.com/matej21/neon-js] -- [NEON para Python |https://github.com/paveldedik/neon-py]. +- [NEON for PHP |@home] +- [NEON for JavaScript |https://github.com/matej21/neon-js] +- [NEON for Python |https://github.com/paveldedik/neon-py]. -Sintaxe .[#toc-syntax] -====================== +Sintaxe +======= -Um arquivo escrito em NEON geralmente consiste de uma seqüência ou mapeamento. +Um arquivo escrito em NEON geralmente representa um array ou um mapeamento. -Mappings .[#toc-mappings] -------------------------- -O mapeamento é um conjunto de pares de valores-chave, em PHP seria chamado de matriz associativa. Cada par é escrito como `key: value`, um espaço após `:` é necessário. O valor pode ser qualquer coisa: string, número, booleano, nulo, seqüência, ou outro mapeamento. +Mapeamento +---------- +Um mapeamento é um conjunto de pares chave-valor, em PHP seria chamado de array associativo. Cada par é escrito como `key: value`, o espaço após `:` é necessário. O valor pode ser qualquer coisa: string, número, booleano, null, sequência ou outro mapeamento. ```neon street: 742 Evergreen Terrace @@ -53,13 +53,13 @@ Em PHP, a mesma estrutura seria escrita como: ] ``` -Esta notação é chamada de notação de bloco porque todos os itens estão em uma linha separada e têm o mesmo recuo (neste caso, nenhum). NEON também suporta a representação em linha para o mapeamento, que está entre parênteses, o recuo não tem nenhum papel, e o separador de cada elemento é ou uma vírgula ou uma nova linha: +Essa notação é chamada de bloco, porque todos os itens estão em linhas separadas e têm a mesma indentação (neste caso, nenhuma). O NEON também suporta uma representação inline de mapeamento, que é fechada entre chaves, a indentação não desempenha nenhum papel e o separador dos elementos individuais é uma vírgula ou uma nova linha: ```neon {street: 742 Evergreen Terrace, city: Springfield, country: USA} ``` -Esta é a mesma escrita em várias linhas (indentação não importa): +O mesmo escrito em várias linhas (a indentação não importa): ```neon { @@ -68,16 +68,16 @@ Esta é a mesma escrita em várias linhas (indentação não importa): } ``` -Alternativamente, `=` pode ser utilizado em vez de <code>: </code> tanto na notação em bloco como na notação em linha: +Em vez de <code>: </code>, também pode ser usado `=` tanto na notação de bloco quanto na inline: ```neon {street=742 Evergreen Terrace, city=Springfield, country=USA} ``` -Seqüências .[#toc-sequences] ----------------------------- -As sequências são arrays indexados em PHP. Elas são escritas como linhas começando com o hífen `-` seguido por um espaço. Novamente, o valor pode ser qualquer coisa: string, número, booleano, nulo, seqüência, ou outro mapeamento. +Sequência +--------- +Sequências são arrays indexados em PHP. São escritas como linhas começando com um hífen `-` seguido por um espaço. O valor novamente pode ser qualquer coisa: string, número, booleano, null, sequência ou outro mapeamento. ```neon - Cat @@ -95,13 +95,13 @@ Em PHP, a mesma estrutura seria escrita como: ] ``` -Esta notação é chamada de notação de bloco porque todos os itens estão em uma linha separada e têm o mesmo recuo (neste caso, nenhum). NEON também suporta a representação em linha para seqüências, que está entre parênteses, o recuo não tem nenhum papel, e o separador de cada elemento é ou uma vírgula ou uma nova linha: +Essa notação é chamada de bloco, porque todos os itens estão em linhas separadas e têm a mesma indentação (neste caso, nenhuma). O NEON também suporta uma representação inline de sequência, que é fechada entre colchetes, a indentação não desempenha nenhum papel e o separador dos elementos individuais é uma vírgula ou uma nova linha: ```neon [Cat, Dog, Goldfish] ``` -Esta é a mesma escrita em várias linhas (indentação não importa): +O mesmo escrito em várias linhas (a indentação não importa): ```neon [ @@ -110,20 +110,20 @@ Esta é a mesma escrita em várias linhas (indentação não importa): ] ``` -Os hífens não podem ser usados em uma representação em linha. +Na representação inline, não é possível usar marcadores de indentação. -Combinação .[#toc-combination] ------------------------------- -Os valores de mapeamentos e seqüências podem ser outros mapeamentos e seqüências. O nível de indentação tem um papel importante. No exemplo a seguir, o hífen usado para indicar itens de seqüência tem um traço maior do que a chave `pets`, de modo que os itens se tornam o valor da primeira linha: +Combinações +----------- +Os valores de mapeamentos e sequências podem ser outros mapeamentos e sequências. O nível de indentação desempenha o papel principal. No exemplo a seguir, o hífen usado para marcar os itens da sequência tem uma indentação maior que a chave `pets`, então os itens se tornam o valor da primeira linha: ```neon pets: - - Cat - - Dog + - Cat + - Dog cars: - - Volvo - - Skoda + - Volvo + - Skoda ``` Em PHP, a mesma estrutura seria escrita como: @@ -141,7 +141,7 @@ Em PHP, a mesma estrutura seria escrita como: ] ``` -É possível combinar a notação de bloco e em linha: +É possível combinar a notação de bloco e inline: ```neon pets: [Cat, Dog] @@ -151,17 +151,39 @@ cars: [ ] ``` -A notação de bloco não pode mais ser usada dentro de uma notação em linha, isto não funciona: +Dentro da notação inline, não é mais possível usar a notação de bloco, isso não funciona: ```neon item: [ pets: - - Cat # THIS IS NOT POSSIBLE!!! + - Cat # ISTO NÃO É POSSÍVEL!!! - Dog ] ``` -Porque PHP usa a mesma estrutura para mapeamento e seqüências, ou seja, arrays, ambos podem ser fundidos. A indentação é a mesma desta vez: +No caso anterior, escrevemos um mapeamento cujos elementos eram sequências, agora tentaremos o contrário e criaremos uma sequência contendo mapeamentos: + +```neon +- + name: John + age: 35 +- + name: Peter + age: 28 +``` + +Não é necessário que os marcadores estejam em linhas separadas, eles também podem ser colocados desta forma: + +```neon +- name: John + age: 35 +- name: Peter + age: 28 +``` + +Depende de você se alinha as chaves em uma coluna usando espaços ou usa uma tabulação. + +Como em PHP a mesma estrutura, ou seja, array, é usada tanto para mapeamentos quanto para sequências, ambos podem ser mesclados. A indentação desta vez é a mesma: ```neon - Cat @@ -180,55 +202,55 @@ Em PHP, a mesma estrutura seria escrita como: ``` -Cordas .[#toc-strings] ----------------------- -As cordas em NEON podem ser colocadas entre aspas simples ou duplas. Mas, como você pode ver, elas também podem ser sem aspas. +Strings +------- +Strings em NEON podem ser fechadas em aspas simples ou duplas. Mas, como você pode ver, elas também podem ficar sem aspas. ```neon -- A unquoted string in NEON -- 'A singled-quoted string in NEON' -- "A double-quoted string in NEON" +- String em NEON sem aspas +- 'String em NEON em aspas simples' +- "String em NEON em aspas duplas" ``` -Se o fio contiver caracteres `# " ' , : = - [ ] { } ( )` que pode ser confundida com a sintaxe NEON, ela deve ser delimitada entre aspas. Recomendamos o uso de aspas simples, pois elas não utilizam fugas. Se você precisar incluir uma aspas em tal seqüência, duplique-a: +Se a string contiver os caracteres `# " ' , : = - [ ] { } ( )`, que podem ser confundidos com a sintaxe NEON, ela precisa ser fechada entre aspas. Recomendamos o uso de aspas simples, pois nelas não se usa escaping. Se você precisar escrever uma aspa em tal string, duplique-a: ```neon -'A single quote '' inside a single-quoted string' +'Aspas '' dentro de uma string em aspas simples' ``` -As citações duplas permitem utilizar sequências de escape para escrever caracteres especiais utilizando barras invertidas `\`. All escape sequences as in the JSON format are supported, plus `\_`, que é um espaço não quebrável, ou seja `\u00A0`. +Aspas duplas permitem usar sequências de escape para escrever caracteres especiais usando barras invertidas `\`. Todas as sequências de escape como no formato JSON são suportadas, e adicionalmente `\_`, que é um espaço inseparável, ou seja, `\u00A0`. ```neon - "\t \n \r \f \b \" \\ \/ \_" - "\u00A9" ``` -Há outros casos em que é necessário colocar as cordas entre aspas: -- elas começam ou terminam com espaços -- parecem números, booleanos, ou nulos -- NEON as entenderia como [datas |#dates] +Existem outros casos em que é necessário fechar strings entre aspas: +- começam ou terminam com espaços +- parecem números, booleanos ou null +- NEON as entenderia como [#datas] -Cordas Multilíngües .[#toc-multiline-strings] ---------------------------------------------- +Strings Multilinha +------------------ -Uma cadeia de várias linhas começa e termina com uma aspas triplas em linhas separadas. O traço da primeira linha é ignorado para todas as linhas: +Uma string multilinha começa e termina com aspas triplas em linhas separadas. A indentação da primeira linha é ignorada para todas as linhas: ```neon ''' - first line - second line - third line + primeira linha + segunda linha + terceira linha ''' ``` -Em PHP, nós escreveríamos o mesmo que: +Em PHP, escreveríamos o mesmo como: ```php -"first line\n\tsecond line\nthird line" // PHP +"primeira linha\n\tsegunda linha\nterceira linha" // PHP ``` -As seqüências de fuga só funcionam para cordas fechadas em aspas duplas em vez de apóstrofes: +Sequências de escape funcionam apenas para strings fechadas em aspas duplas em vez de apóstrofos: ```neon """ @@ -237,24 +259,24 @@ As seqüências de fuga só funcionam para cordas fechadas em aspas duplas em ve ``` -Números .[#toc-numbers] ------------------------ -NEON entende números escritos na chamada notação científica e também números em binário, octal e hexadecimal: +Números +------- +NEON entende números escritos na chamada notação científica e também números nos sistemas binário, octal e hexadecimal: ```neon -- 12 # um número inteiro -- 12.3 # um carro alegórico -- +1.2e-34 # um número exponencial +- 12 # inteiro +- 12.3 # float +- +1.2e-34 # número exponencial -- 0b11010 # número binário -- 0o666 # número de octal -- 0x7A # número hexa +- 0b11010 # número binário +- 0o666 # número octal +- 0x7A # número hexadecimal ``` -Nulls .[#toc-nulls] -------------------- -Nulo pode ser expresso em NEON usando `null` ou não especificando um valor. Também são permitidas variantes com letras maiúsculas em primeiro lugar ou todas as letras maiúsculas. +Nulos +----- +Null pode ser expresso em NEON usando `null` ou omitindo o valor. Variantes com a primeira letra maiúscula ou todas as letras maiúsculas também são permitidas. ```neon a: null @@ -262,50 +284,50 @@ b: ``` -Booleans .[#toc-booleans] -------------------------- -Os valores booleanos são expressos em NEON usando `true` / `false` ou `yes` / `no`. Também são permitidas variantes com letras maiúsculas em primeiro lugar ou todas as letras maiúsculas. +Booleanos +--------- +Valores lógicos são expressos em NEON usando `true` / `false` ou `yes` / `no`. Variantes com a primeira letra maiúscula ou todas as letras maiúsculas também são permitidas. ```neon [true, TRUE, True, false, yes, no] ``` -Datas .[#toc-dates] -------------------- -NEON usa os seguintes formatos para expressar dados e os converte automaticamente para `DateTimeImmutable` objetos: +Datas +----- +NEON usa os seguintes formatos para expressar datas e os converte automaticamente em objetos `DateTimeImmutable`: ```neon -- 2016-06-03 # data -- 2016-06-03 19:00:00 # data e hora -- 2016-06-03 19:00:00.1234 # data e microtempo -- 2016-06-03 19:00:00 +0200 # data & hora & fuso horário -- 2016-06-03 19:00:00 +02:00 # data & hora & fuso horário +- 2016-06-03 # data +- 2016-06-03 19:00:00 # data & hora +- 2016-06-03 19:00:00.1234 # data & microtempo +- 2016-06-03 19:00:00 +0200 # data & hora & zona +- 2016-06-03 19:00:00 +02:00 # data & hora & zona ``` -Entidades .[#toc-entities] --------------------------- +Entidades +--------- Uma entidade é uma estrutura que se assemelha a uma chamada de função: ```neon Column(type: int, nulls: yes) ``` -Em PHP, ele é analisado como um objeto [api:Nette\Neon\Entity]: +Em PHP, é analisado como um objeto [api:Nette\Neon\Entity]: ```php // PHP new Nette\Neon\Entity('Column', ['type' => 'int', 'nulls' => true]) ``` -As entidades também podem ser encadeadas: +Entidades também podem ser encadeadas: ```neon Column(type: int, nulls: yes) Field(id: 1) ``` -Que é analisado em PHP da seguinte forma: +O que é analisado em PHP desta forma: ```php // PHP @@ -315,7 +337,7 @@ new Nette\Neon\Entity(Nette\Neon\Neon::Chain, [ ]) ``` -Dentro dos parênteses, aplicam-se as regras de notação em linha utilizadas para o mapeamento e seqüências, de modo que pode ser dividida em várias linhas e não é necessário acrescentar vírgulas: +Dentro dos parênteses, aplicam-se as regras para a notação inline usada para mapeamentos e sequências, ou seja, pode ser multilinha e, nesse caso, não é necessário usar vírgulas: ```neon Column( @@ -325,21 +347,21 @@ Column( ``` -Comentários .[#toc-comments] ----------------------------- -Os comentários começam com `#` e todos os caracteres a seguir à direita são ignorados: +Comentários +----------- +Comentários começam com o caractere `#` e todos os caracteres seguintes à direita são ignorados: ```neon -# esta linha será ignorada pelo intérprete -rua: 742 Evergreen Terrace -cidade: Springfield # isto também é ignorado -país: EUA +# esta linha será ignorada pelo interpretador +street: 742 Evergreen Terrace +city: Springfield # isto também é ignorado +country: USA ``` -NEON Versus JSON .[#toc-neon-versus-json] -========================================= -O JSON é um subconjunto do NEON. Cada JSON pode, portanto, ser analisado como NEON: +Neon versus JSON +================ +JSON é um subconjunto do NEON. Portanto, todo JSON pode ser analisado como NEON: ```neon { @@ -358,7 +380,7 @@ O JSON é um subconjunto do NEON. Cada JSON pode, portanto, ser analisado como N } ``` -E se pudéssemos omitir citações? +E se omitirmos as aspas? ```neon { @@ -377,7 +399,7 @@ users: [ } ``` -Que tal aparelhos e vírgulas? +E as chaves e vírgulas? ```neon php: @@ -394,7 +416,7 @@ users: [ ] ``` -As balas são mais legíveis? +As listas com marcadores não são mais legíveis? ```neon php: @@ -412,10 +434,10 @@ users: - Rimmer ``` -Que tal comentários? +Adicionamos comentários? ```neon -# my web application config +# config da minha aplicação web php: date.timezone: Europe/Prague @@ -432,8 +454,7 @@ users: - Rimmer ``` -Você encontrou a sintaxe NEON! +Viva, agora você conhece a sintaxe do NEON! -{{description: NEON é uma linguagem de serialização de dados amigável ao ser humano. É semelhante à YAML. A principal diferença é que o NEON suporta "entidades" e caracteres de tabulação para indentação.}} -{{leftbar: utils:@left-menu}} +{{description: NEON é um formato legível por humanos para serialização de dados. É semelhante ao YAML. A principal diferença é que o NEON suporta "entidades" e podemos usar tanto espaços quanto tabulações para indentação.}} diff --git a/neon/ro/@home.texy b/neon/ro/@home.texy index adc84a2078..d25985bcb0 100644 --- a/neon/ro/@home.texy +++ b/neon/ro/@home.texy @@ -1,45 +1,45 @@ -Funcțiile NEON -************** +Nette NEON +********** <div class=perex> -NEON este un limbaj de serializare a datelor ușor de utilizat. Acesta este utilizat în Nette pentru fișierele de configurare. [api:Nette\Neon\Neon] este o clasă statică pentru lucrul cu NEON. +NEON este un limbaj ușor de înțeles pentru serializarea datelor. Este utilizat în Nette pentru fișierele de configurare. [api:Nette\Neon\Neon] este o clasă statică pentru lucrul cu NEON. -Faceți cunoștință cu [formatul NEON |format] și [încercați-l |https://ne-on.org]. +Familiarizați-vă cu [formatul NEON|format] și [încercați-l |https://ne-on.org]. </div> -Următoarele exemple utilizează aceste pseudonime: +Toate exemplele presupun crearea unui alias: ```php use Nette\Neon\Neon; ``` -Instalare .[#toc-installation] ------------------------------- +Instalare +--------- -Descărcați și instalați pachetul folosind [Composer |best-practices:composer]: +Descărcați și instalați biblioteca folosind [Composer|best-practices:composer]: ```shell composer require nette/neon ``` -Puteți verifica dacă există erori de sintaxă în fișierele `*.neon` utilizând comanda de consolă `neon-lint`: +Puteți verifica erorile de sintaxă în fișierele `*.neon` folosind comanda de consolă `neon-lint`: ```shell -vendor/bin/neon-lint <path> +vendor/bin/neon-lint <cale> ``` -encode(mixed $value, bool $blockMode=false): string .[method] -------------------------------------------------------------- +encode(mixed $value, bool $blockMode=false, string $indentation="\t"): string .[method] +--------------------------------------------------------------------------------------- -Returnează `$value` convertit în NEON. Ca parametru `$blockMode` puteți trece true, care va crea o ieșire multiliniară. Parametrul `$indentation` specifică caracterele utilizate pentru indentare (valoarea implicită este tab). +Returnează `$value` convertită în NEON. Ca parametru `$blockMode`, puteți transmite true, ceea ce va crea o ieșire pe mai multe linii. Parametrul `$indentation` specifică caracterele utilizate pentru indentare (implicit este tabulatorul). ```php -Neon::encode($value); // Returnează $valoare convertită în NEON -Neon::encode($value, true); // Returnează $valoare convertită în NEON multiliniar +Neon::encode($value); // Returnează $value convertită în NEON +Neon::encode($value, true); // Returnează $value convertită în NEON pe mai multe linii ``` Metoda `encode()` aruncă `Nette\Neon\Exception` în caz de eroare. @@ -48,7 +48,7 @@ Metoda `encode()` aruncă `Nette\Neon\Exception` în caz de eroare. try { $neon = Neon::encode($value); } catch (Nette\Neon\Exception $e) { - // Tratarea excepțiilor + // procesarea excepției } ``` @@ -56,12 +56,12 @@ try { decode(string $neon): mixed .[method] ------------------------------------- -Convertește NEON dat în valoare PHP. +Convertește un șir din NEON în PHP. -Returnează scalari, array-uri, [date |format#dates] ca obiecte DateTimeImmutable și [entități |format#Entities] ca obiecte [api:Nette\Neon\Entity]. +Returnează scalari, array-uri, [date |format#Dată] ca obiecte DateTimeImmutable și [entități |format#Entități] ca obiecte [api:Nette\Neon\Entity]. ```php -Neon::decode('hello: world'); // Returnează o matrice ['hello' => 'world'] +Neon::decode('hello: world'); // Returnează array-ul ['hello' => 'world'] ``` Metoda `decode()` aruncă `Nette\Neon\Exception` în caz de eroare. @@ -70,7 +70,7 @@ Metoda `decode()` aruncă `Nette\Neon\Exception` în caz de eroare. try { $value = Neon::decode($neon); } catch (Nette\Neon\Exception $e) { - // Tratarea excepțiilor + // procesarea excepției } ``` @@ -78,13 +78,10 @@ try { decodeFile(string $file): mixed .[method] ----------------------------------------- -Convertește conținutul fișierului din NEON în PHP și elimină orice BOM. +Convertește conținutul unui fișier din NEON în PHP și elimină eventualul BOM. ```php Neon::decodeFile('config.neon'); ``` Metoda `decodeFile()` aruncă `Nette\Neon\Exception` în caz de eroare. - - -{{leftbar: utils:@left-menu}} diff --git a/neon/ro/@meta.texy b/neon/ro/@meta.texy new file mode 100644 index 0000000000..b6dd0af695 --- /dev/null +++ b/neon/ro/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Documentație Nette}} +{{leftbar: utils:@left-menu}} diff --git a/neon/ro/format.texy b/neon/ro/format.texy index 7220439434..98b69e1935 100644 --- a/neon/ro/format.texy +++ b/neon/ro/format.texy @@ -2,19 +2,19 @@ Formatul NEON ************* .[perex] -NEON este un format de date structurate lizibile de către om. În Nette, acesta este utilizat pentru fișierele de configurare. De asemenea, este utilizat pentru date structurate, cum ar fi setările, traducerile de limbă etc. [Încercați-l în sandbox |https://ne-on.org]. +NEON este un format de date structurate lizibil pentru om. În Nette, este utilizat pentru fișierele de configurare. De asemenea, este utilizat pentru date structurate, cum ar fi setări, traduceri lingvistice etc. [Încercați-l|https://ne-on.org]. -NEON este acronimul de la *Nette Object Notation*. Este mai puțin complexă și mai neîndemânatică decât XML sau JSON, dar oferă capacități similare. Este foarte asemănătoare cu YAML. Principalul avantaj este că NEON are așa-numitele [entități |#entities], datorită cărora configurarea serviciilor DI este atât de sexy. Și permite tabulări pentru indentare. +NEON este acronimul pentru *Nette Object Notation*. Este mai puțin complex și greoi decât XML sau JSON, dar oferă funcționalități similare. Este foarte asemănător cu YAML. Principalul avantaj este că NEON are așa-numitele [#entități], datorită cărora configurarea serviciilor DI este [la fel de sexy |https://gist.github.com/dg/26baf3ce8f29d0f751e9dddfaa06504f]. Și permite indentarea cu tabulatori. -NEON este construit de la zero pentru a fi simplu de utilizat. +NEON este construit de la zero pentru a fi ușor de utilizat. -Integrare .[#toc-integration] -============================= +Integrare +========= -- NetBeans (are suport integrat) +- NetBeans (are suport încorporat) - PhpStorm ([plugin |https://plugins.jetbrains.com/plugin/7060?pr]) -- Visual Studio Code ([plugin |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) +- Visual Studio Code ([Nette Latte + Neon |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) sau [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode]) - Sublime Text 3 ([plugin |https://github.com/FilipStryk/Nette-Latte-Neon-for-Sublime-Text-3]) - Sublime Text 2 ([plugin |https://github.com/Michal-Mikolas/Nette-package-for-Sublime-Text-2]) - VIM ([plugin |https://github.com/fpob/nette.vim]) @@ -22,20 +22,20 @@ Integrare .[#toc-integration] - Prism.js ([limbaj integrat |https://prismjs.com/#supported-languages]) -- [NEON pentru PHP |@home] -- [NEON pentru JavaScript |https://github.com/matej21/neon-js] -- [NEON pentru Python |https://github.com/paveldedik/neon-py]. +- [NEON for PHP |@home] +- [NEON for JavaScript |https://github.com/matej21/neon-js] +- [NEON for Python |https://github.com/paveldedik/neon-py]. -Sintaxa .[#toc-syntax] -====================== +Sintaxă +======= -Un fișier scris în NEON constă, de obicei, într-o secvență sau o cartografiere. +Un fișier scris în NEON reprezintă de obicei un array sau o mapare. -Mapări .[#toc-mappings] ------------------------ -Mapping-ul este un set de perechi cheie-valoare, în PHP s-ar numi un array asociativ. Fiecare pereche se scrie ca `key: value`, fiind necesar un spațiu după `:`. Valoarea poate fi orice: șir de caractere, număr, boolean, null, secvență sau altă mapare. +Mapare +------ +Maparea este un set de perechi cheie-valoare, în PHP s-ar spune un array asociativ. Fiecare pereche este scrisă ca `key: value`, spațiul după `:` este necesar. Valoarea poate fi orice: șir, număr, boolean, null, secvență sau altă mapare. ```neon street: 742 Evergreen Terrace @@ -43,7 +43,7 @@ city: Springfield country: USA ``` -În PHP, aceeași structură ar fi scrisă ca: +În PHP, aceeași structură s-ar scrie ca: ```php [ // PHP @@ -53,13 +53,13 @@ country: USA ] ``` -Această notație se numește notație de bloc, deoarece toate elementele se află pe o linie separată și au aceeași indentare (niciuna în acest caz). NEON acceptă, de asemenea, reprezentarea în linie pentru cartografiere, care este închisă între paranteze, indentarea nu joacă niciun rol, iar separatorul fiecărui element este fie o virgulă, fie o linie nouă: +Această scriere se numește bloc, deoarece toate elementele sunt pe linii separate și au aceeași indentare (în acest caz, niciuna). NEON suportă și reprezentarea inline a mapării, care este închisă între paranteze, indentarea nu joacă niciun rol, iar separatorul elementelor individuale este fie virgula, fie o linie nouă: ```neon {street: 742 Evergreen Terrace, city: Springfield, country: USA} ``` -Acesta este același lucru scris pe mai multe linii (indentarea nu are importanță): +Același lucru scris pe mai multe linii (indentarea nu contează): ```neon { @@ -68,71 +68,71 @@ Acesta este același lucru scris pe mai multe linii (indentarea nu are importan } ``` -Alternativ, se poate folosi `=` în loc de <code>: </code>, atât în notație bloc, cât și în notație inline: +În loc de <code>: </code> se poate folosi alternativ `=` atât în scrierea bloc, cât și în cea inline: ```neon {street=742 Evergreen Terrace, city=Springfield, country=USA} ``` -Secvențe .[#toc-sequences] --------------------------- -Secvențele sunt array-uri indexate în PHP. Acestea sunt scrise ca linii care încep cu cratima `-` urmată de un spațiu. Din nou, valoarea poate fi orice: șir de caractere, număr, boolean, null, secvență sau altă cartografiere. +Secvențe +-------- +Secvențele sunt în PHP array-uri indexate. Se scriu ca linii care încep cu cratimă `-` urmată de un spațiu. Valoarea poate fi din nou orice: șir, număr, boolean, null, secvență sau altă mapare. ```neon -- Cat -- Dog -- Goldfish +- Pisică +- Câine +- Peștișor auriu ``` -În PHP, aceeași structură ar fi scrisă astfel: +În PHP, aceeași structură s-ar scrie ca: ```php [ // PHP - 'Cat', - 'Dog', - 'Goldfish', + 'Pisică', + 'Câine', + 'Peștișor auriu', ] ``` -Această notație se numește notație de bloc, deoarece toate elementele se află pe o linie separată și au aceeași indentare (niciuna în acest caz). NEON acceptă, de asemenea, reprezentarea în linie pentru secvențe, care este cuprinsă între paranteze, indentarea nu joacă niciun rol, iar separatorul fiecărui element este fie o virgulă, fie o linie nouă: +Această scriere se numește bloc, deoarece toate elementele sunt pe linii separate și au aceeași indentare (în acest caz, niciuna). NEON suportă și reprezentarea inline a secvenței, care este închisă între paranteze drepte, indentarea nu joacă niciun rol, iar separatorul elementelor individuale este fie virgula, fie o linie nouă: ```neon -[Cat, Dog, Goldfish] +[Pisică, Câine, Peștișor auriu] ``` -Este vorba de același lucru scris pe mai multe linii (indentarea nu are importanță): +Același lucru scris pe mai multe linii (indentarea nu contează): ```neon [ - Cat, Dog - Goldfish + Pisică, Câine + Peștișor auriu ] ``` -Nu se pot folosi cratime într-o reprezentare în linie. +În reprezentarea inline nu se pot folosi cratimele de indentare. -Combinație .[#toc-combination] ------------------------------- -Valorile maparelor și secvențelor pot fi alte mape și secvențe. Nivelul de indentare joacă un rol important. În exemplul următor, cratima utilizată pentru a indica elementele secvenței are o indentare mai mare decât tasta `pets`, astfel încât elementele devin valoarea primului rând: +Combinații +---------- +Valorile mapărilor și secvențelor pot fi alte mapări și secvențe. Rolul principal îl joacă nivelul de indentare. În exemplul următor, cratima folosită pentru a marca elementele secvenței are o indentare mai mare decât cheia `pets`, astfel încât elementele devin valoarea primei linii: ```neon pets: - - Cat - - Dog + - Pisică + - Câine cars: - - Volvo - - Skoda + - Volvo + - Skoda ``` -În PHP, aceeași structură ar fi scrisă astfel:: +În PHP, aceeași structură s-ar scrie ca: ```php [ // PHP 'pets' => [ - 'Cat', - 'Dog', + 'Pisică', + 'Câine', ], 'cars' => [ 'Volvo', @@ -141,94 +141,116 @@ cars: ] ``` -Este posibil să se combine notarea în bloc și în linie: +Se poate combina scrierea bloc și inline: ```neon -pets: [Cat, Dog] +pets: [Pisică, Câine] cars: [ Volvo, Skoda, ] ``` -Notarea în bloc nu mai poate fi utilizată în interiorul unei notații inline, deoarece nu funcționează: +În interiorul scrierii inline nu se mai poate folosi scrierea bloc, acest lucru nu funcționează: ```neon item: [ pets: - - Cat # ACEST LUCRU NU ESTE POSIBIL!!! - - Dog + - Pisică # ACEST LUCRU NU ESTE POSIBIL!!! + - Câine ] ``` -Deoarece PHP utilizează aceeași structură pentru cartografiere și secvențe, adică array-uri, ambele pot fi îmbinate. Indentarea este aceeași de această dată: +În cazul anterior am scris o mapare ale cărei elemente erau secvențe, acum vom încerca invers și vom crea o secvență care conține mapări: ```neon -- Cat +- + name: John + age: 35 +- + name: Peter + age: 28 +``` + +Nu este necesar ca cratimele să fie pe linii separate, pot fi plasate și în acest mod: + +```neon +- name: John + age: 35 +- name: Peter + age: 28 +``` + +Depinde de dvs. dacă aliniați cheile într-o coloană folosind spații sau folosiți un tabulator. + +Deoarece în PHP se folosește aceeași structură atât pentru mapări, cât și pentru secvențe, adică array-ul, ambele pot fi combinate. Indentarea este de data aceasta aceeași: + +```neon +- Pisică street: 742 Evergreen Terrace -- Goldfish +- Peștișor auriu ``` -În PHP, aceeași structură ar fi scrisă ca: +În PHP, aceeași structură s-ar scrie ca: ```php [ // PHP - 'Cat', + 'Pisică', 'street' => '742 Evergreen Terrace', - 'Goldfish', + 'Peștișor auriu', ] ``` -Șiruri de caractere .[#toc-strings] ------------------------------------ -Șirurile de caractere în NEON pot fi incluse în ghilimele simple sau duble. Dar, după cum puteți vedea, ele pot fi și fără ghilimele. +Șiruri +------ +Șirurile în NEON pot fi închise între ghilimele simple sau duble. Dar, după cum vedeți, pot fi și fără ghilimele. ```neon -- A unquoted string in NEON -- 'A singled-quoted string in NEON' -- "A double-quoted string in NEON" +- Șir în NEON fără ghilimele +- 'Șir în NEON între ghilimele simple' +- "Șir în NEON între ghilimele duble" ``` -În cazul în care șirul conține caractere `# " ' , : = - [ ] { } ( )` care pot fi confundate cu sintaxa NEON, acesta trebuie să fie inclus în ghilimele. Vă recomandăm să folosiți ghilimele simple, deoarece acestea nu utilizează scăpări. Dacă trebuie să includeți un ghilimele într-un astfel de șir, dublați-l: +Dacă șirul conține caracterele `# " ' , : = - [ ] { } ( )`, care pot fi confundate cu sintaxa NEON, trebuie închis între ghilimele. Recomandăm utilizarea ghilimelelor simple, deoarece în ele nu se folosește escaparea. Dacă aveți nevoie să scrieți o ghilimea într-un astfel de șir, dublați-o: ```neon -'A single quote '' inside a single-quoted string' +'Ghilimea '' în interiorul unui șir între ghilimele simple' ``` -Ghilimelele duble vă permit să folosiți secvențe de evadare pentru a scrie caractere speciale folosind backslash-uri `\`. All escape sequences as in the JSON format are supported, plus `\_`, care este un spațiu fără întrerupere, adică `\u00A0`. +Ghilimelele duble permit utilizarea secvențelor de escape pentru scrierea caracterelor speciale folosind backslash-uri `\`. Sunt suportate toate secvențele de escape ca în formatul JSON și, în plus, `\_`, care este un spațiu nedivizibil, adică `\u00A0`. ```neon - "\t \n \r \f \b \" \\ \/ \_" - "\u00A9" ``` -Există și alte cazuri în care este necesar să închideți șirurile de caractere în ghilimele: -- acestea încep sau se termină cu spații -- seamănă cu numere, booleeni sau nul -- NEON le va înțelege ca fiind [date |#dates] +Există și alte cazuri în care este necesar să închideți șirurile între ghilimele: +- încep sau se termină cu spații +- arată ca numere, booleeni sau null +- NEON le-ar înțelege ca [#dată] -Șiruri de caractere cu mai multe linii .[#toc-multiline-strings] ----------------------------------------------------------------- +Șiruri pe mai multe linii +------------------------- -Un șir de caractere multiliniar începe și se termină cu un ghilimele triplu pe linii separate. Indentarea primei linii este ignorată pentru toate liniile: +Un șir pe mai multe linii începe și se termină cu trei ghilimele pe linii separate. Indentarea primei linii este ignorată pentru toate liniile: ```neon ''' - first line - second line - third line + prima linie + a doua linie + a treia linie ''' ``` -În PHP, am scrie același lucru ca: +În PHP am scrie același lucru ca: ```php -"first line\n\tsecond line\nthird line" // PHP +"prima linie\n\ta doua linie\na treia linie" // PHP ``` -Secvențele de scăpare funcționează numai pentru șirurile de caractere încadrate în ghilimele duble în loc de apostrofuri: +Secvențele de escape funcționează doar pentru șirurile închise între ghilimele duble în loc de apostrofuri: ```neon """ @@ -237,14 +259,14 @@ Secvențele de scăpare funcționează numai pentru șirurile de caractere înca ``` -Numere .[#toc-numbers] ----------------------- -NEON înțelege numerele scrise în așa-numita notație științifică, precum și numerele în binar, octal și hexazecimal: +Numere +------ +NEON înțelege numerele scrise în așa-numita notație științifică și, de asemenea, numerele în sistem binar, octal și hexazecimal: ```neon -- 12 # un număr întreg -- 12.3 # un float -- +1.2e-34 # un număr exponențial +- 12 # număr întreg +- 12.3 # float +- +1.2e-34 # număr exponențial - 0b11010 # număr binar - 0o666 # număr octal @@ -252,9 +274,9 @@ NEON înțelege numerele scrise în așa-numita notație științifică, precum ``` -Nuli .[#toc-nulls] ------------------- -Null poate fi exprimat în NEON prin utilizarea `null` sau prin nespecificarea unei valori. De asemenea, sunt permise variantele cu majusculă sau cu toate literele majuscule. +Nulls +----- +Null poate fi exprimat în NEON folosind `null` sau prin neindicarea valorii. Sunt permise și variantele cu prima literă mare sau cu toate literele mari. ```neon a: null @@ -262,50 +284,50 @@ b: ``` -Booleeni .[#toc-booleans] -------------------------- -Valorile booleene sunt exprimate în NEON folosind `true` / `false` sau `yes` / `no`. Sunt permise, de asemenea, variante cu majusculă sau cu toate literele majuscule. +Booleans +-------- +Valorile logice sunt exprimate în NEON folosind `true` / `false` sau `yes` / `no`. Sunt permise și variantele cu prima literă mare sau cu toate literele mari. ```neon [true, TRUE, True, false, yes, no] ``` -Date .[#toc-dates] ------------------- -NEON utilizează următoarele formate pentru a exprima datele și le convertește automat în obiecte `DateTimeImmutable`: +Dată +---- +NEON folosește următoarele formate pentru a exprima datele și le convertește automat în obiecte `DateTimeImmutable`: ```neon -- 2016-06-03 # data -- 2016-06-03 19:00:00 # data și ora -- 2016-06-03 19:00:00.1234 # data și microora +- 2016-06-03 # dată +- 2016-06-03 19:00:00 # dată & oră +- 2016-06-03 19:00:00.1234 # dată & microtimp - 2016-06-03 19:00:00 +0200 # dată & oră & fus orar - 2016-06-03 19:00:00 +02:00 # dată & oră & fus orar ``` -Entități .[#toc-entities] -------------------------- -O entitate este o structură care seamănă cu un apel de funcție: +Entități +-------- +Entitatea este o structură care seamănă cu un apel de funcție: ```neon Column(type: int, nulls: yes) ``` -În PHP, aceasta este analizată ca un obiect [api:Nette\Neon\Entity]: +În PHP se parsează ca obiect [api:Nette\Neon\Entity]: ```php // PHP new Nette\Neon\Entity('Column', ['type' => 'int', 'nulls' => true]) ``` -Entitățile pot fi, de asemenea, înlănțuite: +Entitățile se pot și înlănțui: ```neon Column(type: int, nulls: yes) Field(id: 1) ``` -Care este analizat în PHP după cum urmează: +Ceea ce în PHP se parsează în acest mod: ```php // PHP @@ -315,7 +337,7 @@ new Nette\Neon\Entity(Nette\Neon\Neon::Chain, [ ]) ``` -În interiorul parantezelor, se aplică regulile de notație în linie utilizate pentru cartografiere și secvențe, astfel încât poate fi împărțită în mai multe linii și nu este necesar să se adauge virgule: +În interiorul parantezelor se aplică regulile pentru scrierea inline folosită la mapări și secvențe, adică poate fi și pe mai multe linii și atunci nu este necesar să se specifice virgule: ```neon Column( @@ -325,21 +347,21 @@ Column( ``` -Comentarii .[#toc-comments] ---------------------------- -Comentariile încep cu `#`, iar toate caracterele care urmează în dreapta sunt ignorate: +Comentarii +---------- +Comentariile încep cu caracterul `#` și toate caracterele următoare la dreapta sunt ignorate: ```neon -# această linie va fi ignorată de către interpretor +# această linie va fi ignorată de interpretor street: 742 Evergreen Terrace city: Springfield # și aceasta este ignorată country: USA ``` -NEON față de JSON .[#toc-neon-versus-json] -========================================== -JSON este un subset al NEON. Prin urmare, fiecare JSON poate fi analizat ca NEON: +Neon versus JSON +================ +JSON este un subset al NEON. Fiecare JSON poate fi, prin urmare, parsat ca NEON: ```neon { @@ -358,7 +380,7 @@ JSON este un subset al NEON. Prin urmare, fiecare JSON poate fi analizat ca NEON } ``` -Ce s-ar întâmpla dacă am putea omite ghilimelele? +Ce s-ar întâmpla dacă am omite ghilimelele? ```neon { @@ -377,7 +399,7 @@ users: [ } ``` -Ce zici de paranteze și virgule? +Și acoladele și virgulele? ```neon php: @@ -394,7 +416,7 @@ users: [ ] ``` -Punctele sunt mai lizibile? +Nu sunt listele cu cratime mai lizibile? ```neon php: @@ -412,14 +434,14 @@ users: - Rimmer ``` -Dar comentariile? +Adăugăm comentarii? ```neon # configurația aplicației mele web php: date.timezone: Europe/Prague - zlib.output_compression: true # folosiți gzip + zlib.output_compression: true # folosește gzip database: driver: mysql @@ -432,8 +454,7 @@ users: - Rimmer ``` -Ați găsit sintaxa NEON! +Ura, acum cunoașteți sintaxa NEON! -{{description: NEON este un limbaj de serializare a datelor ușor de utilizat. Este similar cu YAML. Principala diferență este că NEON suportă "entități" și caractere de tabulare pentru indentare.}} -{{leftbar: utils:@left-menu}} +{{description: NEON este un format ușor de citit pentru serializarea datelor. Este similar cu YAML. Principala diferență este că NEON suportă „entități” și pentru indentare putem folosi atât spații, cât și tabulatori.}} diff --git a/neon/ru/@home.texy b/neon/ru/@home.texy index afc65ca75c..b147c9f670 100644 --- a/neon/ru/@home.texy +++ b/neon/ru/@home.texy @@ -1,45 +1,45 @@ -Функции NEON -************ +Nette NEON +********** <div class=perex> -NEON - это удобный для человека язык сериализации данных. Он используется в Nette для конфигурационных файлов. [api:Nette\Neon\Neon] - это статический класс для работы с NEON. +NEON — это понятный человеку язык для сериализации данных. Он используется в Nette для конфигурационных файлов. [api:Nette\Neon\Neon] — это статический класс для работы с NEON. -Познакомьтесь с [форматом NEON |format] и [попробуйте его |https://ne-on.org] в работе. +Ознакомьтесь с [форматом NEON|format] и [попробуйте его |https://ne-on.org]. </div> -В следующих примерах используются эти псевдонимы: +Все примеры предполагают созданный псевдоним: ```php use Nette\Neon\Neon; ``` -Установка .[#toc-installation] ------------------------------- +Установка +--------- -Загрузите и установите пакет с помощью [Composer |best-practices:composer]: +Вы можете скачать и установить библиотеку с помощью [Composer|best-practices:composer]: ```shell composer require nette/neon ``` -Вы можете проверить наличие синтаксических ошибок в файлах `*.neon` с помощью консольной команды `neon-lint`: +Ошибки синтаксиса в файлах `*.neon` можно проверить с помощью консольной команды `neon-lint`: ```shell -vendor/bin/neon-lint <path> +vendor/bin/neon-lint <путь> ``` -encode(mixed $value, bool $blockMode=false): string .[method] -------------------------------------------------------------- +encode(mixed $value, bool $blockMode=false, string $indentation="\t"): string .[method] +--------------------------------------------------------------------------------------- -Возвращает `$value`, преобразованный в NEON. В качестве параметра `$blockMode` можно передать true, что создаст многострочный вывод. Параметр `$indentation` задает символы, используемые для отступов (по умолчанию это tab). +Возвращает `$value`, преобразованное в NEON. В качестве параметра `$blockMode` можно передать true, что создаст многострочный вывод. Параметр `$indentation` определяет символы, используемые для отступа (по умолчанию табуляция). ```php -Neon::encode($value); // Returns $value converted to NEON -Neon::encode($value, true); // Returns $value converted to multiline NEON +Neon::encode($value); // Возвращает $value, преобразованное в NEON +Neon::encode($value, true); // Возвращает $value, преобразованное в многострочный NEON ``` Метод `encode()` при ошибке выбрасывает `Nette\Neon\Exception`. @@ -48,7 +48,7 @@ Neon::encode($value, true); // Returns $value converted to multiline NEON try { $neon = Neon::encode($value); } catch (Nette\Neon\Exception $e) { - // Exception handling + // обработка исключения } ``` @@ -56,21 +56,21 @@ try { decode(string $neon): mixed .[method] ------------------------------------- -Конвертирует заданный NEON в значение PHP. +Преобразует строку из NEON в PHP. -Возвращает скаляры, массивы, [дату |format#Dates] как объекты DateTimeImmutable и [сущности |format#Entities] как объекты [api:Nette\Neon\Entity]. +Возвращает скаляры, массивы, [даты |format#Дата] как объекты DateTimeImmutable и [сущности |format#Сущности] как объекты [api:Nette\Neon\Entity]. ```php -Neon::decode('hello: world'); // Returns an array ['hello' => 'world'] +Neon::decode('hello: world'); // Возвращает массив ['hello' => 'world'] ``` -Метод `decode()` выбрасывает `Nette\Neon\Exception` при ошибке. +Метод `decode()` при ошибке выбрасывает `Nette\Neon\Exception`. ```php try { $value = Neon::decode($neon); } catch (Nette\Neon\Exception $e) { - // Exception handling + // обработка исключения } ``` @@ -78,13 +78,10 @@ try { decodeFile(string $file): mixed .[method] ----------------------------------------- -Преобразует содержимое файла из NEON в PHP и удаляет все BOM. +Преобразует содержимое файла из NEON в PHP и удаляет возможный BOM. ```php Neon::decodeFile('config.neon'); ``` -Метод `decodeFile()` при ошибке бросает `Nette\Neon\Exception`. - - -{{leftbar: utils:@left-menu}} +Метод `decodeFile()` при ошибке выбрасывает `Nette\Neon\Exception`. diff --git a/neon/ru/@meta.texy b/neon/ru/@meta.texy new file mode 100644 index 0000000000..a9f4271a5c --- /dev/null +++ b/neon/ru/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Документация Nette}} +{{leftbar: utils:@left-menu}} diff --git a/neon/ru/format.texy b/neon/ru/format.texy index 30f5b5ab63..b34e014c24 100644 --- a/neon/ru/format.texy +++ b/neon/ru/format.texy @@ -2,40 +2,40 @@ *********** .[perex] -NEON - это человекочитаемый структурированный формат данных. В Nette он используется для файлов конфигурации. Он также используется для структурированных данных, таких как настройки, языковые переводы и т.д. [Попробуйте его в песочнице |https://ne-on.org]. +NEON — это человекочитаемый формат структурированных данных. В Nette он используется для конфигурационных файлов. Он также используется для структурированных данных, таких как настройки, языковые переводы и т. д. [Попробуйте его|https://ne-on.org]. -NEON расшифровывается как *Nette Object Notation*. Он менее сложен и неуклюж, чем XML или JSON, но предоставляет аналогичные возможности. Он очень похож на YAML. Главное преимущество в том, что NEON имеет так называемые [сущности |#Entities], благодаря которым конфигурация DI-сервисов так сексуальна. И позволяет использовать вкладки для отступов. +NEON — это аббревиатура от *Nette Object Notation*. Он менее сложен и громоздок, чем XML или JSON, но предоставляет схожие функции. Он очень похож на YAML. Главное преимущество заключается в том, что NEON имеет так называемые [#сущности], благодаря которым конфигурация DI-сервисов [тоже сексуальна |https://gist.github.com/dg/26baf3ce8f29d0f751e9dddfaa06504f]. И позволяет использовать табуляцию для отступов. -NEON создан с нуля, чтобы быть простым в использовании. +NEON построен с нуля так, чтобы быть простым в использовании. -Интеграция .[#toc-integration] -============================== +Интеграция +========== - NetBeans (имеет встроенную поддержку) - PhpStorm ([плагин |https://plugins.jetbrains.com/plugin/7060?pr]) -- Visual Studio Code ([плагин |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) +- Visual Studio Code ([Nette Latte + Neon |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) или [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode]) - Sublime Text 3 ([плагин |https://github.com/FilipStryk/Nette-Latte-Neon-for-Sublime-Text-3]) - Sublime Text 2 ([плагин |https://github.com/Michal-Mikolas/Nette-package-for-Sublime-Text-2]) - VIM ([плагин |https://github.com/fpob/nette.vim]) - Emacs ([плагин |https://github.com/Fuco1/neon-mode]) -- Prism.js ([интегрированный язык |https://prismjs.com/#supported-languages]) +- Prism.js ([встроенный язык |https://prismjs.com/#supported-languages]) -- [NEON для PHP |@home] -- [NEON для JavaScript |https://github.com/matej21/neon-js] -- [NEON для Python |https://github.com/paveldedik/neon-py]. +- [NEON for PHP |@home] +- [NEON for JavaScript |https://github.com/matej21/neon-js] +- [NEON for Python |https://github.com/paveldedik/neon-py]. -Синтаксис .[#toc-syntax] -======================== +Синтаксис +========= -Файл, написанный на NEON, обычно состоит из последовательности или отображения. +Файл, написанный в NEON, обычно представляет собой массив или отображение. -Сопоставления .[#toc-mappings] ------------------------------- -Маппинг - это набор пар ключ-значение, в PHP это называется ассоциативным массивом. Каждая пара записывается как `key: value`, пробел после `:` обязателен. Значение может быть любым: строка, число, булево, null, последовательность или другое отображение. +Отображение +----------- +Отображение — это набор пар ключ-значение, в PHP это называется ассоциативным массивом. Каждая пара записывается как `key: value`, пробел после `:` обязателен. Значением может быть что угодно: строка, число, булево значение, null, последовательность или другое отображение. ```neon street: 742 Evergreen Terrace @@ -43,7 +43,7 @@ city: Springfield country: USA ``` -В PHP та же структура будет записана как: +В PHP та же структура была бы записана так: ```php [ // PHP @@ -53,13 +53,13 @@ country: USA ] ``` -Эта нотация называется блочной, потому что все элементы находятся на отдельной строке и имеют одинаковый отступ (в данном случае он отсутствует). NEON также поддерживает построчное представление для отображения, которое заключено в скобки, отступы не играют никакой роли, а разделителем каждого элемента является либо запятая, либо новая строка: +Эта запись называется блочной, потому что все элементы находятся на отдельной строке и имеют одинаковый отступ (в данном случае никакого). NEON также поддерживает инлайн-представление отображений, которое заключено в скобки, отступ не играет роли, а разделителем отдельных элементов является либо запятая, либо новая строка: ```neon {street: 742 Evergreen Terrace, city: Springfield, country: USA} ``` -Это одно и то же, написанное на нескольких строках (отступ не имеет значения): +То же самое, записанное на нескольких строках (отступ не имеет значения): ```neon { @@ -68,16 +68,16 @@ country: USA } ``` -В качестве альтернативы можно использовать `=` вместо <code>: </code>, как в блочной, так и в инлайн-нотации: +Вместо <code>: </code> можно альтернативно использовать `=`, как в блочной, так и в инлайн-записи: ```neon {street=742 Evergreen Terrace, city=Springfield, country=USA} ``` -Последовательности .[#toc-sequences] ------------------------------------- -Последовательности - это индексированные массивы в PHP. Они записываются в виде строк, начинающихся с дефиса `-`, за которым следует пробел. Значение может быть любым: строка, число, булево, null, последовательность или другое отображение. +Последовательность +------------------ +Последовательности в PHP — это индексированные массивы. Они записываются как строки, начинающиеся с дефиса `-`, за которым следует пробел. Значением опять же может быть что угодно: строка, число, булево значение, null, последовательность или другое отображение. ```neon - Cat @@ -85,7 +85,7 @@ country: USA - Goldfish ``` -В PHP та же структура будет выглядеть следующим образом: +В PHP та же структура была бы записана так: ```php [ // PHP @@ -95,13 +95,13 @@ country: USA ] ``` -Эта нотация называется блочной, потому что все элементы находятся на отдельной строке и имеют одинаковый отступ (в данном случае он отсутствует). NEON также поддерживает поточное представление последовательностей, которые заключаются в скобки, отступы не играют никакой роли, а разделителем каждого элемента является либо запятая, либо новая строка: +Эта запись называется блочной, потому что все элементы находятся на отдельной строке и имеют одинаковый отступ (в данном случае никакого). NEON также поддерживает инлайн-представление последовательности, которое заключено в скобки, отступ не играет роли, а разделителем отдельных элементов является либо запятая, либо новая строка: ```neon [Cat, Dog, Goldfish] ``` -Это одно и то же, написанное на нескольких строках (отступ не имеет значения): +То же самое, записанное на нескольких строках (отступ не имеет значения): ```neon [ @@ -110,23 +110,23 @@ country: USA ] ``` -Дефисы не могут быть использованы в инлайн-представлении. +В инлайн-представлении нельзя использовать отступы с дефисами. -Комбинация .[#toc-combination] ------------------------------- -Значения отображений и последовательностей могут быть другими отображениями и последовательностями. Уровень отступа играет важную роль. В следующем примере дефис, используемый для обозначения элементов последовательности, имеет больший отступ, чем ключ `pets`, поэтому элементы становятся значениями первой строки: +Комбинации +---------- +Значениями отображений и последовательностей могут быть другие отображения и последовательности. Главную роль играет уровень отступа. В следующем примере дефис, используемый для обозначения элементов последовательности, имеет больший отступ, чем ключ `pets`, поэтому элементы становятся значением первой строки: ```neon pets: - - Cat - - Dog + - Cat + - Dog cars: - - Volvo - - Skoda + - Volvo + - Skoda ``` -В PHP та же структура была бы записана как: +В PHP та же структура была бы записана так: ```php [ // PHP @@ -141,7 +141,7 @@ cars: ] ``` -Можно комбинировать блочную и инлайн-нотацию: +Можно комбинировать блочную и инлайн-запись: ```neon pets: [Cat, Dog] @@ -151,17 +151,39 @@ cars: [ ] ``` -Блочная нотация больше не может быть использована внутри строчной нотации, это не работает: +Внутри инлайн-записи уже нельзя использовать блочную запись, это не работает: ```neon item: [ pets: - - Cat # THIS IS NOT POSSIBLE!!! + - Cat # ЭТО НЕВОЗМОЖНО!!! - Dog ] ``` -Поскольку PHP использует одну и ту же структуру для отображения и последовательностей, то есть массивы, оба варианта могут быть объединены. На этот раз отступы одинаковы: +В предыдущем случае мы записали отображение, элементами которого были последовательности, теперь попробуем наоборот и создадим последовательность, содержащую отображения: + +```neon +- + name: John + age: 35 +- + name: Peter + age: 28 +``` + +Не обязательно, чтобы дефисы были на отдельных строках, их можно разместить и таким образом: + +```neon +- name: John + age: 35 +- name: Peter + age: 28 +``` + +Вам решать, выравнивать ли ключи в столбец с помощью пробелов или использовать табуляцию. + +Поскольку в PHP для отображений и последовательностей используется одна и та же структура, то есть массив, их можно объединить. Отступ на этот раз одинаковый: ```neon - Cat @@ -169,7 +191,7 @@ street: 742 Evergreen Terrace - Goldfish ``` -В PHP та же структура будет записана как: +В PHP та же структура была бы записана так: ```php [ // PHP @@ -180,23 +202,23 @@ street: 742 Evergreen Terrace ``` -Строки .[#toc-strings] ----------------------- -Строки в NEON могут быть заключены в одинарные или двойные кавычки. Но, как вы видите, они могут быть и без кавычек. +Строки +------ +Строки в NEON можно заключать в одинарные или двойные кавычки. Но, как вы видите, они могут быть и без кавычек. ```neon -- A unquoted string in NEON -- 'A singled-quoted string in NEON' -- "A double-quoted string in NEON" +- Строка в NEON без кавычек +- 'Строка в NEON в одинарных кавычках' +- "Строка в NEON в двойных кавычках" ``` -Если строка содержит символы `# " ' , : = - [ ] { } ( )` которые можно спутать с синтаксисом NEON, она должна быть заключена в кавычки. Мы рекомендуем использовать одинарные кавычки, поскольку они не используют экранирование. Если вам нужно заключить кавычки в такой строке, удвойте их: +Если строка содержит символы `# " ' , : = - [ ] { } ( )`, которые можно спутать с синтаксисом NEON, ее необходимо заключить в кавычки. Рекомендуется использовать одинарные кавычки, так как в них не используется экранирование. Если вам нужно записать кавычку в такой строке, удвойте ее: ```neon -'A single quote '' inside a single-quoted string' +'Кавычка '' внутри строки в одинарных кавычках' ``` -Двойные кавычки позволяют использовать экранирующие последовательности для записи специальных символов, используя обратные косые черты `\`. All escape sequences as in the JSON format are supported, plus `\_`, которые представляют собой неразрывный пробел, т.е. `\u00A0`. +Двойные кавычки позволяют использовать escape-последовательности для записи специальных символов с помощью обратных слешей `\`. Поддерживаются все escape-последовательности, как в формате JSON, а также `\_`, что является неразрывным пробелом, то есть `\u00A0`. ```neon - "\t \n \r \f \b \" \\ \/ \_" @@ -205,30 +227,30 @@ street: 742 Evergreen Terrace Существуют и другие случаи, когда необходимо заключать строки в кавычки: - они начинаются или заканчиваются пробелами -- выглядят как числа, булевы или null. -- NEON будет понимать их как [даты |#Dates] +- они выглядят как числа, булевы значения или null +- NEON воспринял бы их как [дату |#Дата] -Многострочные строки .[#toc-multiline-strings] ----------------------------------------------- +Многострочные строки +-------------------- -Многострочная строка начинается и заканчивается тройной кавычкой на отдельных строках. Отступ первой строки игнорируется для всех строк: +Многострочная строка начинается и заканчивается тройными кавычками на отдельных строках. Отступ первой строки игнорируется, и это касается всех строк: ```neon ''' - first line - second line - third line + первая строка + вторая строка + третья строка ''' ``` -В PHP мы бы написали то же самое: +В PHP мы бы написали то же самое так: ```php -"first line\n\tsecond line\nthird line" // PHP +"первая строка\n\tвторая строка\nтретья строка" // PHP ``` -Последовательности экранирования работают только для строк, заключенных в двойные кавычки вместо апострофов: +Escape-последовательности работают только для строк, заключенных в двойные кавычки вместо апострофов: ```neon """ @@ -237,24 +259,24 @@ street: 742 Evergreen Terrace ``` -Числа .[#toc-numbers] ---------------------- -NEON понимает числа, записанные в так называемой научной нотации, а также числа в двоичной, восьмеричной и шестнадцатеричной системе счисления: +Числа +----- +NEON понимает числа, записанные в так называемой научной нотации, а также числа в двоичной, восьмеричной и шестнадцатеричной системах счисления: ```neon -- 12 # an integer -- 12.3 # a float -- +1.2e-34 # an exponential number +- 12 # целое число +- 12.3 # float +- +1.2e-34 # экспоненциальное число -- 0b11010 # binary number -- 0o666 # octal number -- 0x7A # hexa number +- 0b11010 # двоичное число +- 0o666 # восьмеричное число +- 0x7A # шестнадцатеричное число ``` -Нули .[#toc-nulls] ------------------- -Нуль может быть выражен в NEON с помощью `null` или без указания значения. Также допускаются варианты с заглавной первой или всеми прописными буквами. +Nulls +----- +Null в NEON можно выразить с помощью `null` или не указывая значение. Разрешены также варианты с большой первой или всеми большими буквами. ```neon a: null @@ -262,37 +284,37 @@ b: ``` -Булевы .[#toc-booleans] ------------------------ -Булевы значения выражаются в NEON с помощью `true` / `false` или `yes` / `no`. Также допускаются варианты с заглавной первой или всеми прописными буквами. +Логические значения +------------------- +Логические значения в NEON выражаются с помощью `true` / `false` или `yes` / `no`. Разрешены также варианты с большой первой или всеми большими буквами. ```neon [true, TRUE, True, false, yes, no] ``` -Даты .[#toc-dates] ------------------- -NEON использует следующие форматы для выражения данных и автоматически преобразует их в объекты `DateTimeImmutable`: +Дата +---- +NEON использует для выражения дат следующие форматы и автоматически преобразует их в объекты `DateTimeImmutable`: ```neon -- 2016-06-03 # date -- 2016-06-03 19:00:00 # date & time -- 2016-06-03 19:00:00.1234 # date & microtime -- 2016-06-03 19:00:00 +0200 # date & time & timezone -- 2016-06-03 19:00:00 +02:00 # date & time & timezone +- 2016-06-03 # дата +- 2016-06-03 19:00:00 # дата и время +- 2016-06-03 19:00:00.1234 # дата и микровремя +- 2016-06-03 19:00:00 +0200 # дата и время и зона +- 2016-06-03 19:00:00 +02:00 # дата и время и зона ``` -Сущности .[#toc-entities] -------------------------- -Сущность - это структура, напоминающая вызов функции: +Сущности +-------- +Сущность — это структура, напоминающая вызов функции: ```neon Column(type: int, nulls: yes) ``` -В PHP она разбирается как объект [api:Nette\Neon\Entity]: +В PHP она парсится как объект [api:Nette\Neon\Entity]: ```php // PHP @@ -305,7 +327,7 @@ new Nette\Neon\Entity('Column', ['type' => 'int', 'nulls' => true]) Column(type: int, nulls: yes) Field(id: 1) ``` -Что разбирается в PHP следующим образом: +Что в PHP парсится следующим образом: ```php // PHP @@ -315,7 +337,7 @@ new Nette\Neon\Entity(Nette\Neon\Neon::Chain, [ ]) ``` -Внутри круглых скобок применяются правила инлайн-нотации, используемые для отображения и последовательностей, поэтому его можно разделить на несколько строк и нет необходимости добавлять запятые: +Внутри скобок действуют правила для инлайн-записи, используемой для отображений и последовательностей, то есть она может быть многострочной, и тогда не нужно указывать запятые: ```neon Column( @@ -325,21 +347,21 @@ Column( ``` -Комментарии .[#toc-comments] ----------------------------- -Комментарии начинаются с `#` и все последующие символы справа игнорируются: +Комментарии +----------- +Комментарии начинаются с символа `#`, и все последующие символы справа игнорируются: ```neon -# this line will be ignored by the interpreter +# эта строка будет проигнорирована интерпретатором street: 742 Evergreen Terrace -city: Springfield # this is ignored too +city: Springfield # это тоже игнорируется country: USA ``` -NEON против JSON .[#toc-neon-versus-json] -========================================= -JSON является подмножеством NEON. Поэтому каждый JSON может быть разобран как NEON: +Neon против JSON +================ +JSON является подмножеством NEON. Поэтому любой JSON можно распарсить как NEON: ```neon { @@ -358,7 +380,7 @@ JSON является подмножеством NEON. Поэтому кажды } ``` -Что если бы мы могли опустить кавычки? +Что, если мы уберем кавычки? ```neon { @@ -377,7 +399,7 @@ users: [ } ``` -Как насчет скобок и запятых? +А фигурные скобки и запятые? ```neon php: @@ -394,7 +416,7 @@ users: [ ] ``` -Являются ли пули более разборчивыми? +Не лучше ли читаются списки с дефисами? ```neon php: @@ -412,14 +434,14 @@ users: - Rimmer ``` -Как насчет комментариев? +Добавим комментарии? ```neon -# my web application config +# конфигурация моего веб-приложения php: date.timezone: Europe/Prague - zlib.output_compression: true # use gzip + zlib.output_compression: true # использовать gzip database: driver: mysql @@ -432,8 +454,7 @@ users: - Rimmer ``` -Вы нашли синтаксис NEON! +Ура, теперь вы знаете синтаксис NEON! -{{description: NEON - это удобный для человека язык сериализации данных. Он похож на YAML. Основное отличие заключается в том, что NEON поддерживает "сущности" и символы табуляции для отступов.}} -{{leftbar: utils:@left-menu}} +{{description: NEON — это легко читаемый формат для сериализации данных. Он похож на YAML. Основное отличие в том, что NEON поддерживает «сущности», и для отступов мы можем использовать как пробелы, так и табуляцию.}} diff --git a/neon/sl/@home.texy b/neon/sl/@home.texy index 125fe72f68..a944ff0832 100644 --- a/neon/sl/@home.texy +++ b/neon/sl/@home.texy @@ -1,45 +1,45 @@ -Funkcije NEON -************* +Nette NEON +********** <div class=perex> -NEON je človeku prijazen jezik za serializacijo podatkov. V Nette se uporablja za konfiguracijske datoteke. [api:Nette\Neon\Neon] je statični razred za delo z NEON. +NEON je človeku razumljiv jezik za serializacijo podatkov. V Nette se uporablja za konfiguracijske datoteke. [api:Nette\Neon\Neon] je statični razred za delo z NEONom. -Spoznajte [format NEON |format] in [ga preizkusite|https://ne-on.org]. +Spoznajte [format NEON|format] in [ga preizkusite |https://ne-on.org]. </div> -Naslednji primeri uporabljajo te vzdevke: +Vsi primeri predpostavljajo ustvarjen alias: ```php use Nette\Neon\Neon; ``` -. .[#toc-installation] ----------------------- +Namestitev +---------- -Prenesite in namestite paket s [programom Composer |best-practices:composer]: +Knjižnico prenesete in namestite z orodjem [Composer|best-practices:composer]: ```shell composer require nette/neon ``` -S konzolnim ukazom `neon-lint` lahko preverite, ali so v datotekah `*.neon` skladenjske napake: +Napake v sintaksi v datotekah `*.neon` lahko preverite z ukazom konzole `neon-lint`: ```shell -vendor/bin/neon-lint <path> +vendor/bin/neon-lint <pot> ``` -encode(mixed $value, bool $blockMode=false): string .[method] -------------------------------------------------------------- +encode(mixed $value, bool $blockMode=false, string $indentation="\t"): string .[method] +--------------------------------------------------------------------------------------- -Vrne `$value`, pretvorjeno v NEON. Kot parameter `$blockMode` lahko posredujete true, kar bo ustvarilo večvrstični izpis. Parameter `$indentation` določa znake, ki se uporabljajo za odtiskovanje (privzeto je tabulator). +Vrne `$value`, pretvorjeno v NEON. Kot parameter `$blockMode` lahko predate true, s čimer se ustvari večvrstični izpis. Parameter `$indentation` določa znake, uporabljene za zamik (privzeto je tabulator). ```php -Neon::encode($value); // Vrne $vrednost pretvorjeno v NEON -Neon::encode($value, true); // Vrača $vrednost, pretvorjeno v večvrstični NEON +Neon::encode($value); // Vrne $value, pretvorjeno v NEON +Neon::encode($value, true); // Vrne $value, pretvorjeno v večvrstični NEON ``` Metoda `encode()` ob napaki vrže `Nette\Neon\Exception`. @@ -48,7 +48,7 @@ Metoda `encode()` ob napaki vrže `Nette\Neon\Exception`. try { $neon = Neon::encode($value); } catch (Nette\Neon\Exception $e) { - // Ravnanje z izjemami + // obdelava izjeme } ``` @@ -56,9 +56,9 @@ try { decode(string $neon): mixed .[method] ------------------------------------- -Pretvori podano vrednost NEON v vrednost PHP. +Pretvori niz iz NEONa v PHP. -Vrne skalarje, polja, [datum |format#dates] kot objekte DateTimeImmutable in [entitete |format#Entities] kot objekte [api:Nette\Neon\Entity]. +Vrne skalarje, polja, [datume |format#Datum] kot objekte DateTimeImmutable in [entitete |format#Entitete] kot objekte [api:Nette\Neon\Entity]. ```php Neon::decode('hello: world'); // Vrne polje ['hello' => 'world'] @@ -70,7 +70,7 @@ Metoda `decode()` ob napaki vrže `Nette\Neon\Exception`. try { $value = Neon::decode($neon); } catch (Nette\Neon\Exception $e) { - // Ravnanje z izjemami + // obdelava izjeme } ``` @@ -78,13 +78,10 @@ try { decodeFile(string $file): mixed .[method] ----------------------------------------- -Pretvori vsebino datoteke iz NEON v PHP in odstrani vse BOM. +Pretvori vsebino datoteke iz NEONa v PHP in odstrani morebitni BOM. ```php Neon::decodeFile('config.neon'); ``` Metoda `decodeFile()` ob napaki vrže `Nette\Neon\Exception`. - - -{{leftbar: utils:@left-menu}} diff --git a/neon/sl/@meta.texy b/neon/sl/@meta.texy new file mode 100644 index 0000000000..422d0be168 --- /dev/null +++ b/neon/sl/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Dokumentacija}} +{{leftbar: utils:@left-menu}} diff --git a/neon/sl/format.texy b/neon/sl/format.texy index f1a694bd32..25fe9ceed2 100644 --- a/neon/sl/format.texy +++ b/neon/sl/format.texy @@ -2,40 +2,40 @@ Format NEON *********** .[perex] -NEON je človeku berljiv strukturiran podatkovni format. V omrežju Nette se uporablja za konfiguracijske datoteke. Uporablja se tudi za strukturirane podatke, kot so nastavitve, jezikovni prevodi itd. [Preizkusite ga v peskovniku |https://ne-on.org]. +NEON je človeško berljiv strukturiran podatkovni format. V Nette se uporablja za konfiguracijske datoteke. Uporablja se tudi za strukturirane podatke, kot so nastavitve, jezikovni prevodi itd. [Preizkusite ga|https://ne-on.org]. -NEON je kratica za *Nette Object Notation*. Je manj zapleten in neroden kot XML ali JSON, vendar zagotavlja podobne zmožnosti. Zelo je podoben jeziku YAML. Glavna prednost je, da ima NEON tako imenovane [entitete |#entities], zaradi katerih je konfiguracija storitev DI tako seksi. In dovoljuje zavihke za alineje. +NEON je okrajšava za *Nette Object Notation*. Je manj zapleten in okoren kot XML ali JSON, vendar zagotavlja podobne funkcije. Je zelo podoben YAML. Glavna prednost je v tem, da ima NEON tako imenovane [#Entitete], zahvaljujoč katerim je konfiguracija DI storitev [tudi seksi |https://gist.github.com/dg/26baf3ce8f29d0f751e9dddfaa06504f]. In omogoča zamikanje s tabulatorji. -Sistem NEON je od začetka zgrajen tako, da je preprost za uporabo. +NEON je zgrajen od temeljev tako, da je enostaven za uporabo. -Integracija .[#toc-integration] -=============================== +Integracija +=========== - NetBeans (ima vgrajeno podporo) -- PhpStorm ([vtičnik |https://plugins.jetbrains.com/plugin/7060?pr]) -- Visual Studio Code ([vtičnik |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) -- Sublime Text 3 ([vtičnik |https://github.com/FilipStryk/Nette-Latte-Neon-for-Sublime-Text-3]) -- Sublime Text 2 ([vtičnik |https://github.com/Michal-Mikolas/Nette-package-for-Sublime-Text-2]) -- VIM ([vtičnik |https://github.com/fpob/nette.vim]) -- Emacs ([vtičnik |https://github.com/Fuco1/neon-mode]) -- Prism.js ([integrirani jezik |https://prismjs.com/#supported-languages]) +- PhpStorm ([plugin |https://plugins.jetbrains.com/plugin/7060?pr]) +- Visual Studio Code ([Nette Latte + Neon |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) ali [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode]) +- Sublime Text 3 ([plugin |https://github.com/FilipStryk/Nette-Latte-Neon-for-Sublime-Text-3]) +- Sublime Text 2 ([plugin |https://github.com/Michal-Mikolas/Nette-package-for-Sublime-Text-2]) +- VIM ([plugin |https://github.com/fpob/nette.vim]) +- Emacs ([plugin |https://github.com/Fuco1/neon-mode]) +- Prism.js ([integriran jezik |https://prismjs.com/#supported-languages]) -- [NEON za PHP |@home] -- [NEON za JavaScript |https://github.com/matej21/neon-js] -- [NEON za Python |https://github.com/paveldedik/neon-py]. +- [NEON for PHP |@home] +- [NEON for JavaScript |https://github.com/matej21/neon-js] +- [NEON for Python |https://github.com/paveldedik/neon-py]. -Sintaksa .[#toc-syntax] -======================= +Sintaksa +======== -Datoteka, zapisana v NEON, je običajno sestavljena iz zaporedja ali preslikave. +Datoteka, napisana v NEON, običajno predstavlja polje ali preslikavo. -Zemljevidi .[#toc-mappings] ---------------------------- -Mapiranje je niz parov ključ-vrednost, v jeziku PHP bi ga imenovali asociativno polje. Vsak par je zapisan kot `key: value`, za `:` je potreben presledek. Vrednost je lahko karkoli: niz, število, logična vrednost, ničla, zaporedje ali drugo kartiranje. +Preslikava / Mapiranje +---------------------- +Preslikava je niz parov ključ-vrednost, v PHP bi rekli asociativno polje. Vsak par je zapisan kot `key: value`, presledek za `:` je nujen. Vrednost je lahko karkoli: niz, število, logična vrednost, null, zaporedje ali druga preslikava. ```neon street: 742 Evergreen Terrace @@ -43,7 +43,7 @@ city: Springfield country: USA ``` -V jeziku PHP bi bila enaka struktura zapisana kot: +V PHP bi se enaka struktura zapisala kot: ```php [ // PHP @@ -53,13 +53,13 @@ V jeziku PHP bi bila enaka struktura zapisana kot: ] ``` -Ta zapis se imenuje blokovni zapis, ker so vsi elementi v ločeni vrstici in imajo enako alinejo (v tem primeru je ni). NEON podpira tudi vrstnično predstavitev za preslikavo, ki je zaprta v oklepajih, alineja nima vloge, ločilo vsakega elementa pa je bodisi vejica bodisi nova vrstica: +Ta zapis se označuje kot blokovni, ker so vsi elementi na samostojni vrstici in imajo enak zamik (v tem primeru nobenega). NEON podpira tudi inline predstavitev preslikave, ki je zaprta v oklepaje, zamik ne igra nobene vloge in ločilo posameznih elementov je ali vejica ali nova vrstica: ```neon {street: 742 Evergreen Terrace, city: Springfield, country: USA} ``` -To je isto, kar je zapisano v več vrsticah (alineja ni pomembna): +Enako zapisano v več vrsticah (na zamik ni pomembno): ```neon { @@ -68,16 +68,16 @@ To je isto, kar je zapisano v več vrsticah (alineja ni pomembna): } ``` -Namesto tega lahko namesto <code>: </code>, tako v blokovnem kot v vrstičnem zapisu: +Namesto `: ` lahko alternativno uporabljate `=` in to tako v blokovnem kot v inline zapisu: ```neon {street=742 Evergreen Terrace, city=Springfield, country=USA} ``` -Zaporedja .[#toc-sequences] ---------------------------- -Zaporedja so indeksirana polja v PHP. Zapisane so kot vrstice, ki se začnejo s pomišljajem `-`, ki mu sledi presledek. Tudi v tem primeru je lahko vrednost kar koli: niz, število, logična vrednost, ničla, zaporedje ali drugo prikazovanje. +Zaporedja / Sekvence +-------------------- +Zaporedja so v PHP indeksirana polja. Zapisujejo se kot vrstice, ki se začnejo z vezajem `-`, ki mu sledi presledek. Vrednost je spet lahko karkoli: niz, število, logična vrednost, null, zaporedje ali druga preslikava. ```neon - Cat @@ -85,7 +85,7 @@ Zaporedja so indeksirana polja v PHP. Zapisane so kot vrstice, ki se začnejo s - Goldfish ``` -V PHP bi bila enaka struktura zapisana kot: +V PHP bi se enaka struktura zapisala kot: ```php [ // PHP @@ -95,13 +95,13 @@ V PHP bi bila enaka struktura zapisana kot: ] ``` -Ta zapis se imenuje blokovni zapis, ker so vsi elementi v ločeni vrstici in imajo enako alinejo (v tem primeru je ni). NEON podpira tudi vrstilni zapis za zaporedja, ki so zaprta v oklepajih, alineja nima vloge, ločilo vsakega elementa pa je bodisi vejica bodisi nova vrstica: +Ta zapis se označuje kot blokovni, ker so vsi elementi na samostojni vrstici in imajo enak zamik (v tem primeru nobenega). NEON podpira tudi inline predstavitev zaporedja, ki je zaprta v oklepaje, zamik ne igra nobene vloge in ločilo posameznih elementov je ali vejica ali nova vrstica: ```neon [Cat, Dog, Goldfish] ``` -To je enako zapisano v več vrsticah (alineja ni pomembna): +Enako zapisano v več vrsticah (na zamik ni pomembno): ```neon [ @@ -110,23 +110,23 @@ To je enako zapisano v več vrsticah (alineja ni pomembna): ] ``` -V vrstični predstavitvi ni mogoče uporabljati veznikov. +V inline predstavitvi ni mogoče uporabljati zamikajočih alinej. -Kombinacija .[#toc-combination] -------------------------------- -Vrednosti preslikav in zaporedij so lahko druge preslikave in zaporedja. Pomembno vlogo ima stopnja odmikanja. V naslednjem primeru ima pomišljaj, ki se uporablja za označevanje elementov zaporedja, večjo alinejo kot ključ `pets`, zato elementi postanejo vrednost prve vrstice: +Kombinacije +----------- +Vrednosti preslikav in zaporedij so lahko druge preslikave in zaporedja. Glavno vlogo igra raven zamika. V naslednjem primeru ima pomišljaj, uporabljen za označevanje elementov zaporedja, večji zamik kot ključ `pets`, zato elementi postanejo vrednost prve vrstice: ```neon pets: - - Cat - - Dog + - Cat + - Dog cars: - - Volvo - - Skoda + - Volvo + - Skoda ``` -V jeziku PHP bi bila ista struktura zapisana kot: +V PHP bi se enaka struktura zapisala kot: ```php [ // PHP @@ -141,7 +141,7 @@ V jeziku PHP bi bila ista struktura zapisana kot: ] ``` -Mogoče je kombinirati blokovni in vrstnični zapis: +Lahko kombinirate blokovni in inline zapis: ```neon pets: [Cat, Dog] @@ -151,17 +151,39 @@ cars: [ ] ``` -Blokovnega zapisa ni več mogoče uporabiti znotraj vrstilnega zapisa, saj to ne deluje: +Znotraj inline zapisa ni več mogoče uporabljati blokovnega zapisa, tole ne deluje: ```neon item: [ pets: - - Cat # THIS IS NOT POSSIBLE!!! + - Cat # TOLE NI MOGOČE!!! - Dog ] ``` -Ker PHP uporablja enako strukturo za preslikave in zaporedja, tj. polja, je mogoče oboje združiti. Vstavljanje je tokrat enako: +V prejšnjem primeru smo zapisali preslikavo, katere elementi so bila zaporedja, zdaj pa poskusimo obratno in ustvarimo zaporedje, ki vsebuje preslikave: + +```neon +- + name: John + age: 35 +- + name: Peter + age: 28 +``` + +Ni nujno, da so alineje na samostojnih vrsticah, lahko jih postavite tudi na ta način: + +```neon +- name: John + age: 35 +- name: Peter + age: 28 +``` + +Od vas je odvisno, ali boste ključe poravnali v stolpec s presledki ali uporabili tabulator. + +Ker se v PHP uporablja za preslikave in zaporedja enaka struktura, torej polje, je mogoče oboje združiti. Zamik je tokrat enak: ```neon - Cat @@ -169,7 +191,7 @@ street: 742 Evergreen Terrace - Goldfish ``` -V PHP bi bila ista struktura zapisana kot: +V PHP bi se enaka struktura zapisala kot: ```php [ // PHP @@ -180,55 +202,55 @@ V PHP bi bila ista struktura zapisana kot: ``` -. .[#toc-strings] ------------------ -Nizi v NEON-u so lahko zaprti v enojne ali dvojne narekovaje. Kot lahko vidite, so lahko tudi brez narekovajev. +Nizi +---- +Nizi v NEON so lahko zaprti v enojne ali dvojne narekovaje. Ampak kot vidite, so lahko tudi brez narekovajev. ```neon -- A unquoted string in NEON -- 'A singled-quoted string in NEON' -- "A double-quoted string in NEON" +- Niz v NEON brez narekovajev +- 'Niz v NEON v enojnih narekovajih' +- "Niz v NEON v dvojnih narekovajih" ``` -Če niz vsebuje znake `# " ' , : = - [ ] { } ( )` ki jih je mogoče zamenjati s sintakso NEON, morajo biti zaprti v narekovaje. Priporočamo uporabo enojnih narekovajev, ker se pri njih ne uporablja izogibanje. Če morate v takem nizu zapreti narekovaj, ga podvojite: +Če niz vsebuje znake `# " ' , : = - [ ] { } ( )`, ki jih je mogoče zamenjati s sintakso NEON, ga je treba zapreti v narekovaje. Priporočamo uporabo enojnih narekovajev, ker se v njih ne uporablja ubežanje znakov. Če morate v takem nizu zapisati narekovaj, ga podvojite: ```neon -'A single quote '' inside a single-quoted string' +'Narekovaj '' znotraj niza v enojnih narekovajih' ``` -Dvojni narekovaji omogočajo uporabo pobeglih zaporedij za zapis posebnih znakov z uporabo povratnega lomljenja `\`. All escape sequences as in the JSON format are supported, plus `\_`, ki je neprekinjen presledek, tj. `\u00A0`. +Dvojni narekovaji omogočajo uporabo ubežnih zaporedij za zapis posebnih znakov s pomočjo poševnic nazaj `\`. Podprta so vsa ubežna zaporedja kot pri formatu JSON in poleg tega `\_`, kar je nedeljiv presledek, torej `\u00A0`. ```neon - "\t \n \r \f \b \" \\ \/ \_" - "\u00A9" ``` -Obstajajo tudi drugi primeri, ko morate nize zapreti v narekovaje: +Obstajajo drugi primeri, ko je treba nize zapreti v narekovaje: - se začnejo ali končajo s presledki -- so videti kot številke, logični znaki ali ničelne vrednosti -- NEON bi jih razumel kot [datume |#dates] +- izgledajo kot števila, logične vrednosti ali null +- NEON bi jih razumel kot [#Datum] -Večvrstični nizi .[#toc-multiline-strings] ------------------------------------------- +Večvrstični nizi +---------------- -Večvrstični niz se začne in konča s trojnim narekovajom v ločenih vrsticah. Odmik prve vrstice se pri vseh vrsticah ne upošteva: +Večvrstični niz se začne in konča s trojnim narekovajem na samostojnih vrsticah. Zamik prve vrstice se ignorira in to pri vseh vrsticah: ```neon ''' - first line - second line - third line + prva vrstica + druga vrstica + tretja vrstica ''' ``` -V jeziku PHP bi zapisali enako kot: +V PHP bi enako napisali kot: ```php -"first line\n\tsecond line\nthird line" // PHP +"prva vrstica\n\tdruga vrstica\ntretja vrstica" // PHP ``` -Zaporedja za pobeg delujejo le za nize, ki so namesto z apostrofi zaprti z dvojnimi narekovaji: +Ubežna zaporedja delujejo le pri nizih, zaprtih v dvojne narekovaje namesto apostrofov: ```neon """ @@ -237,24 +259,24 @@ Zaporedja za pobeg delujejo le za nize, ki so namesto z apostrofi zaprti z dvojn ``` -Številke .[#toc-numbers] ------------------------- -NEON razume števila, zapisana v tako imenovanem znanstvenem zapisu, in tudi števila v dvojiškem, osmeriškem in šestnajstiškem zapisu: +Števila +------- +NEON razume števila, zapisana v t.i. znanstveni notaciji, in tudi števila v dvojiškem, osmiškem in šestnajstiškem sistemu: ```neon - 12 # celo število - 12.3 # float - +1.2e-34 # eksponentno število -- 0b11010 # binarno število +- 0b11010 # dvojiško število - 0o666 # osmiško število - 0x7A # šestnajstiško število ``` -Ničle .[#toc-nulls] -------------------- -Ničlo lahko v NEON-u izrazimo z uporabo `null` ali tako, da ne navedemo vrednosti. Dovoljene so tudi različice z veliko začetnico ali vsemi velikimi črkami. +Null vrednosti +-------------- +Null lahko v NEON izrazite s pomočjo `null` ali z ne-navedbo vrednosti. Dovoljene so tudi različice z veliko prvo ali velikimi vsemi črkami. ```neon a: null @@ -262,50 +284,50 @@ b: ``` -Logične vrednosti .[#toc-booleans] ----------------------------------- -Logične vrednosti so v NEON-u izražene z uporabo `true` / `false` ali `yes` / `no`. Dovoljene so tudi različice z veliko začetnico ali vsemi velikimi črkami. +Logične vrednosti / Booleans +---------------------------- +Logične vrednosti so v NEON izražene s pomočjo `true` / `false` ali `yes` / `no`. Dovoljene so tudi različice z veliko prvo ali velikimi vsemi črkami. ```neon [true, TRUE, True, false, yes, no] ``` -Datumi .[#toc-dates] --------------------- -NEON uporablja naslednje formate za izražanje podatkov in jih samodejno pretvori v objekte `DateTimeImmutable`: +Datum +----- +NEON uporablja za izražanje datumov naslednje formate in jih samodejno pretvori v objekte `DateTimeImmutable`: ```neon - 2016-06-03 # datum -- 2016-06-03 19:00:00 # datum in čas -- 2016-06-03 19:00:00.1234 # datum in mikročas -- 2016-06-03 19:00:00 +0200 # datum in čas ter časovni pas -- 2016-06-03 19:00:00 +02:00 # datum in čas ter časovni pas +- 2016-06-03 19:00:00 # datum & čas +- 2016-06-03 19:00:00.1234 # datum & mikročas +- 2016-06-03 19:00:00 +0200 # datum & čas & časovni pas +- 2016-06-03 19:00:00 +02:00 # datum & čas & časovni pas ``` -Entitete .[#toc-entities] -------------------------- -Entiteta je struktura, ki je podobna funkcijskemu klicu: +Entitete +-------- +Entiteta je struktura, ki spominja na klic funkcije: ```neon Column(type: int, nulls: yes) ``` -V jeziku PHP se analizira kot objekt [api:Nette\Neon\Entity]: +V PHP se razčleni kot objekt [api:Nette\Neon\Entity]: ```php // PHP new Nette\Neon\Entity('Column', ['type' => 'int', 'nulls' => true]) ``` -Entitete je mogoče tudi verižiti: +Entitete se lahko tudi združijo: ```neon Column(type: int, nulls: yes) Field(id: 1) ``` -To se v PHP razčleni na naslednji način: +Kar se v PHP razčleni na ta način: ```php // PHP @@ -315,7 +337,7 @@ new Nette\Neon\Entity(Nette\Neon\Neon::Chain, [ ]) ``` -Znotraj oklepajev veljajo pravila za vrstični zapis, ki se uporabljajo za preslikave in zaporedja, zato se lahko razdeli na več vrstic in ni treba dodajati vejic: +Znotraj oklepajev veljajo pravila za inline zapis, uporabljen pri preslikavah in zaporedjih, torej je lahko mirno tudi večvrstičen in potem ni treba navajati vejic: ```neon Column( @@ -325,21 +347,21 @@ Column( ``` -Komentarji .[#toc-comments] ---------------------------- -Komentarji se začnejo z `#` in vsi naslednji znaki na desni strani se ne upoštevajo: +Komentarji +---------- +Komentarji se začnejo z znakom `#` in vsi naslednji znaki na desno so ignorirani: ```neon -# tolmač bo to vrstico ignoriral +# ta vrstica bo ignorirana s strani interpreterja street: 742 Evergreen Terrace -city: Springfield # tudi to se ne upošteva +city: Springfield # to je tudi ignorirano country: USA ``` -NEON proti JSON .[#toc-neon-versus-json] -======================================== -JSON je podmnožica NEON. Vsak JSON je zato mogoče razčleniti kot NEON: +Neon proti JSON +=============== +JSON je podmnožica NEON-a. Vsak JSON se da zato razčleniti kot NEON: ```neon { @@ -358,7 +380,7 @@ JSON je podmnožica NEON. Vsak JSON je zato mogoče razčleniti kot NEON: } ``` -Kaj pa, če bi lahko izpustili narekovaje? +Kaj če bi izpustili narekovaje? ```neon { @@ -377,7 +399,7 @@ users: [ } ``` -Kaj pa oklepaji in vejice? +In zavite oklepaje in vejice? ```neon php: @@ -394,7 +416,7 @@ users: [ ] ``` -Ali so zaporedja bolj čitljiva? +Ali niso seznami z alinejami bolje berljivi? ```neon php: @@ -412,14 +434,14 @@ users: - Rimmer ``` -Kaj pa komentarji? +Dodamo komentarje? ```neon # konfiguracija moje spletne aplikacije php: date.timezone: Europe/Prague - zlib.output_compression: true # uporabite gzip + zlib.output_compression: true # uporabi gzip database: driver: mysql @@ -432,8 +454,7 @@ users: - Rimmer ``` -Našli ste sintakso NEON! +Hura, zdaj poznate sintakso NEON-a! -{{description: NEON je človeku prijazen jezik za serializacijo podatkov. Podoben je jeziku YAML. Glavna razlika je v tem, da NEON podpira "entitete" in znake tabulatorja za alinejo.}} -{{leftbar: utils:@left-menu}} +{{description: NEON je lahko berljiv format za serializacijo podatkov. Podoben je YAMLu. Glavna razlika je v tem, da NEON podpira »entitete« in za zamikanje lahko uporabimo tako presledke kot tabulatorje.}} diff --git a/neon/tr/@home.texy b/neon/tr/@home.texy index f2bf1cb356..05d3e314f1 100644 --- a/neon/tr/@home.texy +++ b/neon/tr/@home.texy @@ -1,54 +1,54 @@ -NEON İşlevleri -************** +Nette NEON +********** <div class=perex> -NEON insan dostu bir veri serileştirme dilidir. Nette yapılandırma dosyaları için kullanılır. [api:Nette\Neon\Neon] NEON ile çalışmak için statik bir sınıftır. +NEON, verilerin serileştirilmesi için insan tarafından okunabilir bir dildir. Nette'de yapılandırma dosyaları için kullanılır. [api:Nette\Neon\Neon], NEON ile çalışmak için statik bir sınıftır. -[NEON formatını |format] tanıyın ve [deneyin |https://ne-on.org]. +[NEON biçimiyle |format] tanışın ve [onu deneyin |https://ne-on.org]. </div> -Aşağıdaki örnekler bu takma adları kullanmaktadır: +Tüm örnekler, bir takma adın oluşturulduğunu varsayar: ```php use Nette\Neon\Neon; ``` -Kurulum .[#toc-installation] ----------------------------- +Kurulum +------- -[Composer'ı |best-practices:composer] kullanarak paketi indirin ve yükleyin: +Kütüphaneyi [Composer|best-practices:composer] aracını kullanarak indirip kurun: ```shell composer require nette/neon ``` -`neon-lint` konsol komutunu kullanarak `*.neon` dosyalarındaki sözdizimi hatalarını kontrol edebilirsiniz: +`*.neon` dosyalarındaki sözdizimi hatalarını konsol komutu `neon-lint` ile kontrol edebilirsiniz: ```shell vendor/bin/neon-lint <path> ``` -encode(mixed $value, bool $blockMode=false): string .[method] -------------------------------------------------------------- +encode(mixed $value, bool $blockMode=false, string $indentation="\t"): string .[method] +--------------------------------------------------------------------------------------- -NEON'a dönüştürülmüş `$value` döndürür. Parametre olarak `$blockMode` çok satırlı çıktı oluşturacak true değerini geçebilirsiniz. `$indentation` parametresi girinti için kullanılan karakterleri belirtir (varsayılan tab'dır). +NEON'a dönüştürülmüş `$value` değerini döndürür. `$blockMode` parametresi olarak true iletebilirsiniz, bu da çok satırlı bir çıktı oluşturur. `$indentation` parametresi, girinti için kullanılan karakterleri belirtir (varsayılan sekmedir). ```php -Neon::encode($value); // NEON'a dönüştürülmüş $value döndürür -Neon::encode($value, true); // Çok satırlı NEON'a dönüştürülmüş $value döndürür +Neon::encode($value); // NEON'a dönüştürülmüş $value değerini döndürür +Neon::encode($value, true); // Çok satırlı NEON'a dönüştürülmüş $value değerini döndürür ``` -`encode()` yöntemi hata verdiğinde `Nette\Neon\Exception` adresini atar. +`encode()` metodu bir hata durumunda `Nette\Neon\Exception` fırlatır. ```php try { $neon = Neon::encode($value); } catch (Nette\Neon\Exception $e) { - // İstisna işleme + // istisna işleme } ``` @@ -56,21 +56,21 @@ try { decode(string $neon): mixed .[method] ------------------------------------- -Verilen NEON değerini PHP değerine dönüştürür. +Bir dizeyi NEON'dan PHP'ye dönüştürür. -Skalaları, dizileri, DateTimeImmutable nesneleri olarak [tarihleri |format#dates] ve [api:Nette\Neon\Entity] nesneleri olarak [varlıkları |format#Entities] döndürür. +Skalerleri, dizileri, [tarihleri |format#Tarih] DateTimeImmutable nesneleri olarak ve [varlıkları |format#Varlıklar Entities] [api:Nette\Neon\Entity] nesneleri olarak döndürür. ```php -Neon::decode('hello: world'); // Bir dizi döndürür ['hello' => 'world'] +Neon::decode('hello: world'); // ['hello' => 'world'] dizisini döndürür ``` -`decode()` yöntemi hata verdiğinde `Nette\Neon\Exception` adresini atar. +`decode()` metodu bir hata durumunda `Nette\Neon\Exception` fırlatır. ```php try { $value = Neon::decode($neon); } catch (Nette\Neon\Exception $e) { - // İstisna işleme + // istisna işleme } ``` @@ -78,13 +78,10 @@ try { decodeFile(string $file): mixed .[method] ----------------------------------------- -Dosyanın içeriğini NEON'dan PHP'ye dönüştürür ve tüm BOM'ları kaldırır. +Bir dosyanın içeriğini NEON'dan PHP'ye dönüştürür ve varsa BOM'u kaldırır. ```php Neon::decodeFile('config.neon'); ``` -`decodeFile()` yöntemi hata verdiğinde `Nette\Neon\Exception` adresini atar. - - -{{leftbar: utils:@left-menu}} +`decodeFile()` metodu bir hata durumunda `Nette\Neon\Exception` fırlatır. diff --git a/neon/tr/@meta.texy b/neon/tr/@meta.texy new file mode 100644 index 0000000000..345c572162 --- /dev/null +++ b/neon/tr/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Dokümantasyonu}} +{{leftbar: utils:@left-menu}} diff --git a/neon/tr/format.texy b/neon/tr/format.texy index b7a214e139..7d724df789 100644 --- a/neon/tr/format.texy +++ b/neon/tr/format.texy @@ -2,19 +2,19 @@ NEON Formatı ************ .[perex] -NEON insan tarafından okunabilen yapılandırılmış bir veri formatıdır. Nette, yapılandırma dosyaları için kullanılır. Ayrıca ayarlar, dil çevirileri vb. gibi yapılandırılmış veriler için de kullanılır. [Sandbox üzerinde deneyin |https://ne-on.org]. +NEON, insan tarafından okunabilir yapılandırılmış bir veri formatıdır. Nette'de yapılandırma dosyaları için kullanılır. Ayrıca ayarlar, dil çevirileri vb. gibi yapılandırılmış veriler için de kullanılır. [Deneyin |https://ne-on.org]. -NEON, *Nette Nesne Notasyonu* anlamına gelir. XML veya JSON'dan daha az karmaşık ve hantaldır, ancak benzer yetenekler sağlar. YAML'ye çok benzemektedir. Ana avantajı, NEON'un DI hizmetlerinin yapılandırılmasının çok seksi olması sayesinde sözde [varlıklara |#entities] sahip olmasıdır. Ve girinti için sekmelere izin verir. +NEON, *Nette Object Notation*'ın kısaltmasıdır. XML veya JSON'dan daha az karmaşık ve hantaldır, ancak benzer işlevler sunar. YAML'ye çok benzer. Ana avantajı, NEON'un DI servislerinin yapılandırılmasını [çok seksi |https://gist.github.com/dg/26baf3ce8f29d0f751e9dddfaa06504f] yapan sözde [#Varlıklar (Entities)]'a sahip olmasıdır. Ve sekmelerle girintilemeye izin verir. -NEON, kullanımı basit olacak şekilde sıfırdan inşa edilmiştir. +NEON, kullanımı kolay olacak şekilde sıfırdan oluşturulmuştur. -Entegrasyon .[#toc-integration] -=============================== +Entegrasyon +=========== - NetBeans (yerleşik desteği vardır) - PhpStorm ([eklenti |https://plugins.jetbrains.com/plugin/7060?pr]) -- Visual Studio Code ([eklenti |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) +- Visual Studio Code ([Nette Latte + Neon |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) veya [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode]) - Sublime Text 3 ([eklenti |https://github.com/FilipStryk/Nette-Latte-Neon-for-Sublime-Text-3]) - Sublime Text 2 ([eklenti |https://github.com/Michal-Mikolas/Nette-package-for-Sublime-Text-2]) - VIM ([eklenti |https://github.com/fpob/nette.vim]) @@ -27,15 +27,15 @@ Entegrasyon .[#toc-integration] - [Python için NEON |https://github.com/paveldedik/neon-py]. -Sözdizimi .[#toc-syntax] -======================== +Sözdizimi +========= -NEON'da yazılmış bir dosya genellikle bir dizi veya eşlemeden oluşur. +NEON'da yazılmış bir dosya genellikle bir dizi veya bir eşleme temsil eder. -Eşlemeler .[#toc-mappings] --------------------------- -Eşleme bir anahtar-değer çiftleri kümesidir, PHP'de buna ilişkisel dizi denir. Her çift `key: value` şeklinde yazılır, `:`'dan sonra bir boşluk bırakılması gerekir. Değer herhangi bir şey olabilir: dize, sayı, boolean, null, dizi veya başka bir eşleme. +Eşleme (Mapping) +---------------- +Eşleme, anahtar-değer çiftleri kümesidir, PHP'de ilişkisel dizi denir. Her çift `key: value` olarak yazılır, `:` işaretinden sonraki boşluk gereklidir. Değer herhangi bir şey olabilir: karakter dizisi, sayı, boolean, null, dizi (sequence) veya başka bir eşleme. ```neon street: 742 Evergreen Terrace @@ -43,7 +43,7 @@ city: Springfield country: USA ``` -PHP'de aynı yapı şu şekilde yazılacaktır: +PHP'de aynı yapı şu şekilde yazılırdı: ```php [ // PHP @@ -53,13 +53,13 @@ PHP'de aynı yapı şu şekilde yazılacaktır: ] ``` -Bu gösterime blok gösterimi denir çünkü tüm öğeler ayrı bir satırdadır ve aynı girintiye sahiptir (bu durumda hiçbiri). NEON ayrıca parantez içine alınan, girintinin rol oynamadığı ve her bir öğenin ayırıcısının virgül veya satırsonu olduğu eşleme için satır içi gösterimi de destekler: +Bu gösterim blok gösterimi olarak adlandırılır, çünkü tüm öğeler ayrı bir satırdadır ve aynı girintiye sahiptir (bu durumda hiçbiri). NEON ayrıca, parantez içine alınmış, girintinin rol oynamadığı ve tek tek öğelerin ayırıcısının virgül veya yeni satır olduğu eşlemelerin satır içi temsilini de destekler: ```neon {street: 742 Evergreen Terrace, city: Springfield, country: USA} ``` -Bu, birden fazla satırda yazılanın aynısıdır (girinti önemli değildir): +Aynı şey birden çok satırda yazılmıştır (girintinin önemi yoktur): ```neon { @@ -68,16 +68,16 @@ Bu, birden fazla satırda yazılanın aynısıdır (girinti önemli değildir): } ``` -Alternatif olarak, `=` yerine kullanılabilir <code>: </code>, hem blok hem de satır içi gösterimde kullanılabilir: +<code>: </code> yerine alternatif olarak hem blok hem de satır içi gösterimde `=` kullanılabilir: ```neon {street=742 Evergreen Terrace, city=Springfield, country=USA} ``` -Diziler .[#toc-sequences] -------------------------- -Diziler PHP'de dizinlenmiş dizilerdir. Bunlar `-` tire işareti ve ardından bir boşluk ile başlayan satırlar olarak yazılır. Yine, değer herhangi bir şey olabilir: dize, sayı, boolean, null, dizi veya başka bir eşleme. +Diziler (Sequences) +------------------- +Diziler PHP'de indekslenmiş dizilerdir. Tire `-` ile başlayıp boşlukla devam eden satırlar olarak yazılırlar. Değer yine herhangi bir şey olabilir: karakter dizisi, sayı, boolean, null, dizi veya başka bir eşleme. ```neon - Cat @@ -85,7 +85,7 @@ Diziler PHP'de dizinlenmiş dizilerdir. Bunlar `-` tire işareti ve ardından bi - Goldfish ``` -PHP'de aynı yapı şu şekilde yazılacaktır: +PHP'de aynı yapı şu şekilde yazılırdı: ```php [ // PHP @@ -95,13 +95,13 @@ PHP'de aynı yapı şu şekilde yazılacaktır: ] ``` -Bu gösterime blok gösterimi denir çünkü tüm öğeler ayrı bir satırdadır ve aynı girintiye sahiptir (bu durumda hiçbiri). NEON ayrıca diziler için parantez içine alınmış satır içi gösterimi de destekler, girinti bir rol oynamaz ve her bir öğenin ayırıcısı virgül veya satırsonudur: +Bu gösterim blok gösterimi olarak adlandırılır, çünkü tüm öğeler ayrı bir satırdadır ve aynı girintiye sahiptir (bu durumda hiçbiri). NEON ayrıca, parantez içine alınmış, girintinin rol oynamadığı ve tek tek öğelerin ayırıcısının virgül veya yeni satır olduğu dizilerin satır içi temsilini de destekler: ```neon [Cat, Dog, Goldfish] ``` -Bu, birden fazla satırda yazılanın aynısıdır (girinti önemli değildir): +Aynı şey birden çok satırda yazılmıştır (girintinin önemi yoktur): ```neon [ @@ -110,23 +110,23 @@ Bu, birden fazla satırda yazılanın aynısıdır (girinti önemli değildir): ] ``` -Satır içi gösterimde kısa çizgiler kullanılamaz. +Satır içi gösterimde girintili madde işaretleri kullanılamaz. -Kombinasyon .[#toc-combination] -------------------------------- -Eşlemelerin ve dizilerin değerleri başka eşlemeler ve diziler olabilir. Girinti seviyesi önemli bir rol oynar. Aşağıdaki örnekte, sıra öğelerini belirtmek için kullanılan kısa çizgi `pets` tuşundan daha büyük bir girintiye sahiptir, bu nedenle öğeler ilk satırın değeri haline gelir: +Kombinasyonlar +-------------- +Eşlemelerin ve dizilerin değerleri başka eşlemeler ve diziler olabilir. Ana rolü girinti seviyesi oynar. Aşağıdaki örnekte, dizi öğelerini belirtmek için kullanılan tire işareti, `pets` anahtarından daha büyük bir girintiye sahiptir, bu nedenle öğeler ilk satırın değeri haline gelir: ```neon pets: - - Cat - - Dog + - Cat + - Dog cars: - - Volvo - - Skoda + - Volvo + - Skoda ``` -PHP'de aynı yapı şu şekilde yazılacaktır: +PHP'de aynı yapı şu şekilde yazılırdı: ```php [ // PHP @@ -141,7 +141,7 @@ PHP'de aynı yapı şu şekilde yazılacaktır: ] ``` -Blok ve satır içi gösterimleri birleştirmek mümkündür: +Blok ve satır içi gösterimi birleştirmek mümkündür: ```neon pets: [Cat, Dog] @@ -151,17 +151,39 @@ cars: [ ] ``` -Blok gösterimi artık bir satır içi gösterimin içinde kullanılamaz, bu çalışmaz: +Satır içi gösterim içinde artık blok gösterimi kullanılamaz, bu çalışmaz: ```neon item: [ pets: - - Cat # THIS IS NOT POSSIBLE!!! + - Cat # BU MÜMKÜN DEĞİL!!! - Dog ] ``` -PHP eşleme ve diziler için aynı yapıyı, yani dizileri kullandığından, her ikisi de birleştirilebilir. Girintileme bu sefer aynıdır: +Önceki durumda, öğeleri dizi olan bir eşleme yazdık, şimdi tersini deneyelim ve eşlemeler içeren bir dizi oluşturalım: + +```neon +- + name: John + age: 35 +- + name: Peter + age: 28 +``` + +Madde işaretlerinin ayrı satırlarda olması gerekmez, şu şekilde de yerleştirilebilirler: + +```neon +- name: John + age: 35 +- name: Peter + age: 28 +``` + +Anahtarları boşluklarla bir sütuna hizalamak veya sekme kullanmak size kalmış. + +PHP'de hem eşlemeler hem de diziler için aynı yapı, yani dizi kullanıldığından, her ikisi de birleştirilebilir. Girinti bu sefer aynıdır: ```neon - Cat @@ -169,7 +191,7 @@ street: 742 Evergreen Terrace - Goldfish ``` -PHP'de aynı yapı şu şekilde yazılacaktır: +PHP'de aynı yapı şu şekilde yazılırdı: ```php [ // PHP @@ -180,81 +202,81 @@ PHP'de aynı yapı şu şekilde yazılacaktır: ``` -Dizeler .[#toc-strings] ------------------------ -NEON'daki dizeler tek veya çift tırnak içine alınabilir. Ancak görebileceğiniz gibi, tırnak işaretleri olmadan da olabilirler. +Karakter Dizileri (Strings) +--------------------------- +NEON'daki karakter dizileri tek veya çift tırnak içine alınabilir. Ancak gördüğünüz gibi, tırnak işaretleri olmadan da olabilirler. ```neon -- A unquoted string in NEON -- 'A singled-quoted string in NEON' -- "A double-quoted string in NEON" +- NEON'da tırnak işareti olmayan karakter dizisi +- 'NEON'da tek tırnak işaretli karakter dizisi' +- "NEON'da çift tırnak işaretli karakter dizisi" ``` -Dize karakter içeriyorsa `# " ' , : = - [ ] { } ( )` NEON sözdizimi ile karıştırılabileceği için tırnak içine alınmalıdır. Kaçış kullanmadıkları için tek tırnak kullanmanızı öneririz. Böyle bir dizede bir tırnak işareti içine almanız gerekiyorsa, iki katına çıkarın: +Bir karakter dizisi `# " ' , : = - [ ] { } ( )` karakterlerini içeriyorsa, bunlar NEON sözdizimi ile karıştırılabileceğinden tırnak içine alınmalıdır. Tek tırnak işaretleri kullanmanızı öneririz, çünkü bunlarda kaçış kullanılmaz. Böyle bir karakter dizisinde tırnak işareti yazmanız gerekiyorsa, çiftleyin: ```neon -'A single quote '' inside a single-quoted string' +'Tek tırnak işaretli karakter dizisi içinde tırnak işareti ''' ``` -Çift tırnaklar, ters eğik çizgileri kullanarak özel karakterler yazmak için kaçış dizilerini kullanmanıza olanak tanır `\`. All escape sequences as in the JSON format are supported, plus `\_`, bu boşluk bırakmayan bir boşluktur, yani `\u00A0`. +Çift tırnak işaretleri, ters eğik çizgiler `\` kullanarak özel karakterleri yazmak için kaçış dizilerini kullanmanıza olanak tanır. JSON formatındaki tüm kaçış dizileri desteklenir ve ek olarak `\_`, yani bölünemez boşluk, yani `\u00A0` desteklenir. ```neon - "\t \n \r \f \b \" \\ \/ \_" - "\u00A9" ``` -Dizeleri tırnak işareti içine almanız gereken başka durumlar da vardır: +Karakter dizilerini tırnak içine almanız gereken başka durumlar da vardır: - boşluklarla başlar veya biterler -- sayı, boole veya null gibi görünebilir -- NEON bunları [tarih |#dates] olarak anlayacaktır +- sayılar, booleanlar veya null gibi görünürler +- NEON onları [#tarih] olarak anlardı -Çok Satırlı Dizeler .[#toc-multiline-strings] ---------------------------------------------- +Çok Satırlı Karakter Dizileri +----------------------------- -Çok satırlı bir dize ayrı satırlarda üçlü tırnak işaretiyle başlar ve biter. İlk satırın girintisi tüm satırlar için yok sayılır: +Çok satırlı bir karakter dizisi, ayrı satırlarda üçlü tırnak işaretiyle başlar ve biter. İlk satırın girintisi tüm satırlar için yok sayılır: ```neon ''' - first line - second line - third line + ilk satır + ikinci satır + üçüncü satır ''' ``` -PHP'de aynısını şöyle yazardık: +PHP'de aynı şeyi şu şekilde yazardık: ```php -"first line\n\tsecond line\nthird line" // PHP +"ilk satır\n\tikinci satır\nüçüncü satır" // PHP ``` -Kaçış dizileri yalnızca kesme işaretleri yerine çift tırnak içine alınmış dizeler için çalışır: +Kaçış dizileri yalnızca tırnak yerine çift tırnak içine alınmış karakter dizilerinde çalışır: ```neon """ - Copyright \u00A9 + Telif Hakkı \u00A9 """ ``` -Sayılar .[#toc-numbers] ------------------------ -NEON, bilimsel gösterim olarak adlandırılan şekilde yazılmış sayıları ve ayrıca ikili, sekizli ve onaltılı sayıları anlar: +Sayılar +------- +NEON, bilimsel gösterimde yazılmış sayıları ve ayrıca ikili, sekizlik ve onaltılık tabandaki sayıları anlar: ```neon -- 12 # bir tamsayı -- 12.3 # a float -- +1.2e-34 # üstel bir sayı +- 12 # tamsayı +- 12.3 # float +- +1.2e-34 # üstel sayı -- 0b11010 # ikili sayı -- 0o666 # sekizli sayı -- 0x7A # hexa sayısı +- 0b11010 # ikili sayı +- 0o666 # sekizlik sayı +- 0x7A # onaltılık sayı ``` -Nulls .[#toc-nulls] -------------------- -Null NEON'da `null` kullanılarak veya bir değer belirtilmeden ifade edilebilir. Baş harfi büyük olan veya tüm harfleri büyük olan varyantlara da izin verilir. +Null Değerler +------------- +Null, NEON'da `null` kullanılarak veya değer belirtmeyerek ifade edilebilir. İlk harfi büyük veya tüm harfleri büyük olan varyantlara da izin verilir. ```neon a: null @@ -262,50 +284,50 @@ b: ``` -Booleans .[#toc-booleans] -------------------------- -Boolean değerler NEON'da `true` / `false` veya `yes` / `no` kullanılarak ifade edilir. Baş harfi büyük veya tamamı büyük harf olan varyantlara da izin verilir. +Boolean Değerler +---------------- +Mantıksal değerler NEON'da `true` / `false` veya `yes` / `no` kullanılarak ifade edilir. İlk harfi büyük veya tüm harfleri büyük olan varyantlara da izin verilir. ```neon [true, TRUE, True, false, yes, no] ``` -Tarihler .[#toc-dates] ----------------------- -NEON, verileri ifade etmek için aşağıdaki formatları kullanır ve bunları otomatik olarak `DateTimeImmutable` nesnelerine dönüştürür: +Tarih +----- +NEON, tarihleri ifade etmek için aşağıdaki formatları kullanır ve bunları otomatik olarak `DateTimeImmutable` nesnelerine dönüştürür: ```neon -- 2016-06-03 # tarih -- 2016-06-03 19:00:00 # tarih ve saat -- 2016-06-03 19:00:00.1234 # tarih ve mikro zaman -- 2016-06-03 19:00:00 +0200 # tarih & saat & zaman dilimi -- 2016-06-03 19:00:00 +02:00 # tarih & saat & zaman dilimi +- 2016-06-03 # tarih +- 2016-06-03 19:00:00 # tarih & saat +- 2016-06-03 19:00:00.1234 # tarih & mikrosaniye +- 2016-06-03 19:00:00 +0200 # tarih & saat & bölge +- 2016-06-03 19:00:00 +02:00 # tarih & saat & bölge ``` -Varlıklar .[#toc-entities] --------------------------- -Varlık, bir fonksiyon çağrısına benzeyen bir yapıdır: +Varlıklar (Entities) +-------------------- +Varlık, bir fonksiyon çağrısını andıran bir yapıdır: ```neon Column(type: int, nulls: yes) ``` -PHP'de bir nesne olarak ayrıştırılır [api:Nette\Neon\Entity]: +PHP'de [api:Nette\Neon\Entity] nesnesi olarak ayrıştırılır: ```php // PHP new Nette\Neon\Entity('Column', ['type' => 'int', 'nulls' => true]) ``` -Varlıklar da zincirlenebilir: +Varlıklar zincirlenebilir de: ```neon Column(type: int, nulls: yes) Field(id: 1) ``` -PHP'de aşağıdaki gibi ayrıştırılır: +Bu, PHP'de şu şekilde ayrıştırılır: ```php // PHP @@ -315,7 +337,7 @@ new Nette\Neon\Entity(Nette\Neon\Neon::Chain, [ ]) ``` -Parantezlerin içinde, eşleme ve diziler için kullanılan satır içi gösterim kuralları geçerlidir, bu nedenle birkaç satıra bölünebilir ve virgül eklemek gerekli değildir: +Parantez içinde, eşlemeler ve diziler için kullanılan satır içi gösterim kuralları geçerlidir, yani birden çok satırlı olabilir ve o zaman virgül belirtmek gerekli değildir: ```neon Column( @@ -325,21 +347,21 @@ Column( ``` -Yorumlar .[#toc-comments] -------------------------- -Yorumlar `#` ile başlar ve sağdaki tüm karakterler yok sayılır: +Yorumlar +-------- +Yorumlar `#` karakteriyle başlar ve sağdaki tüm sonraki karakterler yok sayılır: ```neon -# bu satır yorumlayıcı tarafından göz ardı edilecektir -Sokak: 742 Evergreen Terrace -Şehir: Springfield # bu da göz ardı edildi -ülke ABD +# bu satır yorumlayıcı tarafından yok sayılacak +street: 742 Evergreen Terrace +city: Springfield # bu da yok sayılır +country: USA ``` -NEON JSON'a Karşı .[#toc-neon-versus-json] -========================================== -JSON, NEON'un bir alt kümesidir. Bu nedenle her JSON NEON olarak ayrıştırılabilir: +Neon vs JSON +============ +JSON, NEON'un bir alt kümesidir. Bu nedenle her JSON, NEON olarak ayrıştırılabilir: ```neon { @@ -358,7 +380,7 @@ JSON, NEON'un bir alt kümesidir. Bu nedenle her JSON NEON olarak ayrıştırıl } ``` -Tırnak işaretlerini çıkarırsak ne olur? +Ya tırnak işaretlerini çıkarsaydık? ```neon { @@ -377,7 +399,7 @@ users: [ } ``` -Parantez ve virgüllere ne dersiniz? +Ve süslü parantezler ve virgüller? ```neon php: @@ -394,7 +416,7 @@ users: [ ] ``` -Mermiler daha okunaklı mı? +Madde işaretli listeler daha okunaklı değil mi? ```neon php: @@ -412,14 +434,14 @@ users: - Rimmer ``` -Peki ya yorumlar? +Yorum ekleyelim mi? ```neon -# my web application config +# web uygulamamın yapılandırması php: date.timezone: Europe/Prague - zlib.output_compression: true # use gzip + zlib.output_compression: true # gzip kullan database: driver: mysql @@ -432,8 +454,7 @@ users: - Rimmer ``` -NEON sözdizimini buldunuz! +Yaşasın, şimdi NEON sözdizimini biliyorsunuz! -{{description: NEON insan dostu bir veri serileştirme dilidir. YAML ile benzerlik gösterir. Temel fark, NEON'un "varlıkları" ve girinti için sekme karakterlerini desteklemesidir.}} -{{leftbar: utils:@left-menu}} +{{description: NEON, verileri serileştirmek için okunması kolay bir formattır. YAML'ye benzer. Ana fark, NEON'un "varlıkları" desteklemesi ve girintileme için hem boşlukları hem de sekmeleri kullanabilmemizdir.}} diff --git a/neon/uk/@home.texy b/neon/uk/@home.texy index 26b3e05d9a..bd187fa15f 100644 --- a/neon/uk/@home.texy +++ b/neon/uk/@home.texy @@ -1,45 +1,45 @@ -Функції NEON -************ +Nette NEON +********** <div class=perex> -NEON - це зручна для людини мова серіалізації даних. Вона використовується в Nette для конфігураційних файлів. [api:Nette\Neon\Neon] - це статичний клас для роботи з NEON. +NEON — це зрозуміла для людини мова серіалізації даних. Вона використовується в Nette для конфігураційних файлів. [api:Nette\Neon\Neon] — це статичний клас для роботи з NEON. -Познайомтеся з [форматом NEON |format] і [спробуйте його |https://ne-on.org] в роботі. +Ознайомтеся з [форматом NEON|format] та [спробуйте його |https://ne-on.org]. </div> -У наступних прикладах використовуються ці псевдоніми: +Усі приклади передбачають створений псевдонім: ```php use Nette\Neon\Neon; ``` -Встановлення .[#toc-installation] ---------------------------------- +Встановлення +------------ -Завантажте та встановіть пакет за допомогою [Composer |best-practices:composer]: +Завантажте та встановіть бібліотеку за допомогою [Composer|best-practices:composer]: ```shell composer require nette/neon ``` -Ви можете перевірити наявність синтаксичних помилок у файлах `*.neon` за допомогою консольної команди `neon-lint`: +Помилки синтаксису у файлах `*.neon` можна перевірити за допомогою консольної команди `neon-lint`: ```shell -vendor/bin/neon-lint <path> +vendor/bin/neon-lint <шлях> ``` -encode(mixed $value, bool $blockMode=false): string .[method] -------------------------------------------------------------- +encode(mixed $value, bool $blockMode=false, string $indentation="\t"): string .[method] +--------------------------------------------------------------------------------------- -Повертає `$value`, перетворений у NEON. Як параметр `$blockMode` можна передати true, що створить багаторядковий вивід. Параметр `$indentation` задає символи, що використовуються для відступів (за замовчуванням це tab). +Повертає `$value`, перетворене на NEON. Як параметр `$blockMode` можна передати true, що створить багаторядковий вивід. Параметр `$indentation` визначає символи, що використовуються для відступу (за замовчуванням — табуляція). ```php -Neon::encode($value); // Повертає $value, перетворене в NEON -Neon::encode($value, true); // Повертає $value, перетворене в багаторядковий NEON +Neon::encode($value); // Повертає $value, перетворене на NEON +Neon::encode($value, true); // Повертає $value, перетворене на багаторядковий NEON ``` Метод `encode()` при помилці викидає `Nette\Neon\Exception`. @@ -48,7 +48,7 @@ Neon::encode($value, true); // Повертає $value, перетворене try { $neon = Neon::encode($value); } catch (Nette\Neon\Exception $e) { - // Обробка виключення + // обробка винятку } ``` @@ -56,21 +56,21 @@ try { decode(string $neon): mixed .[method] ------------------------------------- -Конвертує заданий NEON у значення PHP. +Перетворює рядок з NEON на PHP. -Повертає скаляри, масиви, [дату |format#Dates] як об'єкти DateTimeImmutable і [сутності |format#Entities] як об'єкти [api:Nette\Neon\Entity]. +Повертає скаляри, масиви, [дані |format#Дата] як об'єкти DateTimeImmutable та [сутності |format#Сутності] як об'єкти [api:Nette\Neon\Entity]. ```php -Neon::decode('hello: world'); // Повертає масив ['hello' => 'world']. +Neon::decode('hello: world'); // Повертає масив ['hello' => 'world'] ``` -Метод `decode()` викидає `Nette\Neon\Exception` у разі помилки. +Метод `decode()` при помилці викидає `Nette\Neon\Exception`. ```php try { $value = Neon::decode($neon); } catch (Nette\Neon\Exception $e) { - // Обробка виключення + // обробка винятку } ``` @@ -78,13 +78,10 @@ try { decodeFile(string $file): mixed .[method] ----------------------------------------- -Перетворює вміст файлу з NEON на PHP і видаляє всі BOM. +Перетворює вміст файлу з NEON на PHP та видаляє можливий BOM. ```php Neon::decodeFile('config.neon'); ``` -Метод `decodeFile()` при помилці кидає `Nette\Neon\Exception`. - - -{{leftbar: utils:@left-menu}} +Метод `decodeFile()` при помилці викидає `Nette\Neon\Exception`. diff --git a/neon/uk/@meta.texy b/neon/uk/@meta.texy new file mode 100644 index 0000000000..1afb4607cf --- /dev/null +++ b/neon/uk/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Документація Nette}} +{{leftbar: utils:@left-menu}} diff --git a/neon/uk/format.texy b/neon/uk/format.texy index 3a30b080b4..d168a547a3 100644 --- a/neon/uk/format.texy +++ b/neon/uk/format.texy @@ -2,19 +2,19 @@ *********** .[perex] -NEON - це структурований формат даних, що читається людиною. У Nette він використовується для файлів конфігурації. Він також використовується для структурованих даних, таких як налаштування, мовні переклади тощо. [Спробуйте його в пісочниці |https://ne-on.org]. +NEON — це читабельний для людини структурований формат даних. У Nette він використовується для конфігураційних файлів. Також він використовується для структурованих даних, таких як налаштування, мовні переклади тощо. [Спробуйте його |https://ne-on.org]. -NEON розшифровується як *Nette Object Notation*. Він менш складний і незграбний, ніж XML або JSON, але надає аналогічні можливості. Він дуже схожий на YAML. Головна перевага в тому, що NEON має так звані [сутності |#Entities], завдяки яким конфігурація DI-сервісів така сексуальна. І дозволяє використовувати вкладки для відступів. +NEON — це абревіатура від *Nette Object Notation*. Він менш складний і громіздкий, ніж XML або JSON, але надає схожі функції. Він дуже схожий на YAML. Головна перевага полягає в тому, що NEON має так звані [#сутності], завдяки яким конфігурація DI-сервісів [також сексі |https://gist.github.com/dg/26baf3ce8f29d0f751e9dddfaa06504f]. І дозволяє використовувати табуляцію для відступів. -NEON створений з нуля, щоб бути простим у використанні. +NEON створений з нуля так, щоб бути простим у використанні. -Інтеграція .[#toc-integration] -============================== +Інтеграція +========== - NetBeans (має вбудовану підтримку) - PhpStorm ([плагін |https://plugins.jetbrains.com/plugin/7060?pr]) -- Visual Studio Code ([плагін |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) +- Visual Studio Code ([Nette Latte + Neon |https://marketplace.visualstudio.com/items?itemName=Kasik96.latte]) або [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode]) - Sublime Text 3 ([плагін |https://github.com/FilipStryk/Nette-Latte-Neon-for-Sublime-Text-3]) - Sublime Text 2 ([плагін |https://github.com/Michal-Mikolas/Nette-package-for-Sublime-Text-2]) - VIM ([плагін |https://github.com/fpob/nette.vim]) @@ -22,20 +22,20 @@ NEON створений з нуля, щоб бути простим у вико - Prism.js ([інтегрована мова |https://prismjs.com/#supported-languages]) -- [NEON для PHP |@home] -- [NEON для JavaScript |https://github.com/matej21/neon-js] -- NEON [для Python |https://github.com/paveldedik/neon-py]. +- [NEON for PHP |@home] +- [NEON for JavaScript |https://github.com/matej21/neon-js] +- [NEON for Python |https://github.com/paveldedik/neon-py]. -Синтаксис .[#toc-syntax] -======================== +Синтаксис +========= -Файл, написаний на NEON, зазвичай складається з послідовності або відображення. +Файл, написаний у NEON, зазвичай представляє масив або мапування. -Зіставлення .[#toc-mappings] ----------------------------- -Маппінг - це набір пар ключ-значення, у PHP це називається асоціативним масивом. Кожна пара записується як `key: value`, пробіл після `:` обов'язковий. Значення може бути будь-яким: рядок, число, булево, null, послідовність або інше відображення. +Мапування +--------- +Мапування — це набір пар ключ-значення, у PHP це називається асоціативним масивом. Кожна пара записується як `key: value`, пробіл після `:` є обов'язковим. Значенням може бути будь-що: рядок, число, логічне значення, null, послідовність або інше мапування. ```neon street: 742 Evergreen Terrace @@ -43,7 +43,7 @@ city: Springfield country: USA ``` -У PHP та сама структура буде записана як: +У PHP та ж структура записувалася б так: ```php [ // PHP @@ -53,13 +53,13 @@ country: USA ] ``` -Ця нотація називається блоковою, тому що всі елементи розташовані в окремому рядку та мають однаковий відступ (у цьому випадку він відсутній). NEON також підтримує порядкове представлення для відображення, яке укладено в дужки, відступи не відіграють жодної ролі, а роздільником кожного елемента є або кома, або новий рядок: +Цей запис називається блоковим, оскільки всі елементи знаходяться на окремих рядках і мають однаковий відступ (у цьому випадку жодного). NEON також підтримує інлайн-представлення мапувань, яке укладається в дужки, відступ не відіграє жодної ролі, а роздільником окремих елементів є кома або новий рядок: ```neon {street: 742 Evergreen Terrace, city: Springfield, country: USA} ``` -Це одне й те саме, написане на кількох рядках (відступ не має значення): +Те ж саме, записане на кількох рядках (відступ не має значення): ```neon { @@ -68,16 +68,16 @@ country: USA } ``` -Як альтернативу можна використовувати `=` замість <code>: </code>, як у блоковій, так і в інлайн-нотації: +Замість <code>: </code> можна альтернативно використовувати `=` як у блоковому, так і в інлайн-записі: ```neon {street=742 Evergreen Terrace, city=Springfield, country=USA} ``` -Послідовності .[#toc-sequences] -------------------------------- -Послідовності - це індексовані масиви в PHP. Вони записуються у вигляді рядків, що починаються з дефіса `-`, за яким слідує пробіл. Значення може бути будь-яким: рядок, число, булево, null, послідовність або інше відображення. +Послідовності +------------- +Послідовності в PHP — це індексовані масиви. Вони записуються як рядки, що починаються з дефіса `-`, за яким слідує пробіл. Значенням знову може бути будь-що: рядок, число, логічне значення, null, послідовність або інше мапування. ```neon - Cat @@ -85,7 +85,7 @@ country: USA - Goldfish ``` -У PHP та сама структура матиме такий вигляд: +У PHP та ж структура записувалася б так: ```php [ // PHP @@ -95,13 +95,13 @@ country: USA ] ``` -Ця нотація називається блоковою, тому що всі елементи знаходяться на окремому рядку і мають однаковий відступ (у цьому випадку він відсутній). NEON також підтримує потокове представлення послідовностей, які укладаються в дужки, відступи не відіграють жодної ролі, а роздільником кожного елемента є або кома, або новий рядок: +Цей запис називається блоковим, оскільки всі елементи знаходяться на окремих рядках і мають однаковий відступ (у цьому випадку жодного). NEON також підтримує інлайн-представлення послідовності, яке укладається в дужки, відступ не відіграє жодної ролі, а роздільником окремих елементів є кома або новий рядок: ```neon [Cat, Dog, Goldfish] ``` -Це одне й те саме, написане на кількох рядках (відступ не має значення): +Те ж саме, записане на кількох рядках (відступ не має значення): ```neon [ @@ -110,23 +110,23 @@ country: USA ] ``` -Дефіси не можуть бути використані в інлайн-представленні. +В інлайн-представленні не можна використовувати відступи з маркерами списку. -Комбінація .[#toc-combination] ------------------------------- -Значення відображень і послідовностей можуть бути іншими відображеннями і послідовностями. Рівень відступу відіграє важливу роль. У наступному прикладі дефіс, який використовується для позначення елементів послідовності, має більший відступ, ніж ключ `pets`, тому елементи стають значеннями першого рядка: +Комбінації +---------- +Значеннями мапувань і послідовностей можуть бути інші мапування та послідовності. Головну роль відіграє рівень відступу. У наступному прикладі дефіс, використаний для позначення елементів послідовності, має більший відступ, ніж ключ `pets`, тому елементи стають значенням першого рядка: ```neon pets: - - Cat - - Dog + - Cat + - Dog cars: - - Volvo - - Skoda + - Volvo + - Skoda ``` -У PHP та сама структура була б записана як: +У PHP та ж структура записувалася б так: ```php [ // PHP @@ -141,7 +141,7 @@ cars: ] ``` -Можна комбінувати блокову та інлайн-нотацію: +Можна комбінувати блоковий та інлайн-запис: ```neon pets: [Cat, Dog] @@ -151,17 +151,39 @@ cars: [ ] ``` -Блокова нотація більше не може бути використана всередині рядкової нотації, це не працює: +Всередині інлайн-запису вже не можна використовувати блоковий запис, це не працює: ```neon item: [ pets: - - Cat # THIS IS NOT POSSIBLE!!! + - Cat # ЦЕ НЕ МОЖНА!!! - Dog ] ``` -Оскільки PHP використовує одну й ту саму структуру для відображення і послідовностей, тобто масиви, обидва варіанти можуть бути об'єднані. Цього разу відступи однакові: +У попередньому випадку ми записали мапування, елементами якого були послідовності, тепер спробуємо навпаки і створимо послідовність, що містить мапування: + +```neon +- + name: John + age: 35 +- + name: Peter + age: 28 +``` + +Не обов'язково, щоб маркери списку були на окремих рядках, їх можна розмістити й таким чином: + +```neon +- name: John + age: 35 +- name: Peter + age: 28 +``` + +Вирішувати вам, чи вирівнювати ключі в стовпець за допомогою пробілів, чи використовувати табуляцію. + +Оскільки в PHP для мапувань і послідовностей використовується однакова структура, тобто масив, їх можна об'єднати. Відступ цього разу однаковий: ```neon - Cat @@ -169,7 +191,7 @@ street: 742 Evergreen Terrace - Goldfish ``` -У PHP та сама структура буде записана як: +У PHP та ж структура записувалася б так: ```php [ // PHP @@ -180,55 +202,55 @@ street: 742 Evergreen Terrace ``` -Рядки .[#toc-strings] ---------------------- -Рядки в NEON можуть бути укладені в одинарні або подвійні лапки. Але, як ви бачите, вони можуть бути і без лапок. +Рядки +----- +Рядки в NEON можна укладати в одинарні та подвійні лапки. Але, як ви бачите, вони можуть бути і без лапок. ```neon -- A unquoted string in NEON -- 'A singled-quoted string in NEON' -- "A double-quoted string in NEON" +- Рядок у NEON без лапок +- 'Рядок у NEON в одинарних лапках' +- "Рядок у NEON у подвійних лапках" ``` -Якщо рядок містить символи `# " ' , : = - [ ] { } ( )` які можна сплутати із синтаксисом NEON, він має бути укладений у лапки. Ми рекомендуємо використовувати одинарні лапки, оскільки вони не використовують екранування. Якщо вам потрібно укласти лапки в такому рядку, подвійте їх: +Якщо рядок містить символи `# " ' , : = - [ ] { } ( )`, які можна сплутати з синтаксисом NEON, його потрібно укласти в лапки. Рекомендуємо використовувати одинарні лапки, оскільки в них не використовується екранування. Якщо вам потрібно в такому рядку записати лапку, подвойте її: ```neon -'A single quote '' inside a single-quoted string' +'Лапка '' всередині рядка в одинарних лапках' ``` -Подвійні лапки дають змогу використовувати екрануючі послідовності для запису спеціальних символів, використовуючи зворотні косі риски `\`. All escape sequences as in the JSON format are supported, plus `\_`, які являють собою нерозривний пробіл, тобто `\u00A0`. +Подвійні лапки дозволяють використовувати escape-послідовності для запису спеціальних символів за допомогою зворотних слешів `\`. Підтримуються всі escape-послідовності, як у форматі JSON, а також `\_`, що є нерозривним пробілом, тобто `\u00A0`. ```neon - "\t \n \r \f \b \" \\ \/ \_" - "\u00A9" ``` -Існують й інші випадки, коли необхідно укладати рядки в лапки: -- вони починаються або закінчуються пробілами -- виглядають як числа, булеві або null. -- NEON буде розуміти їх як [дати |#Dates] +Існують інші випадки, коли потрібно укладати рядки в лапки: +- починаються або закінчуються пробілами +- виглядають як числа, логічні значення або null +- NEON сприймав би їх як [дату |#Дата] -Багаторядкові рядки .[#toc-multiline-strings] ---------------------------------------------- +Багаторядкові рядки +------------------- -Багаторядковий рядок починається і закінчується потрійною лапкою на окремих рядках. Відступ першого рядка ігнорується для всіх рядків: +Багаторядковий рядок починається і закінчується потрійними лапками на окремих рядках. Відступ першого рядка ігнорується, і це стосується всіх рядків: ```neon ''' - first line - second line - third line + перший рядок + другий рядок + третій рядок ''' ``` -У PHP ми б написали те саме: +У PHP ми б написали те саме так: ```php -"first line\n\tsecond line\nthird line" // PHP +"перший рядок\n\tdругий рядок\nтретій рядок" // PHP ``` -Послідовності екранування працюють тільки для рядків, укладених у подвійні лапки замість апострофів: +Escape-послідовності працюють лише в рядках, укладених у подвійні лапки замість апострофів: ```neon """ @@ -237,24 +259,24 @@ street: 742 Evergreen Terrace ``` -Числа .[#toc-numbers] ---------------------- -NEON розуміє числа, записані в так званій науковій нотації, а також числа в двійковій, вісімковій і шістнадцятковій системі числення: +Числа +----- +NEON розуміє числа, записані в так званій науковій нотації, а також числа в двійковій, вісімковій та шістнадцятковій системах числення: ```neon -- 12 # ціле число -- 12.3 # число з плаваючою комою -- +1.2e-34 # експоненціальне число +- 12 # ціле число +- 12.3 # float +- +1.2e-34 # експоненціальне число -- 0b11010 # двійкове число -- 0o666 # вісімкове число -- 0x7A # шістнадцяткове число +- 0b11010 # двійкове число +- 0o666 # вісімкове число +- 0x7A # шістнадцяткове число ``` -Нулі .[#toc-nulls] ------------------- -Нуль може бути виражений у NEON за допомогою `null` або без зазначення значення. Також допускаються варіанти із великою першою або всіма великими літерами. +Nulls +----- +Null в NEON можна виразити за допомогою `null` або невказанням значення. Дозволені також варіанти з великою першою літерою або всіма великими літерами. ```neon a: null @@ -262,50 +284,50 @@ b: ``` -Булеві .[#toc-booleans] ------------------------ -Булеві значення виражаються в NEON за допомогою `true` / `false` або `yes` / `no`. Також допускаються варіанти із великою першою або всіма великими літерами. +Логічні значення +---------------- +Логічні значення в NEON виражаються за допомогою `true` / `false` або `yes` / `no`. Дозволені також варіанти з великою першою літерою або всіма великими літерами. ```neon [true, TRUE, True, false, yes, no] ``` -Дати .[#toc-dates] ------------------- -NEON використовує такі формати для вираження даних і автоматично перетворює їх на об'єкти `DateTimeImmutable`: +Дата +---- +NEON використовує для вираження дат наступні формати і автоматично перетворює їх на об'єкти `DateTimeImmutable`: ```neon -- 2016-06-03 # дата -- 2016-06-03 19:00:00 # дата та час -- 2016-06-03 19:00:00.1234 # дата та мікрочас -- 2016-06-03 19:00:00 +0200 # дата & час & часовий пояс -- 2016-06-03 19:00:00 +02:00 # дата, час та часовий пояс +- 2016-06-03 # дата +- 2016-06-03 19:00:00 # дата і час +- 2016-06-03 19:00:00.1234 # дата і мікрочас +- 2016-06-03 19:00:00 +0200 # дата і час і зона +- 2016-06-03 19:00:00 +02:00 # дата і час і зона ``` -Сутності .[#toc-entities] -------------------------- -Сутність - це структура, що нагадує виклик функції: +Сутності +-------- +Сутність — це структура, яка нагадує виклик функції: ```neon Column(type: int, nulls: yes) ``` -У PHP вона розбирається як об'єкт [api:Nette\Neon\Entity]: +У PHP це парситься як об'єкт [api:Nette\Neon\Entity]: ```php // PHP new Nette\Neon\Entity('Column', ['type' => 'int', 'nulls' => true]) ``` -Сутності також можуть бути об'єднані в ланцюжок: +Сутності можна також об'єднувати в ланцюжок: ```neon Column(type: int, nulls: yes) Field(id: 1) ``` -Що розбирається в PHP таким чином: +Що в PHP парситься таким чином: ```php // PHP @@ -315,7 +337,7 @@ new Nette\Neon\Entity(Nette\Neon\Neon::Chain, [ ]) ``` -Усередині круглих дужок застосовуються правила інлайн-нотації, які використовуються для відображення і послідовностей, тому його можна розділити на кілька рядків і немає необхідності додавати коми: +Всередині дужок діють правила для інлайн-запису, що використовується для мапувань і послідовностей, тобто він може бути багаторядковим, і тоді не потрібно вказувати коми: ```neon Column( @@ -325,21 +347,21 @@ Column( ``` -Коментарі .[#toc-comments] --------------------------- -Коментарі починаються з `#` і всі наступні символи праворуч ігноруються: +Коментарі +--------- +Коментарі починаються символом `#`, і всі наступні символи праворуч ігноруються: ```neon -# цей рядок буде проігноровано перекладачем -вулиця: 742 Вічнозелена тераса -місто: Спрінгфілд # цей рядок також ігнорується -країна: США +# цей рядок буде проігноровано інтерпретатором +street: 742 Evergreen Terrace +city: Springfield # це також ігнорується +country: USA ``` -NEON проти JSON .[#toc-neon-versus-json] -======================================== -JSON є підмножиною NEON. Тому кожен JSON може бути розібраний як NEON: +Neon проти JSON +=============== +JSON є підмножиною NEON. Тому кожен JSON можна розпарсити як NEON: ```neon { @@ -358,7 +380,7 @@ JSON є підмножиною NEON. Тому кожен JSON може бути } ``` -Що якби ми могли опустити лапки? +Що, якби ми пропустили лапки? ```neon { @@ -377,7 +399,7 @@ users: [ } ``` -Як щодо дужок і ком? +А фігурні дужки та коми? ```neon php: @@ -394,7 +416,7 @@ users: [ ] ``` -Чи є кулі більш розбірливими? +Чи не краще читаються списки з маркерами? ```neon php: @@ -412,14 +434,14 @@ users: - Rimmer ``` -Як щодо коментарів? +Додамо коментарі? ```neon -# my web application config +# конфігурація мого веб-додатку php: date.timezone: Europe/Prague - zlib.output_compression: true # use gzip + zlib.output_compression: true # використовувати gzip database: driver: mysql @@ -432,8 +454,7 @@ users: - Rimmer ``` -Ви знайшли синтаксис NEON! +Ура, тепер ви знаєте синтаксис NEON! -{{description: NEON - це зручна для людини мова серіалізації даних. Вона схожа на YAML. Основна відмінність полягає в тому, що NEON підтримує "сутності" і символи табуляції для відступів.}} -{{leftbar: utils:@left-menu}} +{{description: NEON — це легко читабельний формат для серіалізації даних. Він схожий на YAML. Головна відмінність полягає в тому, що NEON підтримує «сутності», і для відступів ми можемо використовувати як пробіли, так і табуляцію.}} diff --git a/nette/bg/@home.texy b/nette/bg/@home.texy index 186b0c1b59..a1ca3795bf 100644 --- a/nette/bg/@home.texy +++ b/nette/bg/@home.texy @@ -6,45 +6,45 @@ <div> -Въведение ---------- +Запознаване +----------- - [Защо да използвате Nette? |www:10-reasons-why-nette] -- [Инсталация |Installation] -- [Създайте първото си приложение! |quickstart:] +- [Инсталация |installation] +- [Пишем първото приложение! |quickstart:] -Обща информация ---------------- +Общи +---- - [Списък на пакетите |www:packages] -- [Поддръжка и PHP |www:maintenance] -- [Бележки към изданието |https://nette.org/releases] -- [Ръководство за надграждане |migrations:en] -- [Решаване на проблеми |nette:troubleshooting] -- [Създатели на Nette |https://nette.org/contributors] +- [Поддръжка и PHP версии |www:maintenance] +- [Бележки по изданието |https://nette.org/releases] +- [Преминаване към по-нови версии |migrations:en] +- [Отстраняване на проблеми |nette:troubleshooting] +- [Кой създава Nette |https://nette.org/contributors] - [История на Nette |www:history] - [Включете се |contributing:] -- [Развитие на спонсори |https://nette.org/en/donate] -- [Референтно ръководство за API |https://api.nette.org/] +- [Подкрепете разработката |https://nette.org/cs/donate] +- [API референция |https://api.nette.org/] </div> <div> -Приложение Nette ----------------- -- [Как работят приложенията |application:how-it-works]? -- [Bootstrap |application:Bootstrap] -- [Презентатори |application:Presenters] +Приложения в Nette +------------------ +- [Как работят приложенията? |application:how-it-works] +- [Bootstrapping |application:Bootstrapping] +- [Presenters |application:presenters] - [Шаблони |application:templates] -- [Модули |application:modules] -- [Маршрутизиране |application:Routing] -- [Създаване на URL |application:creating-links] +- [Директорийна структура |application:directory-structure] +- [Маршрутизация |application:routing] +- [Създаване на URL връзки |application:creating-links] - [Интерактивни компоненти |application:components] -- [AJAX и фрагменти |application:ajax] +- [AJAX & снипети |application:ajax] -- [Най-добри практики |best-practices:] +- [Ръководства и практики |best-practices:] </div> @@ -54,41 +54,42 @@ Основни теми ------------ - [Конфигурация |nette:configuring] -- [Изпълнение на зависимостта |dependency-injection:] -- [Latte: Шаблони |latte:] -- [Tracy: Инструмент за отстраняване на грешки |tracy:] +- [Dependency Injection|dependency-injection:] +- [Latte: шаблони |latte:] +- [Tracy: дебъгване на код |tracy:] - [Форми |forms:] -- [База данни |database:core] -- [Удостоверяване на потребителя |security:authentication] -- [Контрол на достъпа |security:authorization] +- [База данни |database:guide] +- [Вход на потребители |security:authentication] +- [Проверка на права |security:authorization] - [Сесии |http:Sessions] -- [HTTP заявка и отговор |http:] -- [Кеширане |caching:] +- [HTTP request & response|http:] +- [Активи |assets:] +- [Кеш |caching:] - [Изпращане на имейли |mail:] -- [Schema: валидиране на данни |schema:] +- [Schema: валидация на данни |schema:] - [Генератор на PHP код |php-generator:] -- [Tester: тестване на единици |tester:] +- [Tester: тестване |tester:] </div> <div> -Комунални услуги ----------------- -- [помощни средства:Масиви |utils:Arrays] +Utilities +--------- +- [Масиви |utils:arrays] - [Файлова система |utils:filesystem] -- [Търсачка |utils:finder] -- [HTML елементи |utils:HTML Elements] -- [Images |utils:Images] -- [помощни средства:JSON |utils:JSON] -- [NEON |neon:] -- [Събиране на пароли |security:passwords] -- [SmartObject |utils:SmartObject] -- [Типове PHP |utils:type] -- [Strings |utils:Strings] +- [Finder |utils:finder] +- [HTML елементи |utils:html-elements] +- [Изображения |utils:images] +- [JSON |utils:JSON] +- [NEON|neon:] +- [Хеширане на пароли |security:passwords] +- [PHP типове |utils:type] +- [Низове |utils:strings] - [Валидатори |utils:validators] - [RobotLoader |robot-loader:] +- [SmartObject |utils:smartobject] & [StaticClass |utils:StaticClass] - [SafeStream |safe-stream:] - [...други |utils:] </div> @@ -97,5 +98,5 @@ {{toc:no}} -{{description: Официалната документация на Nette описва как работи Nette и най-добрите практики за разработване на уеб приложения.}} +{{description: Официална документация на Nette: описва как работи Nette и най-добрите практики за разработване на уеб приложения.}} {{maintitle: Документация на Nette}} diff --git a/nette/bg/@menu-topics.texy b/nette/bg/@menu-topics.texy index a0ac4f5a35..a791ba67aa 100644 --- a/nette/bg/@menu-topics.texy +++ b/nette/bg/@menu-topics.texy @@ -1,21 +1,21 @@ Основни теми ************ - [Конфигурация |nette:configuring] -- [Приложение Nette |application:how-it-works] -- [Изпълнение на зависимостта |dependency-injection:] -- [Комунални услуги |utils:] +- [Приложения в Nette |application:how-it-works] +- [Dependency Injection|dependency-injection:] +- [Utilities |utils:] - [Форми |forms:] -- [База данни |database:core] -- [Удостоверяване на потребителя |security:authentication] -- [Контрол на достъпа |security:authorization] +- [База данни |database:guide] +- [Вход на потребители |security:authentication] +- [Проверка на права |security:authorization] - [Сесии |http:Sessions] -- [HTTP заявка и отговор |http:] -- [Кеширане |caching:] +- [HTTP request & response|http:] +- [Кеш |caching:] - [Изпращане на имейли |mail:] -- [Schema: валидиране на данни |schema:] +- [Schema: валидация на данни |schema:] - [Генератор на PHP код |php-generator:] - [Latte: шаблони |latte:] -- [Tracy: отстраняване на грешки |tracy:] +- [Tracy: дебъгване на код |tracy:] - [Tester: тестване |tester:] diff --git a/nette/bg/@meta.texy b/nette/bg/@meta.texy new file mode 100644 index 0000000000..57804a1127 --- /dev/null +++ b/nette/bg/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документация на Nette}} diff --git a/nette/bg/configuring.texy b/nette/bg/configuring.texy index 17f6c99c1b..1f8be0e9b4 100644 --- a/nette/bg/configuring.texy +++ b/nette/bg/configuring.texy @@ -2,34 +2,35 @@ ********************* .[perex] -Преглед на всички опции за конфигуриране в рамката Nette. +Преглед на всички опции за конфигурация в Nette Framework. -Компонентите на Nette се конфигурират с помощта на конфигурационни файлове, които обикновено са записани във формат [NEON |neon:format]. Най-добре е те да се редактират в [редактори, които поддържат този формат |best-practices:editors-and-tools#IDE-Editor]. -Ако използвате пълната рамка, конфигурацията ще бъде [заредена по време на зареждане |application:bootstrap#DI-Container-Configuration], ако не, вижте [как да заредите конфигурацията |bootstrap:]. +Компонентите на Nette се настройват с помощта на конфигурационни файлове, които обикновено се записват във [формат NEON|neon:format]. Най-добре се редактират в [редактори с неговата поддръжка |best-practices:editors-and-tools#IDE редактор]. Ако използвате целия framework, конфигурацията се [зарежда при стартиране на приложението |application:bootstrapping#Конфигурация на DI контейнера], ако не, прочетете [как да заредите конфигурацията|bootstrap:]. <pre> -"application .[prism-token prism-atrule]":[application:configuration#Application]: "приложение .[prism-token prism-comment]"<br> -"constants .[prism-token prism-atrule]":[application:configuration#Constants]: "Дефинира PHP константите .[prism-token prism-comment]"<br> -"database .[prism-token prism-atrule]":[database:configuration]: "база данни .[prism-token prism-comment]"<br> -"decorator .[prism-token prism-atrule]":[dependency-injection:configuration#Decorator]: "декоратор .[prism-token prism-comment]"<br> -"di .[prism-token prism-atrule]":[dependency-injection:configuration#DI]: "DI Container .[prism-token prism-comment]"<br> -"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Extensions]: "DI extensions installation .[prism-token prism-comment]"<br> -"forms .[prism-token prism-atrule]":[forms:configuration]: "формуляри .[prism-token prism-comment]"<br> -"http .[prism-token prism-atrule]":[http:configuration#HTTP Headers]: "HTTP headers .[prism-token prism-comment]"<br> -"includes .[prism-token prism-atrule]":[dependency-injection:configuration#Including files]: "включва файлове .[prism-token prism-comment]"<br> -"latte .[prism-token prism-atrule]":[application:configuration#Latte]: "Latte .[prism-token prism-comment]"<br> -"mail .[prism-token prism-atrule]":[mail:#Configuring]: "Mail .[prism-token prism-comment]"<br> -"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#Parameters]: "Параметри .[prism-token prism-comment]"<br> -"php .[prism-token prism-atrule]":[application:configuration#PHP]: "Параметри на конфигурацията на PHP .[prism-token prism-comment]"<br> -"routing .[prism-token prism-atrule]":[application:configuration#Routing]: "Маршрутизация .[prism-token prism-comment]"<br> -"search .[prism-token prism-atrule]":[dependency-injection:configuration#Search]: "Автоматична регистрация на услуги .[prism-token prism-comment]"<br> -"security .[prism-token prism-atrule]":[security:configuration]: "Контрол на достъпа .[prism-token prism-comment]"<br> -"services .[prism-token prism-atrule]":[dependency-injection:services]: "Услуги .[prism-token prism-comment]"<br> -"session .[prism-token prism-atrule]":[http:configuration#Session]: "Сесия .[prism-token prism-comment]"<br> -"tracy .[prism-token prism-atrule]":[tracy:configuring#Nette Framework]: "Tracy Debugger .[prism-token prism-comment]" +"application .[prism-token prism-atrule]":[application:configuration#Application]: "Приложение .[prism-token prism-comment]"<br> +"assets .[prism-token prism-atrule]":[assets:configuration]: "Assets .[prism-token prism-comment]"<br> +"constants .[prism-token prism-atrule]":[application:configuration#Константи]: "Дефиниране на PHP константи .[prism-token prism-comment]"<br> +"database .[prism-token prism-atrule]":[database:configuration]: "База данни .[prism-token prism-comment]"<br> +"decorator .[prism-token prism-atrule]":[dependency-injection:configuration#Decorator]: "Декоратор .[prism-token prism-comment]"<br> +"di .[prism-token prism-atrule]":[dependency-injection:configuration#DI]: "DI контейнер .[prism-token prism-comment]"<br> +"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Разширения]: "Инсталиране на допълнителни DI разширения .[prism-token prism-comment]"<br> +"forms .[prism-token prism-atrule]":[forms:configuration]: "Форми .[prism-token prism-comment]"<br> +"http .[prism-token prism-atrule]":[http:configuration#HTTP хедъри]: "HTTP хедъри .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[dependency-injection:configuration#Включване на файлове]: "Включване на файлове .[prism-token prism-comment]"<br> +"latte .[prism-token prism-atrule]":[application:configuration#Шаблони Latte]: "Шаблони Latte .[prism-token prism-comment]"<br> +"mail .[prism-token prism-atrule]":[mail:#Конфигурация]: "Имейли .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#Параметри]: "Параметри .[prism-token prism-comment]"<br> +"php .[prism-token prism-atrule]":[application:configuration#PHP]: "PHP конфигурация .[prism-token prism-comment]"<br> +"routing .[prism-token prism-atrule]":[application:configuration#Маршрутизация]: "Маршрутизация .[prism-token prism-comment]"<br> +"search .[prism-token prism-atrule]":[dependency-injection:configuration#Search]: "Автоматична регистрация на сървиси .[prism-token prism-comment]"<br> +"security .[prism-token prism-atrule]":[security:configuration]: "Права за достъп .[prism-token prism-comment]"<br> +"services .[prism-token prism-atrule]":[dependency-injection:services]: "Сървиси .[prism-token prism-comment]"<br> +"session .[prism-token prism-atrule]":[http:configuration#Сесия]: "Сесия .[prism-token prism-comment]"<br> +"tracy .[prism-token prism-atrule]":[tracy:configuring#Nette Framework]: "Tracy дебъгер .[prism-token prism-comment]" </pre> -Ако използвате низ, който започва с `@` или содержит `%` където и да било, трябва да го избегнете, като добавите втория символ `@` или `%`. .[note] +.[note] +Ако искате да запишете низ, съдържащ знака `%`, трябва да го екранирате, като го удвоите до `%%`. {{leftbar: @menu-topics}} diff --git a/nette/bg/glossary.texy b/nette/bg/glossary.texy index cf6970b7fa..d3893a0cfa 100644 --- a/nette/bg/glossary.texy +++ b/nette/bg/glossary.texy @@ -2,154 +2,158 @@ ******************* -AJAX .[#toc-ajax] ------------------ -Асинхронният JavaScript и XML е технология за комуникация между клиент и сървър, базирана на HTTP, без да се налага при всяка заявка да се презарежда цялата страница. Въпреки съкращението, вместо XML често се използва форматът [JSON |#JSON]. +AJAX +---- +Asynchronous JavaScript and XML - технология за обмен на информация между клиента и сървъра чрез HTTP протокол без необходимост от презареждане на цялата страница при всяка заявка. Въпреки че от името може да изглежда, че изпраща данни само във формат XML, често се използва и форматът [#JSON]. -Действие на водещия .[#toc-presenter-action] --------------------------------------------- -Логическа част от [презентатора |#presenter], която извършва едно действие, например показване на продуктова страница, изписване на потребител и т.н. Един водещ може да има няколко действия. +Действие на presenter +--------------------- +Логическа част на presenter-а, която изпълнява едно действие. Например, показва страница на продукт, излиза от системата потребител и т.н. Един presenter може да има няколко действия. BOM --- -Така наречената *маска за ред на байтовете* е специален първи символ във файл, който определя реда на байтовете в кодирането. Някои редактори го активират автоматично, то е почти невидимо, но причинява проблеми със заглавията и изпращането на изход от PHP. Можете да използвате [програмата за проверка на кода |code-checker:], за да я изтриете масово. +Така нареченият *byte order mark* е специален първи знак във файла, който се използва като индикатор за реда на байтовете в кодирането. Някои редактори го вмъкват във файловете. Той е практически невидим, но причинява проблеми с изпращането на изход и хедъри от PHP. За масово премахване можете да използвате [Code Checker|code-checker:]. -Контролер .[#toc-controller] ----------------------------- -Контролерът обработва заявките от потребителя и въз основа на тези заявки извиква специфична логика на приложението (т.е. [модел |#Модель]), след което извиква [View |#Вид], за да визуализира данните. Аналогът на контролерите в рамките на Nette са [презентаторите |#Presenter]. +Controller +---------- +Контролер, който обработва заявките на потребителя и въз основа на тях извиква съответната логика на приложението (т.е. [#модел]) и след това изисква от [изглед |#View] да изобрази данните. Аналог на контролерите в Nette Framework са [presenter-ите |#Presenter]. -Скриптиране на кръстосани сайтове (XSS) .[#toc-cross-site-scripting-xss] ------------------------------------------------------------------------- -Cross-site scripting е метод за нарушаване на работата на уебсайт чрез използване на непроверени входни данни. Атакуващият може да инжектира свой собствен HTML или JavaScript код и да промени външния вид на страницата или дори да събере чувствителна потребителска информация. Защитата срещу XSS е проста: последователно и правилно проверявайте всички низове и входни данни. +Cross-Site Scripting (XSS) +-------------------------- +Cross-Site Scripting е метод за нарушаване на уеб страници, който използва необработени изходи. Нападателят може да вмъкне свой собствен код в страницата и по този начин да я промени или дори да получи чувствителни данни за посетителите. Защитата срещу XSS е възможна само чрез последователна и коректна обработка на всички низове. -Рамката Nette Framework предлага чисто нова технология за [избягване на контекст |latte:safety-first#Context-Aware-Escaping], която завинаги ще премахне рисковете от скриптове на различни сайтове. Той автоматично ескапира всички входни данни въз основа на даден контекст, така че програмистът не може случайно да забрави нещо. +Nette Framework идва с революционна технология [Context-Aware Escaping |latte:safety-first#Контекстно-чувствително екраниране], която завинаги ще ви отърве от риска от Cross-Site Scripting. Тя обработва всички изходи автоматично, така че програмистът не може да забрави нещо. -Фалшифициране на заявка от друг сайт (CSRF) .[#toc-cross-site-request-forgery-csrf] ------------------------------------------------------------------------------------ -Атаката Cross-Site Request Forgery се състои в това, че нападателят примамва жертвата да посети страница, която безшумно изпълнява заявка в браузъра на жертвата към сървъра, в който жертвата е влязла в момента, и сървърът приема, че заявката е направена от жертвата по нейна собствена воля. Сървърът извършва определено действие под самоличността на жертвата, но без нейното знание. Това може да бъде промяна или изтриване на данни, изпращане на съобщение и т.н. +Cross-Site Request Forgery (CSRF) +--------------------------------- +Атаката Cross-Site Request Forgery се състои в това, че нападателят примамва жертвата към страница, която незабелязано в браузъра на жертвата изпълнява заявка към сървъра, на който жертвата е влязла, и сървърът смята, че заявката е изпълнена от жертвата по нейна воля. Така под самоличността на жертвата се извършва определено действие, без тя да знае за това. Това може да бъде промяна или изтриване на данни, изпращане на съобщение и т.н. -Структурата Nette Framework **автоматично защитава формулярите и сигналите в презентаторите** от този тип атаки. Това става, като се предотвратява изпращането или извикването им от друг домейн. +Nette Framework **автоматично защитава формите и сигналите в presenter-ите** от този тип атака. Това става чрез предотвратяване на тяхното изпращане или извикване от друг домейн. -Вкарване на зависимост (Dependency Injection) .[#toc-dependency-injection] --------------------------------------------------------------------------- -Впръскването на зависимости (Dependency Injection - DI) е шаблон за проектиране, който ви казва как да разделите създаването на обекти от техните зависимости. Това означава, че даден клас не е отговорен за създаването или инициализирането на своите зависимости, а вместо това тези зависимости се предоставят от външен код (който може да включва [контейнер за DI |#Dependency Injection container]). Предимството е, че това позволява по-голяма гъвкавост на кода, по-добра четимост и по-лесно тестване на приложенията, тъй като зависимостите са лесно заменяеми и изолирани от други части на кода. За повече информация вижте [Какво е инжектиране на зависимости |dependency-injection:introduction]? +Dependency Injection +-------------------- +Dependency Injection (DI) е патърн за дизайн, който указва как да се отдели създаването на обекти от техните зависимости. Тоест, класът не е отговорен за създаването или инициализирането на своите зависимости, а вместо това тези зависимости му се предоставят от външен код (това може да бъде и [DI контейнер |#Dependency Injection контейнер]). Предимството се състои в това, че позволява по-голяма гъвкавост на кода, по-добра разбираемост и по-лесно тестване на приложението, тъй като зависимостите са лесно заменяеми и изолирани от останалите части на кода. Повече в главата [Какво е Dependency Injection? |dependency-injection:introduction] -Контейнер за инжектиране на зависимости .[#toc-dependency-injection-container] ------------------------------------------------------------------------------- -Контейнерът за инжектиране на зависимости (също DI контейнер или IoC контейнер) е инструмент, който се занимава със създаването и управлението на зависимостите в дадено приложение (или [услуги |#service]). Контейнерът обикновено има конфигурация, която определя кои класове са зависими от други класове, какви конкретни реализации на зависимости да се използват и как да се създават тези зависимости. След това контейнерът създава тези обекти и ги предоставя на класовете, които се нуждаят от тях. За повече информация вижте [Какво е DI контейнер |dependency-injection:container]? +Dependency Injection контейнер +------------------------------ +Dependency Injection контейнер (също DI контейнер или IoC контейнер) е инструмент, който се грижи за създаването и управлението на зависимостите в приложението (или [сървиси |#Сървис]). Контейнерът обикновено има конфигурация, която дефинира кои класове зависят от други класове, кои конкретни имплементации на зависимости трябва да се използват и как трябва да се създават тези зависимости. След това контейнерът създава тези обекти и ги предоставя на класовете, които ги нуждаят. Повече в главата [Какво е DI контейнер? |dependency-injection:container] -Екраниране .[#toc-escaping] ---------------------------- -Скринингът е преобразуване на символи, които имат специално значение в даден контекст, в други еквивалентни последователности. Пример: Искаме да напишем обърнати запетаи в низ с кавички. Тъй като обърнатите запетаи имат специално значение в контекста на цитирания низ, трябва да се използва друга еквивалентна последователност. Конкретната последователност се определя от правилата на контекста (напр. `\"` в низ с кавички в PHP, `"` в атрибути на HTML и т.н.). +Екраниране +---------- +Екранирането е преобразуване на знаци, които имат специално значение в даден контекст, в други съответстващи последователности. Пример: в низ, ограден с кавички, искаме да запишем кавички. Тъй като кавичките имат специално значение в контекста на низа и простото им записване би се разбрало като край на низа, е необходимо да се запишат с друга съответстваща последователност. Коя точно, определят правилата на контекста. -Филтър .[#toc-filter-formerly-helper] -------------------------------------- -Функция за филтриране. В шаблоните [филтърът |latte:syntax#Filters] е функция, която помага да се променят или форматират данните в изходната форма. Няколко [стандартни филтъра |latte:filters] са предварително дефинирани в шаблоните. +Филтър (преди helper) +--------------------- +В шаблоните под понятието [филтър |latte:syntax#Филтри] обикновено се разбира функция, която помага да се модифицират или преформатират данните в крайната им форма. Шаблоните разполагат с няколко [стандартни филтъра |latte:filters]. -Инвалидизация .[#toc-invalidation] ----------------------------------- -Известие за [фрагмент |#SameSite-Cookie] за повторно визуализиране. В друг контекст също така изчистване на кеша. +Инвалидиране +------------ +Уведомяване на [#snippet], за да се прерисува. В друг смисъл също изтриване на съдържанието на кеша. -JSON .[#toc-json] ------------------ -Формат за обмен на данни, базиран на синтаксиса на JavaScript (подмножество на този синтаксис). Точната спецификация можете да намерите на адрес www.json.org. +JSON +---- +Формат за обмен на данни, базиран на синтаксиса на JavaScript (е негово подмножество). Точната спецификация можете да намерите на страницата www.json.org. -Компонент .[#toc-component] ---------------------------- -Част от приложение за многократна употреба. Той може да бъде визуална част от страница, както е описано в главата [application:components], или терминът може да се отнася и за клас [Component |component-model:] (такъв компонент не е задължително да бъде визуален). +Компонент +--------- +Компонент за многократна употреба в приложението. Може да бъде визуална част от страницата, както е описано в главата [Писане на компоненти |application:components], или под понятието компонент се разбира също клас [Component |component-model:] (такъв компонент не е задължително да бъде визуален). -Контролни знаци .[#toc-control-characters] ------------------------------------------- -Контролните знаци са невидими знаци, които могат да се появят в текста и да причинят някои проблеми в крайна сметка. Можете да използвате [Code Checker |code-checker:], за да ги премахнете масово от файловете, и функцията [Strings::normalize() |utils:strings#normalize], за да ги премахнете от променлива. +Контролни знаци +--------------- +Контролните знаци са невидими знаци, които могат да се срещат в текста и евентуално да причиняват проблеми. За масовото им премахване от файлове можете да използвате [Code Checker|code-checker:], а за премахване от променлива - функцията [Strings::normalize() |utils:strings#normalize]. -Събития .[#toc-events] ----------------------- -Събитието е очаквана ситуация в обекта, при настъпването на която се извикват т.нар. манипулатори, т.е. обратни извиквания, реагиращи на събитието ("образец":https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38). Събитието може да бъде например подаване на формуляр, влизане на потребител и т.н. По този начин събитията са форма на *инверсия на контрола*. +Събития (events) +---------------- +Събитие е очаквана ситуация в обект, която когато настъпи, се извикват т.нар. хендлъри, т.е. callback-ове, реагиращи на събитието ("пример":https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38). Събитие може да бъде например изпращане на форма, влизане на потребител и т.н. Събитията са форма на *Inversion of Control*. -Например, потребител влиза в системата по метода `Nette\Security\User::login()`. Обектът `User` има публична променлива `$onLoggedIn`, която представлява масив, към който всеки може да добави обратно извикване. Веднага щом потребителят влезе в системата, методът `login()` извиква всички обратни извиквания в масива. Името на променливата във формата `onXyz` е конвенция, използвана в Nette. +Например, влизането на потребител се случва в метода `Nette\Security\User::login()`. Обектът `User` има публична променлива `$onLoggedIn`, която е масив, към който всеки може да добави callback. В момента, в който потребителят влезе, методът `login()` извиква всички callback-ове в масива. Името на променливата във формата `onXyz` е конвенция, използвана в цял Nette. -Latte .[#toc-latte] -------------------- -Една от най-иновативните [системи за шаблониране |latte:] в историята. +Latte +----- +Една от най-напредналите [системи за шаблони |latte:]. -Модел .[#toc-model] -------------------- -Моделът представлява данните и функционалната основа на цялото приложение. Тя включва цялата логика на приложението (понякога наричана още "бизнес логика"). Това е **M** от **M**VC или MPV. Всяко действие на потребителя (влизане в системата, поставяне на елемент в количка за пазаруване, промяна на стойност в базата данни) представлява действие на модела. +Model +----- +Моделът е данновата и особено функционалната основа на цялото приложение. Той съдържа цялата логика на приложението (използва се и терминът бизнес логика). Това е **M** от **M**VC или MVP. Всяко действие на потребителя (влизане, добавяне на стока в количката, промяна на стойност в базата данни) представлява действие на модела. -Моделът управлява вътрешното си състояние и предоставя публичен интерфейс. Чрез извикването на този интерфейс можем да приемем или променим състоянието му. Моделът не знае за съществуването на [View |#Вид] или [Controller |#Controller], той е напълно независим от тях. +Моделът управлява своето вътрешно състояние и предлага навън строго определен интерфейс. Чрез извикване на функциите на този интерфейс можем да проверяваме или променяме неговото състояние. Моделът не знае за съществуването на [изглед |#View] или [контролера |#Controller]. -Model-View-Controller (MVC) .[#toc-model-view-controller] ---------------------------------------------------------- -Софтуерна архитектура, възникнала при разработването на приложения с графичен потребителски интерфейс, за да се отдели кодът за управление на потока ([Controller |#Контроллер]) от кода на логиката на приложението ([Model |#Модель]) и от кода за визуализиране на данни ([View |#Вид]). Това прави кода по-ясен, улеснява бъдещото разработване и позволява отделните части да бъдат тествани отделно. +Model-View-Controller +--------------------- +Софтуерна архитектура, възникнала от необходимостта да се отдели кодът за управление ([контролер |#Controller]) от кода на логиката на приложението ([#модел]) и от кода за показване на данни ([изглед |#View]) в приложения с графичен интерфейс. Това изяснява приложението, улеснява бъдещото развитие и позволява тестването на отделните части поотделно. -Model-View-Prenderer (MVP) .[#toc-model-view-presenter] -------------------------------------------------------- -Архитектура, базирана на [Model-View-Controller (MVC) |#Модель-Вид-Контроллер (MVC)]. +Model-View-Presenter +-------------------- +Архитектура, базирана на [#Model-View-Controller]. -Модул .[#toc-module] --------------------- -Един модул в рамката Nette е набор от презентатори и шаблони, а също така компоненти и модели, които служат като данни за презентатора. По този начин тя е определена логическа част от приложението. +Модул +----- +Модулът представлява логическа част от приложението. В типична подредба това е група от presenter-и и шаблони, които решават определена област на функционалност. Модулите се поставят в [отделни директории |application:directory-structure#Презентери и шаблони], като например `Front/`, `Admin/` или `Shop/`. + +Например, електронен магазин можем да разделим на: +- Frontend (`Shop/`) за разглеждане на продукти и пазаруване +- Клиентска секция (`Customer/`) за управление на поръчки +- Администрация (`Admin/`) за оператора + +Технически това са обикновени директории, които обаче благодарение на ясното разделение помагат за мащабирането на приложението. Presenter `Admin:Product:List` така ще бъде физически разположен например в директорията `app/Presentation/Admin/Product/List/` (виж [картографиране на presenter-и |application:directory-structure#Мапиране на презентери]). + -Например, един електронен магазин може да се състои от три модула: -1) Продуктов каталог с количка за пазаруване. -2) Администриране за клиента. -3) Администрация за собственика на магазина. +Namespace +--------- +Именно пространство, част от езика PHP от версия 5.3 и някои други програмни езици, позволяващо използването на класове, които са наречени еднакво в различни библиотеки, без да възникне колизия на имена. Вижте [документацията на PHP |https://www.php.net/manual/en/language.namespaces.rationale.php]. -Пространство от имена .[#toc-namespace] ---------------------------------------- -Пространството от имена е функция на PHP от версия 5.3, както и на някои други езици за програмиране. Това помага да се избегнат колизии в имената (например два класа с едно и също име), когато се използват различни библиотеки. За повече информация вижте [документацията на PHP |https://www.php.net/manual/ru/language.namespaces.rationale.php]. +Presenter +--------- +Presenter е обект, който взема [заявка |api:Nette\Application\Request], преведена от рутера от HTTP заявка, и генерира [отговор |api:Nette\Application\Response]. Отговорът може да бъде HTML страница, изображение, XML документ, файл на диска, JSON, пренасочване или каквото и да измислите. +Обикновено под понятието presenter се разбира наследник на класа [api:Nette\Application\UI\Presenter]. Според входящите заявки той стартира съответните [действия |application:presenters#Жизнен цикъл на презентера] и рендира шаблони. -Презентатор .[#toc-presenter] ------------------------------ -Презентаторът е обект, който приема [заявка, |api:Nette\Application\Request] преведена от маршрутизатор от HTTP заявка, и генерира [отговор |api:Nette\Application\Response]. Отговорът може да бъде HTML страница, картина, XML документ, файл, JSON, пренасочване или каквото друго се сетите. -Представящият обикновено е потомък на класа [api:Nette\Application\UI\Presenter]. При заявките той извършва съответните [действия |application:presenters#Life-Cycle-of-Presenter] и визуализира шаблони. +Рутер +----- +Двупосочен преводач между HTTP заявка / URL и действие на presenter. Двупосочно означава, че от HTTP заявка може да се изведе [#действие на presenter], но също така и обратно - към действието да се генерира съответстващ URL адрес. Повече в главата за [маршрутизиране на URL |application:routing]. -Маршрутизатор .[#toc-router] ----------------------------- -Двупосочен транслатор между HTTP заявка / URL адрес и действие на презентатора. Двупосочността означава, че можете не само да извличате [действие на Presenter |#Действие презентера] от HTTP заявка, но и да генерирате съответния URL адрес за действието. За повече подробности вижте главата за [маршрутизиране на URL |application:routing]. +SameSite cookie +--------------- +SameSite бисквитките предоставят механизъм за разпознаване на това, което е довело до зареждането на страницата. Може да има три стойности: `Lax`, `Strict` и `None` (последният изисква HTTPS). Ако заявката за страницата идва директно от уебсайта или потребителят отвори страницата чрез директно въвеждане в адресната лента или чрез кликване върху отметка, браузърът изпраща на сървъра всички бисквитки (т.е. с флагове `Lax`, `Strict` и `None`). Ако потребителят кликне върху връзка от друг уебсайт, на сървъра се предават бисквитки с флагове `Lax` и `None`. Ако заявката възникне по друг начин, като изпращане на POST форма от друг уебсайт, зареждане в iframe, чрез JavaScript и т.н., се изпращат само бисквитки с флаг `None`. -Бисквитка SameSite .[#toc-samesite-cookie] ------------------------------------------- -Бисквитките SameSite осигуряват механизъм за разпознаване на това, което е довело до зареждането на страницата. Тя може да има три стойности: `Lax`, `Strict` и `None` (последната изисква HTTPS). Ако заявката за страницата идва директно от сайта или потребителят отваря страницата чрез директно въвеждане в адресната лента или щракване върху отметки, браузърът изпраща всички бисквитки на сървъра (т.е. с флагове `Lax`, `Strict` и `None`). Ако потребителят щракне върху сайта чрез връзка от друг сайт, на сървъра се предават бисквитки с флагове `Lax` и `None`. Ако заявката е направена по друг начин, например чрез изпращане на POST формуляр от друг сайт, зареждане в iframe, използване на JavaScript и т.н., се изпращат само бисквитки с флаг `None`. +Сървис +------ +В контекста на Dependency Injection като сървис се означава обект, който се създава и управлява от DI контейнера. Сървисът може лесно да бъде заменен с друга имплементация, например за целите на тестване или за промяна на поведението на приложението, без да е необходимо да се променя кодът, който използва сървиса. -Услуга .[#toc-service] ----------------------- -В контекста на Dependency Injection (инжектиране на зависимости) услугата се отнася до обект, който се създава и управлява от DI контейнер. Една услуга може лесно да бъде заменена с друга реализация, например за целите на тестването или за промяна на поведението на приложението, без да се налага да се променя кодът, който използва услугата. +Snippet +------- +Фрагмент, част от страницата, която може да се прерисува самостоятелно по време на AJAX заявка. -Извадка .[#toc-snippet] ------------------------ -Извадка от страница, която може да се визуализира отделно по време на [AJAX |#AJAX] заявка. +View +---- +View, т.е. изглед, е слоят на приложението, който отговаря за показването на резултата от заявката. Обикновено използва система за шаблони и знае как да покаже даден компонент или резултат, получен от модела. -Изглед .[#toc-view] -------------------- -Изгледът е слой от приложението, който отговаря за визуализирането на резултатите от заявките. Обикновено той използва система за шаблониране и знае как да визуализира своите компоненти или резултатите, взети от модела. diff --git a/nette/bg/installation.texy b/nette/bg/installation.texy index a669e5b3ab..1612caebf9 100644 --- a/nette/bg/installation.texy +++ b/nette/bg/installation.texy @@ -2,66 +2,66 @@ ******************** .[perex] -Искате ли да използвате предимствата на Nette във вашия съществуващ проект или планирате да създадете нов проект, базиран на Nette? Това ръководство ще ви преведе стъпка по стъпка през инсталацията. +Искате ли да използвате предимствата на Nette в съществуващ проект или ще създавате нов проект, базиран на Nette? Това ръководство ще ви преведе през инсталацията стъпка по стъпка. -Как да добавите Nette към вашия проект .[#toc-how-to-add-nette-to-your-project] -------------------------------------------------------------------------------- +Как да добавите Nette към вашия проект +-------------------------------------- -Nette предлага колекция от полезни и усъвършенствани пакети (библиотеки) за PHP. За да ги включите във вашия проект, следвайте следните стъпки: +Nette предлага колекция от полезни и усъвършенствани пакети (библиотеки) за PHP. За да ги включите във вашия проект, следвайте тези стъпки: -1) **Настройте [Composer |best-practices:composer]:** Този инструмент е от съществено значение за лесното инсталиране, актуализиране и управление на библиотеките, необходими за вашия проект. +1) **Подгответе [Composer|best-practices:composer]:** Този инструмент е необходим за лесно инсталиране, актуализиране и управление на библиотеките, необходими за вашия проект. -2) **Избор на [пакет |www:packages]:** Да речем, че трябва да се ориентирате във файловата система, което [Finder |utils:finder] от пакета `nette/utils` прави отлично. Можете да намерите името на пакета в дясната колона на неговата документация. +2) **Изберете [пакет|www:packages]:** Да предположим, че трябва да навигирате във файловата система, което [Finder|utils:finder] от пакета `nette/utils` прави чудесно. Можете да видите името на пакета в дясната колона на неговата документация. -3) **Инсталирайте пакета:** Изпълнете тази команда в главната директория на вашия проект: +3) **Инсталирайте пакета:** Изпълнете тази команда в основната директория на вашия проект: ```shell composer require nette/utils ``` -Предпочитате ли графичен интерфейс? Разгледайте [ръководството за |https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] инсталиране на пакети в средата PhpStrom. +Предпочитате графичен интерфейс? Разгледайте [ръководството|https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] за инсталиране на пакети в средата на PhpStorm. -Как да започнете нов проект с Nette .[#toc-how-to-start-a-new-project-with-nette] ---------------------------------------------------------------------------------- +Как да стартирате нов проект с Nette +------------------------------------ -Ако искате да създадете изцяло нов проект в платформата Nette, препоръчваме ви да използвате предварително зададения скелет на [уеб проект |https://github.com/nette/web-project]: +Ако искате да създадете изцяло нов проект на платформата Nette, препоръчваме да използвате предварително конфигурирания скелет [Web Project|https://github.com/nette/web-project]: -1) **Настройте [Composer |best-practices:composer].** +1) **Подгответе [Composer|best-practices:composer].** -2) **Отворете командния ред** и отидете в главната директория на вашия уеб сървър, например `/etc/var/www`, `C:/xampp/htdocs`, `/Library/WebServer/Documents`. +2) **Отворете командния ред** и навигирайте до основната директория на вашия уеб сървър, напр. `/etc/var/www`, `C:/xampp/htdocs`, `/Library/WebServer/Documents`. -3) **Създайте проекта**, като използвате тази команда: +3) **Създайте проекта** с помощта на тази команда: ```shell -composer create-project nette/web-project PROJECT_NAME +composer create-project nette/web-project NAZEV_PROJEKTU ``` -4) **Не използвате Composer?** Просто изтеглете [уеб проекта в ZIP формат |https://github.com/nette/web-project/archive/preloaded.zip] и го разархивирайте. Но повярвайте ни, Composer си заслужава! +4) **Не използвате Composer?** Просто изтеглете [Web Project в ZIP формат|https://github.com/nette/web-project/archive/preloaded.zip] и го разархивирайте. Но повярвайте ми, Composer си заслужава! -5) **Настройване на разрешенията:** В системите MacOS или Linux задайте [разрешения за запис на |nette:troubleshooting#Setting directory permissions] директории. +5) **Настройка на правата:** В системи macOS или Linux задайте [права за запис |nette:troubleshooting#Настройка на правата на директориите] за директориите. -6) **Отваряне на проекта в браузър:** Въведете URL адреса `http://localhost/PROJECT_NAME/www/`. Ще видите началната страница на скелета: +6) **Отваряне на проекта в браузъра:** Въведете URL адреса `http://localhost/NAZEV_PROJEKTU/www/` и ще видите началната страница на скелета: -[* qs-welcome.webp .{url: http://localhost/PROJECT_NAME/www/} *] +[* qs-welcome.webp .{url: http://localhost/NAZEV_PROJEKTU/www/} *] -Поздравления! Вашият уебсайт вече е готов за разработка. Чувствайте се свободни да премахнете шаблона за посрещане и да започнете да изграждате своето приложение. +Поздравления! Вашият уебсайт вече е готов за разработка. Можете да премахнете шаблона за приветствие и да започнете да създавате своето приложение. -Едно от предимствата на Nette е, че проектът работи веднага, без да е необходимо да се конфигурира. Въпреки това, ако се сблъскате с някакви проблеми, помислете дали да не разгледате [общите решения на проблемите |nette:troubleshooting#nette-is-not-working-white-page-is-displayed]. +Едно от предимствата на Nette е, че проектът работи веднага, без да е необходима конфигурация. Ако обаче срещнете проблеми, опитайте да разгледате [решения на често срещани проблеми |nette:troubleshooting#Nette не работи показва се бяла страница]. .[note] -Ако започвате работа с Nette, препоръчваме ви да продължите с [урока |quickstart:]" [Създаване на първото ви приложение |quickstart:]". +Ако започвате с Nette, препоръчваме да продължите с [урока "Писане на първото приложение"|quickstart:]. -Инструменти и препоръки .[#toc-tools-and-recommendations] ---------------------------------------------------------- +Инструменти и препоръки +----------------------- За ефективна работа с Nette препоръчваме следните инструменти: -- [Висококачествен IDE с плъгини за Nette |best-practices:editors-and-tools] +- [Качествено IDE с добавки за Nette|best-practices:editors-and-tools] - Система за контрол на версиите Git -- [Composer |best-practices:composer] +- [Composer|best-practices:composer] {{leftbar: www:@menu-common}} diff --git a/nette/bg/introduction-to-object-oriented-programming.texy b/nette/bg/introduction-to-object-oriented-programming.texy new file mode 100644 index 0000000000..a5880d72cb --- /dev/null +++ b/nette/bg/introduction-to-object-oriented-programming.texy @@ -0,0 +1,841 @@ +Въведение в обектно-ориентираното програмиране +********************************************** + +.[perex] +Терминът "ООП" означава обектно-ориентирано програмиране, което е начин за организиране и структуриране на кода. ООП ни позволява да разглеждаме програмата като набор от обекти, които комуникират помежду си, вместо като последователност от команди и функции. + +В ООП "обектът" е единица, която съдържа данни и функции, които работят с тези данни. Обектите се създават по "класове", които можем да разглеждаме като проекти или шаблони за обекти. Когато имаме клас, можем да създадем негова "инстанция", което е конкретен обект, създаден по този клас. + +Нека покажем как можем да създадем прост клас в PHP. При дефинирането на клас използваме ключовата дума `class`, последвана от името на класа и след това фигурни скоби, които обхващат функциите (наричани "методи") и променливите на класа (наричани "свойства" или на английски "property"): + +```php +class Кола +{ + function клаксон() + { + echo 'Bip bip!'; + } +} +``` + +В този пример създадохме клас с име `Кола` с една функция (или "метод"), наречена `клаксон`. + +Всеки клас трябва да решава само една основна задача. Ако класът прави твърде много неща, може да е подходящо да го разделим на по-малки, специализирани класове. + +Класовете обикновено се съхраняват в отделни файлове, за да бъде кодът организиран и лесен за навигация. Името на файла трябва да съответства на името на класа, така че за клас `Кола` името на файла би било `Кола.php`. + +При именуването на класове е добре да се придържаме към конвенцията "PascalCase", което означава, че всяка дума в името започва с главна буква и няма долни черти или други разделители между тях. Методите и свойствата използват конвенцията "camelCase", което означава, че започват с малка буква. + +Някои методи в PHP имат специални задачи и са маркирани с префикс `__` (две долни черти). Един от най-важните специални методи е "конструкторът", който е маркиран като `__construct`. Конструкторът е метод, който се извиква автоматично, когато създавате нова инстанция на класа. + +Конструкторът често се използва за задаване на началното състояние на обекта. Например, когато създавате обект, представляващ човек, можете да използвате конструктора, за да зададете неговата възраст, име или други свойства. + +Нека покажем как да използваме конструктор в PHP: + +```php +class Човек +{ + private $възраст; + + function __construct($възраст) + { + $this->възраст = $възраст; + } + + function наКолкоСиГодини() + { + return $this->възраст; + } +} + +$човек = new Човек(25); +echo $човек->наКолкоСиГодини(); // Извежда: 25 +``` + +В този пример класът `Човек` има свойство (променлива) `$възраст` и конструктор, който задава това свойство. Методът `наКолкоСиГодини()` след това позволява достъп до възрастта на човека. + +Псевдопроменливата `$this` се използва вътре в класа за достъп до свойствата и методите на обекта. + +Ключовата дума `new` се използва за създаване на нова инстанция на класа. В горния пример създадохме нов човек на възраст 25 години. + +Можете също да зададете стойности по подразбиране за параметрите на конструктора, ако те не са посочени при създаването на обекта. Например: + +```php +class Човек +{ + private $възраст; + + function __construct($възраст = 20) + { + $this->възраст = $възраст; + } + + function наКолкоСиГодини() + { + return $this->възраст; + } +} + +$човек = new Човек; // ако не се предават аргументи, скобите могат да бъдат пропуснати +echo $човек->наКолкоСиГодини(); // Извежда: 20 +``` + +В този пример, ако не посочите възраст при създаването на обект `Човек`, ще бъде използвана стойността по подразбиране 20. + +Приятно е, че дефиницията на свойство с неговата инициализация чрез конструктора може да бъде съкратена и опростена по следния начин: + +```php +class Човек +{ + function __construct( + private $възраст = 20, + ) { + } +} +``` + +За пълнота, освен конструктори, обектите могат да имат и деструктори (метод `__destruct`), които се извикват преди обектът да бъде освободен от паметта. + + +Именни пространства +------------------- + +Именните пространства (или "namespaces" на английски) ни позволяват да организираме и групираме свързани класове, функции и константи, като същевременно избягваме конфликти в имената. Можете да си ги представите като папки в компютъра, където всяка папка съдържа файлове, принадлежащи към определен проект или тема. + +Именните пространства са особено полезни в по-големи проекти или когато използвате библиотеки от трети страни, където могат да възникнат конфликти в имената на класовете. + +Представете си, че имате клас с име `Кола` във вашия проект и искате да го поставите в именно пространство, наречено `Транспорт`. Ще го направите по следния начин: + +```php +namespace Транспорт; + +class Кола +{ + function клаксон() + { + echo 'Bip bip!'; + } +} +``` + +Ако искате да използвате класа `Кола` в друг файл, трябва да посочите от кое именно пространство произхожда класът: + +```php +$кола = new Транспорт\Кола; +``` + +За опростяване можете да посочите в началото на файла кой клас от даденото именно пространство искате да използвате, което позволява създаването на инстанции без необходимост от указване на целия път: + +```php +use Транспорт\Кола; + +$кола = new Кола; +``` + + +Наследяване +----------- + +Наследяването е инструмент на обектно-ориентираното програмиране, който позволява създаването на нови класове въз основа на вече съществуващи класове, като се наследяват техните свойства и методи и се разширяват или предефинират според нуждите. Наследяването позволява да се осигури повторна използваемост на кода и йерархия на класовете. + +Казано по-просто, ако имаме един клас и искаме да създадем друг, производен от него, но с няколко промени, можем да "наследим" новия клас от оригиналния клас. + +В PHP наследяването се реализира с помощта на ключовата дума `extends`. + +Нашият клас `Човек` съхранява информация за възрастта. Можем да имаме друг клас `Студент`, който разширява `Човек` и добавя информация за специалността. + +Нека разгледаме пример: + +```php +class Човек +{ + private $възраст; + + function __construct($възраст) + { + $this->възраст = $възраст; + } + + function покажиИнформация() + { + echo "Възраст: {$this->възраст} години\n"; + } +} + +class Студент extends Човек +{ + private $специалност; + + function __construct($възраст, $специалност) + { + parent::__construct($възраст); + $this->специалност = $специалност; + } + + function покажиИнформация() + { + parent::покажиИнформация(); + echo "Специалност: {$this->специалност} \n"; + } +} + +$студент = new Студент(20, 'Информатика'); +$студент->покажиИнформация(); +``` + +Как работи този код? + +- Използвахме ключовата дума `extends`, за да разширим класа `Човек`, което означава, че класът `Студент` наследява всички методи и свойства от `Човек`. + +- Ключовата дума `parent::` ни позволява да извикваме методи от родителския клас. В този случай извикахме конструктора от класа `Човек`, преди да добавим собствена функционалност към класа `Студент`. И по същия начин и метода `покажиИнформация()` на предка, преди да изведем информацията за студента. + +Наследяването е предназначено за ситуации, в които съществува връзка "е" между класовете. Например, `Студент` е `Човек`. Котката е животно. Дава ни възможност в случаите, когато в кода очакваме един обект (напр. "Човек"), да използваме вместо него наследен обект (напр. "Студент"). + +Важно е да се осъзнае, че основната цел на наследяването **не е** да се предотврати дублирането на код. Напротив, неправилното използване на наследяването може да доведе до сложен и трудно поддържаем код. Ако връзката "е" между класовете не съществува, трябва да обмислим композиция вместо наследяване. + +Забележете, че методите `покажиИнформация()` в класовете `Човек` и `Студент` извеждат малко по-различна информация. И можем да добавим други класове (например `Служител`), които ще предоставят други имплементации на този метод. Способността на обекти от различни класове да реагират на един и същ метод по различни начини се нарича полиморфизъм: + +```php +$хора = [ + new Човек(30), + new Студент(20, 'Информатика'), + new Служител(45, 'Директор'), +]; + +foreach ($хора as $човек) { + $човек->покажиИнформация(); +} +``` + + +Композиция +---------- + +Композицията е техника, при която вместо да наследяваме свойствата и методите на друг клас, просто използваме негова инстанция в нашия клас. Това ни позволява да комбинираме функционалности и свойства на няколко класа без необходимост от създаване на сложни наследствени структури. + +Нека разгледаме пример. Имаме клас `Двигател` и клас `Кола`. Вместо да казваме "Колата е Двигател", казваме "Колата има Двигател", което е типична връзка на композиция. + +```php +class Двигател +{ + function стартирай() + { + echo 'Двигателят работи.'; + } +} + +class Кола +{ + private $двигател; + + function __construct() + { + $this->двигател = new Двигател; + } + + function start() + { + $this->двигател->стартирай(); + echo 'Колата е готова за път!'; + } +} + +$кола = new Кола; +$кола->start(); +``` + +Тук `Кола` няма всички свойства и методи на `Двигател`, но има достъп до него чрез свойството `$двигател`. + +Предимството на композицията е по-голямата гъвкавост в дизайна и по-добрата възможност за промени в бъдеще. + + +Видимост +-------- + +В PHP можете да дефинирате "видимост" за свойствата, методите и константите на класа. Видимостта определя откъде можете да достъпвате тези елементи. + +1. **Public:** Ако елемент е маркиран като `public`, това означава, че можете да го достъпвате отвсякъде, дори извън класа. + +2. **Protected:** Елемент с маркировка `protected` е достъпен само в рамките на дадения клас и всички негови наследници (класове, които наследяват от този клас). + +3. **Private:** Ако елемент е `private`, можете да го достъпвате само отвътре на класа, в който е дефиниран. + +Ако не посочите видимост, PHP автоматично я задава на `public`. + +Нека разгледаме примерен код: + +```php +class ПримерЗаВидимост +{ + public $публичноСвойство = 'Публично'; + protected $защитеноСвойство = 'Защитено'; + private $частноСвойство = 'Частно'; + + public function покажиСвойства() + { + echo $this->публичноСвойство; // Работи + echo $this->защитеноСвойство; // Работи + echo $this->частноСвойство; // Работи + } +} + +$обект = new ПримерЗаВидимост; +$обект->покажиСвойства(); +echo $обект->публичноСвойство; // Работи +// echo $обект->защитеноСвойство; // Ще хвърли грешка +// echo $обект->частноСвойство; // Ще хвърли грешка +``` + +Продължаваме с наследяването на класа: + +```php +class НаследникНаКлас extends ПримерЗаВидимост +{ + public function покажиСвойства() + { + echo $this->публичноСвойство; // Работи + echo $this->защитеноСвойство; // Работи + // echo $this->частноСвойство; // Ще хвърли грешка + } +} +``` + +В този случай методът `покажиСвойства()` в класа `НаследникНаКлас` може да достъпва публичните и защитените свойства, но не може да достъпва частните свойства на родителския клас. + +Данните и методите трябва да бъдат колкото е възможно по-скрити и достъпни само чрез дефиниран интерфейс. Това ви позволява да променяте вътрешната имплементация на класа, без да засягате останалата част от кода. + + +Ключова дума `final` +-------------------- + +В PHP можем да използваме ключовата дума `final`, ако искаме да предотвратим наследяването или презаписването на клас, метод или константа. Когато маркираме клас като `final`, той не може да бъде разширяван. Когато маркираме метод като `final`, той не може да бъде презаписан в наследствен клас. + +Знанието, че определен клас или метод няма да бъде допълнително променян, ни позволява по-лесно да правим промени, без да се притесняваме за възможни конфликти. Например, можем да добавим нов метод, без да се притесняваме, че някой негов наследник вече има метод със същото име и ще възникне колизия. Или можем да променим параметрите на метода, тъй като отново няма опасност да причиним несъответствие с презаписания метод в наследника. + +```php +final class ФиналенКлас +{ +} + +// Следният код ще предизвика грешка, тъй като не можем да наследим от финален клас. +class НаследникНаФиналенКлас extends ФиналенКлас +{ +} +``` + +В този пример опитът за наследяване от финалния клас `ФиналенКлас` ще предизвика грешка. + + +Статични свойства и методи +-------------------------- + +Когато в PHP говорим за "статични" елементи на класа, имаме предвид методи и свойства, които принадлежат на самия клас, а не на конкретна инстанция на този клас. Това означава, че не е необходимо да създавате инстанция на класа, за да имате достъп до тях. Вместо това ги извиквате или достъпвате директно чрез името на класа. + +Имайте предвид, че тъй като статичните елементи принадлежат на класа, а не на неговите инстанции, не можете да използвате псевдопроменливата `$this` вътре в статичните методи. + +Използването на статични свойства води до [неясен код, пълен с клопки|dependency-injection:global-state], затова никога не трябва да ги използвате и тук няма да показваме пример за употреба. За разлика от това, статичните методи са полезни. Пример за употреба: + +```php +class Калкулатор +{ + public static function събиране($a, $b) + { + return $a + $b; + } + + public static function изваждане($a, $b) + { + return $a - $b; + } +} + +// Използване на статичен метод без създаване на инстанция на класа +echo Калкулатор::събиране(5, 3); // Резултат: 8 +echo Калкулатор::изваждане(5, 3); // Резултат: 2 +``` + +В този пример създадохме клас `Калкулатор` с два статични метода. Тези методи можем да извикваме директно без създаване на инстанция на класа с помощта на оператора `::`. Статичните методи са особено полезни за операции, които не зависят от състоянието на конкретна инстанция на класа. + + +Константи на класа +------------------ + +В рамките на класовете имаме възможност да дефинираме константи. Константите са стойности, които никога не се променят по време на изпълнение на програмата. За разлика от променливите, стойността на константата остава винаги същата. + +```php +class Кола +{ + public const БройКолела = 4; + + public function покажиБройКолела(): int + { + echo self::БройКолела; + } +} + +echo Кола::БройКолела; // Изход: 4 +``` + +В този пример имаме клас `Кола` с константа `БройКолела`. Когато искаме да достъпим константата вътре в класа, можем да използваме ключовата дума `self` вместо името на класа. + + +Обектни интерфейси +------------------ + +Обектните интерфейси функционират като "договори" за класовете. Ако клас трябва да имплементира обектен интерфейс, той трябва да съдържа всички методи, които този интерфейс дефинира. Това е чудесен начин да се гарантира, че определени класове спазват същия "договор" или структура. + +В PHP интерфейсът се дефинира с ключовата дума `interface`. Всички методи, дефинирани в интерфейса, са публични (`public`). Когато клас имплементира интерфейс, той използва ключовата дума `implements`. + +```php +interface Животно +{ + function издайЗвук(); +} + +class Котка implements Животно +{ + public function издайЗвук() + { + echo 'Мяу'; + } +} + +$котка = new Котка; +$котка->издайЗвук(); +``` + +Ако клас имплементира интерфейс, но в него не са дефинирани всички очаквани методи, PHP ще хвърли грешка. + +Класът може да имплементира няколко интерфейса едновременно, което е разлика спрямо наследяването, където класът може да наследява само от един клас: + +```php +interface Пазач +{ + function пазиКъщата(); +} + +class Куче implements Животно, Пазач +{ + public function издайЗвук() + { + echo 'Бау'; + } + + public function пазиКъщата() + { + echo 'Кучето бдително пази къщата'; + } +} +``` + + +Абстрактни класове +------------------ + +Абстрактните класове служат като основни шаблони за други класове, но не можете да създавате техни инстанции директно. Те съдържат комбинация от завършени методи и абстрактни методи, които нямат дефинирано съдържание. Класовете, които наследяват от абстрактни класове, трябва да предоставят дефиниции за всички абстрактни методи от предка. + +За дефиниране на абстрактен клас използваме ключовата дума `abstract`. + +```php +abstract class АбстрактенКлас +{ + public function обикновенМетод() + { + echo 'Това е обикновен метод'; + } + + abstract public function абстрактенМетод(); +} + +class Наследник extends АбстрактенКлас +{ + public function абстрактенМетод() + { + echo 'Това е имплементация на абстрактния метод'; + } +} + +$instance = new Наследник; +$instance->обикновенМетод(); +$instance->абстрактенМетод(); +``` + +В този пример имаме абстрактен клас с един обикновен и един абстрактен метод. След това имаме клас `Наследник`, който наследява от `АбстрактенКлас` и предоставя имплементация за абстрактния метод. + +Как всъщност се различават интерфейсите и абстрактните класове? Абстрактните класове могат да съдържат както абстрактни, така и конкретни методи, докато интерфейсите само дефинират какви методи трябва да имплементира класът, но не предоставят никаква имплементация. Класът може да наследява само от един абстрактен клас, но може да имплементира произволен брой интерфейси. + + +Проверка на типове +------------------ + +В програмирането е много важно да сме сигурни, че данните, с които работим, са от правилния тип. В PHP имаме инструменти, които ни осигуряват това. Проверката дали данните имат правилния тип се нарича "проверка на типове". + +Типовете, на които можем да попаднем в PHP: + +1. **Основни типове**: Включват `int` (цели числа), `float` (десетични числа), `bool` (булеви стойности), `string` (низове), `array` (масиви) и `null`. +2. **Класове**: Ако искаме стойността да бъде инстанция на специфичен клас. +3. **Интерфейси**: Дефинира набор от методи, които класът трябва да имплементира. Стойност, която отговаря на интерфейса, трябва да има тези методи. +4. **Смесени типове**: Можем да определим, че променливата може да има няколко позволени типа. +5. **Void**: Този специален тип означава, че функцията или методът не връща никаква стойност. + +Нека покажем как да променим кода, за да включим типове: + +```php +class Човек +{ + private int $възраст; + + public function __construct(int $възраст) + { + $this->възраст = $възраст; + } + + public function покажиВъзраст(): void + { + echo "Този човек е на {$this->възраст} години."; + } +} + +/** + * Функция, която приема обект от клас Човек и извежда възрастта на човека. + */ +function покажиВъзрастНаЧовек(Човек $човек): void +{ + $човек->покажиВъзраст(); +} +``` + +По този начин гарантирахме, че нашият код очаква и работи с данни от правилния тип, което ни помага да предотвратим потенциални грешки. + +Някои типове не могат да бъдат записани директно в PHP. В такъв случай те се посочват в phpDoc коментар, което е стандартен формат за документиране на PHP код, започващ с `/**` и завършващ с `*/`. Позволява добавянето на описания на класове, методи и т.н. А също и указване на сложни типове с помощта на т.нар. анотации `@var`, `@param` и `@return`. Тези типове след това се използват от инструменти за статичен анализ на кода, но самото PHP не ги контролира. + +```php +class Списък +{ + /** @var array<Човек> записът казва, че това е масив от обекти Човек */ + private array $хора = []; + + public function добавиЧовек(Човек $човек): void + { + $this->хора[] = $човек; + } +} +``` + + +Сравнение и идентичност +----------------------- + +В PHP можете да сравнявате обекти по два начина: + +1. Сравнение на стойности `==`: Проверява дали обектите са от един и същи клас и имат еднакви стойности в своите свойства. +2. Идентичност `===`: Проверява дали става въпрос за една и съща инстанция на обекта. + +```php +class Кола +{ + public string $марка; + + public function __construct(string $марка) + { + $this->марка = $марка; + } +} + +$кола1 = new Кола('Skoda'); +$кола2 = new Кола('Skoda'); +$кола3 = $кола1; + +var_dump($кола1 == $кола2); // true, защото имат еднаква стойност +var_dump($кола1 === $кола2); // false, защото не са една и съща инстанция +var_dump($кола1 === $кола3); // true, защото $кола3 е същата инстанция като $кола1 +``` + + +Оператор `instanceof` +--------------------- + +Операторът `instanceof` позволява да се установи дали даден обект е инстанция на определен клас, наследник на този клас, или дали имплементира определен интерфейс. + +Представете си, че имаме клас `Човек` и друг клас `Студент`, който е наследник на класа `Човек`: + +```php +class Човек +{ + private int $възраст; + + public function __construct(int $възраст) + { + $this->възраст = $възраст; + } +} + +class Студент extends Човек +{ + private string $специалност; + + public function __construct(int $възраст, string $специалност) + { + parent::__construct($възраст); + $this->специалност = $специалност; + } +} + +$студент = new Студент(20, 'Информатика'); + +// Проверка дали $студент е инстанция на клас Студент +var_dump($студент instanceof Студент); // Изход: bool(true) + +// Проверка дали $студент е инстанция на клас Човек (тъй като Студент е наследник на Човек) +var_dump($студент instanceof Човек); // Изход: bool(true) +``` + +От изходите е видно, че обектът `$студент` се счита едновременно за инстанция на двата класа - `Студент` и `Човек`. + + +Fluent Interfaces +----------------- + +"Плавният интерфейс" (на английски "Fluent Interface") е техника в ООП, която позволява верижно извикване на методи в едно извикване. Това често опростява и изяснява кода. + +Ключовият елемент на плавния интерфейс е, че всеки метод във веригата връща референция към текущия обект. Това постигаме, като в края на метода използваме `return $this;`. Този стил на програмиране често се свързва с методи, наречени "setters", които задават стойностите на свойствата на обекта. + +Ще покажем как може да изглежда плавен интерфейс на примера с изпращане на имейли: + +```php +public function sendMessage() +{ + $email = new Email; + $email->setFrom('sender@example.com') + ->setRecipient('admin@example.com') + ->setMessage('Здравейте, това е съобщение.') + ->send(); +} +``` + +В този пример методите `setFrom()`, `setRecipient()` и `setMessage()` служат за задаване на съответните стойности (подател, получател, съдържание на съобщението). След задаване на всяка от тези стойности, методите ни връщат текущия обект (`$email`), което ни позволява да верижим следващ метод след него. Накрая извикваме метода `send()`, който действително изпраща имейла. + +Благодарение на плавните интерфейси можем да пишем код, който е интуитивен и лесно четим. + + +Копиране с `clone` +------------------ + +В PHP можем да създадем копие на обект с помощта на оператора `clone`. По този начин получаваме нова инстанция с идентично съдържание. + +Ако при копиране на обект трябва да променим някои от неговите свойства, можем да дефинираме в класа специален метод `__clone()`. Този метод се извиква автоматично, когато обектът се клонира. + +```php +class Овца +{ + public string $име; + + public function __construct(string $име) + { + $this->име = $име; + } + + public function __clone() + { + $this->име = 'Клонинг ' . $this->име; + } +} + +$original = new Овца('Dolly'); +echo $original->име . "\n"; // Извежда: Dolly + +$клонинг = clone $original; +echo $клонинг->име . "\n"; // Извежда: Клонинг Dolly +``` + +В този пример имаме клас `Овца` с едно свойство `$име`. Когато клонираме инстанция на този клас, методът `__clone()` се грижи името на клонираната овца да получи префикс "Клонинг". + + +Traits +------ + +Traits в PHP са инструмент, който позволява споделянето на методи, свойства и константи между класовете и предотвратява дублирането на код. Можете да си ги представите като механизъм "копирай и постави" (Ctrl-C и Ctrl-V), при който съдържанието на trait се "вмъква" в класовете. Това ви позволява да използвате повторно код без необходимост от създаване на сложни йерархии на класове. + +Нека покажем прост пример как да използвате traits в PHP: + +```php +trait Клаксон +{ + public function свирни() + { + echo 'Bip bip!'; + } +} + +class Кола +{ + use Клаксон; +} + +class Камион +{ + use Клаксон; +} + +$кола = new Кола; +$кола->свирни(); // Извежда 'Bip bip!' + +$камион = new Камион; +$камион->свирни(); // Също извежда 'Bip bip!' +``` + +В този пример имаме trait, наречен `Клаксон`, който съдържа един метод `свирни()`. След това имаме два класа: `Кола` и `Камион`, които и двата използват trait `Клаксон`. Благодарение на това и двата класа "имат" метода `свирни()`, и можем да го извикваме на обекти от двата класа. + +Traits ви позволяват лесно и ефективно да споделяте код между класовете. При това те не влизат в наследствената йерархия, т.е. `$кола instanceof Клаксон` ще върне `false`. + + +Изключения +---------- + +Изключенията в ООП ни позволяват елегантно да обработваме грешки и неочаквани ситуации в нашия код. Те са обекти, които носят информация за грешката или необичайната ситуация. + +В PHP имаме вграден клас `Exception`, който служи като основа за всички изключения. Той има няколко метода, които ни позволяват да получим повече информация за изключението, като съобщение за грешка, файл и ред, където е възникнала грешката, и т.н. + +Когато в кода възникне грешка, можем да "хвърлим" изключение с помощта на ключовата дума `throw`. + +```php +function деление(float $a, float $b): float +{ + if ($b === 0.0) { // Сравнение с float изисква внимание + throw new Exception('Делене на нула!'); + } + return $a / $b; +} +``` + +Когато функцията `деление()` получи нула като втори аргумент, тя хвърля изключение със съобщение за грешка `'Делене на нула!'`. За да предотвратим срив на програмата при хвърляне на изключение, го улавяме в блок `try/catch`: + +```php +try { + echo деление(10, 0); +} catch (Exception $e) { + echo 'Изключението е уловено: '. $e->getMessage(); +} +``` + +Кодът, който може да хвърли изключение, е обвит в блок `try`. Ако бъде хвърлено изключение, изпълнението на кода се премества в блок `catch`, където можем да обработим изключението (напр. да изведем съобщение за грешка). + +След блоковете `try` и `catch` можем да добавим незадължителен блок `finally`, който се изпълнява винаги, независимо дали е било хвърлено изключение или не (дори и в случай, че в блок `try` или `catch` използваме команда `return`, `break` или `continue`): + +```php +try { + echo деление(10, 0); +} catch (Exception $e) { + echo 'Изключението е уловено: '. $e->getMessage(); +} finally { + // Код, който винаги се изпълнява, независимо дали е хвърлено изключение или не +} +``` + +Можем също да създадем собствени класове (йерархия) на изключения, които наследяват от класа Exception. Като пример си представете просто банково приложение, което позволява извършване на депозити и тегления: + +```php +class БанковоИзключение extends Exception {} +class ИзключениеЗаНедостатъчнаНаличност extends БанковоИзключение {} +class ИзключениеЗаНадвишенЛимит extends БанковоИзключение {} + +class БанковаСметка +{ + private int $баланс = 0; + private int $дневенЛимит = 1000; + + public function депозит(int $сума): int + { + $this->баланс += $сума; + return $this->баланс; + } + + public function теглене(int $сума): int + { + if ($сума > $this->баланс) { + throw new ИзключениеЗаНедостатъчнаНаличност('Няма достатъчно средства по сметката.'); + } + + if ($сума > $this->дневенЛимит) { + throw new ИзключениеЗаНадвишенЛимит('Дневният лимит за теглене е надвишен.'); + } + + $this->баланс -= $сума; + return $this->баланс; + } +} +``` + +За един блок `try` могат да се посочат няколко блока `catch`, ако очаквате различни типове изключения. + +```php +$сметка = new БанковаСметка; +$сметка->депозит(500); + +try { + $сметка->теглене(1500); +} catch (ИзключениеЗаНадвишенЛимит $e) { + echo $e->getMessage(); +} catch (ИзключениеЗаНедостатъчнаНаличност $e) { + echo $e->getMessage(); +} catch (БанковоИзключение $e) { + echo 'Възникна грешка при извършване на операцията.'; +} +``` + +В този пример е важно да се обърне внимание на реда на блоковете `catch`. Тъй като всички изключения наследяват от `БанковоИзключение`, ако този блок беше първи, в него щяха да се уловят всички изключения, без кодът да достигне до следващите `catch` блокове. Затова е важно по-специфичните изключения (т.е. тези, които наследяват от други) да бъдат в блок `catch` по-нагоре в реда от техните родителски изключения. + + +Итерация +-------- + +В PHP можете да обхождате обекти с помощта на цикъл `foreach`, подобно на обхождането на масиви. За да работи това, обектът трябва да имплементира специален интерфейс. + +Първата възможност е да се имплементира интерфейсът `Iterator`, който има методи `current()` връщащ текущата стойност, `key()` връщащ ключа, `next()` преместващ към следващата стойност, `rewind()` преместващ към началото и `valid()` установяващ дали все още не сме в края. + +Втората възможност е да се имплементира интерфейсът `IteratorAggregate`, който има само един метод `getIterator()`. Той или връща заместващ обект, който ще осигурява обхождането, или може да представлява генератор, което е специална функция, в която се използва `yield` за последователно връщане на ключове и стойности: + +```php +class Човек +{ + public function __construct( + public int $възраст, + ) { + } +} + +class Списък implements IteratorAggregate +{ + private array $хора = []; + + public function добавиЧовек(Човек $човек): void + { + $this->хора[] = $човек; + } + + public function getIterator(): Generator + { + foreach ($this->хора as $човек) { + yield $човек; + } + } +} + +$списък = new Списък; +$списък->добавиЧовек(new Човек(30)); +$списък->добавиЧовек(new Човек(25)); + +foreach ($списък as $човек) { + echo "Възраст: {$човек->възраст} години \n"; +} +``` + + +Добри практики +-------------- + +След като сте усвоили основните принципи на обектно-ориентираното програмиране, е важно да се съсредоточите върху добрите практики в ООП. Те ще ви помогнат да пишете код, който е не само функционален, но и четим, разбираем и лесно поддържаем. + +1) **Разделяне на отговорностите (Separation of Concerns)**: Всеки клас трябва да има ясно дефинирана отговорност и трябва да решава само една основна задача. Ако класът прави твърде много неща, може да е подходящо да го разделим на по-малки, специализирани класове. +2) **Капсулиране (Encapsulation)**: Данните и методите трябва да бъдат колкото е възможно по-скрити и достъпни само чрез дефиниран интерфейс. Това ви позволява да променяте вътрешната имплементация на класа, без да засягате останалата част от кода. +3) **Внедряване на зависимости (Dependency Injection)**: Вместо да създавате зависимости директно в класа, трябва да ги "инжектирате" отвън. За по-задълбочено разбиране на този принцип препоръчваме [главите за Внедряване на зависимости|dependency-injection:introduction]. diff --git a/nette/bg/troubleshooting.texy b/nette/bg/troubleshooting.texy index 129352a151..7b6ac83974 100644 --- a/nette/bg/troubleshooting.texy +++ b/nette/bg/troubleshooting.texy @@ -1,41 +1,70 @@ -Решаване на проблеми -******************** +Отстраняване на неизправности +***************************** -Nette не работи, показва бяла страница .[#toc-nette-is-not-working-white-page-is-displayed] -------------------------------------------------------------------------------------------- -- Опитайте да поставите `ini_set('display_errors', '1'); error_reporting(E_ALL);` след `declare(strict_types=1);` във файла `index.php`, за да накарате грешките да се показват. -- Ако все още виждате бял екран, вероятно има грешка в конфигурацията на сървъра и причината ще откриете в дневника на сървъра. За да сте сигурни, проверете дали PHP изобщо работи, като се опитате да въведете нещо с помощта на `echo 'test';`. -- Ако видите грешка *Server Error: We're sorry! ...*, преминете към следващия раздел: +Nette не работи, показва се бяла страница +----------------------------------------- +- Опитайте да вмъкнете `ini_set('display_errors', '1'); error_reporting(E_ALL);` във файла `index.php` веднага след `declare(strict_types=1);`, това ще принуди показването на грешки +- Ако все още виждате бял екран, вероятно има грешка в конфигурацията на сървъра и причината ще откриете в лога на сървъра. За всеки случай проверете дали PHP изобщо работи, като опитате да отпечатате нещо с помощта на `echo 'test';` +- Ако виждате грешката *Server Error: We're sorry! …*, продължете към следващата секция: -Грешка 500 *Сървърна грешка: Съжаляваме! ...* .[#toc-error-500-server-error-we-re-sorry] ----------------------------------------------------------------------------------------- -Тази страница за грешка се показва от Nette в производствен режим. Ако го видите на машина за разработчици, [превключете на режим за разработчици |application:bootstrap#Development-vs-Production-Mode]. +Грешка 500 *Server Error: We're sorry! …* +----------------------------------------- +Тази страница за грешки се показва от Nette в продукционен режим. Ако я виждате на компютъра си за разработка, [превключете в режим на разработка |application:bootstrapping#Режим за разработка срещу продукционен режим] и ще видите Tracy с подробно съобщение. -Ако съобщението за грешка съдържа `Tracy is unable to log error`, разберете защо грешките не могат да бъдат регистрирани. Това може да стане например чрез [превключване |application:bootstrap#Development-vs-Production-Mode] в режим за разработчици и извикване на `Tracy\Debugger::log('hello');` след `$configurator->enableTracy(...)`. Трейси ще ви каже защо не може да се регистрира. -Обикновено причината е в [недостатъчните права за |#Setting-Directory-Permissions] запис в директорията `log/`. +Причината за грешката винаги можете да прочетете в лога в директорията `log/`. Но ако в съобщението за грешка се показва изречението `Tracy is unable to log error`, първо разберете защо грешките не могат да бъдат логвани. Можете да направите това например, като временно [превключите |application:bootstrapping#Режим за разработка срещу продукционен режим] в режим на разработка и накарате Tracy да логне нещо след стартирането си: -Ако фразата `Tracy is unable to log error` липсва в съобщението за грешка (вече не е така), можете да откриете причината за грешката в директорията на дневника `log/`. +```php +// Bootstrap.php +$configurator->setDebugMode('23.75.345.200'); // вашият IP адрес +$configurator->enableTracy($rootDir . '/log'); +\Tracy\Debugger::log('hello'); +``` + +Tracy ще ви каже защо не може да логва. Причината може да е дефектна електронна лампа е-тринадесет от фабрика "Катода Оломоуц", но по-вероятно са [недостатъчни права |#Настройка на правата на директориите] за запис в директорията `log/`. + +Една от най-честите причини за грешка 500 е остарял кеш. Докато Nette в режим на разработка интелигентно автоматично актуализира кеша, в продукционен режим той се фокусира върху максималната производителност и изчистването на кеша след всяка промяна на кода е ваша отговорност. Опитайте да изтриете `temp/cache`. + + +Грешка 404, маршрутизацията не работи +------------------------------------- +Когато всички страници (с изключение на началната) връщат грешка 404, изглежда има проблем с конфигурацията на сървъра за [красиви URL адреси |#Как да настроите сървъра за красиви URL адреси]. + + +Промените в шаблоните или конфигурацията не се отразяват +-------------------------------------------------------- +"Редактирах шаблона или конфигурацията, но уебсайтът все още показва старата версия." Това поведение се случва в [продукционен режим |application:bootstrapping#Режим за разработка срещу продукционен режим], който поради съображения за производителност не проверява за промени във файловете и поддържа веднъж генериран кеш. + +За да не се налага ръчно да изтривате кеша на продукционния сървър след всяка промяна, активирайте режима за разработка за вашия IP адрес във файла `Bootstrap.php`: + +```php +$this->configurator->setDebugMode('вашият IP адрес'); +``` + + +Как да изключите кеша по време на разработка? +--------------------------------------------- +Nette е интелигентен и не е необходимо да изключвате кеширането в него. По време на разработка той автоматично актуализира кеша при всяка промяна на шаблона или конфигурацията на DI контейнера. Освен това режимът за разработка се активира чрез автоматично откриване, така че обикновено не е необходимо да конфигурирате нищо, [или само IP адреса |application:bootstrapping#Режим за разработка срещу продукционен режим]. -Една от най-честите причини е остарелият кеш. Докато Nette интелигентно актуализира кеша автоматично в режим на разработка, в производствен режим той се фокусира върху максимална производителност и изчистването на кеша след всяка модификация на кода зависи от вас. Опитайте да премахнете `temp/cache`. +При дебъгване на рутера препоръчваме да изключите кеша на браузъра, в който могат да бъдат съхранени например пренасочвания: отворете Developer Tools (Ctrl+Shift+I или Cmd+Option+I) и в панела Network (Мрежа) отметнете изключването на кеша. -Грешка `#[\ReturnTypeWillChange] attribute should be used` .[#toc-error-returntypewillchange-attribute-should-be-used] ----------------------------------------------------------------------------------------------------------------------- -Тази грешка се появява, ако сте обновили PHP до версия 8.1, но използвате Nette, която не е съвместима с нея. Решението е да се обнови Nette до по-нова версия с помощта на `composer update`. Nette поддържа PHP 8.1 от версия 3.0. Ако използвате по-стара версия (можете да разберете това, като погледнете `composer.json`), [обновете Nette |migrations:en] или останете с PHP 8.0. +Грешка `#[\ReturnTypeWillChange] attribute should be used` +---------------------------------------------------------- +Тази грешка се появява, ако сте актуализирали PHP до версия 8.1, но използвате Nette, която не е съвместима с нея. Решението е да актуализирате Nette до по-нова версия с помощта на `composer update`. Nette поддържа PHP 8.1 от версия 3.0. Ако използвате по-стара версия (можете да проверите в `composer.json`), [надстройте Nette |migrations:en] или останете с PHP 8.0. -Задаване на разрешения за директории .[#toc-setting-directory-permissions] --------------------------------------------------------------------------- -Ако разработвате за macOS или Linux (или друга система, базирана на Unix), ще трябва да зададете права за запис на уеб сървъра. Да предположим, че вашето приложение е разположено в директорията по подразбиране `/var/www/html` (Fedora, CentOS, RHEL). +Настройка на правата на директориите +------------------------------------ +Ако разработвате на macOS или Linux (или на всяка друга система, базирана на Unix), ще трябва да настроите права за запис за уеб сървъра. Да предположим, че вашето приложение се намира в директорията по подразбиране `/var/www/html` (Fedora, CentOS, RHEL). ```shell cd /var/www/html/MY_PROJECT chmod -R a+rw temp log ``` -В някои Linux системи (Fedora, CentOS, ...) SELinux може да е активиран по подразбиране. Може да се наложи да актуализирате политиките на SELinux или да зададете пътищата до директориите `temp` и `log` с правилния контекст за сигурност на SELinux. Директориите `temp` и `log` трябва да бъдат настроени на контекста `httpd_sys_rw_content_t`; за останалата част от приложението - главно за папката `app` - ще бъде достатъчен контекстът `httpd_sys_content_t`. Изпълнете изброените команди като root на сървъра: +На някои Linux дистрибуции (Fedora, CentOS, ...) SELinux е активиран по подразбиране. Ще трябва съответно да модифицирате политиките на SELinux и да зададете правилния контекст на сигурност на SELinux за папките `temp` и `log`. За `temp` и `log` ще зададем тип на контекста `httpd_sys_rw_content_t`, за останалата част от приложението (и особено за папката `app`) ще бъде достатъчен `httpd_sys_content_t`. На сървъра изпълнете: ```shell semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/log(/.*)?' @@ -43,25 +72,30 @@ semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/temp(/.*) restorecon -Rv /var/www/html/MY_PROJECT/ ``` -След това трябва да активирате булевия индикатор SELinux `httpd_can_network_connect_db`, за да позволите на Nette да се свързва с базата данни по мрежата. По подразбиране тя е деактивирана. За изпълнението на тази задача може да се използва командата `setsebool`, а ако е зададена опцията `-P`, тази настройка ще се запази при всички рестартирания. +Освен това е необходимо да се активира булевата променлива на SELinux `httpd_can_network_connect_db`, която е изключена по подразбиране и която ще позволи на Nette да се свърже с базата данни през мрежата. Ще използваме командата `setsebool` за това и с опцията `-P` ще направим промяната постоянна, т.е. след рестартиране на сървъра няма да ни очаква неприятна изненада: ```shell setsebool -P httpd_can_network_connect_db on ``` -Как да променя или премахна директорията `www` от URL адреса? .[#toc-how-to-change-or-remove-www-directory-from-url] --------------------------------------------------------------------------------------------------------------------- -Директорията `www/`, използвана в примерните проекти в Nette, е така наречената публична директория или коренът на проекта. Това е единствената директория, чието съдържание е достъпно за браузъра. Той съдържа файла `index.php`, който е входната точка, от която започва уеб приложението, написано на Nette. +Как да промените или премахнете директорията `www` от URL адреса? +----------------------------------------------------------------- +Директорията `www/`, използвана в примерните проекти в Nette, представлява т.нар. публична директория или document-root на проекта. Това е единствената директория, чието съдържание е достъпно за браузъра. Тя съдържа файла `index.php`, входната точка, която стартира уеб приложението, написано в Nette. -За да стартирате приложението в хостинг услуга, трябва да зададете document-root в тази директория в конфигурацията на хостинга. Или ако на хостинга има готова папка за публичната директория с различно име (например `web`, `public_html` и т.н.), просто преименувайте `www/`. +За да стартирате приложението на хостинг, е необходимо да имате правилно конфигуриран document-root. Имате две възможности: +1. В конфигурацията на хостинга задайте document-root на тази директория +2. Ако хостингът има предварително подготвена папка (напр. `public_html`), преименувайте `www/` на това име -Единственото** решение е да се "отървете" от папката `www/`, като използвате правила във файла `.htaccess` или в маршрутизатора. Ако хостингът ви не позволява да зададете корен на документа в поддиректория (т.е. да създавате директории едно ниво над публичната директория), потърсете друга. В противен случай поемате значителен риск за сигурността. Това е като да живееш в апартамент, в който не можеш да затвориш входната врата и тя винаги е отворена. +.[warning] +Никога не се опитвайте да решавате сигурността само с помощта на `.htaccess` или рутера, които биха ограничили достъпа до други папки. +Ако хостингът не позволява задаването на document-root в поддиректория (т.е. създаване на директории едно ниво над публичната директория), потърсете друг. В противен случай бихте поели значителен риск за сигурността. Би било като да живеете в апартамент, където входната врата не може да се затвори и винаги е отворена. -Как да настроя сървъра за красиви URL адреси? .[#toc-how-to-configure-a-server-for-nice-urls] ---------------------------------------------------------------------------------------------- -**Apache**: Разширението mod_rewrite трябва да бъде разрешено и конфигурирано в `.htaccess`. + +Как да настроите сървъра за красиви URL адреси? +----------------------------------------------- +**Apache**: необходимо е да активирате и настроите правилата на mod_rewrite във файла `.htaccess`: ```apacheconf RewriteEngine On @@ -70,37 +104,65 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -За да промените конфигурацията на Apache чрез файловете .htaccess, трябва да е разрешена директивата AllowOverride. Това е поведението по подразбиране за Apache. +Ако срещнете проблеми, уверете се, че: +- файлът `.htaccess` се намира в директорията document-root (т.е. до файла `index.php`) +- [Apache обработва файлове `.htaccess` |#Проверка дали .htaccess работи] +- [mod_rewrite е активиран |#Проверка дали mod rewrite е активиран] + +Ако настройвате приложението в подпапка, може да се наложи да разкоментирате реда за настройка на `RewriteBase` и да го зададете на правилната папка. -**nginx**: В конфигурацията на сървъра трябва да се използва директивата `try_files`: +**nginx**: необходимо е да настроите пренасочването с помощта на директивата `try_files` вътре в блока `location /` в конфигурацията на сървъра. ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args важно + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args Е ВАЖНО! } ``` -Блокът `location` трябва да бъде дефиниран точно веднъж за всеки път към файловата система в блока `server`. Ако в конфигурацията ви вече има блок `location /`, добавете директивата `try_files` към съществуващия блок. +Блокът `location` може да се среща само веднъж за всеки път във файловата система в блока `server`. Ако вече имате `location /` в конфигурацията, добавете директивата `try_files` към него. + + +Проверка дали `.htaccess` работи +-------------------------------- +Най-лесният начин да тествате дали Apache използва или игнорира вашия файл `.htaccess` е умишлено да го повредите. Поставете ред `Test` в началото на файла и сега, ако обновите страницата в браузъра, трябва да видите *Internal Server Error*. + +Ако видите тази грешка, всъщност е добре! Това означава, че Apache анализира файла `.htaccess` и среща грешката, която сме поставили там. Премахнете реда `Test`. + +Ако не се покаже *Internal Server Error*, вашата конфигурация на Apache игнорира файла `.htaccess`. Обикновено Apache го игнорира поради липсваща конфигурационна директива `AllowOverride All`. + +Ако хоствате сами, това може лесно да се поправи. Отворете файла `httpd.conf` или `apache.conf` в текстов редактор, намерете съответната секция `<Directory>` и добавете/променете тази директива: + +```apacheconf +<Directory "/var/www/htdocs"> # път до вашия document root + AllowOverride All + ... +``` + +Ако вашият уебсайт се хоства другаде, проверете в контролния панел дали можете да активирате файла `.htaccess` там. Ако не, свържете се с вашия хостинг доставчик, за да го направи вместо вас. + + +Проверка дали `mod_rewrite` е активиран +--------------------------------------- +Ако сте проверили, че [`.htaccess` работи |#Проверка дали .htaccess работи], можете да проверите дали разширението mod_rewrite е активирано. Поставете ред `RewriteEngine On` в началото на файла `.htaccess` и обновете страницата в браузъра. Ако се покаже *Internal Server Error*, това означава, че mod_rewrite не е активиран. Има няколко начина да го активирате. Различни начини за това в различни конфигурации можете да намерите в Stack Overflow. -Връзките се генерират без `https:`. .[#toc-links-are-generated-without-https] ------------------------------------------------------------------------------ -Nette генерира връзки със същия протокол, който използва текущата страница. Така връзките, започващи с `https://foo` и обратно. -Ако се намирате зад обратен прокси сървър за стрийминг на HTTPS (напр. в Docker), трябва да настроите [прокси сървър |http:configuration#HTTP-Proxy] в конфигурацията, за да работи правилно дефиницията на протокола. +Връзките се генерират без `https:` +---------------------------------- +Nette генерира връзки със същия протокол като самата страница. Тоест, на страница `https://foo` генерира връзки, започващи с `https:` и обратно. Ако сте зад обратен прокси сървър, който премахва HTTPS (например в Docker), тогава трябва да [настроите проксито |http:configuration#HTTP прокси] в конфигурацията, за да работи правилно откриването на протокола. -Ако използвате Nginx като прокси сървър, трябва да настроите пренасочване, както следва: +Ако използвате Nginx като прокси, е необходимо да имате настроено пренасочване например по следния начин: ``` location / { proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-Port $server_port; - proxy_pass http://IP-aplikace:80; # IP или имя хоста сервера/контейнера, на котором запущено приложение. + proxy_set_header X-Forwarded-Port $server_port; + proxy_pass http://IP-aplikace:80; # IP или hostname на сървъра/контейнера, където работи приложението } ``` -След това трябва да посочите IP адреса на прокси сървъра и, ако е приложимо, IP обхвата на локалната мрежа, в която се изпълнява инфраструктурата: +Освен това е необходимо да посочите IP адреса на проксито и евентуално IP обхвата на вашата локална мрежа, където работи инфраструктурата, в конфигурацията: ```neon http: @@ -108,29 +170,27 @@ http: ``` -Използване на знаци { } в JavaScript .[#toc-use-of-characters-in-javascript] ----------------------------------------------------------------------------- -Символите `{` и `}` се използват за изписване на етикети Latte. Всичко (с изключение на бели интервали и кавички) след символа `{`, считается тегом. Если вам нужно вывести символ `{` (често срещан в JavaScript), можете да поставите интервал (или друг празен символ) непосредствено след `{`. По този начин се избягва тълкуването му като таг. +Използване на знаците { } в JavaScript +-------------------------------------- +Знаците `{` и `}` се използват за записване на Latte тагове. Като таг се приема всичко, което следва знака `{`, с изключение на интервал и кавичка. Следователно, ако трябва да отпечатате директно знака `{` (често например в JavaScript), можете да поставите интервал (или друг празен знак) след знака `{`. По този начин ще избегнете превода му като таг. -Ако искате да изведете тези символи в ситуация, в която те ще бъдат интерпретирани като таг, можете да използвате специални тагове за извеждането на тези символи - `{l}` за `{` и `{r}` за `}`. +Ако е необходимо да отпечатате тези знаци в ситуация, в която текстът би се разбрал като таг, можете да използвате специални тагове за отпечатване на тези знаци - `{l}` за `{` и `{r}` за `}`. ``` -{is tag} -{ is not tag } -{l}is not tag{r} +{е таг} +{ не е таг } +{l}не е таг{r} ``` -Уведомление `Presenter::getContext() is deprecated` .[#toc-notice-presenter-getcontext-is-deprecated] ------------------------------------------------------------------------------------------------------ +Съобщение `Presenter::getContext() is deprecated` +------------------------------------------------- -Nette определено е първата рамка на PHP, която преминава към инжектиране на зависимостите и принуждава програмистите да я използват последователно, като се започне от главния модул. Ако водещият иска зависимост, [той ще я поиска |dependency-injection:passing-dependencies]. -За разлика от това, начинът, по който предаваме целия DI контейнер на класа и той директно извлича зависимости от него, се счита за антипатернинг (нарича се service locator). -Този метод се използваше в Nette 0.x преди инжектирането на зависимости и негов реликт е методът `Presenter::getContext()`, отдавна отбелязан като отпаднал. +Nette е далеч първият PHP framework, който премина към инжектиране на зависимости и насочи програмистите към последователното му използване, още от самите presenter-и. Ако presenter-ът се нуждае от някаква зависимост, той [я заявява|dependency-injection:passing-dependencies]. Обратно, подходът, при който предаваме целия DI контейнер на класа и той директно извлича зависимости от него, се счита за антипатърн (нарича се service locator). Този метод се използваше в Nette 0.x още преди появата на инжектирането на зависимости и негов остатък е методът `Presenter::getContext()`, отдавна означен като deprecated. -Ако пренесете много старо приложение на Nette, може да откриете, че то все още използва този метод. Така че, започвайки от версия 3.1 `nette/application`, ще срещнете предупреждение `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection`, а започвайки от версия 4.0, ще срещнете грешка, че методът не съществува. +Ако пренасяте много старо приложение към Nette, може да се окаже, че то все още използва този метод. От версия 3.1 на `nette/application` ще срещнете предупреждението `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection`, а от версия 4.0 - грешка, че методът не съществува. -Разбира се, най-чистото решение е да се преработи дизайнът на приложението, за да се предават зависимостите чрез инжектиране на зависимости. Като заобиколен вариант можете да добавите свой собствен метод `getContext()` към основния презентатор и да заобиколите съобщението: +Чистото решение, разбира се, е да преработите приложението така, че да предава зависимостите чрез инжектиране на зависимости. Като заобиколно решение можете да добавите собствен метод `getContext()` към вашия базов presenter и така да заобиколите съобщението: ```php abstract BasePresenter extends Nette\Application\UI\Presenter diff --git a/nette/bg/vulnerability-protection.texy b/nette/bg/vulnerability-protection.texy index 9000a5b831..b751906d32 100644 --- a/nette/bg/vulnerability-protection.texy +++ b/nette/bg/vulnerability-protection.texy @@ -1,22 +1,22 @@ -Защита от уязвимост -******************* +Защита от уязвимости +******************** .[perex] -От време на време се съобщава за сериозни пропуски в сигурността или дори за злоупотреби. Разбира се, това е малко неприятно. Ако ви е грижа за сигурността на вашите уеб приложения, рамката Nette определено е най-добрият избор за вас. +Почти постоянно се съобщава за дупка в сигурността на поредния голям уебсайт или за злоупотреба с такава дупка. Това е неприятно. Ако ви е грижа за сигурността на вашите уеб приложения, Nette Framework със сигурност е най-добрият избор. -Пресичане на сайтове (Cross-Site Scripting - XSS) .[#toc-cross-site-scripting-xss] -================================================================================== +Cross-Site Scripting (XSS) +========================== -Cross-site scripting е метод за нарушаване на работата на уебсайт чрез използване на непроверени входни данни. Атакуващият може да инжектира свой собствен HTML или JavaScript код и да промени външния вид на страницата или дори да събере чувствителна потребителска информация. Защитата срещу XSS е проста: последователно и правилно проверявайте всички редове и входни данни. Традиционно беше достатъчно програмистът да допусне и най-малката грешка, да забрави веднъж, и целият сайт можеше да бъде компрометиран. +Cross-Site Scripting е метод за нарушаване на уеб страници, който използва необработени изходи. Нападателят може да вмъкне свой собствен код в страницата и по този начин да я промени или дори да получи чувствителни данни за посетителите. Защитата срещу XSS е възможна само чрез последователна и коректна обработка на всички низове. Достатъчно е вашият програмист да пропусне това само веднъж и целият уебсайт може да бъде компрометиран веднага. -Пример за такова инжектиране е предаването на модифициран URL адрес на потребител, който вмъква "злонамерен" скрипт във файл. Ако дадено приложение не защити правилно своя вход, такава заявка може да доведе до изпълнение на скрипт от страна на клиента. Това може да доведе например до кражба на самоличност. +Пример за атака може да бъде подхвърляне на модифициран URL адрес на потребителя, чрез който инжектираме наш код в страницата. Ако приложението не обработва правилно изходите, скриптът ще се изпълни в браузъра на потребителя. По този начин можем например да откраднем самоличността му. ``` -http://example.com/?search=<script>alert('XSS attack.');</script> +https://example.com/?search=<script>alert('Успешна XSS атака.');</script> ``` -Рамката на Nette предлага чисто нова технология за [избягване, |latte:safety-first#Context-Aware-Escaping] която завинаги ще премахне рисковете от скриптове на различни сайтове. Той автоматично ескапира всички входни данни въз основа на зададения контекст, така че програмистът да не може случайно да забрави нещо. Разгледайте следния шаблон като пример: +Nette Framework идва с революционна технология [Context-Aware Escaping |latte:safety-first#Контекстно-чувствително екраниране], която завинаги ще ви отърве от риска от Cross-Site Scripting. Тя обработва всички изходи автоматично, така че програмистът не може да забрави нещо. Пример? Програмистът създава този шаблон: ```latte <p onclick="alert({$message})">{$message}</p> @@ -26,29 +26,29 @@ document.title = {$message}; </script> ``` -Командата `{$message}` извежда променлива. Други рамки често принуждават разработчиците изрично да декларират escape и дори типа на escape в зависимост от контекста. С Nette обаче не е необходимо да декларирате нищо. Всичко се случва автоматично, последователно и прецизно. Ако зададем променлива на `$message = 'Width 1/2"'`, рамката ще генерира този HTML код: +Записът `{$message}` означава отпечатване на променлива. В други framework-ове е необходимо всяко отпечатване да се обработва изрично и дори по различен начин на всяко място. В Nette Framework не е необходимо да се обработва нищо, всичко се прави автоматично, правилно и последователно. Ако зададем на променливата `$message = 'Ширина 1/2"'`, framework-ът ще генерира HTML код: ```latte -<p onclick="alert("Width 1\/2\"")">Width 1/2"</p> +<p onclick="alert("Ширина 1\/2\"")">Ширина 1/2"</p> <script> -document.title = "Width 1\/2\""; +document.title = "Ширина 1\/2\""; </script> ``` -Фалшифициране на заявка от друг сайт (CSRF) .[#toc-cross-site-request-forgery-csrf] -=================================================================================== +Cross-Site Request Forgery (CSRF) +================================= -Атаката Cross-Site Request Forgery се състои в това, че нападателят примамва жертвата да посети страница, която безшумно изпълнява заявка в браузъра на жертвата към сървъра, в който жертвата е влязла в момента, и сървърът приема, че заявката е направена от жертвата по нейна собствена воля. Сървърът извършва определено действие под самоличността на жертвата, но без нейното знание. Това може да бъде промяна или изтриване на данни, изпращане на съобщение и т.н. +Атаката Cross-Site Request Forgery се състои в това, че нападателят примамва жертвата към страница, която незабелязано в браузъра на жертвата изпълнява заявка към сървъра, на който жертвата е влязла, и сървърът смята, че заявката е изпълнена от жертвата по нейна воля. Така под самоличността на жертвата се извършва определено действие, без тя да знае за това. Това може да бъде промяна или изтриване на данни, изпращане на съобщение и т.н. -Nette **автоматично защитава формулярите и сигналите в презентаторите** от този тип атаки. Това става, като се предотвратява изпращането или извикването им от друг домейн. За да деактивирате защитата, използвайте следното за формите +Nette Framework **автоматично защитава формите и сигналите в presenter-ите** от този тип атака. Това става чрез предотвратяване на тяхното изпращане или извикване от друг домейн. Ако искате да изключите защитата, използвайте за формите: ```php $form->allowCrossOrigin(); ``` -или, в случай на сигнал, добавете анотацията `@crossOrigin`: +или в случай на сигнал добавете анотацията `@crossOrigin`: ```php /** @@ -59,42 +59,41 @@ public function handleXyz() } ``` -В PHP 8 можете да използвате и атрибути: +В Nette Application 3.2 можете да използвате и атрибути: ```php -use Nette\Application\Attributes\CrossOrigin; +use Nette\Application\Attributes\Requires; -#[CrossOrigin] +#[Requires(sameOrigin: false)] public function handleXyz() { } ``` -URL атака, контролни кодове, невалиден UTF-8 .[#toc-url-attack-control-codes-invalid-utf-8] -=========================================================================================== +URL атака, контролни кодове, невалиден UTF-8 +============================================ -Различни термини, свързани с опита на нападателя да направи приложението ви "злонамерено". Резултатите могат да варират в широки граници - от непълен XML изход (напр. нарушен RSS канал) до получаване на чувствителна информация от база данни и извличане на пароли на потребители. Защитата срещу тези атаки е последователна проверка на UTF-8 на ниво байт. И честно казано, не бихте го направили без рамка, нали? +Различни термини, свързани с опита на нападателя да подхвърли *злонамерен* вход на вашето уеб приложение. Последствията могат да бъдат много разнообразни, от повреждане на XML изходи (напр. неработещи RSS канали) до получаване на чувствителна информация от базата данни или пароли. Защитата е последователна обработка на всички входове на ниво отделни байтове. И ръка на сърцето, кой от вас го прави? -Рамката на Nette прави това автоматично за вас. Не е необходимо да конфигурирате каквото и да било и приложението ви ще бъде защитено. +Nette Framework го прави вместо вас и освен това автоматично. Не е необходимо да настройвате абсолютно нищо и всички входове ще бъдат обработени. -Отвличане на сесии, кражба на сесии, заключване на сесии .[#toc-session-hijacking-session-stealing-session-fixation] -==================================================================================================================== +Отвличане на сесия, кражба на сесия, фиксиране на сесия +======================================================= -Управлението на сесиите включва няколко вида атаки. Атакуващият може да открадне или да подмени идентификатора на сесията на жертвата и по този начин да получи достъп до уеб приложение без действителна парола. След това нападателят може да направи всичко, което потребителят може да направи, без никакви следи. Защитата се състои в правилното конфигуриране на PHP и на самия уеб сървър. +С управлението на сесиите са свързани няколко типа атаки. Нападателят или краде, или подхвърля на потребителя своето ID на сесия и благодарение на това получава достъп до уеб приложението, без да знае паролата на потребителя. След това може да прави всичко в приложението, без потребителят да знае за това. Защитата се състои в правилната конфигурация на сървъра и PHP. -Nette конфигурира PHP автоматично. По този начин разработчиците не трябва да се притесняват за това дали сесията е достатъчно сигурна и могат да се съсредоточат изцяло върху ключовите части на приложението. За целта е необходимо да активирате функцията `ini_set()`. +При което Nette Framework конфигурира PHP автоматично. Програмистът не трябва да мисли как правилно да защити сесията и може напълно да се съсредоточи върху създаването на приложението. Това обаче изисква активирана функция `ini_set()`. -Бисквитки на SameSite .[#toc-samesite-cookie] -============================================= +SameSite cookie +=============== -Бисквитките SameSite предоставят механизъм за разпознаване на причината за зареждането на дадена страница. Това е абсолютно необходимо от съображения за сигурност. +SameSite бисквитките предоставят механизъм за разпознаване на това, което е довело до зареждането на страницата. Което е абсолютно ключово за сигурността. -Флагът SameSite може да има три стойности: `Lax`, `Strict` и `None` (изисква се HTTPS). Ако заявката за страницата идва директно от самия сайт или ако потребителят влезе директно в страницата, като я напише в адресната лента или като щракне върху отметки, браузърът изпраща всички бисквитки на сървъра (т.е. с флагове `Lax`, `Strict` и `None`). Ако потребителят влезе в уебсайта чрез връзка от друг уебсайт, на сървъра се изпращат "бисквитки" с флагове `Lax` и `None`. Ако заявката е направена на друго място, например при изпращане на POST формуляр от друг източник, изтегляне в iframe, използване на JavaScript и т.н., ще бъде изпратена само бисквитката с флаг `None`. - -По подразбиране Nette изпраща всички бисквитки с флаг `Lax`. +Флагът SameSite може да има три стойности: `Lax`, `Strict` и `None` (последният изисква HTTPS). Ако заявката за страницата идва директно от уебсайта или потребителят отвори страницата чрез директно въвеждане в адресната лента или чрез кликване върху отметка, браузърът изпраща на сървъра всички бисквитки (т.е. с флагове `Lax`, `Strict` и `None`). Ако потребителят кликне върху връзка от друг уебсайт, на сървъра се предават бисквитки с флагове `Lax` и `None`. Ако заявката възникне по друг начин, като изпращане на POST форма от друг уебсайт, зареждане в iframe, чрез JavaScript и т.н., се изпращат само бисквитки с флаг `None`. +Nette по подразбиране изпраща всички бисквитки с флаг `Lax`. {{leftbar: www:@menu-common}} diff --git a/nette/cs/@home.texy b/nette/cs/@home.texy index ec7373b9d4..b07e4cbb99 100644 --- a/nette/cs/@home.texy +++ b/nette/cs/@home.texy @@ -34,10 +34,10 @@ Obecné Aplikace v Nette ---------------- - [Jak fungují aplikace? |application:how-it-works] -- [application:Bootstrap] +- [application:Bootstrapping] - [Presentery |application:presenters] - [Šablony |application:templates] -- [Moduly |application:modules] +- [Adresářová struktura |application:directory-structure] - [Routování |application:routing] - [Vytváření odkazů URL |application:creating-links] - [Interaktivní komponenty |application:components] @@ -58,11 +58,12 @@ Hlavní témata - [Latte: šablony |latte:] - [Tracy: ladění kódu |tracy:] - [Formuláře |forms:] -- [Databáze |database:core] +- [Databáze |database:guide] - [Přihlašování uživatelů |security:authentication] - [Ověřování oprávnění |security:authorization] - [http:Sessions] - [HTTP request & response|http:] +- [Assety |assets:] - [Cache |caching:] - [Odesílání e-mailů |mail:] - [Schema: validace dat |schema:] @@ -84,11 +85,11 @@ Utilities - [utils:JSON] - [NEON|neon:] - [Hashování hesel |security:passwords] -- [SmartObject |utils:smartobject] - [PHP typy |utils:type] - [Řetězce |utils:strings] - [Validátory |utils:validators] - [RobotLoader |robot-loader:] +- [SmartObject |utils:smartobject] & [StaticClass |utils:StaticClass] - [SafeStream |safe-stream:] - [...další |utils:] </div> diff --git a/nette/cs/@menu-topics.texy b/nette/cs/@menu-topics.texy index facb02b4e2..f53c16bd8d 100644 --- a/nette/cs/@menu-topics.texy +++ b/nette/cs/@menu-topics.texy @@ -5,7 +5,7 @@ Hlavní témata - [Dependency Injection|dependency-injection:] - [Utilities |utils:] - [Formuláře |forms:] -- [Databáze |database:core] +- [Databáze |database:guide] - [Přihlašování uživatelů |security:authentication] - [Ověřování oprávnění |security:authorization] - [http:Sessions] diff --git a/nette/cs/@meta.texy b/nette/cs/@meta.texy new file mode 100644 index 0000000000..462d9add80 --- /dev/null +++ b/nette/cs/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokumentace}} diff --git a/nette/cs/configuring.texy b/nette/cs/configuring.texy index acde5ad68e..e5ac8533e7 100644 --- a/nette/cs/configuring.texy +++ b/nette/cs/configuring.texy @@ -4,14 +4,14 @@ Konfigurace Nette .[perex] Přehled všech konfiguračních voleb v Nette Frameworku. -Součásti Nette nastavujeme pomocí konfiguračních souborů, které se obvykle zapisují ve [formátu NEON|neon:format]. Nejlépe se upravují v [editorech s jeho podporou|best-practices:editors-and-tools#ide-editor]. -Pokud používate celý framework, konfigurace se [načte při zavádění aplikace|application:bootstrap#konfigurace-di-kontejneru], pokud ne, přečtěte si, [jak konfiguraci načíst|bootstrap:]. +Součásti Nette nastavujeme pomocí konfiguračních souborů, které se obvykle zapisují ve [formátu NEON|neon:format]. Nejlépe se upravují v [editorech s jeho podporou |best-practices:editors-and-tools#IDE editor]. Pokud používate celý framework, konfigurace se [načte při zavádění aplikace |application:bootstrapping#Konfigurace DI kontejneru], pokud ne, přečtěte si, [jak konfiguraci načíst|bootstrap:]. <pre> "application .[prism-token prism-atrule]":[application:configuration#Application]: "Application .[prism-token prism-comment]"<br> +"assets .[prism-token prism-atrule]":[assets:configuration]: "Assets .[prism-token prism-comment]"<br> "constants .[prism-token prism-atrule]":[application:configuration#Konstanty]: "Definice PHP konstant .[prism-token prism-comment]"<br> "database .[prism-token prism-atrule]":[database:configuration]: "Databáze .[prism-token prism-comment]"<br> -"decorator .[prism-token prism-atrule]":[dependency-injection:configuration#decorator]: "Dekorátor .[prism-token prism-comment]"<br> +"decorator .[prism-token prism-atrule]":[dependency-injection:configuration#Decorator]: "Dekorátor .[prism-token prism-comment]"<br> "di .[prism-token prism-atrule]":[dependency-injection:configuration#DI]: "DI kontejner .[prism-token prism-comment]"<br> "extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Rozšíření]: "Instalace dalších DI rozšíření .[prism-token prism-comment]"<br> "forms .[prism-token prism-atrule]":[forms:configuration]: "Formuláře .[prism-token prism-comment]"<br> @@ -19,8 +19,8 @@ Pokud používate celý framework, konfigurace se [načte při zavádění aplik "includes .[prism-token prism-atrule]":[dependency-injection:configuration#Vkládání souborů]: "Vkládání souborů .[prism-token prism-comment]"<br> "latte .[prism-token prism-atrule]":[application:configuration#Šablony Latte]: "Šablony Latte .[prism-token prism-comment]"<br> "mail .[prism-token prism-atrule]":[mail:#Konfigurace]: "Maily .[prism-token prism-comment]"<br> -"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#parametry]: "Parametry .[prism-token prism-comment]"<br> -"php .[prism-token prism-atrule]":[application:configuration#Php]: "PHP konfigurace .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#Parametry]: "Parametry .[prism-token prism-comment]"<br> +"php .[prism-token prism-atrule]":[application:configuration#PHP]: "PHP konfigurace .[prism-token prism-comment]"<br> "routing .[prism-token prism-atrule]":[application:configuration#Routování]: "Routování .[prism-token prism-comment]"<br> "search .[prism-token prism-atrule]":[dependency-injection:configuration#Search]: "Automatická registrace služeb .[prism-token prism-comment]"<br> "security .[prism-token prism-atrule]":[security:configuration]: "Přístupová oprávnění .[prism-token prism-comment]"<br> @@ -29,7 +29,8 @@ Pokud používate celý framework, konfigurace se [načte při zavádění aplik "tracy .[prism-token prism-atrule]":[tracy:configuring#Nette Framework]: "Tracy debugger .[prism-token prism-comment]" </pre> -Chcete-li zapsat řetězec obsahující znak `%`, musíte jej escapovat zdvojením na `%%`. .[note] +.[note] +Chcete-li zapsat řetězec obsahující znak `%`, musíte jej escapovat zdvojením na `%%`. {{leftbar: @menu-topics}} diff --git a/nette/cs/glossary.texy b/nette/cs/glossary.texy index e67f066a22..7b273ba481 100644 --- a/nette/cs/glossary.texy +++ b/nette/cs/glossary.texy @@ -19,7 +19,7 @@ Tzv. *byte order mark* je speciální první znak v souboru, který se použív Controller ---------- -Řadič, který zpracovává požadavky uživatele a na jejich základě pak volá patřičnou aplikační logiku (tj. [#model]) a poté požádá [#view] o vykreslení dat. Obdobou kontrolerů v Nette Frameworku jsou [presentery|#presenter]. +Řadič, který zpracovává požadavky uživatele a na jejich základě pak volá patřičnou aplikační logiku (tj. [#model]) a poté požádá [#view] o vykreslení dat. Obdobou kontrolerů v Nette Frameworku jsou [presentery |#Presenter]. Cross-Site Scripting (XSS) @@ -36,14 +36,21 @@ Cross-Site Request Forgery (CSRF) Nette Framework **automaticky chrání formuláře a signály v presenterech** před tímto typem útoku. A to tím, že zabraňuje jejich odeslání či vyvolání z jiné domény. +Cross-Origin Resource Sharing (CORS) +------------------------------------ +CORS je bezpečnostní mechanismus, který umožňuje webové stránce provádět JavaScriptové požadavky na jinou doménu, než ze které byla stránka načtena. Bez CORS prohlížeče takové požadavky z bezpečnostních důvodů blokují. + +Například pokud vaše webová stránka běží na `https://myapp.com` a pokusí se pomocí JavaScriptu (AJAX, Fetch API) načíst data z `https://api.example.com`, prohlížeč ověří, zda API server tento požadavek mezi doménami povoluje. API server musí odpovědět speciálními HTTP hlavičkami, jako je `Access-Control-Allow-Origin: https://myapp.com`, aby udělil povolení. + + Dependency Injection -------------------- -Dependency Injection (DI) je návrhový vzor, který říká, jak oddělit vytváření objektů od jejich závislostí. Tedy že třída není zodpovědná za vytváření nebo inicializaci svých závislostí, ale místo toho jsou jí tyto závislosti poskytovány externím kódem (tím může i [DI kontejner|#Dependency Injection kontejner]). Výhoda spočívá v tom, že umožňuje větší flexibilitu kódu, lepší srozumitelnost a snazší testování aplikace, protože závislosti jsou snadno nahraditelné a izolované od ostatních částí kódu. Více v kapitole [Co je Dependency Injection? |dependency-injection:introduction] +Dependency Injection (DI) je návrhový vzor, který říká, jak oddělit vytváření objektů od jejich závislostí. Tedy že třída není zodpovědná za vytváření nebo inicializaci svých závislostí, ale místo toho jsou jí tyto závislosti poskytovány externím kódem (tím může i [DI kontejner |#Dependency Injection kontejner]). Výhoda spočívá v tom, že umožňuje větší flexibilitu kódu, lepší srozumitelnost a snazší testování aplikace, protože závislosti jsou snadno nahraditelné a izolované od ostatních částí kódu. Více v kapitole [Co je Dependency Injection? |dependency-injection:introduction] Dependency Injection kontejner ------------------------------ -Dependency Injection kontejner (také DI kontejner nebo IoC kontejner) je nástroj, který obstarává vytváření a správu závislostí v aplikaci (neboli [služeb|#služba]). Kontejner má většinou konfiguraci, která definuje, jaké třídy jsou závislé na jiných třídách, jaké konkrétní implementace závislostí se mají použít a jak se mají tyto závislosti vytvářet. Poté kontejner vytvoří tyto objekty a poskytne je třídám, které je potřebují. Více v kapitole [Co je DI kontejner? |dependency-injection:container] +Dependency Injection kontejner (také DI kontejner nebo IoC kontejner) je nástroj, který obstarává vytváření a správu závislostí v aplikaci (neboli [služeb |#Služba]). Kontejner má většinou konfiguraci, která definuje, jaké třídy jsou závislé na jiných třídách, jaké konkrétní implementace závislostí se mají použít a jak se mají tyto závislosti vytvářet. Poté kontejner vytvoří tyto objekty a poskytne je třídám, které je potřebují. Více v kapitole [Co je DI kontejner? |dependency-injection:container] Escapování @@ -53,12 +60,12 @@ Escapování je převod znaků majících v daném kontextu speciální význam Filter (dříve helper) --------------------- -V šablonách se pod pojmem [filter |latte:syntax#filtry] obvykle rozumí funkce, která pomáhá upravit nebo přeformátovat data do výsledné podoby. Šablony disponují několika [standardními filtry |latte:filters]. +V šablonách se pod pojmem [filter |latte:syntax#Filtry] obvykle rozumí funkce, která pomáhá upravit nebo přeformátovat data do výsledné podoby. Šablony disponují několika [standardními filtry |latte:filters]. Invalidace ---------- -Oznámení [snippetu |#snippet], aby se překreslil. V jiném významu také smazání obsahu cache. +Oznámení [snippetu |#Snippet], aby se překreslil. V jiném významu také smazání obsahu cache. JSON @@ -73,7 +80,7 @@ Znovupoužitelná součást aplikace. Může to být vizuální část stránky, Kontrolní znaky --------------- -Kontrolní znaky jsou neviditelné znaky, které se mohou vyskytovat v textu a případně i způsobovat problémy. K jejich hromadnému odstranění ze souborů můžete použít [Code Checker|code-checker:] a k odstranění z proměnné funkci [Strings::normalize()|utils:strings#normalize]. +Kontrolní znaky jsou neviditelné znaky, které se mohou vyskytovat v textu a případně i způsobovat problémy. K jejich hromadnému odstranění ze souborů můžete použít [Code Checker|code-checker:] a k odstranění z proměnné funkci [Strings::normalize() |utils:strings#normalize]. Eventy (události) .{toc: události} @@ -92,7 +99,7 @@ Model ----- Model je datový a zejména funkční základ celé aplikace. Je v něm obsažena celá aplikační logika (používá se i termín byznys logika). Je to ono **M** z **M**VC nebo MVP. Jakákoliv akce uživatele (přihlášení, vložení zboží do košíku, změna hodnoty v databázi) představuje akci modelu. -Model si spravuje svůj vnitřní stav a ven nabízí pevně dané rozhraní. Voláním funkcí tohoto rozhraní můžeme zjišťovat či měnit jeho stav. Model neví o existenci [#view] nebo [controlleru |#controller]. +Model si spravuje svůj vnitřní stav a ven nabízí pevně dané rozhraní. Voláním funkcí tohoto rozhraní můžeme zjišťovat či měnit jeho stav. Model neví o existenci [#view] nebo [controlleru |#Controller]. Model-View-Controller @@ -107,12 +114,14 @@ Architektura, vycházející z [#Model-View-Controller]. Modul ----- -[Modul |application:modules] představuje v Nette Framework balíček presenterů a šablon, případně i komponent a modelů, které dodávají presenteru data. Je to tedy určitá logická část aplikace. +Modul představuje logickou část aplikace. V typickém uspořádání jde o skupinu presenterů a šablon, které řeší určitou oblast funkcionality. Moduly umísťujeme do [samostatných adresářů |application:directory-structure#Presentery a šablony], jako např. `Front/`, `Admin/` nebo `Shop/`. -Například e-shop může mít tři moduly: -1) katalog produktů s košíkem -2) administrace pro zákazníka -3) administrace pro provozovatele +Například e-shop rozdělíme na: +- Frontend (`Shop/`) pro prohlížení produktů a nákup +- Zákaznickou sekci (`Customer/`) pro správu objednávek +- Administraci (`Admin/`) pro provozovatele + +Technicky jde o běžné adresáře, které ale díky přehlednému členění pomáhají aplikaci škálovat. Presenter `Admin:Product:List` tak bude fyzicky umístěn například v adresáři `app/Presentation/Admin/Product/List/` (viz [mapování presenterů |application:directory-structure#Mapování presenterů]). Namespace @@ -124,12 +133,12 @@ Presenter --------- Presenter je objekt, který vezme [požadavek |api:Nette\Application\Request] přeložený routerem z HTTP požadavku a vygeneruje [odpověď |api:Nette\Application\Response]. Odpovědí může být HTML stránka, obrázek, XML dokument, soubor na disku, JSON, přesměrování nebo cokoliv vymyslíte. -Obvykle se pod pojmem presenter myslí potomek třídy [api:Nette\Application\UI\Presenter]. Podle příchozích požadavků spouští odpovídající [akce |application:presenters#zivotni-cyklus-presenteru] a vykresluje šablony. +Obvykle se pod pojmem presenter myslí potomek třídy [api:Nette\Application\UI\Presenter]. Podle příchozích požadavků spouští odpovídající [akce |application:presenters#Životní cyklus presenteru] a vykresluje šablony. Router ------ -Obousměrný překladač mezi HTTP požadavkem / URL a akcí presenteru. Obousměrné znamená, že z HTTP požadavku lze odvodit [akci presenteru |#akce presenteru], ale také obráceně k akci vygenerovat odpovídající URL. Více v kapitole o [routování URL |application:routing]. +Obousměrný překladač mezi HTTP požadavkem / URL a akcí presenteru. Obousměrné znamená, že z HTTP požadavku lze odvodit [akci presenteru |#Akce presenteru], ale také obráceně k akci vygenerovat odpovídající URL. Více v kapitole o [routování URL |application:routing]. SameSite cookie @@ -153,11 +162,7 @@ View, tedy pohled, je vrstva aplikace, která má na starost zobrazení výsledk -/--comment -Pokyny pro autory: -- vysvětlení by mělo být stručné, cca 1 odstavec (kde nestačí stručné vysvětlení, musí být odkaz na jiné místo v dokumentaci) -- na pojmy zde pak lze odkazovat z jiných částí dokumentace případně z fóra -\-- + {{leftbar: www:@menu-common}} {{priority: -2}} diff --git a/nette/cs/installation.texy b/nette/cs/installation.texy index b2d4b99c94..9d376c818a 100644 --- a/nette/cs/installation.texy +++ b/nette/cs/installation.texy @@ -20,7 +20,7 @@ Nette nabízí kolekci užitečných a vyspělých balíčků (knihoven) pro PHP composer require nette/utils ``` -Preferujete grafické rozhraní? Prohlédněte si [návod|https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] na instalaci balíčků v prostředí PhpStrom. +Preferujete grafické rozhraní? Prohlédněte si [návod|https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] na instalaci balíčků v prostředí PhpStorm. Jak založit nový projekt s Nette @@ -40,7 +40,7 @@ composer create-project nette/web-project NAZEV_PROJEKTU 4) **Nepoužíváte Composer?** Stačí si stáhnout [Web Project ve formátu ZIP|https://github.com/nette/web-project/archive/preloaded.zip] a rozbalit jej. Ale věřte, Composer stojí za to! -5) **Nastavení práv:** Na systémech macOS či Linux nastavte [práva zápisu|nette:troubleshooting#Nastavení práv adresářů] do adresářů. +5) **Nastavení práv:** Na systémech macOS či Linux nastavte [práva zápisu |nette:troubleshooting#Nastavení práv adresářů] do adresářů. 6) **Otevření projektu v prohlížeči:** Zadejte URL `http://localhost/NAZEV_PROJEKTU/www/` a uvidíte úvodní stránku skeletonu: @@ -48,7 +48,7 @@ composer create-project nette/web-project NAZEV_PROJEKTU Gratulujeme! Váš web je nyní připraven k vývoji. Uvítací šablonu můžete odstranit a začít vytvářet svou aplikaci. -Jedním z kladů Nette je, že projekt funguje ihned bez potřeby konfigurace. Pokud se však setkáte s problémy, zkuste se podívat na [řešení častých problémů|nette:troubleshooting#Nejde mi Nette, zobrazuje se bílá stránka]. +Jedním z kladů Nette je, že projekt funguje ihned bez potřeby konfigurace. Pokud se však setkáte s problémy, zkuste se podívat na [řešení častých problémů |nette:troubleshooting#Nejde mi Nette zobrazuje se bílá stránka]. .[note] Pokud začínáte s Nette, doporučujeme pokračovat [tutoriálem Píšeme první aplikaci|quickstart:]. diff --git a/nette/cs/introduction-to-object-oriented-programming.texy b/nette/cs/introduction-to-object-oriented-programming.texy new file mode 100644 index 0000000000..e7c34e9e30 --- /dev/null +++ b/nette/cs/introduction-to-object-oriented-programming.texy @@ -0,0 +1,841 @@ +Úvod do objektově orientovaného programování +******************************************** + +.[perex] +Termín "OOP" označuje objektově orientované programování, což je způsob, jak organizovat a strukturovat kód. OOP nám umožňuje vidět program jako soubor objektů, které komunikují mezi sebou, místo sledu příkazů a funkcí. + +V OOP je "objekt" jednotka, která obsahuje data a funkce, které s těmito daty pracují. Objekty jsou vytvořeny podle "tříd", které můžeme chápat jako návrhy nebo šablony pro objekty. Když máme třídu, můžeme vytvořit její "instanci", což je konkrétní objekt vytvořený podle této třídy. + +Pojďme si ukázat, jak můžeme vytvořit jednoduchou třídu v PHP. Při definování třídy použijeme klíčové slovo "class", následované názvem třídy a pak složenými závorkami, které obklopují funkce (říká se jim "metody") a proměnné třídy (říká se jim "vlastnosti" nebo anglicky "property"): + +```php +class Auto +{ + function zatrub() + { + echo 'Bip bip!'; + } +} +``` + +V tomto příkladě jsme vytvořili třídu s názvem `Auto` s jednou funkcí (nebo "metodou") nazvanou `zatrub`. + +Každá třída by měla řešit pouze jeden hlavní úkol. Pokud třída dělá příliš mnoho věcí, může být vhodné ji rozdělit na menší, specializované třídy. + +Třídy obvykle ukládáme do samostatných souborů, aby byl kód organizovaný a snadno se v něm orientovalo. Název souboru by měl odpovídat názvu třídy, takže pro třídu `Auto` by název souboru byl `Auto.php`. + +Při pojmenování tříd je dobré držet se konvence "PascalCase", což znamená, že každé slovo v názvu začíná velkým písmenem a nejsou mezi nimi žádné podtržítka nebo jiné oddělovače. Metody a vlastnosti používají konvenci "camelCase", to znamená, že začínají malým písmenem. + +Některé metody v PHP mají speciální úlohy a jsou označené předponou `__` (dvě podtržítka). Jednou z nejdůležitějších speciálních metod je "konstruktor", který je označen jako `__construct`. Konstruktor je metoda, která se automaticky zavolá, když vytváříte novou instanci třídy. + +Konstruktor často používáme k nastavení počátečního stavu objektu. Například, když vytváříte objekt reprezentující osobu, můžete využit konstruktor k nastavení jejího věku, jména nebo jiných vlastností. + +Pojďme si ukázat, jak použít konstruktor v PHP: + +```php +class Osoba +{ + private $vek; + + function __construct($vek) + { + $this->vek = $vek; + } + + function kolikJeTiLet() + { + return $this->vek; + } +} + +$osoba = new Osoba(25); +echo $osoba->kolikJeTiLet(); // Vypíše: 25 +``` + +V tomto příkladě třída `Osoba` má vlastnost (proměnnou) `$vek` a dále konstruktor, který nastavuje tuto vlastnost. Metoda `kolikJeTiLet()` pak umožňuje přístup k věku osoby. + +Pseudoproměnná `$this` se používá uvnitř třídy pro přístup k vlastnostem a metodám objektu. + +Klíčové slovo `new` se používá k vytvoření nové instance třídy. Ve výše uvedeném příkladu jsme vytvořili novou osobu s věkem 25. + +Můžete také nastavit výchozí hodnoty pro parametry konstruktoru, pokud nejsou při vytváření objektu specifikovány. Například: + +```php +class Osoba +{ + private $vek; + + function __construct($vek = 20) + { + $this->vek = $vek; + } + + function kolikJeTiLet() + { + return $this->vek; + } +} + +$osoba = new Osoba; // pokud nepředáváme žádný argument, lze závorky vynechat +echo $osoba->kolikJeTiLet(); // Vypíše: 20 +``` + +V tomto příkladě, pokud nezadáte věk při vytváření objektu `Osoba`, bude použita výchozí hodnota 20. + +Příjemné je, že definice vlastnosti s její inicializací přes konstruktor se dá takto zkrátit a zjednodušit: + +```php +class Osoba +{ + function __construct( + private $vek = 20, + ) { + } +} +``` + +Pro úplnost, kromě konstruktorů mohou mít objekty i destruktory (metoda `__destruct`), které se zavolají před tím, než je objekt uvolněn z paměti. + + +Jmenné prostory +--------------- + +Jmenné prostory (neboli "namespaces" v angličtině) nám umožňují organizovat a seskupovat související třídy, funkce a konstanty, a zároveň se vyhýbat konfliktům v názvech. Můžete si je představit jako složky v počítači, kde každá složka obsahuje soubory, které patří k určitému projektu nebo tématu. + +Jmenné prostory jsou obzvlášť užitečné ve větších projektech nebo když používáte knihovny od třetích stran, kde by mohly vzniknout konflikty v názvech tříd. + +Představte si, že máte třídu s názvem `Auto` ve vašem projektu a chcete ji umístit do jmenného prostoru nazvaného `Doprava`. Uděláte to takto: + +```php +namespace Doprava; + +class Auto +{ + function zatrub() + { + echo 'Bip bip!'; + } +} +``` + +Pokud chcete použít třídu `Auto` v jiném souboru, musíte specifikovat, z jakého jmenného prostoru třída pochází: + +```php +$auto = new Doprava\Auto; +``` + +Pro zjednodušení můžete na začátku souboru uvést, kterou třídu z daného jmenného prostoru chcete používat, což umožňuje vytvářet instance bez nutnosti uvádět celou cestu: + +```php +use Doprava\Auto; + +$auto = new Auto; +``` + + +Dědičnost +--------- + +Dědičnost je nástrojem objektově orientovaného programování, který umožňuje vytvářet nové třídy na základě již existujících tříd, přebírat jejich vlastnosti a metody a rozšiřovat nebo předefinovat je podle potřeby. Dědičnost umožňuje zajistit kódovou znovupoužitelnost a hierarchii tříd. + +Zjednodušeně řečeno, pokud máme jednu třídu a chtěli bychom vytvořit další, od ní odvozenou, ale s několika změnami, můžeme novou třídu "zdědit" z původní třídy. + +V PHP dědičnost realizujeme pomocí klíčového slova `extends`. + +Naše třída `Osoba` uchovává informaci o věku. Můžeme mít další třídu `Student`, která rozšiřuje `Osobu` a přidává informaci o oboru studia. + +Podívejme se na příklad: + +```php +class Osoba +{ + private $vek; + + function __construct($vek) + { + $this->vek = $vek; + } + + function vypisInformace() + { + echo "Věk: {$this->vek} let\n"; + } +} + +class Student extends Osoba +{ + private $obor; + + function __construct($vek, $obor) + { + parent::__construct($vek); + $this->obor = $obor; + } + + function vypisInformace() + { + parent::vypisInformace(); + echo "Obor studia: {$this->obor} \n"; + } +} + +$student = new Student(20, 'Informatika'); +$student->vypisInformace(); +``` + +Jak tento kód funguje? + +- Použili jsme klíčové slovo `extends` k rozšíření třídy `Osoba`, což znamená, že třída `Student` zdědí všechny metody a vlastnosti z `Osoby`. + +- Klíčové slovo `parent::` nám umožňuje volat metody z nadřazené třídy. V tomto případě jsme volali konstruktor z třídy `Osoba` před přidáním vlastní funkcionality do třídy `Student`. A obdobně i metodu `vypisInformace()` předka před vypsáním informací o studentovi. + +Dědičnost je určená pro situace, kdy existuje vztah "je" mezi třídami. Například `Student` je `Osoba`. Kočka je zvíře. Dává nám možnost v případech, kdy v kódu očekáváme jeden objekt (např. "Osoba"), použít místo něj objekt zděděný (např. "Student"). + +Je důležité si uvědomit, že hlavním účelem dědičnosti **není** zabránit duplikaci kódu. Naopak, nesprávné využití dědičnosti může vést k složitému a těžko udržitelnému kódu. Pokud vztah "je" mezi třídami neexistuje, měli bychom místo dědičnosti uvažovat o kompozici. + +Všimněte si, že metody `vypisInformace()` ve třídách `Osoba` a `Student` vypisují trochu jiné informace. A můžeme doplnit další třídy (například `Zamestnanec`), které budou poskytovat další implementace této metody. Schopnost objektů různých tříd reagovat na stejnou metodu různými způsoby se nazývá polymorfismus: + +```php +$osoby = [ + new Osoba(30), + new Student(20, 'Informatika'), + new Zamestnanec(45, 'Ředitel'), +]; + +foreach ($osoby as $osoba) { + $osoba->vypisInformace(); +} +``` + + +Kompozice +--------- + +Kompozice je technika, kdy místo toho, abychom zdědili vlastnosti a metody jiné třídy, jednoduše využijeme její instanci v naší třídě. Toto nám umožňuje kombinovat funkcionality a vlastnosti více tříd bez nutnosti vytvářet složité dědičné struktury. + +Podívejme se na příklad. Máme třídu `Motor` a třídu `Auto`. Místo toho, abychom říkali "Auto je Motor", říkáme "Auto má Motor", což je typický vztah kompozice. + +```php +class Motor +{ + function zapni() + { + echo 'Motor běží.'; + } +} + +class Auto +{ + private $motor; + + function __construct() + { + $this->motor = new Motor; + } + + function start() + { + $this->motor->zapni(); + echo 'Auto je připraveno k jízdě!'; + } +} + +$auto = new Auto; +$auto->start(); +``` + +Zde `Auto` nemá všechny vlastnosti a metody `Motoru`, ale má k němu přístup prostřednictvím vlastnosti `$motor`. + +Výhodou kompozice je větší flexibilita v designu a lepší možnost úprav v budoucnosti. + + +Viditelnost +----------- + +V PHP můžete definovat "viditelnost" pro vlastnosti, metody a konstanty třídy. Viditelnost určuje, odkud můžete přistupovat k těmto prvkům. + +1. **Public:** Pokud je prvek označen jako `public`, znamená to, že k němu můžete přistupovat odkudkoli, i mimo třídu. + +2. **Protected:** Prvek s označením `protected` je přístupný pouze v rámci dané třídy a všech jejích potomků (tříd, které dědí od této třídy). + +3. **Private:** Pokud je prvek `private`, můžete k němu přistupovat pouze zevnitř třídy, ve které byl definována. + +Pokud nespecifikujete viditelnost, PHP ji automaticky nastaví na `public`. + +Podívejme se na ukázkový kód: + +```php +class UkazkaViditelnosti +{ + public $verejnaVlastnost = 'Veřejná'; + protected $chranenaVlastnost = 'Chráněná'; + private $soukromaVlastnost = 'Soukromá'; + + public function vypisVlastnosti() + { + echo $this->verejnaVlastnost; // Funguje + echo $this->chranenaVlastnost; // Funguje + echo $this->soukromaVlastnost; // Funguje + } +} + +$objekt = new UkazkaViditelnosti; +$objekt->vypisVlastnosti(); +echo $objekt->verejnaVlastnost; // Funguje +// echo $objekt->chranenaVlastnost; // Vyhodí chybu +// echo $objekt->soukromaVlastnost; // Vyhodí chybu +``` + +Pokračujeme s děděním třídy: + +```php +class PotomekTridy extends UkazkaViditelnosti +{ + public function vypisVlastnosti() + { + echo $this->verejnaVlastnost; // Funguje + echo $this->chranenaVlastnost; // Funguje + // echo $this->soukromaVlastnost; // Vyhodí chybu + } +} +``` + +V tomto případě metoda `vypisVlastnosti()` v třídě `PotomekTřídy` může přistupovat k veřejným a chráněným vlastnostem, ale nemůže přistupovat k privátním vlastnostem rodičovské třídy. + +Data a metody by měly být co nejvíce skryté a přístupné pouze prostřednictvím definovaného rozhraní. To vám umožní měnit interní implementaci třídy bez ovlivnění zbytku kódu. + + +Klíčové slovo `final` +--------------------- + +V PHP můžeme použít klíčové slovo `final`, pokud chceme zabránit třídě, metodě nebo konstantě být zděděna nebo přepsána. Když označíme třídu jako `final`, nemůže být rozšířena. Když označíme metodu jako `final`, nemůže být v potomkovské třídě přepsána. + +Vědomí, že určitá třída nebo metoda nebude dále upravována, nám umožňuje snáze provádět úpravy, aniž bychom se museli obávat možných konfliktů. Například můžeme přidat novou metodu bez obav, že by některý její potomek už stejně pojmenovanou metodu měl a došlo by ke kolizi. Nebo metodě můžeme pozměnit jejich parametry, neboť opět nehrozí, že způsobíme nesoulad s přepsanou metodou v potomkovi. + +```php +final class FinalniTrida +{ +} + +// Následující kód vyvolá chybu, protože nemůžeme zdědit od finalní třídy. +class PotomekFinalniTridy extends FinalniTrida +{ +} +``` + +V tomto příkladu pokus o zdědění od finalní třídy `FinalniTrida` vyvolá chybu. + + +Statické vlastnosti a metody +---------------------------- + +Když v PHP mluvíme o "statických" prvcích třídy, myslíme tím metody a vlastnosti, které náleží samotné třídě, a ne konkrétní instanci této třídy. To znamená, že nemusíte vytvářet instanci třídy, abyste k nim měli přístup. Místo toho je voláte nebo přistupujete k nim přímo přes název třídy. + +Mějte na paměti, že jelikož statické prvky patří k třídě, a ne k jejím instancím, nemůžete uvnitř statických metod používat pseudoproměnnou `$this`. + +Používání statických vlastností vede k [nepřehlednému kódu plnému záludností|dependency-injection:global-state], proto byste je neměli nikdy použít a ani tu nebudeme ukazovat příklad použití. Naproti tomu statické metody jsou užitečné. Příklad použití: + +```php +class Kalkulator +{ + public static function scitani($a, $b) + { + return $a + $b; + } + + public static function odecitani($a, $b) + { + return $a - $b; + } +} + +// Použití statické metody bez vytvoření instance třídy +echo Kalkulator::scitani(5, 3); // Výsledek: 8 +echo Kalkulator::odecitani(5, 3); // Výsledek: 2 +``` + +V tomto příkladu jsme vytvořili třídu `Kalkulator` s dvěma statickými metodami. Tyto metody můžeme volat přímo bez vytvoření instance třídy pomocí `::` operátoru. Statické metody jsou obzvláště užitečné pro operace, které nezávisí na stavu konkrétní instance třídy. + + +Třídní konstanty +---------------- + +V rámci tříd máme možnost definovat konstanty. Konstanty jsou hodnoty, které se nikdy nezmění během běhu programu. Na rozdíl od proměnných, hodnota konstanty zůstává stále stejná. + +```php +class Auto +{ + public const PocetKol = 4; + + public function zobrazPocetKol(): int + { + echo self::PocetKol; + } +} + +echo Auto::PocetKol; // Výstup: 4 +``` + +V tomto příkladu máme třídu `Auto` s konstantou `PocetKol`. Když chceme přistupovat ke konstantě uvnitř třídy, můžeme použít klíčové slovo `self` místo názvu třídy. + + +Objektová rozhraní +------------------ + +Objektová rozhraní fungují jako "smlouvy" pro třídy. Pokud má třída implementovat objektové rozhraní, musí obsahovat všechny metody, které toto rozhraní definuje. Je to skvělý způsob, jak zajistit, že určité třídy dodržují stejnou "smlouvu" nebo strukturu. + +V PHP se rozhraní definuje klíčovým slovem `interface`. Všechny metody definované v rozhraní jsou veřejné (`public`). Když třída implementuje rozhraní, používá klíčové slovo `implements`. + +```php +interface Zvire +{ + function vydejZvuk(); +} + +class Kocka implements Zvire +{ + public function vydejZvuk() + { + echo 'Mňau'; + } +} + +$kocka = new Kocka; +$kocka->vydejZvuk(); +``` + +Pokud třída implementuje rozhraní, ale nejsou v ní definované všechny očekávané metody, PHP vyhodí chybu. + +Třída může implementovat více rozhraní najednou, což je rozdíl oproti dědičnosti, kde může třída dědit pouze od jedné třídy: + +```php +interface Hlidac +{ + function hlidejDum(); +} + +class Pes implements Zvire, Hlidac +{ + public function vydejZvuk() + { + echo 'Haf'; + } + + public function hlidejDum() + { + echo 'Pes bedlivě střeží dům'; + } +} +``` + + +Abstraktní třídy +---------------- + +Abstraktní třídy slouží jako základní šablony pro jiné třídy, ale nemůžete vytvářet jejich instance přímo. Obsahují kombinaci kompletních metod a abstraktních metod, které nemají definovaný obsah. Třídy, které dědí z abstraktních tříd, musí poskytnout definice pro všechny abstraktní metody z předka. + +K definování abstraktní třídy používáme klíčové slovo `abstract`. + +```php +abstract class AbstraktniTrida +{ + public function obycejnaMetoda() + { + echo 'Toto je obyčejná metoda'; + } + + abstract public function abstraktniMetoda(); +} + +class Potomek extends AbstraktniTrida +{ + public function abstraktniMetoda() + { + echo 'Toto je implementace abstraktní metody'; + } +} + +$instance = new Potomek; +$instance->obycejnaMetoda(); +$instance->abstraktniMetoda(); +``` + +V tomto příkladu máme abstraktní třídu s jednou obyčejnou a jednou abstraktní metodou. Poté máme třídu `Potomek`, která dědí z `AbstraktniTrida` a poskytuje implementaci pro abstraktní metodu. + +Jak se vlastně liší rozhraní a abstraktních tříd? Abstraktní třídy mohou obsahovat jak abstraktní, tak konkrétní metody, zatímco rozhraní pouze definují, jaké metody musí třída implementovat, ale neposkytují žádnou implementaci. Třída může dědit jen od jedné abstraktní třídy, ale může implementovat libovolný počet rozhraní. + + +Typová kontrola +--------------- + +V programování je velmi důležité mít jistotu, že data, se kterými pracujeme, jsou správného typu. V PHP máme nástroje, které nám toto zajišťují. Ověřování, zda data mají správný typ, se nazývá "typová kontrola". + +Typy, na které můžeme v PHP narazit: + +1. **Základní typy**: Zahrnují `int` (celá čísla), `float` (desetinná čísla), `bool` (pravdivostní hodnoty), `string` (řetězce), `array` (pole) a `null`. +2. **Třídy**: Pokud chceme, aby hodnota byla instancí specifické třídy. +3. **Rozhraní**: Definuje soubor metod, které třída musí implementovat. Hodnota, která splňuje rozhraní, musí mít tyto metody. +4. **Smíšené typy**: Můžeme určit, že proměnná může mít více povolených typů. +5. **Void**: Tento speciální typ označuje, že funkce či metoda nevrací žádnou hodnotu. + +Pojďme si ukázat, jak upravit kód, aby zahrnoval typy: + +```php +class Osoba +{ + private int $vek; + + public function __construct(int $vek) + { + $this->vek = $vek; + } + + public function vypisVek(): void + { + echo "Této osobě je {$this->vek} let."; + } +} + +/** + * Funkce, která přijímá objekt třídy Osoba a vypíše věk osoby. + */ +function vypisVekOsoby(Osoba $osoba): void +{ + $osoba->vypisVek(); +} +``` + +Tímto způsobem jsme zajistili, že náš kód očekává a pracuje s daty správného typu, což nám pomáhá předcházet potenciálním chybám. + +Některé typy nelze v PHP přímo zapsat. V takovém případě se uvádí v phpDoc komentáři, což je standardní formát pro dokumentaci PHP kódu začínající `/**` a končící `*/`. Umožňuje přidávat popisy tříd, metod a tak dále. A také uvádět komplexní typy pomocí tzv. anotací `@var`, `@param` a `@return`. Tyto typy pak využívají nástroje pro statickou analýzu kódu, ale samotné PHP je nekontroluje. + +```php +class Seznam +{ + /** @var array<Osoba> zápis říká, že jde o pole objektů Osoba */ + private array $osoby = []; + + public function pridatOsobu(Osoba $osoba): void + { + $this->osoby[] = $osoba; + } +} +``` + + +Porovnávání a identita +---------------------- + +V PHP můžete porovnávat objekty dvěma způsoby: + +1. Porovnání hodnot `==`: Zkontroluje, zda mají objekty jsou stejné třídy a mají stejné hodnoty ve svých vlastnostech. +2. Identita `===`: Zkontroluje, zda jde o stejnou instanci objektu. + +```php +class Auto +{ + public string $znacka; + + public function __construct(string $znacka) + { + $this->znacka = $znacka; + } +} + +$auto1 = new Auto('Skoda'); +$auto2 = new Auto('Skoda'); +$auto3 = $auto1; + +var_dump($auto1 == $auto2); // true, protože mají stejnou hodnotu +var_dump($auto1 === $auto2); // false, protože nejsou stejná instance +var_dump($auto1 === $auto3); // true, protože $auto3 je stejná instance jako $auto1 +``` + + +Operátor `instanceof` +--------------------- + +Operátor `instanceof` umožňuje zjistit, zda je daný objekt instancí určité třídy, potomka této třídy, nebo zda implementuje určité rozhraní. + +Představme si, že máme třídu `Osoba` a další třídu `Student`, která je potomkem třídy `Osoba`: + +```php +class Osoba +{ + private int $vek; + + public function __construct(int $vek) + { + $this->vek = $vek; + } +} + +class Student extends Osoba +{ + private string $obor; + + public function __construct(int $vek, string $obor) + { + parent::__construct($vek); + $this->obor = $obor; + } +} + +$student = new Student(20, 'Informatika'); + +// Ověření, zda je $student instancí třídy Student +var_dump($student instanceof Student); // Výstup: bool(true) + +// Ověření, zda je $student instancí třídy Osoba (protože Student je potomek Osoba) +var_dump($student instanceof Osoba); // Výstup: bool(true) +``` + +Z výstupů je patrné, že objekt `$student` je současně považován za instanci obou tříd - `Student` i `Osoba`. + + +Fluent Interfaces +----------------- + +"Plynulé rozhraní" (anglicky "Fluent Interface") je technika v OOP, která umožňuje řetězit metody dohromady v jednom volání. Tím se často zjednoduší a zpřehlední kód. + +Klíčovým prvkem plynulého rozhraní je, že každá metoda v řetězu vrací odkaz na aktuální objekt. Toho dosáhneme tak, že na konci metody použijeme `return $this;`. Tento styl programování je často spojován s metodami zvanými "setters", které nastavují hodnoty vlastností objektu. + +Ukážeme si, jak může vypadat plynulé rozhraní na příkladu odesílání emailů: + +```php +public function sendMessage() +{ + $email = new Email; + $email->setFrom('sender@example.com') + ->setRecipient('admin@example.com') + ->setMessage('Hello, this is a message.') + ->send(); +} +``` + +V tomto příkladě metody `setFrom()`, `setRecipient()` a `setMessage()` slouží k nastavení odpovídajících hodnot (odesílatele, příjemce, obsahu zprávy). Po nastavení každé z těchto hodnot nám metody vrací aktuální objekt (`$email`), což nám umožňuje řetězit další metodu za ní. Nakonec voláme metodu `send()`, která email skutečně odesílá. + +Díky plynulým rozhraním můžeme psát kód, který je intuitivní a snadno čitelný. + + +Kopírování pomocí `clone` +------------------------- + +V PHP můžeme vytvořit kopii objektu pomocí operátoru `clone`. Tímto způsobem dostaneme novou instanci s totožným obsahem. + +Pokud potřebujeme při kopírování objektu upravit některé jeho vlastnosti, můžeme ve třídě definovat speciální metodu `__clone()`. Tato metoda se automaticky zavolá, když je objekt klonován. + +```php +class Ovce +{ + public string $jmeno; + + public function __construct(string $jmeno) + { + $this->jmeno = $jmeno; + } + + public function __clone() + { + $this->jmeno = 'Klon ' . $this->jmeno; + } +} + +$original = new Ovce('Dolly'); +echo $original->jmeno . "\n"; // Vypíše: Dolly + +$klon = clone $original; +echo $klon->jmeno . "\n"; // Vypíše: Klon Dolly +``` + +V tomto příkladu máme třídu `Ovce` s jednou vlastností `$jmeno`. Když klonujeme instanci této třídy, metoda `__clone()` se postará o to, aby název klonované ovce získal předponu "Klon". + + +Traity +------ + +Traity v PHP jsou nástrojem, který umožňuje sdílet metody, vlastnosti a konstanty mezi třídami a zabránit duplicitě kódu. Můžete si je představit jako mechanismus "kopírovat a vložit" (Ctrl-C a Ctrl-V), kdy se obsah trait "vkládá" do tříd. To vám umožní znovupoužívat kód bez nutnosti vytvářet komplikované hierarchie tříd. + +Pojďme si ukázat jednoduchý příklad, jak používat traity v PHP: + +```php +trait Troubeni +{ + public function zatrub() + { + echo 'Bip bip!'; + } +} + +class Auto +{ + use Troubeni; +} + +class Nakladak +{ + use Troubeni; +} + +$auto = new Auto; +$auto->zatrub(); // Vypíše 'Bip bip!' + +$nakladak = new Nakladak; +$nakladak->zatrub(); // Také vypíše 'Bip bip!' +``` + +V tomto příkladu máme traitu nazvanou `Troubeni`, která obsahuje jednu metodu `zatrub()`. Poté máme dvě třídy: `Auto` a `Nakladak`, které obě používají traitu `Troubeni`. Díky tomu obě třídy "mají" metodu `zatrub()`, a můžeme ji volat na objektech obou tříd. + +Traity vám umožní snadno a efektivně sdílet kód mezi třídami. Přitom nevstupují do dědičné hierarchie, tj. `$auto instanceof Troubeni` vrátí `false`. + + +Výjimky +------- + +Výjimky v OOP nám umožňují elegantně zpracovávat chyby a neočekávané situace v našem kódu. Jsou to objekty, které nesou informace o chybě nebo neobvyklé situaci. + +V PHP máme vestavěnou třídu `Exception`, která slouží jako základ pro všechny výjimky. Ta má několik metod, které nám umožňují získat více informací o výjimce, jako je zpráva o chybě, soubor a řádek, kde k chybě došlo, atd. + +Když v kódu nastane chyba, můžeme "vyhodit" výjimku pomocí klíčového slova `throw`. + +```php +function deleni(float $a, float $b): float +{ + if ($b === 0) { + throw new Exception('Dělení nulou!'); + } + return $a / $b; +} +``` + +Když funkce `deleni()` dostane jako druhý argument nulu, vyhodí výjimku s chybovou zprávou `'Dělení nulou!'`. Abychom zabránili pádu programu při vyhození výjimky, zachytíme ji v bloku `try/catch`: + +```php +try { + echo deleni(10, 0); +} catch (Exception $e) { + echo 'Výjimka zachycena: '. $e->getMessage(); +} +``` + +Kód, který může vyhodit výjimku, je zabalen do bloku `try`. Pokud je výjimka vyhozena, provádění kódu se přesune do bloku `catch`, kde můžeme výjimku zpracovat (např. vypsat chybovou zprávu). + +Po blocích `try` a `catch` můžeme přidat nepovinný blok `finally`, který se provede vždy, ať už byla výjimka vyhozena nebo ne (dokonce i v případě, že v bloku `try` nebo `catch` použijeme příkaz `return`, `break` nebo `continue`): + +```php +try { + echo deleni(10, 0); +} catch (Exception $e) { + echo 'Výjimka zachycena: '. $e->getMessage(); +} finally { + // Kód, který se provede vždy, ať už byla výjimka vyhozena nebo ne +} +``` + +Můžeme také vytvořit vlastní třídy (hierarchii) výjimek, které dědí od třídy Exception. Jako příklad si představme jednoduchou bankovní aplikaci, která umožňuje provádět vklady a výběry: + +```php +class BankovniVyjimka extends Exception {} +class NedostatekProstredkuVyjimka extends BankovniVyjimka {} +class PrekroceniLimituVyjimka extends BankovniVyjimka {} + +class BankovniUcet +{ + private int $zustatek = 0; + private int $denniLimit = 1000; + + public function vlozit(int $castka): int + { + $this->zustatek += $castka; + return $this->zustatek; + } + + public function vybrat(int $castka): int + { + if ($castka > $this->zustatek) { + throw new NedostatekProstredkuVyjimka('Na účtu není dostatek prostředků.'); + } + + if ($castka > $this->denniLimit) { + throw new PrekroceniLimituVyjimka('Byl překročen denní limit pro výběry.'); + } + + $this->zustatek -= $castka; + return $this->zustatek; + } +} +``` + +Pro jeden blok `try` lze uvést více bloků `catch`, pokud očekáváte různé typy výjimek. + +```php +$ucet = new BankovniUcet; +$ucet->vlozit(500); + +try { + $ucet->vybrat(1500); +} catch (PrekroceniLimituVyjimka $e) { + echo $e->getMessage(); +} catch (NedostatekProstredkuVyjimka $e) { + echo $e->getMessage(); +} catch (BankovniVyjimka $e) { + echo 'Vyskytla se chyba při provádění operace.'; +} +``` + +V tomto příkladu je důležité si všimnout pořadí bloků `catch`. Protože všechny výjimky dědí od `BankovniVyjimka`, pokud bychom tento blok měli první, zachytily by se v něm všechny výjimky, aniž by se kód dostal k následujícím `catch` blokům. Proto je důležité mít specifičtější výjimky (tj. ty, které dědí od jiných) v bloku `catch` výše v pořadí než jejich rodičovské výjimky. + + +Iterace +------- + +V PHP můžete procházet objekty pomocí `foreach` smyčky, podobně jako procházíte pole. Aby to fungovalo, objekt musí implementovat speciální rozhraní. + +První možností je implementovat rozhraní `Iterator`, které má metody `current()` vracející aktuální hodnotu, `key()` vracející klíč, `next()` přesouvající se na další hodnotu, `rewind()` přesouvající se na začátek a `valid()` zjišťující, zda ještě nejsme na konci. + +Druhou možností je implementovat rozhraní `IteratorAggregate`, které má jen jednu metodu `getIterator()`. Ta buď vrací zástupný objekt, který bude zajišťovat procházení, nebo může představovat generátor, což je speciální funkce, ve které se používá `yield` pro postupné vracení klíčů a hodnot: + +```php +class Osoba +{ + public function __construct( + public int $vek, + ) { + } +} + +class Seznam implements IteratorAggregate +{ + private array $osoby = []; + + public function pridatOsobu(Osoba $osoba): void + { + $this->osoby[] = $osoba; + } + + public function getIterator(): Generator + { + foreach ($this->osoby as $osoba) { + yield $osoba; + } + } +} + +$seznam = new Seznam; +$seznam->pridatOsobu(new Osoba(30)); +$seznam->pridatOsobu(new Osoba(25)); + +foreach ($seznam as $osoba) { + echo "Věk: {$osoba->vek} let \n"; +} +``` + + +Správné postupy +--------------- + +Když máte za sebou základní principy objektově orientovaného programování, je důležité se zaměřit na správné postupy v OOP. Ty vám pomohou psat kód, který je nejen funkční, ale také čitelný, srozumitelný a snadno udržovatelný. + +1) **Oddělení zájmů (Separation of Concerns)**: Každá třída by měla mít jasně definovanou odpovědnost a měla by řešit pouze jeden hlavní úkol. Pokud třída dělá příliš mnoho věcí, může být vhodné ji rozdělit na menší, specializované třídy. +2) **Zapouzdření (Encapsulation)**: Data a metody by měly být co nejvíce skryté a přístupné pouze prostřednictvím definovaného rozhraní. To vám umožní měnit interní implementaci třídy bez ovlivnění zbytku kódu. +3) **Předávání závislostí (Dependency Injection)**: Místo toho, abyste vytvořili závislosti přímo v třídě, měli byste je "injektovat" z vnějšku. Pro hlubší porozumění tomuto principu doporučujeme [kapitoly o Dependency Injection|dependency-injection:introduction]. diff --git a/nette/cs/troubleshooting.texy b/nette/cs/troubleshooting.texy index 81d492ed03..1efd8dacb5 100644 --- a/nette/cs/troubleshooting.texy +++ b/nette/cs/troubleshooting.texy @@ -11,14 +11,43 @@ Nejde mi Nette, zobrazuje se bílá stránka Chyba 500 *Server Error: We're sorry! …* ---------------------------------------- -Tuto chybovou stránku zobrazuje Nette v produkčním režimu. Pokud se vám zobrazuje na vývojářském počítači, [přepněte se do vývojářského režimu |application:bootstrap#Vývojářský vs produkční režim]. +Tuto chybovou stránku zobrazuje Nette v produkčním režimu. Pokud se vám zobrazuje na vývojářském počítači, [přepněte se do vývojářského režimu |application:bootstrapping#Vývojářský vs produkční režim] a zobrazí se vám Tracy s podrobným hlášením. -Pokud je v chybové hlášce věta `Tracy is unable to log error`, zjistěte, proč nelze chyby logovat. Uděláte to třeba tak, že se [přepnete |application:bootstrap#Vývojářský vs produkční režim] do vývojářského režimu a zavoláte `Tracy\Debugger::log('hello');` po `$configurator->enableTracy(...)`. Tracy vám sdělí, proč nemůže logovat. -Příčinou může být chebná elektrónka é třenáct z podniku Katoda Olomóc, pravděpodobněji ale [nedostatečná oprávnění |#Nastavení práv adresářů] pro zápis do adresáře `log/`. +Důvod chyby vždy vyčtete v logu v adresáři `log/`. Pokud se ale v chybové hlášce ukazuje věta `Tracy is unable to log error`, nejprve zjistěte, proč nelze chyby logovat. Uděláte to třeba tak, že se dočasně [přepnete |application:bootstrapping#Vývojářský vs produkční režim] do vývojářského režimu a necháte Tracy cokoliv zalogovat po jejím spuštění: -Pokud věta `Tracy is unable to log error` v chybové hlášce (už) není, vyčtete důvod chyby v logu v adresáři `log/`. +```php +// Bootstrap.php +$configurator->setDebugMode('23.75.345.200'); // vaše IP adresa +$configurator->enableTracy($rootDir . '/log'); +\Tracy\Debugger::log('hello'); +``` + +Tracy vám sdělí, proč nemůže logovat. Příčinou může být chebná elektrónka é třenáct z podniku Katoda Olomóc, pravděpodobněji ale [nedostatečná oprávnění |#Nastavení práv adresářů] pro zápis do adresáře `log/`. + +Jedním z nejčastějších důvodů chyby 500 je zastaralá cache. Zatímco Nette ve vývojářském režimu chytře automaticky aktualizuje cache, v produkčním režimu se zaměřuje na maximalizaci výkonu a mazání cache, po každé úpravě kódu, je na vás. Zkuste smazat `temp/cache`. + + +Chyba 404, nefunguje routování +------------------------------ +Když všechny stránky (kromě homepage) vrací chybu 404, vypadá to na problém s konfigurací serveru pro [hezká URL |#Jak nastavit server pro hezká URL]. + + +Změny v šablonách nebo konfiguraci se neprojevují +------------------------------------------------- +"Upravil jsem šablonu nebo konfiguraci, ale web stále zobrazuje starou verzi." Toto chování nastává v [produkčním režimu |application:bootstrapping#Vývojářský vs produkční režim], který z důvodu výkonu nekontroluje změny v souborech a udržuje jednou vygenerovanou cache. + +Abyste nemuseli na produkčním serveru po každé úpravě ručně mazat cache, povolte si vývojářský režim pro vaši IP adresu v souboru `Bootstrap.php`: + +```php +$this->configurator->setDebugMode('vase.ip.adresa'); +``` + + +Jak vypnout cache během vývoje? +------------------------------- +Nette je chytré a nemusíte v něm vypínat kešování. Při vývoji totiž automaticky aktualizuje cache při každé změně šablony nebo konfigurace DI kontejneru. Vývojový režimu se navíc zapíná autodetekcí, takže obvykle není potřeba konfigurovat nic, [nebo jen IP adresu |application:bootstrapping#Vývojářský vs produkční režim]. -Jedním z nejčastějších důvodů je zastaralá cache. Zatímco Nette ve vývojářském režimu chytře automaticky aktualizuje cache, v produkčním režimu se zaměřuje na maximalizaci výkonu a mazání cache, po každé úpravě kódu, je na vás. Zkuste smazat `temp/cache`. +Při ladění routeru doporučujeme vypnout cache v prohlížeči, ve které mohou být uložené například přesměrování: otevřete si Developer Tools (Ctrl+Shift+I nebo Cmd+Option+I) a v panelu Network (Síť) zaškrtněne vypnutí cache. Chyba `#[\ReturnTypeWillChange] attribute should be used` @@ -54,14 +83,19 @@ Jak změnit či ostranit z URL adresář `www`? ------------------------------------------- Adresář `www/` používaný u ukázkových projektů v Nette představuje tzv. veřejný adresář neboli document-root projektu. Jde o jediný adresář, jehož obsah je přístupný prohlížeči. A obsahuje soubor `index.php`, vstupní bod, který spouští webovou aplikaci napsanou v Nette. -Pro zprovoznění aplikace na hostingu je potřeba, abyste v konfiguraci hostingu nastavili tzv. document-root do tohoto adresáře. Nebo, pokud hosting má pro veřejný adresář předpřipravenou složku s jiným názvem (například `web`, `public_html` atd.), tak `www/` jednoduše přejmenujte. +Pro zprovoznění aplikace na hostingu je potřeba mít správně nakonfigurovaný document-root. Máte dvě možnosti: +1. V konfiguraci hostingu nastavit document-root na tento adresář +2. Pokud má hosting předpřipravenou složku (např. `public_html`), přejmenujte `www/` na tento název -Řešením **naopak není** „zbavit“ se složky `www/` pomocí pravidel v souboru `.htaccess` nebo v routeru. Pokud by hosting neumožňoval nastavit document-root do podadresáře (tj. vytvářet adresáře o úroveň výš nad veřejným adresářem), poohlédněte se po jiném. Šli byste jinak do značného bezpečnostního rizika. Bylo by to jako bydlet v bytě, kde nejdou zavřít vstupní dveře a jsou stále dokořán. +.[warning] +Nikdy se nesnažte řešit zabezpečení jen pomocí `.htaccess` nebo routeru, které by zamezovaly přístup do ostatních složek. + +Pokud by hosting neumožňoval nastavit document-root do podadresáře (tj. vytvářet adresáře o úroveň výš nad veřejným adresářem), poohlédněte se po jiném. Šli byste jinak do značného bezpečnostního rizika. Bylo by to jako bydlet v bytě, kde nejdou zavřít vstupní dveře a jsou stále dokořán. Jak nastavit server pro hezká URL? ---------------------------------- -**Apache**: je potřeba povolit a nastavit rozšíření `mod_rewrite` v souboru `.htaccess`: +**Apache**: je potřeba povolit a nastavit pravidla mod_rewrite v souboru `.htaccess`: ```apacheconf RewriteEngine On @@ -70,23 +104,51 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -Pro ovlivňování chování Apache soubory `.htaccess` je třeba mít povolenou direktivu `AllowOverride`. Toto je v Apache výchozí chování. +Pokud narazíte na problémy, ujistěte se, že: +- soubor `.htaccess` je nachází v adresáři document-root (tedy vedle souboru `index.php`) +- [Apache zpracovává soubory `.htaccess` |#Ověření že funguje .htaccess] +- [je povolený mod_rewrite |#Ověření že je povolený mod rewrite] + +Pokud nastavujete aplikaci v podsložce, možná budete muset odkomentovat řádek pro nastavení `RewriteBase` a nastavit jej na správnou složku. **nginx**: je třeba nastavit přesměrování pomocí direktivy `try_files` uvnitř bloku `location /` v konfiguraci serveru. ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args je důležité + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args JE DŮLEŽITÉ! } ``` Block `location` se pro každou filesystémovou cestu smí v bloku `server` vyskytovat jen jednou. Pokud již v konfiguraci `location /` máte, přidejte direktivu `try_files` do něj. +Ověření, že funguje `.htaccess` +------------------------------- +Nejjednodušší způsob, jak otestovat, zda Apache používá nebo ignoruje váš soubor `.htaccess`, je záměrně jej poškodit. Vložte na začátek souboru řádek `Test` a nyní, pokud obnovíte stránku v prohlížeči, měli byste vidět *Internal Server Error*. + +Pokud se vám tato chyba zobrazí, je to vlastně dobře! Znamená to, že Apache analyzuje soubor `.htaccess` a narazí na chybu, kterou jsme tam vložili. Odstraňte řádek `Test`. + +Pokud se nezobrazí *Internal Server Error*, vaše nastavení Apache ignoruje soubor `.htaccess`. Obecně jej Apache ignoruje kvůli chybějící konfigurační direktivě `AllowOverride All`. + +Pokud si jej hostujete sami, lze to snadno opravit. Otevřete soubor `httpd.conf` nebo `apache.conf` v textovém editoru, vyhledejte příslušnou část `<Directory>` a přidejte/změňte tuto direktivu: + +```apacheconf +<Directory "/var/www/htdocs"> # cesta k vašemu document root + AllowOverride All + ... +``` + +Pokud je váš web hostován jinde, podívejte se do ovládacího panelu, zda tam můžete povolit soubor `.htaccess`. Pokud ne, obraťte se na poskytovatele hostingu, aby to udělal za vás. + + +Ověření, že je povolený `mod_rewrite` +------------------------------------- +Pokud máte ověřeno, že [funguje `.htaccess` |#Ověření že funguje .htaccess], můžete ověřit, zda je povolené rozšíření mod_rewrite. Vložte na začátek souboru `.htaccess` řádek `RewriteEngine On` a obnovte stránku v prohlížeči. Pokud se zobrazí *Internal Server Error*, znamená to, že mod_rewrite povolený není. Existuje několik způsobů, jak jej povolit. Různé způsoby, jak to lze provést v různých nastaveních, najdete na Stack Overflow. + + Odkazy se generují bez `https:` ------------------------------- -Nette generuje odkazy se stejným protokolem, jaký má samotná stránka. Tedy na stránce `https://foo` generuje odkazy začínající na `https:` a obráceně. -Pokud jste za reverzním proxy serverem, který odstraňuje HTTPS (například v Dockeru), pak je třeba v konfiguraci [nastavit proxy|http:configuration#HTTP proxy], aby detekce protokolu fungovala správně. +Nette generuje odkazy se stejným protokolem, jaký má samotná stránka. Tedy na stránce `https://foo` generuje odkazy začínající na `https:` a obráceně. Pokud jste za reverzním proxy serverem, který odstraňuje HTTPS (například v Dockeru), pak je třeba v konfiguraci [nastavit proxy |http:configuration#HTTP proxy], aby detekce protokolu fungovala správně. Pokud používáte jako proxy Nginx, je potřeba mít nastaveno přesměrování např. takto: @@ -124,16 +186,14 @@ Pokud je nutné vypsat tyto znaky v situaci, kdy by se text chápal jako značka Hláška `Presenter::getContext() is deprecated` ---------------------------------------------- -Nette je zdaleka prvním PHP frameworkem, který přešel na dependency injection a vedl programátory k jeho důslednému používání, už od samotných presenterů. Pokud presenter nějakou závislost potřebuje, [přihlásí se o ni|dependency-injection:passing-dependencies]. -Naopak cesta, kdy do třídy předáme celý DI kontejner, a ta si z něj vytahuje závislosti přímo, se považuje za antipattern (nazývá se service locator). -Tento způsob se používal v Nette 0.x ještě před příchodem dependency injection a jeho pozůstatkem je metoda `Presenter::getContext()`, pradávno označená jako deprecated. +Nette je zdaleka prvním PHP frameworkem, který přešel na dependency injection a vedl programátory k jeho důslednému používání, už od samotných presenterů. Pokud presenter nějakou závislost potřebuje, [přihlásí se o ni|dependency-injection:passing-dependencies]. Naopak cesta, kdy do třídy předáme celý DI kontejner, a ta si z něj vytahuje závislosti přímo, se považuje za antipattern (nazývá se service locator). Tento způsob se používal v Nette 0.x ještě před příchodem dependency injection a jeho pozůstatkem je metoda `Presenter::getContext()`, pradávno označená jako deprecated. Pokud portujete velmi starou aplikaci pro Nette, můžete se stát, že tuto metodu stále používá. Od `nette/application` verze 3.1 se tak setkáte s upozorněním `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection`, od verze 4.0 s chybou že metoda neexistuje. Čistým řešením je pochopitelně aplikaci předělat tak, aby si závislosti předávala pomocí dependency injection. Jako workaround mužete do svého základního presenteru doplnit vlastní metodu `getContext()` a hlášku tak obejít: ```php -abstract BasePresenter extends Nette\Application\UI\Presenter +abstract class BasePresenter extends Nette\Application\UI\Presenter { private Nette\DI\Container $context; diff --git a/nette/cs/vulnerability-protection.texy b/nette/cs/vulnerability-protection.texy index fa2d349d49..b28e24cf5a 100644 --- a/nette/cs/vulnerability-protection.texy +++ b/nette/cs/vulnerability-protection.texy @@ -48,23 +48,12 @@ Nette Framework **automaticky chrání formuláře a signály v presenterech** p $form->allowCrossOrigin(); ``` -nebo v případě signálu přidejte anotaci `@crossOrigin`: +nebo v případě signálu přidejte atribut: ```php -/** - * @crossOrigin - */ -public function handleXyz() -{ -} -``` - -V PHP 8 můžete použít také atributy: - -```php -use Nette\Application\Attributes\CrossOrigin; +use Nette\Application\Attributes\Requires; -#[CrossOrigin] +#[Requires(sameOrigin: false)] public function handleXyz() { } diff --git a/nette/de/@home.texy b/nette/de/@home.texy index b8ef3cad0f..a77749139f 100644 --- a/nette/de/@home.texy +++ b/nette/de/@home.texy @@ -6,24 +6,24 @@ Nette Dokumentation <div> -Einführung ----------- +Kennenlernen +------------ - [Warum Nette verwenden? |www:10-reasons-why-nette] -- [Die Installation |Installation] -- [Erstellen Sie Ihre erste Anwendung! |quickstart:] +- [Installation |installation] +- [Schreiben wir die erste Anwendung! |quickstart:] Allgemein --------- -- [Liste der Pakete |www:packages] -- [Wartung und PHP |www:maintenance] -- [Anmerkungen zur Veröffentlichung |https://nette.org/releases] -- [Upgrade-Anleitung |migrations:en] -- [Fehlersuche |nette:Troubleshooting] +- [Paketliste |www:packages] +- [Wartung und PHP-Versionen |www:maintenance] +- [Release Notes |https://nette.org/releases] +- [Übergang zu neueren Versionen |migrations:en] +- [Fehlerbehebung |nette:troubleshooting] - [Wer erstellt Nette |https://nette.org/contributors] - [Geschichte von Nette |www:history] -- [Beteiligen Sie sich |contributing:] -- [Entwicklung des Sponsors |https://nette.org/en/donate] +- [Machen Sie mit |contributing:] +- [Unterstützen Sie die Entwicklung |https://nette.org/cs/donate] - [API-Referenz |https://api.nette.org/] </div> @@ -31,20 +31,20 @@ Allgemein <div> -Nette Anwendung ---------------- -- [Wie funktionieren die Anwendungen? |application:how-it-works] -- [Bootstrap |application:Bootstrap] -- [Präsentatoren |application:Presenters] -- [Schablonen |application:Templates] -- [Module |application:Modules] -- [Routing |application:Routing] -- [URL-Links erstellen |application:creating-links] +Anwendungen in Nette +-------------------- +- [Wie Anwendungen funktionieren |application:how-it-works] +- [application:Bootstrapping] +- [Presenter |application:presenters] +- [Templates |application:templates] +- [Verzeichnisstruktur |application:directory-structure] +- [Routing |application:routing] +- [Erstellen von URL-Links |application:creating-links] - [Interaktive Komponenten |application:components] -- [AJAX & Schnipsel |application:ajax] +- [AJAX & Snippets |application:ajax] -- [Beste Praktiken |best-practices:] +- [Anleitungen und Verfahren |best-practices:] </div> @@ -54,48 +54,49 @@ Nette Anwendung Hauptthemen ----------- - [Konfiguration |nette:configuring] -- [Injektion von Abhängigkeiten |dependency-injection:] -- [Latte: Schablonen |latte:] -- [Tracy: Debugging-Werkzeug |tracy:] +- [Dependency Injection |dependency-injection:] +- [Latte: Templates |latte:] +- [Tracy: Code-Debugging |tracy:] - [Formulare |forms:] -- [Datenbank |database:core] -- [Authentifizierung von Benutzern |security:authentication] -- [Zugriffskontrolle |security:authorization] -- [Sessionen |http:Sessions] -- [HTTP-Anfrage und -Antwort |http:] -- [Zwischenspeichern |caching:] -- [E-Mail-Versand |mail:] -- [Schema: Datenüberprüfung |schema:] +- [Datenbank |database:guide] +- [Benutzeranmeldung |security:authentication] +- [Berechtigungsprüfung |security:authorization] +- [http:Sessions] +- [HTTP-Request & -Response |http:] +- [Aktiva |assets:] +- [Cache |caching:] +- [E-Mails senden |mail:] +- [Schema: Datenvalidierung |schema:] - [PHP-Code-Generator |php-generator:] -- [Prüfer: Unit-Tests |tester:] +- [Tester: Testen |tester:] </div> <div> -Dienstprogramme ---------------- -- [Arrays |utils:Arrays] +Utilities +--------- +- [Arrays |utils:arrays] - [Dateisystem |utils:filesystem] - [Finder |utils:finder] -- [HTML-Elemente |utils:HTML Elements] -- [Bilder |utils:Images] -- [JSON |utils:JSON] +- [HTML-Elemente |utils:html-elements] +- [Bilder |utils:images] +- [utils:JSON] - [NEON |neon:] - [Passwort-Hashing |security:passwords] -- [SmartObject |utils:SmartObject] -- [PHP Typen |utils:type] -- [Zeichenketten |utils:Strings] -- [Prüfer |utils:validators] +- [PHP-Typen |utils:type] +- [Zeichenketten |utils:strings] +- [Validatoren |utils:validators] - [RobotLoader |robot-loader:] +- [SmartObject |utils:smartobject] & [StaticClass |utils:StaticClass] - [SafeStream |safe-stream:] -- [...andere |utils:] +- [...weitere |utils:] </div> </div> {{toc:no}} -{{description: Offizielle Nette-Dokumentation: beschreibt die Funktionsweise von Nette und die besten Praktiken für die Entwicklung von Webanwendungen.}} -{{maintitle: Netto Dokumentation}} +{{description: Offizielle Nette-Dokumentation: Beschreibt, wie Nette funktioniert und bewährte Praktiken für die Entwicklung von Webanwendungen.}} +{{maintitle: Nette Dokumentation}} diff --git a/nette/de/@menu-topics.texy b/nette/de/@menu-topics.texy index cc870fd15d..5249d74ede 100644 --- a/nette/de/@menu-topics.texy +++ b/nette/de/@menu-topics.texy @@ -1,21 +1,21 @@ Hauptthemen *********** - [Konfiguration |nette:configuring] -- [Nette Anwendung |application:how-it-works] -- [Injektion von Abhängigkeiten |dependency-injection:] -- [Dienstprogramme |utils:] +- [Anwendungen in Nette |application:how-it-works] +- [Dependency Injection |dependency-injection:] +- [Utilities |utils:] - [Formulare |forms:] -- [Datenbank |database:core] -- [Authentifizierung von Benutzern |security:authentication] -- [Zugriffskontrolle |security:authorization] -- [Sessionen |http:Sessions] -- [HTTP-Anfrage und -Antwort |http:] -- [Zwischenspeichern |caching:] -- [E-Mail-Versand |mail:] -- [Schema: Datenüberprüfung |schema:] +- [Datenbank |database:guide] +- [Benutzeranmeldung |security:authentication] +- [Berechtigungsprüfung |security:authorization] +- [http:Sessions] +- [HTTP-Request & -Response |http:] +- [Cache |caching:] +- [E-Mails senden |mail:] +- [Schema: Datenvalidierung |schema:] - [PHP-Code-Generator |php-generator:] -- [Latte: Vorlagen |latte:] -- [Tracy: Fehlersuche |tracy:] +- [Latte: Templates |latte:] +- [Tracy: Code-Debugging |tracy:] - [Tester: Testen |tester:] diff --git a/nette/de/@meta.texy b/nette/de/@meta.texy new file mode 100644 index 0000000000..b3b806b2ca --- /dev/null +++ b/nette/de/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokumentation}} diff --git a/nette/de/configuring.texy b/nette/de/configuring.texy index 920419675c..43c2e10075 100644 --- a/nette/de/configuring.texy +++ b/nette/de/configuring.texy @@ -1,35 +1,36 @@ -Nette konfigurieren -******************* +Konfiguration von Nette +*********************** .[perex] -Ein Überblick über alle Konfigurationsmöglichkeiten im Nette Framework. +Übersicht über alle Konfigurationsoptionen im Nette Framework. -Die Konfiguration der Nette-Komponenten erfolgt über Konfigurationsdateien, die in der Regel in [NEON |neon:format] geschrieben sind. Sie werden am besten in [Editoren |best-practices:editors-and-tools#ide-editor] bearbeitet [, die dies unterstützen |best-practices:editors-and-tools#ide-editor]. -Wenn Sie das vollständige Framework verwenden, wird die Konfiguration [beim Booten geladen |application:bootstrap#di-container-configuration]. Wenn nicht, lesen Sie [, wie Sie die Konfiguration laden |bootstrap:]. +Nette-Komponenten werden über Konfigurationsdateien konfiguriert, die üblicherweise im [NEON-Format|neon:format] geschrieben werden. Am besten bearbeitet man sie in [Editoren mit NEON-Unterstützung |best-practices:editors-and-tools#IDE-Editor]. Wenn Sie das gesamte Framework verwenden, wird die Konfiguration [beim Booten der Anwendung geladen |application:bootstrapping#Konfiguration des DI-Containers], andernfalls lesen Sie, [wie die Konfiguration geladen wird|bootstrap:]. <pre> -"application .[prism-token prism-atrule]":[application:configuration#Application]: "Anmeldung .[prism-token prism-comment]"<br> -"constants .[prism-token prism-atrule]":[application:configuration#Constants]: "Definiert PHP-Konstanten .[prism-token prism-comment]"<br> +"application .[prism-token prism-atrule]":[application:configuration#Application]: "Application .[prism-token prism-comment]"<br> +"assets .[prism-token prism-atrule]":[assets:configuration]: "Assets .[prism-token prism-comment]"<br> +"constants .[prism-token prism-atrule]":[application:configuration#Konstanten]: "Definition von PHP-Konstanten .[prism-token prism-comment]"<br> "database .[prism-token prism-atrule]":[database:configuration]: "Datenbank .[prism-token prism-comment]"<br> -"decorator .[prism-token prism-atrule]":[dependency-injection:configuration#Decorator]: "Dekorateur .[prism-token prism-comment]"<br> -"di .[prism-token prism-atrule]":[dependency-injection:configuration#DI]: "DI-Behälter .[prism-token prism-comment]"<br> -"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Extensions]: "Zusätzliche DI-Erweiterungen installieren .[prism-token prism-comment]"<br> +"decorator .[prism-token prism-atrule]":[dependency-injection:configuration#Decorator]: "Dekorator .[prism-token prism-comment]"<br> +"di .[prism-token prism-atrule]":[dependency-injection:configuration#DI]: "DI-Container .[prism-token prism-comment]"<br> +"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Erweiterungen]: "Installation weiterer DI-Erweiterungen .[prism-token prism-comment]"<br> "forms .[prism-token prism-atrule]":[forms:configuration]: "Formulare .[prism-token prism-comment]"<br> -"http .[prism-token prism-atrule]":[http:configuration#HTTP Headers]: "HTTP-Kopfzeilen .[prism-token prism-comment]"<br> -"includes .[prism-token prism-atrule]":[dependency-injection:configuration#Including files]: "Einschließlich Dateien .[prism-token prism-comment]"<br> -"latte .[prism-token prism-atrule]":[application:configuration#Latte]: "Latte .[prism-token prism-comment]"<br> -"mail .[prism-token prism-atrule]":[mail:#Configuring]: "Mailing .[prism-token prism-comment]"<br> -"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#Parameters]: "Parameter .[prism-token prism-comment]"<br> -"php .[prism-token prism-atrule]":[application:configuration#PHP]: "PHP-Konfigurationsoptionen .[prism-token prism-comment]"<br> +"http .[prism-token prism-atrule]":[http:configuration#HTTP-Header]: "HTTP-Header .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[dependency-injection:configuration#Dateien einbinden]: "Einbinden von Dateien .[prism-token prism-comment]"<br> +"latte .[prism-token prism-atrule]":[application:configuration#Latte-Templates]: "Latte-Templates .[prism-token prism-comment]"<br> +"mail .[prism-token prism-atrule]":[mail:#Konfiguration]: "Mails .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#Parameter]: "Parameter .[prism-token prism-comment]"<br> +"php .[prism-token prism-atrule]":[application:configuration#PHP]: "PHP-Konfiguration .[prism-token prism-comment]"<br> "routing .[prism-token prism-atrule]":[application:configuration#Routing]: "Routing .[prism-token prism-comment]"<br> -"search .[prism-token prism-atrule]":[dependency-injection:configuration#Search]: "Automatische Registrierung von Diensten .[prism-token prism-comment]"<br> -"security .[prism-token prism-atrule]":[security:configuration]: "Zugangskontrolle .[prism-token prism-comment]"<br> -"services .[prism-token prism-atrule]":[dependency-injection:services]: "Dienstleistungen .[prism-token prism-comment]"<br> +"search .[prism-token prism-atrule]":[dependency-injection:configuration#Suche]: "Automatische Registrierung von Diensten .[prism-token prism-comment]"<br> +"security .[prism-token prism-atrule]":[security:configuration]: "Zugriffsberechtigungen .[prism-token prism-comment]"<br> +"services .[prism-token prism-atrule]":[dependency-injection:services]: "Dienste .[prism-token prism-comment]"<br> "session .[prism-token prism-atrule]":[http:configuration#Session]: "Session .[prism-token prism-comment]"<br> -"tracy .[prism-token prism-atrule]":[tracy:configuring#Nette Framework]: "Tracy-Fehlerbehebungsprogramm .[prism-token prism-comment]" +"tracy .[prism-token prism-atrule]":[tracy:configuring#Nette Framework]: "Tracy Debugger .[prism-token prism-comment]" </pre> -Um eine Zeichenkette zu schreiben, die das Zeichen `%`, you must escape it by doubling it to `%%` enthält. .[note] +.[note] +Wenn Sie eine Zeichenkette schreiben möchten, die das Zeichen `%` enthält, müssen Sie es durch Verdoppelung zu `%%` escapen. {{leftbar: @menu-topics}} diff --git a/nette/de/glossary.texy b/nette/de/glossary.texy index 5a06dd1be4..beb74632cd 100644 --- a/nette/de/glossary.texy +++ b/nette/de/glossary.texy @@ -1,155 +1,159 @@ -Glossar der Begriffe -******************** +Glossar +******* -AJAX .[#toc-ajax] ------------------ -Asynchronous JavaScript and XML - Technologie für die Client-Server-Kommunikation über das HTTP-Protokoll, ohne dass die gesamte Seite bei jeder Anfrage neu geladen werden muss. Trotz des Akronyms wird häufig das [JSON-Format |#JSON] anstelle von XML verwendet. +AJAX +---- +Asynchronous JavaScript and XML – Technologie zum Informationsaustausch zwischen Client und Server über das HTTP-Protokoll, ohne dass die gesamte Seite bei jeder Anfrage neu geladen werden muss. Obwohl der Name vermuten lässt, dass Daten nur im XML-Format gesendet werden, wird häufig auch das Format [#JSON] verwendet. -Präsentator-Aktion .[#toc-presenter-action] -------------------------------------------- -Logischer Teil des [Presenters |#presenter], der eine Aktion ausführt, z. B. eine Produktseite anzeigen, einen Benutzer abmelden usw. Ein Presenter kann mehrere Aktionen haben. +Presenter-Aktion +---------------- +Logischer Teil des Presenters, der eine Aktion ausführt. Zum Beispiel zeigt er die Produktseite an, meldet den Benutzer ab usw. Ein Presenter kann mehrere Aktionen haben. BOM --- -Die so genannte *byte order mask* ist ein spezielles erstes Zeichen einer Datei und gibt die Bytereihenfolge in der Kodierung an. Einige Editoren fügen es automatisch ein, es ist praktisch unsichtbar, aber es verursacht Probleme mit Headern und Ausgaben, die von PHP aus gesendet werden. Sie können [Code Checker |code-checker:] zur Entfernung verwenden. +Die sogenannte *Byte Order Mark* ist ein spezielles erstes Zeichen in einer Datei, das als Indikator für die Byte-Reihenfolge in der Kodierung verwendet wird. Einige Editoren fügen es in Dateien ein. Es ist praktisch unsichtbar, verursacht aber Probleme beim Senden der Ausgabe und der Header von PHP. Zur Massenentfernung können Sie den [Code Checker|code-checker:] verwenden. -Steuergerät .[#toc-controller] ------------------------------- -Der Controller verarbeitet die Anfragen des Benutzers und ruft auf deren Grundlage eine bestimmte Anwendungslogik (d.h. ein [Modell |#model]) auf, dann ruft er die [Ansicht |#view] für die Darstellung der Daten auf. Eine Analogie zu Controllern sind [Presenter |#presenter] im Nette Framework. +Controller +---------- +Ein Controller, der Benutzeranfragen verarbeitet und auf deren Grundlage die entsprechende Anwendungslogik (d.h. das [#model]) aufruft und dann die [#view] auffordert, Daten zu rendern. Ein Äquivalent zu Controllern im Nette Framework sind [#Presenter]. -Cross-Site Scripting (XSS) .[#toc-cross-site-scripting-xss] ------------------------------------------------------------ -Cross-Site Scripting ist eine Methode zur Störung von Websites, bei der unverschlüsselte Eingaben verwendet werden. Ein Angreifer kann seinen eigenen HTML- oder JavaScript-Code einschleusen und das Aussehen der Seite verändern oder sogar sensible Informationen über die Benutzer sammeln. Der Schutz gegen XSS ist einfach: konsistentes und korrektes Escaping aller Strings und Eingaben. +Cross-Site Scripting (XSS) +-------------------------- +Cross-Site Scripting ist eine Methode zum Kompromittieren von Webseiten durch Ausnutzung unbehandelter Ausgaben. Der Angreifer kann dann eigenen Code in die Seite einschleusen und dadurch die Seite verändern oder sogar sensible Daten von Besuchern erhalten. Gegen XSS kann man sich nur durch konsequente und korrekte Behandlung aller Zeichenketten schützen. -Nette Framework verfügt über die brandneue Technologie des [Context-Aware Escaping |latte:safety-first#context-aware-escaping], mit der Sie die Cross-Site-Scripting-Risiken für immer ausschalten können. Es entschlüsselt alle Eingaben automatisch auf der Grundlage eines bestimmten Kontexts, so dass es für einen Programmierer unmöglich ist, versehentlich etwas zu vergessen. +Das Nette Framework kommt mit einer revolutionären Technologie [Context-Aware Escaping |latte:safety-first#Kontextsensitives Escaping], die Sie für immer vom Risiko des Cross-Site Scriptings befreit. Alle Ausgaben werden nämlich automatisch behandelt, und so kann es nicht passieren, dass ein Programmierer etwas vergisst. -Cross-Site Request Forgery (CSRF) .[#toc-cross-site-request-forgery-csrf] -------------------------------------------------------------------------- -Ein Cross-Site Request Forgery-Angriff besteht darin, dass der Angreifer das Opfer dazu verleitet, eine Seite zu besuchen, die im Browser des Opfers eine Anfrage an den Server ausführt, bei dem das Opfer gerade angemeldet ist, und der Server glaubt, dass die Anfrage vom Opfer absichtlich gestellt wurde. Der Server führt unter der Identität des Opfers eine bestimmte Aktion durch, ohne dass das Opfer dies bemerkt. Dabei kann es sich um das Ändern oder Löschen von Daten, das Senden einer Nachricht usw. handeln. +Cross-Site Request Forgery (CSRF) +--------------------------------- +Der Cross-Site Request Forgery Angriff besteht darin, dass der Angreifer das Opfer auf eine Seite lockt, die unauffällig im Browser des Opfers eine Anfrage an den Server ausführt, auf dem das Opfer angemeldet ist, und der Server annimmt, dass die Anfrage vom Opfer freiwillig ausgeführt wurde. Und so führt er unter der Identität des Opfers eine bestimmte Aktion aus, ohne dass es davon weiß. Es kann sich um eine Änderung oder Löschung von Daten, das Senden einer Nachricht usw. handeln. -Nette Framework **schützt Formulare und Signale in Presentern** automatisch vor dieser Art von Angriffen. Dies geschieht, indem verhindert wird, dass sie von einer anderen Domäne aus gesendet oder aufgerufen werden. +Das Nette Framework **schützt automatisch Formulare und Signale in Presentern** vor dieser Art von Angriff. Und zwar dadurch, dass es deren Senden oder Aufrufen von einer anderen Domain verhindert. -Einspritzung von Abhängigkeiten .[#toc-dependency-injection] ------------------------------------------------------------- -Dependency Injection (DI) ist ein Entwurfsmuster, das Ihnen sagt, wie Sie die Erstellung von Objekten von ihren Abhängigkeiten trennen können. Das heißt, dass eine Klasse nicht für die Erstellung oder Initialisierung ihrer Abhängigkeiten verantwortlich ist, sondern dass diese Abhängigkeiten von externem Code (der einen [DI-Container |#Dependency Injection container] enthalten kann) bereitgestellt werden. Dies hat den Vorteil, dass der Code flexibler und besser lesbar ist und die Anwendung leichter getestet werden kann, da die Abhängigkeiten leicht austauschbar und von anderen Teilen des Codes isoliert sind. Weitere Informationen finden Sie unter [Was ist Dependency Injection? |dependency-injection:introduction] +Dependency Injection +-------------------- +Dependency Injection (DI) ist ein Entwurfsmuster, das beschreibt, wie man die Erstellung von Objekten von ihren Abhängigkeiten trennt. Das heißt, dass die Klasse nicht für die Erstellung oder Initialisierung ihrer Abhängigkeiten verantwortlich ist, sondern diese Abhängigkeiten stattdessen von externem Code (dies kann auch ein [DI-Container |#Dependency Injection Container] sein) bereitgestellt werden. Der Vorteil besteht darin, dass es eine größere Code-Flexibilität, bessere Verständlichkeit und einfacheres Testen der Anwendung ermöglicht, da Abhängigkeiten leicht austauschbar und von anderen Teilen des Codes isoliert sind. Mehr im Kapitel [Was ist Dependency Injection? |dependency-injection:introduction] -Container für Dependency Injection .[#toc-dependency-injection-container] -------------------------------------------------------------------------- -Ein Dependency Injection-Container (auch DI-Container oder IoC-Container) ist ein Werkzeug, das die Erstellung und Verwaltung von Abhängigkeiten in einer Anwendung (oder einem [Dienst |#service]) übernimmt. Ein Container verfügt in der Regel über eine Konfiguration, die festlegt, welche Klassen von anderen Klassen abhängig sind, welche spezifischen Abhängigkeitsimplementierungen verwendet werden sollen und wie diese Abhängigkeiten erstellt werden sollen. Der Container erstellt dann diese Objekte und stellt sie den Klassen, die sie benötigen, zur Verfügung. Weitere Informationen finden Sie unter [Was ist ein DI-Container? |dependency-injection:container] +Dependency Injection Container +------------------------------ +Ein Dependency Injection Container (auch DI-Container oder IoC-Container) ist ein Werkzeug, das die Erstellung und Verwaltung von Abhängigkeiten in der Anwendung (oder [Diensten |#Dienst]) übernimmt. Der Container hat meistens eine Konfiguration, die definiert, welche Klassen von anderen Klassen abhängen, welche konkreten Implementierungen von Abhängigkeiten verwendet werden sollen und wie diese Abhängigkeiten erstellt werden sollen. Danach erstellt der Container diese Objekte und stellt sie den Klassen zur Verfügung, die sie benötigen. Mehr im Kapitel [Was ist ein DI-Container? |dependency-injection:container] -Entkommen .[#toc-escaping] --------------------------- -Escaping ist die Umwandlung von Zeichen mit besonderer Bedeutung in einem bestimmten Kontext in eine andere gleichwertige Sequenz. Beispiel: Wir wollen Anführungszeichen in eine in Anführungszeichen eingeschlossene Zeichenfolge schreiben. Da Anführungszeichen im Kontext der in Anführungszeichen eingeschlossenen Zeichenfolge eine besondere Bedeutung haben, muss eine andere äquivalente Sequenz verwendet werden. Die konkrete Sequenz wird durch die Kontextregeln bestimmt (z. B. `\"` in PHPs in Anführungszeichen eingeschlossenem String, `"` in HTML-Attributen usw.). +Escaping +-------- +Escaping ist die Umwandlung von Zeichen, die in einem gegebenen Kontext eine besondere Bedeutung haben, in andere entsprechende Sequenzen. Beispiel: In eine von Anführungszeichen begrenzte Zeichenkette möchten wir Anführungszeichen schreiben. Da Anführungszeichen im Kontext der Zeichenkette eine besondere Bedeutung haben und ihre einfache Notation als Ende der Zeichenkette verstanden werden würde, ist es notwendig, sie durch eine andere entsprechende Sequenz zu schreiben. Welche genau, bestimmen die Regeln des Kontexts. -Filter (ehemals Helper) .[#toc-filter-formerly-helper] ------------------------------------------------------- -Filter-Funktion. In Vorlagen ist ein [Filter |latte:syntax#filters] eine Funktion, die dabei hilft, Daten in der Ausgabeform zu verändern oder zu formatieren. In Vorlagen sind mehrere [Standardfilter |latte:filters] vordefiniert. +Filter (früher Helper) +---------------------- +In Templates versteht man unter dem Begriff [Filter |latte:syntax#Filter] üblicherweise eine Funktion, die hilft, Daten in die endgültige Form zu ändern oder neu zu formatieren. Templates verfügen über mehrere [Standardfilter |latte:filters]. -Ungültigkeitserklärung .[#toc-invalidation] -------------------------------------------- -Hinweis auf ein neu zu renderndes [Snippet |#snippet]. In anderem Zusammenhang auch Löschung eines Caches. +Invalidierung +------------- +Benachrichtigung des [Snippets |#Snippet], damit es neu gezeichnet wird. In einer anderen Bedeutung auch das Löschen des Cache-Inhalts. -JSON .[#toc-json] ------------------ -Datenaustauschformat, das auf der JavaScript-Syntax (bzw. deren Untermenge) basiert. Die genaue Spezifikation finden Sie unter www.json.org. +JSON +---- +Format für den Datenaustausch, basierend auf der JavaScript-Syntax (ist eine Teilmenge davon). Die genaue Spezifikation finden Sie auf der Seite www.json.org. -Komponente .[#toc-component] ----------------------------- -Wiederverwendbarer Teil einer Anwendung. Sie kann ein visueller Teil einer Seite sein, wie im Kapitel [application:components] beschrieben, oder der Begriff kann auch für die Klasse [Component |component-model:] stehen (eine solche Komponente muss nicht visuell sein). +Komponente +---------- +Wiederverwendbarer Teil der Anwendung. Es kann ein visueller Teil der Seite sein, wie im Kapitel [Komponenten schreiben |application:components] beschrieben, oder unter dem Begriff Komponente versteht man auch die Klasse [Component |component-model:] (eine solche Komponente muss nicht visuell sein). -Steuerelement-Zeichen .[#toc-control-characters] ------------------------------------------------- -Steuerzeichen sind unsichtbare Zeichen, die in einem Text vorkommen und eventuell Probleme verursachen können. Um sie massenhaft aus Dateien zu entfernen, können Sie [Code Checker |code-checker:] verwenden, um sie aus einer Variablen zu entfernen, die Funktion [Strings::normalize() |utils:strings#normalize]. +Steuerzeichen +------------- +Steuerzeichen sind unsichtbare Zeichen, die im Text vorkommen und möglicherweise auch Probleme verursachen können. Zur Massenentfernung aus Dateien können Sie den [Code Checker|code-checker:] verwenden und zur Entfernung aus einer Variablen die Funktion [Strings::normalize() |utils:strings#normalize]. -Ereignisse .[#toc-events] -------------------------- -Ein Ereignis ist eine erwartete Situation im Objekt, bei deren Eintreten die so genannten Handler aufgerufen werden, d.h. Callbacks, die auf das Ereignis reagieren ("sample":https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38). Das Ereignis kann z.B. das Absenden eines Formulars, die Anmeldung eines Benutzers, etc. sein. Ereignisse sind also eine Form der *Inversion of Control*. +Events (Ereignisse) +------------------- +Ein Ereignis ist eine erwartete Situation in einem Objekt, bei deren Eintreten sogenannte Handler aufgerufen werden, also Callbacks, die auf das Ereignis reagieren ("Beispiel":https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38). Ein Ereignis kann zum Beispiel das Senden eines Formulars, die Benutzeranmeldung usw. sein. Ereignisse sind somit eine Form der *Inversion of Control*. -Zum Beispiel tritt eine Benutzeranmeldung in der Methode `Nette\Security\User::login()` auf. Das Objekt `User` hat eine öffentliche Variable `$onLoggedIn`, die ein Array ist, dem jeder einen Callback hinzufügen kann. Sobald sich der Benutzer anmeldet, ruft die Methode `login()` alle Rückrufe im Array auf. Der Name einer Variablen in der Form `onXyz` ist eine Konvention, die in ganz Nette verwendet wird. +Zum Beispiel erfolgt die Benutzeranmeldung in der Methode `Nette\Security\User::login()`. Das `User`-Objekt hat eine öffentliche Variable `$onLoggedIn`, ein Array, zu dem jeder einen Callback hinzufügen kann. In dem Moment, in dem sich der Benutzer anmeldet, ruft die `login()`-Methode alle Callbacks im Array auf. Der Variablenname in der Form `onXyz` ist eine Konvention, die in ganz Nette verwendet wird. -Latte .[#toc-latte] -------------------- -Eines der innovativsten [Templating-Systeme |latte:] überhaupt. +Latte +----- +Eines der fortschrittlichsten [Template-Systeme |latte:]. -Modell .[#toc-model] --------------------- -Das Modell stellt die Daten- und Funktionsbasis der gesamten Anwendung dar. Es umfasst die gesamte Anwendungslogik (manchmal auch als "Geschäftslogik" bezeichnet). Es ist das **M** von **M**VC oder MPV. Jede Benutzeraktion (Einloggen, in den Warenkorb legen, Änderung eines Datenbankwertes) stellt eine Aktion des Modells dar. +Model +----- +Das Model ist die Daten- und insbesondere Funktionsgrundlage der gesamten Anwendung. Es enthält die gesamte Anwendungslogik (der Begriff Geschäftslogik wird ebenfalls verwendet). Es ist das **M** aus **M**VC oder MVP. Jede Benutzeraktion (Anmeldung, Hinzufügen von Waren zum Warenkorb, Änderung eines Wertes in der Datenbank) stellt eine Aktion des Modells dar. -Das Modell verwaltet seinen inneren Zustand und bietet eine öffentliche Schnittstelle. Durch Aufruf dieser Schnittstelle können wir seinen Zustand annehmen oder ändern. Das Modell weiß nichts von der Existenz einer [Ansicht |#view] oder eines [Controllers |#controller], das Modell ist völlig unabhängig von ihnen. +Das Model verwaltet seinen internen Zustand und bietet nach außen eine feste Schnittstelle an. Durch Aufrufen der Funktionen dieser Schnittstelle können wir seinen Zustand abfragen oder ändern. Das Model weiß nichts von der Existenz der [#view] oder des [Controllers |#Controller]. -Model-View-Controller .[#toc-model-view-controller] ---------------------------------------------------- -Software-Architektur, die sich bei der Entwicklung von GUI-Anwendungen herausgebildet hat, um den Code für die Ablaufsteuerung ([Controller |#controller]) vom Code der Anwendungslogik ([Model |#model]) und vom Code für die Datenwiedergabe ([View |#view]) zu trennen. Auf diese Weise ist der Code besser verständlich, erleichtert die zukünftige Entwicklung und ermöglicht es, die einzelnen Teile getrennt zu testen. +Model-View-Controller +--------------------- +Softwarearchitektur, die aus der Notwendigkeit entstand, bei Anwendungen mit grafischer Benutzeroberfläche den Bedienungscode ([#controller]) vom Code der Anwendungslogik ([#model]) und vom Code, der Daten anzeigt ([#view]), zu trennen. Dadurch wird die Anwendung übersichtlicher, erleichtert die zukünftige Entwicklung und ermöglicht das separate Testen einzelner Teile. -Modell-Ansicht-Präsentator .[#toc-model-view-presenter] -------------------------------------------------------- -Architektur basierend auf [Model-View-Controller |#Model-View-Controller]. +Model-View-Presenter +-------------------- +Architektur, basierend auf [#Model-View-Controller]. -Baustein .[#toc-module] ------------------------ -Ein [Modul |application:modules] im Nette Framework stellt eine Sammlung von Presentern und Templates dar, eventuell auch Komponenten und Modelle, die einem Presenter Daten zur Verfügung stellen. Es ist also ein bestimmter logischer Teil einer Anwendung. +Modul +----- +Ein Modul stellt einen logischen Teil der Anwendung dar. In einer typischen Anordnung handelt es sich um eine Gruppe von Presentern und Templates, die einen bestimmten Funktionsbereich abdecken. Module platzieren wir in [separaten Verzeichnissen |application:directory-structure#Presenter und Templates], wie z.B. `Front/`, `Admin/` oder `Shop/`. -Ein E-Shop kann zum Beispiel drei Module haben: -1) Produktkatalog mit Warenkorb. -2) Verwaltung für den Kunden. -3) Verwaltung für den Ladenbesitzer. +Zum Beispiel teilen wir einen E-Shop auf in: +- Frontend (`Shop/`) zum Durchsuchen von Produkten und zum Einkaufen +- Kundenbereich (`Customer/`) zur Verwaltung von Bestellungen +- Administration (`Admin/`) für den Betreiber +Technisch gesehen handelt es sich um gewöhnliche Verzeichnisse, die aber dank klarer Gliederung helfen, die Anwendung zu skalieren. Der Presenter `Admin:Product:List` wird also physisch zum Beispiel im Verzeichnis `app/Presentation/Admin/Product/List/` platziert (siehe [Presenter-Mapping |application:directory-structure#Presenter-Mapping]). -Namensraum .[#toc-namespace] ----------------------------- -Namespace ist ein Feature der Sprache PHP ab der Version 5.3 und auch einiger anderer Programmiersprachen. Es hilft, Namenskollisionen (z.B. zwei Klassen mit demselben Namen) zu vermeiden, wenn verschiedene Bibliotheken zusammen verwendet werden. Siehe [PHP-Dokumentation |https://www.php.net/manual/en/language.namespaces.rationale.php] für weitere Details. +Namespace +--------- +Namensraum, Teil der PHP-Sprache seit Version 5.3 und einiger anderer Programmiersprachen, ermöglicht die Verwendung von Klassen, die in verschiedenen Bibliotheken gleich benannt sind, ohne dass es zu Namenskollisionen kommt. Siehe [PHP-Dokumentation |https://www.php.net/manual/en/language.namespaces.rationale.php]. -Präsentator .[#toc-presenter] ------------------------------ -Presenter ist ein Objekt, das die vom Router aus der [HTTP-Anfrage |api:Nette\Application\Request] übersetzte [Anfrage |api:Nette\Application\Request] entgegennimmt und eine [Antwort |api:Nette\Application\Response] erzeugt. Die Antwort kann eine HTML-Seite, ein Bild, ein XML-Dokument, eine Datei, JSON, ein Redirect oder was auch immer Sie sich vorstellen. -Unter einem Presenter versteht man in der Regel einen Abkömmling der Klasse [api:Nette\Application\UI\Presenter]. Bei Anfragen führt er entsprechende [Aktionen |application:presenters#life-cycle-of-presenter] aus und rendert Vorlagen. +Presenter +--------- +Ein Presenter ist ein Objekt, das die [Anfrage |api:Nette\Application\Request], die vom Router aus der HTTP-Anfrage übersetzt wurde, entgegennimmt und eine [Antwort |api:Nette\Application\Response] generiert. Die Antwort kann eine HTML-Seite, ein Bild, ein XML-Dokument, eine Datei auf der Festplatte, JSON, eine Weiterleitung oder was auch immer Sie sich ausdenken sein. +Üblicherweise versteht man unter dem Begriff Presenter eine Unterklasse der Klasse [api:Nette\Application\UI\Presenter]. Entsprechend den eingehenden Anfragen startet er entsprechende [Aktionen |application:presenters#Lebenszyklus des Presenters] und rendert Templates. -Router .[#toc-router] ---------------------- -Bidirektionaler Übersetzer zwischen HTTP-Anfrage/URL und Presenter-Aktion. Bidirektional bedeutet, dass es nicht nur möglich ist, eine [Presenter-Aktion |#presenter action] aus der HTTP-Anfrage abzuleiten, sondern auch eine entsprechende URL für eine Aktion zu generieren. Mehr dazu finden Sie im Kapitel über [URL-Routing |application:routing]. +Router +------ +Bidirektionaler Übersetzer zwischen HTTP-Anfrage / URL und der Presenter-Aktion. Bidirektional bedeutet, dass aus der HTTP-Anfrage die [#Presenter-Aktion] abgeleitet werden kann, aber auch umgekehrt zur Aktion die entsprechende URL generiert werden kann. Mehr im Kapitel über [URL-Routing |application:routing]. -SameSite Cookie .[#toc-samesite-cookie] ---------------------------------------- -SameSite-Cookies bieten einen Mechanismus, um zu erkennen, was zum Laden der Seite geführt hat. Es kann drei Werte haben: `Lax`, `Strict` und `None` (letzterer erfordert HTTPS). Wenn die Anforderung der Seite direkt von der Website kommt oder der Nutzer die Seite durch direkte Eingabe in die Adressleiste oder durch Anklicken eines Lesezeichens öffnet, sendet der Browser alle Cookies an den Server (d. h. mit den Flags `Lax`, `Strict` und `None`). Wenn der Nutzer die Seite über einen Link von einer anderen Seite aus anklickt, werden Cookies mit den Kennzeichnungen `Lax` und `None` an den Server weitergeleitet. Erfolgt die Anfrage auf andere Weise, z. B. durch Übermittlung eines POST-Formulars von einer anderen Website, Laden innerhalb eines Iframe, Verwendung von JavaScript usw., werden nur Cookies mit dem Kennzeichen `None` gesendet. +SameSite-Cookie +--------------- +SameSite-Cookies bieten einen Mechanismus, um zu erkennen, was zum Laden der Seite geführt hat. Sie können drei Werte haben: `Lax`, `Strict` und `None` (dieser erfordert HTTPS). Wenn die Anfrage für die Seite direkt von der Website kommt oder der Benutzer die Seite durch direkte Eingabe in die Adresszeile oder durch Klicken auf ein Lesezeichen öffnet, sendet der Browser alle Cookies an den Server (also mit den Flags `Lax`, `Strict` und `None`). Wenn der Benutzer über einen Link von einer anderen Website auf die Website klickt, werden Cookies mit den Flags `Lax` und `None` an den Server übergeben. Wenn die Anfrage auf andere Weise entsteht, wie das Senden eines POST-Formulars von einer anderen Website, Laden innerhalb eines Iframes, mittels JavaScript usw., werden nur Cookies mit dem Flag `None` gesendet. -Dienst .[#toc-service] ----------------------- -Im Kontext von Dependency Injection bezieht sich ein Service auf ein Objekt, das von einem DI-Container erstellt und verwaltet wird. Ein Service kann leicht durch eine andere Implementierung ersetzt werden, z. B. zu Testzwecken oder um das Verhalten einer Anwendung zu ändern, ohne dass der Code, der den Service verwendet, geändert werden muss. +Dienst +------ +Im Kontext von Dependency Injection wird als Dienst ein Objekt bezeichnet, das vom DI-Container erstellt und verwaltet wird. Ein Dienst kann leicht durch eine andere Implementierung ersetzt werden, zum Beispiel zu Testzwecken oder zur Änderung des Anwendungsverhaltens, ohne dass der Code geändert werden muss, der den Dienst verwendet. -Schnipsel .[#toc-snippet] -------------------------- -Ausschnitt einer Seite, der bei einer [AJAX-Anfrage |#AJAX] separat neu gerendert werden kann. + +Snippet +------- +Ausschnitt, Teil der Seite, der separat während einer AJAX-Anfrage neu gezeichnet werden kann. + + +View +---- +Die View, also die Ansicht, ist die Schicht der Anwendung, die für die Anzeige des Ergebnisses der Anfrage verantwortlich ist. Normalerweise verwendet sie ein Template-System und weiß, wie die jeweilige Komponente oder das vom Modell erhaltene Ergebnis angezeigt werden soll. -Ansicht .[#toc-view] --------------------- -Die Ansicht ist eine Anwendungsschicht, die für das Rendern der Anfrageergebnisse verantwortlich ist. Normalerweise verwendet sie ein Templating-System und weiß, wie sie ihre Komponenten oder Ergebnisse aus dem Modell darstellen muss. diff --git a/nette/de/installation.texy b/nette/de/installation.texy index 6e419e22e7..48cd274df7 100644 --- a/nette/de/installation.texy +++ b/nette/de/installation.texy @@ -2,17 +2,17 @@ Installation von Nette ********************** .[perex] -Möchten Sie die Vorteile von Nette in Ihrem bestehenden Projekt nutzen oder planen Sie, ein neues Projekt auf der Basis von Nette zu erstellen? Diese Anleitung wird Sie Schritt für Schritt durch die Installation führen. +Möchten Sie die Vorteile von Nette in Ihrem bestehenden Projekt nutzen oder planen Sie, ein neues Projekt basierend auf Nette zu erstellen? Dieser Leitfaden führt Sie Schritt für Schritt durch die Installation. -Wie Sie Nette zu Ihrem Projekt hinzufügen .[#toc-how-to-add-nette-to-your-project] ----------------------------------------------------------------------------------- +Wie man Nette zu seinem Projekt hinzufügt +----------------------------------------- -Nette bietet eine Sammlung von nützlichen und anspruchsvollen Paketen (Bibliotheken) für PHP. Um sie in Ihr Projekt einzubinden, folgen Sie diesen Schritten: +Nette bietet eine Sammlung nützlicher und ausgereifter Pakete (Bibliotheken) für PHP. Um sie in Ihr Projekt zu integrieren, gehen Sie wie folgt vor: -1) **Set up [Composer |best-practices:composer]:** Dieses Tool ist für die einfache Installation, Aktualisierung und Verwaltung der für Ihr Projekt benötigten Bibliotheken unerlässlich. +1) **Bereiten Sie [Composer|best-practices:composer] vor:** Dieses Werkzeug ist notwendig für die einfache Installation, Aktualisierung und Verwaltung von Bibliotheken, die für Ihr Projekt benötigt werden. -2) **Wählen Sie ein [Paket |www:packages]:** Nehmen wir an, Sie müssen im Dateisystem navigieren, was der [Finder |utils:finder] aus dem Paket `nette/utils` hervorragend kann. Den Namen des Pakets finden Sie in der rechten Spalte der Dokumentation. +2) **Wählen Sie ein [Paket|www:packages] aus:** Angenommen, Sie müssen das Dateisystem durchsuchen, was der [Finder|utils:finder] aus dem Paket `nette/utils` hervorragend macht. Den Paketnamen sehen Sie in der rechten Spalte seiner Dokumentation. 3) **Installieren Sie das Paket:** Führen Sie diesen Befehl im Stammverzeichnis Ihres Projekts aus: @@ -20,48 +20,48 @@ Nette bietet eine Sammlung von nützlichen und anspruchsvollen Paketen (Biblioth composer require nette/utils ``` -Bevorzugen Sie eine grafische Oberfläche? Sehen Sie sich die [Anleitung |https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] zur Installation von Paketen in der PhpStrom-Umgebung an. +Bevorzugen Sie eine grafische Oberfläche? Sehen Sie sich die [Anleitung|https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] zur Installation von Paketen in der PhpStorm-Umgebung an. -Wie man ein neues Projekt mit Nette startet .[#toc-how-to-start-a-new-project-with-nette] ------------------------------------------------------------------------------------------ +Wie man ein neues Projekt mit Nette startet +------------------------------------------- -Wenn Sie ein völlig neues Projekt auf der Nette-Plattform erstellen möchten, empfehlen wir Ihnen, das voreingestellte [Skelett-Webprojekt |https://github.com/nette/web-project] zu verwenden: +Wenn Sie ein völlig neues Projekt auf der Nette-Plattform erstellen möchten, empfehlen wir die Verwendung des vorkonfigurierten Skeletts [Web Project|https://github.com/nette/web-project]: -1) [**Composer |best-practices:composer] einrichten.** +1) **Bereiten Sie [Composer|best-practices:composer] vor.** -2) **Öffnen Sie die Befehlszeile** und navigieren Sie zum Stammverzeichnis Ihres Webservers, z. B. `/etc/var/www`, `C:/xampp/htdocs`, `/Library/WebServer/Documents`. +2) **Öffnen Sie die Befehlszeile** und wechseln Sie in das Stammverzeichnis Ihres Webservers, z.B. `/etc/var/www`, `C:/xampp/htdocs`, `/Library/WebServer/Documents`. 3) **Erstellen Sie das Projekt** mit diesem Befehl: ```shell -composer create-project nette/web-project PROJECT_NAME +composer create-project nette/web-project PROJEKTNAME ``` -4) **Sie verwenden den Composer nicht?** Laden Sie einfach das [Webprojekt im ZIP-Format |https://github.com/nette/web-project/archive/preloaded.zip] herunter und entpacken Sie es. Aber vertrauen Sie uns, Composer ist es wert! +4) **Verwenden Sie Composer nicht?** Laden Sie einfach das [Web Project im ZIP-Format|https://github.com/nette/web-project/archive/preloaded.zip] herunter und entpacken Sie es. Aber glauben Sie uns, Composer ist es wert! -5) **Zugriffsrechte festlegen:** Auf macOS- oder Linux-Systemen müssen Sie [Schreibrechte |nette:troubleshooting#Setting directory permissions] für Verzeichnisse festlegen. +5) **Einstellung der Berechtigungen:** Auf macOS- oder Linux-Systemen setzen Sie [Schreibberechtigungen |nette:troubleshooting#Einstellung der Verzeichnisberechtigungen] für Verzeichnisse. -6) **Öffnen Sie das Projekt in einem Browser:** Geben Sie die URL `http://localhost/PROJECT_NAME/www/` ein. Sie sehen die Landing Page des Skeletts: +6) **Öffnen des Projekts im Browser:** Geben Sie die URL `http://localhost/PROJEKTNAME/www/` ein und Sie sehen die Startseite des Skeletts: -[* qs-welcome.webp .{url: http://localhost/PROJECT_NAME/www/} *] +[* qs-welcome.webp .{url: http://localhost/PROJEKTNAME/www/} *] -Herzlichen Glückwunsch! Ihre Website ist nun bereit für die Entwicklung. Sie können die Willkommensvorlage entfernen und mit der Entwicklung Ihrer Anwendung beginnen. +Herzlichen Glückwunsch! Ihre Website ist nun bereit für die Entwicklung. Sie können das Willkommens-Template entfernen und mit der Erstellung Ihrer Anwendung beginnen. -Einer der Vorteile von Nette ist, dass das Projekt sofort funktioniert, ohne dass eine Konfiguration erforderlich ist. Wenn Sie jedoch auf Probleme stoßen, sollten Sie sich die [allgemeinen Problemlösungen |nette:troubleshooting#nette-is-not-working-white-page-is-displayed] ansehen. +Einer der Vorteile von Nette ist, dass das Projekt sofort ohne Konfigurationsbedarf funktioniert. Wenn Sie jedoch auf Probleme stoßen, versuchen Sie, sich die [Lösungen für häufige Probleme |nette:troubleshooting#Nette funktioniert nicht eine weiße Seite wird angezeigt] anzusehen. .[note] -Wenn Sie mit Nette beginnen, empfehlen wir Ihnen, mit dem [Tutorial "Ihre erste Anwendung erstellen |quickstart:]" fortzufahren. +Wenn Sie mit Nette beginnen, empfehlen wir, mit dem [Tutorial „Unsere erste Anwendung schreiben“|quickstart:] fortzufahren. -Tools und Empfehlungen .[#toc-tools-and-recommendations] --------------------------------------------------------- +Werkzeuge und Empfehlungen +-------------------------- -Für eine effiziente Arbeit mit Nette empfehlen wir die folgenden Tools: +Für effizientes Arbeiten mit Nette empfehlen wir die folgenden Werkzeuge: -- [Hochwertige IDE mit Plugins für Nette |best-practices:editors-and-tools] +- [Hochwertige IDE mit Add-ons für Nette|best-practices:editors-and-tools] - Versionskontrollsystem Git -- [Komponist |best-practices:composer] +- [Composer|best-practices:composer] {{leftbar: www:@menu-common}} diff --git a/nette/de/introduction-to-object-oriented-programming.texy b/nette/de/introduction-to-object-oriented-programming.texy new file mode 100644 index 0000000000..2cb42800e2 --- /dev/null +++ b/nette/de/introduction-to-object-oriented-programming.texy @@ -0,0 +1,841 @@ +Einführung in die objektorientierte Programmierung +************************************************** + +.[perex] +Der Begriff "OOP" bezeichnet die objektorientierte Programmierung, was eine Methode ist, Code zu organisieren und zu strukturieren. OOP ermöglicht es uns, ein Programm als eine Sammlung von Objekten zu sehen, die miteinander kommunizieren, anstelle einer Abfolge von Befehlen und Funktionen. + +In OOP ist ein "Objekt" eine Einheit, die Daten und Funktionen enthält, die mit diesen Daten arbeiten. Objekte werden nach "Klassen" erstellt, die wir als Entwürfe oder Vorlagen für Objekte verstehen können. Wenn wir eine Klasse haben, können wir ihre "Instanz" erstellen, was ein konkretes Objekt ist, das nach dieser Klasse erstellt wurde. + +Lassen Sie uns zeigen, wie wir eine einfache Klasse in PHP erstellen können. Beim Definieren einer Klasse verwenden wir das Schlüsselwort "class", gefolgt vom Klassennamen und dann geschweiften Klammern, die Funktionen (sie werden "Methoden" genannt) und Klassenvariablen (sie werden "Eigenschaften" oder englisch "property" genannt) umschließen: + +```php +class Auto +{ + function hupen() + { + echo 'Bip bip!'; + } +} +``` + +In diesem Beispiel haben wir eine Klasse namens `Auto` mit einer Funktion (oder "Methode") namens `hupen` erstellt. + +Jede Klasse sollte nur eine Hauptaufgabe lösen. Wenn eine Klasse zu viele Dinge tut, kann es sinnvoll sein, sie in kleinere, spezialisierte Klassen aufzuteilen. + +Klassen speichern wir normalerweise in separaten Dateien, damit der Code organisiert ist und man sich leicht darin zurechtfindet. Der Dateiname sollte dem Klassennamen entsprechen, also wäre für die Klasse `Auto` der Dateiname `Auto.php`. + +Bei der Benennung von Klassen ist es gut, sich an die Konvention "PascalCase" zu halten, was bedeutet, dass jedes Wort im Namen mit einem Großbuchstaben beginnt und es keine Unterstriche oder andere Trennzeichen dazwischen gibt. Methoden und Eigenschaften verwenden die Konvention "camelCase", das bedeutet, dass sie mit einem Kleinbuchstaben beginnen. + +Einige Methoden in PHP haben spezielle Aufgaben und sind mit dem Präfix `__` (zwei Unterstriche) gekennzeichnet. Eine der wichtigsten speziellen Methoden ist der "Konstruktor", der als `__construct` gekennzeichnet ist. Der Konstruktor ist eine Methode, die automatisch aufgerufen wird, wenn Sie eine neue Instanz der Klasse erstellen. + +Den Konstruktor verwenden wir oft, um den Anfangszustand des Objekts festzulegen. Zum Beispiel, wenn Sie ein Objekt erstellen, das eine Person repräsentiert, können Sie den Konstruktor verwenden, um ihr Alter, ihren Namen oder andere Eigenschaften einzustellen. + +Lassen Sie uns zeigen, wie man den Konstruktor in PHP verwendet: + +```php +class Person +{ + private $alter; + + function __construct($alter) + { + $this->alter = $alter; + } + + function wieAltBistDu() + { + return $this->alter; + } +} + +$person = new Person(25); +echo $person->wieAltBistDu(); // Ausgabe: 25 +``` + +In diesem Beispiel hat die Klasse `Person` die Eigenschaft (Variable) `$alter` und weiterhin einen Konstruktor, der diese Eigenschaft setzt. Die Methode `wieAltBistDu()` ermöglicht dann den Zugriff auf das Alter der Person. + +Die Pseudovariable `$this` wird innerhalb der Klasse verwendet, für den Zugriff auf Eigenschaften und Methoden des Objekts. + +Das Schlüsselwort `new` wird verwendet, um eine neue Instanz der Klasse zu erstellen. Im obigen Beispiel haben wir eine neue Person mit dem Alter 25 erstellt. + +Sie können auch Standardwerte für Konstruktorparameter festlegen, wenn sie bei der Objekterstellung nicht angegeben werden. Zum Beispiel: + +```php +class Person +{ + private $alter; + + function __construct($alter = 20) + { + $this->alter = $alter; + } + + function wieAltBistDu() + { + return $this->alter; + } +} + +$person = new Person; // wenn wir kein Argument übergeben, können die Klammern weggelassen werden +echo $person->wieAltBistDu(); // Ausgabe: 20 +``` + +In diesem Beispiel, wenn Sie das Alter nicht angeben beim Erstellen des `Person`-Objekts, wird der Standardwert 20 verwendet. + +Angenehm ist, dass die Definition der Eigenschaft mit ihrer Initialisierung über den Konstruktor sich so verkürzen und vereinfachen lässt: + +```php +class Person +{ + function __construct( + private $alter = 20, + ) { + } +} +``` + +Der Vollständigkeit halber können Objekte neben Konstruktoren auch Destruktoren haben (Methode `__destruct`), die aufgerufen werden, bevor das Objekt aus dem Speicher freigegeben wird. + + +Namensräume +----------- + +Namensräume (oder "namespaces" auf Englisch) ermöglichen es uns, zusammengehörige Klassen, Funktionen und Konstanten zu organisieren und zu gruppieren, und gleichzeitig Namenskonflikte zu vermeiden. Sie können sie sich wie Ordner auf einem Computer vorstellen, wo jeder Ordner Dateien enthält, die zu einem bestimmten Projekt oder Thema gehören. + +Namensräume sind besonders nützlich in größeren Projekten oder wenn Sie Bibliotheken von Drittanbietern verwenden, wo Namenskonflikte bei Klassen entstehen könnten. + +Stellen Sie sich vor, Sie haben eine Klasse namens `Auto` in Ihrem Projekt und möchten sie in einem Namensraum namens `Transport` platzieren. Sie tun dies wie folgt: + +```php +namespace Transport; + +class Auto +{ + function hupen() + { + echo 'Bip bip!'; + } +} +``` + +Wenn Sie die Klasse `Auto` in einer anderen Datei verwenden möchten, müssen Sie angeben, aus welchem Namensraum die Klasse stammt: + +```php +$auto = new Transport\Auto; +``` + +Zur Vereinfachung können Sie am Anfang der Datei angeben, welche Klasse aus dem gegebenen Namensraum Sie verwenden möchten, was die Erstellung von Instanzen ermöglicht, ohne die Notwendigkeit, den gesamten Pfad anzugeben: + +```php +use Transport\Auto; + +$auto = new Auto; +``` + + +Vererbung +--------- + +Vererbung ist ein Werkzeug der objektorientierten Programmierung, das die Erstellung neuer Klassen basierend auf bereits existierenden Klassen ermöglicht, deren Eigenschaften und Methoden zu übernehmen und sie nach Bedarf zu erweitern oder neu zu definieren. Vererbung ermöglicht die Sicherstellung der Wiederverwendbarkeit von Code und einer Klassenhierarchie. + +Vereinfacht gesagt, wenn wir eine Klasse haben und eine weitere davon abgeleitete erstellen möchten, aber mit einigen Änderungen, können wir die neue Klasse von der ursprünglichen Klasse "erben". + +In PHP realisieren wir Vererbung mit dem Schlüsselwort `extends`. + +Unsere Klasse `Person` speichert Informationen über das Alter. Wir können eine weitere Klasse `Student` haben, die `Person` erweitert und Informationen über das Studienfach hinzufügt. + +Schauen wir uns ein Beispiel an: + +```php +class Person +{ + private $alter; + + function __construct($alter) + { + $this->alter = $alter; + } + + function gibInformationenAus() + { + echo "Alter: {$this->alter} Jahre\n"; + } +} + +class Student extends Person +{ + private $studienfach; + + function __construct($alter, $studienfach) + { + parent::__construct($alter); + $this->studienfach = $studienfach; + } + + function gibInformationenAus() + { + parent::gibInformationenAus(); + echo "Studienfach: {$this->studienfach} \n"; + } +} + +$student = new Student(20, 'Informatik'); +$student->gibInformationenAus(); +``` + +Wie funktioniert dieser Code? + +- Wir haben das Schlüsselwort `extends` verwendet, um die Klasse `Person` zu erweitern, was bedeutet, dass die Klasse `Student` alle Methoden und Eigenschaften von `Person` erbt. + +- Das Schlüsselwort `parent::` ermöglicht es uns, Methoden aus der übergeordneten Klasse aufzurufen. In diesem Fall haben wir den Konstruktor aus der Klasse `Person` aufgerufen, bevor wir eigene Funktionalität zur Klasse `Student` hinzugefügt haben. Und ähnlich auch die Methode `gibInformationenAus()` des Vorfahren vor der Ausgabe der Informationen über den Studenten. + +Vererbung ist für Situationen gedacht, in denen eine "ist-ein"-Beziehung zwischen Klassen besteht. Zum Beispiel ist ein `Student` eine `Person`. Eine Katze ist ein Tier. Es gibt uns die Möglichkeit, in Fällen, in denen wir im Code ein Objekt (z.B. "Person") erwarten, stattdessen ein geerbtes Objekt (z.B. "Student") zu verwenden. + +Es ist wichtig zu erkennen, dass der Hauptzweck der Vererbung **nicht** darin besteht, Code-Duplizierung zu verhindern. Im Gegenteil, die falsche Verwendung von Vererbung kann zu komplexem und schwer wartbarem Code führen. Wenn keine "ist-ein"-Beziehung zwischen den Klassen besteht, sollten wir anstelle von Vererbung Komposition in Betracht ziehen. + +Beachten Sie, dass die Methoden `gibInformationenAus()` in den Klassen `Person` und `Student` leicht unterschiedliche Informationen ausgeben. Und wir können weitere Klassen hinzufügen (zum Beispiel `Angestellter`), die weitere Implementierungen dieser Methode bereitstellen werden. Die Fähigkeit von Objekten verschiedener Klassen, auf dieselbe Methode unterschiedlich zu reagieren, wird Polymorphismus genannt: + +```php +$personen = [ + new Person(30), + new Student(20, 'Informatik'), + new Angestellter(45, 'Direktor'), +]; + +foreach ($personen as $person) { + $person->gibInformationenAus(); +} +``` + + +Komposition +----------- + +Komposition ist eine Technik, bei der anstatt die Eigenschaften und Methoden einer anderen Klasse zu erben, wir einfach ihre Instanz in unserer Klasse verwenden. Dies ermöglicht es uns, Funktionalitäten und Eigenschaften mehrerer Klassen zu kombinieren, ohne komplexe Vererbungsstrukturen erstellen zu müssen. + +Schauen wir uns ein Beispiel an. Wir haben eine Klasse `Motor` und eine Klasse `Auto`. Anstatt zu sagen "Auto ist ein Motor", sagen wir "Auto hat einen Motor", was eine typische Kompositionsbeziehung ist. + +```php +class Motor +{ + function starten() + { + echo 'Motor läuft.'; + } +} + +class Auto +{ + private $motor; + + function __construct() + { + $this->motor = new Motor; + } + + function starten() + { + $this->motor->starten(); + echo 'Auto ist fahrbereit!'; + } +} + +$auto = new Auto; +$auto->starten(); +``` + +Hier hat `Auto` nicht alle Eigenschaften und Methoden von `Motor`, aber es hat Zugriff darauf über die Eigenschaft `$motor`. + +Der Vorteil der Komposition ist eine größere Flexibilität im Design und eine bessere Möglichkeit für zukünftige Anpassungen. + + +Sichtbarkeit +------------ + +In PHP können Sie die "Sichtbarkeit" für Eigenschaften, Methoden und Konstanten einer Klasse definieren. Sichtbarkeit bestimmt, von wo aus Sie auf diese Elemente zugreifen können. + +1. **Public:** Wenn ein Element als `public` gekennzeichnet ist, bedeutet das, dass Sie von überall darauf zugreifen können, auch außerhalb der Klasse. + +2. **Protected:** Ein Element mit der Kennzeichnung `protected` ist nur innerhalb der gegebenen Klasse und all ihrer Nachkommen (Klassen, die von dieser Klasse erben) zugänglich. + +3. **Private:** Wenn ein Element `private` ist, können Sie nur innerhalb der Klasse darauf zugreifen, in der es definiert wurde. + +Wenn Sie die Sichtbarkeit nicht angeben, setzt PHP sie automatisch auf `public`. + +Schauen wir uns Beispielcode an: + +```php +class Sichtbarkeitsbeispiel +{ + public $oeffentlicheEigenschaft = 'Öffentlich'; + protected $geschuetzteEigenschaft = 'Geschützt'; + private $privateEigenschaft = 'Privat'; + + public function gibEigenschaftenAus() + { + echo $this->oeffentlicheEigenschaft; + echo $this->geschuetzteEigenschaft; + echo $this->privateEigenschaft; // Funktioniert + } +} + +$objekt = new Sichtbarkeitsbeispiel; +$objekt->gibEigenschaftenAus(); +echo $objekt->oeffentlicheEigenschaft; +// echo $objekt->geschuetzteEigenschaft; // Wirft einen Fehler +// echo $objekt->privateEigenschaft; // Wirft einen Fehler +``` + +Wir fahren mit der Vererbung der Klasse fort: + +```php +class NachkommenKlasse extends Sichtbarkeitsbeispiel +{ + public function gibEigenschaftenAus() + { + echo $this->oeffentlicheEigenschaft; // Funktioniert + echo $this->geschuetzteEigenschaft; // Funktioniert + // echo $this->privateEigenschaft; // Wirft einen Fehler + } +} +``` + +In diesem Fall kann die Methode `gibEigenschaftenAus()` in der Klasse `NachkommenKlasse` auf öffentliche und geschützte Eigenschaften zugreifen, aber nicht auf private Eigenschaften der Elternklasse. + +Daten und Methoden sollten so weit wie möglich verborgen sein und nur über eine definierte Schnittstelle zugänglich sein. Dies ermöglicht es Ihnen, die interne Implementierung der Klasse zu ändern, ohne den Rest des Codes zu beeinflussen. + + +Das Schlüsselwort `final` +------------------------- + +In PHP können wir das Schlüsselwort `final` verwenden, wenn wir verhindern wollen, dass eine Klasse, Methode oder Konstante geerbt oder überschrieben wird. Wenn wir eine Klasse als `final` kennzeichnen, kann sie nicht erweitert werden. Wenn wir eine Methode als `final` kennzeichnen, kann sie in einer Kindklasse nicht überschrieben werden. + +Das Wissen, dass eine bestimmte Klasse oder Methode nicht weiter modifiziert wird, ermöglicht es uns, Änderungen leichter durchzuführen, ohne mögliche Konflikte befürchten zu müssen. Zum Beispiel können wir eine neue Methode hinzufügen, ohne Sorge, dass einer ihrer Nachkommen bereits eine gleichnamige Methode hat und es zu einer Kollision kommen würde. Oder wir können die Parameter der Methode ändern, da wiederum keine Gefahr besteht, eine Inkonsistenz mit der überschriebenen Methode im Nachkommen zu verursachen. + +```php +final class FinaleKlasse +{ +} + +// Der folgende Code löst einen Fehler aus, weil wir nicht von einer finalen Klasse erben können. +class NachkommenDerFinalenKlasse extends FinaleKlasse +{ +} +``` + +In diesem Beispiel löst der Versuch, von der finalen Klasse `FinaleKlasse` zu erben, einen Fehler aus. + + +Statische Eigenschaften und Methoden +------------------------------------ + +Wenn wir in PHP von "statischen" Elementen einer Klasse sprechen, meinen wir Methoden und Eigenschaften, die zur Klasse selbst gehören, und nicht zu einer bestimmten Instanz dieser Klasse. Das bedeutet, dass Sie keine Instanz der Klasse erstellen müssen, um darauf zugreifen zu können. Stattdessen rufen Sie sie auf oder greifen darauf zu direkt über den Klassennamen. + +Beachten Sie, dass statische Elemente zur Klasse gehören, und nicht zu ihren Instanzen, können Sie innerhalb statischer Methoden nicht die Pseudovariable `$this` verwenden. + +Die Verwendung statischer Eigenschaften führt zu [unübersichtlichem Code voller Fallstricke|dependency-injection:global-state], deshalb sollten Sie sie niemals verwenden und wir werden hier auch kein Anwendungsbeispiel zeigen. Im Gegensatz dazu sind statische Methoden nützlich. Anwendungsbeispiel: + +```php +class Rechner +{ + public static function addition($a, $b) + { + return $a + $b; + } + + public static function subtraktion($a, $b) + { + return $a - $b; + } +} + +// Verwendung einer statischen Methode ohne Erstellung einer Klasseninstanz +echo Rechner::addition(5, 3); // Ergebnis: 8 +echo Rechner::subtraktion(5, 3); // Ergebnis: 2 +``` + +In diesem Beispiel haben wir die Klasse `Rechner` mit zwei statischen Methoden erstellt. Diese Methoden können wir direkt ohne eine Instanz der Klasse zu erstellen mit dem `::` Operator aufrufen. Statische Methoden sind besonders nützlich für Operationen, die nicht vom Zustand einer bestimmten Instanz der Klasse abhängen. + + +Klassenkonstanten +----------------- + +Innerhalb von Klassen haben wir die Möglichkeit, Konstanten zu definieren. Konstanten sind Werte, die sich während der Programmausführung niemals ändern. Im Gegensatz zu Variablen bleibt der Wert einer Konstante immer gleich. + +```php +class Auto +{ + public const AnzahlRaeder = 4; + + public function zeigeAnzahlRaeder(): int + { + echo self::AnzahlRaeder; + } +} + +echo Auto::AnzahlRaeder; // Ausgabe: 4 +``` + +In diesem Beispiel haben wir eine Klasse `Auto` mit der Konstante `AnzahlRaeder`. Wenn wir auf die Konstante innerhalb der Klasse zugreifen möchten, können wir das Schlüsselwort `self` anstelle des Klassennamens verwenden. + + +Objekt-Schnittstellen +--------------------- + +Objekt-Schnittstellen (Interfaces) funktionieren wie "Verträge" für Klassen. Wenn eine Klasse eine Objektschnittstelle implementieren soll, muss sie alle Methoden enthalten, die diese Schnittstelle definiert. Es ist eine großartige Möglichkeit sicherzustellen, dass bestimmte Klassen denselben "Vertrag" oder dieselbe Struktur einhalten. + +In PHP wird eine Schnittstelle mit dem Schlüsselwort `interface` definiert. Alle in der Schnittstelle definierten Methoden sind öffentlich (`public`). Wenn eine Klasse eine Schnittstelle implementiert, verwendet sie das Schlüsselwort `implements`. + +```php +interface Tier +{ + function gibLaut(); +} + +class Katze implements Tier +{ + public function gibLaut() + { + echo 'Miau'; + } +} + +$katze = new Katze; +$katze->gibLaut(); +``` + +Wenn eine Klasse eine Schnittstelle implementiert, aber nicht alle erwarteten Methoden darin definiert sind, wirft PHP einen Fehler. + +Eine Klasse kann mehrere Schnittstellen gleichzeitig implementieren, was ein Unterschied zur Vererbung ist, wo eine Klasse nur von einer Klasse erben kann: + +```php +interface Wachhund +{ + function bewacheHaus(); +} + +class Hund implements Tier, Wachhund +{ + public function gibLaut() + { + echo 'Wuff'; + } + + public function bewacheHaus() + { + echo 'Hund bewacht aufmerksam das Haus'; + } +} +``` + + +Abstrakte Klassen +----------------- + +Abstrakte Klassen dienen als grundlegende Vorlagen für andere Klassen, aber Sie können ihre Instanzen nicht direkt erstellen. Sie enthalten eine Kombination aus vollständigen Methoden und abstrakten Methoden, die keinen definierten Inhalt haben. Klassen, die von abstrakten Klassen erben, müssen Definitionen für alle abstrakten Methoden des Vorfahren bereitstellen. + +Zum Definieren einer abstrakten Klasse verwenden wir das Schlüsselwort `abstract`. + +```php +abstract class AbstrakteKlasse +{ + public function gewoehnlicheMethode() + { + echo 'Dies ist eine gewöhnliche Methode'; + } + + abstract public function abstrakteMethode(); +} + +class Nachkomme extends AbstrakteKlasse +{ + public function abstrakteMethode() + { + echo 'Dies ist die Implementierung der abstrakten Methode'; + } +} + +$instanz = new Nachkomme; +$instanz->gewoehnlicheMethode(); +$instanz->abstrakteMethode(); +``` + +In diesem Beispiel haben wir eine abstrakte Klasse mit einer gewöhnlichen und einer abstrakten Methode. Dann haben wir die Klasse `Nachkomme`, die von `AbstrakteKlasse` erbt und eine Implementierung für die abstrakte Methode bereitstellt. + +Wie unterscheiden sich eigentlich Schnittstellen und abstrakte Klassen? Abstrakte Klassen können sowohl abstrakte als auch konkrete Methoden enthalten, während Schnittstellen nur definieren, welche Methoden eine Klasse implementieren muss, aber keine Implementierung bereitstellen. Eine Klasse kann nur von einer abstrakten Klasse erben, aber beliebig viele Schnittstellen implementieren. + + +Typüberprüfung +-------------- + +In der Programmierung ist es sehr wichtig, sicherzustellen, dass die Daten, mit denen wir arbeiten, vom richtigen Typ sind. In PHP haben wir Werkzeuge, die uns dies gewährleisten. Die Überprüfung, ob Daten den richtigen Typ haben, wird "Typüberprüfung" (Type Hinting) genannt. + +Typen, auf die wir in PHP stoßen können: + +1. **Grundtypen**: Umfassen `int` (Ganzzahlen), `float` (Gleitkommazahlen), `bool` (Wahrheitswerte), `string` (Zeichenketten), `array` (Arrays) und `null`. +2. **Klassen**: Wenn wir möchten, dass ein Wert eine Instanz einer bestimmten Klasse ist. +3. **Interfaces**: Definiert eine Reihe von Methoden, die eine Klasse implementieren muss. Ein Wert, der die Schnittstelle erfüllt, muss diese Methoden haben. +4. **Union Types**: Wir können festlegen, dass eine Variable mehrere erlaubte Typen haben kann. +5. **Void**: Dieser spezielle Typ gibt an, dass eine Funktion oder Methode keinen Wert zurückgibt. + +Lassen Sie uns zeigen, wie man den Code anpasst, um Typen einzuschließen: + +```php +class Person +{ + private int $alter; + + public function __construct(int $alter) + { + $this->alter = $alter; + } + + public function gibAlterAus(): void + { + echo "Diese Person ist {$this->alter} Jahre alt."; + } +} + +/** + * Funktion, die ein Objekt der Klasse Person akzeptiert und das Alter der Person ausgibt. + */ +function gibAlterDerPersonAus(Person $person): void +{ + $person->gibAlterAus(); +} +``` + +Auf diese Weise haben wir sichergestellt, dass unser Code Daten des richtigen Typs erwartet und verarbeitet, was uns hilft, potenzielle Fehler zu vermeiden. + +Einige Typen können in PHP nicht direkt geschrieben werden. In diesem Fall werden sie in einem phpDoc-Kommentar angegeben, was das Standardformat für die Dokumentation von PHP-Code ist, beginnend mit `/**` und endend mit `*/`. Ermöglicht das Hinzufügen von Beschreibungen zu Klassen, Methoden usw. Und auch das Angeben komplexer Typen mithilfe sogenannter Annotationen `@var`, `@param` und `@return`. Diese Typen werden dann von Werkzeugen zur statischen Code-Analyse verwendet, aber PHP selbst überprüft sie nicht. + +```php +class Liste +{ + /** @var array<Person> die Notation besagt, dass es sich um ein Array von Person-Objekten handelt */ + private array $personen = []; + + public function fuegePersonHinzu(Person $person): void + { + $this->personen[] = $person; + } +} +``` + + +Vergleich und Identität +----------------------- + +In PHP können Sie Objekte auf zwei Arten vergleichen: + +1. Wertvergleich `==`: Überprüft, ob die Objekte derselben Klasse angehören und dieselben Werte in ihren Eigenschaften haben. +2. Identität `===`: Überprüft, ob es sich um dieselbe Objektinstanz handelt. + +```php +class Auto +{ + public string $marke; + + public function __construct(string $marke) + { + $this->marke = $marke; + } +} + +$auto1 = new Auto('Skoda'); +$auto2 = new Auto('Skoda'); +$auto3 = $auto1; + +var_dump($auto1 == $auto2); // true, weil sie denselben Wert haben +var_dump($auto1 === $auto2); // false, weil sie nicht dieselbe Instanz sind +var_dump($auto1 === $auto3); // true, weil $auto3 dieselbe Instanz wie $auto1 ist +``` + + +Der `instanceof`-Operator +------------------------- + +Der `instanceof`-Operator ermöglicht die Feststellung, ob ein gegebenes Objekt eine Instanz einer bestimmten Klasse ist, eines Nachkommen dieser Klasse, oder ob es eine bestimmte Schnittstelle implementiert. + +Stellen wir uns vor, wir haben eine Klasse `Person` und eine weitere Klasse `Student`, die ein Nachkomme der Klasse `Person` ist: + +```php +class Person +{ + private int $alter; + + public function __construct(int $alter) + { + $this->alter = $alter; + } +} + +class Student extends Person +{ + private string $studienfach; + + public function __construct(int $alter, string $studienfach) + { + parent::__construct($alter); + $this->studienfach = $studienfach; + } +} + +$student = new Student(20, 'Informatik'); + +// Überprüfung, ob $student eine Instanz der Klasse Student ist +var_dump($student instanceof Student); // Ausgabe: bool(true) + +// Überprüfung, ob $student eine Instanz der Klasse Person ist (da Student ein Nachkomme von Person ist) +var_dump($student instanceof Person); // Ausgabe: bool(true) +``` + +Aus den Ausgaben ist ersichtlich, dass das Objekt `$student` gleichzeitig als Instanz beider Klassen betrachtet wird - `Student` und `Person`. + + +Fluent Interfaces +----------------- + +"Fluent Interface" (englisch "Fluent Interface") ist eine Technik in OOP, die es ermöglicht, Methoden in einem einzigen Aufruf zu verketten. Dadurch wird der Code oft vereinfacht und übersichtlicher. + +Das Schlüsselelement einer Fluent Interface ist, dass jede Methode in der Kette eine Referenz auf das aktuelle Objekt zurückgibt. Dies erreichen wir, indem wir am Ende der Methode `return $this;` verwenden. Dieser Programmierstil wird oft mit Methoden verbunden, die "Setter" genannt werden, die die Werte von Objekteigenschaften setzen. + +Wir zeigen, wie eine Fluent Interface aussehen kann am Beispiel des E-Mail-Versands: + +```php +public function sendMessage() +{ + $email = new Email; + $email->setFrom('sender@example.com') + ->setRecipient('admin@example.com') + ->setMessage('Hello, this is a message.') + ->send(); +} +``` + +In diesem Beispiel dienen die Methoden `setFrom()`, `setRecipient()` und `setMessage()` zum Setzen der entsprechenden Werte (Absender, Empfänger, Nachrichteninhalt). Nach dem Setzen jedes dieser Werte geben uns die Methoden das aktuelle Objekt (`$email`) zurück, was es uns ermöglicht, eine weitere Methode daran zu ketten. Schließlich rufen wir die Methode `send()` auf, die die E-Mail tatsächlich sendet. + +Dank Fluent Interfaces können wir Code schreiben, der intuitiv und leicht lesbar ist. + + +Kopieren mit `clone` +-------------------- + +In PHP können wir eine Kopie eines Objekts mit dem `clone`-Operator erstellen. Auf diese Weise erhalten wir eine neue Instanz mit identischem Inhalt. + +Wenn wir beim Kopieren eines Objekts einige seiner Eigenschaften ändern müssen, können wir in der Klasse eine spezielle Methode `__clone()` definieren. Diese Methode wird automatisch aufgerufen, wenn das Objekt geklont wird. + +```php +class Schaf +{ + public string $name; + + public function __construct(string $name) + { + $this->name = $name; + } + + public function __clone() + { + $this->name = 'Klon ' . $this->name; + } +} + +$original = new Schaf('Dolly'); +echo $original->name . "\n"; // Gibt aus: Dolly + +$klon = clone $original; +echo $klon->name . "\n"; // Gibt aus: Klon Dolly +``` + +In diesem Beispiel haben wir eine Klasse `Schaf` mit einer Eigenschaft `$name`. Wenn wir eine Instanz dieser Klasse klonen, kümmert sich die Methode `__clone()` darum, dass der Name des geklonten Schafs das Präfix "Klon" erhält. + + +Traits +------ + +Traits in PHP sind ein Werkzeug, das es ermöglicht, Methoden, Eigenschaften und Konstanten zwischen Klassen zu teilen und Code-Duplizierung zu verhindern. Sie können sie sich als einen "Kopieren und Einfügen"-Mechanismus (Strg-C und Strg-V) vorstellen, bei dem der Inhalt des Traits in Klassen "eingefügt" wird. Dies ermöglicht es Ihnen, Code wiederzuverwenden, ohne komplizierte Klassenhierarchien erstellen zu müssen. + +Lassen Sie uns ein einfaches Beispiel zeigen, wie man Traits in PHP verwendet: + +```php +trait Hupen +{ + public function hupen() + { + echo 'Bip bip!'; + } +} + +class Auto +{ + use Hupen; +} + +class LKW +{ + use Hupen; +} + +$auto = new Auto; +$auto->hupen(); // Gibt 'Bip bip!' aus + +$lkw = new LKW; +$lkw->hupen(); // Gibt ebenfalls 'Bip bip!' aus +``` + +In diesem Beispiel haben wir ein Trait namens `Hupen`, das eine Methode `hupen()` enthält. Dann haben wir zwei Klassen: `Auto` und `LKW`, die beide das Trait `Hupen` verwenden. Dadurch "haben" beide Klassen die Methode `hupen()`, und wir können sie auf Objekten beider Klassen aufrufen. + +Traits ermöglichen es Ihnen, Code einfach und effizient zwischen Klassen zu teilen. Dabei treten sie nicht in die Vererbungshierarchie ein, d.h. `$auto instanceof Hupen` gibt `false` zurück. + + +Ausnahmen +--------- + +Ausnahmen (Exceptions) in OOP ermöglichen es uns, Fehler und unerwartete Situationen in unserem Code elegant zu behandeln. Es sind Objekte, die Informationen über einen Fehler oder eine ungewöhnliche Situation tragen. + +In PHP haben wir die eingebaute Klasse `Exception`, die als Basis für alle Ausnahmen dient. Sie hat mehrere Methoden, die es uns ermöglichen, mehr Informationen über die Ausnahme zu erhalten, wie die Fehlermeldung, Datei und Zeile, in der der Fehler aufgetreten ist, usw. + +Wenn im Code ein Fehler auftritt, können wir eine Ausnahme mit dem Schlüsselwort `throw` "werfen". + +```php +function teilung(float $a, float $b): float +{ + if ($b === 0.0) { // $b === 0 + throw new Exception('Division durch Null!'); + } + return $a / $b; +} +``` + +Wenn die Funktion `teilung()` als zweites Argument Null erhält, wirft sie eine Ausnahme mit der Fehlermeldung `'Division durch Null!'`. Um einen Programmabsturz beim Werfen einer Ausnahme zu verhindern, fangen wir sie in einem `try/catch`-Block ab: + +```php +try { + echo teilung(10, 0); +} catch (Exception $e) { + echo 'Ausnahme abgefangen: '. $e->getMessage(); +} +``` + +Code, der eine Ausnahme werfen kann, ist in einen `try`-Block eingeschlossen. Wenn eine Ausnahme geworfen wird, verschiebt sich die Codeausführung in den `catch`-Block, wo wir die Ausnahme behandeln können (z.B. die Fehlermeldung ausgeben). + +Nach den `try`- und `catch`-Blöcken können wir einen optionalen `finally`-Block hinzufügen, der immer ausgeführt wird, egal ob eine Ausnahme geworfen wurde oder nicht (sogar wenn wir im `try`- oder `catch`-Block die Anweisung `return`, `break` oder `continue` verwenden): + +```php +try { + echo teilung(10, 0); +} catch (Exception $e) { + echo 'Ausnahme abgefangen: '. $e->getMessage(); +} finally { + // Code, der immer ausgeführt wird, egal ob eine Ausnahme geworfen wurde oder nicht +} +``` + +Wir können auch eigene Klassen (Hierarchie) von Ausnahmen erstellen, die von der Klasse Exception erben. Als Beispiel stellen wir uns eine einfache Bankanwendung vor, die Ein- und Auszahlungen ermöglicht: + +```php +class Bankausnahme extends Exception {} +class UnzureichendeDeckungAusnahme extends Bankausnahme {} +class LimitUeberschrittenAusnahme extends Bankausnahme {} + +class Bankkonto +{ + private int $saldo = 0; + private int $tageslimit = 1000; + + public function einzahlen(int $betrag): int + { + $this->saldo += $betrag; + return $this->saldo; + } + + public function abheben(int $betrag): int + { + if ($betrag > $this->saldo) { + throw new UnzureichendeDeckungAusnahme('Nicht genügend Geld auf dem Konto.'); + } + + if ($betrag > $this->tageslimit) { + throw new LimitUeberschrittenAusnahme('Das Tageslimit für Abhebungen wurde überschritten.'); + } + + $this->saldo -= $betrag; + return $this->saldo; + } +} +``` + +Für einen `try`-Block können mehrere `catch`-Blöcke angegeben werden, wenn Sie verschiedene Arten von Ausnahmen erwarten. + +```php +$konto = new Bankkonto; +$konto->einzahlen(500); + +try { + $konto->abheben(1500); +} catch (LimitUeberschrittenAusnahme $e) { + echo $e->getMessage(); +} catch (UnzureichendeDeckungAusnahme $e) { + echo $e->getMessage(); +} catch (Bankausnahme $e) { + echo 'Beim Ausführen der Operation ist ein Fehler aufgetreten.'; +} +``` + +In diesem Beispiel ist die Reihenfolge der `catch`-Blöcke wichtig zu beachten. Da alle Ausnahmen von `Bankausnahme` erben, wenn wir diesen Block zuerst hätten, würden alle Ausnahmen darin gefangen werden, ohne dass der Code zu den folgenden `catch`-Blöcken gelangen würde. Daher ist es wichtig, spezifischere Ausnahmen (d.h. solche, die von anderen erben) im `catch`-Block weiter oben in der Reihenfolge als ihre Eltern-Ausnahmen zu haben. + + +Iteration +--------- + +In PHP können Sie Objekte mit einer `foreach`-Schleife durchlaufen, ähnlich wie Sie Arrays durchlaufen. Damit das funktioniert, muss das Objekt spezielle Schnittstellen implementieren. + +Die erste Möglichkeit ist die Implementierung der `Iterator`-Schnittstelle, die Methoden hat: `current()` gibt den aktuellen Wert zurück, `key()` gibt den Schlüssel zurück, `next()` geht zum nächsten Wert über, `rewind()` geht zum Anfang zurück und `valid()` prüft, ob wir noch nicht am Ende sind. + +Die zweite Möglichkeit ist die Implementierung der `IteratorAggregate`-Schnittstelle, die nur eine Methode `getIterator()` hat. Diese gibt entweder ein Ersatzobjekt zurück, das die Iteration sicherstellt, oder sie kann einen Generator darstellen, was eine spezielle Funktion ist, in der `yield` verwendet wird, um Schlüssel und Werte nacheinander zurückzugeben: + +```php +class Person +{ + public function __construct( + public int $alter, + ) { + } +} + +class Liste implements IteratorAggregate +{ + private array $personen = []; + + public function fuegePersonHinzu(Person $person): void + { + $this->personen[] = $person; + } + + public function getIterator(): Generator + { + foreach ($this->personen as $person) { + yield $person; + } + } +} + +$liste = new Liste; +$liste->fuegePersonHinzu(new Person(30)); +$liste->fuegePersonHinzu(new Person(25)); + +foreach ($liste as $person) { + echo "Alter: {$person->alter} Jahre \n"; +} +``` + + +Best Practices +-------------- + +Wenn Sie die grundlegenden Prinzipien von OOP hinter sich haben, ist es wichtig, sich auf die richtigen Praktiken in OOP zu konzentrieren. Diese helfen Ihnen, Code zu schreiben, der nicht nur funktional ist, sondern auch lesbar, verständlich und leicht wartbar. + +1) **Trennung der Belange (Separation of Concerns)**: Jede Klasse sollte eine klar definierte Verantwortung haben und sollte nur eine Hauptaufgabe lösen. Wenn eine Klasse zu viele Dinge tut, kann es sinnvoll sein, sie in kleinere, spezialisierte Klassen aufzuteilen. +2) **Kapselung (Encapsulation)**: Daten und Methoden sollten so weit wie möglich verborgen sein und nur über eine definierte Schnittstelle zugänglich sein. Dies ermöglicht es Ihnen, die interne Implementierung der Klasse zu ändern, ohne den Rest des Codes zu beeinflussen. +3) **Dependency Injection**: Anstatt Abhängigkeiten direkt in der Klasse zu erstellen, sollten Sie sie von außen "injizieren". Für ein tieferes Verständnis dieses Prinzips empfehlen wir die [Kapitel über Dependency Injection|dependency-injection:introduction]. diff --git a/nette/de/troubleshooting.texy b/nette/de/troubleshooting.texy index a1d2b56b0c..fae6a1c273 100644 --- a/nette/de/troubleshooting.texy +++ b/nette/de/troubleshooting.texy @@ -1,41 +1,70 @@ -Fehlersuche -*********** +Fehlerbehebung +************** -Nette funktioniert nicht, es wird eine weiße Seite angezeigt .[#toc-nette-is-not-working-white-page-is-displayed] ------------------------------------------------------------------------------------------------------------------ -- Versuchen Sie, `ini_set('display_errors', '1'); error_reporting(E_ALL);` nach `declare(strict_types=1);` in die Datei `index.php` einzufügen, um die Anzeige von Fehlern zu erzwingen. -- Wenn Sie immer noch einen weißen Bildschirm sehen, liegt wahrscheinlich ein Fehler in der Serverkonfiguration vor und Sie werden den Grund dafür im Serverprotokoll finden. Um sicherzugehen, überprüfen Sie, ob PHP überhaupt funktioniert, indem Sie versuchen, mit `echo 'test';` etwas zu drucken. -- Wenn Sie eine Fehlermeldung *Server Error sehen: Es tut uns leid! ...*, fahren Sie mit dem nächsten Abschnitt fort: +Nette funktioniert nicht, eine weiße Seite wird angezeigt +--------------------------------------------------------- +- Versuchen Sie, in die Datei `index.php` direkt nach `declare(strict_types=1);` `ini_set('display_errors', '1'); error_reporting(E_ALL);` einzufügen, damit erzwingen Sie die Anzeige von Fehlern. +- Wenn Sie immer noch einen weißen Bildschirm sehen, liegt wahrscheinlich ein Fehler in der Serverkonfiguration vor und Sie finden den Grund im Server-Log. Überprüfen Sie zur Sicherheit auch, ob PHP überhaupt funktioniert, indem Sie versuchen, etwas mit `echo 'test';` auszugeben. +- Wenn Sie den Fehler *Server Error: We're sorry! …* sehen, fahren Sie mit dem nächsten Abschnitt fort: -Fehler 500 *Serverfehler: Es tut uns leid! ...* .[#toc-error-500-server-error-we-re-sorry] ------------------------------------------------------------------------------------------- -Diese Fehlerseite wird von Nette im Produktionsmodus angezeigt. Wenn Sie sie auf einem Entwicklerrechner sehen, [wechseln Sie in den Entwicklermodus |application:bootstrap#Development vs Production Mode]. +Fehler 500 *Server Error: We're sorry! …* +----------------------------------------- +Diese Fehlerseite zeigt Nette im Produktionsmodus an. Wenn sie auf Ihrem Entwicklungsrechner angezeigt wird, [wechseln Sie in den Entwicklungsmodus |application:bootstrapping#Entwicklungs- vs. Produktionsmodus] und Tracy wird Ihnen mit einer detaillierten Meldung angezeigt. -Wenn die Fehlermeldung `Tracy is unable to log error` enthält, finden Sie heraus, warum die Fehler nicht protokolliert werden können. [Wechseln |application:bootstrap#Development vs Production Mode] Sie dazu z. B. in den Entwicklermodus und rufen Sie `Tracy\Debugger::log('hello');` nach `$configurator->enableTracy(...)` auf. Tracy wird Ihnen sagen, warum es nicht protokollieren kann. -Die Ursache ist in der Regel eine [unzureichende Berechtigung |#Setting Directory Permissions] zum Schreiben in das Verzeichnis `log/`. +Den Grund für den Fehler finden Sie immer im Log im Verzeichnis `log/`. Wenn jedoch in der Fehlermeldung der Satz `Tracy is unable to log error` erscheint, finden Sie zuerst heraus, warum Fehler nicht protokolliert werden können. Sie können dies zum Beispiel tun, indem Sie vorübergehend in den [Entwicklungsmodus wechseln |application:bootstrapping#Entwicklungs- vs. Produktionsmodus] und Tracy nach dem Start irgendetwas protokollieren lassen: -Wenn der Satz `Tracy is unable to log error` nicht (mehr) in der Fehlermeldung steht, können Sie den Grund für den Fehler im Protokoll im Verzeichnis `log/` herausfinden. +```php +// Bootstrap.php +$configurator->setDebugMode('23.75.345.200'); // Ihre IP-Adresse +$configurator->enableTracy($rootDir . '/log'); +\Tracy\Debugger::log('hello'); +``` + +Tracy wird Ihnen mitteilen, warum sie nicht protokollieren kann. Die Ursache können wahrscheinlich [unzureichende Berechtigungen |#Einstellung der Verzeichnisberechtigungen] für das Schreiben in das Verzeichnis `log/` sein. + +Einer der häufigsten Gründe für einen 500-Fehler ist ein veralteter Cache. Während Nette im Entwicklungsmodus den Cache intelligent automatisch aktualisiert, konzentriert es sich im Produktionsmodus auf die Maximierung der Leistung, und das Löschen des Caches nach jeder Codeänderung liegt bei Ihnen. Versuchen Sie, `temp/cache` zu löschen. + + +Fehler 404, Routing funktioniert nicht +-------------------------------------- +Wenn alle Seiten (außer der Homepage) einen 404-Fehler zurückgeben, sieht es nach einem Problem mit der Serverkonfiguration für [schöne URLs (Pretty URLs) |#Wie konfiguriert man den Server für schöne URLs Pretty URLs] aus. + + +Änderungen in Templates oder Konfiguration werden nicht angezeigt +----------------------------------------------------------------- +"Ich habe das Template oder die Konfiguration geändert, aber die Website zeigt immer noch die alte Version an." Dieses Verhalten tritt im [Produktionsmodus |application:bootstrapping#Entwicklungs- vs. Produktionsmodus] auf, der aus Leistungsgründen keine Änderungen in Dateien überprüft und einen einmal generierten Cache beibehält. + +Damit Sie nicht auf dem Produktionsserver nach jeder Änderung den Cache manuell löschen müssen, aktivieren Sie den Entwicklungsmodus für Ihre IP-Adresse in der Datei `Bootstrap.php`: + +```php +$this->configurator->setDebugMode('ihre.ip.adresse'); +``` + + +Wie schaltet man den Cache während der Entwicklung aus? +------------------------------------------------------- +Nette ist intelligent, und Sie müssen das Caching darin nicht deaktivieren. Während der Entwicklung aktualisiert es nämlich den Cache automatisch bei jeder Änderung des Templates oder der DI-Container-Konfiguration. Der Entwicklungsmodus wird außerdem durch Autodetektion aktiviert, daher ist es normalerweise nicht notwendig, etwas zu konfigurieren, [oder nur die IP-Adresse |application:bootstrapping#Entwicklungs- vs. Produktionsmodus]. -Einer der häufigsten Gründe ist ein veralteter Cache. Während Nette im Entwicklungsmodus den Cache automatisch aktualisiert, konzentriert es sich im Produktionsmodus auf die Maximierung der Leistung, und es liegt an Ihnen, den Cache nach jeder Codeänderung zu leeren. Versuchen Sie, `temp/cache` zu löschen. +Beim Debuggen des Routers empfehlen wir, den Browser-Cache zu deaktivieren, in dem beispielsweise Weiterleitungen gespeichert sein können: öffnen Sie die Developer Tools (Strg+Shift+I oder Cmd+Option+I) und aktivieren Sie im Panel Network (Netzwerk) die Deaktivierung des Caches. -Fehler `#[\ReturnTypeWillChange] attribute should be used` .[#toc-error-returntypewillchange-attribute-should-be-used] ----------------------------------------------------------------------------------------------------------------------- -Dieser Fehler tritt auf, wenn Sie PHP auf Version 8.1 aktualisiert haben, aber Nette verwenden, das damit nicht kompatibel ist. Die Lösung besteht also darin, Nette mit `composer update` auf eine neuere Version zu aktualisieren. Nette unterstützt PHP 8.1 seit Version 3.0. Wenn Sie eine ältere Version verwenden (Sie können dies unter `composer.json` herausfinden), [aktualisieren Sie Nette |migrations:en] oder bleiben Sie bei PHP 8.0. +Fehler `#[\ReturnTypeWillChange] attribute should be used` +---------------------------------------------------------- +Dieser Fehler tritt auf, wenn Sie PHP auf Version 8.1 aktualisiert haben, aber eine Nette-Version verwenden, die nicht damit kompatibel ist. Die Lösung besteht also darin, Nette auf eine neuere Version mit `composer update` zu aktualisieren. Nette unterstützt PHP 8.1 ab Version 3.0. Wenn Sie eine ältere Version verwenden (Sie finden es heraus, indem Sie in `composer.json` nachsehen), [führen Sie ein Upgrade von Nette durch |migrations:en] oder bleiben Sie bei PHP 8.0. -Verzeichnisberechtigungen einstellen .[#toc-setting-directory-permissions] --------------------------------------------------------------------------- -Wenn Sie auf macOS oder Linux (oder einem anderen Unix-basierten System) entwickeln, müssen Sie Schreibrechte für den Webserver konfigurieren. Angenommen, Ihre Anwendung befindet sich in dem Standardverzeichnis `/var/www/html` (Fedora, CentOS, RHEL) +Einstellung der Verzeichnisberechtigungen +----------------------------------------- +Wenn Sie auf macOS oder Linux entwickeln (oder auf einem anderen Unix-basierten System), müssen Sie Schreibberechtigungen für den Webserver festlegen. Angenommen, Ihre Anwendung befindet sich im Standardverzeichnis `/var/www/html` (Fedora, CentOS, RHEL). ```shell cd /var/www/html/MY_PROJECT chmod -R a+rw temp log ``` -Auf einigen Linux-Systemen (Fedora, CentOS, ...) kann SELinux standardmäßig aktiviert sein. Möglicherweise müssen Sie die SELinux-Richtlinien aktualisieren oder die Pfade der Verzeichnisse `temp` und `log` mit dem richtigen SELinux-Sicherheitskontext versehen. Die Verzeichnisse `temp` und `log` sollten auf den Kontext `httpd_sys_rw_content_t` gesetzt werden; für den Rest der Anwendung - hauptsächlich den Ordner `app` - reicht der Kontext `httpd_sys_content_t` aus. Führen Sie das Programm auf dem Server als root aus: +Auf einigen Linux-Systemen (Fedora, CentOS, ...) ist SELinux standardmäßig aktiviert. Sie müssen die SELinux-Policies entsprechend anpassen und den korrekten SELinux-Sicherheitskontext für die Ordner `temp` und `log` festlegen. Für `temp` und `log` setzen wir den Kontexttyp `httpd_sys_rw_content_t`, für den Rest der Anwendung (und insbesondere für den Ordner `app`) wird `httpd_sys_content_t` ausreichen. Führen Sie auf dem Server aus: ```shell semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/log(/.*)?' @@ -43,25 +72,30 @@ semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/temp(/.*) restorecon -Rv /var/www/html/MY_PROJECT/ ``` -Als nächstes muss der SELinux-Boolesche Wert `httpd_can_network_connect_db` aktiviert werden, damit Nette über das Netzwerk eine Verbindung zur Datenbank herstellen kann. Standardmäßig ist er deaktiviert. Der Befehl `setsebool` kann zur Durchführung dieser Aufgabe verwendet werden, und wenn die Option `-P` angegeben wird, bleibt diese Einstellung über Neustarts hinweg bestehen. +Weiterhin muss der SELinux-Boolean `httpd_can_network_connect_db` aktiviert werden, der standardmäßig deaktiviert ist und der Nette erlaubt, sich über das Netzwerk mit der Datenbank zu verbinden. Wir verwenden dazu den Befehl `setsebool` und mit der Option `-P` machen wir die Änderung dauerhaft, d.h. nach einem Server-Neustart erwartet uns keine böse Überraschung: ```shell setsebool -P httpd_can_network_connect_db on ``` -Wie kann man das Verzeichnis `www` aus der URL ändern oder entfernen? .[#toc-how-to-change-or-remove-www-directory-from-url] ----------------------------------------------------------------------------------------------------------------------------- -Das Verzeichnis `www/`, das in den Beispielprojekten in Nette verwendet wird, ist das so genannte öffentliche Verzeichnis oder Dokument-Wurzelverzeichnis des Projekts. Es ist das einzige Verzeichnis, dessen Inhalt für den Browser zugänglich ist. Und es enthält die Datei `index.php`, den Einstiegspunkt, der eine in Nette geschriebene Webanwendung startet. +Wie ändert oder entfernt man das Verzeichnis `www` aus der URL? +--------------------------------------------------------------- +Das Verzeichnis `www/`, das in den Beispielprojekten in Nette verwendet wird, stellt das sogenannte öffentliche Verzeichnis oder das Document-Root des Projekts dar. Es ist das einzige Verzeichnis, dessen Inhalt für den Browser zugänglich ist. Und es enthält die Datei `index.php`, den Einstiegspunkt, der die in Nette geschriebene Webanwendung startet. -Um die Anwendung auf dem Hosting laufen zu lassen, müssen Sie das document-root in der Hosting-Konfiguration auf dieses Verzeichnis setzen. Oder, wenn das Hosting einen vorgefertigten Ordner für das öffentliche Verzeichnis mit einem anderen Namen hat (zum Beispiel `web`, `public_html` usw.), benennen Sie einfach `www/` um. +Um die Anwendung auf einem Hosting zum Laufen zu bringen, muss das Document-Root korrekt konfiguriert sein. Sie haben zwei Möglichkeiten: +1. Stellen Sie in der Hosting-Konfiguration das Document-Root auf dieses Verzeichnis ein. +2. Wenn das Hosting einen vorbereiteten Ordner hat (z.B. `public_html`), benennen Sie `www/` in diesen Namen um. -Die Lösung **ist** nicht, den Ordner `www/` durch Regeln in der Datei `.htaccess` oder im Router "loszuwerden". Wenn das Hosting Ihnen nicht erlaubt, document-root auf ein Unterverzeichnis zu setzen (d.h. Verzeichnisse eine Ebene über dem öffentlichen Verzeichnis zu erstellen), suchen Sie nach einem anderen. Andernfalls würden Sie ein erhebliches Sicherheitsrisiko eingehen. Das wäre so, als würden Sie in einer Wohnung leben, deren Eingangstür Sie nicht schließen können und die immer weit offen steht. +.[warning] +Versuchen Sie niemals, die Sicherheit nur mit `.htaccess` oder dem Router zu lösen, die den Zugriff auf andere Ordner verhindern würden. +Wenn das Hosting es nicht erlaubt, das Document-Root auf ein Unterverzeichnis zu setzen (d.h. Verzeichnisse eine Ebene über dem öffentlichen Verzeichnis zu erstellen), suchen Sie nach einem anderen. Sie würden sonst ein erhebliches Sicherheitsrisiko eingehen. Es wäre, als würde man in einer Wohnung leben, in der die Eingangstür nicht geschlossen werden kann und immer offen steht. -Wie konfiguriert man einen Server für schöne URLs? .[#toc-how-to-configure-a-server-for-nice-urls] --------------------------------------------------------------------------------------------------- -**Apache**: Die Erweiterung mod_rewrite muss erlaubt und in einer Datei `.htaccess` konfiguriert sein. + +Wie konfiguriert man den Server für schöne URLs (Pretty URLs)? +-------------------------------------------------------------- +**Apache**: Es ist notwendig, die mod_rewrite-Regeln in der Datei `.htaccess` zu aktivieren und einzustellen: ```apacheconf RewriteEngine On @@ -70,25 +104,53 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -Um die Apache-Konfiguration mit .htaccess-Dateien zu ändern, muss die AllowOverride-Direktive aktiviert werden. Dies ist das Standardverhalten von Apache. +Wenn Sie auf Probleme stoßen, stellen Sie sicher, dass: +- die Datei `.htaccess` sich im Document-Root-Verzeichnis befindet (also neben der Datei `index.php`) +- [Apache `.htaccess`-Dateien verarbeitet |#Überprüfung ob .htaccess funktioniert] +- [mod_rewrite aktiviert ist |#Überprüfung ob mod rewrite aktiviert ist] + +Wenn Sie die Anwendung in einem Unterordner einrichten, müssen Sie möglicherweise die Zeile für die Einstellung `RewriteBase` auskommentieren und sie auf den richtigen Ordner setzen. -**nginx**: Die Richtlinie `try_files` sollte in der Serverkonfiguration verwendet werden: +**nginx**: Es ist notwendig, die Weiterleitung mit der Direktive `try_files` innerhalb des `location /` Blocks in der Serverkonfiguration einzustellen. ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args is important + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args IST WICHTIG! } ``` -Der Block `location` muss genau einmal für jeden Dateisystempfad im Block `server` definiert werden. Wenn Sie bereits einen `location /` -Block in Ihrer Konfiguration haben, fügen Sie die Direktive `try_files` in den bestehenden Block ein. +Der `location`-Block für jeden Dateisystempfad darf im `server`-Block nur einmal vorkommen. Wenn Sie bereits `location /` in der Konfiguration haben, fügen Sie die Direktive `try_files` hinzu. + + +Überprüfung, ob `.htaccess` funktioniert +---------------------------------------- +Der einfachste Weg zu testen, ob Apache Ihre Datei `.htaccess` verwendet oder ignoriert, ist, sie absichtlich zu beschädigen. Fügen Sie am Anfang der Datei die Zeile `Test` ein und nun, wenn Sie die Seite im Browser aktualisieren, sollten Sie *Internal Server Error* sehen. + +Wenn Ihnen dieser Fehler angezeigt wird, ist das eigentlich gut! Es bedeutet, dass Apache die Datei `.htaccess` analysiert und auf den Fehler stößt, den wir dort eingefügt haben. Entfernen Sie die Zeile `Test`. + +Wenn kein *Internal Server Error* angezeigt wird, ignoriert Ihre Apache-Einstellung die Datei `.htaccess`. Im Allgemeinen ignoriert Apache sie aufgrund der fehlenden Konfigurationsdirektive `AllowOverride All`. + +Wenn Sie es selbst hosten, lässt sich das leicht beheben. Öffnen Sie die Datei `httpd.conf` oder `apache.conf` in einem Texteditor, suchen Sie den entsprechenden `<Directory>`-Abschnitt und fügen/ändern Sie diese Direktive: + +```apacheconf +<Directory "/var/www/htdocs"> # Pfad zu Ihrem Document Root + AllowOverride All + ... +``` + +Wenn Ihre Website woanders gehostet wird, schauen Sie im Control Panel nach, ob Sie dort die Datei `.htaccess` aktivieren können. Wenn nicht, wenden Sie sich an Ihren Hosting-Anbieter, damit er das für Sie erledigt. + + +Überprüfung, ob `mod_rewrite` aktiviert ist +------------------------------------------- +Wenn Sie überprüft haben, dass [.htaccess funktioniert |#Überprüfung ob .htaccess funktioniert], können Sie überprüfen, ob die Erweiterung mod_rewrite aktiviert ist. Fügen Sie am Anfang der Datei `.htaccess` die Zeile `RewriteEngine On` ein und aktualisieren Sie die Seite im Browser. Wenn *Internal Server Error* angezeigt wird, bedeutet das, dass mod_rewrite nicht aktiviert ist. Es gibt mehrere Möglichkeiten, es zu aktivieren. Verschiedene Möglichkeiten, wie dies in verschiedenen Setups durchgeführt werden kann, finden Sie auf Stack Overflow. -Links werden ohne `https:` erzeugt. .[#toc-links-are-generated-without-https] ------------------------------------------------------------------------------ -Nette generiert Links mit demselben Protokoll, das die aktuelle Seite verwendet. Auf der Seite `https://foo` beginnen, und andersherum. -Wenn Sie sich hinter einem HTTPS-Stripping-Reverse-Proxy befinden (z. B. in Docker), müssen Sie in der Konfiguration [einen Proxy ein |http:configuration#HTTP proxy] richten, damit die Protokollerkennung richtig funktioniert. +Links werden ohne `https:` generiert +------------------------------------ +Nette generiert Links mit demselben Protokoll wie die Seite selbst. Also generiert es auf der Seite `https://foo` Links, die mit `https:` beginnen und umgekehrt. Wenn Sie sich hinter einem Reverse-Proxy-Server befinden, der HTTPS entfernt (zum Beispiel in Docker), dann müssen Sie in der Konfiguration [den Proxy einstellen |http:configuration#HTTP-Proxy], damit die Protokollerkennung korrekt funktioniert. -Wenn Sie Nginx als Proxy verwenden, müssen Sie die Umleitung wie folgt einrichten: +Wenn Sie Nginx als Proxy verwenden, muss die Weiterleitung z.B. so eingestellt sein: ``` location / { @@ -96,11 +158,11 @@ location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Port $server_port; - proxy_pass http://IP-aplikace:80; # IP oder Hostname des Servers/Containers, auf dem die Anwendung läuft + proxy_pass http://IP-der-Anwendung:80; # IP oder Hostname des Servers/Containers, auf dem die Anwendung läuft } ``` -Als Nächstes müssen Sie den IP-Proxy und ggf. den IP-Bereich Ihres lokalen Netzwerks angeben, in dem Sie die Infrastruktur betreiben: +Weiterhin muss in der Konfiguration die IP des Proxys und gegebenenfalls der IP-Bereich Ihres lokalen Netzwerks angegeben werden, wo Sie die Infrastruktur betreiben: ```neon http: @@ -108,36 +170,34 @@ http: ``` -Verwendung der Zeichen { } in JavaScript .[#toc-use-of-characters-in-javascript] --------------------------------------------------------------------------------- -Die Zeichen `{` and `}` werden zum Schreiben von Latte-Tags verwendet. Alles (außer Leerzeichen und Anführungszeichen) nach dem `{` character is considered a tag. If you need to print character `{` (oft in JavaScript), können Sie ein Leerzeichen (oder ein anderes leeres Zeichen) direkt nach `{` setzen. Auf diese Weise vermeiden Sie, dass es als Tag interpretiert wird. +Verwendung von Zeichen { } in JavaScript +---------------------------------------- +Die Zeichen `{` und `}` werden für die Notation von Latte-Tags verwendet. Als Tag wird alles betrachtet, was auf das Zeichen `{` folgt, mit Ausnahme eines Leerzeichens und eines Anführungszeichens. Wenn Sie also direkt das Zeichen `{` ausgeben müssen (oft zum Beispiel in JavaScript), können Sie nach dem Zeichen `{` ein Leerzeichen (oder ein anderes Leerzeichen) setzen. Dadurch vermeiden Sie die Interpretation als Tag. -Wenn es notwendig ist, diese Zeichen in einer Situation auszugeben, in der sie als Tag interpretiert werden würden, können Sie spezielle Tags verwenden, um diese Zeichen auszugeben - `{l}` für `{` and `{r}` für `}`. +Wenn es notwendig ist, diese Zeichen in einer Situation auszugeben, in der der Text als Tag verstanden würde, können Sie spezielle Tags zur Ausgabe dieser Zeichen verwenden - `{l}` für `{` und `{r}` für `}`. ``` -{is tag} -{ is not tag } -{l}is not tag{r} +{ist ein Tag} +{ ist kein Tag } +{l}ist kein Tag{r} ``` -Hinweis `Presenter::getContext() is deprecated` .[#toc-notice-presenter-getcontext-is-deprecated] -------------------------------------------------------------------------------------------------- +Meldung `Presenter::getContext() is deprecated` +----------------------------------------------- -Nette ist bei weitem das erste PHP-Framework, das auf Dependency Injection umgestiegen ist und Programmierer dazu gebracht hat, es konsequent zu nutzen, angefangen bei den Moderatoren. Wenn ein Präsentator eine Abhängigkeit benötigt, [wird er danach fragen |dependency-injection:passing-dependencies]. -Im Gegensatz dazu wird die Art und Weise, wie wir den gesamten DI-Container an eine Klasse übergeben und diese die Abhängigkeiten direkt daraus zieht, als ein Antipattern betrachtet (es wird Service-Locator genannt). -Diese Methode wurde in Nette 0.x vor dem Aufkommen von Dependency Injection verwendet, und ihr Überbleibsel ist die Methode `Presenter::getContext()`, die schon vor langer Zeit als veraltet markiert wurde. +Nette ist bei weitem das erste PHP-Framework, das auf Dependency Injection umgestiegen ist und Programmierer zu dessen konsequenter Nutzung angeleitet hat, schon bei den Presentern selbst. Wenn ein Presenter eine Abhängigkeit benötigt, [fordert er sie an|dependency-injection:passing-dependencies]. Umgekehrt gilt der Weg, bei dem wir der Klasse den gesamten DI-Container übergeben und sie sich daraus direkt Abhängigkeiten holt, als Antipattern (wird Service Locator genannt). Diese Methode wurde in Nette 0.x noch vor dem Aufkommen von Dependency Injection verwendet und ihr Überbleibsel ist die Methode `Presenter::getContext()`, die vor langer Zeit als deprecated markiert wurde. -Wenn Sie eine sehr alte Nette-Anwendung portieren, werden Sie feststellen, dass sie diese Methode immer noch verwendet. Seit Version 3.1 von `nette/application` werden Sie also auf die Warnung `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection` stoßen, seit Version 4.0 auf den Fehler, dass die Methode nicht existiert. +Wenn Sie eine sehr alte Anwendung für Nette portieren, kann es vorkommen, dass sie diese Methode immer noch verwendet. Ab `nette/application` Version 3.1 werden Sie auf die Warnung `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection` stoßen, ab Version 4.0 auf den Fehler, dass die Methode nicht existiert. -Die saubere Lösung besteht natürlich darin, die Anwendung so umzugestalten, dass Abhängigkeiten mittels Dependency Injection übergeben werden. Als Workaround können Sie Ihre eigene Methode `getContext()` zu Ihrem Basispräsenter hinzufügen und die Meldung umgehen: +Die saubere Lösung besteht natürlich darin, die Anwendung umzugestalten, sodass sie Abhängigkeiten mittels Dependency Injection übergibt. Als Workaround können Sie Ihrem Basis-Presenter eine eigene `getContext()`-Methode hinzufügen und die Meldung so umgehen: ```php -abstract BasePresenter extends Nette\Application\UI\Presenter +abstract class BasePresenter extends Nette\Application\UI\Presenter { private Nette\DI\Container $context; - public function injectContext(Nette\DI\Container $context) + public function injectContext(Nette\DI\Container $context): void { $this->context = $context; } diff --git a/nette/de/vulnerability-protection.texy b/nette/de/vulnerability-protection.texy new file mode 100644 index 0000000000..7603805293 --- /dev/null +++ b/nette/de/vulnerability-protection.texy @@ -0,0 +1,99 @@ +Schutz vor Schwachstellen +************************* + +.[perex] +Immer wieder wird eine Sicherheitslücke auf einer weiteren wichtigen Website gemeldet oder die Lücke wird ausgenutzt. Das ist unangenehm. Wenn Ihnen die Sicherheit Ihrer Webanwendungen wichtig ist, ist das Nette Framework mit Sicherheit die beste Wahl. + + +Cross-Site Scripting (XSS) +========================== + +Cross-Site Scripting ist eine Methode zur Kompromittierung von Webseiten durch Ausnutzung unbehandelter Ausgaben. Der Angreifer kann dann eigenen Code in die Seite einschleusen und dadurch die Seite verändern oder sogar sensible Daten von Besuchern erhalten. Gegen XSS kann man sich nur durch konsequente und korrekte Behandlung aller Zeichenketten schützen. Dabei genügt es, wenn Ihr Programmierer dies nur ein einziges Mal vergisst, und die gesamte Website kann auf einen Schlag kompromittiert sein. + +Ein Beispiel für einen Angriff kann das Unterschieben einer modifizierten URL an einen Benutzer sein, mit der wir unseren Code in die Seite injizieren. Wenn die Anwendung die Ausgaben nicht ordnungsgemäß behandelt, führt sie das Skript im Browser des Benutzers aus. Auf diese Weise können wir ihm beispielsweise die Identität stehlen. + +``` +https://example.com/?search=<script>alert('Erfolgreicher XSS-Angriff.');</script> +``` + +Das Nette Framework kommt mit einer revolutionären Technologie [Context-Aware Escaping |latte:safety-first#Kontextsensitives Escaping], die Sie für immer vom Risiko des Cross-Site Scriptings befreit. Alle Ausgaben werden nämlich automatisch behandelt, und so kann es nicht passieren, dass ein Programmierer etwas vergisst. Beispiel? Ein Programmierer erstellt dieses Template: + +```latte +<p onclick="alert({$message})">{$message}</p> + +<script> +document.title = {$message}; +</script> +``` + +Die Notation `{$message}` bedeutet die Ausgabe einer Variablen. In anderen Frameworks ist es notwendig, jede Ausgabe explizit zu behandeln und sogar an jeder Stelle anders. Im Nette Framework muss nichts behandelt werden, alles wird automatisch, korrekt und konsequent erledigt. Wenn wir in die Variable `$message = 'Breite 1/2"'` einsetzen, generiert das Framework HTML-Code: + +```latte +<p onclick="alert("Breite 1\/2\"")">Breite 1/2"</p> + +<script> +document.title = "Breite 1\/2\""; +</script> +``` + + +Cross-Site Request Forgery (CSRF) +================================= + +Der Cross-Site Request Forgery Angriff besteht darin, dass der Angreifer das Opfer auf eine Seite lockt, die unauffällig im Browser des Opfers eine Anfrage an den Server ausführt, auf dem das Opfer angemeldet ist, und der Server annimmt, dass die Anfrage vom Opfer freiwillig ausgeführt wurde. Und so führt er unter der Identität des Opfers eine bestimmte Aktion aus, ohne dass es davon weiß. Es kann sich um eine Änderung oder Löschung von Daten, das Senden einer Nachricht usw. handeln. + +Das Nette Framework **schützt automatisch Formulare und Signale in Presentern** vor dieser Art von Angriff. Und zwar dadurch, dass es deren Senden oder Aufrufen von einer anderen Domain verhindert. Wenn Sie den Schutz deaktivieren möchten, verwenden Sie bei Formularen: + +```php +$form->allowCrossOrigin(); +``` + +oder fügen Sie im Falle eines Signals die Annotation `@crossOrigin` hinzu: + +```php +/** + * @crossOrigin + */ +public function handleXyz() +{ +} +``` + +In Nette Application 3.2 können Sie auch Attribute verwenden: + +```php +use Nette\Application\Attributes\Requires; + +#[Requires(sameOrigin: false)] +public function handleXyz() +{ +} +``` + + +URL-Angriff, Steuerzeichen, ungültiges UTF-8 +============================================ + +Verschiedene Begriffe im Zusammenhang mit dem Versuch eines Angreifers, Ihrer Webanwendung *schädliche* Eingaben unterzuschieben. Die Folgen können sehr vielfältig sein, von der Beschädigung von XML-Ausgaben (z.B. nicht funktionierende RSS-Feeds) bis hin zum Erhalt sensibler Informationen aus der Datenbank oder von Passwörtern. Die Abwehr besteht in der konsequenten Behandlung aller Eingaben auf der Ebene einzelner Bytes. Und Hand aufs Herz, wer von Ihnen macht das? + +Das Nette Framework erledigt das für Sie und zwar automatisch. Sie müssen überhaupt nichts einstellen und alle Eingaben werden behandelt. + + +Session Hijacking, Session Stealing, Session Fixation +===================================================== + +Mit der Session-Verwaltung sind gleich mehrere Angriffsarten verbunden. Der Angreifer stiehlt entweder die Session-ID des Benutzers oder schiebt ihm seine eigene unter und erhält dadurch Zugriff auf die Webanwendung, ohne das Passwort des Benutzers zu kennen. Danach kann er in der Anwendung alles tun, ohne dass der Benutzer davon weiß. Die Abwehr besteht in der korrekten Konfiguration des Servers und von PHP. + +Wobei das Nette Framework PHP automatisch konfiguriert. Der Programmierer muss also nicht darüber nachdenken, wie die Session korrekt abgesichert wird und kann sich voll auf die Erstellung der Anwendung konzentrieren. Dies erfordert jedoch die aktivierte Funktion `ini_set()`. + + +SameSite-Cookie +=============== + +SameSite-Cookies bieten einen Mechanismus, um zu erkennen, was zum Laden der Seite geführt hat. Was für die Sicherheit absolut entscheidend ist. + +Das SameSite-Flag kann drei Werte haben: `Lax`, `Strict` und `None` (dieser erfordert HTTPS). Wenn die Anfrage für die Seite direkt von der Website kommt oder der Benutzer die Seite durch direkte Eingabe in die Adresszeile oder durch Klicken auf ein Lesezeichen öffnet, sendet der Browser alle Cookies an den Server (also mit den Flags `Lax`, `Strict` und `None`). Wenn der Benutzer über einen Link von einer anderen Website auf die Website klickt, werden Cookies mit den Flags `Lax` und `None` an den Server übergeben. Wenn die Anfrage auf andere Weise entsteht, wie das Senden eines POST-Formulars von einer anderen Website, Laden innerhalb eines Iframes, mittels JavaScript usw., werden nur Cookies mit dem Flag `None` gesendet. + +Nette sendet standardmäßig alle Cookies mit dem Flag `Lax`. + +{{leftbar: www:@menu-common}} diff --git a/nette/el/@home.texy b/nette/el/@home.texy index 24a21d5a36..c962d94320 100644 --- a/nette/el/@home.texy +++ b/nette/el/@home.texy @@ -1,4 +1,4 @@ -Nette Τεκμηρίωση +Τεκμηρίωση Nette **************** <div class=documentation> @@ -8,22 +8,22 @@ Nette Τεκμηρίωση Εισαγωγή -------- -- [Γιατί να χρησιμοποιήσετε τη Nette; |www:10-reasons-why-nette] -- [Εγκατάσταση |Installation] -- [Δημιουργήστε την πρώτη σας εφαρμογή! |quickstart:] +- [Γιατί να χρησιμοποιήσετε το Nette; |www:10-reasons-why-nette] +- [Εγκατάσταση |installation] +- [Γράφοντας την πρώτη σας εφαρμογή! |quickstart:] Γενικά ------ -- [Κατάλογος πακέτων |www:packages] -- [Συντήρηση και PHP |www:maintenance] +- [Λίστα πακέτων |www:packages] +- [Συντήρηση και εκδόσεις PHP |www:maintenance] - [Σημειώσεις έκδοσης |https://nette.org/releases] -- [Οδηγός αναβάθμισης |migrations:en] -- [nette:Αντιμετώπιση προβλημάτων |nette:Troubleshooting] +- [Μετάβαση σε νεότερες εκδόσεις|migrations:en] +- [Αντιμετώπιση προβλημάτων |nette:troubleshooting] - [Ποιος δημιουργεί το Nette |https://nette.org/contributors] -- [Ιστορία της Nette |www:history] +- [Ιστορία του Nette |www:history] - [Συμμετέχετε |contributing:] -- [Ανάπτυξη χορηγού |https://nette.org/en/donate] +- [Υποστηρίξτε την ανάπτυξη |https://nette.org/cs/donate] - [Αναφορά API |https://api.nette.org/] </div> @@ -31,20 +31,20 @@ Nette Τεκμηρίωση <div> -Εφαρμογή Nette --------------- +Εφαρμογές στο Nette +------------------- - [Πώς λειτουργούν οι εφαρμογές; |application:how-it-works] -- [Bootstrap |application:Bootstrap] -- [Presenters |application:Presenters] -- [Templates |application:Templates] -- [Modules |application:Modules] -- [Routing |application:Routing] +- [Bootstrapping |application:Bootstrapping] +- [Presenters |application:presenters] +- [Templates |application:templates] +- [Δομή καταλόγου |application:directory-structure] +- [Δρομολόγηση |application:routing] - [Δημιουργία συνδέσμων URL |application:creating-links] -- [Διαδραστικά στοιχεία |application:components] -- [AJAX & αποσπάσματα |application:ajax] +- [Διαδραστικά components |application:components] +- [AJAX & snippets |application:ajax] -- [Βέλτιστες πρακτικές |best-practices:] +- [Οδηγοί και βέλτιστες πρακτικές |best-practices:] </div> @@ -54,48 +54,49 @@ Nette Τεκμηρίωση Κύρια θέματα ------------ - [Διαμόρφωση |nette:configuring] -- [Εγχώνευση εξάρτησης |dependency-injection:] -- [Latte: Προτύπων |latte:] -- [Tracy: Tracy: Εργαλείο εντοπισμού σφαλμάτων |tracy:] +- [Dependency Injection|dependency-injection:] +- [Latte: templates |latte:] +- [Tracy: debugging κώδικα |tracy:] - [Φόρμες |forms:] -- [Βάση δεδομένων |database:core] -- [Αυθεντικοποίηση χρηστών |security:authentication] -- [Έλεγχος πρόσβασης |security:authorization] -- [Συνεδρίες |http:Sessions] -- [Αίτημα και απόκριση HTTP |http:] -- [Προσωρινή αποθήκευση δεδομένων |caching:] -- [Αποστολή emails |mail:] -- [Σχήμα: Επικύρωση δεδομένων |schema:] +- [Βάση δεδομένων |database:guide] +- [Σύνδεση χρηστών |security:authentication] +- [Επαλήθευση αδειών |security:authorization] +- [Sessions |http:Sessions] +- [HTTP request & response|http:] +- [Περιουσιακά στοιχεία |assets:] +- [Cache |caching:] +- [Αποστολή μηνυμάτων ηλεκτρονικού ταχυδρομείου |mail:] +- [Schema: επικύρωση δεδομένων |schema:] - [Γεννήτρια κώδικα PHP |php-generator:] -- [Δοκιμαστής: Δοκιμές μονάδας |tester:] +- [Tester: testing |tester:] </div> <div> -Βοηθητικά προγράμματα ---------------------- -- [Arrays |utils:Arrays] +Utilities +--------- +- [Arrays |utils:arrays] - [Σύστημα αρχείων |utils:filesystem] - [Finder |utils:finder] -- [Στοιχεία HTML |utils:HTML Elements] -- [Images |utils:Images] +- [Στοιχεία HTML |utils:html-elements] +- [Εικόνες |utils:images] - [JSON |utils:JSON] -- [NEON |neon:] -- [Κρυπτογράφηση κωδικού πρόσβασης |security:passwords] -- [SmartObject |utils:SmartObject] +- [NEON|neon:] +- [Κατακερματισμός κωδικών πρόσβασης |security:passwords] - [Τύποι PHP |utils:type] -- [Strings |utils:Strings] -- [Επικυρωτές |utils:validators] +- [Strings |utils:strings] +- [Validators |utils:validators] - [RobotLoader |robot-loader:] +- [SmartObject |utils:smartobject] & [StaticClass |utils:StaticClass] - [SafeStream |safe-stream:] -- [...άλλα |utils:] +- [...περισσότερα |utils:] </div> </div> {{toc:no}} -{{description: Περιγράφει τον τρόπο λειτουργίας της Nette και τις βέλτιστες πρακτικές για την ανάπτυξη διαδικτυακών εφαρμογών.}} -{{maintitle: Nette Documentation}} +{{description: Επίσημη τεκμηρίωση του Nette: περιγράφει πώς λειτουργεί το Nette και τις βέλτιστες πρακτικές για την ανάπτυξη εφαρμογών ιστού.}} +{{maintitle: Τεκμηρίωση Nette}} diff --git a/nette/el/@menu-topics.texy b/nette/el/@menu-topics.texy index 7734874138..4a7f6211b8 100644 --- a/nette/el/@menu-topics.texy +++ b/nette/el/@menu-topics.texy @@ -1,21 +1,21 @@ Κύρια θέματα ************ - [Διαμόρφωση |nette:configuring] -- [Εφαρμογή Nette |application:how-it-works] -- [Έγχυση εξάρτησης |dependency-injection:] -- [Βοηθητικά προγράμματα |utils:] +- [Εφαρμογές στο Nette |application:how-it-works] +- [Dependency Injection|dependency-injection:] +- [Utilities |utils:] - [Φόρμες |forms:] -- [Βάση δεδομένων |database:core] -- [Αυθεντικοποίηση χρηστών |security:authentication] -- [Έλεγχος πρόσβασης |security:authorization] -- [Συνεδρίες |http:Sessions] -- [Αίτημα και απόκριση HTTP |http:] -- [Προσωρινή αποθήκευση δεδομένων |caching:] -- [Αποστολή emails |mail:] -- [Σχήμα: Επικύρωση δεδομένων |schema:] +- [Βάση δεδομένων |database:guide] +- [Σύνδεση χρηστών |security:authentication] +- [Επαλήθευση αδειών |security:authorization] +- [Sessions |http:Sessions] +- [HTTP request & response|http:] +- [Cache |caching:] +- [Αποστολή μηνυμάτων ηλεκτρονικού ταχυδρομείου |mail:] +- [Schema: επικύρωση δεδομένων |schema:] - [Γεννήτρια κώδικα PHP |php-generator:] -- [Latte: πρότυπα |latte:] -- [Tracy: αποσφαλμάτωση |tracy:] -- [Tester: δοκιμές |tester:] +- [Latte: templates |latte:] +- [Tracy: debugging κώδικα |tracy:] +- [Tester: testing |tester:] diff --git a/nette/el/@meta.texy b/nette/el/@meta.texy new file mode 100644 index 0000000000..88e29852c7 --- /dev/null +++ b/nette/el/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Τεκμηρίωση}} diff --git a/nette/el/configuring.texy b/nette/el/configuring.texy index e9f5062941..96abd9ee39 100644 --- a/nette/el/configuring.texy +++ b/nette/el/configuring.texy @@ -2,34 +2,35 @@ **************** .[perex] -Μια επισκόπηση όλων των επιλογών διαμόρφωσης στο Nette Framework. +Επισκόπηση όλων των επιλογών διαμόρφωσης στο Nette Framework. -Τα στοιχεία της Nette διαμορφώνονται με τη χρήση αρχείων διαμόρφωσης, τα οποία συνήθως είναι γραμμένα σε [NEON |neon:format]. Είναι καλύτερο να τα επεξεργάζεστε σε [επεξεργαστές που το υποστηρίζουν |best-practices:editors-and-tools#ide-editor]. -Αν χρησιμοποιείτε το πλήρες πλαίσιο, η διαμόρφωση θα [φορτωθεί κατά την εκκίνηση |application:bootstrap#di-container-configuration], αν όχι, δείτε [πώς να φορτώσετε τη διαμόρφωση |bootstrap:]. +Ρυθμίζουμε τα στοιχεία του Nette χρησιμοποιώντας αρχεία διαμόρφωσης, τα οποία συνήθως γράφονται σε [μορφή NEON|neon:format]. Ο καλύτερος τρόπος επεξεργασίας τους είναι σε [επεξεργαστές με υποστήριξη για αυτό |best-practices:editors-and-tools#IDE editor]. Αν χρησιμοποιείτε ολόκληρο το framework, η διαμόρφωση [φορτώνεται κατά την εκκίνηση της εφαρμογής |application:bootstrapping#Διαμόρφωση του DI Container], αν όχι, διαβάστε [πώς να φορτώσετε τη διαμόρφωση|bootstrap:]. <pre> -"application .[prism-token prism-atrule]":[application:configuration#Application]: "Application .[prism-token prism-comment]"<br> -"constants .[prism-token prism-atrule]":[application:configuration#Constants]: "Ορίζει σταθερές της PHP .[prism-token prism-comment]"<br> +"application .[prism-token prism-atrule]":[application:configuration#Application]: "Εφαρμογή .[prism-token prism-comment]"<br> +"assets .[prism-token prism-atrule]":[assets:configuration]: "Assets .[prism-token prism-comment]"<br> +"constants .[prism-token prism-atrule]":[application:configuration#Σταθερές]: "Ορισμός σταθερών PHP .[prism-token prism-comment]"<br> "database .[prism-token prism-atrule]":[database:configuration]: "Βάση δεδομένων .[prism-token prism-comment]"<br> "decorator .[prism-token prism-atrule]":[dependency-injection:configuration#Decorator]: "Διακοσμητής .[prism-token prism-comment]"<br> -"di .[prism-token prism-atrule]":[dependency-injection:configuration#DI]: "DI Container .[prism-token prism-comment]"<br> -"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Extensions]: "Εγκατάσταση πρόσθετων επεκτάσεων DI .[prism-token prism-comment]"<br> +"di .[prism-token prism-atrule]":[dependency-injection:configuration#DI]: "DI container .[prism-token prism-comment]"<br> +"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Επεκτάσεις]: "Εγκατάσταση επιπλέον επεκτάσεων DI .[prism-token prism-comment]"<br> "forms .[prism-token prism-atrule]":[forms:configuration]: "Φόρμες .[prism-token prism-comment]"<br> -"http .[prism-token prism-atrule]":[http:configuration#HTTP Headers]: "HTTP Headers .[prism-token prism-comment]"<br> -"includes .[prism-token prism-atrule]":[dependency-injection:configuration#Including files]: "Συμπεριλαμβανομένων αρχείων .[prism-token prism-comment]"<br> -"latte .[prism-token prism-atrule]":[application:configuration#Latte]: "Latte .[prism-token prism-comment]"<br> -"mail .[prism-token prism-atrule]":[mail:#Configuring]: "Αλληλογραφία .[prism-token prism-comment]"<br> -"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#Parameters]: "Παράμετροι .[prism-token prism-comment]"<br> -"php .[prism-token prism-atrule]":[application:configuration#PHP]: "Επιλογές ρύθμισης παραμέτρων PHP .[prism-token prism-comment]"<br> -"routing .[prism-token prism-atrule]":[application:configuration#Routing]: "Δρομολόγηση .[prism-token prism-comment]"<br> -"search .[prism-token prism-atrule]":[dependency-injection:configuration#Search]: "Αυτόματη καταχώρηση υπηρεσιών .[prism-token prism-comment]"<br> -"security .[prism-token prism-atrule]":[security:configuration]: "Έλεγχος πρόσβασης .[prism-token prism-comment]"<br> +"http .[prism-token prism-atrule]":[http:configuration#Κεφαλίδες HTTP]: "HTTP headers .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[dependency-injection:configuration#Εισαγωγή αρχείων]: "Συμπερίληψη αρχείων .[prism-token prism-comment]"<br> +"latte .[prism-token prism-atrule]":[application:configuration#Templates Latte]: "Πρότυπα Latte .[prism-token prism-comment]"<br> +"mail .[prism-token prism-atrule]":[mail:#Διαμόρφωση]: "Emails .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#Παράμετροι]: "Παράμετροι .[prism-token prism-comment]"<br> +"php .[prism-token prism-atrule]":[application:configuration#PHP]: "Διαμόρφωση PHP .[prism-token prism-comment]"<br> +"routing .[prism-token prism-atrule]":[application:configuration#Δρομολόγηση]: "Δρομολόγηση .[prism-token prism-comment]"<br> +"search .[prism-token prism-atrule]":[dependency-injection:configuration#Αναζήτηση]: "Αυτόματη εγγραφή υπηρεσιών .[prism-token prism-comment]"<br> +"security .[prism-token prism-atrule]":[security:configuration]: "Δικαιώματα πρόσβασης .[prism-token prism-comment]"<br> "services .[prism-token prism-atrule]":[dependency-injection:services]: "Υπηρεσίες .[prism-token prism-comment]"<br> -"session .[prism-token prism-atrule]":[http:configuration#Session]: "Σύνοδος .[prism-token prism-comment]"<br> -"tracy .[prism-token prism-atrule]":[tracy:configuring#Nette Framework]: "Tracy Debugger .[prism-token prism-comment]" +"session .[prism-token prism-atrule]":[http:configuration#Session]: "Session .[prism-token prism-comment]"<br> +"tracy .[prism-token prism-atrule]":[tracy:configuring#Nette Framework]: "Tracy debugger .[prism-token prism-comment]" </pre> -Για να γράψετε μια συμβολοσειρά που περιέχει τον χαρακτήρα `%`, you must escape it by doubling it to `%%`. .[note] +.[note] +Για να γράψετε ένα string που περιέχει τον χαρακτήρα `%`, πρέπει να κάνετε διαφυγή διπλασιάζοντάς τον σε `%%`. {{leftbar: @menu-topics}} diff --git a/nette/el/glossary.texy b/nette/el/glossary.texy index 1b7c2daa06..972316ee71 100644 --- a/nette/el/glossary.texy +++ b/nette/el/glossary.texy @@ -2,154 +2,158 @@ ************** -AJAX .[#toc-ajax] ------------------ -Ασύγχρονη JavaScript και XML - τεχνολογία για επικοινωνία πελάτη-εξυπηρετητή μέσω του πρωτοκόλλου HTTP χωρίς την ανάγκη επαναφόρτωσης ολόκληρης της σελίδας κατά τη διάρκεια κάθε αίτησης. Παρά το ακρωνύμιο, η μορφή [JSON |#JSON] χρησιμοποιείται συχνά αντί της XML. +AJAX +---- +Asynchronous JavaScript and XML - τεχνολογία ανταλλαγής πληροφοριών μεταξύ πελάτη και διακομιστή μέσω του πρωτοκόλλου HTTP χωρίς την ανάγκη επαναφόρτωσης ολόκληρης της σελίδας σε κάθε αίτημα. Αν και το όνομα μπορεί να υποδηλώνει ότι στέλνει δεδομένα μόνο σε μορφή XML, χρησιμοποιείται συνήθως και η μορφή [#JSON]. -Δράση παρουσιαστή .[#toc-presenter-action] ------------------------------------------- -Λογικό τμήμα του [παρουσιαστή |#presenter], που εκτελεί μια ενέργεια, όπως για παράδειγμα την εμφάνιση μιας σελίδας προϊόντος, την αποσύνδεση ενός χρήστη κ.λπ. Ένας παρουσιαστής μπορεί να έχει περισσότερες ενέργειες. +Action (presenter) +------------------ +Λογικό τμήμα του presenter που εκτελεί μία ενέργεια. Για παράδειγμα, εμφανίζει τη σελίδα ενός προϊόντος, αποσυνδέει τον χρήστη κ.λπ. Ένας presenter μπορεί να έχει πολλαπλές ενέργειες (actions). BOM --- -Η λεγόμενη *μάσκα τάξης byte* είναι ένας ειδικός πρώτος χαρακτήρας ενός αρχείου και υποδεικνύει τη σειρά byte στην κωδικοποίηση. Ορισμένοι επεξεργαστές το περιλαμβάνουν αυτόματα, είναι πρακτικά αόρατο, αλλά προκαλεί προβλήματα με τις επικεφαλίδες και την αποστολή εξόδου μέσα από την PHP. Μπορείτε να χρησιμοποιήσετε τον [Code Checker |code-checker:] για μαζική αφαίρεση. +Το λεγόμενο *byte order mark* είναι ένας ειδικός πρώτος χαρακτήρας σε ένα αρχείο που χρησιμοποιείται ως δείκτης της σειράς των bytes στην κωδικοποίηση. Ορισμένοι επεξεργαστές τον εισάγουν στα αρχεία. Είναι πρακτικά αόρατος, αλλά προκαλεί προβλήματα με την αποστολή εξόδου και κεφαλίδων από την PHP. Για μαζική αφαίρεση μπορείτε να χρησιμοποιήσετε το [Code Checker|code-checker:]. -Ελεγκτής .[#toc-controller] ---------------------------- -Ο ελεγκτής επεξεργάζεται τα αιτήματα του χρήστη και βάσει αυτών καλεί συγκεκριμένη λογική της εφαρμογής (π.χ. [μοντέλο |#model]), στη συνέχεια καλεί [την προβολή |#view] για την απόδοση των δεδομένων. Αναλογία με τους ελεγκτές είναι οι [παρουσιαστές |#presenter] στο Nette Framework. +Controller +---------- +Ελεγκτής που επεξεργάζεται τα αιτήματα του χρήστη και βάσει αυτών καλεί την κατάλληλη λογική της εφαρμογής (δηλ. το [#model]) και στη συνέχεια ζητά από το [#view] την απόδοση των δεδομένων. Αντίστοιχοι των controllers στο Nette Framework είναι οι [presenters |#Presenter]. -Cross-Site Scripting (XSS) .[#toc-cross-site-scripting-xss] ------------------------------------------------------------ -Το Cross-Site Scripting είναι μια μέθοδος διακοπής της λειτουργίας του ιστότοπου που χρησιμοποιεί μη αποθηκευμένη είσοδο. Ένας επιτιθέμενος μπορεί να εισάγει τον δικό του κώδικα HTML ή JavaScript και να αλλάξει την εμφάνιση της σελίδας ή ακόμη και να συλλέξει ευαίσθητες πληροφορίες για τους χρήστες. Η προστασία κατά του XSS είναι απλή: συνεπής και σωστή διαφυγή όλων των συμβολοσειρών και των εισόδων. +Cross-Site Scripting (XSS) +-------------------------- +Το Cross-Site Scripting είναι μια μέθοδος παραβίασης ιστοσελίδων που εκμεταλλεύεται μη επεξεργασμένες εξόδους. Ο εισβολέας μπορεί στη συνέχεια να εισάγει τον δικό του κώδικα στη σελίδα και έτσι να την τροποποιήσει ή ακόμη και να αποκτήσει ευαίσθητα δεδομένα για τους επισκέπτες. Η άμυνα κατά του XSS είναι δυνατή μόνο με τη συνεπή και σωστή επεξεργασία όλων των strings. + +Το Nette Framework έρχεται με την επαναστατική τεχνολογία [Context-Aware Escaping |latte:safety-first#Context-Aware Escaping], η οποία σας απαλλάσσει για πάντα από τον κίνδυνο του Cross-Site Scripting. Επεξεργάζεται όλες τις εξόδους αυτόματα και έτσι δεν μπορεί να συμβεί ο κωδικοποιητής να ξεχάσει κάτι. -Το Nette Framework έρχεται με μια ολοκαίνουργια τεχνολογία [Context-Aware Escaping |latte:safety-first#context-aware-escaping], η οποία θα σας απαλλάξει για πάντα από τους κινδύνους Cross-Site Scripting. Διασφαλίζει αυτόματα όλες τις εισόδους με βάση ένα δεδομένο πλαίσιο, οπότε είναι αδύνατο για έναν προγραμματιστή να ξεχάσει κατά λάθος κάτι. +Cross-Site Request Forgery (CSRF) +--------------------------------- +Η επίθεση Cross-Site Request Forgery συνίσταται στο ότι ο εισβολέας δελεάζει το θύμα σε μια σελίδα που εκτελεί διακριτικά ένα αίτημα στον διακομιστή στον οποίο είναι συνδεδεμένο το θύμα, στον περιηγητή του θύματος, και ο διακομιστής πιστεύει ότι το αίτημα εκτελέστηκε από το θύμα με τη θέλησή του. Και έτσι, υπό την ταυτότητα του θύματος, εκτελεί μια συγκεκριμένη ενέργεια χωρίς να το γνωρίζει. Μπορεί να είναι αλλαγή ή διαγραφή δεδομένων, αποστολή μηνύματος κ.λπ. -Πλαστογράφηση αιτήσεων διασταυρούμενης τοποθεσίας (CSRF) .[#toc-cross-site-request-forgery-csrf] ------------------------------------------------------------------------------------------------- -Μια επίθεση Cross-Site Request Forgery είναι ότι ο επιτιθέμενος παρασύρει το θύμα να επισκεφθεί μια σελίδα που εκτελεί σιωπηλά ένα αίτημα στο πρόγραμμα περιήγησης του θύματος προς τον διακομιστή όπου το θύμα είναι συνδεδεμένο αυτή τη στιγμή και ο διακομιστής πιστεύει ότι το αίτημα έγινε από το θύμα κατά βούληση. Ο διακομιστής εκτελεί μια συγκεκριμένη ενέργεια με την ταυτότητα του θύματος αλλά χωρίς το θύμα να το αντιληφθεί. Μπορεί να είναι η αλλαγή ή η διαγραφή δεδομένων, η αποστολή ενός μηνύματος κ.λπ. +Το Nette Framework **προστατεύει αυτόματα τις φόρμες και τα σήματα στους presenters** από αυτόν τον τύπο επίθεσης. Και αυτό εμποδίζοντας την αποστολή ή την κλήση τους από άλλο domain. -Το Nette Framework **προστατεύει αυτόματα τις φόρμες και τα σήματα στους παρουσιαστές** από αυτόν τον τύπο επίθεσης. Αυτό επιτυγχάνεται με την παρεμπόδιση της αποστολής ή της κλήσης τους από άλλο τομέα. +Dependency Injection +-------------------- +Το Dependency Injection (DI) είναι ένα σχεδιαστικό πρότυπο που καθορίζει πώς να διαχωρίσετε τη δημιουργία αντικειμένων από τις εξαρτήσεις τους. Δηλαδή, η κλάση δεν είναι υπεύθυνη για τη δημιουργία ή την αρχικοποίηση των εξαρτήσεών της, αλλά αντίθετα αυτές οι εξαρτήσεις της παρέχονται από εξωτερικό κώδικα (αυτός μπορεί να είναι και ένας [DI container |#Dependency Injection container]). Το πλεονέκτημα είναι ότι επιτρέπει μεγαλύτερη ευελιξία στον κώδικα, καλύτερη κατανόηση και ευκολότερο έλεγχο της εφαρμογής, καθώς οι εξαρτήσεις είναι εύκολα αντικαταστάσιμες και απομονωμένες από τα υπόλοιπα μέρη του κώδικα. Περισσότερα στο κεφάλαιο [Τι είναι το Dependency Injection? |dependency-injection:introduction] -Εγχώνευση εξάρτησης .[#toc-dependency-injection] ------------------------------------------------- -Το Dependency Injection (DI) είναι ένα πρότυπο σχεδίασης που σας λέει πώς να διαχωρίσετε τη δημιουργία αντικειμένων από τις εξαρτήσεις τους. Δηλαδή, μια κλάση δεν είναι υπεύθυνη για τη δημιουργία ή την αρχικοποίηση των εξαρτήσεών της, αλλά αντίθετα αυτές οι εξαρτήσεις παρέχονται από εξωτερικό κώδικα (ο οποίος μπορεί να περιλαμβάνει ένα [DI container |#Dependency Injection container]). Το πλεονέκτημα είναι ότι επιτρέπει μεγαλύτερη ευελιξία του κώδικα, καλύτερη αναγνωσιμότητα και ευκολότερο έλεγχο της εφαρμογής, επειδή οι εξαρτήσεις είναι εύκολα αντικαταστάσιμες και απομονωμένες από άλλα μέρη του κώδικα. Για περισσότερες πληροφορίες, ανατρέξτε στην ενότητα [Τι είναι το Dependency Injection; |dependency-injection:introduction] +Dependency Injection container +------------------------------ +Ένας Dependency Injection container (επίσης DI container ή IoC container) είναι ένα εργαλείο που διαχειρίζεται τη δημιουργία και τη διαχείριση των εξαρτήσεων σε μια εφαρμογή (δηλαδή των [υπηρεσιών |#Service]). Ο container έχει συνήθως μια διαμόρφωση που ορίζει ποιες κλάσεις εξαρτώνται από άλλες κλάσεις, ποιες συγκεκριμένες υλοποιήσεις εξαρτήσεων πρέπει να χρησιμοποιηθούν και πώς πρέπει να δημιουργηθούν αυτές οι εξαρτήσεις. Στη συνέχεια, ο container δημιουργεί αυτά τα αντικείμενα και τα παρέχει στις κλάσεις που τα χρειάζονται. Περισσότερα στο κεφάλαιο [Τι είναι ένας DI container? |dependency-injection:container] -Δοχείο έγχυσης εξαρτήσεων .[#toc-dependency-injection-container] ----------------------------------------------------------------- -Ένα Dependency Injection container (επίσης DI container ή IoC container) είναι ένα εργαλείο που χειρίζεται τη δημιουργία και τη διαχείριση των εξαρτήσεων σε μια εφαρμογή (ή [υπηρεσίες |#service]). Ένας περιέκτης έχει συνήθως μια διαμόρφωση που ορίζει ποιες κλάσεις εξαρτώνται από άλλες κλάσεις, ποιες συγκεκριμένες υλοποιήσεις εξαρτήσεων να χρησιμοποιηθούν και πώς να δημιουργηθούν αυτές οι εξαρτήσεις. Στη συνέχεια, ο περιέκτης δημιουργεί αυτά τα αντικείμενα και τα παρέχει στις κλάσεις που τα χρειάζονται. Για περισσότερες πληροφορίες, ανατρέξτε στην ενότητα [Τι είναι ένας περιέκτης DI; |dependency-injection:container] +Escaping +-------- +Το Escaping είναι η μετατροπή χαρακτήρων που έχουν ειδική σημασία σε ένα δεδομένο πλαίσιο σε άλλες αντίστοιχες ακολουθίες. Παράδειγμα: θέλουμε να γράψουμε εισαγωγικά σε ένα string που οριοθετείται από εισαγωγικά. Δεδομένου ότι τα εισαγωγικά έχουν ειδική σημασία στο πλαίσιο του string και η απλή γραφή τους θα ερμηνευόταν ως τερματισμός του string, είναι απαραίτητο να γραφτούν με μια άλλη αντίστοιχη ακολουθία. Το ποια ακριβώς καθορίζεται από τους κανόνες του πλαισίου. -Αποφυγή του .[#toc-escaping] ----------------------------- -Το escaping είναι η μετατροπή χαρακτήρων με ειδική σημασία σε δεδομένο πλαίσιο σε άλλες ισοδύναμες ακολουθίες. Παράδειγμα: Θέλουμε να γράψουμε εισαγωγικά σε συμβολοσειρά με εισαγωγικά. Επειδή τα εισαγωγικά έχουν ειδική σημασία στο πλαίσιο της συμβολοσειράς που περικλείεται σε εισαγωγικά, υπάρχει ανάγκη να χρησιμοποιήσουμε μια άλλη ισοδύναμη ακολουθία. Η συγκεκριμένη ακολουθία καθορίζεται από τους κανόνες του πλαισίου (π.χ. `\"` σε συμβολοσειρά που περικλείεται σε εισαγωγικά της PHP, `"` σε χαρακτηριστικά της HTML κ.λπ.) +Filter (previously helper) +-------------------------- +Στα πρότυπα, ο όρος [φίλτρο |latte:syntax#Φίλτρα] συνήθως αναφέρεται σε μια συνάρτηση που βοηθά στην τροποποίηση ή την αναδιαμόρφωση των δεδομένων στην τελική τους μορφή. Τα πρότυπα διαθέτουν αρκετά [standard filters |latte:filters]. -Φίλτρο (πρώην βοηθός) .[#toc-filter-formerly-helper] ----------------------------------------------------- -Λειτουργία φίλτρου. Στα πρότυπα, το [φίλτρο |latte:syntax#filters] είναι μια συνάρτηση που βοηθά στην αλλαγή ή τη μορφοποίηση των δεδομένων στη μορφή εξόδου. Τα πρότυπα έχουν αρκετά [τυποποιημένα φίλτρα |latte:filters] προκαθορισμένα. +Invalidation +------------ +Ειδοποίηση ενός [#snippet] για να επανασχεδιαστεί. Με άλλη έννοια, επίσης διαγραφή του περιεχομένου της cache. -Ακύρωση .[#toc-invalidation] ----------------------------- -Ανακοίνωση ενός [αποσπάσματος |#snippet] προς επανεκτέλεση. Σε άλλο πλαίσιο, επίσης, εκκαθάριση μιας προσωρινής μνήμης. +JSON +---- +Μορφή ανταλλαγής δεδομένων που βασίζεται στη σύνταξη της JavaScript (είναι υποσύνολό της). Την ακριβή προδιαγραφή θα βρείτε στη σελίδα www.json.org. -JSON .[#toc-json] ------------------ -Μορφή ανταλλαγής δεδομένων βασισμένη στη σύνταξη της JavaScript (είναι υποσύνολό της). Οι ακριβείς προδιαγραφές μπορούν να βρεθούν στη διεύθυνση www.json.org. +Component +--------- +Επαναχρησιμοποιήσιμο στοιχείο της εφαρμογής. Μπορεί να είναι ένα οπτικό τμήμα της σελίδας, όπως περιγράφει το κεφάλαιο [Γράφοντας Components |application:components], ή ο όρος component αναφέρεται επίσης στην κλάση [Component |component-model:] (ένα τέτοιο component δεν χρειάζεται να είναι οπτικό). -Στοιχείο .[#toc-component] --------------------------- -Επαναχρησιμοποιήσιμο τμήμα μιας εφαρμογής. Μπορεί να είναι ένα οπτικό μέρος μιας σελίδας, όπως περιγράφεται στο κεφάλαιο [application:components], ή ο όρος μπορεί επίσης να αντιπροσωπεύει την κλάση [Component |component-model:] (ένα τέτοιο στοιχείο δεν χρειάζεται να είναι οπτικό). +Control characters +------------------ +Οι χαρακτήρες ελέγχου είναι αόρατοι χαρακτήρες που μπορεί να εμφανίζονται στο κείμενο και ενδεχομένως να προκαλούν προβλήματα. Για τη μαζική αφαίρεσή τους από αρχεία μπορείτε να χρησιμοποιήσετε το [Code Checker|code-checker:] και για την αφαίρεσή τους από μια μεταβλητή τη συνάρτηση [Strings::normalize() |utils:strings#normalize]. -Χαρακτήρες ελέγχου .[#toc-control-characters] ---------------------------------------------- -Οι χαρακτήρες ελέγχου είναι αόρατοι χαρακτήρες, οι οποίοι μπορεί να εμφανιστούν σε ένα κείμενο και τελικά να προκαλέσουν κάποια προβλήματα. Για τη μαζική αφαίρεσή τους από αρχεία, μπορείτε να χρησιμοποιήσετε τον [Code Checker |code-checker:], ενώ για την αφαίρεσή τους από μια μεταβλητή χρησιμοποιήστε τη συνάρτηση [Strings::normalize() |utils:strings#normalize]. +Events +------ +Ένα event είναι μια αναμενόμενη κατάσταση σε ένα αντικείμενο, η οποία όταν συμβεί, καλούνται οι λεγόμενοι handlers, δηλαδή callbacks που αντιδρούν στο event ("παράδειγμα":https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38). Ένα event μπορεί να είναι, για παράδειγμα, η υποβολή μιας φόρμας, η σύνδεση ενός χρήστη κ.λπ. Τα events είναι έτσι μια μορφή *Inversion of Control*. -Γεγονότα .[#toc-events] ------------------------ -Ένα συμβάν είναι μια αναμενόμενη κατάσταση στο αντικείμενο, η οποία όταν συμβεί, καλούνται οι λεγόμενοι χειριστές, δηλαδή οι ανακλήσεις που αντιδρούν στο συμβάν ("δείγμα":https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38). Το συμβάν μπορεί να είναι για παράδειγμα η υποβολή φόρμας, η είσοδος χρήστη κ.λπ. Τα συμβάντα αποτελούν έτσι μια μορφή *Επιστροφή του ελέγχου*. +Για παράδειγμα, η σύνδεση χρήστη συμβαίνει στη μέθοδο `Nette\Security\User::login()`. Το αντικείμενο `User` έχει μια δημόσια μεταβλητή `$onLoggedIn`, η οποία είναι ένας πίνακας στον οποίο ο καθένας μπορεί να προσθέσει ένα callback. Τη στιγμή που ο χρήστης συνδέεται, η μέθοδος `login()` καλεί όλα τα callbacks στον πίνακα. Το όνομα της μεταβλητής με τη μορφή `onXyz` είναι μια σύμβαση που χρησιμοποιείται σε όλο το Nette. -Για παράδειγμα, μια είσοδος χρήστη συμβαίνει στη μέθοδο `Nette\Security\User::login()`. Το αντικείμενο `User` έχει μια δημόσια μεταβλητή `$onLoggedIn`, η οποία είναι ένας πίνακας στον οποίο ο καθένας μπορεί να προσθέσει μια επανάκληση. Μόλις ο χρήστης συνδεθεί, η μέθοδος `login()` καλεί όλα τα callbacks του πίνακα. Το όνομα μιας μεταβλητής με τη μορφή `onXyz` είναι μια σύμβαση που χρησιμοποιείται σε όλη τη Nette. +Latte +----- +Ένα από τα πιο προηγμένα [συστήματα προτύπων |latte:]. -Latte .[#toc-latte] -------------------- -Ένα από τα πιο καινοτόμα [συστήματα template |latte:] που έχουν υπάρξει ποτέ. +Model +----- +Το Model είναι η βάση δεδομένων και κυρίως η λειτουργική βάση ολόκληρης της εφαρμογής. Περιέχει ολόκληρη τη λογική της εφαρμογής (χρησιμοποιείται επίσης ο όρος business logic). Είναι το **M** από το **M**VC ή MVP. Οποιαδήποτε ενέργεια του χρήστη (σύνδεση, προσθήκη προϊόντος στο καλάθι, αλλαγή τιμής στη βάση δεδομένων) αντιπροσωπεύει μια ενέργεια του μοντέλου. -Μοντέλο .[#toc-model] +Το Model διαχειρίζεται την εσωτερική του κατάσταση και προσφέρει προς τα έξω μια σταθερή διεπαφή. Καλώντας τις συναρτήσεις αυτής της διεπαφής, μπορούμε να μάθουμε ή να αλλάξουμε την κατάστασή του. Το Model δεν γνωρίζει την ύπαρξη του [#view] ή του [#controller]. + + +Model-View-Controller --------------------- -Το μοντέλο αποτελεί τη βάση δεδομένων και λειτουργιών ολόκληρης της εφαρμογής. Περιλαμβάνει ολόκληρη τη λογική της εφαρμογής (μερικές φορές αναφέρεται και ως "επιχειρησιακή λογική"). Είναι το **M** της **M**VC ή MPV. Οποιαδήποτε ενέργεια του χρήστη (σύνδεση, τοποθέτηση αντικειμένων στο καλάθι, αλλαγή μιας τιμής της βάσης δεδομένων) αντιπροσωπεύει μια ενέργεια του μοντέλου. +Αρχιτεκτονική λογισμικού που προέκυψε από την ανάγκη διαχωρισμού, σε εφαρμογές με γραφική διεπαφή, του κώδικα χειρισμού ([#controller]) από τον κώδικα της λογικής της εφαρμογής ([#model]) και από τον κώδικα εμφάνισης δεδομένων ([#view]). Αυτό αφενός καθιστά την εφαρμογή πιο κατανοητή, διευκολύνει τη μελλοντική ανάπτυξη και επιτρέπει τον έλεγχο των μεμονωμένων τμημάτων ξεχωριστά. -Το μοντέλο διαχειρίζεται την εσωτερική του κατάσταση και παρέχει μια δημόσια διεπαφή. Με την κλήση αυτής της διεπαφής μπορούμε να πάρουμε ή να αλλάξουμε την κατάστασή του. Το μοντέλο δεν γνωρίζει την ύπαρξη [προβολής |#view] ή [ελεγκτή |#controller], το μοντέλο είναι εντελώς ανεξάρτητο από αυτά. +Model-View-Presenter +-------------------- +Αρχιτεκτονική που βασίζεται στο [#Model-View-Controller]. -Μοντέλο-προβολή-ελεγκτής .[#toc-model-view-controller] ------------------------------------------------------- -Αρχιτεκτονική λογισμικού, που εμφανίστηκε στην ανάπτυξη εφαρμογών GUI για να διαχωρίσει τον κώδικα για τον έλεγχο ροής ([controller |#controller]) από τον κώδικα της λογικής της εφαρμογής ([model |#model]) και από τον κώδικα απόδοσης δεδομένων ([view |#view]). Με αυτόν τον τρόπο ο κώδικας γίνεται καλύτερα κατανοητός, διευκολύνεται η μελλοντική ανάπτυξη και επιτρέπεται η δοκιμή των ξεχωριστών τμημάτων ξεχωριστά. +Module +------ +Ένα module αντιπροσωπεύει ένα λογικό τμήμα της εφαρμογής. Σε μια τυπική διάταξη, πρόκειται για μια ομάδα presenters και προτύπων που χειρίζονται έναν συγκεκριμένο τομέα λειτουργικότητας. Τοποθετούμε τα modules σε [ξεχωριστούς καταλόγους |application:directory-structure#Presenters και Πρότυπα], όπως π.χ. `Front/`, `Admin/` ή `Shop/`. -Μοντέλο-προβολή-παρουσιαστής .[#toc-model-view-presenter] ---------------------------------------------------------- -Αρχιτεκτονική βασισμένη στο [Model-View-Controller |#Model-View-Controller]. +Για παράδειγμα, ένα e-shop το χωρίζουμε σε: +- Frontend (`Shop/`) για την προβολή προϊόντων και την αγορά +- Ενότητα πελατών (`Customer/`) για τη διαχείριση παραγγελιών +- Διαχείριση (`Admin/`) για τον διαχειριστή +Τεχνικά, πρόκειται για συνηθισμένους καταλόγους, οι οποίοι όμως χάρη στη σαφή διάρθρωση βοηθούν στην κλιμάκωση της εφαρμογής. Ο presenter `Admin:Product:List` θα βρίσκεται έτσι φυσικά, για παράδειγμα, στον κατάλογο `app/Presentation/Admin/Product/List/` (δείτε [αντιστοίχιση presenter |application:directory-structure#Αντιστοίχιση Presenters]). -Ενότητα .[#toc-module] ----------------------- -Η [ενότητα |application:modules] στο Nette Framework αντιπροσωπεύει μια συλλογή από παρουσιαστές και πρότυπα, ενδεχομένως επίσης συστατικά και μοντέλα, που εξυπηρετούν δεδομένα σε έναν παρουσιαστή. Έτσι, είναι ορισμένο λογικό μέρος μιας εφαρμογής. -Για παράδειγμα, ένα ηλεκτρονικό κατάστημα μπορεί να έχει τρεις ενότητες: -1) Κατάλογος προϊόντων με καλάθι. -2) Διαχείριση για τον πελάτη. -3) Διαχείριση για τον καταστηματάρχη. +Namespace +--------- +Ένα namespace, μέρος της γλώσσας PHP από την έκδοση 5.3 και ορισμένων άλλων γλωσσών προγραμματισμού, που επιτρέπει τη χρήση κλάσεων που ονομάζονται το ίδιο σε διαφορετικές βιβλιοθήκες χωρίς να προκύψει σύγκρουση ονομάτων. Δείτε την [Τεκμηρίωση PHP |https://www.php.net/manual/en/language.namespaces.rationale.php]. -Χώρος ονομάτων .[#toc-namespace] --------------------------------- -Ο χώρος ονομάτων είναι ένα χαρακτηριστικό της γλώσσας PHP από την έκδοση 5.3 και ορισμένων άλλων γλωσσών προγραμματισμού. Βοηθά στην αποφυγή συγκρούσεων ονομάτων (π.χ. δύο κλάσεις με το ίδιο όνομα) όταν χρησιμοποιούνται διαφορετικές βιβλιοθήκες μαζί. Ανατρέξτε στην [τεκμηρίωση της PHP |https://www.php.net/manual/en/language.namespaces.rationale.php] για περισσότερες λεπτομέρειες. +Presenter +--------- +Ο Presenter είναι ένα αντικείμενο που λαμβάνει το [request |api:Nette\Application\Request] που μεταφράστηκε από τον router από το HTTP request και δημιουργεί την [response |api:Nette\Application\Response]. Η απόκριση μπορεί να είναι μια σελίδα HTML, μια εικόνα, ένα έγγραφο XML, ένα αρχείο στον δίσκο, JSON, μια ανακατεύθυνση ή οτιδήποτε άλλο σκεφτείτε. +Συνήθως, ο όρος presenter αναφέρεται σε έναν απόγονο της κλάσης [api:Nette\Application\UI\Presenter]. Ανάλογα με τα εισερχόμενα αιτήματα, εκτελεί τις αντίστοιχες [actions |application:presenters#Κύκλος ζωής του presenter] και αποδίδει πρότυπα. -Παρουσιαστής .[#toc-presenter] ------------------------------- -Ο παρουσιαστής είναι ένα αντικείμενο, το οποίο λαμβάνει το [αίτημα |api:Nette\Application\Request] όπως μεταφράζεται από το δρομολογητή από το αίτημα HTTP και παράγει μια [απάντηση |api:Nette\Application\Response]. Η απάντηση μπορεί να είναι μια σελίδα HTML, μια εικόνα, ένα έγγραφο XML, ένα αρχείο, ένα JSON, μια ανακατεύθυνση ή ό,τι άλλο σκεφτείτε. -Με τον όρο παρουσιαστής συνήθως εννοείται ένας απόγονος της κλάσης [api:Nette\Application\UI\Presenter]. Με τα αιτήματα εκτελεί τις κατάλληλες [ενέργειες |application:presenters#life-cycle-of-presenter] και αποδίδει τα πρότυπα. +Router +------ +Αμφίδρομος μεταφραστής μεταξύ HTTP request / URL και ενέργειας presenter. Αμφίδρομο σημαίνει ότι από το HTTP request μπορεί να συναχθεί η [ενέργεια presenter |#Action presenter], αλλά και αντίστροφα, για μια ενέργεια μπορεί να δημιουργηθεί το αντίστοιχο URL. Περισσότερα στο κεφάλαιο για τη [δρομολόγηση URL |application:routing]. -Δρομολογητής .[#toc-router] ---------------------------- -Αμφίδρομος μεταφραστής μεταξύ αιτήματος HTTP / URL και δράσης του παρουσιαστή. Αμφίδρομος σημαίνει ότι δεν είναι μόνο δυνατό να προκύψει μια [ενέργεια του παρουσιαστή |#presenter action] από το αίτημα HTTP, αλλά και να δημιουργηθεί η κατάλληλη διεύθυνση URL για μια ενέργεια. Δείτε περισσότερα στο κεφάλαιο σχετικά με τη [δρομολόγηση URL |application:routing]. +SameSite cookie +--------------- +Τα SameSite cookies παρέχουν έναν μηχανισμό για την αναγνώριση του τι οδήγησε στη φόρτωση της σελίδας. Μπορεί να έχει τρεις τιμές: `Lax`, `Strict` και `None` (αυτή απαιτεί HTTPS). Αν το αίτημα για τη σελίδα προέρχεται απευθείας από τον ιστότοπο ή ο χρήστης ανοίγει τη σελίδα πληκτρολογώντας απευθείας στη γραμμή διευθύνσεων ή κάνοντας κλικ σε έναν σελιδοδείκτη, ο περιηγητής στέλνει στον διακομιστή όλα τα cookies (δηλαδή με τις σημαίες `Lax`, `Strict` και `None`). Αν ο χρήστης μεταβεί στον ιστότοπο κάνοντας κλικ σε έναν σύνδεσμο από άλλο ιστότοπο, παραδίδονται στον διακομιστή τα cookies με τις σημαίες `Lax` και `None`. Αν το αίτημα προκύψει με άλλο τρόπο, όπως η υποβολή μιας φόρμας POST από άλλο ιστότοπο, η φόρτωση μέσα σε ένα iframe, με χρήση JavaScript, κ.λπ., αποστέλλονται μόνο τα cookies με τη σημαία `None`. -SameSite Cookie .[#toc-samesite-cookie] ---------------------------------------- -Τα cookies SameSite παρέχουν έναν μηχανισμό αναγνώρισης του τι οδήγησε στη φόρτωση της σελίδας. Μπορεί να έχει τρεις τιμές: `Lax`, `Strict` και `None` (το τελευταίο απαιτεί HTTPS). Εάν το αίτημα για τη σελίδα προέρχεται απευθείας από τον ιστότοπο ή ο χρήστης ανοίγει τη σελίδα πληκτρολογώντας απευθείας στη γραμμή διευθύνσεων ή κάνοντας κλικ σε έναν σελιδοδείκτη, το πρόγραμμα περιήγησης στέλνει όλα τα cookies στον διακομιστή (δηλαδή με τις σημαίες `Lax`, `Strict` και `None`). Εάν ο χρήστης κάνει κλικ στον ιστότοπο μέσω συνδέσμου από άλλον ιστότοπο, τα cookies με τις σημαίες `Lax` και `None` διαβιβάζονται στον διακομιστή. Εάν το αίτημα γίνεται με άλλα μέσα, όπως η υποβολή μιας φόρμας POST από άλλον ιστότοπο, η φόρτωση μέσα σε ένα iframe, η χρήση JavaScript κ.λπ., αποστέλλονται μόνο cookies με τη σημαία `None`. +Service +------- +Στο πλαίσιο του Dependency Injection, ως service ορίζεται ένα αντικείμενο που δημιουργείται και διαχειρίζεται από τον DI container. Μια service μπορεί εύκολα να αντικατασταθεί από μια άλλη υλοποίηση, για παράδειγμα για σκοπούς δοκιμών ή για την αλλαγή της συμπεριφοράς της εφαρμογής, χωρίς να χρειάζεται να τροποποιηθεί ο κώδικας που χρησιμοποιεί τη service. -Υπηρεσία .[#toc-service] ------------------------- -Στο πλαίσιο του Dependency Injection, μια υπηρεσία αναφέρεται σε ένα αντικείμενο που δημιουργείται και διαχειρίζεται από ένα DI container. Μια υπηρεσία μπορεί εύκολα να αντικατασταθεί από μια άλλη υλοποίηση, για παράδειγμα για σκοπούς δοκιμών ή για να αλλάξει η συμπεριφορά μιας εφαρμογής, χωρίς να χρειάζεται να τροποποιηθεί ο κώδικας που χρησιμοποιεί την υπηρεσία. +Snippet +------- +Απόσπασμα, τμήμα της σελίδας που μπορεί να επανασχεδιαστεί ανεξάρτητα κατά τη διάρκεια ενός αιτήματος AJAX. -Αποσπάσματα .[#toc-snippet] ---------------------------- -Απόσπασμα μιας σελίδας, το οποίο μπορεί να επαναπροβληθεί ξεχωριστά κατά τη διάρκεια μιας αίτησης [AJAX |#AJAX]. +View +---- +Το View, δηλαδή η όψη, είναι το επίπεδο της εφαρμογής που είναι υπεύθυνο για την εμφάνιση του αποτελέσματος ενός αιτήματος. Συνήθως χρησιμοποιεί ένα σύστημα προτύπων και γνωρίζει πώς να εμφανίσει το κάθε component ή το αποτέλεσμα που λαμβάνεται από το model. -Προβολή .[#toc-view] --------------------- -Η προβολή είναι ένα επίπεδο εφαρμογής, το οποίο είναι υπεύθυνο για την απόδοση των αποτελεσμάτων της αίτησης. Συνήθως χρησιμοποιεί ένα σύστημα διαμόρφωσης προτύπων και γνωρίζει, πώς να απεικονίσει τα στοιχεία του ή τα αποτελέσματα που λαμβάνονται από το μοντέλο. diff --git a/nette/el/installation.texy b/nette/el/installation.texy index d99ca71ff0..9bde7989e5 100644 --- a/nette/el/installation.texy +++ b/nette/el/installation.texy @@ -1,18 +1,18 @@ -Εγκατάσταση Nette -***************** +Εγκατάσταση του Nette +********************* .[perex] -Θέλετε να αξιοποιήσετε τα οφέλη της Nette στο υπάρχον έργο σας ή σχεδιάζετε να δημιουργήσετε ένα νέο έργο βασισμένο στη Nette; Αυτός ο οδηγός θα σας καθοδηγήσει βήμα προς βήμα στην εγκατάσταση. +Θέλετε να αξιοποιήσετε τα πλεονεκτήματα του Nette στο υπάρχον έργο σας ή ετοιμάζεστε να δημιουργήσετε ένα νέο έργο βασισμένο στο Nette? Αυτός ο οδηγός θα σας καθοδηγήσει στην εγκατάσταση βήμα προς βήμα. -Πώς να προσθέσετε τη Nette στο έργο σας .[#toc-how-to-add-nette-to-your-project] --------------------------------------------------------------------------------- +Πώς να προσθέσετε το Nette στο έργο σας +--------------------------------------- -Η Nette προσφέρει μια συλλογή από χρήσιμα και εξελιγμένα πακέτα (βιβλιοθήκες) για την PHP. Για να τα ενσωματώσετε στο έργο σας, ακολουθήστε τα παρακάτω βήματα: +Το Nette προσφέρει μια συλλογή χρήσιμων και ώριμων πακέτων (βιβλιοθηκών) για PHP. Για να τα ενσωματώσετε στο έργο σας, ακολουθήστε τα παρακάτω βήματα: -1) **Ρύθμιση [του Composer |best-practices:composer]:** Αυτό το εργαλείο είναι απαραίτητο για την εύκολη εγκατάσταση, ενημέρωση και διαχείριση των βιβλιοθηκών που απαιτούνται για το έργο σας. +1) **Προετοιμάστε το [Composer|best-practices:composer]:** Αυτό το εργαλείο είναι απαραίτητο για την εύκολη εγκατάσταση, ενημέρωση και διαχείριση των βιβλιοθηκών που απαιτούνται για το έργο σας. -2) **Επιλέξτε ένα [πακέτο |www:packages]:** Ας υποθέσουμε ότι πρέπει να πλοηγηθείτε στο σύστημα αρχείων, κάτι που το κάνει εξαιρετικά το [Finder |utils:finder] από το πακέτο `nette/utils`. Μπορείτε να βρείτε το όνομα του πακέτου στη δεξιά στήλη της τεκμηρίωσής του. +2) **Επιλέξτε ένα [πακέτο|www:packages]:** Ας υποθέσουμε ότι χρειάζεστε να περιηγηθείτε στο σύστημα αρχείων, κάτι που κάνει εξαιρετικά το [Finder|utils:finder] από το πακέτο `nette/utils`. Το όνομα του πακέτου φαίνεται στη δεξιά στήλη της τεκμηρίωσής του. 3) **Εγκαταστήστε το πακέτο:** Εκτελέστε αυτή την εντολή στον ριζικό κατάλογο του έργου σας: @@ -20,48 +20,48 @@ composer require nette/utils ``` -Προτιμάτε ένα γραφικό περιβάλλον εργασίας; Ανατρέξτε στον [οδηγό |https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] για την εγκατάσταση πακέτων στο περιβάλλον PhpStrom. +Προτιμάτε γραφικό περιβάλλον; Δείτε τον [οδηγό|https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] για την εγκατάσταση πακέτων στο περιβάλλον PhpStorm. -Πώς να ξεκινήσετε ένα νέο έργο με τη Nette .[#toc-how-to-start-a-new-project-with-nette] ----------------------------------------------------------------------------------------- +Πώς να ξεκινήσετε ένα νέο έργο με το Nette +------------------------------------------ -Εάν θέλετε να δημιουργήσετε ένα εντελώς νέο έργο στην πλατφόρμα Nette, σας συνιστούμε να χρησιμοποιήσετε το προκαθορισμένο σκελετό [Web Project |https://github.com/nette/web-project]: +Αν θέλετε να δημιουργήσετε ένα εντελώς νέο έργο στην πλατφόρμα Nette, συνιστούμε να χρησιμοποιήσετε τον προκαθορισμένο σκελετό [Web Project|https://github.com/nette/web-project]: -1) **Ρυθμίστε το [Composer |best-practices:composer].** +1) **Προετοιμάστε το [Composer|best-practices:composer].** -2) **Ανοίξτε τη γραμμή εντολών** και πλοηγηθείτε στον ριζικό κατάλογο του διακομιστή ιστού σας, π.χ. `/etc/var/www`, `C:/xampp/htdocs`, `/Library/WebServer/Documents`. +2) **Ανοίξτε τη γραμμή εντολών** και μεταβείτε στον ριζικό κατάλογο του web server σας, π.χ. `/etc/var/www`, `C:/xampp/htdocs`, `/Library/WebServer/Documents`. 3) **Δημιουργήστε το έργο** χρησιμοποιώντας αυτή την εντολή: ```shell -composer create-project nette/web-project PROJECT_NAME +composer create-project nette/web-project ΟΝΟΜΑ_ΕΡΓΟΥ ``` -4) **Δεν χρησιμοποιείτε το Composer;** Απλά κατεβάστε το [Web Project σε μορφή ZIP |https://github.com/nette/web-project/archive/preloaded.zip] και αποσυμπιέστε το. Αλλά πιστέψτε μας, το Composer αξίζει τον κόπο! +4) **Δεν χρησιμοποιείτε το Composer;** Απλώς κατεβάστε το [Web Project σε μορφή ZIP|https://github.com/nette/web-project/archive/preloaded.zip] και αποσυμπιέστε το. Αλλά πιστέψτε μας, το Composer αξίζει τον κόπο! -5) **Ρύθμιση δικαιωμάτων:** Σε συστήματα macOS ή Linux, ορίστε [δικαιώματα εγγραφής |nette:troubleshooting#Setting directory permissions] για τους καταλόγους. +5) **Ρύθμιση δικαιωμάτων:** Σε συστήματα macOS ή Linux, ορίστε [δικαιώματα εγγραφής |nette:troubleshooting#Ρύθμιση δικαιωμάτων καταλόγου] στους καταλόγους `temp/` και `log/`. -6) **Ανοίξτε το έργο σε ένα πρόγραμμα περιήγησης:** Εισάγετε τη διεύθυνση URL `http://localhost/PROJECT_NAME/www/`. Θα δείτε τη σελίδα προορισμού του σκελετού: +6) **Άνοιγμα του έργου στον περιηγητή:** Πληκτρολογήστε το URL `http://localhost/ΟΝΟΜΑ_ΕΡΓΟΥ/www/` και θα δείτε την αρχική σελίδα του σκελετού: -[* qs-welcome.webp .{url: http://localhost/PROJECT_NAME/www/} *] +[* qs-welcome.webp .{url: http://localhost/ΟΝΟΜΑ_ΕΡΓΟΥ/www/} *] -Συγχαρητήρια! Ο ιστότοπός σας είναι πλέον έτοιμος για ανάπτυξη. Μπορείτε να αφαιρέσετε το πρότυπο καλωσορίσματος και να ξεκινήσετε την κατασκευή της εφαρμογής σας. +Συγχαρητήρια! Ο ιστότοπός σας είναι τώρα έτοιμος για ανάπτυξη. Μπορείτε να αφαιρέσετε το πρότυπο καλωσορίσματος και να αρχίσετε να δημιουργείτε την εφαρμογή σας. -Ένα από τα πλεονεκτήματα της Nette είναι ότι το έργο λειτουργεί αμέσως χωρίς να χρειάζεται διαμόρφωση. Ωστόσο, αν αντιμετωπίσετε οποιαδήποτε προβλήματα, σκεφτείτε να εξετάσετε τις [κοινές λύσεις προβλημάτων |nette:troubleshooting#nette-is-not-working-white-page-is-displayed]. +Ένα από τα πλεονεκτήματα του Nette είναι ότι το έργο λειτουργεί αμέσως χωρίς την ανάγκη διαμόρφωσης. Αν όμως αντιμετωπίσετε προβλήματα, δοκιμάστε να δείτε τις [λύσεις σε συχνά προβλήματα |nette:troubleshooting#Το Nette δεν λειτουργεί εμφανίζεται μια λευκή σελίδα]. .[note] -Αν ξεκινάτε με τη Nette, σας συνιστούμε να συνεχίσετε με το [σεμινάριο Δημιουργία της πρώτης σας εφαρμογής |quickstart:]. +Αν ξεκινάτε με το Nette, συνιστούμε να συνεχίσετε με το [tutorial Γράφοντας την πρώτη σας εφαρμογή|quickstart:]. -Εργαλεία και συστάσεις .[#toc-tools-and-recommendations] --------------------------------------------------------- +Εργαλεία και συστάσεις +---------------------- -Για αποτελεσματική εργασία με τη Nette, συνιστούμε τα ακόλουθα εργαλεία: +Για αποτελεσματική εργασία με το Nette, συνιστούμε τα ακόλουθα εργαλεία: -- [IDE υψηλής ποιότητας με πρόσθετα για τη Nette |best-practices:editors-and-tools] -- Σύστημα ελέγχου εκδόσεων Git -- [Composer |best-practices:composer] +- [Ένα ποιοτικό IDE με πρόσθετα για το Nette|best-practices:editors-and-tools] +- Σύστημα διαχείρισης εκδόσεων Git +- [Composer|best-practices:composer] {{leftbar: www:@menu-common}} diff --git a/nette/el/introduction-to-object-oriented-programming.texy b/nette/el/introduction-to-object-oriented-programming.texy new file mode 100644 index 0000000000..b0346e2e05 --- /dev/null +++ b/nette/el/introduction-to-object-oriented-programming.texy @@ -0,0 +1,841 @@ +Εισαγωγή στον αντικειμενοστραφή προγραμματισμό +********************************************** + +.[perex] +Ο όρος "OOP" αναφέρεται στον αντικειμενοστραφή προγραμματισμό, ο οποίος είναι ένας τρόπος οργάνωσης και δόμησης του κώδικα. Ο OOP μας επιτρέπει να βλέπουμε ένα πρόγραμμα ως ένα σύνολο αντικειμένων που επικοινωνούν μεταξύ τους, αντί για μια ακολουθία εντολών και συναρτήσεων. + +Στον OOP, ένα "αντικείμενο" είναι μια μονάδα που περιέχει δεδομένα και συναρτήσεις που λειτουργούν με αυτά τα δεδομένα. Τα αντικείμενα δημιουργούνται σύμφωνα με "κλάσεις", τις οποίες μπορούμε να κατανοήσουμε ως σχέδια ή πρότυπα για αντικείμενα. Όταν έχουμε μια κλάση, μπορούμε να δημιουργήσουμε ένα "στιγμιότυπο" της, το οποίο είναι ένα συγκεκριμένο αντικείμενο που δημιουργήθηκε σύμφωνα με αυτή την κλάση. + +Ας δείξουμε πώς μπορούμε να δημιουργήσουμε μια απλή κλάση στην PHP. Κατά τον ορισμό μιας κλάσης, χρησιμοποιούμε τη λέξη-κλειδί "class", ακολουθούμενη από το όνομα της κλάσης και στη συνέχεια αγκύλες που περικλείουν τις συναρτήσεις (ονομάζονται "μέθοδοι") και τις μεταβλητές της κλάσης (ονομάζονται "ιδιότητες" ή "property" στα αγγλικά): + +```php +class Aftokinito +{ + function korna() + { + echo 'Μπιπ μπιπ!'; + } +} +``` + +Σε αυτό το παράδειγμα, δημιουργήσαμε μια κλάση με το όνομα `Aftokinito` με μία συνάρτηση (ή "μέθοδο") που ονομάζεται `korna`. + +Κάθε κλάση πρέπει να επιλύει μόνο μία κύρια εργασία. Εάν μια κλάση κάνει πάρα πολλά πράγματα, μπορεί να είναι σκόπιμο να την χωρίσετε σε μικρότερες, εξειδικευμένες κλάσεις. + +Οι κλάσεις συνήθως αποθηκεύονται σε ξεχωριστά αρχεία, ώστε ο κώδικας να είναι οργανωμένος και εύκολος στην πλοήγηση. Το όνομα του αρχείου πρέπει να αντιστοιχεί στο όνομα της κλάσης, οπότε για την κλάση `Aftokinito`, το όνομα του αρχείου θα ήταν `Aftokinito.php`. + +Κατά την ονομασία των κλάσεων, είναι καλό να ακολουθείτε τη σύμβαση "PascalCase", που σημαίνει ότι κάθε λέξη στο όνομα ξεκινά με κεφαλαίο γράμμα και δεν υπάρχουν κάτω παύλες ή άλλοι διαχωριστές μεταξύ τους. Οι μέθοδοι και οι ιδιότητες χρησιμοποιούν τη σύμβαση "camelCase", που σημαίνει ότι ξεκινούν με μικρό γράμμα. + +Ορισμένες μέθοδοι στην PHP έχουν ειδικούς ρόλους και επισημαίνονται με το πρόθεμα `__` (δύο κάτω παύλες). Μία από τις πιο σημαντικές ειδικές μεθόδους είναι ο "κατασκευαστής", ο οποίος επισημαίνεται ως `__construct`. Ο κατασκευαστής είναι μια μέθοδος που καλείται αυτόματα όταν δημιουργείτε ένα νέο στιγμιότυπο της κλάσης. + +Ο κατασκευαστής χρησιμοποιείται συχνά για να ορίσει την αρχική κατάσταση του αντικειμένου. Για παράδειγμα, όταν δημιουργείτε ένα αντικείμενο που αντιπροσωπεύει ένα άτομο, μπορείτε να χρησιμοποιήσετε τον κατασκευαστή για να ορίσετε την ηλικία, το όνομα ή άλλες ιδιότητές του. + +Ας δείξουμε πώς να χρησιμοποιήσετε έναν κατασκευαστή στην PHP: + +```php +class Atomο +{ + private $ilikia; + + function __construct($ilikia) + { + $this->ilikia = $ilikia; + } + + function posoChrononEisai() + { + return $this->ilikia; + } +} + +$atomo = new Atomο(25); +echo $atomo->posoChrononEisai(); // Εκτυπώνει: 25 +``` + +Σε αυτό το παράδειγμα, η κλάση `Atomο` έχει μια ιδιότητα (μεταβλητή) `$ilikia` και έναν κατασκευαστή που ορίζει αυτή την ιδιότητα. Η μέθοδος `posoChrononEisai()` επιτρέπει στη συνέχεια την πρόσβαση στην ηλικία του ατόμου. + +Η ψευδομεταβλητή `$this` χρησιμοποιείται μέσα στην κλάση για πρόσβαση στις ιδιότητες και τις μεθόδους του αντικειμένου. + +Η λέξη-κλειδί `new` χρησιμοποιείται για τη δημιουργία ενός νέου στιγμιότυπου της κλάσης. Στο παραπάνω παράδειγμα, δημιουργήσαμε ένα νέο άτομο με ηλικία 25. + +Μπορείτε επίσης να ορίσετε προεπιλεγμένες τιμές για τις παραμέτρους του κατασκευαστή, εάν δεν καθορίζονται κατά τη δημιουργία του αντικειμένου. Για παράδειγμα: + +```php +class Atomο +{ + private $ilikia; + + function __construct($ilikia = 20) + { + $this->ilikia = $ilikia; + } + + function posoChrononEisai() + { + return $this->ilikia; + } +} + +$atomo = new Atomο; // αν δεν περνάμε κανένα όρισμα, οι παρενθέσεις μπορούν να παραλειφθούν +echo $atomo->posoChrononEisai(); // Εκτυπώνει: 20 +``` + +Σε αυτό το παράδειγμα, εάν δεν καθορίσετε την ηλικία κατά τη δημιουργία του αντικειμένου `Atomο`, θα χρησιμοποιηθεί η προεπιλεγμένη τιμή 20. + +Είναι ευχάριστο το γεγονός ότι ο ορισμός της ιδιότητας με την αρχικοποίησή της μέσω του κατασκευαστή μπορεί να συντομευτεί και να απλοποιηθεί ως εξής: + +```php +class Atomο +{ + function __construct( + private $ilikia = 20, + ) { + } +} +``` + +Για λόγους πληρότητας, εκτός από τους κατασκευαστές, τα αντικείμενα μπορούν να έχουν και καταστροφείς (μέθοδος `__destruct`), οι οποίοι καλούνται πριν το αντικείμενο απελευθερωθεί από τη μνήμη. + + +Ονοματοχώροι +------------ + +Οι ονοματοχώροι (ή "namespaces" στα αγγλικά) μας επιτρέπουν να οργανώνουμε και να ομαδοποιούμε σχετικές κλάσεις, συναρτήσεις και σταθερές, αποφεύγοντας ταυτόχρονα τις συγκρούσεις ονομάτων. Μπορείτε να τους φανταστείτε σαν φακέλους στον υπολογιστή σας, όπου κάθε φάκελος περιέχει αρχεία που ανήκουν σε ένα συγκεκριμένο έργο ή θέμα. + +Οι ονοματοχώροι είναι ιδιαίτερα χρήσιμοι σε μεγαλύτερα έργα ή όταν χρησιμοποιείτε βιβλιοθήκες τρίτων, όπου θα μπορούσαν να προκύψουν συγκρούσεις στα ονόματα των κλάσεων. + +Φανταστείτε ότι έχετε μια κλάση με το όνομα `Aftokinito` στο έργο σας και θέλετε να την τοποθετήσετε σε έναν ονοματοχώρο που ονομάζεται `Metafores`. Θα το κάνετε ως εξής: + +```php +namespace Metafores; + +class Aftokinito +{ + function korna() + { + echo 'Μπιπ μπιπ!'; + } +} +``` + +Αν θέλετε να χρησιμοποιήσετε την κλάση `Aftokinito` σε ένα άλλο αρχείο, πρέπει να καθορίσετε από ποιον ονοματοχώρο προέρχεται η κλάση: + +```php +$auto = new Metafores\Aftokinito; +``` + +Για απλοποίηση, μπορείτε στην αρχή του αρχείου να δηλώσετε ποια κλάση από τον δεδομένο ονοματοχώρο θέλετε να χρησιμοποιήσετε, επιτρέποντας τη δημιουργία στιγμιότυπων χωρίς να χρειάζεται να αναφέρετε ολόκληρη τη διαδρομή: + +```php +use Metafores\Aftokinito; + +$auto = new Aftokinito; +``` + + +Κληρονομικότητα +--------------- + +Η κληρονομικότητα είναι ένα εργαλείο του αντικειμενοστραφούς προγραμματισμού που επιτρέπει τη δημιουργία νέων κλάσεων βάσει ήδη υπαρχουσών κλάσεων, την ανάληψη των ιδιοτήτων και των μεθόδων τους και την επέκταση ή τον επαναπροσδιορισμό τους ανάλογα με τις ανάγκες. Η κληρονομικότητα επιτρέπει την εξασφάλιση της επαναχρησιμοποίησης του κώδικα και την ιεραρχία των κλάσεων. + +Με απλά λόγια, αν έχουμε μια κλάση και θέλουμε να δημιουργήσουμε μια άλλη, παράγωγη από αυτήν, αλλά με μερικές αλλαγές, μπορούμε να "κληρονομήσουμε" τη νέα κλάση από την αρχική κλάση. + +Στην PHP, η κληρονομικότητα υλοποιείται με τη χρήση της λέξης-κλειδιού `extends`. + +Η κλάση μας `Atomο` αποθηκεύει πληροφορίες για την ηλικία. Μπορούμε να έχουμε μια άλλη κλάση `Foititis`, η οποία επεκτείνει την `Atomο` και προσθέτει πληροφορίες για τον τομέα σπουδών. + +Ας δούμε ένα παράδειγμα: + +```php +class Atomο +{ + private $ilikia; + + function __construct($ilikia) + { + $this->ilikia = $ilikia; + } + + function emfanisePlirofories() + { + echo "Ηλικία: {$this->ilikia} έτη\n"; + } +} + +class Foititis extends Atomο +{ + private $tomeas; + + function __construct($ilikia, $tomeas) + { + parent::__construct($ilikia); + $this->tomeas = $tomeas; + } + + function emfanisePlirofories() + { + parent::emfanisePlirofories(); + echo "Τομέας σπουδών: {$this->tomeas} \n"; + } +} + +$foititis = new Foititis(20, 'Πληροφορική'); +$foititis->emfanisePlirofories(); +``` + +Πώς λειτουργεί αυτός ο κώδικας; + +- Χρησιμοποιήσαμε τη λέξη-κλειδί `extends` για να επεκτείνουμε την κλάση `Atomο`, πράγμα που σημαίνει ότι η κλάση `Foititis` κληρονομεί όλες τις μεθόδους και τις ιδιότητες από την `Atomο`. + +- Η λέξη-κλειδί `parent::` μας επιτρέπει να καλούμε μεθόδους από την γονική κλάση. Σε αυτή την περίπτωση, καλέσαμε τον κατασκευαστή από την κλάση `Atomο` πριν προσθέσουμε τη δική μας λειτουργικότητα στην κλάση `Foititis`. Και ομοίως, τη μέθοδο `emfanisePlirofories()` του προγόνου πριν εμφανίσουμε τις πληροφορίες για τον φοιτητή. + +Η κληρονομικότητα προορίζεται για καταστάσεις όπου υπάρχει μια σχέση "είναι" μεταξύ των κλάσεων. Για παράδειγμα, ο `Foititis` είναι `Atomο`. Η γάτα είναι ζώο. Μας δίνει τη δυνατότητα σε περιπτώσεις όπου ο κώδικας αναμένει ένα αντικείμενο (π.χ. "Atomο"), να χρησιμοποιήσουμε αντ' αυτού ένα κληρονομημένο αντικείμενο (π.χ. "Foititis"). + +Είναι σημαντικό να συνειδητοποιήσουμε ότι ο κύριος σκοπός της κληρονομικότητας **δεν είναι** η αποφυγή της διπλοτυπίας του κώδικα. Αντίθετα, η λανθασμένη χρήση της κληρονομικότητας μπορεί να οδηγήσει σε πολύπλοκο και δύσκολα συντηρήσιμο κώδικα. Εάν η σχέση "είναι" μεταξύ των κλάσεων δεν υπάρχει, θα πρέπει να εξετάσουμε τη σύνθεση αντί της κληρονομικότητας. + +Σημειώστε ότι οι μέθοδοι `emfanisePlirofories()` στις κλάσεις `Atomο` και `Foititis` εμφανίζουν ελαφρώς διαφορετικές πληροφορίες. Και μπορούμε να προσθέσουμε και άλλες κλάσεις (για παράδειγμα `Ypallilos`), οι οποίες θα παρέχουν άλλες υλοποιήσεις αυτής της μεθόδου. Η ικανότητα των αντικειμένων διαφορετικών κλάσεων να αντιδρούν στην ίδια μέθοδο με διαφορετικούς τρόπους ονομάζεται πολυμορφισμός: + +```php +$atoma = [ + new Atomο(30), + new Foititis(20, 'Πληροφορική'), + new Ypallilos(45, 'Διευθυντής'), +]; + +foreach ($atoma as $atomo) { + $atomo->emfanisePlirofories(); +} +``` + + +Σύνθεση +------- + +Η σύνθεση είναι μια τεχνική όπου, αντί να κληρονομούμε τις ιδιότητες και τις μεθόδους μιας άλλης κλάσης, απλώς χρησιμοποιούμε ένα στιγμιότυπό της στην κλάση μας. Αυτό μας επιτρέπει να συνδυάζουμε λειτουργικότητες και ιδιότητες πολλαπλών κλάσεων χωρίς την ανάγκη δημιουργίας πολύπλοκων κληρονομικών δομών. + +Ας δούμε ένα παράδειγμα. Έχουμε μια κλάση `Kinitiras` και μια κλάση `Aftokinito`. Αντί να λέμε "Το αυτοκίνητο είναι κινητήρας", λέμε "Το αυτοκίνητο έχει κινητήρα", που είναι μια τυπική σχέση σύνθεσης. + +```php +class Kinitiras +{ + function ekkinise() + { + echo 'Ο κινητήρας λειτουργεί.'; + } +} + +class Aftokinito +{ + private $motor; + + function __construct() + { + $this->motor = new Kinitiras; + } + + function start() + { + $this->motor->ekkinise(); + echo 'Το αυτοκίνητο είναι έτοιμο για οδήγηση!'; + } +} + +$auto = new Aftokinito; +$auto->start(); +``` + +Εδώ, η κλάση `Aftokinito` δεν έχει όλες τις ιδιότητες και τις μεθόδους της κλάσης `Kinitiras`, αλλά έχει πρόσβαση σε αυτήν μέσω της ιδιότητας `$motor`. + +Το πλεονέκτημα της σύνθεσης είναι η μεγαλύτερη ευελιξία στο σχεδιασμό και η καλύτερη δυνατότητα τροποποιήσεων στο μέλλον. + + +Ορατότητα +--------- + +Στην PHP, μπορείτε να ορίσετε την "ορατότητα" για τις ιδιότητες, τις μεθόδους και τις σταθερές μιας κλάσης. Η ορατότητα καθορίζει από πού μπορείτε να έχετε πρόσβαση σε αυτά τα στοιχεία. + +1. **Public:** Εάν ένα στοιχείο επισημαίνεται ως `public`, σημαίνει ότι μπορείτε να έχετε πρόσβαση σε αυτό από οπουδήποτε, ακόμα και εκτός της κλάσης. + +2. **Protected:** Ένα στοιχείο με την επισήμανση `protected` είναι προσβάσιμο μόνο εντός της συγκεκριμένης κλάσης και όλων των απογόνων της (κλάσεων που κληρονομούν από αυτή την κλάση). + +3. **Private:** Εάν ένα στοιχείο είναι `private`, μπορείτε να έχετε πρόσβαση σε αυτό μόνο από το εσωτερικό της κλάσης στην οποία ορίστηκε. + +Εάν δεν καθορίσετε την ορατότητα, η PHP την ορίζει αυτόματα σε `public`. + +Ας δούμε ένα δείγμα κώδικα: + +```php +class ParadeigmaOratotitas +{ + public $dimosiaIdiotita = 'Δημόσια'; + protected $prostatevmeniIdiotita = 'Προστατευμένη'; + private $idiotikiIdiotita = 'Ιδιωτική'; + + public function emfaniseIdiotites() + { + echo $this->dimosiaIdiotita; // Λειτουργεί + echo $this->prostatevmeniIdiotita; // Λειτουργεί + echo $this->idiotikiIdiotita; // Λειτουργεί + } +} + +$objekt = new ParadeigmaOratotitas; +$objekt->emfaniseIdiotites(); +echo $objekt->dimosiaIdiotita; // Λειτουργεί +// echo $objekt->prostatevmeniIdiotita; // Προκαλεί σφάλμα +// echo $objekt->idiotikiIdiotita; // Προκαλεί σφάλμα +``` + +Συνεχίζουμε με την κληρονομικότητα της κλάσης: + +```php +class ApogonosKlaseis extends ParadeigmaOratotitas +{ + public function emfaniseIdiotites() + { + echo $this->dimosiaIdiotita; // Λειτουργεί + echo $this->prostatevmeniIdiotita; // Λειτουργεί + // echo $this->idiotikiIdiotita; // Προκαλεί σφάλμα + } +} +``` + +Σε αυτή την περίπτωση, η μέθοδος `emfaniseIdiotites()` στην κλάση `ApogonosKlaseis` μπορεί να έχει πρόσβαση στις δημόσιες και προστατευμένες ιδιότητες, αλλά δεν μπορεί να έχει πρόσβαση στις ιδιωτικές ιδιότητες της γονικής κλάσης. + +Τα δεδομένα και οι μέθοδοι πρέπει να είναι όσο το δυνατόν πιο κρυμμένα και προσβάσιμα μόνο μέσω μιας καθορισμένης διεπαφής. Αυτό σας επιτρέπει να αλλάξετε την εσωτερική υλοποίηση της κλάσης χωρίς να επηρεάσετε τον υπόλοιπο κώδικα. + + +Λέξη-κλειδί `final` +------------------- + +Στην PHP, μπορούμε να χρησιμοποιήσουμε τη λέξη-κλειδί `final`, εάν θέλουμε να αποτρέψουμε την κληρονομικότητα ή την παράκαμψη μιας κλάσης, μεθόδου ή σταθεράς. Όταν επισημαίνουμε μια κλάση ως `final`, δεν μπορεί να επεκταθεί. Όταν επισημαίνουμε μια μέθοδο ως `final`, δεν μπορεί να παρακαμφθεί σε μια κλάση απογόνου. + +Η γνώση ότι μια συγκεκριμένη κλάση ή μέθοδος δεν θα τροποποιηθεί περαιτέρω μας επιτρέπει να κάνουμε αλλαγές πιο εύκολα, χωρίς να χρειάζεται να ανησυχούμε για πιθανές συγκρούσεις. Για παράδειγμα, μπορούμε να προσθέσουμε μια νέα μέθοδο χωρίς να ανησυχούμε ότι κάποιος από τους απογόνους της έχει ήδη μια μέθοδο με το ίδιο όνομα και θα προκληθεί σύγκρουση. Ή μπορούμε να αλλάξουμε τις παραμέτρους μιας μεθόδου, καθώς και πάλι δεν υπάρχει κίνδυνος να προκαλέσουμε ασυμφωνία με την παρακαμφθείσα μέθοδο στον απόγονο. + +```php +final class TelikiKlasi +{ +} + +// Ο παρακάτω κώδικας θα προκαλέσει σφάλμα, επειδή δεν μπορούμε να κληρονομήσουμε από μια τελική κλάση. +class ApogonosTelikisKlasis extends TelikiKlasi +{ +} +``` + +Σε αυτό το παράδειγμα, η προσπάθεια κληρονομικότητας από την τελική κλάση `TelikiKlasi` θα προκαλέσει σφάλμα. + + +Στατικές ιδιότητες και μέθοδοι +------------------------------ + +Όταν στην PHP μιλάμε για "στατικά" στοιχεία μιας κλάσης, εννοούμε μεθόδους και ιδιότητες που ανήκουν στην ίδια την κλάση, και όχι σε ένα συγκεκριμένο στιγμιότυπο αυτής της κλάσης. Αυτό σημαίνει ότι δεν χρειάζεται να δημιουργήσετε ένα στιγμιότυπο της κλάσης για να έχετε πρόσβαση σε αυτά. Αντ' αυτού, τα καλείτε ή έχετε πρόσβαση σε αυτά απευθείας μέσω του ονόματος της κλάσης. + +Έχετε υπόψη ότι, καθώς τα στατικά στοιχεία ανήκουν στην κλάση και όχι στα στιγμιότυπά της, δεν μπορείτε να χρησιμοποιήσετε την ψευδομεταβλητή `$this` μέσα σε στατικές μεθόδους. + +Η χρήση στατικών ιδιοτήτων οδηγεί σε [κώδικα που προκαλεί σύγχυση και είναι γεμάτος παγίδες|dependency-injection:global-state], γι' αυτό δεν πρέπει ποτέ να τις χρησιμοποιείτε και ούτε θα δείξουμε εδώ παράδειγμα χρήσης. Αντίθετα, οι στατικές μέθοδοι είναι χρήσιμες. Παράδειγμα χρήσης: + +```php +class Ypologistis +{ + public static function prosthesi($a, $b) + { + return $a + $b; + } + + public static function afairesi($a, $b) + { + return $a - $b; + } +} + +// Χρήση στατικής μεθόδου χωρίς δημιουργία στιγμιότυπου της κλάσης +echo Ypologistis::prosthesi(5, 3); // Αποτέλεσμα: 8 +echo Ypologistis::afairesi(5, 3); // Αποτέλεσμα: 2 +``` + +Σε αυτό το παράδειγμα, δημιουργήσαμε μια κλάση `Ypologistis` με δύο στατικές μεθόδους. Μπορούμε να καλέσουμε αυτές τις μεθόδους απευθείας χωρίς να δημιουργήσουμε ένα στιγμιότυπο της κλάσης χρησιμοποιώντας τον τελεστή `::`. Οι στατικές μέθοδοι είναι ιδιαίτερα χρήσιμες για λειτουργίες που δεν εξαρτώνται από την κατάσταση ενός συγκεκριμένου στιγμιότυπου της κλάσης. + + +Σταθερές κλάσης +--------------- + +Μέσα στις κλάσεις, έχουμε τη δυνατότητα να ορίσουμε σταθερές. Οι σταθερές είναι τιμές που δεν αλλάζουν ποτέ κατά τη διάρκεια της εκτέλεσης του προγράμματος. Σε αντίθεση με τις μεταβλητές, η τιμή μιας σταθεράς παραμένει πάντα η ίδια. + +```php +class Aftokinito +{ + public const ArithmosTrochon = 4; + + public function emfaniseArithmoTrochon(): int + { + echo self::ArithmosTrochon; + } +} + +echo Aftokinito::ArithmosTrochon; // Έξοδος: 4 +``` + +Σε αυτό το παράδειγμα, έχουμε την κλάση `Aftokinito` με τη σταθερά `ArithmosTrochon`. Όταν θέλουμε να έχουμε πρόσβαση στη σταθερά μέσα στην κλάση, μπορούμε να χρησιμοποιήσουμε τη λέξη-κλειδί `self` αντί για το όνομα της κλάσης. + + +Διεπαφές αντικειμένων +--------------------- + +Οι διεπαφές αντικειμένων λειτουργούν ως "συμβόλαια" για τις κλάσεις. Εάν μια κλάση πρόκειται να υλοποιήσει μια διεπαφή αντικειμένου, πρέπει να περιέχει όλες τις μεθόδους που ορίζει αυτή η διεπαφή. Είναι ένας εξαιρετικός τρόπος για να διασφαλιστεί ότι ορισμένες κλάσεις τηρούν το ίδιο "συμβόλαιο" ή δομή. + +Στην PHP, μια διεπαφή ορίζεται με τη λέξη-κλειδί `interface`. Όλες οι μέθοδοι που ορίζονται σε μια διεπαφή είναι δημόσιες (`public`). Όταν μια κλάση υλοποιεί μια διεπαφή, χρησιμοποιεί τη λέξη-κλειδί `implements`. + +```php +interface Zoo +{ + function kaneIxο(); +} + +class Gata implements Zoo +{ + public function kaneIxο() + { + echo 'Νιάου'; + } +} + +$kocka = new Gata; +$kocka->kaneIxο(); +``` + +Εάν μια κλάση υλοποιεί μια διεπαφή, αλλά δεν ορίζονται σε αυτήν όλες οι αναμενόμενες μέθοδοι, η PHP θα προκαλέσει σφάλμα. + +Μια κλάση μπορεί να υλοποιεί πολλαπλές διεπαφές ταυτόχρονα, πράγμα που διαφέρει από την κληρονομικότητα, όπου μια κλάση μπορεί να κληρονομήσει μόνο από μία κλάση: + +```php +interface Fylakas +{ + function fylaxeSpiti(); +} + +class Skylos implements Zoo, Fylakas +{ + public function kaneIxο() + { + echo 'Γαβ'; + } + + public function fylaxeSpiti() + { + echo 'Ο σκύλος φυλάει προσεκτικά το σπίτι'; + } +} +``` + + +Αφηρημένες κλάσεις +------------------ + +Οι αφηρημένες κλάσεις χρησιμεύουν ως βασικά πρότυπα για άλλες κλάσεις, αλλά δεν μπορείτε να δημιουργήσετε στιγμιότυπά τους απευθείας. Περιέχουν έναν συνδυασμό πλήρων μεθόδων και αφηρημένων μεθόδων, οι οποίες δεν έχουν ορισμένο περιεχόμενο. Οι κλάσεις που κληρονομούν από αφηρημένες κλάσεις πρέπει να παρέχουν ορισμούς για όλες τις αφηρημένες μεθόδους του προγόνου. + +Για να ορίσουμε μια αφηρημένη κλάση, χρησιμοποιούμε τη λέξη-κλειδί `abstract`. + +```php +abstract class AfirimeniKlasi +{ + public function synithismeniMethodos() + { + echo 'Αυτή είναι μια συνηθισμένη μέθοδος'; + } + + abstract public function afirimeniMethodos(); +} + +class Apogonos extends AfirimeniKlasi +{ + public function afirimeniMethodos() + { + echo 'Αυτή είναι η υλοποίηση της αφηρημένης μεθόδου'; + } +} + +$instance = new Apogonos; +$instance->synithismeniMethodos(); +$instance->afirimeniMethodos(); +``` + +Σε αυτό το παράδειγμα, έχουμε μια αφηρημένη κλάση με μία συνηθισμένη και μία αφηρημένη μέθοδο. Στη συνέχεια, έχουμε την κλάση `Apogonos`, η οποία κληρονομεί από την `AfirimeniKlasi` και παρέχει την υλοποίηση για την αφηρημένη μέθοδο. + +Πώς διαφέρουν στην πραγματικότητα οι διεπαφές και οι αφηρημένες κλάσεις; Οι αφηρημένες κλάσεις μπορούν να περιέχουν τόσο αφηρημένες όσο και συγκεκριμένες μεθόδους, ενώ οι διεπαφές ορίζουν μόνο ποιες μεθόδους πρέπει να υλοποιήσει μια κλάση, αλλά δεν παρέχουν καμία υλοποίηση. Μια κλάση μπορεί να κληρονομήσει μόνο από μία αφηρημένη κλάση, αλλά μπορεί να υλοποιήσει οποιονδήποτε αριθμό διεπαφών. + + +Έλεγχος τύπων +------------- + +Στον προγραμματισμό, είναι πολύ σημαντικό να είμαστε σίγουροι ότι τα δεδομένα με τα οποία εργαζόμαστε είναι του σωστού τύπου. Στην PHP, έχουμε εργαλεία που μας το εξασφαλίζουν αυτό. Η επαλήθευση ότι τα δεδομένα έχουν τον σωστό τύπο ονομάζεται "έλεγχος τύπων". + +Οι τύποι που μπορούμε να συναντήσουμε στην PHP: + +1. **Βασικοί τύποι**: Περιλαμβάνουν `int` (ακέραιοι αριθμοί), `float` (δεκαδικοί αριθμοί), `bool` (τιμές αλήθειας), `string` (συμβολοσειρές), `array` (πίνακες) και `null`. +2. **Κλάσεις**: Εάν θέλουμε η τιμή να είναι στιγμιότυπο μιας συγκεκριμένης κλάσης. +3. **Διεπαφές**: Ορίζει ένα σύνολο μεθόδων που πρέπει να υλοποιήσει μια κλάση. Μια τιμή που ικανοποιεί τη διεπαφή πρέπει να έχει αυτές τις μεθόδους. +4. **Μικτοί τύποι**: Μπορούμε να καθορίσουμε ότι μια μεταβλητή μπορεί να έχει πολλούς επιτρεπόμενους τύπους. +5. **Void**: Αυτός ο ειδικός τύπος υποδηλώνει ότι μια συνάρτηση ή μέθοδος δεν επιστρέφει καμία τιμή. + +Ας δείξουμε πώς να τροποποιήσουμε τον κώδικα ώστε να περιλαμβάνει τύπους: + +```php +class Atomο +{ + private int $ilikia; + + public function __construct(int $ilikia) + { + $this->ilikia = $ilikia; + } + + public function emfaniseIlikia(): void + { + echo "Αυτό το άτομο είναι {$this->ilikia} ετών."; + } +} + +/** + * Συνάρτηση που δέχεται ένα αντικείμενο της κλάσης Atomο και εμφανίζει την ηλικία του ατόμου. + */ +function emfaniseIlikiaAtomou(Atomο $atomo): void +{ + $atomo->emfaniseIlikia(); +} +``` + +Με αυτόν τον τρόπο, διασφαλίσαμε ότι ο κώδικάς μας αναμένει και λειτουργεί με δεδομένα του σωστού τύπου, πράγμα που μας βοηθά να προλαμβάνουμε πιθανά σφάλματα. + +Ορισμένοι τύποι δεν μπορούν να γραφτούν απευθείας στην PHP. Σε αυτή την περίπτωση, αναφέρονται στο σχόλιο phpDoc, το οποίο είναι ένα τυπικό μορφότυπο για την τεκμηρίωση του κώδικα PHP που ξεκινά με `/**` και τελειώνει με `*/`. Επιτρέπει την προσθήκη περιγραφών κλάσεων, μεθόδων κ.λπ. Και επίσης την αναφορά σύνθετων τύπων με τη χρήση των λεγόμενων σχολιασμών `@var`, `@param` και `@return`. Αυτοί οι τύποι χρησιμοποιούνται στη συνέχεια από εργαλεία για τη στατική ανάλυση του κώδικα, αλλά η ίδια η PHP δεν τους ελέγχει. + +```php +class Lista +{ + /** @var array<Atomο> η σημειογραφία δηλώνει ότι πρόκειται για έναν πίνακα αντικειμένων Atomο */ + private array $atoma = []; + + public function prosthikiAtomou(Atomο $atomo): void + { + $this->atoma[] = $atomo; + } +} +``` + + +Σύγκριση και ταυτότητα +---------------------- + +Στην PHP, μπορείτε να συγκρίνετε αντικείμενα με δύο τρόπους: + +1. Σύγκριση τιμών `==`: Ελέγχει εάν τα αντικείμενα είναι της ίδιας κλάσης και έχουν τις ίδιες τιμές στις ιδιότητές τους. +2. Ταυτότητα `===`: Ελέγχει εάν πρόκειται για το ίδιο στιγμιότυπο του αντικειμένου. + +```php +class Aftokinito +{ + public string $marka; + + public function __construct(string $marka) + { + $this->marka = $marka; + } +} + +$auto1 = new Aftokinito('Skoda'); +$auto2 = new Aftokinito('Skoda'); +$auto3 = $auto1; + +var_dump($auto1 == $auto2); // true, επειδή έχουν την ίδια τιμή +var_dump($auto1 === $auto2); // false, επειδή δεν είναι το ίδιο στιγμιότυπο +var_dump($auto1 === $auto3); // true, επειδή το $auto3 είναι το ίδιο στιγμιότυπο με το $auto1 +``` + + +Τελεστής `instanceof` +--------------------- + +Ο τελεστής `instanceof` επιτρέπει να διαπιστώσετε εάν ένα δεδομένο αντικείμενο είναι στιγμιότυπο μιας συγκεκριμένης κλάσης, απόγονος αυτής της κλάσης, ή εάν υλοποιεί μια συγκεκριμένη διεπαφή. + +Ας φανταστούμε ότι έχουμε μια κλάση `Atomο` και μια άλλη κλάση `Foititis`, η οποία είναι απόγονος της κλάσης `Atomο`: + +```php +class Atomο +{ + private int $ilikia; + + public function __construct(int $ilikia) + { + $this->ilikia = $ilikia; + } +} + +class Foititis extends Atomο +{ + private string $tomeas; + + public function __construct(int $ilikia, string $tomeas) + { + parent::__construct($ilikia); + $this->tomeas = $tomeas; + } +} + +$student = new Foititis(20, 'Πληροφορική'); + +// Έλεγχος εάν το $student είναι στιγμιότυπο της κλάσης Student +var_dump($student instanceof Foititis); // Έξοδος: bool(true) + +// Έλεγχος εάν το $student είναι στιγμιότυπο της κλάσης Atomο (επειδή το Student είναι απόγονος του Atomο) +var_dump($student instanceof Atomο); // Έξοδος: bool(true) +``` + +Από τις εξόδους είναι φανερό ότι το αντικείμενο `$student` θεωρείται ταυτόχρονα στιγμιότυπο και των δύο κλάσεων - `Foititis` και `Atomο`. + + +Fluent Interfaces +----------------- + +Η "Ρέουσα διεπαφή" (αγγλικά "Fluent Interface") είναι μια τεχνική στον OOP που επιτρέπει την αλυσιδωτή σύνδεση μεθόδων σε μία κλήση. Αυτό συχνά απλοποιεί και καθιστά τον κώδικα πιο ευανάγνωστο. + +Το βασικό στοιχείο της ρέουσας διεπαφής είναι ότι κάθε μέθοδος στην αλυσίδα επιστρέφει μια αναφορά στο τρέχον αντικείμενο. Αυτό επιτυγχάνεται χρησιμοποιώντας `return $this;` στο τέλος της μεθόδου. Αυτό το στυλ προγραμματισμού συνδέεται συχνά με μεθόδους που ονομάζονται "setters", οι οποίες ορίζουν τις τιμές των ιδιοτήτων του αντικειμένου. + +Ας δείξουμε πώς μπορεί να μοιάζει μια ρέουσα διεπαφή σε ένα παράδειγμα αποστολής email: + +```php +public function apostoliMinimatos() +{ + $email = new Email; + $email->setFrom('sender@example.com') + ->setRecipient('admin@example.com') + ->setMessage('Hello, this is a message.') + ->send(); +} +``` + +Σε αυτό το παράδειγμα, οι μέθοδοι `setFrom()`, `setRecipient()` και `setMessage()` χρησιμεύουν για τον ορισμό των αντίστοιχων τιμών (αποστολέας, παραλήπτης, περιεχόμενο μηνύματος). Αφού οριστεί κάθε μία από αυτές τις τιμές, οι μέθοδοι μας επιστρέφουν το τρέχον αντικείμενο (`$email`), επιτρέποντάς μας να συνδέσουμε την επόμενη μέθοδο μετά από αυτήν. Τέλος, καλούμε τη μέθοδο `send()`, η οποία στέλνει πραγματικά το email. + +Χάρη στις ρέουσες διεπαφές, μπορούμε να γράψουμε κώδικα που είναι διαισθητικός και εύκολα αναγνώσιμος. + + +Αντιγραφή με `clone` +-------------------- + +Στην PHP, μπορούμε να δημιουργήσουμε ένα αντίγραφο ενός αντικειμένου χρησιμοποιώντας τον τελεστή `clone`. Με αυτόν τον τρόπο, λαμβάνουμε ένα νέο στιγμιότυπο με πανομοιότυπο περιεχόμενο. + +Εάν χρειάζεται να τροποποιήσουμε ορισμένες ιδιότητες ενός αντικειμένου κατά την αντιγραφή του, μπορούμε να ορίσουμε στην κλάση μια ειδική μέθοδο `__clone()`. Αυτή η μέθοδος καλείται αυτόματα όταν το αντικείμενο κλωνοποιείται. + +```php +class Provato +{ + public string $onoma; + + public function __construct(string $onoma) + { + $this->onoma = $onoma; + } + + public function __clone() + { + $this->onoma = 'Κλώνος ' . $this->onoma; + } +} + +$original = new Provato('Dolly'); +echo $original->onoma . "\n"; // Εκτυπώνει: Dolly + +$klon = clone $original; +echo $klon->onoma . "\n"; // Εκτυπώνει: Κλώνος Dolly +``` + +Σε αυτό το παράδειγμα, έχουμε την κλάση `Provato` με μία ιδιότητα `$onoma`. Όταν κλωνοποιούμε ένα στιγμιότυπο αυτής της κλάσης, η μέθοδος `__clone()` φροντίζει ώστε το όνομα του κλωνοποιημένου προβάτου να λάβει το πρόθεμα "Κλώνος". + + +Traits +------ + +Τα Traits στην PHP είναι ένα εργαλείο που επιτρέπει την κοινή χρήση μεθόδων, ιδιοτήτων και σταθερών μεταξύ κλάσεων και την αποφυγή της διπλοτυπίας του κώδικα. Μπορείτε να τα φανταστείτε ως έναν μηχανισμό "αντιγραφής και επικόλλησης" (Ctrl-C και Ctrl-V), όπου το περιεχόμενο του trait "επικολλάται" στις κλάσεις. Αυτό σας επιτρέπει να επαναχρησιμοποιείτε κώδικα χωρίς την ανάγκη δημιουργίας πολύπλοκων ιεραρχιών κλάσεων. + +Ας δείξουμε ένα απλό παράδειγμα για το πώς να χρησιμοποιείτε τα traits στην PHP: + +```php +trait Kornarisma +{ + public function korna() + { + echo 'Μπιπ μπιπ!'; + } +} + +class Aftokinito +{ + use Kornarisma; +} + +class Fortigo +{ + use Kornarisma; +} + +$auto = new Aftokinito; +$auto->korna(); // Εκτυπώνει 'Μπιπ μπιπ!' + +$nakladak = new Fortigo; +$nakladak->korna(); // Επίσης εκτυπώνει 'Μπιπ μπιπ!' +``` + +Σε αυτό το παράδειγμα, έχουμε ένα trait που ονομάζεται `Kornarisma`, το οποίο περιέχει μία μέθοδο `korna()`. Στη συνέχεια, έχουμε δύο κλάσεις: `Aftokinito` και `Fortigo`, οι οποίες και οι δύο χρησιμοποιούν το trait `Kornarisma`. Χάρη σε αυτό, και οι δύο κλάσεις "έχουν" τη μέθοδο `korna()`, και μπορούμε να την καλέσουμε σε αντικείμενα και των δύο κλάσεων. + +Τα Traits σας επιτρέπουν να μοιράζεστε εύκολα και αποτελεσματικά κώδικα μεταξύ κλάσεων. Ταυτόχρονα, δεν εισέρχονται στην ιεραρχία κληρονομικότητας, δηλαδή το `$auto instanceof Kornarisma` θα επιστρέψει `false`. + + +Εξαιρέσεις +---------- + +Οι εξαιρέσεις στον OOP μας επιτρέπουν να χειριζόμαστε κομψά σφάλματα και απροσδόκητες καταστάσεις στον κώδικά μας. Είναι αντικείμενα που φέρουν πληροφορίες σχετικά με το σφάλμα ή την ασυνήθιστη κατάσταση. + +Στην PHP, έχουμε την ενσωματωμένη κλάση `Exception`, η οποία χρησιμεύει ως βάση για όλες τις εξαιρέσεις. Αυτή έχει αρκετές μεθόδους που μας επιτρέπουν να λάβουμε περισσότερες πληροφορίες σχετικά με την εξαίρεση, όπως το μήνυμα σφάλματος, το αρχείο και τη γραμμή όπου συνέβη το σφάλμα, κ.λπ. + +Όταν συμβεί ένα σφάλμα στον κώδικα, μπορούμε να "πετάξουμε" μια εξαίρεση χρησιμοποιώντας τη λέξη-κλειδί `throw`. + +```php +function diairesi(float $a, float $b): float +{ + if ($b === 0.0) { // Σύγκριση με float, καλύτερα να χρησιμοποιήσετε === 0.0 + throw new Exception('Διαίρεση με το μηδέν!'); + } + return $a / $b; +} +``` + +Όταν η συνάρτηση `diairesi()` λάβει μηδέν ως δεύτερο όρισμα, πετάει μια εξαίρεση με το μήνυμα σφάλματος `'Διαίρεση με το μηδέν!'`. Για να αποτρέψουμε την κατάρρευση του προγράμματος κατά την πρόκληση της εξαίρεσης, την πιάνουμε σε ένα μπλοκ `try/catch`: + +```php +try { + echo diairesi(10, 0); +} catch (Exception $e) { + echo 'Η εξαίρεση εντοπίστηκε: '. $e->getMessage(); +} +``` + +Ο κώδικας που μπορεί να προκαλέσει μια εξαίρεση περικλείεται σε ένα μπλοκ `try`. Εάν προκληθεί μια εξαίρεση, η εκτέλεση του κώδικα μετακινείται στο μπλοκ `catch`, όπου μπορούμε να επεξεργαστούμε την εξαίρεση (π.χ. να εμφανίσουμε ένα μήνυμα σφάλματος). + +Μετά τα μπλοκ `try` και `catch`, μπορούμε να προσθέσουμε ένα προαιρετικό μπλοκ `finally`, το οποίο εκτελείται πάντα, ανεξάρτητα από το αν προκλήθηκε εξαίρεση ή όχι (ακόμα και στην περίπτωση που στο μπλοκ `try` ή `catch` χρησιμοποιήσουμε την εντολή `return`, `break` ή `continue`): + +```php +try { + echo diairesi(10, 0); +} catch (Exception $e) { + echo 'Η εξαίρεση εντοπίστηκε: '. $e->getMessage(); +} finally { + // Κώδικας που εκτελείται πάντα, ανεξάρτητα από το αν προκλήθηκε εξαίρεση ή όχι +} +``` + +Μπορούμε επίσης να δημιουργήσουμε δικές μας κλάσεις (ιεραρχία) εξαιρέσεων, οι οποίες κληρονομούν από την κλάση Exception. Ως παράδειγμα, ας φανταστούμε μια απλή τραπεζική εφαρμογή που επιτρέπει την πραγματοποίηση καταθέσεων και αναλήψεων: + +```php +class TrapezikiExairesi extends Exception {} +class ExairesiAneparkousYpolipou extends TrapezikiExairesi {} +class ExairesiYpervasisOriou extends TrapezikiExairesi {} + +class TrapezikosLogariasmos +{ + private int $ypolipo = 0; + private int $imerisioOrio = 1000; + + public function katathesi(int $poso): int + { + $this->ypolipo += $poso; + return $this->ypolipo; + } + + public function analipsi(int $poso): int + { + if ($poso > $this->ypolipo) { + throw new ExairesiAneparkousYpolipou('Δεν υπάρχει επαρκές υπόλοιπο στον λογαριασμό.'); + } + + if ($poso > $this->imerisioOrio) { + throw new ExairesiYpervasisOriou('Έγινε υπέρβαση του ημερήσιου ορίου αναλήψεων.'); + } + + $this->ypolipo -= $poso; + return $this->ypolipo; + } +} +``` + +Για ένα μπλοκ `try`, μπορούν να αναφερθούν πολλαπλά μπλοκ `catch`, εάν αναμένετε διαφορετικούς τύπους εξαιρέσεων. + +```php +$ucet = new TrapezikosLogariasmos; +$ucet->katathesi(500); + +try { + $ucet->analipsi(1500); +} catch (ExairesiYpervasisOriou $e) { + echo $e->getMessage(); +} catch (ExairesiAneparkousYpolipou $e) { + echo $e->getMessage(); +} catch (TrapezikiExairesi $e) { + echo 'Παρουσιάστηκε σφάλμα κατά την εκτέλεση της λειτουργίας.'; +} +``` + +Σε αυτό το παράδειγμα, είναι σημαντικό να σημειωθεί η σειρά των μπλοκ `catch`. Επειδή όλες οι εξαιρέσεις κληρονομούν από την `TrapezikiExairesi`, εάν είχαμε αυτό το μπλοκ πρώτο, θα πιάνονταν σε αυτό όλες οι εξαιρέσεις, χωρίς ο κώδικας να φτάσει στα επόμενα μπλοκ `catch`. Γι' αυτό είναι σημαντικό να έχουμε τις πιο συγκεκριμένες εξαιρέσεις (δηλαδή αυτές που κληρονομούν από άλλες) στο μπλοκ `catch` πιο πάνω στη σειρά από τις γονικές τους εξαιρέσεις. + + +Επανάληψη +--------- + +Στην PHP, μπορείτε να διατρέξετε αντικείμενα χρησιμοποιώντας τον βρόχο `foreach`, παρόμοια με τον τρόπο που διατρέχετε πίνακες. Για να λειτουργήσει αυτό, το αντικείμενο πρέπει να υλοποιεί μια ειδική διεπαφή. + +Η πρώτη επιλογή είναι να υλοποιήσετε τη διεπαφή `Iterator`, η οποία έχει τις μεθόδους `current()` που επιστρέφει την τρέχουσα τιμή, `key()` που επιστρέφει το κλειδί, `next()` που μετακινείται στην επόμενη τιμή, `rewind()` που μετακινείται στην αρχή και `valid()` που ελέγχει αν δεν έχουμε φτάσει ακόμα στο τέλος. + +Η δεύτερη επιλογή είναι να υλοποιήσετε τη διεπαφή `IteratorAggregate`, η οποία έχει μόνο μία μέθοδο `getIterator()`. Αυτή είτε επιστρέφει ένα αντικείμενο υποκατάστατο που θα εξασφαλίζει τη διάτρεξη, είτε μπορεί να αντιπροσωπεύει μια γεννήτρια, η οποία είναι μια ειδική συνάρτηση στην οποία χρησιμοποιείται το `yield` για τη σταδιακή επιστροφή κλειδιών και τιμών: + +```php +class Atomο +{ + public function __construct( + public int $ilikia, + ) { + } +} + +class Lista implements IteratorAggregate +{ + private array $atoma = []; + + public function prosthikiAtomou(Atomο $atomo): void + { + $this->atoma[] = $atomo; + } + + public function getIterator(): Generator + { + foreach ($this->atoma as $atomo) { + yield $atomo; + } + } +} + +$seznam = new Lista; +$seznam->prosthikiAtomou(new Atomο(30)); +$seznam->prosthikiAtomou(new Atomο(25)); + +foreach ($seznam as $atomo) { + echo "Ηλικία: {$atomo->ilikia} έτη \n"; +} +``` + + +Ορθές πρακτικές +--------------- + +Όταν έχετε κατανοήσει τις βασικές αρχές του αντικειμενοστραφούς προγραμματισμού, είναι σημαντικό να εστιάσετε στις ορθές πρακτικές στον OOP. Αυτές θα σας βοηθήσουν να γράψετε κώδικα που δεν είναι μόνο λειτουργικός, αλλά και ευανάγνωστος, κατανοητός και εύκολα συντηρήσιμος. + +1) **Διαχωρισμός αρμοδιοτήτων (Separation of Concerns)**: Κάθε κλάση πρέπει να έχει μια σαφώς καθορισμένη ευθύνη και να επιλύει μόνο μία κύρια εργασία. Εάν μια κλάση κάνει πάρα πολλά πράγματα, μπορεί να είναι σκόπιμο να την χωρίσετε σε μικρότερες, εξειδικευμένες κλάσεις. +2) **Ενθυλάκωση (Encapsulation)**: Τα δεδομένα και οι μέθοδοι πρέπει να είναι όσο το δυνατόν πιο κρυμμένα και προσβάσιμα μόνο μέσω μιας καθορισμένης διεπαφής. Αυτό σας επιτρέπει να αλλάξετε την εσωτερική υλοποίηση της κλάσης χωρίς να επηρεάσετε τον υπόλοιπο κώδικα. +3) **Έγχυση εξαρτήσεων (Dependency Injection)**: Αντί να δημιουργείτε εξαρτήσεις απευθείας στην κλάση, θα πρέπει να τις "εγχέετε" από έξω. Για μια βαθύτερη κατανόηση αυτής της αρχής, συνιστούμε τα [κεφάλαια για την Έγχυση Εξαρτήσεων|dependency-injection:introduction]. diff --git a/nette/el/troubleshooting.texy b/nette/el/troubleshooting.texy index b17fea9893..47fff7bcf4 100644 --- a/nette/el/troubleshooting.texy +++ b/nette/el/troubleshooting.texy @@ -2,40 +2,69 @@ ************************ -Το Nette δεν λειτουργεί, εμφανίζεται λευκή σελίδα .[#toc-nette-is-not-working-white-page-is-displayed] ------------------------------------------------------------------------------------------------------- -- Δοκιμάστε να βάλετε το `ini_set('display_errors', '1'); error_reporting(E_ALL);` μετά το `declare(strict_types=1);` στο αρχείο `index.php` για να εξαναγκάσετε την εμφάνιση των σφαλμάτων. -- Εάν εξακολουθείτε να βλέπετε μια λευκή οθόνη, πιθανόν να υπάρχει κάποιο σφάλμα στη ρύθμιση του διακομιστή και θα ανακαλύψετε τον λόγο στο αρχείο καταγραφής του διακομιστή. Για να είστε σίγουροι, ελέγξτε αν η PHP λειτουργεί καθόλου προσπαθώντας να εκτυπώσετε κάτι χρησιμοποιώντας το `echo 'test';`. -- Εάν δείτε ένα σφάλμα *Server Error: Λυπούμαστε! ...*, συνεχίστε με την επόμενη ενότητα: +Το Nette δεν λειτουργεί, εμφανίζεται μια λευκή σελίδα +----------------------------------------------------- +- Δοκιμάστε να εισαγάγετε στο αρχείο `index.php` αμέσως μετά το `declare(strict_types=1);` το `ini_set('display_errors', '1'); error_reporting(E_ALL);`, αυτό θα εξαναγκάσει την εμφάνιση σφαλμάτων +- Αν εξακολουθείτε να βλέπετε μια λευκή οθόνη, πιθανότατα υπάρχει σφάλμα στη ρύθμιση του διακομιστή και ο λόγος θα αποκαλυφθεί στο αρχείο καταγραφής του διακομιστή. Για σιγουριά, ελέγξτε επίσης αν η PHP λειτουργεί καθόλου, δοκιμάζοντας να εκτυπώσετε κάτι χρησιμοποιώντας το `echo 'test';` +- Αν βλέπετε το σφάλμα *Server Error: We're sorry! …*, συνεχίστε στην επόμενη ενότητα: -Σφάλμα 500 *Σφάλμα διακομιστή: Λυπούμαστε! ...* .[#toc-error-500-server-error-we-re-sorry] ------------------------------------------------------------------------------------------- -Αυτή η σελίδα σφάλματος εμφανίζεται από τη Nette σε κατάσταση παραγωγής. Εάν την βλέπετε σε ένα μηχάνημα προγραμματιστή, [μεταβείτε σε λειτουργία προγραμματιστή |application:bootstrap#Development vs Production Mode]. +Σφάλμα 500 *Server Error: We're sorry! …* +----------------------------------------- +Αυτή η σελίδα σφάλματος εμφανίζεται από το Nette σε κατάσταση παραγωγής. Αν εμφανίζεται στον υπολογιστή ανάπτυξης, [μεταβείτε σε κατάσταση ανάπτυξης |application:bootstrapping#Λειτουργία Ανάπτυξης vs Παραγωγής] και θα εμφανιστεί το Tracy με λεπτομερές μήνυμα. -Εάν το μήνυμα σφάλματος περιέχει `Tracy is unable to log error`, μάθετε γιατί τα σφάλματα δεν μπορούν να καταγραφούν. Μπορείτε να το κάνετε αυτό, για παράδειγμα, [μεταβαίνοντας |application:bootstrap#Development vs Production Mode] σε λειτουργία προγραμματιστή και καλώντας το `Tracy\Debugger::log('hello');` μετά το `$configurator->enableTracy(...)`. Το Tracy θα σας πει γιατί δεν μπορεί να καταγραφεί. -Η αιτία είναι συνήθως [ανεπαρκή δικαιώματα |#Setting Directory Permissions] εγγραφής στον κατάλογο `log/`. +Ο λόγος του σφάλματος αναγράφεται πάντα στο αρχείο καταγραφής στον κατάλογο `log/`. Αν όμως στο μήνυμα σφάλματος εμφανίζεται η πρόταση `Tracy is unable to log error`, πρώτα διαπιστώστε γιατί δεν μπορούν να καταγραφούν τα σφάλματα. Μπορείτε να το κάνετε αυτό, για παράδειγμα, [μεταβαίνοντας |application:bootstrapping#Λειτουργία Ανάπτυξης vs Παραγωγής] προσωρινά σε κατάσταση ανάπτυξης και αφήνοντας το Tracy να καταγράψει οτιδήποτε μετά την εκκίνησή του: -Αν η πρόταση `Tracy is unable to log error` δεν υπάρχει στο μήνυμα σφάλματος (πλέον), μπορείτε να μάθετε την αιτία του σφάλματος στο αρχείο καταγραφής στον κατάλογο `log/`. +```php +// Bootstrap.php +$configurator->setDebugMode('23.75.345.200'); // η διεύθυνση IP σας +$configurator->enableTracy($rootDir . '/log'); +\Tracy\Debugger::log('hello'); +``` + +Το Tracy θα σας πει γιατί δεν μπορεί να καταγράψει. Η αιτία μπορεί να είναι ανεπαρκή δικαιώματα για εγγραφή στον κατάλογο `log/`. + +Ένας από τους συνηθέστερους λόγους για το σφάλμα 500 είναι η παλιά cache. Ενώ το Nette σε κατάσταση ανάπτυξης ενημερώνει έξυπνα αυτόματα την cache, σε κατάσταση παραγωγής εστιάζει στη μεγιστοποίηση της απόδοσης και η διαγραφή της cache, μετά από κάθε τροποποίηση κώδικα, εξαρτάται από εσάς. Δοκιμάστε να διαγράψετε το `temp/cache`. + + +Σφάλμα 404, η δρομολόγηση δεν λειτουργεί +---------------------------------------- +Όταν όλες οι σελίδες (εκτός από την αρχική) επιστρέφουν σφάλμα 404, φαίνεται να υπάρχει πρόβλημα με τη διαμόρφωση του διακομιστή για [όμορφα URLs |#Πώς να ρυθμίσετε τον διακομιστή για όμορφα URLs]. + + +Οι αλλαγές στα πρότυπα ή στη διαμόρφωση δεν εφαρμόζονται +-------------------------------------------------------- +"Τροποποίησα το πρότυπο ή τη διαμόρφωση, αλλά ο ιστότοπος εξακολουθεί να εμφανίζει την παλιά έκδοση." Αυτή η συμπεριφορά συμβαίνει σε [κατάσταση παραγωγής |application:bootstrapping#Λειτουργία Ανάπτυξης vs Παραγωγής], η οποία για λόγους απόδοσης δεν ελέγχει τις αλλαγές στα αρχεία και διατηρεί την cache που δημιουργήθηκε μία φορά. + +Για να μην χρειάζεται να διαγράφετε χειροκίνητα την cache στον διακομιστή παραγωγής μετά από κάθε τροποποίηση, ενεργοποιήστε την κατάσταση ανάπτυξης για τη διεύθυνση IP σας στο αρχείο `Bootstrap.php`: + +```php +$this->configurator->setDebugMode('η.δικη.σας.ip.διευθυνση'); +``` + + +Πώς να απενεργοποιήσετε την cache κατά την ανάπτυξη? +---------------------------------------------------- +Το Nette είναι έξυπνο και δεν χρειάζεται να απενεργοποιήσετε την προσωρινή αποθήκευση σε αυτό. Κατά την ανάπτυξη, ενημερώνει αυτόματα την cache με κάθε αλλαγή στο πρότυπο ή στη διαμόρφωση του DI container. Η κατάσταση ανάπτυξης ενεργοποιείται επιπλέον με αυτόματη ανίχνευση, οπότε συνήθως δεν χρειάζεται να διαμορφώσετε τίποτα, [ή μόνο τη διεύθυνση IP |application:bootstrapping#Λειτουργία Ανάπτυξης vs Παραγωγής]. -Μια από τις πιο συνηθισμένες αιτίες είναι μια ξεπερασμένη προσωρινή μνήμη. Ενώ η Nette ενημερώνει έξυπνα αυτόματα την προσωρινή μνήμη σε κατάσταση ανάπτυξης, σε κατάσταση παραγωγής εστιάζει στη μεγιστοποίηση της απόδοσης και η εκκαθάριση της προσωρινής μνήμης μετά από κάθε τροποποίηση κώδικα εξαρτάται από εσάς. Προσπαθήστε να διαγράψετε το `temp/cache`. +Κατά τον εντοπισμό σφαλμάτων του router, συνιστούμε να απενεργοποιήσετε την cache στον περιηγητή, στην οποία μπορεί να είναι αποθηκευμένες, για παράδειγμα, ανακατευθύνσεις: ανοίξτε τα Developer Tools (Ctrl+Shift+I ή Cmd+Option+I) και στον πίνακα Network (Δίκτυο) επιλέξτε την απενεργοποίηση της cache. -Σφάλμα `#[\ReturnTypeWillChange] attribute should be used` .[#toc-error-returntypewillchange-attribute-should-be-used] ----------------------------------------------------------------------------------------------------------------------- -Αυτό το σφάλμα εμφανίζεται αν έχετε αναβαθμίσει την PHP στην έκδοση 8.1 αλλά χρησιμοποιείτε τη Nette, η οποία δεν είναι συμβατή με αυτήν. Έτσι, η λύση είναι να ενημερώσετε τη Nette σε μια νεότερη έκδοση χρησιμοποιώντας το `composer update`. Η Nette υποστηρίζει την PHP 8.1 από την έκδοση 3.0. Αν χρησιμοποιείτε παλαιότερη έκδοση (μπορείτε να το διαπιστώσετε αναζητώντας στο `composer.json`), [αναβαθμίστε το Nette |migrations:en] ή μείνετε με την PHP 8.0. +Σφάλμα `#[\ReturnTypeWillChange] attribute should be used` +---------------------------------------------------------- +Αυτό το σφάλμα εμφανίζεται αν έχετε ενημερώσει την PHP στην έκδοση 8.1, αλλά χρησιμοποιείτε μια έκδοση του Nette που δεν είναι συμβατή με αυτήν. Η λύση είναι επομένως να ενημερώσετε το Nette σε νεότερη έκδοση χρησιμοποιώντας το `composer update`. Το Nette υποστηρίζει την PHP 8.1 από την έκδοση 3.0. Αν χρησιμοποιείτε παλαιότερη έκδοση (μπορείτε να το δείτε στο `composer.json`), [αναβαθμίστε το Nette |migrations:en] ή παραμείνετε στην PHP 8.0. -Ρύθμιση δικαιωμάτων καταλόγου .[#toc-setting-directory-permissions] -------------------------------------------------------------------- -Αν αναπτύσσετε σε macOS ή Linux (ή σε οποιοδήποτε άλλο σύστημα που βασίζεται σε Unix), πρέπει να ρυθμίσετε τα δικαιώματα εγγραφής στον διακομιστή ιστού. Υποθέτοντας ότι η εφαρμογή σας βρίσκεται στον προεπιλεγμένο κατάλογο `/var/www/html` (Fedora, CentOS, RHEL) +Ρύθμιση δικαιωμάτων καταλόγου +----------------------------- +Αν αναπτύσσετε σε macOS ή Linux (ή σε οποιοδήποτε άλλο σύστημα βασισμένο σε Unix), θα χρειαστεί να ρυθμίσετε τα δικαιώματα εγγραφής για τον web server. Ας υποθέσουμε ότι η εφαρμογή σας βρίσκεται στον προεπιλεγμένο κατάλογο `/var/www/html` (Fedora, CentOS, RHEL). ```shell cd /var/www/html/MY_PROJECT chmod -R a+rw temp log ``` -Σε ορισμένα συστήματα Linux (Fedora, CentOS, ...) το SELinux μπορεί να είναι ενεργοποιημένο από προεπιλογή. Μπορεί να χρειαστεί να ενημερώσετε τις πολιτικές SELinux ή να ορίσετε τις διαδρομές των καταλόγων `temp` και `log` με το σωστό πλαίσιο ασφαλείας SELinux. Οι κατάλογοι `temp` και `log` θα πρέπει να οριστούν στο πλαίσιο `httpd_sys_rw_content_t`. Για την υπόλοιπη εφαρμογή -- κυρίως το φάκελο `app` -- το πλαίσιο `httpd_sys_content_t` είναι αρκετό. Εκτελέστε την εφαρμογή στον διακομιστή ως διαχειριστής: +Σε ορισμένα Linux (Fedora, CentOS, ...), το SELinux είναι ενεργοποιημένο από προεπιλογή. Θα χρειαστεί να τροποποιήσετε κατάλληλα τις πολιτικές SELinux και να ορίσετε το σωστό πλαίσιο ασφαλείας SELinux για τους φακέλους `temp` και `log`. Για τα `temp` και `log` θα ορίσουμε τον τύπο πλαισίου `httpd_sys_rw_content_t`, για την υπόλοιπη εφαρμογή (και κυρίως για τον φάκελο `app`) θα αρκεί το `httpd_sys_content_t`. Στον διακομιστή εκτελέστε: ```shell semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/log(/.*)?' @@ -43,25 +72,30 @@ semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/temp(/.*) restorecon -Rv /var/www/html/MY_PROJECT/ ``` -Στη συνέχεια, πρέπει να ενεργοποιηθεί το SELinux boolean `httpd_can_network_connect_db` για να επιτραπεί στη Nette να συνδεθεί στη βάση δεδομένων μέσω δικτύου. Από προεπιλογή, είναι απενεργοποιημένη. Η εντολή `setsebool` μπορεί να χρησιμοποιηθεί για την εκτέλεση αυτής της εργασίας, και αν καθοριστεί η επιλογή `-P`, αυτή η ρύθμιση θα παραμείνει σε όλες τις επανεκκινήσεις. +Περαιτέρω, είναι απαραίτητο να ενεργοποιήσετε το SELinux boolean `httpd_can_network_connect_db`, το οποίο είναι απενεργοποιημένο από προεπιλογή και το οποίο επιτρέπει στο Nette να συνδεθεί στη βάση δεδομένων μέσω δικτύου. Θα χρησιμοποιήσουμε γι' αυτό την εντολή `setsebool` και με την επιλογή `-P` θα κάνουμε την αλλαγή μόνιμη, δηλαδή μετά την επανεκκίνηση του διακομιστή δεν θα μας περιμένει δυσάρεστη έκπληξη: ```shell setsebool -P httpd_can_network_connect_db on ``` -Πώς να αλλάξετε ή να αφαιρέσετε τον κατάλογο `www` από τη διεύθυνση URL; .[#toc-how-to-change-or-remove-www-directory-from-url] -------------------------------------------------------------------------------------------------------------------------------- -Ο κατάλογος `www/` που χρησιμοποιείται στα έργα-δείγματα στο Nette είναι ο λεγόμενος δημόσιος κατάλογος ή η ρίζα εγγράφων του έργου. Είναι ο μόνος κατάλογος του οποίου τα περιεχόμενα είναι προσβάσιμα στο πρόγραμμα περιήγησης. Και περιέχει το αρχείο `index.php`, το σημείο εισόδου που εκκινεί μια εφαρμογή ιστού γραμμένη σε Nette. +Πώς να αλλάξετε ή να αφαιρέσετε τον κατάλογο `www` από το URL? +-------------------------------------------------------------- +Ο κατάλογος `www/` που χρησιμοποιείται στα παραδείγματα έργων στο Nette αντιπροσωπεύει τον λεγόμενο δημόσιο κατάλογο ή document-root του έργου. Είναι ο μόνος κατάλογος του οποίου το περιεχόμενο είναι προσβάσιμο από τον περιηγητή. Και περιέχει το αρχείο `index.php`, το σημείο εισόδου που εκκινεί την διαδικτυακή εφαρμογή γραμμένη σε Nette. -Για να εκτελέσετε την εφαρμογή στη φιλοξενία, πρέπει να ορίσετε το document-root σε αυτόν τον κατάλογο στη διαμόρφωση της φιλοξενίας. Ή, αν η φιλοξενία έχει έναν προκαθορισμένο φάκελο για τον δημόσιο κατάλογο με διαφορετικό όνομα (για παράδειγμα `web`, `public_html` κ.λπ.), απλά μετονομάστε τον σε `www/`. +Για τη λειτουργία της εφαρμογής σε hosting, είναι απαραίτητο να έχετε σωστά διαμορφωμένο το document-root. Έχετε δύο επιλογές: +1. Στη διαμόρφωση του hosting, ορίστε το document-root σε αυτόν τον κατάλογο (`www/`) +2. Εάν το hosting έχει έναν προετοιμασμένο φάκελο για δημόσια αρχεία (π.χ. `public_html`, `htdocs`, `httpdocs`), απλώς μετονομάστε το `www/` σε αυτό το όνομα. -Η λύση **δεν** είναι να "ξεφορτωθείτε" το φάκελο `www/` χρησιμοποιώντας κανόνες στο αρχείο `.htaccess` ή στο δρομολογητή. Αν η φιλοξενία δεν σας επιτρέπει να ορίσετε το document-root σε υποκατάλογο (δηλ. να δημιουργήσετε καταλόγους ένα επίπεδο πάνω από τον δημόσιο κατάλογο), αναζητήστε άλλον. Διαφορετικά, θα αναλάβετε ένα σημαντικό ρίσκο ασφάλειας. Θα ήταν σαν να ζείτε σε ένα διαμέρισμα όπου δεν μπορείτε να κλείσετε την εξώπορτα και είναι πάντα ορθάνοιχτη. +.[warning] +Ποτέ μην προσπαθείτε να λύσετε την ασφάλεια μόνο με `.htaccess` ή router, που θα εμπόδιζαν την πρόσβαση στους υπόλοιπους φακέλους. +Αν το hosting δεν επιτρέπει τον ορισμό του document-root σε υποκατάλογο (δηλ. τη δημιουργία καταλόγων ένα επίπεδο πάνω από τον δημόσιο κατάλογο), αναζητήστε άλλο. Διαφορετικά θα διατρέχατε σημαντικό κίνδυνο ασφαλείας. Θα ήταν σαν να ζείτε σε διαμέρισμα όπου η εξώπορτα δεν κλείνει και είναι πάντα ορθάνοιχτη. -Πώς να διαμορφώσετε έναν διακομιστή για ωραίες διευθύνσεις URL; .[#toc-how-to-configure-a-server-for-nice-urls] ---------------------------------------------------------------------------------------------------------------- -**Apache**: Η επέκταση mod_rewrite πρέπει να επιτρέπεται και να ρυθμίζεται σε ένα αρχείο `.htaccess`. + +Πώς να ρυθμίσετε τον διακομιστή για όμορφα URLs? +------------------------------------------------ +**Apache**: είναι απαραίτητο να ενεργοποιήσετε και να ρυθμίσετε τους κανόνες mod_rewrite στο αρχείο `.htaccess`: ```apacheconf RewriteEngine On @@ -70,25 +104,53 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -Για να αλλάξετε τις ρυθμίσεις του Apache με αρχεία .htaccess, πρέπει να ενεργοποιήσετε την οδηγία AllowOverride. Αυτή είναι η προεπιλεγμένη συμπεριφορά για τον Apache. +Αν αντιμετωπίσετε προβλήματα, βεβαιωθείτε ότι: +- το αρχείο `.htaccess` βρίσκεται στον κατάλογο document-root (δηλαδή δίπλα στο αρχείο `index.php`) +- [ο Apache επεξεργάζεται τα αρχεία `.htaccess` |#Έλεγχος λειτουργίας του .htaccess] +- [το mod_rewrite είναι ενεργοποιημένο |#Έλεγχος ενεργοποίησης του mod rewrite] + +Αν ρυθμίζετε την εφαρμογή σε υποφάκελο, ίσως χρειαστεί να αποσχολιάσετε τη γραμμή για τη ρύθμιση `RewriteBase` και να την ορίσετε στον σωστό φάκελο. -**nginx**: η οδηγία `try_files` πρέπει να χρησιμοποιείται στις ρυθμίσεις του διακομιστή: +**nginx**: είναι απαραίτητο να ρυθμίσετε την ανακατεύθυνση χρησιμοποιώντας την οδηγία `try_files` μέσα στο μπλοκ `location /` στη διαμόρφωση του διακομιστή. ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args is important + try_files $uri $uri/ /index.php$is_args$args; # το $is_args$args ΕΙΝΑΙ ΣΗΜΑΝΤΙΚΟ! } ``` -Το μπλοκ `location` πρέπει να ορίζεται ακριβώς μία φορά για κάθε διαδρομή συστήματος αρχείων στο μπλοκ `server`. Εάν έχετε ήδη ένα μπλοκ `location /` στη διαμόρφωσή σας, προσθέστε την οδηγία `try_files` στο υπάρχον μπλοκ. +Το μπλοκ `location` για κάθε διαδρομή συστήματος αρχείων μπορεί να εμφανίζεται μόνο μία φορά στο μπλοκ `server`. Αν έχετε ήδη `location /` στη διαμόρφωση, προσθέστε την οδηγία `try_files` σε αυτό. + + +Έλεγχος λειτουργίας του `.htaccess` +----------------------------------- +Ο ευκολότερος τρόπος για να ελέγξετε αν ο Apache χρησιμοποιεί ή αγνοεί το αρχείο `.htaccess` σας είναι να το καταστρέψετε σκόπιμα. Εισαγάγετε στην αρχή του αρχείου τη γραμμή `Test` και τώρα, αν ανανεώσετε τη σελίδα στον περιηγητή, θα πρέπει να δείτε *Internal Server Error*. + +Αν εμφανιστεί αυτό το σφάλμα, είναι στην πραγματικότητα καλό! Σημαίνει ότι ο Apache αναλύει το αρχείο `.htaccess` και συναντά το σφάλμα που εισαγάγαμε εκεί. Αφαιρέστε τη γραμμή `Test`. + +Αν δεν εμφανιστεί *Internal Server Error*, η ρύθμισή σας στον Apache αγνοεί το αρχείο `.htaccess`. Γενικά, ο Apache το αγνοεί λόγω έλλειψης της οδηγίας διαμόρφωσης `AllowOverride All`. + +Αν το φιλοξενείτε μόνοι σας, αυτό μπορεί εύκολα να διορθωθεί. Ανοίξτε το αρχείο `httpd.conf` ή `apache.conf` σε έναν επεξεργαστή κειμένου, βρείτε το σχετικό τμήμα `<Directory>` και προσθέστε/αλλάξτε αυτή την οδηγία: + +```apacheconf +<Directory "/var/www/htdocs"> # διαδρομή προς το document root σας + AllowOverride All + ... +``` + +Αν ο ιστότοπός σας φιλοξενείται αλλού, ελέγξτε τον πίνακα ελέγχου σας για να δείτε αν μπορείτε να ενεργοποιήσετε το αρχείο `.htaccess` εκεί. Αν όχι, επικοινωνήστε με τον πάροχο φιλοξενίας σας για να το κάνει για εσάς. + + +Έλεγχος ενεργοποίησης του `mod_rewrite` +--------------------------------------- +Αν έχετε επιβεβαιώσει ότι [το `.htaccess` λειτουργεί |#Έλεγχος λειτουργίας του .htaccess], μπορείτε να ελέγξετε αν η επέκταση mod_rewrite είναι ενεργοποιημένη. Εισαγάγετε στην αρχή του αρχείου `.htaccess` τη γραμμή `RewriteEngine On` και ανανεώστε τη σελίδα στον περιηγητή. Αν εμφανιστεί *Internal Server Error*, σημαίνει ότι το mod_rewrite δεν είναι ενεργοποιημένο. Υπάρχουν διάφοροι τρόποι για να το ενεργοποιήσετε. Διάφορους τρόπους για να το κάνετε αυτό σε διάφορες ρυθμίσεις θα βρείτε στο Stack Overflow. -Οι σύνδεσμοι δημιουργούνται χωρίς το `https:` .[#toc-links-are-generated-without-https] ---------------------------------------------------------------------------------------- -Η Nette παράγει συνδέσμους με το ίδιο πρωτόκολλο που χρησιμοποιεί η τρέχουσα σελίδα. Έτσι, στη σελίδα `https://foo` και αντίστροφα. -Αν βρίσκεστε πίσω από έναν αντίστροφο διακομιστή μεσολάβησης που απογυμνώνει το HTTPS (για παράδειγμα, στο Docker), τότε πρέπει να [ρυθμίσετε έναν διακομιστή μεσολάβησης |http:configuration#HTTP proxy] στις ρυθμίσεις για να λειτουργήσει σωστά η ανίχνευση πρωτοκόλλου. +Οι σύνδεσμοι δημιουργούνται χωρίς `https:` +------------------------------------------ +Το Nette δημιουργεί συνδέσμους με το ίδιο πρωτόκολλο που έχει η ίδια η σελίδα. Δηλαδή, στη σελίδα `https://foo` δημιουργεί συνδέσμους που ξεκινούν με `https:` και αντίστροφα. Αν βρίσκεστε πίσω από έναν reverse proxy server που αφαιρεί το HTTPS (για παράδειγμα στο Docker), τότε πρέπει στη διαμόρφωση να [ρυθμίσετε το proxy |http:configuration#HTTP proxy], ώστε η ανίχνευση πρωτοκόλλου να λειτουργεί σωστά. -Αν χρησιμοποιείτε το Nginx ως διακομιστή μεσολάβησης, θα πρέπει να έχετε ρυθμίσει την ανακατεύθυνση ως εξής: +Αν χρησιμοποιείτε το Nginx ως proxy, είναι απαραίτητο να έχετε ρυθμισμένη την ανακατεύθυνση π.χ. ως εξής: ``` location / { @@ -96,48 +158,46 @@ location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Port $server_port; - proxy_pass http://IP-aplikace:80; # IP or hostname of the server/container where the application is running + proxy_pass http://IP-aplikace:80; # IP ή hostname του διακομιστή/κοντέινερ όπου εκτελείται η εφαρμογή } ``` -Στη συνέχεια, πρέπει να καθορίσετε το διακομιστή μεσολάβησης IP και, αν ισχύει, την περιοχή IP του τοπικού σας δικτύου όπου εκτελείτε την υποδομή: +Περαιτέρω, είναι απαραίτητο να αναφέρετε στη διαμόρφωση την IP του proxy και ενδεχομένως το εύρος IP του τοπικού σας δικτύου, όπου λειτουργεί η υποδομή σας: ```neon http: - proxy: IP-proxy/IP-range + proxy: IP-proxy/IP-range # π.χ. 1.2.3.4 ή 1.2.3.0/24 ``` -Χρήση των χαρακτήρων { } στη JavaScript .[#toc-use-of-characters-in-javascript] -------------------------------------------------------------------------------- -Οι χαρακτήρες `{` and `}` χρησιμοποιούνται για τη συγγραφή ετικετών Latte. Ό,τι (εκτός από το κενό και τα εισαγωγικά) ακολουθεί το `{` character is considered a tag. If you need to print character `{` (συχνά στη JavaScript), μπορείτε να βάλετε ένα κενό (ή άλλο κενό χαρακτήρα) αμέσως μετά το `{`. Με αυτόν τον τρόπο αποφεύγετε την ερμηνεία του ως ετικέτα. +Χρήση χαρακτήρων { } στην JavaScript +------------------------------------ +Οι χαρακτήρες `{` και `}` χρησιμοποιούνται για τη γραφή ετικετών Latte. Ως ετικέτα θεωρείται οτιδήποτε ακολουθεί τον χαρακτήρα `{` με εξαίρεση το κενό και τα εισαγωγικά. Αν λοιπόν χρειαστεί να εκτυπώσετε απευθείας τον χαρακτήρα `{` (συχνά για παράδειγμα στην JavaScript), μπορείτε μετά τον χαρακτήρα `{` να βάλετε ένα κενό (ή άλλο κενό χαρακτήρα). Με αυτόν τον τρόπο αποφεύγετε τη μετάφραση ως ετικέτα. -Αν είναι απαραίτητο να εκτυπώσετε αυτούς τους χαρακτήρες σε μια κατάσταση όπου θα ερμηνεύονταν ως ετικέτα, μπορείτε να χρησιμοποιήσετε ειδικές ετικέτες για να εκτυπώσετε αυτούς τους χαρακτήρες - `{l}` για `{` and `{r}` για `}`. +Αν είναι απαραίτητο να εκτυπώσετε αυτούς τους χαρακτήρες σε μια κατάσταση όπου το κείμενο θα ερμηνευόταν ως ετικέτα, μπορείτε να χρησιμοποιήσετε ειδικές ετικέτες για την εκτύπωση αυτών των χαρακτήρων - `{l}` για `{` και `{r}` για `}`. ``` -{is tag} -{ is not tag } -{l}is not tag{r} +{je značka} {* είναι ετικέτα *} +{ není značka } {* δεν είναι ετικέτα *} +{l}není značka{r} {* δεν είναι ετικέτα *} ``` -Σημείωση `Presenter::getContext() is deprecated` .[#toc-notice-presenter-getcontext-is-deprecated] --------------------------------------------------------------------------------------------------- +Μήνυμα `Presenter::getContext() is deprecated` +---------------------------------------------- -Το Nette είναι μακράν το πρώτο πλαίσιο PHP που πέρασε στην έγχυση εξαρτήσεων και οδήγησε τους προγραμματιστές να το χρησιμοποιούν με συνέπεια, ξεκινώντας από τους παρουσιαστές. Αν ένας παρουσιαστής χρειάζεται μια εξάρτηση, [θα τη ζητήσει |dependency-injection:passing-dependencies]. -Αντίθετα, ο τρόπος με τον οποίο περνάμε ολόκληρο το DI container σε μια κλάση και αυτή αντλεί τις εξαρτήσεις από αυτήν απευθείας θεωρείται αντιπρότυπο (ονομάζεται service locator). -Αυτός ο τρόπος χρησιμοποιήθηκε στη Nette 0.x πριν από την έλευση του dependency injection, και κατάλοιπό του είναι η μέθοδος `Presenter::getContext()`, που έχει χαρακτηριστεί προ πολλού ως απαρχαιωμένη. +Το Nette είναι μακράν το πρώτο PHP framework που πέρασε στο dependency injection και οδήγησε τους προγραμματιστές στη συνεπή χρήση του, ήδη από τους ίδιους τους presenters. Αν ένας presenter χρειάζεται κάποια εξάρτηση, [τη ζητά|dependency-injection:passing-dependencies]. Αντίθετα, ο τρόπος όπου περνάμε ολόκληρο τον DI container στην κλάση, και αυτή αντλεί τις εξαρτήσεις απευθείας από αυτόν, θεωρείται antipattern (ονομάζεται service locator). Αυτός ο τρόπος χρησιμοποιούνταν στο Nette 0.x πριν την έλευση του dependency injection και κατάλοιπό του είναι η μέθοδος `Presenter::getContext()`, προ πολλού χαρακτηρισμένη ως deprecated. -Αν μεταφέρετε μια πολύ παλιά εφαρμογή Nette, μπορεί να διαπιστώσετε ότι εξακολουθεί να χρησιμοποιεί αυτή τη μέθοδο. Έτσι, από την έκδοση 3.1 του `nette/application` θα συναντήσετε την προειδοποίηση `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection`, από την έκδοση 4.0 θα συναντήσετε το σφάλμα ότι η μέθοδος δεν υπάρχει. +Αν μεταφέρετε μια πολύ παλιά εφαρμογή για το Nette, μπορεί να τύχει να χρησιμοποιεί ακόμα αυτή τη μέθοδο. Από την έκδοση `nette/application` 3.1 θα συναντήσετε έτσι την προειδοποίηση `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection`, από την έκδοση 4.0 το σφάλμα ότι η μέθοδος δεν υπάρχει. -Η καθαρή λύση, φυσικά, είναι να επανασχεδιάσετε την εφαρμογή ώστε να περνάει εξαρτήσεις χρησιμοποιώντας dependency injection. Ως εναλλακτική λύση, μπορείτε να προσθέσετε τη δική σας μέθοδο `getContext()` στον βασικό σας παρουσιαστή και να παρακάμψετε το μήνυμα: +Η καθαρή λύση είναι φυσικά να ξαναγράψετε την εφαρμογή ώστε να περνά τις εξαρτήσεις χρησιμοποιώντας dependency injection. Ως λύση ανάγκης, μπορείτε να προσθέσετε στον βασικό σας presenter τη δική σας μέθοδο `getContext()` και να παρακάμψετε έτσι το μήνυμα: ```php -abstract BasePresenter extends Nette\Application\UI\Presenter +abstract class BasePresenter extends Nette\Application\UI\Presenter { private Nette\DI\Container $context; - public function injectContext(Nette\DI\Container $context) + public function injectContext(Nette\DI\Container $context): void { $this->context = $context; } diff --git a/nette/el/vulnerability-protection.texy b/nette/el/vulnerability-protection.texy new file mode 100644 index 0000000000..5e881feb0f --- /dev/null +++ b/nette/el/vulnerability-protection.texy @@ -0,0 +1,99 @@ +Προστασία από ευπάθειες +*********************** + +.[perex] +Κάθε τόσο αναφέρεται μια τρύπα ασφαλείας σε έναν άλλο σημαντικό ιστότοπο ή γίνεται εκμετάλλευση μιας τρύπας. Αυτό είναι δυσάρεστο. Αν σας ενδιαφέρει η ασφάλεια των διαδικτυακών σας εφαρμογών, το Nette Framework είναι σίγουρα η καλύτερη επιλογή. + + +Cross-Site Scripting (XSS) +========================== + +Το Cross-Site Scripting είναι μια μέθοδος παραβίασης ιστοσελίδων που εκμεταλλεύεται μη επεξεργασμένες εξόδους. Ο εισβολέας μπορεί στη συνέχεια να εισάγει τον δικό του κώδικα στη σελίδα και έτσι να την τροποποιήσει ή ακόμη και να αποκτήσει ευαίσθητα δεδομένα για τους επισκέπτες. Η άμυνα κατά του XSS είναι δυνατή μόνο με τη συνεπή και σωστή επεξεργασία όλων των strings. Και όμως, αρκεί ο κωδικοποιητής σας να το παραλείψει έστω και μία φορά, και ολόκληρος ο ιστότοπος μπορεί να διακυβευτεί αμέσως. + +Ένα παράδειγμα επίθεσης μπορεί να είναι η εισαγωγή ενός τροποποιημένου URL στον χρήστη, μέσω του οποίου εισάγουμε τον δικό μας κώδικα στη σελίδα. Αν η εφαρμογή δεν επεξεργάζεται σωστά τις εξόδους, εκτελεί το script στον περιηγητή του χρήστη. Με αυτόν τον τρόπο μπορούμε, για παράδειγμα, να του κλέψουμε την ταυτότητα. + +``` +https://example.com/?search=<script>alert('Επιτυχής επίθεση XSS.');</script> +``` + +Το Nette Framework έρχεται με την επαναστατική τεχνολογία [Context-Aware Escaping |latte:safety-first#Context-Aware Escaping], η οποία σας απαλλάσσει για πάντα από τον κίνδυνο του Cross-Site Scripting. Επεξεργάζεται όλες τις εξόδους αυτόματα και έτσι δεν μπορεί να συμβεί ο κωδικοποιητής να ξεχάσει κάτι. Παράδειγμα; Ο κωδικοποιητής δημιουργεί αυτό το πρότυπο: + +```latte +<p onclick="alert({$message})">{$message}</p> + +<script> +document.title = {$message}; +</script> +``` + +Η γραφή `{$message}` σημαίνει εκτύπωση της μεταβλητής. Σε άλλα frameworks, είναι απαραίτητο να επεξεργάζεστε ρητά κάθε εκτύπωση και μάλιστα διαφορετικά σε κάθε σημείο. Στο Nette Framework δεν χρειάζεται να επεξεργαστείτε τίποτα, όλα γίνονται αυτόματα, σωστά και με συνέπεια. Αν αντικαταστήσουμε στη μεταβλητή `$message = 'Πλάτος 1/2"'`, το framework θα δημιουργήσει τον κώδικα HTML: + +```latte +<p onclick="alert("Πλάτος 1\/2\"")">Πλάτος 1/2"</p> + +<script> +document.title = "Πλάτος 1\/2\""; +</script> +``` + + +Cross-Site Request Forgery (CSRF) +================================= + +Η επίθεση Cross-Site Request Forgery συνίσταται στο ότι ο εισβολέας δελεάζει το θύμα σε μια σελίδα που εκτελεί διακριτικά ένα αίτημα στον διακομιστή στον οποίο είναι συνδεδεμένο το θύμα, στον περιηγητή του θύματος, και ο διακομιστής πιστεύει ότι το αίτημα εκτελέστηκε από το θύμα με τη θέλησή του. Και έτσι, υπό την ταυτότητα του θύματος, εκτελεί μια συγκεκριμένη ενέργεια χωρίς να το γνωρίζει. Μπορεί να είναι αλλαγή ή διαγραφή δεδομένων, αποστολή μηνύματος κ.λπ. + +Το Nette Framework **προστατεύει αυτόματα τις φόρμες και τα σήματα στους presenters** από αυτόν τον τύπο επίθεσης. Και αυτό εμποδίζοντας την αποστολή ή την κλήση τους από άλλο domain. Αν θέλετε να απενεργοποιήσετε την προστασία, χρησιμοποιήστε στις φόρμες: + +```php +$form->allowCrossOrigin(); +``` + +ή στην περίπτωση σήματος προσθέστε την annotation `@crossOrigin`: + +```php +/** + * @crossOrigin + */ +public function handleXyz() +{ +} +``` + +Στο Nette Application 3.2 μπορείτε να χρησιμοποιήσετε επίσης attributes: + +```php +use Nette\Application\Attributes\Requires; + +#[Requires(sameOrigin: false)] +public function handleXyz() +{ +} +``` + + +Επίθεση URL, κωδικοί ελέγχου, μη έγκυρο UTF-8 +============================================= + +Διάφοροι όροι που σχετίζονται με την προσπάθεια του εισβολέα να εισάγει στην διαδικτυακή σας εφαρμογή *κακόβουλη* είσοδο. Οι συνέπειες μπορεί να είναι πολύ διαφορετικές, από την καταστροφή των εξόδων XML (π.χ. μη λειτουργικά RSS feeds) μέχρι την απόκτηση ευαίσθητων πληροφοριών από τη βάση δεδομένων ή κωδικών πρόσβασης. Η άμυνα είναι η συνεπής επεξεργασία όλων των εισόδων σε επίπεδο μεμονωμένων bytes. Και με το χέρι στην καρδιά, ποιος από εσάς το κάνει αυτό; + +Το Nette Framework το κάνει για εσάς και μάλιστα αυτόματα. Δεν χρειάζεται να ρυθμίσετε απολύτως τίποτα και όλες οι είσοδοι θα είναι επεξεργασμένες. + + +Session hijacking, session stealing, session fixation +===================================================== + +Με τη διαχείριση των sessions συνδέονται αμέσως διάφοροι τύποι επιθέσεων. Ο εισβολέας είτε κλέβει είτε εισάγει στον χρήστη το δικό του session ID και χάρη σε αυτό αποκτά πρόσβαση στην διαδικτυακή εφαρμογή χωρίς να γνωρίζει τον κωδικό πρόσβασης του χρήστη. Στη συνέχεια μπορεί να κάνει οτιδήποτε στην εφαρμογή χωρίς να το γνωρίζει ο χρήστης. Η άμυνα συνίσταται στη σωστή διαμόρφωση του διακομιστή και της PHP. + +Ενώ το Nette Framework διαμορφώνει την PHP αυτόματα. Ο προγραμματιστής έτσι δεν χρειάζεται να σκέφτεται πώς να ασφαλίσει σωστά το session και μπορεί να επικεντρωθεί πλήρως στη δημιουργία της εφαρμογής. Απαιτείται όμως η ενεργοποιημένη συνάρτηση `ini_set()`. + + +SameSite cookie +=============== + +Τα SameSite cookies παρέχουν έναν μηχανισμό για την αναγνώριση του τι οδήγησε στη φόρτωση της σελίδας. Κάτι που είναι απολύτως θεμελιώδες για την ασφάλεια. + +Η σημαία SameSite μπορεί να έχει τρεις τιμές: `Lax`, `Strict` και `None` (αυτή απαιτεί HTTPS). Αν το αίτημα για τη σελίδα προέρχεται απευθείας από τον ιστότοπο ή ο χρήστης ανοίγει τη σελίδα πληκτρολογώντας απευθείας στη γραμμή διευθύνσεων ή κάνοντας κλικ σε έναν σελιδοδείκτη, ο περιηγητής στέλνει στον διακομιστή όλα τα cookies (δηλαδή με τις σημαίες `Lax`, `Strict` και `None`). Αν ο χρήστης μεταβεί στον ιστότοπο κάνοντας κλικ σε έναν σύνδεσμο από άλλο ιστότοπο, παραδίδονται στον διακομιστή τα cookies με τις σημαίες `Lax` και `None`. Αν το αίτημα προκύψει με άλλο τρόπο, όπως η υποβολή μιας φόρμας POST από άλλο ιστότοπο, η φόρτωση μέσα σε ένα iframe, με χρήση JavaScript, κ.λπ., αποστέλλονται μόνο τα cookies με τη σημαία `None`. + +Το Nette από προεπιλογή στέλνει όλα τα cookies με τη σημαία `Lax`. + +{{leftbar: www:@menu-common}} diff --git a/nette/en/@home.texy b/nette/en/@home.texy index 2fe80a3dab..0020230ca3 100644 --- a/nette/en/@home.texy +++ b/nette/en/@home.texy @@ -16,15 +16,15 @@ Introduction General ------- - [List of Packages |www:packages] -- [Maintenance and PHP |www:maintenance] +- [Maintenance and PHP Versions |www:maintenance] - [Release Notes |https://nette.org/releases] - [Upgrade Guide |migrations:] - [nette:Troubleshooting] - [Who Creates Nette |https://nette.org/contributors] - [History of Nette |www:history] - [Get Involved |contributing:] -- [Sponsor development |https://nette.org/en/donate] -- [API reference |https://api.nette.org/] +- [Sponsor Development |https://nette.org/en/donate] +- [API Reference |https://api.nette.org/] </div> @@ -34,17 +34,17 @@ General Nette Application ----------------- - [How do Applications Work? |application:how-it-works] -- [application:Bootstrap] +- [application:Bootstrapping] - [application:Presenters] - [application:Templates] -- [application:Modules] +- [Directory Structure |application:directory-structure] - [application:Routing] - [Creating URL Links |application:creating-links] - [Interactive Components |application:components] - [AJAX & Snippets |application:ajax] -- [Best Practices |best-practices:] +- [Tutorials and Best Practices |best-practices:] </div> @@ -58,13 +58,14 @@ Main Topics - [Latte: Templates |latte:] - [Tracy: Debugging Tool |tracy:] - [Forms|forms:] -- [Database |database:core] +- [Database |database:guide] - [Authenticating Users |security:authentication] - [Access Control |security:authorization] -- [http:Sessions] -- [HTTP request & response|http:] +- [Sessions |http:Sessions] +- [HTTP Request & Response|http:] +- [Assets |assets:] - [Caching |caching:] -- [Emails Sending |mail:] +- [Sending Emails |mail:] - [Schema: Data Validation |schema:] - [PHP Code Generator |php-generator:] - [Tester: Unit Testing |tester:] @@ -84,11 +85,11 @@ Utilities - [utils:JSON] - [NEON|neon:] - [Password Hashing |security:passwords] -- [utils:SmartObject] - [PHP Types |utils:type] - [utils:Strings] - [Validators |utils:validators] - [RobotLoader |robot-loader:] +- [SmartObject |utils:smartobject] & [StaticClass |utils:StaticClass] - [SafeStream |safe-stream:] - [...others |utils:] </div> diff --git a/nette/en/@menu-topics.texy b/nette/en/@menu-topics.texy index 0b0ac06465..25477cf621 100644 --- a/nette/en/@menu-topics.texy +++ b/nette/en/@menu-topics.texy @@ -5,17 +5,17 @@ Main Topics - [Dependency Injection|dependency-injection:] - [Utilities |utils:] - [Forms|forms:] -- [Database |database:core] +- [Database |database:guide] - [Authenticating Users |security:authentication] - [Access Control |security:authorization] -- [http:Sessions] -- [HTTP request & response|http:] +- [Sessions |http:Sessions] +- [HTTP Request & Response|http:] - [Caching |caching:] -- [Emails Sending |mail:] +- [Sending Emails |mail:] - [Schema: Data Validation |schema:] - [PHP Code Generator |php-generator:] -- [Latte: templates |latte:] -- [Tracy: debugging |tracy:] -- [Tester: testing |tester:] +- [Latte: Templates |latte:] +- [Tracy: Debugging Tool |tracy:] +- [Tester: Unit Testing |tester:] diff --git a/nette/en/@meta.texy b/nette/en/@meta.texy new file mode 100644 index 0000000000..42471908b0 --- /dev/null +++ b/nette/en/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Documentation}} diff --git a/nette/en/configuring.texy b/nette/en/configuring.texy index 7bf1cad491..4ad1b73150 100644 --- a/nette/en/configuring.texy +++ b/nette/en/configuring.texy @@ -4,21 +4,21 @@ Configuring Nette .[perex] An overview of all configuration options in the Nette Framework. -The Nette components are configured using configuration files, which are usually written in [NEON|neon:format]. They are best edited in [editors that support it |best-practices:editors-and-tools#ide-editor]. -If you are using the full framework, the configuration will be [loaded during booting |application:bootstrap#di-container-configuration], if not, see [how to load the configuration |bootstrap:]. +Nette components are configured using configuration files, which are usually written in the [NEON format|neon:format]. They are best edited in [editors that support it |best-practices:editors-and-tools#IDE Editor]. If you are using the full framework, the configuration will be [loaded during application boot |application:bootstrapping#DI Container Configuration]; if not, read [how to load the configuration|bootstrap:]. <pre> "application .[prism-token prism-atrule]":[application:configuration#Application]: "Application .[prism-token prism-comment]"<br> -"constants .[prism-token prism-atrule]":[application:configuration#Constants]: "Defines PHP constants .[prism-token prism-comment]"<br> +"assets .[prism-token prism-atrule]":[assets:configuration]: "Assets .[prism-token prism-comment]"<br> +"constants .[prism-token prism-atrule]":[application:configuration#Constants]: "Definition of PHP constants .[prism-token prism-comment]"<br> "database .[prism-token prism-atrule]":[database:configuration]: "Database .[prism-token prism-comment]"<br> "decorator .[prism-token prism-atrule]":[dependency-injection:configuration#Decorator]: "Decorator .[prism-token prism-comment]"<br> "di .[prism-token prism-atrule]":[dependency-injection:configuration#DI]: "DI Container .[prism-token prism-comment]"<br> -"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Extensions]: "Install additional DI extensions .[prism-token prism-comment]"<br> +"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Extensions]: "Installation of additional DI extensions .[prism-token prism-comment]"<br> "forms .[prism-token prism-atrule]":[forms:configuration]: "Forms .[prism-token prism-comment]"<br> "http .[prism-token prism-atrule]":[http:configuration#HTTP Headers]: "HTTP Headers .[prism-token prism-comment]"<br> -"includes .[prism-token prism-atrule]":[dependency-injection:configuration#Including files]: "Including files .[prism-token prism-comment]"<br> -"latte .[prism-token prism-atrule]":[application:configuration#Latte]: "Latte .[prism-token prism-comment]"<br> -"mail .[prism-token prism-atrule]":[mail:#Configuring]: "Mailing .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[dependency-injection:configuration#Including Files]: "Including files .[prism-token prism-comment]"<br> +"latte .[prism-token prism-atrule]":[application:configuration#Latte Templates]: "Latte Templates .[prism-token prism-comment]"<br> +"mail .[prism-token prism-atrule]":[mail:#Configuration]: "Mailing .[prism-token prism-comment]"<br> "parameters .[prism-token prism-atrule]":[dependency-injection:configuration#Parameters]: "Parameters .[prism-token prism-comment]"<br> "php .[prism-token prism-atrule]":[application:configuration#PHP]: "PHP configuration options .[prism-token prism-comment]"<br> "routing .[prism-token prism-atrule]":[application:configuration#Routing]: "Routing .[prism-token prism-comment]"<br> @@ -29,7 +29,8 @@ If you are using the full framework, the configuration will be [loaded during bo "tracy .[prism-token prism-atrule]":[tracy:configuring#Nette Framework]: "Tracy Debugger .[prism-token prism-comment]" </pre> -To write a string containing the character `%`, you must escape it by doubling it to `%%`. .[note] +.[note] +To write a string containing the character `%`, you must escape it by doubling it to `%%`. {{leftbar: @menu-topics}} diff --git a/nette/en/glossary.texy b/nette/en/glossary.texy index 9ef8358c9a..bc555933a0 100644 --- a/nette/en/glossary.texy +++ b/nette/en/glossary.texy @@ -4,152 +4,163 @@ Glossary of Terms AJAX ---- -Asynchronous JavaScript and XML - technology for client-server communication over the HTTP protocol without the need for reload of the whole page during each request. Despite the acronym, [#JSON] format is often used instead of XML. +Asynchronous JavaScript and XML - a technology for exchanging information between the client and server over the HTTP protocol without needing to reload the entire page with each request. Although the name might suggest data is sent only in XML format, the [#JSON] format is also commonly used. Presenter Action ---------------- -Logical part of the [#presenter], performing one action, such as to show a product page, to sign out a user etc. One presenter can have more actions. +A logical part of a presenter that performs a single action. For example, displaying a product page, logging out a user, etc. A single presenter can have multiple actions. BOM --- -So-called *byte order mask* is a special first character of a file and indicates byte order in the encoding. Some editors include it automatically, it's practically invisible, but it causes problems with headers and output sending from within PHP. You can use [Code Checker|code-checker:] for bulk removal. +The *byte order mark* is a special first character in a file used as an indicator of byte order in encoding. Some editors insert it into files. It is practically invisible but causes problems with sending output and headers from PHP. You can use [Code Checker|code-checker:] for bulk removal. Controller ---------- -Controller processes requests from user and on their basis it calls particular application logic (ie. [#model]), then it calls [#view] for data rendering. Analogy to controllers are [presenters|#presenter] in Nette Framework. +A controller that processes user requests and, based on them, calls the appropriate application logic (i.e., [#model]) and then asks the [#view] to render the data. The equivalent of controllers in the Nette Framework are [presenters |#Presenter]. Cross-Site Scripting (XSS) -------------------------- -Cross-Site Scripting is a site disruption method using unescaped input. An attacker may inject his own HTML or JavaScript code and change the look of the page or even gather sensitive information about users. Protection against XSS is simple: consistent and correct escaping of all strings and inputs. +Cross-Site Scripting is a method of disrupting websites by exploiting unescaped outputs. An attacker can then inject their own code into the page, thereby modifying the page or even obtaining sensitive data about visitors. Protection against XSS involves consistent and correct escaping of all output strings. -Nette Framework comes up with a brand new technology of [Context-Aware Escaping |latte:safety-first#context-aware-escaping], which will get you rid of the Cross-Site Scripting risks forever. It escapes all inputs automatically based on a given context, so it's impossible for a coder to accidentally forget something. +Nette Framework introduces the revolutionary [Context-Aware Escaping |latte:safety-first#Context-Aware Escaping] technology, which permanently eliminates the risk of Cross-Site Scripting. It automatically escapes all outputs, so a developer cannot forget anything. Cross-Site Request Forgery (CSRF) --------------------------------- -A Cross-Site Request Forgery attack is that the attacker lures the victim to visit a page that silently executes a request in the victim's browser to the server where the victim is currently logged in, and the server believes that the request was made by the victim at will. Server performs a certain action under the identity of the victim but without the victim realizing it. It can be changing or deleting data, sending a message, etc. +A Cross-Site Request Forgery attack involves the attacker luring a victim to a page that subtly executes a request in the victim's browser to a server where the victim is logged in. The server believes the request was made willingly by the victim. Thus, under the victim's identity, it performs an action without their knowledge. This could involve changing or deleting data, sending a message, etc. -Nette Framework **automatically protects forms and signals in presenters** from this type of attack. This is done by preventing them from being sent or called from another domain. +Nette Framework **automatically protects forms and signals in presenters** against this type of attack by preventing them from being submitted or triggered from another domain. + + +Cross-Origin Resource Sharing (CORS) +------------------------------------ +CORS is a security mechanism that allows a web page to make JavaScript requests to a different domain than the one from which the page was loaded. Without CORS, browsers block such requests for security reasons. + +For example, if your website runs at `https://myapp.com` and tries to fetch data from `https://api.example.com` using JavaScript (AJAX, Fetch API), the browser will check if the API server allows this cross-origin request. The API server must respond with special HTTP headers, such as `Access-Control-Allow-Origin: https://myapp.com`, to grant permission. Dependency Injection -------------------- -Dependency Injection (DI) is a design pattern that tells you how to separate the creation of objects from their dependencies. That is, a class is not responsible for creating or initializing its dependencies, but instead those dependencies are provided by external code (which can include a [DI container |#Dependency Injection container]). The advantage is that it allows for greater code flexibility, better readability, and easier application testing because dependencies are easily replaceable and isolated from other parts of the code. For more information, see [What is Dependency Injection? |dependency-injection:introduction] +Dependency Injection (DI) is a design pattern that dictates how to separate the creation of objects from their dependencies. This means a class is not responsible for creating or initializing its dependencies; instead, these dependencies are provided by external code (which could be a [DI container |#Dependency Injection Container]). The advantage lies in increased code flexibility, better understandability, and easier application testing, as dependencies are easily replaceable and isolated from other code parts. More in the chapter [What is Dependency Injection? |dependency-injection:introduction] Dependency Injection Container ------------------------------ -A Dependency Injection container (also DI container or IoC container) is a tool that handles the creation and management of dependencies in an application (or [services |#service]). A container usually has a configuration that defines what classes are dependent on other classes, what specific dependency implementations to use, and how to create those dependencies. The container then creates these objects and provides them to the classes that need them. For more information, see [What is a DI container? |dependency-injection:container] +A Dependency Injection container (also DI container or IoC container) is a tool that manages the creation and administration of dependencies (or [services |#Service]) within an application. The container typically has a configuration defining which classes depend on others, which specific dependency implementations should be used, and how these dependencies should be created. The container then creates these objects and provides them to the classes that need them. More in the chapter [What is a DI container? |dependency-injection:container] Escaping -------- -Escaping is conversion of characters with special meaning in given context to another equivalent sequences. Example: We want to write quotes into quotes-enclosed string. Because quotes have special meaning in context of the quotes-enclosed string, there is a need to use another equivalent sequence. Concrete sequence is determined by the context rules (e.g. `\"` in PHP's quotes-enclosed string, `"` in HTML attributes etc.). +Escaping is the conversion of characters that have a special meaning in a given context into other corresponding sequences. Example: We want to write quotes within a string enclosed by quotes. Since quotes have a special meaning in the context of the string, and simply writing them would be interpreted as the end of the string, they need to be written using a different corresponding sequence. The exact sequence is determined by the rules of the context. -Filter (Formerly Helper) ------------------------- -Filter function. In templates, [filter |latte:syntax#filters] is a function, that helps to alter or format data to the output form. Templates have several [standard filters |latte:filters] predefined. +Filter +------ +In templates, a [filter |latte:syntax#Filters] usually refers to a function that helps modify or reformat data into its final form. Templates provide several [standard filters |latte:filters]. Invalidation ------------ -Notice of a [#snippet] to rerender. In other context also clearing of a cache. +Notifying a [#snippet] to redraw itself. In another context, it also means deleting the cache content. JSON ---- -Data exchange format based on JavaScript syntax (it's its subset). Exact specification can be found at www.json.org. +A data exchange format based on JavaScript syntax (it is a subset of it). The exact specification can be found at www.json.org. Component --------- -Reusable part of an application. It can be a visual part of a page, as described in the [application:components] chapter, or the term can also stand for the class [Component |component-model:] (such a component doesn't have to be visual). +A reusable part of an application. It can be a visual part of a page, as described in the chapter [Writing Components |application:components], or the term component also refers to the class [Component |component-model:] (such a component does not have to be visual). Control Characters ------------------ -Control characters are invisible characters, that can occur in a text and eventually to cause some problems. For their bulk removal from files, you can use [Code Checker|code-checker:], for their removal from a variable use function [Strings::normalize()|utils:strings#normalize]. +Control characters are invisible characters that can appear in text and potentially cause problems. To remove them in bulk from files, you can use [Code Checker|code-checker:], and to remove them from a variable, use the [Strings::normalize() |utils:strings#normalize] function. Events ------ -An event is an expected situation in the object, which when it occurs, the so-called handlers are called, i.e. callbacks reacting to the event ("sample":https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38). The event can be for example form submission, user login, etc. Events are thus a form of *Inversion of Control*. +An event is an expected situation within an object. When it occurs, so-called handlers are called, i.e., callbacks reacting to the event ("example":https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38). An event could be, for example, form submission, user login, etc. Events are thus a form of *Inversion of Control*. -For example, a user login occurs in the `Nette\Security\User::login()` method. The `User` object has a public variable `$onLoggedIn`, which is an array to which anyone can add a callback. As soon as the user logs in, the `login()` method calls all callbacks in the array. The name of a variable in the form `onXyz` is a convention used throughout Nette. +For example, user login occurs in the `Nette\Security\User::login()` method. The `User` object has a public variable `$onLoggedIn`, which is an array to which anyone can add a callback. When the user logs in, the `login()` method calls all callbacks in the array. The variable name format `onXyz` is a convention used throughout Nette. Latte ----- -One of the most innovative [templating systems |latte:] ever. +One of the most advanced [templating systems |latte:]. Model ----- -Model represents data and function basis of the whole application. It includes the whole application logic (sometimes also referred to as a "business logic"). It's the **M** of **M**VC or MPV. Any user action (loging in, putting stuff to basket, change of a database value) represents an action of the model. +The model is the data and, especially, the functional core of the entire application. It contains all the application logic (the term business logic is also used). It's the **M** in **M**VC or MVP. Any user action (logging in, adding goods to the cart, changing a value in the database) represents an action of the model. -Model manages its inner state and provides a public interface. By calling of this interface we can take or change its state. Model doesn't know about an existence of [#view] or [#controller], model is totally independent on them. +The model manages its internal state and offers a fixed interface to the outside. By calling functions of this interface, we can query or change its state. The model is unaware of the existence of the [#view] or [#controller]. Model-View-Controller --------------------- -Software architecture, that emerged in GUI applications development to separate the code for the flow control ([#controller]) from the code of the application logic ([#model]) and from the data rendering code ([#view]). That way the code is better understandable, it eases the future development and it allows to test separate parts separately. +A software architecture that arose from the need to separate the handling code ([#controller]) from the application logic code ([#model]) and the data display code ([#view]) in applications with a graphical interface. This makes the application clearer, facilitates future development, and allows for testing individual parts separately. Model-View-Presenter -------------------- -Architecture based on [#Model-View-Controller]. +An architecture based on [#Model-View-Controller]. Module ------ -[Module |application:modules] in Nette Framework represents a collection of presenters and templates, eventually also components and models, that serve data to a presenter. So it is certain logical part of an application. +A module represents a logical part of an application. In a typical arrangement, it is a group of presenters and templates that address a specific area of functionality. Modules are placed in [separate directories |application:directory-structure#Presenters and Templates], such as `Front/`, `Admin/`, or `Shop/`. -For example, an e-shop can have three modules: -1) Product catalogue with basket. -2) Administration for the customer. -3) Administration for the shopkeeper. +For example, an e-shop can be divided into: +- Frontend (`Shop/`) for browsing products and purchasing +- Customer section (`Customer/`) for order management +- Administration (`Admin/`) for operators + +Technically, these are regular directories, but thanks to clear structuring, they help scale the application. The presenter `Admin:Product:List` would thus be physically located, for example, in the directory `app/Presentation/Admin/Product/List/` (see [presenter mapping |application:directory-structure#Presenter Mapping]). Namespace --------- -Namespace is a feature of the PHP language from its version 5.3 and some other programming languages as well. It helps to avoid names collisions (e.g. two classes with the same name) when using different libraries together. See [PHP documentation |https://www.php.net/manual/en/language.namespaces.rationale.php] for further detail. +A namespace, part of the PHP language since version 5.3 and some other programming languages, allows the use of classes named identically in different libraries without name collisions. See the [PHP documentation |https://www.php.net/manual/en/language.namespaces.rationale.php]. Presenter --------- -Presenter is an object, that takes the [request |api:Nette\Application\Request] as translated by the router from the HTTP request and generates a [response |api:Nette\Application\Response]. Response can be an HTML page, picture, XML document, file, JSON, redirect or whatever you think of. +A presenter is an object that takes a [request |api:Nette\Application\Request] translated by the router from an HTTP request and generates a [response |api:Nette\Application\Response]. The response can be an HTML page, an image, an XML document, a file on disk, JSON, a redirect, or anything you devise. -By a presenter it is usually meant an descendant of the [api:Nette\Application\UI\Presenter] class. By requests it runs appropriate [actions |application:presenters#life-cycle-of-presenter] and renders templates. +Usually, the term presenter refers to a descendant of the [api:Nette\Application\UI\Presenter] class. Based on incoming requests, it executes corresponding [actions |application:presenters#Presenter Life Cycle] and renders templates. Router ------ -Bi-directional translator between HTTP request / URL and presenter action. Bi-directional means, that it's not only possible to derive a [#presenter action] from the HTTP request, but also to generate appropriate URL for an action. See more in the chapter about [URL routing |application:routing]. +A bidirectional translator between an HTTP request / URL and a presenter action. Bidirectional means that it's possible to derive a [#presenter action] from an HTTP request, and conversely, to generate the corresponding URL for an action. More in the chapter on [URL routing |application:routing]. -SameSite Cookie +SameSite cookie --------------- -SameSite cookies provide a mechanism to recognize what led to the page load. It can have three values: `Lax`, `Strict` and `None` (the latter requires HTTPS). If the request to the page comes directly from the site or the user opens the page by typing directly into the address bar or clicking on a bookmark, the browser sends all cookies to the server (i.e. with the flags `Lax`, `Strict` and `None`). If the user clicks on the site via a link from another site, cookies with the `Lax` and `None` flags are passed to the server. If the request is made by other means, such as submitting a POST form from another site, loading inside an iframe, using JavaScript, etc., only cookies with the `None` flag are sent. +SameSite cookies provide a mechanism to recognize what led to the page load. It can have three values: `Lax`, `Strict`, and `None` (the latter requires HTTPS). If the request for the page comes directly from the website or the user opens the page by typing directly into the address bar or clicking on a bookmark, the browser sends all cookies to the server (i.e., with flags `Lax`, `Strict`, and `None`). If the user clicks through to the website from a link on another site, cookies with the `Lax` and `None` flags are passed to the server. If the request originates in another way, such as submitting a POST form from another site, loading within an iframe, using JavaScript, etc., only cookies with the `None` flag are sent. Service ------- -In the context of Dependency Injection, a service refers to an object that is created and managed by a DI container. A service can easily be replaced by another implementation, for example for testing purposes or to change the behavior of an application, without having to modify the code that uses the service. +In the context of Dependency Injection, a service refers to an object that is created and managed by the DI container. A service can be easily replaced by another implementation, for example, for testing purposes or to change the application's behavior, without needing to modify the code that uses the service. Snippet ------- -Snippet of a page, that can be separately re-rendered during an [#AJAX] request. +A snippet, a part of a page that can be independently redrawn during an AJAX request. View ---- -View is a layer of application, that is responsible for request results rendering. Usually it uses a templating system and it knows, how to render its components or results taken from the model. +The view is the application layer responsible for displaying the result of a request. It usually uses a templating system and knows how to display specific components or results obtained from the model. + + diff --git a/nette/en/installation.texy b/nette/en/installation.texy index 016f177da7..a20b31d600 100644 --- a/nette/en/installation.texy +++ b/nette/en/installation.texy @@ -20,17 +20,17 @@ Nette offers a collection of useful and sophisticated packages (libraries) for P composer require nette/utils ``` -Do you prefer a graphical interface? Check out the [guide|https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] on installing packages in the PhpStrom environment. +Do you prefer a graphical interface? Check out the [guide|https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] on installing packages in the PhpStorm environment. How to Start a New Project with Nette ------------------------------------- -If you want to create an entirely new project on the Nette platform, we recommend using the pre-set skeleton [Web Project|https://github.com/nette/web-project]: +If you want to create an entirely new project on the Nette platform, we recommend using the pre-configured [Web Project|https://github.com/nette/web-project] skeleton: 1) **Set up [Composer|best-practices:composer].** -2) **Open the command line** and navigate to the root directory of your web server, e.g., `/etc/var/www`, `C:/xampp/htdocs`, `/Library/WebServer/Documents`. +2) **Open the command line** and navigate to the root directory of your web server, e.g., `/var/www/html`, `C:/xampp/htdocs`, `/Library/WebServer/Documents`. 3) **Create the project** using this command: @@ -40,7 +40,7 @@ composer create-project nette/web-project PROJECT_NAME 4) **Not using Composer?** Just download the [Web Project in ZIP format|https://github.com/nette/web-project/archive/preloaded.zip] and extract it. But trust us, Composer is worth it! -5) **Setting permissions:** On macOS or Linux systems, set [write permissions|nette:troubleshooting#Setting directory permissions] for directories. +5) **Set permissions:** On macOS or Linux systems, set [write permissions |nette:troubleshooting#Setting Directory Permissions] for directories. 6) **Open the project in a browser:** Enter the URL `http://localhost/PROJECT_NAME/www/`. You'll see the landing page of the skeleton: @@ -48,7 +48,7 @@ composer create-project nette/web-project PROJECT_NAME Congratulations! Your website is now ready for development. Feel free to remove the welcome template and start building your application. -One of the advantages of Nette is that the project works immediately without the need for configuration. However, if you encounter any issues, consider looking at [common problem solutions|nette:troubleshooting#nette-is-not-working-white-page-is-displayed]. +One of the advantages of Nette is that the project works immediately without the need for configuration. However, if you encounter any issues, consider looking at [solutions to common problems |nette:troubleshooting#Nette Is Not Working White Page Is Displayed]. .[note] If you're starting with Nette, we recommend continuing with the [Create Your First Application tutorial|quickstart:]. diff --git a/nette/en/introduction-to-object-oriented-programming.texy b/nette/en/introduction-to-object-oriented-programming.texy new file mode 100644 index 0000000000..f8be46baf1 --- /dev/null +++ b/nette/en/introduction-to-object-oriented-programming.texy @@ -0,0 +1,841 @@ +Introduction to Object-Oriented Programming +******************************************* + +.[perex] +The term "OOP" stands for Object-Oriented Programming, which is a way to organize and structure code. OOP allows us to view a program as a collection of objects that communicate with each other, rather than a sequence of commands and functions. + +In OOP, an "object" is a unit that contains data and functions that operate on that data. Objects are created based on "classes", which can be understood as blueprints or templates for objects. Once we have a class, we can create its "instance", which is a specific object made from that class. + +Let's look at how we can create a simple class in PHP. When defining a class, we use the keyword "class", followed by the class name, and then curly braces that enclose the class's functions (called "methods") and class variables (called "properties" or "attributes"): + +```php +class Car +{ + function honk() + { + echo 'Beep beep!'; + } +} +``` + +In this example, we've created a class named `Car` with one function (or "method") called `honk`. + +Each class should solve only one main task. If a class is doing too many things, it may be appropriate to divide it into smaller, specialized classes. + +Classes are typically stored in separate files to keep the code organized and easy to navigate. The file name should match the class name, so for the `Car` class, the file name would be `Car.php`. + +When naming classes, it's good to follow the "PascalCase" convention, meaning each word in the name starts with a capital letter, and there are no underscores or other separators. Methods and properties follow the "camelCase" convention, meaning they start with a lowercase letter. + +Some methods in PHP have special roles and are prefixed with `__` (two underscores). One of the most important special methods is the "constructor", labeled as `__construct`. The constructor is a method that's automatically called when creating a new instance of a class. + +We often use the constructor to set the initial state of an object. For example, when creating an object representing a person, you might use the constructor to set their age, name, or other attributes. + +Let's see how to use a constructor in PHP: + +```php +class Person +{ + private $age; + + function __construct($age) + { + $this->age = $age; + } + + function howOldAreYou() + { + return $this->age; + } +} + +$person = new Person(25); +echo $person->howOldAreYou(); // Outputs: 25 +``` + +In this example, the `Person` class has a property (variable) `$age` and a constructor that sets this property. The `howOldAreYou()` method then provides access to the person's age. + +The `$this` pseudo-variable is used inside the class to access the properties and methods of the object. + +The `new` keyword is used to create a new instance of a class. In the example above, we created a new person aged 25. + +You can also set default values for constructor parameters if they aren't specified when creating an object. For instance: + +```php +class Person +{ + private $age; + + function __construct($age = 20) + { + $this->age = $age; + } + + function howOldAreYou() + { + return $this->age; + } +} + +$person = new Person; // if no argument is passed, parentheses can be omitted +echo $person->howOldAreYou(); // Outputs: 20 +``` + +In this example, if you don't specify an age when creating a `Person` object, the default value of 20 will be used. + +The nice thing is that the property definition with its initialization via the constructor can be shortened and simplified like this: + +```php +class Person +{ + function __construct( + private $age = 20, + ) { + } +} +``` + +For completeness, in addition to constructors, objects can have destructors (method `__destruct`) that are called before the object is released from memory. + + +Namespaces +---------- + +Namespaces allow us to organize and group related classes, functions, and constants while avoiding naming conflicts. You can think of them like folders on a computer, where each folder contains files related to a specific project or topic. + +Namespaces are especially useful in larger projects or when using third-party libraries where class naming conflicts might arise. + +Imagine you have a class named `Car` in your project, and you want to place it in a namespace called `Transport`. You would do it like this: + +```php +namespace Transport; + +class Car +{ + function honk() + { + echo 'Beep beep!'; + } +} +``` + +If you want to use the `Car` class in another file, you need to specify from which namespace the class originates: + +```php +$car = new Transport\Car; +``` + +For simplification, you can specify at the beginning of the file which class from a particular namespace you want to use, allowing you to create instances without mentioning the full path: + +```php +use Transport\Car; + +$car = new Car; +``` + + +Inheritance +----------- + +Inheritance is a tool of object-oriented programming that allows the creation of new classes based on existing ones, inheriting their properties and methods, and extending or redefining them as needed. Inheritance ensures code reusability and class hierarchy. + +Simply put, if we have one class and want to create another derived from it but with some modifications, we can "inherit" the new class from the original one. + +In PHP, inheritance is implemented using the `extends` keyword. + +Our `Person` class stores age information. We can have another class, `Student`, which extends `Person` and adds information about the field of study. + +Let's look at an example: + +```php +class Person +{ + private $age; + + function __construct($age) + { + $this->age = $age; + } + + function printInformation() + { + echo "Age: {$this->age} years\n"; + } +} + +class Student extends Person +{ + private $fieldOfStudy; + + function __construct($age, $fieldOfStudy) + { + parent::__construct($age); + $this->fieldOfStudy = $fieldOfStudy; + } + + function printInformation() + { + parent::printInformation(); + echo "Field of study: {$this->fieldOfStudy} \n"; + } +} + +$student = new Student(20, 'Computer Science'); +$student->printInformation(); +``` + +How does this code work? + +- We used the `extends` keyword to extend the `Person` class, meaning the `Student` class inherits all methods and properties from `Person`. + +- The `parent::` keyword allows us to call methods from the parent class. In this case, we called the constructor from the `Person` class before adding our own functionality to the `Student` class. And similarly, the `printInformation()` ancestor method before listing the student information. + +Inheritance is meant for situations where there's an "is a" relationship between classes. For instance, a `Student` is a `Person`. A cat is an animal. It allows us in cases where we expect one object (e.g., "Person") in the code to use a derived object instead (e.g., "Student"). + +It's essential to realize that the primary purpose of inheritance **is not** to prevent code duplication. On the contrary, misuse of inheritance can lead to complex and hard-to-maintain code. If there's no "is a" relationship between classes, we should consider composition instead of inheritance. + +Note that the `printInformation()` methods in the `Person` and `Student` classes output slightly different information. And we can add other classes (such as `Employee`) that will provide other implementations of this method. The ability of objects of different classes to respond to the same method in different ways is called polymorphism: + +```php +$people = [ + new Person(30), + new Student(20, 'Computer Science'), + new Employee(45, 'Director'), +]; + +foreach ($people as $person) { + $person->printInformation(); +} +``` + + +Composition +----------- + +Composition is a technique where, instead of inheriting properties and methods from another class, we simply use its instance in our class. This allows us to combine functionalities and properties of multiple classes without creating complex inheritance structures. + +For example, we have a `Engine` class and a `Car` class. Instead of saying "A car is an engine", we say "A car has an engine", which is a typical composition relationship. + +```php +class Engine +{ + function start() + { + echo 'Engine is running.'; + } +} + +class Car +{ + private $engine; + + function __construct() + { + $this->engine = new Engine; + } + + function start() + { + $this->engine->start(); + echo 'The car is ready to drive!'; + } +} + +$car = new Car; +$car->start(); +``` + +Here, the `Car` doesn't have all the properties and methods of the `Engine`, but it has access to it through the `$engine` property. + +The advantage of composition is greater design flexibility and better adaptability for future changes. + + +Visibility +---------- + +In PHP, you can define "visibility" for class properties, methods, and constants. Visibility determines where you can access these elements. + +1. **Public:** If an element is marked as `public`, it means you can access it from anywhere, even outside the class. + +2. **Protected:** An element marked as `protected` is accessible only within the class and all its descendants (classes that inherit from it). + +3. **Private:** If an element is `private`, you can access it only from within the class where it was defined. + +If you don't specify visibility, PHP will automatically set it to `public`. + +Let's look at a sample code: + +```php +class VisibilityExample +{ + public $publicProperty = 'Public'; + protected $protectedProperty = 'Protected'; + private $privateProperty = 'Private'; + + public function printProperties() + { + echo $this->publicProperty; // Works + echo $this->protectedProperty; // Works + echo $this->privateProperty; // Works + } +} + +$object = new VisibilityExample; +$object->printProperties(); +echo $object->publicProperty; // Works +// echo $object->protectedProperty; // Throws an error +// echo $object->privateProperty; // Throws an error +``` + +Continuing with class inheritance: + +```php +class ChildClass extends VisibilityExample +{ + public function printProperties() + { + echo $this->publicProperty; // Works + echo $this->protectedProperty; // Works + // echo $this->privateProperty; // Throws an error + } +} +``` + +In this case, the `printProperties()` method in the `ChildClass` can access the public and protected properties but cannot access the private properties of the parent class. + +Data and methods should be as hidden as possible and only accessible through a defined interface. This allows you to change the internal implementation of the class without affecting the rest of the code. + + +Final Keyword +------------- + +In PHP, we can use the `final` keyword if we want to prevent a class, method, or constant from being inherited or overridden. When a class is marked as `final`, it cannot be extended. When a method is marked as `final`, it cannot be overridden in a subclass. + +Being aware that a certain class or method will no longer be modified allows us to make changes more easily without worrying about potential conflicts. For example, we can add a new method without fear that a descendant might already have a method with the same name, leading to a collision. Or we can change the parameters of a method, again without the risk of causing inconsistency with an overridden method in a descendant. + +```php +final class FinalClass +{ +} + +// The following code will throw an error because we cannot inherit from a final class. +class ChildOfFinalClass extends FinalClass +{ +} +``` + +In this example, attempting to inherit from the final class `FinalClass` will result in an error. + + +Static Properties and Methods +----------------------------- + +When we talk about "static" elements of a class in PHP, we mean methods and properties that belong to the class itself, not to a specific instance of the class. This means that you don't have to create an instance of the class to access them. Instead, you call or access them directly through the class name. + +Keep in mind that since static elements belong to the class and not its instances, you cannot use the `$this` pseudo-variable inside static methods. + +Using static properties leads to [obfuscated code full of pitfalls|dependency-injection:global-state], so you should never use them, and we won't show an example here. On the other hand, static methods are useful. Here's an example: + +```php +class Calculator +{ + public static function add($a, $b) + { + return $a + $b; + } + + public static function subtract($a, $b) + { + return $a - $b; + } +} + +// Using the static method without creating an instance of the class +echo Calculator::add(5, 3); // Output: 8 +echo Calculator::subtract(5, 3); // Output: 2 +``` + +In this example, we created a `Calculator` class with two static methods. We can call these methods directly without creating an instance of the class using the `::` operator. Static methods are especially useful for operations that don't depend on the state of a specific class instance. + + +Class Constants +--------------- + +Within classes, we have the option to define constants. Constants are values that never change during the program's execution. Unlike variables, the value of a constant remains the same. + +```php +class Car +{ + public const NumberOfWheels = 4; + + public function displayNumberOfWheels(): int + { + echo self::NumberOfWheels; + } +} + +echo Car::NumberOfWheels; // Output: 4 +``` + +In this example, we have a `Car` class with the `NumberOfWheels` constant. When accessing the constant inside the class, we can use the `self` keyword instead of the class name. + + +Object Interfaces +----------------- + +Object interfaces act as "contracts" for classes. If a class is to implement an object interface, it must contain all the methods that the interface defines. It's a great way to ensure that certain classes adhere to the same "contract" or structure. + +In PHP, interfaces are defined using the `interface` keyword. All methods defined in an interface are public (`public`). When a class implements an interface, it uses the `implements` keyword. + +```php +interface Animal +{ + function makeSound(); +} + +class Cat implements Animal +{ + public function makeSound() + { + echo 'Meow'; + } +} + +$cat = new Cat; +$cat->makeSound(); +``` + +If a class implements an interface, but not all expected methods are defined, PHP will throw an error. + +A class can implement multiple interfaces at once, which is different from inheritance, where a class can only inherit from one class: + +```php +interface Guardian +{ + function guardHouse(); +} + +class Dog implements Animal, Guardian +{ + public function makeSound() + { + echo 'Bark'; + } + + public function guardHouse() + { + echo 'Dog diligently guards the house'; + } +} +``` + + +Abstract Classes +---------------- + +Abstract classes serve as base templates for other classes, but you cannot create their instances directly. They contain a mix of complete methods and abstract methods that don't have a defined content. Classes that inherit from abstract classes must provide definitions for all the abstract methods from the parent. + +We use the `abstract` keyword to define an abstract class. + +```php +abstract class AbstractClass +{ + public function regularMethod() + { + echo 'This is a regular method'; + } + + abstract public function abstractMethod(); +} + +class Child extends AbstractClass +{ + public function abstractMethod() + { + echo 'This is the implementation of the abstract method'; + } +} + +$instance = new Child; +$instance->regularMethod(); +$instance->abstractMethod(); +``` + +In this example, we have an abstract class with one regular and one abstract method. Then we have a `Child` class that inherits from `AbstractClass` and provides an implementation for the abstract method. + +How are interfaces and abstract classes different? Abstract classes can contain both abstract and concrete methods, while interfaces only define what methods the class must implement, but provide no implementation. A class can inherit from only one abstract class, but can implement any number of interfaces. + + +Type Checking +------------- + +In programming, it's crucial to ensure that the data we work with is of the correct type. In PHP, we have tools that provide this assurance. Verifying that data is of the correct type is called "type checking." + +Types we might encounter in PHP: + +1. **Basic types**: These include `int` (integers), `float` (floating-point numbers), `bool` (boolean values), `string` (strings), `array` (arrays), and `null`. +2. **Classes**: When we want a value to be an instance of a specific class. +3. **Interfaces**: Defines a set of methods that a class must implement. A value that meets an interface must have these methods. +4. **Mixed types**: We can specify that a variable can have multiple allowed types. +5. **Void**: This special type indicates that a function or method does not return any value. + +Let's see how to modify the code to include types: + +```php +class Person +{ + private int $age; + + public function __construct(int $age) + { + $this->age = $age; + } + + public function printAge(): void + { + echo "This person is {$this->age} years old."; + } +} + +/** + * A function that accepts a Person object and prints the person's age. + */ +function printPersonAge(Person $person): void +{ + $person->printAge(); +} +``` + +In this way, we ensure that our code expects and works with data of the correct type, helping us prevent potential errors. + +Some types cannot be written directly in PHP. In this case, they are listed in the phpDoc comment, which is the standard format for documenting PHP code, starting with `/**` and ending with `*/`. It allows you to add descriptions of classes, methods, and so on. And also to list complex types using so-called annotations `@var`, `@param` and `@return`. These types are then used by static code analysis tools, but are not checked by PHP itself. + +```php +class Registry +{ + /** @var array<Person> indicates that it's an array of Person objects */ + private array $persons = []; + + public function addPerson(Person $person): void + { + $this->persons[] = $person; + } +} +``` + + +Comparison and Identity +----------------------- + +In PHP, you can compare objects in two ways: + +1. Value comparison `==`: Checks if the objects are of the same class and have the same values in their properties. +2. Identity `===`: Checks if it's the same instance of the object. + +```php +class Car +{ + public string $brand; + + public function __construct(string $brand) + { + $this->brand = $brand; + } +} + +$car1 = new Car('Skoda'); +$car2 = new Car('Skoda'); +$car3 = $car1; + +var_dump($car1 == $car2); // true, because they have the same value +var_dump($car1 === $car2); // false, because they are not the same instance +var_dump($car1 === $car3); // true, because $car3 is the same instance as $car1 +``` + + +The `instanceof` Operator +------------------------- + +The `instanceof` operator allows you to determine if a given object is an instance of a specific class, a descendant of that class, or if it implements a certain interface. + +Imagine we have a class `Person` and another class `Student`, which is a descendant of `Person`: + +```php +class Person +{ + private int $age; + + public function __construct(int $age) + { + $this->age = $age; + } +} + +class Student extends Person +{ + private string $major; + + public function __construct(int $age, string $major) + { + parent::__construct($age); + $this->major = $major; + } +} + +$student = new Student(20, 'Computer Science'); + +// Check if $student is an instance of the Student class +var_dump($student instanceof Student); // Output: bool(true) + +// Check if $student is an instance of the Person class (because Student is a descendant of Person) +var_dump($student instanceof Person); // Output: bool(true) +``` + +From the outputs, it's evident that the `$student` object is considered an instance of both the `Student` and `Person` classes. + + +Fluent Interfaces +----------------- + +A "Fluent Interface" is a technique in OOP that allows chaining methods together in a single call. This often simplifies and clarifies the code. + +The key element of a fluent interface is that each method in the chain returns a reference to the current object. This is achieved by using `return $this;` at the end of the method. This programming style is often associated with methods called "setters", which set the values of an object's properties. + +Let's see what a fluent interface might look like for sending emails: + +```php +public function sendMessage() +{ + $email = new Email; + $email->setFrom('sender@example.com') + ->setRecipient('admin@example.com') + ->setMessage('Hello, this is a message.') + ->send(); +} +``` + +In this example, the methods `setFrom()`, `setRecipient()`, and `setMessage()` are used to set the corresponding values (sender, recipient, message content). After setting each of these values, the methods return the current object (`$email`), allowing us to chain another method after it. Finally, we call the `send()` method, which actually sends the email. + +Thanks to fluent interfaces, we can write code that is intuitive and easily readable. + + +Copying with `clone` +-------------------- + +In PHP, we can create a copy of an object using the `clone` operator. This way, we get a new instance with identical content. + +If we need to modify some of its properties when copying an object, we can define a special `__clone()` method in the class. This method is automatically called when the object is cloned. + +```php +class Sheep +{ + public string $name; + + public function __construct(string $name) + { + $this->name = $name; + } + + public function __clone() + { + $this->name = 'Clone of ' . $this->name; + } +} + +$original = new Sheep('Dolly'); +echo $original->name . "\n"; // Outputs: Dolly + +$clone = clone $original; +echo $clone->name . "\n"; // Outputs: Clone of Dolly +``` + +In this example, we have a `Sheep` class with one property `$name`. When we clone an instance of this class, the `__clone()` method ensures that the name of the cloned sheep gets the prefix "Clone of". + + +Traits +------ + +Traits in PHP are a tool that allows sharing methods, properties and constants between classes and prevents code duplication. You can think of them as a "copy and paste" mechanism (Ctrl-C and Ctrl-V), where the content of a trait is "pasted" into classes. This allows you to reuse code without having to create complicated class hierarchies. + +Let's take a look at a simple example of how to use traits in PHP: + +```php +trait Honking +{ + public function honk() + { + echo 'Beep beep!'; + } +} + +class Car +{ + use Honking; +} + +class Truck +{ + use Honking; +} + +$car = new Car; +$car->honk(); // Outputs 'Beep beep!' + +$truck = new Truck; +$truck->honk(); // Also outputs 'Beep beep!' +``` + +In this example, we have a trait named `Honking` that contains one method `honk()`. Then we have two classes: `Car` and `Truck`, both of which use the `Honking` trait. As a result, both classes "have" the `honk()` method, and we can call it on objects of both classes. + +Traits allow you to easily and efficiently share code between classes. They do not enter the inheritance hierarchy, i.e., `$car instanceof Honking` will return `false`. + + +Exceptions +---------- + +Exceptions in OOP allow us to gracefully handle errors and unexpected situations in our code. They are objects that carry information about an error or unusual situation. + +In PHP, we have a built-in class `Exception`, which serves as the basis for all exceptions. This has several methods that allow us to get more information about the exception, such as the error message, the file and line where the error occurred, etc. + +When an error occurs in the code, we can "throw" the exception using the `throw` keyword. + +```php +function division(float $a, float $b): float +{ + if ($b === 0) { + throw new Exception('Division by zero!'); + } + return $a / $b; +} +``` + +When the `division()` function receives null as its second argument, it throws an exception with the error message `'Division by zero!'`. To prevent the program from crashing when the exception is thrown, we trap it in the `try/catch` block: + +```php +try { + echo division(10, 0); +} catch (Exception $e) { + echo 'Exception caught: '. $e->getMessage(); +} +``` + +Code that can throw an exception is wrapped in a block `try`. If the exception is thrown, the code execution moves to a block `catch`, where we can handle the exception (e.g., write an error message). + +After the `try` and `catch` blocks, we can add an optional block `finally`, which is always executed whether the exception was thrown or not (even if we use `return`, `break`, or `continue` in the `try` or `catch` block): + +```php +try { + echo division(10, 0); +} catch (Exception $e) { + echo 'Exception caught: '. $e->getMessage(); +} finally { + // Code that is always executed whether the exception has been thrown or not +} +``` + +We can also create our own exception classes (hierarchy) that inherit from the Exception class. As an example, consider a simple banking application that allows deposits and withdrawals: + +```php +class BankingException extends Exception {} +class InsufficientFundsException extends BankingException {} +class ExceededLimitException extends BankingException {} + +class BankAccount +{ + private int $balance = 0; + private int $dailyLimit = 1000; + + public function deposit(int $amount): int + { + $this->balance += $amount; + return $this->balance; + } + + public function withdraw(int $amount): int + { + if ($amount > $this->balance) { + throw new InsufficientFundsException('Not enough funds in the account.'); + } + + if ($amount > $this->dailyLimit) { + throw new ExceededLimitException('Daily withdrawal limit exceeded.'); + } + + $this->balance -= $amount; + return $this->balance; + } +} +``` + +Multiple `catch` blocks can be specified for a single `try` block if you expect different types of exceptions. + +```php +$account = new BankAccount; +$account->deposit(500); + +try { + $account->withdraw(1500); +} catch (ExceededLimitException $e) { + echo $e->getMessage(); +} catch (InsufficientFundsException $e) { + echo $e->getMessage(); +} catch (BankingException $e) { + echo 'An error occurred during the operation.'; +} +``` + +In this example, it's important to note the order of the `catch` blocks. Since all exceptions inherit from `BankingException`, if we had this block first, all exceptions would be caught in it without the code reaching the subsequent `catch` blocks. Therefore, it's important to have more specific exceptions (i.e., those that inherit from others) higher in the `catch` block order than their parent exceptions. + + +Iterations +---------- + +In PHP, you can loop through objects using the `foreach` loop, much like you loop through an array. For this to work, the object must implement a special interface. + +The first option is to implement the interface `Iterator`, which has methods `current()` returning the current value, `key()` returning the key, `next()` moving to the next value, `rewind()` moving to the beginning, and `valid()` checking to see if we're at the end yet. + +The other option is to implement an interface `IteratorAggregate`, which has only one method `getIterator()`. This either returns a placeholder object that will provide the traversal, or it can be a generator, which is a special function that uses `yield` to return keys and values sequentially: + +```php +class Person +{ + public function __construct( + public int $age, + ) { + } +} + +class Registry implements IteratorAggregate +{ + private array $people = []; + + public function addPerson(Person $person): void + { + $this->people[] = $person; + } + + public function getIterator(): Generator + { + foreach ($this->people as $person) { + yield $person; + } + } +} + +$list = new Registry; +$list->addPerson(new Person(30)); +$list->addPerson(new Person(25)); + +foreach ($list as $person) { + echo "Age: {$person->age} years\n"; +} +``` + + +Best Practices +-------------- + +Once you have the basic principles of object-oriented programming under your belt, it's crucial to focus on best practices in OOP. These will help you write code that is not only functional but also readable, understandable, and easily maintainable. + +1) **Separation of Concerns**: Each class should have a clearly defined responsibility and should address only one primary task. If a class does too many things, it might be appropriate to split it into smaller, specialized classes. +2) **Encapsulation**: Data and methods should be as hidden as possible and accessible only through a defined interface. This allows you to change the internal implementation of a class without affecting the rest of the code. +3) **Dependency Injection**: Instead of creating dependencies directly within a class, you should "inject" them from the outside. For a deeper understanding of this principle, we recommend the [chapters on Dependency Injection|dependency-injection:introduction]. diff --git a/nette/en/troubleshooting.texy b/nette/en/troubleshooting.texy index 4e71bb00ac..45bac306a1 100644 --- a/nette/en/troubleshooting.texy +++ b/nette/en/troubleshooting.texy @@ -4,38 +4,67 @@ Troubleshooting Nette Is Not Working, White Page Is Displayed --------------------------------------------- -- Try putting `ini_set('display_errors', '1'); error_reporting(E_ALL);` after `declare(strict_types=1);` in the `index.php` file to force the display of errors -- If you still see a white screen, there is probably an error in the server setup and you will discover the reason in the server log. To be sure, check if PHP is working at all by trying to print something using `echo 'test';`. -- If you see an error *Server Error: We're sorry! ...*, continue with the next section: +- Try putting `ini_set('display_errors', '1'); error_reporting(E_ALL);` after `declare(strict_types=1);` in the `index.php` file to force the display of errors. +- If you still see a white screen, there is probably an error in the server configuration, and you will find the reason in the server log. To be sure, check if PHP is working at all by trying to print something using `echo 'test';`. +- If you see the error *Server Error: We're sorry! …*, continue with the next section: -Error 500 *Server Error: We're sorry! ...* ------------------------------------------- -This error page is displayed by Nette in production mode. If you are seeing it on a developer machine, [switch to developer mode |application:bootstrap#Development vs Production Mode]. +Error 500 *Server Error: We're sorry! …* +---------------------------------------- +This error page is displayed by Nette in production mode. If you see it on your development machine, [switch to developer mode |application:bootstrapping#Development vs Production Mode], and Tracy will display a detailed report. + +You can always find the reason for the error in the `log/` directory log. However, if the error message shows the phrase `Tracy is unable to log error`, first determine why errors cannot be logged. You can do this, for example, by temporarily [switching |application:bootstrapping#Development vs Production Mode] to developer mode and letting Tracy log anything after it starts: + +```php +// Bootstrap.php +$configurator->setDebugMode('23.75.345.200'); // your IP address +$configurator->enableTracy($rootDir . '/log'); +\Tracy\Debugger::log('hello'); +``` + +Tracy will tell you why it cannot log. The cause might be [insufficient permissions |#Setting Directory Permissions] to write to the `log/` directory. + +One of the most common reasons for a 500 error is an outdated cache. While Nette smartly updates the cache automatically in development mode, in production mode, it focuses on maximizing performance, and clearing the cache after each code modification is your responsibility. Try deleting `temp/cache`. + -If the error message contains `Tracy is unable to log error`, find out why the errors cannot be logged. You can do this by, for example, [switching |application:bootstrap#Development vs Production Mode] to developer mode and calling `Tracy\Debugger::log('hello');` after `$configurator->enableTracy(...)`. Tracy will tell you why it can't log. -The cause is usually [insufficient permissions |#Setting Directory Permissions] to write to the `log/` directory. +Error 404, Routing Not Working +------------------------------ +When all pages (except the homepage) return a 404 error, it seems like a server configuration problem for [pretty URLs |#How to Configure a Server for Nice URLs]. -If the sentence `Tracy is unable to log error` is not in the error message (anymore), you can find out the reason for the error in the log in the `log/` directory. -One of the most common reasons is an outdated cache. While Nette cleverly automatically updates the cache in development mode, in production mode it focuses on maximizing performance, and clearing the cache after each code modification is up to you. Try to delete `temp/cache`. +Changes in Templates or Configuration Are Not Reflected +------------------------------------------------------- +"I modified the template or configuration, but the website still displays the old version." This behavior occurs in [production mode |application:bootstrapping#Development vs Production Mode], which, for performance reasons, does not check for file changes and maintains the previously generated cache. + +To avoid manually clearing the cache on the production server after every modification, enable development mode for your IP address in the `Bootstrap.php` file: + +```php +$this->configurator->setDebugMode('your.ip.address'); +``` + + +How to Disable Cache During Development? +---------------------------------------- +Nette is smart, and you don't need to disable caching in it. During development, it automatically updates the cache whenever there's a change in the template or the DI container configuration. Moreover, the development mode is activated by auto-detection, so there's usually no need to configure anything, [or just the IP address |application:bootstrapping#Development vs Production Mode]. + +When debugging the router, we recommend disabling the browser cache, where, for example, redirects might be stored: open Developer Tools (Ctrl+Shift+I or Cmd+Option+I) and in the Network panel, check the box to disable the cache. Error `#[\ReturnTypeWillChange] attribute should be used` --------------------------------------------------------- -This error occurs if you have upgraded PHP to version 8.1 but are using Nette, which is not compatible with it. So the solution is to update Nette to a newer version using `composer update`. Nette has supported PHP 8.1 since version 3.0. If you are using an older version (you can find out by looking in `composer.json`), [upgrade Nette |migrations:] or stay with PHP 8.0. +This error occurs if you have upgraded PHP to version 8.1 but are using a version of Nette that is not compatible with it. The solution is to update Nette to a newer version using `composer update`. Nette has supported PHP 8.1 since version 3.0. If you are using an older version (check your `composer.json`), [upgrade Nette |migrations:] or stay with PHP 8.0. Setting Directory Permissions ----------------------------- -If you're developing on macOS or Linux (or any other Unix based system), you need to configure write privileges to the web server. Assuming your application is located in the default directory `/var/www/html` (Fedora, CentOS, RHEL) +If you're developing on macOS or Linux (or any other Unix-based system), you need to configure write privileges for the web server. Assuming your application is located in the default directory `/var/www/html` (Fedora, CentOS, RHEL): ```shell cd /var/www/html/MY_PROJECT chmod -R a+rw temp log ``` -On some Linux systems (Fedora, CentOS, ...) SELinux may be enabled by default. You may need to update SELinux policies, or set paths of `temp` and `log` directories with correct SELinux security context. Directories `temp` and `log` should be set to `httpd_sys_rw_content_t` context; for the rest of the application -- mainly `app` folder -- `httpd_sys_content_t` context will be enough. Run on the server as root: +On some Linux systems (Fedora, CentOS, ...), SELinux may be enabled by default. You may need to update SELinux policies or set the paths of the `temp` and `log` directories with the correct SELinux security context. The `temp` and `log` directories should be set to the `httpd_sys_rw_content_t` context; for the rest of the application – mainly the `app` folder – the `httpd_sys_content_t` context will be sufficient. Run on the server as root: ```shell semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/log(/.*)?' @@ -43,7 +72,7 @@ semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/temp(/.*) restorecon -Rv /var/www/html/MY_PROJECT/ ``` -Next, the SELinux boolean `httpd_can_network_connect_db` needs to be enabled to permit Nette to connect to the database over network. By default, it is disabled. The command `setsebool` can be used to perform this task, and if the option `-P` is specified, this setting will be persistent across reboots. +Next, the SELinux boolean `httpd_can_network_connect_db` needs to be enabled to permit Nette to connect to the database over the network. By default, it is disabled. The `setsebool` command can be used for this task, and if the `-P` option is specified, this setting will be persistent across reboots: ```shell setsebool -P httpd_can_network_connect_db on @@ -52,16 +81,21 @@ setsebool -P httpd_can_network_connect_db on How to Change or Remove `www` Directory from URL? ------------------------------------------------- -The `www/` directory used in the sample projects in Nette is the so-called public directory or document-root of the project. It is the only directory whose contents are accessible to the browser. And it contains the `index.php` file, the entry point that starts a web application written in Nette. +The `www/` directory used in Nette's sample projects represents the public directory or document-root of the project. It is the only directory whose content is accessible to the browser. It contains the `index.php` file, the entry point that starts the Nette web application. + +To run the application on a hosting service, you need to configure the document-root correctly. You have two options: +1. Set the document-root to this directory in the hosting configuration. +2. If the hosting has a pre-prepared folder (e.g., `public_html`), rename `www/` to this name. -To run the application on the hosting, you need to set the document-root to this directory in the hosting configuration. Or, if the hosting has a pre-made folder for the public directory with a different name (for example `web`, `public_html` etc.), simply rename `www/`. +.[warning] +Never try to secure your application by only using `.htaccess` or router rules to prevent access to other folders. -The solution **isn't** to "get rid" of the `www/` folder using rules in the `.htaccess` file or in the router. If the hosting would not allow you to set document-root to a subdirectory (i.e. create directories one level above the public directory), look for another. You would otherwise be taking a significant security risk. It would be like living in an apartment where you can't close the front door and it's always wide open. +If the hosting service does not allow setting the document-root to a subdirectory (i.e., creating directories one level above the public directory), look for another provider. Otherwise, you would face a significant security risk. It would be like living in an apartment where the front door cannot be closed and is always wide open. How to Configure a Server for Nice URLs? ---------------------------------------- -**Apache**: extension mod_rewrite must be allowed and configured in a `.htaccess` file. +**Apache**: You need to enable and configure mod_rewrite rules in the `.htaccess` file: ```apacheconf RewriteEngine On @@ -70,25 +104,53 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -To alter Apache configuration with .htaccess files, the AllowOverride directive has to be enabled. This is the default behavior for Apache. +If you encounter problems, make sure that: +- the `.htaccess` file is located in the document-root directory (i.e., next to the `index.php` file) +- [Apache is processing `.htaccess` files |#Test If .htaccess Is Working] +- [mod_rewrite is enabled |#Test If mod rewrite Is Enabled] -**nginx**: the `try_files` directive should be used in server configuration: +If you are setting up the application in a subfolder, you might need to uncomment the line for the `RewriteBase` setting and set it to the correct folder. + +**nginx**: Redirection needs to be configured using the `try_files` directive inside the `location /` block in the server configuration. ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args is important + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args IS IMPORTANT! } ``` -Block `location` must be defined exactly once for each filesystem path in `server` block. If you already have a `location /` block in your configuration, add the `try_files` directive into the existing block. +The `location` block must appear only once for each filesystem path within the `server` block. If you already have a `location /` block in your configuration, add the `try_files` directive into the existing block. + + +Test If `.htaccess` Is Working +------------------------------ +The simplest way to test if Apache uses or ignores your `.htaccess` file is to intentionally break it. Put the line `Test` at the beginning of the file. Now, if you refresh the page in your browser, you should see an *Internal Server Error*. + +If you see this error, that's actually good! It means that Apache is parsing the `.htaccess` file and encountering the error we put there. Remove the `Test` line. + +If you do not see an *Internal Server Error*, your Apache setup ignores the `.htaccess` file. Generally, Apache ignores it because the configuration directive `AllowOverride All` is missing. + +If you are hosting it yourself, it's easy enough to fix. Open your `httpd.conf` or `apache.conf` in a text editor, locate the relevant `<Directory>` section, and add/change this directive: + +```apacheconf +<Directory "/var/www/htdocs"> # path to your document root + AllowOverride All + ... +``` + +If your site is hosted elsewhere, check your control panel to see if you can enable `.htaccess` there. If not, contact your hosting provider to do it for you. + + +Test If `mod_rewrite` Is Enabled +-------------------------------- +If you have verified that [`.htaccess` works |#Test If .htaccess Is Working], you can verify that the mod_rewrite extension is enabled. Put the line `RewriteEngine On` at the beginning of the `.htaccess` file and refresh the page in your browser. If you see an *Internal Server Error*, it means that mod_rewrite is not enabled. There are several ways to enable it. See Stack Overflow for various ways this can be done on different setups. Links Are Generated Without `https:` ------------------------------------ -Nette generates links with the same protocol as the current page is using. So on the `https://foo` page, it generates links starting with `https:` and vice versa. -If you're behind an HTTPS-stripping reverse proxy (for example, in Docker), then you need to [set up a proxy|http:configuration#HTTP proxy] in configuration to make the protocol detection work properly. +Nette generates links with the same protocol as the current page is using. So, on an `https://foo` page, it generates links starting with `https:`, and vice versa. If you are behind an HTTPS-stripping reverse proxy (for example, in Docker), you need to [set up a proxy |http:configuration#HTTP Proxy] in the configuration so that protocol detection works correctly. -If you use Nginx as a proxy, you need to have redirection set up like this: +If you use Nginx as a proxy, you need to have redirection set up, for example, like this: ``` location / { @@ -100,7 +162,7 @@ location / { } ``` -Next, you need to specify the IP proxy and, if applicable, the IP range of your local network where you run the infrastructure: +Furthermore, you need to specify the proxy IP and optionally the IP range of your local network where you run the infrastructure in the configuration: ```neon http: @@ -110,34 +172,32 @@ http: Use of Characters { } in JavaScript ----------------------------------- -Characters `{` and `}` are used for writing Latte tags. Everything (except space and quotation mark) following the `{` character is considered a tag. If you need to print character `{` (often in JavaScript), you can put a space (or other empty character) right after `{`. By this you avoid interpreting it as a tag. +The characters `{` and `}` are used for writing Latte tags. Anything following the `{` character (except space and quotation mark) is considered a tag. If you need to print the character `{` directly (often in JavaScript), you can put a space (or another whitespace character) right after `{`. This prevents it from being interpreted as a tag. -If it's necessary to print these characters in a situation where they would be interpreted as a tag you can use specials tags to print out these characters - `{l}` for `{` and `{r}` for `}`. +If it's necessary to print these characters in a situation where the text would be interpreted as a tag, you can use special tags to output these characters - `{l}` for `{` and `{r}` for `}`. ``` -{is tag} -{ is not tag } -{l}is not tag{r} +{is a tag} +{ is not a tag } +{l}is not a tag{r} ``` Notice `Presenter::getContext() is deprecated` ---------------------------------------------- -Nette is by far the first PHP framework that switched to dependency injection and led programmers to use it consistently, starting from the presenters. If a presenter needs a dependency, [it will ask for it|dependency-injection:passing-dependencies]. -In contrast, the way we pass the entire DI container to a class and it pulls the dependencies from it directly is considered an antipattern (it's called a service locator). -This way was used in Nette 0.x before the advent of dependency injection, and its relic is the method `Presenter::getContext()`, long ago marked as deprecated. +Nette was by far the first PHP framework to switch to dependency injection and guided programmers to use it consistently, starting right from the presenters. If a presenter needs a dependency, [it asks for it|dependency-injection:passing-dependencies]. Conversely, passing the entire DI container to a class and having it pull dependencies directly is considered an antipattern (known as the service locator pattern). This approach was used in Nette 0.x before the advent of dependency injection, and the method `Presenter::getContext()`, long marked as deprecated, is a remnant of that era. -If you port a very old Nette application, you may find that it still uses this method. So since version 3.1 of `nette/application` you will encounter the warning `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection`, since version 4.0 you will encounter the error that the method does not exist. +If you are porting a very old Nette application, you might find that it still uses this method. Since `nette/application` version 3.1, you will encounter the warning `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection`, and since version 4.0, an error stating the method does not exist. -The clean solution, of course, is to redesign the application to pass dependencies using dependency injection. As a workaround, you can add your own `getContext()` method to your base presenter and bypass the message: +The clean solution, of course, is to refactor the application to pass dependencies using dependency injection. As a workaround, you can add your own `getContext()` method to your base presenter to bypass the message: ```php -abstract BasePresenter extends Nette\Application\UI\Presenter +abstract class BasePresenter extends Nette\Application\UI\Presenter { private Nette\DI\Container $context; - public function injectContext(Nette\DI\Container $context) + public function injectContext(Nette\DI\Container $context): void { $this->context = $context; } diff --git a/nette/en/vulnerability-protection.texy b/nette/en/vulnerability-protection.texy index 0eedf7cade..ceda856ccc 100644 --- a/nette/en/vulnerability-protection.texy +++ b/nette/en/vulnerability-protection.texy @@ -2,21 +2,21 @@ Vulnerability Protection ************************ .[perex] -Every now and then a major security flaw is announced or even abused. For sure that's a little bit unpleasant. If you care about the security of your web applications, Nette Framework is frankly the best choice for you. +Every now and then, a major security flaw is announced or even exploited on another significant website. This is unpleasant. If you care about the security of your web applications, Nette Framework is undoubtedly the best choice. Cross-Site Scripting (XSS) ========================== -Cross-Site Scripting is a site disruption method using unescaped input. An attacker may inject his own HTML or JavaScript code and change the look of the page or even gather sensitive information about users. Protection against XSS is simple: consistent and correct escaping of all strings and inputs. Traditionally, it would be enough if your coder made just one slightest error and forgot once, and the whole website could be compromised. +Cross-Site Scripting is a method of disrupting websites by exploiting unescaped outputs. An attacker can then inject their own code into the page, thereby modifying the page or even obtaining sensitive data about visitors. Protection against XSS requires consistent and correct escaping of all output strings. If your developer forgets this just once, the entire website could be compromised. -An example of such an injection may be slipping the user an altered URL, which inserts a "malicious" script. If an application does not escape its inputs properly, such a request would possibly execute a script on the client's side. This may, for example, lead to stolen identity. +An example of an attack could be providing a user with a modified URL that injects code into the page. If the application doesn't properly escape outputs, the script executes in the user's browser. This way, for example, their identity can be stolen. ``` -https://example.com/?search=<script>alert('XSS attack.');</script> +https://example.com/?search=<script>alert('Successful XSS attack.');</script> ``` -Nette Framework comes up with a brand new technology of [Context-Aware Escaping |latte:safety-first#context-aware-escaping], which will get you rid of the Cross-Site Scripting risks forever. It escapes all inputs automatically based on a given context, so it's impossible for a coder to accidentally forget something. Consider the following template as an example: +Nette Framework introduces the revolutionary [Context-Aware Escaping |latte:safety-first#Context-Aware Escaping] technology, which permanently eliminates the risk of Cross-Site Scripting. It automatically escapes all outputs based on the context, so a developer cannot accidentally forget anything. Example? A developer creates this template: ```latte <p onclick="alert({$message})">{$message}</p> @@ -26,7 +26,7 @@ document.title = {$message}; </script> ``` -The `{$message}` command prints a variable. Other frameworks do often force developers to explicitly declare escaping, and even what type of escaping based on the context. Yet in Nette Framework you don't need to declare anything. Everything is automatic, consistent and just right. If we set the variable to `$message = 'Width 1/2"'`, the framework will generate this HTML code: +The `{$message}` notation means printing a variable. In other frameworks, it's necessary to explicitly escape each output, and even differently in each place. In Nette Framework, nothing needs to be escaped; everything is done automatically, correctly, and consistently. If we set the variable `$message = 'Width 1/2"'`, the framework generates the following HTML code: ```latte <p onclick="alert("Width 1\/2\"")">Width 1/2"</p> @@ -40,31 +40,20 @@ document.title = "Width 1\/2\""; Cross-Site Request Forgery (CSRF) ================================= -A Cross-Site Request Forgery attack is that the attacker lures the victim to visit a page that silently executes a request in the victim's browser to the server where the victim is currently logged in, and the server believes that the request was made by the victim at will. Server performs a certain action under the identity of the victim but without the victim realizing it. It can be changing or deleting data, sending a message, etc. +A Cross-Site Request Forgery attack involves the attacker luring a victim to a page that subtly executes a request in the victim's browser to a server where the victim is logged in. The server believes the request was made willingly by the victim. Thus, under the victim's identity, it performs an action without their knowledge. This could involve changing or deleting data, sending a message, etc. -Nette Framework **automatically protects forms and signals in presenters** from this type of attack. This is done by preventing them from being sent or called from another domain. To turn off protection, use the following for forms: +Nette Framework **automatically protects forms and signals in presenters** against this type of attack by preventing them from being submitted or triggered from another domain. If you want to disable protection, use the following for forms: ```php $form->allowCrossOrigin(); ``` -or in the case of a signal, add an annotation `@crossOrigin`: +or in the case of a signal, add the attribute: ```php -/** - * @crossOrigin - */ -public function handleXyz() -{ -} -``` +use Nette\Application\Attributes\Requires; -In PHP 8, you can also use attributes: - -```php -use Nette\Application\Attributes\CrossOrigin; - -#[CrossOrigin] +#[Requires(sameOrigin: false)] public function handleXyz() { } @@ -74,27 +63,26 @@ public function handleXyz() URL Attack, Control Codes, Invalid UTF-8 ======================================== -Different terms all related to the attacker's effort to give your application a "malicious" input. The results may vary greatly, from broken XML outputs (i.e. malfunctioned RSS stream) to getting sensitive information from an database to getting user passwords. Protection against these attacks is consistent UTF-8 check on byte level. And frankly, you would not do that without a framework, right? +Various terms related to an attacker's attempt to inject *malicious* input into your web application. The consequences can vary widely, from damaging XML outputs (e.g., non-functional RSS feeds) to obtaining sensitive information from the database or passwords. The defense is consistent byte-level validation of all inputs. And let's be honest, how many of you actually do this? -Nette Framework does this for you, automatically. You don't have to configure anything at all and your application will be safe. +Nette Framework does it for you, automatically. You don't need to configure anything, and all inputs will be sanitized. Session Hijacking, Session Stealing, Session Fixation ===================================================== -Session management involves a few types of attacks. The attacker may steal the victim's session ID or forge one and thus gain access to a web application without the actual password. Then the attacker may do whatever the user could, without any trace. The protection lies in proper configuration of both PHP and the web server itself. +Several types of attacks are associated with session management. An attacker either steals or plants their session ID onto the user, thereby gaining access to the web application without knowing the user's password. They can then perform any action within the application without the user's knowledge. The defense lies in the proper configuration of the server and PHP. -Nette Framework configures PHP automatically. Developers thus do not have to worry about how to make a session protected enough and can fully focus on the key parts of the application. This requires the `ini_set()` function to be enabled. +Nette Framework configures PHP automatically. Thus, the programmer doesn't have to think about how to secure the session properly and can fully concentrate on developing the application. However, this requires the `ini_set()` function to be enabled. SameSite Cookie =============== -SameSite cookies provide a mechanism for recognizing what led to a page load. Which is absolutely crucial for security reasons. - -The SameSite flag can have three values: `Lax`, `Strict` and `None` (it requires HTTPS). If a request for a page comes directly from the web itself or the user opens the page by directly entering it in the address bar or clicking on a bookmark, the browser sends all cookies to the server (ie with the flags `Lax`, `Strict` and `None`). If a user comes to website via click on link from another website, cookies with the `Lax` and `None` flags will be passed to the server. If the request arises in another way such as sending a POST form from another origin, loading inside an iframe, using JavaScript, etc., only cookies with the `None` flag will be sent. +SameSite cookies provide a mechanism to recognize what led to the page load, which is crucial for security. -By default, Nette sends all cookies with the `Lax` flag. +The SameSite flag can have three values: `Lax`, `Strict`, and `None` (the latter requires HTTPS). If a request for a page comes directly from the website, or the user opens the page by directly entering it in the address bar or clicking on a bookmark, the browser sends all cookies to the server (i.e., with flags `Lax`, `Strict`, and `None`). If a user clicks through to the website via a link from another website, cookies with the `Lax` and `None` flags are passed to the server. If the request originates in another way, such as submitting a POST form from another origin, loading inside an iframe, using JavaScript, etc., only cookies with the `None` flag are sent. +Nette defaults to sending all cookies with the `Lax` flag. {{leftbar: www:@menu-common}} diff --git a/nette/es/@home.texy b/nette/es/@home.texy index c6644ab4c5..09e8c28881 100644 --- a/nette/es/@home.texy +++ b/nette/es/@home.texy @@ -8,22 +8,22 @@ Documentación de Nette Introducción ------------ -- [¿Por qué utilizar Nette? |www:10-reasons-why-nette] -- [Instalación |Installation] -- [Cree su primera aplicación |quickstart:] +- [¿Por qué usar Nette? |www:10-reasons-why-nette] +- [Instalación |installation] +- [¡Escribamos nuestra primera aplicación! |quickstart:] General ------- - [Lista de paquetes |www:packages] -- [Mantenimiento y PHP |www:maintenance] -- [Notas de la versión |https://nette.org/releases] -- [Guía de actualización |migrations:en] -- [Solución de problemas |Troubleshooting] -- [Quién crea Nette|https://nette.org/contributors] -- [Historia de Nette|www:history] -- [Implíquese |contributing:] -- [Desarrollo del patrocinio |https://nette.org/en/donate] +- [Mantenimiento y versiones de PHP |www:maintenance] +- [Release Notes |https://nette.org/releases] +- [Migración a versiones más recientes|migrations:en] +- [Solución de problemas |nette:troubleshooting] +- [Quién hace Nette |https://nette.org/contributors] +- [Historia de Nette |www:history] +- [Participa |contributing:] +- [Apoya el desarrollo |https://nette.org/cs/donate] - [Referencia API |https://api.nette.org/] </div> @@ -31,20 +31,20 @@ General <div> -Aplicación Nette ----------------- +Aplicaciones en Nette +--------------------- - [¿Cómo funcionan las aplicaciones? |application:how-it-works] -- [Bootstrap |application:Bootstrap] -- [Presentadores |application:Presenters] -- [Plantillas |application:Templates] -- [Módulos |application:Modules] -- [Enrutamiento |application:Routing] +- [application:Bootstrapping] +- [Presenters |application:presenters] +- [Plantillas |application:templates] +- [Estructura de directorios |application:directory-structure] +- [Enrutamiento |application:routing] - [Creación de enlaces URL |application:creating-links] - [Componentes interactivos |application:components] -- [AJAX y fragmentos |application:ajax] +- [AJAX y snippets |application:ajax] -- [Buenas prácticas |best-practices:] +- [Tutoriales y mejores prácticas |best-practices:] </div> @@ -54,20 +54,21 @@ Aplicación Nette Temas principales ----------------- - [Configuración |nette:configuring] -- [Inyección de dependencia |dependency-injection:] -- [Latte: Plantillas |latte:] -- [Tracy: Herramienta de depuración |tracy:] +- [Inyección de Dependencias|dependency-injection:] +- [Latte: plantillas |latte:] +- [Tracy: depuración de código |tracy:] - [Formularios |forms:] -- [Base de datos |database:core] -- [Autenticación de usuarios |security:authentication] -- [Control de Acceso |security:authorization] -- [Sesiones |http:Sessions] -- [HTTP petición |http:request] y [respuesta |http:response] -- [Almacenamiento en caché |caching:] +- [Base de datos |database:guide] +- [Inicio de sesión de usuarios |security:authentication] +- [Verificación de permisos |security:authorization] +- [http:Sessions] +- [Petición y respuesta HTTP|http:] +- [Activos |assets:] +- [Caché |caching:] - [Envío de correos electrónicos |mail:] -- [Schema |schema:] +- [Schema: validación de datos |schema:] - [Generador de código PHP |php-generator:] -- [Tester: Pruebas unitarias |tester:] +- [Tester: pruebas |tester:] </div> @@ -76,26 +77,26 @@ Temas principales Utilidades ---------- -- [Matrices |utils:Arrays] +- [Arrays |utils:arrays] - [Sistema de archivos |utils:filesystem] -- [Buscador |utils:finder] -- [Elementos HTML |utils:HTML Elements] -- [Imágenes |utils:Images] -- [JSON |utils:JSON] -- [NEON |neon:] +- [Finder |utils:finder] +- [Elementos HTML |utils:html-elements] +- [Imágenes |utils:images] +- [utils:JSON] +- [NEON|neon:] - [Hashing de contraseñas |security:passwords] -- [SmartObject |utils:SmartObject] -- [Tipos de PHP |utils:type] -- [Cadenas |utils:Strings] +- [Tipos PHP |utils:type] +- [Cadenas |utils:strings] - [Validadores |utils:validators] - [RobotLoader |robot-loader:] +- [SmartObject |utils:smartobject] & [StaticClass |utils:StaticClass] - [SafeStream |safe-stream:] -- ... [otros |utils:] +- [...más |utils:] </div> </div> {{toc:no}} -{{description: Documentación oficial de Nette: describe cómo funciona Nette y las mejores prácticas para desarrollar aplicaciones web.}} +{{description: Documentación oficial de Nette: describe cómo funciona Nette y las mejores prácticas para el desarrollo de aplicaciones web.}} {{maintitle: Documentación de Nette}} diff --git a/nette/es/@menu-topics.texy b/nette/es/@menu-topics.texy index 430c1c8cc6..c4753078e4 100644 --- a/nette/es/@menu-topics.texy +++ b/nette/es/@menu-topics.texy @@ -1,21 +1,21 @@ Temas principales ***************** - [Configuración |nette:configuring] -- [Aplicación Nette |application:how-it-works] -- [Inyección de Dependencias |dependency-injection:] +- [Aplicaciones en Nette |application:how-it-works] +- [Inyección de Dependencias|dependency-injection:] - [Utilidades |utils:] - [Formularios |forms:] -- [Base de datos |database:core] -- [Autenticación de usuarios |security:authentication] -- [Control de acceso |security:authorization] -- [Sesiones |http:Sessions] -- [HTTP petición y respuesta|http:] -- [Almacenamiento en caché |caching:] +- [Base de datos |database:guide] +- [Inicio de sesión de usuarios |security:authentication] +- [Verificación de permisos |security:authorization] +- [http:Sessions] +- [Petición y respuesta HTTP|http:] +- [Caché |caching:] - [Envío de correos electrónicos |mail:] - [Schema: validación de datos |schema:] - [Generador de código PHP |php-generator:] - [Latte: plantillas |latte:] -- [Tracy: depuración |tracy:] +- [Tracy: depuración de código |tracy:] - [Tester: pruebas |tester:] diff --git a/nette/es/@meta.texy b/nette/es/@meta.texy new file mode 100644 index 0000000000..1670b124ad --- /dev/null +++ b/nette/es/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Documentación}} diff --git a/nette/es/configuring.texy b/nette/es/configuring.texy index 5647f0f478..1ad6725a1d 100644 --- a/nette/es/configuring.texy +++ b/nette/es/configuring.texy @@ -2,34 +2,35 @@ Configuración de Nette ********************** .[perex] -Un resumen de todas las opciones de configuración de Nette Framework. +Resumen de todas las opciones de configuración en Nette Framework. -Los componentes de Nette se configuran usando ficheros de configuración, que normalmente están escritos en [NEON |neon:format]. Es mejor editarlos en [editores que lo soporten |best-practices:editors-and-tools#ide-editor]. -Si está usando el framework completo, la configuración se [cargará durante el arranque |application:bootstrap#di-container-configuration], si no, vea [cómo cargar la configuración |bootstrap:]. +Los componentes de Nette se configuran mediante archivos de configuración, que generalmente se escriben en [formato NEON|neon:format]. Se editan mejor en [editores con soporte para ello |best-practices:editors-and-tools#Editor IDE]. Si utiliza todo el framework, la configuración se [carga al iniciar la aplicación |application:bootstrapping#Configuración del contenedor DI], si no, lea [cómo cargar la configuración|bootstrap:]. <pre> -"application .[prism-token prism-atrule]":[application:configuration#Application]: "Aplicación .[prism-token prism-comment]"<br> -"constants .[prism-token prism-atrule]":[application:configuration#Constants]: "Define las constantes PHP .[prism-token prism-comment]"<br> -"database .[prism-token prism-atrule]":[database:configuration]: "Base de datos .[prism-token prism-comment]"<br> -"decorator .[prism-token prism-atrule]":[dependency-injection:configuration#Decorator]: "Decorador .[prism-token prism-comment]"<br> -"di .[prism-token prism-atrule]":[dependency-injection:configuration#DI]: "Contenedor DI .[prism-token prism-comment]"<br> -"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Extensions]: "Instalar extensiones DI adicionales .[prism-token prism-comment]"<br> -"forms .[prism-token prism-atrule]":[forms:configuration]: "Formularios .[prism-token prism-comment]"<br> -"http .[prism-token prism-atrule]":[http:configuration#HTTP Headers]: "Cabeceras HTTP .[prism-token prism-comment]"<br> -"includes .[prism-token prism-atrule]":[dependency-injection:configuration#Including files]: "Archivos incluidos .[prism-token prism-comment]"<br> -"latte .[prism-token prism-atrule]":[application:configuration#Latte]: "Latte .[prism-token prism-comment]"<br> -"mail .[prism-token prism-atrule]":[mail:#Configuring]: "Correo .[prism-token prism-comment]"<br> -"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#Parameters]: "Parámetros .[prism-token prism-comment]"<br> -"php .[prism-token prism-atrule]":[application:configuration#PHP]: "Opciones de configuración PHP .[prism-token prism-comment]"<br> -"enrutamiento .[prism-token prism-atrule]":[application:configuration#Routing]: "Enrutamiento .[prism-token prism-comment]"<br> -"search .[prism-token prism-atrule]":[dependency-injection:configuration#Search]: "Registro automático de servicios .[prism-token prism-comment]"<br> -"security .[prism-token prism-atrule]":[security:configuration]: "Control de acceso .[prism-token prism-comment]"<br> -"services .[prism-token prism-atrule]":[dependency-injection:services]: "Servicios .[prism-token prism-comment]"<br> -"session .[prism-token prism-atrule]":[http:configuration#Session]: "Sesión .[prism-token prism-comment]"<br> -"tracy .[prism-token prism-atrule]":[tracy:configuring#Nette Framework]: "Depurador de Tracy .[prism-token prism-comment]" +"application .[prism-token prism-atrule]":[application:configuration#Application]: "Aplicación .[prism-token prism-comment]"<br> +"assets .[prism-token prism-atrule]":[assets:configuration]: "Assets .[prism-token prism-comment]"<br> +"constants .[prism-token prism-atrule]":[application:configuration#Constantes]: "Definición de constantes PHP .[prism-token prism-comment]"<br> +"database .[prism-token prism-atrule]":[database:configuration]: "Base de datos .[prism-token prism-comment]"<br> +"decorator .[prism-token prism-atrule]":[dependency-injection:configuration#Decorator]: "Decorador .[prism-token prism-comment]"<br> +"di .[prism-token prism-atrule]":[dependency-injection:configuration#DI]: "Contenedor DI .[prism-token prism-comment]"<br> +"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Extensiones]: "Instalación de extensiones DI adicionales .[prism-token prism-comment]"<br> +"forms .[prism-token prism-atrule]":[forms:configuration]: "Formularios .[prism-token prism-comment]"<br> +"http .[prism-token prism-atrule]":[http:configuration#Cabeceras HTTP]: "Cabeceras HTTP .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[dependency-injection:configuration#Inclusión de archivos]: "Inclusión de archivos .[prism-token prism-comment]"<br> +"latte .[prism-token prism-atrule]":[application:configuration#Plantillas Latte]: "Plantillas Latte .[prism-token prism-comment]"<br> +"mail .[prism-token prism-atrule]":[mail:#Configuración]: "Correos .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#Parámetros]: "Parámetros .[prism-token prism-comment]"<br> +"php .[prism-token prism-atrule]":[application:configuration#PHP]: "Configuración PHP .[prism-token prism-comment]"<br> +"routing .[prism-token prism-atrule]":[application:configuration#Enrutamiento]: "Enrutamiento .[prism-token prism-comment]"<br> +"search .[prism-token prism-atrule]":[dependency-injection:configuration#Search]: "Registro automático de servicios .[prism-token prism-comment]"<br> +"security .[prism-token prism-atrule]":[security:configuration]: "Permisos de acceso .[prism-token prism-comment]"<br> +"services .[prism-token prism-atrule]":[dependency-injection:services]: "Servicios .[prism-token prism-comment]"<br> +"session .[prism-token prism-atrule]":[http:configuration#Sesión]: "Sesión .[prism-token prism-comment]"<br> +"tracy .[prism-token prism-atrule]":[tracy:configuring#Nette Framework]: "Depurador Tracy .[prism-token prism-comment]" </pre> -Para escribir una cadena que contenga el carácter `%`, you must escape it by doubling it to `%%`. .[note] +.[note] +Para escribir una cadena que contenga el carácter `%`, debe escaparlo duplicándolo a `%%`. {{leftbar: @menu-topics}} diff --git a/nette/es/glossary.texy b/nette/es/glossary.texy index 01a7fa50f7..57eb51961d 100644 --- a/nette/es/glossary.texy +++ b/nette/es/glossary.texy @@ -2,154 +2,158 @@ Glosario de términos ******************** -AJAX .[#toc-ajax] ------------------ -Asynchronous JavaScript and XML - tecnología para la comunicación cliente-servidor a través del protocolo HTTP sin necesidad de recargar toda la página en cada petición. A pesar de las siglas, a menudo se utiliza el formato [JSON |#JSON] en lugar de XML. +AJAX +---- +Asynchronous JavaScript and XML - tecnología de intercambio de información entre el cliente y el servidor a través del protocolo HTTP sin necesidad de recargar toda la página con cada solicitud. Aunque el nombre pueda sugerir que solo envía datos en formato XML, también se utiliza comúnmente el formato [#JSON]. -Acción del presentador .[#toc-presenter-action] ------------------------------------------------ -Parte lógica del [presentador |#presenter] que realiza una acción, como mostrar una página de producto, dar de baja a un usuario, etc. Un presentador puede tener más acciones. +Acción del presentador +---------------------- +Parte lógica del presenter que realiza una acción. Por ejemplo, muestra la página de un producto, cierra la sesión del usuario, etc. Un presenter puede tener múltiples acciones. BOM --- -La llamada *máscara de orden de bytes* es un primer carácter especial de un archivo e indica el orden de bytes en la codificación. Algunos editores la incluyen automáticamente, es prácticamente invisible, pero causa problemas con las cabeceras y el envío de salida desde dentro de PHP. Puede usar [Code Checker |code-checker:] para su eliminación masiva. +El llamado *byte order mark* es un carácter especial al principio de un archivo que se utiliza como indicador del orden de los bytes en la codificación. Algunos editores lo insertan en los archivos. Es prácticamente invisible, pero causa problemas con el envío de la salida y las cabeceras desde PHP. Para eliminarlo masivamente, puede usar [Code Checker|code-checker:]. -Controlador .[#toc-controller] ------------------------------- -El controlador procesa las peticiones del usuario y en base a ellas llama a la lógica particular de la aplicación (ej. [modelo |#model]), luego llama a [la vista |#view] para la representación de los datos. Una analogía de los controladores son [los presentadores |#presenter] en Nette Framework. +Controlador +----------- +Controlador que procesa las solicitudes del usuario y, en base a ellas, llama a la lógica de aplicación apropiada (es decir, el [#Modelo]) y luego solicita a la [#Vista] que renderice los datos. El equivalente a los controladores en Nette Framework son los [presenters |#Presenter]. -Cross-Site Scripting (XSS) .[#toc-cross-site-scripting-xss] ------------------------------------------------------------ -Cross-Site Scripting es un método de perturbación de sitios que utiliza entradas no descifradas. Un atacante puede inyectar su propio código HTML o JavaScript y cambiar el aspecto de la página o incluso recopilar información sensible sobre los usuarios. La protección contra el XSS es sencilla: escape coherente y correcto de todas las cadenas y entradas. +Cross-Site Scripting (XSS) +-------------------------- +Cross-Site Scripting es un método de vulneración de sitios web que abusa de salidas no tratadas. El atacante puede entonces inyectar su propio código en la página y así modificarla o incluso obtener datos sensibles sobre los visitantes. La única forma de defenderse contra XSS es tratando de manera consistente y correcta todas las cadenas. -Nette Framework viene con una nueva tecnología de [Context-Aware Escaping |latte:safety-first#context-aware-escaping], que le librará de los riesgos de Cross-Site Scripting para siempre. Escapa todas las entradas automáticamente basándose en un contexto dado, por lo que es imposible que un programador olvide algo accidentalmente. +Nette Framework introduce una tecnología revolucionaria [Context-Aware Escaping |latte:safety-first#Escape sensible al contexto], que le librará para siempre del riesgo de Cross-Site Scripting. Trata automáticamente todas las salidas, por lo que no puede ocurrir que el codificador olvide algo. -Falsificación de petición en sitios cruzados (CSRF) .[#toc-cross-site-request-forgery-csrf] -------------------------------------------------------------------------------------------- -Un ataque Cross-Site Request Forgery consiste en que el atacante atrae a la víctima para que visite una página que ejecuta silenciosamente una petición en el navegador de la víctima al servidor donde la víctima está conectada en ese momento, y el servidor cree que la petición ha sido realizada por la víctima a voluntad. El servidor realiza una determinada acción bajo la identidad de la víctima pero sin que ésta se dé cuenta. Puede ser cambiar o borrar datos, enviar un mensaje, etc. +Cross-Site Request Forgery (CSRF) +--------------------------------- +El ataque Cross-Site Request Forgery consiste en que el atacante atrae a la víctima a una página que, discretamente en el navegador de la víctima, realiza una solicitud al servidor en el que la víctima ha iniciado sesión, y el servidor cree que la solicitud fue realizada voluntariamente por la víctima. Así, bajo la identidad de la víctima, realiza una determinada acción sin que ésta lo sepa. Puede tratarse de un cambio o eliminación de datos, envío de un mensaje, etc. + +Nette Framework **protege automáticamente los formularios y señales en los presenters** contra este tipo de ataque. Lo hace impidiendo que se envíen o se invoquen desde otro dominio. -Nette Framework **protege automáticamente los formularios y señales de los presentadores** de este tipo de ataques. Esto se hace impidiendo que sean enviados o llamados desde otro dominio. +Inyección de Dependencia +------------------------ +La Inyección de Dependencia (DI) es un patrón de diseño que indica cómo separar la creación de objetos de sus dependencias. Es decir, que la clase no es responsable de crear o inicializar sus dependencias, sino que estas dependencias le son proporcionadas por código externo (que también puede ser un [Contenedor DI |#Contenedor de Inyección de Dependencia]). La ventaja es que permite una mayor flexibilidad del código, una mejor comprensión y pruebas más fáciles de la aplicación, ya que las dependencias son fácilmente reemplazables y están aisladas de otras partes del código. Más en el capítulo [¿Qué es la Inyección de Dependencia? |dependency-injection:introduction] -Inyección de dependencia .[#toc-dependency-injection] ------------------------------------------------------ -La inyección de dependencias (DI) es un patrón de diseño que indica cómo separar la creación de objetos de sus dependencias. Es decir, una clase no es responsable de crear o inicializar sus dependencias, sino que éstas son proporcionadas por código externo (que puede incluir un [contenedor DI |#Dependency Injection container]). La ventaja es que permite una mayor flexibilidad del código, una mejor legibilidad y una comprobación más sencilla de la aplicación, ya que las dependencias son fácilmente reemplazables y están aisladas de otras partes del código. Para más información, consulte [¿Qué es la inyección de dependencias? |dependency-injection:introduction] +Contenedor de Inyección de Dependencia +-------------------------------------- +Un Contenedor de Inyección de Dependencia (también Contenedor DI o Contenedor IoC) es una herramienta que se encarga de crear y gestionar las dependencias en una aplicación (o [servicios |#Servicio]). El contenedor suele tener una configuración que define qué clases dependen de otras clases, qué implementaciones concretas de dependencias se deben usar y cómo se deben crear estas dependencias. Luego, el contenedor crea estos objetos y los proporciona a las clases que los necesitan. Más en el capítulo [¿Qué es un Contenedor DI? |dependency-injection:container] -Contenedor de inyección de dependencias .[#toc-dependency-injection-container] ------------------------------------------------------------------------------- -Un contenedor de Inyección de Dependencias (también contenedor DI o contenedor IoC) es una herramienta que se encarga de la creación y gestión de dependencias en una aplicación (o [servicios |#service]). Un contenedor suele tener una configuración que define qué clases dependen de otras clases, qué implementaciones específicas de dependencias utilizar y cómo crear esas dependencias. A continuación, el contenedor crea estos objetos y los proporciona a las clases que los necesitan. Para obtener más información, consulte [¿Qué es un contenedor DI? |dependency-injection:container] +Escape / Escapado +----------------- +El escapado es la conversión de caracteres que tienen un significado especial en un contexto dado a otras secuencias correspondientes. Ejemplo: queremos escribir comillas en una cadena delimitada por comillas. Dado que las comillas tienen un significado especial en el contexto de la cadena y su simple escritura se entendería como el final de la cadena, es necesario escribirlas con otra secuencia correspondiente. Cuál exactamente lo determinan las reglas del contexto. -Escapando de .[#toc-escaping] ------------------------------ -El escape es la conversión de caracteres con un significado especial en un contexto determinado en otras secuencias equivalentes. Ejemplo: Queremos escribir comillas en una cadena entrecomillada. Como las comillas tienen un significado especial en el contexto de la cadena entrecomillada, es necesario utilizar otra secuencia equivalente. La secuencia concreta viene determinada por las reglas del contexto (por ejemplo, `\"` en la cadena entrecomillada de PHP, `"` en los atributos HTML, etc.). +Filtro (antes helper) +--------------------- +En las plantillas, el término [filtro |latte:syntax#Filtros] generalmente se refiere a una función que ayuda a modificar o reformatear los datos a su forma final. Las plantillas disponen de varios [filtros estándar |latte:filters]. -Filtro (antes Helper) .[#toc-filter-formerly-helper] ----------------------------------------------------- -Función de filtro. En las plantillas, el [filtro |latte:syntax#filters] es una función que ayuda a alterar o formatear los datos de salida. Las plantillas tienen varios [filtros estándar |latte:filters] predefinidos. +Invalidación +------------ +Notificación a un [#snippet] para que se vuelva a dibujar. En otro sentido, también la eliminación del contenido de la caché. -Invalidación .[#toc-invalidation] ---------------------------------- -Aviso de un [fragmento |#snippet] para volver a renderizar. En otro contexto también borrado de una caché. +JSON +---- +Formato para el intercambio de datos basado en la sintaxis de JavaScript (es un subconjunto de ella). La especificación exacta se puede encontrar en la página www.json.org. -JSON .[#toc-json] ------------------ -Formato de intercambio de datos basado en la sintaxis de JavaScript (es su subconjunto). La especificación exacta puede encontrarse en www.json.org. +Componente +---------- +Parte reutilizable de una aplicación. Puede ser una parte visual de la página, como describe el capítulo [Escribiendo componentes |application:components], o el término componente también se refiere a la clase [Component |component-model:] (tal componente no tiene por qué ser visual). -Componente .[#toc-component] ----------------------------- -Parte reutilizable de una aplicación. Puede ser una parte visual de una página, como se describe en el capítulo [application:components], o el término también puede referirse a la clase [Component |component-model:] (dicho componente no tiene por qué ser visual). +Caracteres de control +--------------------- +Los caracteres de control son caracteres invisibles que pueden aparecer en el texto y, eventualmente, causar problemas. Para eliminarlos masivamente de los archivos, puede usar [Code Checker|code-checker:] y para eliminarlos de una variable, la función [Strings::normalize() |utils:strings#normalize]. -Caracteres de control .[#toc-control-characters] ------------------------------------------------- -Los caracteres de control son caracteres invisibles que pueden aparecer en un texto y causar problemas. Para su eliminación masiva de archivos, puede usar [Code Checker |code-checker:], para su eliminación de una variable use la función [Strings::normalize() |utils:strings#normalize]. +Eventos +------- +Un evento es una situación esperada en un objeto, que cuando ocurre, se llaman los llamados handlers, es decir, callbacks que reaccionan al evento ("ejemplo":https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38). Un evento puede ser, por ejemplo, el envío de un formulario, el inicio de sesión de un usuario, etc. Los eventos son, por tanto, una forma de *Inversion of Control*. -Eventos .[#toc-events] ----------------------- -Un evento es una situación esperada en el objeto, que cuando se produce, se llama a los llamados manejadores, es decir, callbacks que reaccionan al evento ("muestra":https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38). El evento puede ser, por ejemplo, el envío de un formulario, el inicio de sesión de un usuario, etc. Los eventos son, por tanto, una forma de *Inversión de Control*. +Por ejemplo, el inicio de sesión de un usuario ocurre en el método `Nette\Security\User::login()`. El objeto `User` tiene una variable pública `$onLoggedIn`, que es un array al que cualquiera puede añadir un callback. En el momento en que el usuario inicia sesión, el método `login()` llama a todos los callbacks del array. El nombre de la variable en la forma `onXyz` es una convención utilizada en todo Nette. -Por ejemplo, un inicio de sesión de usuario se produce en el método `Nette\Security\User::login()`. El objeto `User` tiene una variable pública `$onLoggedIn`, que es una matriz a la que cualquiera puede añadir una llamada de retorno. En cuanto el usuario inicia sesión, el método `login()` llama a todas las retrollamadas de la matriz. El nombre de una variable de la forma `onXyz` es una convención utilizada en todo Nette. +Latte +----- +Uno de los [sistemas de plantillas |latte:] más avanzados. -Latte .[#toc-latte] -------------------- -Uno de los [sistemas de plantillas |latte:] más innovadores de la historia. +Modelo +------ +El modelo es la base de datos y, especialmente, funcional de toda la aplicación. Contiene toda la lógica de la aplicación (también se utiliza el término lógica de negocio). Es la **M** de **M**VC o MVP. Cualquier acción del usuario (inicio de sesión, añadir un producto al carrito, cambiar un valor en la base de datos) representa una acción del modelo. -Modelo .[#toc-model] --------------------- -El modelo representa la base de datos y funciones de toda la aplicación. Incluye toda la lógica de la aplicación (a veces también denominada "lógica de negocio"). Es el **M** de **M**VC o MPV. Cualquier acción del usuario (iniciar sesión, poner algo en la cesta, cambiar un valor de la base de datos) representa una acción del modelo. +El modelo gestiona su estado interno y ofrece una interfaz fija al exterior. Llamando a las funciones de esta interfaz podemos consultar o cambiar su estado. El modelo no sabe de la existencia de la [#Vista] o del [controller |#Controlador]. -El modelo gestiona su estado interno y proporciona una interfaz pública. Llamando a esta interfaz podemos tomar o cambiar su estado. El modelo no sabe de la existencia de una [vista |#view] o [controlador |#controller], el modelo es totalmente independiente de ellos. +Modelo-Vista-Controlador +------------------------ +Arquitectura de software que surgió de la necesidad de separar en las aplicaciones con interfaz gráfica el código de manejo ([#Controlador]) del código de la lógica de la aplicación ([#Modelo]) y del código que muestra los datos ([#Vista]). Esto hace que la aplicación sea más clara, facilita el desarrollo futuro y permite probar las partes individuales por separado. -Modelo-Vista-Controlador .[#toc-model-view-controller] ------------------------------------------------------- -Arquitectura de software, que surgió en el desarrollo de aplicaciones GUI para separar el código para el control de flujo ([controlador |#controller]) del código de la lógica de la aplicación ([modelo |#model]) y del código de representación de datos ([vista |#view]). De esta forma el código es más comprensible, facilita el desarrollo futuro y permite probar las partes por separado. +Modelo-Vista-Presentador +------------------------ +Arquitectura basada en [#Modelo-Vista-Controlador]. -Modelo-Vista-Presentador .[#toc-model-view-presenter] ------------------------------------------------------ -Arquitectura basada en [Modelo-Vista-Controlador |#Model-View-Controller]. +Módulo +------ +Un módulo representa una parte lógica de la aplicación. En una disposición típica, es un grupo de presenters y plantillas que abordan un área específica de funcionalidad. Los módulos se colocan en [directorios separados |application:directory-structure#Presenters y plantillas], como `Front/`, `Admin/` o `Shop/`. -Módulo .[#toc-module] ---------------------- -[Módulo |application:modules] en Nette Framework representa una colección de presentadores y plantillas, eventualmente también componentes y modelos, que sirven datos a un presentador. Así que es cierta parte lógica de una aplicación. +Por ejemplo, una tienda online se divide en: +- Frontend (`Shop/`) para ver productos y comprar +- Sección de clientes (`Customer/`) para gestionar pedidos +- Administración (`Admin/`) para el operador -Por ejemplo, una e-shop puede tener tres módulos: -1) Catálogo de productos con cesta. -2) Administración para el cliente. -3) Administración para el comerciante. +Técnicamente, son directorios normales, pero gracias a una división clara ayudan a escalar la aplicación. El presenter `Admin:Product:List` estará físicamente ubicado, por ejemplo, en el directorio `app/Presentation/Admin/Product/List/` (ver [mapeo de presenters |application:directory-structure#Mapeo de presenters]). -Espacio de nombres .[#toc-namespace] ------------------------------------- -Namespace es una característica del lenguaje PHP desde su versión 5.3 y de algunos otros lenguajes de programación también. Ayuda a evitar colisiones de nombres (por ejemplo, dos clases con el mismo nombre) cuando se utilizan diferentes bibliotecas juntas. Consulte [la documentación de |https://www.php.net/manual/en/language.namespaces.rationale.php] PHP para más detalles. +Namespace / Espacio de nombres +------------------------------ +Espacio de nombres, parte del lenguaje PHP desde la versión 5.3 y algunos otros lenguajes de programación, que permite usar clases que tienen el mismo nombre en diferentes librerías sin que se produzca una colisión de nombres. Ver la [documentación de PHP |https://www.php.net/manual/en/language.namespaces.rationale.php]. -Presentador .[#toc-presenter] ------------------------------ -Presentador es un objeto, que toma la [petición |api:Nette\Application\Request] tal y como la traduce el enrutador desde la petición HTTP y genera una [respuesta |api:Nette\Application\Response]. La respuesta puede ser una página HTML, una imagen, un documento XML, un archivo, JSON, una redirección o lo que se te ocurra. +Presenter +--------- +Un presenter es un objeto que toma una [petición |api:Nette\Application\Request] traducida por el router a partir de una petición HTTP y genera una [respuesta |api:Nette\Application\Response]. La respuesta puede ser una página HTML, una imagen, un documento XML, un archivo en disco, JSON, una redirección o cualquier cosa que se le ocurra. -Por presentador suele entenderse un descendiente de la clase [api:Nette\Application\UI\Presenter]. Por peticiones ejecuta las [acciones |application:presenters#life-cycle-of-presenter] apropiadas y renderiza las plantillas. +Normalmente, el término presenter se refiere a un descendiente de la clase [api:Nette\Application\UI\Presenter]. Según las peticiones entrantes, ejecuta las [acciones |application:presenters#Ciclo de vida del presenter] correspondientes y renderiza las plantillas. -Enrutador .[#toc-router] ------------------------- -Traductor bidireccional entre la solicitud HTTP / URL y la acción del presentador. Bidireccional significa que no sólo es posible derivar una acción [del |#presenter action] presentador a partir de la solicitud HTTP, sino también generar la URL adecuada para una acción. Ver más en el capítulo sobre [enrutamiento URL |application:routing]. +Router / Enrutador +------------------ +Traductor bidireccional entre una petición HTTP / URL y una acción del presenter. Bidireccional significa que a partir de una petición HTTP se puede derivar la [acción del presenter |#Acción del presentador], pero también a la inversa, generar la URL correspondiente para una acción. Más en el capítulo sobre [enrutamiento de URL |application:routing]. -Cookie SameSite .[#toc-samesite-cookie] ---------------------------------------- -Las cookies SameSite proporcionan un mecanismo para reconocer qué condujo a la carga de la página. Puede tener tres valores: `Lax`, `Strict` y `None` (este último requiere HTTPS). Si la solicitud de la página procede directamente del sitio o el usuario abre la página escribiendo directamente en la barra de direcciones o haciendo clic en un marcador, el navegador envía todas las cookies al servidor (es decir, con los indicadores `Lax`, `Strict` y `None`). Si el usuario hace clic en el sitio a través de un enlace desde otro sitio, las cookies con las banderas `Lax` y `None` se pasan al servidor. Si la solicitud se realiza por otros medios, como el envío de un formulario POST desde otro sitio, la carga dentro de un iframe, el uso de JavaScript, etc., sólo se envían cookies con la bandera `None`. +Cookie SameSite +--------------- +Las cookies SameSite proporcionan un mecanismo para reconocer qué llevó a la carga de la página. Puede tener tres valores: `Lax`, `Strict` y `None` (este último requiere HTTPS). Si la solicitud de la página proviene directamente del sitio web o el usuario abre la página introduciéndola directamente en la barra de direcciones o haciendo clic en un marcador, el navegador enviará todas las cookies al servidor (es decir, con los indicadores `Lax`, `Strict` y `None`). Si el usuario llega al sitio web haciendo clic en un enlace desde otro sitio web, se pasarán al servidor las cookies con los indicadores `Lax` y `None`. Si la solicitud se origina de otra manera, como el envío de un formulario POST desde otro sitio web, la carga dentro de un iframe, mediante JavaScript, etc., solo se enviarán las cookies con el indicador `None`. -Servicio .[#toc-service] ------------------------- -En el contexto de la inyección de dependencias, un servicio se refiere a un objeto creado y gestionado por un contenedor DI. Un servicio puede ser fácilmente reemplazado por otra implementación, por ejemplo para propósitos de prueba o para cambiar el comportamiento de una aplicación, sin tener que modificar el código que utiliza el servicio. +Servicio +-------- +En el contexto de la Inyección de Dependencia, un servicio se refiere a un objeto que es creado y gestionado por el contenedor DI. Un servicio puede ser fácilmente reemplazado por otra implementación, por ejemplo, con fines de prueba o para cambiar el comportamiento de la aplicación, sin necesidad de modificar el código que utiliza el servicio. + + +Snippet +------- +Fragmento, parte de una página que se puede volver a dibujar de forma independiente durante una solicitud AJAX. -Fragmento .[#toc-snippet] -------------------------- -Fragmento de una página que se puede volver a renderizar por separado durante una solicitud [AJAX |#AJAX]. +Vista +----- +La vista es la capa de la aplicación que se encarga de mostrar el resultado de la solicitud. Normalmente utiliza un sistema de plantillas y sabe cómo mostrar cada componente o el resultado obtenido del modelo. -Ver .[#toc-view] ----------------- -La vista es una capa de la aplicación, que es responsable de renderizar los resultados de la petición. Usualmente utiliza un sistema de plantillas y sabe, como renderizar sus componentes o resultados tomados del modelo. diff --git a/nette/es/installation.texy b/nette/es/installation.texy index 4690a5a0f3..2e4ac08ced 100644 --- a/nette/es/installation.texy +++ b/nette/es/installation.texy @@ -2,66 +2,66 @@ Instalación de Nette ******************** .[perex] -¿Quiere aprovechar las ventajas de Nette en su proyecto actual o está pensando en crear un nuevo proyecto basado en Nette? Esta guía le guiará paso a paso por el proceso de instalación. +¿Quiere aprovechar las ventajas de Nette en su proyecto existente o va a crear un nuevo proyecto basado en Nette? Esta guía le guiará a través de la instalación paso a paso. -Cómo añadir Nette a su proyecto .[#toc-how-to-add-nette-to-your-project] ------------------------------------------------------------------------- +Cómo añadir Nette a tu proyecto +------------------------------- -Nette ofrece una colección de paquetes (librerías) útiles y sofisticados para PHP. Para incorporarlos a su proyecto, siga estos pasos: +Nette ofrece una colección de paquetes (librerías) útiles y avanzados para PHP. Para incorporarlos a su proyecto, siga estos pasos: -1) **Instalar [Composer |best-practices:composer]:** Esta herramienta es esencial para facilitar la instalación, actualización y gestión de las librerías necesarias para tu proyecto. +1) **Prepare [Composer|best-practices:composer]:** Esta herramienta es esencial para instalar, actualizar y gestionar fácilmente las librerías necesarias para su proyecto. -2) **Elige un [paquete |www:packages]:** Supongamos que necesitas navegar por el sistema de archivos, algo que [el Finder |utils:finder] del paquete `nette/utils` hace de forma excelente. Puedes encontrar el nombre del paquete en la columna derecha de su documentación. +2) **Elija un [paquete|www:packages]:** Supongamos que necesita navegar por el sistema de archivos, lo cual hace perfectamente [Finder|utils:finder] del paquete `nette/utils`. Puede ver el nombre del paquete en la columna derecha de su documentación. -3) **Instala el paquete:** Ejecuta este comando en el directorio raíz de tu proyecto: +3) **Instale el paquete:** Ejecute este comando en el directorio raíz de su proyecto: ```shell composer require nette/utils ``` -¿Prefieres una interfaz gráfica? Consulta la [guía |https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] de instalación de paquetes en el entorno PhpStrom. +¿Prefiere una interfaz gráfica? Consulte la [guía|https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] para instalar paquetes en el entorno PhpStorm. -Cómo iniciar un nuevo proyecto con Nette .[#toc-how-to-start-a-new-project-with-nette] --------------------------------------------------------------------------------------- +Cómo crear un nuevo proyecto con Nette +-------------------------------------- -Si desea crear un proyecto completamente nuevo en la plataforma Nette, le recomendamos que utilice el esqueleto preestablecido de [Proyecto Web |https://github.com/nette/web-project]: +Si desea crear un proyecto completamente nuevo en la plataforma Nette, le recomendamos utilizar el esqueleto preconfigurado [Web Project|https://github.com/nette/web-project]: -1) **Configurar [Composer |best-practices:composer].** +1) **Prepare [Composer|best-practices:composer].** 2) **Abra la línea de comandos** y navegue hasta el directorio raíz de su servidor web, por ejemplo, `/etc/var/www`, `C:/xampp/htdocs`, `/Library/WebServer/Documents`. -3) **Crea el proyecto** utilizando este comando: +3) **Cree el proyecto** usando este comando: ```shell -composer create-project nette/web-project PROJECT_NAME +composer create-project nette/web-project NOMBRE_PROYECTO ``` -4) **¿No utilizas Composer?** Sólo tienes que descargar el [Proyecto Web en formato ZIP |https://github.com/nette/web-project/archive/preloaded.zip] y extraerlo. ¡Pero confía en nosotros, Composer vale la pena! +4) **¿No usa Composer?** Simplemente descargue [Web Project en formato ZIP|https://github.com/nette/web-project/archive/preloaded.zip] y descomprímalo. ¡Pero créanos, Composer vale la pena! -5) **Establecer permisos:** En sistemas macOS o Linux, establecer [permisos de escritura |nette:troubleshooting#Setting directory permissions] para los directorios. +5) **Configuración de permisos:** En sistemas macOS o Linux, configure los [permisos de escritura |nette:troubleshooting#Configuración de permisos de directorio] en los directorios `temp/` y `log/`. -6) **Abre el proyecto en un navegador:** Introduce la URL `http://localhost/PROJECT_NAME/www/`. Verás la página de inicio del esqueleto: +6) **Abrir el proyecto en el navegador:** Introduzca la URL `http://localhost/NOMBRE_PROYECTO/www/` y verá la página de inicio del esqueleto: -[* qs-welcome.webp .{url: http://localhost/PROJECT_NAME/www/} *] +[* qs-welcome.webp .{url: http://localhost/NOMBRE_PROYECTO/www/} *] -¡Enhorabuena! Su sitio web ya está listo para el desarrollo. Siéntase libre de eliminar la plantilla de bienvenida y empezar a construir su aplicación. +¡Felicidades! Su sitio web está ahora listo para el desarrollo. Puede eliminar la plantilla de bienvenida y empezar a crear su aplicación. -Una de las ventajas de Nette es que el proyecto funciona inmediatamente sin necesidad de configuración. Sin embargo, si se encuentra con algún problema, considere consultar las [soluciones a problemas comunes |nette:troubleshooting#nette-is-not-working-white-page-is-displayed]. +Una de las ventajas de Nette es que el proyecto funciona inmediatamente sin necesidad de configuración. Sin embargo, si encuentra problemas, intente consultar las [soluciones a problemas comunes |nette:troubleshooting#Nette no funciona se muestra una página en blanco]. .[note] -Si está empezando con Nette, le recomendamos que continúe con el [tutorial Cree su primera aplicación |quickstart:]. +Si está empezando con Nette, le recomendamos continuar con el [tutorial Escribiendo tu primera aplicación|quickstart:]. -Herramientas y recomendaciones .[#toc-tools-and-recommendations] ----------------------------------------------------------------- +Herramientas y recomendaciones +------------------------------ -Para trabajar eficazmente con Nette, recomendamos las siguientes herramientas: +Para trabajar eficientemente con Nette, recomendamos las siguientes herramientas: -- [IDE de alta calidad con plugins para |best-practices:editors-and-tools]Nette +- [Un buen IDE con plugins para Nette|best-practices:editors-and-tools] - Sistema de control de versiones Git -- [Compositor |best-practices:composer] +- [Composer|best-practices:composer] {{leftbar: www:@menu-common}} diff --git a/nette/es/introduction-to-object-oriented-programming.texy b/nette/es/introduction-to-object-oriented-programming.texy new file mode 100644 index 0000000000..36a9e4e034 --- /dev/null +++ b/nette/es/introduction-to-object-oriented-programming.texy @@ -0,0 +1,841 @@ +Introducción a la programación orientada a objetos +************************************************** + +.[perex] +El término "POO" se refiere a la programación orientada a objetos, que es una forma de organizar y estructurar el código. La POO nos permite ver un programa como un conjunto de objetos que se comunican entre sí, en lugar de una secuencia de comandos y funciones. + +En POO, un "objeto" es una unidad que contiene datos y funciones que trabajan con esos datos. Los objetos se crean a partir de "clases", que podemos entender como planos o plantillas para los objetos. Cuando tenemos una clase, podemos crear su "instancia", que es un objeto concreto creado a partir de esa clase. + +Veamos cómo podemos crear una clase simple en PHP. Al definir una clase, usamos la palabra clave "class", seguida del nombre de la clase y luego llaves que encierran las funciones (llamadas "métodos") y las variables de la clase (llamadas "propiedades"): + +```php +class Coche +{ + function tocarBocina() + { + echo '¡Bip bip!'; + } +} +``` + +En este ejemplo, hemos creado una clase llamada `Coche` con una función (o "método") llamada `tocarBocina`. + +Cada clase debe abordar solo una tarea principal. Si una clase hace demasiadas cosas, puede ser apropiado dividirla en clases más pequeñas y especializadas. + +Normalmente, guardamos las clases en archivos separados para mantener el código organizado y fácil de navegar. El nombre del archivo debe coincidir con el nombre de la clase, por lo que para la clase `Coche`, el nombre del archivo sería `Coche.php`. + +Al nombrar las clases, es bueno seguir la convención "PascalCase", lo que significa que cada palabra en el nombre comienza con una letra mayúscula y no hay guiones bajos ni otros separadores entre ellas. Los métodos y propiedades usan la convención "camelCase", lo que significa que comienzan con una letra minúscula. + +Algunos métodos en PHP tienen tareas especiales y se marcan con el prefijo `__` (dos guiones bajos). Uno de los métodos especiales más importantes es el "constructor", que se marca como `__construct`. El constructor es un método que se llama automáticamente cuando se crea una nueva instancia de la clase. + +A menudo usamos el constructor para establecer el estado inicial del objeto. Por ejemplo, al crear un objeto que representa a una persona, puedes usar el constructor para establecer su edad, nombre u otras propiedades. + +Veamos cómo usar un constructor en PHP: + +```php +class Persona +{ + private $edad; + + function __construct($edad) + { + $this->edad = $edad; + } + + function cuantosAnosTiene() + { + return $this->edad; + } +} + +$persona = new Persona(25); +echo $persona->cuantosAnosTiene(); // Imprime: 25 +``` + +En este ejemplo, la clase `Persona` tiene una propiedad (variable) `$edad` y además un constructor que establece esta propiedad. El método `cuantosAnosTiene()` permite acceder a la edad de la persona. + +La pseudovariable `$this` se usa dentro de la clase para acceder a las propiedades y métodos del objeto. + +La palabra clave `new` se usa para crear una nueva instancia de la clase. En el ejemplo anterior, creamos una nueva persona con 25 años. + +También puedes establecer valores predeterminados para los parámetros del constructor si no se especifican al crear el objeto. Por ejemplo: + +```php +class Persona +{ + private $edad; + + function __construct($edad = 20) + { + $this->edad = $edad; + } + + function cuantosAnosTiene() + { + return $this->edad; + } +} + +$persona = new Persona; // si no pasamos ningún argumento, se pueden omitir los paréntesis +echo $persona->cuantosAnosTiene(); // Imprime: 20 +``` + +En este ejemplo, si no especificas la edad al crear el objeto `Persona`, se usará el valor predeterminado de 20. + +Es conveniente que la definición de la propiedad con su inicialización a través del constructor se pueda acortar y simplificar de esta manera: + +```php +class Persona +{ + function __construct( + private $edad = 20, + ) { + } +} +``` + +Para completar, además de los constructores, los objetos también pueden tener destructores (método `__destruct`), que se llaman antes de que el objeto sea liberado de la memoria. + + +Espacios de nombres +------------------- + +Los espacios de nombres (o "namespaces" en inglés) nos permiten organizar y agrupar clases, funciones y constantes relacionadas, evitando al mismo tiempo conflictos de nombres. Puedes imaginarlos como carpetas en tu computadora, donde cada carpeta contiene archivos que pertenecen a un proyecto o tema específico. + +Los espacios de nombres son especialmente útiles en proyectos más grandes o cuando usas librerías de terceros, donde podrían surgir conflictos en los nombres de las clases. + +Imagina que tienes una clase llamada `Coche` en tu proyecto y quieres colocarla en un espacio de nombres llamado `Transporte`. Lo harías así: + +```php +namespace Transporte; + +class Coche +{ + function tocarBocina() + { + echo '¡Bip bip!'; + } +} +``` + +Si quieres usar la clase `Coche` en otro archivo, debes especificar de qué espacio de nombres proviene la clase: + +```php +$coche = new Transporte\Coche; +``` + +Para simplificar, puedes indicar al principio del archivo qué clase del espacio de nombres dado quieres usar, lo que permite crear instancias sin necesidad de especificar la ruta completa: + +```php +use Transporte\Coche; + +$coche = new Coche; +``` + + +Herencia +-------- + +La herencia es una herramienta de la programación orientada a objetos que permite crear nuevas clases basadas en clases ya existentes, heredando sus propiedades y métodos, y extendiéndolos o redefiniéndolos según sea necesario. La herencia permite asegurar la reutilización del código y la jerarquía de clases. + +En pocas palabras, si tenemos una clase y quisiéramos crear otra derivada de ella, pero con algunos cambios, podemos "heredar" la nueva clase de la clase original. + +En PHP, la herencia se realiza usando la palabra clave `extends`. + +Nuestra clase `Persona` almacena información sobre la edad. Podemos tener otra clase `Estudiante` que extiende `Persona` y agrega información sobre el campo de estudio. + +Veamos un ejemplo: + +```php +class Persona +{ + private $edad; + + function __construct($edad) + { + $this->edad = $edad; + } + + function imprimirInformacion() + { + echo "Edad: {$this->edad} años\n"; + } +} + +class Estudiante extends Persona +{ + private $especialidad; + + function __construct($edad, $especialidad) + { + parent::__construct($edad); + $this->especialidad = $especialidad; + } + + function imprimirInformacion() + { + parent::imprimirInformacion(); + echo "Campo de estudio: {$this->especialidad} \n"; + } +} + +$estudiante = new Estudiante(20, 'Informática'); +$estudiante->imprimirInformacion(); +``` + +¿Cómo funciona este código? + +- Usamos la palabra clave `extends` para extender la clase `Persona`, lo que significa que la clase `Estudiante` hereda todos los métodos y propiedades de `Persona`. + +- La palabra clave `parent::` nos permite llamar a métodos de la clase padre. En este caso, llamamos al constructor de la clase `Persona` antes de agregar nuestra propia funcionalidad a la clase `Estudiante`. Y de manera similar, también al método `imprimirInformacion()` del padre antes de imprimir la información del estudiante. + +La herencia está destinada a situaciones en las que existe una relación "es un" entre clases. Por ejemplo, un `Estudiante` es una `Persona`. Un gato es un animal. Nos da la posibilidad, en casos donde el código espera un objeto (por ejemplo, "Persona"), de usar en su lugar un objeto heredado (por ejemplo, "Estudiante"). + +Es importante darse cuenta de que el propósito principal de la herencia **no es** evitar la duplicación de código. Por el contrario, el uso incorrecto de la herencia puede llevar a un código complejo y difícil de mantener. Si no existe una relación "es un" entre las clases, deberíamos considerar la composición en lugar de la herencia. + +Observa que los métodos `imprimirInformacion()` en las clases `Persona` y `Estudiante` imprimen información ligeramente diferente. Y podemos agregar otras clases (por ejemplo, `Empleado`) que proporcionarán otras implementaciones de este método. La capacidad de los objetos de diferentes clases para responder al mismo método de diferentes maneras se llama polimorfismo: + +```php +$personas = [ + new Persona(30), + new Estudiante(20, 'Informática'), + new Empleado(45, 'Director'), // Suponiendo que existe una clase Empleado similar +]; + +foreach ($personas as $persona) { + $persona->imprimirInformacion(); +} +``` + + +Composición +----------- + +La composición es una técnica en la que, en lugar de heredar propiedades y métodos de otra clase, simplemente usamos su instancia en nuestra clase. Esto nos permite combinar funcionalidades y propiedades de múltiples clases sin necesidad de crear estructuras de herencia complejas. + +Veamos un ejemplo. Tenemos una clase `Motor` y una clase `Coche`. En lugar de decir "Coche es un Motor", decimos "Coche tiene un Motor", que es una relación típica de composición. + +```php +class Motor +{ + function encender() + { + echo 'Motor en marcha.'; + } +} + +class Coche +{ + private $motor; + + function __construct() + { + $this->motor = new Motor; + } + + function arrancar() + { + $this->motor->encender(); + echo '¡El coche está listo para conducir!'; + } +} + +$coche = new Coche; +$coche->arrancar(); +``` + +Aquí, `Coche` no tiene todas las propiedades y métodos de `Motor`, pero tiene acceso a él a través de la propiedad `$motor`. + +La ventaja de la composición es una mayor flexibilidad en el diseño y una mejor capacidad de modificación en el futuro. + + +Visibilidad +----------- + +En PHP, puedes definir la "visibilidad" para propiedades, métodos y constantes de una clase. La visibilidad determina desde dónde puedes acceder a estos elementos. + +1. **Public:** Si un elemento está marcado como `public`, significa que puedes acceder a él desde cualquier lugar, incluso fuera de la clase. + +2. **Protected:** Un elemento marcado como `protected` solo es accesible dentro de la clase dada y todos sus descendientes (clases que heredan de esta clase). + +3. **Private:** Si un elemento es `private`, solo puedes acceder a él desde dentro de la clase en la que fue definido. + +Si no especificas la visibilidad, PHP la establecerá automáticamente en `public`. + +Veamos un código de ejemplo: + +```php +class EjemploVisibilidad +{ + public $propiedadPublica = 'Pública'; + protected $propiedadProtegida = 'Protegida'; + private $propiedadPrivada = 'Privada'; + + public function imprimirPropiedades() + { + echo $this->propiedadPublica; // Funciona + echo $this->propiedadProtegida; // Funciona + echo $this->propiedadPrivada; // Funciona + } +} + +$objeto = new EjemploVisibilidad; +$objeto->imprimirPropiedades(); +echo $objeto->propiedadPublica; // Funciona +// echo $objeto->propiedadProtegida; // Lanza un error +// echo $objeto->propiedadPrivada; // Lanza un error +``` + +Continuamos con la herencia de clases: + +```php +class ClaseHija extends EjemploVisibilidad +{ + public function imprimirPropiedades() + { + echo $this->propiedadPublica; // Funciona + echo $this->propiedadProtegida; // Funciona + // echo $this->propiedadPrivada; // Lanza un error + } +} +``` + +En este caso, el método `imprimirPropiedades()` en la clase `ClaseHija` puede acceder a las propiedades públicas y protegidas, pero no puede acceder a las propiedades privadas de la clase padre. + +Los datos y métodos deben estar lo más ocultos posible y ser accesibles solo a través de una interfaz definida. Esto te permitirá cambiar la implementación interna de la clase sin afectar al resto del código. + + +Palabra clave `final` +--------------------- + +En PHP, podemos usar la palabra clave `final` si queremos evitar que una clase, método o constante sea heredada o sobrescrita. Cuando marcamos una clase como `final`, no puede ser extendida. Cuando marcamos un método como `final`, no puede ser sobrescrito en una clase descendiente. + +Saber que una clase o método en particular no será modificado posteriormente nos permite realizar cambios más fácilmente sin tener que preocuparnos por posibles conflictos. Por ejemplo, podemos agregar un nuevo método sin temor a que alguno de sus descendientes ya tenga un método con el mismo nombre y se produzca una colisión. O podemos modificar los parámetros de un método, ya que nuevamente no hay riesgo de causar una inconsistencia con un método sobrescrito en un descendiente. + +```php +final class ClaseFinal +{ +} + +// El siguiente código provocará un error, porque no podemos heredar de una clase final. +class HijaClaseFinal extends ClaseFinal +{ +} +``` + +En este ejemplo, intentar heredar de la clase final `ClaseFinal` provocará un error. + + +Propiedades y métodos estáticos +------------------------------- + +Cuando hablamos de elementos "estáticos" de una clase en PHP, nos referimos a métodos y propiedades que pertenecen a la clase misma, y no a una instancia específica de esa clase. Esto significa que no necesitas crear una instancia de la clase para acceder a ellos. En su lugar, los llamas o accedes a ellos directamente a través del nombre de la clase. + +Ten en cuenta que, dado que los elementos estáticos pertenecen a la clase y no a sus instancias, no puedes usar la pseudovariable `$this` dentro de métodos estáticos. + +El uso de propiedades estáticas conduce a [código confuso lleno de trampas|dependency-injection:global-state], por lo que nunca deberías usarlas y ni siquiera mostraremos un ejemplo de uso aquí. Por el contrario, los métodos estáticos son útiles. Ejemplo de uso: + +```php +class Calculadora +{ + public static function sumar($a, $b) + { + return $a + $b; + } + + public static function restar($a, $b) + { + return $a - $b; + } +} + +// Uso del método estático sin crear una instancia de la clase +echo Calculadora::sumar(5, 3); // Resultado: 8 +echo Calculadora::restar(5, 3); // Resultado: 2 +``` + +En este ejemplo, creamos una clase `Calculadora` con dos métodos estáticos. Podemos llamar a estos métodos directamente sin crear una instancia de la clase usando el operador `::`. Los métodos estáticos son especialmente útiles para operaciones que no dependen del estado de una instancia específica de la clase. + + +Constantes de clase +------------------- + +Dentro de las clases, tenemos la opción de definir constantes. Las constantes son valores que nunca cambian durante la ejecución del programa. A diferencia de las variables, el valor de una constante permanece siempre igual. + +```php +class Coche +{ + public const NumeroRuedas = 4; + + public function mostrarNumeroRuedas(): int + { + echo self::NumeroRuedas; + } +} + +echo Coche::NumeroRuedas; // Salida: 4 +``` + +En este ejemplo, tenemos una clase `Coche` con la constante `NumeroRuedas`. Cuando queremos acceder a la constante dentro de la clase, podemos usar la palabra clave `self` en lugar del nombre de la clase. + + +Interfaces de objeto +-------------------- + +Las interfaces de objeto funcionan como "contratos" para las clases. Si una clase va a implementar una interfaz de objeto, debe contener todos los métodos que define esa interfaz. Es una excelente manera de asegurar que ciertas clases cumplan con el mismo "contrato" o estructura. + +En PHP, una interfaz se define con la palabra clave `interface`. Todos los métodos definidos en la interfaz son públicos (`public`). Cuando una clase implementa una interfaz, usa la palabra clave `implements`. + +```php +interface Animal +{ + function hacerSonido(); +} + +class Gato implements Animal +{ + public function hacerSonido() + { + echo 'Miau'; + } +} + +$gato = new Gato; +$gato->hacerSonido(); +``` + +Si una clase implementa una interfaz, pero no define todos los métodos esperados, PHP lanzará un error. + +Una clase puede implementar múltiples interfaces a la vez, lo cual es una diferencia con respecto a la herencia, donde una clase solo puede heredar de una clase: + +```php +interface Guardian +{ + function vigilarCasa(); +} + +class Perro implements Animal, Guardian +{ + public function hacerSonido() + { + echo 'Guau'; + } + + public function vigilarCasa() + { + echo 'El perro vigila atentamente la casa'; + } +} +``` + + +Clases abstractas +----------------- + +Las clases abstractas sirven como plantillas base para otras clases, pero no puedes crear instancias de ellas directamente. Contienen una combinación de métodos completos y métodos abstractos, que no tienen contenido definido. Las clases que heredan de clases abstractas deben proporcionar definiciones para todos los métodos abstractos del ancestro. + +Para definir una clase abstracta, usamos la palabra clave `abstract`. + +```php +abstract class ClaseAbstracta +{ + public function metodoComun() + { + echo 'Este es un método común'; + } + + abstract public function metodoAbstracto(); +} + +class Hija extends ClaseAbstracta +{ + public function metodoAbstracto() + { + echo 'Esta es la implementación del método abstracto'; + } +} + +$instancia = new Hija; +$instancia->metodoComun(); +$instancia->metodoAbstracto(); +``` + +En este ejemplo, tenemos una clase abstracta con un método común y un método abstracto. Luego tenemos una clase `Hija` que hereda de `ClaseAbstracta` y proporciona la implementación para el método abstracto. + +¿Cuál es la diferencia entre interfaces y clases abstractas? Las clases abstractas pueden contener tanto métodos abstractos como concretos, mientras que las interfaces solo definen qué métodos debe implementar una clase, pero no proporcionan ninguna implementación. Una clase solo puede heredar de una clase abstracta, pero puede implementar cualquier número de interfaces. + + +Comprobación de tipos +--------------------- + +En programación, es muy importante estar seguro de que los datos con los que trabajamos son del tipo correcto. En PHP, tenemos herramientas que nos aseguran esto. La verificación de si los datos tienen el tipo correcto se llama "comprobación de tipos" (type hinting). + +Tipos que podemos encontrar en PHP: + +1. **Tipos básicos**: Incluyen `int` (números enteros), `float` (números decimales), `bool` (valores booleanos), `string` (cadenas), `array` (arrays) y `null`. +2. **Clases**: Si queremos que el valor sea una instancia de una clase específica. +3. **Interfaces**: Define un conjunto de métodos que una clase debe implementar. Un valor que cumple con la interfaz debe tener estos métodos. +4. **Tipos mixtos (Union Types)**: Podemos especificar que una variable puede tener varios tipos permitidos. +5. **Void**: Este tipo especial indica que una función o método no devuelve ningún valor. + +Veamos cómo modificar el código para incluir tipos: + +```php +class Persona +{ + private int $edad; + + public function __construct(int $edad) + { + $this->edad = $edad; + } + + public function imprimirEdad(): void + { + echo "Esta persona tiene {$this->edad} años."; + } +} + +/** + * Función que recibe un objeto de la clase Persona e imprime la edad de la persona. + */ +function imprimirEdadPersona(Persona $persona): void +{ + $persona->imprimirEdad(); +} +``` + +De esta manera, hemos asegurado que nuestro código espera y trabaja con datos del tipo correcto, lo que nos ayuda a prevenir posibles errores. + +Algunos tipos no se pueden escribir directamente en PHP. En tal caso, se indican en un comentario phpDoc, que es un formato estándar para documentar código PHP que comienza con `/**` y termina con `*/`. Permite agregar descripciones de clases, métodos, etc. Y también indicar tipos complejos usando las llamadas anotaciones `@var`, `@param` y `@return`. Estos tipos son luego utilizados por herramientas de análisis estático de código, pero PHP mismo no los verifica. + +```php +class Lista +{ + /** @var array<Persona> la anotación dice que es un array de objetos Persona */ + private array $personas = []; + + public function agregarPersona(Persona $persona): void + { + $this->personas[] = $persona; + } +} +``` + + +Comparación e identidad +----------------------- + +En PHP, puedes comparar objetos de dos maneras: + +1. Comparación de valores `==`: Verifica si los objetos son de la misma clase y tienen los mismos valores en sus propiedades. +2. Identidad `===`: Verifica si se trata de la misma instancia de objeto. + +```php +class Coche +{ + public string $marca; + + public function __construct(string $marca) + { + $this->marca = $marca; + } +} + +$coche1 = new Coche('Skoda'); +$coche2 = new Coche('Skoda'); +$coche3 = $coche1; + +var_dump($coche1 == $coche2); // true, porque tienen el mismo valor +var_dump($coche1 === $coche2); // false, porque no son la misma instancia +var_dump($coche1 === $coche3); // true, porque $coche3 es la misma instancia que $coche1 +``` + + +Operador `instanceof` +--------------------- + +El operador `instanceof` permite determinar si un objeto dado es una instancia de una clase particular, un descendiente de esa clase, o si implementa una interfaz específica. + +Imaginemos que tenemos una clase `Persona` y otra clase `Estudiante`, que es descendiente de la clase `Persona`: + +```php +class Persona +{ + private int $edad; + + public function __construct(int $edad) + { + $this->edad = $edad; + } +} + +class Estudiante extends Persona +{ + private string $especialidad; + + public function __construct(int $edad, string $especialidad) + { + parent::__construct($edad); + $this->especialidad = $especialidad; + } +} + +$estudiante = new Estudiante(20, 'Informática'); + +// Verificar si $estudiante es una instancia de la clase Estudiante +var_dump($estudiante instanceof Estudiante); // Salida: bool(true) + +// Verificar si $estudiante es una instancia de la clase Persona (porque Estudiante es descendiente de Persona) +var_dump($estudiante instanceof Persona); // Salida: bool(true) +``` + +De las salidas se desprende que el objeto `$estudiante` se considera simultáneamente una instancia de ambas clases: `Estudiante` y `Persona`. + + +Interfaces Fluidas +------------------ + +La "interfaz fluida" (en inglés "Fluent Interface") es una técnica en POO que permite encadenar métodos juntos en una sola llamada. Esto a menudo simplifica y aclara el código. + +El elemento clave de una interfaz fluida es que cada método en la cadena devuelve una referencia al objeto actual. Logramos esto usando `return $this;` al final del método. Este estilo de programación a menudo se asocia con métodos llamados "setters", que establecen los valores de las propiedades del objeto. + +Mostremos cómo puede verse una interfaz fluida en el ejemplo del envío de correos electrónicos: + +```php +public function sendMessage() +{ + $email = new Email; // Suponiendo que existe una clase Email + $email->setFrom('sender@example.com') + ->setRecipient('admin@example.com') + ->setMessage('Hola, este es un mensaje.') + ->send(); +} +``` + +En este ejemplo, los métodos `setFrom()`, `setRecipient()` y `setMessage()` sirven para establecer los valores correspondientes (remitente, destinatario, contenido del mensaje). Después de establecer cada uno de estos valores, los métodos nos devuelven el objeto actual (`$email`), lo que nos permite encadenar otro método después. Finalmente, llamamos al método `send()`, que realmente envía el correo electrónico. + +Gracias a las interfaces fluidas, podemos escribir código que es intuitivo y fácil de leer. + + +Copia usando `clone` +-------------------- + +En PHP, podemos crear una copia de un objeto usando el operador `clone`. De esta manera, obtenemos una nueva instancia con contenido idéntico. + +Si necesitamos modificar algunas propiedades al copiar un objeto, podemos definir un método especial `__clone()` en la clase. Este método se llama automáticamente cuando se clona el objeto. + +```php +class Oveja +{ + public string $nombre; + + public function __construct(string $nombre) + { + $this->nombre = $nombre; + } + + public function __clone() + { + $this->nombre = 'Clon ' . $this->nombre; + } +} + +$original = new Oveja('Dolly'); +echo $original->nombre . "\n"; // Imprime: Dolly + +$clon = clone $original; +echo $clon->nombre . "\n"; // Imprime: Clon Dolly +``` + +En este ejemplo, tenemos una clase `Oveja` con una propiedad `$nombre`. Cuando clonamos una instancia de esta clase, el método `__clone()` se encarga de que el nombre de la oveja clonada obtenga el prefijo "Clon". + + +Traits +------ + +Los traits en PHP son una herramienta que permite compartir métodos, propiedades y constantes entre clases y evitar la duplicación de código. Puedes imaginarlos como un mecanismo de "copiar y pegar" (Ctrl-C y Ctrl-V), donde el contenido del trait se "inserta" en las clases. Esto te permite reutilizar código sin necesidad de crear jerarquías de clases complicadas. + +Veamos un ejemplo simple de cómo usar traits en PHP: + +```php +trait TocarBocina +{ + public function tocarBocina() + { + echo '¡Bip bip!'; + } +} + +class Coche +{ + use TocarBocina; +} + +class Camion +{ + use TocarBocina; +} + +$coche = new Coche; +$coche->tocarBocina(); // Imprime '¡Bip bip!' + +$camion = new Camion; +$camion->tocarBocina(); // También imprime '¡Bip bip!' +``` + +En este ejemplo, tenemos un trait llamado `TocarBocina` que contiene un método `tocarBocina()`. Luego tenemos dos clases: `Coche` y `Camion`, que ambas usan el trait `TocarBocina`. Gracias a esto, ambas clases "tienen" el método `tocarBocina()`, y podemos llamarlo en objetos de ambas clases. + +Los traits te permiten compartir código entre clases de manera fácil y eficiente. Sin embargo, no entran en la jerarquía de herencia, es decir, `$coche instanceof TocarBocina` devolverá `false`. + + +Excepciones +----------- + +Las excepciones en POO nos permiten manejar errores y situaciones inesperadas en nuestro código de manera elegante. Son objetos que llevan información sobre el error o la situación inusual. + +En PHP, tenemos una clase incorporada `Exception`, que sirve como base para todas las excepciones. Tiene varios métodos que nos permiten obtener más información sobre la excepción, como el mensaje de error, el archivo y la línea donde ocurrió el error, etc. + +Cuando ocurre un error en el código, podemos "lanzar" una excepción usando la palabra clave `throw`. + +```php +function dividir(float $a, float $b): float +{ + if ($b === 0.0) { // Comparar floats con 0.0 + throw new Exception('¡División por cero!'); + } + return $a / $b; +} +``` + +Cuando la función `dividir()` recibe cero como segundo argumento, lanza una excepción con el mensaje de error `'¡División por cero!'`. Para evitar que el programa se bloquee al lanzar una excepción, la capturamos en un bloque `try/catch`: + +```php +try { + echo dividir(10, 0); +} catch (Exception $e) { + echo 'Excepción capturada: '. $e->getMessage(); +} +``` + +El código que puede lanzar una excepción se envuelve en un bloque `try`. Si se lanza una excepción, la ejecución del código se traslada al bloque `catch`, donde podemos procesar la excepción (por ejemplo, imprimir un mensaje de error). + +Después de los bloques `try` y `catch`, podemos agregar un bloque opcional `finally`, que se ejecutará siempre, ya sea que se haya lanzado una excepción o no (incluso si usamos una instrucción `return`, `break` o `continue` en el bloque `try` o `catch`): + +```php +try { + echo dividir(10, 0); +} catch (Exception $e) { + echo 'Excepción capturada: '. $e->getMessage(); +} finally { + // Código que se ejecutará siempre, se lance o no una excepción +} +``` + +También podemos crear nuestras propias clases (jerarquía) de excepciones que hereden de la clase Exception. Como ejemplo, imaginemos una aplicación bancaria simple que permite realizar depósitos y retiros: + +```php +class ExcepcionBancaria extends Exception {} +class FondosInsuficientesExcepcion extends ExcepcionBancaria {} +class LimiteExcedidoExcepcion extends ExcepcionBancaria {} + +class CuentaBancaria +{ + private int $saldo = 0; + private int $limiteDiario = 1000; + + public function depositar(int $cantidad): int + { + $this->saldo += $cantidad; + return $this->saldo; + } + + public function retirar(int $cantidad): int + { + if ($cantidad > $this->saldo) { + throw new FondosInsuficientesExcepcion('No hay fondos suficientes en la cuenta.'); + } + + if ($cantidad > $this->limiteDiario) { + throw new LimiteExcedidoExcepcion('Se ha excedido el límite diario para retiros.'); + } + + $this->saldo -= $cantidad; + return $this->saldo; + } +} +``` + +Para un bloque `try`, se pueden especificar múltiples bloques `catch` si esperas diferentes tipos de excepciones. + +```php +$cuenta = new CuentaBancaria; +$cuenta->depositar(500); + +try { + $cuenta->retirar(1500); +} catch (LimiteExcedidoExcepcion $e) { + echo $e->getMessage(); +} catch (FondosInsuficientesExcepcion $e) { + echo $e->getMessage(); +} catch (ExcepcionBancaria $e) { + echo 'Ocurrió un error al realizar la operación.'; +} +``` + +En este ejemplo, es importante notar el orden de los bloques `catch`. Dado que todas las excepciones heredan de `ExcepcionBancaria`, si tuviéramos este bloque primero, capturaría todas las excepciones sin que el código llegara a los bloques `catch` siguientes. Por lo tanto, es importante tener las excepciones más específicas (es decir, las que heredan de otras) en el bloque `catch` más arriba en el orden que sus excepciones padre. + + +Iteración +--------- + +En PHP, puedes recorrer objetos usando un bucle `foreach`, de manera similar a como recorres arrays. Para que esto funcione, el objeto debe implementar una interfaz especial. + +La primera opción es implementar la interfaz `Iterator`, que tiene los métodos `current()` que devuelve el valor actual, `key()` que devuelve la clave, `next()` que se mueve al siguiente valor, `rewind()` que se mueve al principio y `valid()` que comprueba si aún no hemos llegado al final. + +La segunda opción es implementar la interfaz `IteratorAggregate`, que solo tiene un método `getIterator()`. Este devuelve un objeto sustituto que se encargará de la iteración, o puede representar un generador, que es una función especial en la que se usa `yield` para devolver claves y valores secuencialmente: + +```php +class Persona +{ + public function __construct( + public int $edad, + ) { + } +} + +class Lista implements IteratorAggregate +{ + private array $personas = []; + + public function agregarPersona(Persona $persona): void + { + $this->personas[] = $persona; + } + + public function getIterator(): Generator + { + foreach ($this->personas as $persona) { + yield $persona; + } + } +} + +$lista = new Lista; +$lista->agregarPersona(new Persona(30)); +$lista->agregarPersona(new Persona(25)); + +foreach ($lista as $persona) { + echo "Edad: {$persona->edad} años \n"; +} +``` + + +Buenas prácticas +---------------- + +Una vez que tienes los principios básicos de la programación orientada a objetos, es importante centrarse en las buenas prácticas en POO. Estas te ayudarán a escribir código que no solo sea funcional, sino también legible, comprensible y fácil de mantener. + +1) **Separación de responsabilidades (Separation of Concerns)**: Cada clase debe tener una responsabilidad claramente definida y debe abordar solo una tarea principal. Si una clase hace demasiadas cosas, puede ser apropiado dividirla en clases más pequeñas y especializadas. +2) **Encapsulación (Encapsulation)**: Los datos y métodos deben estar lo más ocultos posible y ser accesibles solo a través de una interfaz definida. Esto te permitirá cambiar la implementación interna de la clase sin afectar al resto del código. +3) **Inyección de dependencias (Dependency Injection)**: En lugar de crear dependencias directamente en la clase, deberías "inyectarlas" desde el exterior. Para una comprensión más profunda de este principio, recomendamos los [capítulos sobre Inyección de Dependencias|dependency-injection:introduction]. diff --git a/nette/es/troubleshooting.texy b/nette/es/troubleshooting.texy index 6089e15492..24bd645562 100644 --- a/nette/es/troubleshooting.texy +++ b/nette/es/troubleshooting.texy @@ -2,40 +2,69 @@ Solución de problemas ********************* -Nette no funciona, aparece una página en blanco .[#toc-nette-is-not-working-white-page-is-displayed] ----------------------------------------------------------------------------------------------------- -- Pruebe a poner `ini_set('display_errors', '1'); error_reporting(E_ALL);` después de `declare(strict_types=1);` en el archivo `index.php` para forzar la visualización de errores -- Si sigue viendo una pantalla blanca, probablemente haya un error en la configuración del servidor y descubrirá la razón en el registro del servidor. Para estar seguro, verifique si PHP está funcionando intentando imprimir algo usando `echo 'test';`. -- Si ve un error *Server Error: ¡Lo sentimos! ...*, continúe con la siguiente sección: +Nette no funciona, se muestra una página en blanco +-------------------------------------------------- +- Intente insertar `ini_set('display_errors', '1'); error_reporting(E_ALL);` en el archivo `index.php` justo después de `declare(strict_types=1);`, esto forzará la visualización de errores. +- Si sigue viendo una pantalla en blanco, probablemente haya un error en la configuración del servidor y la razón se revelará en el registro del servidor. Por si acaso, verifique si PHP funciona en absoluto intentando imprimir algo usando `echo 'test';` +- Si ve el error *Server Error: We're sorry! …*, continúe con la siguiente sección: -Error 500 *Error del servidor: ¡Lo sentimos! ...* .[#toc-error-500-server-error-we-re-sorry] --------------------------------------------------------------------------------------------- -Esta página de error es mostrada por Nette en modo de producción. Si la ve en una máquina de desarrollador, [cambie al modo de desarrollador |application:bootstrap#Development vs Production Mode]. +Error 500 *Server Error: We're sorry! …* +---------------------------------------- +Esta página de error la muestra Nette en modo de producción. Si se le muestra en su computadora de desarrollo, [cambie al modo de desarrollo |application:bootstrapping#Modo de desarrollo vs producción] y se le mostrará Tracy con un mensaje detallado. -Si el mensaje de error contiene `Tracy is unable to log error`, averigüe por qué no se pueden registrar los errores. Puede hacerlo, por ejemplo, [cambiando |application:bootstrap#Development vs Production Mode] a modo desarrollador y llamando a `Tracy\Debugger::log('hello');` después de `$configurator->enableTracy(...)`. Tracy le dirá por qué no puede registrar. -La causa suele ser la [falta de permisos |#Setting Directory Permissions] para escribir en el directorio `log/`. +La razón del error siempre se puede encontrar en el registro en el directorio `log/`. Sin embargo, si el mensaje de error muestra la frase `Tracy is unable to log error`, primero averigüe por qué no se pueden registrar los errores. Puede hacerlo, por ejemplo, [cambiando |application:bootstrapping#Modo de desarrollo vs producción] temporalmente al modo de desarrollo y dejando que Tracy registre cualquier cosa después de su inicio: -Si la sentencia `Tracy is unable to log error` no aparece en el mensaje de error (ya no), puedes averiguar la razón del error en el log del directorio `log/`. +```php +// Bootstrap.php +$configurator->setDebugMode('23.75.345.200'); // su dirección IP +$configurator->enableTracy($rootDir . '/log'); +\Tracy\Debugger::log('hello'); +``` + +Tracy le dirá por qué no puede registrar. La causa puede ser un componente defectuoso, pero más probablemente [permisos insuficientes |#Configuración de permisos de directorio] para escribir en el directorio `log/`. + +Una de las razones más comunes del error 500 es una caché obsoleta. Mientras que Nette en modo de desarrollo actualiza inteligentemente la caché automáticamente, en modo de producción se centra en maximizar el rendimiento y borrar la caché, después de cada modificación del código, depende de usted. Intente borrar `temp/cache`. + + +Error 404, el enrutamiento no funciona +-------------------------------------- +Cuando todas las páginas (excepto la página de inicio) devuelven un error 404, parece un problema con la configuración del servidor para [URLs amigables |#Cómo configurar el servidor para URLs amigables]. + + +Los cambios en las plantillas o la configuración no se reflejan +--------------------------------------------------------------- +"He modificado la plantilla o la configuración, pero el sitio web sigue mostrando la versión antigua." Este comportamiento ocurre en el [modo de producción |application:bootstrapping#Modo de desarrollo vs producción], que por razones de rendimiento no verifica los cambios en los archivos y mantiene la caché generada una vez. + +Para no tener que borrar manualmente la caché en el servidor de producción después de cada modificación, habilite el modo de desarrollo para su dirección IP en el archivo `Bootstrap.php`: + +```php +$this->configurator->setDebugMode('su.direccion.ip'); +``` + + +¿Cómo desactivar la caché durante el desarrollo? +------------------------------------------------ +Nette es inteligente y no necesita desactivar el almacenamiento en caché. Durante el desarrollo, actualiza automáticamente la caché cada vez que se cambia una plantilla o la configuración del contenedor DI. Además, el modo de desarrollo se activa por autodetección, por lo que generalmente no es necesario configurar nada, [o solo la dirección IP |application:bootstrapping#Modo de desarrollo vs producción]. -Una de las razones más comunes es una caché obsoleta. Mientras que Nette inteligentemente actualiza automáticamente la caché en modo desarrollo, en modo producción se centra en maximizar el rendimiento, y limpiar la caché después de cada modificación de código depende de ti. Intente borrar `temp/cache`. +Al depurar el router, recomendamos desactivar la caché en el navegador, donde pueden almacenarse, por ejemplo, redirecciones: abra las Herramientas de desarrollo (Ctrl+Shift+I o Cmd+Option+I) y en el panel Red (Network) marque la desactivación de la caché. -Error `#[\ReturnTypeWillChange] attribute should be used` .[#toc-error-returntypewillchange-attribute-should-be-used] ---------------------------------------------------------------------------------------------------------------------- -Este error se produce si ha actualizado PHP a la versión 8.1 pero está utilizando Nette, que no es compatible con ella. Entonces la solución es actualizar Nette a una versión más reciente usando `composer update`. Nette es compatible con PHP 8.1 desde la versión 3.0. Si está usando una versión anterior (puede averiguarlo buscando en `composer.json`), [actualice |migrations:en] Nette o quédese con PHP 8.0. +Error `#[\ReturnTypeWillChange] attribute should be used` +--------------------------------------------------------- +Este error aparece si ha actualizado PHP a la versión 8.1, pero está utilizando una versión de Nette que no es compatible con ella. La solución es, por lo tanto, actualizar Nette a una versión más reciente usando `composer update`. Nette es compatible con PHP 8.1 desde la versión 3.0. Si está utilizando una versión anterior (compruébelo en `composer.json`), [actualice Nette |migrations:en] o quédese con PHP 8.0. -Configuración de permisos de directorio .[#toc-setting-directory-permissions] ------------------------------------------------------------------------------ -Si está desarrollando en macOS o Linux (o cualquier otro sistema basado en Unix), necesita configurar los privilegios de escritura en el servidor web. Suponiendo que su aplicación se encuentra en el directorio por defecto `/var/www/html` (Fedora, CentOS, RHEL) +Configuración de permisos de directorio +--------------------------------------- +Si está desarrollando en macOS o Linux (o cualquier otro sistema basado en Unix), deberá configurar los permisos de escritura para el servidor web. Supongamos que su aplicación se encuentra en el directorio predeterminado `/var/www/html` (Fedora, CentOS, RHEL). ```shell cd /var/www/html/MY_PROJECT chmod -R a+rw temp log ``` -En algunos sistemas Linux (Fedora, CentOS, ...) SELinux puede estar activado por defecto. Es posible que tenga que actualizar las políticas de SELinux o configurar las rutas de los directorios `temp` y `log` con el contexto de seguridad SELinux correcto. Los directorios `temp` y `log` deben configurarse con el contexto `httpd_sys_rw_content_t`; para el resto de la aplicación -- principalmente la carpeta `app` -- el contexto `httpd_sys_content_t` será suficiente. Ejecutar en el servidor como root: +En algunos sistemas Linux (Fedora, CentOS, ...), SELinux está habilitado de forma predeterminada. Deberá ajustar las políticas de SELinux en consecuencia y establecer el contexto de seguridad SELinux correcto para las carpetas `temp` y `log`. Para `temp` y `log` estableceremos el tipo de contexto `httpd_sys_rw_content_t`, para el resto de la aplicación (y especialmente para la carpeta `app`) será suficiente `httpd_sys_content_t`. Ejecute en el servidor: ```shell semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/log(/.*)?' @@ -43,25 +72,30 @@ semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/temp(/.*) restorecon -Rv /var/www/html/MY_PROJECT/ ``` -A continuación, es necesario activar el booleano de SELinux `httpd_can_network_connect_db` para permitir que Nette se conecte a la base de datos a través de la red. Por defecto, está desactivado. El comando `setsebool` se puede utilizar para realizar esta tarea, y si se especifica la opción `-P`, esta configuración será persistente a través de reinicios. +Además, es necesario habilitar el booleano de SELinux `httpd_can_network_connect_db`, que está desactivado por defecto y que permite a Nette conectarse a la base de datos a través de la red. Usaremos el comando `setsebool` y con la opción `-P` haremos el cambio permanente, es decir, después de reiniciar el servidor no nos encontraremos con una sorpresa desagradable: ```shell setsebool -P httpd_can_network_connect_db on ``` -¿Cómo cambiar o eliminar el directorio `www` de la URL? .[#toc-how-to-change-or-remove-www-directory-from-url] --------------------------------------------------------------------------------------------------------------- -El directorio `www/` utilizado en los proyectos de ejemplo de Nette es el denominado directorio público o raíz documental del proyecto. Es el único directorio cuyo contenido es accesible para el navegador. Y contiene el archivo `index.php`, el punto de entrada que inicia una aplicación web escrita en Nette. +¿Cómo cambiar o eliminar el directorio `www` de la URL? +------------------------------------------------------- +El directorio `www/` utilizado en los proyectos de ejemplo en Nette representa el llamado directorio público o document-root del proyecto. Es el único directorio cuyo contenido es accesible para el navegador. Y contiene el archivo `index.php`, el punto de entrada que inicia la aplicación web escrita en Nette. -Para ejecutar la aplicación en el hosting, es necesario establecer el document-root a este directorio en la configuración del hosting. O, si el alojamiento tiene una carpeta pre-hecha para el directorio público con un nombre diferente (por ejemplo `web`, `public_html` etc.), simplemente renombre `www/`. +Para poner en funcionamiento la aplicación en un hosting, es necesario tener configurado correctamente el document-root. Tiene dos opciones: +1. Establecer el document-root en este directorio en la configuración del hosting. +2. Si el hosting tiene una carpeta predefinida (por ejemplo, `public_html`), renombre `www/` a este nombre. -La solución **no** es "deshacerse" de la carpeta `www/` utilizando reglas en el archivo `.htaccess` o en el router. Si el alojamiento no te permite establecer document-root en un subdirectorio (es decir, crear directorios un nivel por encima del directorio público), busca otro. De lo contrario, estarías asumiendo un riesgo de seguridad importante. Sería como vivir en un apartamento en el que no puedes cerrar la puerta principal y siempre está abierta de par en par. +.[warning] +Nunca intente resolver la seguridad solo con `.htaccess` o el router, que impedirían el acceso a otras carpetas. +Si el hosting no permitiera establecer el document-root en un subdirectorio (es decir, crear directorios un nivel por encima del directorio público), busque otro. De lo contrario, correría un riesgo de seguridad considerable. Sería como vivir en un apartamento donde la puerta de entrada no se puede cerrar y siempre está abierta. -¿Cómo configurar un servidor para URLs agradables? .[#toc-how-to-configure-a-server-for-nice-urls] --------------------------------------------------------------------------------------------------- -**Apache**: la extensión mod_rewrite debe estar permitida y configurada en un archivo `.htaccess`. + +¿Cómo configurar el servidor para URLs amigables? +------------------------------------------------- +**Apache**: es necesario habilitar y configurar las reglas de mod_rewrite en el archivo `.htaccess`: ```apacheconf RewriteEngine On @@ -70,25 +104,53 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -Para alterar la configuración de Apache con archivos .htaccess, la directiva AllowOverride debe estar habilitada. Este es el comportamiento por defecto de Apache. +Si encuentra problemas, asegúrese de que: +- el archivo `.htaccess` se encuentra en el directorio document-root (es decir, junto al archivo `index.php`) +- [Apache procesa los archivos `.htaccess` |#Verificación de que .htaccess funciona] +- [mod_rewrite está habilitado |#Verificación de que mod rewrite está habilitado] + +Si está configurando la aplicación en una subcarpeta, es posible que deba descomentar la línea para configurar `RewriteBase` y establecerla en la carpeta correcta. -**nginx**: la directiva `try_files` debe utilizarse en la configuración del servidor: +**nginx**: es necesario configurar la redirección usando la directiva `try_files` dentro del bloque `location /` en la configuración del servidor. ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args is important + try_files $uri $uri/ /index.php$is_args$args; # ¡$is_args$args ES IMPORTANTE! } ``` -El bloque `location` debe definirse exactamente una vez para cada ruta del sistema de ficheros en el bloque `server`. Si ya tiene un bloque `location /` en su configuración, añada la directiva `try_files` al bloque existente. +El bloque `location` para cada ruta del sistema de archivos solo puede aparecer una vez en el bloque `server`. Si ya tiene `location /` en la configuración, agregue la directiva `try_files` a él. + + +Verificación de que `.htaccess` funciona +---------------------------------------- +La forma más sencilla de probar si Apache usa o ignora su archivo `.htaccess` es dañarlo intencionalmente. Inserte la línea `Test` al principio del archivo y ahora, si actualiza la página en el navegador, debería ver *Internal Server Error*. + +Si ve este error, ¡en realidad es bueno! Significa que Apache está analizando el archivo `.htaccess` y encuentra el error que insertamos allí. Elimine la línea `Test`. + +Si no aparece *Internal Server Error*, su configuración de Apache está ignorando el archivo `.htaccess`. Generalmente, Apache lo ignora debido a la falta de la directiva de configuración `AllowOverride All`. + +Si lo aloja usted mismo, se puede arreglar fácilmente. Abra el archivo `httpd.conf` o `apache.conf` en un editor de texto, busque la sección `<Directory>` correspondiente y agregue/cambie esta directiva: + +```apacheconf +<Directory "/var/www/htdocs"> # ruta a su document root + AllowOverride All + ... +``` + +Si su sitio web está alojado en otro lugar, consulte el panel de control para ver si puede habilitar el archivo `.htaccess` allí. Si no, póngase en contacto con su proveedor de hosting para que lo haga por usted. + + +Verificación de que `mod_rewrite` está habilitado +------------------------------------------------- +Si ha verificado que [`.htaccess` funciona |#Verificación de que .htaccess funciona], puede verificar si la extensión mod_rewrite está habilitada. Inserte la línea `RewriteEngine On` al principio del archivo `.htaccess` y actualice la página en el navegador. Si aparece *Internal Server Error*, significa que mod_rewrite no está habilitado. Hay varias formas de habilitarlo. Puede encontrar diferentes formas de hacerlo en diversas configuraciones en Stack Overflow. -Los enlaces se generan sin `https:` .[#toc-links-are-generated-without-https] ------------------------------------------------------------------------------ -Nette genera enlaces con el mismo protocolo que utiliza la página actual. Así, en la página `https://foo` y viceversa. -Si estás detrás de un proxy inverso que elimina HTTPS (por ejemplo, en Docker), entonces necesitas [establecer un proxy |http:configuration#HTTP proxy] en la configuración para que la detección de protocolo funcione correctamente. +Los enlaces se generan sin `https:` +----------------------------------- +Nette genera enlaces con el mismo protocolo que tiene la propia página. Es decir, en la página `https://foo` genera enlaces que comienzan con `https:` y viceversa. Si está detrás de un servidor proxy inverso que elimina HTTPS (por ejemplo, en Docker), entonces es necesario [configurar el proxy |http:configuration#Proxy HTTP] en la configuración para que la detección del protocolo funcione correctamente. -Si usas Nginx como proxy, necesitas tener la redirección configurada así: +Si utiliza Nginx como proxy, es necesario tener configurada la redirección, por ejemplo, así: ``` location / { @@ -96,11 +158,11 @@ location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Port $server_port; - proxy_pass http://IP-aplikace:80; # IP o nombre de host del servidor/contenedor donde se ejecuta la aplicación + proxy_pass http://IP-aplicacion:80; # IP o hostname del servidor/contenedor donde se ejecuta la aplicación } ``` -A continuación, debe especificar el proxy IP y, si procede, el rango IP de su red local donde ejecuta la infraestructura: +Además, es necesario indicar en la configuración la IP del proxy y, opcionalmente, el rango de IP de su red local donde opera la infraestructura: ```neon http: @@ -108,29 +170,27 @@ http: ``` -Uso de caracteres { } en JavaScript .[#toc-use-of-characters-in-javascript] ---------------------------------------------------------------------------- -Los caracteres `{` and `}` se utilizan para escribir etiquetas Latte. Todo (excepto el espacio y las comillas) después de `{` character is considered a tag. If you need to print character `{` (a menudo en JavaScript), puede poner un espacio (u otro carácter vacío) justo después de `{`. De esta manera se evita que se interprete como una etiqueta. +Uso de los caracteres { } en JavaScript +--------------------------------------- +Los caracteres `{` y `}` se utilizan para escribir etiquetas Latte. Se considera una etiqueta cualquier cosa que siga al carácter `{` con la excepción de un espacio y una comilla. Por lo tanto, si necesita imprimir directamente el carácter `{` (a menudo, por ejemplo, en JavaScript), puede poner un espacio (u otro carácter en blanco) después del carácter `{`. De esta manera evitará la traducción como etiqueta. -Si es necesario imprimir estos caracteres en una situación en la que se interpretarían como una etiqueta, puede utilizar etiquetas especiales para imprimir estos caracteres: `{l}` para `{` and `{r}` para `}`. +Si es necesario imprimir estos caracteres en una situación en la que el texto se entendería como una etiqueta, puede utilizar etiquetas especiales para imprimir estos caracteres: `{l}` para `{` y `{r}` para `}`. ``` -{is tag} -{ is not tag } -{l}is not tag{r} +{es una etiqueta} +{ no es una etiqueta } +{l}no es una etiqueta{r} ``` -Observe `Presenter::getContext() is deprecated` .[#toc-notice-presenter-getcontext-is-deprecated] -------------------------------------------------------------------------------------------------- +Mensaje `Presenter::getContext() is deprecated` +----------------------------------------------- -Nette es de lejos el primer framework PHP que cambió a la inyección de dependencias y llevó a los programadores a usarla de forma consistente, empezando por los presentadores. Si un presentador necesita una dependencia, [la pedirá |dependency-injection:passing-dependencies]. -Por el contrario, la forma en que pasamos todo el contenedor DI a una clase y ésta extrae las dependencias de él directamente se considera un antipatrón (se llama localizador de servicios). -Esta forma se utilizaba en Nette 0.x antes de la llegada de la inyección de dependencias, y su reliquia es el método `Presenter::getContext()`, hace tiempo marcado como obsoleto. +Nette es, con mucho, el primer framework PHP que pasó a la inyección de dependencia y guió a los programadores a usarla consistentemente, desde los propios presenters. Si un presenter necesita alguna dependencia, [la solicita|dependency-injection:passing-dependencies]. Por el contrario, la forma en que pasamos todo el contenedor DI a la clase, y esta extrae las dependencias directamente de él, se considera un antipatrón (se llama localizador de servicios). Este método se usaba en Nette 0.x antes de la llegada de la inyección de dependencia y su remanente es el método `Presenter::getContext()`, marcado como obsoleto hace mucho tiempo. -Si portas una aplicación Nette muy antigua, puedes encontrar que todavía utiliza este método. Así que desde la versión 3.1 de `nette/application` se encontrará con la advertencia `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection`, desde la versión 4.0 se encontrará con el error de que el método no existe. +Si está portando una aplicación muy antigua para Nette, puede que todavía use este método. Desde la versión 3.1 de `nette/application` se encontrará con la advertencia `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection`, desde la versión 4.0 con el error de que el método no existe. -La solución limpia, por supuesto, es rediseñar la aplicación para pasar dependencias usando inyección de dependencias. Como solución alternativa, puede añadir su propio método `getContext()` a su presentador base y saltarse el mensaje: +La solución limpia es, por supuesto, rediseñar la aplicación para que pase las dependencias usando la inyección de dependencia. Como solución alternativa, puede agregar su propio método `getContext()` a su presenter base y así evitar el mensaje: ```php abstract BasePresenter extends Nette\Application\UI\Presenter diff --git a/nette/es/vulnerability-protection.texy b/nette/es/vulnerability-protection.texy new file mode 100644 index 0000000000..11bdaf82d1 --- /dev/null +++ b/nette/es/vulnerability-protection.texy @@ -0,0 +1,99 @@ +Protección contra vulnerabilidades +********************************** + +.[perex] +Cada cierto tiempo se informa de una brecha de seguridad en otro sitio web importante o se explota una brecha. Esto es desagradable. Si le importa la seguridad de sus aplicaciones web, Nette Framework es sin duda la mejor opción. + + +Cross-Site Scripting (XSS) +========================== + +Cross-Site Scripting es un método de vulneración de sitios web que abusa de salidas no tratadas. El atacante puede entonces inyectar su propio código en la página y así modificarla o incluso obtener datos sensibles sobre los visitantes. La única forma de defenderse contra XSS es tratando de manera consistente y correcta todas las cadenas. Sin embargo, basta con que su codificador lo omita una sola vez, y todo el sitio web puede verse comprometido de inmediato. + +Un ejemplo de ataque puede ser inyectar una URL modificada al usuario, mediante la cual inyectamos nuestro código en la página. Si la aplicación no trata adecuadamente las salidas, ejecutará el script en el navegador del usuario. De esta manera, podemos, por ejemplo, robarle su identidad. + +``` +https://example.com/?search=<script>alert('Ataque XSS exitoso.');</script> +``` + +Nette Framework introduce una tecnología revolucionaria [Context-Aware Escaping |latte:safety-first#Escape sensible al contexto], que le librará para siempre del riesgo de Cross-Site Scripting. Trata automáticamente todas las salidas, por lo que no puede ocurrir que el codificador olvide algo. ¿Un ejemplo? El codificador crea esta plantilla: + +```latte +<p onclick="alert({$message})">{$message}</p> + +<script> +document.title = {$message}; +</script> +``` + +La notación `{$message}` significa imprimir una variable. En otros frameworks, es necesario tratar explícitamente cada impresión e incluso de manera diferente en cada lugar. En Nette Framework no es necesario tratar nada, todo se hace automáticamente, correctamente y de forma consistente. Si asignamos a la variable `$message = 'Ancho 1/2"'`, el framework generará el código HTML: + +```latte +<p onclick="alert("Ancho 1\/2\"")">Ancho 1/2"</p> + +<script> +document.title = "Ancho 1\/2\""; +</script> +``` + + +Cross-Site Request Forgery (CSRF) +================================= + +El ataque Cross-Site Request Forgery consiste en que el atacante atrae a la víctima a una página que, discretamente en el navegador de la víctima, realiza una solicitud al servidor en el que la víctima ha iniciado sesión, y el servidor cree que la solicitud fue realizada voluntariamente por la víctima. Así, bajo la identidad de la víctima, realiza una determinada acción sin que ésta lo sepa. Puede tratarse de un cambio o eliminación de datos, envío de un mensaje, etc. + +Nette Framework **protege automáticamente los formularios y señales en los presenters** contra este tipo de ataque. Lo hace impidiendo que se envíen o se invoquen desde otro dominio. Si desea desactivar la protección, use en los formularios: + +```php +$form->allowCrossOrigin(); +``` + +o en el caso de una señal, agregue la anotación `@crossOrigin`: + +```php +/** + * @crossOrigin + */ +public function handleXyz() +{ +} +``` + +En Nette Application 3.2 también puede usar atributos: + +```php +use Nette\Application\Attributes\Requires; + +#[Requires(sameOrigin: false)] +public function handleXyz() +{ +} +``` + + +Ataque de URL, códigos de control, UTF-8 inválido +================================================= + +Varios términos relacionados con el intento de un atacante de inyectar una entrada *maliciosa* en su aplicación web. Las consecuencias pueden ser muy diversas, desde dañar las salidas XML (por ejemplo, canales RSS no funcionales) hasta obtener información sensible de la base de datos o contraseñas. La defensa consiste en tratar consistentemente todas las entradas a nivel de bytes individuales. Y seamos sinceros, ¿quién de ustedes lo hace? + +Nette Framework lo hace por usted y, además, automáticamente. No necesita configurar absolutamente nada y todas las entradas estarán tratadas. + + +Secuestro de sesión, robo de sesión, fijación de sesión +======================================================= + +La gestión de sesiones está asociada a varios tipos de ataques. El atacante roba o inyecta su ID de sesión al usuario y gracias a ello obtiene acceso a la aplicación web sin conocer la contraseña del usuario. Luego puede hacer cualquier cosa en la aplicación sin que el usuario lo sepa. La defensa consiste en una configuración correcta del servidor y de PHP. + +Nette Framework configura PHP automáticamente. El programador no tiene que pensar en cómo asegurar correctamente la sesión y puede concentrarse plenamente en la creación de la aplicación. Sin embargo, requiere que la función `ini_set()` esté habilitada. + + +Cookie SameSite +=============== + +Las cookies SameSite proporcionan un mecanismo para reconocer qué llevó a la carga de la página. Lo cual es absolutamente crucial por razones de seguridad. + +El indicador SameSite puede tener tres valores: `Lax`, `Strict` y `None` (este último requiere HTTPS). Si la solicitud de la página proviene directamente del sitio web o el usuario abre la página introduciéndola directamente en la barra de direcciones o haciendo clic en un marcador, el navegador enviará todas las cookies al servidor (es decir, con los indicadores `Lax`, `Strict` y `None`). Si el usuario llega al sitio web haciendo clic en un enlace desde otro sitio web, se pasarán al servidor las cookies con los indicadores `Lax` y `None`. Si la solicitud se origina de otra manera, como el envío de un formulario POST desde otro sitio web, la carga dentro de un iframe, mediante JavaScript, etc., solo se enviarán las cookies con el indicador `None`. + +Nette envía por defecto todas las cookies con el indicador `Lax`. + +{{leftbar: www:@menu-common}} diff --git a/nette/fr/@home.texy b/nette/fr/@home.texy index 76241bb9d3..d1e0fd5d94 100644 --- a/nette/fr/@home.texy +++ b/nette/fr/@home.texy @@ -9,21 +9,21 @@ Documentation Nette Introduction ------------ - [Pourquoi utiliser Nette ? |www:10-reasons-why-nette] -- L'[installation |Installation] -- [Créez votre première application ! |quickstart:] +- [Installation |installation] +- [Écrivons notre première application ! |quickstart:] -Généralités ------------ +Général +------- - [Liste des paquets |www:packages] -- [Maintenance et PHP |www:maintenance] +- [Maintenance et versions PHP |www:maintenance] - [Notes de version |https://nette.org/releases] -- [Guide de mise à niveau |migrations:en] -- [Dépannage |nette:Troubleshooting] -- [Qui crée Nette |https://nette.org/contributors] +- [Passer aux versions plus récentes|migrations:en] +- [Résolution de problèmes |nette:troubleshooting] +- [Qui fait Nette |https://nette.org/contributors] - [Histoire de Nette |www:history] -- [S'impliquer |contributing:] -- [Développement du parrainage |https://nette.org/en/donate] +- [Contribuer |contributing:] +- [Soutenir le développement |https://nette.org/fr/donate] - [Référence API |https://api.nette.org/] </div> @@ -31,20 +31,20 @@ Généralités <div> -Application Nette ------------------ +Applications Nette +------------------ - [Comment fonctionnent les applications ? |application:how-it-works] -- [Bootstrap |application:Bootstrap] -- [Présentateurs |application:Presenters] -- [Modèles |application:Templates] -- [Modules |application:Modules] -- [Acheminement |application:Routing] +- [application:Bootstrapping] +- [Presenters |application:presenters] +- [Templates |application:templates] +- [Structure des répertoires |application:directory-structure] +- [Routage |application:routing] - [Création de liens URL |application:creating-links] - [Composants interactifs |application:components] -- [AJAX et Snippets |application:ajax] +- [AJAX & snippets |application:ajax] -- [Meilleures pratiques |best-practices:] +- [Tutoriels et bonnes pratiques |best-practices:] </div> @@ -54,20 +54,21 @@ Application Nette Sujets principaux ----------------- - [Configuration |nette:configuring] -- [Injection de dépendances |dependency-injection:] -- [Latte : Modèles |latte:] -- [Tracy : Outil de débogage |tracy:] +- [Injection de dépendances|dependency-injection:] +- [Latte : templates |latte:] +- [Tracy : débogage de code |tracy:] - [Formulaires |forms:] -- [Base de données |database:core] -- [Authentification des utilisateurs |security:authentication] -- [Contrôle d'accès |security:authorization] -- [Sessions |http:Sessions] -- [Demande et réponse HTTP |http:] -- [Mise en cache |caching:] -- [Envoi de courriels |mail:] -- [Schéma : Validation des données |schema:] +- [Base de données |database:guide] +- [Connexion des utilisateurs |security:authentication] +- [Vérification des autorisations |security:authorization] +- [http:Sessions] +- [Requête & réponse HTTP|http:] +- [Actifs |assets:] +- [Cache |caching:] +- [Envoi d'e-mails |mail:] +- [Schema : validation de données |schema:] - [Générateur de code PHP |php-generator:] -- [Testeur : Test unitaire |tester:] +- [Tester : tests |tester:] </div> @@ -76,19 +77,19 @@ Sujets principaux Utilitaires ----------- -- [Arrays |utils:Arrays] +- [Tableaux |utils:arrays] - [Système de fichiers |utils:filesystem] - [Finder |utils:finder] -- [Éléments HTML |utils:HTML Elements] -- [Images |utils:Images] -- [JSON |utils:JSON] -- [NEON |neon:] -- [Hachage de mot de passe |security:passwords] -- [SmartObject |utils:SmartObject] +- [Éléments HTML |utils:html-elements] +- [Images |utils:images] +- [utils:JSON] +- [NEON|neon:] +- [Hachage de mots de passe |security:passwords] - [Types PHP |utils:type] -- [Strings |utils:Strings] +- [Chaînes de caractères |utils:strings] - [Validateurs |utils:validators] - [RobotLoader |robot-loader:] +- [SmartObject |utils:smartobject] & [StaticClass |utils:StaticClass] - [SafeStream |safe-stream:] - [...autres |utils:] </div> @@ -97,5 +98,5 @@ Utilitaires {{toc:no}} -{{description: Documentation officielle de Nette : décrit le fonctionnement de Nette et les meilleures pratiques pour développer des applications web.}} -{{maintitle: Nette Documentation}} +{{description: Documentation officielle de Nette : décrit comment Nette fonctionne et les meilleures pratiques pour le développement d'applications web.}} +{{maintitle: Documentation Nette}} diff --git a/nette/fr/@menu-topics.texy b/nette/fr/@menu-topics.texy index 4eda66e6b1..bde638e8c9 100644 --- a/nette/fr/@menu-topics.texy +++ b/nette/fr/@menu-topics.texy @@ -1,21 +1,21 @@ -Thèmes principaux +Sujets principaux ***************** - [Configuration |nette:configuring] -- [Application Nette |application:how-it-works] -- [Injection de dépendances |dependency-injection:] +- [Applications Nette |application:how-it-works] +- [Injection de dépendances|dependency-injection:] - [Utilitaires |utils:] - [Formulaires |forms:] -- [Base de données |database:core] -- [Authentification des utilisateurs |security:authentication] -- [Contrôle d'accès |security:authorization] -- [Sessions |http:Sessions] -- [Demande et réponse HTTP |http:] -- [Mise en cache |caching:] -- [Envoi de courriels |mail:] -- [Schéma : Validation des données |schema:] +- [Base de données |database:guide] +- [Connexion des utilisateurs |security:authentication] +- [Vérification des autorisations |security:authorization] +- [http:Sessions] +- [Requête & réponse HTTP|http:] +- [Cache |caching:] +- [Envoi d'e-mails |mail:] +- [Schema : validation de données |schema:] - [Générateur de code PHP |php-generator:] -- [Latte : modèles |latte:] -- [Tracy : débogage |tracy:] -- [Tester : tester |tester:] +- [Latte : templates |latte:] +- [Tracy : débogage de code |tracy:] +- [Tester : tests |tester:] diff --git a/nette/fr/@meta.texy b/nette/fr/@meta.texy new file mode 100644 index 0000000000..72ae4b8db8 --- /dev/null +++ b/nette/fr/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentation Nette}} diff --git a/nette/fr/configuring.texy b/nette/fr/configuring.texy index 8f7feb4060..316dd95a69 100644 --- a/nette/fr/configuring.texy +++ b/nette/fr/configuring.texy @@ -1,35 +1,36 @@ -Configuration du réseau -*********************** +Configuration de Nette +********************** .[perex] -Un aperçu de toutes les options de configuration de Nette Framework. +Aperçu de toutes les options de configuration dans Nette Framework. -Les composants Nette sont configurés à l'aide de fichiers de configuration, qui sont généralement écrits en [NEON |neon:format]. Il est préférable de les éditer dans des [éditeurs qui le supportent |best-practices:editors-and-tools#ide-editor]. -Si vous utilisez le framework complet, la configuration sera [chargée lors du démarrage |application:bootstrap#di-container-configuration], sinon, voyez [comment charger la configuration |bootstrap:]. +Nous configurons les composants Nette à l'aide de fichiers de configuration, qui sont généralement écrits au [format NEON|neon:format]. Il est préférable de les éditer dans des [éditeurs le supportant |best-practices:editors-and-tools#Éditeur IDE]. Si vous utilisez l'ensemble du framework, la configuration est [chargée lors du démarrage de l'application |application:bootstrapping#Configuration du Conteneur DI], sinon, lisez [comment charger la configuration|bootstrap:]. <pre> "application .[prism-token prism-atrule]":[application:configuration#Application]: "Application .[prism-token prism-comment]"<br> -"constants .[prism-token prism-atrule]":[application:configuration#Constants]: "Définit les constantes PHP .[prism-token prism-comment]"<br> +"assets .[prism-token prism-atrule]":[assets:configuration]: "Assets .[prism-token prism-comment]"<br> +"constants .[prism-token prism-atrule]":[application:configuration#Constantes]: "Définition des constantes PHP .[prism-token prism-comment]"<br> "database .[prism-token prism-atrule]":[database:configuration]: "Base de données .[prism-token prism-comment]"<br> "decorator .[prism-token prism-atrule]":[dependency-injection:configuration#Decorator]: "Décorateur .[prism-token prism-comment]"<br> "di .[prism-token prism-atrule]":[dependency-injection:configuration#DI]: "Conteneur DI .[prism-token prism-comment]"<br> -"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Extensions]: "Installer des extensions DI supplémentaires .[prism-token prism-comment]"<br> +"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Extensions]: "Installation d'extensions DI supplémentaires .[prism-token prism-comment]"<br> "forms .[prism-token prism-atrule]":[forms:configuration]: "Formulaires .[prism-token prism-comment]"<br> -"http .[prism-token prism-atrule]":[http:configuration#HTTP Headers]: "En-têtes HTTP .[prism-token prism-comment]"<br> -"includes .[prism-token prism-atrule]":[dependency-injection:configuration#Including files]: "Incluant les fichiers .[prism-token prism-comment]"<br> -"latte .[prism-token prism-atrule]":[application:configuration#Latte]: "Latte .[prism-token prism-comment]"<br> -"mail .[prism-token prism-atrule]":[mail:#Configuring]: "Courrier électronique .[prism-token prism-comment]"<br> -"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#Parameters]: "Paramètres .[prism-token prism-comment]"<br> -"php .[prism-token prism-atrule]":[application:configuration#PHP]: "Options de configuration de PHP .[prism-token prism-comment]"<br> -"routing .[prism-token prism-atrule]":[application:configuration#Routing]: "Routage .[prism-token prism-comment]"<br> +"http .[prism-token prism-atrule]":[http:configuration#En-têtes HTTP]: "En-têtes HTTP .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[dependency-injection:configuration#Inclusion de fichiers]: "Inclusion de fichiers .[prism-token prism-comment]"<br> +"latte .[prism-token prism-atrule]":[application:configuration#Templates Latte]: "Templates Latte .[prism-token prism-comment]"<br> +"mail .[prism-token prism-atrule]":[mail:#Configuration]: "Mails .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#Paramètres]: "Paramètres .[prism-token prism-comment]"<br> +"php .[prism-token prism-atrule]":[application:configuration#PHP]: "Configuration PHP .[prism-token prism-comment]"<br> +"routing .[prism-token prism-atrule]":[application:configuration#Routage]: "Routage .[prism-token prism-comment]"<br> "search .[prism-token prism-atrule]":[dependency-injection:configuration#Search]: "Enregistrement automatique des services .[prism-token prism-comment]"<br> -"security .[prism-token prism-atrule]":[security:configuration]: "Contrôle d'accès .[prism-token prism-comment]"<br> +"security .[prism-token prism-atrule]":[security:configuration]: "Permissions d'accès .[prism-token prism-comment]"<br> "services .[prism-token prism-atrule]":[dependency-injection:services]: "Services .[prism-token prism-comment]"<br> "session .[prism-token prism-atrule]":[http:configuration#Session]: "Session .[prism-token prism-comment]"<br> "tracy .[prism-token prism-atrule]":[tracy:configuring#Nette Framework]: "Débogueur Tracy .[prism-token prism-comment]" </pre> -Pour écrire une chaîne contenant le caractère `%`, you must escape it by doubling it to `%%`. .[note] +.[note] +Pour écrire une chaîne contenant le caractère `%`, vous devez l'échapper en le doublant en `%%`. {{leftbar: @menu-topics}} diff --git a/nette/fr/glossary.texy b/nette/fr/glossary.texy index 7a20fc561f..a049378c3f 100644 --- a/nette/fr/glossary.texy +++ b/nette/fr/glossary.texy @@ -2,154 +2,158 @@ Glossaire des termes ******************** -AJAX .[#toc-ajax] ------------------ -Asynchronous JavaScript and XML - technologie de communication client-serveur via le protocole HTTP sans qu'il soit nécessaire de recharger la page entière à chaque demande. Malgré l'acronyme, le format [JSON |#JSON] est souvent utilisé à la place de XML. +AJAX +---- +Asynchronous JavaScript and XML - technologie d'échange d'informations entre le client et le serveur via le protocole HTTP sans nécessiter le rechargement complet de la page à chaque requête. Bien que le nom puisse suggérer que les données sont envoyées uniquement au format XML, le format [#JSON] est également couramment utilisé. -Action du présentateur .[#toc-presenter-action] ------------------------------------------------ -Partie logique du [présentateur |#presenter], qui exécute une action, comme l'affichage d'une page de produit, la déconnexion d'un utilisateur, etc. Un présentateur peut avoir plusieurs actions. +Action (presenter) +------------------ +Partie logique du presenter qui exécute une action. Par exemple, afficher la page d'un produit, déconnecter l'utilisateur, etc. Un presenter peut avoir plusieurs actions. BOM --- -Le *masque d'ordre des octets* est un premier caractère spécial d'un fichier et indique l'ordre des octets dans l'encodage. Certains éditeurs l'incluent automatiquement, il est pratiquement invisible, mais il cause des problèmes avec les en-têtes et l'envoi de sortie depuis PHP. Vous pouvez utiliser [Code Checker |code-checker:] pour le supprimer en masse. +Le *byte order mark* est un caractère spécial au début d'un fichier, utilisé comme indicateur de l'ordre des octets dans l'encodage. Certains éditeurs l'insèrent dans les fichiers. Il est pratiquement invisible, mais cause des problèmes avec l'envoi de la sortie et des en-têtes depuis PHP. Pour une suppression en masse, vous pouvez utiliser [Code Checker|code-checker:]. -Contrôleur .[#toc-controller] ------------------------------ -Le contrôleur traite les demandes de l'utilisateur et sur la base de celles-ci, il appelle une logique d'application particulière (c'est-à-dire [le modèle |#model]), puis il appelle la [vue |#view] pour le rendu des données. Les [présentateurs de |#presenter] Nette Framework sont des analogues des contrôleurs. +Controller +---------- +Contrôleur qui traite les requêtes de l'utilisateur et, sur cette base, appelle la logique applicative appropriée (c'est-à-dire le [#modèle]) puis demande à la [#vue] d'afficher les données. L'équivalent des contrôleurs dans Nette Framework sont les [presenters |#Presenter]. -Cross-Site Scripting (XSS) .[#toc-cross-site-scripting-xss] ------------------------------------------------------------ -Le Cross-Site Scripting est une méthode de perturbation des sites utilisant des entrées non codées. Un attaquant peut injecter son propre code HTML ou JavaScript et modifier l'apparence de la page, voire recueillir des informations sensibles sur les utilisateurs. La protection contre le XSS est simple : l'échappement cohérent et correct de toutes les chaînes et entrées. +Cross-Site Scripting (XSS) +-------------------------- +Le Cross-Site Scripting est une méthode de violation des pages web exploitant des sorties non traitées. L'attaquant peut alors injecter son propre code dans la page et ainsi la modifier ou même obtenir des données sensibles sur les visiteurs. On ne peut se défendre contre le XSS qu'en traitant de manière cohérente et correcte toutes les chaînes. -Nette Framework propose une toute nouvelle technologie d'[échappement en fonction du contexte |latte:safety-first#context-aware-escaping], qui vous permettra de vous débarrasser définitivement des risques liés aux scripts intersites. Il échappe automatiquement toutes les entrées en fonction d'un contexte donné, de sorte qu'il est impossible pour un codeur d'oublier accidentellement quelque chose. +Nette Framework introduit une technologie révolutionnaire [d'Échappement sensible au contexte |latte:safety-first#Échappement contextuel], qui vous débarrasse à jamais du risque de Cross-Site Scripting. Il traite automatiquement toutes les sorties, de sorte qu'il est impossible qu'un codeur oublie quelque chose. -Falsification de requête intersite (CSRF) .[#toc-cross-site-request-forgery-csrf] ---------------------------------------------------------------------------------- -Une attaque de type Cross-Site Request Forgery consiste pour l'attaquant à inciter la victime à visiter une page qui exécute silencieusement une requête dans le navigateur de la victime vers le serveur où la victime est actuellement connectée, et le serveur croit que la requête a été faite par la victime à son gré. Le serveur exécute une certaine action sous l'identité de la victime mais sans que celle-ci s'en rende compte. Il peut s'agir de modifier ou de supprimer des données, d'envoyer un message, etc. +Cross-Site Request Forgery (CSRF) +--------------------------------- +L'attaque Cross-Site Request Forgery consiste pour l'attaquant à attirer la victime sur une page qui exécute discrètement dans le navigateur de la victime une requête vers le serveur sur lequel la victime est connectée, et le serveur croit que la requête a été exécutée par la victime de sa propre volonté. Ainsi, sous l'identité de la victime, il effectue une certaine action sans que celle-ci le sache. Il peut s'agir de modifier ou de supprimer des données, d'envoyer un message, etc. -Nette Framework **protège automatiquement les formulaires et les signaux des présentateurs** de ce type d'attaque. Cela se fait en empêchant qu'ils soient envoyés ou appelés depuis un autre domaine. +Nette Framework **protège automatiquement les formulaires et les signaux dans les presenters** contre ce type d'attaque. Et ce, en empêchant leur envoi ou leur déclenchement depuis un autre domaine. -Injection de dépendances .[#toc-dependency-injection] ------------------------------------------------------ -L'injection de dépendances (DI) est un modèle de conception qui vous indique comment séparer la création d'objets de leurs dépendances. Autrement dit, une classe n'est pas responsable de la création ou de l'initialisation de ses dépendances, mais ces dépendances sont fournies par du code externe (qui peut inclure un [conteneur DI |#Dependency Injection container]). L'avantage est que cela permet une plus grande flexibilité du code, une meilleure lisibilité et des tests d'application plus faciles car les dépendances sont facilement remplaçables et isolées des autres parties du code. Pour plus d'informations, voir [Qu'est-ce que l'injection de dépendances ? |dependency-injection:introduction] +Injection de Dépendances +------------------------ +L'Injection de Dépendances (DI) est un patron de conception qui explique comment séparer la création d'objets de leurs dépendances. C'est-à-dire que la classe n'est pas responsable de la création ou de l'initialisation de ses dépendances, mais que ces dépendances lui sont fournies par du code externe (cela peut aussi être un [conteneur DI |#Conteneur d Injection de Dépendances]). L'avantage est qu'il permet une plus grande flexibilité du code, une meilleure compréhension et facilite les tests de l'application, car les dépendances sont facilement remplaçables et isolées des autres parties du code. Plus d'informations dans le chapitre [Qu'est-ce que l'Injection de Dépendances ? |dependency-injection:introduction] -Conteneur d'injection de dépendances .[#toc-dependency-injection-container] ---------------------------------------------------------------------------- -Un conteneur d'injection de dépendances (également appelé conteneur DI ou conteneur IoC) est un outil qui gère la création et la gestion des dépendances dans une application (ou des [services |#service]). Un conteneur possède généralement une configuration qui définit les classes qui dépendent d'autres classes, les implémentations de dépendances spécifiques à utiliser et la manière de créer ces dépendances. Le conteneur crée ensuite ces objets et les fournit aux classes qui en ont besoin. Pour plus d'informations, voir [Qu'est-ce qu'un conteneur DI ? |dependency-injection:container] +Conteneur d'Injection de Dépendances +------------------------------------ +Le conteneur d'Injection de Dépendances (également conteneur DI ou conteneur IoC) est un outil qui gère la création et la gestion des dépendances dans l'application (ou [services |#Service]). Le conteneur a généralement une configuration qui définit quelles classes dépendent d'autres classes, quelles implémentations spécifiques des dépendances doivent être utilisées et comment ces dépendances doivent être créées. Ensuite, le conteneur crée ces objets et les fournit aux classes qui en ont besoin. Plus d'informations dans le chapitre [Qu'est-ce qu'un conteneur DI ? |dependency-injection:container] -Échapper à .[#toc-escaping] ---------------------------- -L'échappement est la conversion des caractères ayant une signification particulière dans un contexte donné en une autre séquence équivalente. Exemple : Nous voulons écrire des guillemets dans une chaîne entre guillemets. Comme les guillemets ont une signification particulière dans le contexte de la chaîne entre guillemets, il est nécessaire d'utiliser une autre séquence équivalente. La séquence concrète est déterminée par les règles du contexte (par exemple, `\"` dans la chaîne entre guillemets de PHP, `"` dans les attributs HTML, etc.) +Échappement +----------- +L'échappement est la conversion de caractères ayant une signification spéciale dans un contexte donné en d'autres séquences correspondantes. Exemple : nous voulons écrire des guillemets dans une chaîne délimitée par des guillemets. Comme les guillemets ont une signification spéciale dans le contexte de la chaîne et que leur simple écriture serait comprise comme la fin de la chaîne, il est nécessaire de les écrire avec une autre séquence correspondante. Laquelle exactement est déterminée par les règles du contexte. -Filtre (anciennement Helper) .[#toc-filter-formerly-helper] ------------------------------------------------------------ -Fonction de filtrage. Dans les modèles, le [filtre |latte:syntax#filters] est une fonction qui aide à modifier ou à formater les données dans le formulaire de sortie. Les modèles ont plusieurs [filtres standard |latte:filters] prédéfinis. +Filtre (anciennement helper) +---------------------------- +Dans les templates, le terme [filtre |latte:syntax#Filtres] désigne généralement une fonction qui aide à modifier ou à reformater les données dans leur forme finale. Les templates disposent de plusieurs [filtres standard |latte:filters]. -Invalidation .[#toc-invalidation] ---------------------------------- -Avis d'un [snippet |#snippet] à rendre à nouveau. Dans d'autres contextes, il s'agit également de l'effacement d'un cache. +Invalidation +------------ +Notification au [#snippet] pour qu'il se redessine. Dans un autre sens, également la suppression du contenu du cache. -JSON .[#toc-json] ------------------ -Format d'échange de données basé sur la syntaxe JavaScript (c'est son sous-ensemble). La spécification exacte se trouve sur www.json.org. +JSON +---- +Format d'échange de données basé sur la syntaxe JavaScript (il en est un sous-ensemble). La spécification exacte se trouve sur la page www.json.org. -Composant .[#toc-component] ---------------------------- -Partie réutilisable d'une application. Il peut s'agir d'une partie visuelle d'une page, comme décrit dans le chapitre [application:components], ou le terme peut également désigner la classe [Component |component-model:] (un tel composant ne doit pas nécessairement être visuel). +Composant +--------- +Partie réutilisable de l'application. Il peut s'agir d'une partie visuelle de la page, comme décrit dans le chapitre [Écrire des composants |application:components], ou le terme composant désigne également la classe [Component |component-model:] (un tel composant n'est pas nécessairement visuel). -Caractères de contrôle .[#toc-control-characters] -------------------------------------------------- -Les caractères de contrôle sont des caractères invisibles, qui peuvent apparaître dans un texte et éventuellement causer des problèmes. Pour les supprimer en masse des fichiers, vous pouvez utiliser [Code Checker |code-checker:], pour les supprimer d'une variable, utilisez la fonction [Strings::normalize() |utils:strings#normalize]. +Caractères de contrôle +---------------------- +Les caractères de contrôle sont des caractères invisibles qui peuvent apparaître dans le texte et éventuellement causer des problèmes. Pour leur suppression en masse des fichiers, vous pouvez utiliser [Code Checker|code-checker:] et pour la suppression d'une variable, la fonction [Strings::normalize() |utils:strings#normalize]. -Événements .[#toc-events] -------------------------- -Un événement est une situation attendue dans l'objet qui, lorsqu'elle se produit, appelle ce que l'on appelle les "handlers", c'est-à-dire les callbacks qui réagissent à l'événement ("sample":https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38). L'événement peut être, par exemple, la soumission d'un formulaire, la connexion d'un utilisateur, etc. Les événements sont donc une forme d'"inversion de contrôle". +Événements (events) +------------------- +Un événement est une situation attendue dans un objet, qui, lorsqu'elle se produit, appelle ce qu'on appelle des handlers, c'est-à-dire des callbacks réagissant à l'événement ("démonstration":https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38). Un événement peut être par exemple l'envoi d'un formulaire, la connexion d'un utilisateur, etc. Les événements sont ainsi une forme d'*Inversion of Control*. -Par exemple, une connexion utilisateur se produit dans la méthode `Nette\Security\User::login()`. L'objet `User` possède une variable publique `$onLoggedIn`, qui est un tableau auquel chacun peut ajouter une fonction de rappel. Dès que l'utilisateur se connecte, la méthode `login()` appelle toutes les callbacks du tableau. Le nom d'une variable sous la forme `onXyz` est une convention utilisée dans tout Nette. +Par exemple, la connexion de l'utilisateur se produit dans la méthode `Nette\Security\User::login()`. L'objet `User` a une variable publique `$onLoggedIn`, qui est un tableau auquel n'importe qui peut ajouter un callback. Au moment où l'utilisateur se connecte, la méthode `login()` appelle tous les callbacks du tableau. Le nom de variable de la forme `onXyz` est une convention utilisée dans tout Nette. -Latte .[#toc-latte] -------------------- -L'un des [systèmes de création de modèles |latte:] les plus innovants qui soient. +Latte +----- +L'un des [systèmes de templates |latte:] les plus avancés. -Modèle .[#toc-model] --------------------- -Le modèle représente les données et les fonctions de base de l'ensemble de l'application. Il comprend toute la logique de l'application (parfois aussi appelée "logique métier"). C'est le **M** de **M**VC ou MPV. Toute action de l'utilisateur (connexion, mise au panier, modification d'une valeur de la base de données) représente une action du modèle. +Modèle +------ +Le modèle est la base de données et surtout fonctionnelle de toute l'application. Il contient toute la logique applicative (on utilise aussi le terme logique métier). C'est le **M** de **M**VC ou MVP. Toute action de l'utilisateur (connexion, ajout d'un produit au panier, modification d'une valeur dans la base de données) représente une action du modèle. + +Le modèle gère son état interne et offre une interface fixe vers l'extérieur. En appelant les fonctions de cette interface, nous pouvons interroger ou modifier son état. Le modèle ne connaît pas l'existence de la [#vue] ou du [#controller]. -Le modèle gère son état interne et fournit une interface publique. En appelant cette interface, nous pouvons prendre ou changer son état. Le modèle ne connaît pas l'existence de la [vue |#view] ou du [contrôleur |#controller], le modèle est totalement indépendant d'eux. +Modèle-Vue-Contrôleur +--------------------- +Architecture logicielle née du besoin de séparer, dans les applications à interface graphique, le code de gestion ([#controller]) du code de la logique applicative ([#modèle]) et du code affichant les données ([#vue]). Cela rend l'application plus claire, facilite le développement futur et permet de tester les différentes parties séparément. -Modèle-Vue-Contrôleur .[#toc-model-view-controller] ---------------------------------------------------- -Architecture logicielle, apparue dans le développement d'applications GUI pour séparer le code de contrôle du flux ([contrôleur |#controller]) du code de la logique applicative ([modèle |#model]) et du code de rendu des données ([vue |#view]). De cette façon, le code est plus compréhensible, le développement futur est facilité et il est possible de tester séparément les différentes parties. +Modèle-Vue-Presenter +-------------------- +Architecture basée sur le [#Modèle-Vue-Contrôleur]. -Modèle-Vue-Présentateur .[#toc-model-view-presenter] ----------------------------------------------------- -Architecture basée sur le [Modèle-Vue-Contrôleur |#Model-View-Controller]. +Module +------ +Un module représente une partie logique de l'application. Dans une organisation typique, il s'agit d'un groupe de presenters et de templates qui traitent un domaine fonctionnel spécifique. Nous plaçons les modules dans des [répertoires séparés |application:directory-structure#Presenters et templates], comme par exemple `Front/`, `Admin/` ou `Shop/`. -Module .[#toc-module] ---------------------- -Le [module |application:modules] dans Nette Framework représente une collection de présentateurs et de modèles, éventuellement aussi des composants et des modèles, qui servent des données à un présentateur. Il s'agit donc d'une partie logique certaine d'une application. +Par exemple, une boutique en ligne sera divisée en : +- Frontend (`Shop/`) pour la consultation des produits et l'achat +- Espace client (`Customer/`) pour la gestion des commandes +- Administration (`Admin/`) pour l'opérateur -Par exemple, une boutique en ligne peut avoir trois modules : -1) Catalogue de produits avec panier. -2) Administration pour le client. -3) Administration pour le commerçant. +Techniquement, ce sont des répertoires courants, mais grâce à une structure claire, ils aident à faire évoluer l'application. Le presenter `Admin:Product:List` sera ainsi physiquement situé par exemple dans le répertoire `app/Presentation/Admin/Product/List/` (voir [mapping des presenters |application:directory-structure#Mapping des presenters]). -Espace de nommage .[#toc-namespace] ------------------------------------ -L'espace de noms est une fonctionnalité du langage PHP depuis sa version 5.3 et de certains autres langages de programmation. Il permet d'éviter les collisions de noms (par exemple, deux classes avec le même nom) lors de l'utilisation conjointe de différentes bibliothèques. Consultez la [documentation de PHP |https://www.php.net/manual/en/language.namespaces.rationale.php] pour plus de détails. +Namespace +--------- +Espace de noms, partie du langage PHP depuis la version 5.3 et de certains autres langages de programmation, permettant d'utiliser des classes nommées de la même manière dans différentes bibliothèques sans conflit de noms. Voir la [documentation PHP |https://www.php.net/manual/en/language.namespaces.rationale.php]. -Présentateur .[#toc-presenter] ------------------------------- -Le Presenter est un objet qui prend la [requête |api:Nette\Application\Request] traduite par le routeur à partir de la requête HTTP et génère une [réponse |api:Nette\Application\Response]. La réponse peut être une page HTML, une image, un document XML, un fichier, JSON, une redirection ou tout autre élément auquel vous pensez. +Presenter +--------- +Un presenter est un objet qui prend la [requête |api:Nette\Application\Request] traduite par le routeur à partir de la requête HTTP et génère une [réponse |api:Nette\Application\Response]. La réponse peut être une page HTML, une image, un document XML, un fichier sur le disque, JSON, une redirection ou tout ce que vous pouvez imaginer. -Par présentateur, on entend généralement un descendant de la classe [api:Nette\Application\UI\Presenter]. Par demande, il exécute les [actions |application:presenters#life-cycle-of-presenter] appropriées et rend les modèles. +Généralement, le terme presenter désigne un descendant de la classe [api:Nette\Application\UI\Presenter]. Selon les requêtes entrantes, il exécute les [actions |application:presenters#Cycle de vie du presenter] correspondantes et rend les templates. -Routeur .[#toc-router] ----------------------- -Traducteur bidirectionnel entre la demande HTTP / URL et l'action du présentateur. Bi-directionnel signifie qu'il est non seulement possible de dériver une [action du présentateur |#presenter action] à partir de la demande HTTP, mais aussi de générer l'URL appropriée pour une action. Pour en savoir plus, consultez le chapitre sur le [routage des URL |application:routing]. +Routeur +------- +Traducteur bidirectionnel entre une requête HTTP / URL et une action de presenter. Bidirectionnel signifie qu'à partir d'une requête HTTP, on peut déduire l'[action du presenter |#Action presenter], mais aussi inversement, générer l'URL correspondante pour une action. Plus d'informations dans le chapitre sur le [routage d'URL |application:routing]. -Cookie SameSite .[#toc-samesite-cookie] ---------------------------------------- -Les cookies SameSite fournissent un mécanisme permettant de reconnaître ce qui a conduit au chargement de la page. Il peut avoir trois valeurs : `Lax`, `Strict` et `None` (cette dernière nécessite HTTPS). Si la demande de la page provient directement du site ou si l'utilisateur ouvre la page en la tapant directement dans la barre d'adresse ou en cliquant sur un signet, le navigateur envoie tous les cookies au serveur (c'est-à-dire avec les drapeaux `Lax`, `Strict` et `None`). Si l'utilisateur clique sur le site via un lien provenant d'un autre site, les cookies avec les drapeaux `Lax` et `None` sont transmis au serveur. Si la demande est effectuée par d'autres moyens, comme l'envoi d'un formulaire POST à partir d'un autre site, le chargement à l'intérieur d'une iframe, l'utilisation de JavaScript, etc., seuls les cookies avec le drapeau `None` sont envoyés. +Cookie SameSite +--------------- +Les cookies SameSite fournissent un mécanisme pour reconnaître ce qui a conduit au chargement de la page. Il peut avoir trois valeurs : `Lax`, `Strict` et `None` (ce dernier nécessite HTTPS). Si la requête de page provient directement du site ou si l'utilisateur ouvre la page en la saisissant directement dans la barre d'adresse ou en cliquant sur un signet, le navigateur envoie tous les cookies au serveur (c'est-à-dire avec les indicateurs `Lax`, `Strict` et `None`). Si l'utilisateur accède au site via un lien depuis un autre site, les cookies avec les indicateurs `Lax` et `None` sont transmis au serveur. Si la requête est générée d'une autre manière, comme l'envoi d'un formulaire POST depuis un autre site, le chargement à l'intérieur d'un iframe, via JavaScript, etc., seuls les cookies avec l'indicateur `None` sont envoyés. -Service .[#toc-service] ------------------------ -Dans le contexte de l'injection de dépendances, un service fait référence à un objet qui est créé et géré par un conteneur DI. Un service peut facilement être remplacé par une autre implémentation, par exemple à des fins de test ou pour changer le comportement d'une application, sans avoir à modifier le code qui utilise le service. +Service +------- +Dans le contexte de l'Injection de Dépendances, un service désigne un objet qui est créé et géré par le conteneur DI. Un service peut être facilement remplacé par une autre implémentation, par exemple à des fins de test ou pour modifier le comportement de l'application, sans qu'il soit nécessaire de modifier le code qui utilise le service. -Extrait .[#toc-snippet] ------------------------ -Extrait d'une page, qui peut être rendu séparément lors d'une requête [AJAX |#AJAX]. +Snippet +------- +Extrait, partie de la page qui peut être redessinée indépendamment lors d'une requête AJAX. + + +Vue +--- +La vue est la couche de l'application chargée d'afficher le résultat de la requête. Elle utilise généralement un système de templates et sait comment afficher tel ou tel composant ou le résultat obtenu du modèle. -Voir .[#toc-view] ------------------ -La vue est une couche de l'application qui est responsable du rendu des résultats de la demande. Habituellement, elle utilise un système de templating et sait comment rendre ses composants ou les résultats pris dans le modèle. diff --git a/nette/fr/installation.texy b/nette/fr/installation.texy index 76b5e3fdd8..a2999bba8a 100644 --- a/nette/fr/installation.texy +++ b/nette/fr/installation.texy @@ -2,66 +2,66 @@ Installation de Nette ********************* .[perex] -Vous souhaitez exploiter les avantages de Nette dans votre projet existant ou vous envisagez de créer un nouveau projet basé sur Nette ? Ce guide vous guidera pas à pas dans l'installation. +Voulez-vous profiter des avantages de Nette dans votre projet existant, ou allez-vous créer un nouveau projet basé sur Nette ? Ce guide vous accompagnera pas à pas dans l'installation. -Comment ajouter Nette à votre projet .[#toc-how-to-add-nette-to-your-project] ------------------------------------------------------------------------------ +Comment ajouter Nette à votre projet +------------------------------------ -Nette propose une collection de packages (bibliothèques) utiles et sophistiqués pour PHP. Pour les intégrer à votre projet, suivez les étapes suivantes : +Nette propose une collection de paquets (bibliothèques) PHP utiles et matures. Pour les intégrer à votre projet, procédez comme suit : -1) **Cet outil [Composer|best-practices:composer]:** est essentiel pour faciliter l'installation, la mise à jour et la gestion des bibliothèques nécessaires à votre projet. +1) **Préparez [Composer|best-practices:composer] :** Cet outil est essentiel pour installer, mettre à jour et gérer facilement les bibliothèques nécessaires à votre projet. -2) **Choisir un [paquetage |www:packages]:** Supposons que vous ayez besoin de naviguer dans le système de fichiers, ce que [Finder |utils:finder] du paquetage `nette/utils` fait à merveille. Vous trouverez le nom du paquet dans la colonne de droite de sa documentation. +2) **Choisissez un [paquet|www:packages] :** Supposons que vous ayez besoin de parcourir le système de fichiers, ce que [Finder|utils:finder] du paquet `nette/utils` fait très bien. Vous pouvez voir le nom du paquet dans la colonne de droite de sa documentation. -3) **Installer le paquetage:** Exécutez cette commande dans le répertoire racine de votre projet : +3) **Installez le paquet :** Exécutez cette commande dans le répertoire racine de votre projet : ```shell composer require nette/utils ``` -Vous préférez une interface graphique ? Consultez le [guide d' |https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] installation des paquets dans l'environnement PhpStrom. +Vous préférez une interface graphique ? Consultez le [guide|https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] pour installer des paquets dans l'environnement PhpStorm. -Comment démarrer un nouveau projet avec Nette .[#toc-how-to-start-a-new-project-with-nette] -------------------------------------------------------------------------------------------- +Comment démarrer un nouveau projet avec Nette +--------------------------------------------- -Si vous souhaitez créer un tout nouveau projet sur la plateforme Nette, nous vous recommandons d'utiliser le squelette prédéfini de [projet Web |https://github.com/nette/web-project]: +Si vous souhaitez créer un tout nouveau projet sur la plateforme Nette, nous vous recommandons d'utiliser le squelette prédéfini [Web Project|https://github.com/nette/web-project] : -1) **Set up [Composer |best-practices:composer].** +1) **Préparez [Composer|best-practices:composer].** -2) **Ouvrez la ligne de commande** et naviguez jusqu'au répertoire racine de votre serveur web, par exemple, `/etc/var/www`, `C:/xampp/htdocs`, `/Library/WebServer/Documents`. +2) **Ouvrez la ligne de commande** et accédez au répertoire racine de votre serveur web, par exemple `/etc/var/www`, `C:/xampp/htdocs`, `/Library/WebServer/Documents`. 3) **Créez le projet** à l'aide de cette commande : ```shell -composer create-project nette/web-project PROJECT_NAME +composer create-project nette/web-project NOM_DU_PROJET ``` -4) **Ne pas utiliser Composer?** Téléchargez simplement le [projet Web au format ZIP |https://github.com/nette/web-project/archive/preloaded.zip] et extrayez-le. Mais croyez-nous, Composer en vaut la peine ! +4) **Vous n'utilisez pas Composer ?** Il suffit de télécharger [Web Project au format ZIP|https://github.com/nette/web-project/archive/preloaded.zip] et de le décompresser. Mais croyez-nous, Composer en vaut la peine ! -5) **Définir les permissions:** Sur les systèmes macOS ou Linux, définissez les [permissions d'écriture |nette:troubleshooting#Setting directory permissions] pour les répertoires. +5) **Configuration des permissions :** Sur les systèmes macOS ou Linux, définissez les [permissions d'écriture |nette:troubleshooting#Configuration des permissions de répertoire] pour les répertoires. -6) **Ouvrir le projet dans un navigateur:** Entrez l'URL `http://localhost/PROJECT_NAME/www/`. Vous verrez la page d'accueil du squelette : +6) **Ouvrez le projet dans le navigateur :** Saisissez l'URL `http://localhost/NOM_DU_PROJET/www/` et vous verrez la page d'accueil du squelette : -[* qs-welcome.webp .{url: http://localhost/PROJECT_NAME/www/} *] +[* qs-welcome.webp .{url: http://localhost/NOM_DU_PROJET/www/} *] -Félicitations ! Votre site web est maintenant prêt à être développé. N'hésitez pas à supprimer le modèle de bienvenue et à commencer à construire votre application. +Félicitations ! Votre site est maintenant prêt pour le développement. Vous pouvez supprimer le template de bienvenue et commencer à créer votre application. -L'un des avantages de Nette est que le projet fonctionne immédiatement sans nécessiter de configuration. Toutefois, si vous rencontrez des problèmes, pensez à consulter les [solutions aux problèmes courants |nette:troubleshooting#nette-is-not-working-white-page-is-displayed]. +L'un des avantages de Nette est que le projet fonctionne immédiatement sans nécessiter de configuration. Cependant, si vous rencontrez des problèmes, essayez de consulter les [solutions aux problèmes courants |nette:troubleshooting#Nette ne fonctionne pas une page blanche s affiche]. .[note] -Si vous débutez avec Nette, nous vous recommandons de poursuivre avec le [tutoriel Créer votre première application |quickstart:]. +Si vous débutez avec Nette, nous vous recommandons de poursuivre avec le [tutoriel Écrire votre première application|quickstart:]. -Outils et recommandations .[#toc-tools-and-recommendations] ------------------------------------------------------------ +Outils et recommandations +------------------------- -Pour un travail efficace avec Nette, nous recommandons les outils suivants : +Pour travailler efficacement avec Nette, nous recommandons les outils suivants : -- [IDE de haute qualité avec des plugins pour Nette |best-practices:editors-and-tools] -- Système de contrôle de version Git -- [Compositeur |best-practices:composer] +- [IDE de qualité avec des plugins pour Nette|best-practices:editors-and-tools] +- Système de gestion de version Git +- [Composer|best-practices:composer] {{leftbar: www:@menu-common}} diff --git a/nette/fr/introduction-to-object-oriented-programming.texy b/nette/fr/introduction-to-object-oriented-programming.texy new file mode 100644 index 0000000000..83ed9e9d99 --- /dev/null +++ b/nette/fr/introduction-to-object-oriented-programming.texy @@ -0,0 +1,841 @@ +Introduction à la programmation orientée objet +********************************************** + +.[perex] +Le terme "POO" désigne la programmation orientée objet, qui est une manière d'organiser et de structurer le code. La POO nous permet de voir un programme comme un ensemble d'objets qui communiquent entre eux, plutôt qu'une séquence d'instructions et de fonctions. + +En POO, un "objet" est une unité qui contient des données et des fonctions qui travaillent avec ces données. Les objets sont créés à partir de "classes", que l'on peut comprendre comme des plans ou des modèles pour les objets. Lorsque nous avons une classe, nous pouvons créer son "instance", qui est un objet concret créé selon cette classe. + +Voyons comment nous pouvons créer une classe simple en PHP. Lors de la définition d'une classe, nous utilisons le mot-clé "class", suivi du nom de la classe, puis des accolades qui entourent les fonctions (appelées "méthodes") et les variables de la classe (appelées "propriétés") : + +```php +class Voiture +{ + function klaxonner() + { + echo 'Bip bip!'; + } +} +``` + +Dans cet exemple, nous avons créé une classe nommée `Voiture` avec une fonction (ou "méthode") appelée `klaxonner`. + +Chaque classe ne devrait traiter qu'une seule tâche principale. Si une classe fait trop de choses, il peut être judicieux de la diviser en classes plus petites et spécialisées. + +Les classes sont généralement stockées dans des fichiers séparés pour que le code soit organisé et facile à naviguer. Le nom du fichier doit correspondre au nom de la classe, donc pour la classe `Voiture`, le nom du fichier serait `Voiture.php`. + +Lors de la dénomination des classes, il est bon de suivre la convention "PascalCase", ce qui signifie que chaque mot du nom commence par une majuscule et qu'il n'y a pas de traits de soulignement ou d'autres séparateurs entre eux. Les méthodes et les propriétés utilisent la convention "camelCase", ce qui signifie qu'elles commencent par une lettre minuscule. + +Certaines méthodes en PHP ont des rôles spéciaux et sont préfixées par `__` (deux traits de soulignement). L'une des méthodes spéciales les plus importantes est le "constructeur", qui est désigné par `__construct`. Le constructeur est une méthode qui est automatiquement appelée lorsque vous créez une nouvelle instance de la classe. + +Nous utilisons souvent le constructeur pour définir l'état initial de l'objet. Par exemple, lorsque vous créez un objet représentant une personne, vous pouvez utiliser le constructeur pour définir son âge, son nom ou d'autres propriétés. + +Voyons comment utiliser un constructeur en PHP : + +```php +class Personne +{ + private $age; + + function __construct($age) + { + $this->age = $age; + } + + function quelAgeAsTu() + { + return $this->age; + } +} + +$personne = new Personne(25); +echo $personne->quelAgeAsTu(); // Affiche : 25 +``` + +Dans cet exemple, la classe `Personne` a une propriété (variable) `$age` et un constructeur qui définit cette propriété. La méthode `quelAgeAsTu()` permet ensuite d'accéder à l'âge de la personne. + +La pseudo-variable `$this` est utilisée à l'intérieur de la classe pour accéder aux propriétés et méthodes de l'objet. + +Le mot-clé `new` est utilisé pour créer une nouvelle instance de la classe. Dans l'exemple ci-dessus, nous avons créé une nouvelle personne âgée de 25 ans. + +Vous pouvez également définir des valeurs par défaut pour les paramètres du constructeur si elles ne sont pas spécifiées lors de la création de l'objet. Par exemple : + +```php +class Personne +{ + private $age; + + function __construct($age = 20) + { + $this->age = $age; + } + + function quelAgeAsTu() + { + return $this->age; + } +} + +$personne = new Personne; // si aucun argument n'est passé, les parenthèses peuvent être omises +echo $personne->quelAgeAsTu(); // Affiche : 20 +``` + +Dans cet exemple, si vous ne spécifiez pas l'âge lors de la création de l'objet `Personne`, la valeur par défaut 20 sera utilisée. + +Il est agréable de constater que la définition de la propriété avec son initialisation via le constructeur peut être ainsi raccourcie et simplifiée : + +```php +class Personne +{ + function __construct( + private $age = 20, + ) { + } +} +``` + +Pour être complet, en plus des constructeurs, les objets peuvent également avoir des destructeurs (méthode `__destruct`), qui sont appelés avant que l'objet ne soit libéré de la mémoire. + + +Espaces de noms +--------------- + +Les espaces de noms (ou "namespaces" en anglais) nous permettent d'organiser et de regrouper des classes, fonctions et constantes liées, tout en évitant les conflits de noms. Vous pouvez les imaginer comme des dossiers sur votre ordinateur, où chaque dossier contient des fichiers appartenant à un projet ou à un thème spécifique. + +Les espaces de noms sont particulièrement utiles dans les grands projets ou lorsque vous utilisez des bibliothèques tierces, où des conflits de noms de classes pourraient survenir. + +Imaginez que vous ayez une classe nommée `Voiture` dans votre projet et que vous souhaitiez la placer dans un espace de noms appelé `Transport`. Vous le feriez comme ceci : + +```php +namespace Transport; + +class Voiture +{ + function klaxonner() + { + echo 'Bip bip!'; + } +} +``` + +Si vous souhaitez utiliser la classe `Voiture` dans un autre fichier, vous devez spécifier de quel espace de noms la classe provient : + +```php +$voiture = new Transport\Voiture; +``` + +Pour simplifier, vous pouvez indiquer au début du fichier quelle classe de l'espace de noms donné vous souhaitez utiliser, ce qui permet de créer des instances sans avoir à spécifier le chemin complet : + +```php +use Transport\Voiture; + +$voiture = new Voiture; +``` + + +Héritage +-------- + +L'héritage est un outil de la programmation orientée objet qui permet de créer de nouvelles classes basées sur des classes existantes, d'hériter de leurs propriétés et méthodes, et de les étendre ou de les redéfinir selon les besoins. L'héritage permet d'assurer la réutilisabilité du code et une hiérarchie de classes. + +En termes simples, si nous avons une classe et que nous voulons en créer une autre, dérivée de celle-ci, mais avec quelques modifications, nous pouvons faire "hériter" la nouvelle classe de la classe d'origine. + +En PHP, l'héritage est réalisé à l'aide du mot-clé `extends`. + +Notre classe `Personne` stocke des informations sur l'âge. Nous pouvons avoir une autre classe `Etudiant`, qui étend `Personne` et ajoute des informations sur le domaine d'études. + +Regardons un exemple : + +```php +class Personne +{ + private $age; + + function __construct($age) + { + $this->age = $age; + } + + function afficherInformations() + { + echo "Âge : {$this->age} ans\n"; + } +} + +class Etudiant extends Personne +{ + private $domaine; + + function __construct($age, $domaine) + { + parent::__construct($age); + $this->domaine = $domaine; + } + + function afficherInformations() + { + parent::afficherInformations(); + echo "Domaine d'études : {$this->domaine} \n"; + } +} + +$etudiant = new Etudiant(20, 'Informatique'); +$etudiant->afficherInformations(); +``` + +Comment fonctionne ce code ? + +- Nous avons utilisé le mot-clé `extends` pour étendre la classe `Personne`, ce qui signifie que la classe `Etudiant` hérite de toutes les méthodes et propriétés de `Personne`. + +- Le mot-clé `parent::` nous permet d'appeler des méthodes de la classe parente. Dans ce cas, nous avons appelé le constructeur de la classe `Personne` avant d'ajouter notre propre fonctionnalité à la classe `Etudiant`. Et de même, la méthode `afficherInformations()` du parent avant d'afficher les informations sur l'étudiant. + +L'héritage est destiné aux situations où il existe une relation "est un" entre les classes. Par exemple, un `Etudiant` est une `Personne`. Un chat est un animal. Cela nous donne la possibilité, dans les cas où le code attend un objet (par exemple, "Personne"), d'utiliser à la place un objet hérité (par exemple, "Etudiant"). + +Il est important de noter que l'objectif principal de l'héritage **n'est pas** d'éviter la duplication de code. Au contraire, une utilisation incorrecte de l'héritage peut conduire à un code complexe et difficile à maintenir. Si la relation "est un" n'existe pas entre les classes, nous devrions envisager la composition au lieu de l'héritage. + +Notez que les méthodes `afficherInformations()` dans les classes `Personne` et `Etudiant` affichent des informations légèrement différentes. Et nous pouvons ajouter d'autres classes (par exemple, `Employe`), qui fourniront d'autres implémentations de cette méthode. La capacité des objets de différentes classes à réagir à la même méthode de différentes manières s'appelle le polymorphisme : + +```php +$personnes = [ + new Personne(30), + new Etudiant(20, 'Informatique'), + new Employe(45, 'Directeur'), +]; + +foreach ($personnes as $personne) { + $personne->afficherInformations(); +} +``` + + +Composition +----------- + +La composition est une technique où, au lieu d'hériter des propriétés et méthodes d'une autre classe, nous utilisons simplement son instance dans notre classe. Cela nous permet de combiner les fonctionnalités et les propriétés de plusieurs classes sans avoir à créer de structures d'héritage complexes. + +Regardons un exemple. Nous avons une classe `Moteur` et une classe `Voiture`. Au lieu de dire "Une Voiture est un Moteur", nous disons "Une Voiture a un Moteur", ce qui est une relation typique de composition. + +```php +class Moteur +{ + function demarrer() + { + echo 'Le moteur tourne.'; + } +} + +class Voiture +{ + private $moteur; + + function __construct() + { + $this->moteur = new Moteur; + } + + function demarrerVoiture() + { + $this->moteur->demarrer(); + echo 'La voiture est prête à rouler !'; + } +} + +$voiture = new Voiture; +$voiture->demarrerVoiture(); +``` + +Ici, `Voiture` n'a pas toutes les propriétés et méthodes de `Moteur`, mais elle y a accès via la propriété `$moteur`. + +L'avantage de la composition est une plus grande flexibilité dans la conception et une meilleure possibilité de modifications futures. + + +Visibilité +---------- + +En PHP, vous pouvez définir la "visibilité" pour les propriétés, méthodes et constantes d'une classe. La visibilité détermine d'où vous pouvez accéder à ces éléments. + +1. **Public :** Si un élément est marqué comme `public`, cela signifie que vous pouvez y accéder de n'importe où, même en dehors de la classe. + +2. **Protected :** Un élément marqué `protected` n'est accessible qu'à l'intérieur de la classe donnée et de tous ses descendants (classes qui héritent de cette classe). + +3. **Private :** Si un élément est `private`, vous ne pouvez y accéder que depuis l'intérieur de la classe où il a été défini. + +Si vous ne spécifiez pas de visibilité, PHP la définit automatiquement sur `public`. + +Regardons un exemple de code : + +```php +class ExempleVisibilite +{ + public $proprietePublique = 'Publique'; + protected $proprieteProtegee = 'Protégée'; + private $proprietePrivee = 'Privée'; + + public function afficherProprietes() + { + echo $this->proprietePublique; // Fonctionne + echo $this->proprieteProtegee; // Fonctionne + echo $this->proprietePrivee; // Fonctionne + } +} + +$objet = new ExempleVisibilite; +$objet->afficherProprietes(); +echo $objet->proprietePublique; // Fonctionne +// echo $objet->proprieteProtegee; // Génère une erreur +// echo $objet->proprietePrivee; // Génère une erreur +``` + +Continuons avec l'héritage de classe : + +```php +class ClasseEnfant extends ExempleVisibilite +{ + public function afficherProprietes() + { + echo $this->proprietePublique; // Fonctionne + echo $this->proprieteProtegee; // Fonctionne + // echo $this->proprietePrivee; // Génère une erreur + } +} +``` + +Dans ce cas, la méthode `afficherProprietes()` de la classe `ClasseEnfant` peut accéder aux propriétés publiques et protégées, mais ne peut pas accéder aux propriétés privées de la classe parente. + +Les données et les méthodes doivent être autant que possible cachées et accessibles uniquement via une interface définie. Cela vous permet de modifier l'implémentation interne de la classe sans affecter le reste du code. + + +Le mot-clé `final` +------------------ + +En PHP, nous pouvons utiliser le mot-clé `final` si nous voulons empêcher une classe, une méthode ou une constante d'être héritée ou redéfinie. Lorsque nous marquons une classe comme `final`, elle ne peut pas être étendue. Lorsque nous marquons une méthode comme `final`, elle ne peut pas être redéfinie dans une classe enfant. + +Savoir qu'une certaine classe ou méthode ne sera pas modifiée ultérieurement nous permet d'effectuer des modifications plus facilement, sans avoir à nous soucier des conflits potentiels. Par exemple, nous pouvons ajouter une nouvelle méthode sans craindre qu'un de ses descendants ait déjà une méthode du même nom, ce qui entraînerait une collision. Ou nous pouvons modifier les paramètres d'une méthode, car là encore, il n'y a aucun risque de provoquer une incohérence avec une méthode redéfinie dans un descendant. + +```php +final class ClasseFinale +{ +} + +// Le code suivant provoquera une erreur, car nous ne pouvons pas hériter d'une classe finale. +class EnfantClasseFinale extends ClasseFinale +{ +} +``` + +Dans cet exemple, la tentative d'hériter de la classe finale `ClasseFinale` provoquera une erreur. + + +Propriétés et méthodes statiques +-------------------------------- + +Lorsque nous parlons d'éléments "statiques" d'une classe en PHP, nous entendons des méthodes et des propriétés qui appartiennent à la classe elle-même, et non à une instance spécifique de cette classe. Cela signifie que vous n'avez pas besoin de créer une instance de la classe pour y accéder. Au lieu de cela, vous les appelez ou y accédez directement via le nom de la classe. + +Gardez à l'esprit que, puisque les éléments statiques appartiennent à la classe et non à ses instances, vous ne pouvez pas utiliser la pseudo-variable `$this` à l'intérieur des méthodes statiques. + +L'utilisation de propriétés statiques conduit à un [code confus plein d'embûches|dependency-injection:global-state], c'est pourquoi vous ne devriez jamais les utiliser et nous ne montrerons pas d'exemple d'utilisation ici. En revanche, les méthodes statiques sont utiles. Exemple d'utilisation : + +```php +class Calculatrice +{ + public static function addition($a, $b) + { + return $a + $b; + } + + public static function soustraction($a, $b) + { + return $a - $b; + } +} + +// Utilisation de la méthode statique sans créer d'instance de la classe +echo Calculatrice::addition(5, 3); // Résultat : 8 +echo Calculatrice::soustraction(5, 3); // Résultat : 2 +``` + +Dans cet exemple, nous avons créé une classe `Calculatrice` avec deux méthodes statiques. Nous pouvons appeler ces méthodes directement sans créer d'instance de la classe en utilisant l'opérateur `::`. Les méthodes statiques sont particulièrement utiles pour les opérations qui ne dépendent pas de l'état d'une instance spécifique de la classe. + + +Constantes de classe +-------------------- + +Au sein des classes, nous avons la possibilité de définir des constantes. Les constantes sont des valeurs qui ne changeront jamais pendant l'exécution du programme. Contrairement aux variables, la valeur d'une constante reste toujours la même. + +```php +class Voiture +{ + public const NombreDeRoues = 4; + + public function afficherNombreDeRoues(): int + { + echo self::NombreDeRoues; + } +} + +echo Voiture::NombreDeRoues; // Sortie : 4 +``` + +Dans cet exemple, nous avons une classe `Voiture` avec la constante `NombreDeRoues`. Lorsque nous voulons accéder à la constante à l'intérieur de la classe, nous pouvons utiliser le mot-clé `self` au lieu du nom de la classe. + + +Interfaces d'objet +------------------ + +Les interfaces d'objet fonctionnent comme des "contrats" pour les classes. Si une classe doit implémenter une interface d'objet, elle doit contenir toutes les méthodes définies par cette interface. C'est un excellent moyen de s'assurer que certaines classes respectent le même "contrat" ou la même structure. + +En PHP, une interface est définie avec le mot-clé `interface`. Toutes les méthodes définies dans une interface sont publiques (`public`). Lorsqu'une classe implémente une interface, elle utilise le mot-clé `implements`. + +```php +interface Animal +{ + function emettreSon(); +} + +class Chat implements Animal +{ + public function emettreSon() + { + echo 'Miaou'; + } +} + +$chat = new Chat; +$chat->emettreSon(); +``` + +Si une classe implémente une interface mais que toutes les méthodes attendues n'y sont pas définies, PHP générera une erreur. + +Une classe peut implémenter plusieurs interfaces à la fois, ce qui la différencie de l'héritage, où une classe ne peut hériter que d'une seule classe : + +```php +interface Gardien +{ + function garderMaison(); +} + +class Chien implements Animal, Gardien +{ + public function emettreSon() + { + echo 'Wouf'; + } + + public function garderMaison() + { + echo 'Le chien garde attentivement la maison'; + } +} +``` + + +Classes abstraites +------------------ + +Les classes abstraites servent de modèles de base pour d'autres classes, mais vous ne pouvez pas créer leurs instances directement. Elles contiennent une combinaison de méthodes complètes et de méthodes abstraites, qui n'ont pas de contenu défini. Les classes qui héritent de classes abstraites doivent fournir des définitions pour toutes les méthodes abstraites du parent. + +Pour définir une classe abstraite, nous utilisons le mot-clé `abstract`. + +```php +abstract class ClasseAbstraite +{ + public function methodeOrdinaire() + { + echo 'Ceci est une méthode ordinaire'; + } + + abstract public function methodeAbstraite(); +} + +class Enfant extends ClasseAbstraite +{ + public function methodeAbstraite() + { + echo 'Ceci est l\'implémentation de la méthode abstraite'; + } +} + +$instance = new Enfant; +$instance->methodeOrdinaire(); +$instance->methodeAbstraite(); +``` + +Dans cet exemple, nous avons une classe abstraite avec une méthode ordinaire et une méthode abstraite. Ensuite, nous avons une classe `Enfant` qui hérite de `ClasseAbstraite` et fournit une implémentation pour la méthode abstraite. + +Quelle est la différence entre les interfaces et les classes abstraites ? Les classes abstraites peuvent contenir à la fois des méthodes abstraites et concrètes, tandis que les interfaces définissent uniquement les méthodes qu'une classe doit implémenter, mais ne fournissent aucune implémentation. Une classe ne peut hériter que d'une seule classe abstraite, mais peut implémenter un nombre quelconque d'interfaces. + + +Contrôle de type +---------------- + +En programmation, il est très important d'être sûr que les données avec lesquelles nous travaillons sont du bon type. En PHP, nous avons des outils qui nous assurent cela. La vérification que les données ont le bon type s'appelle le "contrôle de type". + +Les types que nous pouvons rencontrer en PHP : + +1. **Types de base** : Incluent `int` (entiers), `float` (nombres décimaux), `bool` (valeurs booléennes), `string` (chaînes de caractères), `array` (tableaux) et `null`. +2. **Classes** : Si nous voulons qu'une valeur soit une instance d'une classe spécifique. +3. **Interfaces** : Définit un ensemble de méthodes qu'une classe doit implémenter. Une valeur qui satisfait une interface doit avoir ces méthodes. +4. **Types mixtes** : Nous pouvons spécifier qu'une variable peut avoir plusieurs types autorisés. +5. **Void** : Ce type spécial indique qu'une fonction ou une méthode ne retourne aucune valeur. + +Voyons comment modifier le code pour inclure les types : + +```php +class Personne +{ + private int $age; + + public function __construct(int $age) + { + $this->age = $age; + } + + public function afficherAge(): void + { + echo "Cette personne a {$this->age} ans."; + } +} + +/** + * Fonction qui accepte un objet de la classe Personne et affiche l'âge de la personne. + */ +function afficherAgePersonne(Personne $personne): void +{ + $personne->afficherAge(); +} +``` + +De cette manière, nous nous sommes assurés que notre code attend et travaille avec des données du bon type, ce qui nous aide à prévenir les erreurs potentielles. + +Certains types ne peuvent pas être écrits directement en PHP. Dans ce cas, ils sont indiqués dans un commentaire phpDoc, qui est un format standard pour documenter le code PHP commençant par `/**` et se terminant par `*/`. Il permet d'ajouter des descriptions de classes, de méthodes, etc. Et aussi d'indiquer des types complexes à l'aide d'annotations telles que `@var`, `@param` et `@return`. Ces types sont ensuite utilisés par les outils d'analyse statique de code, mais PHP lui-même ne les vérifie pas. + +```php +class Liste +{ + /** @var array<Personne> cette notation indique qu'il s'agit d'un tableau d'objets Personne */ + private array $personnes = []; + + public function ajouterPersonne(Personne $personne): void + { + $this->personnes[] = $personne; + } +} +``` + + +Comparaison et identité +----------------------- + +En PHP, vous pouvez comparer des objets de deux manières : + +1. Comparaison de valeurs `==` : Vérifie si les objets sont de la même classe et ont les mêmes valeurs dans leurs propriétés. +2. Identité `===` : Vérifie s'il s'agit de la même instance d'objet. + +```php +class Voiture +{ + public string $marque; + + public function __construct(string $marque) + { + $this->marque = $marque; + } +} + +$voiture1 = new Voiture('Skoda'); +$voiture2 = new Voiture('Skoda'); +$voiture3 = $voiture1; + +var_dump($voiture1 == $voiture2); // true, car ils ont la même valeur +var_dump($voiture1 === $voiture2); // false, car ce ne sont pas la même instance +var_dump($voiture1 === $voiture3); // true, car $voiture3 est la même instance que $voiture1 +``` + + +L'opérateur `instanceof` +------------------------ + +L'opérateur `instanceof` permet de déterminer si un objet donné est une instance d'une certaine classe, d'un descendant de cette classe, ou s'il implémente une certaine interface. + +Imaginons que nous ayons une classe `Personne` et une autre classe `Etudiant`, qui est un descendant de la classe `Personne` : + +```php +class Personne +{ + private int $age; + + public function __construct(int $age) + { + $this->age = $age; + } +} + +class Etudiant extends Personne +{ + private string $domaine; + + public function __construct(int $age, string $domaine) + { + parent::__construct($age); + $this->domaine = $domaine; + } +} + +$etudiant = new Etudiant(20, 'Informatique'); + +// Vérification si $etudiant est une instance de la classe Etudiant +var_dump($etudiant instanceof Etudiant); // Sortie : bool(true) + +// Vérification si $etudiant est une instance de la classe Personne (car Etudiant est un descendant de Personne) +var_dump($etudiant instanceof Personne); // Sortie : bool(true) +``` + +Il ressort des sorties que l'objet `$etudiant` est considéré simultanément comme une instance des deux classes - `Etudiant` et `Personne`. + + +Interfaces fluides +------------------ + +L'"interface fluide" (en anglais "Fluent Interface") est une technique en POO qui permet d'enchaîner des méthodes ensemble en un seul appel. Cela simplifie et clarifie souvent le code. + +L'élément clé d'une interface fluide est que chaque méthode de la chaîne retourne une référence à l'objet actuel. Nous y parvenons en utilisant `return $this;` à la fin de la méthode. Ce style de programmation est souvent associé aux méthodes appelées "setters", qui définissent les valeurs des propriétés de l'objet. + +Montrons à quoi peut ressembler une interface fluide avec un exemple d'envoi d'e-mails : + +```php +public function envoyerMessage() +{ + $email = new Email; + $email->setFrom('expediteur@example.com') + ->setRecipient('admin@example.com') + ->setMessage('Bonjour, ceci est un message.') + ->send(); +} +``` + +Dans cet exemple, les méthodes `setFrom()`, `setRecipient()` et `setMessage()` servent à définir les valeurs correspondantes (expéditeur, destinataire, contenu du message). Après avoir défini chacune de ces valeurs, les méthodes nous retournent l'objet actuel (`$email`), ce qui nous permet d'enchaîner une autre méthode après elle. Enfin, nous appelons la méthode `send()`, qui envoie réellement l'e-mail. + +Grâce aux interfaces fluides, nous pouvons écrire du code intuitif et facile à lire. + + +Copie avec `clone` +------------------ + +En PHP, nous pouvons créer une copie d'un objet à l'aide de l'opérateur `clone`. De cette façon, nous obtenons une nouvelle instance avec un contenu identique. + +Si nous devons modifier certaines propriétés d'un objet lors de sa copie, nous pouvons définir une méthode spéciale `__clone()` dans la classe. Cette méthode est automatiquement appelée lorsque l'objet est cloné. + +```php +class Mouton +{ + public string $nom; + + public function __construct(string $nom) + { + $this->nom = $nom; + } + + public function __clone() + { + $this->nom = 'Clone ' . $this->nom; + } +} + +$original = new Mouton('Dolly'); +echo $original->nom . "\n"; // Affiche : Dolly + +$klon = clone $original; +echo $klon->nom . "\n"; // Affiche : Clone Dolly +``` + +Dans cet exemple, nous avons une classe `Mouton` avec une propriété `$nom`. Lorsque nous clonons une instance de cette classe, la méthode `__clone()` s'assure que le nom du mouton cloné reçoit le préfixe "Clone". + + +Traits +------ + +Les traits en PHP sont un outil qui permet de partager des méthodes, des propriétés et des constantes entre les classes et d'éviter la duplication de code. Vous pouvez les imaginer comme un mécanisme de "copier-coller" (Ctrl-C et Ctrl-V), où le contenu du trait est "inséré" dans les classes. Cela vous permet de réutiliser du code sans avoir à créer des hiérarchies de classes complexes. + +Montrons un exemple simple d'utilisation des traits en PHP : + +```php +trait KlaxonnerTrait +{ + public function klaxonner() + { + echo 'Bip bip!'; + } +} + +class Voiture +{ + use KlaxonnerTrait; +} + +class Camion +{ + use KlaxonnerTrait; +} + +$voiture = new Voiture; +$voiture->klaxonner(); // Affiche 'Bip bip!' + +$camion = new Camion; +$camion->klaxonner(); // Affiche aussi 'Bip bip!' +``` + +Dans cet exemple, nous avons un trait nommé `KlaxonnerTrait`, qui contient une méthode `klaxonner()`. Ensuite, nous avons deux classes : `Voiture` et `Camion`, qui utilisent toutes deux le trait `KlaxonnerTrait`. Grâce à cela, les deux classes "ont" la méthode `klaxonner()`, et nous pouvons l'appeler sur les objets des deux classes. + +Les traits vous permettent de partager facilement et efficacement du code entre les classes. Cependant, ils n'entrent pas dans la hiérarchie d'héritage, c'est-à-dire que `$voiture instanceof KlaxonnerTrait` retournera `false`. + + +Exceptions +---------- + +Les exceptions en POO nous permettent de gérer élégamment les erreurs et les situations inattendues dans notre code. Ce sont des objets qui transportent des informations sur l'erreur ou la situation inhabituelle. + +En PHP, nous avons une classe intégrée `Exception`, qui sert de base à toutes les exceptions. Elle possède plusieurs méthodes qui nous permettent d'obtenir plus d'informations sur l'exception, telles que le message d'erreur, le fichier et la ligne où l'erreur s'est produite, etc. + +Lorsqu'une erreur se produit dans le code, nous pouvons "lever" une exception à l'aide du mot-clé `throw`. + +```php +function division(float $a, float $b): float +{ + if ($b === 0) { + throw new Exception('Division par zéro !'); + } + return $a / $b; +} +``` + +Lorsque la fonction `division()` reçoit zéro comme deuxième argument, elle lève une exception avec le message d'erreur `'Division par zéro !'`. Pour éviter que le programme ne plante lorsqu'une exception est levée, nous la capturons dans un bloc `try/catch` : + +```php +try { + echo division(10, 0); +} catch (Exception $e) { + echo 'Exception capturée : '. $e->getMessage(); +} +``` + +Le code qui peut lever une exception est encapsulé dans un bloc `try`. Si une exception est levée, l'exécution du code passe au bloc `catch`, où nous pouvons traiter l'exception (par exemple, afficher un message d'erreur). + +Après les blocs `try` et `catch`, nous pouvons ajouter un bloc `finally` facultatif, qui s'exécutera toujours, qu'une exception ait été levée ou non (même si nous utilisons une instruction `return`, `break` ou `continue` dans le bloc `try` ou `catch`) : + +```php +try { + echo division(10, 0); +} catch (Exception $e) { + echo 'Exception capturée : '. $e->getMessage(); +} finally { + // Code qui s'exécute toujours, qu'une exception ait été levée ou non +} +``` + +Nous pouvons également créer nos propres classes (hiérarchie) d'exceptions qui héritent de la classe Exception. À titre d'exemple, imaginons une application bancaire simple qui permet d'effectuer des dépôts et des retraits : + +```php +class ExceptionBancaire extends Exception {} +class ExceptionFondsInsuffisants extends ExceptionBancaire {} +class ExceptionLimiteDepassee extends ExceptionBancaire {} + +class CompteBancaire +{ + private int $solde = 0; + private int $limiteJournaliere = 1000; + + public function deposer(int $montant): int + { + $this->solde += $montant; + return $this->solde; + } + + public function retirer(int $montant): int + { + if ($montant > $this->solde) { + throw new ExceptionFondsInsuffisants('Fonds insuffisants sur le compte.'); + } + + if ($montant > $this->limiteJournaliere) { + throw new ExceptionLimiteDepassee('La limite quotidienne de retrait a été dépassée.'); + } + + $this->solde -= $montant; + return $this->solde; + } +} +``` + +Pour un seul bloc `try`, plusieurs blocs `catch` peuvent être spécifiés si vous attendez différents types d'exceptions. + +```php +$compte = new CompteBancaire; +$compte->deposer(500); + +try { + $compte->retirer(1500); +} catch (ExceptionLimiteDepassee $e) { + echo $e->getMessage(); +} catch (ExceptionFondsInsuffisants $e) { + echo $e->getMessage(); +} catch (ExceptionBancaire $e) { + echo 'Une erreur s\'est produite lors de l\'exécution de l\'opération.'; +} +``` + +Dans cet exemple, il est important de noter l'ordre des blocs `catch`. Étant donné que toutes les exceptions héritent de `ExceptionBancaire`, si nous avions ce bloc en premier, toutes les exceptions y seraient capturées sans que le code n'atteigne les blocs `catch` suivants. Il est donc important de placer les exceptions plus spécifiques (c'est-à-dire celles qui héritent d'autres) dans un bloc `catch` plus haut dans l'ordre que leurs exceptions parentes. + + +Itération +--------- + +En PHP, vous pouvez parcourir des objets à l'aide d'une boucle `foreach`, de la même manière que vous parcourez des tableaux. Pour que cela fonctionne, l'objet doit implémenter une interface spéciale. + +La première option est d'implémenter l'interface `Iterator`, qui possède les méthodes `current()` retournant la valeur actuelle, `key()` retournant la clé, `next()` passant à la valeur suivante, `rewind()` revenant au début et `valid()` vérifiant si nous ne sommes pas encore à la fin. + +La deuxième option est d'implémenter l'interface `IteratorAggregate`, qui n'a qu'une seule méthode `getIterator()`. Celle-ci retourne soit un objet de substitution qui assurera le parcours, soit peut représenter un générateur, qui est une fonction spéciale dans laquelle `yield` est utilisé pour retourner progressivement les clés et les valeurs : + +```php +class Personne +{ + public function __construct( + public int $age, + ) { + } +} + +class Liste implements IteratorAggregate +{ + private array $personnes = []; + + public function ajouterPersonne(Personne $personne): void + { + $this->personnes[] = $personne; + } + + public function getIterator(): Generator + { + foreach ($this->personnes as $personne) { + yield $personne; + } + } +} + +$liste = new Liste; +$liste->ajouterPersonne(new Personne(30)); +$liste->ajouterPersonne(new Personne(25)); + +foreach ($liste as $personne) { + echo "Âge : {$personne->age} ans \n"; +} +``` + + +Bonnes pratiques +---------------- + +Une fois que vous maîtrisez les principes de base de la programmation orientée objet, il est important de se concentrer sur les bonnes pratiques en POO. Celles-ci vous aideront à écrire du code qui est non seulement fonctionnel, mais aussi lisible, compréhensible et facile à maintenir. + +1) **Séparation des préoccupations (Separation of Concerns)** : Chaque classe doit avoir une responsabilité clairement définie et ne doit traiter qu'une seule tâche principale. Si une classe fait trop de choses, il peut être judicieux de la diviser en classes plus petites et spécialisées. +2) **Encapsulation (Encapsulation)** : Les données et les méthodes doivent être autant que possible cachées et accessibles uniquement via une interface définie. Cela vous permet de modifier l'implémentation interne de la classe sans affecter le reste du code. +3) **Injection de dépendances (Dependency Injection)** : Au lieu de créer des dépendances directement dans la classe, vous devriez les "injecter" de l'extérieur. Pour une compréhension plus approfondie de ce principe, nous recommandons les [chapitres sur l'injection de dépendances|dependency-injection:introduction]. diff --git a/nette/fr/troubleshooting.texy b/nette/fr/troubleshooting.texy index 30346cc68f..0fa83dcc34 100644 --- a/nette/fr/troubleshooting.texy +++ b/nette/fr/troubleshooting.texy @@ -1,41 +1,70 @@ -Dépannage -********* +Résolution des problèmes +************************ -Nette ne fonctionne pas, une page blanche s'affiche .[#toc-nette-is-not-working-white-page-is-displayed] --------------------------------------------------------------------------------------------------------- -- Essayez de mettre `ini_set('display_errors', '1'); error_reporting(E_ALL);` après `declare(strict_types=1);` dans le fichier `index.php` pour forcer l'affichage des erreurs. -- Si vous voyez toujours un écran blanc, il y a probablement une erreur dans la configuration du serveur et vous découvrirez la raison dans le journal du serveur. Pour être sûr, vérifiez que PHP fonctionne en essayant d'imprimer quelque chose en utilisant `echo 'test';`. -- Si vous voyez une erreur *Server Error : Nous sommes désolés ! ...*, passez à la section suivante : +Nette ne fonctionne pas, une page blanche s'affiche +--------------------------------------------------- +- Essayez d'insérer `ini_set('display_errors', '1'); error_reporting(E_ALL);` dans le fichier `index.php` juste après `declare(strict_types=1);`, cela forcera l'affichage des erreurs. +- Si vous voyez toujours un écran blanc, il y a probablement une erreur dans la configuration du serveur et la raison sera révélée dans le journal du serveur. Pour être sûr, vérifiez si PHP fonctionne du tout en essayant d'afficher quelque chose avec `echo 'test';`. +- Si vous voyez l'erreur *Server Error: We're sorry! …*, passez à la section suivante : -Erreur 500 *Erreur du serveur : Nous sommes désolés ! ...* .[#toc-error-500-server-error-we-re-sorry] ------------------------------------------------------------------------------------------------------ -Cette page d'erreur est affichée par Nette en mode production. Si vous la voyez sur une machine de développement, [passez en mode de développement |application:bootstrap#Development vs Production Mode]. +Erreur 500 *Server Error: We're sorry! …* +----------------------------------------- +Cette page d'erreur est affichée par Nette en mode production. Si elle s'affiche sur votre ordinateur de développement, [passez en mode développement |application:bootstrapping#Mode Développement vs Production] et Tracy s'affichera avec un message détaillé. -Si le message d'erreur contient `Tracy is unable to log error`, cherchez à savoir pourquoi les erreurs ne peuvent pas être enregistrées. Pour ce faire, vous pouvez, par exemple, [passer |application:bootstrap#Development vs Production Mode] en mode développeur et appeler `Tracy\Debugger::log('hello');` après `$configurator->enableTracy(...)`. Tracy vous dira pourquoi il ne peut pas enregistrer. -La cause est généralement l'[insuffisance des autorisations d' |#Setting Directory Permissions] écriture dans le répertoire `log/`. +La raison de l'erreur se trouve toujours dans le journal du répertoire `log/`. Cependant, si le message d'erreur contient la phrase `Tracy is unable to log error`, déterminez d'abord pourquoi les erreurs ne peuvent pas être journalisées. Vous pouvez le faire, par exemple, en [passant |application:bootstrapping#Mode Développement vs Production] temporairement en mode développement et en laissant Tracy journaliser n'importe quoi après son démarrage : -Si la phrase `Tracy is unable to log error` ne figure pas (ou plus) dans le message d'erreur, vous pouvez trouver la raison de l'erreur dans le journal du répertoire `log/`. +```php +// Bootstrap.php +$configurator->setDebugMode('23.75.345.200'); // votre adresse IP +$configurator->enableTracy($rootDir . '/log'); +\Tracy\Debugger::log('hello'); +``` + +Tracy vous dira pourquoi elle ne peut pas journaliser. La cause peut être des [permissions insuffisantes |#Configuration des permissions de répertoire] pour écrire dans le répertoire `log/`. + +L'une des raisons les plus courantes de l'erreur 500 est un cache obsolète. Alors que Nette en mode développement met intelligemment à jour le cache automatiquement, en mode production, il se concentre sur la maximisation des performances et la suppression du cache, après chaque modification du code, est de votre responsabilité. Essayez de supprimer `temp/cache`. + + +Erreur 404, le routage ne fonctionne pas +---------------------------------------- +Lorsque toutes les pages (sauf la page d'accueil) retournent une erreur 404, cela ressemble à un problème de configuration du serveur pour les [jolies URL |#Comment configurer le serveur pour les jolies URL]. + + +Les modifications dans les templates ou la configuration ne sont pas prises en compte +------------------------------------------------------------------------------------- +"J'ai modifié le template ou la configuration, mais le site affiche toujours l'ancienne version." Ce comportement se produit en [mode production |application:bootstrapping#Mode Développement vs Production], qui, pour des raisons de performances, ne vérifie pas les modifications dans les fichiers et conserve le cache généré une fois. + +Pour ne pas avoir à supprimer manuellement le cache sur le serveur de production après chaque modification, activez le mode développement pour votre adresse IP dans le fichier `Bootstrap.php` : + +```php +$this->configurator->setDebugMode('votre.ip.adresse'); +``` + + +Comment désactiver le cache pendant le développement ? +------------------------------------------------------ +Nette est intelligent et vous n'avez pas besoin d'y désactiver la mise en cache. Pendant le développement, il met automatiquement à jour le cache à chaque modification du template ou de la configuration du conteneur DI. De plus, le mode développement s'active par autodétection, il n'est donc généralement pas nécessaire de configurer quoi que ce soit, [ou juste l'adresse IP |application:bootstrapping#Mode Développement vs Production]. -L'une des raisons les plus courantes est un cache obsolète. Alors que Nette met automatiquement et intelligemment à jour le cache en mode développement, en mode production, il se concentre sur l'optimisation des performances, et c'est à vous de vider le cache après chaque modification de code. Essayez de supprimer `temp/cache`. +Lors du débogage du routeur, nous vous recommandons de désactiver le cache du navigateur, dans lequel peuvent être stockées par exemple des redirections : ouvrez les Outils de développement (Ctrl+Shift+I ou Cmd+Option+I) et dans le panneau Réseau (Network), cochez la désactivation du cache. -Erreur `#[\ReturnTypeWillChange] attribute should be used` .[#toc-error-returntypewillchange-attribute-should-be-used] ----------------------------------------------------------------------------------------------------------------------- -Cette erreur se produit si vous avez mis à niveau PHP vers la version 8.1 mais que vous utilisez Nette, qui n'est pas compatible avec cette version. La solution est donc de mettre à jour Nette vers une version plus récente en utilisant `composer update`. Nette supporte PHP 8.1 depuis la version 3.0. Si vous utilisez une version plus ancienne (vous pouvez le savoir en consultant `composer.json`), [mettez à jour Nette |migrations:en] ou restez avec PHP 8.0. +Erreur `#[\ReturnTypeWillChange] attribute should be used` +---------------------------------------------------------- +Cette erreur apparaît si vous avez mis à jour PHP vers la version 8.1, mais que vous utilisez une version de Nette qui n'est pas compatible avec elle. La solution est donc de mettre à jour Nette vers une version plus récente à l'aide de `composer update`. Nette prend en charge PHP 8.1 à partir de la version 3.0. Si vous utilisez une version plus ancienne (vérifiez dans `composer.json`), [mettez à niveau Nette |migrations:en] ou restez avec PHP 8.0. -Configuration des autorisations de répertoire .[#toc-setting-directory-permissions] ------------------------------------------------------------------------------------ -Si vous développez sous macOS ou Linux (ou tout autre système basé sur Unix), vous devez configurer les privilèges d'écriture sur le serveur web. En supposant que votre application se trouve dans le répertoire par défaut `/var/www/html` (Fedora, CentOS, RHEL) +Configuration des permissions de répertoire +------------------------------------------- +Si vous développez sur macOS ou Linux (ou tout autre système basé sur Unix), vous devrez configurer les permissions d'écriture pour le serveur web. Supposons que votre application se trouve dans le répertoire par défaut `/var/www/html` (Fedora, CentOS, RHEL). ```shell cd /var/www/html/MY_PROJECT chmod -R a+rw temp log ``` -Sur certains systèmes Linux (Fedora, CentOS, ...), SELinux peut être activé par défaut. Vous devrez peut-être mettre à jour les stratégies SELinux, ou définir les chemins des répertoires `temp` et `log` avec le contexte de sécurité SELinux correct. Les répertoires `temp` et `log` doivent être définis avec le contexte `httpd_sys_rw_content_t`; pour le reste de l'application -- principalement le dossier `app` -- le contexte `httpd_sys_content_t` sera suffisant. Exécutez sur le serveur en tant que root : +Sur certains systèmes Linux (Fedora, CentOS, ...), SELinux est activé par défaut. Vous devrez ajuster les politiques SELinux de manière appropriée et définir le contexte de sécurité SELinux correct pour les dossiers `temp` et `log`. Pour `temp` et `log`, nous définirons le type de contexte `httpd_sys_rw_content_t`, pour le reste de l'application (et surtout pour le dossier `app`), `httpd_sys_content_t` suffira. Sur le serveur, exécutez : ```shell semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/log(/.*)?' @@ -43,25 +72,30 @@ semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/temp(/.*) restorecon -Rv /var/www/html/MY_PROJECT/ ``` -Ensuite, le booléen SELinux `httpd_can_network_connect_db` doit être activé pour permettre à Nette de se connecter à la base de données sur le réseau. Par défaut, il est désactivé. La commande `setsebool` peut être utilisée pour effectuer cette tâche, et si l'option `-P` est spécifiée, ce paramètre sera persistant lors des redémarrages. +Ensuite, il est nécessaire d'activer le booléen SELinux `httpd_can_network_connect_db`, qui est désactivé par défaut et qui permettra à Nette de se connecter à la base de données via le réseau. Nous utiliserons pour cela la commande `setsebool` et avec l'option `-P`, nous effectuerons le changement de manière permanente, c'est-à-dire qu'après un redémarrage du serveur, nous n'aurons pas de mauvaise surprise : ```shell setsebool -P httpd_can_network_connect_db on ``` -Comment modifier ou supprimer le répertoire `www` de l'URL ? .[#toc-how-to-change-or-remove-www-directory-from-url] -------------------------------------------------------------------------------------------------------------------- -Le répertoire `www/` utilisé dans les exemples de projets de Nette est le répertoire public ou la racine du document du projet. C'est le seul répertoire dont le contenu est accessible au navigateur. Il contient le fichier `index.php`, le point d'entrée qui permet de démarrer une application web écrite en Nette. +Comment changer ou supprimer le répertoire `www` de l'URL ? +----------------------------------------------------------- +Le répertoire `www/` utilisé dans les projets d'exemple de Nette représente ce qu'on appelle le répertoire public ou document-root du projet. C'est le seul répertoire dont le contenu est accessible au navigateur. Et il contient le fichier `index.php`, le point d'entrée qui lance l'application web écrite en Nette. -Pour exécuter l'application sur l'hébergement, vous devez définir le document-root sur ce répertoire dans la configuration de l'hébergement. Ou, si l'hébergement a un dossier prédéfini pour le répertoire public avec un nom différent (par exemple `web`, `public_html` etc.), renommez simplement `www/`. +Pour faire fonctionner l'application sur un hébergement, il est nécessaire que le document-root soit correctement configuré. Vous avez deux options : +1. Dans la configuration de l'hébergement, définissez le document-root sur ce répertoire +2. Si l'hébergement a un dossier préparé (par exemple `public_html`), renommez `www/` avec ce nom -La solution **n'est pas** de se "débarrasser" du dossier `www/` en utilisant des règles dans le fichier `.htaccess` ou dans le routeur. Si l'hébergement ne vous permet pas de définir document-root sur un sous-répertoire (c'est-à-dire de créer des répertoires un niveau au-dessus du répertoire public), cherchez-en un autre. Sinon, vous prenez un risque important en matière de sécurité. Ce serait comme vivre dans un appartement où vous ne pouvez pas fermer la porte d'entrée et où elle est toujours grande ouverte. +.[warning] +N'essayez jamais de résoudre la sécurité uniquement à l'aide de `.htaccess` ou du routeur, qui empêcheraient l'accès aux autres dossiers. +Si l'hébergement ne permet pas de définir le document-root dans un sous-répertoire (c'est-à-dire de créer des répertoires un niveau au-dessus du répertoire public), cherchez un autre hébergeur. Vous prendriez sinon un risque de sécurité considérable. Ce serait comme vivre dans un appartement où la porte d'entrée ne peut pas être fermée et reste toujours grande ouverte. -Comment configurer un serveur pour de belles URL ? .[#toc-how-to-configure-a-server-for-nice-urls] --------------------------------------------------------------------------------------------------- -**Apache** : l'extension mod_rewrite doit être autorisée et configurée dans un fichier `.htaccess`. + +Comment configurer le serveur pour les jolies URL ? +--------------------------------------------------- +**Apache**: il est nécessaire d'activer et de configurer les règles mod_rewrite dans le fichier `.htaccess`: ```apacheconf RewriteEngine On @@ -70,25 +104,53 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -Pour modifier la configuration d'Apache avec des fichiers .htaccess, la directive AllowOverride doit être activée. Il s'agit du comportement par défaut d'Apache. +Si vous rencontrez des problèmes, assurez-vous que : +- le fichier `.htaccess` se trouve dans le répertoire document-root (c'est-à-dire à côté du fichier `index.php`) +- [Apache traite les fichiers `.htaccess` |#Vérification que .htaccess fonctionne] +- [mod_rewrite est activé |#Vérification que mod rewrite est activé] + +Si vous configurez l'application dans un sous-dossier, vous devrez peut-être décommenter la ligne pour définir `RewriteBase` et la définir sur le dossier correct. -**nginx** : la directive `try_files` doit être utilisée dans la configuration du serveur : +**nginx**: il faut configurer la redirection à l'aide de la directive `try_files` à l'intérieur du bloc `location /` dans la configuration du serveur. ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args is important + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args EST IMPORTANT ! } ``` -Le bloc `location` doit être défini exactement une fois pour chaque chemin du système de fichiers dans le bloc `server`. Si vous avez déjà un bloc `location /` dans votre configuration, ajoutez la directive `try_files` dans le bloc existant. +Le bloc `location` pour chaque chemin de système de fichiers ne peut apparaître qu'une seule fois dans le bloc `server`. Si vous avez déjà `location /` dans votre configuration, ajoutez-y la directive `try_files`. + + +Vérification que `.htaccess` fonctionne +--------------------------------------- +La manière la plus simple de tester si Apache utilise ou ignore votre fichier `.htaccess`, est de le corrompre intentionnellement. Insérez la ligne `Test` au début du fichier et maintenant, si vous actualisez la page dans le navigateur, vous devriez voir *Internal Server Error*. + +Si cette erreur s'affiche, c'est en fait une bonne chose ! Cela signifie qu'Apache analyse le fichier `.htaccess` et rencontre l'erreur que nous y avons insérée. Supprimez la ligne `Test`. + +Si *Internal Server Error* ne s'affiche pas, votre configuration Apache ignore le fichier `.htaccess`. Généralement, Apache l'ignore en raison de l'absence de la directive de configuration `AllowOverride All`. + +Si vous l'hébergez vous-même, cela peut être facilement corrigé. Ouvrez le fichier `httpd.conf` ou `apache.conf` dans un éditeur de texte, recherchez la section `<Directory>` appropriée et ajoutez/modifiez cette directive : + +```apacheconf +<Directory "/var/www/htdocs"> # chemin vers votre document root + AllowOverride All + ... +``` + +Si votre site est hébergé ailleurs, consultez votre panneau de contrôle pour voir si vous pouvez y activer le fichier `.htaccess`. Sinon, contactez votre fournisseur d'hébergement, pour qu'il le fasse pour vous. + + +Vérification que `mod_rewrite` est activé +----------------------------------------- +Si vous avez vérifié que [.htaccess fonctionne |#Vérification que .htaccess fonctionne], vous pouvez vérifier si l'extension mod_rewrite est activée. Insérez la ligne `RewriteEngine On` au début du fichier `.htaccess` et actualisez la page dans le navigateur. Si *Internal Server Error* s'affiche, cela signifie que mod_rewrite n'est pas activé. Il existe plusieurs façons de l'activer. Vous trouverez différentes manières de le faire dans différentes configurations sur Stack Overflow. -Les liens sont générés sans `https:` .[#toc-links-are-generated-without-https] ------------------------------------------------------------------------------- -Nette génère des liens avec le même protocole que celui utilisé par la page actuelle. Ainsi, sur la page `https://foo` et vice versa. -Si vous vous trouvez derrière un reverse proxy HTTPS (par exemple, dans Docker), vous devez mettre en place un [proxy |http:configuration#HTTP proxy] dans la configuration pour que la détection du protocole fonctionne correctement. +Les liens sont générés sans `https:` +------------------------------------ +Nette génère des liens avec le même protocole que la page elle-même. C'est-à-dire que sur la page `https://foo`, il génère des liens commençant par `https:` et inversement. Si vous êtes derrière un proxy inverse qui supprime HTTPS (par exemple dans Docker), il faut alors [configurer le proxy |http:configuration#Proxy HTTP], pour que la détection du protocole fonctionne correctement. -Si vous utilisez Nginx comme proxy, vous devez configurer la redirection de la manière suivante : +Si vous utilisez Nginx comme proxy, il faut avoir configuré la redirection par exemple comme ceci : ``` location / { @@ -96,11 +158,11 @@ location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Port $server_port; - proxy_pass http://IP-aplikace:80; # IP ou nom d'hôte du serveur/conteneur où s'exécute l'application + proxy_pass http://IP-application:80; # IP ou hostname du serveur/conteneur où tourne l'application } ``` -Ensuite, vous devez spécifier le proxy IP et, le cas échéant, la plage IP de votre réseau local où vous exécutez l'infrastructure : +Ensuite, il faut indiquer dans la configuration l'IP du proxy et éventuellement la plage IP de votre réseau local où vous exploitez l'infrastructure : ```neon http: @@ -108,32 +170,30 @@ http: ``` -Utilisation des caractères { } en JavaScript .[#toc-use-of-characters-in-javascript] ------------------------------------------------------------------------------------- -Les caractères `{` and `}` sont utilisés pour écrire les balises Latte. Tout ce qui suit (sauf l'espace et le guillemet) `{` character is considered a tag. If you need to print character `{` (souvent en JavaScript), vous pouvez mettre un espace (ou un autre caractère vide) juste après `{`. De cette façon, vous évitez de l'interpréter comme une balise. +Utilisation des caractères { } en JavaScript +-------------------------------------------- +Les caractères `{` et `}` sont utilisés pour écrire les balises Latte. Est considéré comme une balise tout ce qui suit le caractère `{` à l'exception d'un espace et d'un guillemet. Si vous avez donc besoin d'afficher directement le caractère `{` (souvent par exemple en JavaScript), vous pouvez mettre un espace (ou un autre caractère vide) après le caractère `{`. Cela évite la traduction en tant que balise. -S'il est nécessaire d'imprimer ces caractères dans une situation où ils seraient interprétés comme une balise, vous pouvez utiliser des balises spéciales pour imprimer ces caractères - `{l}` pour `{` and `{r}` pour `}`. +S'il est nécessaire d'afficher ces caractères dans une situation où le texte serait interprété comme une balise, vous pouvez utiliser des balises spéciales pour afficher ces caractères - `{l}` pour `{` et `{r}` pour `}`. ``` -{is tag} -{ is not tag } -{l}is not tag{r} +{est une balise} +{ n'est pas une balise } +{l}n'est pas une balise{r} ``` -Remarque `Presenter::getContext() is deprecated` .[#toc-notice-presenter-getcontext-is-deprecated] --------------------------------------------------------------------------------------------------- +Message `Presenter::getContext() is deprecated` +----------------------------------------------- -Nette est de loin le premier framework PHP à avoir adopté l'injection de dépendances et à avoir incité les programmeurs à l'utiliser systématiquement, en commençant par les présentateurs. Si un présentateur a besoin d'une dépendance, [il la demandera |dependency-injection:passing-dependencies]. -En revanche, la façon dont nous passons l'ensemble du conteneur DI à une classe pour qu'elle en tire directement les dépendances est considérée comme un anti-modèle (cela s'appelle un localisateur de services). -Cette méthode était utilisée dans Nette 0.x avant l'avènement de l'injection de dépendances, et sa relique est la méthode `Presenter::getContext()`, depuis longtemps marquée comme dépréciée. +Nette est de loin le premier framework PHP à être passé à l'injection de dépendances et a conduit les programmeurs à l'utiliser de manière cohérente, dès les presenters eux-mêmes. Si un presenter a besoin d'une dépendance, il [la demande|dependency-injection:passing-dependencies]. Au contraire, la voie consistant à passer l'ensemble du conteneur DI à la classe, et celle-ci en extrait directement les dépendances, est considérée comme un antipattern (appelé service locator). Cette méthode était utilisée dans Nette 0.x avant l'arrivée de l'injection de dépendances et son vestige est la méthode `Presenter::getContext()`, marquée comme obsolète depuis longtemps. -Si vous portez une très vieille application Nette, vous découvrirez peut-être qu'elle utilise encore cette méthode. Ainsi, depuis la version 3.1 de `nette/application` vous rencontrerez l'avertissement `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection`, depuis la version 4.0 vous rencontrerez l'erreur que la méthode n'existe pas. +Si vous portez une très ancienne application Nette, il se peut qu'elle utilise encore cette méthode. À partir de la version 3.1 de `nette/application`, vous rencontrerez l'avertissement `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection`, et à partir de la version 4.0, l'erreur indiquant que la méthode n'existe pas. -La solution propre, bien sûr, est de reconcevoir l'application pour passer les dépendances en utilisant l'injection de dépendances. Comme solution de rechange, vous pouvez ajouter votre propre méthode `getContext()` à votre présentateur de base et contourner le message : +La solution propre est bien sûr de refondre l'application pour qu'elle passe les dépendances via l'injection de dépendances. Comme solution de contournement, vous pouvez ajouter votre propre méthode `getContext()` à votre presenter de base et ainsi contourner le message : ```php -abstract BasePresenter extends Nette\Application\UI\Presenter +abstract class BasePresenter extends Nette\Application\UI\Presenter { private Nette\DI\Container $context; diff --git a/nette/fr/vulnerability-protection.texy b/nette/fr/vulnerability-protection.texy new file mode 100644 index 0000000000..07b1342e33 --- /dev/null +++ b/nette/fr/vulnerability-protection.texy @@ -0,0 +1,99 @@ +Protection contre les vulnérabilités +************************************ + +.[perex] +De temps en temps, une faille de sécurité est signalée sur un autre site web important ou une faille est exploitée. C'est désagréable. Si la sécurité de vos applications web vous tient à cœur, Nette Framework est certainement le meilleur choix. + + +Cross-Site Scripting (XSS) +========================== + +Le Cross-Site Scripting est une méthode de violation des pages web exploitant des sorties non traitées. L'attaquant peut alors injecter son propre code dans la page et ainsi la modifier ou même obtenir des données sensibles sur les visiteurs. On ne peut se défendre contre le XSS qu'en traitant de manière cohérente et correcte toutes les chaînes. Pourtant, il suffit que votre codeur l'omette une seule fois, et tout le site peut être compromis d'un coup. + +Un exemple d'attaque peut être l'injection d'une URL modifiée à l'utilisateur, à l'aide de laquelle nous injectons notre code dans la page. Si l'application ne traite pas correctement les sorties, elle exécutera le script dans le navigateur de l'utilisateur. De cette manière, nous pouvons par exemple lui voler son identité. + +``` +https://example.com/?search=<script>alert('Attaque XSS réussie.');</script> +``` + +Nette Framework introduit une technologie révolutionnaire [d'Échappement sensible au contexte |latte:safety-first#Échappement contextuel], qui vous débarrasse à jamais du risque de Cross-Site Scripting. Il traite automatiquement toutes les sorties, de sorte qu'il est impossible qu'un codeur oublie quelque chose. Un exemple ? Le codeur crée ce template : + +```latte +<p onclick="alert({$message})">{$message}</p> + +<script> +document.title = {$message}; +</script> +``` + +La notation `{$message}` signifie afficher la variable. Dans d'autres frameworks, il est nécessaire de traiter explicitement chaque affichage et même différemment à chaque endroit. Dans Nette Framework, il n'est pas nécessaire de traiter quoi que ce soit, tout est fait automatiquement, correctement et de manière cohérente. Si nous attribuons à la variable `$message = 'Largeur 1/2"'`, le framework générera le code HTML : + +```latte +<p onclick="alert("Largeur 1\/2\"")">Largeur 1/2"</p> + +<script> +document.title = "Largeur 1\/2\""; +</script> +``` + + +Cross-Site Request Forgery (CSRF) +================================= + +L'attaque Cross-Site Request Forgery consiste pour l'attaquant à attirer la victime sur une page qui exécute discrètement dans le navigateur de la victime une requête vers le serveur sur lequel la victime est connectée, et le serveur croit que la requête a été exécutée par la victime de sa propre volonté. Ainsi, sous l'identité de la victime, il effectue une certaine action sans que celle-ci le sache. Il peut s'agir de modifier ou de supprimer des données, d'envoyer un message, etc. + +Nette Framework **protège automatiquement les formulaires et les signaux dans les presenters** contre ce type d'attaque. Et ce, en empêchant leur envoi ou leur déclenchement depuis un autre domaine. Si vous souhaitez désactiver la protection, utilisez pour les formulaires : + +```php +$form->allowCrossOrigin(); +``` + +ou dans le cas d'un signal, ajoutez l'annotation `@crossOrigin`: + +```php +/** + * @crossOrigin + */ +public function handleXyz() +{ +} +``` + +Dans Nette Application 3.2, vous pouvez également utiliser des attributs : + +```php +use Nette\Application\Attributes\Requires; + +#[Requires(sameOrigin: false)] +public function handleXyz() +{ +} +``` + + +Attaque d'URL, codes de contrôle, UTF-8 invalide +================================================ + +Divers termes liés à la tentative de l'attaquant d'injecter une entrée *malveillante* dans votre application web. Les conséquences peuvent être très diverses, allant de l'endommagement des sorties XML (par exemple, des flux RSS non fonctionnels) à l'obtention d'informations sensibles de la base de données ou de mots de passe. La défense consiste en un traitement cohérent de toutes les entrées au niveau des octets individuels. Et soyons honnêtes, qui d'entre vous le fait ? + +Nette Framework le fait pour vous et de plus automatiquement. Vous n'avez rien à configurer du tout et toutes les entrées seront traitées. + + +Détournement de session, vol de session, fixation de session +============================================================ + +Plusieurs types d'attaques sont liés à la gestion des sessions. L'attaquant vole ou injecte son ID de session à l'utilisateur et obtient ainsi l'accès à l'application web sans connaître le mot de passe de l'utilisateur. Il peut ensuite faire n'importe quoi dans l'application sans que l'utilisateur le sache. La défense consiste en une configuration correcte du serveur et de PHP. + +Nette Framework configure PHP automatiquement. Le programmeur n'a donc pas à réfléchir à la manière de sécuriser correctement la session et peut se concentrer pleinement sur la création de l'application. Cela nécessite cependant que la fonction `ini_set()` soit activée. + + +Cookie SameSite +=============== + +Les cookies SameSite fournissent un mécanisme pour reconnaître ce qui a conduit au chargement de la page. Ce qui est absolument essentiel pour la sécurité. + +L'indicateur SameSite peut avoir trois valeurs : `Lax`, `Strict` et `None` (ce dernier nécessite HTTPS). Si la requête de page provient directement du site ou si l'utilisateur ouvre la page en la saisissant directement dans la barre d'adresse ou en cliquant sur un signet, le navigateur envoie tous les cookies au serveur (c'est-à-dire avec les indicateurs `Lax`, `Strict` et `None`). Si l'utilisateur accède au site via un lien depuis un autre site, les cookies avec les indicateurs `Lax` et `None` sont transmis au serveur. Si la requête est générée d'une autre manière, comme l'envoi d'un formulaire POST depuis un autre site, le chargement à l'intérieur d'un iframe, via JavaScript, etc., seuls les cookies avec l'indicateur `None` sont envoyés. + +Nette envoie par défaut tous les cookies avec l'indicateur `Lax`. + +{{leftbar: www:@menu-common}} diff --git a/nette/hu/@home.texy b/nette/hu/@home.texy index 53d3959489..4a115c9ffd 100644 --- a/nette/hu/@home.texy +++ b/nette/hu/@home.texy @@ -1,4 +1,4 @@ -Nette dokumentáció +Nette Dokumentáció ****************** <div class=documentation> @@ -6,96 +6,97 @@ Nette dokumentáció <div> -Bevezetés ---------- -- [Miért használja a Nette-et? |www:10-reasons-why-nette] -- [Telepítés |Installation] -- [Készítse el első alkalmazását! |quickstart:] +Ismerkedés +---------- +- [Miért használja a Nette-t? |www:10-reasons-why-nette] +- [Telepítés |installation] +- [Írjuk meg az első alkalmazást! |quickstart:] Általános --------- - [Csomagok listája |www:packages] -- [Karbantartás és PHP |www:maintenance] +- [Karbantartás és PHP verziók |www:maintenance] - [Kiadási megjegyzések |https://nette.org/releases] -- [Frissítési útmutató |migrations:en] -- [Hibaelhárítás |nette:Troubleshooting] -- [Ki hozza létre a Nette-et |https://nette.org/contributors] +- [Frissítés újabb verziókra|migrations:en] +- [Hibaelhárítás |nette:troubleshooting] +- [Ki alkotja a Nette-t |https://nette.org/contributors] - [A Nette története |www:history] -- [Vegyen részt |contributing:] -- [Szponzorfejlesztés |https://nette.org/en/donate] -- [API hivatkozás |https://api.nette.org/] +- [Csatlakozzon |contributing:] +- [Támogassa a fejlesztést |https://nette.org/cs/donate] +- [API referencia |https://api.nette.org/] </div> <div> -Nette alkalmazás ----------------- +Nette alkalmazások +------------------ - [Hogyan működnek az alkalmazások? |application:how-it-works] -- [Bootstrap |application:Bootstrap] -- [Presenters |application:Presenters] -- [Sablonok |application:Templates] -- [Modules |application:Modules] -- [Routing |application:Routing] +- [Bootstrapping |application:Bootstrapping] +- [Presenterek |application:presenters] +- [Sablonok |application:templates] +- [Könyvtárstruktúra |application:directory-structure] +- [Útválasztás |application:routing] - [URL linkek létrehozása |application:creating-links] - [Interaktív komponensek |application:components] - [AJAX & Snippetek |application:ajax] -- [Legjobb gyakorlatok |best-practices:] +- [Útmutatók és eljárások |best-practices:] </div> <div> -Főbb témák ----------- +Fő témák +-------- - [Konfiguráció |nette:configuring] -- [Függőségi injektálás |dependency-injection:] -- [Latte: Sablonok |latte:] -- [Tracy: Tracy: Hibakereső eszköz |tracy:] -- [Forms |forms:] -- [Adatbázis |database:core] -- [Felhasználók hitelesítése |security:authentication] -- [Hozzáférés-szabályozás |security:authorization] -- [Sessions |http:Sessions] -- [HTTP kérés és válasz |http:] -- [Tárolás |caching:] +- [Dependency Injection|dependency-injection:] +- [Latte: sablonok |latte:] +- [Tracy: kód debuggolás |tracy:] +- [Űrlapok |forms:] +- [Adatbázis |database:guide] +- [Felhasználói bejelentkezés |security:authentication] +- [Jogosultság ellenőrzés |security:authorization] +- [Munkamenetek |http:Sessions] +- [HTTP kérés & válasz|http:] +- [Eszközök |assets:] +- [Cache |caching:] - [E-mailek küldése |mail:] -- [Séma: Adatérvényesítés |schema:] -- [PHP kódgenerátor |php-generator:] -- [Tesztelő: Egységtesztelés |tester:] +- [Schema: adat validáció |schema:] +- [PHP kód generátor |php-generator:] +- [Tester: tesztelés |tester:] </div> <div> -Segédprogramok --------------- -- [Arrays |utils:Arrays] +Utilities +--------- +- [Tömbök |utils:arrays] - [Fájlrendszer |utils:filesystem] - [Finder |utils:finder] -- [HTML elemek |utils:HTML Elements] -- [Képek |utils:Images] +- [HTML elemek |utils:html-elements] +- [Képek |utils:images] - [JSON |utils:JSON] -- [NEON |neon:] -- [Password Hashing |security:passwords] -- [SmartObject |utils:SmartObject] +- [NEON|neon:] +- [Jelszó hashelés |security:passwords] - [PHP típusok |utils:type] -- [Strings |utils:Strings] +- [Stringek |utils:strings] - [Validátorok |utils:validators] - [RobotLoader |robot-loader:] +- [SmartObject |utils:smartobject] & [StaticClass |utils:StaticClass] - [SafeStream |safe-stream:] -- [...mások |utils:] +- [...továbbiak |utils:] </div> </div> {{toc:no}} -{{description: Hivatalos Nette dokumentáció: leírja a Nette működését és a webes alkalmazások fejlesztésének legjobb gyakorlatait.}} -{{maintitle: Net dokumentáció}} +{{description: Hivatalos Nette dokumentáció: leírja, hogyan működik a Nette és a webalkalmazások fejlesztésének legjobb gyakorlatait.}} +{{maintitle: Nette Dokumentáció}} diff --git a/nette/hu/@menu-topics.texy b/nette/hu/@menu-topics.texy index eeebf4e470..a285b72f60 100644 --- a/nette/hu/@menu-topics.texy +++ b/nette/hu/@menu-topics.texy @@ -1,21 +1,21 @@ -Főbb témák -********** +Fő témák +******** - [Konfiguráció |nette:configuring] -- [Nette alkalmazás |application:how-it-works] -- [Függőségi injektálás |dependency-injection:] -- [Segédprogramok |utils:] -- [Formák |forms:] -- [Adatbázis |database:core] -- [Felhasználók hitelesítése |security:authentication] -- [Hozzáférés-szabályozás |security:authorization] -- [Sessions |http:Sessions] -- [HTTP kérés és válasz |http:] -- [Tárolás |caching:] +- [Nette alkalmazások |application:how-it-works] +- [Dependency Injection|dependency-injection:] +- [Utilities |utils:] +- [Űrlapok |forms:] +- [Adatbázis |database:guide] +- [Felhasználói bejelentkezés |security:authentication] +- [Jogosultság ellenőrzés |security:authorization] +- [Munkamenetek |http:Sessions] +- [HTTP kérés & válasz|http:] +- [Cache |caching:] - [E-mailek küldése |mail:] -- [Séma: Adatérvényesítés |schema:] -- [PHP kódgenerátor |php-generator:] +- [Schema: adat validáció |schema:] +- [PHP kód generátor |php-generator:] - [Latte: sablonok |latte:] -- [Tracy: hibakeresés |tracy:] +- [Tracy: kód debuggolás |tracy:] - [Tester: tesztelés |tester:] diff --git a/nette/hu/@meta.texy b/nette/hu/@meta.texy new file mode 100644 index 0000000000..c172d1cda5 --- /dev/null +++ b/nette/hu/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette dokumentáció}} diff --git a/nette/hu/configuring.texy b/nette/hu/configuring.texy index 5517d30ccd..1c10c22485 100644 --- a/nette/hu/configuring.texy +++ b/nette/hu/configuring.texy @@ -1,35 +1,36 @@ -Nette konfigurálása -******************* +Nette Konfiguráció +****************** .[perex] -A Nette keretrendszer összes konfigurációs lehetőségének áttekintése. +A Nette Framework összes konfigurációs opciójának áttekintése. -A Nette komponensek konfigurálása konfigurációs fájlok segítségével történik, amelyek általában [NEON |neon:format] nyelven íródnak. Ezeket a legjobb [olyan szerkesztőkben |best-practices:editors-and-tools#ide-editor] szerkeszteni [, amelyek ezt támogatják |best-practices:editors-and-tools#ide-editor]. -Ha a teljes keretrendszert használja, a konfiguráció [betöltődik a rendszer indításakor |application:bootstrap#di-container-configuration], ha nem, akkor lásd, [hogyan töltse be a konfigurációt |bootstrap:]. +A Nette komponenseit konfigurációs fájlok segítségével állítjuk be, amelyeket általában [NEON formátumban|neon:format] írunk. Legjobban a [NEON-t támogató szerkesztőkben |best-practices:editors-and-tools#IDE szerkesztő] szerkeszthetők. Ha a teljes keretrendszert használja, a konfiguráció [az alkalmazás indításakor töltődik be |application:bootstrapping#DI konténer konfigurálása], ha nem, olvassa el, [hogyan kell betölteni a konfigurációt|bootstrap:]. <pre> "application .[prism-token prism-atrule]":[application:configuration#Application]: "Application .[prism-token prism-comment]"<br> -"constants .[prism-token prism-atrule]":[application:configuration#Constants]: "PHP konstansok meghatározása .[prism-token prism-comment]"<br> +"assets .[prism-token prism-atrule]":[assets:configuration]: "Assets .[prism-token prism-comment]"<br> +"constants .[prism-token prism-atrule]":[application:configuration#Konstansok]: "PHP konstansok definíciója .[prism-token prism-comment]"<br> "database .[prism-token prism-atrule]":[database:configuration]: "Adatbázis .[prism-token prism-comment]"<br> -"decorator .[prism-token prism-atrule]":[dependency-injection:configuration#Decorator]: "Díszítő .[prism-token prism-comment]"<br> -"di .[prism-token prism-atrule]":[dependency-injection:configuration#DI]: "DI Container .[prism-token prism-comment]"<br> -"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Extensions]: "További DI kiterjesztések telepítése .[prism-token prism-comment]"<br> -"forms .[prism-token prism-atrule]":[forms:configuration]: "Forms .[prism-token prism-comment]"<br> -"http .[prism-token prism-atrule]":[http:configuration#HTTP Headers]: "HTTP Headers .[prism-token prism-comment]"<br> -"includes .[prism-token prism-atrule]":[dependency-injection:configuration#Including files]: "Beleértve a fájlokat .[prism-token prism-comment]"<br> -"latte .[prism-token prism-atrule]":[application:configuration#Latte]: "Latte .[prism-token prism-comment]"<br> -"mail .[prism-token prism-atrule]":[mail:#Configuring]: "Levelezés .[prism-token prism-comment]"<br> -"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#Parameters]: "Paraméterek .[prism-token prism-comment]"<br> -"php .[prism-token prism-atrule]":[application:configuration#PHP]: "PHP konfigurációs beállítások .[prism-token prism-comment]"<br> +"decorator .[prism-token prism-atrule]":[dependency-injection:configuration#Decorator]: "Dekorátor .[prism-token prism-comment]"<br> +"di .[prism-token prism-atrule]":[dependency-injection:configuration#DI]: "DI konténer .[prism-token prism-comment]"<br> +"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Kiterjesztések]: "További DI kiterjesztések telepítése .[prism-token prism-comment]"<br> +"forms .[prism-token prism-atrule]":[forms:configuration]: "Űrlapok .[prism-token prism-comment]"<br> +"http .[prism-token prism-atrule]":[http:configuration#HTTP fejlécek]: "HTTP fejlécek .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[dependency-injection:configuration#Fájlok beillesztése]: "Fájlok beillesztése .[prism-token prism-comment]"<br> +"latte .[prism-token prism-atrule]":[application:configuration#Latte sablonok]: "Latte sablonok .[prism-token prism-comment]"<br> +"mail .[prism-token prism-atrule]":[mail:#Konfiguráció]: "E-mailek .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#Paraméterek]: "Paraméterek .[prism-token prism-comment]"<br> +"php .[prism-token prism-atrule]":[application:configuration#PHP]: "PHP konfiguráció .[prism-token prism-comment]"<br> "routing .[prism-token prism-atrule]":[application:configuration#Routing]: "Útválasztás .[prism-token prism-comment]"<br> -"search .[prism-token prism-atrule]":[dependency-injection:configuration#Search]: "Automatikus szolgáltatás regisztráció .[prism-token prism-comment]"<br> -"security .[prism-token prism-atrule]":[security:configuration]: "Hozzáférés-szabályozás .[prism-token prism-comment]"<br> +"search .[prism-token prism-atrule]":[dependency-injection:configuration#Search]: "Szolgáltatások automatikus regisztrálása .[prism-token prism-comment]"<br> +"security .[prism-token prism-atrule]":[security:configuration]: "Hozzáférési jogosultságok .[prism-token prism-comment]"<br> "services .[prism-token prism-atrule]":[dependency-injection:services]: "Szolgáltatások .[prism-token prism-comment]"<br> -"session .[prism-token prism-atrule]":[http:configuration#Session]: "Munkamenet .[prism-token prism-comment]"<br> -"tracy .[prism-token prism-atrule]":[tracy:configuring#Nette Framework]: "Tracy Debugger .[prism-token prism-comment]" +"session .[prism-token prism-atrule]":[http:configuration#Session]: "Session .[prism-token prism-comment]"<br> +"tracy .[prism-token prism-atrule]":[tracy:configuring#Nette Framework]: "Tracy debugger .[prism-token prism-comment]" </pre> -A `%`, you must escape it by doubling it to `%%` karaktert tartalmazó karakterlánc írása. .[note] +.[note] +Ha `%` karaktert tartalmazó stringet szeretne írni, duplázással kell escape-elni `%%`-re. {{leftbar: @menu-topics}} diff --git a/nette/hu/glossary.texy b/nette/hu/glossary.texy index e58b537123..c45c6f5d2f 100644 --- a/nette/hu/glossary.texy +++ b/nette/hu/glossary.texy @@ -2,154 +2,158 @@ Fogalomtár ********** -AJAX .[#toc-ajax] ------------------ -Aszinkron JavaScript és XML - a HTTP protokollon keresztüli kliens-szerver kommunikáció technológiája, amely nem teszi szükségessé az egész oldal újratöltését minden egyes kérés során. A betűszó ellenére az XML helyett gyakran [JSON |#JSON] formátumot használnak. +AJAX +---- +Asynchronous JavaScript and XML - technológia az információ cseréjére a kliens és a szerver között HTTP protokollon keresztül anélkül, hogy minden kérésnél újra kellene tölteni az egész oldalt. Bár a névből úgy tűnhet, hogy csak XML formátumban küld adatokat, gyakran használják a [#JSON] formátumot is. -Bemutató művelet .[#toc-presenter-action] ------------------------------------------ -A [prezenter |#presenter] logikai része, amely egy műveletet hajt végre, például egy termékoldal megjelenítése, egy felhasználó kijelentkezése stb. Egy bemutatóhoz több művelet is tartozhat. +Presenter akció +--------------- +A presenter logikai része, amely egy akciót hajt végre. Például megjeleníti a termékoldalt, kijelentkezteti a felhasználót stb. Egy presenter több akcióval is rendelkezhet. BOM --- -Az úgynevezett *byte order mask* egy speciális első karakter egy fájlban, és a kódolásban a bájtsorrendet jelzi. Néhány szerkesztő automatikusan tartalmazza, gyakorlatilag láthatatlan, de problémákat okoz a fejlécekkel és a PHP-n belülről történő kimeneti küldéssel. A [Code Checker |code-checker:] segítségével tömegesen eltávolítható. +Az ún. *byte order mark* egy speciális első karakter a fájlban, amelyet a bájtsorrend jelzőjeként használnak a kódolásban. Néhány szerkesztő beilleszti a fájlokba. Gyakorlatilag láthatatlan, de problémákat okoz a kimenet és a fejlécek küldésével PHP-ból. Tömeges eltávolításához használhatja a [Code Checker|code-checker:] eszközt. -Vezérlő .[#toc-controller] +Controller +---------- +Vezérlő, amely feldolgozza a felhasználói kéréseket, és ezek alapján meghívja a megfelelő alkalmazáslogikát (azaz a [#modell]), majd megkéri a [#view]-t az adatok megjelenítésére. A kontrollerek megfelelői a Nette Frameworkben a [presenterek |#Presenter]. + + +Cross-Site Scripting (XSS) -------------------------- -A vezérlő feldolgozza a felhasználó kéréseit, és azok alapján meghívja az adott alkalmazás logikáját (azaz a [modellt |#model]), majd meghívja [a nézetet |#view] az adatok megjelenítéséhez. A kontrollerek analógiája a Nette Frameworkben a [prezenterek |#presenter]. +A Cross-Site Scripting egy weboldal-megsértési módszer, amely a nem kezelt kimeneteket használja ki. A támadó ezután képes saját kódot beilleszteni az oldalba, és ezzel módosíthatja az oldalt, vagy akár érzékeny adatokat szerezhet a látogatókról. Az XSS ellen csak az összes karakterlánc következetes és korrekt kezelésével lehet védekezni. +A Nette Framework forradalmi [Context-Aware Escaping |latte:safety-first#Kontextusérzékeny escapelés] technológiával rendelkezik, amely örökre megszabadítja Önt a Cross-Site Scripting kockázatától. Minden kimenetet automatikusan kezel, így nem fordulhat elő, hogy a kódoló valamit elfelejtene. -Oldalközi szkriptelés (XSS) .[#toc-cross-site-scripting-xss] ------------------------------------------------------------- -A Cross-Site Scripting egy webhely megzavarásának módszere, amely a nem kimentett bemenetet használja. A támadó saját HTML- vagy JavaScript-kódot juttathat be, és megváltoztathatja az oldal kinézetét, vagy akár érzékeny információkat is gyűjthet a felhasználókról. Az XSS elleni védelem egyszerű: az összes karakterlánc és bemenet következetes és helyes escapingje. -A Nette Framework egy vadonatúj technológiával, a [Context-Aware Escapinggel |latte:safety-first#context-aware-escaping] áll elő, amely örökre megszabadítja Önt a Cross-Site Scripting kockázatoktól. Az összes bemenetet automatikusan eszkábálja az adott kontextus alapján, így lehetetlen, hogy egy programozó véletlenül elfelejtsen valamit. +Cross-Site Request Forgery (CSRF) +--------------------------------- +A Cross-Site Request Forgery támadás abból áll, hogy a támadó egy olyan oldalra csalogatja az áldozatot, amely észrevétlenül végrehajt egy kérést az áldozat böngészőjében egy olyan szerver felé, amelyen az áldozat be van jelentkezve, és a szerver azt hiszi, hogy a kérést az áldozat saját akaratából hajtotta végre. Így az áldozat identitása alatt végrehajt egy bizonyos műveletet anélkül, hogy az áldozat tudna róla. Ez lehet adatok módosítása vagy törlése, üzenet küldése stb. +A Nette Framework **automatikusan védi az űrlapokat és a presenterekben lévő signálokat** az ilyen típusú támadások ellen. Ezt úgy teszi, hogy megakadályozza azok elküldését vagy meghívását más domainről. -Oldalközi kérés meghamisítása (CSRF) .[#toc-cross-site-request-forgery-csrf] ----------------------------------------------------------------------------- -A Cross-Site Request Forgery támadás lényege, hogy a támadó egy olyan oldal meglátogatására csalja az áldozatot, amely némán végrehajt egy kérést az áldozat böngészőjében a szerver felé, ahol az áldozat éppen bejelentkezve van, és a szerver azt hiszi, hogy a kérést az áldozat akaratából tette. A szerver az áldozat személyazonossága alatt, de az áldozat tudta nélkül hajt végre egy bizonyos műveletet. Ez lehet adatok módosítása vagy törlése, üzenet küldése stb. -A Nette Framework **automatikusan védi a prezenterekben lévő űrlapokat és jeleket** az ilyen típusú támadásoktól. Ez úgy történik, hogy megakadályozza, hogy egy másik tartományból küldjék vagy hívják őket. +Dependency Injection +-------------------- +A Dependency Injection (DI) egy tervezési minta, amely leírja, hogyan válasszuk szét az objektumok létrehozását azok függőségeitől. Tehát az osztály nem felelős a függőségeinek létrehozásáért vagy inicializálásáért, hanem ezeket a függőségeket egy külső kód (ez lehet egy [DI konténer |#Dependency Injection konténer] is) biztosítja számára. Az előnye az, hogy nagyobb rugalmasságot tesz lehetővé a kódban, jobb érthetőséget és könnyebb tesztelhetőséget biztosít az alkalmazás számára, mivel a függőségek könnyen cserélhetők és izolálhatók a kód többi részétől. További információ a [Mi az a Dependency Injection? |dependency-injection:introduction] fejezetben. -Függőségi injekció .[#toc-dependency-injection] ------------------------------------------------ -A Dependency Injection (DI) egy tervezési minta, amely megmondja, hogyan lehet az objektumok létrehozását elválasztani a függőségüktől. Vagyis egy osztály nem felelős a függőségek létrehozásáért vagy inicializálásáért, hanem ezeket a függőségeket külső kód biztosítja (ami lehet egy [DI konténer |#Dependency Injection container] is). Ennek előnye, hogy nagyobb kódrugalmasságot, jobb olvashatóságot és könnyebb alkalmazástesztelést tesz lehetővé, mivel a függőségek könnyen cserélhetők és elszigetelhetők a kód más részeitől. További információért lásd: [Mi a Dependency Injection? |dependency-injection:introduction] +Dependency Injection konténer +----------------------------- +A Dependency Injection konténer (más néven DI konténer vagy IoC konténer) egy eszköz, amely gondoskodik a függőségek létrehozásáról és kezeléséről az alkalmazásban (azaz a [szolgáltatásokról |#Szolgáltatás]). A konténernek általában van egy konfigurációja, amely meghatározza, hogy mely osztályok függenek más osztályoktól, milyen konkrét függőség-implementációkat kell használni, és hogyan kell ezeket a függőségeket létrehozni. Ezután a konténer létrehozza ezeket az objektumokat, és biztosítja őket azoknak az osztályoknak, amelyeknek szükségük van rájuk. További információ a [Mi az a DI konténer? |dependency-injection:container] fejezetben. -Függőségi injektálás konténer .[#toc-dependency-injection-container] --------------------------------------------------------------------- -A Dependency Injection konténer (más néven DI konténer vagy IoC konténer) egy olyan eszköz, amely a függőségek létrehozását és kezelését kezeli egy alkalmazásban (vagy [szolgáltatásban |#service]). A konténer általában rendelkezik egy konfigurációval, amely meghatározza, hogy mely osztályok függenek más osztályoktól, milyen konkrét függőségi implementációkat használjon, és hogyan hozza létre ezeket a függőségeket. A konténer ezután létrehozza ezeket az objektumokat, és biztosítja azokat az osztályok számára, amelyeknek szükségük van rájuk. További információért lásd: [Mi az a DI konténer? |dependency-injection:container] +Escapelés +--------- +Az escapelés a speciális jelentéssel bíró karakterek átalakítása más, megfelelő szekvenciákra egy adott kontextusban. Példa: egy idézőjelekkel határolt karakterláncba idézőjelet akarunk írni. Mivel az idézőjelnek a karakterlánc kontextusában speciális jelentése van, és egyszerű beírása a karakterlánc lezárásaként lenne értelmezve, más, megfelelő szekvenciával kell beírni. Hogy pontosan milyennel, azt a kontextus szabályai határozzák meg. -A megkerülése .[#toc-escaping] ------------------------------- -Az Escaping az adott kontextusban különleges jelentéssel bíró karakterek átalakítása más, egyenértékű szekvenciákká. Példa: Idézőjeleket akarunk írni idézőjelekkel körülvett karakterláncba. Mivel az idézőjelek különleges jelentéssel bírnak az idézőjelekkel körülvett karakterlánc kontextusában, szükség van egy másik egyenértékű szekvencia használatára. A konkrét szekvenciát a kontextus szabályai határozzák meg (pl. `\"` a PHP idézőjelekkel zárt karakterláncában, `"` a HTML attribútumokban stb.). +Szűrő (korábban helper) +----------------------- +A sablonokban a [szűrő |latte:syntax#Szűrők] fogalma alatt általában egy olyan függvényt értünk, amely segít az adatok módosításában vagy újraformázásában a végső megjelenéshez. A sablonok rendelkeznek néhány [standard szűrővel |latte:filters]. -Szűrő (korábban Helper) .[#toc-filter-formerly-helper] ------------------------------------------------------- -Szűrő funkció. A sablonokban a [szűrő |latte:syntax#filters] egy olyan függvény, amely segít megváltoztatni vagy formázni az adatokat a kimeneti formára. A sablonok számos [szabványos szűrővel |latte:filters] rendelkeznek előre definiálva. +Érvénytelenítés +--------------- +Egy [#snippet] értesítése arról, hogy újra kell rajzolnia magát. Más jelentésben a cache tartalmának törlése is. -Érvénytelenítés .[#toc-invalidation] ------------------------------------- -Értesítés egy újraolvasandó [részletről |#snippet]. Más összefüggésben a gyorsítótár törlése is. +JSON +---- +Adatcsere formátum, amely a JavaScript szintaxisán alapul (annak részhalmaza). A pontos specifikációt a www.json.org oldalon találja. -JSON .[#toc-json] ------------------ -JavaScript szintaxison alapuló adatcsere formátum (ez annak részhalmaza). Pontos specifikációja a www.json.org oldalon található. +Komponens +--------- +Az alkalmazás újrafelhasználható része. Lehet egy oldal vizuális része, ahogy azt a [Komponensek írása |application:components] fejezet leírja, vagy a komponens fogalma alatt a [Component |component-model:] osztályt is értjük (egy ilyen komponensnek nem kell vizuálisnak lennie). -Komponens .[#toc-component] ---------------------------- -Egy alkalmazás újrafelhasználható része. Lehet egy oldal vizuális része, ahogyan azt az [application:components] fejezetben leírtuk, vagy a kifejezés állhat a [Component |component-model:] osztályra is (egy ilyen komponensnek nem kell vizuálisnak lennie). +Vezérlő karakterek +------------------ +A vezérlő karakterek láthatatlan karakterek, amelyek előfordulhatnak a szövegben, és esetleg problémákat okozhatnak. Tömeges eltávolításukhoz a fájlokból használhatja a [Code Checker|code-checker:] eszközt, és egy változóból való eltávolításukhoz a [Strings::normalize() |utils:strings#normalize] függvényt. -Vezérlő karakterek .[#toc-control-characters] ---------------------------------------------- -A vezérlő karakterek olyan láthatatlan karakterek, amelyek előfordulhatnak egy szövegben, és esetleg problémákat okozhatnak. A fájlokból való tömeges eltávolításukhoz a [Code Checker |code-checker:], a változókból való eltávolításukhoz a [Strings::normalize() |utils:strings#normalize] függvényt használhatjuk. +Eventek (események) +------------------- +Az esemény egy várt helyzet egy objektumban, amelynek bekövetkezésekor meghívódnak az ún. handlerek, azaz az eseményre reagáló callbackek ("példa":https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38). Esemény lehet például egy űrlap elküldése, egy felhasználó bejelentkezése stb. Az események tehát az *Inversion of Control* egy formája. +Például a felhasználó bejelentkezése a `Nette\Security\User::login()` metódusban történik. A `User` objektumnak van egy nyilvános `$onLoggedIn` változója, ami egy tömb, amelyhez bárki hozzáadhat egy callbacket. Abban a pillanatban, amikor a felhasználó bejelentkezik, a `login()` metódus meghívja az összes callbacket a tömbben. Az `onXyz` formátumú változónév egy konvenció, amelyet az egész Nette-ben használnak. -Események .[#toc-events] ------------------------- -Az esemény egy olyan várható helyzet az objektumban, amelynek bekövetkezésekor az úgynevezett kezelőket, azaz az eseményre reagáló visszahívókat ("minta":https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38) hívja meg. Az esemény lehet például űrlapküldés, felhasználói bejelentkezés stb. Az események tehát a *Inversion of Control* egy formája. -Például egy felhasználói bejelentkezés a `Nette\Security\User::login()` metódusban történik. A `User` objektumnak van egy nyilvános változója `$onLoggedIn`, amely egy tömb, amelyhez bárki hozzáadhat egy visszahívást. Amint a felhasználó bejelentkezik, a `login()` metódus meghívja a tömbben lévő összes callbacket. A változó neve a `onXyz` formában a Nette egész területén használt konvenció. +Latte +----- +Az egyik legfejlettebb [sablonrendszer |latte:]. -Latte .[#toc-latte] -------------------- -Az egyik leginnovatívabb [templating rendszer |latte:]. +Modell +------ +A modell az egész alkalmazás adat- és különösen funkcionális alapja. Tartalmazza az egész alkalmazáslogikát (üzleti logika kifejezést is használják). Ez az **M** az **M**VC-ből vagy MVP-ből. Bármilyen felhasználói művelet (bejelentkezés, áru kosárba helyezése, érték módosítása az adatbázisban) a modell egy műveletét jelenti. +A modell kezeli a belső állapotát, és kifelé egy szilárdan meghatározott interfészt kínál. Ennek az interfésznek a függvényeinek hívásával lekérdezhetjük vagy módosíthatjuk az állapotát. A modell nem tud a [#view] vagy a [#controller] létezéséről. -Modell .[#toc-model] --------------------- -A modell az egész alkalmazás adat- és funkcióalapját jelenti. Tartalmazza a teljes alkalmazási logikát (néha "üzleti logikának" is nevezik). Ez a **M** a **M**VC vagy MPV **M****-jének **M**. Minden felhasználói művelet (bejelentkezés, kosárba helyezés, adatbázis-érték módosítása) a modell műveletét jelenti. -A modell kezeli a belső állapotát, és nyilvános interfészt biztosít. Ennek az interfésznek a meghívásával vehetjük át vagy változtathatjuk meg az állapotát. A modell nem tud a [nézet |#view] vagy a [vezérlő |#controller] létezéséről, a modell teljesen független tőlük. +Model-View-Controller +--------------------- +Szoftverarchitektúra, amely abból az igényből született, hogy a grafikus felülettel rendelkező alkalmazásoknál szétválasszák a kezelő kódot ([#controller]) az alkalmazáslogikai kódtól ([#modell]) és az adatokat megjelenítő kódtól ([#view]). Ezzel egyrészt átláthatóbbá teszi az alkalmazást, megkönnyíti a jövőbeli fejlesztést, és lehetővé teszi az egyes részek külön tesztelését. -Modell-nézet-vezérlő .[#toc-model-view-controller] --------------------------------------------------- -Szoftverarchitektúra, amely a GUI-alkalmazások fejlesztésénél alakult ki, hogy elválassza a folyamatkezelés kódját ([controller |#controller]) az alkalmazási logika kódjától ([model |#model]) és az adatmegjelenítő kódtól ([view |#view]). Így a kód jobban érthető, megkönnyíti a későbbi fejlesztést, és lehetővé teszi a különálló részek külön tesztelését. +Model-View-Presenter +-------------------- +A [#Model-View-Controller]-ből származó architektúra. -Modell-nézet-bemutató .[#toc-model-view-presenter] --------------------------------------------------- -[Model-View-Controller |#Model-View-Controller] alapú architektúra. +Modul +----- +A modul az alkalmazás logikai része. Tipikus elrendezésben ez egy presenter- és sabloncsoport, amely egy adott funkcionalitási területet kezel. A modulokat [különálló könyvtárakba |application:directory-structure#Presenterek és sablonok] helyezzük, mint például `Front/`, `Admin/` vagy `Shop/`. +Például egy e-shopot feloszthatunk: +- Frontend (`Shop/`) a termékek böngészésére és vásárlásra +- Ügyfél szekció (`Customer/`) a rendelések kezelésére +- Adminisztráció (`Admin/`) az üzemeltető számára -Modul .[#toc-module] --------------------- -[A modul |application:modules] a Nette Frameworkben bemutatók és sablonok, esetleg komponensek és modellek gyűjteményét jelenti, amelyek adatokat szolgáltatnak egy bemutatónak. Tehát egy alkalmazás bizonyos logikai része. +Technikailag ezek hagyományos könyvtárak, de az áttekinthető tagolásnak köszönhetően segítenek az alkalmazás skálázásában. Az `Admin:Product:List` presenter fizikailag például az `app/Presentation/Admin/Product/List/` könyvtárban lesz elhelyezve (lásd [presenter mapping |application:directory-structure#Presenterek map-elése]). -Például egy webáruháznak három modulja lehet: -1) Termékkatalógus a kosárral. -2) Adminisztráció a vásárló számára. -3) Adminisztráció a boltvezető számára. +Namespace +--------- +Névtér, a PHP nyelv része az 5.3-as verziótól és néhány más programozási nyelvé, amely lehetővé teszi olyan osztályok használatát, amelyek különböző könyvtárakban ugyanazzal a névvel rendelkeznek, anélkül, hogy névütközés történne. Lásd a [PHP dokumentációt |https://www.php.net/manual/en/language.namespaces.rationale.php]. -Névtér .[#toc-namespace] ------------------------- -A névtér a PHP nyelv 5.3-as verziójától kezdve és néhány más programozási nyelvben is megtalálható. Segít elkerülni a névütközéseket (pl. két azonos nevű osztály), amikor különböző könyvtárakat használunk együtt. További részletekért lásd a [PHP dokumentációját |https://www.php.net/manual/en/language.namespaces.rationale.php]. +Presenter +--------- +A Presenter egy olyan objektum, amely fogadja a router által a HTTP kérésből lefordított [kérést |api:Nette\Application\Request], és generál egy [választ |api:Nette\Application\Response]. A válasz lehet egy HTML oldal, kép, XML dokumentum, fájl a lemezen, JSON, átirányítás vagy bármi, amit kitalál. -Bemutató .[#toc-presenter] --------------------------- -A Presenter egy objektum, amely átveszi a HTTP-kérésből a router által lefordított [kérést |api:Nette\Application\Request], és [választ |api:Nette\Application\Response] generál. A válasz lehet HTML oldal, kép, XML dokumentum, fájl, JSON, átirányítás vagy bármi, ami eszedbe jut. +Általában a presenter fogalma alatt a [api:Nette\Application\UI\Presenter] osztály leszármazottját értjük. A beérkező kérések alapján lefuttatja a megfelelő [akciókat |application:presenters#Presenter életciklusa] és rendereli a sablonokat. -Presenter alatt általában a [api:Nette\Application\UI\Presenter] osztály leszármazottját értik. A kérések alatt a megfelelő [műveleteket |application:presenters#life-cycle-of-presenter] futtatja és a sablonokat rendereli. +Router +------ +Kétirányú fordító a HTTP kérés / URL és a presenter akció között. A kétirányú azt jelenti, hogy a HTTP kérésből levezethető a [#presenter akció], de fordítva is, az akcióhoz generálható a megfelelő URL. További információ az [URL útválasztás |application:routing] fejezetben. -Router .[#toc-router] ---------------------- -Kétirányú fordító a HTTP-kérés/URL és a prezentáló művelet között. A kétirányúság azt jelenti, hogy nem csak a HTTP-kérésből lehet levezetni egy [prezenter-akciót |#presenter action], hanem a megfelelő URL-t is lehet generálni egy akcióhoz. Lásd bővebben az [URL útválasztásról |application:routing] szóló fejezetben. +SameSite cookie +--------------- +A SameSite cookie-k mechanizmust biztosítanak annak felismerésére, hogy mi vezetett az oldal betöltéséhez. Három értéke lehet: `Lax`, `Strict` és `None` (ez HTTPS-t igényel). Ha az oldalra irányuló kérés közvetlenül a webhelyről érkezik, vagy a felhasználó közvetlenül a címsorba írva vagy egy könyvjelzőre kattintva nyitja meg az oldalt, a böngésző elküldi a szervernek az összes cookie-t (azaz a `Lax`, `Strict` és `None` jelzőkkel). Ha a felhasználó egy másik webhelyről származó linken keresztül kattint a webhelyre, a `Lax` és `None` jelzőkkel ellátott cookie-k kerülnek átadásra a szervernek. Ha a kérés más módon jön létre, például egy másik webhelyről küldött POST űrlap elküldésével, iframe-en belüli betöltéssel, JavaScript segítségével stb., csak a `None` jelzővel ellátott cookie-k kerülnek elküldésre. -SameSite cookie .[#toc-samesite-cookie] ---------------------------------------- -A SameSite cookie-k egy olyan mechanizmust biztosítanak, amely felismeri, hogy mi vezetett az oldal betöltéséhez. Három értéke lehet: `Lax`, `Strict` és `None` (ez utóbbi HTTPS-t igényel). Ha az oldalra irányuló kérés közvetlenül a webhelyről érkezik, vagy a felhasználó közvetlenül a címsorba beírva vagy egy könyvjelzőre kattintva nyitja meg az oldalt, a böngésző az összes cookie-t elküldi a szervernek (azaz a `Lax`, `Strict` és `None` jelekkel). Ha a felhasználó egy másik webhelyről származó hivatkozáson keresztül kattint az oldalra, a `Lax` és `None` jelzővel ellátott cookie-kat továbbítja a szervernek. Ha a kérés más módon történik, például egy másik webhelyről származó POST űrlap elküldésével, iframe-en belüli betöltéssel, JavaScript használatával stb., csak a `None` jelű cookie-kat küldi a rendszer. +Szolgáltatás +------------ +A Dependency Injection kontextusában szolgáltatásnak nevezzük azt az objektumot, amelyet a DI konténer hoz létre és kezel. A szolgáltatás könnyen helyettesíthető egy másik implementációval, például tesztelési célokra vagy az alkalmazás viselkedésének megváltoztatására anélkül, hogy módosítani kellene a szolgáltatást használó kódot. -Szolgáltatás .[#toc-service] ----------------------------- -A Dependency Injection kontextusában a szolgáltatás olyan objektumra utal, amelyet egy DI konténer hoz létre és kezel. Egy szolgáltatás könnyen lecserélhető egy másik implementációra, például tesztelési céllal vagy az alkalmazás viselkedésének megváltoztatására, anélkül, hogy a szolgáltatást használó kódot módosítani kellene. +Snippet +------- +Kivágás, az oldal egy része, amelyet önállóan újra lehet rajzolni egy AJAX kérés során. -Snippet .[#toc-snippet] ------------------------ -Egy olyan oldalrészlet, amely egy [AJAX-kérés |#AJAX] során külön-külön újra megjeleníthető. + +View +---- +A View, azaz a nézet, az alkalmazás azon rétege, amely a kérés eredményének megjelenítéséért felelős. Általában sablonrendszert használ, és tudja, hogyan kell megjeleníteni az adott komponenst vagy a modellből kapott eredményt. -Nézet .[#toc-view] ------------------- -A nézet az alkalmazás egy olyan rétege, amely a kérés eredményeinek megjelenítéséért felelős. Általában templating rendszert használ, és tudja, hogyan kell megjeleníteni a modellből vett komponenseket vagy eredményeket. diff --git a/nette/hu/installation.texy b/nette/hu/installation.texy index a390238849..646a75944e 100644 --- a/nette/hu/installation.texy +++ b/nette/hu/installation.texy @@ -1,67 +1,67 @@ -Nette telepítése +Nette Telepítése **************** .[perex] -Szeretné kihasználni a Nette előnyeit meglévő projektjében, vagy egy új projektet tervez létrehozni a Nette alapján? Ez az útmutató lépésről lépésre végigvezet a telepítésen. +Szeretné kihasználni a Nette előnyeit meglévő projektjében, vagy új projektet készül létrehozni a Nette alapjain? Ez az útmutató lépésről lépésre végigvezeti a telepítésen. -A Nette hozzáadása a projektjéhez .[#toc-how-to-add-nette-to-your-project] --------------------------------------------------------------------------- +Hogyan adjuk hozzá a Nette-t a projektünkhöz +-------------------------------------------- -A Nette hasznos és kifinomult csomagok (könyvtárak) gyűjteményét kínálja a PHP számára. Ahhoz, hogy beépítse őket a projektjébe, kövesse az alábbi lépéseket: +A Nette hasznos és kifinomult PHP csomagok (könyvtárak) gyűjteményét kínálja. Ahhoz, hogy beépítse őket a projektjébe, kövesse az alábbi lépéseket: -1) **A [Composer |best-practices:composer] beállítása:** Ez az eszköz elengedhetetlen a projekthez szükséges könyvtárak egyszerű telepítéséhez, frissítéséhez és kezeléséhez. +1) **Készítse elő a [Composer-t|best-practices:composer]:** Ez az eszköz elengedhetetlen a projektjéhez szükséges könyvtárak egyszerű telepítéséhez, frissítéséhez és kezeléséhez. -2) **Válasszon ki egy [csomagot |www:packages]:** Tegyük fel, hogy a fájlrendszerben kell navigálnia, amit a `nette/utils` csomagból származó [Finder |utils:finder] kiválóan tud. A csomag nevét a dokumentációjának jobb oldali oszlopában találod. +2) **Válasszon egy [csomagot|www:packages]:** Tegyük fel, hogy böngésznie kell a fájlrendszerben, amit a `nette/utils` csomagból származó [Finder|utils:finder] kiválóan megtesz. A csomag nevét a dokumentáció jobb oldali oszlopában láthatja. -3) **Telepítsd a csomagot:** Futtasd ezt a parancsot a projekted gyökérkönyvtárában: +3) **Telepítse a csomagot:** Futtassa ezt a parancsot a projekt gyökérkönyvtárában: ```shell composer require nette/utils ``` -Grafikus felületet szeretne? Tekintse meg a csomagok telepítéséről szóló [útmutatót |https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] a PhpStrom környezetben. +Grafikus felületet részesít előnyben? Tekintse meg az [útmutatót|https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] a csomagok telepítéséhez a PhpStorm környezetben. -Új projekt indítása a Nette segítségével .[#toc-how-to-start-a-new-project-with-nette] --------------------------------------------------------------------------------------- +Hogyan hozzunk létre új projektet a Nette-tel +--------------------------------------------- -Ha egy teljesen új projektet szeretne létrehozni a Nette platformon, javasoljuk, hogy használja az előre beállított vázlatos [webes projektet |https://github.com/nette/web-project]: +Ha teljesen új projektet szeretne létrehozni a Nette platformon, javasoljuk az előre beállított [Web Project|https://github.com/nette/web-project] skeleton használatát: -1) **Beállítása a [Composer |best-practices:composer].** +1) **Készítse elő a [Composer-t|best-practices:composer].** -2) **Nyissa meg a parancssort** és navigáljon a webszerver gyökérkönyvtárába, pl. `/etc/var/www`, `C:/xampp/htdocs`, `/Library/WebServer/Documents`. +2) **Nyissa meg a parancssort** és lépjen a webszerver gyökérkönyvtárába, pl. `/etc/var/www`, `C:/xampp/htdocs`, `/Library/WebServer/Documents`. 3) **Hozza létre a projektet** ezzel a paranccsal: ```shell -composer create-project nette/web-project PROJECT_NAME +composer create-project nette/web-project PROJEKT_NEVE ``` -4) **Nem használja a Composert?** Csak töltse le a [webes projektet ZIP formátumban |https://github.com/nette/web-project/archive/preloaded.zip], és csomagolja ki. De bízzon bennünk, a Composer megéri! +4) **Nem használ Composer-t?** Csak töltse le a [Web Project-et ZIP formátumban|https://github.com/nette/web-project/archive/preloaded.zip] és csomagolja ki. De higgye el, a Composer megéri! -5) **Jogosultságok beállítása:** MacOS vagy Linux rendszereken állítsa be a könyvtárak [írási jogosultságait |nette:troubleshooting#Setting directory permissions]. +5) **Jogosultságok beállítása:** macOS vagy Linux rendszereken állítsa be az [írási jogosultságokat |nette:troubleshooting#Könyvtárjogosultságok beállítása] a könyvtárakhoz. -6) **Nyissa meg a projektet egy böngészőben:** Adja meg a `http://localhost/PROJECT_NAME/www/` URL címet. Megjelenik a vázlat landing oldala: +6) **Projekt megnyitása a böngészőben:** Írja be az URL-t `http://localhost/PROJEKT_NEVE/www/` és látni fogja a skeleton üdvözlő oldalát: -[* qs-welcome.webp .{url: http://localhost/PROJECT_NAME/www/} *] +[* qs-welcome.webp .{url: http://localhost/PROJEKT_NEVE/www/} *] -Gratulálunk! A webhelye most már készen áll a fejlesztésre. Nyugodtan távolítsa el az üdvözlő sablont, és kezdje el az alkalmazás építését. +Gratulálunk! A webhelye most készen áll a fejlesztésre. Az üdvözlő sablont eltávolíthatja, és elkezdheti létrehozni az alkalmazását. -A Nette egyik előnye, hogy a projekt azonnal, konfigurálás nélkül működik. Ha azonban bármilyen problémával találkozik, fontolja meg, hogy megnézi a [közös problémamegoldásokat |nette:troubleshooting#nette-is-not-working-white-page-is-displayed]. +A Nette egyik előnye, hogy a projekt azonnal működik konfiguráció nélkül. Ha azonban problémákba ütközik, próbálja meg megnézni a [gyakori problémák megoldásait |nette:troubleshooting#Nem működik a Nette fehér oldal jelenik meg]. .[note] -Ha a Nette-tel kezd, javasoljuk, hogy folytassa az [Első alkalmazás létrehozása |quickstart:] című [bemutatót |quickstart:]. +Ha most kezdi a Nette-tel, javasoljuk, hogy folytassa az [Első alkalmazás írása|quickstart:] oktatóanyaggal. -Eszközök és ajánlások .[#toc-tools-and-recommendations] -------------------------------------------------------- +Eszközök és ajánlások +--------------------- -A Nette-tel való hatékony munkavégzéshez a következő eszközöket ajánljuk: +A Nette-tel való hatékony munkához a következő eszközöket javasoljuk: -- [Kiváló minőségű IDE a Nette-hez való bővítményekkel |best-practices:editors-and-tools] +- [Minőségi IDE Nette kiegészítőkkel|best-practices:editors-and-tools] - Git verziókezelő rendszer -- [Composer |best-practices:composer] +- [Composer|best-practices:composer] {{leftbar: www:@menu-common}} diff --git a/nette/hu/introduction-to-object-oriented-programming.texy b/nette/hu/introduction-to-object-oriented-programming.texy new file mode 100644 index 0000000000..33bc01fa9c --- /dev/null +++ b/nette/hu/introduction-to-object-oriented-programming.texy @@ -0,0 +1,841 @@ +Bevezetés az objektumorientált programozásba +******************************************** + +.[perex] +Az "OOP" kifejezés az objektumorientált programozást jelenti, amely a kód szervezésének és strukturálásának egy módja. Az OOP lehetővé teszi számunkra, hogy a programot egymással kommunikáló objektumok halmazaként tekintsük, nem pedig utasítások és függvények sorozataként. + +Az OOP-ban az "objektum" egy olyan egység, amely adatokat és azokkal az adatokkal dolgozó függvényeket tartalmaz. Az objektumok "osztályok" alapján jönnek létre, amelyeket az objektumok terveiként vagy sablonjaiként foghatunk fel. Ha van egy osztályunk, létrehozhatjuk annak "példányát", ami egy konkrét, az osztály alapján létrehozott objektum. + +Nézzük meg, hogyan hozhatunk létre egy egyszerű osztályt PHP-ban. Az osztály definiálásakor a "class" kulcsszót használjuk, amelyet az osztály neve követ, majd kapcsos zárójelek, amelyek az osztály függvényeit (ezeket "metódusoknak" nevezzük) és változóit (ezeket "property-knek" vagy "tulajdonságoknak" nevezzük) foglalják magukba: + +```php +class Auto +{ + function dudal() + { + echo 'Bip bip!'; + } +} +``` + +Ebben a példában létrehoztunk egy `Auto` nevű osztályt egyetlen `dudal` nevű függvénnyel (vagy "metódussal"). + +Minden osztálynak csak egy fő feladatot kellene megoldania. Ha egy osztály túl sok mindent csinál, érdemes lehet kisebb, specializált osztályokra bontani. + +Az osztályokat általában külön fájlokba mentjük, hogy a kód rendezett és könnyen áttekinthető legyen. A fájl nevének meg kell egyeznie az osztály nevével, tehát az `Auto` osztály esetében a fájl neve `Auto.php` lenne. + +Az osztályok elnevezésekor jó gyakorlat a "PascalCase" konvenció követése, ami azt jelenti, hogy a név minden szava nagybetűvel kezdődik, és nincsenek közöttük aláhúzások vagy más elválasztójelek. A metódusok és property-k a "camelCase" konvenció szerint kisbetűvel kezdődnek. + +Néhány PHP metódusnak speciális feladata van, és `__` (két aláhúzás) előtaggal vannak jelölve. Az egyik legfontosabb speciális metódus a "konstruktor", amelyet `__construct`-ként jelölünk. A konstruktor egy olyan metódus, amely automatikusan meghívódik, amikor létrehozunk egy új osztálypéldányt. + +A konstruktort gyakran használjuk az objektum kezdeti állapotának beállítására. Például, amikor egy személyt reprezentáló objektumot hozunk létre, a konstruktort használhatjuk a korának, nevének vagy más tulajdonságainak beállítására. + +Nézzük meg, hogyan használhatunk konstruktort PHP-ban: + +```php +class Szemely +{ + private $kor; + + function __construct($kor) + { + $this->kor = $kor; + } + + function hanyEvesVagy() + { + return $this->kor; + } +} + +$szemely = new Szemely(25); +echo $szemely->hanyEvesVagy(); // Kiírja: 25 +``` + +Ebben a példában a `Szemely` osztálynak van egy `$kor` property-je (változója) és egy konstruktora, amely ezt a property-t állítja be. A `hanyEvesVagy()` metódus ezután lehetővé teszi a személy korához való hozzáférést. + +A `$this` pszeudo-változót az osztályon belül használjuk az objektum property-jeihez és metódusaihoz való hozzáféréshez. + +A `new` kulcsszót használjuk egy új osztálypéldány létrehozásához. A fenti példában egy új, 25 éves személyt hoztunk létre. + +Beállíthatunk alapértelmezett értékeket is a konstruktor paramétereinek, ha azokat nem adjuk meg az objektum létrehozásakor. Például: + +```php +class Szemely +{ + private $kor; + + function __construct($kor = 20) + { + $this->kor = $kor; + } + + function hanyEvesVagy() + { + return $this->kor; + } +} + +$szemely = new Szemely; // ha nem adunk át argumentumot, a zárójelek elhagyhatók +echo $szemely->hanyEvesVagy(); // Kiírja: 20 +``` + +Ebben a példában, ha nem adja meg a kort a `Szemely` objektum létrehozásakor, az alapértelmezett 20-as érték lesz használva. + +Kellemes, hogy a property definíciója és annak konstruktoron keresztüli inicializálása így lerövidíthető és egyszerűsíthető: + +```php +class Szemely +{ + function __construct( + private $kor = 20, + ) { + } +} +``` + +A teljesség kedvéért, a konstruktorok mellett az objektumoknak lehetnek destruktoraik is (a `__destruct` metódus), amelyek azelőtt hívódnak meg, mielőtt az objektum felszabadulna a memóriából. + + +Névterek +-------- + +A névterek (angolul "namespaces") lehetővé teszik számunkra, hogy a kapcsolódó osztályokat, függvényeket és konstansokat szervezzük és csoportosítsuk, miközben elkerüljük a névütközéseket. Úgy képzelhetjük el őket, mint mappákat a számítógépen, ahol minden mappa egy adott projekthez vagy témához tartozó fájlokat tartalmaz. + +A névterek különösen hasznosak nagyobb projektekben, vagy amikor harmadik féltől származó könyvtárakat használunk, ahol osztálynév-ütközések léphetnek fel. + +Képzeljük el, hogy van egy `Auto` nevű osztályunk a projektünkben, és szeretnénk azt egy `Doprava` nevű névtérbe helyezni. Ezt így tehetjük meg: + +```php +namespace Doprava; + +class Auto +{ + function dudal() + { + echo 'Bip bip!'; + } +} +``` + +Ha az `Auto` osztályt egy másik fájlban szeretnénk használni, meg kell adnunk, hogy melyik névtérből származik az osztály: + +```php +$auto = new Doprava\Auto; +``` + +Az egyszerűsítés érdekében a fájl elején megadhatjuk, hogy melyik osztályt szeretnénk használni az adott névtérből, ami lehetővé teszi a példányok létrehozását anélkül, hogy a teljes elérési utat meg kellene adni: + +```php +use Doprava\Auto; + +$auto = new Auto; +``` + + +Öröklődés +--------- + +Az öröklődés az objektumorientált programozás egyik eszköze, amely lehetővé teszi új osztályok létrehozását már létező osztályok alapján, átvéve azok property-jeit és metódusait, és szükség szerint kiterjesztve vagy újradefiniálva azokat. Az öröklődés lehetővé teszi a kód újrafelhasználhatóságát és az osztályhierarchiát. + +Egyszerűen fogalmazva, ha van egy osztályunk, és szeretnénk egy másikat létrehozni, amely abból származik, de néhány változtatással, akkor az új osztályt "örökölhetjük" az eredeti osztályból. + +PHP-ban az öröklődést az `extends` kulcsszóval valósítjuk meg. + +A `Szemely` osztályunk tárolja a kor információt. Lehet egy másik `Diak` osztályunk, amely kiterjeszti a `Szemely`-t, és hozzáadja a tanulmányi szak információját. + +Nézzünk egy példát: + +```php +class Szemely +{ + private $kor; + + function __construct($kor) + { + $this->kor = $kor; + } + + function informacioKiirasa() + { + echo "Kor: {$this->kor} év\n"; + } +} + +class Diak extends Szemely +{ + private $szak; + + function __construct($kor, $szak) + { + parent::__construct($kor); + $this->szak = $szak; + } + + function informacioKiirasa() + { + parent::informacioKiirasa(); + echo "Tanulmányi szak: {$this->szak} \n"; + } +} + +$diak = new Diak(20, 'Informatika'); +$diak->informacioKiirasa(); +``` + +Hogyan működik ez a kód? + +- Az `extends` kulcsszót használtuk a `Szemely` osztály kiterjesztéséhez, ami azt jelenti, hogy a `Diak` osztály örökli az összes metódust és property-t a `Szemely`-től. + +- A `parent::` kulcsszó lehetővé teszi számunkra, hogy metódusokat hívjunk a szülő osztályból. Ebben az esetben a `Szemely` osztály konstruktorát hívtuk meg, mielőtt saját funkcionalitást adtunk volna hozzá a `Diak` osztályhoz. És hasonlóképpen az ős `informacioKiirasa()` metódusát is, mielőtt kiírtuk volna a diákra vonatkozó információkat. + +Az öröklődés olyan helyzetekre való, amikor "egy" kapcsolat (is-a relationship) áll fenn az osztályok között. Például a `Diak` egy `Szemely`. A macska egy állat. Lehetőséget ad nekünk arra, hogy olyan esetekben, amikor a kódban egy objektumot (pl. "Szemely") várunk, helyette egy öröklött objektumot (pl. "Diak") használjunk. + +Fontos megjegyezni, hogy az öröklődés fő célja **nem** a kódduplikáció elkerülése. Éppen ellenkezőleg, az öröklődés helytelen használata bonyolult és nehezen karbantartható kódhoz vezethet. Ha az "egy" kapcsolat nem létezik az osztályok között, az öröklődés helyett a kompozíciót kellene fontolóra vennünk. + +Vegyük észre, hogy a `Szemely` és `Diak` osztályok `informacioKiirasa()` metódusai kissé eltérő információkat írnak ki. És hozzáadhatunk további osztályokat (például `Alkalmazott`), amelyek további implementációkat biztosítanak ehhez a metódushoz. Azt a képességet, hogy különböző osztályok objektumai ugyanarra a metódusra különböző módon reagáljanak, polimorfizmusnak nevezzük: + +```php +$szemelyek = [ + new Szemely(30), + new Diak(20, 'Informatika'), + new Alkalmazott(45, 'Igazgató'), +]; + +foreach ($szemelyek as $szemely) { + $szemely->informacioKiirasa(); +} +``` + + +Kompozíció +---------- + +A kompozíció egy olyan technika, amikor ahelyett, hogy egy másik osztály property-jeit és metódusait örökölnénk, egyszerűen felhasználjuk annak példányát a saját osztályunkban. Ez lehetővé teszi számunkra, hogy több osztály funkcionalitását és property-jeit kombináljuk anélkül, hogy bonyolult öröklődési struktúrákat kellene létrehoznunk. + +Nézzünk egy példát. Van egy `Motor` osztályunk és egy `Auto` osztályunk. Ahelyett, hogy azt mondanánk "Az Autó egy Motor", azt mondjuk "Az Autónak van Motorja", ami egy tipikus kompozíciós kapcsolat. + +```php +class Motor +{ + function bekapcsol() + { + echo 'Motor fut.'; + } +} + +class Auto +{ + private $motor; + + function __construct() + { + $this->motor = new Motor; + } + + function indit() + { + $this->motor->bekapcsol(); + echo 'Az autó készen áll az indulásra!'; + } +} + +$auto = new Auto; +$auto->indit(); +``` + +Itt az `Auto` nem rendelkezik a `Motor` összes property-jével és metódusával, de hozzáfér hozzá a `$motor` property-n keresztül. + +A kompozíció előnye a nagyobb tervezési flexibilitás és a jövőbeli módosítások jobb lehetősége. + + +Láthatóság +---------- + +PHP-ban definiálhatunk "láthatóságot" az osztály property-jeire, metódusaira és konstansaira. A láthatóság határozza meg, hogy honnan férhetünk hozzá ezekhez az elemekhez. + +1. **Public:** Ha egy elem `public`-ként van megjelölve, az azt jelenti, hogy bárhonnan hozzáférhetünk, akár az osztályon kívülről is. + +2. **Protected:** A `protected` jelölésű elem csak az adott osztályon belül és annak minden leszármazottjában (azok az osztályok, amelyek ebből az osztályból örökölnek) érhető el. + +3. **Private:** Ha egy elem `private`, akkor csak azon az osztályon belülről férhetünk hozzá, amelyben definiálva lett. + +Ha nem adunk meg láthatóságot, a PHP automatikusan `public`-ra állítja. + +Nézzünk egy példakódot: + +```php +class LathatosagDemonstracio +{ + public $nyilvanosTulajdonsag = 'Nyilvános'; + protected $vedettTulajdonsag = 'Védett'; + private $privatTulajdonsag = 'Privát'; + + public function tulajdonsagokKiirasa() + { + echo $this->nyilvanosTulajdonsag; // Működik + echo $this->vedettTulajdonsag; // Működik + echo $this->privatTulajdonsag; // Működik + } +} + +$objektum = new LathatosagDemonstracio; +$objektum->tulajdonsagokKiirasa(); +echo $objektum->nyilvanosTulajdonsag; // Működik +// echo $objektum->vedettTulajdonsag; // Hibát dob +// echo $objektum->privatTulajdonsag; // Hibát dob +``` + +Folytassuk az osztály öröklésével: + +```php +class OsztalyLeszarmazott extends LathatosagDemonstracio +{ + public function tulajdonsagokKiirasa() + { + echo $this->nyilvanosTulajdonsag; // Működik + echo $this->vedettTulajdonsag; // Működik + // echo $this->privatTulajdonsag; // Hibát dob + } +} +``` + +Ebben az esetben az `OsztalyLeszarmazott` osztály `tulajdonsagokKiirasa()` metódusa hozzáférhet a nyilvános és védett property-khez, de nem férhet hozzá a szülő osztály privát property-jeihez. + +Az adatokat és metódusokat a lehető legjobban el kell rejteni, és csak egy definiált interfészen keresztül szabad hozzáférni hozzájuk. Ez lehetővé teszi az osztály belső implementációjának megváltoztatását anélkül, hogy a kód többi részét befolyásolná. + + +A `final` kulcsszó +------------------ + +PHP-ban használhatjuk a `final` kulcsszót, ha meg akarjuk akadályozni egy osztály, metódus vagy konstans öröklését vagy felülírását. Ha egy osztályt `final`-ként jelölünk meg, nem lehet kiterjeszteni. Ha egy metódust `final`-ként jelölünk meg, azt nem lehet felülírni egy leszármazott osztályban. + +Annak tudata, hogy egy bizonyos osztály vagy metódus nem lesz tovább módosítva, lehetővé teszi számunkra, hogy könnyebben végezzünk módosításokat anélkül, hogy aggódnunk kellene a lehetséges konfliktusok miatt. Például hozzáadhatunk egy új metódust anélkül, hogy attól tartanánk, hogy valamelyik leszármazottjának már van egy azonos nevű metódusa, és ütközés történne. Vagy megváltoztathatjuk egy metódus paramétereit, mivel itt sem fenyeget az a veszély, hogy ellentmondást okozunk egy leszármazottban felülírt metódussal. + +```php +final class VeglegesOsztaly +{ +} + +// A következő kód hibát okoz, mert nem örökölhetünk a final osztályból. +class VeglegesOsztalyLeszarmazott extends VeglegesOsztaly +{ +} +``` + +Ebben a példában a `VeglegesOsztaly` final osztályból való öröklési kísérlet hibát okoz. + + +Statikus property-k és metódusok +-------------------------------- + +Amikor PHP-ban "statikus" osztályelemekről beszélünk, olyan metódusokra és property-kre gondolunk, amelyek magához az osztályhoz tartoznak, nem pedig az osztály egy konkrét példányához. Ez azt jelenti, hogy nem kell létrehoznia az osztály egy példányát ahhoz, hogy hozzáférjen hozzájuk. Ehelyett közvetlenül az osztály nevén keresztül hívja meg vagy éri el őket. + +Ne feledje, hogy mivel a statikus elemek az osztályhoz tartoznak, és nem annak példányaihoz, a statikus metódusokon belül nem használhatja a `$this` pszeudo-változót. + +A statikus property-k használata [átláthatatlan, buktatókkal teli kódhoz|dependency-injection:global-state] vezet, ezért soha ne használja őket, és itt nem is mutatunk példát a használatukra. Ezzel szemben a statikus metódusok hasznosak. Példa a használatra: + +```php +class Szamologep +{ + public static function osszeadas($a, $b) + { + return $a + $b; + } + + public static function kivonas($a, $b) + { + return $a - $b; + } +} + +// Statikus metódus használata az osztály példányosítása nélkül +echo Szamologep::osszeadas(5, 3); // Eredmény: 8 +echo Szamologep::kivonas(5, 3); // Eredmény: 2 +``` + +Ebben a példában létrehoztunk egy `Szamologep` osztályt két statikus metódussal. Ezeket a metódusokat közvetlenül, az osztály példányának létrehozása nélkül hívhatjuk meg a `::` operátor segítségével. A statikus metódusok különösen hasznosak olyan műveletekhez, amelyek nem függenek egy konkrét osztálypéldány állapotától. + + +Osztálykonstansok +----------------- + +Az osztályokon belül lehetőségünk van konstansok definiálására. A konstansok olyan értékek, amelyek soha nem változnak a program futása során. A változókkal ellentétben a konstans értéke mindig ugyanaz marad. + +```php +class Auto +{ + public const KerekekSzama = 4; + + public function kerekekSzamaMegjelenitese(): int + { + echo self::KerekekSzama; + } +} + +echo Auto::KerekekSzama; // Kimenet: 4 +``` + +Ebben a példában van egy `Auto` osztályunk a `KerekekSzama` konstanssal. Ha az osztályon belül szeretnénk hozzáférni a konstanshoz, az osztály neve helyett a `self` kulcsszót használhatjuk. + + +Objektuminterfészek +------------------- + +Az objektuminterfészek "szerződésekként" működnek az osztályok számára. Ha egy osztálynak implementálnia kell egy objektuminterfészt, tartalmaznia kell az összes metódust, amelyet ez az interfész definiál. Ez egy nagyszerű módja annak biztosítására, hogy bizonyos osztályok ugyanazt a "szerződést" vagy struktúrát kövessék. + +PHP-ban az interfészt az `interface` kulcsszóval definiáljuk. Az interfészben definiált összes metódus public (`public`). Amikor egy osztály implementál egy interfészt, az `implements` kulcsszót használja. + +```php +interface Allat +{ + function hangotAd(); +} + +class Macska implements Allat +{ + public function hangotAd() + { + echo 'Mňau'; + } +} + +$macska = new Macska; +$macska->hangotAd(); +``` + +Ha egy osztály implementál egy interfészt, de nem definiálja az összes elvárt metódust, a PHP hibát dob. + +Egy osztály egyszerre több interfészt is implementálhat, ami eltérés az öröklődéstől, ahol egy osztály csak egyetlen osztálytól örökölhet: + +```php +interface Orzo +{ + function hazatOriz(); +} + +class Kutya implements Allat, Orzo +{ + public function hangotAd() + { + echo 'Haf'; + } + + public function hazatOriz() + { + echo 'A kutya gondosan őrzi a házat'; + } +} +``` + + +Absztrakt osztályok +------------------- + +Az absztrakt osztályok alap sablonként szolgálnak más osztályok számára, de nem hozhat létre közvetlenül példányokat belőlük. Tartalmaznak teljes metódusok és absztrakt metódusok kombinációját, amelyeknek nincs definiált tartalmuk. Azok az osztályok, amelyek absztrakt osztályokból örökölnek, meg kell adniuk az összes absztrakt metódus definícióját az ősből. + +Absztrakt osztály definiálásához az `abstract` kulcsszót használjuk. + +```php +abstract class AbsztraktOsztaly +{ + public function normalMetodus() + { + echo 'Ez egy normál metódus'; + } + + abstract public function absztraktMetodus(); +} + +class Leszarmazott extends AbsztraktOsztaly +{ + public function absztraktMetodus() + { + echo 'Ez az absztrakt metódus implementációja'; + } +} + +$peldany = new Leszarmazott; +$peldany->normalMetodus(); +$peldany->absztraktMetodus(); +``` + +Ebben a példában van egy absztrakt osztályunk egy normál és egy absztrakt metódussal. Ezután van egy `Leszarmazott` osztályunk, amely az `AbsztraktOsztaly`-ból örököl, és implementációt biztosít az absztrakt metódushoz. + +Miben különböznek valójában az interfészek és az absztrakt osztályok? Az absztrakt osztályok tartalmazhatnak absztrakt és konkrét metódusokat is, míg az interfészek csak azt definiálják, hogy egy osztálynak milyen metódusokat kell implementálnia, de nem nyújtanak semmilyen implementációt. Egy osztály csak egy absztrakt osztálytól örökölhet, de tetszőleges számú interfészt implementálhat. + + +Típusellenőrzés +--------------- + +A programozásban nagyon fontos biztosnak lenni abban, hogy az adatok, amelyekkel dolgozunk, a megfelelő típusúak. PHP-ban vannak eszközeink, amelyek ezt biztosítják számunkra. Annak ellenőrzését, hogy az adatok megfelelő típusúak-e, "típusellenőrzésnek" nevezzük. + +Típusok, amelyekkel PHP-ban találkozhatunk: + +1. **Alaptípusok**: Ide tartoznak az `int` (egész számok), `float` (lebegőpontos számok), `bool` (logikai értékek), `string` (karakterláncok), `array` (tömbök) és `null`. +2. **Osztályok**: Ha azt szeretnénk, hogy az érték egy specifikus osztály példánya legyen. +3. **Interfészek**: Metódusok halmazát definiálja, amelyeket egy osztálynak implementálnia kell. Az interfésznek megfelelő értéknek rendelkeznie kell ezekkel a metódusokkal. +4. **Vegyes típusok**: Meghatározhatjuk, hogy egy változó több megengedett típussal is rendelkezhet. +5. **Void**: Ez a speciális típus azt jelzi, hogy egy függvény vagy metódus nem ad vissza értéket. + +Nézzük meg, hogyan módosíthatjuk a kódot, hogy tartalmazza a típusokat: + +```php +class Szemely +{ + private int $kor; + + public function __construct(int $kor) + { + $this->kor = $kor; + } + + public function korKiirasa(): void + { + echo "Ez a személy {$this->kor} éves."; + } +} + +/** + * Függvény, amely egy Szemely osztály objektumát fogadja el és kiírja a személy korát. + */ +function szemelyKoranakKiirasa(Szemely $szemely): void +{ + $szemely->korKiirasa(); +} +``` + +Ezzel a módszerrel biztosítottuk, hogy a kódunk a megfelelő típusú adatokat várja és dolgozza fel, ami segít megelőzni a potenciális hibákat. + +Néhány típust nem lehet közvetlenül PHP-ban leírni. Ebben az esetben a phpDoc kommentben adjuk meg, ami a PHP kód dokumentálásának szabványos formátuma, `/**`-el kezdődik és `*/`-el végződik. Lehetővé teszi osztályok, metódusok stb. leírásának hozzáadását. És komplex típusok megadását is ún. annotációk `@var`, `@param` és `@return` segítségével. Ezeket a típusokat aztán a statikus kódelemző eszközök használják, de maga a PHP nem ellenőrzi őket. + +```php +class Lista +{ + /** @var array<Szemely> a jelölés azt mondja, hogy Szemely objektumok tömbjéről van szó */ + private array $szemelyek = []; + + public function szemelyHozzaadasa(Szemely $szemely): void + { + $this->szemelyek[] = $szemely; + } +} +``` + + +Összehasonlítás és azonosság +---------------------------- + +PHP-ban kétféleképpen hasonlíthatunk össze objektumokat: + +1. Érték szerinti összehasonlítás `==`: Ellenőrzi, hogy az objektumok azonos osztályúak-e és azonos értékekkel rendelkeznek-e a property-jeikben. +2. Azonosság `===`: Ellenőrzi, hogy ugyanarról az objektumpéldányról van-e szó. + +```php +class Auto +{ + public string $marka; + + public function __construct(string $marka) + { + $this->marka = $marka; + } +} + +$auto1 = new Auto('Skoda'); +$auto2 = new Auto('Skoda'); +$auto3 = $auto1; + +var_dump($auto1 == $auto2); // true, mert azonos értékűek +var_dump($auto1 === $auto2); // false, mert nem ugyanaz a példány +var_dump($auto1 === $auto3); // true, mert $auto3 ugyanaz a példány, mint $auto1 +``` + + +Az `instanceof` operátor +------------------------ + +Az `instanceof` operátor lehetővé teszi annak megállapítását, hogy egy adott objektum egy bizonyos osztály példánya-e, ennek az osztálynak a leszármazottja-e, vagy implementál-e egy bizonyos interfészt. + +Képzeljük el, hogy van egy `Szemely` osztályunk és egy másik `Diak` osztályunk, amely a `Szemely` osztály leszármazottja: + +```php +class Szemely +{ + private int $kor; + + public function __construct(int $kor) + { + $this->kor = $kor; + } +} + +class Diak extends Szemely +{ + private string $szak; + + public function __construct(int $kor, string $szak) + { + parent::__construct($kor); + $this->szak = $szak; + } +} + +$diak = new Diak(20, 'Informatika'); + +// Ellenőrzés, hogy $diak a Diak osztály példánya-e +var_dump($diak instanceof Diak); // Kimenet: bool(true) + +// Ellenőrzés, hogy $diak a Szemely osztály példánya-e (mivel a Diak a Szemely leszármazottja) +var_dump($diak instanceof Szemely); // Kimenet: bool(true) +``` + +A kimenetekből látható, hogy a `$diak` objektum egyszerre mindkét osztály - `Diak` és `Szemely` - példányának tekintendő. + + +Fluent Interfészek +------------------ + +A "Fluent Interface" (magyarul "Fluent Interfész") egy technika az OOP-ban, amely lehetővé teszi a metódusok láncolását egyetlen hívásban. Ez gyakran egyszerűsíti és átláthatóbbá teszi a kódot. + +A fluent interfész kulcsfontosságú eleme, hogy a lánc minden metódusa hivatkozást ad vissza az aktuális objektumra. Ezt úgy érjük el, hogy a metódus végén `return $this;`-t használunk. Ezt a programozási stílust gyakran "settereknek" nevezett metódusokkal társítják, amelyek az objektum property-jeinek értékeit állítják be. + +Mutassuk be, hogyan nézhet ki egy fluent interfész egy e-mail küldési példán: + +```php +public function uzenetKuldese() +{ + $email = new Email; + $email->setFelado('sender@example.com') + ->setCimzett('admin@example.com') + ->setUzenet('Hello, this is a message.') + ->kuldes(); +} +``` + +Ebben a példában a `setFelado()`, `setCimzett()` és `setUzenet()` metódusok a megfelelő értékek (feladó, címzett, üzenet tartalma) beállítására szolgálnak. Miután beállítottuk mindegyik értéket, a metódusok visszaadják az aktuális objektumot (`$email`), ami lehetővé teszi, hogy egy másik metódust láncoljunk utána. Végül meghívjuk a `kuldes()` metódust, amely ténylegesen elküldi az e-mailt. + +A fluent interfészeknek köszönhetően olyan kódot írhatunk, amely intuitív és könnyen olvasható. + + +Másolás a `clone` segítségével +------------------------------ + +PHP-ban létrehozhatunk egy objektum másolatát a `clone` operátor segítségével. Ezzel a módszerrel egy új, azonos tartalmú példányt kapunk. + +Ha az objektum másolásakor módosítani kell néhány property-jét, definiálhatunk egy speciális `__clone()` metódust az osztályban. Ez a metódus automatikusan meghívódik, amikor az objektumot klónozzák. + +```php +class Barany +{ + public string $nev; + + public function __construct(string $nev) + { + $this->nev = $nev; + } + + public function __clone() + { + $this->nev = 'Klón ' . $this->nev; + } +} + +$eredeti = new Barany('Dolly'); +echo $eredeti->nev . "\n"; // Kiírja: Dolly + +$klon = clone $eredeti; +echo $klon->nev . "\n"; // Kiírja: Klón Dolly +``` + +Ebben a példában van egy `Barany` osztályunk egy `$nev` property-vel. Amikor klónozzuk ennek az osztálynak egy példányát, a `__clone()` metódus gondoskodik arról, hogy a klónozott bárány neve "Klón" előtagot kapjon. + + +Traitek +------- + +A traitek PHP-ban olyan eszközök, amelyek lehetővé teszik metódusok, property-k és konstansok megosztását osztályok között, és megakadályozzák a kódduplikációt. Úgy képzelhetjük el őket, mint egy "másolás és beillesztés" (Ctrl-C és Ctrl-V) mechanizmust, ahol a trait tartalma "beillesztődik" az osztályokba. Ez lehetővé teszi a kód újrafelhasználását anélkül, hogy bonyolult osztályhierarchiákat kellene létrehozni. + +Nézzünk egy egyszerű példát a traitek használatára PHP-ban: + +```php +trait Dudalas +{ + public function dudal() + { + echo 'Bip bip!'; + } +} + +class Auto +{ + use Dudalas; +} + +class Teherauto +{ + use Dudalas; +} + +$auto = new Auto; +$auto->dudal(); // Kiírja 'Bip bip!' + +$teherauto = new Teherauto; +$teherauto->dudal(); // Szintén kiírja 'Bip bip!' +``` + +Ebben a példában van egy `Dudalas` nevű traitünk, amely egy `dudal()` metódust tartalmaz. Ezután van két osztályunk: `Auto` és `Teherauto`, amelyek mindketten használják a `Dudalas` traitet. Ennek köszönhetően mindkét osztály "rendelkezik" a `dudal()` metódussal, és meghívhatjuk azt mindkét osztály objektumain. + +A traitek lehetővé teszik a kód egyszerű és hatékony megosztását az osztályok között. Eközben nem lépnek be az öröklődési hierarchiába, azaz `$auto instanceof Dudalas` `false` értéket ad vissza. + + +Kivételek +--------- + +A kivételek az OOP-ban lehetővé teszik számunkra, hogy elegánsan kezeljük a hibákat és a váratlan helyzeteket a kódunkban. Ezek olyan objektumok, amelyek információt hordoznak a hibáról vagy a szokatlan helyzetről. + +PHP-ban van egy beépített `Exception` osztályunk, amely az összes kivétel alapjául szolgál. Több metódusa van, amelyek lehetővé teszik számunkra, hogy több információt kapjunk a kivételről, például a hibaüzenetet, a fájlt és a sort, ahol a hiba történt, stb. + +Amikor hiba történik a kódban, "dobhatunk" egy kivételt a `throw` kulcsszó segítségével. + +```php +function osztas(float $a, float $b): float +{ + if ($b === 0.0) { // Use float comparison + throw new Exception('Nullával való osztás!'); + } + return $a / $b; +} +``` + +Amikor az `osztas()` függvény nullát kap második argumentumként, kivételt dob a `'Nullával való osztás!'` hibaüzenettel. Annak megakadályozására, hogy a program leálljon a kivétel dobásakor, elkapjuk azt egy `try/catch` blokkban: + +```php +try { + echo osztas(10, 0); +} catch (Exception $e) { + echo 'Kivétel elkapva: '. $e->getMessage(); +} +``` + +A kódot, amely kivételt dobhat, egy `try` blokkba csomagoljuk. Ha kivétel dobódik, a kód végrehajtása átkerül a `catch` blokkba, ahol feldolgozhatjuk a kivételt (pl. kiírhatjuk a hibaüzenetet). + +A `try` és `catch` blokkok után hozzáadhatunk egy opcionális `finally` blokkot, amely mindig lefut, függetlenül attól, hogy dobódott-e kivétel vagy sem (még akkor is, ha a `try` vagy `catch` blokkban `return`, `break` vagy `continue` utasítást használunk): + +```php +try { + echo osztas(10, 0); +} catch (Exception $e) { + echo 'Kivétel elkapva: '. $e->getMessage(); +} finally { + // Kód, amely mindig lefut, függetlenül attól, hogy dobódott-e kivétel +} +``` + +Létrehozhatunk saját kivételosztályokat (hierarchiát) is, amelyek az Exception osztálytól örökölnek. Példaként képzeljünk el egy egyszerű banki alkalmazást, amely lehetővé teszi befizetések és kifizetések végrehajtását: + +```php +class BankiKivetel extends Exception {} +class ElegtetlenFedezetKivetel extends BankiKivetel {} +class LimitTullepesKivetel extends BankiKivetel {} + +class Bankszamla +{ + private int $egyenleg = 0; + private int $napiLimit = 1000; + + public function befizet(int $osszeg): int + { + $this->egyenleg += $osszeg; + return $this->egyenleg; + } + + public function kifizet(int $osszeg): int + { + if ($osszeg > $this->egyenleg) { + throw new ElegtetlenFedezetKivetel('Nincs elegendő fedezet a számlán.'); + } + + if ($osszeg > $this->napiLimit) { + throw new LimitTullepesKivetel('Túllépték a napi kifizetési limitet.'); + } + + $this->egyenleg -= $osszeg; + return $this->egyenleg; + } +} +``` + +Egy `try` blokkhoz több `catch` blokkot is megadhatunk, ha különböző típusú kivételekre számítunk. + +```php +$szamla = new Bankszamla; +$szamla->befizet(500); + +try { + $szamla->kifizet(1500); +} catch (LimitTullepesKivetel $e) { + echo $e->getMessage(); +} catch (ElegtetlenFedezetKivetel $e) { + echo $e->getMessage(); +} catch (BankiKivetel $e) { + echo 'Hiba történt a művelet végrehajtása során.'; +} +``` + +Ebben a példában fontos megjegyezni a `catch` blokkok sorrendjét. Mivel minden kivétel a `BankiKivetel`-től örököl, ha ezt a blokkot tennénk elsőnek, az összes kivételt elkapná, anélkül, hogy a kód eljutna a következő `catch` blokkokhoz. Ezért fontos, hogy a specifikusabb kivételek (azaz azok, amelyek másoktól örökölnek) a `catch` blokkban magasabban legyenek a sorrendben, mint a szülő kivételeik. + + +Iteráció +-------- + +PHP-ban objektumokon iterálhatunk a `foreach` ciklus segítségével, hasonlóan ahhoz, ahogy tömbökön iterálunk. Ahhoz, hogy ez működjön, az objektumnak implementálnia kell egy speciális interfészt. + +Az első lehetőség az `Iterator` interfész implementálása, amelynek metódusai a `current()` (aktuális értéket adja vissza), `key()` (kulcsot adja vissza), `next()` (következő értékre lép), `rewind()` (kezdetre lép) és `valid()` (ellenőrzi, hogy még nem értünk-e a végére). + +A második lehetőség az `IteratorAggregate` interfész implementálása, amelynek csak egyetlen `getIterator()` metódusa van. Ez vagy egy helyettesítő objektumot ad vissza, amely biztosítja az iterációt, vagy lehet egy generátor, ami egy speciális függvény, amelyben a `yield`-et használjuk a kulcsok és értékek fokozatos visszaadására: + +```php +class Szemely +{ + public function __construct( + public int $kor, + ) { + } +} + +class Lista implements IteratorAggregate +{ + private array $szemelyek = []; + + public function szemelyHozzaadasa(Szemely $szemely): void + { + $this->szemelyek[] = $szemely; + } + + public function getIterator(): Generator + { + foreach ($this->szemelyek as $szemely) { + yield $szemely; + } + } +} + +$lista = new Lista; +$lista->szemelyHozzaadasa(new Szemely(30)); +$lista->szemelyHozzaadasa(new Szemely(25)); + +foreach ($lista as $szemely) { + echo "Kor: {$szemely->kor} év \n"; +} +``` + + +Helyes gyakorlatok +------------------ + +Miután elsajátította az objektumorientált programozás alapelveit, fontos a helyes OOP gyakorlatokra összpontosítani. Ezek segítenek olyan kódot írni, amely nemcsak funkcionális, hanem olvasható, érthető és könnyen karbantartható is. + +1) **Felelősségek szétválasztása (Separation of Concerns)**: Minden osztálynak világosan meghatározott felelősséggel kell rendelkeznie, és csak egy fő feladatot kell megoldania. Ha egy osztály túl sok mindent csinál, érdemes lehet kisebb, specializált osztályokra bontani. +2) **Egységbezárás (Encapsulation)**: Az adatokat és metódusokat a lehető legjobban el kell rejteni, és csak egy definiált interfészen keresztül szabad hozzáférni hozzájuk. Ez lehetővé teszi az osztály belső implementációjának megváltoztatását anélkül, hogy a kód többi részét befolyásolná. +3) **Függőséginjektálás (Dependency Injection)**: Ahelyett, hogy a függőségeket közvetlenül az osztályban hozná létre, azokat kívülről kellene "injektálni". Ennek az elvnek a mélyebb megértéséhez javasoljuk a [Dependency Injection fejezetek|dependency-injection:introduction] elolvasását. diff --git a/nette/hu/troubleshooting.texy b/nette/hu/troubleshooting.texy index 03c4e388fa..e22c549af9 100644 --- a/nette/hu/troubleshooting.texy +++ b/nette/hu/troubleshooting.texy @@ -2,40 +2,69 @@ Hibaelhárítás ************* -A Nette nem működik, fehér oldal jelenik meg .[#toc-nette-is-not-working-white-page-is-displayed] -------------------------------------------------------------------------------------------------- -- Próbálja meg a `index.php` fájlban a `declare(strict_types=1);` után a `ini_set('display_errors', '1'); error_reporting(E_ALL);` címet beírni, hogy kikényszerítse a hibák megjelenítését. -- Ha továbbra is fehér képernyő jelenik meg, akkor valószínűleg hiba van a szerver beállításában, és az okot a szervernaplóban találja meg. A biztonság kedvéért ellenőrizze, hogy a PHP egyáltalán működik-e, ha megpróbál valamit kiírni a `echo 'test';` segítségével. -- Ha hibaüzenetet lát *Server Error: Sajnáljuk! ...*, folytassa a következő résszel: +Nem működik a Nette, fehér oldal jelenik meg +-------------------------------------------- +- Próbálja meg az `index.php` fájlba közvetlenül a `declare(strict_types=1);` után beilleszteni az `ini_set('display_errors', '1'); error_reporting(E_ALL);` sort, ezzel kikényszeríti a hibák megjelenítését. +- Ha továbbra is fehér képernyőt lát, valószínűleg hiba van a szerver beállításában, és az okot a szerver naplójában találja meg. Biztonság kedvéért ellenőrizze, hogy egyáltalán működik-e a PHP, például próbáljon meg valamit kiírni a `echo 'test';` segítségével. +- Ha a *Server Error: We're sorry! …* hibaüzenetet látja, folytassa a következő szakasszal: -Hiba 500 *Szerver hiba: Sajnáljuk! ...* .[#toc-error-500-server-error-we-re-sorry] ----------------------------------------------------------------------------------- -Ezt a hibaoldalt a Nette jeleníti meg termelési üzemmódban. Ha fejlesztői gépen látja, [váltson fejlesztői módba |application:bootstrap#Development vs Production Mode]. +500-as hiba *Server Error: We're sorry! …* +------------------------------------------ +Ezt a hibaoldalt a Nette éles üzemmódban jeleníti meg. Ha ez a fejlesztői számítógépén jelenik meg, [váltson fejlesztői módba |application:bootstrapping#Fejlesztői vs éles mód], és a Tracy részletes üzenettel jelenik meg. -Ha a hibaüzenet a `Tracy is unable to log error` címet tartalmazza, akkor derítse ki, hogy miért nem lehet a hibákat naplózni. Ezt például úgy teheti meg, hogy fejlesztői üzemmódba [vált |application:bootstrap#Development vs Production Mode], és a `$configurator->enableTracy(...)` után meghívja a `Tracy\Debugger::log('hello');` címet. A Tracy meg fogja mondani, hogy miért nem lehet naplózni. -Az ok általában a `log/` könyvtárba való íráshoz szükséges [engedélyek elégtelensége |#Setting Directory Permissions]. +A hiba okát mindig a `log/` könyvtárban lévő naplóban találja meg. Ha azonban a hibaüzenetben a `Tracy is unable to log error` mondat szerepel, először derítse ki, miért nem lehet naplózni a hibákat. Ezt megteheti például úgy, hogy ideiglenesen [átvált |application:bootstrapping#Fejlesztői vs éles mód] fejlesztői módba, és hagyja, hogy a Tracy bármit naplózzon az indítása után: -Ha a `Tracy is unable to log error` mondat nem szerepel a hibaüzenetben (már), akkor a hiba okát a `log/` könyvtárban lévő naplóból tudhatja meg. +```php +// Bootstrap.php +$configurator->setDebugMode('23.75.345.200'); // az Ön IP címe +$configurator->enableTracy($rootDir . '/log'); +\Tracy\Debugger::log('hello'); +``` + +A Tracy megmondja, miért nem tud naplózni. Az ok lehet egy hibás elektroncső az Olomouci Katoda vállalatból, de valószínűbb, hogy [elégtelen jogosultságok |#Könyvtárjogosultságok beállítása] az íráshoz a `log/` könyvtárba. + +Az 500-as hiba egyik leggyakoribb oka az elavult cache. Míg a Nette fejlesztői módban okosan automatikusan frissíti a cache-t, éles üzemmódban a teljesítmény maximalizálására összpontosít, és a cache törlése minden kódmódosítás után az Ön feladata. Próbálja meg törölni a `temp/cache` tartalmát. + + +404-es hiba, nem működik az útválasztás +--------------------------------------- +Ha minden oldal (a kezdőlap kivételével) 404-es hibát ad vissza, úgy tűnik, probléma van a szerver konfigurációjával a [szép URL-ekhez |#Hogyan állítsuk be a szervert a szép URL-ekhez]. + + +A sablonokban vagy a konfigurációban végzett változtatások nem jelennek meg +--------------------------------------------------------------------------- +"Módosítottam a sablont vagy a konfigurációt, de a webhely továbbra is a régi verziót jeleníti meg." Ez a viselkedés [éles üzemmódban |application:bootstrapping#Fejlesztői vs éles mód] fordul elő, amely a teljesítmény érdekében nem ellenőrzi a fájlok változásait, és megtartja az egyszer generált cache-t. + +Annak érdekében, hogy ne kelljen minden módosítás után manuálisan törölni a cache-t az éles szerveren, engedélyezze a fejlesztői módot az IP-címéhez a `Bootstrap.php` fájlban: + +```php +$this->configurator->setDebugMode('az Ön IP címe'); +``` + + +Hogyan kapcsoljuk ki a cache-t fejlesztés közben? +------------------------------------------------- +A Nette okos, és nem kell kikapcsolnia a gyorsítótárazást. Fejlesztés közben ugyanis automatikusan frissíti a cache-t minden sablon- vagy DI konténer konfigurációváltozáskor. A fejlesztői mód ráadásul automatikus felismeréssel kapcsol be, így általában semmit sem kell konfigurálni, [vagy csak az IP-címet |application:bootstrapping#Fejlesztői vs éles mód]. -Az egyik leggyakoribb ok az elavult gyorsítótár. Míg a Nette okosan, automatikusan frissíti a gyorsítótárat fejlesztői módban, addig termelési módban a teljesítmény maximalizálására összpontosít, és a gyorsítótár törlése minden kódmódosítás után az Ön feladata. Próbálja meg törölni a `temp/cache`. +Az útválasztó hibakeresésekor javasoljuk a böngésző cache-ének kikapcsolását, amelyben például átirányítások lehetnek tárolva: nyissa meg a Fejlesztői Eszközöket (Ctrl+Shift+I vagy Cmd+Option+I), és a Hálózat (Network) panelen jelölje be a cache kikapcsolását. -Hiba `#[\ReturnTypeWillChange] attribute should be used` .[#toc-error-returntypewillchange-attribute-should-be-used] --------------------------------------------------------------------------------------------------------------------- -Ez a hiba akkor jelentkezik, ha a PHP-t a 8.1-es verzióra frissítette, de a Nette-et használja, amely nem kompatibilis vele. A megoldás tehát a Nette újabb verzióra való frissítése a `composer update` segítségével. A Nette a 3.0-s verzió óta támogatja a PHP 8.1-es verzióját. Ha régebbi verziót használ (ezt a `composer.json` oldalon találja meg), [frissítse a Nette-et |migrations:en], vagy maradjon a PHP 8.0-nál. +Hiba `#[\ReturnTypeWillChange] attribute should be used` +-------------------------------------------------------- +Ez a hiba akkor jelenik meg, ha frissítette a PHP-t 8.1-es verzióra, de olyan Nette-t használ, amely nem kompatibilis vele. A megoldás tehát a Nette frissítése egy újabb verzióra a `composer update` segítségével. A Nette a 3.0-s verziótól támogatja a PHP 8.1-et. Ha régebbi verziót használ (ellenőrizze a `composer.json`-ban), [frissítse a Nette-t |migrations:en] vagy maradjon a PHP 8.0-nál. -Címtárengedélyek beállítása .[#toc-setting-directory-permissions] ------------------------------------------------------------------ -Ha macOS-en vagy Linuxon (vagy bármely más Unix-alapú rendszeren) fejleszt, akkor be kell állítania a webszerver írási jogosultságait. Feltételezve, hogy az alkalmazásod az alapértelmezett `/var/www/html` könyvtárban található (Fedora, CentOS, RHEL). +Könyvtárjogosultságok beállítása +-------------------------------- +Ha macOS-en vagy Linuxon (vagy bármely más Unix-alapú rendszeren) fejleszt, be kell állítania a webszerver írási jogosultságait. Tegyük fel, hogy az alkalmazása az alapértelmezett `/var/www/html` könyvtárban található (Fedora, CentOS, RHEL). ```shell cd /var/www/html/MY_PROJECT chmod -R a+rw temp log ``` -Egyes Linux rendszereken (Fedora, CentOS, ...) a SELinux alapértelmezés szerint engedélyezve lehet. Lehet, hogy frissítenie kell a SELinux házirendeket, vagy a `temp` és `log` könyvtárak elérési útvonalát a megfelelő SELinux biztonsági kontextussal kell beállítania. A `temp` és `log` könyvtárakat a `httpd_sys_rw_content_t` kontextusban kell beállítani; az alkalmazás többi részéhez -- főleg a `app` mappához -- a `httpd_sys_content_t` kontextus elegendő. Futtassa a szerveren root felhasználóként: +Néhány Linux disztribúción (Fedora, CentOS, ...) alapértelmezetten be van kapcsolva a SELinux. Megfelelően módosítania kell a SELinux házirendeket, és be kell állítania a helyes SELinux biztonsági kontextust a `temp` és `log` mappákhoz. A `temp` és `log` mappákhoz a `httpd_sys_rw_content_t` kontextustípust állítjuk be, az alkalmazás többi részéhez (és különösen az `app` mappához) elegendő lesz a `httpd_sys_content_t`. A szerveren futtassa: ```shell semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/log(/.*)?' @@ -43,25 +72,30 @@ semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/temp(/.*) restorecon -Rv /var/www/html/MY_PROJECT/ ``` -Ezután engedélyezni kell a SELinux booleant `httpd_can_network_connect_db`, hogy a Nette csatlakozhasson az adatbázishoz a hálózaton keresztül. Alapértelmezés szerint ki van kapcsolva. A `setsebool` parancs használható a feladat elvégzésére, és ha a `-P` opciót adjuk meg, akkor ez a beállítás az újraindítások során is megmarad. +Továbbá engedélyezni kell a SELinux `httpd_can_network_connect_db` boolean értékét, amely alapértelmezés szerint ki van kapcsolva, és amely lehetővé teszi a Nette számára, hogy hálózaton keresztül csatlakozzon az adatbázishoz. Ehhez a `setsebool` parancsot használjuk, és a `-P` kapcsolóval a változtatást tartóssá tesszük, azaz a szerver újraindítása után nem ér minket kellemetlen meglepetés: ```shell setsebool -P httpd_can_network_connect_db on ``` -Hogyan lehet megváltoztatni vagy eltávolítani a `www` címtárat az URL-ből? .[#toc-how-to-change-or-remove-www-directory-from-url] ---------------------------------------------------------------------------------------------------------------------------------- -A Nette mintaprojektekben használt `www/` könyvtár a projekt úgynevezett nyilvános könyvtára vagy dokumentum-gyökere. Ez az egyetlen könyvtár, amelynek tartalma a böngésző számára elérhető. És ez tartalmazza a `index.php` fájlt, a belépési pontot, amely elindítja a Nette-ben írt webalkalmazást. +Hogyan lehet megváltoztatni vagy eltávolítani a `www` könyvtárat az URL-ből? +---------------------------------------------------------------------------- +A Nette példaprojektjeiben használt `www/` könyvtár az úgynevezett nyilvános könyvtárat vagy document-root-ot képviseli a projektben. Ez az egyetlen könyvtár, amelynek tartalma elérhető a böngésző számára. És tartalmazza az `index.php` fájlt, a belépési pontot, amely elindítja a Nette-ben írt webalkalmazást. -Ahhoz, hogy az alkalmazás a tárhelyen fusson, a tárhely konfigurációjában a document-root-ot erre a könyvtárra kell állítani. Vagy, ha a tárhelyen van egy előre elkészített mappa a nyilvános könyvtár számára más névvel (például `web`, `public_html` stb.), egyszerűen nevezze át `www/`. +Az alkalmazás hosztingon történő üzembe helyezéséhez helyesen kell konfigurálni a document-root-ot. Két lehetősége van: +1. A hoszting konfigurációjában állítsa be a document-root-ot erre a könyvtárra. +2. Ha a hosztingnak van előre elkészített mappája (pl. `public_html`), nevezze át a `www/`-t erre a névre. -A megoldás **nem** a `www/` mappától való "megszabadulás" a `.htaccess` fájlban vagy a routerben található szabályok segítségével. Ha a tárhely nem engedné meg, hogy a document-root-ot egy alkönyvtárra állítsa (azaz a nyilvános könyvtár felett egy szinttel magasabb könyvtárakat hozzon létre), keressen másikat. Ellenkező esetben jelentős biztonsági kockázatot vállalna. Olyan lenne, mintha olyan lakásban élnél, ahol nem tudod becsukni a bejárati ajtót, és az mindig nyitva van. +.[warning] +Soha ne próbálja a biztonságot csak `.htaccess` vagy router segítségével megoldani, amelyek megakadályoznák a hozzáférést a többi mappához. +Ha a hoszting nem teszi lehetővé a document-root beállítását egy alkönyvtárra (azaz könyvtárak létrehozását egy szinttel a nyilvános könyvtár felett), keressen másik szolgáltatót. Ellenkező esetben jelentős biztonsági kockázatot vállalna. Olyan lenne, mintha egy olyan lakásban lakna, ahol nem lehet bezárni a bejárati ajtót, és mindig tárva-nyitva van. -Hogyan konfiguráljunk egy kiszolgálót a szép URL-ekhez? .[#toc-how-to-configure-a-server-for-nice-urls] -------------------------------------------------------------------------------------------------------- -**Apache**: a mod_rewrite kiterjesztést engedélyezni kell és be kell állítani a `.htaccess` fájlban. + +Hogyan állítsuk be a szervert a szép URL-ekhez? +----------------------------------------------- +**Apache**: engedélyezni és beállítani kell a mod_rewrite szabályokat a `.htaccess` fájlban: ```apacheconf RewriteEngine On @@ -70,25 +104,53 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -Az Apache konfigurációjának .htaccess fájlokkal történő módosításához engedélyezni kell az AllowOverride direktívát. Ez az Apache alapértelmezett viselkedése. +Ha problémákba ütközik, győződjön meg róla, hogy: +- a `.htaccess` fájl a document-root könyvtárban található (tehát az `index.php` fájl mellett) +- [az Apache feldolgozza a `.htaccess` fájlokat |#Annak ellenőrzése hogy működik-e a .htaccess] +- [engedélyezve van a mod_rewrite |#Annak ellenőrzése hogy engedélyezve van-e a mod rewrite] + +Ha az alkalmazást egy alkönyvtárban állítja be, lehet, hogy ki kell kommenteznie a `RewriteBase` beállítására vonatkozó sort, és be kell állítania a megfelelő mappára. -**nginx**: a `try_files` direktívát kell használni a szerverkonfigurációban: +**nginx**: be kell állítani az átirányítást a `try_files` direktívával a `location /` blokkon belül a szerver konfigurációjában. ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args is important + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args FONTOS! } ``` -A `location` blokkot pontosan egyszer kell definiálni a `server` blokk minden egyes fájlrendszeri elérési útvonalához. Ha már van `location /` blokk a konfigurációban, akkor a `try_files` direktívát adja hozzá a meglévő blokkhoz. +A `location` blokk minden fájlrendszeri útvonalhoz csak egyszer szerepelhet a `server` blokkban. Ha már van `location /` a konfigurációban, adja hozzá a `try_files` direktívát ahhoz. + + +Annak ellenőrzése, hogy működik-e a `.htaccess` +----------------------------------------------- +A legegyszerűbb módja annak tesztelésére, hogy az Apache használja-e vagy figyelmen kívül hagyja-e a `.htaccess` fájlt, ha szándékosan megrongálja azt. Illesszen be a fájl elejére egy `Test` sort, és most, ha frissíti az oldalt a böngészőben, *Internal Server Error*-t kellene látnia. + +Ha ezt a hibát látja, az valójában jó! Azt jelenti, hogy az Apache elemzi a `.htaccess` fájlt, és rábukkan a hibára, amit beillesztettünk. Távolítsa el a `Test` sort. + +Ha nem jelenik meg az *Internal Server Error*, az Apache beállítása figyelmen kívül hagyja a `.htaccess` fájlt. Általában az Apache a hiányzó `AllowOverride All` konfigurációs direktíva miatt hagyja figyelmen kívül. + +Ha saját maga hosztolja, ezt könnyen kijavíthatja. Nyissa meg a `httpd.conf` vagy `apache.conf` fájlt egy szövegszerkesztőben, keresse meg a megfelelő `<Directory>` részt, és adja hozzá/módosítsa ezt a direktívát: + +```apacheconf +<Directory "/var/www/htdocs"> # az Ön document root elérési útja + AllowOverride All + ... +``` + +Ha a webhelyét máshol hosztolják, nézze meg a vezérlőpulton, hogy ott engedélyezheti-e a `.htaccess` fájlt. Ha nem, forduljon a hoszting szolgáltatójához, hogy tegye meg ezt Ön helyett. + + +Annak ellenőrzése, hogy engedélyezve van-e a `mod_rewrite` +---------------------------------------------------------- +Ha ellenőrizte, hogy [működik a `.htaccess` |#Annak ellenőrzése hogy működik-e a .htaccess], ellenőrizheti, hogy engedélyezve van-e a mod_rewrite kiterjesztés. Illesszen be a `.htaccess` fájl elejére egy `RewriteEngine On` sort, és frissítse az oldalt a böngészőben. Ha *Internal Server Error* jelenik meg, az azt jelenti, hogy a mod_rewrite nincs engedélyezve. Több módja is van az engedélyezésének. Különböző módokat találhat erre különböző beállításokban a Stack Overflow-n. -A hivatkozások a `https:` nélkül generálódnak. .[#toc-links-are-generated-without-https] ----------------------------------------------------------------------------------------- -A Nette ugyanolyan protokollal generálja a linkeket, mint amilyet az aktuális oldal használ. Tehát a `https://foo` kezdetű linkeket generálja, és fordítva. -Ha egy HTTPS-csupaszító fordított proxy mögött áll (például Dockerben), akkor a konfigurációban be kell állítania [a proxyt |http:configuration#HTTP proxy], hogy a protokollfelismerés megfelelően működjön. +A linkek `https:` nélkül generálódnak +------------------------------------- +A Nette ugyanazzal a protokollal generálja a linkeket, mint maga az oldal. Tehát a `https://foo` oldalon `https:`-sel kezdődő linkeket generál, és fordítva. Ha egy fordított proxy szerver mögött van, amely eltávolítja a HTTPS-t (például Dockerben), akkor a konfigurációban [be kell állítani a proxyt |http:configuration#HTTP proxy], hogy a protokoll észlelése helyesen működjön. -Ha Nginxet használ proxyként, akkor az átirányítást így kell beállítania: +Ha Nginx-et használ proxyként, be kell állítani az átirányítást például így: ``` location / { @@ -96,44 +158,42 @@ location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Port $server_port; - proxy_pass http://IP-aplikace:80; # IP or hostname of the server/container where the application is running + proxy_pass http://IP-aplikace:80; # Az alkalmazást futtató szerver/konténer IP-címe vagy hosztneve } ``` -Ezután meg kell adnia az IP-proxyt és adott esetben a helyi hálózat IP-tartományát, ahol az infrastruktúrát futtatja: +Továbbá a konfigurációban meg kell adni a proxy IP-címét és esetleg a helyi hálózat IP-tartományát, ahol az infrastruktúrát üzemelteti: ```neon http: - proxy: IP-proxy/IP-range + proxy: IP-proxy/IP-range # pl. 10.0.0.0/8 ``` -A { } karakterek használata JavaScriptben .[#toc-use-of-characters-in-javascript] ---------------------------------------------------------------------------------- -A `{` and `}` karakterek a Latte címkék írására szolgálnak. Minden (a szóköz és az idézőjel kivételével) a `{` character is considered a tag. If you need to print character `{` után (gyakran JavaScriptben), közvetlenül a `{` után szóközt (vagy más üres karaktert) tehetünk. Ezzel elkerüljük, hogy címkeként értelmezzük. +A { } karakterek használata JavaScriptben +----------------------------------------- +A `{` és `}` karaktereket a Latte tagek írására használják. Tagnak számít minden, ami a `{` karaktert követi, kivéve a szóközt és az idézőjelet. Ha tehát közvetlenül a `{` karaktert kell kiírnia (gyakran például JavaScriptben), akkor a `{` karakter után tehet egy szóközt (vagy más üres karaktert). Ezzel elkerülheti a tagként való fordítást. -Ha olyan helyzetben kell ezeket a karaktereket kiírni, ahol címkeként értelmeznék, akkor speciális címkékkel kiírhatod ezeket a karaktereket - `{l}` a `{` and `{r}` a `}` számára. +Ha ezeket a karaktereket olyan helyzetben kell kiírni, amikor a szöveg tagként lenne értelmezve, használhat speciális tageket ezeknek a karaktereknek a kiírására - `{l}` a `{`-hez és `{r}` a `}`-hez. ``` -{is tag} -{ is not tag } -{l}is not tag{r} +{ez egy tag} +{ ez nem tag } +{l}ez nem tag{r} ``` -Megjegyzés: `Presenter::getContext() is deprecated` .[#toc-notice-presenter-getcontext-is-deprecated] ------------------------------------------------------------------------------------------------------ +`Presenter::getContext() is deprecated` üzenet +---------------------------------------------- -A Nette messze az első PHP keretrendszer, amelyik áttért a függőségi injektálásra, és az előadótól kezdve rávezette a programozókat a következetes használatára. Ha egy prezenternek szüksége van egy függőségre, akkor [kérni fogja azt |dependency-injection:passing-dependencies]. -Ezzel szemben az, ahogyan a teljes DI konténert átadjuk egy osztálynak, és az közvetlenül onnan húzza ki a függőségeket, antipatternnek számít (ezt hívják service locatornak). -Ezt a módot a Nette 0.x-ben használták a függőségi injektálás megjelenése előtt, és ennek maradványa a `Presenter::getContext()`, régen elavultnak jelölt metódus. +A Nette messze az első PHP keretrendszer volt, amely áttért a dependency injectionre, és a programozókat annak következetes használatára vezette, már a presenterektől kezdve. Ha egy presenternek szüksége van valamilyen függőségre, [jelentkezik érte|dependency-injection:passing-dependencies]. Ezzel szemben az az út, amikor az egész DI konténert átadjuk az osztálynak, és az közvetlenül abból húzza ki a függőségeket, antipatternnek számít (service locatornak nevezik). Ez a módszer a Nette 0.x verziójában volt használatos még a dependency injection megjelenése előtt, és ennek maradványa a `Presenter::getContext()` metódus, amelyet régen deprecatednek jelöltek. -Ha egy nagyon régi Nette alkalmazást portolsz, akkor lehet, hogy még mindig ezt a módszert használja. Így a `nette/application` 3.1-es verziója óta a `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection` figyelmeztetéssel, a 4.0-s verzió óta pedig azzal a hibával találkozik, hogy a metódus nem létezik. +Ha egy nagyon régi Nette alkalmazást portol, előfordulhat, hogy ez a metódus még mindig használatban van. A `nette/application` 3.1-es verziójától kezdve így találkozhat a `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection` figyelmeztetéssel, a 4.0-s verziótól pedig azzal a hibával, hogy a metódus nem létezik. -A tiszta megoldás természetesen az, hogy áttervezzük az alkalmazást úgy, hogy a függőségeket függőségi injektálással adjuk át. Megoldásként hozzáadhatja saját `getContext()` metódusát az alap prezenterhez, és megkerülheti az üzenetet: +A tiszta megoldás természetesen az alkalmazás átalakítása úgy, hogy a függőségeket dependency injection segítségével adja át. Megkerülő megoldásként hozzáadhatja a saját `getContext()` metódusát az alap presenteréhez, és így megkerülheti az üzenetet: ```php -abstract BasePresenter extends Nette\Application\UI\Presenter +abstract class BasePresenter extends Nette\Application\UI\Presenter { private Nette\DI\Container $context; diff --git a/nette/hu/vulnerability-protection.texy b/nette/hu/vulnerability-protection.texy index d0d5fbcc2c..4608d327e3 100644 --- a/nette/hu/vulnerability-protection.texy +++ b/nette/hu/vulnerability-protection.texy @@ -1,22 +1,22 @@ -Sebezhetőségi védelem -********************* +Védelem a sebezhetőségek ellen +****************************** .[perex] -Időről időre bejelentésre kerül egy-egy nagyobb biztonsági hiba, vagy akár visszaélnek vele. Az biztos, hogy ez egy kicsit kellemetlen. Ha fontos Önnek a webes alkalmazásai biztonsága, akkor a Nette Framework őszintén szólva a legjobb választás az Ön számára. +Időről időre újabb jelentős webhelyen jelentenek be biztonsági rést, vagy kihasználják azt. Ez kellemetlen. Ha fontos Önnek webalkalmazásai biztonsága, a Nette Framework minden bizonnyal a legjobb választás. -Cross-Site Scripting (XSS) .[#toc-cross-site-scripting-xss] -=========================================================== +Cross-Site Scripting (XSS) +========================== -A Cross-Site Scripting egy olyan webhely-megszakítási módszer, amely nem kimentett bemenetet használ. A támadó saját HTML- vagy JavaScript-kódot juttathat be, és megváltoztathatja az oldal kinézetét, vagy akár érzékeny információkat is gyűjthet a felhasználókról. Az XSS elleni védelem egyszerű: az összes karakterlánc és bemenet következetes és helyes escapingje. Hagyományosan elég lenne, ha a kódolója csak egy apró hibát vét, és egyszer elfelejti, és máris veszélybe kerülhet az egész weboldal. +A Cross-Site Scripting egy weboldal-megsértési módszer, amely a nem kezelt kimeneteket használja ki. A támadó ezután képes saját kódot beilleszteni az oldalba, és ezzel módosíthatja az oldalt, vagy akár érzékeny adatokat szerezhet a látogatókról. Az XSS ellen csak az összes karakterlánc következetes és korrekt kezelésével lehet védekezni. Eközben elég, ha a kódolója csak egyszer is elfelejti ezt, és az egész webhely máris kompromittálódhat. -Ilyen injektálásra példa lehet, ha a felhasználónak egy módosított URL-t csúsztatunk, amely egy "rosszindulatú" szkriptet illeszt be. Ha egy alkalmazás nem megfelelően menekíti a bemeneteket, egy ilyen kérés esetleg egy szkriptet hajtana végre a kliens oldalán. Ez például lopott személyazonossághoz vezethet. +A támadás példája lehet egy módosított URL becsempészése a felhasználóhoz, amellyel saját kódot injektálunk az oldalba. Ha az alkalmazás nem kezeli megfelelően a kimeneteket, végrehajtja a szkriptet a felhasználó böngészőjében. Ezzel a módszerrel például ellophatjuk az identitását. ``` -https://example.com/?search=<script>alert('XSS attack.');</script> +https://example.com/?search=<script>alert('Sikeres XSS támadás.');</script> ``` -A Nette Framework egy vadonatúj technológiával, a [Context-Aware Escapinggel |latte:safety-first#context-aware-escaping] állt elő, amely örökre megszabadítja Önt a Cross-Site Scripting kockázatoktól. Az összes bemenetet automatikusan, az adott kontextus alapján eszkábálja, így lehetetlen, hogy egy programozó véletlenül elfelejtsen valamit. Vegyük példaként a következő sablont: +A Nette Framework forradalmi [Context-Aware Escaping |latte:safety-first#Kontextusérzékeny escapelés] technológiával rendelkezik, amely örökre megszabadítja Önt a Cross-Site Scripting kockázatától. Minden kimenetet automatikusan kezel, így nem fordulhat elő, hogy a kódoló valamit elfelejtene. Példa? A kódoló létrehozza ezt a sablont: ```latte <p onclick="alert({$message})">{$message}</p> @@ -26,29 +26,29 @@ document.title = {$message}; </script> ``` -A `{$message}` parancs kiír egy változót. Más keretrendszerek gyakran kényszerítik a fejlesztőket arra, hogy explicit módon deklarálják az escapinget, és még azt is, hogy milyen típusú escapinget alkalmazzanak a kontextus alapján. A Nette Frameworkben azonban nem kell deklarálni semmit. Minden automatikus, következetes és éppen megfelelő. Ha a változót a `$message = 'Width 1/2"'` értékre állítjuk, a keretrendszer ezt a HTML-kódot fogja generálni: +A `{$message}` jelölés a változó kiírását jelenti. Más keretrendszerekben minden kiírást explicit módon kell kezelni, sőt minden helyen másképp. A Nette Frameworkben semmit sem kell kezelni, minden automatikusan, helyesen és következetesen megtörténik. Ha a `$message = 'Szélesség 1/2"'` értéket helyettesítjük be a változóba, a keretrendszer a következő HTML kódot generálja: ```latte -<p onclick="alert("Width 1\/2\"")">Width 1/2"</p> +<p onclick="alert("Szélesség 1\/2\"")">Szélesség 1/2"</p> <script> -document.title = "Width 1\/2\""; +document.title = "Szélesség 1\/2\""; </script> ``` -Cross-Site Request Forgery (CSRF) .[#toc-cross-site-request-forgery-csrf] -========================================================================= +Cross-Site Request Forgery (CSRF) +================================= -A Cross-Site Request Forgery támadás lényege, hogy a támadó egy olyan oldal meglátogatására csábítja az áldozatot, amely némán végrehajt egy kérést az áldozat böngészőjében a szerver felé, ahová az áldozat éppen be van jelentkezve, és a szerver azt hiszi, hogy a kérést az áldozat akaratából tette. A szerver az áldozat személyazonossága alatt, de az áldozat tudta nélkül hajt végre egy bizonyos műveletet. Ez lehet adatok módosítása vagy törlése, üzenet küldése stb. +A Cross-Site Request Forgery támadás abból áll, hogy a támadó egy olyan oldalra csalogatja az áldozatot, amely észrevétlenül végrehajt egy kérést az áldozat böngészőjében egy olyan szerver felé, amelyen az áldozat be van jelentkezve, és a szerver azt hiszi, hogy a kérést az áldozat saját akaratából hajtotta végre. Így az áldozat identitása alatt végrehajt egy bizonyos műveletet anélkül, hogy az áldozat tudna róla. Ez lehet adatok módosítása vagy törlése, üzenet küldése stb. -A Nette Framework **automatikusan védi a prezenterekben lévő űrlapokat és jeleket** az ilyen típusú támadásoktól. Ez úgy történik, hogy megakadályozza, hogy egy másik tartományból küldjék vagy hívják őket. A védelem kikapcsolásához használja a következőket az űrlapok esetében: +A Nette Framework **automatikusan védi az űrlapokat és a presenterekben lévő signálokat** az ilyen típusú támadások ellen. Ezt úgy teszi, hogy megakadályozza azok elküldését vagy meghívását más domainről. Ha ki szeretné kapcsolni a védelmet, használja az űrlapoknál: ```php $form->allowCrossOrigin(); ``` -vagy egy jel esetében adjon hozzá egy megjegyzést `@crossOrigin`: +vagy a signál esetén adjon hozzá egy `@crossOrigin` annotációt: ```php /** @@ -59,44 +59,41 @@ public function handleXyz() } ``` -A PHP 8-ban attribútumokat is használhat: +A Nette Application 3.2-ben attribútumokat is használhat: ```php -use Nette\Application\Attributes\CrossOrigin; +use Nette\Application\Attributes\Requires; -#[CrossOrigin] +#[Requires(sameOrigin: false)] public function handleXyz() { } ``` -URL-támadás, Vezérlő kódok, Érvénytelen UTF-8 .[#toc-url-attack-control-codes-invalid-utf-8] -============================================================================================ +URL támadás, vezérlőkódok, érvénytelen UTF-8 +============================================ -Különböző kifejezések, amelyek mind a támadó azon törekvésével kapcsolatosak, hogy "rosszindulatú" bemenetet adjon az alkalmazásodnak. Az eredmények nagyon különbözőek lehetnek, a hibás XML kimenettől (pl. rosszul működő RSS stream) az érzékeny információk megszerzésén át egy adatbázisból a felhasználói jelszavak megszerzéséig. Az ilyen támadások elleni védelem a következetes UTF-8 ellenőrzés bájtszinten. És őszintén szólva, ezt nem tenné meg egy keretrendszer nélkül, igaz? +Különböző fogalmak, amelyek a támadó azon próbálkozásával kapcsolatosak, hogy *káros* bemenetet csempésszen a webalkalmazásába. A következmények nagyon változatosak lehetnek, az XML kimenetek sérülésétől (pl. nem működő RSS csatornák) az érzékeny adatok adatbázisból való megszerzéséig vagy jelszavakig. A védekezés az összes bemenet következetes kezelése az egyes bájtok szintjén. És őszintén szólva, ki csinálja ezt Önök közül? -A Nette Framework ezt automatikusan elvégzi Ön helyett. Egyáltalán nem kell semmit sem konfigurálnod, és az alkalmazásod biztonságban lesz. +A Nette Framework ezt megteszi Ön helyett, ráadásul automatikusan. Semmit sem kell beállítania, és minden bemenet kezelve lesz. -Munkamenet eltérítés, munkamenet lopás, munkamenet rögzítés .[#toc-session-hijacking-session-stealing-session-fixation] -======================================================================================================================= +Session hijacking, session stealing, session fixation +===================================================== -A munkamenet-kezelés néhány támadástípust foglal magában. A támadó ellophatja az áldozat munkamenet-azonosítóját, vagy meghamisíthatja azt, és így a tényleges jelszó nélkül férhet hozzá egy webes alkalmazáshoz. Ezután a támadó nyom nélkül bármit megtehet, amit a felhasználó megtehet. A védelem mind a PHP, mind magának a webkiszolgálónak a megfelelő konfigurálásában rejlik. +A session kezelésével több támadástípus is összefügg. A támadó vagy ellopja, vagy becsempészi a felhasználóhoz a saját session ID-jét, és ennek köszönhetően hozzáférést szerez a webalkalmazáshoz anélkül, hogy ismerné a felhasználó jelszavát. Ezután bármit megtehet az alkalmazásban anélkül, hogy a felhasználó tudna róla. A védekezés a szerver és a PHP helyes konfigurációjában rejlik. -A Nette Framework automatikusan konfigurálja a PHP-t. A fejlesztőknek így nem kell azon aggódniuk, hogy hogyan tegyék eléggé védetté a munkamenetet, és teljes mértékben az alkalmazás kulcsfontosságú részeire koncentrálhatnak. Ehhez a `ini_set()` funkciót kell engedélyezni. +Eközben a Nette Framework automatikusan konfigurálja a PHP-t. A programozónak így nem kell azon gondolkodnia, hogyan biztosítsa helyesen a sessiont, és teljes mértékben az alkalmazás létrehozására koncentrálhat. Ehhez azonban engedélyezni kell az `ini_set()` függvényt. -SameSite cookie .[#toc-samesite-cookie] -======================================= +SameSite cookie +=============== -A SameSite sütik egy olyan mechanizmust biztosítanak, amely felismeri, hogy mi vezetett az oldal betöltéséhez. Ami biztonsági okokból elengedhetetlenül fontos. +A SameSite cookie-k mechanizmust biztosítanak annak felismerésére, hogy mi vezetett az oldal betöltéséhez. Ami teljesen alapvető a biztonság szempontjából. -A SameSite jelzőnek három értéke lehet: `Lax`, `Strict` és `None` (HTTPS-t igényel). Ha egy oldalra irányuló kérés közvetlenül magától a webről érkezik, vagy a felhasználó úgy nyitja meg az oldalt, hogy közvetlenül beírja a címsorba vagy egy könyvjelzőre kattint, -a böngésző az összes sütit elküldi a szervernek (azaz a `Lax`, `Strict` és `None` jelekkel). Ha a felhasználó egy másik weboldalról érkező linkre kattintva jut el a weboldalra, akkor a `Lax` és `None` jelzővel ellátott cookie-kat továbbítja a szervernek. Ha a kérés egy másik -módon, például egy másik eredetű POST űrlap küldésével, iframe-en belüli betöltéssel, JavaScript használatával stb., csak a `None` jelű cookie-kat küldi el a rendszer. - -Alapértelmezés szerint a Nette minden cookie-t a `Lax` jelzővel küld el. +A SameSite jelzőnek három értéke lehet: `Lax`, `Strict` és `None` (ez HTTPS-t igényel). Ha az oldalra irányuló kérés közvetlenül a webhelyről érkezik, vagy a felhasználó közvetlenül a címsorba írva vagy egy könyvjelzőre kattintva nyitja meg az oldalt, a böngésző elküldi a szervernek az összes cookie-t (azaz a `Lax`, `Strict` és `None` jelzőkkel). Ha a felhasználó egy másik webhelyről származó linken keresztül kattint a webhelyre, a `Lax` és `None` jelzőkkel ellátott cookie-k kerülnek átadásra a szervernek. Ha a kérés más módon jön létre, például egy másik webhelyről küldött POST űrlap elküldésével, iframe-en belüli betöltéssel, JavaScript segítségével stb., csak a `None` jelzővel ellátott cookie-k kerülnek elküldésre. +A Nette alapértelmezés szerint minden cookie-t `Lax` jelzővel küld. {{leftbar: www:@menu-common}} diff --git a/nette/it/@home.texy b/nette/it/@home.texy index 73dfd0447c..44b674e5d2 100644 --- a/nette/it/@home.texy +++ b/nette/it/@home.texy @@ -8,22 +8,22 @@ Documentazione Nette Introduzione ------------ -- [Perché utilizzare Nette? |www:10-reasons-why-nette] -- [Installazione |Installation] -- [Create la vostra prima applicazione! |quickstart:] +- [Perché usare Nette? |www:10-reasons-why-nette] +- [Installazione |nette:installation] +- [Scriviamo la prima applicazione! |quickstart:] Generale -------- - [Elenco dei pacchetti |www:packages] -- [Manutenzione e PHP |www:maintenance] +- [Manutenzione e versioni PHP |www:maintenance] - [Note di rilascio |https://nette.org/releases] -- [Guida all'aggiornamento |migrations:en] -- [nette:Risoluzione dei problemi |nette:Troubleshooting] +- [Passaggio a versioni più recenti|migrations:en] +- [Risoluzione dei problemi |nette:troubleshooting] - [Chi crea Nette |https://nette.org/contributors] - [Storia di Nette |www:history] - [Partecipa |contributing:] -- [Sviluppo degli sponsor |https://nette.org/en/donate] +- [Sostieni lo sviluppo |https://nette.org/cs/donate] - [Riferimento API |https://api.nette.org/] </div> @@ -31,20 +31,20 @@ Generale <div> -Applicazione Nette ------------------- +Applicazioni in Nette +--------------------- - [Come funzionano le applicazioni? |application:how-it-works] -- [Bootstrap |application:Bootstrap] -- [Presentatori |application:Presenters] -- [Modelli |application:Templates] -- [Moduli |application:Modules] -- [Routing |application:Routing] -- [Creazione di collegamenti URL |application:creating-links] +- [Bootstrapping |application:Bootstrapping] +- [Presenter |application:presenters] +- [Template |application:templates] +- [Struttura delle directory |application:directory-structure] +- [Routing |application:routing] +- [Creazione di link URL |application:creating-links] - [Componenti interattivi |application:components] -- [AJAX e Snippet |application:ajax] +- [AJAX & snippet |application:ajax] -- [Migliori pratiche |best-practices:] +- [Guide e procedure |best-practices:] </div> @@ -54,48 +54,49 @@ Applicazione Nette Argomenti principali -------------------- - [Configurazione |nette:configuring] -- [Iniezione di dipendenza |dependency-injection:] -- [Latte: Modelli |latte:] -- [Tracy: Strumento di debug |tracy:] -- [Moduli |forms:] -- [Database |database:core] -- [Autenticazione degli utenti |security:authentication] -- [Controllo dell'accesso |security:authorization] -- [Sessioni |http:Sessions] -- [Richiesta e risposta HTTP |http:] -- [Caching |caching:] +- [Dependency Injection|dependency-injection:] +- [Latte: template |latte:] +- [Tracy: debug del codice |tracy:] +- [Form |forms:] +- [Database |database:guide] +- [Login degli utenti |security:authentication] +- [Verifica delle autorizzazioni |security:authorization] +- [Sessioni |http:sessions] +- [HTTP request & response|http:] +- [Attività |assets:] +- [Cache |caching:] - [Invio di e-mail |mail:] -- [Schema: Convalida dei dati |schema:] +- [Schema: validazione dei dati |schema:] - [Generatore di codice PHP |php-generator:] -- [Tester: Test unitario |tester:] +- [Tester: testing |tester:] </div> <div> -Utilità -------- -- [Array |utils:Arrays] -- [Sistema di file |utils:filesystem] -- [Cercatore |utils:finder] -- [Elementi HTML |utils:HTML Elements] -- [Immagini |utils:Images] +Utilities +--------- +- [Array |utils:arrays] +- [File system |utils:filesystem] +- [Finder |utils:finder] +- [Elementi HTML |utils:html-elements] +- [Immagini |utils:images] - [JSON |utils:JSON] -- [NEON |neon:] -- [Hashing della password |security:passwords] -- [SmartObject |utils:SmartObject] +- [NEON|neon:] +- [Hashing delle password |security:passwords] - [Tipi PHP |utils:type] -- [Stringhe |utils:Strings] +- [Stringhe |utils:strings] - [Validatori |utils:validators] - [RobotLoader |robot-loader:] +- [SmartObject |utils:smartobject] & [StaticClass |utils:StaticClass] - [SafeStream |safe-stream:] -- [...altri |utils:] +- [...altro |utils:] </div> </div> {{toc:no}} -{{description: Documentazione ufficiale di Nette: descrive il funzionamento di Nette e le migliori pratiche per lo sviluppo di applicazioni web.}} -{{maintitle: Documentazione in rete}} +{{description: Documentazione ufficiale di Nette: descrive come funziona Nette e le best practice per lo sviluppo di applicazioni web.}} +{{maintitle: Documentazione Nette}} diff --git a/nette/it/@menu-topics.texy b/nette/it/@menu-topics.texy index 1407bf0fbb..9996aaa07f 100644 --- a/nette/it/@menu-topics.texy +++ b/nette/it/@menu-topics.texy @@ -1,21 +1,21 @@ Argomenti principali ******************** - [Configurazione |nette:configuring] -- [Applicazione Nette |application:how-it-works] -- [Iniezione di dipendenza |dependency-injection:] -- [Utilità |utils:] -- [Moduli |forms:] -- [Database |database:core] -- [Autenticazione degli utenti |security:authentication] -- [Controllo dell'accesso |security:authorization] -- [Sessioni |http:Sessions] -- [Richiesta e risposta HTTP |http:] -- [Caching |caching:] +- [Applicazioni in Nette |application:how-it-works] +- [Dependency Injection|dependency-injection:] +- [Utilities |utils:] +- [Form |forms:] +- [Database |database:guide] +- [Login degli utenti |security:authentication] +- [Verifica delle autorizzazioni |security:authorization] +- [Sessioni |http:sessions] +- [HTTP request & response|http:] +- [Cache |caching:] - [Invio di e-mail |mail:] -- [Schema: Convalida dei dati |schema:] +- [Schema: validazione dei dati |schema:] - [Generatore di codice PHP |php-generator:] -- [Latte: modelli |latte:] -- [Tracy: debug |tracy:] -- [Tester: test |tester:] +- [Latte: template |latte:] +- [Tracy: debug del codice |tracy:] +- [Tester: testing |tester:] diff --git a/nette/it/@meta.texy b/nette/it/@meta.texy new file mode 100644 index 0000000000..4647d0c8a2 --- /dev/null +++ b/nette/it/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentazione Nette}} diff --git a/nette/it/configuring.texy b/nette/it/configuring.texy index 47cb5d5ddf..2371ef4a69 100644 --- a/nette/it/configuring.texy +++ b/nette/it/configuring.texy @@ -2,34 +2,35 @@ Configurazione di Nette *********************** .[perex] -Una panoramica di tutte le opzioni di configurazione di Nette Framework. +Panoramica di tutte le opzioni di configurazione in Nette Framework. -I componenti di Nette vengono configurati tramite file di configurazione, solitamente scritti in [NEON |neon:format]. È preferibile modificarli in [editor che lo supportino |best-practices:editors-and-tools#ide-editor]. -Se si utilizza il framework completo, la configurazione viene [caricata durante l'avvio |application:bootstrap#di-container-configuration]; in caso contrario, vedere [come caricare la configurazione |bootstrap:]. +Configuriamo i componenti di Nette utilizzando file di configurazione, che di solito sono scritti nel [formato NEON |neon:format]. È meglio modificarli in [editor con supporto |best-practices:editors-and-tools#Editor IDE]. Se si utilizza l'intero framework, la configurazione viene [caricata all'avvio dell'applicazione |application:bootstrapping#Configurazione del Container DI], altrimenti leggete [come caricare la configurazione |bootstrap:]. <pre> "application .[prism-token prism-atrule]":[application:configuration#Application]: "Applicazione .[prism-token prism-comment]"<br> -"constants .[prism-token prism-atrule]":[application:configuration#Constants]: "Definisce le costanti di PHP .[prism-token prism-comment]"<br> +"assets .[prism-token prism-atrule]":[assets:configuration]: "Assets .[prism-token prism-comment]"<br> +"constants .[prism-token prism-atrule]":[application:configuration#Costanti]: "Definizione delle costanti PHP .[prism-token prism-comment]"<br> "database .[prism-token prism-atrule]":[database:configuration]: "Database .[prism-token prism-comment]"<br> -"decorator .[prism-token prism-atrule]":[dependency-injection:configuration#Decorator]: "Decorator .[prism-token prism-comment]"<br> -"di .[prism-token prism-atrule]":[dependency-injection:configuration#DI]: "DI Container .[prism-token prism-comment]"<br> -"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Extensions]: "Installa estensioni DI aggiuntive .[prism-token prism-comment]"<br> -"forms .[prism-token prism-atrule]":[forms:configuration]: "Forms .[prism-token prism-comment]"<br> -"http .[prism-token prism-atrule]":[http:configuration#HTTP Headers]: "Intestazioni HTTP .[prism-token prism-comment]"<br> -"includes .[prism-token prism-atrule]":[dependency-injection:configuration#Including files]: "File di inclusione .[prism-token prism-comment]"<br> -"latte .[prism-token prism-atrule]":[application:configuration#Latte]: "Latte .[prism-token prism-comment]"<br> -"mail .[prism-token prism-atrule]":[mail:#Configuring]: "Mailing .[prism-token prism-comment]"<br> -"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#Parameters]: "Parametri .[prism-token prism-comment]"<br> -"php .[prism-token prism-atrule]":[application:configuration#PHP]: "Opzioni di configurazione PHP .[prism-token prism-comment]"<br> +"decorator .[prism-token prism-atrule]":[dependency-injection:configuration#Decorator]: "Decoratore .[prism-token prism-comment]"<br> +"di .[prism-token prism-atrule]":[dependency-injection:configuration#DI]: "Container DI .[prism-token prism-comment]"<br> +"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Estensioni]: "Installazione di altre estensioni DI .[prism-token prism-comment]"<br> +"forms .[prism-token prism-atrule]":[forms:configuration]: "Form .[prism-token prism-comment]"<br> +"http .[prism-token prism-atrule]":[http:configuration#Header HTTP]: "Header HTTP .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[dependency-injection:configuration#Inclusione di file]: "Inclusione di file .[prism-token prism-comment]"<br> +"latte .[prism-token prism-atrule]":[application:configuration#Template Latte]: "Template Latte .[prism-token prism-comment]"<br> +"mail .[prism-token prism-atrule]":[mail:#Configurazione]: "Mail .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#Parametri]: "Parametri .[prism-token prism-comment]"<br> +"php .[prism-token prism-atrule]":[application:configuration#PHP]: "Configurazione PHP .[prism-token prism-comment]"<br> "routing .[prism-token prism-atrule]":[application:configuration#Routing]: "Routing .[prism-token prism-comment]"<br> "search .[prism-token prism-atrule]":[dependency-injection:configuration#Search]: "Registrazione automatica dei servizi .[prism-token prism-comment]"<br> -"security .[prism-token prism-atrule]":[security:configuration]: "Controllo degli accessi .[prism-token prism-comment]"<br> +"security .[prism-token prism-atrule]":[security:configuration]: "Permessi di accesso .[prism-token prism-comment]"<br> "services .[prism-token prism-atrule]":[dependency-injection:services]: "Servizi .[prism-token prism-comment]"<br> -"session .[prism-token prism-atrule]":[http:configuration#Session]: "Sessione .[prism-token prism-comment]"<br> -"tracy .[prism-token prism-atrule]":[tracy:configuring#Nette Framework]: "Tracy Debugger .[prism-token prism-comment]" +"session .[prism-token prism-atrule]":[http:configuration#Sessione]: "Sessione .[prism-token prism-comment]"<br> +"tracy .[prism-token prism-atrule]":[tracy:configuring#Nette Framework]: "Tracy debugger .[prism-token prism-comment]" </pre> -Per scrivere una stringa contenente il carattere `%`, you must escape it by doubling it to `%%`. .[note] +.[note] +Per scrivere una stringa contenente il carattere `%`, è necessario eseguirne l'escape raddoppiandolo in `%%`. {{leftbar: @menu-topics}} diff --git a/nette/it/glossary.texy b/nette/it/glossary.texy index ef63f4b323..86e459362e 100644 --- a/nette/it/glossary.texy +++ b/nette/it/glossary.texy @@ -2,154 +2,158 @@ Glossario dei termini ********************* -AJAX .[#toc-ajax] ------------------ -Asynchronous JavaScript and XML - tecnologia per la comunicazione client-server attraverso il protocollo HTTP senza la necessità di ricaricare l'intera pagina durante ogni richiesta. Nonostante l'acronimo, il formato [JSON |#JSON] è spesso utilizzato al posto di XML. +AJAX +---- +Asynchronous JavaScript and XML - tecnologia di scambio di informazioni tra client e server tramite protocollo HTTP senza la necessità di ricaricare l'intera pagina ad ogni richiesta. Sebbene dal nome possa sembrare che invii dati solo in formato XML, viene comunemente utilizzato anche il formato [#JSON]. -Azione del presentatore .[#toc-presenter-action] ------------------------------------------------- -Parte logica del [presentatore |#presenter], che esegue un'azione, ad esempio per mostrare una pagina di prodotto, per registrare un utente, ecc. Un presentatore può avere più azioni. +Azione del presenter +-------------------- +Parte logica del presenter che esegue una singola azione. Ad esempio, visualizza la pagina di un prodotto, disconnette l'utente, ecc. Un presenter può avere più azioni. BOM --- -La cosiddetta *byte order mask* è il primo carattere speciale di un file e indica l'ordine dei byte nella codifica. Alcuni editor la includono automaticamente, è praticamente invisibile, ma causa problemi con le intestazioni e l'invio di output da PHP. È possibile utilizzare [Code Checker |code-checker:] per rimuoverla in blocco. +Il cosiddetto *byte order mark* è un carattere speciale iniziale in un file, utilizzato come indicatore dell'ordine dei byte nella codifica. Alcuni editor lo inseriscono nei file. È praticamente invisibile, ma causa problemi con l'invio dell'output e degli header da PHP. Per la rimozione di massa è possibile utilizzare [Code Checker |code-checker:]. -Controllore .[#toc-controller] ------------------------------- -Il controllore elabora le richieste dell'utente e, sulla base di queste, richiama una particolare logica applicativa (cioè il [modello |#model]), quindi richiama la [vista |#view] per il rendering dei dati. Un analogo dei controllori sono i [presenter |#presenter] di Nette Framework. +Controller +---------- +Controllore che elabora le richieste dell'utente e, in base ad esse, chiama la logica applicativa appropriata (cioè il [#model]) e quindi chiede alla [#view] di renderizzare i dati. L'equivalente dei controller in Nette Framework sono i [#presenter]. + + +Cross-Site Scripting (XSS) +-------------------------- +Cross-Site Scripting è un metodo di violazione dei siti web che sfrutta output non trattati. L'attaccante può quindi inserire il proprio codice nella pagina e quindi modificarla o addirittura ottenere dati sensibili sui visitatori. Ci si può difendere da XSS solo trattando in modo coerente e corretto tutte le stringhe. +Nette Framework introduce una tecnologia rivoluzionaria [Context-Aware Escaping |latte:safety-first#Escaping sensibile al contesto], che vi libererà per sempre dal rischio di Cross-Site Scripting. Tratta automaticamente tutti gli output, quindi non può succedere che il codificatore dimentichi qualcosa. -Cross-Site Scripting (XSS) .[#toc-cross-site-scripting-xss] ------------------------------------------------------------ -Il Cross-Site Scripting è un metodo di interruzione di un sito che utilizza input non inescapsulati. Un aggressore può iniettare il proprio codice HTML o JavaScript e modificare l'aspetto della pagina o addirittura raccogliere informazioni sensibili sugli utenti. La protezione contro gli XSS è semplice: l'escape coerente e corretto di tutte le stringhe e gli input. -Nette Framework è dotato di una nuovissima tecnologia, il [Context-Aware Escaping |latte:safety-first#context-aware-escaping], che vi permetterà di eliminare per sempre i rischi di Cross-Site Scripting. L'escape di tutti gli input avviene automaticamente in base a un determinato contesto, per cui è impossibile che un coder dimentichi accidentalmente qualcosa. +Cross-Site Request Forgery (CSRF) +--------------------------------- +L'attacco Cross-Site Request Forgery consiste nel fatto che l'attaccante attira la vittima su una pagina che esegue discretamente nel browser della vittima una richiesta al server su cui la vittima è loggata, e il server crede che la richiesta sia stata eseguita dalla vittima di sua spontanea volontà. E così, sotto l'identità della vittima, esegue una determinata azione senza che questa ne sia a conoscenza. Può trattarsi di modifica o cancellazione di dati, invio di un messaggio, ecc. +Nette Framework **protegge automaticamente i form e i segnali nei presenter** da questo tipo di attacco. E lo fa impedendo il loro invio o attivazione da un altro dominio. -Falsificazione delle richieste cross-site (CSRF) .[#toc-cross-site-request-forgery-csrf] ----------------------------------------------------------------------------------------- -Un attacco Cross-Site Request Forgery prevede che l'aggressore induca la vittima a visitare una pagina che esegue silenziosamente una richiesta nel browser della vittima al server in cui la vittima è attualmente loggata, e il server crede che la richiesta sia stata fatta dalla vittima a suo piacimento. Il server esegue una determinata azione sotto l'identità della vittima, ma senza che questa se ne accorga. Può trattarsi della modifica o della cancellazione di dati, dell'invio di un messaggio, ecc. -Nette Framework **protegge automaticamente i moduli e i segnali nei presenter** da questo tipo di attacco. Ciò avviene impedendo che vengano inviati o richiamati da un altro dominio. +Dependency Injection +-------------------- +Dependency Injection (DI) è un pattern di progettazione che indica come separare la creazione degli oggetti dalle loro dipendenze. Cioè, la classe non è responsabile della creazione o inizializzazione delle sue dipendenze, ma invece queste dipendenze le vengono fornite da codice esterno (questo può essere anche un [container DI |#Container Dependency Injection]). Il vantaggio sta nel fatto che consente una maggiore flessibilità del codice, una migliore comprensibilità e un più facile testing dell'applicazione, poiché le dipendenze sono facilmente sostituibili e isolate dalle altre parti del codice. Maggiori informazioni nel capitolo [Cos'è l'Iniezione delle Dipendenze? |dependency-injection:introduction] -Iniezione di dipendenza .[#toc-dependency-injection] ----------------------------------------------------- -La Dependency Injection (DI) è un modello di progettazione che indica come separare la creazione degli oggetti dalle loro dipendenze. In altre parole, una classe non è responsabile della creazione o dell'inizializzazione delle sue dipendenze, ma queste ultime sono fornite da codice esterno (che può includere un [contenitore DI |#Dependency Injection container]). Il vantaggio è che consente una maggiore flessibilità del codice, una migliore leggibilità e una maggiore facilità di test delle applicazioni, perché le dipendenze sono facilmente sostituibili e isolate da altre parti del codice. Per ulteriori informazioni, vedere [Cos'è la Dependency Injection? |dependency-injection:introduction] +Container Dependency Injection +------------------------------ +Un container Dependency Injection (anche container DI o container IoC) è uno strumento che si occupa della creazione e della gestione delle dipendenze in un'applicazione (ovvero i [servizi |#Servizio]). Il container ha solitamente una configurazione che definisce quali classi dipendono da altre classi, quali implementazioni specifiche delle dipendenze devono essere utilizzate e come queste dipendenze devono essere create. Quindi il container crea questi oggetti e li fornisce alle classi che ne hanno bisogno. Maggiori informazioni nel capitolo [Cos'è un container DI? |dependency-injection:container] -Contenitore per l'iniezione di dipendenze .[#toc-dependency-injection-container] --------------------------------------------------------------------------------- -Un contenitore di Dependency Injection (anche contenitore DI o contenitore IoC) è uno strumento che gestisce la creazione e la gestione delle dipendenze in un'applicazione (o [servizi |#service]). Un contenitore di solito ha una configurazione che definisce quali classi dipendono da altre classi, quali implementazioni specifiche di dipendenze utilizzare e come creare tali dipendenze. Il contenitore crea quindi questi oggetti e li fornisce alle classi che ne hanno bisogno. Per ulteriori informazioni, vedere [Cos'è un contenitore DI? |dependency-injection:container] +Escaping +-------- +L'escaping è la conversione di caratteri che hanno un significato speciale in un dato contesto in altre sequenze corrispondenti. Esempio: vogliamo scrivere virgolette in una stringa delimitata da virgolette. Poiché le virgolette hanno un significato speciale nel contesto della stringa e la loro semplice scrittura verrebbe interpretata come la fine della stringa, è necessario scriverle con un'altra sequenza corrispondente. Quale esattamente è determinato dalle regole del contesto. -Sfuggire .[#toc-escaping] -------------------------- -L'escape è la conversione di caratteri con un significato speciale in un determinato contesto in altre sequenze equivalenti. Esempio: Vogliamo scrivere le virgolette in una stringa con le virgolette. Poiché le virgolette hanno un significato speciale nel contesto della stringa con le virgolette, è necessario utilizzare un'altra sequenza equivalente. La sequenza concreta è determinata dalle regole del contesto (ad esempio, `\"` nella stringa chiusa da virgolette di PHP, `"` negli attributi HTML, ecc.) +Filtro (precedentemente helper) +------------------------------- +Nei template, il termine [filtro |latte:syntax#Filtri] si riferisce solitamente a una funzione che aiuta a modificare o riformattare i dati nella forma finale. I template dispongono di diversi [filtri standard |latte:filters]. -Filtro (in precedenza Helper) .[#toc-filter-formerly-helper] ------------------------------------------------------------- -Funzione di filtro. Nei modelli, il [filtro |latte:syntax#filters] è una funzione che aiuta a modificare o formattare i dati nella forma di output. I modelli hanno diversi [filtri standard |latte:filters] predefiniti. +Invalidazione +------------- +Notifica a uno [#snippet] di ridisegnarsi. In un altro significato, anche cancellazione del contenuto della cache. -Invalidazione .[#toc-invalidation] ----------------------------------- -Avviso di uno [snippet |#snippet] da renderizzare. In altri contesti, anche la cancellazione di una cache. +JSON +---- +Formato per lo scambio di dati basato sulla sintassi JavaScript (ne è un sottoinsieme). La specifica esatta si trova sulla pagina www.json.org. -JSON .[#toc-json] ------------------ -Formato di scambio dati basato sulla sintassi di JavaScript (è il suo sottoinsieme). Le specifiche esatte si trovano su www.json.org. +Componente +---------- +Parte riutilizzabile dell'applicazione. Può essere una parte visiva della pagina, come descritto nel capitolo [Scrivere componenti |application:components], oppure il termine componente si riferisce anche alla classe [Component |component-model:] (tale componente non deve essere visivo). -Componente .[#toc-component] ----------------------------- -Parte riutilizzabile di un'applicazione. Può essere una parte visuale di una pagina, come descritto nel capitolo [componenti |application:components], oppure il termine può anche indicare la classe [Componente ( |component-model:] tale componente non deve essere necessariamente visuale). +Caratteri di controllo +---------------------- +I caratteri di controllo sono caratteri invisibili che possono comparire nel testo ed eventualmente causare problemi. Per la loro rimozione di massa dai file è possibile utilizzare [Code Checker |code-checker:] e per la rimozione da una variabile la funzione [Strings::normalize() |utils:strings#normalize]. -Caratteri di controllo .[#toc-control-characters] -------------------------------------------------- -I caratteri di controllo sono caratteri invisibili che possono comparire in un testo e causare problemi. Per la loro rimozione dai file si può usare [Code Checker |code-checker:], per la loro rimozione da una variabile si può usare la funzione [Strings::normalize() |utils:strings#normalize]. +Eventi +------ +Un evento è una situazione attesa in un oggetto, che quando si verifica, vengono chiamati i cosiddetti handler, cioè callback che reagiscono all'evento ("esempio":https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38). Un evento può essere ad esempio l'invio di un form, il login di un utente, ecc. Gli eventi sono quindi una forma di *Inversion of Control*. +Ad esempio, il login dell'utente avviene nel metodo `Nette\Security\User::login()`. L'oggetto `User` ha una variabile pubblica `$onLoggedIn`, che è un array a cui chiunque può aggiungere un callback. Nel momento in cui l'utente effettua il login, il metodo `login()` chiama tutti i callback nell'array. Il nome della variabile nella forma `onXyz` è una convenzione utilizzata in tutto Nette. -Eventi .[#toc-events] ---------------------- -Un evento è una situazione attesa nell'oggetto e, quando si verifica, vengono richiamati i cosiddetti gestori, cioè i callback che reagiscono all'evento ("sample":https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38). L'evento può essere, ad esempio, l'invio di un modulo, il login di un utente, ecc. Gli eventi sono quindi una forma di *inversione del controllo*. -Ad esempio, il login di un utente avviene nel metodo `Nette\Security\User::login()`. L'oggetto `User` ha una variabile pubblica `$onLoggedIn`, che è un array a cui chiunque può aggiungere un callback. Non appena l'utente effettua il login, il metodo `login()` richiama tutti i callback presenti nell'array. Il nome di una variabile nella forma `onXyz` è una convenzione utilizzata in tutto Nette. +Latte +----- +Uno dei più avanzati [sistemi di template |latte:]. -Latte .[#toc-latte] -------------------- -Uno dei [sistemi di template |latte:] più innovativi di sempre. +Model +----- +Il modello è la base dati e soprattutto funzionale dell'intera applicazione. Contiene tutta la logica applicativa (si usa anche il termine business logic). È la **M** di **M**VC o MVP. Qualsiasi azione dell'utente (login, aggiunta di merce al carrello, modifica di un valore nel database) rappresenta un'azione del modello. +Il modello gestisce il proprio stato interno e offre all'esterno un'interfaccia fissa. Chiamando le funzioni di questa interfaccia possiamo interrogare o modificare il suo stato. Il modello non sa dell'esistenza della [#view] o del [#controller]. -Modello .[#toc-model] + +Model-View-Controller --------------------- -Il modello rappresenta la base dei dati e delle funzioni dell'intera applicazione. Include l'intera logica dell'applicazione (talvolta indicata anche come "logica aziendale"). È la **M** di **M**VC o MPV. Qualsiasi azione dell'utente (login, inserimento di oggetti nel carrello, modifica di un valore del database) rappresenta un'azione del modello. +Architettura software nata dalla necessità di separare nelle applicazioni con interfaccia grafica il codice di gestione ([#controller]) dal codice della logica applicativa ([#model]) e dal codice che visualizza i dati ([#view]). Questo rende l'applicazione più chiara, facilita lo sviluppo futuro e consente il testing delle singole parti separatamente. -Il modello gestisce il suo stato interno e fornisce un'interfaccia pubblica. Richiamando questa interfaccia, possiamo prendere o cambiare il suo stato. Il modello non conosce l'esistenza di una [vista |#view] o di un [controllore |#controller], è totalmente indipendente da essi. +Model-View-Presenter +-------------------- +Architettura basata su [#Model-View-Controller]. -Modello-Vista-Controllore .[#toc-model-view-controller] -------------------------------------------------------- -Architettura software emersa nello sviluppo di applicazioni GUI per separare il codice per il controllo del flusso ([controller |#controller]) dal codice della logica dell'applicazione ([model |#model]) e dal codice per il rendering dei dati ([view |#view]). In questo modo il codice è più comprensibile, facilita lo sviluppo futuro e consente di testare separatamente le parti separate. +Modulo +------ +Un modulo rappresenta una parte logica dell'applicazione. In una disposizione tipica, si tratta di un gruppo di presenter e template che gestiscono una determinata area di funzionalità. I moduli vengono collocati in [directory separate |application:directory-structure#Presenter e template], come ad esempio `Front/`, `Admin/` o `Shop/`. -Modello-Vista-Presentatore .[#toc-model-view-presenter] -------------------------------------------------------- -Architettura basata su [Model-View-Controller |#Model-View-Controller]. +Ad esempio, un e-shop può essere suddiviso in: +- Frontend (`Shop/`) per la visualizzazione dei prodotti e l'acquisto +- Sezione clienti (`Customer/`) per la gestione degli ordini +- Amministrazione (`Admin/`) per il gestore +Tecnicamente si tratta di directory comuni, ma grazie a una suddivisione chiara aiutano a scalare l'applicazione. Il presenter `Admin:Product:List` sarà quindi fisicamente collocato, ad esempio, nella directory `app/Presentation/Admin/Product/List/` (vedi [mapping dei presenter |application:directory-structure#Mappatura dei presenter]). -Modulo .[#toc-module] ---------------------- -[Il modulo |application:modules] in Nette Framework rappresenta un insieme di presentatori e modelli, eventualmente anche componenti e modelli, che servono dati a un presentatore. È quindi una parte logica di un'applicazione. -Ad esempio, un e-shop può avere tre moduli: -1) Catalogo prodotti con carrello. -2) Amministrazione per il cliente. -3) Amministrazione per il negoziante. +Namespace +--------- +Spazio dei nomi, parte del linguaggio PHP dalla versione 5.3 e di alcuni altri linguaggi di programmazione, che consente l'uso di classi che hanno lo stesso nome in librerie diverse senza che si verifichi una collisione di nomi. Vedi [documentazione PHP |https://www.php.net/manual/en/language.namespaces.rationale.php]. -Spazio dei nomi .[#toc-namespace] ---------------------------------- -Lo spazio dei nomi è una caratteristica del linguaggio PHP dalla versione 5.3 e di altri linguaggi di programmazione. Aiuta a evitare collisioni di nomi (ad esempio, due classi con lo stesso nome) quando si utilizzano insieme librerie diverse. Per ulteriori dettagli, consultare la [documentazione di PHP |https://www.php.net/manual/en/language.namespaces.rationale.php]. +Presenter +--------- +Un presenter è un oggetto che prende la [richiesta |api:Nette\Application\Request] tradotta dal router dalla richiesta HTTP e genera una [risposta |api:Nette\Application\Response]. La risposta può essere una pagina HTML, un'immagine, un documento XML, un file su disco, JSON, un redirect o qualsiasi cosa si inventi. +Di solito, il termine presenter si riferisce a un discendente della classe [api:Nette\Application\UI\Presenter]. In base alle richieste in arrivo, esegue le [azioni |application:presenters#Ciclo di vita del presenter] corrispondenti e renderizza i template. -Presentatore .[#toc-presenter] ------------------------------- -Il presentatore è un oggetto che prende la [richiesta |api:Nette\Application\Request] tradotta dal router dalla richiesta HTTP e genera una [risposta |api:Nette\Application\Response]. La risposta può essere una pagina HTML, un'immagine, un documento XML, un file, un JSON, un reindirizzamento o qualsiasi altra cosa si pensi. -Per presentatore si intende solitamente un discendente della classe [api:Nette\Application\UI\Presenter]. Con le richieste esegue le [azioni |application:presenters#life-cycle-of-presenter] appropriate e rende i modelli. +Router +------ +Traduttore bidirezionale tra richiesta HTTP / URL e azione del presenter. Bidirezionale significa che dalla richiesta HTTP è possibile derivare l'[#azione del presenter], ma anche viceversa generare l'URL corrispondente per l'azione. Maggiori informazioni nel capitolo sul [routing URL |application:routing]. -Router .[#toc-router] ---------------------- -Traduttore bidirezionale tra richiesta HTTP / URL e azione del presentatore. Bi-direzionale significa che non solo è possibile derivare un'[azione del presentatore |#presenter action] dalla richiesta HTTP, ma anche generare l'URL appropriato per un'azione. Per saperne di più, consultare il capitolo sull'[instradamento degli URL |application:routing]. +Cookie SameSite +--------------- +I cookie SameSite forniscono un meccanismo per riconoscere cosa ha portato al caricamento della pagina. Può avere tre valori: `Lax`, `Strict` e `None` (quest'ultimo richiede HTTPS). Se la richiesta per la pagina proviene direttamente dal sito o l'utente apre la pagina inserendola direttamente nella barra degli indirizzi o cliccando su un segnalibro, il browser invia al server tutti i cookie (cioè con flag `Lax`, `Strict` e `None`). Se l'utente arriva al sito cliccando su un link da un altro sito, vengono trasmessi al server i cookie con flag `Lax` e `None`. Se la richiesta viene generata in altro modo, come l'invio di un form POST da un altro sito, il caricamento all'interno di un iframe, tramite JavaScript, ecc., vengono inviati solo i cookie con flag `None`. -Cookie SameSite .[#toc-samesite-cookie] ---------------------------------------- -I cookie SameSite forniscono un meccanismo per riconoscere la causa del caricamento della pagina. Può avere tre valori: `Lax`, `Strict` e `None` (quest'ultimo richiede HTTPS). Se la richiesta della pagina proviene direttamente dal sito o l'utente apre la pagina digitando direttamente nella barra degli indirizzi o cliccando su un segnalibro, il browser invia tutti i cookie al server (cioè con i flag `Lax`, `Strict` e `None`). Se l'utente clicca sul sito tramite un link proveniente da un altro sito, i cookie con i flag `Lax` e `None` vengono passati al server. Se la richiesta viene effettuata con altri mezzi, come l'invio di un modulo POST da un altro sito, il caricamento all'interno di un iframe, l'utilizzo di JavaScript, ecc. vengono inviati solo i cookie con il flag `None`. +Servizio +-------- +Nel contesto della Dependency Injection, per servizio si intende un oggetto creato e gestito dal container DI. Un servizio può essere facilmente sostituito da un'altra implementazione, ad esempio a scopo di test o per modificare il comportamento dell'applicazione, senza dover modificare il codice che utilizza il servizio. -Servizio .[#toc-service] ------------------------- -Nel contesto della Dependency Injection, un servizio si riferisce a un oggetto creato e gestito da un contenitore DI. Un servizio può essere facilmente sostituito da un'altra implementazione, ad esempio a scopo di test o per modificare il comportamento di un'applicazione, senza dover modificare il codice che utilizza il servizio. +Snippet +------- +Frammento, parte della pagina che può essere ridisegnata separatamente durante una richiesta AJAX. -Frammento .[#toc-snippet] -------------------------- -Snippet di una pagina, che può essere restituito separatamente durante una richiesta [AJAX |#AJAX]. +View +---- +La view, cioè la vista, è il livello dell'applicazione responsabile della visualizzazione del risultato della richiesta. Di solito utilizza un sistema di template e sa come visualizzare un particolare componente o il risultato ottenuto dal modello. -Vista .[#toc-view] ------------------- -La vista è un livello dell'applicazione che è responsabile della resa dei risultati della richiesta. Di solito utilizza un sistema di template e sa come rendere i suoi componenti o i risultati presi dal modello. diff --git a/nette/it/installation.texy b/nette/it/installation.texy index 48fbf452d1..9a6476f930 100644 --- a/nette/it/installation.texy +++ b/nette/it/installation.texy @@ -2,66 +2,66 @@ Installazione di Nette ********************** .[perex] -Volete sfruttare i vantaggi di Nette nel vostro progetto esistente o state pensando di creare un nuovo progetto basato su Nette? Questa guida vi guiderà passo dopo passo nell'installazione. +Volete sfruttare i vantaggi di Nette nel vostro progetto esistente o state per creare un nuovo progetto basato su Nette? Questa guida vi accompagnerà nell'installazione passo dopo passo. -Come aggiungere Nette al vostro progetto .[#toc-how-to-add-nette-to-your-project] ---------------------------------------------------------------------------------- +Come aggiungere Nette al proprio progetto +----------------------------------------- -Nette offre una raccolta di pacchetti (librerie) utili e sofisticati per PHP. Per incorporarli nel vostro progetto, seguite questi passaggi: +Nette offre una collezione di pacchetti (librerie) utili e avanzati per PHP. Per integrarli nel vostro progetto, seguite questi passaggi: -1) **Questo strumento [Composer |best-practices:composer]:** essenziale per facilitare l'installazione, l'aggiornamento e la gestione delle librerie necessarie al progetto. +1) **Preparate [Composer |best-practices:composer]:** Questo strumento è essenziale per installare, aggiornare e gestire facilmente le librerie necessarie per il vostro progetto. -2) **Scegliere un [pacchetto |www:packages]:** Supponiamo di dover navigare nel file system, cosa che [Finder |utils:finder] del pacchetto `nette/utils` fa in modo eccellente. Il nome del pacchetto si trova nella colonna di destra della documentazione. +2) **Scegliete un [pacchetto |www:packages]:** Supponiamo che abbiate bisogno di navigare nel file system, cosa che [Finder |utils:finder] del pacchetto `nette/utils` fa egregiamente. Il nome del pacchetto è visibile nella colonna destra della sua documentazione. -3) **Installare il pacchetto:** Eseguire questo comando nella directory principale del progetto: +3) **Installate il pacchetto:** Eseguite questo comando nella directory principale del vostro progetto: ```shell composer require nette/utils ``` -Preferite un'interfaccia grafica? Consultate la [guida |https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] all'installazione dei pacchetti nell'ambiente PhpStrom. +Preferite un'interfaccia grafica? Consultate la [guida |https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] per l'installazione dei pacchetti nell'ambiente PhpStorm. -Come avviare un nuovo progetto con Nette .[#toc-how-to-start-a-new-project-with-nette] --------------------------------------------------------------------------------------- +Come creare un nuovo progetto con Nette +--------------------------------------- -Se si desidera creare un progetto completamente nuovo sulla piattaforma Nette, si consiglia di utilizzare lo scheletro preimpostato del [progetto Web |https://github.com/nette/web-project]: +Se volete creare un progetto completamente nuovo sulla piattaforma Nette, vi consigliamo di utilizzare lo skeleton preimpostato [Web Project |https://github.com/nette/web-project]: -1) **Impostare [Composer |best-practices:composer].** +1) **Preparate [Composer |best-practices:composer].** -2) **Aprire la riga di comando** e navigare nella directory principale del server web, ad esempio `/etc/var/www`, `C:/xampp/htdocs`, `/Library/WebServer/Documents`. +2) **Aprite la riga di comando** e navigate nella directory principale del vostro server web, ad es. `/etc/var/www`, `C:/xampp/htdocs`, `/Library/WebServer/Documents`. -3) **Creare il progetto** con questo comando: +3) **Create il progetto** utilizzando questo comando: ```shell -composer create-project nette/web-project PROJECT_NAME +composer create-project nette/web-project NOME_PROGETTO ``` -4) **Non utilizzate Composer?** Scaricate il [progetto Web in formato ZIP |https://github.com/nette/web-project/archive/preloaded.zip] ed estraetelo. Ma fidatevi di noi, Composer ne vale la pena! +4) **Non usate Composer?** Basta scaricare [Web Project in formato ZIP |https://github.com/nette/web-project/archive/preloaded.zip] ed estrarlo. Ma credeteci, Composer vale la pena! -5) **Impostazione dei permessi:** Sui sistemi macOS o Linux, impostare i [permessi di scrittura |nette:troubleshooting#Setting directory permissions] per le directory. +5) **Impostazione dei permessi:** Sui sistemi macOS o Linux, impostate i [permessi di scrittura |nette:troubleshooting#Impostazione dei permessi delle directory] per le directory `temp/` e `log/`. -6) **Aprire il progetto in un browser:** Inserire l'URL `http://localhost/PROJECT_NAME/www/`. Verrà visualizzata la pagina di destinazione dello scheletro: +6) **Apertura del progetto nel browser:** Inserite l'URL `http://localhost/NOME_PROGETTO/www/` e vedrete la pagina iniziale dello skeleton: -[* qs-welcome.webp .{url: http://localhost/PROJECT_NAME/www/} *] +[* qs-welcome.webp .{url: http://localhost/NOME_PROGETTO/www/} *] -Congratulazioni! Il vostro sito web è ora pronto per lo sviluppo. Rimuovete pure il modello di benvenuto e iniziate a costruire la vostra applicazione. +Congratulazioni! Il vostro sito è ora pronto per lo sviluppo. Potete rimuovere il template di benvenuto e iniziare a creare la vostra applicazione. -Uno dei vantaggi di Nette è che il progetto funziona immediatamente senza bisogno di configurazione. Tuttavia, se si riscontrano dei problemi, è bene considerare le [soluzioni |nette:troubleshooting#nette-is-not-working-white-page-is-displayed] ai [problemi più comuni |nette:troubleshooting#nette-is-not-working-white-page-is-displayed]. +Uno dei vantaggi di Nette è che il progetto funziona immediatamente senza bisogno di configurazione. Tuttavia, se incontrate problemi, provate a consultare la [soluzione dei problemi comuni |nette:troubleshooting#Nette non funziona viene visualizzata una pagina bianca]. .[note] -Se state iniziando a lavorare con Nette, vi consigliamo di continuare con l'[esercitazione Creare la prima applicazione |quickstart:]. +Se state iniziando con Nette, vi consigliamo di continuare con il [tutorial Scrivere la prima applicazione |quickstart:]. -Strumenti e raccomandazioni .[#toc-tools-and-recommendations] -------------------------------------------------------------- +Strumenti e raccomandazioni +--------------------------- -Per lavorare in modo efficiente con Nette, si consigliano i seguenti strumenti: +Per lavorare efficacemente con Nette, consigliamo i seguenti strumenti: -- [IDE di alta qualità con plugin per Nette |best-practices:editors-and-tools] -- Sistema di controllo delle versioni Git -- [Compositore |best-practices:composer] +- [IDE di qualità con plugin per Nette |best-practices:editors-and-tools] +- Sistema di versionamento Git +- [Composer |best-practices:composer] {{leftbar: www:@menu-common}} diff --git a/nette/it/introduction-to-object-oriented-programming.texy b/nette/it/introduction-to-object-oriented-programming.texy new file mode 100644 index 0000000000..7dc2bc9f66 --- /dev/null +++ b/nette/it/introduction-to-object-oriented-programming.texy @@ -0,0 +1,841 @@ +Introduzione alla programmazione orientata agli oggetti +******************************************************* + +.[perex] +Il termine "OOP" si riferisce alla programmazione orientata agli oggetti, che è un modo per organizzare e strutturare il codice. L'OOP ci permette di vedere un programma come un insieme di oggetti che comunicano tra loro, invece che come una sequenza di comandi e funzioni. + +Nell'OOP, un "oggetto" è un'unità che contiene dati e funzioni che operano su questi dati. Gli oggetti sono creati secondo delle "classi", che possiamo considerare come progetti o modelli per gli oggetti. Quando abbiamo una classe, possiamo creare una sua "istanza", che è un oggetto specifico creato secondo quella classe. + +Vediamo come possiamo creare una semplice classe in PHP. Quando definiamo una classe, usiamo la parola chiave "class", seguita dal nome della classe e poi dalle parentesi graffe, che racchiudono le funzioni (chiamate "metodi") e le variabili della classe (chiamate "proprietà"): + +```php +class Automobile +{ + function suonaClacson() + { + echo 'Bip bip!'; + } +} +``` + +In questo esempio, abbiamo creato una classe chiamata `Automobile` con una funzione (o "metodo") chiamata `suonaClacson`. + +Ogni classe dovrebbe risolvere solo un compito principale. Se una classe fa troppe cose, potrebbe essere opportuno dividerla in classi più piccole e specializzate. + +Le classi vengono solitamente salvate in file separati per mantenere il codice organizzato e facile da navigare. Il nome del file dovrebbe corrispondere al nome della classe, quindi per la classe `Automobile`, il nome del file sarebbe `Automobile.php`. + +Nel nominare le classi, è buona norma seguire la convenzione "PascalCase", il che significa che ogni parola nel nome inizia con una lettera maiuscola e non ci sono trattini bassi o altri separatori tra di esse. Metodi e proprietà usano la convenzione "camelCase", cioè iniziano con una lettera minuscola. + +Alcuni metodi in PHP hanno compiti speciali e sono contrassegnati dal prefisso `__` (due trattini bassi). Uno dei metodi speciali più importanti è il "costruttore", contrassegnato come `__construct`. Il costruttore è un metodo che viene chiamato automaticamente quando si crea una nuova istanza della classe. + +Il costruttore viene spesso utilizzato per impostare lo stato iniziale dell'oggetto. Ad esempio, quando si crea un oggetto che rappresenta una persona, è possibile utilizzare il costruttore per impostare la sua età, il nome o altre proprietà. + +Vediamo come utilizzare un costruttore in PHP: + +```php +class Persona +{ + private $eta; + + function __construct($eta) + { + $this->eta = $eta; + } + + function quantiAnniHai() + { + return $this->eta; + } +} + +$persona = new Persona(25); +echo $persona->quantiAnniHai(); // Stampa: 25 +``` + +In questo esempio, la classe `Persona` ha una proprietà (variabile) `$eta` e un costruttore che imposta questa proprietà. Il metodo `quantiAnniHai()` consente quindi di accedere all'età della persona. + +La pseudo-variabile `$this` viene utilizzata all'interno della classe per accedere alle proprietà e ai metodi dell'oggetto. + +La parola chiave `new` viene utilizzata per creare una nuova istanza della classe. Nell'esempio precedente, abbiamo creato una nuova persona con un'età di 25 anni. + +È anche possibile impostare valori predefiniti per i parametri del costruttore, nel caso in cui non vengano specificati durante la creazione dell'oggetto. Ad esempio: + +```php +class Persona +{ + private $eta; + + function __construct($eta = 20) + { + $this->eta = $eta; + } + + function quantiAnniHai() + { + return $this->eta; + } +} + +$persona = new Persona; // se non si passa alcun argomento, le parentesi possono essere omesse +echo $persona->quantiAnniHai(); // Stampa: 20 +``` + +In questo esempio, se non si specifica l'età durante la creazione dell'oggetto `Persona`, verrà utilizzato il valore predefinito 20. + +È piacevole sapere che la definizione di una proprietà con la sua inizializzazione tramite il costruttore può essere abbreviata e semplificata in questo modo: + +```php +class Persona +{ + function __construct( + private $eta = 20, + ) { + } +} +``` + +Per completezza, oltre ai costruttori, gli oggetti possono avere anche distruttori (metodo `__destruct`), che vengono chiamati prima che l'oggetto venga rilasciato dalla memoria. + + +Namespace +--------- + +I namespace (o "namespaces" in inglese) ci permettono di organizzare e raggruppare classi, funzioni e costanti correlate, evitando al contempo conflitti di nomi. Potete immaginarli come cartelle sul vostro computer, dove ogni cartella contiene file che appartengono a un progetto o argomento specifico. + +I namespace sono particolarmente utili in progetti più grandi o quando si utilizzano librerie di terze parti, dove potrebbero sorgere conflitti nei nomi delle classi. + +Immaginate di avere una classe chiamata `Automobile` nel vostro progetto e di volerla inserire in un namespace chiamato `Trasporti`. Lo fareste in questo modo: + +```php +namespace Trasporti; + +class Automobile +{ + function suonaClacson() + { + echo 'Bip bip!'; + } +} +``` + +Se volete utilizzare la classe `Automobile` in un altro file, dovete specificare da quale namespace proviene la classe: + +```php +$auto = new Trasporti\Automobile; +``` + +Per semplificare, potete indicare all'inizio del file quale classe di un dato namespace volete utilizzare, il che permette di creare istanze senza dover specificare l'intero percorso: + +```php +use Trasporti\Automobile; + +$auto = new Automobile; +``` + + +Ereditarietà +------------ + +L'ereditarietà è uno strumento della programmazione orientata agli oggetti che consente di creare nuove classi basate su classi già esistenti, ereditandone proprietà e metodi e estendendoli o ridefinendoli secondo necessità. L'ereditarietà permette di garantire la riutilizzabilità del codice e una gerarchia di classi. + +In parole povere, se abbiamo una classe e vorremmo crearne un'altra derivata da essa, ma con alcune modifiche, possiamo "ereditare" la nuova classe dalla classe originale. + +In PHP, l'ereditarietà si realizza tramite la parola chiave `extends`. + +La nostra classe `Persona` memorizza informazioni sull'età. Possiamo avere un'altra classe `Studente`, che estende `Persona` e aggiunge informazioni sul corso di studi. + +Vediamo un esempio: + +```php +class Persona +{ + private $eta; + + function __construct($eta) + { + $this->eta = $eta; + } + + function stampaInformazioni() + { + echo "Età: {$this->eta} anni\n"; + } +} + +class Studente extends Persona +{ + private $corsoDiStudi; + + function __construct($eta, $corsoDiStudi) + { + parent::__construct($eta); + $this->corsoDiStudi = $corsoDiStudi; + } + + function stampaInformazioni() + { + parent::stampaInformazioni(); + echo "Corso di studi: {$this->corsoDiStudi} \n"; + } +} + +$studente = new Studente(20, 'Informatica'); +$studente->stampaInformazioni(); +``` + +Come funziona questo codice? + +- Abbiamo usato la parola chiave `extends` per estendere la classe `Persona`, il che significa che la classe `Studente` eredita tutti i metodi e le proprietà da `Persona`. + +- La parola chiave `parent::` ci permette di chiamare metodi dalla classe genitore. In questo caso, abbiamo chiamato il costruttore dalla classe `Persona` prima di aggiungere la nostra funzionalità alla classe `Studente`. E allo stesso modo, il metodo `stampaInformazioni()` del genitore prima di stampare le informazioni sullo studente. + +L'ereditarietà è destinata a situazioni in cui esiste una relazione "è un" tra le classi. Ad esempio, uno `Studente` è una `Persona`. Un gatto è un animale. Ci dà la possibilità, nei casi in cui nel codice ci aspettiamo un oggetto (ad es. "Persona"), di utilizzare al suo posto un oggetto ereditato (ad es. "Studente"). + +È importante rendersi conto che lo scopo principale dell'ereditarietà **non è** evitare la duplicazione del codice. Al contrario, un uso improprio dell'ereditarietà può portare a codice complesso e difficile da mantenere. Se non esiste una relazione "è un" tra le classi, dovremmo considerare la composizione invece dell'ereditarietà. + +Notate che i metodi `stampaInformazioni()` nelle classi `Persona` e `Studente` stampano informazioni leggermente diverse. E possiamo aggiungere altre classi (ad esempio `Impiegato`) che forniranno altre implementazioni di questo metodo. La capacità di oggetti di classi diverse di rispondere allo stesso metodo in modi diversi si chiama polimorfismo: + +```php +$persone = [ + new Persona(30), + new Studente(20, 'Informatica'), + new Impiegato(45, 'Direttore'), // Supponendo che esista una classe Impiegato +]; + +foreach ($persone as $persona) { + $persona->stampaInformazioni(); +} +``` + + +Composizione +------------ + +La composizione è una tecnica in cui, invece di ereditare proprietà e metodi da un'altra classe, utilizziamo semplicemente la sua istanza nella nostra classe. Questo ci permette di combinare funzionalità e proprietà di più classi senza la necessità di creare strutture ereditarie complesse. + +Vediamo un esempio. Abbiamo una classe `Motore` e una classe `Automobile`. Invece di dire "Automobile è un Motore", diciamo "Automobile ha un Motore", che è una tipica relazione di composizione. + +```php +class Motore +{ + function accendi() + { + echo 'Motore acceso.'; + } +} + +class Automobile +{ + private $motore; + + function __construct() + { + $this->motore = new Motore; + } + + function avvia() + { + $this->motore->accendi(); + echo 'Automobile pronta a partire!'; + } +} + +$auto = new Automobile; +$auto->avvia(); +``` + +Qui `Automobile` non ha tutte le proprietà e i metodi di `Motore`, ma ha accesso ad esso tramite la proprietà `$motore`. + +Il vantaggio della composizione è una maggiore flessibilità nel design e una migliore possibilità di modifiche future. + + +Visibilità +---------- + +In PHP, è possibile definire la "visibilità" per proprietà, metodi e costanti di una classe. La visibilità determina da dove è possibile accedere a questi elementi. + +1. **Public:** Se un elemento è contrassegnato come `public`, significa che è possibile accedervi da qualsiasi luogo, anche al di fuori della classe. + +2. **Protected:** Un elemento contrassegnato come `protected` è accessibile solo all'interno della classe data e di tutti i suoi discendenti (classi che ereditano da questa classe). + +3. **Private:** Se un elemento è `private`, è possibile accedervi solo dall'interno della classe in cui è stato definito. + +Se non si specifica la visibilità, PHP la imposta automaticamente su `public`. + +Vediamo un codice di esempio: + +```php +class EsempioVisibilita +{ + public $proprietaPubblica = 'Pubblica'; + protected $proprietaProtetta = 'Protetta'; + private $proprietaPrivata = 'Privata'; + + public function stampaProprieta() + { + echo $this->proprietaPubblica; // Funziona + echo $this->proprietaProtetta; // Funziona + echo $this->proprietaPrivata; // Funziona + } +} + +$oggetto = new EsempioVisibilita; +$oggetto->stampaProprieta(); +echo $oggetto->proprietaPubblica; // Funziona +// echo $oggetto->proprietaProtetta; // Genera un errore +// echo $oggetto->proprietaPrivata; // Genera un errore +``` + +Continuiamo con l'ereditarietà della classe: + +```php +class FiglioClasse extends EsempioVisibilita +{ + public function stampaProprieta() + { + echo $this->proprietaPubblica; // Funziona + echo $this->proprietaProtetta; // Funziona + // echo $this->proprietaPrivata; // Genera un errore + } +} +``` + +In questo caso, il metodo `stampaProprieta()` nella classe `FiglioClasse` può accedere alle proprietà pubbliche e protette, ma non può accedere alle proprietà private della classe genitore. + +Dati e metodi dovrebbero essere il più nascosti possibile e accessibili solo tramite un'interfaccia definita. Ciò consente di modificare l'implementazione interna della classe senza influenzare il resto del codice. + + +Parola chiave `final` +--------------------- + +In PHP, possiamo usare la parola chiave `final` se vogliamo impedire che una classe, un metodo o una costante vengano ereditati o sovrascritti. Quando contrassegniamo una classe come `final`, non può essere estesa. Quando contrassegniamo un metodo come `final`, non può essere sovrascritto in una classe figlia. + +Sapere che una determinata classe o metodo non verrà ulteriormente modificato ci consente di apportare modifiche più facilmente senza doverci preoccupare di possibili conflitti. Ad esempio, possiamo aggiungere un nuovo metodo senza preoccuparci che un suo discendente abbia già un metodo con lo stesso nome, causando una collisione. Oppure possiamo modificare i parametri di un metodo, poiché ancora una volta non c'è rischio di causare un'incompatibilità con un metodo sovrascritto in un discendente. + +```php +final class ClasseFinale +{ +} + +// Il seguente codice genererà un errore, perché non possiamo ereditare da una classe final. +class FiglioClasseFinale extends ClasseFinale +{ +} +``` + +In questo esempio, il tentativo di ereditare dalla classe finale `ClasseFinale` genererà un errore. + + +Proprietà e metodi statici +-------------------------- + +Quando in PHP parliamo di elementi "statici" di una classe, intendiamo metodi e proprietà che appartengono alla classe stessa, e non a un'istanza specifica di quella classe. Ciò significa che non è necessario creare un'istanza della classe per accedervi. Invece, li chiamate o accedete ad essi direttamente tramite il nome della classe. + +Tenete presente che, poiché gli elementi statici appartengono alla classe e non alle sue istanze, non potete usare la pseudo-variabile `$this` all'interno dei metodi statici. + +L'uso di proprietà statiche porta a [codice poco chiaro e pieno di insidie|dependency-injection:global-state], quindi non dovreste mai usarle e non mostreremo nemmeno un esempio di utilizzo qui. Al contrario, i metodi statici sono utili. Esempio di utilizzo: + +```php +class Calcolatrice +{ + public static function addizione($a, $b) + { + return $a + $b; + } + + public static function sottrazione($a, $b) + { + return $a - $b; + } +} + +// Utilizzo del metodo statico senza creare un'istanza della classe +echo Calcolatrice::addizione(5, 3); // Risultato: 8 +echo Calcolatrice::sottrazione(5, 3); // Risultato: 2 +``` + +In questo esempio, abbiamo creato una classe `Calcolatrice` con due metodi statici. Possiamo chiamare questi metodi direttamente senza creare un'istanza della classe usando l'operatore `::`. I metodi statici sono particolarmente utili per operazioni che non dipendono dallo stato di un'istanza specifica della classe. + + +Costanti di classe +------------------ + +All'interno delle classi, abbiamo la possibilità di definire costanti. Le costanti sono valori che non cambiano mai durante l'esecuzione del programma. A differenza delle variabili, il valore di una costante rimane sempre lo stesso. + +```php +class Automobile +{ + public const NumeroRuote = 4; + + public function mostraNumeroRuote(): int + { + echo self::NumeroRuote; + } +} + +echo Automobile::NumeroRuote; // Output: 4 +``` + +In questo esempio, abbiamo una classe `Automobile` con la costante `NumeroRuote`. Quando vogliamo accedere alla costante all'interno della classe, possiamo usare la parola chiave `self` invece del nome della classe. + + +Interfacce di oggetti +--------------------- + +Le interfacce di oggetti funzionano come "contratti" per le classi. Se una classe deve implementare un'interfaccia di oggetto, deve contenere tutti i metodi definiti da quell'interfaccia. È un ottimo modo per garantire che determinate classi aderiscano allo stesso "contratto" o struttura. + +In PHP, un'interfaccia viene definita con la parola chiave `interface`. Tutti i metodi definiti nell'interfaccia sono pubblici (`public`). Quando una classe implementa un'interfaccia, utilizza la parola chiave `implements`. + +```php +interface Animale +{ + function emettiSuono(); +} + +class Gatto implements Animale +{ + public function emettiSuono() + { + echo 'Miao'; + } +} + +$gatto = new Gatto; +$gatto->emettiSuono(); +``` + +Se una classe implementa un'interfaccia, ma non tutti i metodi attesi sono definiti al suo interno, PHP genererà un errore. + +Una classe può implementare più interfacce contemporaneamente, il che è una differenza rispetto all'ereditarietà, dove una classe può ereditare solo da una classe: + +```php +interface Guardiano +{ + function sorvegliaCasa(); +} + +class Cane implements Animale, Guardiano +{ + public function emettiSuono() + { + echo 'Bau'; + } + + public function sorvegliaCasa() + { + echo 'Il cane sorveglia attentamente la casa'; + } +} +``` + + +Classi astratte +--------------- + +Le classi astratte fungono da modelli di base per altre classi, ma non è possibile crearne istanze direttamente. Contengono una combinazione di metodi completi e metodi astratti, che non hanno un contenuto definito. Le classi che ereditano da classi astratte devono fornire definizioni per tutti i metodi astratti del genitore. + +Per definire una classe astratta, usiamo la parola chiave `abstract`. + +```php +abstract class ClasseAstratta +{ + public function metodoComune() + { + echo 'Questo è un metodo comune'; + } + + abstract public function metodoAstratto(); +} + +class Figlio extends ClasseAstratta +{ + public function metodoAstratto() + { + echo 'Questa è l\'implementazione del metodo astratto'; + } +} + +$istanza = new Figlio; +$istanza->metodoComune(); +$istanza->metodoAstratto(); +``` + +In questo esempio, abbiamo una classe astratta con un metodo comune e un metodo astratto. Poi abbiamo una classe `Figlio`, che eredita da `ClasseAstratta` e fornisce l'implementazione per il metodo astratto. + +Qual è la differenza tra interfacce e classi astratte? Le classi astratte possono contenere sia metodi astratti che concreti, mentre le interfacce definiscono solo quali metodi una classe deve implementare, ma non forniscono alcuna implementazione. Una classe può ereditare solo da una classe astratta, ma può implementare un numero qualsiasi di interfacce. + + +Controllo dei tipi +------------------ + +Nella programmazione, è molto importante essere sicuri che i dati con cui lavoriamo siano del tipo corretto. In PHP, abbiamo strumenti che ci garantiscono questo. La verifica che i dati abbiano il tipo corretto si chiama "controllo dei tipi" (type hinting). + +Tipi che possiamo incontrare in PHP: + +1. **Tipi di base**: Includono `int` (numeri interi), `float` (numeri decimali), `bool` (valori booleani), `string` (stringhe), `array` (array) e `null`. +2. **Classi**: Se vogliamo che un valore sia un'istanza di una classe specifica. +3. **Interfacce**: Definisce un insieme di metodi che una classe deve implementare. Un valore che soddisfa un'interfaccia deve avere questi metodi. +4. **Tipi misti**: Possiamo specificare che una variabile può avere più tipi consentiti. +5. **Void**: Questo tipo speciale indica che una funzione o un metodo non restituisce alcun valore. + +Vediamo come modificare il codice per includere i tipi: + +```php +class Persona +{ + private int $eta; + + public function __construct(int $eta) + { + $this->eta = $eta; + } + + public function stampaEta(): void + { + echo "Questa persona ha {$this->eta} anni."; + } +} + +/** + * Funzione che accetta un oggetto della classe Persona e stampa l'età della persona. + */ +function stampaEtaPersona(Persona $persona): void +{ + $persona->stampaEta(); +} +``` + +In questo modo, abbiamo assicurato che il nostro codice si aspetti e lavori con dati del tipo corretto, il che ci aiuta a prevenire potenziali errori. + +Alcuni tipi non possono essere scritti direttamente in PHP. In tal caso, vengono indicati nel commento phpDoc, che è un formato standard per documentare il codice PHP che inizia con `/**` e termina con `*/`. Permette di aggiungere descrizioni a classi, metodi e così via. E anche di specificare tipi complessi tramite le cosiddette annotazioni `@var`, `@param` e `@return`. Questi tipi vengono poi utilizzati dagli strumenti per l'analisi statica del codice, ma PHP stesso non li controlla. + +```php +class Elenco +{ + /** @var array<Persona> la notazione indica che si tratta di un array di oggetti Persona */ + private array $persone = []; + + public function aggiungiPersona(Persona $persona): void + { + $this->persone[] = $persona; + } +} +``` + + +Confronto e identità +-------------------- + +In PHP, è possibile confrontare oggetti in due modi: + +1. Confronto di valori `==`: Verifica se gli oggetti sono della stessa classe e hanno gli stessi valori nelle loro proprietà. +2. Identità `===`: Verifica se si tratta della stessa istanza dell'oggetto. + +```php +class Automobile +{ + public string $marca; + + public function __construct(string $marca) + { + $this->marca = $marca; + } +} + +$auto1 = new Automobile('Skoda'); +$auto2 = new Automobile('Skoda'); +$auto3 = $auto1; + +var_dump($auto1 == $auto2); // true, perché hanno lo stesso valore +var_dump($auto1 === $auto2); // false, perché non sono la stessa istanza +var_dump($auto1 === $auto3); // true, perché $auto3 è la stessa istanza di $auto1 +``` + + +Operatore `instanceof` +---------------------- + +L'operatore `instanceof` consente di verificare se un dato oggetto è un'istanza di una determinata classe, un discendente di quella classe, o se implementa una determinata interfaccia. + +Immaginiamo di avere una classe `Persona` e un'altra classe `Studente`, che è un discendente della classe `Persona`: + +```php +class Persona +{ + private int $eta; + + public function __construct(int $eta) + { + $this->eta = $eta; + } +} + +class Studente extends Persona +{ + private string $corsoDiStudi; + + public function __construct(int $eta, string $corsoDiStudi) + { + parent::__construct($eta); + $this->corsoDiStudi = $corsoDiStudi; + } +} + +$studente = new Studente(20, 'Informatica'); + +// Verifica se $studente è un'istanza della classe Studente +var_dump($studente instanceof Studente); // Output: bool(true) + +// Verifica se $studente è un'istanza della classe Persona (perché Studente è un discendente di Persona) +var_dump($studente instanceof Persona); // Output: bool(true) +``` + +Dagli output è evidente che l'oggetto `$studente` è considerato contemporaneamente un'istanza di entrambe le classi - `Studente` e `Persona`. + + +Interfacce fluenti +------------------ + +L'"interfaccia fluente" (in inglese "Fluent Interface") è una tecnica in OOP che consente di concatenare metodi insieme in una singola chiamata. Questo spesso semplifica e rende più chiaro il codice. + +L'elemento chiave di un'interfaccia fluente è che ogni metodo nella catena restituisce un riferimento all'oggetto corrente. Otteniamo questo usando `return $this;` alla fine del metodo. Questo stile di programmazione è spesso associato a metodi chiamati "setter", che impostano i valori delle proprietà dell'oggetto. + +Vediamo come può apparire un'interfaccia fluente nell'esempio dell'invio di email: + +```php +public function inviaMessaggio() +{ + $email = new Email; // Supponendo che esista una classe Email + $email->setFrom('mittente@example.com') + ->setRecipient('destinatario@example.com') + ->setMessage('Ciao, questo è un messaggio.') + ->send(); // Supponendo che esista un metodo send() +} +``` + +In questo esempio, i metodi `setFrom()`, `setRecipient()` e `setMessage()` servono a impostare i valori corrispondenti (mittente, destinatario, contenuto del messaggio). Dopo aver impostato ciascuno di questi valori, i metodi ci restituiscono l'oggetto corrente (`$email`), il che ci permette di concatenare un altro metodo dopo di esso. Infine, chiamiamo il metodo `send()`, che invia effettivamente l'email. + +Grazie alle interfacce fluenti, possiamo scrivere codice intuitivo e facilmente leggibile. + + +Copia tramite `clone` +--------------------- + +In PHP, possiamo creare una copia di un oggetto usando l'operatore `clone`. In questo modo, otteniamo una nuova istanza con contenuto identico. + +Se abbiamo bisogno di modificare alcune proprietà durante la copia di un oggetto, possiamo definire nella classe un metodo speciale `__clone()`. Questo metodo viene chiamato automaticamente quando l'oggetto viene clonato. + +```php +class Pecora +{ + public string $nome; + + public function __construct(string $nome) + { + $this->nome = $nome; + } + + public function __clone() + { + $this->nome = 'Clone ' . $this->nome; + } +} + +$originale = new Pecora('Dolly'); +echo $originale->nome . "\n"; // Stampa: Dolly + +$clone = clone $originale; +echo $clone->nome . "\n"; // Stampa: Clone Dolly +``` + +In questo esempio, abbiamo una classe `Pecora` con una proprietà `$nome`. Quando cloniamo un'istanza di questa classe, il metodo `__clone()` si assicura che il nome della pecora clonata ottenga il prefisso "Clone". + + +Trait +----- + +I trait in PHP sono uno strumento che consente di condividere metodi, proprietà e costanti tra classi e prevenire la duplicazione del codice. Potete immaginarli come un meccanismo di "copia e incolla" (Ctrl-C e Ctrl-V), in cui il contenuto del trait viene "incollato" nelle classi. Ciò consente di riutilizzare il codice senza la necessità di creare gerarchie di classi complicate. + +Vediamo un semplice esempio di come utilizzare i trait in PHP: + +```php +trait SuonareClacson +{ + public function suonaClacson() + { + echo 'Bip bip!'; + } +} + +class Automobile +{ + use SuonareClacson; +} + +class Camion +{ + use SuonareClacson; +} + +$auto = new Automobile; +$auto->suonaClacson(); // Stampa 'Bip bip!' + +$camion = new Camion; +$camion->suonaClacson(); // Stampa anche 'Bip bip!' +``` + +In questo esempio, abbiamo un trait chiamato `SuonareClacson`, che contiene un metodo `suonaClacson()`. Poi abbiamo due classi: `Automobile` e `Camion`, che entrambe usano il trait `SuonareClacson`. Grazie a questo, entrambe le classi "hanno" il metodo `suonaClacson()`, e possiamo chiamarlo sugli oggetti di entrambe le classi. + +I trait vi permettono di condividere codice tra classi in modo facile ed efficiente. Tuttavia, non entrano nella gerarchia ereditaria, cioè `$auto instanceof SuonareClacson` restituirà `false`. + + +Eccezioni +--------- + +Le eccezioni in OOP ci permettono di gestire elegantemente errori e situazioni inaspettate nel nostro codice. Sono oggetti che trasportano informazioni sull'errore o sulla situazione insolita. + +In PHP, abbiamo una classe incorporata `Exception`, che funge da base per tutte le eccezioni. Ha diversi metodi che ci permettono di ottenere maggiori informazioni sull'eccezione, come il messaggio di errore, il file e la riga in cui si è verificato l'errore, ecc. + +Quando si verifica un errore nel codice, possiamo "lanciare" un'eccezione usando la parola chiave `throw`. + +```php +function divisione(float $a, float $b): float +{ + if ($b === 0.0) { // Confronto più sicuro per i float + throw new Exception('Divisione per zero!'); + } + return $a / $b; +} +``` + +Quando la funzione `divisione()` riceve zero come secondo argomento, lancia un'eccezione con il messaggio di errore `'Divisione per zero!'`. Per evitare che il programma si blocchi quando viene lanciata un'eccezione, la catturiamo in un blocco `try/catch`: + +```php +try { + echo divisione(10, 0); +} catch (Exception $e) { + echo 'Eccezione catturata: '. $e->getMessage(); +} +``` + +Il codice che può lanciare un'eccezione è racchiuso in un blocco `try`. Se viene lanciata un'eccezione, l'esecuzione del codice si sposta al blocco `catch`, dove possiamo gestire l'eccezione (ad esempio, stampare un messaggio di errore). + +Dopo i blocchi `try` e `catch`, possiamo aggiungere un blocco `finally` opzionale, che verrà eseguito sempre, indipendentemente dal fatto che sia stata lanciata o meno un'eccezione (anche nel caso in cui usiamo l'istruzione `return`, `break` o `continue` nel blocco `try` o `catch`): + +```php +try { + echo divisione(10, 0); +} catch (Exception $e) { + echo 'Eccezione catturata: '. $e->getMessage(); +} finally { + // Codice che viene eseguito sempre, indipendentemente dal fatto che sia stata lanciata un'eccezione o meno +} +``` + +Possiamo anche creare le nostre classi (gerarchia) di eccezioni che ereditano dalla classe Exception. Come esempio, immaginiamo una semplice applicazione bancaria che consente di effettuare depositi e prelievi: + +```php +class EccezioneBancaria extends Exception {} +class EccezioneFondiInsufficienti extends EccezioneBancaria {} +class EccezioneLimiteSuperato extends EccezioneBancaria {} + +class ContoBancario +{ + private int $saldo = 0; + private int $limiteGiornaliero = 1000; + + public function deposita(int $importo): int + { + $this->saldo += $importo; + return $this->saldo; + } + + public function preleva(int $importo): int + { + if ($importo > $this->saldo) { + throw new EccezioneFondiInsufficienti('Fondi insufficienti sul conto.'); + } + + if ($importo > $this->limiteGiornaliero) { + throw new EccezioneLimiteSuperato('È stato superato il limite giornaliero per i prelievi.'); + } + + $this->saldo -= $importo; + return $this->saldo; + } +} +``` + +Per un singolo blocco `try`, è possibile specificare più blocchi `catch`, se ci si aspetta diversi tipi di eccezioni. + +```php +$conto = new ContoBancario; +$conto->deposita(500); + +try { + $conto->preleva(1500); +} catch (EccezioneLimiteSuperato $e) { + echo $e->getMessage(); +} catch (EccezioneFondiInsufficienti $e) { + echo $e->getMessage(); +} catch (EccezioneBancaria $e) { + echo 'Si è verificato un errore durante l\'esecuzione dell\'operazione.'; +} +``` + +In questo esempio, è importante notare l'ordine dei blocchi `catch`. Poiché tutte le eccezioni ereditano da `EccezioneBancaria`, se avessimo questo blocco per primo, catturerebbe tutte le eccezioni senza che il codice raggiunga i blocchi `catch` successivi. Pertanto, è importante avere eccezioni più specifiche (cioè quelle che ereditano da altre) nel blocco `catch` più in alto nell'ordine rispetto alle loro eccezioni genitore. + + +Iterazione +---------- + +In PHP, è possibile iterare sugli oggetti usando il ciclo `foreach`, in modo simile a come si itera sugli array. Affinché funzioni, l'oggetto deve implementare un'interfaccia speciale. + +La prima opzione è implementare l'interfaccia `Iterator`, che ha i metodi `current()` che restituisce il valore corrente, `key()` che restituisce la chiave, `next()` che si sposta al valore successivo, `rewind()` che si sposta all'inizio e `valid()` che verifica se non siamo ancora alla fine. + +La seconda opzione è implementare l'interfaccia `IteratorAggregate`, che ha solo un metodo `getIterator()`. Questo restituisce o un oggetto sostitutivo che gestirà l'iterazione, oppure può rappresentare un generatore, che è una funzione speciale in cui si usa `yield` per restituire progressivamente chiavi e valori: + +```php +class Persona +{ + public function __construct( + public int $eta, + ) { + } +} + +class Elenco implements IteratorAggregate +{ + private array $persone = []; + + public function aggiungiPersona(Persona $persona): void + { + $this->persone[] = $persona; + } + + public function getIterator(): Generator + { + foreach ($this->persone as $persona) { + yield $persona; + } + } +} + +$elenco = new Elenco; +$elenco->aggiungiPersona(new Persona(30)); +$elenco->aggiungiPersona(new Persona(25)); + +foreach ($elenco as $persona) { + echo "Età: {$persona->eta} anni \n"; +} +``` + + +Buone pratiche +-------------- + +Una volta compresi i principi fondamentali della programmazione orientata agli oggetti, è importante concentrarsi sulle buone pratiche in OOP. Queste vi aiuteranno a scrivere codice che non sia solo funzionale, ma anche leggibile, comprensibile e facilmente manutenibile. + +1) **Separazione delle responsabilità (Separation of Concerns)**: Ogni classe dovrebbe avere una responsabilità chiaramente definita e dovrebbe risolvere solo un compito principale. Se una classe fa troppe cose, potrebbe essere opportuno dividerla in classi più piccole e specializzate. +2) **Incapsulamento (Encapsulation)**: Dati e metodi dovrebbero essere il più nascosti possibile e accessibili solo tramite un'interfaccia definita. Ciò consente di modificare l'implementazione interna della classe senza influenzare il resto del codice. +3) **Iniezione delle dipendenze (Dependency Injection)**: Invece di creare dipendenze direttamente nella classe, dovreste "iniettarle" dall'esterno. Per una comprensione più approfondita di questo principio, consigliamo i [capitoli sulla Dependency Injection|dependency-injection:introduction]. diff --git a/nette/it/troubleshooting.texy b/nette/it/troubleshooting.texy index d9832fb9ff..6a03e2b8a9 100644 --- a/nette/it/troubleshooting.texy +++ b/nette/it/troubleshooting.texy @@ -2,40 +2,69 @@ Risoluzione dei problemi ************************ -Nette non funziona, viene visualizzata una pagina bianca .[#toc-nette-is-not-working-white-page-is-displayed] -------------------------------------------------------------------------------------------------------------- -- Provare a inserire `ini_set('display_errors', '1'); error_reporting(E_ALL);` dopo `declare(strict_types=1);` nel file `index.php` per forzare la visualizzazione degli errori. -- Se si continua a vedere una schermata bianca, probabilmente c'è un errore nella configurazione del server e si potrà scoprire il motivo nel log del server. Per essere sicuri, verificare se PHP funziona provando a stampare qualcosa con `echo 'test';`. -- Se viene visualizzato un errore *Errore del server: Ci dispiace! ...*, continuare con la sezione successiva: +Nette non funziona, viene visualizzata una pagina bianca +-------------------------------------------------------- +- Provate a inserire `ini_set('display_errors', '1'); error_reporting(E_ALL);` nel file `index.php` subito dopo `declare(strict_types=1);`, questo forzerà la visualizzazione degli errori +- Se vedete ancora una schermata bianca, probabilmente c'è un errore nella configurazione del server e il motivo si troverà nel log del server. Per sicurezza, verificate anche se PHP funziona affatto, provando a stampare qualcosa con `echo 'test';` +- Se vedete l'errore *Server Error: We're sorry! …*, continuate con la sezione successiva: -Errore 500 *Errore del server: Ci dispiace! ...* .[#toc-error-500-server-error-we-re-sorry] -------------------------------------------------------------------------------------------- -Questa pagina di errore viene visualizzata da Nette in modalità di produzione. Se viene visualizzata su una macchina per sviluppatori, [passare alla modalità sviluppatore |application:bootstrap#Development vs Production Mode]. +Errore 500 *Server Error: We're sorry! …* +----------------------------------------- +Questa pagina di errore viene visualizzata da Nette in modalità produzione. Se la visualizzate sul vostro computer di sviluppo, [passare alla modalità sviluppatore |application:bootstrapping#Modalità Sviluppo vs Produzione] e vi verrà mostrato Tracy con un messaggio dettagliato. -Se il messaggio di errore contiene `Tracy is unable to log error`, scoprire perché gli errori non possono essere registrati. È possibile farlo, ad esempio, [passando |application:bootstrap#Development vs Production Mode] alla modalità sviluppatore e chiamando `Tracy\Debugger::log('hello');` dopo `$configurator->enableTracy(...)`. Tracy vi dirà perché non può essere registrato. -Di solito la causa è l'[insufficienza dei permessi |#Setting Directory Permissions] di scrittura nella directory `log/`. +Il motivo dell'errore si trova sempre nel log nella directory `log/`. Tuttavia, se nel messaggio di errore compare la frase `Tracy is unable to log error`, scoprite prima perché non è possibile registrare gli errori. Potete farlo, ad esempio, [passando |application:bootstrapping#Modalità Sviluppo vs Produzione] temporaneamente alla modalità sviluppatore e facendo registrare qualcosa a Tracy dopo il suo avvio: -Se la frase `Tracy is unable to log error` non compare (più) nel messaggio di errore, si può scoprire il motivo dell'errore nel log della cartella `log/`. +```php +// Bootstrap.php +$configurator->setDebugMode('23.75.345.200'); // il vostro indirizzo IP +$configurator->enableTracy($rootDir . '/log'); +\Tracy\Debugger::log('hello'); // Scrive 'hello' nel log +``` + +Tracy vi dirà perché non può registrare. La causa potrebbe essere un tubo elettronico difettoso e-tredici della ditta Katoda Olomouc, ma più probabilmente [permessi insufficienti |#Impostazione dei permessi delle directory] per scrivere nella directory `log/`. + +Uno dei motivi più comuni dell'errore 500 è una cache obsoleta. Mentre Nette in modalità sviluppatore aggiorna intelligentemente la cache automaticamente, in modalità produzione si concentra sulla massimizzazione delle prestazioni e la cancellazione della cache, dopo ogni modifica del codice, spetta a voi. Provate a cancellare `temp/cache`. + + +Errore 404, il routing non funziona +----------------------------------- +Quando tutte le pagine (tranne la homepage) restituiscono un errore 404, sembra esserci un problema con la configurazione del server per gli [URL leggibili |#Come configurare il server per URL leggibili]. + + +Le modifiche ai template o alla configurazione non vengono applicate +-------------------------------------------------------------------- +"Ho modificato il template o la configurazione, ma il sito mostra ancora la vecchia versione." Questo comportamento si verifica in [modalità produzione |application:bootstrapping#Modalità Sviluppo vs Produzione], che per motivi di prestazioni non controlla le modifiche ai file e mantiene la cache generata una volta. + +Per non dover cancellare manualmente la cache sul server di produzione dopo ogni modifica, abilitate la modalità sviluppatore per il vostro indirizzo IP nel file `Bootstrap.php`: + +```php +$configurator->setDebugMode('vostro.indirizzo.ip'); +``` + + +Come disattivare la cache durante lo sviluppo? +---------------------------------------------- +Nette è intelligente e non è necessario disattivare la cache. Durante lo sviluppo, infatti, aggiorna automaticamente la cache ad ogni modifica del template o della configurazione del container DI. La modalità sviluppatore, inoltre, si attiva tramite autodetect, quindi di solito non è necessario configurare nulla, [o solo l'indirizzo IP |application:bootstrapping#Modalità Sviluppo vs Produzione]. -Una delle ragioni più comuni è una cache non aggiornata. Mentre Nette aggiorna automaticamente la cache in modalità di sviluppo, in modalità di produzione si concentra sulla massimizzazione delle prestazioni e la cancellazione della cache dopo ogni modifica del codice dipende da voi. Provate a cancellare `temp/cache`. +Durante il debug del router, consigliamo di disattivare la cache del browser, nella quale potrebbero essere memorizzati, ad esempio, i redirect: aprite gli Strumenti per sviluppatori (Ctrl+Shift+I o Cmd+Option+I) e nel pannello Rete (Network) selezionate la disattivazione della cache. -Errore `#[\ReturnTypeWillChange] attribute should be used` .[#toc-error-returntypewillchange-attribute-should-be-used] ----------------------------------------------------------------------------------------------------------------------- -Questo errore si verifica se si è aggiornato PHP alla versione 8.1 ma si sta utilizzando Nette, che non è compatibile con essa. La soluzione è aggiornare Nette a una versione più recente utilizzando `composer update`. Nette supporta PHP 8.1 dalla versione 3.0. Se si sta utilizzando una versione più vecchia (lo si può scoprire consultando `composer.json`), [aggiornare Nette |migrations:en] o rimanere con PHP 8.0. +Errore `#[\ReturnTypeWillChange] attribute should be used` +---------------------------------------------------------- +Questo errore compare se avete aggiornato PHP alla versione 8.1, ma state utilizzando una versione di Nette non compatibile. La soluzione è quindi aggiornare Nette a una versione più recente usando `composer update`. Nette supporta PHP 8.1 dalla versione 3.0. Se state utilizzando una versione precedente (verificate guardando `composer.json`), [aggiornare Nette |migrations:en] o rimanete con PHP 8.0. -Impostazione dei permessi di directory .[#toc-setting-directory-permissions] ----------------------------------------------------------------------------- -Se si sta sviluppando su macOS o Linux (o qualsiasi altro sistema basato su Unix), è necessario configurare i privilegi di scrittura sul server web. Supponendo che l'applicazione si trovi nella directory predefinita `/var/www/html` (Fedora, CentOS, RHEL) +Impostazione dei permessi delle directory +----------------------------------------- +Se sviluppate su macOS o Linux (o su qualsiasi altro sistema basato su Unix), dovrete impostare i permessi di scrittura per il server web. Supponiamo che la vostra applicazione si trovi nella directory predefinita `/var/www/html` (Fedora, CentOS, RHEL). ```shell cd /var/www/html/MY_PROJECT chmod -R a+rw temp log ``` -Su alcuni sistemi Linux (Fedora, CentOS, ...) SELinux potrebbe essere abilitato per impostazione predefinita. Potrebbe essere necessario aggiornare le politiche SELinux o impostare i percorsi delle directory `temp` e `log` con il corretto contesto di sicurezza SELinux. Le directory `temp` e `log` dovrebbero essere impostate con il contesto `httpd_sys_rw_content_t`; per il resto dell'applicazione, principalmente la cartella `app`, il contesto `httpd_sys_content_t` sarà sufficiente. Eseguire sul server come root: +Su alcuni sistemi Linux (Fedora, CentOS, ...), SELinux è abilitato per impostazione predefinita. Dovrete modificare le policy di SELinux di conseguenza e impostare il contesto di sicurezza SELinux corretto per le cartelle `temp` e `log`. Per `temp` e `log` imposteremo il tipo di contesto `httpd_sys_rw_content_t`, per il resto dell'applicazione (e soprattutto per la cartella `app`) sarà sufficiente `httpd_sys_content_t`. Sul server eseguite: ```shell semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/log(/.*)?' @@ -43,25 +72,30 @@ semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/temp(/.*) restorecon -Rv /var/www/html/MY_PROJECT/ ``` -Successivamente, il booleano SELinux `httpd_can_network_connect_db` deve essere abilitato per consentire a Nette di connettersi al database in rete. Per impostazione predefinita, è disabilitato. Il comando `setsebool` può essere usato per eseguire questa operazione e se viene specificata l'opzione `-P`, questa impostazione sarà persistente tra i vari riavvii. +Inoltre, è necessario abilitare il booleano SELinux `httpd_can_network_connect_db`, che è disabilitato per impostazione predefinita e che consente a Nette di connettersi al database tramite la rete. Utilizzeremo il comando `setsebool` e con l'opzione `-P` renderemo la modifica permanente, cioè dopo il riavvio del server non ci troveremo di fronte a spiacevoli sorprese: ```shell setsebool -P httpd_can_network_connect_db on ``` -Come modificare o rimuovere la directory `www` dall'URL? .[#toc-how-to-change-or-remove-www-directory-from-url] ---------------------------------------------------------------------------------------------------------------- -La directory `www/` utilizzata nei progetti di esempio di Nette è la cosiddetta directory pubblica o document-root del progetto. È l'unica directory il cui contenuto è accessibile al browser. Contiene il file `index.php`, il punto di ingresso che avvia un'applicazione web scritta in Nette. +Come modificare o rimuovere la directory `www` dall'URL? +-------------------------------------------------------- +La directory `www/` utilizzata nei progetti di esempio in Nette rappresenta la cosiddetta directory pubblica o document-root del progetto. È l'unica directory il cui contenuto è accessibile al browser. E contiene il file `index.php`, il punto di ingresso che avvia l'applicazione web scritta in Nette. -Per eseguire l'applicazione sull'hosting, è necessario impostare la document-root su questa directory nella configurazione dell'hosting. Oppure, se l'hosting ha una cartella preconfezionata per la directory pubblica con un nome diverso (ad esempio `web`, `public_html` ecc.), è sufficiente rinominare `www/`. +Per far funzionare l'applicazione sull'hosting, è necessario avere il document-root configurato correttamente. Avete due opzioni: +1. Nella configurazione dell'hosting, impostare il document-root su questa directory +2. Se l'hosting ha una cartella predefinita (ad es. `public_html`), rinominate `www/` con questo nome -La soluzione **non** è quella di "sbarazzarsi" della cartella `www/` usando regole nel file `.htaccess` o nel router. Se l'hosting non vi permette di impostare document-root su una sottodirectory (cioè di creare directory un livello sopra la directory pubblica), cercatene un'altra. In caso contrario, si correrebbe un rischio significativo per la sicurezza. Sarebbe come vivere in un appartamento in cui non si può chiudere la porta d'ingresso, che rimane sempre aperta. +.[warning] +Non cercate mai di risolvere la sicurezza solo con `.htaccess` o il router, che impedirebbero l'accesso alle altre cartelle. +Se l'hosting non consentisse di impostare il document-root in una sottodirectory (cioè creare directory un livello sopra la directory pubblica), cercatene un altro. Altrimenti correreste un rischio significativo per la sicurezza. Sarebbe come vivere in un appartamento dove la porta d'ingresso non si può chiudere ed è sempre spalancata. -Come configurare un server per avere URL gradevoli? .[#toc-how-to-configure-a-server-for-nice-urls] ---------------------------------------------------------------------------------------------------- -**Apache**: l'estensione mod_rewrite deve essere consentita e configurata in un file `.htaccess`. + +Come configurare il server per URL leggibili? +--------------------------------------------- +**Apache**: è necessario abilitare e configurare le regole mod_rewrite nel file `.htaccess`: ```apacheconf RewriteEngine On @@ -70,25 +104,53 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -Per modificare la configurazione di Apache con i file .htaccess, è necessario abilitare la direttiva AllowOverride. Questo è il comportamento predefinito di Apache. +Se incontrate problemi, assicuratevi che: +- il file `.htaccess` si trovi nella directory document-root (cioè accanto al file `index.php`) +- [Apache elabori i file `.htaccess` |#Verifica del funzionamento di .htaccess] +- [mod_rewrite sia abilitato |#Verifica dell abilitazione di mod rewrite] + +Se state configurando l'applicazione in una sottocartella, potrebbe essere necessario decommentare la riga per l'impostazione `RewriteBase` e impostarla sulla cartella corretta. -**nginx**: la direttiva `try_files` deve essere usata nella configurazione del server: +**nginx**: è necessario configurare il reindirizzamento usando la direttiva `try_files` all'interno del blocco `location /` nella configurazione del server. ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args is important + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args È IMPORTANTE! } ``` -Il blocco `location` deve essere definito esattamente una volta per ogni percorso del filesystem nel blocco `server`. Se nella configurazione è già presente un blocco `location /`, aggiungere la direttiva `try_files` al blocco esistente. +Il blocco `location` per ogni percorso del file system può comparire solo una volta nel blocco `server`. Se avete già `location /` nella configurazione, aggiungete la direttiva `try_files` ad esso. + + +Verifica del funzionamento di `.htaccess` +----------------------------------------- +Il modo più semplice per verificare se Apache utilizza o ignora il vostro file `.htaccess` è danneggiarlo intenzionalmente. Inserite la riga `Test` all'inizio del file e ora, se aggiornate la pagina nel browser, dovreste vedere *Internal Server Error*. + +Se vedete questo errore, in realtà è un bene! Significa che Apache sta analizzando il file `.htaccess` e incontra l'errore che abbiamo inserito. Rimuovete la riga `Test`. + +Se non viene visualizzato *Internal Server Error*, la vostra configurazione di Apache sta ignorando il file `.htaccess`. Generalmente Apache lo ignora a causa della mancanza della direttiva di configurazione `AllowOverride All`. + +Se lo ospitate voi stessi, è facile da risolvere. Aprite il file `httpd.conf` o `apache.conf` in un editor di testo, cercate la sezione `<Directory>` pertinente e aggiungete/modificate questa direttiva: + +```apacheconf +<Directory "/var/www/htdocs"> # percorso al vostro document root + AllowOverride All + ... +``` + +Se il vostro sito è ospitato altrove, controllate nel pannello di controllo se potete abilitare il file `.htaccess` lì. In caso contrario, contattate il vostro provider di hosting per farlo per voi. + + +Verifica dell'abilitazione di `mod_rewrite` +------------------------------------------- +Se avete verificato che [funziona `.htaccess` |#Verifica del funzionamento di .htaccess], potete verificare se l'estensione mod_rewrite è abilitata. Inserite la riga `RewriteEngine On` all'inizio del file `.htaccess` e aggiornate la pagina nel browser. Se viene visualizzato *Internal Server Error*, significa che mod_rewrite non è abilitato. Esistono diversi modi per abilitarlo. Diversi modi per farlo in diverse configurazioni si trovano su Stack Overflow. -I collegamenti sono generati senza `https:` .[#toc-links-are-generated-without-https] -------------------------------------------------------------------------------------- -Nette genera i link con lo stesso protocollo utilizzato dalla pagina corrente. Quindi, nella pagina `https://foo` e viceversa. -Se ci si trova dietro a un reverse proxy che blocca HTTPS (per esempio, in Docker), è necessario [impostare un proxy |http:configuration#HTTP proxy] nella configurazione per far funzionare correttamente il rilevamento del protocollo. +I link vengono generati senza `https:` +-------------------------------------- +Nette genera link con lo stesso protocollo della pagina stessa. Quindi, sulla pagina `https://foo` genera link che iniziano con `https:` e viceversa. Se siete dietro un proxy inverso che rimuove HTTPS (ad esempio in Docker), allora è necessario [configurare il proxy |http:configuration#Proxy HTTP] nella configurazione affinché il rilevamento del protocollo funzioni correttamente. -Se si usa Nginx come proxy, è necessario impostare il reindirizzamento in questo modo: +Se utilizzate Nginx come proxy, è necessario avere il reindirizzamento configurato ad esempio così: ``` location / { @@ -96,44 +158,42 @@ location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Port $server_port; - proxy_pass http://IP-aplikace:80; # IP or hostname of the server/container where the application is running + proxy_pass http://IP-applicazione:80; # IP o hostname del server/container dove gira l'applicazione } ``` -Successivamente, è necessario specificare il proxy IP e, se applicabile, l'intervallo IP della rete locale in cui viene eseguita l'infrastruttura: +Inoltre, è necessario specificare nella configurazione l'IP del proxy ed eventualmente l'intervallo IP della vostra rete locale dove gestite l'infrastruttura: ```neon http: - proxy: IP-proxy/IP-range + proxy: IP-proxy/IP-range # Sostituisci con l'IP/range del tuo proxy ``` -Uso dei caratteri { } in JavaScript .[#toc-use-of-characters-in-javascript] ---------------------------------------------------------------------------- -I caratteri `{` and `}` sono utilizzati per scrivere i tag Latte. Per tutto ciò che (eccetto spazi e virgolette) segue `{` character is considered a tag. If you need to print character `{` (spesso in JavaScript), si può mettere uno spazio (o un altro carattere vuoto) subito dopo `{`. In questo modo si evita di interpretarlo come un tag. +Uso dei caratteri { } in JavaScript +----------------------------------- +I caratteri `{` e `}` vengono utilizzati per scrivere i tag Latte. Viene considerato tag tutto ciò che segue il carattere `{` ad eccezione di uno spazio e di una virgoletta. Quindi, se avete bisogno di stampare direttamente il carattere `{` (spesso ad esempio in JavaScript), potete mettere uno spazio (o un altro carattere vuoto) dopo il carattere `{`. In questo modo si evita la traduzione come tag. -Se è necessario stampare questi caratteri in una situazione in cui verrebbero interpretati come tag, si possono usare tag speciali per stampare questi caratteri - `{l}` per `{` and `{r}` per `}`. +Se è necessario stampare questi caratteri in una situazione in cui il testo verrebbe interpretato come un tag, potete utilizzare i tag speciali per stampare questi caratteri - `{l}` per `{` e `{r}` per `}`. ``` -{is tag} -{ is not tag } -{l}is not tag{r} +{è un tag} +{ non è un tag } +{l}non è un tag{r} ``` -Notare `Presenter::getContext() is deprecated` .[#toc-notice-presenter-getcontext-is-deprecated] ------------------------------------------------------------------------------------------------- +Messaggio `Presenter::getContext() is deprecated` +------------------------------------------------- -Nette è di gran lunga il primo framework PHP che è passato all'iniezione di dipendenze e ha indotto i programmatori a usarla in modo coerente, a partire dai presentatori. Se un presentatore ha bisogno di una dipendenza, la [chiederà |dependency-injection:passing-dependencies]. -Al contrario, il modo in cui passiamo l'intero contenitore DI a una classe e questa preleva direttamente le dipendenze è considerato un antipattern (si chiama localizzatore di servizi). -Questo modo era usato in Nette 0.x, prima dell'avvento della dependency injection, e la sua reliquia è il metodo `Presenter::getContext()`, da tempo segnato come deprecato. +Nette è di gran lunga il primo framework PHP che è passato all'iniezione delle dipendenze e ha guidato i programmatori al suo uso coerente, fin dai presenter stessi. Se un presenter ha bisogno di una dipendenza, [la richiede |dependency-injection:passing-dependencies]. Al contrario, il percorso in cui passiamo l'intero container DI alla classe, e questa ne estrae direttamente le dipendenze, è considerato un antipattern (chiamato service locator). Questo metodo veniva utilizzato in Nette 0.x prima dell'avvento dell'iniezione delle dipendenze e il suo residuo è il metodo `Presenter::getContext()`, da tempo contrassegnato come deprecated. -Se si esegue il porting di un'applicazione Nette molto vecchia, si può scoprire che utilizza ancora questo metodo. Quindi, dalla versione 3.1 di `nette/application` si incontrerà l'avviso `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection`, dalla versione 4.0 si incontrerà l'errore che il metodo non esiste. +Se state portando un'applicazione molto vecchia per Nette, potreste scoprire che utilizza ancora questo metodo. Dalla versione 3.1 di `nette/application` incontrerete quindi l'avviso `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection`, dalla versione 4.0 l'errore che il metodo non esiste. -La soluzione pulita, ovviamente, è quella di riprogettare l'applicazione per passare le dipendenze usando la dependency injection. Come soluzione alternativa, si può aggiungere il proprio metodo `getContext()` al presentatore di base e aggirare il messaggio: +La soluzione pulita è ovviamente rifattorizzare l'applicazione in modo che passi le dipendenze usando l'iniezione delle dipendenze. Come workaround potete aggiungere il vostro metodo `getContext()` al vostro presenter di base e aggirare così il messaggio: ```php -abstract BasePresenter extends Nette\Application\UI\Presenter +abstract class BasePresenter extends Nette\Application\UI\Presenter { private Nette\DI\Container $context; diff --git a/nette/it/vulnerability-protection.texy b/nette/it/vulnerability-protection.texy index 6d59b4e31e..77c5832df3 100644 --- a/nette/it/vulnerability-protection.texy +++ b/nette/it/vulnerability-protection.texy @@ -2,21 +2,21 @@ Protezione dalle vulnerabilità ****************************** .[perex] -Ogni tanto viene annunciata o addirittura abusata una grave falla di sicurezza. Sicuramente è un po' spiacevole. Se avete a cuore la sicurezza delle vostre applicazioni web, Nette Framework è francamente la scelta migliore per voi. +Ogni tanto viene segnalata una falla di sicurezza su un altro sito importante o la falla viene sfruttata. Questo è spiacevole. Se vi preoccupate della sicurezza delle vostre applicazioni web, Nette Framework è sicuramente la scelta migliore. -Cross-Site Scripting (XSS) .[#toc-cross-site-scripting-xss] -=========================================================== +Cross-Site Scripting (XSS) +========================== -Il Cross-Site Scripting è un metodo di interruzione del sito che utilizza input non inescritti. Un aggressore può iniettare il proprio codice HTML o JavaScript e modificare l'aspetto della pagina o addirittura raccogliere informazioni sensibili sugli utenti. La protezione contro gli XSS è semplice: l'escape coerente e corretto di tutte le stringhe e gli input. Tradizionalmente, bastava che il codificatore commettesse un minimo errore e se ne dimenticasse una volta, per compromettere l'intero sito web. +Cross-Site Scripting è un metodo di violazione dei siti web che sfrutta output non trattati. L'attaccante può quindi inserire il proprio codice nella pagina e quindi modificarla o addirittura ottenere dati sensibili sui visitatori. Ci si può difendere da XSS solo trattando in modo coerente e corretto tutte le stringhe. Eppure basta che il vostro codificatore lo dimentichi una sola volta, e l'intero sito può essere immediatamente compromesso. -Un esempio di tale iniezione può essere l'invio all'utente di un URL alterato, che inserisce uno script "maligno". Se un'applicazione non esegue correttamente l'escape dei suoi input, una richiesta di questo tipo potrebbe eseguire uno script sul lato del client. Questo potrebbe, ad esempio, portare a un furto di identità. +Un esempio di attacco può essere l'invio di un URL modificato all'utente, tramite il quale iniettiamo il nostro codice nella pagina. Se l'applicazione non tratta correttamente gli output, eseguirà lo script nel browser dell'utente. In questo modo possiamo, ad esempio, rubargli l'identità. ``` -https://example.com/?search=<script>alert('XSS attack.');</script> +https://example.com/?search=<script>alert('Attacco XSS riuscito.');</script> ``` -Nette Framework propone una nuova tecnologia, il [Context-Aware Escaping |latte:safety-first#context-aware-escaping], che consente di eliminare per sempre i rischi di Cross-Site Scripting. L'escape di tutti gli input avviene automaticamente in base a un determinato contesto, per cui è impossibile che un codificatore dimentichi accidentalmente qualcosa. Considerate il seguente modello come esempio: +Nette Framework introduce una tecnologia rivoluzionaria [Context-Aware Escaping |latte:safety-first#Escaping sensibile al contesto], che vi libererà per sempre dal rischio di Cross-Site Scripting. Tratta automaticamente tutti gli output, quindi non può succedere che il codificatore dimentichi qualcosa. Esempio? Il codificatore crea questo template: ```latte <p onclick="alert({$message})">{$message}</p> @@ -26,29 +26,29 @@ document.title = {$message}; </script> ``` -Il comando `{$message}` stampa una variabile. Altri framework spesso costringono gli sviluppatori a dichiarare esplicitamente l'escape e persino il tipo di escape in base al contesto. In Nette Framework, invece, non è necessario dichiarare nulla. Tutto è automatico, coerente e corretto. Se impostiamo la variabile a `$message = 'Width 1/2"'`, il framework genererà questo codice HTML: +La notazione `{$message}` significa stampare la variabile. In altri framework è necessario trattare esplicitamente ogni stampa e persino in modo diverso in ogni punto. In Nette Framework non è necessario trattare nulla, tutto viene fatto automaticamente, correttamente e coerentemente. Se assegniamo alla variabile `$message = 'Larghezza 1/2"'`, il framework genererà il codice HTML: ```latte -<p onclick="alert("Width 1\/2\"")">Width 1/2"</p> +<p onclick="alert("Larghezza 1\/2\"")">Larghezza 1/2"</p> <script> -document.title = "Width 1\/2\""; +document.title = "Larghezza 1\/2\""; </script> ``` -Falsificazione della richiesta cross-site (CSRF) .[#toc-cross-site-request-forgery-csrf] -======================================================================================== +Cross-Site Request Forgery (CSRF) +================================= -Un attacco Cross-Site Request Forgery consiste nel fatto che l'aggressore attira la vittima a visitare una pagina che esegue silenziosamente una richiesta nel browser della vittima al server in cui la vittima è attualmente loggata, e il server crede che la richiesta sia stata fatta dalla vittima a suo piacimento. Il server esegue una determinata azione sotto l'identità della vittima, ma senza che questa se ne renda conto. Può trattarsi della modifica o della cancellazione di dati, dell'invio di un messaggio, ecc. +L'attacco Cross-Site Request Forgery consiste nel fatto che l'attaccante attira la vittima su una pagina che esegue discretamente nel browser della vittima una richiesta al server su cui la vittima è loggata, e il server crede che la richiesta sia stata eseguita dalla vittima di sua spontanea volontà. E così, sotto l'identità della vittima, esegue una determinata azione senza che questa ne sia a conoscenza. Può trattarsi di modifica o cancellazione di dati, invio di un messaggio, ecc. -Nette Framework **protegge automaticamente i moduli e i segnali nei presenter** da questo tipo di attacco. Ciò avviene impedendo che vengano inviati o richiamati da un altro dominio. Per disattivare la protezione, utilizzare quanto segue per i moduli: +Nette Framework **protegge automaticamente i form e i segnali nei presenter** da questo tipo di attacco. E lo fa impedendo il loro invio o attivazione da un altro dominio. Se volete disattivare la protezione, usate per i form: ```php $form->allowCrossOrigin(); ``` -o nel caso di un segnale, aggiungere un'annotazione `@crossOrigin`: +o nel caso di un segnale aggiungete l'annotazione `@crossOrigin`: ```php /** @@ -59,44 +59,41 @@ public function handleXyz() } ``` -In PHP 8, è possibile utilizzare anche gli attributi: +In Nette Application 3.2 potete usare anche gli attributi: ```php -use Nette\Application\Attributes\CrossOrigin; +use Nette\Application\Attributes\Requires; -#[CrossOrigin] +#[Requires(sameOrigin: false)] public function handleXyz() { } ``` -Attacco URL, Codici di controllo, UTF-8 non validi .[#toc-url-attack-control-codes-invalid-utf-8] -================================================================================================= +Attacco URL, codici di controllo, UTF-8 non valido +================================================== -Termini diversi, tutti relativi al tentativo dell'attaccante di fornire all'applicazione un input "dannoso". I risultati possono essere molto diversi, dall'output XML interrotto (ad esempio, un flusso RSS non funzionante) all'ottenimento di informazioni sensibili da un database, fino all'ottenimento delle password degli utenti. La protezione contro questi attacchi consiste in un controllo UTF-8 coerente a livello di byte. E francamente, non lo fareste senza un framework, giusto? +Vari termini legati al tentativo dell'attaccante di inviare alla vostra applicazione web input *dannosi*. Le conseguenze possono essere molto diverse, dal danneggiamento degli output XML (ad es. feed RSS non funzionanti) all'ottenimento di informazioni sensibili dal database o password. La difesa consiste nel trattare coerentemente tutti gli input a livello dei singoli byte. E mano sul cuore, chi di voi lo fa? -Nette Framework lo fa per voi, automaticamente. Non è necessario configurare nulla e la vostra applicazione sarà al sicuro. +Nette Framework lo fa per voi e inoltre automaticamente. Non dovete configurare assolutamente nulla e tutti gli input saranno trattati. -Hijacking di sessione, furto di sessione, fissazione di sessione .[#toc-session-hijacking-session-stealing-session-fixation] -============================================================================================================================ +Session hijacking, session stealing, session fixation +===================================================== -La gestione delle sessioni comporta alcuni tipi di attacchi. L'aggressore può rubare l'ID di sessione della vittima o falsificarne uno, ottenendo così l'accesso a un'applicazione Web senza la password effettiva. A quel punto l'aggressore può fare tutto ciò che l'utente ha potuto fare, senza lasciare traccia. La protezione sta nella corretta configurazione di PHP e del server web stesso. +Alla gestione delle sessioni sono associati diversi tipi di attacchi. L'attaccante ruba o invia all'utente il proprio ID di sessione e grazie a ciò ottiene l'accesso all'applicazione web senza conoscere la password dell'utente. Successivamente può fare qualsiasi cosa nell'applicazione senza che l'utente lo sappia. La difesa consiste nella corretta configurazione del server e di PHP. -Nette Framework configura PHP automaticamente. Gli sviluppatori non devono quindi preoccuparsi di come rendere una sessione sufficientemente protetta e possono concentrarsi completamente sulle parti chiave dell'applicazione. Ciò richiede l'abilitazione della funzione `ini_set()`. +Nette Framework configura PHP automaticamente. Il programmatore non deve quindi pensare a come proteggere correttamente la sessione e può concentrarsi completamente sulla creazione dell'applicazione. Richiede tuttavia la funzione `ini_set()` abilitata. -Cookie SameSite .[#toc-samesite-cookie] -======================================= +Cookie SameSite +=============== -I cookie SameSite forniscono un meccanismo per riconoscere la causa del caricamento di una pagina. Il che è assolutamente fondamentale per ragioni di sicurezza. +I cookie SameSite forniscono un meccanismo per riconoscere cosa ha portato al caricamento della pagina. Il che è assolutamente fondamentale per la sicurezza. -Il flag SameSite può avere tre valori: `Lax`, `Strict` e `None` (richiede HTTPS). Se la richiesta di una pagina proviene direttamente dal web o l'utente apre la pagina inserendola direttamente nella barra degli indirizzi o cliccando su un segnalibro, -il browser invia tutti i cookie al server (cioè con i flag `Lax`, `Strict` e `None`). Se un utente giunge al sito web tramite un link proveniente da un altro sito web, i cookie con i flag `Lax` e `None` saranno trasmessi al server. Se la richiesta avviene in un altro -`None` modo, come ad esempio l'invio di un modulo POST da un'altra origine, il caricamento all'interno di un iframe, l'utilizzo di JavaScript, ecc. - -Per impostazione predefinita, Nette invia tutti i cookie con il flag `Lax`. +Il flag SameSite può avere tre valori: `Lax`, `Strict` e `None` (quest'ultimo richiede HTTPS). Se la richiesta per la pagina proviene direttamente dal sito o l'utente apre la pagina inserendola direttamente nella barra degli indirizzi o cliccando su un segnalibro, il browser invia al server tutti i cookie (cioè con flag `Lax`, `Strict` e `None`). Se l'utente arriva al sito cliccando su un link da un altro sito, vengono trasmessi al server i cookie con flag `Lax` e `None`. Se la richiesta viene generata in altro modo, come l'invio di un form POST da un altro sito, il caricamento all'interno di un iframe, tramite JavaScript, ecc., vengono inviati solo i cookie con flag `None`. +Nette invia di default tutti i cookie con il flag `Lax`. {{leftbar: www:@menu-common}} diff --git a/nette/ja/@home.texy b/nette/ja/@home.texy new file mode 100644 index 0000000000..dd2a680ec0 --- /dev/null +++ b/nette/ja/@home.texy @@ -0,0 +1,102 @@ +Netteドキュメント +*********** + +<div class=documentation> + +<div> + + +はじめに +---- +- [なぜNetteを使うのか? |www:10-reasons-why-nette] +- [インストール |nette:installation] +- [最初のアプリケーションを作成しましょう! |quickstart:] + + +一般 +------ +- [パッケージリスト |www:packages] +- [メンテナンスとPHPバージョン |www:maintenance] +- [リリースノート |https://nette.org/releases] +- [新しいバージョンへの移行|migrations:en] +- [トラブルシューティング |nette:troubleshooting] +- [Netteを作成しているのは誰か |https://nette.org/contributors] +- [Netteの歴史 |www:history] +- [貢献する |contributing:] +- [開発を支援する |https://nette.org/cs/donate] +- [APIリファレンス |https://api.nette.org/] +</div> + + +<div> + + +Nette のアプリケーション +--------------- +- [アプリケーションはどのように動作しますか? |application:how-it-works] +- [Bootstrapping |application:Bootstrapping] +- [Presenter |application:presenters] +- [テンプレート |application:templates] +- [ディレクトリ構造 |application:directory-structure] +- [ルーティング |application:routing] +- [URLリンクの作成 |application:creating-links] +- [インタラクティブコンポーネント |application:components] +- [AJAX とスニペット |application:ajax] + + +- [ガイドとベストプラクティス |best-practices:] +</div> + + +<div> + + +主要なトピック +------- +- [設定 |nette:configuring] +- [依存関係注入|dependency-injection:] +- [Latte: テンプレート |latte:] +- [Tracy: コードデバッグ |tracy:] +- [フォーム |forms:] +- [データベース |database:guide] +- [ユーザーログイン |security:authentication] +- [権限の検証 |security:authorization] +- [セッション |http:Sessions] +- [HTTP リクエストとレスポンス|http:] +- [資産 |assets:] +- [キャッシュ |caching:] +- [メールの送信 |mail:] +- [Schema: データ検証 |schema:] +- [PHP コードジェネレータ |php-generator:] +- [Tester: テスト |tester:] +</div> + + +<div> + + +ユーティリティ +------- +- [配列 |utils:arrays] +- [ファイルシステム |utils:filesystem] +- [Finder |utils:finder] +- [HTML 要素 |utils:html-elements] +- [画像 |utils:images] +- [JSON |utils:JSON] +- [NEON|neon:] +- [パスワードハッシュ化 |security:passwords] +- [PHP 型 |utils:type] +- [文字列 |utils:strings] +- [バリデータ |utils:validators] +- [RobotLoader |robot-loader:] +- [SmartObject |utils:smartobject] & [StaticClass |utils:StaticClass] +- [SafeStream |safe-stream:] +- [...その他 |utils:] +</div> + +</div> + + +{{toc:no}} +{{description: Nette公式ドキュメント:Netteの仕組みとWebアプリケーション開発のベストプラクティスを説明します。}} +{{maintitle: Netteドキュメント}} diff --git a/nette/ja/@menu-topics.texy b/nette/ja/@menu-topics.texy new file mode 100644 index 0000000000..5b69d76bc5 --- /dev/null +++ b/nette/ja/@menu-topics.texy @@ -0,0 +1,21 @@ +主要なトピック +******* +- [設定 |nette:configuring] +- [Nette のアプリケーション |application:how-it-works] +- [依存関係注入|dependency-injection:] +- [ユーティリティ |utils:] +- [フォーム |forms:] +- [データベース |database:guide] +- [ユーザーログイン |security:authentication] +- [権限の検証 |security:authorization] +- [セッション |http:Sessions] +- [HTTP リクエストとレスポンス|http:] +- [キャッシュ |caching:] +- [メールの送信 |mail:] +- [Schema: データ検証 |schema:] +- [PHP コードジェネレータ |php-generator:] + + +- [Latte: テンプレート |latte:] +- [Tracy: コードデバッグ |tracy:] +- [Tester: テスト |tester:] diff --git a/nette/ja/@meta.texy b/nette/ja/@meta.texy new file mode 100644 index 0000000000..d3c41dc3d7 --- /dev/null +++ b/nette/ja/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette ドキュメンテーション}} diff --git a/nette/ja/configuring.texy b/nette/ja/configuring.texy new file mode 100644 index 0000000000..842d250bd6 --- /dev/null +++ b/nette/ja/configuring.texy @@ -0,0 +1,36 @@ +Netteの設定 +******** + +.[perex] +Nette Frameworkのすべての設定オプションの概要。 + +Netteコンポーネントは、通常[NEON形式 |neon:format]で記述される設定ファイルを使用して設定します。これらを編集する最良の方法は、[サポートされているエディタ |best-practices:editors-and-tools#IDEエディタ]を使用することです。 フレームワーク全体を使用している場合、設定は[アプリケーションのブートストラップ時にロードされます |application:bootstrapping#DIコンテナの設定]。そうでない場合は、[設定をロードする方法 |bootstrap:]をお読みください。 + +<pre> +"application .[prism-token prism-atrule]":[application:configuration#Application]: "アプリケーション .[prism-token prism-comment]"<br> +"assets .[prism-token prism-atrule]":[assets:configuration]: "Assets .[prism-token prism-comment]"<br> +"constants .[prism-token prism-atrule]":[application:configuration#定数]: "PHP定数の定義 .[prism-token prism-comment]"<br> +"database .[prism-token prism-atrule]":[database:configuration]: "データベース .[prism-token prism-comment]"<br> +"decorator .[prism-token prism-atrule]":[dependency-injection:configuration#Decorator]: "デコレータ .[prism-token prism-comment]"<br> +"di .[prism-token prism-atrule]":[dependency-injection:configuration#DI]: "DIコンテナ .[prism-token prism-comment]"<br> +"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#拡張機能]: "追加のDI拡張機能のインストール .[prism-token prism-comment]"<br> +"forms .[prism-token prism-atrule]":[forms:configuration]: "フォーム .[prism-token prism-comment]"<br> +"http .[prism-token prism-atrule]":[http:configuration#HTTP ヘッダー]: "HTTPヘッダー .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[dependency-injection:configuration#ファイルのインクルード]: "ファイルのインクルード .[prism-token prism-comment]"<br> +"latte .[prism-token prism-atrule]":[application:configuration#Latte テンプレート]: "Latteテンプレート .[prism-token prism-comment]"<br> +"mail .[prism-token prism-atrule]":[mail:#設定]: "メール .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#パラメータ]: "パラメータ .[prism-token prism-comment]"<br> +"php .[prism-token prism-atrule]":[application:configuration#PHP]: "PHP設定 .[prism-token prism-comment]"<br> +"routing .[prism-token prism-atrule]":[application:configuration#ルーティング]: "ルーティング .[prism-token prism-comment]"<br> +"search .[prism-token prism-atrule]":[dependency-injection:configuration#Search]: "サービスの自動登録 .[prism-token prism-comment]"<br> +"security .[prism-token prism-atrule]":[security:configuration]: "アクセス権限 .[prism-token prism-comment]"<br> +"services .[prism-token prism-atrule]":[dependency-injection:services]: "サービス .[prism-token prism-comment]"<br> +"session .[prism-token prism-atrule]":[http:configuration#セッション]: "セッション .[prism-token prism-comment]"<br> +"tracy .[prism-token prism-atrule]":[tracy:configuring#Nette Framework]: "Tracyデバッガ .[prism-token prism-comment]" +</pre> + +.[note] +`%` 文字を含む文字列を記述するには、`%%` に二重化してエスケープする必要があります。 + + +{{leftbar: @menu-topics}} diff --git a/nette/ja/glossary.texy b/nette/ja/glossary.texy new file mode 100644 index 0000000000..17eb925948 --- /dev/null +++ b/nette/ja/glossary.texy @@ -0,0 +1,161 @@ +用語集 +*** + + +AJAX +---- +Asynchronous JavaScript and XML - クライアントとサーバー間でHTTPプロトコルを介して情報を交換する技術で、各リクエストごとにページ全体を再読み込みする必要がありません。名前からはXML形式でのみデータを送信するように思えるかもしれませんが、一般的には[#JSON]形式も使用されます。 + + +Presenterのアクション +--------------- +Presenterの論理的な部分で、1つのアクションを実行します。例えば、製品ページの表示、ユーザーのログアウトなどです。1つのPresenterは複数のアクションを持つことができます。 + + +BOM +--- +いわゆる*byte order mark*は、ファイルの先頭にある特別な文字で、エンコーディングにおけるバイトオーダーのインジケータとして使用されます。一部のエディタはこの文字をファイルに挿入します。実際には見えませんが、PHPからの出力やヘッダーの送信に問題を引き起こします。一括で削除するには、[Code Checker|code-checker:]を使用できます。 + + +Controller +---------- +ユーザーのリクエストを処理し、それに基づいて適切なアプリケーションロジック(つまり[#モデル])を呼び出し、その後[#ビュー]にデータの描画を依頼するコントローラー。Nette Frameworkにおけるコントローラーに相当するものは[#Presenter]です。 + + +Cross-Site Scripting (XSS) +-------------------------- +Cross-Site Scriptingは、エスケープされていない出力を悪用してウェブサイトを侵害する手法です。攻撃者はページに独自のコードを挿入し、それによってページを変更したり、訪問者の機密情報を取得したりすることができます。XSSに対する防御は、すべての文字列を徹底的かつ正確にエスケープすることによってのみ可能です。 + +Nette Frameworkは、画期的な技術である[コンテキスト対応エスケープ |latte:safety-first#コンテキストに応じたエスケープ]を提供しており、これによりCross-Site Scriptingのリスクから永久に解放されます。すべての出力は自動的にエスケープされるため、コーダーが何かを忘れることはありえません。 + + +Cross-Site Request Forgery (CSRF) +--------------------------------- +Cross-Site Request Forgery攻撃は、攻撃者が被害者をあるページに誘い込み、そのページが被害者のブラウザで、被害者がログインしているサーバーに対して密かにリクエストを実行するというものです。サーバーは、そのリクエストが被害者自身の意思によって実行されたものと誤認します。そして、被害者のアイデンティティの下で、被害者が知らないうちに特定の操作(データの変更や削除、メッセージの送信など)を実行します。 + +Nette Frameworkは、**Presenter内のフォームとシグナルを自動的に**この種の攻撃から保護します。これは、異なるドメインからの送信や呼び出しを防ぐことによって行われます。 + + +Dependency Injection +-------------------- +Dependency Injection(DI)は、オブジェクトの作成とその依存関係の分離方法を示すデザインパターンです。つまり、クラスは自身の依存関係の作成や初期化に責任を負わず、代わりにこれらの依存関係は外部のコード([DIコンテナ |#Dependency Injectionコンテナ]など)によって提供されます。利点は、依存関係が容易に置換可能で、コードの他の部分から隔離されているため、コードの柔軟性が向上し、理解しやすくなり、アプリケーションのテストが容易になることです。詳細は[Dependency Injectionとは? |dependency-injection:introduction]の章を参照してください。 + + +Dependency Injectionコンテナ +------------------------ +Dependency Injectionコンテナ(DIコンテナまたはIoCコンテナとも呼ばれる)は、アプリケーション内の依存関係(つまり[#サービス])の作成と管理を担当するツールです。コンテナは通常、どのクラスが他のクラスに依存しているか、どの具体的な依存関係の実装を使用するか、そしてこれらの依存関係をどのように作成するかを定義する設定を持ちます。その後、コンテナはこれらのオブジェクトを作成し、それらを必要とするクラスに提供します。詳細は[DIコンテナとは? |dependency-injection:container]の章を参照してください。 + + +エスケープ +----- +エスケープとは、特定のコンテキストで特別な意味を持つ文字を、対応する別のシーケンスに変換することです。例:引用符で囲まれた文字列内に引用符を書き込みたい場合。引用符は文字列のコンテキストで特別な意味を持ち、そのまま記述すると文字列の終了と解釈されるため、別の対応するシーケンスで記述する必要があります。どのシーケンスを使用するかは、コンテキストのルールによって決まります。 + + +フィルタ (以前はヘルパー) +-------------- +テンプレートでは、[フィルタ |latte:syntax#フィルタ]という用語は通常、データを最終的な形式に調整または再フォーマットするのに役立つ関数を指します。テンプレートにはいくつかの[標準フィルタ |latte:filters]があります。 + + +無効化 +--- +[#スニペット]に再描画を通知すること。別の意味では、キャッシュの内容を削除することでもあります。 + + +JSON +---- +JavaScriptの構文(そのサブセット)に基づいたデータ交換フォーマット。正確な仕様はwww.json.orgで確認できます。 + + +コンポーネント +------- +アプリケーションの再利用可能な部分。[コンポーネントの作成 |application:components]の章で説明されているように、ページの視覚的な部分である場合もあれば、コンポーネントという用語はクラス[Component |component-model:]を指す場合もあります(そのようなコンポーネントは視覚的である必要はありません)。 + + +制御文字 +---- +制御文字は、テキスト中に存在する可能性があり、問題を引き起こす可能性もある目に見えない文字です。ファイルから一括で削除するには[Code Checker|code-checker:]を使用でき、変数から削除するには[Strings::normalize() |utils:strings#normalize]関数を使用できます。 + + +イベント +---- +イベントとは、オブジェクト内で予期される状況であり、それが起こると、いわゆるハンドラ、つまりイベントに反応するコールバックが呼び出されます([デモ](https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38))。イベントには、フォームの送信、ユーザーのログインなどがあります。イベントは*Inversion of Control*の一形態です。 + +例えば、ユーザーのログインは`Nette\Security\User::login()`メソッドで行われます。`User`オブジェクトにはパブリック変数`$onLoggedIn`があり、これは誰でもコールバックを追加できる配列です。ユーザーがログインすると、`login()`メソッドはこの配列内のすべてのコールバックを呼び出します。`onXyz`形式の変数名は、Nette全体で使用される規約です。 + + +Latte +----- +最も先進的な[テンプレートエンジン |latte:]の1つ。 + + +モデル +--- +モデルは、アプリケーション全体のデータと、特に機能的な基盤です。すべてのアプリケーションロジック(ビジネスロジックという用語も使われます)が含まれています。これは**M**VCまたはMVPの**M**です。ユーザーのあらゆるアクション(ログイン、商品をカートに入れる、データベースの値を変更する)は、モデルのアクションを表します。 + +モデルは自身の内部状態を管理し、外部には固定されたインターフェースを提供します。このインターフェースの関数を呼び出すことで、その状態を照会したり変更したりできます。モデルは[#ビュー]や[コントローラー |#Controller]の存在を知りません。 + + +Model-View-Controller +--------------------- +グラフィカルインターフェースを持つアプリケーションにおいて、操作コード([コントローラー |#Controller])をアプリケーションロジックコード([#モデル])およびデータ表示コード([#ビュー])から分離する必要性から生まれたソフトウェアアーキテクチャ。これにより、アプリケーションが明確になり、将来の開発が容易になり、個々の部分を別々にテストすることが可能になります。 + + +Model-View-Presenter +-------------------- +[#Model-View-Controller]に基づいたアーキテクチャ。 + + +モジュール +----- +モジュールは、アプリケーションの論理的な部分を表します。典型的な構成では、特定の機能領域を扱うPresenterとテンプレートのグループです。モジュールは、`Front/`、`Admin/`、`Shop/`などの[独立したディレクトリ |application:directory-structure#Presenterとテンプレート]に配置します。 + +例えば、eショップを次のように分割します: +- フロントエンド (`Shop/`):製品の閲覧と購入用 +- 顧客セクション (`Customer/`):注文管理用 +- 管理画面 (`Admin/`):運営者用 + +技術的には、これらは通常のディレクトリですが、明確な構造化によりアプリケーションのスケーリングに役立ちます。Presenter `Admin:Product:List`は、物理的には例えば`app/Presentation/Admin/Product/List/`ディレクトリに配置されます([Presenterのマッピング |application:directory-structure#Presenterのマッピング]を参照)。 + + +Namespace +--------- +名前空間は、PHPバージョン5.3以降および他のいくつかのプログラミング言語の一部であり、異なるライブラリで同じ名前が付けられたクラスを、名前の衝突なしに使用できるようにします。[PHP ドキュメント |https://www.php.net/manual/en/language.namespaces.rationale.php]を参照してください。 + + +Presenter +--------- +Presenterは、ルーターによってHTTPリクエストから変換された[リクエスト |api:Nette\Application\Request]を受け取り、[レスポンス |api:Nette\Application\Response]を生成するオブジェクトです。レスポンスは、HTMLページ、画像、XMLドキュメント、ディスク上のファイル、JSON、リダイレクト、または考えられるあらゆるものにすることができます。 + +通常、Presenterという用語は、クラス[api:Nette\Application\UI\Presenter]の子孫を指します。受信したリクエストに応じて、対応する[アクション |application:presenters#Presenterのライフサイクル]を実行し、テンプレートを描画します。 + + +Router +------ +HTTPリクエスト/URLとPresenterのアクション間の双方向トランスレータ。双方向とは、HTTPリクエストから[#Presenterのアクション]を導き出すことができるだけでなく、逆も同様に、アクションに対応するURLを生成できることを意味します。詳細は[URLルーティング |application:routing]の章を参照してください。 + + +SameSite cookie +--------------- +SameSite cookiesは、ページの読み込みにつながった原因を認識するメカニズムを提供します。`Lax`、`Strict`、`None`(これはHTTPSが必要)の3つの値を持つことができます。ページへのリクエストがウェブサイトから直接来た場合、またはユーザーがアドレスバーに直接入力するかブックマークをクリックしてページを開いた場合、ブラウザはすべてのCookie(つまり、`Lax`、`Strict`、`None`フラグを持つもの)をサーバーに送信します。ユーザーが別のウェブサイトからのリンクをクリックしてウェブサイトにアクセスした場合、`Lax`および`None`フラグを持つCookieがサーバーに送信されます。リクエストが他の方法で発生した場合、例えば別のウェブサイトからのPOSTフォームの送信、iframe内での読み込み、JavaScriptによるものなどでは、`None`フラグを持つCookieのみが送信されます。 + + +サービス +---- +Dependency Injectionの文脈では、サービスとはDIコンテナによって作成および管理されるオブジェクトを指します。サービスは、例えばテスト目的や、サービスを使用するコードを変更することなくアプリケーションの動作を変更するために、別の実装に簡単に置き換えることができます。 + + +スニペット +----- +スニペット、AJAXリクエスト中に個別に再描画できるページの一部。 + + +ビュー +--- +ビューは、リクエストの結果を表示する役割を担うアプリケーションのレイヤーです。通常、テンプレートエンジンを使用し、特定のコンポーネントやモデルから取得した結果をどのように表示するかを知っています。 + + + + + +{{leftbar: www:@menu-common}} +{{priority: -2}} diff --git a/nette/ja/installation.texy b/nette/ja/installation.texy new file mode 100644 index 0000000000..9690dae823 --- /dev/null +++ b/nette/ja/installation.texy @@ -0,0 +1,67 @@ +Netteのインストール +************ + +.[perex] +既存のプロジェクトでNetteの利点を活用したいですか、それともNetteに基づいて新しいプロジェクトを作成する予定ですか?このガイドでは、インストールをステップバイステップで説明します。 + + +プロジェクトにNetteを追加する方法 +------------------- + +Netteは、PHP用の便利で洗練されたパッケージ(ライブラリ)のコレクションを提供しています。これらをプロジェクトに組み込むには、次の手順に従います。 + +1) **[Composer|best-practices:composer]を準備します:** このツールは、プロジェクトに必要なライブラリの簡単なインストール、更新、管理に不可欠です。 + +2) **[パッケージ |www:packages]を選択します:** ファイルシステムを探索する必要があるとしましょう。これは、`nette/utils`パッケージの[Finder|utils:finder]がうまく行います。パッケージ名は、ドキュメントの右側の列に表示されます。 + +3) **パッケージをインストールします:** プロジェクトのルートディレクトリで次のコマンドを実行します: + +```shell +composer require nette/utils +``` + +グラフィカルインターフェースを好みますか?PhpStorm環境でのパッケージのインストールに関する[ガイド |https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html]をご覧ください。 + + +Netteで新しいプロジェクトを開始する方法 +---------------------- + +Netteプラットフォームで全く新しいプロジェクトを作成したい場合は、事前設定されたスケルトン[Web Project|https://github.com/nette/web-project]の使用をお勧めします: + +1) **[Composer|best-practices:composer]を準備します。** + +2) **コマンドラインを開き**、Webサーバーのルートディレクトリ(例:`/etc/var/www`、`C:/xampp/htdocs`、`/Library/WebServer/Documents`)に移動します。 + +3) **プロジェクトを作成します** 次のコマンドを使用します: + +```shell +composer create-project nette/web-project PROJECT_NAME +``` + +4) **Composerを使用していませんか?** [ZIP形式のWeb Project |https://github.com/nette/web-project/archive/preloaded.zip]をダウンロードして解凍するだけです。しかし、信じてください、Composerは価値があります! + +5) **権限の設定:** macOSまたはLinuxシステムでは、ディレクトリに[書き込み権限 |nette:troubleshooting#ディレクトリ権限の設定]を設定します。 + +6) **ブラウザでプロジェクトを開く:** URL `http://localhost/PROJECT_NAME/www/` を入力すると、スケルトンの開始ページが表示されます: + +[* qs-welcome.webp .{url: http://localhost/PROJECT_NAME/www/} *] + +おめでとうございます!これでWebサイトは開発の準備が整いました。ウェルカムテンプレートを削除して、アプリケーションの作成を開始できます。 + +Netteの利点の1つは、プロジェクトが設定なしですぐに機能することです。ただし、問題が発生した場合は、[よくある問題の解決策 |nette:troubleshooting#Netteが動作せず 白いページが表示される]を確認してみてください。 + +.[note] +Netteを始めたばかりの場合は、[チュートリアル「最初のアプリケーションを作成する」 |quickstart:]に進むことをお勧めします。 + + +ツールと推奨事項 +-------- + +Netteで効率的に作業するために、次のツールをお勧めします: + +- [Nette用プラグインを備えた高品質なIDE |best-practices:editors-and-tools] +- バージョン管理システム Git +- [Composer|best-practices:composer] + + +{{leftbar: www:@menu-common}} diff --git a/nette/ja/introduction-to-object-oriented-programming.texy b/nette/ja/introduction-to-object-oriented-programming.texy new file mode 100644 index 0000000000..bec60cc388 --- /dev/null +++ b/nette/ja/introduction-to-object-oriented-programming.texy @@ -0,0 +1,841 @@ +オブジェクト指向プログラミング入門 +***************** + +.[perex] +「OOP」という用語はオブジェクト指向プログラミングを指し、これはコードを整理し構造化する方法です。OOPでは、プログラムを一連のコマンドや関数の代わりに、互いに通信するオブジェクトの集合として見ることができます。 + +OOPにおいて、「オブジェクト」とは、データとそのデータを操作する関数を含む単位です。オブジェクトは「クラス」に基づいて作成され、クラスはオブジェクトの設計図やテンプレートと考えることができます。クラスがあれば、その「インスタンス」を作成できます。これは、そのクラスに基づいて作成された具体的なオブジェクトです。 + +PHPで簡単なクラスを作成する方法を見てみましょう。クラスを定義する際には、キーワード「class」を使用し、その後にクラス名、そしてクラスの関数(「メソッド」と呼ばれる)と変数(「プロパティ」と呼ばれる)を囲む波括弧が続きます: + +```php +class Auto +{ + function honk() // zatrub -> honk + { + echo 'Bip bip!'; + } +} +``` + +この例では、`Auto`という名前のクラスを作成し、`honk`という名前の関数(または「メソッド」)を1つ含んでいます。 + +各クラスは、1つの主要なタスクのみを解決する必要があります。クラスが多くのことを行いすぎる場合は、より小さく、特化したクラスに分割するのが適切かもしれません。 + +コードを整理し、ナビゲートしやすくするために、通常、クラスは別々のファイルに保存します。ファイル名はクラス名に対応する必要があるため、`Auto`クラスの場合、ファイル名は`Auto.php`になります。 + +クラスに名前を付ける際には、「PascalCase」という規則に従うのが良いでしょう。これは、名前の各単語が大文字で始まり、アンダースコアや他の区切り文字がないことを意味します。メソッドとプロパティは「camelCase」規則を使用します。これは、小文字で始まることを意味します。 + +PHPの一部のメソッドには特別な役割があり、`__`(2つのアンダースコア)のプレフィックスでマークされています。最も重要な特殊メソッドの1つは「コンストラクタ」であり、`__construct`としてマークされています。コンストラクタは、クラスの新しいインスタンスを作成するときに自動的に呼び出されるメソッドです。 + +コンストラクタは、オブジェクトの初期状態を設定するためによく使用されます。例えば、人を表すオブジェクトを作成する場合、コンストラクタを使用してその年齢、名前、またはその他のプロパティを設定できます。 + +PHPでコンストラクタを使用する方法を見てみましょう: + +```php +class Person // Osoba -> Person +{ + private $age; // vek -> age + + function __construct($age) // vek -> age + { + $this->age = $age; // vek -> age + } + + function getAge() // kolikJeTiLet -> getAge + { + return $this->age; // vek -> age + } +} + +$person = new Person(25); // osoba -> person, Osoba -> Person +echo $person->getAge(); // 出力: 25 // osoba -> person, kolikJeTiLet -> getAge, Vypíše: 25 -> 出力: 25 +``` + +この例では、`Person`クラスにはプロパティ(変数)`$age`があり、さらにこのプロパティを設定するコンストラクタがあります。メソッド`getAge()`は、人の年齢にアクセスすることを可能にします。 + +疑似変数`$this`は、クラス内でオブジェクトのプロパティやメソッドにアクセスするために使用されます。 + +キーワード`new`は、クラスの新しいインスタンスを作成するために使用されます。上記の例では、年齢25歳の新しい人を作成しました。 + +オブジェクト作成時に指定されない場合、コンストラクタのパラメータにデフォルト値を設定することもできます。例えば: + +```php +class Person // Osoba -> Person +{ + private $age; // vek -> age + + function __construct($age = 20) // vek -> age + { + $this->age = $age; // vek -> age + } + + function getAge() // kolikJeTiLet -> getAge + { + return $this->age; // vek -> age + } +} + +$person = new Person; // 引数を渡さない場合は括弧を省略できます // osoba -> person, Osoba -> Person, pokud nepředáváme žádný argument, lze závorky vynechat -> 引数を渡さない場合は括弧を省略できます +echo $person->getAge(); // 出力: 20 // osoba -> person, kolikJeTiLet -> getAge, Vypíše: 20 -> 出力: 20 +``` + +この例では、`Person`オブジェクトを作成する際に年齢を指定しない場合、デフォルト値20が使用されます。 + +嬉しいことに、プロパティの定義とそのコンストラクタによる初期化は、このように短縮および簡略化できます: + +```php +class Person // Osoba -> Person +{ + function __construct( + private $age = 20, // vek -> age + ) { + } +} +``` + +完全を期すために、コンストラクタに加えて、オブジェクトにはデストラクタ(メソッド `__destruct`)もあり、これはオブジェクトがメモリから解放される前に呼び出されます。 + + +名前空間 +---- + +名前空間(英語では「namespaces」)を使用すると、関連するクラス、関数、定数を整理してグループ化し、同時に名前の衝突を回避できます。これらはコンピュータのフォルダのようなものと考えることができ、各フォルダには特定のプロジェクトやテーマに属するファイルが含まれています。 + +名前空間は、大規模なプロジェクトや、クラス名の衝突が発生する可能性のあるサードパーティのライブラリを使用する場合に特に役立ちます。 + +プロジェクトに`Auto`という名前のクラスがあり、それを`Transport`という名前の名前空間に配置したいと想像してください。(Doprava -> Transport) 次のようにします: + +```php +namespace Transport; // Doprava -> Transport + +class Auto +{ + function honk() // zatrub -> honk + { + echo 'Bip bip!'; + } +} +``` + +別のファイルで`Auto`クラスを使用したい場合は、クラスがどの名前空間から来ているかを指定する必要があります: + +```php +$auto = new Transport\Auto; // Doprava -> Transport +``` + +簡略化のために、ファイルの先頭で使用したい特定の名前空間のクラスを指定できます。これにより、完全なパスを指定せずにインスタンスを作成できます: + +```php +use Transport\Auto; // Doprava -> Transport + +$auto = new Auto; +``` + + +継承 +--------- + +継承はオブジェクト指向プログラミングのツールであり、既存のクラスに基づいて新しいクラスを作成し、そのプロパティやメソッドを引き継ぎ、必要に応じて拡張または再定義することができます。継承により、コードの再利用性とクラス階層が保証されます。 + +簡単に言えば、1つのクラスがあり、それから派生した別のクラスをいくつかの変更を加えて作成したい場合、元のクラスから新しいクラスを「継承」できます。 + +PHPでは、キーワード`extends`を使用して継承を実現します。 + +私たちの`Person`クラスは年齢に関する情報を保持しています。`Person`を拡張し、研究分野に関する情報を追加する別のクラス`Student`を持つことができます。 + +例を見てみましょう: + +```php +class Person // Osoba -> Person +{ + private $age; // vek -> age + + function __construct($age) // vek -> age + { + $this->age = $age; // vek -> age + } + + function displayInfo() // vypisInformace -> displayInfo + { + echo "年齢: {$this->age} 歳\n"; // Věk: {$this->vek} let\n -> 年齢: {$this->age} 歳\n + } +} + +class Student extends Person // Osoba -> Person +{ + private $major; // obor -> major + + function __construct($age, $major) // vek -> age, obor -> major + { + parent::__construct($age); // vek -> age + $this->major = $major; // obor -> major + } + + function displayInfo() // vypisInformace -> displayInfo + { + parent::displayInfo(); // vypisInformace -> displayInfo + echo "専攻: {$this->major} \n"; // Obor studia: {$this->obor} \n -> 専攻: {$this->major} \n + } +} + +$student = new Student(20, '情報学'); // Informatika -> 情報学 +$student->displayInfo(); // vypisInformace -> displayInfo +``` + +このコードはどのように機能しますか? + +- キーワード`extends`を使用して`Person`クラスを拡張しました。これは、`Student`クラスが`Person`からすべてのメソッドとプロパティを継承することを意味します。 + +- キーワード`parent::`を使用すると、親クラスのメソッドを呼び出すことができます。この場合、`Student`クラスに独自の機能を追加する前に、`Person`クラスからコンストラクタを呼び出しました。そして同様に、学生に関する情報を表示する前に、祖先のメソッド`displayInfo()`を呼び出しました。 + +継承は、クラス間に「is-a」関係が存在する状況を対象としています。例えば、`Student`は`Person`です。猫は動物です。これにより、コードで1つのオブジェクト(例:「Person」)を期待する場合に、代わりに継承されたオブジェクト(例:「Student」)を使用する可能性が得られます。 + +継承の主な目的はコードの重複を防ぐこと**ではない**ことを認識することが重要です。逆に、継承の不適切な使用は、複雑で保守が困難なコードにつながる可能性があります。クラス間に「is-a」関係が存在しない場合は、継承の代わりにコンポジションを検討する必要があります。 + +`Person`クラスと`Student`クラスの`displayInfo()`メソッドが少し異なる情報を表示することに注意してください。そして、このメソッドの他の実装を提供する追加のクラス(例えば`Employee`)を追加できます。(Zamestnanec -> Employee) 異なるクラスのオブジェクトが同じメソッドに異なる方法で応答する能力は、ポリモーフィズムと呼ばれます: + +```php +$people = [ // osoby -> people + new Person(30), // Osoba -> Person + new Student(20, '情報学'), // Informatika -> 情報学 + new Employee(45, 'ディレクター'), // Zamestnanec -> Employee, Ředitel -> ディレクター +]; + +foreach ($people as $person) { // osoby -> people, osoba -> person + $person->displayInfo(); // osoba -> person, vypisInformace -> displayInfo +} +``` + + +コンポジション +------- + +コンポジションは、他のクラスのプロパティやメソッドを継承する代わりに、単にそのインスタンスを自分のクラス内で利用するテクニックです。これにより、複雑な継承構造を作成することなく、複数のクラスの機能とプロパティを組み合わせることができます。 + +例を見てみましょう。`Engine`クラスと`Car`クラスがあります。(Motor -> Engine, Auto -> Car) 「車はエンジンである」と言う代わりに、「車はエンジンを持つ」と言います。これは典型的なコンポジションの関係です。 + +```php +class Engine // Motor -> Engine +{ + function start() // zapni -> start + { + echo 'エンジンが作動しています。'; // Motor běží. -> エンジンが作動しています。 + } +} + +class Car // Auto -> Auto +{ + private $engine; // motor -> engine + + function __construct() + { + $this->engine = new Engine; // motor -> engine, Motor -> Engine + } + + function start() + { + $this->engine->start(); // motor -> engine, zapni -> start + echo '車は走行準備ができました!'; // Auto je připraveno k jízdě! -> 車は走行準備ができました! + } +} + +$car = new Car; // auto -> car, Auto -> Car +$car->start(); // auto -> car +``` + +ここでは、`Car`は`Engine`のすべてのプロパティとメソッドを持っているわけではありませんが、プロパティ`$engine`を通じてそれにアクセスできます。 + +コンポジションの利点は、設計の柔軟性が高く、将来の変更の可能性が向上することです。 + + +可視性 +--- + +PHPでは、クラスのプロパティ、メソッド、定数に対して「可視性」を定義できます。可視性は、これらの要素にどこからアクセスできるかを決定します。 + +1. **Public:** 要素が`public`としてマークされている場合、クラス外からでもどこからでもアクセスできることを意味します。 + +2. **Protected:** `protected`とマークされた要素は、そのクラス内およびそのすべての子孫(このクラスから継承するクラス)からのみアクセス可能です。 + +3. **Private:** 要素が`private`の場合、それが定義されたクラス内からのみアクセスできます。 + +可視性を指定しない場合、PHPは自動的に`public`に設定します。 + +サンプルコードを見てみましょう: + +```php +class VisibilityDemo // UkazkaViditelnosti -> VisibilityDemo +{ + public $publicProperty = 'Public'; // verejnaVlastnost -> publicProperty, Veřejná -> Public + protected $protectedProperty = 'Protected'; // chranenaVlastnost -> protectedProperty, Chráněná -> Protected + private $privateProperty = 'Private'; // soukromaVlastnost -> privateProperty, Soukromá -> Private + + public function displayProperties() // vypisVlastnosti -> displayProperties + { + echo $this->publicProperty; // 動作します // verejnaVlastnost -> publicProperty, Funguje -> 動作します + echo $this->protectedProperty; // 動作します // chranenaVlastnost -> protectedProperty, Funguje -> 動作します + echo $this->privateProperty; // 動作します // soukromaVlastnost -> privateProperty, Funguje -> 動作します + } +} + +$object = new VisibilityDemo; // objekt -> object, UkazkaViditelnosti -> VisibilityDemo +$object->displayProperties(); // vypisVlastnosti -> displayProperties +echo $object->publicProperty; // 動作します // verejnaVlastnost -> publicProperty, Funguje -> 動作します +// echo $object->protectedProperty; // エラーをスローします // chranenaVlastnost -> protectedProperty, Vyhodí chybu -> エラーをスローします +// echo $object->privateProperty; // エラーをスローします // soukromaVlastnost -> privateProperty, Vyhodí chybu -> エラーをスローします +``` + +クラスの継承を続けます: + +```php +class ChildClass extends VisibilityDemo // PotomekTridy -> ChildClass, UkazkaViditelnosti -> VisibilityDemo +{ + public function displayProperties() // vypisVlastnosti -> displayProperties + { + echo $this->publicProperty; // 動作します // verejnaVlastnost -> publicProperty, Funguje -> 動作します + echo $this->protectedProperty; // 動作します // chranenaVlastnost -> protectedProperty, Funguje -> 動作します + // echo $this->privateProperty; // エラーをスローします // soukromaVlastnost -> privateProperty, Vyhodí chybu -> エラーをスローします + } +} +``` + +この場合、`ChildClass`クラスの`displayProperties()`メソッドは、パブリックおよびプロテクテッドなプロパティにアクセスできますが、親クラスのプライベートなプロパティにはアクセスできません。 + +データとメソッドは可能な限り隠蔽し、定義されたインターフェースを通じてのみアクセス可能であるべきです。これにより、コードの残りの部分に影響を与えることなく、クラスの内部実装を変更できます。 + + +`final`キーワード +------------ + +PHPでは、クラス、メソッド、または定数が継承またはオーバーライドされるのを防ぎたい場合に、`final`キーワードを使用できます。クラスを`final`としてマークすると、拡張できません。メソッドを`final`としてマークすると、子クラスでオーバーライドできません。 + +特定のクラスやメソッドがさらに変更されないことを知っていると、潜在的な競合を心配することなく、変更をより簡単に行うことができます。例えば、どの子孫もすでに同じ名前のメソッドを持っていて衝突が発生するという心配なしに、新しいメソッドを追加できます。または、メソッドのパラメータを変更することもできます。なぜなら、子孫でオーバーライドされたメソッドとの不整合を引き起こすリスクがないからです。 + +```php +final class FinalClass // FinalniTrida -> FinalClass +{ +} + +// 次のコードはエラーをスローします。finalクラスからは継承できないためです。 // Následující kód vyvolá chybu, protože nemůžeme zdědit od finalní třídy. -> 次のコードはエラーをスローします。finalクラスからは継承できないためです。 +class ChildOfFinalClass extends FinalClass // PotomekFinalniTridy -> ChildOfFinalClass, FinalniTrida -> FinalClass +{ +} +``` + +この例では、finalクラス`FinalClass`からの継承の試みはエラーをスローします。 + + +静的プロパティとメソッド +------------ + +PHPでクラスの「静的」要素について話すとき、それは特定のクラスインスタンスではなく、クラス自体に属するメソッドとプロパティを意味します。これは、それらにアクセスするためにクラスのインスタンスを作成する必要がないことを意味します。代わりに、クラス名を介して直接呼び出したりアクセスしたりします。 + +静的要素はクラスに属し、そのインスタンスには属さないため、静的メソッド内で疑似変数`$this`を使用することはできないことに注意してください。 + +静的プロパティの使用は[落とし穴だらけの不明瞭なコード |dependency-injection:global-state]につながるため、決して使用すべきではなく、ここでは使用例も示しません。対照的に、静的メソッドは便利です。使用例: + +```php +class Calculator // Kalkulator -> Calculator +{ + public static function add($a, $b) // scitani -> add + { + return $a + $b; + } + + public static function subtract($a, $b) // odecitani -> subtract + { + return $a - $b; + } +} + +// クラスインスタンスを作成せずに静的メソッドを使用 // Použití statické metody bez vytvoření instance třídy -> クラスインスタンスを作成せずに静的メソッドを使用 +echo Calculator::add(5, 3); // 結果: 8 // Kalkulator -> Calculator, scitani -> add, Výsledek: 8 -> 結果: 8 +echo Calculator::subtract(5, 3); // 結果: 2 // Kalkulator -> Calculator, odecitani -> subtract, Výsledek: 2 -> 結果: 2 +``` + +この例では、2つの静的メソッドを持つ`Calculator`クラスを作成しました。これらのメソッドは、`::`演算子を使用してクラスのインスタンスを作成せずに直接呼び出すことができます。静的メソッドは、特定のクラスインスタンスの状態に依存しない操作に特に役立ちます。 + + +クラス定数 +----- + +クラス内で定数を定義するオプションがあります。定数は、プログラムの実行中に決して変更されない値です。変数とは異なり、定数の値は常に同じままです。 + +```php +class Car // Auto -> Car +{ + public const NumberOfWheels = 4; // PocetKol -> NumberOfWheels + + public function displayNumberOfWheels(): int // zobrazPocetKol -> displayNumberOfWheels + { + echo self::NumberOfWheels; // PocetKol -> NumberOfWheels + } +} + +echo Car::NumberOfWheels; // 出力: 4 // Auto -> Car, PocetKol -> NumberOfWheels, Výstup: 4 -> 出力: 4 +``` + +この例では、定数`NumberOfWheels`を持つ`Car`クラスがあります。クラス内で定数にアクセスしたい場合は、クラス名の代わりにキーワード`self`を使用できます。 + + +オブジェクトインターフェース +-------------- + +オブジェクトインターフェースは、クラスの「契約」として機能します。クラスがオブジェクトインターフェースを実装する場合、そのインターフェースが定義するすべてのメソッドを含まなければなりません。これは、特定のクラスが同じ「契約」または構造に従うことを保証するための優れた方法です。 + +PHPでは、インターフェースはキーワード`interface`で定義されます。インターフェースで定義されたすべてのメソッドはパブリック(`public`)です。クラスがインターフェースを実装する場合、キーワード`implements`を使用します。 + +```php +interface Animal // Zvire -> Animal +{ + function makeSound(); // vydejZvuk -> makeSound +} + +class Cat implements Animal // Kocka -> Cat, Zvire -> Animal +{ + public function makeSound() // vydejZvuk -> makeSound + { + echo 'ニャー'; // Mňau -> ニャー + } +} + +$cat = new Cat; // kocka -> cat, Kocka -> Cat +$cat->makeSound(); // kocka -> cat, vydejZvuk -> makeSound +``` + +クラスがインターフェースを実装しても、期待されるすべてのメソッドが定義されていない場合、PHPはエラーをスローします。 + +クラスは一度に複数のインターフェースを実装できます。これは、クラスが1つのクラスからしか継承できない継承とは異なります: + +```php +interface Guard // Hlidac -> Guard +{ + function guardHouse(); // hlidejDum -> guardHouse +} + +class Dog implements Animal, Guard // Pes -> Dog, Zvire -> Animal, Hlidac -> Guard +{ + public function makeSound() // vydejZvuk -> makeSound + { + echo 'ワン'; // Haf -> ワン + } + + public function guardHouse() // hlidejDum -> guardHouse + { + echo '犬は家を注意深く見守っています'; // Pes bedlivě střeží dům -> 犬は家を注意深く見守っています + } +} +``` + + +抽象クラス +----- + +抽象クラスは他のクラスの基本テンプレートとして機能しますが、直接インスタンスを作成することはできません。これらは、完全なメソッドと、内容が定義されていない抽象メソッドの組み合わせを含んでいます。抽象クラスから継承するクラスは、祖先のすべての抽象メソッドの定義を提供する必要があります。 + +抽象クラスを定義するには、キーワード`abstract`を使用します。 + +```php +abstract class AbstractClass // AbstraktniTrida -> AbstractClass +{ + public function regularMethod() // obycejnaMetoda -> regularMethod + { + echo 'これは通常のメソッドです'; // Toto je obyčejná metoda -> これは通常のメソッドです + } + + abstract public function abstractMethod(); // abstraktniMetoda -> abstractMethod +} + +class Child extends AbstractClass // Potomek -> Child, AbstraktniTrida -> AbstractClass +{ + public function abstractMethod() // abstraktniMetoda -> abstractMethod + { + echo 'これは抽象メソッドの実装です'; // Toto je implementace abstraktní metody -> これは抽象メソッドの実装です + } +} + +$instance = new Child; // Potomek -> Child +$instance->regularMethod(); // obycejnaMetoda -> regularMethod +$instance->abstractMethod(); // abstraktniMetoda -> abstractMethod +``` + +この例では、1つの通常メソッドと1つの抽象メソッドを持つ抽象クラスがあります。次に、`AbstractClass`から継承し、抽象メソッドの実装を提供する`Child`クラスがあります。 + +インターフェースと抽象クラスは実際にはどのように異なりますか?抽象クラスは抽象メソッドと具象メソッドの両方を含むことができますが、インターフェースはクラスが実装しなければならないメソッドを定義するだけで、実装は提供しません。クラスは1つの抽象クラスからしか継承できませんが、任意の数のインターフェースを実装できます。 + + +型チェック +----- + +プログラミングでは、扱っているデータが正しい型であることを確認することが非常に重要です。PHPには、これを保証するためのツールがあります。データが正しい型を持っているかどうかを確認することは、「型チェック」と呼ばれます。 + +PHPで遭遇する可能性のある型: + +1. **基本型**: `int`(整数)、`float`(浮動小数点数)、`bool`(真偽値)、`string`(文字列)、`array`(配列)、`null`が含まれます。 +2. **クラス**: 値が特定のクラスのインスタンスであることを要求する場合。 +3. **インターフェース**: クラスが実装しなければならないメソッドのセットを定義します。インターフェースを満たす値は、これらのメソッドを持っている必要があります。 +4. **混合型**: 変数が複数の許可された型を持つことができるように指定できます。 +5. **Void**: この特殊な型は、関数またはメソッドが値を返さないことを示します。 + +型を含むようにコードを修正する方法を見てみましょう: + +```php +class Person // Osoba -> Person +{ + private int $age; // vek -> age + + public function __construct(int $age) // vek -> age + { + $this->age = $age; // vek -> age + } + + public function displayAge(): void // vypisVek -> displayAge + { + echo "この人は{$this->age}歳です。"; // Této osobě je {$this->vek} let. -> この人は{$this->age}歳です。 + } +} + +/** + * Personクラスのオブジェクトを受け取り、その人の年齢を表示する関数。 // Funkce, která přijímá objekt třídy Osoba a vypíše věk osoby. -> Personクラスのオブジェクトを受け取り、その人の年齢を表示する関数。 + */ +function displayPersonAge(Person $person): void // vypisVekOsoby -> displayPersonAge, osoba -> person, Osoba -> Person +{ + $person->displayAge(); // osoba -> person, vypisVek -> displayAge +} +``` + +このようにして、コードが正しい型のデータを期待し、それを使用して動作することを保証し、潜在的なエラーを防ぐのに役立ちます。 + +PHPでは直接記述できない型もあります。その場合、phpDocコメントで指定されます。これは、`/**`で始まり`*/`で終わるPHPコードを文書化するための標準形式です。これにより、クラス、メソッドなどの説明を追加できます。また、いわゆるアノテーション`@var`、`@param`、`@return`を使用して複雑な型を指定することもできます。これらの型は、静的コード解析ツールによって使用されますが、PHP自体はそれらをチェックしません。 + +```php +class List // Seznam -> List +{ + /** @var array<Person> この表記は、Personオブジェクトの配列であることを示します */ // Osoba -> Person, zápis říká, že jde o pole objektů Osoba -> この表記は、Personオブジェクトの配列であることを示します + private array $people = []; // osoby -> people + + public function addPerson(Person $person): void // pridatOsobu -> addPerson, osoba -> person, Osoba -> Person + { + $this->people[] = $person; // osoby -> people, osoba -> person + } +} +``` + + +比較と同一性 +------ + +PHPでは、2つの方法でオブジェクトを比較できます: + +1. 値の比較 `==`: オブジェクトが同じクラスであり、プロパティに同じ値を持っているかどうかを確認します。 +2. 同一性 `===`: 同じオブジェクトインスタンスであるかどうかを確認します。 + +```php +class Car // Auto -> Car +{ + public string $brand; // znacka -> brand + + public function __construct(string $brand) // znacka -> brand + { + $this->brand = $brand; // znacka -> brand + } +} + +$auto1 = new Car('Skoda'); // Auto -> Car +$auto2 = new Car('Skoda'); // Auto -> Car +$auto3 = $auto1; + +var_dump($auto1 == $auto2); // true、同じ値を持っているため // true, protože mají stejnou hodnotu -> true、同じ値を持っているため +var_dump($auto1 === $auto2); // false、同じインスタンスではないため // false, protože nejsou stejná instance -> false、同じインスタンスではないため +var_dump($auto1 === $auto3); // true、$auto3は$auto1と同じインスタンスであるため // true, protože $auto3 je stejná instance jako $auto1 -> true、$auto3は$auto1と同じインスタンスであるため +``` + + +`instanceof` 演算子 +---------------- + +`instanceof`演算子を使用すると、特定のオブジェクトが特定のクラスのインスタンスであるか、そのクラスの子孫であるか、または特定のインターフェースを実装しているかどうかを確認できます。 + +`Person`クラスと、`Person`クラスの子孫である別のクラス`Student`があると想像してみましょう: + +```php +class Person // Osoba -> Person +{ + private int $age; // vek -> age + + public function __construct(int $age) // vek -> age + { + $this->age = $age; // vek -> age + } +} + +class Student extends Person // Osoba -> Person +{ + private string $major; // obor -> major + + public function __construct(int $age, string $major) // vek -> age, obor -> major + { + parent::__construct($age); // vek -> age + $this->major = $major; // obor -> major + } +} + +$student = new Student(20, '情報学'); // Informatika -> 情報学 + +// $studentがStudentクラスのインスタンスであるかどうかの確認 // Ověření, zda je $student instancí třídy Student -> $studentがStudentクラスのインスタンスであるかどうかの確認 +var_dump($student instanceof Student); // 出力: bool(true) // Výstup: bool(true) -> 出力: bool(true) + +// $studentがPersonクラスのインスタンスであるかどうかの確認(StudentはPersonの子孫であるため) // Ověření, zda je $student instancí třídy Osoba (protože Student je potomek Osoba) -> $studentがPersonクラスのインスタンスであるかどうかの確認(StudentはPersonの子孫であるため) +var_dump($student instanceof Person); // 出力: bool(true) // Výstup: bool(true) -> 出力: bool(true) +``` + +出力から、`$student`オブジェクトは同時に両方のクラス(`Student`と`Person`)のインスタンスと見なされることが明らかです。 + + +Fluent Interface +---------------- + +「Fluent Interface」(英語では「Fluent Interface」)は、OOPのテクニックであり、1回の呼び出しでメソッドを連鎖させることができます。これにより、コードがしばしば簡略化され、明確になります。 + +Fluent Interfaceの重要な要素は、チェーン内の各メソッドが現在のオブジェクトへの参照を返すことです。これは、メソッドの最後に`return $this;`を使用することで実現します。このプログラミングスタイルは、オブジェクトのプロパティ値を設定する「セッター」と呼ばれるメソッドとよく関連付けられます。 + +電子メール送信の例でFluent Interfaceがどのように見えるかを示します: + +```php +public function sendMessage() +{ + $email = new Email; + $email->setFrom('sender@example.com') + ->setRecipient('admin@example.com') + ->setMessage('Hello, this is a message.') + ->send(); +} +``` + +この例では、メソッド`setFrom()`、`setRecipient()`、`setMessage()`は、対応する値(送信者、受信者、メッセージの内容)を設定するために使用されます。これらの各値を設定した後、メソッドは現在のオブジェクト(`$email`)を返し、これにより次のメソッドを連鎖させることができます。最後に、実際に電子メールを送信するメソッド`send()`を呼び出します。 + +Fluent Interfaceのおかげで、直感的で読みやすいコードを書くことができます。 + + +`clone`を使用したコピー +--------------- + +PHPでは、`clone`演算子を使用してオブジェクトのコピーを作成できます。この方法で、同一の内容を持つ新しいインスタンスを取得します。 + +オブジェクトをコピーする際に一部のプロパティを変更する必要がある場合は、クラス内で特殊なメソッド`__clone()`を定義できます。このメソッドは、オブジェクトがクローンされるときに自動的に呼び出されます。 + +```php +class Sheep // Ovce -> Sheep +{ + public string $name; // jmeno -> name + + public function __construct(string $name) // jmeno -> name + { + $this->name = $name; // jmeno -> name + } + + public function __clone() + { + $this->name = 'クローン ' . $this->name; // jmeno -> name, Klon -> クローン + } +} + +$original = new Sheep('Dolly'); // Ovce -> Sheep +echo $original->name . "\n"; // 出力: Dolly // jmeno -> name, Vypíše: Dolly -> 出力: Dolly + +$clone = clone $original; // klon -> clone +echo $clone->name . "\n"; // 出力: クローン Dolly // klon -> clone, jmeno -> name, Vypíše: Klon Dolly -> 出力: クローン Dolly +``` + +この例では、1つのプロパティ`$name`を持つ`Sheep`クラスがあります。このクラスのインスタンスをクローンすると、`__clone()`メソッドは、クローンされた羊の名前が「クローン」プレフィックスを取得するようにします。 + + +トレイト +---- + +PHPのトレイトは、クラス間でメソッド、プロパティ、定数を共有し、コードの重複を防ぐことを可能にするツールです。これらは「コピー&ペースト」(Ctrl-CおよびCtrl-V)メカニズムのようなものと考えることができ、トレイトの内容がクラスに「挿入」されます。これにより、複雑なクラス階層を作成することなくコードを再利用できます。 + +PHPでトレイトを使用する方法の簡単な例を示しましょう: + +```php +trait Honking // Troubeni -> Honking +{ + public function honk() // zatrub -> honk + { + echo 'Bip bip!'; + } +} + +class Car // Auto -> Car +{ + use Honking; // Troubeni -> Honking +} + +class Truck // Nakladak -> Truck +{ + use Honking; // Troubeni -> Honking +} + +$car = new Car; // auto -> car, Auto -> Car +$car->honk(); // 'Bip bip!' を出力 // auto -> car, zatrub -> honk, Vypíše 'Bip bip!' -> 'Bip bip!' を出力 + +$truck = new Truck; // nakladak -> truck, Nakladak -> Truck +$truck->honk(); // 同様に 'Bip bip!' を出力 // nakladak -> truck, zatrub -> honk, Také vypíše 'Bip bip!' -> 同様に 'Bip bip!' を出力 +``` + +この例では、1つのメソッド`honk()`を含む`Honking`という名前のトレイトがあります。次に、`Car`と`Truck`の2つのクラスがあり、どちらも`Honking`トレイトを使用しています。これにより、両方のクラスが`honk()`メソッドを「持ち」、両方のクラスのオブジェクトでそれを呼び出すことができます。 + +トレイトを使用すると、クラス間でコードを簡単かつ効率的に共有できます。同時に、それらは継承階層には入りません。つまり、`$car instanceof Honking`は`false`を返します。 + + +例外 +------- + +OOPの例外を使用すると、コード内のエラーや予期しない状況をエレガントに処理できます。これらは、エラーまたは異常な状況に関する情報を持つオブジェクトです。 + +PHPには、すべての例外の基礎として機能する組み込みクラス`Exception`があります。これには、エラーメッセージ、エラーが発生したファイルと行など、例外に関する詳細情報を取得できるいくつかのメソッドがあります。 + +コードでエラーが発生した場合、キーワード`throw`を使用して例外を「スロー」できます。 + +```php +function divide(float $a, float $b): float // deleni -> divide +{ + if ($b === 0) { + throw new Exception('ゼロ除算!'); // Dělení nulou! -> ゼロ除算! + } + return $a / $b; +} +``` + +関数`divide()`が2番目の引数としてゼロを受け取ると、エラーメッセージ`'ゼロ除算!'`を持つ例外をスローします。例外がスローされたときにプログラムがクラッシュするのを防ぐために、`try/catch`ブロックでそれをキャッチします: + +```php +try { + echo divide(10, 0); // deleni -> divide +} catch (Exception $e) { + echo '例外がキャッチされました: '. $e->getMessage(); // Výjimka zachycena: -> 例外がキャッチされました: +} +``` + +例外をスローする可能性のあるコードは、`try`ブロックにラップされます。例外がスローされると、コードの実行は`catch`ブロックに移動し、そこで例外を処理できます(例:エラーメッセージを表示)。 + +`try`および`catch`ブロックの後には、オプションの`finally`ブロックを追加できます。これは、例外がスローされたかどうかに関係なく常に実行されます(`try`または`catch`ブロックで`return`、`break`、または`continue`ステートメントを使用した場合でも): + +```php +try { + echo divide(10, 0); // deleni -> divide +} catch (Exception $e) { + echo '例外がキャッチされました: '. $e->getMessage(); // Výjimka zachycena: -> 例外がキャッチされました: +} finally { + // 例外がスローされたかどうかに関係なく、常に実行されるコード // Kód, který se provede vždy, ať už byla výjimka vyhozena nebo ne -> 例外がスローされたかどうかに関係なく、常に実行されるコード +} +``` + +Exceptionクラスから継承する独自の例外クラス(階層)を作成することもできます。例として、入金と引き出しを実行できる簡単な銀行アプリケーションを想像してみましょう: + +```php +class BankException extends Exception {} // BankovniVyjimka -> BankException +class InsufficientFundsException extends BankException {} // NedostatekProstredkuVyjimka -> InsufficientFundsException, BankovniVyjimka -> BankException +class LimitExceededException extends BankException {} // PrekroceniLimituVyjimka -> LimitExceededException, BankovniVyjimka -> BankException + +class BankAccount // BankovniUcet -> BankAccount +{ + private int $balance = 0; // zustatek -> balance + private int $dailyLimit = 1000; // denniLimit -> dailyLimit + + public function deposit(int $amount): int // vlozit -> deposit, castka -> amount + { + $this->balance += $amount; // zustatek -> balance, castka -> amount + return $this->balance; // zustatek -> balance + } + + public function withdraw(int $amount): int // vybrat -> withdraw, castka -> amount + { + if ($amount > $this->balance) { // castka -> amount, zustatek -> balance + throw new InsufficientFundsException('口座に十分な残高がありません。'); // NedostatekProstredkuVyjimka -> InsufficientFundsException, Na účtu není dostatek prostředků. -> 口座に十分な残高がありません。 + } + + if ($amount > $this->dailyLimit) { // castka -> amount, denniLimit -> dailyLimit + throw new LimitExceededException('1日の引き出し限度額を超えました。'); // PrekroceniLimituVyjimka -> LimitExceededException, Byl překročen denní limit pro výběry. -> 1日の引き出し限度額を超えました。 + } + + $this->balance -= $amount; // zustatek -> balance, castka -> amount + return $this->balance; // zustatek -> balance + } +} +``` + +1つの`try`ブロックに対して、異なるタイプの例外を予期する場合は、複数の`catch`ブロックを指定できます。 + +```php +$account = new BankAccount; // ucet -> account, BankovniUcet -> BankAccount +$account->deposit(500); // ucet -> account, vlozit -> deposit + +try { + $account->withdraw(1500); // ucet -> account, vybrat -> withdraw +} catch (LimitExceededException $e) { // PrekroceniLimituVyjimka -> LimitExceededException + echo $e->getMessage(); +} catch (InsufficientFundsException $e) { // NedostatekProstredkuVyjimka -> InsufficientFundsException + echo $e->getMessage(); +} catch (BankException $e) { // BankovniVyjimka -> BankException + echo '操作の実行中にエラーが発生しました。'; // Vyskytla se chyba při provádění operace. -> 操作の実行中にエラーが発生しました。 +} +``` + +この例では、`catch`ブロックの順序に注意することが重要です。すべての例外は`BankException`から継承するため、このブロックを最初に配置した場合、後続の`catch`ブロックにコードが到達することなく、すべての例外がここでキャッチされてしまいます。したがって、より具体的な例外(つまり、他の例外から継承するもの)を、親の例外よりも`catch`ブロックの順序で上に配置することが重要です。 + + +イテレーション +------- + +PHPでは、配列を反復処理するのと同様に、`foreach`ループを使用してオブジェクトを反復処理できます。これが機能するためには、オブジェクトは特別なインターフェースを実装する必要があります。 + +最初のオプションは、`Iterator`インターフェースを実装することです。これには、現在の値を返す`current()`、キーを返す`key()`、次の値に移動する`next()`、先頭に移動する`rewind()`、そしてまだ終端に達していないかどうかを確認する`valid()`メソッドがあります。 + +2番目のオプションは、`IteratorAggregate`インターフェースを実装することです。これには`getIterator()`という1つのメソッドしかありません。これは、反復処理を保証するプレースホルダーオブジェクトを返すか、またはジェネレータを表すことができます。ジェネレータは、キーと値を段階的に返すために`yield`を使用する特別な関数です: + +```php +class Person // Osoba -> Person +{ + public function __construct( + public int $age, // vek -> age + ) { + } +} + +class List implements IteratorAggregate // Seznam -> List +{ + private array $people = []; // osoby -> people + + public function addPerson(Person $person): void // pridatOsobu -> addPerson, osoba -> person, Osoba -> Person + { + $this->people[] = $person; // osoby -> people, osoba -> person + } + + public function getIterator(): Generator + { + foreach ($this->people as $person) { // osoby -> people, osoba -> person + yield $person; // osoba -> person + } + } +} + +$list = new List; // seznam -> list, Seznam -> List +$list->addPerson(new Person(30)); // seznam -> list, pridatOsobu -> addPerson, Osoba -> Person +$list->addPerson(new Person(25)); // seznam -> list, pridatOsobu -> addPerson, Osoba -> Person + +foreach ($list as $person) { // seznam -> list, osoba -> person + echo "年齢: {$person->age} 歳 \n"; // Věk: {$osoba->vek} let \n -> 年齢: {$person->age} 歳 \n +} +``` + + +ベストプラクティス +--------- + +オブジェクト指向プログラミングの基本原則を理解したら、OOPのベストプラクティスに焦点を当てることが重要です。これらは、機能的であるだけでなく、読みやすく、理解しやすく、保守しやすいコードを書くのに役立ちます。 + +1) **関心の分離 (Separation of Concerns)**: 各クラスは明確に定義された責任を持ち、1つの主要なタスクのみを解決する必要があります。クラスが多くのことを行いすぎる場合は、より小さく、特化したクラスに分割するのが適切かもしれません。 +2) **カプセル化 (Encapsulation)**: データとメソッドは可能な限り隠蔽し、定義されたインターフェースを通じてのみアクセス可能であるべきです。これにより、コードの残りの部分に影響を与えることなく、クラスの内部実装を変更できます。 +3) **依存性の注入 (Dependency Injection)**: クラス内で直接依存関係を作成する代わりに、外部から「注入」する必要があります。この原則をより深く理解するために、[Dependency Injectionに関する章 |dependency-injection:introduction]をお勧めします。 diff --git a/nette/ja/troubleshooting.texy b/nette/ja/troubleshooting.texy new file mode 100644 index 0000000000..065387d9cf --- /dev/null +++ b/nette/ja/troubleshooting.texy @@ -0,0 +1,213 @@ +トラブルシューティング +*********** + + +Netteが動作せず、白いページが表示される +---------------------- +- ファイル `index.php` の `declare(strict_types=1);` の直後に `ini_set('display_errors', '1'); error_reporting(E_ALL);` を挿入してみてください。これによりエラー表示が強制されます。 +- それでも白い画面が表示される場合は、サーバー設定にエラーがある可能性が高く、サーバーログで原因がわかります。念のため、`echo 'test';` を使用して何かを出力してみて、PHPが実際に機能しているかどうかを確認してください。 +- エラー *Server Error: We're sorry! …* が表示される場合は、次のセクションに進んでください。 + + +エラー 500 *Server Error: We're sorry! …* +-------------------------------------- +このエラーページは、Netteが本番モードで表示します。開発用コンピュータで表示される場合は、[開発モードに切り替える |application:bootstrapping#開発環境 vs 本番環境]と、詳細なメッセージを含むTracyが表示されます。 + +エラーの原因は常に `log/` ディレクトリのログで確認できます。ただし、エラーメッセージに `Tracy is unable to log error` という文が表示される場合は、まずエラーがログに記録できない理由を確認してください。たとえば、一時的に[開発モードに切り替える |application:bootstrapping#開発環境 vs 本番環境]し、Tracyが起動後に何かをログに記録するようにします。 + +```php +// Bootstrap.php +$configurator->setDebugMode('23.75.345.200'); // あなたのIPアドレス +$configurator->enableTracy($rootDir . '/log'); +\Tracy\Debugger::log('hello'); +``` + +Tracyはログに記録できない理由を教えてくれます。原因として考えられるのは、[不十分な権限 |#ディレクトリ権限の設定]による `log/` ディレクトリへの書き込み不可です。 + +500エラーの最も一般的な原因の1つは、古いキャッシュです。Netteは開発モードではキャッシュを賢く自動的に更新しますが、本番モードではパフォーマンスの最大化に重点を置いており、コードを変更するたびにキャッシュを削除するのはあなた次第です。`temp/cache` を削除してみてください。 + + +エラー 404、ルーティングが機能しない +-------------------- +ホームページ以外のすべてのページが404エラーを返す場合、[きれいなURL |#きれいなURLのためにサーバーを設定する方法は]のサーバー設定に問題があるようです。 + + +テンプレートや設定の変更が反映されない +------------------- +「テンプレートや設定を編集しましたが、Webサイトはまだ古いバージョンを表示しています。」この動作は、[本番モード |application:bootstrapping#開発環境 vs 本番環境]で発生します。これは、パフォーマンス上の理由からファイルの変更をチェックせず、一度生成されたキャッシュを保持するためです。 + +本番サーバーで編集するたびに手動でキャッシュを削除する必要がないように、`Bootstrap.php` ファイルで自分のIPアドレスに対して開発モードを有効にします。 + +```php +$this->configurator->setDebugMode('your.ip.address'); +``` + + +開発中にキャッシュを無効にする方法は? +------------------- +Netteは賢いので、キャッシュを無効にする必要はありません。開発中は、テンプレートやDIコンテナの設定が変更されるたびにキャッシュを自動的に更新します。さらに、開発モードは自動検出によって有効になるため、通常は何も設定する必要はありません。[またはIPアドレスのみ |application:bootstrapping#開発環境 vs 本番環境]。 + +ルーターのデバッグ中は、ブラウザのキャッシュを無効にすることをお勧めします。リダイレクトなどが保存されている可能性があります。開発者ツール(Ctrl+Shift+IまたはCmd+Option+I)を開き、ネットワーク(Network)パネルでキャッシュの無効化をチェックします。 + + +エラー `#[\ReturnTypeWillChange] attribute should be used` +------------------------------------------------------- +このエラーは、PHPをバージョン8.1に更新したが、それと互換性のないNetteを使用している場合に表示されます。解決策は、`composer update`を使用してNetteを新しいバージョンに更新することです。Netteはバージョン3.0以降でPHP 8.1をサポートしています。古いバージョン(`composer.json`を確認して確認)を使用している場合は、[Netteをアップグレードする |migrations:en]か、PHP 8.0を使用し続けてください。 + + +ディレクトリ権限の設定 +----------- +macOSまたはLinux(またはその他のUnixベースのシステム)で開発している場合は、Webサーバーに書き込み権限を設定する必要があります。アプリケーションがデフォルトの `/var/www/html` (Fedora、CentOS、RHEL)にあると仮定します。 + +```shell +cd /var/www/html/MY_PROJECT +chmod -R a+rw temp log +``` + +一部のLinux(Fedora、CentOSなど)では、SELinuxがデフォルトで有効になっています。SELinuxポリシーを適切に調整し、`temp`および`log`フォルダに正しいSELinuxセキュリティコンテキストを設定する必要があります。`temp`および`log`には`httpd_sys_rw_content_t`コンテキストタイプを設定し、アプリケーションの残りの部分(特に`app`フォルダ)には`httpd_sys_content_t`で十分です。サーバーで実行します。 + +```shell +semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/log(/.*)?' +semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/temp(/.*)?' +restorecon -Rv /var/www/html/MY_PROJECT/ +``` + +次に、SELinuxブール値`httpd_can_network_connect_db`を有効にする必要があります。これはデフォルト設定では無効になっており、Netteがネットワーク経由でデータベースに接続できるようにします。これには`setsebool`コマンドを使用し、`-P`オプションで変更を永続的にします。つまり、サーバーの再起動後に不快な驚きはありません。 + +```shell +setsebool -P httpd_can_network_connect_db on +``` + + +URLから `www` ディレクトリを変更または削除する方法は? +-------------------------------- +Netteのサンプルプロジェクトで使用されている`www/`ディレクトリは、いわゆるパブリックディレクトリまたはプロジェクトのdocument-rootを表します。これは、ブラウザからアクセスできる唯一のディレクトリです。そして、Netteで書かれたWebアプリケーションを起動するエントリポイントである`index.php`ファイルが含まれています。 + +ホスティングでアプリケーションを動作させるには、document-rootが正しく設定されている必要があります。2つのオプションがあります。 +1. ホスティング設定でdocument-rootをこのディレクトリに設定します。 +2. ホスティングに事前準備されたフォルダ(例:`public_html`)がある場合は、`www/`をその名前に変更します。 + +.[warning] +他のフォルダへのアクセスを妨げる`.htaccess`やルーターだけでセキュリティを解決しようとしないでください。 + +ホスティングがdocument-rootをサブディレクトリに設定することを許可しない場合(つまり、パブリックディレクトリより1レベル上にディレクトリを作成できない場合)、別のホスティングを探してください。そうしないと、重大なセキュリティリスクにさらされることになります。それは、玄関ドアが閉まらず、常に開いているアパートに住んでいるようなものです。 + + +きれいなURLのためにサーバーを設定する方法は? +------------------------ +**Apache**: `.htaccess`ファイルでmod_rewriteルールを有効にして設定する必要があります。 + +```apacheconf +RewriteEngine On +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] +``` + +問題が発生した場合は、次のことを確認してください。 +- `.htaccess`ファイルがdocument-rootディレクトリ(つまり`index.php`ファイルの隣)にあること +- [Apacheが`.htaccess`ファイルを処理していること |#htaccess が機能していることの確認] +- [mod_rewriteが有効になっていること |#mod rewrite が有効になっていることの確認] + +アプリケーションをサブフォルダに設定している場合は、`RewriteBase`設定の行のコメントを解除し、正しいフォルダに設定する必要があるかもしれません。 + +**nginx**: サーバー設定の`location /`ブロック内で`try_files`ディレクティブを使用してリダイレクトを設定する必要があります。 + +```nginx +location / { + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args が重要です! +} +``` + +`location`ブロックは、`server`ブロック内で各ファイルシステムパスに対して1回だけ出現できます。設定にすでに`location /`がある場合は、`try_files`ディレクティブをそこに追加します。 + + +`.htaccess`が機能していることの確認 +----------------------- +Apacheが`.htaccess`ファイルを使用しているか無視しているかをテストする最も簡単な方法は、意図的に破損させることです。ファイルの先頭に行`Test`を挿入し、ブラウザでページを更新すると、*Internal Server Error*が表示されるはずです。 + +このエラーが表示された場合、それは実際には良いことです!Apacheが`.htaccess`ファイルを解析し、挿入したエラーに遭遇したことを意味します。行`Test`を削除してください。 + +*Internal Server Error*が表示されない場合、Apacheの設定は`.htaccess`ファイルを無視しています。一般的に、Apacheは設定ディレクティブ`AllowOverride All`がないためにそれを無視します。 + +自分でホストしている場合は、簡単に修正できます。テキストエディタで`httpd.conf`または`apache.conf`ファイルを開き、関連する`<Directory>`セクションを見つけて、このディレクティブを追加/変更します。 + +```apacheconf +<Directory "/var/www/htdocs"> # あなたのドキュメントルートへのパス + AllowOverride All + ... +``` + +Webサイトが他の場所でホストされている場合は、コントロールパネルで`.htaccess`ファイルを有効にできるかどうかを確認してください。できない場合は、ホスティングプロバイダーに依頼してください。 + + +`mod_rewrite`が有効になっていることの確認 +--------------------------- +[`.htaccess`が機能していること |#htaccess が機能していることの確認]を確認できたら、mod_rewrite拡張機能が有効になっているかどうかを確認できます。`.htaccess`ファイルの先頭に行`RewriteEngine On`を挿入し、ブラウザでページを更新します。*Internal Server Error*が表示された場合、mod_rewriteが有効になっていないことを意味します。有効にする方法はいくつかあります。さまざまな設定でこれを行うさまざまな方法については、Stack Overflowを参照してください。 + + +リンクが `https:` なしで生成される +---------------------- +Netteは、ページ自体と同じプロトコルでリンクを生成します。つまり、`https://foo`ページでは`https:`で始まるリンクを生成し、その逆も同様です。 HTTPSを削除するリバースプロキシサーバー(たとえばDocker内)の背後にいる場合は、プロトコル検出が正しく機能するように、設定で[プロキシを設定する |http:configuration#HTTP プロキシ]必要があります。 + +プロキシとしてNginxを使用している場合は、リダイレクトを次のように設定する必要があります。 + +``` +location / { + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Port $server_port; + proxy_pass http://IP-aplikace:80; # アプリケーションが実行されているサーバー/コンテナのIPまたはホスト名 +} +``` + +さらに、設定にプロキシのIPと、インフラストラクチャを運用しているローカルネットワークのIP範囲を指定する必要があります。 + +```neon +http: + proxy: IP-proxy/IP-range +``` + + +JavaScriptでの { } 文字の使用 +---------------------- +文字 `{` と `}` はLatteタグを記述するために使用されます。`{` 文字に続くものは、スペースと引用符を除き、すべてタグと見なされます。したがって、直接 `{` 文字を出力する必要がある場合(JavaScriptでよくある)、`{` 文字の後にスペース(または他の空白文字)を置くことができます。これにより、タグとしての解釈を回避できます。 + +テキストがタグとして解釈される状況でこれらの文字を出力する必要がある場合は、これらの文字を出力するための特別なタグ `{l}`(`{`用)と `{r}`(`}`用)を使用できます。 + +``` +{これはタグです} +{ これはタグではありません } +{l}これはタグではありません{r} +``` + + +メッセージ `Presenter::getContext() is deprecated` +--------------------------------------------- + +Netteは、依存性注入に移行し、プログラマーにPresenter自体から始めて一貫して使用するように導いた最初のPHPフレームワークです。Presenterが依存関係を必要とする場合、[それを要求します|dependency-injection:passing-dependencies]。 逆に、クラスにDIコンテナ全体を渡し、そこから直接依存関係を取得する方法は、アンチパターンと見なされます(サービスロケータと呼ばれます)。 この方法は、依存性注入が登場する前のNette 0.xで使用されており、その名残がメソッド`Presenter::getContext()`であり、 давно deprecatedとしてマークされています。 + +非常に古いNetteアプリケーションを移植している場合、このメソッドがまだ使用されている可能性があります。`nette/application`バージョン3.1以降では、警告`Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection`が表示され、バージョン4.0以降ではメソッドが存在しないというエラーが表示されます。 + +クリーンな解決策はもちろん、依存性注入を使用して依存関係を渡すようにアプリケーションをリファクタリングすることです。回避策として、独自のメソッド`getContext()`をベースPresenterに追加し、メッセージを回避できます。 + +```php +abstract BasePresenter extends Nette\Application\UI\Presenter +{ + private Nette\DI\Container $context; + + public function injectContext(Nette\DI\Container $context) + { + $this->context = $context; + } + + public function getContext(): Nette\DI\Container + { + return $this->context; + } +} +``` + + +{{leftbar: www:@menu-common}} diff --git a/nette/ja/vulnerability-protection.texy b/nette/ja/vulnerability-protection.texy new file mode 100644 index 0000000000..5d1d14118d --- /dev/null +++ b/nette/ja/vulnerability-protection.texy @@ -0,0 +1,99 @@ +脆弱性からの保護 +******** + +.[perex] +次々と主要なWebサイトでセキュリティホールが報告されたり、悪用されたりしています。これは不快なことです。Webアプリケーションのセキュリティを重視するなら、Nette Frameworkは間違いなく最良の選択肢です。 + + +Cross-Site Scripting (XSS) +========================== + +クロスサイトスクリプティングは、エスケープされていない出力を悪用してWebサイトを侵害する手法です。攻撃者はページに独自のコードを挿入し、それによってページを変更したり、訪問者の機密情報を取得したりすることができます。XSSに対する防御は、すべての文字列を徹底的かつ正確にエスケープすることによってのみ可能です。しかし、コーダーが一度でもこれを怠ると、Webサイト全体がすぐに侵害される可能性があります。 + +攻撃の例としては、ユーザーに改変されたURLを送りつけ、それを使ってページに自分のコードを注入することが挙げられます。アプリケーションが出力を適切にエスケープしない場合、スクリプトはユーザーのブラウザで実行されます。これにより、例えばユーザーのIDを盗むことができます。 + +``` +https://example.com/?search=<script>alert('XSS攻撃成功');</script> +``` + +Nette Frameworkは、画期的な技術である[コンテキスト対応エスケープ |latte:safety-first#コンテキストに応じたエスケープ]を提供しており、これによりクロスサイトスクリプティングのリスクから永久に解放されます。すべての出力は自動的にエスケープされるため、コーダーが何かを忘れることはありえません。例?コーダーがこのテンプレートを作成します: + +```latte +<p onclick="alert({$message})">{$message}</p> + +<script> +document.title = {$message}; +</script> +``` + +`{$message}`という記述は変数の出力を意味します。他のフレームワークでは、各出力を明示的にエスケープする必要があり、しかも場所ごとに異なる方法でエスケープする必要があります。Nette Frameworkでは、何もエスケープする必要はありません。すべてが自動的に、正しく、一貫して行われます。変数に`$message = '幅 1/2"'`を代入すると、フレームワークは次のHTMLコードを生成します: + +```latte +<p onclick="alert("幅 1\/2\"")">幅 1/2"</p> + +<script> +document.title = "幅 1\/2\""; +</script> +``` + + +Cross-Site Request Forgery (CSRF) +================================= + +クロスサイトリクエストフォージェリ攻撃は、攻撃者が被害者をあるページに誘い込み、そのページが被害者のブラウザで、被害者がログインしているサーバーに対して密かにリクエストを実行するというものです。サーバーは、そのリクエストが被害者自身の意思によって実行されたものと誤認します。そして、被害者のアイデンティティの下で、被害者が知らないうちに特定の操作(データの変更や削除、メッセージの送信など)を実行します。 + +Nette Frameworkは、**Presenter内のフォームとシグナルを自動的に**この種の攻撃から保護します。これは、異なるドメインからの送信や呼び出しを防ぐことによって行われます。保護を無効にしたい場合は、フォームで次のように使用します: + +```php +$form->allowCrossOrigin(); +``` + +または、シグナルの場合は `@crossOrigin` アノテーションを追加します: + +```php +/** + * @crossOrigin + */ +public function handleXyz() +{ +} +``` + +Nette Application 3.2では、属性を使用することもできます: + +```php +use Nette\Application\Attributes\Requires; + +#[Requires(sameOrigin: false)] +public function handleXyz() +{ +} +``` + + +URL攻撃、制御コード、無効なUTF-8 +==================== + +攻撃者がWebアプリケーションに*悪意のある*入力を送り込もうとする試みに関連するさまざまな用語。その結果は非常に多様で、XML出力の破損(例:機能しないRSSフィード)から、データベースやパスワードからの機密情報の取得までさまざまです。防御策は、個々のバイトレベルですべての入力を一貫してサニタイズすることです。そして正直に言って、これをしている人はどれくらいいるでしょうか? + +Nette Frameworkはこれを自動的に行います。何も設定する必要はなく、すべての入力がサニタイズされます。 + + +セッションハイジャック、セッション盗用、セッション固定 +=========================== + +セッション管理には、いくつかのタイプの攻撃が関連しています。攻撃者は、ユーザーのセッションIDを盗むか、または自分のセッションIDをユーザーに送りつけ、それによってユーザーのパスワードを知らなくてもWebアプリケーションにアクセスできるようになります。その後、ユーザーが知らないうちにアプリケーションで何でも実行できます。防御策は、サーバーとPHPを正しく設定することです。 + +Nette FrameworkはPHPを自動的に設定します。したがって、プログラマーはセッションを正しく保護する方法を考える必要がなく、アプリケーションの作成に完全に集中できます。ただし、これには`ini_set()`関数が有効になっている必要があります。 + + +SameSite cookie +=============== + +SameSite cookiesは、ページの読み込みにつながった原因を認識するメカニズムを提供します。これはセキュリティにとって絶対に不可欠です。 + +SameSiteフラグは、`Lax`、`Strict`、`None`(これはHTTPSが必要)の3つの値を持つことができます。ページへのリクエストがWebサイトから直接来た場合、またはユーザーがアドレスバーに直接入力するかブックマークをクリックしてページを開いた場合、ブラウザはすべてのCookie(つまり、`Lax`、`Strict`、`None`フラグを持つもの)をサーバーに送信します。ユーザーが別のWebサイトからのリンクをクリックしてWebサイトにアクセスした場合、`Lax`および`None`フラグを持つCookieがサーバーに送信されます。リクエストが他の方法で発生した場合、例えば別のWebサイトからのPOSTフォームの送信、iframe内での読み込み、JavaScriptによるものなどでは、`None`フラグを持つCookieのみが送信されます。 + +NetteはデフォルトですべてのCookieを`Lax`フラグ付きで送信します。 + +{{leftbar: www:@menu-common}} diff --git a/nette/pl/@home.texy b/nette/pl/@home.texy index 9868af29e3..1ce827904d 100644 --- a/nette/pl/@home.texy +++ b/nette/pl/@home.texy @@ -6,25 +6,25 @@ Dokumentacja Nette <div> -Wstęp ------ -- [Dlaczego warto używać Nette? |www:10-reasons-why-nette] -- [Instalacja |Installation] -- [Stwórz swoją pierwszą aplikację! |quickstart:] +Wprowadzenie +------------ +- [Dlaczego używać Nette? |www:10-reasons-why-nette] +- [Instalacja |installation] +- [Pisanie pierwszej aplikacji! |quickstart:] Ogólne ------ - [Lista pakietów |www:packages] -- [Konserwacja i wersja PHP |www:maintenance] -- [Uwagi do wydania |https://nette.org/releases] -- [Aktualizacja do nowszych wersji |migrations:en] +- [Utrzymanie i wersje PHP |www:maintenance] +- [Informacje o wydaniu |https://nette.org/releases] +- [Migracja do nowszych wersji|migrations:en] - [Rozwiązywanie problemów |nette:troubleshooting] - [Kto tworzy Nette |https://nette.org/contributors] - [Historia Nette |www:history] - [Zaangażuj się |contributing:] -- [Rozwój sponsora |https://nette.org/en/donate] -- [Odniesienia do API |https://api.nette.org/] +- [Wspieraj rozwój |https://nette.org/cs/donate] +- [Referencja API |https://api.nette.org/] </div> @@ -34,17 +34,17 @@ Ogólne Aplikacje w Nette ----------------- - [Jak działają aplikacje? |application:how-it-works] -- [Bootstrap |application:Bootstrap] -- [Prezenterzy |application:presenters] +- [Bootstrapping |application:Bootstrapping] +- [Presentery |application:presenters] - [Szablony |application:templates] -- [Moduły |application:modules] -- [Trasowanie |application:routing] +- [Struktura katalogów |application:directory-structure] +- [Routing |application:routing] - [Tworzenie linków URL |application:creating-links] -- [Elementy interaktywne |application:components] -- [AJAX i snippety |application:ajax] +- [Komponenty interaktywne |application:components] +- [AJAX & snippety |application:ajax] -- [Samouczki i procedury |best-practices:] +- [Poradniki i najlepsze praktyki |best-practices:] </div> @@ -54,18 +54,19 @@ Aplikacje w Nette Główne tematy ------------- - [Konfiguracja |nette:configuring] -- [Wtrysk zależności |dependency-injection:] +- [Wstrzykiwanie zależności|dependency-injection:] - [Latte: szablony |latte:] - [Tracy: debugowanie kodu |tracy:] - [Formularze |forms:] -- [Bazy danych |database:core] -- [Login użytkownika |security:authentication] -- [Uwierzytelnianie uprawnień |security:authorization] +- [Baza danych |database:guide] +- [Logowanie użytkowników |security:authentication] +- [Weryfikacja uprawnień |security:authorization] - [Sesje |http:Sessions] -- [Żądanie i odpowiedź HTTP |http:] -- [Pamięć podręczna |caching:] +- [Żądanie i odpowiedź HTTP|http:] +- [Aktywa |assets:] +- [Cache |caching:] - [Wysyłanie e-maili |mail:] -- [Schemat: zatwierdzanie danych |schema:] +- [Schema: walidacja danych |schema:] - [Generator kodu PHP |php-generator:] - [Tester: testowanie |tester:] </div> @@ -76,19 +77,19 @@ Główne tematy Narzędzia --------- -- [Polak |utils:arrays] +- [Tablice |utils:arrays] - [System plików |utils:filesystem] -- [Wyszukiwarka |utils:finder] +- [Finder |utils:finder] - [Elementy HTML |utils:html-elements] - [Obrazy |utils:images] - [JSON |utils:JSON] -- [NEON |neon:] -- [Hashing hasła |security:passwords] -- [SmartObject |utils:smartobject] -- [Rodzaje PHP |utils:type] -- [Struny |utils:strings] +- [NEON|neon:] +- [Haszowanie haseł |security:passwords] +- [Typy PHP |utils:type] +- [Ciągi znaków |utils:strings] - [Walidatory |utils:validators] - [RobotLoader |robot-loader:] +- [SmartObject |utils:smartobject] & [StaticClass |utils:StaticClass] - [SafeStream |safe-stream:] - [...więcej |utils:] </div> @@ -97,5 +98,5 @@ Narzędzia {{toc:no}} -{{description: Oficjalna dokumentacja Nette: opisuje sposób działania Nette i najlepsze praktyki tworzenia aplikacji internetowych.}} +{{description: Oficjalna dokumentacja Nette: opisuje, jak działa Nette i najlepsze praktyki tworzenia aplikacji internetowych.}} {{maintitle: Dokumentacja Nette}} diff --git a/nette/pl/@menu-topics.texy b/nette/pl/@menu-topics.texy index 52f98190f7..ad64cc3faf 100644 --- a/nette/pl/@menu-topics.texy +++ b/nette/pl/@menu-topics.texy @@ -2,17 +2,17 @@ Główne tematy ************* - [Konfiguracja |nette:configuring] - [Aplikacje w Nette |application:how-it-works] -- [Wtrysk zależności |dependency-injection:] +- [Wstrzykiwanie zależności|dependency-injection:] - [Narzędzia |utils:] - [Formularze |forms:] -- [Bazy danych |database:core] -- [Login użytkownika |security:authentication] -- [Uwierzytelnianie uprawnień |security:authorization] +- [Baza danych |database:guide] +- [Logowanie użytkowników |security:authentication] +- [Weryfikacja uprawnień |security:authorization] - [Sesje |http:Sessions] -- [Żądanie i odpowiedź HTTP |http:] -- [Pamięć podręczna |caching:] +- [Żądanie i odpowiedź HTTP|http:] +- [Cache |caching:] - [Wysyłanie e-maili |mail:] -- [Schemat: zatwierdzanie danych |schema:] +- [Schema: walidacja danych |schema:] - [Generator kodu PHP |php-generator:] diff --git a/nette/pl/@meta.texy b/nette/pl/@meta.texy new file mode 100644 index 0000000000..61ac92d1af --- /dev/null +++ b/nette/pl/@meta.texy @@ -0,0 +1 @@ +{{sitename: Dokumentacja Nette}} diff --git a/nette/pl/configuring.texy b/nette/pl/configuring.texy index 464eb7bd26..beddca1443 100644 --- a/nette/pl/configuring.texy +++ b/nette/pl/configuring.texy @@ -4,32 +4,33 @@ Konfiguracja Nette .[perex] Przegląd wszystkich opcji konfiguracyjnych w Nette Framework. -Komponenty Nette konfiguruje się za pomocą plików konfiguracyjnych, które zazwyczaj zapisane są w [formacie NEON |neon:format]. Najlepiej edytować je w [edytorach, które to obsługują |best-practices:editors-and-tools#IDE-Editor]. -Jeśli używasz pełnego frameworka, konfiguracja zostanie [załadowana podczas ładowania aplikacji |application:bootstrap#DI-Container-Configuration], jeśli nie, zobacz, [jak |bootstrap:] załadować konfigurację. +Komponenty Nette konfigurujemy za pomocą plików konfiguracyjnych, które zwykle zapisuje się w [formacie NEON|neon:format]. Najlepiej edytować je w [edytorach z jego obsługą |best-practices:editors-and-tools#Edytor IDE]. Jeśli używasz całego frameworka, konfiguracja zostanie [załadowana podczas uruchamiania aplikacji |application:bootstrapping#Konfiguracja kontenera DI], jeśli nie, przeczytaj, [jak załadować konfigurację|bootstrap:]. <pre> "application .[prism-token prism-atrule]":[application:configuration#Application]: "Aplikacja .[prism-token prism-comment]"<br> -"constants .[prism-token prism-atrule]":[application:configuration#Constants]: "Definicja stałych PHP .[prism-token prism-comment]"<br> +"assets .[prism-token prism-atrule]":[assets:configuration]: "Assets .[prism-token prism-comment]"<br> +"constants .[prism-token prism-atrule]":[application:configuration#Stałe]: "Definicje stałych PHP .[prism-token prism-comment]"<br> "database .[prism-token prism-atrule]":[database:configuration]: "Baza danych .[prism-token prism-comment]"<br> "decorator .[prism-token prism-atrule]":[dependency-injection:configuration#Decorator]: "Dekorator .[prism-token prism-comment]"<br> "di .[prism-token prism-atrule]":[dependency-injection:configuration#DI]: "Kontener DI .[prism-token prism-comment]"<br> -"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Extensions]: "Instalacja dodatkowych rozszerzeń DI .[prism-token prism-comment]"<br> +"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Rozszerzenia]: "Instalacja dodatkowych rozszerzeń DI .[prism-token prism-comment]"<br> "forms .[prism-token prism-atrule]":[forms:configuration]: "Formularze .[prism-token prism-comment]"<br> -"http .[prism-token prism-atrule]":[http:configuration#HTTP Headers]: "Nagłówki HTTP .[prism-token prism-comment]"<br> -"includes .[prism-token prism-atrule]":[dependency-injection:configuration#Including files]: "Embedding files .[prism-token prism-comment]"<br> -"latte .[prism-token prism-atrule]":[application:configuration#Latte]: "Szablony Latte .[prism-token prism-comment]"<br> -"mail .[prism-token prism-atrule]":[mail:#Configuring]: "Mailing .[prism-token prism-comment]"<br> -"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#Parameters]: "Parametry .[prism-token prism-comment]"<br> -"php .[prism-token prism-atrule]":[application:configuration#PHP]: "Konfiguracja php .[prism-token prism-comment]"<br> +"http .[prism-token prism-atrule]":[http:configuration#Nagłówki HTTP]: "Nagłówki HTTP .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[dependency-injection:configuration#Dołączanie plików]: "Dołączanie plików .[prism-token prism-comment]"<br> +"latte .[prism-token prism-atrule]":[application:configuration#Szablony Latte]: "Szablony Latte .[prism-token prism-comment]"<br> +"mail .[prism-token prism-atrule]":[mail:#Konfiguracja]: "Maile .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#Parametry]: "Parametry .[prism-token prism-comment]"<br> +"php .[prism-token prism-atrule]":[application:configuration#PHP]: "Konfiguracja PHP .[prism-token prism-comment]"<br> "routing .[prism-token prism-atrule]":[application:configuration#Routing]: "Routing .[prism-token prism-comment]"<br> -"search .[prism-token prism-atrule]":[dependency-injection:configuration#Search]: "Automatyczna rejestracja serwisu .[prism-token prism-comment]"<br> +"search .[prism-token prism-atrule]":[dependency-injection:configuration#Search]: "Automatyczna rejestracja usług .[prism-token prism-comment]"<br> "security .[prism-token prism-atrule]":[security:configuration]: "Uprawnienia dostępu .[prism-token prism-comment]"<br> "services .[prism-token prism-atrule]":[dependency-injection:services]: "Usługi .[prism-token prism-comment]"<br> -"session .[prism-token prism-atrule]":[http:configuration#Session]: "Sesja .[prism-token prism-comment]"<br> -"tracy .[prism-token prism-atrule]":[tracy:configuring#Nette Framework]: "Tracy debugger .[prism-token prism-comment]" +"session .[prism-token prism-atrule]":[http:configuration#Sesja]: "Sesja .[prism-token prism-comment]"<br> +"tracy .[prism-token prism-atrule]":[tracy:configuring#Nette Framework]: "Debugger Tracy .[prism-token prism-comment]" </pre> -Aby napisać ciąg znaków zawierający znak `%`, musíte jej escapovat zdvojením na `%%`. .[note] +.[note] +Aby zapisać ciąg znaków zawierający znak `%`, należy go escapować, podwajając go do `%%`. {{leftbar: @menu-topics}} diff --git a/nette/pl/glossary.texy b/nette/pl/glossary.texy index 3258359187..37730d36f0 100644 --- a/nette/pl/glossary.texy +++ b/nette/pl/glossary.texy @@ -2,154 +2,158 @@ Słowniczek pojęć **************** -AJAX .[#toc-ajax] ------------------ -Asynchroniczny JavaScript i XML - technologia wymiany informacji między klientem a serwerem za pośrednictwem protokołu HTTP bez konieczności przeładowywania całej strony przy każdym żądaniu. Choć nazwa może sugerować, że wysyła on tylko dane w formacie XML, powszechnie stosowany jest również format [JSON |#JSON]. +AJAX +---- +Asynchronous JavaScript and XML - technologia wymiany informacji między klientem a serwerem za pomocą protokołu HTTP bez konieczności ponownego ładowania całej strony przy każdym żądaniu. Chociaż nazwa mogłaby sugerować, że dane są wysyłane tylko w formacie XML, powszechnie używany jest również [format JSON |#JSON]. -Akcja prezentera .[#toc-presenter-action] ------------------------------------------ -Logiczna część [prezentera |#presenter], wykonująca jedną akcję, np. pokazanie strony produktu, wypisanie użytkownika itp. Jeden prezenter może mieć więcej akcji. +Akcja presentera +---------------- +Logiczna część presentera, która wykonuje jedną akcję. Na przykład wyświetla stronę produktu, wylogowuje użytkownika itp. Jeden presenter może mieć wiele akcji. BOM --- -Tak zwany *znak kolejności bajtów* to specjalny pierwszy znak w pliku, który jest używany jako wskaźnik kolejności bajtów w kodowaniu. Niektórzy redaktorzy wstawiają je do plików. Jest praktycznie niewidoczny, ale powoduje problemy z wysyłaniem danych wyjściowych i nagłówków z PHP. Możesz użyć [Code Checker |code-checker:], aby usunąć go masowo. +Tzw. *byte order mark* to specjalny pierwszy znak w pliku, który jest używany jako wskaźnik kolejności bajtów w kodowaniu. Niektóre edytory wstawiają go do plików. Jest praktycznie niewidoczny, ale powoduje problemy z wysyłaniem danych wyjściowych i nagłówków z PHP. Do masowego usuwania można użyć [Code Checker|code-checker:]. -Kontroler .[#toc-controller] ----------------------------- -Kontroler, który przetwarza żądania użytkownika, a następnie wywołuje odpowiednią logikę aplikacji (tj. [Model |#Model]) na podstawie żądań, a następnie prosi [widok |#View] o renderowanie danych. [Prezentery |#Presenter] są podobne do kontrolerów w Nette Framework. +Controller +---------- +Kontroler, który przetwarza żądania użytkownika i na ich podstawie wywołuje odpowiednią logikę aplikacji (tj. [#model]), a następnie prosi [#view] o wyrenderowanie danych. Odpowiednikiem kontrolerów w Nette Framework są [presentery |#Presenter]. -Cross-Site Scripting (XSS) .[#toc-cross-site-scripting-xss] ------------------------------------------------------------ -Cross-Site Scripting to metoda włamania na stronę internetową poprzez wykorzystanie nieobsługiwanych danych wyjściowych. Napastnik może wtedy wcisnąć na stronę swój własny kod, co pozwoli mu na modyfikację strony lub nawet uzyskanie poufnych danych odwiedzających. Przed XSS można się bronić tylko poprzez konsekwentne i poprawne traktowanie wszystkich ciągów znaków. +Cross-Site Scripting (XSS) +-------------------------- +Cross-Site Scripting to metoda naruszania stron internetowych wykorzystująca nieprzetworzone dane wyjściowe. Atakujący może wstrzyknąć do strony swój własny kod, co pozwala mu zmodyfikować stronę lub nawet uzyskać poufne dane o odwiedzających. Przed XSS można się bronić tylko poprzez konsekwentne i poprawne przetwarzanie (escapowanie) wszystkich ciągów znaków. -Nette Framework wyposażony jest w rewolucyjną technologię [Context-Aware Escaping |latte:safety-first#Context-Aware-Escaping], która na zawsze eliminuje ryzyko związane z Cross-Site Scripting. Traktuje wszystkie wyjścia automatycznie, więc nie może się zdarzyć, że koder o czymś zapomni. +Nette Framework wprowadza rewolucyjną technologię [Context-Aware Escaping |latte:safety-first#Escapowanie kontekstowe], która na zawsze uwolni Cię od ryzyka Cross-Site Scriptingu. Wszystkie dane wyjściowe są przetwarzane automatycznie, więc nie może się zdarzyć, że koder o czymś zapomni. -Cross-Site Request Forgery (CSRF) .[#toc-cross-site-request-forgery-csrf] -------------------------------------------------------------------------- -Atak Cross-Site Request Forgery polega na tym, że atakujący zwabia ofiarę na stronę, która w subtelny sposób wykonuje w przeglądarce ofiary żądanie do serwera, na którym ofiara jest zalogowana, a serwer zakłada, że żądanie zostało wykonane przez ofiarę z własnej woli. Wykonuje więc akcję pod tożsamością ofiary bez jej wiedzy. Może to być zmiana lub usunięcie danych, wysłanie wiadomości itp. +Cross-Site Request Forgery (CSRF) +--------------------------------- +Atak Cross-Site Request Forgery polega na tym, że atakujący zwabia ofiarę na stronę, która niepostrzeżenie w przeglądarce ofiary wykonuje żądanie do serwera, na którym ofiara jest zalogowana, a serwer zakłada, że żądanie zostało wykonane przez ofiarę z własnej woli. W ten sposób pod tożsamością ofiary wykonuje określoną czynność, o której ofiara nie wie. Może to być zmiana lub usunięcie danych, wysłanie wiadomości itp. -Nette Framework **automatycznie chroni formularze i sygnały w presenterech** przed tego typu atakami. Robi to, uniemożliwiając ich wysyłanie lub wywoływanie z innej domeny. +Nette Framework **automatycznie chroni formularze i sygnały w prezenterach** przed tego typu atakami. Robi to, uniemożliwiając ich wysłanie lub wywołanie z innej domeny. -Wstrzykiwanie zależności (Dependency Injection) .[#toc-dependency-injection] ----------------------------------------------------------------------------- -Dependency Injection (DI) jest wzorcem projektowym, który mówi, jak oddzielić tworzenie obiektów od ich zależności. Oznacza to, że klasa nie jest odpowiedzialna za tworzenie lub inicjalizację swoich zależności, ale zamiast tego te zależności są dostarczane przez zewnętrzny kod (który może zawierać [kontener DI |#Dependency Injection container]). Zaletą jest to, że pozwala na większą elastyczność kodu, lepszą czytelność i łatwiejsze testowanie aplikacji, ponieważ zależności są łatwo zastępowalne i izolowane od innych części kodu. Aby uzyskać więcej informacji, zobacz [Co to jest Dependency Injection? |dependency-injection:introduction] +Dependency Injection +-------------------- +Dependency Injection (DI) to wzorzec projektowy, który określa, jak oddzielić tworzenie obiektów od ich zależności. Oznacza to, że klasa nie jest odpowiedzialna za tworzenie ani inicjalizację swoich zależności, ale zamiast tego zależności te są dostarczane przez kod zewnętrzny (może to być również [kontener DI |#Kontener Dependency Injection]). Zaletą jest to, że pozwala na większą elastyczność kodu, lepszą czytelność i łatwiejsze testowanie aplikacji, ponieważ zależności są łatwo zastępowalne i izolowane od pozostałych części kodu. Więcej w rozdziale [Co to jest Dependency Injection? |dependency-injection:introduction] -Kontener Dependency Injection .[#toc-dependency-injection-container] --------------------------------------------------------------------- -Kontener Dependency Injection (także kontener DI lub kontener IoC) jest narzędziem, które obsługuje tworzenie i zarządzanie zależnościami w aplikacji (lub [usługach |#service]). Kontener zazwyczaj posiada konfigurację, która określa, jakie klasy są zależne od innych klas, jakich konkretnych implementacji zależności używać oraz jak tworzyć te zależności. Następnie kontener tworzy te obiekty i dostarcza je do klas, które ich potrzebują. Aby uzyskać więcej informacji, zobacz [Czym jest kontener DI? |dependency-injection:container] +Kontener Dependency Injection +----------------------------- +Kontener Dependency Injection (także kontener DI lub kontener IoC) to narzędzie, które zajmuje się tworzeniem i zarządzaniem zależnościami w aplikacji (czyli [usługami |#Usługa Serwis]). Kontener zazwyczaj ma konfigurację, która definiuje, które klasy są zależne od innych klas, jakie konkretne implementacje zależności mają być użyte i jak te zależności mają być tworzone. Następnie kontener tworzy te obiekty i dostarcza je klasom, które ich potrzebują. Więcej w rozdziale [Co to jest kontener DI? |dependency-injection:container] -Ucieczka z .[#toc-escaping] ---------------------------- -Escaping to zamiana znaków, które mają specjalne znaczenie w danym kontekście na inne pasujące sekwencje. Przykład: chcemy umieścić cudzysłów w ciągu ograniczonym cudzysłowem. Ponieważ znaki cudzysłowu mają specjalne znaczenie w kontekście łańcucha i po prostu napisanie ich byłoby postrzegane jako zakończenie łańcucha, muszą być napisane w innej pasującej sekwencji. Dokładnie, który z nich jest określony przez reguły kontekstowe. +Escapowanie +----------- +Escapowanie to konwersja znaków mających w danym kontekście specjalne znaczenie na inne odpowiadające sekwencje. Przykład: do ciągu ograniczonego cudzysłowami chcemy zapisać cudzysłów. Ponieważ cudzysłów ma w kontekście ciągu specjalne znaczenie i jego proste zapisanie byłoby rozumiane jako zakończenie ciągu, należy go zapisać inną odpowiadającą sekwencją. Jaką dokładnie, określają reguły kontekstu. -Filtr (dawniej helper) .[#toc-filter-formerly-helper] ------------------------------------------------------ -W szablonach termin [filtr |latte:syntax#Filters] zwykle odnosi się do funkcji, która pomaga edytować lub przeformatować dane do ostatecznej postaci. Szablony posiadają kilka [standardowych filtrów |latte:filters]. +Filtr (wcześniej helper) +------------------------ +W szablonach pod pojęciem [filtr |latte:syntax#Filtry] zwykle rozumie się funkcję, która pomaga zmodyfikować lub przeformatować dane do ostatecznej postaci. Szablony dysponują kilkoma [standardowymi filtrami |latte:filters]. -Inwalidyzacja .[#toc-invalidation] ----------------------------------- -Powiadom o tym, że [wycinek |#Snippet] ma zostać przerysowany. W innym sensie również usuwanie zawartości pamięci podręcznej. +Unieważnienie (Invalidation) +---------------------------- +Powiadomienie [snippetu |#Snippet], aby się przerysował. W innym znaczeniu także usunięcie zawartości cache. -JSON .[#toc-json] ------------------ -Format wymiany danych oparty na (podzbiorze) składni JavaScript. Dokładna specyfikacja znajduje się na stronie www.json.org. +JSON +---- +Format wymiany danych oparty na składni JavaScript (jest jej podzbiorem). Dokładną specyfikację znajdziesz na stronie www.json.org. -Składnik .[#toc-component] --------------------------- -Składnik aplikacji wielokrotnego użytku. Może to być wizualna część strony, jak opisano w rozdziale [Pisanie komponentów |application:components], lub komponent może być również klasą [Component |component-model:] (taki komponent nie musi być wizualny). +Komponent +--------- +Reużywalna część aplikacji. Może to być wizualna część strony, jak opisuje rozdział [Pisanie komponentów |application:components], lub pod pojęciem komponent rozumie się także klasę [Component |component-model:] (taki komponent nie musi być wizualny). -Znaki kontrolne .[#toc-control-characters] ------------------------------------------- -Znaki sterujące to niewidoczne znaki, które mogą pojawić się w tekście i ewentualnie powodować problemy. Możesz użyć [Code Checker |code-checker:] do masowego usuwania ich z plików oraz [Strings::normalize() |utils:strings#normalize] do usuwania ich ze zmiennej. +Znaki kontrolne +--------------- +Znaki kontrolne to niewidoczne znaki, które mogą występować w tekście i ewentualnie powodować problemy. Do ich masowego usuwania z plików możesz użyć [Code Checker|code-checker:], a do usuwania ze zmiennej funkcji [Strings::normalize() |utils:strings#normalize]. -Wydarzenia .[#toc-events] -------------------------- -Zdarzenie to oczekiwana sytuacja w obiekcie, która po zaistnieniu wywołuje tzw. handlery, czyli callbacki reagujące na zdarzenie ("próbkę":https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38). Zdarzeniem może być np. przesłanie formularza, zalogowanie się użytkownika itp. Zdarzenia są więc formą *Inversion of Control*. +Eventy (zdarzenia) +------------------ +Zdarzenie to oczekiwana sytuacja w obiekcie, która gdy nastąpi, wywoływane są tzw. handlery, czyli callbacki reagujące na zdarzenie ("przykład":https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38). Zdarzeniem może być np. wysłanie formularza, zalogowanie użytkownika itp. Zdarzenia są więc formą *Inversion of Control*. -Przykładowo, logowanie użytkownika następuje w metodzie `Nette\Security\User::login()`. Obiekt `User` ma publiczną zmienną `$onLoggedIn`, która jest tablicą, do której każdy może dodać callback. W momencie, gdy użytkownik się zaloguje, metoda `login()` wywołuje wszystkie callbacki w tablicy. Nazwa zmiennej o postaci `onXyz` jest konwencją stosowaną w całym Nette. +Na przykład do zalogowania użytkownika dochodzi w metodzie `Nette\Security\User::login()`. Obiekt `User` ma publiczną zmienną `$onLoggedIn`, która jest tablicą, do której każdy może dodać callback. W momencie, gdy użytkownik się zaloguje, metoda `login()` wywołuje wszystkie callbacki w tablicy. Nazwa zmiennej w formacie `onXyz` jest konwencją używaną w całym Nette. -Latte .[#toc-latte] -------------------- -Jeden z najbardziej zaawansowanych [systemów templatkowania |latte:]. +Latte +----- +Jeden z najbardziej zaawansowanych [systemów szablonów |latte:]. -Model .[#toc-model] -------------------- -Model to dane, a zwłaszcza podstawa funkcjonalna całej aplikacji. Zawiera ona całą logikę aplikacji (używa się również terminu logika biznesowa). Jest to **M** z **M**VC lub MVP. Każda akcja użytkownika (zalogowanie się, dodanie przedmiotu do koszyka, zmiana wartości w bazie danych) jest akcją modelu. +Model +----- +Model to dane i przede wszystkim funkcjonalna podstawa całej aplikacji. Zawiera całą logikę aplikacji (używa się również terminu logika biznesowa). Jest to **M** z **M**VC lub MVP. Każda akcja użytkownika (logowanie, dodanie towaru do koszyka, zmiana wartości w bazie danych) stanowi akcję modelu. -Model zarządza swoim wewnętrznym stanem i oferuje stały interfejs na zewnątrz. Wywołując funkcje tego interfejsu możemy dowiedzieć się lub zmienić jego stan. Model nie wie o istnieniu [widoku |#View] ani [kontrolera |#Controller]. +Model zarządza swoim wewnętrznym stanem i na zewnątrz oferuje ściśle określony interfejs. Wywołując funkcje tego interfejsu, możemy sprawdzać lub zmieniać jego stan. Model nie wie o istnieniu [#view] lub [kontrolera |#Controller]. -Model-View-Controller .[#toc-model-view-controller] ---------------------------------------------------- -Architektura oprogramowania, która powstała z potrzeby oddzielenia kodu operatora ([kontrolera |#Controller]) od kodu logiki aplikacji ([modelu |#Model]) oraz od kodu wyświetlającego dane ([widoku |#View]) w aplikacjach GUI. Dzięki temu aplikacja jest bardziej przejrzysta, ułatwia przyszły rozwój i umożliwia testowanie każdej części osobno. +Model-View-Controller (MVC) +--------------------------- +Architektura oprogramowania, która powstała z potrzeby oddzielenia w aplikacjach z interfejsem graficznym kodu obsługi ([#controller]) od kodu logiki aplikacji ([#model]) i od kodu wyświetlającego dane ([#view]). Dzięki temu aplikacja staje się bardziej przejrzysta, ułatwia przyszły rozwój i umożliwia testowanie poszczególnych części osobno. -Model-Widok-Prezenter .[#toc-model-view-presenter] --------------------------------------------------- -Architektura oparta na [Model-View-Controller |#Model-View-Controller]. +Model-View-Presenter (MVP) +-------------------------- +Architektura oparta na [#Model-View-Controller MVC]. -Moduł .[#toc-module] --------------------- -[Ten moduł |application:modules] to pakiet Nette Framework zawierający prezentery i szablony, czyli komponenty i modele dostarczające dane do prezentera. Jest to więc pewna logiczna część aplikacji. +Moduł +----- +Moduł reprezentuje logiczną część aplikacji. W typowym układzie jest to grupa prezenterów i szablonów, które obsługują określoną dziedzinę funkcjonalności. Moduły umieszczamy w [osobnych katalogach |application:directory-structure#Presentery i szablony], takich jak np. `Front/`, `Admin/` lub `Shop/`. -Przykładowo, sklep internetowy może posiadać trzy moduły: -1) katalog produktów z koszykiem -2) administracja dla klienta -3) administracja dla operatora +Na przykład e-sklep dzielimy na: +- Frontend (`Shop/`) do przeglądania produktów i zakupów +- Sekcję klienta (`Customer/`) do zarządzania zamówieniami +- Administrację (`Admin/`) dla operatora +Technicznie są to zwykłe katalogi, które jednak dzięki przejrzystemu podziałowi pomagają skalować aplikację. Prezenter `Admin:Product:List` będzie fizycznie umieszczony na przykład w katalogu `app/Presentation/Admin/Product/List/` (zobacz [mapowanie prezenterów |application:directory-structure#Mapowanie presenterów]). -Przestrzeń nazw .[#toc-namespace] ---------------------------------- -Przestrzeń nazw, część języka PHP od wersji 5.3 i niektórych innych języków programowania, która pozwala na używanie klas o tej samej nazwie w różnych bibliotekach bez kolizji nazw. Zobacz [dokumentację PHP |https://www.php.net/manual/en/language.namespaces.rationale.php]. +Namespace +--------- +Przestrzeń nazw, część języka PHP od wersji 5.3 i niektórych innych języków programowania, umożliwiająca używanie klas, które w różnych bibliotekach nazywają się tak samo, bez konfliktu nazw. Zobacz [dokumentację PHP |https://www.php.net/manual/en/language.namespaces.rationale.php]. + + +Presenter +--------- +Presenter to obiekt, który przyjmuje [żądanie |api:Nette\Application\Request] przetłumaczone przez router z żądania HTTP i generuje [odpowiedź |api:Nette\Application\Response]. Odpowiedzią może być strona HTML, obrazek, dokument XML, plik na dysku, JSON, przekierowanie lub cokolwiek wymyślisz. + +Zwykle pod pojęciem presenter rozumie się potomka klasy [api:Nette\Application\UI\Presenter]. Zgodnie z przychodzącymi żądaniami uruchamia odpowiednie [akcje |application:presenters#Cykl życia presentera] i renderuje szablony. -Prezenter .[#toc-presenter] ---------------------------- -Prezenter to obiekt, który przyjmuje [żądanie |api:Nette\Application\Request] przetłumaczone przez router z żądania HTTP i generuje [odpowiedź |api:Nette\Application\Response]. Odpowiedzią może być strona HTML, obraz, dokument XML, plik na dysku, JSON, przekierowanie lub cokolwiek, co można wymyślić. -Zazwyczaj termin prezenter odnosi się do potomka klasy [api:Nette\Application\UI\Presenter]. Wywołuje on odpowiednie [akcje |application:presenters#Life-Cycle-of-Presenter] i renderuje szablony na podstawie przychodzących żądań. +Router +------ +Dwukierunkowy tłumacz między żądaniem HTTP / URL a akcją presentera. Dwukierunkowość oznacza, że z żądania HTTP można wywnioskować [akcję presentera |#Akcja presentera], ale także odwrotnie, do akcji wygenerować odpowiedni URL. Więcej w rozdziale o [routingu URL |application:routing]. -Router .[#toc-router] ---------------------- -Dwukierunkowy resolver pomiędzy żądaniem HTTP/URL a akcją prezentera. Dwukierunkowość oznacza, że można wyprowadzić [akcję prezentera |#Presenter-Action] z żądania HTTP, ale także odwrócić akcję, aby wygenerować odpowiedni adres URL. Więcej informacji na ten temat znajduje się w rozdziale dotyczącym [routingu URL |application:routing]. +SameSite cookie +--------------- +SameSite cookies zapewniają mechanizm rozpoznawania, co doprowadziło do załadowania strony. Może mieć trzy wartości: `Lax`, `Strict` i `None` (ten wymaga HTTPS). Jeśli żądanie strony pochodzi bezpośrednio ze strony internetowej lub użytkownik otwiera stronę, wpisując ją bezpośrednio w pasku adresu lub klikając zakładkę, przeglądarka wysyła do serwera wszystkie pliki cookie (czyli z flagami `Lax`, `Strict` i `None`). Jeśli użytkownik przejdzie na stronę przez link z innej strony, do serwera zostaną przekazane pliki cookie z flagami `Lax` i `None`. Jeśli żądanie powstanie w inny sposób, np. przez wysłanie formularza POST z innej strony, załadowanie wewnątrz iframe, za pomocą JavaScriptu itp., wysłane zostaną tylko pliki cookie z flagą `None`. -SameSite Cookie .[#toc-samesite-cookie] ---------------------------------------- -Ciasteczka SameSite zapewniają mechanizm rozpoznawania, co doprowadziło do załadowania strony. Może mieć trzy wartości: `Lax`, `Strict` oraz `None` (ta ostatnia wymaga protokołu HTTPS). Jeśli żądanie do strony pochodzi bezpośrednio z serwisu lub użytkownik otwiera stronę wpisując ją bezpośrednio w pasku adresu lub klikając zakładkę, przeglądarka wysyła wszystkie ciasteczka do serwera (czyli z flagami `Lax`, `Strict` i `None`). Jeśli użytkownik kliknie na stronę poprzez link z innej strony, do serwera przekazywane są pliki cookie z flagami `Lax` i `None`. Jeśli żądanie odbywa się w inny sposób, np. poprzez przesłanie formularza POST z innej witryny, załadowanie wewnątrz ramki iframe, użycie JavaScript itp. wysyłane są tylko pliki cookie z flagą `None`. +Usługa / Serwis +--------------- +W kontekście Dependency Injection jako usługa określa się obiekt, który jest tworzony i zarządzany przez kontener DI. Usługa może być łatwo zastąpiona inną implementacją, na przykład w celach testowania lub zmiany zachowania aplikacji, bez konieczności modyfikowania kodu, który używa usługi. -Serwis .[#toc-service] ----------------------- -W kontekście Dependency Injection, usługa odnosi się do obiektu, który jest tworzony i zarządzany przez kontener DI. Usługę można łatwo zastąpić inną implementacją, na przykład w celach testowych lub w celu zmiany zachowania aplikacji, bez konieczności modyfikowania kodu, który korzysta z usługi. +Snippet +------- +Wycinek, część strony, którą można osobno przerysować podczas żądania AJAX. -Snippet .[#toc-snippet] ------------------------ -Snippet, czyli fragment strony, który może być przerysowany niezależnie podczas żądania AJAX. +View +---- +View, czyli widok, to warstwa aplikacji odpowiedzialna za wyświetlenie wyniku żądania. Zwykle używa systemu szablonów i wie, jak wyświetlić dany komponent lub wynik uzyskany z modelu. -Zobacz .[#toc-view] -------------------- -Widok to warstwa aplikacji, która odpowiada za wyświetlenie wyniku żądania. Zwykle korzysta z systemu szablonów i wie, jak wyświetlić, który komponent lub wynik uzyskany z modelu. diff --git a/nette/pl/installation.texy b/nette/pl/installation.texy index 6655f61a7f..48f5ff19ac 100644 --- a/nette/pl/installation.texy +++ b/nette/pl/installation.texy @@ -2,66 +2,66 @@ Instalacja Nette **************** .[perex] -Czy chcesz wykorzystać zalety Nette w swoim istniejącym projekcie lub planujesz stworzyć nowy projekt oparty na Nette? Ten przewodnik przeprowadzi Cię przez instalację krok po kroku. +Chcesz wykorzystać zalety Nette w swoim istniejącym projekcie, czy planujesz stworzyć nowy projekt oparty na Nette? Ten przewodnik przeprowadzi Cię przez instalację krok po kroku. -Jak dodać Nette do swojego projektu .[#toc-how-to-add-nette-to-your-project] ----------------------------------------------------------------------------- +Jak dodać Nette do swojego projektu +----------------------------------- -Nette oferuje kolekcję przydatnych i zaawansowanych pakietów (bibliotek) dla PHP. Aby włączyć je do swojego projektu, wykonaj następujące kroki: +Nette oferuje kolekcję użytecznych i zaawansowanych pakietów (bibliotek) dla PHP. Aby włączyć je do swojego projektu, postępuj zgodnie z poniższymi krokami: -1) **Skonfiguruj [Composer |best-practices:composer]:** To narzędzie jest niezbędne do łatwej instalacji, aktualizacji i zarządzania bibliotekami wymaganymi dla twojego projektu. +1) **Przygotuj [Composer|best-practices:composer]:** To narzędzie jest niezbędne do łatwej instalacji, aktualizacji i zarządzania bibliotekami potrzebnymi dla Twojego projektu. -2) **Wybierz [pakiet |www:packages]:** Powiedzmy, że potrzebujesz nawigować po systemie plików, co [Finder |utils:finder] z pakietu `nette/utils` robi doskonale. Nazwę pakietu można znaleźć w prawej kolumnie jego dokumentacji. +2) **Wybierz [pakiet|www:packages]:** Załóżmy, że potrzebujesz przeglądać system plików, co świetnie robi [Finder|utils:finder] z pakietu `nette/utils`. Nazwę pakietu widzisz w prawej kolumnie jego dokumentacji. -3) **Zainstaluj pakiet:** Uruchom to polecenie w katalogu głównym projektu: +3) **Zainstaluj pakiet:** Uruchom to polecenie w głównym katalogu swojego projektu: ```shell composer require nette/utils ``` -Wolisz interfejs graficzny? Zapoznaj się z [przewodnikiem |https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] dotyczącym instalacji pakietów w środowisku PhpStrom. +Preferujesz interfejs graficzny? Zapoznaj się z [instrukcją|https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] instalacji pakietów w środowisku PhpStorm. -Jak rozpocząć nowy projekt z Nette .[#toc-how-to-start-a-new-project-with-nette] --------------------------------------------------------------------------------- +Jak założyć nowy projekt z Nette +-------------------------------- -Jeśli chcesz stworzyć zupełnie nowy projekt na platformie Nette, zalecamy skorzystanie z gotowego szkieletu [Web Project |https://github.com/nette/web-project]: +Jeśli chcesz stworzyć zupełnie nowy projekt na platformie Nette, zalecamy skorzystanie z gotowego szkieletu [Web Project|https://github.com/nette/web-project]: -1) **Set up [Composer |best-practices:composer].**. +1) **Przygotuj [Composer|best-practices:composer].** -2) **Otworzyć wiersz poleceń** i przejść do katalogu głównego serwera WWW, np. `/etc/var/www`, `C:/xampp/htdocs`, `/Library/WebServer/Documents`. +2) **Otwórz wiersz poleceń** i przejdź do głównego katalogu swojego serwera WWW, np. `/var/www/html`, `C:/xampp/htdocs`, `/Library/WebServer/Documents`. 3) **Utwórz projekt** za pomocą tego polecenia: ```shell -composer create-project nette/web-project PROJECT_NAME +composer create-project nette/web-project NAZWA_PROJEKTU ``` -4) **Nie korzystasz z Composera?** Po prostu pobierz [Web Project w formacie ZIP |https://github.com/nette/web-project/archive/preloaded.zip] i rozpakuj go. Ale zaufaj nam, Composer jest tego wart! +4) **Nie używasz Composera?** Wystarczy pobrać [Web Project w formacie ZIP|https://github.com/nette/web-project/archive/preloaded.zip] i rozpakować go. Ale uwierz, Composer jest tego wart! -5) **Ustawianie uprawnień:** W systemach macOS lub Linux należy ustawić [uprawnienia zapisu |nette:troubleshooting#Setting directory permissions] dla katalogów. +5) **Ustawienia uprawnień:** Na systemach macOS lub Linux ustaw [prawa zapisu |nette:troubleshooting#Ustawianie uprawnień do katalogów] do katalogów `temp/` i `log/`. -6) **Otwórz projekt w przeglądarce:** Wpisz adres URL `http://localhost/PROJECT_NAME/www/`. Zobaczysz stronę docelową szkieletu: +6) **Otwarcie projektu w przeglądarce:** Wpisz URL `http://localhost/NAZWA_PROJEKTU/www/` i zobaczysz stronę powitalną szkieletu: -[* qs-welcome.webp .{url: http://localhost/PROJECT_NAME/www/} *] +[* qs-welcome.webp .{url: http://localhost/NAZWA_PROJEKTU/www/} *] -Gratulacje! Twoja witryna jest teraz gotowa do rozwoju. Możesz usunąć szablon powitalny i rozpocząć tworzenie aplikacji. +Gratulacje! Twoja strona jest teraz gotowa do rozwoju. Możesz usunąć szablon powitalny i zacząć tworzyć swoją aplikację. -Jedną z zalet Nette jest to, że projekt działa natychmiast, bez potrzeby konfiguracji. Jeśli jednak napotkasz jakiekolwiek problemy, rozważ zapoznanie się z [typowymi rozwiązaniami problemów |nette:troubleshooting#nette-is-not-working-white-page-is-displayed]. +Jedną z zalet Nette jest to, że projekt działa od razu bez potrzeby konfiguracji. Jeśli jednak napotkasz problemy, spróbuj zajrzeć do [rozwiązań częstych problemów |nette:troubleshooting#Nette nie działa wyświetla się biała strona]. .[note] -Jeśli dopiero zaczynasz pracę z Nette, zalecamy kontynuowanie [samouczka Create Your First Application |quickstart:]. +Jeśli zaczynasz z Nette, zalecamy kontynuowanie [tutorialem Pisanie pierwszej aplikacji|quickstart:]. -Narzędzia i rekomendacje .[#toc-tools-and-recommendations] ----------------------------------------------------------- +Narzędzia i zalecenia +--------------------- -Do wydajnej pracy z Nette zalecamy następujące narzędzia: +Do efektywnej pracy z Nette polecamy następujące narzędzia: -- [Wysokiej jakości IDE z wtyczkami dla Net |best-practices:editors-and-tools]te +- [Dobrej jakości IDE z dodatkami dla Nette|best-practices:editors-and-tools] - System kontroli wersji Git -- [Composer |best-practices:composer] +- [Composer|best-practices:composer] {{leftbar: www:@menu-common}} diff --git a/nette/pl/introduction-to-object-oriented-programming.texy b/nette/pl/introduction-to-object-oriented-programming.texy new file mode 100644 index 0000000000..b901db9c96 --- /dev/null +++ b/nette/pl/introduction-to-object-oriented-programming.texy @@ -0,0 +1,841 @@ +Wprowadzenie do programowania obiektowego +***************************************** + +.[perex] +Termin "OOP" oznacza programowanie obiektowe, które jest sposobem organizacji i strukturyzacji kodu. OOP pozwala nam postrzegać program jako zbiór obiektów, które komunikują się ze sobą, zamiast sekwencji poleceń i funkcji. + +W OOP "obiekt" to jednostka, która zawiera dane i funkcje, które operują na tych danych. Obiekty są tworzone na podstawie "klas", które możemy rozumieć jako plany lub szablony dla obiektów. Kiedy mamy klasę, możemy utworzyć jej "instancję", czyli konkretny obiekt stworzony na podstawie tej klasy. + +Pokażmy, jak możemy stworzyć prostą klasę w PHP. Podczas definiowania klasy użyjemy słowa kluczowego "class", następnie nazwy klasy, a potem nawiasów klamrowych, które otaczają funkcje (nazywane "metodami") i zmienne klasy (nazywane "właściwościami" lub po angielsku "property"): + +```php +class Samochod +{ + function zatrab() + { + echo 'Bip bip!'; + } +} +``` + +W tym przykładzie stworzyliśmy klasę o nazwie `Samochod` z jedną funkcją (lub "metodą") o nazwie `zatrab`. + +Każda klasa powinna rozwiązywać tylko jedno główne zadanie. Jeśli klasa robi zbyt wiele rzeczy, może być wskazane podzielenie jej na mniejsze, wyspecjalizowane klasy. + +Klasy zazwyczaj przechowujemy w osobnych plikach, aby kod był zorganizowany i łatwo się w nim orientować. Nazwa pliku powinna odpowiadać nazwie klasy, więc dla klasy `Samochod` nazwa pliku byłaby `Samochod.php`. + +Podczas nazywania klas dobrze jest trzymać się konwencji "PascalCase", co oznacza, że każde słowo w nazwie zaczyna się wielką literą i nie ma między nimi żadnych podkreśleń ani innych separatorów. Metody i właściwości używają konwencji "camelCase", co oznacza, że zaczynają się małą literą. + +Niektóre metody w PHP mają specjalne zadania i są oznaczone prefiksem `__` (dwa podkreślenia). Jedną z najważniejszych specjalnych metod jest "konstruktor", który jest oznaczony jako `__construct`. Konstruktor to metoda, która jest automatycznie wywoływana, gdy tworzysz nową instancję klasy. + +Konstruktor często używamy do ustawienia początkowego stanu obiektu. Na przykład, tworząc obiekt reprezentujący osobę, możesz wykorzystać konstruktor do ustawienia jej wieku, imienia lub innych właściwości. + +Pokażmy, jak użyć konstruktora w PHP: + +```php +class Osoba +{ + private $wiek; + + function __construct($wiek) + { + $this->wiek = $wiek; + } + + function ileMaszLat() + { + return $this->wiek; + } +} + +$osoba = new Osoba(25); +echo $osoba->ileMaszLat(); // Wyświetli: 25 +``` + +W tym przykładzie klasa `Osoba` ma właściwość (zmienną) `$wiek` oraz konstruktor, który ustawia tę właściwość. Metoda `ileMaszLat()` następnie umożliwia dostęp do wieku osoby. + +Pseudozmienna `$this` jest używana wewnątrz klasy do uzyskania dostępu do właściwości i metod obiektu. + +Słowo kluczowe `new` jest używane do tworzenia nowej instancji klasy. W powyższym przykładzie stworzyliśmy nową osobę w wieku 25 lat. + +Możesz również ustawić wartości domyślne dla parametrów konstruktora, jeśli nie są one określone podczas tworzenia obiektu. Na przykład: + +```php +class Osoba +{ + private $wiek; + + function __construct($wiek = 20) + { + $this->wiek = $wiek; + } + + function ileMaszLat() + { + return $this->wiek; + } +} + +$osoba = new Osoba; // jeśli nie przekazujemy żadnego argumentu, nawiasy można pominąć +echo $osoba->ileMaszLat(); // Wyświetli: 20 +``` + +W tym przykładzie, jeśli nie podasz wieku podczas tworzenia obiektu `Osoba`, zostanie użyta wartość domyślna 20. + +Przyjemne jest to, że definicję właściwości wraz z jej inicjalizacją przez konstruktor można skrócić i uprościć w ten sposób: + +```php +class Osoba +{ + function __construct( + private $wiek = 20, + ) { + } +} +``` + +Dla kompletności, oprócz konstruktorów obiekty mogą mieć również destruktory (metoda `__destruct`), które są wywoływane przed zwolnieniem obiektu z pamięci. + + +Przestrzenie nazw +----------------- + +Przestrzenie nazw (lub "namespaces" po angielsku) pozwalają nam organizować i grupować powiązane klasy, funkcje i stałe, jednocześnie unikając konfliktów nazw. Możesz je sobie wyobrazić jako foldery na komputerze, gdzie każdy folder zawiera pliki należące do określonego projektu lub tematu. + +Przestrzenie nazw są szczególnie przydatne w większych projektach lub gdy używasz bibliotek stron trzecich, gdzie mogą wystąpić konflikty nazw klas. + +Wyobraź sobie, że masz klasę o nazwie `Samochod` w swoim projekcie i chcesz ją umieścić w przestrzeni nazw o nazwie `Transport`. Zrobisz to w ten sposób: + +```php +namespace Transport; + +class Samochod +{ + function zatrab() + { + echo 'Bip bip!'; + } +} +``` + +Jeśli chcesz użyć klasy `Samochod` w innym pliku, musisz określić, z jakiej przestrzeni nazw pochodzi klasa: + +```php +$auto = new Transport\Samochod; +``` + +Dla uproszczenia możesz na początku pliku określić, której klasy z danej przestrzeni nazw chcesz używać, co pozwala tworzyć instancje bez konieczności podawania całej ścieżki: + +```php +use Transport\Samochod; + +$auto = new Samochod; +``` + + +Dziedziczenie +------------- + +Dziedziczenie jest narzędziem programowania obiektowego, które pozwala tworzyć nowe klasy na podstawie już istniejących klas, przejmować ich właściwości i metody oraz rozszerzać lub przedefiniowywać je według potrzeb. Dziedziczenie pozwala zapewnić ponowne wykorzystanie kodu i hierarchię klas. + +Upraszczając, jeśli mamy jedną klasę i chcielibyśmy stworzyć inną, pochodną od niej, ale z kilkoma zmianami, możemy nową klasę "odziedziczyć" z pierwotnej klasy. + +W PHP dziedziczenie realizujemy za pomocą słowa kluczowego `extends`. + +Nasza klasa `Osoba` przechowuje informację o wieku. Możemy mieć inną klasę `Student`, która rozszerza `Osobę` i dodaje informację o kierunku studiów. + +Spójrzmy na przykład: + +```php +class Osoba +{ + private $wiek; + + function __construct($wiek) + { + $this->wiek = $wiek; + } + + function wypiszInformacje() + { + echo "Wiek: {$this->wiek} lat\n"; + } +} + +class Student extends Osoba +{ + private $kierunek; + + function __construct($wiek, $kierunek) + { + parent::__construct($wiek); + $this->kierunek = $kierunek; + } + + function wypiszInformacje() + { + parent::wypiszInformacje(); + echo "Kierunek studiów: {$this->kierunek} \n"; + } +} + +$student = new Student(20, 'Informatyka'); +$student->wypiszInformacje(); +``` + +Jak działa ten kod? + +- Użyliśmy słowa kluczowego `extends` do rozszerzenia klasy `Osoba`, co oznacza, że klasa `Student` odziedziczy wszystkie metody i właściwości z `Osoby`. + +- Słowo kluczowe `parent::` pozwala nam wywoływać metody z klasy nadrzędnej. W tym przypadku wywołaliśmy konstruktor z klasy `Osoba` przed dodaniem własnej funkcjonalności do klasy `Student`. Podobnie wywołaliśmy metodę `wypiszInformacje()` przodka przed wypisaniem informacji o studencie. + +Dziedziczenie jest przeznaczone dla sytuacji, gdy istnieje relacja "jest" między klasami. Na przykład `Student` jest `Osobą`. Kot jest zwierzęciem. Daje nam to możliwość w przypadkach, gdy w kodzie oczekujemy jednego obiektu (np. "Osoba"), użycia zamiast niego obiektu dziedziczonego (np. "Student"). + +Ważne jest, aby zdać sobie sprawę, że głównym celem dziedziczenia **nie jest** zapobieganie duplikacji kodu. Wręcz przeciwnie, niewłaściwe wykorzystanie dziedziczenia może prowadzić do skomplikowanego i trudnego do utrzymania kodu. Jeśli relacja "jest" między klasami nie istnieje, powinniśmy zamiast dziedziczenia rozważyć kompozycję. + +Zauważ, że metody `wypiszInformacje()` w klasach `Osoba` i `Student` wypisują nieco inne informacje. Możemy dodać kolejne klasy (na przykład `Pracownik`), które będą dostarczać kolejne implementacje tej metody. Zdolność obiektów różnych klas do reagowania na tę samą metodę na różne sposoby nazywa się polimorfizmem: + +```php +$osoby = [ + new Osoba(30), + new Student(20, 'Informatyka'), + new Pracownik(45, 'Dyrektor'), +]; + +foreach ($osoby as $osoba) { + $osoba->wypiszInformacje(); +} +``` + + +Kompozycja +---------- + +Kompozycja to technika, w której zamiast dziedziczyć właściwości i metody innej klasy, po prostu wykorzystujemy jej instancję w naszej klasie. Pozwala to łączyć funkcjonalności i właściwości wielu klas bez konieczności tworzenia złożonych struktur dziedziczenia. + +Spójrzmy na przykład. Mamy klasę `Silnik` i klasę `Samochod`. Zamiast mówić "Samochód jest Silnikiem", mówimy "Samochód ma Silnik", co jest typową relacją kompozycji. + +```php +class Silnik +{ + function wlacz() + { + echo 'Silnik pracuje.'; + } +} + +class Samochod +{ + private $silnik; + + function __construct() + { + $this->silnik = new Silnik; + } + + function start() + { + $this->silnik->wlacz(); + echo 'Samochód jest gotowy do jazdy!'; + } +} + +$samochod = new Samochod; +$samochod->start(); +``` + +Tutaj `Samochod` nie ma wszystkich właściwości i metod `Silnika`, ale ma do niego dostęp za pośrednictwem właściwości `$silnik`. + +Zaletą kompozycji jest większa elastyczność w projektowaniu i lepsza możliwość modyfikacji w przyszłości. + + +Widoczność +---------- + +W PHP możesz zdefiniować "widoczność" dla właściwości, metod i stałych klasy. Widoczność określa, skąd możesz uzyskać dostęp do tych elementów. + +1. **Public:** Jeśli element jest oznaczony jako `public`, oznacza to, że możesz uzyskać do niego dostęp z dowolnego miejsca, nawet spoza klasy. + +2. **Protected:** Element oznaczony jako `protected` jest dostępny tylko w ramach danej klasy i wszystkich jej potomków (klas, które dziedziczą z tej klasy). + +3. **Private:** Jeśli element jest `private`, możesz uzyskać do niego dostęp tylko z wnętrza klasy, w której został zdefiniowany. + +Jeśli nie określisz widoczności, PHP automatycznie ustawi ją na `public`. + +Spójrzmy na przykładowy kod: + +```php +class PrzykladWidocznosci +{ + public $wlasciwoscPubliczna = 'Publiczna'; + protected $wlasciwoscChroniona = 'Chroniona'; + private $wlasciwoscPrywatna = 'Prywatna'; + + public function wypiszWlasciwosci() + { + echo $this->wlasciwoscPubliczna; // Działa + echo $this->wlasciwoscChroniona; // Działa + echo $this->wlasciwoscPrywatna; // Działa + } +} + +$obiekt = new PrzykladWidocznosci; +$obiekt->wypiszWlasciwosci(); +echo $obiekt->wlasciwoscPubliczna; // Działa +// echo $obiekt->wlasciwoscChroniona; // Zgłosi błąd +// echo $obiekt->wlasciwoscPrywatna; // Zgłosi błąd +``` + +Kontynuujemy z dziedziczeniem klasy: + +```php +class PotomekKlasy extends PrzykladWidocznosci +{ + public function wypiszWlasciwosci() + { + echo $this->wlasciwoscPubliczna; // Działa + echo $this->wlasciwoscChroniona; // Działa + // echo $this->wlasciwoscPrywatna; // Zgłosi błąd + } +} +``` + +W tym przypadku metoda `wypiszWlasciwosci()` w klasie `PotomekKlasy` może uzyskać dostęp do publicznych i chronionych właściwości, ale nie może uzyskać dostępu do prywatnych właściwości klasy rodzicielskiej. + +Dane i metody powinny być jak najbardziej ukryte i dostępne tylko za pośrednictwem zdefiniowanego interfejsu. Pozwoli to na zmianę wewnętrznej implementacji klasy bez wpływu na resztę kodu. + + +Słowo kluczowe `final` +---------------------- + +W PHP możemy użyć słowa kluczowego `final`, jeśli chcemy uniemożliwić dziedziczenie lub nadpisywanie klasy, metody lub stałej. Kiedy oznaczymy klasę jako `final`, nie może być ona rozszerzana. Kiedy oznaczymy metodę jako `final`, nie może być ona nadpisana w klasie potomnej. + +Świadomość, że dana klasa lub metoda nie będzie dalej modyfikowana, pozwala nam łatwiej wprowadzać zmiany, nie martwiąc się o możliwe konflikty. Na przykład możemy dodać nową metodę bez obaw, że któryś z jej potomków ma już metodę o tej samej nazwie i doszłoby do kolizji. Lub możemy zmienić parametry metody, ponieważ znów nie ma ryzyka, że spowodujemy niezgodność z nadpisaną metodą w potomku. + +```php +final class KlasaFinalna +{ +} + +// Poniższy kod spowoduje błąd, ponieważ nie możemy dziedziczyć z klasy finalnej. +class PotomekKlasyFinalnej extends KlasaFinalna +{ +} +``` + +W tym przykładzie próba dziedziczenia z finalnej klasy `KlasaFinalna` spowoduje błąd. + + +Statyczne właściwości i metody +------------------------------ + +Kiedy w PHP mówimy o "statycznych" elementach klasy, mamy na myśli metody i właściwości, które należą do samej klasy, a nie do konkretnej instancji tej klasy. Oznacza to, że nie musisz tworzyć instancji klasy, aby mieć do nich dostęp. Zamiast tego wywołujesz je lub uzyskujesz do nich dostęp bezpośrednio przez nazwę klasy. + +Pamiętaj, że ponieważ elementy statyczne należą do klasy, a nie do jej instancji, nie możesz używać pseudozmiennej `$this` wewnątrz metod statycznych. + +Używanie właściwości statycznych prowadzi do [nieprzejrzystego kodu pełnego pułapek|dependency-injection:global-state], dlatego nigdy nie powinieneś ich używać i nawet nie będziemy tutaj pokazywać przykładu użycia. Natomiast metody statyczne są przydatne. Przykład użycia: + +```php +class Kalkulator +{ + public static function dodawanie($a, $b) + { + return $a + $b; + } + + public static function odejmowanie($a, $b) + { + return $a - $b; + } +} + +// Użycie metody statycznej bez tworzenia instancji klasy +echo Kalkulator::dodawanie(5, 3); // Wynik: 8 +echo Kalkulator::odejmowanie(5, 3); // Wynik: 2 +``` + +W tym przykładzie stworzyliśmy klasę `Kalkulator` z dwiema metodami statycznymi. Możemy wywoływać te metody bezpośrednio bez tworzenia instancji klasy za pomocą operatora `::`. Metody statyczne są szczególnie przydatne do operacji, które nie zależą od stanu konkretnej instancji klasy. + + +Stałe klasowe +------------- + +W ramach klas mamy możliwość definiowania stałych. Stałe to wartości, które nigdy się nie zmienią podczas działania programu. W przeciwieństwie do zmiennych, wartość stałej pozostaje zawsze taka sama. + +```php +class Samochod +{ + public const LiczbaKol = 4; + + public function wyswietlLiczbeKol(): int + { + echo self::LiczbaKol; + } +} + +echo Samochod::LiczbaKol; // Wyjście: 4 +``` + +W tym przykładzie mamy klasę `Samochod` ze stałą `LiczbaKol`. Kiedy chcemy uzyskać dostęp do stałej wewnątrz klasy, możemy użyć słowa kluczowego `self` zamiast nazwy klasy. + + +Interfejsy obiektowe +-------------------- + +Interfejsy obiektowe działają jak "kontrakty" dla klas. Jeśli klasa ma implementować interfejs obiektowy, musi zawierać wszystkie metody, które ten interfejs definiuje. Jest to świetny sposób na zapewnienie, że określone klasy przestrzegają tej samej "umowy" lub struktury. + +W PHP interfejs definiuje się słowem kluczowym `interface`. Wszystkie metody zdefiniowane w interfejsie są publiczne (`public`). Kiedy klasa implementuje interfejs, używa słowa kluczowego `implements`. + +```php +interface Zwierze +{ + function wydajDzwiek(); +} + +class Kot implements Zwierze +{ + public function wydajDzwiek() + { + echo 'Miau'; + } +} + +$kot = new Kot; +$kot->wydajDzwiek(); +``` + +Jeśli klasa implementuje interfejs, ale nie są w niej zdefiniowane wszystkie oczekiwane metody, PHP zgłosi błąd. + +Klasa może implementować wiele interfejsów jednocześnie, co stanowi różnicę w porównaniu do dziedziczenia, gdzie klasa może dziedziczyć tylko z jednej klasy: + +```php +interface Stroz +{ + function pilnujDomu(); +} + +class Pies implements Zwierze, Stroz +{ + public function wydajDzwiek() + { + echo 'Hau'; + } + + public function pilnujDomu() + { + echo 'Pies uważnie pilnuje domu'; + } +} +``` + + +Klasy abstrakcyjne +------------------ + +Klasy abstrakcyjne służą jako podstawowe szablony dla innych klas, ale nie można tworzyć ich instancji bezpośrednio. Zawierają kombinację kompletnych metod i metod abstrakcyjnych, które nie mają zdefiniowanej zawartości. Klasy, które dziedziczą z klas abstrakcyjnych, muszą dostarczyć definicje dla wszystkich metod abstrakcyjnych z przodka. + +Do definiowania klasy abstrakcyjnej używamy słowa kluczowego `abstract`. + +```php +abstract class KlasaAbstrakcyjna +{ + public function zwyklaMetoda() + { + echo 'To jest zwykła metoda'; + } + + abstract public function metodaAbstrakcyjna(); +} + +class Potomek extends KlasaAbstrakcyjna +{ + public function metodaAbstrakcyjna() + { + echo 'To jest implementacja metody abstrakcyjnej'; + } +} + +$instancja = new Potomek; +$instancja->zwyklaMetoda(); +$instancja->metodaAbstrakcyjna(); +``` + +W tym przykładzie mamy klasę abstrakcyjną z jedną zwykłą i jedną abstrakcyjną metodą. Następnie mamy klasę `Potomek`, która dziedziczy z `KlasaAbstrakcyjna` i dostarcza implementację dla metody abstrakcyjnej. + +Jak właściwie różnią się interfejsy i klasy abstrakcyjne? Klasy abstrakcyjne mogą zawierać zarówno abstrakcyjne, jak i konkretne metody, podczas gdy interfejsy jedynie definiują, jakie metody musi implementować klasa, ale nie dostarczają żadnej implementacji. Klasa może dziedziczyć tylko z jednej klasy abstrakcyjnej, ale może implementować dowolną liczbę interfejsów. + + +Kontrola typów +-------------- + +W programowaniu bardzo ważne jest, aby mieć pewność, że dane, z którymi pracujemy, są odpowiedniego typu. W PHP mamy narzędzia, które nam to zapewniają. Weryfikacja, czy dane mają poprawny typ, nazywa się "kontrolą typów". + +Typy, na które możemy natknąć się w PHP: + +1. **Typy podstawowe**: Obejmują `int` (liczby całkowite), `float` (liczby dziesiętne), `bool` (wartości logiczne), `string` (ciągi znaków), `array` (tablice) i `null`. +2. **Klasy**: Jeśli chcemy, aby wartość była instancją określonej klasy. +3. **Interfejsy**: Definiuje zestaw metod, które klasa musi implementować. Wartość, która spełnia interfejs, musi mieć te metody. +4. **Typy mieszane**: Możemy określić, że zmienna może mieć więcej niż jeden dozwolony typ. +5. **Void**: Ten specjalny typ oznacza, że funkcja lub metoda nie zwraca żadnej wartości. + +Pokażmy, jak zmodyfikować kod, aby zawierał typy: + +```php +class Osoba +{ + private int $wiek; + + public function __construct(int $wiek) + { + $this->wiek = $wiek; + } + + public function wypiszWiek(): void + { + echo "Ta osoba ma {$this->wiek} lat."; + } +} + +/** + * Funkcja, która przyjmuje obiekt klasy Osoba i wyświetla wiek osoby. + */ +function wypiszWiekOsoby(Osoba $osoba): void +{ + $osoba->wypiszWiek(); +} +``` + +W ten sposób zapewniliśmy, że nasz kod oczekuje i pracuje z danymi odpowiedniego typu, co pomaga nam zapobiegać potencjalnym błędom. + +Niektórych typów nie można bezpośrednio zapisać w PHP. W takim przypadku podaje się je w komentarzu phpDoc, który jest standardowym formatem dokumentacji kodu PHP zaczynającym się od `/**` i kończącym `*/`. Umożliwia dodawanie opisów klas, metod itp. A także podawanie złożonych typów za pomocą tzw. adnotacji `@var`, `@param` i `@return`. Te typy są następnie wykorzystywane przez narzędzia do statycznej analizy kodu, ale samo PHP ich nie kontroluje. + +```php +class Lista +{ + /** @var array<Osoba> zapis mówi, że jest to tablica obiektów Osoba */ + private array $osoby = []; + + public function dodajOsobe(Osoba $osoba): void + { + $this->osoby[] = $osoba; + } +} +``` + + +Porównywanie i tożsamość +------------------------ + +W PHP możesz porównywać obiekty na dwa sposoby: + +1. Porównanie wartości `==`: Sprawdza, czy obiekty są tej samej klasy i mają te same wartości w swoich właściwościach. +2. Tożsamość `===`: Sprawdza, czy chodzi o tę samą instancję obiektu. + +```php +class Samochod +{ + public string $marka; + + public function __construct(string $marka) + { + $this->marka = $marka; + } +} + +$samochod1 = new Samochod('Skoda'); +$samochod2 = new Samochod('Skoda'); +$samochod3 = $samochod1; + +var_dump($samochod1 == $samochod2); // true, ponieważ mają tę samą wartość +var_dump($samochod1 === $samochod2); // false, ponieważ nie są tą samą instancją +var_dump($samochod1 === $samochod3); // true, ponieważ $samochod3 jest tą samą instancją co $samochod1 +``` + + +Operator `instanceof` +--------------------- + +Operator `instanceof` pozwala sprawdzić, czy dany obiekt jest instancją określonej klasy, potomkiem tej klasy, lub czy implementuje określony interfejs. + +Wyobraźmy sobie, że mamy klasę `Osoba` i inną klasę `Student`, która jest potomkiem klasy `Osoba`: + +```php +class Osoba +{ + private int $wiek; + + public function __construct(int $wiek) + { + $this->wiek = $wiek; + } +} + +class Student extends Osoba +{ + private string $kierunek; + + public function __construct(int $wiek, string $kierunek) + { + parent::__construct($wiek); + $this->kierunek = $kierunek; + } +} + +$student = new Student(20, 'Informatyka'); + +// Sprawdzenie, czy $student jest instancją klasy Student +var_dump($student instanceof Student); // Wyjście: bool(true) + +// Sprawdzenie, czy $student jest instancją klasy Osoba (ponieważ Student jest potomkiem Osoba) +var_dump($student instanceof Osoba); // Wyjście: bool(true) +``` + +Z wyników widać, że obiekt `$student` jest jednocześnie uważany za instancję obu klas - `Student` i `Osoba`. + + +Fluent Interfaces +----------------- + +"Płynny interfejs" (ang. "Fluent Interface") to technika w OOP, która pozwala łączyć metody w łańcuch w jednym wywołaniu. To często upraszcza i uczytelnia kod. + +Kluczowym elementem płynnego interfejsu jest to, że każda metoda w łańcuchu zwraca odwołanie do bieżącego obiektu. Osiągamy to, używając `return $this;` na końcu metody. Ten styl programowania jest często kojarzony z metodami zwanymi "setterami", które ustawiają wartości właściwości obiektu. + +Pokażemy, jak może wyglądać płynny interfejs na przykładzie wysyłania e-maili: + +```php +public function wyslijWiadomosc() +{ + $email = new Email; + $email->setFrom('sender@example.com') + ->setRecipient('admin@example.com') + ->setMessage('Hello, this is a message.') + ->send(); +} +``` + +W tym przykładzie metody `setFrom()`, `setRecipient()` i `setMessage()` służą do ustawienia odpowiednich wartości (nadawcy, odbiorcy, treści wiadomości). Po ustawieniu każdej z tych wartości metody zwracają nam bieżący obiekt (`$email`), co pozwala nam połączyć kolejną metodę za nią. Na końcu wywołujemy metodę `send()`, która faktycznie wysyła e-mail. + +Dzięki płynnym interfejsom możemy pisać kod, który jest intuicyjny i łatwy do odczytania. + + +Kopiowanie za pomocą `clone` +---------------------------- + +W PHP możemy utworzyć kopię obiektu za pomocą operatora `clone`. W ten sposób otrzymujemy nową instancję o identycznej zawartości. + +Jeśli podczas kopiowania obiektu potrzebujemy zmodyfikować niektóre jego właściwości, możemy zdefiniować w klasie specjalną metodę `__clone()`. Ta metoda jest automatycznie wywoływana, gdy obiekt jest klonowany. + +```php +class Owca +{ + public string $imie; + + public function __construct(string $imie) + { + $this->imie = $imie; + } + + public function __clone() + { + $this->imie = 'Klon ' . $this->imie; + } +} + +$oryginal = new Owca('Dolly'); +echo $oryginal->imie . "\n"; // Wyświetli: Dolly + +$klon = clone $oryginal; +echo $klon->imie . "\n"; // Wyświetli: Klon Dolly +``` + +W tym przykładzie mamy klasę `Owca` z jedną właściwością `$imie`. Kiedy klonujemy instancję tej klasy, metoda `__clone()` dba o to, aby nazwa sklonowanej owcy otrzymała prefiks "Klon". + + +Traity +------ + +Traity w PHP to narzędzie, które pozwala współdzielić metody, właściwości i stałe między klasami oraz zapobiegać duplikacji kodu. Możesz je sobie wyobrazić jako mechanizm "kopiuj i wklej" (Ctrl-C i Ctrl-V), gdzie zawartość trait jest "wklejana" do klas. Pozwala to na ponowne wykorzystanie kodu bez konieczności tworzenia skomplikowanych hierarchii klas. + +Pokażmy prosty przykład, jak używać traitów w PHP: + +```php +trait Trabienie +{ + public function zatrab() + { + echo 'Bip bip!'; + } +} + +class Samochod +{ + use Trabienie; +} + +class Ciezarowka +{ + use Trabienie; +} + +$samochod = new Samochod; +$samochod->zatrab(); // Wyświetli 'Bip bip!' + +$ciezarowka = new Ciezarowka; +$ciezarowka->zatrab(); // Również wyświetli 'Bip bip!' +``` + +W tym przykładzie mamy trait o nazwie `Trabienie`, który zawiera jedną metodę `zatrab()`. Następnie mamy dwie klasy: `Samochod` i `Ciezarowka`, które obie używają traitu `Trabienie`. Dzięki temu obie klasy "mają" metodę `zatrab()`, i możemy ją wywoływać na obiektach obu klas. + +Traity pozwalają łatwo i efektywnie współdzielić kod między klasami. Jednocześnie nie wchodzą one w hierarchię dziedziczenia, tj. `$samochod instanceof Trabienie` zwróci `false`. + + +Wyjątki +------- + +Wyjątki w OOP pozwalają nam elegancko obsługiwać błędy i nieoczekiwane sytuacje w naszym kodzie. Są to obiekty, które niosą informacje o błędzie lub nietypowej sytuacji. + +W PHP mamy wbudowaną klasę `Exception`, która służy jako podstawa dla wszystkich wyjątków. Ma ona kilka metod, które pozwalają nam uzyskać więcej informacji o wyjątku, takich jak komunikat o błędzie, plik i linia, w której wystąpił błąd, itp. + +Kiedy w kodzie wystąpi błąd, możemy "rzucić" wyjątek za pomocą słowa kluczowego `throw`. + +```php +function dzielenie(float $a, float $b): float +{ + if ($b === 0.0) { // Porównanie z float + throw new Exception('Dzielenie przez zero!'); + } + return $a / $b; +} +``` + +Kiedy funkcja `dzielenie()` otrzyma jako drugi argument zero, rzuci wyjątek z komunikatem o błędzie `'Dzielenie przez zero!'`. Aby zapobiec awarii programu po rzuceniu wyjątku, przechwytujemy go w bloku `try/catch`: + +```php +try { + echo dzielenie(10, 0); +} catch (Exception $e) { + echo 'Wyjątek przechwycony: '. $e->getMessage(); +} +``` + +Kod, który może rzucić wyjątek, jest opakowany w blok `try`. Jeśli wyjątek zostanie rzucony, wykonanie kodu przenosi się do bloku `catch`, gdzie możemy przetworzyć wyjątek (np. wypisać komunikat o błędzie). + +Po blokach `try` i `catch` możemy dodać opcjonalny blok `finally`, który wykona się zawsze, niezależnie od tego, czy wyjątek został rzucony, czy nie (nawet w przypadku, gdy w bloku `try` lub `catch` użyjemy instrukcji `return`, `break` lub `continue`): + +```php +try { + echo dzielenie(10, 0); +} catch (Exception $e) { + echo 'Wyjątek przechwycony: '. $e->getMessage(); +} finally { + // Kod, który wykona się zawsze, niezależnie od tego, czy wyjątek został rzucony, czy nie +} +``` + +Możemy również tworzyć własne klasy (hierarchię) wyjątków, które dziedziczą z klasy Exception. Jako przykład wyobraźmy sobie prostą aplikację bankową, która pozwala dokonywać wpłat i wypłat: + +```php +class WyjatekBankowy extends Exception {} +class WyjatekNiewystarczajacychSrodkow extends WyjatekBankowy {} +class WyjatekPrzekroczeniaLimitu extends WyjatekBankowy {} + +class KontoBankowe +{ + private int $saldo = 0; + private int $limitDzienny = 1000; + + public function wplac(int $kwota): int + { + $this->saldo += $kwota; + return $this->saldo; + } + + public function wyplac(int $kwota): int + { + if ($kwota > $this->saldo) { + throw new WyjatekNiewystarczajacychSrodkow('Na koncie nie ma wystarczających środków.'); + } + + if ($kwota > $this->limitDzienny) { + throw new WyjatekPrzekroczeniaLimitu('Przekroczono dzienny limit wypłat.'); + } + + $this->saldo -= $kwota; + return $this->saldo; + } +} +``` + +Dla jednego bloku `try` można podać wiele bloków `catch`, jeśli oczekujesz różnych typów wyjątków. + +```php +$konto = new KontoBankowe; +$konto->wplac(500); + +try { + $konto->wyplac(1500); +} catch (WyjatekPrzekroczeniaLimitu $e) { + echo $e->getMessage(); +} catch (WyjatekNiewystarczajacychSrodkow $e) { + echo $e->getMessage(); +} catch (WyjatekBankowy $e) { + echo 'Wystąpił błąd podczas wykonywania operacji.'; +} +``` + +W tym przykładzie ważne jest zwrócenie uwagi na kolejność bloków `catch`. Ponieważ wszystkie wyjątki dziedziczą z `WyjatekBankowy`, gdybyśmy mieli ten blok jako pierwszy, przechwyciłby on wszystkie wyjątki, uniemożliwiając kodowi dotarcie do kolejnych bloków `catch`. Dlatego ważne jest, aby bardziej specyficzne wyjątki (tj. te, które dziedziczą z innych) znajdowały się w bloku `catch` wyżej w kolejności niż ich wyjątki nadrzędne. + + +Iteracja +-------- + +W PHP możesz przechodzić przez obiekty za pomocą pętli `foreach`, podobnie jak przechodzisz przez tablice. Aby to działało, obiekt musi implementować specjalny interfejs. + +Pierwszą opcją jest implementacja interfejsu `Iterator`, który ma metody `current()` zwracającą bieżącą wartość, `key()` zwracającą klucz, `next()` przechodzącą do następnej wartości, `rewind()` przechodzącą na początek i `valid()` sprawdzającą, czy jeszcze nie jesteśmy na końcu. + +Drugą opcją jest implementacja interfejsu `IteratorAggregate`, który ma tylko jedną metodę `getIterator()`. Zwraca ona albo obiekt zastępczy, który będzie zapewniał iterację, albo może reprezentować generator, czyli specjalną funkcję, w której używa się `yield` do stopniowego zwracania kluczy i wartości: + +```php +class Osoba +{ + public function __construct( + public int $wiek, + ) { + } +} + +class Lista implements IteratorAggregate +{ + private array $osoby = []; + + public function dodajOsobe(Osoba $osoba): void + { + $this->osoby[] = $osoba; + } + + public function getIterator(): Generator + { + foreach ($this->osoby as $osoba) { + yield $osoba; + } + } +} + +$lista = new Lista; +$lista->dodajOsobe(new Osoba(30)); +$lista->dodajOsobe(new Osoba(25)); + +foreach ($lista as $osoba) { + echo "Wiek: {$osoba->wiek} lat \n"; +} +``` + + +Dobre praktyki +-------------- + +Kiedy masz już za sobą podstawowe zasady programowania obiektowego, ważne jest, aby skupić się na dobrych praktykach w OOP. Pomogą ci one pisać kod, który jest nie tylko funkcjonalny, ale także czytelny, zrozumiały i łatwy do utrzymania. + +1) **Podział odpowiedzialności (Separation of Concerns)**: Każda klasa powinna mieć jasno zdefiniowaną odpowiedzialność i powinna rozwiązywać tylko jedno główne zadanie. Jeśli klasa robi zbyt wiele rzeczy, może być wskazane podzielenie jej na mniejsze, wyspecjalizowane klasy. +2) **Hermetyzacja (Encapsulation)**: Dane i metody powinny być jak najbardziej ukryte i dostępne tylko za pośrednictwem zdefiniowanego interfejsu. Pozwoli to na zmianę wewnętrznej implementacji klasy bez wpływu na resztę kodu. +3) **Wstrzykiwanie zależności (Dependency Injection)**: Zamiast tworzyć zależności bezpośrednio w klasie, powinieneś je "wstrzykiwać" z zewnątrz. Aby lepiej zrozumieć tę zasadę, polecamy [rozdziały o wstrzykiwaniu zależności|dependency-injection:introduction]. diff --git a/nette/pl/troubleshooting.texy b/nette/pl/troubleshooting.texy index 5d772a30dd..7ffed9c17e 100644 --- a/nette/pl/troubleshooting.texy +++ b/nette/pl/troubleshooting.texy @@ -2,40 +2,69 @@ Rozwiązywanie problemów *********************** -Nie mogę uruchomić Nette, wyświetla białą stronę. .[#toc-nette-is-not-working-white-page-is-displayed] ------------------------------------------------------------------------------------------------------- -- Spróbuj umieścić `ini_set('display_errors', '1'); error_reporting(E_ALL);` w pliku `index.php` zaraz po `declare(strict_types=1);`, to wymusi wyświetlanie błędów -- Jeśli nadal widzisz biały ekran, prawdopodobnie wystąpił błąd w ustawieniach serwera i możesz znaleźć przyczynę w dzienniku serwera. Dla pewności sprawdź, czy PHP w ogóle działa, próbując wypisać coś za pomocą `echo 'test';` -- Jeśli widzisz *Błąd serwera: Przepraszamy! ...*, przejdź do następnej sekcji: +Nette nie działa, wyświetla się biała strona +-------------------------------------------- +- Spróbuj w pliku `index.php` zaraz po `declare(strict_types=1);` wstawić `ini_set('display_errors', '1'); error_reporting(E_ALL);`, to wymusi wyświetlanie błędów +- Jeśli nadal widzisz biały ekran, prawdopodobnie jest błąd w konfiguracji serwera, a przyczynę znajdziesz w logu serwera. Dla pewności sprawdź jeszcze, czy w ogóle działa PHP, próbując coś wypisać za pomocą `echo 'test';` +- Jeśli widzisz błąd *Server Error: We're sorry! …*, przejdź do następnej sekcji: -Error 500 *Błąd serwera: Przepraszamy! ...* .[#toc-error-500-server-error-we-re-sorry] --------------------------------------------------------------------------------------- -Ta strona błędu jest wyświetlana przez Nette w trybie produkcyjnym. Jeśli widzisz to na maszynie deweloperskiej, przełącz się na [tryb dewel |application:bootstrap#Development-vs-Production-Mode] operski. +Błąd 500 *Server Error: We're sorry! …* +--------------------------------------- +Tę stronę błędu wyświetla Nette w trybie produkcyjnym. Jeśli wyświetla się na Twoim komputerze deweloperskim, [przełącz się do trybu deweloperskiego |application:bootstrapping#Tryb deweloperski vs produkcyjny], a wyświetli się Tracy ze szczegółowym komunikatem. -Jeśli komunikat o błędzie zawiera zdanie `Tracy is unable to log error`, dowiedz się, dlaczego nie można rejestrować błędów. Aby to zrobić, na przykład [przełącz się do |application:bootstrap#Development-vs-Production-Mode] trybu deweloperskiego i zadzwoń `Tracy\Debugger::log('hello');` po `$configurator->enableTracy(...)`. Tracy powie ci, dlaczego nie może się zalogować. -Przyczyną może być wadliwa elektronowa elektronowa trzynastka z zakładu Katoda Olomóc, ale bardziej prawdopodobne są [niewystarczające uprawnienia |#Setting-Directory-Permissions] do zapisu w katalogu `log/`. +Przyczynę błędu zawsze znajdziesz w logu w katalogu `log/`. Jeśli jednak w komunikacie błędu pojawia się zdanie `Tracy is unable to log error`, najpierw ustal, dlaczego nie można logować błędów. Zrobisz to na przykład tak, że tymczasowo [przełączysz się |application:bootstrapping#Tryb deweloperski vs produkcyjny] do trybu deweloperskiego i pozwolisz Tracy cokolwiek zalogować po jej uruchomieniu: -Jeśli w komunikacie o błędzie nie ma (już) frazy `Tracy is unable to log error`, to przyczynę błędu można przeczytać w logu w katalogu `log/`. +```php +// Bootstrap.php +$configurator->setDebugMode('23.75.345.200'); // twój adres IP +$configurator->enableTracy($rootDir . '/log'); +\Tracy\Debugger::log('hello'); +``` + +Tracy poinformuje Cię, dlaczego nie może logować. Przyczyną mogą być [niewystarczające uprawnienia |#Ustawianie uprawnień do katalogów] do zapisu do katalogu `log/`. + +Jedną z najczęstszych przyczyn błędu 500 jest przestarzała pamięć podręczna (cache). Podczas gdy Nette w trybie deweloperskim sprytnie automatycznie aktualizuje cache, w trybie produkcyjnym skupia się na maksymalizacji wydajności, a czyszczenie cache po każdej modyfikacji kodu należy do Ciebie. Spróbuj usunąć `temp/cache`. + + +Błąd 404, routing nie działa +---------------------------- +Kiedy wszystkie strony (oprócz strony głównej) zwracają błąd 404, wygląda to na problem z konfiguracją serwera dla [przyjaznych adresów URL |#Jak skonfigurować serwer dla przyjaznych adresów URL]. + + +Zmiany w szablonach lub konfiguracji nie są widoczne +---------------------------------------------------- +"Zmodyfikowałem szablon lub konfigurację, ale strona nadal wyświetla starą wersję." To zachowanie występuje w [trybie produkcyjnym |application:bootstrapping#Tryb deweloperski vs produkcyjny], który ze względu na wydajność nie kontroluje zmian w plikach i utrzymuje raz wygenerowaną pamięć podręczną. + +Aby nie musieć na serwerze produkcyjnym po każdej modyfikacji ręcznie czyścić cache, włącz tryb deweloperski dla swojego adresu IP w pliku `Bootstrap.php`: + +```php +$this->configurator->setDebugMode('twoj.adres.ip'); +``` + + +Jak wyłączyć cache podczas developmentu? +---------------------------------------- +Nette jest sprytne i nie musisz w nim wyłączać buforowania. Podczas rozwoju bowiem automatycznie aktualizuje cache przy każdej zmianie szablonu lub konfiguracji kontenera DI. Tryb deweloperski jest ponadto włączany przez autodetekcję, więc zazwyczaj nie trzeba nic konfigurować, [lub tylko adres IP |application:bootstrapping#Tryb deweloperski vs produkcyjny]. -Jedną z najczęstszych przyczyn jest nieaktualny cache. Podczas gdy Nette w trybie deweloperskim sprytnie automatycznie aktualizuje pamięć podręczną, w trybie produkcyjnym skupia się na maksymalizacji wydajności, a usunięcie pamięci podręcznej, po każdej modyfikacji kodu, zależy od Ciebie. Spróbuj usunąć `temp/cache`. +Podczas debugowania routera zalecamy wyłączenie cache w przeglądarce, w której mogą być zapisane na przykład przekierowania: otwórz Narzędzia deweloperskie (Ctrl+Shift+I lub Cmd+Option+I) i w panelu Sieć (Network) zaznacz wyłączenie cache. -Błąd `#[\ReturnTypeWillChange] attribute should be used` .[#toc-error-returntypewillchange-attribute-should-be-used] --------------------------------------------------------------------------------------------------------------------- -Ten błąd pojawia się, jeśli zaktualizowałeś PHP do wersji 8.1, ale używasz Nette, która nie jest z nim kompatybilna. Rozwiązaniem jest więc aktualizacja Nette do nowszej wersji za pomocą `composer update`. Nette wspiera PHP 8.1 od wersji 3.0. Jeśli używasz starszej wersji (możesz to sprawdzić zaglądając na stronę `composer.json`), [zaktualizuj Nette |migrations:en] lub pozostań przy PHP 8.0. +Błąd `#[\ReturnTypeWillChange] attribute should be used` +-------------------------------------------------------- +Ten błąd pojawi się, jeśli zaktualizowałeś PHP do wersji 8.1, ale używasz Nette, które nie jest z nią kompatybilne. Rozwiązaniem jest więc aktualizacja Nette do nowszej wersji za pomocą `composer update`. Nette obsługuje PHP 8.1 od wersji 3.0. Jeśli używasz wersji starszej (sprawdzisz w `composer.json`), [zaktualizuj Nette |migrations:en] lub pozostań przy PHP 8.0. -Ustawianie uprawnień do katalogów .[#toc-setting-directory-permissions] ------------------------------------------------------------------------ -Jeśli tworzysz na macOS lub Linuxie (lub innym systemie opartym na Uniksie), będziesz musiał ustawić uprawnienia do zapisu na serwerze WWW. Załóżmy, że twoja aplikacja znajduje się na domyślnym `/var/www/html` (Fedora, CentOS, RHEL). +Ustawianie uprawnień do katalogów +--------------------------------- +Jeśli tworzysz oprogramowanie na macOS lub Linuksie (lub na jakimkolwiek innym systemie opartym na Uniksie), będziesz musiał ustawić uprawnienia zapisu dla serwera WWW do katalogów `temp/` i `log/`. Załóżmy, że Twoja aplikacja znajduje się w domyślnym `/var/www/html` (Fedora, CentOS, RHEL). ```shell cd /var/www/html/MY_PROJECT chmod -R a+rw temp log ``` -W niektórych systemach Linux (Fedora, CentOS, ...) SELinux jest domyślnie włączony. Należy odpowiednio zmodyfikować polityki SELinux i ustawić właściwy kontekst bezpieczeństwa SELinux dla folderów `temp` i `log`. Dla `temp` i `log` ustaw typ kontekstu na `httpd_sys_rw_content_t`, dla reszty aplikacji (a zwłaszcza dla folderu `app`) wystarczy `httpd_sys_content_t`. Na serwerze należy uruchomić: +Na niektórych dystrybucjach Linuksa (Fedora, CentOS, ...) domyślnie włączony jest SELinux. Będziesz musiał odpowiednio zmodyfikować polityki SELinux i ustawić prawidłowy kontekst bezpieczeństwa SELinux dla folderów `temp` i `log`. Dla `temp` i `log` ustawimy typ kontekstu `httpd_sys_rw_content_t`, dla reszty aplikacji (a zwłaszcza dla folderu `app`) wystarczy `httpd_sys_content_t`. Na serwerze uruchom: ```shell semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/log(/.*)?' @@ -43,25 +72,30 @@ semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/temp(/.*) restorecon -Rv /var/www/html/MY_PROJECT/ ``` -Następnie musimy włączyć boolean SELinux `httpd_can_network_connect_db`, który jest domyślnie wyłączony i który pozwoli Nette połączyć się z bazą danych przez sieć. Wykorzystamy do tego polecenie `setsebool` i użyjemy opcji `-P`, aby zmiana była trwała, czyli nie będzie żadnych nieprzyjemnych niespodzianek po restarcie serwera: +Następnie należy włączyć flagę SELinux `httpd_can_network_connect_db`, która jest domyślnie wyłączona i która pozwoli Nette połączyć się z bazą danych przez sieć. Użyjemy do tego polecenia `setsebool` z opcją `-P`, aby zmiana była trwała, tzn. po restarcie serwera nie spotka nas niemiła niespodzianka: ```shell setsebool -P httpd_can_network_connect_db on ``` -Jak zmienić lub usunąć `www` z adresu URL ? .[#toc-how-to-change-or-remove-www-directory-from-url] --------------------------------------------------------------------------------------------------- -Katalog `www/` używany w przykładowych projektach Nette jest katalogiem publicznym lub document-root projektu. Jest to jedyny katalog, którego zawartość jest dostępna dla przeglądarki. I zawiera plik `index.php`, punkt wejścia uruchamiający aplikację internetową napisaną w Nette. +Jak zmienić lub usunąć katalog `www` z adresu URL? +-------------------------------------------------- +Katalog `www/` używany w przykładowych projektach Nette reprezentuje tzw. katalog publiczny lub document-root projektu. Jest to jedyny katalog, którego zawartość jest dostępna dla przeglądarki. Zawiera on plik `index.php`, punkt wejściowy, który uruchamia aplikację internetową napisaną w Nette. -Aby aplikacja działała na hostingu, należy w konfiguracji hostingu ustawić document-root na ten katalog. Lub, jeśli hosting ma przygotowany folder o innej nazwie dla katalogu publicznego (na przykład `web`, `public_html`, itp.), po prostu zmień nazwę `www/`. +Aby uruchomić aplikację na hostingu, konieczne jest prawidłowe skonfigurowanie document-root. Masz dwie możliwości: +1. W konfiguracji hostingu ustawić document-root na ten katalog +2. Jeśli hosting ma przygotowany folder (np. `public_html`), zmień nazwę `www/` na tę nazwę -Rozwiązaniem **nie jest** "pozbycie się" folderu `www/` za pomocą reguł w pliku `.htaccess` lub w routerze. Jeśli hosting nie pozwoliłby Ci ustawić document-root na podkatalog (czyli tworzyć katalogi jeden poziom powyżej katalogu publicznego), poszukaj innego. W przeciwnym razie podejmowałbyś znaczne ryzyko związane z bezpieczeństwem. Byłoby to jak mieszkanie, w którym nie można zamknąć drzwi wejściowych i są one zawsze szeroko otwarte. +.[warning] +Nigdy nie próbuj rozwiązywać problemów z bezpieczeństwem tylko za pomocą `.htaccess` lub routera, które miałyby uniemożliwiać dostęp do pozostałych folderów. +Jeśli hosting nie pozwala na ustawienie document-root na podkatalog (tj. tworzenie katalogów o poziom wyżej nad katalogiem publicznym), poszukaj innego. W przeciwnym razie naraziłbyś się na znaczne ryzyko bezpieczeństwa. Byłoby to jak mieszkanie w mieszkaniu, w którym nie da się zamknąć drzwi wejściowych i są one stale otwarte. -Jak założyć serwer dla ładnych adresów URL? .[#toc-how-to-configure-a-server-for-nice-urls] -------------------------------------------------------------------------------------------- -**Apache**: należy włączyć i ustawić rozszerzenie `mod_rewrite` w pliku `.htaccess`: + +Jak skonfigurować serwer dla przyjaznych adresów URL? +----------------------------------------------------- +**Apache**: należy włączyć i skonfigurować reguły mod_rewrite w pliku `.htaccess`: ```apacheconf RewriteEngine On @@ -70,25 +104,53 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -Aby wpłynąć na zachowanie plików Apache `.htaccess` należy mieć włączoną dyrektywę `AllowOverride`. Jest to domyślne zachowanie w Apache. +Jeśli napotkasz problemy, upewnij się, że: +- plik `.htaccess` znajduje się w katalogu document-root (czyli obok pliku `index.php`) +- [Apache przetwarza pliki `.htaccess` |#Sprawdzenie czy .htaccess działa] +- [mod_rewrite jest włączony |#Sprawdzenie czy mod rewrite jest włączony] + +Jeśli konfigurujesz aplikację w podkatalogu, być może będziesz musiał odkomentować linię do ustawienia `RewriteBase` i ustawić ją na właściwy folder. -**nginx**: należy ustawić przekierowanie za pomocą dyrektywy `try_files` wewnątrz bloku `location /` w konfiguracji serwera. +**nginx**: należy skonfigurować przekierowanie za pomocą dyrektywy `try_files` wewnątrz bloku `location /` w konfiguracji serwera. ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args je důležité + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args JEST WAŻNE! } ``` -Blok `location` może pojawić się tylko raz w bloku `server` dla każdej ścieżki systemu plików. Jeśli masz już w swojej konfiguracji `location /`, dodaj do niej dyrektywę `try_files`. +Blok `location` dla każdej ścieżki systemu plików może występować w bloku `server` tylko raz. Jeśli już masz w konfiguracji `location /`, dodaj do niego dyrektywę `try_files`. + + +Sprawdzenie, czy `.htaccess` działa +----------------------------------- +Najprostszym sposobem na przetestowanie, czy Apache używa czy ignoruje Twój plik `.htaccess`, jest celowe jego uszkodzenie. Wstaw na początek pliku linię `Test` i teraz, jeśli odświeżysz stronę w przeglądarce, powinieneś zobaczyć *Internal Server Error*. + +Jeśli zobaczysz ten błąd, to właściwie dobrze! Oznacza to, że Apache analizuje plik `.htaccess` i napotyka błąd, który tam wstawiliśmy. Usuń linię `Test`. + +Jeśli nie zobaczysz *Internal Server Error*, Twoja konfiguracja Apache ignoruje plik `.htaccess`. Ogólnie Apache ignoruje go z powodu brakującej dyrektywy konfiguracyjnej `AllowOverride All`. + +Jeśli hostujesz go sam, można to łatwo naprawić. Otwórz plik `httpd.conf` lub `apache.conf` w edytorze tekstu, znajdź odpowiednią sekcję `<Directory>` i dodaj/zmień tę dyrektywę: + +```apacheconf +<Directory "/var/www/htdocs"> # ścieżka do twojego document root + AllowOverride All + ... +``` + +Jeśli Twoja strona jest hostowana gdzie indziej, sprawdź w panelu sterowania, czy możesz tam włączyć plik `.htaccess`. Jeśli nie, skontaktuj się z dostawcą hostingu, aby to zrobił za Ciebie. + + +Sprawdzenie, czy `mod_rewrite` jest włączony +-------------------------------------------- +Jeśli masz pewność, że [`.htaccess` działa |#Sprawdzenie czy .htaccess działa], możesz sprawdzić, czy rozszerzenie mod_rewrite jest włączone. Wstaw na początek pliku `.htaccess` linię `RewriteEngine On` i odśwież stronę w przeglądarce. Jeśli zobaczysz *Internal Server Error*, oznacza to, że mod_rewrite nie jest włączony. Istnieje kilka sposobów, aby go włączyć. Różne sposoby, jak to można zrobić w różnych konfiguracjach, znajdziesz na Stack Overflow. -Linki są generowane bez `https:` .[#toc-links-are-generated-without-https] --------------------------------------------------------------------------- -Nette generuje linki z tym samym protokołem co sama strona. W ten sposób na stronie `https://foo` i odwrotnie. -Jeśli jesteś za odwrotnym proxy, które usuwa HTTPS (na przykład w Docker), to musisz [ustawić proxy |http:configuration#HTTP-Proxy] w konfiguracji, aby wykrywanie protokołu działało poprawnie. +Linki generują się bez `https:` +------------------------------- +Nette generuje linki z tym samym protokołem, jaki ma sama strona. Czyli na stronie `https://foo` generuje linki zaczynające się od `https:` i odwrotnie. Jeśli jesteś za odwrotnym serwerem proxy, który usuwa HTTPS (na przykład w Dockerze), należy w konfiguracji [ustawić proxy |http:configuration#Proxy HTTP], aby detekcja protokołu działała poprawnie. -Jeśli używasz Nginx jako proxy, musisz mieć ustawione przekierowanie w ten sposób: +Jeśli używasz Nginx jako proxy, musisz mieć ustawione przekierowanie np. w ten sposób: ``` location / { @@ -96,11 +158,11 @@ location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Port $server_port; - proxy_pass http://IP-aplikace:80; # IP nebo hostname serveru/kontejneru, kde běží aplikace + proxy_pass http://IP-aplikace:80; # IP lub nazwa hosta serwera/kontenera, na którym działa aplikacja } ``` -Musisz również określić IP proxy i, jeśli dotyczy, zakres IP sieci lokalnej, w której uruchamiasz infrastrukturę: +Następnie należy w konfiguracji podać IP proxy i ewentualnie zakres IP Twojej sieci lokalnej, w której działa infrastruktura: ```neon http: @@ -108,32 +170,30 @@ http: ``` -Używanie znaków { } w JavaScript .[#toc-use-of-characters-in-javascript] ------------------------------------------------------------------------- -Znaki `{` a `}` służą do pisania znaczników Latte. Wszystko co następuje po znaku `{` s výjimkou mezery a uvozovky. Pokud tedy potřebujete vypsat přímo znak `{` jest brane jako znacznik (często np. w JavaScript), można umieścić spację (lub inny pusty znak) po znaku `{`. Pozwala to uniknąć tłumaczenia go jako tagu. +Użycie znaków { } w JavaScript +------------------------------ +Znaki `{` i `}` są używane do zapisu tagów Latte. Jako tag traktowane jest wszystko, co następuje po znaku `{` z wyjątkiem spacji i cudzysłowu. Jeśli więc potrzebujesz wypisać bezpośrednio znak `{` (często na przykład w JavaScript), możesz po znaku `{` wstawić spację (lub inny biały znak). W ten sposób unikniesz interpretacji jako tagu. -Jeśli musisz wyprowadzić te znaki w sytuacji, w której tekst byłby rozumiany jako znacznik, możesz użyć specjalnych znaczników do wyprowadzenia tych znaków - `{l}` dla `{` a `{r}` dla `}`. +Jeśli konieczne jest wypisanie tych znaków w sytuacji, gdy tekst byłby interpretowany jako tag, możesz wykorzystać specjalne tagi do wypisania tych znaków - `{l}` dla `{` i `{r}` dla `}`. ``` -{je značka} -{ není značka } -{l}není značka{r} +{jest tagiem} +{ nie jest tagiem } +{l}nie jest tagiem{r} ``` -Wiadomość `Presenter::getContext() is deprecated` .[#toc-notice-presenter-getcontext-is-deprecated] ---------------------------------------------------------------------------------------------------- +Komunikat `Presenter::getContext() is deprecated` +------------------------------------------------- -Nette jest zdecydowanie pierwszym frameworkiem PHP, który przeszedł na wstrzykiwanie zależności i doprowadził programistów do konsekwentnego używania go, od samych prezenterów. Jeśli prezenter potrzebuje zależności, będzie się [o nią upominał |dependency-injection:passing-dependencies]. -Z drugiej strony sposób, w którym przekazujemy cały kontener DI do klasy, a ona bezpośrednio wyciąga z niego zależności, jest uważany za antipattern (nazywa się to service locator). -Ten sposób był używany w Nette 0.x przed pojawieniem się dependency injection, a jego pozostałością jest metoda `Presenter::getContext()`, dawno temu oznaczona jako deprecated. +Nette jest zdecydowanie pierwszym frameworkiem PHP, który przeszedł na wstrzykiwanie zależności i prowadził programistów do jego konsekwentnego używania, już od samych prezenterów. Jeśli prezenter potrzebuje jakiejś zależności, [zgłasza się po nią|dependency-injection:passing-dependencies]. Natomiast droga, w której do klasy przekazujemy cały kontener DI, a ta wyciąga z niego zależności bezpośrednio, uważana jest za antywzorzec (nazywa się service locator). Ten sposób był używany w Nette 0.x jeszcze przed pojawieniem się wstrzykiwania zależności, a jego pozostałością jest metoda `Presenter::getContext()`, dawno oznaczona jako przestarzała (deprecated). -Jeśli przeportujesz bardzo starą aplikację do Nette, może się okazać, że nadal używa ona tej metody. Tak więc od wersji 3.1 `nette/application` napotkasz ostrzeżenie `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection`, od wersji 4.0 napotkasz błąd, że metoda nie istnieje. +Jeśli przenosisz bardzo starą aplikację do Nette, może się zdarzyć, że ta metoda nadal jest używana. Od `nette/application` wersji 3.1 spotkasz się więc z ostrzeżeniem `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection`, od wersji 4.0 z błędem, że metoda nie istnieje. -Czystym rozwiązaniem jest oczywiście przebudowa aplikacji, aby przekazać zależności za pomocą zastrzyku zależności. Jako obejście, możesz dodać własną metodę `getContext()` do swojego prezentera bazowego i obejść komunikat: +Czystym rozwiązaniem jest oczywiście przerobienie aplikacji tak, aby przekazywała zależności za pomocą wstrzykiwania zależności. Jako obejście (workaround) możesz do swojego podstawowego prezentera dodać własną metodę `getContext()` i w ten sposób obejść komunikat: ```php -abstract BasePresenter extends Nette\Application\UI\Presenter +abstract class BasePresenter extends Nette\Application\UI\Presenter { private Nette\DI\Container $context; diff --git a/nette/pl/vulnerability-protection.texy b/nette/pl/vulnerability-protection.texy index dba95d42a0..ee1b115f52 100644 --- a/nette/pl/vulnerability-protection.texy +++ b/nette/pl/vulnerability-protection.texy @@ -1,22 +1,22 @@ -Ochrona przed podatnością na zagrożenia -*************************************** +Ochrona przed podatnościami +*************************** .[perex] -Co jakiś czas zgłaszana jest dziura w zabezpieczeniach na innej dużej stronie internetowej lub exploit. To jest denerwujące. Jeśli zależy Ci na bezpieczeństwie Twoich aplikacji internetowych, Nette Framework jest z pewnością najlepszym wyborem. +Co chwilę zgłaszana jest luka bezpieczeństwa na kolejnej ważnej stronie internetowej lub luka jest wykorzystywana. To nieprzyjemne. Jeśli zależy Ci na bezpieczeństwie Twoich aplikacji internetowych, Nette Framework jest z pewnością najlepszym wyborem. -Cross-Site Scripting (XSS) .[#toc-cross-site-scripting-xss] -=========================================================== +Cross-Site Scripting (XSS) +========================== -Cross-Site Scripting to metoda włamywania się na strony internetowe poprzez wykorzystanie nieobsługiwanych danych wyjściowych. Napastnik może wtedy wcisnąć na stronę swój własny kod, co pozwoli mu na modyfikację strony lub nawet uzyskanie wrażliwych danych odwiedzających. Przed XSS można się bronić tylko poprzez konsekwentne i poprawne traktowanie wszystkich ciągów znaków. Wystarczy jednak, że Twój koder pominie ten jeden raz, a cała strona może zostać błyskawicznie skompromitowana. +Cross-Site Scripting to metoda naruszania stron internetowych wykorzystująca nieprzetworzone dane wyjściowe. Atakujący może wstrzyknąć do strony swój własny kod, co pozwala mu zmodyfikować stronę lub nawet uzyskać poufne dane o odwiedzających. Przed XSS można się bronić tylko poprzez konsekwentne i poprawne przetwarzanie (escapowanie) wszystkich ciągów znaków. Wystarczy jednak, że Twój koder tylko raz o tym zapomni, a cała strona może zostać natychmiast skompromitowana. -Przykładem ataku może być podsunięcie przez użytkownika zmodyfikowanego adresu URL w celu wstrzyknięcia swojego kodu na stronę. Gdy aplikacja nie radzi sobie poprawnie z wyjściem, wykonuje skrypt w przeglądarce użytkownika. W ten sposób możemy np. wykraść tożsamość użytkownika. +Przykładem ataku może być podsunięcie użytkownikowi zmodyfikowanego adresu URL, za pomocą którego wstrzykujemy do strony swój kod. Jeśli aplikacja nie będzie odpowiednio przetwarzać danych wyjściowych, wykona skrypt w przeglądarce użytkownika. W ten sposób możemy na przykład ukraść jego tożsamość. ``` -https://example.com/?search=<script>alert('Úspěšný XSS útok.');</script> +https://example.com/?search=<script>alert('Udany atak XSS.');</script> ``` -Nette Framework wyposażony jest w rewolucyjną technologię [Context-Aware Escaping |latte:safety-first#Context-Aware-Escaping], która na zawsze eliminuje ryzyko związane z Cross-Site Scripting. Traktuje wszystkie wyjścia automatycznie, więc nie może się zdarzyć, że koder o czymś zapomni. Przykład? Koder tworzy ten szablon: +Nette Framework wprowadza rewolucyjną technologię [Context-Aware Escaping |latte:safety-first#Escapowanie kontekstowe], która na zawsze uwolni Cię od ryzyka Cross-Site Scriptingu. Wszystkie dane wyjściowe są bowiem przetwarzane automatycznie, więc nie może się zdarzyć, że koder o czymś zapomni. Przykład? Koder tworzy ten szablon: ```latte <p onclick="alert({$message})">{$message}</p> @@ -26,29 +26,29 @@ document.title = {$message}; </script> ``` -Pisanie `{$message}` oznacza wypisanie zmiennej. W innych frameworkach każdy listing musi być obsługiwany jawnie, a nawet inaczej w każdym miejscu. W Nette Framework nie trzeba niczego leczyć, wszystko odbywa się automatycznie, prawidłowo i konsekwentnie. Jeśli umieścimy `$message = 'Šířka 1/2"'` w zmiennej, framework wygeneruje kod HTML: +Zapis `{$message}` oznacza wypisanie zmiennej. W innych frameworkach konieczne jest jawne przetworzenie każdego wypisania, a nawet w każdym miejscu inaczej. W Nette Framework nie trzeba niczego przetwarzać, wszystko dzieje się automatycznie, poprawnie i konsekwentnie. Jeśli podstawimy do zmiennej `$message = 'Szerokość 1/2"'`, framework wygeneruje kod HTML: ```latte -<p onclick="alert("Šířka 1\/2\"")">Šířka 1/2"</p> +<p onclick="alert("Szerokość 1\/2\"")">Szerokość 1/2"</p> <script> -document.title = "Šířka 1\/2\""; +document.title = "Szerokość 1\/2\""; </script> ``` -Cross-Site Request Forgery (CSRF) .[#toc-cross-site-request-forgery-csrf] -========================================================================= +Cross-Site Request Forgery (CSRF) +================================= -Atak Cross-Site Request Forgery polega na tym, że napastnik zwabia ofiarę na stronę, która w subtelny sposób wykonuje w przeglądarce ofiary żądanie do serwera, na którym ofiara jest zalogowana, a serwer zakłada, że żądanie zostało wykonane przez ofiarę z własnej woli. Wykonuje więc akcję pod tożsamością ofiary bez jej wiedzy. Może to być zmiana lub usunięcie danych, wysłanie wiadomości itp. +Atak Cross-Site Request Forgery polega na tym, że atakujący zwabia ofiarę na stronę, która niepostrzeżenie w przeglądarce ofiary wykonuje żądanie do serwera, na którym ofiara jest zalogowana, a serwer zakłada, że żądanie zostało wykonane przez ofiarę z własnej woli. W ten sposób pod tożsamością ofiary wykonuje określoną czynność, o której ofiara nie wie. Może to być zmiana lub usunięcie danych, wysłanie wiadomości itp. -Nette Framework **automatycznie chroni formularze i sygnały w presenterech** przed tego typu atakami. Robi to, uniemożliwiając ich wysyłanie lub wywoływanie z innej domeny. Jeśli chcesz wyłączyć ochronę, użyj poniższego dla formularzy: +Nette Framework **automatycznie chroni formularze i sygnały w prezenterach** przed tego typu atakami. Robi to, uniemożliwiając ich wysłanie lub wywołanie z innej domeny. Jeśli chcesz wyłączyć ochronę, użyj w formularzach: ```php $form->allowCrossOrigin(); ``` -lub w przypadku sygnału dodać adnotację `@crossOrigin`: +lub w przypadku sygnału dodaj adnotację `@crossOrigin`: ```php /** @@ -59,41 +59,41 @@ public function handleXyz() } ``` -W PHP 8 można również używać atrybutów: +W Nette Application 3.2 możesz również użyć atrybutów: ```php -use Nette\Application\Attributes\CrossOrigin; +use Nette\Application\Attributes\Requires; -#[CrossOrigin] +#[Requires(sameOrigin: false)] public function handleXyz() { } ``` -Atak na URL, kody kontrolne, nieprawidłowe UTF-8 .[#toc-url-attack-control-codes-invalid-utf-8] -=============================================================================================== +Ataki URL, kody kontrolne, nieprawidłowe UTF-8 +============================================== -Różne terminy związane z napastnikiem próbującym przemycić *szkodliwe* dane do aplikacji internetowej. Konsekwencje mogą być bardzo różnorodne, począwszy od uszkodzonych danych wyjściowych XML (np. zepsute kanały RSS) po uzyskanie wrażliwych informacji z bazy danych lub haseł. Obrona polega na konsekwentnym traktowaniu wszystkich danych wejściowych na poziomie bajt po bajcie. I ręka na sercu, kto z was to robi? +Różne pojęcia związane z próbą podsunięcia przez atakującego Twojej aplikacji internetowej *szkodliwych* danych wejściowych. Skutki mogą być bardzo różnorodne, od uszkodzenia danych wyjściowych XML (np. niedziałające kanały RSS) po uzyskanie poufnych informacji z bazy danych lub haseł. Obroną jest konsekwentne przetwarzanie wszystkich danych wejściowych na poziomie poszczególnych bajtów. A szczerze mówiąc, kto z Was to robi? -Nette Framework robi to za Ciebie, i to automatycznie. Nie musisz w ogóle niczego ustawiać, a wszystkie dane wejściowe będą traktowane. +Nette Framework robi to za Ciebie i dodatkowo automatycznie. Nie musisz niczego konfigurować, a wszystkie dane wejściowe będą przetworzone. -Porywanie sesji, kradzież sesji, utrwalanie sesji .[#toc-session-hijacking-session-stealing-session-fixation] -============================================================================================================= +Session hijacking, session stealing, session fixation +===================================================== -Z porwaniem sesji wiąże się kilka rodzajów ataków. Atakujący kradnie lub psuje identyfikator sesji użytkownika, aby uzyskać dostęp do aplikacji internetowej bez znajomości jego hasła. Może on wtedy zrobić wszystko w aplikacji bez wiedzy użytkownika. Obrona polega na prawidłowym skonfigurowaniu serwera i PHP. +Z zarządzaniem sesją wiąże się od razu kilka typów ataków. Atakujący albo kradnie, albo podsuwa użytkownikowi swoje ID sesji i dzięki temu uzyskuje dostęp do aplikacji internetowej, nie znając hasła użytkownika. Następnie może w aplikacji robić cokolwiek, o czym użytkownik nie wie. Obrona polega na prawidłowej konfiguracji serwera i PHP. -Nette Framework konfiguruje PHP automatycznie. Dzięki temu programista nie musi zastanawiać się jak odpowiednio zabezpieczyć sesję i może w pełni skupić się na tworzeniu aplikacji. Wymaga to jednak włączenia funkcji `ini_set()`. +Przy czym Nette Framework konfiguruje PHP automatycznie. Programista nie musi więc zastanawiać się, jak prawidłowo zabezpieczyć sesję i może w pełni skupić się na tworzeniu aplikacji. Wymaga to jednak włączonej funkcji `ini_set()`. -Plik cookie SameSite .[#toc-samesite-cookie] -============================================ +SameSite cookie +=============== -Pliki cookie SameSite zapewniają mechanizm rozpoznawania, co doprowadziło do załadowania strony. Co jest absolutnie niezbędne ze względów bezpieczeństwa. +SameSite cookies zapewniają mechanizm rozpoznawania, co doprowadziło do załadowania strony. Co jest absolutnie kluczowe ze względów bezpieczeństwa. -Flaga SameSite może przyjmować trzy wartości: `Lax`, `Strict` oraz `None` (ta ostatnia wymaga protokołu HTTPS). Jeśli żądanie do strony pochodzi bezpośrednio z serwisu lub użytkownik otwiera stronę wpisując ją bezpośrednio w pasku adresu lub klikając zakładkę, przeglądarka wyśle wszystkie ciasteczka do serwera (czyli z flagami `Lax`, `Strict` oraz `None`). Jeśli użytkownik kliknie na stronę za pośrednictwem linku z innej witryny, do serwera przekazywane są pliki cookie z flagami `Lax` i `None`. Jeśli żądanie odbywa się w inny sposób, np. poprzez przesłanie formularza POST z innej witryny, załadowanie wewnątrz ramki iframe, użycie JavaScript itp, wysyłane są tylko ciasteczka z flagą `None`. +Flaga SameSite może mieć trzy wartości: `Lax`, `Strict` i `None` (ten wymaga HTTPS). Jeśli żądanie strony pochodzi bezpośrednio ze strony internetowej lub użytkownik otwiera stronę, wpisując ją bezpośrednio w pasku adresu lub klikając zakładkę, przeglądarka wysyła do serwera wszystkie pliki cookie (czyli z flagami `Lax`, `Strict` i `None`). Jeśli użytkownik przejdzie na stronę przez link z innej strony, do serwera zostaną przekazane pliki cookie z flagami `Lax` i `None`. Jeśli żądanie powstanie w inny sposób, np. przez wysłanie formularza POST z innej strony, załadowanie wewnątrz iframe, za pomocą JavaScriptu itp., wysłane zostaną tylko pliki cookie z flagą `None`. -Domyślnie Nette wysyła wszystkie ciasteczka z flagą `Lax`. +Nette domyślnie wszystkie pliki cookie wysyła z flagą `Lax`. {{leftbar: www:@menu-common}} diff --git a/nette/pt/@home.texy b/nette/pt/@home.texy index 6b35915287..68e4ef34e2 100644 --- a/nette/pt/@home.texy +++ b/nette/pt/@home.texy @@ -9,42 +9,42 @@ Documentação Nette Introdução ---------- - [Por que usar Nette? |www:10-reasons-why-nette] -- [Instalação |Installation] -- [Crie sua primeira aplicação! |quickstart:] +- [Instalação |installation] +- [Escrevendo a primeira aplicação! |quickstart:] Geral ----- -- [Lista de Pacotes |www:packages] -- [Manutenção e PHP |www:maintenance] +- [Lista de pacotes |www:packages] +- [Manutenção e versões PHP |www:maintenance] - [Notas de Lançamento |https://nette.org/releases] -- [Guia de Atualização |migrations:en] -- [nette:Solução de problemas |nette:Troubleshooting] -- [Quem Cria a Nette |https://nette.org/contributors] -- [História da Nette |www:history] -- [Envolva-se |contributing:] -- [Desenvolvimento do patrocinador |https://nette.org/en/donate] -- [Referência API |https://api.nette.org/] +- [Migrando para versões mais recentes|migrations:en] +- [Solução de problemas |nette:troubleshooting] +- [Quem faz o Nette |https://nette.org/contributors] +- [História do Nette |www:history] +- [Participe |contributing:] +- [Apoie o desenvolvimento |https://nette.org/pt/donate] +- [Referência da API |https://api.nette.org/] </div> <div> -Aplicação Nette ---------------- +Aplicações em Nette +------------------- - [Como funcionam as aplicações? |application:how-it-works] -- [Bootstrap |application:Bootstrap] -- [Apresentadores |application:Presenters] -- [Modelos |application:Templates] -- [Módulos |application:Modules] -- [Roteamento |application:Routing] -- [Criação de links URL |application:creating-links] +- [Bootstrapping |application:Bootstrapping] +- [Presenters |application:presenters] +- [Templates |application:templates] +- [Estrutura de diretórios |application:directory-structure] +- [Roteamento |application:routing] +- [Criando links URL |application:creating-links] - [Componentes interativos |application:components] -- [AJAX & Snippets |application:ajax] +- [AJAX & snippets |application:ajax] -- [Melhores Práticas |best-practices:] +- [Tutoriais e melhores práticas |best-practices:] </div> @@ -54,41 +54,42 @@ Aplicação Nette Tópicos principais ------------------ - [Configuração |nette:configuring] -- [Injeção de dependência |dependency-injection:] -- [Latte: Templates |latte:] -- [Tracy: Ferramenta de depuração |tracy:] +- [Injeção de Dependência|dependency-injection:] +- [Latte: templates |latte:] +- [Tracy: depuração de código |tracy:] - [Formulários |forms:] -- [Base de dados |database:core] -- [Autenticação de usuários |security:authentication] -- [Controle de acesso |security:authorization] +- [Banco de Dados |database:guide] +- [Login de usuários |security:authentication] +- [Verificação de permissões |security:authorization] - [Sessões |http:Sessions] -- [Solicitação e resposta HTTP |http:] -- [Caching |caching:] -- [Envio de e-mails |mail:] -- [Esquema: Validação de dados |schema:] +- [Requisição & resposta HTTP|http:] +- [Ativos |assets:] +- [Cache |caching:] +- [Enviando e-mails |mail:] +- [Schema: validação de dados |schema:] - [Gerador de código PHP |php-generator:] -- [Testador: Unidade de teste |tester:] +- [Tester: testes |tester:] </div> <div> -Utilidades ----------- -- [Arrays |utils:Arrays] +Utilitários +----------- +- [Arrays |utils:arrays] - [Sistema de arquivos |utils:filesystem] - [Finder |utils:finder] -- [Elementos HTML |utils:HTML Elements] -- [Imagens |utils:Images] +- [Elementos HTML |utils:html-elements] +- [Imagens |utils:images] - [JSON |utils:JSON] -- [NEON |neon:] -- [Hashing de senha |security:passwords] -- [SmartObject |utils:SmartObject] -- [Tipos de PHP |utils:type] -- [Cordas |utils:Strings] +- [NEON|neon:] +- [Hashing de senhas |security:passwords] +- [Tipos PHP |utils:type] +- [Strings |utils:strings] - [Validadores |utils:validators] - [RobotLoader |robot-loader:] +- [SmartObject |utils:smartobject] & [StaticClass |utils:StaticClass] - [SafeStream |safe-stream:] - [...outros |utils:] </div> @@ -97,5 +98,5 @@ Utilidades {{toc:no}} -{{description: Documentação Oficial Nette: descreve como Nette funciona e as melhores práticas para o desenvolvimento de aplicações web.}} -{{maintitle: Documentação em rede}} +{{description: Documentação oficial do Nette: descreve como o Nette funciona e as melhores práticas para o desenvolvimento de aplicações web.}} +{{maintitle: Documentação Nette}} diff --git a/nette/pt/@menu-topics.texy b/nette/pt/@menu-topics.texy index bc4b4886b7..7596822c73 100644 --- a/nette/pt/@menu-topics.texy +++ b/nette/pt/@menu-topics.texy @@ -1,21 +1,21 @@ Tópicos principais ****************** - [Configuração |nette:configuring] -- [Aplicação Nette |application:how-it-works] -- [Injeção de dependência |dependency-injection:] -- [Utilidades |utils:] +- [Aplicações em Nette |application:how-it-works] +- [Injeção de Dependência|dependency-injection:] +- [Utilitários |utils:] - [Formulários |forms:] -- [Base de dados |database:core] -- [Autenticação de usuários |security:authentication] -- [Controle de acesso |security:authorization] +- [Banco de Dados |database:guide] +- [Login de usuários |security:authentication] +- [Verificação de permissões |security:authorization] - [Sessões |http:Sessions] -- [Solicitação e resposta HTTP |http:] -- [Caching |caching:] -- [Envio de e-mails |mail:] -- [Esquema: Validação de dados |schema:] +- [Requisição & resposta HTTP|http:] +- [Cache |caching:] +- [Enviando e-mails |mail:] +- [Schema: validação de dados |schema:] - [Gerador de código PHP |php-generator:] -- [Latte: modelos |latte:] -- [Tracy: depuração |tracy:] -- [Testador: testes |tester:] +- [Latte: templates |latte:] +- [Tracy: depuração de código |tracy:] +- [Tester: testes |tester:] diff --git a/nette/pt/@meta.texy b/nette/pt/@meta.texy new file mode 100644 index 0000000000..41a853b6aa --- /dev/null +++ b/nette/pt/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentação Nette}} diff --git a/nette/pt/configuring.texy b/nette/pt/configuring.texy index 26df545846..f2e874dc10 100644 --- a/nette/pt/configuring.texy +++ b/nette/pt/configuring.texy @@ -1,35 +1,36 @@ -Configuração da Nette +Configuração do Nette ********************* .[perex] -Uma visão geral de todas as opções de configuração no Nette Framework. +Visão geral de todas as opções de configuração no Nette Framework. -Os componentes Nette são configurados utilizando arquivos de configuração, que geralmente são escritos em [NEON |neon:format]. Eles são melhor editados nos [editores que os suportam |best-practices:editors-and-tools#ide-editor]. -Se você estiver usando a estrutura completa, a configuração será [carregada durante a inicialização |application:bootstrap#di-container-configuration], se não, veja [como carregar a configuração |bootstrap:]. +Configuramos os componentes do Nette usando arquivos de configuração, que geralmente são escritos no [formato NEON|neon:format]. A melhor maneira de editá-los é em [editores com suporte |best-practices:editors-and-tools#Editor IDE]. Se você estiver usando o framework completo, a configuração é [carregada durante a inicialização da aplicação |application:bootstrapping#Configuração do contêiner de DI], caso contrário, leia [como carregar a configuração|bootstrap:]. <pre> "application .[prism-token prism-atrule]":[application:configuration#Application]: "Aplicação .[prism-token prism-comment]"<br> -"constants .[prism-token prism-atrule]":[application:configuration#Constants]: "Define as constantes PHP .[prism-token prism-comment]"<br> -"database .[prism-token prism-atrule]":[database:configuration]: "Base de dados .[prism-token prism-comment]"<br> +"assets .[prism-token prism-atrule]":[assets:configuration]: "Assets .[prism-token prism-comment]"<br> +"constants .[prism-token prism-atrule]":[application:configuration#Constantes]: "Definição de constantes PHP .[prism-token prism-comment]"<br> +"database .[prism-token prism-atrule]":[database:configuration]: "Banco de Dados .[prism-token prism-comment]"<br> "decorator .[prism-token prism-atrule]":[dependency-injection:configuration#Decorator]: "Decorador .[prism-token prism-comment]"<br> -"di .[prism-token prism-atrule]":[dependency-injection:configuration#DI]: "DI Container .[prism-token prism-comment]"<br> -"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Extensions]: "Instalar extensões DI adicionais .[prism-token prism-comment]"<br> +"di .[prism-token prism-atrule]":[dependency-injection:configuration#DI]: "Contêiner de DI .[prism-token prism-comment]"<br> +"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Extensões]: "Instalação de extensões DI adicionais .[prism-token prism-comment]"<br> "forms .[prism-token prism-atrule]":[forms:configuration]: "Formulários .[prism-token prism-comment]"<br> -"http .[prism-token prism-atrule]":[http:configuration#HTTP Headers]: "HTTP Headers .[prism-token prism-comment]"<br> -"includes .[prism-token prism-atrule]":[dependency-injection:configuration#Including files]: "Incluindo arquivos .[prism-token prism-comment]"<br> -"latte .[prism-token prism-atrule]":[application:configuration#Latte]: "Latte .[prism-token prism-comment]"<br> -"mail .[prism-token prism-atrule]":[mail:#Configuring]: "Mailing .[prism-token prism-comment]"<br> -"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#Parameters]: "Parâmetros .[prism-token prism-comment]"<br> -"php .[prism-token prism-atrule]":[application:configuration#PHP]: "Opções de configuração PHP .[prism-token prism-comment]"<br> -"routing .[prism-token prism-atrule]":[application:configuration#Routing]: "Routing .[prism-token prism-comment]"<br> -"search .[prism-token prism-atrule]":[dependency-injection:configuration#Search]: "Registro automático de serviço .[prism-token prism-comment]"<br> -"security .[prism-token prism-atrule]":[security:configuration]: "Controle de Acesso .[prism-token prism-comment]"<br> +"http .[prism-token prism-atrule]":[http:configuration#Cabeçalhos HTTP]: "Cabeçalhos HTTP .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[dependency-injection:configuration#Inclusão de arquivos]: "Inclusão de arquivos .[prism-token prism-comment]"<br> +"latte .[prism-token prism-atrule]":[application:configuration#Templates Latte]: "Templates Latte .[prism-token prism-comment]"<br> +"mail .[prism-token prism-atrule]":[mail:#Configuração]: "E-mails .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#Parâmetros]: "Parâmetros .[prism-token prism-comment]"<br> +"php .[prism-token prism-atrule]":[application:configuration#PHP]: "Configuração PHP .[prism-token prism-comment]"<br> +"routing .[prism-token prism-atrule]":[application:configuration#Roteamento]: "Roteamento .[prism-token prism-comment]"<br> +"search .[prism-token prism-atrule]":[dependency-injection:configuration#Search]: "Registro automático de serviços .[prism-token prism-comment]"<br> +"security .[prism-token prism-atrule]":[security:configuration]: "Permissões de acesso .[prism-token prism-comment]"<br> "services .[prism-token prism-atrule]":[dependency-injection:services]: "Serviços .[prism-token prism-comment]"<br> -"session .[prism-token prism-atrule]":[http:configuration#Session]: "Sessão .[prism-token prism-comment]"<br> -"tracy .[prism-token prism-atrule]":[tracy:configuring#Nette Framework]: "Tracy Debugger .[prism-token prism-comment]" +"session .[prism-token prism-atrule]":[http:configuration#Sessão]: "Sessão .[prism-token prism-comment]"<br> +"tracy .[prism-token prism-atrule]":[tracy:configuring#Nette Framework]: "Depurador Tracy .[prism-token prism-comment]" </pre> -Para escrever um texto contendo o caracter `%`, you must escape it by doubling it to `%%`. .[note] +.[note] +Para escrever uma string contendo o caractere `%`, você deve escapá-lo duplicando-o para `%%`. {{leftbar: @menu-topics}} diff --git a/nette/pt/glossary.texy b/nette/pt/glossary.texy index 6345b92387..f682bd4039 100644 --- a/nette/pt/glossary.texy +++ b/nette/pt/glossary.texy @@ -2,154 +2,158 @@ Glossário de Termos ******************* -AJAX .[#toc-ajax] ------------------ -JavaScript e XML assíncronos - tecnologia para comunicação cliente-servidor sobre o protocolo HTTP sem a necessidade de recarregar a página inteira durante cada solicitação. Apesar da sigla, o formato [JSON |#JSON] é freqüentemente utilizado ao invés do XML. +AJAX +---- +Asynchronous JavaScript and XML - tecnologia de troca de informações entre cliente e servidor via protocolo HTTP sem a necessidade de recarregar a página inteira a cada requisição. Embora o nome possa sugerir que envia dados apenas no formato XML, o formato [#JSON] também é comumente usado. -Ação do apresentador .[#toc-presenter-action] ---------------------------------------------- -Parte lógica do [apresentador |#presenter], realizando uma ação, como mostrar uma página de produto, assinar um usuário, etc. Um apresentador pode ter mais ações. +Ação (do presenter) +------------------- +Parte lógica do presenter que executa uma ação. Por exemplo, exibe a página do produto, desloga o usuário, etc. Um presenter pode ter várias ações. BOM --- -A chamada máscara de ordem de byte* é um primeiro caracter especial de um arquivo e indica a ordem de byte na codificação. Alguns editores a incluem automaticamente, é praticamente invisível, mas causa problemas com cabeçalhos e envio de saída de dentro do PHP. Você pode usar o [Code Checker |code-checker:] para remoção em massa. +O chamado *byte order mark* é um caractere especial no início de um arquivo, usado como indicador da ordem dos bytes na codificação. Alguns editores o inserem nos arquivos. É praticamente invisível, mas causa problemas com o envio de saída e cabeçalhos do PHP. Para remoção em massa, você pode usar o [Code Checker|code-checker:]. -Controlador .[#toc-controller] ------------------------------- -O controlador processa as solicitações do usuário e, com base nelas, chama uma determinada lógica de aplicação (ou seja, [modelo |#model]), depois chama a [visualização |#view] para a renderização dos dados. Analogia para os controladores são [os apresentadores |#presenter] no Nette Framework. +Controller +---------- +Controlador que processa as requisições do usuário e, com base nelas, chama a lógica da aplicação apropriada (ou seja, [#model]) e, em seguida, solicita ao [#view] a renderização dos dados. O equivalente aos controllers no Nette Framework são os [presenters |#Presenter]. -Roteiro transversal (XSS) .[#toc-cross-site-scripting-xss] ----------------------------------------------------------- -O Cross-Site Scripting é um método de interrupção do site que utiliza entrada não modelada. Um atacante pode injetar seu próprio código HTML ou JavaScript e mudar a aparência da página ou mesmo reunir informações sensíveis sobre os usuários. A proteção contra XSS é simples: fuga consistente e correta de todas as cadeias de caracteres e entradas. +Cross-Site Scripting (XSS) +-------------------------- +Cross-Site Scripting é um método de violação de sites que explora saídas não tratadas. O invasor pode então injetar seu próprio código na página e, assim, modificá-la ou até mesmo obter dados confidenciais dos visitantes. A única maneira de se defender contra XSS é tratar todas as strings de forma consistente e correta. -Nette Framework vem com uma novíssima tecnologia de [Context-Aware Escaping |latte:safety-first#context-aware-escaping], que o livrará dos riscos do Cross-Site Scripting para sempre. Ela escapa automaticamente de todas as entradas baseadas em um determinado contexto, de modo que é impossível para um codificador esquecer acidentalmente algo. +O Nette Framework vem com uma tecnologia revolucionária [Context-Aware Escaping |latte:safety-first#Escaping sensível ao contexto], que o livrará para sempre do risco de Cross-Site Scripting. Ele trata todas as saídas automaticamente, para que não haja chance de o codificador esquecer algo. -Falsificação de pedido entre locais (CSRF) .[#toc-cross-site-request-forgery-csrf] ----------------------------------------------------------------------------------- -Um ataque de Pedido Cruzado de Falsificação é que o atacante atrai a vítima para visitar uma página que executa silenciosamente um pedido no navegador da vítima para o servidor onde a vítima está atualmente logada, e o servidor acredita que o pedido foi feito pela vítima à sua vontade. O servidor executa uma determinada ação sob a identidade da vítima, mas sem que a vítima se dê conta disso. Ele pode estar alterando ou apagando dados, enviando uma mensagem, etc. +Cross-Site Request Forgery (CSRF) +--------------------------------- +O ataque Cross-Site Request Forgery consiste em o invasor atrair a vítima para uma página que executa discretamente uma requisição no navegador da vítima para um servidor no qual a vítima está logada, e o servidor acredita que a requisição foi feita pela vítima por vontade própria. Assim, sob a identidade da vítima, realiza uma determinada ação sem que ela saiba. Pode ser a alteração ou exclusão de dados, envio de uma mensagem, etc. -Nette Framework ** protege automaticamente as formas e sinais nos apresentadores*** deste tipo de ataque. Isto é feito impedindo que eles sejam enviados ou chamados de outro domínio. +O Nette Framework **protege automaticamente formulários e sinais nos presenters** contra este tipo de ataque. Isso é feito impedindo que sejam enviados ou acionados de outro domínio. -Injeção de dependência .[#toc-dependency-injection] ---------------------------------------------------- -A Injeção de Dependência (DI) é um padrão de design que diz como separar a criação de objetos de suas dependências. Ou seja, uma classe não é responsável por criar ou inicializar suas dependências, mas, em vez disso, essas dependências são fornecidas por um código externo (que pode incluir um [recipiente DI |#Dependency Injection container]). A vantagem é que ela permite maior flexibilidade de código, melhor legibilidade e testes de aplicação mais fáceis porque as dependências são facilmente substituíveis e isoladas de outras partes do código. Para mais informações, veja [O que é Injeção de Dependência? |dependency-injection:introduction] +Dependency Injection +-------------------- +Dependency Injection (DI) é um padrão de projeto que diz como separar a criação de objetos de suas dependências. Ou seja, a classe não é responsável por criar ou inicializar suas dependências, mas, em vez disso, essas dependências são fornecidas a ela por código externo (que pode ser um [#Contêiner de DI]). A vantagem é que permite maior flexibilidade do código, melhor legibilidade e testes mais fáceis da aplicação, pois as dependências são facilmente substituíveis e isoladas de outras partes do código. Mais no capítulo [O que é Injeção de Dependência? |dependency-injection:introduction] -Dependência Recipiente de injeção .[#toc-dependency-injection-container] ------------------------------------------------------------------------- -Um recipiente de injeção de dependência (também recipiente DI ou IoC) é uma ferramenta que lida com a criação e gestão de dependências em uma aplicação (ou [serviços |#service]). Um container geralmente tem uma configuração que define quais classes são dependentes de outras classes, quais implementações específicas de dependência a serem usadas e como criar essas dependências. O contêiner então cria esses objetos e os fornece às classes que deles necessitam. Para mais informações, veja [O que é um contêiner DI? |dependency-injection:container] +Contêiner de DI +--------------- +Contêiner de Injeção de Dependência (também Contêiner de DI ou Contêiner IoC) é uma ferramenta que cuida da criação e gerenciamento de dependências na aplicação (ou seja, [serviços |#Serviço]). O contêiner geralmente tem uma configuração que define quais classes dependem de outras classes, quais implementações específicas de dependências devem ser usadas e como essas dependências devem ser criadas. Em seguida, o contêiner cria esses objetos e os fornece às classes que precisam deles. Mais no capítulo [O que é um Contêiner de DI? |dependency-injection:container] -Fugindo .[#toc-escaping] ------------------------- -Escaping é a conversão de caracteres com significado especial em determinado contexto para outras seqüências equivalentes. Exemplo: Queremos escrever citações entre aspas - cadeia fechada. Como as aspas têm um significado especial no contexto da cadeia de caracteres fechada por aspas, há a necessidade de usar outra seqüência equivalente. A seqüência concreta é determinada pelas regras de contexto (por exemplo `\"` na cadeia de caracteres fechada entre aspas do PHP, `"` nos atributos HTML, etc.). +Escaping +-------- +Escaping é a conversão de caracteres que têm um significado especial em um determinado contexto para outras sequências correspondentes. Exemplo: queremos escrever aspas em uma string delimitada por aspas. Como as aspas têm um significado especial no contexto da string e sua simples escrita seria entendida como o fim da string, é necessário escrevê-las usando outra sequência correspondente. Qual exatamente é determinada pelas regras do contexto. -Filtro (Anteriormente Helper) .[#toc-filter-formerly-helper] ------------------------------------------------------------- -Função de filtro. Nos modelos, o [filtro |latte:syntax#filters] é uma função, que ajuda a alterar ou formatar os dados para a forma de saída. Os gabaritos têm vários [filtros padrão |latte:filters] predefinidos. +Filtro (Latte) +-------------- +Nos templates, o termo [filtro |latte:syntax#Filtros] geralmente se refere a uma função que ajuda a modificar ou reformatar dados para a forma final. Os templates possuem vários [filtros padrão |latte:filters]. -Invalidação .[#toc-invalidation] --------------------------------- -Aviso de um [corte |#snippet] a ser entregue de novo. Em outro contexto, também a liberação de um cache. +Invalidação +----------- +Notificação para um [#snippet] para que ele seja redesenhado. Em outro sentido, também a exclusão do conteúdo do cache. -JSON .[#toc-json] ------------------ -Formato de intercâmbio de dados baseado na sintaxe JavaScript (é seu subconjunto). As especificações exatas podem ser encontradas em www.json.org. +JSON +---- +Formato para troca de dados baseado na sintaxe JavaScript (é um subconjunto dela). A especificação exata pode ser encontrada em www.json.org. -Componente .[#toc-component] ----------------------------- -Parte reutilizável de uma aplicação. Pode ser uma parte visual de uma página, como descrito no capítulo [componentes |application:components], ou o termo também pode significar a classe [Componente |component-model:] (tal componente não tem que ser visual). +Componente +---------- +Parte reutilizável da aplicação. Pode ser uma parte visual da página, como descrito no capítulo [Escrevendo Componentes |application:components], ou o termo componente também se refere à classe [Component |component-model:] (tal componente não precisa ser visual). -Caracteres de controle .[#toc-control-characters] -------------------------------------------------- -Os caracteres de controle são caracteres invisíveis, que podem ocorrer em um texto e eventualmente causar alguns problemas. Para sua remoção em massa de arquivos, você pode usar o [Code Checker |code-checker:], para sua remoção de uma função de uso variável [Strings::normalize() |utils:strings#normalize]. +Caracteres de controle +---------------------- +Caracteres de controle são caracteres invisíveis que podem ocorrer no texto e, eventualmente, causar problemas. Para sua remoção em massa de arquivos, você pode usar o [Code Checker|code-checker:] e para remover de uma variável, a função [Strings::normalize() |utils:strings#normalize]. -Eventos .[#toc-events] ----------------------- -Um evento é uma situação esperada no objeto, que quando ocorre, os chamados manipuladores são chamados, ou seja, chamadas de retorno reagindo ao evento ("amostra":https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38). O evento pode ser, por exemplo, envio de formulário, login de usuário, etc. Os eventos são, portanto, uma forma de *Inversão de Controle*. +Eventos +------- +Um evento é uma situação esperada em um objeto, que quando ocorre, chama os chamados handlers, ou seja, callbacks que reagem ao evento ("exemplo":https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38). Um evento pode ser, por exemplo, o envio de um formulário, o login de um usuário, etc. Os eventos são, portanto, uma forma de *Inversion of Control*. -Por exemplo, um login de usuário ocorre no método `Nette\Security\User::login()`. O objeto `User` tem uma variável pública `$onLoggedIn`, que é um array ao qual qualquer pessoa pode adicionar uma chamada de retorno. Assim que o usuário faz o login, o método `login()` chama todas as chamadas de retorno no array. O nome de uma variável no formulário `onXyz` é uma convenção usada em toda a Nette. +Por exemplo, o login do usuário ocorre no método `Nette\Security\User::login()`. O objeto `User` tem uma variável pública `$onLoggedIn`, que é um array ao qual qualquer um pode adicionar um callback. No momento em que o usuário faz login, o método `login()` chama todos os callbacks no array. O nome da variável no formato `onXyz` é uma convenção usada em todo o Nette. -Latte .[#toc-latte] -------------------- -Um dos [sistemas de modelos |latte:] mais inovadores de todos os tempos. +Latte +----- +Um dos mais avançados [sistemas de template |latte:]. -Modelo .[#toc-model] --------------------- -O modelo representa a base de dados e funções de toda a aplicação. Ele inclui toda a lógica da aplicação (às vezes também referida como "lógica comercial"). É o **M*** de **M***VC ou MPV. Qualquer ação do usuário (login, colocar coisas na cesta, mudança do valor de um banco de dados) representa uma ação do modelo. +Model +----- +O modelo é a base de dados e, especialmente, funcional de toda a aplicação. Contém toda a lógica da aplicação (o termo lógica de negócios também é usado). É o **M** de **M**VC ou MVP. Qualquer ação do usuário (login, adicionar item ao carrinho, alterar valor no banco de dados) representa uma ação do modelo. -O modelo gerencia seu estado interno e fornece uma interface pública. Ao chamar esta interface, podemos tomar ou mudar seu estado. O modelo não sabe da existência de uma [visão |#view] ou [controlador |#controller], o modelo é totalmente independente sobre eles. +O modelo gerencia seu estado interno e oferece uma interface fixa para o exterior. Chamando as funções desta interface, podemos consultar ou alterar seu estado. O modelo não sabe da existência do [#view] ou do [#controller]. -Model-View-Controller .[#toc-model-view-controller] ---------------------------------------------------- -Arquitetura de software, que surgiu no desenvolvimento de aplicações GUI para separar o código para o controle de fluxo ([controlador |#controller]) do código da lógica da aplicação ([modelo |#model]) e do código de renderização de dados ([view |#view]). Dessa forma, o código é mais compreensível, facilita o desenvolvimento futuro e permite testar partes separadas separadamente. +Model-View-Controller +--------------------- +Arquitetura de software que surgiu da necessidade de separar, em aplicações com interface gráfica, o código de manipulação ([#controller]) do código da lógica da aplicação ([#model]) e do código que exibe os dados ([#view]). Isso torna a aplicação mais clara, facilita o desenvolvimento futuro e permite testar partes individuais separadamente. -Model-View-Presenter .[#toc-model-view-presenter] -------------------------------------------------- -Arquitetura baseada em [Model-View-Controller |#Model-View-Controller]. +Model-View-Presenter +-------------------- +Arquitetura baseada no [#Model-View-Controller]. -Módulo .[#toc-module] ---------------------- -[O módulo |application:modules] em Nette Framework representa uma coleção de apresentadores e modelos, eventualmente também componentes e modelos, que servem dados a um apresentador. Portanto, é uma certa parte lógica de uma aplicação. +Módulo +------ +Um módulo representa uma parte lógica da aplicação. Em um arranjo típico, é um grupo de presenters e templates que lidam com uma área específica de funcionalidade. Colocamos os módulos em [diretórios separados |application:directory-structure#Presenters e templates], como `Front/`, `Admin/` ou `Shop/`. + +Por exemplo, dividimos uma loja virtual em: +- Frontend (`Shop/`) para visualização de produtos e compra +- Seção do cliente (`Customer/`) para gerenciamento de pedidos +- Administração (`Admin/`) para o operador + +Tecnicamente, são diretórios comuns, mas graças à organização clara, ajudam a escalar a aplicação. O presenter `Admin:Product:List` estará fisicamente localizado, por exemplo, no diretório `app/Presentation/Admin/Product/List/` (veja [mapeamento de presenters |application:directory-structure#Mapeamento de presenters]). + -Por exemplo, uma e-shop pode ter três módulos: -1) Catálogo de produtos com cesta. -2) Administração para o cliente. -3) Administração para o lojista. +Namespace +--------- +Namespace, parte da linguagem PHP desde a versão 5.3 e algumas outras linguagens de programação, permitindo o uso de classes que são nomeadas da mesma forma em diferentes bibliotecas sem causar conflito de nomes. Veja a [documentação do PHP |https://www.php.net/manual/en/language.namespaces.rationale.php]. -Namespace .[#toc-namespace] ---------------------------- -O espaço de nomes é uma característica da linguagem PHP de sua versão 5.3 e de algumas outras linguagens de programação também. Ele ajuda a evitar colisões de nomes (por exemplo, duas classes com o mesmo nome) ao usar bibliotecas diferentes juntas. Veja a [documentação PHP |https://www.php.net/manual/en/language.namespaces.rationale.php] para mais detalhes. +Presenter +--------- +Presenter é um objeto que pega a [requisição |api:Nette\Application\Request] traduzida pelo roteador da requisição HTTP e gera uma [resposta |api:Nette\Application\Response]. A resposta pode ser uma página HTML, imagem, documento XML, arquivo em disco, JSON, redirecionamento ou qualquer coisa que você inventar. +Geralmente, o termo presenter se refere a um descendente da classe [api:Nette\Application\UI\Presenter]. De acordo com as requisições recebidas, ele executa as [ações |application:presenters#Ciclo de vida do presenter] correspondentes e renderiza os templates. -Apresentador .[#toc-presenter] ------------------------------- -O apresentador é um objeto, que toma a [solicitação |api:Nette\Application\Request] traduzida pelo roteador a partir da solicitação HTTP e gera uma [resposta |api:Nette\Application\Response]. A resposta pode ser uma página HTML, imagem, documento XML, arquivo, JSON, redirecionamento ou o que quer que você pense. -Por um apresentador, geralmente se entende um descendente da classe [api:Nette\Application\UI\Presenter]. Por solicitação, ele executa [ações |application:presenters#life-cycle-of-presenter] apropriadas e apresenta modelos. +Roteador +-------- +Tradutor bidirecional entre requisição HTTP / URL e ação do presenter. Bidirecional significa que a partir da requisição HTTP é possível derivar a [#ação do presenter], mas também o inverso, gerar a URL correspondente para a ação. Mais no capítulo sobre [roteamento de URL |application:routing]. -Roteador .[#toc-router] ------------------------ -Tradutor bidirecional entre solicitação HTTP / URL e ação do apresentador. Bi-direcional significa que não só é possível derivar uma [ação do apresentador |#presenter action] a partir da solicitação HTTP, mas também gerar a URL apropriada para uma ação. Veja mais no capítulo sobre [roteamento de URL |application:routing]. +SameSite cookie +--------------- +Os cookies SameSite fornecem um mecanismo para reconhecer o que levou ao carregamento da página. Pode ter três valores: `Lax`, `Strict` e `None` (este requer HTTPS). Se a requisição para a página vier diretamente do site ou o usuário abrir a página digitando diretamente na barra de endereços ou clicando em um favorito, o navegador enviará todos os cookies ao servidor (ou seja, com os sinalizadores `Lax`, `Strict` e `None`). Se o usuário clicar em um link de outro site para acessar o site, os cookies com os sinalizadores `Lax` e `None` serão passados para o servidor. Se a requisição surgir de outra forma, como o envio de um formulário POST de outro site, carregamento dentro de um iframe, usando JavaScript, etc., apenas os cookies com o sinalizador `None` serão enviados. -Cookie SameSite .[#toc-samesite-cookie] ---------------------------------------- -Os cookies do SameSite fornecem um mecanismo para reconhecer o que levou à carga da página. Ele pode ter três valores: `Lax`, `Strict` e `None` (este último requer HTTPS). Se a solicitação para a página vier diretamente do site ou o usuário abrir a página digitando diretamente na barra de endereço ou clicando em um bookmark, o navegador envia todos os cookies para o servidor (ou seja, com as bandeiras `Lax`, `Strict` e `None`). Se o usuário clicar no site através de um link de outro site, os cookies com as bandeiras `Lax` e `None` são passados para o servidor. Se a solicitação for feita por outros meios, tais como envio de um formulário POST de outro site, carregamento dentro de um iframe, usando JavaScript, etc., somente serão enviados cookies com as bandeiras `None`. +Serviço +------- +No contexto da Injeção de Dependência, um serviço refere-se a um objeto que é criado e gerenciado pelo contêiner de DI. Um serviço pode ser facilmente substituído por outra implementação, por exemplo, para fins de teste ou para alterar o comportamento da aplicação, sem a necessidade de modificar o código que usa o serviço. -Serviço .[#toc-service] ------------------------ -No contexto da injeção por dependência, um serviço refere-se a um objeto que é criado e administrado por um recipiente DI. Um serviço pode ser facilmente substituído por outra implementação, por exemplo, para fins de teste ou para alterar o comportamento de uma aplicação, sem ter que modificar o código que utiliza o serviço. +Snippet +------- +Fragmento, parte da página que pode ser redesenhada separadamente durante uma requisição AJAX. -Snippet .[#toc-snippet] ------------------------ -Corte de uma página, que pode ser entregue separadamente durante um pedido [AJAX |#AJAX]. +View +---- +View, ou visão, é a camada da aplicação responsável por exibir o resultado da requisição. Geralmente usa um sistema de templates e sabe como exibir um determinado componente ou o resultado obtido do modelo. -Ver .[#toc-view] ----------------- -View é uma camada de aplicação, que é responsável pela apresentação dos resultados da solicitação. Normalmente utiliza um sistema de modelagem e sabe, como renderizar seus componentes ou resultados extraídos do modelo. diff --git a/nette/pt/installation.texy b/nette/pt/installation.texy index 66d80dec1f..2a7916032a 100644 --- a/nette/pt/installation.texy +++ b/nette/pt/installation.texy @@ -2,66 +2,66 @@ Instalação do Nette ******************* .[perex] -Deseja aproveitar os benefícios do Nette em seu projeto existente ou está planejando criar um novo projeto baseado no Nette? Este guia o orientará passo a passo na instalação. +Quer aproveitar as vantagens do Nette em seu projeto existente ou está planejando criar um novo projeto baseado no Nette? Este guia o levará pela instalação passo a passo. -Como adicionar o Nette ao seu projeto .[#toc-how-to-add-nette-to-your-project] ------------------------------------------------------------------------------- +Como adicionar o Nette ao seu projeto +------------------------------------- -O Nette oferece uma coleção de pacotes (bibliotecas) úteis e sofisticados para PHP. Para incorporá-los em seu projeto, siga estas etapas: +O Nette oferece uma coleção de pacotes (bibliotecas) úteis e avançados para PHP. Para incorporá-los ao seu projeto, siga estas etapas: -1) **Configure o [Composer |best-practices:composer]:** Essa ferramenta é essencial para facilitar a instalação, a atualização e o gerenciamento das bibliotecas necessárias para o seu projeto. +1) **Prepare o [Composer|best-practices:composer]:** Esta ferramenta é essencial para instalar, atualizar e gerenciar facilmente as bibliotecas necessárias para o seu projeto. -2) **Escolha um [pacote |www:packages]:** Digamos que você precise navegar no sistema de arquivos, o que [o Finder |utils:finder] do pacote `nette/utils` faz de forma excelente. Você pode encontrar o nome do pacote na coluna da direita da documentação. +2) **Escolha um [pacote|www:packages]:** Digamos que você precise navegar pelo sistema de arquivos, o que o [Finder|utils:finder] do pacote `nette/utils` faz muito bem. Você pode ver o nome do pacote na coluna direita de sua documentação. -3) **Instale o pacote:** Execute este comando no diretório raiz de seu projeto: +3) **Instale o pacote:** Execute este comando no diretório raiz do seu projeto: ```shell composer require nette/utils ``` -Você prefere uma interface gráfica? Confira o [guia |https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] sobre a instalação de pacotes no ambiente PhpStrom. +Prefere uma interface gráfica? Confira o [guia|https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] para instalar pacotes no ambiente PhpStorm. -Como iniciar um novo projeto com o Nette .[#toc-how-to-start-a-new-project-with-nette] --------------------------------------------------------------------------------------- +Como iniciar um novo projeto com o Nette +---------------------------------------- -Se você quiser criar um projeto totalmente novo na plataforma Nette, recomendamos usar o esqueleto predefinido do [Projeto Web |https://github.com/nette/web-project]: +Se você deseja criar um projeto totalmente novo na plataforma Nette, recomendamos usar o esqueleto pré-configurado [Web Project|https://github.com/nette/web-project]: -1) **Configure o [Composer |best-practices:composer].** +1) **Prepare o [Composer|best-practices:composer].** -2) **Abra a linha de comando** e navegue até o diretório raiz do seu servidor Web, por exemplo, `/etc/var/www`, `C:/xampp/htdocs`, `/Library/WebServer/Documents`. +2) **Abra a linha de comando** e navegue até o diretório raiz do seu servidor web, por exemplo, `/etc/var/www`, `C:/xampp/htdocs`, `/Library/WebServer/Documents`. 3) **Crie o projeto** usando este comando: ```shell -composer create-project nette/web-project PROJECT_NAME +composer create-project nette/web-project NOME_DO_PROJETO ``` -4) **Não está usando o Composer? Basta fazer o download do [Projeto Web em formato ZIP |https://github.com/nette/web-project/archive/preloaded.zip] e extraí-lo. Mas confie em nós, o Composer vale a pena! +4) **Não usa o Composer?** Basta baixar o [Web Project em formato ZIP|https://github.com/nette/web-project/archive/preloaded.zip] e descompactá-lo. Mas acredite, o Composer vale a pena! -5) **Definição de permissões:** Nos sistemas macOS ou Linux, defina [permissões de gravação |nette:troubleshooting#Setting directory permissions] para os diretórios. +5) **Configuração de permissões:** Em sistemas macOS ou Linux, defina as [permissões de escrita |nette:troubleshooting#Configurando Permissões de Diretório] para os diretórios `temp/` e `log/`. -6) **Abrir o projeto em um navegador:** Digite o URL `http://localhost/PROJECT_NAME/www/`. Você verá a página inicial do esqueleto: +6) **Abrindo o projeto no navegador:** Digite a URL `http://localhost/NOME_DO_PROJETO/www/` e você verá a página inicial do esqueleto: -[* qs-welcome.webp .{url: http://localhost/PROJECT_NAME/www/} *] +[* qs-welcome.webp .{url: http://localhost/NOME_DO_PROJETO/www/} *] -Parabéns! Seu site agora está pronto para o desenvolvimento. Sinta-se à vontade para remover o modelo de boas-vindas e começar a criar seu aplicativo. +Parabéns! Seu site agora está pronto para desenvolvimento. Você pode remover o template de boas-vindas e começar a criar sua aplicação. -Uma das vantagens do Nette é que o projeto funciona imediatamente sem a necessidade de configuração. No entanto, se você encontrar algum problema, considere dar uma olhada nas [soluções de problemas comuns |nette:troubleshooting#nette-is-not-working-white-page-is-displayed]. +Uma das vantagens do Nette é que o projeto funciona imediatamente sem a necessidade de configuração. No entanto, se você encontrar problemas, tente consultar as [soluções para problemas comuns |nette:troubleshooting#O Nette não funciona uma página em branco é exibida]. .[note] -Se estiver começando a usar o Nette, recomendamos que continue com o [tutorial Criar seu primeiro aplicativo |quickstart:]. +Se você está começando com o Nette, recomendamos continuar com o [tutorial Escrevendo a primeira aplicação|quickstart:]. -Ferramentas e recomendações .[#toc-tools-and-recommendations] -------------------------------------------------------------- +Ferramentas e Recomendações +--------------------------- -Para um trabalho eficiente com a Nette, recomendamos as seguintes ferramentas: +Para trabalhar eficientemente com o Nette, recomendamos as seguintes ferramentas: -- [IDE de alta qualidade com plug-ins para Nette |best-practices:editors-and-tools] +- [IDE de qualidade com plugins para Nette|best-practices:editors-and-tools] - Sistema de controle de versão Git -- [Compositor |best-practices:composer] +- [Composer|best-practices:composer] {{leftbar: www:@menu-common}} diff --git a/nette/pt/introduction-to-object-oriented-programming.texy b/nette/pt/introduction-to-object-oriented-programming.texy new file mode 100644 index 0000000000..bc029f24d9 --- /dev/null +++ b/nette/pt/introduction-to-object-oriented-programming.texy @@ -0,0 +1,841 @@ +Introdução à programação orientada a objetos +******************************************** + +.[perex] +O termo "POO" refere-se à programação orientada a objetos, que é uma forma de organizar e estruturar o código. A POO permite-nos ver um programa como um conjunto de objetos que comunicam entre si, em vez de uma sequência de comandos e funções. + +Na POO, um "objeto" é uma unidade que contém dados e funções que operam sobre esses dados. Os objetos são criados a partir de "classes", que podemos entender como projetos ou modelos para objetos. Quando temos uma classe, podemos criar a sua "instância", que é um objeto específico criado a partir dessa classe. + +Vamos mostrar como podemos criar uma classe simples em PHP. Ao definir uma classe, usamos a palavra-chave "class", seguida pelo nome da classe e, em seguida, chaves que envolvem as funções (chamadas de "métodos") e variáveis da classe (chamadas de "propriedades"): + +```php +class Carro +{ + function buzinar() + { + echo 'Bip bip!'; + } +} +``` + +Neste exemplo, criamos uma classe chamada `Carro` com uma função (ou "método") chamada `buzinar`. + +Cada classe deve resolver apenas uma tarefa principal. Se uma classe faz muitas coisas, pode ser apropriado dividi-la em classes menores e especializadas. + +Normalmente, guardamos as classes em arquivos separados para manter o código organizado e fácil de navegar. O nome do arquivo deve corresponder ao nome da classe, portanto, para a classe `Carro`, o nome do arquivo seria `Carro.php`. + +Ao nomear classes, é bom seguir a convenção "PascalCase", o que significa que cada palavra no nome começa com uma letra maiúscula e não há sublinhados ou outros separadores entre elas. Métodos e propriedades usam a convenção "camelCase", o que significa que começam com uma letra minúscula. + +Alguns métodos em PHP têm tarefas especiais e são prefixados com `__` (dois sublinhados). Um dos métodos especiais mais importantes é o "construtor", que é marcado como `__construct`. O construtor é um método que é chamado automaticamente quando você cria uma nova instância da classe. + +Frequentemente usamos o construtor para definir o estado inicial de um objeto. Por exemplo, ao criar um objeto que representa uma pessoa, você pode usar o construtor para definir sua idade, nome ou outras propriedades. + +Vamos ver como usar um construtor em PHP: + +```php +class Pessoa +{ + private $idade; + + function __construct($idade) + { + $this->idade = $idade; + } + + function quantosAnosVoceTem() + { + return $this->idade; + } +} + +$pessoa = new Pessoa(25); +echo $pessoa->quantosAnosVoceTem(); // Imprime: 25 +``` + +Neste exemplo, a classe `Pessoa` tem uma propriedade (variável) `$idade` e um construtor que define essa propriedade. O método `quantosAnosVoceTem()` permite então aceder à idade da pessoa. + +A pseudovariável `$this` é usada dentro da classe para aceder às propriedades e métodos do objeto. + +A palavra-chave `new` é usada para criar uma nova instância da classe. No exemplo acima, criamos uma nova pessoa com 25 anos. + +Você também pode definir valores padrão para os parâmetros do construtor, caso não sejam especificados ao criar o objeto. Por exemplo: + +```php +class Pessoa +{ + private $idade; + + function __construct($idade = 20) + { + $this->idade = $idade; + } + + function quantosAnosVoceTem() + { + return $this->idade; + } +} + +$pessoa = new Pessoa; // se não passarmos nenhum argumento, os parênteses podem ser omitidos +echo $pessoa->quantosAnosVoceTem(); // Imprime: 20 +``` + +Neste exemplo, se você não especificar a idade ao criar o objeto `Pessoa`, o valor padrão 20 será usado. + +É conveniente que a definição da propriedade com sua inicialização através do construtor possa ser abreviada e simplificada desta forma: + +```php +class Pessoa +{ + function __construct( + private $idade = 20, + ) { + } +} +``` + +Para completar, além dos construtores, os objetos também podem ter destrutores (método `__destruct`), que são chamados antes que o objeto seja libertado da memória. + + +Namespaces +---------- + +Namespaces (ou "namespaces" em inglês) permitem-nos organizar e agrupar classes, funções e constantes relacionadas, evitando ao mesmo tempo conflitos de nomes. Pode imaginá-los como pastas num computador, onde cada pasta contém arquivos que pertencem a um determinado projeto ou tópico. + +Namespaces são especialmente úteis em projetos maiores ou quando se usam bibliotecas de terceiros, onde podem ocorrer conflitos nos nomes das classes. + +Imagine que tem uma classe chamada `Carro` no seu projeto e quer colocá-la num namespace chamado `Transporte`. Faria isso da seguinte forma: + +```php +namespace Transporte; + +class Carro +{ + function buzinar() + { + echo 'Bip bip!'; + } +} +``` + +Se quiser usar a classe `Carro` noutro arquivo, precisa especificar de qual namespace a classe vem: + +```php +$carro = new Transporte\Carro; +``` + +Para simplificar, pode indicar no início do arquivo qual classe de um determinado namespace deseja usar, o que permite criar instâncias sem a necessidade de especificar o caminho completo: + +```php +use Transporte\Carro; + +$carro = new Carro; +``` + + +Herança +------- + +A herança é uma ferramenta da programação orientada a objetos que permite criar novas classes com base em classes existentes, herdando suas propriedades e métodos e estendendo-os ou redefinindo-os conforme necessário. A herança permite garantir a reutilização de código e a hierarquia de classes. + +Simplificando, se tivermos uma classe e quisermos criar outra derivada dela, mas com algumas alterações, podemos "herdar" a nova classe da classe original. + +Em PHP, a herança é realizada usando a palavra-chave `extends`. + +Nossa classe `Pessoa` armazena informações sobre a idade. Podemos ter outra classe `Estudante`, que estende `Pessoa` e adiciona informações sobre a área de estudo. + +Vejamos um exemplo: + +```php +class Pessoa +{ + private $idade; + + function __construct($idade) + { + $this->idade = $idade; + } + + function exibirInformacoes() + { + echo "Idade: {$this->idade} anos\n"; + } +} + +class Estudante extends Pessoa +{ + private $curso; + + function __construct($idade, $curso) + { + parent::__construct($idade); + $this->curso = $curso; + } + + function exibirInformacoes() + { + parent::exibirInformacoes(); + echo "Curso: {$this->curso} \n"; + } +} + +$estudante = new Estudante(20, 'Informática'); +$estudante->exibirInformacoes(); +``` + +Como este código funciona? + +- Usamos a palavra-chave `extends` para estender a classe `Pessoa`, o que significa que a classe `Estudante` herdará todos os métodos e propriedades de `Pessoa`. + +- A palavra-chave `parent::` permite-nos chamar métodos da classe pai. Neste caso, chamamos o construtor da classe `Pessoa` antes de adicionar a nossa própria funcionalidade à classe `Estudante`. E da mesma forma, o método `exibirInformacoes()` do ancestral antes de exibir as informações do estudante. + +A herança destina-se a situações em que existe uma relação "é um" entre as classes. Por exemplo, `Estudante` é uma `Pessoa`. Gato é um animal. Dá-nos a possibilidade, nos casos em que esperamos um objeto (por exemplo, "Pessoa") no código, usar em vez disso um objeto herdado (por exemplo, "Estudante"). + +É importante notar que o principal propósito da herança **não é** evitar a duplicação de código. Pelo contrário, o uso incorreto da herança pode levar a código complexo e difícil de manter. Se a relação "é um" entre as classes não existir, devemos considerar a composição em vez da herança. + +Note que os métodos `exibirInformacoes()` nas classes `Pessoa` e `Estudante` exibem informações ligeiramente diferentes. E podemos adicionar outras classes (por exemplo, `Funcionario`), que fornecerão outras implementações deste método. A capacidade de objetos de diferentes classes responderem ao mesmo método de maneiras diferentes é chamada de polimorfismo: + +```php +$pessoas = [ + new Pessoa(30), + new Estudante(20, 'Informática'), + new Funcionario(45, 'Diretor'), // Supondo que a classe Funcionario existe +]; + +foreach ($pessoas as $pessoa) { + $pessoa->exibirInformacoes(); +} +``` + + +Composição +---------- + +A composição é uma técnica em que, em vez de herdarmos as propriedades e métodos de outra classe, simplesmente usamos a sua instância na nossa classe. Isso permite-nos combinar funcionalidades e propriedades de várias classes sem a necessidade de criar estruturas de herança complexas. + +Vejamos um exemplo. Temos uma classe `Motor` e uma classe `Carro`. Em vez de dizermos "Carro é um Motor", dizemos "Carro tem um Motor", que é uma relação típica de composição. + +```php +class Motor +{ + function ligar() + { + echo 'Motor a funcionar.'; + } +} + +class Carro +{ + private $motor; + + function __construct() + { + $this->motor = new Motor; + } + + function iniciar() + { + $this->motor->ligar(); + echo 'Carro pronto para andar!'; + } +} + +$carro = new Carro; +$carro->iniciar(); +``` + +Aqui, `Carro` não tem todas as propriedades e métodos de `Motor`, mas tem acesso a ele através da propriedade `$motor`. + +A vantagem da composição é maior flexibilidade no design e melhor capacidade de modificação no futuro. + + +Visibilidade +------------ + +Em PHP, pode definir a "visibilidade" para propriedades, métodos e constantes de uma classe. A visibilidade determina de onde pode aceder a esses elementos. + +1. **Public:** Se um elemento é marcado como `public`, significa que pode aceder a ele de qualquer lugar, mesmo fora da classe. + +2. **Protected:** Um elemento marcado como `protected` é acessível apenas dentro da classe e de todos os seus descendentes (classes que herdam desta classe). + +3. **Private:** Se um elemento é `private`, só pode aceder a ele de dentro da classe em que foi definido. + +Se não especificar a visibilidade, o PHP definirá automaticamente como `public`. + +Vejamos um código de exemplo: + +```php +class ExemploVisibilidade +{ + public $propriedadePublica = 'Pública'; + protected $propriedadeProtegida = 'Protegida'; + private $propriedadePrivada = 'Privada'; + + public function exibirPropriedades() + { + echo $this->propriedadePublica; // Funciona + echo $this->propriedadeProtegida; // Funciona + echo $this->propriedadePrivada; // Funciona + } +} + +$objeto = new ExemploVisibilidade; +$objeto->exibirPropriedades(); +echo $objeto->propriedadePublica; // Funciona +// echo $objeto->propriedadeProtegida; // Lança um erro +// echo $objeto->propriedadePrivada; // Lança um erro +``` + +Continuamos com a herança da classe: + +```php +class DescendenteClasse extends ExemploVisibilidade +{ + public function exibirPropriedades() + { + echo $this->propriedadePublica; // Funciona + echo $this->propriedadeProtegida; // Funciona + // echo $this->propriedadePrivada; // Lança um erro + } +} +``` + +Neste caso, o método `exibirPropriedades()` na classe `DescendenteClasse` pode aceder às propriedades públicas e protegidas, mas não pode aceder às propriedades privadas da classe pai. + +Dados e métodos devem ser o mais ocultos possível e acessíveis apenas através de uma interface definida. Isso permite que você altere a implementação interna da classe sem afetar o resto do código. + + +Palavra-chave `final` +--------------------- + +Em PHP, podemos usar a palavra-chave `final` se quisermos impedir que uma classe, método ou constante seja herdada ou sobrescrita. Quando marcamos uma classe como `final`, ela não pode ser estendida. Quando marcamos um método como `final`, ele não pode ser sobrescrito numa classe descendente. + +Saber que uma determinada classe ou método não será mais modificado permite-nos fazer alterações mais facilmente, sem ter que nos preocupar com possíveis conflitos. Por exemplo, podemos adicionar um novo método sem medo de que algum descendente já tenha um método com o mesmo nome e ocorra uma colisão. Ou podemos alterar os parâmetros de um método, pois novamente não há risco de causar inconsistência com um método sobrescrito num descendente. + +```php +final class ClasseFinal +{ +} + +// O código a seguir causará um erro, pois não podemos herdar de uma classe final. +class DescendenteClasseFinal extends ClasseFinal +{ +} +``` + +Neste exemplo, a tentativa de herdar da classe final `ClasseFinal` causará um erro. + + +Propriedades e Métodos Estáticos +-------------------------------- + +Quando falamos em PHP sobre elementos "estáticos" de uma classe, referimo-nos a métodos e propriedades que pertencem à própria classe, e não a uma instância específica dessa classe. Isso significa que não precisa criar uma instância da classe para aceder a eles. Em vez disso, chama-os ou acede a eles diretamente através do nome da classe. + +Tenha em mente que, como os elementos estáticos pertencem à classe e não às suas instâncias, não pode usar a pseudovariável `$this` dentro de métodos estáticos. + +O uso de propriedades estáticas leva a [código confuso cheio de armadilhas|dependency-injection:global-state], portanto, nunca deve usá-las e nem mostraremos um exemplo de uso aqui. Por outro lado, os métodos estáticos são úteis. Exemplo de uso: + +```php +class Calculadora +{ + public static function adicao($a, $b) + { + return $a + $b; + } + + public static function subtracao($a, $b) + { + return $a - $b; + } +} + +// Uso de método estático sem criar uma instância da classe +echo Calculadora::adicao(5, 3); // Resultado: 8 +echo Calculadora::subtracao(5, 3); // Resultado: 2 +``` + +Neste exemplo, criamos uma classe `Calculadora` com dois métodos estáticos. Podemos chamar esses métodos diretamente sem criar uma instância da classe usando o operador `::`. Métodos estáticos são especialmente úteis para operações que não dependem do estado de uma instância específica da classe. + + +Constantes de Classe +-------------------- + +Dentro das classes, temos a opção de definir constantes. Constantes são valores que nunca mudam durante a execução do programa. Ao contrário das variáveis, o valor de uma constante permanece sempre o mesmo. + +```php +class Carro +{ + public const NumeroDeRodas = 4; + + public function exibirNumeroDeRodas(): int + { + echo self::NumeroDeRodas; + } +} + +echo Carro::NumeroDeRodas; // Saída: 4 +``` + +Neste exemplo, temos a classe `Carro` com a constante `NumeroDeRodas`. Quando queremos aceder à constante dentro da classe, podemos usar a palavra-chave `self` em vez do nome da classe. + + +Interfaces de Objeto +-------------------- + +Interfaces de objeto funcionam como "contratos" para classes. Se uma classe deve implementar uma interface de objeto, ela deve conter todos os métodos que essa interface define. É uma ótima maneira de garantir que certas classes sigam o mesmo "contrato" ou estrutura. + +Em PHP, uma interface é definida com a palavra-chave `interface`. Todos os métodos definidos na interface são públicos (`public`). Quando uma classe implementa uma interface, ela usa a palavra-chave `implements`. + +```php +interface Animal +{ + function emitirSom(); +} + +class Gato implements Animal +{ + public function emitirSom() + { + echo 'Miau'; + } +} + +$gato = new Gato; +$gato->emitirSom(); +``` + +Se uma classe implementa uma interface, mas nem todos os métodos esperados são definidos nela, o PHP lançará um erro. + +Uma classe pode implementar várias interfaces ao mesmo tempo, o que é uma diferença em relação à herança, onde uma classe pode herdar apenas de uma classe: + +```php +interface Guarda +{ + function guardarCasa(); +} + +class Cachorro implements Animal, Guarda +{ + public function emitirSom() + { + echo 'Au au'; + } + + public function guardarCasa() + { + echo 'O cachorro guarda a casa atentamente'; + } +} +``` + + +Classes Abstratas +----------------- + +Classes abstratas servem como modelos básicos para outras classes, mas não pode criar instâncias delas diretamente. Elas contêm uma combinação de métodos completos e métodos abstratos, que não têm conteúdo definido. Classes que herdam de classes abstratas devem fornecer definições para todos os métodos abstratos do ancestral. + +Para definir uma classe abstrata, usamos a palavra-chave `abstract`. + +```php +abstract class ClasseAbstrata +{ + public function metodoComum() + { + echo 'Este é um método comum'; + } + + abstract public function metodoAbstrato(); +} + +class Descendente extends ClasseAbstrata +{ + public function metodoAbstrato() + { + echo 'Esta é a implementação do método abstrato'; + } +} + +$instancia = new Descendente; +$instancia->metodoComum(); +$instancia->metodoAbstrato(); +``` + +Neste exemplo, temos uma classe abstrata com um método comum e um método abstrato. Em seguida, temos a classe `Descendente`, que herda de `ClasseAbstrata` e fornece a implementação para o método abstrato. + +Qual a diferença entre interfaces e classes abstratas? Classes abstratas podem conter métodos abstratos e concretos, enquanto interfaces apenas definem quais métodos uma classe deve implementar, mas não fornecem nenhuma implementação. Uma classe pode herdar apenas de uma classe abstrata, mas pode implementar qualquer número de interfaces. + + +Verificação de Tipo +------------------- + +Na programação, é muito importante ter certeza de que os dados com os quais trabalhamos são do tipo correto. Em PHP, temos ferramentas que nos garantem isso. A verificação se os dados têm o tipo correto é chamada de "verificação de tipo" ou "type hinting". + +Tipos que podemos encontrar em PHP: + +1. **Tipos básicos**: Incluem `int` (números inteiros), `float` (números decimais), `bool` (valores booleanos), `string` (cadeias de caracteres), `array` (arrays) e `null`. +2. **Classes**: Se quisermos que o valor seja uma instância de uma classe específica. +3. **Interfaces**: Define um conjunto de métodos que uma classe deve implementar. Um valor que satisfaz a interface deve ter esses métodos. +4. **Tipos mistos (Union Types)**: Podemos especificar que uma variável pode ter vários tipos permitidos. +5. **Void**: Este tipo especial indica que a função ou método não retorna nenhum valor. + +Vamos ver como modificar o código para incluir tipos: + +```php +class Pessoa +{ + private int $idade; + + public function __construct(int $idade) + { + $this->idade = $idade; + } + + public function exibirIdade(): void + { + echo "Esta pessoa tem {$this->idade} anos."; + } +} + +/** + * Função que recebe um objeto da classe Pessoa e exibe a idade da pessoa. + */ +function exibirIdadePessoa(Pessoa $pessoa): void +{ + $pessoa->exibirIdade(); +} +``` + +Desta forma, garantimos que nosso código espera e trabalha com dados do tipo correto, o que nos ajuda a prevenir erros potenciais. + +Alguns tipos não podem ser escritos diretamente em PHP. Nesse caso, eles são indicados num comentário phpDoc, que é um formato padrão para documentar código PHP começando com `/**` e terminando com `*/`. Permite adicionar descrições de classes, métodos, etc. E também indicar tipos complexos usando as chamadas anotações `@var`, `@param` e `@return`. Esses tipos são então usados por ferramentas de análise estática de código, mas o próprio PHP não os verifica. + +```php +class Lista +{ + /** @var array<Pessoa> a notação diz que é um array de objetos Pessoa */ + private array $pessoas = []; + + public function adicionarPessoa(Pessoa $pessoa): void + { + $this->pessoas[] = $pessoa; + } +} +``` + + +Comparação e Identidade +----------------------- + +Em PHP, pode comparar objetos de duas maneiras: + +1. Comparação de valores `==`: Verifica se os objetos são da mesma classe e têm os mesmos valores em suas propriedades. +2. Identidade `===`: Verifica se se trata da mesma instância do objeto. + +```php +class Carro +{ + public string $marca; + + public function __construct(string $marca) + { + $this->marca = $marca; + } +} + +$carro1 = new Carro('Skoda'); +$carro2 = new Carro('Skoda'); +$carro3 = $carro1; + +var_dump($carro1 == $carro2); // true, porque têm o mesmo valor +var_dump($carro1 === $carro2); // false, porque não são a mesma instância +var_dump($carro1 === $carro3); // true, porque $carro3 é a mesma instância que $carro1 +``` + + +Operador `instanceof` +--------------------- + +O operador `instanceof` permite verificar se um determinado objeto é uma instância de uma classe específica, um descendente dessa classe, ou se implementa uma determinada interface. + +Imaginemos que temos uma classe `Pessoa` e outra classe `Estudante`, que é descendente da classe `Pessoa`: + +```php +class Pessoa +{ + private int $idade; + + public function __construct(int $idade) + { + $this->idade = $idade; + } +} + +class Estudante extends Pessoa +{ + private string $curso; + + public function __construct(int $idade, string $curso) + { + parent::__construct($idade); + $this->curso = $curso; + } +} + +$estudante = new Estudante(20, 'Informática'); + +// Verificar se $estudante é uma instância da classe Estudante +var_dump($estudante instanceof Estudante); // Saída: bool(true) + +// Verificar se $estudante é uma instância da classe Pessoa (porque Estudante é descendente de Pessoa) +var_dump($estudante instanceof Pessoa); // Saída: bool(true) +``` + +Das saídas, é evidente que o objeto `$estudante` é considerado simultaneamente uma instância de ambas as classes - `Estudante` e `Pessoa`. + + +Interfaces Fluentes +------------------- + +"Interface fluente" (em inglês "Fluent Interface") é uma técnica em POO que permite encadear métodos juntos numa única chamada. Isso muitas vezes simplifica e torna o código mais claro. + +O elemento chave de uma interface fluente é que cada método na cadeia retorna uma referência ao objeto atual. Conseguimos isso usando `return $this;` no final do método. Este estilo de programação é frequentemente associado a métodos chamados "setters", que definem os valores das propriedades do objeto. + +Vamos mostrar como uma interface fluente pode parecer num exemplo de envio de e-mails: + +```php +public function sendMessage() +{ + $email = new Email; + $email->setRemetente('sender@example.com') + ->setDestinatario('admin@example.com') + ->setMensagem('Olá, esta é uma mensagem.') + ->enviar(); +} +``` + +Neste exemplo, os métodos `setRemetente()`, `setDestinatario()` e `setMensagem()` servem para definir os valores correspondentes (remetente, destinatário, conteúdo da mensagem). Após definir cada um desses valores, os métodos retornam o objeto atual (`$email`), o que nos permite encadear outro método a seguir. Finalmente, chamamos o método `enviar()`, que realmente envia o e-mail. + +Graças às interfaces fluentes, podemos escrever código que é intuitivo e fácil de ler. + + +Cópia usando `clone` +-------------------- + +Em PHP, podemos criar uma cópia de um objeto usando o operador `clone`. Desta forma, obtemos uma nova instância com conteúdo idêntico. + +Se precisarmos modificar algumas propriedades ao copiar um objeto, podemos definir um método especial `__clone()` na classe. Este método é chamado automaticamente quando o objeto é clonado. + +```php +class Ovelha +{ + public string $nome; + + public function __construct(string $nome) + { + $this->nome = $nome; + } + + public function __clone() + { + $this->nome = 'Clone ' . $this->nome; + } +} + +$original = new Ovelha('Dolly'); +echo $original->nome . "\n"; // Imprime: Dolly + +$clone = clone $original; +echo $clone->nome . "\n"; // Imprime: Clone Dolly +``` + +Neste exemplo, temos a classe `Ovelha` com uma propriedade `$nome`. Quando clonamos uma instância desta classe, o método `__clone()` garante que o nome da ovelha clonada receba o prefixo "Clone". + + +Traits +------ + +Traits em PHP são uma ferramenta que permite compartilhar métodos, propriedades e constantes entre classes e evitar a duplicação de código. Pode imaginá-los como um mecanismo de "copiar e colar" (Ctrl-C e Ctrl-V), onde o conteúdo do trait é "colado" nas classes. Isso permite reutilizar código sem a necessidade de criar hierarquias de classes complicadas. + +Vamos mostrar um exemplo simples de como usar traits em PHP: + +```php +trait BuzinarTrait +{ + public function buzinar() + { + echo 'Bip bip!'; + } +} + +class Carro +{ + use BuzinarTrait; +} + +class Caminhao +{ + use BuzinarTrait; +} + +$carro = new Carro; +$carro->buzinar(); // Imprime 'Bip bip!' + +$caminhao = new Caminhao; +$caminhao->buzinar(); // Também imprime 'Bip bip!' +``` + +Neste exemplo, temos um trait chamado `BuzinarTrait`, que contém um método `buzinar()`. Em seguida, temos duas classes: `Carro` e `Caminhao`, ambas usando o trait `BuzinarTrait`. Graças a isso, ambas as classes "têm" o método `buzinar()`, e podemos chamá-lo em objetos de ambas as classes. + +Traits permitem compartilhar código entre classes de forma fácil e eficiente. Ao mesmo tempo, eles não entram na hierarquia de herança, ou seja, `$carro instanceof BuzinarTrait` retornará `false`. + + +Exceções +-------- + +Exceções em POO permitem-nos lidar elegantemente com erros e situações inesperadas no nosso código. São objetos que carregam informações sobre o erro ou situação incomum. + +Em PHP, temos a classe integrada `Exception`, que serve como base para todas as exceções. Ela tem vários métodos que nos permitem obter mais informações sobre a exceção, como a mensagem de erro, o arquivo e a linha onde o erro ocorreu, etc. + +Quando ocorre um erro no código, podemos "lançar" uma exceção usando a palavra-chave `throw`. + +```php +function divisao(float $a, float $b): float +{ + if ($b === 0.0) { // Comparação estrita para float + throw new Exception('Divisão por zero!'); + } + return $a / $b; +} +``` + +Quando a função `divisao()` recebe zero como segundo argumento, ela lança uma exceção com a mensagem de erro `'Divisão por zero!'`. Para evitar que o programa falhe ao lançar uma exceção, capturamo-la num bloco `try/catch`: + +```php +try { + echo divisao(10, 0); +} catch (Exception $e) { + echo 'Exceção capturada: '. $e->getMessage(); +} +``` + +O código que pode lançar uma exceção é envolvido num bloco `try`. Se uma exceção for lançada, a execução do código move-se para o bloco `catch`, onde podemos processar a exceção (por exemplo, exibir uma mensagem de erro). + +Após os blocos `try` e `catch`, podemos adicionar um bloco opcional `finally`, que será executado sempre, quer uma exceção tenha sido lançada ou não (mesmo no caso de usarmos a instrução `return`, `break` ou `continue` no bloco `try` ou `catch`): + +```php +try { + echo divisao(10, 0); +} catch (Exception $e) { + echo 'Exceção capturada: '. $e->getMessage(); +} finally { + // Código que será executado sempre, quer a exceção tenha sido lançada ou não +} +``` + +Também podemos criar nossas próprias classes (hierarquia) de exceções que herdam da classe Exception. Como exemplo, imaginemos uma aplicação bancária simples que permite fazer depósitos e saques: + +```php +class ExcecaoBancaria extends Exception {} +class ExcecaoSaldoInsuficiente extends ExcecaoBancaria {} +class ExcecaoLimiteExcedido extends ExcecaoBancaria {} + +class ContaBancaria +{ + private int $saldo = 0; + private int $limiteDiario = 1000; + + public function depositar(int $quantia): int + { + $this->saldo += $quantia; + return $this->saldo; + } + + public function sacar(int $quantia): int + { + if ($quantia > $this->saldo) { + throw new ExcecaoSaldoInsuficiente('Saldo insuficiente na conta.'); + } + + if ($quantia > $this->limiteDiario) { + throw new ExcecaoLimiteExcedido('O limite diário para saques foi excedido.'); + } + + $this->saldo -= $quantia; + return $this->saldo; + } +} +``` + +Para um bloco `try`, podem ser fornecidos vários blocos `catch`, se esperar diferentes tipos de exceções. + +```php +$conta = new ContaBancaria; +$conta->depositar(500); + +try { + $conta->sacar(1500); +} catch (ExcecaoLimiteExcedido $e) { + echo $e->getMessage(); +} catch (ExcecaoSaldoInsuficiente $e) { + echo $e->getMessage(); +} catch (ExcecaoBancaria $e) { + echo 'Ocorreu um erro ao realizar a operação.'; +} +``` + +Neste exemplo, é importante notar a ordem dos blocos `catch`. Como todas as exceções herdam de `ExcecaoBancaria`, se tivéssemos este bloco primeiro, todas as exceções seriam capturadas nele, sem que o código chegasse aos blocos `catch` seguintes. Portanto, é importante ter exceções mais específicas (ou seja, aquelas que herdam de outras) no bloco `catch` mais acima na ordem do que suas exceções pai. + + +Iteração +-------- + +Em PHP, pode percorrer objetos usando o loop `foreach`, semelhante a como percorre arrays. Para que isso funcione, o objeto deve implementar uma interface especial. + +A primeira opção é implementar a interface `Iterator`, que possui os métodos `current()` que retorna o valor atual, `key()` que retorna a chave, `next()` que move para o próximo valor, `rewind()` que move para o início e `valid()` que verifica se ainda não chegamos ao fim. + +A segunda opção é implementar a interface `IteratorAggregate`, que possui apenas um método `getIterator()`. Este retorna um objeto substituto que garantirá a iteração, ou pode representar um gerador, que é uma função especial onde `yield` é usado para retornar chaves e valores sequencialmente: + +```php +class Pessoa +{ + public function __construct( + public int $idade, + ) { + } +} + +class Lista implements IteratorAggregate +{ + private array $pessoas = []; + + public function adicionarPessoa(Pessoa $pessoa): void + { + $this->pessoas[] = $pessoa; + } + + public function getIterator(): Generator + { + foreach ($this->pessoas as $pessoa) { + yield $pessoa; + } + } +} + +$lista = new Lista; +$lista->adicionarPessoa(new Pessoa(30)); +$lista->adicionarPessoa(new Pessoa(25)); + +foreach ($lista as $pessoa) { + echo "Idade: {$pessoa->idade} anos \n"; +} +``` + + +Boas Práticas +------------- + +Depois de dominar os princípios básicos da programação orientada a objetos, é importante focar nas boas práticas em POO. Elas ajudarão a escrever código que não é apenas funcional, mas também legível, compreensível e fácil de manter. + +1) **Separação de Responsabilidades (Separation of Concerns)**: Cada classe deve ter uma responsabilidade claramente definida e deve resolver apenas uma tarefa principal. Se uma classe faz muitas coisas, pode ser apropriado dividi-la em classes menores e especializadas. +2) **Encapsulamento (Encapsulation)**: Dados e métodos devem ser o mais ocultos possível e acessíveis apenas através de uma interface definida. Isso permite que você altere a implementação interna da classe sem afetar o resto do código. +3) **Injeção de Dependência (Dependency Injection)**: Em vez de criar dependências diretamente na classe, deve "injetá-las" de fora. Para uma compreensão mais profunda deste princípio, recomendamos os [capítulos sobre Injeção de Dependência|dependency-injection:introduction]. diff --git a/nette/pt/troubleshooting.texy b/nette/pt/troubleshooting.texy index 5c606058f6..5f54d352b4 100644 --- a/nette/pt/troubleshooting.texy +++ b/nette/pt/troubleshooting.texy @@ -1,67 +1,101 @@ -Solução de problemas +Solução de Problemas ******************** -Nette não está funcionando, página branca é exibida .[#toc-nette-is-not-working-white-page-is-displayed] --------------------------------------------------------------------------------------------------------- -- Tente colocar `ini_set('display_errors', '1'); error_reporting(E_ALL);` depois de `declare(strict_types=1);` no arquivo `index.php` para forçar a exibição de erros -- Se você ainda vir uma tela branca, provavelmente há um erro na configuração do servidor e você descobrirá o motivo no log do servidor. Para ter certeza, verifique se o PHP está funcionando, tentando imprimir algo usando `echo 'test';`. -- Se você vir um erro *Server Error: Pedimos desculpas! ...*, continue com a próxima seção: +O Nette não funciona, uma página em branco é exibida +---------------------------------------------------- +- Tente inserir `ini_set('display_errors', '1'); error_reporting(E_ALL);` no arquivo `index.php` logo após `declare(strict_types=1);`, isso forçará a exibição de erros. +- Se você ainda vir uma tela em branco, provavelmente há um erro na configuração do servidor e o motivo será revelado no log do servidor. Para ter certeza, verifique se o PHP está funcionando, tentando imprimir algo usando `echo 'test';` +- Se você vir o erro *Server Error: We're sorry! …*, prossiga para a próxima seção: -Error 500 *Server Error: Pedimos desculpas! ...* .[#toc-error-500-server-error-we-re-sorry] -------------------------------------------------------------------------------------------- -Esta página de erro é exibida pela Nette no modo de produção. Se você estiver vendo em uma máquina de desenvolvimento, [mude para o modo de desenvolvimento |application:bootstrap#Development vs Production Mode]. +Erro 500 *Server Error: We're sorry! …* +--------------------------------------- +Esta página de erro é exibida pelo Nette no modo de produção. Se ela for exibida no seu computador de desenvolvimento, [mude para o modo de desenvolvimento |application:bootstrapping#Modo de desenvolvimento vs produção] e o Tracy será exibido com uma mensagem detalhada. -Se a mensagem de erro contiver `Tracy is unable to log error`, descubra por que os erros não podem ser registrados. Você pode fazer isso, por exemplo, [mudando |application:bootstrap#Development vs Production Mode] para o modo desenvolvedor e ligando para `Tracy\Debugger::log('hello');` após `$configurator->enableTracy(...)`. Tracy lhe dirá por que ele não pode registrar. -A causa geralmente é a [falta de permissão |#Setting Directory Permissions] para escrever para o diretório `log/`. +O motivo do erro sempre pode ser encontrado no log no diretório `log/`. No entanto, se a mensagem de erro mostrar a frase `Tracy is unable to log error`, primeiro descubra por que os erros não podem ser registrados. Você pode fazer isso, por exemplo, [mudando |application:bootstrapping#Modo de desenvolvimento vs produção] temporariamente para o modo de desenvolvimento e deixando o Tracy registrar qualquer coisa após sua inicialização: -Se a frase `Tracy is unable to log error` não estiver mais na mensagem de erro (não mais), você pode descobrir o motivo do erro no log no diretório `log/`. +```php +// Bootstrap.php +$configurator->setDebugMode('23.75.345.200'); // seu endereço IP +$configurator->enableTracy($rootDir . '/log'); +\Tracy\Debugger::log('hello'); // Registra 'hello' +``` + +O Tracy informará por que não pode registrar. A causa pode ser [permissões insuficientes |#Configurando Permissões de Diretório] para escrever no diretório `log/`. + +Um dos motivos mais comuns para o erro 500 é um cache desatualizado. Enquanto o Nette no modo de desenvolvimento atualiza o cache automaticamente de forma inteligente, no modo de produção ele se concentra em maximizar o desempenho, e limpar o cache após cada modificação no código é sua responsabilidade. Tente excluir `temp/cache`. + + +Erro 404, o roteamento não funciona +----------------------------------- +Quando todas as páginas (exceto a página inicial) retornam um erro 404, parece haver um problema com a configuração do servidor para [URLs amigáveis |#Como configurar o servidor para URLs amigáveis]. + + +Alterações nos templates ou na configuração não são refletidas +-------------------------------------------------------------- +"Modifiquei o template ou a configuração, mas o site ainda exibe a versão antiga." Este comportamento ocorre no [modo de produção |application:bootstrapping#Modo de desenvolvimento vs produção], que, por motivos de desempenho, não verifica alterações nos arquivos e mantém o cache gerado uma vez. + +Para não ter que limpar manualmente o cache no servidor de produção após cada modificação, habilite o modo de desenvolvimento para o seu endereço IP no arquivo `Bootstrap.php`: + +```php +$configurator->setDebugMode('seu.ip.endereco'); +``` + + +Como desativar o cache durante o desenvolvimento? +------------------------------------------------- +O Nette é inteligente e você não precisa desativar o cache nele. Durante o desenvolvimento, ele atualiza automaticamente o cache a cada alteração no template ou na configuração do contêiner DI. Além disso, o modo de desenvolvimento é ativado por autodetecção, então geralmente não é necessário configurar nada, [ou apenas o endereço IP |application:bootstrapping#Modo de desenvolvimento vs produção]. -Uma das razões mais comuns é um cache desatualizado. Enquanto a Nette atualiza o cache de forma inteligente e automática no modo de desenvolvimento, no modo de produção ele se concentra em maximizar o desempenho, e limpar o cache após cada modificação de código fica a seu critério. Tente excluir `temp/cache`. +Ao depurar o roteador, recomendamos desativar o cache no navegador, onde podem estar armazenados, por exemplo, redirecionamentos: abra as Ferramentas do Desenvolvedor (Ctrl+Shift+I ou Cmd+Option+I) e no painel Rede (Network), marque a opção para desativar o cache. -Erro `#[\ReturnTypeWillChange] attribute should be used` .[#toc-error-returntypewillchange-attribute-should-be-used] --------------------------------------------------------------------------------------------------------------------- -Este erro ocorre se você tiver atualizado o PHP para a versão 8.1, mas estiver usando Nette, que não é compatível com ele. Portanto, a solução é atualizar o Nette para uma versão mais recente usando `composer update`. Nette suporta o PHP 8.1 desde a versão 3.0. Se você estiver usando uma versão mais antiga (você pode descobrir procurando em `composer.json`), [atualize Nette |migrations:en] ou fique com o PHP 8.0. +Erro `#[\ReturnTypeWillChange] attribute should be used` +-------------------------------------------------------- +Este erro aparece se você atualizou o PHP para a versão 8.1, mas está usando uma versão do Nette que não é compatível com ela. A solução é, portanto, atualizar o Nette para uma versão mais recente usando `composer update`. O Nette suporta PHP 8.1 a partir da versão 3.0. Se você estiver usando uma versão mais antiga (verifique no `composer.json`), [atualize o Nette |migrations:en] ou permaneça com o PHP 8.0. -Definição de permissões de diretório .[#toc-setting-directory-permissions] --------------------------------------------------------------------------- -Se você estiver desenvolvendo em macOS ou Linux (ou qualquer outro sistema baseado em Unix), você precisa configurar privilégios de escrita para o servidor web. Assumindo que sua aplicação esteja localizada no diretório padrão `/var/www/html` (Fedora, CentOS, RHEL) +Configurando Permissões de Diretório +------------------------------------ +Se você estiver desenvolvendo no macOS ou Linux (ou qualquer outro sistema baseado em Unix), precisará definir permissões de escrita para o servidor web. Supondo que sua aplicação esteja localizada no diretório padrão `/var/www/html` (Fedora, CentOS, RHEL). ```shell -cd /var/www/html/MY_PROJECT +cd /var/www/html/MEU_PROJETO chmod -R a+rw temp log ``` -Em alguns sistemas Linux (Fedora, CentOS, ...) SELinux pode ser habilitado por padrão. Você pode precisar atualizar as políticas do SELinux, ou definir caminhos dos diretórios `temp` e `log` com o contexto correto de segurança do SELinux. Os diretórios `temp` e `log` devem ser configurados no contexto `httpd_sys_rw_content_t`; para o restante da aplicação -- principalmente a pasta `app` -- o contexto `httpd_sys_content_t` será suficiente. Executar no servidor como root: +Em alguns sistemas Linux (Fedora, CentOS, ...), o SELinux está ativado por padrão. Você precisará ajustar as políticas do SELinux e definir o contexto de segurança correto do SELinux para as pastas `temp` e `log`. Para `temp` e `log`, definiremos o tipo de contexto `httpd_sys_rw_content_t`, para o restante da aplicação (e principalmente para a pasta `app`), `httpd_sys_content_t` será suficiente. No servidor, execute: ```shell -semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/log(/.*)?' -semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/temp(/.*)?' -restorecon -Rv /var/www/html/MY_PROJECT/ +semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/html/MEU_PROJETO/log(/.*)?' +semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/html/MEU_PROJETO/temp(/.*)?' +restorecon -Rv /var/www/html/MEU_PROJETO/ ``` -Em seguida, o booleano SELinux `httpd_can_network_connect_db` precisa ser habilitado para permitir que a Nette se conecte ao banco de dados através da rede. Por padrão, ele está desativado. O comando `setsebool` pode ser usado para realizar esta tarefa, e se a opção `-P` for especificada, esta configuração será persistente em todas as reinicializações. +Além disso, é necessário habilitar o booleano do SELinux `httpd_can_network_connect_db`, que está desativado por padrão e permite que o Nette se conecte ao banco de dados pela rede. Usaremos o comando `setsebool` e a opção `-P` para tornar a alteração permanente, ou seja, após a reinicialização do servidor, não teremos surpresas desagradáveis: ```shell setsebool -P httpd_can_network_connect_db on ``` -Como mudar ou remover `www` Diretório do URL? .[#toc-how-to-change-or-remove-www-directory-from-url] ----------------------------------------------------------------------------------------------------- -O diretório `www/` usado nos projetos modelo em Nette é o chamado diretório público ou raiz documental do projeto. É o único diretório cujo conteúdo é acessível ao navegador. E contém o arquivo `index.php`, o ponto de entrada que inicia uma aplicação web escrita em Nette. +Como alterar ou remover o diretório `www` da URL? +------------------------------------------------- +O diretório `www/` usado nos projetos de exemplo do Nette representa o chamado diretório público ou document-root do projeto. É o único diretório cujo conteúdo é acessível pelo navegador. E contém o arquivo `index.php`, o ponto de entrada que inicia a aplicação web escrita em Nette. -Para executar o aplicativo na hospedagem, é necessário definir a raiz do documento para este diretório na configuração de hospedagem. Ou, se a hospedagem tiver uma pasta pré-fabricada para o diretório público com um nome diferente (por exemplo `web`, `public_html` etc.), simplesmente renomeie `www/`. +Para colocar a aplicação em funcionamento na hospedagem, é necessário ter o document-root configurado corretamente. Você tem duas opções: +1. Na configuração da hospedagem, definir o document-root para este diretório (`www/`). +2. Se a hospedagem tiver uma pasta pré-preparada (por exemplo, `public_html`), renomeie `www/` para este nome. -A solução ** não é*** para "se livrar" da pasta `www/` usando regras no arquivo `.htaccess` ou no roteador. Se a hospedagem não permitir que você coloque a raiz do documento em um subdiretório (ou seja, criar diretórios um nível acima do diretório público), procure por outro. Caso contrário, você estaria correndo um risco significativo de segurança. Seria como viver em um apartamento onde não se pode fechar a porta da frente e ela está sempre aberta. +.[warning] +Nunca tente resolver a segurança apenas com `.htaccess` ou roteador, que impediriam o acesso a outras pastas. +Se a hospedagem não permitir definir o document-root para um subdiretório (ou seja, criar diretórios um nível acima do diretório público), procure outra. Caso contrário, você correria um risco de segurança significativo. Seria como morar em um apartamento onde a porta de entrada não pode ser fechada e está sempre aberta. -Como configurar um servidor para URLs legais? .[#toc-how-to-configure-a-server-for-nice-urls] ---------------------------------------------------------------------------------------------- -**Apache**: extensão mod_rewrite deve ser permitida e configurada em um arquivo `.htaccess`. + +Como configurar o servidor para URLs amigáveis? +----------------------------------------------- +**Apache**: é necessário habilitar e configurar as regras mod_rewrite no arquivo `.htaccess`: ```apacheconf RewriteEngine On @@ -70,25 +104,53 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -Para alterar a configuração do Apache com arquivos .htaccess, a diretiva AllowOverride tem que ser habilitada. Este é o comportamento padrão para o Apache. +Se você encontrar problemas, certifique-se de que: +- o arquivo `.htaccess` está localizado no diretório document-root (ou seja, ao lado do arquivo `index.php`) +- [Apache processa arquivos `.htaccess` |#Verificando se o .htaccess funciona] +- [mod_rewrite está habilitado |#Verificando se o mod rewrite está habilitado] + +Se você estiver configurando a aplicação em uma subpasta, pode ser necessário descomentar a linha para definir `RewriteBase` e ajustá-la para a pasta correta. -**nginx**: a diretiva `try_files` deve ser usada na configuração do servidor: +**nginx**: é necessário configurar o redirecionamento usando a diretiva `try_files` dentro do bloco `location /` na configuração do servidor. ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args is important + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args É IMPORTANTE! } ``` -O bloco `location` deve ser definido exatamente uma vez para cada caminho do sistema de arquivos no bloco `server`. Se você já tem um bloco `location /` em sua configuração, adicione a diretiva `try_files` ao bloco existente. +O bloco `location` para cada caminho do sistema de arquivos pode ocorrer apenas uma vez no bloco `server`. Se você já tiver `location /` na configuração, adicione a diretiva `try_files` a ele. + + +Verificando se o `.htaccess` funciona +------------------------------------- +A maneira mais fácil de testar se o Apache está usando ou ignorando seu arquivo `.htaccess`, é danificá-lo intencionalmente. Insira a linha `Test` no início do arquivo e agora, se você atualizar a página no navegador, deverá ver *Internal Server Error*. + +Se você vir este erro, na verdade é bom! Significa que o Apache está analisando o arquivo `.htaccess` e encontrou o erro que inserimos lá. Remova a linha `Test`. + +Se *Internal Server Error* não for exibido, sua configuração do Apache está ignorando o arquivo `.htaccess`. Geralmente, o Apache o ignora devido à falta da diretiva de configuração `AllowOverride All`. + +Se você estiver hospedando você mesmo, isso pode ser facilmente corrigido. Abra o arquivo `httpd.conf` ou `apache.conf` em um editor de texto, localize a seção `<Directory>` relevante e adicione/altere esta diretiva: + +```apacheconf +<Directory "/var/www/htdocs"> # caminho para o seu document root + AllowOverride All + ... +``` + +Se o seu site estiver hospedado em outro lugar, verifique o painel de controle para ver if you can enable the `.htaccess` file there. If not, contact your hosting provider to do it for you. + + +Verificando se o `mod_rewrite` está habilitado +---------------------------------------------- +Se você verificou que [`.htaccess` funciona |#Verificando se o .htaccess funciona], pode verificar se a extensão mod_rewrite está habilitada. Insira a linha `RewriteEngine On` no início do arquivo `.htaccess` e atualize a página no navegador. Se *Internal Server Error* for exibido, significa que o mod_rewrite não está habilitado. Existem várias maneiras de habilitá-lo. Diferentes maneiras de fazer isso em diferentes configurações podem ser encontradas no Stack Overflow. -Os links são gerados sem `https:` .[#toc-links-are-generated-without-https] ---------------------------------------------------------------------------- -Nette gera links com o mesmo protocolo que a página atual está usando. Assim, na página `https://foo` e vice-versa. -Se você estiver atrás de um proxy reverso HTTPS (por exemplo, no Docker), então você precisa configurar [um proxy |http:configuration#HTTP proxy] em configuração para que a detecção do protocolo funcione corretamente. +Links são gerados sem `https:` +------------------------------ +O Nette gera links com o mesmo protocolo da própria página. Ou seja, na página `https://foo`, ele gera links começando com `https:` e vice-versa. Se você estiver atrás de um servidor proxy reverso que remove HTTPS (por exemplo, no Docker), então é necessário [configurar o proxy |http:configuration#Proxy HTTP] na configuração para que a detecção de protocolo funcione corretamente. -Se você usa o Nginx como um proxy, você precisa ter um redirecionamento configurado desta forma: +Se você estiver usando o Nginx como proxy, é necessário ter o redirecionamento configurado, por exemplo, assim: ``` location / { @@ -96,44 +158,42 @@ location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Port $server_port; - proxy_pass http://IP-aplikace:80; # IP or hostname of the server/container where the application is running + proxy_pass http://IP-aplicacao:80; # IP ou hostname do servidor/contêiner onde a aplicação está rodando } ``` -Em seguida, você precisa especificar o proxy IP e, se aplicável, a faixa IP de sua rede local onde você administra a infra-estrutura: +Além disso, é necessário indicar na configuração o IP do proxy e, opcionalmente, o intervalo de IP da sua rede local onde você opera a infraestrutura: ```neon http: - proxy: IP-proxy/IP-range + proxy: IP-do-proxy/Intervalo-de-IP ``` -Uso de caracteres { } em JavaScript .[#toc-use-of-characters-in-javascript] ---------------------------------------------------------------------------- -Os caracteres `{` and `}` são usados para escrever etiquetas Latte. Tudo (exceto espaço e aspas) seguindo o `{` character is considered a tag. If you need to print character `{` (freqüentemente em JavaScript), você pode colocar um espaço (ou outro caractere vazio) logo após `{`. Com isto você evita interpretá-lo como uma tag. +Usando os caracteres { } em JavaScript +-------------------------------------- +Os caracteres `{` e `}` são usados para escrever tags Latte. Qualquer coisa que siga o caractere `{`, exceto um espaço e aspas, é considerada uma tag. Portanto, se você precisar imprimir diretamente o caractere `{` (frequentemente, por exemplo, em JavaScript), pode colocar um espaço (ou outro caractere em branco) após o caractere `{`. Isso evitará a tradução como uma tag. -Se for necessário imprimir estes caracteres em uma situação em que eles seriam interpretados como uma tag, você pode utilizar tags especiais para imprimir estes caracteres - `{l}` para `{` and `{r}` para `}`. +Se for necessário imprimir esses caracteres em uma situação em que o texto seria entendido como uma tag, você pode usar tags especiais para imprimir esses caracteres - `{l}` para `{` e `{r}` para `}`. ``` -{is tag} -{ is not tag } -{l}is not tag{r} +{é uma tag} +{ não é uma tag } +{l}não é uma tag{r} ``` -Aviso `Presenter::getContext() is deprecated` .[#toc-notice-presenter-getcontext-is-deprecated] ------------------------------------------------------------------------------------------------ +Mensagem `Presenter::getContext() is deprecated` +------------------------------------------------ -Nette é de longe a primeira estrutura PHP que mudou para injeção de dependência e levou os programadores a usá-la de forma consistente, a começar pelos apresentadores. Se um apresentador precisar de uma dependência, [ele a solicitará |dependency-injection:passing-dependencies]. -Em contraste, a forma como passamos todo o recipiente DI para uma classe e ele puxa as dependências diretamente dele é considerado um antipadrão (é chamado de localizador de serviços). -Esta forma era usada em Nette 0.x antes do advento da injeção de dependência, e sua relíquia é o método `Presenter::getContext()`, há muito marcado como depreciado. +O Nette é de longe o primeiro framework PHP a migrar para a injeção de dependência e a orientar os programadores a usá-la consistentemente, desde os próprios presenters. Se um presenter precisa de alguma dependência, ele [solicita-a|dependency-injection:passing-dependencies]. Por outro lado, a abordagem em que passamos todo o contêiner DI para a classe, e ela extrai as dependências diretamente dele, é considerada um antipadrão (chamado de service locator). Este método foi usado no Nette 0.x antes da chegada da injeção de dependência, e seu resquício é o método `Presenter::getContext()`, há muito tempo marcado como obsoleto. -Se você portar uma aplicação Nette muito antiga, você pode descobrir que ela ainda usa este método. Portanto, desde a versão 3.1 de `nette/application` você encontrará o aviso `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection`, desde a versão 4.0 você encontrará o erro de que o método não existe. +Se você estiver portando uma aplicação Nette muito antiga, pode ser que ela ainda use este método. A partir da versão 3.1 do `nette/application`, você encontrará o aviso `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection`, e a partir da versão 4.0, um erro informando que o método não existe. -A solução limpa, naturalmente, é redesenhar a aplicação para passar dependências usando injeção de dependência. Como alternativa, você pode adicionar seu próprio método `getContext()` ao seu apresentador base e contornar a mensagem: +A solução limpa é, obviamente, refatorar a aplicação para que as dependências sejam passadas usando injeção de dependência. Como uma solução alternativa, você pode adicionar seu próprio método `getContext()` ao seu presenter base e contornar a mensagem: ```php -abstract BasePresenter extends Nette\Application\UI\Presenter +abstract class BasePresenter extends Nette\Application\UI\Presenter { private Nette\DI\Container $context; diff --git a/nette/pt/vulnerability-protection.texy b/nette/pt/vulnerability-protection.texy index dcbb0cdcac..8698c7d3d9 100644 --- a/nette/pt/vulnerability-protection.texy +++ b/nette/pt/vulnerability-protection.texy @@ -1,22 +1,22 @@ -Proteção contra vulnerabilidades +Proteção contra Vulnerabilidades ******************************** .[perex] -De vez em quando, uma grande falha de segurança é anunciada ou mesmo abusada. Com certeza, isso é um pouco desagradável. Se você se preocupa com a segurança de suas aplicações web, o Nette Framework é francamente a melhor escolha para você. +De tempos em tempos, uma falha de segurança é relatada em outro site importante ou uma falha é explorada. Isso é desagradável. Se você se preocupa com a segurança de suas aplicações web, o Nette Framework é certamente a melhor escolha. -Roteiro transversal (XSS) .[#toc-cross-site-scripting-xss] -========================================================== +Cross-Site Scripting (XSS) +========================== -O Cross-Site Scripting é um método de interrupção do site que utiliza entrada não modelada. Um atacante pode injetar seu próprio código HTML ou JavaScript e mudar a aparência da página ou mesmo reunir informações sensíveis sobre os usuários. A proteção contra XSS é simples: fuga consistente e correta de todas as cadeias de caracteres e entradas. Tradicionalmente, seria suficiente se seu codificador cometesse apenas um pequeno erro e esquecesse uma vez, e todo o website poderia ser comprometido. +Cross-Site Scripting é um método de violação de sites que explora saídas não tratadas. O invasor pode então injetar seu próprio código na página e, assim, modificá-la ou até mesmo obter dados confidenciais dos visitantes. A única maneira de se defender contra XSS é tratar todas as strings de forma consistente e correta. Basta que seu codificador omita isso apenas uma vez, e todo o site pode ser comprometido instantaneamente. -Um exemplo de tal injeção pode ser a inserção de um URL alterado, que insere um script "malicioso". Se uma aplicação não escapar de suas entradas corretamente, tal solicitação possivelmente executaria um script do lado do cliente. Isto pode, por exemplo, levar ao roubo de identidade. +Um exemplo de ataque pode ser injetar uma URL modificada para o usuário, através da qual injetamos nosso próprio código na página. Se a aplicação não tratar adequadamente as saídas, ela executará o script no navegador do usuário. Desta forma, podemos, por exemplo, roubar sua identidade. ``` -https://example.com/?search=<script>alert('XSS attack.');</script> +https://example.com/?search=<script>alert('Ataque XSS bem-sucedido.');</script> ``` -Nette Framework vem com uma novíssima tecnologia de [Context-Aware Escaping |latte:safety-first#context-aware-escaping], que o livrará dos riscos do Cross-Site Scripting para sempre. Ela escapa automaticamente de todas as entradas baseadas em um determinado contexto, de modo que é impossível para um codificador esquecer acidentalmente algo. Considere o seguinte modelo como um exemplo: +O Nette Framework vem com uma tecnologia revolucionária [Context-Aware Escaping |latte:safety-first#Escaping sensível ao contexto], que o livrará para sempre do risco de Cross-Site Scripting. Ele trata todas as saídas automaticamente, para que não haja chance de o codificador esquecer algo. Exemplo? O codificador cria este template: ```latte <p onclick="alert({$message})">{$message}</p> @@ -26,29 +26,29 @@ document.title = {$message}; </script> ``` -O comando `{$message}` imprime uma variável. Outras estruturas geralmente forçam os desenvolvedores a declarar explicitamente a fuga, e até mesmo que tipo de fuga com base no contexto. No entanto, em Nette Framework você não precisa declarar nada. Tudo é automático, consistente e justo. Se definirmos a variável para `$message = 'Width 1/2"'`, o framework irá gerar este código HTML: +A notação `{$message}` significa imprimir a variável. Em outros frameworks, é necessário tratar explicitamente cada impressão e, além disso, de forma diferente em cada local. No Nette Framework, não é necessário tratar nada, tudo é feito automaticamente, corretamente e consistentemente. Se atribuirmos à variável `$message = 'Largura 1/2"'`, o framework gerará o código HTML: ```latte -<p onclick="alert("Width 1\/2\"")">Width 1/2"</p> +<p onclick="alert("Largura 1\/2\"")">Largura 1/2"</p> <script> -document.title = "Width 1\/2\""; +document.title = "Largura 1\/2\""; </script> ``` -Falsificação de pedido entre locais (CSRF) .[#toc-cross-site-request-forgery-csrf] -================================================================================== +Cross-Site Request Forgery (CSRF) +================================= -Um ataque de Pedido Cruzado de Falsificação é que o atacante atrai a vítima para visitar uma página que executa silenciosamente um pedido no navegador da vítima para o servidor onde a vítima está atualmente logada, e o servidor acredita que o pedido foi feito pela vítima à sua vontade. O servidor executa uma determinada ação sob a identidade da vítima, mas sem que a vítima se dê conta disso. Ele pode estar alterando ou apagando dados, enviando uma mensagem, etc. +O ataque Cross-Site Request Forgery consiste em o invasor atrair a vítima para uma página que executa discretamente uma requisição no navegador da vítima para um servidor no qual a vítima está logada, e o servidor acredita que a requisição foi feita pela vítima por vontade própria. Assim, sob a identidade da vítima, realiza uma determinada ação sem que ela saiba. Pode ser a alteração ou exclusão de dados, envio de uma mensagem, etc. -Nette Framework ** protege automaticamente as formas e sinais nos apresentadores*** deste tipo de ataque. Isto é feito impedindo que eles sejam enviados ou chamados de outro domínio. Para desativar a proteção, use o seguinte para os formulários: +O Nette Framework **protege automaticamente formulários e sinais nos presenters** contra este tipo de ataque. Isso é feito impedindo que sejam enviados ou acionados de outro domínio. Se você quiser desativar a proteção, use para formulários: ```php $form->allowCrossOrigin(); ``` -ou, no caso de um sinal, acrescente uma anotação `@crossOrigin`: +ou no caso de um sinal, adicione a anotação `@crossOrigin`: ```php /** @@ -59,44 +59,41 @@ public function handleXyz() } ``` -No PHP 8, você também pode usar atributos: +No Nette Application 3.2, você também pode usar atributos: ```php -use Nette\Application\Attributes\CrossOrigin; +use Nette\Application\Attributes\Requires; -#[CrossOrigin] +#[Requires(sameOrigin: false)] public function handleXyz() { } ``` -URL Attack, Códigos de controle, Inválido UTF-8 .[#toc-url-attack-control-codes-invalid-utf-8] -============================================================================================== +Ataque de URL, códigos de controle, UTF-8 inválido +================================================== -Termos diferentes, todos relacionados ao esforço do atacante para dar à sua aplicação uma contribuição "maliciosa". Os resultados podem variar muito, desde saídas XML quebradas (ou seja, fluxo RSS com mau funcionamento) até a obtenção de informações sensíveis de um banco de dados para a obtenção de senhas de usuários. A proteção contra estes ataques é uma verificação consistente UTF-8 no nível de bytes. E francamente, você não faria isso sem uma estrutura, certo? +Vários termos relacionados à tentativa de um invasor de injetar entrada *maliciosa* em sua aplicação web. As consequências podem ser muito diversas, desde danos às saídas XML (por exemplo, feeds RSS não funcionais) até a obtenção de informações confidenciais do banco de dados ou senhas. A defesa é o tratamento consistente de todas as entradas no nível de bytes individuais. E, sejamos honestos, quem de vocês faz isso? -A Nette Framework faz isso para você, automaticamente. Você não precisa configurar absolutamente nada e sua aplicação será segura. +O Nette Framework faz isso por você e, além disso, automaticamente. Você não precisa configurar absolutamente nada e todas as entradas serão tratadas. -Seqüestro de sessão, Roubo de sessão, Fixação de sessão .[#toc-session-hijacking-session-stealing-session-fixation] -=================================================================================================================== +Sequestro de sessão, roubo de sessão, fixação de sessão +======================================================= -A gestão da sessão envolve alguns tipos de ataques. O atacante pode roubar a identificação da sessão da vítima ou forjar uma e assim obter acesso a uma aplicação web sem a senha real. Então o atacante pode fazer tudo o que o usuário puder, sem qualquer vestígio. A proteção está na configuração adequada tanto do PHP quanto do próprio servidor web. +Vários tipos de ataques estão associados ao gerenciamento de sessões. O invasor rouba ou injeta seu ID de sessão no usuário e, graças a isso, obtém acesso à aplicação web sem conhecer a senha do usuário. Ele pode então fazer qualquer coisa na aplicação sem que o usuário saiba. A defesa consiste na configuração correta do servidor e do PHP. -O Nette Framework configura o PHP automaticamente. Assim, os desenvolvedores não têm que se preocupar em como fazer uma sessão suficientemente protegida e podem se concentrar totalmente nas partes-chave da aplicação. Isto requer que a função `ini_set()` seja habilitada. +O Nette Framework configura o PHP automaticamente. O programador não precisa pensar em como proteger corretamente a sessão e pode se concentrar totalmente na criação da aplicação. No entanto, isso requer que a função `ini_set()` esteja habilitada. -Cookie SameSite .[#toc-samesite-cookie] -======================================= +SameSite cookie +=============== -Os cookies do SameSite fornecem um mecanismo para reconhecer o que levou a uma carga de página. O que é absolutamente crucial por razões de segurança. +Os cookies SameSite fornecem um mecanismo para reconhecer o que levou ao carregamento da página. Isso é absolutamente crucial para a segurança. -A bandeira SameSite pode ter três valores: `Lax`, `Strict` e `None` (requer HTTPS). Se uma solicitação de uma página vem diretamente da própria web ou o usuário abre a página digitando-a diretamente na barra de endereço ou clicando em um marcador de página, -o navegador envia todos os cookies para o servidor (ou seja, com as bandeiras `Lax`, `Strict` e `None`). Se um usuário chegar ao site via link de outro site, os cookies com as bandeiras `Lax` e `None` serão passados para o servidor. Se a solicitação surgir em outro -de outra origem, carregamento dentro de um iframe, usando JavaScript, etc., somente serão enviados cookies com a bandeira `None`. - -Por padrão, a Nette envia todos os cookies com a bandeira `Lax`. +O sinalizador SameSite pode ter três valores: `Lax`, `Strict` e `None` (este requer HTTPS). Se a requisição para a página vier diretamente do site ou o usuário abrir a página digitando diretamente na barra de endereços ou clicando em um favorito, o navegador enviará todos os cookies ao servidor (ou seja, com os sinalizadores `Lax`, `Strict` e `None`). Se o usuário clicar em um link de outro site para acessar o site, os cookies com os sinalizadores `Lax` e `None` serão passados para o servidor. Se a requisição surgir de outra forma, como o envio de um formulário POST de outro site, carregamento dentro de um iframe, usando JavaScript, etc., apenas os cookies com o sinalizador `None` serão enviados. +O Nette, por padrão, envia todos os cookies com o sinalizador `Lax`. {{leftbar: www:@menu-common}} diff --git a/nette/ro/@home.texy b/nette/ro/@home.texy index 93cb581542..b13f9c97eb 100644 --- a/nette/ro/@home.texy +++ b/nette/ro/@home.texy @@ -1,4 +1,4 @@ -Nette Documentație +Documentație Nette ****************** <div class=documentation> @@ -9,21 +9,21 @@ Nette Documentație Introducere ----------- - [De ce să folosiți Nette? |www:10-reasons-why-nette] -- [Instalare |Installation] -- [Creați prima dumneavoastră aplicație! |quickstart:] +- [Instalare |installation] +- [Scriem prima aplicație! |quickstart:] General ------- -- [Lista de pachete |www:packages] -- [Întreținere și PHP |www:maintenance] +- [Lista pachetelor |www:packages] +- [Mentenanță și versiuni PHP |www:maintenance] - [Note de lansare |https://nette.org/releases] -- [Ghid de actualizare |migrations:en] -- [Depanare |nette:Troubleshooting] +- [Trecerea la versiuni mai noi|migrations:en] +- [Rezolvarea problemelor |nette:troubleshooting] - [Cine creează Nette |https://nette.org/contributors] - [Istoria Nette |www:history] -- [Implică-te |contributing:] -- [Dezvoltarea sponsorilor |https://nette.org/en/donate] +- [Implicați-vă |contributing:] +- [Susțineți dezvoltarea |https://nette.org/cs/donate] - [Referință API |https://api.nette.org/] </div> @@ -31,20 +31,20 @@ General <div> -Aplicația Nette ---------------- +Aplicații în Nette +------------------ - [Cum funcționează aplicațiile? |application:how-it-works] -- [Bootstrap |application:Bootstrap] -- [Prezentatori |application:Presenters] -- [Șabloane |application:Templates] -- [Module |application:Modules] -- [Routing |application:Routing] -- [Crearea de legături URL |application:creating-links] +- [application:Bootstrapping] +- [Presenteri |application:presenters] +- [Șabloane |application:templates] +- [Structura directoarelor |application:directory-structure] +- [Rutare |application:routing] +- [Crearea linkurilor URL |application:creating-links] - [Componente interactive |application:components] -- [AJAX & Snippets |application:ajax] +- [AJAX & snippete |application:ajax] -- [Cele mai bune practici |best-practices:] +- [Tutoriale și proceduri |best-practices:] </div> @@ -53,21 +53,22 @@ Aplicația Nette Subiecte principale ------------------- -- [Configurație |nette:configuring] -- [Injectarea dependențelor |dependency-injection:] -- [Latte: Șabloane |latte:] -- [Tracy: Instrument de depanare |tracy:] +- [Configurare |nette:configuring] +- [Injecția de dependențe|dependency-injection:] +- [Latte: șabloane |latte:] +- [Tracy: depanare cod |tracy:] - [Formulare |forms:] -- [Baza de date |database:core] +- [Baze de date |database:guide] - [Autentificarea utilizatorilor |security:authentication] -- [Controlul accesului |security:authorization] -- [Sesiuni |http:Sessions] -- [Cerere și răspuns HTTP |http:] -- [Caching |caching:] +- [Verificarea permisiunilor |security:authorization] +- [http:Sessions] +- [Cerere & răspuns HTTP|http:] +- [Active |assets:] +- [Cache |caching:] - [Trimiterea de e-mailuri |mail:] -- [Schema: Validarea datelor |schema:] +- [Schema: validarea datelor |schema:] - [Generator de cod PHP |php-generator:] -- [Tester: Testarea unitară |tester:] +- [Tester: testare |tester:] </div> @@ -76,19 +77,19 @@ Subiecte principale Utilități --------- -- [Array-uri |utils:Arrays] +- [Array-uri |utils:arrays] - [Sistem de fișiere |utils:filesystem] - [Finder |utils:finder] -- [Elemente HTML |utils:HTML Elements] -- [Imagini |utils:Images] -- [JSON |utils:JSON] -- [NEON |neon:] -- [Hașurarea parolei |security:passwords] -- [SmartObject |utils:SmartObject] +- [Elemente HTML |utils:html-elements] +- [Imagini |utils:images] +- [utils:JSON] +- [NEON|neon:] +- [Hash-uirea parolelor |security:passwords] - [Tipuri PHP |utils:type] -- [Șiruri de caractere |utils:Strings] +- [Șiruri |utils:strings] - [Validatori |utils:validators] - [RobotLoader |robot-loader:] +- [SmartObject |utils:smartobject] & [StaticClass |utils:StaticClass] - [SafeStream |safe-stream:] - [...altele |utils:] </div> @@ -97,5 +98,5 @@ Utilități {{toc:no}} -{{description: Documentația oficială Nette: descrie modul în care funcționează Nette și cele mai bune practici pentru dezvoltarea de aplicații web.}} -{{maintitle: Nette Documentation}} +{{description: Documentația oficială Nette: descrie cum funcționează Nette și cele mai bune practici pentru dezvoltarea aplicațiilor web.}} +{{maintitle: Documentație Nette}} diff --git a/nette/ro/@menu-topics.texy b/nette/ro/@menu-topics.texy index a40d964123..ee2ffb3ef7 100644 --- a/nette/ro/@menu-topics.texy +++ b/nette/ro/@menu-topics.texy @@ -1,21 +1,21 @@ -Principalele subiecte -********************* -- [Configurație |nette:configuring] -- [Aplicație Nette |application:how-it-works] -- [Injectarea dependențelor |dependency-injection:] +Subiecte principale +******************* +- [Configurare |nette:configuring] +- [Aplicații în Nette |application:how-it-works] +- [Injecția de dependențe|dependency-injection:] - [Utilități |utils:] - [Formulare |forms:] -- [Bază de date |database:core] +- [Baze de date |database:guide] - [Autentificarea utilizatorilor |security:authentication] -- [Controlul accesului |security:authorization] -- [Sesiuni |http:Sessions] -- [Cerere și răspuns HTTP |http:] -- [Caching |caching:] +- [Verificarea permisiunilor |security:authorization] +- [http:Sessions] +- [Cerere & răspuns HTTP|http:] +- [Cache |caching:] - [Trimiterea de e-mailuri |mail:] -- [Schema: Validarea datelor |schema:] +- [Schema: validarea datelor |schema:] - [Generator de cod PHP |php-generator:] - [Latte: șabloane |latte:] -- [Tracy: depanare |tracy:] +- [Tracy: depanare cod |tracy:] - [Tester: testare |tester:] diff --git a/nette/ro/@meta.texy b/nette/ro/@meta.texy new file mode 100644 index 0000000000..9c744b37d6 --- /dev/null +++ b/nette/ro/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentație Nette}} diff --git a/nette/ro/configuring.texy b/nette/ro/configuring.texy index ea8407dafa..750df18f88 100644 --- a/nette/ro/configuring.texy +++ b/nette/ro/configuring.texy @@ -2,34 +2,35 @@ Configurarea Nette ****************** .[perex] -O prezentare generală a tuturor opțiunilor de configurare din cadrul Nette. +Prezentare generală a tuturor opțiunilor de configurare din Nette Framework. -Componentele Nette sunt configurate cu ajutorul fișierelor de configurare, care sunt de obicei scrise în [NEON |neon:format]. Acestea sunt editate cel mai bine în [editori care îl acceptă |best-practices:editors-and-tools#ide-editor]. -Dacă utilizați cadrul complet, configurația va fi [încărcată |bootstrap:] [în timpul pornirii |application:bootstrap#di-container-configuration], dacă nu, vedeți [cum se încarcă configurația |bootstrap:]. +Componentele Nette sunt configurate folosind fișiere de configurare, care sunt de obicei scrise în [formatul NEON|neon:format]. Cel mai bine se editează în [editoarele cu suport pentru acesta |best-practices:editors-and-tools#Editor IDE]. Dacă utilizați întregul framework, configurația este [încărcată la pornirea aplicației |application:bootstrapping#Configurarea containerului DI], dacă nu, citiți [cum să încărcați configurația|bootstrap:]. <pre> -"application .[prism-token prism-atrule]":[application:configuration#Application]: "Application .[prism-token prism-comment]"<br> -"constants .[prism-token prism-atrule]":[application:configuration#Constants]: "Definește constantele PHP .[prism-token prism-comment]"<br> -"database .[prism-token prism-atrule]":[database:configuration]: "Database .[prism-token prism-comment]"<br> +"application .[prism-token prism-atrule]":[application:configuration#Application]: "Aplicație .[prism-token prism-comment]"<br> +"assets .[prism-token prism-atrule]":[assets:configuration]: "Assets .[prism-token prism-comment]"<br> +"constants .[prism-token prism-atrule]":[application:configuration#Constante]: "Definirea constantelor PHP .[prism-token prism-comment]"<br> +"database .[prism-token prism-atrule]":[database:configuration]: "Bază de date .[prism-token prism-comment]"<br> "decorator .[prism-token prism-atrule]":[dependency-injection:configuration#Decorator]: "Decorator .[prism-token prism-comment]"<br> -"di .[prism-token prism-atrule]":[dependency-injection:configuration#DI]: "DI Container .[prism-token prism-comment]"<br> -"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Extensions]: "Instalează extensii DI suplimentare .[prism-token prism-comment]"<br> -"forms .[prism-token prism-atrule]":[forms:configuration]: "Forms .[prism-token prism-comment]"<br> -"http .[prism-token prism-atrule]":[http:configuration#HTTP Headers]: "Antete HTTP .[prism-token prism-comment]"<br> -"includes .[prism-token prism-atrule]":[dependency-injection:configuration#Including files]: "Includerea fișierelor .[prism-token prism-comment]"<br> -"latte .[prism-token prism-atrule]":[application:configuration#Latte]: "Latte .[prism-token prism-comment]"<br> -"mail .[prism-token prism-atrule]":[mail:#Configuring]: "Mailing .[prism-token prism-comment]"<br> -"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#Parameters]: "Parametrii .[prism-token prism-comment]"<br> -"php .[prism-token prism-atrule]":[application:configuration#PHP]: "Opțiuni de configurare PHP .[prism-token prism-comment]"<br> -"routing .[prism-token prism-atrule]":[application:configuration#Routing]: "Routing .[prism-token prism-comment]"<br> +"di .[prism-token prism-atrule]":[dependency-injection:configuration#DI]: "Container DI .[prism-token prism-comment]"<br> +"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Extensii]: "Instalarea altor extensii DI .[prism-token prism-comment]"<br> +"forms .[prism-token prism-atrule]":[forms:configuration]: "Formulare .[prism-token prism-comment]"<br> +"http .[prism-token prism-atrule]":[http:configuration#Antete HTTP]: "Antete HTTP .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[dependency-injection:configuration#Includerea fișierelor]: "Includere fișiere .[prism-token prism-comment]"<br> +"latte .[prism-token prism-atrule]":[application:configuration#Șabloane Latte]: "Șabloane Latte .[prism-token prism-comment]"<br> +"mail .[prism-token prism-atrule]":[mail:#Configurație]: "Mailuri .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#Parametri]: "Parametri .[prism-token prism-comment]"<br> +"php .[prism-token prism-atrule]":[application:configuration#PHP]: "Configurație PHP .[prism-token prism-comment]"<br> +"routing .[prism-token prism-atrule]":[application:configuration#Rutare]: "Rutare .[prism-token prism-comment]"<br> "search .[prism-token prism-atrule]":[dependency-injection:configuration#Search]: "Înregistrare automată a serviciilor .[prism-token prism-comment]"<br> -"security .[prism-token prism-atrule]":[security:configuration]: "Controlul accesului .[prism-token prism-comment]"<br> +"security .[prism-token prism-atrule]":[security:configuration]: "Permisiuni de acces .[prism-token prism-comment]"<br> "services .[prism-token prism-atrule]":[dependency-injection:services]: "Servicii .[prism-token prism-comment]"<br> -"session .[prism-token prism-atrule]":[http:configuration#Session]: "Sesiune .[prism-token prism-comment]"<br> -"tracy .[prism-token prism-atrule]":[tracy:configuring#Nette Framework]: "Tracy Debugger .[prism-token prism-comment]" +"session .[prism-token prism-atrule]":[http:configuration#Sesiune]: "Sesiune .[prism-token prism-comment]"<br> +"tracy .[prism-token prism-atrule]":[tracy:configuring#Nette Framework]: "Depanator Tracy .[prism-token prism-comment]" </pre> -Pentru a scrie un șir de caractere care conține caracterul `%`, you must escape it by doubling it to `%%`. .[note] +.[note] +Pentru a scrie un șir care conține caracterul `%`, trebuie să îl escapați dublându-l la `%%`. {{leftbar: @menu-topics}} diff --git a/nette/ro/glossary.texy b/nette/ro/glossary.texy index 0f11a3a37a..a90364944b 100644 --- a/nette/ro/glossary.texy +++ b/nette/ro/glossary.texy @@ -2,154 +2,158 @@ Glosar de termeni ***************** -AJAX .[#toc-ajax] ------------------ -Asynchronous JavaScript and XML - tehnologie pentru comunicarea client-server prin protocolul HTTP fără a fi necesară reîncărcarea întregii pagini la fiecare solicitare. În ciuda acronimului, formatul [JSON |#JSON] este adesea utilizat în loc de XML. +AJAX +---- +Asynchronous JavaScript and XML - tehnologie de schimb de informații între client și server prin protocolul HTTP fără a fi necesară reîncărcarea întregii pagini la fiecare cerere. Deși numele ar putea sugera că trimite date doar în format XML, se utilizează în mod obișnuit și formatul [#JSON]. -Acțiunea prezentatorului .[#toc-presenter-action] -------------------------------------------------- -Parte logică a [prezentatorului |#presenter], care efectuează o acțiune, cum ar fi afișarea unei pagini de produs, deconectarea unui utilizator etc. Un prezentator poate avea mai multe acțiuni. +Acțiune presenter +----------------- +Partea logică a presenterului care execută o singură acțiune. De exemplu, afișează pagina produsului, deconectează utilizatorul etc. Un presenter poate avea mai multe acțiuni. BOM --- -Așa-numita *mască de ordine a octeților* este un prim caracter special al unui fișier și indică ordinea octeților în codificare. Unii editori o includ în mod automat, este practic invizibilă, dar cauzează probleme cu anteturile și trimiterea de ieșire din PHP. Puteți utiliza [Code Checker |code-checker:] pentru eliminarea în masă. +Așa-numitul *byte order mark* este un caracter special la începutul fișierului, care se utilizează ca indicator al ordinii octeților în codificare. Unele editoare îl inserează în fișiere. Este practic invizibil, dar cauzează probleme cu trimiterea ieșirii și a antetelor din PHP. Pentru eliminarea în masă puteți utiliza [Code Checker|code-checker:]. -Controller .[#toc-controller] ------------------------------ -Controlerul procesează cererile de la utilizator și, pe baza acestora, apelează o anumită logică a aplicației (de exemplu, [modelul |#model]), apoi apelează [vizualizarea |#view] pentru redarea datelor. Analogia cu controlorii sunt [prezentatorii |#presenter] din Nette Framework. +Controller +---------- +Controlerul care procesează cererile utilizatorului și, pe baza acestora, apelează logica aplicației corespunzătoare (adică [modelul |#Model]) și apoi solicită [view-ului |#View] să redea datele. Echivalentul controllerelor în Nette Framework sunt [presenterele |#Presenter]. -Cross-Site Scripting (XSS) .[#toc-cross-site-scripting-xss] ------------------------------------------------------------ -Cross-Site Scripting este o metodă de perturbare a site-ului care utilizează intrări neeșalonate. Un atacator își poate injecta propriul cod HTML sau JavaScript și poate schimba aspectul paginii sau chiar colecta informații sensibile despre utilizatori. Protecția împotriva XSS este simplă: scăparea consecventă și corectă a tuturor șirurilor de caractere și intrărilor. +Cross-Site Scripting (XSS) +-------------------------- +Cross-Site Scripting este o metodă de compromitere a paginilor web care exploatează ieșirile neprocesate. Atacatorul poate apoi injecta propriul cod în pagină și astfel poate modifica pagina sau chiar obține date sensibile despre vizitatori. Protecția împotriva XSS se poate realiza doar prin procesarea consecventă și corectă a tuturor șirurilor. -Nette Framework vine cu o tehnologie nouă, [Context-Aware Escaping |latte:safety-first#context-aware-escaping], care vă va scăpa pentru totdeauna de riscurile Cross-Site Scripting. Aceasta evadează automat toate intrările pe baza unui anumit context, astfel încât este imposibil ca un programator să uite ceva din greșeală. +Nette Framework vine cu o tehnologie revoluționară [Escapare sensibilă la context |latte:safety-first#Escapare contextuală sensibilă], care vă scapă pentru totdeauna de riscul Cross-Site Scripting. Toate ieșirile sunt procesate automat, astfel încât nu se poate întâmpla ca un coder să uite ceva. -Cross-Site Request Forgery (CSRF) .[#toc-cross-site-request-forgery-csrf] -------------------------------------------------------------------------- -Un atac Cross-Site Request Forgery constă în faptul că atacatorul atrage victima să viziteze o pagină care execută în tăcere o cerere în browserul victimei către serverul unde victima este conectată în acel moment, iar serverul crede că cererea a fost făcută de victimă în mod voit. Serverul execută o anumită acțiune sub identitatea victimei, dar fără ca victima să își dea seama de acest lucru. Poate fi vorba de modificarea sau ștergerea datelor, trimiterea unui mesaj etc. +Cross-Site Request Forgery (CSRF) +--------------------------------- +Atacul Cross-Site Request Forgery constă în faptul că atacatorul ademenește victima pe o pagină care execută discret în browserul victimei o cerere către serverul pe care victima este conectată, iar serverul crede că cererea a fost executată de victimă din proprie inițiativă. Astfel, sub identitatea victimei, se efectuează o anumită acțiune fără ca aceasta să știe. Poate fi vorba de modificarea sau ștergerea datelor, trimiterea unui mesaj etc. -Nette Framework **protejează automat formularele și semnalele din prezentatori** împotriva acestui tip de atac. Acest lucru se face prin împiedicarea trimiterii sau apelării lor din alt domeniu. +Nette Framework **protejează automat formularele și semnalele în presentere** împotriva acestui tip de atac. Și anume, împiedicând trimiterea sau declanșarea lor dintr-un alt domeniu. -Injectarea dependențelor .[#toc-dependency-injection] ------------------------------------------------------ -Injecția de dependență (DI) este un model de proiectare care vă spune cum să separați crearea obiectelor de dependențele lor. Altfel spus, o clasă nu este responsabilă pentru crearea sau inițializarea dependențelor sale, ci aceste dependențe sunt furnizate de un cod extern (care poate include un [container DI |#Dependency Injection container]). Avantajul este că permite o mai mare flexibilitate a codului, o mai bună lizibilitate și o testare mai ușoară a aplicației, deoarece dependențele sunt ușor de înlocuit și izolate de alte părți ale codului. Pentru mai multe informații, consultați [Ce este injecția de dependență? |dependency-injection:introduction] +Dependency Injection +-------------------- +Dependency Injection (DI) este un pattern de design care spune cum să se separe crearea obiectelor de dependențele lor. Adică, clasa nu este responsabilă pentru crearea sau inițializarea dependențelor sale, ci în schimb aceste dependențe îi sunt furnizate de cod extern (acesta poate fi și un [container DI |#Container Dependency Injection]). Avantajul constă în faptul că permite o mai mare flexibilitate a codului, o mai bună inteligibilitate și o testare mai ușoară a aplicației, deoarece dependențele sunt ușor de înlocuit și izolate de celelalte părți ale codului. Mai multe în capitolul [Ce este Dependency Injection? |dependency-injection:introduction] -Containerul de injecție a dependențelor .[#toc-dependency-injection-container] ------------------------------------------------------------------------------- -Un container de injecție a dependențelor (de asemenea, container DI sau container IoC) este un instrument care se ocupă de crearea și gestionarea dependențelor într-o aplicație (sau [servicii |#service]). Un container are, de obicei, o configurație care definește ce clase sunt dependente de alte clase, ce implementări specifice ale dependențelor trebuie utilizate și cum să creeze aceste dependențe. Containerul creează apoi aceste obiecte și le furnizează claselor care au nevoie de ele. Pentru mai multe informații, consultați [Ce este un container DI? |dependency-injection:container] +Container Dependency Injection +------------------------------ +Containerul Dependency Injection (de asemenea, container DI sau container IoC) este un instrument care se ocupă de crearea și gestionarea dependențelor în aplicație (sau [servicii |#Serviciu]). Containerul are de obicei o configurație care definește ce clase sunt dependente de alte clase, ce implementări concrete ale dependențelor trebuie utilizate și cum trebuie create aceste dependențe. Apoi, containerul creează aceste obiecte și le furnizează claselor care au nevoie de ele. Mai multe în capitolul [Ce este un container DI? |dependency-injection:container] -Evadarea .[#toc-escaping] -------------------------- -Escaparea reprezintă conversia caracterelor cu semnificație specială în contextul dat în alte secvențe echivalente. Exemplu: Dorim să scriem ghilimele în șirul de caractere între ghilimele. Deoarece ghilimelele au o semnificație specială în contextul șirului închis cu ghilimele, este necesar să se utilizeze o altă secvență echivalentă. Secvența concretă este determinată de regulile contextului (de exemplu, `\"` în șirul de ghilimele din PHP, `"` în atributele HTML etc.). +Escapare +-------- +Escaparea este conversia caracterelor care au o semnificație specială într-un context dat în alte secvențe corespunzătoare. Exemplu: într-un șir delimitat de ghilimele dorim să scriem ghilimele. Deoarece ghilimelele au o semnificație specială în contextul șirului și simpla lor scriere ar fi înțeleasă ca sfârșitul șirului, este necesar să le scriem folosind o altă secvență corespunzătoare. Care anume este determinată de regulile contextului. -Filtru (fostul Helper) .[#toc-filter-formerly-helper] ------------------------------------------------------ -Funcție de filtrare. În șabloane, [filtrul |latte:syntax#filters] este o funcție care ajută la modificarea sau la formatarea datelor în forma de ieșire. Șabloanele au mai multe [filtre standard |latte:filters] predefinite. +Filtru (anterior helper) +------------------------ +În șabloane, sub termenul [filtru |latte:syntax#Filtre] se înțelege de obicei o funcție care ajută la modificarea sau reformatarea datelor în forma finală. Șabloanele dispun de mai multe [filtre standard |latte:filters]. -Invalidare .[#toc-invalidation] -------------------------------- -Notificarea unui [fragment |#snippet] de redat. În alt context, de asemenea, ștergerea unui cache. +Invalidare +---------- +Notificarea [snippet-ului |#Snippet] pentru a se redesena. Într-un alt sens, și ștergerea conținutului cache-ului. -JSON .[#toc-json] ------------------ -Format de schimb de date bazat pe sintaxa JavaScript (este subsetul acesteia). Specificațiile exacte pot fi găsite la www.json.org. +JSON +---- +Format pentru schimbul de date derivat din sintaxa JavaScript (este un subset al acesteia). Specificația exactă o găsiți pe pagina www.json.org. + + +Componentă +---------- +Parte reutilizabilă a aplicației. Poate fi o parte vizuală a paginii, așa cum descrie capitolul [Scrierea componentelor |application:components], sau sub termenul de componentă se înțelege și clasa [Component |component-model:] (o astfel de componentă nu trebuie să fie vizuală). -Componenta .[#toc-component] ----------------------------- -Parte reutilizabilă a unei aplicații. Poate fi o parte vizuală a unei pagini, așa cum este descrisă în capitolul [application:components], sau termenul poate reprezenta și clasa [Component |component-model:] (o astfel de componentă nu trebuie să fie neapărat vizuală). +Caractere de control +-------------------- +Caracterele de control sunt caractere invizibile care pot apărea în text și eventual pot cauza probleme. Pentru eliminarea lor în masă din fișiere puteți utiliza [Code Checker|code-checker:] și pentru eliminarea dintr-o variabilă funcția [Strings::normalize() |utils:strings#normalize]. -Caractere de control .[#toc-control-characters] ------------------------------------------------ -Caracterele de control sunt caractere invizibile, care pot apărea într-un text și care, în cele din urmă, pot cauza unele probleme. Pentru eliminarea lor în masă din fișiere, puteți utiliza [Code Checker |code-checker:], iar pentru eliminarea lor dintr-o variabilă utilizați funcția [Strings::normalize() |utils:strings#normalize]. +Evenimente +---------- +Un eveniment este o situație așteptată într-un obiect, care, atunci când apare, apelează așa-numiții handleri, adică callback-uri care reacționează la eveniment ("exemplu":https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38). Un eveniment poate fi, de exemplu, trimiterea unui formular, conectarea unui utilizator etc. Evenimentele sunt astfel o formă de *Inversion of Control*. +De exemplu, conectarea utilizatorului are loc în metoda `Nette\Security\User::login()`. Obiectul `User` are o variabilă publică `$onLoggedIn`, care este un array în care oricine poate adăuga un callback. În momentul în care utilizatorul se conectează, metoda `login()` apelează toate callback-urile din array. Numele variabilei în forma `onXyz` este o convenție utilizată în întregul Nette. -Evenimente .[#toc-events] -------------------------- -Un eveniment este o situație așteptată în obiect, care, atunci când se produce, se apelează așa-numitele handlers, adică callback-uri care reacționează la eveniment ("eșantion":https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38). Evenimentul poate fi, de exemplu, trimiterea unui formular, conectarea utilizatorului etc. Evenimentele sunt astfel o formă de *Inversie a controlului*. -De exemplu, o autentificare a unui utilizator are loc în metoda `Nette\Security\User::login()`. Obiectul `User` are o variabilă publică `$onLoggedIn`, care este o matrice la care oricine poate adăuga un callback. De îndată ce utilizatorul se conectează, metoda `login()` apelează toate callback-urile din matrice. Numele unei variabile de forma `onXyz` este o convenție utilizată în Nette. +Latte +----- +Unul dintre cele mai avansate [sisteme de șabloane |latte:]. -Latte .[#toc-latte] -------------------- -Unul dintre cele mai inovatoare [sisteme de șabloane |latte:] din toate timpurile. +Model +----- +Modelul este baza de date și, în special, funcțională a întregii aplicații. Conține întreaga logică a aplicației (se utilizează și termenul de logică de business). Este acel **M** din **M**VC sau MVP. Orice acțiune a utilizatorului (conectare, adăugarea unui produs în coș, modificarea unei valori în baza de date) reprezintă o acțiune a modelului. +Modelul își gestionează starea internă și oferă o interfață fixă către exterior. Prin apelarea funcțiilor acestei interfețe, putem interoga sau modifica starea sa. Modelul nu știe de existența [view-ului |#View] sau a [controller-ului |#Controller]. -Model .[#toc-model] -------------------- -Modelul reprezintă baza de date și funcții a întregii aplicații. Acesta include întreaga logică a aplicației (denumită uneori și "logică de afaceri"). Este **M** din **M**VC sau MPV. Orice acțiune a utilizatorului (logare, introducerea de produse în coș, modificarea unei valori din baza de date) reprezintă o acțiune a modelului. -Modelul își gestionează starea internă și oferă o interfață publică. Prin apelarea acestei interfețe, putem lua sau modifica starea sa. Modelul nu știe de existența unei [vizualizări |#view] sau a unui [controler |#controller], modelul este total independent de acestea. +Model-View-Controller +--------------------- +Arhitectură software care a apărut din nevoia de a separa în aplicațiile cu interfață grafică codul de gestionare ([#controller]) de codul logicii aplicației ([#model]) și de codul care afișează datele ([#view]). Astfel, aplicația devine mai clară, facilitează dezvoltarea viitoare și permite testarea separată a componentelor individuale. -Model-Vizualizare-Controler .[#toc-model-view-controller] ---------------------------------------------------------- -Arhitectură software, apărută în dezvoltarea aplicațiilor GUI pentru a separa codul pentru controlul fluxului ([controler |#controller]) de codul logicii aplicației ([model |#model]) și de codul de redare a datelor ([view |#view]). În acest fel, codul este mai ușor de înțeles, facilitează dezvoltarea viitoare și permite testarea separată a părților separate. +Model-View-Presenter +-------------------- +Arhitectură, derivată din [#Model-View-Controller]. -Model-View-Presenter .[#toc-model-view-presenter] -------------------------------------------------- -Arhitectură bazată pe [Model-View-Controller |#Model-View-Controller]. +Modul +----- +Modulul reprezintă o parte logică a aplicației. Într-o structură tipică, este vorba de un grup de presentere și șabloane care abordează un anumit domeniu de funcționalitate. Modulele le plasăm în [directoare separate |application:directory-structure#Presentere și șabloane], cum ar fi `Front/`, `Admin/` sau `Shop/`. +De exemplu, un magazin online îl împărțim în: +- Frontend (`Shop/`) pentru vizualizarea produselor și cumpărare +- Secțiunea client (`Customer/`) pentru gestionarea comenzilor +- Administrare (`Admin/`) pentru operator -Modul .[#toc-module] --------------------- -[Modulul |application:modules] în Nette Framework reprezintă o colecție de prezentatori și șabloane, eventual și componente și modele, care servesc date unui prezentator. Este deci o anumită parte logică a unei aplicații. +Din punct de vedere tehnic, sunt directoare obișnuite, dar datorită structurării clare ajută la scalarea aplicației. Presenterul `Admin:Product:List` va fi astfel plasat fizic, de exemplu, în directorul `app/Presentation/Admin/Product/List/` (vezi [maparea presenter-ilor |application:directory-structure#Maparea presenterelor]). -De exemplu, un magazin electronic poate avea trei module: -1) Catalog de produse cu coș. -2) Administrare pentru client. -3) Administrare pentru comerciant. +Namespace +--------- +Spațiu de nume, parte a limbajului PHP începând cu versiunea 5.3 și a altor limbaje de programare, care permite utilizarea claselor denumite la fel în diferite biblioteci, fără a apărea coliziuni de nume. Vezi [documentația PHP |https://www.php.net/manual/en/language.namespaces.rationale.php]. -Spațiul de nume .[#toc-namespace] ---------------------------------- -Namespace este o caracteristică a limbajului PHP începând cu versiunea sa 5.3 și a altor câteva limbaje de programare, de asemenea. Aceasta ajută la evitarea coliziunilor de nume (de exemplu, două clase cu același nume) atunci când se utilizează împreună biblioteci diferite. Pentru mai multe detalii, consultați [documentația PHP |https://www.php.net/manual/en/language.namespaces.rationale.php]. +Presenter +--------- +Presenterul este un obiect care preia [cererea |api:Nette\Application\Request] tradusă de router din cererea HTTP și generează [răspunsul |api:Nette\Application\Response]. Răspunsul poate fi o pagină HTML, o imagine, un document XML, un fișier pe disc, JSON, o redirecționare sau orice altceva vă puteți imagina. -Prezentator .[#toc-presenter] ------------------------------ -Prezentatorul este un obiect care preia [cererea |api:Nette\Application\Request] tradusă de către router din cererea HTTP și generează un [răspuns |api:Nette\Application\Response]. Răspunsul poate fi o pagină HTML, o imagine, un document XML, un fișier, JSON, o redirecționare sau orice altceva la care vă gândiți. +De obicei, sub termenul de presenter se înțelege un descendent al clasei [api:Nette\Application\UI\Presenter]. În funcție de cererile primite, execută [acțiunile |application:presenters#Ciclul de viață al presenterului] corespunzătoare și redă șabloanele. -Prin prezentator se înțelege, de obicei, un descendent al clasei [api:Nette\Application\UI\Presenter]. Prin cereri, acesta execută [acțiunile |application:presenters#life-cycle-of-presenter] corespunzătoare și redă șabloanele. +Router +------ +Traducător bidirecțional între cererea HTTP / URL și acțiunea presenterului. Bidirecțional înseamnă că din cererea HTTP se poate deduce [acțiunea presenter-ului |#Acțiune presenter], dar și invers, pentru o acțiune se poate genera URL-ul corespunzător. Mai multe în capitolul despre [rutarea URL-urilor |application:routing]. -Router .[#toc-router] ---------------------- -Traducător bidirecțional între cererea HTTP / URL și acțiunea prezentatorului. Bidirecțional înseamnă că nu numai că este posibilă derivarea unei acțiuni de [prezentare |#presenter action] din cererea HTTP, ci și generarea unui URL corespunzător pentru o acțiune. Pentru mai multe informații, consultați capitolul despre [rutarea URL |application:routing]. +Cookie SameSite +--------------- +Cookie-urile SameSite oferă un mecanism pentru a recunoaște ce a condus la încărcarea paginii. Poate avea trei valori: `Lax`, `Strict` și `None` (acesta necesită HTTPS). Dacă cererea pentru pagină vine direct de pe site sau utilizatorul deschide pagina introducând-o direct în bara de adrese sau făcând clic pe un bookmark, browserul trimite serverului toate cookie-urile (adică cu flag-urile `Lax`, `Strict` și `None`). Dacă utilizatorul ajunge pe site printr-un link de pe alt site, se transmit serverului cookie-urile cu flag-urile `Lax` și `None`. Dacă cererea apare în alt mod, cum ar fi trimiterea unui formular POST de pe alt site, încărcarea în interiorul unui iframe, folosind JavaScript etc., se trimit doar cookie-urile cu flag-ul `None`. -Cookie SameSite .[#toc-samesite-cookie] ---------------------------------------- -Cookie-urile SameSite oferă un mecanism de recunoaștere a ceea ce a dus la încărcarea paginii. Acesta poate avea trei valori: `Lax`, `Strict` și `None` (acesta din urmă necesită HTTPS). Dacă solicitarea paginii vine direct de pe site sau dacă utilizatorul deschide pagina tastând direct în bara de adrese sau făcând clic pe un marcaj, browserul trimite toate cookie-urile către server (adică cu steagurile `Lax`, `Strict` și `None`). În cazul în care utilizatorul face clic pe site prin intermediul unui link de pe un alt site, cookie-urile cu stegulețele `Lax` și `None` sunt transmise serverului. În cazul în care solicitarea este făcută prin alte mijloace, cum ar fi trimiterea unui formular POST de pe un alt site, încărcarea în interiorul unui iframe, utilizarea JavaScript etc., sunt trimise numai cookie-uri cu marcajul `None`. +Serviciu +-------- +În contextul Dependency Injection, ca serviciu se desemnează un obiect care este creat și gestionat de containerul DI. Un serviciu poate fi ușor înlocuit cu o altă implementare, de exemplu în scopuri de testare sau pentru a schimba comportamentul aplicației, fără a fi necesară modificarea codului care utilizează serviciul. -Serviciul .[#toc-service] -------------------------- -În contextul Injecției de dependență, un serviciu se referă la un obiect care este creat și gestionat de un container DI. Un serviciu poate fi înlocuit cu ușurință cu o altă implementare, de exemplu în scopuri de testare sau pentru a schimba comportamentul unei aplicații, fără a fi nevoie să se modifice codul care utilizează serviciul. +Snippet +------- +Fragment, parte a paginii care poate fi redesenată separat în timpul unei cereri AJAX. -Fragmentul .[#toc-snippet] --------------------------- -Fragment de pagină, care poate fi redat separat în timpul unei cereri [AJAX |#AJAX]. + +View +---- +View, adică vizualizarea, este stratul aplicației responsabil pentru afișarea rezultatului cererii. De obicei, utilizează un sistem de șabloane și știe cum să afișeze o anumită componentă sau rezultatul obținut din model. -Vizualizare .[#toc-view] ------------------------- -Vizualizarea este un nivel al aplicației, care este responsabil pentru redarea rezultatelor cererii. De obicei, utilizează un sistem de modelare și știe cum să redea componentele sale sau rezultatele preluate din model. diff --git a/nette/ro/installation.texy b/nette/ro/installation.texy index a21291725e..6d44b8b0ba 100644 --- a/nette/ro/installation.texy +++ b/nette/ro/installation.texy @@ -2,66 +2,66 @@ Instalarea Nette **************** .[perex] -Doriți să profitați de avantajele Nette în proiectul dvs. existent sau intenționați să creați un nou proiect bazat pe Nette? Acest ghid vă va ghida pas cu pas în procesul de instalare. +Doriți să profitați de avantajele Nette în proiectul dvs. existent sau intenționați să creați un nou proiect bazat pe Nette? Acest ghid vă va conduce prin procesul de instalare pas cu pas. -Cum să adăugați Nette la proiectul dvs. .[#toc-how-to-add-nette-to-your-project] --------------------------------------------------------------------------------- +Cum să adăugați Nette la proiectul dvs. +--------------------------------------- -Nette oferă o colecție de pachete (biblioteci) utile și sofisticate pentru PHP. Pentru a le încorpora în proiectul dumneavoastră, urmați acești pași: +Nette oferă o colecție de pachete (biblioteci) utile și avansate pentru PHP. Pentru a le integra în proiectul dvs., urmați acești pași: -1) **Configurați [Composer |best-practices:composer]:** Acest instrument este esențial pentru instalarea, actualizarea și gestionarea facilă a bibliotecilor necesare pentru proiectul dumneavoastră. +1) **Pregătiți [Composer|best-practices:composer]:** Acest instrument este esențial pentru instalarea, actualizarea și gestionarea ușoară a bibliotecilor necesare pentru proiectul dvs. -2) **Alegeți un [pachet |www:packages]:** Să presupunem că aveți nevoie să navigați prin sistemul de fișiere, lucru pe care [Finder |utils:finder] din pachetul `nette/utils` îl face excelent. Puteți găsi numele pachetului în coloana din dreapta a documentației sale. +2) **Alegeți un [pachet|www:packages]:** Să presupunem că trebuie să navigați prin sistemul de fișiere, ceea ce [Finder|utils:finder] din pachetul `nette/utils` face excelent. Puteți vedea numele pachetului în coloana din dreapta a documentației sale. -3) **Instalați pachetul:** Rulați această comandă în directorul rădăcină al proiectului dvs: +3) **Instalați pachetul:** Rulați această comandă în directorul rădăcină al proiectului dvs.: ```shell composer require nette/utils ``` -Preferați o interfață grafică? Consultați [ghidul |https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] de instalare a pachetelor în mediul PhpStrom. +Preferiți o interfață grafică? Consultați [ghidul|https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] pentru instalarea pachetelor în mediul PhpStorm. -Cum să începeți un proiect nou cu Nette .[#toc-how-to-start-a-new-project-with-nette] -------------------------------------------------------------------------------------- +Cum să începeți un nou proiect cu Nette +--------------------------------------- -Dacă doriți să creați un proiect complet nou pe platforma Nette, vă recomandăm să folosiți scheletul predefinit [Web Project |https://github.com/nette/web-project]: +Dacă doriți să creați un proiect complet nou pe platforma Nette, vă recomandăm să utilizați scheletul preconfigurat [Web Project|https://github.com/nette/web-project]: -1) **Set up [Composer |best-practices:composer].** +1) **Pregătiți [Composer|best-practices:composer].** -2) **Deschideți linia de comandă** și navigați în directorul rădăcină al serverului dvs. web, de exemplu, `/etc/var/www`, `C:/xampp/htdocs`, `/Library/WebServer/Documents`. +2) **Deschideți linia de comandă** și navigați la directorul rădăcină al serverului dvs. web, de exemplu, `/etc/var/www`, `C:/xampp/htdocs`, `/Library/WebServer/Documents`. -3) **Creează proiectul** folosind această comandă: +3) **Creați proiectul** folosind această comandă: ```shell -composer create-project nette/web-project PROJECT_NAME +composer create-project nette/web-project NUME_PROIECT ``` -4) **Nu folosiți Composer?** Descărcați [proiectul web în format ZIP |https://github.com/nette/web-project/archive/preloaded.zip] și extrageți-l. Dar aveți încredere în noi, Composer merită! +4) **Nu folosiți Composer?** Doar descărcați [Web Project în format ZIP|https://github.com/nette/web-project/archive/preloaded.zip] și dezarhivați-l. Dar credeți-ne, Composer merită! -5) **Stabilirea permisiunilor:** Pe sistemele macOS sau Linux, setați [permisiunile de scriere |nette:troubleshooting#Setting directory permissions] pentru directoare. +5) **Setarea permisiunilor:** Pe sistemele macOS sau Linux, setați [permisiunile de scriere |nette:troubleshooting#Setarea permisiunilor pentru directoare] pentru directoare. -6) **Deschideți proiectul într-un browser:** Introduceți URL-ul `http://localhost/PROJECT_NAME/www/`. Veți vedea pagina de destinație a scheletului: +6) **Deschiderea proiectului în browser:** Introduceți URL-ul `http://localhost/NUME_PROIECT/www/` și veți vedea pagina de start a scheletului: -[* qs-welcome.webp .{url: http://localhost/PROJECT_NAME/www/} *] +[* qs-welcome.webp .{url: http://localhost/NUME_PROIECT/www/} *] -Felicitări! Site-ul dvs. este acum pregătit pentru dezvoltare. Nu ezitați să eliminați șablonul de bun venit și începeți să vă construiți aplicația. +Felicitări! Site-ul dvs. este acum gata pentru dezvoltare. Puteți elimina șablonul de bun venit și începe să creați aplicația dvs. -Unul dintre avantajele Nette este că proiectul funcționează imediat, fără a fi nevoie de configurare. Cu toate acestea, dacă întâmpinați probleme, luați în considerare posibilitatea de a consulta [soluțiile la problemele comune |nette:troubleshooting#nette-is-not-working-white-page-is-displayed]. +Unul dintre avantajele Nette este că proiectul funcționează imediat, fără a necesita configurare. Cu toate acestea, dacă întâmpinați probleme, încercați să consultați [soluții la problemele frecvente |nette:troubleshooting#Nette nu funcționează se afișează o pagină albă]. .[note] -Dacă sunteți la început cu Nette, vă recomandăm să continuați cu [tutorialul Create Your First Application |quickstart:]. +Dacă sunteți la început cu Nette, vă recomandăm să continuați cu [tutorialul Scrierea primei aplicații|quickstart:]. -Instrumente și recomandări .[#toc-tools-and-recommendations] ------------------------------------------------------------- +Instrumente și recomandări +-------------------------- -Pentru a lucra eficient cu Nette, vă recomandăm următoarele instrumente: +Pentru a lucra eficient cu Nette, recomandăm următoarele instrumente: -- [IDE de înaltă calitate cu plugin-uri pentru Nette |best-practices:editors-and-tools] -- Sistem de control al versiunilor Git -- [Compozitor |best-practices:composer] +- [IDE de calitate cu plugin-uri pentru Nette|best-practices:editors-and-tools] +- Sistemul de versionare Git +- [Composer|best-practices:composer] {{leftbar: www:@menu-common}} diff --git a/nette/ro/introduction-to-object-oriented-programming.texy b/nette/ro/introduction-to-object-oriented-programming.texy new file mode 100644 index 0000000000..5630142e56 --- /dev/null +++ b/nette/ro/introduction-to-object-oriented-programming.texy @@ -0,0 +1,841 @@ +Introducere în programarea orientată pe obiecte +*********************************************** + +.[perex] +Termenul "OOP" se referă la programarea orientată pe obiecte, care este o modalitate de a organiza și structura codul. OOP ne permite să vedem programul ca pe un set de obiecte care comunică între ele, în loc de o secvență de comenzi și funcții. + +În OOP, un "obiect" este o unitate care conține date și funcții care lucrează cu aceste date. Obiectele sunt create conform "claselor", pe care le putem înțelege ca planuri sau șabloane pentru obiecte. Când avem o clasă, putem crea o "instanță" a acesteia, care este un obiect specific creat conform acelei clase. + +Să vedem cum putem crea o clasă simplă în PHP. Când definim o clasă, folosim cuvântul cheie "class", urmat de numele clasei și apoi acolade care înconjoară funcțiile (numite "metode") și variabilele clasei (numite "proprietăți"): + +```php +class Masina +{ + function claxoneaza() + { + echo 'Bip bip!'; + } +} +``` + +În acest exemplu, am creat o clasă numită `Masina` cu o singură funcție (sau "metodă") numită `claxoneaza`. + +Fiecare clasă ar trebui să rezolve doar o singură sarcină principală. Dacă o clasă face prea multe lucruri, ar putea fi potrivit să o împărțim în clase mai mici, specializate. + +Clasele sunt de obicei stocate în fișiere separate pentru a menține codul organizat și ușor de navigat. Numele fișierului ar trebui să corespundă numelui clasei, deci pentru clasa `Masina`, numele fișierului ar fi `Masina.php`. + +Când denumim clasele, este bine să respectăm convenția "PascalCase", ceea ce înseamnă că fiecare cuvânt din nume începe cu literă mare și nu există caractere de subliniere sau alți separatori între ele. Metodele și proprietățile folosesc convenția "camelCase", ceea ce înseamnă că încep cu literă mică. + +Unele metode în PHP au roluri speciale și sunt marcate cu prefixul `__` (două caractere de subliniere). Una dintre cele mai importante metode speciale este "constructorul", care este marcat ca `__construct`. Constructorul este o metodă care este apelată automat atunci când creați o nouă instanță a clasei. + +Constructorul este adesea folosit pentru a seta starea inițială a obiectului. De exemplu, atunci când creați un obiect care reprezintă o persoană, puteți folosi constructorul pentru a seta vârsta, numele sau alte proprietăți ale acesteia. + +Să vedem cum să folosim un constructor în PHP: + +```php +class Persoana +{ + private $varsta; + + function __construct($varsta) + { + $this->varsta = $varsta; + } + + function catiAniAi() + { + return $this->varsta; + } +} + +$persoana = new Persoana(25); +echo $persoana->catiAniAi(); // Afișează: 25 +``` + +În acest exemplu, clasa `Persoana` are o proprietate (variabilă) `$varsta` și un constructor care setează această proprietate. Metoda `catiAniAi()` permite apoi accesul la vârsta persoanei. + +Pseudovariabila `$this` este utilizată în interiorul clasei pentru a accesa proprietățile și metodele obiectului. + +Cuvântul cheie `new` este folosit pentru a crea o nouă instanță a clasei. În exemplul de mai sus, am creat o nouă persoană cu vârsta de 25 de ani. + +Puteți, de asemenea, să setați valori implicite pentru parametrii constructorului, dacă aceștia nu sunt specificați la crearea obiectului. De exemplu: + +```php +class Persoana +{ + private $varsta; + + function __construct($varsta = 20) + { + $this->varsta = $varsta; + } + + function catiAniAi() + { + return $this->varsta; + } +} + +$persoana = new Persoana; // dacă nu se transmite niciun argument, parantezele pot fi omise +echo $persoana->catiAniAi(); // Afișează: 20 +``` + +În acest exemplu, dacă nu specificați vârsta la crearea obiectului `Persoana`, va fi utilizată valoarea implicită 20. + +Este convenabil că definiția proprietății cu inițializarea sa prin constructor poate fi scurtată și simplificată astfel: + +```php +class Persoana +{ + function __construct( + private $varsta = 20, + ) { + } +} +``` + +Pentru completitudine, pe lângă constructori, obiectele pot avea și destructori (metoda `__destruct`), care sunt apelați înainte ca obiectul să fie eliberat din memorie. + + +Spații de nume +-------------- + +Spațiile de nume (sau "namespaces" în engleză) ne permit să organizăm și să grupăm clase, funcții și constante înrudite, evitând în același timp conflictele de nume. Le puteți imagina ca pe niște foldere pe computer, unde fiecare folder conține fișiere care aparțin unui anumit proiect sau subiect. + +Spațiile de nume sunt deosebit de utile în proiecte mai mari sau atunci când utilizați biblioteci de la terți, unde ar putea apărea conflicte în numele claselor. + +Imaginați-vă că aveți o clasă numită `Masina` în proiectul dvs. și doriți să o plasați într-un spațiu de nume numit `Transport`. Faceți acest lucru astfel: + +```php +namespace Transport; + +class Masina +{ + function claxoneaza() + { + echo 'Bip bip!'; + } +} +``` + +Dacă doriți să utilizați clasa `Masina` într-un alt fișier, trebuie să specificați din ce spațiu de nume provine clasa: + +```php +$masina = new Transport\Masina; +``` + +Pentru simplificare, puteți specifica la începutul fișierului ce clasă dintr-un anumit spațiu de nume doriți să utilizați, ceea ce permite crearea instanțelor fără a fi nevoie să specificați calea completă: + +```php +use Transport\Masina; + +$masina = new Masina; +``` + + +Moștenire +--------- + +Moștenirea este un instrument al programării orientate pe obiecte care permite crearea de noi clase pe baza claselor existente, preluând proprietățile și metodele acestora și extinzându-le sau redefinindu-le după necesități. Moștenirea permite asigurarea reutilizabilității codului și a ierarhiei claselor. + +Pe scurt, dacă avem o clasă și am dori să creăm alta, derivată din ea, dar cu câteva modificări, putem "moșteni" noua clasă din clasa originală. + +În PHP, moștenirea se realizează folosind cuvântul cheie `extends`. + +Clasa noastră `Persoana` stochează informații despre vârstă. Putem avea o altă clasă `Student`, care extinde `Persoana` și adaugă informații despre domeniul de studiu. + +Să vedem un exemplu: + +```php +class Persoana +{ + private $varsta; + + function __construct($varsta) + { + $this->varsta = $varsta; + } + + function afiseazaInformatii() + { + echo "Vârstă: {$this->varsta} ani\n"; + } +} + +class Student extends Persoana +{ + private $specializare; + + function __construct($varsta, $specializare) + { + parent::__construct($varsta); + $this->specializare = $specializare; + } + + function afiseazaInformatii() + { + parent::afiseazaInformatii(); + echo "Specializare: {$this->specializare} \n"; + } +} + +$student = new Student(20, 'Informatică'); +$student->afiseazaInformatii(); +``` + +Cum funcționează acest cod? + +- Am folosit cuvântul cheie `extends` pentru a extinde clasa `Persoana`, ceea ce înseamnă că clasa `Student` moștenește toate metodele și proprietățile din `Persoana`. + +- Cuvântul cheie `parent::` ne permite să apelăm metode din clasa părinte. În acest caz, am apelat constructorul din clasa `Persoana` înainte de a adăuga funcționalitatea proprie în clasa `Student`. Și în mod similar, metoda `afiseazaInformatii()` a părintelui înainte de a afișa informațiile despre student. + +Moștenirea este destinată situațiilor în care există o relație "este" între clase. De exemplu, `Student` este o `Persoana`. Pisica este un animal. Ne oferă posibilitatea, în cazurile în care în cod ne așteptăm la un obiect (de ex. "Persoana"), să folosim în locul lui un obiect moștenit (de ex. "Student"). + +Este important de reținut că scopul principal al moștenirii **nu este** evitarea duplicării codului. Dimpotrivă, utilizarea incorectă a moștenirii poate duce la un cod complex și greu de întreținut. Dacă relația "este" între clase nu există, ar trebui să luăm în considerare compoziția în locul moștenirii. + +Observați că metodele `afiseazaInformatii()` din clasele `Persoana` și `Student` afișează informații ușor diferite. Și putem adăuga alte clase (de exemplu, `Angajat`), care vor oferi alte implementări ale acestei metode. Capacitatea obiectelor de diferite clase de a răspunde la aceeași metodă în moduri diferite se numește polimorfism: + +```php +$persoane = [ + new Persoana(30), + new Student(20, 'Informatică'), + new Angajat(45, 'Director'), +]; + +foreach ($persoane as $persoana) { + $persoana->afiseazaInformatii(); +} +``` + + +Compoziție +---------- + +Compoziția este o tehnică în care, în loc să moștenim proprietățile și metodele unei alte clase, pur și simplu folosim instanța acesteia în clasa noastră. Acest lucru ne permite să combinăm funcționalitățile și proprietățile mai multor clase fără a fi nevoie să creăm structuri de moștenire complexe. + +Să vedem un exemplu. Avem clasa `Motor` și clasa `Masina`. În loc să spunem "Masina este un Motor", spunem "Masina are un Motor", ceea ce este o relație tipică de compoziție. + +```php +class Motor +{ + function porneste() + { + echo 'Motorul funcționează.'; + } +} + +class Masina +{ + private $motor; + + function __construct() + { + $this->motor = new Motor; + } + + function pornesteMasina() + { + $this->motor->porneste(); + echo 'Mașina este gata de drum!'; + } +} + +$masina = new Masina; +$masina->pornesteMasina(); +``` + +Aici, `Masina` nu are toate proprietățile și metodele lui `Motor`, dar are acces la acesta prin intermediul proprietății `$motor`. + +Avantajul compoziției este flexibilitatea mai mare în design și posibilitatea mai bună de modificare în viitor. + + +Vizibilitate +------------ + +În PHP, puteți defini "vizibilitatea" pentru proprietățile, metodele și constantele unei clase. Vizibilitatea determină de unde puteți accesa aceste elemente. + +1. **Public:** Dacă un element este marcat ca `public`, înseamnă că îl puteți accesa de oriunde, chiar și din afara clasei. + +2. **Protected:** Un element marcat ca `protected` este accesibil numai în cadrul clasei respective și al tuturor descendenților săi (clasele care moștenesc de la această clasă). + +3. **Private:** Dacă un element este `private`, îl puteți accesa numai din interiorul clasei în care a fost definit. + +Dacă nu specificați vizibilitatea, PHP o va seta automat la `public`. + +Să vedem un exemplu de cod: + +```php +class ExempluVizibilitate +{ + public $proprietatePublica = 'Publică'; + protected $proprietateProtejata = 'Protejată'; + private $proprietatePrivata = 'Privată'; + + public function afiseazaProprietati() + { + echo $this->proprietatePublica; // Funcționează + echo $this->proprietateProtejata; // Funcționează + echo $this->proprietatePrivata; // Funcționează + } +} + +$obiect = new ExempluVizibilitate; +$obiect->afiseazaProprietati(); +echo $obiect->proprietatePublica; // Funcționează +// echo $obiect->proprietateProtejata; // Generează eroare +// echo $obiect->proprietatePrivata; // Generează eroare +``` + +Continuăm cu moștenirea clasei: + +```php +class ClasaDescendent extends ExempluVizibilitate +{ + public function afiseazaProprietati() + { + echo $this->proprietatePublica; // Funcționează + echo $this->proprietateProtejata; // Funcționează + // echo $this->proprietatePrivata; // Generează eroare + } +} +``` + +În acest caz, metoda `afiseazaProprietati()` din clasa `ClasaDescendent` poate accesa proprietățile publice și protejate, dar nu poate accesa proprietățile private ale clasei părinte. + +Datele și metodele ar trebui să fie cât mai ascunse posibil și accesibile numai printr-o interfață definită. Acest lucru vă permite să modificați implementarea internă a clasei fără a afecta restul codului. + + +Cuvântul cheie `final` +---------------------- + +În PHP, putem folosi cuvântul cheie `final` dacă dorim să împiedicăm o clasă, metodă sau constantă să fie moștenită sau suprascrisă. Când marcăm o clasă ca `final`, aceasta nu poate fi extinsă. Când marcăm o metodă ca `final`, aceasta nu poate fi suprascrisă într-o clasă descendentă. + +Conștientizarea faptului că o anumită clasă sau metodă nu va fi modificată ulterior ne permite să facem modificări mai ușor, fără a ne face griji cu privire la posibile conflicte. De exemplu, putem adăuga o nouă metodă fără teama că un descendent al său ar avea deja o metodă cu același nume și ar apărea o coliziune. Sau putem modifica parametrii unei metode, deoarece din nou nu există riscul de a provoca o neconcordanță cu metoda suprascrisă într-un descendent. + +```php +final class ClasaFinala +{ +} + +// Următorul cod va genera o eroare, deoarece nu putem moșteni de la o clasă finală. +class DescendentClasaFinala extends ClasaFinala +{ +} +``` + +În acest exemplu, încercarea de a moșteni de la clasa finală `ClasaFinala` va genera o eroare. + + +Proprietăți și metode statice +----------------------------- + +Când vorbim în PHP despre elemente "statice" ale unei clase, ne referim la metode și proprietăți care aparțin clasei în sine, și nu unei instanțe specifice a acestei clase. Acest lucru înseamnă că nu trebuie să creați o instanță a clasei pentru a avea acces la ele. În schimb, le apelați sau accesați direct prin numele clasei. + +Rețineți că, deoarece elementele statice aparțin clasei și nu instanțelor sale, nu puteți utiliza pseudovariabila `$this` în interiorul metodelor statice. + +Utilizarea proprietăților statice duce la [cod neclar plin de capcane|dependency-injection:global-state], de aceea nu ar trebui să le folosiți niciodată și nici nu vom arăta aici un exemplu de utilizare. În schimb, metodele statice sunt utile. Exemplu de utilizare: + +```php +class Calculator +{ + public static function adunare($a, $b) + { + return $a + $b; + } + + public static function scadere($a, $b) + { + return $a - $b; + } +} + +// Utilizarea metodei statice fără a crea o instanță a clasei +echo Calculator::adunare(5, 3); // Rezultat: 8 +echo Calculator::scadere(5, 3); // Rezultat: 2 +``` + +În acest exemplu, am creat clasa `Calculator` cu două metode statice. Putem apela aceste metode direct fără a crea o instanță a clasei folosind operatorul `::`. Metodele statice sunt deosebit de utile pentru operații care nu depind de starea unei instanțe specifice a clasei. + + +Constante de clasă +------------------ + +În cadrul claselor, avem posibilitatea de a defini constante. Constantele sunt valori care nu se schimbă niciodată în timpul execuției programului. Spre deosebire de variabile, valoarea unei constante rămâne mereu aceeași. + +```php +class Masina +{ + public const NumarRoti = 4; + + public function afiseazaNumarRoti(): int + { + echo self::NumarRoti; + } +} + +echo Masina::NumarRoti; // Ieșire: 4 +``` + +În acest exemplu, avem clasa `Masina` cu constanta `NumarRoti`. Când dorim să accesăm constanta în interiorul clasei, putem folosi cuvântul cheie `self` în locul numelui clasei. + + +Interfețe de obiecte +-------------------- + +Interfețele de obiecte funcționează ca niște "contracte" pentru clase. Dacă o clasă trebuie să implementeze o interfață de obiect, trebuie să conțină toate metodele pe care le definește acea interfață. Este o modalitate excelentă de a asigura că anumite clase respectă același "contract" sau structură. + +În PHP, interfețele se definesc cu cuvântul cheie `interface`. Toate metodele definite într-o interfață sunt publice (`public`). Când o clasă implementează o interfață, folosește cuvântul cheie `implements`. + +```php +interface Animal +{ + function scoateSunet(); +} + +class Pisica implements Animal +{ + public function scoateSunet() + { + echo 'Miau'; + } +} + +$pisica = new Pisica; +$pisica->scoateSunet(); +``` + +Dacă o clasă implementează o interfață, dar nu sunt definite în ea toate metodele așteptate, PHP va genera o eroare. + +O clasă poate implementa mai multe interfețe simultan, ceea ce este o diferență față de moștenire, unde o clasă poate moșteni doar de la o singură clasă: + +```php +interface Paznic +{ + function pazesteCasa(); +} + +class Caine implements Animal, Paznic +{ + public function scoateSunet() + { + echo 'Ham'; + } + + public function pazesteCasa() + { + echo 'Câinele păzește cu atenție casa'; + } +} +``` + + +Clase abstracte +--------------- + +Clasele abstracte servesc ca șabloane de bază pentru alte clase, dar nu puteți crea instanțe ale acestora direct. Ele conțin o combinație de metode complete și metode abstracte, care nu au conținut definit. Clasele care moștenesc de la clase abstracte trebuie să furnizeze definiții pentru toate metodele abstracte ale părintelui. + +Pentru a defini o clasă abstractă, folosim cuvântul cheie `abstract`. + +```php +abstract class ClasaAbstracta +{ + public function metodaObisnuita() + { + echo 'Aceasta este o metodă obișnuită'; + } + + abstract public function metodaAbstracta(); +} + +class Descendent extends ClasaAbstracta +{ + public function metodaAbstracta() + { + echo 'Aceasta este implementarea metodei abstracte'; + } +} + +$instanta = new Descendent; +$instanta->metodaObisnuita(); +$instanta->metodaAbstracta(); +``` + +În acest exemplu, avem o clasă abstractă cu o metodă obișnuită și una abstractă. Apoi avem clasa `Descendent`, care moștenește de la `ClasaAbstracta` și furnizează implementarea pentru metoda abstractă. + +Cum diferă de fapt interfețele și clasele abstracte? Clasele abstracte pot conține atât metode abstracte, cât și concrete, în timp ce interfețele definesc doar ce metode trebuie să implementeze o clasă, dar nu oferă nicio implementare. O clasă poate moșteni doar de la o singură clasă abstractă, dar poate implementa un număr nelimitat de interfețe. + + +Verificarea tipurilor +--------------------- + +În programare, este foarte important să fim siguri că datele cu care lucrăm sunt de tipul corect. În PHP, avem instrumente care ne asigură acest lucru. Verificarea dacă datele au tipul corect se numește "verificarea tipurilor". + +Tipurile pe care le putem întâlni în PHP: + +1. **Tipuri de bază**: Includ `int` (numere întregi), `float` (numere zecimale), `bool` (valori de adevăr), `string` (șiruri de caractere), `array` (tablouri) și `null`. +2. **Clase**: Dacă dorim ca valoarea să fie o instanță a unei clase specifice. +3. **Interfețe**: Definește un set de metode pe care o clasă trebuie să le implementeze. O valoare care respectă interfața trebuie să aibă aceste metode. +4. **Tipuri mixte**: Putem specifica că o variabilă poate avea mai multe tipuri permise. +5. **Void**: Acest tip special indică faptul că o funcție sau metodă nu returnează nicio valoare. + +Să vedem cum să modificăm codul pentru a include tipuri: + +```php +class Persoana +{ + private int $varsta; + + public function __construct(int $varsta) + { + $this->varsta = $varsta; + } + + public function afiseazaVarsta(): void + { + echo "Această persoană are {$this->varsta} ani."; + } +} + +/** + * Funcție care primește un obiect al clasei Persoana și afișează vârsta persoanei. + */ +function afiseazaVarstaPersoanei(Persoana $persoana): void +{ + $persoana->afiseazaVarsta(); +} +``` + +În acest fel, ne-am asigurat că codul nostru așteaptă și lucrează cu date de tipul corect, ceea ce ne ajută să prevenim potențiale erori. + +Unele tipuri nu pot fi scrise direct în PHP. În acest caz, ele sunt specificate într-un comentariu phpDoc, care este un format standard pentru documentarea codului PHP, începând cu `/**` și terminând cu `*/`. Permite adăugarea de descrieri pentru clase, metode etc. Și, de asemenea, specificarea tipurilor complexe folosind așa-numitele adnotări `@var`, `@param` și `@return`. Aceste tipuri sunt apoi utilizate de instrumentele de analiză statică a codului, dar PHP în sine nu le verifică. + +```php +class Lista +{ + /** @var array<Persoana> notația indică faptul că este un array de obiecte Persoana */ + private array $persoane = []; + + public function adaugaPersoana(Persoana $persoana): void + { + $this->persoane[] = $persoana; + } +} +``` + + +Comparație și identitate +------------------------ + +În PHP, puteți compara obiecte în două moduri: + +1. Comparația valorilor `==`: Verifică dacă obiectele sunt de aceeași clasă și au aceleași valori în proprietățile lor. +2. Identitatea `===`: Verifică dacă este aceeași instanță a obiectului. + +```php +class Masina +{ + public string $marca; + + public function __construct(string $marca) + { + $this->marca = $marca; + } +} + +$masina1 = new Masina('Skoda'); +$masina2 = new Masina('Skoda'); +$masina3 = $masina1; + +var_dump($masina1 == $masina2); // true, deoarece au aceeași valoare +var_dump($masina1 === $masina2); // false, deoarece nu sunt aceeași instanță +var_dump($masina1 === $masina3); // true, deoarece $masina3 este aceeași instanță ca $masina1 +``` + + +Operatorul `instanceof` +----------------------- + +Operatorul `instanceof` permite verificarea dacă un obiect dat este o instanță a unei anumite clase, un descendent al acestei clase sau dacă implementează o anumită interfață. + +Să ne imaginăm că avem clasa `Persoana` și o altă clasă `Student`, care este un descendent al clasei `Persoana`: + +```php +class Persoana +{ + private int $varsta; + + public function __construct(int $varsta) + { + $this->varsta = $varsta; + } +} + +class Student extends Persoana +{ + private string $specializare; + + public function __construct(int $varsta, string $specializare) + { + parent::__construct($varsta); + $this->specializare = $specializare; + } +} + +$student = new Student(20, 'Informatică'); + +// Verifică dacă $student este o instanță a clasei Student +var_dump($student instanceof Student); // Ieșire: bool(true) + +// Verifică dacă $student este o instanță a clasei Persoana (deoarece Student este descendent al Persoana) +var_dump($student instanceof Persoana); // Ieșire: bool(true) +``` + +Din ieșiri reiese că obiectul `$student` este considerat simultan o instanță a ambelor clase - `Student` și `Persoana`. + + +Interfețe fluente +----------------- + +"Interfața fluentă" (în engleză "Fluent Interface") este o tehnică în OOP care permite înlănțuirea metodelor într-un singur apel. Acest lucru simplifică adesea și clarifică codul. + +Elementul cheie al unei interfețe fluente este că fiecare metodă din lanț returnează o referință la obiectul curent. Realizăm acest lucru folosind `return $this;` la sfârșitul metodei. Acest stil de programare este adesea asociat cu metodele numite "setters", care setează valorile proprietăților obiectului. + +Să vedem cum poate arăta o interfață fluentă pe exemplul trimiterii de emailuri: + +```php +public function trimiteMesaj() +{ + $email = new Email; + $email->setFrom('sender@example.com') + ->setRecipient('admin@example.com') + ->setMessage('Hello, this is a message.') + ->send(); +} +``` + +În acest exemplu, metodele `setFrom()`, `setRecipient()` și `setMessage()` servesc la setarea valorilor corespunzătoare (expeditor, destinatar, conținutul mesajului). După setarea fiecăreia dintre aceste valori, metodele ne returnează obiectul curent (`$email`), ceea ce ne permite să înlănțuim următoarea metodă după ea. În final, apelăm metoda `send()`, care trimite efectiv emailul. + +Datorită interfețelor fluente, putem scrie cod care este intuitiv și ușor de citit. + + +Copierea folosind `clone` +------------------------- + +În PHP, putem crea o copie a unui obiect folosind operatorul `clone`. În acest fel, obținem o nouă instanță cu conținut identic. + +Dacă trebuie să modificăm unele proprietăți ale obiectului la copiere, putem defini în clasă o metodă specială `__clone()`. Această metodă este apelată automat atunci când obiectul este clonat. + +```php +class Oaie +{ + public string $nume; + + public function __construct(string $nume) + { + $this->nume = $nume; + } + + public function __clone() + { + $this->nume = 'Clona ' . $this->nume; + } +} + +$original = new Oaie('Dolly'); +echo $original->nume . "\n"; // Afișează: Dolly + +$clona = clone $original; +echo $clona->nume . "\n"; // Afișează: Clona Dolly +``` + +În acest exemplu, avem clasa `Oaie` cu o singură proprietate `$nume`. Când clonăm o instanță a acestei clase, metoda `__clone()` se asigură că numele oii clonate primește prefixul "Clona". + + +Trait-uri +--------- + +Trait-urile în PHP sunt un instrument care permite partajarea metodelor, proprietăților și constantelor între clase și evitarea duplicării codului. Le puteți imagina ca pe un mecanism de "copiere și lipire" (Ctrl-C și Ctrl-V), în care conținutul trait-ului este "inserat" în clase. Acest lucru vă permite să reutilizați codul fără a fi nevoie să creați ierarhii de clase complicate. + +Să vedem un exemplu simplu despre cum să folosim trait-uri în PHP: + +```php +trait Claxonare +{ + public function claxoneaza() + { + echo 'Bip bip!'; + } +} + +class Masina +{ + use Claxonare; +} + +class Camion +{ + use Claxonare; +} + +$masina = new Masina; +$masina->claxoneaza(); // Afișează 'Bip bip!' + +$camion = new Camion; +$camion->claxoneaza(); // De asemenea, afișează 'Bip bip!' +``` + +În acest exemplu, avem un trait numit `Claxonare`, care conține o singură metodă `claxoneaza()`. Apoi avem două clase: `Masina` și `Camion`, care ambele folosesc trait-ul `Claxonare`. Datorită acestui fapt, ambele clase "au" metoda `claxoneaza()`, și o putem apela pe obiectele ambelor clase. + +Trait-urile vă permit să partajați codul între clase ușor și eficient. În același timp, ele nu intră în ierarhia de moștenire, adică `$masina instanceof Claxonare` va returna `false`. + + +Excepții +-------- + +Excepțiile în OOP ne permit să gestionăm elegant erorile și situațiile neașteptate în codul nostru. Sunt obiecte care conțin informații despre eroare sau situația neobișnuită. + +În PHP, avem clasa încorporată `Exception`, care servește ca bază pentru toate excepțiile. Aceasta are mai multe metode care ne permit să obținem mai multe informații despre excepție, cum ar fi mesajul de eroare, fișierul și linia unde a apărut eroarea etc. + +Când apare o eroare în cod, putem "arunca" o excepție folosind cuvântul cheie `throw`. + +```php +function impartire(float $a, float $b): float +{ + if ($b === 0) { + throw new Exception('Împărțire la zero!'); + } + return $a / $b; +} +``` + +Când funcția `impartire()` primește zero ca al doilea argument, aruncă o excepție cu mesajul de eroare `'Împărțire la zero!'`. Pentru a preveni căderea programului la aruncarea unei excepții, o prindem într-un bloc `try/catch`: + +```php +try { + echo impartire(10, 0); +} catch (Exception $e) { + echo 'Excepție prinsă: '. $e->getMessage(); +} +``` + +Codul care poate arunca o excepție este încapsulat într-un bloc `try`. Dacă o excepție este aruncată, execuția codului se mută în blocul `catch`, unde putem procesa excepția (de exemplu, afișând un mesaj de eroare). + +După blocurile `try` și `catch`, putem adăuga un bloc opțional `finally`, care se execută întotdeauna, indiferent dacă a fost aruncată sau nu o excepție (chiar și în cazul în care în blocul `try` sau `catch` folosim instrucțiunea `return`, `break` sau `continue`): + +```php +try { + echo impartire(10, 0); +} catch (Exception $e) { + echo 'Excepție prinsă: '. $e->getMessage(); +} finally { + // Codul care se execută întotdeauna, indiferent dacă a fost aruncată sau nu o excepție +} +``` + +Putem, de asemenea, crea propriile clase (ierarhie) de excepții, care moștenesc de la clasa Exception. Ca exemplu, să ne imaginăm o aplicație bancară simplă care permite efectuarea de depuneri și retrageri: + +```php +class ExceptieBancara extends Exception {} +class ExceptieFonduriInsuficiente extends ExceptieBancara {} +class ExceptieLimitaDepasita extends ExceptieBancara {} + +class ContBancar +{ + private int $sold = 0; + private int $limitaZilnica = 1000; + + public function depune(int $suma): int + { + $this->sold += $suma; + return $this->sold; + } + + public function retrage(int $suma): int + { + if ($suma > $this->sold) { + throw new ExceptieFonduriInsuficiente('Nu există suficiente fonduri în cont.'); + } + + if ($suma > $this->limitaZilnica) { + throw new ExceptieLimitaDepasita('Limita zilnică pentru retrageri a fost depășită.'); + } + + $this->sold -= $suma; + return $this->sold; + } +} +``` + +Pentru un singur bloc `try`, se pot specifica mai multe blocuri `catch`, dacă vă așteptați la diferite tipuri de excepții. + +```php +$cont = new ContBancar; +$cont->depune(500); + +try { + $cont->retrage(1500); +} catch (ExceptieLimitaDepasita $e) { + echo $e->getMessage(); +} catch (ExceptieFonduriInsuficiente $e) { + echo $e->getMessage(); +} catch (ExceptieBancara $e) { + echo 'A apărut o eroare în timpul efectuării operațiunii.'; +} +``` + +În acest exemplu, este important de observat ordinea blocurilor `catch`. Deoarece toate excepțiile moștenesc de la `ExceptieBancara`, dacă am avea acest bloc primul, toate excepțiile ar fi prinse în el, fără ca codul să ajungă la blocurile `catch` următoare. De aceea, este important să avem excepțiile mai specifice (adică cele care moștenesc de la altele) în blocul `catch` mai sus în ordine decât excepțiile lor părinte. + + +Iterație +-------- + +În PHP, puteți parcurge obiecte folosind bucla `foreach`, similar cu parcurgerea array-urilor. Pentru ca acest lucru să funcționeze, obiectul trebuie să implementeze o interfață specială. + +Prima opțiune este implementarea interfeței `Iterator`, care are metodele `current()` returnând valoarea curentă, `key()` returnând cheia, `next()` trecând la următoarea valoare, `rewind()` revenind la început și `valid()` verificând dacă nu am ajuns încă la sfârșit. + +A doua opțiune este implementarea interfeței `IteratorAggregate`, care are doar o singură metodă `getIterator()`. Aceasta returnează fie un obiect substitut care va asigura parcurgerea, fie poate reprezenta un generator, care este o funcție specială în care se folosește `yield` pentru a returna succesiv chei și valori: + +```php +class Persoana +{ + public function __construct( + public int $varsta, + ) { + } +} + +class Lista implements IteratorAggregate +{ + private array $persoane = []; + + public function adaugaPersoana(Persoana $persoana): void + { + $this->persoane[] = $persoana; + } + + public function getIterator(): Generator + { + foreach ($this->persoane as $persoana) { + yield $persoana; + } + } +} + +$lista = new Lista; +$lista->adaugaPersoana(new Persoana(30)); +$lista->adaugaPersoana(new Persoana(25)); + +foreach ($lista as $persoana) { + echo "Vârstă: {$persoana->varsta} ani \n"; +} +``` + + +Bune practici +------------- + +După ce ați însușit principiile de bază ale programării orientate pe obiecte, este important să vă concentrați asupra bunelor practici în OOP. Acestea vă vor ajuta să scrieți cod care nu este doar funcțional, ci și lizibil, ușor de înțeles și ușor de întreținut. + +1) **Separarea responsabilităților (Separation of Concerns)**: Fiecare clasă ar trebui să aibă o responsabilitate clar definită și ar trebui să rezolve doar o singură sarcină principală. Dacă o clasă face prea multe lucruri, poate fi potrivit să o împărțiți în clase mai mici, specializate. +2) **Încapsularea (Encapsulation)**: Datele și metodele ar trebui să fie cât mai ascunse posibil și accesibile numai printr-o interfață definită. Acest lucru vă permite să modificați implementarea internă a clasei fără a afecta restul codului. +3) **Injectarea dependențelor (Dependency Injection)**: În loc să creați dependențe direct în clasă, ar trebui să le "injectați" din exterior. Pentru o înțelegere mai profundă a acestui principiu, recomandăm [capitolele despre Dependency Injection|dependency-injection:introduction]. diff --git a/nette/ro/troubleshooting.texy b/nette/ro/troubleshooting.texy index 0d941fa940..a5c6566ab4 100644 --- a/nette/ro/troubleshooting.texy +++ b/nette/ro/troubleshooting.texy @@ -1,41 +1,70 @@ -Depanare -******** +Rezolvarea problemelor +********************** -Nette nu funcționează, este afișată o pagină albă .[#toc-nette-is-not-working-white-page-is-displayed] ------------------------------------------------------------------------------------------------------- -- Încercați să puneți `ini_set('display_errors', '1'); error_reporting(E_ALL);` după `declare(strict_types=1);` în fișierul `index.php` pentru a forța afișarea erorilor -- Dacă vedeți în continuare o pagină albă, probabil că există o eroare în configurarea serverului și veți descoperi motivul în jurnalul serverului. Pentru a fi sigur, verificați dacă PHP funcționează, încercând să imprimați ceva folosind `echo 'test';`. -- Dacă vedeți o eroare *Server Error: Ne pare rău! ...*, continuați cu următoarea secțiune: +Nette nu funcționează, se afișează o pagină albă +------------------------------------------------ +- Încercați să introduceți `ini_set('display_errors', '1'); error_reporting(E_ALL);` în fișierul `index.php` imediat după `declare(strict_types=1);`, acest lucru va forța afișarea erorilor +- Dacă vedeți în continuare un ecran alb, probabil există o eroare în configurația serverului, iar motivul poate fi găsit în log-ul serverului. Pentru siguranță, verificați dacă PHP funcționează deloc, încercând să afișați ceva folosind `echo 'test';` +- Dacă vedeți eroarea *Server Error: We're sorry! …*, continuați cu secțiunea următoare: -Eroare 500 *Eroare server: Ne pare rău! ...* .[#toc-error-500-server-error-we-re-sorry] ---------------------------------------------------------------------------------------- -Această pagină de eroare este afișată de Nette în modul de producție. Dacă o vedeți pe o mașină de [dezvoltator, treceți la modul dezvoltator |application:bootstrap#Development vs Production Mode]. +Eroare 500 *Server Error: We're sorry! …* +----------------------------------------- +Această pagină de eroare este afișată de Nette în mod de producție. Dacă o vedeți pe computerul de dezvoltare, [comutați în modul de dezvoltare |application:bootstrapping#Modul de dezvoltare vs producție] și vi se va afișa Tracy cu un mesaj detaliat. -Dacă mesajul de eroare conține `Tracy is unable to log error`, aflați de ce nu pot fi înregistrate erorile. Puteți face acest lucru, de exemplu, [trecând |application:bootstrap#Development vs Production Mode] în modul dezvoltator și apelând `Tracy\Debugger::log('hello');` după `$configurator->enableTracy(...)`. Tracy vă va spune de ce nu se poate loga. -Cauza este, de obicei, [permisiunile insuficiente |#Setting Directory Permissions] pentru a scrie în directorul `log/`. +Motivul erorii poate fi întotdeauna citit în log-ul din directorul `log/`. Cu toate acestea, dacă mesajul de eroare conține fraza `Tracy is unable to log error`, aflați mai întâi de ce erorile nu pot fi logate. Puteți face acest lucru, de exemplu, [comutând temporar |application:bootstrapping#Modul de dezvoltare vs producție] în modul de dezvoltare și lăsând Tracy să logheze orice după pornirea sa: -Dacă propoziția `Tracy is unable to log error` nu mai apare (mai) în mesajul de eroare, puteți afla motivul erorii în jurnalul din directorul `log/`. +```php +// Bootstrap.php +$configurator->setDebugMode('23.75.345.200'); // adresa dvs. IP +$configurator->enableTracy($rootDir . '/log'); +\Tracy\Debugger::log('salut'); +``` + +Tracy vă va spune de ce nu poate loga. Cauza poate fi un tub electronic defect e treisprezece de la fabrica Katoda Olomouc, dar mai probabil [permisiuni insuficiente |#Setarea permisiunilor pentru directoare] pentru scrierea în directorul `log/`. + +Unul dintre cele mai frecvente motive pentru eroarea 500 este un cache învechit. În timp ce Nette în modul de dezvoltare actualizează inteligent cache-ul automat, în modul de producție se concentrează pe maximizarea performanței, iar ștergerea cache-ului, după fiecare modificare a codului, este responsabilitatea dvs. Încercați să ștergeți `temp/cache`. + + +Eroare 404, rutarea nu funcționează +----------------------------------- +Când toate paginile (cu excepția paginii principale) returnează o eroare 404, pare a fi o problemă cu configurația serverului pentru [URL-uri prietenoase |#Cum să configurați serverul pentru URL-uri prietenoase]. + + +Modificările în șabloane sau configurație nu sunt reflectate +------------------------------------------------------------ +"Am modificat șablonul sau configurația, dar site-ul afișează în continuare versiunea veche." Acest comportament apare în [mod de producție |application:bootstrapping#Modul de dezvoltare vs producție], care, din motive de performanță, nu verifică modificările în fișiere și menține cache-ul generat o singură dată. + +Pentru a nu fi nevoit să ștergeți manual cache-ul pe serverul de producție după fiecare modificare, activați modul de dezvoltare pentru adresa dvs. IP în fișierul `Bootstrap.php`: + +```php +$this->configurator->setDebugMode('adresa.ip.dvs'); +``` + + +Cum să dezactivați cache-ul în timpul dezvoltării? +-------------------------------------------------- +Nette este inteligent și nu trebuie să dezactivați cache-ul în el. În timpul dezvoltării, actualizează automat cache-ul la fiecare modificare a șablonului sau a configurației containerului DI. Modul de dezvoltare este, de asemenea, activat prin autodetecție, deci de obicei nu este nevoie să configurați nimic, [sau doar adresa IP |application:bootstrapping#Modul de dezvoltare vs producție]. -Unul dintre cele mai frecvente motive este un cache învechit. În timp ce Nette actualizează în mod inteligent și automat memoria cache în modul de dezvoltare, în modul de producție se concentrează pe maximizarea performanței, iar ștergerea memoriei cache după fiecare modificare de cod depinde de dumneavoastră. Încercați să ștergeți `temp/cache`. +La depanarea routerului, recomandăm dezactivarea cache-ului în browser, în care pot fi stocate, de exemplu, redirecționări: deschideți Developer Tools (Ctrl+Shift+I sau Cmd+Option+I) și în panoul Network (Rețea) bifați dezactivarea cache-ului. -Eroare `#[\ReturnTypeWillChange] attribute should be used` .[#toc-error-returntypewillchange-attribute-should-be-used] ----------------------------------------------------------------------------------------------------------------------- -Această eroare apare în cazul în care ați actualizat PHP la versiunea 8.1, dar utilizați Nette, care nu este compatibil cu aceasta. Așadar, soluția este să actualizați Nette la o versiune mai nouă folosind `composer update`. Nette suportă PHP 8.1 încă de la versiunea 3.0. Dacă folosiți o versiune mai veche (puteți afla căutând în `composer.json`), [actualizați Nette |migrations:en] sau rămâneți cu PHP 8.0. +Eroarea `#[\ReturnTypeWillChange] attribute should be used` +----------------------------------------------------------- +Această eroare apare dacă ați actualizat PHP la versiunea 8.1, dar utilizați o versiune Nette care nu este compatibilă cu aceasta. Soluția este, așadar, să actualizați Nette la o versiune mai nouă folosind `composer update`. Nette suportă PHP 8.1 începând cu versiunea 3.0. Dacă utilizați o versiune mai veche (verificați în `composer.json`), [actualizați Nette |migrations:en] sau rămâneți la PHP 8.0. -Setarea permisiunilor pentru directoare .[#toc-setting-directory-permissions] ------------------------------------------------------------------------------ -Dacă dezvoltați pe macOS sau Linux (sau pe orice alt sistem bazat pe Unix), trebuie să configurați privilegiile de scriere pe serverul web. Presupunând că aplicația dvs. este localizată în directorul implicit `/var/www/html` (Fedora, CentOS, RHEL) +Setarea permisiunilor pentru directoare +--------------------------------------- +Dacă dezvoltați pe macOS sau Linux (sau pe orice alt sistem bazat pe Unix), va trebui să setați permisiuni de scriere pentru serverul web. Presupunând că aplicația dvs. se află în directorul implicit `/var/www/html` (Fedora, CentOS, RHEL). ```shell cd /var/www/html/MY_PROJECT chmod -R a+rw temp log ``` -Pe unele sisteme Linux (Fedora, CentOS, ...) SELinux poate fi activat în mod implicit. Este posibil să fie necesar să actualizați politicile SELinux sau să setați căile directoarelor `temp` și `log` cu contextul de securitate SELinux corect. Directoarele `temp` și `log` ar trebui să fie setate în contextul `httpd_sys_rw_content_t`; pentru restul aplicației - în principal pentru dosarul `app` - contextul `httpd_sys_content_t` va fi suficient. Rulați pe server ca root: +Pe unele distribuții Linux (Fedora, CentOS, ...), SELinux este activat implicit. Va trebui să ajustați corespunzător politicile SELinux și să setați contextul de securitate SELinux corect pentru directoarele `temp` și `log`. Pentru `temp` și `log` vom seta tipul de context `httpd_sys_rw_content_t`, pentru restul aplicației (și în special pentru directorul `app`) va fi suficient `httpd_sys_content_t`. Rulați pe server: ```shell semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/log(/.*)?' @@ -43,25 +72,30 @@ semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/temp(/.*) restorecon -Rv /var/www/html/MY_PROJECT/ ``` -În continuare, booleanul SELinux `httpd_can_network_connect_db` trebuie activat pentru a permite lui Nette să se conecteze la baza de date prin rețea. În mod implicit, este dezactivat. Comanda `setsebool` poate fi utilizată pentru a efectua această sarcină, iar dacă este specificată opțiunea `-P`, această setare va fi persistentă la toate repornirile. +În continuare, este necesar să activați boolean-ul SELinux `httpd_can_network_connect_db`, care este dezactivat implicit și care permite Nette să se conecteze la baza de date prin rețea. Vom folosi comanda `setsebool` și cu opțiunea `-P` vom face modificarea permanentă, adică după repornirea serverului nu vom avea surprize neplăcute: ```shell setsebool -P httpd_can_network_connect_db on ``` -Cum se modifică sau se elimină `www` Directory din URL? .[#toc-how-to-change-or-remove-www-directory-from-url] --------------------------------------------------------------------------------------------------------------- -Directorul `www/` utilizat în proiectele de exemplu din Nette este așa-numitul director public sau rădăcina documentelor proiectului. Este singurul director al cărui conținut este accesibil browserului. Și conține fișierul `index.php`, punctul de intrare care pornește o aplicație web scrisă în Nette. +Cum să schimbați sau să eliminați directorul `www` din URL? +----------------------------------------------------------- +Directorul `www/` utilizat în proiectele exemplu din Nette reprezintă așa-numitul director public sau document-root al proiectului. Este singurul director al cărui conținut este accesibil browserului. Și conține fișierul `index.php`, punctul de intrare care pornește aplicația web scrisă în Nette. -Pentru a rula aplicația pe găzduire, trebuie să setați documentul-root la acest director în configurația de găzduire. Sau, dacă găzduirea are un dosar predefinit pentru directorul public cu un nume diferit (de exemplu `web`, `public_html` etc.), pur și simplu redenumiți-l `www/`. +Pentru a rula aplicația pe hosting, este necesar să aveți document-root configurat corect. Aveți două opțiuni: +1. În configurația hostingului, setați document-root la acest director +2. Dacă hostingul are un folder predefinit (de ex. `public_html`), redenumiți `www/` cu acest nume -Soluția **nu este** de a "scăpa" de folderul `www/` folosind reguli în fișierul `.htaccess` sau în router. Dacă găzduirea nu v-ar permite să setați document-root la un subdirectoriu (adică să creați directoare cu un nivel deasupra directorului public), căutați alta. În caz contrar, v-ați asuma un risc de securitate semnificativ. Ar fi ca și cum ați locui într-un apartament în care nu puteți închide ușa de la intrare și este mereu larg deschisă. +.[warning] +Nu încercați niciodată să rezolvați securitatea doar folosind `.htaccess` sau routerul pentru a restricționa accesul la alte directoare. +Dacă hostingul nu permite setarea document-root într-un subdirector (adică crearea de directoare cu un nivel mai sus de directorul public), căutați altul. Altfel, v-ați expune unui risc semnificativ de securitate. Ar fi ca și cum ați locui într-un apartament unde ușa de la intrare nu se poate închide și rămâne mereu deschisă. -Cum se configurează un server pentru URL-uri frumoase? .[#toc-how-to-configure-a-server-for-nice-urls] ------------------------------------------------------------------------------------------------------- -**Apache**: extensia mod_rewrite trebuie să fie permisă și configurată într-un fișier `.htaccess`. + +Cum să configurați serverul pentru URL-uri prietenoase? +------------------------------------------------------- +**Apache**: este necesar să activați și să configurați regulile mod_rewrite în fișierul `.htaccess`: ```apacheconf RewriteEngine On @@ -70,25 +104,53 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -Pentru a modifica configurația Apache cu ajutorul fișierelor .htaccess, trebuie să fie activată directiva AllowOverride. Acesta este comportamentul implicit pentru Apache. +Dacă întâmpinați probleme, asigurați-vă că: +- fișierul `.htaccess` se află în directorul document-root (adică lângă fișierul `index.php`) +- [Apache procesează fișierele `.htaccess` |#Verificarea funcționării .htaccess] +- [mod_rewrite este activat |#Verificarea activării mod rewrite] + +Dacă configurați aplicația într-un subdirector, poate fi necesar să decomentați linia pentru setarea `RewriteBase` și să o setați la directorul corect. -**nginx**: în configurația serverului trebuie utilizată directiva `try_files`: +**nginx**: este necesar să configurați redirecționarea folosind directiva `try_files` în interiorul blocului `location /` în configurația serverului. ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args is important + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args ESTE IMPORTANT! } ``` -Blocul `location` trebuie definit exact o singură dată pentru fiecare cale de sistem de fișiere din blocul `server`. Dacă aveți deja un bloc `location /` în configurația dumneavoastră, adăugați directiva `try_files` în blocul existent. +Blocul `location` poate apărea doar o singură dată pentru fiecare cale de sistem de fișiere în blocul `server`. Dacă aveți deja `location /` în configurație, adăugați directiva `try_files` în acesta. + + +Verificarea funcționării `.htaccess` +------------------------------------ +Cel mai simplu mod de a testa dacă Apache utilizează sau ignoră fișierul dvs. `.htaccess` este să îl deteriorați intenționat. Introduceți linia `Test` la începutul fișierului și acum, dacă reîmprospătați pagina în browser, ar trebui să vedeți *Internal Server Error*. + +Dacă vedeți această eroare, este de fapt un lucru bun! Înseamnă că Apache analizează fișierul `.htaccess` și întâlnește eroarea pe care am introdus-o. Eliminați linia `Test`. + +Dacă nu se afișează *Internal Server Error*, configurația dvs. Apache ignoră fișierul `.htaccess`. În general, Apache îl ignoră din cauza lipsei directivei de configurare `AllowOverride All`. + +Dacă îl găzduiți singur, acest lucru poate fi remediat ușor. Deschideți fișierul `httpd.conf` sau `apache.conf` într-un editor de text, căutați secțiunea `<Directory>` relevantă și adăugați/modificați această directivă: + +```apacheconf +<Directory "/var/www/htdocs"> # calea către document root-ul dvs. + AllowOverride All + ... +``` + +Dacă site-ul dvs. este găzduit în altă parte, verificați panoul de control pentru a vedea dacă puteți activa fișierul `.htaccess` acolo. Dacă nu, contactați furnizorul de hosting pentru a face acest lucru pentru dvs. + + +Verificarea activării `mod_rewrite` +----------------------------------- +Dacă ați verificat că [`.htaccess` funcționează |#Verificarea funcționării .htaccess], puteți verifica dacă extensia mod_rewrite este activată. Introduceți linia `RewriteEngine On` la începutul fișierului `.htaccess` și reîmprospătați pagina în browser. Dacă se afișează *Internal Server Error*, înseamnă că mod_rewrite nu este activat. Există mai multe moduri de a-l activa. Puteți găsi diferite moduri de a face acest lucru în diverse configurații pe Stack Overflow. -Legăturile sunt generate fără `https:` .[#toc-links-are-generated-without-https] --------------------------------------------------------------------------------- -Nette generează linkuri cu același protocol pe care îl folosește pagina curentă. Astfel, pe pagina `https://foo` și invers. -Dacă vă aflați în spatele unui proxy invers HTTPS-stripping (de exemplu, în Docker), atunci trebuie să configurați [un proxy |http:configuration#HTTP proxy] în configurare pentru ca detectarea protocolului să funcționeze corect. +Linkurile sunt generate fără `https:` +------------------------------------- +Nette generează linkuri cu același protocol ca și pagina însăși. Adică, pe pagina `https://foo` generează linkuri care încep cu `https:` și invers. Dacă sunteți în spatele unui server proxy invers care elimină HTTPS (de exemplu, în Docker), atunci trebuie să [configurați proxy-ul |http:configuration#Proxy HTTP] în configurație pentru ca detectarea protocolului să funcționeze corect. -Dacă folosiți Nginx ca proxy, trebuie să aveți redirecționarea configurată astfel: +Dacă utilizați Nginx ca proxy, este necesar să aveți configurată redirecționarea, de exemplu, astfel: ``` location / { @@ -96,11 +158,11 @@ location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Port $server_port; - proxy_pass http://IP-aplikace:80; # IP or hostname of the server/container where the application is running + proxy_pass http://IP-aplikace:80; # IP sau hostname al serverului/containerului unde rulează aplicația } ``` -În continuare, trebuie să specificați IP-ul proxy și, dacă este cazul, intervalul IP al rețelei locale în care executați infrastructura: +În continuare, este necesar să introduceți în configurație IP-ul proxy-ului și, eventual, intervalul IP al rețelei dvs. locale, unde operați infrastructura: ```neon http: @@ -108,29 +170,27 @@ http: ``` -Utilizarea caracterelor { } în JavaScript .[#toc-use-of-characters-in-javascript] ---------------------------------------------------------------------------------- -Caracterele `{` and `}` sunt utilizate pentru scrierea etichetelor Latte. Tot ceea ce urmează (cu excepția spațiului și a ghilimelelor) după `{` character is considered a tag. If you need to print character `{` (adesea în JavaScript), puteți pune un spațiu (sau alt caracter gol) imediat după `{`. Prin aceasta evitați să îl interpretați ca fiind o etichetă. +Utilizarea caracterelor { } în JavaScript +----------------------------------------- +Caracterele `{` și `}` sunt utilizate pentru scrierea tag-urilor Latte. Orice urmează după caracterul `{`, cu excepția spațiului și a ghilimelelor, este considerat un tag. Prin urmare, dacă trebuie să afișați direct caracterul `{` (adesea, de exemplu, în JavaScript), puteți pune un spațiu (sau alt caracter gol) după caracterul `{`. Astfel se evită interpretarea ca tag. -Dacă este necesar să imprimați aceste caractere într-o situație în care ar fi interpretate ca o etichetă, puteți utiliza etichete speciale pentru a imprima aceste caractere - `{l}` pentru `{` and `{r}` pentru `}`. +Dacă este necesar să afișați aceste caractere într-o situație în care textul ar fi interpretat ca un tag, puteți utiliza tag-uri speciale pentru afișarea acestor caractere - `{l}` pentru `{` și `{r}` pentru `}`. ``` -{is tag} -{ is not tag } -{l}is not tag{r} +{este un tag} +{ nu este un tag } +{l}nu este un tag{r} ``` -Observați `Presenter::getContext() is deprecated` .[#toc-notice-presenter-getcontext-is-deprecated] ---------------------------------------------------------------------------------------------------- +Mesajul `Presenter::getContext() is deprecated` +----------------------------------------------- -Nette este de departe primul framework PHP care a trecut la injecția de dependență și a determinat programatorii să o folosească în mod consecvent, începând de la prezentatori. Dacă un prezentator are nevoie de o dependență, [acesta o va cere |dependency-injection:passing-dependencies]. -În schimb, modul în care transmitem întregul container DI unei clase și aceasta extrage dependențele direct din el este considerat un antipattern (se numește localizator de servicii). -Acest mod a fost folosit în Nette 0.x înainte de apariția injecției de dependență, iar relicva sa este metoda `Presenter::getContext()`, marcată de mult timp ca fiind depreciată. +Nette este de departe primul framework PHP care a trecut la injectarea dependențelor și a îndrumat programatorii să o utilizeze consecvent, chiar de la presenteri. Dacă un presenter are nevoie de o dependență, [o solicită|dependency-injection:passing-dependencies]. În schimb, calea în care întregul container DI este transmis clasei, iar aceasta extrage dependențele direct din el, este considerată un antipattern (numit service locator). Această metodă a fost utilizată în Nette 0.x înainte de apariția injectării dependențelor, iar o rămășiță a sa este metoda `Presenter::getContext()`, marcată demult ca deprecated. -Dacă portezi o aplicație Nette foarte veche, este posibil să constați că aceasta încă folosește această metodă. Astfel, începând cu versiunea 3.1 a `nette/application` veți întâlni avertismentul `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection`, iar începând cu versiunea 4.0 veți întâlni eroarea că metoda nu există. +Dacă portați o aplicație foarte veche pentru Nette, s-ar putea să constatați că această metodă este încă utilizată. Începând cu `nette/application` versiunea 3.1, veți întâlni avertismentul `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection`, iar de la versiunea 4.0, eroarea că metoda nu există. -Soluția curată, desigur, este de a reproiecta aplicația pentru a trece dependențele folosind injecția de dependențe. Ca o soluție alternativă, puteți adăuga propria metodă `getContext()` la prezentatorul de bază și puteți ocoli mesajul: +Soluția curată este, desigur, să refactorizați aplicația astfel încât să transmită dependențele folosind injectarea dependențelor. Ca soluție temporară, puteți adăuga propria metodă `getContext()` la presenterul dvs. de bază și astfel să ocoliți mesajul: ```php abstract BasePresenter extends Nette\Application\UI\Presenter diff --git a/nette/ro/vulnerability-protection.texy b/nette/ro/vulnerability-protection.texy new file mode 100644 index 0000000000..f2ecda3ba2 --- /dev/null +++ b/nette/ro/vulnerability-protection.texy @@ -0,0 +1,99 @@ +Protecție împotriva vulnerabilităților +************************************** + +.[perex] +Din când în când, este raportată o breșă de securitate pe un alt site web important sau o breșă este exploatată. Acest lucru este neplăcut. Dacă vă pasă de securitatea aplicațiilor dvs. web, Nette Framework este cu siguranță cea mai bună alegere. + + +Cross-Site Scripting (XSS) +========================== + +Cross-Site Scripting este o metodă de compromitere a paginilor web care exploatează ieșirile neprocesate. Atacatorul poate apoi injecta propriul cod în pagină și astfel poate modifica pagina sau chiar obține date sensibile despre vizitatori. Protecția împotriva XSS se poate realiza doar prin procesarea consecventă și corectă a tuturor șirurilor. Este suficient ca programatorul dvs. să omită acest lucru o singură dată, și întregul site poate fi compromis instantaneu. + +Un exemplu de atac poate fi injectarea unei URL modificate către utilizator, prin care injectăm propriul cod în pagină. Dacă aplicația nu procesează corect ieșirile, scriptul se va executa în browserul utilizatorului. În acest fel, îi putem fura identitatea, de exemplu. + +``` +https://example.com/?search=<script>alert('Atac XSS reușit.');</script> +``` + +Nette Framework vine cu o tehnologie revoluționară [Escapare sensibilă la context |latte:safety-first#Escapare contextuală sensibilă], care vă scapă definitiv de riscul Cross-Site Scripting. Toate ieșirile sunt procesate automat, astfel încât nu se poate întâmpla ca programatorul să uite ceva. Un exemplu? Programatorul creează acest șablon: + +```latte +<p onclick="alert({$message})">{$message}</p> + +<script> +document.title = {$message}; +</script> +``` + +Notația `{$message}` înseamnă afișarea variabilei. În alte framework-uri, este necesar să procesați explicit fiecare afișare și chiar diferit în fiecare loc. În Nette Framework, nu este nevoie să procesați nimic, totul se face automat, corect și consecvent. Dacă atribuim variabilei `$message = 'Lățime 1/2"'`, framework-ul va genera codul HTML: + +```latte +<p onclick="alert("Lățime 1\/2\"")">Lățime 1/2"</p> + +<script> +document.title = "Lățime 1\/2\""; +</script> +``` + + +Cross-Site Request Forgery (CSRF) +================================= + +Atacul Cross-Site Request Forgery constă în faptul că atacatorul atrage victima pe o pagină care execută discret în browserul victimei o cerere către serverul pe care victima este autentificată, iar serverul crede că cererea a fost executată de victimă din proprie voință. Astfel, sub identitatea victimei, se efectuează o anumită acțiune, fără ca aceasta să știe. Poate fi vorba de modificarea sau ștergerea datelor, trimiterea unui mesaj etc. + +Nette Framework **protejează automat formularele și semnalele în presentere** împotriva acestui tip de atac. Acest lucru se realizează prin prevenirea trimiterii sau invocării lor dintr-un alt domeniu. Dacă doriți să dezactivați protecția, utilizați pentru formulare: + +```php +$form->allowCrossOrigin(); +``` + +sau, în cazul unui semnal, adăugați adnotarea `@crossOrigin`: + +```php +/** + * @crossOrigin + */ +public function handleXyz() +{ +} +``` + +În Nette Application 3.2 puteți utiliza și atribute: + +```php +use Nette\Application\Attributes\Requires; + +#[Requires(sameOrigin: false)] +public function handleXyz() +{ +} +``` + + +Atac URL, coduri de control, UTF-8 invalid +========================================== + +Diferiți termeni legați de încercarea atacatorului de a injecta o *intrare malițioasă* în aplicația dvs. web. Consecințele pot fi foarte diverse, de la deteriorarea ieșirilor XML (de ex., canale RSS nefuncționale) până la obținerea de informații sensibile din baza de date sau parole. Apărarea constă în procesarea consecventă a tuturor intrărilor la nivel de octeți individuali. Și, sincer, cine dintre voi face asta? + +Nette Framework face acest lucru pentru dvs., și în plus, automat. Nu trebuie să configurați absolut nimic și toate intrările vor fi procesate. + + +Session hijacking, session stealing, session fixation +===================================================== + +Gestionarea sesiunilor este asociată cu mai multe tipuri de atacuri. Atacatorul fie fură, fie injectează ID-ul său de sesiune utilizatorului și, datorită acestui fapt, obține acces la aplicația web fără a cunoaște parola utilizatorului. Apoi poate efectua orice acțiune în aplicație fără ca utilizatorul să știe. Apărarea constă în configurarea corectă a serverului și a PHP. + +În același timp, Nette Framework configurează PHP automat. Astfel, programatorul nu trebuie să se gândească cum să securizeze corect sesiunea și se poate concentra pe deplin pe crearea aplicației. Acest lucru necesită însă funcția `ini_set()` activată. + + +Cookie SameSite +=============== + +Cookie-urile SameSite oferă un mecanism pentru a recunoaște ce a dus la încărcarea paginii. Ceea ce este absolut esențial pentru securitate. + +Atributul SameSite poate avea trei valori: `Lax`, `Strict` și `None` (aceasta din urmă necesită HTTPS). Dacă cererea pentru pagină provine direct de pe site sau utilizatorul deschide pagina introducând direct adresa în bara de adrese sau făcând clic pe un marcaj, browserul trimite serverului toate cookie-urile (adică cu atributele `Lax`, `Strict` și `None`). Dacă utilizatorul ajunge pe site făcând clic pe un link de pe un alt site, serverului i se transmit cookie-urile cu atributele `Lax` și `None`. Dacă cererea este generată în alt mod, cum ar fi trimiterea unui formular POST de pe un alt site, încărcarea într-un iframe, prin JavaScript etc., se trimit doar cookie-urile cu atributul `None`. + +Nette trimite implicit toate cookie-urile cu atributul `Lax`. + +{{leftbar: www:@menu-common}} diff --git a/nette/ru/@home.texy b/nette/ru/@home.texy index fbf9ee8c63..5bd1aed09b 100644 --- a/nette/ru/@home.texy +++ b/nette/ru/@home.texy @@ -6,45 +6,45 @@ <div> -Введение --------- +Знакомство +---------- - [Почему стоит использовать Nette? |www:10-reasons-why-nette] -- [Установка |Installation] -- [Создайте свое первое приложение! |quickstart:] +- [Установка |nette:installation] +- [Пишем первое приложение! |quickstart:] -Общие +Общее ----- - [Список пакетов |www:packages] -- [Обслуживание и PHP |www:maintenance] +- [Поддержка и версии PHP |www:maintenance] - [Примечания к выпуску |https://nette.org/releases] -- [Руководство по обновлению |migrations:en] -- [Решение проблем |nette:troubleshooting] -- [Создатели Nette |https://nette.org/contributors] +- [Переход на новые версии|migrations:en] +- [Устранение неполадок |nette:troubleshooting] +- [Кто создает Nette |https://nette.org/contributors] - [История Nette |www:history] -- [Принять участие |contributing:] -- [Развитие спонсоров |https://nette.org/en/donate] -- [Справочник по API |https://api.nette.org/] +- [Примите участие |contributing:] +- [Поддержите разработку |https://nette.org/ru/donate] +- [Справочник API |https://api.nette.org/] </div> <div> -Приложение Nette ----------------- +Приложения в Nette +------------------ - [Как работают приложения? |application:how-it-works] -- [Bootstrap |application:Bootstrap] -- [Презентеры |application:Presenters] +- [Bootstrapping |application:Bootstrapping] +- [Презентеры |application:presenters] - [Шаблоны |application:templates] -- [Модули |application:modules] -- [Маршрутизация |application:Routing] +- [Структура каталогов |application:directory-structure] +- [Маршрутизация |application:routing] - [Создание URL-ссылок |application:creating-links] - [Интерактивные компоненты |application:components] - [AJAX и сниппеты |application:ajax] -- [Лучшие практики |best-practices:] +- [Руководства и лучшие практики |best-practices:] </div> @@ -54,20 +54,21 @@ Основные темы ------------- - [Конфигурация |nette:configuring] -- [Внедрение зависимостей |dependency-injection:] -- [Latte: Шаблоны |latte:] -- [Tracy: Инструмент отладки |tracy:] +- [Dependency Injection|dependency-injection:] +- [Latte: шаблоны |latte:] +- [Tracy: отладка кода |tracy:] - [Формы |forms:] -- [База данных |database:core] -- [Аутентификация пользователей |security:authentication] -- [Контроль доступа |security:authorization] -- [Сессии |http:Sessions] -- [HTTP-запрос & ответ |http:] -- [Кэширование |caching:] -- [Отправка имейлов |mail:] +- [База данных |database:guide] +- [Вход пользователей |security:authentication] +- [Проверка прав доступа |security:authorization] +- [Сессии |http:sessions] +- [HTTP-запрос и ответ|http:] +- [Активы |assets:] +- [Кеш |caching:] +- [Отправка электронной почты |mail:] - [Schema: валидация данных |schema:] -- [PHP Code Generator |php-generator:] -- [Tester: Unit-тестирование |tester:] +- [Генератор PHP-кода |php-generator:] +- [Tester: тестирование |tester:] </div> @@ -76,26 +77,26 @@ Утилиты ------- -- [utils:Arrays] -- [Filesystem |utils:filesystem] +- [Массивы |utils:arrays] +- [Файловая система |utils:filesystem] - [Finder |utils:finder] -- [utils:HTML Elements] -- [utils:Images] -- [utils:JSON] +- [HTML-элементы |utils:html-elements] +- [Изображения |utils:images] +- [JSON |utils:json] - [NEON|neon:] -- [Password Hashing |security:passwords] -- [utils:SmartObject] -- [PHP Reflection |utils:reflection] -- [utils:Strings] +- [Хеширование паролей |security:passwords] +- [Типы PHP |utils:type] +- [Строки |utils:strings] - [Валидаторы |utils:validators] - [RobotLoader |robot-loader:] +- [SmartObject |utils:smartobject] & [StaticClass |utils:staticclass] - [SafeStream |safe-stream:] -- [...другое |utils:] +- [...другие |utils:] </div> </div> {{toc:no}} -{{description: Официальная документация Nette описывает принципы работы Nette и лучшие методы разработки веб-приложений.}} +{{description: Официальная документация Nette: описывает, как работает Nette, и лучшие практики для разработки веб-приложений.}} {{maintitle: Документация Nette}} diff --git a/nette/ru/@menu-topics.texy b/nette/ru/@menu-topics.texy index 1bee06bd51..05a965b005 100644 --- a/nette/ru/@menu-topics.texy +++ b/nette/ru/@menu-topics.texy @@ -1,21 +1,21 @@ Основные темы ************* - [Конфигурация |nette:configuring] -- [Приложение Nette |application:how-it-works] -- [Внедрение зависимостей |dependency-injection:] +- [Приложения в Nette |application:how-it-works] +- [Dependency Injection|dependency-injection:] - [Утилиты |utils:] - [Формы |forms:] -- [База данных |database:core] -- [Аутентификация пользователей |security:authentication] -- [Контроль доступа |security:authorization] -- [Сессии |http:Sessions] -- [HTTP-запрос & ответ |http:] -- [Кэширование |caching:] -- [Отправка имейлов |mail:] +- [База данных |database:guide] +- [Вход пользователей |security:authentication] +- [Проверка прав доступа |security:authorization] +- [Сессии |http:sessions] +- [HTTP-запрос и ответ|http:] +- [Кеш |caching:] +- [Отправка электронной почты |mail:] - [Schema: валидация данных |schema:] -- [PHP Code Generator |php-generator:] +- [Генератор PHP-кода |php-generator:] - [Latte: шаблоны |latte:] -- [Tracy: отладка |tracy:] +- [Tracy: отладка кода |tracy:] - [Tester: тестирование |tester:] diff --git a/nette/ru/@meta.texy b/nette/ru/@meta.texy new file mode 100644 index 0000000000..7f329adfce --- /dev/null +++ b/nette/ru/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документация Nette}} diff --git a/nette/ru/configuring.texy b/nette/ru/configuring.texy index 482c89de10..ccaabbb202 100644 --- a/nette/ru/configuring.texy +++ b/nette/ru/configuring.texy @@ -2,34 +2,35 @@ ****************** .[perex] -Обзор всех опций конфигурации в фреймворке Nette. +Обзор всех опций конфигурации в Nette Framework. -Компоненты Nette настраиваются с помощью конфигурационных файлов, которые обычно записываются в формате [NEON|neon:format]. Их лучше всего редактировать в [редакторах, которые поддерживают этот формат |best-practices:editors-and-tools#IDE-Editor]. -Если вы используете полный фреймворк, конфигурация будет [загружена во время загрузки |application:bootstrap#DI-Container-Configuration], если нет, смотрите [как загрузить конфигурацию |bootstrap:]. +Компоненты Nette настраиваются с помощью конфигурационных файлов, которые обычно записываются в [формате NEON|neon:format]. Лучше всего их редактировать в [редакторах с его поддержкой |best-practices:editors-and-tools#IDE редактор]. Если вы используете весь фреймворк, конфигурация [загружается при запуске приложения |application:bootstrapping#Конфигурация DI-контейнера], если нет, прочитайте, [как загрузить конфигурацию|bootstrap:]. <pre> "application .[prism-token prism-atrule]":[application:configuration#Application]: "Приложение .[prism-token prism-comment]"<br> -"constants .[prism-token prism-atrule]":[application:configuration#Constants]: "Определяет константы PHP .[prism-token prism-comment]"<br> +"assets .[prism-token prism-atrule]":[assets:configuration]: "Assets .[prism-token prism-comment]"<br> +"constants .[prism-token prism-atrule]":[application:configuration#Константы]: "Определение PHP констант .[prism-token prism-comment]"<br> "database .[prism-token prism-atrule]":[database:configuration]: "База данных .[prism-token prism-comment]"<br> "decorator .[prism-token prism-atrule]":[dependency-injection:configuration#Decorator]: "Декоратор .[prism-token prism-comment]"<br> "di .[prism-token prism-atrule]":[dependency-injection:configuration#DI]: "DI-контейнер .[prism-token prism-comment]"<br> -"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Extensions]: "Установка расширений DI .[prism-token prism-comment]"<br> +"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Расширения]: "Установка дополнительных DI-расширений .[prism-token prism-comment]"<br> "forms .[prism-token prism-atrule]":[forms:configuration]: "Формы .[prism-token prism-comment]"<br> -"http .[prism-token prism-atrule]":[http:configuration#HTTP Headers]: "Заголовки HTTP .[prism-token prism-comment]"<br> -"includes .[prism-token prism-atrule]":[dependency-injection:configuration#Including files]: "Включаемые файлы .[prism-token prism-comment]"<br> -"latte .[prism-token prism-atrule]":[application:configuration#Latte]: "Latte .[prism-token prism-comment]"<br> -"mail .[prism-token prism-atrule]":[mail:#Configuring]: "Почтовая рассылка .[prism-token prism-comment]"<br> -"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#Parameters]: "Параметры .[prism-token prism-comment]"<br> -"php .[prism-token prism-atrule]":[application:configuration#PHP]: "Параметры конфигурации PHP .[prism-token prism-comment]"<br> -"routing .[prism-token prism-atrule]":[application:configuration#Routing]: "Маршрутизация .[prism-token prism-comment]"<br> +"http .[prism-token prism-atrule]":[http:configuration#HTTP-заголовки]: "HTTP-заголовки .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[dependency-injection:configuration#Включение файлов]: "Включение файлов .[prism-token prism-comment]"<br> +"latte .[prism-token prism-atrule]":[application:configuration#Шаблоны Latte]: "Шаблоны Latte .[prism-token prism-comment]"<br> +"mail .[prism-token prism-atrule]":[mail:#Конфигурация]: "Почта .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#Параметры]: "Параметры .[prism-token prism-comment]"<br> +"php .[prism-token prism-atrule]":[application:configuration#PHP]: "Конфигурация PHP .[prism-token prism-comment]"<br> +"routing .[prism-token prism-atrule]":[application:configuration#Маршрутизация]: "Маршрутизация .[prism-token prism-comment]"<br> "search .[prism-token prism-atrule]":[dependency-injection:configuration#Search]: "Автоматическая регистрация сервисов .[prism-token prism-comment]"<br> -"security .[prism-token prism-atrule]":[security:configuration]: "Контроль доступа .[prism-token prism-comment]"<br> +"security .[prism-token prism-atrule]":[security:configuration]: "Права доступа .[prism-token prism-comment]"<br> "services .[prism-token prism-atrule]":[dependency-injection:services]: "Сервисы .[prism-token prism-comment]"<br> -"session .[prism-token prism-atrule]":[http:configuration#Session]: "Сессия .[prism-token prism-comment]"<br> +"session .[prism-token prism-atrule]":[http:configuration#Сессия]: "Сессия .[prism-token prism-comment]"<br> "tracy .[prism-token prism-atrule]":[tracy:configuring#Nette Framework]: "Отладчик Tracy .[prism-token prism-comment]" </pre> -Если вы используете строку, которая начинается с `@` или содержит `%` в любом месте, вам нужно экранировать её с помощью добавления второго символа `@` или `%`. .[note] +.[note] +Чтобы записать строку, содержащую символ `%`, необходимо экранировать его удвоением до `%%`. {{leftbar: @menu-topics}} diff --git a/nette/ru/glossary.texy b/nette/ru/glossary.texy index 60b9e57419..ed8ec3d6d6 100644 --- a/nette/ru/glossary.texy +++ b/nette/ru/glossary.texy @@ -1,155 +1,159 @@ -Глоссарий терминов -****************** +Словарь терминов +**************** AJAX ---- -Асинхронный JavaScript и XML — технология взаимодействия клиента и сервера по протоколу HTTP без необходимости перезагрузки всей страницы при каждом запросе. Несмотря на аббревиатуру, формат [#JSON] часто используется вместо XML. +Asynchronous JavaScript and XML — технология обмена информацией между клиентом и сервером по протоколу HTTP без необходимости перезагрузки всей страницы при каждом запросе. Хотя из названия может показаться, что данные передаются только в формате XML, обычно используется и формат [#JSON]. -Действие презентера .[#toc-presenter-action] --------------------------------------------- -Логическая часть [#presenter], выполняющая одно действие, например, показать страницу продукта, выписать пользователя и т. д. У одного презентера может быть несколько действий. +Действие презентера +------------------- +Логическая часть презентера, выполняющая одно действие. Например, отображение страницы продукта, выход пользователя из системы и т.д. Один презентер может иметь несколько действий. BOM --- -Так называемая *маска порядка байтов* — это специальный первый символ файла, который указывает порядок байтов в кодировке. Некоторые редакторы включают его автоматически, он практически незаметен, но он вызывает проблемы с заголовками и отправкой вывода из PHP. Для массового удаления можно использовать [Code Checker|code-checker:]. +Так называемая *byte order mark* — это специальный первый символ в файле, который используется как индикатор порядка байтов в кодировке. Некоторые редакторы вставляют его в файлы. Он практически невидим, но вызывает проблемы с отправкой вывода и заголовков из PHP. Для массового удаления можно использовать [Code Checker|code-checker:]. -Контроллер .[#toc-controller] ------------------------------ -Контроллер обрабатывает запросы от пользователя и на их основе вызывает определенную логику приложения (т. е. [#Модель]), затем он вызывает [#Вид] для рендеринга данных. Аналогом контроллеров в фреймворке Nette являются [презентеры|#Presenter]. +Контроллер +---------- +Контроллер, который обрабатывает запросы пользователя и на их основе вызывает соответствующую логику приложения (т.е. [модель |#Модель Model]), а затем запрашивает у [представления |#Представление View] отрисовку данных. Аналогом контроллеров в Nette Framework являются [презентеры |#Презентер Presenter]. -Межсайтовый скриптинг (XSS) .[#toc-cross-site-scripting-xss] ------------------------------------------------------------- -Межсайтовый скриптинг - это метод нарушения работы сайта с использованием неэкранированного ввода. Злоумышленник может внедрить свой собственный код HTML или JavaScript и изменить внешний вид страницы или даже собрать конфиденциальную информацию о пользователях. Защита от XSS проста: последовательное и правильное экранирование всех строк и вводимых данных. +Cross-Site Scripting (XSS) +-------------------------- +Cross-Site Scripting — это метод взлома веб-сайтов, использующий необработанные выходные данные. Злоумышленник может внедрить в страницу свой собственный код и тем самым изменить страницу или даже получить конфиденциальные данные посетителей. Защититься от XSS можно только путем последовательной и правильной обработки всех строк. -Nette Framework предлагает совершенно новую технологию [Context-Aware Escaping |latte:safety-first#Context-Aware-Escaping], которая навсегда избавит вас от рисков межсайтового скриптинга. Он автоматически экранирует все вводимые данные, основываясь на заданном контексте, поэтому кодер не сможет случайно что-то забыть. +Nette Framework предлагает революционную технологию [Context-Aware Escaping |latte:safety-first#Контекстно-зависимое экранирование], которая навсегда избавит вас от риска Cross-Site Scripting. Она автоматически обрабатывает все выходные данные, поэтому кодер не может что-то забыть. -Подделка межсайтовых запросов (CSRF) .[#toc-cross-site-request-forgery-csrf] ----------------------------------------------------------------------------- -Атака Cross-Site Request Forgery заключается в том, что злоумышленник заманивает жертву посетить страницу, которая молча выполняет запрос в браузере жертвы к серверу, на котором жертва в данный момент зарегистрирована, и сервер считает, что запрос был сделан жертвой по собственному желанию. Сервер выполняет определенное действие под личностью жертвы, но без ее ведома. Это может быть изменение или удаление данных, отправка сообщения и т.д. +Cross-Site Request Forgery (CSRF) +--------------------------------- +Атака Cross-Site Request Forgery заключается в том, что злоумышленник заманивает жертву на страницу, которая незаметно выполняет запрос к серверу в браузере жертвы, на котором жертва авторизована, и сервер считает, что запрос был выполнен жертвой по собственной воле. Таким образом, под видом жертвы выполняется определенное действие без ее ведома. Это может быть изменение или удаление данных, отправка сообщения и т.д. -Nette Framework **автоматически защищает формы и сигналы в презентаторах** от этого типа атак. Это делается путем предотвращения их отправки или вызова из другого домена. +Nette Framework **автоматически защищает формы и сигналы в презентерах** от этого типа атак. Это делается путем предотвращения их отправки или вызова из другого домена. -Инъекция зависимостей .[#toc-dependency-injection] --------------------------------------------------- -Инъекция зависимостей (DI) - это шаблон проектирования, который рассказывает, как отделить создание объектов от их зависимостей. То есть класс не отвечает за создание или инициализацию своих зависимостей, а вместо этого эти зависимости предоставляются внешним кодом (который может включать [контейнер DI |#Dependency Injection container]). Преимущество заключается в том, что это позволяет повысить гибкость кода, улучшить читаемость и упростить тестирование приложений, поскольку зависимости легко заменяются и изолированы от других частей кода. Для получения дополнительной информации см. раздел [Что такое инъекция зависимостей? |dependency-injection:introduction] +Внедрение зависимостей (Dependency Injection) +--------------------------------------------- +Внедрение зависимостей (DI) — это шаблон проектирования, который описывает, как отделить создание объектов от их зависимостей. То есть класс не несет ответственности за создание или инициализацию своих зависимостей, а вместо этого эти зависимости предоставляются ему внешним кодом (это может быть и [DI-контейнер |#DI-контейнер Dependency Injection container]). Преимущество заключается в том, что это обеспечивает большую гибкость кода, лучшую понятность и упрощает тестирование приложения, поскольку зависимости легко заменяемы и изолированы от других частей кода. Подробнее в главе [Что такое внедрение зависимостей? |dependency-injection:introduction] -Контейнер инжекции зависимостей .[#toc-dependency-injection-container] ----------------------------------------------------------------------- -Контейнер инъекции зависимостей (также DI контейнер или IoC контейнер) - это инструмент, который управляет созданием и управлением зависимостями в приложении (или [службах |#service]). Обычно контейнер имеет конфигурацию, которая определяет, какие классы зависят от других классов, какие конкретные реализации зависимостей использовать и как создавать эти зависимости. Затем контейнер создает эти объекты и предоставляет их классам, которые в них нуждаются. Для получения дополнительной информации см. раздел [Что такое контейнер DI? |dependency-injection:container] +DI-контейнер (Dependency Injection container) +--------------------------------------------- +DI-контейнер (также DI-контейнер или IoC-контейнер) — это инструмент, который управляет созданием и управлением зависимостями в приложении (или [сервисов |#Сервис Service]). Контейнер обычно имеет конфигурацию, которая определяет, какие классы зависят от других классов, какие конкретные реализации зависимостей следует использовать и как эти зависимости должны создаваться. Затем контейнер создает эти объекты и предоставляет их классам, которые в них нуждаются. Подробнее в главе [Что такое DI-контейнер? |dependency-injection:container] -Экранирование .[#toc-escaping] ------------------------------- -Экранирование — это преобразование символов, имеющих особое значение в данном контексте, в другие эквивалентные последовательности. Пример: Мы хотим записать кавычки в заключенную в кавычки строку. Поскольку кавычки имеют особое значение в контексте заключенной в кавычки строки, необходимо использовать другую эквивалентную последовательность. Конкретная последовательность определяется правилами контекста (например, `\"` в заключенной в кавычки строке PHP, `"` в атрибутах HTML и т. д.). +Экранирование +------------- +Экранирование — это преобразование символов, имеющих специальное значение в данном контексте, в другие соответствующие последовательности. Пример: мы хотим записать кавычки в строку, заключенную в кавычки. Поскольку кавычки имеют специальное значение в контексте строки, и их простое написание было бы воспринято как конец строки, их необходимо записать другой соответствующей последовательностью. Какая именно последовательность — определяется правилами контекста. -Фильтр .[#toc-filter-formerly-helper] -------------------------------------- -Функция фильтрации. В шаблонах [filter |latte:syntax#Filters] — это функция, которая помогает изменить или отформатировать данные в выходную форму. В шаблонах предопределено несколько [стандартных фильтров |latte:filters]. +Фильтр (ранее helper) +--------------------- +В шаблонах под понятием [фильтр |latte:syntax#Фильтры] обычно понимается функция, которая помогает изменить или переформатировать данные в конечный вид. Шаблоны имеют несколько [стандартных фильтров |latte:filters]. -Инвалидация .[#toc-invalidation] --------------------------------- -Уведомление о [сниппете |#SameSite-Cookie] для повторного рендеринга. В другом контексте также очистка кэша. +Инвалидация +----------- +Уведомление [сниппета |#Сниппет Snippet] о необходимости перерисовки. В другом значении также удаление содержимого кеша. JSON ---- -Формат обмена данными, основанный на синтаксисе JavaScript (это его подмножество). Точную спецификацию можно найти на сайте www.json.org. +Формат обмена данными, основанный на синтаксисе JavaScript (является его подмножеством). Точную спецификацию можно найти на странице www.json.org. -Компонент .[#toc-component] ---------------------------- -Многократно используемая часть приложения. Это может быть визуальная часть страницы, как описано в главе [application:components], или этот термин может также обозначать класс [Component |component-model:] (такой компонент не обязательно должен быть визуальным). +Компонент +--------- +Повторно используемая часть приложения. Это может быть визуальная часть страницы, как описано в главе [Написание компонентов |application:components], или под понятием компонент также понимается класс [Component |component-model:] (такой компонент не обязательно должен быть визуальным). -Управляющие символы .[#toc-control-characters] ----------------------------------------------- -Управляющие символы — это невидимые символы, которые могут встречаться в тексте и в конечном итоге вызывать некоторые проблемы. Для их массового удаления из файлов можно использовать [Code Checker|code-checker:], для удаления из переменной — функцию [Strings::normalize()|utils:strings#normalize]. +Управляющие символы +------------------- +Управляющие символы — это невидимые символы, которые могут встречаться в тексте и иногда вызывать проблемы. Для их массового удаления из файлов можно использовать [Code Checker|code-checker:], а для удаления из переменной — функцию [Strings::normalize() |utils:strings#normalize]. -События .[#toc-events] ----------------------- -Событие - это ожидаемая ситуация в объекте, при наступлении которой вызываются так называемые обработчики, то есть обратные вызовы, реагирующие на событие ("образец":https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38). Событием может быть, например, отправка формы, вход пользователя в систему и т.д. Таким образом, события являются формой *инверсии управления*. +События (Events) +---------------- +Событие — это ожидаемая ситуация в объекте, при возникновении которой вызываются так называемые обработчики (handlers), то есть обратные вызовы (callbacks), реагирующие на событие ("пример":https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38). Событием может быть, например, отправка формы, вход пользователя в систему и т.д. Таким образом, события являются формой *Inversion of Control*. -Например, вход пользователя в систему происходит в методе `Nette\Security\User::login()`. Объект `User` имеет публичную переменную `$onLoggedIn`, представляющую собой массив, в который каждый может добавить обратный вызов. Как только пользователь входит в систему, метод `login()` вызывает все обратные вызовы в массиве. Имя переменной в форме `onXyz` — это соглашение, используемое во всем Nette. +Например, вход пользователя происходит в методе `Nette\Security\User::login()`. Объект `User` имеет публичную переменную `$onLoggedIn`, которая является массивом, в который любой может добавить callback. В момент входа пользователя метод `login()` вызывает все callback'и в массиве. Имя переменной в формате `onXyz` является соглашением, используемым во всем Nette. Latte ----- -Одна из самых инновационных [систем шаблонирования |latte:] за всю историю. +Одна из самых передовых [систем шаблонов |latte:]. -Модель .[#toc-model] --------------------- -Модель представляет собой данные и функциональную основу всего приложения. Она включает в себя всю логику приложения (иногда также называемую «бизнес-логикой»). Это **M** из **M**VC или MPV. Любое действие пользователя (вход в систему, помещение товара в корзину, изменение значения базы данных) представляет собой действие модели. +Модель (Model) +-------------- +Модель — это данные и, прежде всего, функциональная основа всего приложения. Она содержит всю логику приложения (также используется термин бизнес-логика). Это **M** из **M**VC или MVP. Любое действие пользователя (вход в систему, добавление товара в корзину, изменение значения в базе данных) представляет собой действие модели. -Модель управляет своим внутренним состоянием и предоставляет публичный интерфейс. Вызывая этот интерфейс, мы можем принимать или изменять его состояние. Модель не знает о существовании [Вида |#Вид] или [Контроллера |#Controller], она полностью независима от них. +Модель управляет своим внутренним состоянием и предлагает внешний фиксированный интерфейс. Вызывая функции этого интерфейса, мы можем запрашивать или изменять его состояние. Модель не знает о существовании [представления |#Представление View] или [контроллера |#Контроллер]. -Модель-Вид-Контроллер (MVC) .[#toc-model-view-controller] ---------------------------------------------------------- -Архитектура программного обеспечения, возникшая при разработке GUI-приложений для отделения кода управления потоком ([#Контроллер]) от кода логики приложения ([#Модель]) и от кода рендеринга данных ([#Вид]). Таким образом, код становится более понятным, это облегчает будущую разработку и позволяет тестировать отдельные части отдельно. +Model-View-Controller +--------------------- +Программная архитектура, возникшая из необходимости отделить код обработки ([контроллера |#Контроллер]) от кода логики приложения ([модели |#Модель Model]) и от кода отображения данных ([представления |#Представление View]) в приложениях с графическим интерфейсом. Это делает приложение более понятным, облегчает дальнейшую разработку и позволяет тестировать отдельные части по отдельности. + +Model-View-Presenter +-------------------- +Архитектура, основанная на [#Model-View-Controller]. -Модель-Вид-Презентер (MVP) .[#toc-model-view-presenter] -------------------------------------------------------- -Архитектура, основанная на [#Модель-Вид-Контроллер (MVC)]. +Модуль (Module) +--------------- +Модуль представляет собой логическую часть приложения. В типичной структуре это группа презентеров и шаблонов, которые решают определенную область функциональности. Модули размещаются в [отдельных каталогах |application:directory-structure#Презентеры и шаблоны], таких как `Front/`, `Admin/` или `Shop/`. -Модуль .[#toc-module] ---------------------- -Модуль в фреймворке Nette представляет собой набор презентеров и шаблонов, в конечном итоге также компонентов и моделей, которые служат данными для презентера. Таким образом, это определенная логическая часть приложения. +Например, интернет-магазин можно разделить на: +- Фронтенд (`Shop/`) для просмотра товаров и покупок +- Клиентскую секцию (`Customer/`) для управления заказами +- Администрирование (`Admin/`) для оператора -Например, электронный магазин может состоять из трех модулей: -1) Каталог товаров с корзиной. -2) Администрирование для клиента. -3) Администрирование для владельца магазина. +Технически это обычные каталоги, которые, однако, благодаря четкому разделению помогают масштабировать приложение. Презентер `Admin:Product:List` физически будет расположен, например, в каталоге `app/Presentation/Admin/Product/List/` (см. [сопоставление презентеров |application:directory-structure#Маппинг презентеров]). -Пространство имен .[#toc-namespace] ------------------------------------ -Пространство имен является особенностью языка PHP, начиная с версии 5.3, а также некоторых других языков программирования. Это помогает избежать столкновений имен (например, два класса с одинаковым именем) при совместном использовании различных библиотек. Более подробную информацию смотрите в [документации PHP |https://www.php.net/manual/ru/language.namespaces.rationale.php]. +Пространство имен (Namespace) +----------------------------- +Пространство имен, часть языка PHP с версии 5.3 и некоторых других языков программирования, позволяющая использовать классы с одинаковыми именами в разных библиотеках без конфликта имен. См. [документацию PHP |https://www.php.net/manual/en/language.namespaces.rationale.php]. -Презентер .[#toc-presenter] ---------------------------- -Презентер — это объект, который принимает [запрос |api:Nette\Application\Request], переведенный маршрутизатором из HTTP-запроса, и генерирует [ответ |api:Nette\Application\Response]. Ответом может быть HTML-страница, картинка, XML-документ, файл, JSON, перенаправление или всё, что вы придумаете. +Презентер (Presenter) +--------------------- +Презентер — это объект, который принимает [запрос |api:Nette\Application\Request], преобразованный маршрутизатором из HTTP-запроса, и генерирует [ответ |api:Nette\Application\Response]. Ответом может быть HTML-страница, изображение, XML-документ, файл на диске, JSON, перенаправление или что угодно, что вы придумаете. -Под презентером обычно подразумевается потомок класса [api:Nette\Application\UI\Presenter]. По запросам он выполняет соответствующие [действия |application:presenters#Life-Cycle-of-Presenter] и рендерит шаблоны. +Обычно под понятием презентер подразумевается потомок класса [api:Nette\Application\UI\Presenter]. В зависимости от входящих запросов он запускает соответствующие [действия |application:presenters#Жизненный цикл презентера] и отрисовывает шаблоны. -Роутер .[#toc-router] ---------------------- -Двунаправленный переводчик между HTTP-запросом / URL и действием презентера. Двунаправленность означает, что можно не только получить [#Действие презентера] из HTTP-запроса, но и сгенерировать соответствующий URL для действия. См. подробнее в главе об [URL-маршрутизации |application:routing]. +Маршрутизатор (Router) +---------------------- +Двунаправленный преобразователь между HTTP-запросом / URL и действием презентера. Двунаправленный означает, что из HTTP-запроса можно вывести [#действие презентера], а также наоборот, для действия сгенерировать соответствующий URL. Подробнее в главе о [маршрутизации URL |application:routing]. -Печенье SameSite .[#toc-samesite-cookie] ----------------------------------------- -Куки SameSite обеспечивают механизм распознавания того, что привело к загрузке страницы. Он может иметь три значения: `Lax`, `Strict` и `None` (последнее требует наличия HTTPS). Если запрос на страницу поступает непосредственно с сайта или пользователь открывает страницу, набрав ее прямо в адресной строке или нажав на закладку, браузер отправляет все cookies на сервер (т.е. с флагами `Lax`, `Strict` и `None`). Если пользователь переходит на сайт по ссылке с другого сайта, на сервер передаются файлы cookie с флагами `Lax` и `None`. Если запрос осуществляется другими способами, например, при отправке POST-формы с другого сайта, загрузке внутри iframe, использовании JavaScript и т. д., передаются только файлы cookie с флагом `None`. +SameSite cookie +--------------- +SameSite cookies предоставляют механизм для определения того, что привело к загрузке страницы. Он может иметь три значения: `Lax`, `Strict` и `None` (последний требует HTTPS). Если запрос на страницу поступает непосредственно с сайта или пользователь открывает страницу, введя ее непосредственно в адресную строку или щелкнув по закладке, браузер отправляет серверу все файлы cookie (т.е. с флагами `Lax`, `Strict` и `None`). Если пользователь переходит на сайт по ссылке с другого сайта, серверу передаются файлы cookie с флагами `Lax` и `None`. Если запрос возникает другим способом, например, при отправке POST-формы с другого сайта, загрузке внутри iframe, с помощью JavaScript и т.д., отправляются только файлы cookie с флагом `None`. -Сервис .[#toc-service] ----------------------- -В контексте Dependency Injection под сервисом понимается объект, который создается и управляется DI-контейнером. Сервис можно легко заменить другой реализацией, например, в целях тестирования или для изменения поведения приложения, без необходимости изменять код, использующий сервис. +Сервис (Service) +---------------- +В контексте внедрения зависимостей сервисом называется объект, который создается и управляется DI-контейнером. Сервис может быть легко заменен другой реализацией, например, для целей тестирования или для изменения поведения приложения без необходимости изменять код, использующий сервис. + +Сниппет (Snippet) +----------------- +Фрагмент, часть страницы, которую можно перерисовать отдельно во время AJAX-запроса. -Фрагмент .[#toc-snippet] ------------------------- -Фрагмент страницы, который может быть отдельно повторно отображен во время [AJAX-запроса |#AJAX]. + +Представление (View) +-------------------- +View, то есть представление, — это слой приложения, отвечающий за отображение результата запроса. Обычно он использует систему шаблонов и знает, как отобразить тот или иной компонент или результат, полученный из модели. -Посмотреть .[#toc-view] ------------------------ -Представление - это слой приложения, который отвечает за отрисовку результатов запроса. Обычно он использует систему шаблонов и знает, как отобразить свои компоненты или результаты, взятые из модели. diff --git a/nette/ru/installation.texy b/nette/ru/installation.texy index 230676af6a..09c7e87d9e 100644 --- a/nette/ru/installation.texy +++ b/nette/ru/installation.texy @@ -2,66 +2,66 @@ *************** .[perex] -Вы хотите использовать преимущества Nette в своем существующем проекте или планируете создать новый проект на базе Nette? В этом руководстве вы шаг за шагом пройдете весь путь установки. +Хотите использовать преимущества Nette в своем существующем проекте или собираетесь создать новый проект на основе Nette? Это руководство проведет вас через установку шаг за шагом. -Как добавить Nette в проект .[#toc-how-to-add-nette-to-your-project] --------------------------------------------------------------------- +Как добавить Nette в ваш проект +------------------------------- -Nette представляет собой набор полезных и сложных пакетов (библиотек) для PHP. Чтобы включить их в свой проект, выполните следующие действия: +Nette предлагает коллекцию полезных и зрелых пакетов (библиотек) для PHP. Чтобы включить их в ваш проект, выполните следующие действия: -1) **Установите [Composer |best-practices:composer]:** Этот инструмент необходим для простой установки, обновления и управления библиотеками, необходимыми для вашего проекта. +1) **Подготовьте [Composer|best-practices:composer]:** Этот инструмент необходим для легкой установки, обновления и управления библиотеками, необходимыми для вашего проекта. -2) **Выбираем [пакет |www:packages]:** Допустим, вам нужно перемещаться по файловой системе, с чем прекрасно справляется [Finder |utils:finder] из пакета `nette/utils`. Название пакета можно найти в правой колонке его документации. +2) **Выберите [пакет|www:packages]:** Допустим, вам нужно просматривать файловую систему, что отлично делает [Finder|utils:finder] из пакета `nette/utils`. Название пакета вы видите в правом столбце его документации. -3) **Установка пакета:** Выполните эту команду в корневом каталоге вашего проекта: +3) **Установите пакет:** Выполните эту команду в корневом каталоге вашего проекта: ```shell composer require nette/utils ``` -Вы предпочитаете графический интерфейс? Ознакомьтесь с [руководством |https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] по установке пакетов в среде PhpStrom. +Предпочитаете графический интерфейс? Ознакомьтесь с [руководством|https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] по установке пакетов в среде PhpStorm. -Как начать новый проект с помощью Nette .[#toc-how-to-start-a-new-project-with-nette] -------------------------------------------------------------------------------------- +Как создать новый проект с Nette +-------------------------------- -Если вы хотите создать совершенно новый проект на платформе Nette, мы рекомендуем использовать готовый скелет [Web-проекта |https://github.com/nette/web-project]: +Если вы хотите создать совершенно новый проект на платформе Nette, рекомендуем использовать предустановленный скелет [Web Project|https://github.com/nette/web-project]: -1) **Установите [Composer |best-practices:composer].**. +1) **Подготовьте [Composer|best-practices:composer].** -2) **Откройте командную строку** и перейдите в корневой каталог вашего веб-сервера, например, `/etc/var/www`, `C:/xampp/htdocs`, `/Library/WebServer/Documents`. +2) **Откройте командную строку** и перейдите в корневой каталог вашего веб-сервера, например `/etc/var/www`, `C:/xampp/htdocs`, `/Library/WebServer/Documents`. -3) **Создайте проект** с помощью данной команды: +3) **Создайте проект** с помощью этой команды: ```shell -composer create-project nette/web-project PROJECT_NAME +composer create-project nette/web-project NAZEV_PROJEKTU ``` -4) **Не используете Composer?** Просто скачайте [Web-проект в формате ZIP |https://github.com/nette/web-project/archive/preloaded.zip] и распакуйте его. Но поверьте нам, Composer того стоит! +4) **Не используете Composer?** Просто скачайте [Web Project в формате ZIP|https://github.com/nette/web-project/archive/preloaded.zip] и распакуйте его. Но поверьте, Composer того стоит! -5) **Установка прав доступа:** В системах macOS или Linux установите [права на запись |nette:troubleshooting#Setting directory permissions] для каталогов. +5) **Настройка прав:** На системах macOS или Linux установите [права на запись |nette:troubleshooting#Настройка прав доступа к каталогам] для каталогов `temp/` и `log/`. -6) **Откройте проект в браузере:** Введите URL `http://localhost/PROJECT_NAME/www/`. Вы увидите посадочную страницу скелета: +6) **Открытие проекта в браузере:** Введите URL `http://localhost/NAZEV_PROJEKTU/www/` и вы увидите стартовую страницу скелета: -[* qs-welcome.webp .{url: http://localhost/PROJECT_NAME/www/} *] +[* qs-welcome.webp .{url: http://localhost/NAZEV_PROJEKTU/www/} *] -Поздравляем! Теперь ваш сайт готов к разработке. Можете удалить шаблон приветствия и приступить к созданию своего приложения. +Поздравляем! Ваш сайт теперь готов к разработке. Приветственный шаблон можно удалить и начать создавать свое приложение. -Одно из преимуществ Nette заключается в том, что проект работает сразу, не требуя настройки. Однако если у вас возникнут какие-либо проблемы, ознакомьтесь с [общими способами их решения |nette:troubleshooting#nette-is-not-working-white-page-is-displayed]. +Одним из преимуществ Nette является то, что проект работает сразу без необходимости конфигурации. Однако, если вы столкнетесь с проблемами, попробуйте посмотреть [решения частых проблем |nette:troubleshooting#Nette не работает отображается белая страница]. .[note] -Если вы только начинаете работать с Nette, мы рекомендуем продолжить работу с [учебным пособием "Создание первого приложения |quickstart:]". +Если вы начинаете работать с Nette, рекомендуем продолжить с [руководством Пишем первое приложение|quickstart:]. -Инструменты и рекомендации .[#toc-tools-and-recommendations] ------------------------------------------------------------- +Инструменты и рекомендации +-------------------------- -Для эффективной работы с Nette мы рекомендуем следующие инструменты: +Для эффективной работы с Nette рекомендуем следующие инструменты: -- [Качественная IDE с плагинами для Nette |best-practices:editors-and-tools] +- [Качественная IDE с дополнениями для Nette|best-practices:editors-and-tools] - Система контроля версий Git -- [Composer |best-practices:composer] +- [Composer|best-practices:composer] {{leftbar: www:@menu-common}} diff --git a/nette/ru/introduction-to-object-oriented-programming.texy b/nette/ru/introduction-to-object-oriented-programming.texy new file mode 100644 index 0000000000..53425ec3d4 --- /dev/null +++ b/nette/ru/introduction-to-object-oriented-programming.texy @@ -0,0 +1,841 @@ +Введение в объектно-ориентированное программирование +**************************************************** + +.[perex] +Термин "ООП" означает объектно-ориентированное программирование, которое представляет собой способ организации и структурирования кода. ООП позволяет нам рассматривать программу как набор объектов, которые взаимодействуют друг с другом, а не как последовательность команд и функций. + +В ООП "объект" - это единица, которая содержит данные и функции, работающие с этими данными. Объекты создаются по "классам", которые можно понимать как чертежи или шаблоны для объектов. Когда у нас есть класс, мы можем создать его "экземпляр", то есть конкретный объект, созданный по этому классу. + +Давайте посмотрим, как можно создать простой класс в PHP. При определении класса мы используем ключевое слово "class", за которым следует имя класса, а затем фигурные скобки, которые заключают в себе функции (их называют "методами") и переменные класса (их называют "свойствами" или по-английски "property"): + +```php +class Car +{ + function beep() + { + echo 'Bip bip!'; + } +} +``` + +В этом примере мы создали класс с именем `Car` с одной функцией (или "методом"), названной `beep`. + +Каждый класс должен решать только одну основную задачу. Если класс делает слишком много вещей, возможно, стоит разделить его на меньшие, специализированные классы. + +Классы обычно хранятся в отдельных файлах, чтобы код был организован и в нем было легко ориентироваться. Имя файла должно соответствовать имени класса, поэтому для класса `Car` имя файла будет `Car.php`. + +При именовании классов рекомендуется придерживаться конвенции "PascalCase", что означает, что каждое слово в названии начинается с заглавной буквы, и между ними нет подчеркиваний или других разделителей. Методы и свойства используют конвенцию "camelCase", то есть они начинаются с маленькой буквы. + +Некоторые методы в PHP имеют специальные задачи и обозначаются префиксом `__` (два подчеркивания). Одним из важнейших специальных методов является "конструктор", который обозначается как `__construct`. Конструктор - это метод, который автоматически вызывается при создании нового экземпляра класса. + +Конструктор часто используется для установки начального состояния объекта. Например, при создании объекта, представляющего человека, вы можете использовать конструктор для установки его возраста, имени или других свойств. + +Давайте посмотрим, как использовать конструктор в PHP: + +```php +class Person +{ + private $age; + + function __construct($age) + { + $this->age = $age; + } + + function getAge() + { + return $this->age; + } +} + +$person = new Person(25); +echo $person->getAge(); // Выведет: 25 +``` + +В этом примере класс `Person` имеет свойство (переменную) `$age` и конструктор, который устанавливает это свойство. Метод `getAge()` затем позволяет получить доступ к возрасту человека. + +Псевдопеременная `$this` используется внутри класса для доступа к свойствам и методам объекта. + +Ключевое слово `new` используется для создания нового экземпляра класса. В приведенном выше примере мы создали нового человека в возрасте 25 лет. + +Вы также можете установить значения по умолчанию для параметров конструктора, если они не указаны при создании объекта. Например: + +```php +class Person +{ + private $age; + + function __construct($age = 20) + { + $this->age = $age; + } + + function getAge() + { + return $this->age; + } +} + +$person = new Person; // если мы не передаем никаких аргументов, скобки можно опустить +echo $person->getAge(); // Выведет: 20 +``` + +В этом примере, если вы не укажете возраст при создании объекта `Person`, будет использовано значение по умолчанию 20. + +Приятно то, что определение свойства с его инициализацией через конструктор можно сократить и упростить следующим образом: + +```php +class Person +{ + function __construct( + private $age = 20, + ) { + } +} +``` + +Для полноты картины, помимо конструкторов, объекты могут иметь и деструкторы (метод `__destruct`), которые вызываются перед тем, как объект будет освобожден из памяти. + + +Пространства имен +----------------- + +Пространства имен (или "namespaces" по-английски) позволяют нам организовывать и группировать связанные классы, функции и константы, избегая при этом конфликтов имен. Вы можете представить их как папки на компьютере, где каждая папка содержит файлы, относящиеся к определенному проекту или теме. + +Пространства имен особенно полезны в больших проектах или при использовании сторонних библиотек, где могут возникнуть конфликты имен классов. + +Представьте, что у вас есть класс с именем `Car` в вашем проекте, и вы хотите поместить его в пространство имен `Transport`. Вы сделаете это так: + +```php +namespace Transport; + +class Car +{ + function beep() + { + echo 'Bip bip!'; + } +} +``` + +Если вы хотите использовать класс `Car` в другом файле, вы должны указать, из какого пространства имен происходит класс: + +```php +$car = new Transport\Car; +``` + +Для упрощения вы можете указать в начале файла, какой класс из данного пространства имен вы хотите использовать, что позволяет создавать экземпляры без необходимости указывать полный путь: + +```php +use Transport\Car; + +$car = new Car; +``` + + +Наследование +------------ + +Наследование - это инструмент объектно-ориентированного программирования, который позволяет создавать новые классы на основе уже существующих, перенимать их свойства и методы, а также расширять или переопределять их по мере необходимости. Наследование позволяет обеспечить повторное использование кода и иерархию классов. + +Проще говоря, если у нас есть один класс и мы хотим создать другой, производный от него, но с некоторыми изменениями, мы можем "унаследовать" новый класс от исходного. + +В PHP наследование реализуется с помощью ключевого слова `extends`. + +Наш класс `Person` хранит информацию о возрасте. Мы можем иметь другой класс `Student`, который расширяет `Person` и добавляет информацию о специальности. + +Рассмотрим пример: + +```php +class Person +{ + private $age; + + function __construct($age) + { + $this->age = $age; + } + + function printInfo() + { + echo "Возраст: {$this->age} лет\n"; + } +} + +class Student extends Person +{ + private $major; + + function __construct($age, $major) + { + parent::__construct($age); + $this->major = $major; + } + + function printInfo() + { + parent::printInfo(); + echo "Специальность: {$this->major} \n"; + } +} + +$student = new Student(20, 'Информатика'); +$student->printInfo(); +``` + +Как работает этот код? + +- Мы использовали ключевое слово `extends` для расширения класса `Person`, что означает, что класс `Student` унаследует все методы и свойства от `Person`. + +- Ключевое слово `parent::` позволяет нам вызывать методы из родительского класса. В данном случае мы вызвали конструктор из класса `Person` перед добавлением собственной функциональности в класс `Student`. Аналогично и метод `printInfo()` предка перед выводом информации о студенте. + +Наследование предназначено для ситуаций, когда существует отношение "является" между классами. Например, `Student` является `Person`. Кошка является животным. Это дает нам возможность в случаях, когда в коде ожидается один объект (например, "Person"), использовать вместо него унаследованный объект (например, "Student"). + +Важно понимать, что основной целью наследования **не является** предотвращение дублирования кода. Напротив, неправильное использование наследования может привести к сложному и трудно поддерживаемому коду. Если отношения "является" между классами не существует, вместо наследования следует рассмотреть композицию. + +Обратите внимание, что методы `printInfo()` в классах `Person` и `Student` выводят немного разную информацию. И мы можем добавить другие классы (например, `Employee`), которые будут предоставлять другие реализации этого метода. Способность объектов разных классов по-разному реагировать на один и тот же метод называется полиморфизмом: + +```php +$persons = [ + new Person(30), + new Student(20, 'Информатика'), + new Employee(45, 'Директор'), +]; + +foreach ($persons as $person) { + $person->printInfo(); +} +``` + + +Композиция +---------- + +Композиция - это техника, при которой вместо того, чтобы наследовать свойства и методы другого класса, мы просто используем его экземпляр в нашем классе. Это позволяет нам комбинировать функциональность и свойства нескольких классов без необходимости создавать сложные структуры наследования. + +Рассмотрим пример. У нас есть класс `Engine` и класс `Car`. Вместо того чтобы говорить "Автомобиль является Двигателем", мы говорим "Автомобиль имеет Двигатель", что является типичным отношением композиции. + +```php +class Engine +{ + function turnOn() + { + echo 'Двигатель работает.'; + } +} + +class Car +{ + private $engine; + + function __construct() + { + $this->engine = new Engine; + } + + function start() + { + $this->engine->turnOn(); + echo 'Автомобиль готов к поездке!'; + } +} + +$car = new Car; +$car->start(); +``` + +Здесь `Car` не имеет всех свойств и методов `Engine`, но имеет к нему доступ через свойство `$engine`. + +Преимуществом композиции является большая гибкость в проектировании и лучшая возможность модификации в будущем. + + +Видимость +--------- + +В PHP вы можете определить "видимость" для свойств, методов и констант класса. Видимость определяет, откуда вы можете получить доступ к этим элементам. + +1. **Public:** Если элемент помечен как `public`, это означает, что к нему можно получить доступ откуда угодно, даже вне класса. + +2. **Protected:** Элемент с пометкой `protected` доступен только в пределах данного класса и всех его потомков (классов, которые наследуют от этого класса). + +3. **Private:** Если элемент `private`, к нему можно получить доступ только изнутри класса, в котором он был определен. + +Если вы не укажете видимость, PHP автоматически установит ее на `public`. + +Рассмотрим пример кода: + +```php +class VisibilityExample +{ + public $publicProperty = 'Публичное'; + protected $protectedProperty = 'Защищенное'; + private $privateProperty = 'Приватное'; + + public function printProperties() + { + echo $this->publicProperty; // Работает + echo $this->protectedProperty; // Работает + echo $this->privateProperty; // Работает + } +} + +$object = new VisibilityExample; +$object->printProperties(); +echo $object->publicProperty; // Работает +// echo $object->protectedProperty; // Вызовет ошибку +// echo $object->privateProperty; // Вызовет ошибку +``` + +Продолжим с наследованием класса: + +```php +class ChildClass extends VisibilityExample +{ + public function printProperties() + { + echo $this->publicProperty; // Работает + echo $this->protectedProperty; // Работает + // echo $this->privateProperty; // Вызовет ошибку + } +} +``` + +В этом случае метод `printProperties()` в классе `ChildClass` может получить доступ к публичным и защищенным свойствам, но не может получить доступ к приватным свойствам родительского класса. + +Данные и методы должны быть максимально скрыты и доступны только через определенный интерфейс. Это позволит вам изменять внутреннюю реализацию класса, не затрагивая остальной код. + + +Ключевое слово `final` +---------------------- + +В PHP мы можем использовать ключевое слово `final`, если хотим запретить наследование или переопределение класса, метода или константы. Когда мы помечаем класс как `final`, он не может быть расширен. Когда мы помечаем метод как `final`, он не может быть переопределен в дочернем классе. + +Знание того, что определенный класс или метод не будет далее изменяться, позволяет нам легче вносить изменения, не беспокоясь о возможных конфликтах. Например, мы можем добавить новый метод, не опасаясь, что какой-либо его потомок уже имеет метод с таким же именем, что привело бы к коллизии. Или мы можем изменить параметры метода, так как опять же нет риска вызвать несоответствие с переопределенным методом в потомке. + +```php +final class FinalClass +{ +} + +// Следующий код вызовет ошибку, так как нельзя наследовать от final класса. +class ChildOfFinalClass extends FinalClass +{ +} +``` + +В этом примере попытка наследования от финального класса `FinalClass` вызовет ошибку. + + +Статические свойства и методы +----------------------------- + +Когда в PHP мы говорим о "статических" элементах класса, мы имеем в виду методы и свойства, которые принадлежат самому классу, а не конкретному экземпляру этого класса. Это означает, что вам не нужно создавать экземпляр класса, чтобы получить к ним доступ. Вместо этого вы вызываете их или обращаетесь к ним непосредственно через имя класса. + +Имейте в виду, что поскольку статические элементы принадлежат классу, а не его экземплярам, вы не можете использовать псевдопеременную `$this` внутри статических методов. + +Использование статических свойств ведет к [непонятному коду, полному подводных камней|dependency-injection:global-state], поэтому вам никогда не следует их использовать, и мы даже не будем приводить здесь пример их использования. Напротив, статические методы полезны. Пример использования: + +```php +class Calculator +{ + public static function add($a, $b) + { + return $a + $b; + } + + public static function subtract($a, $b) + { + return $a - $b; + } +} + +// Использование статического метода без создания экземпляра класса +echo Calculator::add(5, 3); // Результат: 8 +echo Calculator::subtract(5, 3); // Результат: 2 +``` + +В этом примере мы создали класс `Calculator` с двумя статическими методами. Мы можем вызывать эти методы напрямую без создания экземпляра класса с помощью оператора `::`. Статические методы особенно полезны для операций, которые не зависят от состояния конкретного экземпляра класса. + + +Константы класса +---------------- + +В рамках классов у нас есть возможность определять константы. Константы - это значения, которые никогда не изменяются во время выполнения программы. В отличие от переменных, значение константы остается неизменным. + +```php +class Car +{ + public const NumberOfWheels = 4; + + public function displayNumberOfWheels(): int + { + echo self::NumberOfWheels; + } +} + +echo Car::NumberOfWheels; // Вывод: 4 +``` + +В этом примере у нас есть класс `Car` с константой `NumberOfWheels`. Когда мы хотим получить доступ к константе внутри класса, мы можем использовать ключевое слово `self` вместо имени класса. + + +Объектные интерфейсы +-------------------- + +Объектные интерфейсы функционируют как "контракты" для классов. Если класс должен реализовывать объектный интерфейс, он должен содержать все методы, которые определяет этот интерфейс. Это отличный способ гарантировать, что определенные классы придерживаются одного и того же "контракта" или структуры. + +В PHP интерфейс определяется ключевым словом `interface`. Все методы, определенные в интерфейсе, являются публичными (`public`). Когда класс реализует интерфейс, он использует ключевое слово `implements`. + +```php +interface Animal +{ + function makeSound(); +} + +class Cat implements Animal +{ + public function makeSound() + { + echo 'Мяу'; + } +} + +$cat = new Cat; +$cat->makeSound(); +``` + +Если класс реализует интерфейс, но в нем не определены все ожидаемые методы, PHP выдаст ошибку. + +Класс может реализовывать несколько интерфейсов одновременно, что отличает его от наследования, где класс может наследоваться только от одного класса: + +```php +interface Guard +{ + function guardHouse(); +} + +class Dog implements Animal, Guard +{ + public function makeSound() + { + echo 'Гав'; + } + + public function guardHouse() + { + echo 'Собака внимательно охраняет дом'; + } +} +``` + + +Абстрактные классы +------------------ + +Абстрактные классы служат базовыми шаблонами для других классов, но вы не можете создавать их экземпляры напрямую. Они содержат комбинацию полных методов и абстрактных методов, которые не имеют определенного содержания. Классы, наследующие от абстрактных классов, должны предоставить определения для всех абстрактных методов предка. + +Для определения абстрактного класса мы используем ключевое слово `abstract`. + +```php +abstract class AbstractClass +{ + public function regularMethod() + { + echo 'Это обычный метод'; + } + + abstract public function abstractMethod(); +} + +class Child extends AbstractClass +{ + public function abstractMethod() + { + echo 'Это реализация абстрактного метода'; + } +} + +$instance = new Child; +$instance->regularMethod(); +$instance->abstractMethod(); +``` + +В этом примере у нас есть абстрактный класс с одним обычным и одним абстрактным методом. Затем у нас есть класс `Child`, который наследует от `AbstractClass` и предоставляет реализацию для абстрактного метода. + +Чем же отличаются интерфейсы от абстрактных классов? Абстрактные классы могут содержать как абстрактные, так и конкретные методы, в то время как интерфейсы только определяют, какие методы должен реализовывать класс, но не предоставляют никакой реализации. Класс может наследоваться только от одного абстрактного класса, но может реализовывать любое количество интерфейсов. + + +Контроль типов +-------------- + +В программировании очень важно быть уверенным, что данные, с которыми мы работаем, имеют правильный тип. В PHP у нас есть инструменты, которые это обеспечивают. Проверка того, имеют ли данные правильный тип, называется "контролем типов". + +Типы, с которыми мы можем столкнуться в PHP: + +1. **Базовые типы**: Включают `int` (целые числа), `float` (десятичные числа), `bool` (логические значения), `string` (строки), `array` (массивы) и `null`. +2. **Классы**: Если мы хотим, чтобы значение было экземпляром определенного класса. +3. **Интерфейсы**: Определяет набор методов, которые класс должен реализовать. Значение, удовлетворяющее интерфейсу, должно иметь эти методы. +4. **Смешанные типы**: Мы можем указать, что переменная может иметь несколько разрешенных типов. +5. **Void**: Этот специальный тип указывает, что функция или метод не возвращает никакого значения. + +Давайте посмотрим, как изменить код, чтобы включить типы: + +```php +class Person +{ + private int $age; + + public function __construct(int $age) + { + $this->age = $age; + } + + public function printAge(): void + { + echo "Этому человеку {$this->age} лет."; + } +} + +/** + * Функция, которая принимает объект класса Person и выводит возраст человека. + */ +function printPersonAge(Person $person): void +{ + $person->printAge(); +} +``` + +Таким образом, мы обеспечили, что наш код ожидает и работает с данными правильного типа, что помогает нам предотвращать потенциальные ошибки. + +Некоторые типы нельзя напрямую записать в PHP. В таком случае они указываются в комментарии phpDoc, который является стандартным форматом для документирования PHP-кода, начинающимся с `/**` и заканчивающимся `*/`. Он позволяет добавлять описания классов, методов и так далее. А также указывать сложные типы с помощью так называемых аннотаций `@var`, `@param` и `@return`. Эти типы затем используются инструментами для статического анализа кода, но сам PHP их не проверяет. + +```php +class ListClass +{ + /** @var array<Person> запись означает, что это массив объектов Person */ + private array $persons = []; + + public function addPerson(Person $person): void + { + $this->persons[] = $person; + } +} +``` + + +Сравнение и идентичность +------------------------ + +В PHP вы можете сравнивать объекты двумя способами: + +1. Сравнение значений `==`: Проверяет, являются ли объекты одного класса и имеют ли они одинаковые значения в своих свойствах. +2. Идентичность `===`: Проверяет, является ли это одним и тем же экземпляром объекта. + +```php +class Car +{ + public string $brand; + + public function __construct(string $brand) + { + $this->brand = $brand; + } +} + +$car1 = new Car('Skoda'); +$car2 = new Car('Skoda'); +$car3 = $car1; + +var_dump($car1 == $car2); // true, потому что у них одинаковое значение +var_dump($car1 === $car2); // false, потому что это не один и тот же экземпляр +var_dump($car1 === $car3); // true, потому что $car3 - это тот же экземпляр, что и $car1 +``` + + +Оператор `instanceof` +--------------------- + +Оператор `instanceof` позволяет определить, является ли данный объект экземпляром определенного класса, потомком этого класса или реализует ли он определенный интерфейс. + +Представим, что у нас есть класс `Person` и другой класс `Student`, который является потомком класса `Person`: + +```php +class Person +{ + private int $age; + + public function __construct(int $age) + { + $this->age = $age; + } +} + +class Student extends Person +{ + private string $major; + + public function __construct(int $age, string $major) + { + parent::__construct($age); + $this->major = $major; + } +} + +$student = new Student(20, 'Информатика'); + +// Проверка, является ли $student экземпляром класса Student +var_dump($student instanceof Student); // Вывод: bool(true) + +// Проверка, является ли $student экземпляром класса Person (поскольку Student является потомком Person) +var_dump($student instanceof Person); // Вывод: bool(true) +``` + +Из выводов видно, что объект `$student` одновременно считается экземпляром обоих классов - `Student` и `Person`. + + +Текучие интерфейсы (Fluent Interfaces) +-------------------------------------- + +"Текучий интерфейс" (по-английски "Fluent Interface") - это техника в ООП, которая позволяет связывать методы в цепочку в одном вызове. Это часто упрощает и делает код более понятным. + +Ключевым элементом текучего интерфейса является то, что каждый метод в цепочке возвращает ссылку на текущий объект. Этого мы достигаем, используя `return $this;` в конце метода. Этот стиль программирования часто ассоциируется с методами, называемыми "сеттерами", которые устанавливают значения свойств объекта. + +Покажем, как может выглядеть текучий интерфейс на примере отправки электронных писем: + +```php +public function sendMessage() +{ + $email = new Email; + $email->setFrom('sender@example.com') + ->setRecipient('admin@example.com') + ->setMessage('Здравствуйте, это сообщение.') + ->send(); +} +``` + +В этом примере методы `setFrom()`, `setRecipient()` и `setMessage()` служат для установки соответствующих значений (отправителя, получателя, содержания сообщения). После установки каждого из этих значений методы возвращают нам текущий объект (`$email`), что позволяет нам связать следующий метод за ним. Наконец, мы вызываем метод `send()`, который фактически отправляет письмо. + +Благодаря текучим интерфейсам мы можем писать код, который интуитивно понятен и легко читаем. + + +Копирование с помощью `clone` +----------------------------- + +В PHP мы можем создать копию объекта с помощью оператора `clone`. Таким образом, мы получим новый экземпляр с идентичным содержимым. + +Если нам нужно при копировании объекта изменить некоторые его свойства, мы можем определить в классе специальный метод `__clone()`. Этот метод автоматически вызывается при клонировании объекта. + +```php +class Sheep +{ + public string $name; + + public function __construct(string $name) + { + $this->name = $name; + } + + public function __clone() + { + $this->name = 'Клон ' . $this->name; + } +} + +$original = new Sheep('Долли'); +echo $original->name . "\n"; // Выведет: Долли + +$clone = clone $original; +echo $clone->name . "\n"; // Выведет: Клон Долли +``` + +В этом примере у нас есть класс `Sheep` с одним свойством `$name`. Когда мы клонируем экземпляр этого класса, метод `__clone()` позаботится о том, чтобы имя клонированной овцы получило префикс "Клон". + + +Трейты +------ + +Трейты в PHP - это инструмент, который позволяет совместно использовать методы, свойства и константы между классами и избегать дублирования кода. Вы можете представить их как механизм "копировать и вставить" (Ctrl-C и Ctrl-V), при котором содержимое трейта "вставляется" в классы. Это позволяет вам повторно использовать код без необходимости создавать сложные иерархии классов. + +Давайте покажем простой пример использования трейтов в PHP: + +```php +trait Beeping +{ + public function beep() + { + echo 'Bip bip!'; + } +} + +class Car +{ + use Beeping; +} + +class Truck +{ + use Beeping; +} + +$car = new Car; +$car->beep(); // Выведет 'Bip bip!' + +$truck = new Truck; +$truck->beep(); // Также выведет 'Bip bip!' +``` + +В этом примере у нас есть трейт `Beeping`, который содержит один метод `beep()`. Затем у нас есть два класса: `Car` и `Truck`, которые оба используют трейт `Beeping`. Благодаря этому оба класса "имеют" метод `beep()`, и мы можем вызывать его на объектах обоих классов. + +Трейты позволяют легко и эффективно совместно использовать код между классами. При этом они не входят в иерархию наследования, т.е. `$car instanceof Beeping` вернет `false`. + + +Исключения +---------- + +Исключения в ООП позволяют нам элегантно обрабатывать ошибки и непредвиденные ситуации в нашем коде. Это объекты, которые несут информацию об ошибке или необычной ситуации. + +В PHP есть встроенный класс `Exception`, который служит основой для всех исключений. Он имеет несколько методов, которые позволяют нам получить больше информации об исключении, например, сообщение об ошибке, файл и строку, где произошла ошибка, и т.д. + +Когда в коде возникает ошибка, мы можем "выбросить" исключение с помощью ключевого слова `throw`. + +```php +function divide(float $a, float $b): float +{ + if ($b === 0.0) { // Сравнение с float + throw new Exception('Деление на ноль!'); + } + return $a / $b; +} +``` + +Когда функция `divide()` получает ноль в качестве второго аргумента, она выбрасывает исключение с сообщением об ошибке `'Деление на ноль!'`. Чтобы предотвратить сбой программы при выбросе исключения, мы перехватываем его в блоке `try/catch`: + +```php +try { + echo divide(10, 0); +} catch (Exception $e) { + echo 'Исключение перехвачено: '. $e->getMessage(); +} +``` + +Код, который может выбросить исключение, обернут в блок `try`. Если исключение выброшено, выполнение кода перемещается в блок `catch`, где мы можем обработать исключение (например, вывести сообщение об ошибке). + +После блоков `try` и `catch` мы можем добавить необязательный блок `finally`, который выполнится всегда, независимо от того, было ли выброшено исключение или нет (даже если в блоке `try` или `catch` используется оператор `return`, `break` или `continue`): + +```php +try { + echo divide(10, 0); +} catch (Exception $e) { + echo 'Исключение перехвачено: '. $e->getMessage(); +} finally { + // Код, который выполняется всегда, независимо от того, было ли выброшено исключение +} +``` + +Мы также можем создать собственные классы (иерархию) исключений, которые наследуются от класса Exception. В качестве примера представим простое банковское приложение, которое позволяет выполнять вклады и снятия: + +```php +class BankException extends Exception {} +class InsufficientFundsException extends BankException {} +class LimitExceededException extends BankException {} + +class BankAccount +{ + private int $balance = 0; + private int $dailyLimit = 1000; + + public function deposit(int $amount): int + { + $this->balance += $amount; + return $this->balance; + } + + public function withdraw(int $amount): int + { + if ($amount > $this->balance) { + throw new InsufficientFundsException('На счете недостаточно средств.'); + } + + if ($amount > $this->dailyLimit) { + throw new LimitExceededException('Превышен дневной лимит снятия средств.'); + } + + $this->balance -= $amount; + return $this->balance; + } +} +``` + +Для одного блока `try` можно указать несколько блоков `catch`, если вы ожидаете разные типы исключений. + +```php +$account = new BankAccount; +$account->deposit(500); + +try { + $account->withdraw(1500); +} catch (LimitExceededException $e) { + echo $e->getMessage(); +} catch (InsufficientFundsException $e) { + echo $e->getMessage(); +} catch (BankException $e) { + echo 'Произошла ошибка при выполнении операции.'; +} +``` + +В этом примере важно обратить внимание на порядок блоков `catch`. Поскольку все исключения наследуются от `BankException`, если бы мы поместили этот блок первым, в нем были бы перехвачены все исключения, и код не дошел бы до следующих блоков `catch`. Поэтому важно располагать более специфичные исключения (т.е. те, которые наследуются от других) в блоке `catch` выше по порядку, чем их родительские исключения. + + +Итерация +-------- + +В PHP вы можете перебирать объекты с помощью цикла `foreach`, подобно тому, как вы перебираете массивы. Чтобы это работало, объект должен реализовывать специальный интерфейс. + +Первый вариант - реализовать интерфейс `Iterator`, который имеет методы `current()`, возвращающий текущее значение, `key()`, возвращающий ключ, `next()`, переходящий к следующему значению, `rewind()`, переходящий к началу, и `valid()`, проверяющий, не достигли ли мы конца. + +Второй вариант - реализовать интерфейс `IteratorAggregate`, который имеет только один метод `getIterator()`. Он либо возвращает замещающий объект, который будет обеспечивать перебор, либо может представлять собой генератор, то есть специальную функцию, в которой используется `yield` для последовательного возврата ключей и значений: + +```php +class Person +{ + public function __construct( + public int $age, + ) { + } +} + +class ListClass implements IteratorAggregate +{ + private array $persons = []; + + public function addPerson(Person $person): void + { + $this->persons[] = $person; + } + + public function getIterator(): Generator + { + foreach ($this->persons as $person) { + yield $person; + } + } +} + +$list = new ListClass; +$list->addPerson(new Person(30)); +$list->addPerson(new Person(25)); + +foreach ($list as $person) { + echo "Возраст: {$person->age} лет \n"; +} +``` + + +Лучшие практики +--------------- + +Когда вы освоили основные принципы объектно-ориентированного программирования, важно сосредоточиться на лучших практиках в ООП. Они помогут вам писать код, который не только функционален, но и читаем, понятен и легко поддерживаем. + +1) **Разделение ответственности (Separation of Concerns)**: Каждый класс должен иметь четко определенную ответственность и решать только одну основную задачу. Если класс делает слишком много вещей, возможно, стоит разделить его на меньшие, специализированные классы. +2) **Инкапсуляция (Encapsulation)**: Данные и методы должны быть максимально скрыты и доступны только через определенный интерфейс. Это позволит вам изменять внутреннюю реализацию класса, не затрагивая остальной код. +3) **Внедрение зависимостей (Dependency Injection)**: Вместо того чтобы создавать зависимости непосредственно в классе, вы должны "внедрять" их извне. Для более глубокого понимания этого принципа рекомендуем [главы о внедрении зависимостей|dependency-injection:introduction]. diff --git a/nette/ru/troubleshooting.texy b/nette/ru/troubleshooting.texy index 8f8efd9ddb..0f7b76e1c6 100644 --- a/nette/ru/troubleshooting.texy +++ b/nette/ru/troubleshooting.texy @@ -2,40 +2,69 @@ *************** -Nette не работает, отображается белая страница .[#toc-nette-is-not-working-white-page-is-displayed] ---------------------------------------------------------------------------------------------------- -- Попробуйте поставить `ini_set('display_errors', '1'); error_reporting(E_ALL);` после `declare(strict_types=1);` в файле `index.php`, чтобы заставить отображать ошибки. -- Если вы по-прежнему видите белый экран, вероятно, в настройках сервера произошла ошибка, и вы обнаружите причину в журнале сервера. Чтобы убедиться в этом, проверьте, работает ли PHP вообще, попытавшись напечатать что-нибудь с помощью команды `echo 'test';`. -- Если вы увидите ошибку *Server Error: We're sorry! ...*, переходите к следующему разделу: +Nette не работает, отображается белая страница +---------------------------------------------- +- Попробуйте в файл `index.php` сразу после `declare(strict_types=1);` вставить `ini_set('display_errors', '1'); error_reporting(E_ALL);`, это принудительно включит отображение ошибок. +- Если вы по-прежнему видите белый экран, вероятно, проблема в настройках сервера, и причину можно найти в логе сервера. На всякий случай проверьте, работает ли вообще PHP, попробовав что-нибудь вывести с помощью `echo 'test';` +- Если вы видите ошибку *Server Error: We're sorry! …*, перейдите к следующему разделу: -Ошибка 500 *Ошибка сервера: We're sorry! ...* .[#toc-error-500-server-error-we-re-sorry] ----------------------------------------------------------------------------------------- -Эта страница ошибки отображается Nette в производственном режиме. Если вы видите ее на машине разработчика, [переключитесь в режим разработчика |application:bootstrap#Development-vs-Production-Mode]. +Ошибка 500 *Server Error: We're sorry! …* +----------------------------------------- +Эту страницу ошибки отображает Nette в производственном режиме. Если она отображается на вашем компьютере разработчика, [переключитесь в режим разработки |application:bootstrapping#Режим разработки vs режим production], и вам отобразится Tracy с подробным сообщением. -Если сообщение об ошибке содержит `Tracy is unable to log error`, выясните, почему ошибки не могут быть зарегистрированы. Это можно сделать, например, [переключившись |application:bootstrap#Development-vs-Production-Mode] в режим разработчика и вызвав `Tracy\Debugger::log('hello');` после `$configurator->enableTracy(...)`. Трейси расскажет вам, почему он не может вести журнал. -Обычно причиной является [недостаточные разрешения |#Setting-Directory-Permissions] для записи в каталог `log/`. +Причину ошибки всегда можно найти в логе в каталоге `log/`. Однако, если в сообщении об ошибке появляется фраза `Tracy is unable to log error`, сначала выясните, почему ошибки не могут быть залогированы. Сделать это можно, например, временно [переключившись |application:bootstrapping#Режим разработки vs режим production] в режим разработки и позволив Tracy что-нибудь залогировать после ее запуска: -Если фраза `Tracy is unable to log error` отсутствует в сообщении об ошибке (больше нет), вы можете узнать причину ошибки в журнале в директории `log/`. +```php +// Bootstrap.php +$configurator->setDebugMode('23.75.345.200'); // ваш IP-адрес +$configurator->enableTracy($rootDir . '/log'); +\Tracy\Debugger::log('hello'); +``` + +Tracy сообщит вам, почему она не может логировать. Причиной могут быть [недостаточные права |#Настройка прав доступа к каталогам] на запись в каталог `log/`. + +Одной из наиболее частых причин ошибки 500 является устаревший кеш. В то время как Nette в режиме разработки умно автоматически обновляет кеш, в производственном режиме он фокусируется на максимизации производительности, и очистка кеша после каждого изменения кода — ваша задача. Попробуйте удалить `temp/cache`. + + +Ошибка 404, не работает маршрутизация +------------------------------------- +Если все страницы (кроме главной) возвращают ошибку 404, похоже, проблема с конфигурацией сервера для [красивых URL |#Как настроить сервер для красивых URL]. + + +Изменения в шаблонах или конфигурации не отображаются +----------------------------------------------------- +"Я изменил шаблон или конфигурацию, но сайт по-прежнему отображает старую версию." Такое поведение происходит в [производственном режиме |application:bootstrapping#Режим разработки vs режим production], который из соображений производительности не проверяет изменения в файлах и сохраняет однажды сгенерированный кеш. + +Чтобы не приходилось на производственном сервере после каждого изменения вручную удалять кеш, включите режим разработки для вашего IP-адреса в файле `Bootstrap.php`: + +```php +$configurator->setDebugMode('ваш IP-адрес'); +``` + + +Как отключить кеш во время разработки? +-------------------------------------- +Nette умный, и вам не нужно отключать в нем кеширование. Во время разработки он автоматически обновляет кеш при каждом изменении шаблона или конфигурации DI-контейнера. Режим разработки к тому же включается автоопределением, поэтому обычно не нужно ничего настраивать, [или только IP-адрес |application:bootstrapping#Режим разработки vs режим production]. -Одна из наиболее распространенных причин - устаревший кэш. В то время как Nette умно автоматически обновляет кэш в режиме разработки, в производственном режиме он сосредоточен на максимизации производительности, и очистка кэша после каждой модификации кода зависит от вас. Попробуйте удалить `temp/cache`. +При отладке маршрутизатора рекомендуем отключить кеш в браузере, в котором могут быть сохранены, например, перенаправления: откройте Инструменты разработчика (Ctrl+Shift+I или Cmd+Option+I) и на панели Network (Сеть) отметьте отключение кеша. -Ошибка `#[\ReturnTypeWillChange] attribute should be used` .[#toc-error-returntypewillchange-attribute-should-be-used] ----------------------------------------------------------------------------------------------------------------------- -Эта ошибка возникает, если вы обновили PHP до версии 8.1, но используете Nette, который не совместим с ней. Поэтому решением является обновление Nette до более новой версии с помощью `composer update`. Nette поддерживает PHP 8.1 с версии 3.0. Если вы используете более старую версию (вы можете узнать это, посмотрев в `composer.json`), [обновите Nette |migrations:en] или оставайтесь с PHP 8.0. +Ошибка `#[\ReturnTypeWillChange] attribute should be used` +---------------------------------------------------------- +Эта ошибка появляется, если вы обновили PHP до версии 8.1, но используете Nette, которая с ней не совместима. Решением является обновление Nette до более новой версии с помощью `composer update`. Nette поддерживает PHP 8.1 начиная с версии 3.0. Если вы используете более старую версию (узнать можно, посмотрев в `composer.json`), [обновите Nette |migrations:en] или оставайтесь на PHP 8.0. -Установка прав доступа к каталогам .[#toc-setting-directory-permissions] ------------------------------------------------------------------------- -Если вы разрабатываете на macOS или Linux (или любой другой системе на базе Unix), вам необходимо настроить привилегии записи на веб-сервере. Предположим, что ваше приложение расположено в каталоге по умолчанию `/var/www/html` (Fedora, CentOS, RHEL) +Настройка прав доступа к каталогам +---------------------------------- +Если вы разрабатываете на macOS или Linux (или на любой другой системе на базе Unix), вам нужно будет настроить права на запись для веб-сервера. Предположим, ваше приложение находится в стандартном `/var/www/html` (Fedora, CentOS, RHEL). ```shell cd /var/www/html/MY_PROJECT chmod -R a+rw temp log ``` -В некоторых системах Linux (Fedora, CentOS, ...) SELinux может быть включен по умолчанию. Возможно, вам потребуется обновить политики SELinux или установить пути к каталогам `temp` и `log` с правильным контекстом безопасности SELinux. Каталоги `temp` и `log` должны быть установлены в контекст `httpd_sys_rw_content_t`; для остальной части приложения — в основном папки `app` — контекста `httpd_sys_content_t` будет достаточно. Запустите на сервере перечисленные команды от имени root: +На некоторых дистрибутивах Linux (Fedora, CentOS, ...) по умолчанию включен SELinux. Вам нужно будет соответствующим образом изменить политики SELinux и установить правильный контекст безопасности SELinux для папок `temp` и `log`. Для `temp` и `log` установим тип контекста `httpd_sys_rw_content_t`, для остальной части приложения (и особенно для папки `app`) будет достаточно `httpd_sys_content_t`. На сервере выполните: ```shell semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/log(/.*)?' @@ -43,25 +72,30 @@ semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/temp(/.*) restorecon -Rv /var/www/html/MY_PROJECT/ ``` -Далее необходимо включить булево SELinux `httpd_can_network_connect_db`, чтобы разрешить Nette подключаться к базе данных по сети. По умолчанию он отключен. Для выполнения этой задачи можно использовать команду `setsebool`, и если указана опция `-P`, эта настройка будет сохраняться при всех перезагрузках. +Далее необходимо включить булево значение SELinux `httpd_can_network_connect_db`, которое по умолчанию отключено и которое позволит Nette подключаться к базе данных по сети. Для этого используем команду `setsebool` и с опцией `-P` сделаем изменение постоянным, т.е. после перезагрузки сервера нас не будет ждать неприятный сюрприз: ```shell setsebool -P httpd_can_network_connect_db on ``` -Как изменить или удалить каталог `www` из URL? .[#toc-how-to-change-or-remove-www-directory-from-url] ------------------------------------------------------------------------------------------------------ -Директория `www/`, используемая в примерах проектов в Nette, является так называемой публичной директорией или корнем проекта. Это единственный каталог, содержимое которого доступно браузеру. В нем находится файл `index.php` - точка входа, с которой начинается веб-приложение, написанное на Nette. +Как изменить или удалить каталог www из URL? +-------------------------------------------- +Каталог `www/`, используемый в примерах проектов в Nette, представляет собой так называемый публичный каталог или document-root проекта. Это единственный каталог, содержимое которого доступно браузеру. И он содержит файл `index.php`, входную точку, которая запускает веб-приложение, написанное на Nette. -Чтобы запустить приложение на хостинге, необходимо в конфигурации хостинга установить document-root в эту директорию. Или, если на хостинге есть готовая папка для публичного каталога с другим именем (например, `web`, `public_html` и т.д.), просто переименуйте `www/`. +Для запуска приложения на хостинге необходимо правильно настроить document-root. У вас есть два варианта: +1. В конфигурации хостинга установить document-root на этот каталог. +2. Если у хостинга есть предустановленная папка (например, `public_html`), переименуйте `www/` в это название. -Решение **не** состоит в том, чтобы "избавиться" от папки `www/` с помощью правил в файле `.htaccess` или в роутере. Если хостинг не позволяет вам установить document-root в подкаталог (т.е. создавать каталоги на один уровень выше публичного каталога), поищите другой. В противном случае вы существенно рискуете безопасностью. Это похоже на жизнь в квартире, где вы не можете закрыть входную дверь, и она всегда открыта. +.[warning] +Никогда не пытайтесь решать вопросы безопасности только с помощью `.htaccess` или маршрутизатора, которые бы запрещали доступ к другим папкам. +Если хостинг не позволяет установить document-root в подкаталог (т.е. создавать каталоги на уровень выше публичного каталога), поищите другой. В противном случае вы подвергнетесь значительному риску безопасности. Это было бы как жить в квартире, где входная дверь не закрывается и всегда нараспашку. -Как настроить сервер для красивых URL? .[#toc-how-to-configure-a-server-for-nice-urls] --------------------------------------------------------------------------------------- -**Apache**: Расширение mod_rewrite должно быть разрешено и настроено в файле `.htaccess`. + +Как настроить сервер для красивых URL? +-------------------------------------- +**Apache**: необходимо включить и настроить правила mod_rewrite в файле `.htaccess`: ```apacheconf RewriteEngine On @@ -70,37 +104,65 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -Чтобы изменить конфигурацию Apache с помощью файлов .htaccess, необходимо включить директиву AllowOverride. Это поведение по умолчанию для Apache. +Если возникнут проблемы, убедитесь, что: +- файл `.htaccess` находится в каталоге document-root (то есть рядом с файлом `index.php`) +- [Apache обрабатывает файлы `.htaccess` |#Проверка работы .htaccess] +- [включен mod_rewrite |#Проверка включения mod rewrite] + +Если вы настраиваете приложение в подкаталоге, возможно, вам придется раскомментировать строку для настройки `RewriteBase` и установить ее на правильную папку. -**nginx**: директива `try_files` должна использоваться в конфигурации сервера: +**nginx**: необходимо настроить перенаправление с помощью директивы `try_files` внутри блока `location /` в конфигурации сервера. ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args важно + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args ВАЖНО! } ``` -Блок `location` должен быть определен ровно один раз для каждого пути к файловой системе в блоке `server`. Если в вашей конфигурации уже есть блок `location /`, добавьте директиву `try_files` в существующий блок. +Блок `location` для каждого пути файловой системы может встречаться в блоке `server` только один раз. Если у вас уже есть `location /` в конфигурации, добавьте директиву `try_files` в него. + + +Проверка работы .htaccess +------------------------- +Самый простой способ проверить, использует или игнорирует Apache ваш файл `.htaccess`, — это намеренно его повредить. Вставьте в начало файла строку `Test` и теперь, если вы обновите страницу в браузере, вы должны увидеть *Internal Server Error*. + +Если вы видите эту ошибку, это на самом деле хорошо! Это означает, что Apache анализирует файл `.htaccess` и натыкается на ошибку, которую мы туда вставили. Удалите строку `Test`. + +Если *Internal Server Error* не отображается, ваша настройка Apache игнорирует файл `.htaccess`. Обычно Apache игнорирует его из-за отсутствующей конфигурационной директивы `AllowOverride All`. + +Если вы хостите его сами, это легко исправить. Откройте файл `httpd.conf` или `apache.conf` в текстовом редакторе, найдите соответствующий раздел `<Directory>` и добавьте/измените эту директиву: + +```apacheconf +<Directory "/var/www/htdocs"> # путь к вашему document root + AllowOverride All + ... +``` + +Если ваш сайт размещен в другом месте, посмотрите в панели управления, можете ли вы там включить файл `.htaccess`. Если нет, обратитесь к своему хостинг-провайдеру, чтобы он сделал это за вас. + + +Проверка включения mod_rewrite +------------------------------ +Если вы убедились, что [работает `.htaccess` |#Проверка работы .htaccess], вы можете проверить, включено ли расширение mod_rewrite. Вставьте в начало файла `.htaccess` строку `RewriteEngine On` и обновите страницу в браузере. Если отображается *Internal Server Error*, это означает, что mod_rewrite не включен. Существует несколько способов его включить. Различные способы, как это можно сделать в разных настройках, можно найти на Stack Overflow. -Ссылки генерируются без `https:`. .[#toc-links-are-generated-without-https] ---------------------------------------------------------------------------- -Nette генерирует ссылки с тем же протоколом, который использует текущая страница. Таким образом, на странице `https://foo` генерируются ссылки, начинающиеся с `https:` и наоборот. -Если вы находитесь за HTTPS-стриминговым обратным прокси (например, в Docker), то вам нужно [set up a proxy|http:configuration#HTTP-Proxy] в конфигурации, чтобы определение протокола работало правильно. +Ссылки генерируются без `https:` +-------------------------------- +Nette генерирует ссылки с тем же протоколом, что и сама страница. То есть на странице `https://foo` генерирует ссылки, начинающиеся с `https:`, и наоборот. Если вы находитесь за обратным прокси-сервером, который удаляет HTTPS (например, в Docker), то необходимо в конфигурации [настроить прокси |http:configuration#HTTP-прокси], чтобы определение протокола работало правильно. -Если вы используете Nginx в качестве прокси, вам нужно настроить перенаправление следующим образом: +Если вы используете Nginx в качестве прокси, необходимо настроить перенаправление, например, так: ``` location / { proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-Port $server_port; - proxy_pass http://IP-aplikace:80; # IP или имя хоста сервера/контейнера, на котором запущено приложение. + proxy_set_header X-Forwarded-Port $server_port; + proxy_pass http://IP-aplikace:80; # IP или имя хоста сервера/контейнера, где работает приложение } ``` -Далее необходимо указать IP прокси и, если применимо, IP диапазон вашей локальной сети, где вы запускаете инфраструктуру: +Далее необходимо в конфигурацию указать IP прокси и, при необходимости, IP-диапазон вашей локальной сети, где вы разворачиваете инфраструктуру: ```neon http: @@ -108,29 +170,27 @@ http: ``` -Использование символов { } в JavaScript .[#toc-use-of-characters-in-javascript] -------------------------------------------------------------------------------- -Символы `{` и `}` используются для записи тегов Latte. Всё (кроме пробела и кавычек), следующее за символом `{`, считается тегом. Если вам нужно вывести символ `{` (часто встречается в JavaScript), вы можете поставить пробел (или другой пустой символ) сразу после `{`. Таким образом вы избежите интерпретации его как метки. +Использование символов { } в JavaScript +--------------------------------------- +Символы `{` и `}` используются для записи тегов Latte. Тегом считается все, что следует за символом `{`, за исключением пробела и кавычки. Поэтому, если вам нужно вывести непосредственно символ `{` (часто, например, в JavaScript), вы можете после символа `{` поставить пробел (или другой пустой символ). Таким образом, вы избежите интерпретации как тега. -Если необходимо вывести эти символы в ситуации, когда они будут интерпретированы как тег, вы можете использовать специальные теги для вывода этих символов - `{l}` для `{` и `{r}` для `}`. +Если необходимо вывести эти символы в ситуации, когда текст мог бы быть воспринят как тег, вы можете использовать специальные теги для вывода этих символов - `{l}` для `{` и `{r}` для `}`. ``` -{is tag} -{ is not tag } -{l}is not tag{r} +{это тег} +{ это не тег } +{l}это не тег{r} ``` -Уведомление `Presenter::getContext() is deprecated` .[#toc-notice-presenter-getcontext-is-deprecated] ------------------------------------------------------------------------------------------------------ +Сообщение `Presenter::getContext() is deprecated` +------------------------------------------------- -Nette, безусловно, первый PHP-фреймворк, который перешел на инъекцию зависимостей и заставил программистов последовательно использовать ее, начиная с ведущих. Если ведущему нужна зависимость, [он попросит ее|dependency-injection:passing-dependencies]. -В отличие от этого, способ, при котором мы передаем весь DI-контейнер классу, а он напрямую извлекает из него зависимости, считается антипаттерном (он называется service locator). -Этот способ использовался в Nette 0.x до появления инъекции зависимостей, и его реликтом является метод `Presenter::getContext()`, давно помеченный как deprecated. +Nette является, безусловно, первым PHP-фреймворком, который перешел на внедрение зависимостей и побуждал программистов к его последовательному использованию, начиная с самих презентеров. Если презентеру нужна какая-то зависимость, он [заявляет о ней|dependency-injection:passing-dependencies]. Напротив, подход, когда в класс передается весь DI-контейнер, и он сам извлекает из него зависимости, считается антипаттерном (называется service locator). Этот способ использовался в Nette 0.x еще до появления внедрения зависимостей, и его пережитком является метод `Presenter::getContext()`, давно помеченный как устаревший (deprecated). -Если вы портируете очень старое приложение Nette, вы можете обнаружить, что оно все еще использует этот метод. Таким образом, начиная с версии 3.1 `nette/application` вы столкнетесь с предупреждением `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection`, начиная с версии 4.0 вы столкнетесь с ошибкой, что метод не существует. +Если вы портируете очень старое приложение для Nette, вы можете столкнуться с тем, что оно все еще использует этот метод. Начиная с версии `nette/application` 3.1, вы столкнетесь с предупреждением `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection`, а с версии 4.0 — с ошибкой, что метод не существует. -Разумеется, чистым решением является перепроектирование приложения для передачи зависимостей с помощью инъекции зависимостей. В качестве обходного пути вы можете добавить свой собственный метод `getContext()` в базовый презентер и обойти сообщение: +Чистым решением, разумеется, является переделка приложения таким образом, чтобы зависимости передавались с помощью внедрения зависимостей. В качестве обходного пути вы можете добавить в свой базовый презентер собственный метод `getContext()` и таким образом обойти сообщение: ```php abstract BasePresenter extends Nette\Application\UI\Presenter diff --git a/nette/ru/vulnerability-protection.texy b/nette/ru/vulnerability-protection.texy index 74d023728b..c4a812e25c 100644 --- a/nette/ru/vulnerability-protection.texy +++ b/nette/ru/vulnerability-protection.texy @@ -2,21 +2,21 @@ ********************* .[perex] -Время от времени объявляется о серьезных недостатках в системе безопасности или даже злоупотреблениях. Конечно, это немного неприятно. Если вы заботитесь о безопасности своих веб-приложений, фреймворк Nette — это, безусловно, лучший выбор для вас. +То и дело сообщается о дыре в безопасности на очередном крупном сайте или об использовании этой дыры. Это неприятно. Если вам важна безопасность ваших веб-приложений, Nette Framework, безусловно, является лучшим выбором. -Межсайтовый скриптинг (XSS) .[#toc-cross-site-scripting-xss] -============================================================ +Межсайтовый скриптинг (XSS) +=========================== -Межсайтовый скриптинг — это метод нарушения работы сайта с использованием неэкранированного ввода. Злоумышленник может внедрить свой собственный код HTML или JavaScript и изменить внешний вид страницы или даже собрать конфиденциальную информацию о пользователях. Защита от XSS проста: последовательное и правильное экранирование всех строк и вводимых данных. Традиционно было достаточно, если ваш кодер допустил всего одну малейшую ошибку, забыл один раз, и весь сайт может оказаться под угрозой. +Межсайтовый скриптинг — это метод взлома веб-страниц, использующий необработанные выводы. Злоумышленник может внедрить в страницу свой собственный код и тем самым изменить страницу или даже получить конфиденциальные данные о посетителях. Защититься от XSS можно только путем последовательной и корректной обработки всех строк. При этом достаточно, чтобы ваш верстальщик всего лишь один раз это упустил, и весь сайт может быть мгновенно скомпрометирован. -Примером такой инъекции может быть передача пользователю измененного URL-адреса, который вставляет «вредоносный» сценарий в файл. Если приложение не экранирует свои входные данные должным образом, такой запрос может привести к выполнению сценария на стороне клиента. Это может, например, привести к краже личных данных. +Примером атаки может быть подсовывание пользователю измененного URL, с помощью которого мы внедряем в страницу свой код. Если приложение не будет должным образом обрабатывать выводы, оно выполнит скрипт в браузере пользователя. Таким образом, мы можем, например, украсть его личность. ``` -http://example.com/?search=<script>alert('XSS attack.');</script> +https://example.com/?search=<script>alert('Успешная XSS атака.');</script> ``` -Фреймворк Nette предлагает совершенно новую технологию [Context-Aware Escaping |latte:safety-first#Context-Aware-Escaping], которая навсегда избавит вас от рисков межсайтового скриптинга. Он автоматически экранирует все вводимые данные в зависимости от заданного контекста, поэтому кодер не сможет случайно что-то забыть. В качестве примера рассмотрим следующий шаблон: +Nette Framework предлагает революционную технологию [Context-Aware Escaping |latte:safety-first#Контекстно-зависимое экранирование], которая навсегда избавит вас от риска Cross-Site Scripting. Все выводы обрабатываются автоматически, и верстальщик не может что-то забыть. Пример? Верстальщик создает этот шаблон: ```latte <p onclick="alert({$message})">{$message}</p> @@ -26,29 +26,29 @@ document.title = {$message}; </script> ``` -Команда `{$message}` выводит переменную. Другие фреймворки часто заставляют разработчиков явно декларировать экранирование и даже тип экранирования в зависимости от контекста. Однако в Nette вам не нужно ничего декларировать. Всё происходит автоматически, последовательно и точно. Если мы установим переменную в `$message = 'Width 1/2"'`, фреймворк сгенерирует такой HTML-код: +Запись `{$message}` означает вывод переменной. В других фреймворках необходимо каждый вывод явно обрабатывать, причем в каждом месте по-разному. В Nette Framework не нужно ничего обрабатывать, все делается автоматически, правильно и последовательно. Если мы подставим в переменную `$message = 'Ширина 1/2"'`, фреймворк сгенерирует HTML-код: ```latte -<p onclick="alert("Width 1\/2\"")">Width 1/2"</p> +<p onclick="alert("Ширина 1\/2\"")">Ширина 1/2"</p> <script> -document.title = "Width 1\/2\""; +document.title = "Ширина 1\/2\""; </script> ``` -Подделка межсайтовых запросов (CSRF) .[#toc-cross-site-request-forgery-csrf] -============================================================================ +Межсайтовая подделка запроса (CSRF) +=================================== -Атака Cross-Site Request Forgery заключается в том, что злоумышленник заманивает жертву посетить страницу, которая молча выполняет запрос в браузере жертвы к серверу, на котором жертва в данный момент зарегистрирована, и сервер считает, что запрос был сделан жертвой по собственному желанию. Сервер выполняет определенное действие под личностью жертвы, но без её ведома. Это может быть изменение или удаление данных, отправка сообщения и т. д. +Атака Cross-Site Request Forgery заключается в том, что злоумышленник заманивает жертву на страницу, которая незаметно в браузере жертвы выполняет запрос на сервер, на котором жертва авторизована, и сервер полагает, что запрос был выполнен жертвой по собственной воле. Таким образом, под личностью жертвы выполняется определенное действие без ее ведома. Это может быть изменение или удаление данных, отправка сообщения и т. д. -Nette **автоматически защищает формы и сигналы в презентерах** от этого типа атак. Это делается путем предотвращения их отправки или вызова из другого домена. Чтобы отключить защиту, используйте для форм следующее: +Nette Framework **автоматически защищает формы и сигналы в презентерах** от этого типа атак. Это достигается путем предотвращения их отправки или вызова из другого домена. Если вы хотите отключить защиту, используйте для форм: ```php $form->allowCrossOrigin(); ``` -или, в случае сигнала, добавьте аннотацию `@crossOrigin`: +или в случае сигнала добавьте аннотацию `@crossOrigin`: ```php /** @@ -59,42 +59,41 @@ public function handleXyz() } ``` -В PHP 8 вы также можете использовать атрибуты: +В Nette Application 3.2 вы также можете использовать атрибуты: ```php -use Nette\Application\Attributes\CrossOrigin; +use Nette\Application\Attributes\Requires; -#[CrossOrigin] +#[Requires(sameOrigin: false)] public function handleXyz() { } ``` -Атака на URL, управляющие коды, неправильный UTF-8 .[#toc-url-attack-control-codes-invalid-utf-8] -================================================================================================= +Атаки URL, управляющие коды, неверный UTF-8 +=========================================== -Различные термины, связанные с попыткой злоумышленника сделать ваше приложение «вредоносным». Результаты могут сильно различаться, от неполного вывода XML (т. е. сбой в работе RSS-потока) до получения конфиденциальной информации из базы данных и получения паролей пользователей. Защитой от этих атак является последовательная проверка UTF-8 на уровне байтов. И, откровенно говоря, вы бы не стали делать этого без рамок, верно? +Различные термины, связанные с попыткой злоумышленника подсунуть вашему веб-приложению *вредоносный* ввод. Последствия могут быть самыми разнообразными, от повреждения XML-выводов (например, неработающие RSS-каналы) до получения конфиденциальной информации из базы данных или паролей. Защита заключается в последовательной обработке всех входов на уровне отдельных байтов. И положа руку на сердце, кто из вас это делает? -Фреймворк Nette делает это за вас автоматически. Вам не нужно ничего настраивать, и ваше приложение будет в безопасности. +Nette Framework делает это за вас, причем автоматически. Вам не нужно ничего настраивать, и все входы будут обработаны. -Перехват сеанса, кража сеанса, фиксация сеанса .[#toc-session-hijacking-session-stealing-session-fixation] -========================================================================================================== +Перехват сессии, кража сессии, фиксация сессии +============================================== -Управление сеансами включает в себя несколько типов атак. Злоумышленник может украсть идентификатор сессии жертвы или подделать его и таким образом получить доступ к веб-приложению без фактического пароля. Тогда злоумышленник может делать всё, что мог пользователь, без каких-либо следов. Защита заключается в правильной настройке как PHP, так и самого веб-сервера. +С управлением сессиями связано несколько типов атак. Злоумышленник либо крадет, либо подсовывает пользователю свой ID сессии и благодаря этому получает доступ к веб-приложению, не зная пароля пользователя. Затем он может делать в приложении что угодно, без ведома пользователя. Защита заключается в правильной конфигурации сервера и PHP. -Nette настраивает PHP автоматически. Таким образом, разработчикам не нужно беспокоиться о том, как сделать сессию достаточно защищенной, и они могут полностью сосредоточиться на ключевых частях приложения. Для этого необходимо включить функцию `ini_set()`. +При этом Nette Framework настраивает PHP автоматически. Программисту не нужно думать, как правильно защитить сессию, и он может полностью сосредоточиться на создании приложения. Однако для этого требуется включенная функция `ini_set()`. -Куки SameSite .[#toc-samesite-cookie] -===================================== +SameSite cookie +=============== -Куки SameSite обеспечивают механизм распознавания того, что привело к загрузке страницы. Что абсолютно необходимо по соображениям безопасности. +SameSite cookies предоставляют механизм для распознавания того, что привело к загрузке страницы. Что абсолютно необходимо для безопасности. -Флаг SameSite может иметь три значения: `Lax`, `Strict` и `None` (требуется HTTPS). Если запрос на страницу поступает непосредственно с самого сайта или пользователь открывает страницу, непосредственно введя её в адресную строку или нажав на закладку, браузер отправляет все куки на сервер (т. е. с флагами `Lax`, `Strict` и `None`). Если пользователь попадает на сайт по ссылке с другого сайта, на сервер передаются файлы куки с флагами `Lax` и `None`. Если запрос возникает в другом месте, например, отправка POST-формы из другого источника, загрузка внутри iframe, использование JavaScript и т. д., будут отправлены только куки с флагом `None`. - -По умолчанию Nette отправляет все куки с флагом `Lax`. +Флаг SameSite может иметь три значения: `Lax`, `Strict` и `None` (последнее требует HTTPS). Если запрос на страницу поступает непосредственно с сайта, или пользователь открывает страницу, вводя адрес напрямую в адресную строку или кликая по закладке, браузер отправляет серверу все куки (т.е. с флагами `Lax`, `Strict` и `None`). Если пользователь переходит на сайт по ссылке с другого сайта, серверу передаются куки с флагами `Lax` и `None`. Если запрос возникает другим способом, например, отправкой POST-формы с другого сайта, загрузкой внутри iframe, с помощью JavaScript и т.д., отправляются только куки с флагом `None`. +Nette по умолчанию все куки отправляет с флагом `Lax`. {{leftbar: www:@menu-common}} diff --git a/nette/sl/@home.texy b/nette/sl/@home.texy index bafe6b5d10..f3176c2230 100644 --- a/nette/sl/@home.texy +++ b/nette/sl/@home.texy @@ -1,4 +1,4 @@ -Nette Dokumentacija +Dokumentacija Nette ******************* <div class=documentation> @@ -6,45 +6,45 @@ Nette Dokumentacija <div> -Uvod ----- -- [Zakaj uporabljati Nette |www:10-reasons-why-nette]? -- [Namestitev |Installation] -- [Ustvarite svojo prvo aplikacijo |quickstart:]! +Spoznavanje +----------- +- [Zakaj uporabljati Nette? |www:10-reasons-why-nette] +- [Namestitev |installation] +- [Napišimo prvo aplikacijo! |quickstart:] Splošno ------- - [Seznam paketov |www:packages] -- [Vzdrževanje in PHP |www:maintenance] -- [Opombe k izdaji |https://nette.org/releases] -- [Vodnik za nadgradnjo |migrations:en] -- [nette:Odpravljanje težav |nette:Troubleshooting] +- [Vzdrževanje in različice PHP |www:maintenance] +- [Opombe ob izdaji |https://nette.org/releases] +- [Prehod na novejše različice|migrations:en] +- [Odpravljanje težav |nette:troubleshooting] - [Kdo ustvarja Nette |https://nette.org/contributors] -- [Zgodovina družbe Nette |www:history] -- [Vključite se |contributing:] -- [Razvoj sponzorjev |https://nette.org/en/donate] -- [Sklic na API |https://api.nette.org/] +- [Zgodovina Nette |www:history] +- [Pridružite se |contributing:] +- [Podprite razvoj |https://nette.org/cs/donate] +- [API reference |https://api.nette.org/] </div> <div> -Aplikacija Nette ----------------- -- [Kako delujejo aplikacije |application:how-it-works]? -- [Bootstrap |application:Bootstrap] -- [Presenters |application:Presenters] -- [Predloge |application:Templates] -- [Moduli |application:Modules] -- [Usmerjanje |application:Routing] -- [Ustvarjanje povezav URL |application:creating-links] +Aplikacije v Nette +------------------ +- [Kako delujejo aplikacije? |application:how-it-works] +- [Bootstrapping |application:Bootstrapping] +- [Presenterji |application:presenters] +- [Predloge |application:templates] +- [Struktura imenikov |application:directory-structure] +- [Usmerjanje |application:routing] +- [Ustvarjanje URL povezav |application:creating-links] - [Interaktivne komponente |application:components] -- [AJAX in sličice |application:ajax] +- [AJAX & odrezki |application:ajax] -- [Najboljše prakse |best-practices:] +- [Navodila in postopki |best-practices:] </div> @@ -54,48 +54,49 @@ Aplikacija Nette Glavne teme ----------- - [Konfiguracija |nette:configuring] -- [Injekcija odvisnosti (Dependency Injection) |dependency-injection:] -- [Latte: Predloge |latte:] -- [Tracy: Orodje za odpravljanje napak |tracy:] +- [Dependency Injection|dependency-injection:] +- [Latte: predloge |latte:] +- [Tracy: razhroščevanje kode |tracy:] - [Obrazci |forms:] -- [Podatkovna baza |database:core] -- [Preverjanje pristnosti uporabnikov |security:authentication] -- [Nadzor dostopa |security:authorization] +- [Podatkovna baza |database:guide] +- [Prijava uporabnikov |security:authentication] +- [Preverjanje dovoljenj |security:authorization] - [Seje |http:Sessions] -- [Zahteva in odziv HTTP |http:] -- [Predpomnjenje |caching:] +- [HTTP zahteva & odgovor|http:] +- [Sredstva |assets:] +- [Predpomnilnik |caching:] - [Pošiljanje e-pošte |mail:] -- [Shema: Potrjevanje podatkov |schema:] -- [Generator kode PHP |php-generator:] -- [Tester: Testiranje enot |tester:] +- [Schema: validacija podatkov |schema:] +- [Generator PHP kode |php-generator:] +- [Tester: testiranje |tester:] </div> <div> -Storitve --------- -- [Arrays |utils:Arrays] +Pripomočki +---------- +- [Polja |utils:arrays] - [Datotečni sistem |utils:filesystem] -- [Iskalnik |utils:finder] -- [HTML Elementi |utils:HTML Elements] -- [Slike |utils:Images] +- [Finder |utils:finder] +- [HTML elementi |utils:html-elements] +- [Slike |utils:images] - [JSON |utils:JSON] -- [NEON |neon:] -- [Hashing gesel |security:passwords] -- [SmartObject |utils:SmartObject] -- [Tipi PHP |utils:type] -- [Nizi |utils:Strings] +- [NEON|neon:] +- [Zgoščevanje gesel |security:passwords] +- [PHP tipi |utils:type] +- [Nizi |utils:strings] - [Validatorji |utils:validators] - [RobotLoader |robot-loader:] +- [SmartObject |utils:smartobject] & [StaticClass |utils:StaticClass] - [SafeStream |safe-stream:] -- [...drugi |utils:] +- [...ostalo |utils:] </div> </div> {{toc:no}} -{{description: Uradna dokumentacija Nette: opisuje delovanje Nette in najboljše prakse za razvoj spletnih aplikacij.}} +{{description: Uradna dokumentacija Nette: opisuje, kako Nette deluje in najboljše prakse za razvoj spletnih aplikacij.}} {{maintitle: Dokumentacija Nette}} diff --git a/nette/sl/@menu-topics.texy b/nette/sl/@menu-topics.texy index 54e53daf37..2baa1fcbba 100644 --- a/nette/sl/@menu-topics.texy +++ b/nette/sl/@menu-topics.texy @@ -1,21 +1,21 @@ Glavne teme *********** - [Konfiguracija |nette:configuring] -- [Neto aplikacija |application:how-it-works] -- [Injekcija odvisnosti (Dependency Injection) |dependency-injection:] -- [Orodja |utils:] +- [Aplikacije v Nette |application:how-it-works] +- [Dependency Injection|dependency-injection:] +- [Pripomočki |utils:] - [Obrazci |forms:] -- [Podatkovna baza |database:core] -- [Preverjanje pristnosti uporabnikov |security:authentication] -- [Nadzor dostopa |security:authorization] +- [Podatkovna baza |database:guide] +- [Prijava uporabnikov |security:authentication] +- [Preverjanje dovoljenj |security:authorization] - [Seje |http:Sessions] -- [Zahteva in odziv HTTP |http:] -- [Predpomnjenje |caching:] +- [HTTP zahteva & odgovor|http:] +- [Predpomnilnik |caching:] - [Pošiljanje e-pošte |mail:] -- [Shema: Potrjevanje podatkov |schema:] -- [Generator kode PHP |php-generator:] +- [Schema: validacija podatkov |schema:] +- [Generator PHP kode |php-generator:] - [Latte: predloge |latte:] -- [Tracy: razhroščevanje |tracy:] +- [Tracy: razhroščevanje kode |tracy:] - [Tester: testiranje |tester:] diff --git a/nette/sl/@meta.texy b/nette/sl/@meta.texy new file mode 100644 index 0000000000..724324bee5 --- /dev/null +++ b/nette/sl/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokumentacija}} diff --git a/nette/sl/configuring.texy b/nette/sl/configuring.texy index 51ff3ae1c3..212d45b1a7 100644 --- a/nette/sl/configuring.texy +++ b/nette/sl/configuring.texy @@ -1,35 +1,36 @@ -Konfiguriranje omrežja Nette -**************************** +Konfiguracija Nette +******************* .[perex] -Pregled vseh možnosti konfiguracije v ogrodju Nette. +Pregled vseh konfiguracijskih možnosti v Nette Frameworku. -Komponente Nette se konfigurirajo s konfiguracijskimi datotekami, ki so običajno napisane v jeziku [NEON |neon:format]. Najbolje jih je urejati v [urejevalnikih, ki to podpirajo |best-practices:editors-and-tools#ide-editor]. -Če uporabljate celotno ogrodje, se konfiguracija [naloži med zagonom |application:bootstrap#di-container-configuration], če ne, si oglejte, [kako naložiti konfiguracijo |bootstrap:]. +Komponente Nette nastavljamo s pomočjo konfiguracijskih datotek, ki se običajno zapisujejo v [formatu NEON|neon:format]. Najbolje se urejajo v [urejevalnikih z njegovo podporo |best-practices:editors-and-tools#IDE urejevalnik]. Če uporabljate celotno ogrodje, se konfiguracija [naloži ob zagonu aplikacije |application:bootstrapping#Konfiguracija DI vsebnika], če ne, preberite, [kako konfiguracijo naložiti|bootstrap:]. <pre> "application .[prism-token prism-atrule]":[application:configuration#Application]: "Aplikacija .[prism-token prism-comment]"<br> -"constants .[prism-token prism-atrule]":[application:configuration#Constants]: "Opredeljuje konstante PHP .[prism-token prism-comment]"<br> +"assets .[prism-token prism-atrule]":[assets:configuration]: "Assets .[prism-token prism-comment]"<br> +"constants .[prism-token prism-atrule]":[application:configuration#Konstante]: "Definicija PHP konstant .[prism-token prism-comment]"<br> "database .[prism-token prism-atrule]":[database:configuration]: "Podatkovna baza .[prism-token prism-comment]"<br> "decorator .[prism-token prism-atrule]":[dependency-injection:configuration#Decorator]: "Dekorator .[prism-token prism-comment]"<br> -"di .[prism-token prism-atrule]":[dependency-injection:configuration#DI]: "DI Container .[prism-token prism-comment]"<br> -"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Extensions]: "Namesti dodatne razširitve DI .[prism-token prism-comment]"<br> -"forms .[prism-token prism-atrule]":[forms:configuration]: "Forms .[prism-token prism-comment]"<br> -"http .[prism-token prism-atrule]":[http:configuration#HTTP Headers]: "Glave HTTP .[prism-token prism-comment]"<br> -"includes .[prism-token prism-atrule]":[dependency-injection:configuration#Including files]: "Vključuje datoteke .[prism-token prism-comment]"<br> -"latte .[prism-token prism-atrule]":[application:configuration#Latte]: "Latte .[prism-token prism-comment]"<br> -"mail .[prism-token prism-atrule]":[mail:#Configuring]: "Pošiljanje .[prism-token prism-comment]"<br> -"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#Parameters]: "Parametri .[prism-token prism-comment]"<br> -"php .[prism-token prism-atrule]":[application:configuration#PHP]: "Možnosti konfiguracije PHP .[prism-token prism-comment]"<br> -"routing .[prism-token prism-atrule]":[application:configuration#Routing]: "Usmerjanje .[prism-token prism-comment]"<br> -"search .[prism-token prism-atrule]":[dependency-injection:configuration#Search]: "Samodejna registracija storitev .[prism-token prism-comment]"<br> -"security .[prism-token prism-atrule]":[security:configuration]: "Nadzor dostopa .[prism-token prism-comment]"<br> +"di .[prism-token prism-atrule]":[dependency-injection:configuration#DI]: "DI vsebnik .[prism-token prism-comment]"<br> +"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Razširitve]: "Namestitev dodatnih DI razširitev .[prism-token prism-comment]"<br> +"forms .[prism-token prism-atrule]":[forms:configuration]: "Obrazci .[prism-token prism-comment]"<br> +"http .[prism-token prism-atrule]":[http:configuration#Glave HTTP]: "HTTP glave .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[dependency-injection:configuration#Vključevanje datotek]: "Vstavljanje datotek .[prism-token prism-comment]"<br> +"latte .[prism-token prism-atrule]":[application:configuration#Predloge Latte]: "Predloge Latte .[prism-token prism-comment]"<br> +"mail .[prism-token prism-atrule]":[mail:#Konfiguracija]: "E-pošta .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#Parametri]: "Parametri .[prism-token prism-comment]"<br> +"php .[prism-token prism-atrule]":[application:configuration#PHP]: "PHP konfiguracija .[prism-token prism-comment]"<br> +"routing .[prism-token prism-atrule]":[application:configuration#Usmerjanje]: "Usmerjanje .[prism-token prism-comment]"<br> +"search .[prism-token prism-atrule]":[dependency-injection:configuration#Iskanje]: "Samodejna registracija storitev .[prism-token prism-comment]"<br> +"security .[prism-token prism-atrule]":[security:configuration]: "Dovoljenja za dostop .[prism-token prism-comment]"<br> "services .[prism-token prism-atrule]":[dependency-injection:services]: "Storitve .[prism-token prism-comment]"<br> -"session .[prism-token prism-atrule]":[http:configuration#Session]: "Seja .[prism-token prism-comment]"<br> -"tracy .[prism-token prism-atrule]":[tracy:configuring#Nette Framework]: "Tracy Debugger .[prism-token prism-comment]" +"session .[prism-token prism-atrule]":[http:configuration#Seja]: "Seja .[prism-token prism-comment]"<br> +"tracy .[prism-token prism-atrule]":[tracy:configuring#Nette Framework]: "Tracy razhroščevalnik .[prism-token prism-comment]" </pre> -Zapisovanje niza, ki vsebuje znak `%`, you must escape it by doubling it to `%%`. .[note] +.[note] +Če želite zapisati niz, ki vsebuje znak `%`, ga morate ubežati z podvojitvijo na `%%`. {{leftbar: @menu-topics}} diff --git a/nette/sl/glossary.texy b/nette/sl/glossary.texy index 5a501ddebd..1f92bede75 100644 --- a/nette/sl/glossary.texy +++ b/nette/sl/glossary.texy @@ -1,155 +1,159 @@ -Glosar izrazov -************** +Slovarček pojmov +**************** -AJAX .[#toc-ajax] ------------------ -Asinhroni JavaScript in XML - tehnologija za komunikacijo med odjemalcem in strežnikom prek protokola HTTP brez potrebe po ponovnem nalaganju celotne strani med vsako zahtevo. Kljub kratici se namesto XML pogosto uporablja format [JSON |#JSON]. +AJAX +---- +Asinhroni JavaScript in XML - tehnologija izmenjave informacij med odjemalcem in strežnikom preko protokola HTTP brez potrebe po ponovnem nalaganju celotne strani ob vsaki zahtevi. Čeprav bi se lahko iz imena zdelo, da pošilja podatke le v formatu XML, se običajno uporablja tudi format [#JSON]. -Akcija predavatelja .[#toc-presenter-action] --------------------------------------------- -Logični del [predstavnika |#presenter], ki izvaja eno dejanje, na primer prikaže stran izdelka, odjavi uporabnika itd. En predstavnik ima lahko več dejanj. +Akcija presenterja +------------------ +Logični del presenterja, ki izvaja eno akcijo. Na primer prikaže stran izdelka, odjavi uporabnika ipd. En presenter lahko ima več akcij. BOM --- -Tako imenovana *maska vrstnega reda bajtov* je poseben prvi znak datoteke in označuje vrstni red bajtov v kodiranju. Nekateri urejevalniki jo vključijo samodejno in je praktično nevidna, vendar povzroča težave pri glavi in pošiljanju izpisa znotraj PHP. Za množično odstranjevanje lahko uporabite [program Code Checker |code-checker:]. +T.i. *byte order mark* (oznaka vrstnega reda bajtov) je poseben prvi znak v datoteki, ki se uporablja kot indikator vrstnega reda bajtov v kodiranju. Nekateri urejevalniki ga vstavljajo v datoteke. Je praktično neviden, vendar povzroča težave s pošiljanjem izpisa in glav iz PHP. Za množično odstranjevanje lahko uporabite [Code Checker|code-checker:]. -Nadzornik .[#toc-controller] ----------------------------- -Krmilnik obdeluje zahteve uporabnika in na njihovi podlagi pokliče določeno aplikacijsko logiko (tj. [model |#model]), nato pa pokliče [pogled |#view] za prikazovanje podatkov. Analogija kontrolerjem so [predstavniki |#presenter] v okolju Nette Framework. +Controller +---------- +Krmilnik, ki obdeluje zahteve uporabnika in na njihovi podlagi nato kliče ustrezno aplikacijsko logiko (tj. [#model]) ter nato zahteva od [pogleda |#View] izrisovanje podatkov. Ustreznik krmilnikov v Nette Frameworku so [presenterji |#Presenter]. -Navzkrižno pisanje na spletnem mestu (XSS) .[#toc-cross-site-scripting-xss] ---------------------------------------------------------------------------- -Cross-Site Scripting je metoda motenja spletnega mesta z uporabo neoznačenega vnosa. Napadalec lahko vnese lastno kodo HTML ali JavaScript in spremeni videz strani ali celo zbere občutljive podatke o uporabnikih. Zaščita pred XSS je preprosta: dosledno in pravilno eskapiranje vseh nizov in vnosov. +Cross-Site Scripting (XSS) +-------------------------- +Cross-Site Scripting je metoda kršitve spletnih strani z zlorabo neobdelanih izpisov. Napadalec nato lahko v stran podtakne svojo lastno kodo in s tem lahko stran spremeni ali celo pridobi občutljive podatke o obiskovalcih. Proti XSS se je mogoče braniti le z dosledno in pravilno obdelavo vseh nizov. -Okvir Nette je na voljo povsem nova tehnologija [Context-Aware Escaping |latte:safety-first#context-aware-escaping], s katero se boste za vedno znebili tveganj Cross-Site Scripting. Na podlagi danega konteksta samodejno eskapira vse vhode, zato je nemogoče, da bi programer na kaj pomotoma pozabil. +Nette Framework prinaša revolucionarno tehnologijo [Context-Aware Escaping |latte:safety-first#Kontekstno občutljivo ubežanje], ki vas za vedno odpravi tveganja Cross-Site Scriptinga. Vse izpise namreč obdeluje samodejno in tako se ne more zgoditi, da bi koder na kaj pozabil. -Križanje spletnih strani (CSRF) .[#toc-cross-site-request-forgery-csrf] ------------------------------------------------------------------------ -Napad Cross-Site Request Forgery pomeni, da napadalec žrtev zvabi k obisku strani, ki v brskalniku žrtve tiho izvede zahtevo v strežnik, v katerega je žrtev trenutno prijavljena, strežnik pa verjame, da je zahtevo po svoji volji izvedla žrtev. Strežnik izvede določeno dejanje pod identiteto žrtve, vendar se žrtev tega ne zaveda. To je lahko spreminjanje ali brisanje podatkov, pošiljanje sporočila itd. +Cross-Site Request Forgery (CSRF) +--------------------------------- +Napad Cross-Site Request Forgery temelji na tem, da napadalec zvabi žrtev na stran, ki neopazno v brskalniku žrtve izvede zahtevo na strežnik, na katerem je žrtev prijavljena, in strežnik domneva, da je zahtevo izvedla žrtev po svoji volji. In tako pod identiteto žrtve izvede določeno dejanje, ne da bi ta vedela o tem. Lahko gre za spremembo ali brisanje podatkov, pošiljanje sporočila itd. -Okvir Nette **samodejno ščiti obrazce in signale v predstavitvah** pred tovrstnimi napadi. To stori tako, da prepreči njihovo pošiljanje ali klicanje iz druge domene. +Nette Framework **samodejno ščiti obrazce in signale v presenterjih** pred to vrsto napada. In to s tem, da preprečuje njihovo pošiljanje ali klicanje iz druge domene. -Vključevanje odvisnosti (Dependency Injection) .[#toc-dependency-injection] ---------------------------------------------------------------------------- -Vbrizgavanje odvisnosti (Dependency Injection, DI) je načrtovalski vzorec, ki določa, kako ločiti ustvarjanje objektov od njihovih odvisnosti. To pomeni, da razred ni odgovoren za ustvarjanje ali inicializacijo svojih odvisnosti, temveč te odvisnosti zagotovi zunanja koda (ki lahko vključuje [vsebnik DI |#Dependency Injection container]). Prednost tega je, da omogoča večjo prilagodljivost kode, boljšo berljivost in lažje testiranje aplikacije, saj so odvisnosti zlahka zamenljive in ločene od drugih delov kode. Za več informacij glejte [Kaj je vbrizgavanje odvisnosti |dependency-injection:introduction]? +Dependency Injection +-------------------- +Dependency Injection (DI) je načrtovalski vzorec, ki pravi, kako ločiti ustvarjanje objektov od njihovih odvisnosti. Torej da razred ni odgovoren za ustvarjanje ali inicializacijo svojih odvisnosti, ampak so mu namesto tega te odvisnosti zagotovljene s strani zunanje kode (to je lahko tudi [DI vsebnik |#Dependency Injection vsebnik]). Prednost temelji na tem, da omogoča večjo fleksibilnost kode, boljšo razumljivost in lažje testiranje aplikacije, ker so odvisnosti lahko nadomestljive in izolirane od drugih delov kode. Več v poglavju [Kaj je Dependency Injection? |dependency-injection:introduction] -Zabojnik za vbrizgavanje odvisnosti .[#toc-dependency-injection-container] --------------------------------------------------------------------------- -Posoda za vbrizgavanje odvisnosti (tudi posoda DI ali posoda IoC) je orodje, ki skrbi za ustvarjanje in upravljanje odvisnosti v aplikaciji (ali [storitvah |#service]). Vsebnik ima običajno konfiguracijo, ki določa, kateri razredi so odvisni od drugih razredov, katere posebne implementacije odvisnosti je treba uporabiti in kako ustvariti te odvisnosti. Vsebnik nato ustvari te objekte in jih zagotovi razredom, ki jih potrebujejo. Za več informacij glejte [Kaj je vsebnik DI |dependency-injection:container]? +Dependency Injection vsebnik +---------------------------- +Dependency Injection vsebnik (tudi DI vsebnik ali IoC vsebnik) je orodje, ki skrbi za ustvarjanje in upravljanje odvisnosti v aplikaciji (ali [storitev |#Služba]). Vsebnik ima večinoma konfiguracijo, ki definira, kateri razredi so odvisni od drugih razredov, katere konkretne implementacije odvisnosti se naj uporabijo in kako se naj te odvisnosti ustvarjajo. Nato vsebnik ustvari te objekte in jih zagotovi razredom, ki jih potrebujejo. Več v poglavju [Kaj je DI vsebnik? |dependency-injection:container] -Izogibanje .[#toc-escaping] ---------------------------- -Escaping je pretvorba znakov s posebnim pomenom v danem kontekstu v drugo enakovredno zaporedje. Primer: Želimo zapisati narekovaje v niz z narekovaji. Ker imajo narekovaji v kontekstu z narekovaji zaprtega niza poseben pomen, je treba uporabiti drugo enakovredno zaporedje. Konkretno zaporedje določajo pravila konteksta (npr. `\"` v PHP-jevem nizu z narekovaji, `"` v atributih HTML itd.). +Ubežanje znakov / Escaping +-------------------------- +Ubežanje znakov je pretvorba znakov, ki imajo v danem kontekstu poseben pomen, v druge ustrezne zaporedje. Primer: v niz, omejen z narekovaji, želimo zapisati narekovaje. Ker imajo narekovaji v kontekstu niza poseben pomen in bi bil njihov preprost zapis razumljen kot konec niza, jih je treba zapisati z drugim ustreznim zaporedjem. Kakšnim natančno, določajo pravila konteksta. -Filter (prej pomočnik) .[#toc-filter-formerly-helper] ------------------------------------------------------ -Funkcija filtra. V predlogah je [filter |latte:syntax#filters] funkcija, ki pomaga spremeniti ali oblikovati podatke v izhodno obliko. Predloge imajo vnaprej definiranih več [standardnih filtrov |latte:filters]. +Filter (prej helper) +-------------------- +V predlogah se pod pojmom [filter |latte:syntax#Filtri] običajno razume funkcija, ki pomaga urediti ali preoblikovati podatke v končno obliko. Predloge razpolagajo z več [standardnimi filtri |latte:filters]. -Invalidacija .[#toc-invalidation] ---------------------------------- -Obvestilo o [snippetu |#snippet] za ponovno prikazovanje. V drugem kontekstu tudi brisanje predpomnilnika. +Invalidacija +------------ +Obvestilo [snippetu |#Snippet], naj se ponovno izriše. V drugem pomenu tudi brisanje vsebine predpomnilnika. -JSON .[#toc-json] ------------------ -Format za izmenjavo podatkov, ki temelji na sintaksi JavaScript (je njena podmnožica). Natančna specifikacija je na voljo na naslovu www.json.org. +JSON +---- +Format za izmenjavo podatkov, ki izhaja iz sintakse JavaScripta (je njena podmnožica). Natančno specifikacijo najdete na strani www.json.org. -Komponenta .[#toc-component] ----------------------------- -Del aplikacije, ki ga je mogoče ponovno uporabiti. Lahko je vizualni del strani, kot je opisano v poglavju [application:components], lahko pa izraz pomeni tudi razred [Component |component-model:] (ni nujno, da je takšna komponenta vizualna). +Komponenta +---------- +Ponovno uporabna komponenta aplikacije. Lahko je vizualni del strani, kot opisuje poglavje [Pišemo komponente |application:components], ali pa se pod pojmom komponenta razume tudi razred [Component |component-model:] (takšna komponenta ni nujno vizualna). -Kontrolni znaki .[#toc-control-characters] ------------------------------------------- -Kontrolni znaki so nevidni znaki, ki se lahko pojavijo v besedilu in sčasoma povzročijo nekaj težav. Za njihovo množično odstranjevanje iz datotek lahko uporabite [program Code Checker |code-checker:], za njihovo odstranjevanje iz spremenljivke pa funkcijo [Strings::normalize() |utils:strings#normalize]. +Kontrolni znaki +--------------- +Kontrolni znaki so nevidni znaki, ki se lahko pojavljajo v besedilu in morebiti tudi povzročajo težave. Za njihovo množično odstranjevanje iz datotek lahko uporabite [Code Checker|code-checker:] in za odstranjevanje iz spremenljivke funkcijo [Strings::normalize() |utils:strings#normalize]. -Dogodki .[#toc-events] ----------------------- -Dogodek je pričakovana situacija v objektu, ko se zgodi, se pokličejo tako imenovani handlerji, tj. povratni klici, ki se odzovejo na dogodek ("vzorec":https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38). Dogodek je lahko na primer oddaja obrazca, prijava uporabnika itd. Dogodki so torej oblika *inverzije nadzora*. +Dogodki (eventi) +---------------- +Dogodek je pričakovana situacija v objektu, ki ko nastane, se pokličejo t.i. obdelovalci, torej povratni klici, ki se odzivajo na dogodek ("primer":https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38). Dogodek je lahko na primer pošiljanje obrazca, prijava uporabnika itd. Dogodki so tako oblika *Inversion of Control* (Obrat nadzora). -Na primer, prijava uporabnika se zgodi v metodi `Nette\Security\User::login()`. Objekt `User` ima javno spremenljivko `$onLoggedIn`, ki je polje, v katerega lahko vsakdo doda povratni klic. Takoj ko se uporabnik prijavi, metoda `login()` pokliče vse povratne klice v polju. Ime spremenljivke v obliki `onXyz` je konvencija, ki se uporablja v celotnem sistemu Nette. +Na primer, prijava uporabnika se dogaja v metodi `Nette\Security\User::login()`. Objekt `User` ima javno spremenljivko `$onLoggedIn`, kar je polje, v katerega lahko kdorkoli doda povratni klic. V trenutku, ko se uporabnik prijavi, metoda `login()` pokliče vse povratne klice v polju. Ime spremenljivke v obliki `onXyz` je konvencija, ki se uporablja v celotnem Nette. -Latte .[#toc-latte] -------------------- -Eden najbolj inovativnih [sistemov za oblikovanje predlog |latte:] doslej. +Latte +----- +Eden najnaprednejših [sistemov predlog |latte:]. -Model .[#toc-model] -------------------- -Model predstavlja podatkovno in funkcijsko osnovo celotne aplikacije. Vključuje celotno logiko aplikacije (včasih imenovano tudi "poslovna logika"). To je **M** **M**VC ali MPV. Vsako dejanje uporabnika (prijava, dajanje stvari v košarico, sprememba vrednosti podatkovne zbirke) predstavlja dejanje modela. +Model +----- +Model je podatkovna in predvsem funkcionalna osnova celotne aplikacije. V njem je vsebovana celotna aplikacijska logika (uporablja se tudi izraz poslovna logika). Je tisti **M** iz **M**VC ali MVP. Kakršnakoli akcija uporabnika (prijava, dodajanje izdelka v košarico, sprememba vrednosti v podatkovni bazi) predstavlja akcijo modela. -Model upravlja svoje notranje stanje in zagotavlja javni vmesnik. S klicem tega vmesnika lahko prevzamemo ali spremenimo njegovo stanje. Model ne ve za obstoj [pogleda |#view] ali [krmilnika |#controller], model je od njiju popolnoma neodvisen. +Model upravlja svoje notranje stanje in navzven ponuja trdno določen vmesnik. S klicanjem funkcij tega vmesnika lahko ugotavljamo ali spreminjamo njegovo stanje. Model ne ve za obstoj [pogleda |#View] ali [krmilnika |#Controller]. -Model-pogled-kontroler .[#toc-model-view-controller] ----------------------------------------------------- -Programska arhitektura, ki se je pojavila pri razvoju aplikacij grafičnega vmesnika, da bi ločila kodo za nadzor pretoka ([kontroler |#controller]) od kode aplikacijske logike ([model) |#model] in kode za prikazovanje podatkov ([pogled |#view]). Na ta način je koda bolje razumljiva, olajša nadaljnji razvoj in omogoča ločeno testiranje ločenih delov. +Model-View-Controller +--------------------- +Programska arhitektura, ki je nastala iz potrebe po ločitvi kode za obdelavo ([krmilnik |#Controller]) od kode aplikacijske logike ([#model]) in od kode za prikaz podatkov ([pogled |#View]) pri aplikacijah z grafičnim vmesnikom. S tem aplikacijo naredi bolj pregledno, olajšuje prihodnji razvoj in omogoča testiranje posameznih delov ločeno. -Model-pogled-predstavnik .[#toc-model-view-presenter] ------------------------------------------------------ -Arhitektura, ki temelji na [Model-View-Controller |#Model-View-Controller]. +Model-View-Presenter +-------------------- +Arhitektura, ki izhaja iz [#Model-View-Controller]. -Modul .[#toc-module] --------------------- -[Modul |application:modules] v ogrodju Nette predstavlja zbirko predstavnikov in predlog, lahko tudi komponent in modelov, ki predstavljajo podatke za predstavnika. Tako je določen logični del aplikacije. +Modul +----- +Modul predstavlja logični del aplikacije. V tipični ureditvi gre za skupino presenterjev in predlog, ki rešujejo določeno področje funkcionalnosti. Module postavljamo v [samostojne mape |application:directory-structure#Presenterji in predloge], kot npr. `Front/`, `Admin/` ali `Shop/`. -Na primer, e-trgovina ima lahko tri module: -1) Katalog izdelkov s košarico. -2) Administracija za stranko. -3) Administracija za lastnika trgovine. +Na primer, spletno trgovino razdelimo na: +- Frontend (`Shop/`) za pregledovanje izdelkov in nakup +- Odsek za stranke (`Customer/`) za upravljanje naročil +- Administracijo (`Admin/`) za upravljavca +Tehnično gre za običajne mape, ki pa zahvaljujoč pregledni razdelitvi pomagajo skalirati aplikacijo. Presenter `Admin:Product:List` bo tako fizično nameščen na primer v mapi `app/Presentation/Admin/Product/List/` (glej [preslikavo presenterjev |application:directory-structure#Mapiranje presenterjev]). -Imenski prostor .[#toc-namespace] ---------------------------------- -Prostor imen je značilnost jezika PHP od različice 5.3 naprej in tudi nekaterih drugih programskih jezikov. Pomaga preprečiti kolizijo imen (npr. dva razreda z istim imenom) pri skupni uporabi različnih knjižnic. Za več podrobnosti glejte [dokumentacijo PHP |https://www.php.net/manual/en/language.namespaces.rationale.php]. +Namespace +--------- +Imenski prostor, del jezika PHP od različice 5.3 in nekaterih drugih programskih jezikov, ki omogoča uporabo razredov, ki so v različnih knjižnicah enako poimenovani, ne da bi prišlo do konflikta imen. Glej [dokumentacijo PHP |https://www.php.net/manual/en/language.namespaces.rationale.php]. -Predavatelj .[#toc-presenter] ------------------------------ -Presenter je objekt, ki prevzame [zahtevo, |api:Nette\Application\Request] kot jo usmerjevalnik prevede iz zahteve HTTP, in ustvari [odgovor |api:Nette\Application\Response]. Odgovor je lahko stran HTML, slika, dokument XML, datoteka, JSON, preusmeritev ali kar koli si zamislite. -S predstavnikom je običajno mišljen potomec razreda [api:Nette\Application\UI\Presenter]. Z zahtevami izvaja ustrezna [dejanja |application:presenters#life-cycle-of-presenter] in izrisuje predloge. +Presenter +--------- +Presenter je objekt, ki vzame [zahtevo |api:Nette\Application\Request], prevedeno z usmerjevalnikom iz HTTP zahteve, in generira [odgovor |api:Nette\Application\Response]. Odgovor je lahko HTML stran, slika, XML dokument, datoteka na disku, JSON, preusmeritev ali karkoli si zamislite. +Običajno se pod pojmom presenter misli na potomca razreda [api:Nette\Application\UI\Presenter]. Glede na dohodne zahteve poganja ustrezne [akcije |application:presenters#Življenjski cikel presenterja] in izrisuje predloge. -Usmerjevalnik .[#toc-router] ----------------------------- -Dvosmerni prevajalnik med zahtevo HTTP/naslovom URL in dejanjem predstavnika. Dvosmerno pomeni, da ni mogoče le izpeljati [predstavitvene akcije |#presenter action] iz zahteve HTTP, temveč tudi ustvariti ustrezen URL za akcijo. Več informacij najdete v poglavju o [usmerjanju URL |application:routing]. + +Router +------ +Dvosmerni prevajalnik med HTTP zahtevo / URL in akcijo presenterja. Dvosmerno pomeni, da je mogoče iz HTTP zahteve izpeljati [akcijo presenterja |#Akcija presenterja], pa tudi obratno k akciji generirati ustrezen URL. Več v poglavju o [usmerjanju URL |application:routing]. + + +SameSite cookie +--------------- +SameSite piškotki zagotavljajo mehanizem za prepoznavanje, kaj je vodilo k nalaganju strani. Lahko ima tri vrednosti: `Lax`, `Strict` in `None` (ta zahteva HTTPS). Če zahteva za stran prihaja neposredno s spletnega mesta ali uporabnik odpre stran z neposrednim vnosom v naslovno vrstico ali klikom na zaznamek, brskalnik pošlje strežniku vse piškotke (torej z zastavicami `Lax`, `Strict` in `None`). Če se uporabnik na spletno mesto preklikne preko povezave z drugega spletnega mesta, se strežniku posredujejo piškotki z zastavicami `Lax` in `None`. Če zahteva nastane na drug način, kot je pošiljanje POST obrazca z drugega spletnega mesta, nalaganje znotraj iframe, s pomočjo JavaScripta, itd., se pošljejo samo piškotki z zastavico `None`. -Piškotek SameSite .[#toc-samesite-cookie] ------------------------------------------ -Piškotki SameSite zagotavljajo mehanizem za prepoznavanje, kaj je privedlo do nalaganja strani. Ima lahko tri vrednosti: `Lax`, `Strict` in `None` (slednja zahteva HTTPS). Če zahteva po strani prihaja neposredno s spletnega mesta ali če uporabnik odpre stran z neposrednim vnosom v naslovno vrstico ali klikom na zaznamek, brskalnik pošlje strežniku vse piškotke (tj. z oznakami `Lax`, `Strict` in `None`). Če uporabnik klikne na spletno mesto prek povezave z drugega spletnega mesta, se strežniku posredujejo piškotki z zastavicama `Lax` in `None`. Če je zahteva poslana na drug način, na primer z oddajo obrazca POST z drugega spletnega mesta, nalaganjem znotraj iframe, uporabo JavaScripta itd., se pošljejo samo piškotki z zastavico `None`. +Služba +------ +V kontekstu Dependency Injection se kot storitev označuje objekt, ki je ustvarjen in upravljan s strani DI vsebnika. Storitev je mogoče enostavno nadomestiti z drugo implementacijo, na primer za namene testiranja ali za spremembo obnašanja aplikacije, ne da bi bilo treba urejati kodo, ki storitev uporablja. -Storitev .[#toc-service] ------------------------- -V kontekstu vrivanja odvisnosti se storitev nanaša na objekt, ki ga ustvari in upravlja vsebnik DI. Storitev je mogoče preprosto zamenjati z drugo izvedbo, na primer za namene testiranja ali za spremembo obnašanja aplikacije, ne da bi bilo treba spreminjati kodo, ki uporablja storitev. +Snippet +------- +Izrezek, del strani, ki ga je mogoče samostojno ponovno izrisati med AJAX zahtevo. -Utrinek .[#toc-snippet] ------------------------ -Odlomek strani, ki se lahko ločeno ponovno prikaže med zahtevo [AJAX |#AJAX]. +View +---- +View, torej pogled, je plast aplikacije, ki je zadolžena za prikaz rezultata zahteve. Običajno uporablja sistem predlog in ve, kako se naj prikaže ta ali ona komponenta ali rezultat, pridobljen iz modela. -Pogled .[#toc-view] -------------------- -Pogled je plast aplikacije, ki je odgovorna za upodabljanje rezultatov zahtevkov. Običajno uporablja sistem šablon in ve, kako naj prikaže svoje komponente ali rezultate, prevzete iz modela. diff --git a/nette/sl/installation.texy b/nette/sl/installation.texy index b7d5e84ccc..b662ef230e 100644 --- a/nette/sl/installation.texy +++ b/nette/sl/installation.texy @@ -1,67 +1,67 @@ -Namestitev sistema Nette -************************ +Namestitev Nette +**************** .[perex] -Ali želite izkoristiti prednosti sistema Nette v obstoječem projektu ali nameravate ustvariti nov projekt, ki bo temeljil na sistemu Nette? Ta vodnik vas bo po korakih popeljal skozi namestitev. +Želite izkoriščati prednosti Nette v svojem obstoječem projektu ali se nameravate ustvariti nov projekt, ki temelji na Nette? Ta vodnik vas bo vodil skozi namestitev korak za korakom. -Kako dodati Nette v svoj projekt .[#toc-how-to-add-nette-to-your-project] -------------------------------------------------------------------------- +Kako dodati Nette v svoj projekt +-------------------------------- -Nette ponuja zbirko uporabnih in izpopolnjenih paketov (knjižnic) za PHP. Če jih želite vključiti v svoj projekt, sledite naslednjim korakom: +Nette ponuja zbirko koristnih in naprednih paketov (knjižnic) za PHP. Za njihovo vključitev v vaš projekt postopajte na naslednji način: -1) **Nastavi [program Composer |best-practices:composer]:** To orodje je nujno za enostavno namestitev, posodabljanje in upravljanje knjižnic, potrebnih za vaš projekt. +1) **Pripravite si [Composer|best-practices:composer]:** To orodje je nujno za enostavno namestitev, posodobitev in upravljanje knjižnic, potrebnih za vaš projekt. -2) **Izbira [paketa |www:packages]:** Recimo, da morate krmariti po datotečnem sistemu, kar odlično počne [Finder |utils:finder] iz paketa `nette/utils`. Ime paketa lahko najdete v desnem stolpcu njegove dokumentacije. +2) **Izberite si [paket|www:packages]:** Recimo, da potrebujete brskanje po datotečnem sistemu, kar odlično opravlja [Finder|utils:finder] iz paketa `nette/utils`. Ime paketa vidite v desnem stolpcu njegove dokumentacije. -3) **Ustanovite paket:** Ta ukaz zaženite v korenskem imeniku svojega projekta: +3) **Namestite paket:** Zagnite ta ukaz v korenski mapi vašega projekta: ```shell composer require nette/utils ``` -Ali imate raje grafični vmesnik? Oglejte si [vodnik za |https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] nameščanje paketov v okolje PhpStrom. +Preferirate grafični vmesnik? Oglejte si [navodila|https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] za namestitev paketov v okolju PhpStorm. -Kako začeti nov projekt z Nette .[#toc-how-to-start-a-new-project-with-nette] ------------------------------------------------------------------------------ +Kako ustvariti nov projekt z Nette +---------------------------------- -Če želite ustvariti popolnoma nov projekt na platformi Nette, vam priporočamo, da uporabite prednastavljeno ogrodje [spletnega projekta |https://github.com/nette/web-project]: +Če želite ustvariti popolnoma nov projekt na platformi Nette, priporočamo uporabo vnaprej nastavljenega ogrodja [Web Project|https://github.com/nette/web-project]: -1) **Nastavi [program Composer |best-practices:composer].** +1) **Pripravite si [Composer|best-practices:composer].** -2) **Odprite ukazno vrstico** in se pomaknite v korenski imenik spletnega strežnika, npr. `/etc/var/www`, `C:/xampp/htdocs`, `/Library/WebServer/Documents`. +2) **Odprite ukazno vrstico** in preidite v korensko mapo vašega spletnega strežnika, npr. `/etc/var/www`, `C:/xampp/htdocs`, `/Library/WebServer/Documents`. -3) **S tem ukazom ustvarite projekt**: +3) **Ustvarite projekt** s pomočjo tega ukaza: ```shell -composer create-project nette/web-project PROJECT_NAME +composer create-project nette/web-project IME_PROJEKTA ``` -4) **Ne uporabljate programa Composer?** Prenesite [spletni projekt v formatu ZIP |https://github.com/nette/web-project/archive/preloaded.zip] in ga razpršite. Vendar nam zaupajte, Composer je vreden tega! +4) **Ne uporabljate Composerja?** Samo prenesite si [Web Project v formatu ZIP|https://github.com/nette/web-project/archive/preloaded.zip] in ga razširite. Ampak verjemite, Composer je vreden tega! -5) **Nastavitev dovoljenj:** V sistemih macOS ali Linux nastavite [dovoljenja za pisanje v |nette:troubleshooting#Setting directory permissions] imenike. +5) **Nastavitev dovoljenj:** Na sistemih macOS ali Linux nastavite [dovoljenja za pisanje |nette:troubleshooting#Nastavitev pravic map] v mape. -6) **Odprite projekt v brskalniku:** Vnesite naslov URL `http://localhost/PROJECT_NAME/www/`. Videli boste pristajalno stran ogrodja: +6) **Odpiranje projekta v brskalniku:** Vnesite URL `http://localhost/IME_PROJEKTA/www/` in videli boste uvodno stran ogrodja: -[* qs-welcome.webp .{url: http://localhost/PROJECT_NAME/www/} *] +[* qs-welcome.webp .{url: http://localhost/IME_PROJEKTA/www/} *] -Čestitamo! Vaše spletno mesto je zdaj pripravljeno za razvoj. Odstranite predlogo dobrodošlice in začnite graditi svojo aplikacijo. +Čestitamo! Vaše spletno mesto je zdaj pripravljeno za razvoj. Pozdravno predlogo lahko odstranite in začnete ustvarjati svojo aplikacijo. -Ena od prednosti Nette je, da projekt deluje takoj, brez potrebe po konfiguraciji. Če pa naletite na kakršne koli težave, razmislite o [pogostih rešitvah težav |nette:troubleshooting#nette-is-not-working-white-page-is-displayed]. +Ena od prednosti Nette je, da projekt deluje takoj brez potrebe po konfiguraciji. Če pa naletite na težave, poskusite pogledati [rešitve pogostih težav |nette:troubleshooting#Nette mi ne deluje prikazuje se bela stran]. .[note] -Če začenjate z aplikacijo Nette, priporočamo, da nadaljujete z [učnim gradivom Ustvarite svojo prvo aplikacijo |quickstart:]. +Če začenjate z Nette, priporočamo nadaljevanje z [vodičem Pišemo prvo aplikacijo|quickstart:]. -Orodja in priporočila .[#toc-tools-and-recommendations] -------------------------------------------------------- +Orodja in priporočila +--------------------- Za učinkovito delo z Nette priporočamo naslednja orodja: -- [Visokokakovostne IDE z vtičniki za Nette |best-practices:editors-and-tools] -- Sistem za nadzor različic Git -- [Composer |best-practices:composer] +- [Kakovosten IDE z dodatki za Nette|best-practices:editors-and-tools] +- Sistem za upravljanje različic Git +- [Composer|best-practices:composer] {{leftbar: www:@menu-common}} diff --git a/nette/sl/introduction-to-object-oriented-programming.texy b/nette/sl/introduction-to-object-oriented-programming.texy new file mode 100644 index 0000000000..57c71a8b4e --- /dev/null +++ b/nette/sl/introduction-to-object-oriented-programming.texy @@ -0,0 +1,841 @@ +Uvod v objektno usmerjeno programiranje +*************************************** + +.[perex] +Izraz "OOP" označuje objektno usmerjeno programiranje, kar je način organiziranja in strukturiranja kode. OOP nam omogoča, da program vidimo kot zbirko objektov, ki med seboj komunicirajo, namesto zaporedja ukazov in funkcij. + +V OOP je "objekt" enota, ki vsebuje podatke in funkcije, ki delujejo s temi podatki. Objekti so ustvarjeni po "razredih", ki jih lahko razumemo kot načrte ali predloge za objekte. Ko imamo razred, lahko ustvarimo njegovo "instanco", kar je konkreten objekt, ustvarjen po tem razredu. + +Poglejmo si, kako lahko ustvarimo preprost razred v PHP. Pri definiranju razreda uporabimo ključno besedo "class", ki ji sledi ime razreda, nato pa zaviti oklepaji, ki obdajajo funkcije (imenovane "metode") in spremenljivke razreda (imenovane "lastnosti" ali angleško "property"): + +```php +class Avto +{ + function potrobi() + { + echo 'Bip bip!'; + } +} +``` + +V tem primeru smo ustvarili razred z imenom `Avto` z eno funkcijo (ali "metodo"), imenovano `potrobi`. + +Vsak razred bi moral reševati samo eno glavno nalogo. Če razred počne preveč stvari, ga je morda primerno razdeliti na manjše, specializirane razrede. + +Razrede običajno shranjujemo v ločene datoteke, da je koda organizirana in se v njej lahko enostavno orientiramo. Ime datoteke bi moralo ustrezati imenu razreda, tako da bi za razred `Avto` ime datoteke bilo `Avto.php`. + +Pri poimenovanju razredov je dobro slediti konvenciji "PascalCase", kar pomeni, da se vsaka beseda v imenu začne z veliko začetnico in med njimi ni podčrtajev ali drugih ločil. Metode in lastnosti uporabljajo konvencijo "camelCase", kar pomeni, da se začnejo z malo začetnico. + +Nekatere metode v PHP imajo posebne naloge in so označene s predpono `__` (dva podčrtaja). Ena najpomembnejših posebnih metod je "konstruktor", ki je označen kot `__construct`. Konstruktor je metoda, ki se samodejno pokliče, ko ustvarite novo instanco razreda. + +Konstruktor pogosto uporabljamo za nastavitev začetnega stanja objekta. Na primer, ko ustvarjate objekt, ki predstavlja osebo, lahko uporabite konstruktor za nastavitev njene starosti, imena ali drugih lastnosti. + +Poglejmo si, kako uporabiti konstruktor v PHP: + +```php +class Oseba +{ + private $starost; + + function __construct($starost) + { + $this->starost = $starost; + } + + function kolikoSiStar() + { + return $this->starost; + } +} + +$oseba = new Oseba(25); +echo $oseba->kolikoSiStar(); // Izpiše: 25 +``` + +V tem primeru ima razred `Oseba` lastnost (spremenljivko) `$starost` in konstruktor, ki nastavi to lastnost. Metoda `kolikoSiStar()` nato omogoča dostop do starosti osebe. + +Psevdo-spremenljivka `$this` se uporablja znotraj razreda za dostop do lastnosti in metod objekta. + +Ključna beseda `new` se uporablja za ustvarjanje nove instance razreda. V zgornjem primeru smo ustvarili novo osebo s starostjo 25. + +Lahko nastavite tudi privzete vrednosti za parametre konstruktorja, če niso določene pri ustvarjanju objekta. Na primer: + +```php +class Oseba +{ + private $starost; + + function __construct($starost = 20) + { + $this->starost = $starost; + } + + function kolikoSiStar() + { + return $this->starost; + } +} + +$oseba = new Oseba; // če ne posredujemo nobenega argumenta, lahko oklepaje izpustimo +echo $oseba->kolikoSiStar(); // Izpiše: 20 +``` + +V tem primeru, če ne določite starosti pri ustvarjanju objekta `Oseba`, bo uporabljena privzeta vrednost 20. + +Prijetno je, da se definicija lastnosti z njeno inicializacijo preko konstruktorja lahko tako skrajša in poenostavi: + +```php +class Oseba +{ + function __construct( + private $starost = 20, + ) { + } +} +``` + +Za popolnost, poleg konstruktorjev imajo lahko objekti tudi destruktorje (metoda `__destruct`), ki se pokličejo, preden se objekt sprosti iz pomnilnika. + + +Imenski prostori +---------------- + +Imenski prostori (ali "namespaces" v angleščini) nam omogočajo organiziranje in združevanje povezanih razredov, funkcij in konstant, hkrati pa se izogibamo konfliktom v imenih. Lahko si jih predstavljate kot mape v računalniku, kjer vsaka mapa vsebuje datoteke, ki pripadajo določenemu projektu ali temi. + +Imenski prostori so še posebej uporabni pri večjih projektih ali ko uporabljate knjižnice tretjih oseb, kjer bi lahko prišlo do konfliktov v imenih razredov. + +Predstavljajte si, da imate v svojem projektu razred z imenom `Avto` in ga želite umestiti v imenski prostor, imenovan `Doprava`. To storite takole: + +```php +namespace Doprava; + +class Avto +{ + function potrobi() + { + echo 'Bip bip!'; + } +} +``` + +Če želite uporabiti razred `Avto` v drugi datoteki, morate določiti, iz katerega imenskega prostora razred izvira: + +```php +$avto = new Doprava\Avto; +``` + +Za poenostavitev lahko na začetku datoteke navedete, kateri razred iz danega imenskega prostora želite uporabljati, kar omogoča ustvarjanje instanc brez potrebe po navajanju celotne poti: + +```php +use Doprava\Avto; + +$avto = new Avto; +``` + + +Dedovanje +--------- + +Dedovanje je orodje objektno usmerjenega programiranja, ki omogoča ustvarjanje novih razredov na podlagi že obstoječih razredov, prevzemanje njihovih lastnosti in metod ter njihovo razširjanje ali ponovno definiranje po potrebi. Dedovanje omogoča zagotavljanje ponovne uporabnosti kode in hierarhije razredov. + +Poenostavljeno rečeno, če imamo en razred in bi želeli ustvariti drugega, iz njega izpeljanega, vendar z nekaj spremembami, lahko nov razred "podedujemo" iz prvotnega razreda. + +V PHP dedovanje izvedemo s ključno besedo `extends`. + +Naš razred `Oseba` hrani informacijo o starosti. Lahko imamo drug razred `Student`, ki razširja `Osebo` in dodaja informacijo o smeri študija. + +Poglejmo si primer: + +```php +class Oseba +{ + private $starost; + + function __construct($starost) + { + $this->starost = $starost; + } + + function izpisiInformacije() + { + echo "Starost: {$this->starost} let\n"; + } +} + +class Student extends Oseba +{ + private $smer; + + function __construct($starost, $smer) + { + parent::__construct($starost); + $this->smer = $smer; + } + + function izpisiInformacije() + { + parent::izpisiInformacije(); + echo "Smer študija: {$this->smer} \n"; + } +} + +$student = new Student(20, 'Informatika'); +$student->izpisiInformacije(); +``` + +Kako ta koda deluje? + +- Uporabili smo ključno besedo `extends` za razširitev razreda `Oseba`, kar pomeni, da razred `Student` podeduje vse metode in lastnosti iz `Osebe`. + +- Ključna beseda `parent::` nam omogoča klicanje metod iz nadrejenega razreda. V tem primeru smo klicali konstruktor iz razreda `Oseba`, preden smo dodali lastno funkcionalnost v razred `Student`. Podobno smo klicali tudi metodo `izpisiInformacije()` prednika, preden smo izpisali informacije o študentu. + +Dedovanje je namenjeno situacijam, ko obstaja odnos "je" med razredi. Na primer, `Student` je `Oseba`. Mačka je žival. Omogoča nam, da v primerih, ko v kodi pričakujemo en objekt (npr. "Oseba"), namesto njega uporabimo podedovani objekt (npr. "Student"). + +Pomembno je vedeti, da glavni namen dedovanja **ni** preprečevanje podvajanja kode. Nasprotno, nepravilna uporaba dedovanja lahko vodi do zapletene in težko vzdrževane kode. Če odnos "je" med razredi ne obstaja, bi morali namesto dedovanja razmisliti o kompoziciji. + +Opazite, da metodi `izpisiInformacije()` v razredih `Oseba` in `Student` izpisujeta nekoliko drugačne informacije. In lahko dodamo druge razrede (na primer `Zaposleni`), ki bodo zagotavljali druge implementacije te metode. Sposobnost objektov različnih razredov, da se na isto metodo odzovejo na različne načine, se imenuje polimorfizem: + +```php +$osebe = [ + new Oseba(30), + new Student(20, 'Informatika'), + new Zaposleni(45, 'Direktor'), // Predpostavimo, da razred Zaposleni obstaja +]; + +foreach ($osebe as $oseba) { + $oseba->izpisiInformacije(); +} +``` + + +Kompozicija +----------- + +Kompozicija je tehnika, pri kateri namesto da podedujemo lastnosti in metode drugega razreda, preprosto uporabimo njegovo instanco v našem razredu. To nam omogoča kombiniranje funkcionalnosti in lastnosti več razredov brez potrebe po ustvarjanju zapletenih dednih struktur. + +Poglejmo si primer. Imamo razred `Motor` in razred `Avto`. Namesto da bi rekli "Avto je Motor", rečemo "Avto ima Motor", kar je tipičen odnos kompozicije. + +```php +class Motor +{ + function vklopi() + { + echo 'Motor teče.'; + } +} + +class Avto +{ + private $motor; + + function __construct() + { + $this->motor = new Motor; + } + + function zazeni() + { + $this->motor->vklopi(); + echo 'Avto je pripravljen na vožnjo!'; + } +} + +$avto = new Avto; +$avto->zazeni(); +``` + +Tukaj `Avto` nima vseh lastnosti in metod `Motorja`, vendar ima dostop do njega preko lastnosti `$motor`. + +Prednost kompozicije je večja fleksibilnost pri oblikovanju in boljša možnost prilagajanja v prihodnosti. + + +Vidnost +------- + +V PHP lahko definirate "vidnost" za lastnosti, metode in konstante razreda. Vidnost določa, od kod lahko dostopate do teh elementov. + +1. **Public:** Če je element označen kot `public`, pomeni, da lahko do njega dostopate od kjerkoli, tudi zunaj razreda. + +2. **Protected:** Element z oznako `protected` je dostopen samo znotraj danega razreda in vseh njegovih potomcev (razredov, ki dedujejo od tega razreda). + +3. **Private:** Če je element `private`, lahko do njega dostopate samo znotraj razreda, v katerem je bil definiran. + +Če ne določite vidnosti, jo PHP samodejno nastavi na `public`. + +Poglejmo si vzorčno kodo: + +```php +class PrimerVidnosti +{ + public $javnaLastnost = 'Javna'; + protected $zascitenaLastnost = 'Zaščitena'; + private $zasebnaLastnost = 'Zasebna'; + + public function izpisiLastnosti() + { + echo $this->javnaLastnost; // Deluje + echo $this->zascitenaLastnost; // Deluje + echo $this->zasebnaLastnost; // Deluje + } +} + +$objekt = new PrimerVidnosti; +$objekt->izpisiLastnosti(); +echo $objekt->javnaLastnost; // Deluje +// echo $objekt->zascitenaLastnost; // Javi napako +// echo $objekt->zasebnaLastnost; // Javi napako +``` + +Nadaljujemo z dedovanjem razreda: + +```php +class PotomecRazreda extends PrimerVidnosti +{ + public function izpisiLastnosti() + { + echo $this->javnaLastnost; // Deluje + echo $this->zascitenaLastnost; // Deluje + // echo $this->zasebnaLastnost; // Javi napako + } +} +``` + +V tem primeru lahko metoda `izpisiLastnosti()` v razredu `PotomecRazreda` dostopa do javnih in zaščitenih lastnosti, ne more pa dostopati do zasebnih lastnosti starševskega razreda. + +Podatki in metode bi morali biti čim bolj skriti in dostopni samo preko definiranega vmesnika. To vam omogoča spreminjanje interne implementacije razreda brez vpliva na preostalo kodo. + + +Ključna beseda `final` +---------------------- + +V PHP lahko uporabimo ključno besedo `final`, če želimo preprečiti, da bi bil razred, metoda ali konstanta podedovana ali prepisana. Ko označimo razred kot `final`, ga ni mogoče razširiti. Ko označimo metodo kot `final`, je ni mogoče prepisati v potomskem razredu. + +Zavedanje, da določen razred ali metoda ne bo dalje spreminjana, nam omogoča lažje izvajanje prilagoditev, ne da bi se morali bati možnih konfliktov. Na primer, lahko dodamo novo metodo brez skrbi, da bi kateri koli njen potomec že imel metodo z istim imenom in bi prišlo do trka. Ali pa lahko metodi spremenimo njene parametre, saj spet ni nevarnosti, da bi povzročili neskladje s prepisano metodo v potomcu. + +```php +final class KoncniRazred +{ +} + +// Naslednja koda bo javila napako, ker ne moremo dedovati od končnega razreda. +class PotomecKoncnegaRazreda extends KoncniRazred +{ +} +``` + +V tem primeru bo poskus dedovanja od končnega razreda `KoncniRazred` javil napako. + + +Statične lastnosti in metode +---------------------------- + +Ko v PHP govorimo o "statičnih" elementih razreda, mislimo na metode in lastnosti, ki pripadajo samemu razredu in ne konkretni instanci tega razreda. To pomeni, da vam ni treba ustvariti instance razreda, da bi imeli dostop do njih. Namesto tega jih kličete ali dostopate do njih neposredno preko imena razreda. + +Upoštevajte, da ker statični elementi pripadajo razredu in ne njegovim instancam, znotraj statičnih metod ne morete uporabljati psevdo-spremenljivke `$this`. + +Uporaba statičnih lastnosti vodi do [nepregledni kodi, polni pasti|dependency-injection:global-state], zato jih ne bi smeli nikoli uporabiti in tukaj tudi ne bomo prikazali primera uporabe. Nasprotno pa so statične metode uporabne. Primer uporabe: + +```php +class Kalkulator +{ + public static function sestevanje($a, $b) + { + return $a + $b; + } + + public static function odstevanje($a, $b) + { + return $a - $b; + } +} + +// Uporaba statične metode brez ustvarjanja instance razreda +echo Kalkulator::sestevanje(5, 3); // Rezultat: 8 +echo Kalkulator::odstevanje(5, 3); // Rezultat: 2 +``` + +V tem primeru smo ustvarili razred `Kalkulator` z dvema statičnima metodama. Te metode lahko kličemo neposredno brez ustvarjanja instance razreda z uporabo operatorja `::`. Statične metode so še posebej uporabne za operacije, ki niso odvisne od stanja konkretne instance razreda. + + +Razredne konstante +------------------ + +Znotraj razredov imamo možnost definirati konstante. Konstante so vrednosti, ki se nikoli ne spremenijo med izvajanjem programa. Za razliko od spremenljivk, vrednost konstante ostaja vedno enaka. + +```php +class Avto +{ + public const SteviloKoles = 4; + + public function prikaziSteviloKoles(): int + { + echo self::SteviloKoles; + } +} + +echo Avto::SteviloKoles; // Izpis: 4 +``` + +V tem primeru imamo razred `Avto` s konstanto `SteviloKoles`. Ko želimo dostopati do konstante znotraj razreda, lahko uporabimo ključno besedo `self` namesto imena razreda. + + +Objektni vmesniki +----------------- + +Objektni vmesniki delujejo kot "pogodbe" za razrede. Če mora razred implementirati objektni vmesnik, mora vsebovati vse metode, ki jih ta vmesnik definira. To je odličen način za zagotovitev, da določeni razredi upoštevajo isto "pogodbo" ali strukturo. + +V PHP se vmesnik definira s ključno besedo `interface`. Vse metode, definirane v vmesniku, so javne (`public`). Ko razred implementira vmesnik, uporablja ključno besedo `implements`. + +```php +interface Zival +{ + function izdajZvok(); +} + +class Macka implements Zival +{ + public function izdajZvok() + { + echo 'Mijav'; + } +} + +$macka = new Macka; +$macka->izdajZvok(); +``` + +Če razred implementira vmesnik, vendar v njem niso definirane vse pričakovane metode, bo PHP javil napako. + +Razred lahko implementira več vmesnikov hkrati, kar je razlika v primerjavi z dedovanjem, kjer lahko razred deduje samo od enega razreda: + +```php +interface Varuh +{ + function varujHiso(); +} + +class Pes implements Zival, Varuh +{ + public function izdajZvok() + { + echo 'Hov'; + } + + public function varujHiso() + { + echo 'Pes skrbno varuje hišo'; + } +} +``` + + +Abstraktni razredi +------------------ + +Abstraktni razredi služijo kot osnovne predloge za druge razrede, vendar njihovih instanc ne morete ustvariti neposredno. Vsebujejo kombinacijo popolnih metod in abstraktnih metod, ki nimajo definirane vsebine. Razredi, ki dedujejo od abstraktnih razredov, morajo zagotoviti definicije za vse abstraktne metode iz prednika. + +Za definiranje abstraktnega razreda uporabljamo ključno besedo `abstract`. + +```php +abstract class AbstraktniRazred +{ + public function navadnaMetoda() + { + echo 'To je navadna metoda'; + } + + abstract public function abstraktnaMetoda(); +} + +class Potomec extends AbstraktniRazred +{ + public function abstraktnaMetoda() + { + echo 'To je implementacija abstraktne metode'; + } +} + +$instanca = new Potomec; +$instanca->navadnaMetoda(); +$instanca->abstraktnaMetoda(); +``` + +V tem primeru imamo abstraktni razred z eno navadno in eno abstraktno metodo. Nato imamo razred `Potomec`, ki deduje od `AbstraktniRazred` in zagotavlja implementacijo za abstraktno metodo. + +Kako se pravzaprav razlikujejo vmesniki in abstraktni razredi? Abstraktni razredi lahko vsebujejo tako abstraktne kot konkretne metode, medtem ko vmesniki samo definirajo, katere metode mora razred implementirati, vendar ne zagotavljajo nobene implementacije. Razred lahko deduje samo od enega abstraktnega razreda, lahko pa implementira poljubno število vmesnikov. + + +Preverjanje tipov +----------------- + +V programiranju je zelo pomembno imeti gotovost, da so podatki, s katerimi delamo, pravilnega tipa. V PHP imamo orodja, ki nam to zagotavljajo. Preverjanje, ali imajo podatki pravilen tip, se imenuje "preverjanje tipov". + +Tipi, na katere lahko naletimo v PHP: + +1. **Osnovni tipi**: Vključujejo `int` (cela števila), `float` (decimalna števila), `bool` (logične vrednosti), `string` (nizi), `array` (polja) in `null`. +2. **Razredi**: Če želimo, da je vrednost instanca specifičnega razreda. +3. **Vmesniki**: Definira nabor metod, ki jih mora razred implementirati. Vrednost, ki izpolnjuje vmesnik, mora imeti te metode. +4. **Mešani tipi**: Lahko določimo, da ima spremenljivka lahko več dovoljenih tipov. +5. **Void**: Ta poseben tip označuje, da funkcija ali metoda ne vrača nobene vrednosti. + +Poglejmo si, kako prilagoditi kodo, da bo vključevala tipe: + +```php +class Oseba +{ + private int $starost; + + public function __construct(int $starost) + { + $this->starost = $starost; + } + + public function izpisiStarost(): void + { + echo "Ta oseba je stara {$this->starost} let."; + } +} + +/** + * Funkcija, ki sprejme objekt razreda Oseba in izpiše starost osebe. + */ +function izpisiStarostOsebe(Oseba $oseba): void +{ + $oseba->izpisiStarost(); +} +``` + +Na ta način smo zagotovili, da naša koda pričakuje in dela s podatki pravilnega tipa, kar nam pomaga preprečevati morebitne napake. + +Nekaterih tipov v PHP ni mogoče neposredno zapisati. V takem primeru se navedejo v phpDoc komentarju, kar je standardni format za dokumentiranje PHP kode, ki se začne z `/**` in konča z `*/`. Omogoča dodajanje opisov razredov, metod itd. In tudi navajanje kompleksnih tipov s pomočjo t.i. anotacij `@var`, `@param` in `@return`. Te tipe nato uporabljajo orodja za statično analizo kode, vendar jih sam PHP ne preverja. + +```php +class Seznam +{ + /** @var array<Oseba> zapis pravi, da gre za polje objektov Oseba */ + private array $osebe = []; + + public function dodajOsebo(Oseba $oseba): void + { + $this->osebe[] = $oseba; + } +} +``` + + +Primerjava in identiteta +------------------------ + +V PHP lahko objekte primerjate na dva načina: + +1. Primerjava vrednosti `==`: Preveri, ali sta objekta istega razreda in imata enake vrednosti v svojih lastnostih. +2. Identiteta `===`: Preveri, ali gre za isto instanco objekta. + +```php +class Avto +{ + public string $znamka; + + public function __construct(string $znamka) + { + $this->znamka = $znamka; + } +} + +$avto1 = new Avto('Skoda'); +$avto2 = new Avto('Skoda'); +$avto3 = $avto1; + +var_dump($avto1 == $avto2); // true, ker imata enako vrednost +var_dump($avto1 === $avto2); // false, ker nista ista instanca +var_dump($avto1 === $avto3); // true, ker je $avto3 ista instanca kot $avto1 +``` + + +Operator `instanceof` +--------------------- + +Operator `instanceof` omogoča ugotoviti, ali je dani objekt instanca določenega razreda, potomec tega razreda, ali pa implementira določen vmesnik. + +Predstavljajmo si, da imamo razred `Oseba` in drug razred `Student`, ki je potomec razreda `Oseba`: + +```php +class Oseba +{ + private int $starost; + + public function __construct(int $starost) + { + $this->starost = $starost; + } +} + +class Student extends Oseba +{ + private string $smer; + + public function __construct(int $starost, string $smer) + { + parent::__construct($starost); + $this->smer = $smer; + } +} + +$student = new Student(20, 'Informatika'); + +// Preverjanje, ali je $student instanca razreda Student +var_dump($student instanceof Student); // Izpis: bool(true) + +// Preverjanje, ali je $student instanca razreda Oseba (ker je Student potomec Osebe) +var_dump($student instanceof Osoba); // Izpis: bool(true) +``` + +Iz izpisov je razvidno, da se objekt `$student` hkrati šteje za instanco obeh razredov - `Student` in `Oseba`. + + +Tekoči vmesniki +--------------- + +"Tekoči vmesnik" (angleško "Fluent Interface") je tehnika v OOP, ki omogoča veriženje metod skupaj v enem klicu. S tem se pogosto poenostavi in naredi koda bolj pregledna. + +Ključni element tekočega vmesnika je, da vsaka metoda v verigi vrne referenco na trenutni objekt. To dosežemo tako, da na koncu metode uporabimo `return $this;`. Ta slog programiranja je pogosto povezan z metodami, imenovanimi "setters", ki nastavljajo vrednosti lastnosti objekta. + +Pokažimo si, kako lahko izgleda tekoči vmesnik na primeru pošiljanja e-pošte: + +```php +public function sendMessage() +{ + $email = new Email; + $email->setFrom('sender@example.com') + ->setRecipient('admin@example.com') + ->setMessage('Hello, this is a message.') + ->send(); +} +``` + +V tem primeru metode `setFrom()`, `setRecipient()` in `setMessage()` služijo za nastavitev ustreznih vrednosti (pošiljatelja, prejemnika, vsebine sporočila). Po nastavitvi vsake od teh vrednosti nam metode vrnejo trenutni objekt (`$email`), kar nam omogoča veriženje naslednje metode za njo. Na koncu kličemo metodo `send()`, ki e-pošto dejansko pošlje. + +Zahvaljujoč tekočim vmesnikom lahko pišemo kodo, ki je intuitivna in lahko berljiva. + + +Kopiranje s `clone` +------------------- + +V PHP lahko ustvarimo kopijo objekta z uporabo operatorja `clone`. Na ta način dobimo novo instanco z identično vsebino. + +Če moramo pri kopiranju objekta prilagoditi nekatere njegove lastnosti, lahko v razredu definiramo posebno metodo `__clone()`. Ta metoda se samodejno pokliče, ko je objekt kloniran. + +```php +class Ovca +{ + public string $ime; + + public function __construct(string $ime) + { + $this->ime = $ime; + } + + public function __clone() + { + $this->ime = 'Klon ' . $this->ime; + } +} + +$original = new Ovca('Dolly'); +echo $original->ime . "\n"; // Izpiše: Dolly + +$klon = clone $original; +echo $klon->ime . "\n"; // Izpiše: Klon Dolly +``` + +V tem primeru imamo razred `Ovca` z eno lastnostjo `$ime`. Ko kloniramo instanco tega razreda, metoda `__clone()` poskrbi, da ime klonirane ovce dobi predpono "Klon". + + +Lastnosti (Traits) +------------------ + +Lastnosti (Traits) v PHP so orodje, ki omogoča deljenje metod, lastnosti in konstant med razredi ter preprečuje podvajanje kode. Lahko si jih predstavljate kot mehanizem "kopiraj in prilepi" (Ctrl-C in Ctrl-V), kjer se vsebina lastnosti "vstavi" v razrede. To vam omogoča ponovno uporabo kode brez potrebe po ustvarjanju zapletenih hierarhij razredov. + +Poglejmo si preprost primer, kako uporabljati lastnosti v PHP: + +```php +trait Trobljenje +{ + public function potrobi() + { + echo 'Bip bip!'; + } +} + +class Avto +{ + use Trobljenje; +} + +class Tovornjak +{ + use Trobljenje; +} + +$avto = new Avto; +$avto->potrobi(); // Izpiše 'Bip bip!' + +$tovornjak = new Tovornjak; +$tovornjak->potrobi(); // Prav tako izpiše 'Bip bip!' +``` + +V tem primeru imamo lastnost, imenovano `Trobljenje`, ki vsebuje eno metodo `potrobi()`. Nato imamo dva razreda: `Avto` in `Tovornjak`, ki oba uporabljata lastnost `Trobljenje`. Zahvaljujoč temu oba razreda "imata" metodo `potrobi()`, in jo lahko kličemo na objektih obeh razredov. + +Lastnosti vam omogočajo enostavno in učinkovito deljenje kode med razredi. Pri tem ne vstopajo v dedno hierarhijo, tj. `$avto instanceof Trobljenje` vrne `false`. + + +Izjeme +------ + +Izjeme v OOP nam omogočajo elegantno obravnavanje napak in nepričakovanih situacij v naši kodi. So objekti, ki nosijo informacije o napaki ali nenavadni situaciji. + +V PHP imamo vgrajen razred `Exception`, ki služi kot osnova za vse izjeme. Ta ima več metod, ki nam omogočajo pridobiti več informacij o izjemi, kot so sporočilo o napaki, datoteka in vrstica, kjer je prišlo do napake, itd. + +Ko v kodi pride do napake, lahko "sprožimo" izjemo z uporabo ključne besede `throw`. + +```php +function deljenje(float $a, float $b): float +{ + if ($b === 0.0) { + throw new Exception('Deljenje z nič!'); + } + return $a / $b; +} +``` + +Ko funkcija `deljenje()` dobi kot drugi argument ničlo, sproži izjemo s sporočilom o napaki `'Deljenje z nič!'`. Da preprečimo sesutje programa ob sprožitvi izjeme, jo ujamemo v bloku `try/catch`: + +```php +try { + echo deljenje(10, 0.0); +} catch (Exception $e) { + echo 'Izjema ujeta: '. $e->getMessage(); +} +``` + +Koda, ki lahko sproži izjemo, je zavita v blok `try`. Če je izjema sprožena, se izvajanje kode premakne v blok `catch`, kjer lahko izjemo obdelamo (npr. izpišemo sporočilo o napaki). + +Po blokih `try` in `catch` lahko dodamo neobvezen blok `finally`, ki se izvede vedno, ne glede na to, ali je bila izjema sprožena ali ne (tudi v primeru, da v bloku `try` ali `catch` uporabimo ukaz `return`, `break` ali `continue`): + +```php +try { + echo deljenje(10, 0.0); +} catch (Exception $e) { + echo 'Izjema ujeta: '. $e->getMessage(); +} finally { + // Koda, ki se izvede vedno, ne glede na to, ali je bila izjema sprožena ali ne +} +``` + +Lahko ustvarimo tudi lastne razrede (hierarhijo) izjem, ki dedujejo od razreda Exception. Kot primer si predstavljajmo preprosto bančno aplikacijo, ki omogoča izvajanje pologov in dvigov: + +```php +class BancnaIzjema extends Exception {} +class PomanjkanjeSredstevIzjema extends BancnaIzjema {} +class PrekoracitevOmejitveIzjema extends BancnaIzjema {} + +class BancniRacun +{ + private int $stanje = 0; + private int $dnevnaOmejitev = 1000; + + public function poloziti(int $znesek): int + { + $this->stanje += $znesek; + return $this->stanje; + } + + public function dvigniti(int $znesek): int + { + if ($znesek > $this->stanje) { + throw new PomanjkanjeSredstevIzjema('Na računu ni dovolj sredstev.'); + } + + if ($znesek > $this->dnevnaOmejitev) { + throw new PrekoracitevOmejitveIzjema('Dnevna omejitev dvigov je bila presežena.'); + } + + $this->stanje -= $znesek; + return $this->stanje; + } +} +``` + +Za en blok `try` lahko navedemo več blokov `catch`, če pričakujete različne vrste izjem. + +```php +$racun = new BancniRacun; +$racun->poloziti(500); + +try { + $racun->dvigniti(1500); +} catch (PrekoracitevOmejitveIzjema $e) { + echo $e->getMessage(); +} catch (PomanjkanjeSredstevIzjema $e) { + echo $e->getMessage(); +} catch (BancnaIzjema $e) { + echo 'Pri izvajanju operacije je prišlo do napake.'; +} +``` + +V tem primeru je pomemben vrstni red blokov `catch`. Ker vse izjeme dedujejo od `BancnaIzjema`, če bi imeli ta blok prvi, bi se v njem ujele vse izjeme, ne da bi koda prišla do naslednjih `catch` blokov. Zato je pomembno imeti bolj specifične izjeme (tj. tiste, ki dedujejo od drugih) v bloku `catch` višje v vrstnem redu kot njihove starševske izjeme. + + +Iteracija +--------- + +V PHP lahko prehajate skozi objekte z uporabo `foreach` zanke, podobno kot prehajate skozi polja. Da bi to delovalo, mora objekt implementirati poseben vmesnik. + +Prva možnost je implementirati vmesnik `Iterator`, ki ima metode `current()` za vračanje trenutne vrednosti, `key()` za vračanje ključa, `next()` za premik na naslednjo vrednost, `rewind()` za premik na začetek in `valid()` za ugotavljanje, ali še nismo na koncu. + +Druga možnost je implementirati vmesnik `IteratorAggregate`, ki ima samo eno metodo `getIterator()`. Ta bodisi vrne nadomestni objekt, ki bo zagotavljal prehajanje, ali pa lahko predstavlja generator, kar je posebna funkcija, v kateri se uporablja `yield` za postopno vračanje ključev in vrednosti: + +```php +class Oseba +{ + public function __construct( + public int $starost, + ) { + } +} + +class Seznam implements IteratorAggregate +{ + private array $osebe = []; + + public function dodajOsebo(Oseba $oseba): void + { + $this->osebe[] = $oseba; + } + + public function getIterator(): Generator + { + foreach ($this->osebe as $oseba) { + yield $oseba; + } + } +} + +$seznam = new Seznam; +$seznam->dodajOsebo(new Oseba(30)); +$seznam->dodajOsebo(new Oseba(25)); + +foreach ($seznam as $oseba) { + echo "Starost: {$oseba->starost} let \n"; +} +``` + + +Dobre prakse +------------ + +Ko imate za sabo osnovna načela objektno usmerjenega programiranja, je pomembno, da se osredotočite na dobre prakse v OOP. Te vam bodo pomagale pisati kodo, ki ni samo funkcionalna, ampak tudi berljiva, razumljiva in enostavno vzdrževana. + +1) **Ločevanje odgovornosti (Separation of Concerns)**: Vsak razred bi moral imeti jasno definirano odgovornost in bi moral reševati samo eno glavno nalogo. Če razred počne preveč stvari, ga je morda primerno razdeliti na manjše, specializirane razrede. +2) **Inkapsulacija (Encapsulation)**: Podatki in metode bi morali biti čim bolj skriti in dostopni samo preko definiranega vmesnika. To vam omogoča spreminjanje interne implementacije razreda brez vpliva na preostalo kodo. +3) **Vnašanje odvisnosti (Dependency Injection)**: Namesto da bi ustvarili odvisnosti neposredno v razredu, bi jih morali "vnašati" od zunaj. Za globlje razumevanje tega načela priporočamo [poglavja o vnašanju odvisnosti|dependency-injection:introduction]. diff --git a/nette/sl/troubleshooting.texy b/nette/sl/troubleshooting.texy index 60a00f90a8..b19f2d7831 100644 --- a/nette/sl/troubleshooting.texy +++ b/nette/sl/troubleshooting.texy @@ -1,41 +1,70 @@ -Odpravljanje težav -****************** +Reševanje težav +*************** -Nette ne deluje, prikazana je bela stran .[#toc-nette-is-not-working-white-page-is-displayed] ---------------------------------------------------------------------------------------------- -- Poskusite v datoteko `index.php` za `declare(strict_types=1);` vstaviti `ini_set('display_errors', '1'); error_reporting(E_ALL);`, da bi izsilili prikaz napak. -- Če se še vedno prikazuje bel zaslon, je verjetno prišlo do napake v nastavitvah strežnika, razlog pa boste odkrili v dnevniku strežnika. Če želite biti prepričani, preverite, ali PHP sploh deluje, tako da poskusite nekaj natisniti z uporabo `echo 'test';`. -- Če se prikaže napaka *Server Error: Spoštovani! ...*, nadaljujte z naslednjim razdelkom: +Nette mi ne deluje, prikazuje se bela stran +------------------------------------------- +- Poskusite v datoteko `index.php` takoj za `declare(strict_types=1);` vstaviti `ini_set('display_errors', '1'); error_reporting(E_ALL);`, s tem boste prisilili prikazovanje napak +- Če še vedno vidite bel zaslon, je verjetno napaka v nastavitvah strežnika in razlog boste odkrili v dnevniku strežnika. Za vsak slučaj še preverite, ali PHP sploh deluje, tako da poskusite nekaj izpisati z `echo 'test';` +- Če vidite napako *Server Error: We're sorry! …*, nadaljujte z naslednjim odsekom: -Napaka 500 * Napaka strežnika: Opravičujemo se! ...* .[#toc-error-500-server-error-we-re-sorry] ------------------------------------------------------------------------------------------------ -To stran z napako prikaže Nette v produkcijskem načinu. Če jo vidite v računalniku za razvijalce, [preklopite na način za razvijalce |application:bootstrap#Development vs Production Mode]. +Napaka 500 *Server Error: We're sorry! …* +----------------------------------------- +To stran z napako prikazuje Nette v produkcijskem načinu. Če se vam prikazuje na razvijalskem računalniku, [preklopite v razvijalski način |application:bootstrapping#Razvojni vs produkcijski način] in prikazala se vam bo Tracy s podrobnim sporočilom. -Če sporočilo o napaki vsebuje `Tracy is unable to log error`, ugotovite, zakaj napak ni mogoče zabeležiti. To lahko storite tako, da na primer [preklopite |application:bootstrap#Development vs Production Mode] v način za razvijalce in po `$configurator->enableTracy(...)` pokličete `Tracy\Debugger::log('hello');`. Tracy vam bo povedal, zakaj se ne more prijaviti. -Vzrok je običajno [nezadostna dovoljenja za |#Setting Directory Permissions] pisanje v imenik `log/`. +Razlog napake vedno preberete v dnevniku v mapi `log/`. Če pa se v sporočilu o napaki pokaže stavek `Tracy is unable to log error`, najprej ugotovite, zakaj ni mogoče beležiti napak. To storite na primer tako, da se začasno [preklopite |application:bootstrapping#Razvojni vs produkcijski način] v razvijalski način in pustite Tracy, da karkoli zabeleži po njenem zagonu: -Če stavka `Tracy is unable to log error` v sporočilu o napaki ni (več), lahko razlog za napako ugotovite v dnevniku v imeniku `log/`. +```php +// Bootstrap.php +$configurator->setDebugMode('23.75.345.200'); // vaš IP naslov +$configurator->enableTracy($rootDir . '/log'); +\Tracy\Debugger::log('hello'); +``` + +Tracy vam bo sporočila, zakaj ne more beležiti. Vzrok so lahko [nezadostna dovoljenja |#Nastavitev pravic map] za pisanje v mapo `log/`. + +Eden najpogostejših vzrokov napake 500 je zastarel predpomnilnik. Medtem ko Nette v razvijalskem načinu pametno samodejno posodablja predpomnilnik, se v produkcijskem načinu osredotoča na maksimizacijo zmogljivosti in brisanje predpomnilnika, po vsaki spremembi kode, je na vas. Poskusite izbrisati `temp/cache`. + + +Napaka 404, usmerjanje ne deluje +-------------------------------- +Če vse strani (razen domače strani) vračajo napako 404, kaže na težavo s konfiguracijo strežnika za [lepe URL-je |#Kako nastaviti strežnik za lepe URL-je]. + + +Spremembe v predlogah ali konfiguraciji se ne odražajo +------------------------------------------------------ +"Uredil sem predlogo ali konfiguracijo, vendar spletno mesto še vedno prikazuje staro različico." To vedenje se pojavi v [produkcijskem načinu |application:bootstrapping#Razvojni vs produkcijski način], ki zaradi zmogljivosti ne preverja sprememb v datotekah in ohranja enkrat generiran predpomnilnik. + +Da vam na produkcijskem strežniku po vsaki spremembi ne bi bilo treba ročno brisati predpomnilnika, omogočite razvijalski način za vaš IP naslov v datoteki `Bootstrap.php`: + +```php +$this->configurator->setDebugMode('vas.ip.naslov'); +``` + + +Kako izklopiti predpomnilnik med razvojem? +------------------------------------------ +Nette je pameten in v njem ni treba izklapljati predpomnjenja. Med razvojem namreč samodejno posodablja predpomnilnik ob vsaki spremembi predloge ali konfiguracije DI vsebnika. Razvijalski način se poleg tega vklopi s samodejnim zaznavanjem, zato običajno ni treba konfigurirati ničesar, [ali samo IP naslov |application:bootstrapping#Razvojni vs produkcijski način]. -Eden najpogostejših razlogov je zastarel predpomnilnik. Medtem ko Nette v razvojnem načinu pametno samodejno posodablja predpomnilnik, se v produkcijskem načinu osredotoča na čim večjo zmogljivost, čiščenje predpomnilnika po vsaki spremembi kode pa je odvisno od vas. Poskusite izbrisati `temp/cache`. +Pri razhroščevanju usmerjevalnika priporočamo izklop predpomnilnika v brskalniku, v katerem so lahko shranjene na primer preusmeritve: odprite Razvijalska orodja (Ctrl+Shift+I ali Cmd+Option+I) in v plošči Network (Omrežje) označite izklop predpomnilnika. -Napaka `#[\ReturnTypeWillChange] attribute should be used` .[#toc-error-returntypewillchange-attribute-should-be-used] ----------------------------------------------------------------------------------------------------------------------- -Ta napaka se pojavi, če ste PHP nadgradili na različico 8.1, vendar uporabljate Nette, ki z njo ni združljiv. Rešitev je, da Nette posodobite na novejšo različico z uporabo `composer update`. Nette podpira PHP 8.1 od različice 3.0. Če uporabljate starejšo različico (to lahko ugotovite z vpogledom v `composer.json`), [nadgradite Nette |migrations:en] ali pa ostanite pri PHP 8.0. +Napaka `#[\ReturnTypeWillChange] attribute should be used` +---------------------------------------------------------- +Ta napaka se pojavi, če ste posodobili PHP na različico 8.1, vendar uporabljate Nette, ki z njo ni združljiv. Rešitev je torej posodobitev Nette na novejšo različico z uporabo `composer update`. Nette podpira PHP 8.1 od različice 3.0. Če uporabljate starejšo različico (ugotovite s pogledom v `composer.json`), [nadgradite Nette |migrations:en] ali ostanite pri PHP 8.0. -Nastavitev dovoljenj za imenik .[#toc-setting-directory-permissions] --------------------------------------------------------------------- -Če razvijate v operacijskem sistemu MacOS ali Linux (ali katerem koli drugem sistemu, ki temelji na Unixu), morate nastaviti pravice za pisanje v spletni strežnik. Ob predpostavki, da se vaša aplikacija nahaja v privzetem imeniku `/var/www/html` (Fedora, CentOS, RHEL) +Nastavitev pravic map +--------------------- +Če razvijate na macOS ali Linuxu (ali katerem koli drugem sistemu, ki temelji na Unixu), boste morali nastaviti pravice za pisanje spletnemu strežniku. Predpostavimo, da se vaša aplikacija nahaja v privzeti `/var/www/html` (Fedora, CentOS, RHEL). ```shell cd /var/www/html/MY_PROJECT chmod -R a+rw temp log ``` -V nekaterih sistemih Linux (Fedora, CentOS, ...) je lahko SELinux privzeto omogočen. Morda boste morali posodobiti politike SELinuxa ali nastaviti poti do imenikov `temp` in `log` s pravilnim varnostnim kontekstom SELinuxa. Direktorja `temp` in `log` je treba nastaviti na kontekst `httpd_sys_rw_content_t`; za preostalo aplikacijo - predvsem mapo `app` - bo zadostoval kontekst `httpd_sys_content_t`. V strežniku zaženite kot root: +Na nekaterih distribucijah Linuxa (Fedora, CentOS, ...) je SELinux privzeto omogočen. Morali boste ustrezno prilagoditi politike SELinuxa in nastaviti pravilen varnostni kontekst SELinuxa za mape `temp` in `log`. Za `temp` in `log` bomo nastavili tip konteksta `httpd_sys_rw_content_t`, za preostanek aplikacije (in predvsem za mapo `app`) bo zadostoval `httpd_sys_content_t`. Na strežniku zaženite: ```shell semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/log(/.*)?' @@ -43,25 +72,30 @@ semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/temp(/.*) restorecon -Rv /var/www/html/MY_PROJECT/ ``` -Nato je treba omogočiti SELinuxov boolean `httpd_can_network_connect_db`, da se Nette lahko poveže s podatkovno bazo prek omrežja. Privzeto je onemogočen. Za izvedbo tega opravila lahko uporabite ukaz `setsebool`, in če je navedena možnost `-P`, bo ta nastavitev obstojna med ponovnimi zagoni. +Nato je treba omogočiti logično vrednost SELinuxa `httpd_can_network_connect_db`, ki je v privzeti nastavitvi izklopljena in ki bo Nette omogočila povezavo z bazo podatkov prek omrežja. Za to bomo uporabili ukaz `setsebool` in z možnostjo `-P` bomo spremembo naredili trajno, tj. po ponovnem zagonu strežnika nas ne bo čakalo neprijetno presenečenje: ```shell setsebool -P httpd_can_network_connect_db on ``` -Kako spremeniti ali odstraniti imenik `www` z naslova URL? .[#toc-how-to-change-or-remove-www-directory-from-url] ------------------------------------------------------------------------------------------------------------------ -Imenik `www/`, ki se uporablja v vzorčnih projektih v Nette, je tako imenovani javni imenik ali dokumentni koren projekta. To je edini imenik, katerega vsebina je dostopna brskalniku. Vsebuje pa tudi datoteko `index.php`, vstopno točko, ki zažene spletno aplikacijo, napisano v programu Nette. +Kako spremeniti ali odstraniti mapo `www` iz URL-ja? +---------------------------------------------------- +Mapa `www/`, ki se uporablja v vzorčnih projektih v Nette, predstavlja tako imenovano javno mapo ali document-root projekta. To je edina mapa, katere vsebina je dostopna brskalniku. Vsebuje datoteko `index.php`, vstopno točko, ki zažene spletno aplikacijo, napisano v Nette. -Če želite zagnati aplikacijo na gostovanju, morate v konfiguraciji gostovanja nastaviti document-root na ta imenik. Če pa ima gostovanje vnaprej pripravljeno mapo za javni imenik z drugačnim imenom (na primer `web`, `public_html` itd.), preprosto preimenujte `www/`. +Za zagon aplikacije na gostovanju je treba imeti pravilno konfiguriran document-root. Imate dve možnosti: +1. V konfiguraciji gostovanja nastavite document-root na to mapo +2. Če ima gostovanje vnaprej pripravljeno mapo (npr. `public_html`), preimenujte `www/` v to ime -Rešitev **ni**, da bi se mape `www/` "znebili" z uporabo pravil v datoteki `.htaccess` ali v usmerjevalniku. Če gostovanje ne bi dovolilo nastavitve document-root na podimenik (tj. ustvarjanje imenikov eno raven nad javnim imenikom), poiščite drugega. V nasprotnem primeru bi se izpostavili precejšnjemu varnostnemu tveganju. To bi bilo tako, kot če bi živeli v stanovanju, v katerem ne morete zapreti vhodnih vrat in so ta vedno na široko odprta. +.[warning] +Nikoli se ne poskušajte zanašati samo na `.htaccess` ali usmerjevalnik za zaščito, ki bi preprečeval dostop do drugih map. +Če gostovanje ne omogoča nastavitve document-root v podmapo (tj. ustvarjanja map eno raven višje nad javno mapo), poiščite drugega ponudnika. Sicer bi tvegali znatno varnostno tveganje. Bilo bi kot živeti v stanovanju, kjer vhodnih vrat ni mogoče zapreti in so vedno na stežaj odprta. -Kako konfigurirati strežnik za lepe naslove URL? .[#toc-how-to-configure-a-server-for-nice-urls] ------------------------------------------------------------------------------------------------- -**Apache**: razširitev mod_rewrite mora biti dovoljena in konfigurirana v datoteki `.htaccess`. + +Kako nastaviti strežnik za lepe URL-je? +--------------------------------------- +**Apache**: potrebno je omogočiti in nastaviti pravila mod_rewrite v datoteki `.htaccess`: ```apacheconf RewriteEngine On @@ -70,25 +104,53 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -Če želite spremeniti konfiguracijo Apache z datotekami .htaccess, je treba omogočiti direktivo AllowOverride. To je privzeto vedenje za Apache. +Če naletite na težave, se prepričajte, da: +- se datoteka `.htaccess` nahaja v mapi document-root (torej poleg datoteke `index.php`) +- [Apache obdeluje datoteke `.htaccess` |#Preverjanje ali .htaccess deluje] +- [je omogočen mod_rewrite |#Preverjanje ali je omogočen mod rewrite] + +Če nastavljate aplikacijo v podmapi, boste morda morali odkomentirati vrstico za nastavitev `RewriteBase` in jo nastaviti na pravilno mapo. -**nginx**: v konfiguraciji strežnika je treba uporabiti direktivo `try_files`: +**nginx**: potrebno je nastaviti preusmeritev z uporabo direktive `try_files` znotraj bloka `location /` v konfiguraciji strežnika. ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args is important + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args JE POMEMBNO! } ``` -Blok `location` mora biti opredeljen natanko enkrat za vsako pot do datotečnega sistema v bloku `server`. Če v konfiguraciji že imate blok `location /`, dodajte direktivo `try_files` v obstoječi blok. +Blok `location` se za vsako datotečno sistemsko pot sme v bloku `server` pojaviti samo enkrat. Če že imate v konfiguraciji `location /`, dodajte direktivo `try_files` vanj. + + +Preverjanje, ali `.htaccess` deluje +----------------------------------- +Najlažji način, da preizkusite, ali Apache uporablja ali ignorira vašo datoteko `.htaccess`, je, da jo namerno poškodujete. Na začetek datoteke vstavite vrstico `Test` in zdaj, če osvežite stran v brskalniku, bi morali videti *Internal Server Error*. + +Če se vam ta napaka prikaže, je to pravzaprav dobro! Pomeni, da Apache analizira datoteko `.htaccess` in naleti na napako, ki smo jo vstavili. Odstranite vrstico `Test`. + +Če se ne prikaže *Internal Server Error*, vaša nastavitev Apache ignorira datoteko `.htaccess`. Na splošno jo Apache ignorira zaradi manjkajoče konfiguracijske direktive `AllowOverride All`. + +Če gostujete sami, lahko to enostavno popravite. Odprite datoteko `httpd.conf` ali `apache.conf` v urejevalniku besedil, poiščite ustrezen del `<Directory>` in dodajte/spremenite to direktivo: + +```apacheconf +<Directory "/var/www/htdocs"> # pot do vašega document root + AllowOverride All + ... +``` + +Če vaše spletno mesto gostuje drugje, preverite v nadzorni plošči, ali lahko tam omogočite datoteko `.htaccess`. Če ne, se obrnite na ponudnika gostovanja, da to stori za vas. + + +Preverjanje, ali je omogočen `mod_rewrite` +------------------------------------------ +Če ste preverili, da [`htaccess` deluje |#Preverjanje ali .htaccess deluje], lahko preverite, ali je omogočena razširitev mod_rewrite. Na začetek datoteke `.htaccess` vstavite vrstico `RewriteEngine On` in osvežite stran v brskalniku. Če se prikaže *Internal Server Error*, pomeni, da mod_rewrite ni omogočen. Obstaja več načinov, kako ga omogočiti. Različne načine, kako to storiti v različnih nastavitvah, najdete na Stack Overflow. -Povezave se ustvarijo brez `https:` .[#toc-links-are-generated-without-https] ------------------------------------------------------------------------------ -Nette generira povezave z enakim protokolom, kot ga uporablja trenutna stran. Tako na strani `https://foo`, in obratno. -Če se nahajate za povratnim posredniškim strežnikom, ki odvzema protokol HTTPS (na primer v programu Docker), morate v konfiguraciji nastaviti [posredniški strežnik |http:configuration#HTTP proxy], da bo zaznavanje protokola delovalo pravilno. +Povezave se generirajo brez `https:` +------------------------------------ +Nette generira povezave z istim protokolom, kot ga ima sama stran. Torej na strani `https://foo` generira povezave, ki se začnejo s `https:`, in obratno. Če ste za povratnim proxy strežnikom, ki odstranjuje HTTPS (na primer v Dockerju), potem je treba v konfiguraciji [nastaviti proxy |http:configuration#HTTP proxy], da zaznavanje protokola deluje pravilno. -Če kot posrednik uporabljate Nginx, morate preusmeritev nastaviti na naslednji način: +Če kot proxy uporabljate Nginx, je treba imeti nastavljeno preusmeritev npr. takole: ``` location / { @@ -96,11 +158,11 @@ location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Port $server_port; - proxy_pass http://IP-aplikace:80; # IP or hostname of the server/container where the application is running + proxy_pass http://IP-aplikace:80; # IP ali ime gostitelja strežnika/kontejnerja, kjer teče aplikacija } ``` -Nato morate določiti posrednika IP in po potrebi območje IP lokalnega omrežja, v katerem izvajate infrastrukturo: +Nato je treba v konfiguracijo vnesti IP proxyja in po možnosti IP obseg vašega lokalnega omrežja, kjer upravljate infrastrukturo: ```neon http: @@ -108,29 +170,27 @@ http: ``` -Uporaba znakov { } v jeziku JavaScript .[#toc-use-of-characters-in-javascript] ------------------------------------------------------------------------------- -Znaki `{` and `}` se uporabljajo za pisanje oznak Latte. Vse (razen presledka in narekovajev), ki sledijo `{` character is considered a tag. If you need to print character `{` (pogosto v javascriptu), lahko takoj za `{` postavite presledek (ali drug prazen znak). S tem se izognete interpretaciji kot oznake. +Uporaba znakov { } v JavaScriptu +-------------------------------- +Znaka `{` in `}` se uporabljata za zapis Latte značk. Kot značka se šteje karkoli, kar sledi znaku `{`, razen presledka in narekovaja. Če torej morate izpisati neposredno znak `{` (pogosto na primer v JavaScriptu), lahko za znakom `{` dodate presledek (ali drug prazen znak). S tem se izognete prevajanju kot značke. -Če je treba te znake izpisati v situaciji, ko bi se razlagali kot oznaka, lahko za izpis teh znakov uporabite posebne oznake - `{l}` za `{` and `{r}` za `}`. +Če je treba te znake izpisati v situaciji, ko bi se besedilo razumelo kot značka, lahko uporabite posebne značke za izpis teh znakov - `{l}` za `{` in `{r}` za `}`. ``` -{is tag} -{ is not tag } -{l}is not tag{r} +{je značka} +{ ni značka } +{l}ni značka{r} ``` -Obvestilo `Presenter::getContext() is deprecated` .[#toc-notice-presenter-getcontext-is-deprecated] ---------------------------------------------------------------------------------------------------- +Sporočilo `Presenter::getContext() is deprecated` +------------------------------------------------- -Nette je daleč prvo ogrodje PHP, ki je prešlo na vbrizgavanje odvisnosti in programerje spodbudilo k njegovi dosledni uporabi, začenši s predavatelji. Če predstavnik potrebuje odvisnost, [bo zanjo zaprosil |dependency-injection:passing-dependencies]. -Nasprotno pa način, ko celoten vsebnik DI posredujemo razredu, ta pa iz njega neposredno potegne odvisnosti, velja za antivzorec (imenuje se iskalnik storitev). -Ta način se je uporabljal v različici Nette 0.x pred pojavom vbrizgavanja odvisnosti, njegov ostanek pa je metoda `Presenter::getContext()`, ki je že zdavnaj označena kot zastarela. +Nette je daleč prvi PHP framework, ki je prešel na vbrizgavanje odvisnosti in vodil programerje k njegovi dosledni uporabi, že od samih presenterjev. Če presenter potrebuje kakšno odvisnost, [se zanjo prijavi |dependency-injection:passing-dependencies]. Nasprotno pa se pot, ko v razred predamo celoten DI vsebnik, in ta si iz njega neposredno jemlje odvisnosti, šteje za antipattern (imenuje se service locator). Ta način se je uporabljal v Nette 0.x še pred prihodom vbrizgavanja odvisnosti in njegov ostanek je metoda `Presenter::getContext()`, že davno označena kot zastarela. -Če prenesete zelo staro aplikacijo Nette, boste morda ugotovili, da še vedno uporablja to metodo. Tako boste od različice 3.1 `nette/application` naleteli na opozorilo `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection`, od različice 4.0 pa na napako, da metoda ne obstaja. +Če prenašate zelo staro aplikacijo za Nette, se lahko zgodi, da to metodo še vedno uporablja. Od `nette/application` različice 3.1 se boste tako srečali z opozorilom `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection`, od različice 4.0 pa z napako, da metoda ne obstaja. -Čista rešitev je seveda ta, da aplikacijo preoblikujete tako, da bo odvisnosti posredovala z uporabo vbrizgavanja odvisnosti. Kot obvoznico lahko osnovnemu predstavniku dodate svojo metodo `getContext()` in tako zaobidete sporočilo: +Čista rešitev je seveda predelati aplikacijo tako, da si odvisnosti predaja z uporabo vbrizgavanja odvisnosti. Kot obhodno rešitev lahko v svoj osnovni presenter dodate lastno metodo `getContext()` in tako obidete sporočilo: ```php abstract BasePresenter extends Nette\Application\UI\Presenter diff --git a/nette/sl/vulnerability-protection.texy b/nette/sl/vulnerability-protection.texy new file mode 100644 index 0000000000..cd8c8f96d8 --- /dev/null +++ b/nette/sl/vulnerability-protection.texy @@ -0,0 +1,99 @@ +Zaščita pred ranljivostmi +************************* + +.[perex] +Vsake toliko časa poročajo o varnostni luknji na še enem pomembnem spletnem mestu ali pa je luknja izkoriščena. To je neprijetno. Če vam je mar za varnost vaših spletnih aplikacij, je Nette Framework zagotovo najboljša izbira. + + +Cross-Site Scripting (XSS) +========================== + +Cross-Site Scripting je metoda vdora v spletne strani z izkoriščanjem neobdelanih izpisov. Napadalec lahko nato v stran vstavi svojo lastno kodo in s tem lahko stran spremeni ali celo pridobi občutljive podatke o obiskovalcih. Proti XSS se je mogoče braniti le z doslednim in pravilnim obdelovanjem vseh nizov. Pri tem zadostuje, da vaš koder samo enkrat to opusti, in celotno spletno mesto je lahko takoj ogroženo. + +Primer napada je lahko podtikanje spremenjenega URL-ja uporabniku, s katerim v stran vbrizgamo svojo kodo. Če aplikacija ne bo pravilno obdelovala izpisov, bo skript izvedla v brskalniku uporabnika. Na ta način mu lahko na primer ukrademo identiteto. + +``` +https://example.com/?search=<script>alert('Uspešen XSS napad.');</script> +``` + +Nette Framework prihaja z revolucionarno tehnologijo [Context-Aware Escaping |latte:safety-first#Kontekstno občutljivo ubežanje], ki vas za vedno reši tveganja Cross-Site Scriptinga. Vse izpise namreč obdeluje samodejno in tako se ne more zgoditi, da bi koder na kaj pozabil. Primer? Koder ustvari to predlogo: + +```latte +<p onclick="alert({$message})">{$message}</p> + +<script> +document.title = {$message}; +</script> +``` + +Zapis `{$message}` pomeni izpis spremenljivke. V drugih ogrodjih je treba vsak izpis izrecno obdelati in celo na vsakem mestu drugače. V Nette Framework ni treba obdelovati ničesar, vse se naredi samodejno, pravilno in dosledno. Če v spremenljivko vstavimo `$message = 'Širina 1/2"'`, ogrodje generira HTML kodo: + +```latte +<p onclick="alert("Širina 1\/2\"")">Širina 1/2"</p> + +<script> +document.title = "Širina 1\/2\""; +</script> +``` + + +Cross-Site Request Forgery (CSRF) +================================= + +Napad Cross-Site Request Forgery temelji na tem, da napadalec žrtev zvabi na stran, ki neopazno v brskalniku žrtve izvede zahtevek na strežnik, na katerem je žrtev prijavljena, in strežnik domneva, da je zahtevek izvedla žrtev po lastni volji. Tako pod identiteto žrtve izvede določeno dejanje, ne da bi ta za to vedela. Lahko gre za spremembo ali brisanje podatkov, pošiljanje sporočila itd. + +Nette Framework **samodejno ščiti obrazce in signale v presenterjih** pred to vrsto napada. In sicer tako, da preprečuje njihovo pošiljanje ali klicanje iz druge domene. Če želite zaščito izklopiti, uporabite pri obrazcih: + +```php +$form->allowCrossOrigin(); +``` + +ali v primeru signala dodajte anotacijo `@crossOrigin`: + +```php +/** + * @crossOrigin + */ +public function handleXyz() +{ +} +``` + +V Nette Application 3.2 lahko uporabite tudi atribute: + +```php +use Nette\Application\Attributes\Requires; + +#[Requires(sameOrigin: false)] +public function handleXyz() +{ +} +``` + + +URL napad, kontrolne kode, neveljaven UTF-8 +=========================================== + +Različni pojmi, povezani s poskusom napadalca, da vaši spletni aplikaciji podtakne *škodljiv* vnos. Posledice so lahko zelo raznolike, od poškodovanja XML izpisov (npr. nedelujoči RSS viri) do pridobivanja občutljivih informacij iz baze podatkov ali gesel. Obramba je dosledno obdelovanje vseh vnosov na ravni posameznih bajtov. In roko na srce, kdo od vas to počne? + +Nette Framework to počne za vas in poleg tega samodejno. Ni vam treba nastaviti ničesar in vsi vnosi bodo obdelani. + + +Ugrabitev seje, kraja seje, fiksacija seje +========================================== + +Z upravljanjem sej je povezanih več vrst napadov. Napadalec bodisi ukrade ali uporabniku podtakne svoj ID seje in s tem pridobi dostop do spletne aplikacije, ne da bi poznal geslo uporabnika. Nato lahko v aplikaciji počne karkoli, ne da bi uporabnik za to vedel. Obramba temelji na pravilni konfiguraciji strežnika in PHP. + +Pri čemer Nette Framework konfigurira PHP samodejno. Programerju tako ni treba razmišljati, kako pravilno zavarovati sejo, in se lahko popolnoma osredotoči na ustvarjanje aplikacije. Vendar to zahteva omogočeno funkcijo `ini_set()`. + + +SameSite cookie +=============== + +SameSite piškotki zagotavljajo mehanizem za prepoznavanje, kaj je vodilo do nalaganja strani. Kar je absolutno ključno zaradi varnosti. + +Zastavica SameSite lahko ima tri vrednosti: `Lax`, `Strict` in `None` (ta zahteva HTTPS). Če zahtevek za stran prihaja neposredno s spletnega mesta ali uporabnik odpre stran z neposrednim vnosom v naslovno vrstico ali s klikom na zaznamek, brskalnik strežniku pošlje vse piškotke (torej z zastavicami `Lax`, `Strict` in `None`). Če se uporabnik na spletno mesto preklikne prek povezave z drugega spletnega mesta, se strežniku predajo piškotki z zastavicami `Lax` in `None`. Če zahtevek nastane na drug način, kot je pošiljanje POST obrazca z drugega spletnega mesta, nalaganje znotraj iframe, s pomočjo JavaScripta itd., se pošljejo samo piškotki z zastavico `None`. + +Nette privzeto vse piškotke pošilja z zastavico `Lax`. + +{{leftbar: www:@menu-common}} diff --git a/nette/tr/@home.texy b/nette/tr/@home.texy index 095f3e9aff..2043154c6d 100644 --- a/nette/tr/@home.texy +++ b/nette/tr/@home.texy @@ -1,73 +1,74 @@ -Nette Dokümantasyon -******************* +Nette Dokümantasyonu +******************** <div class=documentation> <div> -Giriş ------ -- [Neden Nette Kullanılmalı? |www:10-reasons-why-nette] -- [Kurulum |Installation] -- [İlk Başvurunuzu Oluşturun! |quickstart:] +Tanıtım +------- +- [Neden Nette kullanmalı? |www:10-reasons-why-nette] +- [Kurulum |installation] +- [İlk uygulamamızı yazıyoruz! |quickstart:] Genel ----- -- [Paketlerin Listesi |www:packages] -- [Bakım ve PHP |www:maintenance] +- [Paket listesi |www:packages] +- [Bakım ve PHP sürümleri |www:maintenance] - [Sürüm Notları |https://nette.org/releases] -- [Yükseltme Kılavuzu |migrations:en] -- [nette:Sorun Giderme |nette:Troubleshooting] -- [Nette'i Kim Yarattı |https://nette.org/contributors] -- [Nette'in Tarihçesi |www:history] -- [Katılın |contributing:] -- [Sponsor geliştirme |https://nette.org/en/donate] -- [API referansı |https://api.nette.org/] +- [Daha yeni sürümlere geçiş|migrations:en] +- [Sorun Giderme |nette:troubleshooting] +- [Nette'yi kimler oluşturuyor |https://nette.org/contributors] +- [Nette Tarihçesi |www:history] +- [Katkıda Bulunun |contributing:] +- [Geliştirmeyi Destekleyin |https://nette.org/en/donate] +- [API Referansı |https://api.nette.org/] </div> <div> -Nette Uygulama --------------- -- [Uygulamalar Nasıl Çalışır? |application:how-it-works] -- [Bootstrap |application:Bootstrap] -- [Sunucular |application:Presenters] -- [Şablonlar |application:Templates] -- [Modüller |application:Modules] -- [Yönlendirme |application:Routing] +Nette Uygulamaları +------------------ +- [Uygulamalar nasıl çalışır? |application:how-it-works] +- [application:Bootstrapping] +- [Presenter'lar |application:presenters] +- [Şablonlar |application:templates] +- [Dizin Yapısı |application:directory-structure] +- [Yönlendirme |application:routing] - [URL Bağlantıları Oluşturma |application:creating-links] -- [İnteraktif Bileşenler |application:components] -- [AJAX ve Snippet'ler |application:ajax] +- [Etkileşimli Bileşenler |application:components] +- [AJAX & Snippet'ler |application:ajax] -- [En İyi Uygulamalar |best-practices:] +- [Kılavuzlar ve yöntemler |best-practices:] </div> <div> -Ana Başlıklar -------------- -- [Konfigürasyon |nette:configuring] -- [Bağımlılık Enjeksiyonu |dependency-injection:] +Ana Konular +----------- +- [Yapılandırma |nette:configuring] +- [Bağımlılık Enjeksiyonu|dependency-injection:] - [Latte: Şablonlar |latte:] -- [Tracy: Hata Ayıklama Aracı |tracy:] +- [Tracy: Kod Hata Ayıklama |tracy:] - [Formlar |forms:] -- [Veritabanı |database:core] -- [Kullanıcıların Kimliğini Doğrulama |security:authentication] -- [Erişim Kontrolü |security:authorization] -- [Oturumlar |http:Sessions] -- [HTTP isteği ve yanıtı |http:] -- [Önbellekleme |caching:] +- [Veritabanı |database:guide] +- [Kullanıcı Girişi |security:authentication] +- [Yetkilendirme |security:authorization] +- [http:Sessions] +- [HTTP İsteği & Yanıtı|http:] +- [Varlıklar |assets:] +- [Önbellek |caching:] - [E-posta Gönderme |mail:] -- [Şema: Veri Doğrulama |schema:] -- [PHP Kod Oluşturucu |php-generator:] -- [Tester: Birim Testi |tester:] +- [Schema: Veri Doğrulama |schema:] +- [PHP Kod Üreteci |php-generator:] +- [Tester: Test Etme |tester:] </div> @@ -76,19 +77,19 @@ Ana Başlıklar Yardımcı Programlar ------------------- -- [Diziler |utils:Arrays] +- [Diziler |utils:arrays] - [Dosya Sistemi |utils:filesystem] -- [Bulucu |utils:finder] -- [HTML Elemanları |utils:HTML Elements] -- [Görüntüler |utils:Images] -- [JSON |utils:JSON] -- [NEON |neon:] -- [Parola Hashing |security:passwords] -- [SmartObject |utils:SmartObject] -- [PHP Türleri |utils:type] -- [Dizeler |utils:Strings] +- [Finder |utils:finder] +- [HTML Öğeleri |utils:html-elements] +- [Resimler |utils:images] +- [utils:JSON] +- [NEON|neon:] +- [Şifre Hashleme |security:passwords] +- [PHP Tipleri |utils:type] +- [Karakter Dizileri |utils:strings] - [Doğrulayıcılar |utils:validators] - [RobotLoader |robot-loader:] +- [SmartObject |utils:smartobject] & [StaticClass |utils:StaticClass] - [SafeStream |safe-stream:] - [...diğerleri |utils:] </div> @@ -97,5 +98,5 @@ Yardımcı Programlar {{toc:no}} -{{description: Resmi Nette Dokümantasyonu: Nette'in nasıl çalıştığını ve web uygulamaları geliştirmek için en iyi uygulamaları açıklar.}} -{{maintitle: Net Dokümantasyon}} +{{description: Resmi Nette dokümantasyonu: Nette'nin nasıl çalıştığını ve web uygulamaları geliştirmek için en iyi uygulamaları açıklar.}} +{{maintitle: Nette Dokümantasyonu}} diff --git a/nette/tr/@menu-topics.texy b/nette/tr/@menu-topics.texy index 24ac514ee8..99e1d8bcd9 100644 --- a/nette/tr/@menu-topics.texy +++ b/nette/tr/@menu-topics.texy @@ -1,21 +1,21 @@ -Ana Başlıklar -************* -- [Konfigürasyon |nette:configuring] -- [Nette Uygulama |application:how-it-works] -- [Bağımlılık Enjeksiyonu |dependency-injection:] +Ana Konular +*********** +- [Yapılandırma |nette:configuring] +- [Nette Uygulamaları |application:how-it-works] +- [Bağımlılık Enjeksiyonu|dependency-injection:] - [Yardımcı Programlar |utils:] - [Formlar |forms:] -- [Veritabanı |database:core] -- [Kullanıcıların Kimliğini Doğrulama |security:authentication] -- [Erişim Kontrolü |security:authorization] -- [Oturumlar |http:Sessions] -- [HTTP isteği ve yanıtı |http:] -- [Önbellekleme |caching:] +- [Veritabanı |database:guide] +- [Kullanıcı Girişi |security:authentication] +- [Yetkilendirme |security:authorization] +- [http:Sessions] +- [HTTP İsteği & Yanıtı|http:] +- [Önbellek |caching:] - [E-posta Gönderme |mail:] -- [Şema: Veri Doğrulama |schema:] -- [PHP Kod Oluşturucu |php-generator:] +- [Schema: Veri Doğrulama |schema:] +- [PHP Kod Üreteci |php-generator:] -- [Latte: şablonlar |latte:] -- [Tracy: hata ayıklama |tracy:] -- [Tester: test |tester:] +- [Latte: Şablonlar |latte:] +- [Tracy: Kod Hata Ayıklama |tracy:] +- [Tester: Test Etme |tester:] diff --git a/nette/tr/@meta.texy b/nette/tr/@meta.texy new file mode 100644 index 0000000000..8dfe82f311 --- /dev/null +++ b/nette/tr/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokümantasyonu}} diff --git a/nette/tr/configuring.texy b/nette/tr/configuring.texy index 56dcedf7b6..347625dcca 100644 --- a/nette/tr/configuring.texy +++ b/nette/tr/configuring.texy @@ -1,35 +1,36 @@ -Nette Yapılandırma -****************** +Nette Yapılandırması +******************** .[perex] -Nette Framework'teki tüm yapılandırma seçeneklerine genel bir bakış. +Nette Framework'teki tüm yapılandırma seçeneklerine genel bakış. -Nette bileşenleri, genellikle [NEON |neon:format] dilinde yazılan yapılandırma dosyaları kullanılarak yapılandırılır. En iyi şekilde [bunu destekleyen editörlerde düzenlenirler |best-practices:editors-and-tools#ide-editor]. -Tam çerçeveyi kullanıyorsanız, yapılandırma [önyükleme sırasında yüklenecektir |application:bootstrap#di-container-configuration], değilse, yapılandırmanın [nasıl |bootstrap:] yükleneceğine bakın. +Nette bileşenlerini, genellikle [NEON formatında |neon:format] yazılan yapılandırma dosyaları aracılığıyla ayarlarız. En iyi şekilde [onu destekleyen düzenleyicilerde |best-practices:editors-and-tools#IDE Editörü] düzenlenirler. Tüm framework'ü kullanıyorsanız, yapılandırma [uygulama başlatılırken yüklenir |application:bootstrapping#DI Konteyner Yapılandırması], aksi takdirde [yapılandırmanın nasıl yükleneceği |bootstrap:] bölümünü okuyun. <pre> -"application .[prism-token prism-atrule]":[application:configuration#Application]: "Application .[prism-token prism-comment]"<br> -"constants .[prism-token prism-atrule]":[application:configuration#Constants]: "PHP sabitlerini tanımlar .[prism-token prism-comment]"<br> +"application .[prism-token prism-atrule]":[application:configuration#Application]: "Uygulama .[prism-token prism-comment]"<br> +"assets .[prism-token prism-atrule]":[assets:configuration]: "Assets .[prism-token prism-comment]"<br> +"constants .[prism-token prism-atrule]":[application:configuration#Sabitler]: "PHP sabitlerinin tanımı .[prism-token prism-comment]"<br> "database .[prism-token prism-atrule]":[database:configuration]: "Veritabanı .[prism-token prism-comment]"<br> -"decorator .[prism-token prism-atrule]":[dependency-injection:configuration#Decorator]: "Dekoratör .[prism-token prism-comment]"<br> -"di .[prism-token prism-atrule]":[dependency-injection:configuration#DI]: "DI Konteyner .[prism-token prism-comment]"<br> -"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Extensions]: "Ek DI uzantılarını yükleyin .[prism-token prism-comment]"<br> +"decorator .[prism-token prism-atrule]":[dependency-injection:configuration#Dekoratör Decorator]: "Dekoratör .[prism-token prism-comment]"<br> +"di .[prism-token prism-atrule]":[dependency-injection:configuration#DI]: "DI konteyner .[prism-token prism-comment]"<br> +"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Uzantılar]: "Ek DI uzantılarının kurulumu .[prism-token prism-comment]"<br> "forms .[prism-token prism-atrule]":[forms:configuration]: "Formlar .[prism-token prism-comment]"<br> -"http .[prism-token prism-atrule]":[http:configuration#HTTP Headers]: "HTTP Üstbilgileri .[prism-token prism-comment]"<br> -"includes .[prism-token prism-atrule]":[dependency-injection:configuration#Including files]: "Including files .[prism-token prism-comment]"<br> -"latte .[prism-token prism-atrule]":[application:configuration#Latte]: "Latte .[prism-token prism-comment]"<br> -"mail .[prism-token prism-atrule]":[mail:#Configuring]: "Mailing .[prism-token prism-comment]"<br> -"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#Parameters]: "Parameters .[prism-token prism-comment]"<br> -"php .[prism-token prism-atrule]":[application:configuration#PHP]: "PHP yapılandırma seçenekleri .[prism-token prism-comment]"<br> -"routing .[prism-token prism-atrule]":[application:configuration#Routing]: "Yönlendirme .[prism-token prism-comment]"<br> -"search .[prism-token prism-atrule]":[dependency-injection:configuration#Search]: "Otomatik hizmet kaydı .[prism-token prism-comment]"<br> -"security .[prism-token prism-atrule]":[security:configuration]: "Erişim Kontrolü .[prism-token prism-comment]"<br> -"services .[prism-token prism-atrule]":[dependency-injection:services]: "Services .[prism-token prism-comment]"<br> -"session .[prism-token prism-atrule]":[http:configuration#Session]: "Oturum .[prism-token prism-comment]"<br> -"tracy .[prism-token prism-atrule]":[tracy:configuring#Nette Framework]: "Tracy Hata Ayıklayıcı .[prism-token prism-comment]" +"http .[prism-token prism-atrule]":[http:configuration#HTTP Başlıkları]: "HTTP başlıkları .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[dependency-injection:configuration#Dosya Dahil Etme]: "Dosya dahil etme .[prism-token prism-comment]"<br> +"latte .[prism-token prism-atrule]":[application:configuration#Latte Şablonları]: "Latte şablonları .[prism-token prism-comment]"<br> +"mail .[prism-token prism-atrule]":[mail:#Yapılandırma]: "E-postalar .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#Parametreler]: "Parametreler .[prism-token prism-comment]"<br> +"php .[prism-token prism-atrule]":[application:configuration#PHP]: "PHP yapılandırması .[prism-token prism-comment]"<br> +"routing .[prism-token prism-atrule]":[application:configuration#Yönlendirme]: "Yönlendirme .[prism-token prism-comment]"<br> +"search .[prism-token prism-atrule]":[dependency-injection:configuration#Arama Search]: "Servislerin otomatik kaydı .[prism-token prism-comment]"<br> +"security .[prism-token prism-atrule]":[security:configuration]: "Erişim izinleri .[prism-token prism-comment]"<br> +"services .[prism-token prism-atrule]":[dependency-injection:services]: "Servisler .[prism-token prism-comment]"<br> +"session .[prism-token prism-atrule]":[http:configuration#Oturum Session]: "Oturum .[prism-token prism-comment]"<br> +"tracy .[prism-token prism-atrule]":[tracy:configuring#Nette Framework]: "Tracy hata ayıklayıcı .[prism-token prism-comment]" </pre> -`%`, you must escape it by doubling it to `%%` karakterini içeren bir dize yazmak için. .[note] +.[note] +`%` karakterini içeren bir karakter dizisi yazmak istiyorsanız, onu `%%` şeklinde çiftleyerek kaçış (escape) yapmanız gerekir. {{leftbar: @menu-topics}} diff --git a/nette/tr/glossary.texy b/nette/tr/glossary.texy index 4ddc3cd3a7..c699b3b2e0 100644 --- a/nette/tr/glossary.texy +++ b/nette/tr/glossary.texy @@ -2,154 +2,158 @@ Terimler Sözlüğü **************** -AJAX .[#toc-ajax] ------------------ -Asenkron JavaScript ve XML - her istek sırasında tüm sayfanın yeniden yüklenmesine gerek kalmadan HTTP protokolü üzerinden istemci-sunucu iletişimi için teknoloji. Kısaltmaya rağmen, [JSON |#JSON] formatı genellikle XML yerine kullanılır. +AJAX +---- +Asynchronous JavaScript and XML - istemci ve sunucu arasında, her istekte tüm sayfanın yeniden yüklenmesine gerek kalmadan HTTP protokolü üzerinden bilgi alışverişi sağlayan bir teknoloji. Adından yalnızca XML formatında veri gönderdiği anlaşılsa da, yaygın olarak [#JSON] formatı da kullanılır. -Sunucu Eylemi .[#toc-presenter-action] --------------------------------------- -[Sunucunun |#presenter] mantıksal parçası, bir ürün sayfasını göstermek, bir kullanıcının oturumunu kapatmak vb. gibi bir eylemi gerçekleştirir. Bir sunucunun daha fazla eylemi olabilir. +Presenter Eylemi +---------------- +Presenter'ın tek bir eylemi gerçekleştiren mantıksal bölümü. Örneğin, bir ürün sayfasını görüntüler, kullanıcının oturumunu kapatır vb. Bir presenter birden fazla eyleme sahip olabilir. BOM --- -Bayt sırası maskesi* bir dosyanın özel bir ilk karakteridir ve kodlamadaki bayt sırasını gösterir. Bazı editörler bunu otomatik olarak ekler, pratikte görünmezdir, ancak başlıklarda ve PHP içinden gönderilen çıktılarda sorunlara neden olur. Toplu olarak kaldırmak için [Code Checker |code-checker:] 'ı kullanabilirsiniz. +Sözde *byte order mark*, bir dosyadaki, kodlamada bayt sırasının göstergesi olarak kullanılan özel ilk karakterdir. Bazı düzenleyiciler bunu dosyalara ekler. Pratik olarak görünmezdir, ancak PHP'den çıktı ve başlıkların (header) gönderilmesinde sorunlara neden olur. Toplu olarak kaldırmak için [Code Checker |code-checker:] kullanabilirsiniz. -Kontrolör .[#toc-controller] ----------------------------- -Denetleyici, kullanıcıdan gelen istekleri işler ve bunlara dayanarak belirli uygulama mantığını (yani [modeli |#model]) çağırır, ardından veri oluşturma için [görünümü |#view] çağırır. Denetleyicilerin benzeri Nette Framework'teki [sunuculardır |#presenter]. +Controller +---------- +Kullanıcı isteklerini işleyen ve bunlara dayanarak uygun uygulama mantığını (yani [#model]) çağıran ve ardından verileri işlemek için [#View Görünüm]'den (görünüm) istekte bulunan denetleyici. Nette Framework'teki denetleyicilerin karşılığı [presenter'lardır |#Presenter]. -Siteler Arası Komut Dosyası Oluşturma (XSS) .[#toc-cross-site-scripting-xss] ----------------------------------------------------------------------------- -Siteler Arası Komut Dosyası Yazma (Cross-Site Scripting), yazılmamış girdileri kullanan bir site bozma yöntemidir. Bir saldırgan kendi HTML veya JavaScript kodunu enjekte edebilir ve sayfanın görünümünü değiştirebilir, hatta kullanıcılar hakkında hassas bilgiler toplayabilir. XSS'ye karşı koruma basittir: tüm dizelerin ve girdilerin tutarlı ve doğru bir şekilde kaçması. +Cross-Site Scripting (XSS) +-------------------------- +Cross-Site Scripting, işlenmemiş çıktıları kötüye kullanan web sitelerini ihlal etme yöntemidir. Saldırgan daha sonra sayfaya kendi kodunu ekleyebilir ve böylece sayfayı değiştirebilir veya hatta ziyaretçiler hakkında hassas veriler elde edebilir. XSS'e karşı yalnızca tüm karakter dizilerinin tutarlı ve doğru bir şekilde işlenmesiyle savunulabilir. -Nette Framework, sizi Siteler Arası Komut Dosyası risklerinden sonsuza kadar kurtaracak yepyeni bir [Bağlam Farkında |latte:safety-first#context-aware-escaping] Kaçış teknolojisi ile geliyor. Tüm girdileri belirli bir bağlama göre otomatik olarak kaçar, böylece bir kodlayıcının yanlışlıkla bir şeyi unutması imkansızdır. +Nette Framework, sizi Cross-Site Scripting riskinden kalıcı olarak kurtaracak devrim niteliğinde bir [Context-Aware Escaping |latte:safety-first#Bağlama Duyarlı Kaçış] teknolojisi sunar. Tüm çıktıları otomatik olarak işlediği için kodlayıcının bir şeyi unutması mümkün olmaz. -Siteler Arası İstek Sahteciliği (CSRF) .[#toc-cross-site-request-forgery-csrf] ------------------------------------------------------------------------------- -Siteler Arası İstek Sahteciliği saldırısı, saldırganın kurbanı, kurbanın tarayıcısında kurbanın o anda oturum açtığı sunucuya sessizce bir istek yürüten bir sayfayı ziyaret etmesi için kandırması ve sunucunun isteğin kurban tarafından kendi isteğiyle yapıldığına inanmasıdır. Sunucu, kurbanın kimliği altında ancak kurban farkında olmadan belirli bir eylem gerçekleştirir. Bu, veri değiştirme veya silme, mesaj gönderme vb. olabilir. +Cross-Site Request Forgery (CSRF) +--------------------------------- +Cross-Site Request Forgery saldırısı, saldırganın kurbanı, kurbanın oturum açtığı sunucuya kurbanın tarayıcısında gizlice bir istek yürüten bir sayfaya çekmesi ve sunucunun isteğin kurban tarafından kendi isteğiyle yapıldığına inanmasıdır. Ve böylece kurbanın kimliği altında, kurbanın haberi olmadan belirli bir eylemi gerçekleştirir. Bu, verilerin değiştirilmesi veya silinmesi, bir mesaj gönderilmesi vb. olabilir. -Nette Framework **sunuculardaki formları ve sinyalleri** bu tür saldırılara karşı otomatik olarak korur. Bu, başka bir etki alanından gönderilmelerini veya çağrılmalarını önleyerek yapılır. +Nette Framework, **formları ve presenter'lardaki sinyalleri otomatik olarak** bu tür saldırılara karşı korur. Bunu, başka bir alan adından gönderilmelerini veya çağrılmalarını engelleyerek yapar. -Bağımlılık Enjeksiyonu .[#toc-dependency-injection] ---------------------------------------------------- -Dependency Injection (DI), nesnelerin oluşturulmasını bağımlılıklarından nasıl ayıracağınızı anlatan bir tasarım modelidir. Yani, bir sınıf bağımlılıklarını oluşturmaktan veya başlatmaktan sorumlu değildir, bunun yerine bu bağımlılıklar harici kod ( [DI kapsayıcısı |#Dependency Injection container] içerebilir) tarafından sağlanır. Bunun avantajı, bağımlılıklar kolayca değiştirilebildiği ve kodun diğer bölümlerinden izole edildiği için daha fazla kod esnekliği, daha iyi okunabilirlik ve daha kolay uygulama testine olanak sağlamasıdır. Daha fazla bilgi için [Dependency Injection Nedir? |dependency-injection:introduction] +Dependency Injection +-------------------- +Dependency Injection (DI), nesnelerin oluşturulmasını bağımlılıklarından nasıl ayıracağımızı belirten bir tasarım desenidir. Yani, bir sınıf kendi bağımlılıklarını oluşturmaktan veya başlatmaktan sorumlu değildir; bunun yerine bu bağımlılıklar ona harici bir kod tarafından sağlanır (bu bir [DI konteyner |#Dependency Injection Konteyner] de olabilir). Avantajı, bağımlılıkların kolayca değiştirilebilir ve kodun diğer bölümlerinden izole edilmiş olması nedeniyle daha fazla kod esnekliği, daha iyi anlaşılırlık ve uygulamanın daha kolay test edilmesini sağlamasıdır. Daha fazla bilgi için [Dependency Injection Nedir? |dependency-injection:introduction] bölümüne bakın. -Bağımlılık Enjeksiyonu konteyneri .[#toc-dependency-injection-container] ------------------------------------------------------------------------- -Bağımlılık Enjeksiyonu konteyneri (DI konteyneri veya IoC konteyneri olarak da bilinir) bir uygulamadaki (veya [hizmetlerdeki |#service]) bağımlılıkların oluşturulmasını ve yönetilmesini sağlayan bir araçtır. Bir kapsayıcı genellikle hangi sınıfların diğer sınıflara bağımlı olduğunu, hangi özel bağımlılık uygulamalarının kullanılacağını ve bu bağımlılıkların nasıl oluşturulacağını tanımlayan bir yapılandırmaya sahiptir. Kapsayıcı daha sonra bu nesneleri oluşturur ve bunlara ihtiyaç duyan sınıflara sağlar. Daha fazla bilgi için [DI konteyneri nedir? |dependency-injection:container] +Dependency Injection Konteyner +------------------------------ +Dependency Injection konteyner (DI konteyner veya IoC konteyner olarak da bilinir), bir uygulamadaki bağımlılıkların (yani [servislerin |#Servis Service]) oluşturulmasını ve yönetimini sağlayan bir araçtır. Konteyner genellikle hangi sınıfların diğer sınıflara bağımlı olduğunu, hangi belirli bağımlılık uygulamalarının kullanılacağını ve bu bağımlılıkların nasıl oluşturulacağını tanımlayan bir yapılandırmaya sahiptir. Ardından konteyner bu nesneleri oluşturur ve onlara ihtiyaç duyan sınıflara sağlar. Daha fazla bilgi için [DI Konteyner Nedir? |dependency-injection:container] bölümüne bakın. -Kaçış .[#toc-escaping] ----------------------- -Kaçış, verilen bağlamda özel anlamı olan karakterlerin başka bir eşdeğer diziye dönüştürülmesidir. Örnek: Tırnak içine alınmış dizgiye tırnak yazmak istiyoruz. Tırnak işaretleri tırnak içine alınmış dizge bağlamında özel bir anlama sahip olduğundan, başka bir eşdeğer dizinin kullanılmasına ihtiyaç vardır. Somut dizi bağlam kuralları tarafından belirlenir (örneğin PHP'nin tırnak içine alınmış dizesinde `\"`, HTML özniteliklerinde `"` vb.) +Kaçış (Escaping) +---------------- +Kaçış (Escaping), belirli bir bağlamda özel anlamı olan karakterlerin, onlara karşılık gelen başka dizilere dönüştürülmesidir. Örneğin: tırnak işaretleriyle (`"`) sınırlanmış bir karakter dizisine tırnak işareti yazmak istediğimizde. Tırnak işaretlerinin karakter dizisi bağlamında özel bir anlamı olduğundan ve doğrudan yazılmaları karakter dizisinin sonu olarak anlaşılacağından, bunun yerine karşılık gelen başka bir diziyle yazılmaları gerekir. Hangi dizinin kullanılacağını bağlamın kuralları belirler. -Filtre (Eski adıyla Yardımcı) .[#toc-filter-formerly-helper] ------------------------------------------------------------- -Filtre işlevi. Şablonlarda [filtre |latte:syntax#filters], verileri çıktı formunda değiştirmeye veya biçimlendirmeye yardımcı olan bir işlevdir. Şablonların önceden tanımlanmış birkaç [standart filtresi |latte:filters] vardır. +Filtre (Filter) (eskiden helper) +-------------------------------- +Şablonlarda, [filtre |latte:syntax#Filtreler] terimi genellikle verileri nihai forma dönüştürmeye veya yeniden biçimlendirmeye yardımcı olan bir fonksiyonu ifade eder. Şablonlar birkaç [standart filtre |latte:filters] içerir. -Geçersiz kılma .[#toc-invalidation] ------------------------------------ -Yeniden işlenecek bir [snippet |#snippet] bildirimi. Başka bir bağlamda da bir önbelleğin temizlenmesi. +Geçersiz Kılma (Invalidation) +----------------------------- +Bir [snippet'in |#Snippet Kod Parçacığı] yeniden çizilmesi gerektiğinin bildirilmesi. Başka bir anlamda, önbellek (cache) içeriğinin silinmesi. -JSON .[#toc-json] ------------------ -JavaScript sözdizimine dayalı veri değişim formatı (onun alt kümesidir). Tam spesifikasyon www.json.org adresinde bulunabilir. +JSON +---- +JavaScript sözdizimine dayanan (onun bir alt kümesidir) veri alışverişi formatı. Kesin belirtimi www.json.org sayfasında bulabilirsiniz. -Bileşen .[#toc-component] -------------------------- -Bir uygulamanın yeniden kullanılabilir parçası. [Bileşenler |application:components] bölümünde açıklandığı gibi bir sayfanın görsel bir parçası olabilir veya bu terim [Bileşen |component-model:] sınıfı anlamına da gelebilir (böyle bir bileşenin görsel olması gerekmez). +Bileşen (Component) +------------------- +Uygulamanın yeniden kullanılabilir bir parçası. [Bileşen Yazma |application:components] bölümünde açıklandığı gibi sayfanın görsel bir parçası olabilir veya bileşen terimi altında [Component |component-model:] sınıfı da anlaşılabilir (böyle bir bileşen görsel olmak zorunda değildir). -Kontrol Karakterleri .[#toc-control-characters] ------------------------------------------------ -Kontrol karakterleri, bir metinde ortaya çıkabilen ve sonunda bazı sorunlara neden olabilen görünmez karakterlerdir. Bunların dosyalardan toplu olarak kaldırılması için [Code Checker'ı |code-checker:], bir değişkenden kaldırılması için [Strings::normalize() |utils:strings#normalize] fonksiyonunu kullanabilirsiniz. +Kontrol Karakterleri +-------------------- +Kontrol karakterleri, metinde bulunabilen ve muhtemelen sorunlara neden olabilen görünmez karakterlerdir. Bunları dosyalardan toplu olarak kaldırmak için [Code Checker |code-checker:] ve bir değişkenden kaldırmak için [Strings::normalize() |utils:strings#normalize] fonksiyonunu kullanabilirsiniz. -Etkinlikler .[#toc-events] --------------------------- -Olay, nesnede beklenen bir durumdur ve gerçekleştiğinde işleyiciler, yani olaya tepki veren geri aramalar ("örnek":https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38) olarak adlandırılır. Olay örneğin form gönderimi, kullanıcı girişi vb. olabilir. Olaylar bu nedenle bir *Kontrolün Tersine Çevrilmesi* biçimidir. +Olaylar (Events) +---------------- +Olay, bir nesnede beklenen bir durumdur ve gerçekleştiğinde, olaya tepki veren geri çağrılar (callback) olan sözde işleyiciler (handler) çağrılır ("örnek":https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38). Olay, örneğin bir formun gönderilmesi, bir kullanıcının oturum açması vb. olabilir. Olaylar bu nedenle bir *Inversion of Control* biçimidir. -Örneğin, `Nette\Security\User::login()` yönteminde bir kullanıcı girişi gerçekleşir. `User` nesnesi, herkesin geri arama ekleyebileceği bir dizi olan `$onLoggedIn` genel değişkenine sahiptir. Kullanıcı oturum açar açmaz, `login()` yöntemi dizideki tüm geri aramaları çağırır. Bir değişkenin `onXyz` biçimindeki adı, Nette genelinde kullanılan bir kuraldır. +Örneğin, kullanıcı girişi `Nette\Security\User::login()` metodunda gerçekleşir. `User` nesnesinin, herkesin bir geri çağrı (callback) ekleyebileceği bir dizi olan `$onLoggedIn` adında genel bir değişkeni vardır. Kullanıcı oturum açtığı anda, `login()` metodu dizideki tüm geri çağrıları çağırır. `onXyz` biçimindeki değişken adı, tüm Nette'de kullanılan bir konvansiyondur. -Latte .[#toc-latte] -------------------- -Şimdiye kadarki en yenilikçi [şablonlama sistemlerinden |latte:] biri. +Latte +----- +En gelişmiş [şablon sistemlerinden |latte:] biri. -Model .[#toc-model] -------------------- -Model, tüm uygulamanın veri ve işlev temelini temsil eder. Tüm uygulama mantığını içerir (bazen "iş mantığı" olarak da adlandırılır). Bu, **M**VC veya MPV'nin **M**'sidir. Herhangi bir kullanıcı eylemi (oturum açma, sepete bir şeyler koyma, bir veritabanı değerinin değiştirilmesi) modelin bir eylemini temsil eder. +Model +----- +Model, tüm uygulamanın veri ve özellikle işlevsel temelidir. Tüm uygulama mantığını (iş mantığı terimi de kullanılır) içerir. **M**VC veya MVP'deki **M**'dir. Herhangi bir kullanıcı eylemi (oturum açma, sepete ürün ekleme, veritabanındaki değeri değiştirme) bir model eylemini temsil eder. -Model kendi iç durumunu yönetir ve public bir arayüz sağlar. Bu arayüzü çağırarak onun durumunu alabilir veya değiştirebiliriz. Model, [view |#view] veya [controller'ın |#controller] varlığından haberdar değildir, model bunlardan tamamen bağımsızdır. +Model kendi iç durumunu yönetir ve dışarıya sabit bir arayüz (interface) sunar. Bu arayüzün fonksiyonlarını çağırarak durumunu sorgulayabilir veya değiştirebiliriz. Model, [#View Görünüm] (görünüm) veya [controller'ın |#Controller] varlığından haberdar değildir. -Model-View-Controller .[#toc-model-view-controller] ---------------------------------------------------- -GUI uygulamalarının geliştirilmesinde, akış kontrol kodunu ([kontrolör |#controller]) uygulama mantığı kodundan ([model |#model]) ve veri işleme kodundan ([görünüm |#view]) ayırmak için ortaya çıkan yazılım mimarisi. Bu şekilde kod daha iyi anlaşılabilir, gelecekteki geliştirmeleri kolaylaştırır ve ayrı parçaların ayrı ayrı test edilmesini sağlar. +Model-View-Controller +--------------------- +Grafik arayüzlü uygulamalarda işleme kodunu ([#controller]) uygulama mantığı kodundan ([#model]) ve veri görüntüleme kodundan ([#View Görünüm]) ayırma ihtiyacından doğan yazılım mimarisi. Bu, uygulamayı daha anlaşılır hale getirir, gelecekteki geliştirmeyi kolaylaştırır ve tek tek parçaların ayrı ayrı test edilmesini sağlar. -Model-Görünüm-Sunucusu .[#toc-model-view-presenter] ---------------------------------------------------- -[Model-View-Controller |#Model-View-Controller] tabanlı mimari. +Model-View-Presenter +-------------------- +[#Model-View-Controller]'dan türetilen mimari. -Modül .[#toc-module] --------------------- -Nette Framework'teki [modül |application:modules], bir sunumcuya veri sunan sunumcuların ve şablonların, nihayetinde de bileşenlerin ve modellerin bir koleksiyonunu temsil eder. Yani bir uygulamanın belirli bir mantıksal parçasıdır. +Modül +----- +Modül, uygulamanın mantıksal bir bölümünü temsil eder. Tipik bir düzende, belirli bir işlevsellik alanını ele alan bir grup presenter ve şablondur. Modülleri [ayrı dizinlere |application:directory-structure#Presenter lar ve Şablonlar] yerleştiririz, örneğin `Front/`, `Admin/` veya `Shop/`. -Örneğin, bir e-mağazanın üç modülü olabilir: -1) Sepetli ürün kataloğu. -2) Müşteri için yönetim. -3) Dükkan sahibi için yönetim. +Örneğin, bir e-mağazayı şunlara ayırırız: +- Ürünleri görüntülemek ve satın almak için Frontend (`Shop/`) +- Siparişleri yönetmek için Müşteri bölümü (`Customer/`) +- Operatör için Yönetim (`Admin/`) +Teknik olarak bunlar sıradan dizinlerdir, ancak net bölümleme sayesinde uygulamanın ölçeklenmesine yardımcı olurlar. `Admin:Product:List` presenter'ı fiziksel olarak örneğin `app/Presentation/Admin/Product/List/` dizininde bulunur (bkz. [presenter eşlemesi |application:directory-structure#Presenter Eşlemesi]). -İsim Alanı .[#toc-namespace] ----------------------------- -İsim alanı, PHP dilinin 5.3 sürümünden itibaren ve diğer bazı programlama dillerinde de bulunan bir özelliktir. Farklı kütüphaneleri birlikte kullanırken isim çakışmalarını (örneğin aynı isimde iki sınıf) önlemeye yardımcı olur. Daha fazla ayrıntı için PHP [belgelerine |https://www.php.net/manual/en/language.namespaces.rationale.php] bakınız. + +Namespace (Ad Alanı) +-------------------- +PHP sürüm 5.3'ten ve diğer bazı programlama dillerinden itibaren dilin bir parçası olan ad alanı, farklı kütüphanelerde aynı şekilde adlandırılmış sınıfların ad çakışması olmadan kullanılmasını sağlar. Bkz. [PHP belgeleri |https://www.php.net/manual/en/language.namespaces.rationale.php]. -Sunucu .[#toc-presenter] ------------------------- -Sunucu, yönlendirici tarafından HTTP isteğinden çevrilen [isteği |api:Nette\Application\Request] alan ve bir [yanıt |api:Nette\Application\Response] oluşturan bir nesnedir. Yanıt bir HTML sayfası, resim, XML belgesi, dosya, JSON, yönlendirme veya aklınıza ne gelirse olabilir. +Presenter +--------- +Presenter, yönlendirici tarafından HTTP isteğinden çevrilen [isteği |api:Nette\Application\Request] alan ve bir [yanıt |api:Nette\Application\Response] oluşturan bir nesnedir. Yanıt bir HTML sayfası, bir resim, bir XML belgesi, diskteki bir dosya, JSON, bir yönlendirme veya aklınıza gelebilecek herhangi bir şey olabilir. -Sunucu ile genellikle [api:Nette\Application\UI\Presenter] sınıfının soyundan gelen bir kişi kastedilir. İsteklere göre uygun [eylemleri |application:presenters#life-cycle-of-presenter] çalıştırır ve şablonları işler. +Genellikle presenter terimi altında [api:Nette\Application\UI\Presenter] sınıfının bir alt sınıfı anlaşılır. Gelen isteklere göre ilgili [eylemleri |application:presenters#Presenter Yaşam Döngüsü] çalıştırır ve şablonları işler. -Yönlendirici .[#toc-router] ---------------------------- -HTTP isteği / URL ve sunucu eylemi arasında çift yönlü çevirmen. Çift yönlü olması, yalnızca HTTP isteğinden bir [sunum eylem |#presenter action] i türetmenin değil, aynı zamanda bir eylem için uygun URL oluşturmanın da mümkün olduğu anlamına gelir. [URL yönlendirme |application:routing] hakkında daha fazla bilgi için bkz. +Yönlendirici (Router) +--------------------- +HTTP isteği / URL ile presenter eylemi arasında çift yönlü bir çeviricidir. Çift yönlü olması, hem bir HTTP isteğinden [presenter eylemini |#Presenter Eylemi] türetmenin hem de tersine, bir eyleme karşılık gelen URL'yi oluşturmanın mümkün olduğu anlamına gelir. Daha fazla bilgi [URL Yönlendirme |application:routing] bölümünde. -AynıSite Çerezi .[#toc-samesite-cookie] ---------------------------------------- -SameSite çerezleri, sayfanın yüklenmesine neyin yol açtığını tanımak için bir mekanizma sağlar. Üç değere sahip olabilir: `Lax`, `Strict` ve `None` (ikincisi HTTPS gerektirir). Sayfaya istek doğrudan siteden gelirse veya kullanıcı sayfayı doğrudan adres çubuğuna yazarak veya bir yer imine tıklayarak açarsa, tarayıcı tüm çerezleri sunucuya gönderir (yani `Lax`, `Strict` ve `None` bayraklarıyla). Kullanıcı başka bir siteden gelen bir bağlantı aracılığıyla siteye tıklarsa, `Lax` ve `None` bayraklı çerezler sunucuya iletilir. İstek, başka bir siteden bir POST formu gönderme, bir iframe içine yükleme, JavaScript kullanma vb. gibi başka yollarla yapılırsa, yalnızca `None` bayrağına sahip çerezler gönderilir. +SameSite Çerezi +--------------- +SameSite çerezleri, sayfanın yüklenmesine neyin yol açtığını tanımak için bir mekanizma sağlar. Üç değeri olabilir: `Lax`, `Strict` ve `None` (bu HTTPS gerektirir). Sayfa isteği doğrudan web sitesinden geliyorsa veya kullanıcı sayfayı doğrudan adres çubuğuna yazarak veya bir yer imine tıklayarak açarsa, tarayıcı sunucuya tüm çerezleri gönderir (yani `Lax`, `Strict` ve `None` bayraklarıyla). Kullanıcı başka bir web sitesinden bir bağlantı aracılığıyla web sitesine tıklarsa, sunucuya `Lax` ve `None` bayraklarına sahip çerezler iletilir. İstek başka bir şekilde, örneğin başka bir web sitesinden bir POST formu göndererek, bir iframe içinde yükleyerek, JavaScript kullanarak vb. ortaya çıkarsa, yalnızca `None` bayrağına sahip çerezler gönderilir. -Hizmet .[#toc-service] ----------------------- -Bağımlılık Enjeksiyonu bağlamında bir hizmet, bir DI konteyneri tarafından oluşturulan ve yönetilen bir nesneyi ifade eder. Bir hizmet, örneğin test amacıyla veya bir uygulamanın davranışını değiştirmek için, hizmeti kullanan kodu değiştirmek zorunda kalmadan kolayca başka bir uygulama ile değiştirilebilir. +Servis (Service) +---------------- +Dependency Injection bağlamında, servis terimi DI konteyner tarafından oluşturulan ve yönetilen bir nesneyi ifade eder. Servis, örneğin test amacıyla veya servisi kullanan kodu değiştirmeye gerek kalmadan uygulamanın davranışını değiştirmek için başka bir uygulamayla kolayca değiştirilebilir. -Snippet .[#toc-snippet] +Snippet (Kod Parçacığı) ----------------------- -Bir [AJAX |#AJAX] isteği sırasında ayrı olarak yeniden oluşturulabilen bir sayfanın parçacığı. +Bir AJAX isteği sırasında bağımsız olarak yeniden çizilebilen sayfa bölümü, kesit. + + +View (Görünüm) +-------------- +View, yani görünüm, isteğin sonucunu görüntülemekten sorumlu olan uygulama katmanıdır. Genellikle bir şablon sistemi kullanır ve hangi bileşenin veya modelden elde edilen sonucun nasıl görüntüleneceğini bilir. -Görünüm .[#toc-view] --------------------- -Görünüm, istek sonuçlarının işlenmesinden sorumlu olan bir uygulama katmanıdır. Genellikle bir şablonlama sistemi kullanır ve bileşenlerini veya modelden alınan sonuçları nasıl oluşturacağını bilir. diff --git a/nette/tr/installation.texy b/nette/tr/installation.texy index 9acc4f5ba5..3c82d3c605 100644 --- a/nette/tr/installation.texy +++ b/nette/tr/installation.texy @@ -2,66 +2,66 @@ Nette Kurulumu ************** .[perex] -Mevcut projenizde Nette'in avantajlarından yararlanmak mı istiyorsunuz yoksa Nette tabanlı yeni bir proje mi oluşturmayı planlıyorsunuz? Bu kılavuz, kurulum boyunca size adım adım yol gösterecektir. +Mevcut projenizde Nette'nin avantajlarından yararlanmak mı istiyorsunuz, yoksa Nette tabanlı yeni bir proje mi oluşturacaksınız? Bu kılavuz size adım adım kurulum sürecinde rehberlik edecektir. -Projenize Nette Nasıl Eklenir .[#toc-how-to-add-nette-to-your-project] ----------------------------------------------------------------------- +Nette'yi Projenize Nasıl Eklersiniz +----------------------------------- -Nette, PHP için kullanışlı ve sofistike paketlerden (kütüphaneler) oluşan bir koleksiyon sunar. Bunları projenize dahil etmek için aşağıdaki adımları izleyin: +Nette, PHP için kullanışlı ve gelişmiş paketler (kütüphaneler) koleksiyonu sunar. Bunları projenize dahil etmek için aşağıdaki adımları izleyin: -1) **[Composer |best-practices:composer]'ı Kurun:** Bu araç, projeniz için gerekli olan kütüphanelerin kolay kurulumu, güncellenmesi ve yönetimi için gereklidir. +1) **[Composer |best-practices:composer]'ı hazırlayın:** Bu araç, projeniz için gerekli kütüphanelerin kolay kurulumu, güncellenmesi ve yönetimi için gereklidir. -2) **Bir [paket |www:packages] seçin:** Diyelim ki `nette/utils` paketindeki [Finder |utils:finder] 'ın mükemmel bir şekilde yaptığı dosya sisteminde gezinmeniz gerekiyor. Paket adını belgelerinin sağ sütununda bulabilirsiniz. +2) **Bir [paket |www:packages] seçin:** Dosya sisteminde gezinmeniz gerektiğini varsayalım, bunu `nette/utils` paketindeki [Finder |utils:finder] mükemmel bir şekilde yapar. Paketin adını belgelerinin sağ sütununda görebilirsiniz. -3) **Paketi yükleyin:** Bu komutu projenizin kök dizininde çalıştırın: +3) **Paketi kurun:** Projenizin kök dizininde şu komutu çalıştırın: ```shell composer require nette/utils ``` -Grafik arayüzü mü tercih ediyorsunuz? PhpStrom ortamında paket yükleme [kılavuzuna |https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] göz atın. +Grafik arayüz mü tercih ediyorsunuz? PhpStorm ortamında paketlerin kurulumu için [kılavuza |https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] göz atın. -Nette ile Yeni Bir Projeye Nasıl Başlanır .[#toc-how-to-start-a-new-project-with-nette] ---------------------------------------------------------------------------------------- +Nette ile Yeni Bir Proje Nasıl Başlatılır +----------------------------------------- -Nette platformunda tamamen yeni bir proje oluşturmak istiyorsanız, önceden ayarlanmış iskelet [Web Projesini |https://github.com/nette/web-project] kullanmanızı öneririz: +Nette platformunda tamamen yeni bir proje oluşturmak istiyorsanız, önceden ayarlanmış [Web Projesi |https://github.com/nette/web-project] iskeletini kullanmanızı öneririz: -1) **[Composer |best-practices:composer]'ı kurun.** +1) **[Composer |best-practices:composer]'ı hazırlayın.** -2) **Komut satırını** açın ve web sunucunuzun kök dizinine gidin, örneğin, `/etc/var/www`, `C:/xampp/htdocs`, `/Library/WebServer/Documents`. +2) **Komut istemcisini açın** ve web sunucunuzun kök dizinine gidin, örn. `/etc/var/www`, `C:/xampp/htdocs`, `/Library/WebServer/Documents`. -3) **Bu komutu kullanarak** Projeyi oluşturun: +3) **Projeyi oluşturun** şu komutu kullanarak: ```shell -composer create-project nette/web-project PROJECT_NAME +composer create-project nette/web-project PROJE_ADI ``` -4) **Composer kullanmıyor musunuz?** Sadece [Web Projesini ZIP formatında |https://github.com/nette/web-project/archive/preloaded.zip] indirin ve çıkarın. Ama bize güvenin, Composer buna değer! +4) **Composer kullanmıyor musunuz?** Sadece [ZIP formatında Web Projesi |https://github.com/nette/web-project/archive/preloaded.zip]'ni indirin ve açın. Ama inanın, Composer buna değer! -5) **İzinleri ayarlama:** macOS veya Linux sistemlerinde, dizinler için [yazma izin |nette:troubleshooting#Setting directory permissions] lerini ayarlayın. +5) **İzinleri ayarlama:** macOS veya Linux sistemlerinde, dizinlere [yazma izinlerini |nette:troubleshooting#Dizin İzinlerini Ayarlama] ayarlayın. -6) **Projeyi bir tarayıcıda açın:** `http://localhost/PROJECT_NAME/www/` URL'sini girin. İskeletin açılış sayfasını göreceksiniz: +6) **Projeyi tarayıcıda açma:** `http://localhost/PROJE_ADI/www/` URL'sini girin ve iskeletin başlangıç sayfasını göreceksiniz: -[* qs-welcome.webp .{url: http://localhost/PROJECT_NAME/www/} *] +[* qs-welcome.webp .{url: http://localhost/PROJE_ADI/www/} *] -Tebrikler! Web siteniz artık geliştirilmeye hazır. Karşılama şablonunu kaldırmaktan çekinmeyin ve uygulamanızı oluşturmaya başlayın. +Tebrikler! Web siteniz şimdi geliştirilmeye hazır. Karşılama şablonunu kaldırabilir ve uygulamanızı oluşturmaya başlayabilirsiniz. -Nette'in avantajlarından biri de projenin yapılandırmaya gerek kalmadan hemen çalışmasıdır. Ancak, herhangi bir sorunla karşılaşırsanız, [genel sorun çözümlerine |nette:troubleshooting#nette-is-not-working-white-page-is-displayed] bakmayı düşünün. +Nette'nin avantajlarından biri, projenin herhangi bir yapılandırmaya gerek kalmadan hemen çalışmasıdır. Ancak sorunlarla karşılaşırsanız, [yaygın sorunların çözümlerine |nette:troubleshooting#Nette Çalışmıyor Beyaz Sayfa Görünüyor] göz atmayı deneyin. .[note] -Nette ile başlıyorsanız, [İlk Uygulamanızı Oluşturun |quickstart:] eğitimine devam etmenizi öneririz. +Nette'ye yeni başlıyorsanız, [İlk Uygulamanızı Yazma eğitimi |quickstart:] ile devam etmenizi öneririz. -Araçlar ve Öneriler .[#toc-tools-and-recommendations] ------------------------------------------------------ +Araçlar ve Öneriler +------------------- -Nette ile verimli çalışmak için aşağıdaki araçları öneriyoruz: +Nette ile verimli çalışmak için aşağıdaki araçları öneririz: -- [Nette için eklentilere sahip yüksek kaliteli IDE |best-practices:editors-and-tools] +- [Nette eklentileriyle kaliteli bir IDE |best-practices:editors-and-tools] - Sürüm kontrol sistemi Git -- [Besteci |best-practices:composer] +- [Composer |best-practices:composer] {{leftbar: www:@menu-common}} diff --git a/nette/tr/introduction-to-object-oriented-programming.texy b/nette/tr/introduction-to-object-oriented-programming.texy new file mode 100644 index 0000000000..78f6067bf8 --- /dev/null +++ b/nette/tr/introduction-to-object-oriented-programming.texy @@ -0,0 +1,841 @@ +Nesne Yönelimli Programlamaya Giriş +*********************************** + +.[perex] +"OOP" terimi, kodu organize etme ve yapılandırma yöntemi olan nesne yönelimli programlamayı ifade eder. OOP, bir programı bir dizi komut ve fonksiyon yerine, birbirleriyle iletişim kuran nesneler topluluğu olarak görmemizi sağlar. + +OOP'de "nesne", verileri ve bu verilerle çalışan fonksiyonları içeren bir birimdir. Nesneler, nesneler için taslak veya şablon olarak anlayabileceğimiz "sınıflara" göre oluşturulur. Bir sınıfımız olduğunda, bu sınıfa göre oluşturulmuş belirli bir nesne olan "örneğini" (instance) oluşturabiliriz. + +PHP'de basit bir sınıfı nasıl oluşturabileceğimizi görelim. Bir sınıf tanımlarken, "class" anahtar kelimesini, ardından sınıf adını ve ardından fonksiyonları (bunlara "metotlar" denir) ve sınıf değişkenlerini (bunlara "özellikler" veya İngilizce "property" denir) çevreleyen küme parantezlerini kullanırız: + +```php +class Araba +{ + function kornaCal() + { + echo 'Bip bip!'; + } +} +``` + +Bu örnekte, `kornaCal` adlı bir fonksiyon (veya "metot") içeren `Araba` adında bir sınıf oluşturduk. + +Her sınıf yalnızca bir ana görevi çözmelidir. Eğer bir sınıf çok fazla şey yapıyorsa, onu daha küçük, uzmanlaşmış sınıflara bölmek uygun olabilir. + +Sınıfları genellikle ayrı dosyalarda saklarız, böylece kod düzenli olur ve içinde gezinmesi kolay olur. Dosya adı sınıf adıyla eşleşmelidir, bu nedenle `Araba` sınıfı için dosya adı `Araba.php` olacaktır. + +Sınıfları adlandırırken, "PascalCase" kuralına uymak iyidir, bu da addaki her kelimenin büyük harfle başladığı ve aralarında alt çizgi veya başka ayırıcılar olmadığı anlamına gelir. Metotlar ve özellikler "camelCase" kuralını kullanır, yani küçük harfle başlarlar. + +PHP'deki bazı metotların özel görevleri vardır ve `__` (iki alt çizgi) önekiyle işaretlenirler. En önemli özel metotlardan biri, `__construct` olarak işaretlenen "yapıcı metot"tur (constructor). Yapıcı metot, bir sınıfın yeni bir örneğini oluşturduğunuzda otomatik olarak çağrılan bir metottur. + +Yapıcı metodu genellikle nesnenin başlangıç durumunu ayarlamak için kullanırız. Örneğin, bir kişiyi temsil eden bir nesne oluşturduğunuzda, yaşını, adını veya diğer özelliklerini ayarlamak için yapıcı metodu kullanabilirsiniz. + +PHP'de yapıcı metodu nasıl kullanacağımızı görelim: + +```php +class Kisi +{ + private $yas; + + function __construct($yas) + { + $this->yas = $yas; + } + + function kacYasindasin() + { + return $this->yas; + } +} + +$kisi = new Kisi(25); +echo $kisi->kacYasindasin(); // Yazdırır: 25 +``` + +Bu örnekte, `Kisi` sınıfının bir özelliği (değişkeni) `$yas` ve ayrıca bu özelliği ayarlayan bir yapıcı metodu vardır. `kacYasindasin()` metodu daha sonra kişinin yaşına erişim sağlar. + +`$this` sözde değişkeni, nesnenin özelliklerine ve metotlarına erişmek için sınıf içinde kullanılır. + +`new` anahtar kelimesi, bir sınıfın yeni bir örneğini oluşturmak için kullanılır. Yukarıdaki örnekte, 25 yaşında yeni bir kişi oluşturduk. + +Ayrıca, nesne oluşturulurken belirtilmemişse, yapıcı metot parametreleri için varsayılan değerler ayarlayabilirsiniz. Örneğin: + +```php +class Kisi +{ + private $yas; + + function __construct($yas = 20) + { + $this->yas = $yas; + } + + function kacYasindasin() + { + return $this->yas; + } +} + +$kisi = new Kisi; // eğer hiçbir argüman geçmiyorsak, parantezler atlanabilir +echo $kisi->kacYasindasin(); // Yazdırır: 20 +``` + +Bu örnekte, `Kisi` nesnesini oluştururken yaşı belirtmezseniz, varsayılan değer olan 20 kullanılacaktır. + +Hoş olan şey, özelliğin yapıcı metot aracılığıyla başlatılmasıyla birlikte tanımının bu şekilde kısaltılıp basitleştirilebilmesidir: + +```php +class Kisi +{ + function __construct( + private $yas = 20, + ) { + } +} +``` + +Tamamlamak gerekirse, yapıcı metotlara ek olarak, nesnelerin yıkıcı metotları (destructor) da olabilir (`__destruct` metodu), bunlar nesne bellekten serbest bırakılmadan önce çağrılır. + + +İsim Alanları +------------- + +İsim alanları (veya İngilizce "namespaces"), ilgili sınıfları, fonksiyonları ve sabitleri organize etmemize ve gruplandırmamıza olanak tanırken aynı zamanda isim çakışmalarını önlememizi sağlar. Bunları bilgisayarınızdaki klasörler gibi düşünebilirsiniz; her klasör belirli bir projeye veya konuya ait dosyaları içerir. + +İsim alanları özellikle büyük projelerde veya üçüncü taraf kütüphaneleri kullanırken sınıf adlarında çakışmaların meydana gelebileceği durumlarda kullanışlıdır. + +Projenizde `Araba` adında bir sınıfınız olduğunu ve bunu `Tasima` adlı bir isim alanına yerleştirmek istediğinizi hayal edin. Bunu şu şekilde yaparsınız: + +```php +namespace Tasima; + +class Araba +{ + function kornaCal() + { + echo 'Bip bip!'; + } +} +``` + +`Araba` sınıfını başka bir dosyada kullanmak isterseniz, sınıfın hangi isim alanından geldiğini belirtmeniz gerekir: + +```php +$araba = new Tasima\Araba; +``` + +Basitleştirmek için, dosyanın başında belirli bir isim alanından hangi sınıfı kullanmak istediğinizi belirtebilirsiniz, bu da tam yolu belirtme zorunluluğu olmadan örnekler oluşturmanıza olanak tanır: + +```php +use Tasima\Araba; + +$araba = new Araba; +``` + + +Kalıtım +------- + +Kalıtım, nesne yönelimli programlamanın bir aracıdır ve mevcut sınıflara dayanarak yeni sınıflar oluşturmaya, onların özelliklerini ve metotlarını devralmaya ve bunları ihtiyaca göre genişletmeye veya yeniden tanımlamaya olanak tanır. Kalıtım, kodun yeniden kullanılabilirliğini ve sınıfların hiyerarşisini sağlamaya olanak tanır. + +Basitçe söylemek gerekirse, bir sınıfımız varsa ve ondan türetilmiş ancak birkaç değişiklikle başka bir sınıf oluşturmak istiyorsak, yeni sınıfı orijinal sınıftan "kalıtabiliriz". + +PHP'de kalıtımı `extends` anahtar kelimesi kullanarak gerçekleştiririz. + +`Kisi` sınıfımız yaş bilgisini saklar. `Kisi` sınıfını genişleten ve eğitim alanı hakkında bilgi ekleyen başka bir `Ogrenci` sınıfımız olabilir. + +Bir örneğe bakalım: + +```php +class Kisi +{ + private $yas; + + function __construct($yas) + { + $this->yas = $yas; + } + + function bilgileriYazdir() + { + echo "Yaş: {$this->yas} yıl\n"; + } +} + +class Ogrenci extends Kisi +{ + private $bolum; + + function __construct($yas, $bolum) + { + parent::__construct($yas); + $this->bolum = $bolum; + } + + function bilgileriYazdir() + { + parent::bilgileriYazdir(); + echo "Eğitim Alanı: {$this->bolum} \n"; + } +} + +$ogrenci = new Ogrenci(20, 'Bilgisayar Bilimi'); +$ogrenci->bilgileriYazdir(); +``` + +Bu kod nasıl çalışır? + +- `Kisi` sınıfını genişletmek için `extends` anahtar kelimesini kullandık, bu da `Ogrenci` sınıfının `Kisi` sınıfından tüm metotları ve özellikleri devraldığı anlamına gelir. + +- `parent::` anahtar kelimesi, üst sınıftan metotları çağırmamıza olanak tanır. Bu durumda, `Ogrenci` sınıfına kendi işlevselliğimizi eklemeden önce `Kisi` sınıfından yapıcı metodu çağırdık. Ve benzer şekilde, öğrenci hakkındaki bilgileri yazdırmadan önce atanın `bilgileriYazdir()` metodunu çağırdık. + +Kalıtım, sınıflar arasında "bir" ilişkisi ("is-a" relationship) olduğu durumlar için tasarlanmıştır. Örneğin, `Ogrenci` bir `Kisi`dir. Kedi bir hayvandır. Kodda bir nesne (örneğin, "Kisi") beklediğimiz durumlarda, onun yerine kalıtılmış bir nesne (örneğin, "Ogrenci") kullanma imkanı verir. + +Kalıtımın ana amacının kod tekrarını önlemek **olmadığını** anlamak önemlidir. Aksine, kalıtımın yanlış kullanımı karmaşık ve bakımı zor koda yol açabilir. Sınıflar arasında "bir" ilişkisi yoksa, kalıtım yerine kompozisyonu düşünmeliyiz. + +`Kisi` ve `Ogrenci` sınıflarındaki `bilgileriYazdir()` metotlarının biraz farklı bilgiler yazdırdığına dikkat edin. Ve bu metodun başka uygulamalarını sağlayacak başka sınıflar (örneğin, `Calisan`) ekleyebiliriz. Farklı sınıfların nesnelerinin aynı metoda farklı şekillerde yanıt verme yeteneğine polimorfizm denir: + +```php +$kisiler = [ + new Kisi(30), + new Ogrenci(20, 'Bilgisayar Bilimi'), + new Calisan(45, 'Müdür'), +]; + +foreach ($kisiler as $kisi) { + $kisi->bilgileriYazdir(); +} +``` + + +Kompozisyon +----------- + +Kompozisyon, başka bir sınıfın özelliklerini ve metotlarını devralmak yerine, onun örneğini kendi sınıfımızda basitçe kullandığımız bir tekniktir. Bu, karmaşık kalıtım yapıları oluşturmaya gerek kalmadan birden fazla sınıfın işlevselliğini ve özelliklerini birleştirmemizi sağlar. + +Bir örneğe bakalım. Bir `Motor` sınıfımız ve bir `Araba` sınıfımız var. "Araba bir Motordur" demek yerine, "Araba bir Motora sahiptir" diyoruz, bu tipik bir kompozisyon ilişkisidir. + +```php +class Motor +{ + function calistir() + { + echo 'Motor çalışıyor.'; + } +} + +class Araba +{ + private $motor; + + function __construct() + { + $this->motor = new Motor; + } + + function baslat() + { + $this->motor->calistir(); + echo 'Araba sürüşe hazır!'; + } +} + +$araba = new Araba; +$araba->baslat(); +``` + +Burada `Araba`, `Motor`un tüm özelliklerine ve metotlarına sahip değildir, ancak `$motor` özelliği aracılığıyla ona erişimi vardır. + +Kompozisyonun avantajı, tasarımda daha fazla esneklik ve gelecekte daha iyi değişiklik yapma imkanıdır. + + +Görünürlük +---------- + +PHP'de, bir sınıfın özellikleri, metotları ve sabitleri için "görünürlük" tanımlayabilirsiniz. Görünürlük, bu öğelere nereden erişebileceğinizi belirler. + +1. **Public:** Bir öğe `public` olarak işaretlenmişse, bu ona sınıf dışından bile her yerden erişebileceğiniz anlamına gelir. + +2. **Protected:** `protected` olarak işaretlenmiş bir öğeye yalnızca tanımlandığı sınıf içinden ve tüm alt sınıflarından (bu sınıftan kalıtım alan sınıflar) erişilebilir. + +3. **Private:** Bir öğe `private` ise, ona yalnızca tanımlandığı sınıfın içinden erişebilirsiniz. + +Görünürlüğü belirtmezseniz, PHP onu otomatik olarak `public` olarak ayarlar. + +Örnek bir koda bakalım: + +```php +class GorunurlukGosterimi +{ + public $publicOzellik = 'Public'; + protected $protectedOzellik = 'Protected'; + private $privateOzellik = 'Private'; + + public function ozellikleriYazdir() + { + echo $this->publicOzellik; // Çalışır + echo $this->protectedOzellik; // Çalışır + echo $this->privateOzellik; // Çalışır + } +} + +$nesne = new GorunurlukGosterimi; +$nesne->ozellikleriYazdir(); +echo $nesne->publicOzellik; // Çalışır +// echo $nesne->protectedOzellik; // Hata verir +// echo $nesne->privateOzellik; // Hata verir +``` + +Sınıf kalıtımı ile devam edelim: + +```php +class AltSinif extends GorunurlukGosterimi +{ + public function ozellikleriYazdir() + { + echo $this->publicOzellik; // Çalışır + echo $this->protectedOzellik; // Çalışır + // echo $this->privateOzellik; // Hata verir + } +} +``` + +Bu durumda, `AltSinif` sınıfındaki `ozellikleriYazdir()` metodu public ve protected özelliklere erişebilir, ancak üst sınıfın private özelliklerine erişemez. + +Veriler ve metotlar mümkün olduğunca gizli tutulmalı ve yalnızca tanımlanmış bir arayüz aracılığıyla erişilebilir olmalıdır. Bu, kodun geri kalanını etkilemeden sınıfın dahili uygulamasını değiştirmenize olanak tanır. + + +`final` Anahtar Kelimesi +------------------------ + +PHP'de, bir sınıfın, metodun veya sabitin kalıtılmasını veya üzerine yazılmasını önlemek istiyorsak `final` anahtar kelimesini kullanabiliriz. Bir sınıfı `final` olarak işaretlediğimizde, genişletilemez. Bir metodu `final` olarak işaretlediğimizde, alt sınıfta üzerine yazılamaz. + +Belirli bir sınıfın veya metodun daha fazla değiştirilmeyeceğini bilmek, olası çakışmalardan endişe etmeden daha kolay değişiklikler yapmamızı sağlar. Örneğin, herhangi bir alt sınıfının zaten aynı ada sahip bir metodu olacağı ve bir çakışma olacağı endişesi olmadan yeni bir metot ekleyebiliriz. Veya metodun parametrelerini değiştirebiliriz, çünkü yine alt sınıftaki üzerine yazılmış metotla bir uyumsuzluğa neden olma riski yoktur. + +```php +final class FinalSinif +{ +} + +// Aşağıdaki kod hata verir, çünkü final bir sınıftan kalıtım alamayız. +class FinalSinifAltSinifi extends FinalSinif +{ +} +``` + +Bu örnekte, final sınıf `FinalSinif`'tan kalıtım alma girişimi bir hata verecektir. + + +Statik Özellikler ve Metotlar +----------------------------- + +PHP'de bir sınıfın "statik" öğelerinden bahsettiğimizde, sınıfın kendisine ait olan ve bu sınıfın belirli bir örneğine ait olmayan metotları ve özellikleri kastederiz. Bu, onlara erişmek için sınıfın bir örneğini oluşturmanız gerekmediği anlamına gelir. Bunun yerine, onları doğrudan sınıf adı üzerinden çağırır veya erişirsiniz. + +Statik öğeler sınıfa ait olduğundan ve örneklerine ait olmadığından, statik metotlar içinde `$this` sözde değişkenini kullanamayacağınızı unutmayın. + +Statik özelliklerin kullanılması [tuzaklarla dolu anlaşılmaz koda|dependency-injection:global-state] yol açar, bu yüzden onları asla kullanmamalısınız ve burada kullanım örneğini göstermeyeceğiz. Buna karşılık, statik metotlar kullanışlıdır. Kullanım örneği: + +```php +class HesapMakinesi +{ + public static function toplama($a, $b) + { + return $a + $b; + } + + public static function cikarma($a, $b) + { + return $a - $b; + } +} + +// Sınıfın bir örneğini oluşturmadan statik metot kullanımı +echo HesapMakinesi::toplama(5, 3); // Sonuç: 8 +echo HesapMakinesi::cikarma(5, 3); // Sonuç: 2 +``` + +Bu örnekte, iki statik metot içeren `HesapMakinesi` sınıfını oluşturduk. Bu metotları, sınıfın bir örneğini oluşturmadan doğrudan `::` operatörünü kullanarak çağırabiliriz. Statik metotlar, sınıfın belirli bir örneğinin durumuna bağlı olmayan işlemler için özellikle kullanışlıdır. + + +Sınıf Sabitleri +--------------- + +Sınıflar içinde sabitler tanımlama imkanımız vardır. Sabitler, programın çalışması sırasında asla değişmeyen değerlerdir. Değişkenlerin aksine, sabitin değeri her zaman aynı kalır. + +```php +class Araba +{ + public const TekerlekSayisi = 4; + + public function tekerlekSayisiniGoster(): int + { + echo self::TekerlekSayisi; + } +} + +echo Araba::TekerlekSayisi; // Çıktı: 4 +``` + +Bu örnekte, `TekerlekSayisi` sabitine sahip `Araba` sınıfımız var. Sınıf içindeki sabite erişmek istediğimizde, sınıf adı yerine `self` anahtar kelimesini kullanabiliriz. + + +Nesne Arayüzleri +---------------- + +Nesne arayüzleri, sınıflar için "sözleşmeler" gibi çalışır. Bir sınıf bir nesne arayüzünü uygulayacaksa, bu arayüzün tanımladığı tüm metotları içermelidir. Belirli sınıfların aynı "sözleşmeye" veya yapıya uymasını sağlamanın harika bir yoludur. + +PHP'de arayüzler `interface` anahtar kelimesiyle tanımlanır. Arayüzde tanımlanan tüm metotlar public (`public`)'tir. Bir sınıf bir arayüzü uyguladığında, `implements` anahtar kelimesini kullanır. + +```php +interface Hayvan +{ + function sesCikar(); +} + +class Kedi implements Hayvan +{ + public function sesCikar() + { + echo 'Miyav'; + } +} + +$kedi = new Kedi; +$kedi->sesCikar(); +``` + +Bir sınıf bir arayüzü uygularsa ancak beklenen tüm metotlar içinde tanımlanmamışsa, PHP bir hata verir. + +Bir sınıf aynı anda birden fazla arayüzü uygulayabilir, bu da bir sınıfın yalnızca bir sınıftan kalıtım alabileceği kalıtımdan farklıdır: + +```php +interface Bekci +{ + function eviKoru(); +} + +class Kopek implements Hayvan, Bekci +{ + public function sesCikar() + { + echo 'Hav'; + } + + public function eviKoru() + { + echo 'Köpek evi dikkatlice koruyor'; + } +} +``` + + +Soyut Sınıflar +-------------- + +Soyut sınıflar, diğer sınıflar için temel şablonlar olarak hizmet eder, ancak doğrudan örneklerini oluşturamazsınız. Tamamlanmış metotların ve içeriği tanımlanmamış soyut metotların bir kombinasyonunu içerirler. Soyut sınıflardan kalıtım alan sınıflar, atadan gelen tüm soyut metotlar için tanımlamalar sağlamalıdır. + +Soyut bir sınıf tanımlamak için `abstract` anahtar kelimesini kullanırız. + +```php +abstract class SoyutSinif +{ + public function normalMetot() + { + echo 'Bu normal bir metottur'; + } + + abstract public function soyutMetot(); +} + +class AltSinif extends SoyutSinif +{ + public function soyutMetot() + { + echo 'Bu soyut metodun uygulamasıdır'; + } +} + +$ornek = new AltSinif; +$ornek->normalMetot(); +$ornek->soyutMetot(); +``` + +Bu örnekte, bir normal ve bir soyut metot içeren soyut bir sınıfımız var. Ardından, `SoyutSinif`'tan kalıtım alan ve soyut metot için bir uygulama sağlayan `AltSinif` sınıfımız var. + +Arayüzler ve soyut sınıflar aslında nasıl farklılık gösterir? Soyut sınıflar hem soyut hem de somut metotlar içerebilirken, arayüzler yalnızca bir sınıfın hangi metotları uygulaması gerektiğini tanımlar ancak herhangi bir uygulama sağlamaz. Bir sınıf yalnızca bir soyut sınıftan kalıtım alabilir, ancak istediği sayıda arayüzü uygulayabilir. + + +Tip Kontrolü +------------ + +Programlamada, çalıştığımız verilerin doğru türde olduğundan emin olmak çok önemlidir. PHP'de bunu sağlayan araçlarımız vardır. Verilerin doğru türe sahip olup olmadığını doğrulamaya "tip kontrolü" denir. + +PHP'de karşılaşabileceğimiz tipler: + +1. **Temel tipler**: `int` (tam sayılar), `float` (ondalıklı sayılar), `bool` (mantıksal değerler), `string` (karakter dizileri), `array` (diziler) ve `null` içerir. +2. **Sınıflar**: Değerin belirli bir sınıfın örneği olmasını istiyorsak. +3. **Arayüzler**: Bir sınıfın uygulaması gereken metotlar kümesini tanımlar. Arayüzü karşılayan bir değerin bu metotlara sahip olması gerekir. +4. **Karışık tipler**: Bir değişkenin birden fazla izin verilen türe sahip olabileceğini belirleyebiliriz. +5. **Void**: Bu özel tip, bir fonksiyonun veya metodun herhangi bir değer döndürmediğini belirtir. + +Tipleri içerecek şekilde kodu nasıl düzenleyeceğimizi görelim: + +```php +class Kisi +{ + private int $yas; + + public function __construct(int $yas) + { + $this->yas = $yas; + } + + public function yasiYazdir(): void + { + echo "Bu kişi {$this->yas} yaşında."; + } +} + +/** + * Kisi sınıfından bir nesne alan ve kişinin yaşını yazdıran fonksiyon. + */ +function kisininYasiniYazdir(Kisi $kisi): void +{ + $kisi->yasiYazdir(); +} +``` + +Bu şekilde, kodumuzun doğru türde veriler beklediğini ve bunlarla çalıştığını sağladık, bu da potansiyel hataları önlememize yardımcı olur. + +Bazı tipler PHP'de doğrudan yazılamaz. Bu durumda, `/**` ile başlayan ve `*/` ile biten PHP kodunu belgelemek için standart bir format olan phpDoc yorumunda belirtilirler. Sınıfların, metotların vb. açıklamalarını eklemeye olanak tanır. Ayrıca `@var`, `@param` ve `@return` gibi anotasyonlar kullanarak karmaşık tipleri belirtmeye de olanak tanır. Bu tipler daha sonra statik kod analizi araçları tarafından kullanılır, ancak PHP'nin kendisi bunları kontrol etmez. + +```php +class Liste +{ + /** @var array<Kisi> bu gösterim, Kisi nesnelerinden oluşan bir dizi olduğunu söyler */ + private array $kisiler = []; + + public function kisiEkle(Kisi $kisi): void + { + $this->kisiler[] = $kisi; + } +} +``` + + +Karşılaştırma ve Kimlik +----------------------- + +PHP'de nesneleri iki şekilde karşılaştırabilirsiniz: + +1. Değer karşılaştırması `==`: Nesnelerin aynı sınıftan olup olmadığını ve özelliklerinde aynı değerlere sahip olup olmadığını kontrol eder. +2. Kimlik `===`: Aynı nesne örneği olup olmadığını kontrol eder. + +```php +class Araba +{ + public string $marka; + + public function __construct(string $marka) + { + $this->marka = $marka; + } +} + +$araba1 = new Araba('Skoda'); +$araba2 = new Araba('Skoda'); +$araba3 = $araba1; + +var_dump($araba1 == $araba2); // true, çünkü aynı değere sahipler +var_dump($araba1 === $araba2); // false, çünkü aynı örnek değiller +var_dump($araba1 === $araba3); // true, çünkü $araba3, $araba1 ile aynı örnektir +``` + + +`instanceof` Operatörü +---------------------- + +`instanceof` operatörü, belirli bir nesnenin belirli bir sınıfın örneği, bu sınıfın bir alt sınıfının örneği olup olmadığını veya belirli bir arayüzü uygulayıp uygulamadığını belirlemeye olanak tanır. + +`Kisi` sınıfımız ve `Kisi` sınıfının bir alt sınıfı olan başka bir `Ogrenci` sınıfımız olduğunu hayal edelim: + +```php +class Kisi +{ + private int $yas; + + public function __construct(int $yas) + { + $this->yas = $yas; + } +} + +class Ogrenci extends Kisi +{ + private string $bolum; + + public function __construct(int $yas, string $bolum) + { + parent::__construct($yas); + $this->bolum = $bolum; + } +} + +$ogrenci = new Ogrenci(20, 'Bilgisayar Bilimi'); + +// $ogrenci'nin Ogrenci sınıfının bir örneği olup olmadığını kontrol etme +var_dump($ogrenci instanceof Ogrenci); // Çıktı: bool(true) + +// $ogrenci'nin Kisi sınıfının bir örneği olup olmadığını kontrol etme (çünkü Ogrenci, Kisi'nin alt sınıfıdır) +var_dump($ogrenci instanceof Kisi); // Çıktı: bool(true) +``` + +Çıktılardan, `$ogrenci` nesnesinin aynı anda her iki sınıfın - `Ogrenci` ve `Kisi` - örneği olarak kabul edildiği açıktır. + + +Akıcı Arayüzler (Fluent Interfaces) +----------------------------------- + +"Akıcı Arayüz" (İngilizce "Fluent Interface"), OOP'de metotları tek bir çağrıda birbirine zincirlemeyi sağlayan bir tekniktir. Bu genellikle kodu basitleştirir ve daha okunabilir hale getirir. + +Akıcı arayüzün anahtar öğesi, zincirdeki her metodun mevcut nesneye bir referans döndürmesidir. Bunu, metodun sonunda `return $this;` kullanarak başarırız. Bu programlama tarzı genellikle nesnenin özelliklerinin değerlerini ayarlayan "setter" olarak adlandırılan metotlarla ilişkilidir. + +E-posta gönderme örneğinde akıcı bir arayüzün nasıl görünebileceğini gösterelim: + +```php +public function sendMessage() +{ + $email = new Email; + $email->setFrom('sender@example.com') + ->setRecipient('admin@example.com') + ->setMessage('Hello, this is a message.') + ->send(); +} +``` + +Bu örnekte, `setFrom()`, `setRecipient()` ve `setMessage()` metotları ilgili değerleri (gönderen, alıcı, mesaj içeriği) ayarlamaya hizmet eder. Bu değerlerin her biri ayarlandıktan sonra, metotlar bize mevcut nesneyi (`$email`) döndürür, bu da bir sonraki metodu ona zincirlememizi sağlar. Son olarak, e-postayı gerçekten gönderen `send()` metodunu çağırırız. + +Akıcı arayüzler sayesinde, sezgisel ve kolay okunabilir kod yazabiliriz. + + +`clone` ile Kopyalama +--------------------- + +PHP'de `clone` operatörünü kullanarak bir nesnenin kopyasını oluşturabiliriz. Bu şekilde, aynı içeriğe sahip yeni bir örnek elde ederiz. + +Bir nesneyi kopyalarken bazı özelliklerini değiştirmemiz gerekirse, sınıfta özel bir `__clone()` metodu tanımlayabiliriz. Bu metot, nesne klonlandığında otomatik olarak çağrılır. + +```php +class Koyun +{ + public string $isim; + + public function __construct(string $isim) + { + $this->isim = $isim; + } + + public function __clone() + { + $this->isim = 'Klon ' . $this->isim; + } +} + +$orijinal = new Koyun('Dolly'); +echo $orijinal->isim . "\n"; // Yazdırır: Dolly + +$klon = clone $orijinal; +echo $klon->isim . "\n"; // Yazdırır: Klon Dolly +``` + +Bu örnekte, bir `$isim` özelliğine sahip `Koyun` sınıfımız var. Bu sınıfın bir örneğini klonladığımızda, `__clone()` metodu klonlanmış koyunun adının "Klon" önekini almasını sağlar. + + +Traitler +-------- + +PHP'deki traitler, sınıflar arasında metotları, özellikleri ve sabitleri paylaşmayı ve kod tekrarını önlemeyi sağlayan bir araçtır. Bunları, trait içeriğinin sınıflara "yapıştırıldığı" bir "kopyala ve yapıştır" (Ctrl-C ve Ctrl-V) mekanizması olarak düşünebilirsiniz. Bu, karmaşık sınıf hiyerarşileri oluşturmaya gerek kalmadan kodu yeniden kullanmanıza olanak tanır. + +PHP'de traitleri nasıl kullanacağımıza dair basit bir örnek görelim: + +```php +trait KornaCalma +{ + public function kornaCal() + { + echo 'Bip bip!'; + } +} + +class Araba +{ + use KornaCalma; +} + +class Kamyon +{ + use KornaCalma; +} + +$araba = new Araba; +$araba->kornaCal(); // 'Bip bip!' yazdırır + +$kamyon = new Kamyon; +$kamyon->kornaCal(); // Ayrıca 'Bip bip!' yazdırır +``` + +Bu örnekte, bir `kornaCal()` metodu içeren `KornaCalma` adlı bir traitimiz var. Ardından, her ikisi de `KornaCalma` traitini kullanan iki sınıfımız var: `Araba` ve `Kamyon`. Bu sayede her iki sınıf da `kornaCal()` metoduna "sahiptir" ve onu her iki sınıfın nesnelerinde de çağırabiliriz. + +Traitler, sınıflar arasında kodu kolayca ve verimli bir şekilde paylaşmanıza olanak tanır. Ancak kalıtım hiyerarşisine girmezler, yani `$araba instanceof KornaCalma` ifadesi `false` döndürür. + + +İstisnalar +---------- + +OOP'deki istisnalar, kodumuzdaki hataları ve beklenmedik durumları zarif bir şekilde ele almamızı sağlar. Bunlar, hata veya olağandışı durum hakkında bilgi taşıyan nesnelerdir. + +PHP'de, tüm istisnalar için temel görevi gören yerleşik bir `Exception` sınıfımız vardır. Bu sınıf, hata mesajı, hatanın oluştuğu dosya ve satır gibi istisna hakkında daha fazla bilgi edinmemizi sağlayan birkaç metoda sahiptir. + +Kodda bir hata oluştuğunda, `throw` anahtar kelimesini kullanarak bir istisna "fırlatabiliriz". + +```php +function bolme(float $a, float $b): float +{ + if ($b === 0) { + throw new Exception('Sıfıra bölme!'); + } + return $a / $b; +} +``` + +`bolme()` fonksiyonu ikinci argüman olarak sıfır aldığında, `'Sıfıra bölme!'` hata mesajıyla bir istisna fırlatır. İstisna fırlatıldığında programın çökmesini önlemek için, onu bir `try/catch` bloğunda yakalarız: + +```php +try { + echo bolme(10, 0); +} catch (Exception $e) { + echo 'İstisna yakalandı: '. $e->getMessage(); +} +``` + +İstisna fırlatabilecek kod `try` bloğuna sarılır. Eğer bir istisna fırlatılırsa, kodun yürütülmesi `catch` bloğuna taşınır, burada istisnayı işleyebiliriz (örneğin, hata mesajını yazdırabiliriz). + +`try` ve `catch` bloklarından sonra, isteğe bağlı bir `finally` bloğu ekleyebiliriz, bu blok istisna fırlatılmış olsun ya da olmasın her zaman yürütülür (hatta `try` veya `catch` bloğunda `return`, `break` veya `continue` deyimi kullansak bile): + +```php +try { + echo bolme(10, 0); +} catch (Exception $e) { + echo 'İstisna yakalandı: '. $e->getMessage(); +} finally { + // İstisna fırlatılmış olsun ya da olmasın her zaman yürütülecek kod +} +``` + +Ayrıca Exception sınıfından kalıtım alan kendi istisna sınıflarımızı (hiyerarşisini) oluşturabiliriz. Örnek olarak, para yatırma ve çekme işlemlerine izin veren basit bir bankacılık uygulamasını düşünelim: + +```php +class BankaIstisnasi extends Exception {} +class YetersizBakiyeIstisnasi extends BankaIstisnasi {} +class LimitAsimiIstisnasi extends BankaIstisnasi {} + +class BankaHesabi +{ + private int $bakiye = 0; + private int $gunlukLimit = 1000; + + public function yatir(int $miktar): int + { + $this->bakiye += $miktar; + return $this->bakiye; + } + + public function cek(int $miktar): int + { + if ($miktar > $this->bakiye) { + throw new YetersizBakiyeIstisnasi('Hesapta yeterli bakiye yok.'); + } + + if ($miktar > $this->gunlukLimit) { + throw new LimitAsimiIstisnasi('Günlük para çekme limiti aşıldı.'); + } + + $this->bakiye -= $miktar; + return $this->bakiye; + } +} +``` + +Bir `try` bloğu için, farklı türde istisnalar bekliyorsanız birden fazla `catch` bloğu belirleyebilirsiniz. + +```php +$hesap = new BankaHesabi; +$hesap->yatir(500); + +try { + $hesap->cek(1500); +} catch (LimitAsimiIstisnasi $e) { + echo $e->getMessage(); +} catch (YetersizBakiyeIstisnasi $e) { + echo $e->getMessage(); +} catch (BankaIstisnasi $e) { + echo 'İşlem gerçekleştirilirken bir hata oluştu.'; +} +``` + +Bu örnekte, `catch` bloklarının sırasına dikkat etmek önemlidir. Çünkü tüm istisnalar `BankaIstisnasi`'ndan kalıtım alır, eğer bu bloğu ilk sıraya koysaydık, tüm istisnalar kod sonraki `catch` bloklarına ulaşamadan bu blokta yakalanırdı. Bu nedenle, daha spesifik istisnaları (yani diğerlerinden kalıtım alanları) `catch` bloğunda üst sınıflarından daha önce sıralamak önemlidir. + + +Yineleme (Iteration) +-------------------- + +PHP'de, dizilerde dolaştığınız gibi `foreach` döngüsünü kullanarak nesneler üzerinde de dolaşabilirsiniz. Bunun çalışması için nesnenin özel bir arayüz uygulaması gerekir. + +İlk seçenek, mevcut değeri döndüren `current()`, anahtarı döndüren `key()`, bir sonraki değere geçen `next()`, başa dönen `rewind()` ve henüz sonda olup olmadığımızı kontrol eden `valid()` metotlarına sahip `Iterator` arayüzünü uygulamaktır. + +İkinci seçenek, yalnızca tek bir `getIterator()` metoduna sahip olan `IteratorAggregate` arayüzünü uygulamaktır. Bu metot ya dolaşımı sağlayacak bir yer tutucu nesne döndürür ya da anahtarları ve değerleri kademeli olarak döndürmek için `yield` kullanılan özel bir fonksiyon olan bir jeneratör (generator) temsil edebilir: + +```php +class Kisi +{ + public function __construct( + public int $yas, + ) { + } +} + +class Liste implements IteratorAggregate +{ + private array $kisiler = []; + + public function kisiEkle(Kisi $kisi): void + { + $this->kisiler[] = $kisi; + } + + public function getIterator(): Generator + { + foreach ($this->kisiler as $kisi) { + yield $kisi; + } + } +} + +$liste = new Liste; +$liste->kisiEkle(new Kisi(30)); +$liste->kisiEkle(new Kisi(25)); + +foreach ($liste as $kisi) { + echo "Yaş: {$kisi->yas} yıl \n"; +} +``` + + +İyi Uygulamalar (Best practices) +-------------------------------- + +Nesne yönelimli programlamanın temel prensiplerini öğrendikten sonra, OOP'deki iyi uygulamalara odaklanmak önemlidir. Bunlar, yalnızca işlevsel değil, aynı zamanda okunabilir, anlaşılır ve bakımı kolay kod yazmanıza yardımcı olacaktır. + +1) **Sorumlulukların Ayrılması (Separation of Concerns)**: Her sınıfın açıkça tanımlanmış bir sorumluluğu olmalı ve yalnızca bir ana görevi çözmelidir. Eğer bir sınıf çok fazla şey yapıyorsa, onu daha küçük, uzmanlaşmış sınıflara bölmek uygun olabilir. +2) **Kapsülleme (Encapsulation)**: Veriler ve metotlar mümkün olduğunca gizli tutulmalı ve yalnızca tanımlanmış bir arayüz aracılığıyla erişilebilir olmalıdır. Bu, kodun geri kalanını etkilemeden sınıfın dahili uygulamasını değiştirmenize olanak tanır. +3) **Bağımlılık Enjeksiyonu (Dependency Injection)**: Bağımlılıkları doğrudan sınıf içinde oluşturmak yerine, onları dışarıdan "enjekte etmelisiniz". Bu prensibi daha derinlemesine anlamak için [Bağımlılık Enjeksiyonu bölümlerini|dependency-injection:introduction] öneririz. diff --git a/nette/tr/troubleshooting.texy b/nette/tr/troubleshooting.texy index bf0c471d55..a841163115 100644 --- a/nette/tr/troubleshooting.texy +++ b/nette/tr/troubleshooting.texy @@ -2,40 +2,69 @@ Sorun Giderme ************* -Nette Çalışmıyor, Beyaz Sayfa Görüntüleniyor .[#toc-nette-is-not-working-white-page-is-displayed] -------------------------------------------------------------------------------------------------- -- Hataların görüntülenmesini zorlamak için `index.php` dosyasında `declare(strict_types=1);` adresinden sonra `ini_set('display_errors', '1'); error_reporting(E_ALL);` adresini koymayı deneyin -- Hala beyaz bir ekran görüyorsanız, muhtemelen sunucu kurulumunda bir hata vardır ve nedenini sunucu günlüğünde keşfedeceksiniz. Emin olmak için, `echo 'test';` adresini kullanarak bir şeyler yazdırmayı deneyerek PHP'nin çalışıp çalışmadığını kontrol edin. -- Eğer bir hata görürseniz *Sunucu Hatası: Üzgünüz! ...* hatasını görürseniz, bir sonraki bölümle devam edin: +Nette Çalışmıyor, Beyaz Sayfa Görünüyor +--------------------------------------- +- `index.php` dosyasına `declare(strict_types=1);` satırından hemen sonra `ini_set('display_errors', '1'); error_reporting(E_ALL);` eklemeyi deneyin, bu hataların görüntülenmesini zorlar +- Hala beyaz bir ekran görüyorsanız, muhtemelen sunucu ayarlarında bir hata vardır ve nedeni sunucu günlüğünde ortaya çıkacaktır. Emin olmak için, `echo 'test';` kullanarak bir şeyler yazdırmayı deneyerek PHP'nin çalışıp çalışmadığını kontrol edin +- *Server Error: We're sorry! …* hatasını görüyorsanız, sonraki bölümle devam edin: -Hata 500 *Sunucu Hatası: Üzgünüz! ...* .[#toc-error-500-server-error-we-re-sorry] ---------------------------------------------------------------------------------- -Bu hata sayfası Nette tarafından üretim modunda görüntülenir. Bunu bir geliştirici makinesinde görüyorsanız, [geliştirici moduna geçin |application:bootstrap#Development vs Production Mode]. +Hata 500 *Server Error: We're sorry! …* +--------------------------------------- +Bu hata sayfası Nette tarafından üretim modunda görüntülenir. Geliştirici bilgisayarınızda görüntüleniyorsa, [geliştirme moduna geçin |application:bootstrapping#Geliştirme vs Üretim Modu] ve ayrıntılı bir mesaj içeren Tracy hata ayıklama aracı görüntülenecektir. -Hata mesajı `Tracy is unable to log error` içeriyorsa, hataların neden günlüğe kaydedilemediğini öğrenin. Bunu, örneğin, geliştirici moduna [geçerek |application:bootstrap#Development vs Production Mode] ve `$configurator->enableTracy(...)` adresinden sonra `Tracy\Debugger::log('hello');` adresini çağırarak yapabilirsiniz. Tracy size neden günlüğe kaydedemediğini söyleyecektir. -Bunun nedeni genellikle `log/` dizinine yazma [izinlerinin yetersiz |#Setting Directory Permissions] olmasıdır. +Hatanın nedeni her zaman `log/` dizinindeki günlükte okunabilir. Ancak hata mesajında `Tracy is unable to log error` cümlesi görünüyorsa, önce hataların neden günlüğe kaydedilemediğini öğrenin. Bunu, örneğin geçici olarak geliştirme moduna [geçerek |application:bootstrapping#Geliştirme vs Üretim Modu] ve Tracy'nin başlatıldıktan sonra herhangi bir şeyi günlüğe kaydetmesini sağlayarak yapabilirsiniz: -Eğer `Tracy is unable to log error` cümlesi hata mesajında yer almıyorsa (artık), hatanın nedenini `log/` dizinindeki günlükten öğrenebilirsiniz. +```php +// Bootstrap.php +$configurator->setDebugMode('23.75.345.200'); // IP adresiniz +$configurator->enableTracy($rootDir . '/log'); +\Tracy\Debugger::log('hello'); +``` + +Tracy size neden günlük kaydı yapamadığını söyleyecektir. Nedeni muhtemelen `log/` dizinine yazmak için [yetersiz izinler |#Dizin İzinlerini Ayarlama] olabilir. + +500 hatasının en yaygın nedenlerinden biri eski önbellektir. Nette geliştirme modunda önbelleği akıllıca otomatik olarak güncellerken, üretim modunda performansı en üst düzeye çıkarmaya odaklanır ve her kod değişikliğinden sonra önbelleği temizlemek size kalmıştır. `temp/cache`'i silmeyi deneyin. + + +Hata 404, Yönlendirme Çalışmıyor +-------------------------------- +Tüm sayfalar (ana sayfa hariç) 404 hatası veriyorsa, bu durum sunucunun [kullanıcı dostu URL'ler |#Sunucu Kullanıcı Dostu URL ler İçin Nasıl Ayarlanır] için yapılandırılmasıyla ilgili bir soruna işaret ediyor olabilir. + + +Şablonlardaki veya Yapılandırmadaki Değişiklikler Görünmüyor +------------------------------------------------------------ +"Şablonu veya yapılandırmayı düzenledim, ancak web sitesi hala eski sürümü gösteriyor." Bu davranış, performans nedeniyle dosyalardaki değişiklikleri kontrol etmeyen ve bir kez oluşturulan önbelleği koruyan [üretim modunda |application:bootstrapping#Geliştirme vs Üretim Modu] meydana gelir. + +Üretim sunucusunda her düzenlemeden sonra önbelleği manuel olarak silmek zorunda kalmamak için, `Bootstrap.php` dosyasında IP adresiniz için geliştirme modunu etkinleştirin: + +```php +$this->configurator->setDebugMode('sizin.ip.adresiniz'); +``` + + +Geliştirme Sırasında Önbellek Nasıl Kapatılır? +---------------------------------------------- +Nette akıllıdır ve içinde önbelleğe almayı kapatmanız gerekmez. Geliştirme sırasında, şablonun veya DI konteyner yapılandırmasının her değişikliğinde önbelleği otomatik olarak günceller. Geliştirme modu ayrıca otomatik algılama ile etkinleştirilir, bu nedenle genellikle hiçbir şeyi yapılandırmaya gerek yoktur, [veya sadece IP adresini |application:bootstrapping#Geliştirme vs Üretim Modu]. -En yaygın nedenlerden biri eski bir önbellektir. Nette, geliştirme modunda önbelleği akıllıca otomatik olarak güncellerken, üretim modunda performansı en üst düzeye çıkarmaya odaklanır ve her kod değişikliğinden sonra önbelleği temizlemek size bağlıdır. `temp/cache` adresini silmeyi deneyin. +Yönlendiriciyi hata ayıklarken, tarayıcıdaki önbelleği kapatmanızı öneririz, örneğin yönlendirmeler burada saklanabilir: Geliştirici Araçları'nı açın (Ctrl+Shift+I veya Cmd+Option+I) ve Ağ (Network) panelinde önbelleği kapatmayı işaretleyin. -Hata `#[\ReturnTypeWillChange] attribute should be used` .[#toc-error-returntypewillchange-attribute-should-be-used] --------------------------------------------------------------------------------------------------------------------- -Bu hata, PHP'yi 8.1 sürümüne yükselttiyseniz ancak onunla uyumlu olmayan Nette kullanıyorsanız ortaya çıkar. Çözüm, `composer update` adresini kullanarak Nette'yi daha yeni bir sürüme güncellemektir. Nette, PHP 8.1'i 3.0 sürümünden beri desteklemektedir. Daha eski bir sürüm kullanıyorsanız ( `composer.json` adresine bakarak öğrenebilirsiniz), Nette'yi [yükseltin |migrations:en] veya PHP 8.0 ile kalın. +Hata `#[\ReturnTypeWillChange] attribute should be used` +-------------------------------------------------------- +Bu hata, PHP'yi 8.1 sürümüne güncellediyseniz ancak onunla uyumlu olmayan bir Nette kullanıyorsanız ortaya çıkar. Çözüm, Nette'yi `composer update` kullanarak daha yeni bir sürüme güncellemektir. Nette, 3.0 sürümünden itibaren PHP 8.1'i destekler. Daha eski bir sürüm kullanıyorsanız ( `composer.json`'a bakarak öğrenin), [Nette'yi yükseltin |migrations:en] veya PHP 8.0'da kalın. -Dizin İzinlerini Ayarlama .[#toc-setting-directory-permissions] ---------------------------------------------------------------- -MacOS veya Linux (veya başka bir Unix tabanlı sistem) üzerinde geliştirme yapıyorsanız, web sunucusuna yazma ayrıcalıklarını yapılandırmanız gerekir. Uygulamanızın varsayılan dizinde bulunduğunu varsayarsak `/var/www/html` (Fedora, CentOS, RHEL) +Dizin İzinlerini Ayarlama +------------------------- +macOS veya Linux üzerinde (veya Unix tabanlı başka bir sistemde) geliştirme yapıyorsanız, web sunucusuna yazma izinleri ayarlamanız gerekecektir. Uygulamanızın varsayılan `/var/www/html` (Fedora, CentOS, RHEL) içinde bulunduğunu varsayalım. ```shell cd /var/www/html/MY_PROJECT chmod -R a+rw temp log ``` -Bazı Linux sistemlerinde (Fedora, CentOS, ...) SELinux varsayılan olarak etkin olabilir. SELinux politikalarını güncellemeniz veya `temp` ve `log` dizinlerinin yollarını doğru SELinux güvenlik bağlamı ile ayarlamanız gerekebilir. `temp` ve `log` dizinleri `httpd_sys_rw_content_t` bağlamına ayarlanmalıdır; uygulamanın geri kalanı için - özellikle `app` klasörü - `httpd_sys_content_t` bağlamı yeterli olacaktır. Sunucu üzerinde root olarak çalıştırın: +Bazı Linux'larda (Fedora, CentOS, ...) SELinux varsayılan olarak etkindir. SELinux politikalarını uygun şekilde düzenlemeniz ve `temp` ve `log` klasörleri için doğru SELinux güvenlik bağlamını ayarlamanız gerekecektir. `temp` ve `log` için `httpd_sys_rw_content_t` bağlam türünü ayarlayacağız, uygulamanın geri kalanı (ve özellikle `app` klasörü) için `httpd_sys_content_t` yeterli olacaktır. Sunucuda şunu çalıştırın: ```shell semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/log(/.*)?' @@ -43,25 +72,30 @@ semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/temp(/.*) restorecon -Rv /var/www/html/MY_PROJECT/ ``` -Daha sonra, Nette'nin ağ üzerinden veritabanına bağlanmasına izin vermek için SELinux boolean `httpd_can_network_connect_db` etkinleştirilmelidir. Varsayılan olarak devre dışıdır. Bu görevi gerçekleştirmek için `setsebool` komutu kullanılabilir ve `-P` seçeneği belirtilirse, bu ayar yeniden başlatmalarda kalıcı olacaktır. +Ayrıca, varsayılan olarak kapalı olan ve Nette'nin ağ üzerinden veritabanına bağlanmasına izin veren `httpd_can_network_connect_db` SELinux boolean'ını etkinleştirmek gerekir. Bunun için `setsebool` komutunu ve `-P` seçeneğini kullanarak değişikliği kalıcı hale getireceğiz, yani sunucuyu yeniden başlattıktan sonra hoş olmayan bir sürprizle karşılaşmayacağız: ```shell setsebool -P httpd_can_network_connect_db on ``` -URL'den `www` Dizini Nasıl Değiştirilir veya Kaldırılır? .[#toc-how-to-change-or-remove-www-directory-from-url] ---------------------------------------------------------------------------------------------------------------- -Nette'deki örnek projelerde kullanılan `www/` dizini, projenin genel dizini veya belge kökü olarak adlandırılır. İçeriği tarayıcı tarafından erişilebilir olan tek dizindir. Ve Nette'de yazılmış bir web uygulamasını başlatan giriş noktası olan `index.php` dosyasını içerir. +URL'den `www` Dizini Nasıl Değiştirilir veya Kaldırılır? +-------------------------------------------------------- +Nette'deki örnek projelerde kullanılan `www/` dizini, projenin sözde genel dizini veya document-root'unu temsil eder. İçeriği tarayıcı tarafından erişilebilen tek dizindir. Ve Nette'de yazılmış web uygulamasını başlatan giriş noktası olan `index.php` dosyasını içerir. -Uygulamayı hosting üzerinde çalıştırmak için, hosting yapılandırmasında document-root'u bu dizine ayarlamanız gerekir. Veya, barındırma, farklı bir adla (örneğin `web`, `public_html` vb.) genel dizin için önceden oluşturulmuş bir klasöre sahipse, `www/` adını değiştirmeniz yeterlidir. +Uygulamayı barındırmada çalıştırmak için document-root'un doğru şekilde yapılandırılması gerekir. İki seçeneğiniz var: +1. Barındırma yapılandırmasında document-root'u bu dizine ayarlayın +2. Barındırmanın önceden hazırlanmış bir klasörü varsa (örneğin `public_html`), `www/`'yi bu adla yeniden adlandırın -Çözüm, `.htaccess` dosyasındaki veya yönlendiricideki kuralları kullanarak `www/` klasöründen "kurtulmak" değildir. Hosting, document-root'u bir alt dizine ayarlamanıza (yani genel dizinin bir seviye üstünde dizinler oluşturmanıza) izin vermiyorsa, başka bir tane arayın. Aksi takdirde önemli bir güvenlik riski almış olursunuz. Bu, ön kapısını kapatamadığınız ve her zaman ardına kadar açık olan bir apartman dairesinde yaşamaya benzer. +.[warning] +Güvenliği yalnızca diğer klasörlere erişimi engelleyecek `.htaccess` veya yönlendirici ile çözmeye asla çalışmayın. +Barındırma, document-root'u bir alt dizine ayarlamaya izin vermiyorsa (yani genel dizinin bir seviye üzerinde dizinler oluşturmaya), başka bir tane arayın. Aksi takdirde önemli bir güvenlik riskiyle karşı karşıya kalırsınız. Bu, giriş kapısı kapatılamayan ve her zaman ardına kadar açık olan bir dairede yaşamak gibi olurdu. -Güzel URL'ler için Sunucu Nasıl Yapılandırılır? .[#toc-how-to-configure-a-server-for-nice-urls] ------------------------------------------------------------------------------------------------ -**Apache**: mod_rewrite uzantısına izin verilmeli ve `.htaccess` dosyasında yapılandırılmalıdır. + +Sunucu Kullanıcı Dostu URL'ler İçin Nasıl Ayarlanır? +---------------------------------------------------- +**Apache**: `.htaccess` dosyasında mod_rewrite kurallarını etkinleştirmek ve ayarlamak gerekir: ```apacheconf RewriteEngine On @@ -70,25 +104,53 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -Apache yapılandırmasını .htaccess dosyaları ile değiştirmek için AllowOverride yönergesi etkinleştirilmelidir. Bu Apache için öntanımlı davranıştır. +Sorunlarla karşılaşırsanız, şunlardan emin olun: +- `.htaccess` dosyası document-root dizininde bulunur (yani `index.php` dosyasının yanında) +- [Apache'nin `.htaccess` dosyalarını işlediğinden |#htaccess ın Çalıştığını Doğrulama] +- [mod_rewrite'ın etkinleştirildiğinden |#mod rewrite ın Etkinleştirildiğini Doğrulama] + +Uygulamayı bir alt klasörde ayarlıyorsanız, `RewriteBase` ayarı için satırın yorumunu kaldırmanız ve doğru klasöre ayarlamanız gerekebilir. -**nginx**: sunucu yapılandırmasında `try_files` yönergesi kullanılmalıdır: +**nginx**: sunucu yapılandırmasındaki `location /` bloğunun içinde `try_files` yönergesini kullanarak yönlendirmeyi ayarlamak gerekir. ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args is important + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args ÖNEMLİDİR! } ``` -`location` bloğu, `server` bloğundaki her dosya sistemi yolu için tam olarak bir kez tanımlanmalıdır. Yapılandırmanızda zaten bir `location /` bloğu varsa, `try_files` yönergesini mevcut bloğa ekleyin. +`location` bloğu, `server` bloğu içinde her dosya sistemi yolu için yalnızca bir kez bulunabilir. Yapılandırmanızda zaten `location /` varsa, `try_files` yönergesini ona ekleyin. + + +`.htaccess`'ın Çalıştığını Doğrulama +------------------------------------ +Apache'nin `.htaccess` dosyanızı kullanıp kullanmadığını veya yok sayıp saymadığını test etmenin en kolay yolu, onu kasıtlı olarak bozmaktır. Dosyanın başına `Test` satırını ekleyin ve şimdi tarayıcıda sayfayı yenilerseniz, *Internal Server Error* görmelisiniz. + +Bu hatayı görürseniz, bu aslında iyi bir şeydir! Apache'nin `.htaccess` dosyasını analiz ettiği ve oraya eklediğimiz hatayla karşılaştığı anlamına gelir. `Test` satırını kaldırın. + +*Internal Server Error* görüntülenmezse, Apache ayarınız `.htaccess` dosyasını yok sayıyor demektir. Genel olarak Apache, eksik `AllowOverride All` yapılandırma yönergesi nedeniyle onu yok sayar. + +Kendiniz barındırıyorsanız, bunu kolayca düzeltebilirsiniz. `httpd.conf` veya `apache.conf` dosyasını bir metin düzenleyicide açın, ilgili `<Directory>` bölümünü bulun ve bu yönergeyi ekleyin/değiştirin: + +```apacheconf +<Directory "/var/www/htdocs"> # belge kök dizininizin yolu + AllowOverride All + ... +``` + +Web siteniz başka bir yerde barındırılıyorsa, `.htaccess` dosyasını etkinleştirip etkinleştiremeyeceğinizi görmek için kontrol panelinize bakın. Değilse, bunu sizin için yapması için barındırma sağlayıcınızla iletişime geçin. + + +`mod_rewrite`'ın Etkinleştirildiğini Doğrulama +---------------------------------------------- +[`.htaccess`'ın çalıştığını |#htaccess ın Çalıştığını Doğrulama] doğruladıysanız, mod_rewrite uzantısının etkinleştirilip etkinleştirilmediğini doğrulayabilirsiniz. `.htaccess` dosyasının başına `RewriteEngine On` satırını ekleyin ve tarayıcıda sayfayı yenileyin. *Internal Server Error* görüntülenirse, bu mod_rewrite'ın etkinleştirilmediği anlamına gelir. Etkinleştirmenin birkaç yolu vardır. Farklı ayarlarda bunu yapmanın çeşitli yollarını Stack Overflow'da bulabilirsiniz. -Bağlantılar `https:` Olmadan Oluşturulur .[#toc-links-are-generated-without-https] ----------------------------------------------------------------------------------- -Nette, geçerli sayfanın kullandığı protokolle aynı protokole sahip bağlantılar oluşturur. Yani `https://foo` ile başlayan bağlantılar oluşturur ve bunun tersi de geçerlidir. -HTTPS sıyırıcı bir ters proxy'nin arkasındaysanız (örneğin Docker'da), protokol algılamanın düzgün çalışması için yapılandırmada [bir proxy ayarlamanız |http:configuration#HTTP proxy] gerekir. +Bağlantılar `https:` Olmadan Oluşturuluyor +------------------------------------------ +Nette, bağlantıları sayfanın kendisiyle aynı protokolle oluşturur. Yani `https://foo` sayfasında `https:` ile başlayan bağlantılar oluşturur ve tersi de geçerlidir. HTTPS'yi kaldıran bir ters proxy sunucusunun arkasındaysanız (örneğin Docker'da), protokol algılamasının doğru çalışması için yapılandırmada [proxy'yi yapılandırmak |http:configuration#HTTP proxy] gerekir. -Nginx'i proxy olarak kullanıyorsanız, yeniden yönlendirmeyi şu şekilde ayarlamanız gerekir: +Proxy olarak Nginx kullanıyorsanız, yönlendirmenin örneğin şu şekilde ayarlanması gerekir: ``` location / { @@ -96,11 +158,11 @@ location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Port $server_port; - proxy_pass http://IP-aplikace:80; # IP or hostname of the server/container where the application is running + proxy_pass http://IP-aplikace:80; # Uygulamanın çalıştığı sunucunun/konteynerin IP'si veya ana bilgisayar adı } ``` -Ardından, IP proxy'sini ve varsa altyapıyı çalıştırdığınız yerel ağınızın IP aralığını belirtmeniz gerekir: +Ayrıca, yapılandırmaya proxy'nin IP'sini ve muhtemelen altyapıyı çalıştırdığınız yerel ağınızın IP aralığını belirtmeniz gerekir: ```neon http: @@ -108,29 +170,27 @@ http: ``` -JavaScript'te { } Karakterlerinin Kullanımı .[#toc-use-of-characters-in-javascript] ------------------------------------------------------------------------------------ -`{` and `}` karakterleri Latte etiketlerini yazmak için kullanılır. `{` character is considered a tag. If you need to print character `{` adresini takip eden her şey (boşluk ve tırnak işareti hariç) (genellikle JavaScript'te), `{` karakterinden hemen sonra bir boşluk (veya başka bir boş karakter) koyabilirsiniz. Bu şekilde etiket olarak yorumlanmasını önlersiniz. +JavaScript'te { } Karakterlerinin Kullanımı +------------------------------------------- +`{` ve `}` karakterleri Latte etiketlerini yazmak için kullanılır. Boşluk ve tırnak işareti hariç `{` karakterini takip eden her şey etiket olarak alınır. Bu nedenle doğrudan `{` karakterini yazdırmanız gerekiyorsa (genellikle örneğin JavaScript'te), `{` karakterinden sonra bir boşluk (veya başka bir boş karakter) koyabilirsiniz. Bu, etiket olarak çevrilmesini önler. -Bu karakterlerin bir etiket olarak yorumlanacağı bir durumda yazdırılması gerekiyorsa, bu karakterleri yazdırmak için özel etiketler kullanabilirsiniz - `{l}` için `{` and `{r}` `}` için. +Metnin etiket olarak anlaşılacağı bir durumda bu karakterleri yazdırmak gerekirse, bu karakterleri yazdırmak için özel etiketler kullanabilirsiniz - `{` için `{l}` ve `}` için `{r}`. ``` -{is tag} -{ is not tag } -{l}is not tag{r} +{bu bir etikettir} +{ bu bir etiket değildir } +{l}bu bir etiket değildir{r} ``` -Duyuru `Presenter::getContext() is deprecated` .[#toc-notice-presenter-getcontext-is-deprecated] ------------------------------------------------------------------------------------------------- +`Presenter::getContext() is deprecated` Mesajı +---------------------------------------------- -Nette, bağımlılık enjeksiyonuna geçiş yapan ve programcıları sunum yapanlardan başlayarak tutarlı bir şekilde kullanmaya yönlendiren ilk PHP çatısıdır. Eğer bir sunumcu bir bağımlılığa ihtiyaç duyarsa, [bunu isteyecektir |dependency-injection:passing-dependencies]. -Buna karşılık, tüm DI konteynerini bir sınıfa aktarma ve bağımlılıkları doğrudan ondan çekme yöntemi bir antipattern olarak kabul edilir (buna servis bulucu denir). -Bu yol, bağımlılık enjeksiyonunun ortaya çıkmasından önce Nette 0.x'te kullanılıyordu ve kalıntısı, uzun zaman önce kullanımdan kaldırılmış olarak işaretlenmiş olan `Presenter::getContext()` yöntemidir. +Nette, dependency injection'a geçen ve programcıları presenter'lardan başlayarak tutarlı bir şekilde kullanmaya yönlendiren açık ara ilk PHP framework'üdür. Presenter'ın bir bağımlılığa ihtiyacı varsa, [onu talep eder |dependency-injection:passing-dependencies]. Aksine, sınıfa tüm DI konteynerini ilettiğimiz ve sınıfın bağımlılıkları doğrudan ondan çektiği yol, bir antipattern olarak kabul edilir (service locator olarak adlandırılır). Bu yöntem, dependency injection'ın gelmesinden önce Nette 0.x'te kullanılıyordu ve kalıntısı, uzun zaman önce deprecated olarak işaretlenen `Presenter::getContext()` metodudur. -Çok eski bir Nette uygulamasını taşırsanız, hala bu yöntemi kullandığını görebilirsiniz. Yani `nette/application` 'un 3.1 sürümünden beri `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection` uyarısıyla, 4.0 sürümünden beri ise yöntemin mevcut olmadığı hatasıyla karşılaşırsınız. +Nette için çok eski bir uygulamayı taşıyorsanız, hala bu metodu kullandığını görebilirsiniz. `nette/application` sürüm 3.1'den itibaren `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection` uyarısıyla, sürüm 4.0'dan itibaren ise metodun mevcut olmadığı hatasıyla karşılaşırsınız. -Elbette temiz çözüm, bağımlılık enjeksiyonu kullanarak bağımlılıkları aktarmak için uygulamayı yeniden tasarlamaktır. Geçici bir çözüm olarak, temel sunucunuza kendi `getContext()` yönteminizi ekleyebilir ve mesajı atlayabilirsiniz: +Temiz çözüm elbette uygulamayı dependency injection kullanarak bağımlılıkları iletecek şekilde yeniden düzenlemektir. Bir geçici çözüm olarak, temel presenter'ınıza kendi `getContext()` metodunuzu ekleyebilir ve böylece mesajı atlayabilirsiniz: ```php abstract BasePresenter extends Nette\Application\UI\Presenter diff --git a/nette/tr/vulnerability-protection.texy b/nette/tr/vulnerability-protection.texy index 4a81dc0f86..672cecc566 100644 --- a/nette/tr/vulnerability-protection.texy +++ b/nette/tr/vulnerability-protection.texy @@ -1,22 +1,22 @@ -Güvenlik Açığı Koruması -*********************** +Güvenlik Açıklarına Karşı Koruma +******************************** .[perex] -Arada sırada büyük bir güvenlik açığı duyuruluyor ve hatta suistimal ediliyor. Elbette bu biraz tatsız bir durum. Web uygulamalarınızın güvenliğine önem veriyorsanız, Nette Framework açıkçası sizin için en iyi seçimdir. +Her an başka bir önemli web sitesinde bir güvenlik açığı bildiriliyor veya bir açık kötüye kullanılıyor. Bu can sıkıcı. Web uygulamalarınızın güvenliğine önem veriyorsanız, Nette Framework kesinlikle en iyi seçimdir. -Siteler Arası Komut Dosyası Oluşturma (XSS) .[#toc-cross-site-scripting-xss] -============================================================================ +Cross-Site Scripting (XSS) +========================== -Siteler Arası Komut Dosyası Yazma, yazılmamış girdi kullanan bir site bozma yöntemidir. Bir saldırgan kendi HTML veya JavaScript kodunu enjekte edebilir ve sayfanın görünümünü değiştirebilir, hatta kullanıcılar hakkında hassas bilgiler toplayabilir. XSS'ye karşı koruma basittir: tüm dizelerin ve girdilerin tutarlı ve doğru bir şekilde kaçması. Geleneksel olarak, kodlayıcınızın en ufak bir hata yapması ve bir kez unutması yeterli olurdu ve tüm web sitesi tehlikeye girebilirdi. +Cross-Site Scripting, işlenmemiş çıktıları kötüye kullanan web sitelerini ihlal etme yöntemidir. Saldırgan daha sonra sayfaya kendi kodunu ekleyebilir ve böylece sayfayı değiştirebilir veya hatta ziyaretçiler hakkında hassas veriler elde edebilir. XSS'e karşı yalnızca tüm karakter dizilerinin tutarlı ve doğru bir şekilde işlenmesiyle savunulabilir. Bu arada, kodlayıcınızın bunu yalnızca bir kez unutması yeterlidir ve tüm web sitesi bir anda tehlikeye girebilir. -Bu tür bir enjeksiyona örnek olarak, kullanıcıya "kötü niyetli" bir komut dosyası ekleyen değiştirilmiş bir URL vermek verilebilir. Bir uygulama girdilerini düzgün bir şekilde kaçmazsa, böyle bir istek muhtemelen istemci tarafında bir komut dosyası çalıştıracaktır. Bu, örneğin, kimliğin çalınmasına yol açabilir. +Saldırı örneği, kullanıcıya değiştirilmiş bir URL göndererek sayfaya kendi kodumuzu enjekte etmek olabilir. Uygulama çıktıları düzgün bir şekilde işlemezse, komut dosyasını kullanıcının tarayıcısında yürütür. Bu şekilde, örneğin kimliğini çalabiliriz. ``` -https://example.com/?search=<script>alert('XSS attack.');</script> +https://example.com/?search=<script>alert('Başarılı XSS saldırısı.');</script> ``` -Nette Framework, sizi Siteler Arası Komut Dosyası risklerinden sonsuza kadar kurtaracak yepyeni bir [Bağlam Farkında |latte:safety-first#context-aware-escaping] Kaçış teknolojisi ile geliyor. Tüm girdileri belirli bir bağlama göre otomatik olarak kaçar, böylece bir kodlayıcının yanlışlıkla bir şeyi unutması imkansızdır. Örnek olarak aşağıdaki şablonu ele alalım: +Nette Framework, sizi Cross-Site Scripting riskinden kalıcı olarak kurtaracak devrim niteliğinde bir [Context-Aware Escaping |latte:safety-first#Bağlama Duyarlı Kaçış] teknolojisi sunar. Tüm çıktıları otomatik olarak işlediği için kodlayıcının bir şeyi unutması mümkün olmaz. Örneğin, bir kodlayıcı şu şablonu oluşturur: ```latte <p onclick="alert({$message})">{$message}</p> @@ -26,29 +26,29 @@ document.title = {$message}; </script> ``` -`{$message}` komutu bir değişkeni yazdırır. Diğer çerçeveler genellikle geliştiricileri kaçışları ve hatta bağlama göre ne tür kaçışları açıkça beyan etmeye zorlar. Ancak Nette Framework'te hiçbir şey bildirmenize gerek yoktur. Her şey otomatik, tutarlı ve doğrudur. Değişkeni `$message = 'Width 1/2"'` olarak ayarlarsak, framework bu HTML kodunu oluşturacaktır: +`{$message}` gösterimi, bir değişkenin yazdırılması anlamına gelir. Diğer framework'lerde, her yazdırmanın açıkça işlenmesi ve hatta her yerde farklı şekilde işlenmesi gerekir. Nette Framework'te hiçbir şeyi işlemenize gerek yoktur, her şey otomatik, doğru ve tutarlı bir şekilde yapılır. Değişkene `$message = 'Šířka 1/2"'` değerini atarsak, framework şu HTML kodunu oluşturur: ```latte -<p onclick="alert("Width 1\/2\"")">Width 1/2"</p> +<p onclick="alert("Šířka 1\/2\"")">Šířka 1/2"</p> <script> -document.title = "Width 1\/2\""; +document.title = "Šířka 1\/2\""; </script> ``` -Siteler Arası İstek Sahteciliği (CSRF) .[#toc-cross-site-request-forgery-csrf] -============================================================================== +Cross-Site Request Forgery (CSRF) +================================= -Siteler Arası İstek Sahteciliği saldırısı, saldırganın kurbanın tarayıcısında kurbanın o anda oturum açtığı sunucuya sessizce bir istek yürüten bir sayfayı ziyaret etmesi için kurbanı kandırması ve sunucunun isteğin kurban tarafından kendi isteğiyle yapıldığına inanmasıdır. Sunucu, kurbanın kimliği altında ancak kurban farkında olmadan belirli bir eylem gerçekleştirir. Bu, veri değiştirme veya silme, mesaj gönderme vb. olabilir. +Cross-Site Request Forgery saldırısı, saldırganın kurbanı, kurbanın oturum açtığı sunucuya kurbanın tarayıcısında gizlice bir istek yürüten bir sayfaya çekmesi ve sunucunun isteğin kurban tarafından kendi isteğiyle yapıldığına inanmasıdır. Ve böylece kurbanın kimliği altında, kurbanın haberi olmadan belirli bir eylemi gerçekleştirir. Bu, verilerin değiştirilmesi veya silinmesi, bir mesaj gönderilmesi vb. olabilir. -Nette Framework **sunuculardaki formları ve sinyalleri** bu tür saldırılara karşı otomatik olarak korur. Bu, başka bir etki alanından gönderilmelerini veya çağrılmalarını önleyerek yapılır. Korumayı kapatmak için formlar için aşağıdakileri kullanın: +Nette Framework **formları ve presenter'lardaki sinyalleri otomatik olarak** bu tür saldırılara karşı korur. Ve bunu, başka bir alan adından gönderilmelerini veya çağrılmalarını engelleyerek yapar. Korumayı kapatmak istiyorsanız, formlarda şunu kullanın: ```php $form->allowCrossOrigin(); ``` -veya bir sinyal söz konusu olduğunda, `@crossOrigin` şeklinde bir açıklama ekleyin: +veya sinyal durumunda `@crossOrigin` ek açıklamasını ekleyin: ```php /** @@ -59,44 +59,41 @@ public function handleXyz() } ``` -PHP 8'de öznitelikleri de kullanabilirsiniz: +Nette Application 3.2'de nitelikleri de kullanabilirsiniz: ```php -use Nette\Application\Attributes\CrossOrigin; +use Nette\Application\Attributes\Requires; -#[CrossOrigin] +#[Requires(sameOrigin: false)] public function handleXyz() { } ``` -URL Saldırısı, Kontrol Kodları, Geçersiz UTF-8 .[#toc-url-attack-control-codes-invalid-utf-8] -============================================================================================= +URL Saldırısı, Kontrol Kodları, Geçersiz UTF-8 +============================================== -Farklı terimlerin hepsi saldırganın uygulamanıza "kötü niyetli" bir girdi verme çabasıyla ilgilidir. Sonuçlar, bozuk XML çıktılarından (örn. arızalı RSS akışı) bir veritabanından hassas bilgilerin alınmasına ve kullanıcı şifrelerinin ele geçirilmesine kadar çok çeşitli olabilir. Bu saldırılara karşı koruma, bayt düzeyinde tutarlı UTF-8 kontrolüdür. Ve açıkçası, bunu bir çerçeve olmadan yapamazsınız, değil mi? +Saldırganın web uygulamanıza *zararlı* girdi gönderme çabasıyla ilgili çeşitli terimler. Sonuçlar çok çeşitli olabilir, XML çıktılarının bozulmasından (örneğin çalışmayan RSS beslemeleri) veritabanından hassas bilgilerin veya şifrelerin alınmasına kadar. Savunma, tüm girdilerin tek tek bayt düzeyinde tutarlı bir şekilde işlenmesidir. Ve elinizi kalbinize koyun, hanginiz bunu yapıyor? -Nette Framework bunu sizin için otomatik olarak yapar. Hiçbir şey yapılandırmanız gerekmez ve uygulamanız güvende olur. +Nette Framework bunu sizin için ve ayrıca otomatik olarak yapar. Hiçbir şey ayarlamanıza gerek yoktur ve tüm girdiler işlenir. -Oturum Korsanlığı, Oturum Çalınması, Oturum Sabitleme .[#toc-session-hijacking-session-stealing-session-fixation] -================================================================================================================= +Oturum Kaçırma, Oturum Çalma, Oturum Sabitleme +============================================== -Oturum yönetimi birkaç tür saldırı içerir. Saldırgan kurbanın oturum kimliğini çalabilir ya da taklit edebilir ve böylece gerçek parola olmadan bir web uygulamasına erişim sağlayabilir. Ardından saldırgan, kullanıcının yapabildiği her şeyi hiçbir iz bırakmadan yapabilir. Koruma, hem PHP'nin hem de web sunucusunun uygun şekilde yapılandırılmasında yatmaktadır. +Oturum yönetimiyle ilgili birkaç tür saldırı vardır. Saldırgan ya kullanıcının oturum kimliğini (session ID) çalar ya da ona kendi oturum kimliğini gönderir ve bu sayede kullanıcının şifresini bilmeden web uygulamasına erişim kazanır. Ardından uygulamada kullanıcının haberi olmadan herhangi bir şey yapabilir. Savunma, sunucunun ve PHP'nin doğru yapılandırılmasında yatar. -Nette Framework PHP'yi otomatik olarak yapılandırır. Böylece geliştiricilerin bir oturumu nasıl yeterince korumalı hale getirecekleri konusunda endişelenmelerine gerek kalmaz ve tamamen uygulamanın önemli kısımlarına odaklanabilirler. Bunun için `ini_set()` işlevinin etkinleştirilmesi gerekir. +Bu arada Nette Framework, PHP'yi otomatik olarak yapılandırır. Programcı bu nedenle oturumu nasıl doğru bir şekilde güvence altına alacağını düşünmek zorunda kalmaz ve tamamen uygulama oluşturmaya odaklanabilir. Ancak bu, `ini_set()` fonksiyonunun etkinleştirilmesini gerektirir. -AynıSite Çerezi .[#toc-samesite-cookie] -======================================= +SameSite Çerezi +=============== -SameSite çerezleri, bir sayfanın yüklenmesine neyin yol açtığını tanımak için bir mekanizma sağlar. Bu da güvenlik açısından kesinlikle çok önemlidir. +SameSite çerezleri, sayfanın yüklenmesine neyin yol açtığını tanımak için bir mekanizma sağlar. Bu, güvenlik açısından kesinlikle çok önemlidir. -SameSite bayrağı üç değere sahip olabilir: `Lax`, `Strict` ve `None` (HTTPS gerektirir). Bir sayfa için istek doğrudan web'in kendisinden geliyorsa veya kullanıcı sayfayı doğrudan adres çubuğuna girerek veya bir yer imine tıklayarak açıyorsa, -tarayıcı tüm çerezleri sunucuya gönderir (yani `Lax`, `Strict` ve `None` bayrakları ile). Bir kullanıcı web sitesine başka bir web sitesinden linke tıklayarak gelirse, `Lax` ve `None` bayraklı çerezler sunucuya iletilecektir. Eğer istek başka bir -Başka bir kaynaktan POST formu gönderme, bir iframe içine yükleme, JavaScript kullanma vb. gibi yollarla yalnızca `None` bayrağına sahip çerezler gönderilecektir. - -Varsayılan olarak, Nette tüm çerezleri `Lax` bayrağı ile gönderir. +SameSite bayrağı üç değere sahip olabilir: `Lax`, `Strict` ve `None` (bu HTTPS gerektirir). Sayfa isteği doğrudan web sitesinden geliyorsa veya kullanıcı sayfayı doğrudan adres çubuğuna yazarak veya bir yer imine tıklayarak açarsa, tarayıcı sunucuya tüm çerezleri gönderir (yani `Lax`, `Strict` ve `None` bayraklarıyla). Kullanıcı başka bir web sitesinden bir bağlantı aracılığıyla web sitesine tıklarsa, sunucuya `Lax` ve `None` bayraklarına sahip çerezler iletilir. İstek başka bir şekilde, örneğin başka bir web sitesinden bir POST formu göndererek, bir iframe içinde yükleyerek, JavaScript kullanarak vb. ortaya çıkarsa, yalnızca `None` bayrağına sahip çerezler gönderilir. +Nette varsayılan olarak tüm çerezleri `Lax` bayrağıyla gönderir. {{leftbar: www:@menu-common}} diff --git a/nette/uk/@home.texy b/nette/uk/@home.texy index 22a16c5c9d..b9daa24f9f 100644 --- a/nette/uk/@home.texy +++ b/nette/uk/@home.texy @@ -6,45 +6,45 @@ <div> -Вступ ------ -- [Чому варто використовувати Nette? |www:10-reasons-why-nette] -- [Встановлення |Installation] -- [Створіть свій перший додаток! |quickstart:] +Знайомство +---------- +- [Чому використовувати Nette? |www:10-reasons-why-nette] +- [Встановлення |installation] +- [Пишемо першу програму! |quickstart:] -Загальна інформація -------------------- +Загальне +-------- - [Список пакетів |www:packages] -- [Обслуговування та PHP |www:maintenance] -- [Примітки до випуску |https://nette.org/releases] -- [Керівництво з оновлення |migrations:en] +- [Підтримка та версії PHP |www:maintenance] +- [Release Notes |https://nette.org/releases] +- [Перехід на новіші версії|migrations:en] - [Вирішення проблем |nette:troubleshooting] -- [Творці Nette |https://nette.org/contributors] +- [Хто створює Nette |https://nette.org/contributors] - [Історія Nette |www:history] -- [Взяти участь |contributing:] -- [Розвиток спонсорів |https://nette.org/en/donate] -- [Довідник з API |https://api.nette.org/] +- [Долучайтеся |contributing:] +- [Підтримайте розробку |https://nette.org/cs/donate] +- [API reference |https://api.nette.org/] </div> <div> -Додаток Nette -------------- -- [Як працюють додатки? |application:how-it-works] -- [Bootstrap |application:Bootstrap] -- [Презентери |application:Presenters] +Програми в Nette +---------------- +- [Як працюють програми? |application:how-it-works] +- [Bootstrapping |application:Bootstrapping] +- [Presenters |application:presenters] - [Шаблони |application:templates] -- [Модулі |application:modules] -- [Маршрутизація |application:Routing] -- [Створення URL-посилань |application:creating-links] +- [Структура каталогів |application:directory-structure] +- [Маршрутизація |application:routing] +- [Створення посилань URL |application:creating-links] - [Інтерактивні компоненти |application:components] -- [AJAX і сніпети |application:ajax] +- [AJAX & сніпети |application:ajax] -- [Найкращі практики |best-practices:] +- [Посібники та практики |best-practices:] </div> @@ -54,41 +54,42 @@ Основні теми ------------ - [Конфігурація |nette:configuring] -- [Впровадження залежностей |dependency-injection:] -- [Latte: Шаблони |latte:] -- [Tracy: Інструмент налагодження |tracy:] +- [Dependency Injection|dependency-injection:] +- [Latte: шаблони |latte:] +- [Tracy: налагодження коду |tracy:] - [Форми |forms:] -- [База даних |database:core] -- [Аутентифікація користувачів |security:authentication] -- [Контроль доступу |security:authorization] +- [База даних |database:guide] +- [Вхід користувачів |security:authentication] +- [Перевірка прав доступу |security:authorization] - [Сесії |http:Sessions] -- [HTTP-запит & відповідь |http:] -- [Кешування |caching:] -- [Надсилання імейлів |mail:] +- [HTTP запит & відповідь|http:] +- [Активи |assets:] +- [Кеш |caching:] +- [Відправлення електронних листів |mail:] - [Schema: валідація даних |schema:] -- [PHP Code Generator |php-generator:] -- [Tester: Unit-тестування |tester:] +- [Генератор PHP коду |php-generator:] +- [Tester: тестування |tester:] </div> <div> -Утиліти -------- -- [Arrays |utils:Arrays] -- [Filesystem |utils:filesystem] +Utilities +--------- +- [Масиви |utils:arrays] +- [Файлова система |utils:filesystem] - [Finder |utils:finder] -- [HTML Elements |utils:HTML Elements] -- [Зображення |utils:Images] +- [HTML елементи |utils:html-elements] +- [Зображення |utils:images] - [JSON |utils:JSON] -- [NEON |neon:] -- [Зашивання пароля |security:passwords] -- [SmartObject |utils:SmartObject] -- [Типи PHP |utils:type] -- [Strings |utils:Strings] +- [NEON|neon:] +- [Хешування паролів |security:passwords] +- [PHP типи |utils:type] +- [Рядки |utils:strings] - [Валідатори |utils:validators] - [RobotLoader |robot-loader:] +- [SmartObject |utils:smartobject] & [StaticClass |utils:StaticClass] - [SafeStream |safe-stream:] - [...інше |utils:] </div> @@ -97,5 +98,5 @@ {{toc:no}} -{{description: Офіційна документація Nette описує принципи роботи Nette і найкращі методи розробки веб-додатків.}} +{{description: Офіційна документація Nette: описує, як працює Nette та найкращі практики для розробки веб-додатків.}} {{maintitle: Документація Nette}} diff --git a/nette/uk/@menu-topics.texy b/nette/uk/@menu-topics.texy index 30c287a830..a0a66ba174 100644 --- a/nette/uk/@menu-topics.texy +++ b/nette/uk/@menu-topics.texy @@ -1,21 +1,21 @@ Основні теми ************ - [Конфігурація |nette:configuring] -- [Додаток Nette |application:how-it-works] -- [Впровадження залежностей |dependency-injection:] -- [Утиліти |utils:] +- [Програми в Nette |application:how-it-works] +- [Dependency Injection|dependency-injection:] +- [Utilities |utils:] - [Форми |forms:] -- [База даних |database:core] -- [Аутентифікація користувачів |security:authentication] -- [Контроль доступу |security:authorization] +- [База даних |database:guide] +- [Вхід користувачів |security:authentication] +- [Перевірка прав доступу |security:authorization] - [Сесії |http:Sessions] -- [HTTP-запит & відповідь |http:] -- [Кешування |caching:] -- [Надсилання імейлів |mail:] +- [HTTP запит & відповідь|http:] +- [Кеш |caching:] +- [Відправлення електронних листів |mail:] - [Schema: валідація даних |schema:] -- [Генератор PHP Code Generator |php-generator:] +- [Генератор PHP коду |php-generator:] - [Latte: шаблони |latte:] -- [Tracy: налагодження |tracy:] +- [Tracy: налагодження коду |tracy:] - [Tester: тестування |tester:] diff --git a/nette/uk/@meta.texy b/nette/uk/@meta.texy new file mode 100644 index 0000000000..96e2d9752a --- /dev/null +++ b/nette/uk/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документація Nette}} diff --git a/nette/uk/configuring.texy b/nette/uk/configuring.texy index 11dc2f1473..235090634d 100644 --- a/nette/uk/configuring.texy +++ b/nette/uk/configuring.texy @@ -2,34 +2,35 @@ ****************** .[perex] -Огляд усіх опцій конфігурації у фреймворку Nette. +Огляд усіх опцій конфігурації в Nette Framework. -Компоненти Nette налаштовуються за допомогою конфігураційних файлів, які зазвичай записуються у форматі [NEON |neon:format]. Їх найкраще редагувати в [редакторах, які підтримують цей формат |best-practices:editors-and-tools#IDE-Editor]. -Якщо ви використовуєте повний фреймворк, конфігурація буде [завантажена під час завантаження |application:bootstrap#DI-Container-Configuration], якщо ні, дивіться [як завантажити конфігурацію |bootstrap:]. +Компоненти Nette налаштовуються за допомогою конфігураційних файлів, які зазвичай записуються у [форматі NEON|neon:format]. Найкраще їх редагувати в [редакторах з його підтримкою |best-practices:editors-and-tools#IDE редактор]. Якщо ви використовуєте весь фреймворк, конфігурація [завантажується під час запуску програми |application:bootstrapping#Конфігурація DI-контейнера], якщо ні, прочитайте, [як завантажити конфігурацію|bootstrap:]. <pre> "application .[prism-token prism-atrule]":[application:configuration#Application]: "Додаток .[prism-token prism-comment]"<br> -"constants .[prism-token prism-atrule]":[application:configuration#Constants]: "Визначає константи PHP .[prism-token prism-comment]"<br> +"assets .[prism-token prism-atrule]":[assets:configuration]: "Assets .[prism-token prism-comment]"<br> +"constants .[prism-token prism-atrule]":[application:configuration#Константи]: "Визначення PHP констант .[prism-token prism-comment]"<br> "database .[prism-token prism-atrule]":[database:configuration]: "База даних .[prism-token prism-comment]"<br> "decorator .[prism-token prism-atrule]":[dependency-injection:configuration#Decorator]: "Декоратор .[prism-token prism-comment]"<br> "di .[prism-token prism-atrule]":[dependency-injection:configuration#DI]: "DI-контейнер .[prism-token prism-comment]"<br> -"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Extensions]: "Встановлення розширень DI .[prism-token prism-comment]"<br> +"extensions .[prism-token prism-atrule]":[dependency-injection:configuration#Розширення]: "Встановлення додаткових DI розширень .[prism-token prism-comment]"<br> "forms .[prism-token prism-atrule]":[forms:configuration]: "Форми .[prism-token prism-comment]"<br> -"http .[prism-token prism-atrule]":[http:configuration#HTTP Headers]: "Заголовки HTTP .[prism-token prism-comment]"<br> -"includes .[prism-token prism-atrule]":[dependency-injection:configuration#Including files]: "Файли, що включаються .[prism-token prism-comment]"<br> -"latte .[prism-token prism-atrule]":[application:configuration#Latte]: "Latte .[prism-token prism-comment]"<br> -"mail .[prism-token prism-atrule]":[mail:#Configuring]: "Поштова розсилка .[prism-token prism-comment]"<br> -"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#Parameters]: "Параметри .[prism-token prism-comment]"<br> -"php .[prism-token prism-atrule]":[application:configuration#PHP]: "Параметри конфігурації PHP .[prism-token prism-comment]"<br> -"routing .[prism-token prism-atrule]":[application:configuration#Routing]: "Маршрутизація .[prism-token prism-comment]"<br> +"http .[prism-token prism-atrule]":[http:configuration#HTTP-заголовки]: "HTTP-заголовки .[prism-token prism-comment]"<br> +"includes .[prism-token prism-atrule]":[dependency-injection:configuration#Включення файлів]: "Включення файлів .[prism-token prism-comment]"<br> +"latte .[prism-token prism-atrule]":[application:configuration#Шаблони Latte]: "Шаблони Latte .[prism-token prism-comment]"<br> +"mail .[prism-token prism-atrule]":[mail:#Конфігурація]: "Пошта .[prism-token prism-comment]"<br> +"parameters .[prism-token prism-atrule]":[dependency-injection:configuration#Параметри]: "Параметри .[prism-token prism-comment]"<br> +"php .[prism-token prism-atrule]":[application:configuration#PHP]: "Конфігурація PHP .[prism-token prism-comment]"<br> +"routing .[prism-token prism-atrule]":[application:configuration#Маршрутизація]: "Маршрутизація .[prism-token prism-comment]"<br> "search .[prism-token prism-atrule]":[dependency-injection:configuration#Search]: "Автоматична реєстрація сервісів .[prism-token prism-comment]"<br> -"security .[prism-token prism-atrule]":[security:configuration]: "Контроль доступу .[prism-token prism-comment]"<br> +"security .[prism-token prism-atrule]":[security:configuration]: "Права доступу .[prism-token prism-comment]"<br> "services .[prism-token prism-atrule]":[dependency-injection:services]: "Сервіси .[prism-token prism-comment]"<br> -"session .[prism-token prism-atrule]":[http:configuration#Session]: "Сесія .[prism-token prism-comment]"<br> +"session .[prism-token prism-atrule]":[http:configuration#Сесія]: "Сесія .[prism-token prism-comment]"<br> "tracy .[prism-token prism-atrule]":[tracy:configuring#Nette Framework]: "Налагоджувач Tracy .[prism-token prism-comment]" </pre> -Якщо ви використовуєте рядок, який починається з `@` или содержит `%` в будь-якому місці, вам потрібно екранувати його за допомогою додавання другого символу `@` или `%`. .[note] +.[note] +Щоб записати рядок, що містить символ `%`, ви повинні екранувати його, подвоївши до `%%`. {{leftbar: @menu-topics}} diff --git a/nette/uk/glossary.texy b/nette/uk/glossary.texy index 2f694dc280..7ac777d23f 100644 --- a/nette/uk/glossary.texy +++ b/nette/uk/glossary.texy @@ -1,155 +1,159 @@ -Глосарій термінів -***************** +Словник термінів +**************** -AJAX .[#toc-ajax] ------------------ -Асинхронний JavaScript і XML - технологія взаємодії клієнта і сервера за протоколом HTTP без необхідності перезавантаження всієї сторінки при кожному запиті. Незважаючи на абревіатуру, формат [JSON |#JSON] часто використовують замість XML. +AJAX +---- +Asynchronous JavaScript and XML - технологія обміну інформацією між клієнтом і сервером за допомогою протоколу HTTP без необхідності перезавантаження всієї сторінки при кожному запиті. Хоча з назви може здатися, що дані надсилаються лише у форматі XML, зазвичай використовується також формат [#JSON]. -Дія презентера .[#toc-presenter-action] ---------------------------------------- -Логічна частина [presenter |#presenter], що виконує одну дію, наприклад, показати сторінку продукту, виписати користувача тощо. В одного презентера може бути кілька дій. +Дія presenter'а +--------------- +Логічна частина presenter'а, яка виконує одну дію. Наприклад, відображає сторінку продукту, виходить з системи користувача тощо. Один presenter може мати кілька дій. BOM --- -Так звана *маска порядку байтів* - це спеціальний перший символ файлу, який вказує порядок байтів у кодуванні. Деякі редактори вмикають його автоматично, він практично непомітний, але він спричиняє проблеми із заголовками та надсиланням виводу з PHP. Для масового видалення можна використовувати [Code Checker |code-checker:]. +Так звана *byte order mark* - це спеціальний перший символ у файлі, який використовується як індикатор порядку байтів у кодуванні. Деякі редактори вставляють його у файли. Він практично невидимий, але спричиняє проблеми з надсиланням виводу та заголовків з PHP. Для масового видалення можна використовувати [Code Checker|code-checker:]. -Контролер .[#toc-controller] ----------------------------- -Контролер обробляє запити від користувача і на їхній основі викликає певну логіку застосунку (тобто [Модель |#Модель]), потім він викликає [Вигляд |#Вид] для рендерингу даних. Аналогом контролерів у фреймворку Nette є [презентери |#Presenter]. +Controller +---------- +Контролер, який обробляє запити користувача і на їх основі викликає відповідну логіку програми (тобто [#модель]) і потім запитує [#представлення] для відображення даних. Аналогом контролерів у Nette Framework є [presenter'и |#Presenter]. -Міжсайтовий скриптинг (XSS) .[#toc-cross-site-scripting-xss] ------------------------------------------------------------- -Міжсайтовий скриптинг - це метод порушення роботи сайту з використанням неекранованого введення. Зловмисник може впровадити свій власний код HTML або JavaScript і змінити зовнішній вигляд сторінки або навіть зібрати конфіденційну інформацію про користувачів. Захист від XSS простий: послідовне і правильне екранування всіх рядків і даних, що вводяться. +Cross-Site Scripting (XSS) +-------------------------- +Cross-Site Scripting - це метод порушення веб-сайтів, що використовує необроблені вихідні дані. Зловмисник може вставити свій власний код на сторінку, тим самим змінивши її або навіть отримавши конфіденційні дані відвідувачів. Захиститися від XSS можна лише послідовною та коректною обробкою всіх рядків. -Nette Framework пропонує абсолютно нову технологію [Context-Aware Escaping |latte:safety-first#Context-Aware-Escaping], яка назавжди позбавить вас від ризиків міжсайтового скриптингу. Він автоматично екранує всі дані, що вводяться, ґрунтуючись на заданому контексті, тому кодер не зможе випадково щось забути. +Nette Framework пропонує революційну технологію [Context-Aware Escaping |latte:safety-first#Контекстно-залежне екранування], яка назавжди позбавить вас ризику Cross-Site Scripting. Вона автоматично обробляє всі вихідні дані, тому кодер не може щось забути. -Підробка міжсайтових запитів (CSRF) .[#toc-cross-site-request-forgery-csrf] ---------------------------------------------------------------------------- -Атака Cross-Site Request Forgery полягає в тому, що зловмисник заманює жертву відвідати сторінку, яка мовчки виконує запит у браузері жертви до сервера, на якому жертва на даний момент зареєстрована, і сервер вважає, що запит був зроблений жертвою за власним бажанням. Сервер виконує певну дію під особистістю жертви, але без її відома. Це може бути зміна або видалення даних, надсилання повідомлення тощо. +Cross-Site Request Forgery (CSRF) +--------------------------------- +Атака Cross-Site Request Forgery полягає в тому, що зловмисник заманює жертву на сторінку, яка непомітно виконує запит у браузері жертви до сервера, на якому жертва авторизована, і сервер вважає, що запит був виконаний жертвою за власним бажанням. Таким чином, під ідентичністю жертви виконується певна дія, про яку вона не знає. Це може бути зміна або видалення даних, надсилання повідомлення тощо. -Nette Framework **автоматично захищає форми та сигнали в презентаторах** від цього типу атак. Це робиться шляхом запобігання їхнього надсилання або виклику з іншого домену. +Nette Framework **автоматично захищає форми та сигнали в presenter'ах** від цього типу атак. Це робиться шляхом запобігання їх надсиланню або виклику з іншого домену. -Ін'єкція залежностей .[#toc-dependency-injection] -------------------------------------------------- -Ін'єкція залежностей (Dependency Injection, DI) - це патерн проектування, який показує, як відокремити створення об'єктів від їхніх залежностей. Тобто клас не відповідає за створення або ініціалізацію своїх залежностей, натомість ці залежності надаються зовнішнім кодом (який може містити контейнер [DI |#Dependency Injection container]). Перевага полягає в тому, що це забезпечує більшу гнучкість коду, кращу читабельність і легше тестування додатків, оскільки залежності легко замінюються та ізолюються від інших частин коду. Для отримання додаткової інформації див. статтю [Що таке ін'єкція залежностей? |dependency-injection:introduction] +Dependency Injection +-------------------- +Dependency Injection (DI) - це патерн проектування, який визначає, як відокремити створення об'єктів від їхніх залежностей. Тобто клас не відповідає за створення або ініціалізацію своїх залежностей, а натомість ці залежності надаються йому зовнішнім кодом (це може бути [DI-контейнер |#Dependency Injection контейнер]). Перевага полягає в тому, що це забезпечує більшу гнучкість коду, кращу зрозумілість та легше тестування програми, оскільки залежності легко замінюються та ізолюються від інших частин коду. Більше в розділі [Що таке Dependency Injection? |dependency-injection:introduction] -Контейнер Dependency Injection .[#toc-dependency-injection-container] ---------------------------------------------------------------------- -Контейнер Dependency Injection (також DI-контейнер або IoC-контейнер) - це інструмент для створення та управління залежностями в додатку (або [сервісах |#service]). Контейнер зазвичай має конфігурацію, яка визначає, які класи залежать від інших класів, які конкретні реалізації залежностей використовувати і як створювати ці залежності. Потім контейнер створює ці об'єкти і надає їх класам, які їх потребують. Для отримання додаткової інформації див. статтю [Що таке DI-контейнер? |dependency-injection:container] +Dependency Injection контейнер +------------------------------ +Dependency Injection контейнер (також DI-контейнер або IoC-контейнер) - це інструмент, який забезпечує створення та керування залежностями в додатку (або [сервісами |#Сервіс]). Контейнер зазвичай має конфігурацію, яка визначає, які класи залежать від інших класів, які конкретні реалізації залежностей слід використовувати та як ці залежності мають створюватися. Потім контейнер створює ці об'єкти та надає їх класам, які їх потребують. Більше в розділі [Що таке DI-контейнер? |dependency-injection:container] -Екранування .[#toc-escaping] ----------------------------- -Екранування - це перетворення символів, що мають особливе значення в даному контексті, в інші еквівалентні послідовності. Приклад: Ми хочемо записати лапки в укладений у лапки рядок. Оскільки лапки мають особливе значення в контексті укладеного в лапки рядка, необхідно використовувати іншу еквівалентну послідовність. Конкретна послідовність визначається правилами контексту (наприклад, `\"` в укладеному в лапки рядку PHP, `"` в атрибутах HTML тощо). +Екранування +----------- +Екранування - це перетворення символів, що мають спеціальне значення в даному контексті, на інші відповідні послідовності. Приклад: у рядок, обмежений лапками, ми хочемо записати лапки. Оскільки лапки мають спеціальне значення в контексті рядка, і їх просте написання було б розцінено як завершення рядка, їх потрібно записати іншою відповідною послідовністю. Якою саме, визначають правила контексту. -Фільтр .[#toc-filter-formerly-helper] -------------------------------------- -Функція фільтрації. У шаблонах [filter |latte:syntax#Filters] - це функція, яка допомагає змінити або відформатувати дані у вихідну форму. У шаблонах зумовлено кілька [стандартних фільтрів |latte:filters]. +Фільтр (раніше helper) +---------------------- +У шаблонах під терміном [фільтр |latte:syntax#Фільтри] зазвичай розуміють функцію, яка допомагає змінити або переформатувати дані до кінцевого вигляду. Шаблони мають кілька [стандартних фільтрів |latte:filters]. -Інвалідація .[#toc-invalidation] --------------------------------- -Повідомлення про [сніпет |#SameSite-Cookie] для повторного рендерингу. В іншому контексті також очищення кешу. +Інвалідація +----------- +Повідомлення [сніпету |#Сніпет] про необхідність перемалювання. В іншому значенні також видалення вмісту кешу. -JSON .[#toc-json] ------------------ -Формат обміну даними, заснований на синтаксисі JavaScript (це його підмножина). Точну специфікацію можна знайти на сайті www.json.org. +JSON +---- +Формат обміну даними, що базується на синтаксисі JavaScript (є його підмножиною). Точну специфікацію можна знайти на сторінці www.json.org. -Компонент .[#toc-component] ---------------------------- -Багаторазово використовувана частина програми. Це може бути візуальна частина сторінки, як описано в розділі [application:components], або цей термін може також позначати клас [Component |component-model:] (такий компонент не обов'язково має бути візуальним). +Компонент +--------- +Повторно використовувана частина програми. Це може бути візуальна частина сторінки, як описано в розділі [Пишемо компоненти |application:components], або під терміном компонент також розуміють клас [Component |component-model:] (такий компонент не обов'язково має бути візуальним). -Керівні символи .[#toc-control-characters] ------------------------------------------- -Керуючі символи - це невидимі символи, які можуть зустрічатися в тексті та зрештою спричиняти деякі проблеми. Для їх масового видалення з файлів можна використовувати [Code Checker |code-checker:], для видалення зі змінної - функцію [Strings::normalize() |utils:strings#normalize]. +Керуючі символи +--------------- +Керуючі символи - це невидимі символи, які можуть зустрічатися в тексті та іноді спричиняти проблеми. Для їх масового видалення з файлів можна використовувати [Code Checker|code-checker:], а для видалення зі змінної - функцію [Strings::normalize() |utils:strings#normalize]. -Події .[#toc-events] --------------------- -Подія - це очікувана ситуація в об'єкті, при виникненні якої викликаються так звані обробники, тобто зворотні виклики, що реагують на подію ("зразок":https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38). Подією може бути, наприклад, відправлення форми, вхід користувача в систему тощо. Таким чином, події є формою *інверсії управління*. +Події (události) +---------------- +Подія - це очікувана ситуація в об'єкті, при настанні якої викликаються так звані обробники (handler), тобто callback-функції, що реагують на подію ("приклад":https://gist.github.com/dg/332cdd51bdf7d66a6d8003b134508a38). Подією може бути, наприклад, відправка форми, вхід користувача тощо. Події є формою *Inversion of Control*. -Наприклад, вхід користувача в систему відбувається в методі `Nette\Security\User::login()`. Об'єкт `User` має публічну змінну `$onLoggedIn`, що являє собою масив, у який кожен може додати зворотний виклик. Щойно користувач входить у систему, метод `login()` викликає всі зворотні виклики в масиві. Ім'я змінної у формі `onXyz` - це угода, яка використовується в усьому Nette. +Наприклад, вхід користувача відбувається в методі `Nette\Security\User::login()`. Об'єкт `User` має публічну змінну `$onLoggedIn`, яка є масивом, до якого будь-хто може додати callback. У момент входу користувача метод `login()` викликає всі callback-функції в масиві. Назва змінної у форматі `onXyz` є конвенцією, що використовується у всьому Nette. -Latte .[#toc-latte] -------------------- -Одна з найбільш інноваційних [систем шаблонування |latte:] за всю історію. +Latte +----- +Одна з найпрогресивніших [систем шаблонів |latte:]. -Модель .[#toc-model] --------------------- -Модель являє собою дані та функціональну основу всього додатка. Вона включає в себе всю логіку програми (іноді також звану "бізнес-логікою"). Це **M** з **M**VC або MPV. Будь-яка дія користувача (вхід у систему, поміщення товару в кошик, зміна значення бази даних) являє собою дію моделі. +Модель +------ +Модель - це дані та, перш за все, функціональна основа всієї програми. Вона містить всю логіку програми (також використовується термін бізнес-логіка). Це **M** з **M**VC або MVP. Будь-яка дія користувача (вхід, додавання товару в кошик, зміна значення в базі даних) є дією моделі. + +Модель керує своїм внутрішнім станом і надає зовнішньому світу фіксований інтерфейс. Викликаючи функції цього інтерфейсу, ми можемо дізнаватися або змінювати її стан. Модель не знає про існування [#представлення] або [контролера |#Controller]. + -Модель керує своїм внутрішнім станом і надає публічний інтерфейс. Викликаючи цей інтерфейс, ми можемо приймати або змінювати його стан. Модель не знає про існування [Виду |#Вид] або [Контролера |#Controller], вона повністю незалежна від них. +Model-View-Controller +--------------------- +Архітектура програмного забезпечення, яка виникла з потреби відокремити код обробки ([контролер |#Controller]) від коду логіки програми ([#модель]) та коду відображення даних ([#представлення]) у програмах з графічним інтерфейсом. Це робить програму зрозумілішою, полегшує майбутню розробку та дозволяє тестувати окремі частини окремо. -Модель-Вид-Контролер (MVC) .[#toc-model-view-controller] --------------------------------------------------------- -Архітектура програмного забезпечення, що виникла під час розроблення GUI-додатків для відокремлення коду керування потоком ([Контролер |#Контроллер]) від коду логіки додатка ([Модель |#Модель]) та від коду візуалізації даних ([Вид |#Вид]). Таким чином, код стає зрозумілішим, це полегшує майбутню розробку і дає змогу тестувати окремі частини окремо. +Model-View-Presenter +-------------------- +Архітектура, що базується на [#Model-View-Controller]. -Модель-Вид-Презентер (MVP) .[#toc-model-view-presenter] -------------------------------------------------------- -Архітектура, заснована на [Модель-Вид-Контролер (MVC) |#Модель-Вид-Контроллер (MVC)]. +Модуль +------ +Модуль представляє логічну частину програми. У типовій структурі це група presenter'ів та шаблонів, які вирішують певну область функціональності. Модулі розміщуються в [окремих каталогах |application:directory-structure#Presenter и та шаблони], таких як `Front/`, `Admin/` або `Shop/`. +Наприклад, інтернет-магазин ми розділимо на: +- Фронтенд (`Shop/`) для перегляду товарів та здійснення покупок +- Клієнтську секцію (`Customer/`) для керування замовленнями +- Адміністрацію (`Admin/`) для оператора -Модуль .[#toc-module] ---------------------- -Модуль у фреймворку Nette являє собою набір презентерів і шаблонів, в кінцевому підсумку також компонентів і моделей, які слугують даними для презентера. Таким чином, це певна логічна частина програми. +Технічно це звичайні каталоги, але завдяки чіткому поділу вони допомагають масштабувати програму. Presenter `Admin:Product:List` фізично буде розташований, наприклад, у каталозі `app/Presentation/Admin/Product/List/` (див. [мапінг presenter'ів |application:directory-structure#Мапінг presenter ів]). -Наприклад, електронний магазин може складатися з трьох модулів: -1) Каталог товарів із кошиком. -2) Адміністрування для клієнта. -3) Адміністрування для власника магазину. +Namespace +--------- +Простір імен, частина мови PHP з версії 5.3 та деяких інших мов програмування, що дозволяє використовувати класи, які мають однакові назви в різних бібліотеках, без конфліктів імен. Див. [документацію PHP |https://www.php.net/manual/en/language.namespaces.rationale.php]. -Простір імен .[#toc-namespace] ------------------------------- -Простір імен є особливістю мови PHP, починаючи з версії 5.3, а також деяких інших мов програмування. Це допомагає уникнути зіткнень імен (наприклад, два класи з однаковим ім'ям) при спільному використанні різних бібліотек. Більш детальну інформацію дивіться в [документації PHP |https://www.php.net/manual/ru/language.namespaces.rationale.php]. +Presenter +--------- +Presenter - це об'єкт, який приймає [запит |api:Nette\Application\Request], перетворений маршрутизатором з HTTP-запиту, і генерує [відповідь |api:Nette\Application\Response]. Відповіддю може бути HTML-сторінка, зображення, XML-документ, файл на диску, JSON, перенаправлення або будь-що, що ви придумаєте. -Презентер .[#toc-presenter] ---------------------------- -Презентер - це об'єкт, який приймає [запит |api:Nette\Application\Request], перекладений маршрутизатором з HTTP-запиту, і генерує [відповідь |api:Nette\Application\Response]. Відповіддю може бути HTML-сторінка, картинка, XML-документ, файл, JSON, перенаправлення або все, що ви придумаєте. +Зазвичай під терміном presenter розуміють нащадка класу [api:Nette\Application\UI\Presenter]. Залежно від вхідних запитів він запускає відповідні [дії |application:presenters#Життєвий цикл презентера] та рендерить шаблони. -Під презентером зазвичай мається на увазі нащадок класу [api:Nette\Application\UI\Presenter]. За запитами він виконує відповідні [дії |application:presenters#Life-Cycle-of-Presenter] та рендерить шаблони. +Маршрутизатор +------------- +Двосторонній перекладач між HTTP-запитом / URL та дією presenter'а. Двосторонній означає, що з HTTP-запиту можна визначити [дію presenter'а |#Дія presenter а], але також навпаки, для дії можна згенерувати відповідний URL. Більше в розділі про [маршрутизацію URL |application:routing]. -Роутер .[#toc-router] ---------------------- -Двонаправлений перекладач між HTTP-запитом / URL і дією презентера. Двоспрямованість означає, що можна не тільки отримати [Дію презентера |#Действие презентера] з HTTP-запиту, а й згенерувати відповідний URL для дії. Див. докладніше в розділі про [URL-маршрутизацію |application:routing]. +SameSite cookie +--------------- +SameSite cookies надають механізм для розпізнавання того, що призвело до завантаження сторінки. Може мати три значення: `Lax`, `Strict` та `None` (останній вимагає HTTPS). Якщо запит на сторінку надходить безпосередньо з веб-сайту, або користувач відкриває сторінку, вводячи її безпосередньо в адресний рядок або клацаючи на закладку, браузер надсилає серверу всі файли cookie (тобто з прапорцями `Lax`, `Strict` та `None`). Якщо користувач переходить на веб-сайт за посиланням з іншого веб-сайту, серверу передаються файли cookie з прапорцями `Lax` та `None`. Якщо запит виникає іншим чином, наприклад, надсиланням POST-форми з іншого веб-сайту, завантаженням усередині iframe, за допомогою JavaScript тощо, надсилаються лише файли cookie з прапорцем `None`. -SameSite Cookie .[#toc-samesite-cookie] ---------------------------------------- -Файли cookie SameSite забезпечують механізм розпізнавання того, що призвело до завантаження сторінки. Він може мати три значення: `Lax`, `Strict` і `None` (останнє вимагає HTTPS). Якщо запит на сторінку надходить безпосередньо з сайту або користувач відкриває сторінку, вводячи адресу безпосередньо в адресному рядку або натискаючи на закладку, браузер відправляє всі файли cookie на сервер (тобто з прапорами `Lax`, `Strict` і `None`). Якщо користувач переходить на сайт за посиланням з іншого сайту, на сервер передаються файли cookie з прапорами `Lax` і `None`. Якщо запит здійснюється іншими способами, наприклад, відправка POST-форми з іншого сайту, завантаження в iframe, використання JavaScript і т.д., надсилаються тільки файли cookie з прапором `None`. +Сервіс +------ +У контексті Dependency Injection сервісом називається об'єкт, який створюється та керується DI-контейнером. Сервіс може бути легко замінений іншою реалізацією, наприклад, для тестування або для зміни поведінки програми, без необхідності змінювати код, який використовує сервіс. -Сервіс .[#toc-service] ----------------------- -В контексті Dependency Injection, сервіс - це об'єкт, який створюється і управляється контейнером DI. Сервіс можна легко замінити іншою реалізацією, наприклад, для тестування або зміни поведінки програми, без необхідності змінювати код, який використовує сервіс. + +Сніпет +------ +Фрагмент, частина сторінки, яку можна окремо перемалювати під час AJAX-запиту. -Фрагмент .[#toc-snippet] ------------------------- -Фрагмент сторінки, який можна окремо відрендерити під час [AJAX-запиту |#AJAX]. +Представлення +------------- +Представлення (View) - це шар програми, який відповідає за відображення результату запиту. Зазвичай він використовує систему шаблонів і знає, як відобразити той чи інший компонент або результат, отриманий з моделі. -Вигляд .[#toc-view] -------------------- -Представлення - це рівень додатку, який відповідає за відображення результатів запиту. Зазвичай він використовує систему шаблонів і знає, як відображати свої компоненти або результати, взяті з моделі. diff --git a/nette/uk/installation.texy b/nette/uk/installation.texy index d0bb92c629..fe1ed0bdd3 100644 --- a/nette/uk/installation.texy +++ b/nette/uk/installation.texy @@ -2,66 +2,66 @@ ****************** .[perex] -Ви хочете використати переваги Nette у вашому існуючому проекті або плануєте створити новий проект на основі Nette? Цей посібник крок за кроком проведе вас через процес інсталяції. +Хочете використовувати переваги Nette у своєму існуючому проекті або збираєтеся створити новий проект на основі Nette? Цей посібник проведе вас крок за кроком через процес встановлення. -Як додати Nette до вашого проекту .[#toc-how-to-add-nette-to-your-project] --------------------------------------------------------------------------- +Як додати Nette до свого проекту +-------------------------------- -Nette пропонує колекцію корисних і складних пакетів (бібліотек) для PHP. Щоб додати їх до вашого проекту, виконайте наступні кроки: +Nette пропонує колекцію корисних та зрілих пакетів (бібліотек) для PHP. Щоб включити їх у свій проект, виконайте такі дії: -1) **Налаштуйте [Composer |best-practices:composer]:** Цей інструмент необхідний для легкого встановлення, оновлення та керування бібліотеками, необхідними для вашого проекту. +1) **Підготуйте [Composer|best-practices:composer]:** Цей інструмент необхідний для легкого встановлення, оновлення та керування бібліотеками, потрібними для вашого проекту. -2) **Виберіть [пакунок |www:packages]:** Припустімо, вам потрібно здійснити навігацію по файловій системі, з чим чудово впорається [Finder |utils:finder] з пакунка `nette/utils`. Назву пакунка можна знайти у правій колонці його документації. +2) **Виберіть [пакет|www:packages]:** Припустимо, вам потрібно переглядати файлову систему, що чудово робить [Finder|utils:finder] з пакету `nette/utils`. Назву пакету ви бачите у правому стовпці його документації. -3) **Встановити пакунок:** Запустіть цю команду у кореневому каталозі вашого проекту: +3) **Встановіть пакет:** Виконайте цю команду в кореневому каталозі вашого проекту: ```shell composer require nette/utils ``` -Віддаєте перевагу графічному інтерфейсу? Ознайомтеся з [посібником |https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] зі встановлення пакунків у середовищі PhpStrom. +Віддаєте перевагу графічному інтерфейсу? Перегляньте [інструкцію|https://www.jetbrains.com/help/phpstorm/using-the-composer-dependency-manager.html] зі встановлення пакетів у середовищі PhpStorm. -Як розпочати новий проект за допомогою Nette .[#toc-how-to-start-a-new-project-with-nette] ------------------------------------------------------------------------------------------- +Як створити новий проект з Nette +-------------------------------- -Якщо ви хочете створити абсолютно новий проект на платформі Nette, ми рекомендуємо використовувати попередньо встановлений скелет [Web Project |https://github.com/nette/web-project]: +Якщо ви хочете створити абсолютно новий проект на платформі Nette, рекомендуємо використовувати попередньо налаштований скелет [Web Project|https://github.com/nette/web-project]: -1) **Налаштуйте [Composer |best-practices:composer].** +1) **Підготуйте [Composer|best-practices:composer].** 2) **Відкрийте командний рядок** і перейдіть до кореневого каталогу вашого веб-сервера, наприклад, `/etc/var/www`, `C:/xampp/htdocs`, `/Library/WebServer/Documents`. 3) **Створіть проект** за допомогою цієї команди: ```shell -composer create-project nette/web-project PROJECT_NAME +composer create-project nette/web-project НАЗВА_ПРОЕКТУ ``` -4) **Не використовуєте Composer?** Просто завантажте [веб-проект у форматі ZIP |https://github.com/nette/web-project/archive/preloaded.zip] і розархівуйте його. Але повірте нам, Composer того вартий! +4) **Не використовуєте Composer?** Просто завантажте [Web Project у форматі ZIP|https://github.com/nette/web-project/archive/preloaded.zip] та розпакуйте його. Але повірте, Composer вартий того! -5) **Встановлення дозволів:** У системах macOS або Linux встановіть [дозволи на запис |nette:troubleshooting#Setting directory permissions] для каталогів. +5) **Налаштування прав:** На системах macOS або Linux встановіть [права на запис |nette:troubleshooting#Налаштування прав доступу до каталогів] для каталогів `temp/` та `log/`. -6) **Відкрийте проєкт у браузері:** Введіть URL-адресу `http://localhost/PROJECT_NAME/www/`. Ви побачите цільову сторінку скелету: +6) **Відкриття проекту в браузері:** Введіть URL `http://localhost/НАЗВА_ПРОЕКТУ/www/` і ви побачите початкову сторінку скелета: -[* qs-welcome.webp .{url: http://localhost/PROJECT_NAME/www/} *] +[* qs-welcome.webp .{url: http://localhost/НАЗВА_ПРОЕКТУ/www/} *] -Вітаємо! Ваш сайт готовий до розробки. Не соромтеся видалити шаблон привітання і почати створювати свій додаток. +Вітаємо! Ваш веб-сайт тепер готовий до розробки. Ви можете видалити вітальний шаблон і почати створювати свій додаток. -Однією з переваг Nette є те, що проект працює одразу, без необхідності налаштування. Однак, якщо у вас виникнуть якісь проблеми, ознайомтеся з типовими [рішеннями |nette:troubleshooting#nette-is-not-working-white-page-is-displayed]. +Однією з переваг Nette є те, що проект працює одразу без необхідності конфігурації. Однак, якщо ви зіткнетеся з проблемами, спробуйте переглянути [вирішення поширених проблем |nette:troubleshooting#Nette не працює відображається біла сторінка]. .[note] -Якщо ви починаєте працювати з Nette, ми рекомендуємо продовжити з підручника " [Створення першої програми |quickstart:]". +Якщо ви починаєте працювати з Nette, рекомендуємо продовжити з [посібником Пишемо перший додаток|quickstart:]. -Інструменти та рекомендації .[#toc-tools-and-recommendations] -------------------------------------------------------------- +Інструменти та рекомендації +--------------------------- -Для ефективної роботи з Nette ми рекомендуємо наступні інструменти: +Для ефективної роботи з Nette рекомендуємо наступні інструменти: -- [Якісна IDE з плагінами для Nette |best-practices:editors-and-tools] +- [Якісне IDE з доповненнями для Nette|best-practices:editors-and-tools] - Система контролю версій Git -- [Composer |best-practices:composer] +- [Composer|best-practices:composer] {{leftbar: www:@menu-common}} diff --git a/nette/uk/introduction-to-object-oriented-programming.texy b/nette/uk/introduction-to-object-oriented-programming.texy new file mode 100644 index 0000000000..2b1d8fbbb1 --- /dev/null +++ b/nette/uk/introduction-to-object-oriented-programming.texy @@ -0,0 +1,841 @@ +Вступ до об'єктно-орієнтованого програмування +********************************************* + +.[perex] +Термін "ООП" означає об'єктно-орієнтоване програмування, що є способом організації та структурування коду. ООП дозволяє нам розглядати програму як набір об'єктів, що взаємодіють між собою, а не як послідовність команд і функцій. + +В ООП "об'єкт" - це одиниця, яка містить дані та функції, що працюють з цими даними. Об'єкти створюються за "класами", які можна розуміти як креслення або шаблони для об'єктів. Коли ми маємо клас, ми можемо створити його "екземпляр", що є конкретним об'єктом, створеним за цим класом. + +Давайте покажемо, як ми можемо створити простий клас у PHP. При визначенні класу ми використовуємо ключове слово "class", за яким слідує назва класу, а потім фігурні дужки, що оточують функції (їх називають "методами") та змінні класу (їх називають "властивостями" або англійською "property"): + +```php +class Автомобіль +{ + function посигналити() + { + echo 'Bip bip!'; + } +} +``` + +У цьому прикладі ми створили клас з назвою `Автомобіль` з однією функцією (або "методом"), названою `посигналити`. + +Кожен клас повинен вирішувати лише одне основне завдання. Якщо клас робить занадто багато речей, може бути доцільно розділити його на менші, спеціалізовані класи. + +Класи зазвичай зберігаються в окремих файлах, щоб код був організованим і легким для навігації. Назва файлу повинна відповідати назві класу, тому для класу `Автомобіль` назва файлу буде `Автомобіль.php`. + +При іменуванні класів добре дотримуватися конвенції "PascalCase", що означає, що кожне слово в назві починається з великої літери, і між ними немає підкреслень або інших роздільників. Методи та властивості використовують конвенцію "camelCase", тобто починаються з малої літери. + +Деякі методи в PHP мають спеціальні завдання і позначаються префіксом `__` (два підкреслення). Одним з найважливіших спеціальних методів є "конструктор", який позначається як `__construct`. Конструктор - це метод, який автоматично викликається, коли ви створюєте новий екземпляр класу. + +Конструктор часто використовується для встановлення початкового стану об'єкта. Наприклад, коли ви створюєте об'єкт, що представляє особу, ви можете використати конструктор для встановлення її віку, імені або інших властивостей. + +Давайте покажемо, як використовувати конструктор у PHP: + +```php +class Людина +{ + private $вік; + + function __construct($вік) + { + $this->вік = $вік; + } + + function отриматиВік() + { + return $this->вік; + } +} + +$людина = new Людина(25); +echo $людина->отриматиВік(); // Виведе: 25 +``` + +У цьому прикладі клас `Людина` має властивість (змінну) `$вік` та конструктор, який встановлює цю властивість. Метод `отриматиВік()` потім дозволяє отримати доступ до віку особи. + +Псевдозмінна `$this` використовується всередині класу для доступу до властивостей та методів об'єкта. + +Ключове слово `new` використовується для створення нового екземпляра класу. У наведеному вище прикладі ми створили нову людину з віком 25. + +Ви також можете встановити значення за замовчуванням для параметрів конструктора, якщо вони не вказані під час створення об'єкта. Наприклад: + +```php +class Людина +{ + private $вік; + + function __construct($вік = 20) + { + $this->вік = $вік; + } + + function отриматиВік() + { + return $this->вік; + } +} + +$людина = new Людина; // якщо ми не передаємо жодного аргументу, дужки можна опустити +echo $людина->отриматиВік(); // Виведе: 20 +``` + +У цьому прикладі, якщо ви не вкажете вік при створенні об'єкта `Людина`, буде використано значення за замовчуванням 20. + +Приємно, що визначення властивості з її ініціалізацією через конструктор можна так скоротити та спростити: + +```php +class Людина +{ + function __construct( + private $вік = 20, + ) { + } +} +``` + +Для повноти, крім конструкторів, об'єкти можуть мати й деструктори (метод `__destruct`), які викликаються перед тим, як об'єкт буде звільнений з пам'яті. + + +Простори імен +------------- + +Простори імен (або "namespaces" англійською) дозволяють нам організовувати та групувати пов'язані класи, функції та константи, а також уникати конфліктів імен. Ви можете уявити їх як папки на комп'ютері, де кожна папка містить файли, що належать до певного проекту або теми. + +Простори імен особливо корисні у великих проектах або коли ви використовуєте бібліотеки сторонніх розробників, де можуть виникнути конфлікти в назвах класів. + +Уявіть, що у вас є клас з назвою `Автомобіль` у вашому проекті, і ви хочете розмістити його в просторі імен під назвою `Транспорт`. Ви зробите це так: + +```php +namespace Транспорт; + +class Автомобіль +{ + function посигналити() + { + echo 'Bip bip!'; + } +} +``` + +Якщо ви хочете використати клас `Автомобіль` в іншому файлі, ви повинні вказати, з якого простору імен походить клас: + +```php +$автомобіль = new Транспорт\Автомобіль; +``` + +Для спрощення ви можете на початку файлу вказати, який клас з даного простору імен ви хочете використовувати, що дозволяє створювати екземпляри без необхідності вказувати повний шлях: + +```php +use Транспорт\Автомобіль; + +$автомобіль = new Автомобіль; +``` + + +Успадкування +------------ + +Успадкування є інструментом об'єктно-орієнтованого програмування, який дозволяє створювати нові класи на основі вже існуючих класів, переймати їхні властивості та методи, а також розширювати або перевизначати їх за потребою. Успадкування дозволяє забезпечити повторне використання коду та ієрархію класів. + +Простіше кажучи, якщо ми маємо один клас і хотіли б створити інший, похідний від нього, але з деякими змінами, ми можемо "успадкувати" новий клас від початкового класу. + +У PHP успадкування реалізується за допомогою ключового слова `extends`. + +Наш клас `Людина` зберігає інформацію про вік. Ми можемо мати інший клас `Student`, який розширює `Людину` і додає інформацію про спеціальність. + +Розглянемо приклад: + +```php +class Людина +{ + private $вік; + + function __construct($вік) + { + $this->вік = $вік; + } + + function вивестиІнформацію() + { + echo "Вік: {$this->вік} років\n"; + } +} + +class Student extends Людина +{ + private $спеціальність; + + function __construct($вік, $спеціальність) + { + parent::__construct($вік); + $this->спеціальність = $спеціальність; + } + + function вивестиІнформацію() + { + parent::вивестиІнформацію(); + echo "Спеціальність: {$this->спеціальність} \n"; + } +} + +$student = new Student(20, 'Інформатика'); +$student->вивестиІнформацію(); +``` + +Як працює цей код? + +- Ми використали ключове слово `extends` для розширення класу `Людина`, що означає, що клас `Student` успадкує всі методи та властивості від `Людини`. + +- Ключове слово `parent::` дозволяє нам викликати методи з батьківського класу. У цьому випадку ми викликали конструктор з класу `Людина` перед додаванням власної функціональності до класу `Student`. І аналогічно метод `вивестиІнформацію()` батьківського класу перед виведенням інформації про студента. + +Успадкування призначене для ситуацій, коли існує відношення "є" між класами. Наприклад, `Student` є `Людина`. Кішка є твариною. Це дає нам можливість у випадках, коли в коді ми очікуємо один об'єкт (напр., "Людина"), використати замість нього успадкований об'єкт (напр., "Student"). + +Важливо усвідомити, що основною метою успадкування **не є** запобігання дублюванню коду. Навпаки, неправильне використання успадкування може призвести до складного і важкопідтримуваного коду. Якщо відношення "є" між класами не існує, ми повинні замість успадкування розглянути композицію. + +Зверніть увагу, що методи `вивестиІнформацію()` в класах `Людина` та `Student` виводять трохи різну інформацію. І ми можемо додати інші класи (наприклад, `Працівник`), які надаватимуть інші реалізації цього методу. Здатність об'єктів різних класів реагувати на один і той самий метод різними способами називається поліморфізмом: + +```php +$люди = [ + new Людина(30), + new Student(20, 'Інформатика'), + new Працівник(45, 'Директор'), +]; + +foreach ($люди as $людина) { + $людина->вивестиІнформацію(); +} +``` + + +Композиція +---------- + +Композиція - це техніка, коли замість того, щоб успадковувати властивості та методи іншого класу, ми просто використовуємо його екземпляр у нашому класі. Це дозволяє нам комбінувати функціональність та властивості кількох класів без необхідності створювати складні структури успадкування. + +Розглянемо приклад. Ми маємо клас `Двигун` і клас `Автомобіль`. Замість того, щоб говорити "Автомобіль є Двигун", ми говоримо "Автомобіль має Двигун", що є типовим відношенням композиції. + +```php +class Двигун +{ + function запустити() + { + echo 'Двигун працює.'; + } +} + +class Автомобіль +{ + private $двигун; + + function __construct() + { + $this->двигун = new Двигун; + } + + function завести() + { + $this->двигун->запустити(); + echo 'Автомобіль готовий до поїздки!'; + } +} + +$автомобіль = new Автомобіль; +$автомобіль->завести(); +``` + +Тут `Автомобіль` не має всіх властивостей та методів `Двигуна`, але має до нього доступ через властивість `$двигун`. + +Перевагою композиції є більша гнучкість у дизайні та краща можливість модифікації в майбутньому. + + +Видимість +--------- + +У PHP ви можете визначити "видимість" для властивостей, методів та констант класу. Видимість визначає, звідки ви можете отримати доступ до цих елементів. + +1. **Public:** Якщо елемент позначений як `public`, це означає, що до нього можна отримати доступ звідусіль, навіть поза класом. + +2. **Protected:** Елемент з позначкою `protected` доступний лише в межах даного класу та всіх його нащадків (класів, що успадковують від цього класу). + +3. **Private:** Якщо елемент є `private`, до нього можна отримати доступ лише зсередини класу, в якому він був визначений. + +Якщо ви не вкажете видимість, PHP автоматично встановить її на `public`. + +Розглянемо приклад коду: + +```php +class ПрикладВидимості +{ + public $публічнаВластивість = 'Публічна'; + protected $захищенаВластивість = 'Захищена'; + private $приватнаВластивість = 'Приватна'; + + public function вивестиВластивості() + { + echo $this->публічнаВластивість; // Працює + echo $this->захищенаВластивість; // Працює + echo $this->приватнаВластивість; // Працює + } +} + +$обєкт = new ПрикладВидимості; +$обєкт->вивестиВластивості(); +echo $обєкт->публічнаВластивість; // Працює +// echo $обєкт->захищенаВластивість; // Викличе помилку +// echo $обєкт->приватнаВластивість; // Викличе помилку +``` + +Продовжимо з успадкуванням класу: + +```php +class НащадокКласу extends ПрикладВидимості +{ + public function вивестиВластивості() + { + echo $this->публічнаВластивість; // Працює + echo $this->захищенаВластивість; // Працює + // echo $this->приватнаВластивість; // Викличе помилку + } +} +``` + +У цьому випадку метод `вивестиВластивості()` в класі `НащадокКласу` може отримати доступ до публічних та захищених властивостей, але не може отримати доступ до приватних властивостей батьківського класу. + +Дані та методи повинні бути якомога більше прихованими та доступними лише через визначений інтерфейс. Це дозволить вам змінювати внутрішню реалізацію класу, не впливаючи на решту коду. + + +Ключове слово `final` +--------------------- + +У PHP ми можемо використовувати ключове слово `final`, якщо хочемо заборонити класу, методу або константі успадковуватися або перевизначатися. Коли ми позначаємо клас як `final`, він не може бути розширений. Коли ми позначаємо метод як `final`, він не може бути перевизначений у класі-нащадку. + +Усвідомлення того, що певний клас або метод не буде далі модифікуватися, дозволяє нам легше вносити зміни, не побоюючись можливих конфліктів. Наприклад, ми можемо додати новий метод без побоювань, що якийсь його нащадок вже має метод з такою ж назвою і сталася б колізія. Або ми можемо змінити параметри методу, оскільки знову ж таки немає ризику викликати невідповідність з перевизначеним методом у нащадку. + +```php +final class ФінальнийКлас +{ +} + +// Наступний код викличе помилку, оскільки ми не можемо успадковувати від фінального класу. +class НащадокФінальногоКласу extends ФінальнийКлас +{ +} +``` + +У цьому прикладі спроба успадкування від фінального класу `ФінальнийКлас` викличе помилку. + + +Статичні властивості та методи +------------------------------ + +Коли ми говоримо в PHP про "статичні" елементи класу, ми маємо на увазі методи та властивості, які належать самому класу, а не конкретному екземпляру цього класу. Це означає, що вам не потрібно створювати екземпляр класу, щоб отримати до них доступ. Замість цього ви викликаєте їх або отримуєте до них доступ безпосередньо через назву класу. + +Майте на увазі, що оскільки статичні елементи належать класу, а не його екземплярам, ви не можете всередині статичних методів використовувати псевдозмінну `$this`. + +Використання статичних властивостей призводить до [незрозумілого коду, повного підводних каменів|dependency-injection:global-state], тому ви ніколи не повинні їх використовувати, і ми навіть не будемо показувати приклад використання тут. Навпаки, статичні методи корисні. Приклад використання: + +```php +class Калькулятор +{ + public static function додавання($a, $b) + { + return $a + $b; + } + + public static function віднімання($a, $b) + { + return $a - $b; + } +} + +// Використання статичного методу без створення екземпляра класу +echo Калькулятор::додавання(5, 3); // Результат: 8 +echo Калькулятор::віднімання(5, 3); // Результат: 2 +``` + +У цьому прикладі ми створили клас `Калькулятор` з двома статичними методами. Ці методи ми можемо викликати безпосередньо без створення екземпляра класу за допомогою оператора `::`. Статичні методи особливо корисні для операцій, які не залежать від стану конкретного екземпляра класу. + + +Константи класу +--------------- + +У межах класів ми маємо можливість визначати константи. Константи - це значення, які ніколи не змінюються під час виконання програми. На відміну від змінних, значення константи залишається незмінним. + +```php +class Автомобіль +{ + public const КількістьКоліс = 4; + + public function показатиКількістьКоліс(): int + { + echo self::КількістьКоліс; + } +} + +echo Автомобіль::КількістьКоліс; // Вивід: 4 +``` + +У цьому прикладі ми маємо клас `Автомобіль` з константою `КількістьКоліс`. Коли ми хочемо отримати доступ до константи всередині класу, ми можемо використовувати ключове слово `self` замість назви класу. + + +Інтерфейси об'єктів +------------------- + +Інтерфейси об'єктів діють як "контракти" для класів. Якщо клас має реалізувати інтерфейс, він повинен містити всі методи, які визначає цей інтерфейс. Це чудовий спосіб забезпечити, щоб певні класи дотримувалися однакового "контракту" або структури. + +У PHP інтерфейс визначається ключовим словом `interface`. Усі методи, визначені в інтерфейсі, є публічними (`public`). Коли клас реалізує інтерфейс, він використовує ключове слово `implements`. + +```php +interface Тварина +{ + function видатиЗвук(); +} + +class Кішка implements Тварина +{ + public function видатиЗвук() + { + echo 'Няв'; + } +} + +$кішка = new Кішка; +$кішка->видатиЗвук(); +``` + +Якщо клас реалізує інтерфейс, але в ньому не визначені всі очікувані методи, PHP видасть помилку. + +Клас може реалізувати кілька інтерфейсів одночасно, що є відмінністю від успадкування, де клас може успадковувати лише від одного класу: + +```php +interface Охоронець +{ + function охоронятиБудинок(); +} + +class Собака implements Тварина, Охоронець +{ + public function видатиЗвук() + { + echo 'Гав'; + } + + public function охоронятиБудинок() + { + echo 'Собака пильно охороняє будинок'; + } +} +``` + + +Абстрактні класи +---------------- + +Абстрактні класи служать базовими шаблонами для інших класів, але ви не можете створювати їхні екземпляри безпосередньо. Вони містять комбінацію повних методів та абстрактних методів, які не мають визначеного вмісту. Класи, що успадковують від абстрактних класів, повинні надати визначення для всіх абстрактних методів предка. + +Для визначення абстрактного класу ми використовуємо ключове слово `abstract`. + +```php +abstract class АбстрактнийКлас +{ + public function звичайнийМетод() + { + echo 'Це звичайний метод'; + } + + abstract public function абстрактнийМетод(); +} + +class Нащадок extends АбстрактнийКлас +{ + public function абстрактнийМетод() + { + echo 'Це реалізація абстрактного методу'; + } +} + +$екземпляр = new Нащадок; +$екземпляр->звичайнийМетод(); +$екземпляр->абстрактнийМетод(); +``` + +У цьому прикладі ми маємо абстрактний клас з одним звичайним та одним абстрактним методом. Потім ми маємо клас `Нащадок`, який успадковує від `АбстрактнийКлас` і надає реалізацію для абстрактного методу. + +Чим насправді відрізняються інтерфейси та абстрактні класи? Абстрактні класи можуть містити як абстрактні, так і конкретні методи, тоді як інтерфейси лише визначають, які методи повинен реалізувати клас, але не надають жодної реалізації. Клас може успадковувати лише від одного абстрактного класу, але може реалізувати будь-яку кількість інтерфейсів. + + +Перевірка типів +--------------- + +У програмуванні дуже важливо мати впевненість, що дані, з якими ми працюємо, мають правильний тип. У PHP є інструменти, які нам це забезпечують. Перевірка того, чи мають дані правильний тип, називається "перевіркою типів". + +Типи, з якими ми можемо зіткнутися в PHP: + +1. **Базові типи**: Включають `int` (цілі числа), `float` (дійсні числа), `bool` (логічні значення), `string` (рядки), `array` (масиви) та `null`. +2. **Класи**: Якщо ми хочемо, щоб значення було екземпляром певного класу. +3. **Інтерфейси**: Визначає набір методів, які клас повинен реалізувати. Значення, яке відповідає інтерфейсу, повинно мати ці методи. +4. **Змішані типи**: Ми можемо вказати, що змінна може мати кілька дозволених типів. +5. **Void**: Цей спеціальний тип позначає, що функція чи метод не повертає жодного значення. + +Давайте покажемо, як змінити код, щоб він включав типи: + +```php +class Людина +{ + private int $вік; + + public function __construct(int $вік) + { + $this->вік = $вік; + } + + public function вивестиВік(): void + { + echo "Цій людині {$this->вік} років."; + } +} + +/** + * Функція, яка приймає об'єкт класу Людина та виводить вік людини. + */ +function вивестиВікЛюдини(Людина $людина): void +{ + $людина->вивестиВік(); +} +``` + +Таким чином ми забезпечили, що наш код очікує та працює з даними правильного типу, що допомагає нам запобігати потенційним помилкам. + +Деякі типи неможливо записати безпосередньо в PHP. У такому випадку вони вказуються в коментарі phpDoc, що є стандартним форматом для документування коду PHP, який починається з `/**` і закінчується `*/`. Він дозволяє додавати описи класів, методів тощо. А також вказувати складні типи за допомогою так званих анотацій `@var`, `@param` та `@return`. Ці типи потім використовуються інструментами статичного аналізу коду, але сам PHP їх не перевіряє. + +```php +class Список +{ + /** @var array<Людина> запис означає, що це масив об'єктів Людина */ + private array $люди = []; + + public function додатиЛюдину(Людина $людина): void + { + $this->люди[] = $людина; + } +} +``` + + +Порівняння та ідентичність +-------------------------- + +У PHP ви можете порівнювати об'єкти двома способами: + +1. Порівняння значень `==`: Перевіряє, чи об'єкти належать до одного класу і мають однакові значення у своїх властивостях. +2. Ідентичність `===`: Перевіряє, чи це той самий екземпляр об'єкта. + +```php +class Автомобіль +{ + public string $марка; + + public function __construct(string $марка) + { + $this->марка = $марка; + } +} + +$автомобіль1 = new Автомобіль('Skoda'); +$автомобіль2 = new Автомобіль('Skoda'); +$автомобіль3 = $автомобіль1; + +var_dump($автомобіль1 == $автомобіль2); // true, оскільки вони мають однакове значення +var_dump($автомобіль1 === $автомобіль2); // false, оскільки це не той самий екземпляр +var_dump($автомобіль1 === $автомобіль3); // true, оскільки $автомобіль3 є тим самим екземпляром, що й $автомобіль1 +``` + + +Оператор `instanceof` +--------------------- + +Оператор `instanceof` дозволяє визначити, чи є даний об'єкт екземпляром певного класу, нащадком цього класу, або чи реалізує він певний інтерфейс. + +Уявімо собі, що ми маємо клас `Людина` та інший клас `Student`, який є нащадком класу `Людина`: + +```php +class Людина +{ + private int $вік; + + public function __construct(int $вік) + { + $this->вік = $вік; + } +} + +class Student extends Людина +{ + private string $спеціальність; + + public function __construct(int $вік, string $спеціальність) + { + parent::__construct($вік); + $this->спеціальність = $спеціальність; + } +} + +$студент = new Student(20, 'Інформатика'); + +// Перевірка, чи є $студент екземпляром класу Student +var_dump($студент instanceof Student); // Вивід: bool(true) + +// Перевірка, чи є $студент екземпляром класу Людина (оскільки Student є нащадком Людини) +var_dump($студент instanceof Людина); // Вивід: bool(true) +``` + +З виводів видно, що об'єкт `$студент` одночасно вважається екземпляром обох класів - `Student` і `Людина`. + + +Fluent Interfaces +----------------- + +"Плавний інтерфейс" (англійською "Fluent Interface") - це техніка в ООП, яка дозволяє ланцюжком викликати методи один за одним в одному виклику. Це часто спрощує та робить код зрозумілішим. + +Ключовим елементом плавного інтерфейсу є те, що кожен метод у ланцюжку повертає посилання на поточний об'єкт. Цього ми досягаємо тим, що в кінці методу використовуємо `return $this;`. Цей стиль програмування часто асоціюється з методами, що називаються "сеттерами", які встановлюють значення властивостей об'єкта. + +Покажемо, як може виглядати плавний інтерфейс на прикладі надсилання електронних листів: + +```php +public function sendMessage() +{ + $email = new Email; + $email->setFrom('sender@example.com') + ->setRecipient('admin@example.com') + ->setMessage('Hello, this is a message.') + ->send(); +} +``` + +У цьому прикладі методи `setFrom()`, `setRecipient()` та `setMessage()` служать для встановлення відповідних значень (відправника, одержувача, вмісту повідомлення). Після встановлення кожного з цих значень методи повертають поточний об'єкт (`$email`), що дозволяє нам викликати наступний метод у ланцюжку. Нарешті, ми викликаємо метод `send()`, який фактично надсилає електронний лист. + +Завдяки плавним інтерфейсам ми можемо писати код, який є інтуїтивно зрозумілим та легко читабельним. + + +Копіювання за допомогою `clone` +------------------------------- + +У PHP ми можемо створити копію об'єкта за допомогою оператора `clone`. Таким чином ми отримаємо новий екземпляр з ідентичним вмістом. + +Якщо нам потрібно під час копіювання об'єкта змінити деякі його властивості, ми можемо визначити в класі спеціальний метод `__clone()`. Цей метод автоматично викликається, коли об'єкт клонується. + +```php +class Вівця +{ + public string $імя; + + public function __construct(string $імя) + { + $this->імя = $імя; + } + + public function __clone() + { + $this->імя = 'Клон ' . $this->імя; + } +} + +$оригінал = new Вівця('Dolly'); +echo $оригінал->імя . "\n"; // Виведе: Dolly + +$клон = clone $оригінал; +echo $клон->імя . "\n"; // Виведе: Клон Dolly +``` + +У цьому прикладі ми маємо клас `Вівця` з однією властивістю `$імя`. Коли ми клонуємо екземпляр цього класу, метод `__clone()` дбає про те, щоб ім'я клонованої вівці отримало префікс "Клон". + + +Трейди +------ + +Трейди в PHP - це інструмент, який дозволяє спільно використовувати методи, властивості та константи між класами та запобігти дублюванню коду. Ви можете уявити їх як механізм "копіювати та вставити" (Ctrl-C та Ctrl-V), коли вміст трейту "вставляється" в класи. Це дозволяє вам повторно використовувати код без необхідності створювати складні ієрархії класів. + +Давайте покажемо простий приклад, як використовувати трейти в PHP: + +```php +trait Сигналення +{ + public function посигналити() + { + echo 'Bip bip!'; + } +} + +class Автомобіль +{ + use Сигналення; +} + +class Вантажівка +{ + use Сигналення; +} + +$автомобіль = new Автомобіль; +$автомобіль->посигналити(); // Виведе 'Bip bip!' + +$вантажівка = new Вантажівка; +$вантажівка->посигналити(); // Також виведе 'Bip bip!' +``` + +У цьому прикладі ми маємо трейт під назвою `Сигналення`, який містить один метод `посигналити()`. Потім ми маємо два класи: `Автомобіль` та `Вантажівка`, які обидва використовують трейт `Сигналення`. Завдяки цьому обидва класи "мають" метод `посигналити()`, і ми можемо викликати його на об'єктах обох класів. + +Трейди дозволяють вам легко та ефективно спільно використовувати код між класами. При цьому вони не входять до ієрархії успадкування, тобто `$автомобіль instanceof Сигналення` поверне `false`. + + +Винятки +------- + +Винятки в ООП дозволяють нам елегантно обробляти помилки та неочікувані ситуації в нашому коді. Це об'єкти, які несуть інформацію про помилку або незвичайну ситуацію. + +У PHP є вбудований клас `Exception`, який служить основою для всіх винятків. Він має кілька методів, які дозволяють нам отримати більше інформації про виняток, як-от повідомлення про помилку, файл і рядок, де сталася помилка, тощо. + +Коли в коді виникає помилка, ми можемо "викинути" виняток за допомогою ключового слова `throw`. + +```php +function ділення(float $a, float $b): float +{ + if ($b === 0.0) { // Порівняння з float + throw new Exception('Ділення на нуль!'); + } + return $a / $b; +} +``` + +Коли функція `ділення()` отримує нуль як другий аргумент, вона викине виняток з повідомленням про помилку `'Ділення на нуль!'`. Щоб запобігти аварійному завершенню програми під час викидання винятку, ми перехоплюємо його в блоці `try/catch`: + +```php +try { + echo ділення(10, 0); +} catch (Exception $e) { + echo 'Виняток перехоплено: '. $e->getMessage(); +} +``` + +Код, який може викинути виняток, загортається в блок `try`. Якщо виняток викинуто, виконання коду переходить до блоку `catch`, де ми можемо обробити виняток (наприклад, вивести повідомлення про помилку). + +Після блоків `try` та `catch` ми можемо додати необов'язковий блок `finally`, який виконується завжди, незалежно від того, чи було викинуто виняток (навіть якщо в блоці `try` або `catch` ми використовуємо оператор `return`, `break` або `continue`): + +```php +try { + echo ділення(10, 0); +} catch (Exception $e) { + echo 'Виняток перехоплено: '. $e->getMessage(); +} finally { + // Код, який виконується завжди, незалежно від того, чи було викинуто виняток +} +``` + +Ми також можемо створити власні класи (ієрархію) винятків, які успадковують від класу Exception. Як приклад, уявімо собі простий банківський додаток, який дозволяє здійснювати поповнення та зняття коштів: + +```php +class БанківськийВиняток extends Exception {} +class ВинятокНедостатньоКоштів extends БанківськийВиняток {} +class ВинятокПеревищенняЛіміту extends БанківськийВиняток {} + +class БанківськийРахунок +{ + private int $баланс = 0; + private int $деннийЛіміт = 1000; + + public function поповнити(int $сума): int + { + $this->баланс += $сума; + return $this->баланс; + } + + public function зняти(int $сума): int + { + if ($сума > $this->баланс) { + throw new ВинятокНедостатньоКоштів('На рахунку недостатньо коштів.'); + } + + if ($сума > $this->деннийЛіміт) { + throw new ВинятокПеревищенняЛіміту('Перевищено денний ліміт зняття коштів.'); + } + + $this->баланс -= $сума; + return $this->баланс; + } +} +``` + +Для одного блоку `try` можна вказати кілька блоків `catch`, якщо ви очікуєте різні типи винятків. + +```php +$рахунок = new БанківськийРахунок; +$рахунок->поповнити(500); + +try { + $рахунок->зняти(1500); +} catch (ВинятокПеревищенняЛіміту $e) { + echo $e->getMessage(); +} catch (ВинятокНедостатньоКоштів $e) { + echo $e->getMessage(); +} catch (БанківськийВиняток $e) { + echo 'Під час виконання операції сталася помилка.'; +} +``` + +У цьому прикладі важливо звернути увагу на порядок блоків `catch`. Оскільки всі винятки успадковують від `БанківськийВиняток`, якби цей блок був першим, у ньому були б перехоплені всі винятки, і код не дійшов би до наступних блоків `catch`. Тому важливо розміщувати більш специфічні винятки (тобто ті, що успадковують від інших) у блоці `catch` вище за порядком, ніж їхні батьківські винятки. + + +Ітерація +-------- + +У PHP ви можете перебирати об'єкти за допомогою циклу `foreach`, подібно до того, як ви перебираєте масиви. Щоб це працювало, об'єкт повинен реалізувати спеціальний інтерфейс. + +Перший варіант - реалізувати інтерфейс `Iterator`, який має методи `current()`, що повертає поточне значення, `key()`, що повертає ключ, `next()`, що переходить до наступного значення, `rewind()`, що переходить на початок, та `valid()`, що перевіряє, чи ми ще не дійшли до кінця. + +Другий варіант - реалізувати інтерфейс `IteratorAggregate`, який має лише один метод `getIterator()`. Він або повертає об'єкт-заступник, який забезпечуватиме ітерацію, або може представляти генератор, що є спеціальною функцією, в якій використовується `yield` для послідовного повернення ключів та значень: + +```php +class Людина +{ + public function __construct( + public int $вік, + ) { + } +} + +class Список implements IteratorAggregate +{ + private array $люди = []; + + public function додатиЛюдину(Людина $людина): void + { + $this->люди[] = $людина; + } + + public function getIterator(): Generator + { + foreach ($this->люди as $людина) { + yield $людина; + } + } +} + +$список = new Список; +$список->додатиЛюдину(new Людина(30)); +$список->додатиЛюдину(new Людина(25)); + +foreach ($список as $людина) { + echo "Вік: {$людина->вік} років \n"; +} +``` + + +Найкращі практики +----------------- + +Коли ви засвоїли основні принципи об'єктно-орієнтованого програмування, важливо зосередитися на найкращих практиках в ООП. Вони допоможуть вам писати код, який є не тільки функціональним, але й читабельним, зрозумілим та легким у підтримці. + +1) **Розділення відповідальності (Separation of Concerns)**: Кожен клас повинен мати чітко визначену відповідальність і вирішувати лише одне основне завдання. Якщо клас робить занадто багато речей, може бути доцільно розділити його на менші, спеціалізовані класи. +2) **Інкапсуляція (Encapsulation)**: Дані та методи повинні бути якомога більше прихованими та доступними лише через визначений інтерфейс. Це дозволить вам змінювати внутрішню реалізацію класу, не впливаючи на решту коду. +3) **Впровадження залежностей (Dependency Injection)**: Замість того, щоб створювати залежності безпосередньо в класі, ви повинні "впроваджувати" їх ззовні. Для глибшого розуміння цього принципу рекомендуємо [розділи про Dependency Injection|dependency-injection:introduction]. diff --git a/nette/uk/troubleshooting.texy b/nette/uk/troubleshooting.texy index ed42e2e18c..f8ef361e4c 100644 --- a/nette/uk/troubleshooting.texy +++ b/nette/uk/troubleshooting.texy @@ -2,40 +2,69 @@ ***************** -Nette не працює, відображається біла сторінка .[#toc-nette-is-not-working-white-page-is-displayed] --------------------------------------------------------------------------------------------------- -- Спробуйте поставити `ini_set('display_errors', '1'); error_reporting(E_ALL);` після `declare(strict_types=1);` у файлі `index.php`, щоб змусити відображати помилки. -- Якщо ви як і раніше бачите білий екран, імовірно, в налаштуваннях сервера сталася помилка, і ви виявите причину в журналі сервера. Щоб переконатися в цьому, перевірте, чи працює PHP взагалі, спробувавши надрукувати що-небудь за допомогою команди `echo 'test';`. -- Якщо ви побачите помилку *Server Error: We're sorry! ...*, переходьте до наступного розділу: +Nette не працює, відображається біла сторінка +--------------------------------------------- +- Спробуйте додати у файл `index.php` одразу після `declare(strict_types=1);` рядок `ini_set('display_errors', '1'); error_reporting(E_ALL);`, це примусово увімкне відображення помилок. +- Якщо ви все ще бачите білий екран, ймовірно, проблема в налаштуваннях сервера, і причину можна знайти в логах сервера. Для впевненості перевірте, чи працює PHP взагалі, спробувавши щось вивести за допомогою `echo 'test';` +- Якщо ви бачите помилку *Server Error: We're sorry! …*, перейдіть до наступного розділу: -Помилка 500 *Ошибка сервера: We're sorry! ...*. .[#toc-error-500-server-error-we-re-sorry] ------------------------------------------------------------------------------------------- -Ця сторінка помилки відображається Nette у виробничому режимі. Якщо ви бачите її на машині розробника, [переключіться в режим розробника |application:bootstrap#Development-vs-Production-Mode]. +Помилка 500 *Server Error: We're sorry! …* +------------------------------------------ +Цю сторінку помилки Nette відображає в робочому режимі. Якщо вона з'являється на вашому комп'ютері розробника, [переключіться в режим розробки |application:bootstrapping#Режим розробки проти робочого режиму], і Tracy покаже детальне повідомлення. -Якщо повідомлення про помилку містить `Tracy is unable to log error`, з'ясуйте, чому помилки не можуть бути зареєстровані. Це можна зробити, наприклад, [переключившись |application:bootstrap#Development-vs-Production-Mode] у режим розробника і викликавши `Tracy\Debugger::log('hello');` після `$configurator->enableTracy(...)`. Трейсі розповість вам, чому він не може вести журнал. -Зазвичай причиною є [недостатні дозволи |#Setting-Directory-Permissions] для запису в каталог `log/`. +Причину помилки завжди можна знайти в логах у каталозі `log/`. Однак, якщо в повідомленні про помилку вказано `Tracy is unable to log error`, спочатку з'ясуйте, чому помилки не можна залогувати. Це можна зробити, наприклад, тимчасово [переключившись |application:bootstrapping#Режим розробки проти робочого режиму] у режим розробки і дозволивши Tracy залогувати будь-що після її запуску: -Якщо фраза `Tracy is unable to log error` відсутня в повідомленні про помилку (більше немає), ви можете дізнатися причину помилки в журналі в директорії `log/`. +```php +// Bootstrap.php +$configurator->setDebugMode('23.75.345.200'); // ваша IP-адреса +$configurator->enableTracy($rootDir . '/log'); +\Tracy\Debugger::log('hello'); +``` + +Tracy повідомить вам, чому вона не може логувати. Причиною можуть бути [недостатні права |#Налаштування прав доступу до каталогів] на запис у каталог `log/`. + +Однією з найпоширеніших причин помилки 500 є застарілий кеш. Хоча Nette в режимі розробки розумно автоматично оновлює кеш, у робочому режимі він зосереджується на максимізації продуктивності, і очищення кешу після кожної зміни коду - це ваша відповідальність. Спробуйте видалити `temp/cache`. + + +Помилка 404, не працює маршрутизація +------------------------------------ +Якщо всі сторінки (крім домашньої) повертають помилку 404, це схоже на проблему з конфігурацією сервера для [гарних URL |#Як налаштувати сервер для гарних URL]. + + +Зміни в шаблонах або конфігурації не відображаються +--------------------------------------------------- +"Я змінив шаблон або конфігурацію, але веб-сайт все ще показує стару версію." Така поведінка виникає в [робочому режимі |application:bootstrapping#Режим розробки проти робочого режиму], який з міркувань продуктивності не перевіряє зміни у файлах і зберігає один раз згенерований кеш. + +Щоб не доводилося вручну очищати кеш на робочому сервері після кожної зміни, увімкніть режим розробки для вашої IP-адреси у файлі `Bootstrap.php`: + +```php +$this->configurator->setDebugMode('ваша.ip.адреса'); +``` + + +Як вимкнути кеш під час розробки? +--------------------------------- +Nette розумний, і вам не потрібно вимикати кешування в ньому. Під час розробки він автоматично оновлює кеш при кожній зміні шаблону або конфігурації DI-контейнера. Режим розробки, крім того, вмикається автовизначенням, тому зазвичай не потрібно нічого налаштовувати, [або лише IP-адресу |application:bootstrapping#Режим розробки проти робочого режиму]. -Одна з найпоширеніших причин - застарілий кеш. У той час як Nette розумно автоматично оновлює кеш у режимі розробки, у виробничому режимі він зосереджений на максимізації продуктивності, і очищення кешу після кожної модифікації коду залежить від вас. Спробуйте видалити `temp/cache`. +Під час налагодження маршрутизатора рекомендуємо вимкнути кеш у браузері, де можуть зберігатися, наприклад, перенаправлення: відкрийте Developer Tools (Ctrl+Shift+I або Cmd+Option+I) і на панелі Network (Мережа) встановіть прапорець для вимкнення кешу. -Помилка `#[\ReturnTypeWillChange] attribute should be used` .[#toc-error-returntypewillchange-attribute-should-be-used] ------------------------------------------------------------------------------------------------------------------------ -Ця помилка виникає, якщо ви оновили PHP до версії 8.1, але використовуєте Nette, який не сумісний з нею. Тому рішенням є оновлення Nette до новішої версії за допомогою `composer update`. Nette підтримує PHP 8.1 з версії 3.0. Якщо ви використовуєте старішу версію (ви можете дізнатися це, подивившись у `composer.json`), [оновіть Nette |migrations:en] або залишайтеся з PHP 8.0. +Помилка `#[\ReturnTypeWillChange] attribute should be used` +----------------------------------------------------------- +Ця помилка з'являється, якщо ви оновили PHP до версії 8.1, але використовуєте Nette, яка не сумісна з нею. Рішенням є оновлення Nette до новішої версії за допомогою `composer update`. Nette підтримує PHP 8.1 з версії 3.0. Якщо ви використовуєте старішу версію (перевірте в `composer.json`), [оновіть Nette |migrations:en] або залишайтеся на PHP 8.0. -Встановлення прав доступу до каталогів .[#toc-setting-directory-permissions] ----------------------------------------------------------------------------- -Якщо ви розробляєте на macOS або Linux (або будь-якій іншій системі на базі Unix), вам необхідно налаштувати привілеї запису на веб-сервері. Припустимо, що ваш застосунок розташований у каталозі за замовчуванням `/var/www/html` (Fedora, CentOS, RHEL) +Налаштування прав доступу до каталогів +-------------------------------------- +Якщо ви розробляєте на macOS або Linux (або будь-якій іншій системі на базі Unix), вам потрібно буде налаштувати права запису для веб-сервера. Припустимо, ваш додаток знаходиться у стандартному каталозі `/var/www/html` (Fedora, CentOS, RHEL). ```shell cd /var/www/html/MY_PROJECT chmod -R a+rw temp log ``` -У деяких системах Linux (Fedora, CentOS, ...) SELinux може бути ввімкнено за замовчуванням. Можливо, вам знадобиться оновити політики SELinux або встановити шляхи до каталогів `temp` і `log` з правильним контекстом безпеки SELinux. Каталоги `temp` і `log` мають бути встановлені в контекст `httpd_sys_rw_content_t`; для іншої частини програми - в основному папки `app` - контексту `httpd_sys_content_t` буде достатньо. Запустіть на сервері перераховані команди від імені root: +На деяких дистрибутивах Linux (Fedora, CentOS, ...) SELinux увімкнено за замовчуванням. Вам потрібно буде відповідно налаштувати політики SELinux та встановити правильний контекст безпеки SELinux для папок `temp` та `log`. Для `temp` та `log` ми встановимо тип контексту `httpd_sys_rw_content_t`, для решти додатку (і особливо для папки `app`) буде достатньо `httpd_sys_content_t`. На сервері виконайте: ```shell semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/log(/.*)?' @@ -43,25 +72,30 @@ semanage fcontext -at httpd_sys_rw_content_t '/var/www/html/MY_PROJECT/temp(/.*) restorecon -Rv /var/www/html/MY_PROJECT/ ``` -Далі необхідно увімкнути булево SELinux `httpd_can_network_connect_db`, щоб дозволити Nette підключатися до бази даних по мережі. За замовчуванням його вимкнено. Для виконання цього завдання можна використовувати команду `setsebool`, і якщо вказано опцію `-P`, це налаштування зберігатиметься під час усіх перезавантажень. +Далі потрібно увімкнути логічний параметр SELinux `httpd_can_network_connect_db`, який за замовчуванням вимкнено і який дозволить Nette підключатися до бази даних через мережу. Для цього ми використаємо команду `setsebool` і за допомогою опції `-P` зробимо зміну постійною, тобто після перезавантаження сервера нас не чекатиме неприємний сюрприз: ```shell setsebool -P httpd_can_network_connect_db on ``` -Як змінити або видалити каталог `www` з URL? .[#toc-how-to-change-or-remove-www-directory-from-url] ---------------------------------------------------------------------------------------------------- -Директорія `www/`, використовувана в прикладах проектів у Nette, є так званою публічною директорією або коренем проекту. Це єдиний каталог, вміст якого доступний браузеру. У ньому знаходиться файл `index.php` - точка входу, з якої починається веб-додаток, написаний на Nette. +Як змінити або видалити каталог `www` з URL? +-------------------------------------------- +Каталог `www/`, що використовується в демонстраційних проектах Nette, є так званим публічним каталогом або document-root проекту. Це єдиний каталог, вміст якого доступний браузеру. Він містить файл `index.php`, вхідну точку, яка запускає веб-додаток, написаний на Nette. -Щоб запустити додаток на хостингу, необхідно в конфігурації хостингу встановити document-root у цю директорію. Або, якщо на хостингу є готова папка для публічного каталогу з іншим ім'ям (наприклад, `web`, `public_html` тощо), просто перейменуйте `www/`. +Для запуску програми на хостингу необхідно правильно налаштувати document-root. У вас є два варіанти: +1. У конфігурації хостингу встановити document-root на цей каталог. +2. Якщо хостинг має заздалегідь підготовлену папку (наприклад, `public_html`), перейменуйте `www/` на цю назву. -Рішення **не** полягає в тому, щоб "позбутися" папки `www/` за допомогою правил у файлі `.htaccess` або в роутері. Якщо хостинг не дозволяє вам встановити document-root у підкаталог (тобто створювати каталоги на один рівень вище публічного каталогу), пошукайте інший. В іншому разі ви істотно ризикуєте безпекою. Це схоже на життя у квартирі, де ви не можете закрити вхідні двері, і вони завжди відчинені. +.[warning] +Ніколи не намагайтеся вирішити питання безпеки лише за допомогою `.htaccess` або маршрутизатора, які б забороняли доступ до інших папок. +Якщо хостинг не дозволяє встановити document-root у підкаталог (тобто створювати каталоги на рівень вище над публічним каталогом), пошукайте інший. Інакше ви наражаєте себе на значний ризик безпеки. Це було б як жити в квартирі, де вхідні двері не зачиняються і завжди відчинені. -Як налаштувати сервер для красивих URL? .[#toc-how-to-configure-a-server-for-nice-urls] ---------------------------------------------------------------------------------------- -**Apache**: Розширення mod_rewrite має бути дозволено та налаштовано у файлі `.htaccess`. + +Як налаштувати сервер для гарних URL? +------------------------------------- +**Apache**: необхідно увімкнути та налаштувати правила mod_rewrite у файлі `.htaccess`: ```apacheconf RewriteEngine On @@ -70,67 +104,93 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -Щоб змінити конфігурацію Apache за допомогою файлів .htaccess, необхідно включити директиву AllowOverride. Це поведінка за замовчуванням для Apache. +Якщо виникають проблеми, переконайтеся, що: +- файл `.htaccess` знаходиться в каталозі document-root (тобто поруч із файлом `index.php`) +- [Apache обробляє файли `.htaccess` |#Перевірка роботи .htaccess] +- [увімкнено mod_rewrite |#Перевірка чи увімкнено mod rewrite] + +Якщо ви налаштовуєте додаток у підкаталозі, можливо, вам доведеться розкоментувати рядок для налаштування `RewriteBase` та встановити його на правильну папку. -**nginx**: директива `try_files` повинна використовуватися в конфігурації сервера: +**nginx**: необхідно налаштувати перенаправлення за допомогою директиви `try_files` всередині блоку `location /` у конфігурації сервера. ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args важно + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args Є ВАЖЛИВИМ! } ``` -Блок `location` повинен бути визначений рівно один раз для кожного шляху до файлової системи в блоці `server`. Якщо у вашій конфігурації вже є блок `location /`, додайте директиву `try_files` в існуючий блок. +Блок `location` для кожного шляху файлової системи може зустрічатися в блоці `server` лише один раз. Якщо у вас вже є `location /` у конфігурації, додайте директиву `try_files` до нього. + + +Перевірка роботи `.htaccess` +---------------------------- +Найпростіший спосіб перевірити, чи Apache використовує або ігнорує ваш файл `.htaccess`, - це навмисно його пошкодити. Вставте на початок файлу рядок `Test` і тепер, якщо ви оновите сторінку в браузері, ви повинні побачити *Internal Server Error*. + +Якщо ви бачите цю помилку, це насправді добре! Це означає, що Apache аналізує файл `.htaccess` і натрапляє на помилку, яку ми туди вставили. Видаліть рядок `Test`. + +Якщо *Internal Server Error* не відображається, ваше налаштування Apache ігнорує файл `.htaccess`. Зазвичай Apache ігнорує його через відсутність конфігураційної директиви `AllowOverride All`. + +Якщо ви розміщуєте його самостійно, це легко виправити. Відкрийте файл `httpd.conf` або `apache.conf` у текстовому редакторі, знайдіть відповідну секцію `<Directory>` та додайте/змініть цю директиву: + +```apacheconf +<Directory "/var/www/htdocs"> # шлях до вашого document root + AllowOverride All + ... +``` + +Якщо ваш веб-сайт розміщено деінде, перевірте панель керування, чи можете ви там увімкнути файл `.htaccess`. Якщо ні, зверніться до свого хостинг-провайдера, щоб він зробив це за вас. + + +Перевірка, чи увімкнено `mod_rewrite` +------------------------------------- +Якщо ви переконалися, що [`.htaccess` працює |#Перевірка роботи .htaccess], ви можете перевірити, чи увімкнено розширення mod_rewrite. Вставте на початок файлу `.htaccess` рядок `RewriteEngine On` і оновіть сторінку в браузері. Якщо відображається *Internal Server Error*, це означає, що mod_rewrite не увімкнено. Існує кілька способів його увімкнення. Різні способи, як це можна зробити в різних налаштуваннях, можна знайти на Stack Overflow. -Посилання генеруються без `https:`. .[#toc-links-are-generated-without-https] ------------------------------------------------------------------------------ -Nette генерує посилання з тим самим протоколом, який використовує поточна сторінка. Таким чином, на сторінці `https://foo` і навпаки. -Якщо ви перебуваєте за HTTPS-стримінговим зворотним проксі (наприклад, у Docker), то вам потрібно [set up a proxy |http:configuration#HTTP-Proxy] в конфігурації, щоб визначення протоколу працювало правильно. +Посилання генеруються без `https:` +---------------------------------- +Nette генерує посилання з тим самим протоколом, який має сама сторінка. Тобто на сторінці `https://foo` генеруються посилання, що починаються з `https:`, і навпаки. Якщо ви перебуваєте за зворотним проксі-сервером, який видаляє HTTPS (наприклад, у Docker), то потрібно в конфігурації [налаштувати проксі |http:configuration#HTTP-проксі], щоб виявлення протоколу працювало правильно. -Якщо ви використовуєте Nginx як проксі, вам потрібно налаштувати перенаправлення наступним чином: +Якщо ви використовуєте Nginx як проксі, необхідно налаштувати перенаправлення, наприклад, так: ``` location / { proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-Port $server_port; - proxy_pass http://IP-aplikace:80; # IP или имя хоста сервера/контейнера, на котором запущено приложение. + proxy_set_header X-Forwarded-Port $server_port; + proxy_pass http://IP-додатку:80; # IP або ім'я хоста сервера/контейнера, де працює додаток } ``` -Далі необхідно вказати IP проксі і, якщо може бути застосовано, IP діапазон вашої локальної мережі, де ви запускаєте інфраструктуру: +Далі потрібно вказати в конфігурації IP-адресу проксі та, можливо, діапазон IP-адрес вашої локальної мережі, де ви керуєте інфраструктурою: ```neon http: - proxy: IP-proxy/IP-range + proxy: IP-проксі/IP-діапазон ``` -Використання символів { } у JavaScript .[#toc-use-of-characters-in-javascript] ------------------------------------------------------------------------------- -Символи `{` и `}` використовуються для запису тегів Latte. Усе (крім пробілу та лапок), що слідує за символом `{`, считается тегом. Если вам нужно вывести символ `{` (часто зустрічається в JavaScript), ви можете поставити пробіл (або інший порожній символ) одразу після `{`. Таким чином ви уникнете інтерпретації його як мітки. +Використання символів { } у JavaScript +-------------------------------------- +Символи `{` та `}` використовуються для запису тегів Latte. Тегом вважається все, що йде за символом `{`, за винятком пробілу та лапок. Тому, якщо вам потрібно вивести безпосередньо символ `{` (часто, наприклад, у JavaScript), ви можете поставити пробіл (або інший порожній символ) після символу `{`. Це дозволить уникнути його інтерпретації як тегу. -Якщо необхідно вивести ці символи в ситуації, коли вони будуть інтерпретовані як тег, ви можете використовувати спеціальні теги для виведення цих символів - `{l}` для `{` и `{r}` для `}`. +Якщо необхідно вивести ці символи в ситуації, коли текст міг би бути сприйнятий як тег, ви можете використовувати спеціальні теги для виведення цих символів - `{l}` для `{` та `{r}` для `}`. ``` -{is tag} -{ is not tag } -{l}is not tag{r} +{є тегом} +{ не є тегом } +{l}не є тегом{r} ``` -Повідомлення `Presenter::getContext() is deprecated` .[#toc-notice-presenter-getcontext-is-deprecated] ------------------------------------------------------------------------------------------------------- +Повідомлення `Presenter::getContext() is deprecated` +---------------------------------------------------- -Nette, безумовно, перший PHP-фреймворк, який перейшов на ін'єкцію залежностей і змусив програмістів послідовно використовувати її, починаючи з провідних. Якщо ведучому потрібна залежність, [він попросить її |dependency-injection:passing-dependencies]. -На відміну від цього, спосіб, за якого ми передаємо весь DI-контейнер класу, а він безпосередньо витягує з нього залежності, вважається антипаттерном (він називається service locator). -Цей спосіб використовувався в Nette 0.x до появи ін'єкції залежностей, і його реліктом є метод `Presenter::getContext()`, давно позначений як deprecated. +Nette є першим PHP-фреймворком, який перейшов на dependency injection і спонукав програмістів до його послідовного використання, починаючи з самих presenter'ів. Якщо presenter'у потрібна якась залежність, він [заявляє про неї|dependency-injection:passing-dependencies]. Навпаки, підхід, коли в клас передається весь DI-контейнер, а клас сам витягує з нього залежності, вважається антипатерном (називається service locator). Цей спосіб використовувався в Nette 0.x ще до появи dependency injection, і його залишком є метод `Presenter::getContext()`, давно позначений як застарілий (deprecated). -Якщо ви портуєте дуже старий додаток Nette, ви можете виявити, що він все ще використовує цей метод. Таким чином, починаючи з версії 3.1 `nette/application` ви зіткнетеся з попередженням `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection`, починаючи з версії 4.0 ви зіткнетеся з помилкою, що метод не існує. +Якщо ви портуєте дуже стару програму для Nette, ви можете зіткнутися з тим, що вона все ще використовує цей метод. Починаючи з `nette/application` версії 3.1, ви зіткнетеся з попередженням `Nette\Application\UI\Presenter::getContext() is deprecated, use dependency injection`, а з версії 4.0 - з помилкою, що метод не існує. -Зрозуміло, чистим рішенням є перепроектування програми для передачі залежностей за допомогою ін'єкції залежностей. Як обхідний шлях ви можете додати свій власний метод `getContext()` до базового презентера та обійти повідомлення: +Чистим рішенням, звичайно, є переробка програми таким чином, щоб залежності передавалися за допомогою dependency injection. Як обхідний шлях, ви можете додати власний метод `getContext()` до свого базового presenter'а і таким чином обійти повідомлення: ```php abstract BasePresenter extends Nette\Application\UI\Presenter diff --git a/nette/uk/vulnerability-protection.texy b/nette/uk/vulnerability-protection.texy index 530c962e4a..53442a433a 100644 --- a/nette/uk/vulnerability-protection.texy +++ b/nette/uk/vulnerability-protection.texy @@ -1,22 +1,22 @@ -Захист від уразливостей +Захист від вразливостей *********************** .[perex] -Час від часу оголошується про серйозні недоліки в системі безпеки або навіть зловживання. Звичайно, це трохи неприємно. Якщо ви дбаєте про безпеку своїх веб-додатків, фреймворк Nette - це, безумовно, найкращий вибір для вас. +Час від часу повідомляється про безпекову діру на черговому великому веб-сайті або про її використання. Це неприємно. Якщо вам важлива безпека ваших веб-додатків, Nette Framework, безумовно, є найкращим вибором. -Міжсайтовий скриптинг (XSS) .[#toc-cross-site-scripting-xss] -============================================================ +Cross-Site Scripting (XSS) +========================== -Міжсайтовий скриптинг - це метод порушення роботи сайту з використанням неекранованого введення. Зловмисник може впровадити свій власний код HTML або JavaScript і змінити зовнішній вигляд сторінки або навіть зібрати конфіденційну інформацію про користувачів. Захист від XSS простий: послідовне і правильне екранування всіх рядків і даних, що вводяться. Традиційно було достатньо, якщо ваш кодер припустився всього однієї найменшої помилки, забув один раз, і весь сайт може опинитися під загрозою. +Cross-Site Scripting - це метод порушення веб-сайтів, що використовує необроблені вихідні дані. Зловмисник може вставити свій власний код на сторінку, тим самим змінивши її або навіть отримавши конфіденційні дані відвідувачів. Захиститися від XSS можна лише послідовною та коректною обробкою всіх рядків. При цьому достатньо, щоб ваш кодер лише один раз забув про це, і весь веб-сайт може бути миттєво скомпрометований. -Прикладом такої ін'єкції може бути передача користувачеві зміненої URL-адреси, яка вставляє "шкідливий" сценарій у файл. Якщо додаток не екранує свої вхідні дані належним чином, такий запит може призвести до виконання сценарію на стороні клієнта. Це може, наприклад, призвести до крадіжки особистих даних. +Прикладом атаки може бути підстановка зміненої URL користувачу, за допомогою якої ми впроваджуємо свій код на сторінку. Якщо додаток не буде належним чином обробляти вихідні дані, він виконає скрипт у браузері користувача. Таким чином, ми можемо, наприклад, викрасти його ідентичність. ``` -http://example.com/?search=<script>alert('XSS attack.');</script> +https://example.com/?search=<script>alert('Успішна XSS атака.');</script> ``` -Фреймворк Nette пропонує абсолютно нову технологію [Context-Aware Escaping |latte:safety-first#Context-Aware-Escaping], яка назавжди позбавить вас від ризиків міжсайтового скриптингу. Він автоматично екранує всі дані, що вводяться, залежно від заданого контексту, тому кодер не зможе випадково щось забути. Як приклад розглянемо такий шаблон: +Nette Framework пропонує революційну технологію [Context-Aware Escaping |latte:safety-first#Контекстно-залежне екранування], яка назавжди позбавить вас ризику Cross-Site Scriptingu. Вона автоматично обробляє всі вихідні дані, тому кодер не може щось забути. Приклад? Кодер створює цей шаблон: ```latte <p onclick="alert({$message})">{$message}</p> @@ -26,29 +26,29 @@ document.title = {$message}; </script> ``` -Команда `{$message}` виводить змінну. Інші фреймворки часто змушують розробників явно декларувати екранування і навіть тип екранування залежно від контексту. Однак у Nette вам не потрібно нічого декларувати. Все відбувається автоматично, послідовно і точно. Якщо ми встановимо змінну в `$message = 'Width 1/2"'`, фреймворк згенерує такий HTML-код: +Запис `{$message}` означає виведення змінної. В інших фреймворках необхідно явно обробляти кожне виведення, і навіть у кожному місці по-різному. У Nette Framework не потрібно нічого обробляти, все робиться автоматично, правильно та послідовно. Якщо ми присвоїмо змінній `$message = 'Ширина 1/2"'`, фреймворк згенерує HTML-код: ```latte -<p onclick="alert("Width 1\/2\"")">Width 1/2"</p> +<p onclick="alert("Ширина 1\/2\"")">Ширина 1/2"</p> <script> -document.title = "Width 1\/2\""; +document.title = "Ширина 1\/2\""; </script> ``` -Підробка міжсайтових запитів (CSRF) .[#toc-cross-site-request-forgery-csrf] -=========================================================================== +Cross-Site Request Forgery (CSRF) +================================= -Атака Cross-Site Request Forgery полягає в тому, що зловмисник заманює жертву відвідати сторінку, яка мовчки виконує запит у браузері жертви до сервера, на якому жертва на даний момент зареєстрована, і сервер вважає, що запит був зроблений жертвою за власним бажанням. Сервер виконує певну дію під особистістю жертви, але без її відома. Це може бути зміна або видалення даних, надсилання повідомлення тощо. +Атака Cross-Site Request Forgery полягає в тому, що зловмисник заманює жертву на сторінку, яка непомітно виконує запит у браузері жертви до сервера, на якому жертва авторизована, і сервер вважає, що запит був виконаний жертвою за власним бажанням. Таким чином, під ідентичністю жертви виконується певна дія, про яку вона не знає. Це може бути зміна або видалення даних, надсилання повідомлення тощо. -Nette **автоматично захищає форми та сигнали в презентерах** від цього типу атак. Це робиться шляхом запобігання їх надсилання або виклику з іншого домену. Щоб вимкнути захист, використовуйте для форм наступне: +Nette Framework **автоматично захищає форми та сигнали в presenter'ах** від цього типу атак. Це робиться шляхом запобігання їх надсиланню або виклику з іншого домену. Якщо ви хочете вимкнути захист, використовуйте для форм: ```php $form->allowCrossOrigin(); ``` -або, у разі сигналу, додайте анотацію `@crossOrigin`: +або у випадку сигналу додайте анотацію `@crossOrigin`: ```php /** @@ -59,42 +59,41 @@ public function handleXyz() } ``` -У PHP 8 ви також можете використовувати атрибути: +У Nette Application 3.2 ви також можете використовувати атрибути: ```php -use Nette\Application\Attributes\CrossOrigin; +use Nette\Application\Attributes\Requires; -#[CrossOrigin] +#[Requires(sameOrigin: false)] public function handleXyz() { } ``` -Атака на URL, керуючі коди, неправильний UTF-8 .[#toc-url-attack-control-codes-invalid-utf-8] -============================================================================================= +URL attack, керуючі символи, недійсний UTF-8 +============================================ -Різні терміни, пов'язані зі спробою зловмисника зробити ваш додаток "шкідливим". Результати можуть сильно відрізнятися, від неповного виведення XML (тобто збій у роботі RSS-потоку) до отримання конфіденційної інформації з бази даних і отримання паролів користувачів. Захистом від цих атак є послідовна перевірка UTF-8 на рівні байтів. І, відверто кажучи, ви б не стали робити цього без рамок, вірно? +Різні терміни, пов'язані зі спробою зловмисника підсунути вашому веб-додатку *шкідливі* вхідні дані. Наслідки можуть бути дуже різноманітними, від пошкодження XML-виводів (наприклад, непрацюючі RSS-канали) до отримання конфіденційної інформації з бази даних або паролів. Захистом є послідовна обробка всіх вхідних даних на рівні окремих байтів. І, чесно кажучи, хто з вас це робить? -Фреймворк Nette робить це за вас автоматично. Вам не потрібно нічого налаштовувати, і ваш додаток буде в безпеці. +Nette Framework робить це за вас, і до того ж автоматично. Вам не потрібно нічого налаштовувати, і всі вхідні дані будуть оброблені. -Перехоплення сеансу, крадіжка сеансу, фіксація сеансу .[#toc-session-hijacking-session-stealing-session-fixation] -================================================================================================================= +Session hijacking, session stealing, session fixation +===================================================== -Управління сеансами охоплює кілька типів атак. Зловмисник може вкрасти ідентифікатор сесії жертви або підробити його і таким чином отримати доступ до веб-додатку без фактичного пароля. Тоді зловмисник може робити все, що міг користувач, без будь-яких слідів. Захист полягає в правильному налаштуванні як PHP, так і самого веб-сервера. +З керуванням сесіями пов'язано кілька типів атак. Зловмисник або викрадає, або підсовує користувачеві свій ідентифікатор сесії, і завдяки цьому отримує доступ до веб-додатку, не знаючи пароля користувача. Потім він може робити в додатку будь-що, не знаючи про це користувача. Захист полягає в правильній конфігурації сервера та PHP. -Nette налаштовує PHP автоматично. Таким чином, розробникам не потрібно турбуватися про те, як зробити сесію достатньо захищеною, і вони можуть повністю зосередитися на ключових частинах програми. Для цього необхідно увімкнути функцію `ini_set()`. +При цьому Nette Framework налаштовує PHP автоматично. Програміст таким чином не повинен думати, як правильно захистити сесію, і може повністю зосередитися на створенні додатку. Однак це вимагає увімкненої функції `ini_set()`. -Куки SameSite .[#toc-samesite-cookie] -===================================== +SameSite cookie +=============== -Куки SameSite забезпечують механізм розпізнавання того, що призвело до завантаження сторінки. Що абсолютно необхідно з міркувань безпеки. +SameSite cookies надають механізм для розпізнавання того, що призвело до завантаження сторінки. Що є абсолютно критичним для безпеки. -Прапор SameSite може мати три значення: `Lax`, `Strict` і `None` (потрібен HTTPS). Якщо запит на сторінку надходить безпосередньо з самого сайту або користувач відкриває сторінку, безпосередньо ввівши її в адресний рядок або натиснувши на закладку, браузер надсилає всі куки на сервер (тобто з прапорами `Lax`, `Strict` і `None`). Якщо користувач потрапляє на сайт за посиланням з іншого сайту, на сервер передаються файли кукі з прапорами `Lax` і `None`. Якщо запит виникає в іншому місці, наприклад, надсилання POST-форми з іншого джерела, завантаження всередині iframe, використання JavaScript тощо, буде надіслано тільки кукі з прапором `None`. - -За замовчуванням Nette надсилає всі куки з прапором `Lax`. +Прапорець SameSite може мати три значення: `Lax`, `Strict` та `None` (останній вимагає HTTPS). Якщо запит на сторінку надходить безпосередньо з веб-сайту, або користувач відкриває сторінку, вводячи її безпосередньо в адресний рядок або клацаючи на закладку, браузер надсилає серверу всі файли cookie (тобто з прапорцями `Lax`, `Strict` та `None`). Якщо користувач переходить на веб-сайт за посиланням з іншого веб-сайту, серверу передаються файли cookie з прапорцями `Lax` та `None`. Якщо запит виникає іншим чином, наприклад, надсиланням POST-форми з іншого веб-сайту, завантаженням усередині iframe, за допомогою JavaScript тощо, надсилаються лише файли cookie з прапорцем `None`. +Nette за замовчуванням надсилає всі файли cookie з прапорцем `Lax`. {{leftbar: www:@menu-common}} diff --git a/php-generator/bg/@home.texy b/php-generator/bg/@home.texy index 93e00a44a3..425f7fe575 100644 --- a/php-generator/bg/@home.texy +++ b/php-generator/bg/@home.texy @@ -1,31 +1,32 @@ -Генератор на PHP код -******************** - -<div class=perex> -- Трябва да генерирате PHP код за класове, функции, PHP файлове и др. -- Поддържа всички най-нови функции на PHP като енуми, атрибути и др. -- Позволява ви лесно да променяте съществуващи класове -- Изход, съвместим с PSR-12 -- Високо развита, стабилна и широко използвана библиотека +Nette PhpGenerator +****************** + +<div class="perex"> +Търсите инструмент за генериране на PHP код за класове, функции или цели файлове? + +- Поддържа всички най-нови функции в PHP (като property hooks, enums, атрибути и т.н.) +- Позволява ви лесно да модифицирате съществуващи класове +- Изходният код е в съответствие с PSR-12 / PER coding style +- Зряла, стабилна и широко използвана библиотека </div> -Инсталация .[#toc-installation] -------------------------------- +Инсталация +---------- -Изтеглете и инсталирайте пакета с помощта на [Composer |best-practices:composer]: +Можете да изтеглите и инсталирате библиотеката с помощта на инструмента [Composer |best-practices:composer]: ```shell composer require nette/php-generator ``` -За съвместимост с PHP вижте [таблицата |#Compatibility Table]. +Можете да намерите съвместимостта с PHP в [таблицата |#Таблица за съвместимост]. -Класове .[#toc-classes] ------------------------ +Класове +------- -Нека да започнем с прост пример за генериране на клас с помощта на [ClassType |api:Nette\PhpGenerator\ClassType]: +Нека започнем направо с пример за създаване на клас с помощта на [ClassType |api:Nette\PhpGenerator\ClassType]: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -34,19 +35,19 @@ $class ->setFinal() ->setExtends(ParentClass::class) ->addImplement(Countable::class) - ->addComment("Description of class.\nSecond line\n") + ->addComment("Описание на класа.\nВтори ред\n") ->addComment('@property-read Nette\Forms\Form $form'); -// за генериране на PHP код просто го превърнете в низ или използвайте echo: +// можете лесно да генерирате кода чрез преобразуване в низ или с помощта на echo: echo $class; ``` -Той ще даде този резултат: +Връща следния резултат: ```php /** - * Description of class. - * Second line + * Описание на класа + * Втори ред * * @property-read Nette\Forms\Form $form */ @@ -55,18 +56,18 @@ final class Demo extends ParentClass implements Countable } ``` -Можем също така да използваме принтер за генериране на кода, който, за разлика от `echo $class`, ще можем да [конфигурираме допълнително |#Printers and PSR compliance]: +За генериране на кода можем да използваме и т.нар. printer, който за разлика от `echo $class` ще можем да [конфигурирате допълнително |#Printer и съответствие с PSR]: ```php $printer = new Nette\PhpGenerator\Printer; echo $printer->printClass($class); ``` -Можем да добавяме константи (клас [Constant |api:Nette\PhpGenerator\Constant]) и свойства (клас [Property |api:Nette\PhpGenerator\Property]): +Можем да добавим константи (клас [Constant |api:Nette\PhpGenerator\Constant]) и променливи (клас [Property |api:Nette\PhpGenerator\Property]): ```php $class->addConstant('ID', 123) - ->setProtected() // постоянна видимост + ->setProtected() // видимост на константите ->setType('int') ->setFinal(); @@ -77,10 +78,10 @@ $class->addProperty('items', [1, 2, 3]) $class->addProperty('list') ->setType('?array') - ->setInitialized(); // отпечатва '= null' + ->setInitialized(); // извежда '= null' ``` -Той генерира: +Генерира: ```php final protected const int ID = 123; @@ -91,14 +92,14 @@ private static $items = [1, 2, 3]; public ?array $list = null; ``` -Можем да добавяме и [методи |#Method and Function Signature]: +И можем да добавим [методи |#Сигнатури на методи и функции]: ```php $method = $class->addMethod('count') - ->addComment('Count it.') + ->addComment('Пребройте го.') ->setFinal() ->setProtected() - ->setReturnType('?int') // тип връщане на метода + ->setReturnType('?int') // типове на връщане за методи ->setBody('return count($items ?: $this->items);'); $method->addParameter('items', []) // $items = [] @@ -106,11 +107,11 @@ $method->addParameter('items', []) // $items = [] ->setType('array'); // array &$items = [] ``` -Това води до: +Резултатът е: ```php /** - * Count it. + * Пребройте го. */ final protected function count(array &$items = []): ?int { @@ -118,7 +119,7 @@ final protected function count(array &$items = []): ?int } ``` -Промотираните параметри, въведени от PHP 8.0, могат да се предават на конструктора: +Промотирани параметри, въведени в PHP 8.0, могат да бъдат предадени на конструктора: ```php $method = $class->addMethod('__construct'); @@ -127,7 +128,7 @@ $method->addPromotedParameter('args', []) ->setPrivate(); ``` -Това води до: +Резултатът е: ```php public function __construct( @@ -137,15 +138,15 @@ public function __construct( } ``` -Свойствата и класовете само за четене могат да бъдат маркирани чрез `setReadOnly()`. +Свойства и класове само за четене могат да бъдат маркирани с помощта на функцията `setReadOnly()`. ------ -Ако добавеното свойство, константа, метод или параметър вече съществуват, се изхвърля изключение. +Ако добавеното свойство, константа, метод или параметър вече съществуват, се хвърля изключение. -Членовете могат да бъдат премахнати с помощта на `removeProperty()`, `removeConstant()`, `removeMethod()` или `removeParameter()`. +Членовете на класа могат да бъдат премахнати с помощта на `removeProperty()`, `removeConstant()`, `removeMethod()` или `removeParameter()`. -Можете също така да добавяте съществуващи обекти `Method`, `Property` или `Constant` към класа: +Можете също така да добавите съществуващи обекти `Method`, `Property` или `Constant` към класа: ```php $method = new Nette\PhpGenerator\Method('getHandle'); @@ -158,7 +159,7 @@ $class = (new Nette\PhpGenerator\ClassType('Demo')) ->addMember($const); ``` -Можете да клонирате съществуващи методи, свойства и константи с различно име, като използвате `cloneWithName()`: +Можете също така да клонирате съществуващи методи, свойства и константи под друго име с помощта на `cloneWithName()`: ```php $methodCount = $class->getMethod('count'); @@ -167,17 +168,17 @@ $class->addMember($methodRecount); ``` -Интерфейс или черта .[#toc-interface-or-trait] ----------------------------------------------- +Интерфейс или Trait +------------------- -Можете да създавате интерфейси и черти (класове [InterfaceType |api:Nette\PhpGenerator\InterfaceType] и [TraitType |api:Nette\PhpGenerator\TraitType]): +Можете да създавате интерфейси и trait-ове (класове [InterfaceType |api:Nette\PhpGenerator\InterfaceType] и [TraitType |api:Nette\PhpGenerator\TraitType]): ```php $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait'); ``` -Използване на черти: +Използване на trait-ове: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -202,10 +203,10 @@ class Demo ``` -Енуми .[#toc-enums] -------------------- +Enums +----- -Можете лесно да създадете енумите, които PHP 8.1 въвежда (клас [EnumType |api:Nette\PhpGenerator\EnumType]): +Можете лесно да създадете enum-и, въведени в PHP 8.1, по следния начин: (клас [EnumType |api:Nette\PhpGenerator\EnumType]): ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -229,20 +230,20 @@ enum Suit } ``` -Можете също така да дефинирате скаларни еквиваленти за случаите, за да създадете подкрепен енум: +Можете също така да дефинирате скаларни еквиваленти и да създадете "backed" enum: ```php $enum->addCase('Clubs', '♣'); $enum->addCase('Diamonds', '♦'); ``` -Възможно е да добавите коментар или [атрибути |#attributes] към всеки случай, като използвате `addComment()` или `addAttribute()`. +Към всеки *case* е възможно да се добави коментар или [#атрибути] с помощта на `addComment()` или `addAttribute()`. -Анонимен клас .[#toc-anonymous-class] -------------------------------------- +Анонимни класове +---------------- -Дайте името `null` и ще имате анонимен клас: +Предаваме `null` като име и имаме анонимен клас: ```php $class = new Nette\PhpGenerator\ClassType(null); @@ -264,10 +265,10 @@ $obj = new class ($val) { ``` -Глобална функция .[#toc-global-function] ----------------------------------------- +Глобални функции +---------------- -Кодът на функциите ще генерира клас [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction]: +Кодът на функциите се генерира от класа [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction]: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -276,7 +277,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// или използвайте PsrPrinter за изход, съответстващ на PSR-2 / PSR-12 +// или използвайте PsrPrinter за изход в съответствие с PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -290,10 +291,10 @@ function foo($a, $b) ``` -Затваряне .[#toc-closure] -------------------------- +Анонимни функции +---------------- -Кодът на затворите ще генерира клас [Closure |api:Nette\PhpGenerator\Closure]: +Кодът на анонимните функции се генерира от класа [Closure |api:Nette\PhpGenerator\Closure]: ```php $closure = new Nette\PhpGenerator\Closure; @@ -304,7 +305,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// или използвайте PsrPrinter за изход, съответстващ на PSR-2 / PSR-12 +// или използвайте PsrPrinter за изход в съответствие с PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -317,10 +318,10 @@ function ($a, $b) use (&$c) { ``` -Функция стрелка .[#toc-arrow-function] --------------------------------------- +Съкратени arrow функции +----------------------- -Можете също така да отпечатате затварянето като функция стрелка, като използвате принтер: +Можете също така да изведете съкратена анонимна функция с помощта на printer: ```php $closure = new Nette\PhpGenerator\Closure; @@ -338,34 +339,34 @@ fn($a, $b) => $a + $b ``` -Подпис на метод и функция .[#toc-method-and-function-signature] ---------------------------------------------------------------- +Сигнатури на методи и функции +----------------------------- -Методите се представят чрез класа [Method |api:Nette\PhpGenerator\Method]. Можете да задавате видимост, връщана стойност, да добавяте коментари, [атрибути |#Attributes] и т.н: +Методите се представят от класа [Method |api:Nette\PhpGenerator\Method]. Можете да настроите видимост, върната стойност, да добавите коментари, [#Атрибути] и т.н.: ```php $method = $class->addMethod('count') - ->addComment('Count it.') + ->addComment('Пребройте го.') ->setFinal() ->setProtected() ->setReturnType('?int'); ``` -Всеки параметър се представя от клас [Parameter |api:Nette\PhpGenerator\Parameter]. Отново можете да задавате всички възможни свойства: +Отделните параметри се представят от класа [Parameter |api:Nette\PhpGenerator\Parameter]. Отново можете да настроите всички възможни свойства: ```php $method->addParameter('items', []) // $items = [] - ->setReference() // &$items = [] - ->setType('array'); // array &$items = [] + ->setReference() // &$items = [] + ->setType('array'); // array &$items = [] // function count(&$items = []) ``` -За да дефинирате т.нар. вариационни параметри (а също и оператора splat, spread, ellipsis, unpacking или three dots), използвайте `setVariadics()`: +За дефиниране на т.нар. variadics параметри (или също splat оператор) служи `setVariadic()`: ```php $method = $class->addMethod('count'); -$method->setVariadics(true); +$method->setVariadic(true); $method->addParameter('items'); ``` @@ -378,10 +379,10 @@ function count(...$items) ``` -Метод и тяло на функцията .[#toc-method-and-function-body] ----------------------------------------------------------- +Тела на методи и функции +------------------------ -Тялото може да се предаде на метода `setBody()` наведнъж или последователно (ред по ред) чрез многократно извикване на `addBody()`: +Тялото може да бъде предадено наведнъж на метода `setBody()` или постепенно (ред по ред) чрез повтарящо се извикване на `addBody()`: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -400,9 +401,9 @@ function foo() } ``` -Можете да използвате специални заместители за удобен начин за инжектиране на променливи. +Можете да използвате специални placeholders за лесно вмъкване на променливи. -Обикновени заместители `?` +Прости placeholders `?` ```php $str = 'any string'; @@ -412,7 +413,7 @@ $function->addBody('return substr(?, ?);', [$str, $num]); echo $function; ``` -Резултат: +Резултат ```php function foo() @@ -421,7 +422,7 @@ function foo() } ``` -Variadic placeholder `...?` +Placeholder за variadic `...?` ```php $items = [1, 2, 3]; @@ -439,7 +440,7 @@ function foo() } ``` -Можете също така да използвате именувани параметри в PHP 8, като използвате заместител `...?:` +Можете също така да използвате именувани параметри за PHP 8 с помощта на `...?:` ```php $items = ['foo' => 1, 'bar' => true]; @@ -448,7 +449,7 @@ $function->setBody('myfunc(...?:);', [$items]); // myfunc(foo: 1, bar: true); ``` -Избягване на заместителя чрез наклонена черта `\?` +Placeholder се екранира с помощта на наклонена черта `\?` ```php $num = 3; @@ -468,56 +469,81 @@ function foo($a) ``` -Принтери и съответствие с PSR .[#toc-printers-and-psr-compliance] ------------------------------------------------------------------ +Printer и съответствие с PSR +---------------------------- -PHP кодът се генерира от обекти `Printer`. Съществува `PsrPrinter`, чийто изход съответства на PSR-2 и PSR-12 и използва интервали за отстъп, и `Printer`, който използва табулатори за отстъп. +За генериране на PHP код служи класът [Printer |api:Nette\PhpGenerator\Printer]: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // същото като: echo $class +``` + +Той може да генерира код за всички други елементи, предлага методи като `printFunction()`, `printNamespace()` и т.н. + +На разположение е и класът `PsrPrinter`, чийто изход е в съответствие с PSR-2 / PSR-12 / PER coding style: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // Отстъпление от 4 интервала +echo $printer->printClass($class); ``` -Трябва да персонализирате поведението на принтера? Създайте свой собствен, като наследите класа `Printer`. Можете да преконфигурирате тези променливи: +Нуждаете се от персонализиране на поведението? Създайте собствена версия чрез наследяване на класа `Printer`. Могат да бъдат преконфигурирани следните променливи: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // дължина на реда, след която се пренася редът public int $wrapLength = 120; + // знак за индентация, може да бъде заменен с последователност от интервали public string $indentation = "\t"; + // брой празни редове между свойствата public int $linesBetweenProperties = 0; + // брой празни редове между методите public int $linesBetweenMethods = 2; + // брой празни редове между групите 'use statements' за класове, функции и константи public int $linesBetweenUseTypes = 0; + // позиция на отварящата къдрава скоба за функции и методи public bool $bracesOnNextLine = true; + // поставете един параметър на един ред, дори ако има атрибут или е промотиран + public bool $singleParameterOnOneLine = false; + // пропуска пространства от имена, които не съдържат клас или функция + public bool $omitEmptyNamespaces = true; + // разделител между дясната скоба и типа на връщане на функции и методи public string $returnTypeColon = ': '; } ``` +Как и защо всъщност се различават стандартният `Printer` и `PsrPrinter`? Защо в пакета няма само един printer, а именно `PsrPrinter`? -Типове .[#toc-types] --------------------- +Стандартният `Printer` форматира кода така, както го правим в цялото Nette. Тъй като Nette възникна много по-рано от PSR и също така защото PSR дълги години не доставяше стандарти навреме, а например с няколко години закъснение след въвеждането на нова функция в PHP, се стигна до това, че [стандартът за кодиране |contributing:coding-standard] се различава в няколко дреболии. По-голямата разлика е само използването на табулатори вместо интервали. Знаем, че използването на табулатори в нашите проекти позволява персонализиране на ширината, което е [необходимо за хора със зрителни увреждания |contributing:coding-standard#Табулатори вместо интервали]. Пример за дребна разлика е разположението на къдравата скоба на отделен ред при функции и методи и то винаги. Препоръката на PSR ни се струва нелогична и води до [намаляване на прегледността на кода |contributing:coding-standard#Обвиване и скоби]. + + +Типове +------ -Всеки тип или тип съюз/междинна секция може да бъде предаден като низ, можете също така да използвате предварително дефинирани константи за местни типове: +Всеки тип или union/intersection тип може да бъде предаден като низ, можете също така да използвате предефинирани константи за нативни типове: ```php use Nette\PhpGenerator\Type; $member->setType('array'); // или Type::Array; -$member->setType('array|string'); // или Type::union('array', 'string') +$member->setType('?array'); // или Type::nullable(Type::Array); +$member->setType('array|string'); // или Type::union(Type::Array, Type::String) $member->setType('Foo&Bar'); // или Type::intersection(Foo::class, Bar::class) -$member->setType(null); // премахва тип +$member->setType(null); // премахва типа ``` -Същото се отнася и за метода `setReturnType()`. +Същото важи и за метода `setReturnType()`. -Литерали .[#toc-literals] -------------------------- +Литерали +-------- -С помощта на `Literal` можете да предавате произволен PHP код, например стойности на свойства или параметри по подразбиране и т.н: +С помощта на `Literal` можете да предавате произволен PHP код, например за стойности по подразбиране на свойства или параметри и т.н.: ```php use Nette\PhpGenerator\Literal; @@ -545,25 +571,37 @@ class Demo } ``` -Можете също така да подавате параметри на `Literal` и да ги форматирате във валиден PHP код, като използвате [специални заместители |#method-and-function-body-generator]: +Можете също така да предавате параметри на `Literal` и да ги оставите да бъдат форматирани в валиден PHP код с помощта на [placeholders |#Тела на методи и функции]: ```php new Literal('substr(?, ?)', [$a, $b]); -// генерира, например: substr('hello', 5); +// генерира например: substr('hello', 5); ``` +Литерал, представляващ създаването на нов обект, може лесно да бъде генериран с помощта на метода `new`: + +```php +Literal::new(Demo::class, [$a, 'foo' => $b]); +// генерира например: new Demo(10, foo: 20) +``` -Атрибути .[#toc-attributes] ---------------------------- -Можете да добавяте атрибути на PHP 8 към всички класове, методи, свойства, константи, списъци, функции, затваряния и параметри. [Литералите |#Literals] също могат да се използват като стойности на параметрите. +Атрибути +-------- + +Можете да добавите PHP 8 атрибути към всички класове, методи, свойства, константи, enum-и, функции, closures и параметри. Като стойности на параметрите могат да се използват и [#литерали]. ```php $class = new Nette\PhpGenerator\ClassType('Demo'); -$class->addAttribute('Deprecated'); +$class->addAttribute('Table', [ + 'name' => 'user', + 'constraints' => [ + Literal::new('UniqueConstraint', ['name' => 'ean', 'columns' => ['ean']]), + ], +]); $class->addProperty('list') - ->addAttribute('WithArguments', [1, 2]); + ->addAttribute('Deprecated'); $method = $class->addMethod('count') ->addAttribute('Foo\Cached', ['mode' => true]); @@ -577,42 +615,126 @@ echo $class; Резултат: ```php -#[Deprecated] +#[Table(name: 'user', constraints: [new UniqueConstraint(name: 'ean', columns: ['ean'])])] class Demo { - #[WithArguments(1, 2)] + #[Deprecated] public $list; #[Foo\Cached(mode: true)] - public function count(#[Bar] $items) - { + public function count( + #[Bar] + $items, + ) { } } ``` -Пространство от имена .[#toc-namespace] ---------------------------------------- +Property Hooks +-------------- + +С помощта на property hooks (представени от класа [PropertyHook|api:Nette\PhpGenerator\PropertyHook]) можете да дефинирате операции get и set за свойства, което е функция, въведена в PHP 8.4: + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$prop = $class->addProperty('firstName') + ->setType('string'); + +$prop->addHook('set', 'strtolower($value)') + ->addParameter('value') + ->setType('string'); + +$prop->addHook('get') + ->setBody('return ucfirst($this->firstName);'); + +echo $class; +``` + +Генерира: + +```php +class Demo +{ + public string $firstName { + set(string $value) => strtolower($value); + get { + return ucfirst($this->firstName); + } + } +} +``` + +Свойствата и property hooks могат да бъдат абстрактни или финални: + +```php +$class->addProperty('id') + ->setType('int') + ->addHook('get') + ->setAbstract(); + +$class->addProperty('role') + ->setType('string') + ->addHook('set', 'strtolower($value)') + ->setFinal(); +``` + + +Асиметрична видимост +-------------------- + +PHP 8.4 въвежда асиметрична видимост за свойствата. Можете да настроите различни нива на достъп за четене и запис. + +Видимостта може да бъде настроена или с помощта на метода `setVisibility()` с два параметъра, или с помощта на `setPublic()`, `setProtected()` или `setPrivate()` с параметър `mode`, който определя дали видимостта се отнася към четене или запис на свойството. Режимът по подразбиране е `'get'`. + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); + +$class->addProperty('name') + ->setType('string') + ->setVisibility('public', 'private'); // public за четене, private за запис + +$class->addProperty('id') + ->setType('int') + ->setProtected('set'); // protected за запис + +echo $class; +``` + +Генерира: + +```php +class Demo +{ + public private(set) string $name; + + protected(set) int $id; +} +``` + + +Пространство от имена +--------------------- -Класовете, чертите, интерфейсите и енумите (наричани по-долу класове) могат да бъдат групирани в пространства от имена[(PhpNamespace) |api:Nette\PhpGenerator\PhpNamespace]: +Класове, свойства, интерфейси и enum-и (наричани по-долу класове) могат да бъдат групирани в пространства от имена, представени от класа [PhpNamespace |api:Nette\PhpGenerator\PhpNamespace]: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); -// създаване на нови класове в пространството от имена +// създаваме нови класове в пространството от имена $class = $namespace->addClass('Task'); $interface = $namespace->addInterface('Countable'); $trait = $namespace->addTrait('NameAware'); -// или да вмъкнете съществуващ клас в пространството от имена +// или вмъкваме съществуващ клас в пространството от имена $class = new Nette\PhpGenerator\ClassType('Task'); $namespace->add($class); ``` -Ако класът вече съществува, се изхвърля изключение. +Ако класът вече съществува, се хвърля изключение. -Можете да дефинирате декларации за употреба: +Можете да дефинирате use клаузи: ```php // use Http\Request; @@ -623,14 +745,14 @@ $namespace->addUse(Http\Request::class, 'HttpReq'); $namespace->addUseFunction('iter\range'); ``` -За да опростите напълно квалифицирано име на клас, функция или константа в съответствие с дефинираните псевдоними, използвайте метода `simplifyName`: +За да опростите напълно квалифицираното име на клас, функция или константа според дефинираните псевдоними, използвайте метода `simplifyName`: ```php echo $namespace->simplifyName('Foo\Bar'); // 'Bar', защото 'Foo' е текущото пространство от имена -echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', защото е дефинирано твърдение за употреба +echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', поради дефинирания use-statement ``` -И обратно, можете да преобразувате опростено име на клас, функция или константа в пълно квалифицирано име, като използвате метода `resolveName`: +Опростеното име на клас, функция или константа можете, напротив, да преобразувате в напълно квалифицирано име с помощта на метода `resolveName`: ```php echo $namespace->resolveName('Bar'); // 'Foo\Bar' @@ -638,29 +760,27 @@ echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range' ``` -Преобразуване на имена на класове .[#toc-class-names-resolving] ---------------------------------------------------------------- +Преводи на имена на класове +--------------------------- -**Когато класът е част от пространството от имена, той се визуализира по малко по-различен начин**: всички типове (т.е. подсказките за тип, типовете за връщане, името на родителския клас, -имплементирани интерфейси, използвани черти и атрибути) се решават автоматично* (освен ако не го изключите, вижте по-долу). -Това означава, че трябва да **използвате пълни имена на класове** в дефинициите и те ще бъдат заменени с псевдоними (в съответствие с декларациите за употреба) или с напълно квалифицирани имена в получения код: +**Когато класът е част от пространство от имена, той се изобразява леко различно:** всички типове (например typehint-ове, типове на връщане, име на родителски клас, имплементирани интерфейси, използвани свойства и атрибути) се *превеждат* автоматично (освен ако не го изключите, вижте по-долу). Това означава, че трябва **да използвате пълни имена на класове** в дефинициите и те ще бъдат заменени с псевдоними (според use клаузите) или с напълно квалифицирани имена в резултатния код: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); $namespace->addUse('Bar\AliasedClass'); $class = $namespace->addClass('Demo'); -$class->addImplement('Foo\A') // ще се опрости до A - ->addTrait('Bar\AliasedClass'); // ще се опрости до AliasedClass +$class->addImplement('Foo\A') // ще бъде опростен до A + ->addTrait('Bar\AliasedClass'); // ще бъде опростен до AliasedClass $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // в коментарите опростете ръчно +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // в коментарите опростяваме ръчно $method->addParameter('arg') - ->setType('Bar\OtherClass'); // ще се разреши до \Bar\OtherClass + ->setType('Bar\OtherClass'); // ще бъде преведен на \Bar\OtherClass echo $namespace; -// или използвайте PsrPrinter за изход, съответстващ на PSR-2 / PSR-12 +// или използвайте PsrPrinter за изход в съответствие с PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -684,7 +804,7 @@ class Demo implements A } ``` -По този начин автоматичното разрешаване може да бъде изключено: +Автоматичният превод може да бъде изключен по следния начин: ```php $printer = new Nette\PhpGenerator\Printer; // или PsrPrinter @@ -693,14 +813,14 @@ echo $printer->printNamespace($namespace); ``` -PHP файлове .[#toc-php-files] ------------------------------ +PHP файлове +----------- -Класовете, функциите и пространствата от имена могат да бъдат групирани в PHP файлове, представени от класа [PhpFile |api:Nette\PhpGenerator\PhpFile]: +Класове, функции и пространства от имена могат да бъдат групирани в PHP файлове, представени от класа [PhpFile|api:Nette\PhpGenerator\PhpFile]: ```php $file = new Nette\PhpGenerator\PhpFile; -$file->addComment('This file is auto-generated.'); +$file->addComment('Този файл е автоматично генериран.'); $file->setStrictTypes(); // добавя declare(strict_types=1) $class = $file->addClass('Foo\A'); @@ -713,7 +833,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// или използвайте PsrPrinter за изход, съответстващ на PSR-2 / PSR-12 +// или използвайте PsrPrinter за изход в съответствие с PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -723,7 +843,7 @@ echo $file; <?php /** - * This file is auto-generated. + * Този файл е автоматично генериран. */ declare(strict_types=1); @@ -739,27 +859,28 @@ function foo() } ``` +**Предупреждение:** Във файловете не може да се добавя никакъв друг код извън функциите и класовете. -Генериране според съществуващите .[#toc-generating-according-to-existing-ones] ------------------------------------------------------------------------------- -Освен че можете да моделирате класове и функции, използвайки API, описан по-горе, можете също така да ги генерирате автоматично по съществуващи такива: +Генериране по съществуващи +-------------------------- + +Освен това, че можете да моделирате класове и функции с помощта на описаното по-горе API, можете също така да ги оставите да бъдат генерирани автоматично по съществуващи модели: ```php // създава клас, идентичен на класа PDO $class = Nette\PhpGenerator\ClassType::from(PDO::class); -// създава функция, идентична на trim() +// създава функция, идентична на функцията trim() $function = Nette\PhpGenerator\GlobalFunction::from('trim'); -// създава затваряне, както е посочено +// създава closure според посочената $closure = Nette\PhpGenerator\Closure::from( function (stdClass $a, $b = null) {}, ); ``` -По подразбиране телата на функциите и методите са празни. Ако искате да ги заредите също, използвайте този начин -(той изисква да е инсталиран `nikic/php-parser`): +Телата на функциите и методите са празни по подразбиране. Ако искате да ги заредите също, използвайте този начин (изисква инсталиране на пакета `nikic/php-parser`): ```php $class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true); @@ -768,10 +889,10 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); ``` -Зареждане от PHP файл .[#toc-loading-from-php-file] ---------------------------------------------------- +Зареждане от PHP файлове +------------------------ -Можете също така да зареждате функции, класове, интерфейси и енуми директно от низ от PHP код. Например, създаваме обект `ClassType` по този начин: +Можете да зареждате функции, класове, интерфейси и enum-и също директно от низ, съдържащ PHP код. Например, така създаваме обект `ClassType`: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -784,39 +905,69 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -Когато зареждате класове от PHP код, коментарите на един ред извън тялото на метода се игнорират (например за свойства и т.н.), тъй като тази библиотека не разполага с API за работа с тях. +При зареждане на класове от PHP код, едноредовите коментари извън телата на методите се игнорират (напр. при свойства и т.н.), тъй като тази библиотека няма API за работа с тях. -Можете също така да заредите директно целия PHP файл, който може да съдържа произволен брой класове, функции или дори няколко пространства от имена: +Можете също така да заредите директно цял PHP файл, който може да съдържа произволен брой класове, функции или дори пространства от имена: ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` -Първоначалният коментар на файла и декларацията `strict_types` също се зареждат. От друга страна, всички останали глобални кодове се игнорират. +Зареждат се също уводният коментар към файла и декларацията `strict_types`. Напротив, целият останал глобален код се игнорира. -Това изисква да е инсталиран `nikic/php-parser`. +Изисква се да бъде инсталиран `nikic/php-parser`. .[note] -Ако трябва да манипулирате глобален код във файлове или отделни оператори в тялото на метод, по-добре е да използвате директно библиотеката `nikic/php-parser`. +Ако трябва да манипулирате с глобален код във файлове или с отделни инструкции в телата на методите, е по-добре да използвате директно библиотеката `nikic/php-parser`. -Дъмпер за променливи .[#toc-variables-dumper] ---------------------------------------------- +Class Manipulator +----------------- -Dumper връща символно представяне на променлива в PHP низ. Осигурява по-добър и по-ясен изход от нативната функция `var_export()`. +Класът [ClassManipulator|api:Nette\PhpGenerator\ClassManipulator] предоставя инструменти за манипулация с класове. + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$manipulator = new Nette\PhpGenerator\ClassManipulator($class); +``` + +Методът `inheritMethod()` копира метод от родителски клас или имплементиран интерфейс във вашия клас. Това ви позволява да презапишете метода или да разширите неговата сигнатура: + +```php +$method = $manipulator->inheritMethod('bar'); +$method->setBody('...'); +``` + +Методът `inheritProperty()` копира свойство от родителски клас във вашия клас. Това е полезно, когато искате да имате същото свойство във вашия клас, но например с друга стойност по подразбиране: + +```php +$property = $manipulator->inheritProperty('foo'); +$property->setValue('new value'); +``` + +Методът `implement()` автоматично имплементира всички методи и свойства от даден интерфейс или абстрактен клас във вашия клас: + +```php +$manipulator->implement(SomeInterface::class); +// Сега вашият клас имплементира SomeInterface и съдържа всички негови методи +``` + + +Извеждане на променливи +----------------------- + +Класът `Dumper` преобразува променлива в разпознаваем PHP код. Предоставя по-добър и по-прегледен изход от стандартната функция `var_export()`. ```php $dumper = new Nette\PhpGenerator\Dumper; $var = ['a', 'b', 123]; -echo $dumper->dump($var); // отпечатва ['a', 'b', 123] +echo $dumper->dump($var); // извежда ['a', 'b', 123] ``` -Таблица за съвместимост .[#toc-compatibility-table] ---------------------------------------------------- - -PhpGenerator 4.0 е съвместим с PHP 8.0 до 8.2 +Таблица за съвместимост +----------------------- -{{leftbar: nette:@menu-topics}} +PhpGenerator 4.1 е съвместим с PHP 8.0 до 8.4. diff --git a/php-generator/bg/@meta.texy b/php-generator/bg/@meta.texy new file mode 100644 index 0000000000..794cbc8522 --- /dev/null +++ b/php-generator/bg/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Документация на Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/php-generator/cs/@home.texy b/php-generator/cs/@home.texy index d8d5ab84b5..77e0c5083b 100644 --- a/php-generator/cs/@home.texy +++ b/php-generator/cs/@home.texy @@ -1,11 +1,12 @@ -Generátor PHP kódu +Nette PhpGenerator ****************** -<div class=perex> -- Potřebujete generovat kód tříd, funkcí, PHP souborů atd? -- Umí všechny nejnovější vychytávky v PHP (jako enumy atd.) -- Dovolí vám snadno modifikovat existující třídy -- Výstup vyhovující PSR-12 +<div class="perex"> +Hledáte nástroj pro generování PHP kódu tříd, funkcí či kompletních souborů? + +- Umí všechny nejnovější vychytávky v PHP (jako property hooks, enumy, atributy atd.) +- Umožní vám snadno modifikovat existující třídy +- Výstupní kód je v souladu s PSR-12 / PER coding style - Zralá, stabilní a široce používaná knihovna </div> @@ -91,7 +92,7 @@ private static $items = [1, 2, 3]; public ?array $list = null; ``` -A můžeme přidat [metody|#Signatury metod a funkcí]: +A můžeme přidat [metody |#Signatury metod a funkcí]: ```php $method = $class->addMethod('count') @@ -276,7 +277,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// nebo použijte PsrPrinter pro výstup v souladu s PSR-2 / PSR-12 +// nebo použijte PsrPrinter pro výstup v souladu s PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -304,7 +305,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// nebo použijte PsrPrinter pro výstup v souladu s PSR-2 / PSR-12 +// nebo použijte PsrPrinter pro výstup v souladu s PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -341,7 +342,7 @@ fn($a, $b) => $a + $b Signatury metod a funkcí ------------------------ -Metody reprezentuje třída [Method |api:Nette\PhpGenerator\Method]. Můžete nastavit viditelnost, návratovou hodnotu, přidat komentáře, [atributy|#Atributy] atd: +Metody reprezentuje třída [Method |api:Nette\PhpGenerator\Method]. Můžete nastavit viditelnost, návratovou hodnotu, přidat komentáře, [#atributy] atd: ```php $method = $class->addMethod('count') @@ -358,10 +359,10 @@ $method->addParameter('items', []) // $items = [] ->setReference() // &$items = [] ->setType('array'); // array &$items = [] -// function count(&$items = []) +// function count(array &$items = []) ``` -Pro definici tzv. variadics parametrů (nebo též splat operátor) slouží `setVariadics()`: +Pro definici tzv. variadics parametrů (nebo též splat operátor) slouží `setVariadic()`: ```php $method = $class->addMethod('count'); @@ -471,31 +472,57 @@ function foo($a) Printer a soulad s PSR ---------------------- -PHP kód generují objekty `Printer`. K je vám tiskárna `PsrPrinter`, jejíž výstup je v souladu s PSR-2 a PSR-12 a k odsazování používá mezery, a dále `Printer`, která pro odsazování používá tabulátory. +Ke generování PHP kódu slouží třída [Printer |api:Nette\PhpGenerator\Printer]: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // totéž, jako: echo $class +``` + +Umí vygenerovat kód všech dalších prvků, nabízí metody jako `printFunction()`, `printNamespace()`, atd. + +K dispozici je také třída `PsrPrinter`, jejíž výstup je v souladu s PSR-2 / PSR-12 / PER coding style: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // odsazení 4 mezerami +echo $printer->printClass($class); ``` -Potřebujete na míru upravit chování printeru? Vytvořte si vlastní poděděním třídy `Printer`. Můžete překonfigurovat tyto proměnné: +Potřebujete chování doladit na míru? Vytvořte si vlastní verzi poděděním třídy `Printer`. Lze překonfigurovat tyto proměnné: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // délka řádku, po které dojde k zalamování řádku public int $wrapLength = 120; + // znak odsazení, může být nahrazen sekvencí mezer public string $indentation = "\t"; + // počet prázdných řádků mezi properties public int $linesBetweenProperties = 0; + // počet prázdných řádků mezi metodami public int $linesBetweenMethods = 2; + // počet prázdných řádků mezi skupinami 'use statements' pro třídy, funkce a konstanty public int $linesBetweenUseTypes = 0; + // pozice otevírací složené závorky pro funkce a metody public bool $bracesOnNextLine = true; + // umístěte jeden parametr na jeden řádek, i když má atribut nebo je podporován + public bool $singleParameterOnOneLine = false; + // omits namespaces that do not contain any class or function + public bool $omitEmptyNamespaces = true; + // umístí declare(strict_types) na stejný řádek jako <?php + public bool $declareOnOpenTag = false; + // oddělovač mezi pravou závorkou a návratovým typem funkcí a metod public string $returnTypeColon = ': '; } ``` +Jak a proč se vlastně liší standardní `Printer` a `PsrPrinter`? Proč není v balíčku jen jeden printer, a to `PsrPrinter`? + +Standardní `Printer` formátuje kód tak, jak to děláme v celém Nette. Tím, že Nette vzniklo mnohem dřív, než PSR, a také proto, že PSR dlouhé roky nedodávalo standardy včas, ale třeba až s několikaletým zpožděním od uvedení nové featury v PHP, došlo k tomu, že [kódovací standard |contributing:coding-standard] se v několika drobnostech liší. Větším rozdílem je jen používání tabulátorů místo mezer. Víme, že používáním tabulátorů v našich projektech umožňujeme přizpůsobení šířky, které je pro [lidi se zrakovým postižením nezbytné |contributing:coding-standard#Tabulátory místo mezer]. Příkladem drobné odlišnosti je umístění složené závorky na samostatném řádku u funkcí a metod a to vždy. Doporučení PSR se nám jeví jako nelogické a vede k [snížení přehlednosti kódu |contributing:coding-standard#Wrapping and Braces]. + Typy ---- @@ -506,7 +533,8 @@ Každý typ nebo union/intersection typ lze předat jako řetězec, můžete tak use Nette\PhpGenerator\Type; $member->setType('array'); // nebo Type::Array; -$member->setType('array|string'); // nebo Type::union('array', 'string') +$member->setType('?array'); // or Type::nullable(Type::Array); +$member->setType('array|string'); // or Type::union(Type::Array, Type::String) $member->setType('Foo&Bar'); // nebo Type::intersection(Foo::class, Bar::class) $member->setType(null); // odstraní typ ``` @@ -545,13 +573,20 @@ class Demo } ``` -Můžete také předat parametry do `Literal` a nechat je zformátovat do platného kódu PHP pomocí [zástupných znaků|#Generování těl metod a funkcí]: +Můžete také předat parametry do `Literal` a nechat je zformátovat do platného kódu PHP pomocí [zástupných znaků |#Těla metod a funkcí]: ```php new Literal('substr(?, ?)', [$a, $b]); // generuje například: substr('hello', 5); ``` +Literál představující vytvoření nového objektu lze snadno vygenerovat pomocí metody `new`: + +```php +Literal::new(Demo::class, [$a, 'foo' => $b]); +// generuje například: new Demo(10, foo: 20) +``` + Atributy -------- @@ -560,10 +595,15 @@ PHP 8 atributy můžete přidat do všech tříd, metod, vlastností, konstant, ```php $class = new Nette\PhpGenerator\ClassType('Demo'); -$class->addAttribute('Deprecated'); +$class->addAttribute('Table', [ + 'name' => 'user', + 'constraints' => [ + Literal::new('UniqueConstraint', ['name' => 'ean', 'columns' => ['ean']]), + ], +]); $class->addProperty('list') - ->addAttribute('WithArguments', [1, 2]); + ->addAttribute('Deprecated'); $method = $class->addMethod('count') ->addAttribute('Foo\Cached', ['mode' => true]); @@ -577,21 +617,105 @@ echo $class; Výsledek: ```php -#[Deprecated] +#[Table(name: 'user', constraints: [new UniqueConstraint(name: 'ean', columns: ['ean'])])] class Demo { - #[WithArguments(1, 2)] + #[Deprecated] public $list; #[Foo\Cached(mode: true)] - public function count(#[Bar] $items) - { + public function count( + #[Bar] + $items, + ) { } } ``` +Property Hooks +-------------- + +Pomocí property hooks (reprezentované třídou [PropertyHook|api:Nette\PhpGenerator\PropertyHook]) můžete definovat operace get a set pro vlastnosti, což je funkce zavedená v PHP 8.4: + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$prop = $class->addProperty('firstName') + ->setType('string'); + +$prop->addHook('set', 'strtolower($value)') + ->addParameter('value') + ->setType('string'); + +$prop->addHook('get') + ->setBody('return ucfirst($this->firstName);'); + +echo $class; +``` + +Vygeneruje: + +```php +class Demo +{ + public string $firstName { + set(string $value) => strtolower($value); + get { + return ucfirst($this->firstName); + } + } +} +``` + +Property a property hooks mohou být abstraktní nebo finální: + +```php +$class->addProperty('id') + ->setType('int') + ->addHook('get') + ->setAbstract(); + +$class->addProperty('role') + ->setType('string') + ->addHook('set', 'strtolower($value)') + ->setFinal(); +``` + + +Asymetrická viditelnost +----------------------- + +PHP 8.4 zavádí asymetrickou viditelnost pro vlastnosti. Můžete nastavit různé úrovně přístupu pro čtení a zápis. + +Viditelnost lze nastavit buď pomocí metody `setVisibility()` se dvěma parametry, nebo pomocí `setPublic()`, `setProtected()` nebo `setPrivate()` s parametrem `mode`, který určuje, zda se viditelnost vztahuje ke čtení nebo zápisu vlastnosti. Výchozí režim je `'get'`. + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); + +$class->addProperty('name') + ->setType('string') + ->setVisibility('public', 'private'); // public pro čtení, private pro zápis + +$class->addProperty('id') + ->setType('int') + ->setProtected('set'); // protected pro zápis + +echo $class; +``` + +Vygeneruje: + +```php +class Demo +{ + public private(set) string $name; + + protected(set) int $id; +} +``` + + Jmenný prostor -------------- @@ -605,7 +729,7 @@ $class = $namespace->addClass('Task'); $interface = $namespace->addInterface('Countable'); $trait = $namespace->addTrait('NameAware'); -// nebo vložíme existující třídu do namespace +// nebo vložíme existující třídu nebo funkci do namespace $class = new Nette\PhpGenerator\ClassType('Task'); $namespace->add($class); ``` @@ -641,9 +765,7 @@ echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range' Překlady názvů tříd ------------------- -**Když je třída součástí jmenného prostoru, je vykreslena mírně odlišně:** všechny typy (například typehinty, návratové typy, název rodičovské třídy, -implementovaná rozhraní, použité vlastnosti a atributy) jsou automaticky *překládány* (pokud to nevypnete, viz níže). -To znamená, že musíte v definicích **používat úplné názvy tříd** a ty budou nahrazeny za aliasy (podle klauzulí use) nebo za plně kvalifikovaná jména ve výsledném kódu: +**Když je třída součástí jmenného prostoru, je vykreslena mírně odlišně:** všechny typy (například typehinty, návratové typy, název rodičovské třídy, implementovaná rozhraní, použité vlastnosti a atributy) jsou automaticky *překládány* (pokud to nevypnete, viz níže). To znamená, že musíte v definicích **používat úplné názvy tříd** a ty budou nahrazeny za aliasy (podle klauzulí use) nebo za plně kvalifikovaná jména ve výsledném kódu: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); @@ -660,7 +782,7 @@ $method->addParameter('arg') echo $namespace; -// nebo použijte PsrPrinter pro výstup v souladu s PSR-2 / PSR-12 +// nebo použijte PsrPrinter pro výstup v souladu s PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -713,21 +835,19 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// nebo použijte PsrPrinter pro výstup v souladu s PSR-2 / PSR-12 +// nebo použijte PsrPrinter pro výstup v souladu s PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` Výsledek: ```php -<?php +<?php declare(strict_types=1); /** * This file is auto-generated. */ -declare(strict_types=1); - namespace Foo; class A @@ -739,6 +859,16 @@ function foo() } ``` +Do souboru lze vkládat i existující objekty tříd, funkcí a jmenných prostorů pomocí metody `add()`: + +```php +$file = new Nette\PhpGenerator\PhpFile; +$class = new Nette\PhpGenerator\ClassType('Demo'); +$file->add($class); +``` + +**Upozornění:** Do souborů není možné přidávat žádný další kód mimo funkce a třídy. + Generování podle existujících ----------------------------- @@ -758,8 +888,7 @@ $closure = Nette\PhpGenerator\Closure::from( ); ``` -Těla funkcí a metod jsou ve výchozím stavu prázdná. Pokud je chcete také načíst, použijte tento způsob -(vyžaduje instalaci balíčku `nikic/php-parser`): +Těla funkcí a metod jsou ve výchozím stavu prázdná. Pokud je chcete také načíst, použijte tento způsob (vyžaduje instalaci balíčku `nikic/php-parser`): ```php $class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true); @@ -800,6 +929,38 @@ Vyžaduje se, aby byl nainstalován `nikic/php-parser`. Pokud potřebujete manipulovat s globálním kódem v souborech nebo s jednotlivými příkazy v tělech metod, je lepší použít přímo knihovnu `nikic/php-parser`. +Class Manipulator +----------------- + +Třída [ClassManipulator|api:Nette\PhpGenerator\ClassManipulator] poskytuje nástroje pro manipulaci s třídami. + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$manipulator = new Nette\PhpGenerator\ClassManipulator($class); +``` + +Metoda `inheritMethod()` zkopíruje metodu z rodičovské třídy nebo implementovaného rozhraní do vaší třídy. To vám umožní přepsat metodu nebo rozšířit její signaturu: + +```php +$method = $manipulator->inheritMethod('bar'); +$method->setBody('...'); +``` + +Metoda `inheritProperty()` zkopíruje vlastnost z rodičovské třídy do vaší třídy. Je to užitečné, když chcete ve své třídě mít stejnou vlastnost, ale třeba s jinou výchozí hodnotou: + +```php +$property = $manipulator->inheritProperty('foo'); +$property->setValue('new value'); +``` + +Metoda `implement()` automaticky implementuje všechny metody a vlastnosti z daného rozhraní nebo abstraktní třídy ve vaší třídě: + +```php +$manipulator->implement(SomeInterface::class); +// Nyní vaše třída implementuje SomeInterface a obsahuje všechny jeho metody +``` + + Výpis proměnných ---------------- @@ -817,7 +978,4 @@ echo $dumper->dump($var); // vypíše ['a', 'b', 123] Tabulka kompatibility --------------------- -PhpGenerator 4.0 je kompatibilní s PHP 8.0 až 8.2 - - -{{leftbar: nette:@menu-topics}} +PhpGenerator 4.1 je kompatibilní s PHP 8.0 až 8.4. diff --git a/php-generator/cs/@meta.texy b/php-generator/cs/@meta.texy new file mode 100644 index 0000000000..08edde785b --- /dev/null +++ b/php-generator/cs/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Dokumentace}} +{{leftbar: nette:@menu-topics}} diff --git a/php-generator/de/@home.texy b/php-generator/de/@home.texy index fb588f2863..3d50af633d 100644 --- a/php-generator/de/@home.texy +++ b/php-generator/de/@home.texy @@ -1,31 +1,32 @@ -PHP-Code-Generator +Nette PhpGenerator ****************** -<div class=perex> -- Müssen Sie PHP-Code für Klassen, Funktionen, PHP-Dateien usw. generieren? -- Unterstützt alle aktuellen PHP-Funktionen wie Enums, Attribute, etc. -- Ermöglicht die einfache Änderung bestehender Klassen -- PSR-12-konforme Ausgabe -- Hochgradig ausgereifte, stabile und weit verbreitete Bibliothek +<div class="perex"> +Suchen Sie ein Werkzeug zur Generierung von PHP-Code für Klassen, Funktionen oder komplette Dateien? + +- Kennt alle neuesten PHP-Features (wie Property Hooks, Enums, Attribute usw.) +- Ermöglicht Ihnen die einfache Modifizierung bestehender Klassen +- Der Ausgabecode entspricht dem PSR-12 / PER Coding Style +- Eine ausgereifte, stabile und weit verbreitete Bibliothek </div> -Installation .[#toc-installation] ---------------------------------- +Installation +------------ -Laden Sie das Paket herunter und installieren Sie es mit [Composer |best-practices:composer]: +Sie können die Bibliothek mit dem Werkzeug [Composer|best-practices:composer] herunterladen und installieren: ```shell composer require nette/php-generator ``` -Für die PHP-Kompatibilität siehe die [Tabelle |#Compatibility Table]. +Die PHP-Kompatibilität finden Sie in der [#Kompatibilitätstabelle]. -Klassen .[#toc-classes] ------------------------ +Klassen +------- -Beginnen wir mit einem einfachen Beispiel für die Erzeugung von Klassen mit [ClassType |api:Nette\PhpGenerator\ClassType]: +Beginnen wir gleich mit einem Beispiel zur Erstellung einer Klasse mithilfe von [ClassType |api:Nette\PhpGenerator\ClassType]: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -34,19 +35,19 @@ $class ->setFinal() ->setExtends(ParentClass::class) ->addImplement(Countable::class) - ->addComment("Description of class.\nSecond line\n") + ->addComment("Beschreibung der Klasse.\nZweite Zeile\n") ->addComment('@property-read Nette\Forms\Form $form'); -// zur Erzeugung von PHP-Code einfach in einen String umwandeln oder echo verwenden: +// Code einfach durch Typumwandlung in einen String oder mit echo generieren: echo $class; ``` -Es wird dieses Ergebnis wiedergeben: +Gibt folgendes Ergebnis zurück: ```php /** - * Description of class. - * Second line + * Beschreibung der Klasse + * Zweite Zeile * * @property-read Nette\Forms\Form $form */ @@ -55,7 +56,7 @@ final class Demo extends ParentClass implements Countable } ``` -Wir können auch einen Drucker verwenden, um den Code zu erzeugen, den wir im Gegensatz zu `echo $class`[weiter konfigurieren |#Printers and PSR compliance] können: +Zur Generierung des Codes können wir auch den sogenannten Printer verwenden, den wir im Gegensatz zu `echo $class` [weiter konfigurieren |#Printer und PSR-Konformität] können: ```php $printer = new Nette\PhpGenerator\Printer; @@ -66,21 +67,21 @@ Wir können Konstanten (Klasse [Constant |api:Nette\PhpGenerator\Constant]) und ```php $class->addConstant('ID', 123) - ->setProtected() // Konstante Sichtbarkeit + ->setProtected() // Sichtbarkeit von Konstanten ->setType('int') ->setFinal(); $class->addProperty('items', [1, 2, 3]) - ->setPrivate() // oder setVisibility('privat') + ->setPrivate() // oder setVisibility('private') ->setStatic() ->addComment('@var int[]'); $class->addProperty('list') ->setType('?array') - ->setInitialized(); // druckt '= null' + ->setInitialized(); // schreibt '= null' ``` -Es erzeugt: +Generiert: ```php final protected const int ID = 123; @@ -91,14 +92,14 @@ private static $items = [1, 2, 3]; public ?array $list = null; ``` -Und wir können [Methoden |#Method and Function Signature] hinzufügen: +Und wir können [Methoden |#Signaturen von Methoden und Funktionen] hinzufügen: ```php $method = $class->addMethod('count') - ->addComment('Count it.') + ->addComment('Zähle es.') ->setFinal() ->setProtected() - ->setReturnType('?int') // Methode Rückgabetyp + ->setReturnType('?int') // Rückgabetypen bei Methoden ->setBody('return count($items ?: $this->items);'); $method->addParameter('items', []) // $items = [] @@ -110,7 +111,7 @@ Das Ergebnis ist: ```php /** - * Count it. + * Zähle es. */ final protected function count(array &$items = []): ?int { @@ -118,7 +119,7 @@ final protected function count(array &$items = []): ?int } ``` -Die mit PHP 8.0 eingeführten promotierten Parameter können an den Konstruktor übergeben werden: +Promoted Properties, eingeführt in PHP 8.0, können an den Konstruktor übergeben werden: ```php $method = $class->addMethod('__construct'); @@ -137,15 +138,15 @@ public function __construct( } ``` -Readonly-Eigenschaften und -Klassen können über `setReadOnly()` markiert werden. +Readonly-Eigenschaften und -Klassen können mit der Funktion `setReadOnly()` markiert werden. ------ Wenn die hinzugefügte Eigenschaft, Konstante, Methode oder der Parameter bereits existiert, wird eine Ausnahme ausgelöst. -Mitglieder können mit `removeProperty()`, `removeConstant()`, `removeMethod()` oder `removeParameter()` entfernt werden. +Klassenmitglieder können mit `removeProperty()`, `removeConstant()`, `removeMethod()` oder `removeParameter()` entfernt werden. -Sie können auch bestehende `Method`, `Property` oder `Constant` Objekte zur Klasse hinzufügen: +Sie können auch vorhandene `Method`-, `Property`- oder `Constant`-Objekte zur Klasse hinzufügen: ```php $method = new Nette\PhpGenerator\Method('getHandle'); @@ -158,7 +159,7 @@ $class = (new Nette\PhpGenerator\ClassType('Demo')) ->addMember($const); ``` -Mit `cloneWithName()` können Sie vorhandene Methoden, Eigenschaften und Konstanten mit einem anderen Namen klonen: +Sie können auch bestehende Methoden, Eigenschaften und Konstanten unter einem anderen Namen mit `cloneWithName()` klonen: ```php $methodCount = $class->getMethod('count'); @@ -167,17 +168,17 @@ $class->addMember($methodRecount); ``` -Schnittstelle oder Trait .[#toc-interface-or-trait] ---------------------------------------------------- +Interface oder Trait +-------------------- -Sie können Schnittstellen und Traits (Klassen [InterfaceType |api:Nette\PhpGenerator\InterfaceType] und [TraitType |api:Nette\PhpGenerator\TraitType]) erstellen: +Sie können Schnittstellen und Traits erstellen (Klassen [InterfaceType |api:Nette\PhpGenerator\InterfaceType] und [TraitType |api:Nette\PhpGenerator\TraitType]): ```php $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait'); ``` -Eigenschaften verwenden: +Verwendung von Traits: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -202,10 +203,10 @@ class Demo ``` -Enums .[#toc-enums] -------------------- +Enums +----- -Sie können ganz einfach die Enums erstellen, die PHP 8.1 mitbringt (Klasse [EnumType |api:Nette\PhpGenerator\EnumType]): +Aufzählungen (Enums), die mit PHP 8.1 eingeführt wurden, können Sie einfach so erstellen: (Klasse [EnumType |api:Nette\PhpGenerator\EnumType]): ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -229,20 +230,20 @@ enum Suit } ``` -Sie können auch skalare Äquivalente für Fälle definieren, um eine zurückgesetzte Aufzählung zu erstellen: +Sie können auch skalare Äquivalente definieren und so eine "backed" Aufzählung erstellen: ```php $enum->addCase('Clubs', '♣'); $enum->addCase('Diamonds', '♦'); ``` -Es ist möglich, jedem Fall einen Kommentar oder [Attribute |#attributes] hinzuzufügen, indem Sie `addComment()` oder `addAttribute()` verwenden. +Zu jedem *case* kann ein Kommentar oder [#Attribute] mit `addComment()` oder `addAttribute()` hinzugefügt werden. -Anonyme Klasse .[#toc-anonymous-class] --------------------------------------- +Anonyme Klassen +--------------- -Geben Sie `null` als Namen an und Sie haben eine anonyme Klasse: +Wir übergeben `null` als Namen und haben eine anonyme Klasse: ```php $class = new Nette\PhpGenerator\ClassType(null); @@ -264,10 +265,10 @@ $obj = new class ($val) { ``` -Globale Funktion .[#toc-global-function] ----------------------------------------- +Globale Funktionen +------------------ -Der Code der Funktionen wird die Klasse [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction] erzeugen: +Den Code von Funktionen generiert die Klasse [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction]: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -276,7 +277,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// oder PsrPrinter für PSR-2 / PSR-12-konforme Ausgabe verwenden +// oder verwenden Sie PsrPrinter für die Ausgabe gemäß PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -290,10 +291,10 @@ function foo($a, $b) ``` -Schließung .[#toc-closure] --------------------------- +Anonyme Funktionen +------------------ -Der Code von Closures wird die Klasse [Closure |api:Nette\PhpGenerator\Closure] erzeugen: +Den Code anonymer Funktionen generiert die Klasse [Closure |api:Nette\PhpGenerator\Closure]: ```php $closure = new Nette\PhpGenerator\Closure; @@ -304,7 +305,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// oder PsrPrinter für PSR-2 / PSR-12-konforme Ausgabe verwenden +// oder verwenden Sie PsrPrinter für die Ausgabe gemäß PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -317,10 +318,10 @@ function ($a, $b) use (&$c) { ``` -Pfeil-Funktion .[#toc-arrow-function] -------------------------------------- +Kurze Arrow-Funktionen +---------------------- -Sie können den Abschluss auch als Pfeilfunktion mit einem Drucker ausdrucken: +Sie können auch eine verkürzte anonyme Funktion mit dem Printer ausgeben: ```php $closure = new Nette\PhpGenerator\Closure; @@ -338,38 +339,38 @@ fn($a, $b) => $a + $b ``` -Methode und Funktionssignatur .[#toc-method-and-function-signature] -------------------------------------------------------------------- +Signaturen von Methoden und Funktionen +-------------------------------------- -Methoden werden durch die Klasse [Method |api:Nette\PhpGenerator\Method] repräsentiert. Sie können die Sichtbarkeit und den Rückgabewert festlegen, Kommentare und [Attribute |#Attributes] hinzufügen usw: +Methoden werden durch die Klasse [Method |api:Nette\PhpGenerator\Method] repräsentiert. Sie können Sichtbarkeit, Rückgabewert festlegen, Kommentare, [#Attribute] hinzufügen usw.: ```php $method = $class->addMethod('count') - ->addComment('Count it.') + ->addComment('Zähle es.') ->setFinal() ->setProtected() ->setReturnType('?int'); ``` -Jeder Parameter wird durch eine Klasse [Parameter |api:Nette\PhpGenerator\Parameter] repräsentiert. Auch hier können Sie jede erdenkliche Eigenschaft einstellen: +Einzelne Parameter werden durch die Klasse [Parameter |api:Nette\PhpGenerator\Parameter] repräsentiert. Auch hier können Sie alle denkbaren Eigenschaften festlegen: ```php $method->addParameter('items', []) // $items = [] - ->setReference() // &$items = [] - ->setType('array'); // array &$items = [] + ->setReference() // &$items = [] + ->setType('array'); // array &$items = [] -// function count(&$items = []) +// function count(array &$items = []) ``` -Um die sogenannten Variadics-Parameter (oder auch den Splat-, Spread-, Ellipsis-, Unpacking- oder Three Dots-Operator) zu definieren, verwenden Sie `setVariadics()`: +Zur Definition sogenannter variadischer Parameter (oder auch Splat-Operator) dient `setVariadic()`: ```php $method = $class->addMethod('count'); -$method->setVariadics(true); +$method->setVariadic(true); $method->addParameter('items'); ``` -Erzeugt: +Generiert: ```php function count(...$items) @@ -378,10 +379,10 @@ function count(...$items) ``` -Methode und Funktionskörper .[#toc-method-and-function-body] ------------------------------------------------------------- +Körper von Methoden und Funktionen +---------------------------------- -Der Körper kann der Methode `setBody()` auf einmal oder sequentiell (Zeile für Zeile) durch wiederholten Aufruf von `addBody()` übergeben werden: +Der Körper kann auf einmal an die Methode `setBody()` übergeben oder schrittweise (zeilenweise) durch wiederholten Aufruf von `addBody()`: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -400,7 +401,7 @@ function foo() } ``` -Sie können spezielle Platzhalter verwenden, um Variablen auf praktische Art und Weise zu injizieren. +Sie können spezielle Platzhalter verwenden, um Variablen einfach einzufügen. Einfache Platzhalter `?` @@ -412,7 +413,7 @@ $function->addBody('return substr(?, ?);', [$str, $num]); echo $function; ``` -Ergebnis: +Ergebnis ```php function foo() @@ -421,7 +422,7 @@ function foo() } ``` -Variadischer Platzhalter `...?` +Platzhalter für Variadic `...?` ```php $items = [1, 2, 3]; @@ -439,7 +440,7 @@ function foo() } ``` -Sie können auch PHP 8 benannte Parameter mit Platzhaltern verwenden `...?:` +Sie können auch benannte Parameter für PHP 8 mit `...?:` verwenden: ```php $items = ['foo' => 1, 'bar' => true]; @@ -448,7 +449,7 @@ $function->setBody('myfunc(...?:);', [$items]); // myfunc(foo: 1, bar: true); ``` -Platzhalter mit Schrägstrich ausblenden `\?` +Der Platzhalter wird mit einem Backslash `\?` escapet. ```php $num = 3; @@ -468,56 +469,81 @@ function foo($a) ``` -Drucker und PSR-Konformität .[#toc-printers-and-psr-compliance] ---------------------------------------------------------------- +Printer und PSR-Konformität +--------------------------- -PHP-Code wird von `Printer` Objekten erzeugt. Es gibt eine `PsrPrinter`, deren Ausgabe mit PSR-2 und PSR-12 konform ist und Leerzeichen für die Einrückung verwendet, und eine `Printer`, die Tabulatoren für die Einrückung verwendet. +Zur Generierung von PHP-Code dient die Klasse [Printer |api:Nette\PhpGenerator\Printer]: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // dasselbe wie: echo $class +``` + +Er kann Code aller weiteren Elemente generieren und bietet Methoden wie `printFunction()`, `printNamespace()` usw. + +Es gibt auch die Klasse `PsrPrinter`, deren Ausgabe dem PSR-2 / PSR-12 / PER Coding Style entspricht: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // 4 Leerzeichen Einrückung +echo $printer->printClass($class); ``` -Möchten Sie das Verhalten des Druckers anpassen? Erstellen Sie Ihr eigenes, indem Sie die Klasse `Printer` erben. Sie können diese Variablen neu konfigurieren: +Müssen Sie das Verhalten anpassen? Erstellen Sie Ihre eigene Version durch Vererbung der Klasse `Printer`. Diese Variablen können neu konfiguriert werden: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // Zeilenlänge, nach der ein Zeilenumbruch erfolgt public int $wrapLength = 120; + // Einrückungszeichen, kann durch eine Sequenz von Leerzeichen ersetzt werden public string $indentation = "\t"; + // Anzahl der Leerzeilen zwischen Properties public int $linesBetweenProperties = 0; + // Anzahl der Leerzeilen zwischen Methoden public int $linesBetweenMethods = 2; + // Anzahl der Leerzeilen zwischen Gruppen von 'use statements' für Klassen, Funktionen und Konstanten public int $linesBetweenUseTypes = 0; + // Position der öffnenden geschweiften Klammer für Funktionen und Methoden public bool $bracesOnNextLine = true; + // Platziert einen einzelnen Parameter auf einer Zeile, auch wenn er ein Attribut hat oder unterstützt wird + public bool $singleParameterOnOneLine = false; + // lässt Namespaces aus, die keine Klasse oder Funktion enthalten + public bool $omitEmptyNamespaces = true; + // Trennzeichen zwischen der rechten Klammer und dem Rückgabetyp von Funktionen und Methoden public string $returnTypeColon = ': '; } ``` +Wie und warum unterscheiden sich eigentlich der Standard-`Printer` und `PsrPrinter`? Warum gibt es im Paket nicht nur einen Printer, nämlich `PsrPrinter`? + +Der Standard-`Printer` formatiert den Code so, wie wir es in ganz Nette tun. Da Nette viel früher als PSR entstanden ist und PSR jahrelang keine Standards rechtzeitig lieferte, sondern teilweise erst mit mehrjähriger Verspätung nach Einführung eines neuen Features in PHP, kam es dazu, dass sich der [Codierungsstandard |contributing:coding-standard] in einigen Kleinigkeiten unterscheidet. Der größere Unterschied ist nur die Verwendung von Tabulatoren anstelle von Leerzeichen. Wir wissen, dass die Verwendung von Tabulatoren in unseren Projekten die Anpassung der Breite ermöglicht, was für [Menschen mit Sehbehinderungen unerlässlich ist |contributing:coding-standard#Tabulatoren statt Leerzeichen]. Ein Beispiel für einen kleinen Unterschied ist die Platzierung der geschweiften Klammer auf einer separaten Zeile bei Funktionen und Methoden, und zwar immer. Die Empfehlung von PSR erscheint uns unlogisch und führt zu einer [Verringerung der Code-Übersichtlichkeit |contributing:coding-standard#Umbrüche und Klammern]. + -Typen .[#toc-types] -------------------- +Typen +----- -Jeder Typ oder Gewerkschafts-/Schnittstellentyp kann als String übergeben werden, für native Typen können Sie auch vordefinierte Konstanten verwenden: +Jeder Typ oder Union-/Intersection-Typ kann als String übergeben werden, Sie können auch vordefinierte Konstanten für native Typen verwenden: ```php use Nette\PhpGenerator\Type; $member->setType('array'); // oder Type::Array; -$member->setType('array|string'); // oder Type::union('array', 'string') +$member->setType('?array'); // oder Type::nullable(Type::Array); +$member->setType('array|string'); // oder Type::union(Type::Array, Type::String) $member->setType('Foo&Bar'); // oder Type::intersection(Foo::class, Bar::class) -$member->setType(null); // Entfernt Typ +$member->setType(null); // entfernt den Typ ``` -Das gleiche gilt für die Methode `setReturnType()`. +Dasselbe gilt für die Methode `setReturnType()`. -Wörterbücher .[#toc-literals] ------------------------------ +Literale +-------- -Mit `Literal` können Sie beliebigen PHP-Code an z.B. Standard-Eigenschafts- oder Parameterwerte etc. übergeben: +Mit `Literal` können Sie beliebigen PHP-Code übergeben, zum Beispiel für Standardwerte von Eigenschaften oder Parametern usw.: ```php use Nette\PhpGenerator\Literal; @@ -545,25 +571,37 @@ class Demo } ``` -Sie können auch Parameter an `Literal` übergeben und diese mit Hilfe [spezieller Platzhalter |#method-and-function-body-generator] in gültigen PHP-Code umwandeln lassen: +Sie können auch Parameter an `Literal` übergeben und sie mithilfe von [Platzhaltern |#Körper von Methoden und Funktionen] in gültigen PHP-Code formatieren lassen: ```php new Literal('substr(?, ?)', [$a, $b]); -// erzeugt zum Beispiel: substr('hallo', 5); +// generiert beispielsweise: substr('hello', 5); ``` +Ein Literal, das die Erstellung eines neuen Objekts darstellt, kann einfach mit der Methode `new` generiert werden: + +```php +Literal::new(Demo::class, [$a, 'foo' => $b]); +// generiert beispielsweise: new Demo(10, foo: 20) +``` -Attribute .[#toc-attributes] ----------------------------- -Sie können PHP 8 Attribute zu allen Klassen, Methoden, Eigenschaften, Konstanten, Enum-Fällen, Funktionen, Closures und Parametern hinzufügen. [Literale |#Literals] können auch als Parameterwerte verwendet werden. +Attribute +--------- + +PHP 8-Attribute können zu allen Klassen, Methoden, Eigenschaften, Konstanten, Enums, Funktionen, Closures und Parametern hinzugefügt werden. Als Parameterwerte können auch [#Literale] verwendet werden. ```php $class = new Nette\PhpGenerator\ClassType('Demo'); -$class->addAttribute('Deprecated'); +$class->addAttribute('Table', [ + 'name' => 'user', + 'constraints' => [ + Literal::new('UniqueConstraint', ['name' => 'ean', 'columns' => ['ean']]), + ], +]); $class->addProperty('list') - ->addAttribute('WithArguments', [1, 2]); + ->addAttribute('Deprecated'); $method = $class->addMethod('count') ->addAttribute('Foo\Cached', ['mode' => true]); @@ -577,25 +615,109 @@ echo $class; Ergebnis: ```php -#[Deprecated] +#[Table(name: 'user', constraints: [new UniqueConstraint(name: 'ean', columns: ['ean'])])] class Demo { - #[WithArguments(1, 2)] + #[Deprecated] public $list; #[Foo\Cached(mode: true)] - public function count(#[Bar] $items) - { + public function count( + #[Bar] + $items, + ) { } } ``` -Namensraum .[#toc-namespace] ----------------------------- +Property Hooks +-------------- -Klassen, Traits, Schnittstellen und Enums (im Folgenden Klassen) können in Namespaces ([PhpNamespace |api:Nette\PhpGenerator\PhpNamespace]) gruppiert werden: +Mit Property Hooks (repräsentiert durch die Klasse [PropertyHook|api:Nette\PhpGenerator\PropertyHook]) können Sie Get- und Set-Operationen für Eigenschaften definieren, eine Funktion, die in PHP 8.4 eingeführt wurde: + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$prop = $class->addProperty('firstName') + ->setType('string'); + +$prop->addHook('set', 'strtolower($value)') + ->addParameter('value') + ->setType('string'); + +$prop->addHook('get') + ->setBody('return ucfirst($this->firstName);'); + +echo $class; +``` + +Generiert: + +```php +class Demo +{ + public string $firstName { + set(string $value) => strtolower($value); + get { + return ucfirst($this->firstName); + } + } +} +``` + +Eigenschaften und Property Hooks können abstrakt oder final sein: + +```php +$class->addProperty('id') + ->setType('int') + ->addHook('get') + ->setAbstract(); + +$class->addProperty('role') + ->setType('string') + ->addHook('set', 'strtolower($value)') + ->setFinal(); +``` + + +Asymmetrische Sichtbarkeit +-------------------------- + +PHP 8.4 führt asymmetrische Sichtbarkeit für Eigenschaften ein. Sie können unterschiedliche Zugriffsebenen für Lesen und Schreiben festlegen. + +Die Sichtbarkeit kann entweder mit der Methode `setVisibility()` mit zwei Parametern oder mit `setPublic()`, `setProtected()` oder `setPrivate()` mit dem Parameter `mode` eingestellt werden, der angibt, ob sich die Sichtbarkeit auf das Lesen oder Schreiben der Eigenschaft bezieht. Der Standardmodus ist `'get'`. + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); + +$class->addProperty('name') + ->setType('string') + ->setVisibility('public', 'private'); // public zum Lesen, private zum Schreiben + +$class->addProperty('id') + ->setType('int') + ->setProtected('set'); // protected zum Schreiben + +echo $class; +``` + +Generiert: + +```php +class Demo +{ + public private(set) string $name; + + protected(set) int $id; +} +``` + + +Namensraum +---------- + +Klassen, Eigenschaften, Schnittstellen und Enums (im Folgenden Klassen genannt) können in Namensräumen gruppiert werden, die durch die Klasse [PhpNamespace |api:Nette\PhpGenerator\PhpNamespace] repräsentiert werden: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); @@ -605,14 +727,14 @@ $class = $namespace->addClass('Task'); $interface = $namespace->addInterface('Countable'); $trait = $namespace->addTrait('NameAware'); -// oder eine bestehende Klasse in den Namespace einfügen +// oder eine existierende Klasse in den Namespace einfügen $class = new Nette\PhpGenerator\ClassType('Task'); $namespace->add($class); ``` -Wenn die Klasse bereits existiert, wird eine Ausnahme geworfen. +Wenn die Klasse bereits existiert, wird eine Ausnahme ausgelöst. -Sie können Use-Statements definieren: +Sie können use-Klauseln definieren: ```php // use Http\Request; @@ -623,14 +745,14 @@ $namespace->addUse(Http\Request::class, 'HttpReq'); $namespace->addUseFunction('iter\range'); ``` -Um einen voll qualifizierten Klassen-, Funktions- oder Konstantennamen entsprechend den definierten Aliases zu vereinfachen, verwenden Sie die Methode `simplifyName`: +Um den vollqualifizierten Namen einer Klasse, Funktion oder Konstante gemäß den definierten Aliasen zu vereinfachen, verwenden Sie die Methode `simplifyName`: ```php -echo $namespace->simplifyName('Foo\Bar'); // 'Bar', weil 'Foo' der aktuelle Namensraum ist -echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', wegen der definierten Use-Anweisung +echo $namespace->simplifyName('Foo\Bar'); // 'Bar', da 'Foo' der aktuelle Namespace ist +echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', aufgrund des definierten use-statements ``` -Umgekehrt können Sie einen vereinfachten Klassen-, Funktions- oder Konstantennamen mit der Methode `resolveName` in einen voll qualifizierten Namen umwandeln: +Den vereinfachten Namen einer Klasse, Funktion oder Konstante können Sie umgekehrt mit der Methode `resolveName` in den vollqualifizierten Namen umwandeln: ```php echo $namespace->resolveName('Bar'); // 'Foo\Bar' @@ -638,12 +760,10 @@ echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range' ``` -Auflösen von Klassennamen .[#toc-class-names-resolving] -------------------------------------------------------- +Übersetzungen von Klassennamen +------------------------------ -**Wenn die Klasse Teil des Namespaces ist, wird sie etwas anders dargestellt**: alle Typen (d.h. Typ-Hinweise, Rückgabetypen, Name der Elternklasse, -implementierte Schnittstellen, verwendete Traits und Attribute) werden automatisch *aufgelöst* (es sei denn, Sie schalten dies aus, siehe unten). -Das bedeutet, dass Sie **vollständige Klassennamen** in Definitionen verwenden müssen und diese im resultierenden Code durch Aliasnamen (entsprechend den Verwendungsbestimmungen) oder voll qualifizierte Namen ersetzt werden: +**Wenn eine Klasse Teil eines Namensraums ist, wird sie leicht unterschiedlich gerendert:** Alle Typen (z. B. Typehints, Rückgabetypen, Name der Elternklasse, implementierte Schnittstellen, verwendete Traits und Attribute) werden automatisch *aufgelöst* (wenn Sie dies nicht deaktivieren, siehe unten). Das bedeutet, dass Sie in den Definitionen **vollständige Klassennamen verwenden müssen**, und diese werden im resultierenden Code durch Aliase (gemäß den use-Klauseln) oder durch vollqualifizierte Namen ersetzt: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); @@ -651,16 +771,16 @@ $namespace->addUse('Bar\AliasedClass'); $class = $namespace->addClass('Demo'); $class->addImplement('Foo\A') // wird zu A vereinfacht - ->addTrait('Bar\AliasedClass'); // es wird zu AliasedClass vereinfacht + ->addTrait('Bar\AliasedClass'); // wird zu AliasedClass vereinfacht $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // in Kommentaren manuell vereinfachen +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // in Kommentaren vereinfachen wir manuell $method->addParameter('arg') - ->setType('Bar\OtherClass'); // es wird in \Bar\OtherClass aufgelöst + ->setType('Bar\OtherClass'); // wird zu \Bar\OtherClass aufgelöst echo $namespace; -// oder verwenden Sie PsrPrinter für eine PSR-2 / PSR-12 konforme Ausgabe +// oder verwenden Sie PsrPrinter für die Ausgabe gemäß PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -684,23 +804,23 @@ class Demo implements A } ``` -Die automatische Auflösung kann auf diese Weise ausgeschaltet werden: +Die automatische Auflösung kann wie folgt deaktiviert werden: ```php -$printer = new Nette\PhpGenerator\Printer; // oder PsrDrucker +$printer = new Nette\PhpGenerator\Printer; // oder PsrPrinter $printer->setTypeResolving(false); echo $printer->printNamespace($namespace); ``` -PHP-Dateien .[#toc-php-files] ------------------------------ +PHP-Dateien +----------- -Klassen, Funktionen und Namespaces können in PHP-Dateien gruppiert werden, die durch die Klasse [PhpFile |api:Nette\PhpGenerator\PhpFile] repräsentiert werden: +Klassen, Funktionen und Namensräume können in PHP-Dateien gruppiert werden, die durch die Klasse [PhpFile|api:Nette\PhpGenerator\PhpFile] repräsentiert werden: ```php $file = new Nette\PhpGenerator\PhpFile; -$file->addComment('This file is auto-generated.'); +$file->addComment('Diese Datei wurde automatisch generiert.'); $file->setStrictTypes(); // fügt declare(strict_types=1) hinzu $class = $file->addClass('Foo\A'); @@ -713,7 +833,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// oder PsrPrinter für PSR-2 / PSR-12 konforme Ausgabe verwenden +// oder verwenden Sie PsrPrinter für die Ausgabe gemäß PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -723,7 +843,7 @@ Ergebnis: <?php /** - * This file is auto-generated. + * Diese Datei wurde automatisch generiert. */ declare(strict_types=1); @@ -739,27 +859,28 @@ function foo() } ``` +**Warnung:** Es ist nicht möglich, weiteren Code außerhalb von Funktionen und Klassen zu Dateien hinzuzufügen. -Generierung nach vorhandenen Elementen .[#toc-generating-according-to-existing-ones] ------------------------------------------------------------------------------------- -Neben der Möglichkeit, Klassen und Funktionen mit Hilfe der oben beschriebenen API zu modellieren, können Sie diese auch automatisch anhand vorhandener Klassen und Funktionen generieren lassen: +Generierung nach bestehenden Mustern +------------------------------------ + +Neben der Modellierung von Klassen und Funktionen mit der oben beschriebenen API können Sie diese auch automatisch nach bestehenden Mustern generieren lassen: ```php -// erstellt eine Klasse, die mit der PDO-Klasse identisch ist +// erstellt eine Klasse, die identisch mit der PDO-Klasse ist $class = Nette\PhpGenerator\ClassType::from(PDO::class); -// erstellt eine Funktion, die mit trim() identisch ist +// erstellt eine Funktion, die identisch mit der trim()-Funktion ist $function = Nette\PhpGenerator\GlobalFunction::from('trim'); -// erstellt eine Schließung wie angegeben +// erstellt eine Closure nach der angegebenen $closure = Nette\PhpGenerator\Closure::from( function (stdClass $a, $b = null) {}, ); ``` -Funktions- und Methodenkörper sind standardmäßig leer. Wenn Sie diese auch laden wollen, verwenden Sie diesen Weg -(dafür muss `nikic/php-parser` installiert sein): +Die Körper von Funktionen und Methoden sind standardmäßig leer. Wenn Sie sie ebenfalls laden möchten, verwenden Sie diese Methode (erfordert die Installation des Pakets `nikic/php-parser`): ```php $class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true); @@ -768,10 +889,10 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); ``` -Laden aus PHP-Datei .[#toc-loading-from-php-file] -------------------------------------------------- +Laden aus PHP-Dateien +--------------------- -Sie können auch Funktionen, Klassen, Schnittstellen und Enums direkt aus einem String von PHP-Code laden. Zum Beispiel erstellen wir das Objekt `ClassType` auf diese Weise: +Funktionen, Klassen, Schnittstellen und Enums können auch direkt aus einem String geladen werden, der PHP-Code enthält. Zum Beispiel erstellen wir so ein `ClassType`-Objekt: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -784,39 +905,69 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -Beim Laden von Klassen aus PHP-Code werden einzeilige Kommentare außerhalb von Methodenkörpern ignoriert (z. B. für Eigenschaften usw.), da diese Bibliothek keine API hat, um mit ihnen zu arbeiten. +Beim Laden von Klassen aus PHP-Code werden einzeilige Kommentare außerhalb von Methodenkörpern ignoriert (z. B. bei Properties usw.), da diese Bibliothek keine API zur Bearbeitung hat. -Sie können auch die gesamte PHP-Datei direkt laden, die eine beliebige Anzahl von Klassen, Funktionen oder sogar mehrere Namespaces enthalten kann: +Sie können auch direkt eine gesamte PHP-Datei laden, die eine beliebige Anzahl von Klassen, Funktionen oder sogar Namensräumen enthalten kann: ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` -Der anfängliche Dateikommentar und die Erklärung `strict_types` werden ebenfalls geladen. Alle anderen globalen Codes werden dagegen ignoriert. +Der einleitende Kommentar der Datei und die `strict_types`-Deklaration werden ebenfalls geladen. Jeglicher andere globale Code wird hingegen ignoriert. -Dazu muss `nikic/php-parser` installiert sein. +Es ist erforderlich, dass `nikic/php-parser` installiert ist. .[note] -Wenn Sie globalen Code in Dateien oder einzelne Anweisungen in Methodenkörpern manipulieren müssen, ist es besser, die Bibliothek `nikic/php-parser` direkt zu verwenden. +Wenn Sie globalen Code in Dateien oder einzelne Anweisungen in Methodenkörpern manipulieren müssen, ist es besser, direkt die Bibliothek `nikic/php-parser` zu verwenden. + + +Class Manipulator +----------------- +Die Klasse [ClassManipulator|api:Nette\PhpGenerator\ClassManipulator] bietet Werkzeuge zur Manipulation von Klassen. -Variablen-Dumper .[#toc-variables-dumper] ------------------------------------------ +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$manipulator = new Nette\PhpGenerator\ClassManipulator($class); +``` -Der Dumper gibt eine parsbare PHP-String-Darstellung einer Variablen zurück. Bietet eine bessere und übersichtlichere Ausgabe als die native Funktion `var_export()`. +Die Methode `inheritMethod()` kopiert eine Methode aus einer Elternklasse oder einer implementierten Schnittstelle in Ihre Klasse. Dies ermöglicht es Ihnen, die Methode zu überschreiben oder ihre Signatur zu erweitern: + +```php +$method = $manipulator->inheritMethod('bar'); +$method->setBody('...'); +``` + +Die Methode `inheritProperty()` kopiert eine Eigenschaft aus einer Elternklasse in Ihre Klasse. Dies ist nützlich, wenn Sie dieselbe Eigenschaft in Ihrer Klasse haben möchten, aber vielleicht mit einem anderen Standardwert: + +```php +$property = $manipulator->inheritProperty('foo'); +$property->setValue('new value'); +``` + +Die Methode `implement()` implementiert automatisch alle Methoden und Eigenschaften aus einer gegebenen Schnittstelle oder abstrakten Klasse in Ihrer Klasse: + +```php +$manipulator->implement(SomeInterface::class); +// Jetzt implementiert Ihre Klasse SomeInterface und enthält alle seine Methoden +``` + + +Ausgabe von Variablen +--------------------- + +Die Klasse `Dumper` konvertiert eine Variable in parsbaren PHP-Code. Sie liefert eine bessere und übersichtlichere Ausgabe als die Standardfunktion `var_export()`. ```php $dumper = new Nette\PhpGenerator\Dumper; $var = ['a', 'b', 123]; -echo $dumper->dump($var); // druckt ['a', 'b', 123] +echo $dumper->dump($var); // gibt ['a', 'b', 123] aus ``` -Kompatibilitätstabelle .[#toc-compatibility-table] --------------------------------------------------- - -PhpGenerator 4.0 ist kompatibel mit PHP 8.0 bis 8.2 +Kompatibilitätstabelle +---------------------- -{{leftbar: nette:@menu-topics}} +PhpGenerator 4.1 ist kompatibel mit PHP 8.0 bis 8.4. diff --git a/php-generator/de/@meta.texy b/php-generator/de/@meta.texy new file mode 100644 index 0000000000..2cf383a5cf --- /dev/null +++ b/php-generator/de/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Dokumentation}} +{{leftbar: nette:@menu-topics}} diff --git a/php-generator/el/@home.texy b/php-generator/el/@home.texy index 8f03c11a08..83ac3e2cc4 100644 --- a/php-generator/el/@home.texy +++ b/php-generator/el/@home.texy @@ -1,31 +1,32 @@ -Γεννήτρια κώδικα PHP -******************** - -<div class=perex> -- Χρειάζεται να δημιουργήσετε κώδικα PHP για κλάσεις, συναρτήσεις, αρχεία PHP κ.λπ. -- Υποστηρίζει όλα τα τελευταία χαρακτηριστικά της PHP, όπως enums, attributes κ.λπ. -- Σας επιτρέπει να τροποποιείτε εύκολα τις υπάρχουσες κλάσεις -- Έξοδος συμβατή με το PSR-12 -- Εξαιρετικά ώριμη, σταθερή και ευρέως χρησιμοποιούμενη βιβλιοθήκη +Nette PhpGenerator +****************** + +<div class="perex"> +Ψάχνετε για ένα εργαλείο για την παραγωγή κώδικα PHP για κλάσεις, συναρτήσεις ή ολόκληρα αρχεία; + +- Υποστηρίζει όλα τα τελευταία χαρακτηριστικά της PHP (όπως property hooks, enums, attributes κ.λπ.) +- Σας επιτρέπει να τροποποιήσετε εύκολα υπάρχουσες κλάσεις +- Ο κώδικας εξόδου συμμορφώνεται με το PSR-12 / PER coding style +- Ώριμη, σταθερή και ευρέως χρησιμοποιούμενη βιβλιοθήκη </div> -Εγκατάσταση .[#toc-installation] --------------------------------- +Εγκατάσταση +----------- -Κατεβάστε και εγκαταστήστε το πακέτο χρησιμοποιώντας το [Composer |best-practices:composer]: +Μπορείτε να κατεβάσετε και να εγκαταστήσετε τη βιβλιοθήκη χρησιμοποιώντας το [Composer|best-practices:composer]: ```shell composer require nette/php-generator ``` -Για τη συμβατότητα με την PHP, δείτε τον [πίνακα |#Compatibility Table]. +Μπορείτε να βρείτε τη συμβατότητα με την PHP στον [πίνακα |#Πίνακας συμβατότητας]. -Κλάσεις .[#toc-classes] ------------------------ +Κλάσεις +------- -Ας ξεκινήσουμε με ένα απλό παράδειγμα δημιουργίας κλάσης με τη χρήση [του ClassType |api:Nette\PhpGenerator\ClassType]: +Ας ξεκινήσουμε με ένα παράδειγμα δημιουργίας μιας κλάσης χρησιμοποιώντας την [ClassType |api:Nette\PhpGenerator\ClassType]: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -34,19 +35,19 @@ $class ->setFinal() ->setExtends(ParentClass::class) ->addImplement(Countable::class) - ->addComment("Description of class.\nSecond line\n") + ->addComment("Περιγραφή κλάσης.\nΔεύτερη γραμμή\n") ->addComment('@property-read Nette\Forms\Form $form'); -// για να δημιουργήσετε κώδικα PHP, απλά μετατρέψτε τον σε συμβολοσειρά ή χρησιμοποιήστε το echo: +// μπορείτε εύκολα να παράγετε τον κώδικα κάνοντας cast σε string ή χρησιμοποιώντας το echo: echo $class; ``` -Θα αποδώσει αυτό το αποτέλεσμα: +Επιστρέφει το ακόλουθο αποτέλεσμα: ```php /** - * Description of class. - * Second line + * Περιγραφή κλάσης + * Δεύτερη γραμμή * * @property-read Nette\Forms\Form $form */ @@ -55,7 +56,7 @@ final class Demo extends ParentClass implements Countable } ``` -τον οποίο, σε αντίθεση με το `echo $class`, θα μπορούμε να [διαμορφώσουμε περαιτέρω |#Printers and PSR compliance]: +Μπορούμε επίσης να χρησιμοποιήσουμε έναν λεγόμενο printer για την παραγωγή κώδικα, τον οποίο, σε αντίθεση με το `echo $class`, θα μπορούμε να [διαμορφώσουμε περαιτέρω |#Printer και συμμόρφωση με το PSR]: ```php $printer = new Nette\PhpGenerator\Printer; @@ -66,7 +67,7 @@ echo $printer->printClass($class); ```php $class->addConstant('ID', 123) - ->setProtected() // σταθερή ορατότητα + ->setProtected() // ορατότητα σταθερών ->setType('int') ->setFinal(); @@ -80,10 +81,10 @@ $class->addProperty('list') ->setInitialized(); // εκτυπώνει '= null' ``` -Δημιουργεί: +Παράγει: ```php -final protected const int ID = 123, +final protected const int ID = 123; /** @var int[] */ private static $items = [1, 2, 3]; @@ -91,14 +92,14 @@ private static $items = [1, 2, 3]; public ?array $list = null; ``` -Και μπορούμε να προσθέσουμε [μεθόδους |#Method and Function Signature]: +Και μπορούμε να προσθέσουμε [μεθόδους |#Υπογραφές μεθόδων και συναρτήσεων]: ```php $method = $class->addMethod('count') ->addComment('Count it.') ->setFinal() ->setProtected() - ->setReturnType('?int') // τύπος επιστροφής της μεθόδου + ->setReturnType('?int') // τύποι επιστροφής για μεθόδους ->setBody('return count($items ?: $this->items);'); $method->addParameter('items', []) // $items = [] @@ -106,7 +107,7 @@ $method->addParameter('items', []) // $items = [] ->setType('array'); // array &$items = [] ``` -Αυτό έχει ως αποτέλεσμα: +Το αποτέλεσμα είναι: ```php /** @@ -118,7 +119,7 @@ final protected function count(array &$items = []): ?int } ``` -Οι προωθημένες παράμετροι που εισήχθησαν από την PHP 8.0 μπορούν να περάσουν στον κατασκευαστή: +Οι προωθημένες παράμετροι που εισήχθησαν στην PHP 8.0 μπορούν να περάσουν στον κατασκευαστή: ```php $method = $class->addMethod('__construct'); @@ -127,7 +128,7 @@ $method->addPromotedParameter('args', []) ->setPrivate(); ``` -Αυτό έχει ως αποτέλεσμα: +Το αποτέλεσμα είναι: ```php public function __construct( @@ -137,13 +138,13 @@ public function __construct( } ``` -Οι ιδιότητες και οι κλάσεις που είναι μόνο για ανάγνωση μπορούν να επισημανθούν μέσω της διεύθυνσης `setReadOnly()`. +Οι ιδιότητες και οι κλάσεις μόνο για ανάγνωση μπορούν να επισημανθούν χρησιμοποιώντας τη συνάρτηση `setReadOnly()`. ------ -Αν η προστιθέμενη ιδιότητα, σταθερά, μέθοδος ή παράμετρος υπάρχει ήδη, τότε θα εκπέμψει εξαίρεση. +Αν η ιδιότητα, η σταθερά, η μέθοδος ή η παράμετρος που προστίθεται ήδη υπάρχει, ρίχνεται μια εξαίρεση. -Τα μέλη μπορούν να αφαιρεθούν με τη χρήση `removeProperty()`, `removeConstant()`, `removeMethod()` ή `removeParameter()`. +Τα μέλη της κλάσης μπορούν να αφαιρεθούν χρησιμοποιώντας `removeProperty()`, `removeConstant()`, `removeMethod()` ή `removeParameter()`. Μπορείτε επίσης να προσθέσετε υπάρχοντα αντικείμενα `Method`, `Property` ή `Constant` στην κλάση: @@ -158,7 +159,7 @@ $class = (new Nette\PhpGenerator\ClassType('Demo')) ->addMember($const); ``` -Μπορείτε να κλωνοποιήσετε υπάρχουσες μεθόδους, ιδιότητες και σταθερές με διαφορετικό όνομα χρησιμοποιώντας το `cloneWithName()`: +Μπορείτε επίσης να κλωνοποιήσετε υπάρχουσες μεθόδους, ιδιότητες και σταθερές με διαφορετικό όνομα χρησιμοποιώντας το `cloneWithName()`: ```php $methodCount = $class->getMethod('count'); @@ -167,17 +168,17 @@ $class->addMember($methodRecount); ``` -Διεπαφή ή γνώρισμα .[#toc-interface-or-trait] ---------------------------------------------- +Interface ή trait +----------------- -Μπορείτε να δημιουργήσετε διασυνδέσεις και γνωρίσματα (κλάσεις [InterfaceType |api:Nette\PhpGenerator\InterfaceType] και [TraitType |api:Nette\PhpGenerator\TraitType]): +Μπορείτε να δημιουργήσετε interfaces και traits (κλάσεις [InterfaceType |api:Nette\PhpGenerator\InterfaceType] και [TraitType |api:Nette\PhpGenerator\TraitType]): ```php $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait'); ``` -Χρήση γνωρισμάτων: +Χρήση traits: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -202,10 +203,10 @@ class Demo ``` -Enums .[#toc-enums] -------------------- +Enums +----- -Μπορείτε εύκολα να δημιουργήσετε τα enums που φέρνει η PHP 8.1 (κλάση [EnumType |api:Nette\PhpGenerator\EnumType]): +Μπορείτε εύκολα να δημιουργήσετε enums, που εισήχθησαν στην PHP 8.1, ως εξής: (κλάση [EnumType |api:Nette\PhpGenerator\EnumType]): ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -229,20 +230,20 @@ enum Suit } ``` -Μπορείτε επίσης να ορίσετε κλιμακωτά ισοδύναμα για τις περιπτώσεις για να δημιουργήσετε ένα υποστηριζόμενο enum: +Μπορείτε επίσης να ορίσετε ισοδύναμα scalar και να δημιουργήσετε ένα "backed" enum: ```php $enum->addCase('Clubs', '♣'); $enum->addCase('Diamonds', '♦'); ``` -Είναι δυνατόν να προσθέσετε ένα σχόλιο ή [χαρακτηριστικά |#attributes] σε κάθε περίπτωση χρησιμοποιώντας το `addComment()` ή το `addAttribute()`. +Σε κάθε *case* είναι δυνατόν να προσθέσετε ένα σχόλιο ή [#attributes] χρησιμοποιώντας `addComment()` ή `addAttribute()`. -Ανώνυμη κλάση .[#toc-anonymous-class] -------------------------------------- +Ανώνυμες κλάσεις +---------------- -Δώστε το όνομα `null` και έχετε μια ανώνυμη κλάση: +Περνάμε `null` ως όνομα και έχουμε μια ανώνυμη κλάση: ```php $class = new Nette\PhpGenerator\ClassType(null); @@ -264,10 +265,10 @@ $obj = new class ($val) { ``` -Παγκόσμια Λειτουργία .[#toc-global-function] --------------------------------------------- +Καθολικές συναρτήσεις +--------------------- -Ο κώδικας των συναρτήσεων θα δημιουργήσει την κλάση [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction]: +Ο κώδικας των συναρτήσεων παράγεται από την κλάση [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction]: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -276,7 +277,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// ή χρησιμοποιήστε το PsrPrinter για έξοδο που συμμορφώνεται με PSR-2 / PSR-12 +// ή χρησιμοποιήστε το PsrPrinter για έξοδο συμβατή με PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -290,10 +291,10 @@ function foo($a, $b) ``` -Κλείσιμο .[#toc-closure] ------------------------- +Ανώνυμες συναρτήσεις +-------------------- -Ο κώδικας των closures θα δημιουργήσει την κλάση [Closure |api:Nette\PhpGenerator\Closure]: +Ο κώδικας των ανώνυμων συναρτήσεων παράγεται από την κλάση [Closure |api:Nette\PhpGenerator\Closure]: ```php $closure = new Nette\PhpGenerator\Closure; @@ -304,7 +305,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// ή χρησιμοποιήστε το PsrPrinter για έξοδο που συμμορφώνεται με PSR-2 / PSR-12 +// ή χρησιμοποιήστε το PsrPrinter για έξοδο συμβατή με PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -317,10 +318,10 @@ function ($a, $b) use (&$c) { ``` -Βέλος Συνάρτηση .[#toc-arrow-function] --------------------------------------- +Συντομευμένες συναρτήσεις βέλους +-------------------------------- -Μπορείτε επίσης να εκτυπώσετε το κλείσιμο ως λειτουργία βέλους χρησιμοποιώντας εκτυπωτή: +Μπορείτε επίσης να εκτυπώσετε μια συντομευμένη ανώνυμη συνάρτηση χρησιμοποιώντας τον printer: ```php $closure = new Nette\PhpGenerator\Closure; @@ -338,10 +339,10 @@ fn($a, $b) => $a + $b ``` -Υπογραφή μεθόδου και συνάρτησης .[#toc-method-and-function-signature] ---------------------------------------------------------------------- +Υπογραφές μεθόδων και συναρτήσεων +--------------------------------- -Οι μέθοδοι αντιπροσωπεύονται από την κλάση [Method |api:Nette\PhpGenerator\Method]. Μπορείτε να ορίσετε την ορατότητα, την τιμή επιστροφής, να προσθέσετε σχόλια, [χαρακτηριστικά |#Attributes] κ.λπ: +Οι μέθοδοι αντιπροσωπεύονται από την κλάση [Method |api:Nette\PhpGenerator\Method]. Μπορείτε να ορίσετε την ορατότητα, την τιμή επιστροφής, να προσθέσετε σχόλια, [#attributes] κ.λπ.: ```php $method = $class->addMethod('count') @@ -351,25 +352,25 @@ $method = $class->addMethod('count') ->setReturnType('?int'); ``` -Κάθε παράμετρος αναπαρίσταται από μια κλάση [Parameter |api:Nette\PhpGenerator\Parameter]. Και πάλι, μπορείτε να ορίσετε κάθε δυνατή ιδιότητα: +Οι μεμονωμένες παράμετροι αντιπροσωπεύονται από την κλάση [Parameter |api:Nette\PhpGenerator\Parameter]. Και πάλι, μπορείτε να ορίσετε όλες τις πιθανές ιδιότητες: ```php $method->addParameter('items', []) // $items = [] - ->setReference() // &$items = [] - ->setType('array'); // array &$items = [] + ->setReference() // &$items = [] + ->setType('array'); // array &$items = [] -// function count(&$items = []) +// function count(array &$items = []) ``` -Για να ορίσετε τις λεγόμενες παραμέτρους variadics (ή επίσης τον τελεστή splat, spread, ellipsis, unpacking ή three dots), χρησιμοποιήστε `setVariadics()`: +Για τον ορισμό των λεγόμενων variadic παραμέτρων (ή αλλιώς τελεστής splat) χρησιμοποιείται το `setVariadic()`: ```php $method = $class->addMethod('count'); -$method->setVariadics(true); +$method->setVariadic(true); $method->addParameter('items'); ``` -Generates: +Παράγει: ```php function count(...$items) @@ -378,10 +379,10 @@ function count(...$items) ``` -Σώμα μεθόδου και συνάρτησης .[#toc-method-and-function-body] ------------------------------------------------------------- +Σώματα μεθόδων και συναρτήσεων +------------------------------ -Το σώμα μπορεί να περάσει στη μέθοδο `setBody()` αμέσως ή διαδοχικά (γραμμή προς γραμμή) καλώντας επανειλημμένα τη μέθοδο `addBody()`: +Το σώμα μπορεί να περαστεί ολόκληρο στη μέθοδο `setBody()` ή διαδοχικά (ανά γραμμή) με επαναλαμβανόμενη κλήση της `addBody()`: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -390,7 +391,7 @@ $function->addBody('return $a;'); echo $function; ``` -Αποτέλεσµα +Αποτέλεσμα ```php function foo() @@ -400,9 +401,9 @@ function foo() } ``` -Μπορείτε να χρησιμοποιήσετε ειδικά placeholders για εύχρηστο τρόπο εισαγωγής μεταβλητών. +Μπορείτε να χρησιμοποιήσετε ειδικούς χαρακτήρες υποκατάστασης για εύκολη εισαγωγή μεταβλητών. -Απλά πλαίσια θέσης `?` +Απλοί χαρακτήρες υποκατάστασης `?` ```php $str = 'any string'; @@ -412,7 +413,7 @@ $function->addBody('return substr(?, ?);', [$str, $num]); echo $function; ``` -Αποτέλεσμα: +Αποτέλεσμα ```php function foo() @@ -421,7 +422,7 @@ function foo() } ``` -Variadic placeholder `...?` +Χαρακτήρας υποκατάστασης για variadic `...?` ```php $items = [1, 2, 3]; @@ -439,7 +440,7 @@ function foo() } ``` -Μπορείτε επίσης να χρησιμοποιήσετε ονομαστικές παραμέτρους PHP 8 χρησιμοποιώντας placeholder `...?:` +Μπορείτε επίσης να χρησιμοποιήσετε ονομασμένες παράμετροι για την PHP 8 χρησιμοποιώντας `...?:` ```php $items = ['foo' => 1, 'bar' => true]; @@ -448,7 +449,7 @@ $function->setBody('myfunc(...?:);', [$items]); // myfunc(foo: 1, bar: true); ``` -Αποφύγετε το placeholder χρησιμοποιώντας slash `\?` +Ο χαρακτήρας υποκατάστασης διαφεύγει με κάθετο `\?` ```php $num = 3; @@ -468,45 +469,70 @@ function foo($a) ``` -Εκτυπωτές και συμμόρφωση PSR .[#toc-printers-and-psr-compliance] ----------------------------------------------------------------- +Printer και συμμόρφωση με το PSR +-------------------------------- -Ο κώδικας PHP παράγεται από τα αντικείμενα `Printer`. Υπάρχει ένα `PsrPrinter` του οποίου η έξοδος συμμορφώνεται με τα PSR-2 και PSR-12 και χρησιμοποιεί κενά για εσοχή, και ένα `Printer` που χρησιμοποιεί tabs για εσοχή. +Για την παραγωγή κώδικα PHP χρησιμοποιείται η κλάση [Printer |api:Nette\PhpGenerator\Printer]: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // το ίδιο με: echo $class +``` + +Μπορεί να παράγει κώδικα για όλα τα άλλα στοιχεία, προσφέρει μεθόδους όπως `printFunction()`, `printNamespace()`, κ.λπ. + +Υπάρχει επίσης η κλάση `PsrPrinter`, της οποίας η έξοδος συμμορφώνεται με το PSR-2 / PSR-12 / PER coding style: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // 4 κενά εσοχή +echo $printer->printClass($class); ``` -Θέλετε να προσαρμόσετε τη συμπεριφορά του εκτυπωτή; Δημιουργήστε το δικό σας κληρονομώντας την κλάση `Printer`. Μπορείτε να επαναδιαμορφώσετε αυτές τις μεταβλητές: +Χρειάζεστε να προσαρμόσετε τη συμπεριφορά στις ανάγκες σας; Δημιουργήστε τη δική σας έκδοση κληρονομώντας την κλάση `Printer`. Μπορείτε να επαναδιαμορφώσετε αυτές τις ιδιότητες: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // μήκος γραμμής μετά το οποίο γίνεται αναδίπλωση γραμμής public int $wrapLength = 120; + // χαρακτήρας εσοχής, μπορεί να αντικατασταθεί από μια ακολουθία κενών public string $indentation = "\t"; + // αριθμός κενών γραμμών μεταξύ properties public int $linesBetweenProperties = 0; + // αριθμός κενών γραμμών μεταξύ μεθόδων public int $linesBetweenMethods = 2; + // αριθμός κενών γραμμών μεταξύ ομάδων 'use statements' για κλάσεις, συναρτήσεις και σταθερές public int $linesBetweenUseTypes = 0; + // θέση του αριστερού άγκιστρου για συναρτήσεις και μεθόδους public bool $bracesOnNextLine = true; + // τοποθετήστε μια παράμετρο ανά γραμμή, ακόμα κι αν έχει attribute ή είναι promoted + public bool $singleParameterOnOneLine = false; + // παραλείπει namespaces που δεν περιέχουν καμία κλάση ή συνάρτηση + public bool $omitEmptyNamespaces = true; + // διαχωριστικό μεταξύ της δεξιάς παρένθεσης και του τύπου επιστροφής συναρτήσεων και μεθόδων public string $returnTypeColon = ': '; } ``` +Πώς και γιατί διαφέρει ο τυπικός `Printer` από τον `PsrPrinter`? Γιατί δεν υπάρχει μόνο ένας printer στο πακέτο, και αυτός να είναι ο `PsrPrinter`? -Τύποι .[#toc-types] -------------------- +Ο τυπικός `Printer` μορφοποιεί τον κώδικα όπως το κάνουμε σε όλο το Nette. Επειδή το Nette δημιουργήθηκε πολύ νωρίτερα από το PSR, και επίσης επειδή το PSR για πολλά χρόνια δεν παρείχε πρότυπα έγκαιρα, αλλά ίσως με καθυστέρηση αρκετών ετών από την εισαγωγή ενός νέου χαρακτηριστικού στην PHP, συνέβη το [πρότυπο κωδικοποίησης |contributing:coding-standard] να διαφέρει σε μερικές λεπτομέρειες. Η μεγαλύτερη διαφορά είναι μόνο η χρήση tabs αντί για κενά. Γνωρίζουμε ότι η χρήση tabs στα έργα μας επιτρέπει την προσαρμογή του πλάτους, η οποία είναι [απαραίτητη για άτομα με προβλήματα όρασης |contributing:coding-standard#Tabulators αντί για Κενά]. Ένα παράδειγμα μικρής διαφοράς είναι η τοποθέτηση του άγκιστρου σε ξεχωριστή γραμμή για συναρτήσεις και μεθόδους, και αυτό πάντα. Η σύσταση του PSR μας φαίνεται παράλογη και οδηγεί σε [μείωση της σαφήνειας του κώδικα |contributing:coding-standard#Αναδίπλωση και Άγκιστρα]. -Κάθε τύπος ή τύπος ένωσης/διασταύρωσης μπορεί να περάσει ως συμβολοσειρά, μπορείτε επίσης να χρησιμοποιήσετε προκαθορισμένες σταθερές για εγγενείς τύπους: + +Τύποι +----- + +Κάθε τύπος ή union/intersection τύπος μπορεί να περαστεί ως string, μπορείτε επίσης να χρησιμοποιήσετε προκαθορισμένες σταθερές για εγγενείς τύπους: ```php use Nette\PhpGenerator\Type; -$member->setType('array'); // ή Type::Array, -$member->setType('array|string'); // ή Type::union('array', 'string') +$member->setType('array'); // ή Type::Array; +$member->setType('?array'); // ή Type::nullable(Type::Array); +$member->setType('array|string'); // ή Type::union(Type::Array, Type::String) $member->setType('Foo&Bar'); // ή Type::intersection(Foo::class, Bar::class) $member->setType(null); // αφαιρεί τον τύπο ``` @@ -514,10 +540,10 @@ $member->setType(null); // αφαιρεί τον τύπο Το ίδιο ισχύει και για τη μέθοδο `setReturnType()`. -Κυριολεκτικά .[#toc-literals] ------------------------------ +Literals +-------- -Με το `Literal` μπορείτε να περάσετε αυθαίρετο κώδικα PHP, για παράδειγμα, σε προεπιλεγμένες τιμές ιδιοτήτων ή παραμέτρων κ.λπ: +Με το `Literal` μπορείτε να περάσετε οποιονδήποτε κώδικα PHP, για παράδειγμα για προεπιλεγμένες τιμές ιδιοτήτων ή παραμέτρων κ.λπ.: ```php use Nette\PhpGenerator\Literal; @@ -545,25 +571,37 @@ class Demo } ``` -Μπορείτε επίσης να περάσετε παραμέτρους στη διεύθυνση `Literal` και να τις μορφοποιήσετε σε έγκυρο κώδικα PHP χρησιμοποιώντας [ειδικούς συμπαραστάτες |#method-and-function-body-generator]: +Μπορείτε επίσης να περάσετε παραμέτρους στο `Literal` και να τις μορφοποιήσετε σε έγκυρο κώδικα PHP χρησιμοποιώντας [χαρακτήρες υποκατάστασης |#Σώματα μεθόδων και συναρτήσεων]: ```php new Literal('substr(?, ?)', [$a, $b]); -// παράγει, για παράδειγμα: substr('hello', 5); +// παράγει για παράδειγμα: substr('hello', 5); ``` +Ένα literal που αντιπροσωπεύει τη δημιουργία ενός νέου αντικειμένου μπορεί εύκολα να παραχθεί χρησιμοποιώντας τη μέθοδο `new`: -Χαρακτηριστικά .[#toc-attributes] ---------------------------------- +```php +Literal::new(Demo::class, [$a, 'foo' => $b]); +// παράγει για παράδειγμα: new Demo(10, foo: 20) +``` -Μπορείτε να προσθέσετε χαρακτηριστικά της PHP 8 σε όλες τις κλάσεις, μεθόδους, ιδιότητες, σταθερές, περιπτώσεις enum, συναρτήσεις, κλεισίματα και παραμέτρους. [Οι κυριολεκτικοί χαρακτήρες |#Literals] μπορούν επίσης να χρησιμοποιηθούν ως τιμές παραμέτρων. + +Attributes +---------- + +Μπορείτε να προσθέσετε attributes της PHP 8 σε όλες τις κλάσεις, μεθόδους, ιδιότητες, σταθερές, enums, συναρτήσεις, closures και παραμέτρους. Μπορείτε επίσης να χρησιμοποιήσετε [#literals] ως τιμές παραμέτρων. ```php $class = new Nette\PhpGenerator\ClassType('Demo'); -$class->addAttribute('Deprecated'); +$class->addAttribute('Table', [ + 'name' => 'user', + 'constraints' => [ + Literal::new('UniqueConstraint', ['name' => 'ean', 'columns' => ['ean']]), + ], +]); $class->addProperty('list') - ->addAttribute('WithArguments', [1, 2]); + ->addAttribute('Deprecated'); $method = $class->addMethod('count') ->addAttribute('Foo\Cached', ['mode' => true]); @@ -577,42 +615,126 @@ echo $class; Αποτέλεσμα: ```php -#[Deprecated] +#[Table(name: 'user', constraints: [new UniqueConstraint(name: 'ean', columns: ['ean'])])] class Demo { - #[WithArguments(1, 2)] + #[Deprecated] public $list; #[Foo\Cached(mode: true)] - public function count(#[Bar] $items) - { + public function count( + #[Bar] + $items, + ) { } } ``` -Χώρος ονομάτων .[#toc-namespace] --------------------------------- +Property Hooks +-------------- + +Χρησιμοποιώντας τα property hooks (που αντιπροσωπεύονται από την κλάση [PropertyHook|api:Nette\PhpGenerator\PropertyHook]), μπορείτε να ορίσετε τις λειτουργίες get και set για τις ιδιότητες, ένα χαρακτηριστικό που εισήχθη στην PHP 8.4: + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$prop = $class->addProperty('firstName') + ->setType('string'); + +$prop->addHook('set', 'strtolower($value)') + ->addParameter('value') + ->setType('string'); + +$prop->addHook('get') + ->setBody('return ucfirst($this->firstName);'); + +echo $class; +``` + +Παράγει: + +```php +class Demo +{ + public string $firstName { + set(string $value) => strtolower($value); + get { + return ucfirst($this->firstName); + } + } +} +``` + +Οι ιδιότητες και τα property hooks μπορούν να είναι abstract ή final: + +```php +$class->addProperty('id') + ->setType('int') + ->addHook('get') + ->setAbstract(); + +$class->addProperty('role') + ->setType('string') + ->addHook('set', 'strtolower($value)') + ->setFinal(); +``` + + +Ασύμμετρη ορατότητα +------------------- + +Η PHP 8.4 εισάγει την ασύμμετρη ορατότητα για τις ιδιότητες. Μπορείτε να ορίσετε διαφορετικά επίπεδα πρόσβασης για ανάγνωση και εγγραφή. + +Η ορατότητα μπορεί να οριστεί είτε με τη μέθοδο `setVisibility()` με δύο παραμέτρους, είτε με τις `setPublic()`, `setProtected()` ή `setPrivate()` με μια παράμετρο `mode` που καθορίζει αν η ορατότητα εφαρμόζεται στην ανάγνωση ή την εγγραφή της ιδιότητας. Η προεπιλεγμένη λειτουργία είναι `'get'`. + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); + +$class->addProperty('name') + ->setType('string') + ->setVisibility('public', 'private'); // public για ανάγνωση, private για εγγραφή + +$class->addProperty('id') + ->setType('int') + ->setProtected('set'); // protected για εγγραφή + +echo $class; +``` + +Παράγει: + +```php +class Demo +{ + public private(set) string $name; + + protected(set) int $id; +} +``` -Οι κλάσεις, τα γνωρίσματα, οι διεπαφές και τα enums (εφεξής κλάσεις) μπορούν να ομαδοποιηθούν σε χώρους ονομάτων ([PhpNamespace |api:Nette\PhpGenerator\PhpNamespace]): + +Namespace +--------- + +Οι κλάσεις, οι ιδιότητες, τα interfaces και τα enums (εφεξής κλάσεις) μπορούν να ομαδοποιηθούν σε namespaces που αντιπροσωπεύονται από την κλάση [PhpNamespace |api:Nette\PhpGenerator\PhpNamespace]: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); -// δημιουργία νέων κλάσεων στο χώρο ονομάτων +// δημιουργούμε νέες κλάσεις στο namespace $class = $namespace->addClass('Task'); $interface = $namespace->addInterface('Countable'); $trait = $namespace->addTrait('NameAware'); -// ή να εισαγάγετε μια υπάρχουσα κλάση στο χώρο ονομάτων +// ή εισάγουμε μια υπάρχουσα κλάση στο namespace $class = new Nette\PhpGenerator\ClassType('Task'); $namespace->add($class); ``` -Εάν η κλάση υπάρχει ήδη, πετάει εξαίρεση. +Αν η κλάση ήδη υπάρχει, ρίχνεται μια εξαίρεση. -Μπορείτε να ορίσετε δηλώσεις χρήσης: +Μπορείτε να ορίσετε use clauses: ```php // use Http\Request; @@ -623,14 +745,14 @@ $namespace->addUse(Http\Request::class, 'HttpReq'); $namespace->addUseFunction('iter\range'); ``` -Χρησιμοποιήστε τη μέθοδο `simplifyName` για να απλοποιήσετε ένα όνομα κλάσης, συνάρτησης ή σταθεράς με πλήρη προσόντα σύμφωνα με τα καθορισμένα ψευδώνυμα: +Για να απλοποιήσετε το πλήρως πιστοποιημένο όνομα μιας κλάσης, συνάρτησης ή σταθεράς σύμφωνα με τα ορισμένα ψευδώνυμα, χρησιμοποιήστε τη μέθοδο `simplifyName`: ```php -echo $namespace->simplifyName('Foo\Bar'); // 'Bar', επειδή το 'Foo' είναι ο τρέχων χώρος ονομάτων -echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', λόγω του καθορισμένου use-statement +echo $namespace->simplifyName('Foo\Bar'); // 'Bar', επειδή το 'Foo' είναι το τρέχον namespace +echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', λόγω του ορισμένου use-statement ``` -Αντίθετα, μπορείτε να μετατρέψετε ένα απλοποιημένο όνομα κλάσης, συνάρτησης ή σταθεράς σε όνομα με πλήρη προσόντα χρησιμοποιώντας τη μέθοδο `resolveName`: +Αντίθετα, μπορείτε να μετατρέψετε το απλοποιημένο όνομα μιας κλάσης, συνάρτησης ή σταθεράς στο πλήρως πιστοποιημένο όνομα χρησιμοποιώντας τη μέθοδο `resolveName`: ```php echo $namespace->resolveName('Bar'); // 'Foo\Bar' @@ -638,12 +760,10 @@ echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range' ``` -Επίλυση ονομάτων κλάσεων .[#toc-class-names-resolving] ------------------------------------------------------- +Μεταφράσεις ονομάτων κλάσεων +---------------------------- -**Όταν η κλάση είναι μέρος του χώρου ονομάτων, αποδίδεται ελαφρώς διαφορετικά**: όλοι οι τύποι (δηλαδή οι υποδείξεις τύπου, οι τύποι επιστροφής, το όνομα της γονικής κλάσης, -υλοποιημένες διεπαφές, χρησιμοποιούμενα γνωρίσματα και χαρακτηριστικά) *επιλύονται* αυτόματα (εκτός αν το απενεργοποιήσετε, βλ. παρακάτω). -Αυτό σημαίνει ότι πρέπει να **χρησιμοποιείτε πλήρη ονόματα κλάσεων** στους ορισμούς και αυτά θα αντικατασταθούν με ψευδώνυμα (σύμφωνα με τις δηλώσεις χρήσης) ή πλήρως προσδιορισμένα ονόματα στον κώδικα που θα προκύψει: +**Όταν μια κλάση είναι μέρος ενός namespace, αποδίδεται ελαφρώς διαφορετικά:** όλοι οι τύποι (για παράδειγμα, typehints, τιμές επιστροφής, όνομα γονικής κλάσης, υλοποιημένα interfaces, χρησιμοποιημένες ιδιότητες και attributes) μεταφράζονται αυτόματα (εκτός αν το απενεργοποιήσετε, δείτε παρακάτω). Αυτό σημαίνει ότι πρέπει να **χρησιμοποιείτε πλήρη ονόματα κλάσεων** στους ορισμούς και αυτά θα αντικατασταθούν με ψευδώνυμα (σύμφωνα με τις use clauses) ή με πλήρως πιστοποιημένα ονόματα στον τελικό κώδικα: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); @@ -654,13 +774,13 @@ $class->addImplement('Foo\A') // θα απλοποιηθεί σε A ->addTrait('Bar\AliasedClass'); // θα απλοποιηθεί σε AliasedClass $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // στα σχόλια απλοποιήστε χειροκίνητα +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // στα σχόλια, απλοποιούμε χειροκίνητα $method->addParameter('arg') - ->setType('Bar\OtherClass'); // θα επιλυθεί σε \Bar\OtherClass + ->setType('Bar\OtherClass'); // θα μεταφραστεί σε \Bar\OtherClass echo $namespace; -// ή χρήση PsrPrinter για έξοδο σύμφωνη με PSR-2 / PSR-12 +// ή χρησιμοποιήστε το PsrPrinter για έξοδο συμβατή με PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -684,7 +804,7 @@ class Demo implements A } ``` -Η αυτόματη ανάλυση μπορεί να απενεργοποιηθεί με αυτόν τον τρόπο: +Η αυτόματη μετάφραση μπορεί να απενεργοποιηθεί με αυτόν τον τρόπο: ```php $printer = new Nette\PhpGenerator\Printer; // ή PsrPrinter @@ -693,14 +813,14 @@ echo $printer->printNamespace($namespace); ``` -PHP Files .[#toc-php-files] ---------------------------- +Αρχεία PHP +---------- -Οι κλάσεις, οι συναρτήσεις και οι χώροι ονομάτων μπορούν να ομαδοποιηθούν σε αρχεία PHP που αντιπροσωπεύονται από την κλάση [PhpFile |api:Nette\PhpGenerator\PhpFile]: +Οι κλάσεις, οι συναρτήσεις και τα namespaces μπορούν να ομαδοποιηθούν σε αρχεία PHP που αντιπροσωπεύονται από την κλάση [PhpFile|api:Nette\PhpGenerator\PhpFile]: ```php $file = new Nette\PhpGenerator\PhpFile; -$file->addComment('This file is auto-generated.'); +$file->addComment('Αυτό το αρχείο παράγεται αυτόματα.'); $file->setStrictTypes(); // προσθέτει declare(strict_types=1) $class = $file->addClass('Foo\A'); @@ -713,7 +833,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// ή χρήση του PsrPrinter για έξοδο σύμφωνη με PSR-2 / PSR-12 +// ή χρησιμοποιήστε το PsrPrinter για έξοδο συμβατή με PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -723,7 +843,7 @@ echo $file; <?php /** - * This file is auto-generated. + * Αυτό το αρχείο παράγεται αυτόματα. */ declare(strict_types=1); @@ -739,27 +859,28 @@ function foo() } ``` +**Προειδοποίηση:** Δεν είναι δυνατόν να προσθέσετε επιπλέον κώδικα στα αρχεία εκτός από συναρτήσεις και κλάσεις. + -Δημιουργία σύμφωνα με τις υπάρχουσες .[#toc-generating-according-to-existing-ones] ----------------------------------------------------------------------------------- +Παραγωγή βάσει υπαρχόντων +------------------------- -Εκτός από τη δυνατότητα μοντελοποίησης κλάσεων και συναρτήσεων χρησιμοποιώντας το API που περιγράφηκε παραπάνω, μπορείτε επίσης να τις δημιουργήσετε αυτόματα με βάση τις υπάρχουσες: +Εκτός από τη δυνατότητα να μοντελοποιήσετε κλάσεις και συναρτήσεις χρησιμοποιώντας το API που περιγράφηκε παραπάνω, μπορείτε επίσης να τις παράγετε αυτόματα βάσει υπαρχόντων προτύπων: ```php -// δημιουργεί μια κλάση πανομοιότυπη με την κλάση PDO +// δημιουργεί μια κλάση ίδια με την κλάση PDO $class = Nette\PhpGenerator\ClassType::from(PDO::class); -// δημιουργεί μια συνάρτηση πανομοιότυπη με την trim() +// δημιουργεί μια συνάρτηση ίδια με τη συνάρτηση trim() $function = Nette\PhpGenerator\GlobalFunction::from('trim'); -// δημιουργεί ένα κλείσιμο όπως ορίζεται +// δημιουργεί ένα closure βάσει του παρεχόμενου $closure = Nette\PhpGenerator\Closure::from( function (stdClass $a, $b = null) {}, ); ``` -Τα σώματα συναρτήσεων και μεθόδων είναι κενά από προεπιλογή. Αν θέλετε να τα φορτώσετε και αυτά, χρησιμοποιήστε αυτόν τον τρόπο -(απαιτεί την εγκατάσταση του `nikic/php-parser` ): +Τα σώματα των συναρτήσεων και των μεθόδων είναι κενά από προεπιλογή. Αν θέλετε να τα φορτώσετε επίσης, χρησιμοποιήστε αυτόν τον τρόπο (απαιτεί την εγκατάσταση του πακέτου `nikic/php-parser`): ```php $class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true); @@ -768,10 +889,10 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); ``` -Φορτώνοντας από αρχείο PHP .[#toc-loading-from-php-file] --------------------------------------------------------- +Φόρτωση από αρχεία PHP +---------------------- -Μπορείτε επίσης να φορτώσετε συναρτήσεις, κλάσεις, διεπαφές και enums απευθείας από μια συμβολοσειρά κώδικα PHP. Για παράδειγμα, δημιουργούμε το αντικείμενο `ClassType` με αυτόν τον τρόπο: +Μπορείτε επίσης να φορτώνετε συναρτήσεις, κλάσεις, interfaces και enums απευθείας από ένα string που περιέχει κώδικα PHP. Για παράδειγμα, έτσι δημιουργούμε ένα αντικείμενο `ClassType`: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -784,26 +905,58 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -Όταν φορτώνετε κλάσεις από κώδικα PHP, τα σχόλια μιας γραμμής εκτός των σωμάτων των μεθόδων αγνοούνται (π.χ. για τις ιδιότητες κ.λπ.), επειδή αυτή η βιβλιοθήκη δεν διαθέτει API για να δουλέψει με αυτά. +Κατά τη φόρτωση κλάσεων από κώδικα PHP, τα σχόλια μιας γραμμής εκτός των σωμάτων των μεθόδων αγνοούνται (π.χ. σε properties κ.λπ.), καθώς αυτή η βιβλιοθήκη δεν διαθέτει API για την εργασία μαζί τους. -Μπορείτε επίσης να φορτώσετε απευθείας ολόκληρο το αρχείο PHP, το οποίο μπορεί να περιέχει οποιονδήποτε αριθμό κλάσεων, συναρτήσεων ή ακόμα και πολλαπλά namespaces: +Μπορείτε επίσης να φορτώσετε απευθείας ένα ολόκληρο αρχείο PHP, το οποίο μπορεί να περιέχει οποιονδήποτε αριθμό κλάσεων, συναρτήσεων ή ακόμα και namespaces: ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` -Το αρχικό σχόλιο του αρχείου και η δήλωση `strict_types` φορτώνονται επίσης. Από την άλλη πλευρά, όλος ο υπόλοιπος παγκόσμιος κώδικας αγνοείται. +Φορτώνεται επίσης το εισαγωγικό σχόλιο του αρχείου και η δήλωση `strict_types`. Αντίθετα, όλος ο υπόλοιπος καθολικός κώδικας αγνοείται. -Αυτό απαιτεί την εγκατάσταση του `nikic/php-parser`. +Απαιτείται να είναι εγκατεστημένο το `nikic/php-parser`. .[note] -Αν πρέπει να χειριστείτε συνολικό κώδικα σε αρχεία ή μεμονωμένες εντολές σε σώματα μεθόδων, είναι προτιμότερο να χρησιμοποιήσετε απευθείας τη βιβλιοθήκη `nikic/php-parser`. +Αν χρειάζεστε να χειριστείτε τον καθολικό κώδικα σε αρχεία ή τις μεμονωμένες εντολές στα σώματα των μεθόδων, είναι καλύτερο να χρησιμοποιήσετε απευθείας τη βιβλιοθήκη `nikic/php-parser`. + + +Class Manipulator +----------------- + +Η κλάση [ClassManipulator|api:Nette\PhpGenerator\ClassManipulator] παρέχει εργαλεία για τον χειρισμό κλάσεων. +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$manipulator = new Nette\PhpGenerator\ClassManipulator($class); +``` -Μεταβλητές Dumper .[#toc-variables-dumper] ------------------------------------------- +Η μέθοδος `inheritMethod()` αντιγράφει μια μέθοδο από τη γονική κλάση ή το υλοποιημένο interface στην κλάση σας. Αυτό σας επιτρέπει να αντικαταστήσετε τη μέθοδο ή να επεκτείνετε την signature της: + +```php +$method = $manipulator->inheritMethod('bar'); +$method->setBody('...'); +``` -Ο Dumper επιστρέφει μια αναλύσιμη αναπαράσταση συμβολοσειράς PHP μιας μεταβλητής. Παρέχει καλύτερη και σαφέστερη έξοδο από την εγγενή συνάρτηση `var_export()`. +Η μέθοδος `inheritProperty()` αντιγράφει μια ιδιότητα από τη γονική κλάση στην κλάση σας. Είναι χρήσιμο όταν θέλετε να έχετε την ίδια ιδιότητα στην κλάση σας, αλλά ίσως με διαφορετική προεπιλεγμένη τιμή: + +```php +$property = $manipulator->inheritProperty('foo'); +$property->setValue('new value'); +``` + +Η μέθοδος `implement()` υλοποιεί αυτόματα όλες τις μεθόδους και τις ιδιότητες από το δοθέν interface ή την abstract κλάση στην κλάση σας: + +```php +$manipulator->implement(SomeInterface::class); +// Τώρα η κλάση σας υλοποιεί το SomeInterface και περιέχει όλες τις μεθόδους του +``` + + +Έξοδος μεταβλητών +----------------- + +Η κλάση `Dumper` μετατρέπει μια μεταβλητή σε αναλύσιμο κώδικα PHP. Παρέχει καλύτερη και σαφέστερη έξοδο από την τυπική συνάρτηση `var_export()`. ```php $dumper = new Nette\PhpGenerator\Dumper; @@ -814,9 +967,7 @@ echo $dumper->dump($var); // εκτυπώνει ['a', 'b', 123] ``` -Πίνακας συμβατότητας .[#toc-compatibility-table] ------------------------------------------------- - -Το PhpGenerator 4.0 είναι συμβατό με την PHP 8.0 έως 8.2 +Πίνακας συμβατότητας +-------------------- -{{leftbar: nette:@menu-topics}} +Το PhpGenerator 4.1 είναι συμβατό με PHP 8.0 έως 8.4. diff --git a/php-generator/el/@meta.texy b/php-generator/el/@meta.texy new file mode 100644 index 0000000000..a09ce5fe0d --- /dev/null +++ b/php-generator/el/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Τεκμηρίωση}} +{{leftbar: nette:@menu-topics}} diff --git a/php-generator/en/@home.texy b/php-generator/en/@home.texy index 8b2bc0e7e7..219c18eafe 100644 --- a/php-generator/en/@home.texy +++ b/php-generator/en/@home.texy @@ -1,31 +1,32 @@ -PHP Code Generator +Nette PhpGenerator ****************** <div class=perex> -- Need to generate PHP code for classes, functions, PHP files, etc.? -- Supports all the latest PHP features like enums, attributes, etc. +Are you looking for a tool to generate PHP code for classes, functions, or complete files? + +- Supports all the latest PHP features (like property hooks, enums, attributes, etc.) - Allows you to easily modify existing classes -- PSR-12 compliant output -- Highly mature, stable, and widely used library +- Output compliant with PSR-12 / PER coding style +- Mature, stable, and widely used library </div> Installation ------------ -Download and install the package using [Composer|best-practices:composer]: +Download and install the library using the [Composer|best-practices:composer] tool: ```shell composer require nette/php-generator ``` -For PHP compatibility, see the [table |#Compatibility Table]. +For PHP compatibility, see the [#compatibility table]. Classes ------- -Let's start with a straightforward example of generating class using [ClassType |api:Nette\PhpGenerator\ClassType]: +Let's start with an example of creating a class using [ClassType |api:Nette\PhpGenerator\ClassType]: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -34,18 +35,18 @@ $class ->setFinal() ->setExtends(ParentClass::class) ->addImplement(Countable::class) - ->addComment("Description of class.\nSecond line\n") + ->addComment("Class description.\nSecond line\n") ->addComment('@property-read Nette\Forms\Form $form'); -// to generate PHP code simply cast to string or use echo: +// generate code simply by typecasting to string or using echo: echo $class; ``` -It will render this result: +This returns the following result: ```php /** - * Description of class. + * Class description * Second line * * @property-read Nette\Forms\Form $form @@ -55,18 +56,18 @@ final class Demo extends ParentClass implements Countable } ``` -We can also use a printer to generate the code, which, unlike `echo $class`, we will be able to [further configure |#Printers and PSR compliance]: +To generate the code, you can also use a printer, which, unlike `echo $class`, can be [further configured |#Printer and PSR Compliance]: ```php $printer = new Nette\PhpGenerator\Printer; echo $printer->printClass($class); ``` -We can add constants (class [Constant |api:Nette\PhpGenerator\Constant]) and properties (class [Property |api:Nette\PhpGenerator\Property]): +You can add constants (class [Constant |api:Nette\PhpGenerator\Constant]) and properties (class [Property |api:Nette\PhpGenerator\Property]): ```php $class->addConstant('ID', 123) - ->setProtected() // constant visiblity + ->setProtected() // constant visibility ->setType('int') ->setFinal(); @@ -77,10 +78,10 @@ $class->addProperty('items', [1, 2, 3]) $class->addProperty('list') ->setType('?array') - ->setInitialized(); // prints '= null' + ->setInitialized(); // outputs '= null' ``` -It generates: +This generates: ```php final protected const int ID = 123; @@ -91,14 +92,14 @@ private static $items = [1, 2, 3]; public ?array $list = null; ``` -And we can add [methods|#Method and Function Signature]: +And you can add [methods |#Method and Function Signatures]: ```php $method = $class->addMethod('count') ->addComment('Count it.') ->setFinal() ->setProtected() - ->setReturnType('?int') // method return type + ->setReturnType('?int') // return types for methods ->setBody('return count($items ?: $this->items);'); $method->addParameter('items', []) // $items = [] @@ -106,7 +107,7 @@ $method->addParameter('items', []) // $items = [] ->setType('array'); // array &$items = [] ``` -It results in: +The result is: ```php /** @@ -118,7 +119,7 @@ final protected function count(array &$items = []): ?int } ``` -Promoted parameters introduced by PHP 8.0 can be passed to the constructor: +Promoted parameters introduced in PHP 8.0 can be passed to the constructor: ```php $method = $class->addMethod('__construct'); @@ -127,7 +128,7 @@ $method->addPromotedParameter('args', []) ->setPrivate(); ``` -It results in: +The result is: ```php public function __construct( @@ -137,15 +138,15 @@ public function __construct( } ``` -Readonly properties and classes can be marked via `setReadOnly()`. +Readonly properties and classes can be marked using the `setReadOnly()` function. ------ -If the added property, constant, method or parameter already exist, it throws exception. +If an added property, constant, method, or parameter already exists, an exception is thrown. -Members can be removed using `removeProperty()`, `removeConstant()`, `removeMethod()` or `removeParameter()`. +Class members can be removed using `removeProperty()`, `removeConstant()`, `removeMethod()`, or `removeParameter()`. -You can also add existing `Method`, `Property` or `Constant` objects to the class: +You can also add existing `Method`, `Property`, or `Constant` objects to the class: ```php $method = new Nette\PhpGenerator\Method('getHandle'); @@ -158,7 +159,7 @@ $class = (new Nette\PhpGenerator\ClassType('Demo')) ->addMember($const); ``` -You can clone existing methods, properties and constants with a different name using `cloneWithName()`: +You can also clone existing methods, properties, and constants under a different name using `cloneWithName()`: ```php $methodCount = $class->getMethod('count'); @@ -167,8 +168,8 @@ $class->addMember($methodRecount); ``` -Interface or Trait ------------------- +Interfaces or Traits +-------------------- You can create interfaces and traits (classes [InterfaceType |api:Nette\PhpGenerator\InterfaceType] and [TraitType |api:Nette\PhpGenerator\TraitType]): @@ -177,7 +178,7 @@ $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait'); ``` -Using traits: +Using a trait: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -188,7 +189,7 @@ $class->addTrait('MyTrait') echo $class; ``` -Result: +The result is: ```php class Demo @@ -205,7 +206,7 @@ class Demo Enums ----- -You can easily create the enums that PHP 8.1 brings (class [EnumType |api:Nette\PhpGenerator\EnumType]): +You can easily create enums introduced in PHP 8.1 like this (class [EnumType |api:Nette\PhpGenerator\EnumType]): ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -217,7 +218,7 @@ $enum->addCase('Spades'); echo $enum; ``` -Result: +The result is: ```php enum Suit @@ -229,20 +230,20 @@ enum Suit } ``` -You can also define scalar equivalents for cases to create a backed enum: +You can also define scalar equivalents and create a backed enum: ```php $enum->addCase('Clubs', '♣'); $enum->addCase('Diamonds', '♦'); ``` -It is possible to add a comment or [#attributes] to each case using `addComment()` or `addAttribute()`. +For each case, you can add a comment or [#attributes] using `addComment()` or `addAttribute()`. -Anonymous Class ---------------- +Anonymous Classes +----------------- -Give `null` as the name and you have an anonymous class: +Pass `null` as the name, and you have an anonymous class: ```php $class = new Nette\PhpGenerator\ClassType(null); @@ -252,7 +253,7 @@ $class->addMethod('__construct') echo '$obj = new class ($val) ' . $class . ';'; ``` -Result: +The result is: ```php $obj = new class ($val) { @@ -264,10 +265,10 @@ $obj = new class ($val) { ``` -Global Function ---------------- +Global Functions +---------------- -Code of functions will generate class [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction]: +The code for global functions is generated by the class [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction]: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -276,11 +277,11 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// or use PsrPrinter for output conforming to PSR-2 / PSR-12 +// or use PsrPrinter for output compliant with PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` -Result: +The result is: ```php function foo($a, $b) @@ -290,10 +291,10 @@ function foo($a, $b) ``` -Closure -------- +Anonymous Functions +------------------- -Code of closures will generate class [Closure |api:Nette\PhpGenerator\Closure]: +The code for anonymous functions (closures) is generated by the class [Closure |api:Nette\PhpGenerator\Closure]: ```php $closure = new Nette\PhpGenerator\Closure; @@ -304,11 +305,11 @@ $closure->addUse('c') ->setReference(); echo $closure; -// or use PsrPrinter for output conforming to PSR-2 / PSR-12 +// or use PsrPrinter for output compliant with PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` -Result: +The result is: ```php function ($a, $b) use (&$c) { @@ -317,10 +318,10 @@ function ($a, $b) use (&$c) { ``` -Arrow Function --------------- +Short Arrow Functions +--------------------- -You can also print closure as arrow function using printer: +You can also output a short arrow function using the printer: ```php $closure = new Nette\PhpGenerator\Closure; @@ -331,17 +332,17 @@ $closure->addParameter('b'); echo (new Nette\PhpGenerator\Printer)->printArrowFunction($closure); ``` -Result: +The result is: ```php fn($a, $b) => $a + $b ``` -Method and Function Signature ------------------------------ +Method and Function Signatures +------------------------------ -Methods are represented by the class [Method |api:Nette\PhpGenerator\Method]. You can set visibility, return value, add comments, [attributes|#Attributes] etc: +Methods are represented by the class [Method |api:Nette\PhpGenerator\Method]. You can set visibility, return type, add comments, [#attributes], etc.: ```php $method = $class->addMethod('count') @@ -351,25 +352,25 @@ $method = $class->addMethod('count') ->setReturnType('?int'); ``` -Each parameter is represented by a class [Parameter |api:Nette\PhpGenerator\Parameter]. Again, you can set every conceivable property: +Individual parameters are represented by the class [Parameter |api:Nette\PhpGenerator\Parameter]. Again, you can set all conceivable properties: ```php $method->addParameter('items', []) // $items = [] - ->setReference() // &$items = [] - ->setType('array'); // array &$items = [] + ->setReference() // &$items = [] + ->setType('array'); // array &$items = [] -// function count(&$items = []) +// function count(array &$items = []) ``` -To define the so-called variadics parameters (or also the splat, spread, ellipsis, unpacking or three dots operator), use `setVariadics()`: +To define variadic parameters (also known as the splat operator), use `setVariadic()`: ```php $method = $class->addMethod('count'); -$method->setVariadics(true); +$method->setVariadic(true); $method->addParameter('items'); ``` -Generates: +This generates: ```php function count(...$items) @@ -378,10 +379,10 @@ function count(...$items) ``` -Method and Function Body ------------------------- +Method and Function Bodies +-------------------------- -The body can be passed to the `setBody()` method at once or sequentially (line by line) by repeatedly calling `addBody()`: +The body can be passed all at once to the `setBody()` method or gradually (line by line) by repeatedly calling `addBody()`: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -390,7 +391,7 @@ $function->addBody('return $a;'); echo $function; ``` -Result +The result is: ```php function foo() @@ -400,9 +401,9 @@ function foo() } ``` -You can use special placeholders for handy way to inject variables. +You can use special placeholders for easy variable insertion. -Simple placeholders `?` +Simple placeholders `?`: ```php $str = 'any string'; @@ -412,7 +413,7 @@ $function->addBody('return substr(?, ?);', [$str, $num]); echo $function; ``` -Result: +The result is: ```php function foo() @@ -421,7 +422,7 @@ function foo() } ``` -Variadic placeholder `...?` +Placeholder for variadic `...?`: ```php $items = [1, 2, 3]; @@ -430,7 +431,7 @@ $function->setBody('myfunc(...?);', [$items]); echo $function; ``` -Result: +The result is: ```php function foo() @@ -439,7 +440,7 @@ function foo() } ``` -You can also use PHP 8 named parameters using placeholder `...?:` +You can also use named parameters for PHP 8 with `...?:`: ```php $items = ['foo' => 1, 'bar' => true]; @@ -448,7 +449,7 @@ $function->setBody('myfunc(...?:);', [$items]); // myfunc(foo: 1, bar: true); ``` -Escape placeholder using slash `\?` +The placeholder is escaped with a backslash `\?`: ```php $num = 3; @@ -458,7 +459,7 @@ $function->addBody('return $a \? 10 : ?;', [$num]); echo $function; ``` -Result: +The result is: ```php function foo($a) @@ -468,56 +469,83 @@ function foo($a) ``` -Printers and PSR Compliance ---------------------------- +Printer and PSR Compliance +-------------------------- -PHP code is generated by `Printer` objects. There is a `PsrPrinter` whose output conforms to PSR-2 and PSR-12 and uses spaces for indentation, and a `Printer` that uses tabs for indentation. +The [Printer |api:Nette\PhpGenerator\Printer] class is used for generating PHP code: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // same as: echo $class +``` + +It can generate code for all other elements, offering methods like `printFunction()`, `printNamespace()`, etc. + +There's also the `PsrPrinter` class, whose output conforms to the PSR-2 / PSR-12 / PER coding style: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // 4 spaces indentation +echo $printer->printClass($class); ``` -Need to customize printer behavior? Create your own by inheriting the `Printer` class. You can reconfigure these variables: +Need to customize the behavior? Create your own version by inheriting the `Printer` class. You can reconfigure these variables: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // line length after which line wrapping occurs public int $wrapLength = 120; + // indentation character, can be replaced with a sequence of spaces public string $indentation = "\t"; + // number of blank lines between properties public int $linesBetweenProperties = 0; + // number of blank lines between methods public int $linesBetweenMethods = 2; + // number of blank lines between 'use statement' groups for classes, functions, and constants public int $linesBetweenUseTypes = 0; + // position of the opening curly brace for functions and methods public bool $bracesOnNextLine = true; + // place a single parameter on one line, even if it has an attribute or is promoted + public bool $singleParameterOnOneLine = false; + // omits namespaces that do not contain any class or function + public bool $omitEmptyNamespaces = true; + // places declare(strict_types) on the same line as <?php + public bool $declareOnOpenTag = false; + // separator between the right parenthesis and the return type of functions and methods public string $returnTypeColon = ': '; } ``` +How and why do the standard `Printer` and `PsrPrinter` actually differ? Why isn't there just one printer, `PsrPrinter`, in the package? + +The standard `Printer` formats code as we do throughout Nette. Because Nette was created much earlier than PSR, and also because PSR standards were often delivered late (sometimes years after a new PHP feature was introduced), the [Nette coding standard |contributing:coding-standard] differs in a few minor details. The main difference is the use of tabs instead of spaces. We know that using tabs in our projects allows for width customization, which is essential for [people with visual impairments |contributing:coding-standard#Tabs Instead of Spaces]. An example of a minor difference is placing the opening curly brace on a separate line for functions and methods, always. The PSR recommendation seems illogical to us and leads to [reduced code clarity |contributing:coding-standard#Wrapping and Braces]. + Types ----- -Each type or union/intersection type can be passed as a string, you can also use predefined constants for native types: +Every type or union/intersection type can be passed as a string; you can also use predefined constants for native types: ```php use Nette\PhpGenerator\Type; -$member->setType('array'); // or Type::Array; -$member->setType('array|string'); // or Type::union('array', 'string') +$member->setType('array'); // or Type::Array +$member->setType('?array'); // or Type::nullable(Type::Array) +$member->setType('array|string'); // or Type::union(Type::Array, Type::String) $member->setType('Foo&Bar'); // or Type::intersection(Foo::class, Bar::class) -$member->setType(null); // removes type +$member->setType(null); // removes the type ``` -The same applies to the method `setReturnType()`. +The same applies to the `setReturnType()` method. Literals -------- -With `Literal` you can pass arbitrary PHP code to, for example, default property or parameter values etc: +Using `Literal`, you can pass any PHP code, for example, for default values of properties or parameters: ```php use Nette\PhpGenerator\Literal; @@ -545,25 +573,37 @@ class Demo } ``` -You can also pass parameters to `Literal` and have it formatted into valid PHP code using [special placeholders|#method-and-function-body-generator]: +You can also pass parameters to `Literal` and have them formatted into valid PHP code using [placeholders |#Method and Function Bodies]: ```php new Literal('substr(?, ?)', [$a, $b]); -// generates, for example: substr('hello', 5); +// generates for example: substr('hello', 5) +``` + +A literal representing the creation of a new object can easily be generated using the `new` method: + +```php +Literal::new(Demo::class, [$a, 'foo' => $b]); +// generates for example: new Demo(10, foo: 20) ``` Attributes ---------- -You can add PHP 8 attributes to all classes, methods, properties, constants, enum cases, functions, closures and parameters. [#Literals] can also be used as parameter values. +PHP 8 attributes can be added to all classes, methods, properties, constants, enums, functions, closures, and parameters. [#Literals] can also be used as parameter values. ```php $class = new Nette\PhpGenerator\ClassType('Demo'); -$class->addAttribute('Deprecated'); +$class->addAttribute('Table', [ + 'name' => 'user', + 'constraints' => [ + Literal::new('UniqueConstraint', ['name' => 'ean', 'columns' => ['ean']]), + ], +]); $class->addProperty('list') - ->addAttribute('WithArguments', [1, 2]); + ->addAttribute('Deprecated'); $method = $class->addMethod('count') ->addAttribute('Foo\Cached', ['mode' => true]); @@ -577,25 +617,109 @@ echo $class; Result: ```php -#[Deprecated] +#[Table(name: 'user', constraints: [new UniqueConstraint(name: 'ean', columns: ['ean'])])] class Demo { - #[WithArguments(1, 2)] + #[Deprecated] public $list; #[Foo\Cached(mode: true)] - public function count(#[Bar] $items) - { + public function count( + #[Bar] + $items, + ) { } } ``` +Property Hooks +-------------- + +Using property hooks (represented by the [PropertyHook|api:Nette\PhpGenerator\PropertyHook] class), you can define get and set operations for properties, a feature introduced in PHP 8.4: + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$prop = $class->addProperty('firstName') + ->setType('string'); + +$prop->addHook('set', 'strtolower($value)') + ->addParameter('value') + ->setType('string'); + +$prop->addHook('get') + ->setBody('return ucfirst($this->firstName);'); + +echo $class; +``` + +This generates: + +```php +class Demo +{ + public string $firstName { + set(string $value) => strtolower($value); + get { + return ucfirst($this->firstName); + } + } +} +``` + +Properties and property hooks can be abstract or final: + +```php +$class->addProperty('id') + ->setType('int') + ->addHook('get') + ->setAbstract(); + +$class->addProperty('role') + ->setType('string') + ->addHook('set', 'strtolower($value)') + ->setFinal(); +``` + + +Asymmetric Visibility +--------------------- + +PHP 8.4 introduces asymmetric visibility for properties. You can set different access levels for reading and writing. + +Visibility can be set either using the `setVisibility()` method with two parameters, or using `setPublic()`, `setProtected()`, or `setPrivate()` with the `mode` parameter specifying whether the visibility applies to reading or writing the property. The default mode is `'get'`. + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); + +$class->addProperty('name') + ->setType('string') + ->setVisibility('public', 'private'); // public for read, private for write + +$class->addProperty('id') + ->setType('int') + ->setProtected('set'); // protected for write + +echo $class; +``` + +This generates: + +```php +class Demo +{ + public private(set) string $name; + + protected(set) int $id; +} +``` + + Namespace --------- -Classes, traits, interfaces and enums (hereinafter classes) can be grouped into namespaces ([PhpNamespace |api:Nette\PhpGenerator\PhpNamespace]): +Classes, traits, interfaces, and enums (hereafter referred to as classes) can be grouped into namespaces represented by the [PhpNamespace |api:Nette\PhpGenerator\PhpNamespace] class: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); @@ -605,14 +729,14 @@ $class = $namespace->addClass('Task'); $interface = $namespace->addInterface('Countable'); $trait = $namespace->addTrait('NameAware'); -// or insert an existing class into the namespace +// or insert an existing class or function into the namespace $class = new Nette\PhpGenerator\ClassType('Task'); $namespace->add($class); ``` -If the class already exists, it throws exception. +If a class with the same name already exists in the namespace, an exception is thrown. -You can define use-statements: +You can define use clauses: ```php // use Http\Request; @@ -623,14 +747,14 @@ $namespace->addUse(Http\Request::class, 'HttpReq'); $namespace->addUseFunction('iter\range'); ``` -To simplify a fully qualified class, function or constant name according to the defined aliases, use the `simplifyName` method: +To simplify a fully qualified class, function, or constant name based on defined aliases or the current namespace, use the `simplifyName` method: ```php -echo $namespace->simplifyName('Foo\Bar'); // 'Bar', because 'Foo' is current namespace -echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', because of the defined use-statement +echo $namespace->simplifyName('Foo\Bar'); // 'Bar', because 'Foo' is the current namespace +echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', due to the defined use-statement ``` -Conversely, you can convert a simplified class, function or constant name to a fully qualified one using the `resolveName` method: +Conversely, you can convert a simplified class, function, or constant name back to a fully qualified name using the `resolveName` method: ```php echo $namespace->resolveName('Bar'); // 'Foo\Bar' @@ -641,26 +765,24 @@ echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range' Class Names Resolving --------------------- -**When the class is part of the namespace, it is rendered slightly differently**: all types (ie. type hints, return types, parent class name, -implemented interfaces, used traits and attributes) are automatically *resolved* (unless you turn it off, see below). -It means that you have to **use full class names** in definitions and they will be replaced with aliases (according to the use-statements) or fully qualified names in the resulting code: +**When a class is part of a namespace, it's rendered slightly differently:** all types (e.g., type hints, return types, parent class name, implemented interfaces, used traits, and attributes) are automatically *resolved* (unless you disable it, see below). This means you must **use fully qualified class names** in definitions, and they will be replaced with aliases (based on use clauses) or simplified names (if in the same namespace) in the resulting code: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); $namespace->addUse('Bar\AliasedClass'); $class = $namespace->addClass('Demo'); -$class->addImplement('Foo\A') // it will simplify to A - ->addTrait('Bar\AliasedClass'); // it will simplify to AliasedClass +$class->addImplement('Foo\A') // will be simplified to A + ->addTrait('Bar\AliasedClass'); // will be simplified to AliasedClass $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // in comments simplify manually +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // we manually simplify in comments $method->addParameter('arg') - ->setType('Bar\OtherClass'); // it will resolve to \Bar\OtherClass + ->setType('Bar\OtherClass'); // will be translated to \Bar\OtherClass echo $namespace; -// or use PsrPrinter for output conforming to PSR-2 / PSR-12 +// or use PsrPrinter for output compliant with PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -684,7 +806,7 @@ class Demo implements A } ``` -Auto-resolving can be turned off this way: +Auto-resolving can be disabled like this: ```php $printer = new Nette\PhpGenerator\Printer; // or PsrPrinter @@ -696,7 +818,7 @@ echo $printer->printNamespace($namespace); PHP Files --------- -Classes, functions and namespaces can be grouped into PHP files represented by the class [PhpFile|api:Nette\PhpGenerator\PhpFile]: +Classes, functions, and namespaces can be grouped into PHP files represented by the [PhpFile|api:Nette\PhpGenerator\PhpFile] class: ```php $file = new Nette\PhpGenerator\PhpFile; @@ -713,21 +835,19 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// or use PsrPrinter for output conforming to PSR-2 / PSR-12 +// or use PsrPrinter for output compliant with PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` Result: ```php -<?php +<?php declare(strict_types=1); /** * This file is auto-generated. */ -declare(strict_types=1); - namespace Foo; class A @@ -739,27 +859,36 @@ function foo() } ``` +You can also insert existing class, function, and namespace objects into the file using the `add()` method: + +```php +$file = new Nette\PhpGenerator\PhpFile; +$class = new Nette\PhpGenerator\ClassType('Demo'); +$file->add($class); +``` + +**Please note:** No additional code (like `echo 'hello'`) can be added to the files outside of functions, classes, or namespaces. -Generating According to Existing Ones -------------------------------------- -In addition to being able to model classes and functions using the API described above, you can also have them automatically generated using existing ones: +Generating from Existing Elements +--------------------------------- + +Besides modeling classes and functions using the API described above, you can also have them automatically generated based on existing ones using reflection: ```php // creates a class identical to the PDO class $class = Nette\PhpGenerator\ClassType::from(PDO::class); -// creates a function identical to trim() +// creates a function identical to the trim() function $function = Nette\PhpGenerator\GlobalFunction::from('trim'); -// creates a closure as specified +// creates a closure based on the provided one $closure = Nette\PhpGenerator\Closure::from( function (stdClass $a, $b = null) {}, ); ``` -Function and method bodies are empty by default. If you want to load them as well, use this way -(it requires `nikic/php-parser` to be installed): +By default, function and method bodies are empty. If you also want to load them, use this method (requires the `nikic/php-parser` package to be installed): ```php $class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true); @@ -768,10 +897,10 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); ``` -Loading from PHP File ---------------------- +Loading from PHP Files +---------------------- -You can also load functions, classes, interfaces and enums directly from a string of PHP code. For example, we create `ClassType` object this way: +You can also load functions, classes, interfaces, and enums directly from a string containing PHP code. For example, to create a `ClassType` object: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -784,39 +913,69 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -When loading classes from PHP code, single line comments outside of method bodies are ignored (e.g. for properties, etc.) because this library does not have an API to work with them. +When loading classes from PHP code, single-line comments outside method bodies (e.g., for properties) are ignored, as this library doesn't have an API to work with them. -You can also load the entire PHP file directly, which can contain any number of classes, functions or even multiple namespaces: +You can also directly load an entire PHP file, which can contain any number of classes, functions, or even namespaces: ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` -The initial file comment and the `strict_types` declaration are also loaded. On the other hand, all other global code is ignored. +The file's initial comment and `strict_types` declaration are also loaded. However, all other global code is ignored. -This requires `nikic/php-parser` to be installed. +Requires `nikic/php-parser` to be installed. .[note] -If you need to manipulate global code in files or individual statements in method bodies, it is better to use the `nikic/php-parser` library directly. +If you need to manipulate global code in files or individual statements within method bodies, it's better to use the `nikic/php-parser` library directly. + + +Class Manipulator +----------------- + +The [ClassManipulator|api:Nette\PhpGenerator\ClassManipulator] class provides tools for manipulating classes. + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$manipulator = new Nette\PhpGenerator\ClassManipulator($class); +``` +The `inheritMethod()` method copies a method from a parent class or implemented interface into your class. This allows you to override the method or extend its signature: -Variables Dumper +```php +$method = $manipulator->inheritMethod('bar'); +$method->setBody('...'); +``` + +The `inheritProperty()` method copies a property from a parent class into your class. This is useful when you want to have the same property in your class, but possibly with a different default value: + +```php +$property = $manipulator->inheritProperty('foo'); +$property->setValue('new value'); +``` + +The `implement()` method automatically implements all methods and properties from the given interface or abstract class in your class: + +```php +$manipulator->implement(SomeInterface::class); +// Now your class implements SomeInterface and contains stubs for all its methods +``` + + +Variable Dumping ---------------- -The Dumper returns a parsable PHP string representation of a variable. Provides better and clearer output that native function `var_export()`. +The `Dumper` class converts a variable into parseable PHP code. It provides a better and clearer output than the standard `var_export()` function. ```php $dumper = new Nette\PhpGenerator\Dumper; $var = ['a', 'b', 123]; -echo $dumper->dump($var); // prints ['a', 'b', 123] +echo $dumper->dump($var); // outputs ['a', 'b', 123] ``` Compatibility Table ------------------- -PhpGenerator 4.0 is compatible with PHP 8.0 to 8.2 - -{{leftbar: nette:@menu-topics}} +PhpGenerator 4.1 is compatible with PHP 8.0 to 8.4. diff --git a/php-generator/en/@meta.texy b/php-generator/en/@meta.texy new file mode 100644 index 0000000000..91205786e5 --- /dev/null +++ b/php-generator/en/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Documentation}} +{{leftbar: nette:@menu-topics}} diff --git a/php-generator/es/@home.texy b/php-generator/es/@home.texy index d125d1f74f..c0f96d3e8e 100644 --- a/php-generator/es/@home.texy +++ b/php-generator/es/@home.texy @@ -1,31 +1,32 @@ -Generador de código PHP -*********************** - -<div class=perex> -- ¿Necesita generar código PHP para clases, funciones, archivos PHP, etc.? -- Soporta todas las últimas características de PHP como enums, atributos, etc. -- Le permite modificar fácilmente las clases existentes -- Salida compatible con PSR-12 -- Librería altamente madura, estable y ampliamente utilizada +Nette PhpGenerator +****************** + +<div class="perex"> +¿Busca una herramienta para generar código PHP para clases, funciones o archivos completos? + +- Admite todas las características más recientes de PHP (como property hooks, enums, atributos, etc.) +- Le permite modificar fácilmente clases existentes +- El código de salida cumple con el estilo de codificación PSR-12 / PER +- Biblioteca madura, estable y ampliamente utilizada </div> -Instalación .[#toc-installation] --------------------------------- +Instalación +----------- -Descargue e instale el paquete utilizando [Composer |best-practices:composer]: +Descargue e instale la biblioteca usando [Composer|best-practices:composer]: ```shell composer require nette/php-generator ``` -Para la compatibilidad con PHP, consulte la [tabla |#Compatibility Table]. +Puede encontrar la compatibilidad con PHP en la [tabla |#Tabla de compatibilidad]. -Clases .[#toc-classes] ----------------------- +Clases +------ -Empecemos con un ejemplo sencillo de generación de clases utilizando [ClassType |api:Nette\PhpGenerator\ClassType]: +Comencemos directamente con un ejemplo de creación de una clase usando [ClassType |api:Nette\PhpGenerator\ClassType]: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -34,19 +35,19 @@ $class ->setFinal() ->setExtends(ParentClass::class) ->addImplement(Countable::class) - ->addComment("Description of class.\nSecond line\n") + ->addComment("Descripción de la clase.\nSegunda línea\n") ->addComment('@property-read Nette\Forms\Form $form'); -// para generar código PHP simplemente cast to string o use echo: +// simplemente genere el código convirtiéndolo a cadena o usando echo: echo $class; ``` -Dará este resultado: +Devuelve el siguiente resultado: ```php /** - * Description of class. - * Second line + * Descripción de la clase + * Segunda línea * * @property-read Nette\Forms\Form $form */ @@ -55,18 +56,18 @@ final class Demo extends ParentClass implements Countable } ``` -También podemos utilizar una impresora para generar el código, que, a diferencia de `echo $class`, podremos [configurar posteriormente |#Printers and PSR compliance]: +También podemos usar el llamado printer para generar el código, que a diferencia de `echo $class`, podremos [configurar más |#Printer y conformidad con PSR]: ```php $printer = new Nette\PhpGenerator\Printer; echo $printer->printClass($class); ``` -Podemos añadir constantes (clase [Constant |api:Nette\PhpGenerator\Constant]) y propiedades (clase [Property |api:Nette\PhpGenerator\Property]): +Podemos agregar constantes (clase [Constant |api:Nette\PhpGenerator\Constant]) y propiedades (clase [Property |api:Nette\PhpGenerator\Property]): ```php $class->addConstant('ID', 123) - ->setProtected() // visibilidad constante + ->setProtected() // visibilidad de constantes ->setType('int') ->setFinal(); @@ -91,14 +92,14 @@ private static $items = [1, 2, 3]; public ?array $list = null; ``` -Y podemos añadir [métodos |#Method and Function Signature]: +Y podemos agregar [métodos |#Firmas de métodos y funciones]: ```php $method = $class->addMethod('count') ->addComment('Count it.') ->setFinal() ->setProtected() - ->setReturnType('?int') // método devolver tipo + ->setReturnType('?int') // tipos de retorno en métodos ->setBody('return count($items ?: $this->items);'); $method->addParameter('items', []) // $items = [] @@ -106,7 +107,7 @@ $method->addParameter('items', []) // $items = [] ->setType('array'); // array &$items = [] ``` -Resulta en: +El resultado es: ```php /** @@ -118,7 +119,7 @@ final protected function count(array &$items = []): ?int } ``` -Los parámetros promocionados introducidos por PHP 8.0 pueden pasarse al constructor: +Los parámetros promocionados introducidos en PHP 8.0 se pueden pasar al constructor: ```php $method = $class->addMethod('__construct'); @@ -127,7 +128,7 @@ $method->addPromotedParameter('args', []) ->setPrivate(); ``` -Esto resulta en: +El resultado es: ```php public function __construct( @@ -137,15 +138,15 @@ public function __construct( } ``` -Las propiedades y clases de sólo lectura pueden marcarse a través de `setReadOnly()`. +Las propiedades y clases de solo lectura se pueden marcar usando la función `setReadOnly()`. ------ -Si la propiedad, constante, método o parámetro añadido ya existe, lanza una excepción. +Si la propiedad, constante, método o parámetro agregado ya existe, se lanza una excepción. -Los miembros pueden eliminarse utilizando `removeProperty()`, `removeConstant()`, `removeMethod()` o `removeParameter()`. +Los miembros de la clase se pueden eliminar usando `removeProperty()`, `removeConstant()`, `removeMethod()` o `removeParameter()`. -También puede añadir objetos existentes `Method`, `Property` o `Constant` a la clase: +También puede agregar objetos `Method`, `Property` o `Constant` existentes a la clase: ```php $method = new Nette\PhpGenerator\Method('getHandle'); @@ -158,7 +159,7 @@ $class = (new Nette\PhpGenerator\ClassType('Demo')) ->addMember($const); ``` -Puede clonar métodos, propiedades y constantes existentes con un nombre diferente utilizando `cloneWithName()`: +También puede clonar métodos, propiedades y constantes existentes con un nombre diferente usando `cloneWithName()`: ```php $methodCount = $class->getMethod('count'); @@ -167,8 +168,8 @@ $class->addMember($methodRecount); ``` -Interfaz o Trait .[#toc-interface-or-trait] -------------------------------------------- +Interfaz o trait +---------------- Puede crear interfaces y traits (clases [InterfaceType |api:Nette\PhpGenerator\InterfaceType] y [TraitType |api:Nette\PhpGenerator\TraitType]): @@ -177,7 +178,7 @@ $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait'); ``` -Utilizar rasgos: +Uso de traits: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -202,10 +203,10 @@ class Demo ``` -Enums .[#toc-enums] -------------------- +Enums +----- -Puedes crear fácilmente los enums que trae PHP 8.1 (clase [EnumType |api:Nette\PhpGenerator\EnumType]): +Los enums, introducidos en PHP 8.1, se pueden crear fácilmente así: (clase [EnumType |api:Nette\PhpGenerator\EnumType]): ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -229,20 +230,20 @@ enum Suit } ``` -También puede definir equivalentes escalares para los casos para crear un enum respaldado: +También puede definir equivalentes escalares y crear así un enum "backed": ```php $enum->addCase('Clubs', '♣'); $enum->addCase('Diamonds', '♦'); ``` -Es posible añadir un comentario o [atributos |#attributes] a cada caso utilizando `addComment()` o `addAttribute()`. +A cada *case* se le puede agregar un comentario o [#atributos] usando `addComment()` o `addAttribute()`. -Clase anónima .[#toc-anonymous-class] -------------------------------------- +Clases anónimas +--------------- -Dale `null` como nombre y tendrás una clase anónima: +Pasamos `null` como nombre y tenemos una clase anónima: ```php $class = new Nette\PhpGenerator\ClassType(null); @@ -264,10 +265,10 @@ $obj = new class ($val) { ``` -Función global .[#toc-global-function] --------------------------------------- +Funciones globales +------------------ -El código de las funciones generará la clase [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction]: +El código de las funciones lo genera la clase [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction]: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -276,7 +277,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// o utilice PsrPrinter para una salida conforme a PSR-2 / PSR-12 +// o use PsrPrinter para salida conforme a PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -290,10 +291,10 @@ function foo($a, $b) ``` -Cierre .[#toc-closure] ----------------------- +Funciones anónimas +------------------ -El código de cierres generará la clase [Closure |api:Nette\PhpGenerator\Closure]: +El código de las funciones anónimas lo genera la clase [Closure |api:Nette\PhpGenerator\Closure]: ```php $closure = new Nette\PhpGenerator\Closure; @@ -304,7 +305,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// o utilice PsrPrinter para una salida conforme a PSR-2 / PSR-12 +// o use PsrPrinter para salida conforme a PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -317,10 +318,10 @@ function ($a, $b) use (&$c) { ``` -Función Flecha .[#toc-arrow-function] -------------------------------------- +Funciones flecha abreviadas +--------------------------- -También puede imprimir el cierre como función de flecha utilizando la impresora: +También puede imprimir una función anónima abreviada usando el printer: ```php $closure = new Nette\PhpGenerator\Closure; @@ -338,10 +339,10 @@ fn($a, $b) => $a + $b ``` -Firma de método y función .[#toc-method-and-function-signature] ---------------------------------------------------------------- +Firmas de métodos y funciones +----------------------------- -Los métodos están representados por la clase [Method |api:Nette\PhpGenerator\Method]. Se puede establecer la visibilidad, el valor de retorno, añadir comentarios, [atributos |#Attributes], etc: +Los métodos los representa la clase [Method |api:Nette\PhpGenerator\Method]. Puede establecer la visibilidad, el tipo de retorno, agregar comentarios, [#atributos], etc: ```php $method = $class->addMethod('count') @@ -351,21 +352,21 @@ $method = $class->addMethod('count') ->setReturnType('?int'); ``` -Cada parámetro está representado por una clase [Parámetro |api:Nette\PhpGenerator\Parameter]. Una vez más, puede establecer todas las propiedades imaginables: +Los parámetros individuales los representa la clase [Parameter |api:Nette\PhpGenerator\Parameter]. Nuevamente, puede establecer todas las propiedades imaginables: ```php $method->addParameter('items', []) // $items = [] - ->setReference() // &$items = [] - ->setType('array'); // array &$items = [] + ->setReference() // &$items = [] + ->setType('array'); // array &$items = [] -// function count(&$items = []) +// function count(array &$items = []) ``` -Para definir los llamados parámetros variádicos (o también el operador splat, spread, ellipsis, unpacking o tres puntos), utilice `setVariadics()`: +Para definir los llamados parámetros variádicos (o también operador splat) sirve `setVariadic()`: ```php $method = $class->addMethod('count'); -$method->setVariadics(true); +$method->setVariadic(true); $method->addParameter('items'); ``` @@ -378,10 +379,10 @@ function count(...$items) ``` -Método y cuerpo de la función .[#toc-method-and-function-body] --------------------------------------------------------------- +Cuerpos de métodos y funciones +------------------------------ -El cuerpo puede pasarse al método `setBody()` de una vez o secuencialmente (línea por línea) llamando repetidamente a `addBody()`: +El cuerpo se puede pasar de una vez al método `setBody()` o gradualmente (línea por línea) llamando repetidamente a `addBody()`: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -400,9 +401,9 @@ function foo() } ``` -Puede utilizar marcadores de posición especiales para inyectar variables de forma práctica. +Puede usar placeholders especiales para insertar variables fácilmente. -Marcadores de posición simples `?` +Placeholders simples `?` ```php $str = 'any string'; @@ -412,7 +413,7 @@ $function->addBody('return substr(?, ?);', [$str, $num]); echo $function; ``` -Resultado: +Resultado ```php function foo() @@ -421,7 +422,7 @@ function foo() } ``` -Marcador de posición variable `...?` +Placeholder para variadic `...?` ```php $items = [1, 2, 3]; @@ -439,7 +440,7 @@ function foo() } ``` -También puede utilizar PHP 8 parámetros con nombre utilizando marcador de posición `...?:` +También puede usar parámetros con nombre para PHP 8 usando `...?:` ```php $items = ['foo' => 1, 'bar' => true]; @@ -448,7 +449,7 @@ $function->setBody('myfunc(...?:);', [$items]); // myfunc(foo: 1, bar: true); ``` -Escapar el marcador de posición usando la barra `\?` +El placeholder se escapa con una barra invertida `\?` ```php $num = 3; @@ -468,45 +469,70 @@ function foo($a) ``` -Impresoras y cumplimiento del PSR .[#toc-printers-and-psr-compliance] ---------------------------------------------------------------------- +Printer y conformidad con PSR +----------------------------- -El código PHP es generado por los objetos `Printer`. Existe un `PsrPrinter` cuya salida se ajusta a PSR-2 y PSR-12 y utiliza espacios para la sangría, y un `Printer` que utiliza tabuladores para la sangría. +Para generar código PHP sirve la clase [Printer |api:Nette\PhpGenerator\Printer]: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // lo mismo que: echo $class +``` + +Puede generar código de todos los demás elementos, ofrece métodos como `printFunction()`, `printNamespace()`, etc. + +También está disponible la clase `PsrPrinter`, cuya salida cumple con el estilo de codificación PSR-2 / PSR-12 / PER: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // 4 espacios de sangría +echo $printer->printClass($class); ``` -¿Necesita personalizar el comportamiento de la impresora? Cree la suya propia heredando la clase `Printer`. Puedes reconfigurar estas variables: +¿Necesita ajustar el comportamiento a medida? Cree su propia versión heredando la clase `Printer`. Se pueden reconfigurar estas variables: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // longitud de línea tras la cual se produce el salto de línea public int $wrapLength = 120; + // carácter de indentación, puede ser reemplazado por una secuencia de espacios public string $indentation = "\t"; + // número de líneas vacías entre propiedades public int $linesBetweenProperties = 0; + // número de líneas vacías entre métodos public int $linesBetweenMethods = 2; + // número de líneas vacías entre grupos de 'use statements' para clases, funciones y constantes public int $linesBetweenUseTypes = 0; + // posición de la llave de apertura para funciones y métodos public bool $bracesOnNextLine = true; + // colocar un parámetro por línea, incluso si tiene un atributo o es promocionado + public bool $singleParameterOnOneLine = false; + // omite espacios de nombres que no contienen ninguna clase o función + public bool $omitEmptyNamespaces = true; + // separador entre el paréntesis derecho y el tipo de retorno de funciones y métodos public string $returnTypeColon = ': '; } ``` +¿Cómo y por qué difieren realmente el `Printer` estándar y el `PsrPrinter`? ¿Por qué no hay solo un printer en el paquete, es decir, `PsrPrinter`? + +El `Printer` estándar formatea el código como lo hacemos en todo Nette. Dado que Nette se creó mucho antes que PSR, y también porque PSR durante muchos años no entregó estándares a tiempo, sino tal vez con varios años de retraso desde la introducción de una nueva característica en PHP, sucedió que el [estándar de codificación |contributing:coding-standard] difiere en algunos pequeños detalles. La mayor diferencia es solo el uso de tabuladores en lugar de espacios. Sabemos que al usar tabuladores en nuestros proyectos, permitimos la personalización del ancho, lo cual es [necesario para personas con discapacidad visual |contributing:coding-standard#Tabuladores en lugar de espacios]. Un ejemplo de una pequeña diferencia es la colocación de la llave de apertura en una línea separada para funciones y métodos, y siempre. La recomendación de PSR nos parece ilógica y conduce a una [reducción de la claridad del código |contributing:coding-standard#Envoltura y llaves]. -Tipos .[#toc-types] -------------------- -Cada tipo o tipo de unión/intersección puede pasarse como una cadena, también puede utilizar constantes predefinidas para tipos nativos: +Tipos +----- + +Cada tipo o tipo unión/intersección se puede pasar como una cadena, también puede usar constantes predefinidas para tipos nativos: ```php use Nette\PhpGenerator\Type; $member->setType('array'); // o Type::Array; -$member->setType('array|string'); // o Type::union('array', 'string') +$member->setType('?array'); // o Type::nullable(Type::Array); +$member->setType('array|string'); // o Type::union(Type::Array, Type::String) $member->setType('Foo&Bar'); // o Type::intersection(Foo::class, Bar::class) $member->setType(null); // elimina el tipo ``` @@ -514,10 +540,10 @@ $member->setType(null); // elimina el tipo Lo mismo se aplica al método `setReturnType()`. -Literales .[#toc-literals] --------------------------- +Literales +--------- -Con `Literal` puede pasar código PHP arbitrario a, por ejemplo, valores predeterminados de propiedades o parámetros, etc: +Con `Literal` puede pasar cualquier código PHP, por ejemplo, para valores predeterminados de propiedades o parámetros, etc: ```php use Nette\PhpGenerator\Literal; @@ -545,25 +571,37 @@ class Demo } ``` -También puede pasar parámetros a `Literal` y hacer que se formatee en código PHP válido utilizando [marcadores de posición especiales |#method-and-function-body-generator]: +También puede pasar parámetros a `Literal` y dejar que se formateen en código PHP válido usando [placeholders |#Cuerpos de métodos y funciones]: ```php new Literal('substr(?, ?)', [$a, $b]); -// genera, por ejemplo: substr('hola', 5); +// genera por ejemplo: substr('hello', 5); +``` + +Un literal que representa la creación de un nuevo objeto se puede generar fácilmente usando el método `new`: + +```php +Literal::new(Demo::class, [$a, 'foo' => $b]); +// genera por ejemplo: new Demo(10, foo: 20) ``` -Atributos .[#toc-attributes] ----------------------------- +Atributos +--------- -Puede agregar atributos PHP 8 a todas las clases, métodos, propiedades, constantes, casos enum, funciones, cierres y parámetros. [Los literales |#Literals] también pueden ser usados como valores de parámetros. +Los atributos de PHP 8 se pueden agregar a todas las clases, métodos, propiedades, constantes, enums, funciones, closures y parámetros. También se pueden usar [#literales] como valores de parámetros. ```php $class = new Nette\PhpGenerator\ClassType('Demo'); -$class->addAttribute('Deprecated'); +$class->addAttribute('Table', [ + 'name' => 'user', + 'constraints' => [ + Literal::new('UniqueConstraint', ['name' => 'ean', 'columns' => ['ean']]), + ], +]); $class->addProperty('list') - ->addAttribute('WithArguments', [1, 2]); + ->addAttribute('Deprecated'); $method = $class->addMethod('count') ->addAttribute('Foo\Cached', ['mode' => true]); @@ -577,42 +615,126 @@ echo $class; Resultado: ```php -#[Deprecated] +#[Table(name: 'user', constraints: [new UniqueConstraint(name: 'ean', columns: ['ean'])])] class Demo { - #[WithArguments(1, 2)] + #[Deprecated] public $list; #[Foo\Cached(mode: true)] - public function count(#[Bar] $items) - { + public function count( + #[Bar] + $items, + ) { } } ``` -Espacio de nombres .[#toc-namespace] ------------------------------------- +Property Hooks +-------------- + +Con los property hooks (representados por la clase [PropertyHook|api:Nette\PhpGenerator\PropertyHook]) puede definir operaciones get y set para propiedades, una característica introducida en PHP 8.4: + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$prop = $class->addProperty('firstName') + ->setType('string'); + +$prop->addHook('set', 'strtolower($value)') + ->addParameter('value') + ->setType('string'); + +$prop->addHook('get') + ->setBody('return ucfirst($this->firstName);'); + +echo $class; +``` + +Genera: + +```php +class Demo +{ + public string $firstName { + set(string $value) => strtolower($value); + get { + return ucfirst($this->firstName); + } + } +} +``` + +Las propiedades y los property hooks pueden ser abstractos o finales: + +```php +$class->addProperty('id') + ->setType('int') + ->addHook('get') + ->setAbstract(); + +$class->addProperty('role') + ->setType('string') + ->addHook('set', 'strtolower($value)') + ->setFinal(); +``` + + +Visibilidad asimétrica +---------------------- + +PHP 8.4 introduce la visibilidad asimétrica para propiedades. Puede establecer diferentes niveles de acceso para lectura y escritura. + +La visibilidad se puede establecer ya sea usando el método `setVisibility()` con dos parámetros, o usando `setPublic()`, `setProtected()` o `setPrivate()` con el parámetro `mode`, que especifica si la visibilidad se aplica a la lectura o escritura de la propiedad. El modo predeterminado es `'get'`. + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); + +$class->addProperty('name') + ->setType('string') + ->setVisibility('public', 'private'); // public para lectura, private para escritura + +$class->addProperty('id') + ->setType('int') + ->setProtected('set'); // protected para escritura + +echo $class; +``` + +Genera: + +```php +class Demo +{ + public private(set) string $name; + + protected(set) int $id; +} +``` + + +Espacio de nombres +------------------ -Las clases, traits, interfaces y enums (en adelante clases) pueden agruparse en espacios de nombres ([PhpNamespace |api:Nette\PhpGenerator\PhpNamespace]): +Las clases, propiedades, interfaces y enums (en adelante, clases) se pueden agrupar en espacios de nombres representados por la clase [PhpNamespace |api:Nette\PhpGenerator\PhpNamespace]: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); -// crear nuevas clases en el espacio de nombres +// creamos nuevas clases en el namespace $class = $namespace->addClass('Task'); $interface = $namespace->addInterface('Countable'); $trait = $namespace->addTrait('NameAware'); -// o insertar una clase existente en el espacio de nombres +// o insertamos una clase existente en el namespace $class = new Nette\PhpGenerator\ClassType('Task'); $namespace->add($class); ``` -Si la clase ya existe, lanza excepción. +Si la clase ya existe, se lanza una excepción. -Puede definir declaraciones de uso: +Puede definir cláusulas use: ```php // use Http\Request; @@ -623,14 +745,14 @@ $namespace->addUse(Http\Request::class, 'HttpReq'); $namespace->addUseFunction('iter\range'); ``` -Para simplificar un nombre completo de clase, función o constante según los alias definidos, utilice el método `simplifyName`: +Para simplificar el nombre completamente calificado de una clase, función o constante según los alias definidos, use el método `simplifyName`: ```php -echo $namespace->simplifyName('Foo\Bar'); // Bar', porque 'Foo' es el espacio de nombres actual -echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', debido a la declaración de uso definida +echo $namespace->simplifyName('Foo\Bar'); // 'Bar', porque 'Foo' es el espacio de nombres actual +echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', debido al use-statement definido ``` -A la inversa, puede convertir un nombre de clase, función o constante simplificado en uno totalmente cualificado utilizando el método `resolveName`: +Por el contrario, puede convertir el nombre simplificado de una clase, función o constante a su nombre completamente calificado usando el método `resolveName`: ```php echo $namespace->resolveName('Bar'); // 'Foo\Bar' @@ -638,12 +760,10 @@ echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range' ``` -Resolución de nombres de clase .[#toc-class-names-resolving] ------------------------------------------------------------- +Resolución de nombres de clases +------------------------------- -**Cuando la clase es parte del espacio de nombres, se renderiza de forma ligeramente diferente**: todos los tipos (ie. type hints, return types, parent class name, -interfaces implementadas, rasgos y atributos usados) se *resuelven* automáticamente (a menos que lo desactives, ver más abajo). -Esto significa que tienes que **usar nombres de clase completos** en las definiciones y serán reemplazados por alias (de acuerdo con las declaraciones de uso) o nombres completamente cualificados en el código resultante: +**Cuando una clase forma parte de un espacio de nombres, se renderiza de forma ligeramente diferente:** todos los tipos (por ejemplo, typehints, tipos de retorno, nombre de la clase padre, interfaces implementadas, propiedades usadas y atributos) se *resuelven* automáticamente (a menos que lo desactive, ver más abajo). Esto significa que debe **usar nombres de clase completos** en las definiciones y se reemplazarán por alias (según las cláusulas use) o por nombres completamente calificados en el código resultante: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); @@ -651,16 +771,16 @@ $namespace->addUse('Bar\AliasedClass'); $class = $namespace->addClass('Demo'); $class->addImplement('Foo\A') // se simplificará a A - ->addTrait('Bar\AliasedClass'); // simplificará a AliasedClass + ->addTrait('Bar\AliasedClass'); // se simplificará a AliasedClass $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // en comentarios simplificar manualmente +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // en comentarios simplificamos manualmente $method->addParameter('arg') ->setType('Bar\OtherClass'); // se resolverá a \Bar\OtherClass echo $namespace; -// o utilizar PsrPrinter para una salida conforme a PSR-2 / PSR-12 +// o use PsrPrinter para salida conforme a PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -693,15 +813,15 @@ echo $printer->printNamespace($namespace); ``` -Archivos PHP .[#toc-php-files] ------------------------------- +Archivos PHP +------------ -Las clases, funciones y espacios de nombres pueden agruparse en archivos PHP representados por la clase [PhpFile |api:Nette\PhpGenerator\PhpFile]: +Las clases, funciones y espacios de nombres se pueden agrupar en archivos PHP representados por la clase [PhpFile|api:Nette\PhpGenerator\PhpFile]: ```php $file = new Nette\PhpGenerator\PhpFile; $file->addComment('This file is auto-generated.'); -$file->setStrictTypes(); // añade declare(strict_types=1) +$file->setStrictTypes(); // agrega declare(strict_types=1) $class = $file->addClass('Foo\A'); $function = $file->addFunction('Foo\foo'); @@ -713,7 +833,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// o utilice PsrPrinter para una salida conforme a PSR-2 / PSR-12 +// o use PsrPrinter para salida conforme a PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -739,27 +859,28 @@ function foo() } ``` +**Advertencia:** No es posible agregar ningún otro código fuera de funciones y clases a los archivos. + -Generar en función de los existentes .[#toc-generating-according-to-existing-ones] ----------------------------------------------------------------------------------- +Generación a partir de existentes +--------------------------------- -Además de poder modelar clases y funciones utilizando la API descrita anteriormente, también puede hacer que se generen automáticamente utilizando las existentes: +Además de poder modelar clases y funciones usando la API descrita anteriormente, también puede hacer que se generen automáticamente a partir de patrones existentes: ```php -// crea una clase idéntica a la clase PDO +// crea una clase igual que la clase PDO $class = Nette\PhpGenerator\ClassType::from(PDO::class); -// crea una función idéntica a trim() +// crea una función idéntica a la función trim() $function = Nette\PhpGenerator\GlobalFunction::from('trim'); -// crea un cierre como el especificado +// crea un closure según el indicado $closure = Nette\PhpGenerator\Closure::from( function (stdClass $a, $b = null) {}, ); ``` -Los cuerpos de las funciones y métodos están vacíos por defecto. Si quieres cargarlos también, utiliza esta forma -(requiere que `nikic/php-parser` esté instalado): +Los cuerpos de funciones y métodos están vacíos de forma predeterminada. Si también desea cargarlos, use este método (requiere la instalación del paquete `nikic/php-parser`): ```php $class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true); @@ -768,10 +889,10 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); ``` -Carga desde archivo PHP .[#toc-loading-from-php-file] ------------------------------------------------------ +Carga desde archivos PHP +------------------------ -También puede cargar funciones, clases, interfaces y enums directamente desde una cadena de código PHP. Por ejemplo, creamos el objeto `ClassType` de esta manera: +También puede cargar funciones, clases, interfaces y enums directamente desde una cadena que contenga código PHP. Por ejemplo, así creamos un objeto `ClassType`: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -784,26 +905,58 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -Cuando se cargan clases desde código PHP, se ignoran los comentarios de una sola línea fuera de los cuerpos de los métodos (por ejemplo, para propiedades, etc.) porque esta biblioteca no tiene una API para trabajar con ellos. +Al cargar clases desde código PHP, los comentarios de una sola línea fuera de los cuerpos de los métodos se ignoran (por ejemplo, en propiedades, etc.), ya que esta biblioteca no tiene una API para trabajar con ellos. -También puede cargar directamente el archivo PHP completo, que puede contener cualquier número de clases, funciones o incluso múltiples espacios de nombres: +También puede cargar directamente un archivo PHP completo, que puede contener cualquier número de clases, funciones o incluso espacios de nombres: ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` -También se cargan el comentario inicial del archivo y la declaración `strict_types`. En cambio, el resto del código global se ignora. +También se cargan el comentario introductorio del archivo y la declaración `strict_types`. Por el contrario, todo el demás código global se ignora. -Esto requiere que `nikic/php-parser` esté instalado. +Se requiere que esté instalado `nikic/php-parser`. .[note] -Si necesita manipular código global en archivos o sentencias individuales en cuerpos de métodos, es mejor utilizar directamente la biblioteca `nikic/php-parser`. +Si necesita manipular código global en archivos o sentencias individuales en cuerpos de métodos, es mejor usar directamente la biblioteca `nikic/php-parser`. + + +Class Manipulator +----------------- + +La clase [ClassManipulator|api:Nette\PhpGenerator\ClassManipulator] proporciona herramientas para manipular clases. +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$manipulator = new Nette\PhpGenerator\ClassManipulator($class); +``` -Volquete de variables .[#toc-variables-dumper] ----------------------------------------------- +El método `inheritMethod()` copia un método de la clase padre o interfaz implementada a su clase. Esto le permite sobrescribir el método o extender su firma: + +```php +$method = $manipulator->inheritMethod('bar'); +$method->setBody('...'); +``` -El Dumper devuelve una representación de cadena PHP parseable de una variable. Proporciona una salida mejor y más clara que la función nativa `var_export()`. +El método `inheritProperty()` copia una propiedad de la clase padre a su clase. Es útil cuando desea tener la misma propiedad en su clase, pero quizás con un valor predeterminado diferente: + +```php +$property = $manipulator->inheritProperty('foo'); +$property->setValue('new value'); +``` + +El método `implement()` implementa automáticamente todos los métodos y propiedades de la interfaz o clase abstracta dada en su clase: + +```php +$manipulator->implement(SomeInterface::class); +// Ahora su clase implementa SomeInterface y contiene todos sus métodos +``` + + +Volcado de variables +-------------------- + +La clase `Dumper` convierte una variable en código PHP analizable. Proporciona una salida mejor y más clara que la función estándar `var_export()`. ```php $dumper = new Nette\PhpGenerator\Dumper; @@ -814,9 +967,7 @@ echo $dumper->dump($var); // imprime ['a', 'b', 123] ``` -Tabla de compatibilidad .[#toc-compatibility-table] ---------------------------------------------------- - -PhpGenerator 4.0 es compatible con PHP 8.0 a 8.2 +Tabla de compatibilidad +----------------------- -{{leftbar: nette:@menu-topics}} +PhpGenerator 4.1 es compatible con PHP 8.0 a 8.4. diff --git a/php-generator/es/@meta.texy b/php-generator/es/@meta.texy new file mode 100644 index 0000000000..25d506cde9 --- /dev/null +++ b/php-generator/es/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Documentación}} +{{leftbar: nette:@menu-topics}} diff --git a/php-generator/fr/@home.texy b/php-generator/fr/@home.texy index b0ba3f22d2..69563975b2 100644 --- a/php-generator/fr/@home.texy +++ b/php-generator/fr/@home.texy @@ -1,31 +1,32 @@ -Générateur de code PHP -********************** +Nette PhpGenerator +****************** -<div class=perex> -- Vous avez besoin de générer du code PHP pour des classes, des fonctions, des fichiers PHP, etc. -- Supporte toutes les dernières fonctionnalités de PHP comme les enums, les attributs, etc. +<div class="perex"> +Vous cherchez un outil pour générer du code PHP pour des classes, des fonctions ou des fichiers complets ? + +- Prend en charge toutes les dernières fonctionnalités de PHP (comme les property hooks, les enums, les attributs, etc.) - Vous permet de modifier facilement les classes existantes -- Sortie conforme à PSR-12 -- Bibliothèque très mature, stable et largement utilisée. +- Le code de sortie est conforme au style de codage PSR-12 / PER +- Bibliothèque mature, stable et largement utilisée </div> -Installation .[#toc-installation] ---------------------------------- +Installation +------------ -Téléchargez et installez le paquet en utilisant [Composer |best-practices:composer]: +Vous pouvez télécharger et installer la bibliothèque à l'aide de [Composer|best-practices:composer] : ```shell composer require nette/php-generator ``` -Pour la compatibilité avec PHP, voir le [tableau |#Compatibility Table]. +La compatibilité avec PHP se trouve dans le [tableau |#Tableau de compatibilité]. -Classes .[#toc-classes] ------------------------ +Classes +------- -Commençons par un exemple simple de génération de classe à l'aide de [ClassType |api:Nette\PhpGenerator\ClassType]: +Commençons directement par un exemple de création de classe à l'aide de [ClassType |api:Nette\PhpGenerator\ClassType] : ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -34,19 +35,19 @@ $class ->setFinal() ->setExtends(ParentClass::class) ->addImplement(Countable::class) - ->addComment("Description of class.\nSecond line\n") + ->addComment("Description de la classe.\nDeuxième ligne\n") ->addComment('@property-read Nette\Forms\Form $form'); -// pour générer du code PHP, il suffit de le convertir en chaîne ou d'utiliser echo: +// générez simplement le code en le castant en chaîne ou en utilisant echo : echo $class; ``` -Cela donnera le résultat suivant : +Retourne le résultat suivant : ```php /** - * Description of class. - * Second line + * Description de la classe + * Deuxième ligne * * @property-read Nette\Forms\Form $form */ @@ -55,7 +56,7 @@ final class Demo extends ParentClass implements Countable } ``` -Nous pouvons également utiliser une imprimante pour générer le code, qui, contrairement à `echo $class`, pourra être [configuré ultérieurement |#Printers and PSR compliance]: +Pour générer le code, nous pouvons également utiliser ce qu'on appelle un printer, que nous pourrons [configurer davantage |#Printer et conformité PSR] contrairement à `echo $class` : ```php $printer = new Nette\PhpGenerator\Printer; @@ -66,7 +67,7 @@ Nous pouvons ajouter des constantes (classe [Constant |api:Nette\PhpGenerator\Co ```php $class->addConstant('ID', 123) - ->setProtected() // visibilité constante + ->setProtected() // visibilité des constantes ->setType('int') ->setFinal(); @@ -77,13 +78,13 @@ $class->addProperty('items', [1, 2, 3]) $class->addProperty('list') ->setType('?array') - ->setInitialized(); // imprime '= null'. + ->setInitialized(); // écrit '= null' ``` -Il génère : +Génère : ```php -final protected const int ID = 123 ; +final protected const int ID = 123; /** @var int[] */ private static $items = [1, 2, 3]; @@ -91,14 +92,14 @@ private static $items = [1, 2, 3]; public ?array $list = null; ``` -Et on peut ajouter des [méthodes |#Method and Function Signature]: +Et nous pouvons ajouter des [méthodes |#Signatures de méthodes et de fonctions] : ```php $method = $class->addMethod('count') - ->addComment('Count it.') + ->addComment('Comptez-le.') ->setFinal() ->setProtected() - ->setReturnType('?int') // méthode retour type + ->setReturnType('?int') // types de retour pour les méthodes ->setBody('return count($items ?: $this->items);'); $method->addParameter('items', []) // $items = [] @@ -106,11 +107,11 @@ $method->addParameter('items', []) // $items = [] ->setType('array'); // array &$items = [] ``` -Il en résulte : +Le résultat est : ```php /** - * Count it. + * Comptez-le. */ final protected function count(array &$items = []): ?int { @@ -118,7 +119,7 @@ final protected function count(array &$items = []): ?int } ``` -Les paramètres promus introduits par PHP 8.0 peuvent être passés au constructeur : +Les paramètres promus introduits en PHP 8.0 peuvent être passés au constructeur : ```php $method = $class->addMethod('__construct'); @@ -127,7 +128,7 @@ $method->addPromotedParameter('args', []) ->setPrivate(); ``` -Il en résulte : +Le résultat est : ```php public function __construct( @@ -137,15 +138,15 @@ public function __construct( } ``` -Les propriétés et les classes en lecture seule peuvent être marquées via `setReadOnly()`. +Les propriétés et classes en lecture seule peuvent être marquées à l'aide de la fonction `setReadOnly()`. ------ Si la propriété, la constante, la méthode ou le paramètre ajouté existe déjà, une exception est levée. -Les membres peuvent être supprimés en utilisant `removeProperty()`, `removeConstant()`, `removeMethod()` ou `removeParameter()`. +Les membres de la classe peuvent être supprimés à l'aide de `removeProperty()`, `removeConstant()`, `removeMethod()` ou `removeParameter()`. -Vous pouvez également ajouter des objets existants `Method`, `Property` ou `Constant` à la classe : +Vous pouvez également ajouter des objets `Method`, `Property` ou `Constant` existants à la classe : ```php $method = new Nette\PhpGenerator\Method('getHandle'); @@ -158,7 +159,7 @@ $class = (new Nette\PhpGenerator\ClassType('Demo')) ->addMember($const); ``` -Vous pouvez cloner des méthodes, propriétés et constantes existantes avec un nom différent en utilisant `cloneWithName()`: +Vous pouvez également cloner des méthodes, propriétés et constantes existantes sous un autre nom à l'aide de `cloneWithName()` : ```php $methodCount = $class->getMethod('count'); @@ -167,8 +168,8 @@ $class->addMember($methodRecount); ``` -Interface ou Trait .[#toc-interface-or-trait] ---------------------------------------------- +Interface ou trait +------------------ Vous pouvez créer des interfaces et des traits (classes [InterfaceType |api:Nette\PhpGenerator\InterfaceType] et [TraitType |api:Nette\PhpGenerator\TraitType]) : @@ -177,7 +178,7 @@ $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait'); ``` -Utilisation des traits de caractère : +Utilisation des traits : ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -202,10 +203,10 @@ class Demo ``` -Enums .[#toc-enums] -------------------- +Enums +----- -Vous pouvez facilement créer les enums que PHP 8.1 apporte (classe [EnumType |api:Nette\PhpGenerator\EnumType]) : +Les énumérations, introduites par PHP 8.1, peuvent être facilement créées comme ceci : (classe [EnumType |api:Nette\PhpGenerator\EnumType]) : ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -229,20 +230,20 @@ enum Suit } ``` -Vous pouvez également définir des équivalents scalaires pour les cas afin de créer un enum adossé : +Vous pouvez également définir des équivalents scalaires et créer ainsi une énumération "backed" : ```php $enum->addCase('Clubs', '♣'); $enum->addCase('Diamonds', '♦'); ``` -Il est possible d'ajouter un commentaire ou des [attributs |#attributes] à chaque cas en utilisant `addComment()` ou `addAttribute()`. +À chaque *case*, il est possible d'ajouter un commentaire ou des [#Attributs] à l'aide de `addComment()` ou `addAttribute()`. -Classe anonyme .[#toc-anonymous-class] --------------------------------------- +Classes anonymes +---------------- -Donnez le nom `null` et vous avez une classe anonyme : +Nous passons `null` comme nom et nous avons une classe anonyme : ```php $class = new Nette\PhpGenerator\ClassType(null); @@ -264,10 +265,10 @@ $obj = new class ($val) { ``` -Fonction globale .[#toc-global-function] ----------------------------------------- +Fonctions globales +------------------ -Le code des fonctions va générer la classe [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction]: +Le code des fonctions est généré par la classe [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction] : ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -276,7 +277,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// ou utiliser PsrPrinter pour une sortie conforme à PSR-2 / PSR-12 +// ou utilisez PsrPrinter pour une sortie conforme à PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -290,10 +291,10 @@ function foo($a, $b) ``` -Fermeture .[#toc-closure] -------------------------- +Fonctions anonymes +------------------ -Le code des fermetures va générer la classe [Closure |api:Nette\PhpGenerator\Closure]: +Le code des fonctions anonymes est généré par la classe [Closure |api:Nette\PhpGenerator\Closure] : ```php $closure = new Nette\PhpGenerator\Closure; @@ -304,7 +305,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// ou utiliser PsrPrinter pour une sortie conforme à PSR-2 / PSR-12 +// ou utilisez PsrPrinter pour une sortie conforme à PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -317,10 +318,10 @@ function ($a, $b) use (&$c) { ``` -Fonction flèche .[#toc-arrow-function] --------------------------------------- +Fonctions fléchées courtes +-------------------------- -Vous pouvez également imprimer la fermeture comme fonction de flèche en utilisant une imprimante : +Vous pouvez également afficher une fonction anonyme courte à l'aide du printer : ```php $closure = new Nette\PhpGenerator\Closure; @@ -338,34 +339,34 @@ fn($a, $b) => $a + $b ``` -Signature de la méthode et de la fonction .[#toc-method-and-function-signature] -------------------------------------------------------------------------------- +Signatures de méthodes et de fonctions +-------------------------------------- -Les méthodes sont représentées par la classe [Method |api:Nette\PhpGenerator\Method]. Vous pouvez définir la visibilité, la valeur de retour, ajouter des commentaires, des [attributs |#Attributes], etc : +Les méthodes sont représentées par la classe [Method |api:Nette\PhpGenerator\Method]. Vous pouvez définir la visibilité, la valeur de retour, ajouter des commentaires, des [#attributs], etc : ```php $method = $class->addMethod('count') - ->addComment('Count it.') + ->addComment('Comptez-le.') ->setFinal() ->setProtected() ->setReturnType('?int'); ``` -Chaque paramètre est représenté par une classe [Paramètre |api:Nette\PhpGenerator\Parameter]. Là encore, vous pouvez définir toutes les propriétés imaginables : +Les paramètres individuels sont représentés par la classe [Parameter |api:Nette\PhpGenerator\Parameter]. Encore une fois, vous pouvez définir toutes les propriétés imaginables : ```php $method->addParameter('items', []) // $items = [] - ->setReference() // &$items = [] - ->setType('array'); // array &$items = [] + ->setReference() // &$items = [] + ->setType('array'); // array &$items = [] // function count(&$items = []) ``` -Pour définir les paramètres dits variadiques (ou également l'opérateur splat, spread, ellipsis, unpacking ou trois points), utilisez `setVariadics()`: +Pour définir les paramètres dits variadiques (ou aussi opérateur splat), utilisez `setVariadic()` : ```php $method = $class->addMethod('count'); -$method->setVariadics(true); +$method->setVariadic(true); $method->addParameter('items'); ``` @@ -378,10 +379,10 @@ function count(...$items) ``` -Méthode et corps de fonction .[#toc-method-and-function-body] -------------------------------------------------------------- +Corps de méthodes et de fonctions +--------------------------------- -Le corps peut être transmis à la méthode `setBody()` en une seule fois ou de manière séquentielle (ligne par ligne) en appelant à plusieurs reprises `addBody()`: +Le corps peut être passé en une seule fois à la méthode `setBody()` ou progressivement (ligne par ligne) en appelant répétitivement `addBody()` : ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -400,28 +401,28 @@ function foo() } ``` -Vous pouvez utiliser des caractères de remplacement spéciaux pour injecter des variables de manière pratique. +Vous pouvez utiliser des placeholders spéciaux pour insérer facilement des variables. -Caractères de remplacement simples `?` +Placeholders simples `?` ```php -$str = 'any string'; +$str = 'n\'importe quelle chaîne'; $num = 3; $function = new Nette\PhpGenerator\GlobalFunction('foo'); $function->addBody('return substr(?, ?);', [$str, $num]); echo $function; ``` -Résultat : +Résultat ```php function foo() { - return substr('any string', 3); + return substr('n\'importe quelle chaîne', 3); } ``` -Variadic placeholder `...?` +Placeholder pour variadic `...?` ```php $items = [1, 2, 3]; @@ -439,7 +440,7 @@ function foo() } ``` -Vous pouvez également utiliser les paramètres nommés de PHP 8 en utilisant des caractères de substitution. `...?:` +Vous pouvez également utiliser des paramètres nommés pour PHP 8 en utilisant `...?:` ```php $items = ['foo' => 1, 'bar' => true]; @@ -448,7 +449,7 @@ $function->setBody('myfunc(...?:);', [$items]); // myfunc(foo: 1, bar: true); ``` -Échapper au caractère de remplacement avec une barre oblique `\?` +Le placeholder est échappé à l'aide d'une barre oblique inverse `\?` ```php $num = 3; @@ -468,45 +469,70 @@ function foo($a) ``` -Imprimantes et conformité aux RPS .[#toc-printers-and-psr-compliance] ---------------------------------------------------------------------- +Printer et conformité PSR +------------------------- -Le code PHP est généré par les objets `Printer`. Il existe un `PsrPrinter` dont la sortie est conforme à PSR-2 et PSR-12 et qui utilise des espaces pour l'indentation, et un `Printer` qui utilise des tabulations pour l'indentation. +Pour générer du code PHP, la classe [Printer |api:Nette\PhpGenerator\Printer] est utilisée : ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // identique à : echo $class +``` + +Il peut générer le code de tous les autres éléments, offre des méthodes comme `printFunction()`, `printNamespace()`, etc. + +Il existe également la classe `PsrPrinter`, dont la sortie est conforme au style de codage PSR-2 / PSR-12 / PER : + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // Retrait de 4 espaces +echo $printer->printClass($class); ``` -Vous avez besoin de personnaliser le comportement de l'imprimante ? Créez le vôtre en héritant de la classe `Printer`. Vous pouvez reconfigurer ces variables : +Besoin d'ajuster le comportement sur mesure ? Créez votre propre version en héritant de la classe `Printer`. Ces variables peuvent être reconfigurées : ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // longueur de ligne après laquelle un retour à la ligne se produit public int $wrapLength = 120; + // caractère d'indentation, peut être remplacé par une séquence d'espaces public string $indentation = "\t"; + // nombre de lignes vides entre les propriétés public int $linesBetweenProperties = 0; + // nombre de lignes vides entre les méthodes public int $linesBetweenMethods = 2; + // nombre de lignes vides entre les groupes de 'use statements' pour les classes, fonctions et constantes public int $linesBetweenUseTypes = 0; + // position de l'accolade ouvrante pour les fonctions et méthodes public bool $bracesOnNextLine = true; + // placez un seul paramètre sur une seule ligne, même s'il a un attribut ou est promu + public bool $singleParameterOnOneLine = false; + // omet les espaces de noms qui ne contiennent aucune classe ou fonction + public bool $omitEmptyNamespaces = true; + // séparateur entre la parenthèse droite et le type de retour des fonctions et méthodes public string $returnTypeColon = ': '; } ``` +Comment et pourquoi le `Printer` standard et le `PsrPrinter` diffèrent-ils réellement ? Pourquoi n'y a-t-il pas qu'un seul printer dans le paquet, à savoir `PsrPrinter` ? -Types .[#toc-types] -------------------- +Le `Printer` standard formate le code comme nous le faisons dans tout Nette. Comme Nette a été créé bien avant PSR, et aussi parce que PSR n'a pas fourni de normes à temps pendant de nombreuses années, mais par exemple seulement plusieurs années après l'introduction d'une nouvelle fonctionnalité en PHP, il s'est avéré que le [standard de codage |contributing:coding-standard] diffère sur quelques détails mineurs. La plus grande différence est l'utilisation de tabulations au lieu d'espaces. Nous savons que l'utilisation de tabulations dans nos projets permet d'ajuster la largeur, ce qui est [nécessaire pour les personnes ayant une déficience visuelle |contributing:coding-standard#Tabulations au lieu d espaces]. Un exemple de différence mineure est le placement de l'accolade sur une ligne distincte pour les fonctions et les méthodes, et ce toujours. La recommandation PSR nous semble illogique et conduit à une [réduction de la clarté du code |contributing:coding-standard#Retours à la ligne et accolades]. -Chaque type ou type d'union/intersection peut être passé comme une chaîne de caractères, vous pouvez également utiliser des constantes prédéfinies pour les types natifs : + +Types +----- + +Chaque type ou type union/intersection peut être passé sous forme de chaîne, vous pouvez également utiliser des constantes prédéfinies pour les types natifs : ```php use Nette\PhpGenerator\Type; $member->setType('array'); // ou Type::Array; -$member->setType('array|string'); // ou Type::union('array', 'string') +$member->setType('?array'); // ou Type::nullable(Type::Array); +$member->setType('array|string'); // ou Type::union(Type::Array, Type::String) $member->setType('Foo&Bar'); // ou Type::intersection(Foo::class, Bar::class) $member->setType(null); // supprime le type ``` @@ -514,10 +540,10 @@ $member->setType(null); // supprime le type Il en va de même pour la méthode `setReturnType()`. -Littéraux .[#toc-literals] --------------------------- +Littéraux +--------- -Avec `Literal`, vous pouvez transmettre un code PHP arbitraire, par exemple, aux valeurs par défaut des propriétés ou des paramètres, etc : +À l'aide de `Literal`, vous pouvez passer n'importe quel code PHP, par exemple pour les valeurs par défaut des propriétés ou des paramètres, etc : ```php use Nette\PhpGenerator\Literal; @@ -545,25 +571,37 @@ class Demo } ``` -Vous pouvez également passer des paramètres à `Literal` et les faire formater en code PHP valide à l'aide de [caractères de remplacement spéciaux |#method-and-function-body-generator]: +Vous pouvez également passer des paramètres à `Literal` et les faire formater en code PHP valide à l'aide de [placeholders |#Corps de méthodes et de fonctions] : ```php new Literal('substr(?, ?)', [$a, $b]); -// génère, par exemple: substr('hello', 5); +// génère par exemple : substr('hello', 5); ``` +Un littéral représentant la création d'un nouvel objet peut être facilement généré à l'aide de la méthode `new` : + +```php +Literal::new(Demo::class, [$a, 'foo' => $b]); +// génère par exemple : new Demo(10, foo: 20) +``` -Attributs .[#toc-attributes] ----------------------------- -Vous pouvez ajouter des attributs PHP 8 à toutes les classes, méthodes, propriétés, constantes, cas d'enum, fonctions, fermetures et paramètres. Les [littéraux |#Literals] peuvent aussi être utilisés comme valeurs de paramètres. +Attributs +--------- + +Les attributs PHP 8 peuvent être ajoutés à toutes les classes, méthodes, propriétés, constantes, enums, fonctions, closures et paramètres. Il est également possible d'utiliser des [#Littéraux] comme valeurs de paramètres. ```php $class = new Nette\PhpGenerator\ClassType('Demo'); -$class->addAttribute('Deprecated'); +$class->addAttribute('Table', [ + 'name' => 'user', + 'constraints' => [ + Literal::new('UniqueConstraint', ['name' => 'ean', 'columns' => ['ean']]), + ], +]); $class->addProperty('list') - ->addAttribute('WithArguments', [1, 2]); + ->addAttribute('Deprecated'); $method = $class->addMethod('count') ->addAttribute('Foo\Cached', ['mode' => true]); @@ -577,42 +615,126 @@ echo $class; Résultat : ```php -#[Deprecated] +#[Table(name: 'user', constraints: [new UniqueConstraint(name: 'ean', columns: ['ean'])])] class Demo { - #[WithArguments(1, 2)] + #[Deprecated] public $list; #[Foo\Cached(mode: true)] - public function count(#[Bar] $items) - { + public function count( + #[Bar] + $items, + ) { } } ``` -Espace de nommage .[#toc-namespace] ------------------------------------ +Property Hooks +-------------- + +À l'aide des property hooks (représentés par la classe [PropertyHook|api:Nette\PhpGenerator\PropertyHook]), vous pouvez définir des opérations get et set pour les propriétés, une fonctionnalité introduite en PHP 8.4 : + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$prop = $class->addProperty('firstName') + ->setType('string'); + +$prop->addHook('set', 'strtolower($value)') + ->addParameter('value') + ->setType('string'); + +$prop->addHook('get') + ->setBody('return ucfirst($this->firstName);'); + +echo $class; +``` + +Génère : + +```php +class Demo +{ + public string $firstName { + set(string $value) => strtolower($value); + get { + return ucfirst($this->firstName); + } + } +} +``` -Les classes, traits, interfaces et enums (ci-après dénommés "classes") peuvent être regroupés en espaces de noms ([PhpNamespace |api:Nette\PhpGenerator\PhpNamespace]) : +Les propriétés et les property hooks peuvent être abstraits ou finaux : + +```php +$class->addProperty('id') + ->setType('int') + ->addHook('get') + ->setAbstract(); + +$class->addProperty('role') + ->setType('string') + ->addHook('set', 'strtolower($value)') + ->setFinal(); +``` + + +Visibilité asymétrique +---------------------- + +PHP 8.4 introduit la visibilité asymétrique pour les propriétés. Vous pouvez définir différents niveaux d'accès pour la lecture et l'écriture. + +La visibilité peut être définie soit à l'aide de la méthode `setVisibility()` avec deux paramètres, soit à l'aide de `setPublic()`, `setProtected()` ou `setPrivate()` avec le paramètre `mode`, qui spécifie si la visibilité s'applique à la lecture ou à l'écriture de la propriété. Le mode par défaut est `'get'`. + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); + +$class->addProperty('name') + ->setType('string') + ->setVisibility('public', 'private'); // public pour la lecture, private pour l'écriture + +$class->addProperty('id') + ->setType('int') + ->setProtected('set'); // protected pour l'écriture + +echo $class; +``` + +Génère : + +```php +class Demo +{ + public private(set) string $name; + + protected(set) int $id; +} +``` + + +Espace de noms +-------------- + +Les classes, propriétés, interfaces et énumérations (ci-après dénommées classes) peuvent être regroupées dans des espaces de noms représentés par la classe [PhpNamespace |api:Nette\PhpGenerator\PhpNamespace] : ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); -// créer de nouvelles classes dans l'espace de noms +// créons de nouvelles classes dans l'espace de noms $class = $namespace->addClass('Task'); $interface = $namespace->addInterface('Countable'); $trait = $namespace->addTrait('NameAware'); -// ou insérer une classe existante dans l'espace de noms +// ou insérons une classe existante dans l'espace de noms $class = new Nette\PhpGenerator\ClassType('Task'); $namespace->add($class); ``` -Si la classe existe déjà, elle lève une exception. +Si la classe existe déjà, une exception est levée. -Vous pouvez définir des déclarations d'utilisation : +Vous pouvez définir des clauses use : ```php // use Http\Request; @@ -623,14 +745,14 @@ $namespace->addUse(Http\Request::class, 'HttpReq'); $namespace->addUseFunction('iter\range'); ``` -Pour simplifier un nom de classe, de fonction ou de constante entièrement qualifié en fonction des alias définis, utilisez la méthode `simplifyName`: +Pour simplifier le nom de classe, de fonction ou de constante pleinement qualifié selon les alias définis, utilisez la méthode `simplifyName` : ```php -echo $namespace->simplifyName('Foo\Bar'); // 'Bar', car 'Foo' est l'espace de nom courant -echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', à cause de l'énoncé d'utilisation défini +echo $namespace->simplifyName('Foo\Bar'); // 'Bar', car 'Foo' est l'espace de noms actuel +echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', à cause du use-statement défini ``` -Inversement, vous pouvez convertir un nom de classe, de fonction ou de constante simplifié en un nom pleinement qualifié en utilisant la méthode `resolveName`: +Inversement, vous pouvez convertir le nom simplifié de classe, de fonction ou de constante en nom pleinement qualifié à l'aide de la méthode `resolveName` : ```php echo $namespace->resolveName('Bar'); // 'Foo\Bar' @@ -638,29 +760,27 @@ echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range' ``` -Résolution des noms de classe .[#toc-class-names-resolving] ------------------------------------------------------------ +Traductions des noms de classes +------------------------------- -**Lorsque la classe fait partie de l'espace de nom, elle est rendue de manière légèrement différente** : tous les types (c'est-à-dire les indications de type, les types de retour, le nom de la classe parente, -interfaces implémentées, traits et attributs utilisés) sont automatiquement *résolus* (sauf si vous le désactivez, voir ci-dessous). -Cela signifie que vous devez **utiliser les noms de classe complets** dans les définitions et qu'ils seront remplacés par des alias (selon les déclarations d'utilisation) ou des noms pleinement qualifiés dans le code résultant : +**Lorsqu'une classe fait partie d'un espace de noms, elle est rendue légèrement différemment :** tous les types (par exemple, les typehints, les types de retour, le nom de la classe parente, les interfaces implémentées, les propriétés et attributs utilisés) sont automatiquement *traduits* (sauf si vous le désactivez, voir ci-dessous). Cela signifie que vous devez **utiliser les noms de classes complets** dans les définitions et ils seront remplacés par des alias (selon les clauses use) ou par des noms pleinement qualifiés dans le code résultant : ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); $namespace->addUse('Bar\AliasedClass'); $class = $namespace->addClass('Demo'); -$class->addImplement('Foo\A') // il sera simplifié en A - ->addTrait('Bar\AliasedClass'); // il sera simplifié en AliasedClass +$class->addImplement('Foo\A') // sera simplifié en A + ->addTrait('Bar\AliasedClass'); // sera simplifié en AliasedClass $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyType('Foo\D')) ; // dans les commentaires simplifier manuellement +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // dans les commentaires, nous simplifions manuellement $method->addParameter('arg') - ->setType('Bar\OtherClass'); // elle sera résolue en \Bar\OtherClass + ->setType('Bar\OtherClass'); // sera traduit en \Bar\OtherClass echo $namespace; -// ou utiliser PsrPrinter pour une sortie conforme à PSR-2 / PSR-12 +// ou utilisez PsrPrinter pour une sortie conforme à PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -684,7 +804,7 @@ class Demo implements A } ``` -La résolution automatique peut être désactivée de cette façon : +La traduction automatique peut être désactivée de cette manière : ```php $printer = new Nette\PhpGenerator\Printer; // ou PsrPrinter @@ -693,14 +813,14 @@ echo $printer->printNamespace($namespace); ``` -Fichiers PHP .[#toc-php-files] ------------------------------- +Fichiers PHP +------------ -Les classes, fonctions et espaces de noms peuvent être regroupés en fichiers PHP représentés par la classe [PhpFile |api:Nette\PhpGenerator\PhpFile]: +Les classes, fonctions et espaces de noms peuvent être regroupés dans des fichiers PHP représentés par la classe [PhpFile|api:Nette\PhpGenerator\PhpFile] : ```php $file = new Nette\PhpGenerator\PhpFile; -$file->addComment('This file is auto-generated.'); +$file->addComment('Ce fichier est auto-généré.'); $file->setStrictTypes(); // ajoute declare(strict_types=1) $class = $file->addClass('Foo\A'); @@ -713,7 +833,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// ou utiliser PsrPrinter pour une sortie conforme à PSR-2 / PSR-12 +// ou utilisez PsrPrinter pour une sortie conforme à PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -723,7 +843,7 @@ Résultat : <?php /** - * This file is auto-generated. + * Ce fichier est auto-généré. */ declare(strict_types=1); @@ -739,27 +859,28 @@ function foo() } ``` +**Attention :** Il n'est pas possible d'ajouter d'autre code aux fichiers en dehors des fonctions et des classes. + -Générer en fonction de ceux qui existent déjà .[#toc-generating-according-to-existing-ones] -------------------------------------------------------------------------------------------- +Génération basée sur l'existant +------------------------------- -En plus de pouvoir modéliser des classes et des fonctions à l'aide de l'API décrite ci-dessus, vous pouvez également les faire générer automatiquement en fonction de celles qui existent déjà : +En plus de pouvoir modéliser des classes et des fonctions à l'aide de l'API décrite ci-dessus, vous pouvez également les faire générer automatiquement à partir de modèles existants : ```php // crée une classe identique à la classe PDO $class = Nette\PhpGenerator\ClassType::from(PDO::class); -// crée une fonction identique à trim() +// crée une fonction identique à la fonction trim() $function = Nette\PhpGenerator\GlobalFunction::from('trim'); -// crée une fermeture comme spécifié +// crée une closure basée sur celle fournie $closure = Nette\PhpGenerator\Closure::from( function (stdClass $a, $b = null) {}, ); ``` -Les corps des fonctions et des méthodes sont vides par défaut. Si vous souhaitez les charger également, utilisez cette méthode -(elle nécessite l'installation de `nikic/php-parser` ) : +Les corps des fonctions et des méthodes sont vides par défaut. Si vous souhaitez également les charger, utilisez cette méthode (nécessite l'installation du paquet `nikic/php-parser`) : ```php $class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true); @@ -768,10 +889,10 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); ``` -Chargement depuis le fichier PHP .[#toc-loading-from-php-file] --------------------------------------------------------------- +Chargement depuis des fichiers PHP +---------------------------------- -Vous pouvez également charger des fonctions, des classes, des interfaces et des enums directement à partir d'une chaîne de code PHP. Par exemple, nous créons l'objet `ClassType` de cette manière : +Vous pouvez également charger des fonctions, classes, interfaces et enums directement à partir d'une chaîne contenant du code PHP. Par exemple, voici comment créer un objet `ClassType` : ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -784,39 +905,69 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -Lors du chargement de classes à partir de code PHP, les commentaires d'une seule ligne en dehors du corps des méthodes sont ignorés (par exemple pour les propriétés, etc.) car cette bibliothèque ne dispose pas d'une API pour les gérer. +Lors du chargement de classes à partir de code PHP, les commentaires sur une seule ligne en dehors du corps des méthodes sont ignorés (par exemple, pour les propriétés, etc.), car cette bibliothèque n'a pas d'API pour les gérer. -Vous pouvez également charger directement le fichier PHP entier, qui peut contenir n'importe quel nombre de classes, de fonctions ou même plusieurs espaces de noms : +Vous pouvez également charger directement un fichier PHP entier, qui peut contenir n'importe quel nombre de classes, de fonctions ou même d'espaces de noms : ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` -Le commentaire initial du fichier et la déclaration `strict_types` sont également chargés. En revanche, tout autre code global est ignoré. +Le commentaire d'introduction du fichier et la déclaration `strict_types` sont également chargés. En revanche, tout autre code global est ignoré. -Cela nécessite l'installation de `nikic/php-parser`. +Il est nécessaire que `nikic/php-parser` soit installé. .[note] -Si vous devez manipuler du code global dans des fichiers ou des instructions individuelles dans des corps de méthodes, il est préférable d'utiliser directement la bibliothèque `nikic/php-parser`. +Si vous avez besoin de manipuler du code global dans des fichiers ou des instructions individuelles dans les corps de méthodes, il est préférable d'utiliser directement la bibliothèque `nikic/php-parser`. + + +Class Manipulator +----------------- + +La classe [ClassManipulator|api:Nette\PhpGenerator\ClassManipulator] fournit des outils pour manipuler les classes. + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$manipulator = new Nette\PhpGenerator\ClassManipulator($class); +``` + +La méthode `inheritMethod()` copie une méthode de la classe parente ou de l'interface implémentée dans votre classe. Cela vous permet de remplacer la méthode ou d'étendre sa signature : + +```php +$method = $manipulator->inheritMethod('bar'); +$method->setBody('...'); +``` + +La méthode `inheritProperty()` copie une propriété de la classe parente dans votre classe. C'est utile lorsque vous souhaitez avoir la même propriété dans votre classe, mais peut-être avec une valeur par défaut différente : + +```php +$property = $manipulator->inheritProperty('foo'); +$property->setValue('nouvelle valeur'); +``` +La méthode `implement()` implémente automatiquement toutes les méthodes et propriétés de l'interface ou de la classe abstraite donnée dans votre classe : + +```php +$manipulator->implement(SomeInterface::class); +// Maintenant, votre classe implémente SomeInterface et contient toutes ses méthodes +``` -Dumper de variables .[#toc-variables-dumper] --------------------------------------------- -La fonction Dumper renvoie la représentation d'une variable sous forme de chaîne PHP. Fournit une sortie meilleure et plus claire que la fonction native `var_export()`. +Affichage des variables +----------------------- + +La classe `Dumper` convertit une variable en code PHP analysable. Elle fournit une sortie meilleure et plus claire que la fonction standard `var_export()`. ```php $dumper = new Nette\PhpGenerator\Dumper; $var = ['a', 'b', 123]; -echo $dumper->dump($var); // imprime ['a', 'b', 123] +echo $dumper->dump($var); // affiche ['a', 'b', 123] ``` -Tableau de compatibilité .[#toc-compatibility-table] ----------------------------------------------------- - -PhpGenerator 4.0 est compatible avec PHP 8.0 à 8.2. +Tableau de compatibilité +------------------------ -{{leftbar: nette:@menu-topics}} +PhpGenerator 4.1 est compatible avec PHP 8.0 à 8.4. diff --git a/php-generator/fr/@meta.texy b/php-generator/fr/@meta.texy new file mode 100644 index 0000000000..95ec8a4ef6 --- /dev/null +++ b/php-generator/fr/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Documentation Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/php-generator/hu/@home.texy b/php-generator/hu/@home.texy index 30436e2022..ec759588d5 100644 --- a/php-generator/hu/@home.texy +++ b/php-generator/hu/@home.texy @@ -1,31 +1,32 @@ -PHP kód generátor -***************** +Nette PhpGenerator +****************** -<div class=perex> -- PHP kódot szeretne generálni osztályokhoz, függvényekhez, PHP fájlokhoz stb.? -- Támogatja az összes legújabb PHP funkciót, mint például az enumokat, attribútumokat stb. +<div class="perex"> +Eszközt keres osztályok, függvények vagy teljes PHP fájlok kódjának generálásához? + +- Ismeri az összes legújabb PHP funkciót (mint a property hookok, enumok, attribútumok stb.) - Lehetővé teszi a meglévő osztályok egyszerű módosítását -- PSR-12 szabványnak megfelelő kimenet -- Rendkívül kiforrott, stabil és széles körben használt könyvtár +- A kimeneti kód megfelel a PSR-12 / PER kódolási stílusnak +- Érett, stabil és széles körben használt könyvtár </div> -Telepítés .[#toc-installation] ------------------------------- +Telepítés +--------- -Töltse le és telepítse a csomagot a [Composer |best-practices:composer] segítségével: +A könyvtárat a [Composer|best-practices:composer] segítségével töltheti le és telepítheti: ```shell composer require nette/php-generator ``` -A PHP-kompatibilitásról lásd a [táblázatot |#Compatibility Table]. +A PHP kompatibilitást a [kompatibilitási táblázatban |#Kompatibilitási táblázat] találja. -Osztályok .[#toc-classes] -------------------------- +Osztályok +--------- -Kezdjük egy egyszerű példával az osztály generálására a [ClassType |api:Nette\PhpGenerator\ClassType] használatával: +Kezdjük rögtön egy példával egy osztály létrehozására a [ClassType |api:Nette\PhpGenerator\ClassType] segítségével: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -34,19 +35,19 @@ $class ->setFinal() ->setExtends(ParentClass::class) ->addImplement(Countable::class) - ->addComment("Description of class.\nSecond line\n") + ->addComment("Osztály leírása.\nMásodik sor\n") ->addComment('@property-read Nette\Forms\Form $form'); -// PHP kód generálásához egyszerűen csak öntsd stringre, vagy használd az echo-t: +// a kódot egyszerűen generálhatja stringgé alakítással vagy az echo használatával: echo $class; ``` -A következő eredményt adja ki: +A következő eredményt adja vissza: ```php /** - * Description of class. - * Second line + * Osztály leírása + * Második sor * * @property-read Nette\Forms\Form $form */ @@ -55,18 +56,18 @@ final class Demo extends ParentClass implements Countable } ``` -A kód generálásához használhatunk nyomtatót is, amelyet a `echo $class` címmel ellentétben [tovább konfigurálhatunk |#Printers and PSR compliance]: +A kód generálásához használhatunk egy ún. printert is, amelyet az `echo $class`-szal ellentétben [tovább konfigurálni |#Printer és PSR megfelelőség] tudunk: ```php $printer = new Nette\PhpGenerator\Printer; echo $printer->printClass($class); ``` -Konstanciákat (class [Constant |api:Nette\PhpGenerator\Constant]) és tulajdonságokat (class [Property |api:Nette\PhpGenerator\Property]) adhatunk hozzá: +Hozzáadhatunk konstansokat ([Constant |api:Nette\PhpGenerator\Constant] osztály) és propertyket ([Property |api:Nette\PhpGenerator\Property] osztály): ```php $class->addConstant('ID', 123) - ->setProtected() // konstans láthatóság + ->setProtected() // konstansok láthatósága ->setType('int') ->setFinal(); @@ -80,7 +81,7 @@ $class->addProperty('list') ->setInitialized(); // kiírja '= null' ``` -Ez generálja: +Generálja: ```php final protected const int ID = 123; @@ -91,14 +92,14 @@ private static $items = [1, 2, 3]; public ?array $list = null; ``` -És hozzáadhatunk [metódusokat |#Method and Function Signature]: +És hozzáadhatunk [metódusokat |#Metódus és függvény szignatúrák]: ```php $method = $class->addMethod('count') - ->addComment('Count it.') + ->addComment('Számold meg.') ->setFinal() ->setProtected() - ->setReturnType('?int') // módszer visszatérési típusa + ->setReturnType('?int') // visszatérési típusok metódusoknál ->setBody('return count($items ?: $this->items);'); $method->addParameter('items', []) // $items = [] @@ -106,11 +107,11 @@ $method->addParameter('items', []) // $items = [] ->setType('array'); // array &$items = [] ``` -Ez a következőket eredményezi: +Az eredmény: ```php /** - * Count it. + * Számold meg. */ final protected function count(array &$items = []): ?int { @@ -118,7 +119,7 @@ final protected function count(array &$items = []): ?int } ``` -A PHP 8.0 által bevezetett támogatott paraméterek átadhatók a konstruktornak: +A PHP 8.0 által bevezetett promoted paramétereket átadhatjuk a konstruktornak: ```php $method = $class->addMethod('__construct'); @@ -127,7 +128,7 @@ $method->addPromotedParameter('args', []) ->setPrivate(); ``` -Ez a következőket eredményezi: +Az eredmény: ```php public function __construct( @@ -137,15 +138,15 @@ public function __construct( } ``` -A csak olvasható tulajdonságok és osztályok a `setReadOnly()` címen keresztül jelölhetők meg. +A csak olvasható propertyket és osztályokat a `setReadOnly()` függvénnyel lehet megjelölni. ------ -Ha a hozzáadott tulajdonság, konstans, metódus vagy paraméter már létezik, akkor kivételt dob. +Ha a hozzáadott property, konstans, metódus vagy paraméter már létezik, kivétel dobódik. -A tagok eltávolítása a `removeProperty()`, `removeConstant()`, `removeMethod()` vagy a `removeParameter()` segítségével történhet. +Az osztály tagjait eltávolíthatjuk a `removeProperty()`, `removeConstant()`, `removeMethod()` vagy `removeParameter()` segítségével. -A már létező `Method`, `Property` vagy `Constant` objektumokat is hozzáadhatja az osztályhoz: +Az osztályhoz hozzáadhatunk meglévő `Method`, `Property` vagy `Constant` objektumokat is: ```php $method = new Nette\PhpGenerator\Method('getHandle'); @@ -158,7 +159,7 @@ $class = (new Nette\PhpGenerator\ClassType('Demo')) ->addMember($const); ``` -A meglévő metódusokat, tulajdonságokat és konstansokat más névvel klónozhatja a `cloneWithName()` segítségével: +Klónozhatunk meglévő metódusokat, propertyket és konstansokat más néven a `cloneWithName()` segítségével: ```php $methodCount = $class->getMethod('count'); @@ -167,17 +168,17 @@ $class->addMember($methodRecount); ``` -Interface vagy Trait .[#toc-interface-or-trait] ------------------------------------------------ +Interfész vagy Trait +-------------------- -Interfészeket és tulajdonságokat hozhat létre ( [InterfaceType |api:Nette\PhpGenerator\InterfaceType] és [TraitType |api:Nette\PhpGenerator\TraitType] osztályok): +Létrehozhat interfészeket és traitteket ([InterfaceType |api:Nette\PhpGenerator\InterfaceType] és [TraitType |api:Nette\PhpGenerator\TraitType] osztályok): ```php $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait'); ``` -Vonások használata: +Trait használata: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -202,10 +203,10 @@ class Demo ``` -Enums .[#toc-enums] -------------------- +Enumok +------ -A PHP 8.1 által hozott enumokat ( [EnumType |api:Nette\PhpGenerator\EnumType] osztály) könnyen létrehozhatod: +A PHP 8.1 által bevezetett enumokat könnyen létrehozhatja így: ([EnumType |api:Nette\PhpGenerator\EnumType] osztály): ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -229,20 +230,20 @@ enum Suit } ``` -Az esetek skaláris megfelelőit is definiálhatja, hogy létrehozzon egy támogatott enumot: +Definiálhat skaláris ekvivalenseket is, és létrehozhat egy "backed" enumot: ```php $enum->addCase('Clubs', '♣'); $enum->addCase('Diamonds', '♦'); ``` -A `addComment()` vagy a `addAttribute()` segítségével minden egyes esethez hozzáadhatunk egy megjegyzést vagy [attribútumokat |#attributes]. +Minden *case*-hez hozzáadhat kommentet vagy [#Attribútumok] a `addComment()` vagy `addAttribute()` segítségével. -Névtelen osztály .[#toc-anonymous-class] ----------------------------------------- +Névtelen osztályok +------------------ -Adjuk meg a `null` nevet, és máris van egy névtelen osztályunk: +Névként `null`-t adunk át, és máris van egy névtelen osztályunk: ```php $class = new Nette\PhpGenerator\ClassType(null); @@ -264,10 +265,10 @@ $obj = new class ($val) { ``` -Globális funkció .[#toc-global-function] ----------------------------------------- +Globális függvények +------------------- -A függvények kódja létrehozza a [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction] osztályt: +A függvények kódját a [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction] osztály generálja: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -276,7 +277,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// vagy használja a PsrPrintert a PSR-2 / PSR-12 szabványnak megfelelő kimenethez. +// vagy használja a PsrPrintert a PSR-2 / PSR-12 / PER szerinti kimenethez // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -290,10 +291,10 @@ function foo($a, $b) ``` -Zárás .[#toc-closure] ---------------------- +Névtelen függvények +------------------- -A lezárások kódja a [Closure |api:Nette\PhpGenerator\Closure] osztályt fogja létrehozni: +A névtelen függvények kódját a [Closure |api:Nette\PhpGenerator\Closure] osztály generálja: ```php $closure = new Nette\PhpGenerator\Closure; @@ -304,7 +305,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// vagy használja a PsrPrintert a PSR-2 / PSR-12 szabványnak megfelelő kimenethez. +// vagy használja a PsrPrintert a PSR-2 / PSR-12 / PER szerinti kimenethez // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -317,10 +318,10 @@ function ($a, $b) use (&$c) { ``` -Nyíl funkció .[#toc-arrow-function] ------------------------------------ +Rövidített nyíl függvények +-------------------------- -A lezárást nyíl funkcióként is kinyomtathatja a nyomtató segítségével: +Kiírhat egy rövidített névtelen függvényt is a printer segítségével: ```php $closure = new Nette\PhpGenerator\Closure; @@ -338,38 +339,38 @@ fn($a, $b) => $a + $b ``` -Módszer és funkció aláírása .[#toc-method-and-function-signature] ------------------------------------------------------------------ +Metódus és függvény szignatúrák +------------------------------- -A metódusokat a [Method |api:Nette\PhpGenerator\Method] osztály képviseli. Beállíthatja a láthatóságot, a visszatérési értéket, megjegyzéseket, [attribútumokat |#Attributes] stb. adhat hozzá: +A metódusokat a [Method |api:Nette\PhpGenerator\Method] osztály reprezentálja. Beállíthatja a láthatóságot, a visszatérési értéket, hozzáadhat kommenteket, [attribútumokat |#Attribútumok] stb.: ```php $method = $class->addMethod('count') - ->addComment('Count it.') + ->addComment('Számold meg.') ->setFinal() ->setProtected() ->setReturnType('?int'); ``` -Minden paramétert egy [Parameter |api:Nette\PhpGenerator\Parameter] osztály képvisel. Ismét minden elképzelhető tulajdonságot beállíthat: +Az egyes paramétereket a [Parameter |api:Nette\PhpGenerator\Parameter] osztály reprezentálja. Ismét beállíthat minden elképzelhető tulajdonságot: ```php $method->addParameter('items', []) // $items = [] - ->setReference() // &$items = [] - ->setType('array'); // array &$items = [] + ->setReference() // &$items = [] + ->setType('array'); // array &$items = [] -// function count(&$items = []) +// function count(array &$items = []) ``` -Az úgynevezett variadics paraméterek (vagy akár a splat, spread, ellipszis, kipakolás vagy a három pont operátor) definiálásához használja a `setVariadics()`: +Az ún. variadics paraméterek (vagy splat operátor) definiálására a `setVariadic()` szolgál: ```php $method = $class->addMethod('count'); -$method->setVariadics(true); +$method->setVariadic(true); $method->addParameter('items'); ``` -Generates: +Generálja: ```php function count(...$items) @@ -378,10 +379,10 @@ function count(...$items) ``` -Módszer és funkciótest .[#toc-method-and-function-body] -------------------------------------------------------- +Metódus és függvény törzsek +--------------------------- -A test átadható a `setBody()` metódusnak egyszerre vagy szekvenciálisan (soronként) a `addBody()` ismételt hívásával: +A törzset átadhatjuk egyszerre a `setBody()` metódusnak, vagy fokozatosan (soronként) az `addBody()` ismételt hívásával: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -400,9 +401,9 @@ function foo() } ``` -Speciális helyőrzőket használhat a változók befecskendezésének praktikus módjához. +Speciális helyettesítő karaktereket használhat a változók egyszerű beillesztéséhez. -Egyszerű helytartók `?` +Egyszerű helyettesítő szimbólumok `?` ```php $str = 'any string'; @@ -412,7 +413,7 @@ $function->addBody('return substr(?, ?);', [$str, $num]); echo $function; ``` -Eredmény: +Eredmény ```php function foo() @@ -421,7 +422,7 @@ function foo() } ``` -Variadic placeholder `...?` +Helyettesítő karakter variadic-hoz `...?` ```php $items = [1, 2, 3]; @@ -439,7 +440,7 @@ function foo() } ``` -A PHP 8 névvel ellátott paramétereket is használhatja a helyőrző használatával. `...?:` +Használhat névvel ellátott paramétereket is a PHP 8-hoz a `...?:` segítségével ```php $items = ['foo' => 1, 'bar' => true]; @@ -448,7 +449,7 @@ $function->setBody('myfunc(...?:);', [$items]); // myfunc(foo: 1, bar: true); ``` -Helytartó szláv segítségével menekülhet `\?` +A helyettesítő szimbólumot a `\` karakterrel lehet escape-elni `\?` ```php $num = 3; @@ -468,56 +469,81 @@ function foo($a) ``` -Nyomtatók és PSR-megfelelőség .[#toc-printers-and-psr-compliance] ------------------------------------------------------------------ +Printer és PSR megfelelőség +--------------------------- -A PHP-kódot a `Printer` objektumok generálják. Létezik egy `PsrPrinter`, amelynek kimenete megfelel a PSR-2 és PSR-12 szabványoknak, és a behúzásnál szóközöket használ, valamint egy `Printer`, amely a behúzásnál tabulátorokat használ. +A PHP kód generálására a [Printer |api:Nette\PhpGenerator\Printer] osztály szolgál: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // ugyanaz, mint: echo $class +``` + +Képes generálni az összes többi elem kódját, kínál metódusokat, mint a `printFunction()`, `printNamespace()`, stb. + +Rendelkezésre áll a `PsrPrinter` osztály is, amelynek kimenete megfelel a PSR-2 / PSR-12 / PER kódolási stílusnak: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // 4 szóköz behúzás +echo $printer->printClass($class); ``` -Testreszabni szeretné a nyomtató viselkedését? Hozzon létre sajátot a `Printer` osztály öröklésével. Ezeket a változókat átkonfigurálhatja: +Szeretné testre szabni a viselkedést? Hozzon létre saját verziót a `Printer` osztály öröklésével. Ezeket a változókat lehet újrakonfigurálni: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // sor hossza, amely után sortörés történik public int $wrapLength = 120; + // behúzás karaktere, helyettesíthető szóközök sorozatával public string $indentation = "\t"; + // üres sorok száma a propertyk között public int $linesBetweenProperties = 0; + // üres sorok száma a metódusok között public int $linesBetweenMethods = 2; + // üres sorok száma az 'use statements' csoportok között osztályokhoz, függvényekhez és konstansokhoz public int $linesBetweenUseTypes = 0; + // nyitó kapcsos zárójel pozíciója függvényeknél és metódusoknál public bool $bracesOnNextLine = true; + // helyezzen egy paramétert egy sorba, még akkor is, ha attribútuma van vagy promoted + public bool $singleParameterOnOneLine = false; + // kihagyja azokat a névtereket, amelyek nem tartalmaznak osztályt vagy függvényt + public bool $omitEmptyNamespaces = true; + // elválasztó a jobb zárójel és a függvények és metódusok visszatérési típusa között public string $returnTypeColon = ': '; } ``` +Hogyan és miért különbözik valójában a standard `Printer` és a `PsrPrinter`? Miért nincs csak egy printer a csomagban, mégpedig a `PsrPrinter`? + +A standard `Printer` úgy formázza a kódot, ahogyan azt az egész Nette-ben tesszük. Mivel a Nette sokkal korábban jött létre, mint a PSR, és mivel a PSR évekig nem szállított időben szabványokat, hanem például többéves késéssel az új PHP funkció bevezetése után, előfordult, hogy a [kódolási szabvány |contributing:coding-standard] néhány apróságban eltér. A nagyobb különbség csak a tabulátorok használata szóközök helyett. Tudjuk, hogy a tabulátorok használata projektjeinkben lehetővé teszi a szélesség testreszabását, ami [látássérültek számára elengedhetetlen |contributing:coding-standard#Tabulátorok szóközök helyett]. Egy apró eltérés példája a kapcsos zárójel elhelyezése külön sorban a függvényeknél és metódusoknál, és ez mindig így van. A PSR ajánlása számunkra logikátlannak tűnik, és [a kód olvashatóságának csökkenéséhez |contributing:coding-standard#Tördelés és zárójelek] vezet. + -Típusok .[#toc-types] ---------------------- +Típusok +------- -Minden típus vagy union/intersection típus átadható stringként, a natív típusokhoz előre definiált konstansokat is használhat: +Minden típust vagy union/intersection típust átadhatunk stringként, használhatunk előre definiált konstansokat is a natív típusokhoz: ```php use Nette\PhpGenerator\Type; $member->setType('array'); // vagy Type::Array; -$member->setType('array|string'); // vagy Type::union('array', 'string') +$member->setType('?array'); // vagy Type::nullable(Type::Array); +$member->setType('array|string'); // vagy Type::union(Type::Array, Type::String) $member->setType('Foo&Bar'); // vagy Type::intersection(Foo::class, Bar::class) $member->setType(null); // eltávolítja a típust ``` -Ugyanez vonatkozik a `setReturnType()` módszerre is. +Ugyanez vonatkozik a `setReturnType()` metódusra is. -Literálisok .[#toc-literals] ----------------------------- +Literálok +--------- -A `Literal` segítségével tetszőleges PHP kódot adhat át, például alapértelmezett tulajdonság vagy paraméter értékeket stb: +A `Literal` segítségével tetszőleges PHP kódot adhatunk át, például propertyk vagy paraméterek alapértelmezett értékeihez stb.: ```php use Nette\PhpGenerator\Literal; @@ -545,25 +571,37 @@ class Demo } ``` -A `Literal` oldalnak paramétereket is átadhat, és azt [speciális helyőrzőket |#method-and-function-body-generator] használva érvényes PHP-kóddá formázhatja: +Paramétereket is átadhat a `Literal`-nak, és hagyhatja, hogy érvényes PHP kóddá formázza őket [helyettesítő karaktereket |#Metódus és függvény törzsek] használva: ```php new Literal('substr(?, ?)', [$a, $b]); -// generál, például: substr('hello', 5); +// generál például: substr('hello', 5); ``` +Egy új objektum létrehozását reprezentáló literált könnyen generálhatunk a `new` metódussal: -Attribútumok .[#toc-attributes] -------------------------------- +```php +Literal::new(Demo::class, [$a, 'foo' => $b]); +// generál például: new Demo(10, foo: 20) +``` -A PHP 8 attribútumokat minden osztályhoz, metódushoz, tulajdonsághoz, konstanshoz, enum esetekhez, függvényekhez, lezárásokhoz és paraméterekhez hozzáadhatja. [Irodalmi karakterek |#Literals] is használhatók paraméterértékként. + +Attribútumok +------------ + +A PHP 8 attribútumokat hozzáadhatja az összes osztályhoz, metódushoz, propertyhez, konstanshoz, enumhoz, függvényhez, closure-höz és paraméterhez. Paraméterértékként [#Literálok] is használhatók. ```php $class = new Nette\PhpGenerator\ClassType('Demo'); -$class->addAttribute('Deprecated'); +$class->addAttribute('Table', [ + 'name' => 'user', + 'constraints' => [ + Literal::new('UniqueConstraint', ['name' => 'ean', 'columns' => ['ean']]), + ], +]); $class->addProperty('list') - ->addAttribute('WithArguments', [1, 2]); + ->addAttribute('Deprecated'); $method = $class->addMethod('count') ->addAttribute('Foo\Cached', ['mode' => true]); @@ -577,25 +615,109 @@ echo $class; Eredmény: ```php -#[Deprecated] +#[Table(name: 'user', constraints: [new UniqueConstraint(name: 'ean', columns: ['ean'])])] class Demo { - #[WithArguments(1, 2)] + #[Deprecated] public $list; #[Foo\Cached(mode: true)] - public function count(#[Bar] $items) - { + public function count( + #[Bar] + $items, + ) { } } ``` -Namespace .[#toc-namespace] ---------------------------- +Property Hookok +--------------- -Az osztályok, tulajdonságok, interfészek és enumok (a továbbiakban osztályok) névterekbe ([PhpNamespace |api:Nette\PhpGenerator\PhpNamespace]) csoportosíthatók: +A property hookok segítségével ([PropertyHook|api:Nette\PhpGenerator\PropertyHook] osztály által reprezentálva) definiálhat get és set műveleteket a propertykhez, ami a PHP 8.4-ben bevezetett funkció: + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$prop = $class->addProperty('firstName') + ->setType('string'); + +$prop->addHook('set', 'strtolower($value)') + ->addParameter('value') + ->setType('string'); + +$prop->addHook('get') + ->setBody('return ucfirst($this->firstName);'); + +echo $class; +``` + +Generálja: + +```php +class Demo +{ + public string $firstName { + set(string $value) => strtolower($value); + get { + return ucfirst($this->firstName); + } + } +} +``` + +A propertyk és property hookok lehetnek absztraktak vagy finálak: + +```php +$class->addProperty('id') + ->setType('int') + ->addHook('get') + ->setAbstract(); + +$class->addProperty('role') + ->setType('string') + ->addHook('set', 'strtolower($value)') + ->setFinal(); +``` + + +Aszimmetrikus láthatóság +------------------------ + +A PHP 8.4 bevezeti az aszimmetrikus láthatóságot a propertykhez. Különböző hozzáférési szinteket állíthat be az olvasáshoz és íráshoz. + +A láthatóságot beállíthatja vagy a `setVisibility()` metódussal két paraméterrel, vagy a `setPublic()`, `setProtected()` vagy `setPrivate()` metódusokkal a `mode` paraméterrel, amely meghatározza, hogy a láthatóság az olvasásra vagy az írásra vonatkozik-e. Az alapértelmezett mód `'get'`. + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); + +$class->addProperty('name') + ->setType('string') + ->setVisibility('public', 'private'); // public olvasáshoz, private íráshoz + +$class->addProperty('id') + ->setType('int') + ->setProtected('set'); // protected íráshoz + +echo $class; +``` + +Generálja: + +```php +class Demo +{ + public private(set) string $name; + + protected(set) int $id; +} +``` + + +Névtér +------ + +Az osztályokat, propertyket, interfészeket és enumokat (továbbiakban osztályok) csoportosíthatjuk névterekbe, amelyeket a [PhpNamespace |api:Nette\PhpGenerator\PhpNamespace] osztály reprezentál: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); @@ -605,14 +727,14 @@ $class = $namespace->addClass('Task'); $interface = $namespace->addInterface('Countable'); $trait = $namespace->addTrait('NameAware'); -// vagy egy meglévő osztály beillesztése a névtérbe +// vagy meglévő osztály beillesztése a névtérbe $class = new Nette\PhpGenerator\ClassType('Task'); $namespace->add($class); ``` -Ha az osztály már létezik, akkor kivételt dob. +Ha az osztály már létezik, kivétel dobódik. -Használati utasításokat definiálhat: +Definiálhat use klózokat: ```php // use Http\Request; @@ -623,14 +745,14 @@ $namespace->addUse(Http\Request::class, 'HttpReq'); $namespace->addUseFunction('iter\range'); ``` -A `simplifyName` módszerrel egyszerűsíthet egy teljesen minősített osztály-, függvény- vagy konstansnevet a definiált aliasoknak megfelelően: +Ha egyszerűsíteni szeretné a teljesen minősített osztály-, függvény- vagy konstansnevet a definiált aliasok szerint, használja a `simplifyName` metódust: ```php -echo $namespace->simplifyName('Foo\Bar'); // 'Bar', mert 'Foo' az aktuális névtér +echo $namespace->simplifyName('Foo\Bar'); // 'Bar', mert a 'Foo' az aktuális névtér echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', a definiált use-statement miatt ``` -Ezzel szemben az egyszerűsített osztály-, függvény- vagy konstansnevet a `resolveName` módszerrel alakíthatja át teljes minősítésűvé: +Az egyszerűsített osztály-, függvény- vagy konstansnevet fordítva átalakíthatja teljesen minősített névre a `resolveName` metódussal: ```php echo $namespace->resolveName('Bar'); // 'Foo\Bar' @@ -638,29 +760,27 @@ echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range' ``` -Osztálynevek feloldása .[#toc-class-names-resolving] ----------------------------------------------------- +Osztálynevek fordítása +---------------------- -**Ha az osztály a névtér része, akkor kissé másképp jelenik meg**: minden típus (azaz a típushivatkozások, a visszatérési típusok, a szülő osztály neve, -implementált interfészek, használt tulajdonságok és attribútumok) automatikusan *feloldódnak* (hacsak ki nem kapcsolod, lásd alább). -Ez azt jelenti, hogy a definíciókban **teljes osztályneveket** kell használnod, és ezek a keletkező kódban aliasokkal (a use-statementsnek megfelelően) vagy teljesen minősített nevekkel lesznek helyettesítve: +**Ha egy osztály egy névtér része, kissé eltérően jelenik meg:** minden típus (például typehintek, visszatérési típusok, szülőosztály neve, implementált interfészek, használt propertyk és attribútumok) automatikusan *lefordításra* kerül (hacsak nem kapcsolja ki, lásd alább). Ez azt jelenti, hogy a definíciókban **teljes osztályneveket kell használnia**, és ezeket aliasokra (a use klózok szerint) vagy teljesen minősített nevekre cseréli a végső kódban: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); $namespace->addUse('Bar\AliasedClass'); $class = $namespace->addClass('Demo'); -$class->addImplement('Foo\A') // egyszerűsödik A - ->addTrait('Bar\AliasedClass'); // AliasedClass-ra fog egyszerűsödni +$class->addImplement('Foo\A') // A-ra lesz egyszerűsítve + ->addTrait('Bar\AliasedClass'); // AliasedClass-ra lesz egyszerűsítve $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // a megjegyzésekben manuálisan egyszerűsítünk +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // kommentekben manuálisan egyszerűsítünk $method->addParameter('arg') - ->setType('Bar\OtherClass'); // feloldódik \Bar\OtherClass-ra + ->setType('Bar\OtherClass'); // \Bar\OtherClass-ra lesz fordítva echo $namespace; -// vagy használja a PsrPrintert a PSR-2 / PSR-12 szabványnak megfelelő kimenethez. +// vagy használja a PsrPrintert a PSR-2 / PSR-12 / PER szerinti kimenethez // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -684,7 +804,7 @@ class Demo implements A } ``` -Az automatikus feloldás így kikapcsolható: +Az automatikus fordítást így lehet kikapcsolni: ```php $printer = new Nette\PhpGenerator\Printer; // vagy PsrPrinter @@ -693,15 +813,15 @@ echo $printer->printNamespace($namespace); ``` -PHP Fájlok .[#toc-php-files] ----------------------------- +PHP fájlok +---------- -Az osztályok, függvények és névterek PHP fájlokba csoportosíthatók, amelyeket a [PhpFile |api:Nette\PhpGenerator\PhpFile] osztály képvisel: +Az osztályokat, függvényeket és névtereket PHP fájlokba csoportosíthatjuk, amelyeket a [PhpFile|api:Nette\PhpGenerator\PhpFile] osztály reprezentál: ```php $file = new Nette\PhpGenerator\PhpFile; -$file->addComment('This file is auto-generated.'); -$file->setStrictTypes(); // adds declare(strict_types=1) +$file->addComment('Ez a fájl automatikusan generált.'); +$file->setStrictTypes(); // hozzáadja declare(strict_types=1) $class = $file->addClass('Foo\A'); $function = $file->addFunction('Foo\foo'); @@ -713,7 +833,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// vagy használja a PsrPrintert a PSR-2 / PSR-12 szabványnak megfelelő kimenethez. +// vagy használja a PsrPrintert a PSR-2 / PSR-12 / PER szerinti kimenethez // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -723,7 +843,7 @@ Eredmény: <?php /** - * This file is auto-generated. + * Ez a fájl automatikusan generált. */ declare(strict_types=1); @@ -739,27 +859,28 @@ function foo() } ``` +**Figyelmeztetés:** A fájlokhoz nem lehet további kódot hozzáadni a függvényeken és osztályokon kívül. + -A meglévők szerint generálva .[#toc-generating-according-to-existing-ones] --------------------------------------------------------------------------- +Generálás meglévők alapján +-------------------------- -Amellett, hogy az osztályokat és függvényeket a fent leírt API segítségével modellezhetjük, lehetőségünk van arra is, hogy automatikusan generáljuk őket a meglévők alapján: +Amellett, hogy az osztályokat és függvényeket a fent leírt API segítségével modellezheti, automatikusan is generáltathatja őket meglévő minták alapján: ```php -// létrehoz egy, a PDO osztállyal azonos osztályt +// létrehoz egy osztályt, amely megegyezik a PDO osztállyal $class = Nette\PhpGenerator\ClassType::from(PDO::class); -// létrehoz egy, a trim() függvénnyel azonos függvényt +// létrehoz egy függvényt, amely azonos a trim() függvénnyel $function = Nette\PhpGenerator\GlobalFunction::from('trim'); -// létrehoz egy lezárást a megadottak szerint +// létrehoz egy closure-t a megadott alapján $closure = Nette\PhpGenerator\Closure::from( function (stdClass $a, $b = null) {}, ); ``` -A függvény- és metódustestek alapértelmezés szerint üresek. Ha ezeket is be akarja tölteni, használja ezt a módszert -(ehhez a `nikic/php-parser` oldal telepítése szükséges): +A függvények és metódusok törzsei alapértelmezés szerint üresek. Ha ezeket is be szeretné tölteni, használja ezt a módszert (szükséges a `nikic/php-parser` csomag telepítése): ```php $class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true); @@ -768,10 +889,10 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); ``` -Betöltés PHP fájlból .[#toc-loading-from-php-file] --------------------------------------------------- +Betöltés PHP fájlokból +---------------------- -A függvényeket, osztályokat, interfészeket és enumokat közvetlenül egy PHP kódsorozatból is betöltheti. Például így hozzuk létre a `ClassType` objektumot: +Függvényeket, osztályokat, interfészeket és enumokat közvetlenül PHP kódot tartalmazó stringből is betölthet. Például így hozunk létre egy `ClassType` objektumot: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -784,39 +905,69 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -Az osztályok PHP kódból történő betöltésekor a metódus testén kívüli egysoros megjegyzéseket figyelmen kívül hagyjuk (pl. tulajdonságok stb. esetén), mivel ez a könyvtár nem rendelkezik API-val ezek kezelésére. +Ha osztályokat PHP kódból tölt be, az egysoros kommentek a metódustörzseken kívül (pl. propertyknél stb.) figyelmen kívül maradnak, mivel ez a könyvtár nem rendelkezik API-val a kezelésükhöz. -A teljes PHP-fájlt közvetlenül is betöltheti, amely tetszőleges számú osztályt, függvényt vagy akár több névteret is tartalmazhat: +Közvetlenül betölthet egy teljes PHP fájlt is, amely tetszőleges számú osztályt, függvényt vagy akár névteret tartalmazhat: ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` -A kezdeti fájlkommentár és a `strict_types` nyilatkozat is betöltődik. Másrészt minden más globális kódot figyelmen kívül hagyunk. +Betöltődik a fájl bevezető kommentje és a `strict_types` deklaráció is. Ezzel szemben minden más globális kód figyelmen kívül marad. -Ehhez telepíteni kell a `nikic/php-parser` oldalt. +Szükséges, hogy a `nikic/php-parser` telepítve legyen. .[note] -Ha fájlokban lévő globális kódot vagy a metódusok testében lévő egyes utasításokat kell manipulálnia, jobb, ha közvetlenül a `nikic/php-parser` könyvtárat használja. +Ha globális kódot kell manipulálnia fájlokban vagy egyes utasításokat metódustörzsekben, jobb közvetlenül a `nikic/php-parser` könyvtárat használni. + + +Class Manipulator +----------------- + +A [ClassManipulator|api:Nette\PhpGenerator\ClassManipulator] osztály eszközöket biztosít az osztályok manipulálásához. +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$manipulator = new Nette\PhpGenerator\ClassManipulator($class); +``` -Változók Dumper .[#toc-variables-dumper] ----------------------------------------- +Az `inheritMethod()` metódus átmásol egy metódust a szülőosztályból vagy implementált interfészből az Ön osztályába. Ez lehetővé teszi a metódus felülírását vagy a szignatúrájának kiterjesztését: + +```php +$method = $manipulator->inheritMethod('bar'); +$method->setBody('...'); +``` -A Dumper egy változó egy elemezhető PHP-string reprezentációját adja vissza. Jobb és egyértelműbb kimenetet biztosít, mint a natív `var_export()` függvény. +Az `inheritProperty()` metódus átmásol egy propertyt a szülőosztályból az Ön osztályába. Ez akkor hasznos, ha ugyanazt a propertyt szeretné az osztályában, de esetleg más alapértelmezett értékkel: + +```php +$property = $manipulator->inheritProperty('foo'); +$property->setValue('new value'); +``` + +Az `implement()` metódus automatikusan implementálja az összes metódust és propertyt a megadott interfészből vagy absztrakt osztályból az Ön osztályában: + +```php +$manipulator->implement(SomeInterface::class); +// Most az Ön osztálya implementálja a SomeInterface-t és tartalmazza annak összes metódusát +``` + + +Változók kiírása +---------------- + +A `Dumper` osztály átalakít egy változót elemezhető PHP kóddá. Jobb és áttekinthetőbb kimenetet biztosít, mint a standard `var_export()` függvény. ```php $dumper = new Nette\PhpGenerator\Dumper; $var = ['a', 'b', 123]; -echo $dumper->dump($var); // prints ['a', 'b', 123] +echo $dumper->dump($var); // kiírja ['a', 'b', 123] ``` -Kompatibilitási táblázat .[#toc-compatibility-table] ----------------------------------------------------- - -A PhpGenerator 4.0 kompatibilis a PHP 8.0 és 8.2 közötti változatokkal. +Kompatibilitási táblázat +------------------------ -{{leftbar: nette:@menu-topics}} +A PhpGenerator 4.1 kompatibilis a PHP 8.0-tól 8.4-ig. diff --git a/php-generator/hu/@meta.texy b/php-generator/hu/@meta.texy new file mode 100644 index 0000000000..c00a2158aa --- /dev/null +++ b/php-generator/hu/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette dokumentáció}} +{{leftbar: nette:@menu-topics}} diff --git a/php-generator/it/@home.texy b/php-generator/it/@home.texy index 258f3cb2d3..1411e0c2f4 100644 --- a/php-generator/it/@home.texy +++ b/php-generator/it/@home.texy @@ -1,31 +1,32 @@ -Generatore di codice PHP -************************ - -<div class=perex> -- Avete bisogno di generare codice PHP per classi, funzioni, file PHP e così via? -- Supporta tutte le più recenti caratteristiche di PHP, come enum, attributi, ecc. -- Permette di modificare facilmente le classi esistenti -- Output conforme a PSR-12 -- Libreria altamente matura, stabile e ampiamente utilizzata +Nette PhpGenerator +****************** + +<div class="perex"> +Cerchi uno strumento per generare codice PHP per classi, funzioni o file completi? + +- Supporta tutte le ultime novità di PHP (come property hooks, enum, attributi, ecc.) +- Ti consente di modificare facilmente le classi esistenti +- Il codice di output è conforme allo stile di codifica PSR-12 / PER +- Libreria matura, stabile e ampiamente utilizzata </div> -Installazione .[#toc-installation] ----------------------------------- +Installazione +------------- -Scaricare e installare il pacchetto utilizzando [Composer |best-practices:composer]: +Scarica e installa la libreria utilizzando [Composer |best-practices:composer]: ```shell composer require nette/php-generator ``` -Per la compatibilità con PHP, vedere la [tabella |#Compatibility Table]. +La compatibilità con PHP è disponibile nella [tabella |#Tabella di compatibilità]. -Classi .[#toc-classes] ----------------------- +Classi +------ -Cominciamo con un esempio semplice di generazione di classi usando [ClassType |api:Nette\PhpGenerator\ClassType]: +Iniziamo subito con un esempio di creazione di una classe utilizzando [ClassType |api:Nette\PhpGenerator\ClassType]: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -34,19 +35,19 @@ $class ->setFinal() ->setExtends(ParentClass::class) ->addImplement(Countable::class) - ->addComment("Description of class.\nSecond line\n") + ->addComment("Descrizione della classe.\nSeconda riga\n") ->addComment('@property-read Nette\Forms\Form $form'); -// per generare codice PHP è sufficiente eseguire il cast in stringa o usare echo: +// genera semplicemente il codice convertendolo in stringa o usando echo: echo $class; ``` -Il risultato sarà questo: +Restituisce il seguente risultato: ```php /** - * Description of class. - * Second line + * Descrizione della classe + * Seconda riga * * @property-read Nette\Forms\Form $form */ @@ -55,7 +56,7 @@ final class Demo extends ParentClass implements Countable } ``` -Possiamo anche utilizzare una stampante per generare il codice che, a differenza di `echo $class`, potremo [configurare ulteriormente |#Printers and PSR compliance]: +Possiamo anche utilizzare il cosiddetto printer per generare il codice, che a differenza di `echo $class` potremo [configurare ulteriormente |#Printer e conformità con PSR]: ```php $printer = new Nette\PhpGenerator\Printer; @@ -66,7 +67,7 @@ Possiamo aggiungere costanti (classe [Constant |api:Nette\PhpGenerator\Constant] ```php $class->addConstant('ID', 123) - ->setProtected() // visiblità costante + ->setProtected() // visibilità delle costanti ->setType('int') ->setFinal(); @@ -77,13 +78,13 @@ $class->addProperty('items', [1, 2, 3]) $class->addProperty('list') ->setType('?array') - ->setInitialized(); // stampa '= null' + ->setInitialized(); // scrive '= null' ``` Genera: ```php -finale protetto const int ID = 123; +final protected const int ID = 123; /** @var int[] */ private static $items = [1, 2, 3]; @@ -91,14 +92,14 @@ private static $items = [1, 2, 3]; public ?array $list = null; ``` -E possiamo aggiungere [metodi |#Method and Function Signature]: +E possiamo aggiungere [metodi |#Firme di metodi e funzioni]: ```php $method = $class->addMethod('count') - ->addComment('Count it.') + ->addComment('Contalo.') ->setFinal() ->setProtected() - ->setReturnType('?int') // metodo tipo di ritorno + ->setReturnType('?int') // tipi di ritorno nei metodi ->setBody('return count($items ?: $this->items);'); $method->addParameter('items', []) // $items = [] @@ -110,7 +111,7 @@ Il risultato è: ```php /** - * Count it. + * Contalo. */ final protected function count(array &$items = []): ?int { @@ -137,15 +138,15 @@ public function __construct( } ``` -Le proprietà e le classi di sola lettura possono essere contrassegnate tramite `setReadOnly()`. +Le proprietà e le classi di sola lettura possono essere contrassegnate utilizzando la funzione `setReadOnly()`. ------ Se la proprietà, la costante, il metodo o il parametro aggiunto esistono già, viene lanciata un'eccezione. -I membri possono essere rimossi utilizzando `removeProperty()`, `removeConstant()`, `removeMethod()` o `removeParameter()`. +I membri della classe possono essere rimossi utilizzando `removeProperty()`, `removeConstant()`, `removeMethod()` o `removeParameter()`. -È anche possibile aggiungere alla classe oggetti esistenti `Method`, `Property` o `Constant`: +Puoi anche aggiungere oggetti `Method`, `Property` o `Constant` esistenti alla classe: ```php $method = new Nette\PhpGenerator\Method('getHandle'); @@ -158,7 +159,7 @@ $class = (new Nette\PhpGenerator\ClassType('Demo')) ->addMember($const); ``` -È possibile clonare metodi, proprietà e costanti esistenti con un nome diverso, utilizzando `cloneWithName()`: +Puoi anche clonare metodi, proprietà e costanti esistenti con un nome diverso utilizzando `cloneWithName()`: ```php $methodCount = $class->getMethod('count'); @@ -167,17 +168,17 @@ $class->addMember($methodRecount); ``` -Interfaccia o tratto .[#toc-interface-or-trait] ------------------------------------------------ +Interfaccia o trait +------------------- -È possibile creare interfacce e tratti (classi [InterfaceType |api:Nette\PhpGenerator\InterfaceType] e [TraitType |api:Nette\PhpGenerator\TraitType]): +Puoi creare interfacce e trait (classi [InterfaceType |api:Nette\PhpGenerator\InterfaceType] e [TraitType |api:Nette\PhpGenerator\TraitType]): ```php $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait'); ``` -Utilizzo dei tratti: +Utilizzo dei trait: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -202,10 +203,10 @@ class Demo ``` -Enum .[#toc-enums] ------------------- +Enum +---- -È possibile creare facilmente gli enum introdotti da PHP 8.1 (classe [EnumType |api:Nette\PhpGenerator\EnumType]): +Puoi facilmente creare le enumerazioni introdotte da PHP 8.1 in questo modo: (classe [EnumType |api:Nette\PhpGenerator\EnumType]): ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -229,20 +230,20 @@ enum Suit } ``` -È possibile definire anche equivalenti scalari per i casi, in modo da creare un'enum supportata: +Puoi anche definire equivalenti scalari e creare così un'enumerazione "backed": ```php $enum->addCase('Clubs', '♣'); $enum->addCase('Diamonds', '♦'); ``` -È possibile aggiungere un commento o degli [attributi |#attributes] a ciascun caso utilizzando `addComment()` o `addAttribute()`. +Ad ogni *case* è possibile aggiungere un commento o [#attributi] utilizzando `addComment()` o `addAttribute()`. -Classe anonima .[#toc-anonymous-class] --------------------------------------- +Classi anonime +-------------- -Date il nome `null` e avrete una classe anonima: +Passiamo `null` come nome e abbiamo una classe anonima: ```php $class = new Nette\PhpGenerator\ClassType(null); @@ -264,10 +265,10 @@ $obj = new class ($val) { ``` -Funzione globale .[#toc-global-function] ----------------------------------------- +Funzioni globali +---------------- -Il codice delle funzioni genererà la classe [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction]: +Il codice delle funzioni viene generato dalla classe [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction]: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -276,7 +277,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// oppure utilizzare PsrPrinter per ottenere un output conforme a PSR-2 / PSR-12 +// oppure usa PsrPrinter per l'output conforme a PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -290,10 +291,10 @@ function foo($a, $b) ``` -Chiusura .[#toc-closure] ------------------------- +Funzioni anonime +---------------- -Il codice delle chiusure genererà la classe [Closure |api:Nette\PhpGenerator\Closure]: +Il codice delle funzioni anonime viene generato dalla classe [Closure |api:Nette\PhpGenerator\Closure]: ```php $closure = new Nette\PhpGenerator\Closure; @@ -304,7 +305,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// oppure utilizzare PsrPrinter per un output conforme a PSR-2 / PSR-12 +// oppure usa PsrPrinter per l'output conforme a PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -317,10 +318,10 @@ function ($a, $b) use (&$c) { ``` -Funzione freccia .[#toc-arrow-function] ---------------------------------------- +Funzioni freccia abbreviate +--------------------------- -È inoltre possibile stampare la chiusura come funzione freccia utilizzando la stampante: +Puoi anche stampare una funzione anonima abbreviata utilizzando il printer: ```php $closure = new Nette\PhpGenerator\Closure; @@ -338,34 +339,34 @@ fn($a, $b) => $a + $b ``` -Firma di metodi e funzioni .[#toc-method-and-function-signature] ----------------------------------------------------------------- +Firme di metodi e funzioni +-------------------------- -I metodi sono rappresentati dalla classe [Metodo |api:Nette\PhpGenerator\Method]. È possibile impostare la visibilità, il valore di ritorno, aggiungere commenti, [attributi |#Attributes] ecc: +I metodi sono rappresentati dalla classe [Method |api:Nette\PhpGenerator\Method]. Puoi impostare la visibilità, il valore di ritorno, aggiungere commenti, [#attributi], ecc: ```php $method = $class->addMethod('count') - ->addComment('Count it.') + ->addComment('Contalo.') ->setFinal() ->setProtected() ->setReturnType('?int'); ``` -Ogni parametro è rappresentato da una classe [Parametro |api:Nette\PhpGenerator\Parameter]. Anche in questo caso, si possono impostare tutte le proprietà possibili: +I singoli parametri sono rappresentati dalla classe [Parameter |api:Nette\PhpGenerator\Parameter]. Anche qui puoi impostare tutte le proprietà immaginabili: ```php $method->addParameter('items', []) // $items = [] - ->setReference() // &$items = [] - ->setType('array'); // array &$items = [] + ->setReference() // &$items = [] + ->setType('array'); // array &$items = [] -// function count(&$items = []) +// function count(array &$items = []) ``` -Per definire i cosiddetti parametri variadici (o anche gli operatori splat, spread, ellipsis, unpacking o tre punti), utilizzare `setVariadics()`: +Per definire i cosiddetti parametri variadici (o anche operatore splat) serve `setVariadic()`: ```php $method = $class->addMethod('count'); -$method->setVariadics(true); +$method->setVariadic(true); $method->addParameter('items'); ``` @@ -378,10 +379,10 @@ function count(...$items) ``` -Metodo e corpo della funzione .[#toc-method-and-function-body] --------------------------------------------------------------- +Corpi di metodi e funzioni +-------------------------- -Il corpo può essere passato al metodo `setBody()` in una sola volta o in sequenza (riga per riga) chiamando ripetutamente `addBody()`: +Il corpo può essere passato tutto in una volta al metodo `setBody()` o gradualmente (riga per riga) chiamando ripetutamente `addBody()`: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -400,9 +401,9 @@ function foo() } ``` -È possibile utilizzare segnaposto speciali per iniettare variabili in modo pratico. +Puoi usare segnaposto speciali per inserire facilmente le variabili. -Segnaposto semplici `?` +Semplici segnaposto `?` ```php $str = 'any string'; @@ -412,7 +413,7 @@ $function->addBody('return substr(?, ?);', [$str, $num]); echo $function; ``` -Risultato: +Risultato ```php function foo() @@ -421,7 +422,7 @@ function foo() } ``` -Segnaposto variabile `...?` +Segnaposto per variadic `...?` ```php $items = [1, 2, 3]; @@ -439,7 +440,7 @@ function foo() } ``` -È anche possibile utilizzare i parametri denominati di PHP 8 utilizzando il segnaposto `...?:` +Puoi anche usare parametri nominati per PHP 8 usando `...?:` ```php $items = ['foo' => 1, 'bar' => true]; @@ -448,7 +449,7 @@ $function->setBody('myfunc(...?:);', [$items]); // myfunc(foo: 1, bar: true); ``` -Sfuggire al segnaposto usando una barra `\?` +Il segnaposto viene escapato con una barra rovesciata `\?` ```php $num = 3; @@ -468,45 +469,70 @@ function foo($a) ``` -Stampanti e conformità PSR .[#toc-printers-and-psr-compliance] --------------------------------------------------------------- +Printer e conformità con PSR +---------------------------- -Il codice PHP è generato dagli oggetti `Printer`. Esiste un `PsrPrinter` il cui output è conforme a PSR-2 e PSR-12 e utilizza gli spazi per l'indentazione e un `Printer` che utilizza le tabulazioni per l'indentazione. +Per generare codice PHP serve la classe [Printer |api:Nette\PhpGenerator\Printer]: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // lo stesso di: echo $class +``` + +Può generare codice per tutti gli altri elementi, offre metodi come `printFunction()`, `printNamespace()`, ecc. + +È disponibile anche la classe `PsrPrinter`, il cui output è conforme allo stile di codifica PSR-2 / PSR-12 / PER: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // 4 spazi di rientro +echo $printer->printClass($class); ``` -Avete bisogno di personalizzare il comportamento della stampante? Createne una vostra ereditando la classe `Printer`. È possibile riconfigurare queste variabili: +Hai bisogno di personalizzare il comportamento? Crea la tua versione ereditando la classe `Printer`. È possibile riconfigurare queste variabili: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // lunghezza della riga dopo la quale avviene l'a capo public int $wrapLength = 120; + // carattere di indentazione, può essere sostituito da una sequenza di spazi public string $indentation = "\t"; + // numero di righe vuote tra le proprietà public int $linesBetweenProperties = 0; + // numero di righe vuote tra i metodi public int $linesBetweenMethods = 2; + // numero di righe vuote tra i gruppi di 'use statements' per classi, funzioni e costanti public int $linesBetweenUseTypes = 0; + // posizione della parentesi graffa di apertura per funzioni e metodi public bool $bracesOnNextLine = true; + // posiziona un singolo parametro su una riga, anche se ha un attributo o è promosso + public bool $singleParameterOnOneLine = false; + // omette i namespace che non contengono alcuna classe o funzione + public bool $omitEmptyNamespaces = true; + // separatore tra la parentesi destra e il tipo di ritorno di funzioni e metodi public string $returnTypeColon = ': '; } ``` +Come e perché differiscono effettivamente il `Printer` standard e il `PsrPrinter`? Perché nel pacchetto non c'è solo un printer, ovvero `PsrPrinter`? -Tipi .[#toc-types] ------------------- +Il `Printer` standard formatta il codice come facciamo in tutto Nette. Poiché Nette è nato molto prima di PSR, e anche perché PSR per molti anni non ha fornito standard in tempo, ma ad esempio solo con diversi anni di ritardo rispetto all'introduzione di una nuova feature in PHP, è successo che lo [standard di codifica |contributing:coding-standard] differisce in alcuni piccoli dettagli. La differenza maggiore è solo l'uso di tabulazioni invece di spazi. Sappiamo che utilizzando le tabulazioni nei nostri progetti consentiamo la personalizzazione della larghezza, che è [necessaria per le persone con disabilità visive |contributing:coding-standard#Tabulazioni invece di spazi]. Un esempio di piccola differenza è il posizionamento della parentesi graffa su una riga separata per funzioni e metodi e sempre. La raccomandazione di PSR ci sembra illogica e porta a una [riduzione della leggibilità del codice |contributing:coding-standard#A capo e parentesi graffe]. -Ogni tipo o tipo di unione/intersezione può essere passato come stringa; si possono anche usare costanti predefinite per i tipi nativi: + +Tipi +---- + +Ogni tipo o tipo union/intersection può essere passato come stringa, puoi anche usare costanti predefinite per i tipi nativi: ```php use Nette\PhpGenerator\Type; $member->setType('array'); // o Type::Array; -$member->setType('array|string'); // o Type::union('array', 'stringa') +$member->setType('?array'); // o Type::nullable(Type::Array); +$member->setType('array|string'); // o Type::union(Type::Array, Type::String) $member->setType('Foo&Bar'); // o Type::intersection(Foo::class, Bar::class) $member->setType(null); // rimuove il tipo ``` @@ -514,10 +540,10 @@ $member->setType(null); // rimuove il tipo Lo stesso vale per il metodo `setReturnType()`. -Letterali .[#toc-literals] --------------------------- +Letterali +--------- -Con `Literal` è possibile passare codice PHP arbitrario, ad esempio per i valori predefiniti di proprietà o parametri, ecc: +Con `Literal` puoi passare qualsiasi codice PHP, ad esempio per i valori predefiniti di proprietà o parametri, ecc: ```php use Nette\PhpGenerator\Literal; @@ -545,25 +571,37 @@ class Demo } ``` -È anche possibile passare dei parametri a `Literal` e farli formattare in codice PHP valido, utilizzando [speciali segnaposto |#method-and-function-body-generator]: +Puoi anche passare parametri a `Literal` e farli formattare in codice PHP valido usando [segnaposto |#Corpi di metodi e funzioni]: ```php new Literal('substr(?, ?)', [$a, $b]); -// genera, ad esempio: substr('ciao', 5); +// genera ad esempio: substr('hello', 5); ``` +Un letterale che rappresenta la creazione di un nuovo oggetto può essere facilmente generato usando il metodo `new`: -Attributi .[#toc-attributes] ----------------------------- +```php +Literal::new(Demo::class, [$a, 'foo' => $b]); +// genera ad esempio: new Demo(10, foo: 20) +``` -È possibile aggiungere attributi di PHP 8 a tutte le classi, i metodi, le proprietà, le costanti, i casi enum, le funzioni, le chiusure e i parametri. Anche i [letterali |#Literals] possono essere usati come valori dei parametri. + +Attributi +--------- + +Puoi aggiungere attributi PHP 8 a tutte le classi, metodi, proprietà, costanti, enum, funzioni, closure e parametri. Come valori dei parametri si possono usare anche [#letterali]. ```php $class = new Nette\PhpGenerator\ClassType('Demo'); -$class->addAttribute('Deprecated'); +$class->addAttribute('Table', [ + 'name' => 'user', + 'constraints' => [ + Literal::new('UniqueConstraint', ['name' => 'ean', 'columns' => ['ean']]), + ], +]); $class->addProperty('list') - ->addAttribute('WithArguments', [1, 2]); + ->addAttribute('Deprecated'); $method = $class->addMethod('count') ->addAttribute('Foo\Cached', ['mode' => true]); @@ -577,42 +615,126 @@ echo $class; Risultato: ```php -#[Deprecated] +#[Table(name: 'user', constraints: [new UniqueConstraint(name: 'ean', columns: ['ean'])])] class Demo { - #[WithArguments(1, 2)] + #[Deprecated] public $list; #[Foo\Cached(mode: true)] - public function count(#[Bar] $items) - { + public function count( + #[Bar] + $items, + ) { } } ``` -Spazio dei nomi .[#toc-namespace] ---------------------------------- +Property Hooks +-------------- + +Con i property hooks (rappresentati dalla classe [PropertyHook |api:Nette\PhpGenerator\PropertyHook]) puoi definire operazioni get e set per le proprietà, una funzione introdotta in PHP 8.4: + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$prop = $class->addProperty('firstName') + ->setType('string'); + +$prop->addHook('set', 'strtolower($value)') + ->addParameter('value') + ->setType('string'); + +$prop->addHook('get') + ->setBody('return ucfirst($this->firstName);'); + +echo $class; +``` + +Genera: + +```php +class Demo +{ + public string $firstName { + set(string $value) => strtolower($value); + get { + return ucfirst($this->firstName); + } + } +} +``` -Classi, tratti, interfacce ed enum (di seguito classi) possono essere raggruppati in spazi dei nomi ([PhpNamespace |api:Nette\PhpGenerator\PhpNamespace]): +Le proprietà e i property hooks possono essere astratti o finali: + +```php +$class->addProperty('id') + ->setType('int') + ->addHook('get') + ->setAbstract(); + +$class->addProperty('role') + ->setType('string') + ->addHook('set', 'strtolower($value)') + ->setFinal(); +``` + + +Visibilità asimmetrica +---------------------- + +PHP 8.4 introduce la visibilità asimmetrica per le proprietà. Puoi impostare diversi livelli di accesso per la lettura e la scrittura. + +La visibilità può essere impostata sia con il metodo `setVisibility()` con due parametri, sia con `setPublic()`, `setProtected()` o `setPrivate()` con il parametro `mode`, che specifica se la visibilità si applica alla lettura o alla scrittura della proprietà. La modalità predefinita è `'get'`. + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); + +$class->addProperty('name') + ->setType('string') + ->setVisibility('public', 'private'); // public per la lettura, private per la scrittura + +$class->addProperty('id') + ->setType('int') + ->setProtected('set'); // protected per la scrittura + +echo $class; +``` + +Genera: + +```php +class Demo +{ + public private(set) string $name; + + protected(set) int $id; +} +``` + + +Namespace +--------- + +Classi, proprietà, interfacce ed enum (di seguito classi) possono essere raggruppate in namespace rappresentati dalla classe [PhpNamespace |api:Nette\PhpGenerator\PhpNamespace]: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); -// creare nuove classi nello spazio dei nomi +// creiamo nuove classi nel namespace $class = $namespace->addClass('Task'); $interface = $namespace->addInterface('Countable'); $trait = $namespace->addTrait('NameAware'); -// o inserire una classe esistente nello spazio dei nomi +// oppure inseriamo una classe esistente nel namespace $class = new Nette\PhpGenerator\ClassType('Task'); $namespace->add($class); ``` Se la classe esiste già, viene lanciata un'eccezione. -È possibile definire dichiarazioni d'uso: +Puoi definire clausole use: ```php // use Http\Request; @@ -623,14 +745,14 @@ $namespace->addUse(Http\Request::class, 'HttpReq'); $namespace->addUseFunction('iter\range'); ``` -Per semplificare il nome di una classe, di una funzione o di una costante completamente qualificata secondo gli alias definiti, utilizzare il metodo `simplifyName`: +Per semplificare il nome completo di una classe, funzione o costante secondo gli alias definiti, usa il metodo `simplifyName`: ```php -echo $namespace->simplifyName('Foo\Bar'); // 'Bar', perché 'Foo' è lo spazio dei nomi attuale -echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', a causa della dichiarazione d'uso definita +echo $namespace->simplifyName('Foo\Bar'); // 'Bar', perché 'Foo' è il namespace corrente +echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', a causa della use-statement definita ``` -Al contrario, è possibile convertire un nome di classe, funzione o costante semplificato in uno pienamente qualificato, utilizzando il metodo `resolveName`: +Al contrario, puoi convertire il nome semplificato di una classe, funzione o costante nel nome completo usando il metodo `resolveName`: ```php echo $namespace->resolveName('Bar'); // 'Foo\Bar' @@ -638,29 +760,27 @@ echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range' ``` -Risoluzione dei nomi di classe .[#toc-class-names-resolving] ------------------------------------------------------------- +Traduzioni dei nomi delle classi +-------------------------------- -**Quando la classe fa parte dello spazio dei nomi, viene resa in modo leggermente diverso**: tutti i tipi (cioè i suggerimenti sui tipi, i tipi di ritorno, il nome della classe genitore, -interfacce implementate, tratti e attributi utilizzati) sono automaticamente *risolti* (a meno che non lo si disattivi, vedere sotto). -Ciò significa che bisogna **usare i nomi completi delle classi** nelle definizioni, che saranno sostituiti da alias (secondo le dichiarazioni d'uso) o da nomi pienamente qualificati nel codice risultante: +**Quando una classe fa parte di un namespace, viene renderizzata leggermente diversamente:** tutti i tipi (ad esempio typehint, tipi di ritorno, nome della classe genitore, interfacce implementate, proprietà usate e attributi) vengono automaticamente *tradotti* (a meno che non lo disabiliti, vedi sotto). Ciò significa che devi **usare i nomi completi delle classi** nelle definizioni e questi verranno sostituiti da alias (secondo le clausole use) o da nomi completi nel codice risultante: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); $namespace->addUse('Bar\AliasedClass'); $class = $namespace->addClass('Demo'); -$class->addImplement('Foo\A') // si semplificherà in A - ->addTrait('Bar\AliasedClass'); // semplificherà in AliasedClass +$class->addImplement('Foo\A') // sarà semplificato in A + ->addTrait('Bar\AliasedClass'); // sarà semplificato in AliasedClass $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // nei commenti semplifica manualmente +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // nei commenti semplifichiamo manualmente $method->addParameter('arg') - ->setType('Bar\OtherClass'); // si risolverà in \Bar\OtherClass + ->setType('Bar\OtherClass'); // sarà tradotto in \Bar\OtherClass echo $namespace; -// o usare PsrPrinter per un output conforme a PSR-2 / PSR-12 +// oppure usa PsrPrinter per l'output conforme a PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -684,7 +804,7 @@ class Demo implements A } ``` -In questo modo è possibile disattivare la risoluzione automatica: +La traduzione automatica può essere disabilitata in questo modo: ```php $printer = new Nette\PhpGenerator\Printer; // o PsrPrinter @@ -693,14 +813,14 @@ echo $printer->printNamespace($namespace); ``` -File PHP .[#toc-php-files] --------------------------- +File PHP +-------- -Classi, funzioni e spazi dei nomi possono essere raggruppati in file PHP rappresentati dalla classe [PhpFile |api:Nette\PhpGenerator\PhpFile]: +Classi, funzioni e namespace possono essere raggruppati in file PHP rappresentati dalla classe [PhpFile |api:Nette\PhpGenerator\PhpFile]: ```php $file = new Nette\PhpGenerator\PhpFile; -$file->addComment('This file is auto-generated.'); +$file->addComment('Questo file è generato automaticamente.'); $file->setStrictTypes(); // aggiunge declare(strict_types=1) $class = $file->addClass('Foo\A'); @@ -713,7 +833,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// oppure utilizzare PsrPrinter per un output conforme a PSR-2 / PSR-12 +// oppure usa PsrPrinter per l'output conforme a PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -723,7 +843,7 @@ Risultato: <?php /** - * This file is auto-generated. + * Questo file è generato automaticamente. */ declare(strict_types=1); @@ -739,27 +859,28 @@ function foo() } ``` +**Attenzione:** Non è possibile aggiungere alcun altro codice ai file al di fuori di funzioni e classi. + -Generare in base a quelli esistenti .[#toc-generating-according-to-existing-ones] ---------------------------------------------------------------------------------- +Generazione basata su esistenti +------------------------------- -Oltre a poter modellare classi e funzioni utilizzando le API descritte in precedenza, è anche possibile generarle automaticamente in base a quelle esistenti: +Oltre a poter modellare classi e funzioni con l'API sopra descritta, puoi anche farle generare automaticamente basandoti su pattern esistenti: ```php -// crea una classe identica alla classe PDO +// crea una classe uguale alla classe PDO $class = Nette\PhpGenerator\ClassType::from(PDO::class); -// crea una funzione identica a trim() +// crea una funzione identica alla funzione trim() $function = Nette\PhpGenerator\GlobalFunction::from('trim'); -// crea una chiusura come specificato +// crea una closure basata su quella fornita $closure = Nette\PhpGenerator\Closure::from( function (stdClass $a, $b = null) {}, ); ``` -I corpi delle funzioni e dei metodi sono vuoti per impostazione predefinita. Se si desidera caricarli, utilizzare questo metodo -(richiede l'installazione di `nikic/php-parser` ): +I corpi delle funzioni e dei metodi sono vuoti per impostazione predefinita. Se vuoi caricarli anche tu, usa questo metodo (richiede l'installazione del pacchetto `nikic/php-parser`): ```php $class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true); @@ -768,10 +889,10 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); ``` -Caricamento da file PHP .[#toc-loading-from-php-file] ------------------------------------------------------ +Caricamento da file PHP +----------------------- -È anche possibile caricare funzioni, classi, interfacce ed enum direttamente da una stringa di codice PHP. Ad esempio, creiamo l'oggetto `ClassType` in questo modo: +Puoi caricare funzioni, classi, interfacce ed enum anche direttamente da una stringa contenente codice PHP. Ad esempio, in questo modo creiamo un oggetto `ClassType`: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -784,39 +905,69 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -Quando si caricano le classi dal codice PHP, i commenti di una sola riga al di fuori dei corpi dei metodi vengono ignorati (ad esempio per le proprietà, ecc.), perché questa libreria non dispone di un'API per gestirli. +Quando si caricano classi da codice PHP, i commenti su riga singola al di fuori dei corpi dei metodi vengono ignorati (ad es. per le proprietà, ecc.), poiché questa libreria non ha un'API per lavorarci. -È anche possibile caricare direttamente l'intero file PHP, che può contenere un numero qualsiasi di classi, funzioni o anche spazi dei nomi multipli: +Puoi anche caricare direttamente un intero file PHP, che può contenere un numero qualsiasi di classi, funzioni o persino namespace: ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` -Vengono caricati anche il commento iniziale del file e la dichiarazione `strict_types`. D'altra parte, tutto il resto del codice globale viene ignorato. +Vengono caricati anche il commento iniziale del file e la dichiarazione `strict_types`. Al contrario, tutto il resto del codice globale viene ignorato. -Questo richiede l'installazione di `nikic/php-parser`. +È richiesto che sia installato `nikic/php-parser`. .[note] -Se è necessario manipolare il codice globale nei file o le singole dichiarazioni nei corpi dei metodi, è meglio usare direttamente la libreria `nikic/php-parser`. +Se hai bisogno di manipolare il codice globale nei file o le singole istruzioni nei corpi dei metodi, è meglio usare direttamente la libreria `nikic/php-parser`. + + +Class Manipulator +----------------- + +La classe [ClassManipulator |api:Nette\PhpGenerator\ClassManipulator] fornisce strumenti per manipolare le classi. + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$manipulator = new Nette\PhpGenerator\ClassManipulator($class); +``` + +Il metodo `inheritMethod()` copia un metodo dalla classe genitore o dall'interfaccia implementata nella tua classe. Ciò ti consente di sovrascrivere il metodo o estendere la sua firma: + +```php +$method = $manipulator->inheritMethod('bar'); +$method->setBody('...'); +``` + +Il metodo `inheritProperty()` copia una proprietà dalla classe genitore nella tua classe. È utile quando vuoi avere la stessa proprietà nella tua classe, ma magari con un valore predefinito diverso: + +```php +$property = $manipulator->inheritProperty('foo'); +$property->setValue('new value'); +``` +Il metodo `implement()` implementa automaticamente tutti i metodi e le proprietà dall'interfaccia o dalla classe astratta data nella tua classe: + +```php +$manipulator->implement(SomeInterface::class); +// Ora la tua classe implementa SomeInterface e contiene tutti i suoi metodi +``` -Dumper di variabili .[#toc-variables-dumper] --------------------------------------------- -Dumper restituisce una rappresentazione in stringhe PHP parsabili di una variabile. Fornisce un output migliore e più chiaro rispetto alla funzione nativa `var_export()`. +Stampa di variabili +------------------- + +La classe `Dumper` converte una variabile in codice PHP analizzabile. Fornisce un output migliore e più chiaro rispetto alla funzione standard `var_export()`. ```php $dumper = new Nette\PhpGenerator\Dumper; $var = ['a', 'b', 123]; -echo $dumper->dump($var); // stampa ['a', 'b', 123]. +echo $dumper->dump($var); // stampa ['a', 'b', 123] ``` -Tabella di compatibilità .[#toc-compatibility-table] ----------------------------------------------------- - -PhpGenerator 4.0 è compatibile con PHP 8.0 - 8.2 +Tabella di compatibilità +------------------------ -{{leftbar: nette:@menu-topics}} +PhpGenerator 4.1 è compatibile con PHP 8.0 fino a 8.4. diff --git a/php-generator/it/@meta.texy b/php-generator/it/@meta.texy new file mode 100644 index 0000000000..9d19e7312c --- /dev/null +++ b/php-generator/it/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Documentazione Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/php-generator/ja/@home.texy b/php-generator/ja/@home.texy index 57fc483550..5ed81beb08 100644 --- a/php-generator/ja/@home.texy +++ b/php-generator/ja/@home.texy @@ -1,31 +1,32 @@ -PHP コードジェネレータ -************* - -<div class=perex> -- クラス、関数、PHPファイルなどのPHPコードを生成する必要がありますか? -- enum、属性など、最新のPHP機能をすべてサポートしています。 -- 既存のクラスを簡単に修正することが可能 -- PSR-12に準拠した出力 -- 非常に成熟した、安定した、広く使われているライブラリです。 +Nette PhpGenerator +****************** + +<div class="perex"> +クラス、関数、または完全なPHPファイルのコードを生成するためのツールをお探しですか? + +- PHPの最新機能(プロパティフック、enum、属性など)をすべてサポート +- 既存のクラスを簡単に変更できます +- 出力コードはPSR-12 / PERコーディングスタイルに準拠しています +- 成熟し、安定し、広く使用されているライブラリ </div> -インストール .[#toc-installation] ---------------------------- +インストール +------ -[Composerを |best-practices:en:composer]使用して、パッケージをダウンロードし、インストールします。 +ライブラリは[Composer|best-practices:composer]ツールを使用してダウンロードおよびインストールします: ```shell composer require nette/php-generator ``` -PHPの互換性については、[表を |#Compatibility Table]参照してください。 +PHPとの互換性は[#互換性テーブル]で確認できます。 -クラス .[#toc-classes] -------------------- +クラス +--- -まずは、[ClassType |api:Nette\PhpGenerator\ClassType] を使ってクラスを生成する簡単な例から見ていきましょう。 +まず、[ClassType |api:Nette\PhpGenerator\ClassType]を使用してクラスを作成する例から始めましょう: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -34,19 +35,19 @@ $class ->setFinal() ->setExtends(ParentClass::class) ->addImplement(Countable::class) - ->addComment("Description of class.\nSecond line\n") + ->addComment("クラスの説明。\n2行目\n") ->addComment('@property-read Nette\Forms\Form $form'); -// to generate PHP code simply cast to string or use echo: +// コードは文字列にキャストするか、echo を使用して簡単に生成できます: echo $class; ``` -このような結果がレンダリングされます。 +次の結果を返します: ```php /** - * Description of class. - * Second line + * クラスの説明 + * 2行目 * * @property-read Nette\Forms\Form $form */ @@ -55,35 +56,35 @@ final class Demo extends ParentClass implements Countable } ``` -また、プリンタを使ってコードを生成することもできます。この場合、`echo $class` とは異なり、[さらに設定を |#Printers and PSR compliance]行うことができるようになります。 +コードを生成するために、いわゆるプリンタを使用することもできます。これは`echo $class`とは異なり、[さらに設定 |#プリンタとPSR準拠]できます: ```php $printer = new Nette\PhpGenerator\Printer; echo $printer->printClass($class); ``` -定数([Constant |api:Nette\PhpGenerator\Constant]クラス)やプロパティ([Property |api:Nette\PhpGenerator\Property]クラス)を追加することができます。 +定数(クラス[Constant |api:Nette\PhpGenerator\Constant])とプロパティ(クラス[Property |api:Nette\PhpGenerator\Property])を追加できます: ```php $class->addConstant('ID', 123) - ->setProtected() // 可視性を一定にする。 + ->setProtected() // 定数の可視性 ->setType('int') ->setFinal(); $class->addProperty('items', [1, 2, 3]) - ->setPrivate() // or setVisibility('private') + ->setPrivate() // または setVisibility('private') ->setStatic() ->addComment('@var int[]'); $class->addProperty('list') ->setType('?array') - ->setInitialized(); // prints '= null' + ->setInitialized(); // '= null' を出力します ``` -生成されます。 +生成されるもの: ```php -final protected const int ID = 123; +final protected const int ID = 123; /** @var int[] */ private static $items = [1, 2, 3]; @@ -91,14 +92,14 @@ private static $items = [1, 2, 3]; public ?array $list = null; ``` -そして、[メソッドを |#Method and Function Signature]追加することができます。 +そして、[メソッド |#メソッドと関数のシグネチャ]を追加できます: ```php $method = $class->addMethod('count') - ->addComment('Count it.') + ->addComment('カウント処理。') ->setFinal() ->setProtected() - ->setReturnType('?int') // method return type + ->setReturnType('?int') // メソッドの戻り値の型 ->setBody('return count($items ?: $this->items);'); $method->addParameter('items', []) // $items = [] @@ -106,11 +107,11 @@ $method->addParameter('items', []) // $items = [] ->setType('array'); // array &$items = [] ``` -という結果になります。 +結果は: ```php /** - * Count it. + * カウント処理。 */ final protected function count(array &$items = []): ?int { @@ -118,7 +119,7 @@ final protected function count(array &$items = []): ?int } ``` -PHP 8.0 で導入されたプロモートパラメータをコンストラクタに渡すことができるようになりました。 +PHP 8.0で導入されたプロモートされたパラメータは、コンストラクタに渡すことができます: ```php $method = $class->addMethod('__construct'); @@ -127,7 +128,7 @@ $method->addPromotedParameter('args', []) ->setPrivate(); ``` -その結果 +結果は: ```php public function __construct( @@ -137,15 +138,15 @@ public function __construct( } ``` -Readonlyプロパティやクラスは、`setReadOnly()` を使ってマークすることができます。 +読み取り専用のプロパティとクラスは、関数 `setReadOnly()` を使用してマークできます。 ------ -追加されたプロパティや定数、メソッド、パラメータがすでに存在する場合は、例外がスローされます。 +追加されたプロパティ、定数、メソッド、またはパラメータが既に存在する場合、例外がスローされます。 -メンバーは、`removeProperty()`,`removeConstant()`,`removeMethod()` または`removeParameter()` を使って削除することができます。 +クラスのメンバーは、`removeProperty()`、`removeConstant()`、`removeMethod()`、または`removeParameter()`を使用して削除できます。 -また、既存の`Method`,`Property` または`Constant` オブジェクトをクラスに追加することもできます。 +クラスに既存の`Method`、`Property`、または`Constant`オブジェクトを追加することもできます: ```php $method = new Nette\PhpGenerator\Method('getHandle'); @@ -158,7 +159,7 @@ $class = (new Nette\PhpGenerator\ClassType('Demo')) ->addMember($const); ``` -既存のメソッド、プロパティ、定数を別の名前でクローンするには、`cloneWithName()` を使用します。 +`cloneWithName()`を使用して、既存のメソッド、プロパティ、および定数を別の名前でクローンすることもできます: ```php $methodCount = $class->getMethod('count'); @@ -167,17 +168,17 @@ $class->addMember($methodRecount); ``` -インターフェイスまたはトレイト .[#toc-interface-or-trait] ------------------------------------------- +インターフェースまたはトレイト +--------------- -インターフェースや特性(クラス[InterfaceType |api:Nette\PhpGenerator\InterfaceType]、[TraitType |api:Nette\PhpGenerator\TraitType])を作成することができます: +インターフェースとトレイトを作成できます(クラス[InterfaceType |api:Nette\PhpGenerator\InterfaceType]と[TraitType |api:Nette\PhpGenerator\TraitType]): ```php $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait'); ``` -特性を利用する: +トレイトの使用: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -188,13 +189,13 @@ $class->addTrait('MyTrait') echo $class; ``` -結果 +結果: ```php class Demo { use SmartObject; - /** @use MyTrait<Foo> */。 + /** @use MyTrait<Foo> */ use MyTrait { sayHello as protected; } @@ -202,10 +203,10 @@ class Demo ``` -列挙 .[#toc-enums] ----------------- +Enums +----- -PHP 8.1がもたらすenum(クラス[EnumType |api:Nette\PhpGenerator\EnumType])を簡単に作成することができます: +PHP 8.1で導入されたEnumは、次のように簡単に作成できます:(クラス[EnumType |api:Nette\PhpGenerator\EnumType]): ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -217,7 +218,7 @@ $enum->addCase('Spades'); echo $enum; ``` -結果 +結果: ```php enum Suit @@ -229,20 +230,20 @@ enum Suit } ``` -また、ケースに対してスカラー等価物を定義し、裏付けされたenumを作成することができます。 +スカラー等価物を定義して、「backed」enumを作成することもできます: ```php $enum->addCase('Clubs', '♣'); $enum->addCase('Diamonds', '♦'); ``` -`addComment()` または`addAttribute()` を用いて,各ケースに対してコメントや[属性を |#attributes]追加することが可能です. +各*case*にコメントまたは[#属性]を`addComment()`または`addAttribute()`を使用して追加できます。 -匿名クラス .[#toc-anonymous-class] ------------------------------ +匿名クラス +----- -名前を`null` とすると、匿名クラスができます。 +名前として`null`を渡すと、匿名クラスが作成されます: ```php $class = new Nette\PhpGenerator\ClassType(null); @@ -252,7 +253,7 @@ $class->addMethod('__construct') echo '$obj = new class ($val) ' . $class . ';'; ``` -結果 +結果: ```php $obj = new class ($val) { @@ -264,10 +265,10 @@ $obj = new class ($val) { ``` -グローバル機能 .[#toc-global-function] -------------------------------- +グローバル関数 +------- -関数のコードは、クラス[GlobalFunction |api:Nette\PhpGenerator\GlobalFunction] を生成します。 +関数のコードはクラス[GlobalFunction |api:Nette\PhpGenerator\GlobalFunction]によって生成されます: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -276,11 +277,11 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// or use PsrPrinter for output conforming to PSR-2 / PSR-12 +// または PsrPrinter を使用して PSR-2 / PSR-12 / PER に準拠した出力を生成します // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` -結果 +結果: ```php function foo($a, $b) @@ -290,10 +291,10 @@ function foo($a, $b) ``` -閉鎖 .[#toc-closure] ------------------- +匿名関数 +---- -クロージャのコードは、クラス[Closureを |api:Nette\PhpGenerator\Closure]生成します。 +匿名関数のコードはクラス[Closure |api:Nette\PhpGenerator\Closure]によって生成されます: ```php $closure = new Nette\PhpGenerator\Closure; @@ -304,11 +305,11 @@ $closure->addUse('c') ->setReference(); echo $closure; -// or use PsrPrinter for output conforming to PSR-2 / PSR-12 +// または PsrPrinter を使用して PSR-2 / PSR-12 / PER に準拠した出力を生成します // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` -結果 +結果: ```php function ($a, $b) use (&$c) { @@ -317,10 +318,10 @@ function ($a, $b) use (&$c) { ``` -アロー関数 .[#toc-arrow-function] ----------------------------- +短縮アロー関数 +------- -プリンターでクロージャを矢印機能として印刷することもできます。 +プリンタを使用して、短縮された匿名関数を出力することもできます: ```php $closure = new Nette\PhpGenerator\Closure; @@ -331,45 +332,45 @@ $closure->addParameter('b'); echo (new Nette\PhpGenerator\Printer)->printArrowFunction($closure); ``` -結果 +結果: ```php fn($a, $b) => $a + $b ``` -メソッドと関数のシグネチャ .[#toc-method-and-function-signature] ---------------------------------------------------- +メソッドと関数のシグネチャ +------------- -メソッドは[Methodという |api:Nette\PhpGenerator\Method]クラスで表現されます。可視性の設定、戻り値の設定、コメントや[属性の |#Attributes]追加などが可能です。 +メソッドはクラス[Method |api:Nette\PhpGenerator\Method]によって表されます。可視性、戻り値の型を設定したり、コメント、[#属性]などを追加したりできます: ```php $method = $class->addMethod('count') - ->addComment('Count it.') + ->addComment('カウント処理。') ->setFinal() ->setProtected() ->setReturnType('?int'); ``` -各パラメーターは[Parameter |api:Nette\PhpGenerator\Parameter]クラスで表されます。ここでも、考えられる限りのプロパティを設定することができる。 +個々のパラメータはクラス[Parameter |api:Nette\PhpGenerator\Parameter]によって表されます。ここでも、考えられるすべてのプロパティを設定できます: ```php $method->addParameter('items', []) // $items = [] - ->setReference() // &$items = [] - ->setType('array'); // array &$items = [] + ->setReference() // &$items = [] + ->setType('array'); // array &$items = [] // function count(&$items = []) ``` -いわゆる variadics パラメータ(あるいは splat, spread, ellipsis, unpacking, three dots オペレータ)を定義するには、`setVariadics()` を使用します。 +いわゆる可変長引数パラメータ(またはスプラット演算子)を定義するには、`setVariadic()`を使用します: ```php $method = $class->addMethod('count'); -$method->setVariadics(true); +$method->setVariadic(true); $method->addParameter('items'); ``` -を生成する。 +生成されるもの: ```php function count(...$items) @@ -378,10 +379,10 @@ function count(...$items) ``` -メソッドと関数本体 .[#toc-method-and-function-body] ------------------------------------------- +メソッドと関数の本体 +---------- -本体は、`setBody()` メソッドに一度に渡すこともできますし、`addBody()` を繰り返し呼び出すことで順次(一行ずつ)渡すこともできます。 +本体は`setBody()`メソッドに一度に渡すか、`addBody()`を繰り返し呼び出して段階的に(行ごとに)渡すことができます: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -400,7 +401,7 @@ function foo() } ``` -特別なプレースホルダーを使用することで、変数を注入する便利な方法があります。 +特別なプレースホルダーを使用して、変数を簡単に挿入できます。 単純なプレースホルダー `?` @@ -421,7 +422,7 @@ function foo() } ``` -可変長プレースホルダー`...?` +可変長引数のプレースホルダー `...?` ```php $items = [1, 2, 3]; @@ -430,7 +431,7 @@ $function->setBody('myfunc(...?);', [$items]); echo $function; ``` -結果 +結果: ```php function foo() @@ -439,7 +440,7 @@ function foo() } ``` -PHP 8 の名前つきパラメータは、プレースホルダを使用して使用することもできます。`...?:` +PHP 8の`...?:`を使用して、名前付きパラメータを使用することもできます: ```php $items = ['foo' => 1, 'bar' => true]; @@ -448,7 +449,7 @@ $function->setBody('myfunc(...?:);', [$items]); // myfunc(foo: 1, bar: true); ``` -スラッシュを使用してプレースホルダをエスケープします。`\?` +プレースホルダーはバックスラッシュ`\?`でエスケープされます ```php $num = 3; @@ -458,7 +459,7 @@ $function->addBody('return $a \? 10 : ?;', [$num]); echo $function; ``` -結果 +結果: ```php function foo($a) @@ -468,56 +469,81 @@ function foo($a) ``` -プリンターとPSRの適合性 .[#toc-printers-and-psr-compliance] -------------------------------------------------- +プリンタとPSR準拠 +---------- -PHPのコードは、`Printer` オブジェクトによって生成されます。出力が PSR-2 および PSR-12 に準拠し、インデントにスペースを使用する`PsrPrinter` と、インデントにタブを使用する`Printer` が用意されています。 +PHPコードの生成にはクラス[Printer |api:Nette\PhpGenerator\Printer]を使用します: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // echo $class と同じ +``` + +他のすべての要素のコードを生成でき、`printFunction()`、`printNamespace()`などのメソッドを提供します。 + +`PsrPrinter`クラスも利用可能で、その出力はPSR-2 / PSR-12 / PERコーディングスタイルに準拠しています: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // 4 spaces indentation +echo $printer->printClass($class); ``` -プリンタの動作をカスタマイズする必要がありますか?`Printer` クラスを継承して、独自のものを作成してください。これらの変数を再設定することができます。 +動作をカスタマイズする必要がありますか? `Printer`クラスを継承して独自のバージョンを作成します。次の変数を再設定できます: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // 行が折り返される行の長さ public int $wrapLength = 120; + // インデント文字、スペースのシーケンスに置き換えることができます public string $indentation = "\t"; + // プロパティ間の空行の数 public int $linesBetweenProperties = 0; + // メソッド間の空行の数 public int $linesBetweenMethods = 2; + // クラス、関数、定数の 'use statements' グループ間の空行の数 public int $linesBetweenUseTypes = 0; + // 関数とメソッドの開始波括弧の位置 public bool $bracesOnNextLine = true; + // 属性があるか、サポートされている場合でも、1つのパラメータを1行に配置します + public bool $singleParameterOnOneLine = false; + // クラスや関数を含まない名前空間を省略します + public bool $omitEmptyNamespaces = true; + // 右括弧と関数およびメソッドの戻り値の型の間の区切り文字 public string $returnTypeColon = ': '; } ``` +標準の`Printer`と`PsrPrinter`は実際にはどのように、そしてなぜ異なるのでしょうか? なぜパッケージには`PsrPrinter`だけでなく、1つのプリンタしかないのでしょうか? + +標準の`Printer`は、Nette全体で行っているようにコードをフォーマットします。NetteがPSRよりもずっと前に登場したこと、そしてPSRが長年にわたって標準をタイムリーに提供せず、PHPの新機能が導入されてから数年遅れて提供することもあったため、[コーディング標準 |contributing:coding-standard]はいくつかの細かい点で異なります。 大きな違いは、スペースの代わりにタブを使用することだけです。プロジェクトでタブを使用することで、[視覚障害を持つ人々にとって不可欠な |contributing:coding-standard#スペースの代わりにタブ]幅のカスタマイズが可能になることを知っています。 小さな違いの例としては、関数とメソッドの波括弧を常に別の行に配置することです。PSRの推奨は非論理的であり、[コードの可読性の低下 |contributing:coding-standard#折り返しと波括弧]につながると考えています。 -タイプ .[#toc-types] ------------------ -各タイプやユニオン/交差タイプは、文字列として渡すことができます。また、ネイティブタイプには定義済みの定数を使用することもできます。 +型 +---- + +各型またはunion/intersection型は文字列として渡すことができ、ネイティブ型には事前定義された定数を使用することもできます: ```php use Nette\PhpGenerator\Type; -$member->setType('array'); // or Type::Array; -$member->setType('array|string'); // or Type::union('array', 'string') -$member->setType('Foo&Bar'); // or Type::intersection(Foo::class, Bar::class) -$member->setType(null); // removes type +$member->setType('array'); // または Type::Array; +$member->setType('?array'); // or Type::nullable(Type::Array); +$member->setType('array|string'); // or Type::union(Type::Array, Type::String) +$member->setType('Foo&Bar'); // または Type::intersection(Foo::class, Bar::class) +$member->setType(null); // 型を削除します ``` -同じことが,`setReturnType()` というメソッドにも当てはまります. +`setReturnType()`メソッドにも同じことが当てはまります。 -リテラル .[#toc-literals] ---------------------- +リテラル +---- -`Literal` を使用すると、任意の PHP コードを、例えばデフォルトのプロパティやパラメータの値などに渡すことができます。 +`Literal`を使用すると、プロパティやパラメータのデフォルト値など、任意のPHPコードを渡すことができます: ```php use Nette\PhpGenerator\Literal; @@ -532,7 +558,7 @@ $class->addMethod('bar') echo $class; ``` -結果 +結果: ```php class Demo @@ -545,25 +571,37 @@ class Demo } ``` -`Literal` にパラメータを渡すと、[特別なプレースホルダーを使って |#method-and-function-body-generator]有効な PHP コードにフォーマットさせることもできます。 +`Literal`にパラメータを渡し、[プレースホルダー |#メソッドと関数の本体]を使用して有効なPHPコードにフォーマットさせることもできます: ```php new Literal('substr(?, ?)', [$a, $b]); -// generates, for example: substr('hello', 5); +// 例えば、substr('hello', 5); を生成します ``` +新しいオブジェクトの作成を表すリテラルは、`new`メソッドを使用して簡単に生成できます: -属性 .[#toc-attributes] ---------------------- +```php +Literal::new(Demo::class, [$a, 'foo' => $b]); +// 例えば、new Demo(10, foo: 20) を生成します +``` + + +属性 +-------- -PHP 8 の属性を、すべてのクラス、メソッド、プロパティ、定数、enum case、関数、クロージャ、パラメータに追加することができます。[リテラルも |#Literals]パラメータの値として使用できます。 +PHP 8属性は、すべてのクラス、メソッド、プロパティ、定数、enum、関数、クロージャ、およびパラメータに追加できます。パラメータ値として[#リテラル]を使用することもできます。 ```php $class = new Nette\PhpGenerator\ClassType('Demo'); -$class->addAttribute('Deprecated'); +$class->addAttribute('Table', [ + 'name' => 'user', + 'constraints' => [ + Literal::new('UniqueConstraint', ['name' => 'ean', 'columns' => ['ean']]), + ], +]); $class->addProperty('list') - ->addAttribute('WithArguments', [1, 2]); + ->addAttribute('Deprecated'); $method = $class->addMethod('count') ->addAttribute('Foo\Cached', ['mode' => true]); @@ -574,45 +612,129 @@ $method->addParameter('items') echo $class; ``` -結果 +結果: ```php -#[Deprecated] +#[Table(name: 'user', constraints: [new UniqueConstraint(name: 'ean', columns: ['ean'])])] class Demo { - #[WithArguments(1, 2)] + #[Deprecated] public $list; #[Foo\Cached(mode: true)] - public function count(#[Bar] $items) - { + public function count( + #[Bar] + $items, + ) { } } ``` -名前空間 .[#toc-namespace] ----------------------- +プロパティフック +-------- + +プロパティフック(クラス[PropertyHook|api:Nette\PhpGenerator\PropertyHook]で表される)を使用して、PHP 8.4で導入された機能であるプロパティのgetおよびset操作を定義できます: + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$prop = $class->addProperty('firstName') + ->setType('string'); + +$prop->addHook('set', 'strtolower($value)') + ->addParameter('value') + ->setType('string'); + +$prop->addHook('get') + ->setBody('return ucfirst($this->firstName);'); + +echo $class; +``` + +生成されるもの: + +```php +class Demo +{ + public string $firstName { + set(string $value) => strtolower($value); + get { + return ucfirst($this->firstName); + } + } +} +``` + +プロパティとプロパティフックは、abstractまたはfinalにすることができます: + +```php +$class->addProperty('id') + ->setType('int') + ->addHook('get') + ->setAbstract(); + +$class->addProperty('role') + ->setType('string') + ->addHook('set', 'strtolower($value)') + ->setFinal(); +``` + + +非対称可視性 +------ + +PHP 8.4では、プロパティの非対称可視性が導入されました。読み取りと書き込みに対して異なるアクセスレベルを設定できます。 + +可視性は、2つのパラメータを持つ`setVisibility()`メソッドを使用するか、または`setPublic()`、`setProtected()`、または`setPrivate()`メソッドを使用し、可視性がプロパティの読み取りまたは書き込みに適用されるかどうかを指定する`mode`パラメータを使用して設定できます。デフォルトのモードは`'get'`です。 + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); + +$class->addProperty('name') + ->setType('string') + ->setVisibility('public', 'private'); // 読み取りは public、書き込みは private + +$class->addProperty('id') + ->setType('int') + ->setProtected('set'); // 書き込みは protected + +echo $class; +``` + +生成されるもの: + +```php +class Demo +{ + public private(set) string $name; + + protected(set) int $id; +} +``` + + +Namespace +--------- -クラス、trait、interface、enum(以下クラス)は、名前空間[(PhpNamespace |api:Nette\PhpGenerator\PhpNamespace])にグループ化することができます。 +クラス、プロパティ、インターフェース、およびenum(以下、クラス)は、クラス[PhpNamespace |api:Nette\PhpGenerator\PhpNamespace]で表される名前空間にグループ化できます: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); -// create new classes in the namespace +// 名前空間に新しいクラスを作成します $class = $namespace->addClass('Task'); $interface = $namespace->addInterface('Countable'); $trait = $namespace->addTrait('NameAware'); -// or insert an existing class into the namespace +// または、既存のクラスを名前空間に挿入します $class = new Nette\PhpGenerator\ClassType('Task'); $namespace->add($class); ``` -クラスがすでに存在する場合は、例外をスローします。 +クラスが既に存在する場合、例外がスローされます。 -use-statementsを定義することができます。 +use句を定義できます: ```php // use Http\Request; @@ -623,14 +745,14 @@ $namespace->addUse(Http\Request::class, 'HttpReq'); $namespace->addUseFunction('iter\range'); ``` -定義されたエイリアスに従って、完全修飾されたクラス、関数、定数名を簡略化するには、`simplifyName` メソッドを使用します。 +定義されたエイリアスに従って、完全修飾クラス名、関数名、または定数名を簡略化するには、`simplifyName`メソッドを使用します: ```php -echo $namespace->simplifyName('Foo\Bar'); // 'Bar', because 'Foo' is current namespace -echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', because of the defined use-statement +echo $namespace->simplifyName('Foo\Bar'); // 'Bar'、'Foo' は現在の名前空間のため +echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range'、定義された use ステートメントのため ``` -逆に、簡略化されたクラス名、関数名、定数名を完全修飾名に変換するには、`resolveName` メソッドを使用します。 +逆に、`resolveName`メソッドを使用して、簡略化されたクラス名、関数名、または定数名を完全修飾名に変換できます: ```php echo $namespace->resolveName('Bar'); // 'Foo\Bar' @@ -638,33 +760,31 @@ echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range' ``` -クラス名の解決 .[#toc-class-names-resolving] -------------------------------------- +クラス名の変換 +------- -**クラスが名前空間の一部である場合、わずかに異なる方法でレンダリングされます**:すべてのタイプ(すなわち、タイプヒント、戻り値タイプ、親クラス名、 -実装されたインターフェース、使用された特性や属性)は自動的に*解決*されます(オフにしない限り、以下を参照)。 -つまり、定義では完全なクラス名**を使用しなければならず、結果のコードではエイリアス(use-statementに従う)または完全修飾名に置き換えられます: +**クラスが名前空間の一部である場合、わずかに異なる方法でレンダリングされます:** すべての型(例えば、タイプヒント、戻り値の型、親クラスの名前、実装されたインターフェース、使用されるプロパティ、および属性)は自動的に*変換*されます(これを無効にしない限り、下記参照)。 これは、定義で**完全なクラス名を使用する必要がある**ことを意味し、それらは結果のコードでエイリアス(use句に従って)または完全修飾名に置き換えられます: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); $namespace->addUse('Bar\AliasedClass'); $class = $namespace->addClass('Demo'); -$class->addImplement('Foo\A') // it will simplify to A - ->addTrait('Bar\AliasedClass'); // it will simplify to AliasedClass +$class->addImplement('Foo\A') // A に簡略化されます + ->addTrait('Bar\AliasedClass'); // AliasedClass に簡略化されます -メソッド->addComment('@return ' . $namespace->simplifyType('FooD')); // コメントで手動で簡略化する -$method->addComment('@return ' . $namespace->simplifyName('Foo\D')); // in comments simplify manually +$method = $class->addMethod('method'); +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // コメント内で手動で簡略化します $method->addParameter('arg') - ->setType('Bar\OtherClass'); // it will resolve to \Bar\OtherClass + ->setType('Bar\OtherClass'); // \Bar\OtherClass に変換されます echo $namespace; -// or use PsrPrinter for output conforming to PSR-2 / PSR-12 +// または PsrPrinter を使用して PSR-2 / PSR-12 / PER に準拠した出力を生成します // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` -結果 +結果: ```php namespace Foo; @@ -684,46 +804,46 @@ class Demo implements A } ``` -この方法で自動解像度をオフにすることができます。 +自動変換は次のように無効にできます: ```php -$printer = new Nette\PhpGenerator\Printer; // or PsrPrinter +$printer = new Nette\PhpGenerator\Printer; // または PsrPrinter $printer->setTypeResolving(false); echo $printer->printNamespace($namespace); ``` -PHPファイル .[#toc-php-files] -------------------------- +PHPファイル +------- -クラスや関数、名前空間は、[PhpFile |api:Nette\PhpGenerator\PhpFile] というクラスで表される PHP ファイルにまとめることができます。 +クラス、関数、および名前空間は、クラス[PhpFile|api:Nette\PhpGenerator\PhpFile]で表されるPHPファイルにグループ化できます: ```php $file = new Nette\PhpGenerator\PhpFile; -$file->addComment('This file is auto-generated.'); -$file->setStrictTypes(); // adds declare(strict_types=1) +$file->addComment('このファイルは自動生成されました。'); +$file->setStrictTypes(); // declare(strict_types=1) を追加します $class = $file->addClass('Foo\A'); $function = $file->addFunction('Foo\foo'); -// or +// または // $namespace = $file->addNamespace('Foo'); // $class = $namespace->addClass('A'); // $function = $namespace->addFunction('foo'); echo $file; -// or use PsrPrinter for output conforming to PSR-2 / PSR-12 +// または PsrPrinter を使用して PSR-2 / PSR-12 / PER に準拠した出力を生成します // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` -結果 +結果: ```php <?php /** - * This file is auto-generated. + * このファイルは自動生成されました。 */ declare(strict_types=1); @@ -739,27 +859,28 @@ function foo() } ``` +**注意:** 関数やクラス以外のコードをファイルに追加することはできません。 + -既存のものに合わせて生成する .[#toc-generating-according-to-existing-ones] ------------------------------------------------------------- +既存のものに基づく生成 +----------- -上記のAPIを使ってクラスや関数をモデリングすることができるだけでなく、既存のものを使って自動的に生成させることもできます。 +上記で説明したAPIを使用してクラスや関数をモデル化するだけでなく、既存のパターンに基づいて自動的に生成させることもできます: ```php -// creates a class identical to the PDO class +// PDO クラスと同じクラスを作成します $class = Nette\PhpGenerator\ClassType::from(PDO::class); -// creates a function identical to trim() +// trim() 関数と同じ関数を作成します $function = Nette\PhpGenerator\GlobalFunction::from('trim'); -// creates a closure as specified +// 指定されたクロージャに基づいてクロージャを作成します $closure = Nette\PhpGenerator\Closure::from( function (stdClass $a, $b = null) {}, ); ``` -関数やメソッドのボディはデフォルトでは空です。もし、それらもロードしたい場合は、次の方法を使います。 -(を使います(`nikic/php-parser` がインストールされていることが必要です)。 +関数とメソッドの本体はデフォルトで空です。それらも読み込みたい場合は、この方法を使用します(`nikic/php-parser`パッケージのインストールが必要です): ```php $class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true); @@ -768,10 +889,10 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); ``` -PHPファイルからの読み込み .[#toc-loading-from-php-file] --------------------------------------------- +PHPファイルからの読み込み +-------------- -また、関数、クラス、インターフェイス、列挙型を PHP のコード列から直接読み込むこともできます。例えば、このようにして `ClassType` オブジェクトを作成します: +PHPコードを含む文字列から直接、関数、クラス、インターフェース、およびenumを読み込むこともできます。例えば、このようにして`ClassType`オブジェクトを作成します: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -784,39 +905,69 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -PHP コードからクラスを読み込む際、メソッド本体の外側にある一行コメントは無視されます (例えば、プロパティなど)。これは、このライブラリがそれらを扱う API を持っていないためです。 +PHPコードからクラスを読み込む際、メソッド本体外の単一行コメント(例えば、プロパティなど)は無視されます。なぜなら、このライブラリにはそれらを操作するためのAPIがないためです。 -また、PHP ファイル全体を直接読み込むこともできます。このファイルには、任意の数のクラスや関数、あるいは複数の名前空間が含まれる可能性があります: +任意の数のクラス、関数、または名前空間を含むPHPファイル全体を直接読み込むこともできます: ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` -初期ファイルコメントと `strict_types` 宣言もロードされます。一方、その他のグローバルコードはすべて無視される。 +ファイルの冒頭のコメントと`strict_types`宣言も読み込まれます。逆に、他のすべてのグローバルコードは無視されます。 -これには `nikic/php-parser` がインストールされている必要がある。 +`nikic/php-parser`がインストールされている必要があります。 .[note] -ファイル内のグローバルコードやメソッド本体内の個々のステートメントを操作する必要がある場合は、 `nikic/php-parser` ライブラリを直接使用するのがよい。 +ファイル内のグローバルコードやメソッド本体内の個々のステートメントを操作する必要がある場合は、`nikic/php-parser`ライブラリを直接使用する方が良いでしょう。 + + +Class Manipulator +----------------- + +クラス[ClassManipulator|api:Nette\PhpGenerator\ClassManipulator]は、クラスを操作するためのツールを提供します。 + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$manipulator = new Nette\PhpGenerator\ClassManipulator($class); +``` + +`inheritMethod()`メソッドは、親クラスまたは実装されたインターフェースからメソッドをクラスにコピーします。これにより、メソッドをオーバーライドしたり、そのシグネチャを拡張したりできます: +```php +$method = $manipulator->inheritMethod('bar'); +$method->setBody('...'); +``` + +`inheritProperty()`メソッドは、親クラスからプロパティをクラスにコピーします。これは、同じプロパティをクラスに持ちたいが、例えば異なるデフォルト値を持つ場合に便利です: + +```php +$property = $manipulator->inheritProperty('foo'); +$property->setValue('new value'); +``` -変数ダンパ .[#toc-variables-dumper] ------------------------------- +`implement()`メソッドは、指定されたインターフェースまたは抽象クラスのすべてのメソッドとプロパティをクラスに自動的に実装します: + +```php +$manipulator->implement(SomeInterface::class); +// これで、クラスは SomeInterface を実装し、そのすべてのメソッドを含みます +``` -ダンパは、変数のパース可能な PHP 文字列表現を返します。ネイティブ関数`var_export()` よりも、より良い、明確な出力を提供します。 + +変数の出力 +----- + +`Dumper`クラスは、変数を解析可能なPHPコードに変換します。標準の`var_export()`関数よりも優れた、より明確な出力を提供します。 ```php $dumper = new Nette\PhpGenerator\Dumper; $var = ['a', 'b', 123]; -echo $dumper->dump($var); // prints ['a', 'b', 123] +echo $dumper->dump($var); // ['a', 'b', 123] を出力します ``` -互換性テーブル .[#toc-compatibility-table] ------------------------------------ - -PhpGenerator 4.0 は、PHP 8.0 から 8.2 と互換性があります。 +互換性テーブル +------- -{{leftbar: nette:en:@menu-topics}} +PhpGenerator 4.1はPHP 8.0から8.4と互換性があります。 diff --git a/php-generator/ja/@meta.texy b/php-generator/ja/@meta.texy new file mode 100644 index 0000000000..7d67dcb7b8 --- /dev/null +++ b/php-generator/ja/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette ドキュメンテーション}} +{{leftbar: nette:@menu-topics}} diff --git a/php-generator/meta.json b/php-generator/meta.json index ce577b3ca8..9e0f61f4f7 100644 --- a/php-generator/meta.json +++ b/php-generator/meta.json @@ -1,5 +1,5 @@ { - "version": "4.0", + "version": "4.x", "repo": "nette/php-generator", "composer": "nette/php-generator" } diff --git a/php-generator/pl/@home.texy b/php-generator/pl/@home.texy index f9ef01754e..6aa49c4677 100644 --- a/php-generator/pl/@home.texy +++ b/php-generator/pl/@home.texy @@ -1,31 +1,32 @@ -Generator kodu PHP +Nette PhpGenerator ****************** -<div class=perex> -- Potrzebujesz wygenerować kod PHP dla klas, funkcji, plików PHP, itp. -- Obsługuje wszystkie najnowsze funkcje PHP, takie jak enumy, atrybuty, itp. -- Pozwala na łatwą modyfikację istniejących klas -- Wyjście zgodne z PSR-12 -- Wysoce dojrzała, stabilna i szeroko stosowana biblioteka +<div class="perex"> +Szukasz narzędzia do generowania kodu PHP klas, funkcji lub kompletnych plików? + +- Obsługuje wszystkie najnowsze funkcje PHP (takie jak property hooks, enumy, atrybuty itp.) +- Umożliwia łatwą modyfikację istniejących klas +- Kod wyjściowy jest zgodny ze stylem kodowania PSR-12 / PER +- Dojrzała, stabilna i szeroko stosowana biblioteka </div> -Instalacja .[#toc-installation] -------------------------------- +Instalacja +---------- -Pobierz i zainstaluj pakiet za pomocą [Composera |best-practices:composer]: +Bibliotekę pobierzesz i zainstalujesz za pomocą narzędzia [Composer|best-practices:composer]: ```shell composer require nette/php-generator ``` -W celu uzyskania informacji o kompatybilności z PHP, patrz [tabela |#Compatibility Table]. +Kompatybilność z PHP znajdziesz w [tabeli |#Tabela kompatybilności]. -Klasy .[#toc-classes] ---------------------- +Klasy +----- -Zacznijmy od prostego przykładu generowania klasy za pomocą [ClassType |api:Nette\PhpGenerator\ClassType]: +Zacznijmy od razu od przykładu tworzenia klasy za pomocą [ClassType |api:Nette\PhpGenerator\ClassType]: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -34,19 +35,19 @@ $class ->setFinal() ->setExtends(ParentClass::class) ->addImplement(Countable::class) - ->addComment("Description of class.\nSecond line\n") + ->addComment("Opis klasy.\nDruga linia\n") ->addComment('@property-read Nette\Forms\Form $form'); -// aby wygenerować kod PHP wystarczy rzutować na string lub użyć echo: +// kod można łatwo wygenerować przez rzutowanie na ciąg znaków lub użycie echo: echo $class; ``` -Wyrenderuje on taki wynik: +Zwraca następujący wynik: ```php /** - * Description of class. - * Second line + * Opis klasy + * Druga linia * * @property-read Nette\Forms\Form $form */ @@ -55,7 +56,7 @@ final class Demo extends ParentClass implements Countable } ``` -Do wygenerowania kodu możemy również użyć drukarki, którą w przeciwieństwie do `echo $class`, będziemy mogli [dodatkowo skonfigurować |#Printers and PSR compliance]: +Do wygenerowania kodu możemy również użyć tzw. printera, który w przeciwieństwie do `echo $class` będziemy mogli [dalej konfigurować |#Printer i zgodność z PSR]: ```php $printer = new Nette\PhpGenerator\Printer; @@ -66,7 +67,7 @@ Możemy dodać stałe (klasa [Constant |api:Nette\PhpGenerator\Constant]) i wła ```php $class->addConstant('ID', 123) - ->setProtected() // stała widoczność + ->setProtected() // widoczność stałych ->setType('int') ->setFinal(); @@ -77,10 +78,10 @@ $class->addProperty('items', [1, 2, 3]) $class->addProperty('list') ->setType('?array') - ->setInitialized(); // drukuje '= null' + ->setInitialized(); // wypisze '= null' ``` -Generuje: +Wygeneruje: ```php final protected const int ID = 123; @@ -91,14 +92,14 @@ private static $items = [1, 2, 3]; public ?array $list = null; ``` -I możemy dodać [metody |#Method and Function Signature]: +I możemy dodać [metody |#Sygnatury metod i funkcji]: ```php $method = $class->addMethod('count') - ->addComment('Count it.') + ->addComment('Policz to.') ->setFinal() ->setProtected() - ->setReturnType('?int') // metoda return type + ->setReturnType('?int') // typy zwracane w metodach ->setBody('return count($items ?: $this->items);'); $method->addParameter('items', []) // $items = [] @@ -106,11 +107,11 @@ $method->addParameter('items', []) // $items = [] ->setType('array'); // array &$items = [] ``` -Wynika z tego, że: +Wynikiem jest: ```php /** - * Count it. + * Policz to. */ final protected function count(array &$items = []): ?int { @@ -118,7 +119,7 @@ final protected function count(array &$items = []): ?int } ``` -Parametry promowane wprowadzone przez PHP 8.0 mogą być przekazywane do konstruktora: +Promowane parametry wprowadzone w PHP 8.0 można przekazać do konstruktora: ```php $method = $class->addMethod('__construct'); @@ -127,7 +128,7 @@ $method->addPromotedParameter('args', []) ->setPrivate(); ``` -Skutkuje to: +Wynikiem jest: ```php public function __construct( @@ -137,15 +138,15 @@ public function __construct( } ``` -Właściwości i klasy readonly mogą być oznaczone poprzez `setReadOnly()`. +Właściwości i klasy przeznaczone tylko do odczytu można oznaczyć za pomocą funkcji `setReadOnly()`. ------ -Jeśli dodana właściwość, stała, metoda lub parametr już istnieje, rzuca wyjątek. +Jeśli dodawana właściwość, stała, metoda lub parametr już istnieją, zostanie rzucony wyjątek. -Członków można usunąć używając `removeProperty()`, `removeConstant()`, `removeMethod()` lub `removeParameter()`. +Członków klasy można usunąć za pomocą `removeProperty()`, `removeConstant()`, `removeMethod()` lub `removeParameter()`. -Możesz również dodać do klasy istniejące obiekty `Method`, `Property` lub `Constant`: +Do klasy można również dodać istniejące obiekty `Method`, `Property` lub `Constant`: ```php $method = new Nette\PhpGenerator\Method('getHandle'); @@ -158,7 +159,7 @@ $class = (new Nette\PhpGenerator\ClassType('Demo')) ->addMember($const); ``` -Możesz sklonować istniejące metody, właściwości i stałe z inną nazwą używając `cloneWithName()`: +Można również klonować istniejące metody, właściwości i stałe pod inną nazwą za pomocą `cloneWithName()`: ```php $methodCount = $class->getMethod('count'); @@ -167,17 +168,17 @@ $class->addMember($methodRecount); ``` -Interface lub Trait .[#toc-interface-or-trait] ----------------------------------------------- +Interfejs lub Trait +------------------- -Można tworzyć interfejsy i cechy (klasy [InterfaceType |api:Nette\PhpGenerator\InterfaceType] i [TraitType |api:Nette\PhpGenerator\TraitType]): +Można tworzyć interfejsy i traity (klasy [InterfaceType |api:Nette\PhpGenerator\InterfaceType] i [TraitType |api:Nette\PhpGenerator\TraitType]): ```php $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait'); ``` -Wykorzystanie cech: +Używanie traitów: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -202,10 +203,10 @@ class Demo ``` -Enums .[#toc-enums] -------------------- +Enumy +----- -Możesz łatwo stworzyć enum, które przynosi PHP 8.1 (klasa [EnumType |api:Nette\PhpGenerator\EnumType]): +Wyliczenia (enumy), które wprowadza PHP 8.1, można łatwo utworzyć w ten sposób (klasa [EnumType |api:Nette\PhpGenerator\EnumType]): ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -229,20 +230,20 @@ enum Suit } ``` -Możesz również zdefiniować skalarne odpowiedniki dla przypadków, aby utworzyć backed enum: +Można również zdefiniować ekvivalenty skalarne i utworzyć w ten sposób "backed" wyliczenie: ```php $enum->addCase('Clubs', '♣'); $enum->addCase('Diamonds', '♦'); ``` -Możliwe jest dodanie komentarza lub [atrybutów |#attributes] do każdego przypadku za pomocą `addComment()` lub `addAttribute()`. +Do każdego *case* można dodać komentarz lub [#atrybuty] za pomocą `addComment()` lub `addAttribute()`. -Klasa anonimowa .[#toc-anonymous-class] ---------------------------------------- +Klasy anonimowe +--------------- -Podaj `null` jako nazwę i masz klasę anonimową: +Jako nazwę przekażemy `null` i mamy klasę anonimową: ```php $class = new Nette\PhpGenerator\ClassType(null); @@ -264,10 +265,10 @@ $obj = new class ($val) { ``` -Funkcja globalna .[#toc-global-function] ----------------------------------------- +Funkcje globalne +---------------- -Kod funkcji wygeneruje klasa [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction]: +Kod funkcji generuje klasa [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction]: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -276,11 +277,11 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// lub użyć PsrPrinter dla wyjścia zgodnego z PSR-2 / PSR-12 +// lub użyj PsrPrinter dla wyjścia zgodnego z PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` -Result: +Wynik: ```php function foo($a, $b) @@ -290,10 +291,10 @@ function foo($a, $b) ``` -Zamknięcie .[#toc-closure] --------------------------- +Funkcje anonimowe +----------------- -Kod zamknięcia wygeneruje klasę [Closure |api:Nette\PhpGenerator\Closure]: +Kod funkcji anonimowych generuje klasa [Closure |api:Nette\PhpGenerator\Closure]: ```php $closure = new Nette\PhpGenerator\Closure; @@ -304,7 +305,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// lub użyć PsrPrinter dla wyjścia zgodnego z PSR-2 / PSR-12 +// lub użyj PsrPrinter dla wyjścia zgodnego z PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -317,10 +318,10 @@ function ($a, $b) use (&$c) { ``` -Funkcja strzałki .[#toc-arrow-function] ---------------------------------------- +Skrócone funkcje strzałkowe +--------------------------- -Możesz również wydrukować zamknięcie jako funkcję strzałki za pomocą drukarki: +Można również wypisać skróconą funkcję anonimową za pomocą printera: ```php $closure = new Nette\PhpGenerator\Closure; @@ -338,38 +339,38 @@ fn($a, $b) => $a + $b ``` -Sygnatura metody i funkcji .[#toc-method-and-function-signature] ----------------------------------------------------------------- +Sygnatury metod i funkcji +------------------------- -Metody są reprezentowane przez klasę [Method |api:Nette\PhpGenerator\Method]. Możesz ustawić widoczność, wartość zwrotną, dodać komentarze, [atrybuty |#Attributes] itp: +Metody reprezentuje klasa [Method |api:Nette\PhpGenerator\Method]. Można ustawić widoczność, wartość zwracaną, dodać komentarze, [#atrybuty] itp.: ```php $method = $class->addMethod('count') - ->addComment('Count it.') + ->addComment('Policz to.') ->setFinal() ->setProtected() ->setReturnType('?int'); ``` -Każdy parametr jest reprezentowany przez klasę [Parameter |api:Nette\PhpGenerator\Parameter]. Ponownie, możesz ustawić każdą możliwą właściwość: +Poszczególne parametry reprezentuje klasa [Parameter |api:Nette\PhpGenerator\Parameter]. Ponownie można ustawić wszystkie możliwe właściwości: ```php $method->addParameter('items', []) // $items = [] - ->setReference() // &$items = [] - ->setType('array'); // array &$items = [] + ->setReference() // &$items = [] + ->setType('array'); // array &$items = [] -// function count(&$items = []) +// function count(array &$items = []) ``` -Aby zdefiniować tzw. parametry variadics (lub również operator splat, spread, elipsa, rozpakowywanie czy trzy kropki), należy użyć `setVariadics()`: +Do definicji tzw. parametrów variadics (lub też operatora splat) służy `setVariadic()`: ```php $method = $class->addMethod('count'); -$method->setVariadics(true); +$method->setVariadic(true); $method->addParameter('items'); ``` -Generates: +Wygeneruje: ```php function count(...$items) @@ -378,10 +379,10 @@ function count(...$items) ``` -Metoda i ciało funkcji .[#toc-method-and-function-body] -------------------------------------------------------- +Ciała metod i funkcji +--------------------- -Ciało może być przekazane do metody `setBody()` jednorazowo lub sekwencyjnie (linia po linii) poprzez wielokrotne wywołanie `addBody()`: +Ciało można przekazać naraz metodzie `setBody()` lub stopniowo (linia po linii) przez wielokrotne wywołanie `addBody()`: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -390,7 +391,7 @@ $function->addBody('return $a;'); echo $function; ``` -Result +Wynik ```php function foo() @@ -400,9 +401,9 @@ function foo() } ``` -Możesz użyć specjalnych zamienników dla poręcznego sposobu wstrzykiwania zmiennych. +Można użyć specjalnych symboli zastępczych do łatwego wstawiania zmiennych. -Proste symbole miejsc `?` +Proste symbole zastępcze `?` ```php $str = 'any string'; @@ -412,7 +413,7 @@ $function->addBody('return substr(?, ?);', [$str, $num]); echo $function; ``` -Wynik: +Wynik ```php function foo() @@ -421,7 +422,7 @@ function foo() } ``` -Variadic placeholder `...?` +Symbol zastępczy dla variadic `...?` ```php $items = [1, 2, 3]; @@ -439,7 +440,7 @@ function foo() } ``` -Możesz również użyć PHP 8 nazwanych parametrów używając placeholder `...?:` +Można również użyć nazwanych parametrów dla PHP 8 za pomocą `...?:` ```php $items = ['foo' => 1, 'bar' => true]; @@ -448,7 +449,7 @@ $function->setBody('myfunc(...?:);', [$items]); // myfunc(foo: 1, bar: true); ``` -Ucieknij od placeholder używając slash `\?` +Symbol zastępczy escapuje się za pomocą ukośnika `\?` ```php $num = 3; @@ -468,45 +469,70 @@ function foo($a) ``` -Drukarki i zgodność z PSR .[#toc-printers-and-psr-compliance] -------------------------------------------------------------- +Printer i zgodność z PSR +------------------------ -Kod PHP jest generowany przez obiekty `Printer`. Istnieje `PsrPrinter`, którego wyjście jest zgodne z PSR-2 i PSR-12 i używa spacji do wcięć, oraz `Printer`, który używa tabulacji do wcięć. +Do generowania kodu PHP służy klasa [Printer |api:Nette\PhpGenerator\Printer]: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // to samo, co: echo $class +``` + +Potrafi wygenerować kod wszystkich innych elementów, oferuje metody takie jak `printFunction()`, `printNamespace()`, itd. + +Dostępna jest również klasa `PsrPrinter`, której wyjście jest zgodne ze stylem kodowania PSR-2 / PSR-12 / PER: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // 4 spacje wcięcie +echo $printer->printClass($class); ``` -Potrzebujesz dostosować zachowanie drukarki? Utwórz własne, dziedzicząc po klasie `Printer`. Możesz przekonfigurować te zmienne: +Potrzebujesz dostosować zachowanie do własnych potrzeb? Utwórz własną wersję, dziedzicząc po klasie `Printer`. Można przekonfigurować następujące zmienne: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // długość linii, po której następuje zawijanie wiersza public int $wrapLength = 120; + // znak wcięcia, może być zastąpiony sekwencją spacji public string $indentation = "\t"; + // liczba pustych linii między właściwościami public int $linesBetweenProperties = 0; + // liczba pustych linii między metodami public int $linesBetweenMethods = 2; + // liczba pustych linii między grupami 'use statements' dla klas, funkcji i stałych public int $linesBetweenUseTypes = 0; + // pozycja otwierającego nawiasu klamrowego dla funkcji i metod public bool $bracesOnNextLine = true; + // umieść jeden parametr w jednej linii, nawet jeśli ma atrybut lub jest promowany + public bool $singleParameterOnOneLine = false; + // pomija przestrzenie nazw, które nie zawierają żadnej klasy ani funkcji + public bool $omitEmptyNamespaces = true; + // separator między prawym nawiasem a typem zwracanym funkcji i metod public string $returnTypeColon = ': '; } ``` +Jak i dlaczego właściwie różnią się standardowy `Printer` i `PsrPrinter`? Dlaczego w pakiecie nie ma tylko jednego printera, a mianowicie `PsrPrinter`? -Typy .[#toc-types] ------------------- +Standardowy `Printer` formatuje kod tak, jak to robimy w całym Nette. Ponieważ Nette powstało znacznie wcześniej niż PSR, a także dlatego, że PSR przez długie lata nie dostarczało standardów na czas, ale na przykład z kilkuletnim opóźnieniem od wprowadzenia nowej funkcji w PHP, doszło do tego, że [standard kodowania |contributing:coding-standard] różni się w kilku drobnych szczegółach. Większą różnicą jest tylko używanie tabulatorów zamiast spacji. Wiemy, że używanie tabulatorów w naszych projektach umożliwia dostosowanie szerokości, co jest [niezbędne dla osób z wadami wzroku |contributing:coding-standard#Tabulatory zamiast spacji]. Przykładem drobnej różnicy jest umieszczenie nawiasu klamrowego na osobnej linii w przypadku funkcji i metod, i to zawsze. Zalecenie PSR wydaje nam się nielogiczne i prowadzi do [zmniejszenia czytelności kodu |contributing:coding-standard#Zawijanie i nawiasy klamrowe]. -Każdy typ lub typ unii / przecięcia może być przekazany jako ciąg, możesz również użyć predefiniowanych stałych dla typów natywnych: + +Typy +---- + +Każdy typ lub typ union/intersection można przekazać jako ciąg znaków, można również użyć predefiniowanych stałych dla typów natywnych: ```php use Nette\PhpGenerator\Type; $member->setType('array'); // lub Type::Array; -$member->setType('array|string'); // lub Type::union('array', 'string') +$member->setType('?array'); // lub Type::nullable(Type::Array); +$member->setType('array|string'); // lub Type::union(Type::Array, Type::String) $member->setType('Foo&Bar'); // lub Type::intersection(Foo::class, Bar::class) $member->setType(null); // usuwa typ ``` @@ -514,10 +540,10 @@ $member->setType(null); // usuwa typ To samo dotyczy metody `setReturnType()`. -Literały .[#toc-literals] -------------------------- +Literały +-------- -Dzięki `Literal` możesz przekazać dowolny kod PHP do, na przykład, domyślnych wartości właściwości lub parametrów itp: +Za pomocą `Literal` można przekazywać dowolny kod PHP, na przykład dla domyślnych wartości właściwości lub parametrów itp.: ```php use Nette\PhpGenerator\Literal; @@ -545,25 +571,37 @@ class Demo } ``` -Możesz również przekazać parametry do `Literal` i mieć je sformatowane w poprawny kod PHP za pomocą [specjalnych placeholderów |#method-and-function-body-generator]: +Można również przekazać parametry do `Literal` i pozwolić je sformatować do poprawnego kodu PHP za pomocą [symboli zastępczych |#Ciała metod i funkcji]: ```php new Literal('substr(?, ?)', [$a, $b]); -// generuje, na przykład: substr('hello', 5); +// generuje na przykład: substr('hello', 5); ``` +Literał reprezentujący utworzenie nowego obiektu można łatwo wygenerować za pomocą metody `new`: -Atrybuty .[#toc-attributes] ---------------------------- +```php +Literal::new(Demo::class, [$a, 'foo' => $b]); +// generuje na przykład: new Demo(10, foo: 20) +``` + + +Atrybuty +-------- -Możesz dodać atrybuty PHP 8 do wszystkich klas, metod, właściwości, stałych, przypadków enum, funkcji, domknięć i parametrów. [Literały |#Literals] mogą być również używane jako wartości parametrów. +Atrybuty PHP 8 można dodać do wszystkich klas, metod, właściwości, stałych, enumów, funkcji, closures i parametrów. Jako wartości parametrów można używać również [#literaly]. ```php $class = new Nette\PhpGenerator\ClassType('Demo'); -$class->addAttribute('Deprecated'); +$class->addAttribute('Table', [ + 'name' => 'user', + 'constraints' => [ + Literal::new('UniqueConstraint', ['name' => 'ean', 'columns' => ['ean']]), + ], +]); $class->addProperty('list') - ->addAttribute('WithArguments', [1, 2]); + ->addAttribute('Deprecated'); $method = $class->addMethod('count') ->addAttribute('Foo\Cached', ['mode' => true]); @@ -577,42 +615,126 @@ echo $class; Wynik: ```php -#[Deprecated] +#[Table(name: 'user', constraints: [new UniqueConstraint(name: 'ean', columns: ['ean'])])] class Demo { - #[WithArguments(1, 2)] + #[Deprecated] public $list; #[Foo\Cached(mode: true)] - public function count(#[Bar] $items) - { + public function count( + #[Bar] + $items, + ) { } } ``` -Przestrzeń nazw .[#toc-namespace] ---------------------------------- +Property Hooks +-------------- + +Za pomocą property hooks (reprezentowanych przez klasę [PropertyHook|api:Nette\PhpGenerator\PropertyHook]) można zdefiniować operacje get i set dla właściwości, co jest funkcją wprowadzoną w PHP 8.4: + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$prop = $class->addProperty('firstName') + ->setType('string'); + +$prop->addHook('set', 'strtolower($value)') + ->addParameter('value') + ->setType('string'); + +$prop->addHook('get') + ->setBody('return ucfirst($this->firstName);'); + +echo $class; +``` + +Wygeneruje: + +```php +class Demo +{ + public string $firstName { + set(string $value) => strtolower($value); + get { + return ucfirst($this->firstName); + } + } +} +``` + +Właściwości i property hooks mogą być abstrakcyjne lub finalne: + +```php +$class->addProperty('id') + ->setType('int') + ->addHook('get') + ->setAbstract(); + +$class->addProperty('role') + ->setType('string') + ->addHook('set', 'strtolower($value)') + ->setFinal(); +``` + + +Widoczność asymetryczna +----------------------- + +PHP 8.4 wprowadza widoczność asymetryczną dla właściwości. Można ustawić różne poziomy dostępu dla odczytu i zapisu. + +Widoczność można ustawić albo za pomocą metody `setVisibility()` z dwoma parametrami, albo za pomocą `setPublic()`, `setProtected()` lub `setPrivate()` z parametrem `mode`, który określa, czy widoczność dotyczy odczytu czy zapisu właściwości. Domyślny tryb to `'get'`. + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); + +$class->addProperty('name') + ->setType('string') + ->setVisibility('public', 'private'); // public dla odczytu, private dla zapisu + +$class->addProperty('id') + ->setType('int') + ->setProtected('set'); // protected dla zapisu + +echo $class; +``` + +Wygeneruje: + +```php +class Demo +{ + public private(set) string $name; + + protected(set) int $id; +} +``` + + +Przestrzeń nazw +--------------- -Klasy, cechy, interfejsy i enum (dalej klasy) mogą być grupowane w przestrzenie nazw[(PhpNamespace |api:Nette\PhpGenerator\PhpNamespace]): +Klasy, właściwości, interfejsy i wyliczenia (dalej zwane klasami) można grupować w przestrzenie nazw reprezentowane przez klasę [PhpNamespace |api:Nette\PhpGenerator\PhpNamespace]: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); -// tworzyć nowe klasy w przestrzeni nazw +// tworzymy nowe klasy w przestrzeni nazw $class = $namespace->addClass('Task'); $interface = $namespace->addInterface('Countable'); $trait = $namespace->addTrait('NameAware'); -// lub wstawić istniejącą klasę do przestrzeni nazw +// lub wstawiamy istniejącą klasę do przestrzeni nazw $class = new Nette\PhpGenerator\ClassType('Task'); $namespace->add($class); ``` -Jeśli klasa już istnieje, to rzuca wyjątek. +Jeśli klasa już istnieje, zostanie rzucony wyjątek. -Możesz zdefiniować oświadczenia o użyciu: +Można zdefiniować klauzule use: ```php // use Http\Request; @@ -623,14 +745,14 @@ $namespace->addUse(Http\Request::class, 'HttpReq'); $namespace->addUseFunction('iter\range'); ``` -Aby uprościć w pełni kwalifikowaną nazwę klasy, funkcji lub stałej zgodnie ze zdefiniowanymi aliasami, użyj metody `simplifyName`: +Aby uprościć w pełni kwalifikowaną nazwę klasy, funkcji lub stałej zgodnie z zdefiniowanymi aliasami, użyj metody `simplifyName`: ```php -echo $namespace->simplifyName('Foo\Bar'); // 'Bar', ponieważ 'Foo' jest bieżącą przestrzenią nazw -echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', z powodu zdefiniowanej deklaracji użycia +echo $namespace->simplifyName('Foo\Bar'); // 'Bar', ponieważ 'Foo' to bieżąca przestrzeń nazw +echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', z powodu zdefiniowanego use-statement ``` -I odwrotnie, można przekształcić uproszczoną nazwę klasy, funkcji lub stałej na w pełni kwalifikowaną, używając metody `resolveName`: +Uproszczoną nazwę klasy, funkcji lub stałej można na odwrót przekształcić na w pełni kwalifikowaną nazwę za pomocą metody `resolveName`: ```php echo $namespace->resolveName('Bar'); // 'Foo\Bar' @@ -638,29 +760,27 @@ echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range' ``` -Class Names Resolving (rozwiązywanie nazw klas) .[#toc-class-names-resolving] ------------------------------------------------------------------------------ +Tłumaczenie nazw klas +--------------------- -**Kiedy klasa jest częścią przestrzeni nazw, jest renderowana nieco inaczej**: wszystkie typy (tj. podpowiedzi typów, typy zwracane, nazwa klasy nadrzędnej, -zaimplementowane interfejsy, użyte cechy i atrybuty) są automatycznie *rozwiązywane* (chyba że wyłączysz tę funkcję, patrz poniżej). -Oznacza to, że musisz **używać pełnych nazw klas** w definicjach, a zostaną one zastąpione aliasami (zgodnie z deklaracjami użycia) lub w pełni kwalifikowanymi nazwami w wynikowym kodzie: +**Gdy klasa jest częścią przestrzeni nazw, jest renderowana nieco inaczej:** wszystkie typy (na przykład typehinty, typy zwracane, nazwa klasy nadrzędnej, implementowane interfejsy, używane traity i atrybuty) są automatycznie *tłumaczone* (jeśli tego nie wyłączysz, patrz poniżej). Oznacza to, że w definicjach musisz **używać pełnych nazw klas**, a te zostaną zastąpione aliasami (zgodnie z klauzulami use) lub w pełni kwalifikowanymi nazwami w wynikowym kodzie: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); $namespace->addUse('Bar\AliasedClass'); $class = $namespace->addClass('Demo'); -$class->addImplement('Foo\A') // to się uprości do A - ->addTrait('Bar\AliasedClass'); // uprości się do AliasedClass +$class->addImplement('Foo\A') // zostanie uproszczone do A + ->addTrait('Bar\AliasedClass'); // zostanie uproszczone do AliasedClass $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyType('Foo')); // w komentarzach uprościć ręcznie +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // w komentarzach upraszczamy ręcznie $method->addParameter('arg') - ->setType('Bar\OtherClass'); // zostanie rozwiązany do klasy \Bar\Bar\Class + ->setType('Bar\OtherClass'); // zostanie przetłumaczone na \Bar\OtherClass echo $namespace; -// lub użyć PsrPrinter dla wyjścia zgodnego z PSR-2 / PSR-12 +// lub użyj PsrPrinter dla wyjścia zgodnego z PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -684,7 +804,7 @@ class Demo implements A } ``` -Auto-resolving można wyłączyć w ten sposób: +Automatyczne tłumaczenie można wyłączyć w ten sposób: ```php $printer = new Nette\PhpGenerator\Printer; // lub PsrPrinter @@ -693,14 +813,14 @@ echo $printer->printNamespace($namespace); ``` -Pliki PHP. .[#toc-php-files] ----------------------------- +Pliki PHP +--------- -Klasy, funkcje i przestrzenie nazw mogą być pogrupowane w pliki PHP reprezentowane przez klasę [PhpFile |api:Nette\PhpGenerator\PhpFile]: +Klasy, funkcje i przestrzenie nazw można grupować w pliki PHP reprezentowane przez klasę [PhpFile|api:Nette\PhpGenerator\PhpFile]: ```php $file = new Nette\PhpGenerator\PhpFile; -$file->addComment('This file is auto-generated.'); +$file->addComment('Ten plik jest generowany automatycznie.'); $file->setStrictTypes(); // dodaje declare(strict_types=1) $class = $file->addClass('Foo\A'); @@ -713,7 +833,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// lub użyj PsrPrinter dla wyjścia zgodnego z PSR-2 / PSR-12 +// lub użyj PsrPrinter dla wyjścia zgodnego z PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -723,7 +843,7 @@ Wynik: <?php /** - * This file is auto-generated. + * Ten plik jest generowany automatycznie. */ declare(strict_types=1); @@ -739,27 +859,28 @@ function foo() } ``` +**Uwaga:** Do plików nie można dodawać żadnego innego kodu poza funkcjami i klasami. + -Generowanie według istniejących .[#toc-generating-according-to-existing-ones] ------------------------------------------------------------------------------ +Generowanie na podstawie istniejących +------------------------------------- -Oprócz możliwości modelowania klas i funkcji za pomocą opisanego powyżej API, można również zlecić ich automatyczne generowanie według istniejących: +Oprócz tego, że klasy i funkcje można modelować za pomocą opisanego powyżej API, można je również wygenerować automatycznie na podstawie istniejących wzorców: ```php -// tworzy klasę identyczną z klasą PDO +// tworzy klasę taką samą jak klasa PDO $class = Nette\PhpGenerator\ClassType::from(PDO::class); -// tworzy funkcję identyczną z trim() +// tworzy funkcję identyczną z funkcją trim() $function = Nette\PhpGenerator\GlobalFunction::from('trim'); -// tworzy zamknięcie identyczne jak +// tworzy closure na podstawie podanej $closure = Nette\PhpGenerator\Closure::from( function (stdClass $a, $b = null) {}, ); ``` -Ciała funkcji i metod są domyślnie puste. Jeśli chcesz je również załadować, użyj tego sposobu -(wymaga on zainstalowania `nikic/php-parser` ): +Ciała funkcji i metod są domyślnie puste. Jeśli chcesz je również załadować, użyj tego sposobu (wymaga instalacji pakietu `nikic/php-parser`): ```php $class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true); @@ -768,10 +889,10 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); ``` -Ładowanie z pliku PHP .[#toc-loading-from-php-file] ---------------------------------------------------- +Wczytywanie z plików PHP +------------------------ -Można też ładować funkcje, klasy, interfejsy i enumy bezpośrednio z ciągu kodu PHP. Na przykład tworzymy obiekt `ClassType` w ten sposób: +Funkcje, klasy, interfejsy i enumy można wczytywać również bezpośrednio z ciągu znaków zawierającego kod PHP. Na przykład w ten sposób utworzymy obiekt `ClassType`: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -784,39 +905,69 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -Podczas ładowania klas z kodu PHP, komentarze jednolinijkowe poza ciałami metod są ignorowane (np. dla właściwości itp.), ponieważ ta biblioteka nie posiada API do pracy z nimi. +Podczas wczytywania klas z kodu PHP, jednoliniowe komentarze poza ciałami metod są ignorowane (np. przy właściwościach itp.), ponieważ ta biblioteka nie ma API do pracy z nimi. -Możesz również załadować bezpośrednio cały plik PHP, który może zawierać dowolną liczbę klas, funkcji, a nawet wiele przestrzeni nazw: +Można również wczytać bezpośrednio cały plik PHP, który może zawierać dowolną liczbę klas, funkcji lub nawet przestrzeni nazw: ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` -Ładowany jest również początkowy komentarz do pliku oraz deklaracja `strict_types`. Z drugiej strony, wszystkie inne globalne kody są ignorowane. +Wczytany zostanie również komentarz początkowy pliku i deklaracja `strict_types`. Natomiast cały pozostały kod globalny jest ignorowany. -Wymaga to zainstalowania `nikic/php-parser`. +Wymagane jest zainstalowanie `nikic/php-parser`. .[note] -Jeśli musisz manipulować globalnym kodem w plikach lub pojedynczymi stwierdzeniami w ciałach metod, lepiej jest użyć bezpośrednio biblioteki `nikic/php-parser`. +Jeśli potrzebujesz manipulować globalnym kodem w plikach lub poszczególnymi instrukcjami w ciałach metod, lepiej użyć bezpośrednio biblioteki `nikic/php-parser`. + + +Class Manipulator +----------------- + +Klasa [ClassManipulator|api:Nette\PhpGenerator\ClassManipulator] dostarcza narzędzi do manipulacji klasami. +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$manipulator = new Nette\PhpGenerator\ClassManipulator($class); +``` -Zrzutka zmiennych .[#toc-variables-dumper] ------------------------------------------- +Metoda `inheritMethod()` kopiuje metodę z klasy nadrzędnej lub implementowanego interfejsu do Twojej klasy. Pozwala to nadpisać metodę lub rozszerzyć jej sygnaturę: + +```php +$method = $manipulator->inheritMethod('bar'); +$method->setBody('...'); +``` -Dumper zwraca parsowalną reprezentację zmiennej w PHP. Zapewnia lepsze i bardziej przejrzyste wyjście niż funkcja natywna `var_export()`. +Metoda `inheritProperty()` kopiuje właściwość z klasy nadrzędnej do Twojej klasy. Jest to przydatne, gdy chcesz mieć w swojej klasie tę samą właściwość, ale na przykład z inną wartością domyślną: + +```php +$property = $manipulator->inheritProperty('foo'); +$property->setValue('new value'); +``` + +Metoda `implement()` automatycznie implementuje wszystkie metody i właściwości z danego interfejsu lub klasy abstrakcyjnej w Twojej klasie: + +```php +$manipulator->implement(SomeInterface::class); +// Teraz Twoja klasa implementuje SomeInterface i zawiera wszystkie jego metody +``` + + +Zrzut zmiennych +--------------- + +Klasa `Dumper` konwertuje zmienną na parsowalny kod PHP. Dostarcza lepsze i bardziej przejrzyste wyjście niż standardowa funkcja `var_export()`. ```php $dumper = new Nette\PhpGenerator\Dumper; $var = ['a', 'b', 123]; -echo $dumper->dump($var); // drukuje ['a', 'b', 123] +echo $dumper->dump($var); // wypisze ['a', 'b', 123] ``` -Tabela kompatybilności .[#toc-compatibility-table] --------------------------------------------------- - -PhpGenerator 4.0 jest kompatybilny z PHP 8.0 do 8.2 +Tabela kompatybilności +---------------------- -{{leftbar: nette:@menu-topics}} +PhpGenerator 4.1 jest kompatybilny z PHP 8.0 do 8.4. diff --git a/php-generator/pl/@meta.texy b/php-generator/pl/@meta.texy new file mode 100644 index 0000000000..08f2227fb5 --- /dev/null +++ b/php-generator/pl/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Dokumentacja Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/php-generator/pt/@home.texy b/php-generator/pt/@home.texy index de1fc96fef..cd81feaadc 100644 --- a/php-generator/pt/@home.texy +++ b/php-generator/pt/@home.texy @@ -1,31 +1,32 @@ -Gerador de código PHP -********************* - -<div class=perex> -- Necessidade de gerar código PHP para classes, funções, arquivos PHP, etc.? -- Suporta todas as últimas características do PHP, como enumeração, atributos, etc. -- Permite modificar facilmente as classes existentes -- Saída compatível com PSR-12 -- Biblioteca altamente madura, estável e amplamente utilizada +Nette PhpGenerator +****************** + +<div class="perex"> +Procurando uma ferramenta para gerar código PHP para classes, funções ou arquivos completos? + +- Suporta todos os recursos mais recentes do PHP (como property hooks, enums, atributos, etc.) +- Permite modificar facilmente classes existentes +- O código de saída está em conformidade com o estilo de codificação PSR-12 / PER +- Biblioteca madura, estável e amplamente utilizada </div> -Instalação .[#toc-installation] -------------------------------- +Instalação +---------- -Baixe e instale o pacote usando [o Composer |best-practices:composer]: +Baixe e instale a biblioteca usando o [Composer|best-practices:composer]: ```shell composer require nette/php-generator ``` -Para compatibilidade com PHP, consulte a [tabela |#Compatibility Table]. +A compatibilidade com PHP pode ser encontrada na [tabela |#Tabela de compatibilidade]. -Aulas .[#toc-classes] ---------------------- +Classes +------- -Vamos começar com um exemplo simples de geração de classe usando o [ClassType |api:Nette\PhpGenerator\ClassType]: +Vamos começar com um exemplo de criação de classe usando [ClassType |api:Nette\PhpGenerator\ClassType]: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -34,19 +35,19 @@ $class ->setFinal() ->setExtends(ParentClass::class) ->addImplement(Countable::class) - ->addComment("Description of class.\nSecond line\n") + ->addComment("Descrição da classe.\nSegunda linha\n") ->addComment('@property-read Nette\Forms\Form $form'); -// para gerar o código PHP simplesmente lançar para string ou usar eco: +// o código é simplesmente gerado convertendo para string ou usando echo: echo $class; ``` -Ele renderá este resultado: +Retorna o seguinte resultado: ```php /** - * Description of class. - * Second line + * Descrição da classe + * Segunda linha * * @property-read Nette\Forms\Form $form */ @@ -55,32 +56,32 @@ final class Demo extends ParentClass implements Countable } ``` -Também podemos utilizar uma impressora para gerar o código, que, ao contrário de `echo $class`, poderemos [configurar ainda mais |#Printers and PSR compliance]: +Também podemos usar o chamado printer para gerar o código, que, ao contrário de `echo $class`, poderemos [configurar posteriormente |#Printer e conformidade com PSR]: ```php $printer = new Nette\PhpGenerator\Printer; echo $printer->printClass($class); ``` -Podemos acrescentar constantes (classe [Constante |api:Nette\PhpGenerator\Constant]) e propriedades (classe [Propriedade |api:Nette\PhpGenerator\Property]): +Podemos adicionar constantes (classe [Constant |api:Nette\PhpGenerator\Constant]) e propriedades (classe [Property |api:Nette\PhpGenerator\Property]): ```php $class->addConstant('ID', 123) - ->setProtected() // visiblidade constante + ->setProtected() // visibilidade das constantes ->setType('int') ->setFinal(); $class->addProperty('items', [1, 2, 3]) - ->setPrivate() // ou setVisibilidade ("privado") + ->setPrivate() // ou setVisibility('private') ->setStatic() ->addComment('@var int[]'); $class->addProperty('list') ->setType('?array') - ->setInitialized(); // estampas '= nulo + ->setInitialized(); // imprime '= null' ``` -Ele gera: +Gera: ```php final protected const int ID = 123; @@ -91,14 +92,14 @@ private static $items = [1, 2, 3]; public ?array $list = null; ``` -E podemos acrescentar [métodos |#Method and Function Signature]: +E podemos adicionar [métodos |#Assinaturas de métodos e funções]: ```php $method = $class->addMethod('count') - ->addComment('Count it.') + ->addComment('Contá-lo.') ->setFinal() ->setProtected() - ->setReturnType('?int') // tipo de retorno do método + ->setReturnType('?int') // tipos de retorno em métodos ->setBody('return count($items ?: $this->items);'); $method->addParameter('items', []) // $items = [] @@ -106,11 +107,11 @@ $method->addParameter('items', []) // $items = [] ->setType('array'); // array &$items = [] ``` -O resultado é +O resultado é: ```php /** - * Count it. + * Contá-lo. */ final protected function count(array &$items = []): ?int { @@ -118,7 +119,7 @@ final protected function count(array &$items = []): ?int } ``` -Os parâmetros promovidos introduzidos pelo PHP 8.0 podem ser passados para o construtor: +Parâmetros promovidos introduzidos no PHP 8.0 podem ser passados para o construtor: ```php $method = $class->addMethod('__construct'); @@ -127,7 +128,7 @@ $method->addPromotedParameter('args', []) ->setPrivate(); ``` -O resultado é +O resultado é: ```php public function __construct( @@ -137,15 +138,15 @@ public function __construct( } ``` -As propriedades e classes só de leitura podem ser marcadas via `setReadOnly()`. +Propriedades e classes somente leitura podem ser marcadas usando a função `setReadOnly()`. ------ -Se a propriedade adicionada, constante, método ou parâmetro já existir, ela lança uma exceção. +Se a propriedade, constante, método ou parâmetro adicionado já existir, uma exceção será lançada. -Os membros podem ser removidos usando `removeProperty()`, `removeConstant()`, `removeMethod()` ou `removeParameter()`. +Membros da classe podem ser removidos usando `removeProperty()`, `removeConstant()`, `removeMethod()` ou `removeParameter()`. -Você também pode adicionar objetos existentes `Method`, `Property` ou `Constant` à classe: +Você também pode adicionar objetos `Method`, `Property` ou `Constant` existentes à classe: ```php $method = new Nette\PhpGenerator\Method('getHandle'); @@ -158,7 +159,7 @@ $class = (new Nette\PhpGenerator\ClassType('Demo')) ->addMember($const); ``` -Você pode clonar métodos, propriedades e constantes existentes com um nome diferente usando `cloneWithName()`: +Você também pode clonar métodos, propriedades e constantes existentes com um nome diferente usando `cloneWithName()`: ```php $methodCount = $class->getMethod('count'); @@ -167,17 +168,17 @@ $class->addMember($methodRecount); ``` -Interface ou Traço .[#toc-interface-or-trait] ---------------------------------------------- +Interface ou Trait +------------------ -Você pode criar interfaces e traços (classes [InterfaceType |api:Nette\PhpGenerator\InterfaceType] e [TraitType |api:Nette\PhpGenerator\TraitType]): +Você pode criar interfaces e traits (classes [InterfaceType |api:Nette\PhpGenerator\InterfaceType] e [TraitType |api:Nette\PhpGenerator\TraitType]): ```php $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait'); ``` -Usando traços: +Usando traits: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -202,10 +203,10 @@ class Demo ``` -Enums .[#toc-enums] -------------------- +Enums +----- -Você pode criar facilmente os enums que o PHP 8.1 traz (classe [EnumType |api:Nette\PhpGenerator\EnumType]): +Enums, introduzidos no PHP 8.1, podem ser facilmente criados assim: (classe [EnumType |api:Nette\PhpGenerator\EnumType]): ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -229,20 +230,20 @@ enum Suit } ``` -Você também pode definir equivalentes escalares para casos a fim de criar um enumero de apoio: +Você também pode definir equivalentes escalares e criar assim um enum "backed": ```php $enum->addCase('Clubs', '♣'); $enum->addCase('Diamonds', '♦'); ``` -É possível adicionar um comentário ou [atributos |#attributes] a cada caso usando `addComment()` ou `addAttribute()`. +É possível adicionar um comentário ou [#atributos] a cada *case* usando `addComment()` ou `addAttribute()`. -Classe Anônima .[#toc-anonymous-class] --------------------------------------- +Classes Anônimas +---------------- -Dê `null` como o nome e você tem uma classe anônima: +Passamos `null` como nome e temos uma classe anônima: ```php $class = new Nette\PhpGenerator\ClassType(null); @@ -264,10 +265,10 @@ $obj = new class ($val) { ``` -Função Global .[#toc-global-function] -------------------------------------- +Funções Globais +--------------- -O código de funções irá gerar a classe [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction]: +O código das funções é gerado pela classe [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction]: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -276,8 +277,8 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// ou usar PsrPrinter para saída conforme PSR-2 / PSR-12 -// echo (novo Nette\PhpGenerator\PsrPrinter)->printFunction($function); +// ou use PsrPrinter para saída em conformidade com PSR-2 / PSR-12 / PER +// echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` Resultado: @@ -290,10 +291,10 @@ function foo($a, $b) ``` -Fechamento .[#toc-closure] --------------------------- +Funções Anônimas +---------------- -O código de fechamentos gerará [fechamento de |api:Nette\PhpGenerator\Closure] classe: +O código das funções anônimas é gerado pela classe [Closure |api:Nette\PhpGenerator\Closure]: ```php $closure = new Nette\PhpGenerator\Closure; @@ -304,8 +305,8 @@ $closure->addUse('c') ->setReference(); echo $closure; -// ou usar PsrPrinter para saída conforme PSR-2 / PSR-12 -// echo (novo Nette\PhpGenerator\PsrPrinter)->printClosure($closure); +// ou use PsrPrinter para saída em conformidade com PSR-2 / PSR-12 / PER +// echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` Resultado: @@ -317,10 +318,10 @@ function ($a, $b) use (&$c) { ``` -Função de Seta .[#toc-arrow-function] -------------------------------------- +Funções de seta abreviadas +-------------------------- -Você também pode imprimir o fechamento como função de seta usando a impressora: +Você também pode imprimir uma função anônima abreviada usando o printer: ```php $closure = new Nette\PhpGenerator\Closure; @@ -338,34 +339,34 @@ fn($a, $b) => $a + $b ``` -Método e Assinatura da Função .[#toc-method-and-function-signature] -------------------------------------------------------------------- +Assinaturas de métodos e funções +-------------------------------- -Os métodos são representados pelo [método de |api:Nette\PhpGenerator\Method] classe. Você pode definir visibilidade, valor de retorno, adicionar comentários, [atributos |#Attributes], etc: +Métodos são representados pela classe [Method |api:Nette\PhpGenerator\Method]. Você pode definir visibilidade, valor de retorno, adicionar comentários, [#atributos], etc: ```php $method = $class->addMethod('count') - ->addComment('Count it.') + ->addComment('Contá-lo.') // Count it. ->setFinal() ->setProtected() ->setReturnType('?int'); ``` -Cada parâmetro é representado por um [parâmetro de |api:Nette\PhpGenerator\Parameter] classe. Novamente, você pode definir todos os bens concebíveis: +Parâmetros individuais são representados pela classe [Parameter |api:Nette\PhpGenerator\Parameter]. Novamente, você pode definir todas as propriedades imagináveis: ```php $method->addParameter('items', []) // $items = [] - ->setReference() // &$items = [] - ->setType('array'); // array &$items = [] + ->setReference() // &$items = [] + ->setType('array'); // array &$items = [] -// function count(&$items = []) +// function count(array &$items = []) ``` -Para definir os chamados parâmetros de variação (ou também o splat, spread, elipse, desempacotamento ou operador de três pontos), use `setVariadics()`: +Para definir os chamados parâmetros variádicos (ou também operador splat), use `setVariadic()`: ```php $method = $class->addMethod('count'); -$method->setVariadics(true); +$method->setVariadic(true); $method->addParameter('items'); ``` @@ -378,10 +379,10 @@ function count(...$items) ``` -Método e Função Corpo .[#toc-method-and-function-body] ------------------------------------------------------- +Corpos de métodos e funções +--------------------------- -O corpo pode ser passado para o método `setBody()` de uma vez ou sequencialmente (linha por linha), ligando repetidamente para `addBody()`: +O corpo pode ser passado de uma vez para o método `setBody()` ou gradualmente (linha por linha) chamando repetidamente `addBody()`: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -400,28 +401,28 @@ function foo() } ``` -Você pode usar porta-lugares especiais para injetar variáveis de forma prática. +Você pode usar placeholders especiais para inserir variáveis facilmente. -Porta-lugares simples `?` +Placeholders simples `?` ```php -$str = 'any string'; +$str = 'qualquer string'; $num = 3; $function = new Nette\PhpGenerator\GlobalFunction('foo'); $function->addBody('return substr(?, ?);', [$str, $num]); echo $function; ``` -Resultado: +Resultado ```php function foo() { - return substr('any string', 3); + return substr('qualquer string', 3); } ``` -Variadic placeholder `...?` +Placeholder para variadic `...?` ```php $items = [1, 2, 3]; @@ -439,7 +440,7 @@ function foo() } ``` -Você também pode usar parâmetros nomeados no PHP 8 usando placeholder `...?:` +Você também pode usar parâmetros nomeados para PHP 8 usando `...?:` ```php $items = ['foo' => 1, 'bar' => true]; @@ -448,7 +449,7 @@ $function->setBody('myfunc(...?:);', [$items]); // myfunc(foo: 1, bar: true); ``` -Porta-lugar de fuga usando barra `\?` +O placeholder é escapado com uma barra invertida `\?` ```php $num = 3; @@ -468,56 +469,81 @@ function foo($a) ``` -Impressoras e conformidade PSR .[#toc-printers-and-psr-compliance] ------------------------------------------------------------------- +Printer e conformidade com PSR +------------------------------ -O código PHP é gerado por objetos `Printer`. Há um `PsrPrinter` cuja saída está em conformidade com PSR-2 e PSR-12 e usa espaços para recuo, e um `Printer` que usa abas para recuo. +Para gerar código PHP, use a classe [Printer |api:Nette\PhpGenerator\Printer]: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // o mesmo que: echo $class +``` + +Ele pode gerar código para todos os outros elementos, oferece métodos como `printFunction()`, `printNamespace()`, etc. + +Também está disponível a classe `PsrPrinter`, cuja saída está em conformidade com o estilo de codificação PSR-2 / PSR-12 / PER: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // 4 espaços de indentação +echo $printer->printClass($class); ``` -Necessidade de personalizar o comportamento da impressora? Crie seu próprio, herdando a classe `Printer`. Você pode reconfigurar estas variáveis: +Precisa ajustar o comportamento sob medida? Crie sua própria versão herdando da classe `Printer`. Estas variáveis podem ser reconfiguradas: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // comprimento da linha após o qual ocorrerá a quebra de linha public int $wrapLength = 120; + // caractere de indentação, pode ser substituído por uma sequência de espaços public string $indentation = "\t"; + // número de linhas em branco entre propriedades public int $linesBetweenProperties = 0; + // número de linhas em branco entre métodos public int $linesBetweenMethods = 2; + // número de linhas em branco entre grupos de 'use statements' para classes, funções e constantes public int $linesBetweenUseTypes = 0; + // posição da chave de abertura para funções e métodos public bool $bracesOnNextLine = true; + // coloque um parâmetro por linha, mesmo que tenha um atributo ou seja promovido + public bool $singleParameterOnOneLine = false; + // omite namespaces que não contêm nenhuma classe ou função + public bool $omitEmptyNamespaces = true; + // separador entre o parêntese direito e o tipo de retorno de funções e métodos public string $returnTypeColon = ': '; } ``` +Como e por que o `Printer` padrão e o `PsrPrinter` diferem? Por que não há apenas um printer no pacote, o `PsrPrinter`? -Tipos .[#toc-types] -------------------- +O `Printer` padrão formata o código como fazemos em todo o Nette. Como o Nette surgiu muito antes do PSR, e também porque o PSR por muitos anos não entregou padrões em tempo hábil, mas talvez apenas com vários anos de atraso após a introdução de um novo recurso no PHP, aconteceu que o [padrão de codificação |contributing:coding-standard] difere em alguns pequenos detalhes. A maior diferença é apenas o uso de tabulações em vez de espaços. Sabemos que usar tabulações em nossos projetos permite o ajuste de largura, que é [essencial para pessoas com deficiência visual |contributing:coding-standard#Tabulações em Vez de Espaços]. Um exemplo de pequena diferença é a colocação da chave de abertura em uma linha separada para funções e métodos, e sempre. A recomendação do PSR nos parece ilógica e leva a uma [redução da clareza do código |contributing:coding-standard#Quebra de Linha e Chaves]. -Cada tipo ou tipo de união/intersecção pode ser passado como uma corda, você também pode usar constantes pré-definidas para tipos nativos: + +Tipos +----- + +Qualquer tipo ou tipo union/intersection pode ser passado como uma string, você também pode usar constantes predefinidas para tipos nativos: ```php use Nette\PhpGenerator\Type; -$member->setType('array'); // or Type::Array; -$member->setType('array|string'); // or Type::union('array', 'string') -$member->setType('Foo&Bar'); // or Type::intersection(Foo::class, Bar::class) -$member->setType(null); // removes type +$member->setType('array'); // ou Type::Array; +$member->setType('?array'); // ou Type::nullable(Type::Array); +$member->setType('array|string'); // ou Type::union(Type::Array, Type::String) +$member->setType('Foo&Bar'); // ou Type::intersection(Foo::class, Bar::class) +$member->setType(null); // remove o tipo ``` O mesmo se aplica ao método `setReturnType()`. -Literals .[#toc-literals] -------------------------- +Literais +-------- -Com `Literal` você pode passar código PHP arbitrário para, por exemplo, propriedade padrão ou valores de parâmetros, etc: +Usando `Literal`, você pode passar qualquer código PHP, por exemplo, para valores padrão de propriedades ou parâmetros, etc: ```php use Nette\PhpGenerator\Literal; @@ -545,25 +571,37 @@ class Demo } ``` -Você também pode passar os parâmetros para `Literal` e formatá-lo em código PHP válido usando [marcadores de lugar especiais |#method-and-function-body-generator]: +Você também pode passar parâmetros para `Literal` e formatá-los em código PHP válido usando [placeholders |#Corpos de métodos e funções]: ```php new Literal('substr(?, ?)', [$a, $b]); -// gera, por exemplo: substrato ("olá", 5); +// gera por exemplo: substr('hello', 5); ``` +Um literal representando a criação de um novo objeto pode ser facilmente gerado usando o método `new`: + +```php +Literal::new(Demo::class, [$a, 'foo' => $b]); +// gera por exemplo: new Demo(10, foo: 20) +``` -Atributos .[#toc-attributes] ----------------------------- -Você pode adicionar atributos PHP 8 a todas as classes, métodos, propriedades, constantes, casos de enumeração, funções, fechamentos e parâmetros. [Os literais |#Literals] também podem ser usados como valores de parâmetros. +Atributos +--------- + +Atributos do PHP 8 podem ser adicionados a todas as classes, métodos, propriedades, constantes, enums, funções, closures e parâmetros. Também é possível usar [#Literais] como valores de parâmetros. ```php $class = new Nette\PhpGenerator\ClassType('Demo'); -$class->addAttribute('Deprecated'); +$class->addAttribute('Table', [ + 'name' => 'user', + 'constraints' => [ + Literal::new('UniqueConstraint', ['name' => 'ean', 'columns' => ['ean']]), + ], +]); $class->addProperty('list') - ->addAttribute('WithArguments', [1, 2]); + ->addAttribute('Deprecated'); $method = $class->addMethod('count') ->addAttribute('Foo\Cached', ['mode' => true]); @@ -577,42 +615,126 @@ echo $class; Resultado: ```php -#[Deprecated] +#[Table(name: 'user', constraints: [new UniqueConstraint(name: 'ean', columns: ['ean'])])] class Demo { - #[WithArguments(1, 2)] + #[Deprecated] public $list; #[Foo\Cached(mode: true)] - public function count(#[Bar] $items) - { + public function count( + #[Bar] + $items, + ) { } } ``` -Namespace .[#toc-namespace] ---------------------------- +Property Hooks +-------------- + +Usando property hooks (representados pela classe [PropertyHook|api:Nette\PhpGenerator\PropertyHook]), você pode definir operações get e set para propriedades, um recurso introduzido no PHP 8.4: + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$prop = $class->addProperty('firstName') + ->setType('string'); + +$prop->addHook('set', 'strtolower($value)') + ->addParameter('value') + ->setType('string'); + +$prop->addHook('get') + ->setBody('return ucfirst($this->firstName);'); + +echo $class; +``` + +Gera: + +```php +class Demo +{ + public string $firstName { + set(string $value) => strtolower($value); + get { + return ucfirst($this->firstName); + } + } +} +``` + +Propriedades e property hooks podem ser abstratos ou finais: + +```php +$class->addProperty('id') + ->setType('int') + ->addHook('get') + ->setAbstract(); + +$class->addProperty('role') + ->setType('string') + ->addHook('set', 'strtolower($value)') + ->setFinal(); +``` + + +Visibilidade Assimétrica +------------------------ + +O PHP 8.4 introduz visibilidade assimétrica para propriedades. Você pode definir diferentes níveis de acesso para leitura e escrita. -Classes, traços, interfaces e enumeros (doravante classes) podem ser agrupados em namespaces ([PhpNamespace |api:Nette\PhpGenerator\PhpNamespace]): +A visibilidade pode ser definida usando o método `setVisibility()` com dois parâmetros, ou usando `setPublic()`, `setProtected()` ou `setPrivate()` com o parâmetro `mode`, que especifica se a visibilidade se aplica à leitura ou escrita da propriedade. O modo padrão é `'get'`. + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); + +$class->addProperty('name') + ->setType('string') + ->setVisibility('public', 'private'); // public para leitura, private para escrita + +$class->addProperty('id') + ->setType('int') + ->setProtected('set'); // protected para escrita + +echo $class; +``` + +Gera: + +```php +class Demo +{ + public private(set) string $name; + + protected(set) int $id; +} +``` + + +Namespace +--------- + +Classes, propriedades, interfaces e enums (doravante classes) podem ser agrupados em namespaces representados pela classe [PhpNamespace |api:Nette\PhpGenerator\PhpNamespace]: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); -// criar novas classes no namespace +// criamos novas classes no namespace $class = $namespace->addClass('Task'); $interface = $namespace->addInterface('Countable'); $trait = $namespace->addTrait('NameAware'); -// ou inserir uma classe existente no namespace +// ou inserimos uma classe existente no namespace $class = new Nette\PhpGenerator\ClassType('Task'); $namespace->add($class); ``` -Se a classe já existe, ela lança uma exceção. +Se a classe já existir, uma exceção será lançada. -Você pode definir as declarações de uso: +Você pode definir cláusulas use: ```php // use Http\Request; @@ -623,44 +745,42 @@ $namespace->addUse(Http\Request::class, 'HttpReq'); $namespace->addUseFunction('iter\range'); ``` -Para simplificar uma classe, função ou nome constante totalmente qualificado de acordo com os pseudônimos definidos, utilize o método `simplifyName`: +Para simplificar o nome totalmente qualificado de uma classe, função ou constante de acordo com os aliases definidos, use o método `simplifyName`: ```php echo $namespace->simplifyName('Foo\Bar'); // 'Bar', porque 'Foo' é o namespace atual -echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', por causa do uso-definido +echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', devido ao use-statement definido ``` -Por outro lado, você pode converter uma classe, função ou nome constante simplificado para uma classe totalmente qualificada usando o método `resolveName`: +Você pode converter o nome simplificado de uma classe, função ou constante para o nome totalmente qualificado usando o método `resolveName`: ```php -echo $namespace->resolveName('Bar'); // 'Foo\Bar'; // 'Foo\Bar'. +echo $namespace->resolveName('Bar'); // 'Foo\Bar' echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range' ``` -Resolução de Nomes de Classe .[#toc-class-names-resolving] ----------------------------------------------------------- +Resolução de nomes de classes +----------------------------- -** Quando a classe faz parte do namespace, ela é renderizada de forma ligeiramente diferente**: todos os tipos (ou seja, dicas de tipo, tipos de retorno, nome da classe pai, -interfaces implementadas, características e atributos utilizados) são automaticamente *resolvidos* (a menos que você o desligue, veja abaixo). -Isso significa que você tem que **utilizar nomes completos de classe** em definições e eles serão substituídos por apelidos (de acordo com as declarações de uso) ou nomes totalmente qualificados no código resultante: +**Quando uma classe faz parte de um namespace, ela é renderizada de forma ligeiramente diferente:** todos os tipos (por exemplo, typehints, tipos de retorno, nome da classe pai, interfaces implementadas, traits e atributos usados) são automaticamente *resolvidos* (a menos que você desative isso, veja abaixo). Isso significa que você deve **usar nomes de classe completos** nas definições e eles serão substituídos por aliases (de acordo com as cláusulas use) ou por nomes totalmente qualificados no código resultante: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); $namespace->addUse('Bar\AliasedClass'); $class = $namespace->addClass('Demo'); -$class->addImplement('Foo\A') // simplificará para A - ->addTrait('Bar\AliasedClass'); // simplificará para a AliasedClass +$class->addImplement('Foo\A') // será simplificado para A + ->addTrait('Bar\AliasedClass'); // será simplificado para AliasedClass $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // em comentários simplifique manualmente +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // em comentários, simplificamos manualmente $method->addParameter('arg') - ->setType('Bar\OtherClass'); // resolverá a barrar a outra classe + ->setType('Bar\OtherClass'); // será traduzido para \Bar\OtherClass echo $namespace; -// ou usar PsrPrinter para saída em conformidade com PSR-2 / PSR-12 +// ou use PsrPrinter para saída em conformidade com PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -684,7 +804,7 @@ class Demo implements A } ``` -A auto-resolução pode ser desligada desta maneira: +A resolução automática pode ser desativada desta forma: ```php $printer = new Nette\PhpGenerator\Printer; // ou PsrPrinter @@ -693,14 +813,14 @@ echo $printer->printNamespace($namespace); ``` -Arquivos PHP .[#toc-php-files] ------------------------------- +Arquivos PHP +------------ -As classes, funções e namespaces podem ser agrupadas em arquivos PHP representados pela classe [PhpFile |api:Nette\PhpGenerator\PhpFile]: +Classes, funções e namespaces podem ser agrupados em arquivos PHP representados pela classe [PhpFile|api:Nette\PhpGenerator\PhpFile]: ```php $file = new Nette\PhpGenerator\PhpFile; -$file->addComment('This file is auto-generated.'); +$file->addComment('Este arquivo é gerado automaticamente.'); $file->setStrictTypes(); // adiciona declare(strict_types=1) $class = $file->addClass('Foo\A'); @@ -713,7 +833,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// ou usar PsrPrinter para saída em conformidade com PSR-2 / PSR-12 +// ou use PsrPrinter para saída em conformidade com PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -723,7 +843,7 @@ Resultado: <?php /** - * This file is auto-generated. + * Este arquivo é gerado automaticamente. */ declare(strict_types=1); @@ -739,27 +859,28 @@ function foo() } ``` +**Aviso:** Não é possível adicionar nenhum outro código aos arquivos fora de funções e classes. -Gerando de acordo com os já existentes .[#toc-generating-according-to-existing-ones] ------------------------------------------------------------------------------------- -Além de poder modelar classes e funções usando a API descrita acima, você também pode tê-las geradas automaticamente usando as já existentes: +Geração baseada em existentes +----------------------------- + +Além de poder modelar classes e funções usando a API descrita acima, você também pode gerá-las automaticamente com base em padrões existentes: ```php -// cria uma classe idêntica à classe da DOP +// cria uma classe igual à classe PDO $class = Nette\PhpGenerator\ClassType::from(PDO::class); -// cria uma função idêntica à guarnição() +// cria uma função idêntica à função trim() $function = Nette\PhpGenerator\GlobalFunction::from('trim'); -// cria um fechamento como especificado +// cria uma closure com base na fornecida $closure = Nette\PhpGenerator\Closure::from( function (stdClass $a, $b = null) {}, ); ``` -A função e o método estão vazios por padrão. Se você quiser carregá-los também, use desta forma -(requer `nikic/php-parser` para ser instalado): +Os corpos das funções e métodos estão vazios por padrão. Se você também quiser carregá-los, use esta abordagem (requer a instalação do pacote `nikic/php-parser`): ```php $class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true); @@ -768,10 +889,10 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); ``` -Carregando do arquivo PHP .[#toc-loading-from-php-file] -------------------------------------------------------- +Carregando de arquivos PHP +-------------------------- -Você também pode carregar funções, classes, interfaces e enumeros diretamente de uma seqüência de código PHP. Por exemplo, criamos o objeto `ClassType` desta forma: +Você também pode carregar funções, classes, interfaces e enums diretamente de uma string contendo código PHP. Por exemplo, criamos um objeto `ClassType` assim: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -784,39 +905,69 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -Ao carregar classes do código PHP, os comentários de linha única fora dos corpos do método são ignorados (por exemplo, para propriedades, etc.) porque esta biblioteca não tem uma API para trabalhar com eles. +Ao carregar classes de código PHP, comentários de linha única fora dos corpos dos métodos são ignorados (por exemplo, em propriedades, etc.), pois esta biblioteca não possui uma API para trabalhar com eles. -Você também pode carregar o arquivo PHP inteiro diretamente, que pode conter qualquer número de classes, funções ou até mesmo vários espaços de nomes: +Você também pode carregar diretamente um arquivo PHP inteiro, que pode conter qualquer número de classes, funções ou até namespaces: ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` -O comentário inicial do arquivo e a declaração `strict_types` também são carregados. Por outro lado, todos os outros códigos globais são ignorados. +O comentário inicial do arquivo e a declaração `strict_types` também são carregados. Por outro lado, todo o outro código global é ignorado. -Isto requer que `nikic/php-parser` seja instalado. +É necessário que `nikic/php-parser` esteja instalado. .[note] -Se você precisar manipular o código global em arquivos ou declarações individuais em corpos de métodos, é melhor usar a biblioteca `nikic/php-parser` diretamente. +Se você precisar manipular código global em arquivos ou instruções individuais nos corpos dos métodos, é melhor usar diretamente a biblioteca `nikic/php-parser`. + + +Class Manipulator +----------------- + +A classe [ClassManipulator|api:Nette\PhpGenerator\ClassManipulator] fornece ferramentas para manipular classes. + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$manipulator = new Nette\PhpGenerator\ClassManipulator($class); +``` + +O método `inheritMethod()` copia um método da classe pai ou interface implementada para sua classe. Isso permite sobrescrever o método ou estender sua assinatura: + +```php +$method = $manipulator->inheritMethod('bar'); +$method->setBody('...'); +``` + +O método `inheritProperty()` copia uma propriedade da classe pai para sua classe. É útil quando você deseja ter a mesma propriedade em sua classe, mas talvez com um valor padrão diferente: + +```php +$property = $manipulator->inheritProperty('foo'); +$property->setValue('novo valor'); +``` + +O método `implement()` implementa automaticamente todos os métodos e propriedades da interface ou classe abstrata fornecida em sua classe: + +```php +$manipulator->implement(SomeInterface::class); +// Agora sua classe implementa SomeInterface e contém todos os seus métodos +``` -Variáveis Dumper .[#toc-variables-dumper] ------------------------------------------ +Exibição de variáveis +--------------------- -O Dumper retorna uma parábola de representação de uma variável em PHP. Fornece uma saída melhor e mais clara que a função nativa `var_export()`. +A classe `Dumper` converte uma variável em código PHP analisável. Ela fornece uma saída melhor e mais clara do que a função padrão `var_export()`. ```php $dumper = new Nette\PhpGenerator\Dumper; $var = ['a', 'b', 123]; -echo $dumper->dump($var); // gravuras ['a', 'b', 123] +echo $dumper->dump($var); // imprime ['a', 'b', 123] ``` -Tabela de Compatibilidade .[#toc-compatibility-table] ------------------------------------------------------ - -PhpGenerator 4.0 é compatível com PHP 8.0 a 8.2 +Tabela de compatibilidade +------------------------- -{{leftbar: nette:@menu-topics}} +PhpGenerator 4.1 é compatível com PHP 8.0 a 8.4. diff --git a/php-generator/pt/@meta.texy b/php-generator/pt/@meta.texy new file mode 100644 index 0000000000..e2566bcb44 --- /dev/null +++ b/php-generator/pt/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Documentação Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/php-generator/ro/@home.texy b/php-generator/ro/@home.texy index 94905efd5c..17bbc278c7 100644 --- a/php-generator/ro/@home.texy +++ b/php-generator/ro/@home.texy @@ -1,31 +1,32 @@ -Generator de coduri PHP -*********************** +Nette PhpGenerator +****************** -<div class=perex> -- Aveți nevoie să generați cod PHP pentru clase, funcții, fișiere PHP, etc.? -- Suportă toate cele mai recente caracteristici PHP, cum ar fi enums, atribute, etc. +<div class="perex"> +Căutați un instrument pentru generarea codului PHP al claselor, funcțiilor sau fișierelor complete? + +- Cunoaște toate cele mai recente caracteristici PHP (cum ar fi property hooks, enumuri, atribute etc.) - Vă permite să modificați cu ușurință clasele existente -- Ieșire conformă cu PSR-12 -- Bibliotecă extrem de matură, stabilă și utilizată pe scară largă +- Codul de ieșire este în conformitate cu stilul de codare PSR-12 / PER +- Bibliotecă matură, stabilă și utilizată pe scară largă </div> -Instalare .[#toc-installation] ------------------------------- +Instalare +--------- -Descărcați și instalați pachetul folosind [Composer |best-practices:composer]: +Descărcați și instalați biblioteca folosind [Composer|best-practices:composer]: ```shell composer require nette/php-generator ``` -Pentru compatibilitatea PHP, consultați [tabelul |#Compatibility Table]. +Compatibilitatea cu PHP o găsiți în [tabel |#Tabelul de compatibilitate]. -Clase .[#toc-classes] ---------------------- +Clase +----- -Să începem cu un exemplu simplu de generare a unei clase folosind [ClassType |api:Nette\PhpGenerator\ClassType]: +Să începem direct cu un exemplu de creare a unei clase folosind [ClassType |api:Nette\PhpGenerator\ClassType]: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -34,19 +35,19 @@ $class ->setFinal() ->setExtends(ParentClass::class) ->addImplement(Countable::class) - ->addComment("Description of class.\nSecond line\n") + ->addComment("Descrierea clasei.\nA doua linie\n") ->addComment('@property-read Nette\Forms\Form $form'); -// pentru a genera cod PHP, pur și simplu transformați în șir de caractere sau utilizați echo: +// generați codul simplu prin conversie la șir sau folosind echo: echo $class; ``` -Acesta va reda acest rezultat: +Returnează următorul rezultat: ```php /** - * Description of class. - * Second line + * Descrierea clasei + * A doua linie * * @property-read Nette\Forms\Form $form */ @@ -55,18 +56,18 @@ final class Demo extends ParentClass implements Countable } ``` -De asemenea, putem folosi o imprimantă pentru a genera codul, pe care, spre deosebire de `echo $class`, o vom putea [configura ulterior |#Printers and PSR compliance]: +Pentru a genera codul, putem folosi și așa-numitul printer, pe care, spre deosebire de `echo $class`, îl vom putea [configura ulterior |#Printer și conformitatea cu PSR]: ```php $printer = new Nette\PhpGenerator\Printer; echo $printer->printClass($class); ``` -Putem adăuga constante (clasa [Constant |api:Nette\PhpGenerator\Constant]) și proprietăți (clasa [Property |api:Nette\PhpGenerator\Property]): +Putem adăuga constante (clasa [Constant |api:Nette\PhpGenerator\Constant]) și variabile (clasa [Property |api:Nette\PhpGenerator\Property]): ```php $class->addConstant('ID', 123) - ->setProtected() // vizibilitate constantă + ->setProtected() // vizibilitatea constantelor ->setType('int') ->setFinal(); @@ -77,13 +78,13 @@ $class->addProperty('items', [1, 2, 3]) $class->addProperty('list') ->setType('?array') - ->setInitialized(); // tipărește '= null' + ->setInitialized(); // afișează '= null' ``` -Se generează: +Generează: ```php -final protected const const int ID = 123; +final protected const int ID = 123; /** @var int[] */ private static $items = [1, 2, 3]; @@ -91,14 +92,14 @@ private static $items = [1, 2, 3]; public ?array $list = null; ``` -Și putem adăuga [metode |#Method and Function Signature]: +Și putem adăuga [metode |#Semnături de metode și funcții]: ```php $method = $class->addMethod('count') - ->addComment('Count it.') + ->addComment('Numără-le.') ->setFinal() ->setProtected() - ->setReturnType('?int') // tipul de returnare a metodei + ->setReturnType('?int') // tipuri de returnare la metode ->setBody('return count($items ?: $this->items);'); $method->addParameter('items', []) // $items = [] @@ -106,11 +107,11 @@ $method->addParameter('items', []) // $items = [] ->setType('array'); // array &$items = [] ``` -Rezultă: +Rezultatul este: ```php /** - * Count it. + * Numără-le. */ final protected function count(array &$items = []): ?int { @@ -118,7 +119,7 @@ final protected function count(array &$items = []): ?int } ``` -Parametrii promovați introduși de PHP 8.0 pot fi trecuți la constructor: +Parametrii promovați introduși în PHP 8.0 pot fi transmiși constructorului: ```php $method = $class->addMethod('__construct'); @@ -127,7 +128,7 @@ $method->addPromotedParameter('args', []) ->setPrivate(); ``` -Aceasta are ca rezultat: +Rezultatul este: ```php public function __construct( @@ -137,15 +138,15 @@ public function __construct( } ``` -Proprietățile și clasele de citire exclusivă pot fi marcate prin intermediul `setReadOnly()`. +Proprietățile și clasele destinate doar citirii pot fi marcate folosind funcția `setReadOnly()`. ------ -În cazul în care proprietatea, constanta, metoda sau parametrul adăugat există deja, se aruncă o excepție. +Dacă proprietatea, constanta, metoda sau parametrul adăugat există deja, se aruncă o excepție. -Membrii pot fi eliminați utilizând `removeProperty()`, `removeConstant()`, `removeMethod()` sau `removeParameter()`. +Membrii clasei pot fi eliminați folosind `removeProperty()`, `removeConstant()`, `removeMethod()` sau `removeParameter()`. -De asemenea, se pot adăuga la clasă obiecte existente `Method`, `Property` sau `Constant`: +Puteți adăuga, de asemenea, obiecte `Method`, `Property` sau `Constant` existente în clasă: ```php $method = new Nette\PhpGenerator\Method('getHandle'); @@ -158,7 +159,7 @@ $class = (new Nette\PhpGenerator\ClassType('Demo')) ->addMember($const); ``` -Puteți clona metodele, proprietățile și constantele existente cu un nume diferit folosind `cloneWithName()`: +Puteți, de asemenea, clona metode, proprietăți și constante existente sub un alt nume folosind `cloneWithName()`: ```php $methodCount = $class->getMethod('count'); @@ -167,17 +168,17 @@ $class->addMember($methodRecount); ``` -Interfață sau trăsătură .[#toc-interface-or-trait] --------------------------------------------------- +Interfață sau trait +------------------- -Puteți crea interfețe și trăsături (clasele [InterfaceType |api:Nette\PhpGenerator\InterfaceType] și [TraitType |api:Nette\PhpGenerator\TraitType]): +Puteți crea interfețe și trait-uri (clasele [InterfaceType |api:Nette\PhpGenerator\InterfaceType] și [TraitType |api:Nette\PhpGenerator\TraitType]): ```php $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait'); ``` -Utilizarea trăsăturilor: +Utilizarea trait-urilor: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -202,10 +203,10 @@ class Demo ``` -Enums .[#toc-enums] -------------------- +Enumuri +------- -Puteți crea cu ușurință enumerațiile pe care le aduce PHP 8.1 (clasa [EnumType |api:Nette\PhpGenerator\EnumType]): +Enumurile, introduse în PHP 8.1, pot fi create cu ușurință astfel: (clasa [EnumType |api:Nette\PhpGenerator\EnumType]): ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -229,20 +230,20 @@ enum Suit } ``` -De asemenea, puteți defini echivalenți scalari pentru cazuri pentru a crea un enum susținut: +Puteți, de asemenea, defini echivalente scalare și crea astfel un enum "backed": ```php $enum->addCase('Clubs', '♣'); $enum->addCase('Diamonds', '♦'); ``` -Este posibil să se adauge un comentariu sau [atribute |#attributes] la fiecare caz folosind `addComment()` sau `addAttribute()`. +La fiecare *case* este posibil să adăugați un comentariu sau [#atribute] folosind `addComment()` sau `addAttribute()`. -Clasa Anonymous .[#toc-anonymous-class] ---------------------------------------- +Clase anonime +------------- -Dați `null` ca nume și veți avea o clasă anonimă: +Ca nume transmitem `null` și avem o clasă anonimă: ```php $class = new Nette\PhpGenerator\ClassType(null); @@ -264,10 +265,10 @@ $obj = new class ($val) { ``` -Funcția globală .[#toc-global-function] ---------------------------------------- +Funcții globale +--------------- -Codul de funcții va genera clasa [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction]: +Codul funcțiilor este generat de clasa [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction]: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -276,7 +277,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// sau utilizați PsrPrinter pentru o ieșire conformă cu PSR-2 / PSR-12 +// sau folosiți PsrPrinter pentru ieșire conformă cu PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -290,10 +291,10 @@ function foo($a, $b) ``` -Închidere .[#toc-closure] -------------------------- +Funcții anonime +--------------- -Codul de închidere va genera clasa [Closure |api:Nette\PhpGenerator\Closure]: +Codul funcțiilor anonime este generat de clasa [Closure |api:Nette\PhpGenerator\Closure]: ```php $closure = new Nette\PhpGenerator\Closure; @@ -304,7 +305,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// sau utilizați PsrPrinter pentru o ieșire conformă cu PSR-2 / PSR-12 +// sau folosiți PsrPrinter pentru ieșire conformă cu PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -317,10 +318,10 @@ function ($a, $b) use (&$c) { ``` -Funcția săgeată .[#toc-arrow-function] --------------------------------------- +Funcții săgeată prescurtate +--------------------------- -De asemenea, puteți imprima închiderea ca funcție săgeată folosind imprimanta: +Puteți, de asemenea, afișa o funcție anonimă prescurtată folosind printerul: ```php $closure = new Nette\PhpGenerator\Closure; @@ -338,38 +339,38 @@ fn($a, $b) => $a + $b ``` -Semnătura metodei și a funcției .[#toc-method-and-function-signature] ---------------------------------------------------------------------- +Semnături de metode și funcții +------------------------------ -Metodele sunt reprezentate de clasa [Method |api:Nette\PhpGenerator\Method]. Puteți seta vizibilitatea, valoarea de returnare, adăuga comentarii, [atribute |#Attributes] etc: +Metodele sunt reprezentate de clasa [Method |api:Nette\PhpGenerator\Method]. Puteți seta vizibilitatea, valoarea returnată, adăuga comentarii, [#atribute] etc: ```php $method = $class->addMethod('count') - ->addComment('Count it.') + ->addComment('Numără-le.') ->setFinal() ->setProtected() ->setReturnType('?int'); ``` -Fiecare parametru este reprezentat de o clasă [Parameter |api:Nette\PhpGenerator\Parameter]. Din nou, puteți seta toate proprietățile imaginabile: +Parametrii individuali sunt reprezentați de clasa [Parameter |api:Nette\PhpGenerator\Parameter]. Din nou, puteți seta toate proprietățile imaginabile: ```php $method->addParameter('items', []) // $items = [] - ->setReference() // &$items = [] - ->setType('array'); // array &$items = [] + ->setReference() // &$items = [] + ->setType('array'); // array &$items = [] // function count(&$items = []) ``` -Pentru a defini așa-numiții parametri variadici (sau, de asemenea, operatorul splat, spread, elipsis, unpacking sau trei puncte), utilizați `setVariadics()`: +Pentru definirea așa-numiților parametri variadici (sau operatorul splat) se folosește `setVariadic()`: ```php $method = $class->addMethod('count'); -$method->setVariadics(true); +$method->setVariadic(true); $method->addParameter('items'); ``` -Generates: +Generează: ```php function count(...$items) @@ -378,10 +379,10 @@ function count(...$items) ``` -Metoda și corpul funcției .[#toc-method-and-function-body] ----------------------------------------------------------- +Corpuri de metode și funcții +---------------------------- -Corpul poate fi transmis metodei `setBody()` o dată sau secvențial (linie cu linie) prin apelarea repetată a metodei `addBody()`: +Corpul poate fi transmis dintr-o dată metodei `setBody()` sau treptat (linie cu linie) prin apelarea repetată a `addBody()`: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -400,9 +401,9 @@ function foo() } ``` -Puteți utiliza caractere de poziție speciale pentru a injecta variabilele în mod practic. +Puteți folosi placeholder-uri speciale pentru inserarea ușoară a variabilelor. -Semne de poziție simple `?` +Placeholder-uri simple `?` ```php $str = 'any string'; @@ -412,7 +413,7 @@ $function->addBody('return substr(?, ?);', [$str, $num]); echo $function; ``` -Rezultat: +Rezultat ```php function foo() @@ -421,7 +422,7 @@ function foo() } ``` -Variadic placeholder `...?` +Placeholder pentru variadic `...?` ```php $items = [1, 2, 3]; @@ -439,7 +440,7 @@ function foo() } ``` -De asemenea, puteți utiliza parametrii numiți din PHP 8 folosind caractere de poziție `...?:` +Puteți folosi, de asemenea, parametri numiți pentru PHP 8 folosind `...?:` ```php $items = ['foo' => 1, 'bar' => true]; @@ -448,7 +449,7 @@ $function->setBody('myfunc(...?:);', [$items]); // myfunc(foo: 1, bar: true); ``` -Scăpați de locul rezervat utilizând slash `\?` +Placeholder-ul se escapează folosind slash `\?` ```php $num = 3; @@ -458,7 +459,7 @@ $function->addBody('return $a \? 10 : ?;', [$num]); echo $function; ``` -Rezultatul: +Rezultat: ```php function foo($a) @@ -468,45 +469,70 @@ function foo($a) ``` -Imprimantele și conformitatea PSR .[#toc-printers-and-psr-compliance] ---------------------------------------------------------------------- +Printer și conformitatea cu PSR +------------------------------- -Codul PHP este generat de obiectele `Printer`. Există un `PsrPrinter` a cărui ieșire este conformă cu PSR-2 și PSR-12 și care utilizează spații pentru indentare și un `Printer` care utilizează tabulatoare pentru indentare. +Pentru generarea codului PHP se folosește clasa [Printer |api:Nette\PhpGenerator\Printer]: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // la fel ca: echo $class +``` + +Poate genera codul tuturor celorlalte elemente, oferă metode precum `printFunction()`, `printNamespace()`, etc. + +Este disponibilă și clasa `PsrPrinter`, a cărei ieșire este conformă cu stilul de codare PSR-2 / PSR-12 / PER: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // 4 spații de indentare +echo $printer->printClass($class); ``` -Aveți nevoie să personalizați comportamentul imprimantei? Creați-vă propria imprimantă moștenind clasa `Printer`. Puteți reconfigura aceste variabile: +Aveți nevoie să ajustați comportamentul la comandă? Creați-vă propria versiune moștenind clasa `Printer`. Puteți reconfigura aceste variabile: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // lungimea liniei după care se va face împărțirea liniei public int $wrapLength = 120; + // caracterul de indentare, poate fi înlocuit cu o secvență de spații public string $indentation = "\t"; + // numărul de linii goale între proprietăți public int $linesBetweenProperties = 0; + // numărul de linii goale între metode public int $linesBetweenMethods = 2; + // numărul de linii goale între grupurile de 'use statements' pentru clase, funcții și constante public int $linesBetweenUseTypes = 0; + // poziția acoladei de deschidere pentru funcții și metode public bool $bracesOnNextLine = true; + // plasați un singur parametru pe o singură linie, chiar dacă are un atribut sau este suportat + public bool $singleParameterOnOneLine = false; + // omite spațiile de nume care nu conțin nicio clasă sau funcție + public bool $omitEmptyNamespaces = true; + // separatorul între paranteza dreaptă și tipul de returnare al funcțiilor și metodelor public string $returnTypeColon = ': '; } ``` +Cum și de ce diferă de fapt `Printer`-ul standard și `PsrPrinter`? De ce nu există un singur printer în pachet, și anume `PsrPrinter`? + +`Printer`-ul standard formatează codul așa cum o facem în întregul Nette. Deoarece Nette a apărut cu mult înainte de PSR și, de asemenea, pentru că PSR nu a livrat standarde la timp timp de mulți ani, ci poate chiar cu câțiva ani întârziere de la introducerea unei noi caracteristici în PHP, s-a întâmplat ca [standardul de codare |contributing:coding-standard] să difere în câteva mici detalii. Diferența mai mare este doar utilizarea tabulatorilor în loc de spații. Știm că utilizarea tabulatorilor în proiectele noastre permite ajustarea lățimii, care este [necesară pentru persoanele cu deficiențe de vedere |contributing:coding-standard#Tabulatori în loc de spații]. Un exemplu de mică diferență este plasarea acoladei pe o linie separată pentru funcții și metode, și întotdeauna. Recomandarea PSR ni se pare ilogică și duce la [reducerea lizibilității codului |contributing:coding-standard#Wrapping and Braces]. -Tipuri .[#toc-types] --------------------- -Fiecare tip sau tip de uniune/intersecție poate fi transmis ca un șir de caractere, puteți utiliza, de asemenea, constante predefinite pentru tipurile native: +Tipuri +------ + +Fiecare tip sau tip union/intersection poate fi transmis ca șir, puteți folosi și constante predefinite pentru tipuri native: ```php use Nette\PhpGenerator\Type; $member->setType('array'); // sau Type::Array; -$member->setType('array|string'); // sau Type::union('array', 'string') +$member->setType('?array'); // sau Type::nullable(Type::Array); +$member->setType('array|string'); // sau Type::union(Type::Array, Type::String) $member->setType('Foo&Bar'); // sau Type::intersection(Foo::class, Bar::class) $member->setType(null); // elimină tipul ``` @@ -514,10 +540,10 @@ $member->setType(null); // elimină tipul Același lucru este valabil și pentru metoda `setReturnType()`. -Literali .[#toc-literals] -------------------------- +Literali +-------- -Cu `Literal` puteți transmite cod PHP arbitrar, de exemplu, pentru valori implicite ale proprietăților sau parametrilor etc: +Folosind `Literal` puteți transmite orice cod PHP, de exemplu pentru valorile implicite ale proprietăților sau parametrilor etc: ```php use Nette\PhpGenerator\Literal; @@ -545,25 +571,37 @@ class Demo } ``` -Puteți, de asemenea, să transmiteți parametri la `Literal` și să formatați codul PHP valid folosind [caractere de poziție speciale |#method-and-function-body-generator]: +Puteți, de asemenea, transmite parametri la `Literal` și să îi formatați în cod PHP valid folosind [placeholder-uri |#Corpuri de metode și funcții]: ```php new Literal('substr(?, ?)', [$a, $b]); -// generează, de exemplu: substr('hello', 5); +// generează de exemplu: substr('hello', 5); ``` +Literalul reprezentând crearea unui nou obiect poate fi generat cu ușurință folosind metoda `new`: -Atribute .[#toc-attributes] ---------------------------- +```php +Literal::new(Demo::class, [$a, 'foo' => $b]); +// generează de exemplu: new Demo(10, foo: 20) +``` -Puteți adăuga atribute PHP 8 la toate clasele, metodele, proprietățile, constantele, cazurile enum, funcțiile, închiderile și parametrii. [Literalii |#Literals] pot fi, de asemenea, utilizați ca valori de parametru. + +Atribute +-------- + +Atributele PHP 8 pot fi adăugate la toate clasele, metodele, proprietățile, constantele, enumurile, funcțiile, closure-urile și parametrii. Ca valori ale parametrilor pot fi utilizate și [#literali]. ```php $class = new Nette\PhpGenerator\ClassType('Demo'); -$class->addAttribute('Deprecated'); +$class->addAttribute('Table', [ + 'name' => 'user', + 'constraints' => [ + Literal::new('UniqueConstraint', ['name' => 'ean', 'columns' => ['ean']]), + ], +]); $class->addProperty('list') - ->addAttribute('WithArguments', [1, 2]); + ->addAttribute('Deprecated'); $method = $class->addMethod('count') ->addAttribute('Foo\Cached', ['mode' => true]); @@ -577,42 +615,126 @@ echo $class; Rezultat: ```php -#[Deprecated] +#[Table(name: 'user', constraints: [new UniqueConstraint(name: 'ean', columns: ['ean'])])] class Demo { - #[WithArguments(1, 2)] + #[Deprecated] public $list; #[Foo\Cached(mode: true)] - public function count(#[Bar] $items) - { + public function count( + #[Bar] + $items, + ) { } } ``` -Spațiul de nume .[#toc-namespace] ---------------------------------- +Property Hooks +-------------- + +Folosind property hooks (reprezentate de clasa [PropertyHook|api:Nette\PhpGenerator\PropertyHook]) puteți defini operațiile get și set pentru proprietăți, o funcție introdusă în PHP 8.4: + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$prop = $class->addProperty('firstName') + ->setType('string'); + +$prop->addHook('set', 'strtolower($value)') + ->addParameter('value') + ->setType('string'); + +$prop->addHook('get') + ->setBody('return ucfirst($this->firstName);'); + +echo $class; +``` + +Generează: + +```php +class Demo +{ + public string $firstName { + set(string $value) => strtolower($value); + get { + return ucfirst($this->firstName); + } + } +} +``` + +Proprietățile și property hooks pot fi abstracte sau finale: + +```php +$class->addProperty('id') + ->setType('int') + ->addHook('get') + ->setAbstract(); + +$class->addProperty('role') + ->setType('string') + ->addHook('set', 'strtolower($value)') + ->setFinal(); +``` + + +Vizibilitate asimetrică +----------------------- + +PHP 8.4 introduce vizibilitatea asimetrică pentru proprietăți. Puteți seta diferite niveluri de acces pentru citire și scriere. + +Vizibilitatea poate fi setată fie folosind metoda `setVisibility()` cu doi parametri, fie folosind `setPublic()`, `setProtected()` sau `setPrivate()` cu parametrul `mode`, care specifică dacă vizibilitatea se aplică citirii sau scrierii proprietății. Modul implicit este `'get'`. + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); + +$class->addProperty('name') + ->setType('string') + ->setVisibility('public', 'private'); // public pentru citire, private pentru scriere + +$class->addProperty('id') + ->setType('int') + ->setProtected('set'); // protected pentru scriere + +echo $class; +``` + +Generează: + +```php +class Demo +{ + public private(set) string $name; + + protected(set) int $id; +} +``` + -Clasele, trăsăturile, interfețele și enumerațiile (denumite în continuare clase) pot fi grupate în spații de nume ([PhpNamespace |api:Nette\PhpGenerator\PhpNamespace]): +Spațiu de nume +-------------- + +Clasele, proprietățile, interfețele și enumurile (denumite în continuare clase) pot fi grupate în spații de nume reprezentate de clasa [PhpNamespace |api:Nette\PhpGenerator\PhpNamespace]: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); -// crearea de noi clase în spațiul de nume +// creăm noi clase în namespace $class = $namespace->addClass('Task'); $interface = $namespace->addInterface('Countable'); $trait = $namespace->addTrait('NameAware'); -// sau inserați o clasă existentă în spațiul de nume +// sau inserăm o clasă existentă în namespace $class = new Nette\PhpGenerator\ClassType('Task'); $namespace->add($class); ``` -În cazul în care clasa există deja, se aruncă o excepție. +Dacă clasa există deja, se aruncă o excepție. -Puteți defini declarații de utilizare: +Puteți defini clauze use: ```php // use Http\Request; @@ -623,14 +745,14 @@ $namespace->addUse(Http\Request::class, 'HttpReq'); $namespace->addUseFunction('iter\range'); ``` -Pentru a simplifica un nume de clasă, funcție sau constantă complet calificat în conformitate cu aliasurile definite, utilizați metoda `simplifyName`: +Pentru a simplifica numele complet calificat al unei clase, funcții sau constante conform aliasurilor definite, utilizați metoda `simplifyName`: ```php echo $namespace->simplifyName('Foo\Bar'); // 'Bar', deoarece 'Foo' este spațiul de nume curent -echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // "range", din cauza declarației de utilizare definite +echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', datorită use-statement-ului definit ``` -În schimb, puteți converti un nume simplificat de clasă, funcție sau constantă într-un nume complet calificat utilizând metoda `resolveName`: +Numele simplificat al unei clase, funcții sau constante poate fi, dimpotrivă, convertit în numele complet calificat folosind metoda `resolveName`: ```php echo $namespace->resolveName('Bar'); // 'Foo\Bar' @@ -638,29 +760,27 @@ echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range' ``` -Rezolvarea numelor de clase .[#toc-class-names-resolving] ---------------------------------------------------------- +Traduceri de nume de clase +-------------------------- -**Când clasa face parte din spațiul de nume, este redată puțin diferit**: toate tipurile (adică indicii de tip, tipuri de returnare, numele clasei părinte, -interfețele implementate, trăsăturile și atributele utilizate) sunt automat *rezolvate* (cu excepția cazului în care dezactivați această funcție, a se vedea mai jos). -Aceasta înseamnă că trebuie să **utilizați nume de clasă complete** în definiții și că acestea vor fi înlocuite cu alias-uri (în conformitate cu declarațiile de utilizare) sau cu nume complet calificate în codul rezultat: +**Când o clasă face parte dintr-un spațiu de nume, este redată ușor diferit:** toate tipurile (de exemplu, typehint-uri, tipuri de returnare, numele clasei părinte, interfețele implementate, proprietățile și atributele utilizate) sunt automat *traduse* (dacă nu dezactivați acest lucru, vezi mai jos). Aceasta înseamnă că trebuie să **utilizați nume complete de clase** în definiții și acestea vor fi înlocuite cu aliasuri (conform clauzelor use) sau cu nume complet calificate în codul rezultat: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); $namespace->addUse('Bar\AliasedClass'); $class = $namespace->addClass('Demo'); -$class->addImplement('Foo\A') // se va simplifica la A - ->addTrait('Bar\AliasedClass'); // se va simplifica în AliasedClass +$class->addImplement('Foo\A') // va fi simplificat la A + ->addTrait('Bar\AliasedClass'); // va fi simplificat la AliasedClass $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // în comentarii simplificați manual +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // în comentarii simplificăm manual $method->addParameter('arg') - ->setType('Bar\OtherClass'); // se va rezolva în \Bar\OtherClass + ->setType('Bar\OtherClass'); // va fi tradus la \Bar\OtherClass echo $namespace; -// sau utilizați PsrPrinter pentru o ieșire conformă cu PSR-2 / PSR-12 +// sau folosiți PsrPrinter pentru ieșire conformă cu PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -684,7 +804,7 @@ class Demo implements A } ``` -Rezolvarea automată poate fi dezactivată în acest mod: +Traducerea automată poate fi dezactivată în acest mod: ```php $printer = new Nette\PhpGenerator\Printer; // sau PsrPrinter @@ -693,14 +813,14 @@ echo $printer->printNamespace($namespace); ``` -Fișiere PHP .[#toc-php-files] ------------------------------ +Fișiere PHP +----------- -Clasele, funcțiile și spațiile de nume pot fi grupate în fișiere PHP reprezentate de clasa [PhpFile |api:Nette\PhpGenerator\PhpFile]: +Clasele, funcțiile și spațiile de nume pot fi grupate în fișiere PHP reprezentate de clasa [PhpFile|api:Nette\PhpGenerator\PhpFile]: ```php $file = new Nette\PhpGenerator\PhpFile; -$file->addComment('This file is auto-generated.'); +$file->addComment('Acest fișier este generat automat.'); $file->setStrictTypes(); // adaugă declare(strict_types=1) $class = $file->addClass('Foo\A'); @@ -713,7 +833,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// sau utilizați PsrPrinter pentru o ieșire conformă cu PSR-2 / PSR-12 +// sau folosiți PsrPrinter pentru ieșire conformă cu PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -723,7 +843,7 @@ Rezultat: <?php /** - * This file is auto-generated. + * Acest fișier este generat automat. */ declare(strict_types=1); @@ -739,27 +859,28 @@ function foo() } ``` +**Atenție:** Nu este posibil să adăugați niciun alt cod în fișiere în afara funcțiilor și claselor. + -Generarea în funcție de cele existente .[#toc-generating-according-to-existing-ones] ------------------------------------------------------------------------------------- +Generare conform celor existente +-------------------------------- -Pe lângă posibilitatea de a modela clase și funcții utilizând API-ul descris mai sus, puteți, de asemenea, să le generați automat în funcție de cele existente: +Pe lângă faptul că puteți modela clase și funcții folosind API-ul descris mai sus, le puteți lăsa să fie generate automat conform modelelor existente: ```php // creează o clasă identică cu clasa PDO $class = Nette\PhpGenerator\ClassType::from(PDO::class); -// creează o funcție identică cu trim() +// creează o funcție identică cu funcția trim() $function = Nette\PhpGenerator\GlobalFunction::from('trim'); -// creează o închidere așa cum este specificat +// creează o closure conform celei indicate $closure = Nette\PhpGenerator\Closure::from( function (stdClass $a, $b = null) {}, ); ``` -Corpurile funcțiilor și metodelor sunt goale în mod implicit. Dacă doriți să le încărcați și pe acestea, utilizați această modalitate -(necesită instalarea `nikic/php-parser` ): +Corpurile funcțiilor și metodelor sunt goale în mod implicit. Dacă doriți să le încărcați și pe acestea, utilizați acest mod (necesită instalarea pachetului `nikic/php-parser`): ```php $class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true); @@ -768,10 +889,10 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); ``` -Încărcare din fișierul PHP .[#toc-loading-from-php-file] --------------------------------------------------------- +Încărcare din fișiere PHP +------------------------- -De asemenea, puteți încărca funcții, clase, interfețe și enume direct dintr-un șir de cod PHP. De exemplu, creăm obiectul `ClassType` în acest mod: +Funcțiile, clasele, interfețele și enumurile pot fi încărcate și direct dintr-un șir care conține cod PHP. De exemplu, astfel creăm un obiect `ClassType`: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -784,39 +905,69 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -La încărcarea claselor din codul PHP, comentariile pe o singură linie din afara corpului metodelor sunt ignorate (de exemplu, pentru proprietăți etc.), deoarece această bibliotecă nu dispune de un API pentru a lucra cu acestea. +La încărcarea claselor din cod PHP, comentariile pe o singură linie din afara corpurilor metodelor sunt ignorate (de exemplu, la proprietăți etc.), deoarece această bibliotecă nu are API pentru lucrul cu ele. -De asemenea, puteți încărca direct întregul fișier PHP, care poate conține orice număr de clase, funcții sau chiar mai multe spații de nume: +Puteți, de asemenea, încărca direct un întreg fișier PHP, care poate conține orice număr de clase, funcții sau chiar spații de nume: ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` -Se încarcă, de asemenea, comentariul inițial al fișierului și declarația `strict_types`. Pe de altă parte, toate celelalte coduri globale sunt ignorate. +Se încarcă și comentariul introductiv al fișierului și declarația `strict_types`. În schimb, tot restul codului global este ignorat. -Acest lucru necesită instalarea `nikic/php-parser`. +Este necesar să fie instalat `nikic/php-parser`. .[note] -Dacă aveți nevoie să manipulați codul global din fișiere sau declarațiile individuale din corpurile metodelor, este mai bine să utilizați direct biblioteca `nikic/php-parser`. +Dacă aveți nevoie să manipulați codul global în fișiere sau instrucțiunile individuale în corpurile metodelor, este mai bine să utilizați direct biblioteca `nikic/php-parser`. + + +Class Manipulator +----------------- + +Clasa [ClassManipulator|api:Nette\PhpGenerator\ClassManipulator] oferă instrumente pentru manipularea claselor. + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$manipulator = new Nette\PhpGenerator\ClassManipulator($class); +``` + +Metoda `inheritMethod()` copiază o metodă din clasa părinte sau interfața implementată în clasa dvs. Acest lucru vă permite să suprascrieți metoda sau să extindeți semnătura sa: + +```php +$method = $manipulator->inheritMethod('bar'); +$method->setBody('...'); +``` + +Metoda `inheritProperty()` copiază o proprietate din clasa părinte în clasa dvs. Este util atunci când doriți să aveți aceeași proprietate în clasa dvs., dar poate cu o valoare implicită diferită: + +```php +$property = $manipulator->inheritProperty('foo'); +$property->setValue('new value'); +``` +Metoda `implement()` implementează automat toate metodele și proprietățile din interfața dată sau clasa abstractă în clasa dvs.: + +```php +$manipulator->implement(SomeInterface::class); +// Acum clasa dvs. implementează SomeInterface și conține toate metodele sale +``` -Descărcător de variabile .[#toc-variables-dumper] -------------------------------------------------- -Dumper returnează o reprezentare de tip șir de caractere PHP analizabilă a unei variabile. Oferă o ieșire mai bună și mai clară decât funcția nativă `var_export()`. +Afișarea variabilelor +--------------------- + +Clasa `Dumper` convertește o variabilă în cod PHP parsabil. Oferă o ieșire mai bună și mai clară decât funcția standard `var_export()`. ```php $dumper = new Nette\PhpGenerator\Dumper; $var = ['a', 'b', 123]; -echo $dumper->dump($var); // prints ['a', 'b', 123] +echo $dumper->dump($var); // afișează ['a', 'b', 123] ``` -Tabel de compatibilitate .[#toc-compatibility-table] ----------------------------------------------------- - -PhpGenerator 4.0 este compatibil cu PHP 8.0 până la 8.2 +Tabelul de compatibilitate +-------------------------- -{{leftbar: nette:@menu-topics}} +PhpGenerator 4.1 este compatibil cu PHP 8.0 până la 8.4. diff --git a/php-generator/ro/@meta.texy b/php-generator/ro/@meta.texy new file mode 100644 index 0000000000..6554692600 --- /dev/null +++ b/php-generator/ro/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Documentație Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/php-generator/ru/@home.texy b/php-generator/ru/@home.texy index dc199d6068..0143095296 100644 --- a/php-generator/ru/@home.texy +++ b/php-generator/ru/@home.texy @@ -1,31 +1,32 @@ -Генератор кода PHP +Nette PhpGenerator ****************** -<div class=perex> -- Вам нужно сгенерировать PHP-код для классов, функций, PHP-файлов и т.д.? -- Поддерживает все новейшие возможности PHP, такие как перечисления, атрибуты и т.д. +<div class="perex"> +Ищете инструмент для генерации PHP-кода классов, функций или целых файлов? + +- Поддерживает все последние возможности PHP (такие как property hooks, enums, атрибуты и т.д.) - Позволяет легко модифицировать существующие классы -- Выходные данные соответствуют стандарту PSR-12 -- Высокоразвитая, стабильная и широко используемая библиотека +- Выходной код соответствует стилю кодирования PSR-12 / PER +- Зрелая, стабильная и широко используемая библиотека </div> -Установка .[#toc-installation] ------------------------------- +Установка +--------- -Загрузите и установите пакет с помощью [Composer |best-practices:composer]: +Вы можете скачать и установить библиотеку с помощью [Composer|best-practices:composer]: ```shell composer require nette/php-generator ``` -Совместимость с PHP см. в [таблице |#Compatibility Table]. +Совместимость с PHP можно найти в [таблице |#Таблица совместимости]. -Классы .[#toc-classes] ----------------------- +Классы +------ -Начнём с простого примера генерации класса с использованием [ClassType |api:Nette\PhpGenerator\ClassType]: +Начнем сразу с примера создания класса с помощью [ClassType |api:Nette\PhpGenerator\ClassType]: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -34,19 +35,19 @@ $class ->setFinal() ->setExtends(ParentClass::class) ->addImplement(Countable::class) - ->addComment("Description of class.\nSecond line\n") + ->addComment("Описание класса.\nВторая строка\n") ->addComment('@property-read Nette\Forms\Form $form'); -// для генерации PHP-кода просто приведите к строке или используйте echo: +// код легко сгенерировать приведением к строке или использованием echo: echo $class; ``` -Это приведет к следующему результату: +Возвращает следующий результат: ```php /** - * Description of class. - * Second line + * Описание класса + * Вторая строка * * @property-read Nette\Forms\Form $form */ @@ -55,18 +56,18 @@ final class Demo extends ParentClass implements Countable } ``` -Мы также можем использовать класс `Printer` для генерации кода, который, в отличие от `echo $class`, мы сможем [дополнительно настроить |#Printers-and-PSR-Compliance]: +Для генерации кода мы также можем использовать так называемый printer, который, в отличие от `echo $class`, мы сможем [далее конфигурировать |#Printer и соответствие PSR]: ```php $printer = new Nette\PhpGenerator\Printer; echo $printer->printClass($class); ``` -Можно добавлять константы ([Constant |api:Nette\PhpGenerator\Constant]) и свойства ([Property |api:Nette\PhpGenerator\Property]): +Мы можем добавить константы (класс [Constant |api:Nette\PhpGenerator\Constant]) и свойства (класс [Property |api:Nette\PhpGenerator\Property]): ```php $class->addConstant('ID', 123) - ->setProtected() // постоянная видимость + ->setProtected() // видимость констант ->setType('int') ->setFinal(); @@ -77,10 +78,10 @@ $class->addProperty('items', [1, 2, 3]) $class->addProperty('list') ->setType('?array') - ->setInitialized(); // печатает '= null' + ->setInitialized(); // выведет '= null' ``` -Он генерирует: +Сгенерирует: ```php final protected const int ID = 123; @@ -91,14 +92,14 @@ private static $items = [1, 2, 3]; public ?array $list = null; ``` -И мы можем добавить [методы |#Method and Function Signature]: +И мы можем добавить [методы |#Сигнатуры методов и функций]: ```php $method = $class->addMethod('count') - ->addComment('Count it.') + ->addComment('Подсчитать.') ->setFinal() ->setProtected() - ->setReturnType('?int') // тип возврата метода + ->setReturnType('?int') // возвращаемые типы у методов ->setBody('return count($items ?: $this->items);'); $method->addParameter('items', []) // $items = [] @@ -106,11 +107,11 @@ $method->addParameter('items', []) // $items = [] ->setType('array'); // array &$items = [] ``` -Это приводит к: +Результат: ```php /** - * Count it. + * Подсчитать. */ final protected function count(array &$items = []): ?int { @@ -118,7 +119,7 @@ final protected function count(array &$items = []): ?int } ``` -Продвигаемые параметры, введенные в PHP 8.0, могут быть переданы в конструктор: +Продвигаемые параметры, введенные в PHP 8.0, можно передать конструктору: ```php $method = $class->addMethod('__construct'); @@ -127,7 +128,7 @@ $method->addPromotedParameter('args', []) ->setPrivate(); ``` -Это приводит к: +Результат: ```php public function __construct( @@ -137,15 +138,15 @@ public function __construct( } ``` -Свойства и классы, доступные для чтения, могут быть помечены с помощью `setReadOnly()`. +Свойства и классы, предназначенные только для чтения, можно отметить с помощью функции `setReadOnly()`. ------ -Если добавляемое свойство, константа, метод или параметр уже существуют, это вызовет исключение. +Если добавляемое свойство, константа, метод или параметр уже существуют, будет выброшено исключение. -Члены можно удалить с помощью `removeProperty()`, `removeConstant()`, `removeMethod()` или `removeParameter()`. +Члены класса можно удалить с помощью `removeProperty()`, `removeConstant()`, `removeMethod()` или `removeParameter()`. -Вы также можете добавить существующие объекты `Method`, `Property` или `Constant` в класс: +В класс можно также добавить существующие объекты `Method`, `Property` или `Constant`: ```php $method = new Nette\PhpGenerator\Method('getHandle'); @@ -158,7 +159,7 @@ $class = (new Nette\PhpGenerator\ClassType('Demo')) ->addMember($const); ``` -Вы можете клонировать существующие методы, свойства и константы с другим именем, используя `cloneWithName()`: +Вы также можете клонировать существующие методы, свойства и константы под другим именем с помощью `cloneWithName()`: ```php $methodCount = $class->getMethod('count'); @@ -167,8 +168,8 @@ $class->addMember($methodRecount); ``` -Интерфейс или трейты .[#toc-interface-or-trait] ------------------------------------------------ +Интерфейс или трейт +------------------- Вы можете создавать интерфейсы и трейты (классы [InterfaceType |api:Nette\PhpGenerator\InterfaceType] и [TraitType |api:Nette\PhpGenerator\TraitType]): @@ -177,7 +178,7 @@ $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait'); ``` -Использование признаков: +Использование трейтов: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -202,10 +203,10 @@ class Demo ``` -Энумы .[#toc-enums] -------------------- +Перечисления (Enums) +-------------------- -Вы можете легко создавать перечисления, которые появились в PHP 8.1 (класс [EnumType |api:Nette\PhpGenerator\EnumType]): +Перечисления, которые появились в PHP 8.1, можно легко создать так: (класс [EnumType |api:Nette\PhpGenerator\EnumType]): ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -229,20 +230,20 @@ enum Suit } ``` -Вы также можете определить скалярные эквиваленты для случаев, чтобы создать подкрепленное перечисление: +Вы также можете определить скалярные эквиваленты и создать так называемый "backed" enum (поддерживаемое перечисление): ```php $enum->addCase('Clubs', '♣'); $enum->addCase('Diamonds', '♦'); ``` -Можно добавить комментарий или [атрибуты |#attributes] к каждому случаю, используя `addComment()` или `addAttribute()`. +К каждому *case* можно добавить комментарий или [##атрибуты] с помощью `addComment()` или `addAttribute()`. -Анонимные классы .[#toc-anonymous-class] ----------------------------------------- +Анонимные классы +---------------- -Передайте `null` в качестве имени, и у вас будет анонимный класс: +Передадим `null` в качестве имени, и у нас будет анонимный класс: ```php $class = new Nette\PhpGenerator\ClassType(null); @@ -264,10 +265,10 @@ $obj = new class ($val) { ``` -Глобальные функции .[#toc-global-function] ------------------------------------------- +Глобальные функции +------------------ -За генерацию кода обычных функций отвечает класс [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction]: +Код функций генерирует класс [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction]: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -276,7 +277,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// или использовать PsrPrinter для вывода, совместимого с PSR-2 / PSR-12 +// или используйте PsrPrinter для вывода в соответствии с PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -290,10 +291,10 @@ function foo($a, $b) ``` -Замыкания .[#toc-closure] -------------------------- +Анонимные функции +----------------- -Класс [Closure |api:Nette\PhpGenerator\Closure] поможет вам сгенерировать код замыканий: +Код анонимных функций генерирует класс [Closure |api:Nette\PhpGenerator\Closure]: ```php $closure = new Nette\PhpGenerator\Closure; @@ -304,7 +305,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// или использовать PsrPrinter для вывода, совместимого с PSR-2 / PSR-12 +// или используйте PsrPrinter для вывода в соответствии с PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -317,10 +318,10 @@ function ($a, $b) use (&$c) { ``` -Стрелочные функции .[#toc-arrow-function] ------------------------------------------ +Короткие стрелочные функции +--------------------------- -Вы также можете генерировать стрелочные функции: +Вы также можете вывести короткую анонимную функцию с помощью printer: ```php $closure = new Nette\PhpGenerator\Closure; @@ -338,38 +339,38 @@ fn($a, $b) => $a + $b ``` -Подпись метода и функции .[#toc-method-and-function-signature] --------------------------------------------------------------- +Сигнатуры методов и функций +--------------------------- -Методы представлены классом [Method |api:Nette\PhpGenerator\Method]. Вы можете установить видимость, возвращаемое значение, добавить комментарии, [атрибуты |#Attributes] и т.д: +Методы представляет класс [Method |api:Nette\PhpGenerator\Method]. Вы можете установить видимость, возвращаемое значение, добавить комментарии, [#атрибуты] и т.д.: ```php $method = $class->addMethod('count') - ->addComment('Count it.') + ->addComment('Подсчитать.') ->setFinal() ->setProtected() ->setReturnType('?int'); ``` -Каждый параметр представлен классом [Parameter |api:Nette\PhpGenerator\Parameter]. Опять же, вы можете установить все возможные свойства: +Отдельные параметры представляет класс [Parameter |api:Nette\PhpGenerator\Parameter]. Опять же, вы можете установить все мыслимые свойства: ```php $method->addParameter('items', []) // $items = [] - ->setReference() // &$items = [] - ->setType('array'); // array &$items = [] + ->setReference() // &$items = [] + ->setType('array'); // array &$items = [] -// function count(&$items = []) +// function count(array &$items = []) ``` -Для определения так называемых вариативных параметров (а также операторов splat, spread, ellipsis, unpacking или three dots) используйте `setVariadics()`: +Для определения так называемых variadic параметров (или splat оператора) служит `setVariadic()`: ```php $method = $class->addMethod('count'); -$method->setVariadics(true); +$method->setVariadic(true); $method->addParameter('items'); ``` -Generates: +Сгенерирует: ```php function count(...$items) @@ -378,10 +379,10 @@ function count(...$items) ``` -Метод и тело функции .[#toc-method-and-function-body] ------------------------------------------------------ +Тела методов и функций +---------------------- -Содержимое функции или метода может быть передано методу `setBody()` сразу или последовательно (строка за строкой) путем многократного вызова `addBody()`: +Тело можно передать сразу методу `setBody()` или постепенно (по строкам) повторным вызовом `addBody()`: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -390,7 +391,7 @@ $function->addBody('return $a;'); echo $function; ``` -Результат: +Результат ```php function foo() @@ -400,9 +401,9 @@ function foo() } ``` -Для удобного внедрения переменных можно использовать специальные заполнители. +Вы можете использовать специальные плейсхолдеры для легкой вставки переменных. -Простой заполнитель `?` +Простые плейсхолдеры `?` ```php $str = 'any string'; @@ -412,7 +413,7 @@ $function->addBody('return substr(?, ?);', [$str, $num]); echo $function; ``` -Результат: +Результат ```php function foo() @@ -421,7 +422,7 @@ function foo() } ``` -Вариативный заполнитель `...?` +Плейсхолдер для variadic `...?` ```php $items = [1, 2, 3]; @@ -439,7 +440,7 @@ function foo() } ``` -Вы также можете использовать именованные параметры PHP 8 с помощью заполнителя `...?:` +Вы также можете использовать именованные параметры для PHP 8 с помощью `...?:` ```php $items = ['foo' => 1, 'bar' => true]; @@ -448,7 +449,7 @@ $function->setBody('myfunc(...?:);', [$items]); // myfunc(foo: 1, bar: true); ``` -Экранируйте заполнители с помощью косой черты: `\?` +Плейсхолдер экранируется с помощью косой черты `\?` ```php $num = 3; @@ -468,56 +469,81 @@ function foo($a) ``` -Принтеры и соответствие ПСР .[#toc-printers-and-psr-compliance] ---------------------------------------------------------------- +Printer и соответствие PSR +-------------------------- -Код PHP генерируется объектами `Printer`. Существует `PsrPrinter`, чей вывод соответствует PSR-2 и PSR-12 и использует пробелы для отступов, и `Printer`, который использует табуляцию для отступов. +Для генерации PHP-кода служит класс [Printer |api:Nette\PhpGenerator\Printer]: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // то же самое, что: echo $class +``` + +Он умеет генерировать код всех других элементов, предлагает методы, такие как `printFunction()`, `printNamespace()` и т.д. + +Доступен также класс `PsrPrinter`, вывод которого соответствует стилю кодирования PSR-2 / PSR-12 / PER: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // отступ 4 пробела +echo $printer->printClass($class); ``` -Нужно настроить поведение принтера? Создайте свой собственный, унаследовав класс `Printer`. Вы можете изменить конфигурацию этих переменных: +Нужно настроить поведение под себя? Создайте собственную версию, унаследовав класс `Printer`. Можно переконфигурировать следующие переменные: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // длина строки, после которой происходит перенос строки public int $wrapLength = 120; + // символ отступа, может быть заменен последовательностью пробелов public string $indentation = "\t"; + // количество пустых строк между свойствами public int $linesBetweenProperties = 0; + // количество пустых строк между методами public int $linesBetweenMethods = 2; + // количество пустых строк между группами 'use statements' для классов, функций и констант public int $linesBetweenUseTypes = 0; + // позиция открывающей фигурной скобки для функций и методов public bool $bracesOnNextLine = true; + // размещать один параметр на одной строке, даже если у него есть атрибут или он продвигаемый + public bool $singleParameterOnOneLine = false; + // пропускает пространства имен, которые не содержат ни одного класса или функции + public bool $omitEmptyNamespaces = true; + // разделитель между закрывающей скобкой и возвращаемым типом функций и методов public string $returnTypeColon = ': '; } ``` +Как и почему, собственно, отличаются стандартный `Printer` и `PsrPrinter`? Почему в пакете не один printer, а именно `PsrPrinter`? -Типы .[#toc-types] ------------------- +Стандартный `Printer` форматирует код так, как это делаем мы во всем Nette. Поскольку Nette появилось гораздо раньше, чем PSR, а также потому, что PSR долгие годы не предоставляло стандарты вовремя, а, например, с многолетней задержкой после введения новой фичи в PHP, произошло то, что [стандарт кодирования |contributing:coding-standard] в нескольких мелочах отличается. Большим отличием является только использование табуляции вместо пробелов. Мы знаем, что использование табуляции в наших проектах позволяет настраивать ширину, что [необходимо для людей с нарушениями зрения |contributing:coding-standard#Табуляции вместо пробелов]. Примером незначительного отличия является размещение фигурной скобки на отдельной строке у функций и методов, и это всегда. Рекомендация PSR нам кажется нелогичной и ведет к [снижению читаемости кода |contributing:coding-standard#Перенос строк и скобки]. + + +Типы +---- -Каждый тип или тип объединения/пересечения может быть передан как строка. Вы также можете использовать предопределенные константы для собственных типов: +Каждый тип или union/intersection тип можно передать как строку, вы также можете использовать предопределенные константы для встроенных типов: ```php use Nette\PhpGenerator\Type; $member->setType('array'); // или Type::Array; -$member->setType('array|string'); // или Type::union('array', 'string') +$member->setType('?array'); // или Type::nullable(Type::Array); +$member->setType('array|string'); // или Type::union(Type::Array, Type::String) $member->setType('Foo&Bar'); // или Type::intersection(Foo::class, Bar::class) $member->setType(null); // удаляет тип ``` -То же самое относится и к методу `setReturnType()`. +То же самое относится к методу `setReturnType()`. -Литералы .[#toc-literals] -------------------------- +Литералы +-------- -С помощью `Literal` вы можете передавать произвольный PHP-код, например, значения свойств или параметров по умолчанию и т. д: +С помощью `Literal` вы можете передавать любой PHP-код, например, для значений по умолчанию свойств или параметров и т.д.: ```php use Nette\PhpGenerator\Literal; @@ -545,25 +571,37 @@ class Demo } ``` -Вы также можете передавать параметры в `Literal` и форматировать их в правильный PHP-код с помощью [специальных заполнителей |#method-and-function-body-generator]: +Вы также можете передать параметры в `Literal` и отформатировать их в валидный PHP-код с помощью [плейсхолдеров |#Тела методов и функций]: ```php new Literal('substr(?, ?)', [$a, $b]); // генерирует, например: substr('hello', 5); ``` +Литерал, представляющий создание нового объекта, можно легко сгенерировать с помощью метода `new`: -Атрибуты .[#toc-attributes] ---------------------------- +```php +Literal::new(Demo::class, [$a, 'foo' => $b]); +// генерирует, например: new Demo(10, foo: 20) +``` -Вы можете добавлять атрибуты PHP 8 ко всем классам, методам, свойствам, константам, перечислениям, функциям, замыканиям и параметрам. [Литералы|#Literals] также можно использовать в качестве значений параметров. + +Атрибуты +-------- + +Атрибуты PHP 8 можно добавить ко всем классам, методам, свойствам, константам, перечислениям, функциям, замыканиям и параметрам. В качестве значений параметров можно использовать и [##литералы]. ```php $class = new Nette\PhpGenerator\ClassType('Demo'); -$class->addAttribute('Deprecated'); +$class->addAttribute('Table', [ + 'name' => 'user', + 'constraints' => [ + Literal::new('UniqueConstraint', ['name' => 'ean', 'columns' => ['ean']]), + ], +]); $class->addProperty('list') - ->addAttribute('WithArguments', [1, 2]); + ->addAttribute('Deprecated'); $method = $class->addMethod('count') ->addAttribute('Foo\Cached', ['mode' => true]); @@ -577,25 +615,109 @@ echo $class; Результат: ```php -#[Deprecated] +#[Table(name: 'user', constraints: [new UniqueConstraint(name: 'ean', columns: ['ean'])])] class Demo { - #[WithArguments(1, 2)] + #[Deprecated] public $list; #[Foo\Cached(mode: true)] - public function count(#[Bar] $items) - { + public function count( + #[Bar] + $items, + ) { } } ``` -Пространство имен .[#toc-namespace] ------------------------------------ +Property Hooks +-------------- + +С помощью property hooks (представленных классом [PropertyHook|api:Nette\PhpGenerator\PropertyHook]) вы можете определить операции get и set для свойств, что является функцией, введенной в PHP 8.4: + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$prop = $class->addProperty('firstName') + ->setType('string'); + +$prop->addHook('set', 'strtolower($value)') + ->addParameter('value') + ->setType('string'); + +$prop->addHook('get') + ->setBody('return ucfirst($this->firstName);'); + +echo $class; +``` + +Сгенерирует: + +```php +class Demo +{ + public string $firstName { + set(string $value) => strtolower($value); + get { + return ucfirst($this->firstName); + } + } +} +``` -Классы, черты, интерфейсы и перечисления (далее классы) могут быть сгруппированы в пространства имен ([PhpNamespace |api:Nette\PhpGenerator\PhpNamespace]): +Свойства и property hooks могут быть абстрактными или финальными: + +```php +$class->addProperty('id') + ->setType('int') + ->addHook('get') + ->setAbstract(); + +$class->addProperty('role') + ->setType('string') + ->addHook('set', 'strtolower($value)') + ->setFinal(); +``` + + +Асимметричная видимость +----------------------- + +PHP 8.4 вводит асимметричную видимость для свойств. Вы можете установить разные уровни доступа для чтения и записи. + +Видимость можно установить либо с помощью метода `setVisibility()` с двумя параметрами, либо с помощью `setPublic()`, `setProtected()` или `setPrivate()` с параметром `mode`, который определяет, относится ли видимость к чтению или записи свойства. Режим по умолчанию — `'get'`. + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); + +$class->addProperty('name') + ->setType('string') + ->setVisibility('public', 'private'); // public для чтения, private для записи + +$class->addProperty('id') + ->setType('int') + ->setProtected('set'); // protected для записи + +echo $class; +``` + +Сгенерирует: + +```php +class Demo +{ + public private(set) string $name; + + protected(set) int $id; +} +``` + + +Пространство имен +----------------- + +Классы, свойства, интерфейсы и перечисления (далее — классы) можно сгруппировать в пространства имен, представленные классом [PhpNamespace |api:Nette\PhpGenerator\PhpNamespace]: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); @@ -605,14 +727,14 @@ $class = $namespace->addClass('Task'); $interface = $namespace->addInterface('Countable'); $trait = $namespace->addTrait('NameAware'); -// или добавляем существующий класс в пространство имен +// или вставляем существующий класс в пространство имен $class = new Nette\PhpGenerator\ClassType('Task'); $namespace->add($class); ``` -Если класс уже существует, он будет перезаписан. +Если класс уже существует, будет выброшено исключение. -Вы можете добавлять используемые с помощью конструкции `use` классы, трейты и функции: +Вы можете определить use-выражения: ```php // use Http\Request; @@ -623,14 +745,14 @@ $namespace->addUse(Http\Request::class, 'HttpReq'); $namespace->addUseFunction('iter\range'); ``` -Чтобы упростить полное имя класса, функции или константы в соответствии с определенными псевдонимами, используйте метод `simplifyName`: +Чтобы упростить полностью квалифицированное имя класса, функции или константы в соответствии с определенными псевдонимами, используйте метод `simplifyName`: ```php -echo $namespace->simplifyName('Foo\Bar'); // 'Bar', потому что 'Foo' является текущим пространством имен -echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', из-за определенного положения об использовании +echo $namespace->simplifyName('Foo\Bar'); // 'Bar', так как 'Foo' - текущее пространство имен +echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', из-за определенного use-выражения ``` -И наоборот, вы можете преобразовать упрощенное имя класса, функции или константы в полное имя, используя метод `resolveName`: +Упрощенное имя класса, функции или константы вы можете, наоборот, преобразовать в полностью квалифицированное имя с помощью метода `resolveName`: ```php echo $namespace->resolveName('Bar'); // 'Foo\Bar' @@ -638,29 +760,27 @@ echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range' ``` -Разрешение имён классов .[#toc-class-names-resolving] ------------------------------------------------------ +Разрешение имен классов +----------------------- -**Когда класс является частью пространства имен, он отображается несколько иначе**: все типы (т.е. подсказки типов, возвращаемые типы, имя родительского класса, -реализованные интерфейсы, используемые черты и атрибуты) автоматически *разрешаются* (если вы не отключите эту функцию, см. ниже). -Это означает, что вы должны **использовать полные имена классов** в определениях, и они будут заменены псевдонимами (в соответствии с условиями использования) или полностью квалифицированными именами в результирующем коде: +**Когда класс является частью пространства имен, он отображается немного иначе:** все типы (например, typehint'ы, возвращаемые типы, имя родительского класса, реализованные интерфейсы, используемые свойства и атрибуты) автоматически *разрешаются* (если вы это не отключите, см. ниже). Это означает, что вы должны в определениях **использовать полные имена классов**, и они будут заменены на псевдонимы (согласно use-выражениям) или на полностью квалифицированные имена в результирующем коде: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); $namespace->addUse('Bar\AliasedClass'); $class = $namespace->addClass('Demo'); -$class->addImplement('Foo\A') // упрощается до A - ->addTrait('Bar\AliasedClass'); // упрощается до AliasedClass +$class->addImplement('Foo\A') // будет упрощено до A + ->addTrait('Bar\AliasedClass'); // будет упрощено до AliasedClass $method = $class->addMethod('method'); $method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // в комментариях упрощаем вручную $method->addParameter('arg') - ->setType('Bar\OtherClass'); // он будет разрешен в \Bar\OtherClass + ->setType('Bar\OtherClass'); // будет преобразовано в \Bar\OtherClass echo $namespace; -// или использовать PsrPrinter для вывода в соответствии с PSR-2 / PSR-12 +// или используйте PsrPrinter для вывода в соответствии с PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -684,7 +804,7 @@ class Demo implements A } ``` -Авторазрешение можно отключить следующим образом: +Автоматическое разрешение можно отключить следующим образом: ```php $printer = new Nette\PhpGenerator\Printer; // или PsrPrinter @@ -693,15 +813,15 @@ echo $printer->printNamespace($namespace); ``` -PHP-файлы .[#toc-php-files] ---------------------------- +PHP-файлы +--------- -Классы, функции и пространства имен могут быть сгруппированы в файлы PHP, представленные классом [PhpFile|api:Nette\PhpGenerator\PhpFile]: +Классы, функции и пространства имен можно сгруппировать в PHP-файлы, представленные классом [PhpFile|api:Nette\PhpGenerator\PhpFile]: ```php $file = new Nette\PhpGenerator\PhpFile; -$file->addComment('This file is auto-generated.'); -$file->setStrictTypes(); // добавляет declare(strict_types=1) +$file->addComment('Этот файл сгенерирован автоматически.'); +$file->setStrictTypes(); // добавит declare(strict_types=1) $class = $file->addClass('Foo\A'); $function = $file->addFunction('Foo\foo'); @@ -713,7 +833,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// или использовать PsrPrinter для вывода, совместимого с PSR-2 / PSR-12 +// или используйте PsrPrinter для вывода в соответствии с PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -723,7 +843,7 @@ echo $file; <?php /** - * This file is auto-generated. + * Этот файл сгенерирован автоматически. */ declare(strict_types=1); @@ -739,27 +859,28 @@ function foo() } ``` +**Внимание:** В файлы нельзя добавлять никакой другой код, кроме функций и классов. + -Генерация с использованием Reflection .[#toc-generating-according-to-existing-ones] ------------------------------------------------------------------------------------ +Генерация на основе существующих +-------------------------------- -Помимо того, что вы можете моделировать классы и функции с помощью описанного выше API, вы также можете автоматически генерировать их на основе существующих: +Помимо того, что классы и функции можно моделировать с помощью описанного выше API, их также можно генерировать автоматически на основе существующих образцов: ```php -// создает класс, идентичный классу PDO +// создаст класс, идентичный классу PDO $class = Nette\PhpGenerator\ClassType::from(PDO::class); -// создает функцию, идентичную функции trim() +// создаст функцию, идентичную функции trim() $function = Nette\PhpGenerator\GlobalFunction::from('trim'); -// создает закрытие, как указано +// создаст closure на основе указанного $closure = Nette\PhpGenerator\Closure::from( function (stdClass $a, $b = null) {}, ); ``` -Тела функций и методов по умолчанию пусты. Если вы хотите загрузить и их, воспользуйтесь следующим способом -(он требует установки `nikic/php-parser` ): +Тела функций и методов по умолчанию пусты. Если вы хотите их также загрузить, используйте этот способ (требует установки пакета `nikic/php-parser`): ```php $class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true); @@ -768,10 +889,10 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); ``` -Загрузка класса из файла .[#toc-loading-from-php-file] ------------------------------------------------------- +Загрузка из PHP-файлов +---------------------- -Вы также можете загружать функции, классы, интерфейсы и перечисления непосредственно из строки PHP-кода. Например, мы создаем объект `ClassType` таким образом: +Функции, классы, интерфейсы и перечисления можно загружать также прямо из строки, содержащей PHP-код. Например, так создадим объект `ClassType`: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -784,39 +905,69 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -При загрузке классов из PHP-кода однострочные комментарии вне тела методов игнорируются (например, для свойств и т.д.), поскольку в этой библиотеке нет API для работы с ними. +При загрузке классов из PHP-кода однострочные комментарии вне тел методов игнорируются (например, у свойств и т.д.), поскольку эта библиотека не имеет API для работы с ними. -Вы также можете загрузить непосредственно весь PHP-файл, который может содержать любое количество классов, функций или даже несколько пространств имен: +Вы также можете загрузить прямо весь PHP-файл, который может содержать любое количество классов, функций или даже пространств имен: ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` -Начальный комментарий к файлу и объявление `strict_types` также загружаются. С другой стороны, весь остальной глобальный код игнорируется. +Загрузится также вступительный комментарий к файлу и декларация `strict_types`. Напротив, весь остальной глобальный код игнорируется. -Для этого необходимо установить `nikic/php-parser`. +Требуется, чтобы был установлен `nikic/php-parser`. .[note] -Если вам нужно манипулировать глобальным кодом в файлах или отдельными утверждениями в телах методов, лучше использовать непосредственно библиотеку `nikic/php-parser`. +Если вам нужно манипулировать глобальным кодом в файлах или отдельными инструкциями в телах методов, лучше использовать непосредственно библиотеку `nikic/php-parser`. + + +Class Manipulator +----------------- + +Класс [ClassManipulator|api:Nette\PhpGenerator\ClassManipulator] предоставляет инструменты для манипулирования классами. + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$manipulator = new Nette\PhpGenerator\ClassManipulator($class); +``` + +Метод `inheritMethod()` скопирует метод из родительского класса или реализованного интерфейса в ваш класс. Это позволит вам переопределить метод или расширить его сигнатуру: +```php +$method = $manipulator->inheritMethod('bar'); +$method->setBody('...'); +``` + +Метод `inheritProperty()` скопирует свойство из родительского класса в ваш класс. Это полезно, когда вы хотите иметь в своем классе то же свойство, но, например, с другим значением по умолчанию: + +```php +$property = $manipulator->inheritProperty('foo'); +$property->setValue('new value'); +``` -Дампинг переменных .[#toc-variables-dumper] -------------------------------------------- +Метод `implement()` автоматически реализует все методы и свойства из данного интерфейса или абстрактного класса в вашем классе: + +```php +$manipulator->implement(SomeInterface::class); +// Теперь ваш класс реализует SomeInterface и содержит все его методы +``` -Класс `Dumper` возвращает разобранное строковое представление переменной в формате PHP. Обеспечивает более качественный и четкий вывод, чем родная функция `var_export()`. + +Вывод переменных +---------------- + +Класс `Dumper` преобразует переменную в парсируемый PHP-код. Он предоставляет лучший и более понятный вывод, чем стандартная функция `var_export()`. ```php $dumper = new Nette\PhpGenerator\Dumper; $var = ['a', 'b', 123]; -echo $dumper->dump($var); // печатает ['a', 'b', 123] +echo $dumper->dump($var); // выведет ['a', 'b', 123] ``` -Таблица совместимости .[#toc-compatibility-table] -------------------------------------------------- - -PhpGenerator 4.0 совместим с PHP 8.0 - 8.2 +Таблица совместимости +--------------------- -{{leftbar: nette:@menu-topics}} +PhpGenerator 4.1 совместим с PHP 8.0 по 8.4. diff --git a/php-generator/ru/@meta.texy b/php-generator/ru/@meta.texy new file mode 100644 index 0000000000..61577d6323 --- /dev/null +++ b/php-generator/ru/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Документация Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/php-generator/sl/@home.texy b/php-generator/sl/@home.texy index 4dd5a7c891..bd104f96a3 100644 --- a/php-generator/sl/@home.texy +++ b/php-generator/sl/@home.texy @@ -1,31 +1,32 @@ -Generator kode PHP +Nette PhpGenerator ****************** -<div class=perex> -- Potrebujete kodo PHP za razrede, funkcije, datoteke PHP itd.? -- Podpira vse najnovejše funkcije PHP, kot so enumi, atributi itd. -- Omogoča enostavno spreminjanje obstoječih razredov -- Izpis v skladu s standardom PSR-12 -- Zelo zrela, stabilna in pogosto uporabljena knjižnica +<div class="perex"> +Iščete orodje za generiranje PHP kode razredov, funkcij ali celotnih datotek? + +- Zna vse najnovejše funkcije v PHP (kot so property hooks, enumi, atributi itd.) +- Omogoča vam enostavno spreminjanje obstoječih razredov +- Izhodna koda je v skladu s PSR-12 / PER coding style +- Zrela, stabilna in široko uporabljana knjižnica </div> -Namestitev .[#toc-installation] -------------------------------- +Namestitev +---------- -Prenesite in namestite paket s [programom Composer |best-practices:composer]: +Knjižnico prenesete in namestite z orodjem [Composer|best-practices:composer]: ```shell composer require nette/php-generator ``` -Za združljivost s PHP glejte [tabelo |#Compatibility Table]. +Združljivost s PHP najdete v [tabeli |#Tabela združljivosti]. -Razredi .[#toc-classes] ------------------------ +Razredi +------- -Začnimo z enostavnim primerom generiranja razreda z uporabo [ClassType |api:Nette\PhpGenerator\ClassType]: +Začnimo kar s primerom ustvarjanja razreda z uporabo [ClassType |api:Nette\PhpGenerator\ClassType]: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -34,19 +35,19 @@ $class ->setFinal() ->setExtends(ParentClass::class) ->addImplement(Countable::class) - ->addComment("Description of class.\nSecond line\n") + ->addComment("Opis razreda.\nDruga vrstica\n") ->addComment('@property-read Nette\Forms\Form $form'); -// za generiranje kode PHP preprosto pretvorite v niz ali uporabite echo: +// kodo enostavno generirate s pretvorbo v niz ali z uporabo echo: echo $class; ``` -Rezultat bo naslednji: +Vrne naslednji rezultat: ```php /** - * Description of class. - * Second line + * Opis razreda + * Druga vrstica * * @property-read Nette\Forms\Form $form */ @@ -55,18 +56,18 @@ final class Demo extends ParentClass implements Countable } ``` -Za generiranje kode lahko uporabimo tudi tiskalnik, ki ga bomo za razliko od `echo $class` lahko [dodatno konfigurirali |#Printers and PSR compliance]: +Za generiranje kode lahko uporabimo tudi t.i. printer, ki ga bomo za razliko od `echo $class` lahko [nadalje konfigurirali |#Printer in skladnost s PSR]: ```php $printer = new Nette\PhpGenerator\Printer; echo $printer->printClass($class); ``` -Dodamo lahko konstante (razred [Constant |api:Nette\PhpGenerator\Constant]) in lastnosti (razred [Property |api:Nette\PhpGenerator\Property]): +Lahko dodamo konstante (razred [Constant |api:Nette\PhpGenerator\Constant]) in spremenljivke (razred [Property |api:Nette\PhpGenerator\Property]): ```php $class->addConstant('ID', 123) - ->setProtected() // konstantna vidnost + ->setProtected() // vidnost konstant ->setType('int') ->setFinal(); @@ -80,7 +81,7 @@ $class->addProperty('list') ->setInitialized(); // izpiše '= null' ``` -Ustvari se: +Generira: ```php final protected const int ID = 123; @@ -91,14 +92,14 @@ private static $items = [1, 2, 3]; public ?array $list = null; ``` -In lahko dodamo [metode |#Method and Function Signature]: +In lahko dodamo [metode |#Signature metod in funkcij]: ```php $method = $class->addMethod('count') - ->addComment('Count it.') + ->addComment('Preštej.') ->setFinal() ->setProtected() - ->setReturnType('?int') // tip vrnitve metode + ->setReturnType('?int') // povratni tipi pri metodah ->setBody('return count($items ?: $this->items);'); $method->addParameter('items', []) // $items = [] @@ -110,7 +111,7 @@ Rezultat je: ```php /** - * Count it. + * Preštej. */ final protected function count(array &$items = []): ?int { @@ -118,7 +119,7 @@ final protected function count(array &$items = []): ?int } ``` -Konstruktorju se lahko posredujejo promovirani parametri, ki jih je uvedel PHP 8.0: +Promovirane parametre, uvedene v PHP 8.0, lahko predate konstruktorju: ```php $method = $class->addMethod('__construct'); @@ -137,15 +138,15 @@ public function __construct( } ``` -Lastnosti in razrede, ki so namenjeni samo branju, lahko označite s spletno stranjo `setReadOnly()`. +Lastnosti in razrede, namenjene samo za branje, lahko označite s funkcijo `setReadOnly()`. ------ Če dodana lastnost, konstanta, metoda ali parameter že obstajajo, se vrže izjema. -Člane lahko odstranite z uporabo `removeProperty()`, `removeConstant()`, `removeMethod()` ali `removeParameter()`. +Člane razreda lahko odstranite z uporabo `removeProperty()`, `removeConstant()`, `removeMethod()` ali `removeParameter()`. -Razredu lahko dodate tudi obstoječe predmete `Method`, `Property` ali `Constant`: +V razred lahko dodate tudi obstoječe objekte `Method`, `Property` ali `Constant`: ```php $method = new Nette\PhpGenerator\Method('getHandle'); @@ -158,7 +159,7 @@ $class = (new Nette\PhpGenerator\ClassType('Demo')) ->addMember($const); ``` -Obstoječe metode, lastnosti in konstante lahko klonirate z drugim imenom z uporabo `cloneWithName()`: +Lahko tudi klonirate obstoječe metode, lastnosti in konstante pod drugim imenom z uporabo `cloneWithName()`: ```php $methodCount = $class->getMethod('count'); @@ -167,10 +168,10 @@ $class->addMember($methodRecount); ``` -Vmesnik ali lastnost .[#toc-interface-or-trait] ------------------------------------------------ +Vmesnik ali lastnost +-------------------- -Ustvarite lahko vmesnike in lastnosti (razreda [InterfaceType |api:Nette\PhpGenerator\InterfaceType] in [TraitType |api:Nette\PhpGenerator\TraitType]): +Lahko ustvarjate vmesnike in lastnosti (razreda [InterfaceType |api:Nette\PhpGenerator\InterfaceType] in [TraitType |api:Nette\PhpGenerator\TraitType]): ```php $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); @@ -202,10 +203,10 @@ class Demo ``` -Enumi .[#toc-enums] -------------------- +Enumi +----- -Enostavno lahko ustvarite enume, ki jih prinaša PHP 8.1 (razred [EnumType |api:Nette\PhpGenerator\EnumType]): +Naštevne tipe, ki jih prinaša PHP 8.1, lahko enostavno ustvarite takole: (razred [EnumType |api:Nette\PhpGenerator\EnumType]): ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -229,20 +230,20 @@ enum Suit } ``` -Za ustvarjanje podprtega enuma lahko določite tudi skalarne ekvivalente za primere: +Lahko tudi definirate skalarne ekvivalente in tako ustvarite "backed" naštevni tip: ```php $enum->addCase('Clubs', '♣'); $enum->addCase('Diamonds', '♦'); ``` -Vsakemu primeru je mogoče dodati komentar ali [atribute |#attributes] z uporabo `addComment()` ali `addAttribute()`. +Vsakemu `case` je mogoče dodati komentar ali [atribute |#Atributi] z uporabo `addComment()` ali `addAttribute()`. -Anonimni razred .[#toc-anonymous-class] ---------------------------------------- +Anonimni razredi +---------------- -Kot ime dajte `null` in dobili boste anonimni razred: +Kot ime predamo `null` in imamo anonimni razred: ```php $class = new Nette\PhpGenerator\ClassType(null); @@ -264,10 +265,10 @@ $obj = new class ($val) { ``` -Globalna funkcija .[#toc-global-function] ------------------------------------------ +Globalne funkcije +----------------- -Koda funkcije bo ustvarila razred [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction]: +Kodo funkcij generira razred [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction]: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -276,7 +277,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// ali uporabite PsrPrinter za izpis v skladu s PSR-2 / PSR-12 +// ali uporabite PsrPrinter za izpis v skladu s PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -290,10 +291,10 @@ function foo($a, $b) ``` -Zaprtje .[#toc-closure] ------------------------ +Anonimne funkcije +----------------- -Koda zaprtja bo ustvarila razred [Closure |api:Nette\PhpGenerator\Closure]: +Kodo anonimnih funkcij generira razred [Closure |api:Nette\PhpGenerator\Closure]: ```php $closure = new Nette\PhpGenerator\Closure; @@ -304,7 +305,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// ali uporabite PsrPrinter za izpis v skladu s PSR-2 / PSR-12 +// ali uporabite PsrPrinter za izpis v skladu s PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -317,10 +318,10 @@ function ($a, $b) use (&$c) { ``` -Funkcija puščice .[#toc-arrow-function] ---------------------------------------- +Skrajšane puščične funkcije +--------------------------- -Zaključek lahko natisnete tudi s tiskalnikom kot funkcijo puščice: +Lahko tudi izpišete skrajšano anonimno funkcijo z uporabo printerja: ```php $closure = new Nette\PhpGenerator\Closure; @@ -338,34 +339,34 @@ fn($a, $b) => $a + $b ``` -Podpis metode in funkcije .[#toc-method-and-function-signature] ---------------------------------------------------------------- +Signature metod in funkcij +-------------------------- -Metode so predstavljene z razredom [Method |api:Nette\PhpGenerator\Method]. Nastavite lahko vidnost, povratno vrednost, dodate komentarje, [atribute |#Attributes] itd: +Metode predstavlja razred [Method |api:Nette\PhpGenerator\Method]. Lahko nastavite vidnost, povratno vrednost, dodate komentarje, [atribute |#Atributi] itd: ```php $method = $class->addMethod('count') - ->addComment('Count it.') + ->addComment('Preštej.') ->setFinal() ->setProtected() ->setReturnType('?int'); ``` -Vsak parameter je predstavljen z razredom [Parameter |api:Nette\PhpGenerator\Parameter]. Spet lahko nastavite vse možne lastnosti: +Posamezne parametre predstavlja razred [Parameter |api:Nette\PhpGenerator\Parameter]. Spet lahko nastavite vse mogoče lastnosti: ```php $method->addParameter('items', []) // $items = [] - ->setReference() // &$items = [] - ->setType('array'); // array &$items = [] + ->setReference() // &$items = [] + ->setType('array'); // array &$items = [] // function count(&$items = []) ``` -Za določitev tako imenovanih parametrov variadics (ali tudi operatorjev splat, spread, elipsis, unpacking ali tri pike) uporabite `setVariadics()`: +Za definiranje t.i. variadics parametrov (ali tudi splat operator) služi `setVariadic()`: ```php $method = $class->addMethod('count'); -$method->setVariadics(true); +$method->setVariadic(true); $method->addParameter('items'); ``` @@ -378,10 +379,10 @@ function count(...$items) ``` -Metoda in telo funkcije .[#toc-method-and-function-body] --------------------------------------------------------- +Telesa metod in funkcij +----------------------- -Telo lahko metodi `setBody()` posredujete naenkrat ali zaporedno (vrstico za vrstico) z večkratnim klicem `addBody()`: +Telo lahko predate naenkrat metodi `setBody()` ali postopoma (po vrsticah) z večkratnim klicem `addBody()`: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -400,9 +401,9 @@ function foo() } ``` -Za priročen način injiciranja spremenljivk lahko uporabite posebna nadomestna imena. +Lahko uporabite posebne ograde za enostavno vstavljanje spremenljivk. -Enostavna nadomestna imena `?` +Enostavni simboli za ograde `?` ```php $str = 'any string'; @@ -412,7 +413,7 @@ $function->addBody('return substr(?, ?);', [$str, $num]); echo $function; ``` -Rezultat: +Rezultat ```php function foo() @@ -421,7 +422,7 @@ function foo() } ``` -Variadični nosilec mesta `...?` +Ograda za variadic `...?` ```php $items = [1, 2, 3]; @@ -439,7 +440,7 @@ function foo() } ``` -Poimenovane parametre PHP 8 lahko uporabite tudi z uporabo nadomestnega imena `...?:` +Lahko uporabite tudi imenovane parametre za PHP 8 z uporabo `...?:` ```php $items = ['foo' => 1, 'bar' => true]; @@ -448,7 +449,7 @@ $function->setBody('myfunc(...?:);', [$items]); // myfunc(foo: 1, bar: true); ``` -Namestno znamenje pobegnite s poševnico `\?` +Ograda se ubeži z uporabo poševnice `\?` ```php $num = 3; @@ -468,45 +469,70 @@ function foo($a) ``` -Tiskalniki in skladnost s predpisi PSR .[#toc-printers-and-psr-compliance] --------------------------------------------------------------------------- +Printer in skladnost s PSR +-------------------------- -Koda PHP se ustvari s pomočjo predmetov `Printer`. Obstaja `PsrPrinter`, katerega izpis je skladen s PSR-2 in PSR-12 ter uporablja presledke za alineje, in `Printer`, ki za alineje uporablja tabulatorje. +Za generiranje PHP kode služi razred [Printer |api:Nette\PhpGenerator\Printer]: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // enako kot: echo $class +``` + +Zna generirati kodo vseh drugih elementov, ponuja metode kot so `printFunction()`, `printNamespace()`, itd. + +Na voljo je tudi razred `PsrPrinter`, katerega izpis je v skladu s PSR-2 / PSR-12 / PER coding style: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // Odmik 4 presledki +echo $printer->printClass($class); ``` -Potrebujete prilagoditi obnašanje tiskalnika? Ustvarite svojega tako, da podedujete razred `Printer`. Te spremenljivke lahko ponovno konfigurirate: +Potrebujete obnašanje prilagoditi po meri? Ustvarite svojo različico z dedovanjem razreda `Printer`. Lahko prekonfigurirate te spremenljivke: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // dolžina vrstice, po kateri pride do preloma vrstice public int $wrapLength = 120; + // znak za zamik, lahko ga nadomesti zaporedje presledkov public string $indentation = "\t"; + // število praznih vrstic med lastnostmi public int $linesBetweenProperties = 0; + // število praznih vrstic med metodami public int $linesBetweenMethods = 2; + // število praznih vrstic med skupinami 'use statements' za razrede, funkcije in konstante public int $linesBetweenUseTypes = 0; + // položaj odpirajočega zavitega oklepaja za funkcije in metode public bool $bracesOnNextLine = true; + // postavite en parameter na eno vrstico, tudi če ima atribut ali je podprt + public bool $singleParameterOnOneLine = false; + // izpusti imenske prostore, ki ne vsebujejo nobenega razreda ali funkcije + public bool $omitEmptyNamespaces = true; + // ločilo med desnim oklepajem in povratnim tipom funkcij in metod public string $returnTypeColon = ': '; } ``` +Kako in zakaj se pravzaprav razlikujeta standardni `Printer` in `PsrPrinter`? Zakaj v paketu ni samo en printer, in sicer `PsrPrinter`? -Tipi .[#toc-types] ------------------- +Standardni `Printer` formatira kodo tako, kot to počnemo v celotnem Nette. Ker je Nette nastal veliko prej kot PSR, in tudi zato, ker PSR dolga leta ni pravočasno zagotavljal standardov, ampak na primer šele z večletno zamudo po uvedbi nove funkcije v PHP, je prišlo do tega, da se [standard kodiranja |contributing:coding-standard] v nekaj manjših podrobnostih razlikuje. Večja razlika je le uporaba tabulatorjev namesto presledkov. Vemo, da z uporabo tabulatorjev v naših projektih omogočamo prilagajanje širine, kar je za [ljudi z okvaro vida nujno |contributing:coding-standard#Zavihki namesto presledkov]. Primer manjše razlike je postavitev zavitega oklepaja na samostojno vrstico pri funkcijah in metodah, in to vedno. Priporočilo PSR se nam zdi nelogično in vodi k [zmanjšanju preglednosti kode |contributing:coding-standard#Oblikovanje in oklepaji]. -Vsak tip ali tip zveze/intersekcije je mogoče posredovati kot niz, uporabite lahko tudi vnaprej določene konstante za domače tipe: + +Tipi +---- + +Vsak tip ali union/intersection tip lahko predate kot niz, lahko pa uporabite tudi preddefinirane konstante za nativne tipe: ```php use Nette\PhpGenerator\Type; $member->setType('array'); // ali Type::Array; -$member->setType('array|string'); // ali Type::union('array', 'string') +$member->setType('?array'); // ali Type::nullable(Type::Array); +$member->setType('array|string'); // ali Type::union(Type::Array, Type::String) $member->setType('Foo&Bar'); // ali Type::intersection(Foo::class, Bar::class) $member->setType(null); // odstrani tip ``` @@ -514,10 +540,10 @@ $member->setType(null); // odstrani tip Enako velja za metodo `setReturnType()`. -Literali .[#toc-literals] -------------------------- +Literali +-------- -S `Literal` lahko poljubno kodo PHP posredujete na primer privzetim vrednostim lastnosti ali parametrov itd: +Z uporabo `Literal` lahko predate poljubno PHP kodo, na primer za privzete vrednosti lastnosti ali parametrov itd: ```php use Nette\PhpGenerator\Literal; @@ -545,25 +571,37 @@ class Demo } ``` -Rezultat: Na naslov `Literal` lahko posredujete tudi parametre, ki se s [posebnimi nadomestki |#method-and-function-body-generator] oblikujejo v veljavno PHP kodo: +Lahko tudi predate parametre v `Literal` in jih pustite formatirati v veljavno PHP kodo z uporabo [ograd |#Telesa metod in funkcij]: ```php new Literal('substr(?, ?)', [$a, $b]); -// generira, na primer: substr('hello', 5); +// generira na primer: substr('hello', 5); ``` +Literal, ki predstavlja ustvarjanje novega objekta, lahko enostavno generirate z metodo `new`: -Atributi .[#toc-attributes] ---------------------------- +```php +Literal::new(Demo::class, [$a, 'foo' => $b]); +// generira na primer: new Demo(10, foo: 20) +``` -Vsem razredom, metodam, lastnostim, konstantam, imenskim primerom, funkcijam, zaključkom in parametrom lahko dodate atribute PHP 8. Kot vrednosti parametrov lahko uporabite tudi [literale |#Literals]. + +Atributi +-------- + +PHP 8 atribute lahko dodate vsem razredom, metodam, lastnostim, konstantam, enumom, funkcijam, closureom in parametrom. Kot vrednosti parametrov lahko uporabljate tudi [literale |#Literali]. ```php $class = new Nette\PhpGenerator\ClassType('Demo'); -$class->addAttribute('Deprecated'); +$class->addAttribute('Table', [ + 'name' => 'user', + 'constraints' => [ + Literal::new('UniqueConstraint', ['name' => 'ean', 'columns' => ['ean']]), + ], +]); $class->addProperty('list') - ->addAttribute('WithArguments', [1, 2]); + ->addAttribute('Deprecated'); $method = $class->addMethod('count') ->addAttribute('Foo\Cached', ['mode' => true]); @@ -577,42 +615,126 @@ echo $class; Rezultat: ```php -#[Deprecated] +#[Table(name: 'user', constraints: [new UniqueConstraint(name: 'ean', columns: ['ean'])])] class Demo { - #[WithArguments(1, 2)] + #[Deprecated] public $list; #[Foo\Cached(mode: true)] - public function count(#[Bar] $items) - { + public function count( + #[Bar] + $items, + ) { } } ``` -Imenski prostor .[#toc-namespace] ---------------------------------- +Property Hooks +-------------- + +Z uporabo property hooks (predstavljenih z razredom [PropertyHook|api:Nette\PhpGenerator\PropertyHook]) lahko definirate operacije get in set za lastnosti, kar je funkcija, uvedena v PHP 8.4: + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$prop = $class->addProperty('firstName') + ->setType('string'); + +$prop->addHook('set', 'strtolower($value)') + ->addParameter('value') + ->setType('string'); + +$prop->addHook('get') + ->setBody('return ucfirst($this->firstName);'); + +echo $class; +``` + +Generira: + +```php +class Demo +{ + public string $firstName { + set(string $value) => strtolower($value); + get { + return ucfirst($this->firstName); + } + } +} +``` -Razrede, lastnosti, vmesnike in enume (v nadaljevanju razredi) lahko združimo v imenski prostor ([PhpNamespace |api:Nette\PhpGenerator\PhpNamespace]): +Lastnosti in property hooks so lahko abstraktne ali končne: + +```php +$class->addProperty('id') + ->setType('int') + ->addHook('get') + ->setAbstract(); + +$class->addProperty('role') + ->setType('string') + ->addHook('set', 'strtolower($value)') + ->setFinal(); +``` + + +Asimetrična vidnost +------------------- + +PHP 8.4 uvaja asimetrično vidnost za lastnosti. Lahko nastavite različne ravni dostopa za branje in pisanje. + +Vidnost lahko nastavite bodisi z metodo `setVisibility()` z dvema parametroma, bodisi z `setPublic()`, `setProtected()` ali `setPrivate()` s parametrom `mode`, ki določa, ali se vidnost nanaša na branje ali pisanje lastnosti. Privzeti način je `'get'`. + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); + +$class->addProperty('name') + ->setType('string') + ->setVisibility('public', 'private'); // public za branje, private za pisanje + +$class->addProperty('id') + ->setType('int') + ->setProtected('set'); // protected za pisanje + +echo $class; +``` + +Generira: + +```php +class Demo +{ + public private(set) string $name; + + protected(set) int $id; +} +``` + + +Imenski prostor +--------------- + +Razrede, lastnosti, vmesnike in naštevne tipe (v nadaljevanju razredi) lahko združimo v imenske prostore, ki jih predstavlja razred [PhpNamespace |api:Nette\PhpGenerator\PhpNamespace]: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); -// ustvarjanje novih razredov v imenskem prostoru +// ustvarimo nove razrede v imenskem prostoru $class = $namespace->addClass('Task'); $interface = $namespace->addInterface('Countable'); $trait = $namespace->addTrait('NameAware'); -// ali vstavite obstoječi razred v imenski prostor. +// ali vstavimo obstoječi razred v imenski prostor $class = new Nette\PhpGenerator\ClassType('Task'); $namespace->add($class); ``` Če razred že obstaja, se vrže izjema. -Določite lahko izjave o uporabi: +Lahko definirate klavzule use: ```php // use Http\Request; @@ -623,14 +745,14 @@ $namespace->addUse(Http\Request::class, 'HttpReq'); $namespace->addUseFunction('iter\range'); ``` -Če želite poenostaviti polno kvalificirano ime razreda, funkcije ali konstante v skladu z opredeljenimi vzdevki, uporabite metodo `simplifyName`: +Če želite poenostaviti polno kvalificirano ime razreda, funkcije ali konstante glede na definirane aliase, uporabite metodo `simplifyName`: ```php echo $namespace->simplifyName('Foo\Bar'); // 'Bar', ker je 'Foo' trenutni imenski prostor -echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', ker je opredeljena izjava o uporabi +echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', zaradi definiranega use-statement ``` -Poenostavljeno ime razreda, funkcije ali konstante pa lahko pretvorite v polno kvalificirano ime z uporabo metode `resolveName`: +Poenostavljeno ime razreda, funkcije ali konstante lahko nasprotno pretvorite v polno kvalificirano ime z metodo `resolveName`: ```php echo $namespace->resolveName('Bar'); // 'Foo\Bar' @@ -638,29 +760,27 @@ echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range' ``` -Razreševanje imen razredov .[#toc-class-names-resolving] --------------------------------------------------------- +Prevodi imen razredov +--------------------- -**Ko je razred del imenskega prostora, se prikaže nekoliko drugače**: vse vrste (tj. namigi na vrsto, vrnitvene vrste, ime nadrejenega razreda, -implementirani vmesniki, uporabljene lastnosti in atributi) se samodejno *razrešijo* (razen če to izklopite, glejte spodaj). -To pomeni, da morate v definicijah **uporabiti polna imena razredov**, ki bodo v dobljeni kodi nadomeščena s približki (glede na izjave o uporabi) ali polno kvalificiranimi imeni: +**Ko je razred del imenskega prostora, je izrisan nekoliko drugače:** vsi tipi (na primer typehinty, povratni tipi, ime starševskega razreda, implementirani vmesniki, uporabljene lastnosti in atributi) se samodejno *prevajajo* (razen če to izklopite, glejte spodaj). To pomeni, da morate v definicijah **uporabljati polna imena razredov** in ta bodo v končni kodi nadomeščena z aliasi (glede na klavzule use) ali s polno kvalificiranimi imeni: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); $namespace->addUse('Bar\AliasedClass'); $class = $namespace->addClass('Demo'); -$class->addImplement('Foo\A') // se poenostavi na A - ->addTrait('Bar\AliasedClass'); // se poenostavi na AliasedClass +$class->addImplement('Foo\A') // bo poenostavljen na A + ->addTrait('Bar\AliasedClass'); // bo poenostavljen na AliasedClass $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // v komentarjih poenostavite ročno +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // v komentarjih poenostavimo ročno $method->addParameter('arg') - ->setType('Bar\OtherClass'); // razreši se na \Bar\OtherClass + ->setType('Bar\OtherClass'); // bo preveden na \Bar\OtherClass echo $namespace; -// ali uporabite PsrPrinter za izpis v skladu s PSR-2 / PSR-12 +// ali uporabite PsrPrinter za izpis v skladu s PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -684,7 +804,7 @@ class Demo implements A } ``` -Na ta način je mogoče izklopiti samodejno reševanje: +Samodejno prevajanje lahko izklopite na ta način: ```php $printer = new Nette\PhpGenerator\Printer; // ali PsrPrinter @@ -693,14 +813,14 @@ echo $printer->printNamespace($namespace); ``` -Datoteke PHP .[#toc-php-files] ------------------------------- +PHP datoteke +------------ -Razrede, funkcije in prostore imen lahko združite v datoteke PHP, ki jih predstavlja razred [PhpFile |api:Nette\PhpGenerator\PhpFile]: +Razrede, funkcije in imenske prostore lahko združimo v PHP datoteke, ki jih predstavlja razred [PhpFile|api:Nette\PhpGenerator\PhpFile]: ```php $file = new Nette\PhpGenerator\PhpFile; -$file->addComment('This file is auto-generated.'); +$file->addComment('Ta datoteka je samodejno generirana.'); $file->setStrictTypes(); // doda declare(strict_types=1) $class = $file->addClass('Foo\A'); @@ -713,7 +833,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// ali uporabite PsrPrinter za izpis v skladu s PSR-2 / PSR-12 +// ali uporabite PsrPrinter za izpis v skladu s PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -723,7 +843,7 @@ Rezultat: <?php /** - * This file is auto-generated. + * Ta datoteka je samodejno generirana. */ declare(strict_types=1); @@ -739,27 +859,28 @@ function foo() } ``` +**Opozorilo:** V datoteke ni mogoče dodajati nobene druge kode razen funkcij in razredov. + -Ustvarjanje glede na obstoječe .[#toc-generating-according-to-existing-ones] ----------------------------------------------------------------------------- +Generiranje po obstoječih +------------------------- -Poleg tega, da lahko razrede in funkcije modelirate z uporabo zgoraj opisanega API-ja, jih lahko tudi samodejno generirate na podlagi obstoječih: +Poleg tega, da lahko razrede in funkcije modelirate z zgoraj opisanim API-jem, jih lahko pustite tudi samodejno generirati po obstoječih vzorcih: ```php -// ustvari razred, ki je enak razredu PDO +// ustvari razred enak kot razred PDO $class = Nette\PhpGenerator\ClassType::from(PDO::class); -// ustvari funkcijo, ki je enaka funkciji trim() +// ustvari funkcijo enako funkciji trim() $function = Nette\PhpGenerator\GlobalFunction::from('trim'); -// ustvari zaprtje, kot je določeno +// ustvari closure po navedeni $closure = Nette\PhpGenerator\Closure::from( function (stdClass $a, $b = null) {}, ); ``` -Privzeto so telesa funkcij in metod prazna. Če jih želite tudi naložiti, uporabite ta način -(zahteva namestitev spletne strani `nikic/php-parser` ): +Telesa funkcij in metod so privzeto prazna. Če jih želite tudi naložiti, uporabite ta način (zahteva namestitev paketa `nikic/php-parser`): ```php $class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true); @@ -768,10 +889,10 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); ``` -Nalaganje iz datoteke PHP .[#toc-loading-from-php-file] -------------------------------------------------------- +Nalaganje iz PHP datotek +------------------------ -Funkcije, razrede, vmesnike in enume lahko naložite tudi neposredno iz niza kode PHP. Tako na primer ustvarimo objekt `ClassType`: +Funkcije, razrede, vmesnike in enume lahko nalagate tudi neposredno iz niza, ki vsebuje PHP kodo. Na primer, tako ustvarimo objekt `ClassType`: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -784,39 +905,69 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -Pri nalaganju razredov iz kode PHP se enovrstični komentarji zunaj teles metod ne upoštevajo (npr. za lastnosti itd.), ker ta knjižnica nima API za delo z njimi. +Pri nalaganju razredov iz PHP kode so enovrstični komentarji zunaj teles metod prezrti (npr. pri lastnostih itd.), ker ta knjižnica nima API-ja za delo z njimi. -Neposredno lahko naložite tudi celotno datoteko PHP, ki lahko vsebuje poljubno število razredov, funkcij ali celo več imenskih prostorov: +Lahko tudi naložite neposredno celotno PHP datoteko, ki lahko vsebuje poljubno število razredov, funkcij ali celo imenskih prostorov: ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` -Prav tako se naložita začetni komentar datoteke in deklaracija `strict_types`. Po drugi strani pa se vsa druga globalna koda ne upošteva. +Naloži se tudi uvodni komentar k datoteki in deklaracija `strict_types`. Nasprotno pa je vsa ostala globalna koda prezrta. -Za to je treba namestiti `nikic/php-parser`. +Zahteva se, da je nameščen `nikic/php-parser`. .[note] -Če morate upravljati globalno kodo v datotekah ali posamezne stavke v telesih metod, je bolje, da neposredno uporabite knjižnico `nikic/php-parser`. +Če morate manipulirati z globalno kodo v datotekah ali s posameznimi stavki v telesih metod, je bolje uporabiti neposredno knjižnico `nikic/php-parser`. + + +Class Manipulator +----------------- + +Razred [ClassManipulator|api:Nette\PhpGenerator\ClassManipulator] ponuja orodja za manipulacijo z razredi. + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$manipulator = new Nette\PhpGenerator\ClassManipulator($class); +``` + +Metoda `inheritMethod()` kopira metodo iz starševskega razreda ali implementiranega vmesnika v vaš razred. To vam omogoča, da prepišete metodo ali razširite njeno signaturo: + +```php +$method = $manipulator->inheritMethod('bar'); +$method->setBody('...'); +``` + +Metoda `inheritProperty()` kopira lastnost iz starševskega razreda v vaš razred. To je uporabno, ko želite v svojem razredu imeti enako lastnost, vendar morda z drugačno privzeto vrednostjo: + +```php +$property = $manipulator->inheritProperty('foo'); +$property->setValue('nova vrednost'); +``` +Metoda `implement()` samodejno implementira vse metode in lastnosti iz danega vmesnika ali abstraktnega razreda v vašem razredu: + +```php +$manipulator->implement(SomeInterface::class); +// Zdaj vaš razred implementira SomeInterface in vsebuje vse njegove metode +``` -Zbiralnik spremenljivk .[#toc-variables-dumper] ------------------------------------------------ -Dumper vrne razčlenljiv niz PHP za predstavitev spremenljivke. Zagotavlja boljši in jasnejši izpis kot nativna funkcija `var_export()`. +Izpis spremenljivk +------------------ + +Razred `Dumper` pretvori spremenljivko v razčlenljivo PHP kodo. Zagotavlja boljši in preglednejši izpis kot standardna funkcija `var_export()`. ```php $dumper = new Nette\PhpGenerator\Dumper; $var = ['a', 'b', 123]; -echo $dumper->dump($var); // natisne ['a', 'b', 123] +echo $dumper->dump($var); // izpiše ['a', 'b', 123] ``` -Preglednica združljivosti .[#toc-compatibility-table] ------------------------------------------------------ - -PhpGenerator 4.0 je združljiv s PHP 8.0 do 8.2 +Tabela združljivosti +-------------------- -{{leftbar: nette:@menu-topics}} +PhpGenerator 4.1 je združljiv s PHP 8.0 do 8.4. diff --git a/php-generator/sl/@meta.texy b/php-generator/sl/@meta.texy new file mode 100644 index 0000000000..282883a3d6 --- /dev/null +++ b/php-generator/sl/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Dokumentacija}} +{{leftbar: nette:@menu-topics}} diff --git a/php-generator/tr/@home.texy b/php-generator/tr/@home.texy index 8c91de5a48..7d60cc0e74 100644 --- a/php-generator/tr/@home.texy +++ b/php-generator/tr/@home.texy @@ -1,31 +1,32 @@ -PHP Kod Oluşturucu +Nette PhpGenerator ****************** -<div class=perex> -- Sınıflar, fonksiyonlar, PHP dosyaları vb. için PHP kodu oluşturmanız mı gerekiyor? -- Enumlar, nitelikler vb. gibi en son PHP özelliklerini destekler. +<div class="perex"> +Sınıflar, fonksiyonlar veya tam dosyalar için PHP kodu oluşturmak için bir araç mı arıyorsunuz? + +- PHP'deki en son yeniliklerin tümünü destekler (property hooks, enumlar, nitelikler vb.) - Mevcut sınıfları kolayca değiştirmenizi sağlar -- PSR-12 uyumlu çıkış -- Son derece olgun, istikrarlı ve yaygın olarak kullanılan kütüphane +- Çıktı kodu PSR-12 / PER kodlama stili ile uyumludur +- Olgun, kararlı ve yaygın olarak kullanılan bir kütüphane </div> -Kurulum .[#toc-installation] ----------------------------- +Kurulum +------- -[Composer'ı |best-practices:composer] kullanarak paketi indirin ve yükleyin: +Kütüphaneyi [Composer|best-practices:composer] aracını kullanarak indirip kurun: ```shell composer require nette/php-generator ``` -PHP uyumluluğu için [tabloya |#Compatibility Table] bakın. +PHP uyumluluğunu [tabloda |#Uyumluluk Tablosu] bulabilirsiniz. -Sınıflar .[#toc-classes] ------------------------- +Sınıflar +-------- -[ClassType |api:Nette\PhpGenerator\ClassType] kullanarak sınıf oluşturmanın basit bir örneği ile başlayalım: +Doğrudan [ClassType |api:Nette\PhpGenerator\ClassType] kullanarak bir sınıf oluşturma örneğiyle başlayalım: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -34,19 +35,19 @@ $class ->setFinal() ->setExtends(ParentClass::class) ->addImplement(Countable::class) - ->addComment("Description of class.\nSecond line\n") + ->addComment("Sınıf açıklaması.\nİkinci satır\n") ->addComment('@property-read Nette\Forms\Form $form'); -// PHP kodu oluşturmak için sadece dizgiye dönüştürün veya echo kullanın: +// kodu basitçe bir dizeye dönüştürerek veya echo kullanarak oluşturun: echo $class; ``` -Bu sonucu verecektir: +Aşağıdaki sonucu döndürür: ```php /** - * Description of class. - * Second line + * Sınıf açıklaması + * İkinci satır * * @property-read Nette\Forms\Form $form */ @@ -55,18 +56,18 @@ final class Demo extends ParentClass implements Countable } ``` -Kodu oluşturmak için `echo $class` adresinden farklı olarak [daha fazla yapılandırabileceğimiz |#Printers and PSR compliance] bir yazıcı da kullanabiliriz: +Kodu oluşturmak için, `echo $class`'tan farklı olarak [daha fazla yapılandırabileceğimiz |#Yazıcı ve PSR Uyumluluğu] sözde bir yazıcı (printer) da kullanabiliriz: ```php $printer = new Nette\PhpGenerator\Printer; echo $printer->printClass($class); ``` -Sabitleri ( [Constant |api:Nette\PhpGenerator\Constant] sınıfı) ve özellikleri ( [Property |api:Nette\PhpGenerator\Property] sınıfı) ekleyebiliriz: +Sabitler ([Constant |api:Nette\PhpGenerator\Constant] sınıfı) ve değişkenler ([Property |api:Nette\PhpGenerator\Property] sınıfı) ekleyebiliriz: ```php $class->addConstant('ID', 123) - ->setProtected() // sabit görünürlük + ->setProtected() // sabitlerin görünürlüğü ->setType('int') ->setFinal(); @@ -80,7 +81,7 @@ $class->addProperty('list') ->setInitialized(); // '= null' yazdırır ``` -Üretiyor: +Oluşturur: ```php final protected const int ID = 123; @@ -91,14 +92,14 @@ private static $items = [1, 2, 3]; public ?array $list = null; ``` -Ve [yöntemler |#Method and Function Signature] ekleyebiliriz: +Ve [metotları |#Metot ve Fonksiyon İmzaları] ekleyebiliriz: ```php $method = $class->addMethod('count') - ->addComment('Count it.') + ->addComment('Say.') ->setFinal() ->setProtected() - ->setReturnType('?int') // yöntem dönüş türü + ->setReturnType('?int') // metotlarda dönüş türleri ->setBody('return count($items ?: $this->items);'); $method->addParameter('items', []) // $items = [] @@ -106,11 +107,11 @@ $method->addParameter('items', []) // $items = [] ->setType('array'); // array &$items = [] ``` -Sonuç olarak: +Sonuç: ```php /** - * Count it. + * Say. */ final protected function count(array &$items = []): ?int { @@ -118,7 +119,7 @@ final protected function count(array &$items = []): ?int } ``` -PHP 8.0 ile tanıtılan tanıtılan değiştirgeler kurucuya aktarılabilir: +PHP 8.0 tarafından tanıtılan tanıtılmış parametreler yapıcıya iletilebilir: ```php $method = $class->addMethod('__construct'); @@ -127,7 +128,7 @@ $method->addPromotedParameter('args', []) ->setPrivate(); ``` -Sonuç olarak: +Sonuç: ```php public function __construct( @@ -137,15 +138,15 @@ public function __construct( } ``` -Readonly özellikler ve sınıflar `setReadOnly()` aracılığıyla işaretlenebilir. +Salt okunur özellikler ve sınıflar `setReadOnly()` fonksiyonu kullanılarak işaretlenebilir. ------ -Eklenen özellik, sabit, yöntem veya parametre zaten mevcutsa, istisna atar. +Eklenen özellik, sabit, metot veya parametre zaten mevcutsa, bir istisna fırlatılır. -Üyeler `removeProperty()`, `removeConstant()`, `removeMethod()` veya `removeParameter()` adresleri kullanılarak çıkarılabilir. +Sınıf üyeleri `removeProperty()`, `removeConstant()`, `removeMethod()` veya `removeParameter()` kullanılarak kaldırılabilir. -Ayrıca mevcut `Method`, `Property` veya `Constant` nesnelerini de sınıfa ekleyebilirsiniz: +Sınıfa mevcut `Method`, `Property` veya `Constant` nesnelerini de ekleyebilirsiniz: ```php $method = new Nette\PhpGenerator\Method('getHandle'); @@ -158,7 +159,7 @@ $class = (new Nette\PhpGenerator\ClassType('Demo')) ->addMember($const); ``` -Mevcut yöntemleri, özellikleri ve sabitleri `cloneWithName()` adresini kullanarak farklı bir adla klonlayabilirsiniz: +Ayrıca mevcut metotları, özellikleri ve sabitleri `cloneWithName()` kullanarak farklı bir ad altında klonlayabilirsiniz: ```php $methodCount = $class->getMethod('count'); @@ -167,17 +168,17 @@ $class->addMember($methodRecount); ``` -Arayüz veya Özellik .[#toc-interface-or-trait] ----------------------------------------------- +Arayüz veya Trait +----------------- -Arayüzler ve özellikler oluşturabilirsiniz ( [InterfaceType |api:Nette\PhpGenerator\InterfaceType] ve [TraitType |api:Nette\PhpGenerator\TraitType] sınıfları): +Arayüzler ve trait'ler oluşturabilirsiniz ([InterfaceType |api:Nette\PhpGenerator\InterfaceType] ve [TraitType |api:Nette\PhpGenerator\TraitType] sınıfları): ```php $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait'); ``` -Özellikleri kullanma: +Trait kullanma: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -202,10 +203,10 @@ class Demo ``` -Enumlar .[#toc-enums] ---------------------- +Enumlar +------- -PHP 8.1'in getirdiği [enumları (EnumType |api:Nette\PhpGenerator\EnumType] sınıfı) kolayca oluşturabilirsiniz: +PHP 8.1'in getirdiği enumları şu şekilde kolayca oluşturabilirsiniz: ([EnumType |api:Nette\PhpGenerator\EnumType] sınıfı): ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -229,20 +230,20 @@ enum Suit } ``` -Destekli bir enum oluşturmak için durumlar için skaler eşdeğerler de tanımlayabilirsiniz: +Ayrıca skaler eşdeğerleri tanımlayabilir ve böylece "backed" bir enum oluşturabilirsiniz: ```php $enum->addCase('Clubs', '♣'); $enum->addCase('Diamonds', '♦'); ``` -`addComment()` veya `addAttribute()` adreslerini kullanarak her bir vakaya bir yorum veya [nitelik |#attributes] eklemek mümkündür. +Her *case*'e `addComment()` veya `addAttribute()` kullanarak bir yorum veya [#nitelikler] eklemek mümkündür. -Anonim Sınıf .[#toc-anonymous-class] ------------------------------------- +Anonim Sınıflar +--------------- -İsim olarak `null` verin ve anonim bir sınıfınız olsun: +Ad olarak `null` iletiriz ve anonim bir sınıfımız olur: ```php $class = new Nette\PhpGenerator\ClassType(null); @@ -264,10 +265,10 @@ $obj = new class ($val) { ``` -Küresel İşlev .[#toc-global-function] -------------------------------------- +Genel Fonksiyonlar +------------------ -Fonksiyonların kodu [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction] sınıfını oluşturacaktır: +Fonksiyonların kodunu [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction] sınıfı oluşturur: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -276,7 +277,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// veya PSR-2 / PSR-12'ye uygun çıktı için PsrPrinter kullanın +// veya PSR-2 / PSR-12 / PER uyumlu çıktı için PsrPrinter kullanın // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -290,10 +291,10 @@ function foo($a, $b) ``` -Kapanış .[#toc-closure] ------------------------ +Anonim Fonksiyonlar +------------------- -Kapanışların kodu [Closure |api:Nette\PhpGenerator\Closure] sınıfını oluşturacaktır: +Anonim fonksiyonların kodunu [Closure |api:Nette\PhpGenerator\Closure] sınıfı oluşturur: ```php $closure = new Nette\PhpGenerator\Closure; @@ -304,7 +305,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// veya PSR-2 / PSR-12'ye uygun çıktı için PsrPrinter kullanın +// veya PSR-2 / PSR-12 / PER uyumlu çıktı için PsrPrinter kullanın // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -317,10 +318,10 @@ function ($a, $b) use (&$c) { ``` -Ok Fonksiyonu .[#toc-arrow-function] ------------------------------------- +Kısaltılmış Ok Fonksiyonları +---------------------------- -Kapanışı yazıcı kullanarak ok işlevi olarak da yazdırabilirsiniz: +Ayrıca bir yazıcı kullanarak kısaltılmış bir anonim fonksiyon yazdırabilirsiniz: ```php $closure = new Nette\PhpGenerator\Closure; @@ -338,38 +339,38 @@ fn($a, $b) => $a + $b ``` -Yöntem ve İşlev İmzası .[#toc-method-and-function-signature] ------------------------------------------------------------- +Metot ve Fonksiyon İmzaları +--------------------------- -Metotlar [Metot |api:Nette\PhpGenerator\Method] sınıfı tarafından temsil edilir. Görünürlüğü, dönüş değerini ayarlayabilir, yorumlar, [nitelikler |#Attributes] vb. ekleyebilirsiniz: +Metotları [Method |api:Nette\PhpGenerator\Method] sınıfı temsil eder. Görünürlüğü, dönüş değerini ayarlayabilir, yorumlar, [#nitelikler] vb. ekleyebilirsiniz: ```php $method = $class->addMethod('count') - ->addComment('Count it.') + ->addComment('Say.') ->setFinal() ->setProtected() ->setReturnType('?int'); ``` -Her parametre bir [Parameter |api:Nette\PhpGenerator\Parameter] sınıfı ile temsil edilir. Yine, akla gelebilecek her özelliği ayarlayabilirsiniz: +Bireysel parametreleri [Parameter |api:Nette\PhpGenerator\Parameter] sınıfı temsil eder. Yine, akla gelebilecek tüm özellikleri ayarlayabilirsiniz: ```php $method->addParameter('items', []) // $items = [] - ->setReference() // &$items = [] - ->setType('array'); // array &$items = [] + ->setReference() // &$items = [] + ->setType('array'); // array &$items = [] // function count(&$items = []) ``` -Variadics parametrelerini (veya splat, spread, ellipsis, unpacking veya three dots operatörlerini) tanımlamak için `setVariadics()` adresini kullanın: +Sözde variadics parametreleri (veya splat operatörü) tanımlamak için `setVariadic()` kullanılır: ```php $method = $class->addMethod('count'); -$method->setVariadics(true); +$method->setVariadic(true); $method->addParameter('items'); ``` -Üretir: +Oluşturur: ```php function count(...$items) @@ -378,10 +379,10 @@ function count(...$items) ``` -Yöntem ve İşlev Gövde .[#toc-method-and-function-body] ------------------------------------------------------- +Metot ve Fonksiyon Gövdeleri +---------------------------- -Gövde `setBody()` yöntemine bir kerede veya `addBody()` adresini tekrar tekrar çağırarak sırayla (satır satır) aktarılabilir: +Gövde, `setBody()` metoduna bir kerede iletilebilir veya `addBody()`'nin tekrarlanan çağrılarıyla (satır satır) kademeli olarak iletilebilir: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -400,7 +401,7 @@ function foo() } ``` -Değişkenleri enjekte etmenin kullanışlı bir yolu için özel yer tutucular kullanabilirsiniz. +Değişkenleri kolayca eklemek için özel yer tutucular kullanabilirsiniz. Basit yer tutucular `?` @@ -412,7 +413,7 @@ $function->addBody('return substr(?, ?);', [$str, $num]); echo $function; ``` -Sonuç: +Sonuç ```php function foo() @@ -421,7 +422,7 @@ function foo() } ``` -Varyadik yer tutucu `...?` +Variadic yer tutucu `...?` ```php $items = [1, 2, 3]; @@ -439,7 +440,7 @@ function foo() } ``` -Yer tutucu kullanarak PHP 8 isimli parametreleri de kullanabilirsiniz `...?:` +PHP 8 için adlandırılmış parametreleri `...?:` kullanarak da kullanabilirsiniz: ```php $items = ['foo' => 1, 'bar' => true]; @@ -448,7 +449,7 @@ $function->setBody('myfunc(...?:);', [$items]); // myfunc(foo: 1, bar: true); ``` -Eğik çizgi kullanarak yer tutucudan kaçış `\?` +Yer tutucu, ters eğik çizgi `\?` ile kaçılır ```php $num = 3; @@ -468,56 +469,81 @@ function foo($a) ``` -Yazıcılar ve PSR Uyumluluğu .[#toc-printers-and-psr-compliance] ---------------------------------------------------------------- +Yazıcı ve PSR Uyumluluğu +------------------------ -PHP kodu `Printer` nesneleri tarafından üretilir. Çıktısı PSR-2 ve PSR-12'ye uygun olan ve girinti için boşluk kullanan bir `PsrPrinter` ve girinti için sekme kullanan bir `Printer` vardır. +PHP kodu oluşturmak için [Printer |api:Nette\PhpGenerator\Printer] sınıfı kullanılır: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // şununla aynı: echo $class +``` + +Diğer tüm öğelerin kodunu oluşturabilir, `printFunction()`, `printNamespace()` vb. gibi metotlar sunar. + +Ayrıca, çıktısı PSR-2 / PSR-12 / PER kodlama stili ile uyumlu olan `PsrPrinter` sınıfı da mevcuttur: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // 4 boşluk girinti +echo $printer->printClass($class); ``` -Yazıcı davranışını özelleştirmeniz mi gerekiyor? `Printer` sınıfını miras alarak kendi sınıfınızı oluşturun. Bu değişkenleri yeniden yapılandırabilirsiniz: +Davranışı özel ihtiyaçlarınıza göre ayarlamanız mı gerekiyor? `Printer` sınıfından miras alarak kendi sürümünüzü oluşturun. Bu değişkenler yeniden yapılandırılabilir: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // satır kaydırmanın gerçekleştiği satır uzunluğu public int $wrapLength = 120; + // girinti karakteri, boşluk dizisiyle değiştirilebilir public string $indentation = "\t"; + // özellikler arasındaki boş satır sayısı public int $linesBetweenProperties = 0; + // metotlar arasındaki boş satır sayısı public int $linesBetweenMethods = 2; + // sınıflar, fonksiyonlar ve sabitler için 'use ifadeleri' grupları arasındaki boş satır sayısı public int $linesBetweenUseTypes = 0; + // fonksiyonlar ve metotlar için açılış küme parantezinin konumu public bool $bracesOnNextLine = true; + // bir niteliği olsa veya desteklense bile tek bir parametreyi tek bir satıra yerleştirin + public bool $singleParameterOnOneLine = false; + // herhangi bir sınıf veya fonksiyon içermeyen ad alanlarını atlar + public bool $omitEmptyNamespaces = true; + // fonksiyonların ve metotların sağ parantezi ile dönüş türü arasındaki ayırıcı public string $returnTypeColon = ': '; } ``` +Standart `Printer` ve `PsrPrinter` tam olarak nasıl ve neden farklıdır? Neden pakette yalnızca bir yazıcı, yani `PsrPrinter` yok? -Türleri .[#toc-types] ---------------------- +Standart `Printer`, kodu tüm Nette'de kullandığımız şekilde biçimlendirir. Nette'nin PSR'den çok daha önce ortaya çıkması ve PSR'nin yıllarca standartları zamanında değil, örneğin PHP'de yeni bir özelliğin tanıtılmasından ancak birkaç yıl sonra sunması nedeniyle, [kodlama standardı |contributing:coding-standard] birkaç küçük ayrıntıda farklılık gösterir. En büyük fark, boşluklar yerine sekmelerin kullanılmasıdır. Projelerimizde sekmeler kullanarak, [görme engelli kişiler için gerekli |contributing:coding-standard#Boşluklar yerine sekmeler] olan genişlik ayarlamasına izin verdiğimizi biliyoruz. Küçük bir farklılık örneği, fonksiyonlarda ve metotlarda açılış küme parantezinin her zaman ayrı bir satıra yerleştirilmesidir. PSR tavsiyesi bize mantıksız görünüyor ve [kod okunabilirliğinin azalmasına |contributing:coding-standard#Sarma ve Parantezler] yol açıyor. + + +Tipler +------ -Her tür veya birlik/kesişim türü bir dize olarak geçirilebilir, ayrıca yerel türler için önceden tanımlanmış sabitleri de kullanabilirsiniz: +Herhangi bir tür veya union/intersection türü bir dize olarak iletilebilir, ayrıca yerel türler için önceden tanımlanmış sabitleri de kullanabilirsiniz: ```php use Nette\PhpGenerator\Type; $member->setType('array'); // veya Type::Array; -$member->setType('array|string'); // veya Type::union('array', 'string') +$member->setType('?array'); // veya Type::nullable(Type::Array); +$member->setType('array|string'); // veya Type::union(Type::Array, Type::String) $member->setType('Foo&Bar'); // veya Type::intersection(Foo::class, Bar::class) $member->setType(null); // türü kaldırır ``` -Aynı durum `setReturnType()` yöntemi için de geçerlidir. +Aynısı `setReturnType()` metodu için de geçerlidir. -Gerçekler .[#toc-literals] --------------------------- +Değişmezler +----------- -`Literal` ile, örneğin varsayılan özellik veya parametre değerleri vb. için rastgele PHP kodu aktarabilirsiniz: +`Literal` kullanarak herhangi bir PHP kodunu iletebilirsiniz, örneğin özelliklerin veya parametrelerin varsayılan değerleri için vb: ```php use Nette\PhpGenerator\Literal; @@ -545,25 +571,37 @@ class Demo } ``` -Ayrıca `Literal` adresine parametre aktarabilir ve [özel yer tutucular |#method-and-function-body-generator] kullanarak geçerli PHP kodu olarak biçimlendirilmesini sağlayabilirsiniz: +Ayrıca `Literal`'a parametreler iletebilir ve bunların [yer tutucuları |#Metot ve Fonksiyon Gövdeleri] kullanarak geçerli PHP koduna biçimlendirilmesini sağlayabilirsiniz: ```php new Literal('substr(?, ?)', [$a, $b]); -// oluşturur, örneğin: substr('hello', 5); +// örneğin oluşturur: substr('hello', 5); ``` +Yeni bir nesnenin oluşturulmasını temsil eden bir değişmez, `new` metodu kullanılarak kolayca oluşturulabilir: + +```php +Literal::new(Demo::class, [$a, 'foo' => $b]); +// örneğin oluşturur: new Demo(10, foo: 20) +``` -Nitelikler .[#toc-attributes] ------------------------------ -PHP 8 özniteliklerini tüm sınıflara, yöntemlere, özelliklere, sabitlere, enum durumlarına, işlevlere, kapanışlara ve değiştirgelere ekleyebilirsiniz. Değiştirge değeri olarak [değişmezler |#Literals] de kullanılabilir. +Nitelikler +---------- + +PHP 8 niteliklerini tüm sınıflara, metotlara, özelliklere, sabitlere, enumlara, fonksiyonlara, closure'lara ve parametrelere ekleyebilirsiniz. Parametre değerleri olarak [#değişmezler] de kullanılabilir. ```php $class = new Nette\PhpGenerator\ClassType('Demo'); -$class->addAttribute('Deprecated'); +$class->addAttribute('Table', [ + 'name' => 'user', + 'constraints' => [ + Literal::new('UniqueConstraint', ['name' => 'ean', 'columns' => ['ean']]), + ], +]); $class->addProperty('list') - ->addAttribute('WithArguments', [1, 2]); + ->addAttribute('Deprecated'); $method = $class->addMethod('count') ->addAttribute('Foo\Cached', ['mode' => true]); @@ -577,42 +615,126 @@ echo $class; Sonuç: ```php -#[Deprecated] +#[Table(name: 'user', constraints: [new UniqueConstraint(name: 'ean', columns: ['ean'])])] class Demo { - #[WithArguments(1, 2)] + #[Deprecated] public $list; #[Foo\Cached(mode: true)] - public function count(#[Bar] $items) - { + public function count( + #[Bar] + $items, + ) { } } ``` -İsim Alanı .[#toc-namespace] ----------------------------- +Property Hooks +-------------- + +Property hooks ([PropertyHook|api:Nette\PhpGenerator\PropertyHook] sınıfı tarafından temsil edilir) kullanarak, PHP 8.4'te tanıtılan bir özellik olan özellikler için get ve set işlemlerini tanımlayabilirsiniz: + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$prop = $class->addProperty('firstName') + ->setType('string'); + +$prop->addHook('set', 'strtolower($value)') + ->addParameter('value') + ->setType('string'); + +$prop->addHook('get') + ->setBody('return ucfirst($this->firstName);'); -Sınıflar, özellikler, arayüzler ve enumlar (bundan sonra sınıflar olarak anılacaktır) isim alanları ([PhpNamespace |api:Nette\PhpGenerator\PhpNamespace]) halinde gruplandırılabilir: +echo $class; +``` + +Oluşturur: + +```php +class Demo +{ + public string $firstName { + set(string $value) => strtolower($value); + get { + return ucfirst($this->firstName); + } + } +} +``` + +Özellikler ve property hooks soyut veya final olabilir: + +```php +$class->addProperty('id') + ->setType('int') + ->addHook('get') + ->setAbstract(); + +$class->addProperty('role') + ->setType('string') + ->addHook('set', 'strtolower($value)') + ->setFinal(); +``` + + +Asimetrik Görünürlük +-------------------- + +PHP 8.4, özellikler için asimetrik görünürlük sunar. Okuma ve yazma için farklı erişim seviyeleri ayarlayabilirsiniz. + +Görünürlük, iki parametreli `setVisibility()` metoduyla veya görünürlüğün özelliğin okunmasına mı yoksa yazılmasına mı uygulanacağını belirten `mode` parametresiyle `setPublic()`, `setProtected()` veya `setPrivate()` kullanılarak ayarlanabilir. Varsayılan mod `'get'`'tir. + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); + +$class->addProperty('name') + ->setType('string') + ->setVisibility('public', 'private'); // okuma için public, yazma için private + +$class->addProperty('id') + ->setType('int') + ->setProtected('set'); // yazma için protected + +echo $class; +``` + +Oluşturur: + +```php +class Demo +{ + public private(set) string $name; + + protected(set) int $id; +} +``` + + +Ad Alanı +-------- + +Sınıflar, özellikler, arayüzler ve enumlar (bundan sonra sınıflar olarak anılacaktır), [PhpNamespace |api:Nette\PhpGenerator\PhpNamespace] sınıfı tarafından temsil edilen ad alanlarında gruplandırılabilir: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); -// isim alanında yeni sınıflar oluşturun +// ad alanında yeni sınıflar oluşturun $class = $namespace->addClass('Task'); $interface = $namespace->addInterface('Countable'); $trait = $namespace->addTrait('NameAware'); -// veya mevcut bir sınıfı ad alanına ekleyebilirsiniz +// veya mevcut bir sınıfı ad alanına ekleyin $class = new Nette\PhpGenerator\ClassType('Task'); $namespace->add($class); ``` -Sınıf zaten mevcutsa, istisna atar. +Sınıf zaten mevcutsa, bir istisna fırlatılır. -Kullanım ifadelerini tanımlayabilirsiniz: +Use yan tümceleri tanımlayabilirsiniz: ```php // use Http\Request; @@ -623,14 +745,14 @@ $namespace->addUse(Http\Request::class, 'HttpReq'); $namespace->addUseFunction('iter\range'); ``` -Tam nitelikli bir sınıf, fonksiyon veya sabit adını tanımlanan takma adlara göre basitleştirmek için `simplifyName` yöntemini kullanın: +Tanımlanan takma adlara göre tam nitelikli bir sınıf, fonksiyon veya sabit adını basitleştirmek için `simplifyName` metodunu kullanın: ```php -echo $namespace->simplifyName('Foo\Bar'); // 'Bar', çünkü 'Foo' geçerli ad alanıdır -echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', tanımlanan kullanım ifadesi nedeniyle +echo $namespace->simplifyName('Foo\Bar'); // 'Bar', çünkü 'Foo' mevcut ad alanıdır +echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', tanımlanan use ifadesi nedeniyle ``` -Tersine, `resolveName` yöntemini kullanarak basitleştirilmiş bir sınıf, fonksiyon veya sabit adını tam nitelikli bir ada dönüştürebilirsiniz: +Tersine, basitleştirilmiş bir sınıf, fonksiyon veya sabit adını `resolveName` metodunu kullanarak tam nitelikli bir ada dönüştürebilirsiniz: ```php echo $namespace->resolveName('Bar'); // 'Foo\Bar' @@ -638,29 +760,27 @@ echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range' ``` -Sınıf Adları Çözülüyor .[#toc-class-names-resolving] ----------------------------------------------------- +Sınıf Adı Çözümlemesi +--------------------- -**Sınıf isim alanının bir parçası olduğunda, biraz farklı şekilde işlenir**: tüm tipler (yani tip ipuçları, dönüş tipleri, ana sınıf adı, -uygulanan arayüzler, kullanılan özellikler ve nitelikler) otomatik olarak *çözülür* (kapatmadığınız sürece, aşağıya bakın). -Bu, tanımlarda **tam sınıf adlarını** kullanmanız gerektiği ve sonuçta ortaya çıkan kodda bunların takma adlarla (kullanım ifadelerine göre) veya tam nitelikli adlarla değiştirileceği anlamına gelir: +**Bir sınıf bir ad alanının parçası olduğunda, biraz farklı oluşturulur:** tüm türler (örneğin, tür ipuçları, dönüş türleri, üst sınıf adı, uygulanan arayüzler, kullanılan özellikler ve nitelikler) otomatik olarak *çözümlenir* (bunu kapatmazsanız, aşağıya bakın). Bu, tanımlarda **tam sınıf adlarını kullanmanız gerektiği** ve bunların sonuç kodunda takma adlarla (use yan tümcelerine göre) veya tam nitelikli adlarla değiştirileceği anlamına gelir: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); $namespace->addUse('Bar\AliasedClass'); $class = $namespace->addClass('Demo'); -$class->addImplement('Foo\A') // A'ya basitleştirilecektir - ->addTrait('Bar\AliasedClass'); // AliasedClass olarak basitleştirilecektir +$class->addImplement('Foo\A') // A olarak basitleştirilecek + ->addTrait('Bar\AliasedClass'); // AliasedClass olarak basitleştirilecek $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // yorumlarda manuel olarak basitleştirin +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // yorumlarda manuel olarak basitleştiriyoruz $method->addParameter('arg') - ->setType('Bar\OtherClass'); // \Bar\OtherClass olarak çözümlenecektir + ->setType('Bar\OtherClass'); // \Bar\OtherClass olarak çözümlenecek echo $namespace; -// veya PSR-2 / PSR-12'ye uygun çıktı için PsrPrinter kullanın +// veya PSR-2 / PSR-12 / PER uyumlu çıktı için PsrPrinter kullanın // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -684,7 +804,7 @@ class Demo implements A } ``` -Otomatik çözümleme bu şekilde kapatılabilir: +Otomatik çözümleme şu şekilde kapatılabilir: ```php $printer = new Nette\PhpGenerator\Printer; // veya PsrPrinter @@ -693,14 +813,14 @@ echo $printer->printNamespace($namespace); ``` -PHP Dosyaları .[#toc-php-files] -------------------------------- +PHP Dosyaları +------------- -Sınıflar, işlevler ve isim alanları [PhpFile |api:Nette\PhpGenerator\PhpFile] sınıfı tarafından temsil edilen PHP dosyaları içinde gruplandırılabilir: +Sınıflar, fonksiyonlar ve ad alanları, [PhpFile|api:Nette\PhpGenerator\PhpFile] sınıfı tarafından temsil edilen PHP dosyalarında gruplandırılabilir: ```php $file = new Nette\PhpGenerator\PhpFile; -$file->addComment('This file is auto-generated.'); +$file->addComment('Bu dosya otomatik olarak oluşturulmuştur.'); $file->setStrictTypes(); // declare(strict_types=1) ekler $class = $file->addClass('Foo\A'); @@ -713,7 +833,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// veya PSR-2 / PSR-12'ye uygun çıktı için PsrPrinter kullanın +// veya PSR-2 / PSR-12 / PER uyumlu çıktı için PsrPrinter kullanın // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -723,7 +843,7 @@ Sonuç: <?php /** - * This file is auto-generated. + * Bu dosya otomatik olarak oluşturulmuştur. */ declare(strict_types=1); @@ -739,27 +859,28 @@ function foo() } ``` +**Uyarı:** Dosyalara fonksiyonlar ve sınıflar dışında başka kod eklemek mümkün değildir. + -Mevcut Olanlara Göre Üretme .[#toc-generating-according-to-existing-ones] -------------------------------------------------------------------------- +Mevcut Olanlara Göre Oluşturma +------------------------------ -Yukarıda açıklanan API'yi kullanarak sınıfları ve fonksiyonları modelleyebilmenin yanı sıra, mevcut olanları kullanarak bunların otomatik olarak oluşturulmasını da sağlayabilirsiniz: +Sınıfları ve fonksiyonları yukarıda açıklanan API kullanarak modellemenin yanı sıra, mevcut kalıplara göre otomatik olarak oluşturulmalarını da sağlayabilirsiniz: ```php -// PDO sınıfına benzer bir sınıf oluşturur +// PDO sınıfıyla aynı bir sınıf oluşturur $class = Nette\PhpGenerator\ClassType::from(PDO::class); -// trim() ile aynı işlevi oluşturur +// trim() fonksiyonuyla aynı bir fonksiyon oluşturur $function = Nette\PhpGenerator\GlobalFunction::from('trim'); -// belirtildiği gibi bir kapanış oluşturur +// belirtilene göre bir closure oluşturur $closure = Nette\PhpGenerator\Closure::from( function (stdClass $a, $b = null) {}, ); ``` -Fonksiyon ve metot gövdeleri varsayılan olarak boştur. Bunları da yüklemek istiyorsanız, şu yolu kullanın -( `nikic/php-parser` adresinin yüklenmesini gerektirir): +Fonksiyonların ve metotların gövdeleri varsayılan olarak boştur. Bunları da yüklemek istiyorsanız, bu yöntemi kullanın (`nikic/php-parser` paketinin yüklenmesini gerektirir): ```php $class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true); @@ -768,10 +889,10 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); ``` -PHP Dosyasından Yükleme .[#toc-loading-from-php-file] ------------------------------------------------------ +PHP Dosyalarından Yükleme +------------------------- -Fonksiyonları, sınıfları, arayüzleri ve enumları doğrudan bir PHP kodu dizisinden de yükleyebilirsiniz. Örneğin, `ClassType` nesnesini bu şekilde oluşturuyoruz: +Fonksiyonları, sınıfları, arayüzleri ve enumları doğrudan PHP kodu içeren bir dizeden de yükleyebilirsiniz. Örneğin, bir `ClassType` nesnesini şu şekilde oluştururuz: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -784,39 +905,69 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -PHP kodundan sınıflar yüklenirken, yöntem gövdelerinin dışındaki tek satırlık yorumlar yok sayılır (örneğin, özellikler vb. için) çünkü bu kütüphanenin bunlarla çalışmak için bir API'si yoktur. +PHP kodundan sınıfları yüklerken, metot gövdeleri dışındaki tek satırlık yorumlar (ör. özelliklerde vb.) yoksayılır, çünkü bu kütüphanenin bunlarla çalışmak için bir API'si yoktur. -Ayrıca, istediğiniz sayıda sınıf, işlev ve hatta birden fazla ad alanı içerebilen PHP dosyasının tamamını doğrudan yükleyebilirsiniz: +Ayrıca, herhangi bir sayıda sınıf, fonksiyon veya hatta ad alanı içerebilen tüm bir PHP dosyasını doğrudan yükleyebilirsiniz: ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` -İlk dosya yorumu ve `strict_types` bildirimi de yüklenir. Öte yandan, diğer tüm global kodlar yok sayılır. +Dosyanın başlangıç yorumu ve `strict_types` bildirimi de yüklenir. Tersine, diğer tüm genel kod yoksayılır. -Bunun için `nikic/php-parser` adresinin yüklenmesi gerekir. +`nikic/php-parser`'ın yüklenmesi gerekir. .[note] -Dosyalardaki genel kodu veya yöntem gövdelerindeki tek tek ifadeleri değiştirmeniz gerekiyorsa, `nikic/php-parser` kütüphanesini doğrudan kullanmak daha iyidir. +Dosyalardaki genel kodu veya metot gövdelerindeki bireysel deyimleri manipüle etmeniz gerekiyorsa, doğrudan `nikic/php-parser` kütüphanesini kullanmak daha iyidir. + + +Sınıf Manipülatörü +------------------ + +[ClassManipulator|api:Nette\PhpGenerator\ClassManipulator] sınıfı, sınıfları manipüle etmek için araçlar sağlar. +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$manipulator = new Nette\PhpGenerator\ClassManipulator($class); +``` -Değişkenler Damperi .[#toc-variables-dumper] --------------------------------------------- +`inheritMethod()` metodu, üst sınıftan veya uygulanan arayüzden bir metodu sınıfınıza kopyalar. Bu, metodu geçersiz kılmanıza veya imzasını genişletmenize olanak tanır: + +```php +$method = $manipulator->inheritMethod('bar'); +$method->setBody('...'); +``` -Dumper, bir değişkenin ayrıştırılabilir bir PHP dize gösterimini döndürür. Yerel işlevden daha iyi ve daha net çıktı sağlar `var_export()`. +`inheritProperty()` metodu, üst sınıftan bir özelliği sınıfınıza kopyalar. Sınıfınızda aynı özelliğe sahip olmak istediğinizde, ancak belki farklı bir varsayılan değerle yararlıdır: + +```php +$property = $manipulator->inheritProperty('foo'); +$property->setValue('new value'); +``` + +`implement()` metodu, verilen arayüzden veya soyut sınıftan tüm metotları ve özellikleri sınıfınızda otomatik olarak uygular: + +```php +$manipulator->implement(SomeInterface::class); +// Artık sınıfınız SomeInterface'i uygular ve tüm metotlarını içerir +``` + + +Değişken Dökümü +--------------- + +`Dumper` sınıfı, bir değişkeni ayrıştırılabilir PHP koduna dönüştürür. Standart `var_export()` fonksiyonundan daha iyi ve daha net bir çıktı sağlar. ```php $dumper = new Nette\PhpGenerator\Dumper; $var = ['a', 'b', 123]; -echo $dumper->dump($var); // prints ['a', 'b', 123] +echo $dumper->dump($var); // ['a', 'b', 123] yazdırır ``` -Uyumluluk Tablosu .[#toc-compatibility-table] ---------------------------------------------- - -PhpGenerator 4.0 PHP 8.0 ila 8.2 ile uyumludur +Uyumluluk Tablosu +----------------- -{{leftbar: nette:@menu-topics}} +PhpGenerator 4.1, PHP 8.0 ila 8.4 ile uyumludur. diff --git a/php-generator/tr/@meta.texy b/php-generator/tr/@meta.texy new file mode 100644 index 0000000000..e5c5cea355 --- /dev/null +++ b/php-generator/tr/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Dokümantasyonu}} +{{leftbar: nette:@menu-topics}} diff --git a/php-generator/uk/@home.texy b/php-generator/uk/@home.texy index f1b50fa884..5928f82ba7 100644 --- a/php-generator/uk/@home.texy +++ b/php-generator/uk/@home.texy @@ -1,31 +1,32 @@ -Генератор PHP коду +Nette PhpGenerator ****************** -<div class=perex> -- Потрібно згенерувати PHP-код для класів, функцій, PHP-файлів тощо? -- Підтримує всі найновіші можливості PHP, такі як перерахування, атрибути тощо. +<div class="perex"> +Шукаєте інструмент для генерації PHP коду класів, функцій чи повних файлів? + +- Підтримує всі найновіші можливості PHP (такі як property hooks, enum, атрибути тощо) - Дозволяє легко модифікувати існуючі класи -- Вихідні дані, сумісні з PSR-12 -- Досконала, стабільна та широко використовувана бібліотека +- Вихідний код відповідає стилю кодування PSR-12 / PER +- Зріла, стабільна та широко використовувана бібліотека </div> -Встановлення .[#toc-installation] ---------------------------------- +Встановлення +------------ -Завантажте та встановіть пакунок за допомогою [Composer |best-practices:composer]: +Завантажте та встановіть бібліотеку за допомогою [Composer|best-practices:composer]: ```shell composer require nette/php-generator ``` -Сумісність з PHP див. у [таблиці |#Compatibility Table]. +Сумісність з PHP можна знайти в [таблиці |#Таблиця сумісності]. -Класи .[#toc-classes] ---------------------- +Класи +----- -Почнемо з простого прикладу створення класу за допомогою [ClassType |api:Nette\PhpGenerator\ClassType]: +Почнемо одразу з прикладу створення класу за допомогою [ClassType |api:Nette\PhpGenerator\ClassType]: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -34,19 +35,19 @@ $class ->setFinal() ->setExtends(ParentClass::class) ->addImplement(Countable::class) - ->addComment("Description of class.\nSecond line\n") + ->addComment("Опис класу.\nДругий рядок\n") ->addComment('@property-read Nette\Forms\Form $form'); -// Щоб згенерувати PHP-код, просто перетворіть його в рядок або використовуйте echo: +// код легко згенерувати, перетворивши на рядок або використавши echo: echo $class; ``` -Він відобразить такий результат: +Повертає наступний результат: ```php /** - * Description of class. - * Second line + * Опис класу + * Другий рядок * * @property-read Nette\Forms\Form $form */ @@ -55,18 +56,18 @@ final class Demo extends ParentClass implements Countable } ``` -Ми також можемо використовувати принтер для генерації коду, який, на відміну від `echo $class`, ми зможемо [додатково налаштувати |#Printers and PSR compliance]: +Для генерації коду ми також можемо використати так званий printer, який, на відміну від `echo $class`, ми зможемо [далі конфігурувати |#Printer та відповідність PSR]: ```php $printer = new Nette\PhpGenerator\Printer; echo $printer->printClass($class); ``` -Ми можемо додавати константи (клас [Constant |api:Nette\PhpGenerator\Constant]) та властивості (клас [Property |api:Nette\PhpGenerator\Property]): +Ми можемо додати константи (клас [Constant |api:Nette\PhpGenerator\Constant]) та властивості (клас [Property |api:Nette\PhpGenerator\Property]): ```php $class->addConstant('ID', 123) - ->setProtected() // постійна видимість + ->setProtected() // видимість констант ->setType('int') ->setFinal(); @@ -77,10 +78,10 @@ $class->addProperty('items', [1, 2, 3]) $class->addProperty('list') ->setType('?array') - ->setInitialized(); // виводить '= null' + ->setInitialized(); // виведе '= null' ``` -Це генерує: +Згенерує: ```php final protected const int ID = 123; @@ -91,26 +92,26 @@ private static $items = [1, 2, 3]; public ?array $list = null; ``` -І ми можемо додавати [методи |#Method and Function Signature]: +І ми можемо додати [методи |#Сигнатури методів та функцій]: ```php $method = $class->addMethod('count') - ->addComment('Count it.') + ->addComment('Підрахувати.') ->setFinal() ->setProtected() - ->setReturnType('?int') // тип повернення методу + ->setReturnType('?int') // типи повернення у методів ->setBody('return count($items ?: $this->items);'); $method->addParameter('items', []) // $items = [] ->setReference() // &$items = [] - ->setType('array'); // масив &$items = [] + ->setType('array'); // array &$items = [] ``` -Це призводить до: +Результатом є: ```php /** - * Count it. + * Підрахувати. */ final protected function count(array &$items = []): ?int { @@ -118,7 +119,7 @@ final protected function count(array &$items = []): ?int } ``` -До конструктора можна передавати просунуті параметри, введені в PHP 8.0: +Пропаговані параметри, введені в PHP 8.0, можна передати конструктору: ```php $method = $class->addMethod('__construct'); @@ -127,7 +128,7 @@ $method->addPromotedParameter('args', []) ->setPrivate(); ``` -Це призводить до: +Результатом є: ```php public function __construct( @@ -137,15 +138,15 @@ public function __construct( } ``` -Властивості та класи, призначені лише для читання, можна позначити за допомогою `setReadOnly()`. +Властивості та класи, призначені лише для читання, можна позначити за допомогою функції `setReadOnly()`. ------ -Якщо додана властивість, константа, метод або параметр вже існують, вони згенерують виключення. +Якщо додана властивість, константа, метод або параметр вже існують, буде викинуто виняток. -Члени можуть бути видалені за допомогою `removeProperty()`, `removeConstant()`, `removeMethod()` або `removeParameter()`. +Члени класу можна видалити за допомогою `removeProperty()`, `removeConstant()`, `removeMethod()` або `removeParameter()`. -Ви також можете додати до класу існуючі об'єкти `Method`, `Property` або `Constant`: +До класу можна також додати існуючі об'єкти `Method`, `Property` або `Constant`: ```php $method = new Nette\PhpGenerator\Method('getHandle'); @@ -158,7 +159,7 @@ $class = (new Nette\PhpGenerator\ClassType('Demo')) ->addMember($const); ``` -Ви можете клонувати існуючі методи, властивості та константи з іншим іменем за допомогою `cloneWithName()`: +Ви також можете клонувати існуючі методи, властивості та константи під іншою назвою за допомогою `cloneWithName()`: ```php $methodCount = $class->getMethod('count'); @@ -167,17 +168,17 @@ $class->addMember($methodRecount); ``` -Інтерфейс або Властивість .[#toc-interface-or-trait] ----------------------------------------------------- +Інтерфейс або трейт +------------------- -Ви можете створювати інтерфейси та риси (класи [InterfaceType |api:Nette\PhpGenerator\InterfaceType] та [TraitType |api:Nette\PhpGenerator\TraitType]): +Ви можете створювати інтерфейси та трейти (класи [InterfaceType |api:Nette\PhpGenerator\InterfaceType] та [TraitType |api:Nette\PhpGenerator\TraitType]): ```php $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait'); ``` -Використовуючи риси: +Використання трейтів: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -194,7 +195,7 @@ echo $class; class Demo { use SmartObject; - /** @використовуйте MyTrait<Foo> */ + /** @use MyTrait<Foo> */ use MyTrait { sayHello as protected; } @@ -202,10 +203,10 @@ class Demo ``` -Перерахування .[#toc-enums] ---------------------------- +Enums +----- -Ви можете легко створювати зчислення, які з'явилися у PHP 8.1 (клас [EnumType |api:Nette\PhpGenerator\EnumType]): +Переліки (enums), які з'явилися в PHP 8.1, можна легко створити так: (клас [EnumType |api:Nette\PhpGenerator\EnumType]): ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -229,20 +230,20 @@ enum Suit } ``` -Ви також можете визначити скалярні еквіваленти для випадків, щоб створити підтримуваний зчислення: +Ви також можете визначити скалярні еквіваленти та створити таким чином "backed" перелік: ```php $enum->addCase('Clubs', '♣'); $enum->addCase('Diamonds', '♦'); ``` -Ви можете додати коментар або [атрибути |#attributes] до кожного випадку за допомогою `addComment()` або `addAttribute()`. +До кожного *case* можна додати коментар або [#атрибути] за допомогою `addComment()` або `addAttribute()`. -Анонімний клас .[#toc-anonymous-class] --------------------------------------- +Анонімні класи +-------------- -Введіть `null` як ім'я, і ви отримаєте анонімний клас: +Як назву передамо `null` і отримаємо анонімний клас: ```php $class = new Nette\PhpGenerator\ClassType(null); @@ -264,10 +265,10 @@ $obj = new class ($val) { ``` -Глобальна функція .[#toc-global-function] ------------------------------------------ +Глобальні функції +----------------- -Код функцій створить клас [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction]: +Код функцій генерує клас [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction]: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -276,7 +277,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// або використовуйте PsrPrinter для виводу відповідно до PSR-2 / PSR-12 +// або використовуйте PsrPrinter для виведення відповідно до PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -290,10 +291,10 @@ function foo($a, $b) ``` -Закриття .[#toc-closure] ------------------------- +Анонімні функції +---------------- -Код закриття згенерує клас [Closure |api:Nette\PhpGenerator\Closure]: +Код анонімних функцій генерує клас [Closure |api:Nette\PhpGenerator\Closure]: ```php $closure = new Nette\PhpGenerator\Closure; @@ -304,7 +305,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// або використовуйте PsrPrinter для виведення відповідно до PSR-2 / PSR-12 +// або використовуйте PsrPrinter для виведення відповідно до PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -317,10 +318,10 @@ function ($a, $b) use (&$c) { ``` -Функція стрілки .[#toc-arrow-function] --------------------------------------- +Скорочені стрілочні функції +--------------------------- -Ви також можете роздрукувати закриття як функцію стрілки за допомогою принтера: +Ви також можете вивести скорочену анонімну функцію за допомогою printer: ```php $closure = new Nette\PhpGenerator\Closure; @@ -338,38 +339,38 @@ fn($a, $b) => $a + $b ``` -Сигнатура методу та функції .[#toc-method-and-function-signature] ------------------------------------------------------------------ +Сигнатури методів та функцій +---------------------------- -Методи представлені класом [Method |api:Nette\PhpGenerator\Method]. Ви можете встановити видимість, значення, що повертається, додати коментарі, [атрибути |#Attributes] тощо: +Методи представляє клас [Method |api:Nette\PhpGenerator\Method]. Ви можете встановити видимість, тип повернення, додати коментарі, [#атрибути] тощо: ```php $method = $class->addMethod('count') - ->addComment('Count it.') + ->addComment('Підрахувати.') ->setFinal() ->setProtected() ->setReturnType('?int'); ``` -Кожен параметр представлений класом [Parameter |api:Nette\PhpGenerator\Parameter]. Знову ж таки, ви можете встановити всі можливі властивості: +Окремі параметри представляє клас [Parameter |api:Nette\PhpGenerator\Parameter]. Знову ж таки, ви можете встановити всі можливі властивості: ```php $method->addParameter('items', []) // $items = [] - ->setReference() // &$items = [] - ->setType('array'); // array &$items = [] + ->setReference() // &$items = [] + ->setType('array'); // array &$items = [] // function count(&$items = []) ``` -Для визначення так званих варіадичних параметрів (або також операторів splat, spread, еліпсис, розпакування або три крапки) використовуйте `setVariadics()`: +Для визначення так званих variadics параметрів (або також splat оператора) служить `setVariadic()`: ```php $method = $class->addMethod('count'); -$method->setVariadics(true); +$method->setVariadic(true); $method->addParameter('items'); ``` -Generates: +Згенерує: ```php function count(...$items) @@ -378,10 +379,10 @@ function count(...$items) ``` -Метод і тіло функції .[#toc-method-and-function-body] ------------------------------------------------------ +Тіла методів та функцій +----------------------- -Тіло можна передати в метод `setBody()` одразу або послідовно (рядок за рядком) шляхом багаторазового виклику `addBody()`: +Тіло можна передати одразу методом `setBody()` або поступово (по рядках) повторним викликом `addBody()`: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -400,9 +401,9 @@ function foo() } ``` -Ви можете використовувати спеціальні заповнювачі для зручного введення змінних. +Ви можете використовувати спеціальні плейсхолдери для легкого вставлення змінних. -Прості заповнювачі `?` +Прості плейсхолдери `?` ```php $str = 'any string'; @@ -412,7 +413,7 @@ $function->addBody('return substr(?, ?);', [$str, $num]); echo $function; ``` -Результат: +Результат ```php function foo() @@ -421,7 +422,7 @@ function foo() } ``` -Варіадний заповнювач `...?` +Плейсхолдер для variadic `...?` ```php $items = [1, 2, 3]; @@ -439,7 +440,7 @@ function foo() } ``` -Ви також можете використовувати іменовані параметри PHP 8, використовуючи заповнювач `...?:` +Ви також можете використовувати іменовані параметри для PHP 8 за допомогою `...?:` ```php $items = ['foo' => 1, 'bar' => true]; @@ -448,7 +449,7 @@ $function->setBody('myfunc(...?:);', [$items]); // myfunc(foo: 1, bar: true); ``` -Екранування заповнювача за допомогою косої риски `\?` +Плейсхолдер екранується за допомогою зворотної косої риски `\?` ```php $num = 3; @@ -468,56 +469,81 @@ function foo($a) ``` -Відповідність принтерів та PSR вимогам .[#toc-printers-and-psr-compliance] --------------------------------------------------------------------------- +Printer та відповідність PSR +---------------------------- -PHP-код генерується об'єктами `Printer`. Існує об'єкт `PsrPrinter`, вивід якого відповідає стандартам PSR-2 і PSR-12 і використовує пробіли для відступів, і об'єкт `Printer`, який використовує табуляцію для відступів. +Для генерації PHP коду служить клас [Printer |api:Nette\PhpGenerator\Printer]: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // те саме, що: echo $class +``` + +Він може генерувати код усіх інших елементів, пропонує методи, такі як `printFunction()`, `printNamespace()`, тощо. + +Доступний також клас `PsrPrinter`, вивід якого відповідає стилю кодування PSR-2 / PSR-12 / PER: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // Відступ 4 пробіли +echo $printer->printClass($class); ``` -Потрібно налаштувати поведінку принтера? Створіть власний, успадкувавши клас `Printer`. Ви можете переналаштувати ці змінні: +Потрібно налаштувати поведінку? Створіть власну версію, успадкувавши клас `Printer`. Можна переналаштувати ці змінні: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // довжина рядка, після якої відбувається перенесення рядка public int $wrapLength = 120; + // символ відступу, може бути замінений послідовністю пробілів public string $indentation = "\t"; + // кількість порожніх рядків між властивостями public int $linesBetweenProperties = 0; + // кількість порожніх рядків між методами public int $linesBetweenMethods = 2; + // кількість порожніх рядків між групами 'use statements' для класів, функцій та констант public int $linesBetweenUseTypes = 0; + // позиція відкриваючої фігурної дужки для функцій та методів public bool $bracesOnNextLine = true; + // розміщуйте один параметр на одному рядку, навіть якщо він має атрибут або підтримується + public bool $singleParameterOnOneLine = false; + // пропускає простори імен, які не містять жодного класу чи функції + public bool $omitEmptyNamespaces = true; + // роздільник між правою дужкою та типом повернення функцій та методів public string $returnTypeColon = ': '; } ``` +Як і чому власне відрізняються стандартний `Printer` та `PsrPrinter`? Чому в пакеті немає лише одного принтера, а саме `PsrPrinter`? -Типи .[#toc-types] ------------------- +Стандартний `Printer` форматує код так, як ми це робимо в усьому Nette. Оскільки Nette виникло набагато раніше, ніж PSR, а також тому, що PSR довгі роки не надавало стандартів вчасно, а, наприклад, лише з кількарічним запізненням після введення нової функції в PHP, сталося так, що [стандарт кодування |contributing:coding-standard] в кількох дрібницях відрізняється. Більшою відмінністю є лише використання табуляції замість пробілів. Ми знаємо, що використання табуляції в наших проектах дозволяє налаштувати ширину, що є необхідним для [людей з вадами зору |contributing:coding-standard#Табуляції замість пробілів]. Прикладом незначної відмінності є розміщення фігурної дужки на окремому рядку для функцій та методів, і це завжди. Рекомендація PSR нам здається нелогічною і призводить до [зменшення читабельності коду |contributing:coding-standard#Перенесення та фігурні дужки]. + + +Типи +---- -Кожен тип або тип об'єднання/перетину може бути переданий як рядок, ви також можете використовувати попередньо визначені константи для нативних типів: +Кожен тип або об'єднання/перетин типів можна передати як рядок, ви також можете використовувати попередньо визначені константи для нативних типів: ```php use Nette\PhpGenerator\Type; $member->setType('array'); // або Type::Array; -$member->setType('array|string'); // або Type::union('array', 'string') +$member->setType('?array'); // or Type::nullable(Type::Array); +$member->setType('array|string'); // or Type::union(Type::Array, Type::String) $member->setType('Foo&Bar'); // або Type::intersection(Foo::class, Bar::class) $member->setType(null); // видаляє тип ``` -Те саме стосується і методу `setReturnType()`. +Те саме стосується методу `setReturnType()`. -Літерали .[#toc-literals] -------------------------- +Літерали +-------- -За допомогою `Literal` ви можете передати довільний PHP-код, наприклад, значення властивостей або параметрів за замовчуванням тощо: +За допомогою `Literal` ви можете передавати будь-який PHP-код, наприклад, для значень за замовчуванням властивостей або параметрів тощо: ```php use Nette\PhpGenerator\Literal; @@ -545,25 +571,37 @@ class Demo } ``` -Ви також можете передати параметри на адресу `Literal` і відформатувати їх у коректний PHP-код за допомогою [спеціальних заповнювачів |#method-and-function-body-generator]: +Ви також можете передати параметри до `Literal` і дозволити їм форматуватися в дійсний PHP-код за допомогою [плейсхолдерів |#Тіла методів та функцій]: ```php new Literal('substr(?, ?)', [$a, $b]); // генерує, наприклад: substr('hello', 5); ``` +Літерал, що представляє створення нового об'єкта, можна легко згенерувати за допомогою методу `new`: -Атрибути .[#toc-attributes] ---------------------------- +```php +Literal::new(Demo::class, [$a, 'foo' => $b]); +// генерує, наприклад: new Demo(10, foo: 20) +``` + + +Атрибути +-------- -Ви можете додавати атрибути PHP 8 до всіх класів, методів, властивостей, констант, зчислень, функцій, закриттів і параметрів. [Літерали |#Literals] також можна використовувати як значення параметрів. +Атрибути PHP 8 ви можете додати до всіх класів, методів, властивостей, констант, enum, функцій, closures та параметрів. Як значення параметрів можна використовувати також [#літерали]. ```php $class = new Nette\PhpGenerator\ClassType('Demo'); -$class->addAttribute('Deprecated'); +$class->addAttribute('Table', [ + 'name' => 'user', + 'constraints' => [ + Literal::new('UniqueConstraint', ['name' => 'ean', 'columns' => ['ean']]), + ], +]); $class->addProperty('list') - ->addAttribute('WithArguments', [1, 2]); + ->addAttribute('Deprecated'); $method = $class->addMethod('count') ->addAttribute('Foo\Cached', ['mode' => true]); @@ -577,42 +615,126 @@ echo $class; Результат: ```php -#[Deprecated] +#[Table(name: 'user', constraints: [new UniqueConstraint(name: 'ean', columns: ['ean'])])] class Demo { - #[WithArguments(1, 2)] + #[Deprecated] public $list; #[Foo\Cached(mode: true)] - public function count(#[Bar] $items) - { + public function count( + #[Bar] + $items, + ) { } } ``` -Простір імен .[#toc-namespace] ------------------------------- +Property Hooks +-------------- + +За допомогою property hooks (представлених класом [PropertyHook|api:Nette\PhpGenerator\PropertyHook]) ви можете визначити операції get та set для властивостей, що є функцією, введеною в PHP 8.4: + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$prop = $class->addProperty('firstName') + ->setType('string'); + +$prop->addHook('set', 'strtolower($value)') + ->addParameter('value') + ->setType('string'); + +$prop->addHook('get') + ->setBody('return ucfirst($this->firstName);'); + +echo $class; +``` + +Згенерує: + +```php +class Demo +{ + public string $firstName { + set(string $value) => strtolower($value); + get { + return ucfirst($this->firstName); + } + } +} +``` + +Властивості та property hooks можуть бути абстрактними або фінальними: + +```php +$class->addProperty('id') + ->setType('int') + ->addHook('get') + ->setAbstract(); + +$class->addProperty('role') + ->setType('string') + ->addHook('set', 'strtolower($value)') + ->setFinal(); +``` + + +Асиметрична видимість +--------------------- + +PHP 8.4 вводить асиметричну видимість для властивостей. Ви можете встановити різні рівні доступу для читання та запису. + +Видимість можна встановити або за допомогою методу `setVisibility()` з двома параметрами, або за допомогою `setPublic()`, `setProtected()` чи `setPrivate()` з параметром `mode`, який визначає, чи стосується видимість читання чи запису властивості. Режим за замовчуванням — `'get'`. + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); + +$class->addProperty('name') + ->setType('string') + ->setVisibility('public', 'private'); // public для читання, private для запису + +$class->addProperty('id') + ->setType('int') + ->setProtected('set'); // protected для запису + +echo $class; +``` + +Згенерує: + +```php +class Demo +{ + public private(set) string $name; + + protected(set) int $id; +} +``` + + +Простір імен +------------ -Класи, риси, інтерфейси та зчислення (далі класи) можуть бути згруповані у простори імен[(PhpNamespace |api:Nette\PhpGenerator\PhpNamespace]): +Класи, властивості, інтерфейси та переліки (далі — класи) можна групувати в простори імен, представлені класом [PhpNamespace |api:Nette\PhpGenerator\PhpNamespace]: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); -// створити нові класи у просторі імен +// створимо нові класи в просторі імен $class = $namespace->addClass('Task'); $interface = $namespace->addInterface('Countable'); $trait = $namespace->addTrait('NameAware'); -// або вставити існуючий клас у простір імен +// або вставимо існуючий клас до простору імен $class = new Nette\PhpGenerator\ClassType('Task'); $namespace->add($class); ``` -Якщо клас вже існує, він генерує виключення. +Якщо клас вже існує, буде викинуто виняток. -Ви можете визначати оператори використання: +Ви можете визначити оператори use: ```php // use Http\Request; @@ -623,14 +745,14 @@ $namespace->addUse(Http\Request::class, 'HttpReq'); $namespace->addUseFunction('iter\range'); ``` -Щоб спростити повністю кваліфіковане ім'я класу, функції або константи відповідно до визначених псевдонімів, використовуйте метод `simplifyName`: +Щоб спростити повне кваліфіковане ім'я класу, функції або константи відповідно до визначених псевдонімів, використовуйте метод `simplifyName`: ```php echo $namespace->simplifyName('Foo\Bar'); // 'Bar', тому що 'Foo' є поточним простором імен -echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', тому що визначено оператор використання +echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', через визначений use-statement ``` -І навпаки, ви можете перетворити спрощене ім'я класу, функції або константи на повне за допомогою методу `resolveName`: +Спрощене ім'я класу, функції або константи можна, навпаки, перетворити на повне кваліфіковане ім'я за допомогою методу `resolveName`: ```php echo $namespace->resolveName('Bar'); // 'Foo\Bar' @@ -638,29 +760,27 @@ echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range' ``` -Приведення імен класів .[#toc-class-names-resolving] ----------------------------------------------------- +Перетворення імен класів +------------------------ -**Коли клас є частиною простору імен, він відображається дещо інакше**: всі типи (тобто підказки типів, типи, що повертаються, ім'я батьківського класу, -реалізовані інтерфейси, використані риси та атрибути) автоматично *розв'язуються* (якщо ви не вимкнете цю функцію, див. нижче). -Це означає, що ви повинні **використовувати повні імена класів** у визначеннях, а в результуючому коді вони будуть замінені на псевдоніми (відповідно до операторів використання) або повністю кваліфіковані імена: +**Коли клас є частиною простору імен, він відображається дещо інакше:** всі типи (наприклад, typehint, типи повернення, ім'я батьківського класу, реалізовані інтерфейси, використані трейти та атрибути) автоматично *перетворюються* (якщо ви це не вимкнете, див. нижче). Це означає, що ви повинні **використовувати повні імена класів** у визначеннях, і вони будуть замінені на псевдоніми (відповідно до операторів use) або на повні кваліфіковані імена у вихідному коді: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); $namespace->addUse('Bar\AliasedClass'); $class = $namespace->addClass('Demo'); -$class->addImplement('Foo\A') // спроститься до A - ->addTrait('Bar\AliasedClass'); // спростить до AliasedClass +$class->addImplement('Foo\A') // буде спрощено до A + ->addTrait('Bar\AliasedClass'); // буде спрощено до AliasedClass $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // в коментарях спростити вручну +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // у коментарях спростимо вручну $method->addParameter('arg') - ->setType('Bar\OtherClass'); // буде спрощено до \Bar\OtherClass + ->setType('Bar\OtherClass'); // буде перетворено на \Bar\OtherClass echo $namespace; -// або використовуйте PsrPrinter для виведення відповідно до PSR-2 / PSR-12 +// або використовуйте PsrPrinter для виведення відповідно до PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -684,7 +804,7 @@ class Demo implements A } ``` -Авторозв'язання можна вимкнути таким чином: +Автоматичне перетворення можна вимкнути таким чином: ```php $printer = new Nette\PhpGenerator\Printer; // або PsrPrinter @@ -693,14 +813,14 @@ echo $printer->printNamespace($namespace); ``` -Файли PHP .[#toc-php-files] ---------------------------- +PHP файли +--------- -Класи, функції та простори імен можуть бути згруповані у PHP-файли, представлені класом [PhpFile |api:Nette\PhpGenerator\PhpFile]: +Класи, функції та простори імен можна групувати в PHP-файли, представлені класом [PhpFile|api:Nette\PhpGenerator\PhpFile]: ```php $file = new Nette\PhpGenerator\PhpFile; -$file->addComment('This file is auto-generated.'); +$file->addComment('Цей файл згенеровано автоматично.'); $file->setStrictTypes(); // додає declare(strict_types=1) $class = $file->addClass('Foo\A'); @@ -713,7 +833,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// або використовуйте PsrPrinter для виведення відповідно до PSR-2 / PSR-12 +// або використовуйте PsrPrinter для виведення відповідно до PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -723,7 +843,7 @@ echo $file; <?php /** - * This file is auto-generated. + * Цей файл згенеровано автоматично. */ declare(strict_types=1); @@ -739,27 +859,28 @@ function foo() } ``` +**Увага:** До файлів неможливо додавати будь-який інший код поза функціями та класами. -Генерувати відповідно до існуючого .[#toc-generating-according-to-existing-ones] --------------------------------------------------------------------------------- -Окрім можливості моделювати класи та функції за допомогою API, описаного вище, ви також можете автоматично генерувати їх на основі вже існуючих: +Генерація на основі існуючих +---------------------------- + +Окрім того, що класи та функції можна моделювати за допомогою описаного вище API, їх також можна генерувати автоматично на основі існуючих шаблонів: ```php -// створює клас, ідентичний класу PDO +// створить клас, ідентичний класу PDO $class = Nette\PhpGenerator\ClassType::from(PDO::class); -// створює функцію, ідентичну функції trim() +// створить функцію, ідентичну функції trim() $function = Nette\PhpGenerator\GlobalFunction::from('trim'); -// створює закриття як вказано +// створить closure відповідно до зазначеної $closure = Nette\PhpGenerator\Closure::from( function (stdClass $a, $b = null) {}, ); ``` -Тіла функцій і методів за замовчуванням порожні. Якщо ви хочете завантажити їх, скористайтеся цим способом -(для цього потрібно, щоб було встановлено `nikic/php-parser` ): +Тіла функцій та методів за замовчуванням порожні. Якщо ви хочете їх також завантажити, використовуйте цей спосіб (вимагає встановлення пакету `nikic/php-parser`): ```php $class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true); @@ -768,10 +889,10 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); ``` -Завантаження з PHP-файлу .[#toc-loading-from-php-file] ------------------------------------------------------- +Завантаження з PHP файлів +------------------------- -Ви також можете завантажувати функції, класи, інтерфейси та зчислення безпосередньо з рядка PHP-коду. Наприклад, ми створюємо об'єкт `ClassType` таким чином: +Функції, класи, інтерфейси та enum можна завантажувати також безпосередньо з рядка, що містить PHP-код. Наприклад, так створимо об'єкт `ClassType`: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -784,39 +905,69 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -При завантаженні класів з PHP-коду ігноруються однорядкові коментарі за межами тіл методів (наприклад, для властивостей тощо), оскільки ця бібліотека не має API для роботи з ними. +При завантаженні класів з PHP-коду однорядкові коментарі поза тілами методів ігноруються (наприклад, біля властивостей тощо), оскільки ця бібліотека не має API для роботи з ними. -Ви також можете завантажити безпосередньо весь PHP-файл, який може містити будь-яку кількість класів, функцій або навіть декілька просторів імен: +Ви також можете завантажити безпосередньо весь PHP-файл, який може містити будь-яку кількість класів, функцій або навіть просторів імен: ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` -Також завантажуються початковий коментар до файлу та декларація `strict_types`. З іншого боку, весь інший глобальний код ігнорується. +Завантажується також вступний коментар до файлу та декларація `strict_types`. Навпаки, весь інший глобальний код ігнорується. -Для цього потрібно, щоб було встановлено `nikic/php-parser`. +Вимагається, щоб був встановлений `nikic/php-parser`. .[note] Якщо вам потрібно маніпулювати глобальним кодом у файлах або окремими операторами в тілах методів, краще використовувати безпосередньо бібліотеку `nikic/php-parser`. -Дампер змінних .[#toc-variables-dumper] ---------------------------------------- +Class Manipulator +----------------- + +Клас [ClassManipulator|api:Nette\PhpGenerator\ClassManipulator] надає інструменти для маніпулювання класами. + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$manipulator = new Nette\PhpGenerator\ClassManipulator($class); +``` + +Метод `inheritMethod()` копіює метод з батьківського класу або реалізованого інтерфейсу до вашого класу. Це дозволяє вам перевизначити метод або розширити його сигнатуру: + +```php +$method = $manipulator->inheritMethod('bar'); +$method->setBody('...'); +``` -Дампер повертає розбірне PHP-рядкове представлення змінної. Забезпечує кращий і чіткіший вивід, ніж рідна функція `var_export()`. +Метод `inheritProperty()` копіює властивість з батьківського класу до вашого класу. Це корисно, коли ви хочете мати у своєму класі таку саму властивість, але, наприклад, з іншим значенням за замовчуванням: + +```php +$property = $manipulator->inheritProperty('foo'); +$property->setValue('нове значення'); +``` + +Метод `implement()` автоматично реалізує всі методи та властивості з даного інтерфейсу або абстрактного класу у вашому класі: + +```php +$manipulator->implement(SomeInterface::class); +// Тепер ваш клас реалізує SomeInterface і містить усі його методи +``` + + +Виведення змінних +----------------- + +Клас `Dumper` перетворює змінну на парсований PHP-код. Він забезпечує кращий та зрозуміліший вивід, ніж стандартна функція `var_export()`. ```php $dumper = new Nette\PhpGenerator\Dumper; $var = ['a', 'b', 123]; -echo $dumper->dump($var); // prints ['a', 'b', 123] +echo $dumper->dump($var); // виведе ['a', 'b', 123] ``` -Таблиця сумісності .[#toc-compatibility-table] ----------------------------------------------- - -PhpGenerator 4.0 сумісний з версіями PHP від 8.0 до 8.2 +Таблиця сумісності +------------------ -{{leftbar: nette:@menu-topics}} +PhpGenerator 4.1 сумісний з PHP 8.0 до 8.4. diff --git a/php-generator/uk/@meta.texy b/php-generator/uk/@meta.texy new file mode 100644 index 0000000000..083a8ab9f7 --- /dev/null +++ b/php-generator/uk/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Документація Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/quickstart/bg/@home.texy b/quickstart/bg/@home.texy index 58f142b6aa..155939976d 100644 --- a/quickstart/bg/@home.texy +++ b/quickstart/bg/@home.texy @@ -1,113 +1,114 @@ -Създайте първото си приложение! -******************************* +Пишем първото приложение! +************************* .[perex] -Запознайте се с рамката Nette, като създадете прост блог с коментари. Да започнем! +Нека се запознаем заедно с Nette Framework, като създадем прост блог с коментари. Да започваме! -След първите две глави ще имате свой собствен работещ блог и ще сте готови да публикувате страхотни публикации, въпреки че функциите ще бъдат доста ограничени след приключването на тези две глави. За да направите нещата по-приятни за вашите потребители, трябва да прочетете и следващите глави и да продължите да подобрявате приложението си. +Още след първите две глави ще имаме собствен функциониращ блог и ще можем да публикуваме страхотните си публикации, въпреки че функциите засега ще бъдат до голяма степен ограничени. Трябва да прочетете и следващите глави, където ще програмираме добавяне на коментари, редактиране на статии и накрая ще обезопасим блога. .[tip] -Този урок предполага, че сте завършили документа [Инсталиране |nette:installation] и успешно сте настроили инструментите си. +Това ръководство предполага, че сте прочели страницата [Инсталация |nette:installation] и успешно сте подготвили необходимите инструменти. Също така предполага, че разбирате [обектно-ориентирано програмиране в PHP |nette:introduction-to-object-oriented-programming]. -Моля, използвайте PHP 8.0 или по-нова версия. Можете да намерите пълното приложение [в GitHub |https://github.com/nette-examples/quickstart/tree/v4.0]. +Моля, използвайте PHP 8.1 или по-нова версия. Цялото приложение можете да намерите [в GitHub |https://github.com/nette-examples/quickstart/tree/v4.0]. -Страницата за добре дошли .[#toc-the-welcome-page] -================================================== +Страница за добре дошли +======================= -Нека започнем със създаването на нов проект в директорията `nette-blog`: +Да започнем със създаването на нов проект в директорията `nette-blog`: ```shell composer create-project nette/web-project nette-blog ``` -В този момент трябва да се стартира началната страница на уеб проекта. Опитайте я, като отворите браузъра си и отидете на следния URL адрес: +В този момент началната страница на Web Project вече трябва да работи. Ще изпробваме това, като отворим браузъра на следния URL адрес: ``` http://localhost/nette-blog/www/ ``` -и ще видите началната страница на рамката: +и ще видим началната страница на Nette Framework: [* qs-welcome.webp .{url: http://localhost/nette-blog/www/} *] -Приложението е готово и вече можете да започнете да правите промени в него. +Приложението работи и можете да започнете да правите редакции. .[note] -Ако имате проблеми, [можете да опитате няколко съвета |nette:troubleshooting#Nette-Is-Not-Working-White-Page-Is-Displayed]. +Ако възникне проблем, [опитайте тези няколко съвета |nette:troubleshooting#Nette не работи показва се бяла страница]. -Съдържание на уеб проекта .[#toc-web-project-s-content] -======================================================= +Съдържание на Web Project +========================= -Нашият проект има следната структура: +Web Project има следната структура: /--pre <b>nette-blog/</b> -├── <b>app/</b> ← каталог приложения -│ ├── <b>Presenters/</b> ← классы презентеров -│ │ └── <b>templates/</b>← шаблоны -│ ├── <b>Router/</b> ← конфигурация адресов URL -│ └── <b>Bootstrap.php</b> ← загрузочный класс Bootstrap -├── <b>bin/</b> ← скрипты для командной строки -├── <b>config/</b> ← конфигурационные файлы -├── <b>log/</b> ← журналы ошибок -├── <b>temp/</b> ← временные файлы, кэш, … -├── <b>vendor/</b> ← библиотеки, установленные через Composer -│ └── <b>autoload.php</b> ← автозагрузка библиотек, установленных Composer -└── <b>www/</b> ← общая папка — единственное место, доступное из браузера - └── <b>index.php</b> ← начальный файл, запускающий приложение +├── <b>app/</b> ← директория с приложението +│ ├── <b>Core/</b> ← основни класове, необходими за работа +│ ├── <b>Presentation/</b> ← презентери, шаблони и т.н. +│ │ └── <b>Home/</b> ← директория на презентера Home +│ └── <b>Bootstrap.php</b> ← зареждащ клас Bootstrap +├── <b>assets/</b> ← сурови активи (SCSS, TypeScript, изходни изображения) +├── <b>bin/</b> ← скриптове, стартирани от командния ред +├── <b>config/</b> ← конфигурационни файлове +├── <b>log/</b> ← логване на грешки +├── <b>temp/</b> ← временни файлове, кеш, ... +├── <b>vendor/</b> ← библиотеки, инсталирани от Composer +│ └── <b>autoload.php</b> ← autoloading на всички инсталирани пакети +└── <b>www/</b> ← публична директория - единствената достъпна от браузъра + ├── <b>assets/</b> ← компилирани статични файлове (CSS, JS, изображения, ...) + └── <b>index.php</b> ← първоначален файл, чрез който се стартира приложението \-- -Директорията `www` е предназначена за съхранение на изображения, JavaScript, CSS и други публични файлове. Това е единствената директория, достъпна директно от браузъра, така че тук можете да посочите коренната директория на вашия уеб сървър (можете да я настроите в Apache, но нека го направим по-късно, тъй като сега това не е важно). +Директорията `www/` е предназначена за съхранение на изображения, JavaScript файлове, CSS стилове и други публично достъпни файлове. Само тази директория е достъпна от интернет, така че настройте коренната директория на вашето приложение да сочи точно тук (това можете да настроите в конфигурацията на Apache или nginx, но нека го направим по-късно, сега не е важно). -Най-важната директория за вас е `app/`. Там ще намерите файла `Bootstrap.php`, в който ще намерите клас, който зарежда рамката и конфигурира приложението. Той активира [автозадаващото устройство |robot-loader:] и настройва [дебъгъра |tracy:] и [маршрутите |application:routing]. +Най-важната папка за нас е `app/`. Тук намираме файла `Bootstrap.php`, в който има клас, който служи за зареждане на целия framework и настройка на приложението. Тук се активира [autoloading |robot-loader:], настройва се [дебъгер |tracy:] и [маршрути |application:routing]. -Почистване .[#toc-cleanup] -========================== +Почистване +========== -Уеб проектът съдържа начална страница, която можем да премахнем - не се колебайте да замените съдържанието на файла `app/Presenters/templates/Home/default.latte` с текста `Hello world!`. +Web Project съдържа начална страница, която ще изтрием, преди да започнем да програмираме нещо. Без притеснения, следователно, ще заменим съдържанието на файла `app/Presentation/Home/default.latte` с "Hello world!". [* qs-hello.webp .{url:-} *] -Tracy (дебъгер) .[#toc-tracy-debugger] -====================================== +Tracy (дебъгер) +=============== -Изключително важен инструмент за разработка е [дебъгер, наречен Tracy. |tracy:] Опитайте се да направите някои грешки във вашия файл `app/Presenters/HomePresenter.php` (например да премахнете къдравата скоба от дефиницията на класа HomePresenter) и вижте какво ще се случи. Ще се появи страница с червен екран и разбираемо описание на грешката. +Изключително важен инструмент за разработка е [инструментът за отстраняване на грешки Tracy |tracy:]. Опитайте да предизвикате някаква грешка във файла `app/Presentation/Home/HomePresenter.php` (напр. като премахнете къдравата скоба в дефиницията на класа HomePresenter) и вижте какво ще се случи. Ще изскочи страница за уведомяване, която описва грешката разбираемо. -[* qs-tracy.webp .{url:-}(debugger screen) *] +[* qs-tracy.avif .{url:-}(debugger screen) *] -Трейси ще ви помогне значително да откриете грешките. Обърнете внимание и на плаващата лента Tracy Bar в долния десен ъгъл, която ви информира за важни данни по време на работа. +Tracy ще ни помогне изключително много, когато търсим грешки в приложението. Също така забележете плаващия Tracy Bar в долния десен ъгъл на екрана, който съдържа информация от работата на приложението. [* qs-tracybar.webp .{url:-} *] -В производствен режим Tracy, разбира се, е деактивирана и не разкрива никаква чувствителна информация. Всички грешки се записват в директорията `log/`. Просто опитайте. Във файла `app/Bootstrap.php` намерете следната част от кода, коментирайте реда и променете параметъра за извикване на метода на `false`, така че да изглежда по следния начин +В продукционен режим Tracy, разбира се, е изключена и не показва никаква чувствителна информация. Всички грешки в този случай се съхраняват в папката `log/`. Нека да го изпробваме. Във файла `app/Bootstrap.php` разкоментираме следния ред и променяме параметъра на извикването на `false`, така че кодът да изглежда така: ```php .{file:app/Bootstrap.php} ... -$configurator->setDebugMode(false); -$configurator->enableTracy(__DIR__ . '/../log'); +$this->configurator->setDebugMode(false); ... ``` -Когато уебстраницата се опресни, червеният екран на страницата ще се промени на удобно за потребителя съобщение: +След обновяване на страницата вече няма да видим Tracy. Вместо нея ще се покаже удобно за потребителя съобщение: [* qs-fatal.webp .{url:-}(error screen) *] -Сега погледнете в директорията `log/`. Там ще намерите дневник на грешките (във файла exception.log), както и страница за грешки (записана в HTML файл с име, започващо с `exception`). +Сега да погледнем в директорията `log/`. Тук (във файла `exception.log`) намираме логнатата грешка, а също и вече познатата страница със съобщение за грешка (съхранена като HTML файл с име, започващо с `exception-`). -Изтрийте отново реда `// $configurator->setDebugMode(false);`. Tracy автоматично активира режима за разработка в средата `localhost` и го деактивира другаде. +Коментираме отново реда `// $configurator->setDebugMode(false);`. Tracy автоматично разрешава режим за разработка на localhost и го забранява навсякъде другаде. -Сега можем да поправим грешката и да продължим да разработваме нашето приложение. +Можем да поправим грешката, която създадохме, и да продължим с писането на приложението. -Изпратете благодарности до .[#toc-send-thanks] -============================================== +Изпратете благодарност +====================== -Ще ви покажем един трик, който ще зарадва авторите на софтуер с отворен код. Можете лесно да присвоите звезда в GitHub на библиотеките, които използват вашия проект. Просто въведете в конзолата: +Ще ви покажем трик, с който ще зарадвате авторите на софтуер с отворен код. По прост начин можете да дадете звездичка в GitHub на библиотеките, които вашият проект използва. Достатъчно е да стартирате: ```shell composer thanks @@ -116,4 +117,3 @@ composer thanks Опитайте! {{priority: -1}} -{{sitename: Быстрый старт с Nette}} diff --git a/quickstart/bg/@left-menu.texy b/quickstart/bg/@left-menu.texy index cbc1f3b57d..5af0b22ed3 100644 --- a/quickstart/bg/@left-menu.texy +++ b/quickstart/bg/@left-menu.texy @@ -1,9 +1,9 @@ -Учебник -******* -- [Създайте първото си приложение |@home]! +Урок +**** +- [Пишем първото приложение! |@home] - [Начална страница на блога |home-page] -- [Страница за индивидуално вписване |single-post] +- [Страница с публикация |single-post] - [Коментари |comments] - [Създаване и редактиране на публикации |creating-posts] -- [Модел |model] -- [Удостоверяване |authentication] +- [Модел |Model] +- [Автентикация |authentication] diff --git a/quickstart/bg/@meta.texy b/quickstart/bg/@meta.texy new file mode 100644 index 0000000000..47410863e7 --- /dev/null +++ b/quickstart/bg/@meta.texy @@ -0,0 +1 @@ +{{sitename: Пишем първото приложение!}} diff --git a/quickstart/bg/authentication.texy b/quickstart/bg/authentication.texy index 85d904fb3c..86fedb5f01 100644 --- a/quickstart/bg/authentication.texy +++ b/quickstart/bg/authentication.texy @@ -1,36 +1,36 @@ -Удостоверяване -************** +Автентикация +************ -Nette предоставя начин за програмиране на удостоверяване на нашия уебсайт, но не ни принуждава да правим нищо. Изпълнението зависи от нас. Nette включва интерфейс `Nette\Security\Authenticator`, който изисква само един метод `authenticate` за удостоверяване на потребителя, както искаме. +Nette предоставя начин за програмиране на автентикация на нашите страници, но не ни налага нищо. Имплементацията зависи изцяло от нас. Nette съдържа интерфейса `Nette\Security\Authenticator`, който изисква само един метод `authenticate`, който удостоверява потребителя по какъвто начин искаме. -Има много начини, по които потребителят може да се удостовери. Най-разпространеният метод е *удостоверяване с парола* (потребителят посочва своето име или имейл и парола), но има и други методи. Може би сте виждали бутони като "Влезте във Facebook/Google/Twitter/GitHub и т.н." в много уебсайтове. С Nette можем да използваме всеки метод за влизане, който желаем, и можем да ги комбинираме. Това зависи от нас. +Има много начини, по които потребителят може да бъде удостоверен. Най-често срещаният начин за удостоверяване е чрез парола (потребителят предоставя своето име или имейл и парола), но има и други начини. Може би познавате бутони от типа "Вход с Facebook" или влизане с Google/Twitter/GitHub на някои сайтове. С Nette можем да имаме всякакъв метод за влизане или можем да ги комбинираме. Зависи само от нас. -Обикновено пишем собствен автентификатор, но за този прост блог ще използваме вградения автентификатор, който влиза в системата въз основа на паролата и потребителското име, записани в конфигурационния файл. Това работи добре за целите на тестването. Добавяне на раздел *security* в конфигурационния файл `config/common.neon`: +Обикновено бихме написали собствен автентикатор, но за този прост малък блог ще използваме вградения автентикатор, който влиза въз основа на парола и потребителско име, съхранени в конфигурационния файл. Той е подходящ за тестови цели. Следователно ще добавим следната секция *security* към конфигурационния файл `config/common.neon`: ```neon .{file:config/common.neon} security: users: - admin: secret # вход 'admin', парола 'secret' + admin: secret # потребител 'admin', парола 'secret' ``` -Nette автоматично ще създаде услуга в контейнера DI. +Nette автоматично създава сървис в DI контейнера. -Регистрационен формуляр .[#toc-sign-in-form] -============================================ +Форма за вход +============= -Вече имаме готово удостоверяване и трябва да подготвим потребителския интерфейс за влизане в системата. Затова нека създадем нов презентатор, наречен *SignPresenter*, който ще: +Сега имаме готова автентикация и трябва да подготвим потребителски интерфейс за влизане. Нека създадем нов presenter с име *SignPresenter*, който: -- показване на формуляра за вход (изискват се потребителско име и парола) -- Удостоверяване на автентичността на потребителя при подаване на формуляра -- Осигуряване на изход от системата +- показва форма за вход (с потребителско име и парола) +- след изпращане на формата удостоверява потребителя +- предоставя възможност за изход -Нека започнем с формата за вход. Вече знаете как работят формулярите в програмата Presenter. Създайте `SignPresenter` и метод `createComponentSignInForm`. Това трябва да изглежда по следния начин: +Да започнем с формата за вход. Вече знаем как работят формите в presenter-ите. Ще създадем presenter `SignPresenter` и ще напишем метода `createComponentSignInForm`. Той трябва да изглежда по следния начин: -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Sign; use Nette; use Nette\Application\UI\Form; @@ -40,69 +40,69 @@ final class SignPresenter extends Nette\Application\UI\Presenter protected function createComponentSignInForm(): Form { $form = new Form; - $form->addText('username', 'Имя пользователя:') - ->setRequired('Пожалуйста, введите ваше имя.'); + $form->addText('username', 'Потребителско име:') + ->setRequired('Моля, въведете вашето потребителско име.'); - $form->addPassword('password', 'Пароль:') - ->setRequired('Пожалуйста, введите ваш пароль.'); + $form->addPassword('password', 'Парола:') + ->setRequired('Моля, въведете вашата парола.'); - $form->addSubmit('send', 'Войти'); + $form->addSubmit('send', 'Вход'); - $form->onSuccess[] = [$this, 'signInFormSucceeded']; + $form->onSuccess[] = $this->signInFormSucceeded(...); return $form; } } ``` -Вече имаме въведени потребителско име и парола. +Има полета за потребителско име и парола. -Шаблон .[#toc-template] ------------------------ +Шаблон +------ -Формулярът ще бъде показан в шаблона `app/Presenters/templates/Sign/in.latte`. +Формата ще се рендира в шаблона `in.latte`: -```latte .{file:app/Presenters/templates/Sign/in.latte} +```latte .{file:app/Presentation/Sign/in.latte} {block content} -<h1 n:block=title>Войти</h1> +<h1 n:block=title>Вход</h1> {control signInForm} ``` -Обслужване на вход .[#toc-login-handler] ----------------------------------------- +Callback за вход +---------------- -След това ще добавим *обработчик на формуляра* за влизане на потребителя, който ще бъде извикан веднага след успешното изпращане на формуляра. +След това ще добавим callback за влизане на потребителя, който ще бъде извикан веднага след успешното изпращане на формата. -Обслужващият модул просто взема потребителското име и паролата, които потребителят е въвел, и ги предава на удостоверителя. След като влезете в системата, ще ви пренасочим към началната страница: +Callback-ът просто взема потребителското име и паролата, които потребителят е попълнил, и ги предава на автентикатора. След влизане пренасочваме към началната страница. -```php .{file:app/Presenters/SignPresenter.php} -public function signInFormSucceeded(Form $form, \stdClass $data): void +```php .{file:app/Presentation/Sign/SignPresenter.php} +private function signInFormSucceeded(Form $form, \stdClass $data): void { try { $this->getUser()->login($data->username, $data->password); $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { - $form->addError('Неправильные логин или пароль.'); + $form->addError('Невалидно потребителско име или парола.'); } } ``` -Методът [User::login( |api:Nette\Security\User::login()] ) трябва да хвърли изключение, ако потребителското име или паролата не съвпадат с тези, които определихме по-рано. Както вече знаем, това ще доведе до червена страница за грешка на [Tracy |tracy:] или, в производствен режим, до съобщение за грешка на вътрешния сървър. Но ние не искаме това. Така че прихващаме изключението и добавяме хубаво, дружелюбно съобщение за грешка към формуляра. +Методът [User::login() |api:Nette\Security\User::login()] хвърля изключение, ако потребителското име и паролата не съвпадат с данните в конфигурационния файл. Както вече знаем, това може да доведе до червена страница за грешка или, в продукционен режим, до съобщение за грешка на сървъра. Не искаме това. Затова улавяме това изключение и предаваме приятно, лесно за разбиране съобщение за грешка към формата. -Когато във формуляра възникне грешка, страницата на формуляра ще бъде показана отново, а над формуляра ще се появи хубаво съобщение, което ще информира потребителя, че е въвел грешно потребителско име или парола. +Щом възникне грешка във формата, страницата с формата се прерисува и над формата се показва приятно съобщение, информиращо потребителя, че е въвел грешно потребителско име или парола. -Сигурност на водещия .[#toc-security-of-presenters] -=================================================== +Защита на presenter-ите +======================= -Подсигурете формата за добавяне и редактиране на публикации. Целта е да се предотврати достъпът на неоторизирани потребители до страницата. +Ще защитим формата за добавяне и редактиране на публикации. Тя е дефинирана в presenter-а `EditPresenter`. Целта е да се забрани достъпът до страницата на потребители, които не са влезли. -Ще създадем метод `startup()`, който се изпълнява веднага в началото на [жизнения цикъл на презентатора |application:presenters#Life-Cycle-of-Presenter]. Това пренасочва нерегистрираните потребители към формата за вход. +Ще създадем метод `startup()`, който се изпълнява веднага в началото на [жизнения цикъл на presenter-а |application:presenters#Жизнен цикъл на презентера]. Той пренасочва невлезлите потребители към формата за вход. -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} public function startup(): void { parent::startup(); @@ -114,67 +114,66 @@ public function startup(): void ``` -Скриване на връзки .[#toc-hide-links] -------------------------------------- +Скриване на връзки +------------------ -Неоторизиран потребител вече не може да вижда страниците за създаване и редактиране, но все още може да вижда връзките, които сочат към тях. Нека скрием и тях. Една такава връзка се намира на адрес `app/Presenters/templates/Home/default.latte`, като тя трябва да бъде видима само ако потребителят е влязъл в системата. +Неоторизиран потребител вече не може да вижда страниците *create* или *edit*, но все още може да вижда връзките към тях. Трябва да ги скрием също. Една такава връзка е в шаблона `app/Presentation/Home/default.latte` и трябва да се вижда само от влезли потребители. -Можем да го скрием, като използваме *n:атрибута*, наречен `n:if`. Ако изявлението в него е `false`, тогава целият таг `<a>` и съдържанието му няма да бъде показано: +Можем да го скрием, като използваме *n:атрибут* с име `n:if`. Ако това условие е `false`, целият таг `<a>`, включително съдържанието му, ще остане скрит. ```latte -<a n:href="Edit:create" n:if="$user->isLoggedIn()">Создать пост</a> +<a n:href="Edit:create" n:if="$user->isLoggedIn()">Създай публикация</a> ``` -това е съкращение (да не се бърка с `tag-if`): +което е съкращение на следния запис (да не се бърка с `tag-if`): ```latte -{if $user->isLoggedIn()}<a n:href="Edit:create">Создать пост</a>{/if} +{if $user->isLoggedIn()}<a n:href="Edit:create">Създай публикация</a>{/if} ``` -По същия начин скрийте връзката за редактиране, намираща се на адрес `app/Presenters/templates/Post/show.latte`. +По същия начин ще скрием и връзката в шаблона `app/Presentation/Post/show.latte`. -Връзка към формуляра за влизане .[#toc-login-form-link] -======================================================= +Връзка за вход +============== -Здравейте, но как да стигнем до страницата за вход? Няма връзка, която да сочи към него. Нека да го добавим към файла с шаблона `app/Presenters/templates/@layout.latte`. Опитайте се да намерите добро място, може да е всяко място, което ви харесва най-много. +Как всъщност да стигнем до страницата за вход? Няма връзка, която да води към нея. Така че нека я добавим към шаблона `@layout.latte`. Опитайте се да намерите подходящо място - може да бъде почти навсякъде. -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <ul class="navig"> - <li><a n:href="Home:">Главная</a></li> + <li><a n:href="Home:">Статии</a></li> {if $user->isLoggedIn()} - <li><a n:href="Sign:out">Выйти</a></li> + <li><a n:href="Sign:out">Изход</a></li> {else} - <li><a n:href="Sign:in">Войти</a></li> + <li><a n:href="Sign:in">Вход</a></li> {/if} </ul> ... ``` -Ако потребителят все още не е влязъл в системата, той ще види връзка "Вход". В противен случай ще се вижда връзката "Изход". Добавете това действие в `SignPresenter`. +Ако потребителят не е влязъл, ще се покаже връзката "Вход". В противен случай ще се покаже връзката "Изход". Ще добавим това действие и в `SignPresenter`. -Действието за излизане от системата изглежда по следния начин и тъй като пренасочваме потребителя веднага, няма нужда от шаблон на изгледа: +Тъй като пренасочваме потребителя веднага след излизане, не е необходим шаблон. Изходът изглежда така: -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} public function actionOut(): void { $this->getUser()->logout(); - $this->flashMessage('Вы вышли.'); + $this->flashMessage('Изходът беше успешен.'); $this->redirect('Home:'); } ``` -Той просто извиква метода `logout()` и след това показва на потребителя хубаво съобщение. +Само се извиква методът `logout()` и след това се показва приятно съобщение, потвърждаващо успешния изход. -За да обобщим .[#toc-summary] -============================= +Резюме +====== -Имаме връзка за влизане и връзка за излизане на потребителя. Използвахме вградения автентификатор за удостоверяване, а данните за вход са в конфигурационния файл, тъй като това е просто тестово приложение. Също така защитихме формите за редактиране, така че само влезлите в системата потребители да могат да добавят и редактират съобщения. +Имаме връзка за вход и изход на потребителя. За удостоверяване използвахме вградения автентикатор и данните за вход са в конфигурационния файл, тъй като това е просто тестово приложение. Също така защитихме формите за редактиране, така че само влезли потребители могат да добавят и редактират публикации. .[note] -Тук можете да прочетете повече за [user login |security:authentication] и [authorization |security:authorization]. +Тук можете да прочетете повече за [влизане на потребители |security:authentication] и [Проверка на правата |security:authorization]. {{priority: -1}} -{{sitename: Быстрый старт с Nette}} diff --git a/quickstart/bg/comments.texy b/quickstart/bg/comments.texy index 36490514c3..bdf5455491 100644 --- a/quickstart/bg/comments.texy +++ b/quickstart/bg/comments.texy @@ -1,28 +1,28 @@ -Коментар -******** +Коментари +********* -Блогът беше пуснат в действие, написахме няколко много добри публикации в блога и ги публикувахме чрез Adminer. Хората четат блога и са много ентусиазирани от нашите идеи. Всеки ден получаваме много похвални писма. Но защо са всички тези похвали, ако ги получаваме само по имейл и никой друг не може да ги прочете? Няма ли да е по-добре, ако хората могат да оставят коментари директно в блога, за да могат всички останали да прочетат колко сме страхотни? +Качихме блога на уеб сървъра и публикувахме няколко много интересни публикации с помощта на Adminer. Хората четат блога ни и са много ентусиазирани от него. Всеки ден получаваме много имейли с похвали. Но каква е ползата от всички тези похвали, ако ги имаме само в имейла и никой не може да ги прочете? Би било по-добре, ако читателят можеше директно да коментира статията, така че всеки да може да прочете колко сме страхотни. -Нека направим всички статии коментируеми. +Така че нека програмираме коментарите. -Създаване на нова таблица .[#toc-creating-a-new-table] -====================================================== +Създаване на нова таблица +========================= -Стартирайте отново Adminer и създайте нова таблица, наречена `comments`, с тези колони: +Ще стартираме Adminer и ще създадем таблица `comments` със следните колони: -- `id` int, маркиране на автоинкремента (AI) -- `post_id`, външен ключ, препращащ към таблицата `posts`. -- `name` varchar, дължина 255 -- `email` varchar, дължина 255 -- `content` текст +- `id` int, отметнете autoincrement (AI) +- `post_id`, външен ключ, който препраща към таблицата `posts` +- `name` varchar, length 255 +- `email` varchar, length 255 +- `content` text - `created_at` timestamp -Това трябва да изглежда по следния начин +Така таблицата трябва да изглежда по следния начин: [* adminer-comments.webp *] -Не забравяйте да използвате съхранението на таблицата InnoDB и щракнете върху Запис. +Не забравяйте отново да използвате хранилище InnoDB. ```sql CREATE TABLE `comments` ( @@ -37,83 +37,83 @@ CREATE TABLE `comments` ( ``` -Формуляр за коментари .[#toc-form-for-commenting] -================================================= +Форма за коментиране +==================== -Първо, трябва да създадем формуляр, който ще позволи на потребителите да коментират на нашата страница. Рамката на Nette има отлична поддръжка на формуляри. Те могат да бъдат персонализирани в програмата за представяне и показани в шаблона. +Първо трябва да създадем форма, която ще позволи на потребителите да коментират публикациите. Nette Framework има страхотна поддръжка за форми. Можем да ги конфигурираме в presenter-а и да ги рендираме в шаблона. -Nette има понятие за *компоненти*. **Компонент** е клас или част от кода за многократна употреба, който може да бъде прикрепен към друг компонент. Дори водещият е компонент. Всеки компонент се създава с помощта на фабрика за компоненти. И така, нека дефинираме фабриката за формуляри за коментари в `PostPresenter`. +Nette Framework използва концепцията за *компоненти*. **Компонент** е клас за многократна употреба или част от код, който може да бъде прикрепен към друг компонент. Дори presenter-ът е компонент. Всеки компонент се създава чрез фабрика. Така че ще създадем фабрика за формата за коментари в presenter-а `PostPresenter`. -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} protected function createComponentCommentForm(): Form { - $form = new Form; // означава Nette\Application\UI\Form + $form = new Form; // means Nette\Application\UI\Form - $form->addText('name', 'вашето име:') + $form->addText('name', 'Име:') ->setRequired(); - $form->addEmail('email', 'Email:'); + $form->addEmail('email', 'Имейл:'); - $form->addTextArea('content', 'Comment:') + $form->addTextArea('content', 'Коментар:') ->setRequired(); - $form->addSubmit('send', 'Send'); + $form->addSubmit('send', 'Публикувай коментар'); return $form; } ``` -Нека да го обясним малко. Първият ред създава нова инстанция на компонента `Form`. Методите по-долу поставят HTML елементите *input* във формуляра. `->addText` ще се покаже като `<input type=text name=name>`, с `<label>Ваше имя:</label>`. Както вече сте се досетили, `->addTextArea` прикрепя `<textarea>`, а `->addSubmit` добавя `<input type=submit>`. Има и други подобни методи, но това е всичко, което трябва да знаете сега. Можете да [намерите повече информация в документацията |forms:]. +Нека отново да обясним малко. Първият ред създава нов екземпляр на компонента `Form`. Следващите методи прикачват HTML входове към дефиницията на тази форма. `->addText` ще се рендира като `<input type="text" name="name">` с `<label>Име:</label>`. Както вероятно вече правилно предполагате, `->addTextArea` ще се рендира като `<textarea>`, а `->addSubmit` като `<input type="submit">`. Има много повече подобни методи, но тези засега са достатъчни за тази форма. Можете да [прочетете повече в документацията|forms:]. -След като компонентът на формуляра е дефиниран в презентатора, можем да го визуализираме в шаблона. За да направите това, поставете тага `{control}` в края на шаблона за подробна информация за публикацията в `app/Presenters/templates/Post/show.latte`. Тъй като името на компонента е `commentForm` (то идва от името на метода `createComponentCommentForm`), тагът ще изглежда по следния начин +Ако формата вече е дефинирана в presenter-а, можем да я рендираме (покажем) в шаблона. Това се прави чрез поставяне на тага `{control}` в края на шаблона, който рендира една конкретна публикация, в `Post/show.latte`. Тъй като компонентът се нарича `commentForm` (името произлиза от името на метода `createComponentCommentForm`), тагът ще изглежда така: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... -<h2>Оставить комментарий</h2> +<h2>Вложете нова публикация</h2> {control commentForm} ``` -Сега, ако отидете на отделна страница на някоя публикация, ще се появи нов формуляр за публикуване на коментари. +Сега, когато видите страницата с детайли на публикацията, в края й ще видите нова форма за коментари. -Запазване в базата данни .[#toc-saving-to-database] -=================================================== +Запазване в базата данни +======================== -Опитахте ли се да изпратите данни? Може би сте забелязали, че формулярът не извършва никакво действие. Той просто е там, изглежда готино и не прави нищо. Към него трябва да приложим метод за обратна връзка, който ще запази изпратените данни. +Опитахте ли вече да попълните и изпратите формата? Вероятно сте забелязали, че формата всъщност не прави нищо. Трябва да прикачим callback метод, който ще запази изпратените данни. -Поставете следния ред преди реда `return` във фабриката за компоненти за `commentForm`: +На реда преди `return` във фабриката за компонента `commentForm` поставяме следния ред: ```php -$form->onSuccess[] = [$this, 'commentFormSucceeded']; +$form->onSuccess[] = $this->commentFormSucceeded(...); ``` -Това означава "след успешно изпращане на формуляра извикайте метода `commentFormSucceeded` на текущия презентатор". Този метод все още не съществува, затова нека го създадем. +Предишният запис означава "след успешно изпращане на формата извикай метода `commentFormSucceeded` от текущия presenter". Този метод обаче все още не съществува. Нека го създадем: -```php .{file:app/Presenters/PostPresenter.php} -public function commentFormSucceeded(\stdClass $data): void +```php .{file:app/Presentation/Post/PostPresenter.php} +private function commentFormSucceeded(\stdClass $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); $this->database->table('comments')->insert([ - 'post_id' => $postId, + 'post_id' => $id, 'name' => $data->name, 'email' => $data->email, 'content' => $data->content, ]); - $this->flashMessage('Спасибо за комментарий!', 'success'); + $this->flashMessage('Благодаря за коментара', 'success'); $this->redirect('this'); } ``` -Трябва да го поставите веднага след фабриката за компоненти `commentForm`. +Поставяме този метод непосредствено след фабриката на формата `commentForm`. -Методът new има един аргумент, който е инстанция на подадената форма, създадена от фабриката за компоненти. Извличаме предадените стойности в `$data`. След това вмъкваме данните в таблицата на базата данни `comments`. +Този нов метод има един аргумент, който е екземпляр на формата, която е била изпратена - създадена от фабриката. Получаваме изпратените стойности в `$data`. И след това запазваме данните в таблицата `comments` в базата данни. -Необходимо е да се обяснят още две извиквания на методи. `$this->redirect('this')` буквално пренасочва към текущата страница. Трябва да правите това всеки път, когато формулярът е изпратен, валиден и операцията за обратно извикване е изпълнила това, което е трябвало да направи. Освен това, когато пренасочите страницата след изпращане на формуляра, няма да видите добре познатото съобщение `Вы хотите отправить данные сообщения снова?`, което понякога се вижда в браузъра. (По принцип след изпращане на формуляр чрез метода `POST` винаги трябва да пренасочвате потребителя към действието `GET`). +Има още два метода, които заслужават обяснение. Методът Redirect буквално пренасочва обратно към текущата страница. Това е препоръчително да се направи след всяко изпращане на форма, ако тя съдържа валидни данни и callback-ът е извършил операцията както трябва. Също така, ако пренасочим страницата след изпращане на формата, няма да видим добре познатото съобщение `Искате ли да изпратите данните от формата отново?`, което понякога можем да видим в браузъра. (Общоприето е, че след изпращане на форма с метода `POST` винаги трябва да следва пренасочване към `GET` действие.) -`$this->flashMessage` има за цел да информира потребителя за резултата от дадена операция. Тъй като пренасочваме, съобщението не може да бъде директно предадено на шаблона и показано. Ето защо има метод за запазване и предоставяне на достъп до него при следващото зареждане на страницата. Съобщенията на Flash се показват в стандартния файл `app/Presenters/templates/@layout.latte` и изглеждат по следния начин +Методът `flashMessage` е за информиране на потребителя за резултата от някаква операция. Тъй като пренасочваме, съобщението не може просто да бъде предадено на шаблона и рендирано. Затова съществува този метод, който запазва това съобщение и го прави достъпно при следващото зареждане на страницата. Flash съобщенията се рендират в главния шаблон `app/Presentation/@layout.latte` и изглеждат така: ```latte <div n:foreach="$flashes as $flash" n:class="flash, $flash->type"> @@ -121,20 +121,20 @@ public function commentFormSucceeded(\stdClass $data): void </div> ``` -Както вече знаем, те се предават автоматично на шаблона, така че не е нужно да мислите много за това, а просто работи. За повече информация вижте [документацията |application:presenters#Flash-Messages]. +Както вече знаем, flash съобщенията се предават автоматично на шаблона, така че не трябва да мислим много за това, просто работи. За повече информация [посетете документацията |application:presenters#Flash съобщения]. -Показване на коментари .[#toc-rendering-the-comments] -===================================================== +Рендиране на коментари +====================== -Това е едно от онези неща, които просто ще ви харесат. Базата данни на Nette има страхотна функция, наречена [Explorer |database:explorer]. Спомняте ли си, че създадохме таблици като InnoDB? Adminer създаде така наречените [външни ключове, |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html] които ще ни спестят много работа. +Това е едно от нещата, в които просто ще се влюбите. Nette Database има страхотна функция, наречена [Explorer |database:explorer]. Спомняте ли си още, че нарочно създадохме таблиците в базата данни с помощта на хранилище InnoDB? Adminer така създаде нещо, което се нарича [външни ключове |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html], които ще ни спестят много работа. -Nette Database Explorer използва чужди ключове, за да определи връзките между таблиците, и като знае тези връзки, може автоматично да създава заявки за вас. +Nette Database Explorer използва външни ключове, за да разреши взаимната връзка между таблиците и въз основа на знанието за тези връзки може автоматично да създава заявки към базата данни. -Както си спомняте, предадохме променливата `$post` на шаблона в `PostPresenter::renderShow()` и сега искаме да изброим всички коментари, които имат колона `post_id`, равна на нашата `$post->id`. Можете да направите това, като се обадите на `$post->related('comments')`. Всичко е толкова просто. Разгледайте получения код. +Както със сигурност си спомняте, предадохме променливата `$post` на шаблона с помощта на метода `PostPresenter::renderShow()` и сега искаме да итерираме през всички коментари, които имат стойност на колоната `post_id`, съвпадаща с `$post->id`. Можем да постигнем това чрез извикване на `$post->related('comments')`. Да, толкова е просто. Нека да разгледаме получения код: -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { ... $this->template->post = $post; @@ -142,17 +142,17 @@ public function renderShow(int $postId): void } ``` -В шаблона: +И шаблона: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... -<h2>Комментарии</h2> +<h2>Коментари</h2> <div class="comments"> {foreach $comments as $comment} <p><b><a href="mailto:{$comment->email}" n:tag-if="$comment->email"> {$comment->name} - </a></b>:</p> + </a></b> написа:</p> <div>{$comment->content}</div> {/foreach} @@ -160,13 +160,12 @@ public function renderShow(int $postId): void ... ``` -Обърнете внимание на специалния атрибут `n:tag-if`. Вече знаете как работи `n: атрибуты`. Ако добавите `tag-` към атрибута, той ще обгражда само таговете, но не и тяхното съдържание. Това ви позволява да превърнете името на коментиращия във връзка, ако той е предоставил своя имейл. Резултатите от двата низа са идентични: +Забележете специалния атрибут `n:tag-if`. Вече знаете как работят `n:атрибутите`. Ако добавите префикс `tag-` към атрибута, функционалността се прилага само към HTML тага, а не към съдържанието му. Това ни позволява да направим името на коментатора връзка само в случай, че е предоставил своя имейл. Тези два реда са идентични: ```latte -<strong n:tag-if="$important"> Здравствуйте! </strong> +<strong n:tag-if="$important"> Добър ден! </strong> -{if $important}<strong>{/if} Здравствуйте! {if $important}</strong>{/if} +{if $important}<strong>{/if} Добър ден! {if $important}</strong>{/if} ``` {{priority: -1}} -{{sitename: Быстрый старт с Nette}} diff --git a/quickstart/bg/creating-posts.texy b/quickstart/bg/creating-posts.texy index 054dab8ff2..9464e0e917 100644 --- a/quickstart/bg/creating-posts.texy +++ b/quickstart/bg/creating-posts.texy @@ -1,30 +1,30 @@ Създаване и редактиране на публикации ************************************* -Какво страхотно време. Имаме супер готин нов блог, хората спорят в коментарите и най-накрая имаме време за програмиране. Въпреки че обичаме Adminer, писането на статии за блогове в него не е много удобно. Сега може би е подходящ момент да добавим прост формуляр за добавяне на нови публикации директно от нашето приложение. Да го направим. +Това е страхотно! Имаме супер готин нов блог, хората усилено дискутират в коментарите и най-накрая имаме малко време за още програмиране. Въпреки че Adminer е страхотен инструмент, той не е напълно идеален за писане на нови публикации в блога. Вероятно е дошло времето да създадем проста форма за добавяне на нови публикации директно от приложението. Да го направим. -Нека започнем с проектирането на потребителския интерфейс: +Да започнем с проектирането на потребителския интерфейс: -1. На началната страница нека добавим връзка "Напиши нова публикация". -2. ще се покаже формуляр със заглавие и текстова област за съдържанието. -3. Когато щракнете върху Запази, публикацията в блога ще бъде запазена. +1. На началната страница добавяме връзка "Напиши нова публикация". +2. Тази връзка ще покаже форма със заглавие и текстова област за съдържанието на публикацията. +3. Когато кликнем върху бутона Запази, публикацията ще се запази в базата данни. -По-късно ще добавим и удостоверяване на автентичността и ще позволим само на влезлите в системата потребители да добавят нови публикации. Какъв код трябва да напишем, за да работи? +По-късно ще добавим и влизане и ще позволим добавянето на публикации само на влезли потребители. Но това по-късно. Какъв код трябва да напишем сега, за да работи всичко? -1. Създайте нов презентатор с формуляр за добавяне на публикации. -2. дефинирайте обратна връзка, която ще се задейства след успешно изпращане на формуляра и която ще запази новата публикация в базата данни. -3. Създайте нов шаблон за формуляра. -4. Добавете връзка към формуляра в шаблона на началната страница. +1. Ще създадем нов presenter с форма за добавяне на публикации. +2. Ще дефинираме callback, който ще се стартира след успешно изпращане на формата и който ще запази новата публикация в базата данни. +3. Ще създадем нов шаблон, на който ще бъде тази форма. +4. Ще добавим връзка към формата в шаблона на главната страница. -Нов водещ .[#toc-new-presenter] -=============================== +Нов presenter +============= -Дайте име на новия презентатор `EditPresenter` и го запишете на `app/Presenters/EditPresenter.php`. Той също така трябва да се свърже с базата данни, така че тук отново ще напишем конструктор, който ще изисква свързване с базата данни: +Новият presenter ще наречем `EditPresenter` и ще го запазим в `app/Presentation/Edit/`. Той също трябва да се свърже с базата данни, така че тук отново ще напишем конструктор, който ще изисква връзка с базата данни: -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Edit; use Nette; use Nette\Application\UI\Form; @@ -39,95 +39,95 @@ final class EditPresenter extends Nette\Application\UI\Presenter ``` -Форма за запазване на съобщения .[#toc-form-for-saving-posts] -============================================================= +Форма за запазване на публикации +================================ -Формите и компонентите вече бяха разгледани при добавянето на поддръжка на коментари. Ако сте объркани от темата, вижте отново [как работят формите и компонентите |comments#Form-for-Commenting], ние ще почакаме тук ;) +Вече обяснихме формите и компонентите при създаването на коментари. Ако все още не е ясно, върнете се и прегледайте [създаването на форми и компоненти |comments#Форма за коментиране], ние ще изчакаме тук ;) -Сега добавете този метод в `EditPresenter`: +Сега добавете този метод към presenter-а `EditPresenter`: -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} protected function createComponentPostForm(): Form { $form = new Form; - $form->addText('title', 'Заголовок:') + $form->addText('title', 'Заглавие:') ->setRequired(); - $form->addTextArea('content', 'Содержание:') + $form->addTextArea('content', 'Съдържание:') ->setRequired(); - $form->addSubmit('send', 'Сохранить и опубликовать'); - $form->onSuccess[] = [$this, 'postFormSucceeded']; + $form->addSubmit('send', 'Запази и публикувай'); + $form->onSuccess[] = $this->postFormSucceeded(...); return $form; } ``` -Запазване на нова публикация от формуляр .[#toc-saving-new-post-from-form] -========================================================================== +Запазване на нова публикация от формата +======================================= -Нека да добавим метод за обработка: +Продължаваме с добавянето на метод, който ще обработи данните от формата: -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { $post = $this->database ->table('posts') ->insert($data); - $this->flashMessage('Пост опубликован!', 'success'); + $this->flashMessage("Публикацията беше успешно публикувана.", 'success'); $this->redirect('Post:show', $post->id); } ``` -Малко обяснение: този метод извлича стойностите от формуляра, вмъква ги в базата данни, създава съобщение за потребителя, че публикацията е била успешно публикувана, и пренасочва към страницата, на която е публикувана публикацията, за да можете да видите как изглежда тя. +Само бързо обобщение: този метод получава данните от формата, вмъква ги в базата данни, създава съобщение за потребителя за успешното запазване на публикацията и пренасочва към страницата с новата публикация, така че веднага да видим как изглежда. -Страница за създаване на нова публикация .[#toc-page-for-creating-a-new-post] -============================================================================= +Страница за създаване на нова публикация +======================================== -Нека просто създадем шаблон (`app/Presenters/templates/Edit/create.latte`): +Сега нека създадем шаблона `Edit/create.latte`: -```latte .{file:app/Presenters/templates/Edit/create.latte} +```latte .{file:app/Presentation/Edit/create.latte} {block content} -<h1>Новый пост</h1> +<h1>Нова публикация</h1> {control postForm} ``` -Всичко вече трябва да е ясно. Последният ред показва формата, която ще създадем. +Всичко вече трябва да е ясно. Последният ред рендира формата, която тепърва ще създадем. -Бихме могли да създадем и съответен метод `renderCreate`, но това не е необходимо. Не е необходимо да извличаме данни от базата данни и да ги предаваме на шаблона, така че този метод ще бъде празен. В такива случаи методът може изобщо да не съществува. +Можем да създадем и съответния метод `renderCreate`, но не е необходимо. Не е нужно да извличаме никакви данни от базата данни и да ги предаваме на шаблона, така че този метод би бил празен. В такива случаи методът изобщо не трябва да съществува. -Връзка за създаване на публикации .[#toc-link-for-creating-posts] -================================================================= +Връзка за създаване на публикации +================================= -Вероятно вече знаете как да добавите връзка към `EditPresenter` и нейното действие `create`. Опитайте. +Вероятно вече знаете как да добавите връзка към `EditPresenter` и неговото действие `create`. Опитайте сами. -Просто добавете към файла `app/Presenters/templates/Home/default.latte`: +Просто добавете към файла `app/Presentation/Home/default.latte`: ```latte -<a n:href="Edit:create">Создать пост</a> +<a n:href="Edit:create">Напиши нова публикация</a> ``` -Редактиране на публикации .[#toc-editing-posts] -=============================================== +Редактиране на публикации +========================= -Нека добавим и възможност за редактиране на съществуващи публикации. Това ще бъде съвсем просто - вече имаме `postForm`, а можем да го използваме и за редактиране. +Сега ще добавим и възможност за редактиране на публикация. Ще бъде много лесно. Вече имаме готова форма `postForm` и можем да я използваме и за редактиране. -Ще добавим нова страница `edit` към `EditPresenter`: +Добавяме нова страница `edit` към presenter-а `EditPresenter`: -```php .{file:app/Presenters/EditPresenter.php} -public function renderEdit(int $postId): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +public function renderEdit(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { - $this->error('Пост не найден'); + $this->error('Публикацията не е намерена'); } $this->getComponent('postForm') @@ -135,26 +135,26 @@ public function renderEdit(int $postId): void } ``` -И създайте шаблон `Edit/edit.latte`: +И създаваме още един шаблон `Edit/edit.latte`: -```latte .{file:app/Presenters/templates/Edit/edit.latte} +```latte .{file:app/Presentation/Edit/edit.latte} {block content} -<h1>Редактирование поста</h1> +<h1>Редактирай публикация</h1> {control postForm} ``` -И актуализирайте метода `postFormSucceeded`, който ще може да добавя нова публикация (както сега) или да редактира съществуващите: +И ще променим метода `postFormSucceeded`, който ще може както да добавя нова статия (както прави сега), така и да редактира вече съществуваща статия: -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); - if ($postId) { + if ($id) { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); $post->update($data); } else { @@ -163,26 +163,25 @@ public function postFormSucceeded(array $data): void ->insert($data); } - $this->flashMessage('Пост опубликован', 'success'); + $this->flashMessage('Публикацията беше успешно публикувана.', 'success'); $this->redirect('Post:show', $post->id); } ``` -Ако е посочен `postId`, това означава, че публикацията се редактира. В този случай ще проверим дали публикацията действително съществува и ако е така, ще я актуализираме в базата данни. Ако не е посочен `postId`, това означава, че ще бъде добавена нова публикация. +Ако е наличен параметърът `id`, това означава, че ще редактираме публикация. В този случай проверяваме дали исканата публикация наистина съществува и ако да, я актуализираме в базата данни. Ако параметърът `id` не е наличен, тогава това означава, че трябва да бъде добавена нова публикация. -Но откъде идва `postId`? Това е параметърът, който се предава на метода `renderEdit`. +Но откъде се взема този параметър `id`? Това е параметърът, който беше подаден на метода `renderEdit`. -Сега можете да добавите връзка за промяна на публикацията в шаблона `app/Presenters/templates/Post/show.latte`: +Сега можем да добавим връзка към шаблона `app/Presentation/Post/show.latte`: ```latte -<a n:href="Edit:edit $post->id">Изменить пост</a> +<a n:href="Edit:edit $post->id">Редактирай публикация</a> ``` -За да обобщим .[#toc-summary] -============================= +Резюме +====== -Блогът работи, хората бързо коментират и вече не разчитаме на администратора да добавя нови публикации. Блогът е напълно независим и в него могат да публикуват дори обикновени хора. Но чакайте, вероятно не е нормално всеки, имам предвид наистина всеки в интернет, да може да пише в нашия блог. Изисква се някаква форма на удостоверяване, така че само регистрираните потребители да могат да публикуват. Това ще добавим в следващата глава. +Блогът вече е функционален, посетителите активно го коментират и вече не се нуждаем от Adminer за публикуване. Приложението е напълно независимо и всеки може да добави нова публикация. Но почакайте, това вероятно не е съвсем наред, че всеки - и имам предвид наистина всеки с достъп до интернет - може да добавя нови публикации. Необходима е някаква защита, така че само влезлият потребител да може да добави нова публикация. Ще разгледаме това в следващата глава. {{priority: -1}} -{{sitename: Быстрый старт с Nette}} diff --git a/quickstart/bg/home-page.texy b/quickstart/bg/home-page.texy index fcb701ca5b..ad458becda 100644 --- a/quickstart/bg/home-page.texy +++ b/quickstart/bg/home-page.texy @@ -1,41 +1,41 @@ -Основна страница на блога +Начална страница на блога ************************* .[perex] -Нека създадем начална страница, на която да се показват последните ви публикации. +Сега ще създадем начална страница, показваща последните публикации. -Преди да започнем, трябва да знаете поне някои основни неща за шаблона за проектиране Model-View-Presenter (подобен на MVC((Model-View-Controller))): +Преди да започнем, е необходимо да познавате поне основите на дизайнерския модел Model-View-Presenter (подобен на MVC((Model-View-Controller))): -- **Моделът** е слоят за манипулиране на данни. Той е напълно отделен от останалата част на приложението и комуникира само с презентаторите. +- **Модел** - слой, работещ с данни. Той е напълно отделен от останалата част на приложението. Комуникира само с презентера. -- **Виж** (или _Представяне_) е външният слой за дефиниране. Той показва на потребителя исканите данни с помощта на шаблони. +- **Изглед** - фронт-енд слой. Рендира изискваните данни с помощта на шаблони и ги показва на потребителя. -- **Presenter** (или _Controller_) е нивото на връзката. Водещият свързва модела и изгледа. Той обработва заявките, изисква данни от модела и след това ги предава на текущия изглед. +- **Presenter** (или Контролер) - свързващ слой. Presenter свързва Модела и Изгледа. Обработва заявките, изисква данни от Модела и ги връща обратно на Изгледа. -В случай на много просто приложение, каквото е нашият блог, слоят Model всъщност ще се състои само от заявки към самата база данни - за това не е необходим допълнителен код на PHP. Необходимо е само да създадем слоевете Presenter и View. В Nette всеки презентатор има свои собствени изгледи, така че ще продължим и с двата едновременно. +В случай на прости приложения, като нашия блог, целият моделен слой ще се състои само от заявки към базата данни - засега не се нуждаем от допълнителен код за това. За начало ще създадем само презентери и шаблони. В Nette всеки презентер има свои собствени шаблони, така че ще ги създаваме едновременно. -Създаване на база данни с помощта на Adminer .[#toc-creating-the-database-with-adminer] -======================================================================================= +Създаване на база данни с помощта на Adminer +============================================ -Ще използваме база данни MySQL за съхранение на данни, тъй като това е най-разпространеният избор сред уеб разработчиците. Но ако не ви харесва, не се колебайте да използвате база данни по ваш избор. +За съхраняване на данни ще използваме MySQL база данни, тъй като тя е най-разпространената сред програмистите на уеб приложения. Ако обаче не искате да я използвате, спокойно изберете база данни по ваш избор. -Нека подготвим базата данни, в която ще се съхраняват записите в блога ни. Нека започнем с една таблица за публикациите. +Сега ще подготвим структурата на базата данни, където ще се съхраняват статиите на нашия блог. Ще започнем много просто - ще създадем само една таблица за публикациите. -Можем да изтеглим [Adminer |https://www.adminer.org], за да създадем базата данни, или можете да използвате друг инструмент за управление на бази данни. +За създаване на базата данни можем да изтеглим [Adminer |https://www.adminer.org] или друг ваш любим инструмент за управление на бази данни. -Нека да отворим Adminer и да създадем нова база данни, наречена `quickstart`. +Отваряме Adminer и създаваме нова база данни с име `quickstart`. -Създайте нова таблица с име `posts` и добавете тези колони към нея: -- `id` int, щракнете върху автоматичното увеличаване (AI) -- `title` varchar, дължина 255 -- `content` текст +Създаваме нова таблица с име `posts` и със следните колони: +- `id` int, отметнете autoincrement (AI) +- `title` varchar, length 255 +- `content` text - `created_at` timestamp -Това трябва да изглежда по следния начин +Получената структура трябва да изглежда така: [* adminer-posts.webp *] @@ -49,49 +49,46 @@ CREATE TABLE `posts` ( ``` .[caution] -Много е важно да използвате хранилище за таблици **InnoDB**. Причината за това ще видите по-късно. Засега просто създайте всичко, както е указано, и щракнете върху Запис. Или използвайте пълен код за създаване на таблица и бутон за SQL заявка в Adminer. +Наистина е важно да използвате хранилище **InnoDB**. След малко ще покажем защо. Засега просто го изберете и кликнете върху запазване. -Опитайте се да добавите няколко записа в блога, преди да въведем възможността за добавяне на нови записи директно от нашето приложение. +Преди да създадем възможност за добавяне на статии в базата данни с помощта на приложението, добавете няколко примерни статии в блога ръчно. ```sql INSERT INTO `posts` (`id`, `title`, `content`, `created_at`) VALUES -(1, 'Статья первая', 'Lorem ipusm dolor one', CURRENT_TIMESTAMP), -(2, 'Статья вторая', 'Lorem ipsum dolor two', CURRENT_TIMESTAMP), -(3, 'Статья третья', 'Lorem ipsum dolor three', CURRENT_TIMESTAMP); +(1, 'Article One', 'Lorem ipusm dolor one', CURRENT_TIMESTAMP), +(2, 'Article Two', 'Lorem ipsum dolor two', CURRENT_TIMESTAMP), +(3, 'Article Three', 'Lorem ipsum dolor three', CURRENT_TIMESTAMP); ``` -Свързване с база данни .[#toc-connecting-to-the-database] -========================================================= +Свързване с базата данни +======================== -Сега, след като базата данни е създадена и в нея има няколко публикации, е време да ги покажем на нашата нова блестяща страница. +Сега, когато базата данни вече е създадена и имаме запазени няколко статии в нея, е време да ги покажем на нашата красива нова страница. -Първо, трябва да укажем на приложението коя база данни да използва. Конфигурацията на връзката с базата данни се съхранява във файла `config/local.neon`. Създайте връзка DSN((Име на източника на данни)) и вашите пълномощия. Това трябва да изглежда по следния начин: +Първо трябва да кажем на приложението коя база данни да използва. Връзката с базата данни се настройва във файла `config/common.neon` с помощта на DSN((Data Source Name)) и данните за вход. Трябва да изглежда по следния начин: -```neon .{file:config/local.neon} +```neon .{file:config/common.neon} database: dsn: 'mysql:host=127.0.0.1;dbname=quickstart' - user: *укажите здесь имя пользователя* - password: *укажите здесь пароль* + user: *тук въведете потребителско име* + password: *тук въведете парола за базата данни* ``` .[note] -Не забравяйте да правите отстъпи, когато редактирате този файл. [Форматът NEON |neon:format] приема както интервали, така и табулации, но не и двете заедно! В конфигурационния файл в уеб проекта по подразбиране се използва таблично представяне. +При редактиране на този файл внимавайте с индентацията на редовете. Форматът [NEON |neon:format] приема както индентация с интервали, така и индентация с табулации, но не и двете едновременно. Конфигурационният файл по подразбиране в Web Project използва табулации. -Цялата конфигурация се съхранява в `config/` във файловете `common.neon` и `local.neon`. Файлът `common.neon` съдържа глобалната конфигурация на приложението, докато `local.neon` съдържа само специфични за средата параметри (например разликата между сървъра за разработка и производствения сървър). +Предаване на връзката с базата данни +==================================== -Осъществяване на връзката с базата данни .[#toc-injecting-the-database-connection] -================================================================================== +Presenter `HomePresenter`, който ще се грижи за извеждането на статиите, се нуждае от връзка с базата данни. За да я получим, ще използваме конструктор, който ще изглежда така: -Презентаторът (намиращ се на адрес `app/Presenters/HomePresenter.php`), който ще изписва статиите, трябва да се свърже с базата данни. За да направите това, модифицирайте конструктора по следния начин: - -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; -use Nette\Application\UI\Form; final class HomePresenter extends Nette\Application\UI\Presenter { @@ -105,12 +102,12 @@ final class HomePresenter extends Nette\Application\UI\Presenter ``` -Зареждане на публикации от базата данни .[#toc-loading-posts-from-the-database] -=============================================================================== +Зареждане на публикации от базата данни +======================================= -Нека сега да извлечем публикациите от базата данни и да ги подадем към шаблона, който след това ще визуализира HTML кода. За това е предназначен така нареченият метод *render*: +Сега ще заредим публикациите от базата данни и ще ги изпратим към шаблона, който след това ще ги рендира като HTML код. За това е предназначен така нареченият *render* метод: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} public function renderDefault(): void { $this->template->posts = $this->database @@ -120,41 +117,41 @@ public function renderDefault(): void } ``` -Сега презентаторът има един метод за визуализация `renderDefault()`, който предава данните на изглед, наречен `default`. Шаблоните Preenter могат да бъдат намерени в `app/Presenters/templates/{PresenterName}/{viewName}.latte`, така че в този случай шаблонът ще се намира в `app/Presenters/templates/Home/default.latte`. В шаблона вече е налична променливата `$posts`, която съдържа публикациите от базата данни. +Presenter сега съдържа един рендиращ метод `renderDefault()`, който предава данни от базата данни към шаблона (Изглед). Шаблоните са разположени в `app/Presentation/{PresenterName}/{viewName}.latte`, така че в този случай шаблонът е разположен в `app/Presentation/Home/default.latte`. В шаблона сега ще бъде налична променлива `$posts`, в която са публикациите, получени от базата данни. -Шаблон .[#toc-template] -======================= +Шаблон +====== -Има общ шаблон за цялата страница (наречен *layout*, със заглавие, стилове, колонтитул и т.н.), както и специфични шаблони за всеки изглед (например за показване на списък със записи в блога), които могат да отменят някои части от шаблона за оформление. +За целия уеб сайт имаме на разположение главен шаблон (който се нарича *лейаут*, съдържа хедър, стилове, футър,...) и след това конкретни шаблони за всеки изглед (View) (например за показване на публикации в блога), които могат да презапишат някои части от главния шаблон. -По подразбиране шаблонът за оформление се намира във файла `app/Presenters/templates/@layout.latte`, който съдържа: +По подразбиране лейаут шаблонът е разположен в `app/Presentation/@layout.latte` и съдържа: -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... {include content} ... ``` -`{include content}` вмъква блок с име `content` в главния шаблон. Можете да го определите в шаблоните на всеки изглед. В нашия случай ще редактираме файла `app/Presenters/templates/Home/default.latte` по следния начин +Записът `{include content}` вмъква в главния шаблон блок с име `content`. Него ще дефинираме в шаблоните на отделните изгледи (View). В нашия случай файлът `Home/default.latte` ще променим по следния начин: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - Привет, мир! + Hello World {/block} ``` -Той определя [блока |latte:tags#block] от *съдържание*, който ще бъде вмъкнат в оформлението. Ако опресните браузъра си, ще видите страница с текст "Hello, world! (в изходния код също с HTML заглавието и колонтитула, определени в `@layout.latte`). +С това дефинирахме [блок |latte:tags#block] *content*, който ще бъде вмъкнат в главния лейаут. Ако отново обновим браузъра, ще видим страница с текст "Hello World" (в изходния код също с HTML хедър и футър, дефинирани в `@layout.latte`). -Нека покажем записите в блога - за целта редактираме шаблона по следния начин: +Нека покажем публикациите от блога - ще променим шаблона по следния начин: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - <h1 n:block="title">Мой блог</h1> + <h1>Моят блог</h1> {foreach $posts as $post} <div class="post"> - <div class="date">{$post->created_at|date:'j.m.Y'}</div> + <div class="date">{$post->created_at|date:'F j, Y'}</div> <h2>{$post->title}</h2> @@ -164,42 +161,41 @@ public function renderDefault(): void {/block} ``` -Ако опресните браузъра си, ще видите списък със записите в блога си. Списъкът не е много изискан или цветен, така че не се колебайте да добавите някой [блестящ CSS |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] към `www/css/style.css`, а след това да поставите връзка към този файл в оформлението (файл `@layout.latte`): +Ако обновим браузъра, ще видим списък с всички публикации. Списъкът засега не е много хубав, нито цветен, затова можем да добавим към файла `www/css/style.css` няколко [CSS стила |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] и да го свържем в лейаута: -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <link rel="stylesheet" href="{$basePath}/css/style.css"> </head> ... ``` -Тагът `{foreach}` преминава през всички публикации, предадени на шаблона в променливата `$posts`, и извежда фрагмент от HTML код за всяка публикация. Точно по същия начин се прави и в кода на PHP. +Тагът `{foreach}` итерира през всички публикации, които сме предали на шаблона в променливата `$posts`, и за всяка рендира дадения HTML код. Държи се точно като PHP код. -Функцията `|date` се нарича филтър. Филтрите се използват за форматиране на изхода. Този конкретен филтър преобразува датата (напр. `2013-04-12`) в по-разбираема форма (`12.04.2013`). Филтърът `|truncate` съкращава низ до определена максимална дължина и добавя елипса в края, ако низът е съкратен. Тъй като това е предварителен преглед, няма смисъл да се показва пълното съдържание на статията. Други филтри по подразбиране [можете да намерите в документацията |latte:filters] или можете да създадете свои собствени, ако е необходимо. +На записа `|date:` казваме филтър. Филтрите са предназначени за форматиране на изхода. Този конкретен филтър преобразува дата (напр. `2013-04-12`) в нейната по-четима форма (`April 12, 2013`). Филтърът `|truncate` отрязва низ до посочената максимална дължина и в случай, че низът бъде скъсен, добавя накрая три точки. Тъй като това е преглед, няма смисъл да се показва цялото съдържание на статията. Други филтри по подразбиране [можем да намерим в документацията |latte:filters] или можем да създадем собствени, когато е необходимо. -И още нещо. Можем да направим кода малко по-кратък и следователно по-прост. Можем да заменим *tags Latte* с *n:attributes* по следния начин: +Още нещо. Предишният код можем да съкратим и опростим. Това ще постигнем чрез замяна на *Latte тагове* с *n:атрибути*: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - <h1>My blog</h1> + <h1>Моят блог</h1> <div n:foreach="$posts as $post" class="post"> <div class="date">{$post->created_at|date:'F j, Y'}</div> <h2>{$post->title}</h2> - <div>{$post->content}</div> + <div>{$post->content|truncate:256}</div> </div> {/block} ``` -`n:foreach` просто обвийте *div* с блок *foreach* (той прави точно същото нещо като предишния блок код). +Атрибутът `n:foreach` обвива *div* блока с *foreach* (работи абсолютно същото като предишния код). -За да обобщим .[#toc-summary] -============================= +Резюме +====== -Имаме много проста база данни MySQL с няколко публикации в блогове. Приложението се свързва с базата данни и извежда прост списък с публикации. +Сега имаме много проста MySQL база данни с няколко публикации. Приложението се свързва с тази база данни и извежда прост списък с тези публикации в шаблона. {{priority: -1}} -{{sitename: Быстрый старт с Nette}} diff --git a/quickstart/bg/model.texy b/quickstart/bg/model.texy index 00e8a1f6b2..66edb89e39 100644 --- a/quickstart/bg/model.texy +++ b/quickstart/bg/model.texy @@ -1,13 +1,13 @@ Модел ***** -С разрастването на нашето приложение скоро ще установим, че трябва да извършваме подобни операции с базата данни на различни места и в различни презентатори. Например за извличане на най-новите публикувани статии. Ако подобрим приложението си, като добавим флаг, указващ състоянието на готовност на статиите, трябва също така да преминем през всички места в приложението си и да добавим условие *where*, за да сме сигурни, че се избират само готови статии. +С нарастването на приложението скоро ще открием, че на различни места, в различни презентери, трябва да извършваме подобни операции с базата данни. Например, да получаваме най-новите публикувани статии. Когато подобрим приложението, например като добавим флаг към статиите, дали са в процес на писане, трябва след това да преминем през всички места в приложението, където се извличат статии от базата данни и да добавим условие where, за да се избират само статии, които не са в процес на писане. -В този момент директната работа с базата данни става недостатъчна и е по-разумно да си помогнем с нова функция, която връща публикувани статии. А когато по-късно добавим друга клауза (например да не се показват статии с бъдеща дата), ще редактираме кода само на едно място. +В този момент директната работа с базата данни става недостатъчна и ще бъде по-удобно да си помогнем с нова функция, която ще ни връща публикуваните статии. И когато по-късно добавим друго условие, например да не се показват статии с бъдеща дата, ще променим кода само на едно място. -Ще поставим функцията в класа `PostFacade` и ще я наречем `getPublicArticles()`. +Функцията ще поставим например в клас `PostFacade` и ще я наречем `getPublicArticles()`. -Нека да създадем нашия моделен клас `PostFacade` в директорията `app/Model/`, който да се грижи за нашите статии. Нека го поставим във файла `PostFacade.php`. +В директорията `app/Model/` ще създадем нашия моделен клас `PostFacade`, който ще се грижи за статиите: ```php .{file:app/Model/PostFacade.php} <?php @@ -32,13 +32,13 @@ final class PostFacade } ``` -В този клас предаваме базата данни Explorer:[api:Nette\Database\Explorer]. Това ще ни позволи да използваме функциите на [DI-контейнера |dependency-injection:passing-dependencies]. +В класа с помощта на конструктора ще си поискаме да ни бъде предаден Database Explorer:[api:Nette\Database\Explorer]. Ще използваме силата на [DI контейнера|dependency-injection:passing-dependencies]. -Нека отидем във файла `HomePresenter.php`, който ще редактираме, за да се отървем от зависимостта от `Nette\Database\Explorer`, като я заменим с новата зависимост на създадения от нас клас. +Ще преминем към `HomePresenter`, който ще променим така, че да се отървем от зависимостта от `Nette\Database\Explorer` и да я заменим с нова зависимост към нашия нов клас. -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use App\Model\PostFacade; use Nette; @@ -59,10 +59,9 @@ final class HomePresenter extends Nette\Application\UI\Presenter } ``` -В раздела `use` използваме `App\Model\PostFacade`. По този начин можем да сведем кода на PHP само до PostFacade (не се страхувайте, той работи дори в коментари и вашият интелигентен IDE би трябвало да може да се справи с него). +В секцията use имаме `App\Model\PostFacade`, така че можем да съкратим записа в PHP кода до `PostFacade`. Ще поискаме този обект в конструктора, ще го запишем в свойството `$facade` и ще го използваме в метода renderDefault. -Последната оставаща стъпка е да научим DI-контейнера да създава този обект. Обикновено това се прави чрез добавяне на клауза към файла `config/services.neon` в секцията `services` с пълното име на класа и параметрите на конструктора. -Това го регистрира, така да се каже, и след това обектът се нарича **услуга**. Благодарение на една магия, наречена [autowiring (автоматично свързване) |dependency-injection:autowiring], обикновено не е необходимо да посочваме параметрите на конструктора, защото DI ги разпознава и предава автоматично. Следователно трябва само да посочим името на класа: +Остава последната стъпка, а именно да научим DI контейнера да произвежда този обект. Това обикновено се прави, като във файла `config/services.neon` в секцията `services` добавим точка, посочим пълното име на класа и параметрите на конструктора. Така го регистрираме и обектът след това се нарича **сървис**. Благодарение на магията, наречена [autowiring |dependency-injection:autowiring], обикновено не е необходимо да посочваме параметрите на конструктора, защото DI ги разпознава и предава сам. Би било достатъчно да посочим само името на класа: ```neon .{file:config/services.neon} ... @@ -71,16 +70,15 @@ services: - App\Model\PostFacade ``` -Не е необходимо обаче да добавяте и този ред. Разделът `search` в началото на `services.neon` определя, че всички класове, завършващи на `-Facade` или `-Factory`, ще бъдат търсени автоматично от DI, което се отнася и за `PostFacade`. +Въпреки това, дори този ред не е необходимо да добавяте. В секцията `search` в началото на `services.neon` е дефинирано, че всички класове, завършващи с думата `-Facade` или `-Factory`, DI ще намери сам, което е и случаят с `PostFacade`. -За да обобщим .[#toc-summary] -============================= +Резюме +====== -Класът `PostFacade` изисква `Nette\Database\Explorer` в конструктора и тъй като този клас е регистриран в контейнера DI, контейнерът създава тази инстанция и я предава. По този начин DI създава за нас инстанция на PostFacade и я предава в конструктора на класа HomePresenter, който я е поискал. Нещо като код на матрьошка. :) Всички компоненти изискват само това, от което се нуждаят, и не се интересуват от това къде или как се създава. Създаването се обработва от DI-контейнера. +Класът `PostFacade` си иска в конструктора предаването на `Nette\Database\Explorer` и тъй като този клас е регистриран в DI контейнера, контейнерът създава този екземпляр и го предава. DI така създава за нас екземпляр на `PostFacade` и го предава в конструктора на класа HomePresenter, който го е поискал. Като матрьошка. :) Всички просто казват какво искат и не се интересуват къде и как се създава нещо. За създаването се грижи DI контейнерът. .[note] -Можете да прочетете повече за разгръщането на зависимостите [тук |dependency-injection:introduction] и за конфигурацията [тук |nette:configuring]. +Тук можете да прочетете повече за [dependency injection |dependency-injection:introduction] и [конфигурацията |nette:configuring]. {{priority: -1}} -{{sitename: Быстрый старт с Nette}} diff --git a/quickstart/bg/single-post.texy b/quickstart/bg/single-post.texy index 745d846943..c1167ad7bc 100644 --- a/quickstart/bg/single-post.texy +++ b/quickstart/bg/single-post.texy @@ -1,17 +1,17 @@ -Страница за индивидуално вписване -********************************* +Страница с публикация +********************* .[perex] -Нека да добавим още една страница към нашия блог, за да покажем съдържанието на един конкретен запис в блога. +Сега ще създадем още една страница на блога, която ще показва една конкретна публикация. -Трябва да създадем нов метод за визуализация, който да вземе един конкретен запис в блога и да го предаде на шаблона. Това визуализиране в `HomePresenter` не е много приятно, защото става дума за запис в блог, а не за начална страница. Така че нека създадем нов клас `PostPresenter` и да го поставим в `app/Presenters`. Тя ще се нуждае от връзка с база данни, така че поставете отново кода за инжектиране на зависимости* там. +Трябва да създадем нов render метод, който ще получи една конкретна статия и ще я предаде на шаблона. Да имаме този метод в `HomePresenter` не е много хубаво, защото говорим за статия, а не за начална страница. Затова ще създадем `PostPresenter` в `app/Presentation/Post/`. Този презентер също трябва да се свърже с базата данни, така че тук отново ще напишем конструктор, който ще изисква връзка с базата данни. -`PostPresenter` трябва да изглежда по следния начин: +`PostPresenter` би могъл да изглежда така: -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Post; use Nette; use Nette\Application\UI\Form; @@ -23,103 +23,102 @@ final class PostPresenter extends Nette\Application\UI\Presenter ) { } - public function renderShow(int $postId): void + public function renderShow(int $id): void { $this->template->post = $this->database ->table('posts') - ->get($postId); + ->get($id); } } ``` -Трябва да зададем правилното пространство от имена `App\Presenters` за нашия презентатор. Това зависи от [картографирането на водещия |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7]. +Не трябва да забравяме да посочим правилното пространство от имена `App\Presentation\Post`, което подлежи на настройката на [картографиране на презентери |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7]. -Методът `renderShow` изисква един аргумент - ID на публикацията на водещия. След това той зарежда тази публикация от базата данни и предава резултата на шаблона. +Методът `renderShow` изисква един аргумент - ID на една конкретна статия, която трябва да бъде показана. След това зарежда тази статия от базата данни и я предава на шаблона. -В шаблона `Home/default.latte` добавяме връзка към действието `Post:show`: +В шаблона `Home/default.latte` ще вмъкнем връзка към действието `Post:show`. -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a href="{link Post:show $post->id}">{$post->title}</a></h2> ... ``` -Тагът `{link}` генерира URL адрес, който насочва към действието `Post:show`. Този таг също така предава идентификатора на публикацията като аргумент. +Тагът `{link}` генерира URL адрес, който сочи към действието `Post:show`. Също така предава ID на публикацията като аргумент. -Можем да напишем същото накратко, като използваме n:attribute: +Същото можем да запишем съкратено с помощта на n:атрибут: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a n:href="Post:show $post->id">{$post->title}</a></h2> ... ``` -Атрибутът `n:href` е подобен на тага `{link}`. +Атрибутът `n:href` е аналог на тага `{link}`. -Шаблонът за действието `Post:show` все още не съществува. Можем да отворим връзка към тази публикация. [Tracy |tracy:] ще покаже грешка, че `app/Presenters/templates/Post/show.latte` не съществува. Ако видите друг отчет за грешка, вероятно трябва да активирате mod_rewrite на уеб сървъра си. +За действието `Post:show` обаче все още не съществува шаблон. Можем да опитаме да отворим връзката към тази публикация. [Tracy |tracy:] ще покаже грешка, защото шаблонът `Post/show.latte` все още не съществува. Ако видите друго съобщение за грешка, вероятно ще трябва да активирате `mod_rewrite` на уеб сървъра. -Затова ще създадем `Post/show.latte` с това съдържание: +Ще създадем шаблона `Post/show.latte` със следното съдържание: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} {block content} -<p><a n:href="Home:default">← вернуться к списку постов</a></p> +<p><a n:href="Home:default">← обратно към списъка с публикации</a></p> -<div class="date">{$post->created_at|date:'j.m.Y'}</div> +<div class="date">{$post->created_at|date:'F j, Y'}</div> <h1 n:block="title">{$post->title}</h1> <div class="post">{$post->content}</div> ``` -Обмислете някои точки. +Сега ще разгледаме отделните части на шаблона. -Първият ред започва дефиницията на *именния блок*, наречен `content`, който видяхме по-рано. Той ще се появи в шаблона на *разположението*. +Първият ред започва дефиницията на блок с име "content", точно както беше на началната страница. Този блок отново ще бъде показан в главния шаблон. Както виждате, липсва крайният таг `{/block}`. Той всъщност е незадължителен. -Вторият ред съдържа обратна връзка към списъка с публикации в блога, така че потребителят да може да навигира плавно напред-назад из нашия блог. Отново използваме атрибута `n:href`, така че Nette ще се погрижи да генерира URL адреса вместо нас. Връзката сочи към действието `default` на водещия `Home` (може да напишете просто `n:href="Home:"`, тъй като действието `default` може да бъде пропуснато). +На следващия ред има връзка обратно към списъка със статии в блога, така че потребителят може лесно да се движи между списъка със статии и една конкретна. Тъй като използваме атрибута `n:href`, Nette само се грижи за генерирането на връзки. Връзката сочи към действието `default` на презентера `Home` (можем да напишем също `n:href="Home:"`, защото действието с име `default` може да бъде пропуснато, допълва се автоматично). -Третият ред форматира времевия печат на публикацията, като използва филтъра `date`, както вече знаем. +Третият ред форматира извеждането на датата с помощта на филтъра, който вече познаваме. -Четвъртият ред показва *заглавието* на записа в блога като заглавие `<h1>`. Има една част, която може би не познавате, а именно `n:block="title"`. Можете ли да познаете какво прави? Ако сте прочели внимателно предишните части, споменахме `n: атрибуты`. Ето още един пример. Това е равносилно на: +Четвъртият ред показва *заглавието* на блога в HTML тага `<h1>`. Този таг съдържа атрибут, който може би не познавате (`n:block="title"`). Можете ли да познаете какво прави? Ако сте чели предишната част внимателно, вече знаете, че това е `n:атрибут`. Това е още един техен пример, който е еквивалентен на: ```latte {block title}<h1>{$post->title}</h1>{/block} ``` -Просто казано, той *заменя* блок, наречен `title`. Блокът е дефиниран в *шаблона за оформление* (`/app/Presenters/templates/@layout.latte:11`) и, както в случая на пренаписване на ООП, се пренаписва тук. Следователно `<title>` страници ще съдържа заглавието на показаната публикация. Премахнахме заглавието на страницата и всичко, от което се нуждаехме, беше `n:block="title"`. Чудесно, нали? +Просто казано, този блок предефинира блока с име `title`. Този блок вече е дефиниран в главния *лейаут* шаблон (`/app/Presentation/@layout.latte:11`) и както при припокриването на методи в ООП, точно по същия начин този блок в главния шаблон се припокрива. Така че `<title>` на страницата сега съдържа заглавието на показаната публикация и ни беше достатъчно да използваме само един прост атрибут `n:block="title"`. Страхотно, нали? -Петият и последен ред на шаблона показва пълното съдържание на публикацията ви. +Петият и последен ред на шаблона показва цялото съдържание на една конкретна публикация. -Проверка на ID на публикацията .[#toc-checking-post-id] -======================================================= +Проверка на ID на публикацията +============================== -Какво ще стане, ако някой промени URL адреса и вмъкне несъществуващ `postId`? Трябва да предоставим на потребителя хубава страница за грешка "Страницата не е намерена". Нека да актуализираме метода `render` във файла `PostPresenter.php`: +Какво ще се случи, ако някой промени ID в URL адреса и въведе някое несъществуващо `id`? Трябва да предложим на потребителя хубава грешка от типа "страницата не е намерена". Ще променим малко render метода в `PostPresenter`: -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { - $this->error('Страница не найдена!'); + $this->error('Страницата не беше намерена'); } $this->template->post = $post; } ``` -Ако публикацията не може да бъде намерена, при извикване на `$this->error(...)` ще се покаже страница 404 с хубаво и ясно съобщение. Обърнете внимание, че в режим на разработка няма да видите страницата за грешки. Вместо това Трейси ще покаже изключение с пълна информация, което е доста удобно за разработка. Можете да проверите и двата режима, като просто промените стойността, подадена на `setDebugMode` в `Bootstrap.php`. +Ако публикацията не може да бъде намерена, с извикването на `$this->error(...)` ще покажем страница за грешка 404 с разбираемо съобщение. Внимавайте, че в режим на разработка (localhost) няма да видите тази страница за грешка. Вместо това ще се покаже Tracy с детайли за изключението, което е доста удобно за разработка. Ако искаме да ни се показват и двата режима, е достатъчно само да променим аргумента на метода `setDebugMode` във файла `Bootstrap.php`. -За да обобщим .[#toc-summary] -============================= +Резюме +====== -Имаме база данни с вписвания в блога и уеб приложение с два изгледа: първият показва обобщение на всички скорошни вписвания, а вторият - едно конкретно вписване. +Имаме база данни с публикации и уеб приложение, което има два изгледа - първият показва преглед на всички публикации, а вторият показва една конкретна публикация. {{priority: -1}} -{{sitename: Быстрый старт с Nette}} diff --git a/quickstart/cs/@home.texy b/quickstart/cs/@home.texy index bdedaec45a..8ed549342e 100644 --- a/quickstart/cs/@home.texy +++ b/quickstart/cs/@home.texy @@ -7,9 +7,9 @@ Poznejme spolu Nette Framework, při vytváření jednoduchého blogu s komentá Již po prvních dvou kapitolách budeme mít svůj vlastní funkční blog a budeme moci publikovat své skvělé příspěvky, i když funkce budou zatím do značné míry omezeny. Měli byste si přečíst také následující kapitoly, kde si naprogramujeme přidávání komentářů, editování článků a na závěr blog zabezpečíme. .[tip] -Tento návod předpokládá, že jste přečetli stránku [Instalace |nette:installation] a úspěšně si připravili potřebné nástroje. +Tento návod předpokládá, že jste přečetli stránku [Instalace |nette:installation] a úspěšně si připravili potřebné nástroje. Také předpokládá, že rozumíte [objektově orientovanému programování v PHP |nette:introduction-to-object-oriented-programming]. -Používejte prosím PHP 8.0 nebo novější. Kompletní aplikaci najdete [na GitHubu |https://github.com/nette-examples/quickstart/tree/v4.0]. +Používejte prosím PHP 8.1 nebo novější. Kompletní aplikaci najdete [na GitHubu |https://github.com/nette-examples/quickstart/tree/v4.0]. Uvítací stránka @@ -34,7 +34,7 @@ a uvidíme úvodní stránku Nette Frameworku: Aplikace funguje a můžete začít dělat úpravy. .[note] -Pokud nastal problém, [zkuste těchto pár tipů |nette:troubleshooting#Nejde mi Nette, zobrazuje se bílá stránka]. +Pokud nastal problém, [zkuste těchto pár tipů |nette:troubleshooting#Nejde mi Nette zobrazuje se bílá stránka]. Obsah Web Projectu @@ -45,10 +45,11 @@ Web Project má následující strukturu: /--pre <b>nette-blog/</b> ├── <b>app/</b> ← adresář s aplikací -│ ├── <b>Presenters/</b> ← třídy presenterů -│ │ └── <b>templates/</b>← šablony -│ ├── <b>Router/</b> ← konfigurace URL adres +│ ├── <b>Core/</b> ← základní třídy nutné pro chod +│ ├── <b>Presentation/</b> ← presentery, šablony & spol. +│ │ └── <b>Home/</b> ← adresář presenteru Home │ └── <b>Bootstrap.php</b> ← zaváděcí třída Bootstrap +├── <b>assets/</b> ← zdroje (SCSS, TypeScript, zdrojové obrázky) ├── <b>bin/</b> ← skripty spouštěné z příkazové řádky ├── <b>config/</b> ← konfigurační soubory ├── <b>log/</b> ← logování chyb @@ -56,6 +57,7 @@ Web Project má následující strukturu: ├── <b>vendor/</b> ← knihovny instalované Composerem │ └── <b>autoload.php</b> ← autoloading všech nainstalovaných balíčků └── <b>www/</b> ← veřejný adresář - jediný přístupný z prohlížeče + ├── <b>assets/</b> ← zkompilované statické soubory (CSS, JS, obrázky, …) └── <b>index.php</b> ← prvotní soubor, kterým se aplikace spouští \-- @@ -67,7 +69,7 @@ Nejdůležitější složka je pro nás `app/`. Zde nalezneme soubor `Bootstrap. Úklid ===== -Web Project obsahuje úvodní stránku, kterou smažeme předtím, než začneme něco programovat. Bez obav tedy nahradíme obsah souboru `app/Presenters/templates/Home/default.latte` za "Hello world!". +Web Project obsahuje úvodní stránku, kterou smažeme předtím, než začneme něco programovat. Bez obav tedy nahradíme obsah souboru `app/Presentation/Home/default.latte` za "Hello world!". [* qs-hello.webp .{url:-} *] @@ -76,9 +78,9 @@ Web Project obsahuje úvodní stránku, kterou smažeme předtím, než začneme Tracy (debugger) ================ -Extrémně důležitý nástroj pro vývoj je [ladicí nástroj Tracy |tracy:]. Vyzkoušejte si vyvolání nějaké chyby v souboru `app/Presenters/HomePresenter.php` (např. odstraněním složené závorky v definici třídy HomePresenter) a podívejte se, co se stane. Vyskočí oznamovací stránka, která chybu srozumitelně popisuje. +Extrémně důležitý nástroj pro vývoj je [ladicí nástroj Tracy |tracy:]. Vyzkoušejte si vyvolání nějaké chyby v souboru `app/Presentation/Home/HomePresenter.php` (např. odstraněním složené závorky v definici třídy HomePresenter) a podívejte se, co se stane. Vyskočí oznamovací stránka, která chybu srozumitelně popisuje. -[* qs-tracy.webp .{url:-}(debugger screen) *] +[* qs-tracy.avif .{url:-}(debugger screen) *] Tracy nám ohromně pomůže, až budeme hledat chyby v aplikaci. Také si všimněte plovoucího Tracy Baru v pravém dolním rohu obrazovky, který obsahuje informace z běhu aplikace. @@ -88,8 +90,7 @@ V produkčním módu je Tracy samozřejmě vypnuta a nezobrazuje žádné citliv ```php .{file:app/Bootstrap.php} ... -$configurator->setDebugMode(false); -$configurator->enableTracy($appDir . '/log'); +$this->configurator->setDebugMode(false); ... ``` @@ -116,4 +117,3 @@ composer thanks Zkuste si to! {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/cs/@meta.texy b/quickstart/cs/@meta.texy new file mode 100644 index 0000000000..13ec901393 --- /dev/null +++ b/quickstart/cs/@meta.texy @@ -0,0 +1 @@ +{{sitename: Píšeme první aplikaci!}} diff --git a/quickstart/cs/authentication.texy b/quickstart/cs/authentication.texy index 58063407f3..c63088a535 100644 --- a/quickstart/cs/authentication.texy +++ b/quickstart/cs/authentication.texy @@ -28,9 +28,9 @@ Nyní máme autentifikaci připravenu a musíme připravit uživatelské rozhran Začneme s přihlašovacím formulářem. Již víme, jak formuláře v presenterech fungují. Vytvoříme si tedy presenter `SignPresenter` a zapíšeme metodu `createComponentSignInForm`. Měl by vypadat nějak takto: -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Sign; use Nette; use Nette\Application\UI\Form; @@ -48,7 +48,7 @@ final class SignPresenter extends Nette\Application\UI\Presenter $form->addSubmit('send', 'Přihlásit'); - $form->onSuccess[] = [$this, 'signInFormSucceeded']; + $form->onSuccess[] = $this->signInFormSucceeded(...); return $form; } } @@ -62,7 +62,7 @@ Jsou zde políčka pro uživatelské jméno a heslo. Formulář se bude vykreslovat v šabloně `in.latte`: -```latte .{file:app/Presenters/templates/Sign/in.latte} +```latte .{file:app/Presentation/Sign/in.latte} {block content} <h1 n:block=title>Přihlášení</h1> @@ -77,8 +77,8 @@ Dále doplníme callback pro přihlášení uživatele, který bude volán hned Callback pouze převezme uživatelské jméno a heslo, které uživatel vyplnil a předá je authenticatoru. Po přihlášení přesměrujeme na úvodní stránku. -```php .{file:app/Presenters/SignPresenter.php} -public function signInFormSucceeded(Form $form, \stdClass $data): void +```php .{file:app/Presentation/Sign/SignPresenter.php} +private function signInFormSucceeded(Form $form, \stdClass $data): void { try { $this->getUser()->login($data->username, $data->password); @@ -100,9 +100,9 @@ Zabezpečení presenterů Zabezpečíme formulář pro přidávání a editování příspěvků. Ten je definován v presenteru `EditPresenter`. Cílem je znemožnit přístup na stránku uživatelům, kteří nejsou přihlášeni. -Vytvoříme metodu `startup()`, která se spouští ihned na začátku [životního cyklu presenteru|application:presenters#zivotni-cyklus-presenteru]. Ta přesměruje nepřihlášené uživatele na formulář s přihlášením. +Vytvoříme metodu `startup()`, která se spouští ihned na začátku [životního cyklu presenteru |application:presenters#Životní cyklus presenteru]. Ta přesměruje nepřihlášené uživatele na formulář s přihlášením. -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} public function startup(): void { parent::startup(); @@ -117,7 +117,7 @@ public function startup(): void Skrytí odkazů ------------- -Neautorizovaný uživatel už nemůže vidět stránku *create* ani *edit*, ale stále na ně může vidět odkazy. Ty bychom měli také schovat. Jeden takový odkaz je v šabloně `app/Presenters/templates/Home/default.latte` a měli by jej vidět pouze přihlášení uživatelé. +Neautorizovaný uživatel už nemůže vidět stránku *create* ani *edit*, ale stále na ně může vidět odkazy. Ty bychom měli také schovat. Jeden takový odkaz je v šabloně `app/Presentation/Home/default.latte` a měli by jej vidět pouze přihlášení uživatelé. Můžeme jej schovat využitím *n:atributu* jménem `n:if`. Pokud je tato podmínka `false`, celý tag `<a>`, včetně obsahu, zůstane skrytý. @@ -131,7 +131,7 @@ což je zkratka následujícího zápisu (neplést s `tag-if`): {if $user->isLoggedIn()}<a n:href="Edit:create">Vytvořit příspěvek</a>{/if} ``` -Stejným způsobem skryjeme také odkaz v šabloně `app/Presenters/templates/Post/show.latte`. +Stejným způsobem skryjeme také odkaz v šabloně `app/Presentation/Post/show.latte`. Odkaz na přihlášení @@ -139,7 +139,7 @@ Odkaz na přihlášení Jak se vlastně dostaneme na přihlašovací stránku? Není zde žádný odkaz, který by na ni vedl. Tak si ho tedy přidáme do šablony `@layout.latte`. Pokuste se najít vhodné místo - může to být téměř kdekoliv. -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <ul class="navig"> <li><a n:href="Home:">Články</a></li> @@ -156,7 +156,7 @@ Pokud není uživatel přihlášen, zobrazí se odkaz "Přihlásit". V opačném Jelikož uživatele po odhlášení okamžitě přesměrujeme, není potřeba žádná šablona. Odhlášení vypadá takto: -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} public function actionOut(): void { $this->getUser()->logout(); @@ -177,4 +177,3 @@ Máme odkaz pro přihlášení a také odhlášení uživatele. K ověření jsm Zde si můžete přečíst více o [přihlašování uživatelů |security:authentication] a [Ověřování oprávnění |security:authorization]. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/cs/comments.texy b/quickstart/cs/comments.texy index da94081a2e..a33c122d0c 100644 --- a/quickstart/cs/comments.texy +++ b/quickstart/cs/comments.texy @@ -44,7 +44,7 @@ Prvně musíme vytvořit formulář, který umožní uživatelům příspěvky k Nette Framework využívá koncept *komponent*. **Komponenta** je znovupoužitelná třída, nebo část kódu, který může být přiložen k jiné komponentě. Dokonce i presenter je komponenta. Každá komponenta je vytvořena prostřednictvím továrny. Vytvoříme si tedy továrnu pro formulář na komentáře v presenteru `PostPresenter`. -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} protected function createComponentCommentForm(): Form { $form = new Form; // means Nette\Application\UI\Form @@ -67,7 +67,7 @@ Pojďme si to zase trochu vysvětlit. První řádka vytvoří novou instanci ko Pokud je již formulář definován v presenteru, můžeme ho vykreslit (zobrazit) v šabloně. To uděláme umístěním značky `{control}` na konec šablony, která vykresluje jeden konkrétní příspěvek, do `Post/show.latte`. Protože se komponenta jmenuje `commentForm` (název je odvozen od názvu metody `createComponentCommentForm`), značka bude vypadat takto: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... <h2>Vložte nový příspěvek</h2> @@ -85,18 +85,18 @@ Už jste zkoušeli formulář vyplnit a odeslat? Asi jste si všimli, že ten fo Na řádek před `return` v továrničce pro komponentu `commentForm` umístíme následující řádek: ```php -$form->onSuccess[] = [$this, 'commentFormSucceeded']; +$form->onSuccess[] = $this->commentFormSucceeded(...); ``` Předchozí zápis znamená "po úspěšném odeslání formuláře zavolej metodu `commentFormSucceeded` ze současného presenteru". Tato metoda však ještě neexistuje. Pojďme ji tedy vytvořit: -```php .{file:app/Presenters/PostPresenter.php} -public function commentFormSucceeded(\stdClass $data): void +```php .{file:app/Presentation/Post/PostPresenter.php} +private function commentFormSucceeded(\stdClass $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); $this->database->table('comments')->insert([ - 'post_id' => $postId, + 'post_id' => $id, 'name' => $data->name, 'email' => $data->email, 'content' => $data->content, @@ -113,7 +113,7 @@ Tato nová metoda má jeden argument, což je instance formuláře, který byl o Ještě jsou zde další dvě metody, které si zaslouží vysvětlit. Redirect metoda doslova přesměrovává zpět na aktuální stránku. Toto je vhodné udělat po každém odeslání formuláře, pokud obsahoval validní data a callback provedl operaci tak jak měl. A taky pokud přesměrujeme stránku po odeslání formuláře, neuvidíme dobře známou hlášku `Chcete odeslat data z formuláře znovu?`, kterou občas můžeme v prohlížeči spatřit. (Obecně platí, že po odeslání formuláře metodou `POST` by mělo následovat vždy přesměrování na `GET` akci.) -Metoda `flashMessage` je pro informování uživatele o výsledku nějaké operace. Protože přesměrováváme, zpráva nemůže být jednoduše předána šabloně a vykreslena. Proto je zde tato metoda, která si tuto zprávu uloží a zpřístupní ji při dalším načtení stránky. Flash zprávy jsou vykreslovány v hlavní šabloně `app/Presenters/templates/@layout.latte` a vypadá to takto: +Metoda `flashMessage` je pro informování uživatele o výsledku nějaké operace. Protože přesměrováváme, zpráva nemůže být jednoduše předána šabloně a vykreslena. Proto je zde tato metoda, která si tuto zprávu uloží a zpřístupní ji při dalším načtení stránky. Flash zprávy jsou vykreslovány v hlavní šabloně `app/Presentation/@layout.latte` a vypadá to takto: ```latte <div n:foreach="$flashes as $flash" n:class="flash, $flash->type"> @@ -121,7 +121,7 @@ Metoda `flashMessage` je pro informování uživatele o výsledku nějaké opera </div> ``` -Jak již víme, tak flash zprávy jsou automaticky předávány do šablony, takže o tom nemusíme moc přemýšlet, zkrátka to funguje. Pro více informací [navštivte dokumentaci |application:presenters#flash-zpravy]. +Jak již víme, tak flash zprávy jsou automaticky předávány do šablony, takže o tom nemusíme moc přemýšlet, zkrátka to funguje. Pro více informací [navštivte dokumentaci |application:presenters#Flash zprávy]. Vykreslování komentářů @@ -133,8 +133,8 @@ Nette Database Explorer používá cizí klíče pro vyřešení vzájemného vz Jak si jistě pamatujete, do šablony jsme předali proměnnou `$post` pomocí metody `PostPresenter::renderShow()` a nyní chceme iterovat přes všechny komentáře, které mají hodnotu sloupce `post_id` shodnou s `$post->id`. Toho můžeme docílit voláním `$post->related('comments')`. Ano, takhle jednoduše. Podívejme se na výsledný kód: -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { ... $this->template->post = $post; @@ -144,7 +144,7 @@ public function renderShow(int $postId): void A šablonu: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... <h2>Komentáře</h2> @@ -169,4 +169,3 @@ Všimněte si speciálního atributu `n:tag-if`. Již víte jak `n:atributy` fun ``` {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/cs/creating-posts.texy b/quickstart/cs/creating-posts.texy index 9a40744649..6f47d2fc38 100644 --- a/quickstart/cs/creating-posts.texy +++ b/quickstart/cs/creating-posts.texy @@ -20,11 +20,11 @@ Později také přidáme přihlašování a přidávání příspěvků umožní Nový presenter ============== -Nový presenter nazveme `EditPresenter` a uložíme do `app/Presenters/EditPresenter.php`. Také se potřebuje připojit k databázi, takže zde opět napíšeme konstruktor, který bude vyžadovat databázové připojení: +Nový presenter nazveme `EditPresenter` a uložíme do `app/Presentation/Edit/`. Také se potřebuje připojit k databázi, takže zde opět napíšeme konstruktor, který bude vyžadovat databázové připojení: -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Edit; use Nette; use Nette\Application\UI\Form; @@ -42,11 +42,11 @@ final class EditPresenter extends Nette\Application\UI\Presenter Formulář pro ukládání příspěvků =============================== -Formuláře a komponenty jsme si již vysvětlili při vytváření komentářů. Pokud to stále není jasné, jděte si projít [tvorbu formulářů a komponent |comments#formular-pro-komentovani], my zde zatím počkáme ;) +Formuláře a komponenty jsme si již vysvětlili při vytváření komentářů. Pokud to stále není jasné, jděte si projít [tvorbu formulářů a komponent |comments#Formulář pro komentování], my zde zatím počkáme ;) Nyní přidejme tuto metodu do presenteru `EditPresenter`: -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} protected function createComponentPostForm(): Form { $form = new Form; @@ -56,7 +56,7 @@ protected function createComponentPostForm(): Form ->setRequired(); $form->addSubmit('send', 'Uložit a publikovat'); - $form->onSuccess[] = [$this, 'postFormSucceeded']; + $form->onSuccess[] = $this->postFormSucceeded(...); return $form; } @@ -68,8 +68,8 @@ Ukládání nového příspěvku z formuláře Pokračujeme přidáním metody, která zpracuje data z formuláře: -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { $post = $this->database ->table('posts') @@ -88,7 +88,7 @@ Stránka pro vytvoření nového příspěvku Vytvořme nyní šablonu `Edit/create.latte`: -```latte .{file:app/Presenters/templates/Edit/create.latte} +```latte .{file:app/Presentation/Edit/create.latte} {block content} <h1>Nový příspěvek</h1> @@ -105,7 +105,7 @@ Odkaz na vytváření příspěvků Pravděpodobně již víte jak přidat odkaz na `EditPresenter` a jeho akci `create`. Vyzkoušejte si to. -Stačí do souboru `app/Presenters/templates/Home/default.latte` přidat: +Stačí do souboru `app/Presentation/Home/default.latte` přidat: ```latte <a n:href="Edit:create">Napsat nový příspěvek</a> @@ -119,12 +119,12 @@ Nyní přidáme také možnost editace příspěvku. Bude to velmi jednoduché. Přidáme novou stránku `edit` do presenteru `EditPresenter`: -```php .{file:app/Presenters/EditPresenter.php} -public function renderEdit(int $postId): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +public function renderEdit(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { $this->error('Post not found'); @@ -137,7 +137,7 @@ public function renderEdit(int $postId): void A vytvoříme další šablonu `Edit/edit.latte`: -```latte .{file:app/Presenters/templates/Edit/edit.latte} +```latte .{file:app/Presentation/Edit/edit.latte} {block content} <h1>Upravit příspěvek</h1> @@ -146,15 +146,15 @@ A vytvoříme další šablonu `Edit/edit.latte`: A upravíme metodu `postFormSucceeded`, která bude schopna jednak přidat nový článek (tak jako to dělá teď) a také již existující článek editovat: -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); - if ($postId) { + if ($id) { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); $post->update($data); } else { @@ -168,11 +168,11 @@ public function postFormSucceeded(array $data): void } ``` -Pokud je k dispozici parametr `postId`, znamená to, že budeme upravovat příspěvek. V tom případě ověříme, že požadovaný příspěvek opravdu existuje a pokud ano, aktualizujeme jej v databázi. Pokud parametr `postId` není k dispozici, pak to znamená, že by měl být nový příspěvek přidán. +Pokud je k dispozici parametr `id`, znamená to, že budeme upravovat příspěvek. V tom případě ověříme, že požadovaný příspěvek opravdu existuje a pokud ano, aktualizujeme jej v databázi. Pokud parametr `id` není k dispozici, pak to znamená, že by měl být nový příspěvek přidán. -Kde se však onen parametr `postId` vezme? Jedná se o parametr, který byl vložen do metody `renderEdit`. +Kde se však onen parametr `id` vezme? Jedná se o parametr, který byl vložen do metody `renderEdit`. -Nyní můžeme přidat odkaz do šablony `app/Presenters/templates/Post/show.latte`: +Nyní můžeme přidat odkaz do šablony `app/Presentation/Post/show.latte`: ```latte <a n:href="Edit:edit $post->id">Upravit příspěvek</a> @@ -185,4 +185,3 @@ Shrnutí Blog je nyní funkční, návštěvníci jej aktivně komentují a my již nepotřebujeme na publikaci Adminer. Aplikace je plně nezávislá a kdokoliv může přidat nový příspěvek. Tak moment, to asi není úplně v pořádku, že kdokoliv - a tím myslím opravdu kdokoliv s přístupem na internet - může přidávat nové příspěvky. Je zapotřebí nějaké zabezpečení, aby mohl nový příspěvek přidat pouze přihlášený uživatel. Na to se podíváme v příští kapitole. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/cs/home-page.texy b/quickstart/cs/home-page.texy index b1c620ae4e..63c98d5fdf 100644 --- a/quickstart/cs/home-page.texy +++ b/quickstart/cs/home-page.texy @@ -66,9 +66,9 @@ Připojení k databázi Nyní, když je již databáze vytvořena a máme v ní uloženo pár článků, je ten správný čas zobrazit je na naší krásné nové stránce. -Prvně musíme aplikaci říct, jakou databázi má použít. Připojení k databázi nastavíme v souboru `config/local.neon` pomocí DSN((Data Source Name)) a přihlašovacích údajů. Mělo by to vypadat nějak takto: +Prvně musíme aplikaci říct, jakou databázi má použít. Připojení k databázi nastavíme v souboru `config/common.neon` pomocí DSN((Data Source Name)) a přihlašovacích údajů. Mělo by to vypadat nějak takto: -```neon .{file:config/local.neon} +```neon .{file:config/common.neon} database: dsn: 'mysql:host=127.0.0.1;dbname=quickstart' user: *zde vložte jméno uživatele* @@ -78,20 +78,17 @@ database: .[note] Při editování tohoto souboru dávejte pozor na odsazení řádků. Formát [NEON |neon:format] akceptuje jak odsazení pomocí mezer, tak odsazení pomocí tabulátorů, ale ne obojí zároveň. Výchozí konfigurační soubor ve Web Projectu využívá tabulátory. -Veškerá konfigurace, včetně konfigurace databáze, je uložena v adresáři `/config/` v souborech `common.neon` a `local.neon`. Soubor `common.neon` obsahuje globální nastavení aplikace a `local.neon` pouze ty parametry, které jsou specifické pro aktuální prostředí (rozdíl mezi vývojovým a produkčním serverem apod.). - Předání databázového spojení ============================ Presenter `HomePresenter`, který se bude starat o výpis článků, potřebuje připojení k databázi. Pro jeho získání využijeme konstruktor, který bude vypadat takto: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; -use Nette\Application\UI\Form; final class HomePresenter extends Nette\Application\UI\Presenter { @@ -110,7 +107,7 @@ Načítání příspěvků z databáze Nyní načteme příspěvky z databáze a pošleme je do šablony, která je následně vykreslí jako HTML kód. Pro tohle je určena takzvaná *render* metoda: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} public function renderDefault(): void { $this->template->posts = $this->database @@ -120,7 +117,7 @@ public function renderDefault(): void } ``` -Presenter nyní obsahuje jednu renderovací metodu `renderDefault()`, která předává data z databáze do šablony (View). Šablony jsou umístěny v `app/Presenters/templates/{PresenterName}/{viewName}.latte`, takže v tomto případě je šablona umístěna v `app/Presenters/templates/Home/default.latte`. V šabloně nyní bude k dispozici proměnná `$posts`, ve které jsou příspěvky získané z databáze. +Presenter nyní obsahuje jednu renderovací metodu `renderDefault()`, která předává data z databáze do šablony (View). Šablony jsou umístěny v `app/Presentation/{PresenterName}/{viewName}.latte`, takže v tomto případě je šablona umístěna v `app/Presentation/Home/default.latte`. V šabloně nyní bude k dispozici proměnná `$posts`, ve které jsou příspěvky získané z databáze. Šablona @@ -128,9 +125,9 @@ Presenter nyní obsahuje jednu renderovací metodu `renderDefault()`, která př Pro celou webovou stránku máme k dispozici hlavní šablonu (která se jmenuje *layout*, obsahuje hlavičku, styly, patičku,...) a dále konkrétní šablony pro každý pohled (View) (např. pro zobrazení příspěvků na blogu), které mohou přepsat některé části hlavní šablony. -Ve výchozím stavu je layout šablona umístěna v `app/Presenters/templates/@layout.latte` a obsahuje: +Ve výchozím stavu je layout šablona umístěna v `app/Presentation/@layout.latte` a obsahuje: -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... {include content} ... @@ -138,7 +135,7 @@ Ve výchozím stavu je layout šablona umístěna v `app/Presenters/templates/@l Zápis `{include content}` vkládá do hlavní šablony blok s názvem `content`. Ten budeme definovat v šablonách jednotlivých pohledů (View). V našem případě soubor `Home/default.latte` upravíme následovně: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} Hello World {/block} @@ -148,7 +145,7 @@ Tímto jsme nadefinovali [blok |latte:tags#block] *content*, který bude vložen Pojďme zobrazit příspěvky z blogu - šablonu upravíme následovně: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} <h1>Můj blog</h1> @@ -166,7 +163,7 @@ Pojďme zobrazit příspěvky z blogu - šablonu upravíme následovně: Pokud obnovíme prohlížeč, uvidíme výpis všech příspěvků. Výpis zatím není moc hezký, ani barevný, proto můžeme do souboru `www/css/style.css` přidat pár [CSS stylů |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] a zalinkovat jej v layoutu: -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <link rel="stylesheet" href="{$basePath}/css/style.css"> </head> @@ -179,7 +176,7 @@ Zápisu `|date:` říkáme filtr. Filtry jsou určeny k formátování výstupu. Ještě jedna věc. Předchozí kód můžeme zkrátit a zjednodušit. Toho docílíme záměnou *Latte tagů* za *n:atributy*: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} <h1>Můj blog</h1> @@ -202,4 +199,3 @@ Shrnutí Nyní máme velmi jednoduchou MySQL databázi s pár příspěvky. Aplikace se připojuje k této databázi a vypisuje jednoduchý seznam těchto příspěvků do šablony. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/cs/model.texy b/quickstart/cs/model.texy index 21dacab887..1a31034e5b 100644 --- a/quickstart/cs/model.texy +++ b/quickstart/cs/model.texy @@ -36,9 +36,9 @@ Ve třídě si pomocí konstruktoru necháme předat databázový Explorer:[api: Přepneme se na `HomePresenter`, který upravíme tak, že se zbavíme závislosti na `Nette\Database\Explorer` a nahradíme za novou závislost na naší nové třídě. -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use App\Model\PostFacade; use Nette; @@ -61,8 +61,7 @@ final class HomePresenter extends Nette\Application\UI\Presenter V sekci use máme `App\Model\PostFacade`, tak si můžeme zápis v PHP kódu zkrátit na `PostFacade`. O tento objekt požádáme v konstruktoru, zapíšeme jej do vlastnosti `$facade` a použijeme v metodě renderDefault. -Zbývá poslední krok a to naučit DI container tento objekt vyrábět. To se obvykle dělá tak, že do souboru `config/services.neon` v sekci `services` přidáme odrážku, uvedeme plný název třídy a parametry konstruktoru. -Tím ji takzvaně zaregistrujeme a objekt se pak nazývá **služba**. Díky kouzlu jménem [autowiring |dependency-injection:autowiring] nemusíme většinou parametry konstruktoru uvádět, protože DI je samo rozpozná a předá. Stačilo by tak uvést jen název třídy: +Zbývá poslední krok a to naučit DI container tento objekt vyrábět. To se obvykle dělá tak, že do souboru `config/services.neon` v sekci `services` přidáme odrážku, uvedeme plný název třídy a parametry konstruktoru. Tím ji takzvaně zaregistrujeme a objekt se pak nazývá **služba**. Díky kouzlu jménem [autowiring |dependency-injection:autowiring] nemusíme většinou parametry konstruktoru uvádět, protože DI je samo rozpozná a předá. Stačilo by tak uvést jen název třídy: ```neon .{file:config/services.neon} ... @@ -83,4 +82,3 @@ Třída `PostFacade` si v konstruktoru řekne o předání `Nette\Database\Explo Zde si můžete přečíst více o [dependency injection |dependency-injection:introduction] a [konfiguraci |nette:configuring]. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/cs/single-post.texy b/quickstart/cs/single-post.texy index d0edcb0654..685b32ee62 100644 --- a/quickstart/cs/single-post.texy +++ b/quickstart/cs/single-post.texy @@ -5,13 +5,13 @@ Stránka s příspěvkem Nyní si vytvoříme další stránku blogu, která bude zobrazovat jeden konkrétní příspěvek. -Musíme si vytvořit novou render metodu, která získá jeden konkrétní článek a předá jej do šablony. Mít tuto metodu v `HomePresenter` není moc hezké, protože se bavíme o článku a ne úvodní stránce. Vytvořme si tedy `PostPresenter` v `app/Presenters`. Tento presenter se také potřebuje připojit k databázi, takže zde opět napíšeme konstruktor, který bude vyžadovat databázové připojení. +Musíme si vytvořit novou render metodu, která získá jeden konkrétní článek a předá jej do šablony. Mít tuto metodu v `HomePresenter` není moc hezké, protože se bavíme o článku a ne úvodní stránce. Vytvořme si tedy `PostPresenter` v `app/Presentation/Post/`. Tento presenter se také potřebuje připojit k databázi, takže zde opět napíšeme konstruktor, který bude vyžadovat databázové připojení. `PostPresenter` by mohl tedy vypadat takto: -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Post; use Nette; use Nette\Application\UI\Form; @@ -23,22 +23,22 @@ final class PostPresenter extends Nette\Application\UI\Presenter ) { } - public function renderShow(int $postId): void + public function renderShow(int $id): void { $this->template->post = $this->database ->table('posts') - ->get($postId); + ->get($id); } } ``` -Nesmíme zapomenout uvést správný namespace `App\Presenters`, který podléhá nastavení [mapování presenterů |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7]. +Nesmíme zapomenout uvést správný namespace `App\Presentation\Post`, který podléhá nastavení [mapování presenterů |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7]. Metoda `renderShow` vyžaduje jeden argument - ID jednoho konkrétního článku, který má být zobrazen. Poté tento článek načte z databáze a předá ho do šablony. Do šablony `Home/default.latte` vložíme odkaz na akci `Post:show`. -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a href="{link Post:show $post->id}">{$post->title}</a></h2> ... @@ -49,7 +49,7 @@ Značka `{link}` generuje URL adresu, která směřuje na akci `Post:show`. Tak Totéž můžeme zapsat zkráceně pomocí n:atributu: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a n:href="Post:show $post->id">{$post->title}</a></h2> ... @@ -63,7 +63,7 @@ Pro akci `Post:show` však ještě neexistuje šablona. Můžeme si vyzkoušet o Vytvoříme tedy šablonu `Post/show.latte` s tímto obsahem: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} {block content} <p><a n:href="Home:default">← zpět na výpis příspěvků</a></p> @@ -89,7 +89,7 @@ Třetí řádka formátuje výpis data pomocí filtru, který už známe. {block title}<h1>{$post->title}</h1>{/block} ``` -Jednoduše řečeno, tento blok předefinovává blok s názvem `title`. Tento blok je již definován v hlavní *layout* šabloně (`/app/Presenters/templates/@layout.latte:11`) a tak jako u překrývání metod v OOP, úplně stejně se tento blok v hlavní šabloně překryje. Takže `<title>` stránky nyní obsahuje titulek zobrazeného příspěvku a stačilo nám k tomu použít pouze jednoho jednoduchého atributu `n:block="title"`. Skvělé, že? +Jednoduše řečeno, tento blok předefinovává blok s názvem `title`. Tento blok je již definován v hlavní *layout* šabloně (`/app/Presentation/@layout.latte:11`) a tak jako u překrývání metod v OOP, úplně stejně se tento blok v hlavní šabloně překryje. Takže `<title>` stránky nyní obsahuje titulek zobrazeného příspěvku a stačilo nám k tomu použít pouze jednoho jednoduchého atributu `n:block="title"`. Skvělé, že? Pátá a poslední řádka šablony zobrazuje celý obsah jednoho konkrétního příspěvku. @@ -97,14 +97,14 @@ Pátá a poslední řádka šablony zobrazuje celý obsah jednoho konkrétního Kontrola ID příspěvku ===================== -Co se stane, když někdo změní ID v URL a vloží nějaké neexistující `postId`? Měli bychom uživateli nabídnout pěknou chybu typu "stránka nebyla nalezena". Pozměníme tedy trošku render metodu v `PostPresenter`: +Co se stane, když někdo změní ID v URL a vloží nějaké neexistující `id`? Měli bychom uživateli nabídnout pěknou chybu typu "stránka nebyla nalezena". Pozměníme tedy trošku render metodu v `PostPresenter`: -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { $this->error('Stránka nebyla nalezena'); } @@ -122,4 +122,3 @@ Shrnutí Máme databázi s příspěvky a webovou aplikaci, která má dva pohledy - první zobrazuje přehled všech příspěvků a druhá zobrazuje jeden konkrétní příspěvek. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/de/@home.texy b/quickstart/de/@home.texy index 9b31cf8353..4d95bd9123 100644 --- a/quickstart/de/@home.texy +++ b/quickstart/de/@home.texy @@ -1,19 +1,19 @@ -Erstellen Sie Ihre erste Bewerbung! -*********************************** +Wir schreiben die erste Anwendung! +********************************** .[perex] -Lernen Sie das Nette Framework kennen, indem Sie einen einfachen Blog mit Kommentaren erstellen. Los geht's! +Lernen wir gemeinsam das Nette Framework kennen, indem wir einen einfachen Blog mit Kommentaren erstellen. Los geht's! -Nach den ersten beiden Kapiteln haben Sie Ihren eigenen funktionierenden Blog und können Ihre tollen Beiträge veröffentlichen, auch wenn der Funktionsumfang nach Abschluss dieser beiden Kapitel ziemlich eingeschränkt ist. Um die Dinge für Ihre Nutzer schöner zu machen, sollten Sie auch die folgenden Kapitel lesen und Ihre Anwendung weiter verbessern. +Bereits nach den ersten beiden Kapiteln werden wir unseren eigenen funktionierenden Blog haben und unsere großartigen Beiträge veröffentlichen können, auch wenn die Funktionen vorerst noch stark eingeschränkt sind. Sie sollten auch die folgenden Kapitel lesen, in denen wir das Hinzufügen von Kommentaren, das Bearbeiten von Artikeln und schließlich die Absicherung des Blogs programmieren werden. .[tip] -In diesem Tutorial wird davon ausgegangen, dass Sie das Dokument [Installation |nette:installation] abgeschlossen und Ihr Tooling erfolgreich eingerichtet haben. +Diese Anleitung setzt voraus, dass Sie die Seite [Installation |nette:installation] gelesen und die erforderlichen Werkzeuge erfolgreich vorbereitet haben. Sie setzt auch voraus, dass Sie [objektorientierte Programmierung in PHP |nette:introduction-to-object-oriented-programming] verstehen. -Bitte verwenden Sie PHP 8.0 oder höher. Sie finden die vollständige Anwendung [auf GitHub |https://github.com/nette-examples/quickstart/tree/v4.0]. +Bitte verwenden Sie PHP 8.1 oder neuer. Die vollständige Anwendung finden Sie [auf GitHub |https://github.com/nette-examples/quickstart/tree/v4.0]. -Die Willkommensseite .[#toc-the-welcome-page] -============================================= +Willkommensseite +================ Beginnen wir mit der Erstellung eines neuen Projekts im Verzeichnis `nette-blog`: @@ -21,93 +21,94 @@ Beginnen wir mit der Erstellung eines neuen Projekts im Verzeichnis `nette-blog` composer create-project nette/web-project nette-blog ``` -Zu diesem Zeitpunkt sollte die Willkommensseite des Webprojekts laufen. Probieren Sie es aus, indem Sie Ihren Browser öffnen und die folgende URL aufrufen: +Zu diesem Zeitpunkt sollte die Startseite des Webprojekts bereits funktionieren. Probieren wir es aus, indem wir den Browser unter folgender URL öffnen: ``` http://localhost/nette-blog/www/ ``` -und Sie sollten die Nette Framework Willkommensseite sehen: +und wir sehen die Startseite des Nette Frameworks: [* qs-welcome.webp .{url: http://localhost/nette-blog/www/} *] -Die Anwendung funktioniert und Sie können nun Änderungen vornehmen. +Die Anwendung funktioniert und Sie können mit den Änderungen beginnen. .[note] -Wenn Sie ein Problem haben, [versuchen Sie die folgenden Tipps |nette:troubleshooting#Nette Is Not Working, White Page Is Displayed]. +Wenn ein Problem aufgetreten ist, [versuchen Sie diese Tipps |nette:troubleshooting#Nette funktioniert nicht eine weiße Seite wird angezeigt]. -Inhalt des Webprojekts .[#toc-web-project-s-content] -==================================================== +Inhalt des Webprojekts +====================== -Web Project hat die folgende Struktur: +Das Webprojekt hat folgende Struktur: /--pre <b>nette-blog/</b> -├── <b>app/</b> ← Anwendungsverzeichnis -│ ├── <b>Presenters/</b> ← Presenter-Klassen -│ │ └── <b>templates/</b>← Vorlagen -│ ├── <b>Router/</b> ← Konfiguration von URL-Adressen -│ └── <b>Bootstrap.php</b> ← bootende Klasse Bootstrap -├── <b>bin/</b> ← Skripte für die Kommandozeile +├── <b>app/</b> ← Verzeichnis mit der Anwendung +│ ├── <b>Core/</b> ← grundlegende Klassen, die für den Betrieb notwendig sind +│ ├── <b>Presentation/</b> ← Presenter, Templates & Co. +│ │ └── <b>Home/</b> ← Verzeichnis des Home-Presenters +│ └── <b>Bootstrap.php</b> ← Bootstrap-Klasse zum Starten +├── <b>assets/</b> ← Rohdaten (SCSS, TypeScript, Quellbilder) +├── <b>bin/</b> ← Skripte, die von der Kommandozeile ausgeführt werden ├── <b>config/</b> ← Konfigurationsdateien -├── <b>log/</b> ← Fehlerprotokolle +├── <b>log/</b> ← Fehlerprotokollierung ├── <b>temp/</b> ← temporäre Dateien, Cache, … -├── <b>vendor/</b> ← vom Composer installierte Bibliotheken -│ └── <b>autoload.php</b> ← Automatisches Laden der vom Composer installierten Bibliotheken -└── <b>www/</b> ← öffentlicher Ordner - der einzige Ort, der vom Browser aus zugänglich ist - └── <b>index.php</b> ← Anfangsdatei, die die Anwendung startet +├── <b>vendor/</b> ← Bibliotheken, die von Composer installiert wurden +│ └── <b>autoload.php</b> ← Autoloading aller installierten Pakete +└── <b>www/</b> ← öffentliches Verzeichnis - das einzige, das vom Browser zugänglich ist + ├── <b>assets/</b> ← kompilierte statische Dateien (CSS, JS, Bilder, ...) + └── <b>index.php</b> ← Startdatei, mit der die Anwendung gestartet wird \-- -Das Verzeichnis `www` ist für Bilder, JavaScript, CSS und andere öffentlich verfügbare Dateien vorgesehen. Dies ist das einzige Verzeichnis, auf das der Browser direkt zugreift. Sie können also das Stammverzeichnis Ihres Webservers hierher verweisen (Sie können es im Apache konfigurieren, aber das machen wir später, da es im Moment nicht wichtig ist). +Das Verzeichnis `www/` ist zum Speichern von Bildern, JavaScript-Dateien, CSS-Stilen und anderen öffentlich zugänglichen Dateien vorgesehen. Nur dieses Verzeichnis ist aus dem Internet zugänglich, also stellen Sie das Stammverzeichnis Ihrer Anwendung so ein, dass es genau hierhin zeigt (dies können Sie in der Konfiguration von Apache oder Nginx tun, aber lassen Sie uns das später machen, jetzt ist es nicht wichtig). -Das wichtigste Verzeichnis für Sie ist `app/`. Dort finden Sie die Datei `Bootstrap.php`, in der sich eine Klasse befindet, die das Framework lädt und die Anwendung konfiguriert. Sie aktiviert das [Autoloading |robot-loader:] und richtet den [Debugger |tracy:] und die [Routen |application:routing] ein. +Der wichtigste Ordner für uns ist `app/`. Hier finden wir die Datei `Bootstrap.php`, in der sich die Klasse befindet, die zum Laden des gesamten Frameworks und zur Konfiguration der Anwendung dient. Hier wird das [Autoloading |robot-loader:] aktiviert, der [Debugger |tracy:] und die [Routen |application:routing] werden hier eingestellt. -Aufräumen .[#toc-cleanup] -========================= +Aufräumen +========= -Das Webprojekt enthält eine Willkommensseite, die wir entfernen können - löschen Sie die Datei `app/Presenters/templates/Home/default.latte` und ersetzen Sie sie durch den Text "Hello world!". +Das Webprojekt enthält eine Startseite, die wir löschen, bevor wir mit dem Programmieren beginnen. Ersetzen Sie also bedenkenlos den Inhalt der Datei `app/Presentation/Home/default.latte` durch "Hello world!". [* qs-hello.webp .{url:-} *] -Tracy (Debugger) .[#toc-tracy-debugger] -======================================= +Tracy (Debugger) +================ -Ein äußerst wichtiges Werkzeug für die Entwicklung ist [ein Debugger namens Tracy |tracy:]. Versuchen Sie, einige Fehler in Ihrer `app/Presenters/HomePresenter.php` Datei zu machen (z.B. entfernen Sie eine geschweifte Klammer aus der Definition der Klasse HomePresenter) und sehen Sie, was passiert. Es wird eine rote Bildschirmseite mit einer verständlichen Fehlerbeschreibung erscheinen. +Ein extrem wichtiges Werkzeug für die Entwicklung ist das [Debugging-Tool Tracy |tracy:]. Versuchen Sie, einen Fehler in der Datei `app/Presentation/Home/HomePresenter.php` zu provozieren (z. B. durch Entfernen einer geschweiften Klammer in der Definition der Klasse HomePresenter) und sehen Sie, was passiert. Es erscheint eine Benachrichtigungsseite, die den Fehler verständlich beschreibt. -[* qs-tracy.webp .{url:-}(debugger screen) *] +[* qs-tracy.avif .{url:-}(Debugger-Bildschirm) *] -Tracy wird Ihnen bei der Fehlersuche sehr helfen. Beachten Sie auch die schwebende Tracy-Leiste in der unteren rechten Ecke, die Sie über wichtige Laufzeitdaten informiert. +Tracy wird uns enorm helfen, wenn wir Fehler in der Anwendung suchen. Beachten Sie auch die schwebende Tracy Bar in der unteren rechten Ecke des Bildschirms, die Informationen zum Lauf der Anwendung enthält. [* qs-tracybar.webp .{url:-} *] -Im Produktionsmodus ist Tracy natürlich deaktiviert und gibt keine sensiblen Informationen preis. Alle Fehler werden stattdessen im Verzeichnis `log/` gespeichert. Probieren Sie es einfach aus. Suchen Sie in `app/Bootstrap.php` das folgende Codestück, heben Sie die Kommentare in der Zeile auf und ändern Sie den Parameter für den Methodenaufruf in `false`, so dass es wie folgt aussieht: +Im Produktionsmodus ist Tracy natürlich deaktiviert und zeigt keine sensiblen Informationen an. Alle Fehler werden in diesem Fall im Ordner `log/` gespeichert. Probieren wir es aus. In der Datei `app/Bootstrap.php` kommentieren wir die folgende Zeile aus und ändern den Parameter des Aufrufs auf `false`, sodass der Code wie folgt aussieht: ```php .{file:app/Bootstrap.php} ... -$configurator->setDebugMode(false); -$configurator->enableTracy(__DIR__ . '/../log'); +$this->configurator->setDebugMode(false); ... ``` -Nach dem Aktualisieren der Webseite wird die Seite mit dem roten Bildschirm durch die benutzerfreundliche Meldung ersetzt: +Nach dem Neuladen der Seite sehen wir Tracy nicht mehr. Stattdessen wird eine benutzerfreundliche Meldung angezeigt: -[* qs-fatal.webp .{url:-}(error screen) *] +[* qs-fatal.webp .{url:-}(Fehlerbildschirm) *] -Schauen Sie nun in das Verzeichnis `log/`. Dort finden Sie das Fehlerprotokoll (in der Datei exception.log) und auch die Seite mit der Fehlermeldung (gespeichert in einer HTML-Datei mit einem Namen, der mit `exception` beginnt). +Schauen wir nun in das Verzeichnis `log/`. Hier (in der Datei `exception.log`) finden wir den protokollierten Fehler und auch die bereits bekannte Seite mit der Fehlermeldung (gespeichert als HTML-Datei mit einem Namen, der mit `exception-` beginnt). -Kommentieren Sie die Zeile `// $configurator->setDebugMode(false);` erneut aus. Tracy aktiviert automatisch den Entwicklungsmodus in der Umgebung `localhost` und deaktiviert ihn an anderer Stelle. +Kommentieren wir die Zeile `// $configurator->setDebugMode(false);` wieder ein. Tracy aktiviert den Entwicklermodus automatisch auf localhost und deaktiviert ihn überall sonst. -Jetzt können wir den Fehler beheben und mit der Entwicklung unserer Anwendung fortfahren. +Den Fehler, den wir erstellt haben, können wir beheben und mit dem Schreiben der Anwendung fortfahren. -Dank senden .[#toc-send-thanks] -=============================== +Senden Sie ein Dankeschön +========================= -Wir werden Ihnen einen Trick zeigen, der Open-Source-Autoren glücklich machen wird. Sie können den Bibliotheken, die Ihr Projekt verwendet, auf GitHub ganz einfach einen Stern geben. Führen Sie einfach aus: +Wir zeigen Ihnen einen Trick, mit dem Sie Open-Source-Autoren eine Freude machen können. Geben Sie den Bibliotheken, die Ihr Projekt verwendet, einfach einen Stern auf GitHub. Führen Sie einfach Folgendes aus: ```shell composer thanks @@ -116,4 +117,3 @@ composer thanks Probieren Sie es aus! {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/de/@left-menu.texy b/quickstart/de/@left-menu.texy index 1158df0dac..1659839298 100644 --- a/quickstart/de/@left-menu.texy +++ b/quickstart/de/@left-menu.texy @@ -1,9 +1,9 @@ Tutorial ******** -- [Erstellen Sie Ihre erste Anwendung! |@home] +- [Schreiben wir die erste Anwendung! |@home] - [Blog-Startseite |home-page] -- [Einzelne Beitragsseite |single-post] +- [Seite mit einem Beitrag |single-post] - [Kommentare |comments] - [Erstellen und Bearbeiten von Beiträgen |creating-posts] -- [Modell |Model] +- [Model |Model] - [Authentifizierung |authentication] diff --git a/quickstart/de/@meta.texy b/quickstart/de/@meta.texy new file mode 100644 index 0000000000..5012a8e8a6 --- /dev/null +++ b/quickstart/de/@meta.texy @@ -0,0 +1 @@ +{{sitename: Wir schreiben die erste Anwendung!}} diff --git a/quickstart/de/authentication.texy b/quickstart/de/authentication.texy index d01aaa82a2..7da1bae0d2 100644 --- a/quickstart/de/authentication.texy +++ b/quickstart/de/authentication.texy @@ -1,36 +1,36 @@ Authentifizierung ***************** -Nette stellt Ihnen Richtlinien zur Verfügung, wie Sie die Authentifizierung auf Ihrer Seite programmieren können, zwingt Sie aber nicht dazu, es auf eine bestimmte Weise zu tun. Die Implementierung ist Ihnen überlassen. Nette hat eine Schnittstelle `Nette\Security\Authenticator`, die Sie zwingt, nur eine einzige Methode namens `authenticate` zu implementieren, die den Benutzer auf beliebige Weise findet. +Nette bietet eine Möglichkeit, die Authentifizierung auf unseren Seiten zu programmieren, zwingt uns aber zu nichts. Die Implementierung liegt ganz bei uns. Nette enthält das Interface `Nette\Security\Authenticator`, das nur eine Methode `authenticate` erfordert, die den Benutzer auf beliebige Weise überprüft. -Es gibt viele Möglichkeiten, wie sich ein Benutzer authentifizieren kann. Die gebräuchlichste Methode ist die *passwortbasierte Authentifizierung* (der Benutzer gibt seinen Namen oder seine E-Mail-Adresse und ein Passwort an), aber es gibt auch andere Möglichkeiten. Vielleicht kennen Sie die "Login mit Facebook"-Schaltflächen auf vielen Websites, oder Sie melden sich über Google/Twitter/GitHub oder eine andere Website an. Mit Nette können Sie jede beliebige Authentifizierungsmethode verwenden oder sie miteinander kombinieren. Es liegt ganz bei Ihnen. +Es gibt viele Möglichkeiten, wie ein Benutzer authentifiziert werden kann. Die häufigste Methode ist die Authentifizierung per Passwort (der Benutzer gibt seinen Namen oder seine E-Mail-Adresse und sein Passwort an), aber es gibt auch andere Methoden. Vielleicht kennen Sie die Schaltflächen „Mit Facebook anmelden“ oder die Anmeldung über Google/Twitter/GitHub auf einigen Websites. Mit Nette können wir jede beliebige Anmeldemethode verwenden oder sie sogar kombinieren. Es liegt ganz bei uns. -Normalerweise würden Sie Ihren eigenen Authentifikator schreiben, aber für diesen einfachen kleinen Blog werden wir den eingebauten Authentifikator verwenden, der sich anhand eines Passworts und eines Benutzernamens authentifiziert, die in einer Konfigurationsdatei gespeichert sind. Das ist gut für Testzwecke. Wir fügen also den folgenden *Sicherheits*-Abschnitt in die `config/common.neon` Konfigurationsdatei ein: +Normalerweise würden wir unseren eigenen Authenticator schreiben, aber für diesen einfachen kleinen Blog verwenden wir den integrierten Authenticator, der die Anmeldung anhand eines in der Konfigurationsdatei gespeicherten Passworts und Benutzernamens durchführt. Er eignet sich gut für Testzwecke. Fügen wir also den folgenden `security`-Abschnitt zur Konfigurationsdatei `config/common.neon` hinzu: ```neon .{file:config/common.neon} security: users: - admin: secret # user 'admin', password 'secret' + admin: secret # Benutzer 'admin', Passwort 'secret' ``` -Nette wird automatisch einen Dienst im DI-Container erstellen. +Nette erstellt automatisch einen Dienst im DI-Container. -Anmeldeformular .[#toc-sign-in-form] -==================================== +Anmeldeformular +=============== -Wir haben nun den Backend-Teil der Authentifizierung fertig und müssen eine Benutzeroberfläche bereitstellen, über die sich der Benutzer anmelden kann. Lassen Sie uns einen neuen Präsentator namens *SignPresenter* erstellen, der +Nun haben wir die Authentifizierung vorbereitet und müssen die Benutzeroberfläche für die Anmeldung erstellen. Erstellen wir also einen neuen Presenter namens `SignPresenter`, der: -- ein Anmeldeformular anzeigt (und nach Benutzername und Passwort fragt) -- den Benutzer authentifiziert, wenn das Formular abgeschickt wird -- eine Abmeldefunktion bereitstellt +- das Anmeldeformular anzeigt (mit Anmeldenamen und Passwort) +- nach dem Absenden des Formulars den Benutzer authentifiziert +- die Möglichkeit zur Abmeldung bietet -Lassen Sie uns mit dem Anmeldeformular beginnen. Sie wissen bereits, wie Formulare in einem Presenter funktionieren. Erstellen Sie die `SignPresenter` und die Methode `createComponentSignInForm`. Es sollte wie folgt aussehen: +Beginnen wir mit dem Anmeldeformular. Wir wissen bereits, wie Formulare in Presentern funktionieren. Erstellen wir also den Presenter `SignPresenter` und schreiben die Methode `createComponentSignInForm`. Sie sollte etwa so aussehen: -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Sign; use Nette; use Nette\Application\UI\Form; @@ -40,69 +40,69 @@ final class SignPresenter extends Nette\Application\UI\Presenter protected function createComponentSignInForm(): Form { $form = new Form; - $form->addText('username', 'Username:') - ->setRequired('Please enter your username.'); + $form->addText('username', 'Benutzername:') + ->setRequired('Bitte geben Sie Ihren Benutzernamen ein.'); - $form->addPassword('password', 'Password:') - ->setRequired('Please enter your password.'); + $form->addPassword('password', 'Passwort:') + ->setRequired('Bitte geben Sie Ihr Passwort ein.'); - $form->addSubmit('send', 'Sign in'); + $form->addSubmit('send', 'Anmelden'); - $form->onSuccess[] = [$this, 'signInFormSucceeded']; + $form->onSuccess[] = $this->signInFormSucceeded(...); return $form; } } ``` -Es gibt eine Eingabe für Benutzernamen und Passwort. +Es gibt Felder für Benutzername und Passwort. -Vorlage .[#toc-template] ------------------------- +Template +-------- -Das Formular wird in der Vorlage gerendert `in.latte` +Das Formular wird im Template `in.latte` gerendert: -```latte .{file:app/Presenters/templates/Sign/in.latte} +```latte .{file:app/Presentation/Sign/in.latte} {block content} -<h1 n:block=title>Sign in</h1> +<h1 n:block=title>Anmeldung</h1> {control signInForm} ``` -Login-Handler .[#toc-login-handler] ------------------------------------ +Anmelde-Callback +---------------- -Wir fügen auch einen *Form Handler* für die Anmeldung des Benutzers hinzu, der direkt nach dem Absenden des Formulars aufgerufen wird. +Als Nächstes fügen wir den Callback für die Benutzeranmeldung hinzu, der direkt nach dem erfolgreichen Absenden des Formulars aufgerufen wird. -Der Handler nimmt einfach den Benutzernamen und das Passwort, die der Benutzer eingegeben hat, und übergibt sie an den zuvor definierten Authentifikator. Nachdem sich der Benutzer angemeldet hat, leiten wir ihn auf die Homepage um. +Der Callback übernimmt lediglich den vom Benutzer eingegebenen Benutzernamen und das Passwort und übergibt sie an den Authenticator. Nach der Anmeldung leiten wir zur Startseite weiter. -```php .{file:app/Presenters/SignPresenter.php} -public function signInFormSucceeded(Form $form, \stdClass $data): void +```php .{file:app/Presentation/Sign/SignPresenter.php} +private function signInFormSucceeded(Form $form, \stdClass $data): void { try { $this->getUser()->login($data->username, $data->password); $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { - $form->addError('Incorrect username or password.'); + $form->addError('Falscher Benutzername oder falsches Passwort.'); } } ``` -Die Methode [User::login() |api:Nette\Security\User::login()] sollte eine Ausnahme auslösen, wenn der Benutzername oder das Passwort nicht mit den zuvor definierten Werten übereinstimmt. Wie wir bereits wissen, würde dies zu einem roten [Tracy-Bildschirm |tracy:] führen, oder, im Produktionsmodus, zu einer Meldung über einen internen Serverfehler. Das würde uns nicht gefallen. Deshalb fangen wir die Ausnahme ab und fügen eine nette und freundliche Fehlermeldung in das Formular ein. +Die Methode [User::login() |api:Nette\Security\User::login()] löst eine Ausnahme aus, wenn Benutzername und Passwort nicht mit den Angaben in der Konfigurationsdatei übereinstimmen. Wie wir bereits wissen, kann dies zu einer roten Fehlerseite oder im Produktionsmodus zu einer Meldung über einen Serverfehler führen. Das wollen wir jedoch nicht. Daher fangen wir diese Ausnahme ab und übergeben eine schöne, benutzerfreundliche Fehlermeldung an das Formular. -Wenn der Fehler im Formular auftritt, wird die Seite mit dem Formular erneut gerendert, und über dem Formular erscheint eine nette Meldung, die den Benutzer darüber informiert, dass er einen falschen Benutzernamen oder ein falsches Passwort eingegeben hat. +Sobald ein Fehler im Formular auftritt, wird die Seite mit dem Formular neu gezeichnet und über dem Formular wird eine nette Nachricht angezeigt, die den Benutzer darüber informiert, dass er einen falschen Benutzernamen oder ein falsches Passwort eingegeben hat. -Sicherheit der Präsentatoren .[#toc-security-of-presenters] -=========================================================== +Absicherung von Presentern +========================== -Wir werden ein Formular zum Hinzufügen und Bearbeiten von Beiträgen sichern. Es ist im Präsentator `EditPresenter` definiert. Ziel ist es, zu verhindern, dass nicht angemeldete Benutzer auf die Seite zugreifen. +Wir sichern das Formular zum Hinzufügen und Bearbeiten von Beiträgen ab. Dieses ist im Presenter `EditPresenter` definiert. Ziel ist es, Benutzern, die nicht angemeldet sind, den Zugriff auf die Seite zu verwehren. -Wir erstellen eine Methode `startup()`, die sofort zu Beginn des [Lebenszyklus des Präsentators |application:presenters#life-cycle-of-presenter] gestartet wird. Diese leitet nicht eingeloggte Benutzer zum Anmeldeformular um. +Wir erstellen die Methode `startup()`, die sofort zu Beginn des [Lebenszyklus des Presenters |application:presenters#Lebenszyklus des Presenters] ausgeführt wird. Sie leitet nicht angemeldete Benutzer zum Anmeldeformular weiter. -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} public function startup(): void { parent::startup(); @@ -114,67 +114,66 @@ public function startup(): void ``` -Links ausblenden .[#toc-hide-links] ------------------------------------ +Verbergen von Links +------------------- -Ein nicht authentifizierter Benutzer kann weder die Seite *Erstellen* noch die Seite *Bearbeiten* sehen, aber er kann immer noch die Links sehen, die auf sie verweisen. Diese sollten wir ebenfalls ausblenden. Ein solcher Link befindet sich in `app/Presenters/templates/Home/default.latte`, und er sollte nur sichtbar sein, wenn der Benutzer angemeldet ist. +Ein nicht autorisierter Benutzer kann die Seiten `create` und `edit` nicht mehr sehen, aber er kann immer noch die Links dorthin sehen. Diese sollten wir ebenfalls verbergen. Ein solcher Link befindet sich im Template `app/Presentation/Home/default.latte` und sollte nur für angemeldete Benutzer sichtbar sein. -Wir können ihn mit einem *n:Attribut* namens `n:if` ausblenden. Wenn die darin enthaltene Anweisung `false` lautet, werden der gesamte `<a>` Tag und sein Inhalt werden nicht angezeigt: +Wir können ihn mithilfe des *n:Attributs* namens `n:if` verbergen. Wenn diese Bedingung `false` ist, bleibt das gesamte `<a>`-Tag einschließlich seines Inhalts verborgen. ```latte -<a n:href="Edit:create" n:if="$user->isLoggedIn()">Create post</a> +<a n:href="Edit:create" n:if="$user->isLoggedIn()">Beitrag erstellen</a> ``` -Dies ist eine Abkürzung für (nicht zu verwechseln mit `tag-if`): +was eine Abkürzung für die folgende Schreibweise ist (nicht zu verwechseln mit `tag-if`): ```latte -{if $user->isLoggedIn()}<a n:href="Edit:create">Create post</a>{/if} +{if $user->isLoggedIn()}<a n:href="Edit:create">Beitrag erstellen</a>{/if} ``` -Sie sollten den Bearbeitungslink unter `app/Presenters/templates/Post/show.latte` auf ähnliche Weise ausblenden. +Auf die gleiche Weise verbergen wir auch den Link im Template `app/Presentation/Post/show.latte`. -Link zum Anmeldeformular .[#toc-login-form-link] -================================================ +Link zur Anmeldung +================== -Hey, aber wie kommen wir auf die Anmeldeseite? Es gibt keinen Link, der auf sie verweist. Fügen wir einen in die `@layout.latte` Vorlagendatei ein. Versuchen Sie, einen schönen Platz zu finden, es kann überall sein, wo es Ihnen am besten gefällt. +Wie gelangen wir eigentlich zur Anmeldeseite? Es gibt keinen Link, der dorthin führt. Fügen wir ihn also dem Template `@layout.latte` hinzu. Versuchen Sie, einen geeigneten Platz zu finden - er kann fast überall sein. -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <ul class="navig"> - <li><a n:href="Home:">Home</a></li> + <li><a n:href="Home:">Artikel</a></li> {if $user->isLoggedIn()} - <li><a n:href="Sign:out">Sign out</a></li> + <li><a n:href="Sign:out">Abmelden</a></li> {else} - <li><a n:href="Sign:in">Sign in</a></li> + <li><a n:href="Sign:in">Anmelden</a></li> {/if} </ul> ... ``` -Wenn der Benutzer noch nicht eingeloggt ist, wird der Link "Anmelden" angezeigt. Andernfalls zeigen wir den Link "Abmelden" an. Wir fügen diese Aktion in SignPresenter hinzu. +Wenn der Benutzer nicht angemeldet ist, wird der Link "Anmelden" angezeigt. Andernfalls wird der Link "Abmelden" angezeigt. Diese Aktion fügen wir auch dem `SignPresenter` hinzu. -Die Abmeldeaktion sieht wie folgt aus, und da wir den Benutzer sofort umleiten, ist keine Ansichtsvorlage erforderlich. +Da wir den Benutzer nach der Abmeldung sofort weiterleiten, ist kein Template erforderlich. Die Abmeldung sieht wie folgt aus: -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} public function actionOut(): void { $this->getUser()->logout(); - $this->flashMessage('You have been signed out.'); + $this->flashMessage('Abmeldung erfolgreich.'); $this->redirect('Home:'); } ``` -Sie ruft einfach die Methode `logout()` auf und zeigt dann eine nette Nachricht an den Benutzer an. +Es wird nur die Methode `logout()` aufgerufen und anschließend eine nette Nachricht angezeigt, die die erfolgreiche Abmeldung bestätigt. -Zusammenfassung .[#toc-summary] -=============================== +Zusammenfassung +=============== -Wir haben einen Link zum Anmelden und zum Abmelden des Benutzers. Da es sich um eine einfache Testanwendung handelt, haben wir zur Authentifizierung den eingebauten Authentifikator verwendet und die Anmeldedaten in der Konfigurationsdatei hinterlegt. Wir haben auch die Bearbeitungsformulare gesichert, so dass nur angemeldete Benutzer Beiträge hinzufügen und bearbeiten können. +Wir haben einen Link zur Anmeldung und auch zur Abmeldung des Benutzers. Zur Authentifizierung haben wir den integrierten Authenticator verwendet und die Anmeldedaten befinden sich in der Konfigurationsdatei, da es sich um eine einfache Testanwendung handelt. Wir haben auch die Bearbeitungsformulare gesichert, sodass nur angemeldete Benutzer Beiträge hinzufügen und bearbeiten können. .[note] -Hier können Sie mehr über [Benutzeranmeldung |security:authentication] und [Autorisierung |security:authorization] lesen. +Hier können Sie mehr über die [Benutzeranmeldung |security:authentication] und die [Berechtigungsprüfung |security:authorization] lesen. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/de/comments.texy b/quickstart/de/comments.texy index 149c908627..312f41b658 100644 --- a/quickstart/de/comments.texy +++ b/quickstart/de/comments.texy @@ -1,28 +1,28 @@ Kommentare ********** -Der Blog wurde eingerichtet, wir haben einige sehr gute Blogbeiträge geschrieben und sie über Adminer veröffentlicht. Die Leute lesen den Blog und sind sehr begeistert von unseren Ideen. Wir erhalten jeden Tag viele E-Mails mit Lob. Aber was nützt all das Lob, wenn wir es nur in der E-Mail bekommen, so dass niemand sonst es lesen kann? Wäre es nicht besser, wenn die Leute direkt im Blog kommentieren könnten, damit alle anderen lesen können, wie toll wir sind? +Wir haben den Blog auf den Webserver hochgeladen und einige sehr interessante Beiträge mit Adminer veröffentlicht. Die Leute lesen unseren Blog und sind sehr begeistert davon. Wir erhalten jeden Tag viele E-Mails mit Lob. Aber was nützt all dieses Lob, wenn wir es nur per E-Mail haben und niemand es lesen kann? Es wäre besser, wenn der Leser den Artikel direkt kommentieren könnte, damit jeder lesen kann, wie großartig wir sind. -Lasst uns alle Artikel kommentierbar machen. +Programmieren wir also Kommentare. -Eine neue Tabelle erstellen .[#toc-creating-a-new-table] -======================================================== +Erstellen einer neuen Tabelle +============================= -Starten Sie Adminer erneut und erstellen Sie eine neue Tabelle mit dem Namen `comments` mit diesen Spalten: +Wir starten Adminer und erstellen eine Tabelle `comments` mit diesen Spalten: -- `id` int, Prüfung auf Autoinkrement (AI) -- `post_id`, ein Fremdschlüssel, der auf die Tabelle `posts` verweist +- `id` int, Autoincrement (AI) ankreuzen +- `post_id`, Fremdschlüssel, der auf die Tabelle `posts` verweist - `name` varchar, Länge 255 - `email` varchar, Länge 255 - `content` text -- `created_at` Zeitstempel +- `created_at` timestamp -Es sollte wie folgt aussehen: +Die Tabelle sollte also ungefähr so aussehen: [* adminer-comments.webp *] -Vergessen Sie nicht, die InnoDB-Tabellenspeicherung zu verwenden und klicken Sie auf Speichern. +Vergessen Sie nicht, wieder den InnoDB-Speicher zu verwenden. ```sql CREATE TABLE `comments` ( @@ -37,22 +37,22 @@ CREATE TABLE `comments` ( ``` -Formular für Kommentare .[#toc-form-for-commenting] -=================================================== +Kommentarformular +================= -Zuerst müssen wir ein Formular erstellen, das es den Benutzern ermöglicht, auf unserer Seite zu kommentieren. Nette Framework hat eine großartige Unterstützung für Formulare. Sie können in einem Presenter konfiguriert und in einer Vorlage gerendert werden. +Zuerst müssen wir ein Formular erstellen, das es Benutzern ermöglicht, Beiträge zu kommentieren. Das Nette Framework bietet eine erstaunliche Unterstützung für Formulare. Wir können sie im Presenter konfigurieren und im Template rendern. -Nette Framework hat ein Konzept von *Komponenten*. Eine **Komponente** ist eine wiederverwendbare Klasse oder ein Stück Code, das mit einer anderen Komponente verbunden werden kann. Auch ein Präsentator ist eine Komponente. Jede Komponente wird mit Hilfe der Komponentenfabrik erstellt. Definieren wir also die Kommentarformularfabrik in `PostPresenter`. +Das Nette Framework verwendet das Konzept der *Komponenten*. Eine **Komponente** ist eine wiederverwendbare Klasse oder ein Codeabschnitt, der an eine andere Komponente angehängt werden kann. Sogar ein Presenter ist eine Komponente. Jede Komponente wird über eine Factory erstellt. Erstellen wir also eine Factory für das Kommentarformular im Presenter `PostPresenter`. -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} protected function createComponentCommentForm(): Form { $form = new Form; // bedeutet Nette\Application\UI\Form - $form->addText('name', 'Ihr Name:') + $form->addText('name', 'Name:') ->setRequired(); - $form->addEmail('email', 'Email:'); + $form->addEmail('email', 'E-Mail:'); $form->addTextArea('content', 'Kommentar:') ->setRequired(); @@ -63,78 +63,78 @@ protected function createComponentCommentForm(): Form } ``` -Erklären wir sie ein wenig. Die erste Zeile erzeugt eine neue Instanz der Komponente `Form`. Die folgenden Methoden fügen HTML-Eingaben in die Formulardefinition ein. `->addText` wird als `<input type=text name=name>`mit `<label>Your name:</label>`. Wie Sie vielleicht schon erraten haben, fügt die `->addTextArea` ein `<textarea>` und `->addSubmit` fügt ein `<input type=submit>`. Es gibt noch weitere Methoden dieser Art, aber das ist alles, was Sie im Moment wissen müssen. Sie können [mehr in der Dokumentation erfahren |forms:]. +Lassen Sie uns das wieder ein wenig erklären. Die erste Zeile erstellt eine neue Instanz der Komponente `Form`. Die folgenden Methoden fügen HTML-Inputs zur Definition dieses Formulars hinzu. `->addText` wird als `<input type="text" name="name">` mit `<label>Name:</label>` gerendert. Wie Sie wahrscheinlich schon richtig erraten haben, wird `->addTextArea` als `<textarea>` und `->addSubmit` als `<input type="submit">` gerendert. Es gibt noch viel mehr ähnliche Methoden, aber diese reichen vorerst für dieses Formular aus. Mehr dazu finden Sie [in der Dokumentation|forms:]. -Sobald die Formularkomponente in einem Presenter definiert ist, können wir sie in einer Vorlage rendern (anzeigen). Dazu platzieren Sie das Tag `{control}` am Ende der Post-Detail-Vorlage in `Post/show.latte`. Da der Name der Komponente `commentForm` ist (er leitet sich vom Namen der Methode `createComponentCommentForm` ab), sieht das Tag wie folgt aus +Wenn das Formular bereits im Presenter definiert ist, können wir es im Template rendern (anzeigen). Dazu platzieren wir das Tag `{control}` am Ende des Templates, das einen bestimmten Beitrag rendert, in `Post/show.latte`. Da die Komponente `commentForm` heißt (der Name leitet sich vom Namen der Methode `createComponentCommentForm` ab), sieht das Tag wie folgt aus: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... -<h2>Post new comment</h2> +<h2>Neuen Kommentar einfügen</h2> {control commentForm} ``` -Wenn Sie sich nun die Details eines Beitrags ansehen, wird ein neues Formular für die Eingabe von Kommentaren angezeigt. +Wenn Sie nun die Seite mit den Beitragsdetails anzeigen, sehen Sie am Ende ein neues Formular für Kommentare. -Speichern in der Datenbank .[#toc-saving-to-database] -===================================================== +Speichern in der Datenbank +========================== -Haben Sie versucht, einige Daten zu übermitteln? Vielleicht haben Sie bemerkt, dass das Formular keine Aktion ausführt. Es ist einfach da, sieht cool aus und tut nichts. Wir müssen eine Callback-Methode anhängen, die die übermittelten Daten speichert. +Haben Sie schon versucht, das Formular auszufüllen und abzusenden? Sie haben wahrscheinlich bemerkt, dass das Formular eigentlich nichts tut. Wir müssen eine Callback-Methode anhängen, die die gesendeten Daten speichert. -Fügen Sie die folgende Zeile vor der Zeile `return` in der Komponentenfabrik für `commentForm` ein: +Fügen Sie die folgende Zeile in die Factory für die Komponente `commentForm` vor `return` ein: -```php -$form->onSuccess[] = [$this, 'commentFormSucceeded']; +```php .{file:app/Presentation/Post/PostPresenter.php} +$form->onSuccess[] = $this->commentFormSucceeded(...); ``` -Sie bedeutet "nachdem das Formular erfolgreich abgeschickt wurde, rufe die Methode `commentFormSucceeded` des aktuellen Präsentators auf". Diese Methode existiert noch nicht, also erstellen wir sie. +Die vorherige Anweisung bedeutet "rufe nach erfolgreichem Absenden des Formulars die Methode `commentFormSucceeded` des aktuellen Presenters auf". Diese Methode existiert jedoch noch nicht. Erstellen wir sie also: -```php .{file:app/Presenters/PostPresenter.php} -public function commentFormSucceeded(\stdClass $data): void +```php .{file:app/Presentation/Post/PostPresenter.php} +private function commentFormSucceeded(\stdClass $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); $this->database->table('comments')->insert([ - 'post_id' => $postId, + 'post_id' => $id, 'name' => $data->name, 'email' => $data->email, 'content' => $data->content, ]); - $this->flashMessage('Thank you for your comment', 'success'); + $this->flashMessage('Vielen Dank für Ihren Kommentar', 'success'); $this->redirect('this'); } ``` -Sie sollten sie direkt nach der Komponentenfabrik `commentForm` platzieren. +Platzieren Sie diese Methode direkt nach der Factory des `commentForm`. -Die neue Methode hat ein Argument, nämlich die Instanz des übermittelten Formulars, die von der Komponentenfabrik erstellt wurde. Wir erhalten die übermittelten Werte in `$data`. Und dann fügen wir die Daten in die Datenbanktabelle `comments` ein. +Diese neue Methode hat ein Argument, nämlich die übermittelten Daten als Objekt `$data`. Anschließend speichern wir die Daten in der Datenbanktabelle `comments`. -Es gibt noch zwei weitere Methodenaufrufe zu erklären. Der Redirect leitet buchstäblich zur aktuellen Seite um. Das sollten Sie jedes Mal tun, wenn das Formular übermittelt wurde, gültig ist und die Callback-Operation das getan hat, was sie hätte tun sollen. Wenn Sie die Seite nach dem Absenden des Formulars umleiten, wird auch nicht die bekannte Meldung `Would you like to submit the post data again?` angezeigt, die Sie manchmal im Browser sehen können. (Im Allgemeinen sollten Sie den Benutzer nach dem Absenden eines Formulars mit der Methode `POST` immer zu einer Aktion `GET` weiterleiten). +Es gibt noch zwei weitere Methoden, die einer Erklärung bedürfen. Die `redirect`-Methode leitet buchstäblich zurück zur aktuellen Seite. Dies ist nach jedem Absenden eines Formulars sinnvoll, wenn es gültige Daten enthielt und der Callback die Operation wie vorgesehen durchgeführt hat. Wenn wir die Seite nach dem Absenden des Formulars weiterleiten, sehen wir auch nicht die bekannte Meldung `Möchten Sie die Formulardaten erneut senden?`, die wir manchmal im Browser sehen können. (Generell gilt, dass nach dem Absenden eines Formulars mit der `POST`-Methode immer eine Weiterleitung zu einer `GET`-Aktion erfolgen sollte.) -Die `flashMessage` dient dazu, den Benutzer über das Ergebnis einer bestimmten Operation zu informieren. Da wir eine Routing vornehmen, kann die Nachricht nicht direkt an die Vorlage übergeben und gerendert werden. Deshalb gibt es diese Methode, die sie speichert und beim nächsten Laden der Seite zur Verfügung stellt. Die Flash-Nachrichten werden in der Standarddatei `app/Presenters/templates/@layout.latte` gerendert und sehen wie folgt aus: +Die Methode `flashMessage` dient dazu, den Benutzer über das Ergebnis einer Operation zu informieren. Da wir weiterleiten, kann die Nachricht nicht einfach an das Template übergeben und gerendert werden. Deshalb gibt es diese Methode, die diese Nachricht speichert und beim nächsten Laden der Seite verfügbar macht. Flash-Nachrichten werden im Haupt-Template `app/Presentation/@layout.latte` gerendert und sehen so aus: -```latte +```latte .{file:app/Presentation/@layout.latte} <div n:foreach="$flashes as $flash" n:class="flash, $flash->type"> {$flash->message} </div> ``` -Wie wir bereits wissen, werden sie automatisch an die Vorlage übergeben, so dass Sie nicht viel darüber nachdenken müssen, es funktioniert einfach. Für weitere Details [siehe die Dokumentation |application:presenters#flash-messages]. +Wie wir bereits wissen, werden Flash-Nachrichten automatisch an das Template übergeben, sodass wir uns darüber keine großen Gedanken machen müssen, es funktioniert einfach. Weitere Informationen finden Sie [in der Dokumentation |application:presenters#Flash-Nachrichten]. -Rendering der Kommentare .[#toc-rendering-the-comments] -======================================================= +Rendern von Kommentaren +======================= -Dies ist eines der Dinge, die Sie einfach lieben werden. Nette Database hat diese coole Funktion namens [Explorer |database:explorer]. Erinnern Sie sich, dass wir die Tabellen als InnoDB erstellt haben? Adminer hat die sogenannten [Fremdschlüssel |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html] erstellt, die uns eine Menge Arbeit ersparen werden. +Dies ist eines der Dinge, die Sie einfach lieben werden. Nette Database hat eine großartige Funktion namens [Explorer |database:explorer]. Erinnern Sie sich noch daran, dass wir die Tabellen in der Datenbank absichtlich mit dem InnoDB-Speicher erstellt haben? Adminer hat dadurch etwas geschaffen, das sich [Fremdschlüssel |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html] nennt, was uns viel Arbeit erspart. -Der Nette Database Explorer verwendet die Fremdschlüssel, um die Beziehungen zwischen den Tabellen aufzulösen, und da er die Beziehungen kennt, kann er automatisch Abfragen für Sie erstellen. +Der Nette Database Explorer verwendet Fremdschlüssel, um die Beziehung zwischen Tabellen aufzulösen, und aus dem Wissen über diese Beziehungen kann er automatisch Datenbankabfragen erstellen. -Wie Sie sich vielleicht erinnern, haben wir die Variable `$post` an die Vorlage in `PostPresenter::renderShow()` übergeben, und jetzt wollen wir alle Kommentare durchgehen, deren Spalte `post_id` gleich unserer `$post->id` ist. Sie können dies tun, indem Sie `$post->related('comments')` aufrufen. So einfach ist das. Sehen Sie sich den resultierenden Code an. +Wie Sie sich sicher erinnern, haben wir die Variable `$post` mit der Methode `PostPresenter::renderShow()` an das Template übergeben, und jetzt möchten wir über alle Kommentare iterieren, deren Spaltenwert `post_id` mit `$post->id` übereinstimmt. Dies können wir durch den Aufruf von `$post->related('comments')` erreichen. Ja, so einfach ist das. Schauen wir uns den resultierenden Code an: -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { ... $this->template->post = $post; @@ -142,17 +142,17 @@ public function renderShow(int $postId): void } ``` -Und die Vorlage: +Und das Template: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... -<h2>Comments</h2> +<h2>Kommentare</h2> <div class="comments"> {foreach $comments as $comment} <p><b><a href="mailto:{$comment->email}" n:tag-if="$comment->email"> {$comment->name} - </a></b> said:</p> + </a></b> schrieb:</p> <div>{$comment->content}</div> {/foreach} @@ -160,13 +160,12 @@ Und die Vorlage: ... ``` -Beachten Sie das spezielle Attribut `n:tag-if`. Sie wissen bereits, wie `n: attributes` funktioniert. Wenn Sie dem Attribut `tag-` vorangestellt haben, umschließt es nur die Tags, nicht deren Inhalt. So können Sie den Namen des Kommentators in einen Link umwandeln, wenn er seine E-Mail-Adresse angegeben hat. Diese beiden Zeilen sind im Ergebnis identisch: +Beachten Sie das spezielle Attribut `n:tag-if`. Sie wissen bereits, wie `n:Attribute` funktionieren. Wenn Sie dem Attribut das Präfix `tag-` hinzufügen, wird die Funktionalität nur auf das HTML-Tag angewendet, nicht auf dessen Inhalt. Dies ermöglicht es uns, den Namen des Kommentators nur dann zu einem Link zu machen, wenn er seine E-Mail-Adresse angegeben hat. Diese beiden Zeilen sind identisch: ```latte -<strong n:tag-if="$important"> Hello there! </strong> +<strong n:tag-if="$important"> Guten Tag! </strong> -{if $important}<strong>{/if} Hello there! {if $important}</strong>{/if} +{if $important}<strong>{/if} Guten Tag! {if $important}</strong>{/if} ``` {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/de/creating-posts.texy b/quickstart/de/creating-posts.texy index 4e5d91b9ad..19d02d11cb 100644 --- a/quickstart/de/creating-posts.texy +++ b/quickstart/de/creating-posts.texy @@ -1,30 +1,30 @@ Erstellen und Bearbeiten von Beiträgen ************************************** -Was für eine tolle Zeit. Wir haben einen super coolen neuen Blog, die Leute streiten sich in den Kommentaren und wir haben endlich etwas Zeit für mehr Programmierung. Obwohl wir Adminer mögen, ist es nicht so bequem, Blogbeiträge darin zu schreiben. Vielleicht ist es an der Zeit, ein einfaches Formular einzuführen, mit dem man direkt aus unserer App neue Beiträge hinzufügen kann. Los geht's. +Das ist großartig! Wir haben einen super coolen neuen Blog, die Leute diskutieren eifrig in den Kommentaren und wir haben endlich etwas Zeit für weitere Programmierung. Obwohl Adminer ein großartiges Werkzeug ist, ist es nicht ganz ideal zum Schreiben neuer Blogbeiträge. Es ist wahrscheinlich der richtige Zeitpunkt, ein einfaches Formular zum Hinzufügen neuer Beiträge direkt aus der Anwendung heraus zu erstellen. Legen wir los. -Beginnen wir mit der Gestaltung der Benutzeroberfläche: +Beginnen wir mit dem Entwurf der Benutzeroberfläche: -1. Fügen wir auf der Startseite einen Link "Neuen Beitrag schreiben" ein. -2. Es wird ein Formular mit Titel und Textarea für den Inhalt angezeigt. -3. Wenn Sie auf die Schaltfläche "Speichern" klicken, wird der Blogbeitrag gespeichert. +1. Auf der Startseite fügen wir einen Link "Neuen Beitrag schreiben" hinzu. +2. Dieser Link zeigt ein Formular mit einem Titel und einem Textbereich für den Inhalt des Beitrags an. +3. Wenn wir auf die Schaltfläche Speichern klicken, wird der Beitrag in der Datenbank gespeichert. -Später werden wir auch die Authentifizierung hinzufügen und nur angemeldeten Benutzern erlauben, neue Beiträge hinzuzufügen. Aber das machen wir später. Welchen Code müssen wir schreiben, damit es funktioniert? +Später werden wir auch eine Anmeldung hinzufügen und das Hinzufügen von Beiträgen nur angemeldeten Benutzern erlauben. Aber das kommt später. Welchen Code müssen wir jetzt schreiben, damit alles funktioniert? -1. Erstellen Sie einen neuen Präsentator mit einem Formular zum Hinzufügen von Beiträgen. -2. Definieren Sie einen Callback, der nach erfolgreicher Übermittlung des Formulars ausgelöst wird und der den neuen Beitrag in der Datenbank speichert. -3. Erstellen Sie eine neue Vorlage für das Formular. -4. Fügen Sie einen Link zum Formular in die Hauptseitenvorlage ein. +1. Wir erstellen einen neuen Presenter mit einem Formular zum Hinzufügen von Beiträgen. +2. Wir definieren einen Callback, der nach erfolgreichem Absenden des Formulars ausgeführt wird und den neuen Beitrag in der Datenbank speichert. +3. Wir erstellen ein neues Template, auf dem dieses Formular angezeigt wird. +4. Wir fügen einen Link zum Formular in das Template der Hauptseite ein. -Neuer Präsentator .[#toc-new-presenter] -======================================= +Neuer Presenter +=============== -Nennen Sie den neuen Presenter `EditPresenter` und speichern Sie ihn unter `app/Presenters/EditPresenter.php`. Er muss sich auch mit der Datenbank verbinden, also schreiben wir auch hier einen Konstruktor, der eine Datenbankverbindung benötigt: +Wir nennen den neuen Presenter `EditPresenter` und speichern ihn in `app/Presentation/Edit/`. Er muss sich auch mit der Datenbank verbinden, also schreiben wir hier wieder einen Konstruktor, der eine Datenbankverbindung erfordert: -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Edit; use Nette; use Nette\Application\UI\Form; @@ -39,95 +39,95 @@ final class EditPresenter extends Nette\Application\UI\Presenter ``` -Formular zum Speichern von Beiträgen .[#toc-form-for-saving-posts] -================================================================== +Formular zum Speichern von Beiträgen +==================================== -Formulare und Komponenten wurden bereits behandelt, als wir die Unterstützung für Kommentare hinzugefügt haben. Wenn Sie verwirrt sind, schauen Sie sich noch einmal an [, wie Formulare und Komponenten funktionieren |comments#form-for-commenting], wir warten hier ;) +Formulare und Komponenten haben wir bereits bei der Erstellung von Kommentaren erklärt. Wenn es immer noch nicht klar ist, gehen Sie zurück zum Abschnitt [Erstellung von Formularen und Komponenten |comments#Kommentarformular], wir warten hier in der Zwischenzeit ;) -Fügen Sie nun diese Methode in die `EditPresenter` ein: +Fügen wir nun diese Methode zum Presenter `EditPresenter` hinzu: -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} protected function createComponentPostForm(): Form { $form = new Form; - $form->addText('title', 'Title:') + $form->addText('title', 'Titel:') ->setRequired(); - $form->addTextArea('content', 'Content:') + $form->addTextArea('content', 'Inhalt:') ->setRequired(); - $form->addSubmit('send', 'Save and publish'); - $form->onSuccess[] = [$this, 'postFormSucceeded']; + $form->addSubmit('send', 'Speichern und veröffentlichen'); + $form->onSuccess[] = $this->postFormSucceeded(...); return $form; } ``` -Speichern eines neuen Beitrags aus einem Formular .[#toc-saving-new-post-from-form] -=================================================================================== +Speichern eines neuen Beitrags aus dem Formular +=============================================== -Fahren Sie fort, indem Sie eine Handler-Methode hinzufügen. +Fahren wir fort, indem wir die Methode hinzufügen, die die Formulardaten verarbeitet: -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { $post = $this->database ->table('posts') ->insert($data); - $this->flashMessage('Post was published', 'success'); + $this->flashMessage("Der Beitrag wurde erfolgreich veröffentlicht.", 'success'); $this->redirect('Post:show', $post->id); } ``` -Nur eine kurze Erklärung: Sie holt die Werte aus dem Formular, fügt sie in die Datenbank ein, erstellt eine Nachricht für den Benutzer, dass der Beitrag erfolgreich gespeichert wurde, und leitet zu der Seite weiter, auf der dieser Beitrag veröffentlicht wird, damit Sie sehen können, wie er aussieht. +Nur eine kurze Zusammenfassung: Diese Methode erhält die Daten aus dem Formular, fügt sie in die Datenbank ein, erstellt eine Nachricht für den Benutzer über die erfolgreiche Speicherung des Beitrags und leitet zur Seite mit dem neuen Beitrag weiter, sodass wir sofort sehen, wie er aussieht. -Seite zum Erstellen eines neuen Beitrags .[#toc-page-for-creating-a-new-post] -============================================================================= +Seite zum Erstellen eines neuen Beitrags +======================================== -Lassen Sie uns einfach die Vorlage `Edit/create.latte` erstellen: +Erstellen wir nun das Template `Edit/create.latte`: -```latte .{file:app/Presenters/templates/Edit/create.latte} +```latte .{file:app/Presentation/Edit/create.latte} {block content} -<h1>New post</h1> +<h1>Neuer Beitrag</h1> {control postForm} ``` -Jetzt sollte alles klar sein. Die letzte Zeile zeigt das Formular, das wir erstellen werden. +Alles sollte bereits klar sein. Die letzte Zeile rendert das Formular, das wir gerade erstellen. -Wir könnten auch eine entsprechende Methode `renderCreate` erstellen, aber das ist nicht notwendig. Wir brauchen keine Daten aus der Datenbank zu holen und an die Vorlage zu übergeben, also wäre diese Methode leer. In solchen Fällen darf die Methode überhaupt nicht existieren. +Wir könnten auch eine entsprechende `renderCreate`-Methode erstellen, aber das ist nicht notwendig. Wir müssen keine Daten aus der Datenbank abrufen und an das Template übergeben, sodass die Methode leer wäre. In solchen Fällen muss die Methode überhaupt nicht existieren. -Link zum Erstellen von Beiträgen .[#toc-link-for-creating-posts] -================================================================ +Link zum Erstellen von Beiträgen +================================ -Sie wissen wahrscheinlich schon, wie Sie einen Link zu `EditPresenter` und der Aktion `create` hinzufügen können. Probieren Sie es aus. +Sie wissen wahrscheinlich bereits, wie Sie einen Link zum `EditPresenter` und seiner `create`-Aktion hinzufügen können. Probieren Sie es aus. -Fügen Sie einfach die Datei `app/Presenters/templates/Home/default.latte` hinzu: +Fügen Sie einfach Folgendes zur Datei `app/Presentation/Home/default.latte` hinzu: ```latte -<a n:href="Edit:create">Write new post</a> +<a n:href="Edit:create">Neuen Beitrag schreiben</a> ``` -Bearbeiten von Beiträgen .[#toc-editing-posts] -============================================== +Bearbeiten von Beiträgen +======================== -Lassen Sie uns auch die Möglichkeit hinzufügen, bestehende Beiträge zu bearbeiten. Es wird ziemlich einfach sein - wir haben bereits `postForm` und wir können es auch zum Bearbeiten verwenden. +Fügen wir nun auch die Möglichkeit hinzu, einen Beitrag zu bearbeiten. Es wird sehr einfach sein. Wir haben bereits das Formular `postForm` fertig und können es auch zum Bearbeiten verwenden. -Wir fügen eine neue Seite `edit` zu `EditPresenter` hinzu: +Fügen wir eine neue Seite `edit` zum Presenter `EditPresenter` hinzu: -```php .{file:app/Presenters/EditPresenter.php} -public function renderEdit(int $postId): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +public function renderEdit(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { - $this->error('Post not found'); + $this->error('Beitrag nicht gefunden'); } $this->getComponent('postForm') @@ -135,26 +135,26 @@ public function renderEdit(int $postId): void } ``` -Und erstellen die Vorlage `Edit/edit.latte`: +Und erstellen wir ein weiteres Template `Edit/edit.latte`: -```latte .{file:app/Presenters/templates/Edit/edit.latte} +```latte .{file:app/Presentation/Edit/edit.latte} {block content} -<h1>Edit post</h1> +<h1>Beitrag bearbeiten</h1> {control postForm} ``` -Und aktualisieren Sie die Methode `postFormSucceeded`, mit der Sie entweder einen neuen Beitrag hinzufügen können (wie jetzt) oder bestehende Beiträge bearbeiten können: +Und passen wir die Methode `postFormSucceeded` an, sodass sie sowohl einen neuen Artikel hinzufügen (wie sie es jetzt tut) als auch einen bereits vorhandenen Artikel bearbeiten kann: -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); - if ($postId) { + if ($id) { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); $post->update($data); } else { @@ -163,26 +163,25 @@ public function postFormSucceeded(array $data): void ->insert($data); } - $this->flashMessage('Post was published', 'success'); + $this->flashMessage('Der Beitrag wurde erfolgreich veröffentlicht.', 'success'); $this->redirect('Post:show', $post->id); } ``` -Wenn der Parameter `postId` angegeben wird, bedeutet dies, dass ein Beitrag bearbeitet wird. In diesem Fall wird überprüft, ob der Beitrag wirklich existiert, und wenn ja, wird er in der Datenbank aktualisiert. Wenn der Parameter `postId` nicht angegeben wird, bedeutet dies, dass ein neuer Beitrag hinzugefügt wird. +Wenn der Parameter `id` verfügbar ist, bedeutet dies, dass wir einen Beitrag bearbeiten werden. In diesem Fall überprüfen wir, ob der angeforderte Beitrag wirklich existiert, und wenn ja, aktualisieren wir ihn in der Datenbank. Wenn der Parameter `id` nicht verfügbar ist, bedeutet dies, dass ein neuer Beitrag hinzugefügt werden soll. -Aber woher kommt die `postId`? Es ist der Parameter, der an die Methode `renderEdit` übergeben wird. +Woher kommt aber dieser Parameter `id`? Es handelt sich um den Parameter, der an die Methode `renderEdit` übergeben wurde. -Sie können nun einen Link zur Vorlage `app/Presenters/templates/Post/show.latte` hinzufügen: +Nun können wir einen Link zum Template `app/Presentation/Post/show.latte` hinzufügen: ```latte -<a n:href="Edit:edit $post->id">Edit this post</a> +<a n:href="Edit:edit $post->id">Beitrag bearbeiten</a> ``` -Zusammenfassung .[#toc-summary] -=============================== +Zusammenfassung +=============== -Der Blog funktioniert, die Leute kommentieren schnell und wir sind nicht mehr auf den Administrator angewiesen, um neue Beiträge hinzuzufügen. Er ist völlig unabhängig und auch normale Leute können dort posten. Aber halt, das ist wahrscheinlich nicht in Ordnung, dass jeder, ich meine wirklich jeder im Internet, in unserem Blog posten kann. Es ist eine Form der Authentifizierung erforderlich, damit nur angemeldete Benutzer Beiträge schreiben können. Das werden wir im nächsten Kapitel hinzufügen. +Der Blog ist nun funktionsfähig, Besucher kommentieren aktiv und wir benötigen Adminer nicht mehr zur Veröffentlichung. Die Anwendung ist vollständig unabhängig und jeder kann einen neuen Beitrag hinzufügen. Moment mal, das ist wahrscheinlich nicht ganz in Ordnung, dass jeder - und damit meine ich wirklich jeder mit Internetzugang - neue Beiträge hinzufügen kann. Es ist eine Art von Sicherheit erforderlich, damit nur angemeldete Benutzer einen neuen Beitrag hinzufügen können. Das werden wir uns im nächsten Kapitel ansehen. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/de/home-page.texy b/quickstart/de/home-page.texy index 2ab1c8db32..8079522c8f 100644 --- a/quickstart/de/home-page.texy +++ b/quickstart/de/home-page.texy @@ -1,41 +1,41 @@ -Blog-Startseite -*************** +Startseite des Blogs +******************** .[perex] -Erstellen wir die Startseite, auf der Ihre letzten Beiträge angezeigt werden. +Jetzt erstellen wir eine Startseite, die die neuesten Beiträge anzeigt. -Bevor wir beginnen, sollten Sie zumindest einige Grundlagen über das Model-View-Presenter-Designmuster (ähnlich wie MVC((Model-View-Controller))) kennen: +Bevor wir beginnen, ist es notwendig, zumindest die Grundlagen des Model-View-Presenter (ähnlich wie MVC((Model-View-Controller))) Entwurfsmusters zu kennen: -- **Modell** - Datenverarbeitungsschicht. Sie ist vollständig vom Rest der Anwendung getrennt. Sie kommuniziert nur mit den Präsentatoren. +- **Model** - Die Schicht, die mit Daten arbeitet. Sie ist komplett vom Rest der Anwendung getrennt und kommuniziert nur mit dem Presenter. -- **View** - eine Front-End-Definitionsschicht. Sie stellt dem Benutzer die angeforderten Daten mithilfe von Vorlagen dar. +- **View** - Die Front-End-Schicht. Sie rendert die angeforderten Daten mithilfe von Templates und zeigt sie dem Benutzer an. -- **Presenter** (oder Controller) - eine Verbindungsschicht. Der Presenter verbindet Model und View. Bearbeitet Anfragen, fragt das Modell nach Daten und übergibt sie dann an die aktuelle Ansicht. +- **Presenter** (oder Controller) - Die Verbindungsschicht. Der Presenter verbindet das Model und die View. Er verarbeitet Anfragen, fragt Daten vom Model ab und gibt sie an die View zurück. -Im Falle einer sehr einfachen Anwendung wie unserem Blog besteht die Model-Schicht eigentlich nur aus Abfragen an die Datenbank selbst - wir brauchen keinen zusätzlichen PHP-Code dafür. Wir müssen nur Presenter- und View-Schichten erstellen. In Nette hat jeder Presenter seine eigenen Views, also werden wir mit beiden gleichzeitig fortfahren. +Im Falle einfacher Anwendungen, wie unser Blog eine sein wird, besteht die gesamte Model-Schicht nur aus Datenbankabfragen – dafür benötigen wir vorerst keinen zusätzlichen Code. Für den Anfang erstellen wir also nur Presenter und Templates. In Nette hat jeder Presenter seine eigenen Templates, daher werden wir sie gleichzeitig erstellen. -Erstellen der Datenbank mit Adminer .[#toc-creating-the-database-with-adminer] -============================================================================== +Erstellung der Datenbank mit Adminer +==================================== -Um die Daten zu speichern, werden wir die MySQL-Datenbank verwenden, da sie unter Webentwicklern die häufigste Wahl ist. Wenn Ihnen das nicht zusagt, können Sie natürlich eine Datenbank Ihrer Wahl verwenden. +Zur Datenspeicherung verwenden wir eine MySQL-Datenbank, da sie unter Webentwicklern am weitesten verbreitet ist. Wenn Sie sie jedoch nicht verwenden möchten, können Sie gerne eine Datenbank Ihrer Wahl wählen. -Bereiten wir nun die Datenbank vor, in der unsere Blogbeiträge gespeichert werden sollen. Wir können ganz einfach beginnen - mit einer einzigen Tabelle für die Beiträge. +Nun bereiten wir die Datenbankstruktur vor, in der die Artikel unseres Blogs gespeichert werden. Wir beginnen sehr einfach – wir erstellen nur eine Tabelle für Beiträge. -Um die Datenbank zu erstellen, können wir [Adminer |https://www.adminer.org] herunterladen, oder Sie können ein anderes Tool für die Datenbankverwaltung verwenden. +Zur Erstellung der Datenbank können wir [Adminer |https://www.adminer.org] herunterladen oder ein anderes bevorzugtes Werkzeug zur Datenbankverwaltung verwenden. -Öffnen wir Adminer und erstellen wir eine neue Datenbank mit dem Namen `quickstart`. +Öffnen wir Adminer und erstellen eine neue Datenbank mit dem Namen `quickstart`. -Erstellen Sie eine neue Tabelle mit dem Namen `posts` und fügen Sie diese Spalten hinzu: -- `id` int, klicken Sie auf autoincrement (AI) +Wir erstellen eine neue Tabelle mit dem Namen `posts` und diesen Spalten: +- `id` int, Autoincrement (AI) ankreuzen - `title` varchar, Länge 255 - `content` text -- `created_at` Zeitstempel +- `created_at` timestamp -Es sollte wie folgt aussehen: +Die resultierende Struktur sollte so aussehen: [* adminer-posts.webp *] @@ -49,49 +49,46 @@ CREATE TABLE `posts` ( ``` .[caution] -Es ist sehr wichtig, den **InnoDB**-Tabellenspeicher zu verwenden. Sie werden den Grund dafür später sehen. Wählen Sie das jetzt einfach aus und senden Sie es ab. Sie können jetzt auf Speichern klicken. +Es ist wirklich wichtig, den **InnoDB**-Speicher zu verwenden. Wir werden gleich sehen, warum. Vorerst wählen Sie ihn einfach aus und klicken Sie auf Speichern. -Versuchen Sie, einige Beispiel-Blogbeiträge hinzuzufügen, bevor wir die Möglichkeit implementieren, neue Beiträge direkt aus unserer Anwendung heraus hinzuzufügen. +Bevor wir die Möglichkeit schaffen, Artikel über die Anwendung zur Datenbank hinzuzufügen, fügen Sie einige Beispielartikel manuell zum Blog hinzu. ```sql INSERT INTO `posts` (`id`, `title`, `content`, `created_at`) VALUES -(1, 'Article One', 'Lorem ipusm dolor one', CURRENT_TIMESTAMP), -(2, 'Article Two', 'Lorem ipsum dolor two', CURRENT_TIMESTAMP), -(3, 'Article Three', 'Lorem ipsum dolor three', CURRENT_TIMESTAMP); +(1, 'Artikel Eins', 'Lorem ipusm dolor eins', CURRENT_TIMESTAMP), +(2, 'Artikel Zwei', 'Lorem ipsum dolor zwei', CURRENT_TIMESTAMP), +(3, 'Artikel Drei', 'Lorem ipsum dolor drei', CURRENT_TIMESTAMP); ``` -Verbinden mit der Datenbank .[#toc-connecting-to-the-database] -============================================================== +Verbindung zur Datenbank herstellen +=================================== -Jetzt, wo die Datenbank erstellt ist und wir einige Beiträge darin haben, ist es an der Zeit, sie auf unserer neuen glänzenden Seite anzuzeigen. +Nun, da die Datenbank erstellt ist und wir einige Artikel darin gespeichert haben, ist es der richtige Zeitpunkt, sie auf unserer schönen neuen Seite anzuzeigen. -Zuerst müssen wir unserer Anwendung mitteilen, welche Datenbank sie verwenden soll. Die Konfiguration der Datenbankverbindung wird in `config/local.neon` gespeichert. Legen Sie die Verbindung DSN((Data Source Name)) und Ihre Anmeldedaten fest. Es sollte so aussehen: +Zuerst müssen wir der Anwendung mitteilen, welche Datenbank sie verwenden soll. Die Verbindung zur Datenbank konfigurieren wir in der Datei `config/common.neon` mithilfe von DSN((Data Source Name)) und Anmeldedaten. Es sollte ungefähr so aussehen: -```neon .{file:config/local.neon} +```neon .{file:config/common.neon} database: dsn: 'mysql:host=127.0.0.1;dbname=quickstart' - user: *enter user name* - password: *enter password here* + user: *hier Benutzernamen einfügen* + password: *hier Datenbankpasswort einfügen* ``` .[note] -Achten Sie bei der Bearbeitung dieser Datei auf die Einrückung. Das [NEON-Format |neon:format] akzeptiert sowohl Leerzeichen als auch Tabulatoren, aber nicht beides zusammen! Die Konfigurationsdatei im Webprojekt verwendet standardmäßig Tabulatoren. +Achten Sie beim Bearbeiten dieser Datei auf die Einrückung der Zeilen. Das [NEON-Format |neon:format] akzeptiert sowohl Einrückungen mit Leerzeichen als auch mit Tabulatoren, aber nicht beides gleichzeitig. Die Standard-Konfigurationsdatei im Web Project verwendet Tabulatoren. -Die gesamte Konfiguration ist in `config/` in den Dateien `common.neon` und `local.neon` gespeichert. Die Datei `common.neon` enthält die globale Konfiguration der Anwendung und `local.neon` enthält nur die Parameter, die für die Umgebung spezifisch sind (z. B. den Unterschied zwischen Entwicklungs- und Produktionsserver). +Datenbankverbindung übergeben +============================= -Injizieren der Datenbankverbindung .[#toc-injecting-the-database-connection] -============================================================================ +Der Presenter `HomePresenter`, der für die Anzeige der Artikel zuständig ist, benötigt eine Verbindung zur Datenbank. Um diese zu erhalten, verwenden wir einen Konstruktor, der wie folgt aussieht: -Der Presenter `HomePresenter`, der die Artikel auflistet, benötigt eine Datenbankverbindung. Um sie zu erhalten, schreiben Sie einen Konstruktor wie diesen: - -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; -use Nette\Application\UI\Form; final class HomePresenter extends Nette\Application\UI\Presenter { @@ -105,12 +102,12 @@ final class HomePresenter extends Nette\Application\UI\Presenter ``` -Laden von Beiträgen aus der Datenbank .[#toc-loading-posts-from-the-database] -============================================================================= +Laden von Beiträgen aus der Datenbank +===================================== -Holen wir nun die Beiträge aus der Datenbank und übergeben sie an die Vorlage, die dann den HTML-Code rendert. Dafür ist die sogenannte *render*-Methode gedacht: +Nun laden wir die Beiträge aus der Datenbank und übergeben sie an das Template, das sie anschließend als HTML-Code rendert. Dafür ist die sogenannte *render*-Methode vorgesehen: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} public function renderDefault(): void { $this->template->posts = $this->database @@ -120,41 +117,41 @@ public function renderDefault(): void } ``` -Der Presenter hat jetzt eine Render-Methode `renderDefault()`, die Daten an eine Ansicht namens `default` weitergibt. Presenter-Vorlagen befinden sich in `app/Presenters/templates/{PresenterName}/{viewName}.latte`, in diesem Fall also in `app/Presenters/templates/Home/default.latte`. In der Vorlage ist nun eine Variable namens `$posts` verfügbar, die die Beiträge aus der Datenbank enthält. +Der Presenter enthält nun eine Render-Methode `renderDefault()`, die Daten aus der Datenbank an das Template (View) übergibt. Templates befinden sich in `app/Presentation/{PresenterName}/{viewName}.latte`, daher befindet sich das Template in diesem Fall in `app/Presentation/Home/default.latte`. Im Template steht nun die Variable `$posts` zur Verfügung, die die aus der Datenbank abgerufenen Beiträge enthält. -Vorlage .[#toc-template] -======================== +Template +======== -Es gibt eine allgemeine Vorlage für die gesamte Seite (genannt *Layout*, mit Kopfzeile, Stylesheets, Fußzeile, ...) und dann spezifische Vorlagen für jede Ansicht (z.B. für die Anzeige der Liste der Blogbeiträge), die einige Teile der Layout-Vorlage überschreiben können. +Für die gesamte Website steht uns ein Haupt-Template zur Verfügung (das *layout* heißt und Header, Stile, Footer usw. enthält) sowie spezifische Templates für jede Ansicht (View) (z. B. zur Anzeige von Blogbeiträgen), die einige Teile des Haupt-Templates überschreiben können. -Standardmäßig befindet sich die Layout-Vorlage im Verzeichnis `templates/@layout.latte`, das Folgendes enthält: +Standardmäßig befindet sich das Layout-Template in `app/Presentation/@layout.latte` und enthält: -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... {include content} ... ``` -`{include content}` fügt einen Block namens `content` in die Hauptvorlage ein. Sie können ihn in den Vorlagen der einzelnen Ansichten definieren. In diesem Fall werden wir die Datei `Home/default.latte` wie folgt bearbeiten: +Die Anweisung `{include content}` fügt den Block namens `content` in das Haupt-Template ein. Diesen definieren wir in den Templates der einzelnen Ansichten (View). In unserem Fall passen wir die Datei `Home/default.latte` wie folgt an: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - Hello World + Hallo Welt {/block} ``` -Sie definiert den [Block |latte:tags#block]*Inhalt*, der in das Layout eingefügt wird. Wenn Sie den Browser aktualisieren, sehen Sie eine Seite mit dem Text "Hello world" (im Quellcode auch mit HTML-Kopf- und Fußzeile, die in `@layout.latte` definiert sind). +Damit haben wir den [Block |latte:tags#block] *content* definiert, der in das Hauptlayout eingefügt wird. Wenn wir den Browser erneut aktualisieren, sehen wir eine Seite mit dem Text "Hallo Welt" (im Quellcode auch mit dem HTML-Header und -Footer, die in `@layout.latte` definiert sind). -Zeigen wir nun die Blogeinträge an - wir werden die Vorlage wie folgt bearbeiten: +Lassen Sie uns die Blogbeiträge anzeigen – wir passen das Template wie folgt an: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - <h1>My blog</h1> + <h1>Mein Blog</h1> {foreach $posts as $post} <div class="post"> - <div class="date">{$post->created_at|date:'j. n. Y'}</div> + <div class="date">{$post->created_at|date:'F j, Y'}</div> <h2>{$post->title}</h2> @@ -164,42 +161,41 @@ Zeigen wir nun die Blogeinträge an - wir werden die Vorlage wie folgt bearbeite {/block} ``` -Wenn Sie Ihren Browser aktualisieren, sehen Sie die Liste Ihrer Blogeinträge. Die Liste ist nicht sehr schick oder bunt, also fügen Sie ruhig etwas [glänzendes CSS |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] zu `www/css/style.css` hinzu und verknüpfen Sie es mit einem Layout: +Wenn wir den Browser aktualisieren, sehen wir eine Auflistung aller Beiträge. Die Auflistung ist noch nicht sehr schön oder farbenfroh, daher können wir der Datei `www/css/style.css` einige [CSS-Stile |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] hinzufügen und sie im Layout verlinken: -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <link rel="stylesheet" href="{$basePath}/css/style.css"> </head> ... ``` -Der Tag `{foreach}` durchläuft alle Beiträge, die der Vorlage in der Variablen `$posts` übergeben wurden, und zeigt für jeden Beitrag ein Stück HTML-Code an. Genau wie es ein PHP-Code tun würde. +Das Tag `{foreach}` iteriert über alle Beiträge, die wir dem Template in der Variablen `$posts` übergeben haben, und rendert für jeden das entsprechende HTML-Stück. Es verhält sich genau wie PHP-Code. -Die Variable `|date` wird als Filter bezeichnet. Filter werden verwendet, um die Ausgabe zu formatieren. Dieser spezielle Filter wandelt ein Datum (z. B. `2013-04-12`) in seine besser lesbare Form um (`12. 4. 2013`). Der Filter `|truncate` schneidet die Zeichenkette auf die angegebene Maximallänge ab und fügt am Ende ein Auslassungszeichen ein, wenn die Zeichenkette abgeschnitten wird. Da es sich um eine Vorschau handelt, hat es keinen Sinn, den vollständigen Inhalt des Artikels anzuzeigen. Weitere Standardfilter finden [Sie in der Dokumentation |latte:filters] oder Sie können bei Bedarf eigene Filter erstellen. +Die Schreibweise `|date:` nennen wir Filter. Filter dienen zur Formatierung der Ausgabe. Dieser spezielle Filter konvertiert ein Datum (z. B. `2013-04-12`) in seine lesbarere Form (`April 12, 2013`). Der Filter `|truncate` kürzt eine Zeichenkette auf die angegebene maximale Länge und fügt am Ende drei Punkte hinzu, wenn die Zeichenkette gekürzt wird. Da es sich um eine Vorschau handelt, macht es keinen Sinn, den gesamten Inhalt des Artikels anzuzeigen. Weitere Standardfilter [finden Sie in der Dokumentation |latte:filters], oder wir können bei Bedarf eigene erstellen. -Eine weitere Sache. Wir können den Code ein wenig kürzer und damit einfacher machen. Wir können *Latte Tags* durch *n:Attribute* wie folgt ersetzen: +Noch eine Sache. Den vorherigen Code können wir kürzen und vereinfachen. Dies erreichen wir, indem wir *Latte-Tags* durch *n:Attribute* ersetzen: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - <h1>My blog</h1> + <h1>Mein Blog</h1> <div n:foreach="$posts as $post" class="post"> <div class="date">{$post->created_at|date:'F j, Y'}</div> <h2>{$post->title}</h2> - <div>{$post->content}</div> + <div>{$post->content|truncate:256}</div> </div> {/block} ``` -Der `n:foreach`, umhüllt einfach das *div* mit einem *foreach*-Block (er tut genau das Gleiche wie der vorherige Codeblock). +Das Attribut `n:foreach` umschließt den *div*-Block mit einem *foreach* (funktioniert absolut genauso wie der vorherige Code). -Zusammenfassung .[#toc-summary] -=============================== +Zusammenfassung +=============== -Wir haben eine sehr einfache MySQL-Datenbank mit einigen Blog-Beiträgen darin. Die Anwendung verbindet sich mit der Datenbank und zeigt eine einfache Liste der Beiträge an. +Wir haben jetzt eine sehr einfache MySQL-Datenbank mit einigen Beiträgen. Die Anwendung verbindet sich mit dieser Datenbank und gibt eine einfache Liste dieser Beiträge im Template aus. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/de/model.texy b/quickstart/de/model.texy index 6a00704dfd..bcbbf07801 100644 --- a/quickstart/de/model.texy +++ b/quickstart/de/model.texy @@ -1,13 +1,13 @@ -Modell -****** +Model +***** -Wenn unsere Anwendung wächst, werden wir bald feststellen, dass wir ähnliche Datenbankoperationen an verschiedenen Stellen und in verschiedenen Präsentatoren durchführen müssen, um beispielsweise die neuesten veröffentlichten Artikel zu erfassen. Wenn wir unsere Anwendung verbessern, indem wir den Artikeln eine Markierung hinzufügen, die den Status "in Arbeit" anzeigt, müssen wir auch alle Standorte in unserer Anwendung durchgehen und eine Where-Klausel hinzufügen, um sicherzustellen, dass nur fertige Artikel ausgewählt werden. +Mit zunehmender Größe der Anwendung stellen wir bald fest, dass wir an verschiedenen Stellen, in verschiedenen Presentern, ähnliche Datenbankoperationen durchführen müssen. Zum Beispiel die neuesten veröffentlichten Artikel abrufen. Wenn wir die Anwendung verbessern, indem wir beispielsweise bei Artikeln ein Kennzeichen hinzufügen, ob sie sich in Bearbeitung befinden, müssen wir anschließend alle Stellen in der Anwendung durchgehen, an denen Artikel aus der Datenbank abgerufen werden, und eine `where`-Bedingung hinzufügen, um nur nicht in Bearbeitung befindliche Artikel auszuwählen. -An diesem Punkt wird die direkte Arbeit mit der Datenbank unzureichend, und es wird klüger sein, uns mit einer neuen Funktion zu helfen, die veröffentlichte Artikel zurückgibt. Und wenn wir später eine weitere Klausel hinzufügen (z. B. um keine Artikel mit einem zukünftigen Datum anzuzeigen), müssen wir unseren Code nur an einer Stelle bearbeiten. +In diesem Moment wird die direkte Arbeit mit der Datenbank unzureichend, und es wäre geschickter, sich mit einer neuen Funktion zu behelfen, die uns die veröffentlichten Artikel zurückgibt. Und wenn wir später eine weitere Bedingung hinzufügen, zum Beispiel dass Artikel mit zukünftigem Datum nicht angezeigt werden sollen, passen wir den Code nur an einer Stelle an. -Wir platzieren die Funktion in der Klasse `PostFacade` und nennen sie `getPublicArticles()`. +Die Funktion platzieren wir beispielsweise in der Klasse `PostFacade` und nennen sie `getPublicArticles()`. -Wir erstellen unsere Modellklasse `PostFacade` im Verzeichnis `app/Model/`, die sich um unsere Artikel kümmert: +Im Verzeichnis `app/Model/` erstellen wir unsere Model-Klasse `PostFacade`, die sich um die Artikel kümmert: ```php .{file:app/Model/PostFacade.php} <?php @@ -32,13 +32,13 @@ final class PostFacade } ``` -In der Klasse übergeben wir die Datenbank Explorer:[api:Nette\Database\Explorer]. Damit nutzen wir die Leistungsfähigkeit des [DI-Containers |dependency-injection:passing-dependencies]. +In der Klasse lassen wir uns über den Konstruktor den Datenbank-Explorer [api:Nette\Database\Explorer] übergeben. Wir nutzen so die Stärke des [DI-Containers|dependency-injection:passing-dependencies]. -Wir werden zu `HomePresenter` wechseln, das wir so bearbeiten, dass wir die Abhängigkeit von `Nette\Database\Explorer` loswerden und durch eine neue Abhängigkeit von unserer neuen Klasse ersetzen. +Wir wechseln zum `HomePresenter`, den wir so anpassen, dass wir die Abhängigkeit von `Nette\Database\Explorer` entfernen und durch eine neue Abhängigkeit von unserer neuen Klasse ersetzen. -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use App\Model\PostFacade; use Nette; @@ -59,10 +59,9 @@ final class HomePresenter extends Nette\Application\UI\Presenter } ``` -Im Abschnitt use verwenden wir `App\Model\PostFacade`, so dass wir den PHP-Code auf `PostFacade` verkürzen können. Wir fordern dieses Objekt im Konstruktor an, schreiben es in die Eigenschaft `$facade` und verwenden es in der Methode renderDefault. +Im `use`-Abschnitt haben wir `App\Model\PostFacade`, sodass wir die Schreibweise im PHP-Code auf `PostFacade` verkürzen können. Dieses Objekt fordern wir im Konstruktor an, schreiben es in die Eigenschaft `$facade` und verwenden es in der Methode `renderDefault`. -Der letzte verbleibende Schritt besteht darin, dem DI-Container beizubringen, dieses Objekt zu erzeugen. Dies geschieht in der Regel durch Hinzufügen eines Aufzählungspunkts in der Datei `config/services.neon` im Abschnitt `services`, in dem der vollständige Klassenname und die Konstruktorparameter angegeben werden. -Dadurch wird es sozusagen registriert, und das Objekt wird dann **service** genannt. Dank eines Zaubers namens [Autowiring |dependency-injection:autowiring] müssen wir die Konstruktorparameter normalerweise nicht angeben, da DI sie erkennt und automatisch übergibt. Es würde also ausreichen, nur den Namen der Klasse anzugeben: +Der letzte Schritt ist, dem DI-Container beizubringen, dieses Objekt zu erstellen. Dies geschieht normalerweise, indem wir zur Datei `config/services.neon` im Abschnitt `services` einen Bindestrich hinzufügen, den vollständigen Klassennamen und die Konstruktorparameter angeben. Damit registrieren wir sie sozusagen, und das Objekt wird dann **Dienst** genannt. Dank des Zaubers namens [Autowiring |dependency-injection:autowiring] müssen wir die Konstruktorparameter meistens nicht angeben, da DI sie selbst erkennt und übergibt. Es würde also ausreichen, nur den Klassennamen anzugeben: ```neon .{file:config/services.neon} ... @@ -71,16 +70,15 @@ services: - App\Model\PostFacade ``` -Sie brauchen diese Zeile jedoch auch nicht hinzuzufügen. Im Abschnitt `search` am Anfang von `services.neon` ist festgelegt, dass alle Klassen, die auf `-Facade` oder `-Factory` enden, automatisch von DI nachgeschlagen werden, was auch für `PostFacade` gilt. +Allerdings müssen Sie nicht einmal diese Zeile hinzufügen. Im Abschnitt `search` am Anfang von `services.neon` ist definiert, dass alle Klassen, die auf `-Facade` oder `-Factory` enden, von DI selbst gefunden werden, was auch bei `PostFacade` der Fall ist. -Zusammenfassung .[#toc-summary] -=============================== +Zusammenfassung +=============== -Die Klasse `PostFacade` fragt in einem Konstruktor nach `Nette\Database\Explorer` und da diese Klasse im DI-Container registriert ist, erzeugt der Container diese Instanz und übergibt sie. DI erzeugt auf diese Weise eine `PostFacade` Instanz für uns und übergibt sie in einem Konstruktor an die HomePresenter Klasse, die danach gefragt hat. Eine Art Matrjoschka-Puppe des Codes :) Alle Komponenten fordern nur an, was sie brauchen, und es ist ihnen egal, wo und wie es erstellt wird. Die Erstellung wird vom DI-Container übernommen. +Die Klasse `PostFacade` fordert im Konstruktor die Übergabe von `Nette\Database\Explorer` an, und da diese Klasse im DI-Container registriert ist, erstellt der Container diese Instanz und übergibt sie. DI erstellt für uns auf diese Weise eine Instanz von `PostFacade` und übergibt sie im Konstruktor an die Klasse `HomePresenter`, die sie angefordert hat. Eine Art Matrjoschka. :) Jeder sagt nur, was er will, und kümmert sich nicht darum, wo und wie etwas erstellt wird. Um die Erstellung kümmert sich der DI-Container. .[note] -Hier können Sie mehr über [Dependency Injection |dependency-injection:introduction] und über die [Konfiguration |nette:configuring] lesen. +Hier können Sie mehr über [Dependency Injection |dependency-injection:introduction] und [Konfiguration |nette:configuring] lesen. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/de/single-post.texy b/quickstart/de/single-post.texy index 047f4cb235..691acfd4f5 100644 --- a/quickstart/de/single-post.texy +++ b/quickstart/de/single-post.texy @@ -1,17 +1,17 @@ -Einzelner Beitrag Seite +Seite mit einem Beitrag *********************** .[perex] -Fügen wir unserem Blog eine weitere Seite hinzu, die den Inhalt eines bestimmten Blogbeitrags anzeigt. +Nun erstellen wir eine weitere Seite des Blogs, die einen bestimmten Beitrag anzeigt. -Wir müssen eine neue Render-Methode erstellen, die einen bestimmten Blogeintrag abruft und ihn an die Vorlage weitergibt. Diese Ansicht in `HomePresenter` zu haben, ist nicht schön, da es sich um einen Blogeintrag und nicht um die Homepage handelt. Also erstellen wir eine neue Klasse `PostPresenter` und platzieren sie in `app/Presenters`. Sie benötigt eine Datenbankverbindung, also fügen wir den *Datenbankinjektions*-Code dort wieder ein. +Wir müssen eine neue Render-Methode erstellen, die einen bestimmten Artikel abruft und an das Template übergibt. Diese Methode in `HomePresenter` zu haben, ist nicht sehr elegant, da wir über einen Artikel sprechen und nicht über die Startseite. Erstellen wir also `PostPresenter` in `app/Presentation/Post/`. Dieser Presenter muss sich ebenfalls mit der Datenbank verbinden, daher schreiben wir hier wieder einen Konstruktor, der eine Datenbankverbindung erfordert. -Die `PostPresenter` sollte so aussehen: +`PostPresenter` könnte also so aussehen: -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Post; use Nette; use Nette\Application\UI\Form; @@ -23,50 +23,50 @@ final class PostPresenter extends Nette\Application\UI\Presenter ) { } - public function renderShow(int $postId): void + public function renderShow(int $id): void { $this->template->post = $this->database ->table('posts') - ->get($postId); + ->get($id); } } ``` -Wir müssen einen korrekten Namespace `App\Presenters` für unseren Präsentator festlegen. Dies hängt von der [Zuordnung der Präsentatoren |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7] ab. +Wir dürfen nicht vergessen, den korrekten Namespace `App\Presentation\Post` anzugeben, der der Einstellung des [Presenter-Mappings |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7] unterliegt. -Die Methode `renderShow` benötigt ein Argument - die ID des Beitrags, der angezeigt werden soll. Dann lädt sie den Beitrag aus der Datenbank und übergibt das Ergebnis an die Vorlage. +Die Methode `renderShow` benötigt ein Argument – die ID eines bestimmten Artikels, der angezeigt werden soll. Dann lädt sie diesen Artikel aus der Datenbank und übergibt ihn an das Template. -In der Vorlage `Home/default.latte` fügen wir einen Link zur Aktion `Post:show` hinzu: +In das Template `Home/default.latte` fügen wir einen Link zur Aktion `Post:show` ein. -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a href="{link Post:show $post->id}">{$post->title}</a></h2> ... ``` -Das Tag `{link}` erzeugt eine URL-Adresse, die auf die Aktion `Post:show` verweist. Dieser Tag leitet auch die ID des Beitrags als Argument weiter. +Das Tag `{link}` generiert eine URL-Adresse, die auf die Aktion `Post:show` verweist. Es übergibt auch die ID des Beitrags als Argument. -Das Gleiche können wir kurz mit n:attribute schreiben: +Dasselbe können wir kürzer mit einem n:Attribut schreiben: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a n:href="Post:show $post->id">{$post->title}</a></h2> ... ``` -Das Attribut `n:href` ist dem Tag `{link}` ähnlich. +Das Attribut `n:href` ist analog zum Tag `{link}`. -Die Vorlage für die Aktion `Post:show` existiert noch nicht. Wir können einen Link zu diesem Beitrag öffnen. [Tracy |tracy:] wird eine Fehlermeldung anzeigen, warum `Post/show.latte` nicht existiert. Wenn Sie eine andere Fehlermeldung sehen, müssen Sie wahrscheinlich mod_rewrite in Ihrem Webserver einschalten. +Für die Aktion `Post:show` existiert jedoch noch kein Template. Wir können versuchen, den Link zu diesem Beitrag zu öffnen. [Tracy |tracy:] zeigt einen Fehler an, da das Template `Post/show.latte` noch nicht existiert. Wenn Sie eine andere Fehlermeldung sehen, müssen Sie wahrscheinlich `mod_rewrite` auf dem Webserver aktivieren. -Wir werden also `Post/show.latte` mit diesem Inhalt erstellen: +Erstellen wir also das Template `Post/show.latte` mit diesem Inhalt: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} {block content} -<p><a n:href="Home:default">← back to posts list</a></p> +<p><a n:href="Home:default">← zurück zur Beitragsliste</a></p> <div class="date">{$post->created_at|date:'F j, Y'}</div> @@ -75,51 +75,50 @@ Wir werden also `Post/show.latte` mit diesem Inhalt erstellen: <div class="post">{$post->content}</div> ``` -Schauen wir uns die einzelnen Teile an. +Gehen wir nun die einzelnen Teile des Templates durch. -Die erste Zeile beginnt mit der Definition eines *benannten Blocks* namens "content", den wir bereits gesehen haben. Er wird in einer *Layoutvorlage* angezeigt. Wie Sie sehen können, fehlt der Endtag `{/block}`. Er ist optional. +Die erste Zeile beginnt mit der Definition eines Blocks namens "content", genauso wie auf der Startseite. Dieser Block wird wieder im Haupt-Template angezeigt. Wie Sie sehen, fehlt das schließende Tag `{/block}`. Das ist nämlich optional. -Die zweite Zeile enthält einen Rückverweis auf die Liste der Blog-Einträge, so dass der Benutzer problemlos in unserem Blog hin und her navigieren kann. Wir verwenden wieder das Attribut `n:href`, so dass Nette die URL für uns generiert. Der Link verweist auf die Aktion `default` des Presenters `Home` (Sie könnten auch `n:href="Home:"` schreiben, da die Aktion `default` weggelassen werden kann). +Auf der nächsten Zeile befindet sich ein Link zurück zur Blog-Beitragsliste, sodass sich der Benutzer einfach zwischen der Beitragsliste und einem einzelnen Beitrag bewegen kann. Da wir das Attribut `n:href` verwenden, kümmert sich Nette selbst um die Generierung von Links. Der Link verweist auf die Aktion `default` des Presenters `Home` (wir können auch `n:href="Home:"` schreiben, da die Aktion mit dem Namen `default` weggelassen werden kann, sie wird automatisch ergänzt). -In der dritten Zeile wird der Zeitstempel der Veröffentlichung mit einem Filter formatiert, wie wir bereits wissen. +Die dritte Zeile formatiert die Datumsausgabe mit einem Filter, den wir bereits kennen. -Die vierte Zeile zeigt den *Titel* des Blogbeitrags als `<h1>` Überschrift. Es gibt einen Teil, mit dem Sie vielleicht nicht vertraut sind, und das ist `n:block="title"`. Können Sie erraten, was er bewirkt? Wenn Sie die vorherigen Teile aufmerksam gelesen haben, haben wir `n: attributes` erwähnt. Dies ist ein weiteres Beispiel. Es ist gleichbedeutend mit: +Die vierte Zeile zeigt den *Titel* des Blogs im HTML-Tag `<h1>` an. Dieses Tag enthält ein Attribut, das Sie vielleicht nicht kennen (`n:block="title"`). Raten Sie mal, was es tut? Wenn Sie den vorherigen Teil aufmerksam gelesen haben, wissen Sie bereits, dass es sich um ein `n:Attribut` handelt. Dies ist ein weiteres Beispiel dafür, das äquivalent ist zu: ```latte {block title}<h1>{$post->title}</h1>{/block} ``` -In einfachen Worten: Es *definiert* einen Block namens `title` neu. Der Block ist in der *Layout-Vorlage* (`/app/Presenters/templates/@layout.latte:11`) definiert und wird hier, wie bei OOP-Überschreibungen, überschrieben. Daher wird die Seite `<title>` den Titel des angezeigten Beitrags enthalten. Wir haben den Titel der Seite überschrieben, und alles, was wir brauchten, war `n:block="title"`. Großartig, nicht wahr? +Einfach gesagt, dieser Block definiert den Block namens `title` neu. Dieser Block ist bereits im Haupt-*Layout*-Template (`/app/Presentation/@layout.latte:11`) definiert, und wie bei der Überschreibung von Methoden in OOP wird dieser Block im Haupt-Template vollständig überschrieben. Somit enthält der `<title>` der Seite nun den Titel des angezeigten Beitrags, und dafür mussten wir nur ein einfaches Attribut `n:block="title"` verwenden. Großartig, nicht wahr? -In der fünften und letzten Zeile der Vorlage wird der gesamte Inhalt Ihres Beitrags angezeigt. +Die fünfte und letzte Zeile des Templates zeigt den gesamten Inhalt eines bestimmten Beitrags an. -Überprüfen der Post-ID .[#toc-checking-post-id] -=============================================== +Überprüfung der Beitrags-ID +=========================== -Was passiert, wenn jemand die URL ändert und `postId` einfügt, die nicht existiert? Wir sollten dem Benutzer eine schöne Fehlermeldung "Seite nicht gefunden" geben. Aktualisieren wir die Render-Methode in `PostPresenter`: +Was passiert, wenn jemand die ID in der URL ändert und eine nicht existierende `id` einfügt? Wir sollten dem Benutzer einen freundlichen Fehler vom Typ "Seite nicht gefunden" anbieten. Ändern wir also die Render-Methode im `PostPresenter` ein wenig: -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { - $this->error('Post not found'); + $this->error('Seite nicht gefunden'); } $this->template->post = $post; } ``` -Wenn der Beitrag nicht gefunden werden kann, wird durch den Aufruf von `$this->error(...)` eine 404-Seite mit einer schönen und verständlichen Meldung angezeigt. Beachten Sie, dass Sie in Ihrer Entwicklungsumgebung (auf Ihrem Laptop) die Fehlerseite nicht sehen werden. Stattdessen zeigt Tracy die Ausnahme mit allen Details an, was für die Entwicklung sehr praktisch ist. Sie können beide Modi prüfen, indem Sie den an `setDebugMode` übergebenen Wert in `Bootstrap.php` ändern. +Wenn der Beitrag nicht gefunden werden kann, zeigen wir durch den Aufruf von `$this->error(...)` eine Fehlerseite 404 mit einer verständlichen Meldung an. Beachten Sie, dass Sie diese Fehlerseite im Entwicklermodus (localhost) nicht sehen werden. Stattdessen wird Tracy mit Details zur Ausnahme angezeigt, was für die Entwicklung ziemlich vorteilhaft ist. Wenn wir uns beide Modi anzeigen lassen möchten, ändern wir einfach das Argument der Methode `setDebugMode` in der Datei `Bootstrap.php`. -Zusammenfassung .[#toc-summary] -=============================== +Zusammenfassung +=============== -Wir haben eine Datenbank mit Blog-Beiträgen und eine Web-Applikation mit zwei Ansichten - die erste zeigt die Zusammenfassung aller aktuellen Beiträge, die zweite einen bestimmten Beitrag an. +Wir haben eine Datenbank mit Beiträgen und eine Webanwendung, die zwei Ansichten hat – die erste zeigt eine Übersicht aller Beiträge und die zweite zeigt einen bestimmten Beitrag an. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/el/@home.texy b/quickstart/el/@home.texy index cac085ca22..880c1814d0 100644 --- a/quickstart/el/@home.texy +++ b/quickstart/el/@home.texy @@ -1,19 +1,19 @@ -Δημιουργήστε την πρώτη σας εφαρμογή! -************************************ +Γράφοντας την πρώτη σας εφαρμογή! +********************************* .[perex] -Γνωρίστε το Nette Framework δημιουργώντας ένα απλό ιστολόγιο με σχόλια. Ας ξεκινήσουμε! +Ας γνωρίσουμε μαζί το Nette Framework, δημιουργώντας ένα απλό blog με σχόλια. Πάμε! -Μετά τα δύο πρώτα κεφάλαια, θα έχετε το δικό σας λειτουργικό ιστολόγιο και θα είστε έτοιμοι να δημοσιεύσετε τις φοβερές αναρτήσεις σας, αν και οι δυνατότητες θα είναι αρκετά περιορισμένες μετά την ολοκλήρωση αυτών των δύο κεφαλαίων. Για να κάνετε τα πράγματα πιο ευχάριστα για τους χρήστες σας, θα πρέπει επίσης να διαβάσετε τα επόμενα κεφάλαια και να συνεχίσετε να βελτιώνετε την εφαρμογή σας. +Ήδη μετά τα δύο πρώτα κεφάλαια, θα έχουμε το δικό μας λειτουργικό blog και θα μπορούμε να δημοσιεύσουμε τα υπέροχα άρθρα μας, αν και οι λειτουργίες θα είναι ακόμα σε μεγάλο βαθμό περιορισμένες. Θα πρέπει να διαβάσετε και τα επόμενα κεφάλαια, όπου θα προγραμματίσουμε την προσθήκη σχολίων, την επεξεργασία άρθρων και τέλος θα ασφαλίσουμε το blog. .[tip] -Αυτό το σεμινάριο προϋποθέτει ότι έχετε ολοκληρώσει το έγγραφο [Εγκατάσταση |nette:installation] και έχετε ρυθμίσει με επιτυχία τα εργαλεία σας. +Αυτός ο οδηγός προϋποθέτει ότι έχετε διαβάσει τη σελίδα [Εγκατάσταση |nette:installation] και έχετε προετοιμάσει επιτυχώς τα απαραίτητα εργαλεία. Επίσης προϋποθέτει ότι κατανοείτε τον [αντικειμενοστραφή προγραμματισμό στην PHP |nette:introduction-to-object-oriented-programming]. -Παρακαλούμε χρησιμοποιήστε PHP 8.0 ή νεότερη έκδοση. Μπορείτε να βρείτε την πλήρη εφαρμογή [στο GitHub |https://github.com/nette-examples/quickstart/tree/v4.0]. +Χρησιμοποιήστε παρακαλώ PHP 8.1 ή νεότερη. Μπορείτε να βρείτε την πλήρη εφαρμογή [στο GitHub |https://github.com/nette-examples/quickstart/tree/v4.0]. -Η σελίδα καλωσορίσματος .[#toc-the-welcome-page] -================================================ +Σελίδα καλωσορίσματος +===================== Ας ξεκινήσουμε δημιουργώντας ένα νέο έργο στον κατάλογο `nette-blog`: @@ -21,93 +21,94 @@ composer create-project nette/web-project nette-blog ``` -Αυτή τη στιγμή θα πρέπει να εκτελείται η σελίδα καλωσορίσματος του Web Project. Δοκιμάστε την ανοίγοντας το πρόγραμμα περιήγησής σας και πηγαίνοντας στην ακόλουθη διεύθυνση URL: +Αυτή τη στιγμή, η αρχική σελίδα του Web Project θα πρέπει ήδη να λειτουργεί. Θα το δοκιμάσουμε ανοίγοντας τον περιηγητή στην ακόλουθη διεύθυνση URL: ``` http://localhost/nette-blog/www/ ``` -και θα πρέπει να δείτε τη σελίδα καλωσορίσματος του Nette Framework: +και θα δούμε την αρχική σελίδα του Nette Framework: [* qs-welcome.webp .{url: http://localhost/nette-blog/www/} *] -Η εφαρμογή λειτουργεί και μπορείτε τώρα να αρχίσετε να κάνετε αλλαγές σε αυτήν. +Η εφαρμογή λειτουργεί και μπορείτε να ξεκινήσετε να κάνετε τροποποιήσεις. .[note] -Αν έχετε κάποιο πρόβλημα, [δοκιμάστε αυτές τις συμβουλές |nette:troubleshooting#Nette Is Not Working, White Page Is Displayed]. +Αν παρουσιάστηκε πρόβλημα, [δοκιμάστε αυτές τις λίγες συμβουλές |nette:troubleshooting#Το Nette δεν λειτουργεί εμφανίζεται μια λευκή σελίδα]. -Περιεχόμενο του Web Project .[#toc-web-project-s-content] -========================================================= +Περιεχόμενο του Web Project +=========================== Το Web Project έχει την ακόλουθη δομή: /--pre <b>nette-blog/</b> -├── <b>app/</b> ← application directory -│ ├── <b>Presenters/</b> ← presenter classes -│ │ └── <b>templates/</b>← templates -│ ├── <b>Router/</b> ← configuration of URL addresses -│ └── <b>Bootstrap.php</b> ← booting class Bootstrap -├── <b>bin/</b> ← scripts for the command line -├── <b>config/</b> ← configuration files -├── <b>log/</b> ← error logs -├── <b>temp/</b> ← temporary files, cache, … -├── <b>vendor/</b> ← libraries installed by Composer -│ └── <b>autoload.php</b> ← autoloading of libraries installed by Composer -└── <b>www/</b> ← public folder - the only place accessible from browser - └── <b>index.php</b> ← initial file that launches the application +├── <b>app/</b> ← κατάλογος εφαρμογής +│ ├── <b>Core/</b> ← βασικές κλάσεις απαραίτητες για τη λειτουργία +│ ├── <b>Presentation/</b> ← presenters, templates & λοιπά +│ │ └── <b>Home/</b> ← κατάλογος του presenter Home +│ └── <b>Bootstrap.php</b> ← κλάση εκκίνησης Bootstrap +├── <b>assets/</b> ← ακατέργαστα περιουσιακά στοιχεία (SCSS, TypeScript, εικόνες πηγής) +├── <b>bin/</b> ← scripts που εκτελούνται από τη γραμμή εντολών +├── <b>config/</b> ← αρχεία διαμόρφωσης +├── <b>log/</b> ← καταγραφή σφαλμάτων +├── <b>temp/</b> ← προσωρινά αρχεία, cache, … +├── <b>vendor/</b> ← βιβλιοθήκες που εγκαταστάθηκαν από τον Composer +│ └── <b>autoload.php</b> ← autoloading όλων των εγκατεστημένων πακέτων +└── <b>www/</b> ← δημόσιος κατάλογος - ο μόνος προσβάσιμος από τον περιηγητή + ├── <b>assets/</b> ← μεταγλωττισμένα στατικά αρχεία (CSS, JS, εικόνες, ...) + └── <b>index.php</b> ← αρχικό αρχείο με το οποίο ξεκινά η εφαρμογή \-- -Υποτίθεται ότι ο κατάλογος `www` αποθηκεύει εικόνες, JavaScript, CSS και άλλα δημόσια διαθέσιμα αρχεία. Αυτός είναι ο μόνος κατάλογος που είναι άμεσα προσβάσιμος από το πρόγραμμα περιήγησης, οπότε μπορείτε να στρέψετε εδώ τον ριζικό κατάλογο του διακομιστή ιστού σας (μπορείτε να το ρυθμίσετε στον Apache, αλλά ας το κάνουμε αργότερα, καθώς δεν είναι σημαντικό αυτή τη στιγμή). +Ο κατάλογος `www/` προορίζεται για την αποθήκευση εικόνων, αρχείων JavaScript, στυλ CSS και άλλων δημόσια προσβάσιμων αρχείων. Μόνο αυτός ο κατάλογος είναι προσβάσιμος από το διαδίκτυο, οπότε ορίστε τον ριζικό κατάλογο της εφαρμογής σας ώστε να δείχνει ακριβώς εδώ (αυτό μπορείτε να το ορίσετε στη διαμόρφωση του Apache ή του nginx, αλλά ας το κάνουμε αργότερα, τώρα δεν είναι σημαντικό). -Ο πιο σημαντικός κατάλογος για εσάς είναι ο `app/`. Εκεί θα βρείτε το αρχείο `Bootstrap.php`, μέσα στο οποίο υπάρχει μια κλάση που φορτώνει το πλαίσιο και ρυθμίζει την εφαρμογή. Ενεργοποιεί το [autoloading |robot-loader:] και ρυθμίζει τον [αποσφαλματωτή |tracy:] και τις [διαδρομές |application:routing]. +Ο πιο σημαντικός φάκελος για εμάς είναι ο `app/`. Εδώ βρίσκουμε το αρχείο `Bootstrap.php`, στο οποίο υπάρχει η κλάση που χρησιμεύει για τη φόρτωση ολόκληρου του framework και τη ρύθμιση της εφαρμογής. Εδώ ενεργοποιείται το [autoloading |robot-loader:], ρυθμίζεται ο [debugger |tracy:] και οι [routes |application:routing]. -Καθαρισμός .[#toc-cleanup] -========================== +Καθαρισμός +========== -Το Web Project περιέχει μια σελίδα καλωσορίσματος, την οποία μπορούμε να αφαιρέσουμε - μπορείτε να διαγράψετε το αρχείο `app/Presenters/templates/Home/default.latte` και να το αντικαταστήσετε με το κείμενο "Hello world!". +Το Web Project περιέχει μια αρχική σελίδα, την οποία θα διαγράψουμε πριν αρχίσουμε να προγραμματίζουμε οτιδήποτε. Χωρίς ανησυχία, λοιπόν, αντικαθιστούμε το περιεχόμενο του αρχείου `app/Presentation/Home/default.latte` με "Hello world!". [* qs-hello.webp .{url:-} *] -Tracy (αποσφαλματωτής) .[#toc-tracy-debugger] -============================================= +Tracy (debugger) +================ -Ένα εξαιρετικά σημαντικό εργαλείο για την ανάπτυξη είναι [ένας αποσφαλματωτής που ονομάζεται Tracy |tracy:]. Δοκιμάστε να κάνετε κάποια λάθη στο αρχείο σας `app/Presenters/HomePresenter.php` (π.χ. αφαιρέστε μια καμπύλη αγκύλη από τον ορισμό της κλάσης HomePresenter) και δείτε τι θα συμβεί. Θα εμφανιστεί μια σελίδα με κόκκινη οθόνη και μια κατανοητή περιγραφή του σφάλματος. +Ένα εξαιρετικά σημαντικό εργαλείο για την ανάπτυξη είναι το [εργαλείο debugging Tracy |tracy:]. Δοκιμάστε να προκαλέσετε κάποιο σφάλμα στο αρχείο `app/Presentation/Home/HomePresenter.php` (π.χ. αφαιρώντας ένα άγκιστρο στον ορισμό της κλάσης `HomePresenter`) και δείτε τι θα συμβεί. Θα εμφανιστεί μια σελίδα ειδοποίησης που περιγράφει το σφάλμα με κατανοητό τρόπο. -[* qs-tracy.webp .{url:-}(debugger screen) *] +[* qs-tracy.avif .{url:-}(οθόνη debugger) *] -Το Tracy θα σας βοηθήσει σημαντικά κατά το κυνήγι των σφαλμάτων. Σημειώστε επίσης την αιωρούμενη μπάρα Tracy Bar στην κάτω δεξιά γωνία, η οποία σας ενημερώνει για σημαντικά δεδομένα εκτέλεσης. +Το Tracy θα μας βοηθήσει πάρα πολύ όταν θα ψάχνουμε για σφάλματα στην εφαρμογή. Επίσης, παρατηρήστε το αιωρούμενο Tracy Bar στην κάτω δεξιά γωνία της οθόνης, το οποίο περιέχει πληροφορίες από την εκτέλεση της εφαρμογής. [* qs-tracybar.webp .{url:-} *] -Στη λειτουργία παραγωγής, το Tracy είναι, φυσικά, απενεργοποιημένο και δεν αποκαλύπτει καμία ευαίσθητη πληροφορία. Αντ' αυτού, όλα τα σφάλματα αποθηκεύονται στον κατάλογο `log/`. Απλά δοκιμάστε το. Στον κατάλογο `app/Bootstrap.php`, βρείτε το ακόλουθο κομμάτι κώδικα, ξεσχολιάστε τη γραμμή και αλλάξτε την παράμετρο κλήσης της μεθόδου σε `false`, έτσι ώστε να μοιάζει ως εξής: +Στην κατάσταση παραγωγής, το Tracy είναι φυσικά απενεργοποιημένο και δεν εμφανίζει καμία ευαίσθητη πληροφορία. Όλα τα σφάλματα σε αυτή την περίπτωση αποθηκεύονται στον φάκελο `log/`. Ας το δοκιμάσουμε. Στο αρχείο `app/Bootstrap.php` αποσχολιάζουμε την ακόλουθη γραμμή και αλλάζουμε την παράμετρο της κλήσης σε `false`, ώστε ο κώδικας να μοιάζει έτσι: ```php .{file:app/Bootstrap.php} ... -$configurator->setDebugMode(false); -$configurator->enableTracy(__DIR__ . '/../log'); +$this->configurator->setDebugMode(false); ... ``` -Μετά την ανανέωση της ιστοσελίδας, η σελίδα με την κόκκινη οθόνη θα αντικατασταθεί με το φιλικό προς τον χρήστη μήνυμα: +Μετά την ανανέωση της σελίδας, δεν θα δούμε πλέον το Tracy. Αντ' αυτού, θα εμφανιστεί ένα φιλικό προς το χρήστη μήνυμα: -[* qs-fatal.webp .{url:-}(error screen) *] +[* qs-fatal.webp .{url:-}(οθόνη σφάλματος) *] -Τώρα, κοιτάξτε στον κατάλογο `log/`. Εκεί θα βρείτε το αρχείο καταγραφής σφαλμάτων (στο αρχείο exception.log), καθώς και τη σελίδα με το μήνυμα σφάλματος (αποθηκευμένη σε ένα αρχείο HTML με όνομα που αρχίζει με `exception`). +Τώρα ας ρίξουμε μια ματιά στον κατάλογο `log/`. Εδώ (στο αρχείο `exception.log`) βρίσκουμε το καταγεγραμμένο σφάλμα και επίσης τη γνωστή σελίδα με το μήνυμα σφάλματος (αποθηκευμένη ως αρχείο HTML με όνομα που ξεκινά με `exception-`). -Σχολιάστε ξανά τη γραμμή `// $configurator->setDebugMode(false);`. Το Tracy ενεργοποιεί αυτόματα τη λειτουργία ανάπτυξης στο περιβάλλον `localhost` και την απενεργοποιεί αλλού. +Θα σχολιάσουμε ξανά τη γραμμή `// $configurator->setDebugMode(false);`. Το Tracy ενεργοποιεί αυτόματα την κατάσταση ανάπτυξης στο localhost και την απενεργοποιεί παντού αλλού. -Τώρα, μπορούμε να διορθώσουμε το σφάλμα και να συνεχίσουμε να σχεδιάζουμε την εφαρμογή μας. +Μπορούμε να διορθώσουμε το σφάλμα που δημιουργήσαμε και να συνεχίσουμε το γράψιμο της εφαρμογής. -Στείλτε ευχαριστίες .[#toc-send-thanks] -======================================= +Στείλτε ευχαριστίες +=================== -Θα σας δείξουμε ένα κόλπο που θα κάνει τους συγγραφείς ανοιχτού κώδικα ευτυχισμένους. Μπορείτε εύκολα να δώσετε ένα αστέρι στο GitHub στις βιβλιοθήκες που χρησιμοποιεί το έργο σας. Απλά εκτελέστε: +Θα σας δείξουμε ένα κόλπο που θα ευχαριστήσει τους συγγραφείς open source. Με έναν απλό τρόπο, δίνετε ένα αστέρι στο GitHub στις βιβλιοθήκες που χρησιμοποιεί το έργο σας. Απλά εκτελέστε: ```shell composer thanks @@ -116,4 +117,3 @@ composer thanks Δοκιμάστε το! {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/el/@left-menu.texy b/quickstart/el/@left-menu.texy index a1f57ca115..423a85fd14 100644 --- a/quickstart/el/@left-menu.texy +++ b/quickstart/el/@left-menu.texy @@ -1,9 +1,9 @@ -Σεμινάριο -********* -- [Δημιουργήστε την πρώτη σας εφαρμογή! |@home] -- [Αρχική σελίδα ιστολογίου |home-page] -- [Σελίδα μονής δημοσίευσης |single-post] +Tutorial +******** +- [Γράφοντας την πρώτη σας εφαρμογή! |@home] +- [Αρχική σελίδα του blog |home-page] +- [Σελίδα με ανάρτηση |single-post] - [Σχόλια |comments] - [Δημιουργία και επεξεργασία αναρτήσεων |creating-posts] -- [Μοντέλο |Model] -- [Πιστοποίηση ταυτότητας |authentication] +- [Model] +- [Authentication |authentication] diff --git a/quickstart/el/@meta.texy b/quickstart/el/@meta.texy new file mode 100644 index 0000000000..7286ef98ab --- /dev/null +++ b/quickstart/el/@meta.texy @@ -0,0 +1 @@ +{{sitename: Γράφοντας την πρώτη σας εφαρμογή!}} diff --git a/quickstart/el/authentication.texy b/quickstart/el/authentication.texy index 3e9482a49f..fcbeb244e9 100644 --- a/quickstart/el/authentication.texy +++ b/quickstart/el/authentication.texy @@ -1,36 +1,36 @@ -Αυθεντικοποίηση -*************** +Authentication +************** -Η Nette σας παρέχει οδηγίες για το πώς να προγραμματίσετε τον έλεγχο ταυτότητας στη σελίδα σας, αλλά δεν σας υποχρεώνει να το κάνετε με κάποιον συγκεκριμένο τρόπο. Η υλοποίηση εξαρτάται από εσάς. Η Nette έχει μια διεπαφή `Nette\Security\Authenticator` η οποία σας αναγκάζει να υλοποιήσετε μόνο μια μοναδική μέθοδο που ονομάζεται `authenticate`, η οποία βρίσκει τον χρήστη όπως εσείς θέλετε. +Το Nette παρέχει έναν τρόπο προγραμματισμού της αυθεντικοποίησης στις σελίδες μας, αλλά δεν μας επιβάλλει τίποτα. Η υλοποίηση εξαρτάται αποκλειστικά από εμάς. Το Nette περιέχει το interface `Nette\Security\Authenticator`, το οποίο απαιτεί μόνο μία μέθοδο `authenticate`, η οποία επαληθεύει τον χρήστη με οποιονδήποτε τρόπο θέλουμε. -Υπάρχουν πολλοί τρόποι με τους οποίους ένας χρήστης μπορεί να πιστοποιήσει τον εαυτό του. Ο πιο συνηθισμένος τρόπος είναι ο *πιστοποίηση με βάση τον κωδικό πρόσβασης* (ο χρήστης παρέχει το όνομά του ή το email του και έναν κωδικό πρόσβασης), αλλά υπάρχουν και άλλοι τρόποι. Μπορεί να σας είναι γνωστά τα κουμπιά "Σύνδεση με το Facebook" σε πολλούς ιστότοπους, ή σύνδεση μέσω Google/Twitter/GitHub ή οποιουδήποτε άλλου ιστότοπου. Με τη Nette, μπορείτε να έχετε οποιαδήποτε μέθοδο ελέγχου ταυτότητας θέλετε, ή μπορείτε να τις συνδυάσετε. Εξαρτάται από εσάς. +Υπάρχουν πολλές δυνατότητες για τον τρόπο με τον οποίο μπορεί να επαληθευτεί ένας χρήστης. Ο πιο συνηθισμένος τρόπος επαλήθευσης είναι μέσω κωδικού πρόσβασης (ο χρήστης παρέχει το όνομά του ή το e-mail και τον κωδικό πρόσβασης), αλλά υπάρχουν και άλλοι τρόποι. Ίσως γνωρίζετε τα κουμπιά τύπου "Σύνδεση μέσω Facebook" ή τη σύνδεση μέσω Google/Twitter/GitHub σε ορισμένες σελίδες. Με το Nette μπορούμε να έχουμε οποιαδήποτε μέθοδο σύνδεσης ή μπορούμε ακόμη και να τις συνδυάσουμε. Εξαρτάται αποκλειστικά από εμάς. -Κανονικά θα γράφατε το δικό σας authenticator, αλλά για αυτό το απλό μικρό blog θα χρησιμοποιήσουμε τον ενσωματωμένο authenticator, ο οποίος αυθεντικοποιείται με βάση έναν κωδικό πρόσβασης και ένα όνομα χρήστη που είναι αποθηκευμένα σε ένα αρχείο ρυθμίσεων. Είναι καλός για δοκιμαστικούς σκοπούς. Έτσι, θα προσθέσουμε την ακόλουθη ενότητα *security* στο αρχείο ρυθμίσεων `config/common.neon`: +Συνήθως θα γράφαμε τον δικό μας authenticator, αλλά για αυτό το απλό μικρό blog θα χρησιμοποιήσουμε τον ενσωματωμένο authenticator, ο οποίος συνδέει βάσει κωδικού πρόσβασης και ονόματος χρήστη που είναι αποθηκευμένα στο αρχείο διαμόρφωσης. Είναι χρήσιμος για δοκιμαστικούς σκοπούς. Προσθέτουμε λοιπόν την ακόλουθη ενότητα `security` στο αρχείο διαμόρφωσης `config/common.neon`: ```neon .{file:config/common.neon} security: users: - admin: secret # χρήστης 'admin', κωδικός πρόσβασης 'secret' + admin: secret # χρήστης 'admin', κωδικός 'secret' ``` -Η Nette θα δημιουργήσει αυτόματα μια υπηρεσία στο δοχείο DI. +Το Nette δημιουργεί αυτόματα μια υπηρεσία στον DI container. -Φόρμα εγγραφής .[#toc-sign-in-form] -=================================== +Φόρμα σύνδεσης +============== -Τώρα έχουμε έτοιμο το backend μέρος του ελέγχου ταυτότητας και πρέπει να παρέχουμε μια διεπαφή χρήστη, μέσω της οποίας ο χρήστης θα συνδεθεί. Ας δημιουργήσουμε έναν νέο παρουσιαστή με όνομα *SignPresenter*, ο οποίος θα +Τώρα έχουμε έτοιμη την αυθεντικοποίηση και πρέπει να προετοιμάσουμε το περιβάλλον χρήστη για τη σύνδεση. Ας δημιουργήσουμε λοιπόν έναν νέο presenter με το όνομα `SignPresenter`, ο οποίος: -- εμφανίζει μια φόρμα σύνδεσης (ζητώντας όνομα χρήστη και κωδικό πρόσβασης) -- θα πιστοποιεί τον χρήστη όταν υποβληθεί η φόρμα -- παρέχει ενέργεια αποσύνδεσης +- εμφανίζει τη φόρμα σύνδεσης (με όνομα χρήστη και κωδικό πρόσβασης) +- μετά την υποβολή της φόρμας, επαληθεύει τον χρήστη +- παρέχει τη δυνατότητα αποσύνδεσης -Ας ξεκινήσουμε με τη φόρμα σύνδεσης. Γνωρίζετε ήδη πώς λειτουργούν οι φόρμες σε έναν παρουσιαστή. Δημιουργήστε την `SignPresenter` και τη μέθοδο `createComponentSignInForm`. Θα πρέπει να μοιάζει με αυτό: +Θα ξεκινήσουμε με τη φόρμα σύνδεσης. Γνωρίζουμε ήδη πώς λειτουργούν οι φόρμες στους presenters. Θα δημιουργήσουμε λοιπόν τον presenter `SignPresenter` και θα γράψουμε τη μέθοδο `createComponentSignInForm`. Θα πρέπει να μοιάζει κάπως έτσι: -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Sign; use Nette; use Nette\Application\UI\Form; @@ -40,69 +40,69 @@ final class SignPresenter extends Nette\Application\UI\Presenter protected function createComponentSignInForm(): Form { $form = new Form; - $form->addText('username', 'Username:') - ->setRequired('Please enter your username.'); + $form->addText('username', 'Όνομα χρήστη:') + ->setRequired('Παρακαλώ συμπληρώστε το όνομα χρήστη σας.'); - $form->addPassword('password', 'Password:') - ->setRequired('Please enter your password.'); + $form->addPassword('password', 'Κωδικός πρόσβασης:') + ->setRequired('Παρακαλώ συμπληρώστε τον κωδικό πρόσβασής σας.'); - $form->addSubmit('send', 'Sign in'); + $form->addSubmit('send', 'Σύνδεση'); - $form->onSuccess[] = [$this, 'signInFormSucceeded']; + $form->onSuccess[] = $this->signInFormSucceeded(...); return $form; } } ``` -Υπάρχει μια είσοδος για το όνομα χρήστη και τον κωδικό πρόσβασης. +Υπάρχουν πεδία για το όνομα χρήστη και τον κωδικό πρόσβασης. -Πρότυπο .[#toc-template] ------------------------- +Template +-------- -Η φόρμα θα εμφανιστεί στο πρότυπο `in.latte` +Η φόρμα θα αποδοθεί στο πρότυπο `in.latte`: -```latte .{file:app/Presenters/templates/Sign/in.latte} +```latte .{file:app/Presentation/Sign/in.latte} {block content} -<h1 n:block=title>Sign in</h1> +<h1 n:block=title>Σύνδεση</h1> {control signInForm} ``` -Χειριστής σύνδεσης .[#toc-login-handler] ----------------------------------------- +Callback σύνδεσης +----------------- -Προσθέτουμε επίσης έναν *χειριστή φόρμας* για την εγγραφή του χρήστη, ο οποίος καλείται αμέσως μετά την υποβολή της φόρμας. +Στη συνέχεια, θα συμπληρώσουμε το callback για τη σύνδεση του χρήστη, το οποίο θα κληθεί αμέσως μετά την επιτυχή υποβολή της φόρμας. -Ο χειριστής θα λάβει απλώς το όνομα χρήστη και τον κωδικό πρόσβασης που εισήγαγε ο χρήστης και θα τα περάσει στον authenticator που ορίστηκε νωρίτερα. Αφού ο χρήστης συνδεθεί, θα τον ανακατευθύνουμε στην αρχική σελίδα. +Το callback απλώς λαμβάνει το όνομα χρήστη και τον κωδικό πρόσβασης που συμπλήρωσε ο χρήστης και τα παραδίδει στον authenticator. Μετά τη σύνδεση, ανακατευθύνουμε στην αρχική σελίδα. -```php .{file:app/Presenters/SignPresenter.php} -public function signInFormSucceeded(Form $form, \stdClass $data): void +```php .{file:app/Presentation/Sign/SignPresenter.php} +private function signInFormSucceeded(Form $form, \stdClass $data): void { try { $this->getUser()->login($data->username, $data->password); $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { - $form->addError('Incorrect username or password.'); + $form->addError('Λανθασμένο όνομα χρήστη ή κωδικός πρόσβασης.'); } } ``` -Η μέθοδος [User::login() |api:Nette\Security\User::login()] θα πρέπει να πετάει μια εξαίρεση όταν το όνομα χρήστη ή ο κωδικός πρόσβασης δεν ταιριάζει με αυτά που έχουμε ορίσει νωρίτερα. Όπως ήδη γνωρίζουμε, αυτό θα είχε ως αποτέλεσμα μια κόκκινη οθόνη [του Tracy |tracy:] ή, σε λειτουργία παραγωγής, ένα μήνυμα που θα ενημέρωνε για ένα εσωτερικό σφάλμα του διακομιστή. Αυτό δεν θα μας άρεσε. Γι' αυτό πιάνουμε την εξαίρεση και προσθέτουμε ένα ωραίο και φιλικό μήνυμα σφάλματος στη φόρμα. +Η μέθοδος [User::login() |api:Nette\Security\User::login()] πετάει μια εξαίρεση αν το όνομα χρήστη και ο κωδικός πρόσβασης δεν συμφωνούν με τα στοιχεία στο αρχείο διαμόρφωσης. Όπως ήδη γνωρίζουμε, αυτό μπορεί να οδηγήσει σε μια κόκκινη σελίδα σφάλματος ή, σε κατάσταση παραγωγής, σε ένα μήνυμα που ενημερώνει για σφάλμα διακομιστή. Αυτό όμως δεν το θέλουμε. Γι' αυτό πιάνουμε αυτή την εξαίρεση και παραδίδουμε ένα όμορφο, φιλικό προς τον χρήστη μήνυμα σφάλματος στη φόρμα. -Όταν το σφάλμα εμφανιστεί στη φόρμα, η σελίδα με τη φόρμα θα αποδοθεί ξανά και πάνω από τη φόρμα θα υπάρχει ένα ωραίο μήνυμα, που θα ενημερώνει τον χρήστη ότι έχει εισάγει λάθος όνομα χρήστη ή κωδικό πρόσβασης. +Μόλις προκύψει σφάλμα στη φόρμα, η σελίδα με τη φόρμα επανασχεδιάζεται και πάνω από τη φόρμα εμφανίζεται ένα όμορφο μήνυμα που ενημερώνει τον χρήστη ότι συμπλήρωσε λάθος όνομα χρήστη ή κωδικό πρόσβασης. -Ασφάλεια των παρουσιαστών .[#toc-security-of-presenters] -======================================================== +Ασφάλεια των presenters +======================= -Θα εξασφαλίσουμε μια φόρμα για την προσθήκη και την επεξεργασία αναρτήσεων. Ορίζεται στον παρουσιαστή `EditPresenter`. Ο στόχος είναι να αποτρέψουμε την πρόσβαση στη σελίδα από χρήστες που δεν είναι συνδεδεμένοι. +Θα ασφαλίσουμε τη φόρμα για την προσθήκη και την επεξεργασία αναρτήσεων. Αυτή ορίζεται στον presenter `EditPresenter`. Ο στόχος είναι να αποτρέψουμε την πρόσβαση στη σελίδα σε χρήστες που δεν είναι συνδεδεμένοι. -Δημιουργούμε μια μέθοδο `startup()` που ξεκινάει αμέσως στην αρχή του [κύκλου ζωής του παρουσιαστή |application:presenters#life-cycle-of-presenter]. Αυτή ανακατευθύνει τους μη συνδεδεμένους χρήστες στη φόρμα σύνδεσης. +Θα δημιουργήσουμε τη μέθοδο `startup()`, η οποία εκτελείται αμέσως στην αρχή του [κύκλου ζωής του presenter |application:presenters#Κύκλος ζωής του presenter]. Αυτή ανακατευθύνει τους μη συνδεδεμένους χρήστες στη φόρμα σύνδεσης. -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} public function startup(): void { parent::startup(); @@ -114,67 +114,66 @@ public function startup(): void ``` -Απόκρυψη συνδέσμων .[#toc-hide-links] -------------------------------------- +Απόκρυψη συνδέσμων +------------------ -Ένας μη πιστοποιημένος χρήστης δεν μπορεί πλέον να δει τη σελίδα *δημιουργίας* ή *επεξεργασίας*, αλλά μπορεί ακόμα να δει τους συνδέσμους που οδηγούν σε αυτές. Ας αποκρύψουμε και αυτούς. Ένας τέτοιος σύνδεσμος βρίσκεται στη διεύθυνση `app/Presenters/templates/Home/default.latte`, και θα πρέπει να είναι ορατός μόνο αν ο χρήστης είναι συνδεδεμένος. +Ο μη εξουσιοδοτημένος χρήστης δεν μπορεί πλέον να δει τη σελίδα `create` ή `edit`, αλλά μπορεί ακόμα να δει τους συνδέσμους προς αυτές. Θα πρέπει να τους κρύψουμε επίσης. Ένας τέτοιος σύνδεσμος βρίσκεται στο πρότυπο `app/Presentation/Home/default.latte` και θα πρέπει να τον βλέπουν μόνο οι συνδεδεμένοι χρήστες. -Μπορούμε να τον αποκρύψουμε χρησιμοποιώντας το *n:attribute* που ονομάζεται `n:if`. Εάν η δήλωση μέσα σε αυτό είναι `false`, ολόκληρο το `<a>` ετικέτα και τα περιεχόμενά της δεν θα εμφανίζονται: +Μπορούμε να τον κρύψουμε χρησιμοποιώντας ένα *n:attribute* με το όνομα `n:if`. Αν αυτή η συνθήκη είναι `false`, ολόκληρη η ετικέτα `<a>`, συμπεριλαμβανομένου του περιεχομένου, παραμένει κρυφή. -```latte -<a n:href="Edit:create" n:if="$user->isLoggedIn()">Create post</a> +```latte .{file:app/Presentation/Home/default.latte} +<a n:href="Edit:create" n:if="$user->isLoggedIn()">Δημιουργία ανάρτησης</a> ``` -(μην το συγχέετε με το `tag-if`): +που είναι συντομογραφία της ακόλουθης γραφής (μην το συγχέετε με το `tag-if`): ```latte -{if $user->isLoggedIn()}<a n:href="Edit:create">Create post</a>{/if} +{if $user->isLoggedIn()}<a n:href="Edit:create">Δημιουργία ανάρτησης</a>{/if} ``` -Θα πρέπει να αποκρύψετε το σύνδεσμο επεξεργασίας που βρίσκεται στο `app/Presenters/templates/Post/show.latte` με παρόμοιο τρόπο. +Με τον ίδιο τρόπο θα κρύψουμε επίσης τον σύνδεσμο στο πρότυπο `app/Presentation/Post/show.latte`. -Σύνδεσμος φόρμας σύνδεσης .[#toc-login-form-link] -================================================= +Σύνδεσμος σύνδεσης +================== -Γεια σας, αλλά πώς μπορούμε να φτάσουμε στη σελίδα σύνδεσης; Δεν υπάρχει κανένας σύνδεσμος που να οδηγεί σε αυτήν. Ας προσθέσουμε έναν στο αρχείο προτύπου `@layout.latte`. Προσπαθήστε να βρείτε ένα ωραίο μέρος, μπορεί να είναι οπουδήποτε σας αρέσει περισσότερο. +Πώς θα φτάσουμε στην πραγματικότητα στη σελίδα σύνδεσης; Δεν υπάρχει κανένας σύνδεσμος που να οδηγεί σε αυτήν. Ας τον προσθέσουμε λοιπόν στο πρότυπο `@layout.latte`. Προσπαθήστε να βρείτε ένα κατάλληλο μέρος - μπορεί να είναι σχεδόν οπουδήποτε. -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <ul class="navig"> - <li><a n:href="Home:">Home</a></li> + <li><a n:href="Home:">Άρθρα</a></li> {if $user->isLoggedIn()} - <li><a n:href="Sign:out">Sign out</a></li> + <li><a n:href="Sign:out">Αποσύνδεση</a></li> {else} - <li><a n:href="Sign:in">Sign in</a></li> + <li><a n:href="Sign:in">Σύνδεση</a></li> {/if} </ul> ... ``` -Αν ο χρήστης δεν έχει συνδεθεί ακόμα, θα εμφανίσουμε το σύνδεσμο "Σύνδεση". Διαφορετικά, θα εμφανιστεί ο σύνδεσμος "Είσοδος". Προσθέτουμε αυτή την ενέργεια στο SignPresenter. +Αν ο χρήστης δεν είναι συνδεδεμένος, εμφανίζεται ο σύνδεσμος "Σύνδεση". Σε αντίθετη περίπτωση, εμφανίζεται ο σύνδεσμος "Αποσύνδεση". Αυτή την ενέργεια θα την προσθέσουμε επίσης στον `SignPresenter`. -Η ενέργεια αποσύνδεσης μοιάζει με αυτή, και επειδή ανακατευθύνουμε τον χρήστη αμέσως, δεν υπάρχει ανάγκη για ένα πρότυπο προβολής. +Δεδομένου ότι ανακατευθύνουμε αμέσως τον χρήστη μετά την αποσύνδεση, δεν χρειάζεται κανένα πρότυπο. Η αποσύνδεση μοιάζει κάπως έτσι: -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} public function actionOut(): void { $this->getUser()->logout(); - $this->flashMessage('You have been signed out.'); + $this->flashMessage('Η αποσύνδεση ήταν επιτυχής.'); $this->redirect('Home:'); } ``` -Απλά καλεί τη μέθοδο `logout()` και στη συνέχεια εμφανίζει ένα ωραίο μήνυμα στο χρήστη. +Απλώς καλείται η μέθοδος `logout()` και στη συνέχεια εμφανίζεται ένα όμορφο μήνυμα που επιβεβαιώνει την επιτυχή αποσύνδεση. -Περίληψη .[#toc-summary] -======================== +Περίληψη +======== -Έχουμε έναν σύνδεσμο για να συνδεθούμε και να αποσυνδεθούμε από τον χρήστη. Έχουμε χρησιμοποιήσει τον ενσωματωμένο αυθεντικοποιητή για τον έλεγχο ταυτότητας και τα στοιχεία σύνδεσης βρίσκονται στο αρχείο ρυθμίσεων, καθώς πρόκειται για μια απλή δοκιμαστική εφαρμογή. Έχουμε επίσης εξασφαλίσει τις φόρμες επεξεργασίας ώστε μόνο οι συνδεδεμένοι χρήστες να μπορούν να προσθέτουν και να επεξεργάζονται αναρτήσεις. +Έχουμε έναν σύνδεσμο για τη σύνδεση και επίσης την αποσύνδεση του χρήστη. Για την επαλήθευση χρησιμοποιήσαμε τον ενσωματωμένο authenticator και τα στοιχεία σύνδεσης τα έχουμε στο αρχείο διαμόρφωσης, καθώς πρόκειται για μια απλή δοκιμαστική εφαρμογή. Επίσης, ασφαλίσαμε τις φόρμες επεξεργασίας, ώστε μόνο οι συνδεδεμένοι χρήστες να μπορούν να προσθέτουν και να επεξεργάζονται αναρτήσεις. .[note] -Εδώ μπορείτε να διαβάσετε περισσότερα σχετικά με τη [σύνδεση |security:authentication] και την [εξουσιοδότηση |security:authorization] [χρηστών |security:authentication]. +Εδώ μπορείτε να διαβάσετε περισσότερα για το [user login |security:authentication] και την [Authorization |security:authorization]. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/el/comments.texy b/quickstart/el/comments.texy index 37ce04b9f5..94ac4ea543 100644 --- a/quickstart/el/comments.texy +++ b/quickstart/el/comments.texy @@ -1,28 +1,28 @@ Σχόλια ****** -Το ιστολόγιο έχει αναπτυχθεί, έχουμε γράψει μερικές πολύ καλές αναρτήσεις στο ιστολόγιο και τις δημοσιεύσαμε μέσω του Adminer. Οι άνθρωποι διαβάζουν το ιστολόγιο και είναι πολύ παθιασμένοι με τις ιδέες μας. Λαμβάνουμε πολλά μηνύματα ηλεκτρονικού ταχυδρομείου με επαίνους κάθε μέρα. Όμως, για ποιο λόγο όλοι αυτοί οι έπαινοι όταν τους έχουμε μόνο στο email, ώστε να μην μπορεί να τους διαβάσει κανείς άλλος; Δεν θα ήταν καλύτερα αν οι άνθρωποι μπορούσαν να σχολιάζουν απευθείας στο ιστολόγιο, ώστε όλοι οι άλλοι να μπορούν να διαβάσουν πόσο φοβεροί είμαστε; +Ανεβάσαμε το blog στον webserver και δημοσιεύσαμε μερικές πολύ ενδιαφέρουσες αναρτήσεις χρησιμοποιώντας το Adminer. Οι άνθρωποι διαβάζουν το blog μας και είναι πολύ ενθουσιασμένοι με αυτό. Λαμβάνουμε πολλά email με επαίνους κάθε μέρα. Αλλά τι νόημα έχει όλος αυτός ο έπαινος αν τον έχουμε μόνο στο email και κανείς δεν μπορεί να τον διαβάσει; Θα ήταν καλύτερο αν ο αναγνώστης μπορούσε να σχολιάσει απευθείας το άρθρο, ώστε όλοι να μπορούν να διαβάσουν πόσο καταπληκτικοί είμαστε. -Ας κάνουμε όλα τα άρθρα σχολιάσιμα. +Ας προγραμματίσουμε λοιπόν τα σχόλια. -Δημιουργία νέου πίνακα .[#toc-creating-a-new-table] -=================================================== +Δημιουργία νέου πίνακα +====================== -Ανοίξτε ξανά το Adminer και δημιουργήστε έναν νέο πίνακα με όνομα `comments` με τις παρακάτω στήλες: +Θα ενεργοποιήσουμε το Adminer και θα δημιουργήσουμε έναν πίνακα `comments` με τις ακόλουθες στήλες: -- `id` int, έλεγχος αυτόματης αύξησης (AI) -- `post_id`, ξένο κλειδί που παραπέμπει στον πίνακα `posts` -- `name` varchar, μήκος 255 -- `email` varchar, μήκος 255 +- `id` int, επιλέγουμε autoincrement (AI) +- `post_id`, ξένο κλειδί που αναφέρεται στον πίνακα `posts` +- `name` varchar, length 255 +- `email` varchar, length 255 - `content` text - `created_at` timestamp -Θα πρέπει να μοιάζει ως εξής: +Ο πίνακας θα πρέπει λοιπόν να μοιάζει κάπως έτσι: [* adminer-comments.webp *] -Μην ξεχάσετε να χρησιμοποιήσετε την αποθήκευση πίνακα InnoDB και πατήστε Αποθήκευση. +Μην ξεχάσετε να χρησιμοποιήσετε ξανά την αποθήκευση InnoDB. ```sql CREATE TABLE `comments` ( @@ -37,83 +37,83 @@ CREATE TABLE `comments` ( ``` -Φόρμα για σχολιασμό .[#toc-form-for-commenting] -=============================================== +Φόρμα σχολιασμού +================ -Αρχικά, πρέπει να δημιουργήσουμε μια φόρμα, η οποία θα επιτρέπει στους χρήστες να σχολιάζουν στη σελίδα μας. Το Nette Framework έχει φοβερή υποστήριξη για φόρμες. Μπορούν να διαμορφωθούν σε έναν παρουσιαστή και να αποδοθούν σε ένα πρότυπο. +Πρώτα, πρέπει να δημιουργήσουμε μια φόρμα που θα επιτρέπει στους χρήστες να σχολιάζουν τις αναρτήσεις. Το Nette Framework έχει εκπληκτική υποστήριξη για φόρμες. Μπορούμε να τις διαμορφώσουμε στον presenter και να τις αποδώσουμε στο template. -Το Nette Framework διαθέτει την έννοια των *στοιχείων*. Ένα **συστατικό** είναι μια επαναχρησιμοποιήσιμη κλάση ή ένα κομμάτι κώδικα, το οποίο μπορεί να συνδεθεί με ένα άλλο συστατικό. Ακόμα και ένας παρουσιαστής είναι ένα συστατικό. Κάθε συστατικό δημιουργείται με τη χρήση του εργοστασίου συστατικών. Ας ορίσουμε λοιπόν το εργοστάσιο της φόρμας σχολίων στο `PostPresenter`. +Το Nette Framework χρησιμοποιεί την έννοια των *components*. Ένα **component** είναι μια επαναχρησιμοποιήσιμη κλάση ή τμήμα κώδικα που μπορεί να επισυναφθεί σε άλλο component. Ακόμη και ο presenter είναι ένα component. Κάθε component δημιουργείται μέσω ενός factory. Θα δημιουργήσουμε λοιπόν ένα factory για τη φόρμα σχολίων στον presenter `PostPresenter`. -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} protected function createComponentCommentForm(): Form { - $form = new Form; // σημαίνει Nette\Application\UII\Form + $form = new Form; // σημαίνει Nette\Application\UI\Form - $form->addText('name', 'Your name:') + $form->addText('name', 'Όνομα:') ->setRequired(); - $form->addEmail('email', 'Email:'); + $form->addEmail('email', 'E-mail:'); - $form->addTextArea('content', 'Comment:') + $form->addTextArea('content', 'Σχόλιο:') ->setRequired(); - $form->addSubmit('send', 'Publish comment'); + $form->addSubmit('send', 'Δημοσίευση σχολίου'); return $form; } ``` -Ας το εξηγήσουμε λίγο. Η πρώτη γραμμή δημιουργεί μια νέα περίπτωση του συστατικού `Form`. Οι ακόλουθες μέθοδοι επισυνάπτουν εισόδους HTML στον ορισμό της φόρμας. `->addText` θα αποδίδεται ως `<input type=text name=name>`, με `<label>Your name:</label>`. Όπως ίσως έχετε ήδη μαντέψει αυτή τη στιγμή, το `->addTextArea` επισυνάπτει μια `<textarea>` και το `->addSubmit` προσθέτει ένα `<input type=submit>`. Υπάρχουν κι άλλες τέτοιες μέθοδοι, αλλά αυτό είναι το μόνο που πρέπει να ξέρετε αυτή τη στιγμή. Μπορείτε να [μάθετε περισσότερα στην τεκμηρίωση |forms:]. +Ας το εξηγήσουμε λίγο ξανά. Η πρώτη γραμμή δημιουργεί μια νέα παρουσία του component `Form`. Οι επόμενες μέθοδοι επισυνάπτουν HTML inputs στον ορισμό αυτής της φόρμας. Το `->addText` θα αποδοθεί ως `<input type="text" name="name">` με `<label>Όνομα:</label>`. Όπως πιθανώς μαντεύετε σωστά, το `->addTextArea` θα αποδοθεί ως `<textarea>` και το `->addSubmit` ως `<input type="submit">`. Υπάρχουν πολλές παρόμοιες μέθοδοι, αλλά αυτές αρκούν για αυτήν τη φόρμα προς το παρόν. Μπορείτε να [διαβάσετε περισσότερα στην τεκμηρίωση|forms:]. -Αφού οριστεί το συστατικό της φόρμας σε έναν παρουσιαστή, μπορούμε να το αποδώσουμε (εμφανίσουμε) σε ένα πρότυπο. Για να το κάνετε αυτό, τοποθετήστε την ετικέτα `{control}` στο τέλος του προτύπου λεπτομερειών δημοσίευσης, στο `Post/show.latte`. Επειδή το όνομα του συστατικού είναι `commentForm` (προέρχεται από το όνομα της μεθόδου `createComponentCommentForm`), η ετικέτα θα μοιάζει ως εξής +Εάν η φόρμα έχει ήδη οριστεί στον presenter, μπορούμε να την αποδώσουμε (εμφανίσουμε) στο template. Αυτό γίνεται τοποθετώντας το tag `{control}` στο τέλος του template που αποδίδει μια συγκεκριμένη ανάρτηση, στο `Post/show.latte`. Επειδή το component ονομάζεται `commentForm` (το όνομα προέρχεται από το όνομα της μεθόδου `createComponentCommentForm`), το tag θα μοιάζει έτσι: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... -<h2>Post new comment</h2> +<h2>Εισαγάγετε νέα ανάρτηση</h2> {control commentForm} ``` -Τώρα, αν ελέγξετε τις λεπτομέρειες κάποιας δημοσίευσης, θα υπάρχει μια νέα φόρμα για τη δημοσίευση σχολίων. +Τώρα, αν εμφανίσετε τη σελίδα με τις λεπτομέρειες της ανάρτησης, θα δείτε μια νέα φόρμα σχολίων στο τέλος της. -Αποθήκευση στη βάση δεδομένων .[#toc-saving-to-database] -======================================================== +Αποθήκευση στη βάση δεδομένων +============================= -Προσπαθήσατε να υποβάλετε κάποια δεδομένα; Ίσως έχετε παρατηρήσει ότι η φόρμα δεν εκτελεί καμία ενέργεια. Είναι απλά εκεί, φαίνεται ωραία και δεν κάνει τίποτα. Πρέπει να της επισυνάψουμε μια μέθοδο επανάκλησης, η οποία θα αποθηκεύσει τα υποβληθέντα δεδομένα. +Έχετε ήδη δοκιμάσει να συμπληρώσετε και να υποβάλετε τη φόρμα; Πιθανόν να παρατηρήσατε ότι η φόρμα στην πραγματικότητα δεν κάνει τίποτα. Πρέπει να επισυνάψουμε μια μέθοδο callback που θα αποθηκεύσει τα υποβληθέντα δεδομένα. -Τοποθετήστε την ακόλουθη γραμμή πριν από τη γραμμή `return` στο component factory για το `commentForm`: +Στη γραμμή πριν από το `return` στο factory για το component `commentForm`, τοποθετούμε την ακόλουθη γραμμή: ```php -$form->onSuccess[] = [$this, 'commentFormSucceeded']; +$form->onSuccess[] = $this->commentFormSucceeded(...); ``` -Σημαίνει "μετά την επιτυχή υποβολή της φόρμας, καλέστε τη μέθοδο `commentFormSucceeded` του τρέχοντος παρουσιαστή". Αυτή η μέθοδος δεν υπάρχει ακόμα, οπότε ας τη δημιουργήσουμε. +Η προηγούμενη σύνταξη σημαίνει "μετά την επιτυχή υποβολή της φόρμας, καλέστε τη μέθοδο `commentFormSucceeded` από τον τρέχοντα presenter". Ωστόσο, αυτή η μέθοδος δεν υπάρχει ακόμα. Ας τη δημιουργήσουμε λοιπόν: -```php .{file:app/Presenters/PostPresenter.php} -public function commentFormSucceeded(\stdClass $data): void +```php .{file:app/Presentation/Post/PostPresenter.php} +private function commentFormSucceeded(\stdClass $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); $this->database->table('comments')->insert([ - 'post_id' => $postId, + 'post_id' => $id, 'name' => $data->name, 'email' => $data->email, 'content' => $data->content, ]); - $this->flashMessage('Thank you for your comment', 'success'); + $this->flashMessage('Ευχαριστώ για το σχόλιο', 'success'); $this->redirect('this'); } ``` -Θα πρέπει να την τοποθετήσετε αμέσως μετά το εργοστάσιο του συστατικού `commentForm`. +Θα τοποθετήσουμε αυτήν τη μέθοδο ακριβώς μετά το factory της φόρμας `commentForm`. -Η μέθοδος new έχει ένα όρισμα που είναι η περίπτωση της φόρμας που υποβάλλεται, η οποία δημιουργήθηκε από το εργοστάσιο συστατικών. Λαμβάνουμε τις υποβληθείσες τιμές στο `$data`. Και στη συνέχεια εισάγουμε τα δεδομένα στον πίνακα της βάσης δεδομένων `comments`. +Αυτή η νέα μέθοδος έχει ένα όρισμα, το οποίο είναι μια παρουσία της φόρμας που υποβλήθηκε - δημιουργήθηκε από το factory. Λαμβάνουμε τις υποβληθείσες τιμές στο `$data`. Και στη συνέχεια αποθηκεύουμε τα δεδομένα στον πίνακα της βάσης δεδομένων `comments`. -Υπάρχουν δύο ακόμη κλήσεις μεθόδων που πρέπει να εξηγήσουμε. Η ανακατεύθυνση ανακατευθύνει κυριολεκτικά στην τρέχουσα σελίδα. Θα πρέπει να το κάνετε αυτό κάθε φορά που η φόρμα έχει υποβληθεί, είναι έγκυρη και η λειτουργία επανάκλησης έκανε αυτό που έπρεπε να κάνει. Επίσης, όταν ανακατευθύνετε τη σελίδα μετά την υποβολή της φόρμας, δεν θα βλέπετε το γνωστό μήνυμα `Would you like to submit the post data again?` που μερικές φορές μπορείτε να δείτε στο πρόγραμμα περιήγησης. (Γενικά, μετά την υποβολή μιας φόρμας με τη μέθοδο `POST`, θα πρέπει πάντα να ανακατευθύνετε τον χρήστη σε μια ενέργεια `GET` ). +Υπάρχουν ακόμα δύο μέθοδοι που αξίζουν εξήγηση. Η μέθοδος redirect κυριολεκτικά ανακατευθύνει πίσω στην τρέχουσα σελίδα. Αυτό είναι καλό να γίνεται μετά από κάθε υποβολή φόρμας, εάν περιείχε έγκυρα δεδομένα και το callback εκτέλεσε τη λειτουργία όπως έπρεπε. Επίσης, εάν ανακατευθύνουμε τη σελίδα μετά την υποβολή της φόρμας, δεν θα δούμε το γνωστό μήνυμα `Θέλετε να υποβάλετε ξανά τα δεδομένα της φόρμας;`, το οποίο μερικές φορές μπορούμε να δούμε στον browser. (Γενικά, μετά την υποβολή μιας φόρμας με τη μέθοδο `POST`, θα πρέπει πάντα να ακολουθεί ανακατεύθυνση σε μια `GET` action.) -Το `flashMessage` είναι για την ενημέρωση του χρήστη σχετικά με το αποτέλεσμα κάποιας ενέργειας. Επειδή κάνουμε ανακατεύθυνση, το μήνυμα δεν μπορεί να περάσει απευθείας στο πρότυπο και να αποδοθεί. Έτσι υπάρχει αυτή η μέθοδος, που θα το αποθηκεύσει και θα το κάνει διαθέσιμο κατά την επόμενη φόρτωση της σελίδας. Τα μηνύματα flash αποδίδονται στο προεπιλεγμένο αρχείο `app/Presenters/templates/@layout.latte` και μοιάζει κάπως έτσι: +Η μέθοδος `flashMessage` είναι για την ενημέρωση του χρήστη σχετικά με το αποτέλεσμα κάποιας λειτουργίας. Επειδή ανακατευθύνουμε, το μήνυμα δεν μπορεί απλά να περάσει στο template και να αποδοθεί. Γι' αυτό υπάρχει αυτή η μέθοδος, η οποία αποθηκεύει αυτό το μήνυμα και το καθιστά διαθέσιμο στην επόμενη φόρτωση της σελίδας. Τα flash μηνύματα αποδίδονται στο κύριο template `app/Presentation/@layout.latte` και μοιάζουν κάπως έτσι: ```latte <div n:foreach="$flashes as $flash" n:class="flash, $flash->type"> @@ -121,20 +121,20 @@ public function commentFormSucceeded(\stdClass $data): void </div> ``` -Όπως ήδη γνωρίζουμε, περνούν αυτόματα στο πρότυπο, οπότε δεν χρειάζεται να το σκεφτείτε πολύ, απλά λειτουργεί. Για περισσότερες λεπτομέρειες [δείτε την τεκμηρίωση |application:presenters#flash-messages]. +Όπως ήδη γνωρίζουμε, τα flash μηνύματα περνούν αυτόματα στο template, οπότε δεν χρειάζεται να το σκεφτόμαστε πολύ, απλά λειτουργεί. Για περισσότερες πληροφορίες [επισκεφθείτε την τεκμηρίωση |application:presenters#Flash μηνύματα]. -Απόδοση των σχολίων .[#toc-rendering-the-comments] -================================================== +Απόδοση σχολίων +=============== -Αυτό είναι ένα από τα πράγματα που θα σας αρέσει πολύ. Η βάση δεδομένων Nette έχει αυτό το δροσερό χαρακτηριστικό που ονομάζεται [Explorer |database:explorer]. Θυμάστε ότι έχουμε δημιουργήσει τους πίνακες ως InnoDB; Ο Adminer έχει δημιουργήσει τα λεγόμενα [ξένα κλειδιά |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html] που θα μας γλιτώσουν από έναν τόνο δουλειάς. +Αυτό είναι ένα από τα πράγματα που απλά θα λατρέψετε. Η Nette Database έχει μια εξαιρετική λειτουργία που ονομάζεται [Explorer |database:explorer]. Θυμάστε ακόμα ότι δημιουργήσαμε τους πίνακες στη βάση δεδομένων χρησιμοποιώντας την αποθήκευση InnoDB; Το Adminer δημιούργησε έτσι κάτι που ονομάζεται [ξένα κλειδιά |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html], τα οποία θα μας γλιτώσουν από πολλή δουλειά. -Ο Εξερευνητής της Nette Database χρησιμοποιεί τα ξένα κλειδιά για να επιλύσει τις σχέσεις μεταξύ των πινάκων και γνωρίζοντας τις σχέσεις, μπορεί να δημιουργήσει αυτόματα ερωτήματα για εσάς. +Η Nette Database Explorer χρησιμοποιεί ξένα κλειδιά για να επιλύσει την αμοιβαία σχέση μεταξύ των πινάκων και από τη γνώση αυτών των σχέσεων μπορεί να δημιουργήσει αυτόματα ερωτήματα βάσης δεδομένων. -Όπως ίσως θυμάστε, έχουμε περάσει τη μεταβλητή `$post` στο πρότυπο στο `PostPresenter::renderShow()` και τώρα θέλουμε να επαναλάβουμε όλα τα σχόλια που έχουν τη στήλη `post_id` ίση με το `$post->id` μας . Μπορείτε να το κάνετε καλώντας το `$post->related('comments')`. Είναι τόσο απλό. Κοιτάξτε τον κώδικα που προκύπτει. +Όπως σίγουρα θυμάστε, περάσαμε τη μεταβλητή `$post` στο template χρησιμοποιώντας τη μέθοδο `PostPresenter::renderShow()` και τώρα θέλουμε να επαναλάβουμε όλα τα σχόλια που έχουν την τιμή της στήλης `post_id` ίδια με το `$post->id`. Μπορούμε να το πετύχουμε καλώντας `$post->related('comments')`. Ναι, τόσο απλά. Ας δούμε τον τελικό κώδικα: -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { ... $this->template->post = $post; @@ -142,17 +142,17 @@ public function renderShow(int $postId): void } ``` -Και το πρότυπο: +Και το template: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... -<h2>Comments</h2> +<h2>Σχόλια</h2> <div class="comments"> {foreach $comments as $comment} <p><b><a href="mailto:{$comment->email}" n:tag-if="$comment->email"> {$comment->name} - </a></b> said:</p> + </a></b> έγραψε:</p> {* Translate "napsal:" *} <div>{$comment->content}</div> {/foreach} @@ -160,13 +160,12 @@ public function renderShow(int $postId): void ... ``` -Παρατηρήστε το ειδικό χαρακτηριστικό `n:tag-if`. Γνωρίζετε ήδη πώς λειτουργεί το `n: attributes`. Λοιπόν, αν προτάξετε το χαρακτηριστικό με το `tag-`, θα τυλίξει μόνο τις ετικέτες, όχι το περιεχόμενό τους. Αυτό σας επιτρέπει να μετατρέψετε το όνομα του σχολιαστή σε σύνδεσμο, αν έχει δώσει το email του. Τα αποτελέσματα αυτών των δύο γραμμών είναι πανομοιότυπα: +Παρατηρήστε το ειδικό attribute `n:tag-if`. Ήδη ξέρετε πώς λειτουργούν τα `n:attributes`. Αν επισυνάψετε το πρόθεμα `tag-` στο attribute, η λειτουργικότητα εφαρμόζεται μόνο στο HTML tag, όχι στο περιεχόμενό του. Αυτό μας επιτρέπει να κάνουμε το όνομα του σχολιαστή σύνδεσμο μόνο στην περίπτωση που παρείχε το email του. Αυτές οι δύο γραμμές είναι πανομοιότυπες: ```latte -<strong n:tag-if="$important"> Hello there! </strong> +<strong n:tag-if="$important"> Καλημέρα! </strong> -{if $important}<strong>{/if} Hello there! {if $important}</strong>{/if} +{if $important}<strong>{/if} Καλημέρα! {if $important}</strong>{/if} ``` {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/el/creating-posts.texy b/quickstart/el/creating-posts.texy index 6d919d88ac..904b4a70aa 100644 --- a/quickstart/el/creating-posts.texy +++ b/quickstart/el/creating-posts.texy @@ -1,30 +1,30 @@ Δημιουργία και επεξεργασία αναρτήσεων ************************************* -Τι ωραία στιγμή! Έχουμε ένα σούπερ γαμάτο νέο blog, οι άνθρωποι διαφωνούν στα σχόλια και έχουμε επιτέλους λίγο χρόνο για περισσότερο προγραμματισμό. Αν και μας αρέσει το Adminer, δεν είναι τόσο άνετο να γράφουμε αναρτήσεις στο blog σε αυτό. Ίσως είναι η κατάλληλη στιγμή να προσθέσουμε μια απλή φόρμα για την προσθήκη νέων αναρτήσεων απευθείας από την εφαρμογή μας. Ας το κάνουμε. +Αυτό είναι υπέροχο! Έχουμε ένα σούπερ cool νέο blog, οι άνθρωποι συζητούν έντονα στα σχόλια και εμείς έχουμε επιτέλους λίγο χρόνο για περαιτέρω προγραμματισμό. Παρόλο που το Adminer είναι ένα εξαιρετικό εργαλείο, δεν είναι εντελώς ιδανικό για τη συγγραφή νέων αναρτήσεων στο blog. Προφανώς είναι η κατάλληλη στιγμή για να δημιουργήσουμε μια απλή φόρμα για την προσθήκη νέων αναρτήσεων απευθείας από την εφαρμογή. Ας το κάνουμε. -Ας ξεκινήσουμε με το σχεδιασμό του UI: +Ας ξεκινήσουμε με τον σχεδιασμό του user interface: -1. Στην αρχική σελίδα, ας προσθέσουμε έναν σύνδεσμο "Γράψτε νέα δημοσίευση". -2. Θα εμφανίζεται μια φόρμα με τίτλο και textarea για το περιεχόμενο. -3. Όταν κάνετε κλικ σε ένα κουμπί Αποθήκευση, θα αποθηκεύσει την ανάρτηση του ιστολογίου. +1. Στην αρχική σελίδα θα προσθέσουμε έναν σύνδεσμο "Γράψτε νέα ανάρτηση". +2. Αυτός ο σύνδεσμος θα εμφανίσει μια φόρμα με τίτλο και textarea για το περιεχόμενο της ανάρτησης. +3. Όταν κάνουμε κλικ στο κουμπί Αποθήκευση, η ανάρτηση θα αποθηκευτεί στη βάση δεδομένων. -Αργότερα θα προσθέσουμε επίσης έλεγχο ταυτότητας και θα επιτρέπουμε μόνο στους συνδεδεμένους χρήστες να προσθέτουν νέες αναρτήσεις. Αλλά ας το κάνουμε αυτό αργότερα. Τι κώδικα θα πρέπει να γράψουμε για να το κάνουμε να λειτουργήσει; +Αργότερα θα προσθέσουμε επίσης σύνδεση και θα επιτρέψουμε την προσθήκη αναρτήσεων μόνο σε συνδεδεμένους χρήστες. Αλλά αυτό αργότερα. Ποιον κώδικα πρέπει να γράψουμε τώρα για να λειτουργήσουν όλα; -1. Δημιουργήστε έναν νέο παρουσιαστή με μια φόρμα για την προσθήκη αναρτήσεων. -2. Ορίστε ένα callback που θα ενεργοποιείται μετά την επιτυχή υποβολή της φόρμας και που θα αποθηκεύει τη νέα ανάρτηση στη βάση δεδομένων. -3. Δημιουργήστε ένα νέο πρότυπο για τη φόρμα. -4. Προσθέστε έναν σύνδεσμο προς τη φόρμα στο πρότυπο της κύριας σελίδας. +1. Θα δημιουργήσουμε έναν νέο presenter με μια φόρμα για την προσθήκη αναρτήσεων. +2. Θα ορίσουμε ένα callback που θα εκτελεστεί μετά την επιτυχή υποβολή της φόρμας και το οποίο θα αποθηκεύσει τη νέα ανάρτηση στη βάση δεδομένων. +3. Θα δημιουργήσουμε ένα νέο template στο οποίο θα βρίσκεται αυτή η φόρμα. +4. Θα προσθέσουμε έναν σύνδεσμο προς τη φόρμα στο template της κύριας σελίδας. -Νέος παρουσιαστής .[#toc-new-presenter] -======================================= +Νέος presenter +============== -Ονομάστε τον νέο παρουσιαστή `EditPresenter` και αποθηκεύστε τον στο `app/Presenters/EditPresenter.php`. Πρέπει επίσης να συνδεθεί με τη βάση δεδομένων, οπότε και εδώ γράφουμε έναν κατασκευαστή που θα απαιτεί σύνδεση με τη βάση δεδομένων: +Θα ονομάσουμε τον νέο presenter `EditPresenter` και θα τον αποθηκεύσουμε στο `app/Presentation/Edit/`. Επίσης, πρέπει να συνδεθεί στη βάση δεδομένων, οπότε θα γράψουμε ξανά έναν constructor που θα απαιτεί σύνδεση στη βάση δεδομένων: -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Edit; use Nette; use Nette\Application\UI\Form; @@ -39,92 +39,92 @@ final class EditPresenter extends Nette\Application\UI\Presenter ``` -Φόρμα για την αποθήκευση δημοσιεύσεων .[#toc-form-for-saving-posts] -=================================================================== +Φόρμα για αποθήκευση αναρτήσεων +=============================== -Οι φόρμες και τα στοιχεία έχουν ήδη καλυφθεί όταν προσθέταμε υποστήριξη για σχόλια. Αν έχετε μπερδευτεί με το θέμα, πηγαίνετε να δείτε ξανά [πώς λειτουργούν οι φόρμες και τα συστατικά |comments#form-for-commenting], εμείς θα περιμένουμε εδώ ;) +Έχουμε ήδη εξηγήσει τις φόρμες και τα components κατά τη δημιουργία των σχολίων. Αν ακόμα δεν είναι σαφές, πηγαίνετε να δείτε τη [δημιουργία φορμών και components |comments#Φόρμα σχολιασμού], εμείς θα περιμένουμε εδώ ;) -Τώρα προσθέστε αυτή τη μέθοδο στο `EditPresenter`: +Τώρα ας προσθέσουμε αυτή τη μέθοδο στον presenter `EditPresenter`: -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} protected function createComponentPostForm(): Form { $form = new Form; - $form->addText('title', 'Title:') + $form->addText('title', 'Τίτλος:') ->setRequired(); - $form->addTextArea('content', 'Content:') + $form->addTextArea('content', 'Περιεχόμενο:') ->setRequired(); - $form->addSubmit('send', 'Save and publish'); - $form->onSuccess[] = [$this, 'postFormSucceeded']; + $form->addSubmit('send', 'Αποθήκευση και δημοσίευση'); + $form->onSuccess[] = $this->postFormSucceeded(...); return $form; } ``` -Αποθήκευση νέας δημοσίευσης από φόρμα .[#toc-saving-new-post-from-form] -======================================================================= +Αποθήκευση νέας ανάρτησης από τη φόρμα +====================================== -Συνεχίστε με την προσθήκη μιας μεθόδου χειρισμού. +Συνεχίζουμε προσθέτοντας τη μέθοδο που επεξεργάζεται τα δεδομένα από τη φόρμα: -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { $post = $this->database ->table('posts') ->insert($data); - $this->flashMessage('Post was published', 'success'); + $this->flashMessage("Η ανάρτηση δημοσιεύτηκε με επιτυχία.", 'success'); $this->redirect('Post:show', $post->id); } ``` -Μια γρήγορη εξήγηση: παίρνει τις τιμές από τη φόρμα, τις εισάγει στη βάση δεδομένων, δημιουργεί ένα μήνυμα για τον χρήστη ότι η δημοσίευση αποθηκεύτηκε με επιτυχία και ανακατευθύνει στη σελίδα όπου δημοσιεύεται η συγκεκριμένη δημοσίευση, ώστε να μπορείτε να δείτε πώς είναι. +Μόνο μια γρήγορη ανακεφαλαίωση: αυτή η μέθοδος λαμβάνει τα δεδομένα από τη φόρμα, τα εισάγει στη βάση δεδομένων, δημιουργεί ένα μήνυμα για τον χρήστη σχετικά με την επιτυχή αποθήκευση της ανάρτησης και ανακατευθύνει στη σελίδα με τη νέα ανάρτηση, ώστε να δούμε αμέσως πώς φαίνεται. -Σελίδα για τη δημιουργία μιας νέας δημοσίευσης .[#toc-page-for-creating-a-new-post] -=================================================================================== +Σελίδα για δημιουργία νέας ανάρτησης +==================================== -Ας δημιουργήσουμε απλά το πρότυπο `Edit/create.latte`: +Ας δημιουργήσουμε τώρα το template `Edit/create.latte`: -```latte .{file:app/Presenters/templates/Edit/create.latte} +```latte .{file:app/Presentation/Edit/create.latte} {block content} -<h1>New post</h1> +<h1>Νέα ανάρτηση</h1> {control postForm} ``` -Όλα θα πρέπει να είναι ξεκάθαρα τώρα. Η τελευταία γραμμή δείχνει τη φόρμα που πρόκειται να δημιουργήσουμε. +Όλα θα πρέπει να είναι ήδη σαφή. Η τελευταία γραμμή αποδίδει τη φόρμα που μόλις δημιουργήσαμε. -Θα μπορούσαμε επίσης να δημιουργήσουμε μια αντίστοιχη μέθοδο `renderCreate`, αλλά δεν είναι απαραίτητο. Δεν χρειάζεται να πάρουμε δεδομένα από τη βάση δεδομένων και να τα περάσουμε στο πρότυπο, οπότε η μέθοδος αυτή θα είναι κενή. Σε τέτοιες περιπτώσεις, η μέθοδος μπορεί να μην υπάρχει καθόλου. +Θα μπορούσαμε επίσης να δημιουργήσουμε την αντίστοιχη μέθοδο `renderCreate`, αλλά δεν είναι απαραίτητο. Δεν χρειαζόμαστε να λάβουμε δεδομένα από τη βάση δεδομένων και να τα περάσουμε στο template, οπότε αυτή η μέθοδος θα ήταν κενή. Σε τέτοιες περιπτώσεις, η μέθοδος δεν χρειάζεται καν να υπάρχει. -Σύνδεσμος για τη δημιουργία αναρτήσεων .[#toc-link-for-creating-posts] -====================================================================== +Σύνδεσμος για δημιουργία αναρτήσεων +=================================== -Πιθανώς γνωρίζετε ήδη πώς να προσθέσετε έναν σύνδεσμο στο `EditPresenter` και την ενέργεια `create`. Δοκιμάστε το. +Πιθανότατα ήδη ξέρετε πώς να προσθέσετε έναν σύνδεσμο στον `EditPresenter` και την action `create` του. Δοκιμάστε το. -Απλά προσθέστε στο αρχείο `app/Presenters/templates/Home/default.latte`: +Απλά προσθέστε στο αρχείο `app/Presentation/Home/default.latte`: ```latte -<a n:href="Edit:create">Write new post</a> +<a n:href="Edit:create">Γράψτε νέα ανάρτηση</a> ``` -Επεξεργασία αναρτήσεων .[#toc-editing-posts] -============================================ +Επεξεργασία αναρτήσεων +====================== -Ας προσθέσουμε επίσης τη δυνατότητα επεξεργασίας υφιστάμενων αναρτήσεων. Θα είναι πολύ απλό - έχουμε ήδη το `postForm` και μπορούμε να το χρησιμοποιήσουμε και για την επεξεργασία. +Τώρα θα προσθέσουμε επίσης τη δυνατότητα επεξεργασίας της ανάρτησης. Θα είναι πολύ απλό. Έχουμε ήδη έτοιμη τη φόρμα `postForm` και μπορούμε να τη χρησιμοποιήσουμε και για επεξεργασία. -Θα προσθέσουμε μια νέα σελίδα `edit` στο `EditPresenter`: +Θα προσθέσουμε μια νέα σελίδα `edit` στον presenter `EditPresenter`: -```php .{file:app/Presenters/EditPresenter.php} -public function renderEdit(int $postId): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +public function renderEdit(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { $this->error('Post not found'); @@ -135,26 +135,26 @@ public function renderEdit(int $postId): void } ``` -Και θα δημιουργήσουμε το πρότυπο `Edit/edit.latte`: +Και θα δημιουργήσουμε ένα άλλο template `Edit/edit.latte`: -```latte .{file:app/Presenters/templates/Edit/edit.latte} +```latte .{file:app/Presentation/Edit/edit.latte} {block content} -<h1>Edit post</h1> +<h1>Επεξεργασία ανάρτησης</h1> {control postForm} ``` -Και ενημερώστε τη μέθοδο `postFormSucceeded`, η οποία θα μπορεί είτε να προσθέτει μια νέα ανάρτηση (όπως κάνει τώρα), είτε να επεξεργάζεται τις υπάρχουσες: +Και θα τροποποιήσουμε τη μέθοδο `postFormSucceeded`, η οποία θα μπορεί τόσο να προσθέσει ένα νέο άρθρο (όπως κάνει τώρα) όσο και να επεξεργαστεί ένα ήδη υπάρχον άρθρο: -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); - if ($postId) { + if ($id) { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); $post->update($data); } else { @@ -163,26 +163,25 @@ public function postFormSucceeded(array $data): void ->insert($data); } - $this->flashMessage('Post was published', 'success'); + $this->flashMessage('Η ανάρτηση δημοσιεύτηκε με επιτυχία.', 'success'); $this->redirect('Post:show', $post->id); } ``` -Όταν παρέχεται η παράμετρος `postId`, σημαίνει ότι γίνεται επεξεργασία μιας ανάρτησης. Σε μια τέτοια περίπτωση, θα ελέγξουμε ότι η ανάρτηση υπάρχει πραγματικά και αν ναι, θα την ενημερώσουμε στη βάση δεδομένων. Εάν δεν παρέχεται η παράμετρος `postId`, σημαίνει ότι θα προστεθεί μια νέα ανάρτηση. +Εάν η παράμετρος `id` είναι διαθέσιμη, σημαίνει ότι θα επεξεργαστούμε την ανάρτηση. Σε αυτήν την περίπτωση, θα επαληθεύσουμε ότι η ζητούμενη ανάρτηση υπάρχει πραγματικά και, αν ναι, θα την ενημερώσουμε στη βάση δεδομένων. Εάν η παράμετρος `id` δεν είναι διαθέσιμη, τότε σημαίνει ότι θα πρέπει να προστεθεί μια νέα ανάρτηση. -Αλλά από πού προέρχεται το `postId`; Είναι η παράμετρος που δίνεται στη μέθοδο `renderEdit`. +Αλλά από πού προέρχεται αυτή η παράμετρος `id`; Είναι η παράμετρος που εισήχθη στη μέθοδο `renderEdit`. -Μπορείτε τώρα να προσθέσετε έναν σύνδεσμο στο πρότυπο `app/Presenters/templates/Post/show.latte`: +Τώρα μπορούμε να προσθέσουμε έναν σύνδεσμο στο template `app/Presentation/Post/show.latte`: ```latte -<a n:href="Edit:edit $post->id">Edit this post</a> +<a n:href="Edit:edit $post->id">Επεξεργασία ανάρτησης</a> ``` -Περίληψη .[#toc-summary] -======================== +Σύνοψη +====== -Το ιστολόγιο λειτουργεί, ο κόσμος σχολιάζει γρήγορα και δεν βασιζόμαστε πλέον στον Adminer για την προσθήκη νέων αναρτήσεων. Είναι πλήρως ανεξάρτητο και ακόμη και κανονικοί άνθρωποι μπορούν να δημοσιεύουν εκεί. Αλλά περιμένετε, αυτό μάλλον δεν είναι εντάξει, ότι ο οποιοσδήποτε, εννοώ πραγματικά ο οποιοσδήποτε στο Διαδίκτυο, μπορεί να δημοσιεύσει στο ιστολόγιό μας. Απαιτείται κάποια μορφή ελέγχου ταυτότητας, ώστε μόνο οι συνδεδεμένοι χρήστες να μπορούν να δημοσιεύουν. Αυτό θα το προσθέσουμε στο επόμενο κεφάλαιο. +Το blog είναι τώρα λειτουργικό, οι επισκέπτες σχολιάζουν ενεργά και δεν χρειαζόμαστε πλέον το Adminer για δημοσίευση. Η εφαρμογή είναι πλήρως ανεξάρτητη και ο καθένας μπορεί να προσθέσει μια νέα ανάρτηση. Μια στιγμή, αυτό μάλλον δεν είναι εντάξει, ότι ο καθένας - και εννοώ πραγματικά οποιονδήποτε με πρόσβαση στο διαδίκτυο - μπορεί να προσθέσει νέες αναρτήσεις. Απαιτείται κάποια ασφάλεια, ώστε μόνο ένας συνδεδεμένος χρήστης να μπορεί να προσθέσει μια νέα ανάρτηση. Θα το δούμε αυτό στο επόμενο κεφάλαιο. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/el/home-page.texy b/quickstart/el/home-page.texy index 812b5ef745..5f70fbc612 100644 --- a/quickstart/el/home-page.texy +++ b/quickstart/el/home-page.texy @@ -1,41 +1,41 @@ -Αρχική σελίδα Blog -****************** +Αρχική σελίδα του blog +********************** .[perex] -Ας δημιουργήσουμε την αρχική σελίδα που εμφανίζει τις πρόσφατες δημοσιεύσεις σας. +Τώρα θα δημιουργήσουμε την αρχική σελίδα που θα εμφανίζει τις τελευταίες αναρτήσεις. -Πριν ξεκινήσουμε, θα πρέπει να γνωρίζετε τουλάχιστον κάποια βασικά στοιχεία για το πρότυπο σχεδίασης Model-View-Presenter (παρόμοιο με το MVC((Model-View-Controller))): +Πριν ξεκινήσουμε, είναι απαραίτητο να γνωρίζουμε τουλάχιστον τα βασικά του σχεδιαστικού προτύπου Model-View-Presenter (παρόμοιο με το MVC((Model-View-Controller))): -- **Μοντέλο** - επίπεδο επεξεργασίας δεδομένων. Είναι πλήρως διαχωρισμένο από την υπόλοιπη εφαρμογή. Επικοινωνεί μόνο με τους παρουσιαστές. +- **Model** - το επίπεδο που εργάζεται με τα δεδομένα. Είναι εντελώς διαχωρισμένο από την υπόλοιπη εφαρμογή. Επικοινωνεί μόνο με τον presenter. -- **View** - ένα επίπεδο ορισμού front-end. Αποδίδει τα ζητούμενα δεδομένα στο χρήστη χρησιμοποιώντας πρότυπα. +- **View** - το επίπεδο front-end. Αποδίδει τα ζητούμενα δεδομένα χρησιμοποιώντας πρότυπα και τα εμφανίζει στον χρήστη. -- **Παρουσιαστής** (ή ελεγκτής) - ένα επίπεδο σύνδεσης. Ο παρουσιαστής συνδέει το μοντέλο και την προβολή. Χειρίζεται τα αιτήματα, ζητά από το Μοντέλο δεδομένα και στη συνέχεια τα μεταβιβάζει στην τρέχουσα Προβολή. +- **Presenter** (ή Controller) - το συνδετικό επίπεδο. Ο Presenter συνδέει το Model και το View. Επεξεργάζεται τα αιτήματα, ζητά δεδομένα από το Model και τα επιστρέφει στο View. -Στην περίπτωση μιας πολύ απλής εφαρμογής όπως το ιστολόγιό μας, το επίπεδο Μοντέλου θα αποτελείται στην πραγματικότητα μόνο από ερωτήματα προς την ίδια τη βάση δεδομένων - δεν χρειαζόμαστε επιπλέον κώδικα PHP γι' αυτό. Το μόνο που χρειάζεται είναι να δημιουργήσουμε τα στρώματα Presenter και View. Στη Nette, κάθε Presenter έχει τα δικά του Views, οπότε θα συνεχίσουμε και με τα δύο ταυτόχρονα. +Στην περίπτωση απλών εφαρμογών, όπως θα είναι το blog μας, ολόκληρο το επίπεδο model θα αποτελείται μόνο από ερωτήματα στη βάση δεδομένων - γι' αυτό δεν χρειαζόμαστε προς το παρόν επιπλέον κώδικα. Για αρχή, θα δημιουργήσουμε λοιπόν μόνο τους presenters και τα πρότυπα. Στο Nette, κάθε presenter έχει τα δικά του πρότυπα, οπότε θα τα δημιουργούμε ταυτόχρονα. -Δημιουργία της βάσης δεδομένων με το Adminer .[#toc-creating-the-database-with-adminer] -======================================================================================= +Δημιουργία βάσης δεδομένων με το Adminer +======================================== -Για την αποθήκευση των δεδομένων, θα χρησιμοποιήσουμε τη βάση δεδομένων MySQL επειδή είναι η πιο συνηθισμένη επιλογή μεταξύ των προγραμματιστών ιστοσελίδων. Αλλά αν δεν σας αρέσει, μπορείτε να χρησιμοποιήσετε μια βάση δεδομένων της επιλογής σας. +Για την αποθήκευση δεδομένων θα χρησιμοποιήσουμε τη βάση δεδομένων MySQL, επειδή είναι η πιο διαδεδομένη μεταξύ των προγραμματιστών web εφαρμογών. Ωστόσο, αν δεν θέλετε να τη χρησιμοποιήσετε, μπορείτε να επιλέξετε μια βάση δεδομένων της αρεσκείας σας. -Ας προετοιμάσουμε τη βάση δεδομένων που θα αποθηκεύει τις αναρτήσεις του ιστολογίου μας. Μπορούμε να ξεκινήσουμε πολύ απλά - μόνο με έναν ενιαίο πίνακα για τις αναρτήσεις. +Τώρα θα προετοιμάσουμε τη δομή της βάσης δεδομένων όπου θα αποθηκεύονται τα άρθρα του blog μας. Θα ξεκινήσουμε πολύ απλά - θα δημιουργήσουμε μόνο έναν πίνακα για τις αναρτήσεις. -Για να δημιουργήσουμε τη βάση δεδομένων μπορούμε να κατεβάσουμε [το Adminer |https://www.adminer.org], ή μπορείτε να χρησιμοποιήσετε κάποιο άλλο εργαλείο για τη διαχείριση βάσεων δεδομένων. +Για τη δημιουργία της βάσης δεδομένων μπορούμε να κατεβάσουμε το [Adminer |https://www.adminer.org], ή άλλο αγαπημένο σας εργαλείο διαχείρισης βάσεων δεδομένων. -Ας ανοίξουμε το Adminer και ας δημιουργήσουμε μια νέα βάση δεδομένων με όνομα `quickstart`. +Ανοίγουμε το Adminer και δημιουργούμε μια νέα βάση δεδομένων με το όνομα `quickstart`. -Δημιουργήστε έναν νέο πίνακα με το όνομα `posts` και προσθέστε αυτές τις στήλες: -- `id` int, κάντε κλικ στο autoincrement (AI) -- `title` varchar, μήκος 255 +Δημιουργούμε έναν νέο πίνακα με το όνομα `posts` και με τις ακόλουθες στήλες: +- `id` int, επιλέγουμε autoincrement (AI) +- `title` varchar, length 255 - `content` text - `created_at` timestamp -Θα πρέπει να μοιάζει ως εξής: +Η τελική δομή θα πρέπει να μοιάζει έτσι: [* adminer-posts.webp *] @@ -49,9 +49,9 @@ CREATE TABLE `posts` ( ``` .[caution] -Είναι πολύ σημαντικό να χρησιμοποιείτε τον αποθηκευτικό χώρο του πίνακα **InnoDB**. Θα δείτε τον λόγο αργότερα. Προς το παρόν, απλά επιλέξτε αυτό και στείλτε το. Μπορείτε να πατήσετε Save τώρα. +Είναι πραγματικά σημαντικό να χρησιμοποιήσετε την αποθήκευση **InnoDB**. Σε λίγο θα δείξουμε γιατί. Προς το παρόν, απλά επιλέξτε την και κάντε κλικ στην αποθήκευση. -Δοκιμάστε να προσθέσετε μερικές δειγματικές αναρτήσεις στο ιστολόγιο πριν υλοποιήσουμε τη δυνατότητα προσθήκης νέων αναρτήσεων απευθείας από την εφαρμογή μας. +Πριν δημιουργήσουμε τη δυνατότητα προσθήκης άρθρων στη βάση δεδομένων μέσω της εφαρμογής, προσθέστε μερικά δείγματα άρθρων στο blog χειροκίνητα. ```sql INSERT INTO `posts` (`id`, `title`, `content`, `created_at`) VALUES @@ -61,37 +61,34 @@ INSERT INTO `posts` (`id`, `title`, `content`, `created_at`) VALUES ``` -Σύνδεση με τη βάση δεδομένων .[#toc-connecting-to-the-database] -=============================================================== +Σύνδεση στη βάση δεδομένων +========================== -Τώρα, όταν η βάση δεδομένων έχει δημιουργηθεί και έχουμε κάποιες αναρτήσεις σε αυτήν, είναι η κατάλληλη στιγμή να τις εμφανίσουμε στη νέα μας λαμπερή σελίδα. +Τώρα που η βάση δεδομένων έχει δημιουργηθεί και έχουμε αποθηκεύσει μερικά άρθρα σε αυτήν, είναι η κατάλληλη στιγμή να τα εμφανίσουμε στην όμορφη νέα μας σελίδα. -Πρώτα, πρέπει να πούμε στην εφαρμογή μας ποια βάση δεδομένων θα χρησιμοποιήσει. Η ρύθμιση της σύνδεσης με τη βάση δεδομένων αποθηκεύεται στο `config/local.neon`. Ορίστε τη σύνδεση DSN((Data Source Name)) και τα διαπιστευτήριά σας. Θα πρέπει να μοιάζει κάπως έτσι: +Πρώτα πρέπει να πούμε στην εφαρμογή ποια βάση δεδομένων να χρησιμοποιήσει. Η σύνδεση στη βάση δεδομένων ρυθμίζεται στο αρχείο `config/common.neon` χρησιμοποιώντας το DSN((Data Source Name)) και τα διαπιστευτήρια σύνδεσης. Θα πρέπει να μοιάζει κάπως έτσι: -```neon .{file:config/local.neon} +```neon .{file:config/common.neon} database: dsn: 'mysql:host=127.0.0.1;dbname=quickstart' - user: *enter user name* - password: *enter password here* + user: *εισάγετε εδώ το όνομα χρήστη* + password: *εισάγετε εδώ τον κωδικό πρόσβασης στη βάση δεδομένων* ``` .[note] -Προσέξτε τις εσοχές κατά την επεξεργασία αυτού του αρχείου. Η [μορφή NEON |neon:format] δέχεται τόσο κενά όσο και ταμπέλες, αλλά όχι και τα δύο μαζί! Το αρχείο ρυθμίσεων στο Web Project χρησιμοποιεί τα tabs ως προεπιλογή. +Κατά την επεξεργασία αυτού του αρχείου, προσέξτε την εσοχή των γραμμών. Η μορφή [NEON |neon:format] δέχεται τόσο εσοχή με κενά όσο και εσοχή με tabs, αλλά όχι και τα δύο ταυτόχρονα. Το προεπιλεγμένο αρχείο διαμόρφωσης στο Web Project χρησιμοποιεί tabs. -Ολόκληρη η διαμόρφωση αποθηκεύεται στο `config/` στα αρχεία `common.neon` και `local.neon`. Το αρχείο `common.neon` περιέχει τη γενική διαμόρφωση της εφαρμογής και το `local.neon` περιέχει μόνο τις παραμέτρους που αφορούν το περιβάλλον (π.χ. τη διαφορά μεταξύ του διακομιστή ανάπτυξης και του διακομιστή παραγωγής). +Πέρασμα της σύνδεσης βάσης δεδομένων +==================================== -Εισαγωγή της σύνδεσης της βάσης δεδομένων .[#toc-injecting-the-database-connection] -=================================================================================== +Ο presenter `HomePresenter`, ο οποίος θα είναι υπεύθυνος για την εμφάνιση των άρθρων, χρειάζεται σύνδεση στη βάση δεδομένων. Για να την αποκτήσουμε, θα χρησιμοποιήσουμε τον κατασκευαστή, ο οποίος θα μοιάζει έτσι: -Ο παρουσιαστής `HomePresenter`, ο οποίος θα απαριθμεί τα άρθρα, χρειάζεται μια σύνδεση με τη βάση δεδομένων. Για να τη λάβει, γράψτε έναν κατασκευαστή όπως αυτός: - -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; -use Nette\Application\UI\Form; final class HomePresenter extends Nette\Application\UI\Presenter { @@ -105,12 +102,12 @@ final class HomePresenter extends Nette\Application\UI\Presenter ``` -Φόρτωση αναρτήσεων από τη βάση δεδομένων .[#toc-loading-posts-from-the-database] -================================================================================ +Φόρτωση αναρτήσεων από τη βάση δεδομένων +======================================== -Τώρα ας φέρουμε τις αναρτήσεις από τη βάση δεδομένων και ας τις περάσουμε στο πρότυπο, το οποίο στη συνέχεια θα αποδώσει τον κώδικα HTML. Γι' αυτό το σκοπό υπάρχει η λεγόμενη μέθοδος *render*: +Τώρα θα φορτώσουμε τις αναρτήσεις από τη βάση δεδομένων και θα τις στείλουμε στο πρότυπο, το οποίο στη συνέχεια θα τις αποδώσει ως κώδικα HTML. Γι' αυτό προορίζεται η λεγόμενη *render* μέθοδος: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} public function renderDefault(): void { $this->template->posts = $this->database @@ -120,41 +117,41 @@ public function renderDefault(): void } ``` -Ο παρουσιαστής έχει τώρα μια μέθοδο render `renderDefault()` που περνάει τα δεδομένα σε μια προβολή που ονομάζεται `default`. Τα πρότυπα του παρουσιαστή μπορούν να βρεθούν στο `app/Presenters/templates/{PresenterName}/{viewName}.latte`, οπότε σε αυτή την περίπτωση το πρότυπο θα βρίσκεται στο `app/Presenters/templates/Home/default.latte`. Στο πρότυπο, μια μεταβλητή με το όνομα `$posts` είναι τώρα διαθέσιμη, η οποία περιέχει τις δημοσιεύσεις από τη βάση δεδομένων. +Ο presenter τώρα περιέχει μία μέθοδο απόδοσης `renderDefault()`, η οποία περνά δεδομένα από τη βάση δεδομένων στο πρότυπο (View). Τα πρότυπα βρίσκονται στο `app/Presentation/{PresenterName}/{viewName}.latte`, οπότε σε αυτήν την περίπτωση το πρότυπο βρίσκεται στο `app/Presentation/Home/default.latte`. Στο πρότυπο τώρα θα είναι διαθέσιμη η μεταβλητή `$posts`, στην οποία βρίσκονται οι αναρτήσεις που λήφθηκαν από τη βάση δεδομένων. -Πρότυπο .[#toc-template] -======================== +Πρότυπο +======= -Υπάρχει ένα γενικό πρότυπο για ολόκληρη τη σελίδα (που ονομάζεται *layout*, με κεφαλίδα, φύλλα στυλ, υποσέλιδο, ...) και στη συνέχεια ειδικά πρότυπα για κάθε προβολή (π.χ. για την εμφάνιση της λίστας των αναρτήσεων του ιστολογίου), τα οποία μπορούν να παρακάμψουν ορισμένα τμήματα του προτύπου layout. +Για ολόκληρη την ιστοσελίδα έχουμε διαθέσιμο το κύριο πρότυπο (το οποίο ονομάζεται *layout*, περιέχει την κεφαλίδα, τα στυλ, το υποσέλιδο,...) και επιπλέον συγκεκριμένα πρότυπα για κάθε view (π.χ. για την εμφάνιση των αναρτήσεων στο blog), τα οποία μπορούν να αντικαταστήσουν ορισμένα μέρη του κύριου προτύπου. -Από προεπιλογή, το πρότυπο διάταξης βρίσκεται στο `templates/@layout.latte`, το οποίο περιέχει: +Στην προεπιλεγμένη κατάσταση, το layout πρότυπο βρίσκεται στο `app/Presentation/@layout.latte` και περιέχει: -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... {include content} ... ``` -`{include content}` εισάγει ένα μπλοκ με το όνομα `content` στο κύριο πρότυπο. Μπορείτε να το ορίσετε στα πρότυπα κάθε προβολής. Σε αυτή την περίπτωση, θα επεξεργαστούμε το αρχείο `Home/default.latte` ως εξής: +Η σύνταξη `{include content}` εισάγει στο κύριο πρότυπο ένα μπλοκ με το όνομα `content`. Αυτό θα το ορίσουμε στα πρότυπα των επιμέρους views. Στην περίπτωσή μας, το αρχείο `Home/default.latte` θα το τροποποιήσουμε ως εξής: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} Hello World {/block} ``` -Ορίζει το [μπλοκ |latte:tags#block]*περιεχόμενο*, το οποίο θα εισαχθεί στη διάταξη. Αν ανανεώσετε το πρόγραμμα περιήγησης, θα δείτε μια σελίδα με το κείμενο "Hello world" (στον πηγαίο κώδικα επίσης με HTML κεφαλίδα και υποσέλιδο που ορίζονται στο `@layout.latte`). +Με αυτόν τον τρόπο ορίσαμε το [μπλοκ |latte:tags#block] *content*, το οποίο θα εισαχθεί στο κύριο layout. Αν ανανεώσουμε ξανά τον browser, θα δούμε τη σελίδα με το κείμενο "Hello World" (στον πηγαίο κώδικα και με την HTML κεφαλίδα και υποσέλιδο που ορίζονται στο `@layout.latte`). -Ας εμφανίσουμε τις αναρτήσεις του ιστολογίου - θα επεξεργαστούμε το πρότυπο ως εξής: +Ας εμφανίσουμε τις αναρτήσεις από το blog - θα τροποποιήσουμε το πρότυπο ως εξής: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - <h1>My blog</h1> + <h1>Το blog μου</h1> {foreach $posts as $post} <div class="post"> - <div class="date">{$post->created_at|date:'j. n. Y'}</div> + <div class="date">{$post->created_at|date:'F j, Y'}</div> <h2>{$post->title}</h2> @@ -164,42 +161,41 @@ public function renderDefault(): void {/block} ``` -Αν ανανεώσετε το πρόγραμμα περιήγησής σας, θα δείτε τη λίστα με τις αναρτήσεις του ιστολογίου σας. Η λίστα δεν είναι πολύ φανταχτερή ή πολύχρωμη, γι' αυτό μη διστάσετε να προσθέσετε κάποιο [λαμπερό CSS |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] στο `www/css/style.css` και να το συνδέσετε σε μια διάταξη: +Αν ανανεώσουμε τον browser, θα δούμε τη λίστα όλων των αναρτήσεων. Η λίστα δεν είναι ακόμα πολύ όμορφη, ούτε πολύχρωμη, γι' αυτό μπορούμε να προσθέσουμε στο αρχείο `www/css/style.css` μερικά [στυλ CSS |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] και να το συνδέσουμε στο layout: -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <link rel="stylesheet" href="{$basePath}/css/style.css"> </head> ... ``` -Η ετικέτα `{foreach}` επαναλαμβάνει όλες τις δημοσιεύσεις που έχουν περάσει στο πρότυπο στη μεταβλητή `$posts` και εμφανίζει ένα κομμάτι κώδικα HTML για κάθε δημοσίευση. Ακριβώς όπως θα έκανε ένας κώδικας PHP. +Το tag `{foreach}` επαναλαμβάνει όλες τις αναρτήσεις που περάσαμε στο πρότυπο στη μεταβλητή `$posts`, και για κάθε μία αποδίδει το δεδομένο κομμάτι HTML. Συμπεριφέρεται ακριβώς όπως ο κώδικας PHP. -Το `|date` ονομάζεται φίλτρο. Τα φίλτρα χρησιμοποιούνται για τη μορφοποίηση της εξόδου. Το συγκεκριμένο φίλτρο μετατρέπει μια ημερομηνία (π.χ. `2013-04-12`) στην πιο ευανάγνωστη μορφή της (`12. 4. 2013`). Το φίλτρο `|truncate` περικόπτει τη συμβολοσειρά στο καθορισμένο μέγιστο μήκος και προσθέτει μια έλλειψη στο τέλος, αν η συμβολοσειρά είναι περικομμένη. Δεδομένου ότι πρόκειται για προεπισκόπηση, δεν υπάρχει λόγος να εμφανιστεί το πλήρες περιεχόμενο του άρθρου. Άλλα προεπιλεγμένα φίλτρα [μπορείτε να βρείτε στην τεκμηρίωση |latte:filters] ή μπορείτε να δημιουργήσετε τα δικά σας αν χρειάζεται. +Τη σύνταξη `|date:` την ονομάζουμε φίλτρο. Τα φίλτρα προορίζονται για τη μορφοποίηση της εξόδου. Αυτό το συγκεκριμένο φίλτρο μετατρέπει την ημερομηνία (π.χ. `2013-04-12`) στην πιο ευανάγνωστη μορφή της (`April 12, 2013`). Το φίλτρο `|truncate` περικόπτει τη συμβολοσειρά στο αναφερόμενο μέγιστο μήκος και στην περίπτωση που η συμβολοσειρά περικοπεί, προσθέτει στο τέλος τρεις τελείες. Δεδομένου ότι πρόκειται για προεπισκόπηση, δεν έχει νόημα να εμφανίζεται ολόκληρο το περιεχόμενο του άρθρου. Άλλα προεπιλεγμένα φίλτρα [βρίσκουμε στην τεκμηρίωση |latte:filters] ή μπορούμε να δημιουργήσουμε τα δικά μας, όταν χρειάζεται. -Και κάτι ακόμα. Μπορούμε να κάνουμε τον κώδικα λίγο πιο σύντομο και συνεπώς πιο απλό. Μπορούμε να αντικαταστήσουμε τα *Latte tags* με *n:attributes* όπως παρακάτω: +Ακόμα ένα πράγμα. Τον προηγούμενο κώδικα μπορούμε να τον συντομεύσουμε και να τον απλοποιήσουμε. Αυτό το πετυχαίνουμε αντικαθιστώντας τα *Latte tags* με *n:attributes*: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - <h1>My blog</h1> + <h1>Το blog μου</h1> <div n:foreach="$posts as $post" class="post"> <div class="date">{$post->created_at|date:'F j, Y'}</div> <h2>{$post->title}</h2> - <div>{$post->content}</div> + <div>{$post->content|truncate:256}</div> </div> {/block} ``` -Το `n:foreach`, απλά τυλίγει το *div* με ένα μπλοκ *foreach* (κάνει ακριβώς το ίδιο πράγμα με το προηγούμενο μπλοκ κώδικα). +Η ιδιότητα `n:foreach` περιβάλλει το *div* με ένα μπλοκ *foreach* (λειτουργεί ακριβώς το ίδιο με τον προηγούμενο κώδικα). -Περίληψη .[#toc-summary] -======================== +Σύνοψη +====== -Έχουμε μια πολύ απλή βάση δεδομένων MySQL με μερικές αναρτήσεις σε ιστολόγια. Η εφαρμογή συνδέεται με τη βάση δεδομένων και εμφανίζει μια απλή λίστα με τις αναρτήσεις. +Τώρα έχουμε μια πολύ απλή βάση δεδομένων MySQL με μερικές αναρτήσεις. Η εφαρμογή συνδέεται σε αυτήν τη βάση δεδομένων και εμφανίζει μια απλή λίστα αυτών των αναρτήσεων στο πρότυπο. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/el/model.texy b/quickstart/el/model.texy index 2f8aa72258..57d179f34f 100644 --- a/quickstart/el/model.texy +++ b/quickstart/el/model.texy @@ -1,13 +1,13 @@ -Μοντέλο -******* +Model +***** -Καθώς η εφαρμογή μας αναπτύσσεται, σύντομα διαπιστώνουμε ότι πρέπει να εκτελέσουμε παρόμοιες λειτουργίες της βάσης δεδομένων σε διάφορες τοποθεσίες και σε διάφορους παρουσιαστές, για παράδειγμα να αποκτήσουμε τα πιο πρόσφατα δημοσιευμένα άρθρα. Αν βελτιώσουμε την εφαρμογή μας προσθέτοντας μια σημαία στα άρθρα για να υποδεικνύει μια κατάσταση εργασίας σε εξέλιξη, πρέπει επίσης να περάσουμε από όλες τις τοποθεσίες της εφαρμογής μας και να προσθέσουμε μια ρήτρα where για να βεβαιωθούμε ότι επιλέγονται μόνο τα έτοιμα άρθρα. +Καθώς η εφαρμογή μεγαλώνει, σύντομα θα διαπιστώσουμε ότι σε διάφορα σημεία, σε διάφορους presenters, χρειαζόμαστε να εκτελούμε παρόμοιες λειτουργίες με τη βάση δεδομένων. Για παράδειγμα, να λαμβάνουμε τα πιο πρόσφατα δημοσιευμένα άρθρα. Όταν βελτιώνουμε την εφαρμογή, για παράδειγμα προσθέτοντας στα άρθρα μια σημαία που υποδεικνύει αν είναι πρόχειρο, πρέπει στη συνέχεια να περάσουμε από όλα τα σημεία στην εφαρμογή όπου λαμβάνονται άρθρα από τη βάση δεδομένων και να συμπληρώσουμε την συνθήκη where, ώστε να επιλέγονται μόνο τα μη πρόχειρα άρθρα. -Σε αυτό το σημείο, η άμεση εργασία με τη βάση δεδομένων καθίσταται ανεπαρκής και θα είναι πιο έξυπνο να βοηθήσουμε τον εαυτό μας με μια νέα συνάρτηση που επιστρέφει τα δημοσιευμένα άρθρα. Και όταν αργότερα προσθέσουμε μια άλλη ρήτρα (για παράδειγμα για να μην εμφανίζονται άρθρα με μελλοντική ημερομηνία), επεξεργαζόμαστε τον κώδικά μας μόνο σε ένα σημείο. +Σε εκείνη τη στιγμή, η άμεση εργασία με τη βάση δεδομένων γίνεται ανεπαρκής και θα ήταν πιο έξυπνο να βοηθηθούμε με μια νέα συνάρτηση που θα μας επιστρέφει τα δημοσιευμένα άρθρα. Και όταν αργότερα προσθέσουμε μια άλλη συνθήκη, για παράδειγμα ότι δεν πρέπει να εμφανίζονται άρθρα με μελλοντική ημερομηνία, θα τροποποιήσουμε τον κώδικα μόνο σε ένα σημείο. -Θα τοποθετήσουμε τη συνάρτηση στην κλάση `PostFacade` και θα την ονομάσουμε `getPublicArticles()`. +Θα τοποθετήσουμε τη συνάρτηση, για παράδειγμα, στην κλάση `PostFacade` και θα την ονομάσουμε `getPublicArticles()`. -Θα δημιουργήσουμε την κλάση μοντέλου `PostFacade` στον κατάλογο `app/Model/` για να αναλάβει τη φροντίδα των άρθρων μας: +Στον κατάλογο `app/Model/` θα δημιουργήσουμε την κλάση model `PostFacade`, η οποία θα είναι υπεύθυνη για τα άρθρα: ```php .{file:app/Model/PostFacade.php} <?php @@ -32,13 +32,13 @@ final class PostFacade } ``` -Στην κλάση περνάμε την βάση δεδομένων Explorer:[api:Nette\Database\Explorer]. Με αυτόν τον τρόπο θα εκμεταλλευτούμε τη δύναμη του [DI container |dependency-injection:passing-dependencies]. +Στην κλάση, μέσω του κατασκευαστή, θα ζητήσουμε να μας περαστεί το Database Explorer:[api:Nette\Database\Explorer]. Θα εκμεταλλευτούμε έτσι τη δύναμη του [DI container|dependency-injection:passing-dependencies]. -Θα μεταβούμε στο `HomePresenter` το οποίο θα επεξεργαστούμε έτσι ώστε να απαλλαγούμε από την εξάρτηση από το `Nette\Database\Explorer` αντικαθιστώντας την με μια νέα εξάρτηση από τη νέα μας κλάση. +Θα μεταβούμε στον `HomePresenter`, τον οποίο θα τροποποιήσουμε έτσι ώστε να απαλλαγούμε από την εξάρτηση από το `Nette\Database\Explorer` και να την αντικαταστήσουμε με μια νέα εξάρτηση από τη νέα μας κλάση. -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use App\Model\PostFacade; use Nette; @@ -59,10 +59,9 @@ final class HomePresenter extends Nette\Application\UI\Presenter } ``` -Στο τμήμα χρήσης, χρησιμοποιούμε το `App\Model\PostFacade`, οπότε μπορούμε να συντομεύσουμε τον κώδικα PHP σε `PostFacade`. Ζητάμε αυτό το αντικείμενο στον κατασκευαστή, το γράφουμε στην ιδιότητα `$facade` και το χρησιμοποιούμε στη μέθοδο renderDefault. +Στην ενότητα use έχουμε `App\Model\PostFacade`, οπότε μπορούμε να συντομεύσουμε τη σύνταξη στον κώδικα PHP σε `PostFacade`. Θα ζητήσουμε αυτό το αντικείμενο στον κατασκευαστή, θα το γράψουμε στην ιδιότητα `$facade` και θα το χρησιμοποιήσουμε στη μέθοδο renderDefault. -Το τελευταίο βήμα που απομένει είναι να διδάξουμε στον περιέκτη DI να παράγει αυτό το αντικείμενο. Αυτό συνήθως γίνεται με την προσθήκη ενός σημείου αναφοράς στο αρχείο `config/services.neon` στην ενότητα `services`, δίνοντας το πλήρες όνομα της κλάσης και τις παραμέτρους του κατασκευαστή. -Αυτό το καταχωρεί, τρόπον τινά, και το αντικείμενο καλείται στη συνέχεια **service**. Χάρη σε κάποια μαγεία που ονομάζεται [αυτόματη σύνδεση |dependency-injection:autowiring], συνήθως δεν χρειάζεται να καθορίσουμε τις παραμέτρους του κατασκευαστή, επειδή το DI θα τις αναγνωρίσει και θα τις περάσει αυτόματα. Έτσι, θα ήταν αρκετό να δώσουμε απλώς το όνομα της κλάσης: +Απομένει το τελευταίο βήμα, να διδάξουμε στον DI container πώς να παράγει αυτό το αντικείμενο. Αυτό συνήθως γίνεται προσθέτοντας μια κουκκίδα στο αρχείο `config/services.neon` στην ενότητα `services`, αναφέροντας το πλήρες όνομα της κλάσης και τις παραμέτρους του κατασκευαστή. Με αυτόν τον τρόπο την καταχωρούμε, όπως λέγεται, και το αντικείμενο ονομάζεται τότε **υπηρεσία**. Χάρη στη μαγεία που ονομάζεται [autowiring |dependency-injection:autowiring], συνήθως δεν χρειάζεται να αναφέρουμε τις παραμέτρους του κατασκευαστή, επειδή το DI τις αναγνωρίζει και τις περνά αυτόματα. Θα αρκούσε λοιπόν να αναφέρουμε μόνο το όνομα της κλάσης: ```neon .{file:config/services.neon} ... @@ -71,16 +70,15 @@ services: - App\Model\PostFacade ``` -Ωστόσο, δεν χρειάζεται να προσθέσετε ούτε αυτή τη γραμμή. Στην ενότητα `search` στην αρχή του `services.neon` ορίζεται ότι όλες οι κλάσεις που τελειώνουν με `-Facade` ή `-Factory` θα αναζητηθούν αυτόματα από το DI, κάτι που ισχύει και για το `PostFacade`. +Ωστόσο, ούτε αυτή τη γραμμή δεν χρειάζεται να προσθέσετε. Στην ενότητα `search` στην αρχή του `services.neon` ορίζεται ότι όλες οι κλάσεις που τελειώνουν με τη λέξη `-Facade` ή `-Factory` τις βρίσκει το DI από μόνο του, κάτι που ισχύει και για την `PostFacade`. -Περίληψη .[#toc-summary] -======================== +Σύνοψη +====== -Η κλάση `PostFacade` ζητάει το `Nette\Database\Explorer` σε έναν κατασκευαστή και επειδή αυτή η κλάση είναι καταχωρημένη στον περιέκτη DI, ο περιέκτης δημιουργεί αυτή την περίπτωση και την περνάει. Το DI με αυτόν τον τρόπο δημιουργεί μια περίπτωση `PostFacade` για εμάς και την περνάει σε έναν κατασκευαστή στην κλάση HomePresenter που τη ζήτησε. Κάτι σαν μια κούκλα Matryoshka του κώδικα :) Όλα τα συστατικά ζητούν μόνο αυτό που χρειάζονται και δεν τα ενδιαφέρει πού και πώς δημιουργείται. Η δημιουργία αναλαμβάνεται από το DI container. +Η κλάση `PostFacade` ζητά στον κατασκευαστή της να της περαστεί το `Nette\Database\Explorer` και επειδή αυτή η κλάση είναι καταχωρημένη στον DI container, ο container δημιουργεί αυτήν την παρουσία και την περνά. Το DI δημιουργεί έτσι για εμάς την παρουσία `PostFacade` και την περνά στον κατασκευαστή της κλάσης HomePresenter, η οποία την ζήτησε. Σαν μια ματριόσκα. :) Όλοι απλά λένε τι θέλουν και δεν ενδιαφέρονται για το πού και πώς δημιουργείται κάτι. Για τη δημιουργία φροντίζει ο DI container. .[note] -Εδώ μπορείτε να διαβάσετε περισσότερα για το [dependency injection |dependency-injection:introduction], και για [τη διαμόρφωση |nette:configuring]. +Εδώ μπορείτε να διαβάσετε περισσότερα για το [dependency injection |dependency-injection:introduction] και τη [διαμόρφωση |nette:configuring]. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/el/single-post.texy b/quickstart/el/single-post.texy index dd6677c763..de06e3494f 100644 --- a/quickstart/el/single-post.texy +++ b/quickstart/el/single-post.texy @@ -1,17 +1,17 @@ -Σελίδα μονής δημοσίευσης -************************ +Σελίδα με την ανάρτηση +********************** .[perex] -Ας προσθέσουμε μια άλλη σελίδα στο ιστολόγιό μας, η οποία θα εμφανίζει το περιεχόμενο μιας συγκεκριμένης ανάρτησης ιστολογίου. +Τώρα θα δημιουργήσουμε μια άλλη σελίδα του blog, η οποία θα εμφανίζει μια συγκεκριμένη ανάρτηση. -Πρέπει να δημιουργήσουμε μια νέα μέθοδο render, η οποία θα αντλεί μια συγκεκριμένη ανάρτηση του ιστολογίου και θα την περνάει στο πρότυπο. Το να έχουμε αυτή την προβολή στη διεύθυνση `HomePresenter` δεν είναι ωραίο, επειδή πρόκειται για μια ανάρτηση ιστολογίου και όχι για την αρχική σελίδα. Έτσι, ας δημιουργήσουμε μια νέα κλάση `PostPresenter` και ας την τοποθετήσουμε στο `app/Presenters`. Θα χρειαστεί μια σύνδεση με τη βάση δεδομένων, οπότε βάλτε και πάλι εκεί τον κώδικα *database injection*. +Πρέπει να δημιουργήσουμε μια νέα μέθοδο render, η οποία θα λαμβάνει ένα συγκεκριμένο άρθρο και θα το περνά στο πρότυπο. Το να έχουμε αυτή τη μέθοδο στον `HomePresenter` δεν είναι πολύ ωραίο, επειδή μιλάμε για ένα άρθρο και όχι για την αρχική σελίδα. Ας δημιουργήσουμε λοιπόν τον `PostPresenter` στο `app/Presentation/Post/`. Αυτός ο presenter χρειάζεται επίσης να συνδεθεί στη βάση δεδομένων, οπότε θα γράψουμε ξανά έναν κατασκευαστή που θα απαιτεί σύνδεση στη βάση δεδομένων. -Το `PostPresenter` θα πρέπει να μοιάζει με αυτό: +Ο `PostPresenter` θα μπορούσε λοιπόν να μοιάζει έτσι: -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Post; use Nette; use Nette\Application\UI\Form; @@ -23,50 +23,50 @@ final class PostPresenter extends Nette\Application\UI\Presenter ) { } - public function renderShow(int $postId): void + public function renderShow(int $id): void { $this->template->post = $this->database ->table('posts') - ->get($postId); + ->get($id); } } ``` -Πρέπει να ορίσουμε ένα σωστό namespaces `App\Presenters` για τον παρουσιαστή μας. Εξαρτάται από την [αντιστοίχιση του παρουσιαστή |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7]. +Δεν πρέπει να ξεχάσουμε να αναφέρουμε το σωστό namespace `App\Presentation\Post`, το οποίο υπόκειται στη ρύθμιση [αντιστοίχισης presenter |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7]. -Η μέθοδος `renderShow` απαιτεί ένα όρισμα - το ID της δημοσίευσης που θα εμφανιστεί. Στη συνέχεια, φορτώνει τη δημοσίευση από τη βάση δεδομένων και περνάει το αποτέλεσμα στο πρότυπο. +Η μέθοδος `renderShow` απαιτεί ένα όρισμα - το ID ενός συγκεκριμένου άρθρου που πρέπει να εμφανιστεί. Στη συνέχεια, φορτώνει αυτό το άρθρο από τη βάση δεδομένων και το περνά στο πρότυπο. -Στο πρότυπο `Home/default.latte` προσθέτουμε έναν σύνδεσμο προς την ενέργεια `Post:show`: +Στο πρότυπο `Home/default.latte` θα εισαγάγουμε έναν σύνδεσμο προς την action `Post:show`. -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a href="{link Post:show $post->id}">{$post->title}</a></h2> ... ``` -Η ετικέτα `{link}` δημιουργεί διεύθυνση URL που παραπέμπει στη δράση `Post:show`. Αυτή η ετικέτα προωθεί επίσης το αναγνωριστικό της δημοσίευσης ως όρισμα. +Το tag `{link}` δημιουργεί μια διεύθυνση URL που οδηγεί στην action `Post:show`. Επίσης, περνά το ID της ανάρτησης ως όρισμα. -Το ίδιο μπορούμε να γράψουμε σύντομα χρησιμοποιώντας το n:attribute: +Το ίδιο μπορούμε να γράψουμε συντομευμένα χρησιμοποιώντας την n:ιδιότητα: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a n:href="Post:show $post->id">{$post->title}</a></h2> ... ``` -Το χαρακτηριστικό `n:href` είναι παρόμοιο με την ετικέτα `{link}`. +Η ιδιότητα `n:href` είναι ανάλογη του tag `{link}`. -Το πρότυπο για τη δράση `Post:show` δεν υπάρχει ακόμη. Μπορούμε να ανοίξουμε έναν σύνδεσμο προς αυτή τη θέση. Η [Tracy |tracy:] θα εμφανίσει ένα σφάλμα, γιατί το `Post/show.latte` δεν υπάρχει. Αν δείτε κάποια άλλη αναφορά σφάλματος, πιθανόν να πρέπει να ενεργοποιήσετε το mod_rewrite στον webserver σας. +Για την action `Post:show` όμως, δεν υπάρχει ακόμα πρότυπο. Μπορούμε να δοκιμάσουμε να ανοίξουμε τον σύνδεσμο προς αυτήν την ανάρτηση. Η [Tracy |tracy:] θα εμφανίσει ένα σφάλμα, επειδή το πρότυπο `Post/show.latte` δεν υπάρχει ακόμα. Εάν βλέπετε διαφορετικό μήνυμα σφάλματος, τότε πιθανότατα θα πρέπει να ενεργοποιήσετε το `mod_rewrite` στον webserver. -Έτσι θα δημιουργήσουμε το `Post/show.latte` με αυτό το περιεχόμενο: +Θα δημιουργήσουμε λοιπόν το πρότυπο `Post/show.latte` με το ακόλουθο περιεχόμενο: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} {block content} -<p><a n:href="Home:default">← back to posts list</a></p> +<p><a n:href="Home:default">← πίσω στη λίστα αναρτήσεων</a></p> <div class="date">{$post->created_at|date:'F j, Y'}</div> @@ -75,51 +75,50 @@ final class PostPresenter extends Nette\Application\UI\Presenter <div class="post">{$post->content}</div> ``` -Ας ρίξουμε μια ματιά στα επιμέρους μέρη. +Τώρα ας περάσουμε από τα επιμέρους μέρη του προτύπου. -Η πρώτη γραμμή ξεκινά τον ορισμό ενός *ονομαζόμενου μπλοκ* που ονομάζεται "content" και το οποίο είδαμε νωρίτερα. Θα εμφανιστεί σε ένα *πρότυπο διάταξης*. Όπως μπορείτε να δείτε, λείπει η ετικέτα τέλους `{/block}`. Είναι προαιρετική. +Η πρώτη γραμμή ξεκινά τον ορισμό του μπλοκ με το όνομα "content" όπως ακριβώς και στην αρχική σελίδα. Αυτό το μπλοκ θα εμφανιστεί ξανά στο κύριο πρότυπο. Όπως βλέπετε, λείπει το τελικό tag `{/block}`. Αυτό είναι προαιρετικό. -Η δεύτερη γραμμή παρέχει έναν σύνδεσμο προς τη λίστα των αναρτήσεων του ιστολογίου, ώστε ο χρήστης να μπορεί να πλοηγείται ομαλά μπρος-πίσω στο ιστολόγιό μας. Χρησιμοποιούμε και πάλι το χαρακτηριστικό `n:href`, επομένως η Nette θα φροντίσει να δημιουργήσει τη διεύθυνση URL για εμάς. Ο σύνδεσμος δείχνει στην ενέργεια `default` του παρουσιαστή `Home` (θα μπορούσατε επίσης να γράψετε `n:href="Home:"`, καθώς η ενέργεια `default` μπορεί να παραλειφθεί). +Στην επόμενη γραμμή υπάρχει ένας σύνδεσμος πίσω στη λίστα των άρθρων του blog, ώστε ο χρήστης να μπορεί εύκολα να κινείται μεταξύ της λίστας των άρθρων και ενός συγκεκριμένου. Επειδή χρησιμοποιούμε την ιδιότητα `n:href`, το Nette φροντίζει αυτόματα για τη δημιουργία των συνδέσμων. Ο σύνδεσμος οδηγεί στην action `default` του presenter `Home` (μπορούμε να γράψουμε επίσης `n:href="Home:"`, επειδή η action με το όνομα `default` μπορεί να παραλειφθεί, συμπληρώνεται αυτόματα). -Η τρίτη γραμμή μορφοποιεί τη χρονοσφραγίδα δημοσίευσης με ένα φίλτρο, όπως ήδη γνωρίζουμε. +Η τρίτη γραμμή μορφοποιεί την εμφάνιση της ημερομηνίας χρησιμοποιώντας το φίλτρο που ήδη γνωρίζουμε. -Η τέταρτη γραμμή εμφανίζει τον *τίτλο* της δημοσίευσης στο ιστολόγιο ως `<h1>` επικεφαλίδα. Υπάρχει ένα μέρος που ίσως δεν γνωρίζετε, και αυτό είναι το `n:block="title"`. Μπορείτε να μαντέψετε τι κάνει; Αν έχετε διαβάσει προσεκτικά τα προηγούμενα μέρη, έχουμε αναφέρει το `n: attributes`. Αυτό είναι ένα άλλο παράδειγμα. Είναι ισοδύναμο με: +Η τέταρτη γραμμή εμφανίζει τον *τίτλο* του blog στο HTML tag `<h1>`. Αυτό το tag περιέχει μια ιδιότητα που ίσως δεν γνωρίζετε (`n:block="title"`). Μπορείτε να μαντέψετε τι κάνει; Αν διαβάσατε προσεκτικά το προηγούμενο μέρος, τότε ήδη ξέρετε ότι πρόκειται για μια `n:ιδιότητα`. Αυτό είναι ένα άλλο παράδειγμά τους, το οποίο είναι ισοδύναμο με: ```latte {block title}<h1>{$post->title}</h1>{/block} ``` -Με απλά λόγια, * επαναπροσδιορίζει* ένα μπλοκ που ονομάζεται `title`. Το μπλοκ ορίζεται στο *πρότυπο διάταξης* (`/app/Presenters/templates/@layout.latte:11`) και όπως συμβαίνει με την παράκαμψη OOP, παρακάμπτεται εδώ. Επομένως, το αρχείο της σελίδας `<title>` θα περιέχει τον τίτλο της εμφανιζόμενης ανάρτησης. Έχουμε παρακάμψει τον τίτλο της σελίδας και το μόνο που χρειαζόμασταν ήταν το `n:block="title"`. Υπέροχα, ε; +Με απλά λόγια, αυτό το μπλοκ επαναπροσδιορίζει το μπλοκ με το όνομα `title`. Αυτό το μπλοκ είναι ήδη ορισμένο στο κύριο *layout* πρότυπο (`/app/Presentation/@layout.latte:11`) και όπως και με την επικάλυψη μεθόδων στην OOP, ακριβώς το ίδιο αυτό το μπλοκ στο κύριο πρότυπο επικαλύπτεται. Έτσι, το `<title>` της σελίδας τώρα περιέχει τον τίτλο της εμφανιζόμενης ανάρτησης και μας αρκούσε γι' αυτό να χρησιμοποιήσουμε μόνο μια απλή ιδιότητα `n:block="title"`. Υπέροχο, έτσι δεν είναι; -Η πέμπτη και τελευταία γραμμή του προτύπου εμφανίζει το πλήρες περιεχόμενο της ανάρτησής σας. +Η πέμπτη και τελευταία γραμμή του προτύπου εμφανίζει ολόκληρο το περιεχόμενο μιας συγκεκριμένης ανάρτησης. -Έλεγχος του αναγνωριστικού της δημοσίευσης .[#toc-checking-post-id] -=================================================================== +Έλεγχος του ID της ανάρτησης +============================ -Τι συμβαίνει αν κάποιος αλλάξει το URL και εισάγει το `postId` που δεν υπάρχει? Θα πρέπει να παρέχουμε στο χρήστη ένα ωραίο σφάλμα "η σελίδα δεν βρέθηκε". Ας ενημερώσουμε τη μέθοδο render στο `PostPresenter`: +Τι θα συμβεί αν κάποιος αλλάξει το ID στη διεύθυνση URL και εισαγάγει κάποιο ανύπαρκτο `id`; Θα έπρεπε να προσφέρουμε στον χρήστη ένα ωραίο σφάλμα τύπου "η σελίδα δεν βρέθηκε". Θα τροποποιήσουμε λοιπόν λίγο τη μέθοδο render στον `PostPresenter`: -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { - $this->error('Post not found'); + $this->error('Η σελίδα δεν βρέθηκε'); } $this->template->post = $post; } ``` -Εάν η δημοσίευση δεν μπορεί να βρεθεί, η κλήση του `$this->error(...)` θα εμφανίζει μια σελίδα 404 με ένα ωραίο και κατανοητό μήνυμα. Σημειώστε, ότι στο περιβάλλον ανάπτυξης (στον φορητό σας υπολογιστή), δεν θα δείτε τη σελίδα σφάλματος. Αντ' αυτού, το Tracy θα εμφανίσει την εξαίρεση με πλήρεις λεπτομέρειες, κάτι που είναι αρκετά βολικό για την ανάπτυξη. Μπορείτε να ελέγξετε και τις δύο λειτουργίες, απλά αλλάξτε την τιμή που περνάτε στο `setDebugMode` στο `Bootstrap.php`. +Εάν η ανάρτηση δεν μπορεί να βρεθεί, καλώντας το `$this->error(...)` θα εμφανίσουμε μια σελίδα σφάλματος 404 με ένα κατανοητό μήνυμα. Προσοχή στο ότι σε κατάσταση ανάπτυξης (localhost) δεν θα δείτε αυτήν τη σελίδα σφάλματος. Αντ' αυτού, θα εμφανιστεί η Tracy με λεπτομέρειες για την εξαίρεση, κάτι που είναι αρκετά βολικό για την ανάπτυξη. Αν θέλουμε να εμφανίζονται και οι δύο καταστάσεις, αρκεί απλώς να αλλάξουμε το όρισμα της μεθόδου `setDebugMode` στο αρχείο `Bootstrap.php`. -Περίληψη .[#toc-summary] -======================== +Σύνοψη +====== -Έχουμε μια βάση δεδομένων με αναρτήσεις ιστολογίου και μια εφαρμογή ιστού με δύο προβολές - η πρώτη εμφανίζει τη σύνοψη όλων των πρόσφατων αναρτήσεων και η δεύτερη εμφανίζει μια συγκεκριμένη ανάρτηση. +Έχουμε μια βάση δεδομένων με αναρτήσεις και μια web εφαρμογή που έχει δύο views - η πρώτη εμφανίζει μια επισκόπηση όλων των αναρτήσεων και η δεύτερη εμφανίζει μια συγκεκριμένη ανάρτηση. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/en/@home.texy b/quickstart/en/@home.texy index 96c44b9173..8fd028c07f 100644 --- a/quickstart/en/@home.texy +++ b/quickstart/en/@home.texy @@ -4,24 +4,24 @@ Create Your First Application! .[perex] Get to know Nette Framework while creating a simple blog with comments. Let's begin! -After the first two chapters, you will have your own working blog and you'll be ready to publish your awesome posts, although the features will be pretty much limited after completing these two chapters. To make things nicer for your users, you should also read the following chapters and keep improving your application. +After the first two chapters, you will have your own working blog and you'll be ready to publish your awesome posts, although the features will be quite limited after completing just these two chapters. You should also read the following chapters, where we will implement adding comments, editing posts, and finally, securing the blog. .[tip] -This tutorial assumes that you completed the [Installation |nette:installation] document and have successfully set up your tooling. +This tutorial assumes that you completed the [Installation |nette:installation] document and have successfully set up your tooling. It also assumes that you understand [object-oriented programming in PHP |nette:introduction-to-object-oriented-programming]. -Please use PHP 8.0 or later. You can find the complete application [on GitHub |https://github.com/nette-examples/quickstart/tree/v4.0]. +Please use PHP 8.1 or later. You can find the complete application [on GitHub |https://github.com/nette-examples/quickstart/tree/v4.0]. The Welcome Page ================ -Let's start by creating a new project into the `nette-blog` directory: +Let's start by creating a new project in the `nette-blog` directory: ```shell composer create-project nette/web-project nette-blog ``` -At this moment, the welcome page of the Web Project should be running. Try it by opening your browser and going to the following URL: +At this moment, the welcome page of the Web Project should be running. Try it by opening your browser and navigating to the following URL: ``` http://localhost/nette-blog/www/ @@ -31,43 +31,45 @@ and you should see the Nette Framework welcome page: [* qs-welcome.webp .{url: http://localhost/nette-blog/www/} *] -The application works and you can now start making changes to it. +The application works, and you can now start making changes to it. .[note] -If you have a problem, [try these few tips |nette:troubleshooting#Nette Is Not Working, White Page Is Displayed]. +If you have a problem, [try these few tips |nette:troubleshooting#Nette Is Not Working White Page Is Displayed]. -Web Project’s Content -===================== +Web Project Content +=================== -Web Project has the following structure: +The Web Project has the following structure: /--pre <b>nette-blog/</b> ├── <b>app/</b> ← application directory -│ ├── <b>Presenters/</b> ← presenter classes -│ │ └── <b>templates/</b>← templates -│ ├── <b>Router/</b> ← configuration of URL addresses +│ ├── <b>Core/</b> ← basic necessary classes +│ ├── <b>Presentation/</b> ← presenters, templates & co. +│ │ └── <b>Home/</b> ← Home presenter directory │ └── <b>Bootstrap.php</b> ← booting class Bootstrap +├── <b>assets/</b> ← raw assets (SCSS, TypeScript, source images) ├── <b>bin/</b> ← scripts for the command line ├── <b>config/</b> ← configuration files ├── <b>log/</b> ← error logs ├── <b>temp/</b> ← temporary files, cache, … ├── <b>vendor/</b> ← libraries installed by Composer -│ └── <b>autoload.php</b> ← autoloading of libraries installed by Composer -└── <b>www/</b> ← public folder - the only place accessible from browser +│ └── <b>autoload.php</b> ← autoloading of all installed packages +└── <b>www/</b> ← public folder - the only place accessible from the browser + ├── <b>assets/</b> ← compiled static files (CSS, JS, images, …) └── <b>index.php</b> ← initial file that launches the application \-- -Directory `www` is supposed to store images, JavaScript, CSS, and other publicly available files. This is the only directory directly accessible from the browser, so you can point the root directory of your web server here (you can configure it in Apache, but let’s do it later as it’s not important right now). +The `www/` directory is intended for storing images, JavaScript files, CSS styles, and other publicly accessible files. This is the only directory directly accessible from the internet, so set the document root of your web server to point here (you can configure this in Apache or Nginx, but let's do it later, as it's not critical right now). -The most important directory for you is `app/`. You can find `Bootstrap.php` file there, inside which is a class that loads the framework and configures the application. It activates [autoloading |robot-loader:] and sets up the [debugger |tracy:] and [routes |application:routing]. +The most important directory for you is `app/`. Here you will find the `Bootstrap.php` file, which contains a class that loads the framework and configures the application. It activates [autoloading |robot-loader:], sets up the [debugger |tracy:], and configures [routes |application:routing]. Cleanup ======= -The Web Project contains a welcome page, which we can remove - feel free to delete the `app/Presenters/templates/Home/default.latte` file and replace it with the text "Hello world!". +The Web Project contains a welcome page, which we will remove before starting any programming. Feel free to replace the content of the `app/Presentation/Home/default.latte` file with "Hello world!". [* qs-hello.webp .{url:-} *] @@ -76,38 +78,37 @@ The Web Project contains a welcome page, which we can remove - feel free to dele Tracy (Debugger) ================ -An extremely important tool for development is [a debugger called Tracy |tracy:]. Try to make some errors in your `app/Presenters/HomePresenter.php` file (e.g. remove a curly bracket from the definition of class HomePresenter) and see what happens. A red-screen page will pop up with an understandable error description. +An extremely important tool for development is the [debugging tool Tracy |tracy:]. Try introducing an error in your `app/Presentation/Home/HomePresenter.php` file (e.g., remove a curly brace from the `HomePresenter` class definition) and see what happens. A red screen page will appear with an understandable error description. -[* qs-tracy.webp .{url:-}(debugger screen) *] +[* qs-tracy.avif .{url:-}(debugger screen) *] -Tracy will significantly help you while hunting down errors. Also note the floating Tracy Bar in the bottom right corner, which informs you about important runtime data. +Tracy will significantly help you when debugging errors. Also, notice the floating Tracy Bar in the bottom right corner of the screen, which displays important runtime information. [* qs-tracybar.webp .{url:-} *] -In the production mode, Tracy is, of course, disabled and does not reveal any sensitive information. All errors are saved into `log/` directory instead. Just try it out. In `app/Bootstrap.php`, find the following piece of code, uncomment the line and change the method call parameter to `false`, so it looks like this: +In production mode, Tracy is, of course, disabled and does not display any sensitive information. All errors are saved in the `log/` directory instead. Let's try it out. In the `app/Bootstrap.php` file, find the following piece of code, uncomment the line, and change the method call parameter to `false`, so it looks like this: ```php .{file:app/Bootstrap.php} ... -$configurator->setDebugMode(false); -$configurator->enableTracy(__DIR__ . '/../log'); +$this->configurator->setDebugMode(false); ... ``` -After refreshing the web page, the red-screen page will be replaced with the user-friendly message: +After refreshing the web page, you will no longer see the red screen. Instead, a user-friendly message will be displayed: [* qs-fatal.webp .{url:-}(error screen) *] -Now, look into the `log/` directory. You can find the error log there (in exception.log file) and also the page with the error message (saved in an HTML file with a name starting with `exception`). +Now, look into the `log/` directory. Here (in the `exception.log` file), you will find the logged error and also the familiar error message page (saved as an HTML file with a name starting with `exception-`). -Comment line `// $configurator->setDebugMode(false);` again. Tracy automatically enables development mode on `localhost` environment and disables it elsewhere. +Comment out the line `// $configurator->setDebugMode(false);` again. Tracy automatically enables development mode on a `localhost` environment and disables it elsewhere. -Now, we can fix the bug and continue designing our application. +Now, we can fix the error we introduced and continue building our application. -Send thanks +Send Thanks =========== -We will show you a trick that will make open source authors happy. You can easily give a star on GitHub to the libraries that your project uses. Just run: +We'll show you a trick that will delight open-source authors. You can easily give a star on GitHub to the libraries your project uses. Just run: ```shell composer thanks @@ -116,4 +117,3 @@ composer thanks Try it! {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/en/@meta.texy b/quickstart/en/@meta.texy new file mode 100644 index 0000000000..b2effeab60 --- /dev/null +++ b/quickstart/en/@meta.texy @@ -0,0 +1 @@ +{{sitename: Create Your First Application!}} diff --git a/quickstart/en/authentication.texy b/quickstart/en/authentication.texy index ff10a5fd46..127d56c2d7 100644 --- a/quickstart/en/authentication.texy +++ b/quickstart/en/authentication.texy @@ -1,11 +1,11 @@ Authentication ************** -Nette provides you with guidelines on how to program authentication on your page, but it doesn't force you to do it any particular way. The implementation is up to you. Nette has a `Nette\Security\Authenticator` interface which forces you to implement just a single method called `authenticate`, which finds the user anyhow you want. +Nette provides a way to implement authentication on our sites, but it doesn't force any specific approach. The implementation is entirely up to us. Nette includes the `Nette\Security\Authenticator` interface, which requires only one method, `authenticate`, to verify the user in any way we choose. -There are many ways how a user can authenticate himself. The most common way is *password-based authentication* (user provides his name or email and a password), but there are other means as well. You may be familiar with "Login with Facebook" buttons on many websites, or login via Google/Twitter/GitHub or any other site. With Nette, you can have any authentication method you want, or you can combine them. It's up to you. +There are many ways a user can be authenticated. The most common method is password-based authentication (where the user provides their name or email and a password), but other methods exist. You might be familiar with "Login with Facebook" buttons or logging in via Google/Twitter/GitHub on some sites. With Nette, we can have any login method, or even combine them. It's entirely our decision. -Normally you would write your own authenticator, but for this simple little blog we'll use the built-in authenticator, which authenticates based on a password and username stored in a configuration file. It's good for testing purposes. So we'll add the following *security* section to the `config/common.neon` configuration file: +Typically, we would write our own authenticator. However, for this simple blog, we'll use the built-in authenticator that logs in based on a username and password stored in the configuration file. This is suitable for testing purposes. Let's add the following `security` section to the configuration file `config/common.neon`: ```neon .{file:config/common.neon} @@ -14,23 +14,23 @@ security: admin: secret # user 'admin', password 'secret' ``` -Nette will automatically create a service in the DI container. +Nette automatically creates a service for this authenticator in the DI container. Sign-In Form ============ -We now have the backend part of authentication ready and we need to provide a user interface, through which the user would log in. Let's create a new presenter called *SignPresenter*, which will +Now that authentication is set up, we need to create a user interface for logging in. Let's create a new presenter named `SignPresenter` that will: -- display a login form (asking for username and password) +- display a login form (with username and password fields) - authenticate the user when the form is submitted -- provide log out action +- provide a sign-out option -Let's start with the login form. You already know how forms work in a presenter. Create the `SignPresenter` and method `createComponentSignInForm`. It should look like this: +We'll start with the login form. We already know how forms work in presenters. Let's create the `SignPresenter` and write the `createComponentSignInForm` method. It should look something like this: -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Sign; use Nette; use Nette\Application\UI\Form; @@ -48,21 +48,21 @@ final class SignPresenter extends Nette\Application\UI\Presenter $form->addSubmit('send', 'Sign in'); - $form->onSuccess[] = [$this, 'signInFormSucceeded']; + $form->onSuccess[] = $this->signInFormSucceeded(...); return $form; } } ``` -There is an input for username and password. +There are fields for the username and password. Template -------- -The form will be rendered in the template `in.latte` +The form will be rendered in the `in.latte` template: -```latte .{file:app/Presenters/templates/Sign/in.latte} +```latte .{file:app/Presentation/Sign/in.latte} {block content} <h1 n:block=title>Sign in</h1> @@ -70,15 +70,15 @@ The form will be rendered in the template `in.latte` ``` -Login Handler -------------- +Login Callback +-------------- -We add also a *form handler* for signing in the user, that gets invoked right after the form is submitted. +Next, we'll add the callback method for logging in the user, which will be called immediately after the form is successfully submitted. -The handler will just take the username and password the user entered and will pass it to the authenticator defined earlier. After the user has logged in, we will redirect him to the homepage. +The callback simply takes the username and password entered by the user and passes them to the authenticator. After successful login, we redirect to the homepage. -```php .{file:app/Presenters/SignPresenter.php} -public function signInFormSucceeded(Form $form, \stdClass $data): void +```php .{file:app/Presentation/Sign/SignPresenter.php} +private function signInFormSucceeded(Form $form, \stdClass $data): void { try { $this->getUser()->login($data->username, $data->password); @@ -90,19 +90,19 @@ public function signInFormSucceeded(Form $form, \stdClass $data): void } ``` -The method [User::login() |api:Nette\Security\User::login()] should throw an exception when the username or password doesn't match those we've defined earlier. As we already know, that would result in a [Tracy|tracy:] red-screen, or, in production mode, a message informing about an internal server error. We wouldn't like that. That's why we catch the exception and add a nice and friendly error message to the form. +The [User::login() |api:Nette\Security\User::login()] method throws an exception if the username and password do not match the credentials in the configuration file. As we know, this could result in a Tracy red screen or, in production mode, a server error message. We don't want that. Therefore, we catch the exception and add a nice, user-friendly error message to the form. -When the error occurs in the form, the page with the form will be rendered again, and above the form, there will be a nice message, informing the user that they have entered a wrong username or password. +When a form error occurs, the page with the form is re-rendered, and a message appears above the form informing the user that they entered the wrong username or password. -Security of Presenters -====================== +Securing Presenters +=================== -We will secure a form for adding and editing posts. It is defined in the presenter `EditPresenter`. The goal is to prevent users who are not logged in from accessing the page. +We will secure the form for adding and editing posts, which is defined in the `EditPresenter`. The goal is to prevent users who are not logged in from accessing the page. -We create a method `startup()` that is started immediately at the beginning of the [presenter life cycle|application:presenters#life-cycle-of-presenter]. This redirects non-logged-in users to the login form. +We'll create a `startup()` method, which runs immediately at the beginning of the [presenter life cycle |application:presenters#Presenter Life Cycle]. This method will redirect non-logged-in users to the login form. -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} public function startup(): void { parent::startup(); @@ -114,32 +114,32 @@ public function startup(): void ``` -Hide Links ----------- +Hiding Links +------------ -An unauthenticated user can no longer see the *create* nor *edit page*, but he can still see the links pointing to them. Let's hide those as well. One such link is in `app/Presenters/templates/Home/default.latte`, and it should be visible only if the user is logged in. +An unauthorized user can no longer see the *create* or *edit* pages, but they can still see the links pointing to them. We should hide these as well. One such link is in the `app/Presentation/Home/default.latte` template and should only be visible to logged-in users. -We can hide it using *n:attribute* called `n:if`. If the statement inside it is `false`, the whole `<a>` tag and it's contents will be not displayed: +We can hide it using an *n:attribute* called `n:if`. If the condition is `false`, the entire `<a>` tag, including its content, will not be displayed. ```latte <a n:href="Edit:create" n:if="$user->isLoggedIn()">Create post</a> ``` -this is a shortcut for (do not confuse it with `tag-if`): +which is a shortcut for the following notation (do not confuse it with `tag-if`): ```latte {if $user->isLoggedIn()}<a n:href="Edit:create">Create post</a>{/if} ``` -You should hide the edit link located in `app/Presenters/templates/Post/show.latte` in a similar fashion. +Hide the edit link in the `app/Presentation/Post/show.latte` template in the same way. -Login Form Link -=============== +Sign-in Link +============ -Hey, but how do we get to the login page? There is no link pointing to it. Let's add one in the `@layout.latte` template file. Try finding a nice place, it can be anywhere you like it the most. +How do we actually get to the sign-in page? There's no link leading to it. Let's add one to the `@layout.latte` template. Try to find a suitable place - it can be almost anywhere. -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <ul class="navig"> <li><a n:href="Home:">Home</a></li> @@ -152,11 +152,11 @@ Hey, but how do we get to the login page? There is no link pointing to it. Let's ... ``` -If the user is not yet logged in, we will show the "Sign in" link. Otherwise, we will show the "Sign out" link. We add that action in SignPresenter. +If the user is not logged in, the "Sign in" link is displayed. Otherwise, the "Sign out" link is shown. We'll also add this action to `SignPresenter`. -The logout action looks like this, and because we redirect the user immediately, there is no need for a view template. +Since we redirect the user immediately after signing out, no template is needed. The sign-out action looks like this: -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} public function actionOut(): void { $this->getUser()->logout(); @@ -165,16 +165,15 @@ public function actionOut(): void } ``` -It just calls the `logout()` method and then shows a nice message to the user. +It simply calls the `logout()` method and then displays a confirmation message to the user. Summary ======= -We have a link to log in and also to log out the user. We have used the built-in authenticator for authentication and the login details are in the configuration file as this is a simple test application. We have also secured the edit forms so that only logged in users can add and edit posts. +We have links for signing in and signing out the user. We used the built-in authenticator for authentication, and the login credentials are in the configuration file, as this is a simple test application. We also secured the editing forms so that only logged-in users can add and edit posts. .[note] -Here you can read more about [user login |security:authentication] and [authorization |security:authorization]. +You can read more about [user login |security:authentication] and [authorization |security:authorization] here. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/en/comments.texy b/quickstart/en/comments.texy index 97c98c349a..9c30a5aacb 100644 --- a/quickstart/en/comments.texy +++ b/quickstart/en/comments.texy @@ -1,33 +1,33 @@ Comments ******** -The blog has been deployed, we’ve written some very good blog posts and published them via Adminer. People are reading the blog and they’re very passionate about our ideas. We’re receiving many emails with praise each day. But what is all the praise for when we’ve got it only in the email, so no one else can read it? Wouldn’t it be better if people could comment directly on the blog so that everyone else could read how awesome we are? +The blog has been deployed, we’ve written some very interesting blog posts and published them using Adminer. People are reading our blog and are very enthusiastic about it. We receive many emails with praise every day. But what good is all this praise if it's only in our email and no one else can read it? Wouldn't it be better if readers could comment directly on the articles, so everyone could see how amazing we are? -Let’s make all the articles commentable. +Let's implement comments. Creating a New Table ==================== -Fire up Adminer again and create a new table named `comments` with these columns: +Let's fire up Adminer again and create a `comments` table with these columns: - `id` int, check autoincrement (AI) -- `post_id`, a foreign key that references the `posts` table +- `post_id`, foreign key referencing the `posts` table - `name` varchar, length 255 - `email` varchar, length 255 - `content` text - `created_at` timestamp -It should look like this: +The table should look something like this: [* adminer-comments.webp *] -Don’t forget to use InnoDB table storage and hit Save. +Remember to use the InnoDB storage engine again. ```sql CREATE TABLE `comments` ( `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY, - `post_id` int(11) NOT NULL, + `post_id` int NOT NULL, `name` varchar(250) NOT NULL, `email` varchar(250) NOT NULL, `content` text NOT NULL, @@ -40,11 +40,11 @@ CREATE TABLE `comments` ( Form for Commenting =================== -First, we need to create a form, that will allow the users to comment on our page. Nette Framework has awesome support for forms. They can be configured in a presenter and rendered in a template. +First, we need to create a form that allows users to comment on posts. Nette Framework has excellent support for forms. We can configure them in the presenter and render them in the template. -Nette Framework has a concept of *components*. A **component** is a reusable class or piece of code, that can be attached to another component. Even a presenter is a component. Each component is created using the component factory. So let’s define the comments form factory in `PostPresenter`. +Nette Framework uses the concept of *components*. A **component** is a reusable class or piece of code that can be attached to another component. Even a presenter is a component. Each component is created using a factory method. Let's create a factory for the comment form in the `PostPresenter`. -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} protected function createComponentCommentForm(): Form { $form = new Form; // means Nette\Application\UI\Form @@ -63,40 +63,40 @@ protected function createComponentCommentForm(): Form } ``` -Let’s explain it a little bit. The first line creates a new instance of `Form` component. The following methods are attaching HTML inputs into the form definition. `->addText` will render as `<input type=text name=name>`, with `<label>Your name:</label>`. As you might have already guessed right now, the `->addTextArea` attaches a `<textarea>` and `->addSubmit` adds an `<input type=submit>`. There are more methods like that, but this is all you have to know right now. You can [learn more in the documentation|forms:]. +Let's explain this briefly. The first line creates a new instance of the `Form` component. The subsequent methods attach HTML inputs to the form definition. `->addText` renders as `<input type="text" name="name">` with a `<label>Your name:</label>`. As you might guess, `->addTextArea` attaches a `<textarea>`, and `->addSubmit` adds an `<input type="submit">`. There are many similar methods, but these are sufficient for now. You can [learn more in the documentation|forms:]. -Once the form component is defined in a presenter, we can render (display) it in a template. To do so, place the tag `{control}` at the end of the post detail template, in `Post/show.latte`. Because the component's name is `commentForm` (it's derived from the name of the method `createComponentCommentForm`), the tag will look like this +Once the form component is defined in the presenter, we can render (display) it in the template. To do this, place the `{control}` tag at the end of the template that displays a single post, in `Post/show.latte`. Since the component is named `commentForm` (derived from the method name `createComponentCommentForm`), the tag will look like this: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... <h2>Post new comment</h2> {control commentForm} ``` -Now if you check out the detail of some post, there will be a new form for posting comments. +Now, if you view the detail page of a post, you will see the new comment form at the end. Saving to Database ================== -Have you tried to submit some data? You might have noticed, that the form is not performing any action. It’s just there, looking cool and doing nothing. We have to attach a callback method to it, that will save the submitted data. +Have you tried filling out and submitting the form? You might have noticed that the form doesn't actually do anything yet. We need to attach a callback method that will save the submitted data. -Place the following line before the `return` line in the component factory for the `commentForm`: +Add the following line before the `return` statement in the factory method for the `commentForm` component: ```php -$form->onSuccess[] = [$this, 'commentFormSucceeded']; +$form->onSuccess[] = $this->commentFormSucceeded(...); ``` -It means "after the form is successfully submitted, call the method `commentFormSucceeded` of the current presenter". This method does not exist yet, so let’s create it. +This line means "after the form is successfully submitted, call the `commentFormSucceeded` method of the current presenter". This method doesn't exist yet, so let's create it: -```php .{file:app/Presenters/PostPresenter.php} -public function commentFormSucceeded(\stdClass $data): void +```php .{file:app/Presentation/Post/PostPresenter.php} +private function commentFormSucceeded(\stdClass $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); $this->database->table('comments')->insert([ - 'post_id' => $postId, + 'post_id' => $id, 'name' => $data->name, 'email' => $data->email, 'content' => $data->content, @@ -111,9 +111,9 @@ You should place it right after the `commentForm` component factory. The new method has one argument which is the instance of the form being submitted, created by the component factory. We receive submitted values in `$data`. And then we insert the data into the database table `comments`. -There are two more method calls to explain. The redirect literally redirects to the current page. You should do that every time when the form is submitted, valid, and the callback operation did what it should have done. Also, when you redirect the page after submitting the form, you won’t see the well known `Would you like to submit the post data again?` message that you can sometimes see in the browser. (In general, after submitting a form by `POST` method, you should always redirect the user to a `GET` action.) +There are two more method calls to explain. The `redirect('this')` method literally redirects back to the current page. You should do this every time a form is submitted successfully and the callback operation has completed. Redirecting after form submission also prevents the well-known "Would you like to submit the post data again?" message in the browser. (Generally, after submitting a form using the `POST` method, you should always redirect to a `GET` action.) -The `flashMessage` is for informing the user about the result of some operation. Because we’re redirecting, the message cannot be directly passed to the template and rendered. So there is this method, that will store it and make it available on the next page load. The flash messages are rendered in the default `app/Presenters/templates/@layout.latte` file, and it looks like this: +The `flashMessage` method is used to inform the user about the result of an operation. Since we are redirecting, the message cannot be simply passed to the template and rendered directly. This method stores the message and makes it available on the next page load. Flash messages are rendered in the main layout template `app/Presentation/@layout.latte`, looking like this: ```latte <div n:foreach="$flashes as $flash" n:class="flash, $flash->type"> @@ -121,20 +121,20 @@ The `flashMessage` is for informing the user about the result of some operation. </div> ``` -As we already know, they are passed automatically to the template, so you don’t have to think about it too much, it just works. For more details [check the documentation |application:presenters#flash-messages]. +As we already know, flash messages are automatically passed to the template, so we don't need to think much about it; it just works. For more details, [visit the documentation |application:presenters#Flash Messages]. Rendering the Comments ====================== -This is one of the things you will just love. Nette Database has this cool feature named [Explorer |database:explorer]. Do you remember that we’ve created the tables as InnoDB? Adminer has created the so-called [foreign keys |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html] that will save us a ton of work. +This is one of those features you'll simply love. Nette Database has a great feature called [Explorer |database:explorer]. Remember how we intentionally created the database tables using the InnoDB engine? Adminer created what are called [foreign keys |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html], which save us a lot of work. -Nette Database Explorer uses the foreign keys to resolve relations between tables, and knowing the relations, it can automatically create queries for you. +Nette Database Explorer uses these foreign keys to resolve relationships between tables. Knowing these relationships, it can automatically create database queries for you. -As you may remember, we’ve passed the `$post` variable to the template in `PostPresenter::renderShow()` and now we want to iterate through all the comments that have the column `post_id` equal to our `$post->id`. You can do it by calling `$post->related('comments')`. It’s that simple. Look at the resulting code. +As you might recall, we passed the `$post` variable to the template in `PostPresenter::renderShow()`. Now, we want to iterate over all comments where the `post_id` column matches our `$post->id`. We can achieve this by calling `$post->related('comments')`. Yes, it's that simple. Let's look at the final code: -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { ... $this->template->post = $post; @@ -144,7 +144,7 @@ public function renderShow(int $postId): void And the template: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... <h2>Comments</h2> @@ -160,7 +160,7 @@ And the template: ... ``` -Notice the special `n:tag-if` attribute. You already know how `n: attributes` work. Well, If you prepend the attribute with `tag-`, it will only wrap around the tags, not their content. This allows you to make the commenter's name into a link if they provided their email. These two lines are identical in results: +Notice the special `n:tag-if` attribute. You already know how `n:attributes` work. If you prefix the attribute with `tag-`, the functionality applies only to the HTML tag itself, not its content. This allows us to make the commenter's name a link only if they provided their email. These two lines produce identical results: ```latte <strong n:tag-if="$important"> Hello there! </strong> @@ -169,4 +169,3 @@ Notice the special `n:tag-if` attribute. You already know how `n: attributes` wo ``` {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/en/creating-posts.texy b/quickstart/en/creating-posts.texy index 77f9e93059..bc96dd4842 100644 --- a/quickstart/en/creating-posts.texy +++ b/quickstart/en/creating-posts.texy @@ -1,30 +1,30 @@ Creating and Editing Posts ************************** -What a great time. We have a super cool new blog, people are arguing in comments and we have finally some time for more programming. Though we like Adminer, it is not that comfortable to write blog posts in it. Perhaps it's the right time to add a simple form for adding new posts directly from our app. Let’s do it. +That's great! We have a super cool new blog, people are actively discussing in the comments, and we finally have some time for more programming. Although Adminer is a great tool, it's not very comfortable for writing new blog posts. Perhaps it's the right time to create a simple form for adding new posts directly from the application. Let's do it. -Let’s start by designing the UI: +Let's start by designing the user interface: -1. On the homepage, let’s add a "Write new post" link. -2. It will show a form with title and textarea for content. -3. When you click a Save button, it’ll save the blog post. +1. On the homepage, add a "Write new post" link. +2. This link will display a form with a title field and a textarea for the post content. +3. When we click the Save button, the post will be saved to the database. -Later we’ll also add authentication and allow only logged-in users to add new posts. But let’s do that later. What code will we need to write to make it work? +Later, we will also add authentication and allow only logged-in users to add new posts. But let's do that later. What code do we need to write now to make everything work? 1. Create a new presenter with a form for adding posts. -2. Define a callback that will be triggered after successful submission of the form and that will save the new post to the database. +2. Define a callback that will be triggered after the successful submission of the form and will save the new post to the database. 3. Create a new template for the form. -4. Add a link to the form to the main page template. +4. Add a link to the form on the main page template. New Presenter ============= -Name the new presenter `EditPresenter` and save it in `app/Presenters/EditPresenter.php`. It also needs to connect to the database, so here again we write a constructor that will require a database connection: +Let's name the new presenter `EditPresenter` and save it in `app/Presentation/Edit/EditPresenter.php`. It also needs to connect to the database, so here again, we write a constructor that requires a database connection: -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Edit; use Nette; use Nette\Application\UI\Form; @@ -42,11 +42,11 @@ final class EditPresenter extends Nette\Application\UI\Presenter Form for Saving Posts ===================== -Forms and components have been already covered when we were adding support for comments. If you're confused about the topic, go checkout [how forms and components work |comments#form-for-commenting] again, we'll wait here ;) +Forms and components were already covered when we added support for comments. If it's still unclear, go review [how forms and components work |comments#Form for Commenting], we'll wait here ;) Now add this method to the `EditPresenter`: -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} protected function createComponentPostForm(): Form { $form = new Form; @@ -56,7 +56,7 @@ protected function createComponentPostForm(): Form ->setRequired(); $form->addSubmit('send', 'Save and publish'); - $form->onSuccess[] = [$this, 'postFormSucceeded']; + $form->onSuccess[] = $this->postFormSucceeded(...); return $form; } @@ -66,16 +66,16 @@ protected function createComponentPostForm(): Form Saving New Post from Form ========================= -Continue by adding a handler method. +Continue by adding the handler method: -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { $post = $this->database ->table('posts') ->insert($data); - $this->flashMessage('Post was published', 'success'); + $this->flashMessage('Post was published successfully.', 'success'); $this->redirect('Post:show', $post->id); } ``` @@ -86,18 +86,18 @@ Just a quick explanation: it fetches the values from the form, inserts them into Page for Creating a New Post ============================ -Let’s just create the template `Edit/create.latte`: +Now let's create the template `Edit/create.latte`: -```latte .{file:app/Presenters/templates/Edit/create.latte} +```latte .{file:app/Presentation/Edit/create.latte} {block content} <h1>New post</h1> {control postForm} ``` -Everything should be clear by now. The last line shows the form we are about to create. +Everything should be clear by now. The last line renders the form we just created. -We could also create a corresponding `renderCreate` method, but it is not necessary. We don't need to get any data from the database and pass it to the template, so that method would be empty. In such cases, the method may not exist at all. +We could also create a corresponding `renderCreate()` method, but it's not necessary. We don't need to retrieve any data from the database or pass it to the template, so the method would be empty. In such cases, the method doesn't need to exist at all. Link for Creating Posts @@ -105,7 +105,7 @@ Link for Creating Posts You probably already know how to add a link to `EditPresenter` and its `create` action. Try it out. -Just add to the `app/Presenters/templates/Home/default.latte` file: +Just add the following to the `app/Presentation/Home/default.latte` file: ```latte <a n:href="Edit:create">Write new post</a> @@ -115,16 +115,16 @@ Just add to the `app/Presenters/templates/Home/default.latte` file: Editing Posts ============= -Let’s also add the capability to edit existing posts. It shall be pretty simple - we already have `postForm` and we can use it for editing as well. +Now let's add the capability to edit existing posts. It will be very simple. We already have the `postForm` form, and we can use it for editing as well. -We’ll add a new `edit` page to the `EditPresenter`: +We'll add a new `edit` page to the `EditPresenter`: -```php .{file:app/Presenters/EditPresenter.php} -public function renderEdit(int $postId): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +public function renderEdit(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { $this->error('Post not found'); @@ -135,9 +135,9 @@ public function renderEdit(int $postId): void } ``` -And create the template `Edit/edit.latte`: +And create the corresponding template `Edit/edit.latte`: -```latte .{file:app/Presenters/templates/Edit/edit.latte} +```latte .{file:app/Presentation/Edit/edit.latte} {block content} <h1>Edit post</h1> @@ -146,15 +146,15 @@ And create the template `Edit/edit.latte`: And update the method `postFormSucceeded`, which will be able either to add a new post (as it does now), or to edit existing ones: -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); - if ($postId) { + if ($id) { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); $post->update($data); } else { @@ -163,16 +163,16 @@ public function postFormSucceeded(array $data): void ->insert($data); } - $this->flashMessage('Post was published', 'success'); + $this->flashMessage('Post was published successfully.', 'success'); $this->redirect('Post:show', $post->id); } ``` -When `postId` parameter is provided, it means that a post is being edited. In such case, we’ll check that the post really exists and if so, we’ll update it in the database. If the `postId` is not provided, it means that a new post shall be added. +When `id` parameter is provided, it means that a post is being edited. In such case, we’ll check that the post really exists and if so, we’ll update it in the database. If the `id` is not provided, it means that a new post shall be added. -But where does the `postId` come from? It is the parameter passed to `renderEdit` method. +But where does the `id` come from? It is the parameter passed to `renderEdit` method. -You can now add a link to the `app/Presenters/templates/Post/show.latte` template: +You can now add a link to the `app/Presentation/Post/show.latte` template: ```latte <a n:href="Edit:edit $post->id">Edit this post</a> @@ -185,4 +185,3 @@ Summary The blog is working, people are commenting rapidly and we no longer rely on Adminer for adding new posts. It is fully independent and even normal people can post there. But wait, that’s probably not ok, that anyone, I mean really anyone on the Internet, can post on our blog. Some form of authentication is required so that only logged-in users would be able to post. We'll add that in the next chapter. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/en/home-page.texy b/quickstart/en/home-page.texy index 411f3dd8a3..251c629d50 100644 --- a/quickstart/en/home-page.texy +++ b/quickstart/en/home-page.texy @@ -5,32 +5,32 @@ Blog Home Page Let’s create the home page displaying your recent posts. -Before we start, you should know at least some basics about Model-View-Presenter design pattern (similar to MVC((Model-View-Controller))): +Before we start, you should know at least some basics about the Model-View-Presenter design pattern (similar to MVC((Model-View-Controller))): -- **Model** - data manipulation layer. It is completely separated from the rest of the application. It only communicates with presenters. +- **Model** - the layer working with data. It is completely separated from the rest of the application. It only communicates with the presenter. -- **View** - a front-end definition layer. It renders requested data to the user using templates. +- **View** - the front-end layer. It renders requested data using templates and displays it to the user. -- **Presenter** (or Controller) - a connection layer. Presenter connects Model and View. Handles requests, asks Model for data and then passes them to the current View. +- **Presenter** (or Controller) - the connection layer. The presenter connects the Model and the View. It handles requests, asks the Model for data, and then passes them to the View. -In case of a very simple application like our blog, the Model layer will actually consist only of queries to the database itself - we don't need any extra PHP code for it. We only need to create Presenter and View layers. In Nette, each Presenter has its own Views, so we will continue with both simultaneously. +In the case of a simple application like our blog, the entire model layer will consist only of database queries - we don't need any extra PHP code for that yet. So, for starters, we will only create presenters and templates. In Nette, each presenter has its own templates, so we will create them simultaneously. Creating the Database with Adminer ================================== -To store the data, we will use the MySQL database because it is the most common choice among web developers. But if you don’t like it, feel free to use a database of your choice. +To store the data, we will use a MySQL database because it is the most common choice among web developers. However, if you don't want to use it, feel free to choose a database of your choice. -Let’s prepare the database which will store our blog posts. We can start very simply - just with a single table for posts. +Let’s prepare the database structure which will store our blog posts. We can start very simply - just with a single table for posts. -To create the database we can download [Adminer |https://www.adminer.org], or you can use another tool for database management. +To create the database, we can download [Adminer |https://www.adminer.org], or use another database management tool you prefer. Let’s open Adminer and create a new database called `quickstart`. Create a new table named `posts` and add these columns: -- `id` int, click on autoincrement (AI) +- `id` int, check autoincrement (AI) - `title` varchar, length 255 - `content` text - `created_at` timestamp @@ -49,9 +49,9 @@ CREATE TABLE `posts` ( ``` .[caution] -It’s very important to use the **InnoDB** table storage. You will see the reason later. For now, just choose that and submit. You can hit Save now. +It’s very important to use the **InnoDB** storage engine. You will see the reason later. For now, just choose it and click Save. -Try adding some sample blog posts before we implement the capability of adding new posts directly from our application. +Try adding some sample blog posts manually before we implement adding new posts directly from our application. ```sql INSERT INTO `posts` (`id`, `title`, `content`, `created_at`) VALUES @@ -64,34 +64,31 @@ INSERT INTO `posts` (`id`, `title`, `content`, `created_at`) VALUES Connecting to the Database ========================== -Now, when the database is created and we have some posts in it, it’s the right time to display them on our new shiny page. +Now that the database is created and we have some posts in it, it’s the right time to display them on our brand new page. -First, we need to tell our application about which database to use. The database connection configuration is stored in `config/local.neon`. Set the connection DSN((Data Source Name)) and your credentials. It should look like this: +First, we need to tell our application which database to use. The database connection configuration is set in the `config/common.neon` file using DSN((Data Source Name)) and credentials. It should look something like this: -```neon .{file:config/local.neon} +```neon .{file:config/common.neon} database: dsn: 'mysql:host=127.0.0.1;dbname=quickstart' - user: *enter user name* - password: *enter password here* + user: *enter your username here* + password: *enter your database password here* ``` .[note] -Be aware of indenting while editing this file. [NEON format|neon:format] accepts both spaces and tabs but not both together! The configuration file in the Web Project uses tabs as default. - -The whole configuration including is stored in `config/` in files `common.neon` and `local.neon`. File `common.neon` contains the global configuration of the application and `local.neon` contains only the parameters specific to the environment (e.g. the difference between development and production server). +Be careful with indentation while editing this file. The [NEON format |neon:format] accepts both spaces and tabs, but not both together. The default configuration file in the Web Project uses tabs. Injecting the Database Connection ================================= -The presenter `HomePresenter`, which will list the articles, needs a database connection. To receive it, write a constructor like this: +The presenter `HomePresenter`, which will list the posts, needs a database connection. To receive it, we'll use the constructor like this: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; -use Nette\Application\UI\Form; final class HomePresenter extends Nette\Application\UI\Presenter { @@ -110,7 +107,7 @@ Loading Posts from the Database Now let’s fetch the posts from the database and pass them to the template, which will then render the HTML code. This is what the so-called *render* method is for: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} public function renderDefault(): void { $this->template->posts = $this->database @@ -120,41 +117,41 @@ public function renderDefault(): void } ``` -The presenter now has one render method `renderDefault()` that passes data to a view called `default`. Presenter templates can be found in `app/Presenters/templates/{PresenterName}/{viewName}.latte`, so in this case the template will be located in `app/Presenters/templates/Home/default.latte`. In the template, a variable named `$posts` is now available, which contains the posts from database. +The presenter now has one render method `renderDefault()`, which passes data from the database to the template (View). Templates are located in `app/Presentation/{PresenterName}/{viewName}.latte`, so in this case, the template is located in `app/Presentation/Home/default.latte`. In the template, a variable named `$posts` is now available, containing the posts fetched from the database. Template ======== -There is a generic template for the whole page (called *layout*, with header, stylesheets, footer, ...) and then specific templates for each view (e.g. for displaying the list of blog posts), which can override some of layout template parts. +For the entire website, we have a main template (called *layout*, containing the header, styles, footer, ...) and specific templates for each view (e.g., for displaying blog posts), which can override parts of the main template. -By default, the layout template is located in `templates/@layout.latte`, which contains: +By default, the layout template is located in `app/Presentation/@layout.latte` and contains: -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... {include content} ... ``` -`{include content}` inserts a block named `content` into the main template. You can define it in the templates of each view. In this case, we will edit the file `Home/default.latte` like this: +The `{include content}` tag inserts a block named `content` into the main template. We will define this block in the templates for individual views. In our case, we will modify the `Home/default.latte` file as follows: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} Hello World {/block} ``` -It defines the *content* [block |latte:tags#block], which will be inserted into the layout. If you refresh the browser, you’ll see a page with text "Hello world" (in source code also with HTML header and footer defined in `@layout.latte`). +This defines the *content* [block |latte:tags#block], which will be inserted into the main layout. If you refresh the browser again, you will see a page with the text "Hello World" (in the source code, also with the HTML header and footer defined in `@layout.latte`). -Let’s display the blog posts - we will edit the template like this: +Let’s display the blog posts - we will modify the template as follows: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - <h1>My blog</h1> + <h1>My Blog</h1> {foreach $posts as $post} <div class="post"> - <div class="date">{$post->created_at|date:'j. n. Y'}</div> + <div class="date">{$post->created_at|date:'F j, Y'}</div> <h2>{$post->title}</h2> @@ -164,42 +161,41 @@ Let’s display the blog posts - we will edit the template like this: {/block} ``` -If you refresh your browser, you’ll see the list of your blog posts. The list isn't very fancy or colorful, so feel free to add some [shiny CSS |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] to `www/css/style.css` and link it in a layout: +If you refresh your browser, you’ll see the list of your blog posts. The list isn't very fancy or colorful yet, so feel free to add some [nice CSS styles |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] to the `www/css/style.css` file and link it in the layout: -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <link rel="stylesheet" href="{$basePath}/css/style.css"> </head> ... ``` -The `{foreach}` tag iterates over all posts passed to the template in `$posts` variable and displays a piece of HTML code for each post. Just like a PHP code would. +The `{foreach}` tag iterates over all posts passed to the template in the `$posts` variable and displays a piece of HTML code for each post. It behaves just like PHP code. -The `|date` thing is called a filter. Filters are used to format the output. This particular filter converts a date (e.g. `2013-04-12`) to its more readable form (`12. 4. 2013`). The `|truncate` filter truncates the string to the specified maximum length, and adds a ellipsis to the end if the string is truncated. Since this is a preview, there is no point in displaying the full content of the article. Other default filters [can be found in the documentation |latte:filters] or you can create your own if needed. +The `|date:` notation is called a filter. Filters are used to format the output. This particular filter converts a date (e.g., `2013-04-12`) to its more readable form (`April 12, 2013`). The `|truncate` filter truncates the string to the specified maximum length and adds an ellipsis (...) to the end if the string is truncated. Since this is a preview, there is no point in displaying the full content of the post. Other default filters [can be found in the documentation |latte:filters], or you can create your own if needed. -One more thing. We can make the code a little bit shorter and thus simpler. We can replace *Latte tags* with *n:attributes* like this: +One more thing. We can make the previous code shorter and simpler. We achieve this by replacing *Latte tags* with *n:attributes*: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - <h1>My blog</h1> + <h1>My Blog</h1> <div n:foreach="$posts as $post" class="post"> <div class="date">{$post->created_at|date:'F j, Y'}</div> <h2>{$post->title}</h2> - <div>{$post->content}</div> + <div>{$post->content|truncate:256}</div> </div> {/block} ``` -The `n:foreach`, simply wraps the *div* with a *foreach* block (it does exactly the same thing as the previous block of code). +The `n:foreach` attribute wraps the *div* block with a *foreach* loop (it works exactly the same as the previous code). Summary ======= -We have a very simple MySQL database with some blog posts in it. The application connects to the database and displays a simple list of the posts. +We now have a very simple MySQL database with a few posts. The application connects to this database and displays a simple list of these posts in a template. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/en/model.texy b/quickstart/en/model.texy index 08765fa6b8..042c1c9518 100644 --- a/quickstart/en/model.texy +++ b/quickstart/en/model.texy @@ -1,13 +1,13 @@ Model ***** -As our application grows, we soon find out that we need to perform similar database operations in various locations and in various presenters, for example acquiring the newest published articles. If we improve our application by adding a flag to articles to indicate a work-in-progress state, we must also go through all locations in our application and add a where clause to make sure only finished articles are selected. +As our application grows, we soon find out that we need to perform similar database operations in various places and in various presenters, for example, retrieving the latest published posts. If we improve our application, for instance, by adding a flag to posts indicating whether they are drafts, we must also review all places in our application where posts are retrieved from the database and add a `where` condition to ensure only non-draft posts are selected. -At this point, direct work with the database becomes insufficient and it will be smarter to help ourselves with a new function that returns published articles. And when we add another clause later (for example not to display articles with a future date), we only edit our code in one place. +At this point, working directly with the database becomes insufficient, and it will be smarter to use a new method that returns the published posts. And when we later add another condition (for example, not to display posts with a future date), we only edit our code in one place. -We'll place the function into the `PostFacade` class and call it `getPublicArticles()`. +We'll place the method into the `PostFacade` class and call it `getPublicArticles()`. -We'll create our model class `PostFacade` in the directory `app/Model/` to take care of our articles: +We'll create our model class `PostFacade` in the `app/Model/` directory to take care of our posts: ```php .{file:app/Model/PostFacade.php} <?php @@ -32,13 +32,13 @@ final class PostFacade } ``` -In the class we pass the database Explorer:[api:Nette\Database\Explorer]. This will take advantage of the power of [DI container|dependency-injection:passing-dependencies]. +In the class, we request the database Explorer:[api:Nette\Database\Explorer] via the constructor. This utilizes the power of the [DI container |dependency-injection:passing-dependencies]. -We will switch to `HomePresenter` which we will edit so that we get rid of the dependency on `Nette\Database\Explorer` replacing that with a new dependency on our new class. +We will switch to `HomePresenter`, which we will modify by getting rid of the dependency on `Nette\Database\Explorer` and replacing it with a new dependency on our new class. -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use App\Model\PostFacade; use Nette; @@ -59,10 +59,9 @@ final class HomePresenter extends Nette\Application\UI\Presenter } ``` -In the use section, we are using `App\Model\PostFacade`, so we can shorten the PHP code to `PostFacade`. We request this object in the constructor, write it to the `$facade` property and use it in the renderDefault method. +In the `use` section, we have `App\Model\PostFacade`, so we can shorten the notation in the PHP code to `PostFacade`. We request this object in the constructor, write it to the `$facade` property, and use it in the `renderDefault` method. -The last step left is to teach the DI container to produce this object. This is usually done by adding a bullet point to the `config/services.neon` file in the `services` section, giving the full class name and constructor parameters. -This registers it, so to speak, and the object is then called **service**. Thanks to some magic called [autowiring |dependency-injection:autowiring], we usually don't need to specify the constructor parameters because DI will recognize them and pass them automatically. Thus, it would be sufficient to just provide the name of the class: +The last step is to teach the DI container to produce this object. This is usually done by adding an item to the `config/services.neon` file in the `services` section, specifying the full class name and constructor parameters. This registers it, and the object is then called a **service**. Thanks to the magic of [autowiring |dependency-injection:autowiring], we usually don't need to specify the constructor parameters because DI will recognize and pass them automatically. Thus, it would be sufficient to just provide the class name: ```neon .{file:config/services.neon} ... @@ -71,16 +70,15 @@ services: - App\Model\PostFacade ``` -However, you do not need to add this line either. In the `search` section at the beginning of `services.neon` it is defined that all classes ending with `-Facade` or `-Factory` will be looked up by DI automatically, which is also the case for `PostFacade`. +However, you don't need to add this line either. In the `search` section at the beginning of `services.neon`, it is defined that all classes ending with `-Facade` or `-Factory` will be found by DI automatically, which is also the case for `PostFacade`. Summary ======= -The `PostFacade` class asks for `Nette\Database\Explorer` in a constructor and because this class is registered in the DI container, the container creates this instance and passes it. DI this way creates an `PostFacade` instance for us and passes it in a constructor to the HomePresenter class which asked for it. Sort of a Matryoshka doll of code. :) All the components only request what they need and they don't care where and how it gets created. The creation is handled by DI container. +The `PostFacade` class asks for `Nette\Database\Explorer` in its constructor, and since this class is registered in the DI container, the container creates this instance and passes it. DI thus creates an instance of `PostFacade` for us and passes it in the constructor to the `HomePresenter` class, which requested it. It's like a Matryoshka doll. :) Everyone just states what they want, and they don't care where or how it gets created. The creation is handled by the DI container. .[note] -Here you can read more about [dependency injection |dependency-injection:introduction], and about [configuration |nette:configuring]. +Here you can read more about [dependency injection |dependency-injection:introduction] and [configuration |nette:configuring]. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/en/single-post.texy b/quickstart/en/single-post.texy index 9618f4281a..1906df1419 100644 --- a/quickstart/en/single-post.texy +++ b/quickstart/en/single-post.texy @@ -2,16 +2,16 @@ Single Post Page **************** .[perex] -Let’s add another page to our blog, which will display the content of one particular blog post. +Let’s add another page to our blog, which will display the content of one particular post. -We need to create a new render method, that will fetch one specific blog post and pass it to the template. Having this view in `HomePresenter` is not nice because it’s about a blog post, not the homepage. So, let’s create a new class `PostPresenter` and place it to `app/Presenters`. It will need a database connection, so put the *database injection* code there again. +We need to create a new render method that will retrieve one specific post and pass it to the template. Putting this method in `HomePresenter` is not very neat because it’s about a single post, not the homepage. So, let’s create a new class `PostPresenter` and place it in `app/Presentation/Post/`. This presenter will also need a database connection, so we'll add the constructor which will require the database connection. -The `PostPresenter` should look like this: +The `PostPresenter` could look like this: -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Post; use Nette; use Nette\Application\UI\Form; @@ -23,47 +23,47 @@ final class PostPresenter extends Nette\Application\UI\Presenter ) { } - public function renderShow(int $postId): void + public function renderShow(int $id): void { $this->template->post = $this->database ->table('posts') - ->get($postId); + ->get($id); } } ``` -We have to set a correct namespaces `App\Presenters` for our presenter. It depends on [presenter mapping |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7]. +We must not forget to specify the correct namespace `App\Presentation\Post`, which depends on the [presenter mapping |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7] configuration. -The `renderShow` method requires one argument - the ID of the post to be displayed. Then, it loads the post from the database and passes the result to the template. +The `renderShow` method requires one argument - the ID of the post to be displayed. It then loads the post from the database and passes it to the template. -In the `Home/default.latte` template we add a link to the `Post:show` action: +In the `Home/default.latte` template, we add a link to the `Post:show` action: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a href="{link Post:show $post->id}">{$post->title}</a></h2> ... ``` -Tag `{link}` generates URL address which points to the action `Post:show`. This tag also forwards the ID of the post as an argument. +The `{link}` tag generates a URL address that points to the `Post:show` action. It also passes the post ID as an argument. -The same we can write shortly using n:attribute: +The same can be written concisely using an n:attribute: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a n:href="Post:show $post->id">{$post->title}</a></h2> ... ``` -Attribute `n:href` is similar to the `{link}` tag. +The `n:href` attribute is similar to the `{link}` tag. -The template for `Post:show` action does not yet exist. We can open a link to this post. [Tracy |tracy:] will show an error, why `Post/show.latte` doesn't exist. If you see any other error report you probably have to turn on mod_rewrite in your webserver. +However, the template for the `Post:show` action does not exist yet. We can try opening the link to this post. [Tracy |tracy:] will show an error because the template `Post/show.latte` doesn't exist yet. If you see a different error message, you probably need to enable `mod_rewrite` on your web server. -So we'll create `Post/show.latte` with this content: +So, we'll create `Post/show.latte` with this content: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} {block content} <p><a n:href="Home:default">← back to posts list</a></p> @@ -75,36 +75,36 @@ So we'll create `Post/show.latte` with this content: <div class="post">{$post->content}</div> ``` -Let’s have a look at the individual parts. +Now let’s review the individual parts of the template. -The first line starts the definition of a *named block* called "content" that we saw earlier. It will be displayed in a *layout template*. As you can see, the end tag `{/block}` is missing. It is optional. +The first line starts the definition of a block named "content", just like on the homepage. This block will again be displayed within the main layout template. As you can see, the end tag `{/block}` is missing. It is optional. -The second line provides a backlink to the list of blog posts, so the user can navigate smoothly back and forth on our blog. We use `n:href` attribute again, therefore Nette will take care of generating the URL for us. The link points to the `default` action of the `Home` presenter (you could also write `n:href="Home:"` as the `default` action can be omitted). +The second line provides a backlink to the list of blog posts, allowing the user to navigate easily between the post list and a specific post. We use the `n:href` attribute again, so Nette will take care of generating the URL. The link points to the `default` action of the `Home` presenter (you could also write `n:href="Home:"`, as the `default` action name can be omitted; it will be added automatically). -The third line formats the publication timestamp with a filter, as we already know. +The third line formats the date using a filter, which we are already familiar with. -The fourth line displays the *title* of the blog post as a `<h1>` heading. There is a part that you may not be familiar with, and that is `n:block="title"`. Can you guess what it does? If you’ve read the previous parts carefully, we've mentioned `n: attributes`. This is another example. It is equivalent to: +The fourth line displays the *title* of the blog post within an `<h1>` HTML tag. This tag contains an attribute you might not recognize (`n:block="title"`). Can you guess what it does? If you’ve read the previous section carefully, you already know it's an `n:attribute`. This is another example, equivalent to: ```latte {block title}<h1>{$post->title}</h1>{/block} ``` -In simple words, it *re-defines* a block called `title`. The block is defined in *layout template* (`/app/Presenters/templates/@layout.latte:11`) and like with OOP overriding, it gets overridden here. Therefore, the page’s `<title>` will contain the title of the displayed post. We’ve overridden the title of the page and all we needed was `n:block="title"`. Great, huh? +Simply put, this block redefines the block named `title`. This block is already defined in the main *layout* template (`/app/Presentation/@layout.latte:11`), and just like method overriding in OOP, this block overrides the one in the main template. Therefore, the page's `<title>` will now contain the title of the displayed post, and all we needed was this simple `n:block="title"` attribute. Awesome, right? -The fifth and the last line of the template displays full content of your post. +The fifth and final line of the template displays the full content of the specific post. Checking Post ID ================ -What happens if someone alters the URL and inserts `postId` which does not exist? We should provide the user with a nice "page not found" error. Let’s update the render method in `PostPresenter`: +What happens if someone alters the ID in the URL and inserts a non-existent `id`? We should show the user a nice "page not found" error. Let’s modify the render method in `PostPresenter` slightly: -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { $this->error('Post not found'); } @@ -113,13 +113,12 @@ public function renderShow(int $postId): void } ``` -If the post cannot be found, calling `$this->error(...)` will show a 404 page with a nice and understandable message. Note, that in your development environment (on your laptop), you won’t see the error page. Instead, Tracy will show the exception with full details, which is pretty convenient for development. You can check both modes, just change the value passed to `setDebugMode` in `Bootstrap.php`. +If the post cannot be found, calling `$this->error(...)` will display a 404 page with an understandable message. Note that in the development environment (on localhost), you won’t see this error page. Instead, Tracy will show the exception with full details, which is quite convenient for development. If you want to test both modes, simply change the argument passed to the `setDebugMode` method in `Bootstrap.php`. Summary ======= -We have a database with blog posts and a web app with two views - the first one displays the summary of all recent posts and the second one displays one specific post. +We have a database with posts and a web application with two views - the first displays an overview of all posts, and the second displays one specific post. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/es/@home.texy b/quickstart/es/@home.texy index 0f6818ec6b..d98fc86139 100644 --- a/quickstart/es/@home.texy +++ b/quickstart/es/@home.texy @@ -1,119 +1,119 @@ -Cree su primera aplicación -************************** +¡Escribamos nuestra primera aplicación! +*************************************** .[perex] -Conoce Nette Framework mientras creas un sencillo blog con comentarios. ¡Empecemos! +Conozcamos juntos Nette Framework, mientras creamos un blog simple con comentarios. ¡Vamos a ello! -Después de los dos primeros capítulos, tendrás tu propio blog funcionando y estarás listo para publicar tus increíbles posts, aunque las características serán bastante limitadas después de completar estos dos capítulos. Para hacer las cosas más agradables para tus usuarios, también deberías leer los siguientes capítulos y seguir mejorando tu aplicación. +Ya después de los dos primeros capítulos tendremos nuestro propio blog funcional y podremos publicar nuestras excelentes entradas, aunque las funciones estarán por ahora bastante limitadas. Debería leer también los siguientes capítulos, donde programaremos la adición de comentarios, la edición de artículos y, finalmente, aseguraremos el blog. .[tip] -Este tutorial asume que usted ha completado el documento de [Instalación |nette:installation] y ha configurado con éxito sus herramientas. +Este tutorial asume que ha leído la página [Instalación |nette:installation] y ha preparado con éxito las herramientas necesarias. También asume que comprende la [programación orientada a objetos en PHP |nette:introduction-to-object-oriented-programming]. -Por favor, utilice PHP 8.0 o posterior. Puedes encontrar la aplicación completa [en GitHub |https://github.com/nette-examples/quickstart/tree/v4.0]. +Utilice PHP 8.1 o posterior. La aplicación completa se puede encontrar [en GitHub |https://github.com/nette-examples/quickstart/tree/v4.0]. -La página de bienvenida .[#toc-the-welcome-page] -================================================ +Página de bienvenida +==================== -Empecemos creando un nuevo proyecto en el directorio `nette-blog`: +Comencemos creando un nuevo proyecto en el directorio `nette-blog`: ```shell composer create-project nette/web-project nette-blog ``` -En este momento, la página de bienvenida del Proyecto Web debería estar funcionando. Pruébela abriendo su navegador y accediendo a la siguiente URL: +En este momento, la página de inicio del Web Project ya debería funcionar. Lo probaremos abriendo el navegador en la siguiente URL: ``` http://localhost/nette-blog/www/ ``` -y deberías ver la página de bienvenida de Nette Framework: +y veremos la página de inicio de Nette Framework: [* qs-welcome.webp .{url: http://localhost/nette-blog/www/} *] -La aplicación funciona y ya puedes empezar a hacer cambios en ella. +La aplicación funciona y puede comenzar a hacer modificaciones. .[note] -Si tienes algún problema, [prueba estos consejos |nette:troubleshooting#Nette Is Not Working, White Page Is Displayed]. +Si surgió un problema, [prueba estos consejos |nette:troubleshooting#Nette no funciona se muestra una página en blanco]. -Contenido del proyecto web .[#toc-web-project-s-content] -======================================================== +Contenido del Web Project +========================= -Web Project tiene la siguiente estructura: +El Web Project tiene la siguiente estructura: /--pre <b>nette-blog/</b> -├── <b>app/</b> ← directorio de aplicaciones -│ ├── <b>Presenters/</b> ← clases de presentador -│ │ └── <b>templates/</b>← plantillas -│ ├── <b>Router/</b> ← configuración de direcciones URL +├── <b>app/</b> ← directorio de la aplicación +│ ├── <b>Core/</b> ← clases base necesarias para la ejecución +│ ├── <b>Presentation/</b> ← presenters, plantillas y compañía +│ │ └── <b>Home/</b> ← directorio del presenter Home │ └── <b>Bootstrap.php</b> ← clase de arranque Bootstrap -├── <b>bin/</b> ← scripts para la línea de comandos +├── <b>assets/</b> ← activos en bruto (SCSS, TypeScript, imágenes de origen). +├── <b>bin/</b> ← scripts ejecutados desde la línea de comandos ├── <b>config/</b> ← archivos de configuración -├── <b>log/</b> ← registros de errores +├── <b>log/</b> ← registro de errores ├── <b>temp/</b> ← archivos temporales, caché, … -├── <b>vendor/</b> ← bibliotecas instaladas por Composer -│ └── <b>autoload.php</b> ← carga automática de las bibliotecas instaladas por Composer -└── <b>www/</b> ← carpeta pública - el único lugar accesible desde el navegador - └── <b>index.php</b> ← archivo inicial que lanza la aplicación +├── <b>vendor/</b> ← librerías instaladas por Composer +│ └── <b>autoload.php</b> ← autoloading de todos los paquetes instalados +└── <b>www/</b> ← directorio público - el único accesible desde el navegador + ├── <b>assets/</b> ← archivos estáticos compilados (CSS, JS, imágenes, ...) + └── <b>index.php</b> ← archivo inicial que inicia la aplicación \-- -Directorio `www` se supone que almacena imágenes, JavaScript, CSS y otros archivos disponibles públicamente. Este es el único directorio directamente accesible desde el navegador, así que puedes apuntar el directorio raíz de tu servidor web aquí (puedes configurarlo en Apache, pero vamos a hacerlo más tarde ya que no es importante ahora). +El directorio `www/` está destinado a almacenar imágenes, archivos JavaScript, estilos CSS y otros archivos de acceso público. Solo este directorio es accesible desde Internet, así que configure el directorio raíz de su aplicación para que apunte aquí (puede configurarlo en la configuración de Apache o nginx, pero hagámoslo más tarde, ahora no es importante). -El directorio más importante para ti es `app/`. Allí puedes encontrar el archivo `Bootstrap.php`, dentro del cual hay una clase que carga el framework y configura la aplicación. Activa [la autocarga |robot-loader:] y configura el [depurador |tracy:] y [las rutas |application:routing]. +La carpeta más importante para nosotros es `app/`. Aquí encontramos el archivo `Bootstrap.php`, que contiene una clase que sirve para cargar todo el framework y configurar la aplicación. Aquí se activa el [autoloading |robot-loader:], se configura el [depurador |tracy:] y las [rutas |application:routing]. -Limpiar .[#toc-cleanup] -======================= +Limpieza +======== -El Proyecto Web contiene una página de bienvenida, que podemos eliminar - siéntase libre de borrar el archivo `app/Presenters/templates/Home/default.latte` y sustituirlo por el texto "¡Hola mundo!". +El Web Project contiene una página de inicio, que eliminaremos antes de comenzar a programar algo. Sin miedo, reemplazaremos el contenido del archivo `app/Presentation/Home/default.latte` por "Hello world!". [* qs-hello.webp .{url:-} *] -Tracy (Depurador) .[#toc-tracy-debugger] -======================================== +Tracy (depurador) +================= -Una herramienta extremadamente importante para el desarrollo es [un depurador llamado Tracy |tracy:]. Intente cometer algunos errores en su archivo `app/Presenters/HomePresenter.php` (por ejemplo, elimine una llave de la definición de la clase HomePresenter) y vea qué ocurre. Aparecerá una página en pantalla roja con una descripción comprensible del error. +Una herramienta extremadamente importante para el desarrollo es la [herramienta de depuración Tracy |tracy:]. Intente provocar algún error en el archivo `app/Presentation/Home/HomePresenter.php` (por ejemplo, eliminando una llave en la definición de la clase HomePresenter) y vea qué sucede. Aparecerá una página de notificación que describe el error de forma comprensible. -[* qs-tracy.webp .{url:-}(debugger screen) *] +[* qs-tracy.avif .{url:-}(debugger screen) *] -Tracy te ayudará significativamente mientras buscas errores. Observe también la barra flotante de Tracy en la esquina inferior derecha, que le informa sobre datos importantes en tiempo de ejecución. +Tracy nos ayudará enormemente cuando busquemos errores en la aplicación. También observe la barra flotante de Tracy en la esquina inferior derecha de la pantalla, que contiene información sobre la ejecución de la aplicación. [* qs-tracybar.webp .{url:-} *] -En el modo de producción, Tracy está, por supuesto, desactivado y no revela ninguna información sensible. En su lugar, todos los errores se guardan en el directorio `log/`. Pruébalo. En `app/Bootstrap.php`, encuentra el siguiente trozo de código, descomenta la línea y cambia el parámetro de llamada al método a `false`, para que quede así: +En modo de producción, Tracy está, por supuesto, desactivado y no muestra ninguna información sensible. Todos los errores en este caso se guardan en la carpeta `log/`. Probémoslo. En el archivo `app/Bootstrap.php`, descomente la siguiente línea y cambie el parámetro de la llamada a `false`, para que el código se vea así: ```php .{file:app/Bootstrap.php} ... -$configurator->setDebugMode(false); -$configurator->enableTracy(__DIR__ . '/../log'); +$this->configurator->setDebugMode(false); ... ``` -Después de refrescar la página web, la página en pantalla roja será reemplazada por el mensaje amigable para el usuario: +Después de actualizar la página, ya no veremos Tracy. En su lugar, se mostrará un mensaje amigable para el usuario: [* qs-fatal.webp .{url:-}(error screen) *] -Ahora, busca en el directorio `log/`. Allí encontrarás el registro de errores (en el archivo exception.log) y también la página con el mensaje de error (guardada en un archivo HTML cuyo nombre empieza por `exception`). +Ahora echemos un vistazo al directorio `log/`. Aquí (en el archivo `exception.log`) encontraremos el error registrado y también la página ya conocida con el mensaje de error (guardada como un archivo HTML con un nombre que comienza con `exception-`). -Comente de nuevo la línea `// $configurator->setDebugMode(false);`. Tracy habilita automáticamente el modo de desarrollo en el entorno `localhost` y lo deshabilita en el resto. +Comentemos nuevamente la línea `// $configurator->setDebugMode(false);`. Tracy habilita automáticamente el modo de desarrollador en localhost y lo deshabilita en cualquier otro lugar. -Ahora, podemos arreglar el error y continuar diseñando nuestra aplicación. +Podemos corregir el error que creamos y continuar escribiendo la aplicación. -Enviar gracias .[#toc-send-thanks] -================================== +Envía un agradecimiento +======================= -Te vamos a enseñar un truco que hará felices a los autores de código abierto. Puedes dar fácilmente una estrella en GitHub a las librerías que utiliza tu proyecto. Sólo tienes que ejecutar: +Le mostraremos un truco que complacerá a los autores de código abierto. De una manera simple, dé una estrella en GitHub a las bibliotecas que usa su proyecto. Simplemente ejecute: ```shell composer thanks ``` -¡Pruébalo! +¡Pruébelo! {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/es/@left-menu.texy b/quickstart/es/@left-menu.texy index 80a5ed5ddc..9a8d5dada8 100644 --- a/quickstart/es/@left-menu.texy +++ b/quickstart/es/@left-menu.texy @@ -1,9 +1,9 @@ Tutorial ******** -- [Crea tu primera aplicación |@home] +- [¡Escribamos nuestra primera aplicación! |@home] - [Página de inicio del blog |home-page] -- [Página de una sola entrada |single-post] +- [Página de entrada |single-post] - [Comentarios |comments] -- [Crear y editar entradas |creating-posts] -- [Modelo |Model] -- [Autentificación |authentication] +- [Creación y edición de entradas |creating-posts] +- [Model |Model] +- [Autenticación |authentication] diff --git a/quickstart/es/@meta.texy b/quickstart/es/@meta.texy new file mode 100644 index 0000000000..a2fdefb31b --- /dev/null +++ b/quickstart/es/@meta.texy @@ -0,0 +1 @@ +{{sitename: ¡Escribamos nuestra primera aplicación!}} diff --git a/quickstart/es/authentication.texy b/quickstart/es/authentication.texy index b51d1e430a..ce43f22653 100644 --- a/quickstart/es/authentication.texy +++ b/quickstart/es/authentication.texy @@ -1,11 +1,11 @@ Autenticación ************* -Nette le proporciona directrices sobre cómo programar la autenticación en su página, pero no le obliga a hacerlo de ninguna manera en particular. La implementación depende de ti. Nette tiene una interfaz `Nette\Security\Authenticator` que te obliga a implementar un único método llamado `authenticate`, que encuentra al usuario como tú quieras. +Nette proporciona una forma de implementar la autenticación en nuestras páginas, pero no nos obliga a nada. La implementación depende únicamente de nosotros. Nette contiene la interfaz `Nette\Security\Authenticator`, que requiere solo un método `authenticate`, que verifica al usuario de la manera que queramos. -Hay muchas formas de autenticar a un usuario. La forma más común es la *autenticación basada en contraseña* (el usuario proporciona su nombre o correo electrónico y una contraseña), pero también hay otros medios. Puede que estés familiarizado con los botones "Iniciar sesión con Facebook" de muchos sitios web, o iniciar sesión a través de Google/Twitter/GitHub o cualquier otro sitio. Con Nette, puedes tener el método de autenticación que quieras, o puedes combinarlos. Tú decides. +Hay muchas opciones sobre cómo se puede verificar a un usuario. La forma más común de verificación es mediante contraseña (el usuario proporciona su nombre o correo electrónico y contraseña), pero existen otras formas. Quizás conozca los botones tipo "Iniciar sesión con Facebook" o el inicio de sesión con Google/Twitter/GitHub en algunas páginas. Con Nette podemos tener cualquier método de inicio de sesión, o incluso podemos combinarlos. Depende solo de nosotros. -Normalmente escribirías tu propio autenticador, pero para este sencillo blog usaremos el autenticador integrado, que autentica basándose en una contraseña y un nombre de usuario almacenados en un archivo de configuración. Es bueno para propósitos de prueba. Así que añadiremos la siguiente sección *security* al archivo de configuración `config/common.neon`: +Normalmente escribiríamos nuestro propio autenticador, pero para este pequeño blog simple usaremos el autenticador incorporado, que inicia sesión basándose en la contraseña y el nombre de usuario almacenados en el archivo de configuración. Es útil para fines de prueba. Por lo tanto, agregaremos la siguiente sección `security` al archivo de configuración `config/common.neon`: ```neon .{file:config/common.neon} @@ -17,20 +17,20 @@ security: Nette creará automáticamente un servicio en el contenedor DI. -Formulario de registro .[#toc-sign-in-form] -=========================================== +Formulario de inicio de sesión +============================== -Ya tenemos lista la parte backend de la autenticación y necesitamos proporcionar una interfaz de usuario, a través de la cual el usuario iniciaría sesión. Vamos a crear un nuevo presentador llamado *SignPresenter*, que será +Ahora tenemos la autenticación preparada y debemos preparar la interfaz de usuario para el inicio de sesión. Creemos un nuevo presenter llamado `SignPresenter`, que: -- mostrará un formulario de inicio de sesión (solicitando nombre de usuario y contraseña) -- autenticará al usuario cuando envíe el formulario -- proporcionará una acción de cierre de sesión +- mostrará el formulario de inicio de sesión (con nombre de usuario y contraseña) +- después de enviar el formulario, verificará al usuario +- proporcionará la opción de cerrar sesión -Empecemos con el formulario de inicio de sesión. Ya sabes cómo funcionan los formularios en un presentador. Crea el `SignPresenter` y el método `createComponentSignInForm`. Debería tener este aspecto: +Comenzaremos con el formulario de inicio de sesión. Ya sabemos cómo funcionan los formularios en los presenters. Crearemos el presenter `SignPresenter` y escribiremos el método `createComponentSignInForm`. Debería verse algo así: -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Sign; use Nette; use Nette\Application\UI\Form; @@ -40,69 +40,69 @@ final class SignPresenter extends Nette\Application\UI\Presenter protected function createComponentSignInForm(): Form { $form = new Form; - $form->addText('username', 'Username:') - ->setRequired('Please enter your username.'); + $form->addText('username', 'Nombre de usuario:') + ->setRequired('Por favor, introduzca su nombre de usuario.'); - $form->addPassword('password', 'Password:') - ->setRequired('Please enter your password.'); + $form->addPassword('password', 'Contraseña:') + ->setRequired('Por favor introduzca su contraseña.'); - $form->addSubmit('send', 'Sign in'); + $form->addSubmit('send', 'Iniciar sesión'); - $form->onSuccess[] = [$this, 'signInFormSucceeded']; + $form->onSuccess[] = $this->signInFormSucceeded(...); return $form; } } ``` -Hay una entrada para el nombre de usuario y la contraseña. +Hay campos para el nombre de usuario y la contraseña. -Plantilla .[#toc-template] --------------------------- +Plantilla +--------- -El formulario se mostrará en la plantilla `in.latte` +El formulario se renderizará en la plantilla `in.latte`: -```latte .{file:app/Presenters/templates/Sign/in.latte} +```latte .{file:app/Presentation/Sign/in.latte} {block content} -<h1 n:block=title>Sign in</h1> +<h1 n:block=title>Iniciar sesión</h1> {control signInForm} ``` -Controlador de inicio de sesión .[#toc-login-handler] ------------------------------------------------------ +Callback de inicio de sesión +---------------------------- -Añadimos también un *form handler* para registrar al usuario, que es invocado justo después de que el formulario es enviado. +A continuación, agregaremos el callback para iniciar sesión del usuario, que se llamará inmediatamente después de enviar el formulario con éxito. -El manejador tomará el nombre de usuario y la contraseña introducidos por el usuario y los pasará al autenticador definido anteriormente. Después de que el usuario haya iniciado sesión, lo redirigiremos a la página de inicio. +El callback simplemente tomará el nombre de usuario y la contraseña que el usuario completó y los pasará al autenticador. Después de iniciar sesión, redirigiremos a la página de inicio. -```php .{file:app/Presenters/SignPresenter.php} -public function signInFormSucceeded(Form $form, \stdClass $data): void +```php .{file:app/Presentation/Sign/SignPresenter.php} +private function signInFormSucceeded(Form $form, \stdClass $data): void { try { $this->getUser()->login($data->username, $data->password); $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { - $form->addError('Incorrect username or password.'); + $form->addError('Nombre de usuario o contraseña incorrectos.'); } } ``` -El método [User::login() |api:Nette\Security\User::login()] debería lanzar una excepción cuando el nombre de usuario o la contraseña no coincidan con los que hemos definido anteriormente. Como ya sabemos, eso resultaría en una pantalla roja de [Tracy |tracy:], o, en modo producción, en un mensaje informando de un error interno del servidor. Eso no nos gustaría. Por eso capturamos la excepción y añadimos un bonito y amigable mensaje de error al formulario. +El método [User::login() |api:Nette\Security\User::login()] lanzará una excepción si el nombre de usuario y la contraseña no coinciden con los datos del archivo de configuración. Como ya sabemos, esto puede resultar en una página de error roja, o en modo de producción en un mensaje informando sobre un error del servidor. Sin embargo, no queremos eso. Por lo tanto, capturaremos esta excepción y pasaremos un mensaje de error agradable y fácil de usar al formulario. -Cuando se produce el error en el formulario, la página con el formulario se vuelve a mostrar, y encima del formulario, habrá un mensaje agradable, informando al usuario de que ha introducido un nombre de usuario o contraseña incorrectos. +Tan pronto como ocurra un error en el formulario, la página con el formulario se volverá a dibujar y encima del formulario se mostrará un mensaje agradable informando al usuario que ingresó un nombre de usuario o contraseña incorrectos. -Seguridad de los presentadores .[#toc-security-of-presenters] -============================================================= +Asegurando los presenters +========================= -Aseguraremos un formulario para añadir y editar entradas. Está definido en el presentador `EditPresenter`. El objetivo es evitar que los usuarios que no hayan iniciado sesión accedan a la página. +Aseguraremos el formulario para agregar y editar publicaciones. Este está definido en el presenter `EditPresenter`. El objetivo es impedir el acceso a la página a los usuarios que no han iniciado sesión. -Creamos un método `startup()` que se inicia inmediatamente al principio del [ciclo de vida |application:presenters#life-cycle-of-presenter] del presentador. Esto redirige a los usuarios que no han iniciado sesión al formulario de inicio de sesión. +Crearemos el método `startup()`, que se ejecuta inmediatamente al principio del [ciclo de vida del presenter |application:presenters#Ciclo de vida del presenter]. Este redirigirá a los usuarios no autenticados al formulario de inicio de sesión. -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} public function startup(): void { parent::startup(); @@ -114,67 +114,66 @@ public function startup(): void ``` -Ocultar enlaces .[#toc-hide-links] ----------------------------------- +Ocultar enlaces +--------------- -Un usuario no autentificado ya no puede ver la página *crear* ni *editar*, pero todavía puede ver los enlaces que apuntan a ellas. Ocultémoslos también. Uno de esos enlaces está en `app/Presenters/templates/Home/default.latte`, y debería ser visible sólo si el usuario ha iniciado sesión. +Un usuario no autorizado ya no puede ver la página `create` ni `edit`, pero aún puede ver los enlaces a ellas. Deberíamos ocultarlos también. Uno de esos enlaces está en la plantilla `app/Presentation/Home/default.latte` y solo deberían verlo los usuarios que hayan iniciado sesión. -Podemos ocultarlo usando un *n:attribute* llamado `n:if`. Si la declaración que contiene es `false`, toda la etiqueta `<a>` y su contenido no se mostrarán: +Podemos ocultarlo usando un *n:atributo* llamado `n:if`. Si esta condición es `false`, toda la etiqueta `<a>`, incluido el contenido, permanecerá oculta. ```latte -<a n:href="Edit:create" n:if="$user->isLoggedIn()">Create post</a> +<a n:href="Edit:create" n:if="$user->isLoggedIn()">Crear publicación</a> ``` -(no confundir con `tag-if`): +que es una abreviatura de la siguiente notación (no confundir con `tag-if`): ```latte -{if $user->isLoggedIn()}<a n:href="Edit:create">Create post</a>{/if} +{if $user->isLoggedIn()}<a n:href="Edit:create">Crear publicación</a>{/if} ``` -Debe ocultar el enlace de edición situado en `app/Presenters/templates/Post/show.latte` de forma similar. +De la misma manera, ocultaremos también el enlace en la plantilla `app/Presentation/Post/show.latte`. -Enlace al formulario de inicio de sesión .[#toc-login-form-link] -================================================================ +Enlace de inicio de sesión +========================== -Hola, pero ¿cómo llegamos a la página de inicio de sesión? No hay ningún enlace que apunte a ella. Vamos a añadir uno en el archivo de plantilla `@layout.latte`. Intenta encontrar un buen lugar, puede ser donde más te guste. +¿Cómo llegamos realmente a la página de inicio de sesión? No hay ningún enlace que conduzca a ella. Así que agreguémoslo a la plantilla `@layout.latte`. Intente encontrar un lugar adecuado, puede estar casi en cualquier lugar. -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <ul class="navig"> - <li><a n:href="Home:">Home</a></li> + <li><a n:href="Home:">Artículos</a></li> {if $user->isLoggedIn()} - <li><a n:href="Sign:out">Sign out</a></li> + <li><a n:href="Sign:out">Cerrar sesión</a></li> {else} - <li><a n:href="Sign:in">Sign in</a></li> + <li><a n:href="Sign:in">Iniciar sesión</a></li> {/if} </ul> ... ``` -Si el usuario aún no ha iniciado sesión, mostraremos el enlace "Iniciar sesión". En caso contrario, mostraremos el enlace "Cerrar sesión". Añadimos esa acción en SignPresenter. +Si el usuario no ha iniciado sesión, se mostrará el enlace "Iniciar sesión". En caso contrario, se mostrará el enlace "Cerrar sesión". También agregaremos esta acción a `SignPresenter`. -La acción de cierre de sesión tiene este aspecto, y como redirigimos al usuario inmediatamente, no hay necesidad de una plantilla de vista. +Dado que redirigimos al usuario inmediatamente después de cerrar la sesión, no se necesita ninguna plantilla. El cierre de sesión se ve así: -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} public function actionOut(): void { $this->getUser()->logout(); - $this->flashMessage('You have been signed out.'); + $this->flashMessage('El cierre de sesión fue exitoso.'); $this->redirect('Home:'); } ``` -Simplemente llama al método `logout()` y luego muestra un bonito mensaje al usuario. +Simplemente se llama al método `logout()` y luego se muestra un mensaje agradable confirmando el cierre de sesión exitoso. -Resumen .[#toc-summary] -======================= +Resumen +======= -Tenemos un enlace para iniciar sesión y también para cerrar la sesión del usuario. Hemos utilizado el autenticador incorporado para la autenticación y los detalles de inicio de sesión están en el archivo de configuración, ya que esta es una simple aplicación de prueba. También hemos asegurado los formularios de edición para que sólo los usuarios registrados puedan añadir y editar mensajes. +Tenemos un enlace para iniciar sesión y también para cerrar sesión del usuario. Para la verificación, utilizamos el autenticador incorporado y tenemos los datos de inicio de sesión en el archivo de configuración, ya que se trata de una aplicación de prueba simple. También hemos asegurado los formularios de edición, por lo que solo los usuarios que hayan iniciado sesión pueden agregar y editar publicaciones. .[note] -Aquí puedes leer más sobre [login |security:authentication] y [autorización |security:authorization] de usuarios. +Aquí puede leer más sobre el [inicio de sesión de usuarios |security:authentication] y la [Verificación de permisos |security:authorization]. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/es/comments.texy b/quickstart/es/comments.texy index a0b090dec0..b3289763ba 100644 --- a/quickstart/es/comments.texy +++ b/quickstart/es/comments.texy @@ -1,28 +1,28 @@ Comentarios *********** -El blog se ha puesto en marcha, hemos escrito algunas entradas muy buenas y las hemos publicado a través de Adminer. La gente está leyendo el blog y les apasionan nuestras ideas. Estamos recibiendo muchos correos electrónicos con elogios cada día. Pero, ¿para qué sirven todos esos elogios si sólo los recibimos en el correo electrónico, de modo que nadie más puede leerlos? ¿No sería mejor que la gente pudiera comentar directamente en el blog para que todo el mundo pudiera leer lo increíbles que somos? +Hemos subido el blog al servidor web y publicado algunas entradas muy interesantes usando Adminer. La gente lee nuestro blog y está muy entusiasmada con él. Recibimos muchos correos electrónicos con elogios todos los días. Pero, ¿de qué sirven todos estos elogios si solo los tenemos en el correo electrónico y nadie más puede leerlos? Sería mejor si el lector pudiera comentar directamente en el artículo, para que todos pudieran leer lo geniales que somos. -Hagamos que todos los artículos se puedan comentar. +Así que vamos a programar los comentarios. -Crear una nueva tabla .[#toc-creating-a-new-table] -================================================== +Creación de una nueva tabla +=========================== -Inicie Adminer de nuevo y cree una nueva tabla llamada `comments` con estas columnas: +Iniciamos Adminer y creamos una tabla `comments` con estas columnas: -- `id` int, comprobar autoincremento (AI) -- `post_id`, una clave externa que hace referencia a la tabla `posts` +- `id` int, marcamos autoincrement (AI) +- `post_id`, clave foránea que hace referencia a la tabla `posts` - `name` varchar, longitud 255 - `email` varchar, longitud 255 -- `content` texto +- `content` text - `created_at` timestamp -Debería tener este aspecto: +La tabla debería verse algo así: [* adminer-comments.webp *] -No olvides usar el almacenamiento de tablas InnoDB y pulsa Guardar. +No olvides usar de nuevo el almacenamiento InnoDB. ```sql CREATE TABLE `comments` ( @@ -37,83 +37,83 @@ CREATE TABLE `comments` ( ``` -Formulario de comentarios .[#toc-form-for-commenting] -===================================================== +Formulario para comentar +======================== -Primero, necesitamos crear un formulario, que permitirá a los usuarios comentar en nuestra página. Nette Framework tiene un soporte impresionante para formularios. Pueden ser configurados en un presentador y renderizados en una plantilla. +Primero, debemos crear un formulario que permita a los usuarios comentar las entradas. Nette Framework tiene un soporte increíble para formularios. Podemos configurarlos en el presenter y renderizarlos en la plantilla. -Nette Framework tiene un concepto de *componentes*. Un **componente** es una clase reutilizable o una pieza de código, que puede ser adjuntada a otro componente. Incluso un presentador es un componente. Cada componente se crea utilizando la fábrica de componentes. Así que vamos a definir la fábrica de formularios de comentarios en `PostPresenter`. +Nette Framework utiliza el concepto de *componentes*. Un **componente** es una clase reutilizable o una parte de código que se puede adjuntar a otro componente. Incluso el presenter es un componente. Cada componente se crea a través de una fábrica. Por lo tanto, crearemos una fábrica para el formulario de comentarios en el presenter `PostPresenter`. -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} protected function createComponentCommentForm(): Form { - $form = new Form; // means Nette\Application\UI\Form + $form = new Form; // significa Nette\Application\UI\Form - $form->addText('name', 'Your name:') + $form->addText('name', 'Nombre:') ->setRequired(); - $form->addEmail('email', 'Email:'); + $form->addEmail('email', 'E-mail:'); - $form->addTextArea('content', 'Comment:') + $form->addTextArea('content', 'Comentario:') ->setRequired(); - $form->addSubmit('send', 'Publish comment'); + $form->addSubmit('send', 'Publicar comentario'); return $form; } ``` -Vamos a explicarlo un poco. La primera línea crea una nueva instancia del componente `Form`. Los siguientes métodos están adjuntando entradas HTML en la definición del formulario. `->addText` se renderizará como `<input type=text name=name>`con `<label>Your name:</label>`. Como ya habrás adivinado en este momento, el `->addTextArea` adjunta un `<textarea>` y `->addSubmit` añade un `<input type=submit>`. Hay más métodos como este, pero esto es todo lo que tienes que saber por ahora. Puedes [aprender más en la documentación |forms:]. +Vamos a explicarlo un poco de nuevo. La primera línea crea una nueva instancia del componente `Form`. Los métodos siguientes adjuntan entradas HTML a la definición de este formulario. `->addText` se renderizará como `<input type="text" name="name">` con `<label>Nombre:</label>`. Como probablemente ya adivinas correctamente, `->addTextArea` se renderizará como `<textarea>` y `->addSubmit` como `<input type="submit">`. Hay muchos más métodos similares, pero estos son suficientes para este formulario por ahora. Puedes [leer más en la documentación|forms:]. -Una vez definido el componente formulario en un presentador, podemos renderizarlo (mostrarlo) en una plantilla. Para ello, coloque la etiqueta `{control}` al final de la plantilla de detalles de la entrada, en `Post/show.latte`. Dado que el nombre del componente es `commentForm` (se deriva del nombre del método `createComponentCommentForm`), la etiqueta tendrá el siguiente aspecto +Si el formulario ya está definido en el presenter, podemos renderizarlo (mostrarlo) en la plantilla. Esto se hace colocando la etiqueta `{control}` al final de la plantilla que renderiza una entrada específica, en `Post/show.latte`. Como el componente se llama `commentForm` (el nombre se deriva del nombre del método `createComponentCommentForm`), la etiqueta se verá así: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... -<h2>Post new comment</h2> +<h2>Añadir nuevo comentario</h2> {control commentForm} ``` -Ahora, si revisas el detalle de algún post, habrá un nuevo formulario para publicar comentarios. +Si ahora ves la página con el detalle de la entrada, verás un nuevo formulario para comentarios al final. -Guardar en base de datos .[#toc-saving-to-database] -=================================================== +Guardando en la base de datos +============================= -¿Ha intentado enviar algunos datos? Te habrás dado cuenta de que el formulario no realiza ninguna acción. Simplemente está ahí, con un aspecto chulo y sin hacer nada. Tenemos que adjuntarle un método callback, que guardará los datos enviados. +¿Ya has intentado rellenar el formulario y enviarlo? Probablemente te hayas dado cuenta de que el formulario en realidad no hace nada. Necesitamos adjuntar un método de callback que guarde los datos enviados. -Coloca la siguiente línea antes de la línea `return` en la fábrica del componente `commentForm`: +En la línea antes de `return` en la fábrica para el componente `commentForm`, colocamos la siguiente línea: ```php -$form->onSuccess[] = [$this, 'commentFormSucceeded']; +$form->onSuccess[] = $this->commentFormSucceeded(...); ``` -Significa "después de que el formulario se envíe correctamente, llama al método `commentFormSucceeded` del presentador actual". Este método no existe todavía, así que vamos a crearlo. +La notación anterior significa "después de enviar el formulario con éxito, llama al método `commentFormSucceeded` del presenter actual". Sin embargo, este método aún no existe. Vamos a crearlo: -```php .{file:app/Presenters/PostPresenter.php} -public function commentFormSucceeded(\stdClass $data): void +```php .{file:app/Presentation/Post/PostPresenter.php} +private function commentFormSucceeded(\stdClass $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); $this->database->table('comments')->insert([ - 'post_id' => $postId, + 'post_id' => $id, 'name' => $data->name, 'email' => $data->email, 'content' => $data->content, ]); - $this->flashMessage('Thank you for your comment', 'success'); + $this->flashMessage('Gracias por tu comentario', 'success'); $this->redirect('this'); } ``` -Debes colocarlo justo después de la fábrica del componente `commentForm`. +Colocamos este método justo después de la fábrica del formulario `commentForm`. -El nuevo método tiene un argumento que es la instancia del formulario que se está enviando, creada por la fábrica de componentes. Recibimos los valores enviados en `$data`. Y luego insertamos los datos en la tabla de la base de datos `comments`. +Este nuevo método tiene un argumento, que es una instancia del formulario que fue enviado - creado por la fábrica. Obtenemos los valores enviados en `$data`. Y luego guardamos los datos en la tabla de la base de datos `comments`. -Hay otras dos llamadas a métodos que explicar. La redirección redirige literalmente a la página actual. Deberías hacer eso cada vez que el formulario es enviado, válido, y la operación callback hizo lo que debería haber hecho. Además, cuando redirija la página después de enviar el formulario, no verá el conocido mensaje `Would you like to submit the post data again?` que a veces puede ver en el navegador. (En general, después de enviar un formulario por el método `POST`, siempre debes redirigir al usuario a una acción `GET` ). +Todavía hay otros dos métodos que merecen ser explicados. El método `redirect` literalmente redirige de vuelta a la página actual. Es conveniente hacer esto después de cada envío de formulario, si contenía datos válidos y el callback realizó la operación como debía. Y también, si redirigimos la página después de enviar el formulario, no veremos el conocido mensaje `¿Quieres volver a enviar los datos del formulario?`, que a veces podemos ver en el navegador. (En general, después de enviar un formulario con el método `POST`, siempre debe seguir una redirección a una acción `GET`). -El `flashMessage` sirve para informar al usuario del resultado de alguna operación. Debido a que estamos redirigiendo, el mensaje no puede ser pasado directamente a la plantilla y renderizado. Así que existe este método, que lo almacenará y lo hará disponible en la siguiente carga de página. Los mensajes flash son renderizados en el archivo por defecto `app/Presenters/templates/@layout.latte`, y se ve así: +El método `flashMessage` es para informar al usuario sobre el resultado de alguna operación. Como estamos redirigiendo, el mensaje no puede simplemente pasarse a la plantilla y renderizarse. Por eso existe este método, que guarda este mensaje y lo hace accesible en la próxima carga de la página. Los mensajes flash se renderizan en la plantilla principal `app/Presentation/@layout.latte` y se ve así: ```latte <div n:foreach="$flashes as $flash" n:class="flash, $flash->type"> @@ -121,20 +121,20 @@ El `flashMessage` sirve para informar al usuario del resultado de alguna operaci </div> ``` -Como ya sabemos, se pasan automáticamente a la plantilla, así que no tienes que pensar mucho en ello, simplemente funciona. Para más detalles [consulta la documentación |application:presenters#flash-messages]. +Como ya sabemos, los mensajes flash se pasan automáticamente a la plantilla, así que no tenemos que pensar mucho en ello, simplemente funciona. Para más información, [visita la documentación |application:presenters#Mensajes flash]. -Renderizando los Comentarios .[#toc-rendering-the-comments] -=========================================================== +Renderizando comentarios +======================== -Esta es una de las cosas que te encantará. La Base de Datos Nette tiene esta característica genial llamada [Explorador |database:explorer]. ¿Recuerdas que hemos creado las tablas como InnoDB? Adminer ha creado las llamadas [claves foráneas |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html] que nos ahorrarán un montón de trabajo. +Esta es una de esas cosas que simplemente te encantarán. Nette Database tiene una función genial llamada [Explorer |database:explorer]. ¿Recuerdas que creamos intencionalmente las tablas en la base de datos usando el almacenamiento InnoDB? Adminer creó así algo llamado [claves foráneas |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html], que nos ahorrarán mucho trabajo. -Nette Database Explorer utiliza las claves externas para resolver las relaciones entre las tablas, y conociendo las relaciones, puede crear automáticamente consultas para usted. +Nette Database Explorer utiliza claves foráneas para resolver la relación mutua entre tablas y, a partir del conocimiento de estas relaciones, puede crear automáticamente consultas a la base de datos. -Como recordará, hemos pasado la variable `$post` a la plantilla en `PostPresenter::renderShow()` y ahora queremos iterar por todos los comentarios que tengan la columna `post_id` igual a nuestra `$post->id`. Puedes hacerlo llamando a `$post->related('comments')`. Así de sencillo. Mira el código resultante. +Como seguramente recordarás, pasamos la variable `$post` a la plantilla usando el método `PostPresenter::renderShow()` y ahora queremos iterar sobre todos los comentarios que tienen el valor de la columna `post_id` igual a `$post->id`. Podemos lograr esto llamando a `$post->related('comments')`. Sí, así de simple. Veamos el código resultante: -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { ... $this->template->post = $post; @@ -144,15 +144,15 @@ public function renderShow(int $postId): void Y la plantilla: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... -<h2>Comments</h2> +<h2>Comentarios</h2> <div class="comments"> {foreach $comments as $comment} <p><b><a href="mailto:{$comment->email}" n:tag-if="$comment->email"> {$comment->name} - </a></b> said:</p> + </a></b> escribió:</p> <div>{$comment->content}</div> {/foreach} @@ -160,13 +160,12 @@ Y la plantilla: ... ``` -Fíjate en el atributo especial `n:tag-if`. Ya sabes cómo funciona `n: attributes`. Pues bien, si antepones al atributo `tag-`, sólo envolverá las etiquetas, no su contenido. Esto le permite convertir el nombre del comentarista en un enlace si ha proporcionado su dirección de correo electrónico. Los resultados de estas dos líneas son idénticos: +Observa el atributo especial `n:tag-if`. Ya sabes cómo funcionan los `n:atributos`. Si adjuntas el prefijo `tag-` al atributo, la funcionalidad se aplica solo a la etiqueta HTML, no a su contenido. Esto nos permite convertir el nombre del comentarista en un enlace solo si proporcionó su correo electrónico. Estas dos líneas son idénticas: ```latte -<strong n:tag-if="$important"> Hello there! </strong> +<strong n:tag-if="$important"> ¡Hola! </strong> -{if $important}<strong>{/if} Hello there! {if $important}</strong>{/if} +{if $important}<strong>{/if} ¡Hola! {if $important}</strong>{/if} ``` {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/es/creating-posts.texy b/quickstart/es/creating-posts.texy index 08383c6f2e..e668ce9c85 100644 --- a/quickstart/es/creating-posts.texy +++ b/quickstart/es/creating-posts.texy @@ -1,30 +1,30 @@ Creación y edición de entradas ****************************** -Qué gran momento. Tenemos un nuevo blog super chulo, la gente discute en los comentarios y por fin tenemos algo de tiempo para programar más. Aunque nos gusta Adminer, no es tan cómodo escribir entradas de blog en él. Tal vez es el momento adecuado para añadir un sencillo formulario para añadir nuevos mensajes directamente desde nuestra aplicación. Hagámoslo. +¡Esto es genial! Tenemos un blog nuevo súper genial, la gente discute intensamente en los comentarios y finalmente tenemos un poco de tiempo para seguir programando. Aunque Adminer es una gran herramienta, no es del todo ideal para escribir nuevas entradas de blog. Aparentemente, es el momento adecuado para crear un formulario simple para agregar nuevas entradas directamente desde la aplicación. Vamos a ello. -Empecemos por diseñar la interfaz de usuario: +Comencemos diseñando la interfaz de usuario: -1. En la página de inicio, añadamos un enlace "Escribir nuevo post". -2. Mostrará un formulario con título y textarea para el contenido. -3. Al hacer clic en el botón Guardar, se guardará la entrada del blog. +1. En la página de inicio, agregaremos un enlace "Escribir nueva entrada". +2. Este enlace mostrará un formulario con un título y un área de texto para el contenido de la entrada. +3. Cuando hagamos clic en el botón Guardar, la entrada se guardará en la base de datos. -Más adelante también añadiremos autenticación y permitiremos que sólo los usuarios registrados puedan añadir nuevas entradas. Pero eso lo haremos más adelante. ¿Qué código tendremos que escribir para que funcione? +Más tarde, también agregaremos inicio de sesión y permitiremos agregar entradas solo a los usuarios que hayan iniciado sesión. Pero eso será más tarde. ¿Qué código necesitamos escribir ahora para que todo funcione? -1. Crea un nuevo presentador con un formulario para añadir entradas. -2. Definir un callback que se disparará tras el envío exitoso del formulario y que guardará el nuevo post en la base de datos. -3. Cree una nueva plantilla para el formulario. -4. 4. Añada un enlace al formulario en la plantilla de la página principal. +1. Crearemos un nuevo presenter con un formulario para agregar entradas. +2. Definiremos un callback que se ejecutará después de enviar el formulario con éxito y que guardará la nueva entrada en la base de datos. +3. Crearemos una nueva plantilla en la que estará dicho formulario. +4. Agregaremos un enlace al formulario en la plantilla de la página principal. -Nuevo presentador .[#toc-new-presenter] -======================================= +Nuevo presenter +=============== -Nombra al nuevo presentador `EditPresenter` y guárdalo en `app/Presenters/EditPresenter.php`. También necesita conectarse a la base de datos, así que aquí de nuevo escribimos un constructor que requerirá una conexión a la base de datos: +Llamaremos al nuevo presenter `EditPresenter` y lo guardaremos en `app/Presentation/Edit/`. También necesita conectarse a la base de datos, por lo que aquí nuevamente escribiremos un constructor que requerirá una conexión a la base de datos: -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Edit; use Nette; use Nette\Application\UI\Form; @@ -39,95 +39,95 @@ final class EditPresenter extends Nette\Application\UI\Presenter ``` -Formulario para guardar entradas .[#toc-form-for-saving-posts] -============================================================== +Formulario para guardar entradas +================================ -Los formularios y componentes ya han sido cubiertos cuando añadimos soporte para comentarios. Si estás confundido sobre el tema, revisa [cómo funcionan los formularios y componentes |comments#form-for-commenting] de nuevo, esperaremos aquí ;) +Ya hemos explicado los formularios y componentes al crear comentarios. Si todavía no está claro, ve a repasar [la creación de formularios y componentes |comments#Formulario para comentar], nosotros esperaremos aquí mientras tanto ;) -Ahora añade este método a `EditPresenter`: +Ahora agreguemos este método al presenter `EditPresenter`: -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} protected function createComponentPostForm(): Form { $form = new Form; - $form->addText('title', 'Title:') + $form->addText('title', 'Título:') ->setRequired(); - $form->addTextArea('content', 'Content:') + $form->addTextArea('content', 'Contenido:') ->setRequired(); - $form->addSubmit('send', 'Save and publish'); - $form->onSuccess[] = [$this, 'postFormSucceeded']; + $form->addSubmit('send', 'Guardar y publicar'); + $form->onSuccess[] = $this->postFormSucceeded(...); return $form; } ``` -Guardar nueva entrada desde el formulario .[#toc-saving-new-post-from-form] -=========================================================================== +Guardar nueva entrada desde el formulario +========================================= -Continúe añadiendo un método manejador. +Continuamos agregando un método que procesará los datos del formulario: -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { $post = $this->database ->table('posts') ->insert($data); - $this->flashMessage('Post was published', 'success'); + $this->flashMessage("La entrada se publicó con éxito.", 'success'); $this->redirect('Post:show', $post->id); } ``` -Sólo una explicación rápida: obtiene los valores del formulario, los inserta en la base de datos, crea un mensaje para el usuario indicando que la entrada se ha guardado correctamente, y redirige a la página donde se publica esa entrada para que pueda ver cómo queda. +Solo un rápido resumen: este método obtiene los datos del formulario, los inserta en la base de datos, crea un mensaje para el usuario sobre el guardado exitoso de la entrada y redirige a la página con la nueva entrada, para que veamos de inmediato cómo se ve. -Página para crear un nuevo post .[#toc-page-for-creating-a-new-post] -==================================================================== +Página para crear una nueva entrada +=================================== -Vamos a crear la plantilla `Edit/create.latte`: +Ahora creemos la plantilla `Edit/create.latte`: -```latte .{file:app/Presenters/templates/Edit/create.latte} +```latte .{file:app/Presentation/Edit/create.latte} {block content} -<h1>New post</h1> +<h1>Nueva entrada</h1> {control postForm} ``` -Todo debería estar claro ahora. La última línea muestra el formulario que vamos a crear. +Todo debería estar claro ya. La última línea renderiza el formulario que acabamos de crear. -También podríamos crear un método `renderCreate` correspondiente, pero no es necesario. No necesitamos obtener ningún dato de la base de datos y pasarlo a la plantilla, por lo que ese método estaría vacío. En estos casos, el método puede no existir en absoluto. +Podríamos crear también el método `renderCreate` correspondiente, pero no es necesario. No necesitamos obtener ningún dato de la base de datos y pasarlo a la plantilla, por lo que ese método estaría vacío. En tales casos, el método no necesita existir en absoluto. -Enlace para crear entradas .[#toc-link-for-creating-posts] -========================================================== +Enlace para crear entradas +========================== -Probablemente ya sabes cómo añadir un enlace a `EditPresenter` y su acción `create`. Pruébalo. +Probablemente ya sepas cómo agregar un enlace a `EditPresenter` y su acción `create`. Inténtalo. -Sólo tiene que añadir al archivo `app/Presenters/templates/Home/default.latte`: +Simplemente agrega esto al archivo `app/Presentation/Home/default.latte`: ```latte -<a n:href="Edit:create">Write new post</a> +<a n:href="Edit:create">Escribir nueva entrada</a> ``` -Editar Mensajes .[#toc-editing-posts] -===================================== +Edición de entradas +=================== -Añadamos también la capacidad de editar entradas existentes. Será bastante simple - ya tenemos `postForm` y podemos usarlo para editar también. +Ahora también agregaremos la opción de editar una entrada. Será muy simple. Ya tenemos listo el formulario `postForm` y podemos usarlo también para editar. -Añadiremos una nueva página `edit` a `EditPresenter`: +Agregaremos una nueva página `edit` al presenter `EditPresenter`: -```php .{file:app/Presenters/EditPresenter.php} -public function renderEdit(int $postId): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +public function renderEdit(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { - $this->error('Post not found'); + $this->error('Entrada no encontrada'); } $this->getComponent('postForm') @@ -135,26 +135,26 @@ public function renderEdit(int $postId): void } ``` -Y crearemos la plantilla `Edit/edit.latte`: +Y crearemos otra plantilla `Edit/edit.latte`: -```latte .{file:app/Presenters/templates/Edit/edit.latte} +```latte .{file:app/Presentation/Edit/edit.latte} {block content} -<h1>Edit post</h1> +<h1>Editar entrada</h1> {control postForm} ``` -Y actualizar el método `postFormSucceeded`, que será capaz de añadir un nuevo mensaje (como lo hace ahora), o para editar los ya existentes: +Y modificaremos el método `postFormSucceeded`, que podrá tanto agregar un nuevo artículo (como lo hace ahora) como editar un artículo ya existente: -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); - if ($postId) { + if ($id) { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); $post->update($data); } else { @@ -163,26 +163,25 @@ public function postFormSucceeded(array $data): void ->insert($data); } - $this->flashMessage('Post was published', 'success'); + $this->flashMessage('La entrada se publicó con éxito.', 'success'); $this->redirect('Post:show', $post->id); } ``` -Cuando se proporciona el parámetro `postId`, significa que se está editando un post. En tal caso, comprobaremos que la entrada existe realmente y, si es así, la actualizaremos en la base de datos. Si no se proporciona el parámetro `postId`, significa que se añadirá un nuevo mensaje. +Si el parámetro `id` está disponible, significa que vamos a editar la entrada. En ese caso, verificamos que la entrada solicitada realmente exista y, si es así, la actualizamos en la base de datos. Si el parámetro `id` no está disponible, significa que se debe agregar una nueva entrada. -¿Pero de dónde viene `postId`? Es el parámetro que se pasa al método `renderEdit`. +Pero, ¿de dónde viene el parámetro `id`? Es el parámetro que se pasó al método `renderEdit`. -Ahora puede añadir un enlace a la plantilla `app/Presenters/templates/Post/show.latte`: +Ahora podemos agregar un enlace a la plantilla `app/Presentation/Post/show.latte`: ```latte -<a n:href="Edit:edit $post->id">Edit this post</a> +<a n:href="Edit:edit $post->id">Editar entrada</a> ``` -Resumen .[#toc-summary] -======================= +Resumen +======= -El blog funciona, la gente comenta rápidamente y ya no dependemos de Adminer para añadir nuevas entradas. Es totalmente independiente e incluso la gente normal puede publicar allí. Pero espera, eso probablemente no está bien, que cualquiera, quiero decir realmente cualquier persona en Internet, puede publicar en nuestro blog. Es necesario algún tipo de autenticación para que sólo los usuarios registrados puedan publicar. Lo añadiremos en el próximo capítulo. +El blog ahora es funcional, los visitantes comentan activamente y ya no necesitamos Adminer para publicar. La aplicación es completamente independiente y cualquiera puede agregar una nueva entrada. Espera un momento, probablemente no esté del todo bien que cualquiera - y me refiero a realmente cualquiera con acceso a Internet - pueda agregar nuevas entradas. Se necesita alguna seguridad para que solo un usuario que haya iniciado sesión pueda agregar una nueva entrada. Veremos eso en el próximo capítulo. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/es/home-page.texy b/quickstart/es/home-page.texy index 9eb7e78e61..960039c292 100644 --- a/quickstart/es/home-page.texy +++ b/quickstart/es/home-page.texy @@ -2,40 +2,40 @@ Página de inicio del blog ************************* .[perex] -Vamos a crear la página de inicio mostrando tus entradas recientes. +Ahora crearemos una página de inicio que muestre las últimas entradas. -Antes de empezar, deberías conocer al menos algunos conceptos básicos sobre el patrón de diseño Modelo-Vista-Presentador (similar a MVC((Modelo-Vista-Controlador))): +Antes de comenzar, es necesario conocer al menos los fundamentos del patrón de diseño Model-View-Presenter (similar a MVC((Model-View-Controller))): -- **Modelo** - capa de manipulación de datos. Está completamente separada del resto de la aplicación. Sólo se comunica con los presentadores. +- **Model** - capa que trabaja con los datos. Está completamente separada del resto de la aplicación. Se comunica solo con el presenter. -- **Vista** - capa de definición del front-end. Presenta los datos solicitados al usuario utilizando plantillas. +- **View** - capa front-end. Renderiza los datos solicitados usando plantillas y los muestra al usuario. -- **Presentador** (o Controlador) - una capa de conexión. El presentador conecta el modelo y la vista. Maneja las solicitudes, pide datos al Modelo y luego los pasa a la Vista actual. +- **Presenter** (o Controller) - capa de conexión. El Presenter conecta el Model y el View. Procesa las peticiones, consulta al Model por los datos y los devuelve al View. -En el caso de una aplicación muy simple como nuestro blog, la capa Modelo consistirá en realidad sólo de consultas a la base de datos en sí - no necesitamos ningún código PHP extra para ello. Sólo necesitamos crear las capas Presentador y Vista. En Nette, cada Presentador tiene sus propias Vistas, así que continuaremos con ambas simultáneamente. +En el caso de aplicaciones simples, como será nuestro blog, toda la capa de modelo consistirá solo en consultas a la base de datos; para eso, por ahora, no necesitamos ningún código extra. Para empezar, crearemos solo los presenters y las plantillas. En Nette, cada presenter tiene sus propias plantillas, así que las crearemos al mismo tiempo. -Creando la Base de Datos con Adminer .[#toc-creating-the-database-with-adminer] -=============================================================================== +Creación de la base de datos con Adminer +======================================== -Para almacenar los datos, utilizaremos la base de datos MySQL porque es la opción más común entre los desarrolladores web. Pero si no te gusta, siéntete libre de usar una base de datos de tu elección. +Para almacenar los datos, usaremos una base de datos MySQL, ya que es la más extendida entre los programadores de aplicaciones web. Sin embargo, si no quieres usarla, siéntete libre de elegir la base de datos que prefieras. -Preparemos la base de datos que almacenará las entradas de nuestro blog. Podemos empezar de forma muy simple - sólo con una única tabla para las entradas. +Ahora prepararemos la estructura de la base de datos donde se almacenarán los artículos de nuestro blog. Comenzaremos de manera muy simple: crearemos solo una tabla para las entradas. -Para crear la base de datos podemos descargar [Adminer |https://www.adminer.org], o puede utilizar otra herramienta para la gestión de bases de datos. +Para crear la base de datos, podemos descargar [Adminer |https://www.adminer.org], u otra herramienta de administración de bases de datos de tu preferencia. -Abramos Adminer y creemos una nueva base de datos llamada `quickstart`. +Abrimos Adminer y creamos una nueva base de datos llamada `quickstart`. -Creamos una nueva tabla llamada `posts` y añadimos estas columnas -- `id` int, haga clic en autoincrementar (AI) +Creamos una nueva tabla llamada `posts` con estas columnas: +- `id` int, marcamos autoincrement (AI) - `title` varchar, longitud 255 -- `content` texto +- `content` text - `created_at` timestamp -Debería tener este aspecto: +La estructura resultante debería verse así: [* adminer-posts.webp *] @@ -49,9 +49,9 @@ CREATE TABLE `posts` ( ``` .[caution] -Es muy importante utilizar la tabla de almacenamiento **InnoDB**. Verás la razón más adelante. Por ahora, sólo tienes que elegir eso y enviar. Ya puedes pulsar Guardar. +Es realmente importante usar el motor de almacenamiento **InnoDB**. En un momento mostraremos por qué. Por ahora, simplemente selecciónalo y haz clic en guardar. -Intenta añadir algunas entradas de blog de ejemplo antes de que implementemos la capacidad de añadir nuevas entradas directamente desde nuestra aplicación. +Antes de crear la posibilidad de agregar artículos a la base de datos mediante la aplicación, agrega manualmente algunos artículos de ejemplo al blog. ```sql INSERT INTO `posts` (`id`, `title`, `content`, `created_at`) VALUES @@ -61,37 +61,34 @@ INSERT INTO `posts` (`id`, `title`, `content`, `created_at`) VALUES ``` -Conexión a la base de datos .[#toc-connecting-to-the-database] -============================================================== +Conexión a la base de datos +=========================== -Ahora, cuando la base de datos está creada y tenemos algunos posts en ella, es el momento de mostrarlos en nuestra nueva y brillante página. +Ahora que la base de datos está creada y tenemos algunos artículos guardados en ella, es el momento adecuado para mostrarlos en nuestra hermosa nueva página. -Primero, necesitamos decirle a nuestra aplicación qué base de datos usar. La configuración de la conexión a la base de datos se almacena en `config/local.neon`. Configura la conexión DSN((Data Source Name)) y tus credenciales. Debería verse así: +Primero, debemos decirle a la aplicación qué base de datos usar. La conexión a la base de datos se configura en el archivo `config/common.neon` usando DSN((Data Source Name)) y las credenciales de inicio de sesión. Debería verse algo así: -```neon .{file:config/local.neon} +```neon .{file:config/common.neon} database: dsn: 'mysql:host=127.0.0.1;dbname=quickstart' - user: *enter user name* - password: *enter password here* + user: *aquí inserta el nombre de usuario* + password: *aquí inserta la contraseña de la base de datos* ``` .[note] -Tenga en cuenta la sangría al editar este archivo. El [formato NEON |neon:format] acepta tanto espacios como tabuladores, pero no ambos a la vez. El archivo de configuración del Proyecto Web utiliza tabuladores por defecto. +Al editar este archivo, ten cuidado con la indentación de las líneas. El formato [NEON |neon:format] acepta tanto la indentación con espacios como con tabuladores, pero no ambos al mismo tiempo. El archivo de configuración predeterminado en Web Project utiliza tabuladores. -Toda la configuración incluida se almacena en `config/` en los archivos `common.neon` y `local.neon`. El archivo `common.neon` contiene la configuración global de la aplicación y `local.neon` contiene sólo los parámetros específicos del entorno (por ejemplo, la diferencia entre el servidor de desarrollo y el de producción). +Pasar la conexión de la base de datos +===================================== -Inyección de la conexión a la base de datos .[#toc-injecting-the-database-connection] -===================================================================================== +El presenter `HomePresenter`, que se encargará de mostrar los artículos, necesita una conexión a la base de datos. Para obtenerla, utilizaremos un constructor que se verá así: -El presentador `HomePresenter`, que listará los artículos, necesita una conexión a la base de datos. Para recibirla, escribe un constructor como este: - -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; -use Nette\Application\UI\Form; final class HomePresenter extends Nette\Application\UI\Presenter { @@ -105,12 +102,12 @@ final class HomePresenter extends Nette\Application\UI\Presenter ``` -Cargar mensajes de la base de datos .[#toc-loading-posts-from-the-database] -=========================================================================== +Cargar entradas desde la base de datos +====================================== -Ahora vamos a obtener las entradas de la base de datos y pasarlas a la plantilla, que luego renderizará el código HTML. Para esto está el método *render*: +Ahora cargaremos las entradas desde la base de datos y las enviaremos a la plantilla, que luego las renderizará como código HTML. Para esto está destinado el llamado método *render*: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} public function renderDefault(): void { $this->template->posts = $this->database @@ -120,41 +117,41 @@ public function renderDefault(): void } ``` -El presentador tiene ahora un método render `renderDefault()` que pasa los datos a una vista llamada `default`. Las plantillas del presentador se pueden encontrar en `app/Presenters/templates/{PresenterName}/{viewName}.latte`, así que en este caso la plantilla se encontrará en `app/Presenters/templates/Home/default.latte`. En la plantilla, ahora está disponible una variable llamada `$posts`, que contiene los mensajes de la base de datos. +El presenter ahora contiene un método de renderizado `renderDefault()`, que pasa datos de la base de datos a la plantilla (View). Las plantillas se encuentran en `app/Presentation/{PresenterName}/{viewName}.latte`, por lo que en este caso, la plantilla se encuentra en `app/Presentation/Home/default.latte`. En la plantilla ahora estará disponible la variable `$posts`, que contiene las entradas obtenidas de la base de datos. -Plantilla .[#toc-template] -========================== +Plantilla +========= -Existe una plantilla genérica para toda la página (llamada *layout*, con cabecera, hojas de estilo, pie de página, ...) y luego plantillas específicas para cada vista (por ejemplo, para mostrar la lista de entradas del blog), que pueden anular algunas partes de la plantilla layout. +Para todo el sitio web, tenemos disponible una plantilla principal (que se llama *layout*, contiene la cabecera, estilos, pie de página,...) y luego plantillas específicas para cada vista (View) (por ejemplo, para mostrar las entradas del blog), que pueden sobrescribir algunas partes de la plantilla principal. -Por defecto, la plantilla de diseño se encuentra en `templates/@layout.latte`, que contiene: +Por defecto, la plantilla de layout se encuentra en `app/Presentation/@layout.latte` y contiene: -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... {include content} ... ``` -`{include content}` inserta un bloque llamado `content` en la plantilla principal. Puedes definirlo en las plantillas de cada vista. En este caso, editaremos el archivo `Home/default.latte` así: +La notación `{include content}` inserta en la plantilla principal un bloque llamado `content`. Lo definiremos en las plantillas de las vistas individuales (View). En nuestro caso, modificaremos el archivo `Home/default.latte` de la siguiente manera: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} Hello World {/block} ``` -Define el [bloque |latte:tags#block] *content*, que se insertará en el diseño. Si actualizas el navegador, verás una página con el texto "Hola mundo" (en el código fuente también con la cabecera y el pie HTML definidos en `@layout.latte`). +Con esto hemos definido el [bloque |latte:tags#block] *content*, que se insertará en el layout principal. Si actualizamos nuevamente el navegador, veremos una página con el texto "Hello World" (en el código fuente también con la cabecera y el pie de página HTML definidos en `@layout.latte`). -Vamos a mostrar las entradas del blog - editaremos la plantilla así: +Vamos a mostrar las entradas del blog - modificaremos la plantilla de la siguiente manera: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - <h1>My blog</h1> + <h1>Mi blog</h1> {foreach $posts as $post} <div class="post"> - <div class="date">{$post->created_at|date:'j. n. Y'}</div> + <div class="date">{$post->created_at|date:'F j, Y'}</div> <h2>{$post->title}</h2> @@ -164,42 +161,41 @@ Vamos a mostrar las entradas del blog - editaremos la plantilla así: {/block} ``` -Si actualizas tu navegador, verás la lista de entradas de tu blog. La lista no es muy elegante o colorido, así que siéntase libre de añadir un poco de [CSS brillante |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] a `www/css/style.css` y vincularlo en un diseño: +Si actualizamos el navegador, veremos una lista de todas las entradas. La lista aún no es muy bonita ni colorida, por lo que podemos agregar algunos [estilos CSS |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] al archivo `www/css/style.css` y enlazarlo en el layout: -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <link rel="stylesheet" href="{$basePath}/css/style.css"> </head> ... ``` -La etiqueta `{foreach}` itera sobre todas las entradas pasadas a la plantilla en la variable `$posts` y muestra un fragmento de código HTML para cada entrada. Igual que lo haría un código PHP. +La etiqueta `{foreach}` itera sobre todas las entradas que pasamos a la plantilla en la variable `$posts`, y para cada una renderiza la pieza de HTML dada. Se comporta exactamente como el código PHP. -Lo de `|date` se llama filtro. Los filtros se utilizan para dar formato a la salida. Este filtro en particular convierte una fecha (por ejemplo `2013-04-12`) a su forma más legible (`12. 4. 2013`). El filtro `|truncate` trunca la cadena hasta la longitud máxima especificada, y añade una elipsis al final si la cadena está truncada. Dado que se trata de una vista previa, no tiene sentido mostrar el contenido completo del artículo. Puede encontrar otros filtros por defecto [en la documentación |latte:filters] o crear los suyos propios si lo necesita. +A la notación `|date:` la llamamos filtro. Los filtros están destinados a formatear la salida. Este filtro en particular convierte la fecha (p. ej., `2013-04-12`) a su forma más legible (`April 12, 2013`). El filtro `|truncate` recorta la cadena a la longitud máxima especificada y, si la cadena se acorta, agrega puntos suspensivos al final. Dado que se trata de una vista previa, no tiene sentido mostrar todo el contenido del artículo. Otros filtros predeterminados [se encuentran en la documentación |latte:filters] o podemos crear los nuestros propios cuando sea necesario. -Una cosa más. Podemos hacer el código un poco más corto y por lo tanto más simple. Podemos reemplazar *Latte tags* por *n:attributes* así: +Una cosa más. Podemos acortar y simplificar el código anterior. Lograremos esto reemplazando las *etiquetas Latte* por *n:atributos*: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - <h1>My blog</h1> + <h1>Mi blog</h1> <div n:foreach="$posts as $post" class="post"> <div class="date">{$post->created_at|date:'F j, Y'}</div> <h2>{$post->title}</h2> - <div>{$post->content}</div> + <div>{$post->content|truncate:256}</div> </div> {/block} ``` -El `n:foreach`, simplemente envuelve el *div* con un bloque *foreach* (hace exactamente lo mismo que el bloque de código anterior). +El atributo `n:foreach` envuelve el bloque `div` con un `foreach` (funciona exactamente igual que el código anterior). -Resumen .[#toc-summary] -======================= +Resumen +======= -Tenemos una base de datos MySQL muy simple con algunas entradas de blog en ella. La aplicación se conecta a la base de datos y muestra una lista simple de los mensajes. +Ahora tenemos una base de datos MySQL muy simple con algunas entradas. La aplicación se conecta a esta base de datos y muestra una lista simple de estas entradas en la plantilla. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/es/model.texy b/quickstart/es/model.texy index 4946906423..fa9954d5d5 100644 --- a/quickstart/es/model.texy +++ b/quickstart/es/model.texy @@ -1,13 +1,13 @@ Modelo ****** -A medida que nuestra aplicación crece, pronto descubrimos que necesitamos realizar operaciones de base de datos similares en varias ubicaciones y en varios presentadores, por ejemplo, adquirir los artículos publicados más recientes. Si mejoramos nuestra aplicación añadiendo una bandera a los artículos para indicar un estado de trabajo en curso, también debemos recorrer todas las ubicaciones de nuestra aplicación y añadir una cláusula where para asegurarnos de que sólo se seleccionan los artículos terminados. +A medida que la aplicación crece, pronto descubriremos que en diferentes lugares, en diferentes presenters, necesitamos realizar operaciones similares con la base de datos. Por ejemplo, obtener los artículos publicados más recientes. Si mejoramos la aplicación, por ejemplo, agregando una marca a los artículos para indicar si están en borrador, debemos revisar todos los lugares en la aplicación donde se obtienen los artículos de la base de datos y agregar una condición `where` para seleccionar solo los artículos que no están en borrador. -En este punto, el trabajo directo con la base de datos resulta insuficiente y será más inteligente ayudarnos con una nueva función que devuelva los artículos publicados. Y cuando añadamos otra cláusula más adelante (por ejemplo para no mostrar artículos con fecha futura), sólo editaremos nuestro código en un lugar. +En ese momento, el trabajo directo con la base de datos se vuelve insuficiente y será más conveniente ayudarnos con una nueva función que nos devuelva los artículos publicados. Y si luego agregamos otra condición, por ejemplo, que no se muestren artículos con fecha futura, modificaremos el código solo en un lugar. -Colocaremos la función en la clase `PostFacade` y la llamaremos `getPublicArticles()`. +Colocaremos la función, por ejemplo, en la clase `PostFacade` y la llamaremos `getPublicArticles()`. -Crearemos nuestra clase modelo `PostFacade` en el directorio `app/Model/` para que se encargue de nuestros artículos: +En el directorio `app/Model/` crearemos nuestra clase de modelo `PostFacade`, que se encargará de los artículos: ```php .{file:app/Model/PostFacade.php} <?php @@ -32,13 +32,13 @@ final class PostFacade } ``` -En la clase pasaremos la base de datos Explorer:[api:Nette\Database\Explorer]. Esto aprovechará la potencia del [contenedor DI |dependency-injection:passing-dependencies]. +En la clase, mediante el constructor, solicitaremos que se nos pase el [api:Nette\Database\Explorer]. Aprovecharemos así la fuerza del [contenedor DI|dependency-injection:passing-dependencies]. -Pasaremos a `HomePresenter` que editaremos de forma que nos deshagamos de la dependencia de `Nette\Database\Explorer` sustituyéndola por una nueva dependencia de nuestra nueva clase. +Cambiamos a `HomePresenter`, que modificaremos eliminando la dependencia de `Nette\Database\Explorer` y reemplazándola por una nueva dependencia de nuestra nueva clase. -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use App\Model\PostFacade; use Nette; @@ -59,10 +59,9 @@ final class HomePresenter extends Nette\Application\UI\Presenter } ``` -En la sección de uso, estamos usando `App\Model\PostFacade`, por lo que podemos acortar el código PHP a `PostFacade`. Solicitamos este objeto en el constructor, lo escribimos en la propiedad `$facade` y lo usamos en el método renderDefault. +En la sección `use` tenemos `App\Model\PostFacade`, por lo que podemos acortar la notación en el código PHP a `PostFacade`. Solicitaremos este objeto en el constructor, lo escribiremos en la propiedad `$facade` y lo usaremos en el método `renderDefault`. -El último paso que queda es enseñar al contenedor DI a producir este objeto. Esto se hace normalmente añadiendo una viñeta al archivo `config/services.neon` en la sección `services`, dando el nombre completo de la clase y los parámetros del constructor. -Esto lo registra, por así decirlo, y el objeto se llama entonces **service**. Gracias a una magia llamada [autocableado |dependency-injection:autowiring], normalmente no necesitamos especificar los parámetros del constructor porque DI los reconocerá y los pasará automáticamente. Por lo tanto, bastaría con proporcionar el nombre de la clase: +Queda el último paso, que es enseñar al contenedor DI a fabricar este objeto. Esto generalmente se hace agregando una viñeta a la sección `services` en el archivo `config/services.neon`, indicando el nombre completo de la clase y los parámetros del constructor. De esta manera, lo registramos y el objeto se llama entonces **servicio**. Gracias a la magia llamada [autowiring |dependency-injection:autowiring], generalmente no necesitamos especificar los parámetros del constructor, porque DI los reconoce y los pasa automáticamente. Bastaría con indicar solo el nombre de la clase: ```neon .{file:config/services.neon} ... @@ -71,16 +70,15 @@ services: - App\Model\PostFacade ``` -Sin embargo, tampoco es necesario añadir esta línea. En la sección `search` al principio de `services.neon` se define que todas las clases que terminen en `-Facade` o `-Factory` serán buscadas por DI automáticamente, lo que también es el caso para `PostFacade`. +Sin embargo, ni siquiera necesita agregar esta línea. En la sección `search` al principio de `services.neon` se define que todas las clases que terminan con la palabra `-Facade` o `-Factory` serán encontradas automáticamente por DI, lo cual es también el caso de `PostFacade`. -Resumen .[#toc-summary] -======================= +Resumen +======= -La clase `PostFacade` pide `Nette\Database\Explorer` en un constructor y como esta clase está registrada en el contenedor DI, el contenedor crea esta instancia y la pasa. DI de esta manera nos crea una instancia de `PostFacade` y se la pasa en un constructor a la clase HomePresenter que la pidió. Una especie de muñeca Matryoshka de código :) Todos los componentes sólo piden lo que necesitan y no les importa dónde y cómo se crea. La creación es manejada por el contenedor DI. +La clase `PostFacade` solicita en su constructor que se le pase `Nette\Database\Explorer` y, como esta clase está registrada en el contenedor DI, el contenedor crea esta instancia y se la pasa. DI crea así por nosotros la instancia de `PostFacade` y la pasa en el constructor a la clase `HomePresenter`, que la solicitó. Como una muñeca rusa. :) Todos simplemente dicen lo que quieren y no se preocupan por dónde y cómo se crea qué. El contenedor DI se encarga de la creación. .[note] -Aquí puedes leer más sobre [inyección de dependencias |dependency-injection:introduction], y sobre [configuración |nette:configuring]. +Aquí puedes leer más sobre [inyección de dependencias |dependency-injection:introduction] y [configuración |nette:configuring]. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/es/single-post.texy b/quickstart/es/single-post.texy index 6a37b6f544..fec14d7c98 100644 --- a/quickstart/es/single-post.texy +++ b/quickstart/es/single-post.texy @@ -1,17 +1,17 @@ -Página individual -***************** +Página con la entrada +********************* .[perex] -Vamos a añadir otra página a nuestro blog, que mostrará el contenido de una entrada del blog en particular. +Ahora crearemos otra página del blog que mostrará una entrada específica. -Necesitamos crear un nuevo método de renderizado, que obtendrá una entrada de blog específica y la pasará a la plantilla. Tener esta vista en `HomePresenter` no es agradable porque se trata de una entrada de blog, no de la página principal. Por lo tanto, vamos a crear una nueva clase `PostPresenter` y colocarla en `app/Presenters`. Necesitará una conexión a la base de datos, por lo que pondremos el código de *inyección a la base de datos* allí de nuevo. +Necesitamos crear un nuevo método de renderizado que obtenga un artículo específico y lo pase a la plantilla. Tener este método en `HomePresenter` no es muy elegante, porque estamos hablando de un artículo y no de la página de inicio. Por lo tanto, creemos `PostPresenter` en `app/Presentation/Post/`. Este presenter también necesita conectarse a la base de datos, por lo que aquí nuevamente escribiremos un constructor que requerirá una conexión a la base de datos. -El `PostPresenter` debería verse así: +`PostPresenter` podría verse así: -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Post; use Nette; use Nette\Application\UI\Form; @@ -23,50 +23,50 @@ final class PostPresenter extends Nette\Application\UI\Presenter ) { } - public function renderShow(int $postId): void + public function renderShow(int $id): void { $this->template->post = $this->database ->table('posts') - ->get($postId); + ->get($id); } } ``` -Tenemos que establecer un namespaces correcto `App\Presenters` para nuestro presentador. Depende de la [asignación del |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7] presentador. +No debemos olvidar indicar el namespace correcto `App\Presentation\Post`, que está sujeto a la configuración del [mapeo de presenters |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7]. -El método `renderShow` requiere un argumento - el ID de la entrada a mostrar. Luego, carga la entrada desde la base de datos y pasa el resultado a la plantilla. +El método `renderShow` requiere un argumento: el ID de un artículo específico que debe mostrarse. Luego, carga este artículo de la base de datos y lo pasa a la plantilla. -En la plantilla `Home/default.latte` añadimos un enlace a la acción `Post:show`: +En la plantilla `Home/default.latte` insertamos un enlace a la acción `Post:show`. -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a href="{link Post:show $post->id}">{$post->title}</a></h2> ... ``` -La etiqueta `{link}` genera una dirección URL que apunta a la acción `Post:show`. Esta etiqueta también reenvía el ID del post como argumento. +La etiqueta `{link}` genera una dirección URL que apunta a la acción `Post:show`. También pasa el ID de la entrada como argumento. -Lo mismo podemos escribir en breve utilizando n:attribute: +Podemos escribir lo mismo de forma abreviada usando un n:atributo: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a n:href="Post:show $post->id">{$post->title}</a></h2> ... ``` -El atributo `n:href` es similar a la etiqueta `{link}`. +El atributo `n:href` es análogo a la etiqueta `{link}`. -La plantilla para la acción `Post:show` aún no existe. Podemos abrir un enlace a este post. [Tracy |tracy:] mostrará un error, por qué `Post/show.latte` no existe. Si usted ve cualquier otro informe de error que probablemente tiene que activar mod_rewrite en su servidor web. +Sin embargo, para la acción `Post:show` aún no existe una plantilla. Podemos intentar abrir el enlace a esta entrada. [Tracy |tracy:] mostrará un error porque la plantilla `Post/show.latte` aún no existe. Si ves otro mensaje de error, probablemente necesitarás habilitar `mod_rewrite` en el servidor web. -Así que vamos a crear `Post/show.latte` con este contenido: +Por lo tanto, creamos la plantilla `Post/show.latte` con este contenido: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} {block content} -<p><a n:href="Home:default">← back to posts list</a></p> +<p><a n:href="Home:default">← volver a la lista de entradas</a></p> <div class="date">{$post->created_at|date:'F j, Y'}</div> @@ -75,51 +75,50 @@ Así que vamos a crear `Post/show.latte` con este contenido: <div class="post">{$post->content}</div> ``` -Echemos un vistazo a las partes individuales. +Ahora repasemos las partes individuales de la plantilla. -La primera línea inicia la definición de un *bloque con nombre* llamado "contenido" que vimos antes. Se mostrará en una *plantilla de diseño*. Como puedes ver, falta la etiqueta final `{/block}`. Es opcional. +La primera línea comienza la definición de un bloque llamado "content", igual que en la página de inicio. Este bloque se mostrará nuevamente en la plantilla principal. Como puedes ver, falta la etiqueta de cierre `{/block}`. Es opcional. -La segunda línea proporciona un backlink a la lista de entradas del blog, para que el usuario pueda navegar sin problemas hacia adelante y hacia atrás en nuestro blog. Volvemos a utilizar el atributo `n:href`, por lo que Nette se encargará de generar la URL por nosotros. El enlace apunta a la acción `default` del presentador `Home` (también se podría escribir `n:href="Home:"`, ya que la acción `default` se puede omitir). +En la siguiente línea hay un enlace de vuelta a la lista de artículos del blog, para que el usuario pueda moverse fácilmente entre la lista de artículos y uno específico. Como usamos el atributo `n:href`, Nette se encarga automáticamente de generar los enlaces. El enlace apunta a la acción `default` del presenter `Home` (también podemos escribir `n:href="Home:"`, porque la acción llamada `default` puede omitirse, se completa automáticamente). -La tercera línea formatea la marca de tiempo de publicación con un filtro, como ya sabemos. +La tercera línea formatea la visualización de la fecha usando un filtro que ya conocemos. -La cuarta línea muestra el *título* de la entrada del blog como un `<h1>` encabezamiento. Hay una parte con la que quizá no estés familiarizado, y es `n:block="title"`. ¿Puedes adivinar lo que hace? Si has leído con atención las partes anteriores, hemos mencionado `n: attributes`. Este es otro ejemplo. Equivale a: +La cuarta línea muestra el *título* del blog en la etiqueta HTML `<h1>`. Esta etiqueta contiene un atributo que quizás no conozcas (`n:block="title"`). ¿Adivinas qué hace? Si leíste la parte anterior con atención, ya sabes que se trata de un `n:atributo`. Este es otro ejemplo de ellos, que es equivalente a: ```latte {block title}<h1>{$post->title}</h1>{/block} ``` -En palabras simples, *redefine* un bloque llamado `title`. El bloque está definido en la *plantilla de diseño* (`/app/Presenters/templates/@layout.latte:11`) y, al igual que ocurre con la sobreescritura de programación orientada a objetos, se sobreescribe aquí. Por lo tanto, la página `<title>` contendrá el título de la entrada mostrada. Hemos sobreescrito el título de la página y todo lo que necesitábamos era `n:block="title"`. Genial, ¿eh? +En pocas palabras, este bloque redefine el bloque llamado `title`. Este bloque ya está definido en la plantilla principal *layout* (`/app/Presentation/@layout.latte:11`) y, al igual que con la sobrescritura de métodos en OOP, este bloque en la plantilla principal se sobrescribe exactamente igual. Así que el `<title>` de la página ahora contiene el título de la entrada mostrada y solo necesitamos usar un simple atributo `n:block="title"`. Genial, ¿verdad? -La quinta y última línea de la plantilla muestra el contenido completo de tu post. +La quinta y última línea de la plantilla muestra todo el contenido de una entrada específica. -Comprobando el ID del post .[#toc-checking-post-id] -=================================================== +Comprobación del ID de la entrada +================================= -¿Qué pasa si alguien altera la URL e inserta `postId` que no existe? Deberíamos proporcionar al usuario un bonito error de "página no encontrada". Actualicemos el método render en `PostPresenter`: +¿Qué sucede si alguien cambia el ID en la URL e inserta un `id` inexistente? Deberíamos ofrecer al usuario un error agradable del tipo "página no encontrada". Modificaremos un poco el método de renderizado en `PostPresenter`: -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { - $this->error('Post not found'); + $this->error('Página no encontrada'); } $this->template->post = $post; } ``` -Si la entrada no puede ser encontrada, llamando a `$this->error(...)` se mostrará una página 404 con un mensaje agradable y comprensible. Ten en cuenta que en tu entorno de desarrollo (en tu portátil), no verás la página de error. En su lugar, Tracy mostrará la excepción con todos los detalles, lo cual es bastante conveniente para el desarrollo. Puede comprobar ambos modos, simplemente cambie el valor pasado a `setDebugMode` en `Bootstrap.php`. +Si no se puede encontrar la entrada, al llamar a `$this->error(...)` mostraremos una página de error 404 con un mensaje comprensible. Ten en cuenta que en el modo de desarrollo (localhost) no verás esta página de error. En su lugar, se mostrará Tracy con detalles sobre la excepción, lo cual es bastante conveniente para el desarrollo. Si queremos que se muestren ambos modos, simplemente cambia el argumento del método `setDebugMode` en el archivo `Bootstrap.php`. -Resumen .[#toc-summary] -======================= +Resumen +======= -Tenemos una base de datos con entradas de blog y una aplicación web con dos vistas - la primera muestra el resumen de todas las entradas recientes y la segunda muestra una entrada específica. +Tenemos una base de datos con entradas y una aplicación web que tiene dos vistas: la primera muestra un resumen de todas las entradas y la segunda muestra una entrada específica. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/files/qs-tracy.avif b/quickstart/files/qs-tracy.avif new file mode 100644 index 0000000000..ad44340733 Binary files /dev/null and b/quickstart/files/qs-tracy.avif differ diff --git a/quickstart/files/qs-tracy.webp b/quickstart/files/qs-tracy.webp deleted file mode 100644 index bd5f3cf757..0000000000 Binary files a/quickstart/files/qs-tracy.webp and /dev/null differ diff --git a/quickstart/fr/@home.texy b/quickstart/fr/@home.texy index c4bb8cb12a..f6ac65cf15 100644 --- a/quickstart/fr/@home.texy +++ b/quickstart/fr/@home.texy @@ -1,119 +1,119 @@ -Créez votre première application ! -********************************** +Écrivons notre première application ! +************************************* .[perex] -Apprenez à connaître Nette Framework tout en créant un simple blog avec des commentaires. Commençons ! +Découvrons ensemble Nette Framework en créant un blog simple avec des commentaires. Allons-y ! -Après les deux premiers chapitres, vous aurez votre propre blog fonctionnel et vous serez prêt à publier vos superbes articles, bien que les fonctionnalités soient assez limitées après avoir terminé ces deux chapitres. Pour rendre les choses plus agréables pour vos utilisateurs, vous devriez également lire les chapitres suivants et continuer à améliorer votre application. +Dès les deux premiers chapitres, nous aurons notre propre blog fonctionnel et pourrons publier nos superbes articles, bien que les fonctionnalités soient encore largement limitées. Vous devriez également lire les chapitres suivants, où nous programmerons l'ajout de commentaires, l'édition d'articles et enfin sécuriserons le blog. .[tip] -Ce tutoriel suppose que vous avez terminé le document d'[installation |nette:installation] et que vous avez configuré votre outil avec succès. +Ce tutoriel suppose que vous avez lu la page [Installation |nette:installation] et préparé avec succès les outils nécessaires. Il suppose également que vous comprenez la [programmation orientée objet en PHP |nette:introduction-to-object-oriented-programming]. -Veuillez utiliser PHP 8.0 ou plus récent. Vous pouvez trouver l'application complète [sur GitHub |https://github.com/nette-examples/quickstart/tree/v4.0]. +Veuillez utiliser PHP 8.1 ou une version plus récente. L'application complète se trouve [sur GitHub |https://github.com/nette-examples/quickstart/tree/v4.0]. -La page d'accueil .[#toc-the-welcome-page] -========================================== +Page d'accueil +============== -Commençons par créer un nouveau projet dans le répertoire `nette-blog`: +Commençons par créer un nouveau projet dans le répertoire `nette-blog` : ```shell composer create-project nette/web-project nette-blog ``` -La page d'accueil du projet Web devrait alors s'afficher. Essayez-la en ouvrant votre navigateur et en allant à l'URL suivante : +À ce stade, la page d'accueil du Web Project devrait déjà fonctionner. Essayons en ouvrant le navigateur à l'URL suivante : ``` http://localhost/nette-blog/www/ ``` -et vous devriez voir la page de bienvenue de Nette Framework : +et nous verrons la page d'accueil de Nette Framework : [* qs-welcome.webp .{url: http://localhost/nette-blog/www/} *] -L'application fonctionne et vous pouvez maintenant commencer à y apporter des modifications. +L'application fonctionne et vous pouvez commencer à faire des modifications. .[note] -Si vous rencontrez un problème, [essayez ces quelques conseils |nette:troubleshooting#Nette Is Not Working, White Page Is Displayed]. +Si un problème survient, [essayez ces quelques conseils |nette:troubleshooting#Nette ne fonctionne pas une page blanche s affiche]. -Contenu du projet Web .[#toc-web-project-s-content] -=================================================== +Contenu du Web Project +====================== -Le projet Web a la structure suivante : +Le Web Project a la structure suivante : /--pre <b>nette-blog/</b> -├── <b>app/</b> ← répertoire des applications -│ ├── <b>Presenters/</b> ← classes de présentateurs -│ │ └── <b>templates/</b>← modèles -│ ├── <b>Router/</b> ← configuration des adresses URL +├── <b>app/</b> ← répertoire de l'application +│ ├── <b>Core/</b> ← classes de base nécessaires au fonctionnement +│ ├── <b>Presentation/</b> ← presenters, templates & co. +│ │ └── <b>Home/</b> ← répertoire du presenter Home │ └── <b>Bootstrap.php</b> ← classe de démarrage Bootstrap -├── <b>bin/</b> ← scripts pour la ligne de commande -├── <b>config/</b> ← les fichiers de configuration -├── <b>log/</b> ← journaux d'erreurs +├── <b>assets/</b> ← actifs bruts (SCSS, TypeScript, images sources) +├── <b>bin/</b> ← scripts exécutés depuis la ligne de commande +├── <b>config/</b> ← fichiers de configuration +├── <b>log/</b> ← journalisation des erreurs ├── <b>temp/</b> ← fichiers temporaires, cache, … ├── <b>vendor/</b> ← bibliothèques installées par Composer -│ └── <b>autoload.php</b> ← autoloading des bibliothèques installées par Composer -└── <b>www/</b> ← dossier public - le seul endroit accessible depuis le navigateur - └── <b>index.php</b> ← fichier initial qui lance l'application +│ └── <b>autoload.php</b> ← autoloading de tous les paquets installés +└── <b>www/</b> ← répertoire public - le seul accessible depuis le navigateur + ├── <b>assets/</b> ← fichiers statiques compilés (CSS, JS, images, ...) + └── <b>index.php</b> ← fichier initial par lequel l'application démarre \-- -Le répertoire `www` est censé stocker les images, JavaScript, CSS, et autres fichiers accessibles au public. C'est le seul répertoire directement accessible depuis le navigateur, vous pouvez donc faire pointer le répertoire racine de votre serveur web ici (vous pouvez le configurer dans Apache, mais faisons-le plus tard car ce n'est pas important pour le moment). +Le répertoire `www/` est destiné au stockage des images, des fichiers JavaScript, des styles CSS et d'autres fichiers accessibles au public. Seul ce répertoire est accessible depuis Internet, alors configurez le répertoire racine de votre application pour qu'il pointe ici (vous pouvez le configurer dans la configuration d'Apache ou de nginx, mais faisons cela plus tard, ce n'est pas important pour l'instant). -Le répertoire le plus important pour vous est `app/`. Vous y trouverez le fichier `Bootstrap.php`, dans lequel se trouve une classe qui charge le framework et configure l'application. Elle active l'[autoloading |robot-loader:] et configure le [débogueur |tracy:] et les [routes |application:routing]. +Le dossier le plus important pour nous est `app/`. Nous y trouvons le fichier `Bootstrap.php`, qui contient la classe servant à charger l'ensemble du framework et à configurer l'application. L'[autoloading |robot-loader:] y est activé, le [debugger |tracy:] et les [routes |application:routing] y sont configurés. -Nettoyage de .[#toc-cleanup] -============================ +Nettoyage +========= -Le projet Web contient une page de bienvenue, que nous pouvons supprimer - n'hésitez pas à supprimer le fichier `app/Presenters/templates/Home/default.latte` et à le remplacer par le texte "Hello world !". +Le Web Project contient une page d'accueil que nous supprimerons avant de commencer à programmer quoi que ce soit. Remplacez donc sans crainte le contenu du fichier `app/Presentation/Home/default.latte` par "Hello world!". [* qs-hello.webp .{url:-} *] -Tracy (Débogueur) .[#toc-tracy-debugger] -======================================== +Tracy (debugger) +================ -Un outil extrêmement important pour le développement est [un débogueur appelé Tracy |tracy:]. Essayez de faire quelques erreurs dans votre fichier `app/Presenters/HomePresenter.php` (par exemple, supprimez une accolade de la définition de la classe HomePresenter) et voyez ce qui se passe. Une page d'écran rouge apparaîtra avec une description compréhensible de l'erreur. +Un outil extrêmement important pour le développement est [l'outil de débogage Tracy |tracy:]. Essayez de provoquer une erreur dans le fichier `app/Presentation/Home/HomePresenter.php` (par exemple, en supprimant une accolade dans la définition de la classe `HomePresenter`) et voyez ce qui se passe. Une page de notification apparaîtra, décrivant l'erreur de manière compréhensible. -[* qs-tracy.webp .{url:-}(debugger screen) *] +[* qs-tracy.avif .{url:-}(écran du débogueur) *] -Tracy vous aidera considérablement dans la recherche des erreurs. Notez également la barre Tracy flottante dans le coin inférieur droit, qui vous informe sur les données d'exécution importantes. +Tracy nous aidera énormément lorsque nous chercherons des erreurs dans l'application. Remarquez également la barre Tracy flottante dans le coin inférieur droit de l'écran, qui contient des informations sur l'exécution de l'application. [* qs-tracybar.webp .{url:-} *] -En mode production, Tracy est, bien entendu, désactivé et ne révèle aucune information sensible. Toutes les erreurs sont enregistrées dans le répertoire `log/` à la place. Essayez-le. Dans `app/Bootstrap.php`, trouvez le morceau de code suivant, décommentez la ligne et changez le paramètre d'appel de méthode en `false`, de sorte que cela ressemble à ceci : +En mode production, Tracy est bien sûr désactivé et n'affiche aucune information sensible. Toutes les erreurs sont dans ce cas stockées dans le dossier `log/`. Essayons cela. Dans le fichier `app/Bootstrap.php`, décommentez la ligne suivante et changez le paramètre de l'appel à `false`, pour que le code ressemble à ceci : ```php .{file:app/Bootstrap.php} ... -$configurator->setDebugMode(false); -$configurator->enableTracy(__DIR__ . '/../log'); +$this->configurator->setDebugMode(false); ... ``` -Après avoir rafraîchi la page Web, la page d'écran rouge sera remplacée par le message convivial : +Après avoir rechargé la page, nous ne verrons plus Tracy. À la place, un message convivial s'affichera : -[* qs-fatal.webp .{url:-}(error screen) *] +[* qs-fatal.webp .{url:-}(écran d'erreur) *] -Maintenant, regardez dans le répertoire `log/`. Vous y trouverez le journal des erreurs (dans le fichier exception.log) ainsi que la page contenant le message d'erreur (enregistrée dans un fichier HTML dont le nom commence par `exception`). +Maintenant, regardons dans le répertoire `log/`. Ici (dans le fichier `exception.log`), nous trouverons l'erreur journalisée ainsi que la page déjà connue avec le message d'erreur (enregistrée sous forme de fichier HTML avec un nom commençant par `exception-`). -Commentez à nouveau la ligne `// $configurator->setDebugMode(false);`. Tracy active automatiquement le mode de développement sur l'environnement `localhost` et le désactive ailleurs. +Commentez à nouveau la ligne `$configurator->setDebugMode(false);`. Tracy active automatiquement le mode développeur sur localhost et le désactive partout ailleurs. -Maintenant, nous pouvons corriger le bogue et continuer à concevoir notre application. +Nous pouvons corriger l'erreur que nous avons créée et continuer à écrire l'application. -Envoyer des remerciements .[#toc-send-thanks] -============================================= +Envoyez un remerciement +======================= -Nous allons vous montrer une astuce qui fera le bonheur des auteurs de logiciels libres. Vous pouvez facilement donner une étoile sur GitHub aux bibliothèques que votre projet utilise. Il suffit de l'exécuter : +Nous allons vous montrer une astuce qui fera plaisir aux auteurs open source. Vous pouvez facilement donner une étoile sur GitHub aux bibliothèques que votre projet utilise. Il suffit d'exécuter : ```shell composer thanks ``` -Essayez-le ! +Essayez ! {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/fr/@left-menu.texy b/quickstart/fr/@left-menu.texy index 25d2059785..40532872f6 100644 --- a/quickstart/fr/@left-menu.texy +++ b/quickstart/fr/@left-menu.texy @@ -1,9 +1,9 @@ Tutoriel ******** -- [Créez votre première application ! |@home] +- [Écrivons notre première application ! |@home] - [Page d'accueil du blog |home-page] -- [Page d'un seul article |single-post] +- [Page d'un article |single-post] - [Commentaires |comments] -- [Créer et modifier des articles |creating-posts] +- [Création et édition d'articles |creating-posts] - [Modèle |Model] - [Authentification |authentication] diff --git a/quickstart/fr/@meta.texy b/quickstart/fr/@meta.texy new file mode 100644 index 0000000000..dd7a9d1f02 --- /dev/null +++ b/quickstart/fr/@meta.texy @@ -0,0 +1 @@ +{{sitename: Écrivons notre première application !}} diff --git a/quickstart/fr/authentication.texy b/quickstart/fr/authentication.texy index 531b193477..3fa6872585 100644 --- a/quickstart/fr/authentication.texy +++ b/quickstart/fr/authentication.texy @@ -1,36 +1,36 @@ Authentification **************** -Nette vous fournit des lignes directrices sur la façon de programmer l'authentification sur votre page, mais il ne vous oblige pas à le faire d'une manière particulière. L'implémentation dépend de vous. Nette a une interface `Nette\Security\Authenticator` qui vous oblige à implémenter une seule méthode appelée `authenticate`, qui trouve l'utilisateur comme vous le souhaitez. +Nette fournit un moyen de programmer l'authentification sur nos pages, mais ne nous impose rien. L'implémentation dépend entièrement de nous. Nette contient l'interface `Nette\Security\Authenticator`, qui ne nécessite qu'une seule méthode `authenticate`, qui vérifie l'utilisateur de la manière que nous souhaitons. -Il existe de nombreuses façons pour un utilisateur de s'authentifier. La méthode la plus courante est l'authentification par mot de passe (l'utilisateur fournit son nom ou son adresse électronique et un mot de passe), mais il existe d'autres moyens. Vous connaissez peut-être les boutons "Connexion avec Facebook" sur de nombreux sites web, ou la connexion via Google/Twitter/GitHub ou tout autre site. Avec Nette, vous pouvez avoir la méthode d'authentification que vous voulez, ou vous pouvez les combiner. C'est vous qui décidez. +Il existe de nombreuses possibilités pour vérifier un utilisateur. La méthode d'authentification la plus courante est par mot de passe (l'utilisateur fournit son nom ou son e-mail et son mot de passe), mais il existe d'autres moyens. Vous connaissez peut-être les boutons "Se connecter avec Facebook", ou la connexion via Google/Twitter/GitHub sur certains sites. Avec Nette, nous pouvons avoir n'importe quelle méthode de connexion, ou nous pouvons même les combiner. C'est à nous de décider. -Normalement, vous devriez écrire votre propre authentificateur, mais pour ce petit blog simple, nous utiliserons l'authentificateur intégré, qui authentifie sur la base d'un mot de passe et d'un nom d'utilisateur stockés dans un fichier de configuration. C'est une bonne solution pour les tests. Nous allons donc ajouter la section *security* suivante au fichier de configuration de `config/common.neon`: +Normalement, nous écririons notre propre authentificateur, mais pour ce petit blog simple, nous utiliserons l'authentificateur intégré, qui se connecte sur la base du mot de passe et du nom d'utilisateur stockés dans le fichier de configuration. Il est utile à des fins de test. Ajoutons donc la section `security` suivante au fichier de configuration `config/common.neon`: ```neon .{file:config/common.neon} security: users: - admin: secret # user 'admin', password 'secret' + admin: secret # utilisateur 'admin', mot de passe 'secret' ``` -Nette va automatiquement créer un service dans le conteneur DI. +Nette créera automatiquement un service dans le conteneur DI. -Formulaire d'inscription .[#toc-sign-in-form] -============================================= +Formulaire de connexion +======================= -Nous avons maintenant la partie backend de l'authentification prête et nous devons fournir une interface utilisateur, par laquelle l'utilisateur se connectera. Créons un nouveau présentateur appelé *SignPresenter*, qui va +Maintenant que l'authentification est prête, nous devons préparer l'interface utilisateur pour la connexion. Créons donc un nouveau presenter nommé `SignPresenter`, qui : -- afficher un formulaire de connexion (demandant le nom d'utilisateur et le mot de passe) -- authentifier l'utilisateur lorsque le formulaire est soumis -- fournir une action de déconnexion +- affichera le formulaire de connexion (avec nom d'utilisateur et mot de passe) +- après l'envoi du formulaire, vérifiera l'utilisateur +- fournira la possibilité de se déconnecter -Commençons par le formulaire de connexion. Vous savez déjà comment fonctionnent les formulaires dans un présentateur. Créez le site `SignPresenter` et la méthode `createComponentSignInForm`. Cela devrait ressembler à ceci : +Commençons par le formulaire de connexion. Nous savons déjà comment fonctionnent les formulaires dans les presenters. Créons donc le presenter `SignPresenter` et écrivons la méthode `createComponentSignInForm`. Il devrait ressembler à quelque chose comme ça : -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Sign; use Nette; use Nette\Application\UI\Form; @@ -40,69 +40,69 @@ final class SignPresenter extends Nette\Application\UI\Presenter protected function createComponentSignInForm(): Form { $form = new Form; - $form->addText('username', 'Username:') - ->setRequired('Please enter your username.'); + $form->addText('username', 'Nom d\'utilisateur :') + ->setRequired('Veuillez entrer votre nom d\'utilisateur.'); - $form->addPassword('password', 'Password:') - ->setRequired('Please enter your password.'); + $form->addPassword('password', 'Mot de passe :') + ->setRequired('Veuillez entrer votre mot de passe.'); - $form->addSubmit('send', 'Sign in'); + $form->addSubmit('send', 'Se connecter'); - $form->onSuccess[] = [$this, 'signInFormSucceeded']; + $form->onSuccess[] = $this->signInFormSucceeded(...); return $form; } } ``` -Il y a une entrée pour le nom d'utilisateur et le mot de passe. +Il y a des champs pour le nom d'utilisateur et le mot de passe. -Modèle .[#toc-template] ------------------------ +Template +-------- -Le formulaire sera rendu dans le modèle `in.latte` +Le formulaire sera rendu dans le template `in.latte`: -```latte .{file:app/Presenters/templates/Sign/in.latte} +```latte .{file:app/Presentation/Sign/in.latte} {block content} -<h1 n:block=title>Sign in</h1> +<h1 n:block=title>Connexion</h1> {control signInForm} ``` -Gestionnaire de connexion .[#toc-login-handler] ------------------------------------------------ +Callback de connexion +--------------------- -Nous ajoutons également un *manipulateur de formulaire* pour la connexion de l'utilisateur, qui est invoqué juste après la soumission du formulaire. +Ensuite, ajoutons le callback pour la connexion de l'utilisateur, qui sera appelé juste après l'envoi réussi du formulaire. -Le gestionnaire prendra simplement le nom d'utilisateur et le mot de passe que l'utilisateur a entré et les passera à l'authentificateur défini plus tôt. Après que l'utilisateur se soit connecté, nous le redirigerons vers la page d'accueil. +Le callback récupère simplement le nom d'utilisateur et le mot de passe que l'utilisateur a remplis et les transmet à l'authentificateur. Après la connexion, nous redirigeons vers la page d'accueil. -```php .{file:app/Presenters/SignPresenter.php} -public function signInFormSucceeded(Form $form, \stdClass $data): void +```php .{file:app/Presentation/Sign/SignPresenter.php} +private function signInFormSucceeded(Form $form, \stdClass $data): void { try { $this->getUser()->login($data->username, $data->password); $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { - $form->addError('Incorrect username or password.'); + $form->addError('Nom d\'utilisateur ou mot de passe incorrect.'); } } ``` -La méthode [User::login() |api:Nette\Security\User::login()] doit lever une exception lorsque le nom d'utilisateur ou le mot de passe ne correspond pas à ceux que nous avons définis précédemment. Comme nous le savons déjà, cela entraînerait un écran rouge de [Tracy |tracy:] ou, en mode production, un message informant d'une erreur interne du serveur. Nous n'aimerions pas cela. C'est pourquoi nous attrapons l'exception et ajoutons un message d'erreur sympathique au formulaire. +La méthode [User::login() |api:Nette\Security\User::login()] lèvera une exception si le nom d'utilisateur et le mot de passe ne correspondent pas aux informations du fichier de configuration. Comme nous le savons déjà, cela peut entraîner une page d'erreur rouge, ou en mode production, un message informant d'une erreur serveur. Nous ne voulons pas cela. C'est pourquoi nous attrapons cette exception et transmettons un message d'erreur agréable et convivial au formulaire. -Lorsqu'une erreur se produit dans le formulaire, la page contenant le formulaire est à nouveau affichée et, au-dessus du formulaire, un message sympathique informe l'utilisateur qu'il a saisi un nom d'utilisateur ou un mot de passe incorrect. +Dès qu'une erreur se produit dans le formulaire, la page avec le formulaire est redessinée et un message agréable s'affiche au-dessus du formulaire informant l'utilisateur qu'il a saisi un nom d'utilisateur ou un mot de passe incorrect. -Sécurité des présentateurs .[#toc-security-of-presenters] -========================================================= +Sécurisation des presenters +=========================== -Nous allons sécuriser un formulaire pour l'ajout et la modification des messages. Il est défini dans le présentateur `EditPresenter`. L'objectif est d'empêcher les utilisateurs qui ne sont pas connectés d'accéder à la page. +Sécurisons le formulaire pour ajouter et modifier des articles. Il est défini dans le presenter `EditPresenter`. L'objectif est d'empêcher l'accès à la page aux utilisateurs qui ne sont pas connectés. -Nous créons une méthode `startup()` qui est lancée immédiatement au début du [cycle de vie du présentateur |application:presenters#life-cycle-of-presenter]. Cette méthode redirige les utilisateurs non connectés vers le formulaire de connexion. +Nous créerons une méthode `startup()`, qui s'exécute immédiatement au début du [cycle de vie du presenter |application:presenters#Cycle de vie du presenter]. Elle redirigera les utilisateurs non connectés vers le formulaire de connexion. -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} public function startup(): void { parent::startup(); @@ -114,67 +114,66 @@ public function startup(): void ``` -Cacher les liens .[#toc-hide-links] ------------------------------------ +Masquer les liens +----------------- -Un utilisateur non authentifié ne peut plus voir les pages *créer* et *modifier*, mais il peut toujours voir les liens qui y mènent. Cachons-les également. L'un de ces liens se trouve sur `app/Presenters/templates/Home/default.latte`, et il ne doit être visible que si l'utilisateur est connecté. +Un utilisateur non autorisé ne peut plus voir les pages *create* ou *edit*, mais il peut toujours voir les liens vers elles. Nous devrions également les masquer. Un tel lien se trouve dans le template `app/Presentation/Home/default.latte` et ne devrait être visible que par les utilisateurs connectés. -Nous pouvons le masquer en utilisant *n:attribut* appelé `n:if`. Si la déclaration qu'il contient est `false`, l'ensemble de la balise `<a>` et son contenu ne seront pas affichés : +Nous pouvons le masquer en utilisant un *n:attribut* nommé `n:if`. Si cette condition est `false`, toute la balise `<a>`, y compris son contenu, restera cachée. ```latte -<a n:href="Edit:create" n:if="$user->isLoggedIn()">Create post</a> +<a n:href="Edit:create" n:if="$user->isLoggedIn()">Créer un article</a> ``` -ceci est un raccourci pour (ne pas confondre avec `tag-if`) : +ce qui est un raccourci pour la notation suivante (à ne pas confondre avec `tag-if`) : ```latte -{if $user->isLoggedIn()}<a n:href="Edit:create">Create post</a>{/if} +{if $user->isLoggedIn()}<a n:href="Edit:create">Créer un article</a>{/if} ``` -Vous devez masquer le lien d'édition situé dans `app/Presenters/templates/Post/show.latte` de manière similaire. +De la même manière, nous masquerons également le lien dans le template `app/Presentation/Post/show.latte`. -Lien vers le formulaire de connexion .[#toc-login-form-link] -============================================================ +Lien de connexion +================= -Hé, mais comment accéder à la page de connexion ? Il n'y a pas de lien qui pointe vers elle. Ajoutons-en un dans le fichier modèle `@layout.latte`. Essayez de trouver un endroit sympa, ça peut être n'importe quel endroit qui vous plaît le plus. +Comment accédons-nous réellement à la page de connexion ? Il n'y a aucun lien qui y mène. Ajoutons-le donc au template `@layout.latte`. Essayez de trouver un endroit approprié - cela peut être presque n'importe où. -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <ul class="navig"> - <li><a n:href="Home:">Home</a></li> + <li><a n:href="Home:">Articles</a></li> {if $user->isLoggedIn()} - <li><a n:href="Sign:out">Sign out</a></li> + <li><a n:href="Sign:out">Se déconnecter</a></li> {else} - <li><a n:href="Sign:in">Sign in</a></li> + <li><a n:href="Sign:in">Se connecter</a></li> {/if} </ul> ... ``` -Si l'utilisateur n'est pas encore connecté, nous afficherons le lien "Sign in". Sinon, nous affichons le lien "Sign out". Nous ajoutons cette action dans SignPresenter. +Si l'utilisateur n'est pas connecté, le lien "Se connecter" s'affiche. Sinon, le lien "Se déconnecter" s'affiche. Ajoutons également cette action au `SignPresenter`. -L'action de déconnexion ressemble à ceci, et parce que nous redirigeons l'utilisateur immédiatement, il n'y a pas besoin d'un modèle de vue. +Comme nous redirigeons immédiatement l'utilisateur après la déconnexion, aucun template n'est nécessaire. La déconnexion ressemble à ceci : -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} public function actionOut(): void { $this->getUser()->logout(); - $this->flashMessage('You have been signed out.'); + $this->flashMessage('Déconnexion réussie.'); $this->redirect('Home:'); } ``` -Elle appelle simplement la méthode `logout()` et affiche ensuite un message sympathique à l'utilisateur. +Seule la méthode `logout()` est appelée, puis un message agréable confirmant la déconnexion réussie s'affiche. -Résumé .[#toc-summary] -====================== +Résumé +====== -Nous avons un lien pour se connecter et aussi pour déconnecter l'utilisateur. Nous avons utilisé l'authentificateur intégré pour l'authentification et les détails de connexion sont dans le fichier de configuration car il s'agit d'une simple application de test. Nous avons également sécurisé les formulaires d'édition afin que seuls les utilisateurs connectés puissent ajouter et modifier des articles. +Nous avons un lien pour la connexion et aussi pour la déconnexion de l'utilisateur. Pour l'authentification, nous avons utilisé l'authentificateur intégré et les informations de connexion se trouvent dans le fichier de configuration, car il s'agit d'une simple application de test. Nous avons également sécurisé les formulaires d'édition, de sorte que seuls les utilisateurs connectés peuvent ajouter et modifier des articles. .[note] -Ici vous pouvez lire plus sur la [connexion |security:authentication] et l'[autorisation des |security:authorization] [utilisateurs |security:authentication]. +Ici, vous pouvez en lire plus sur la [connexion des utilisateurs |security:authentication] et la [Vérification des permissions |security:authorization]. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/fr/comments.texy b/quickstart/fr/comments.texy index 1b7305f7b1..2aa15e1230 100644 --- a/quickstart/fr/comments.texy +++ b/quickstart/fr/comments.texy @@ -1,28 +1,28 @@ Commentaires ************ -Le blog a été déployé, nous avons écrit de très bons articles de blog et les avons publiés via Adminer. Les gens lisent le blog et sont très passionnés par nos idées. Nous recevons chaque jour de nombreux courriels élogieux. Mais à quoi servent tous ces éloges si nous ne les recevons que dans le courrier électronique, de sorte que personne d'autre ne peut les lire ? Ne serait-il pas préférable que les gens puissent commenter directement sur le blog afin que tout le monde puisse lire à quel point nous sommes géniaux ? +Nous avons mis en ligne le blog sur le serveur web et publié quelques articles très intéressants en utilisant Adminer. Les gens lisent notre blog et en sont très enthousiastes. Nous recevons chaque jour de nombreux e-mails de compliments. Mais à quoi servent tous ces éloges si nous ne les avons que dans nos e-mails et que personne ne peut les lire ? Il serait préférable que le lecteur puisse commenter directement l'article, afin que tout le monde puisse lire à quel point nous sommes géniaux. -Rendons tous les articles commentables. +Programmons donc les commentaires. -Création d'un nouveau tableau .[#toc-creating-a-new-table] -========================================================== +Création d'une nouvelle table +============================= -Lancez à nouveau Adminer et créez une nouvelle table nommée `comments` avec ces colonnes : +Lançons Adminer et créons une table `comments` avec les colonnes suivantes : -- `id` int, vérifier l'auto-incrément (AI) -- `post_id`, une clé étrangère qui fait référence à la table `posts` +- `id` int, cochez autoincrement (AI) +- `post_id`, clé étrangère qui référence la table `posts` - `name` varchar, longueur 255 - `email` varchar, longueur 255 -- `content` texte +- `content` text - `created_at` timestamp -Cela devrait ressembler à ceci : +La table devrait donc ressembler à quelque chose comme ça : [* adminer-comments.webp *] -N'oubliez pas d'utiliser le stockage de table InnoDB et cliquez sur Enregistrer. +N'oubliez pas d'utiliser à nouveau le stockage InnoDB. ```sql CREATE TABLE `comments` ( @@ -37,104 +37,104 @@ CREATE TABLE `comments` ( ``` -Formulaire de commentaire .[#toc-form-for-commenting] -===================================================== +Formulaire de commentaire +========================= -Tout d'abord, nous devons créer un formulaire qui permettra aux utilisateurs de commenter notre page. Le Nette Framework offre un support impressionnant pour les formulaires. Ils peuvent être configurés dans un présentateur et rendus dans un modèle. +Tout d'abord, nous devons créer un formulaire qui permettra aux utilisateurs de commenter les articles. Le Nette Framework a un support incroyable pour les formulaires. Nous pouvons les configurer dans le presenter et les afficher dans le template. -Nette Framework a un concept de *composants*. Un **composant** est une classe réutilisable ou un morceau de code, qui peut être attaché à un autre composant. Même un présentateur est un composant. Chaque composant est créé à l'aide de la fabrique de composants. Définissons donc la fabrique du formulaire de commentaires dans `PostPresenter`. +Le Nette Framework utilise le concept de *composants*. Un **composant** est une classe réutilisable, ou une partie de code, qui peut être attachée à un autre composant. Même le presenter est un composant. Chaque composant est créé via une factory. Créons donc une factory pour le formulaire de commentaire dans le presenter `PostPresenter`. -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} protected function createComponentCommentForm(): Form { $form = new Form; // signifie Nette\Application\UI\Form - $form->addText('name', 'Your name:') + $form->addText('name', 'Nom :') ->setRequired(); - $form->addEmail('email', 'Email:'); + $form->addEmail('email', 'E-mail :'); - $form->addTextArea('content', 'Comment:') + $form->addTextArea('content', 'Commentaire :') ->setRequired(); - $form->addSubmit('send', 'Publish comment'); + $form->addSubmit('send', 'Publier le commentaire'); return $form; } ``` -Nous allons l'expliquer un peu. La première ligne crée une nouvelle instance du composant `Form`. Les méthodes suivantes permettent d'attacher des entrées HTML à la définition du formulaire. `->addText` sera rendu en tant que `<input type=text name=name>`, avec `<label>Your name:</label>`. Comme vous l'avez sans doute déjà deviné, le composant `->addTextArea` attache une balise `<textarea>` et `->addSubmit` ajoute un `<input type=submit>`. Il existe d'autres méthodes de ce type, mais c'est tout ce que vous devez savoir pour le moment. Vous pouvez en [apprendre davantage dans la documentation |forms:]. +Expliquons cela un peu plus. La première ligne crée une nouvelle instance du composant `Form`. Les méthodes suivantes attachent des entrées HTML à la définition de ce formulaire. `->addText` sera rendu comme `<input type="text" name="name">` avec `<label>Nom :</label>`. Comme vous l'avez probablement deviné, `->addTextArea` sera rendu comme `<textarea>` et `->addSubmit` comme `<input type="submit">`. Il existe de nombreuses autres méthodes similaires, mais celles-ci suffisent pour ce formulaire. Vous pouvez en [lire plus dans la documentation|forms:]. -Une fois le composant de formulaire défini dans un présentateur, nous pouvons le rendre (l'afficher) dans un modèle. Pour ce faire, placez la balise `{control}` à la fin du modèle de détail de l'article, dans `Post/show.latte`. Comme le nom du composant est `commentForm` (dérivé du nom de la méthode `createComponentCommentForm`), la balise ressemblera à ceci +Si le formulaire est déjà défini dans le presenter, nous pouvons le rendre (l'afficher) dans le template. Pour ce faire, placez la balise `{control}` à la fin du template qui affiche un article spécifique, dans `Post/show.latte`. Comme le composant s'appelle `commentForm` (le nom est dérivé du nom de la méthode `createComponentCommentForm`), la balise ressemblera à ceci : -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... -<h2>Post new comment</h2> +<h2>Ajouter un nouveau commentaire</h2> {control commentForm} ``` -Maintenant, si vous consultez les détails d'un article, vous trouverez un nouveau formulaire pour poster des commentaires. +Maintenant, si vous affichez la page de détail de l'article, vous verrez le nouveau formulaire de commentaire à la fin. -Sauvegarde dans la base de données .[#toc-saving-to-database] -============================================================= +Sauvegarde dans la base de données +================================== -Avez-vous essayé de soumettre des données ? Vous avez peut-être remarqué que le formulaire n'effectue aucune action. Il est juste là, à l'air cool et ne fait rien. Nous devons attacher une méthode de rappel à ce formulaire, qui enregistrera les données soumises. +Avez-vous déjà essayé de remplir et de soumettre le formulaire ? Vous avez probablement remarqué que le formulaire ne fait rien. Nous devons attacher une méthode de rappel qui enregistrera les données soumises. -Placez la ligne suivante avant la ligne `return` dans la fabrique du composant `commentForm`: +Sur la ligne avant `return` dans la factory du composant `commentForm`, placez la ligne suivante : -```php -$form->onSuccess[] = [$this, 'commentFormSucceeded']; +```php .{file:app/Presentation/Post/PostPresenter.php} +$form->onSuccess[] = $this->commentFormSucceeded(...); ``` -Elle signifie "après que le formulaire ait été soumis avec succès, appelez la méthode `commentFormSucceeded` du présentateur actuel". Cette méthode n'existe pas encore, alors créons-la. +L'écriture précédente signifie "après la soumission réussie du formulaire, appelez la méthode `commentFormSucceeded` du presenter actuel". Cependant, cette méthode n'existe pas encore. Créons-la : -```php .{file:app/Presenters/PostPresenter.php} -public function commentFormSucceeded(\stdClass $data): void +```php .{file:app/Presentation/Post/PostPresenter.php} +private function commentFormSucceeded(\stdClass $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); $this->database->table('comments')->insert([ - 'post_id' => $postId, + 'post_id' => $id, 'name' => $data->name, 'email' => $data->email, 'content' => $data->content, ]); - $this->flashMessage('Thank you for your comment', 'success'); + $this->flashMessage('Merci pour votre commentaire', 'success'); $this->redirect('this'); } ``` -Vous devez la placer juste après la fabrique du composant `commentForm`. +Nous plaçons cette méthode juste après la factory du formulaire `commentForm`. -La nouvelle méthode a un argument qui est l'instance du formulaire soumis, créé par la fabrique de composants. Nous recevons les valeurs soumises dans `$data`. Et ensuite nous insérons les données dans la table de la base de données `comments`. +Cette nouvelle méthode a un argument, `$data`, qui est un objet contenant les données soumises du formulaire. Nous obtenons l'ID de l'article à partir des paramètres du presenter. Ensuite, nous insérons les données dans la table `comments` de la base de données. -Il y a deux autres appels de méthode à expliquer. La redirection redirige littéralement vers la page actuelle. Vous devez le faire à chaque fois que le formulaire est soumis, valide et que l'opération de rappel a fait ce qu'elle devait faire. En outre, lorsque vous redirigez la page après avoir soumis le formulaire, vous ne verrez pas le message bien connu `Would you like to submit the post data again?` que vous pouvez parfois voir dans le navigateur. (En général, après avoir soumis un formulaire par la méthode `POST`, vous devriez toujours rediriger l'utilisateur vers une action `GET` ). +Il y a encore deux autres méthodes qui méritent d'être expliquées. La méthode `redirect('this')` redirige vers la page actuelle. Il est conseillé de le faire après chaque soumission de formulaire réussie. Lorsque vous redirigez après une soumission de formulaire, vous ne verrez pas le message `Voulez-vous renvoyer les données du formulaire ?` que les navigateurs affichent parfois. (En général, après avoir soumis un formulaire avec la méthode `POST`, il devrait toujours y avoir une redirection vers une action `GET`.) -Le message `flashMessage` sert à informer l'utilisateur du résultat d'une opération quelconque. Comme il s'agit d'une redirection, le message ne peut pas être transmis directement au modèle et rendu. Il y a donc cette méthode qui le stocke et le rend disponible au prochain chargement de la page. Les messages flash sont rendus dans le fichier `app/Presenters/templates/@layout.latte` par défaut, et cela ressemble à ceci : +La méthode `flashMessage()` sert à afficher un message à l'utilisateur sur le résultat d'une opération. Comme nous redirigeons, le message ne peut pas être simplement transmis au template et rendu immédiatement. C'est pourquoi cette méthode existe : elle enregistre le message pour qu'il soit affiché lors du prochain chargement de la page. Les messages flash sont rendus dans le template principal `app/Presentation/@layout.latte` et ressemblent à ceci : -```latte +```latte .{file:app/Presentation/@layout.latte} <div n:foreach="$flashes as $flash" n:class="flash, $flash->type"> {$flash->message} </div> ``` -Comme nous le savons déjà, ils sont transmis automatiquement au modèle, de sorte que vous n'avez pas besoin d'y penser trop, cela fonctionne tout simplement. Pour plus de détails [, consultez la documentation |application:presenters#flash-messages]. +Comme nous le savons déjà, les messages flash sont automatiquement transmis au template, nous n'avons donc pas besoin d'y penser beaucoup, cela fonctionne tout simplement. Pour plus d'informations, [visitez la documentation |application:presenters#Messages Flash]. -Rendu des commentaires .[#toc-rendering-the-comments] -===================================================== +Affichage des commentaires +========================== -Voici l'une des choses que vous allez adorer. La base de données Nette dispose d'une fonctionnalité sympa appelée [Explorer |database:explorer]. Vous vous souvenez que nous avons créé les tables en InnoDB ? Adminer a créé ce qu'on appelle des [clés étrangères |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html] qui vont nous épargner une tonne de travail. +C'est l'une de ces choses que vous allez adorer. Nette Database a une fonctionnalité géniale appelée [Explorer |database:explorer]. Vous souvenez-vous que nous avons délibérément créé les tables de la base de données en utilisant le stockage InnoDB ? Adminer a ainsi créé quelque chose appelé [clés étrangères |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html], qui nous épargnera beaucoup de travail. -Nette Database Explorer utilise les clés étrangères pour résoudre les relations entre les tables, et connaissant les relations, il peut automatiquement créer des requêtes pour vous. +Nette Database Explorer utilise les clés étrangères pour résoudre les relations entre les tables et, grâce à la connaissance de ces relations, peut créer automatiquement des requêtes de base de données. -Comme vous vous en souvenez peut-être, nous avons passé la variable `$post` au modèle dans `PostPresenter::renderShow()` et maintenant nous voulons itérer à travers tous les commentaires qui ont la colonne `post_id` égale à notre `$post->id`. Vous pouvez le faire en appelant `$post->related('comments')`. C'est aussi simple que cela. Regardez le code résultant. +Comme vous vous en souvenez sûrement, nous avons transmis la variable `$post` au template en utilisant la méthode `PostPresenter::renderShow()` et nous voulons maintenant itérer sur tous les commentaires dont la valeur de la colonne `post_id` est identique à `$post->id`. Nous pouvons y parvenir en appelant `$post->related('comments')`. Oui, c'est aussi simple que ça. Jetons un coup d'œil au code résultant : -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { ... $this->template->post = $post; @@ -142,17 +142,17 @@ public function renderShow(int $postId): void } ``` -Et le modèle : +Et le template : -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... -<h2>Comments</h2> +<h2>Commentaires</h2> <div class="comments"> {foreach $comments as $comment} <p><b><a href="mailto:{$comment->email}" n:tag-if="$comment->email"> {$comment->name} - </a></b> said:</p> + </a></b> a écrit:</p> <div>{$comment->content}</div> {/foreach} @@ -160,13 +160,12 @@ Et le modèle : ... ``` -Remarquez l'attribut spécial `n:tag-if`. Vous savez déjà comment fonctionne `n: attributes`. Si vous faites précéder l'attribut de `tag-`, il n'enveloppera que les balises, et non leur contenu. Cela vous permet de transformer le nom du commentateur en un lien s'il a fourni son adresse électronique. Les résultats de ces deux lignes sont identiques : +Notez l'attribut spécial `n:tag-if`. Vous savez déjà comment fonctionnent les `n:attributs`. Si vous ajoutez le préfixe `tag-` à l'attribut, la fonctionnalité ne s'applique qu'à la balise HTML, pas à son contenu. Cela nous permet de faire du nom du commentateur un lien uniquement s'il a fourni son e-mail. Ces deux lignes sont identiques : ```latte -<strong n:tag-if="$important"> Hello there! </strong> +<strong n:tag-if="$important"> Bonjour ! </strong> -{if $important}<strong>{/if} Hello there! {if $important}</strong>{/if} +{if $important}<strong>{/if} Bonjour ! {if $important}</strong>{/if} ``` {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/fr/creating-posts.texy b/quickstart/fr/creating-posts.texy index ff4e664909..2a0cc3ad9d 100644 --- a/quickstart/fr/creating-posts.texy +++ b/quickstart/fr/creating-posts.texy @@ -1,30 +1,30 @@ -Créer et modifier des messages -****************************** +Création et modification des articles +************************************* -C'est le moment idéal. Nous avons un nouveau blog super cool, les gens discutent dans les commentaires et nous avons enfin du temps pour plus de programmation. Bien que nous aimions Adminer, il n'est pas si facile d'y écrire des articles de blog. C'est peut-être le bon moment pour ajouter un formulaire simple pour ajouter de nouveaux articles directement depuis notre application. C'est parti. +C'est génial ! Nous avons un nouveau blog super cool, les gens discutent avec acharnement dans les commentaires et nous avons enfin un peu de temps pour programmer davantage. Bien qu'Adminer soit un excellent outil, il n'est pas tout à fait idéal pour écrire de nouveaux articles de blog. Il est probablement temps de créer un formulaire simple pour ajouter de nouveaux articles directement depuis l'application. Allons-y. Commençons par concevoir l'interface utilisateur : -1. Sur la page d'accueil, ajoutons un lien "Write new post". -2. Il affichera un formulaire avec un titre et une zone de texte pour le contenu. -3. Lorsque vous cliquez sur le bouton "Enregistrer", l'article de blog est enregistré. +1. Sur la page d'accueil, ajoutons un lien "Écrire un nouvel article". +2. Ce lien affichera un formulaire avec un titre et une zone de texte pour le contenu de l'article. +3. Lorsque nous cliquons sur le bouton Enregistrer, l'article sera enregistré dans la base de données. -Plus tard, nous ajouterons également l'authentification et autoriserons uniquement les utilisateurs connectés à ajouter de nouveaux articles. Mais nous ferons cela plus tard. Quel code devrons-nous écrire pour que cela fonctionne ? +Plus tard, nous ajouterons également la connexion et autoriserons l'ajout d'articles uniquement aux utilisateurs connectés. Mais cela viendra plus tard. Quel code devons-nous écrire maintenant pour que tout fonctionne ? -1. Créer un nouveau présentateur avec un formulaire pour ajouter des articles. -2. Définissez un callback qui sera déclenché après l'envoi réussi du formulaire et qui enregistrera le nouvel article dans la base de données. -3. Créez un nouveau modèle pour le formulaire. -4. Ajoutez un lien vers le formulaire dans le modèle de page principale. +1. Créons un nouveau presenter avec un formulaire pour ajouter des articles. +2. Définissons un callback qui se déclenchera après la soumission réussie du formulaire et qui enregistrera le nouvel article dans la base de données. +3. Créons un nouveau template sur lequel se trouvera ce formulaire. +4. Ajoutons un lien vers le formulaire dans le template de la page principale. -Nouveau présentateur .[#toc-new-presenter] -========================================== +Nouveau Presenter +================= -Nommez le nouveau présentateur `EditPresenter` et enregistrez-le dans `app/Presenters/EditPresenter.php`. Il doit également se connecter à la base de données, donc ici aussi nous écrivons un constructeur qui nécessitera une connexion à la base de données : +Nous appellerons le nouveau presenter `EditPresenter` et le sauvegarderons dans `app/Presentation/Edit/`. Il doit également se connecter à la base de données, nous écrirons donc à nouveau un constructeur qui nécessitera une connexion à la base de données : -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Edit; use Nette; use Nette\Application\UI\Form; @@ -39,95 +39,95 @@ final class EditPresenter extends Nette\Application\UI\Presenter ``` -Formulaire de sauvegarde des messages .[#toc-form-for-saving-posts] -=================================================================== +Formulaire de sauvegarde des articles +===================================== -Les formulaires et les composants ont déjà été abordés lorsque nous avons ajouté le support des commentaires. Si vous êtes confus sur le sujet, allez voir [comment fonctionnent les formulaires et les composants |comments#form-for-commenting], nous attendrons ici ;) +Nous avons déjà expliqué les formulaires et les composants lors de la création des commentaires. Si ce n'est toujours pas clair, allez consulter la [création de formulaires et de composants |comments#Formulaire de commentaire], nous attendrons ici ;) -Maintenant, ajoutez cette méthode à l'adresse `EditPresenter`: +Maintenant, ajoutons cette méthode au presenter `EditPresenter` : -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} protected function createComponentPostForm(): Form { $form = new Form; - $form->addText('title', 'Title:') + $form->addText('title', 'Titre :') ->setRequired(); - $form->addTextArea('content', 'Content:') + $form->addTextArea('content', 'Contenu :') ->setRequired(); - $form->addSubmit('send', 'Save and publish'); - $form->onSuccess[] = [$this, 'postFormSucceeded']; + $form->addSubmit('send', 'Sauvegarder et publier'); + $form->onSuccess[] = $this->postFormSucceeded(...); return $form; } ``` -Enregistrement d'un nouveau message à partir d'un formulaire .[#toc-saving-new-post-from-form] -============================================================================================== +Sauvegarde d'un nouvel article depuis le formulaire +=================================================== -Continuez en ajoutant une méthode de gestion. +Continuons en ajoutant une méthode qui traitera les données du formulaire : -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { $post = $this->database ->table('posts') ->insert($data); - $this->flashMessage('Post was published', 'success'); + $this->flashMessage("L'article a été publié avec succès.", 'success'); $this->redirect('Post:show', $post->id); } ``` -Une explication rapide : il récupère les valeurs du formulaire, les insère dans la base de données, crée un message pour l'utilisateur indiquant que le message a été enregistré avec succès, et redirige vers la page où ce message est publié afin que vous puissiez voir à quoi il ressemble. +Juste un rapide récapitulatif : cette méthode récupère les données du formulaire (`$data` est un tableau), les insère dans la table `posts` de la base de données, crée un message flash pour l'utilisateur indiquant que l'article a été sauvegardé avec succès, et redirige vers la page affichant le nouvel article (`Post:show`) afin que nous puissions voir immédiatement à quoi il ressemble. -Page de création d'un nouveau message .[#toc-page-for-creating-a-new-post] -========================================================================== +Page de création d'un nouvel article +==================================== -Créons simplement le modèle `Edit/create.latte`: +Créons maintenant le template `Edit/create.latte` : -```latte .{file:app/Presenters/templates/Edit/create.latte} +```latte .{file:app/Presentation/Edit/create.latte} {block content} -<h1>New post</h1> +<h1>Nouvel article</h1> {control postForm} ``` -Tout devrait être clair maintenant. La dernière ligne montre le formulaire que nous sommes sur le point de créer. +Tout devrait être clair maintenant. La dernière ligne affiche le formulaire que nous avons défini dans le presenter. -Nous pourrions également créer une méthode correspondante à l'adresse `renderCreate`, mais ce n'est pas nécessaire. Nous n'avons pas besoin d'obtenir des données de la base de données et de les transmettre au modèle, donc cette méthode serait vide. Dans ce cas, la méthode peut ne pas exister du tout. +Nous pourrions également créer une méthode `renderCreate` correspondante, mais ce n'est pas nécessaire. Nous n'avons pas besoin de récupérer de données de la base de données et de les transmettre au template, donc cette méthode serait vide. Dans de tels cas, la méthode n'a pas besoin d'exister du tout. -Lien pour la création de messages .[#toc-link-for-creating-posts] -================================================================= +Lien vers la création d'articles +================================ -Vous savez probablement déjà comment ajouter un lien à `EditPresenter` et à son action `create`. Essayez-le. +Vous savez probablement déjà comment ajouter un lien vers `EditPresenter` et son action `create`. Essayez-le. -Il suffit d'ajouter au fichier `app/Presenters/templates/Home/default.latte`: +Il suffit d'ajouter au fichier `app/Presentation/Home/default.latte` : ```latte -<a n:href="Edit:create">Write new post</a> +<a n:href="Edit:create">Écrire un nouvel article</a> ``` -Modifier les messages .[#toc-editing-posts] -=========================================== +Modification des articles +========================= -Ajoutons également la possibilité de modifier des messages existants. Ce sera assez simple - nous avons déjà `postForm` et nous pouvons l'utiliser pour l'édition également. +Maintenant, ajoutons également la possibilité de modifier un article. Ce sera très simple. Nous avons déjà le formulaire `postForm` prêt et nous pouvons l'utiliser également pour la modification. -Nous allons ajouter une nouvelle page `edit` au site `EditPresenter`: +Ajoutons une nouvelle action `edit` (et sa vue `renderEdit`) au presenter `EditPresenter` : -```php .{file:app/Presenters/EditPresenter.php} -public function renderEdit(int $postId): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +public function renderEdit(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { - $this->error('Post not found'); + $this->error('Article non trouvé'); } $this->getComponent('postForm') @@ -135,26 +135,26 @@ public function renderEdit(int $postId): void } ``` -Et créer le modèle `Edit/edit.latte`: +Et créons le template `Edit/edit.latte` : -```latte .{file:app/Presenters/templates/Edit/edit.latte} +```latte .{file:app/Presentation/Edit/edit.latte} {block content} -<h1>Edit post</h1> +<h1>Modifier l'article</h1> {control postForm} ``` -Et mettre à jour la méthode `postFormSucceeded`, qui permettra soit d'ajouter un nouveau message (comme maintenant), soit de modifier les messages existants : +Maintenant, nous devons modifier la méthode `postFormSucceeded`, qui sera capable à la fois d'ajouter un nouvel article (comme elle le fait maintenant) et de modifier un article existant : -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); - if ($postId) { + if ($id) { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); $post->update($data); } else { @@ -163,26 +163,25 @@ public function postFormSucceeded(array $data): void ->insert($data); } - $this->flashMessage('Post was published', 'success'); + $this->flashMessage('L\'article a été publié avec succès.', 'success'); $this->redirect('Post:show', $post->id); } ``` -Lorsque le paramètre `postId` est fourni, cela signifie qu'un message est en cours de modification. Dans ce cas, nous vérifierons que le message existe réellement et si c'est le cas, nous le mettrons à jour dans la base de données. Si le paramètre `postId` n'est pas fourni, cela signifie qu'un nouveau message sera ajouté. +La méthode vérifie maintenant si le paramètre `id` est présent dans la requête. S'il l'est, cela signifie que nous modifions un article existant. Nous récupérons l'article et mettons à jour ses données avec `$post->update($data)`. S'il n'y a pas de paramètre `id`, nous insérons un nouvel article comme avant avec `$this->database->table('posts')->insert($data)`. -Mais d'où vient le `postId`? C'est le paramètre passé à la méthode `renderEdit`. +Mais d'où vient ce paramètre `id` ? Il s'agit du paramètre qui a été passé à la méthode `renderEdit`. -Vous pouvez maintenant ajouter un lien vers le modèle `app/Presenters/templates/Post/show.latte`: +Nous pouvons maintenant ajouter un lien dans le template `app/Presentation/Post/show.latte` pour permettre la modification : -```latte -<a n:href="Edit:edit $post->id">Edit this post</a> +```latte .{file:app/Presentation/Post/show.latte} +<a n:href="Edit:edit $post->id">Modifier l'article</a> ``` -Résumé .[#toc-summary] -====================== +Résumé +====== -Le blog fonctionne, les gens commentent rapidement et nous ne dépendons plus de l'Adminer pour ajouter de nouveaux articles. Il est totalement indépendant et même les gens normaux peuvent y poster. Mais attendez, ce n'est probablement pas bien, que n'importe qui, je veux dire vraiment n'importe qui sur Internet, puisse poster sur notre blog. Une certaine forme d'authentification est nécessaire pour que seuls les utilisateurs connectés puissent publier des messages. Nous l'ajouterons dans le prochain chapitre. +Le blog est maintenant fonctionnel, les visiteurs le commentent activement et nous n'avons plus besoin d'Adminer pour publier. L'application est entièrement indépendante et n'importe qui peut ajouter un nouvel article. Attendez une minute, ce n'est probablement pas tout à fait correct que n'importe qui - et je veux dire vraiment n'importe qui ayant accès à Internet - puisse ajouter de nouveaux articles. Une certaine sécurité est nécessaire pour que seul un utilisateur connecté puisse ajouter un nouvel article. Nous examinerons cela dans le prochain chapitre. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/fr/home-page.texy b/quickstart/fr/home-page.texy index 9b97de5ee3..e6159c4f28 100644 --- a/quickstart/fr/home-page.texy +++ b/quickstart/fr/home-page.texy @@ -2,40 +2,40 @@ Page d'accueil du blog ********************** .[perex] -Créons la page d'accueil affichant vos articles récents. +Créons maintenant la page d'accueil affichant les derniers articles. -Avant de commencer, vous devez connaître au moins les bases du modèle de conception Modèle-Vue-Présentateur (similaire à MVC((Model-View-Controller))) : +Avant de commencer, il est nécessaire de connaître au moins les bases du patron de conception Modèle-Vue-Présenteur (similaire à MVC ((Modèle-Vue-Contrôleur))) : -- **Modèle** - couche de manipulation des données. Elle est complètement séparée du reste de l'application. Elle ne communique qu'avec les présentateurs. +- **Modèle** - la couche qui travaille avec les données. Elle est complètement séparée du reste de l'application. Elle communique uniquement avec le presenter. -- **View** - une couche de définition frontale. Elle rend les données demandées à l'utilisateur en utilisant des modèles. +- **Vue** - la couche front-end. Elle affiche les données demandées à l'aide de templates et les présente à l'utilisateur. -- **Presenter** (ou Controller) - une couche de connexion. Le présentateur relie le modèle et la vue. Il traite les demandes, demande des données au modèle et les transmet ensuite à la vue en cours. +- **Presenter** (ou Controller) - la couche de liaison. Le Presenter relie le Modèle et la Vue. Il traite les requêtes, interroge le Modèle pour obtenir des données et les renvoie à la Vue. -Dans le cas d'une application très simple comme notre blog, la couche Modèle sera en fait constituée uniquement de requêtes à la base de données elle-même - nous n'avons pas besoin de code PHP supplémentaire pour cela. Nous avons seulement besoin de créer les couches Presenter et View. Dans Nette, chaque Presenter a ses propres Views, donc nous allons continuer avec les deux simultanément. +Dans le cas d'applications simples, comme notre blog, toute la couche modèle sera constituée uniquement de requêtes vers la base de données - pour cela, nous n'avons pas besoin de code supplémentaire pour le moment. Pour commencer, nous créerons donc uniquement les presenters et les templates. Dans Nette, chaque presenter a ses propres templates, nous les créerons donc en même temps. -Création de la base de données avec Adminer .[#toc-creating-the-database-with-adminer] -====================================================================================== +Création de la base de données avec Adminer +=========================================== -Pour stocker les données, nous utiliserons la base de données MySQL car c'est le choix le plus courant parmi les développeurs web. Mais si vous ne l'aimez pas, n'hésitez pas à utiliser une base de données de votre choix. +Pour stocker les données, nous utiliserons une base de données MySQL, car c'est la plus répandue parmi les programmeurs d'applications web. Cependant, si vous ne souhaitez pas l'utiliser, n'hésitez pas à choisir une base de données de votre choix. -Préparons la base de données qui stockera nos articles de blog. Nous pouvons commencer très simplement - avec une seule table pour les articles. +Préparons maintenant la structure de la base de données où les articles de notre blog seront stockés. Commençons très simplement - nous créerons une seule table pour les articles. -Pour créer la base de données, nous pouvons télécharger [Adminer |https://www.adminer.org], ou vous pouvez utiliser un autre outil de gestion de base de données. +Pour créer la base de données, nous pouvons télécharger [Adminer |https://www.adminer.org], ou un autre de vos outils préférés pour la gestion des bases de données. -Ouvrons Adminer et créons une nouvelle base de données appelée `quickstart`. +Ouvrons Adminer et créons une nouvelle base de données nommée `quickstart`. -Créez une nouvelle table nommée `posts` et ajoutez ces colonnes : -- `id` int, cliquez sur autoincrement (AI) +Créons une nouvelle table nommée `posts` avec ces colonnes : +- `id` int, cochez autoincrement (AI) - `title` varchar, longueur 255 - `content` text - `created_at` timestamp -Cela devrait ressembler à ceci : +La structure résultante devrait ressembler à ceci : [* adminer-posts.webp *] @@ -49,9 +49,9 @@ CREATE TABLE `posts` ( ``` .[caution] -Il est très important d'utiliser la table de stockage **InnoDB**. Vous en verrez la raison plus tard. Pour l'instant, choisissez simplement cela et soumettez. Vous pouvez cliquer sur Enregistrer maintenant. +Il est vraiment important d'utiliser le moteur de stockage **InnoDB**. Nous verrons bientôt pourquoi. Pour l'instant, sélectionnez-le simplement et cliquez sur enregistrer. -Essayez d'ajouter quelques exemples d'articles de blog avant de mettre en œuvre la possibilité d'ajouter de nouveaux articles directement à partir de notre application. +Avant de créer la possibilité d'ajouter des articles à la base de données via l'application, ajoutez manuellement quelques exemples d'articles sur le blog. ```sql INSERT INTO `posts` (`id`, `title`, `content`, `created_at`) VALUES @@ -61,37 +61,34 @@ INSERT INTO `posts` (`id`, `title`, `content`, `created_at`) VALUES ``` -Connexion à la base de données .[#toc-connecting-to-the-database] -================================================================= +Connexion à la base de données +============================== -Maintenant, quand la base de données est créée et que nous avons des articles dedans, c'est le bon moment pour les afficher sur notre nouvelle page brillante. +Maintenant que la base de données est créée et que nous y avons stocké quelques articles, il est temps de les afficher sur notre belle nouvelle page. -Tout d'abord, nous devons indiquer à notre application quelle base de données utiliser. La configuration de la connexion à la base de données est stockée dans `config/local.neon`. Définissez la connexion DSN((Data Source Name)) et vos informations d'identification. Cela devrait ressembler à ceci : +Tout d'abord, nous devons indiquer à l'application quelle base de données utiliser. La connexion à la base de données est configurée dans le fichier `config/common.neon` à l'aide du DSN ((Data Source Name)) et des informations d'identification. Cela devrait ressembler à quelque chose comme ceci : -```neon .{file:config/local.neon} +```neon .{file:config/common.neon} database: dsn: 'mysql:host=127.0.0.1;dbname=quickstart' - user: *enter user name* - password: *enter password here* + user: *insérez ici votre nom d'utilisateur* + password: *insérez ici votre mot de passe de base de données* ``` .[note] -Faites attention à l'indentation lorsque vous modifiez ce fichier. Le [format NEON |neon:format] accepte les espaces et les tabulations, mais pas les deux ensemble ! Le fichier de configuration du projet Web utilise les tabulations par défaut. +Lors de la modification de ce fichier, faites attention à l'indentation des lignes. Le format [NEON |neon:format] accepte à la fois l'indentation par espaces et l'indentation par tabulations, mais pas les deux en même temps. Le fichier de configuration par défaut dans Web Project utilise des tabulations. -L'ensemble de la configuration y compris est stocké dans `config/` dans les fichiers `common.neon` et `local.neon`. Le fichier `common.neon` contient la configuration globale de l'application et `local.neon` ne contient que les paramètres spécifiques à l'environnement (par exemple, la différence entre le serveur de développement et le serveur de production). +Passage de la connexion à la base de données +============================================ -Injection de la connexion à la base de données .[#toc-injecting-the-database-connection] -======================================================================================== +Le presenter `HomePresenter`, qui sera chargé de l'affichage des articles, a besoin d'une connexion à la base de données. Pour l'obtenir, nous utiliserons un constructeur qui ressemblera à ceci : -Le présentateur `HomePresenter`, qui va lister les articles, a besoin d'une connexion à la base de données. Pour la recevoir, écrivez un constructeur comme celui-ci : - -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; -use Nette\Application\UI\Form; final class HomePresenter extends Nette\Application\UI\Presenter { @@ -105,12 +102,12 @@ final class HomePresenter extends Nette\Application\UI\Presenter ``` -Chargement des messages depuis la base de données .[#toc-loading-posts-from-the-database] -========================================================================================= +Chargement des articles depuis la base de données +================================================= -Maintenant, récupérons les messages de la base de données et transmettons-les au modèle, qui rendra ensuite le code HTML. C'est à cela que sert la méthode dite *render* : +Maintenant, chargeons les articles de la base de données et envoyons-les au template, qui les affichera ensuite sous forme de code HTML. À cette fin, une méthode appelée *render* est utilisée : -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} public function renderDefault(): void { $this->template->posts = $this->database @@ -120,41 +117,41 @@ public function renderDefault(): void } ``` -Le présentateur a maintenant une méthode de rendu `renderDefault()` qui transmet les données à une vue appelée `default`. Les modèles de présentateur peuvent être trouvés dans `app/Presenters/templates/{PresenterName}/{viewName}.latte`, donc dans ce cas le modèle sera situé dans `app/Presenters/templates/Home/default.latte`. Dans le modèle, une variable nommée `$posts` est maintenant disponible, qui contient les messages de la base de données. +Le presenter contient maintenant une méthode de rendu `renderDefault()`, qui transmet les données de la base de données au template (Vue). Les templates sont situés dans `app/Presentation/{PresenterName}/{viewName}.latte`, donc dans ce cas, le template est situé dans `app/Presentation/Home/default.latte`. Dans le template, la variable `$posts` sera désormais disponible, contenant les articles récupérés de la base de données. -Modèle .[#toc-template] -======================= +Template +======== -Il existe un modèle générique pour l'ensemble de la page (appelé *layout*, avec l'en-tête, les feuilles de style, le pied de page, ...), puis des modèles spécifiques pour chaque vue (par exemple, pour afficher la liste des articles du blog), qui peuvent remplacer certaines parties du modèle layout. +Pour l'ensemble du site web, nous disposons d'un template principal (appelé *layout*, contenant l'en-tête, les styles, le pied de page,...) et de templates spécifiques pour chaque vue (par exemple, pour afficher les articles du blog), qui peuvent remplacer certaines parties du template principal. -Par défaut, le modèle de mise en page est situé dans `templates/@layout.latte`, qui contient : +Par défaut, le template de layout est situé dans `app/Presentation/@layout.latte` et contient : -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... {include content} ... ``` -`{include content}` insère un bloc nommé `content` dans le modèle principal. Vous pouvez le définir dans les modèles de chaque vue. Dans ce cas, nous allons éditer le fichier `Home/default.latte` comme ceci : +La syntaxe `{include content}` insère dans le template principal un bloc nommé `content`. Nous le définirons dans les templates des vues individuelles. Dans notre cas, nous modifions le fichier `Home/default.latte` comme suit : -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} Hello World {/block} ``` -Il définit le [bloc |latte:tags#block]*contenu*, qui sera inséré dans la mise en page. Si vous rafraîchissez le navigateur, vous verrez une page avec le texte "Hello world" (dans le code source également avec l'en-tête et le pied de page HTML définis dans `@layout.latte`). +Nous avons ainsi défini le [bloc |latte:tags#block] *content*, qui sera inséré dans le layout principal. Si nous actualisons à nouveau le navigateur, nous verrons une page avec le texte "Hello World" (dans le code source également avec l'en-tête et le pied de page HTML définis dans `@layout.latte`). -Affichons les articles du blog - nous allons modifier le modèle comme suit : +Affichons les articles du blog - modifions le template comme suit : -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - <h1>My blog</h1> + <h1>Mon blog</h1> {foreach $posts as $post} <div class="post"> - <div class="date">{$post->created_at|date:'j. n. Y'}</div> + <div class="date">{$post->created_at|date:'F j, Y'}</div> <h2>{$post->title}</h2> @@ -164,42 +161,41 @@ Affichons les articles du blog - nous allons modifier le modèle comme suit : {/block} ``` -Si vous rafraîchissez votre navigateur, vous verrez la liste de vos articles de blog. La liste n'est pas très fantaisiste ou colorée, alors n'hésitez pas à ajouter quelques [CSS brillants |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] à `www/css/style.css` et à les lier dans une mise en page : +Si nous actualisons le navigateur, nous verrons la liste de tous les articles. L'affichage n'est pas encore très joli, ni coloré, nous pouvons donc ajouter quelques [styles CSS |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] au fichier `www/css/style.css` et le lier dans le layout : -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <link rel="stylesheet" href="{$basePath}/css/style.css"> </head> ... ``` -La balise `{foreach}` parcourt tous les messages transmis au modèle dans la variable `$posts` et affiche un morceau de code HTML pour chaque message. Tout comme le ferait un code PHP. +La balise `{foreach}` itère sur tous les articles que nous avons transmis au template dans la variable `$posts`, et pour chacun d'eux, elle affiche le fragment HTML correspondant. Elle se comporte exactement comme le code PHP. -L'élément `|date` est appelé un filtre. Les filtres sont utilisés pour formater la sortie. Ce filtre particulier convertit une date (par exemple `2013-04-12`) en sa forme la plus lisible (`12. 4. 2013`). Le filtre `|truncate` tronque la chaîne de caractères à la longueur maximale spécifiée, et ajoute une ellipse à la fin si la chaîne est tronquée. Comme il s'agit d'un aperçu, il est inutile d'afficher le contenu complet de l'article. D'autres filtres par défaut [peuvent être trouvés dans la documentation |latte:filters] ou vous pouvez créer les vôtres si nécessaire. +La syntaxe `|date:` est appelée un filtre. Les filtres sont destinés à formater la sortie. Ce filtre particulier convertit la date (par exemple `2013-04-12`) en une forme plus lisible (`April 12, 2013`). Le filtre `|truncate` coupe la chaîne à la longueur maximale spécifiée et, si la chaîne est raccourcie, ajoute des points de suspension à la fin. Comme il s'agit d'un aperçu, il n'est pas nécessaire d'afficher l'intégralité du contenu de l'article. D'autres filtres par défaut sont [trouvés dans la documentation |latte:filters] ou nous pouvons créer les nôtres si nécessaire. -Une dernière chose. Nous pouvons rendre le code un peu plus court et donc plus simple. Nous pouvons remplacer les *balises lattes* par des *n:attributs* comme ceci : +Encore une chose. Nous pouvons raccourcir et simplifier le code précédent. Nous y parvenons en remplaçant les *balises Latte* par des *n:attributs* : -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - <h1>My blog</h1> + <h1>Mon blog</h1> <div n:foreach="$posts as $post" class="post"> <div class="date">{$post->created_at|date:'F j, Y'}</div> <h2>{$post->title}</h2> - <div>{$post->content}</div> + <div>{$post->content|truncate:256}</div> </div> {/block} ``` -Le site `n:foreach`, enveloppe simplement la *div* avec un bloc *foreach* (il fait exactement la même chose que le bloc de code précédent). +L'attribut `n:foreach` enveloppe le bloc *div* avec un *foreach* (fonctionne exactement de la même manière que le code précédent). -Résumé .[#toc-summary] -====================== +Résumé +====== -Nous avons une base de données MySQL très simple contenant quelques articles de blog. L'application se connecte à la base de données et affiche une simple liste des articles. +Nous avons maintenant une base de données MySQL très simple avec quelques articles. L'application se connecte à cette base de données et affiche une simple liste de ces articles dans le template. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/fr/model.texy b/quickstart/fr/model.texy index 19a0e76642..5b9d747b8a 100644 --- a/quickstart/fr/model.texy +++ b/quickstart/fr/model.texy @@ -1,13 +1,13 @@ Modèle ****** -Au fur et à mesure que notre application se développe, nous nous apercevons rapidement que nous devons effectuer des opérations de base de données similaires dans divers emplacements et dans divers présentateurs, par exemple acquérir les articles publiés les plus récents. Si nous améliorons notre application en ajoutant un drapeau aux articles pour indiquer un état de travail en cours, nous devons également passer par tous les emplacements de notre application et ajouter une clause where pour nous assurer que seuls les articles terminés sont sélectionnés. +Au fur et à mesure que l'application grandit, nous découvrirons bientôt que nous devons effectuer des opérations similaires avec la base de données à différents endroits, dans différents presenters. Par exemple, obtenir les derniers articles publiés. Si nous améliorons l'application, par exemple en ajoutant un indicateur aux articles pour savoir s'ils sont en cours de rédaction, nous devons également parcourir tous les endroits de l'application où les articles sont récupérés de la base de données et ajouter une condition `where` pour ne sélectionner que les articles non rédigés. -À ce stade, le travail direct avec la base de données devient insuffisant et il sera plus judicieux de nous aider avec une nouvelle fonction qui renvoie les articles publiés. Et lorsque nous ajouterons plus tard une autre clause (par exemple pour ne pas afficher les articles ayant une date future), nous ne modifierons notre code qu'à un seul endroit. +À ce moment-là, le travail direct avec la base de données devient insuffisant et il sera plus pratique de s'aider d'une nouvelle fonction qui nous renverra les articles publiés. Et si nous ajoutons plus tard une autre condition, par exemple que les articles avec une date future ne doivent pas être affichés, nous ne modifierons le code qu'à un seul endroit. -Nous allons placer la fonction dans la classe `PostFacade` et l'appeler `getPublicArticles()`. +Nous placerons la fonction par exemple dans la classe `PostFacade` et l'appellerons `getPublicArticles()`. -Nous allons créer notre classe modèle `PostFacade` dans le répertoire `app/Model/` pour s'occuper de nos articles : +Dans le répertoire `app/Model/`, nous créons notre classe de modèle `PostFacade`, qui s'occupera des articles : ```php .{file:app/Model/PostFacade.php} <?php @@ -32,13 +32,13 @@ final class PostFacade } ``` -Dans la classe nous passons la base de données Explorer :[api:Nette\Database\Explorer]. Cela permettra de profiter de la puissance du [conteneur DI |dependency-injection:passing-dependencies]. +Dans la classe, via le constructeur, nous demandons l'injection de [api:Nette\Database\Explorer]. Nous utilisons ainsi la puissance du [conteneur DI|dependency-injection:passing-dependencies]. -Nous passerons à `HomePresenter` que nous éditerons de manière à nous débarrasser de la dépendance sur `Nette\Database\Explorer` en la remplaçant par une nouvelle dépendance sur notre nouvelle classe. +Nous passons à `HomePresenter`, que nous modifions en nous débarrassant de la dépendance à `Nette\Database\Explorer` et en la remplaçant par une nouvelle dépendance à notre nouvelle classe. -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use App\Model\PostFacade; use Nette; @@ -59,10 +59,9 @@ final class HomePresenter extends Nette\Application\UI\Presenter } ``` -Dans la section utilisation, nous utilisons `App\Model\PostFacade`, nous pouvons donc raccourcir le code PHP à `PostFacade`. Nous demandons cet objet dans le constructeur, l'écrivons dans la propriété `$facade` et l'utilisons dans la méthode renderDefault. +Dans la section `use`, nous avons `App\Model\PostFacade`, nous pouvons donc raccourcir l'écriture dans le code PHP à `PostFacade`. Nous demandons cet objet dans le constructeur, l'écrivons dans la propriété `$facade` et l'utilisons dans la méthode `renderDefault`. -La dernière étape consiste à apprendre au conteneur DI à produire cet objet. Cela se fait généralement en ajoutant un point au fichier `config/services.neon` dans la section `services`, en donnant le nom complet de la classe et les paramètres du constructeur. -Cela l'enregistre, pour ainsi dire, et l'objet est alors appelé **service**. Grâce à une magie appelée [câblage automatique |dependency-injection:autowiring], nous n'avons généralement pas besoin de spécifier les paramètres du constructeur car l'ID les reconnaît et les transmet automatiquement. Ainsi, il suffirait de fournir le nom de la classe : +Il reste une dernière étape, apprendre au conteneur DI à fabriquer cet objet. Cela se fait généralement en ajoutant un tiret dans le fichier `config/services.neon` dans la section `services`, en indiquant le nom complet de la classe et les paramètres du constructeur. Nous l'enregistrons ainsi et l'objet est alors appelé **service**. Grâce à la magie appelée [autowiring |dependency-injection:autowiring], nous n'avons généralement pas besoin d'indiquer les paramètres du constructeur, car DI les reconnaît et les transmet lui-même. Il suffirait donc d'indiquer uniquement le nom de la classe : ```neon .{file:config/services.neon} ... @@ -71,16 +70,15 @@ services: - App\Model\PostFacade ``` -Cependant, vous n'avez pas besoin d'ajouter cette ligne non plus. Dans la section `search` au début de `services.neon` il est défini que toutes les classes se terminant par `-Facade` ou `-Factory` seront recherchées par DI automatiquement, ce qui est également le cas pour `PostFacade`. +Cependant, vous n'avez même pas besoin d'ajouter cette ligne. Dans la section `search` au début de `services.neon`, il est défini que toutes les classes se terminant par le mot `-Facade` ou `-Factory` sont recherchées automatiquement par DI, ce qui est également le cas de `PostFacade`. -Résumé .[#toc-summary] -====================== +Résumé +====== -La classe `PostFacade` demande `Nette\Database\Explorer` dans un constructeur et comme cette classe est enregistrée dans le conteneur DI, le conteneur crée cette instance et la transmet. DI crée ainsi une instance de `PostFacade` pour nous et la transmet dans un constructeur à la classe HomePresenter qui l'a demandée. Une sorte de poupée Matryoshka de code :) Tous les composants ne demandent que ce dont ils ont besoin et ils ne se soucient pas de savoir où et comment cela est créé. La création est gérée par le conteneur DI. +Dans son constructeur, la classe `PostFacade` demande l'injection de `Nette\Database\Explorer` et comme cette classe est enregistrée dans le conteneur DI, le conteneur crée cette instance et la transmet. DI crée ainsi pour nous l'instance de `PostFacade` et la transmet dans le constructeur à la classe `HomePresenter`, qui l'a demandée. Un emboîtement de dépendances. :) Tout le monde dit simplement ce qu'il veut et ne se soucie pas de savoir où et comment quoi que ce soit est créé. Le conteneur DI s'occupe de la création. .[note] -Vous trouverez ici plus d'informations sur l'[injection de dépendances |dependency-injection:introduction] et sur la [configuration |nette:configuring]. +Ici, vous pouvez en lire plus sur [l'injection de dépendances |dependency-injection:introduction] et la [configuration |nette:configuring]. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/fr/single-post.texy b/quickstart/fr/single-post.texy index 66aeaae68b..6684e4c79c 100644 --- a/quickstart/fr/single-post.texy +++ b/quickstart/fr/single-post.texy @@ -1,17 +1,17 @@ -Page d'affichage unique -*********************** +Page de l'article +***************** .[perex] -Ajoutons une autre page à notre blog, qui affichera le contenu d'un article particulier. +Créons maintenant une autre page du blog, qui affichera un article spécifique. -Nous devons créer une nouvelle méthode de rendu, qui récupérera un article de blog spécifique et le transmettra au modèle. Avoir cette vue dans `HomePresenter` n'est pas agréable car il s'agit d'un article de blog, pas de la page d'accueil. Donc, créons une nouvelle classe `PostPresenter` et plaçons-la dans `app/Presenters`. Elle aura besoin d'une connexion à la base de données, donc remettez le code *database injection*. +Nous devons créer une nouvelle méthode `render`, qui obtiendra un article spécifique et le transmettra au template. Avoir cette méthode dans `HomePresenter` n'est pas très propre, car nous parlons d'un article et non de la page d'accueil. Créons donc `PostPresenter` dans `app/Presentation/Post/`. Ce presenter a également besoin de se connecter à la base de données, nous écrirons donc à nouveau un constructeur qui nécessitera une connexion à la base de données. -Le `PostPresenter` devrait ressembler à ceci : +`PostPresenter` pourrait donc ressembler à ceci : -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Post; use Nette; use Nette\Application\UI\Form; @@ -23,50 +23,50 @@ final class PostPresenter extends Nette\Application\UI\Presenter ) { } - public function renderShow(int $postId): void + public function renderShow(int $id): void { $this->template->post = $this->database ->table('posts') - ->get($postId); + ->get($id); } } ``` -Nous devons définir un espace de noms correct `App\Presenters` pour notre présentateur. Cela dépend du [mappage du présentateur |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7]. +Nous ne devons pas oublier d'indiquer le namespace correct `App\Presentation\Post`, qui est soumis aux paramètres de [mappage des presenters |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7]. -La méthode `renderShow` requiert un argument - l'ID de l'article à afficher. Ensuite, elle charge l'article depuis la base de données et transmet le résultat au modèle. +La méthode `renderShow` nécessite un argument - l'ID d'un article spécifique qui doit être affiché. Ensuite, elle charge cet article depuis la base de données et le transmet au template. -Dans le modèle `Home/default.latte`, nous ajoutons un lien vers l'action `Post:show`: +Dans le template `Home/default.latte`, insérons un lien vers l'action `Post:show`. -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a href="{link Post:show $post->id}">{$post->title}</a></h2> ... ``` -La balise `{link}` génère une adresse URL qui pointe vers l'action `Post:show`. Cette balise transmet également l'ID du message en tant qu'argument. +La balise `{link}` génère une adresse URL qui pointe vers l'action `Post:show`. Elle transmet également l'ID de l'article comme argument. -On peut écrire la même chose en utilisant n:attribute : +Nous pouvons écrire la même chose de manière abrégée en utilisant un n:attribut : -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a n:href="Post:show $post->id">{$post->title}</a></h2> ... ``` -L'attribut `n:href` est similaire à la balise `{link}`. +L'attribut `n:href` est analogue à la balise `{link}`. -Le modèle pour l'action `Post:show` n'existe pas encore. Nous pouvons ouvrir un lien vers ce poste. [Tracy |tracy:] montrera une erreur, pourquoi `Post/show.latte` n'existe pas. Si vous voyez un autre rapport d'erreur, vous devez probablement activer le mod_rewrite dans votre serveur Web. +Cependant, il n'existe pas encore de template pour l'action `Post:show`. Nous pouvons essayer d'ouvrir le lien vers cet article. [Tracy |tracy:] affichera une erreur, car le template `Post/show.latte` n'existe pas encore. Si vous voyez un autre message d'erreur, vous devrez probablement activer `mod_rewrite` sur le serveur web. -Nous allons donc créer `Post/show.latte` avec ce contenu : +Créons donc le template `Post/show.latte` avec ce contenu : -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} {block content} -<p><a n:href="Home:default">← back to posts list</a></p> +<p><a n:href="Home:default">← retour à la liste des articles</a></p> <div class="date">{$post->created_at|date:'F j, Y'}</div> @@ -75,51 +75,50 @@ Nous allons donc créer `Post/show.latte` avec ce contenu : <div class="post">{$post->content}</div> ``` -Regardons les différentes parties. +Passons maintenant en revue les différentes parties du template. -La première ligne commence la définition d'un *bloc nommé* appelé "contenu" que nous avons vu précédemment. Il sera affiché dans un *modèle de mise en page*. Comme vous pouvez le constater, la balise de fin `{/block}` est absente. Elle est facultative. +La première ligne commence la définition d'un bloc nommé "content", comme c'était le cas sur la page d'accueil. Ce bloc sera à nouveau affiché dans le template principal. Comme vous pouvez le voir, la balise de fin `{/block}` est manquante. Elle est en effet facultative. -La deuxième ligne fournit un lien retour vers la liste des articles du blog, de sorte que l'utilisateur puisse naviguer en douceur d'avant en arrière sur notre blog. Nous utilisons à nouveau l'attribut `n:href`, donc Nette se chargera de générer l'URL pour nous. Le lien pointe vers l'action `default` du présentateur `Home` (vous pouvez également écrire `n:href="Home:"` car l'action `default` peut être omise). +Sur la ligne suivante se trouve un lien de retour vers la liste des articles du blog, afin que l'utilisateur puisse facilement naviguer entre la liste des articles et un article spécifique. Comme nous utilisons l'attribut `n:href`, Nette s'occupe lui-même de la génération des liens. Le lien pointe vers l'action `default` du presenter `Home` (nous pouvons également écrire `n:href="Home:"`, car l'action nommée `default` peut être omise, elle est complétée automatiquement). -La troisième ligne formate l'horodatage de la publication avec un filtre, comme nous le savons déjà. +La troisième ligne formate l'affichage de la date à l'aide du filtre que nous connaissons déjà. -La quatrième ligne affiche le *titre* de l'article de blog comme un `<h1>` titre. Il y a une partie que vous ne connaissez peut-être pas, c'est `n:block="title"`. Pouvez-vous deviner ce qu'elle fait ? Si vous avez lu attentivement les parties précédentes, nous avons mentionné `n: attributes`. Voici un autre exemple. Il est équivalent à : +La quatrième ligne affiche le *titre* du blog dans la balise HTML `<h1>`. Cette balise contient un attribut que vous ne connaissez peut-être pas (`n:block="title"`). Devinez-vous ce qu'il fait ? Si vous avez lu attentivement la partie précédente, vous savez déjà qu'il s'agit d'un `n:attribut`. C'est un autre exemple de ceux-ci, qui est équivalent à : ```latte {block title}<h1>{$post->title}</h1>{/block} ``` -En termes simples, il *re-définit* un bloc appelé `title`. Le bloc est défini dans le *modèle de mise en page* (`/app/Presenters/templates/@layout.latte:11`) et, comme dans le cas de la surcharge de la POO, il est surchargé ici. Par conséquent, la page `<title>` de la page contiendra le titre de l'article affiché. Nous avons remplacé le titre de la page et tout ce dont nous avions besoin était `n:block="title"`. Super, non ? +En termes simples, ce bloc redéfinit le bloc nommé `title`. Ce bloc est déjà défini dans le template *layout* principal (`/app/Presentation/@layout.latte:11`) et, tout comme pour le remplacement de méthodes en POO, ce bloc dans le template principal est complètement remplacé. Ainsi, le `<title>` de la page contient maintenant le titre de l'article affiché, et il nous a suffi d'utiliser un simple attribut `n:block="title"`. Génial, n'est-ce pas ? -La cinquième et dernière ligne du modèle affiche le contenu complet de votre article. +La cinquième et dernière ligne du template affiche l'intégralité du contenu d'un article spécifique. -Vérification de l'ID du message .[#toc-checking-post-id] -======================================================== +Vérification de l'ID de l'article +================================= -Que se passe-t-il si quelqu'un modifie l'URL et insère `postId` qui n'existe pas ? Nous devrions fournir à l'utilisateur une belle erreur "page non trouvée". Mettons à jour la méthode de rendu dans `PostPresenter`: +Que se passe-t-il si quelqu'un modifie l'ID dans l'URL et insère un `id` inexistant ? Nous devrions proposer à l'utilisateur une belle erreur de type "page non trouvée". Modifions donc un peu la méthode `renderShow` dans `PostPresenter` : -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { - $this->error('Post not found'); + $this->error('Page non trouvée'); } $this->template->post = $post; } ``` -Si le message est introuvable, l'appel à `$this->error(...)` affichera une page 404 avec un message agréable et compréhensible. Notez que dans votre environnement de développement (sur votre ordinateur portable), vous ne verrez pas la page d'erreur. Au lieu de cela, Tracy affichera l'exception avec tous les détails, ce qui est assez pratique pour le développement. Vous pouvez vérifier les deux modes, il suffit de changer la valeur passée à `setDebugMode` dans `Bootstrap.php`. +Si l'article ne peut pas être trouvé, en appelant `$this->error(...)`, nous affichons une page d'erreur 404 avec un message compréhensible. Attention, en mode développeur (localhost), vous ne verrez pas cette page d'erreur. À la place, Tracy s'affichera avec les détails de l'exception, ce qui est assez pratique pour le développement. Si nous voulons afficher les deux modes, il suffit de changer l'argument de la méthode `setDebugMode` dans le fichier `Bootstrap.php`. -Résumé .[#toc-summary] -====================== +Résumé +====== -Nous avons une base de données avec des articles de blog et une application web avec deux vues - la première affiche le résumé de tous les articles récents et la seconde affiche un article spécifique. +Nous avons une base de données avec des articles et une application web qui a deux vues - la première affiche un aperçu de tous les articles et la seconde affiche un article spécifique. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/hu/@home.texy b/quickstart/hu/@home.texy index 0cf994797e..7e28828501 100644 --- a/quickstart/hu/@home.texy +++ b/quickstart/hu/@home.texy @@ -1,113 +1,114 @@ -Készítsd el az első alkalmazásodat! -*********************************** +Írjuk meg az első alkalmazást! +****************************** .[perex] -Ismerkedjen meg a Nette Frameworkkel, miközben létrehoz egy egyszerű blogot kommentekkel. Kezdjük el! +Ismerkedjünk meg együtt a Nette Frameworkkel, miközben létrehozunk egy egyszerű blogot hozzászólásokkal. Vágjunk bele! -Az első két fejezet után saját működő blogod lesz, és készen állsz arra, hogy közzétedd a fantasztikus bejegyzéseidet, bár a funkciók eléggé korlátozottak lesznek a két fejezet befejezése után. Hogy a felhasználók számára még szebb legyen, érdemes a következő fejezeteket is elolvasnod, és folyamatosan fejlesztened az alkalmazásodat. +Már az első két fejezet után lesz egy saját működő blogunk, és publikálhatjuk nagyszerű bejegyzéseinket, bár a funkciók egyelőre meglehetősen korlátozottak lesznek. Érdemes elolvasni a következő fejezeteket is, ahol beprogramozzuk a hozzászólások hozzáadását, a cikkek szerkesztését, és végül biztonságossá tesszük a blogot. .[tip] -Ez a bemutató feltételezi, hogy befejezte a [Telepítés |nette:installation] dokumentumot, és sikeresen beállította a szerszámokat. +Ez az útmutató feltételezi, hogy elolvasta a [Telepítés |nette:installation] oldalt, és sikeresen előkészítette a szükséges eszközöket. Feltételezi továbbá, hogy érti az [objektumorientált programozást PHP-ban |nette:introduction-to-object-oriented-programming]. -Kérjük, használjon PHP 8.0 vagy újabb verziót. A teljes alkalmazás megtalálható a [GitHubon |https://github.com/nette-examples/quickstart/tree/v4.0]. +Kérjük, használjon PHP 8.1-et vagy újabbat. A teljes alkalmazást megtalálja [a GitHubon |https://github.com/nette-examples/quickstart/tree/v4.0]. -Az üdvözlő oldal .[#toc-the-welcome-page] -========================================= +Üdvözlő oldal +============= -Kezdjük egy új projekt létrehozásával a `nette-blog` könyvtárban: +Kezdjük egy új projekt létrehozásával a `nette-blog` könyvtárba: ```shell composer create-project nette/web-project nette-blog ``` -Ebben a pillanatban a webes projekt üdvözlő oldalának kell futnia. Próbálja ki, ha megnyitja a böngészőjét, és a következő URL-re lép: +Ebben a pillanatban a Web Project bevezető oldalának már működnie kellene. Próbáljuk ki a böngésző megnyitásával a következő URL címen: ``` http://localhost/nette-blog/www/ ``` -és meg kell látnia a Nette Framework üdvözlő oldalát: +és látni fogjuk a Nette Framework bevezető oldalát: [* qs-welcome.webp .{url: http://localhost/nette-blog/www/} *] -Az alkalmazás működik, és most már elkezdhet változtatásokat végezni rajta. +Az alkalmazás működik, és elkezdheti a módosításokat. .[note] -Ha problémája van, [próbálja ki ezt a néhány tippet |nette:troubleshooting#Nette Is Not Working, White Page Is Displayed]. +Ha probléma merült fel, [próbálja ki ezeket a tippeket |nette:troubleshooting#Nem működik a Nette fehér oldal jelenik meg]. -A webes projekt tartalma .[#toc-web-project-s-content] -====================================================== +A Web Project tartalma +====================== -A Web Project a következő felépítésű: +A Web Project a következő struktúrával rendelkezik: /--pre <b>nette-blog/</b> -├── <b>app/</b> ← application directory -│ ├── <b>Presenters/</b> ← presenter classes -│ │ └── <b>templates/</b>← templates -│ ├── <b>Router/</b> ← configuration of URL addresses -│ └── <b>Bootstrap.php</b> ← booting class Bootstrap -├── <b>bin/</b> ← scripts for the command line -├── <b>config/</b> ← configuration files -├── <b>log/</b> ← error logs -├── <b>temp/</b> ← temporary files, cache, … -├── <b>vendor/</b> ← libraries installed by Composer -│ └── <b>autoload.php</b> ← autoloading of libraries installed by Composer -└── <b>www/</b> ← public folder - the only place accessible from browser - └── <b>index.php</b> ← initial file that launches the application +├── <b>app/</b> ← alkalmazás könyvtára +│ ├── <b>Core/</b> ← működéshez szükséges alaposztályok +│ ├── <b>Presentation/</b> ← presenterek, sablonok & társai +│ │ └── <b>Home/</b> ← Home presenter könyvtára +│ └── <b>Bootstrap.php</b> ← Bootstrap indító osztály +├── <b>assets/</b> ← nyers eszközök (SCSS, TypeScript, forrásképek) +├── <b>bin/</b> ← parancssorból futtatott szkriptek +├── <b>config/</b> ← konfigurációs fájlok +├── <b>log/</b> ← hibák naplózása +├── <b>temp/</b> ← ideiglenes fájlok, cache, … +├── <b>vendor/</b> ← Composerrel telepített könyvtárak +│ └── <b>autoload.php</b> ← az összes telepített csomag autoloadingja +└── <b>www/</b> ← nyilvános könyvtár - az egyetlen elérhető a böngészőből + ├─ <b>assets/</b> ← összeállított statikus fájlok (CSS, JS, képek, ...) + └── <b>index.php</b> ← kezdőfájl, amellyel az alkalmazás elindul \-- -A `www` könyvtár a képek, JavaScript, CSS és egyéb nyilvánosan elérhető fájlok tárolására szolgál. Ez az egyetlen könyvtár, amely közvetlenül elérhető a böngészőből, ezért a webszerver gyökérkönyvtárát ide irányíthatjuk (ezt az Apache-ban is beállíthatjuk, de tegyük ezt később, mert most nem fontos). +A `www/` könyvtár képek, JavaScript fájlok, CSS stílusok és más nyilvánosan elérhető fájlok tárolására szolgál. Csak ez a könyvtár érhető el az internetről, ezért állítsa be az alkalmazás gyökérkönyvtárát úgy, hogy ide mutasson (ezt beállíthatja az Apache vagy nginx konfigurációjában, de tegyük ezt később, most nem fontos). -A számodra legfontosabb könyvtár a `app/`. Ott találod a `Bootstrap.php` fájlt, benne egy osztályt, amely betölti a keretrendszert és konfigurálja az alkalmazást. Aktiválja az [automatikus betöltést |robot-loader:], és beállítja a [hibakeresőt |tracy:] és az [útvonalakat |application:routing]. +A legfontosabb mappa számunkra az `app/`. Itt találjuk a `Bootstrap.php` fájlt, amelyben egy osztály található, amely a teljes keretrendszer betöltésére és az alkalmazás beállítására szolgál. Itt aktiválódik az [autoloading |robot-loader:], beállítódik a [debugger |tracy:] és az [útvonalak |application:routing]. -Tisztítsa meg a .[#toc-cleanup] -=============================== +Takarítás +========= -A webes projekt tartalmaz egy üdvözlő oldalt, amelyet eltávolíthatunk - nyugodtan töröljük a `app/Presenters/templates/Home/default.latte` fájlt, és cseréljük ki a "Hello world!" szövegre. +A Web Project tartalmaz egy bevezető oldalt, amelyet törlünk, mielőtt bármit is programozni kezdenénk. Nyugodtan cseréljük le tehát az `app/Presentation/Home/default.latte` fájl tartalmát "Hello world!"-re. [* qs-hello.webp .{url:-} *] -Tracy (hibakereső) .[#toc-tracy-debugger] -========================================= +Tracy (debugger) +================ -A fejlesztés rendkívül fontos eszköze a [Tracy nevű hibakereső |tracy:]. Próbálj meg néhány hibát elkövetni a `app/Presenters/HomePresenter.php` fájlodban (pl. távolíts el egy szögletes zárójelet a HomePresenter osztály definíciójából), és nézd meg, mi történik. Egy piros képernyős oldal fog felugrani egy érthető hibaleírással. +Rendkívül fontos eszköz a fejlesztéshez a [Tracy hibakereső eszköz |tracy:]. Próbáljon meg valamilyen hibát előidézni az `app/Presentation/Home/HomePresenter.php` fájlban (pl. a HomePresenter osztály definíciójában lévő kapcsos zárójel eltávolításával), és nézze meg, mi történik. Felugrik egy értesítő oldal, amely érthetően leírja a hibát. -[* qs-tracy.webp .{url:-}(debugger screen) *] +[* qs-tracy.avif .{url:-}(hibakereső képernyő) *] -A Tracy jelentősen segít a hibák felkutatásában. Figyelje meg a jobb alsó sarokban lebegő Tracy sávot is, amely tájékoztatja Önt a fontos futásidejű adatokról. +A Tracy óriási segítséget nyújt, amikor hibákat keresünk az alkalmazásban. Figyelje meg a képernyő jobb alsó sarkában lebegő Tracy Bart is, amely információkat tartalmaz az alkalmazás futásáról. [* qs-tracybar.webp .{url:-} *] -Termelési üzemmódban a Tracy természetesen le van tiltva, és nem árul el semmilyen érzékeny információt. Helyette minden hiba a `log/` könyvtárba kerül mentésre. Csak próbálja ki. A `app/Bootstrap.php` könyvtárban keresse meg a következő kódrészletet, vegye ki a megjegyzést a sorból, és változtassa meg a metódushívás paraméterét `false`, így néz ki: +Éles (produkciós) módban a Tracy természetesen ki van kapcsolva, és nem jelenít meg semmilyen érzékeny információt. Ebben az esetben minden hiba a `log/` mappába kerül mentésre. Próbáljuk ki. Az `app/Bootstrap.php` fájlban vegyük ki a kommentet a következő sorból, és változtassuk meg a hívás paraméterét `false`-ra, hogy a kód így nézzen ki: ```php .{file:app/Bootstrap.php} ... -$configurator->setDebugMode(false); -$configurator->enableTracy(__DIR__ . '/../log'); +$this->configurator->setDebugMode(false); ... ``` -A weboldal frissítése után a piros képernyős oldal helyébe a felhasználóbarát üzenet lép: +Az oldal frissítése után már nem látjuk a Tracy-t. Helyette egy felhasználóbarát üzenet jelenik meg: -[* qs-fatal.webp .{url:-}(error screen) *] +[* qs-fatal.webp .{url:-}(hiba képernyő) *] -Most nézze meg a `log/` könyvtárat. Ott megtalálja a hibanaplót (az exception.log fájlban) és a hibaüzenetet tartalmazó oldalt is (egy HTML fájlba mentve, amelynek a neve `exception`). +Most nézzünk bele a `log/` könyvtárba. Itt (az `exception.log` fájlban) megtaláljuk a naplózott hibát, valamint a már ismert hibaüzenetet tartalmazó oldalt (HTML fájlként mentve, amelynek neve `exception-`-nel kezdődik). -Kommentáld újra a `// $configurator->setDebugMode(false);` sort. A Tracy automatikusan bekapcsolja a `localhost` környezetben a fejlesztési módot, máshol pedig letiltja. +Kommenteljük vissza a `// $configurator->setDebugMode(false);` sort. A Tracy automatikusan engedélyezi a fejlesztői módot a localhoston, és letiltja mindenhol máshol. -Most már kijavíthatjuk a hibát, és folytathatjuk az alkalmazásunk tervezését. +A létrehozott hibát kijavíthatjuk, és folytathatjuk az alkalmazás írását. -Köszönet küldése .[#toc-send-thanks] -==================================== +Küldjön köszönetet +================== -Mutatunk egy trükköt, aminek a nyílt forráskódú szerzők örülni fognak. A GitHubon egyszerűen adhatsz csillagot azoknak a könyvtáraknak, amelyeket a projekted használ. Csak futtasd le: +Mutatunk egy trükköt, amellyel örömet szerezhet az open source szerzőknek. Egyszerű módon adhat csillagot a GitHubon azoknak a könyvtáraknak, amelyeket a projektje használ. Csak futtassa: ```shell composer thanks @@ -116,4 +117,3 @@ composer thanks Próbálja ki! {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/hu/@left-menu.texy b/quickstart/hu/@left-menu.texy index 124eac17d7..0be04c9f71 100644 --- a/quickstart/hu/@left-menu.texy +++ b/quickstart/hu/@left-menu.texy @@ -1,9 +1,9 @@ -Tutorial -******** -- [Készítse el első alkalmazását! |@home] -- [Blog kezdőlap |home-page] -- [Egyetlen bejegyzés oldal |single-post] -- [Megjegyzések |comments] +Oktatóanyag +*********** +- [Írjuk meg az első alkalmazást! |@home] +- [A blog kezdőlapja |home-page] +- [Bejegyzés oldal |single-post] +- [Hozzászólások |comments] - [Bejegyzések létrehozása és szerkesztése |creating-posts] - [Modell |Model] -- [Hitelesítés |authentication] +- [Azonosítás |authentication] diff --git a/quickstart/hu/@meta.texy b/quickstart/hu/@meta.texy new file mode 100644 index 0000000000..c89e3580d5 --- /dev/null +++ b/quickstart/hu/@meta.texy @@ -0,0 +1 @@ +{{sitename: Írjuk meg az első alkalmazást!}} diff --git a/quickstart/hu/authentication.texy b/quickstart/hu/authentication.texy index 2ced928b78..339df4712c 100644 --- a/quickstart/hu/authentication.texy +++ b/quickstart/hu/authentication.texy @@ -1,36 +1,36 @@ -Hitelesítés -*********** +Authentikáció +************* -A Nette iránymutatást ad arra vonatkozóan, hogyan programozd be a hitelesítést az oldaladon, de nem kényszerít arra, hogy ezt egy bizonyos módon tedd. A megvalósítás az Önre van bízva. A Nette rendelkezik egy `Nette\Security\Authenticator` interfésszel, amely arra kényszerít, hogy csak egyetlen metódust valósítson meg, a `authenticate` metódust, amely úgy találja meg a felhasználót, ahogyan csak akarja. +A Nette módot biztosít az authentikáció programozására az oldalainkon, de semmire sem kényszerít minket. Az implementáció kizárólag rajtunk múlik. A Nette tartalmazza a `Nette\Security\Authenticator` interfészt, amely csak egyetlen `authenticate` metódust követel meg, amely a felhasználót bármilyen általunk kívánt módon ellenőrzi. -Sokféleképpen lehet egy felhasználó hitelesíteni magát. A leggyakoribb módszer a *jelszó alapú hitelesítés* (a felhasználó megadja a nevét vagy e-mail címét és egy jelszót), de vannak más módszerek is. Talán ismerősek a "Login with Facebook" gombok számos weboldalon, vagy a Google/Twitter/GitHub vagy bármely más oldalon történő bejelentkezés. A Nette segítségével bármilyen hitelesítési módszerrel rendelkezhet, vagy kombinálhatja őket. Ez csak rajtad múlik. +Számos lehetőség van arra, hogyan lehet egy felhasználót hitelesíteni. A leggyakoribb hitelesítési mód a jelszó használata (a felhasználó megadja a nevét vagy e-mail címét és jelszavát), de vannak más módszerek is. Talán ismeri a "Bejelentkezés Facebookkal" típusú gombokat, vagy a Google/Twitter/GitHub segítségével történő bejelentkezést néhány oldalon. A Nette segítségével bármilyen bejelentkezési módszerünk lehet, vagy akár kombinálhatjuk is őket. Ez csak rajtunk múlik. -Normális esetben saját autentikátort írnál, de ehhez az egyszerű kis bloghoz a beépített autentikátort fogjuk használni, amely egy konfigurációs fájlban tárolt jelszó és felhasználónév alapján hitelesíti a felhasználót. Ez jó tesztelési célokra. Tehát a következő *security* szakaszt adjuk hozzá a `config/common.neon` konfigurációs fájlhoz: +Általában saját authentikátort írnánk, de ehhez az egyszerű kis bloghoz a beépített authentikátort fogjuk használni, amely a konfigurációs fájlban tárolt jelszó és felhasználónév alapján jelentkeztet be. Tesztelési célokra jól használható. Adjunk hozzá tehát a következő `security` szekciót a `config/common.neon` konfigurációs fájlhoz: ```neon .{file:config/common.neon} security: users: - admin: secret # user 'admin', password 'secret' + admin: secret # felhasználó 'admin', jelszó 'secret' ``` A Nette automatikusan létrehoz egy szolgáltatást a DI konténerben. -Bejelentkezési űrlap .[#toc-sign-in-form] -========================================= +Bejelentkezési űrlap +==================== -Most már készen áll a hitelesítés backend része, és meg kell adnunk egy felhasználói felületet, amelyen keresztül a felhasználó bejelentkezik. Hozzunk létre egy új bemutatót *SignPresenter* néven, amely a következő lesz +Most már készen áll az authentikáció, és elő kell készítenünk a felhasználói felületet a bejelentkezéshez. Hozzunk létre tehát egy új presentert `SignPresenter` néven, amely: -- megjelenít egy bejelentkezési űrlapot (felhasználónév és jelszó megadásával) -- az űrlap elküldésekor hitelesíti a felhasználót -- kijelentkezési műveletet biztosít +- megjeleníti a bejelentkezési űrlapot (felhasználónévvel és jelszóval) +- az űrlap elküldése után ellenőrzi a felhasználót +- lehetőséget biztosít a kijelentkezésre -Kezdjük a bejelentkezési űrlappal. Azt már tudjuk, hogyan működnek az űrlapok a prezenterben. Hozzuk létre a `SignPresenter` és a `createComponentSignInForm` metódust. Ennek így kell kinéznie: +Kezdjük a bejelentkezési űrlappal. Már tudjuk, hogyan működnek az űrlapok a presenterekben. Hozzunk létre tehát egy `SignPresenter` presentert, és írjuk meg a `createComponentSignInForm` metódust. Valahogy így kellene kinéznie: -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Sign; use Nette; use Nette\Application\UI\Form; @@ -40,69 +40,69 @@ final class SignPresenter extends Nette\Application\UI\Presenter protected function createComponentSignInForm(): Form { $form = new Form; - $form->addText('username', 'Username:') - ->setRequired('Please enter your username.'); + $form->addText('username', 'Felhasználónév:') + ->setRequired('Kérjük, töltse ki a felhasználónevét.'); - $form->addPassword('password', 'Password:') - ->setRequired('Please enter your password.'); + $form->addPassword('password', 'Jelszó:') + ->setRequired('Kérjük, töltse ki a jelszavát.'); - $form->addSubmit('send', 'Sign in'); + $form->addSubmit('send', 'Bejelentkezés'); - $form->onSuccess[] = [$this, 'signInFormSucceeded']; + $form->onSuccess[] = $this->signInFormSucceeded(...); return $form; } } ``` -A felhasználónév és a jelszó megadására van lehetőség. +Vannak mezők a felhasználónévhez és a jelszóhoz. -Sablon .[#toc-template] ------------------------ +Sablon +------ -Az űrlap a sablonban lesz megjelenítve `in.latte` +Az űrlap az `in.latte` sablonban fog renderelődni: -```latte .{file:app/Presenters/templates/Sign/in.latte} +```latte .{file:app/Presentation/Sign/in.latte} {block content} -<h1 n:block=title>Sign in</h1> +<h1 n:block=title>Bejelentkezés</h1> {control signInForm} ``` -Bejelentkezés kezelő .[#toc-login-handler] ------------------------------------------- +Bejelentkezési callback +----------------------- -Hozzáadunk egy *űrlapkezelőt* is a felhasználó bejelentkezéséhez, amely az űrlap elküldése után azonnal meghívásra kerül. +Ezután hozzáadjuk a felhasználó bejelentkezéséhez szükséges callbacket, amely az űrlap sikeres elküldése után azonnal meghívódik. -A kezelő csak a felhasználó által megadott felhasználónevet és jelszót veszi át, és továbbítja a korábban definiált hitelesítőnek. Miután a felhasználó bejelentkezett, átirányítjuk őt a kezdőlapra. +A callback csak átveszi a felhasználó által kitöltött felhasználónevet és jelszót, és átadja azokat az authentikátornak. Bejelentkezés után átirányítunk a kezdőoldalra. -```php .{file:app/Presenters/SignPresenter.php} -public function signInFormSucceeded(Form $form, \stdClass $data): void +```php .{file:app/Presentation/Sign/SignPresenter.php} +private function signInFormSucceeded(Form $form, \stdClass $data): void { try { $this->getUser()->login($data->username, $data->password); $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { - $form->addError('Incorrect username or password.'); + $form->addError('Helytelen felhasználónév vagy jelszó.'); } } ``` -A [User::login() |api:Nette\Security\User::login()] metódusnak kivételt kell dobnia, ha a felhasználónév vagy a jelszó nem egyezik a korábban definiáltakkal. Mint már tudjuk, ez egy [Tracy |tracy:] red-screen-t eredményezne, vagy, termelési üzemmódban, egy belső szerverhibáról tájékoztató üzenetet. Ezt nem szeretnénk. Ezért elkapjuk a kivételt, és egy szép és barátságos hibaüzenetet adunk az űrlaphoz. +A [User::login() |api:Nette\Security\User::login()] metódus kivételt dob, ha a felhasználónév és a jelszó nem egyezik a konfigurációs fájlban szereplő adatokkal. Ahogy már tudjuk, ez piros hibaoldalt eredményezhet, vagy éles módban egy szerverhibáról tájékoztató üzenetet. Ezt azonban nem akarjuk. Ezért elkapjuk ezt a kivételt, és egy szép, felhasználóbarát hibaüzenetet adunk át az űrlapnak. -Ha az űrlapon hiba lép fel, az űrlapot tartalmazó oldal újra megjelenik, és az űrlap felett egy kedves üzenet jelenik meg, amely tájékoztatja a felhasználót, hogy rossz felhasználónevet vagy jelszót adott meg. +Amint hiba történik az űrlapon, az űrlapot tartalmazó oldal újrarajzolódik, és az űrlap felett megjelenik egy szép üzenet, amely tájékoztatja a felhasználót, hogy rossz felhasználónevet vagy jelszót adott meg. -Az előadók biztonsága .[#toc-security-of-presenters] -==================================================== +Presenterek védelme +=================== -Biztosítani fogunk egy űrlapot a hozzászólások hozzáadásához és szerkesztéséhez. Ezt a prezenterben határozzuk meg `EditPresenter`. A cél az, hogy a nem bejelentkezett felhasználók ne férhessenek hozzá az oldalhoz. +Biztosítsuk a bejegyzések hozzáadására és szerkesztésére szolgáló űrlapot. Ez az `EditPresenter` presenterben van definiálva. A cél az, hogy megakadályozzuk a nem bejelentkezett felhasználók hozzáférését az oldalhoz. -Létrehozunk egy `startup()` metódust, amely a [prezenter életciklusának |application:presenters#life-cycle-of-presenter] kezdetén azonnal elindul. Ez a nem bejelentkezett felhasználókat a bejelentkezési űrlapra irányítja át. +Létrehozunk egy `startup()` metódust, amely azonnal elindul a [presenter életciklusának |application:presenters#Presenter életciklusa] elején. Ez átirányítja a nem bejelentkezett felhasználókat a bejelentkezési űrlapra. -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} public function startup(): void { parent::startup(); @@ -114,67 +114,66 @@ public function startup(): void ``` -Linkek elrejtése .[#toc-hide-links] ------------------------------------ +Linkek elrejtése +---------------- -Egy nem hitelesített felhasználó már nem láthatja sem a *készítés*, sem a *szerkesztés oldalt*, de a rájuk mutató linkeket továbbra is láthatja. Rejtsük el azokat is. Az egyik ilyen link a `app/Presenters/templates/Home/default.latte` oldalon található, és csak akkor legyen látható, ha a felhasználó bejelentkezett. +Az illetéktelen felhasználó már nem láthatja a *create* vagy *edit* oldalt, de továbbra is láthatja a rájuk mutató linkeket. Ezeket is el kellene rejtenünk. Egy ilyen link az `app/Presentation/Home/default.latte` sablonban található, és csak a bejelentkezett felhasználók láthatják. -Ezt a `n:if` nevű *n:attribútummal* tudjuk elrejteni. Ha a benne lévő utasítás a `false`, akkor az egész `<a>` tag és annak tartalma nem jelenik meg: +Elrejthetjük egy *n:attribútum* használatával, amelynek neve `n:if`. Ha ez a feltétel `false`, az egész `<a>` tag, beleértve a tartalmát is, rejtve marad. ```latte -<a n:href="Edit:create" n:if="$user->isLoggedIn()">Create post</a> +<a n:href="Edit:create" n:if="$user->isLoggedIn()">Bejegyzés létrehozása</a> ``` -Ez egy rövidítés (ne keverjük össze a `tag-if` címmel): +ami a következő jelölés rövidítése (nem összetévesztendő a `tag-if`-fel): ```latte -{if $user->isLoggedIn()}<a n:href="Edit:create">Create post</a>{/if} +{if $user->isLoggedIn()}<a n:href="Edit:create">Bejegyzés létrehozása</a>{/if} ``` -Hasonló módon el kell rejtenie a `app/Presenters/templates/Post/show.latte` alatt található szerkesztési linket. +Ugyanígy elrejtjük a linket az `app/Presentation/Post/show.latte` sablonban is. -Bejelentkezési űrlap link .[#toc-login-form-link] -================================================= +Bejelentkezési link +=================== -Szia, de hogyan jutunk el a bejelentkezési oldalra? Nincs rá mutató link. Adjunk hozzá egyet a `@layout.latte` sablonfájlban. Próbálj meg találni egy szép helyet, lehet bárhol, ahol a legjobban tetszik. +Hogyan jutunk el valójában a bejelentkezési oldalra? Nincs rá mutató link. Adjunk hozzá egyet az `@layout.latte` sablonhoz. Próbáljon megfelelő helyet találni - szinte bárhol lehet. -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <ul class="navig"> - <li><a n:href="Home:">Home</a></li> + <li><a n:href="Home:">Cikkek</a></li> {if $user->isLoggedIn()} - <li><a n:href="Sign:out">Sign out</a></li> + <li><a n:href="Sign:out">Kijelentkezés</a></li> {else} - <li><a n:href="Sign:in">Sign in</a></li> + <li><a n:href="Sign:in">Bejelentkezés</a></li> {/if} </ul> ... ``` -Ha a felhasználó még nincs bejelentkezve, akkor a "Sign in" linket fogjuk megjeleníteni. Ellenkező esetben a "Kijelentkezés" linket fogjuk megjeleníteni. Ezt a műveletet a SignPresenterben adjuk hozzá. +Ha a felhasználó nincs bejelentkezve, a "Bejelentkezés" link jelenik meg. Ellenkező esetben a "Kijelentkezés" link jelenik meg. Ezt az akciót is hozzáadjuk a `SignPresenter`-hez. -A kijelentkezési művelet így néz ki, és mivel azonnal átirányítjuk a felhasználót, nincs szükség nézetsablonra. +Mivel a felhasználót kijelentkezés után azonnal átirányítjuk, nincs szükség sablonra. A kijelentkezés így néz ki: -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} public function actionOut(): void { $this->getUser()->logout(); - $this->flashMessage('You have been signed out.'); + $this->flashMessage('A kijelentkezés sikeres volt.'); $this->redirect('Home:'); } ``` -Csak meghívja a `logout()` metódust, majd egy szép üzenetet mutat a felhasználónak. +Csak a `logout()` metódus hívódik meg, majd megjelenik egy szép üzenet, amely megerősíti a sikeres kijelentkezést. -Összefoglaló .[#toc-summary] -============================ +Összegzés +========= -Van egy linkünk a bejelentkezéshez és a felhasználó kijelentkezéséhez is. A hitelesítéshez a beépített hitelesítőt használtuk, a bejelentkezési adatok pedig a konfigurációs fájlban vannak, mivel ez egy egyszerű tesztalkalmazás. A szerkesztési űrlapokat is biztosítottuk, hogy csak a bejelentkezett felhasználók tudjanak hozzászólásokat hozzáadni és szerkeszteni. +Van linkünk a bejelentkezéshez és a felhasználó kijelentkezéséhez is. Az ellenőrzéshez a beépített authentikátort használtuk, és a bejelentkezési adatokat a konfigurációs fájlban tároljuk, mivel ez egy egyszerű tesztalkalmazás. Biztosítottuk a szerkesztési űrlapokat is, így csak a bejelentkezett felhasználók adhatnak hozzá és szerkeszthetnek bejegyzéseket. .[note] -Itt olvashat bővebben a [felhasználói bejelentkezésről |security:authentication] és az [engedélyezésről |security:authorization]. +Itt olvashat többet a [felhasználók bejelentkeztetéséről |security:authentication] és a [jogosultságok ellenőrzéséről |security:authorization]. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/hu/comments.texy b/quickstart/hu/comments.texy index 757277cc23..bd9ffb4b67 100644 --- a/quickstart/hu/comments.texy +++ b/quickstart/hu/comments.texy @@ -1,28 +1,28 @@ -Megjegyzések -************ +Kommentek +********* -A blogot beüzemeltük, írtunk néhány nagyon jó blogbejegyzést és közzétettük őket az Adminer-en keresztül. Az emberek olvassák a blogot, és nagyon lelkesek az ötleteink iránt. Naponta sok dicsérő e-mailt kapunk. De mire jó ez a sok dicséret, ha csak e-mailben kaptuk meg, így senki más nem tudja elolvasni? Nem lenne jobb, ha az emberek közvetlenül a blogon tudnának hozzászólni, hogy mindenki más is olvashassa, milyen nagyszerűek vagyunk? +Feltöltöttük a blogot a webszerverre, és közzétettünk néhány nagyon érdekes bejegyzést az Adminer segítségével. Az emberek olvassák a blogunkat, és nagyon lelkesek érte. Minden nap sok dicsérő e-mailt kapunk. De mit ér ez a sok dicséret, ha csak e-mailben van meg, és senki sem olvashatja el? Jobb lenne, ha az olvasó közvetlenül kommentelhetné a cikket, így mindenki elolvashatná, milyen csodálatosak vagyunk. -Tegyük az összes cikket kommentelhetővé. +Tehát programozzunk kommenteket. -Új táblázat létrehozása .[#toc-creating-a-new-table] -==================================================== +Új tábla létrehozása +==================== -Indítsa el újra az Adminer programot, és hozzon létre egy új táblát `comments` néven ezekkel az oszlopokkal: +Indítsuk el az Adminert, és hozzunk létre egy `comments` táblát a következő oszlopokkal: -- `id` int, check autoincrement (AI) -- `post_id`, idegen kulcs, amely a `posts` táblára hivatkozik. -- `name` varchar, hossza 255 -- `email` varchar, hossza 255 +- `id` int, jelöljük be az autoincrement-et (AI) +- `post_id`, idegen kulcs, amely a `posts` táblára hivatkozik +- `name` varchar, length 255 +- `email` varchar, length 255 - `content` text - `created_at` timestamp -Így kell kinéznie: +A táblának tehát valahogy így kell kinéznie: -[* adminer-comments.webp *] +[* adminer-comments.webp *] *** Az Adminerben létrehozott comments tábla struktúrája *** -Ne felejtsd el használni az InnoDB tábla tárolását, és nyomd meg a Mentés gombot. +Ne felejtse el újra az InnoDB tárolót használni. ```sql CREATE TABLE `comments` ( @@ -37,104 +37,104 @@ CREATE TABLE `comments` ( ``` -Formanyomtatvány a hozzászóláshoz .[#toc-form-for-commenting] -============================================================= +Kommentálási űrlap +================== -Először is létre kell hoznunk egy űrlapot, amely lehetővé teszi a felhasználók számára, hogy hozzászóljanak az oldalunkhoz. A Nette Framework fantasztikusan támogatja az űrlapokat. Ezeket egy prezenterben lehet konfigurálni és egy sablonban megjeleníteni. +Először létre kell hoznunk egy űrlapot, amely lehetővé teszi a felhasználók számára a bejegyzések kommentálását. A Nette Framework csodálatos támogatást nyújt az űrlapokhoz. Konfigurálhatjuk őket a presenterben, és megjeleníthetjük a sablonban. -A Nette Framework rendelkezik a *komponensek* fogalmával. A **komponens** egy újrafelhasználható osztály vagy kódrészlet, amely egy másik komponenshez csatolható. Még a prezenter is egy komponens. Minden komponens a komponens gyár segítségével jön létre. Definiáljuk tehát a `PostPresenter` oldalon a comments form factory-t . +A Nette Framework a *komponensek* koncepcióját használja. A **komponens** egy újra felhasználható osztály vagy kódrészlet, amely egy másik komponenshez csatolható. Még a presenter is egy komponens. Minden komponenst egy factory hoz létre. Tehát létrehozunk egy factory-t a komment űrlaphoz a `PostPresenter`-ben. -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} protected function createComponentCommentForm(): Form { - $form = new Form; // jelentése: Nette\Application\UI\Form + $form = new Form; // means Nette\Application\UI\Form - $form->addText('name', 'Az Ön neve:') + $form->addText('name', 'Név:') ->setRequired(); - $form->addEmail('email', 'Email:'); + $form->addEmail('email', 'E-mail:'); - $form->addTextArea('content', 'Megjegyzés:') + $form->addTextArea('content', 'Komment:') ->setRequired(); - $form->addSubmit('send', 'Publish comment'); + $form->addSubmit('send', 'Komment közzététele'); return $form; } ``` -Magyarázzuk el egy kicsit. Az első sor létrehoz egy új példányt a `Form` komponensből. A következő metódusok HTML-bemeneteket csatolnak az űrlap definíciójához. `->addText` a következő formában jelenik meg `<input type=text name=name>`, a `<label>Your name:</label>`. Ahogy már most kitalálhattad, a `->addTextArea` csatol egy `<textarea>` és a `->addSubmit` hozzáad egy `<input type=submit>`. Több ilyen módszer is van, de most csak ennyit kell tudnod. [Többet megtudhatsz a dokumentációban |forms:]. +Magyarázzuk el ezt is egy kicsit. Az első sor létrehoz egy új `Form` komponens példányt. A következő metódusok HTML inputokat csatolnak ennek az űrlapnak a definíciójához. A `->addText` `<input type="text" name="name">`-ként jelenik meg `<label>Név:</label>`-lel. Ahogy valószínűleg helyesen sejti, a `->addTextArea` `<textarea>`-ként, a `->addSubmit` pedig `<input type="submit">`-ként jelenik meg. Sokkal több hasonló metódus létezik, de ezek egyelőre elegendőek ehhez az űrlaphoz. Többet a [dokumentációban olvashat |forms:]. -Miután az űrlapkomponens definiálva van egy prezenterben, megjeleníthetjük (megjeleníthetjük) egy sablonban. Ehhez helyezze a `{control}` címkét a poszt részlet sablonjának végére, a `Post/show.latte`. Mivel a komponens neve `commentForm` (a `createComponentCommentForm` metódus nevéből származik), a címke így fog kinézni +Ha az űrlap már definiálva van a presenterben, megjeleníthetjük (kirajzolhatjuk) a sablonban. Ezt úgy tehetjük meg, hogy a `{control}` taget elhelyezzük a sablon végén, amely egy konkrét bejegyzést jelenít meg, a `Post/show.latte`-ban. Mivel a komponens neve `commentForm` (a név a `createComponentCommentForm` metódus nevéből származik), a tag így fog kinézni: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... -<h2>Post new comment</h2> +<h2>Új komment hozzáadása</h2> {control commentForm} ``` -Most, ha megnézzük valamelyik bejegyzés részletét, egy új űrlapot fogunk találni a hozzászólások írásához. +Ha most megtekinti a bejegyzés részleteit tartalmazó oldalt, annak végén látni fogja az új komment űrlapot. -Mentés az adatbázisba .[#toc-saving-to-database] -================================================ +Mentés az adatbázisba +===================== -Próbált már adatokat elküldeni? Bizonyára észrevette, hogy az űrlap nem hajt végre semmilyen műveletet. Csak ott van, jól néz ki, de nem csinál semmit. Egy callback metódust kell hozzá csatolnunk, amely elmenti a beküldött adatokat. +Próbálta már kitölteni és elküldeni az űrlapot? Valószínűleg észrevette, hogy az űrlap valójában semmit sem csinál. Csatolnunk kell egy callback metódust, amely elmenti az elküldött adatokat. -Helyezzük a következő sort a `return` sor elé a `commentForm` komponens gyárában: +A `return` előtti sorba a `commentForm` komponens factory-jában helyezzük el a következő sort: ```php -$form->onSuccess[] = [$this, 'commentFormSucceeded']; +$form->onSuccess[] = $this->commentFormSucceeded(...); ``` -Ez azt jelenti, hogy "az űrlap sikeres elküldése után hívja meg az aktuális prezenter `commentFormSucceeded` metódusát". Ez a metódus még nem létezik, ezért hozzuk létre. +Az előző írásmód azt jelenti, hogy "az űrlap sikeres elküldése után hívd meg a `commentFormSucceeded` metódust a jelenlegi presenterből". Ez a metódus azonban még nem létezik. Hozzuk létre tehát: -```php .{file:app/Presenters/PostPresenter.php} -public function commentFormSucceeded(\stdClass $data): void +```php .{file:app/Presentation/Post/PostPresenter.php} +private function commentFormSucceeded(\stdClass $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); $this->database->table('comments')->insert([ - 'post_id' => $postId, + 'post_id' => $id, 'name' => $data->name, 'email' => $data->email, 'content' => $data->content, ]); - $this->flashMessage('Thank you for your comment', 'success'); + $this->flashMessage('Köszönöm a kommentet', 'success'); $this->redirect('this'); } ``` -Rögtön a `commentForm` komponensgyár után kell elhelyeznünk. +Ezt a metódust közvetlenül a `commentForm` űrlap factory-ja után helyezzük el. -Az új metódusnak egy argumentuma van, ami a beküldendő űrlap példánya, amelyet a komponensgyár hozott létre. A benyújtott értékeket a `$data` címen kapjuk meg. Ezután pedig beszúrjuk az adatokat az `comments` adatbázis táblába. +Ennek az új metódusnak egy argumentuma van, ami az elküldött űrlap példánya - amelyet a factory hozott létre. Az elküldött értékeket a `$data`-ban kapjuk meg. Ezután az adatokat elmentjük a `comments` adatbázis táblába. -Még két metódushívást kell elmagyarázni. A redirect szó szerint átirányít az aktuális oldalra. Ezt minden alkalommal meg kell tennünk, amikor az űrlapot elküldtük, érvényes, és a callback művelet megtette, amit kellett volna. Továbbá, ha az űrlap elküldése után átirányítod az oldalt, nem fogod látni a jól ismert `Would you like to submit the post data again?` üzenetet, amelyet néha láthatsz a böngészőben. (Általánosságban elmondható, hogy egy űrlap `POST` módszerrel történő elküldése után mindig át kell irányítania a felhasználót a `GET` műveletre). +Van még két másik metódus, amelyek magyarázatot érdemelnek. A redirect metódus szó szerint visszairányít az aktuális oldalra. Ezt célszerű minden űrlap elküldése után megtenni, ha érvényes adatokat tartalmazott, és a callback a műveletet a tervek szerint hajtotta végre. Továbbá, ha az űrlap elküldése után átirányítjuk az oldalt, nem fogjuk látni a jól ismert `Szeretné újra elküldeni az űrlap adatait?` üzenetet, amelyet néha a böngészőben láthatunk. (Általában az űrlap `POST` metódussal történő elküldése után mindig átirányításnak kell következnie egy `GET` akcióra.) -A `flashMessage` arra szolgál, hogy tájékoztassa a felhasználót valamilyen művelet eredményéről. Mivel átirányítunk, az üzenetet nem lehet közvetlenül a sablonhoz átadni és megjeleníteni. Ezért van ez a metódus, amely tárolja és a következő oldalbetöltéskor elérhetővé teszi. A flash üzeneteket az alapértelmezett `app/Presenters/templates/@layout.latte` fájlban rendereljük, és ez így néz ki: +A `flashMessage` metódus arra szolgál, hogy tájékoztassa a felhasználót valamilyen művelet eredményéről. Mivel átirányítunk, az üzenetet nem lehet egyszerűen átadni a sablonnak és megjeleníteni. Ezért van ez a metódus, amely elmenti ezt az üzenetet, és elérhetővé teszi a következő oldalbetöltéskor. A Flash üzenetek a fő sablonban, az `app/Presentation/@layout.latte`-ban jelennek meg, és így néznek ki: -```latte +```latte .{file:app/Presentation/@layout.latte} <div n:foreach="$flashes as $flash" n:class="flash, $flash->type"> {$flash->message} </div> ``` -Mint már tudjuk, automatikusan átkerülnek a sablonba, így nem kell sokat gondolkodni rajta, egyszerűen csak működik. További részletekért [nézd meg a dokumentációt |application:presenters#flash-messages]. +Ahogy már tudjuk, a flash üzenetek automatikusan átadódnak a sablonnak, így nem kell sokat gondolkodnunk rajta, egyszerűen működik. További információkért [látogassa meg a dokumentációt |application:presenters#Flash üzenetek]. -A megjegyzések megjelenítése .[#toc-rendering-the-comments] -=========================================================== +Kommentek megjelenítése +======================= -Ez az egyik olyan dolog, amit egyszerűen imádni fogsz. A Nette adatbázisnak van ez a menő funkciója, amit úgy hívnak, hogy [Explorer |database:explorer]. Emlékszel, hogy a táblákat InnoDB-ként hoztuk létre? Az Adminer létrehozta az úgynevezett [idegen kulcsokat |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html], amelyek rengeteg munkát takarítanak meg nekünk. +Ez az egyik olyan dolog, amit egyszerűen imádni fog. A Nette Database nagyszerű funkcióval rendelkezik, amelyet [Explorer |database:explorer]-nek hívnak. Emlékszik még, hogy az adatbázis tábláit szándékosan InnoDB tárolóval hoztuk létre? Az Adminer így létrehozott valamit, amit [idegen kulcsok |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html]-nak neveznek, amelyek sok munkát takarítanak meg nekünk. -A Nette Database Explorer az idegen kulcsokat használja a táblák közötti kapcsolatok feloldására, és a kapcsolatok ismeretében automatikusan lekérdezéseket tud létrehozni számodra. +A Nette Database Explorer idegen kulcsokat használ a táblák közötti kapcsolatok feloldására, és ezen kapcsolatok ismeretében automatikusan képes adatbázis lekérdezéseket létrehozni. -Mint emlékezhetsz, a `$post` változót átadtuk a sablonban a `PostPresenter::renderShow()`, és most szeretnénk végigmenni az összes olyan megjegyzésen, amelynek a `post_id` oszlopa megegyezik a mi `$post->id`-unkkal . Ezt a `$post->related('comments')` meghívásával teheti meg. Ez ilyen egyszerű. Nézze meg a kapott kódot. +Ahogy biztosan emlékszik, a sablonnak átadtuk a `$post` változót a `PostPresenter::renderShow()` metódussal, és most iterálni szeretnénk az összes kommenten, amelyek `post_id` oszlopának értéke megegyezik a `$post->id`-val. Ezt a `$post->related('comments')` hívásával érhetjük el. Igen, ilyen egyszerűen. Nézzük meg az eredményül kapott kódot: -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { ... $this->template->post = $post; @@ -144,15 +144,15 @@ public function renderShow(int $postId): void És a sablont: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... -<h2>Comments</h2> +<h2>Kommentek</h2> <div class="comments"> {foreach $comments as $comment} <p><b><a href="mailto:{$comment->email}" n:tag-if="$comment->email"> {$comment->name} - </a></b> said:</p> + </a></b> írta:</p> <div>{$comment->content}</div> {/foreach} @@ -160,13 +160,12 @@ public function renderShow(int $postId): void ... ``` -Figyeljük meg a speciális `n:tag-if` attribútumot. Már tudod, hogyan működik a `n: attributes`. Nos, ha az attribútum elé a `tag-` attribútumot írja, az csak a címkéket fogja körbeölelni, a tartalmukat nem. Ez lehetővé teszi, hogy a hozzászóló nevét linkké alakítsa, ha megadta az e-mail címét. Ez a két sor azonos eredményt ad: +Figyelje meg a speciális `n:tag-if` attribútumot. Már tudja, hogyan működnek az `n:attribútumok`. Ha az attribútumhoz `tag-` előtagot csatol, a funkcionalitás csak a HTML tagre vonatkozik, nem a tartalmára. Ez lehetővé teszi számunkra, hogy a kommentelő nevét csak akkor tegyük linkké, ha megadta az e-mail címét. Ez a két sor azonos: ```latte -<strong n:tag-if="$important"> Hello there! </strong> +<strong n:tag-if="$important"> Jó napot! </strong> -{if $important}<strong>{/if} Hello there! {if $important}</strong>{/if} +{if $important}<strong>{/if} Jó napot! {if $important}</strong>{/if} ``` {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/hu/creating-posts.texy b/quickstart/hu/creating-posts.texy index 5a72375b9a..972645d4ce 100644 --- a/quickstart/hu/creating-posts.texy +++ b/quickstart/hu/creating-posts.texy @@ -1,30 +1,30 @@ -Hozzászólások létrehozása és szerkesztése -***************************************** +Bejegyzések létrehozása és szerkesztése +*************************************** -Micsoda remek időtöltés. Van egy szuper klassz új blogunk, az emberek vitatkoznak a kommentekben, és végre van egy kis időnk több programozásra. Bár szeretjük az Adminert, de nem olyan kényelmes benne blogbejegyzéseket írni. Talán itt az ideje, hogy egy egyszerű űrlapot adjunk hozzá, amivel közvetlenül az alkalmazásunkból lehet új bejegyzéseket hozzáadni. Csináljuk meg. +Ez nagyszerű! Van egy szuper új blogunk, az emberek hevesen vitatkoznak a kommentekben, és végre van egy kis időnk további programozásra. Bár az Adminer nagyszerű eszköz, nem teljesen ideális új blogbejegyzések írására. Valószínűleg itt az ideje létrehozni egy egyszerű űrlapot új bejegyzések hozzáadására közvetlenül az alkalmazásból. Lássunk hozzá. Kezdjük a felhasználói felület megtervezésével: 1. A kezdőlapon adjunk hozzá egy "Új bejegyzés írása" linket. -2. Megjelenít egy űrlapot címmel és szövegterülettel a tartalomhoz. -3. A Mentés gombra kattintva elmenti a blogbejegyzést. +2. Ez a link megjelenít egy űrlapot címsorral és egy textarea-val a bejegyzés tartalmához. +3. Amikor a Mentés gombra kattintunk, a bejegyzés elmentődik az adatbázisba. -Később hitelesítést is hozzáadunk, és csak a bejelentkezett felhasználók számára engedélyezzük az új bejegyzések hozzáadását. De ezt majd később tesszük meg. Milyen kódot kell írnunk ahhoz, hogy ez működjön? +Később hozzáadunk bejelentkezést is, és a bejegyzések hozzáadását csak a bejelentkezett felhasználók számára engedélyezzük. De ez csak később. Milyen kódot kell most írnunk, hogy minden működjön? -1. Hozzunk létre egy új bemutatót egy űrlappal a hozzászólások hozzáadásához. -2. Definiáljunk egy visszahívást, amely az űrlap sikeres elküldése után lép működésbe, és amely elmenti az új bejegyzést az adatbázisba. -3. Hozzon létre egy új sablont az űrlaphoz. -4. Adjon hozzá egy linket az űrlaphoz a főoldal sablonjához. +1. Létrehozunk egy új presentert egy űrlappal a bejegyzések hozzáadásához. +2. Definiálunk egy callback-et, amely az űrlap sikeres elküldése után fut le, és amely az új bejegyzést elmenti az adatbázisba. +3. Létrehozunk egy új sablont, amelyen ez az űrlap lesz. +4. Hozzáadunk egy linket az űrlaphoz a főoldal sablonjában. -Új bemutató .[#toc-new-presenter] -================================= +Új presenter +============ -Nevezzük el az új prezentert `EditPresenter` és mentsük el a `app/Presenters/EditPresenter.php` címre. Az új prezenternek is csatlakoznia kell az adatbázishoz, ezért itt is írunk egy konstruktort, amelyhez adatbázis-kapcsolat szükséges: +Az új presentert `EditPresenter`-nek nevezzük el, és az `app/Presentation/Edit/` könyvtárba mentjük. Szüksége van adatbázis-kapcsolatra is, ezért itt is írunk egy konstruktort, amely adatbázis-kapcsolatot igényel: -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Edit; use Nette; use Nette\Application\UI\Form; @@ -39,95 +39,95 @@ final class EditPresenter extends Nette\Application\UI\Presenter ``` -Form for Saving Posts .[#toc-form-for-saving-posts] -=================================================== +Bejegyzések mentési űrlapja +=========================== -Az űrlapokkal és komponensekkel már foglalkoztunk, amikor a hozzászólások támogatását adtuk hozzá. Ha zavarban vagy a témával kapcsolatban, nézd meg újra, [hogyan működnek az űrlapok és a komponensek |comments#form-for-commenting], mi itt várunk ;) +Az űrlapokat és komponenseket már elmagyaráztuk a kommentek létrehozásakor. Ha még mindig nem világos, menjen át az [űrlapok és komponensek létrehozása |comments#Kommentálási űrlap] részen, mi addig itt várunk ;) -Most adjuk hozzá ezt a metódust a `EditPresenter`: +Most adjuk hozzá ezt a metódust az `EditPresenter`-hez: -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} protected function createComponentPostForm(): Form { $form = new Form; - $form->addText('title', 'Title:') + $form->addText('title', 'Címsor:') ->setRequired(); - $form->addTextArea('content', 'Content:') + $form->addTextArea('content', 'Tartalom:') ->setRequired(); - $form->addSubmit('send', 'Save and publish'); - $form->onSuccess[] = [$this, 'postFormSucceeded']; + $form->addSubmit('send', 'Mentés és közzététel'); + $form->onSuccess[] = $this->postFormSucceeded(...); return $form; } ``` -Új hozzászólás mentése űrlapról .[#toc-saving-new-post-from-form] -================================================================= +Új bejegyzés mentése az űrlapról +================================ -Folytassa egy kezelő metódus hozzáadásával. +Folytassuk egy metódus hozzáadásával, amely feldolgozza az űrlap adatait: -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { $post = $this->database ->table('posts') ->insert($data); - $this->flashMessage('Post was published', 'success'); + $this->flashMessage("A bejegyzés sikeresen közzétéve.", 'success'); $this->redirect('Post:show', $post->id); } ``` -Csak egy gyors magyarázat: lekérdezi az értékeket az űrlapról, beilleszti őket az adatbázisba, létrehoz egy üzenetet a felhasználónak, hogy a bejegyzés sikeresen el lett mentve, és átirányít arra az oldalra, ahol az adott bejegyzés megjelent, hogy megnézhesse, hogyan néz ki. +Csak egy gyors összefoglaló: ez a metódus megszerzi az adatokat az űrlapról, beilleszti őket az adatbázisba, létrehoz egy üzenetet a felhasználónak a bejegyzés sikeres mentéséről, és átirányít az új bejegyzés oldalára, hogy azonnal lássuk, hogyan néz ki. -Új bejegyzés létrehozására szolgáló oldal .[#toc-page-for-creating-a-new-post] -============================================================================== +Oldal új bejegyzés létrehozásához +================================= -Hozzuk csak létre a `Edit/create.latte` sablont: +Most hozzuk létre az `Edit/create.latte` sablont: -```latte .{file:app/Presenters/templates/Edit/create.latte} +```latte .{file:app/Presentation/Edit/create.latte} {block content} -<h1>New post</h1> +<h1>Új bejegyzés</h1> {control postForm} ``` -Mostanra már mindennek világosnak kell lennie. Az utolsó sorban látható az űrlap, amit most fogunk létrehozni. +Mindennek már világosnak kell lennie. Az utolsó sor megjeleníti az űrlapot, amelyet még csak most fogunk létrehozni. -Létrehozhatnánk egy megfelelő `renderCreate` metódust is, de erre nincs szükség. Nincs szükségünk arra, hogy adatokat kérjünk az adatbázisból és átadjuk a sablonhoz, így ez a metódus üres lenne. Ilyen esetben a metódus egyáltalán nem is létezhet. +Létrehozhatnánk egy megfelelő `renderCreate()` metódust is, de erre nincs szükség. Nem kell adatokat lekérnünk az adatbázisból és átadnunk a sablonnak, így ez a metódus üres lenne. Ilyen esetekben a metódusnak egyáltalán nem kell léteznie. -Link a hozzászólások létrehozásához .[#toc-link-for-creating-posts] -=================================================================== +Link a bejegyzések létrehozásához +================================= -Valószínűleg már tudod, hogyan adhatsz linket a `EditPresenter` és a `create` művelethez. Próbálja ki. +Valószínűleg már tudja, hogyan adjon hozzá egy linket az `EditPresenter`-hez és annak `create` akciójához. Próbálja ki. -Csak adja hozzá a `app/Presenters/templates/Home/default.latte` fájlhoz: +Csak adja hozzá az `app/Presentation/Home/default.latte` fájlhoz: ```latte -<a n:href="Edit:create">Write new post</a> +<a n:href="Edit:create">Új bejegyzés írása</a> ``` -Hozzászólások szerkesztése .[#toc-editing-posts] -================================================ +Bejegyzések szerkesztése +======================== -Adjuk hozzá a meglévő hozzászólások szerkesztésének lehetőségét is. Ez elég egyszerű lesz - már van `postForm` és ezt használhatjuk szerkesztésre is. +Most adjuk hozzá a bejegyzés szerkesztésének lehetőségét is. Nagyon egyszerű lesz. Már van egy kész `postForm` űrlapunk, és ezt használhatjuk a szerkesztéshez is. -Hozzáadunk egy új `edit` oldalt a `EditPresenter`: +Adjunk hozzá egy új `edit` oldalt az `EditPresenter`-hez: -```php .{file:app/Presenters/EditPresenter.php} -public function renderEdit(int $postId): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +public function renderEdit(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { - $this->error('Post not found'); + $this->error('A bejegyzés nem található'); } $this->getComponent('postForm') @@ -135,26 +135,26 @@ public function renderEdit(int $postId): void } ``` -És létrehozzuk a `Edit/edit.latte` sablont: +És hozzunk létre egy másik `Edit/edit.latte` sablont: -```latte .{file:app/Presenters/templates/Edit/edit.latte} +```latte .{file:app/Presentation/Edit/edit.latte} {block content} -<h1>Edit post</h1> +<h1>Bejegyzés szerkesztése</h1> {control postForm} ``` -És frissítsük a `postFormSucceeded` módszert, amely képes lesz új hozzászólást hozzáadni (ahogy most is), vagy a meglévőket szerkeszteni: +És módosítsuk a `postFormSucceeded` metódust, amely képes lesz új cikket hozzáadni (ahogy most is teszi), és egy már létező cikket szerkeszteni is: -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); - if ($postId) { + if ($id) { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); $post->update($data); } else { @@ -163,26 +163,25 @@ public function postFormSucceeded(array $data): void ->insert($data); } - $this->flashMessage('Post was published', 'success'); + $this->flashMessage('A bejegyzés sikeresen közzétéve.', 'success'); $this->redirect('Post:show', $post->id); } ``` -Ha a `postId` paramétert adjuk meg, az azt jelenti, hogy egy hozzászólást szerkesztünk. Ilyen esetben ellenőrizzük, hogy a bejegyzés valóban létezik-e, és ha igen, akkor frissítjük az adatbázisban. Ha a `postId` nincs megadva, akkor ez azt jelenti, hogy egy új bejegyzést kell hozzáadni. +Ha az `id` paraméter rendelkezésre áll, az azt jelenti, hogy egy bejegyzést fogunk szerkeszteni. Ebben az esetben ellenőrizzük, hogy a kért bejegyzés valóban létezik-e, és ha igen, frissítjük az adatbázisban. Ha az `id` paraméter nem áll rendelkezésre, akkor az azt jelenti, hogy új bejegyzést kell hozzáadni. -De honnan származik a `postId`? Ez a `renderEdit` metódusnak átadott paraméter. +De honnan származik ez az `id` paraméter? Ez az a paraméter, amelyet a `renderEdit` metódus kap meg az URL-ből. -Most már hozzáadhat egy linket a `app/Presenters/templates/Post/show.latte` sablonhoz: +Most hozzáadhatunk egy linket a szerkesztéshez az `app/Presentation/Post/show.latte` sablonhoz: ```latte -<a n:href="Edit:edit $post->id">Edit this post</a> +<a n:href="Edit:edit $post->id">Bejegyzés szerkesztése</a> ``` -Összefoglaló: .[#toc-summary] -============================= +Összegzés +========= -A blog működik, az emberek gyorsan kommentelnek, és már nem az Adminerre hagyatkozunk az új bejegyzések hozzáadásában. Teljesen független, és akár normális emberek is írhatnak oda. De várjunk csak, ez valószínűleg nem oké, hogy bárki, mármint tényleg bárki az interneten, posztolhat a blogunkba. Valamiféle hitelesítésre van szükség, hogy csak a bejelentkezett felhasználók tudjanak posztolni. Ezt a következő fejezetben adjuk hozzá. +A blog most már működőképes, a látogatók aktívan kommentelnek, és már nincs szükségünk az Adminerre a publikáláshoz. Az alkalmazás teljesen független, és bárki hozzáadhat új bejegyzést. Várjunk csak, ez valószínűleg nem teljesen helyes, hogy bárki - és itt tényleg bárkit értek, akinek van internet-hozzáférése - hozzáadhat új bejegyzéseket. Szükség van valamilyen biztonságra, hogy csak a bejelentkezett felhasználó adhasson hozzá új bejegyzést. Ezt a következő fejezetben fogjuk megvizsgálni. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/hu/home-page.texy b/quickstart/hu/home-page.texy index bbb0f10169..b4b963e599 100644 --- a/quickstart/hu/home-page.texy +++ b/quickstart/hu/home-page.texy @@ -1,41 +1,41 @@ -Blog kezdőlap -************* +A blog kezdőlapja +***************** .[perex] -Hozzuk létre a legutóbbi bejegyzéseinket megjelenítő kezdőlapot. +Most létrehozzuk a kezdőlapot, amely a legutóbbi bejegyzéseket jeleníti meg. -Mielőtt elkezdjük, legalább néhány alapismeretet tudnod kell a Model-View-Presenter tervezési mintáról (hasonlóan az MVC((Model-View-Controller))): +Mielőtt elkezdenénk, ismerni kell legalább a Model-View-Presenter tervezési minta alapjait (hasonlóan az MVC-hez ((Model-View-Controller))): -- **Modell** - adatmanipulációs réteg. Teljesen elkülönül az alkalmazás többi részétől. Csak a prezenterekkel kommunikál. +- **Modell** - az adatokkal dolgozó réteg. Teljesen elkülönül az alkalmazás többi részétől. Csak a presenterrel kommunikál. -- **Nézet** - egy front-end definíciós réteg. Sablonok segítségével megjeleníti a felhasználó számára a kért adatokat. +- **View** - a front-end réteg. A kért adatokat sablonok segítségével jeleníti meg, és megjeleníti a felhasználónak. -- **Presenter** (vagy Controller) - egy kapcsolati réteg. A bemutató összeköti a modellt és a nézetet. Kezeli a kéréseket, adatokat kér a Modelltől, majd átadja azokat az aktuális Nézetnek. +- **Presenter** (vagy Controller) - az összekötő réteg. A Presenter összeköti a Modellt és a View-t. Feldolgozza a kéréseket, lekérdezi az adatokat a Modelltől, és visszaküldi azokat a View-nak. -Egy olyan nagyon egyszerű alkalmazás esetén, mint a mi blogunk, a Model réteg valójában csak magának az adatbázisnak a lekérdezéséből áll - ehhez nincs szükségünk semmilyen extra PHP kódra. Nekünk csak a Presenter és a View rétegeket kell létrehoznunk. A Nette-ben minden Presenterhez saját Views tartozik, így mindkettővel egyszerre fogjuk folytatni. +Egyszerű alkalmazások esetén, mint a mi blogunk, az egész modell réteget csak adatbázis-lekérdezések alkotják - ehhez egyelőre nincs szükségünk extra kódra. Kezdetnek tehát csak presentereket és sablonokat hozunk létre. A Nette-ben minden presenternek saját sablonjai vannak, így ezeket egyszerre fogjuk létrehozni. -Az adatbázis létrehozása az Adminerrel .[#toc-creating-the-database-with-adminer] -================================================================================= +Adatbázis létrehozása az Adminer segítségével +============================================= -Az adatok tárolására a MySQL adatbázist fogjuk használni, mivel ez a legelterjedtebb választás a webfejlesztők körében. De ha nem tetszik, nyugodtan használjon egy tetszőleges adatbázist. +Az adatok tárolására MySQL adatbázist fogunk használni, mivel ez a legelterjedtebb a webalkalmazás-fejlesztők körében. Ha azonban nem szeretné használni, nyugodtan válasszon saját belátása szerint adatbázist. -Készítsük el az adatbázist, amely a blogbejegyzéseinket fogja tárolni. Kezdhetjük nagyon egyszerűen - csak egyetlen táblával a bejegyzéseknek. +Most előkészítjük az adatbázis struktúráját, ahol a blogunk cikkei lesznek tárolva. Nagyon egyszerűen kezdünk - csak egy táblát hozunk létre a bejegyzésekhez. -Az adatbázis létrehozásához letölthetjük [az Adminert |https://www.adminer.org], vagy használhatunk más adatbázis-kezelő eszközt is. +Az adatbázis létrehozásához letölthetjük az [Adminer |https://www.adminer.org]-t, vagy bármely más kedvenc adatbázis-kezelő eszközünket. Nyissuk meg az Adminert, és hozzunk létre egy új adatbázist `quickstart` néven. -Hozzunk létre egy új táblát `posts` néven, és adjuk hozzá ezeket az oszlopokat: -- `id` int, kattintsunk az autoincrement (AI) gombra. -- `title` varchar, hossza 255 -- `content` szöveg +Hozzunk létre egy új `posts` nevű táblát a következő oszlopokkal: +- `id` int, jelöljük be az autoincrement-et (AI) +- `title` varchar, length 255 +- `content` text - `created_at` timestamp -Így kell kinéznie: +Az eredményül kapott struktúrának így kell kinéznie: [* adminer-posts.webp *] @@ -49,9 +49,9 @@ CREATE TABLE `posts` ( ``` .[caution] -Nagyon fontos, hogy az **InnoDB** tábla tárolását használjuk. Később látni fogja az okát. Egyelőre csak válassza ezt és küldje el. Most már megnyomhatja a Mentés gombot. +Nagyon fontos az **InnoDB** tároló használata. Hamarosan megmutatjuk, miért. Egyelőre egyszerűen válassza ki, és kattintson a mentésre. -Próbáljon ki néhány minta blogbejegyzés hozzáadását, mielőtt megvalósítjuk az új bejegyzések hozzáadásának képességét közvetlenül az alkalmazásunkból. +Mielőtt létrehoznánk a cikkek adatbázisba való hozzáadásának lehetőségét az alkalmazáson keresztül, adjon hozzá néhány minta cikket a bloghoz manuálisan. ```sql INSERT INTO `posts` (`id`, `title`, `content`, `created_at`) VALUES @@ -61,37 +61,34 @@ INSERT INTO `posts` (`id`, `title`, `content`, `created_at`) VALUES ``` -Csatlakozás az adatbázishoz .[#toc-connecting-to-the-database] -============================================================== +Csatlakozás az adatbázishoz +=========================== -Most, hogy az adatbázis elkészült, és van benne néhány bejegyzés, itt az ideje, hogy megjelenítsük őket az új, csillogó oldalunkon. +Most, hogy az adatbázis már létre van hozva, és van benne néhány cikkünk, itt az ideje megjeleníteni őket a gyönyörű új oldalunkon. -Először is meg kell mondanunk az alkalmazásunknak, hogy melyik adatbázist használja. Az adatbázis-kapcsolat konfigurációját a `config/local.neon` oldalon tároljuk. Állítsuk be a DSN((Data Source Name)) kapcsolatot és a hitelesítő adatokat. Így kell kinéznie: +Először meg kell mondanunk az alkalmazásnak, hogy melyik adatbázist használja. Az adatbázis-kapcsolatot a `config/common.neon` fájlban állítjuk be DSN ((Data Source Name)) és bejelentkezési adatok segítségével. Valahogy így kell kinéznie: -```neon .{file:config/local.neon} +```neon .{file:config/common.neon} database: dsn: 'mysql:host=127.0.0.1;dbname=quickstart' - user: *enter user name* - password: *enter password here* + user: *itt adja meg a felhasználónevet* + password: *itt adja meg az adatbázis jelszavát* ``` .[note] -Figyeljen a behúzásokra a fájl szerkesztése közben. A [NEON formátum |neon:format] elfogadja a szóközöket és a tabulátorokat is, de nem mindkettőt együtt! A webes projekt konfigurációs fájlja alapértelmezés szerint tabulátorokat használ. +Ennek a fájlnak a szerkesztésekor ügyeljen a sorok behúzására. A [NEON |neon:format] formátum elfogadja mind a szóközökkel, mind a tabulátorokkal történő behúzást, de nem mindkettőt egyszerre. A Web Project alapértelmezett konfigurációs fájlja tabulátorokat használ. -A teljes konfiguráció, beleértve a `config/` fájlt is, a `common.neon` és a `local.neon` fájlokban található. A `common.neon` fájl tartalmazza az alkalmazás globális konfigurációját, a `local.neon` pedig csak a környezetre jellemző paramétereket (pl. a fejlesztési és a termelési szerver közötti különbség). +Adatbázis-kapcsolat átadása +=========================== -Az adatbázis-csatlakozás beillesztése .[#toc-injecting-the-database-connection] -=============================================================================== +A `HomePresenter` presenternek, amely a cikkek listázásáért felel, szüksége van adatbázis-kapcsolatra. Ennek megszerzéséhez a konstruktort használjuk, amely így fog kinézni: -A cikkeket listázó `HomePresenter` bemutatónak szüksége van egy adatbázis-kapcsolatra. Ahhoz, hogy megkapja, írjunk egy ilyen konstruktort: - -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; -use Nette\Application\UI\Form; final class HomePresenter extends Nette\Application\UI\Presenter { @@ -105,12 +102,12 @@ final class HomePresenter extends Nette\Application\UI\Presenter ``` -Hozzászólások betöltése az adatbázisból .[#toc-loading-posts-from-the-database] -=============================================================================== +Bejegyzések betöltése az adatbázisból +===================================== -Most pedig hívjuk le a bejegyzéseket az adatbázisból, és adjuk át a sablonhoz, amely ezután megjeleníti a HTML kódot. Erre szolgál az úgynevezett *render* metódus: +Most betöltjük a bejegyzéseket az adatbázisból, és elküldjük őket a sablonnak, amely azután HTML kódként jeleníti meg őket. Erre szolgál az úgynevezett *render* metódus: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} public function renderDefault(): void { $this->template->posts = $this->database @@ -120,41 +117,41 @@ public function renderDefault(): void } ``` -A prezenternek most egy renderelési metódusa van: `renderDefault()`, amely az adatokat a `default` nevű nézetnek adja át. A prezenter sablonok a `app/Presenters/templates/{PresenterName}/{viewName}.latte` oldalon találhatók, így ebben az esetben a sablon a `app/Presenters/templates/Home/default.latte` oldalon lesz. A sablonban most már elérhető egy `$posts` nevű változó, amely az adatbázisból származó bejegyzéseket tartalmazza. +A presenter most egy renderelő metódust tartalmaz, a `renderDefault()`-t, amely adatokat ad át az adatbázisból a sablonnak (View). A sablonok az `app/Presentation/{PresenterName}/{viewName}.latte` helyen találhatók, tehát ebben az esetben a sablon az `app/Presentation/Home/default.latte` helyen van. A sablonban most elérhető lesz a `$posts` változó, amelyben az adatbázisból lekérdezett bejegyzések találhatók. -Sablon .[#toc-template] -======================= +Sablon +====== -Van egy általános sablon az egész oldalra (az úgynevezett *layout*, fejléccel, stíluslapokkal, lábléccel, ...), majd specifikus sablonok az egyes nézetekhez (pl. a blogbejegyzések listájának megjelenítéséhez), amelyek felülírhatják a layout sablon egyes részeit. +Az egész weboldalhoz rendelkezésünkre áll egy fő sablon (amelyet *layout*-nak neveznek, tartalmazza a fejlécet, stílusokat, láblécet,...) és továbbá konkrét sablonok minden nézethez (View) (pl. a blogbejegyzések megjelenítéséhez), amelyek felülírhatják a fő sablon egyes részeit. -Alapértelmezés szerint az elrendezési sablon a `templates/@layout.latte` oldalon található, amely a következőket tartalmazza: +Alapértelmezés szerint a layout sablon az `app/Presentation/@layout.latte` helyen található, és tartalmazza: -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... {include content} ... ``` -`{include content}` beilleszt egy `content` nevű blokkot a fő sablonba. Ezt az egyes nézetek sablonjaiban határozhatja meg. Ebben az esetben a `Home/default.latte` fájlt így szerkesztjük: +A `{include content}` beilleszti a fő sablonba a `content` nevű blokkot. Ezt az egyes nézetek (View) sablonjaiban fogjuk definiálni. Esetünkben az `Home/default.latte` fájlt a következőképpen módosítjuk: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} Hello World {/block} ``` -Ez határozza meg a *tartalom* [blokkot |latte:tags#block], amely be lesz illesztve az elrendezésbe. Ha frissítjük a böngészőt, egy "Hello world" szövegű oldalt fogunk látni (a forráskódban a `@layout.latte`) által definiált HTML fejléccel és lábléccel is. +Ezzel definiáltuk a [blokkot |latte:tags#block] *content*, amely be lesz illesztve a fő layoutba. Ha újra frissítjük a böngészőt, egy oldalt látunk a "Hello World" szöveggel (a forráskódban az `@layout.latte`-ban definiált HTML fejléccel és lábléccel együtt). -Jelenítsük meg a blogbejegyzéseket - a sablont így szerkesztjük: +Jelenítsük meg a blogbejegyzéseket - a sablont a következőképpen módosítjuk: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - <h1>My blog</h1> + <h1>Blogom</h1> {foreach $posts as $post} <div class="post"> - <div class="date">{$post->created_at|date:'j. n. Y'}</div> + <div class="date">{$post->created_at|date:'F j, Y'}</div> <h2>{$post->title}</h2> @@ -164,42 +161,41 @@ Jelenítsük meg a blogbejegyzéseket - a sablont így szerkesztjük: {/block} ``` -Ha frissítjük a böngészőnket, megjelenik a blogbejegyzések listája. A lista nem túl díszes vagy színes, ezért nyugodtan adjunk hozzá néhány [csillogó CSS-t |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] a `www/css/style.css` címre, és linkeljük be egy elrendezésbe: +Ha frissítjük a böngészőt, látni fogjuk az összes bejegyzés listáját. A lista egyelőre nem túl szép, sem színes, ezért a `www/css/style.css` fájlba hozzáadhatunk néhány [CSS stílust |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css], és belinkelhetjük a layoutban: -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <link rel="stylesheet" href="{$basePath}/css/style.css"> </head> ... ``` -A `{foreach}` címke a `$posts` változóban a sablonhoz átadott összes bejegyzést átnézi, és minden egyes bejegyzéshez megjelenít egy darab HTML-kódot. Pontosan úgy, mint egy PHP kód. +A `{foreach}` tag iterál az összes bejegyzésen, amelyeket a `$posts` változóban adtunk át a sablonnak, és mindegyikhez megjeleníti az adott HTML darabot. Pontosan úgy viselkedik, mint a PHP kód. -A `|date` dolgot szűrőnek hívják. A szűrők a kimenet formázására szolgálnak. Ez a konkrét szűrő egy dátumot (pl. `2013-04-12`) alakít át olvashatóbb formájúra (`12. 4. 2013`). A `|truncate` szűrő a megadott maximális hosszúságra vágja le a karakterláncot, és egy ellipszist ad a végéhez, ha a karakterláncot lecsonkolja. Mivel ez egy előnézet, nincs értelme a cikk teljes tartalmát megjeleníteni. Más alapértelmezett szűrők megtalálhatók [a dokumentációban |latte:filters], vagy szükség esetén létrehozhat saját szűrőket. +A `|date:` írásmódot szűrőnek nevezzük. A szűrők a kimenet formázására szolgálnak. Ez a konkrét szűrő átalakítja a dátumot (pl. `2013-04-12`) annak olvashatóbb formájára (`April 12, 2013`). A `|truncate` szűrő levágja a stringet a megadott maximális hosszúságra, és ha a stringet lerövidíti, a végére három pontot tesz. Mivel ez egy előnézet, nincs értelme a teljes cikk tartalmát megjeleníteni. További alapértelmezett szűrőket [megtalálhatók a dokumentációban |latte:filters], vagy létrehozhatunk sajátokat, ha szükséges. -Még egy dolog. A kódot egy kicsit rövidebbé és ezáltal egyszerűbbé tehetjük. A *Latte címkéket* helyettesíthetjük *n:attribútumokkal*, így: +Még egy dolog. Az előző kódot lerövidíthetjük és egyszerűsíthetjük. Ezt a *Latte tagek* *n:attribútumokra* cserélésével érhetjük el: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - <h1>My blog</h1> + <h1>Blogom</h1> <div n:foreach="$posts as $post" class="post"> <div class="date">{$post->created_at|date:'F j, Y'}</div> <h2>{$post->title}</h2> - <div>{$post->content}</div> + <div>{$post->content|truncate:256}</div> </div> {/block} ``` -A `n:foreach`, egyszerűen körbecsomagolja a *div*-et egy *foreach* blokkal (pontosan ugyanazt teszi, mint az előző kódblokk). +Az `n:foreach` attribútum a *div* blokkot *foreach*-csel veszi körül (teljesen ugyanúgy működik, mint az előző kód). -Összefoglaló .[#toc-summary] -============================ +Összegzés +========= -Van egy nagyon egyszerű MySQL adatbázisunk, amelyben néhány blogbejegyzés található. Az alkalmazás csatlakozik az adatbázishoz, és megjeleníti a bejegyzések egyszerű listáját. +Most van egy nagyon egyszerű MySQL adatbázisunk néhány bejegyzéssel. Az alkalmazás csatlakozik ehhez az adatbázishoz, és egyszerű listát jelenít meg ezekről a bejegyzésekről a sablonban. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/hu/model.texy b/quickstart/hu/model.texy index 13eea18802..55f91cac57 100644 --- a/quickstart/hu/model.texy +++ b/quickstart/hu/model.texy @@ -1,13 +1,13 @@ Modell ****** -Ahogy alkalmazásunk növekszik, hamarosan rájövünk, hogy hasonló adatbázisműveleteket kell végrehajtanunk különböző helyeken és különböző prezenterekben, például a legújabb megjelent cikkek megszerzéséhez. Ha úgy fejlesztjük az alkalmazásunkat, hogy a cikkekhez hozzáadunk egy flaget, amely a folyamatban lévő cikkek állapotát jelzi, akkor is végig kell mennünk az alkalmazásunk összes helyszínén, és hozzá kell adnunk egy where záradékot, hogy csak a kész cikkek legyenek kiválasztva. +Ahogy az alkalmazás növekszik, hamarosan rájövünk, hogy különböző helyeken, különböző presenterekben hasonló adatbázis-műveleteket kell végrehajtanunk. Például a legújabb publikált cikkek lekérése. Ha az alkalmazást például úgy fejlesztjük tovább, hogy a cikkeknél hozzáadunk egy jelzőt, hogy vázlatban van-e, akkor végig kell mennünk az alkalmazás összes olyan helyén, ahol cikkeket kérünk le az adatbázisból, és hozzá kell adnunk a where feltételt, hogy csak a nem vázlatban lévő cikkeket válasszuk ki. -Ezen a ponton az adatbázissal való közvetlen munka elégtelenné válik, és okosabb lesz egy új függvénnyel segíteni magunkat, amely visszaadja a megjelent cikkeket. Ha pedig később újabb záradékot adunk hozzá (például, hogy ne jelenítsünk meg jövőbeni dátumú cikkeket), akkor csak egy helyen szerkesztjük a kódunkat. +Ebben a pillanatban a közvetlen adatbázis-kezelés elégtelenné válik, és hasznosabb lesz egy új függvénnyel segíteni magunkon, amely a publikált cikkeket fogja visszaadni nekünk. És ha később hozzáadunk egy másik feltételt, például hogy ne jelenjenek meg a jövőbeli dátummal rendelkező cikkek, a kódot csak egy helyen módosítjuk. -A függvényt a `PostFacade` osztályba helyezzük, és `getPublicArticles()` névre kereszteljük. +A funkciót például a `PostFacade` osztályba helyezzük, és `getPublicArticles()`-nak nevezzük el. -Létrehozzuk a `PostFacade` modellosztályunkat a `app/Model/` könyvtárban, hogy gondoskodjon a cikkeinkről: +Az `app/Model/` könyvtárban létrehozzuk a `PostFacade` modell osztályunkat, amely a cikkekkel fog foglalkozni: ```php .{file:app/Model/PostFacade.php} <?php @@ -32,13 +32,13 @@ final class PostFacade } ``` -Az osztályban átadjuk az adatbázis Explorer-t:[api:Nette\Database\Explorer]. Ezzel kihasználjuk a [DI konténer |dependency-injection:passing-dependencies] erejét. +Az osztályban a konstruktor segítségével kérjük át az adatbázis [api:Nette\Database\Explorer]-t. Kihasználjuk a [DI konténer |dependency-injection:passing-dependencies] erejét. -Átváltunk a `HomePresenter` -ra, amelyet úgy szerkesztünk, hogy megszabadulunk a `Nette\Database\Explorer` függőségtől, és azt az új osztályunk függőségével helyettesítjük. +Váltsunk a `HomePresenter`-re, amelyet úgy módosítunk, hogy megszabadulunk a `Nette\Database\Explorer` függőségtől, és lecseréljük az új osztályunktól való új függőségre. -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use App\Model\PostFacade; use Nette; @@ -59,10 +59,9 @@ final class HomePresenter extends Nette\Application\UI\Presenter } ``` -A használati szakaszban a `App\Model\PostFacade` címet használjuk, így a PHP-kódot lerövidíthetjük a `PostFacade` címre. Ezt az objektumot a konstruktorban kérjük, a `$facade` tulajdonságba írjuk, és a renderDefault metódusban használjuk. +A use szakaszban `App\Model\PostFacade` van, így a PHP kódban a jelölést lerövidíthetjük `PostFacade`-ra. Ezt az objektumot kérjük a konstruktorban, beírjuk a `$facade` property-be, és használjuk a renderDefault metódusban. -Az utolsó hátralévő lépés az, hogy megtanítjuk a DI konténert, hogy ezt az objektumot előállítsa. Ez általában úgy történik, hogy a `config/services.neon` fájlban a `services` szakaszban egy bullet pointot adunk hozzá a fájlhoz, megadva a teljes osztálynevet és a konstruktor paramétereit. -Ezzel úgymond regisztráljuk, és az objektumot ezután **service**-nek hívjuk. Az [autowiring |dependency-injection:autowiring] nevű varázslatnak köszönhetően általában nem kell megadnunk a konstruktor paramétereit, mert a DI felismeri és automatikusan átadja őket. Így elegendő lenne csak az osztály nevét megadni: +Már csak az utolsó lépés van hátra, megtanítani a DI konténert, hogy ezt az objektumot előállítsa. Ez általában úgy történik, hogy a `config/services.neon` fájl `services` szakaszába hozzáadunk egy felsorolásjelet, megadjuk az osztály teljes nevét és a konstruktor paramétereit. Ezzel regisztráljuk, és az objektumot ezután **szolgáltatásnak** nevezzük. Az [autowiring |dependency-injection:autowiring] nevű varázslatnak köszönhetően általában nem kell megadnunk a konstruktor paramétereit, mert a DI felismeri és átadja őket. Így elegendő lenne csak az osztály nevét megadni: ```neon .{file:config/services.neon} ... @@ -71,16 +70,15 @@ services: - App\Model\PostFacade ``` -Azonban ezt a sort sem kell hozzáadni. A `search` szakaszban, a `services.neon` elején van definiálva, hogy a `-Facade` vagy `-Factory` végződésű osztályokat a DI automatikusan megkeresi, ami a `PostFacade` esetében is így van. +Azonban ezt a sort sem kell hozzáadnia. A `services.neon` elején a `search` szakaszban definiálva van, hogy minden `-Facade` vagy `-Factory` végződésű osztályt a DI maga keres meg, ami a `PostFacade` esetében is így van. -Összefoglaló .[#toc-summary] -============================ +Összegzés +========= -A `PostFacade` osztály egy konstruktorban kéri a `Nette\Database\Explorer` címet, és mivel ez az osztály regisztrálva van a DI konténerben, a konténer létrehozza ezt a példányt és átadja. A DI így létrehoz nekünk egy `PostFacade` példányt, és egy konstruktorban átadja azt a HomePresenter osztálynak, amelyik ezt kérte. Afféle Matrjoska baba kód :) Minden komponens csak azt kéri, amire szüksége van, és nem érdekli őket, hogy hol és hogyan jön létre. A létrehozást a DI konténer kezeli. +A `PostFacade` osztály a konstruktorában kéri a `Nette\Database\Explorer` átadását, és mivel ez az osztály regisztrálva van a DI konténerben, a konténer létrehozza ezt a példányt és átadja neki. A DI így létrehozza nekünk a `PostFacade` példányt, és átadja a konstruktorban a HomePresenter osztálynak, amely kérte. Ilyen matrjoska. :) Mindenki csak megmondja, mit akar, és nem érdekli, hol és hogyan jön létre valami. A létrehozásról a DI konténer gondoskodik. .[note] -Itt olvashatsz többet a [függőségi injektálásról |dependency-injection:introduction], és a [konfigurációról |nette:configuring]. +Itt többet olvashat a [dependency injection |dependency-injection:introduction]-ről és a [konfigurációról |nette:configuring]. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/hu/single-post.texy b/quickstart/hu/single-post.texy index d04ee616b9..388a3ed519 100644 --- a/quickstart/hu/single-post.texy +++ b/quickstart/hu/single-post.texy @@ -1,17 +1,17 @@ -Egyetlen poszt oldal -******************** +Bejegyzés oldala +**************** .[perex] -Adjunk hozzá egy másik oldalt a blogunkhoz, amely egy adott blogbejegyzés tartalmát jeleníti meg. +Most létrehozunk egy másik blogoldalt, amely egy konkrét bejegyzést fog megjeleníteni. -Létre kell hoznunk egy új renderelési metódust, amely egy adott blogbejegyzést fog lekérni és átadni a sablonhoz. Ha ez a nézet a `HomePresenter` oldalon van, az nem szép, mert egy blogbejegyzésről van szó, nem pedig a kezdőlapról. Tehát hozzunk létre egy új osztályt `PostPresenter` és helyezzük el a `app/Presenters`. Szüksége lesz egy adatbázis-kapcsolatra, ezért a *adatbázis injekció* kódot ismét oda tesszük. +Létre kell hoznunk egy új render metódust, amely lekér egy konkrét cikket, és átadja a sablonnak. Ennek a metódusnak a `HomePresenter`-ben való elhelyezése nem túl elegáns, mivel egy cikkről beszélünk, nem a kezdőlapról. Hozzunk létre tehát egy `PostPresenter`-t az `app/Presentation/Post/`-ban. Ennek a presenternek is csatlakoznia kell az adatbázishoz, ezért itt is írunk egy konstruktort, amely adatbázis-kapcsolatot igényel. -A `PostPresenter` így kell kinéznie: +A `PostPresenter` tehát így nézhet ki: -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Post; use Nette; use Nette\Application\UI\Form; @@ -23,50 +23,50 @@ final class PostPresenter extends Nette\Application\UI\Presenter ) { } - public function renderShow(int $postId): void + public function renderShow(int $id): void { $this->template->post = $this->database ->table('posts') - ->get($postId); + ->get($id); } } ``` -Be kell állítanunk egy megfelelő névteret `App\Presenters` a prezenterünk számára. Ez a [prezenter leképezésétől |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7] függ. +Nem szabad elfelejtenünk megadni a helyes `App\Presentation\Post` névteret, amely a [presenter leképezés |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7] beállításainak van alárendelve. -A `renderShow` módszer egy argumentumot igényel - a megjelenítendő poszt azonosítóját. Ezután betölti a bejegyzést az adatbázisból, és az eredményt átadja a sablonhoz. +A `renderShow` metódus egy argumentumot igényel - egy konkrét cikk azonosítóját, amelyet meg kell jeleníteni. Ezután betölti ezt a cikket az adatbázisból, és átadja a sablonnak. -A `Home/default.latte` sablonban a `Post:show` műveletre mutató linket adunk hozzá: +A `Home/default.latte` sablonba beillesztünk egy linket a `Post:show` akcióra. -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a href="{link Post:show $post->id}">{$post->title}</a></h2> ... ``` -A `{link}` címke URL-címet generál, amely a `Post:show` akcióra mutat. Ez a tag a bejegyzés azonosítóját is továbbítja argumentumként. +A `{link}` tag egy URL címet generál, amely a `Post:show` akcióra mutat. Átadja a bejegyzés azonosítóját is argumentumként. -Ugyanezt röviden megírhatjuk az n:attribútum használatával: +Ugyanezt rövidebben is leírhatjuk egy n:attribútum segítségével: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a n:href="Post:show $post->id">{$post->title}</a></h2> ... ``` -A `n:href` attribútum hasonló a `{link}` címkéhez. +Az `n:href` attribútum a `{link}` tag megfelelője. -A `Post:show` művelet sablonja még nem létezik. Meg tudjuk nyitni a linket erre a bejegyzésre. [Tracy |tracy:] hibát fog mutatni, hogy miért nem létezik a `Post/show.latte`. Ha bármilyen más hibajelentést lát, akkor valószínűleg be kell kapcsolnia a mod_rewrite-et a webszerverén. +A `Post:show` akcióhoz azonban még nem létezik sablon. Kipróbálhatjuk megnyitni a linket ehhez a bejegyzéshez. A [Tracy |tracy:] hibát fog megjeleníteni, mert a `Post/show.latte` sablon még nem létezik. Ha más hibaüzenetet lát, valószínűleg engedélyeznie kell a `mod_rewrite`-ot a webszerveren. -Tehát létrehozzuk a `Post/show.latte` címet ezzel a tartalommal: +Hozzunk létre tehát egy `Post/show.latte` sablont ezzel a tartalommal: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} {block content} -<p><a n:href="Home:default">← back to posts list</a></p> +<p><a n:href="Home:default">← vissza a bejegyzések listájához</a></p> <div class="date">{$post->created_at|date:'F j, Y'}</div> @@ -75,51 +75,50 @@ Tehát létrehozzuk a `Post/show.latte` címet ezzel a tartalommal: <div class="post">{$post->content}</div> ``` -Nézzük meg az egyes részeket. +Most nézzük át a sablon egyes részeit. -Az első sor a korábban már látott "tartalom" nevű *nevesített blokk* definícióját kezdi. Ez egy *layout sablonban* fog megjelenni. Mint látható, hiányzik a `{/block}` végcímke. Ez opcionális. +Az első sor a "content" nevű blokk definíciójával kezdődik, ugyanúgy, mint a kezdőlapon. Ez a blokk ismét a fő sablonban lesz megjelenítve. Ahogy láthatja, hiányzik a `{/block}` záró tag. Ez ugyanis nem kötelező. -A második sorban a blogbejegyzések listájára mutató visszahivatkozást adunk meg, így a felhasználó zökkenőmentesen tud előre-hátra navigálni a blogunkon. Ismét a `n:href` attribútumot használjuk, ezért a Nette gondoskodik az URL generálásáról helyettünk. A link a `Home` bemutató `default` műveletére mutat (írhatnánk `n:href="Home:"` is, mivel a `default` művelet elhagyható). +A következő sorban egy link található vissza a blogbejegyzések listájához, így a felhasználó egyszerűen mozoghat a cikkek listája és egy konkrét cikk között. Mivel az `n:href` attribútumot használjuk, a Nette maga gondoskodik a linkek generálásáról. A link a `Home` presenter `default` akciójára mutat (írhatjuk úgy is, hogy `n:href="Home:"`, mert a `default` nevű akció elhagyható, automatikusan kiegészül). -A harmadik sorban a publikációs időbélyeget formázzuk meg egy szűrővel, ahogy azt már tudjuk. +A harmadik sor formázza a dátum kiírását a már ismert szűrő segítségével. -A negyedik sor a blogbejegyzés *címét* jeleníti meg, mint egy `<h1>` címként. Van egy rész, amit talán nem ismersz, ez pedig a `n:block="title"`. Ki tudja találni, hogy mit csinál? Ha figyelmesen olvastad az előző részeket, akkor említettük a `n: attributes` címet. Ez egy másik példa. Ez a következővel egyenértékű: +A negyedik sor a blog *címsorát* jeleníti meg a `<h1>` HTML tagben. Ez a tag tartalmaz egy attribútumot, amelyet talán nem ismer (`n:block="title"`). Kitalálja, mit csinál? Ha figyelmesen olvasta az előző részt, akkor már tudja, hogy ez egy `n:atribut`. Ez egy másik példa rájuk, amely ekvivalens ezzel: ```latte {block title}<h1>{$post->title}</h1>{/block} ``` -Egyszerűen fogalmazva, *újra definiál* egy `title` nevű blokkot. A blokk a *layout template*-ben (`/app/Presenters/templates/@layout.latte:11`) van definiálva, és az OOP felülbíráláshoz hasonlóan itt is felülírásra kerül. Ezért az oldal `<title>` a megjelenített bejegyzés címét fogja tartalmazni. Az oldal címét felülbíráltuk, és ehhez csak a `n:block="title"` kellett. Nagyszerű, nem? +Egyszerűen fogalmazva, ez a blokk újradefiniálja a `title` nevű blokkot. Ez a blokk már definiálva van a fő *layout* sablonban (`/app/Presentation/@layout.latte:11`), és ahogy az OOP metódusok felülírásánál, ugyanúgy ez a blokk a fő sablonban felülíródik. Tehát az oldal `<title>`-je most a megjelenített bejegyzés címsorát tartalmazza, és ehhez csak egy egyszerű `n:block="title"` attribútumot kellett használnunk. Nagyszerű, ugye? -A sablon ötödik és egyben utolsó sorában megjelenik a bejegyzés teljes tartalma. +A sablon ötödik és utolsó sora egy konkrét bejegyzés teljes tartalmát jeleníti meg. -A bejegyzés azonosítójának ellenőrzése .[#toc-checking-post-id] -=============================================================== +Bejegyzésazonosító ellenőrzése +============================== -Mi történik, ha valaki megváltoztatja az URL-t és beilleszti a `postId` címet, ami nem létezik? Egy szép "az oldal nem található" hibaüzenetet kell adnunk a felhasználónak. Frissítsük a renderelési metódust a `PostPresenter`: +Mi történik, ha valaki megváltoztatja az azonosítót az URL-ben, és beilleszt egy nem létező `id`-t? Egy szép „Az oldal nem található” hibaüzenetet kellene megjelenítenünk a felhasználónak. Módosítsuk tehát egy kicsit a render metódust a `PostPresenter`-ben: -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { - $this->error('Post not found'); + $this->error('Az oldal nem található'); } $this->template->post = $post; } ``` -Ha a poszt nem található, a `$this->error(...)` meghívása egy 404-es oldalt fog megjeleníteni egy szép és érthető üzenettel. Vegyük figyelembe, hogy a fejlesztői környezetben (a laptopon) nem fogjuk látni a hibaoldalt. Ehelyett a Tracy a kivételt fogja megmutatni a teljes részletességgel, ami elég kényelmes a fejlesztéshez. Mindkét módot ellenőrizheted, csak változtasd meg a `setDebugMode` címre átadott értéket a `Bootstrap.php`. +Ha a bejegyzés nem található, a `$this->error(...)` hívásával egy 404-es hibaoldalt jelenítünk meg érthető üzenettel. Figyeljen arra, hogy fejlesztői módban (localhost) ezt a hibaoldalt nem fogja látni. Helyette a Tracy jelenik meg a kivétel részleteivel, ami elég előnyös a fejlesztéshez. Ha mindkét módot meg akarjuk jeleníteni, csak meg kell változtatnunk a `setDebugMode` metódus argumentumát a `Bootstrap.php` fájlban. -Összefoglaló .[#toc-summary] -============================ +Összegzés +========= -Van egy adatbázisunk blogbejegyzésekkel és egy webes alkalmazásunk két nézettel - az első az összes legutóbbi bejegyzés összefoglalóját, a második pedig egy adott bejegyzést jelenít meg. +Van egy adatbázisunk bejegyzésekkel és egy webalkalmazásunk, amelynek két nézete van - az első az összes bejegyzés áttekintését mutatja, a második pedig egy konkrét bejegyzést. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/it/@home.texy b/quickstart/it/@home.texy index ae9b601629..0ebb54af3b 100644 --- a/quickstart/it/@home.texy +++ b/quickstart/it/@home.texy @@ -1,119 +1,119 @@ -Crea la tua prima applicazione! -******************************* +Scriviamo la prima applicazione! +******************************** .[perex] -Imparate a conoscere Nette Framework creando un semplice blog con commenti. Iniziamo! +Conosciamo insieme Nette Framework, creando un semplice blog con commenti. Andiamo! -Dopo i primi due capitoli, avrete il vostro blog funzionante e sarete pronti a pubblicare i vostri fantastici post, anche se le funzionalità saranno piuttosto limitate dopo aver completato questi due capitoli. Per rendere le cose più piacevoli ai vostri utenti, dovreste leggere anche i capitoli successivi e continuare a migliorare la vostra applicazione. +Già dopo i primi due capitoli avremo il nostro blog funzionante e potremo pubblicare i nostri fantastici post, anche se le funzioni saranno per ora piuttosto limitate. Dovresti leggere anche i capitoli successivi, dove programmeremo l'aggiunta di commenti, la modifica degli articoli e infine metteremo in sicurezza il blog. .[tip] -Questa esercitazione presuppone che abbiate completato il documento [Installazione |nette:installation] e che abbiate configurato con successo il vostro tooling. +Questo tutorial presuppone che tu abbia letto la pagina [Installazione |nette:installation] e preparato con successo gli strumenti necessari. Presuppone inoltre che tu comprenda la [programmazione orientata agli oggetti in PHP |nette:introduction-to-object-oriented-programming]. -Utilizzare PHP 8.0 o successivo. L'applicazione completa è disponibile [su GitHub |https://github.com/nette-examples/quickstart/tree/v4.0]. +Utilizza PHP 8.1 o versioni successive. L'applicazione completa è disponibile [su GitHub |https://github.com/nette-examples/quickstart/tree/v4.0]. -La pagina di benvenuto .[#toc-the-welcome-page] -=============================================== +Pagina di benvenuto +=================== -Iniziamo creando un nuovo progetto nella cartella `nette-blog`: +Iniziamo creando un nuovo progetto nella directory `nette-blog`: ```shell composer create-project nette/web-project nette-blog ``` -A questo punto, la pagina di benvenuto del progetto Web dovrebbe essere in esecuzione. Provatela aprendo il browser e accedendo al seguente URL: +A questo punto, la pagina iniziale del Web Project dovrebbe già funzionare. Proviamolo aprendo il browser al seguente indirizzo URL: ``` http://localhost/nette-blog/www/ ``` -e dovreste vedere la pagina di benvenuto di Nette Framework: +e vedremo la pagina iniziale di Nette Framework: [* qs-welcome.webp .{url: http://localhost/nette-blog/www/} *] -L'applicazione funziona e ora potete iniziare a modificarla. +L'applicazione funziona e puoi iniziare a fare modifiche. .[note] -In caso di problemi, [provate a seguire questi suggerimenti |nette:troubleshooting#Nette Is Not Working, White Page Is Displayed]. +Se si è verificato un problema, [prova questi pochi suggerimenti |nette:troubleshooting#Nette non funziona viene visualizzata una pagina bianca]. -Contenuto del progetto Web .[#toc-web-project-s-content] -======================================================== +Contenuto del Web Project +========================= -Il progetto Web ha la seguente struttura: +Il Web Project ha la seguente struttura: /--pre <b>nette-blog/</b> -├── <b>app/</b> ← application directory -│ ├── <b>Presenters/</b> ← presenter classes -│ │ └── <b>templates/</b>← templates -│ ├── <b>Router/</b> ← configuration of URL addresses -│ └── <b>Bootstrap.php</b> ← booting class Bootstrap -├── <b>bin/</b> ← scripts for the command line -├── <b>config/</b> ← configuration files -├── <b>log/</b> ← error logs -├── <b>temp/</b> ← temporary files, cache, … -├── <b>vendor/</b> ← libraries installed by Composer -│ └── <b>autoload.php</b> ← autoloading of libraries installed by Composer -└── <b>www/</b> ← public folder - the only place accessible from browser - └── <b>index.php</b> ← initial file that launches the application +├── <b>app/</b> ← directory dell'applicazione +│ ├── <b>Core/</b> ← classi di base necessarie per il funzionamento +│ ├── <b>Presentation/</b> ← presenter, template & co. +│ │ └── <b>Home/</b> ← directory del presenter Home +│ └── <b>Bootstrap.php</b> ← classe di avvio Bootstrap +├── <b>assets/</b> ← raw assets (SCSS, TypeScript, immagini sorgente) +├── <b>bin/</b> ← script eseguiti dalla riga di comando +├── <b>config/</b> ← file di configurazione +├── <b>log/</b> ← log degli errori +├── <b>temp/</b> ← file temporanei, cache, … +├── <b>vendor/</b> ← librerie installate da Composer +│ └── <b>autoload.php</b> ← autoloading di tutti i pacchetti installati +└── <b>www/</b> ← directory pubblica - l'unica accessibile dal browser + ├── <b>assets/</b> ← file statici compilati (CSS, JS, immagini, ...) + └── <b>index.php</b> ← file iniziale con cui si avvia l'applicazione \-- -La directory `www` dovrebbe contenere immagini, JavaScript, CSS e altri file disponibili al pubblico. Questa è l'unica directory direttamente accessibile dal browser, quindi si può puntare qui la directory principale del server web (si può configurare in Apache, ma lo faremo più avanti perché non è importante in questo momento). +La directory `www/` è destinata all'archiviazione di immagini, file JavaScript, stili CSS e altri file accessibili pubblicamente. Solo questa directory è accessibile da Internet, quindi imposta la directory radice della tua applicazione in modo che punti proprio qui (puoi farlo nella configurazione di Apache o nginx, ma facciamolo più tardi, ora non è importante). -La directory più importante per voi è `app/`. Vi si trova il file `Bootstrap.php`, all'interno del quale si trova una classe che carica il framework e configura l'applicazione. Attiva l'[autocaricamento |robot-loader:] e imposta il [debugger |tracy:] e le [rotte |application:routing]. +La cartella più importante per noi è `app/`. Qui troviamo il file `Bootstrap.php`, che contiene la classe che serve a caricare l'intero framework e a configurare l'applicazione. Qui si attiva l'[autoloading |robot-loader:], si imposta il [debugger |tracy:] e le [route |application:routing]. -Pulire .[#toc-cleanup] -====================== +Pulizia +======= -Il progetto Web contiene una pagina di benvenuto, che possiamo rimuovere: cancellate pure il file `app/Presenters/templates/Home/default.latte` e sostituitelo con il testo "Hello world!". +Il Web Project contiene una pagina iniziale, che cancelleremo prima di iniziare a programmare qualcosa. Quindi, senza timore, sostituiamo il contenuto del file `app/Presentation/Home/default.latte` con "Hello world!". [* qs-hello.webp .{url:-} *] -Tracy (Debugger) .[#toc-tracy-debugger] -======================================= +Tracy (debugger) +================ -Uno strumento estremamente importante per lo sviluppo è [un debugger chiamato Tracy |tracy:]. Provate a fare qualche errore nel vostro file `app/Presenters/HomePresenter.php` (per esempio, rimuovendo una parentesi graffa dalla definizione della classe HomePresenter) e vedete cosa succede. Verrà visualizzata una pagina a schermo rosso con una descrizione comprensibile dell'errore. +Uno strumento estremamente importante per lo sviluppo è lo [strumento di debug Tracy |tracy:]. Prova a provocare un errore nel file `app/Presentation/Home/HomePresenter.php` (ad esempio rimuovendo una parentesi graffa nella definizione della classe HomePresenter) e guarda cosa succede. Apparirà una pagina di notifica che descrive l'errore in modo comprensibile. -[* qs-tracy.webp .{url:-}(debugger screen) *] +[* qs-tracy.avif .{url:-}(schermata del debugger) *] -Tracy vi aiuterà notevolmente nella ricerca degli errori. Notate anche la barra Tracy fluttuante nell'angolo in basso a destra, che vi informa su importanti dati di runtime. +Tracy ci aiuterà enormemente quando cercheremo errori nell'applicazione. Nota anche la Tracy Bar fluttuante nell'angolo in basso a destra dello schermo, che contiene informazioni sull'esecuzione dell'applicazione. [* qs-tracybar.webp .{url:-} *] -In modalità di produzione, Tracy è ovviamente disattivato e non rivela alcuna informazione sensibile. Tutti gli errori vengono invece salvati nella directory `log/`. Provate. In `app/Bootstrap.php`, trovate il seguente pezzo di codice, decommentate la riga e cambiate il parametro della chiamata al metodo in `false`, in modo che appaia come questo: +In modalità produzione, Tracy è ovviamente disattivata e non mostra alcuna informazione sensibile. Tutti gli errori in questo caso vengono salvati nella cartella `log/`. Proviamolo. Nel file `app/Bootstrap.php`, decommentiamo la riga seguente e cambiamo il parametro della chiamata a `false`, in modo che il codice appaia così: ```php .{file:app/Bootstrap.php} ... -$configurator->setDebugMode(false); -$configurator->enableTracy(__DIR__ . '/../log'); +$this->configurator->setDebugMode(false); ... ``` -Dopo l'aggiornamento della pagina web, la pagina a schermo rosso sarà sostituita dal messaggio di facile comprensione: +Dopo aver aggiornato la pagina, non vedremo più Tracy. Al suo posto verrà visualizzato un messaggio user-friendly: -[* qs-fatal.webp .{url:-}(error screen) *] +[* qs-fatal.webp .{url:-}(schermata di errore) *] -Ora, guardate nella directory `log/`. Vi si trova il registro degli errori (nel file exception.log) e anche la pagina con il messaggio di errore (salvata in un file HTML con un nome che inizia con `exception`). +Ora diamo un'occhiata alla directory `log/`. Qui (nel file `exception.log`) troveremo l'errore registrato e anche la già nota pagina con il messaggio di errore (salvata come file HTML con un nome che inizia con `exception-`). -Commentare nuovamente la riga `// $configurator->setDebugMode(false);`. Tracy abilita automaticamente la modalità di sviluppo nell'ambiente `localhost` e la disabilita altrove. +Commentiamo di nuovo la riga `// $configurator->setDebugMode(false);`. Tracy abilita automaticamente la modalità sviluppatore su localhost e la disabilita ovunque altrove. -Ora possiamo risolvere il bug e continuare a progettare la nostra applicazione. +Possiamo correggere l'errore che abbiamo creato e continuare a scrivere l'applicazione. -Inviare i ringraziamenti .[#toc-send-thanks] -============================================ +Invia un ringraziamento +======================= -Vi mostriamo un trucco che renderà felici gli autori open source. Potete facilmente assegnare una stella su GitHub alle librerie utilizzate dal vostro progetto. Basta eseguire: +Ti mostreremo un trucco che farà piacere agli autori open source. In modo semplice, dai una stella su GitHub alle librerie che il tuo progetto utilizza. Basta eseguire: ```shell composer thanks ``` -Provate! +Provalo! {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/it/@left-menu.texy b/quickstart/it/@left-menu.texy index 252bc2a1fc..85d02736f1 100644 --- a/quickstart/it/@left-menu.texy +++ b/quickstart/it/@left-menu.texy @@ -1,9 +1,9 @@ Tutorial ******** -- [Create la vostra prima applicazione! |@home] -- [Home page del blog |home-page] +- [Scriviamo la prima applicazione! |@home] +- [Pagina iniziale del blog |home-page] - [Pagina del singolo post |single-post] - [Commenti |comments] -- [Creare e modificare i post |creating-posts] +- [Creazione e modifica dei post |creating-posts] - [Modello |Model] - [Autenticazione |authentication] diff --git a/quickstart/it/@meta.texy b/quickstart/it/@meta.texy new file mode 100644 index 0000000000..8bc81ddb0c --- /dev/null +++ b/quickstart/it/@meta.texy @@ -0,0 +1 @@ +{{sitename: Scriviamo la prima applicazione!}} diff --git a/quickstart/it/authentication.texy b/quickstart/it/authentication.texy index bfd8b3f57b..d1522f2b4c 100644 --- a/quickstart/it/authentication.texy +++ b/quickstart/it/authentication.texy @@ -1,36 +1,36 @@ Autenticazione ************** -Nette fornisce le linee guida su come programmare l'autenticazione nella propria pagina, ma non obbliga a farlo in un modo particolare. L'implementazione dipende da voi. Nette ha un'interfaccia `Nette\Security\Authenticator` che obbliga a implementare un solo metodo, chiamato `authenticate`, che trova l'utente in qualsiasi modo. +Nette fornisce un modo per programmare l'autenticazione sulle nostre pagine, ma non ci obbliga a nulla. L'implementazione dipende solo da noi. Nette contiene l'interfaccia `Nette\Security\Authenticator`, che richiede solo un metodo `authenticate`, che verifica l'utente in qualsiasi modo desideriamo. -Ci sono molti modi in cui un utente può autenticarsi. Il modo più comune è l'autenticazione basata su password (l'utente fornisce il suo nome o la sua e-mail e una password), ma ci sono anche altri modi. Forse conoscete i pulsanti "Accedi con Facebook" su molti siti web, o il login tramite Google/Twitter/GitHub o qualsiasi altro sito. Con Nette, potete scegliere qualsiasi metodo di autenticazione, oppure combinarli. Sta a voi decidere. +Ci sono molte opzioni su come un utente può essere autenticato. Il metodo di autenticazione più comune è tramite password (l'utente fornisce il proprio nome o e-mail e password), ma ci sono anche altri modi. Forse conoscete i pulsanti del tipo "Accedi con Facebook", o l'accesso tramite Google/Twitter/GitHub su alcuni siti. Con Nette possiamo avere qualsiasi metodo di accesso, o possiamo anche combinarli. Dipende solo da noi. -Normalmente si dovrebbe scrivere il proprio autenticatore, ma per questo semplice blog useremo l'autenticatore integrato, che si autentica in base a una password e a un nome utente memorizzati in un file di configurazione. È ottimo per i test. Aggiungeremo la seguente sezione *security* al file di configurazione `config/common.neon`: +Normalmente scriveremmo il nostro authenticator, ma per questo semplice piccolo blog useremo l'authenticator integrato, che accede in base a password e nome utente memorizzati nel file di configurazione. È utile per scopi di test. Aggiungiamo quindi la seguente sezione *security* al file di configurazione `config/common.neon`: ```neon .{file:config/common.neon} security: users: - admin: secret # utente 'admin', password 'secret' + admin: secret # utente 'admin', password 'secret' ``` -Nette creerà automaticamente un servizio nel contenitore DI. +Nette crea automaticamente un servizio nel container DI. -Modulo di accesso .[#toc-sign-in-form] -====================================== +Form di accesso +=============== -Ora abbiamo la parte di backend dell'autenticazione pronta e dobbiamo fornire un'interfaccia utente, attraverso la quale l'utente possa effettuare il login. Creiamo un nuovo presentatore, chiamato *SignPresenter*, che +Ora abbiamo l'autenticazione pronta e dobbiamo preparare l'interfaccia utente per l'accesso. Creiamo quindi un nuovo presenter chiamato *SignPresenter*, che: -- visualizzerà un modulo di login (chiedendo nome utente e password) -- autenticare l'utente quando il modulo viene inviato -- fornire un'azione di logout +- visualizzerà il form di accesso (con nome utente e password) +- dopo l'invio del form, verificherà l'utente +- fornirà la possibilità di disconnettersi -Cominciamo con il modulo di accesso. Si sa già come funzionano i moduli in un presentatore. Creare il metodo `SignPresenter` e il metodo `createComponentSignInForm`. Dovrebbe avere questo aspetto: +Iniziamo con il form di accesso. Sappiamo già come funzionano i form nei presenter. Creiamo quindi il presenter `SignPresenter` e scriviamo il metodo `createComponentSignInForm`. Dovrebbe assomigliare a qualcosa del genere: -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Sign; use Nette; use Nette\Application\UI\Form; @@ -40,69 +40,69 @@ final class SignPresenter extends Nette\Application\UI\Presenter protected function createComponentSignInForm(): Form { $form = new Form; - $form->addText('username', 'Username:') - ->setRequired('Please enter your username.'); + $form->addText('username', 'Nome utente:') + ->setRequired('Per favore, inserisci il tuo nome utente.'); $form->addPassword('password', 'Password:') - ->setRequired('Please enter your password.'); + ->setRequired('Per favore, inserisci la tua password.'); - $form->addSubmit('send', 'Sign in'); + $form->addSubmit('send', 'Accedi'); - $form->onSuccess[] = [$this, 'signInFormSucceeded']; + $form->onSuccess[] = $this->signInFormSucceeded(...); return $form; } } ``` -C'è un input per il nome utente e la password. +Ci sono campi per nome utente e password. -Modello .[#toc-template] ------------------------- +Template +-------- -Il modulo sarà reso nel template `in.latte` +Il form verrà renderizzato nel template `in.latte`: -```latte .{file:app/Presenters/templates/Sign/in.latte} +```latte .{file:app/Presentation/Sign/in.latte} {block content} -<h1 n:block=title>Sign in</h1> +<h1 n:block=title>Accesso</h1> {control signInForm} ``` -Gestore dell'accesso .[#toc-login-handler] ------------------------------------------- +Callback di accesso +------------------- -Aggiungiamo anche un *gestore del modulo* per l'accesso dell'utente, che viene invocato subito dopo l'invio del modulo. +Successivamente, aggiungiamo il callback per l'accesso dell'utente, che verrà chiamato subito dopo l'invio riuscito del form. -Il gestore prenderà il nome utente e la password inseriti dall'utente e li passerà all'autenticatore definito in precedenza. Dopo che l'utente si è loggato, lo reindirizziamo alla homepage. +Il callback prende semplicemente il nome utente e la password che l'utente ha inserito e li passa all'authenticator. Dopo l'accesso, reindirizziamo alla pagina iniziale. -```php .{file:app/Presenters/SignPresenter.php} -public function signInFormSucceeded(Form $form, \stdClass $data): void +```php .{file:app/Presentation/Sign/SignPresenter.php} +private function signInFormSucceeded(Form $form, \stdClass $data): void { try { $this->getUser()->login($data->username, $data->password); $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { - $form->addError('Incorrect username or password.'); + $form->addError('Nesprávné přihlašovací jméno nebo heslo.'); } } ``` -Il metodo [User::login() |api:Nette\Security\User::login()] dovrebbe lanciare un'eccezione quando il nome utente o la password non corrispondono a quelli definiti in precedenza. Come già sappiamo, ciò si tradurrebbe in una schermata rossa di [Tracy |tracy:] o, in modalità di produzione, in un messaggio che informa di un errore interno del server. Non ci piacerebbe. Ecco perché catturiamo l'eccezione e aggiungiamo un messaggio di errore simpatico e amichevole al modulo. +Il metodo [User::login() |api:Nette\Security\User::login()] lancerà un'eccezione se il nome utente e la password non corrispondono ai dati nel file di configurazione. Come già sappiamo, questo può portare a una pagina di errore rossa, o in modalità produzione a un messaggio che informa di un errore del server. Tuttavia, non vogliamo questo. Pertanto, catturiamo questa eccezione e passiamo un messaggio di errore carino e user-friendly al form. -Quando si verifica un errore nel modulo, la pagina con il modulo viene resa di nuovo e sopra il modulo viene visualizzato un messaggio simpatico, che informa l'utente che ha inserito un nome utente o una password errati. +Non appena si verifica un errore nel form, la pagina con il form viene ridisegnata e sopra il form viene visualizzato un messaggio carino che informa l'utente che ha inserito un nome utente o una password errati. -Sicurezza dei presentatori .[#toc-security-of-presenters] -========================================================= +Protezione dei presenter +======================== -Verrà messo in sicurezza un modulo per l'aggiunta e la modifica dei messaggi. È definito nel presentatore `EditPresenter`. L'obiettivo è impedire l'accesso alla pagina agli utenti che non hanno effettuato il login. +Proteggiamo il form per l'aggiunta e la modifica dei post. Questo è definito nel presenter `EditPresenter`. L'obiettivo è impedire l'accesso alla pagina agli utenti che non sono loggati. -Creiamo un metodo `startup()` che viene avviato immediatamente all'inizio del [ciclo di vita del presentatore |application:presenters#life-cycle-of-presenter]. Questo metodo reindirizza gli utenti non loggati al modulo di login. +Creiamo il metodo `startup()`, che viene eseguito immediatamente all'inizio del [ciclo di vita del presenter |application:presenters#Ciclo di vita del presenter]. Questo reindirizzerà gli utenti non loggati al form di accesso. -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} public function startup(): void { parent::startup(); @@ -114,67 +114,66 @@ public function startup(): void ``` -Nascondi link .[#toc-hide-links] --------------------------------- +Nascondere i link +----------------- -Un utente non autenticato non può più vedere la pagina *create* né la pagina *edit*, ma può ancora vedere i collegamenti che puntano a esse. Nascondiamo anche quelli. Uno di questi collegamenti si trova in `app/Presenters/templates/Home/default.latte`, e dovrebbe essere visibile solo se l'utente è loggato. +Un utente non autorizzato non può più vedere la pagina *create* né *edit*, ma può ancora vedere i link ad esse. Dovremmo nascondere anche questi. Uno di questi link si trova nel template `app/Presentation/Home/default.latte` e dovrebbe essere visibile solo agli utenti loggati. -Possiamo nasconderlo usando un *n:attributo* chiamato `n:if`. Se l'istruzione al suo interno è `false`, l'intero tag `<a>` e il suo contenuto non saranno visualizzati: +Possiamo nasconderlo utilizzando un *n:attributo* chiamato `n:if`. Se questa condizione è `false`, l'intero tag `<a>`, incluso il contenuto, rimarrà nascosto. -```latte -<a n:href="Edit:create" n:if="$user->isLoggedIn()">Create post</a> +```latte .{file:app/Presentation/Home/default.latte} +<a n:href="Edit:create" n:if="$user->isLoggedIn()">Crea post</a> ``` -questa è una scorciatoia per (non confonderla con `tag-if`): +che è l'abbreviazione della seguente notazione (da non confondere con `tag-if`): ```latte -{if $user->isLoggedIn()}<a n:href="Edit:create">Create post</a>{/if} +{if $user->isLoggedIn()}<a n:href="Edit:create">Crea post</a>{/if} ``` -Si dovrebbe nascondere il link di modifica situato in `app/Presenters/templates/Post/show.latte` in modo simile. +Allo stesso modo, nascondiamo anche il link nel template `app/Presentation/Post/show.latte`. -Collegamento al modulo di accesso .[#toc-login-form-link] -========================================================= +Link di accesso +=============== -Ehi, ma come si arriva alla pagina di login? Non c'è nessun link che punta ad essa. Aggiungiamone uno nel file del template `@layout.latte`. Prova a trovare un posto carino, può essere ovunque ti piaccia di più. +Come arriviamo effettivamente alla pagina di accesso? Non c'è nessun link che porti ad essa. Quindi aggiungiamolo al template `@layout.latte`. Provate a trovare un posto adatto - può essere quasi ovunque. -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <ul class="navig"> - <li><a n:href="Home:">Home</a></li> + <li><a n:href="Home:">Articoli</a></li> {if $user->isLoggedIn()} - <li><a n:href="Sign:out">Sign out</a></li> + <li><a n:href="Sign:out">Esci</a></li> {else} - <li><a n:href="Sign:in">Sign in</a></li> + <li><a n:href="Sign:in">Accedi</a></li> {/if} </ul> ... ``` -Se l'utente non ha ancora effettuato il login, mostreremo il link "Accedi". Altrimenti, mostreremo il link "Esci". Aggiungiamo questa azione in SignPresenter. +Se l'utente non è loggato, verrà visualizzato il link "Accedi". Altrimenti, verrà visualizzato il link "Esci". Aggiungiamo anche questa azione a `SignPresenter`. -L'azione di logout ha questo aspetto e, poiché reindirizziamo l'utente immediatamente, non è necessario un modello di vista. +Poiché reindirizziamo immediatamente l'utente dopo la disconnessione, non è necessario alcun template. La disconnessione assomiglia a questo: -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} public function actionOut(): void { $this->getUser()->logout(); - $this->flashMessage('You have been signed out.'); + $this->flashMessage('Odhlášení bylo úspěšné.'); $this->redirect('Home:'); } ``` -Richiama semplicemente il metodo `logout()` e mostra un bel messaggio all'utente. +Viene semplicemente chiamato il metodo `logout()` e successivamente viene visualizzato un messaggio carino che conferma la disconnessione riuscita. -Riepilogo .[#toc-summary] -========================= +Riepilogo +========= -Abbiamo un link per il login e anche per il logout dell'utente. Abbiamo usato l'autenticatore integrato per l'autenticazione e i dettagli di login sono nel file di configurazione, poiché si tratta di una semplice applicazione di prova. Abbiamo anche protetto i moduli di modifica in modo che solo gli utenti registrati possano aggiungere e modificare i post. +Abbiamo un link per l'accesso e anche per la disconnessione dell'utente. Per l'autenticazione abbiamo utilizzato l'authenticator integrato e i dati di accesso li abbiamo nel file di configurazione, poiché si tratta di una semplice applicazione di test. Abbiamo anche protetto i form di modifica, quindi solo gli utenti loggati possono aggiungere e modificare i post. .[note] -Qui si possono leggere ulteriori informazioni su [login |security:authentication] e [autorizzazione |security:authorization] [degli utenti |security:authentication]. +Qui potete leggere di più sul [login degli utenti |security:authentication] e sulla [Verifica delle autorizzazioni |security:authorization]. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/it/comments.texy b/quickstart/it/comments.texy index b35504729e..4c5648f47e 100644 --- a/quickstart/it/comments.texy +++ b/quickstart/it/comments.texy @@ -1,28 +1,28 @@ Commenti ******** -Il blog è stato implementato, abbiamo scritto alcuni ottimi post e li abbiamo pubblicati tramite Adminer. Le persone leggono il blog e sono molto appassionate delle nostre idee. Ogni giorno riceviamo molte e-mail di elogio. Ma a cosa servono tutti questi apprezzamenti se li riceviamo solo nell'e-mail, in modo che nessun altro possa leggerli? Non sarebbe meglio se le persone potessero commentare direttamente sul blog, in modo che tutti gli altri possano leggere quanto siamo fantastici? +Abbiamo caricato il blog sul webserver e pubblicato alcuni post molto interessanti usando Adminer. Le persone leggono il nostro blog e ne sono molto entusiaste. Riceviamo ogni giorno molte email con complimenti. Ma a cosa serve tutta questa lode se la abbiamo solo nelle email e nessuno può leggerla? Sarebbe meglio se il lettore potesse commentare direttamente l'articolo, così che tutti possano leggere quanto siamo fantastici. -Rendiamo tutti gli articoli commentabili. +Programmiamo quindi i commenti. -Creare una nuova tabella .[#toc-creating-a-new-table] -===================================================== +Creazione di una nuova tabella +============================== -Avviare nuovamente Adminer e creare una nuova tabella denominata `comments` con queste colonne: +Avviamo Adminer e creiamo una tabella `comments` con le seguenti colonne: -- `id` int, controllo autoincremento (AI) -- `post_id`, una chiave esterna che fa riferimento alla tabella `posts` +- `id` int, selezioniamo autoincrement (AI) +- `post_id`, chiave esterna che fa riferimento alla tabella `posts` - `name` varchar, lunghezza 255 - `email` varchar, lunghezza 255 -- `content` testo +- `content` text - `created_at` timestamp -L'aspetto dovrebbe essere il seguente: +La tabella dovrebbe quindi apparire più o meno così: [* adminer-comments.webp *] -Non dimenticare di utilizzare la memorizzazione delle tabelle InnoDB e premere Salva. +Non dimenticate di usare nuovamente lo storage InnoDB. ```sql CREATE TABLE `comments` ( @@ -37,104 +37,104 @@ CREATE TABLE `comments` ( ``` -Modulo per i commenti .[#toc-form-for-commenting] -================================================= +Form per commentare +=================== -Per prima cosa, dobbiamo creare un modulo che permetta agli utenti di commentare la nostra pagina. Nette Framework offre un ottimo supporto per i moduli. Possono essere configurati in un presenter e resi in un template. +Prima di tutto, dobbiamo creare un form che permetta agli utenti di commentare i post. Nette Framework ha un supporto straordinario per i form. Possiamo configurarli nel presenter e renderizzarli nel template. -Nette Framework ha un concetto di *componenti*. Un **componente** è una classe o un pezzo di codice riutilizzabile, che può essere collegato a un altro componente. Anche un presentatore è un componente. Ogni componente viene creato utilizzando il component factory. Definiamo quindi il factory del modulo dei commenti in `PostPresenter`. +Nette Framework utilizza il concetto di *componenti*. Un **componente** è una classe riutilizzabile, o una parte di codice, che può essere allegata a un altro componente. Anche il presenter è un componente. Ogni componente viene creato tramite una factory. Creeremo quindi una factory per il form dei commenti nel presenter `PostPresenter`. -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} protected function createComponentCommentForm(): Form { $form = new Form; // significa Nette\Application\UI\Form - $form->addText('name', 'Your name:') + $form->addText('name', 'Nome:') ->setRequired(); - $form->addEmail('email', 'Email:'); + $form->addEmail('email', 'E-mail:'); - $form->addTextArea('content', 'Comment:') + $form->addTextArea('content', 'Commento:') ->setRequired(); - $form->addSubmit('send', 'Publish comment'); + $form->addSubmit('send', 'Pubblica commento'); return $form; } ``` -Spieghiamolo un po'. La prima riga crea una nuova istanza del componente `Form`. I metodi seguenti allegano gli input HTML alla definizione del form. `->addText` sarà reso come `<input type=text name=name>`con `<label>Your name:</label>`. Come si sarà già intuito, `->addTextArea` aggiunge un input di tipo `<textarea>` e `->addSubmit` aggiunge un `<input type=submit>`. Esistono altri metodi del genere, ma questo è tutto ciò che occorre sapere al momento. Per [saperne di più, consultare la documentazione |forms:]. +Spieghiamolo di nuovo un po'. La prima riga crea una nuova istanza del componente `Form`. I metodi successivi aggiungono input HTML alla definizione di questo form. `->addText()` verrà renderizzato come `<input type=text name=name>` con `<label>Nome:</label>`. Come probabilmente avrete già indovinato correttamente, `->addTextArea()` verrà renderizzato come `<textarea>` e `->addSubmit()` come `<input type=submit>`. Esistono molti altri metodi simili, ma questi per ora sono sufficienti per questo form. Potete leggere di più [nella documentazione |forms:]. -Una volta definito il componente del modulo in un presentatore, possiamo renderlo (visualizzarlo) in un template. Per farlo, posizionare il tag `{control}` alla fine del template dei dettagli del post, in `Post/show.latte`. Poiché il nome del componente è `commentForm` (deriva dal nome del metodo `createComponentCommentForm`), il tag avrà il seguente aspetto +Se il form è già definito nel presenter, possiamo renderizzarlo (visualizzarlo) nel template. Lo facciamo inserendo il tag `{control}` alla fine del template che renderizza un singolo post, in `app/Presentation/Post/show.latte`. Poiché il componente si chiama `commentForm` (il nome deriva dal nome del metodo `createComponentCommentForm`), il tag apparirà così: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... -<h2>Post new comment</h2> +<h2>Inserisci un nuovo post</h2> {control commentForm} ``` -Ora, se si controlla il dettaglio di un post, ci sarà un nuovo modulo per inserire i commenti. +Se ora visualizzate la pagina con i dettagli del post, alla fine vedrete il nuovo form per i commenti. -Salvataggio nel database .[#toc-saving-to-database] -=================================================== +Salvataggio nel database +======================== -Avete provato a inviare dei dati? Avrete notato che il modulo non esegue alcuna azione. È solo lì, bello da vedere e non fa nulla. Dobbiamo collegare un metodo di callback, che salverà i dati inviati. +Avete già provato a compilare e inviare il form? Probabilmente avrete notato che il form in realtà non fa nulla. Dobbiamo collegare un metodo di callback che salvi i dati inviati. -Inserire la seguente riga prima della riga `return` nel factory del componente `commentForm`: +Nella factory `createComponentCommentForm`, sulla riga prima di `return`, inseriamo la seguente riga: ```php -$form->onSuccess[] = [$this, 'commentFormSucceeded']; +$form->onSuccess[] = $this->commentFormSucceeded(...); ``` -Significa "dopo che il modulo è stato inviato con successo, chiamare il metodo `commentFormSucceeded` del presentatore corrente". Questo metodo non esiste ancora, quindi creiamolo. +La scrittura precedente significa "dopo l'invio riuscito del form, chiama il metodo `commentFormSucceeded` del presenter corrente". Questo metodo, tuttavia, non esiste ancora. Creiamolo quindi: -```php .{file:app/Presenters/PostPresenter.php} -public function commentFormSucceeded(\stdClass $data): void +```php .{file:app/Presentation/Post/PostPresenter.php} +private function commentFormSucceeded(\stdClass $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); $this->database->table('comments')->insert([ - 'post_id' => $postId, + 'post_id' => $id, 'name' => $data->name, 'email' => $data->email, 'content' => $data->content, ]); - $this->flashMessage('Thank you for your comment', 'success'); + $this->flashMessage('Grazie per il commento', 'success'); $this->redirect('this'); } ``` -Dovremmo posizionarlo subito dopo il factory del componente `commentForm`. +Posizioniamo questo metodo subito dopo la factory del form `commentForm`. -Il metodo new ha un parametro, che è l'istanza del form da inviare, creata dal component factory. Riceviamo i valori inviati in `$data`. Poi inseriamo i dati nella tabella del database `comments`. +Questo nuovo metodo ha un argomento, che è un'istanza del form che è stato inviato - creato dalla factory. Otteniamo i valori inviati in `$data`. E successivamente salviamo i dati nella tabella del database `comments`. -Ci sono altre due chiamate di metodo da spiegare. Il redirect reindirizza letteralmente alla pagina corrente. Si dovrebbe fare ogni volta che il form è stato inviato, è valido e l'operazione di callback ha fatto ciò che doveva fare. Inoltre, quando si reindirizza la pagina dopo l'invio del modulo, non si vedrà il noto messaggio `Would you like to submit the post data again?`, che a volte si può vedere nel browser. (In generale, dopo aver inviato un modulo con il metodo `POST`, si dovrebbe sempre reindirizzare l'utente a un'azione `GET` ). +Ci sono ancora altri due metodi che meritano di essere spiegati. Il metodo `redirect('this')` reindirizza letteralmente alla pagina corrente. È opportuno farlo dopo ogni invio di form, se conteneva dati validi e il callback ha eseguito l'operazione come previsto. E anche se reindirizziamo la pagina dopo l'invio del form, non vedremo il ben noto messaggio `Vuoi inviare nuovamente i dati del form?`, che a volte possiamo vedere nel browser. (In generale, dopo l'invio di un form con il metodo `POST` dovrebbe sempre seguire un reindirizzamento a un'azione `GET`.) Abbiamo aggiunto `#comments` per reindirizzare direttamente alla sezione dei commenti. -Il messaggio `flashMessage` serve a informare l'utente sul risultato di qualche operazione. Poiché si tratta di un rinvio, il messaggio non può essere passato direttamente al template e reso. Quindi c'è questo metodo, che lo memorizza e lo rende disponibile al successivo caricamento della pagina. I messaggi flash sono resi nel file predefinito `app/Presenters/templates/@layout.latte` e si presentano così: +Il metodo `flashMessage()` serve per informare l'utente sul risultato di qualche operazione. Poiché stiamo reindirizzando, il messaggio non può essere semplicemente passato al template e renderizzato. Per questo esiste questo metodo, che salva questo messaggio e lo rende disponibile al successivo caricamento della pagina. I messaggi flash vengono renderizzati nel template principale `app/Presentation/@layout.latte` e appaiono così: -```latte +```latte .{file:app/Presentation/@layout.latte} <div n:foreach="$flashes as $flash" n:class="flash, $flash->type"> {$flash->message} </div> ``` -Come già sappiamo, vengono passati automaticamente al template, quindi non occorre pensarci troppo, funziona e basta. Per maggiori dettagli, [consultare la documentazione |application:presenters#flash-messages]. +Come già sappiamo, i messaggi flash vengono automaticamente passati al template tramite la variabile `$flashes`, quindi non dobbiamo pensarci molto, semplicemente funziona. Per maggiori informazioni [visitate la documentazione |application:presenters#Messaggi flash]. -Rendering dei commenti .[#toc-rendering-the-comments] -===================================================== +Renderizzazione dei commenti +============================ -Questa è una delle cose che vi piacerà di più. Nette Database ha questa bella funzione chiamata [Explorer |database:explorer]. Ricordate che abbiamo creato le tabelle come InnoDB? Adminer ha creato le cosiddette [chiavi esterne |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html] che ci risparmieranno un sacco di lavoro. +Questa è una di quelle cose che semplicemente adorerete. Nette Database ha una funzione fantastica chiamata [Explorer |database:explorer]. Ricordate ancora che abbiamo volutamente creato le tabelle nel database usando lo storage InnoDB? Adminer ha così creato qualcosa chiamato [chiavi esterne |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html], che ci risparmierà molto lavoro. -Nette Database Explorer utilizza le chiavi esterne per risolvere le relazioni tra le tabelle e, conoscendo le relazioni, può creare automaticamente le query per voi. +Nette Database Explorer utilizza le chiavi esterne per risolvere le relazioni reciproche tra le tabelle e, grazie alla conoscenza di queste relazioni, può creare automaticamente query sul database. -Come ricorderete, abbiamo passato la variabile `$post` al modello in `PostPresenter::renderShow()` e ora vogliamo iterare tutti i commenti che hanno la colonna `post_id` uguale alla nostra `$post->id`. Per farlo, basta richiamare `$post->related('comments')`. È così semplice. Guardate il codice risultante. +Come sicuramente ricorderete, abbiamo passato la variabile `$post` al template tramite il metodo `PostPresenter::renderShow()` e ora vogliamo iterare su tutti i commenti che hanno il valore della colonna `post_id` uguale a `$post->id`. Possiamo ottenere ciò chiamando `$post->related('comments')`. Sì, è così semplice. Diamo un'occhiata al codice risultante: -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { ... $this->template->post = $post; @@ -142,17 +142,17 @@ public function renderShow(int $postId): void } ``` -E il modello: +E il template: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... -<h2>Comments</h2> +<h2>Commenti</h2> <div class="comments"> {foreach $comments as $comment} <p><b><a href="mailto:{$comment->email}" n:tag-if="$comment->email"> {$comment->name} - </a></b> said:</p> + </a></b> ha scritto:</p> <div>{$comment->content}</div> {/foreach} @@ -160,13 +160,12 @@ E il modello: ... ``` -Si noti l'attributo speciale `n:tag-if`. Si sa già come funziona `n: attributes`. Se si antepone l'attributo `tag-`, questo si avvolgerà solo intorno ai tag, non al loro contenuto. Questo permette di trasformare il nome del commentatore in un link, se ha fornito la sua e-mail. Queste due righe hanno risultati identici: +Notate l'attributo speciale `n:tag-if`. Sapete già come funzionano gli `n:attributi`. Se aggiungete il prefisso `tag-`, la funzionalità viene applicata solo al tag HTML, non al suo contenuto. Questo ci permette di trasformare il nome del commentatore in un link solo nel caso in cui abbia fornito la sua email. Queste due righe sono equivalenti: ```latte -<strong n:tag-if="$important"> Hello there! </strong> +<strong n:tag-if="$important"> Buongiorno! </strong> -{if $important}<strong>{/if} Hello there! {if $important}</strong>{/if} +{if $important}<strong>{/if} Buongiorno! {if $important}</strong>{/if} ``` {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/it/creating-posts.texy b/quickstart/it/creating-posts.texy index 39a429f8af..3bbc829be1 100644 --- a/quickstart/it/creating-posts.texy +++ b/quickstart/it/creating-posts.texy @@ -1,30 +1,30 @@ Creazione e modifica dei post ***************************** -Che momento fantastico. Abbiamo un nuovo blog superfigo, la gente discute nei commenti e finalmente abbiamo un po' di tempo per programmare. Anche se ci piace Adminer, non è così comodo scriverci i post del blog. Forse è il momento giusto per aggiungere un semplice modulo per aggiungere nuovi post direttamente dalla nostra applicazione. Facciamolo. +È fantastico! Abbiamo un nuovo blog super cool, le persone discutono animatamente nei commenti e noi abbiamo finalmente un po' di tempo per programmare ulteriormente. Sebbene Adminer sia uno strumento eccellente, non è del tutto ideale per scrivere nuovi post sul blog. Probabilmente è il momento giusto per creare un semplice form per aggiungere nuovi post direttamente dall'applicazione. Facciamolo. -Iniziamo a progettare l'interfaccia utente: +Iniziamo progettando l'interfaccia utente: -1. Nella homepage, aggiungiamo un link "Scrivi un nuovo post". -2. Verrà visualizzato un modulo con titolo e textarea per il contenuto. -3. Quando si fa clic sul pulsante Salva, il post viene salvato. +1. Nella pagina iniziale aggiungeremo un link "Scrivi un nuovo post". +2. Questo link visualizzerà un form con un titolo e una textarea per il contenuto del post. +3. Quando clicchiamo sul pulsante Salva, il post verrà salvato nel database. -In seguito aggiungeremo anche l'autenticazione e permetteremo solo agli utenti loggati di aggiungere nuovi post. Ma questo lo faremo più avanti. Quale codice dovremo scrivere per farlo funzionare? +Successivamente aggiungeremo anche il login e permetteremo l'aggiunta di post solo agli utenti loggati. Ma questo più tardi. Quale codice dobbiamo scrivere ora affinché tutto funzioni? -1. Creare un nuovo presenter con un modulo per l'aggiunta di post. -2. Definire un callback che verrà attivato dopo l'invio del modulo e che salverà il nuovo post nel database. -3. Creare un nuovo modello per il modulo. -4. Aggiungere un link al modulo nel modello della pagina principale. +1. Creeremo un nuovo presenter con un form per aggiungere post. +2. Definiremo un callback che verrà eseguito dopo l'invio riuscito del form e che salverà il nuovo post nel database. +3. Creeremo un nuovo template su cui sarà presente quel form. +4. Aggiungeremo un link al form nel template della pagina principale. -Nuovo presentatore .[#toc-new-presenter] -======================================== +Nuovo presenter +=============== -Nominiamo il nuovo presentatore `EditPresenter` e salviamolo in `app/Presenters/EditPresenter.php`. Deve anche connettersi al database, quindi anche in questo caso scriviamo un costruttore che richieda una connessione al database: +Chiameremo il nuovo presenter `EditPresenter` e lo salveremo in `app/Presentation/Edit/EditPresenter.php`. Ha anche bisogno di connettersi al database, quindi scriveremo di nuovo un costruttore che richiederà la connessione al database: -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Edit; use Nette; use Nette\Application\UI\Form; @@ -39,95 +39,95 @@ final class EditPresenter extends Nette\Application\UI\Presenter ``` -Modulo per il salvataggio dei messaggi .[#toc-form-for-saving-posts] -==================================================================== +Form per il salvataggio dei post +================================ -I moduli e i componenti sono già stati trattati quando abbiamo aggiunto il supporto per i commenti. Se siete confusi sull'argomento, andate a vedere di nuovo [come funzionano i moduli e i componenti |comments#form-for-commenting], noi aspetteremo qui ;) +Abbiamo già spiegato i form e i componenti durante la creazione dei commenti. Se non è ancora chiaro, andate a rivedere [la creazione di form e componenti |comments#Form per commentare], noi aspetteremo qui ;) -Ora aggiungete questo metodo a `EditPresenter`: +Ora aggiungiamo questo metodo al presenter `EditPresenter`: -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} protected function createComponentPostForm(): Form { $form = new Form; - $form->addText('title', 'Title:') + $form->addText('title', 'Titolo:') ->setRequired(); - $form->addTextArea('content', 'Content:') + $form->addTextArea('content', 'Contenuto:') ->setRequired(); - $form->addSubmit('send', 'Save and publish'); - $form->onSuccess[] = [$this, 'postFormSucceeded']; + $form->addSubmit('send', 'Salva e pubblica'); + $form->onSuccess[] = $this->postFormSucceeded(...); return $form; } ``` -Salvare un nuovo messaggio dal modulo .[#toc-saving-new-post-from-form] -======================================================================= +Salvataggio di un nuovo post dal form +===================================== -Continuare aggiungendo un metodo di gestione. +Continuiamo aggiungendo un metodo che elabori i dati dal form: -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { $post = $this->database ->table('posts') ->insert($data); - $this->flashMessage('Post was published', 'success'); + $this->flashMessage("Il post è stato pubblicato con successo.", 'success'); $this->redirect('Post:show', $post->id); } ``` -Una breve spiegazione: recupera i valori dal modulo, li inserisce nel database, crea un messaggio per l'utente che indica che il post è stato salvato con successo e reindirizza alla pagina in cui il post è pubblicato, in modo che l'utente possa vederne l'aspetto. +Solo un rapido riepilogo: questo metodo ottiene i dati dal form, li inserisce nel database, crea un messaggio per l'utente sull'avvenuto salvataggio del post e reindirizza alla pagina con il nuovo post, così vedremo subito come appare. -Pagina per la creazione di un nuovo post .[#toc-page-for-creating-a-new-post] -============================================================================= +Pagina per la creazione di un nuovo post +======================================== -Creiamo il modello `Edit/create.latte`: +Creiamo ora il template `Edit/create.latte`: -```latte .{file:app/Presenters/templates/Edit/create.latte} +```latte .{file:app/Presentation/Edit/create.latte} {block content} -<h1>New post</h1> +<h1>Nuovo post</h1> {control postForm} ``` -A questo punto dovrebbe essere tutto chiaro. L'ultima riga mostra il modulo che stiamo per creare. +Tutto dovrebbe essere già chiaro. L'ultima riga renderizza il form che creeremo a breve. -Potremmo anche creare un metodo corrispondente `renderCreate`, ma non è necessario. Non abbiamo bisogno di ottenere alcun dato dal database e di passarlo al template, quindi il metodo sarebbe vuoto. In questi casi, il metodo potrebbe non esistere affatto. +Potremmo creare anche il metodo `renderCreate()` corrispondente nel presenter, ma non è necessario. Non abbiamo bisogno di ottenere dati dal database e passarli al template, quindi quel metodo sarebbe vuoto. In questi casi, il metodo `render<View>` può essere omesso. -Collegamento per la creazione dei post .[#toc-link-for-creating-posts] -====================================================================== +Link per la creazione dei post +============================== -Probabilmente sapete già come aggiungere un link a `EditPresenter` e alla relativa azione `create`. Provate. +Probabilmente sapete già come aggiungere un link a `EditPresenter` e alla sua azione `create`. Provateci. -Basta aggiungere al file `app/Presenters/templates/Home/default.latte`: +Basta aggiungere al file `app/Presentation/Home/default.latte`: ```latte -<a n:href="Edit:create">Write new post</a> +<a n:href="Edit:create">Scrivi un nuovo post</a> ``` -Modifica dei messaggi .[#toc-editing-posts] -=========================================== +Modifica dei post +================= -Aggiungiamo anche la possibilità di modificare i post esistenti. Sarà piuttosto semplice: abbiamo già `postForm` e possiamo usarlo anche per la modifica. +Ora aggiungeremo anche la possibilità di modificare un post. Sarà molto semplice. Abbiamo già pronto il form `postForm` e possiamo usarlo anche per la modifica. -Aggiungeremo una nuova pagina `edit` al sito `EditPresenter`: +Aggiungiamo una nuova pagina `edit` al presenter `EditPresenter`: -```php .{file:app/Presenters/EditPresenter.php} -public function renderEdit(int $postId): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +public function renderEdit(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { - $this->error('Post not found'); + $this->error('Post non trovato'); } $this->getComponent('postForm') @@ -135,26 +135,26 @@ public function renderEdit(int $postId): void } ``` -E creeremo il modello `Edit/edit.latte`: +E creiamo un altro template `Edit/edit.latte`: -```latte .{file:app/Presenters/templates/Edit/edit.latte} +```latte .{file:app/Presentation/Edit/edit.latte} {block content} -<h1>Edit post</h1> +<h1>Modifica post</h1> {control postForm} ``` -E aggiornare il metodo `postFormSucceeded`, che potrà sia aggiungere un nuovo post (come fa ora), sia modificare quelli esistenti: +E modifichiamo il metodo `postFormSucceeded`, che sarà in grado sia di aggiungere un nuovo articolo (come fa ora) sia di modificare un articolo già esistente: -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); - if ($postId) { + if ($id) { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); $post->update($data); } else { @@ -163,26 +163,25 @@ public function postFormSucceeded(array $data): void ->insert($data); } - $this->flashMessage('Post was published', 'success'); + $this->flashMessage('Il post è stato pubblicato con successo.', 'success'); $this->redirect('Post:show', $post->id); } ``` -Quando viene fornito il parametro `postId`, significa che si sta modificando un post. In questo caso, controlleremo che il post esista davvero e, in caso affermativo, lo aggiorneremo nel database. Se il parametro `postId` non viene fornito, significa che verrà aggiunto un nuovo post. +Se il parametro `id` è disponibile, significa che modificheremo il post. In tal caso, verifichiamo che il post richiesto esista davvero e, in caso affermativo, lo aggiorniamo nel database. Se il parametro `id` non è disponibile, significa che dovrebbe essere aggiunto un nuovo post. -Ma da dove viene `postId`? È il parametro passato al metodo `renderEdit`. +Ma da dove viene il parametro `id`? Si tratta del parametro che è stato inserito nel metodo `renderEdit`. -Ora è possibile aggiungere un link al modello `app/Presenters/templates/Post/show.latte`: +Ora possiamo aggiungere un link al template `app/Presentation/Post/show.latte`: ```latte -<a n:href="Edit:edit $post->id">Edit this post</a> +<a n:href="Edit:edit $post->id">Modifica post</a> ``` -Sommario .[#toc-summary] -======================== +Shrnutí +======= -Il blog funziona, le persone commentano rapidamente e non ci affidiamo più all'Adminer per aggiungere nuovi post. È completamente indipendente e anche le persone normali possono scriverci. Ma aspettate, probabilmente non va bene che chiunque, intendo davvero chiunque su Internet, possa postare sul nostro blog. È necessaria una forma di autenticazione, in modo che solo gli utenti loggati possano postare. La aggiungeremo nel prossimo capitolo. +Il blog è ora funzionante, i visitatori commentano attivamente e non abbiamo più bisogno di Adminer per la pubblicazione. L'applicazione è completamente indipendente e chiunque può aggiungere un nuovo post. Aspetta un attimo, probabilmente non è del tutto corretto che chiunque - e intendo davvero chiunque abbia accesso a Internet - possa aggiungere nuovi post. È necessaria una qualche forma di sicurezza affinché solo un utente loggato possa aggiungere un nuovo post. Vedremo questo nel prossimo capitolo. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/it/home-page.texy b/quickstart/it/home-page.texy index f5698f2217..023de9d884 100644 --- a/quickstart/it/home-page.texy +++ b/quickstart/it/home-page.texy @@ -1,41 +1,41 @@ -Home page del blog -****************** +Pagina iniziale del blog +************************ .[perex] -Creiamo la pagina iniziale che mostra i post recenti. +Ora creeremo la pagina iniziale che mostra gli ultimi post. -Prima di iniziare, è necessario conoscere almeno alcune nozioni di base sul modello di progettazione Model-View-Presenter (simile a MVC((Model-View-Controller)): +Prima di iniziare, è necessario conoscere almeno le basi del pattern di progettazione Model-View-Presenter (simile a MVC((Model-View-Controller))): -- **Modello** - livello di manipolazione dei dati. È completamente separato dal resto dell'applicazione. Comunica solo con i presentatori. +- **Model** - lo strato che lavora con i dati. È completamente separato dal resto dell'applicazione. Comunica solo con il presenter. -- **Vista** - livello di definizione del front-end. Rende i dati richiesti all'utente utilizzando i modelli. +- **View** - lo strato front-end. Renderizza i dati richiesti tramite template e li visualizza all'utente. -- **Presenter** (o Controller) - un livello di connessione. Il presentatore collega Modello e Vista. Gestisce le richieste, chiede i dati al Modello e li passa alla Vista corrente. +- **Presenter** (o Controller) - lo strato di collegamento. Il Presenter collega il Model e la View. Elabora le richieste, interroga il Model per i dati e li restituisce alla View. -Nel caso di un'applicazione molto semplice come il nostro blog, il livello Model consisterà in realtà solo di query al database stesso: non abbiamo bisogno di codice PHP aggiuntivo per questo. Dobbiamo solo creare i livelli Presenter e View. In Nette, ogni Presentatore ha le proprie Viste, quindi continueremo con entrambe contemporaneamente. +Nel caso di applicazioni semplici, come sarà il nostro blog, l'intero strato model sarà costituito solo da query al database - per questo per ora non abbiamo bisogno di codice extra. Per iniziare, creeremo quindi solo i presenter e i template. In Nette, ogni presenter ha i propri template, quindi li creeremo contemporaneamente. -Creare il database con Adminer .[#toc-creating-the-database-with-adminer] -========================================================================= +Creazione del database tramite Adminer +====================================== -Per memorizzare i dati, utilizzeremo il database MySQL, perché è la scelta più comune tra gli sviluppatori web. Ma se non vi piace, siete liberi di usare un database di vostra scelta. +Per salvare i dati useremo un database MySQL, perché è il più diffuso tra i programmatori di applicazioni web. Se però non volete usarlo, scegliete pure un database a vostra discrezione. -Prepariamo il database che memorizzerà i post del nostro blog. Possiamo iniziare in modo molto semplice, con una sola tabella per i post. +Ora prepareremo la struttura del database dove verranno salvati gli articoli del nostro blog. Inizieremo in modo molto semplice - creeremo solo una tabella per i post. -Per creare il database possiamo scaricare [Adminer |https://www.adminer.org], oppure utilizzare un altro strumento per la gestione dei database. +Per creare il database possiamo scaricare [Adminer |https://www.adminer.org], o un altro vostro strumento preferito per la gestione dei database. -Apriamo Adminer e creiamo un nuovo database chiamato `quickstart`. +Apriamo Adminer e creiamo un nuovo database con il nome `quickstart`. -Creiamo una nuova tabella denominata `posts` e aggiungiamo le seguenti colonne: -- `id` int, cliccare su autoincremento (AI) +Creiamo una nuova tabella con il nome `posts` e con le seguenti colonne: +- `id` int, selezioniamo autoincrement (AI) - `title` varchar, lunghezza 255 -- `content` testo +- `content` text - `created_at` timestamp -L'aspetto dovrebbe essere il seguente: +La struttura risultante dovrebbe apparire così: [* adminer-posts.webp *] @@ -49,9 +49,9 @@ CREATE TABLE `posts` ( ``` .[caution] -È molto importante utilizzare la memorizzazione della tabella **InnoDB**. Il motivo lo si vedrà più avanti. Per ora, scegliere questo e inviare. Ora si può premere Salva. +È davvero importante usare lo storage **InnoDB**. Tra poco vedremo perché. Per ora, selezionatelo semplicemente e cliccate su salva. -Provate ad aggiungere alcuni post del blog di esempio prima di implementare la possibilità di aggiungere nuovi post direttamente dalla nostra applicazione. +Prima di creare la possibilità di aggiungere articoli al database tramite l'applicazione, aggiungete manualmente alcuni articoli di esempio sul blog. ```sql INSERT INTO `posts` (`id`, `title`, `content`, `created_at`) VALUES @@ -61,37 +61,34 @@ INSERT INTO `posts` (`id`, `title`, `content`, `created_at`) VALUES ``` -Connessione al database .[#toc-connecting-to-the-database] -========================================================== +Connessione al database +======================= -Ora, quando il database è stato creato e abbiamo alcuni post al suo interno, è il momento giusto per visualizzarli sulla nostra nuova pagina. +Ora che il database è stato creato e abbiamo salvato alcuni articoli, è il momento giusto per visualizzarli sulla nostra bella nuova pagina. -Per prima cosa, dobbiamo indicare alla nostra applicazione quale database utilizzare. La configurazione della connessione al database è memorizzata in `config/local.neon`. Impostare la connessione DSN((Data Source Name)) e le credenziali. Dovrebbe essere così: +Prima di tutto dobbiamo dire all'applicazione quale database usare. Impostiamo la connessione al database nel file `config/common.neon` usando DSN((Data Source Name)) e le credenziali di accesso. Dovrebbe apparire più o meno così: -```neon .{file:config/local.neon} +```neon .{file:config/common.neon} database: dsn: 'mysql:host=127.0.0.1;dbname=quickstart' - user: *enter user name* - password: *enter password here* + user: *inserisci qui il nome utente* + password: *inserisci qui la password del database* ``` .[note] -Fare attenzione all'indentazione durante la modifica di questo file. Il [formato NEON |neon:format] accetta sia spazi che tabulazioni, ma non entrambi insieme! Il file di configurazione del progetto Web utilizza le tabulazioni come impostazione predefinita. - -L'intera configurazione è memorizzata in `config/` nei file `common.neon` e `local.neon`. Il file `common.neon` contiene la configurazione globale dell'applicazione e `local.neon` contiene solo i parametri specifici dell'ambiente (ad esempio, la differenza tra il server di sviluppo e quello di produzione). +Durante la modifica di questo file, fate attenzione all'indentazione delle righe. Il formato [NEON |neon:format] accetta sia l'indentazione tramite spazi, sia l'indentazione tramite tabulazioni, ma non entrambe contemporaneamente. Il file di configurazione predefinito in Web Project utilizza le tabulazioni. -Iniettare la connessione al database .[#toc-injecting-the-database-connection] -============================================================================== +Passaggio della connessione al database +======================================= -Il presentatore `HomePresenter`, che elencherà gli articoli, ha bisogno di una connessione al database. Per riceverla, scrivere un costruttore come questo: +Il presenter `HomePresenter`, che si occuperà della visualizzazione degli articoli, ha bisogno della connessione al database. Per ottenerla, useremo un costruttore che apparirà così: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; -use Nette\Application\UI\Form; final class HomePresenter extends Nette\Application\UI\Presenter { @@ -105,12 +102,12 @@ final class HomePresenter extends Nette\Application\UI\Presenter ``` -Caricamento dei post dal database .[#toc-loading-posts-from-the-database] -========================================================================= +Caricamento dei post dal database +================================= -Ora recuperiamo i post dal database e passiamoli al template, che poi renderà il codice HTML. A questo serve il cosiddetto metodo *render*: +Ora carichiamo i post dal database e li inviamo al template, che li renderizzerà successivamente come codice HTML. A questo scopo è destinato il cosiddetto metodo *render*: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} public function renderDefault(): void { $this->template->posts = $this->database @@ -120,41 +117,41 @@ public function renderDefault(): void } ``` -Il presentatore ha ora un metodo di render `renderDefault()` che passa i dati a una vista chiamata `default`. I modelli del presentatore si trovano in `app/Presenters/templates/{PresenterName}/{viewName}.latte`, quindi in questo caso il modello si trova in `app/Presenters/templates/Home/default.latte`. Nel modello, è ora disponibile una variabile chiamata `$posts`, che contiene i post del database. +Il presenter ora contiene un metodo di rendering `renderDefault()`, che passa i dati dal database al template (View). I template si trovano in `app/Presentation/{PresenterName}/{viewName}.latte`, quindi in questo caso il template si trova in `app/Presentation/Home/default.latte`. Nel template sarà ora disponibile la variabile `$posts`, in cui si trovano i post ottenuti dal database. -Modello .[#toc-template] -======================== +Template +======== -Esiste un modello generico per l'intera pagina (chiamato *layout*, con intestazione, fogli di stile, piè di pagina, ...) e poi modelli specifici per ogni vista (ad esempio per visualizzare l'elenco dei post del blog), che possono sovrascrivere alcune parti del modello di layout. +Per l'intera pagina web abbiamo a disposizione un template principale (che si chiama *layout*, contiene l'intestazione, gli stili, il piè di pagina,...) e poi template specifici per ogni vista (View) (ad esempio per la visualizzazione dei post sul blog), che possono sovrascrivere alcune parti del template principale. -Per impostazione predefinita, il modello di layout si trova in `templates/@layout.latte`, che contiene: +Nello stato predefinito, il template di layout si trova in `app/Presentation/@layout.latte` e contiene: -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... {include content} ... ``` -`{include content}` inserisce un blocco chiamato `content` nel modello principale. È possibile definirlo nei modelli di ciascuna vista. In questo caso, modificheremo il file `Home/default.latte` in questo modo: +La scrittura `{include content}` inserisce nel template principale un blocco con il nome `content`. Lo definiremo nei template delle singole viste (View). Nel nostro caso, modificheremo il file `Home/default.latte` come segue: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - Hello World + Ciao Mondo {/block} ``` -Definisce il [blocco |latte:tags#block]*content*, che sarà inserito nel layout. Se si aggiorna il browser, si vedrà una pagina con il testo "Hello world" (nel codice sorgente anche con l'intestazione e il piè di pagina HTML definiti in `@layout.latte`). +Con questo abbiamo definito il [blocco |latte:tags#block] *content*, che sarà inserito nel layout principale. Se aggiorniamo nuovamente il browser, vedremo una pagina con il testo "Ciao Mondo" (nel codice sorgente anche con l'intestazione e il piè di pagina HTML definiti in `@layout.latte`). -Visualizziamo i post del blog: modificheremo il template in questo modo: +Visualizziamo i post del blog - modifichiamo il template come segue: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - <h1>My blog</h1> + <h1>Il mio blog</h1> {foreach $posts as $post} <div class="post"> - <div class="date">{$post->created_at|date:'j. n. Y'}</div> + <div class="date">{$post->created_at|date:'F j, Y'}</div> <h2>{$post->title}</h2> @@ -164,42 +161,41 @@ Visualizziamo i post del blog: modificheremo il template in questo modo: {/block} ``` -Se si aggiorna il browser, si vedrà l'elenco dei post del blog. L'elenco non è molto elegante o colorato, quindi sentitevi liberi di aggiungere qualche [CSS brillante |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] a `www/css/style.css` e di collegarlo a un layout: +Se aggiorniamo il browser, vedremo l'elenco di tutti i post. L'elenco per ora non è molto bello, né colorato, quindi possiamo aggiungere al file `www/css/style.css` alcuni [stili CSS |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] e collegarlo nel layout: -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <link rel="stylesheet" href="{$basePath}/css/style.css"> </head> ... ``` -Il tag `{foreach}` itera tutti i post passati al template nella variabile `$posts` e mostra un pezzo di codice HTML per ogni post. Proprio come farebbe un codice PHP. +Il tag `{foreach}` itera su tutti i post che abbiamo passato al template nella variabile `$posts`, e per ognuno renderizza la porzione di HTML specificata. Si comporta esattamente come il codice PHP. -L'elemento `|date` è chiamato filtro. I filtri sono usati per formattare l'output. Questo particolare filtro converte una data (per esempio `2013-04-12`) nella sua forma più leggibile (`12. 4. 2013`). Il filtro `|truncate` tronca la stringa alla lunghezza massima specificata e aggiunge un'ellissi alla fine se la stringa è troncata. Trattandosi di un'anteprima, non ha senso visualizzare il contenuto completo dell'articolo. Altri filtri predefiniti sono disponibili [nella documentazione |latte:filters] o, se necessario, è possibile crearne di propri. +Alla scrittura `|date:` diciamo filtro. I filtri sono destinati alla formattazione dell'output. Questo particolare filtro converte la data (ad es. `2013-04-12`) nella sua forma più leggibile (`April 12, 2013`). Il filtro `|truncate` tronca la stringa alla lunghezza massima specificata e, nel caso in cui la stringa venga accorciata, aggiunge tre puntini alla fine. Poiché si tratta di un'anteprima, non ha senso visualizzare l'intero contenuto dell'articolo. Altri filtri predefiniti [li troviamo nella documentazione |latte:filters] oppure possiamo crearne di nostri, quando necessario. -Un'altra cosa. Possiamo rendere il codice un po' più corto e quindi più semplice. Possiamo sostituire i tag *Latte* con *n:attributi* in questo modo: +Ancora una cosa. Il codice precedente può essere accorciato e semplificato. Lo otteniamo sostituendo i *tag Latte* con gli *n:attributi*: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - <h1>My blog</h1> + <h1>Il mio blog</h1> <div n:foreach="$posts as $post" class="post"> <div class="date">{$post->created_at|date:'F j, Y'}</div> <h2>{$post->title}</h2> - <div>{$post->content}</div> + <div>{$post->content|truncate:256}</div> </div> {/block} ``` -`n:foreach`, semplicemente avvolge il *div* con un blocco *foreach* (fa esattamente la stessa cosa del blocco di codice precedente). +L'attributo `n:foreach` avvolge il blocco *div* con un *foreach* (funziona esattamente come il codice precedente). -Sintesi .[#toc-summary] -======================= +Riepilogo +========= -Abbiamo un database MySQL molto semplice con alcuni post del blog. L'applicazione si connette al database e visualizza un semplice elenco dei post. +Ora abbiamo un database MySQL molto semplice con alcuni post. L'applicazione si connette a questo database e visualizza un semplice elenco di questi post nel template. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/it/model.texy b/quickstart/it/model.texy index 9a6c891511..2aeaec0014 100644 --- a/quickstart/it/model.texy +++ b/quickstart/it/model.texy @@ -1,13 +1,13 @@ -Modello -******* +Model +***** -Man mano che la nostra applicazione cresce, ci accorgiamo presto di dover eseguire operazioni di database simili in varie posizioni e in vari presentatori, ad esempio acquisendo gli articoli pubblicati più recenti. Se miglioriamo la nostra applicazione aggiungendo un flag agli articoli per indicare lo stato di work-in-progress, dobbiamo anche esaminare tutte le posizioni della nostra applicazione e aggiungere una clausola where per assicurarci che vengano selezionati solo gli articoli finiti. +Man mano che l'applicazione cresce, ci accorgeremo presto che in diversi punti, in diversi presenter, abbiamo bisogno di eseguire operazioni simili con il database. Ad esempio, ottenere gli articoli pubblicati più di recente. Se miglioriamo l'applicazione, ad esempio aggiungendo un flag agli articoli per indicare se sono in bozza, dobbiamo poi passare attraverso tutti i punti dell'applicazione in cui gli articoli vengono recuperati dal database e aggiungere una condizione where per selezionare solo gli articoli non in bozza. -A questo punto, il lavoro diretto con il database diventa insufficiente e sarà più intelligente aiutarsi con una nuova funzione che restituisca gli articoli pubblicati. E quando in seguito aggiungeremo un'altra clausola (ad esempio per non visualizzare gli articoli con data futura), modificheremo il codice in un solo punto. +In quel momento, il lavoro diretto con il database diventa insufficiente e sarà più pratico aiutarci con una nuova funzione che ci restituirà gli articoli pubblicati. E quando in seguito aggiungeremo un'altra condizione, ad esempio che non devono essere visualizzati articoli con data futura, modificheremo il codice solo in un punto. -Inseriamo la funzione nella classe `PostFacade` e la chiamiamo `getPublicArticles()`. +Posizioneremo la funzione ad esempio nella classe `PostFacade` e la chiameremo `getPublicArticles()`. -Creeremo la nostra classe modello `PostFacade` nella cartella `app/Model/` per occuparci dei nostri articoli: +Nella directory `app/Model/` creiamo la nostra classe model `PostFacade`, che si occuperà degli articoli: ```php .{file:app/Model/PostFacade.php} <?php @@ -32,13 +32,13 @@ final class PostFacade } ``` -Nella classe passiamo il database Explorer:[api:Nette\Database\Explorer]. Questo sfrutterà la potenza del [contenitore DI |dependency-injection:passing-dependencies]. +Nella classe, tramite il costruttore, ci facciamo passare l'Explorer del database:[api:Nette\Database\Explorer]. Sfruttiamo così la potenza del [container DI|dependency-injection:passing-dependencies]. -Passiamo a `HomePresenter`, che modificheremo in modo da eliminare la dipendenza da `Nette\Database\Explorer`, sostituendola con una nuova dipendenza dalla nostra nuova classe. +Passiamo a `HomePresenter`, che modificheremo in modo da eliminare la dipendenza da `Nette\Database\Explorer` e sostituirla con la nuova dipendenza dalla nostra nuova classe. -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use App\Model\PostFacade; use Nette; @@ -59,10 +59,9 @@ final class HomePresenter extends Nette\Application\UI\Presenter } ``` -Nella sezione d'uso, stiamo usando `App\Model\PostFacade`, quindi possiamo abbreviare il codice PHP in `PostFacade`. Richiediamo questo oggetto nel costruttore, lo scriviamo nella proprietà `$facade` e lo usiamo nel metodo renderDefault. +Nella sezione `use` abbiamo `App\Model\PostFacade`, quindi possiamo abbreviare la scrittura nel codice PHP in `PostFacade`. Richiediamo questo oggetto nel costruttore, lo scriviamo nella proprietà `$facade` e lo usiamo nel metodo `renderDefault`. -L'ultimo passo da fare è insegnare al contenitore DI a produrre questo oggetto. Di solito lo si fa aggiungendo un punto elenco al file `config/services.neon` nella sezione `services`, indicando il nome completo della classe e i parametri del costruttore. -Questo lo registra, per così dire, e l'oggetto viene chiamato **service**. Grazie a una magia chiamata [autowiring |dependency-injection:autowiring], di solito non è necessario specificare i parametri del costruttore, perché DI li riconosce e li passa automaticamente. Pertanto, sarebbe sufficiente fornire solo il nome della classe: +Resta l'ultimo passo, ovvero insegnare al container DI a produrre questo oggetto. Di solito si fa aggiungendo un trattino nella sezione `services` del file `config/services.neon`, indicando il nome completo della classe e i parametri del costruttore. In questo modo la registriamo e l'oggetto viene quindi chiamato **servizio**. Grazie alla magia chiamata [autowiring |dependency-injection:autowiring] di solito non dobbiamo specificare i parametri del costruttore, perché DI li riconosce e li passa da solo. Basterebbe quindi indicare solo il nome della classe: ```neon .{file:config/services.neon} ... @@ -71,16 +70,15 @@ services: - App\Model\PostFacade ``` -Tuttavia, non è necessario aggiungere questa riga. Nella sezione `search` all'inizio di `services.neon` si definisce che tutte le classi che terminano con `-Facade` o `-Factory` saranno cercate automaticamente da DI, cosa che avviene anche per `PostFacade`. +Tuttavia, non è nemmeno necessario aggiungere questa riga. Nella sezione `search` all'inizio di `services.neon` è definito che tutte le classi che terminano con la parola `-Facade` o `-Factory` vengono trovate automaticamente da DI, il che è anche il caso di `PostFacade`. -Riepilogo .[#toc-summary] -========================= +Riepilogo +========= -La classe `PostFacade` chiede `Nette\Database\Explorer` in un costruttore e poiché questa classe è registrata nel contenitore DI, il contenitore crea questa istanza e la passa. DI crea così un'istanza di `PostFacade` per noi e la passa in un costruttore alla classe HomePresenter che l'ha richiesta. Una specie di matrioska di codice. :) Tutti i componenti richiedono solo ciò di cui hanno bisogno e non si preoccupano di dove e come viene creato. La creazione è gestita dal contenitore DI. +La classe `PostFacade` richiede nel costruttore il passaggio di `Nette\Database\Explorer` e poiché questa classe è registrata nel container DI, il container crea questa istanza e la passa. DI crea così per noi l'istanza di `PostFacade` e la passa nel costruttore alla classe HomePresenter, che l'ha richiesta. Una specie di matrioska. :) Tutti dicono solo cosa vogliono e non si preoccupano di dove e come viene creato qualcosa. Della creazione si occupa il container DI. .[note] -Qui si possono leggere ulteriori informazioni sulla [dependency injection |dependency-injection:introduction] e sulla [configurazione |nette:configuring]. +Qui potete leggere di più sulla [dependency injection |dependency-injection:introduction] e sulla [configurazione |nette:configuring]. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/it/single-post.texy b/quickstart/it/single-post.texy index a897bca6ee..46c63e7191 100644 --- a/quickstart/it/single-post.texy +++ b/quickstart/it/single-post.texy @@ -1,17 +1,17 @@ -Pagina del singolo post -*********************** +Pagina con il post +****************** .[perex] -Aggiungiamo un'altra pagina al nostro blog, che mostrerà il contenuto di un particolare post. +Ora creeremo un'altra pagina del blog che visualizzerà un singolo post specifico. -Dobbiamo creare un nuovo metodo di rendering, che recupererà un post specifico del blog e lo passerà al template. Avere questa vista in `HomePresenter` non è bello, perché si tratta di un post del blog, non della homepage. Quindi, creiamo una nuova classe `PostPresenter` e posizioniamola in `app/Presenters`. Avrà bisogno di una connessione al database, quindi inseriamo di nuovo il codice *database injection*. +Dobbiamo creare un nuovo metodo render che ottenga un articolo specifico e lo passi al template. Avere questo metodo in `HomePresenter` non è molto bello, perché stiamo parlando di un articolo e non della pagina iniziale. Creiamo quindi `PostPresenter` in `app/Presentation/Post/`. Questo presenter ha anche bisogno di connettersi al database, quindi scriveremo di nuovo un costruttore che richiederà la connessione al database. -`PostPresenter` dovrebbe avere questo aspetto: +`PostPresenter` potrebbe quindi apparire così: -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Post; use Nette; use Nette\Application\UI\Form; @@ -23,50 +23,50 @@ final class PostPresenter extends Nette\Application\UI\Presenter ) { } - public function renderShow(int $postId): void + public function renderShow(int $id): void { $this->template->post = $this->database ->table('posts') - ->get($postId); + ->get($id); } } ``` -Dobbiamo impostare un namespace `App\Presenters` corretto per il nostro presentatore. Dipende dalla [mappatura del presentatore |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7]. +Non dobbiamo dimenticare di specificare il namespace corretto `App\Presentation\Post`, che è soggetto all'impostazione del [mapping dei presenter |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7]. -Il metodo `renderShow` richiede un solo parametro: l'ID del post da visualizzare. Quindi, carica il post dal database e passa il risultato al template. +Il metodo `renderShow` richiede un argomento - l'ID di un articolo specifico che deve essere visualizzato. Successivamente carica questo articolo dal database e lo passa al template. -Nel modello `Home/default.latte` aggiungiamo un link all'azione `Post:show`: +Nel template `Home/default.latte` inseriamo un link all'azione `Post:show`. -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a href="{link Post:show $post->id}">{$post->title}</a></h2> ... ``` -Il tag `{link}` genera un indirizzo URL che punta all'azione `Post:show`. Questo tag inoltra anche l'ID del post come argomento. +Il tag `{link}` genera un indirizzo URL che punta all'azione `Post:show`. Passa anche l'ID del post come argomento. -Lo stesso si può scrivere brevemente usando n:attribute: +Lo stesso possiamo scriverlo in forma abbreviata usando un n:attributo: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a n:href="Post:show $post->id">{$post->title}</a></h2> ... ``` -L'attributo `n:href` è simile al tag `{link}`. +L'attributo `n:href` è analogo al tag `{link}`. -Il template per l'azione `Post:show` non esiste ancora. Possiamo aprire un link a questo post. [Tracy |tracy:] mostrerà un errore, perché `Post/show.latte` non esiste. Se viene visualizzato un altro errore, probabilmente è necessario attivare il mod_rewrite nel server web. +Per l'azione `Post:show`, tuttavia, non esiste ancora un template. Possiamo provare ad aprire il link a questo post. [Tracy |tracy:] visualizzerà un errore, perché il template `Post/show.latte` non esiste ancora. Se vedete un messaggio di errore diverso, probabilmente dovrete abilitare `mod_rewrite` sul webserver. -Creeremo quindi `Post/show.latte` con questo contenuto: +Creiamo quindi il template `Post/show.latte` con questo contenuto: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} {block content} -<p><a n:href="Home:default">← back to posts list</a></p> +<p><a n:href="Home:default">← torna all'elenco dei post</a></p> <div class="date">{$post->created_at|date:'F j, Y'}</div> @@ -75,51 +75,50 @@ Creeremo quindi `Post/show.latte` con questo contenuto: <div class="post">{$post->content}</div> ``` -Diamo un'occhiata alle singole parti. +Ora esaminiamo le singole parti del template. -La prima riga inizia la definizione di un *blocco con nome* chiamato "content", che abbiamo visto in precedenza. Verrà visualizzato in un *modello di layout*. Come si può notare, manca il tag finale `{/block}`. È opzionale. +La prima riga inizia la definizione del blocco con il nome "content" proprio come sulla pagina iniziale. Questo blocco sarà nuovamente visualizzato nel template principale. Come vedete, manca il tag di chiusura `{/block}`. Questo è infatti facoltativo. -La seconda riga fornisce un backlink all'elenco dei post del blog, in modo che l'utente possa navigare agevolmente avanti e indietro nel nostro blog. Utilizziamo di nuovo l'attributo `n:href`, quindi Nette si occuperà di generare l'URL per noi. Il link punta all'azione `default` del presentatore `Home` (si potrebbe anche scrivere `n:href="Home:"`, perché l'azione `default` può essere omessa). +Sulla riga successiva c'è un link per tornare all'elenco degli articoli del blog, così l'utente può muoversi facilmente tra l'elenco degli articoli e uno specifico. Poiché usiamo l'attributo `n:href`, Nette si occupa da solo della generazione dei link. Il link punta all'azione `default` del presenter `Home` (possiamo scrivere anche `n:href="Home:"`, perché l'azione con il nome `default` può essere omessa, viene aggiunta automaticamente). -La terza riga formatta il timestamp di pubblicazione con un filtro, come già sappiamo. +La terza riga formatta la visualizzazione della data usando il filtro che già conosciamo. -La quarta riga mostra il *titolo* del post del blog come un `<h1>` titolo. C'è una parte che forse non conoscete: `n:block="title"`. Riuscite a indovinare cosa fa? Se avete letto attentamente le parti precedenti, abbiamo menzionato `n: attributes`. Questo è un altro esempio. È equivalente a: +La quarta riga visualizza il *titolo* del blog nel tag HTML `<h1>`. Questo tag contiene un attributo che forse non conoscete (`n:block="title"`). Indovinate cosa fa? Se avete letto attentamente la parte precedente, sapete già che si tratta di un `n:attributo`. Questo è un altro esempio, che è equivalente a: ```latte {block title}<h1>{$post->title}</h1>{/block} ``` -In parole povere, *ridefinisce* un blocco chiamato `title`. Il blocco è definito nel *modello di layout* (`/app/Presenters/templates/@layout.latte:11`) e, come per l'overriding OOP, viene sovrascritto qui. Pertanto, la pagina `<title>` conterrà il titolo del post visualizzato. Abbiamo sovrascritto il titolo della pagina e tutto ciò di cui avevamo bisogno era `n:block="title"`. Ottimo, no? +In parole povere, questo blocco ridefinisce il blocco con il nome `title`. Questo blocco è già definito nel template principale *layout* (`/app/Presentation/@layout.latte:11`) e, proprio come nella sovrascrittura dei metodi in OOP, questo blocco nel template principale viene sovrascritto allo stesso modo. Quindi il `<title>` della pagina ora contiene il titolo del post visualizzato e ci è bastato usare solo un semplice attributo `n:block="title"`. Fantastico, vero? -La quinta e ultima riga del template mostra il contenuto completo del post. +La quinta e ultima riga del template visualizza l'intero contenuto di un post specifico. -Controllo dell'ID del post .[#toc-checking-post-id] -=================================================== +Controllo dell'ID del post +========================== -Cosa succede se qualcuno altera l'URL e inserisce `postId` che non esiste? Dovremmo fornire all'utente un simpatico errore di "pagina non trovata". Aggiorniamo il metodo di rendering in `PostPresenter`: +Cosa succede se qualcuno cambia l'ID nell'URL e inserisce un `id` inesistente? Dovremmo offrire all'utente un bell'errore del tipo "pagina non trovata". Modifichiamo quindi un po' il metodo render in `PostPresenter`: -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { - $this->error('Post not found'); + $this->error('Pagina non trovata'); } $this->template->post = $post; } ``` -Se il post non può essere trovato, la chiamata a `$this->error(...)` mostrerà una pagina 404 con un messaggio chiaro e comprensibile. Si noti che nell'ambiente di sviluppo (sul portatile), non si vedrà la pagina di errore. Invece, Tracy mostrerà l'eccezione con tutti i dettagli, il che è piuttosto comodo per lo sviluppo. È possibile controllare entrambe le modalità, basta cambiare il valore passato a `setDebugMode` in `Bootstrap.php`. +Se il post non può essere trovato, chiamando `$this->error(...)` visualizziamo una pagina di errore 404 con un messaggio comprensibile. Attenzione al fatto che in modalità sviluppatore (localhost) non vedrete questa pagina di errore. Al suo posto apparirà Tracy con i dettagli sull'eccezione, il che è abbastanza vantaggioso per lo sviluppo. Se vogliamo visualizzare entrambe le modalità, basta semplicemente cambiare l'argomento del metodo `setDebugMode` nel file `Bootstrap.php`. -Riepilogo .[#toc-summary] -========================= +Riepilogo +========= -Abbiamo un database con i post di un blog e un'applicazione web con due viste: la prima visualizza il riepilogo di tutti i post recenti e la seconda visualizza un post specifico. +Abbiamo un database con i post e un'applicazione web che ha due viste - la prima visualizza un riepilogo di tutti i post e la seconda visualizza un post specifico. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/ja/@home.texy b/quickstart/ja/@home.texy new file mode 100644 index 0000000000..c74404c43b --- /dev/null +++ b/quickstart/ja/@home.texy @@ -0,0 +1,119 @@ +最初のアプリケーションを作成しましょう! +******************** + +.[perex] +コメント付きのシンプルなブログを作成しながら、Nette Framework を一緒に学びましょう。さあ、始めましょう! + +最初の2つの章の後には、独自の機能的なブログを持ち、素晴らしい投稿を公開できるようになりますが、機能はまだかなり制限されています。コメントの追加、記事の編集、そして最後にブログのセキュリティ保護をプログラムする次の章も読むべきです。 + +.[tip] +このガイドは、[インストール |nette:installation]ページを読み、必要なツールを正常に準備したことを前提としています。また、[PHP でのオブジェクト指向プログラミング |nette:introduction-to-object-oriented-programming]を理解していることを前提としています。 + +PHP 8.1以降を使用してください。完全なアプリケーションは[GitHub |https://github.com/nette-examples/quickstart/tree/v4.0]で見つけることができます。 + + +ウェルカムページ +======== + +まず、`nette-blog`ディレクトリに新しいプロジェクトを作成することから始めましょう: + +```shell +composer create-project nette/web-project nette-blog +``` + +この時点で、Web Projectの初期ページはすでに機能しているはずです。ブラウザで次のURLを開いて試してみましょう: + +``` +http://localhost/nette-blog/www/ +``` + +そして、Nette Frameworkの初期ページが表示されます: + +[* qs-welcome.webp .{url: http://localhost/nette-blog/www/} *] + +アプリケーションは機能しており、編集を開始できます。 + +.[note] +問題が発生した場合は、[これらのいくつかのヒント |nette:troubleshooting#Netteが動作せず 白いページが表示される]を試してください。 + + +Web Project の内容 +=============== + +Web Projectには次の構造があります: + +/--pre +<b>nette-blog/</b> +├── <b>app/</b> ← アプリケーションディレクトリ +│ ├── <b>Core/</b> ← 動作に必要な基本クラス +│ ├── <b>Presentation/</b> ← Presenter、テンプレートなど +│ │ └── <b>Home/</b> ← Home Presenter のディレクトリ +│ └── <b>Bootstrap.php</b> ← ブートストラップクラス Bootstrap +├──<b>assets/</b> ← 生アセット(SCSS、TypeScript、ソース画像) +├── <b>bin/</b> ← コマンドラインから実行されるスクリプト +├── <b>config/</b> ← 設定ファイル +├── <b>log/</b> ← エラーログ +├── <b>temp/</b> ← 一時ファイル、キャッシュなど +├── <b>vendor/</b> ← Composer によってインストールされたライブラリ +│ └── <b>autoload.php</b> ← インストールされたすべてのパッケージのオートロード +└── <b>www/</b> ← 公開ディレクトリ - ブラウザからアクセス可能な唯一のディレクトリ + <b>assets/</b> ← コンパイルされた静的ファイル(CSS、JS、画像、...) + └── <b>index.php</b> ← アプリケーションが起動する最初のファイル +\-- + +`www/`ディレクトリは、画像、JavaScriptファイル、CSSスタイル、およびその他の公開アクセス可能なファイルを保存するためのものです。このディレクトリのみがインターネットからアクセス可能なので、アプリケーションのルートディレクトリをここに設定してください(これはApacheまたはnginxの設定で設定できますが、後で行いましょう。今は重要ではありません)。 + +私たちにとって最も重要なフォルダは`app/`です。ここには`Bootstrap.php`ファイルがあり、フレームワーク全体を読み込み、アプリケーションを設定するクラスが含まれています。ここで[オートロード |robot-loader:]がアクティブになり、[デバッガー |tracy:]と[ルート |application:routing]が設定されます。 + + +クリーンアップ +======= + +Web Projectには初期ページが含まれており、プログラミングを開始する前に削除します。したがって、ファイル`app/Presentation/Home/default.latte`の内容をためらうことなく「Hello world!」に置き換えます。 + + +[* qs-hello.webp .{url:-} *] + + +Tracy (デバッガー) +============= + +開発にとって非常に重要なツールは[デバッグツール Tracy |tracy:]です。ファイル`app/Presentation/Home/HomePresenter.php`で何らかのエラーを引き起こしてみてください(例えば、HomePresenterクラス定義の波括弧を削除するなど)、何が起こるか見てみましょう。エラーを分かりやすく説明する通知ページが表示されます。 + +[* qs-tracy.avif .{url:-}(debugger screen) *] + +Tracyは、アプリケーションのエラーを探す際に非常に役立ちます。また、画面右下隅にフローティングTracyバーが表示され、アプリケーションの実行に関する情報が含まれていることにも注意してください。 + +[* qs-tracybar.webp .{url:-} *] + +本番モードでは、Tracyはもちろん無効になっており、機密情報は表示されません。この場合、すべてのエラーは`log/`フォルダに保存されます。試してみましょう。`app/Bootstrap.php`ファイルで次の行のコメントを解除し、呼び出しのパラメータを`false`に変更して、コードが次のようになるようにします: + +```php .{file:app/Bootstrap.php} +... +$this->configurator->setDebugMode(false); +... +``` + +ページを更新すると、Tracyは表示されなくなります。代わりに、ユーザーフレンドリーなメッセージが表示されます: + +[* qs-fatal.webp .{url:-}(error screen) *] + +次に、`log/`ディレクトリを見てみましょう。ここ(`exception.log`ファイル)に、ログに記録されたエラーと、既知のエラーメッセージページ(`exception-`で始まる名前のHTMLファイルとして保存されている)が見つかります。 + +行`// $configurator->setDebugMode(false);`を再度コメントアウトします。Tracyはlocalhostで開発者モードを自動的に有効にし、他の場所では無効にします。 + +作成したエラーを修正し、アプリケーションの作成を続行できます。 + + +感謝を送る +===== + +オープンソースの作者を喜ばせるトリックをお見せします。簡単な方法で、プロジェクトが使用しているライブラリにGitHubでスターを付けることができます。実行するだけです: + +```shell +composer thanks +``` + +試してみてください! + +{{priority: -1}} diff --git a/quickstart/ja/@left-menu.texy b/quickstart/ja/@left-menu.texy new file mode 100644 index 0000000000..0cd4c07776 --- /dev/null +++ b/quickstart/ja/@left-menu.texy @@ -0,0 +1,9 @@ +チュートリアル +******* +- [最初のアプリケーションを作成しましょう! |@home] +- [ブログのホームページ |home-page] +- [投稿ページ |single-post] +- [コメント |comments] +- [投稿の作成と編集 |creating-posts] +- [モデル |Model] +- [認証 |authentication] diff --git a/quickstart/ja/@meta.texy b/quickstart/ja/@meta.texy new file mode 100644 index 0000000000..1de030b40d --- /dev/null +++ b/quickstart/ja/@meta.texy @@ -0,0 +1 @@ +{{sitename: 最初のアプリケーションを作成しましょう!}} diff --git a/quickstart/ja/authentication.texy b/quickstart/ja/authentication.texy new file mode 100644 index 0000000000..3c64a99ad7 --- /dev/null +++ b/quickstart/ja/authentication.texy @@ -0,0 +1,179 @@ +認証 +************* + +Netteは、私たちのサイトで認証をプログラムする方法を提供しますが、何も強制しません。実装は私たち次第です。Netteには`Nette\Security\Authenticator`インターフェースが含まれており、これは`authenticate`という1つのメソッドのみを要求します。このメソッドは、私たちが望む任意の方法でユーザーを検証します。 + +ユーザーを認証する方法はたくさんあります。最も一般的な認証方法はパスワードによるものです(ユーザーは名前またはメールアドレスとパスワードを提供します)が、他の方法もあります。一部のサイトで「Facebookでログイン」ボタンやGoogle/Twitter/GitHubによるログインを見たことがあるかもしれません。Netteを使用すると、任意のログイン方法を持つことができ、それらを組み合わせることもできます。実装は開発者に委ねられています。 + +通常は独自の認証システムを作成しますが、このシンプルな小さなブログでは、設定ファイルに保存されているパスワードとユーザー名に基づいてログインする組み込みの認証システムを使用します。これはテスト目的に適しています。したがって、設定ファイル`config/common.neon`に次の*security*セクションを追加します。 + + +```neon .{file:config/common.neon} +security: + users: + admin: secret # ユーザー 'admin', パスワード 'secret' +``` + +NetteはDIコンテナに自動的にサービスを作成します。 + + +ログインフォーム +======== + +これで認証の準備が整い、ログイン用のユーザーインターフェースを準備する必要があります。*SignPresenter*という名前の新しいPresenterを作成しましょう。これは次のことを行います。 + +- ログインフォーム(ログイン名とパスワード付き)を表示する +- フォーム送信後、ユーザーを認証する +- ログアウトオプションを提供する + +ログインフォームから始めましょう。Presenterでフォームがどのように機能するかはすでに知っています。したがって、Presenter `SignPresenter`を作成し、メソッド`createComponentSignInForm`を記述します。次のようになります。 + +```php .{file:app/Presentation/Sign/SignPresenter.php} +<?php +namespace App\Presentation\Sign; + +use Nette; +use Nette\Application\UI\Form; + +final class SignPresenter extends Nette\Application\UI\Presenter +{ + protected function createComponentSignInForm(): Form + { + $form = new Form; + $form->addText('username', 'ユーザー名:') + ->setRequired('ユーザー名を入力してください。'); + + $form->addPassword('password', 'パスワード:') + ->setRequired('パスワードを入力してください。'); + + $form->addSubmit('send', 'ログイン'); + + $form->onSuccess[] = $this->signInFormSucceeded(...); + return $form; + } +} +``` + +ユーザー名とパスワードのフィールドがあります。 + + +テンプレート +------ + +フォームはテンプレート`in.latte`でレンダリングされます。 + +```latte .{file:app/Presentation/Sign/in.latte} +{block content} +<h1 n:block=title>ログイン</h1> + +{control signInForm} +``` + + +ログインコールバック +---------- + +次に、フォームが正常に送信された直後に呼び出されるユーザーログイン用のコールバックを追加します。 + +コールバックは、ユーザーが入力したユーザー名とパスワードを受け取り、それらを認証システムに渡すだけです。ログイン後、ホームページにリダイレクトします。 + +```php .{file:app/Presentation/Sign/SignPresenter.php} +private function signInFormSucceeded(Form $form, \stdClass $data): void +{ + try { + $this->getUser()->login($data->username, $data->password); + $this->redirect('Home:'); + + } catch (Nette\Security\AuthenticationException $e) { + $form->addError('ユーザー名またはパスワードが正しくありません。'); + } +} +``` + +メソッド[User::login() |api:Nette\Security\User::login()]は、ユーザー名とパスワードが設定ファイルの情報と一致しない場合に例外をスローします。すでに知っているように、これにより赤いエラーページが表示されるか、本番モードではサーバーエラーを通知するメッセージが表示される可能性があります。しかし、それは望ましくありません。したがって、この例外をキャッチし、フォームにわかりやすい、ユーザーフレンドリーなエラーメッセージを渡します。 + +フォームでエラーが発生すると、フォームのあるページが再描画され、フォームの上に、ユーザーが間違ったログイン名またはパスワードを入力したことを通知するわかりやすいメッセージが表示されます。 + + +Presenterの保護 +============ + +投稿を追加および編集するためのフォームを保護します。これはPresenter `EditPresenter`で定義されています。目標は、ログインしていないユーザーがページにアクセスできないようにすることです。 + +[Presenterのライフサイクル |application:presenters#Presenterのライフサイクル]の開始直後に実行される`startup()`メソッドを作成します。これにより、ログインしていないユーザーがログインフォームにリダイレクトされます。 + +```php .{file:app/Presentation/Edit/EditPresenter.php} +public function startup(): void +{ + parent::startup(); + + if (!$this->getUser()->isLoggedIn()) { + $this->redirect('Sign:in'); + } +} +``` + + +リンクの非表示 +------- + +認証されていないユーザーは、*create*ページや*edit*ページを表示できなくなりましたが、それらへのリンクはまだ表示されます。これらも非表示にする必要があります。そのようなリンクの1つはテンプレート`app/Presentation/Home/default.latte`にあり、ログインしているユーザーのみが表示できるようにする必要があります。 + +*n:属性* `n:if`を使用して非表示にできます。この条件が`false`の場合、コンテンツを含む`<a>`タグ全体が非表示のままになります。 + +```latte +<a n:href="Edit:create" n:if="$user->isLoggedIn()">投稿を作成</a> +``` + +これは、次の記述の省略形です(`tag-if`と混同しないでください): + +```latte +{if $user->isLoggedIn()}<a n:href="Edit:create">投稿を作成</a>{/if} +``` + +同様の方法で、テンプレート`app/Presentation/Post/show.latte`のリンクも非表示にします。 + + +ログインリンク +======= + +実際にログインページにアクセスするにはどうすればよいでしょうか?そこにつながるリンクはありません。それでは、テンプレート`@layout.latte`に追加しましょう。適切な場所を見つけてみてください - ほとんどどこでもかまいません。 + +```latte .{file:app/Presentation/@layout.latte} +... +<ul class="navig"> + <li><a n:href="Home:">記事</a></li> + {if $user->isLoggedIn()} + <li><a n:href="Sign:out">ログアウト</a></li> + {else} + <li><a n:href="Sign:in">ログイン</a></li> + {/if} +</ul> +... +``` + +ユーザーがログインしていない場合は、「ログイン」リンクが表示されます。それ以外の場合は、「ログアウト」リンクが表示されます。このアクションも`SignPresenter`に追加します。 + +ログアウト後すぐにユーザーをリダイレクトするため、テンプレートは必要ありません。ログアウトは次のようになります。 + +```php .{file:app/Presentation/Sign/SignPresenter.php} +public function actionOut(): void +{ + $this->getUser()->logout(); + $this->flashMessage('ログアウトしました。'); + $this->redirect('Home:'); +} +``` + +`logout()`メソッドが呼び出され、その後、ログアウトが成功したことを確認するわかりやすいメッセージが表示されます。 + + +まとめ +=== + +ログインとユーザーログアウトのためのリンクがあります。検証には組み込みの認証システムを使用し、ログイン情報は設定ファイルにあります。これは簡単なテストアプリケーションであるためです。また、編集フォームも保護したので、ログインしているユーザーのみが投稿を追加および編集できます。 + +.[note] +ここでは、[ユーザーログイン |security:authentication]と[権限の検証 |security:authorization]について詳しく読むことができます。 + +{{priority: -1}} diff --git a/quickstart/ja/comments.texy b/quickstart/ja/comments.texy new file mode 100644 index 0000000000..e941880694 --- /dev/null +++ b/quickstart/ja/comments.texy @@ -0,0 +1,171 @@ +コメント +**** + +ブログをウェブサーバーにアップロードし、Adminer を使って非常に興味深い記事をいくつか公開しました。人々は私たちのブログを読み、非常に熱心です。毎日、賞賛のメールをたくさん受け取っています。しかし、この賞賛がメールの中にしかなく、誰も読むことができなければ、何の意味があるのでしょうか?読者が記事に直接コメントできれば、誰もが私たちがどれほど素晴らしいかを読むことができるので、より良いでしょう。 + +それでは、コメントをプログラムしましょう。 + + +新しいテーブルの作成 +========== + +Adminer を起動し、次のカラムを持つ `comments` テーブルを作成します。 + +- `id` int、オートインクリメント (AI) にチェック +- `post_id`、`posts` テーブルを参照する外部キー +- `name` varchar、長さ 255 +- `email` varchar、長さ 255 +- `content` text +- `created_at` timestamp + +したがって、テーブルはこのようになります。 + +[* adminer-comments.webp *] + +再度、InnoDB ストレージを使用することを忘れないでください。 + +```sql +CREATE TABLE `comments` ( + `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY, + `post_id` int(11) NOT NULL, + `name` varchar(250) NOT NULL, + `email` varchar(250) NOT NULL, + `content` text NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`) +) ENGINE=InnoDB CHARSET=utf8; +``` + + +コメントフォーム +======== + +まず、ユーザーが記事にコメントできるようにするフォームを作成する必要があります。Nette Framework はフォームに対する素晴らしいサポートを提供しています。Presenter で設定し、テンプレートでレンダリングできます。 + +Nette Framework は *コンポーネント* の概念を利用しています。**コンポーネント** は、別のコンポーネントに添付できる再利用可能なクラスまたはコードの一部です。Presenter でさえコンポーネントです。各コンポーネントはファクトリを通じて作成されます。そこで、`PostPresenter` にコメントフォームのファクトリを作成します。 + +```php .{file:app/Presentation/Post/PostPresenter.php} +protected function createComponentCommentForm(): Form +{ + $form = new Form; // Nette\Application\UI\Form を意味します + + $form->addText('name', '名前:') + ->setRequired(); + + $form->addEmail('email', 'E-mail:'); + + $form->addTextArea('content', 'コメント:') + ->setRequired(); + + $form->addSubmit('send', 'コメントを公開'); + + return $form; +} +``` + +これを少し説明しましょう。最初の行は `Form` コンポーネントの新しいインスタンスを作成します。続くメソッドは、このフォーム定義に HTML 入力要素を追加します。`->addText` は `<label>名前:</label>` 付きの `<input type="text" name="name">` としてレンダリングされます。すでにお察しの通り、`->addTextArea` は `<textarea>` として、`->addSubmit` は `<input type="submit">` としてレンダリングされます。同様のメソッドは他にもたくさんありますが、このフォームにはこれで十分です。[ドキュメントで詳細を読むことができます|forms:]。 + +フォームが Presenter で定義されたら、テンプレートでレンダリング(表示)できます。これを行うには、特定の記事を表示するテンプレート `Post/show.latte` の最後に `{control}` タグを配置します。コンポーネント名は `commentForm`(メソッド名 `createComponentCommentForm` から派生)なので、タグは次のようになります。 + +```latte .{file:app/Presentation/Post/show.latte} +... +<h2>新しいコメントを投稿</h2> + +{control commentForm} +``` + +これで記事詳細ページを表示すると、その最後に新しいコメントフォームが表示されます。 + + +データベースへの保存 +========== + +フォームに入力して送信してみましたか?おそらく、フォームが実際には何もしていないことに気づいたでしょう。送信されたデータを保存するコールバックメソッドを接続する必要があります。 + +`commentForm` コンポーネントのファクトリの `return` の前の行に、次の行を配置します。 + +```php +$form->onSuccess[] = $this->commentFormSucceeded(...); +``` + +上記の記述は、「フォームが正常に送信された後、現在の Presenter の `commentFormSucceeded` メソッドを呼び出す」ことを意味します。しかし、このメソッドはまだ存在しません。それでは作成しましょう。 + +```php .{file:app/Presentation/Post/PostPresenter.php} +private function commentFormSucceeded(\stdClass $data): void +{ + $id = $this->getParameter('id'); + + $this->database->table('comments')->insert([ + 'post_id' => $id, + 'name' => $data->name, + 'email' => $data->email, + 'content' => $data->content, + ]); + + $this->flashMessage('コメントありがとうございます', 'success'); + $this->redirect('this'); +} +``` + +このメソッドを `commentForm` フォームファクトリの直後に配置します。 + +この新しいメソッドには 1 つの引数があり、これは送信されたフォームのインスタンスです - ファクトリによって作成されました。送信された値は `$data` で取得します。そして、データをデータベーステーブル `comments` に保存します。 + +説明が必要なメソッドが他に 2 つあります。`redirect` メソッドは文字通り現在のページにリダイレクトします。これは、フォームが有効なデータを含み、コールバックが操作を正しく実行した場合、フォーム送信後に毎回行うのが適切です。また、フォーム送信後にページをリダイレクトすると、ブラウザで時々見かける「フォームデータを再送信しますか?」というよく知られたメッセージが表示されなくなります。(一般的に、`POST` メソッドでフォームを送信した後は、常に `GET` アクションへのリダイレクトが続くべきです。) + +`flashMessage` メソッドは、何らかの操作の結果をユーザーに通知するためのものです。リダイレクトしているため、メッセージを単純にテンプレートに渡してレンダリングすることはできません。そのため、このメソッドがあり、このメッセージを保存し、次のページ読み込み時に利用可能にします。フラッシュメッセージはメインテンプレート `app/Presentation/@layout.latte` でレンダリングされ、次のようになります。 + +```latte +<div n:foreach="$flashes as $flash" n:class="flash, $flash->type"> + {$flash->message} +</div> +``` + +すでに知っているように、フラッシュメッセージは自動的にテンプレートに渡されるため、あまり考える必要はありません。単に機能します。詳細については、[ドキュメントをご覧ください |application:presenters#フラッシュメッセージ]。 + + +コメントのレンダリング +=========== + +これは、あなたがきっと気に入るものの 1 つです。Nette Database には [Explorer |database:explorer] と呼ばれる素晴らしい機能があります。データベースのテーブルを意図的に InnoDB ストレージを使用して作成したことをまだ覚えていますか?Adminer は、[外部キー |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html] と呼ばれるものを作成しました。これにより、多くの作業が節約されます。 + +Nette Database Explorer は外部キーを使用してテーブル間の相互関係を解決し、これらの関係の知識から自動的にデータベースクエリを作成できます。 + +覚えているように、`PostPresenter::renderShow()` メソッドを使用して変数 `$post` をテンプレートに渡しました。そして今、カラム `post_id` の値が `$post->id` と一致するすべてのコメントを反復処理したいと考えています。これは `$post->related('comments')` を呼び出すことで実現できます。はい、これほど簡単です。結果のコードを見てみましょう。 + +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void +{ + ... + $this->template->post = $post; + $this->template->comments = $post->related('comments')->order('created_at'); +} +``` + +そしてテンプレート: + +```latte .{file:app/Presentation/Post/show.latte} +... +<h2>コメント</h2> + +<div class="comments"> + {foreach $comments as $comment} + <p><b><a href="mailto:{$comment->email}" n:tag-if="$comment->email"> + {$comment->name} + </a></b> が書きました:</p> + + <div>{$comment->content}</div> + {/foreach} +</div> +... +``` + +特別な属性 `n:tag-if` に注目してください。`n:属性` がどのように機能するかはすでに知っています。属性に接頭辞 `tag-` を付けると、機能はその内容ではなく HTML タグにのみ適用されます。これにより、コメンターがメールアドレスを提供した場合にのみ、その名前をリンクにすることができます。次の 2 行は同じです。 + +```latte +<strong n:tag-if="$important"> こんにちは! </strong> + +{if $important}<strong>{/if} こんにちは! {if $important}</strong>{/if} +``` + +{{priority: -1}} diff --git a/quickstart/ja/creating-posts.texy b/quickstart/ja/creating-posts.texy new file mode 100644 index 0000000000..b43320a01b --- /dev/null +++ b/quickstart/ja/creating-posts.texy @@ -0,0 +1,187 @@ +記事の作成と編集 +******** + +これで、新しいブログができました。人々はコメントで熱心に議論しており、私たちにはようやくさらにプログラミングする時間が少しできました。Adminer は素晴らしいツールですが、ブログに新しい記事を書くには完全に理想的ではありません。アプリケーションから直接新しい記事を追加するための簡単なフォームを作成するのに適切な時期のようです。始めましょう。 + +まず、ユーザーインターフェースのデザインから始めましょう。 + +1. トップページに「新しい記事を書く」リンクを追加します。 +2. このリンクをクリックすると、タイトルと記事の内容を入力するテキストエリアのあるフォームが表示されます。 +3. 保存ボタンをクリックすると、記事がデータベースに保存されます。 + +後で、ログイン機能も追加し、ログインしたユーザーのみが記事を追加できるようにします。しかし、それは後で。すべてが機能するように、今すぐ書く必要があるコードは何ですか? + +1. 記事を追加するためのフォームを持つ新しい Presenter を作成します。 +2. フォームが正常に送信された後に実行され、新しい記事をデータベースに保存するコールバックを定義します。 +3. そのフォームが表示される新しいテンプレートを作成します。 +4. メインページのテンプレートにフォームへのリンクを追加します。 + + +新しい Presenter +============= + +新しい Presenter を `EditPresenter` と名付け、`app/Presentation/Edit/` に保存します。また、データベースに接続する必要があるため、ここでもデータベース接続を要求するコンストラクタを記述します。 + +```php .{file:app/Presentation/Edit/EditPresenter.php} +<?php +namespace App\Presentation\Edit; + +use Nette; +use Nette\Application\UI\Form; + +final class EditPresenter extends Nette\Application\UI\Presenter +{ + public function __construct( + private Nette\Database\Explorer $database, + ) { + } +} +``` + + +記事保存フォーム +======== + +コメント作成時にフォームとコンポーネントについてはすでに説明しました。まだ不明な点がある場合は、[フォームとコンポーネントの作成 |comments#コメントフォーム] を確認してください。 + +次に、このメソッドを `EditPresenter` に追加します。 + +```php .{file:app/Presentation/Edit/EditPresenter.php} +protected function createComponentPostForm(): Form +{ + $form = new Form; + $form->addText('title', 'タイトル:') + ->setRequired(); + $form->addTextArea('content', '内容:') + ->setRequired(); + + $form->addSubmit('send', '保存して公開'); + $form->onSuccess[] = $this->postFormSucceeded(...); + + return $form; +} +``` + + +フォームから新しい記事を保存する +================ + +フォームからのデータを処理するメソッドを追加して続行します。 + +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void +{ + $post = $this->database + ->table('posts') + ->insert($data); + + $this->flashMessage("記事は正常に公開されました。", 'success'); + $this->redirect('Post:show', $post->id); +} +``` + +簡単な要約:このメソッドはフォームからデータを取得し、データベースに挿入(または更新)し、記事が正常に保存されたことをユーザーに通知するメッセージを作成し、新しい(または編集された)記事のページにリダイレクトして、どのように見えるかをすぐに確認できるようにします。 + + +新しい記事を作成するページ +============= + +次に、`Edit/create.latte` テンプレートを作成しましょう。 + +```latte .{file:app/Presentation/Edit/create.latte} +{block content} +<h1>新しい記事</h1> + +{control postForm} +``` + +すべてが明確になっているはずです。最後の行は、これから作成するフォームをレンダリングします。 + +対応する `renderCreate` メソッドを作成することもできますが、必須ではありません。データベースからデータを取得してテンプレートに渡す必要はないため、そのメソッドは空になります。このような場合、メソッドはまったく存在する必要はありません。 + + +記事作成へのリンク +========= + +おそらく、`EditPresenter` とその `create` アクションへのリンクを追加する方法はすでに知っているでしょう。試してみてください。 + +`app/Presentation/Home/default.latte` ファイルに以下を追加するだけです。 + +```latte +<a n:href="Edit:create">新しい記事を書く</a> +``` + + +記事の編集 +===== + +次に、記事を編集する機能も追加しましょう。非常に簡単です。すでに `postForm` フォームが完成しており、編集にも使用できます。 + +`EditPresenter` に新しいページ `edit` を追加します。 + +```php .{file:app/Presentation/Edit/EditPresenter.php} +public function renderEdit(int $id): void +{ + $post = $this->database + ->table('posts') + ->get($id); + + if (!$post) { + $this->error('記事が見つかりません'); + } + + $this->getComponent('postForm') + ->setDefaults($post->toArray()); +} +``` + +そして、別のテンプレート `Edit/edit.latte` を作成します。 + +```latte .{file:app/Presentation/Edit/edit.latte} +{block content} +<h1>記事を編集</h1> + +{control postForm} +``` + +そして、`postFormSucceeded` メソッドを修正します。これにより、新しい記事を追加する(現在行っているように)だけでなく、既存の記事を編集することもできるようになります。 + +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void +{ + $id = $this->getParameter('id'); + + if ($id) { + $post = $this->database + ->table('posts') + ->get($id); + $post->update($data); + + } else { + $post = $this->database + ->table('posts') + ->insert($data); + } + + $this->flashMessage('記事は正常に公開されました。', 'success'); + $this->redirect('Post:show', $post->id); +} +``` + +`id` パラメータが利用可能な場合、それは記事を編集することを意味します。その場合、要求された記事が実際に存在することを確認し、存在する場合はデータベースで更新します。`id` パラメータが利用できない場合、それは新しい記事を追加する必要があることを意味します。 + +しかし、その `id` パラメータはどこから来るのでしょうか?これは `renderEdit` メソッドに渡されたパラメータです。 + +これで、`app/Presentation/Post/show.latte` テンプレートに編集へのリンクを追加できます。 + +```latte +<a n:href="Edit:edit $post->id">記事を編集</a> +``` + + +まとめ +=== + +ブログは現在機能しており、訪問者は積極的にコメントしており、公開に Adminer を使用する必要はもうありません。アプリケーションは完全に独立しており、誰でも新しい記事を追加できます。しかし、誰でも(インターネットにアクセスできる人なら誰でも)新しい記事を追加できるというのは、おそらく望ましくありません。ログインしたユーザーのみが新しい記事を追加できるように、何らかのセキュリティが必要です。これについては次の章で見ていきます。 + +{{priority: -1}} diff --git a/quickstart/ja/home-page.texy b/quickstart/ja/home-page.texy new file mode 100644 index 0000000000..72664b81af --- /dev/null +++ b/quickstart/ja/home-page.texy @@ -0,0 +1,201 @@ +ブログのトップページ +********** + +.[perex] +次に、最新の記事を表示するトップページを作成しましょう。 + + +始める前に、少なくとも Model-View-Presenter 設計パターン(MVC((Model-View-Controller)) に似ています)の基本を知っておく必要があります。 + +- **Model** - データを扱う層。アプリケーションの残りの部分から完全に分離されています。Presenterとのみ通信します。 + +- **View** - フロントエンド層。テンプレートを使用して要求されたデータをレンダリングし、ユーザーに表示します。 + +- **Presenter**(または Controller) - 接続層。Presenter は Model と View を接続します。リクエストを処理し、Model にデータを問い合わせ、それを View に返します。 + + +私たちのブログのような単純なアプリケーションの場合、モデル層全体はデータベースへのクエリのみで構成されます - これには今のところ特別なコードは必要ありません。したがって、最初は Presenter とテンプレートのみを作成します。Nette では、各 Presenter には独自のテンプレートがあるため、同時に作成します。 + + +Adminer を使用したデータベースの作成 +====================== + +データの保存には MySQL データベースを使用します。これは、Web アプリケーション開発者の間で最も普及しているためです。ただし、使用したくない場合は、自由に選択したデータベースを選択してください。 + +次に、ブログの記事を保存するデータベース構造を準備します。非常に単純なものから始めます - 記事用のテーブルを 1 つだけ作成します。 + +データベースを作成するには、[Adminer |https://www.adminer.org] または他の好きなデータベース管理ツールをダウンロードできます。 + + +Adminer を開き、`quickstart` という名前の新しいデータベースを作成します。 + +`posts` という名前の新しいテーブルを次のカラムで作成します。 +- `id` int、オートインクリメント (AI) にチェック +- `title` varchar、長さ 255 +- `content` text +- `created_at` timestamp + +結果の構造は次のようになります。 + +[* adminer-posts.webp *] + +```sql +CREATE TABLE `posts` ( + `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, + `title` varchar(255) NOT NULL, + `content` text NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB CHARSET=utf8; +``` + +.[caution] +**InnoDB** ストレージを使用することは本当に重要です。その理由はすぐにわかります。今のところ、単にそれを選択して保存をクリックしてください。 + +アプリケーションを使用してデータベースに記事を追加する機能を作成する前に、ブログにいくつかのサンプル記事を手動で追加してください。 + +```sql +INSERT INTO `posts` (`id`, `title`, `content`, `created_at`) VALUES +(1, 'Article One', 'Lorem ipusm dolor one', CURRENT_TIMESTAMP), +(2, 'Article Two', 'Lorem ipsum dolor two', CURRENT_TIMESTAMP), +(3, 'Article Three', 'Lorem ipsum dolor three', CURRENT_TIMESTAMP); +``` + + +データベースへの接続 +========== + +データベースが作成され、いくつかの記事が保存されたので、それらを美しい新しいページに表示するのに適切な時期です。 + +まず、アプリケーションに使用するデータベースを伝える必要があります。データベースへの接続は、DSN((Data Source Name)) とログイン資格情報を使用して `config/common.neon` ファイルで設定します。次のようになります。 + +```neon .{file:config/common.neon} +database: + dsn: 'mysql:host=127.0.0.1;dbname=quickstart' + user: *ここにユーザー名を入力* + password: *ここにデータベースのパスワードを入力* +``` + +.[note] +このファイルを編集するときは、行のインデントに注意してください。[NEON |neon:format] 形式は、スペースによるインデントとタブによるインデントの両方を受け入れますが、両方を同時に使用することはできません。Web Project のデフォルト設定ファイルはタブを使用します。 + + +データベース接続の受け渡し +============= + +記事のリスト表示を担当する `HomePresenter` は、データベースへの接続が必要です。それを取得するために、次のようなコンストラクタを使用します。 + +```php .{file:app/Presentation/Home/HomePresenter.php} +<?php +namespace App\Presentation\Home; + +use Nette; + +final class HomePresenter extends Nette\Application\UI\Presenter +{ + public function __construct( + private Nette\Database\Explorer $database, + ) { + } + + // ... +} +``` + + +データベースからの記事の読み込み +================ + +次に、データベースから記事を読み込み、テンプレートに送信します。テンプレートはその後、HTML コードとしてレンダリングします。これには、いわゆる *render* メソッドが使用されます。 + +```php .{file:app/Presentation/Home/HomePresenter.php} +public function renderDefault(): void +{ + $this->template->posts = $this->database + ->table('posts') + ->order('created_at DESC') + ->limit(5); +} +``` + +Presenter には、データベースからテンプレート(View)にデータを渡す 1 つのレンダリングメソッド `renderDefault()` が含まれるようになりました。テンプレートは `app/Presentation/{PresenterName}/{viewName}.latte` に配置されているため、この場合、テンプレートは `app/Presentation/Home/default.latte` に配置されます。テンプレートでは、データベースから取得した記事を含む変数 `$posts` が利用可能になります。 + + +テンプレート +====== + +Web サイト全体には、メインテンプレート(*layout* と呼ばれ、ヘッダー、スタイル、フッターなどを含む)と、各ビュー(View)用の特定のテンプレート(例:ブログ記事の表示用)があり、これらはメインテンプレートの一部を上書きできます。 + +デフォルトでは、レイアウトテンプレートは `app/Presentation/@layout.latte` に配置され、次の内容が含まれます。 + +```latte .{file:app/Presentation/@layout.latte} +... +{include content} +... +``` + +`{include content}` という記述は、`content` という名前のブロックをメインテンプレートに挿入します。これは、個々のビュー(View)のテンプレートで定義します。私たちの場合、`Home/default.latte` ファイルを次のように変更します。 + +```latte .{file:app/Presentation/Home/default.latte} +{block content} + Hello World +{/block} +``` + +これにより、メインレイアウトに挿入される *content* [ブロック |latte:tags#block] を定義しました。ブラウザを再度更新すると、「Hello World」というテキストが表示されたページが表示されます(ソースコードには `@layout.latte` で定義された HTML ヘッダーとフッターも含まれます)。 + +ブログ記事を表示しましょう - テンプレートを次のように変更します。 + +```latte .{file:app/Presentation/Home/default.latte} +{block content} + <h1>私のブログ</h1> + + {foreach $posts as $post} + <div class="post"> + <div class="date">{$post->created_at|date:'F j, Y'}</div> + + <h2>{$post->title}</h2> + + <div>{$post->content|truncate:256}</div> + </div> + {/foreach} +{/block} +``` + +ブラウザを更新すると、すべての記事のリストが表示されます。リストはまだあまり見栄えが良くなく、色もありません。そのため、`www/css/style.css` ファイルにいくつかの [CSS スタイル |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] を追加し、レイアウトでリンクすることができます。 + +```latte .{file:app/Presentation/@layout.latte} + ... + <link rel="stylesheet" href="{$basePath}/css/style.css"> +</head> +... +``` + +`{foreach}` タグは、変数 `$posts` でテンプレートに渡したすべての記事を反復処理し、それぞれに対して指定された HTML の一部をレンダリングします。これは PHP コードとまったく同じように動作します。 + +`|date:` という記述をフィルタと呼びます。フィルタは出力のフォーマットに使用されます。この特定のフィルタは、日付(例:`2013-04-12`)をより読みやすい形式(`April 12, 2013`)に変換します。`|truncate` フィルタは、文字列を指定された最大長に切り詰め、文字列が短縮された場合は末尾に 3 つのドットを追加します。これはプレビューであるため、記事の全文を表示する意味はありません。その他のデフォルトフィルタは [ドキュメント |latte:filters] にあり、必要に応じて独自のフィルタを作成することもできます。 + +もう 1 つ。前のコードを短縮して簡略化できます。これは、*Latte タグ* を *n:属性* に置き換えることで実現できます。 + +```latte .{file:app/Presentation/Home/default.latte} +{block content} + <h1>私のブログ</h1> + + <div n:foreach="$posts as $post" class="post"> + <div class="date">{$post->created_at|date:'F j, Y'}</div> + + <h2>{$post->title}</h2> + + <div>{$post->content|truncate:256}</div> + </div> +{/block} +``` + +`n:foreach` 属性は、*div* ブロックを *foreach* でラップします(前のコードとまったく同じように機能します)。 + + +まとめ +=== + +これで、いくつかの記事を含む非常に単純な MySQL データベースができました。アプリケーションはこのデータベースに接続し、これらの記事の単純なリストをテンプレートに出力します。 + +{{priority: -1}} diff --git a/quickstart/ja/model.texy b/quickstart/ja/model.texy new file mode 100644 index 0000000000..1643fd4d26 --- /dev/null +++ b/quickstart/ja/model.texy @@ -0,0 +1,84 @@ +モデル +*** + +アプリケーションが成長するにつれて、異なる場所、異なる Presenter で、データベースに対して同様の操作を実行する必要があることにすぐに気づくでしょう。たとえば、最新の公開記事を取得するなどです。記事に下書きかどうかを示すフラグを追加するなどしてアプリケーションを改善する場合、データベースから記事を取得するアプリケーション内のすべての場所を調べて、下書きでない記事のみを選択するように where 条件を追加する必要があります。 + +その時点で、データベースとの直接的なやり取りは不十分になり、公開された記事を返す新しい関数で支援する方が賢明です。そして、後で別の条件、たとえば将来の日付の記事を表示しないように追加する場合、コードを 1 か所だけ変更します。 + +関数を `PostFacade` クラスに配置し、`getPublicArticles()` と名付けます。 + +`app/Model/` ディレクトリに、記事を処理するモデルクラス `PostFacade` を作成します。 + +```php .{file:app/Model/PostFacade.php} +<?php +namespace App\Model; + +use Nette; + +final class PostFacade +{ + public function __construct( + private Nette\Database\Explorer $database, + ) { + } + + public function getPublicArticles() + { + return $this->database + ->table('posts') + ->where('created_at < ', new \DateTime) + ->order('created_at DESC'); + } +} +``` + +クラスでは、コンストラクタを使用してデータベース Explorer:[api:Nette\Database\Explorer] を渡してもらいます。[DI コンテナ|dependency-injection:passing-dependencies] の力を活用します。 + +`HomePresenter` に切り替えて、`Nette\Database\Explorer` への依存関係を取り除き、新しいクラスへの新しい依存関係に置き換えるように変更します。 + +```php .{file:app/Presentation/Home/HomePresenter.php} +<?php +namespace App\Presentation\Home; + +use App\Model\PostFacade; +use Nette; + +final class HomePresenter extends Nette\Application\UI\Presenter +{ + public function __construct( + private PostFacade $facade, + ) { + } + + public function renderDefault(): void + { + $this->template->posts = $this->facade + ->getPublicArticles() + ->limit(5); + } +} +``` + +use セクションには `App\Model\PostFacade` があるため、PHP コードの記述を `PostFacade` に短縮できます。コンストラクタでこのオブジェクトを要求し、`$facade` プロパティに書き込み、renderDefault メソッドで使用します。 + +残りの最後のステップは、DI コンテナにこのオブジェクトを作成する方法を教えることです。これは通常、`config/services.neon` ファイルの `services` セクションにインデントを追加し、クラスの完全な名前とコンストラクタのパラメータを指定することによって行われます。 これにより、いわゆる登録が行われ、オブジェクトは **サービス** と呼ばれます。[autowiring |dependency-injection:autowiring] という魔法のおかげで、DI がコンストラクタのパラメータを自動的に認識して渡すため、ほとんどの場合、パラメータを指定する必要はありません。したがって、クラス名だけを指定するだけで十分です。 + +```neon .{file:config/services.neon} +... + +services: + - App\Model\PostFacade +``` + +ただし、この行を追加する必要さえありません。`services.neon` の冒頭にある `search` セクションで、`-Facade` または `-Factory` で終わるすべてのクラスは DI によって自動的に検索されるように定義されています。これは `PostFacade` の場合も同様です。 + + +まとめ +=== + +`PostFacade` クラスはコンストラクタで `Nette\Database\Explorer` の受け渡しを要求し、このクラスは DI コンテナに登録されているため、コンテナはこのインスタンスを作成して渡します。DI はこのようにして `PostFacade` のインスタンスを作成し、それを要求した HomePresenter クラスのコンストラクタに渡します。まるでマトリョーシカのようです。:) 誰もが欲しいものを言うだけで、何がどこでどのように作成されるかについては気にしません。作成は DI コンテナが担当します。 + +.[note] +ここでは、[dependency injection |dependency-injection:introduction] と [設定 |nette:configuring] について詳しく読むことができます。 + +{{priority: -1}} diff --git a/quickstart/ja/single-post.texy b/quickstart/ja/single-post.texy new file mode 100644 index 0000000000..af26eba943 --- /dev/null +++ b/quickstart/ja/single-post.texy @@ -0,0 +1,124 @@ +記事ページ +***** + +.[perex] +次に、特定の 1 つの記事を表示する別のブログページを作成します。 + + +特定の 1 つの記事を取得し、それをテンプレートに渡す新しい render メソッドを作成する必要があります。このメソッドを `HomePresenter` に置くのは、記事について話しているのであって、トップページについてではないため、あまりきれいではありません。そこで、`app/Presentation/Post/` に `PostPresenter` を作成しましょう。この Presenter もデータベースに接続する必要があるため、ここでもデータベース接続を要求するコンストラクタを記述します。 + +したがって、`PostPresenter` は次のようになります。 + +```php .{file:app/Presentation/Post/PostPresenter.php} +<?php +namespace App\Presentation\Post; + +use Nette; +use Nette\Application\UI\Form; + +final class PostPresenter extends Nette\Application\UI\Presenter +{ + public function __construct( + private Nette\Database\Explorer $database, + ) { + } + + public function renderShow(int $id): void + { + $this->template->post = $this->database + ->table('posts') + ->get($id); + } +} +``` + +[Presenter のマッピング |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7] の設定に従う正しい名前空間 `App\Presentation\Post` を指定することを忘れないでください。 + +`renderShow` メソッドは 1 つの引数、つまり表示する特定の記事の ID を必要とします。次に、この ID を使用してデータベースから記事を読み込み、テンプレートに渡します。 + +`Home/default.latte` テンプレートに `Post:show` アクションへのリンクを挿入します。 + +```latte .{file:app/Presentation/Home/default.latte} +... +<h2><a href="{link Post:show $post->id}">{$post->title}</a></h2> +... +``` + +`{link}` タグは、`Post:show` アクションを指す URL アドレスを生成します。また、記事の ID を引数として渡します。 + + +同じことを n:属性を使用して短縮して記述できます。 + +```latte .{file:app/Presentation/Home/default.latte} +... +<h2><a n:href="Post:show $post->id">{$post->title}</a></h2> +... +``` + +`n:href` 属性は `{link}` タグに似ています。 + + + +ただし、`Post:show` アクションのテンプレートはまだ存在しません。この投稿へのリンクを開いてみることができます。[Tracy |tracy:] は、`Post/show.latte` テンプレートがまだ存在しないため、エラーを表示します。別のエラーメッセージが表示される場合は、おそらく Web サーバーで `mod_rewrite` を有効にする必要があります。 + +そこで、次の内容で `Post/show.latte` テンプレートを作成します。 + +```latte .{file:app/Presentation/Post/show.latte} +{block content} + +<p><a n:href="Home:default">← 記事一覧に戻る</a></p> + +<div class="date">{$post->created_at|date:'F j, Y'}</div> + +<h1 n:block="title">{$post->title}</h1> + +<div class="post">{$post->content}</div> +``` + +次に、テンプレートの各部分を見ていきましょう。 + +最初の行は、トップページと同様に "content" という名前のブロック定義を開始します。このブロックは再びメインテンプレートに表示されます。ご覧のとおり、終了タグ `{/block}` がありません。これはオプションだからです。 + +次の行には、ブログ記事のリストに戻るリンクがあり、ユーザーは記事リストと特定の記事の間を簡単に移動できます。`n:href` 属性を使用しているため、Nette が自動的にリンクの生成を処理します。リンクは `Home` Presenter の `default` アクションを指します(`default` という名前のアクションは省略でき、自動的に補完されるため、`n:href="Home:"` と書くこともできます)。 + +3 行目は、すでにおなじみのフィルタを使用して日付の出力をフォーマットします。 + +4 行目は、HTML タグ `<h1>` でブログの *タイトル* を表示します。このタグには、おそらく知らない属性 (`n:block="title"`) が含まれています。何をするか推測できますか?前の部分を注意深く読んだ場合、これが `n:属性` であることはすでに知っています。これは、次と同等の別の例です。 + +```latte +{block title}<h1>{$post->title}</h1>{/block} +``` + +簡単に言えば、このブロックは `title` という名前のブロックを再定義します。このブロックはすでにメイン *layout* テンプレート (`/app/Presentation/@layout.latte:11`) で定義されており、OOP でのメソッドのオーバーライドと同様に、メインテンプレートのこのブロックは完全にオーバーライドされます。したがって、ページの `<title>` には表示されている記事のタイトルが含まれるようになり、これには 1 つの単純な属性 `n:block="title"` を使用するだけで十分でした。素晴らしいですね。 + +テンプレートの 5 行目と最後の行は、特定の 1 つの記事の全文を表示します。 + + +記事 ID の確認 +========= + +誰かが URL の ID を変更して、存在しない `id` を入力したらどうなるでしょうか?ユーザーに「ページが見つかりません」という種類のわかりやすいエラーを提供する必要があります。そこで、`PostPresenter` の render メソッドを少し変更します。 + +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void +{ + $post = $this->database + ->table('posts') + ->get($id); + if (!$post) { + $this->error('ページが見つかりません'); + } + + $this->template->post = $post; +} +``` + +記事が見つからない場合、`$this->error(...)` を呼び出すことで、わかりやすいメッセージとともに 404 エラーページが表示されます。開発モード(localhost)では、このエラーページは表示されないことに注意してください。代わりに、例外の詳細を示す Tracy が表示されます。これは開発には非常に便利です。両方のモードを表示したい場合は、`Bootstrap.php` ファイルの `setDebugMode` メソッドの引数を変更するだけです。 + + +まとめ +=== + +記事を含むデータベースと、2 つのビューを持つ Web アプリケーションがあります。1 つ目はすべての記事の概要を表示し、2 つ目は特定の 1 つの記事を表示します。 + +{{priority: -1}} diff --git a/quickstart/pl/@home.texy b/quickstart/pl/@home.texy index 63f85508e4..11dda3405f 100644 --- a/quickstart/pl/@home.texy +++ b/quickstart/pl/@home.texy @@ -1,19 +1,19 @@ -Piszemy pierwszą aplikację! -*************************** +Pisanie pierwszej aplikacji! +**************************** .[perex] -Poznajmy wspólnie Nette Framework, tworząc jednocześnie prosty blog z komentarzami. Zróbmy to! +Poznajmy razem Nette Framework, tworząc prosty blog z komentarzami. Zaczynajmy! -Po pierwszych dwóch rozdziałach będziesz miał swój własny działający blog i będziesz gotowy do publikowania swoich niesamowitych postów, chociaż funkcje będą dość mocno ograniczone po ukończeniu tych dwóch rozdziałów. Aby uczynić rzeczy milszymi dla Twoich użytkowników, powinieneś również przeczytać kolejne rozdziały i wciąż ulepszać swoją aplikację. +Już po pierwszych dwóch rozdziałach będziemy mieli swój własny działający blog i będziemy mogli publikować swoje wspaniałe posty, chociaż funkcje będą na razie w znacznym stopniu ograniczone. Powinieneś przeczytać również następne rozdziały, gdzie zaprogramujemy dodawanie komentarzy, edytowanie artykułów i na koniec zabezpieczymy blog. .[tip] -Ten poradnik zakłada, że użytkownik ukończył dokument [Instalacja |nette:installation] i pomyślnie skonfigurował swoje oprzyrządowanie. +Ten poradnik zakłada, że przeczytałeś stronę [Instalacja |nette:installation] i pomyślnie przygotowałeś potrzebne narzędzia. Zakłada również, że rozumiesz [programowanie obiektowe w PHP |nette:introduction-to-object-oriented-programming]. -Proszę używać PHP 8.0 lub nowszego. Kompletną aplikację można znaleźć [na GitHubie |https://github.com/nette-examples/quickstart/tree/v4.0]. +Używaj PHP 8.1 lub nowszej wersji. Kompletną aplikację znajdziesz [na GitHubie |https://github.com/nette-examples/quickstart/tree/v4.0]. -Strona powitalna .[#toc-the-welcome-page] -========================================= +Strona powitalna +================ Zacznijmy od utworzenia nowego projektu w katalogu `nette-blog`: @@ -21,93 +21,94 @@ Zacznijmy od utworzenia nowego projektu w katalogu `nette-blog`: composer create-project nette/web-project nette-blog ``` -W tej chwili powinna być uruchomiona strona powitalna projektu internetowego. Wypróbuj ją, otwierając przeglądarkę i przechodząc do następującego adresu URL: +W tym momencie powinna już działać strona startowa Web Projectu. Wypróbujemy to, otwierając przeglądarkę pod następującym adresem URL: ``` http://localhost/nette-blog/www/ ``` -i zobaczyć stronę główną Nette Framework: +i zobaczymy stronę startową Nette Frameworku: [* qs-welcome.webp .{url: http://localhost/nette-blog/www/} *] -Aplikacja działa i można rozpocząć edycję. +Aplikacja działa i możesz zacząć wprowadzać zmiany. .[note] -Jeśli jest problem, [spróbuj tych kilku wskazówek |nette:troubleshooting#Nette-Is-Not-Working-White-Page-Is-Displayed]. +Jeśli wystąpił problem, wypróbuj [te wskazówki |nette:troubleshooting#Nette nie działa wyświetla się biała strona]. -Zawartość projektu internetowego .[#toc-web-project-s-content] -============================================================== +Zawartość Web Project +===================== -Projekt internetowy ma następującą strukturę: +Web Project ma następującą strukturę: /--pre <b>nette-blog/</b> -├── <b>app/</b> ← adresář s aplikací -│ ├── <b>Presenters/</b> ← třídy presenterů -│ │ └── <b>templates/</b>← šablony -│ ├── <b>Router/</b> ← konfigurace URL adres -│ └── <b>Bootstrap.php</b> ← zaváděcí třída Bootstrap -├── <b>bin/</b> ← skripty spouštěné z příkazové řádky -├── <b>config/</b> ← konfigurační soubory -├── <b>log/</b> ← logování chyb -├── <b>temp/</b> ← dočasné soubory, cache, … -├── <b>vendor/</b> ← knihovny instalované Composerem -│ └── <b>autoload.php</b> ← autoloading všech nainstalovaných balíčků -└── <b>www/</b> ← veřejný adresář - jediný přístupný z prohlížeče - └── <b>index.php</b> ← prvotní soubor, kterým se aplikace spouští +├── <b>app/</b> ← katalog aplikacji +│ ├── <b>Core/</b> ← podstawowe klasy niezbędne do działania +│ ├── <b>Presentation/</b> ← presentery, szablony itp. +│ │ └── <b>Home/</b> ← katalog presentera Home +│ └── <b>Bootstrap.php</b> ← klasa startowa Bootstrap +├── <b>assets/</b> ← surowe zasoby (SCSS, TypeScript, obrazy źródłowe) +├── <b>bin/</b> ← skrypty uruchamiane z linii poleceń +├── <b>config/</b> ← pliki konfiguracyjne +├── <b>log/</b> ← logowanie błędów +├── <b>temp/</b> ← pliki tymczasowe, cache, … +├── <b>vendor/</b> ← biblioteki zainstalowane przez Composer +│ └── <b>autoload.php</b> ← autoloading wszystkich zainstalowanych pakietów +└── <b>www/</b> ← katalog publiczny - jedyny dostępny z przeglądarki + ├── <b>assets/</b> ← skompilowane pliki statyczne (CSS, JS, obrazy, ...) + └── <b>index.php</b> ← plik początkowy, który uruchamia aplikację \-- -Katalog `www/` służy do przechowywania obrazów, plików JavaScript, arkuszy stylów CSS i innych publicznie dostępnych plików. Tylko ten katalog jest dostępny z Internetu, więc ustaw katalog główny swojej aplikacji, aby wskazywał właśnie na niego (możesz to ustawić w konfiguracji Apache'a lub nginxa, ale zróbmy to później, teraz nie jest to ważne). +Katalog `www/` jest przeznaczony do przechowywania obrazów, plików JavaScript, stylów CSS i innych publicznie dostępnych plików. Tylko ten katalog jest dostępny z internetu, więc ustaw katalog główny swojej aplikacji tak, aby wskazywał właśnie tutaj (można to ustawić w konfiguracji Apache lub nginx, ale zróbmy to później, teraz to nie jest ważne). -Najważniejszym dla nas folderem jest `app/`. Znajdziemy tu plik `Bootstrap.php`, który zawiera klasę służącą do załadowania całego frameworka i skonfigurowania aplikacji. Tutaj włącza się [autoloading |robot-loader:], ustawia się [debugger |tracy:] i [trasy |application:routing]. +Najważniejszy folder dla nas to `app/`. Tutaj znajdziemy plik `Bootstrap.php`, w którym znajduje się klasa służąca do załadowania całego frameworka i ustawienia aplikacji. Aktywuje się tutaj [autoloading |robot-loader:], ustawia się tutaj [debugger |tracy:] i [routing |application:routing]. -Czyszczenie .[#toc-cleanup] -=========================== +Sprzątanie +========== -Web Project zawiera splash page, który usuwamy zanim zaczniemy cokolwiek programować. Nie krępuj się więc zastąpić zawartości strony `app/Presenters/templates/Home/default.latte` słowami "Hello world!". +Web Project zawiera stronę startową, którą usuniemy, zanim zaczniemy coś programować. Bez obaw zastąpimy więc zawartość pliku `app/Presentation/Home/default.latte` tekstem "Witaj świecie!". [* qs-hello.webp .{url:-} *] -Tracy (debugger) .[#toc-tracy-debugger] -======================================= +Tracy (debugger) +================ -Niezwykle ważnym narzędziem programistycznym jest [debugger Tracy |tracy:]. Spróbuj wywołać błąd w `app/Presenters/HomePresenter.php` (np. Usuwając nawias w definicji klasy HomePresenter) i zobacz, co się stanie. Pojawi się strona z powiadomieniem, która wyraźnie opisuje błąd. +Niezwykle ważnym narzędziem do rozwoju jest [narzędzie do debugowania Tracy |tracy:]. Spróbuj wywołać jakiś błąd w pliku `app/Presentation/Home/HomePresenter.php` (np. usuwając nawias klamrowy w definicji klasy HomePresenter) i zobacz, co się stanie. Pojawi się strona z powiadomieniem, która w zrozumiały sposób opisuje błąd. -[* qs-tracy.webp .{url:-}(debugger screen) *] +[* qs-tracy.avif .{url:-}(ekran debuggera) *] -Tracy pomoże nam ogromnie, gdy będziemy szukać błędów w aplikacji. Zwróć też uwagę na pływający w prawym dolnym rogu ekranu pasek Tracy Bar, który zawiera informacje z uruchomionej aplikacji. +Tracy ogromnie nam pomoże, gdy będziemy szukać błędów w aplikacji. Zwróć również uwagę na pływający pasek Tracy Baru w prawym dolnym rogu ekranu, który zawiera informacje z działania aplikacji. [* qs-tracybar.webp .{url:-} *] -Oczywiście w trybie produkcyjnym Tracy jest wyłączona i nie wyświetla żadnych wrażliwych informacji. W tym przypadku wszystkie błędy są przechowywane w folderze `log/` Spróbujmy. W pliku `app/Bootstrap.php` odkomentuj następującą linię i zmień parametr wywołania na `false`, tak aby kod wyglądał jak poniżej: +W trybie produkcyjnym Tracy jest oczywiście wyłączona i nie wyświetla żadnych wrażliwych informacji. Wszystkie błędy są w tym przypadku zapisywane w folderze `log/`. Spróbujmy to zrobić. W pliku `app/Bootstrap.php` odkomentujemy następujący wiersz i zmienimy parametr wywołania na `false`, aby kod wyglądał tak: ```php .{file:app/Bootstrap.php} ... -$configurator->setDebugMode(false); -$configurator->enableTracy($appDir . '/log'); +$this->configurator->setDebugMode(false); ... ``` -Po odświeżeniu strony nie zobaczymy już Tracy. Zamiast tego pojawi się przyjazny dla użytkownika komunikat: +Po odświeżeniu strony już nie zobaczymy Tracy. Zamiast niej wyświetli się przyjazna dla użytkownika wiadomość: -[* qs-fatal.webp .{url:-}(error screen) *] +[* qs-fatal.webp .{url:-}(ekran błędu) *] -Zajrzyjmy teraz do katalogu `log/`. Tutaj (w pliku `exception.log`) znajdziemy zalogowany błąd, a także znaną nam stronę z komunikatem o błędzie (zapisaną jako plik HTML o nazwie zaczynającej się od `exception-`). +Teraz spójrzmy do katalogu `log/`. Tutaj (w pliku `exception.log`) znajdziemy zalogowany błąd, a także już znaną stronę z komunikatem o błędzie (zapisaną jako plik HTML o nazwie zaczynającej się od `exception-`). -Ponownie skomentuj linię `// $configurator->setDebugMode(false);`. Tracy automatycznie włączy tryb deweloperski na localhost i wyłączy go wszędzie indziej. +Zakomentujemy ponownie wiersz `// $configurator->setDebugMode(false);`. Tracy automatycznie włączy tryb deweloperski na localhost i wyłączy go wszędzie indziej. -Możemy naprawić stworzony przez nas błąd i kontynuować pisanie aplikacji. +Błąd, który stworzyliśmy, możemy naprawić i kontynuować pisanie aplikacji. -Wyślij podziękowania .[#toc-send-thanks] -======================================== +Wyślij podziękowania +==================== -Pokażemy Ci sztuczkę, która pozwoli Ci zadowolić autorów open source. Prosty sposób na rozgwiazdkowanie bibliotek, z których korzysta twój projekt na GitHubie. Po prostu zacznij: +Pokażemy Ci sztuczkę, którą ucieszysz autorów open source. W prosty sposób dasz na GitHubie gwiazdkę bibliotekom, których używa Twój projekt. Wystarczy uruchomić: ```shell composer thanks @@ -116,4 +117,3 @@ composer thanks Spróbuj! {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/pl/@left-menu.texy b/quickstart/pl/@left-menu.texy index 522089ad19..a050b16fc8 100644 --- a/quickstart/pl/@left-menu.texy +++ b/quickstart/pl/@left-menu.texy @@ -1,9 +1,9 @@ Tutorial ******** -- [Stwórz swoją pierwszą aplikację! |@home] +- [Pisanie pierwszej aplikacji! |@home] - [Strona główna bloga |home-page] -- [Strona po stronie |single-post] -- [Uwagi |comments] +- [Strona z postem |single-post] +- [Komentarze |comments] - [Tworzenie i edycja postów |creating-posts] - [Model |Model] - [Uwierzytelnianie |authentication] diff --git a/quickstart/pl/@meta.texy b/quickstart/pl/@meta.texy new file mode 100644 index 0000000000..e9a0da2b04 --- /dev/null +++ b/quickstart/pl/@meta.texy @@ -0,0 +1 @@ +{{sitename: Pisanie pierwszej aplikacji!}} diff --git a/quickstart/pl/authentication.texy b/quickstart/pl/authentication.texy index 01f8aab935..3dc83f791b 100644 --- a/quickstart/pl/authentication.texy +++ b/quickstart/pl/authentication.texy @@ -1,36 +1,36 @@ Uwierzytelnianie **************** -Nette zapewnia sposób na zaprogramowanie uwierzytelniania na naszej stronie, ale nie zmusza nas do niczego. Realizacja zależy od nas samych. Nette zawiera interfejs `Nette\Security\Authenticator`, który wymaga tylko jednej metody `authenticate`, która uwierzytelnia użytkownika jakkolwiek chcemy. +Nette dostarcza sposób na zaprogramowanie uwierzytelniania na naszych stronach, ale do niczego nas nie zmusza. Implementacja zależy wyłącznie od nas. Nette zawiera interfejs `Nette\Security\Authenticator`, który wymaga tylko jednej metody `authenticate`, która weryfikuje użytkownika w dowolny sposób, jaki zechcemy. -Istnieje wiele sposobów uwierzytelnienia użytkownika. Najczęstszą metodą uwierzytelniania jest użycie hasła (użytkownik podaje swoje imię i nazwisko lub e-mail i hasło), ale istnieją też inne sposoby. Być może znasz przyciski typu "Zaloguj się za pomocą Facebooka", lub zaloguj się za pomocą Google/Twitter/GitHub na niektórych stronach. W przypadku Nette możemy mieć dowolną metodę logowania lub możemy je łatwo połączyć. To zależy od nas. +Istnieje wiele możliwości, jak użytkownik może zostać uwierzytelniony. Najczęstszym sposobem uwierzytelniania jest użycie hasła (użytkownik podaje swoją nazwę lub e-mail i hasło), ale są też inne sposoby. Być może znasz przyciski typu "Zaloguj przez Facebooka" lub logowanie za pomocą Google/Twitter/GitHub na niektórych stronach. Z Nette możemy mieć dowolną metodę logowania, lub możemy je nawet łączyć. To zależy tylko od nas. -Normalnie napisalibyśmy własny authenticator, ale dla tego prostego małego bloga użyjemy wbudowanego authenticatora, który loguje się na podstawie hasła i nazwy użytkownika zapisanych w pliku konfiguracyjnym. Jest to dobre rozwiązanie do celów testowych. Dodajemy więc do pliku konfiguracyjnego następującą sekcję *security* `config/common.neon`: +Zwykle napisalibyśmy własny authenticator, ale dla tego prostego małego bloga użyjemy wbudowanego authenticatora, który loguje na podstawie hasła i nazwy użytkownika zapisanych w pliku konfiguracyjnym. Przydaje się do celów testowych. Dodajmy więc następującą sekcję `security` do pliku konfiguracyjnego `config/common.neon`: ```neon .{file:config/common.neon} security: users: - admin: secret # użytkownik 'admin', hasło 'secret' + admin: secret # użytkownik 'admin', hasło 'secret' ``` Nette automatycznie utworzy usługę w kontenerze DI. -Formularz logowania .[#toc-sign-in-form] -======================================== +Formularz logowania +=================== -Teraz mamy już gotowe uwierzytelnienie i musimy przygotować interfejs użytkownika do logowania. Stwórzmy więc nowy prezenter o nazwie *SignPresenter*, który: +Teraz mamy przygotowane uwierzytelnianie i musimy przygotować interfejs użytkownika do logowania. Utwórzmy więc nowy presenter o nazwie `SignPresenter`, który: -- wyświetla formularz logowania (z nazwą i hasłem) -- uwierzytelnia użytkownika po wysłaniu formularza -- zapewnia opcję wylogowania +- wyświetli formularz logowania (z nazwą użytkownika i hasłem) +- po wysłaniu formularza zweryfikuje użytkownika +- zapewni możliwość wylogowania -Zacznijmy od formularza logowania. Wiemy już jak działają formularze w presenterech. Stwórzmy więc prezenter `SignPresenter` i napiszmy metodę `createComponentSignInForm`. Powinna ona wyglądać coś takiego: +Zaczniemy od formularza logowania. Już wiemy, jak działają formularze w prezenterach. Utworzymy więc presenter `SignPresenter` i napiszemy metodę `createComponentSignInForm`. Powinien wyglądać mniej więcej tak: -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Sign; use Nette; use Nette\Application\UI\Form; @@ -40,69 +40,69 @@ final class SignPresenter extends Nette\Application\UI\Presenter protected function createComponentSignInForm(): Form { $form = new Form; - $form->addText('username', 'Uživatelské jméno:') - ->setRequired('Prosím vyplňte své uživatelské jméno.'); + $form->addText('username', 'Nazwa użytkownika:') + ->setRequired('Proszę podać swoją nazwę użytkownika.'); - $form->addPassword('password', 'Heslo:') - ->setRequired('Prosím vyplňte své heslo.'); + $form->addPassword('password', 'Hasło:') + ->setRequired('Proszę podać swoje hasło.'); - $form->addSubmit('send', 'Přihlásit'); + $form->addSubmit('send', 'Zaloguj'); - $form->onSuccess[] = [$this, 'signInFormSucceeded']; + $form->onSuccess[] = $this->signInFormSucceeded(...); return $form; } } ``` -Znajdują się tam pola na nazwę użytkownika i hasło. +Są tu pola na nazwę użytkownika i hasło. -Szablon .[#toc-template] ------------------------- +Szablon +------- -Formularz zostanie wyrenderowany w szablonie `in.latte`: +Formularz będzie renderowany w szablonie `in.latte`: -```latte .{file:app/Presenters/templates/Sign/in.latte} +```latte .{file:app/Presentation/Sign/in.latte} {block content} -<h1 n:block=title>Přihlášení</h1> +<h1 n:block=title>Logowanie</h1> {control signInForm} ``` -Wywołanie zwrotne logowania .[#toc-login-handler] -------------------------------------------------- +Callback logowania +------------------ -Następnie dodamy callback dla logowania użytkownika, który zostanie wywołany zaraz po pomyślnym przesłaniu formularza. +Następnie dodamy callback do logowania użytkownika, który będzie wywoływany zaraz po pomyślnym wysłaniu formularza. -Callback po prostu bierze nazwę użytkownika i hasło, które użytkownik wypełnił i przekazuje je do authenticatora. Po zalogowaniu przekierowujemy na stronę główną. +Callback jedynie pobierze nazwę użytkownika i hasło, które użytkownik wypełnił, i przekaże je do authenticatora. Po zalogowaniu przekierujemy na stronę główną. -```php .{file:app/Presenters/SignPresenter.php} -public function signInFormSucceeded(Form $form, \stdClass $data): void +```php .{file:app/Presentation/Sign/SignPresenter.php} +private function signInFormSucceeded(Form $form, \stdClass $data): void { try { $this->getUser()->login($data->username, $data->password); $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { - $form->addError('Nesprávné přihlašovací jméno nebo heslo.'); + $form->addError('Nieprawidłowa nazwa użytkownika lub hasło.'); } } ``` -Metoda [User::login() |api:Nette\Security\User::login()] rzuca wyjątek, jeśli nazwa użytkownika i hasło nie pasują do informacji zawartych w pliku konfiguracyjnym. Jak już wiemy, może to skutkować czerwoną stroną błędu lub, w trybie produkcyjnym, komunikatem informującym o errorem serwera. Jednak my tego nie chcemy. Dlatego łapiemy ten wyjątek i przekazujemy do formularza ładny, przyjazny dla użytkownika komunikat o błędzie. +Metoda [User::login() |api:Nette\Security\User::login()] rzuci wyjątek, jeśli nazwa użytkownika i hasło nie zgadzają się z danymi w pliku konfiguracyjnym. Jak już wiemy, może to skutkować czerwoną stroną błędu lub, w trybie produkcyjnym, komunikatem informującym o błędzie serwera. Tego jednak nie chcemy. Dlatego przechwycimy ten wyjątek i przekażemy ładny, przyjazny dla użytkownika komunikat błędu do formularza. -Gdy w formularzu pojawi się błąd, strona formularza zostanie przerysowana, a nad formularzem pojawi się ładny komunikat informujący użytkownika, że wypełnił zły login lub hasło. +Gdy w formularzu wystąpi błąd, strona z formularzem zostanie ponownie wyrenderowana, a nad formularzem pojawi się ładny komunikat informujący użytkownika, że podał złą nazwę użytkownika lub hasło. -Bezpieczeństwo prezentera .[#toc-security-of-presenters] -======================================================== +Zabezpieczenie prezenterów +========================== -Udostępniamy formularz do dodawania i edycji prezenterów. Jest to zdefiniowane w prezenterze `EditPresenter`. Celem jest uniemożliwienie dostępu do strony użytkownikom, którzy nie są zalogowani. +Zabezpieczymy formularz do dodawania i edytowania postów. Jest on zdefiniowany w presenterze `EditPresenter`. Celem jest uniemożliwienie dostępu do strony użytkownikom, którzy nie są zalogowani. -Stworzymy metodę `startup()`, która jest uruchamiana natychmiast na początku [cyklu życia prezentera |application:presenters#Life-Cycle-of-Presenter]. To przekierowuje niezalogowanych użytkowników do formularza logowania. +Utworzymy metodę `startup()`, która uruchamia się natychmiast na początku [cyklu życia presentera |application:presenters#Cykl życia presentera]. Przekieruje ona niezalogowanych użytkowników do formularza logowania. -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} public function startup(): void { parent::startup(); @@ -114,67 +114,66 @@ public function startup(): void ``` -Ukryj linki .[#toc-hide-links] ------------------------------- +Ukrywanie linków +---------------- -Nieautoryzowany użytkownik nie może już zobaczyć strony *create* lub *edit*, ale nadal może zobaczyć linki do nich. Te również powinniśmy ukryć. Jeden z takich linków znajduje się w szablonie `app/Presenters/templates/Home/default.latte` i powinien być widoczny tylko dla zalogowanych użytkowników. +Nieautoryzowany użytkownik już nie może zobaczyć strony `create` ani `edit`, ale nadal może widzieć do nich linki. Te również powinniśmy ukryć. Jeden taki link znajduje się w szablonie `app/Presentation/Home/default.latte` i powinni go widzieć tylko zalogowani użytkownicy. -Możemy go ukryć używając *n:atrybutu* w imieniu `n:if`. Jeśli ten warunek jest `false`, to cały znacznik `<a>`, łącznie z treścią, pozostanie ukryty. +Możemy go ukryć za pomocą *n:atrybutu* o nazwie `n:if`. Jeśli ten warunek jest `false`, cały tag `<a>`, wraz z zawartością, pozostanie ukryty. ```latte -<a n:href="Edit:create" n:if="$user->isLoggedIn()">Vytvořit příspěvek</a> +<a n:href="Edit:create" n:if="$user->isLoggedIn()">Utwórz post</a> ``` -co jest skrótem następującej notacji (nie mylić z `tag-if`): +co jest skrótem następującego zapisu (nie mylić z `tag-if`): ```latte -{if $user->isLoggedIn()}<a n:href="Edit:create">Vytvořit příspěvek</a>{/if} +{if $user->isLoggedIn()}<a n:href="Edit:create">Utwórz post</a>{/if} ``` -W ten sam sposób ukrywamy link w szablonie `app/Presenters/templates/Post/show.latte`. +W ten sam sposób ukryjemy również link w szablonie `app/Presentation/Post/show.latte`. -Link do logowania .[#toc-login-form-link] -========================================= +Link do logowania +================= -Jak właściwie dostać się na stronę logowania? Nie ma żadnego linku, który by do niego prowadził. Dodajmy więc go do szablonu `@layout.latte`. Spróbujcie znaleźć odpowiednie miejsce - może to być niemal każde. +Jak właściwie dostaniemy się na stronę logowania? Nie ma tu żadnego linku, który by do niej prowadził. Dodajmy go więc do szablonu `@layout.latte`. Spróbuj znaleźć odpowiednie miejsce - może to być prawie wszędzie. -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <ul class="navig"> - <li><a n:href="Home:">Články</a></li> + <li><a n:href="Home:">Artykuły</a></li> {if $user->isLoggedIn()} - <li><a n:href="Sign:out">Odhlásit</a></li> + <li><a n:href="Sign:out">Wyloguj</a></li> {else} - <li><a n:href="Sign:in">Přihlásit</a></li> + <li><a n:href="Sign:in">Zaloguj</a></li> {/if} </ul> ... ``` -Jeśli użytkownik nie jest zalogowany, pojawi się link "Zaloguj się". W przeciwnym razie pojawi się link "Wyloguj". Dodamy też tę akcję do strony `SignPresenter`. +Jeśli użytkownik nie jest zalogowany, wyświetli się link "Zaloguj". W przeciwnym razie wyświetli się link "Wyloguj". Tę akcję również dodamy do `SignPresenter`. -Ponieważ przekierowujemy użytkownika od razu po wylogowaniu, nie jest potrzebny żaden szablon. Wylogowanie wygląda tak: +Ponieważ użytkownika po wylogowaniu natychmiast przekierowujemy, nie jest potrzebny żaden szablon. Wylogowanie wygląda tak: -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} public function actionOut(): void { $this->getUser()->logout(); - $this->flashMessage('Odhlášení bylo úspěšné.'); + $this->flashMessage('Wylogowanie powiodło się.'); $this->redirect('Home:'); } ``` -Wystarczy wywołać metodę `logout()`, a następnie wyświetlić ładny komunikat potwierdzający pomyślne wylogowanie. +Wywołuje się tylko metodę `logout()`, a następnie wyświetla się ładny komunikat potwierdzający pomyślne wylogowanie. -Streszczenie. .[#toc-summary] -============================= +Podsumowanie +============ -Mamy link do logowania, a także wylogowania użytkowników. Użyliśmy wbudowanego authenticatora do uwierzytelniania i mamy dane logowania w pliku konfiguracyjnym, ponieważ jest to prosta aplikacja testowa. Zabezpieczyliśmy również formularze edycji, aby tylko zalogowani użytkownicy mogli dodawać i edytować posty. +Mamy link do logowania i wylogowania użytkownika. Do uwierzytelniania użyliśmy wbudowanego authenticatora, a dane logowania mamy w pliku konfiguracyjnym, ponieważ jest to prosta aplikacja testowa. Zabezpieczyliśmy również formularze edycji, więc dodawać i edytować posty mogą tylko zalogowani użytkownicy. .[note] -Więcej o [weryfikacji |security:authorization] [logowania |security:authentication] i [uprawnień |security:authorization] [użytkowników |security:authentication] można przeczytać tutaj. +Tutaj możesz przeczytać więcej o [logowaniu użytkowników |security:authentication] i [autoryzacji |security:authorization]. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/pl/comments.texy b/quickstart/pl/comments.texy index 2c9c295541..145bd91739 100644 --- a/quickstart/pl/comments.texy +++ b/quickstart/pl/comments.texy @@ -1,28 +1,28 @@ -Uwagi -***** +Komentarze +********** -Wrzuciliśmy bloga na serwer internetowy i opublikowaliśmy kilka bardzo ciekawych postów za pomocą Adminera. Ludzie czytają naszego bloga i są nim zachwyceni. Codziennie dostajemy wiele maili z komplementami. Ale jaki jest sens tych wszystkich pochwał, jeśli mamy je tylko w mailu i nikt nie może ich przeczytać? Lepiej by było, gdyby czytelnik mógł bezpośrednio skomentować artykuł, żeby wszyscy mogli przeczytać, jacy jesteśmy zajebiści. +Umieściliśmy bloga na serwerze internetowym i opublikowaliśmy kilka bardzo interesujących postów za pomocą Adminera. Ludzie czytają naszego bloga i są nim bardzo podekscytowani. Codziennie otrzymujemy wiele e-maili z pochwałami. Ale po co nam te wszystkie pochwały, skoro mamy je tylko w e-mailu i nikt nie może ich przeczytać? Byłoby lepiej, gdyby czytelnik mógł bezpośrednio komentować artykuł, aby każdy mógł przeczytać, jacy jesteśmy wspaniali. Zaprogramujmy więc komentarze. -Tworzenie nowej tabeli .[#toc-creating-a-new-table] -=================================================== +Tworzenie nowej tabeli +====================== -Załaduj Adminera i utwórz tabelę `comments` z następującymi kolumnami: +Uruchomimy Adminera i utworzymy tabelę `comments` z następującymi kolumnami: -- `id` int, sprawdzenie autoinkrementacji (AI) -- `post_id`, klucz obcy wskazujący na tabelę `posts` -- `name` varchar, długość 255 -- `email` varchar, długość 255 -- `content` tekst -- `created_at` znacznik czasu +- `id` int, zaznaczamy autoincrement (AI) +- `post_id`, klucz obcy, który odwołuje się do tabeli `posts` +- `name` varchar, length 255 +- `email` varchar, length 255 +- `content` text +- `created_at` timestamp -Tak więc tabela powinna wyglądać coś takiego: +Tabela powinna więc wyglądać mniej więcej tak: [* adminer-comments.webp *] -Nie zapomnij ponownie użyć magazynu InnoDB. +Pamiętaj, aby ponownie użyć silnika InnoDB. ```sql CREATE TABLE `comments` ( @@ -37,104 +37,104 @@ CREATE TABLE `comments` ( ``` -Formularz do komentowania .[#toc-form-for-commenting] -===================================================== +Formularz do komentowania +========================= -Najpierw musimy stworzyć formularz, który pozwoli użytkownikom komentować posty. Nette Framework ma niesamowite wsparcie dla formularzy. Możemy je skonfigurować w prezenterze i wyrenderować w szablonie. +Najpierw musimy stworzyć formularz, który umożliwi użytkownikom komentowanie postów. Nette Framework ma niesamowite wsparcie dla formularzy. Możemy je skonfigurować w presenterze i wyrenderować w szablonie. -Nette Framework wykorzystuje pojęcie *komponentu*. **Komponent** to klasa wielokrotnego użytku lub fragment kodu, który można dołączyć do innego komponentu. Nawet prezenter jest składnikiem. Każdy komponent jest tworzony poprzez fabrykę. Stwórzmy więc fabrykę dla formularza komentarza w prezenterze `PostPresenter`. +Nette Framework wykorzystuje koncept *komponentów*. **Komponent** to reużywalna klasa lub część kodu, która może być dołączona do innego komponentu. Nawet presenter jest komponentem. Każdy komponent jest tworzony za pomocą fabryki. Stworzymy więc fabrykę dla formularza komentarzy w presenterze `PostPresenter`. -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} protected function createComponentCommentForm(): Form { - $form = new Form; // czyli Nette\Application\UI\Form + $form = new Form; // oznacza Nette\Application\UI\Form - $form->addText('name', 'Jméno:') + $form->addText('name', 'Imię:') ->setRequired(); $form->addEmail('email', 'E-mail:'); - $form->addTextArea('content', 'Komentář:') + $form->addTextArea('content', 'Komentarz:') ->setRequired(); - $form->addSubmit('send', 'Publikovat komentář'); + $form->addSubmit('send', 'Opublikuj komentarz'); return $form; } ``` -Wyjaśnijmy to jeszcze raz trochę. Pierwsza linia tworzy nową instancję komponentu `Form`. Kolejne metody dołączają wejścia HTML do definicji tego formularza. `->addText` jest renderowany jako `<input type="text" name="name">` s `<label>Jméno:</label>`. Jak zapewne dobrze się domyśliłeś, `->addTextArea` jest renderowany jako `<textarea>` oraz `->addSubmit` jako `<input type="submit">`. Podobnych metod jest znacznie więcej, ale te na razie wystarczą dla tej formy. Więcej na ten temat można przeczytać w [dokumentacji |forms:]. +Wyjaśnijmy to sobie jeszcze raz. Pierwsza linia tworzy nową instancję komponentu `Form`. Następne metody dołączają inputy HTML do definicji tego formularza. `->addText()` zostanie wyrenderowane jako `<input type="text" name="name">` z `<label>Imię:</label>`. Jak już pewnie słusznie się domyślacie, `->addTextArea()` zostanie wyrenderowane jako `<textarea>`, a `->addSubmit()` jako `<input type="submit">`. Istnieje znacznie więcej podobnych metod, ale te na razie wystarczą dla tego formularza. Więcej [przeczytasz w dokumentacji|forms:]. -Jeśli formularz jest już zdefiniowany w prezenterze, możemy go renderować (wyświetlać) w szablonie. Zrobimy to, umieszczając znacznik `{control}` na końcu szablonu renderującego jeden konkretny post, w `Post/show.latte`. Ponieważ komponent nazywa się `commentForm` (nazwa pochodzi od nazwy metody `createComponentCommentForm`), znacznik będzie wyglądał tak: +Jeśli formularz jest już zdefiniowany w presenterze, możemy go wyrenderować (wyświetlić) w szablonie. Zrobimy to, umieszczając znacznik `{control}` na końcu szablonu, który renderuje jeden konkretny post, w `Post/show.latte`. Ponieważ komponent nazywa się `commentForm` (nazwa pochodzi od nazwy metody `createComponentCommentForm`), znacznik będzie wyglądał tak: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... -<h2>Vložte nový příspěvek</h2> +<h2>Dodaj nowy komentarz</h2> {control commentForm} ``` -Teraz, gdy zobaczysz stronę szczegółów postu, zobaczysz nowy formularz komentarza na końcu postu. +Gdy teraz wyświetlisz stronę ze szczegółami posta, na jego końcu zobaczysz nowy formularz do komentowania. -Zapisywanie do bazy danych .[#toc-saving-to-database] -===================================================== +Zapisywanie do bazy danych +========================== -Czy próbowałeś wypełnić i przesłać formularz? Być może zauważyłeś, że formularz w zasadzie nic nie robi. Musimy dołączyć metodę callback, która zapisuje przesłane dane. +Czy próbowałeś już wypełnić i wysłać formularz? Prawdopodobnie zauważyłeś, że ten formularz właściwie nic nie robi. Musimy dołączyć metodę callback, która zapisze wysłane dane. -Na linii przed `return` w fabryce dla komponentu `commentForm` umieszczamy następujący wiersz: +W linii przed `return` w fabryce dla komponentu `commentForm` umieścimy następującą linię: -```php -$form->onSuccess[] = [$this, 'commentFormSucceeded']; +```php .{file:app/Presentation/Post/PostPresenter.php} +$form->onSuccess[] = $this->commentFormSucceeded(...); ``` -Poprzedni zapis oznacza "po udanym przesłaniu formularza wywołaj metodę `commentFormSucceeded` z bieżącego prezentera". Jednak ta metoda jeszcze nie istnieje. Więc stwórzmy go: +Poprzedni zapis oznacza "po pomyślnym wysłaniu formularza wywołaj metodę `commentFormSucceeded` z obecnego presentera". Ta metoda jednak jeszcze nie istnieje. Stwórzmy ją więc: -```php .{file:app/Presenters/PostPresenter.php} -public function commentFormSucceeded(\stdClass $data): void +```php .{file:app/Presentation/Post/PostPresenter.php} +private function commentFormSucceeded(\stdClass $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); $this->database->table('comments')->insert([ - 'post_id' => $postId, + 'post_id' => $id, 'name' => $data->name, 'email' => $data->email, 'content' => $data->content, ]); - $this->flashMessage('Děkuji za komentář', 'success'); + $this->flashMessage('Dziękuję za komentarz', 'success'); $this->redirect('this'); } ``` -Metodę tę umieścimy bezpośrednio za fabryką formularzy `commentForm`. +Tę metodę umieścimy bezpośrednio za fabryką formularza `commentForm`. -Ta nowa metoda przyjmuje jeden argument, którym jest instancja formularza, która została złożona - utworzona przez fabrykę. Pobieramy przekazane wartości w `$data`. A następnie przechowujemy dane w tabeli bazy danych `comments`. +Ta nowa metoda ma jeden argument, `$data`, który jest obiektem zawierającym wysłane dane. Następnie zapisujemy dane do tabeli bazy danych `comments`. -Są jeszcze dwie metody, które zasługują na wyjaśnienie. Metoda Redirect dosłownie przekierowuje z powrotem na bieżącą stronę. Jest to przydatne do zrobienia po każdym przesłaniu formularza, o ile zawierał on prawidłowe dane, a callback wykonał operację tak, jak powinien. Ponadto, jeśli przekierujemy stronę po przesłaniu formularza, nie zobaczymy znanego komunikatu `Chcete odeslat data z formuláře znovu?`, który czasem widzimy w przeglądarce. (Ogólnie rzecz biorąc, po przesłaniu formularza metodą `POST` zawsze powinno nastąpić przekierowanie do akcji `GET` ). +Są tu jeszcze dwie inne metody, które zasługują na wyjaśnienie. Metoda `redirect('this')` dosłownie przekierowuje z powrotem na bieżącą stronę. Jest to wskazane po każdym pomyślnym przetworzeniu formularza. Dzięki temu, jeśli użytkownik odświeży stronę po wysłaniu formularza, nie zobaczy dobrze znanego komunikatu `Czy chcesz ponownie wysłać dane z formularza?`, który czasami można zobaczyć w przeglądarce. (Ogólnie rzecz biorąc, po wysłaniu formularza metodą `POST` zawsze powinno następować przekierowanie do akcji `GET`.) -Metoda `flashMessage` służy do informowania użytkownika o wyniku jakiejś operacji. Ponieważ przekierowujemy, wiadomość nie może być po prostu przekazana do szablonu i wyrenderowana. Dlatego istnieje ta metoda, która zapisuje wiadomość i udostępnia ją przy następnym załadowaniu strony. Komunikaty flash są renderowane w głównym szablonie `app/Presenters/templates/@layout.latte` i wygląda to tak: +Metoda `flashMessage()` służy do informowania użytkownika o wyniku jakiejś operacji. Ponieważ przekierowujemy, wiadomość nie może być po prostu przekazana do szablonu i wyrenderowana. Dlatego istnieje ta metoda, która zapisuje tę wiadomość i udostępnia ją przy następnym załadowaniu strony. Wiadomości flash są renderowane w głównym szablonie `app/Presentation/@layout.latte` i wygląda to tak: -```latte +```latte .{file:app/Presentation/@layout.latte} <div n:foreach="$flashes as $flash" n:class="flash, $flash->type"> {$flash->message} </div> ``` -Jak już wiemy, komunikaty flash są automatycznie przekazywane do szablonu, więc nie musimy się nad tym zbytnio zastanawiać, to po prostu działa. Więcej informacji na ten temat można [znaleźć w dokumentacji |application:presenters#Flash-Messages]. +Jak już wiemy, wiadomości flash są automatycznie przekazywane do szablonu, więc nie musimy o tym zbytnio myśleć, po prostu to działa. Aby uzyskać więcej informacji, [odwiedź dokumentację |application:presenters#Wiadomości flash]. -Uwagi dotyczące renderingu .[#toc-rendering-the-comments] -========================================================= +Renderowanie komentarzy +======================= -To jedna z tych rzeczy, które po prostu się kocha. Nette Database posiada świetną funkcję o nazwie [Explorer |database:explorer]. Czy pamiętasz jeszcze, że celowo tworzyliśmy tabele w bazie danych przy użyciu repozytorium InnoDB? Więc Adminer stworzył coś, co nazywa się [kluczami obcymi |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html], co oszczędza nam wiele pracy. +To jedna z tych rzeczy, które po prostu pokochasz. Nette Database ma świetną funkcję o nazwie [Explorer |database:explorer]. Pamiętasz jeszcze, że tabele w bazie danych celowo tworzyliśmy za pomocą silnika InnoDB? Adminer stworzył w ten sposób coś, co nazywa się [kluczami obcymi |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html], które zaoszczędzą nam mnóstwo pracy. -Nette Database Explorer wykorzystuje klucze obce do rozwiązywania relacji między tabelami i może automatycznie tworzyć zapytania do bazy danych na podstawie znajomości tych relacji. +Nette Database Explorer używa kluczy obcych do rozwiązania wzajemnych relacji między tabelami, a na podstawie znajomości tych relacji potrafi automatycznie tworzyć zapytania do bazy danych. -Jak pamiętasz, przekazaliśmy zmienną `$post` do szablonu za pomocą metody `PostPresenter::renderShow()`, a teraz chcemy iterować po wszystkich komentarzach, które mają wartość kolumny `post_id` pasującą do `$post->id`. Możemy to zrobić, wywołując `$post->related('comments')`. Tak, to takie proste. Przyjrzyjmy się wynikowemu kodowi: +Jak zapewne pamiętasz, do szablonu przekazaliśmy zmienną `$post` za pomocą metody `PostPresenter::renderShow()` i teraz chcemy iterować po wszystkich komentarzach, które mają wartość kolumny `post_id` zgodną z `$post->id`. Możemy to osiągnąć, wywołując `$post->related('comments')`. Tak, to takie proste. Spójrzmy na wynikowy kod: -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { ... $this->template->post = $post; @@ -142,17 +142,17 @@ public function renderShow(int $postId): void } ``` -I szablon: +A szablon: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... -<h2>Komentáře</h2> +<h2>Komentarze</h2> <div class="comments"> {foreach $comments as $comment} <p><b><a href="mailto:{$comment->email}" n:tag-if="$comment->email"> {$comment->name} - </a></b> napsal:</p> + </a></b> napisał:</p> <div>{$comment->content}</div> {/foreach} @@ -160,13 +160,12 @@ I szablon: ... ``` -Zwróć uwagę na specjalny atrybut `n:tag-if`. Wiesz już jak działa `n:atributy`. Jeśli poprzedzisz atrybut znakiem `tag-`, funkcjonalność jest stosowana tylko do znacznika HTML, a nie do jego zawartości. Dzięki temu możemy sprawić, że imię komentatora stanie się linkiem tylko wtedy, gdy podał on swój e-mail. Obie linie są identyczne: +Zwróć uwagę na specjalny atrybut `n:tag-if`. Już wiesz, jak działają `n:atrybuty`. Jeśli do atrybutu dodasz prefiks `tag-`, funkcjonalność zostanie zastosowana tylko do tagu HTML, a nie do jego zawartości. Pozwala nam to zrobić z imienia komentatora link tylko w przypadku, gdy podał swój e-mail. Te dwie linie są identyczne: ```latte -<strong n:tag-if="$important"> Dobrý den! </strong> +<strong n:tag-if="$important"> Dzień dobry! </strong> -{if $important}<strong>{/if} Dobrý den! {if $important}</strong>{/if} +{if $important}<strong>{/if} Dzień dobry! {if $important}</strong>{/if} ``` {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/pl/creating-posts.texy b/quickstart/pl/creating-posts.texy index 498c9c58dd..40ad10deba 100644 --- a/quickstart/pl/creating-posts.texy +++ b/quickstart/pl/creating-posts.texy @@ -1,30 +1,30 @@ Tworzenie i edycja postów ************************* -To jest niesamowite! Mamy super fajnego nowego bloga, ludzie wściekle dyskutują w komentarzach, a my w końcu mamy trochę czasu, żeby jeszcze trochę poprogramować. O ile Adminer jest świetnym narzędziem, to do pisania nowych postów na blogu nie jest do końca idealny. To chyba dobry moment, aby stworzyć prosty formularz do dodawania nowych postów bezpośrednio z aplikacji. Zróbmy to. +To świetnie! Mamy super fajnego nowego bloga, ludzie zawzięcie dyskutują w komentarzach, a my mamy wreszcie trochę czasu na dalsze programowanie. Chociaż Adminer jest świetnym narzędziem, nie jest do końca idealny do pisania nowych postów na blogu. Najwyraźniej nadszedł właściwy czas na stworzenie prostego formularza do dodawania nowych postów bezpośrednio z aplikacji. Zróbmy to. Zacznijmy od zaprojektowania interfejsu użytkownika: -1) Na stronie głównej dodamy link "Napisz nowy post". -2. Ten link wyświetli formularz z tytułem i obszarem tekstowym dla treści postu. -3. Gdy klikniemy przycisk Save, post zostanie zapisany w bazie danych. +1. Na stronie głównej dodamy link "Napisz nowy post". +2. Ten link wyświetli formularz z tytułem i polem tekstowym na treść posta. +3. Gdy klikniemy przycisk Zapisz, post zostanie zapisany w bazie danych. -Później dodamy również logowanie i pozwolimy tylko zalogowanym użytkownikom dodawać posty. Ale to na później. Jaki kod musimy teraz napisać, aby wszystko działało? +Później dodamy również logowanie i dodawanie postów umożliwimy tylko zalogowanym użytkownikom. Ale to później. Jaki kod musimy napisać teraz, aby wszystko działało? -1. Stworzymy nowy prezenter z formularzem do dodawania postów. -2. Zdefiniuj callback, który zostanie uruchomiony, gdy formularz zostanie pomyślnie przesłany i który zapisze nowy post do bazy danych. -3. Tworzymy nowy szablon, na którym będzie umieszczony formularz. -4. Dodaj link do formularza do szablonu strony głównej. +1. Stworzymy nowy presenter z formularzem do dodawania postów. +2. Zdefiniujemy callback, który uruchomi się po pomyślnym wysłaniu formularza i który zapisze nowy post do bazy danych. +3. Stworzymy nowy szablon, na którym będzie ten formularz. +4. Dodamy link do formularza w szablonie strony głównej. -Nowy prezenter .[#toc-new-presenter] -==================================== +Nowy presenter +============== -Nowy prezenter będzie się nazywał `EditPresenter` i będzie przechowywany w `app/Presenters/EditPresenter.php`. Musi również połączyć się z bazą danych, więc ponownie napiszemy tutaj konstruktor, który będzie wymagał połączenia z bazą danych: +Nowy presenter nazwiemy `EditPresenter` i zapiszemy w `app/Presentation/Edit/`. Musi on również połączyć się z bazą danych, więc ponownie napiszemy konstruktor, który będzie wymagał połączenia z bazą danych: -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Edit; use Nette; use Nette\Application\UI\Form; @@ -39,92 +39,92 @@ final class EditPresenter extends Nette\Application\UI\Presenter ``` -Formularz do zapisywania składek .[#toc-form-for-saving-posts] -============================================================== +Formularz do zapisywania postów +=============================== -Formy i komponenty wyjaśniliśmy już przy okazji tworzenia komentarzy. Jeśli nadal nie jest to jasne, przejdź przez [tworzenie formularzy i komponentów |comments#Form-for-Commenting], my tu na razie poczekamy ;) +Formularze i komponenty wyjaśniliśmy już przy tworzeniu komentarzy. Jeśli nadal nie jest to jasne, przejdź do [tworzenia formularzy i komponentów |comments#Formularz do komentowania], my tu poczekamy ;) -Teraz dodajmy tę metodę do prezentera `EditPresenter`: +Teraz dodajmy tę metodę do presentera `EditPresenter`: -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} protected function createComponentPostForm(): Form { $form = new Form; - $form->addText('title', 'Titulek:') + $form->addText('title', 'Tytuł:') ->setRequired(); - $form->addTextArea('content', 'Obsah:') + $form->addTextArea('content', 'Treść:') ->setRequired(); - $form->addSubmit('send', 'Uložit a publikovat'); - $form->onSuccess[] = [$this, 'postFormSucceeded']; + $form->addSubmit('send', 'Zapisz i opublikuj'); + $form->onSuccess[] = $this->postFormSucceeded(...); return $form; } ``` -Zapisywanie nowego postu z poziomu formularza .[#toc-saving-new-post-from-form] -=============================================================================== +Zapisywanie nowego postu z formularza +===================================== -Dalej dodajemy metodę, która przetwarza dane z formularza: +Kontynuujemy dodaniem metody, która przetworzy dane z formularza: -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { $post = $this->database ->table('posts') ->insert($data); - $this->flashMessage("Příspěvek byl úspěšně publikován.", 'success'); + $this->flashMessage("Post został pomyślnie opublikowany.", 'success'); $this->redirect('Post:show', $post->id); } ``` -Krótkie podsumowanie: metoda ta pobiera dane z formularza, umieszcza je w bazie danych, tworzy wiadomość dla użytkownika, gdy post zostanie pomyślnie zapisany, i przekierowuje na stronę z nowym postem, więc zaraz zobaczymy, jak to wygląda. +Tylko szybkie podsumowanie: ta metoda pobiera dane z formularza, wstawia je do bazy danych, tworzy wiadomość dla użytkownika o pomyślnym zapisaniu posta i przekierowuje na stronę z nowym postem, dzięki czemu od razu zobaczymy, jak wygląda. -Strona do tworzenia nowego postu .[#toc-page-for-creating-a-new-post] -===================================================================== +Strona do tworzenia nowego postu +================================ -Utwórzmy teraz szablon `Edit/create.latte`: +Stwórzmy teraz szablon `Edit/create.latte`: -```latte .{file:app/Presenters/templates/Edit/create.latte} +```latte .{file:app/Presentation/Edit/create.latte} {block content} -<h1>Nový příspěvek</h1> +<h1>Nowy post</h1> {control postForm} ``` -Wszystko powinno być już jasne. Ostatnia linijka oddaje formę, którą dopiero mamy stworzyć. +Wszystko powinno być już jasne. Ostatnia linia renderuje formularz, który stworzyliśmy w poprzednim kroku. -Moglibyśmy również stworzyć odpowiednią metodę `renderCreate`, ale nie jest to konieczne. Nie potrzebujemy pobierać żadnych danych z bazy i przekazywać ich do szablonu, więc metoda byłaby pusta. W takich przypadkach metoda może w ogóle nie istnieć. +Moglibyśmy również stworzyć odpowiednią metodę `renderCreate()`, ale nie jest to konieczne. Nie potrzebujemy pobierać żadnych danych z bazy danych i przekazywać ich do szablonu, więc ta metoda byłaby pusta. W takich przypadkach metoda w ogóle nie musi istnieć. -Link do tworzenia postów .[#toc-link-for-creating-posts] -======================================================== +Link do tworzenia postów +======================== -Zapewne już wiesz jak dodać link do strony `EditPresenter` i jej akcji `create`. Wypróbuj to. +Prawdopodobnie już wiesz, jak dodać link do `EditPresenter` i jego akcji `create`. Spróbuj to zrobić. -Wystarczy dodać `app/Presenters/templates/Home/default.latte` do pliku: +Wystarczy do pliku `app/Presentation/Home/default.latte` dodać: ```latte -<a n:href="Edit:create">Napsat nový příspěvek</a> +<a n:href="Edit:create">Napisz nowy post</a> ``` -Edytuj posty .[#toc-editing-posts] -================================== +Edycja postów +============= -Teraz dodamy również możliwość edycji postu. To będzie bardzo proste. Mamy już formularz `postForm` i możemy go wykorzystać do edycji. +Teraz dodamy również możliwość edycji posta. Będzie to bardzo proste. Mamy już gotowy formularz `postForm` i możemy go użyć również do edycji. -Dodamy nową stronę `edit` do prezentera `EditPresenter`: +Dodamy nową stronę `edit` do presentera `EditPresenter`: -```php .{file:app/Presenters/EditPresenter.php} -public function renderEdit(int $postId): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +public function renderEdit(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { $this->error('Post not found'); @@ -135,26 +135,26 @@ public function renderEdit(int $postId): void } ``` -I utwórz kolejny szablon `Edit/edit.latte`: +I stworzymy kolejny szablon `Edit/edit.latte`: -```latte .{file:app/Presenters/templates/Edit/edit.latte} +```latte .{file:app/Presentation/Edit/edit.latte} {block content} -<h1>Upravit příspěvek</h1> +<h1>Edytuj post</h1> {control postForm} ``` -I zmodyfikuj metodę `postFormSucceeded`, aby można było zarówno dodać nowy artykuł (tak jak teraz), jak i edytować istniejący: +Zmodyfikujemy metodę `postFormSucceeded()`, która będzie mogła zarówno dodać nowy artykuł (tak jak robi to teraz), jak i edytować już istniejący artykuł: -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); - if ($postId) { + if ($id) { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); $post->update($data); } else { @@ -163,26 +163,25 @@ public function postFormSucceeded(array $data): void ->insert($data); } - $this->flashMessage('Příspěvek byl úspěšně publikován.', 'success'); + $this->flashMessage('Post został pomyślnie opublikowany.', 'success'); $this->redirect('Post:show', $post->id); } ``` -Jeśli parametr `postId` jest dostępny , oznacza to, że będziemy edytować post. W takim przypadku sprawdzimy, czy żądany post naprawdę istnieje, a jeśli tak, zaktualizuj go w bazie danych. Jeśli parametr `postId` nie jest dostępny, oznacza to, że należy dodać nowy post. +Jeśli parametr `id` jest dostępny, oznacza to, że będziemy edytować post. W tym przypadku sprawdzimy, czy żądany post naprawdę istnieje, a jeśli tak, zaktualizujemy go w bazie danych. Jeśli parametr `id` nie jest dostępny, oznacza to, że powinien zostać dodany nowy post. -Skąd jednak bierze się parametr `postId`? Jest to parametr, który został wstawiony do metody `renderEdit`. +Skąd jednak weźmie się ten parametr `id`? Jest to parametr, który został przekazany do metody `renderEdit`. -Teraz możemy dodać link do szablonu `app/Presenters/templates/Post/show.latte`: +Teraz możemy dodać link do szablonu `app/Presentation/Post/show.latte`: ```latte -<a n:href="Edit:edit $post->id">Upravit příspěvek</a> +<a n:href="Edit:edit $post->id">Edytuj post</a> ``` -Podsumowanie .[#toc-summary] -============================ +Podsumowanie +============ -Blog już działa, goście aktywnie go komentują i nie potrzebujemy już Adminera do jego publikacji. Aplikacja jest w pełni niezależna i każdy może dodać nowy post. Więc zaraz, chyba nie do końca jest tak, że każdy - i naprawdę mam na myśli każdego z dostępem do internetu - może dodawać nowe posty. Potrzebne są pewne zabezpieczenia, aby tylko zalogowany użytkownik mógł dodać nowy post. Przyjrzymy się temu w następnym rozdziale. +Blog jest teraz funkcjonalny, odwiedzający aktywnie go komentują, a my już nie potrzebujemy Adminera do publikacji. Aplikacja jest w pełni niezależna i każdy może dodać nowy post. Chwila, to chyba nie jest do końca w porządku, że ktokolwiek - i mam na myśli naprawdę ktokolwiek z dostępem do internetu - może dodawać nowe posty. Potrzebne jest jakieś zabezpieczenie, aby nowy post mógł dodać tylko zalogowany użytkownik. Zajmiemy się tym w następnym rozdziale. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/pl/home-page.texy b/quickstart/pl/home-page.texy index 205ecb59d6..4d84adbff5 100644 --- a/quickstart/pl/home-page.texy +++ b/quickstart/pl/home-page.texy @@ -2,40 +2,40 @@ Strona główna bloga ******************* .[perex] -Teraz stwórzmy stronę główną pokazującą najnowsze posty. +Teraz stworzymy stronę główną wyświetlającą najnowsze posty. -Zanim zaczniemy, musimy znać przynajmniej podstawy wzorca projektowego Model-Widok-Prezenter (podobnego do MVC((Model-Widok-Kontroler)): +Zanim zaczniemy, trzeba znać przynajmniej podstawy wzorca projektowego Model-View-Presenter (podobnego do MVC((Model-View-Controller))): -- **Model** - warstwa, która pracuje z danymi. Jest on całkowicie oddzielony od reszty aplikacji. Komunikuje się tylko z prezenterem. +- **Model** - warstwa pracująca z danymi. Jest całkowicie oddzielona od reszty aplikacji. Komunikuje się tylko z presenterem. -- **Widok** - warstwa front-end. Renderuje wymagane dane za pomocą szablonów i wyświetla je użytkownikowi. +- **View** - warstwa front-endowa. Renderuje żądane dane za pomocą szablonów i wyświetla je użytkownikowi. -- **Prezenter** (lub Kontroler) - warstwa łącząca. Prezenter łączy Model i Widok. Przetwarza on żądania, zapytuje Model o dane i zwraca je z powrotem do Widoku. +- **Presenter** (lub Controller) - warstwa łącząca. Presenter łączy Model i View. Przetwarza żądania, pyta Model o dane i zwraca je z powrotem do View. -W przypadku prostych aplikacji, jakimi będzie nasz blog, cała warstwa modelu będzie składać się z samych zapytań do bazy danych - nie potrzebujemy do tego jeszcze żadnego dodatkowego kodu. Na początek więc stworzymy tylko prezentery i szablony. W Nette każdy prezenter ma swoje szablony, więc będziemy je tworzyć jednocześnie. +W przypadku prostych aplikacji, takich jak nasz blog, całą warstwę modelową będą stanowić tylko zapytania do bazy danych - na to na razie nie potrzebujemy żadnego dodatkowego kodu. Na początek stworzymy więc tylko presentery i szablony. W Nette każdy presenter ma swoje własne szablony, więc będziemy je tworzyć jednocześnie. -Tworzenie bazy danych za pomocą Adminera .[#toc-creating-the-database-with-adminer] -=================================================================================== +Tworzenie bazy danych za pomocą Adminera +======================================== -Do przechowywania danych wykorzystamy bazę MySQL, ponieważ jest ona najczęściej spotykana wśród programistów aplikacji internetowych. Jeśli jednak nie chcesz z niego korzystać, nie krępuj się i wybierz dowolną bazę danych. +Do przechowywania danych użyjemy bazy danych MySQL, ponieważ jest najbardziej rozpowszechniona wśród programistów aplikacji internetowych. Jeśli jednak nie chcesz jej używać, śmiało wybierz bazę danych według własnego uznania. -Teraz przygotujemy strukturę bazy danych, w której będą przechowywane nasze artykuły na blogu. Zaczniemy bardzo prosto - stworzymy tylko jedną tabelę dla postów. +Teraz przygotujemy strukturę bazy danych, w której będą przechowywane posty naszego bloga. Zaczniemy bardzo prosto - stworzymy tylko jedną tabelę dla postów. -Aby stworzyć bazę danych, możemy pobrać [Adminera |https://www.adminer.org], lub inne ulubione narzędzie do zarządzania bazą danych. +Do stworzenia bazy danych możemy pobrać [Adminer |https://www.adminer.org] lub inne ulubione narzędzie do zarządzania bazami danych. -Otwórz Adminera i utwórz nową bazę danych o nazwie `quickstart`. +Otworzymy Adminera i stworzymy nową bazę danych o nazwie `quickstart`. -Utwórz nową tabelę o nazwie `posts` z następującymi kolumnami: -- `id` int, sprawdzenie autoinkrementacji (AI) -- `title` varchar, długość 255 -- `content` tekst -- `created_at` znacznik czasu +Stworzymy nową tabelę o nazwie `posts` z następującymi kolumnami: +- `id` int, zaznaczymy autoincrement (AI) +- `title` varchar, length 255 +- `content` text +- `created_at` timestamp -Powstała struktura powinna wyglądać tak: +Wynikowa struktura powinna wyglądać tak: [* adminer-posts.webp *] @@ -49,49 +49,46 @@ CREATE TABLE `posts` ( ``` .[caution] -Naprawdę ważne jest, aby używać **InnoDB** storage. Za chwilę pokażemy dlaczego. Na razie wystarczy ją zaznaczyć i kliknąć Zapisz. +Naprawdę ważne jest użycie silnika **InnoDB**. Za chwilę pokażemy dlaczego. Na razie po prostu go wybierz i kliknij zapisz. -Zanim stworzymy możliwość dodawania artykułów do bazy danych za pomocą aplikacji, dodajmy ręcznie kilka przykładowych artykułów z bloga. +Zanim stworzymy możliwość dodawania postów do bazy danych za pomocą aplikacji, dodaj kilka przykładowych postów na blogu ręcznie. ```sql INSERT INTO `posts` (`id`, `title`, `content`, `created_at`) VALUES -(1, 'Article One', 'Lorem ipusm dolor one', CURRENT_TIMESTAMP), -(2, 'Article Two', 'Lorem ipsum dolor two', CURRENT_TIMESTAMP), -(3, 'Article Three', 'Lorem ipsum dolor three', CURRENT_TIMESTAMP); +(1, 'Artykuł Pierwszy', 'Lorem ipsum dolor jeden', CURRENT_TIMESTAMP), +(2, 'Artykuł Drugi', 'Lorem ipsum dolor dwa', CURRENT_TIMESTAMP), +(3, 'Artykuł Trzeci', 'Lorem ipsum dolor trzy', CURRENT_TIMESTAMP); ``` -Podłączenie do bazy danych .[#toc-connecting-to-the-database] -============================================================= +Połączenie z bazą danych +======================== -Teraz, gdy baza danych jest utworzona i mamy kilka artykułów w niej zapisanych, to dobry moment, aby wyświetlić je na naszej pięknej, nowej stronie. +Teraz, gdy baza danych jest już stworzona i mamy w niej zapisanych kilka postów, nadszedł właściwy czas, aby wyświetlić je na naszej pięknej nowej stronie. -Najpierw musimy powiedzieć aplikacji, z jakiej bazy danych ma korzystać. Konfigurujemy połączenie z bazą danych w pliku `config/local.neon` używając DSN((Data Source Name)) i poświadczeń logowania. Powinno to wyglądać coś takiego: +Najpierw musimy powiedzieć aplikacji, jakiej bazy danych ma używać. Połączenie z bazą danych ustawimy w pliku `config/common.neon` za pomocą DSN((Data Source Name)) i danych logowania. Powinno to wyglądać mniej więcej tak: -```neon .{file:config/local.neon} +```neon .{file:config/common.neon} database: dsn: 'mysql:host=127.0.0.1;dbname=quickstart' - user: *zde vložte jméno uživatele* - password: *zde vložte heslo k databázi* + user: *tutaj wstaw nazwę użytkownika* + password: *tutaj wstaw hasło do bazy danych* ``` .[note] -Podczas edycji tego pliku zwróć uwagę na wcięcie linii. Format [NEON |neon:format] akceptuje zarówno wcięcie spacji, jak i wcięcie tabulatora, ale nie oba jednocześnie. Domyślny plik konfiguracyjny w Web Project wykorzystuje zakładki. - -Cała konfiguracja, w tym konfiguracja bazy danych, jest przechowywana w katalogu `/config/` w plikach `common.neon` i `local.neon`. Plik `common.neon` zawiera globalne ustawienia aplikacji, a plik `local.neon` zawiera tylko te parametry, które są specyficzne dla bieżącego środowiska (różnica między serwerami deweloperskimi i produkcyjnymi, itp.) +Podczas edycji tego pliku zwracaj uwagę na wcięcia linii. Format [NEON |neon:format] akceptuje zarówno wcięcia za pomocą spacji, jak i wcięcia za pomocą tabulatorów, ale nie oba jednocześnie. Domyślny plik konfiguracyjny w Web Project używa tabulatorów. -Przekazanie połączenia z bazą danych .[#toc-injecting-the-database-connection] -============================================================================== +Przekazanie połączenia z bazą danych +==================================== -Prezenter `HomePresenter`, który będzie obsługiwał zestawienie artykułów, potrzebuje połączenia z bazą danych. Aby go uzyskać, użyjemy konstruktora, który wygląda tak: +Presenter `HomePresenter`, który będzie zajmował się wyświetlaniem postów, potrzebuje połączenia z bazą danych. Aby je uzyskać, wykorzystamy konstruktor, który będzie wyglądał tak: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; -use Nette\Application\UI\Form; final class HomePresenter extends Nette\Application\UI\Presenter { @@ -105,12 +102,12 @@ final class HomePresenter extends Nette\Application\UI\Presenter ``` -Ładowanie postów z bazy danych .[#toc-loading-posts-from-the-database] -====================================================================== +Wczytywanie postów z bazy danych +================================ -Teraz ładujemy posty z bazy danych i wysyłamy je do szablonu, który następnie renderuje je jako kod HTML. Do tego właśnie służy tzw. metoda *render*: +Teraz wczytamy posty z bazy danych i przekażemy je do szablonu, który następnie wyrenderuje je jako kod HTML. Do tego służy tak zwana metoda *render*: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} public function renderDefault(): void { $this->template->posts = $this->database @@ -120,41 +117,41 @@ public function renderDefault(): void } ``` -Presenter zawiera teraz jedną metodę render `renderDefault()`, która przekazuje dane z bazy danych do szablonu (View). Szablony znajdują się w `app/Presenters/templates/{PresenterName}/{viewName}.latte`, więc w tym przypadku szablon znajduje się w `app/Presenters/templates/Home/default.latte`. Szablon będzie miał teraz zmienną `$posts`, która zawiera posty pobrane z bazy danych. +Presenter teraz zawiera jedną metodę renderującą `renderDefault()`, która przekazuje dane z bazy danych do szablonu (View). Szablony są umieszczone w `app/Presentation/{PresenterName}/{viewName}.latte`, więc w tym przypadku szablon znajduje się w `app/Presentation/Home/default.latte`. W szablonie teraz będzie dostępna zmienna `$posts`, w której znajdują się posty pobrane z bazy danych. -Szablon .[#toc-template] -======================== +Szablon +======= -Dla całej strony mamy szablon główny (który nazywa się *layout*, zawiera nagłówek, style, stopkę,...) oraz specyficzne szablony dla każdego widoku (View) (np. do wyświetlania postów na blogu), które mogą nadpisywać niektóre części szablonu głównego. +Dla całej strony internetowej mamy do dyspozycji główny szablon (który nazywa się *layout*, zawiera nagłówek, style, stopkę,...) oraz konkretne szablony dla każdego widoku (View) (np. do wyświetlania postów na blogu), które mogą nadpisać niektóre części głównego szablonu. -Domyślnie szablon układu znajduje się w `app/Presenters/templates/@layout.latte` i zawiera: +Domyślnie szablon layoutu znajduje się w `app/Presentation/@layout.latte` i zawiera: -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... {include content} ... ``` -Wpis `{include content}` wstawia do głównego szablonu blok o nazwie `content`. zdefiniujemy to w poszczególnych szablonach widoku (View). W naszym przypadku zmodyfikujemy plik `Home/default.latte` w następujący sposób: +Zapis `{include content}` wstawia do głównego szablonu blok o nazwie `content`. Będziemy go definiować w szablonach poszczególnych widoków (View). W naszym przypadku plik `Home/default.latte` zmodyfikujemy następująco: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - Hello World + Witaj Świecie {/block} ``` -W ten sposób zdefiniowaliśmy [blok |latte:tags#block] *content*, który zostanie wstawiony do głównego układu. Jeśli ponownie odświeżymy przeglądarkę, zobaczymy stronę z tekstem "Hello World" (w kodzie źródłowym oraz z nagłówkiem i stopką HTML zdefiniowanymi w `@layout.latte`). +W ten sposób zdefiniowaliśmy [blok |latte:tags#block] *content*, który zostanie wstawiony do głównego layoutu. Jeśli ponownie odświeżymy przeglądarkę, zobaczymy stronę z tekstem "Witaj Świecie" (w kodzie źródłowym również z nagłówkiem i stopką HTML zdefiniowanymi w `@layout.latte`). -Wyświetlmy wpisy na blogu - zmodyfikujmy szablon w następujący sposób: +Wyświetlmy posty z bloga - szablon zmodyfikujemy następująco: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - <h1>Můj blog</h1> + <h1>Mój blog</h1> {foreach $posts as $post} <div class="post"> - <div class="date">{$post->created_at|date:'F j, Y'}</div> + <div class="date">{$post->created_at|date:'j F Y'}</div> <h2>{$post->title}</h2> @@ -164,27 +161,27 @@ Wyświetlmy wpisy na blogu - zmodyfikujmy szablon w następujący sposób: {/block} ``` -Jeśli odświeżymy przeglądarkę, zobaczymy zestawienie wszystkich postów. Listing nie jest jeszcze zbyt ładny, nawet nie jest kolorowy, więc możemy dodać kilka [stylów CSS |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] do pliku `www/css/style.css` i połączyć go w układzie: +Jeśli odświeżymy przeglądarkę, zobaczymy listę wszystkich postów. Lista na razie nie jest zbyt ładna ani kolorowa, dlatego możemy do pliku `www/css/style.css` dodać kilka [stylów CSS |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] i podlinkować go w layoucie: -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <link rel="stylesheet" href="{$basePath}/css/style.css"> </head> ... ``` -Znacznik `{foreach}` iteruje po wszystkich postach, które przekazaliśmy do szablonu w zmiennej `$posts`, i dla każdego z nich renderuje dany fragment HTML. Zachowuje się dokładnie jak kod PHP. +Znacznik `{foreach}` iteruje po wszystkich postach, które przekazaliśmy do szablonu w zmiennej `$posts`, i dla każdego renderuje dany fragment HTML. Zachowuje się dokładnie jak kod PHP. -Wpis `|date:` nazywamy filtrem. Filtry mają za zadanie sformatować dane wyjściowe. Ten konkretny filtr konwertuje datę (np. `2013-04-12`) na jej bardziej czytelną postać (`April 12, 2013`). Filtr `|truncate` obcina łańcuch do maksymalnej podanej długości i dodaje na końcu trójkę, jeśli łańcuch jest obcięty. Ponieważ jest to podgląd, nie ma sensu pokazywać całej treści artykułu. Inne domyślne filtry [można znaleźć w dokumentacji |latte:filters] lub w razie potrzeby można stworzyć własne. +Zapisowi `|date:` mówimy filtr. Filtry służą do formatowania wyjścia. Ten konkretny filtr konwertuje datę (np. `2013-04-12`) na jej bardziej czytelną postać (`12 kwietnia 2013`). Filtr `|truncate` przycina ciąg znaków do podanej maksymalnej długości, a w przypadku skrócenia ciągu dodaje na końcu wielokropek. Ponieważ jest to podgląd, nie ma sensu wyświetlać całej treści posta. Inne domyślne filtry [znajdziemy w dokumentacji |latte:filters], a także możemy tworzyć własne, gdy jest to potrzebne. -Jeszcze jedna rzecz. Możemy skrócić i uprościć poprzedni kod. Możemy to zrobić zastępując znaczniki *Latte* przez *n:attributes*: +Jeszcze jedna rzecz. Poprzedni kod możemy skrócić i uprościć. Osiągniemy to, zamieniając *tagi Latte* na *n:atrybuty*: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - <h1>Můj blog</h1> + <h1>Mój blog</h1> <div n:foreach="$posts as $post" class="post"> - <div class="date">{$post->created_at|date:'F j, Y'}</div> + <div class="date">{$post->created_at|date:'j F Y'}</div> <h2>{$post->title}</h2> @@ -193,13 +190,12 @@ Jeszcze jedna rzecz. Możemy skrócić i uprościć poprzedni kod. Możemy to zr {/block} ``` -Atrybut `n:foreach` owija *div* blokiem *foreach* (działa dokładnie tak samo jak poprzedni kod). +Atrybut `n:foreach` opakowuje blok *div* blokiem *foreach* (działa dokładnie tak samo jak poprzedni kod). -Streszczenie. .[#toc-summary] -============================= +Podsumowanie +============ -Mamy teraz bardzo prostą bazę danych MySQL z kilkoma postami. Aplikacja łączy się z tą bazą danych i wyprowadza prostą listę tych postów do szablonu. +Teraz mamy bardzo prostą bazę danych MySQL z kilkoma postami. Aplikacja łączy się z tą bazą danych i wyświetla prostą listę tych postów w szablonie. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/pl/model.texy b/quickstart/pl/model.texy index 8a5b786b04..8b6e1e90c4 100644 --- a/quickstart/pl/model.texy +++ b/quickstart/pl/model.texy @@ -1,13 +1,13 @@ Model ***** -Wraz z rozwojem aplikacji szybko okazuje się, że musimy wykonywać podobne operacje na bazie danych w różnych miejscach, w różnych prezenterach. Na przykład, aby pobrać najnowsze opublikowane artykuły. Jeśli wzbogacimy aplikację, dodając na przykład flagę dla artykułów, aby wskazać, czy są one niepublikowane, musimy wtedy przejść przez wszystkie miejsca w aplikacji, gdzie artykuły są pobierane z bazy danych i dodać warunek where, aby wybrać tylko niepublikowane artykuły. +W miarę jak aplikacja rośnie, szybko odkryjemy, że w różnych miejscach, w różnych presenterach, potrzebujemy wykonywać podobne operacje na bazie danych. Na przykład pobierać najnowsze opublikowane posty. Kiedy ulepszymy aplikację, na przykład dodając do postów flagę oznaczającą, czy są w trakcie pisania, musimy przejrzeć wszystkie miejsca w aplikacji, gdzie posty są pobierane z bazy danych i dodać warunek where, aby wybierać tylko posty niebędące w trakcie pisania. -W tym momencie praca bezpośrednio z bazą danych staje się niewystarczająca i przydatne będzie użycie nowej funkcji do zwrócenia opublikowanych artykułów. A jeśli później dodamy kolejny warunek, np. że artykuły z przyszłą datą nie powinny być wyświetlane, zmodyfikujemy kod tylko w jednym miejscu. +W tym momencie bezpośrednia praca z bazą danych staje się niewystarczająca i wygodniej będzie wspomóc się nową funkcją, która będzie nam zwracać opublikowane posty. A kiedy później dodamy kolejny warunek, na przykład że nie mają być wyświetlane posty z przyszłą datą, zmodyfikujemy kod tylko w jednym miejscu. -Na przykład umieść funkcję w klasie `PostFacade` i nazwij ją `getPublicArticles()`. +Funkcję umieścimy na przykład w klasie `PostFacade` i nazwiemy ją `getPublicArticles()`. -W katalogu `app/Model/` stworzymy naszą klasę modelową `PostFacade`, która zajmie się artykułami: +W katalogu `app/Model/` stworzymy naszą klasę modelową `PostFacade`, która będzie zajmować się postami: ```php .{file:app/Model/PostFacade.php} <?php @@ -32,13 +32,13 @@ final class PostFacade } ``` -W klasie będziemy mieli konstruktor przekazujący eksplorator bazy danych:[api:Nette\Database\Explorer]. Wykorzystamy moc [kontenera DI |dependency-injection:passing-dependencies]. +W klasie za pomocą konstruktora poprosimy o przekazanie [Nette\Database\Explorer|api:Nette\Database\Explorer]. Wykorzystamy w ten sposób siłę [kontenera DI|dependency-injection:passing-dependencies]. -Przechodzimy na stronę `HomePresenter`, którą modyfikujemy pozbywając się zależności od `Nette\Database\Explorer` i zastępując ją zależnością od naszej nowej klasy. +Przełączymy się na `HomePresenter`, który zmodyfikujemy tak, że pozbędziemy się zależności od `Nette\Database\Explorer` i zastąpimy ją nową zależnością od naszej nowej klasy. -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use App\Model\PostFacade; use Nette; @@ -59,10 +59,9 @@ final class HomePresenter extends Nette\Application\UI\Presenter } ``` -W sekcji use mamy `App\Model\PostFacade`, więc możemy skrócić zapis kodu PHP do `PostFacade`. Będziemy żądać tego obiektu w konstruktorze, zapisywać go do właściwości `$facade` i używać w metodzie renderDefault. +W sekcji use mamy `App\Model\PostFacade`, więc możemy skrócić zapis w kodzie PHP do `PostFacade`. O ten obiekt poprosimy w konstruktorze, zapiszemy go we właściwości `$facade` i użyjemy w metodzie renderDefault. -Pozostał ostatni krok - nauczenie kontenera DI produkowania tego obiektu. Zwykle robi się to poprzez dodanie punktu wypunktowania do pliku `config/services.neon` w sekcji `services`, podając pełną nazwę klasy i parametry konstruktora. -To rejestruje go, że tak powiem, a obiekt jest następnie nazywany **serwisem**. Dzięki magicznej sztuczce zwanej [autowiring |dependency-injection:autowiring], zazwyczaj nie musimy podawać parametrów konstruktora, ponieważ DI sam je rozpozna i przekaże. Tak więc wystarczyłoby po prostu podać nazwę klasy: +Pozostaje ostatni krok, czyli nauczenie kontenera DI tworzenia tego obiektu. Zwykle robi się to tak, że do pliku `config/services.neon` w sekcji `services` dodajemy myślnik, podajemy pełną nazwę klasy i parametry konstruktora. W ten sposób tak zwaną ją zarejestrujemy, a obiekt nazywa się wtedy **usługą**. Dzięki magii zwanej [autowiring |dependency-injection:autowiring] zazwyczaj nie musimy podawać parametrów konstruktora, ponieważ DI samo je rozpozna i przekaże. Wystarczyłoby więc podać tylko nazwę klasy: ```neon .{file:config/services.neon} ... @@ -71,16 +70,15 @@ services: - App\Model\PostFacade ``` -Nie musisz jednak dodawać również tej linii. Sekcja `search` na początku `services.neon` definiuje, że wszystkie klasy kończące się na `-Facade` lub `-Factory` będą wyszukiwane przez DI, co dotyczy również `PostFacade`. +Jednak nawet tej linii nie musisz dodawać. W sekcji `search` na początku `services.neon` zdefiniowano, że wszystkie klasy kończące się słowem `-Facade` lub `-Factory` DI wyszuka samo, co jest również przypadkiem `PostFacade`. -Streszczenie. .[#toc-summary] -============================= +Podsumowanie +============ -Klasa `PostFacade` prosi konstruktora o przekazanie `Nette\Database\Explorer`, a ponieważ ta klasa jest zarejestrowana w kontenerze DI, kontener tworzy i przekazuje tę instancję. DI tworzy więc dla nas instancję `PostFacade` i przekazuje ją w konstruktorze do klasy HomePresenter, która jej zażądała. To jest matryca. :) Każdy mówi tylko to, co chce i nie przejmuje się tym, gdzie co powstaje i jak. Kontener DI zajmuje się tworzeniem. +Klasa `PostFacade` w konstruktorze prosi o przekazanie `Nette\Database\Explorer` i ponieważ ta klasa jest zarejestrowana w kontenerze DI, kontener tworzy tę instancję i przekazuje ją. DI w ten sposób tworzy dla nas instancję `PostFacade` i przekazuje ją w konstruktorze klasie HomePresenter, która o nią poprosiła. Taka matrioszka. :) Wszyscy tylko mówią, czego chcą i nie interesują się tym, gdzie co i jak się tworzy. O tworzenie dba kontener DI. .[note] -Możesz przeczytać więcej o [wtrysku zależności |dependency-injection:introduction] i [konfiguracji |nette:configuring] tutaj. +Tutaj możesz przeczytać więcej o [wstrzykiwaniu zależności |dependency-injection:introduction] i [konfiguracji |nette:configuring]. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/pl/single-post.texy b/quickstart/pl/single-post.texy index f02f3543c9..ddabd7353b 100644 --- a/quickstart/pl/single-post.texy +++ b/quickstart/pl/single-post.texy @@ -1,17 +1,17 @@ -Strona z wkładem -**************** +Strona z postem +*************** .[perex] -Teraz stwórzmy kolejną stronę bloga, która będzie wyświetlała jeden konkretny post. +Teraz stworzymy kolejną stronę bloga, która będzie wyświetlać jeden konkretny post. -Musimy stworzyć nową metodę renderującą, która pobiera jeden konkretny artykuł i przekazuje go do szablonu. Posiadanie tej metody w `HomePresenter` nie jest zbyt miłe, ponieważ mówimy o artykule, a nie o stronie głównej. Stwórzmy więc `PostPresenter` w `app/Presenters`. Ten prezenter również musi połączyć się z bazą danych, więc tutaj znowu napiszemy konstruktor, który będzie wymagał połączenia z bazą danych. +Musimy stworzyć nową metodę render, która pobierze jeden konkretny post i przekaże go do szablonu. Umieszczenie tej metody w `HomePresenter` nie jest zbyt eleganckie, ponieważ mówimy o poście, a nie o stronie głównej. Stwórzmy więc `PostPresenter` w `app/Presentation/Post/`. Ten presenter również potrzebuje połączenia z bazą danych, więc ponownie napiszemy konstruktor, który będzie wymagał połączenia z bazą danych. -`PostPresenter` Mogłoby to wtedy wyglądać tak: +`PostPresenter` mógłby więc wyglądać tak: -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Post; use Nette; use Nette\Application\UI\Form; @@ -23,103 +23,102 @@ final class PostPresenter extends Nette\Application\UI\Presenter ) { } - public function renderShow(int $postId): void + public function renderShow(int $id): void { $this->template->post = $this->database ->table('posts') - ->get($postId); + ->get($id); } } ``` -Nie zapomnij uwzględnić prawidłowej przestrzeni nazw `App\Presenters`, która podlega ustawieniom [mapowania prezentera |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7]. +Nie możemy zapomnieć o podaniu poprawnej przestrzeni nazw `App\Presentation\Post`, która podlega ustawieniom [mapowania presenterów |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7]. -Metoda `renderShow` wymaga jednego argumentu - ID jednego konkretnego artykułu, który ma być wyświetlony. Następnie pobiera ten artykuł z bazy danych i przekazuje go do szablonu. +Metoda `renderShow` wymaga jednego argumentu - ID jednego konkretnego posta, który ma być wyświetlony. Następnie ten post wczytuje z bazy danych i przekazuje go do szablonu. -W szablonie `Home/default.latte` wstawiamy link do akcji `Post:show`. +Do szablonu `Home/default.latte` wstawimy link do akcji `Post:show`. -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a href="{link Post:show $post->id}">{$post->title}</a></h2> ... ``` -Znacznik `{link}` generuje adres URL, który wskazuje na akcję `Post:show`. Przekazuje również identyfikator postu jako argument. +Znacznik `{link}` generuje adres URL, który wskazuje na akcję `Post:show`. Przekazuje również ID posta jako argument. -To samo można napisać w skrócie, używając atrybutu n:: +To samo możemy zapisać skrótowo za pomocą n:atrybutu: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a n:href="Post:show $post->id">{$post->title}</a></h2> ... ``` -Atrybut `n:href` jest podobny do znacznika `{link}`. +Atrybut `n:href` jest odpowiednikiem tagu `{link}`. -Nie ma jednak jeszcze szablonu dla akcji `Post:show`. Możemy spróbować otworzyć link do tego postu. [Tracy |tracy:] wyświetla błąd, ponieważ szablon `Post/show.latte` jeszcze nie istnieje. Jeśli widzisz inny komunikat o błędzie, prawdopodobnie musisz włączyć `mod_rewrite` na serwerze internetowym. +Dla akcji `Post:show` jednak jeszcze nie istnieje szablon. Możemy spróbować otworzyć link do tego posta. [Tracy |tracy:] wyświetli błąd, ponieważ szablon `Post/show.latte` jeszcze nie istnieje. Jeśli widzisz inny komunikat błędu, prawdopodobnie będziesz musiał włączyć `mod_rewrite` na serwerze WWW. -Stwórzmy więc szablon `Post/show.latte` z tą treścią: +Stworzymy więc szablon `Post/show.latte` z następującą zawartością: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} {block content} -<p><a n:href="Home:default">← zpět na výpis příspěvků</a></p> +<p><a n:href="Home:default">← wróć do listy postów</a></p> -<div class="date">{$post->created_at|date:'F j, Y'}</div> +<div class="date">{$post->created_at|date:'j F Y'}</div> <h1 n:block="title">{$post->title}</h1> <div class="post">{$post->content}</div> ``` -Teraz przejdźmy przez poszczególne części szablonu. +Teraz przejdziemy przez poszczególne części szablonu. -Pierwsza linia zaczyna się od zdefiniowania bloku o nazwie "content" tak jak to było na stronie głównej. Ten blok będzie ponownie wyświetlany w głównym szablonie. Jak widać, brakuje znacznika końcowego `{/block}`. Jest to opcja. +Pierwsza linia zaczyna definicję bloku o nazwie "content", tak samo jak było to na stronie głównej. Ten blok zostanie ponownie wyświetlony w głównym szablonie. Jak widzisz, brakuje końcowego znacznika `{/block}`. Jest on bowiem opcjonalny. -W kolejnym wierszu znajduje się link zwrotny do listy artykułów na blogu, dzięki czemu użytkownik może łatwo poruszać się między listą artykułów a jednym konkretnym. Ponieważ używamy atrybutu `n:href`, Nette samo zajmuje się generowaniem linków. Link wskazuje na działanie prezentera `default` `Home` (możemy też napisać `n:href="Home:"`, bo działanie o nazwie `default` można pominąć, zostanie wypełnione automatycznie). +W następnej linii znajduje się link powrotny do listy postów bloga, dzięki czemu użytkownik może łatwo poruszać się między listą postów a jednym konkretnym. Ponieważ używamy atrybutu `n:href`, Nette samo zadba o generowanie linków. Link wskazuje na akcję `default` presentera `Home` (możemy również napisać `n:href="Home:"`, ponieważ akcja o nazwie `default` może być pominięta, zostanie uzupełniona automatycznie). -Trzecia linia formatuje datę zrzutu za pomocą znanego nam już filtra. +Trzecia linia formatuje wyświetlanie daty za pomocą filtra, który już znamy. -Czwarta linia wyświetla *tytuł* wpisu na blogu w znaczniku HTML `<h1>`. Ten znacznik zawiera atrybut, którego być może nie znasz (`n:block="title"`). Czy zgadniesz, co to robi? Jeśli uważnie przeczytałeś poprzedni rozdział, wiesz już, że jest to `n:atribut`. To jest ich kolejny przykład, który jest równoważny: +Czwarta linia wyświetla *tytuł* posta w tagu HTML `<h1>`. Ten tag zawiera atrybut, którego być może nie znasz (`n:block="title"`). Zgadniesz, co robi? Jeśli czytałeś poprzednią część uważnie, już wiesz, że jest to `n:atrybut`. To jest ich kolejny przykład, który jest równoważny z: ```latte {block title}<h1>{$post->title}</h1>{/block} ``` -Po prostu ten blok predefiniuje blok o nazwie `title`. Blok ten jest już zdefiniowany w głównym szablonie *layout* (`/app/Presenters/templates/@layout.latte:11`) i podobnie jak w przypadku nakładek na metody w OOP, nakłada ten blok w szablonie głównym w dokładnie taki sam sposób. Więc `<title>` strona zawiera teraz nagłówek wyświetlanego postu, a wszystko co musieliśmy zrobić to użyć jednego prostego atrybutu `n:block="title"`. Świetnie, prawda? +Mówiąc prościej, ten blok przedefiniowuje blok o nazwie `title`. Ten blok jest już zdefiniowany w głównym szablonie *layout* (`/app/Presentation/@layout.latte:11`) i tak jak w przypadku nadpisywania metod w OOP, dokładnie tak samo ten blok w głównym szablonie zostanie nadpisany. Tak więc `<title>` strony teraz zawiera tytuł wyświetlanego posta, a wystarczyło nam do tego użyć tylko jednego prostego atrybutu `n:block="title"`. Świetnie, prawda? -Piąta i ostatnia linia szablonu wyświetla całą zawartość jednego konkretnego postu. +Piąta i ostatnia linia szablonu wyświetla całą treść jednego konkretnego posta. -Sprawdź identyfikator postu .[#toc-checking-post-id] -==================================================== +Sprawdzanie ID posta +==================== -Co się stanie jeśli ktoś zmieni ID w URL i umieści jakiś nieistniejący `postId`? Powinniśmy zaoferować użytkownikowi ładny błąd "page not found". Zmodyfikujmy więc nieco metodę render w `PostPresenter`: +Co się stanie, jeśli ktoś zmieni ID w URL i wstawi jakieś nieistniejące `id`? Powinniśmy zaoferować użytkownikowi ładny błąd typu "strona nie została znaleziona". Zmodyfikujemy więc trochę metodę render w `PostPresenter`: -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { - $this->error('Stránka nebyla nalezena'); + $this->error('Strona nie została znaleziona'); } $this->template->post = $post; } ``` -Jeśli nie można znaleźć postu, wywołanie `$this->error(...)` wyświetli stronę błędu 404 z jasnym komunikatem. Zauważ, że w trybie deweloperskim (localhost) nie zobaczysz tej strony błędu. Zamiast tego pokaże Tracy ze szczegółami wyjątku, co jest dość wygodne dla rozwoju. Jeśli chcemy mieć wyświetlane oba tryby, wystarczy zmienić argument metody `setDebugMode` w pliku `Bootstrap.php`. +Jeśli post nie może zostać znaleziony, wywołaniem `$this->error(...)` wyświetlimy stronę błędu 404 z czytelnym komunikatem. Uważaj na to, że w trybie deweloperskim (localhost) tej strony błędu nie zobaczysz. Zamiast tego pokaże się Tracy ze szczegółami wyjątku, co jest dość wygodne podczas rozwoju. Jeśli chcemy wyświetlić oba tryby, wystarczy zmienić argument metody `setDebugMode` w pliku `Bootstrap.php`. -Streszczenie. .[#toc-summary] -============================= +Podsumowanie +============ -Mamy bazę danych z postami i aplikację internetową, która ma dwa widoki - pierwszy pokazuje przegląd wszystkich postów, a drugi pokazuje jeden konkretny post. +Mamy bazę danych z postami i aplikację internetową, która ma dwa widoki - pierwszy wyświetla przegląd wszystkich postów, a drugi wyświetla jeden konkretny post. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/pt/@home.texy b/quickstart/pt/@home.texy index ee1c598724..b63d82987e 100644 --- a/quickstart/pt/@home.texy +++ b/quickstart/pt/@home.texy @@ -1,113 +1,114 @@ -Crie sua primeira aplicação! -**************************** +Escrevendo a primeira aplicação! +******************************** .[perex] -Conheça o Nette Framework enquanto cria um blog simples com comentários. Vamos começar! +Vamos conhecer juntos o Nette Framework, criando um blog simples com comentários. Vamos lá! -Após os dois primeiros capítulos, você terá seu próprio blog de trabalho e estará pronto para publicar seus incríveis posts, embora as características sejam bastante limitadas após completar estes dois capítulos. Para tornar as coisas mais agradáveis para seus usuários, você também deve ler os capítulos seguintes e continuar melhorando sua aplicação. +Já após os dois primeiros capítulos teremos nosso próprio blog funcional e poderemos publicar nossos ótimos posts, embora as funções ainda sejam bastante limitadas. Você também deve ler os capítulos seguintes, onde programaremos a adição de comentários, edição de artigos e, finalmente, protegeremos o blog. .[tip] -Este tutorial pressupõe que você completou o documento de [instalação |nette:installation] e configurou com sucesso suas ferramentas. +Este tutorial assume que você leu a página [Instalação |nette:installation] e preparou com sucesso as ferramentas necessárias. Também assume que você entende [programação orientada a objetos em PHP |nette:introduction-to-object-oriented-programming]. -Por favor, use PHP 8.0 ou posterior. Você pode encontrar a aplicação completa [no GitHub |https://github.com/nette-examples/quickstart/tree/v4.0]. +Por favor, use PHP 8.1 ou mais recente. A aplicação completa pode ser encontrada [no GitHub |https://github.com/nette-examples/quickstart/tree/v4.0]. -A página de boas-vindas .[#toc-the-welcome-page] -================================================ +Página de boas-vindas +===================== -Vamos começar criando um novo projeto no diretório `nette-blog`: +Comecemos criando um novo projeto no diretório `nette-blog`: ```shell composer create-project nette/web-project nette-blog ``` -Neste momento, a página de boas-vindas do Projeto Web deve estar em funcionamento. Experimente-o abrindo seu navegador e indo para o seguinte URL: +Neste ponto, a página inicial do Web Project já deve estar funcionando. Vamos testar abrindo o navegador no seguinte endereço URL: ``` http://localhost/nette-blog/www/ ``` -e você deve ver a página de boas-vindas da Nette Framework: +e veremos a página inicial do Nette Framework: [* qs-welcome.webp .{url: http://localhost/nette-blog/www/} *] -A aplicação funciona e agora você pode começar a fazer mudanças nela. +A aplicação funciona e você pode começar a fazer alterações. .[note] -Se você tiver algum problema, [tente estas poucas dicas |nette:troubleshooting#Nette Is Not Working, White Page Is Displayed]. +Se ocorreu um problema, [tente estas poucas dicas |nette:troubleshooting#O Nette não funciona uma página em branco é exibida]. -Conteúdo do Projeto Web .[#toc-web-project-s-content] -===================================================== +Conteúdo do Web Project +======================= -O Projeto Web tem a seguinte estrutura: +O Web Project tem a seguinte estrutura: /--pre <b>nette-blog/</b> -├── <b>app/</b> ← application directory -│ ├── <b>Presenters/</b> ← presenter classes -│ │ └── <b>templates/</b>← templates -│ ├── <b>Router/</b> ← configuration of URL addresses -│ └── <b>Bootstrap.php</b> ← booting class Bootstrap -├── <b>bin/</b> ← scripts for the command line -├── <b>config/</b> ← configuration files -├── <b>log/</b> ← error logs -├── <b>temp/</b> ← temporary files, cache, … -├── <b>vendor/</b> ← libraries installed by Composer -│ └── <b>autoload.php</b> ← autoloading of libraries installed by Composer -└── <b>www/</b> ← public folder - the only place accessible from browser - └── <b>index.php</b> ← initial file that launches the application +├── <b>app/</b> ← diretório com a aplicação +│ ├── <b>Core/</b> ← classes básicas necessárias para o funcionamento +│ ├── <b>Presentation/</b> ← presenters, templates & cia. +│ │ └── <b>Home/</b> ← diretório do presenter Home +│ └── <b>Bootstrap.php</b> ← classe de inicialização Bootstrap +├── <b>assets/</b> ← ativos brutos (SCSS, TypeScript, imagens de origem) +├── <b>bin/</b> ← scripts executados da linha de comando +├── <b>config/</b> ← arquivos de configuração +├── <b>log/</b> ← log de erros +├── <b>temp/</b> ← arquivos temporários, cache, … +├── <b>vendor/</b> ← bibliotecas instaladas pelo Composer +│ └── <b>autoload.php</b> ← autoloading de todos os pacotes instalados +└── <b>www/</b> ← diretório público - o único acessível pelo navegador + ├── <b>assets/</b> ← arquivos estáticos compilados (CSS, JS, imagens, ...) + └── <b>index.php</b> ← arquivo inicial pelo qual a aplicação é iniciada \-- -O diretório `www` deve armazenar imagens, JavaScript, CSS, e outros arquivos disponíveis ao público. Este é o único diretório diretamente acessível a partir do navegador, portanto você pode apontar o diretório raiz de seu servidor web aqui (você pode configurá-lo no Apache, mas vamos fazê-lo mais tarde, pois não é importante agora). +O diretório `www/` destina-se ao armazenamento de imagens, arquivos JavaScript, estilos CSS e outros arquivos publicamente acessíveis. Apenas este diretório é acessível pela internet, então configure o diretório raiz da sua aplicação para apontar para cá (você pode configurar isso na configuração do Apache ou nginx, mas vamos fazer isso depois, não é importante agora). -O diretório mais importante para você é `app/`. Lá você pode encontrar o arquivo `Bootstrap.php`, dentro do qual há uma classe que carrega a estrutura e configura a aplicação. Ela ativa o [auto-carregamento |robot-loader:] e configura o [depurador |tracy:] e as [rotas |application:routing]. +A pasta mais importante para nós é `app/`. Aqui encontramos o arquivo `Bootstrap.php`, que contém a classe que serve para carregar todo o framework e configurar a aplicação. Ativa-se aqui o [autoloading |robot-loader:], configura-se o [debugger |tracy:] e as [rotas |application:routing]. -Limpeza .[#toc-cleanup] -======================= +Limpeza +======= -O Projeto Web contém uma página de boas-vindas, que podemos remover - sinta-se à vontade para excluir o arquivo `app/Presenters/templates/Home/default.latte` e substituí-lo pelo texto "Olá mundo! +O Web Project contém uma página inicial, que excluiremos antes de começarmos a programar algo. Portanto, sem medo, substituiremos o conteúdo do arquivo `app/Presentation/Home/default.latte` por "Olá mundo!". [* qs-hello.webp .{url:-} *] -Tracy (Depurador) .[#toc-tracy-debugger] -======================================== +Tracy (debugger) +================ -Uma ferramenta extremamente importante para o desenvolvimento é [uma depuradora chamada Tracy |tracy:]. Tente cometer alguns erros em seu arquivo `app/Presenters/HomePresenter.php` (por exemplo, remova um colchete da definição da classe HomePresenter) e veja o que acontece. Uma página na tela vermelha irá aparecer com uma descrição compreensível do erro. +Uma ferramenta extremamente importante para o desenvolvimento é a [ferramenta de depuração Tracy |tracy:]. Tente provocar algum erro no arquivo `app/Presentation/Home/HomePresenter.php` (por exemplo, removendo uma chave na definição da classe HomePresenter) e veja o que acontece. Aparecerá uma página de notificação que descreve o erro de forma compreensível. -[* qs-tracy.webp .{url:-}(debugger screen) *] +[* qs-tracy.avif .{url:-}(tela do debugger) *] -Tracy o ajudará significativamente durante a caça aos erros. Observe também a barra flutuante Tracy no canto inferior direito, que o informa sobre dados importantes de tempo de execução. +O Tracy nos ajudará imensamente quando estivermos procurando erros na aplicação. Observe também a Barra Tracy flutuante no canto inferior direito da tela, que contém informações sobre a execução da aplicação. [* qs-tracybar.webp .{url:-} *] -No modo de produção, Tracy está, naturalmente, desativada e não revela nenhuma informação sensível. Em vez disso, todos os erros são salvos no diretório `log/`. Basta experimentá-lo. Em `app/Bootstrap.php`, encontre o seguinte código, descomente a linha e mude o parâmetro de chamada do método para `false`, de modo que se pareça com isto: +No modo de produção, o Tracy é obviamente desativado e não exibe nenhuma informação sensível. Todos os erros neste caso são armazenados na pasta `log/`. Vamos experimentar. No arquivo `app/Bootstrap.php`, descomente a seguinte linha e altere o parâmetro da chamada para `false`, para que o código fique assim: ```php .{file:app/Bootstrap.php} ... -$configurator->setDebugMode(false); -$configurator->enableTracy(__DIR__ . '/../log'); +$this->configurator->setDebugMode(false); ... ``` -Depois de atualizar a página web, a página da tela vermelha será substituída pela mensagem de fácil utilização: +Após atualizar a página, não veremos mais o Tracy. Em vez disso, uma mensagem amigável ao usuário será exibida: -[* qs-fatal.webp .{url:-}(error screen) *] +[* qs-fatal.webp .{url:-}(tela de erro) *] -Agora, consulte o diretório `log/`. Lá você pode encontrar o log de erros (em arquivo exception.log) e também a página com a mensagem de erro (salva em um arquivo HTML com um nome que começa com `exception`). +Agora, vejamos o diretório `log/`. Aqui (no arquivo `exception.log`) encontraremos o erro registrado e também a já conhecida página com a mensagem de erro (salva como um arquivo HTML com um nome começando com `exception-`). -Linha de comentários `// $configurator->setDebugMode(false);` novamente. Tracy habilita automaticamente o modo de desenvolvimento no ambiente `localhost` e o desabilita em outro lugar. +Comente novamente a linha `// $configurator->setDebugMode(false);`. O Tracy ativa automaticamente o modo de desenvolvimento no localhost e o desativa em todos os outros lugares. -Agora, podemos consertar o bug e continuar projetando nossa aplicação. +Podemos corrigir o erro que criamos e continuar escrevendo a aplicação. -Enviar agradecimentos .[#toc-send-thanks] -========================================= +Envie um agradecimento +====================== -Mostraremos a vocês um truque que fará felizes os autores de código aberto. Você pode facilmente dar uma estrela no GitHub para as bibliotecas que seu projeto utiliza. Basta executar: +Mostraremos um truque que agradará os autores de código aberto. De forma simples, você pode dar uma estrela no GitHub às bibliotecas que seu projeto usa. Basta executar: ```shell composer thanks @@ -116,4 +117,3 @@ composer thanks Experimente! {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/pt/@left-menu.texy b/quickstart/pt/@left-menu.texy index 1173fe66ec..646b1cf05b 100644 --- a/quickstart/pt/@left-menu.texy +++ b/quickstart/pt/@left-menu.texy @@ -1,9 +1,9 @@ Tutorial ******** -- [Crie sua primeira aplicação! |@home] -- [Página inicial do Blog |home-page] -- [Página única de postagem |single-post] +- [Escrevendo a primeira aplicação! |@home] +- [Página inicial do blog |home-page] +- [Página com postagem |single-post] - [Comentários |comments] -- [Criação e Edição de Posts |creating-posts] -- [Modelo |Model] +- [Criando e editando postagens |creating-posts] +- [Model |Model] - [Autenticação |authentication] diff --git a/quickstart/pt/@meta.texy b/quickstart/pt/@meta.texy new file mode 100644 index 0000000000..d25846d4bb --- /dev/null +++ b/quickstart/pt/@meta.texy @@ -0,0 +1 @@ +{{sitename: Escrevendo a primeira aplicação!}} diff --git a/quickstart/pt/authentication.texy b/quickstart/pt/authentication.texy index 9a9c810d86..9f85c77967 100644 --- a/quickstart/pt/authentication.texy +++ b/quickstart/pt/authentication.texy @@ -1,36 +1,36 @@ Autenticação ************ -Nette fornece diretrizes sobre como programar a autenticação em sua página, mas não o obriga a fazê-lo de nenhuma forma em particular. A implementação é da sua responsabilidade. Nette tem uma interface `Nette\Security\Authenticator` que o força a implementar apenas um único método chamado `authenticate`, que encontra o usuário de qualquer maneira que você queira. +O Nette fornece uma maneira de programar a autenticação em nossos sites, mas não nos força a nada. A implementação depende inteiramente de nós. O Nette contém a interface `Nette\Security\Authenticator`, que requer apenas um método `authenticate`, que verifica o usuário da maneira que quisermos. -Há muitas maneiras de como um usuário pode se autenticar. A maneira mais comum é *a autenticação baseada em senha* (o usuário fornece seu nome ou e-mail e uma senha), mas também há outros meios. Você pode estar familiarizado com os botões "Login com Facebook" em muitos sites, ou fazer login via Google/Twitter/GitHub ou qualquer outro site. Com Nette, você pode ter qualquer método de autenticação que desejar, ou pode combiná-los. A decisão é sua. +Existem muitas opções de como um usuário pode ser autenticado. O método mais comum de autenticação é por senha (o usuário fornece seu nome ou e-mail e senha), mas existem outras formas. Você pode conhecer os botões do tipo "Login com Facebook" ou login com Google/Twitter/GitHub em alguns sites. Com o Nette, podemos ter qualquer método de login, ou podemos combiná-los. Depende apenas de nós. -Normalmente você escreveria seu próprio autenticador, mas para este pequeno e simples blog nós usaremos o autenticador incorporado, que se autentica com base em uma senha e nome de usuário armazenados em um arquivo de configuração. Ele é bom para fins de teste. Portanto, adicionaremos a seguinte seção *security* ao arquivo de configuração `config/common.neon`: +Normalmente, escreveríamos nosso próprio autenticador, mas para este pequeno blog simples, usaremos o autenticador embutido, que faz login com base em uma senha e nome de usuário armazenados no arquivo de configuração. É útil para fins de teste. Portanto, adicionaremos a seguinte seção `security` ao arquivo de configuração `config/common.neon`: ```neon .{file:config/common.neon} security: users: - admin: secreto # usuário 'admin', senha 'secret' + admin: secret # usuário 'admin', senha 'secret' ``` -A Nette criará automaticamente um serviço no contêiner DI. +O Nette criará automaticamente um serviço no contêiner DI. -Formulário de inscrição .[#toc-sign-in-form] -============================================ +Formulário de Login +=================== -Agora temos a parte back-end da autenticação pronta e precisamos fornecer uma interface de usuário, através da qual o usuário faria o login. Vamos criar um novo apresentador chamado *SignPresenter*, que irá +Agora temos a autenticação pronta e precisamos preparar a interface do usuário para o login. Vamos criar um novo presenter chamado `SignPresenter`, que: -- exibir um formulário de login (solicitando nome de usuário e senha) -- autenticar o usuário quando o formulário é submetido -- proporcionar ação de log out +- exibirá o formulário de login (com nome de usuário e senha) +- após o envio do formulário, verificará o usuário +- fornecerá a opção de logout -Vamos começar com o formulário de login. Você já sabe como funcionam os formulários em um apresentador. Crie o `SignPresenter` e o método `createComponentSignInForm`. Deve ser parecido com isto: +Começaremos com o formulário de login. Já sabemos como os formulários funcionam nos presenters. Criaremos o presenter `SignPresenter` e escreveremos o método `createComponentSignInForm`. Deve ficar algo assim: -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Sign; use Nette; use Nette\Application\UI\Form; @@ -40,69 +40,69 @@ final class SignPresenter extends Nette\Application\UI\Presenter protected function createComponentSignInForm(): Form { $form = new Form; - $form->addText('username', 'Username:') - ->setRequired('Please enter your username.'); + $form->addText('username', 'Nome de usuário:') + ->setRequired('Por favor, preencha seu nome de usuário.'); - $form->addPassword('password', 'Password:') - ->setRequired('Please enter your password.'); + $form->addPassword('password', 'Senha:') + ->setRequired('Por favor, preencha sua senha.'); - $form->addSubmit('send', 'Sign in'); + $form->addSubmit('send', 'Entrar'); - $form->onSuccess[] = [$this, 'signInFormSucceeded']; + $form->onSuccess[] = $this->signInFormSucceeded(...); return $form; } } ``` -Há uma entrada para nome de usuário e senha. +Existem campos para nome de usuário e senha. -Modelo .[#toc-template] ------------------------ +Template +-------- -O formulário será apresentado no modelo `in.latte` +O formulário será renderizado no template `in.latte`: -```latte .{file:app/Presenters/templates/Sign/in.latte} +```latte .{file:app/Presentation/Sign/in.latte} {block content} -<h1 n:block=title>Sign in</h1> +<h1 n:block=title>Login</h1> {control signInForm} ``` -Manipulador de Login .[#toc-login-handler] ------------------------------------------- +Callback de Login +----------------- -Acrescentamos também um *agitador de formulário* para assinar no usuário, que é invocado logo após o envio do formulário. +Em seguida, adicionaremos o callback para o login do usuário, que será chamado logo após o envio bem-sucedido do formulário. -O manipulador pegará apenas o nome de usuário e a senha que o usuário digitou e a passará para o autenticador definido anteriormente. Após o login do usuário, nós o redirecionaremos para a página inicial. +O callback apenas pega o nome de usuário e a senha que o usuário preencheu e os passa para o autenticador. Após o login, redirecionamos para a página inicial. -```php .{file:app/Presenters/SignPresenter.php} -public function signInFormSucceeded(Form $form, \stdClass $data): void +```php .{file:app/Presentation/Sign/SignPresenter.php} +private function signInFormSucceeded(Form $form, \stdClass $data): void { try { $this->getUser()->login($data->username, $data->password); $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { - $form->addError('Incorrect username or password.'); + $form->addError('Nome de usuário ou senha incorretos.'); } } ``` -O método [Usuário::login() |api:Nette\Security\User::login()] deve lançar uma exceção quando o nome de usuário ou senha não corresponder aos que definimos anteriormente. Como já sabemos, isso resultaria em uma tela vermelha [Tracy |tracy:], ou, no modo de produção, uma mensagem informando sobre um erro interno do servidor. Nós não gostaríamos disso. É por isso que pegamos a exceção e adicionamos uma mensagem de erro agradável e amigável ao formulário. +O método [User::login() |api:Nette\Security\User::login()] lançará uma exceção `Nette\Security\AuthenticationException` se o nome de usuário e a senha não corresponderem aos dados no arquivo de configuração. Como já sabemos, isso pode resultar em uma página de erro vermelha ou, no modo de produção, em uma mensagem informando sobre um erro do servidor. No entanto, não queremos isso. Portanto, capturamos essa exceção no bloco `catch` e passamos uma mensagem de erro agradável e amigável para o formulário usando `$form->addError()`. -Quando o erro ocorrer no formulário, a página com o formulário será novamente apresentada, e acima do formulário, haverá uma mensagem agradável, informando ao usuário que ele digitou um nome de usuário ou senha errada. +Assim que ocorrer um erro no formulário, a página com o formulário será redesenhada e uma mensagem agradável será exibida acima do formulário, informando ao usuário que ele preencheu o nome de usuário ou senha errados. -Segurança dos apresentadores .[#toc-security-of-presenters] -=========================================================== +Protegendo Presenters +===================== -Iremos assegurar um formulário para adicionar e editar postos. Ele está definido no apresentador `EditPresenter`. O objetivo é impedir que usuários que não estão logados acessem a página. +Protegeremos o formulário para adicionar e editar posts. Ele é definido no presenter `EditPresenter`. O objetivo é impedir o acesso à página por usuários que não estão logados. -Criamos um método `startup()` que é iniciado imediatamente no início do [ciclo de vida do apresentador |application:presenters#life-cycle-of-presenter]. Isto redireciona os usuários não-logados para o formulário de login. +Vytvoříme metodu `startup()`, která se spouští ihned na začátku [životního cyklu presenteru |application:presenters#Ciclo de vida do presenter]. Ta přesměruje nepřihlášené uživatele na formulář s přihlášením. -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} public function startup(): void { parent::startup(); @@ -114,67 +114,66 @@ public function startup(): void ``` -Ocultar Links .[#toc-hide-links] --------------------------------- +Ocultando Links +--------------- -Um usuário não autenticado não pode mais ver a página *criar* nem *editar*, mas ele ainda pode ver os links que apontam para eles. Vamos escondê-los também. Um desses links está em `app/Presenters/templates/Home/default.latte`, e deve ser visível somente se o usuário estiver logado. +Um usuário não autorizado não pode mais ver as páginas `create` ou `edit`, mas ainda pode ver os links para elas. Devemos escondê-los também. Um desses links está no template `app/Presentation/Home/default.latte` e deve ser visível apenas para usuários logados. -Podemos escondê-lo usando *n:attribute* chamado `n:if`. Se a declaração dentro dele for `false`, o todo `<a>` e o seu conteúdo não será exibido: +Podemos escondê-lo usando um *n:attribute* chamado `n:if`. Se esta condição for `false`, toda a tag `<a>`, incluindo o conteúdo, permanecerá oculta. -```latte -<a n:href="Edit:create" n:if="$user->isLoggedIn()">Create post</a> +```latte .{file:app/Presentation/Home/default.latte} +<a n:href="Edit:create" n:if="$user->isLoggedIn()">Escrever nova postagem</a> ``` -este é um atalho para (não confundir com `tag-if`): +que é uma abreviação da seguinte notação (não confundir com `tag-if`): ```latte -{if $user->isLoggedIn()}<a n:href="Edit:create">Create post</a>{/if} +{if $user->isLoggedIn()}<a n:href="Edit:create">Escrever nova postagem</a>{/if} ``` -Você deve ocultar o link de edição localizado em `app/Presenters/templates/Post/show.latte` de maneira semelhante. +Da mesma forma, ocultaremos também o link no template `app/Presentation/Post/show.latte`. -Link para o Formulário de Login .[#toc-login-form-link] -======================================================= +Link de Login +============= -Mas como chegamos à página de login? Não há nenhum link que aponte para ela. Vamos adicionar um no arquivo modelo `@layout.latte`. Tente encontrar um lugar agradável, pode ser em qualquer lugar que você mais goste. +Como realmente chegamos à página de login? Não há nenhum link que leve a ela. Então, vamos adicioná-lo ao template `@layout.latte`. Tente encontrar um local adequado - pode ser em quase qualquer lugar. -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <ul class="navig"> - <li><a n:href="Home:">Home</a></li> + <li><a n:href="Home:">Artigos</a></li> {if $user->isLoggedIn()} - <li><a n:href="Sign:out">Sign out</a></li> + <li><a n:href="Sign:out">Sair</a></li> {else} - <li><a n:href="Sign:in">Sign in</a></li> + <li><a n:href="Sign:in">Entrar</a></li> {/if} </ul> ... ``` -Se o usuário ainda não estiver logado, mostraremos o link "Entrar". Caso contrário, mostraremos o link "Sign out". Acrescentamos essa ação no SignPresenter. +Se o usuário não estiver logado, o link "Entrar" será exibido. Caso contrário, o link "Sair" será exibido. A ação de logout (`actionOut`) também foi adicionada ao `SignPresenter` no código anterior. -A ação de logout parece assim, e como redirecionamos o usuário imediatamente, não há necessidade de um modelo de visualização. +Jelikož uživatele po odhlášení okamžitě přesměrujeme, není potřeba žádná šablona. Odhlášení vypadá takto: -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} public function actionOut(): void { $this->getUser()->logout(); - $this->flashMessage('You have been signed out.'); + $this->flashMessage('Logout realizado com sucesso.'); $this->redirect('Home:'); } ``` -Ele apenas chama o método `logout()` e depois mostra uma mensagem agradável para o usuário. +Apenas o método `logout()` é chamado e, em seguida, uma mensagem agradável é exibida confirmando o logout bem-sucedido usando `flashMessage`. -Sumário .[#toc-summary] -======================= +Resumo +====== -Temos um link para fazer o login e também para fazer o logout do usuário. Utilizamos o autenticador incorporado para autenticação e os detalhes de login estão no arquivo de configuração, pois este é um aplicativo de teste simples. Também asseguramos os formulários de edição para que somente usuários logados possam adicionar e editar mensagens. +Temos um link para login e também para logout do usuário. Para autenticação, usamos o autenticador embutido e temos os dados de login no arquivo de configuração, pois se trata de uma aplicação de teste simples. Também protegemos os formulários de edição, para que apenas usuários logados possam adicionar e editar posts. .[note] -Aqui você pode ler mais sobre o [login |security:authentication] e [autorização |security:authorization] do [usuário |security:authentication]. +Aqui você pode ler mais sobre [login de usuários |security:authentication] e [Autorização |security:authorization]. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/pt/comments.texy b/quickstart/pt/comments.texy index 891535930b..724eefc20b 100644 --- a/quickstart/pt/comments.texy +++ b/quickstart/pt/comments.texy @@ -1,28 +1,28 @@ Comentários *********** -O blog foi implantado, escrevemos alguns posts muito bons no blog e os publicamos via Adminer. As pessoas estão lendo o blog e são muito apaixonadas por nossas idéias. Estamos recebendo muitos e-mails com elogios a cada dia. Mas para que servem todos os elogios quando os recebemos apenas no e-mail, para que ninguém mais possa lê-lo? Não seria melhor se as pessoas pudessem comentar diretamente no blog para que todos pudessem ler o quanto somos fantásticos? +Nós carregamos o blog para o servidor web e publicamos algumas postagens muito interessantes usando o Adminer. As pessoas estão lendo nosso blog e estão muito entusiasmadas com ele. Recebemos muitos e-mails com elogios todos os dias. Mas de que adianta todo esse elogio se o temos apenas em nosso e-mail e ninguém pode lê-lo? Seria melhor se o leitor pudesse comentar diretamente no artigo, para que todos pudessem ler como somos incríveis. -Vamos tornar todos os artigos louváveis. +Então, vamos programar os comentários. -Criando uma nova tabela .[#toc-creating-a-new-table] -==================================================== +Criação de uma nova tabela +========================== -Ligue novamente o Adminer e crie uma nova tabela chamada `comments` com estas colunas: +Vamos iniciar o Adminer e criar uma tabela `comments` com as seguintes colunas: -- `id` int, verificar autoincremento (AI) -- `post_id`, uma chave estrangeira que faz referência à tabela `posts` +- `id` int, marque autoincremento (AI) +- `post_id`, chave estrangeira que referencia a tabela `posts` - `name` varchar, comprimento 255 - `email` varchar, comprimento 255 -- `content` texto +- `content` text - `created_at` timestamp -Deveria ser assim: +A tabela deve, portanto, ter a seguinte aparência: [* adminer-comments.webp *] -Não se esqueça de usar o armazenamento de mesa InnoDB e pressione Save. +Não se esqueça de usar o armazenamento InnoDB novamente. ```sql CREATE TABLE `comments` ( @@ -37,104 +37,104 @@ CREATE TABLE `comments` ( ``` -Formulário para comentar .[#toc-form-for-commenting] -==================================================== +Formulário para comentar +======================== -Primeiro, precisamos criar um formulário, que permitirá aos usuários comentar em nossa página. Nette Framework tem um ótimo suporte para formulários. Eles podem ser configurados em um apresentador e renderizados em um modelo. +Primeiro, precisamos criar um formulário que permita aos usuários comentar nas postagens. O Nette Framework tem um suporte incrível para formulários. Podemos configurá-los no presenter e renderizá-los no template. -Nette Framework tem um conceito de *componentes*. Um **componente*** é uma classe ou código reutilizável, que pode ser anexado a outro componente. Até mesmo um apresentador é um componente. Cada componente é criado usando a fábrica de componentes. Portanto, vamos definir os comentários da fábrica em `PostPresenter`. +O Nette Framework utiliza o conceito de *componentes*. Um **componente** é uma classe reutilizável ou parte de código que pode ser anexada a outro componente. Até mesmo o presenter é um componente. Cada componente é criado através de uma fábrica. Portanto, criaremos uma fábrica para o formulário de comentários no presenter `PostPresenter`. -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} protected function createComponentCommentForm(): Form { - $form = new Form; // means Nette\Application\UI\Form + $form = new Form; // significa Nette\Application\UI\Form - $form->addText('name', 'Your name:') + $form->addText('name', 'Nome:') ->setRequired(); - $form->addEmail('email', 'Email:'); + $form->addEmail('email', 'E-mail:'); - $form->addTextArea('content', 'Comment:') + $form->addTextArea('content', 'Comentário:') ->setRequired(); - $form->addSubmit('send', 'Publish comment'); + $form->addSubmit('send', 'Publicar comentário'); return $form; } ``` -Vamos explicar um pouco. A primeira linha cria uma nova instância do componente `Form`. Os seguintes métodos estão anexando entradas HTML na definição do formulário. `->addText` renderizará como `<input type=text name=name>`, com `<label>Your name:</label>`. Como você já deve ter adivinhado neste momento, o `->addTextArea` anexa um `<textarea>` e `->addSubmit` acrescenta um `<input type=submit>`. Há mais métodos como esse, mas isso é tudo que você tem que saber agora mesmo. Você pode [saber mais na documentação |forms:]. +Vamos explicar isso um pouco mais. A primeira linha cria uma nova instância do componente `Form`. Os métodos seguintes anexam inputs HTML à definição deste formulário. `->addText()` será renderizado como `<input type="text" name="name">` com `<label>Nome:</label>`. Como você provavelmente já adivinhou corretamente, `->addTextArea()` será renderizado como `<textarea>` e `->addSubmit()` como `<input type="submit">`. Existem muitos outros métodos semelhantes, mas estes são suficientes para este formulário por enquanto. Você pode [ler mais na documentação|forms:]. -Uma vez que o componente do formulário é definido em um apresentador, podemos renderizá-lo (exibir) em um modelo. Para isso, coloque a tag `{control}` no final do modelo de detalhe do post, em `Post/show.latte`. Como o nome do componente é `commentForm` (é derivado do nome do método `createComponentCommentForm`), a tag terá o seguinte aspecto +Se o formulário já estiver definido no presenter, podemos renderizá-lo (exibi-lo) no template. Faremos isso colocando a tag `{control}` no final do template que renderiza uma postagem específica, em `Post/show.latte`. Como o componente se chama `commentForm` (o nome é derivado do nome do método `createComponentCommentForm`), a tag ficará assim: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... -<h2>Post new comment</h2> +<h2>Insira uma nova postagem</h2> {control commentForm} ``` -Agora, se você verificar os detalhes de algum post, haverá um novo formulário para postar comentários. +Agora, se você visualizar a página com os detalhes da postagem, verá um novo formulário de comentários no final. -Salvando para o banco de dados .[#toc-saving-to-database] -========================================================= +Salvando no banco de dados +========================== -Você já tentou enviar alguns dados? Você deve ter notado, que o formulário não está realizando nenhuma ação. Só que está lá, com um visual legal e sem fazer nada. Temos que anexar-lhe um método de retorno, que salvará os dados enviados. +Você já tentou preencher e enviar o formulário? Você provavelmente notou que o formulário na verdade não faz nada. Precisamos anexar um método de callback que salvará os dados enviados. -Coloque a seguinte linha antes da linha `return` na fábrica de componentes para o `commentForm`: +O callback `commentFormSucceeded` já foi adicionado ao código do presenter `PostPresenter` acima. ```php -$form->onSuccess[] = [$this, 'commentFormSucceeded']; +$form->onSuccess[] = $this->commentFormSucceeded(...); ``` -Significa "depois que o formulário for enviado com sucesso, ligue para o método `commentFormSucceeded` do atual apresentador". Este método ainda não existe, portanto, vamos criá-lo. +A escrita anterior significa "após o envio bem-sucedido do formulário, chame o método `commentFormSucceeded` do presenter atual". -```php .{file:app/Presenters/PostPresenter.php} -public function commentFormSucceeded(\stdClass $data): void +```php .{file:app/Presentation/Post/PostPresenter.php} +private function commentFormSucceeded(\stdClass $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); $this->database->table('comments')->insert([ - 'post_id' => $postId, + 'post_id' => $id, 'name' => $data->name, 'email' => $data->email, 'content' => $data->content, ]); - $this->flashMessage('Thank you for your comment', 'success'); + $this->flashMessage('Obrigado pelo comentário', 'success'); $this->redirect('this'); } ``` -Você deve colocá-lo logo após a fábrica de componentes `commentForm`. +Colocaremos este método logo após a fábrica do formulário `commentForm`. -O novo método tem um argumento que é a instância do formulário que está sendo apresentado, criado pela fábrica de componentes. Recebemos os valores enviados em `$data`. E depois inserimos os dados na tabela do banco de dados `comments`. +Este novo método tem um argumento, que é uma instância do formulário que foi enviado - criado pela fábrica. Obtemos os valores enviados em `$data`. E, em seguida, salvamos os dados na tabela do banco de dados `comments`. -Há mais duas chamadas de método para explicar. O redirecionamento é literalmente redirecionado para a página atual. Você deve fazer isso toda vez que o formulário for apresentado, válido, e a operação de retorno da chamada fez o que deveria ter feito. Além disso, quando você redireciona a página após enviar o formulário, você não verá a conhecida mensagem `Would you like to submit the post data again?` que às vezes você pode ver no navegador. (Em geral, após submeter um formulário pelo método `POST`, você deve sempre redirecionar o usuário para uma ação `GET` ). +Existem ainda outros dois métodos que merecem explicação. O método redirect literalmente redireciona de volta para a página atual. Isso é apropriado fazer após cada envio de formulário, se ele continha dados válidos e o callback realizou a operação como deveria. E também, se redirecionarmos a página após o envio do formulário, não veremos a conhecida mensagem `Deseja reenviar os dados do formulário?`, que às vezes podemos ver no navegador. (Geralmente, após o envio de um formulário pelo método `POST`, deve sempre seguir um redirecionamento para uma ação `GET`.) -O `flashMessage` é para informar o usuário sobre o resultado de alguma operação. Como estamos redirecionando, a mensagem não pode ser passada diretamente para o modelo e entregue. Portanto, existe este método, que irá armazená-lo e torná-lo disponível no carregamento da próxima página. As mensagens flash são renderizadas no arquivo padrão `app/Presenters/templates/@layout.latte`, e é assim que parece: +O método `flashMessage()` serve para informar o usuário sobre o resultado de alguma operação. Como estamos redirecionando, a mensagem não pode ser simplesmente passada para o template e renderizada diretamente. Por isso, existe este método, que armazena esta mensagem na sessão e a disponibiliza na próxima carga da página. As mensagens flash são renderizadas no template principal `app/Presentation/@layout.latte` e têm a seguinte aparência: -```latte +```latte .{file:app/Presentation/@layout.latte} <div n:foreach="$flashes as $flash" n:class="flash, $flash->type"> {$flash->message} </div> ``` -Como já sabemos, eles são passados automaticamente para o modelo, de modo que não é preciso pensar muito sobre isso, ele apenas funciona. Para obter mais detalhes, [consulte a documentação |application:presenters#flash-messages]. +Como já sabemos, as mensagens flash são automaticamente passadas para o template, então não precisamos pensar muito sobre isso, simplesmente funciona. Para mais informações [visite a documentação |application:presenters#Mensagens Flash]. -Comentários .[#toc-rendering-the-comments] -========================================== +Renderizando comentários +======================== -Esta é uma das coisas que você vai adorar. O Nette Database tem este recurso legal chamado [Explorer |database:explorer]. Você se lembra que nós criamos as tabelas como InnoDB? Adminer criou as chamadas [chaves estrangeiras |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html] que nos pouparão uma tonelada de trabalho. +Esta é uma daquelas coisas que você simplesmente vai adorar. O Nette Database tem uma função incrível chamada [Explorer |database:explorer]. Lembra-se que criamos intencionalmente as tabelas no banco de dados usando o armazenamento InnoDB? O Adminer criou algo chamado [chaves estrangeiras |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html], que nos poupará muito trabalho. -O Nette Database Explorer utiliza as chaves estrangeiras para resolver as relações entre tabelas, e conhecendo as relações, ele pode criar automaticamente consultas para você. +O Nette Database Explorer usa chaves estrangeiras para resolver o relacionamento mútuo entre as tabelas e, com base no conhecimento dessas relações, pode criar automaticamente consultas ao banco de dados. -Como você deve se lembrar, passamos a variável `$post` para o modelo em `PostPresenter::renderShow()` e agora queremos iterar através de todos os comentários que têm a coluna `post_id` igual ao nosso `$post->id`. Você pode fazer isso ligando para `$post->related('comments')`. É muito simples. Veja o código resultante. +Como você certamente se lembra, passamos a variável `$post` para o template usando o método `PostPresenter::renderShow()` e agora queremos iterar sobre todos os comentários que têm o valor da coluna `post_id` igual a `$post->id`. Podemos conseguir isso chamando `$post->related('comments')`. Sim, é simples assim. Vejamos o código resultante: -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { ... $this->template->post = $post; @@ -142,17 +142,17 @@ public function renderShow(int $postId): void } ``` -E o modelo: +E o template: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... -<h2>Comments</h2> +<h2>Comentários</h2> <div class="comments"> {foreach $comments as $comment} <p><b><a href="mailto:{$comment->email}" n:tag-if="$comment->email"> {$comment->name} - </a></b> said:</p> + </a></b> escreveu:</p> <div>{$comment->content}</div> {/foreach} @@ -160,13 +160,12 @@ E o modelo: ... ``` -Observe o atributo especial `n:tag-if`. Você já sabe como funciona `n: attributes`. Bem, se você preender o atributo com `tag-`, ele só envolverá as tags, não o seu conteúdo. Isto permite que você transforme o nome do comentarista em um link, caso ele tenha fornecido seu e-mail. Estas duas linhas são idênticas em resultados: +Observe o atributo especial `n:tag-if`. Você já sabe como os `n:atributos` funcionam. Se você anexar o prefixo `tag-` ao atributo, a funcionalidade será aplicada apenas à tag HTML, não ao seu conteúdo. Isso nos permite transformar o nome do comentarista em um link apenas se ele forneceu seu e-mail. Estas duas linhas são idênticas: ```latte -<strong n:tag-if="$important"> Hello there! </strong> +<strong n:tag-if="$important"> Olá! </strong> -{if $important}<strong>{/if} Hello there! {if $important}</strong>{/if} +{if $important}<strong>{/if} Olá! {if $important}</strong>{/if} ``` {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/pt/creating-posts.texy b/quickstart/pt/creating-posts.texy index a6caec6ce7..24b7931246 100644 --- a/quickstart/pt/creating-posts.texy +++ b/quickstart/pt/creating-posts.texy @@ -1,30 +1,30 @@ -Criação e Edição de Posts -************************* +Criando e editando postagens +**************************** -Que grande momento. Temos um novo blog super legal, as pessoas estão discutindo nos comentários e finalmente temos algum tempo para mais programação. Embora gostemos de Adminer, não é tão confortável escrever nele posts de blogs. Talvez seja o momento certo para adicionar um formulário simples para adicionar novos posts diretamente de nosso aplicativo. Vamos fazer isso. +Isso é ótimo! Temos um novo blog super legal, as pessoas estão discutindo intensamente nos comentários e finalmente temos um pouco de tempo para programar mais. Embora o Adminer seja uma ótima ferramenta, não é totalmente ideal para escrever novas postagens no blog. Provavelmente é a hora certa de criar um formulário simples para adicionar novas postagens diretamente do aplicativo. Vamos lá. -Vamos começar projetando a IU: +Comecemos projetando a interface do usuário: -1. Na página inicial, vamos adicionar um link "Escrever novo post". -2. Ele mostrará um formulário com título e área de texto para o conteúdo. -3. Quando você clicar no botão Salvar, ele salvará o post do blog. +1. Na página inicial, adicionaremos um link "Escrever nova postagem". +2. Este link exibirá um formulário com um título e uma área de texto para o conteúdo da postagem. +3. Quando clicarmos no botão Salvar, a postagem será salva no banco de dados. -Mais tarde também adicionaremos autenticação e permitiremos apenas usuários logados para adicionar novos posts. Mas vamos fazer isso mais tarde. Que código precisaremos escrever para que funcione? +Mais tarde, também adicionaremos login e permitiremos a adição de postagens apenas para usuários logados. Mas isso depois. Que código precisamos escrever agora para que tudo funcione? -1. Criar um novo apresentador com um formulário para adicionar postos. -2. Definir uma chamada de retorno que será acionada após o envio bem sucedido do formulário e que salvará o novo post no banco de dados. -3. Criar um novo modelo para o formulário. -4. Adicionar um link para o formulário ao modelo da página principal. +1. Criaremos um novo presenter com um formulário para adicionar postagens. +2. Definiremos um callback que será executado após o envio bem-sucedido do formulário e que salvará a nova postagem no banco de dados. +3. Criaremos um novo template no qual o formulário estará. +4. Adicionaremos um link para o formulário no template da página principal. -Novo apresentador .[#toc-new-presenter] -======================================= +Novo presenter +============== -Nomeie o novo apresentador `EditPresenter` e salve-o em `app/Presenters/EditPresenter.php`. Ele também precisa se conectar ao banco de dados, então aqui novamente escrevemos um construtor que exigirá uma conexão com o banco de dados: +Chamaremos o novo presenter de `EditPresenter` e o salvaremos em `app/Presentation/Edit/EditPresenter.php`. Ele também precisa se conectar ao banco de dados, então escreveremos novamente um construtor que exigirá a conexão com o banco de dados: -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Edit; use Nette; use Nette\Application\UI\Form; @@ -39,92 +39,92 @@ final class EditPresenter extends Nette\Application\UI\Presenter ``` -Formulário para salvar postes .[#toc-form-for-saving-posts] -=========================================================== +Formulário para salvar postagens +================================ -Formulários e componentes já foram cobertos quando estávamos adicionando apoio para comentários. Se você estiver confuso sobre o tópico, vá verificar [como os formulários e componentes funcionam |comments#form-for-commenting] novamente, nós esperaremos aqui ;) +Já explicamos formulários e componentes ao criar comentários. Se ainda não estiver claro, vá revisar [a criação de formulários e componentes |comments#Formulário para comentar], nós esperaremos aqui ;) -Agora adicione este método ao `EditPresenter`: +Agora adicione este método ao presenter `EditPresenter`: -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} protected function createComponentPostForm(): Form { $form = new Form; - $form->addText('title', 'Title:') + $form->addText('title', 'Título:') ->setRequired(); - $form->addTextArea('content', 'Content:') + $form->addTextArea('content', 'Conteúdo:') ->setRequired(); - $form->addSubmit('send', 'Save and publish'); - $form->onSuccess[] = [$this, 'postFormSucceeded']; + $form->addSubmit('send', 'Salvar e publicar'); + $form->onSuccess[] = $this->postFormSucceeded(...); return $form; } ``` -Salvando novos postos do formulário .[#toc-saving-new-post-from-form] -===================================================================== +Salvando nova postagem do formulário +==================================== -Continue adicionando um método de manipulador. +Continuamos adicionando um método que processará os dados do formulário: -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { $post = $this->database ->table('posts') ->insert($data); - $this->flashMessage('Post was published', 'success'); + $this->flashMessage("A postagem foi publicada com sucesso.", 'success'); $this->redirect('Post:show', $post->id); } ``` -Apenas uma explicação rápida: ele pega os valores do formulário, insere-os no banco de dados, cria uma mensagem para o usuário de que o post foi salvo com sucesso e redireciona para a página onde esse post é publicado para que você possa ver como ele se parece. +Apenas uma rápida recapitulação: este método obtém os dados do formulário, insere-os no banco de dados, cria uma mensagem para o usuário sobre o salvamento bem-sucedido da postagem e redireciona para a página com a nova postagem, para que possamos ver imediatamente como ela ficou. -Página para criar um novo posto .[#toc-page-for-creating-a-new-post] -==================================================================== +Página para criar nova postagem +=============================== -Vamos apenas criar o modelo `Edit/create.latte`: +Vamos criar agora o template `Edit/create.latte`: -```latte .{file:app/Presenters/templates/Edit/create.latte} +```latte .{file:app/Presentation/Edit/create.latte} {block content} -<h1>New post</h1> +<h1>Nova postagem</h1> {control postForm} ``` -Tudo já deve estar claro a esta altura. A última linha mostra a forma que estamos prestes a criar. +Tudo já deve estar claro. A última linha renderiza o formulário que acabamos de criar. -Poderíamos também criar um método `renderCreate` correspondente, mas não é necessário. Não precisamos obter nenhum dado do banco de dados e passá-lo para o modelo, para que esse método fique vazio. Nesses casos, o método pode não existir em absoluto. +Poderíamos criar também o método `renderCreate` correspondente, mas não é necessário. Não precisamos obter nenhum dado do banco de dados e passá-lo para o template, então esse método estaria vazio. Nesses casos, o método não precisa existir. -Link para a criação de postos .[#toc-link-for-creating-posts] -============================================================= +Link para criar postagens +========================= -Você provavelmente já sabe como adicionar um link para `EditPresenter` e sua ação `create`. Experimente-o. +Você provavelmente já sabe como adicionar um link para o `EditPresenter` e sua ação `create`. Experimente. -Basta adicionar ao arquivo `app/Presenters/templates/Home/default.latte`: +Basta adicionar ao arquivo `app/Presentation/Home/default.latte`: ```latte -<a n:href="Edit:create">Write new post</a> +<a n:href="Edit:create">Escrever nova postagem</a> ``` -Postos de edição .[#toc-editing-posts] -====================================== +Edição de postagens +=================== -Vamos também acrescentar a capacidade de editar os postos existentes. Será muito simples - já temos `postForm` e podemos utilizá-lo também para edição. +Agora também adicionaremos a opção de editar uma postagem. Será muito simples. Já temos o formulário `postForm` pronto e podemos usá-lo também para edição. -Vamos adicionar uma nova página `edit` ao `EditPresenter`: +Adicionaremos uma nova página `edit` ao presenter `EditPresenter`: -```php .{file:app/Presenters/EditPresenter.php} -public function renderEdit(int $postId): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +public function renderEdit(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { $this->error('Post not found'); @@ -135,26 +135,26 @@ public function renderEdit(int $postId): void } ``` -E crie o modelo `Edit/edit.latte`: +E criaremos outro template `Edit/edit.latte`: -```latte .{file:app/Presenters/templates/Edit/edit.latte} +```latte .{file:app/Presentation/Edit/edit.latte} {block content} -<h1>Edit post</h1> +<h1>Editar postagem</h1> {control postForm} ``` -E atualizar o método `postFormSucceeded`, que poderá ou adicionar um novo post (como faz agora), ou editar os já existentes: +E modificaremos o método `postFormSucceeded`, que será capaz tanto de adicionar um novo artigo (como faz agora) quanto de editar um artigo já existente: -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); - if ($postId) { + if ($id) { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); $post->update($data); } else { @@ -163,26 +163,25 @@ public function postFormSucceeded(array $data): void ->insert($data); } - $this->flashMessage('Post was published', 'success'); + $this->flashMessage('A postagem foi publicada com sucesso.', 'success'); $this->redirect('Post:show', $post->id); } ``` -Quando o parâmetro `postId` é fornecido, isso significa que um post está sendo editado. Nesse caso, verificaremos se o post realmente existe e, se for o caso, atualizá-lo-emos no banco de dados. Se o parâmetro `postId` não for fornecido, significa que um novo post será adicionado. +Se o parâmetro `id` estiver disponível, significa que vamos editar a postagem. Nesse caso, verificaremos se a postagem solicitada realmente existe e, se sim, a atualizaremos no banco de dados. Se o parâmetro `id` não estiver disponível, significa que uma nova postagem deve ser adicionada. -Mas de onde vem o `postId`? É o parâmetro passado para o método `renderEdit`. +Mas de onde vem o parâmetro `id`? É o parâmetro que foi passado para o método `renderEdit`. -Agora você pode adicionar um link para o modelo `app/Presenters/templates/Post/show.latte`: +Agora podemos adicionar um link ao template `app/Presentation/Post/show.latte`: ```latte -<a n:href="Edit:edit $post->id">Edit this post</a> +<a n:href="Edit:edit $post->id">Editar postagem</a> ``` -Sumário .[#toc-summary] -======================= +Resumo +====== -O blog está funcionando, as pessoas estão comentando rapidamente e nós não confiamos mais no Adminer para adicionar novos posts. É totalmente independente e até mesmo pessoas normais podem postar lá. Mas espere, isso provavelmente não está bem, que qualquer pessoa, quero dizer, qualquer pessoa na Internet, possa postar em nosso blog. É necessária alguma forma de autenticação para que somente usuários logados possam postar. Vamos acrescentar isso no próximo capítulo. +O blog agora está funcional, os visitantes estão comentando ativamente e não precisamos mais do Adminer para publicar. O aplicativo é totalmente independente e qualquer pessoa pode adicionar uma nova postagem. Espere um momento, isso provavelmente não está totalmente certo, que qualquer pessoa - e quero dizer realmente qualquer pessoa com acesso à internet - possa adicionar novas postagens. É necessária alguma segurança para que apenas um usuário logado possa adicionar uma nova postagem. Veremos isso no próximo capítulo. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/pt/home-page.texy b/quickstart/pt/home-page.texy index 9d93cca03f..cb59a3af23 100644 --- a/quickstart/pt/home-page.texy +++ b/quickstart/pt/home-page.texy @@ -1,41 +1,41 @@ -Página inicial do Blog +Página inicial do blog ********************** .[perex] -Vamos criar a home page exibindo seus posts recentes. +Agora criaremos a página inicial exibindo as postagens mais recentes. -Antes de começarmos, você deve saber pelo menos algumas noções básicas sobre o padrão de design Model-View-Presenter (similar ao MVC((Model-View-Controller))): +Antes de começarmos, é necessário conhecer pelo menos os fundamentos do padrão de projeto Model-View-Presenter (semelhante ao MVC((Model-View-Controller))): -- **Modelo*** - camada de manipulação de dados. Está completamente separado do resto da aplicação. Ele só se comunica com os apresentadores. +- **Model** - camada que trabalha com dados. É completamente separada do resto da aplicação. Comunica apenas com o presenter. -- **Veja** - uma camada de definição front-end. Ela torna os dados solicitados ao usuário usando modelos. +- **View** - camada de front-end. Renderiza os dados solicitados usando templates e os exibe ao usuário. -- **Presente** (ou Controlador) - uma camada de conexão. O apresentador conecta Modelo e Vista. Trata os pedidos, pede dados ao Modelo e depois os passa para a Vista atual. +- **Presenter** (ou Controller) - camada de conexão. O Presenter conecta o Model e a View. Processa requisições, consulta o Model por dados e os retorna para a View. -No caso de uma aplicação muito simples como nosso blog, a camada Model consistirá apenas em consultas ao próprio banco de dados - não precisamos de nenhum código PHP extra para isso. Precisamos apenas criar camadas de Apresentador e Visualização. Na Nette, cada Apresentador tem suas próprias Views, portanto continuaremos com ambas simultaneamente. +No caso de aplicações simples, como será nosso blog, toda a camada de modelo consistirá apenas em consultas ao banco de dados - para isso, por enquanto, não precisamos de nenhum código extra. Para começar, criaremos apenas os presenters e templates. No Nette, cada presenter tem seus próprios templates, então os criaremos simultaneamente. -Criação do banco de dados com Adminer .[#toc-creating-the-database-with-adminer] -================================================================================ +Criação do banco de dados usando o Adminer +========================================== -Para armazenar os dados, utilizaremos o banco de dados MySQL porque é a escolha mais comum entre os desenvolvedores web. Mas se você não gostar, sinta-se à vontade para usar um banco de dados de sua escolha. +Para armazenar dados, usaremos um banco de dados MySQL, pois é o mais difundido entre os programadores de aplicações web. No entanto, se você não quiser usá-lo, sinta-se à vontade para escolher o banco de dados de sua preferência. -Vamos preparar o banco de dados que irá armazenar nossos posts no blog. Podemos começar de forma muito simples - apenas com uma única tabela para postagens. +Agora prepararemos a estrutura do banco de dados onde os artigos do nosso blog serão armazenados. Começaremos de forma muito simples - criaremos apenas uma tabela para as postagens. -Para criar o banco de dados, podemos baixar o [Adminer |https://www.adminer.org], ou você pode usar outra ferramenta para o gerenciamento do banco de dados. +Para criar o banco de dados, podemos baixar o [Adminer |https://www.adminer.org], ou outra ferramenta de gerenciamento de banco de dados de sua preferência. -Vamos abrir o Adminer e criar um novo banco de dados chamado `quickstart`. +Abra o Adminer e crie um novo banco de dados com o nome `quickstart`. -Crie uma nova tabela chamada `posts` e adicione estas colunas: -- `id` int, clique em autoincrement (AI) +Crie uma nova tabela chamada `posts` com as seguintes colunas: +- `id` int, marque autoincremento (AI) - `title` varchar, comprimento 255 -- `content` texto +- `content` text - `created_at` timestamp -Deveria ser assim: +A estrutura resultante deve ter a seguinte aparência: [* adminer-posts.webp *] @@ -49,9 +49,9 @@ CREATE TABLE `posts` ( ``` .[caution] -É muito importante utilizar o **InnoDB** armazenamento de mesa. Você verá o motivo mais tarde. Por enquanto, basta escolher isso e enviar. Você pode clicar em Salvar agora. +É realmente importante usar o armazenamento **InnoDB**. Em breve mostraremos por quê. Por enquanto, simplesmente selecione-o e clique em salvar. -Tente adicionar alguns exemplos de posts no blog antes de implementarmos a capacidade de adicionar novos posts diretamente de nossa aplicação. +Antes de criarmos a possibilidade de adicionar artigos ao banco de dados através da aplicação, adicione alguns artigos de exemplo ao blog manualmente. ```sql INSERT INTO `posts` (`id`, `title`, `content`, `created_at`) VALUES @@ -61,37 +61,34 @@ INSERT INTO `posts` (`id`, `title`, `content`, `created_at`) VALUES ``` -Conexão com o banco de dados .[#toc-connecting-to-the-database] -=============================================================== +Conexão com o banco de dados +============================ -Agora, quando o banco de dados é criado e temos alguns posts nele, é o momento certo para exibi-los em nossa nova página brilhante. +Agora que o banco de dados foi criado e temos alguns artigos armazenados nele, é hora de exibi-los em nossa bela nova página. -Em primeiro lugar, precisamos informar nossa aplicação sobre qual banco de dados utilizar. A configuração da conexão do banco de dados é armazenada em `config/local.neon`. Defina a conexão DSN((Data Source Name)) e suas credenciais. Deve ter este aspecto: +Primeiro, precisamos dizer à aplicação qual banco de dados usar. A conexão com o banco de dados é configurada no arquivo `config/common.neon` usando DSN((Data Source Name)) e credenciais de login. Deve ter a seguinte aparência: -```neon .{file:config/local.neon} +```neon .{file:config/common.neon} database: dsn: 'mysql:host=127.0.0.1;dbname=quickstart' - user: *enter user name* - password: *enter password here* + user: *insira o nome de usuário aqui* + password: *insira a senha do banco de dados aqui* ``` .[note] -Esteja atento à indentação durante a edição deste arquivo. [O formato NEON |neon:format] aceita ambos os espaços e abas, mas não ambos juntos! O arquivo de configuração no Projeto Web utiliza abas como padrão. +Ao editar este arquivo, preste atenção à indentação das linhas. O formato [NEON |neon:format] aceita tanto indentação por espaços quanto por tabulações, mas não ambos ao mesmo tempo. O arquivo de configuração padrão no Web Project usa tabulações. -Toda a configuração inclusive é armazenada em `config/` nos arquivos `common.neon` e `local.neon`. O arquivo `common.neon` contém a configuração global da aplicação e `local.neon` contém apenas os parâmetros específicos do ambiente (por exemplo, a diferença entre servidor de desenvolvimento e de produção). +Passando a conexão do banco de dados +==================================== -Injetando a conexão do banco de dados .[#toc-injecting-the-database-connection] -=============================================================================== +O presenter `HomePresenter`, que cuidará da listagem dos artigos, precisa de uma conexão com o banco de dados. Para obtê-la, usaremos um construtor, que terá a seguinte aparência: -O apresentador `HomePresenter`, que listará os artigos, precisa de uma conexão de banco de dados. Para recebê-la, escreva um construtor como este: - -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; -use Nette\Application\UI\Form; final class HomePresenter extends Nette\Application\UI\Presenter { @@ -105,12 +102,12 @@ final class HomePresenter extends Nette\Application\UI\Presenter ``` -Carregamento de postes a partir do banco de dados .[#toc-loading-posts-from-the-database] -========================================================================================= +Carregando postagens do banco de dados +====================================== -Agora vamos buscar os posts no banco de dados e passá-los para o template, que então renderizará o código HTML. É para isso que serve o chamado método *render*: +Agora carregaremos as postagens do banco de dados e as enviaremos para o template, que as renderizará como código HTML. Para isso, existe o chamado método *render*: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} public function renderDefault(): void { $this->template->posts = $this->database @@ -120,41 +117,41 @@ public function renderDefault(): void } ``` -O apresentador agora tem um método de renderização `renderDefault()` que passa dados para uma visualização chamada `default`. Os modelos do apresentador podem ser encontrados em `app/Presenters/templates/{PresenterName}/{viewName}.latte`, então neste caso o modelo será localizado em `app/Presenters/templates/Home/default.latte`. No modelo, uma variável chamada `$posts` está agora disponível, que contém os posts do banco de dados. +O presenter agora contém um método de renderização `renderDefault()`, que passa dados do banco de dados para o template (View). Os templates estão localizados em `app/Presentation/{PresenterName}/{viewName}.latte`, então neste caso o template está localizado em `app/Presentation/Home/default.latte`. No template, a variável `$posts` estará agora disponível, contendo as postagens obtidas do banco de dados. -Modelo .[#toc-template] -======================= +Template +======== -Há um modelo genérico para toda a página (chamado *layout*, com cabeçalho, folhas de estilo, rodapé, ...) e depois modelos específicos para cada visualização (por exemplo, para exibir a lista de posts do blog), que podem substituir algumas das peças do modelo de layout. +Para todo o site, temos um template principal (que se chama *layout*, contém cabeçalho, estilos, rodapé,...) e, em seguida, templates específicos para cada view (por exemplo, para exibir postagens no blog), que podem sobrescrever algumas partes do template principal. -Por padrão, o modelo de layout está localizado em `templates/@layout.latte`, que contém: +Por padrão, o template de layout está localizado em `app/Presentation/@layout.latte` e contém: -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... {include content} ... ``` -`{include content}` insere um bloco chamado `content` no modelo principal. Você pode defini-lo nos gabaritos de cada vista. Neste caso, editaremos o arquivo `Home/default.latte` desta forma: +A escrita `{include content}` insere no template principal um bloco chamado `content`. Definiremos isso nos templates das views individuais. No nosso caso, modificaremos o arquivo `Home/default.latte` da seguinte forma: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - Hello World + Olá Mundo {/block} ``` -Ele define o [bloco |latte:tags#block]*content*, que será inserido no layout. Se você atualizar o navegador, verá uma página com o texto "Hello world" (em código fonte também com cabeçalho e rodapé de página HTML definidos em `@layout.latte`). +Com isso, definimos o [bloco |latte:tags#block] *content*, que será inserido no layout principal. Se atualizarmos o navegador novamente, veremos a página com o texto "Olá Mundo" (no código-fonte também com o cabeçalho e rodapé HTML definidos em `@layout.latte`). -Vamos exibir os posts do blog - editaremos o modelo desta forma: +Vamos exibir as postagens do blog - modificaremos o template da seguinte forma: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - <h1>My blog</h1> + <h1>Meu blog</h1> {foreach $posts as $post} <div class="post"> - <div class="date">{$post->created_at|date:'j. n. Y'}</div> + <div class="date">{$post->created_at|date:'F j, Y'}</div> <h2>{$post->title}</h2> @@ -164,42 +161,41 @@ Vamos exibir os posts do blog - editaremos o modelo desta forma: {/block} ``` -Se você atualizar seu navegador, você verá a lista de seus posts no blog. A lista não é muito chique ou colorida, portanto, sinta-se à vontade para adicionar algum [CSS brilhante |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] a `www/css/style.css` e vinculá-lo em um layout: +Se atualizarmos o navegador, veremos a lista de todas as postagens. A lista ainda não está muito bonita nem colorida, então podemos adicionar alguns [estilos CSS |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] ao arquivo `www/css/style.css` e vinculá-lo no layout: -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <link rel="stylesheet" href="{$basePath}/css/style.css"> </head> ... ``` -A tag `{foreach}` itera todos os posts passados para a variável `$posts` e exibe um pedaço de código HTML para cada post. Assim como um código PHP faria. +A tag `{foreach}` itera sobre todas as postagens que passamos para o template na variável `$posts`, e para cada uma renderiza o pedaço de HTML correspondente. Comporta-se exatamente como o código PHP. -A coisa `|date` é chamada de filtro. Os filtros são usados para formatar a saída. Este filtro particular converte uma data (por exemplo, `2013-04-12`) para sua forma mais legível (`12. 4. 2013`). O filtro `|truncate` truncata a string no comprimento máximo especificado, e adiciona uma elipse à extremidade se a string for truncada. Como isto é uma prévia, não há sentido em exibir o conteúdo completo do artigo. Outros filtros padrão [podem ser encontrados na documentação |latte:filters] ou você pode criar o seu próprio, se necessário. +Chamamos a escrita `|date:` de filtro. Os filtros são destinados a formatar a saída. Este filtro específico converte a data (por exemplo, `2013-04-12`) para sua forma mais legível (`Abril 12, 2013`). O filtro `|truncate` corta a string no comprimento máximo especificado e, caso a string seja encurtada, adiciona reticências no final. Como se trata de uma prévia, não faz sentido exibir todo o conteúdo do artigo. Outros filtros padrão [podem ser encontrados na documentação |latte:filters] ou podemos criar os nossos próprios, quando necessário. -Mais uma coisa. Podemos tornar o código um pouco mais curto e, portanto, mais simples. Podemos substituir as *etiquetas* por *n:atributos* desta forma: +Mais uma coisa. Podemos encurtar e simplificar o código anterior. Conseguimos isso substituindo as *tags Latte* por *n:atributos*: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - <h1>My blog</h1> + <h1>Meu blog</h1> <div n:foreach="$posts as $post" class="post"> <div class="date">{$post->created_at|date:'F j, Y'}</div> <h2>{$post->title}</h2> - <div>{$post->content}</div> + <div>{$post->content|truncate:256}</div> </div> {/block} ``` -O `n:foreach`, simplesmente envolve o *div* com um *para cada* bloco (ele faz exatamente a mesma coisa que o bloco de código anterior). +O atributo `n:foreach` envolve o bloco *div* com um *foreach* (funciona exatamente da mesma forma que o código anterior). -Sumário .[#toc-summary] -======================= +Resumo +====== -Temos um banco de dados MySQL muito simples, com alguns posts em blogs. O aplicativo se conecta ao banco de dados e exibe uma lista simples dos posts. +Agora temos um banco de dados MySQL muito simples com algumas postagens. A aplicação se conecta a este banco de dados e exibe uma lista simples dessas postagens no template. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/pt/model.texy b/quickstart/pt/model.texy index 2f010ab7e7..317f214f51 100644 --- a/quickstart/pt/model.texy +++ b/quickstart/pt/model.texy @@ -1,13 +1,13 @@ Modelo ****** -À medida que nossa aplicação cresce, logo descobrimos que precisamos realizar operações similares de banco de dados em vários locais e em vários apresentadores, por exemplo, adquirindo os mais novos artigos publicados. Se melhorarmos nossa aplicação adicionando uma bandeira aos artigos para indicar um estado de trabalho em andamento, devemos também passar por todos os locais em nossa aplicação e adicionar uma cláusula de localização para garantir que apenas os artigos acabados sejam selecionados. +À medida que a aplicação cresce, logo descobriremos que em diferentes lugares, em diferentes presenters, precisamos realizar operações semelhantes com o banco de dados. Por exemplo, obter os artigos publicados mais recentemente. Se melhorarmos a aplicação, por exemplo, adicionando um sinalizador aos artigos para indicar se estão em rascunho, teremos que percorrer todos os lugares na aplicação onde os artigos são obtidos do banco de dados e adicionar uma condição where para selecionar apenas os artigos não rascunhados. -Neste ponto, o trabalho direto com o banco de dados torna-se insuficiente e será mais inteligente nos ajudar com uma nova função que retorna artigos publicados. E quando adicionamos outra cláusula posteriormente (por exemplo, para não exibir artigos com uma data futura), editamos nosso código apenas em um lugar. +Nesse momento, o trabalho direto com o banco de dados se torna insuficiente e será mais conveniente usar uma nova função que nos retornará os artigos publicados. E quando adicionarmos outra condição posteriormente, por exemplo, que artigos com data futura não devem ser exibidos, modificaremos o código em apenas um lugar. -Colocaremos a função na classe `PostFacade` e a chamaremos de `getPublicArticles()`. +Colocaremos a função, por exemplo, na classe `PostFacade` e a chamaremos de `getPublicArticles()`. -Vamos criar nossa classe modelo `PostFacade` no diretório `app/Model/` para cuidar de nossos artigos: +No diretório `app/Model/`, criaremos nossa classe de modelo `PostFacade`, que cuidará dos artigos: ```php .{file:app/Model/PostFacade.php} <?php @@ -32,13 +32,13 @@ final class PostFacade } ``` -Na classe passamos no Explorer do banco de dados:[api:Nette\Database\Explorer]. Isto irá tirar proveito do poder do [recipiente DI |dependency-injection:passing-dependencies]. +Na classe, solicitaremos a passagem do Database Explorer:[api:Nette\Database\Explorer] através do construtor. Aproveitaremos assim o poder do [contêiner de DI|dependency-injection:passing-dependencies]. -Mudaremos para `HomePresenter` que editaremos para nos livrarmos da dependência em `Nette\Database\Explorer`, substituindo-a por uma nova dependência de nossa nova classe. +Mudaremos para o `HomePresenter`, que modificaremos para nos livrarmos da dependência de `Nette\Database\Explorer` e a substituiremos pela nova dependência de nossa nova classe. -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use App\Model\PostFacade; use Nette; @@ -59,10 +59,9 @@ final class HomePresenter extends Nette\Application\UI\Presenter } ``` -Na seção de uso, estamos usando `App\Model\PostFacade`, assim podemos encurtar o código PHP para `PostFacade`. Solicitamos este objeto no construtor, escrevemo-lo na propriedade `$facade` e o usamos no método renderDefault. +Na seção use, temos `App\Model\PostFacade`, então podemos encurtar a escrita no código PHP para `PostFacade`. Solicitaremos este objeto no construtor, o escreveremos na propriedade `$facade` e o usaremos no método renderDefault. -O último passo restante é ensinar o recipiente DI a produzir este objeto. Isto geralmente é feito adicionando um ponto no arquivo `config/services.neon` na seção `services`, dando o nome completo da classe e os parâmetros do construtor. -Isto o registra, por assim dizer, e o objeto é então chamado de **service***. Graças a alguma mágica chamada [autowiring |dependency-injection:autowiring], normalmente não precisamos especificar os parâmetros do construtor porque DI os reconhecerá e os passará automaticamente. Assim, seria suficiente apenas fornecer o nome da classe: +Resta o último passo, que é ensinar o contêiner de DI a produzir este objeto. Isso geralmente é feito adicionando um marcador ao arquivo `config/services.neon` na seção `services`, indicando o nome completo da classe e os parâmetros do construtor. Assim, o registramos e o objeto é então chamado de **serviço**. Graças à mágica chamada [autowiring |dependency-injection:autowiring], geralmente não precisamos especificar os parâmetros do construtor, pois o DI os reconhece e os passa automaticamente. Bastaria, portanto, indicar apenas o nome da classe: ```neon .{file:config/services.neon} ... @@ -71,16 +70,15 @@ services: - App\Model\PostFacade ``` -Entretanto, você também não precisa acrescentar esta linha. Na seção `search` no início de `services.neon` está definido que todas as classes que terminem com `-Facade` ou `-Factory` serão pesquisadas automaticamente por DI, o que também é o caso de `PostFacade`. +No entanto, você nem precisa adicionar esta linha. Na seção `search` no início de `services.neon`, está definido que todas as classes terminadas com a palavra `-Facade` ou `-Factory` serão encontradas automaticamente pelo DI, o que também é o caso de `PostFacade`. -Sumário .[#toc-summary] -======================= +Resumo +====== -A classe `PostFacade` pede `Nette\Database\Explorer` em um construtor e como esta classe está registrada no container DI, o container cria esta instância e a passa. DI desta forma cria uma instância `PostFacade` para nós e a passa em uma construtora para a classe HomePresenter que a solicitou. Uma espécie de boneca Matryoshka de código :) Todos os componentes só pedem o que precisam e não se importam onde e como ele é criado. A criação é feita por um recipiente DI. +A classe `PostFacade` solicita a passagem de `Nette\Database\Explorer` em seu construtor e, como esta classe está registrada no contêiner de DI, o contêiner cria esta instância e a passa. O DI cria assim para nós a instância de `PostFacade` e a passa no construtor para a classe HomePresenter, que a solicitou. É como uma matriosca. :) Todos apenas dizem o que querem e não se preocupam onde e como algo é criado. O contêiner de DI cuida da criação. .[note] -Aqui você pode ler mais sobre [injeção de dependência |dependency-injection:introduction], e sobre [configuração |nette:configuring]. +Aqui você pode ler mais sobre [injeção de dependência |dependency-injection:introduction] e [configuração |nette:configuring]. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/pt/single-post.texy b/quickstart/pt/single-post.texy index d991cfed1b..22215db410 100644 --- a/quickstart/pt/single-post.texy +++ b/quickstart/pt/single-post.texy @@ -1,17 +1,17 @@ -Página única de postagem -************************ +Página com a postagem +********************* .[perex] -Vamos adicionar outra página ao nosso blog, que exibirá o conteúdo de um determinado post do blog. +Agora criaremos outra página do blog, que exibirá uma postagem específica. -Precisamos criar um novo método de renderização, que irá buscar um post específico no blog e passá-lo para o modelo. Ter esta visão em `HomePresenter` não é legal porque se trata de um post de blog, não da página inicial. Então, vamos criar uma nova classe `PostPresenter` e colocá-la em `app/Presenters`. Ela precisará de uma conexão de banco de dados, então coloque o código *injeção de banco de dados* lá novamente. +Precisamos criar um novo método de renderização que obterá um artigo específico e o passará para o template. Ter este método no `HomePresenter` não é muito elegante, pois estamos falando de um artigo e não da página inicial. Portanto, criaremos um `PostPresenter` em `app/Presentation/Post/`. Este presenter também precisa se conectar ao banco de dados, então escreveremos novamente um construtor que exigirá a conexão com o banco de dados. -O `PostPresenter` deve se parecer com isto: +O `PostPresenter` poderia, portanto, ter a seguinte aparência: -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Post; use Nette; use Nette\Application\UI\Form; @@ -23,50 +23,50 @@ final class PostPresenter extends Nette\Application\UI\Presenter ) { } - public function renderShow(int $postId): void + public function renderShow(int $id): void { $this->template->post = $this->database ->table('posts') - ->get($postId); + ->get($id); } } ``` -Temos que definir um namespaces correto `App\Presenters` para nosso apresentador. Isso depende do [mapeamento do apresentador |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7]. +Não devemos esquecer de indicar o namespace correto `App\Presentation\Post`, que está sujeito à configuração de [mapeamento de presenters |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7]. -O método `renderShow` requer um argumento - a identificação do posto a ser exibido. Em seguida, ele carrega o posto do banco de dados e passa o resultado para o modelo. +O método `renderShow` requer um argumento - o ID de um artigo específico que deve ser exibido. Em seguida, ele carrega este artigo do banco de dados e o passa para o template. -No modelo `Home/default.latte`, adicionamos um link para a ação `Post:show`: +No template `Home/default.latte`, inseriremos um link para a ação `Post:show`. -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a href="{link Post:show $post->id}">{$post->title}</a></h2> ... ``` -A tag `{link}` gera um endereço URL que aponta para a ação `Post:show`. Esta tag também encaminha a identificação do correio como argumento. +A tag `{link}` gera um endereço URL que aponta para a ação `Post:show`. Também passa o ID da postagem como argumento. -O mesmo podemos escrever em breve usando n:attribute: +Podemos escrever o mesmo de forma abreviada usando um n:atributo: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a n:href="Post:show $post->id">{$post->title}</a></h2> ... ``` -O atributo `n:href` é similar à tag `{link}`. +O atributo `n:href` é análogo à tag `{link}`. -O modelo para a ação `Post:show` ainda não existe. Podemos abrir um link para este post. [Tracy |tracy:] mostrará um erro, porque `Post/show.latte` ainda não existe. Se você vir qualquer outro relatório de erro, provavelmente terá que ligar o mod_rewrite em seu servidor web. +No entanto, ainda não existe um template para a ação `Post:show`. Podemos tentar abrir o link para esta postagem. O [Tracy |tracy:] exibirá um erro porque o template `Post/show.latte` ainda não existe. Se você vir outra mensagem de erro, provavelmente precisará habilitar o `mod_rewrite` no servidor web. -Por isso, criaremos `Post/show.latte` com este conteúdo: +Criaremos, portanto, o template `Post/show.latte` com este conteúdo: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} {block content} -<p><a n:href="Home:default">← back to posts list</a></p> +<p><a n:href="Home:default">← voltar para a lista de postagens</a></p> <div class="date">{$post->created_at|date:'F j, Y'}</div> @@ -75,51 +75,50 @@ Por isso, criaremos `Post/show.latte` com este conteúdo: <div class="post">{$post->content}</div> ``` -Vamos dar uma olhada nas partes individuais. +Agora vamos percorrer as partes individuais do template. -A primeira linha inicia a definição de um bloco *nomeado* chamado "conteúdo", que vimos anteriormente. Ele será exibido em um modelo de *layout*. Como você pode ver, a etiqueta final `{/block}` está faltando. Ela é opcional. +A primeira linha começa a definição do bloco chamado "content", assim como na página inicial. Este bloco será novamente exibido no template principal. Como você pode ver, falta a tag final `{/block}`. Ela é, na verdade, opcional. -A segunda linha fornece um backlink para a lista de postagens do blog, para que o usuário possa navegar suavemente para frente e para trás em nosso blog. Usamos novamente o atributo `n:href`, portanto a Nette se encarregará de gerar a URL para nós. O link aponta para a ação `default` do apresentador `Home` (você também poderia escrever `n:href="Home:"`, pois a ação `default` pode ser omitida). +Na linha seguinte, há um link de volta para a lista de artigos do blog, para que o usuário possa navegar facilmente entre a lista de artigos e um artigo específico. Como estamos usando o atributo `n:href`, o Nette cuidará da geração dos links. O link aponta para a ação `default` do presenter `Home` (podemos escrever também `n:href="Home:"`, pois a ação chamada `default` pode ser omitida, ela é completada automaticamente). -A terceira linha formata o carimbo de tempo de publicação com um filtro, como já sabemos. +A terceira linha formata a exibição da data usando o filtro que já conhecemos. -A quarta linha exibe o *título* do post do blog como um `<h1>` título. Há uma parte que você pode não estar familiarizado, e que é `n:block="title"`. Você pode adivinhar o que ela faz? Se você leu cuidadosamente as partes anteriores, mencionamos `n: attributes`. Este é outro exemplo. É equivalente a: +A quarta linha exibe o *título* do blog na tag HTML `<h1>`. Esta tag contém um atributo que talvez você não conheça (`n:block="title"`). Consegue adivinhar o que ele faz? Se você leu a parte anterior com atenção, já sabe que se trata de um `n:atributo`. Este é outro exemplo deles, que é equivalente a: ```latte {block title}<h1>{$post->title}</h1>{/block} ``` -Em palavras simples, ele *redefine* um bloco chamado `title`. O bloco é definido no modelo *layout* (`/app/Presenters/templates/@layout.latte:11`) e, como no OOP, ele é anulado aqui. Portanto, a página é `<title>` conterá o título do post exibido. Anulamos o título da página e tudo o que precisávamos era `n:block="title"`. Ótimo, não é? +Simplificando, este bloco redefine o bloco chamado `title`. Este bloco já está definido no template principal *layout* (`/app/Presentation/@layout.latte:11`) e, assim como na sobreposição de métodos em OOP, este bloco no template principal é sobreposto da mesma forma. Portanto, o `<title>` da página agora contém o título da postagem exibida, e bastou usar apenas um simples atributo `n:block="title"`. Ótimo, não é? -A quinta e última linha do modelo exibe o conteúdo completo do seu post. +A quinta e última linha do template exibe todo o conteúdo de uma postagem específica. -Verificação da identificação do posto .[#toc-checking-post-id] -============================================================== +Verificação do ID da postagem +============================= -O que acontece se alguém altera a URL e insere `postId` que não existe? Devemos fornecer ao usuário um bom erro de "página não encontrada". Vamos atualizar o método de renderização em `PostPresenter`: +O que acontece se alguém alterar o ID na URL e inserir algum `id` inexistente? Devemos oferecer ao usuário um erro agradável do tipo "página não encontrada". Modificaremos, portanto, um pouco o método de renderização no `PostPresenter`: -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { - $this->error('Post not found'); + $this->error('Página não encontrada'); } $this->template->post = $post; } ``` -Se o correio não puder ser encontrado, ligando para `$this->error(...)`, aparecerá uma página 404 com uma mensagem agradável e compreensível. Note que em seu ambiente de desenvolvimento (em seu laptop), você não verá a página de erro. Em vez disso, Tracy mostrará a exceção com todos os detalhes, o que é bastante conveniente para o desenvolvimento. Você pode verificar os dois modos, basta alterar o valor passado para `setDebugMode` em `Bootstrap.php`. +Se a postagem não puder ser encontrada, chamando `$this->error(...)` exibiremos uma página de erro 404 com uma mensagem compreensível. Atenção: no modo de desenvolvimento (localhost), você não verá esta página de erro. Em vez disso, o Tracy aparecerá com detalhes sobre a exceção, o que é bastante vantajoso para o desenvolvimento. Se quisermos exibir ambos os modos, basta alterar o argumento do método `setDebugMode` no arquivo `Bootstrap.php`. -Sumário .[#toc-summary] -======================= +Resumo +====== -Temos um banco de dados com posts em blogs e um aplicativo web com duas visualizações - o primeiro exibe o resumo de todos os posts recentes e o segundo exibe um post específico. +Temos um banco de dados com postagens e uma aplicação web que tem duas views - a primeira exibe uma visão geral de todas as postagens e a segunda exibe uma postagem específica. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/ro/@home.texy b/quickstart/ro/@home.texy index 0e3bdc79e4..b3123354ed 100644 --- a/quickstart/ro/@home.texy +++ b/quickstart/ro/@home.texy @@ -1,119 +1,119 @@ -Creează-ți prima aplicație! -*************************** +Scriem prima aplicație! +*********************** .[perex] -Faceți cunoștință cu Nette Framework în timp ce creați un blog simplu cu comentarii. Să începem! +Să cunoaștem împreună Nette Framework, creând un blog simplu cu comentarii. Să începem! -După primele două capitole, veți avea propriul blog funcțional și veți fi gata să vă publicați postările minunate, deși funcțiile vor fi destul de limitate după finalizarea acestor două capitole. Pentru ca lucrurile să fie mai frumoase pentru utilizatori, ar trebui să citiți și următoarele capitole și să continuați să vă îmbunătățiți aplicația. +Deja după primele două capitole vom avea propriul nostru blog funcțional și vom putea publica postările noastre grozave, deși funcțiile vor fi deocamdată destul de limitate. Ar trebui să citiți și capitolele următoare, unde vom programa adăugarea de comentarii, editarea articolelor și, în final, vom securiza blogul. .[tip] -Acest tutorial pornește de la premisa că ați finalizat documentul de [instalare |nette:installation] și că ați configurat cu succes uneltele. +Acest ghid presupune că ați citit pagina [Instalare |nette:installation] și ați pregătit cu succes instrumentele necesare. De asemenea, presupune că înțelegeți [programarea orientată pe obiecte în PHP |nette:introduction-to-object-oriented-programming]. -Vă rugăm să utilizați PHP 8.0 sau o versiune ulterioară. Puteți găsi aplicația completă [pe GitHub |https://github.com/nette-examples/quickstart/tree/v4.0]. +Vă rugăm să utilizați PHP 8.1 sau o versiune mai nouă. Aplicația completă o găsiți [pe GitHub |https://github.com/nette-examples/quickstart/tree/v4.0]. -Pagina de bun venit .[#toc-the-welcome-page] -============================================ +Pagina de bun venit +=================== -Să începem prin a crea un nou proiect în directorul `nette-blog`: +Să începem prin crearea unui nou proiect în directorul `nette-blog`: ```shell composer create-project nette/web-project nette-blog ``` -În acest moment, pagina de bun venit a Proiectului Web ar trebui să ruleze. Încercați-o deschizând browserul și accesând următoarea adresă URL: +În acest moment, pagina de start a Web Project ar trebui să funcționeze deja. Vom testa acest lucru deschizând browserul la următoarea adresă URL: ``` http://localhost/nette-blog/www/ ``` -și ar trebui să vedeți pagina de bun venit a Nette Framework: +și vom vedea pagina de start a Nette Framework: [* qs-welcome.webp .{url: http://localhost/nette-blog/www/} *] -Aplicația funcționează și acum puteți începe să faceți modificări la ea. +Aplicația funcționează și puteți începe să faceți modificări. .[note] -Dacă aveți o problemă, [încercați aceste câteva sfaturi |nette:troubleshooting#Nette Is Not Working, White Page Is Displayed]. +Dacă a apărut o problemă, [încercați aceste câteva sfaturi |nette:troubleshooting#Nette nu funcționează se afișează o pagină albă]. -Conținutul proiectului web .[#toc-web-project-s-content] -======================================================== +Conținutul Web Project +====================== -Proiectul Web are următoarea structură: +Web Project are următoarea structură: /--pre <b>nette-blog/</b> -├── <b>app/</b> ← application directory -│ ├── <b>Presenters/</b> ← presenter classes -│ │ └── <b>templates/</b>← templates -│ ├── <b>Router/</b> ← configuration of URL addresses -│ └── <b>Bootstrap.php</b> ← booting class Bootstrap -├── <b>bin/</b> ← scripts for the command line -├── <b>config/</b> ← configuration files -├── <b>log/</b> ← error logs -├── <b>temp/</b> ← temporary files, cache, … -├── <b>vendor/</b> ← libraries installed by Composer -│ └── <b>autoload.php</b> ← autoloading of libraries installed by Composer -└── <b>www/</b> ← public folder - the only place accessible from browser - └── <b>index.php</b> ← initial file that launches the application +├── <b>app/</b> ← directorul cu aplicația +│ ├── <b>Core/</b> ← clase de bază necesare pentru funcționare +│ ├── <b>Presentation/</b> ← presenteri, șabloane & co. +│ │ └── <b>Home/</b> ← directorul presenterului Home +│ └── <b>Bootstrap.php</b> ← clasa de pornire Bootstrap +├── <b>assets/</b> ← active brute (SCSS, TypeScript, imagini sursă) +├── <b>bin/</b> ← scripturi rulate din linia de comandă +├── <b>config/</b> ← fișiere de configurare +├── <b>log/</b> ← logarea erorilor +├── <b>temp/</b> ← fișiere temporare, cache, … +├── <b>vendor/</b> ← biblioteci instalate de Composer +│ └── <b>autoload.php</b> ← autoloading pentru toate pachetele instalate +└── <b>www/</b> ← directorul public - singurul accesibil din browser + ├── <b>assets/</b> ← fișiere statice compilate (CSS, JS, imagini, ...) + └── <b>index.php</b> ← fișierul inițial prin care se lansează aplicația \-- -Directorul `www` este destinat să stocheze imagini, JavaScript, CSS și alte fișiere disponibile publicului. Acesta este singurul director direct accesibil din browser, așa că puteți îndrepta directorul rădăcină al serverului web aici (îl puteți configura în Apache, dar să o facem mai târziu, deoarece nu este important acum). +Directorul `www/` este destinat stocării imaginilor, fișierelor JavaScript, stilurilor CSS și altor fișiere accesibile public. Numai acest director este accesibil de pe internet, așa că setați directorul rădăcină al aplicației dvs. astfel încât să indice aici (puteți seta acest lucru în configurația Apache sau nginx, dar să facem asta mai târziu, acum nu este important). -Cel mai important director pentru dumneavoastră este `app/`. Acolo găsiți fișierul `Bootstrap.php`, în interiorul căruia se află o clasă care încarcă cadrul și configurează aplicația. Aceasta activează [încărcarea automată |robot-loader:] și configurează [depanatorul |tracy:] și [rutele |application:routing]. +Cel mai important folder pentru noi este `app/`. Aici găsim fișierul `Bootstrap.php`, în care se află clasa care servește la încărcarea întregului framework și la configurarea aplicației. Aici se activează [autoloading-ul |robot-loader:], se setează [debugger-ul |tracy:] și [rutele |application:routing]. -Curățați .[#toc-cleanup] -======================== +Curățenie +========= -Proiectul Web conține o pagină de bun venit, pe care o putem elimina - nu ezitați să ștergeți fișierul `app/Presenters/templates/Home/default.latte` și să îl înlocuiți cu textul "Hello world!". +Web Project conține o pagină de start, pe care o vom șterge înainte de a începe să programăm ceva. Fără griji, deci, înlocuim conținutul fișierului `app/Presentation/Home/default.latte` cu "Hello world!". [* qs-hello.webp .{url:-} *] -Tracy (Depanator) .[#toc-tracy-debugger] -======================================== +Tracy (debugger) +================ -Un instrument extrem de important pentru dezvoltare este [un depanator numit Tracy |tracy:]. Încercați să faceți câteva erori în fișierul `app/Presenters/HomePresenter.php` (de exemplu, eliminați o paranteză curly bracket din definiția clasei HomePresenter) și vedeți ce se întâmplă. Va apărea o pagină pe ecran roșu cu o descriere inteligibilă a erorii. +Un instrument extrem de important pentru dezvoltare este [instrumentul de depanare Tracy |tracy:]. Încercați să provocați o eroare în fișierul `app/Presentation/Home/HomePresenter.php` (de exemplu, eliminând acolada din definiția clasei HomePresenter) și vedeți ce se întâmplă. Va apărea o pagină de notificare care descrie eroarea în mod clar. -[* qs-tracy.webp .{url:-}(debugger screen) *] +[* qs-tracy.avif .{url:-}(ecran debugger) *] -Tracy vă va ajuta în mod semnificativ în timpul vânătorii de erori. Rețineți, de asemenea, bara Tracy plutitoare din colțul din dreapta jos, care vă informează cu privire la datele importante din timpul execuției. +Tracy ne va ajuta enorm atunci când vom căuta erori în aplicație. De asemenea, observați bara Tracy plutitoare în colțul din dreapta jos al ecranului, care conține informații din timpul rulării aplicației. [* qs-tracybar.webp .{url:-} *] -În modul de producție, Tracy este, bineînțeles, dezactivat și nu dezvăluie nicio informație sensibilă. În schimb, toate erorile sunt salvate în directorul `log/`. Încercați doar. În `app/Bootstrap.php`, găsiți următoarea bucată de cod, decomentați linia și schimbați parametrul de apelare a metodei în `false`, astfel încât să arate astfel: +În modul de producție, Tracy este, desigur, dezactivată și nu afișează nicio informație sensibilă. Toate erorile sunt în acest caz stocate în folderul `log/`. Să încercăm acest lucru. În fișierul `app/Bootstrap.php`, decomentăm următoarea linie și schimbăm parametrul apelului la `false`, astfel încât codul să arate astfel: ```php .{file:app/Bootstrap.php} ... -$configurator->setDebugMode(false); -$configurator->enableTracy(__DIR__ . '/../log'); +$this->configurator->setDebugMode(false); ... ``` -După reîmprospătarea paginii web, pagina cu ecran roșu va fi înlocuită cu mesajul ușor de utilizat: +După reîmprospătarea paginii, nu vom mai vedea Tracy. În locul ei, se va afișa un mesaj prietenos pentru utilizator: -[* qs-fatal.webp .{url:-}(error screen) *] +[* qs-fatal.webp .{url:-}(ecran eroare) *] -Acum, căutați în directorul `log/`. Acolo puteți găsi jurnalul de erori (în fișierul exception.log) și, de asemenea, pagina cu mesajul de eroare (salvată într-un fișier HTML cu un nume care începe cu `exception`). +Acum să ne uităm în directorul `log/`. Aici (în fișierul `exception.log`) vom găsi eroarea logată și, de asemenea, pagina deja cunoscută cu mesajul de eroare (stocată ca fișier HTML cu un nume care începe cu `exception-`). -Comentați din nou linia `// $configurator->setDebugMode(false);`. Tracy activează automat modul de dezvoltare în mediul `localhost` și îl dezactivează în altă parte. +Comentăm din nou linia `// $configurator->setDebugMode(false);`. Tracy activează automat modul de dezvoltare pe localhost și îl dezactivează peste tot altundeva. -Acum, putem remedia eroarea și putem continua proiectarea aplicației noastre. +Putem repara eroarea pe care am creat-o și continua să scriem aplicația. -Trimiteți mulțumiri .[#toc-send-thanks] -======================================= +Trimiteți mulțumiri +=================== -Vă vom arăta un truc care îi va face fericiți pe autorii open source. Puteți să acordați cu ușurință o stea pe GitHub bibliotecilor pe care le folosește proiectul dumneavoastră. Trebuie doar să executați: +Vă vom arăta un truc care îi va încânta pe autorii open source. Puteți da cu ușurință o stea pe GitHub bibliotecilor pe care le utilizează proiectul dvs. Este suficient să rulați: ```shell composer thanks ``` -Încearcă-l! +Încercați! {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/ro/@left-menu.texy b/quickstart/ro/@left-menu.texy index 0a2372dc6a..b0fb416dca 100644 --- a/quickstart/ro/@left-menu.texy +++ b/quickstart/ro/@left-menu.texy @@ -1,8 +1,8 @@ Tutorial ******** -- [Creează-ți prima aplicație! |@home] -- [Pagina principală a blogului |home-page] -- [Pagină cu o singură postare |single-post] +- [Scriem prima aplicație! |@home] +- [Pagina de start a blogului |home-page] +- [Pagina cu postarea |single-post] - [Comentarii |comments] - [Crearea și editarea postărilor |creating-posts] - [Model |Model] diff --git a/quickstart/ro/@meta.texy b/quickstart/ro/@meta.texy new file mode 100644 index 0000000000..6d5003de70 --- /dev/null +++ b/quickstart/ro/@meta.texy @@ -0,0 +1 @@ +{{sitename: Scriem prima aplicație!}} diff --git a/quickstart/ro/authentication.texy b/quickstart/ro/authentication.texy index 4c21b537ae..e1827a7ad5 100644 --- a/quickstart/ro/authentication.texy +++ b/quickstart/ro/authentication.texy @@ -1,36 +1,36 @@ Autentificare ************* -Nette vă oferă îndrumări cu privire la modul în care să programați autentificarea pe pagina dvs., dar nu vă obligă să faceți acest lucru într-un anumit mod. Implementarea depinde de dumneavoastră. Nette are o interfață `Nette\Security\Authenticator` care vă obligă să implementați doar o singură metodă numită `authenticate`, care găsește utilizatorul în orice mod doriți. +Nette oferă o modalitate de a programa autentificarea pe paginile noastre, dar nu ne impune nimic. Implementarea depinde în totalitate de noi. Nette include interfața `Nette\Security\Authenticator`, care necesită doar o singură metodă `authenticate`, care verifică utilizatorul în orice mod dorim. -Există mai multe moduri în care un utilizator se poate autentifica. Cel mai comun mod este *Autentificarea pe bază de parolă* (utilizatorul își furnizează numele sau adresa de e-mail și o parolă), dar există și alte mijloace. Probabil că sunteți familiarizat cu butoanele "Autentificare cu Facebook" de pe multe site-uri web sau autentificarea prin Google/Twitter/GitHub sau orice alt site. Cu Nette, puteți avea orice metodă de autentificare doriți, sau le puteți combina. Depinde de dumneavoastră. +Există multe posibilități prin care un utilizator poate fi verificat. Cea mai frecventă metodă de autentificare este prin parolă (utilizatorul furnizează numele său sau e-mailul și parola), dar există și alte metode. Poate cunoașteți butoanele de tip "Conectare cu Facebook" sau autentificarea prin Google/Twitter/GitHub pe unele site-uri. Cu Nette, putem avea orice metodă de autentificare sau le putem combina. Depinde doar de noi. -În mod normal, ar trebui să vă scrieți propriul autentificator, dar pentru acest mic blog simplu vom folosi autentificatorul încorporat, care se autentifică pe baza unei parole și a unui nume de utilizator stocate într-un fișier de configurare. Este bun pentru scopuri de testare. Așadar, vom adăuga următoarea secțiune *security* în fișierul de configurare `config/common.neon`: +În mod normal, am scrie propriul nostru authenticator, dar pentru acest blog mic și simplu vom folosi authenticatorul încorporat, care autentifică pe baza parolei și a numelui de utilizator stocate în fișierul de configurare. Este util în scopuri de testare. Adăugăm deci următoarea secțiune `security` în fișierul de configurare `config/common.neon`: ```neon .{file:config/common.neon} security: users: - admin: secret # utilizator "admin", parolă "secret + admin: secret # utilizator 'admin', parolă 'secret' ``` Nette va crea automat un serviciu în containerul DI. -Formular de înregistrare .[#toc-sign-in-form] -============================================= +Formular de autentificare +========================= -Acum avem gata partea de autentificare din backend și trebuie să furnizăm o interfață de utilizator, prin care utilizatorul se va autentifica. Haideți să creăm un nou prezentator numit *SignPresenter*, care va +Acum avem autentificarea pregătită și trebuie să pregătim interfața utilizator pentru autentificare. Creăm deci un nou presenter numit `SignPresenter`, care: -- va afișa un formular de autentificare (care va cere numele de utilizator și parola) -- va autentifica utilizatorul atunci când formularul este trimis -- să ofere o acțiune de deconectare +- afișează formularul de autentificare (cu nume de utilizator și parolă) +- după trimiterea formularului, verifică utilizatorul +- oferă posibilitatea de deconectare -Să începem cu formularul de autentificare. Știți deja cum funcționează formularele într-un prezentator. Creați `SignPresenter` și metoda `createComponentSignInForm`. Ar trebui să arate astfel: +Începem cu formularul de autentificare. Știm deja cum funcționează formularele în presenteri. Creăm deci presenterul `SignPresenter` și scriem metoda `createComponentSignInForm`. Ar trebui să arate cam așa: -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Sign; use Nette; use Nette\Application\UI\Form; @@ -40,69 +40,69 @@ final class SignPresenter extends Nette\Application\UI\Presenter protected function createComponentSignInForm(): Form { $form = new Form; - $form->addText('username', 'Username:') - ->setRequired('Please enter your username.'); + $form->addText('username', 'Nume utilizator:') + ->setRequired('Vă rugăm să completați numele de utilizator.'); - $form->addPassword('password', 'Password:') - ->setRequired('Please enter your password.'); + $form->addPassword('password', 'Parolă:') + ->setRequired('Vă rugăm să completați parola.'); - $form->addSubmit('send', 'Sign in'); + $form->addSubmit('send', 'Autentificare'); - $form->onSuccess[] = [$this, 'signInFormSucceeded']; + $form->onSuccess[] = $this->signInFormSucceeded(...); return $form; } } ``` -Există o intrare pentru numele de utilizator și parola. +Există câmpuri pentru nume de utilizator și parolă. -Șablon .[#toc-template] ------------------------ +Șablon +------ -Formularul va fi redat în șablon `in.latte` +Formularul va fi redat în șablonul `in.latte`: -```latte .{file:app/Presenters/templates/Sign/in.latte} +```latte .{file:app/Presentation/Sign/in.latte} {block content} -<h1 n:block=title>Sign in</h1> +<h1 n:block=title>Autentificare</h1> {control signInForm} ``` -Manipulator de autentificare .[#toc-login-handler] --------------------------------------------------- +Callback de autentificare +------------------------- -Adăugăm, de asemenea, un *manager de formular* pentru autentificarea utilizatorului, care este invocat imediat după ce formularul este trimis. +Apoi completăm callback-ul pentru autentificarea utilizatorului, care va fi apelat imediat după trimiterea cu succes a formularului. -Manipulatorul va lua doar numele de utilizator și parola introduse de utilizator și le va transmite către autentificatorul definit anterior. După ce utilizatorul s-a logat, îl vom redirecționa către pagina de start. +Callback-ul preia pur și simplu numele de utilizator și parola pe care utilizatorul le-a completat și le transmite authenticatorului. După autentificare, redirecționăm către pagina principală. -```php .{file:app/Presenters/SignPresenter.php} -public function signInFormSucceeded(Form $form, \stdClass $data): void +```php .{file:app/Presentation/Sign/SignPresenter.php} +private function signInFormSucceeded(Form $form, \stdClass $data): void { try { $this->getUser()->login($data->username, $data->password); $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { - $form->addError('Incorrect username or password.'); + $form->addError('Nume de utilizator sau parolă incorectă.'); } } ``` -Metoda [User::login() |api:Nette\Security\User::login()] ar trebui să arunce o excepție atunci când numele de utilizator sau parola nu se potrivește cu cele pe care le-am definit anterior. După cum știm deja, acest lucru ar avea ca rezultat un ecran roșu [Tracy |tracy:] sau, în modul de producție, un mesaj care să informeze despre o eroare internă a serverului. Nu ne-ar plăcea acest lucru. De aceea, capturăm excepția și adăugăm un mesaj de eroare frumos și prietenos în formular. +Metoda [User::login() |api:Nette\Security\User::login()] aruncă o excepție dacă numele de utilizator și parola nu corespund datelor din fișierul de configurare. După cum știm deja, acest lucru poate duce la o pagină roșie de eroare sau, în mod de producție, la un mesaj care informează despre o eroare de server. Nu dorim însă acest lucru. De aceea, prindem această excepție și transmitem un mesaj de eroare frumos și prietenos pentru utilizator în formular. -Atunci când apare eroarea în formular, pagina cu formularul va fi redată din nou, iar deasupra formularului va apărea un mesaj frumos, informând utilizatorul că a introdus un nume de utilizator sau o parolă greșită. +Odată ce apare o eroare în formular, pagina cu formularul se redesenează și deasupra formularului se afișează un mesaj frumos care informează utilizatorul că a introdus un nume de utilizator sau o parolă greșită. -Securitatea prezentatorilor .[#toc-security-of-presenters] -========================================================== +Securizarea presenterilor +========================= -Vom securiza un formular pentru adăugarea și editarea mesajelor. Acesta este definit în prezentator `EditPresenter`. Scopul este de a împiedica utilizatorii care nu sunt conectați să acceseze pagina. +Vom securiza formularul pentru adăugarea și editarea postărilor. Acesta este definit în presenterul `EditPresenter`. Scopul este de a restricționa accesul la pagină pentru utilizatorii care nu sunt autentificați. -Creăm o metodă `startup()` care este lansată imediat la începutul [ciclului de viață al prezentatorului |application:presenters#life-cycle-of-presenter]. Aceasta redirecționează utilizatorii care nu sunt autentificați către formularul de autentificare. +Creăm metoda `startup()`, care se execută imediat la începutul [ciclului de viață al presenterului |application:presenters#Ciclul de viață al presenterului]. Aceasta redirecționează utilizatorii neautentificați către formularul de autentificare. -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} public function startup(): void { parent::startup(); @@ -114,67 +114,66 @@ public function startup(): void ``` -Ascundeți linkurile .[#toc-hide-links] --------------------------------------- +Ascunderea linkurilor +--------------------- -Un utilizator neautentificat nu mai poate vedea nici pagina *create*, nici pagina *edit*, dar poate vedea în continuare legăturile care indică spre ele. Haideți să le ascundem și pe acestea. Un astfel de link se află în `app/Presenters/templates/Home/default.latte`, și ar trebui să fie vizibil numai dacă utilizatorul este autentificat. +Utilizatorul neautorizat nu mai poate vedea pagina *create* sau *edit*, dar încă poate vedea linkurile către acestea. Ar trebui să le ascundem și pe acestea. Un astfel de link se află în șablonul `app/Presentation/Home/default.latte` și ar trebui să fie vizibil doar pentru utilizatorii autentificați. -Îl putem ascunde folosind *n:atribut* numit `n:if`. Dacă afirmația din interiorul acestuia este `false`, întregul `<a>` tag și conținutul său nu vor fi afișate: +Îl putem ascunde folosind un *n:atribut* numit `n:if`. Dacă această condiție este `false`, întregul tag `<a>`, inclusiv conținutul, rămâne ascuns. ```latte -<a n:href="Edit:create" n:if="$user->isLoggedIn()">Create post</a> +<a n:href="Edit:create" n:if="$user->isLoggedIn()">Creează postare</a> ``` -aceasta este o prescurtare pentru (nu o confundați cu `tag-if`): +ceea ce este o prescurtare a următoarei notații (a nu se confunda cu `tag-if`): ```latte -{if $user->isLoggedIn()}<a n:href="Edit:create">Create post</a>{/if} +{if $user->isLoggedIn()}<a n:href="Edit:create">Creează postare</a>{/if} ``` -Ar trebui să ascundeți linkul de editare aflat în `app/Presenters/templates/Post/show.latte` într-un mod similar. +În același mod, ascundem și linkul din șablonul `app/Presentation/Post/show.latte`. -Legătura formularului de autentificare .[#toc-login-form-link] -============================================================== +Link către autentificare +======================== -Hei, dar cum ajungem la pagina de autentificare? Nu există nici un link care să arate spre ea. Să adăugăm unul în fișierul șablon `@layout.latte`. Încercați să găsiți un loc frumos, poate fi oriunde vă place cel mai mult. +Cum ajungem de fapt la pagina de autentificare? Nu există niciun link care să ducă la ea. Așa că îl vom adăuga în șablonul `@layout.latte`. Încercați să găsiți un loc potrivit - poate fi aproape oriunde. -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <ul class="navig"> - <li><a n:href="Home:">Home</a></li> + <li><a n:href="Home:">Articole</a></li> {if $user->isLoggedIn()} - <li><a n:href="Sign:out">Sign out</a></li> + <li><a n:href="Sign:out">Deconectare</a></li> {else} - <li><a n:href="Sign:in">Sign in</a></li> + <li><a n:href="Sign:in">Autentificare</a></li> {/if} </ul> ... ``` -Dacă utilizatorul nu este încă logat, vom afișa link-ul "Sign in". În caz contrar, vom afișa linkul "Sign out". Adăugăm această acțiune în SignPresenter. +Dacă utilizatorul nu este autentificat, se afișează linkul "Autentificare". În caz contrar, se afișează linkul "Deconectare". Vom adăuga și această acțiune în `SignPresenter`. -Acțiunea de deconectare arată astfel și, deoarece redirecționăm utilizatorul imediat, nu este nevoie de un șablon de vizualizare. +Deoarece redirecționăm imediat utilizatorul după deconectare, nu este nevoie de niciun șablon. Deconectarea arată astfel: -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} public function actionOut(): void { $this->getUser()->logout(); - $this->flashMessage('You have been signed out.'); + $this->flashMessage('Deconectarea a avut succes.'); $this->redirect('Home:'); } ``` -Pur și simplu se apelează metoda `logout()` și apoi se afișează un mesaj frumos pentru utilizator. +Se apelează doar metoda `logout()` și apoi se afișează un mesaj frumos care confirmă deconectarea cu succes. -Rezumat .[#toc-summary] -======================= +Rezumat +======= -Avem un link pentru a ne conecta și pentru a deconecta utilizatorul. Am folosit autentificatorul încorporat pentru autentificare, iar detaliile de conectare se află în fișierul de configurare, deoarece aceasta este o simplă aplicație de testare. De asemenea, am securizat formularele de editare astfel încât numai utilizatorii autentificați să poată adăuga și edita postări. +Avem un link pentru autentificare și, de asemenea, pentru deconectarea utilizatorului. Pentru verificare, am folosit authenticatorul încorporat și avem datele de autentificare în fișierul de configurare, deoarece este o aplicație simplă de test. Am securizat, de asemenea, formularele de editare, astfel încât doar utilizatorii autentificați pot adăuga și edita postări. .[note] -Aici puteți citi mai multe despre [autentificarea |security:authentication] și [autorizarea |security:authorization] [utilizatorilor |security:authentication]. +Aici puteți citi mai multe despre [autentificarea utilizatorilor |security:authentication] și [Autorizarea permisiunilor |security:authorization]. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/ro/comments.texy b/quickstart/ro/comments.texy index 4bd3cd9add..fdda4cc67c 100644 --- a/quickstart/ro/comments.texy +++ b/quickstart/ro/comments.texy @@ -1,28 +1,28 @@ Comentarii ********** -Blogul a fost implementat, am scris câteva articole de blog foarte bune și le-am publicat prin Adminer. Oamenii citesc blogul și sunt foarte pasionați de ideile noastre. Primim multe e-mailuri cu laude în fiecare zi. Dar la ce ne folosesc toate laudele când le primim doar în e-mail, deci nimeni altcineva nu le poate citi? Nu ar fi mai bine dacă oamenii ar putea să comenteze direct pe blog, astfel încât toată lumea să poată citi cât de grozavi suntem? +Am încărcat blogul pe serverul web și am publicat câteva postări foarte interesante folosind Adminer. Oamenii citesc blogul nostru și sunt foarte entuziasmați de el. Primim zilnic multe e-mailuri cu laude. Dar la ce bun toate aceste laude dacă le avem doar în e-mail și nimeni altcineva nu le poate citi? Ar fi mai bine dacă cititorul ar putea comenta direct articolul, astfel încât toată lumea să poată citi cât de minunați suntem. -Haideți să facem ca toate articolele să poată fi comentate. +Deci, haideți să programăm comentariile. -Crearea unui tabel nou .[#toc-creating-a-new-table] -=================================================== +Crearea unui nou tabel +====================== -Porniți din nou Adminer și creați un tabel nou numit `comments` cu aceste coloane: +Pornim Adminer și creăm tabelul `comments` cu următoarele coloane: -- `id` int, verificați autoincrementare (AI) -- `post_id`, o cheie externă care face trimitere la tabelul `posts` -- `name` varchar, lungime 255 -- `email` varchar, lungime 255 +- `id` int, bifăm autoincrement (AI) +- `post_id`, cheie străină care face referire la tabelul `posts` +- `name` varchar, length 255 +- `email` varchar, length 255 - `content` text - `created_at` timestamp -Ar trebui să arate astfel: +Tabelul ar trebui să arate cam așa: [* adminer-comments.webp *] -Nu uitați să folosiți stocarea tabelelor InnoDB și apăsați Save. +Nu uitați să folosiți din nou stocarea InnoDB. ```sql CREATE TABLE `comments` ( @@ -37,83 +37,83 @@ CREATE TABLE `comments` ( ``` -Formular pentru comentarii .[#toc-form-for-commenting] -====================================================== +Formular pentru comentarii +========================== -În primul rând, trebuie să creăm un formular, care va permite utilizatorilor să comenteze pe pagina noastră. Nette Framework are un suport minunat pentru formulare. Acestea pot fi configurate într-un prezentator și redate într-un șablon. +În primul rând, trebuie să creăm un formular care să permită utilizatorilor să comenteze postările. Nette Framework are un suport uimitor pentru formulare. Le putem configura în presenter și le putem reda în șablon. -Nette Framework are un concept de *componente*. Un **component** este o clasă sau o bucată de cod reutilizabilă, care poate fi atașată unei alte componente. Chiar și un prezentator este o componentă. Fiecare componentă este creată cu ajutorul fabricii de componente. Deci, să definim fabrica de formulare de comentarii în `PostPresenter`. +Nette Framework utilizează conceptul de *componente*. O **componentă** este o clasă reutilizabilă sau o bucată de cod care poate fi atașată la o altă componentă. Chiar și presenterul este o componentă. Fiecare componentă este creată printr-o fabrică (factory). Vom crea deci o fabrică pentru formularul de comentarii în presenterul `PostPresenter`. -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} protected function createComponentCommentForm(): Form { $form = new Form; // înseamnă Nette\Application\UI\Form - $form->addText('name', 'Your name:') + $form->addText('name', 'Nume:') ->setRequired(); - $form->addEmail('email', 'Email:'); + $form->addEmail('email', 'E-mail:'); - $form->addTextArea('content', 'Comment:') + $form->addTextArea('content', 'Comentariu:') ->setRequired(); - $form->addSubmit('send', 'Publish comment'); + $form->addSubmit('send', 'Publică comentariul'); return $form; } ``` -Să o explicăm puțin. Prima linie creează o nouă instanță a componentei `Form`. Următoarele metode atașează intrările HTML în definiția formularului. `->addText` va fi redat ca `<input type=text name=name>`, cu `<label>Your name:</label>`. După cum probabil ați ghicit deja acum, `->addTextArea` atașează o componentă `<textarea>` iar `->addSubmit` adaugă un `<input type=submit>`. Există mai multe metode de acest gen, dar asta este tot ce trebuie să știți acum. Puteți [afla mai multe în documentație |forms:]. +Să explicăm din nou puțin. Prima linie creează o nouă instanță a componentei `Form`. Următoarele metode atașează inputuri HTML la definiția acestui formular. `->addText` va fi redat ca `<input type="text" name="name">` cu `<label>Nume:</label>`. După cum probabil ghiciți corect, `->addTextArea` va fi redat ca `<textarea>` și `->addSubmit` ca `<input type="submit">`. Există mult mai multe metode similare, dar acestea sunt suficiente pentru acest formular deocamdată. Puteți [citiți mai multe în documentație |forms:]. -Odată ce componenta de formular este definită într-un prezentator, o putem reda (afișa) într-un șablon. Pentru a face acest lucru, plasați eticheta `{control}` la sfârșitul șablonului de detaliu al postării, în `Post/show.latte`. Deoarece numele componentei este `commentForm` (este derivat din numele metodei `createComponentCommentForm`), eticheta va arăta astfel +Dacă formularul este deja definit în presenter, îl putem reda (afișa) în șablon. Facem acest lucru plasând tag-ul `{control}` la sfârșitul șablonului care redă o postare specifică, în `Post/show.latte`. Deoarece componenta se numește `commentForm` (numele este derivat din numele metodei `createComponentCommentForm`), tag-ul va arăta astfel: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... -<h2>Post new comment</h2> +<h2>Adăugați un comentariu nou</h2> {control commentForm} ``` -Acum, dacă verificați detaliul unei anumite postări, va exista un nou formular pentru a posta comentarii. +Acum, dacă vizualizați pagina cu detaliile postării, veți vedea noul formular de comentarii la sfârșitul acesteia. -Salvarea în baza de date .[#toc-saving-to-database] -=================================================== +Salvarea în baza de date +======================== -Ați încercat să trimiteți niște date? Poate ați observat că formularul nu efectuează nicio acțiune. Este doar acolo, arată bine și nu face nimic. Trebuie să-i atașăm o metodă callback, care va salva datele trimise. +Ați încercat deja să completați și să trimiteți formularul? Probabil ați observat că formularul nu face de fapt nimic. Trebuie să atașăm o metodă callback care va salva datele trimise. -Așezați următoarea linie înainte de linia `return` în fabrica de componente pentru `commentForm`: +Pe linia dinaintea `return` în fabrica pentru componenta `commentForm`, plasăm următoarea linie: ```php -$form->onSuccess[] = [$this, 'commentFormSucceeded']; +$form->onSuccess[] = $this->commentFormSucceeded(...); ``` -Aceasta înseamnă "după ce formularul este trimis cu succes, apelați metoda `commentFormSucceeded` a prezentatorului curent". Această metodă nu există încă, așa că haideți să o creăm. +Notația anterioară înseamnă "după trimiterea cu succes a formularului, apelează metoda `commentFormSucceeded` din presenterul curent". Această metodă însă nu există încă. Haideți să o creăm: -```php .{file:app/Presenters/PostPresenter.php} -public function commentFormSucceeded(\stdClass $data): void +```php .{file:app/Presentation/Post/PostPresenter.php} +private function commentFormSucceeded(\stdClass $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); $this->database->table('comments')->insert([ - 'post_id' => $postId, + 'post_id' => $id, 'name' => $data->name, 'email' => $data->email, 'content' => $data->content, ]); - $this->flashMessage('Thank you for your comment', 'success'); + $this->flashMessage('Vă mulțumim pentru comentariu', 'success'); $this->redirect('this'); } ``` -Ar trebui să o plasați imediat după fabrica de componente `commentForm`. +Plasăm această metodă imediat după fabrica formularului `commentForm`. -Metoda new are un singur argument, care este instanța formularului care se trimite, creată de fabrica de componente. Primim valorile trimise în `$data`. Apoi inserăm datele în tabelul din baza de date `comments`. +Această nouă metodă are un argument, care este instanța formularului care a fost trimis - creat de fabrică. Obținem valorile trimise în `$data`. Și apoi salvăm datele în tabelul `comments` din baza de date. -Mai sunt două apeluri de metode care trebuie explicate. Redirecționarea redirecționează literalmente către pagina curentă. Ar trebui să faceți acest lucru de fiecare dată când formularul este trimis, este valid și operațiunea de rechemare a făcut ceea ce trebuia să facă. De asemenea, atunci când redirecționați pagina după trimiterea formularului, nu veți vedea binecunoscutul mesaj `Would you like to submit the post data again?` pe care îl puteți vedea uneori în browser. (În general, după trimiterea unui formular prin metoda `POST`, ar trebui să redirecționați întotdeauna utilizatorul către o acțiune `GET` ). +Mai sunt încă două metode care merită explicate. Metoda `redirect` redirecționează literalmente înapoi la pagina curentă. Este indicat să faceți acest lucru după fiecare trimitere a formularului, dacă acesta conținea date valide și callback-ul a efectuat operația așa cum trebuia. De asemenea, dacă redirecționăm pagina după trimiterea formularului, nu vom vedea mesajul binecunoscut `Doriți să retrimiteți datele formularului?`, pe care îl putem vedea uneori în browser. (În general, după trimiterea unui formular prin metoda `POST`, ar trebui să urmeze întotdeauna o redirecționare către o acțiune `GET`.) - `flashMessage` are rolul de a informa utilizatorul despre rezultatul unei anumite operațiuni. Pentru că redirecționăm, mesajul nu poate fi trecut direct în șablon și redat. Așa că există această metodă, care îl va stoca și îl va face disponibil la următoarea încărcare a paginii. Mesajele flash sunt redate în fișierul implicit `app/Presenters/templates/@layout.latte` și arată astfel: +Metoda `flashMessage` este pentru informarea utilizatorului despre rezultatul unei operații. Deoarece redirecționăm, mesajul nu poate fi pur și simplu transmis șablonului și redat. De aceea există această metodă, care salvează acest mesaj și îl face disponibil la următoarea încărcare a paginii. Mesajele flash sunt redate în șablonul principal `app/Presentation/@layout.latte` și arată astfel: ```latte <div n:foreach="$flashes as $flash" n:class="flash, $flash->type"> @@ -121,20 +121,20 @@ Mai sunt două apeluri de metode care trebuie explicate. Redirecționarea redire </div> ``` -După cum știm deja, acestea sunt transmise automat șablonului, așa că nu trebuie să vă gândiți prea mult la asta, pur și simplu funcționează. Pentru mai multe detalii, [consultați documentația |application:presenters#flash-messages]. +După cum știm deja, mesajele flash sunt transmise automat șablonului, așa că nu trebuie să ne gândim prea mult la asta, pur și simplu funcționează. Pentru mai multe informații, [vizitați documentația |application:presenters#Mesaje flash]. -Redarea comentariilor .[#toc-rendering-the-comments] -==================================================== +Redarea comentariilor +===================== -Acesta este unul dintre lucrurile pe care le veți adora. Baza de date Nette are o funcție interesantă numită [Explorer |database:explorer]. Vă amintiți că am creat tabelele ca InnoDB? Adminer a creat așa-numitele [chei străine |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html] care ne vor salva o tonă de muncă. +Acesta este unul dintre acele lucruri pe care pur și simplu le veți iubi. Nette Database are o funcție grozavă numită [Explorer |database:explorer]. Vă mai amintiți că am creat intenționat tabelele din baza de date folosind stocarea InnoDB? Adminer a creat astfel ceva numit [chei străine |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html], care ne vor economisi multă muncă. -Nette Database Explorer folosește cheile străine pentru a rezolva relațiile dintre tabele și, cunoscând relațiile, poate crea automat interogări pentru dumneavoastră. +Nette Database Explorer folosește cheile străine pentru a rezolva relația reciprocă dintre tabele și, cunoscând aceste relații, poate crea automat interogări în baza de date. -După cum probabil vă amintiți, am trecut variabila `$post` în șablonul din `PostPresenter::renderShow()` și acum dorim să iterăm prin toate comentariile care au coloana `post_id` egală cu a noastră `$post->id`. Puteți face acest lucru apelând `$post->related('comments')`. Este atât de simplu. Priviți codul rezultat. +După cum vă amintiți cu siguranță, am transmis variabila `$post` șablonului folosind metoda `PostPresenter::renderShow()` și acum dorim să iterăm prin toate comentariile care au valoarea coloanei `post_id` identică cu `$post->id`. Putem realiza acest lucru apelând `$post->related('comments')`. Da, atât de simplu. Să vedem codul rezultat: -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { ... $this->template->post = $post; @@ -144,15 +144,15 @@ public function renderShow(int $postId): void Și șablonul: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... -<h2>Comments</h2> +<h2>Comentarii</h2> <div class="comments"> {foreach $comments as $comment} <p><b><a href="mailto:{$comment->email}" n:tag-if="$comment->email"> {$comment->name} - </a></b> said:</p> + </a></b> a scris:</p> <div>{$comment->content}</div> {/foreach} @@ -160,13 +160,12 @@ public function renderShow(int $postId): void ... ``` -Observați atributul special `n:tag-if`. Știți deja cum funcționează `n: attributes`. Ei bine, dacă adăugați atributul cu `tag-`, acesta se va înfășura doar în jurul etichetelor, nu și în jurul conținutului acestora. Acest lucru vă permite să transformați numele comentatorului într-un link dacă acesta a furnizat adresa de e-mail. Aceste două linii sunt identice ca rezultate: +Observați atributul special `n:tag-if`. Știți deja cum funcționează `n:atributele`. Dacă adăugați prefixul `tag-` la atribut, funcționalitatea se aplică doar tag-ului HTML, nu și conținutului său. Acest lucru ne permite să transformăm numele comentatorului într-un link doar dacă și-a furnizat e-mailul. Aceste două linii sunt identice: ```latte -<strong n:tag-if="$important"> Hello there! </strong> +<strong n:tag-if="$important"> Bună ziua! </strong> -{if $important}<strong>{/if} Hello there! {if $important}</strong>{/if} +{if $important}<strong>{/if} Bună ziua! {if $important}</strong>{/if} ``` {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/ro/creating-posts.texy b/quickstart/ro/creating-posts.texy index 408be76187..43c5831837 100644 --- a/quickstart/ro/creating-posts.texy +++ b/quickstart/ro/creating-posts.texy @@ -1,30 +1,30 @@ -Crearea și editarea posturilor +Crearea și editarea postărilor ****************************** -Ce timp minunat! Avem un nou blog super tare, oamenii se ceartă în comentarii și în sfârșit avem timp pentru mai multă programare. Deși ne place Adminer, nu este atât de confortabil să scriem articole de blog în el. Poate că este momentul potrivit pentru a adăuga un formular simplu pentru adăugarea de noi postări direct din aplicația noastră. Haideți să o facem. +Este minunat! Avem un blog nou super cool, oamenii discută aprins în comentarii și avem în sfârșit puțin timp pentru mai multă programare. Deși Adminer este un instrument excelent, nu este tocmai ideal pentru scrierea de postări noi pe blog. Probabil că este momentul potrivit pentru a crea un formular simplu pentru adăugarea de postări noi direct din aplicație. Să trecem la treabă. -Să începem prin a proiecta interfața de utilizare: +Să începem cu proiectarea interfeței utilizator: -1. Pe pagina principală, să adăugăm un link "Scrieți o nouă postare". -2. Se va afișa un formular cu titlu și textarea pentru conținut. -3. Când faceți clic pe un buton "Save", va salva postarea de pe blog. +1. Pe pagina de pornire, adăugăm un link "Scrie o postare nouă". +2. Acest link va afișa un formular cu titlu și o zonă de text (textarea) pentru conținutul postării. +3. Când facem clic pe butonul Salvare, postarea va fi salvată în baza de date. -Mai târziu vom adăuga și autentificarea și vom permite doar utilizatorilor autentificați să adauge postări noi. Dar să facem asta mai târziu. Ce cod va trebui să scriem pentru a face să funcționeze acest lucru? +Mai târziu, vom adăuga și autentificarea și vom permite adăugarea de postări doar utilizatorilor autentificați. Dar asta mai târziu. Ce cod trebuie să scriem acum pentru ca totul să funcționeze? -1. Creați un nou prezentator cu un formular pentru adăugarea de postări. -2. Definiți un callback care va fi declanșat după trimiterea cu succes a formularului și care va salva noua postare în baza de date. -3. Creați un nou șablon pentru formular. -4. Adăugați un link către formular în șablonul paginii principale. +1. Creăm un nou presenter cu un formular pentru adăugarea postărilor. +2. Definim un callback care se va executa după trimiterea cu succes a formularului și care va salva noua postare în baza de date. +3. Creăm un nou șablon în care va fi formularul respectiv. +4. Adăugăm un link către formular în șablonul paginii principale. -Noul prezentator .[#toc-new-presenter] -====================================== +Noul presenter +============== -Numiți noul prezentator `EditPresenter` și salvați-l în `app/Presenters/EditPresenter.php`. De asemenea, acesta trebuie să se conecteze la baza de date, așa că și aici vom scrie un constructor care va necesita o conexiune la baza de date: +Vom numi noul presenter `EditPresenter` și îl vom salva în `app/Presentation/Edit/`. De asemenea, trebuie să se conecteze la baza de date, așa că vom scrie din nou aici un constructor care va necesita conexiunea la baza de date: -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Edit; use Nette; use Nette\Application\UI\Form; @@ -39,95 +39,95 @@ final class EditPresenter extends Nette\Application\UI\Presenter ``` -Formular pentru salvarea postărilor .[#toc-form-for-saving-posts] -================================================================= +Formular pentru salvarea postărilor +=================================== -Formularele și componentele au fost deja abordate atunci când am adăugat suport pentru comentarii. Dacă sunteți confuz în legătură cu subiectul, mergeți să verificați din nou [cum funcționează formularele și componentele |comments#form-for-commenting], noi vom aștepta aici ;) +Am explicat deja formularele și componentele la crearea comentariilor. Dacă încă nu este clar, mergeți să parcurgeți [crearea formularelor și componentelor |comments#Formular pentru comentarii], noi vom aștepta aici între timp ;) -Acum adăugați această metodă la `EditPresenter`: +Acum adăugați această metodă în presenterul `EditPresenter`: -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} protected function createComponentPostForm(): Form { $form = new Form; - $form->addText('title', 'Title:') + $form->addText('title', 'Titlu:') ->setRequired(); - $form->addTextArea('content', 'Content:') + $form->addTextArea('content', 'Conținut:') ->setRequired(); - $form->addSubmit('send', 'Save and publish'); - $form->onSuccess[] = [$this, 'postFormSucceeded']; + $form->addSubmit('send', 'Salvează și publică'); + $form->onSuccess[] = $this->postFormSucceeded(...); return $form; } ``` -Salvarea unei noi postări din formular .[#toc-saving-new-post-from-form] -======================================================================== +Salvarea unei postări noi din formular +====================================== -Continuați prin adăugarea unei metode de gestionare. +Continuăm prin adăugarea metodei care va procesa datele din formular: -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { $post = $this->database ->table('posts') ->insert($data); - $this->flashMessage('Post was published', 'success'); + $this->flashMessage("Postarea a fost publicată cu succes.", 'success'); $this->redirect('Post:show', $post->id); } ``` -Doar o scurtă explicație: aceasta preia valorile din formular, le inserează în baza de date, creează un mesaj pentru utilizator că postarea a fost salvată cu succes și redirecționează către pagina în care este publicată acea postare, astfel încât să puteți vedea cum arată. +Doar o recapitulare rapidă: această metodă obține datele din formular, le inserează în baza de date, creează un mesaj pentru utilizator despre salvarea cu succes a postării și redirecționează către pagina cu noua postare, astfel încât să vedem imediat cum arată. -Pagina pentru crearea unei postări noi .[#toc-page-for-creating-a-new-post] -=========================================================================== +Pagina pentru crearea unei postări noi +====================================== -Să creăm doar șablonul `Edit/create.latte`: +Creăm acum șablonul `Edit/create.latte`: -```latte .{file:app/Presenters/templates/Edit/create.latte} +```latte .{file:app/Presentation/Edit/create.latte} {block content} -<h1>New post</h1> +<h1>Postare nouă</h1> {control postForm} ``` -Totul ar trebui să fie clar până acum. Ultima linie arată formularul pe care urmează să îl creăm. +Totul ar trebui să fie deja clar. Ultima linie redă formularul pe care îl vom crea. -Am putea, de asemenea, să creăm o metodă `renderCreate` corespunzătoare, dar nu este necesar. Nu avem nevoie să obținem date din baza de date și să le transmitem șablonului, așa că acea metodă ar fi goală. În astfel de cazuri, este posibil ca metoda să nu existe deloc. +Am putea crea și o metodă `renderCreate` corespunzătoare, dar nu este necesar. Nu avem nevoie să obținem date din baza de date și să le transmitem șablonului, așa că metoda ar fi goală. În astfel de cazuri, metoda nu trebuie să existe deloc. -Legătură pentru crearea de mesaje .[#toc-link-for-creating-posts] -================================================================= +Link către crearea postărilor +============================= -Probabil că știți deja cum să adăugați un link la `EditPresenter` și la acțiunea sa `create`. Încercați-o. +Probabil știți deja cum să adăugați un link către `EditPresenter` și acțiunea sa `create`. Încercați să o faceți. -Trebuie doar să adăugați la fișierul `app/Presenters/templates/Home/default.latte`: +Este suficient să adăugați în fișierul `app/Presentation/Home/default.latte`: ```latte -<a n:href="Edit:create">Write new post</a> +<a n:href="Edit:create">Scrie o postare nouă</a> ``` -Editarea posturilor .[#toc-editing-posts] -========================================= +Editarea postărilor +=================== -Să adăugăm, de asemenea, posibilitatea de a edita posturile existente. Va fi destul de simplu - avem deja `postForm` și îl putem folosi și pentru editare. +Acum vom adăuga și posibilitatea de a edita o postare. Va fi foarte simplu. Avem deja formularul `postForm` gata și îl putem folosi și pentru editare. -Vom adăuga o nouă pagină `edit` la `EditPresenter`: +Adăugăm o nouă pagină `edit` în presenterul `EditPresenter`: -```php .{file:app/Presenters/EditPresenter.php} -public function renderEdit(int $postId): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +public function renderEdit(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { - $this->error('Post not found'); + $this->error('Postarea nu a fost găsită'); } $this->getComponent('postForm') @@ -135,26 +135,26 @@ public function renderEdit(int $postId): void } ``` -Și vom crea șablonul `Edit/edit.latte`: +Și creăm un alt șablon `Edit/edit.latte`: -```latte .{file:app/Presenters/templates/Edit/edit.latte} +```latte .{file:app/Presentation/Edit/edit.latte} {block content} -<h1>Edit post</h1> +<h1>Editează postarea</h1> {control postForm} ``` -Și actualizați metoda `postFormSucceeded`, care va putea fie să adauge o nouă postare (așa cum se întâmplă acum), fie să le editeze pe cele existente: +Și modificăm metoda `postFormSucceeded`, care va putea atât să adauge un articol nou (așa cum face acum), cât și să editeze un articol existent: -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); - if ($postId) { + if ($id) { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); $post->update($data); } else { @@ -163,26 +163,25 @@ public function postFormSucceeded(array $data): void ->insert($data); } - $this->flashMessage('Post was published', 'success'); + $this->flashMessage('Postarea a fost publicată cu succes.', 'success'); $this->redirect('Post:show', $post->id); } ``` -Atunci când este furnizat parametrul `postId`, înseamnă că se editează o postare. În acest caz, vom verifica dacă postul există cu adevărat și, dacă da, îl vom actualiza în baza de date. În cazul în care parametrul `postId` nu este furnizat, înseamnă că se va adăuga o nouă postare. +Dacă parametrul `id` este disponibil, înseamnă că vom edita postarea. În acest caz, verificăm dacă postarea solicitată există într-adevăr și, dacă da, o actualizăm în baza de date. Dacă parametrul `id` nu este disponibil, atunci înseamnă că ar trebui adăugată o postare nouă. -Dar de unde provine `postId`? Este parametrul transmis metodei `renderEdit`. +Dar de unde vine parametrul `id`? Este parametrul care a fost introdus în metoda `renderEdit`. -Acum puteți adăuga un link la șablonul `app/Presenters/templates/Post/show.latte`: +Acum putem adăuga un link în șablonul `app/Presentation/Post/show.latte`: ```latte -<a n:href="Edit:edit $post->id">Edit this post</a> +<a n:href="Edit:edit $post->id">Editează postarea</a> ``` -Rezumat .[#toc-summary] -======================= +Rezumat +======= -Blogul funcționează, oamenii comentează rapid și nu ne mai bazăm pe Adminer pentru adăugarea de noi articole. Este complet independent și chiar și oamenii normali pot posta acolo. Dar stați puțin, probabil că nu este în regulă, că oricine, adică chiar oricine de pe internet, poate posta pe blogul nostru. Este necesară o formă de autentificare, astfel încât doar utilizatorii conectați să poată posta. Vom adăuga acest lucru în capitolul următor. +Blogul este acum funcțional, vizitatorii comentează activ și nu mai avem nevoie de Adminer pentru publicare. Aplicația este complet independentă și oricine poate adăuga o postare nouă. Stai puțin, probabil că nu este tocmai în regulă ca oricine - și prin asta înțeleg absolut oricine cu acces la internet - să poată adăuga postări noi. Este necesară o anumită securitate, astfel încât doar un utilizator autentificat să poată adăuga o postare nouă. Vom aborda acest aspect în capitolul următor. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/ro/home-page.texy b/quickstart/ro/home-page.texy index b9c41ef20f..5009988c29 100644 --- a/quickstart/ro/home-page.texy +++ b/quickstart/ro/home-page.texy @@ -1,41 +1,41 @@ -Pagina principală a blogului +Pagina de pornire a blogului **************************** .[perex] -Să creăm pagina de pornire care să afișeze postările recente. +Acum vom crea pagina de pornire care afișează cele mai recente postări. -Înainte de a începe, ar trebui să știți cel puțin câteva noțiuni de bază despre modelul de proiectare Model-View-Presenter (similar cu MVC((Model-View-Controller))): +Înainte de a începe, este necesar să cunoașteți cel puțin elementele de bază ale pattern-ului de design Model-View-Presenter (similar cu MVC((Model-View-Controller))): -- **Model** - strat de manipulare a datelor. Acesta este complet separat de restul aplicației. Comunică doar cu prezentatorii. +- **Model** - stratul care lucrează cu datele. Este complet separat de restul aplicației. Comunică doar cu presenterul. -- **View** - un strat de definire frontală. Acesta redă datele solicitate utilizatorului folosind șabloane. +- **View** - stratul front-end. Redă datele solicitate folosind șabloane și le afișează utilizatorului. -- **Prezentator** (sau controler) - un nivel de conexiune. Prezentatorul conectează Modelul și View. Gestionează solicitările, cere date de la Model și apoi le transmite la View curent. +- **Presenter** (sau Controller) - stratul de legătură. Presenterul leagă Modelul și View-ul. Procesează cererile, interoghează Modelul pentru date și le returnează înapoi View-ului. -În cazul unei aplicații foarte simple precum blogul nostru, stratul Model va consta de fapt doar în interogări către baza de date propriu-zisă - nu avem nevoie de cod PHP suplimentar pentru aceasta. Trebuie doar să creăm straturile Presenter și View. În Nette, fiecare Presenter are propriile sale Views, așa că vom continua cu ambele simultan. +În cazul aplicațiilor simple, cum va fi blogul nostru, întregul strat de model va consta doar din interogări în baza de date - pentru asta nu avem nevoie de cod suplimentar deocamdată. Pentru început, vom crea doar presenterele și șabloanele. În Nette, fiecare presenter are propriile sale șabloane, așa că le vom crea simultan. -Crearea bazei de date cu Adminer .[#toc-creating-the-database-with-adminer] -=========================================================================== +Crearea bazei de date folosind Adminer +====================================== -Pentru a stoca datele, vom folosi baza de date MySQL, deoarece este cea mai frecventă alegere printre dezvoltatorii web. Dar dacă nu vă place, nu ezitați să folosiți o bază de date la alegere. +Pentru stocarea datelor vom folosi o bază de date MySQL, deoarece este cea mai răspândită printre programatorii de aplicații web. Totuși, dacă nu doriți să o utilizați, puteți alege o bază de date la alegerea dvs. -Să pregătim baza de date care va stoca postările blogului nostru. Putem începe foarte simplu - doar cu un singur tabel pentru postări. +Acum vom pregăti structura bazei de date unde vor fi stocate articolele blogului nostru. Vom începe foarte simplu - vom crea doar un singur tabel pentru postări. -Pentru a crea baza de date putem descărca [Adminer |https://www.adminer.org], sau puteți folosi un alt instrument pentru gestionarea bazelor de date. +Pentru crearea bazei de date, putem descărca [Adminer |https://www.adminer.org] sau alt instrument preferat pentru administrarea bazelor de date. -Să deschidem Adminer și să creăm o nouă bază de date numită `quickstart`. +Deschidem Adminer și creăm o nouă bază de date cu numele `quickstart`. -Creăm un tabel nou numit `posts` și adăugăm aceste coloane: -- `id` int, faceți clic pe autoincrement (AI) -- `title` varchar, lungime 255 +Creăm un nou tabel cu numele `posts` și cu următoarele coloane: +- `id` int, bifăm autoincrement (AI) +- `title` varchar, length 255 - `content` text - `created_at` timestamp -Ar trebui să arate astfel: +Structura rezultată ar trebui să arate astfel: [* adminer-posts.webp *] @@ -49,49 +49,46 @@ CREATE TABLE `posts` ( ``` .[caution] -Este foarte important să folosiți stocarea tabelului **InnoDB**. Veți vedea motivul mai târziu. Deocamdată, alegeți acest lucru și trimiteți. Puteți apăsa Save (Salvare) acum. +Este foarte important să folosiți motorul de stocare **InnoDB**. Vom arăta în curând de ce. Deocamdată, pur și simplu selectați-l și faceți clic pe salvare. -Încercați să adăugați câteva exemple de postări pe blog înainte de a implementa capacitatea de a adăuga noi postări direct din aplicația noastră. +Înainte de a crea posibilitatea de a adăuga articole în baza de date prin intermediul aplicației, adăugați manual câteva articole exemplu pe blog. ```sql INSERT INTO `posts` (`id`, `title`, `content`, `created_at`) VALUES -(1, 'Article One', 'Lorem ipusm dolor one', CURRENT_TIMESTAMP), -(2, 'Article Two', 'Lorem ipsum dolor two', CURRENT_TIMESTAMP), -(3, 'Article Three', 'Lorem ipsum dolor three', CURRENT_TIMESTAMP); +(1, 'Articolul Unu', 'Lorem ipusm dolor unu', CURRENT_TIMESTAMP), +(2, 'Articolul Doi', 'Lorem ipsum dolor doi', CURRENT_TIMESTAMP), +(3, 'Articolul Trei', 'Lorem ipsum dolor trei', CURRENT_TIMESTAMP); ``` -Conectarea la baza de date .[#toc-connecting-to-the-database] -============================================================= +Conectarea la baza de date +========================== -Acum, când baza de date este creată și avem câteva mesaje în ea, este momentul potrivit pentru a le afișa pe noua noastră pagină strălucitoare. +Acum că baza de date este creată și avem câteva articole stocate în ea, este momentul potrivit să le afișăm pe noua noastră pagină frumoasă. -În primul rând, trebuie să spunem aplicației noastre ce bază de date să folosească. Configurația conexiunii la baza de date este stocată în `config/local.neon`. Setați conexiunea DSN((Data Source Name)) și acreditările. Ar trebui să arate astfel: +În primul rând, trebuie să spunem aplicației ce bază de date să folosească. Conexiunea la baza de date o setăm în fișierul `config/common.neon` folosind DSN((Data Source Name)) și datele de autentificare. Ar trebui să arate cam așa: -```neon .{file:config/local.neon} +```neon .{file:config/common.neon} database: dsn: 'mysql:host=127.0.0.1;dbname=quickstart' - user: *enter user name* - password: *enter password here* + user: *introduceți aici numele de utilizator* + password: *introduceți aici parola bazei de date* ``` .[note] -Fiți atenți la indentare în timpul editării acestui fișier. [Formatul NEON |neon:format] acceptă atât spații, cât și tabulări, dar nu ambele împreună! Fișierul de configurare din proiectul web utilizează tabulatoarele în mod implicit. +La editarea acestui fișier, aveți grijă la indentarea liniilor. Formatul [NEON |neon:format] acceptă atât indentarea cu spații, cât și indentarea cu tabulatori, dar nu ambele simultan. Fișierul de configurare implicit din Web Project utilizează tabulatori. -Întreaga configurație, inclusiv este stocată în `config/` în fișierele `common.neon` și `local.neon`. Fișierul `common.neon` conține configurația globală a aplicației, iar `local.neon` conține doar parametrii specifici mediului (de exemplu, diferența dintre serverul de dezvoltare și cel de producție). +Transmiterea conexiunii la baza de date +======================================= -Injectarea conexiunii la baza de date .[#toc-injecting-the-database-connection] -=============================================================================== +Presenterul `HomePresenter`, care se va ocupa de afișarea articolelor, are nevoie de conexiunea la baza de date. Pentru a o obține, vom folosi un constructor, care va arăta astfel: -Prezentatorul `HomePresenter`, care va lista articolele, are nevoie de o conexiune la baza de date. Pentru a o primi, scrieți un constructor ca acesta: - -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; -use Nette\Application\UI\Form; final class HomePresenter extends Nette\Application\UI\Presenter { @@ -105,12 +102,12 @@ final class HomePresenter extends Nette\Application\UI\Presenter ``` -Încărcarea mesajelor din baza de date .[#toc-loading-posts-from-the-database] -============================================================================= +Încărcarea postărilor din baza de date +====================================== -Acum să preluăm posturile din baza de date și să le transmitem șablonului, care va reda apoi codul HTML. Pentru aceasta există așa-numita metodă *render*: +Acum vom încărca postările din baza de date și le vom trimite șablonului, care le va reda ulterior ca și cod HTML. Pentru aceasta este destinată așa-numita metodă *render*: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} public function renderDefault(): void { $this->template->posts = $this->database @@ -120,41 +117,41 @@ public function renderDefault(): void } ``` -Prezentatorul are acum o metodă de randare `renderDefault()` care transmite datele către o vizualizare numită `default`. Șabloanele prezentatorului pot fi găsite în `app/Presenters/templates/{PresenterName}/{viewName}.latte`, astfel încât în acest caz șablonul va fi localizat în `app/Presenters/templates/Home/default.latte`. În șablon, o variabilă numită `$posts` este acum disponibilă, care conține postările din baza de date. +Presenterul conține acum o metodă de redare `renderDefault()`, care transmite datele din baza de date către șablon (View). Șabloanele sunt localizate în `app/Presentation/{PresenterName}/{viewName}.latte`, deci în acest caz șablonul este localizat în `app/Presentation/Home/default.latte`. În șablon va fi acum disponibilă variabila `$posts`, în care se află postările obținute din baza de date. -Șablonul .[#toc-template] -========================= +Șablonul +======== -Există un șablon generic pentru întreaga pagină (numit *layout*, cu antet, foi de stil, footer, ...) și apoi șabloane specifice pentru fiecare vizualizare (de exemplu, pentru afișarea listei de articole de pe blog), care pot suprascrie unele dintre părțile șablonului layout. +Pentru întreaga pagină web avem la dispoziție un șablon principal (care se numește *layout*, conține antet, stiluri, subsol,...) și, în plus, șabloane specifice pentru fiecare vizualizare (View) (de exemplu, pentru afișarea postărilor pe blog), care pot suprascrie unele părți ale șablonului principal. -În mod implicit, șablonul de aspect este localizat în `templates/@layout.latte`, care conține: +În mod implicit, șablonul layout este localizat în `app/Presentation/@layout.latte` și conține: -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... {include content} ... ``` -`{include content}` inserează un bloc numit `content` în șablonul principal. Îl puteți defini în șabloanele fiecărei vizualizări. În acest caz, vom edita fișierul `Home/default.latte` în felul următor: +Notația `{include content}` inserează în șablonul principal blocul cu numele `content`. Acesta îl vom defini în șabloanele vizualizărilor individuale (View). În cazul nostru, vom modifica fișierul `Home/default.latte` astfel: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} Hello World {/block} ``` -Acesta definește [blocul |latte:tags#block]*content*, care va fi inserat în aspect. Dacă reîmprospătați browserul, veți vedea o pagină cu textul "Hello world" (în codul sursă, de asemenea, cu antetul și subsolul HTML definite în `@layout.latte`). +Prin aceasta am definit [blocul |latte:tags#block] *content*, care va fi inserat în layout-ul principal. Dacă reîmprospătăm din nou browserul, vom vedea pagina cu textul "Hello World" (în codul sursă și cu antetul și subsolul HTML definite în `@layout.latte`). -Să afișăm articolele de pe blog - vom edita șablonul astfel: +Să afișăm postările de pe blog - vom modifica șablonul astfel: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - <h1>My blog</h1> + <h1>Blogul meu</h1> {foreach $posts as $post} <div class="post"> - <div class="date">{$post->created_at|date:'j. n. Y'}</div> + <div class="date">{$post->created_at|date:'F j, Y'}</div> <h2>{$post->title}</h2> @@ -164,42 +161,41 @@ Să afișăm articolele de pe blog - vom edita șablonul astfel: {/block} ``` -Dacă vă reîmprospătați browserul, veți vedea lista postărilor de pe blog. Lista nu este foarte fantezistă sau colorată, așa că nu ezitați să adăugați niște [CSS strălucitor |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] la `www/css/style.css` și să o legați într-un layout: +Dacă reîmprospătăm browserul, vom vedea lista tuturor postărilor. Lista nu este deocamdată foarte frumoasă, nici colorată, de aceea putem adăuga în fișierul `www/css/style.css` câteva [stiluri CSS |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] și să-l legăm în layout: -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <link rel="stylesheet" href="{$basePath}/css/style.css"> </head> ... ``` -Eticheta `{foreach}` trece în revistă toate postările transmise șablonului în variabila `$posts` și afișează o bucată de cod HTML pentru fiecare postare. La fel cum ar face-o un cod PHP. +Tag-ul `{foreach}` iterează prin toate postările pe care le-am transmis șablonului în variabila `$posts` și pentru fiecare redă bucata respectivă de HTML. Se comportă exact ca și codul PHP. -Chestia `|date` se numește filtru. Filtrele sunt folosite pentru a formata ieșirea. Acest filtru particular convertește o dată (de exemplu, `2013-04-12`) în forma sa mai ușor de citit (`12. 4. 2013`). Filtrul `|truncate` trunchiază șirul la lungimea maximă specificată și adaugă o elipsă la sfârșit dacă șirul este trunchiat. Deoarece aceasta este o previzualizare, nu are rost să afișeze conținutul complet al articolului. Alte filtre implicite pot [fi găsite în documentație |latte:filters] sau vă puteți crea propriile filtre, dacă este necesar. +Notației `|date:` îi spunem filtru. Filtrele sunt destinate formatării ieșirii. Acest filtru specific convertește data (de ex. `2013-04-12`) în forma sa mai lizibilă (`April 12, 2013`). Filtrul `|truncate` trunchiază șirul la lungimea maximă specificată și, în cazul în care șirul este scurtat, adaugă puncte de suspensie la sfârșit. Deoarece este vorba de o previzualizare, nu are sens să afișăm întregul conținut al articolului. Alte filtre implicite le [găsim în documentație |latte:filters] sau putem crea propriile noastre filtre, atunci când este necesar. -Încă un lucru. Putem face codul un pic mai scurt și, astfel, mai simplu. Putem înlocui *Latte tags* cu *n:attributes* astfel: +Încă un lucru. Putem scurta și simplifica codul anterior. Realizăm acest lucru înlocuind *tag-urile Latte* cu *n:atribute*: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - <h1>My blog</h1> + <h1>Blogul meu</h1> <div n:foreach="$posts as $post" class="post"> <div class="date">{$post->created_at|date:'F j, Y'}</div> <h2>{$post->title}</h2> - <div>{$post->content}</div> + <div>{$post->content|truncate:256}</div> </div> {/block} ``` - `n:foreach`, înfășoară pur și simplu *div* cu un bloc *foreach* (face exact același lucru ca și blocul de cod anterior). +Atributul `n:foreach` învelește blocul *div* cu un *foreach* (funcționează absolut la fel ca și codul anterior). -Rezumat .[#toc-summary] -======================= +Rezumat +======= -Avem o bază de date MySQL foarte simplă cu câteva articole de blog în ea. Aplicația se conectează la baza de date și afișează o listă simplă a postărilor. +Acum avem o bază de date MySQL foarte simplă cu câteva postări. Aplicația se conectează la această bază de date și afișează o listă simplă a acestor postări în șablon. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/ro/model.texy b/quickstart/ro/model.texy index b5b25d50e1..8f267b1835 100644 --- a/quickstart/ro/model.texy +++ b/quickstart/ro/model.texy @@ -1,13 +1,13 @@ Model ***** -Pe măsură ce aplicația noastră se dezvoltă, vom descoperi curând că trebuie să efectuăm operațiuni similare în baza de date în diferite locații și în diferite prezentări, de exemplu, achiziționarea celor mai noi articole publicate. Dacă ne îmbunătățim aplicația prin adăugarea unui indicator la articole pentru a indica o stare de lucru în curs de desfășurare, trebuie, de asemenea, să trecem prin toate locațiile din aplicația noastră și să adăugăm o clauză where pentru a ne asigura că sunt selectate numai articolele finalizate. +Pe măsură ce aplicația crește, vom descoperi curând că în diferite locuri, în diferiți presenteri, avem nevoie să efectuăm operații similare cu baza de date. De exemplu, să obținem cele mai recente articole publicate. Dacă îmbunătățim aplicația, de exemplu, adăugând un indicator la articole dacă sunt în curs de redactare, trebuie apoi să parcurgem toate locurile din aplicație unde se obțin articole din baza de date și să adăugăm condiția where, pentru a selecta doar articolele care nu sunt în curs de redactare. -În acest moment, lucrul direct cu baza de date devine insuficient și va fi mai inteligent să ne ajutăm cu o nouă funcție care să returneze articolele publicate. Iar atunci când adăugăm o altă clauză mai târziu (de exemplu, pentru a nu afișa articolele cu o dată viitoare), vom edita codul nostru doar într-un singur loc. +În acel moment, lucrul direct cu baza de date devine insuficient și va fi mai inteligent să ne ajutăm cu o nouă funcție care ne va returna articolele publicate. Și dacă ulterior adăugăm o altă condiție, de exemplu, că nu trebuie afișate articolele cu dată viitoare, vom modifica codul doar într-un singur loc. -Vom plasa funcția în clasa `PostFacade` și o vom numi `getPublicArticles()`. +Vom plasa funcția, de exemplu, în clasa `PostFacade` și o vom numi `getPublicArticles()`. -Vom crea clasa noastră model `PostFacade` în directorul `app/Model/` pentru a avea grijă de articolele noastre: +În directorul `app/Model/` vom crea clasa noastră de model `PostFacade`, care se va ocupa de articole: ```php .{file:app/Model/PostFacade.php} <?php @@ -32,13 +32,13 @@ final class PostFacade } ``` -În clasă vom trece baza de date Explorer:[api:Nette\Database\Explorer]. Acest lucru va profita de puterea [containerului DI |dependency-injection:passing-dependencies]. +În clasă, prin intermediul constructorului, vom solicita transmiterea bazei de date [api:Nette\Database\Explorer]. Vom folosi astfel puterea [containerului DI|dependency-injection:passing-dependencies]. -Vom trece la `HomePresenter` pe care îl vom edita astfel încât să scăpăm de dependența de `Nette\Database\Explorer` înlocuind-o cu o nouă dependență de noua noastră clasă. +Trecem la `HomePresenter`, pe care îl vom modifica astfel încât să eliminăm dependența de `Nette\Database\Explorer` și să o înlocuim cu noua dependență de clasa noastră nouă. -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use App\Model\PostFacade; use Nette; @@ -59,10 +59,9 @@ final class HomePresenter extends Nette\Application\UI\Presenter } ``` -În secțiunea de utilizare, folosim `App\Model\PostFacade`, astfel încât putem scurta codul PHP la `PostFacade`. Solicităm acest obiect în constructor, îl scriem în proprietatea `$facade` și îl folosim în metoda renderDefault. +În secțiunea `use` avem `App\Model\PostFacade`, așa că putem scurta notația în codul PHP la `PostFacade`. Solicităm acest obiect în constructor, îl scriem în proprietatea `$facade` și îl folosim în metoda `renderDefault`. -Ultimul pas rămas este să învățăm containerul DI să producă acest obiect. Acest lucru se face, de obicei, prin adăugarea unui punct în fișierul `config/services.neon` în secțiunea `services`, oferind numele complet al clasei și parametrii constructorului. -Acest lucru îl înregistrează, ca să spunem așa, iar obiectul este apoi numit **service**. Mulțumită unei magii numite [autowiring |dependency-injection:autowiring], de obicei nu este nevoie să specificăm parametrii constructorului, deoarece DI îi va recunoaște și îi va trece automat. Astfel, ar fi suficient să furnizăm doar numele clasei: +Rămâne ultimul pas și anume să învățăm containerul DI să producă acest obiect. Acest lucru se face de obicei adăugând un element cu bulină în fișierul `config/services.neon` în secțiunea `services`, specificând numele complet al clasei și parametrii constructorului. Astfel o înregistrăm, iar obiectul se numește apoi **serviciu**. Datorită magiei numite [autowiring |dependency-injection:autowiring], de cele mai multe ori nu trebuie să specificăm parametrii constructorului, deoarece DI îi recunoaște și îi transmite automat. Ar fi suficient deci să specificăm doar numele clasei: ```neon .{file:config/services.neon} ... @@ -71,16 +70,15 @@ services: - App\Model\PostFacade ``` -Cu toate acestea, nu este necesar să adăugați nici această linie. În secțiunea `search` de la începutul `services.neon` este definit faptul că toate clasele care se termină cu `-Facade` sau `-Factory` vor fi căutate automat de către DI, ceea ce este și cazul pentru `PostFacade`. +Cu toate acestea, nici această linie nu trebuie adăugată. În secțiunea `search` de la începutul `services.neon` este definit că toate clasele care se termină cu cuvântul `-Facade` sau `-Factory` vor fi găsite automat de DI, ceea ce este și cazul `PostFacade`. -Rezumat .[#toc-summary] -======================= +Rezumat +======= -Clasa `PostFacade` solicită `Nette\Database\Explorer` într-un constructor și, deoarece această clasă este înregistrată în containerul DI, containerul creează această instanță și o transmite. În acest fel, DI creează o instanță `PostFacade` pentru noi și o transmite într-un constructor clasei HomePresenter care a cerut-o. Un fel de păpușă Matryoshka de cod. :) Toate componentele solicită doar ceea ce au nevoie și nu le pasă unde și cum este creat. Crearea este gestionată de containerul DI. +Clasa `PostFacade` solicită în constructor transmiterea `Nette\Database\Explorer` și, deoarece această clasă este înregistrată în containerul DI, containerul creează această instanță și o transmite. DI creează astfel pentru noi instanța `PostFacade` și o transmite în constructor clasei HomePresenter, care a solicitat-o. Un fel de păpușă Matrioșka. :) Toată lumea spune doar ce vrea și nu se interesează unde și cum se creează ceva. De creare se ocupă containerul DI. .[note] -Aici puteți citi mai multe despre [injecția de dependență |dependency-injection:introduction] și despre [configurare |nette:configuring]. +Aici puteți citi mai multe despre [injecția de dependențe |dependency-injection:introduction] și [configurare |nette:configuring]. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/ro/single-post.texy b/quickstart/ro/single-post.texy index e1a1de7e60..5c75c429ab 100644 --- a/quickstart/ro/single-post.texy +++ b/quickstart/ro/single-post.texy @@ -1,17 +1,17 @@ -Pagină cu o singură postare -*************************** +Pagina cu postarea +****************** .[perex] -Să adăugăm încă o pagină la blogul nostru, care va afișa conținutul unei anumite postări de pe blog. +Acum vom crea o altă pagină a blogului, care va afișa o postare specifică. -Trebuie să creăm o nouă metodă de redare, care va prelua o anumită postare de pe blog și o va transmite șablonului. A avea această vizualizare în `HomePresenter` nu este plăcută, deoarece este vorba despre un articol de blog, nu despre pagina de start. Așadar, să creăm o nouă clasă `PostPresenter` și să o plasăm în `app/Presenters`. Aceasta va avea nevoie de o conexiune la baza de date, așa că puneți din nou codul de *injecție în baza de date* acolo. +Trebuie să creăm o nouă metodă `render`, care va obține un articol specific și îl va transmite șablonului. A avea această metodă în `HomePresenter` nu este foarte elegant, deoarece vorbim despre un articol și nu despre pagina de pornire. Să creăm deci `PostPresenter` în `app/Presentation/Post/`. Acest presenter trebuie, de asemenea, să se conecteze la baza de date, așa că vom scrie din nou aici un constructor care va necesita conexiunea la baza de date. -Clasa `PostPresenter` ar trebui să arate astfel: +`PostPresenter` ar putea arăta deci astfel: -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Post; use Nette; use Nette\Application\UI\Form; @@ -23,50 +23,50 @@ final class PostPresenter extends Nette\Application\UI\Presenter ) { } - public function renderShow(int $postId): void + public function renderShow(int $id): void { $this->template->post = $this->database ->table('posts') - ->get($postId); + ->get($id); } } ``` -Trebuie să setăm un namespace corect `App\Presenters` pentru prezentatorul nostru. Depinde de [cartografierea prezentatorului |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7]. +Nu trebuie să uităm să specificăm spațiul de nume corect `App\Presentation\Post`, care este supus setărilor de [maparea presenterilor |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7]. -Metoda `renderShow` are nevoie de un singur argument - ID-ul postului care urmează să fie afișat. Apoi, aceasta încarcă postul din baza de date și transmite rezultatul către șablon. +Metoda `renderShow` necesită un argument - ID-ul unui articol specific care trebuie afișat. Apoi încarcă acest articol din baza de date și îl transmite șablonului. -În șablonul `Home/default.latte` adăugăm un link către acțiunea `Post:show`: +În șablonul `Home/default.latte` inserăm un link către acțiunea `Post:show`. -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a href="{link Post:show $post->id}">{$post->title}</a></h2> ... ``` -Eticheta `{link}` generează o adresă URL care trimite la acțiunea `Post:show`. Această etichetă transmite, de asemenea, ID-ul mesajului ca argument. +Tag-ul `{link}` generează adresa URL care indică spre acțiunea `Post:show`. De asemenea, transmite ID-ul postării ca argument. -Același lucru îl putem scrie pe scurt folosind n:attribute: +Același lucru îl putem scrie prescurtat folosind n:atributul: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a n:href="Post:show $post->id">{$post->title}</a></h2> ... ``` -Atributul `n:href` este similar cu eticheta `{link}`. +Atributul `n:href` este echivalentul tag-ului `{link}`. -Șablonul pentru acțiunea `Post:show` nu există încă. Putem deschide un link către această postare. [Tracy |tracy:] va afișa o eroare, de ce `Post/show.latte` nu există. Dacă vedeți orice alt raport de eroare, probabil că trebuie să activați mod_rewrite în serverul dvs. web. +Pentru acțiunea `Post:show` însă, nu există încă un șablon. Putem încerca să deschidem linkul către această postare. [Tracy |tracy:] va afișa o eroare, deoarece șablonul `Post/show.latte` nu există încă. Dacă vedeți un alt mesaj de eroare, probabil va trebui să activați `mod_rewrite` pe serverul web. -Deci, vom crea `Post/show.latte` cu acest conținut: +Creăm deci șablonul `Post/show.latte` cu acest conținut: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} {block content} -<p><a n:href="Home:default">← back to posts list</a></p> +<p><a n:href="Home:default">← înapoi la lista postărilor</a></p> <div class="date">{$post->created_at|date:'F j, Y'}</div> @@ -75,51 +75,50 @@ Deci, vom crea `Post/show.latte` cu acest conținut: <div class="post">{$post->content}</div> ``` -Să ne uităm la părțile individuale. +Acum vom parcurge părțile individuale ale șablonului. -Prima linie începe definiția unui *bloc cu nume* numit "conținut", pe care l-am văzut mai devreme. Acesta va fi afișat într-un *șablon de prezentare*. După cum puteți vedea, lipsește eticheta de sfârșit `{/block}`. Aceasta este opțională. +Prima linie începe definiția blocului cu numele "content", la fel cum a fost pe pagina de pornire. Acest bloc va fi din nou afișat în șablonul principal. După cum vedeți, lipsește tag-ul de închidere `{/block}`. Acesta este, de fapt, opțional. -A doua linie oferă un backlink către lista de articole de pe blog, astfel încât utilizatorul să poată naviga fără probleme înainte și înapoi pe blogul nostru. Utilizăm din nou atributul `n:href`, prin urmare Nette se va ocupa de generarea URL-ului pentru noi. Legătura indică acțiunea `default` din prezentatorul `Home` (ați putea scrie și `n:href="Home:"`, deoarece acțiunea `default` poate fi omisă). +Pe linia următoare este un link înapoi la lista articolelor blogului, astfel încât utilizatorul să poată naviga ușor între lista articolelor și unul specific. Deoarece folosim atributul `n:href`, Nette se ocupă singur de generarea linkurilor. Linkul indică spre acțiunea `default` a presenterului `Home` (putem scrie și `n:href="Home:"`, deoarece acțiunea cu numele `default` poate fi omisă, se completează automat). -A treia linie formatează data și ora publicării cu un filtru, așa cum știm deja. +A treia linie formatează afișarea datei folosind filtrul pe care îl cunoaștem deja. -A patra linie afișează *titlul* postării de pe blog sub forma unui fișier `<h1>` titlu. Există o parte cu care poate nu sunteți familiarizați, și anume `n:block="title"`. Puteți ghici ce face? Dacă ați citit cu atenție părțile anterioare, am menționat `n: attributes`. Acesta este un alt exemplu. Este echivalent cu: +A patra linie afișează *titlul* blogului în tag-ul HTML `<h1>`. Acest tag conține un atribut pe care poate nu îl cunoașteți (`n:block="title"`). Ghiciți ce face? Dacă ați citit partea anterioară cu atenție, știți deja că este vorba despre un `n:atribut`. Acesta este un alt exemplu al lor, care este echivalent cu: ```latte {block title}<h1>{$post->title}</h1>{/block} ``` -Cu alte cuvinte, se *redefinește* un bloc numit `title`. Blocul este definit în *șablonul de machetare* (`/app/Presenters/templates/@layout.latte:11`) și, ca în cazul suprapunerii OOP, este suprapus aici. Prin urmare, pagina `<title>` va conține titlul articolului afișat. Am suprascris titlul paginii și tot ce aveam nevoie era `n:block="title"`. Grozav, nu? +Simplu spus, acest bloc redefinește blocul cu numele `title`. Acest bloc este deja definit în șablonul *layout* principal (`/app/Presentation/@layout.latte:11`) și, la fel ca la suprascrierea metodelor în OOP, la fel acest bloc din șablonul principal va fi suprascris. Deci `<title>` paginii conține acum titlul postării afișate și ne-a fost suficient să folosim doar un singur atribut simplu `n:block="title"`. Minunat, nu-i așa? -A cincea și ultima linie a șablonului afișează conținutul complet al postării. +A cincea și ultima linie a șablonului afișează întregul conținut al unei postări specifice. -Verificarea ID-ului postului .[#toc-checking-post-id] -===================================================== +Verificarea ID-ului postării +============================ -Ce se întâmplă dacă cineva modifică URL-ul și inserează `postId` care nu există? Ar trebui să oferim utilizatorului o eroare frumoasă de tipul "pagina nu a fost găsită". Să actualizăm metoda de redare din `PostPresenter`: +Ce se întâmplă dacă cineva modifică ID-ul în URL și introduce un `id` inexistent? Ar trebui să oferim utilizatorului o eroare frumoasă de tipul "pagina nu a fost găsită". Modificăm deci puțin metoda `render` în `PostPresenter`: -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { - $this->error('Post not found'); + $this->error('Pagina nu a fost găsită'); } $this->template->post = $post; } ``` -Dacă postul nu poate fi găsit, apelarea `$this->error(...)` va afișa o pagină 404 cu un mesaj frumos și ușor de înțeles. Rețineți că, în mediul de dezvoltare (pe laptop), nu veți vedea pagina de eroare. În schimb, Tracy va afișa excepția cu detalii complete, ceea ce este destul de convenabil pentru dezvoltare. Puteți verifica ambele moduri, doar schimbați valoarea transmisă la `setDebugMode` în `Bootstrap.php`. +Dacă postarea nu poate fi găsită, apelând `$this->error(...)` afișăm pagina de eroare 404 cu un mesaj inteligibil. Atenție, în modul dezvoltator (localhost) nu veți vedea această pagină de eroare. În schimb, se va afișa Tracy cu detalii despre excepție, ceea ce este destul de avantajos pentru dezvoltare. Dacă dorim să ne afișăm ambele moduri, este suficient să schimbăm argumentul metodei `setDebugMode` în fișierul `Bootstrap.php`. -Rezumat .[#toc-summary] -======================= +Rezumat +======= -Avem o bază de date cu postări pe blog și o aplicație web cu două vizualizări - prima afișează rezumatul tuturor postărilor recente, iar cea de-a doua afișează o anumită postare. +Avem o bază de date cu postări și o aplicație web care are două vizualizări - prima afișează o prezentare generală a tuturor postărilor și a doua afișează o postare specifică. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/ru/@home.texy b/quickstart/ru/@home.texy index 336f66ee5f..8de33e49ff 100644 --- a/quickstart/ru/@home.texy +++ b/quickstart/ru/@home.texy @@ -1,113 +1,114 @@ -Создайте свое первое приложение! -******************************** +Пишем первое приложение! +************************ .[perex] -Познакомьтесь с фреймворком Nette, создав простой блог с комментариями. Давайте начнём! +Давайте познакомимся с Nette Framework, создавая простой блог с комментариями. Начнем! -После первых двух глав у вас будет свой собственный работающий блог, и вы будете готовы публиковать свои потрясающие посты, хотя после завершения этих двух глав возможности будут довольно сильно ограничены. Чтобы сделать все более приятным для ваших пользователей, вам также следует прочитать следующие главы и продолжать совершенствовать свое приложение. +Уже после первых двух глав у нас будет свой собственный работающий блог, и мы сможем публиковать свои замечательные посты, хотя функциональность пока будет в значительной степени ограничена. Вам также следует прочитать следующие главы, где мы запрограммируем добавление комментариев, редактирование статей и, наконец, обезопасим блог. .[tip] -В этом руководстве предполагается, что вы завершили документ [Установка |nette:installation] и успешно настроили инструментарий. +Это руководство предполагает, что вы прочитали страницу [Установка |nette:installation] и успешно подготовили необходимые инструменты. Также предполагается, что вы понимаете [объектно-ориентированное программирование в PHP |nette:introduction-to-object-oriented-programming]. -Пожалуйста, используйте PHP 8.0 или более позднюю версию. Полное приложение вы можете найти [на GitHub |https://github.com/nette-examples/quickstart/tree/v4.0]. +Пожалуйста, используйте PHP 8.1 или новее. Полное приложение можно найти [на GitHub |https://github.com/nette-examples/quickstart/tree/v4.0]. -Приветственная страница .[#toc-the-welcome-page] -================================================ +Приветственная страница +======================= -Давайте начнем с создания нового проекта в каталоге `nette-blog`: +Начнем с создания нового проекта в каталоге `nette-blog`: ```shell composer create-project nette/web-project nette-blog ``` -В этот момент должна быть запущена страница приветствия веб-проекта. Попробуйте ее, открыв браузер и перейдя по следующему URL: +В этот момент стартовая страница Web Project уже должна работать. Проверим это, открыв браузер по следующему URL-адресу: ``` http://localhost/nette-blog/www/ ``` -и вы увидите страницу приветствия фреймворка: +и увидим стартовую страницу Nette Framework: [* qs-welcome.webp .{url: http://localhost/nette-blog/www/} *] -Приложение работает, и теперь можно начать вносить в него изменения. +Приложение работает, и вы можете начать вносить изменения. .[note] -Если у вас возникли проблемы, [попробуйте воспользоваться несколькими советами |nette:troubleshooting#Nette-Is-Not-Working-White-Page-Is-Displayed]. +Если возникла проблема, [попробуйте эти советы |nette:troubleshooting#Nette не работает отображается белая страница]. -Содержание веб-проекта .[#toc-web-project-s-content] -==================================================== +Содержимое Web Project +====================== -Наш проект имеет следующую структуру: +Web Project имеет следующую структуру: /--pre <b>nette-blog/</b> ├── <b>app/</b> ← каталог приложения -│ ├── <b>Presenters/</b> ← классы презентеров -│ │ └── <b>templates/</b>← шаблоны -│ ├── <b>Router/</b> ← конфигурация адресов URL +│ ├── <b>Core/</b> ← базовые классы, необходимые для работы +│ ├── <b>Presentation/</b> ← презентеры, шаблоны и т.п. +│ │ └── <b>Home/</b> ← каталог презентера Home │ └── <b>Bootstrap.php</b> ← загрузочный класс Bootstrap -├── <b>bin/</b> ← скрипты для командной строки +├── <b>assets/</b> ← необработанные активы (SCSS, TypeScript, исходные изображения) +├── <b>bin/</b> ← скрипты, запускаемые из командной строки ├── <b>config/</b> ← конфигурационные файлы -├── <b>log/</b> ← журналы ошибок -├── <b>temp/</b> ← временные файлы, кэш, … -├── <b>vendor/</b> ← библиотеки, установленные через Composer -│ └── <b>autoload.php</b> ← автозагрузка библиотек, установленных Composer -└── <b>www/</b> ← общая папка — единственное место, доступное из браузера - └── <b>index.php</b> ← начальный файл, запускающий приложение +├── <b>log/</b> ← логирование ошибок +├── <b>temp/</b> ← временные файлы, кеш, … +├── <b>vendor/</b> ← библиотеки, установленные Composer +│ └── <b>autoload.php</b> ← автозагрузка всех установленных пакетов +└── <b>www/</b> ← публичный каталог - единственный доступный из браузера + ├── <b>assets/</b> ← скомпилированные статические файлы (CSS, JS, изображения, ...) + └── <b>index.php</b> ← начальный файл, с которого запускается приложение \-- -Каталог `www` предназначен для хранения изображений, JavaScript, CSS и других общедоступных файлов. Это единственный каталог, доступный непосредственно из браузера, поэтому вы можете указать здесь корневой каталог вашего веб-сервера (можно настроить его в Apache, но давайте сделаем это позже, так как сейчас это не важно). +Каталог `www/` предназначен для хранения изображений, JavaScript-файлов, CSS-стилей и других общедоступных файлов. Только этот каталог доступен из интернета, поэтому настройте корневой каталог вашего приложения так, чтобы он указывал именно сюда (это можно настроить в конфигурации Apache или nginx, но давайте сделаем это позже, сейчас это не важно). -Наиболее важным каталогом для вас является `app/`. Там можно найти файл `Bootstrap.php`, внутри которого находится класс, загружающий фреймворк и конфигурирующий приложение. Он активирует [автозагрузку |robot-loader:] и устанавливает [отладчик |tracy:] и [маршруты |application:routing]. +Самая важная папка для нас — `app/`. Здесь мы найдем файл `Bootstrap.php`, в котором находится класс, служащий для загрузки всего фреймворка и настройки приложения. Здесь активируется [автозагрузка |robot-loader:], настраивается [отладчик |tracy:] и [маршруты |application:routing]. -Очистка .[#toc-cleanup] -======================= +Очистка +======= -Веб-проект содержит страницу приветствия, которую мы можем удалить — смело замените содержимое файла `app/Presenters/templates/Home/default.latte` текстом `Hello world!`. +Web Project содержит стартовую страницу, которую мы удалим перед тем, как начнем что-либо программировать. Без опасений заменим содержимое файла `app/Presentation/Home/default.latte` на "Hello world!". [* qs-hello.webp .{url:-} *] -Tracy (отладчик) .[#toc-tracy-debugger] -======================================= +Tracy (отладчик) +================ -Чрезвычайно важным инструментом для разработки является [отладчик под названием Tracy |tracy:]. Попробуйте сделать несколько ошибок в вашем файле `app/Presenters/HomePresenter.php` (например, удалите фигурную скобку из определения класса HomePresenter) и посмотрите, что произойдет. Появится страница с красным экраном и понятным описанием ошибки. +Чрезвычайно важный инструмент для разработки — [инструмент отладки Tracy |tracy:]. Попробуйте вызвать какую-нибудь ошибку в файле `app/Presentation/Home/HomePresenter.php` (например, удалив фигурную скобку в определении класса HomePresenter) и посмотрите, что произойдет. Появится страница уведомления, которая понятно описывает ошибку. -[* qs-tracy.webp .{url:-}(debugger screen) *] +[* qs-tracy.avif .{url:-}(экран отладчика) *] -Tracy существенно поможет вам в поиске ошибок. Также обратите внимание на плавающую панель Tracy Bar в правом нижнем углу, которая информирует вас о важных данных во время выполнения. +Tracy нам очень поможет, когда мы будем искать ошибки в приложении. Также обратите внимание на плавающий Tracy Bar в правом нижнем углу экрана, который содержит информацию о выполнении приложения. [* qs-tracybar.webp .{url:-} *] -В производственном режиме Tracy, разумеется, отключена и не раскрывает никакой конфиденциальной информации. Все ошибки сохраняются в директории `log/`. Просто попробуйте. В файле `app/Bootstrap.php` найдите следующий кусок кода, откомментируйте строку и измените параметр вызова метода на `false`, чтобы он выглядел следующим образом: +В production-режиме Tracy, конечно, отключена и не отображает никакой конфиденциальной информации. Все ошибки в этом случае сохраняются в папке `log/`. Давайте попробуем это. В файле `app/Bootstrap.php` раскомментируем следующую строку и изменим параметр вызова на `false`, чтобы код выглядел так: ```php .{file:app/Bootstrap.php} ... -$configurator->setDebugMode(false); -$configurator->enableTracy(__DIR__ . '/../log'); +$this->configurator->setDebugMode(false); ... ``` -После обновления веб-страницы страница с красным экраном сменится удобным для пользователя сообщением: +После обновления страницы мы больше не увидим Tracy. Вместо нее отобразится дружественное пользователю сообщение: -[* qs-fatal.webp .{url:-}(error screen) *] +[* qs-fatal.webp .{url:-}(экран ошибки) *] -Теперь загляните в каталог `log/`. Вы можете найти там журнал ошибок (в файле exception.log), а также страницу с сообщением об ошибке (сохраненную в HTML-файле с именем, начинающимся с `exception`). +Теперь посмотрим в каталог `log/`. Здесь (в файле `exception.log`) мы найдем залогированную ошибку, а также уже знакомую страницу с сообщением об ошибке (сохраненную как HTML-файл с именем, начинающимся на `exception-`). -Прокомментируйте строку `// $configurator->setDebugMode(false);` ещё раз. Tracy автоматически включает режим разработки в окружении `localhost` и отключает его в других местах. +Снова закомментируем строку `// $configurator->setDebugMode(false);`. Tracy автоматически включает режим разработки на localhost и отключает его везде. -Теперь мы можем исправить ошибку и продолжить разработку нашего приложения. +Ошибку, которую мы создали, можно исправить и продолжить писать приложение. -Отправьте спасибо .[#toc-send-thanks] -===================================== +Отправьте благодарность +======================= -Мы покажем вам трюк, который порадует авторов открытых исходников. Вы можете легко присвоить звезду на GitHub библиотекам, которые использует ваш проект. Просто наберите в консоли: +Мы покажем вам трюк, которым вы порадуете авторов open source. Простым способом вы можете поставить звездочку на GitHub библиотекам, которые использует ваш проект. Достаточно запустить: ```shell composer thanks @@ -116,4 +117,3 @@ composer thanks Попробуйте! {{priority: -1}} -{{sitename: Быстрый старт с Nette}} diff --git a/quickstart/ru/@left-menu.texy b/quickstart/ru/@left-menu.texy index 365e30696e..fb01f9acb8 100644 --- a/quickstart/ru/@left-menu.texy +++ b/quickstart/ru/@left-menu.texy @@ -1,8 +1,8 @@ Учебник ******* -- [Создайте свое первое приложение! |@home] +- [Пишем первое приложение! |@home] - [Главная страница блога |home-page] -- [Страница отдельной записи |single-post] +- [Страница с постом |single-post] - [Комментарии |comments] - [Создание и редактирование постов |creating-posts] - [Модель |model] diff --git a/quickstart/ru/@meta.texy b/quickstart/ru/@meta.texy new file mode 100644 index 0000000000..88217feb6b --- /dev/null +++ b/quickstart/ru/@meta.texy @@ -0,0 +1 @@ +{{sitename: Пишем первое приложение!}} diff --git a/quickstart/ru/authentication.texy b/quickstart/ru/authentication.texy index f967ef8e15..4c1469c51f 100644 --- a/quickstart/ru/authentication.texy +++ b/quickstart/ru/authentication.texy @@ -1,36 +1,36 @@ Аутентификация ************** -Nette предоставляет способ программирования аутентификации на нашем сайте, но не заставляет нас ничего делать. Реализация зависит от нас. Nette включает интерфейс `Nette\Security\Authenticator`, который требует только один метод `authenticate` для аутентификации пользователя, как мы хотим. +Nette предоставляет способ программирования аутентификации на наших страницах, но ни к чему нас не принуждает. Реализация зависит только от нас. Nette содержит интерфейс `Nette\Security\Authenticator`, который требует только одного метода `authenticate`, который проверяет пользователя любым способом, каким мы захотим. -Существует множество способов, с помощью которых пользователь может аутентифицировать себя. Наиболее распространенным является *аутентификация на основе пароля* (пользователь указывает свое имя или электронную почту и пароль), но существуют и другие способы. Возможно, вы видели кнопки типа «Войти с помощью Facebook/Google/Twitter/GitHub и т. д.» на многих сайтах. С Nette мы можем использовать любой метод входа в систему, а также комбинировать их. Это зависит от нас. +Существует много возможностей, как пользователь может быть аутентифицирован. Самый частый способ аутентификации — с помощью пароля (пользователь предоставляет свое имя или e-mail и пароль), но есть и другие способы. Возможно, вы знакомы с кнопками типа "Войти через Facebook" или входом через Google/Twitter/GitHub на некоторых сайтах. С Nette мы можем иметь любой метод входа, или мы можем их комбинировать. Это зависит только от нас. -Обычно мы пишем собственный аутентификатор, но для этого простого блога мы воспользуемся встроенным аутентификатором, который осуществляет вход на основе пароля и имени пользователя, хранящихся в конфигурационном файле. Это хорошо подходит для тестирования. Добавьте секцию *security* в конфигурационный файл `config/common.neon`: +Обычно мы бы написали собственный аутентификатор, но для этого простого небольшого блога мы используем встроенный аутентификатор, который входит в систему на основе пароля и имени пользователя, сохраненных в конфигурационном файле. Он подходит для тестовых целей. Добавим следующую секцию `security` в конфигурационный файл `config/common.neon`: ```neon .{file:config/common.neon} security: users: - admin: secret # логин 'admin', пароль 'secret' + admin: secret # пользователь 'admin', пароль 'secret' ``` -Nette автоматически создаст службу в контейнере DI. +Nette автоматически создаст сервис в DI-контейнере. -Форма регистрации .[#toc-sign-in-form] -====================================== +Форма входа +=========== -Теперь у нас есть готовая аутентификация, и нам нужно подготовить пользовательский интерфейс для входа в систему. Поэтому давайте создадим новый презентер под названием *SignPresenter*, который будет: +Теперь у нас готова аутентификация, и нам нужно подготовить пользовательский интерфейс для входа. Создадим новый презентер с именем `SignPresenter`, который: -- отображать форму входа в систему (запрашивается имя пользователя и пароль) -- аутентифицировать пользователя при отправке формы -- обеспечивать выход из системы +- отобразит форму входа (с именем пользователя и паролем) +- после отправки формы проверит пользователя +- предоставит возможность выхода -Давайте начнем с формы входа в систему. Вы уже знаете, как работают формы в презентере. Создайте `SignPresenter` и метод `createComponentSignInForm`. Это должно выглядеть следующим образом: +Начнем с формы входа. Мы уже знаем, как работают формы в презентерах. Создадим презентер `SignPresenter` и напишем метод `createComponentSignInForm`. Он должен выглядеть примерно так: -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Sign; use Nette; use Nette\Application\UI\Form; @@ -41,68 +41,68 @@ final class SignPresenter extends Nette\Application\UI\Presenter { $form = new Form; $form->addText('username', 'Имя пользователя:') - ->setRequired('Пожалуйста, введите ваше имя.'); + ->setRequired('Пожалуйста, введите ваше имя пользователя.'); $form->addPassword('password', 'Пароль:') ->setRequired('Пожалуйста, введите ваш пароль.'); $form->addSubmit('send', 'Войти'); - $form->onSuccess[] = [$this, 'signInFormSucceeded']; + $form->onSuccess[] = $this->signInFormSucceeded(...); return $form; } } ``` -Теперь у нас есть ввод имени пользователя и пароля. +Здесь есть поля для имени пользователя и пароля. -Шаблон .[#toc-template] ------------------------ +Шаблон +------ -Форма будет отображаться в шаблоне `app/Presenters/templates/Sign/in.latte`. +Форма будет отрисовываться в шаблоне `in.latte`: -```latte .{file:app/Presenters/templates/Sign/in.latte} +```latte .{file:app/Presentation/Sign/in.latte} {block content} -<h1 n:block=title>Войти</h1> +<h1 n:block=title>Вход</h1> {control signInForm} ``` -Обработчик входа в систему .[#toc-login-handler] ------------------------------------------------- +Callback для входа +------------------ -Далее мы добавим *обработчик формы* для входа пользователя, который будет вызван сразу после успешной отправки формы. +Далее добавим callback для входа пользователя, который будет вызван сразу после успешной отправки формы. -Обработчик просто принимает имя пользователя и пароль, которые пользователь ввел, и передает их аутентификатору. После входа в систему мы перенаправим вас на главную страницу: +Callback просто примет имя пользователя и пароль, которые пользователь ввел, и передаст их аутентификатору. После входа перенаправим на главную страницу. -```php .{file:app/Presenters/SignPresenter.php} -public function signInFormSucceeded(Form $form, \stdClass $data): void +```php .{file:app/Presentation/Sign/SignPresenter.php} +private function signInFormSucceeded(Form $form, \stdClass $data): void { try { $this->getUser()->login($data->username, $data->password); $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { - $form->addError('Неправильные логин или пароль.'); + $form->addError('Неверное имя пользователя или пароль.'); } } ``` -Метод [User::login() |api:Nette\Security\User::login()] должен выбрасывать исключение, если имя пользователя или пароль не соответствуют тем, которые мы определили ранее. Как мы уже знаем, это приведет к появлению красной страницы ошибки [Tracy|tracy:] или, в производственном режиме, сообщения о внутренней ошибке сервера. Но мы этого не хотим. Поэтому мы перехватываем исключение и добавляем в форму красивое и дружелюбное сообщение об ошибке. +Метод [User::login() |api:Nette\Security\User::login()] выбросит исключение, если имя пользователя и пароль не совпадают с данными в конфигурационном файле. Как мы уже знаем, это может привести к красной странице ошибки или, в производственном режиме, к сообщению об ошибке сервера. Этого мы не хотим. Поэтому мы перехватим это исключение и передадим красивое, дружественное к пользователю сообщение об ошибке в форму. -Когда в форме произойдет ошибка, страница с формой будет отображена снова, а над формой появится красивое сообщение, информирующее пользователя о том, что он ввел неправильное имя пользователя или пароль. +Как только в форме произойдет ошибка, страница с формой перерисуется, и над формой отобразится красивое сообщение, информирующее пользователя о том, что он ввел неправильное имя пользователя или пароль. -Безопасность презентеров .[#toc-security-of-presenters] -======================================================= +Защита презентеров +================== -Обезопасим форму для добавления и редактирования постов. Цель — предотвратить доступ к странице неавторизованным пользователям. +Защитим форму для добавления и редактирования постов. Она определена в презентере `EditPresenter`. Цель — запретить доступ к странице пользователям, которые не вошли в систему. -Создадим метод `startup()`, который запускается сразу в начале [жизненного цикла презентера|application:presenters#Life-Cycle-of-Presenter]. Это перенаправляет незарегистрированных пользователей на форму входа в систему. +Создадим метод `startup()`, который запускается сразу в начале [жизненного цикла презентера |application:presenters#Жизненный цикл презентера]. Он перенаправит неавторизованных пользователей на форму входа. -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} public function startup(): void { parent::startup(); @@ -114,35 +114,35 @@ public function startup(): void ``` -Скрываем ссылки .[#toc-hide-links] ----------------------------------- +Скрытие ссылок +-------------- -Неавторизованный пользователь больше не может видеть страницы создания и редактирования, но он всё ещё может видеть ссылки, указывающие на них. Давайте спрячем и их. Одна из таких ссылок находится в `app/Presenters/templates/Home/default.latte`, и она должна быть видна, только если пользователь вошел в систему. +Неавторизованный пользователь больше не может видеть страницы `create` и `edit`, но все еще может видеть ссылки на них. Их мы тоже должны скрыть. Одна такая ссылка находится в шаблоне `app/Presentation/Home/default.latte` и должна быть видна только вошедшим пользователям. -Мы можем скрыть её с помощью *n:атрибута* под названием `n:if`. Если утверждение внутри него `false`, то весь тег `<a>` и его содержимое не будут отображаться: +Мы можем скрыть ее с помощью *n:атрибута* с именем `n:if`. Если это условие `false`, весь тег `<a>`, включая содержимое, останется скрытым. ```latte <a n:href="Edit:create" n:if="$user->isLoggedIn()">Создать пост</a> ``` -это сокращение для (не путайте с `tag-if`): +что является сокращением следующей записи (не путать с `tag-if`): ```latte {if $user->isLoggedIn()}<a n:href="Edit:create">Создать пост</a>{/if} ``` -Аналогичным образом следует скрыть ссылку редактирования, расположенную в `app/Presenters/templates/Post/show.latte`. +Таким же образом скроем ссылку в шаблоне `app/Presentation/Post/show.latte`. -Ссылка на форму входа .[#toc-login-form-link] -============================================= +Ссылка для входа +================ -Эй, но как нам попасть на страницу входа в систему? Нет ссылки, указывающей на нее. Давайте добавим её в файл шаблона `app/Presenters/templates/@layout.latte`. Попробуйте найти хорошее место, это может быть любое место, которое вам больше всего нравится. +Как же нам попасть на страницу входа? Нет никакой ссылки, которая бы на нее вела. Так что добавим ее в шаблон `@layout.latte`. Попробуйте найти подходящее место - это может быть почти где угодно. -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <ul class="navig"> - <li><a n:href="Home:">Главная</a></li> + <li><a n:href="Home:">Статьи</a></li> {if $user->isLoggedIn()} <li><a n:href="Sign:out">Выйти</a></li> {else} @@ -152,29 +152,28 @@ public function startup(): void ... ``` -Если пользователь ещё не вошел в систему, он увидит ссылку «Войти». В противном случае будет видна ссылка «Выйти». Добавим это действие в `SignPresenter`. +Если пользователь не вошел в систему, отобразится ссылка "Войти". В противном случае отобразится ссылка "Выйти". Это действие также добавим в `SignPresenter`. -Действие выхода из системы выглядит следующим образом, и поскольку мы перенаправляем пользователя немедленно, нет необходимости в шаблоне представления: +Поскольку пользователя после выхода мы сразу же перенаправляем, никакой шаблон не нужен. Выход выглядит так: -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} public function actionOut(): void { $this->getUser()->logout(); - $this->flashMessage('Вы вышли.'); + $this->flashMessage('Выход выполнен успешно.'); $this->redirect('Home:'); } ``` -Он просто вызывает метод `logout()` и затем показывает пользователю красивое сообщение. +Просто вызывается метод `logout()`, а затем отображается красивое сообщение, подтверждающее успешный выход. -Подведём итог .[#toc-summary] -============================= +Итог +==== -У нас есть ссылка для входа в систему, а также для выхода пользователя из системы. Для аутентификации мы использовали встроенный аутентификатор, а данные для входа находятся в конфигурационном файле, поскольку это простое тестовое приложение. Мы также защитили формы редактирования, чтобы только вошедшие в систему пользователи могли добавлять и редактировать сообщения. +У нас есть ссылка для входа и также для выхода пользователя. Для проверки мы использовали встроенный аутентификатор, а данные для входа хранятся в конфигурационном файле, так как это простое тестовое приложение. Мы также защитили формы редактирования, так что добавлять и редактировать посты могут только вошедшие пользователи. .[note] -Здесь вы можете прочитать больше о [вход пользователя |security:authentication] и [авторизация |security:authorization]. +Здесь вы можете прочитать больше о [входе пользователей |security:authentication] и [проверке прав |security:authorization]. {{priority: -1}} -{{sitename: Быстрый старт с Nette}} diff --git a/quickstart/ru/comments.texy b/quickstart/ru/comments.texy index 671b0a41cc..96829a9e56 100644 --- a/quickstart/ru/comments.texy +++ b/quickstart/ru/comments.texy @@ -1,28 +1,28 @@ Комментарии *********** -Блог был развернут, мы написали несколько очень хороших постов для блога и опубликовали их через Adminer. Люди читают блог, и они очень увлечены нашими идеями. Каждый день мы получаем множество писем с похвалами. Но к чему все эти похвалы, если мы получаем их только по электронной почте, и никто больше не может их прочитать? Разве не лучше было бы, если бы люди могли оставлять комментарии прямо в блоге, чтобы все остальные могли прочитать, какие мы замечательные? +Мы загрузили блог на веб-сервер и опубликовали несколько очень интересных постов с помощью Adminer. Люди читают наш блог и очень им довольны. Мы каждый день получаем много писем с похвалами. Но какая польза от всей этой похвалы, если она есть только в электронной почте и никто не может ее прочитать? Было бы лучше, если бы читатель мог комментировать статью напрямую, чтобы каждый мог прочитать, какие мы замечательные. -Давайте сделаем все статьи комментируемыми. +Итак, давайте запрограммируем комментарии. -Создание новой таблицы .[#toc-creating-a-new-table] -=================================================== +Создание новой таблицы +====================== -Снова запустите Adminer и создайте новую таблицу с именем `comments` с этими столбцами: +Запустим Adminer и создадим таблицу `comments` со следующими столбцами: -- `id` int, отметьте автоинкремент (AI) -- `post_id`, внешний ключ, ссылающийся на таблицу `posts`. -- `name` varchar, длина 255 -- `email` varchar, длина 255 +- `id` int, отметим autoincrement (AI) +- `post_id`, внешний ключ, который ссылается на таблицу `posts` +- `name` varchar, length 255 +- `email` varchar, length 255 - `content` text - `created_at` timestamp -Это должно выглядеть следующим образом: +Таблица должна выглядеть примерно так: [* adminer-comments.webp *] -Не забудьте использовать хранилище таблиц InnoDB и нажмите кнопку Сохранить. +Не забудьте снова использовать хранилище InnoDB. ```sql CREATE TABLE `comments` ( @@ -37,104 +37,104 @@ CREATE TABLE `comments` ( ``` -Форма для комментирования .[#toc-form-for-commenting] -===================================================== +Форма для комментирования +========================= -Во-первых, нам нужно создать форму, которая позволит пользователям комментировать нашу страницу. Фреймворк Nette имеет отличную поддержку форм. Они могут быть настроены в презентере и отображены в шаблоне. +Сначала нам нужно создать форму, которая позволит пользователям комментировать посты. Nette Framework имеет отличную поддержку форм. Мы можем настроить их в презентере и отобразить в шаблоне. -В Nette есть понятие *компоненты*. **Компонент** — это многократно используемый класс или фрагмент кода, который может быть присоединен к другому компоненту. Даже презентер является компонентом. Каждый компонент создается с помощью фабрики компонентов. Итак, давайте определим фабрику формы комментариев в `PostPresenter`. +Nette Framework использует концепцию *компонентов*. **Компонент** — это повторно используемый класс или часть кода, который может быть присоединен к другому компоненту. Даже презентер является компонентом. Каждый компонент создается с помощью фабрики. Итак, создадим фабрику для формы комментариев в презентере `PostPresenter`. -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} protected function createComponentCommentForm(): Form { $form = new Form; // означает Nette\Application\UI\Form - $form->addText('name', 'Ваше имя:') + $form->addText('name', 'Имя:') ->setRequired(); - $form->addEmail('email', 'Имейл:'); + $form->addEmail('email', 'E-mail:'); $form->addTextArea('content', 'Комментарий:') ->setRequired(); - $form->addSubmit('send', 'Отправить'); + $form->addSubmit('send', 'Опубликовать комментарий'); return $form; } ``` -Давайте немного объясним это. Первая строка создает новый экземпляр компонента `Form`. Методы, которые указаны ниже, размещают HTML-элементы *input* внутри формы. `->addText` будет отображаться как `<input type=text name=name>`, с `<label>Ваше имя:</label>`. Как вы уже догадались, `->addTextArea` прикрепляет `<textarea>`, а `->addSubmit` добавляет `<input type=submit>`. Подобных методов больше, но это всё, что вам нужно знать прямо сейчас. Вы можете [узнать больше в документации|forms:]. +Давайте снова немного объясним. Первая строка создает новый экземпляр компонента `Form`. Следующие методы добавляют HTML-инпуты в определение этой формы. `->addText()` будет отображен как `<input type="text" name="name">` с `<label>Имя:</label>`. Как вы, вероятно, уже догадались, `->addTextArea()` будет отображен как `<textarea>`, а `->addSubmit()` как `<input type="submit">`. Существует гораздо больше подобных методов, но этих пока достаточно для этой формы. Больше вы можете [прочитаете в документации|forms:]. -После того как компонент формы определен в презентере, мы можем отобразить его в шаблоне. Для этого поместите тег `{control}` в конец шаблона детализации поста в `app/Presenters/templates/Post/show.latte`. Поскольку имя компонента — `commentForm` (оно происходит от названия метода `createComponentCommentForm`), тег будет выглядеть следующим образом: +Если форма уже определена в презентере, мы можем ее отрисовать (отобразить) в шаблоне. Это делается путем размещения тега `{control}` в конце шаблона, который отображает один конкретный пост, в `Post/show.latte`. Поскольку компонент называется `commentForm` (название происходит от названия метода `createComponentCommentForm`), тег будет выглядеть так: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... -<h2>Оставить комментарий</h2> +<h2>Добавьте новый комментарий</h2> {control commentForm} ``` -Теперь, если вы перейдете на отдельную страницу какого-то поста, там будет новая форма для размещения комментариев. +Теперь, если вы откроете страницу с деталями поста, в ее конце вы увидите новую форму для комментариев. -Сохранение в базе данных .[#toc-saving-to-database] -=================================================== +Сохранение в базу данных +======================== -Пытались ли вы отправить какие-либо данные? Вы могли заметить, что форма не выполняет никаких действий. Она просто есть, выглядит круто и ничего не делает. Мы должны прикрепить к ней метод обратного вызова, который будет сохранять переданные данные. +Вы уже пробовали заполнить и отправить форму? Вероятно, вы заметили, что форма на самом деле ничего не делает. Нам нужно подключить метод обратного вызова (callback), который сохранит отправленные данные. -Поместите следующую строку перед строкой `return` в фабрике компонентов для `commentForm`: +На строку перед `return` в фабрике для компонента `commentForm` поместим следующую строку: ```php -$form->onSuccess[] = [$this, 'commentFormSucceeded']; +$form->onSuccess[] = $this->commentFormSucceeded(...); ``` -Это означает «после успешной отправки формы вызвать метод `commentFormSucceeded` текущего презентера». Этот метод ещё не существует, поэтому давайте создадим его. +Предыдущая запись означает "после успешной отправки формы вызови метод `commentFormSucceeded` из текущего презентера". Однако этот метод еще не существует. Давайте его создадим: -```php .{file:app/Presenters/PostPresenter.php} -public function commentFormSucceeded(\stdClass $data): void +```php .{file:app/Presentation/Post/PostPresenter.php} +private function commentFormSucceeded(\stdClass $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); $this->database->table('comments')->insert([ - 'post_id' => $postId, + 'post_id' => $id, 'name' => $data->name, 'email' => $data->email, 'content' => $data->content, ]); - $this->flashMessage('Спасибо за комментарий!', 'success'); + $this->flashMessage('Спасибо за комментарий', 'success'); $this->redirect('this'); } ``` -Вы должны разместить его сразу после фабрики компонента `commentForm`. +Этот метод разместим сразу после фабрики формы `commentForm`. -Новый метод имеет один аргумент, которым является экземпляр отправляемой формы, созданный фабрикой компонентов. Мы получаем переданные значения в `$data`. Затем мы вставляем данные в таблицу базы данных `comments`. +Этот новый метод имеет один аргумент `$data`, который является объектом, содержащим отправленные данные формы. Мы получаем ID поста из параметров запроса. А затем сохраняем данные в таблицу базы данных `comments`. -Необходимо объяснить ещё два вызова метода. `$this->redirect('this')` буквально перенаправляет на текущую страницу. Вы должны делать это каждый раз, когда форма отправлена, действительна, и операция обратного вызова сделала то, что должна была сделать. Кроме того, когда вы перенаправляете страницу после отправки формы, вы не увидите хорошо известного сообщения `Вы хотите отправить данные сообщения снова?`, которое иногда можно увидеть в браузере. (В целом, после отправки формы методом `POST`, вы всегда должны перенаправлять пользователя на действие `GET`). +Есть еще два метода, которые заслуживают объяснения. Метод `redirect('this')` буквально перенаправляет обратно на текущую страницу. Это целесообразно делать после каждой отправки формы, если она содержала валидные данные и обратный вызов выполнил операцию так, как должен был. А также, если мы перенаправляем страницу после отправки формы, мы не увидим хорошо известное сообщение `Хотите отправить данные формы снова?`, которое иногда можно увидеть в браузере. (В общем, после отправки формы методом `POST` всегда должно следовать перенаправление на `GET` действие.) -`$this->flashMessage` предназначен для информирования пользователя о результате некоторой операции. Поскольку мы перенаправляем, сообщение не может быть напрямую передано в шаблон и отображено. Поэтому существует метод, который сохранит его и сделает доступным при следующей загрузке страницы. Флэш-сообщения отображаются в стандартном файле `app/Presenters/templates/@layout.latte`, и выглядят они следующим образом: +Метод `flashMessage()` предназначен для информирования пользователя о результате какой-либо операции. Поскольку мы перенаправляем, сообщение не может быть просто передано шаблону и отрисовано. Поэтому существует этот метод, который сохраняет это сообщение и делает его доступным при следующей загрузке страницы. Flash-сообщения отрисовываются в главном шаблоне `app/Presentation/@layout.latte` и выглядят так: -```latte +```latte .{file:app/Presentation/@layout.latte} <div n:foreach="$flashes as $flash" n:class="flash, $flash->type"> {$flash->message} </div> ``` -Как мы уже знаем, они автоматически передаются в шаблон, поэтому вам не нужно много думать об этом, это просто работает. Для получения более подробной информации [ознакомьтесь с документацией |application:presenters#Flash-Messages]. +Как мы уже знаем, flash-сообщения автоматически передаются в шаблон, так что нам не нужно об этом много думать, это просто работает. Для получения дополнительной информации [посетите документацию |application:presenters#Flash-сообщения]. -Отображение комментариев .[#toc-rendering-the-comments] -======================================================= +Отображение комментариев +======================== -Это одна из тех вещей, которые вам просто понравятся. Nette Database имеет классную функцию под названием [Explorer |database:explorer]. Помните ли вы, что мы создали таблицы как InnoDB? Adminer создал так называемые [внешние ключи |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html], которые сэкономят нам тонну работы. +Это одна из тех вещей, которые вы просто полюбите. Nette Database имеет отличную функцию под названием [Explorer |database:explorer]. Помните ли вы еще, что таблицы в базе данных мы намеренно создавали с помощью хранилища InnoDB? Adminer таким образом создал то, что называется [внешние ключи |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html], которые сэкономят нам много работы. -Nette Database Explorer использует внешние ключи для разрешения отношений между таблицами, а зная эти отношения, он может автоматически создавать запросы для вас. +Nette Database Explorer использует внешние ключи для разрешения взаимных связей между таблицами и на основе знаний об этих связях умеет автоматически создавать запросы к базе данных. -Как вы помните, мы передали переменную `$post` шаблону в `PostPresenter::renderShow()` и теперь хотим перебрать все комментарии, у которых столбец `post_id` равен нашему `$post->id`. Вы можете сделать это, вызвав `$post->related('comments')`. Это так просто. Посмотрите на полученный код. +Как вы наверняка помните, в шаблон мы передали переменную `$post` с помощью метода `PostPresenter::renderShow()`, и теперь мы хотим итерировать по всем комментариям, которые имеют значение столбца `post_id`, совпадающее с `$post->id`. Этого можно достичь вызовом `$post->related('comments')`. Да, вот так просто. Посмотрим на итоговый код: -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { ... $this->template->post = $post; @@ -142,9 +142,9 @@ public function renderShow(int $postId): void } ``` -В шаблоне: +И шаблон: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... <h2>Комментарии</h2> @@ -152,7 +152,7 @@ public function renderShow(int $postId): void {foreach $comments as $comment} <p><b><a href="mailto:{$comment->email}" n:tag-if="$comment->email"> {$comment->name} - </a></b>:</p> + </a></b> написал:</p> <div>{$comment->content}</div> {/foreach} @@ -160,13 +160,12 @@ public function renderShow(int $postId): void ... ``` -Обратите внимание на специальный атрибут `n:tag-if`. Вы уже знаете, как `n: атрибуты` работают. Если вы добавите к атрибуту `tag-`, он будет обводить только теги, а не их содержимое. Это позволяет превратить имя комментатора в ссылку, если он указал свою электронную почту. Эти две строки идентичны по результатам: +Обратите внимание на специальный атрибут `n:tag-if`. Вы уже знаете, как работают `n:атрибуты`. Если к атрибуту добавить префикс `tag-`, функциональность применяется только к HTML-тегу, а не к его содержимому. Это позволяет нам сделать имя комментатора ссылкой только в том случае, если он предоставил свой e-mail. Эти две строки идентичны: ```latte -<strong n:tag-if="$important"> Здравствуйте! </strong> +<strong n:tag-if="$important"> Добрый день! </strong> -{if $important}<strong>{/if} Здравствуйте! {if $important}</strong>{/if} +{if $important}<strong>{/if} Добрый день! {if $important}</strong>{/if} ``` {{priority: -1}} -{{sitename: Быстрый старт с Nette}} diff --git a/quickstart/ru/creating-posts.texy b/quickstart/ru/creating-posts.texy index 18c9dfd69d..0e6fc66110 100644 --- a/quickstart/ru/creating-posts.texy +++ b/quickstart/ru/creating-posts.texy @@ -1,30 +1,30 @@ Создание и редактирование постов ******************************** -Какое замечательное время. У нас появился новый суперкрутой блог, люди спорят в комментариях, и у нас наконец-то появилось время для программирования. Хотя нам нравится Adminer, писать в нем статьи для блога не очень удобно. Возможно, сейчас самое время добавить простую форму для добавления новых постов прямо из нашего приложения. Давайте сделаем это. +Это здорово! У нас есть супер крутой новый блог, люди активно обсуждают в комментариях, и у нас наконец-то есть немного времени на дальнейшее программирование. Хотя Adminer — отличный инструмент, он не совсем идеален для написания новых постов в блог. Вероятно, пришло время создать простую форму для добавления новых постов прямо из приложения. Давайте сделаем это. -Начнём с разработки пользовательского интерфейса: +Начнем с проектирования пользовательского интерфейса: -1. На главной странице добавим ссылку «Написать новый пост». -2. Она отобразит форму с заголовком и текстовой областью для содержимого. -3. Когда вы нажмете кнопку «Сохранить», она сохранит запись в блоге. +1. На главной странице добавим ссылку "Написать новый пост". +2. Эта ссылка отобразит форму с заголовком и текстовой областью для содержимого поста. +3. Когда мы нажмем кнопку Сохранить, пост сохранится в базе данных. -Позже мы также добавим аутентификацию и разрешим добавлять новые сообщения только вошедшим в систему пользователям. Какой код нам нужно написать, чтобы он работал? +Позже мы также добавим вход в систему и разрешим добавление постов только вошедшим пользователям. Но это позже. Какой код нам нужно написать сейчас, чтобы все заработало? -1. Создайте новый презентер с формой для добавления постов. -2. Определите обратный вызов, который будет срабатывать после успешной отправки формы и который сохранит новое сообщение в базе данных. -3. Создайте новый шаблон для формы. -4. Добавьте ссылку на форму в шаблон главной страницы. +1. Создадим новый презентер с формой для добавления постов. +2. Определим обратный вызов (callback), который запустится после успешной отправки формы и который сохранит новый пост в базе данных. +3. Создадим новый шаблон, на котором будет эта форма. +4. Добавим ссылку на форму в шаблон главной страницы. -Новый презентер .[#toc-new-presenter] -===================================== +Новый презентер +=============== -Назовите новый презентер `EditPresenter` и сохраните его в `app/Presenters/EditPresenter.php`. Ему также необходимо подключиться к базе данных, поэтому здесь мы снова пишем конструктор, который потребует подключения к базе данных: +Новый презентер назовем `EditPresenter` и сохраним в `app/Presentation/Edit/`. Ему также необходимо подключиться к базе данных, поэтому здесь снова напишем конструктор, который будет требовать подключение к базе данных: -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Edit; use Nette; use Nette\Application\UI\Form; @@ -39,95 +39,95 @@ final class EditPresenter extends Nette\Application\UI\Presenter ``` -Форма для сохранения сообщений .[#toc-form-for-saving-posts] -============================================================ +Форма для сохранения постов +=========================== -Формы и компоненты уже рассматривались, когда мы добавляли поддержку комментариев. Если вы запутались в теме, посмотрите [как работают формы и компоненты |comments#Form-for-Commenting] ещё раз, мы подождем здесь ;) +Формы и компоненты мы уже объяснили при создании комментариев. Если это все еще неясно, пройдите [создание форм и компонентов |comments#Форма для комментирования], мы здесь пока подождем ;) -Теперь добавьте этот метод в `EditPresenter`: +Теперь добавим этот метод в презентер `EditPresenter`: -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} protected function createComponentPostForm(): Form { $form = new Form; $form->addText('title', 'Заголовок:') ->setRequired(); - $form->addTextArea('content', 'Содержание:') + $form->addTextArea('content', 'Содержимое:') ->setRequired(); $form->addSubmit('send', 'Сохранить и опубликовать'); - $form->onSuccess[] = [$this, 'postFormSucceeded']; + $form->onSuccess[] = $this->postFormSucceeded(...); return $form; } ``` -Сохранение нового поста из формы .[#toc-saving-new-post-from-form] -================================================================== +Сохранение нового поста из формы +================================ -Добавим метод обработчика: +Продолжаем добавлением метода, который обработает данные из формы: -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { $post = $this->database ->table('posts') ->insert($data); - $this->flashMessage('Пост опубликован!', 'success'); + $this->flashMessage('Пост был успешно опубликован.', 'success'); $this->redirect('Post:show', $post->id); } ``` -Небольшое пояснение: этот метод извлекает значения из формы, вставляет их в базу данных, создает сообщение для пользователя об успешной публикации поста и перенаправляет на страницу, где этот пост опубликован, чтобы вы могли увидеть, как он выглядит. +Краткое резюме: этот метод получает данные из формы в виде массива `$data`, вставляет их в базу данных, создает сообщение для пользователя об успешном сохранении поста и перенаправляет на страницу с новым постом, чтобы мы сразу увидели, как он выглядит. -Страница для создания нового поста .[#toc-page-for-creating-a-new-post] -======================================================================= +Страница для создания нового поста +================================== -Давайте просто создадим шаблон (`app/Presenters/templates/Edit/create.latte`): +Теперь создадим шаблон `Edit/create.latte`: -```latte .{file:app/Presenters/templates/Edit/create.latte} +```latte .{file:app/Presentation/Edit/create.latte} {block content} <h1>Новый пост</h1> {control postForm} ``` -Теперь всё должно быть ясно. Последняя строка показывает форму, которую мы собираемся создать. +Все уже должно быть понятно. Последняя строка отрисовывает форму, которую мы только что создали. -Мы могли бы также создать соответствующий метод `renderCreate`, но это не обязательно. Нам не нужно получать данные из базы данных и передавать их в шаблон, поэтому этот метод будет пустым. В таких случаях метод может вообще не существовать. +Мы могли бы создать также соответствующий метод `renderCreate()`, но это не обязательно. Нам не нужно получать какие-либо данные из базы данных и передавать их в шаблон, так что этот метод был бы пустым. В таких случаях метод может вообще не существовать. -Ссылка для создания постов .[#toc-link-for-creating-posts] -========================================================== +Ссылка на создание постов +========================= -Вы, вероятно, уже знаете, как добавить ссылку на `EditPresenter` и его действие `create`. Попробуйте. +Вероятно, вы уже знаете, как добавить ссылку на `EditPresenter` и его действие `create`. Попробуйте это сделать. -Просто добавьте в файл `app/Presenters/templates/Home/default.latte`: +Достаточно в файл `app/Presentation/Home/default.latte` добавить: ```latte -<a n:href="Edit:create">Создать пост</a> +<a n:href="Edit:create">Написать новый пост</a> ``` -Редактирование постов .[#toc-editing-posts] -=========================================== +Редактирование постов +===================== -Давайте также добавим возможность редактировать существующие сообщения. Это будет довольно просто — у нас уже есть `postForm`, и мы можем использовать её и для редактирования. +Теперь добавим также возможность редактирования поста. Это будет очень просто. У нас уже есть готовая форма `postForm`, и мы можем использовать ее и для редактирования. -Мы добавим новую страницу `edit` в `EditPresenter`: +Добавим новую страницу `edit` в презентер `EditPresenter`: -```php .{file:app/Presenters/EditPresenter.php} -public function renderEdit(int $postId): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +public function renderEdit(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { - $this->error('Пост не найден'); + $this->error('Post not found'); } $this->getComponent('postForm') @@ -135,26 +135,26 @@ public function renderEdit(int $postId): void } ``` -И создайте шаблон `Edit/edit.latte`: +И создадим еще один шаблон `Edit/edit.latte`: -```latte .{file:app/Presenters/templates/Edit/edit.latte} +```latte .{file:app/Presentation/Edit/edit.latte} {block content} -<h1>Редактирование поста</h1> +<h1>Редактировать пост</h1> {control postForm} ``` -И обновите метод `postFormSucceeded`, который сможет либо добавлять новый пост (как сейчас), либо редактировать существующие: +И изменим метод `postFormSucceeded()`, который сможет как добавлять новую статью (как он делает это сейчас), так и редактировать уже существующую статью: -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); - if ($postId) { + if ($id) { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); $post->update($data); } else { @@ -163,26 +163,25 @@ public function postFormSucceeded(array $data): void ->insert($data); } - $this->flashMessage('Пост опубликован', 'success'); + $this->flashMessage('Пост был успешно опубликован.', 'success'); $this->redirect('Post:show', $post->id); } ``` -Если указан параметр `postId`, это означает, что пост редактируется. В этом случае мы проверим, действительно ли пост существует, и если да, то обновим его в базе данных. Если `postId` не указан, это означает, что будет добавлен новый пост. +Если доступен параметр `id`, это означает, что мы будем редактировать пост. В этом случае мы получаем пост из базы данных и обновляем его новыми данными `$data`. Если параметр `id` недоступен, то это означает, что должен быть добавлен новый пост, поэтому мы вставляем данные `$data`. -Но откуда берется `postId`? Это параметр, передаваемый методу `renderEdit`. +Но откуда возьмется этот параметр `id`? Это параметр, который был передан в метод `renderEdit()`. -Теперь вы можете добавить ссылку для изменения поста в шаблон `app/Presenters/templates/Post/show.latte`: +Теперь мы можем добавить ссылку в шаблон `app/Presentation/Post/show.latte`: ```latte -<a n:href="Edit:edit $post->id">Изменить пост</a> +<a n:href="Edit:edit $post->id">Редактировать пост</a> ``` -Подведём итог .[#toc-summary] -============================= +Резюме +====== -Блог работает, люди быстро комментируют, и мы больше не полагаемся на Админа для добавления новых постов. Блог полностью независим, и даже обычные люди могут размещать там свои сообщения. Но подождите, это, наверное, не нормально, что любой, я имею в виду действительно любой человек в Интернете, может написать в нашем блоге. Требуется определенная форма аутентификации, чтобы только вошедшие в систему пользователи могли размещать сообщения. Мы добавим это в следующей главе. +Блог теперь функционален, посетители активно его комментируют, и нам больше не нужен Adminer для публикации. Приложение полностью независимо, и кто угодно может добавить новый пост. Так, стоп, это, наверное, не совсем правильно, что кто угодно - и я имею в виду действительно кого угодно с доступом в интернет - может добавлять новые посты. Требуется какая-то защита, чтобы новый пост мог добавить только вошедший пользователь. Это мы рассмотрим в следующей главе. {{priority: -1}} -{{sitename: Быстрый старт с Nette}} diff --git a/quickstart/ru/home-page.texy b/quickstart/ru/home-page.texy index d678339fd4..8a7651d5cc 100644 --- a/quickstart/ru/home-page.texy +++ b/quickstart/ru/home-page.texy @@ -2,40 +2,40 @@ ********************** .[perex] -Давайте создадим главную страницу, на которой будут отображаться ваши последние посты. +Теперь мы создадим главную страницу, отображающую последние посты. -Прежде чем мы начнем, вы должны знать хотя бы некоторые основы паттерна проектирования Model-View-Presenter (аналогичного MVC((Model-View-Controller))): +Прежде чем начать, необходимо знать хотя бы основы паттерна проектирования Model-View-Presenter (похожего на MVC((Model-View-Controller))): -- **Модель** — уровень манипулирования данными. Он полностью отделен от остальной части приложения и общается только с презентерами. +- **Модель (Model)** - слой, работающий с данными. Полностью отделен от остальной части приложения. Общается только с презентером. -- **Вид** (или _Представление_) — внешний уровень определения. Он отображает запрашиваемые данные пользователю с помощью шаблонов. +- **Представление (View)** - фронтенд-слой. Отображает запрашиваемые данные с помощью шаблонов и показывает их пользователю. -- **Презентер** (или _Контроллер_) — уровень соединения. Презентер соединяет модель и вид. Обрабатывает запросы, запрашивает данные у модели и затем передает их текущему представлению. +- **Презентер (Presenter)** (или Контроллер) - связующий слой. Презентер связывает Модель и Представление. Обрабатывает запросы, запрашивает данные у Модели и возвращает их в Представление. -В случае очень простого приложения, такого как наш блог, слой Model фактически будет состоять только из запросов к самой базе данных — нам не нужен дополнительный PHP-код для этого. Нам нужно создать только слои Presenter и View. В Nette у каждого презентера есть свои представления, поэтому мы продолжим работу с ними обоими одновременно. +В случае простых приложений, каким будет наш блог, весь модельный слой будет состоять только из запросов к базе данных - для этого пока не нужен дополнительный код. Для начала создадим только презентеры и шаблоны. В Nette у каждого презентера есть свои собственные шаблоны, поэтому мы будем создавать их одновременно. -Создание базы данных с помощью Adminer .[#toc-creating-the-database-with-adminer] -================================================================================= +Создание базы данных с помощью Adminer +====================================== -Для хранения данных мы будем использовать базу данных MySQL, поскольку это наиболее распространенный выбор среди веб-разработчиков. Но если вам это не нравится, не стесняйтесь использовать базу данных по своему выбору. +Для хранения данных мы будем использовать базу данных MySQL, так как она наиболее распространена среди разработчиков веб-приложений. Однако, если вы не хотите ее использовать, смело выбирайте базу данных по своему усмотрению. -Давайте подготовим базу данных, в которой будут храниться записи нашего блога. Начнём с одной таблицы для постов. +Теперь подготовим структуру базы данных, где будут храниться статьи нашего блога. Начнем очень просто - создадим только одну таблицу для постов. -Для создания базы данных мы можем скачать [Adminer |https://www.adminer.org], или вы можете использовать другой инструмент для управления базами данных. +Для создания базы данных мы можем скачать [Adminer |https://www.adminer.org] или другой ваш любимый инструмент для управления базами данных. -Давайте откроем Adminer и создадим новую базу данных под названием `quickstart`. +Откроем Adminer и создадим новую базу данных с именем `quickstart`. -Создайте новую таблицу с именем `posts` и добавьте в нее эти столбцы: -- `id` int, нажмите на автоинкремент (AI) -- `title` varchar, длина 255 +Создадим новую таблицу с именем `posts` и следующими столбцами: +- `id` int, отметим autoincrement (AI) +- `title` varchar, length 255 - `content` text - `created_at` timestamp -Это должно выглядеть следующим образом: +Итоговая структура должна выглядеть так: [* adminer-posts.webp *] @@ -49,49 +49,46 @@ CREATE TABLE `posts` ( ``` .[caution] -Очень важно использовать хранилище таблиц **InnoDB**. Причину вы увидите позже. Пока что просто создайте всё по инструкции и нажмите кнопку Сохранить. Либо используйте полный код создания таблицы и кнопку SQL-запрос в Adminer. +Действительно важно использовать хранилище **InnoDB**. Скоро мы покажем почему. Пока просто выберите его и нажмите сохранить. -Попробуйте добавить несколько записей в блог, прежде чем мы реализуем возможность добавления новых записей непосредственно из нашего приложения. +Прежде чем создать возможность добавлять статьи в базу данных с помощью приложения, добавьте несколько примеров статей в блог вручную. ```sql INSERT INTO `posts` (`id`, `title`, `content`, `created_at`) VALUES -(1, 'Статья первая', 'Lorem ipusm dolor one', CURRENT_TIMESTAMP), -(2, 'Статья вторая', 'Lorem ipsum dolor two', CURRENT_TIMESTAMP), -(3, 'Статья третья', 'Lorem ipsum dolor three', CURRENT_TIMESTAMP); +(1, 'Article One', 'Lorem ipusm dolor one', CURRENT_TIMESTAMP), +(2, 'Article Two', 'Lorem ipsum dolor two', CURRENT_TIMESTAMP), +(3, 'Article Three', 'Lorem ipsum dolor three', CURRENT_TIMESTAMP); ``` -Подключение к базе данных .[#toc-connecting-to-the-database] -============================================================ +Подключение к базе данных +========================= -Теперь, когда база данных создана и в ней есть несколько постов, самое время отобразить их на нашей новой блестящей странице. +Теперь, когда база данных создана и в ней есть несколько статей, самое время отобразить их на нашей красивой новой странице. -Во-первых, нам нужно сообщить нашему приложению, какую базу данных использовать. Конфигурация подключения к базе данных хранится в файле `config/local.neon`. Установите соединение DSN((Имя источника данных)) и свои учётные данные. Это должно выглядеть следующим образом: +Сначала мы должны сообщить приложению, какую базу данных использовать. Подключение к базе данных настраивается в файле `config/common.neon` с помощью DSN((Data Source Name)) и учетных данных. Это должно выглядеть примерно так: -```neon .{file:config/local.neon} +```neon .{file:config/common.neon} database: dsn: 'mysql:host=127.0.0.1;dbname=quickstart' - user: *укажите здесь имя пользователя* - password: *укажите здесь пароль* + user: *здесь вставьте имя пользователя* + password: *здесь вставьте пароль к базе данных* ``` .[note] -Помните об отступах при редактировании этого файла. [Формат NEON|neon:format] принимает и пробелы, и табуляцию, но не то и другое вместе! В файле конфигурации в веб-проекте по умолчанию используется табуляция. +При редактировании этого файла будьте осторожны с отступами строк. Формат [NEON |neon:format] принимает как отступы пробелами, так и отступы табами, но не оба одновременно. Файл конфигурации по умолчанию в Web Project использует табы. -Вся конфигурация хранится в `config/` в файлах `common.neon` и `local.neon`. Файл `common.neon` содержит глобальную конфигурацию приложения, а `local.neon` содержит только параметры, специфичные для среды (например, разница между сервером разработки и рабочим сервером). +Передача подключения к базе данных +================================== -Внедрение подключения к базе данных .[#toc-injecting-the-database-connection] -============================================================================= +Презентер `HomePresenter`, который будет отвечать за вывод статей, нуждается в подключении к базе данных. Для его получения мы используем конструктор, который будет выглядеть так: -Презентер (расположенный в `app/Presenters/HomePresenter.php`), который будет перечислять статьи, нуждается в подключении к базе данных. Для этого измените конструктор следующим образом: - -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; -use Nette\Application\UI\Form; final class HomePresenter extends Nette\Application\UI\Presenter { @@ -105,12 +102,12 @@ final class HomePresenter extends Nette\Application\UI\Presenter ``` -Загрузка постов из базы данных .[#toc-loading-posts-from-the-database] -====================================================================== +Загрузка постов из базы данных +============================== -Теперь давайте извлечём посты из базы данных и передадим их в шаблон, который затем отобразит HTML-код. Для этого и предназначен так называемый метод *render*: +Теперь загрузим посты из базы данных и передадим их в шаблон, который затем отобразит их как HTML-код. Для этого предназначен так называемый метод *render*: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} public function renderDefault(): void { $this->template->posts = $this->database @@ -120,41 +117,41 @@ public function renderDefault(): void } ``` -Теперь в презентере есть один метод рендеринга `renderDefault()`, который передает данные в представление под названием `default`. Шаблоны презентера можно найти в `app/Presenters/templates/{PresenterName}/{viewName}.latte`, поэтому в данном случае шаблон будет расположен в `app/Presenters/templates/Home/default.latte`. В шаблоне теперь доступна переменная `$posts`, которая содержит посты из базы данных. +Презентер теперь содержит один метод рендеринга `renderDefault()`, который передает данные из базы данных в шаблон (Представление). Шаблоны находятся в `app/Presentation/{PresenterName}/{viewName}.latte`, так что в этом случае шаблон находится в `app/Presentation/Home/default.latte`. В шаблоне теперь будет доступна переменная `$posts`, в которой содержатся посты, полученные из базы данных. -Шаблон .[#toc-template] -======================= +Шаблон +====== -Существует общий шаблон для всей страницы (называется *layout* (макет), с заголовком, таблицами стилей, нижним колонтитулом и т. д.), а также специфические шаблоны для каждого вида (например, для отображения списка записей блога), которые могут переопределять некоторые части шаблона макета. +Для всего веб-сайта у нас есть главный шаблон (который называется *layout*, содержит шапку, стили, подвал,...) и далее конкретные шаблоны для каждого представления (View) (например, для отображения постов в блоге), которые могут переопределить некоторые части главного шаблона. -По умолчанию шаблон макета располагается в файле `app/Presenters/templates/@layout.latte`, который содержит: +По умолчанию шаблон layout находится в `app/Presentation/@layout.latte` и содержит: -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... {include content} ... ``` -`{include content}` вставляет блок с именем `content` в основной шаблон. Вы можете определить его в шаблонах каждого представления. В нашем случае мы отредактируем файл `app/Presenters/templates/Home/default.latte` следующим образом: +Запись `{include content}` вставляет в главный шаблон блок с именем `content`. Его мы будем определять в шаблонах отдельных представлений (View). В нашем случае файл `Home/default.latte` изменим следующим образом: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - Привет, мир! + Hello World {/block} ``` -Он определяет [блок |latte:tags#block] *контента*, который будет вставлен в макет. Если вы обновите браузер, то увидите страницу с текстом «Привет, мир!» (в исходном коде также с HTML заголовком и колонтитулом, определенными в `@layout.latte`). +Таким образом мы определили [блок |latte:tags#block] *content*, который будет вставлен в главный макет. Если мы снова обновим браузер, увидим страницу с текстом "Hello World" (в исходном коде также с HTML-шапкой и подвалом, определенными в `@layout.latte`). -Давайте отобразим записи блога — для этого отредактируем шаблон следующим образом: +Давайте отобразим посты из блога - шаблон изменим следующим образом: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - <h1 n:block="title">Мой блог</h1> + <h1>Мой блог</h1> {foreach $posts as $post} <div class="post"> - <div class="date">{$post->created_at|date:'j.m.Y'}</div> + <div class="date">{$post->created_at|date:'F j, Y'}</div> <h2>{$post->title}</h2> @@ -164,42 +161,41 @@ public function renderDefault(): void {/block} ``` -Если вы обновите браузер, вы увидите список записей вашего блога. Список не очень причудлив или красочен, поэтому не стесняйтесь добавить немного [блестящего CSS |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] в `www/css/style.css`, а затем вставьте ссылку на этот файл в макет (файл `@layout.latte`): +Если мы обновим браузер, увидим список всех постов. Список пока не очень красивый и не цветной, поэтому мы можем добавить в файл `www/css/style.css` несколько [CSS стилей |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] и подключить его в макете: -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <link rel="stylesheet" href="{$basePath}/css/style.css"> </head> ... ``` -Тег `{foreach}` перебирает все посты, переданные шаблону в переменной `$posts`, и выводит фрагмент HTML-кода для каждого поста. Точно так же, как это делается в PHP-коде. +Тег `{foreach}` итерирует по всем постам, которые мы передали шаблону в переменной `$posts`, и для каждого отрисовывает данный кусок HTML. Он ведет себя точно так же, как PHP-код. -Функция `|date` называется фильтром. Фильтры используются для форматирования вывода. Этот конкретный фильтр преобразует дату (например, `2013-04-12`) в более читаемую форму (`12.04.2013`). Фильтр `|truncate` усекает строку до указанной максимальной длины и добавляет многоточие в конец, если строка усечена. Поскольку это предварительный просмотр, нет смысла отображать полное содержание статьи. Другие фильтры по умолчанию [можно найти в документации |latte:filters] или вы можете создать свои собственные, если это необходимо. +Запись `|date:` мы называем фильтром. Фильтры предназначены для форматирования вывода. Этот конкретный фильтр преобразует дату (например, `2013-04-12`) в ее более читаемый вид (`April 12, 2013`). Фильтр `|truncate` обрезает строку до указанной максимальной длины и в случае, если строка укорачивается, добавляет в конец многоточие. Поскольку это предварительный просмотр, нет смысла отображать все содержимое статьи. Другие фильтры по умолчанию [найдем в документации |latte:filters] или мы можем создать собственные, когда это необходимо. -И ещё одно. Мы можем сделать код немного короче и, следовательно, проще. Мы можем заменить *теги Latte* на *n:attributes* следующим образом: +Еще одна вещь. Предыдущий код можно сократить и упростить. Этого можно достичь заменой *Latte тегов* на *n:атрибуты*: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - <h1>My blog</h1> + <h1>Мой блог</h1> <div n:foreach="$posts as $post" class="post"> <div class="date">{$post->created_at|date:'F j, Y'}</div> <h2>{$post->title}</h2> - <div>{$post->content}</div> + <div>{$post->content|truncate:256}</div> </div> {/block} ``` -`n:foreach` просто обертывает *div* блоком *foreach* (он делает точно то же самое, что и предыдущий блок кода). +Атрибут `n:foreach` оборачивает *div* блоком *foreach* (работает абсолютно так же, как предыдущий код). -Подведём итог .[#toc-summary] -============================= +Резюме +====== -У нас есть очень простая база данных MySQL с некоторыми записями в блоге. Приложение подключается к базе данных и отображает простой список постов. +Теперь у нас есть очень простая база данных MySQL с несколькими постами. Приложение подключается к этой базе данных и выводит простой список этих постов в шаблон. {{priority: -1}} -{{sitename: Быстрый старт с Nette}} diff --git a/quickstart/ru/model.texy b/quickstart/ru/model.texy index 7f0e1c4224..2426d28ae3 100644 --- a/quickstart/ru/model.texy +++ b/quickstart/ru/model.texy @@ -1,13 +1,13 @@ Модель ****** -По мере роста нашего приложения мы вскоре обнаруживаем, что нам необходимо выполнять аналогичные операции с базой данных в разных местах и в разных презентерах. Например, получать самые новые опубликованные статьи. Если мы улучшим наше приложение, добавив к статьям флаг, указывающий на состояние готовности, мы также должны пройтись по всем местам в нашем приложении и добавить условие *where*, чтобы убедиться, что выбираются только готовые статьи. +По мере роста приложения мы скоро обнаружим, что в разных местах, в разных презентерах, нам нужно выполнять похожие операции с базой данных. Например, получать последние опубликованные статьи. Если мы улучшим приложение, например, добавив к статьям флаг, указывающий, является ли она черновиком, нам придется пройти по всем местам в приложении, где статьи извлекаются из базы данных, и добавить условие where, чтобы выбирались только не черновики. -На этом этапе прямой работы с базой данных становится недостаточно, и разумнее будет помочь себе новой функцией, возвращающей опубликованные статьи. И когда позже мы добавим ещё один пункт (например, не отображать статьи с будущей датой), мы будем редактировать наш код только в одном месте. +В этот момент прямая работа с базой данных становится недостаточной, и будет удобнее воспользоваться новой функцией, которая будет возвращать нам опубликованные статьи. И когда позже мы добавим еще одно условие, например, что не должны отображаться статьи с будущей датой, мы изменим код только в одном месте. -Мы поместим функцию в класс `PostFacade` и назовем её `getPublicArticles()`. +Функцию разместим, например, в классе `PostFacade` и назовем ее `getPublicArticles()`. -Создадим наш класс модели `PostFacade` в директории `app/Model/`, чтобы позаботиться о наших статьях. Давайте поместим его в файл `PostFacade.php`. +В каталоге `app/Model/` создадим наш модельный класс `PostFacade`, который будет отвечать за статьи: ```php .{file:app/Model/PostFacade.php} <?php @@ -32,13 +32,13 @@ final class PostFacade } ``` -В этом классе мы передаем базу данных Explorer:[api:Nette\Database\Explorer]. Это позволит использовать возможности [DI-контейнера|dependency-injection:passing-dependencies]. +В классе с помощью конструктора запросим передачу Database Explorer:[api:Nette\Database\Explorer]. Таким образом, мы используем силу [DI-контейнера|dependency-injection:passing-dependencies]. -Перейдем к файлу `HomePresenter.php`, который мы отредактируем так, чтобы избавиться от зависимости от `Nette\Database\Explorer`, заменив её новой зависимостью от нашего созданного класса. +Переключимся на `HomePresenter`, который изменим так, чтобы избавиться от зависимости от `Nette\Database\Explorer` и заменить ее новой зависимостью от нашего нового класса. -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use App\Model\PostFacade; use Nette; @@ -59,10 +59,9 @@ final class HomePresenter extends Nette\Application\UI\Presenter } ``` -В секции `use` мы используем `App\Model\PostFacade`. Таким образом можно сократить наш PHP-код только до PostFacade (не бойтесь, он работает даже в комментариях, и ваша умная IDE должна быть в состоянии справиться с этим). +В секции use у нас есть `App\Model\PostFacade`, так что мы можем сократить запись в PHP коде до `PostFacade`. Этот объект мы запросим в конструкторе, запишем его в свойство `$facade` и используем в методе renderDefault. -Остался последний шаг - научить DI-контейнер создавать этот объект. Обычно это делается путем добавления пункта в файл `config/services.neon` в секции `services` с указанием полного имени класса и параметров конструктора. -Это, так сказать, регистрирует его, и объект затем называется **сервис**. Благодаря некоторой магии, называемой [autowiring |dependency-injection:autowiring], нам обычно не нужно указывать параметры конструктора, поскольку DI распознает их и передает автоматически. Таким образом, достаточно просто указать имя класса: +Остался последний шаг - научить DI-контейнер создавать этот объект. Обычно это делается так: в файл `config/services.neon` в секцию `services` добавляем пункт, указываем полное имя класса и параметры конструктора. Таким образом мы его так называемо регистрируем, и объект затем называется **сервис**. Благодаря волшебству под названием [autowiring |dependency-injection:autowiring] нам в большинстве случаев не нужно указывать параметры конструктора, потому что DI их само распознает и передаст. Таким образом, достаточно было бы указать только имя класса: ```neon .{file:config/services.neon} ... @@ -71,16 +70,15 @@ services: - App\Model\PostFacade ``` -Однако, добавлять эту строку также не обязательно. В секции `search` в начале `services.neon` определено, что все классы, заканчивающиеся на `-Facade` или `-Factory`, будут искаться DI автоматически, что также относится и к `PostFacade`. +Однако даже эту строку добавлять не нужно. В секции `search` в начале `services.neon` определено, что все классы, заканчивающиеся словом `-Facade` или `-Factory`, DI найдет сам, что и относится к `PostFacade`. -Подведём итог .[#toc-summary] -============================= +Резюме +====== -Класс `PostFacade` запрашивает `Nette\Database\Explorer` в конструкторе, и поскольку этот класс зарегистрирован в контейнере DI, контейнер создает этот экземпляр и передает его. DI таким образом создает для нас экземпляр PostFacade и передает его в конструкторе классу HomePresenter, который его запросил. Своего рода матрешка кода. :) Все компоненты запрашивают только то, что им нужно, и их не волнует, где и как это будет создано. Созданием занимается DI-контейнер. +Класс `PostFacade` в конструкторе запрашивает передачу `Nette\Database\Explorer`, и поскольку этот класс зарегистрирован в DI-контейнере, контейнер создает этот экземпляр и передает его. DI таким образом создает для нас экземпляр `PostFacade` и передает его в конструкторе классу HomePresenter, который его запросил. Такая матрешка. :) Все просто говорят, что им нужно, и не интересуются тем, где и как что создается. Созданием занимается DI-контейнер. .[note] -Подробнее о внедрении зависимостей можно прочитать [здесь |dependency-injection:introduction],а о конфигурации — [здесь |nette:configuring] +Здесь вы можете прочитать больше о [dependency injection |dependency-injection:introduction] и [конфигурации |nette:configuring]. {{priority: -1}} -{{sitename: Быстрый старт с Nette}} diff --git a/quickstart/ru/single-post.texy b/quickstart/ru/single-post.texy index c031837962..5cedaa5c53 100644 --- a/quickstart/ru/single-post.texy +++ b/quickstart/ru/single-post.texy @@ -1,17 +1,17 @@ -Страница отдельной записи -************************* +Страница поста +************** .[perex] -Давайте добавим в наш блог еще одну страницу, на которой будет отображаться содержимое одной конкретной записи блога. +Теперь мы создадим еще одну страницу блога, которая будет отображать один конкретный пост. -Нам нужно создать новый метод render, который будет получать одну конкретную запись блога и передавать её в шаблон. Иметь это представление в `HomePresenter` не очень приятно, потому что речь идёт о записи в блоге, а не о главной странице. Итак, давайте создадим новый класс `PostPresenter` и поместим его в `app/Presenters`. Ему потребуется соединение с базой данных, поэтому снова поместите туда код *внедрения зависимости*. +Нам нужно создать новый метод render, который получит одну конкретную статью и передаст ее в шаблон. Иметь этот метод в `HomePresenter` не очень красиво, потому что мы говорим о статье, а не о главной странице. Поэтому создадим `PostPresenter` в `app/Presentation/Post/`. Этот презентер также должен подключаться к базе данных, поэтому здесь снова напишем конструктор, который будет требовать подключение к базе данных. -`PostPresenter` должен выглядеть следующим образом: +`PostPresenter` мог бы выглядеть так: -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Post; use Nette; use Nette\Application\UI\Form; @@ -23,103 +23,102 @@ final class PostPresenter extends Nette\Application\UI\Presenter ) { } - public function renderShow(int $postId): void + public function renderShow(int $id): void { $this->template->post = $this->database ->table('posts') - ->get($postId); + ->get($id); } } ``` -Мы должны установить правильное пространство имен `App\Presenters` для нашего презентера. Это зависит от [presenter mapping |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7]. +Нельзя забывать указывать правильное пространство имен `App\Presentation\Post`, которое подчиняется настройке [сопоставления презентеров |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7]. -Метод `renderShow` требует один аргумент — ID отображаемого поста. Затем он загружает этот пост из базы данных и передает результат в шаблон. +Метод `renderShow` требует один аргумент - ID одной конкретной статьи, которая должна быть отображена. Затем он загружает эту статью из базы данных и передает ее в шаблон. -В шаблоне `Home/default.latte` мы добавляем ссылку на действие `Post:show`: +В шаблон `Home/default.latte` вставим ссылку на действие `Post:show`. -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a href="{link Post:show $post->id}">{$post->title}</a></h2> ... ``` -Тег `{link}` генерирует адрес URL, который указывает на действие `Post:show`. Этот тег также передает ID поста в качестве аргумента. +Тег `{link}` генерирует URL-адрес, который указывает на действие `Post:show`. Он также передает ID поста в качестве аргумента. -То же самое мы можем написать коротко, используя n:attribute: +То же самое можно записать короче с помощью n:атрибута: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a n:href="Post:show $post->id">{$post->title}</a></h2> ... ``` -Атрибут `n:href` аналогичен тегу `{link}`. +Атрибут `n:href` является аналогом тега `{link}`. -Шаблон для действия `Post:show` ещё не существует. Мы можем открыть ссылку на этот пост. [Tracy |tracy:] покажет ошибку о том, что `app/Presenters/templates/Post/show.latte` не существует. Если вы видите любой другой отчёт об ошибке, вероятно, вам нужно включить mod_rewrite в вашем веб-сервере. +Однако для действия `Post:show` еще не существует шаблона. Мы можем попробовать открыть ссылку на этот пост. [Tracy |tracy:] отобразит ошибку, потому что шаблон `Post/show.latte` еще не существует. Если вы видите другое сообщение об ошибке, то, вероятно, вам нужно будет включить `mod_rewrite` на веб-сервере. -Поэтому мы создадим `Post/show.latte` с таким содержанием: +Создадим шаблон `Post/show.latte` с таким содержимым: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} {block content} -<p><a n:href="Home:default">← вернуться к списку постов</a></p> +<p><a n:href="Home:default">← назад к списку постов</a></p> -<div class="date">{$post->created_at|date:'j.m.Y'}</div> +<div class="date">{$post->created_at|date:'F j, Y'}</div> <h1 n:block="title">{$post->title}</h1> <div class="post">{$post->content}</div> ``` -Рассмотрим некоторые моменты. +Теперь пройдемся по отдельным частям шаблона. -Первая строка начинает определение *именованного блока* под названием `content`, которые мы видели ранее. Он будет отображаться в *шаблоне макета*. +Первая строка начинает определение блока с именем "content", так же, как это было на главной странице. Этот блок снова будет отображен в главном шаблоне. Как видите, отсутствует закрывающий тег `{/block}`. Он необязателен. -Вторая строка содержит обратную ссылку на список постов блога, чтобы пользователь мог плавно перемещаться по нашему блогу вперед и назад. Мы снова используем атрибут `n:href`, поэтому Nette позаботится о генерации URL для нас. Ссылка указывает на действие `default` презентера `Home` (можно просто написать `n:href="Home:"`, так как действие `default` может быть опущено). +На следующей строке находится ссылка обратно на список статей блога, так что пользователь может легко перемещаться между списком статей и одной конкретной. Поскольку мы используем атрибут `n:href`, Nette само позаботится о генерации ссылок. Ссылка указывает на действие `default` презентера `Home` (можно также написать `n:href="Home:"`, потому что действие с именем `default` может быть опущено, оно дополнится автоматически). -Третья строка форматирует временную метку публикации с помощью фильтра `date`, как мы уже знаем. +Третья строка форматирует вывод даты с помощью фильтра, который нам уже знаком. -Четвертая строка отображает *заголовок* записи блога в виде заголовка `<h1>`. Есть часть, с которой вы, возможно, не знакомы, это `n:block="title"`. Можете ли вы догадаться, что она делает? Если вы внимательно читали предыдущие части, мы упоминали `n: атрибуты`. Вот ещё один пример. Это эквивалентно: +Четвертая строка отображает *заголовок* блога в HTML-теге `<h1>`. Этот тег содержит атрибут, который вы, возможно, не знаете (`n:block="title"`). Угадаете, что он делает? Если вы читали предыдущую часть внимательно, то уже знаете, что это `n:атрибут`. Это еще один их пример, который эквивалентен: ```latte {block title}<h1>{$post->title}</h1>{/block} ``` -Проще говоря, он *переопределяет* блок под названием `title`. Блок определен в *шаблоне макета* (`/app/Presenters/templates/@layout.latte:11`) и, как и в случае с переопределением ООП, он переопределяется здесь. Поэтому `<title>` страницы будет содержать заголовок отображаемого поста. Мы переопределили заголовок страницы, и всё, что нам было нужно, это `n:block="title"`. Здорово, да? +Проще говоря, этот блок переопределяет блок с именем `title`. Этот блок уже определен в главном шаблоне *layout* (`/app/Presentation/@layout.latte:11`), и так же, как при переопределении методов в ООП, точно так же этот блок в главном шаблоне перекрывается. Так что `<title>` страницы теперь содержит заголовок отображаемого поста, и для этого нам понадобилось использовать всего лишь один простой атрибут `n:block="title"`. Здорово, не правда ли? -Пятая и последняя строка шаблона отображает полное содержание вашего поста. +Пятая и последняя строка шаблона отображает все содержимое одного конкретного поста. -Проверка идентификатора поста .[#toc-checking-post-id] -====================================================== +Проверка ID поста +================= -Что произойдет, если кто-то изменит URL и вставит несуществующий `postId`? Мы должны предоставить пользователю красивую страницу ошибки «страница не найдена». Давайте обновим метод `render` в файле `PostPresenter.php`: +Что произойдет, если кто-то изменит ID в URL и вставит какой-то несуществующий `id`? Мы должны предложить пользователю красивую ошибку типа "страница не найдена". Изменим немного метод render в `PostPresenter`: -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { - $this->error('Страница не найдена!'); + $this->error('Страница не найдена'); } $this->template->post = $post; } ``` -Если пост не может быть найден, вызов `$this->error(...)` покажет страницу 404 с красивым и понятным сообщением. Обратите внимание, что в режиме разработки вы не увидите страницу ошибки. Вместо этого Tracy покажет исключение с полной информацией, что довольно удобно для разработки. Вы можете проверить оба режима, просто изменив значение, передаваемое в `setDebugMode` в `Bootstrap.php`. +Если пост не может быть найден, вызовом `$this->error(...)` мы отобразим страницу ошибки 404 с понятным сообщением. Обратите внимание, что в режиме разработки (localhost) вы эту страницу ошибки не увидите. Вместо этого покажется Tracy с деталями об исключении, что довольно удобно для разработки. Если мы хотим отобразить оба режима, достаточно просто изменить аргумент метода `setDebugMode` в файле `Bootstrap.php`. -Подведём итог .[#toc-summary] -============================= +Резюме +====== -У нас есть база данных с записями блога и веб-приложение с двумя представлениями: первое отображает сводку всех последних записей, а второе — одну конкретную запись. +У нас есть база данных с постами и веб-приложение, которое имеет два представления - первое отображает обзор всех постов, а второе отображает один конкретный пост. {{priority: -1}} -{{sitename: Быстрый старт с Nette}} diff --git a/quickstart/sl/@home.texy b/quickstart/sl/@home.texy index 871239483e..c9fe823278 100644 --- a/quickstart/sl/@home.texy +++ b/quickstart/sl/@home.texy @@ -1,119 +1,119 @@ -Ustvarite svojo prvo aplikacijo! -******************************** +Napišimo prvo aplikacijo! +************************* .[perex] -Spoznajte ogrodje Nette in ustvarite preprost blog s komentarji. Začnimo! +Spoznajmo skupaj Nette Framework, medtem ko ustvarjamo preprost blog s komentarji. Gremo! -Po prvih dveh poglavjih boste imeli svoj delujoči blog in pripravljeni boste objavljati svoje odlične objave, čeprav bodo funkcije po zaključku teh dveh poglavij precej omejene. Da bi bile stvari za vaše uporabnike prijetnejše, preberite tudi naslednja poglavja in še naprej izboljšujte svojo aplikacijo. +Že po prvih dveh poglavjih bomo imeli svoj lasten delujoč blog in bomo lahko objavljali svoje odlične prispevke, čeprav bodo funkcije zaenkrat precej omejene. Prebrati bi morali tudi naslednja poglavja, kjer bomo programirali dodajanje komentarjev, urejanje člankov in na koncu blog zavarovali. .[tip] -Ta vadnica predpostavlja, da ste dokončali dokument [Namestitev |nette:installation] in uspešno nastavili svoje orodje. +Ta vadnica predpostavlja, da ste prebrali stran [Namestitev |nette:installation] in uspešno pripravili potrebna orodja. Prav tako predpostavlja, da razumete [objektno usmerjeno programiranje v PHP |nette:introduction-to-object-oriented-programming]. -Uporabite PHP 8.0 ali novejšo različico. Celotno aplikacijo lahko najdete [na GitHubu |https://github.com/nette-examples/quickstart/tree/v4.0]. +Prosimo, uporabljajte PHP 8.1 ali novejšo različico. Celotno aplikacijo najdete [na GitHubu |https://github.com/nette-examples/quickstart/tree/v4.0]. -Dobrodošla stran .[#toc-the-welcome-page] -========================================= +Pozdravna stran +=============== -Začnimo z ustvarjanjem novega projekta v imeniku `nette-blog`: +Začnimo z ustvarjanjem novega projekta v imenik `nette-blog`: ```shell composer create-project nette/web-project nette-blog ``` -V tem trenutku se mora zagnati pozdravna stran spletnega projekta. Preizkusite jo tako, da odprete brskalnik in odprete naslednji naslov URL: +V tem trenutku bi morala že delovati uvodna stran Web Projecta. Preizkusimo to z odprtjem brskalnika na naslednjem URL naslovu: ``` http://localhost/nette-blog/www/ ``` -in videli boste pozdravno stran ogrodja Nette: +in videli bomo uvodno stran Nette Frameworka: [* qs-welcome.webp .{url: http://localhost/nette-blog/www/} *] -Aplikacija deluje in zdaj jo lahko začnete spreminjati. +Aplikacija deluje in lahko začnete delati spremembe. .[note] -Če imate težave, [poskusite z nekaj nasveti |nette:troubleshooting#Nette Is Not Working, White Page Is Displayed]. +Če je prišlo do težave, [poskusite teh nekaj nasvetov |nette:troubleshooting#Nette mi ne deluje prikazuje se bela stran]. -Vsebina spletnega projekta .[#toc-web-project-s-content] -======================================================== +Vsebina Web Projecta +==================== -Spletni projekt ima naslednjo strukturo: +Web Project ima naslednjo strukturo: /--pre <b>nette-blog/</b> -├── <b>app/</b> ← application directory -│ ├── <b>Presenters/</b> ← presenter classes -│ │ └── <b>templates/</b>← templates -│ ├── <b>Router/</b> ← configuration of URL addresses -│ └── <b>Bootstrap.php</b> ← booting class Bootstrap -├── <b>bin/</b> ← scripts for the command line -├── <b>config/</b> ← configuration files -├── <b>log/</b> ← error logs -├── <b>temp/</b> ← temporary files, cache, … -├── <b>vendor/</b> ← libraries installed by Composer -│ └── <b>autoload.php</b> ← autoloading of libraries installed by Composer -└── <b>www/</b> ← public folder - the only place accessible from browser - └── <b>index.php</b> ← initial file that launches the application +├── <b>app/</b> ← imenik z aplikacijo +│ ├── <b>Core/</b> ← osnovni razredi, potrebni za delovanje +│ ├── <b>Presentation/</b> ← presenterji, predloge & co. +│ │ └── <b>Home/</b> ← imenik presenterja Home +│ └── <b>Bootstrap.php</b> ← zagonski razred Bootstrap +├── <b>assets/</b> ← neobdelana sredstva (SCSS, TypeScript, izvorne slike) +├── <b>bin/</b> ← skripte, ki se zaganjajo iz ukazne vrstice +├── <b>config/</b> ← konfiguracijske datoteke +├── <b>log/</b> ← beleženje napak +├── <b>temp/</b> ← začasne datoteke, predpomnilnik, … +├── <b>vendor/</b> ← knjižnice, nameščene s Composerjem +│ └── <b>autoload.php</b> ← samodejno nalaganje vseh nameščenih paketov +└── <b>www/</b> ← javni imenik - edini dostopen iz brskalnika + ├── <b>assets/</b> ← sestavljene statične datoteke (CSS, JS, slike, ...) + └── <b>index.php</b> ← začetna datoteka, s katero se aplikacija zažene \-- -V imeniku `www` so shranjene slike, JavaScript, CSS in druge javno dostopne datoteke. To je edini imenik, ki je neposredno dostopen iz brskalnika, zato lahko sem usmerite korenski imenik svojega spletnega strežnika (to lahko nastavite v programu Apache, vendar to storimo pozneje, saj zdaj to ni pomembno). +Imenik `www/` je namenjen shranjevanju slik, JavaScript datotek, CSS stilov in drugih javno dostopnih datotek. Samo ta imenik je dostopen z interneta, zato nastavite korenski imenik vaše aplikacije tako, da kaže prav sem (to lahko nastavite v konfiguraciji Apache ali nginx, ampak naredimo to kasneje, zdaj to ni pomembno). -Najpomembnejši imenik za vas je `app/`. V njem najdete datoteko `Bootstrap.php`, znotraj katere je razred, ki naloži ogrodje in konfigurira aplikacijo. Vključi [samodejno nalaganje |robot-loader:] ter nastavi [razhroščevalnik |tracy:] in [poti |application:routing]. +Najpomembnejša mapa za nas je `app/`. Tu najdemo datoteko `Bootstrap.php`, v kateri je razred, ki služi za nalaganje celotnega ogrodja in nastavitev aplikacije. Tu se aktivira [samodejno nalaganje |robot-loader:], nastavi se [razhroščevalnik |tracy:] in [poti |application:routing]. -Očistite .[#toc-cleanup] -======================== +Čiščenje +======== -Spletni projekt vsebuje pozdravno stran, ki jo lahko odstranimo - odstranite datoteko `app/Presenters/templates/Home/default.latte` in jo nadomestite z besedilom "Hello world!". +Web Project vsebuje uvodno stran, ki jo bomo izbrisali, preden začnemo karkoli programirati. Brez skrbi torej zamenjamo vsebino datoteke `app/Presentation/Home/default.latte` z "Hello world!". [* qs-hello.webp .{url:-} *] -Tracy (Debugger) .[#toc-tracy-debugger] -======================================= +Tracy (razhroščevalnik) +======================= -Izjemno pomembno orodje za razvoj je [razhroščevalnik Tracy. |tracy:] Poskusite narediti nekaj napak v svoji datoteki `app/Presenters/HomePresenter.php` (npr. odstranite oglati oklepaj iz definicije razreda HomePresenter) in si oglejte, kaj se bo zgodilo. Pojavila se bo stran z rdečim zaslonom in razumljivim opisom napake. +Izjemno pomembno orodje za razvoj je [razhroščevalno orodje Tracy |tracy:]. Poskusite povzročiti kakšno napako v datoteki `app/Presentation/Home/HomePresenter.php` (npr. z odstranitvijo zavitega oklepaja v definiciji razreda HomePresenter) in poglejte, kaj se zgodi. Pojavi se obvestilna stran, ki napako razumljivo opisuje. -[* qs-tracy.webp .{url:-}(debugger screen) *] +[* qs-tracy.avif .{url:-}(zaslon razhroščevalnika) *] -Tracy vam bo bistveno pomagal pri lovljenju napak. Upoštevajte tudi plavajočo vrstico Tracy v spodnjem desnem kotu, ki vas obvešča o pomembnih podatkih v času izvajanja. +Tracy nam bo izjemno pomagal, ko bomo iskali napake v aplikaciji. Prav tako opazite plavajočo Tracy vrstico v desnem spodnjem kotu zaslona, ki vsebuje informacije iz izvajanja aplikacije. [* qs-tracybar.webp .{url:-} *] -V produkcijskem načinu je Tracy seveda onemogočen in ne razkriva nobenih občutljivih informacij. Namesto tega se vse napake shranijo v imenik `log/`. Preizkusite ga. V naslovu `app/Bootstrap.php` poiščite naslednji del kode, odkomentirajte vrstico in spremenite parameter klica metode v `false`, tako da bo videti takole: +V produkcijskem načinu je Tracy seveda izklopljena in ne prikazuje nobenih občutljivih informacij. Vse napake so v tem primeru shranjene v mapi `log/`. Poskusimo to. V datoteki `app/Bootstrap.php` odkomentiramo naslednjo vrstico in spremenimo parameter klica na `false`, da koda izgleda takole: ```php .{file:app/Bootstrap.php} ... -$configurator->setDebugMode(false); -$configurator->enableTracy(__DIR__ . '/../log'); +$this->configurator->setDebugMode(false); ... ``` -Po osvežitvi spletne strani bo stran z rdečim zaslonom zamenjalo uporabniku prijazno sporočilo: +Po osvežitvi strani Tracyja ne bomo več videli. Namesto njega se prikaže uporabniku prijazno sporočilo: -[* qs-fatal.webp .{url:-}(error screen) *] +[* qs-fatal.webp .{url:-}(zaslon napake) *] -Zdaj poglejte v imenik `log/`. Tam boste našli dnevnik napak (v datoteki exception.log) in tudi stran s sporočilom o napaki (shranjeno v datoteki HTML z imenom, ki se začne s `exception`). +Zdaj poglejmo v imenik `log/`. Tu (v datoteki `exception.log`) najdemo zabeleženo napako in tudi že znano stran s sporočilom o napaki (shranjeno kot HTML datoteka z imenom, ki se začne z `exception-`). -Ponovno komentirajte vrstico `// $configurator->setDebugMode(false);`. Tracy samodejno omogoči razvojni način v okolju `localhost` in ga drugje onemogoči. +Ponovno zakomentiramo vrstico `// $configurator->setDebugMode(false);`. Tracy samodejno omogoči razvojni način na localhostu in ga onemogoči povsod drugje. -Zdaj lahko odpravimo napako in nadaljujemo z načrtovanjem naše aplikacije. +Napako, ki smo jo ustvarili, lahko popravimo in nadaljujemo s pisanjem aplikacije. -Pošljite zahvalo .[#toc-send-thanks] -==================================== +Pošljite zahvalo +================ -Pokazali vam bomo trik, ki bo razveselil avtorje odprte kode. Knjižnicam, ki jih uporablja vaš projekt, lahko na GitHubu preprosto dodate zvezdico. Preprosto zaženite: +Pokazali vam bomo trik, s katerim boste razveselili avtorje odprtokodnih projektov. Na preprost način boste na GitHubu dali zvezdico knjižnicam, ki jih vaš projekt uporablja. Samo zaženite: ```shell composer thanks ``` -Preizkusite! +Poskusite! {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/sl/@left-menu.texy b/quickstart/sl/@left-menu.texy index a27a314c3e..9dbda7e191 100644 --- a/quickstart/sl/@left-menu.texy +++ b/quickstart/sl/@left-menu.texy @@ -1,9 +1,9 @@ Vadnica ******* -- [Ustvarite svojo prvo aplikacijo |@home]! -- [Domača stran spletnega dnevnika |home-page] -- [Stran z eno objavo |single-post] +- [Napišimo prvo aplikacijo! |@home] +- [Uvodna stran bloga |home-page] +- [Stran s prispevkom |single-post] - [Komentarji |comments] -- [Ustvarjanje in urejanje objav |creating-posts] +- [Ustvarjanje in urejanje prispevkov |creating-posts] - [Model |Model] -- [Preverjanje pristnosti |authentication] +- [Avtentikacija |authentication] diff --git a/quickstart/sl/@meta.texy b/quickstart/sl/@meta.texy new file mode 100644 index 0000000000..aa52625b5c --- /dev/null +++ b/quickstart/sl/@meta.texy @@ -0,0 +1 @@ +{{sitename: Napišimo prvo aplikacijo!}} diff --git a/quickstart/sl/authentication.texy b/quickstart/sl/authentication.texy index e55096dab4..e20a66a0f6 100644 --- a/quickstart/sl/authentication.texy +++ b/quickstart/sl/authentication.texy @@ -1,11 +1,11 @@ -Preverjanje pristnosti -********************** +Avtentikacija +************* -Nette vam ponuja smernice za programiranje avtentikacije na vaši strani, vendar vas ne sili, da to storite na kakršen koli poseben način. Izvedba je odvisna od vas. Nette ima vmesnik `Nette\Security\Authenticator`, ki vas prisili, da implementirate le eno metodo, imenovano `authenticate`, ki poišče uporabnika, kakorkoli želite. +Nette zagotavlja način za programiranje avtentikacije na naših straneh, vendar nas v nič ne sili. Implementacija je samo naša. Nette vsebuje vmesnik `Nette\Security\Authenticator`, ki zahteva samo eno metodo `authenticate`, ki preverja uporabnika na kakršen koli način želimo. -Obstaja veliko načinov, kako se lahko uporabnik avtentificira. Najpogostejši način je *avtentikacija na podlagi gesla* (uporabnik navede svoje ime ali e-pošto in geslo), vendar obstajajo tudi drugi načini. Morda poznate gumbe "Prijava s Facebookom" na številnih spletnih mestih ali prijavo prek Googla/Twitterja/GitHuba ali katerega koli drugega spletnega mesta. Z Nette lahko uporabite kateri koli način avtentikacije, ki ga želite, lahko pa jih tudi kombinirate. Vse je odvisno od vas. +Obstaja veliko možnosti, kako je lahko uporabnik preverjen. Najpogostejši način preverjanja je z geslom (uporabnik navede svoje ime ali e-pošto in geslo), vendar obstajajo tudi drugi načini. Morda poznate gumbe tipa "Prijavi se s Facebookom" ali prijavo z Google/Twitter/GitHub na nekaterih straneh. Z Nette lahko imamo katero koli metodo prijave ali pa jih lahko celo kombiniramo. Odvisno je samo od nas. -Običajno bi napisali svoj avtentifikator, vendar bomo za ta preprost mali blog uporabili vgrajeni avtentifikator, ki avtentificira na podlagi gesla in uporabniškega imena, shranjenega v konfiguracijski datoteki. To je dobro za namene testiranja. Zato bomo v konfiguracijsko datoteko `config/common.neon` dodali naslednji razdelek *security*: +Običajno bi napisali lasten avtentikator, vendar bomo za ta preprost majhen blog uporabili vgrajeni avtentikator, ki prijavlja na podlagi gesla in uporabniškega imena, shranjenega v konfiguracijski datoteki. Primeren je za testne namene. Dodajmo torej naslednji odsek `security` v konfiguracijsko datoteko `config/common.neon`: ```neon .{file:config/common.neon} @@ -14,23 +14,23 @@ security: admin: secret # uporabnik 'admin', geslo 'secret' ``` -Nette bo samodejno ustvaril storitev v vsebniku DI. +Nette samodejno ustvari storitev v DI vsebniku. -Obrazec za prijavo .[#toc-sign-in-form] -======================================= +Prijavni obrazec +================ -Zdaj imamo pripravljen zaledni del avtentikacije, zagotoviti pa moramo uporabniški vmesnik, prek katerega se bo uporabnik prijavil. Ustvarimo nov predstavnik z imenom *SignPresenter*, ki bo +Zdaj imamo avtentikacijo pripravljeno in moramo pripraviti uporabniški vmesnik za prijavo. Ustvarimo torej nov presenter z imenom `SignPresenter`, ki: -- prikazal obrazec za prijavo (ki bo zahteval uporabniško ime in geslo) -- preveril pristnost uporabnika, ko bo obrazec oddan -- zagotovil dejanje odjave +- prikaže prijavni obrazec (z uporabniškim imenom in geslom) +- po oddaji obrazca preveri uporabnika +- zagotovi možnost odjave -Začnimo z obrazcem za prijavo. Že veste, kako delujejo obrazci v predstavitvenem programu. Ustvarite `SignPresenter` in metodo `createComponentSignInForm`. Izgledati morata takole: +Začnimo s prijavnim obrazcem. Že vemo, kako obrazci v presenterjih delujejo. Ustvarimo si torej presenter `SignPresenter` in zapišemo metodo `createComponentSignInForm`. Moral bi izgledati nekako takole: -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Sign; use Nette; use Nette\Application\UI\Form; @@ -40,69 +40,69 @@ final class SignPresenter extends Nette\Application\UI\Presenter protected function createComponentSignInForm(): Form { $form = new Form; - $form->addText('username', 'Username:') - ->setRequired('Please enter your username.'); + $form->addText('username', 'Uporabniško ime:') + ->setRequired('Prosimo, vnesite svoje uporabniško ime.'); - $form->addPassword('password', 'Password:') - ->setRequired('Please enter your password.'); + $form->addPassword('password', 'Geslo:') + ->setRequired('Prosimo, vnesite svoje geslo.'); - $form->addSubmit('send', 'Sign in'); + $form->addSubmit('send', 'Prijavi se'); - $form->onSuccess[] = [$this, 'signInFormSucceeded']; + $form->onSuccess[] = $this->signInFormSucceeded(...); return $form; } } ``` -Na voljo je vnos za uporabniško ime in geslo. +Tukaj sta polji za uporabniško ime in geslo. -Predloga .[#toc-template] -------------------------- +Predloga +-------- -Obrazec bo prikazan v predlogi `in.latte` +Obrazec se bo izrisal v predlogi `in.latte`: -```latte .{file:app/Presenters/templates/Sign/in.latte} +```latte .{file:app/Presentation/Sign/in.latte} {block content} -<h1 n:block=title>Sign in</h1> +<h1 n:block=title>Prijava</h1> {control signInForm} ``` -Obvladovalnik prijave .[#toc-login-handler] -------------------------------------------- +Povratni klic za prijavo +------------------------ -Dodamo tudi *obdelovalnik obrazca* za prijavo uporabnika, ki se sproži takoj po oddaji obrazca. +Nato dodamo povratni klic za prijavo uporabnika, ki bo klican takoj po uspešni oddaji obrazca. -Obvladovalnik bo samo prevzel uporabniško ime in geslo, ki ju je vnesel uporabnik, ter ju posredoval prej opredeljenemu avtentifikatorju. Ko se uporabnik prijavi, ga bomo preusmerili na domačo stran. +Povratni klic samo prevzame uporabniško ime in geslo, ki ju je uporabnik izpolnil, in ju preda avtentikatorju. Po prijavi preusmerimo na začetno stran. -```php .{file:app/Presenters/SignPresenter.php} -public function signInFormSucceeded(Form $form, \stdClass $data): void +```php .{file:app/Presentation/Sign/SignPresenter.php} +private function signInFormSucceeded(Form $form, \stdClass $data): void { try { $this->getUser()->login($data->username, $data->password); $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { - $form->addError('Incorrect username or password.'); + $form->addError('Napačno uporabniško ime ali geslo.'); } } ``` -Metoda [User::login() |api:Nette\Security\User::login()] mora zavreči izjemo, če se uporabniško ime ali geslo ne ujemata s tistima, ki smo ju opredelili prej. Kot že vemo, bi to povzročilo rdeči zaslon [Tracy |tracy:] ali, v produkcijskem načinu, sporočilo, ki obvešča o notranji napaki strežnika. To nam ne bi bilo všeč. Zato ujamemo izjemo in v obrazec dodamo lepo in prijazno sporočilo o napaki. +Metoda [User::login() |api:Nette\Security\User::login()] vrže izjemo, če se uporabniško ime in geslo ne ujemata s podatki v konfiguracijski datoteki. Kot že vemo, lahko to privede do rdeče strani z napako ali v produkcijskem načinu do sporočila o napaki strežnika. Tega pa nočemo. Zato to izjemo ujamemo in predamo lepo, uporabniku prijazno sporočilo o napaki v obrazec. -Ko se v obrazcu pojavi napaka, se stran z obrazcem ponovno prikaže, nad obrazcem pa je lepo sporočilo, ki uporabnika obvesti, da je vnesel napačno uporabniško ime ali geslo. +Ko pride do napake v obrazcu, se stran z obrazcem ponovno izriše in nad obrazcem se prikaže lepo sporočilo, ki uporabnika obvešča, da je vnesel napačno uporabniško ime ali geslo. -Varnost predstavnikov .[#toc-security-of-presenters] -==================================================== +Zavarovanje presenterjev +======================== -Zagotovili bomo obrazec za dodajanje in urejanje objav. Opredeljen je v predstavniku `EditPresenter`. Cilj je preprečiti dostop do strani neprijavljenim uporabnikom. +Zavarovali bomo obrazec za dodajanje in urejanje prispevkov. Ta je definiran v presenterju `EditPresenter`. Cilj je onemogočiti dostop do strani uporabnikom, ki niso prijavljeni. -Ustvarimo metodo `startup()`, ki se zažene takoj na začetku [življenjskega cikla predstavnika |application:presenters#life-cycle-of-presenter]. Ta uporabnike, ki niso prijavljeni, preusmeri na obrazec za prijavo. +Ustvarimo metodo `startup()`, ki se zažene takoj na začetku [življenjskega cikla presenterja |application:presenters#Življenjski cikel presenterja]. Ta preusmeri neprijavljene uporabnike na obrazec za prijavo. -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} public function startup(): void { parent::startup(); @@ -114,67 +114,66 @@ public function startup(): void ``` -Skrij povezave .[#toc-hide-links] ---------------------------------- +Skrivanje povezav +----------------- -Uporabnik brez avtentikacije ne more več videti strani *ustvari* in *revidiraj*, še vedno pa lahko vidi povezave, ki kažejo nanju. Skrijmo tudi te. Ena takšnih povezav je na naslovu `app/Presenters/templates/Home/default.latte`, vidna pa mora biti le, če je uporabnik prijavljen. +Nepooblaščen uporabnik že ne more videti strani `create` ali `edit`, vendar še vedno lahko vidi povezave do njih. Te bi morali tudi skriti. Ena taka povezava je v predlogi `app/Presentation/Home/default.latte` in bi jo morali videti samo prijavljeni uporabniki. -Skrijemo jo lahko z uporabo *n:atributa*, imenovanega `n:if`. Če je izjava v njem `false`, se celotna `<a>` oznaka in njena vsebina ne bosta prikazani: +Lahko jo skrijemo z uporabo *n:atributa* z imenom `n:if`. Če je ta pogoj `false`, celotna značka `<a>`, vključno z vsebino, ostane skrita. ```latte -<a n:href="Edit:create" n:if="$user->isLoggedIn()">Create post</a> +<a n:href="Edit:create" n:if="$user->isLoggedIn()">Ustvari prispevek</a> ``` -To je bližnjica za (ne zamenjujte s `tag-if`): +kar je okrajšava naslednjega zapisa (ne zamenjujte z `tag-if`): ```latte -{if $user->isLoggedIn()}<a n:href="Edit:create">Create post</a>{/if} +{if $user->isLoggedIn()}<a n:href="Edit:create">Ustvari prispevek</a>{/if} ``` -Na podoben način morate skriti povezavo za urejanje, ki se nahaja na naslovu `app/Presenters/templates/Post/show.latte`. +Na enak način skrijemo tudi povezavo v predlogi `app/Presentation/Post/show.latte`. -Povezava na prijavni obrazec .[#toc-login-form-link] -==================================================== +Povezava za prijavo +=================== -Hej, ampak kako pridemo do prijavne strani? Ni nobene povezave, ki bi kazala nanjo. Dodajmo jo v datoteko predloge `@layout.latte`. Poskusi najti lepo mesto, lahko je kjerkoli, kjer ti je najbolj všeč. +Kako pravzaprav pridemo do prijavne strani? Ni nobene povezave, ki bi vodila nanjo. Torej jo dodajmo v predlogo `@layout.latte`. Poskusite najti primerno mesto - lahko je skoraj kjerkoli. -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <ul class="navig"> - <li><a n:href="Home:">Home</a></li> + <li><a n:href="Home:">Članki</a></li> {if $user->isLoggedIn()} - <li><a n:href="Sign:out">Sign out</a></li> + <li><a n:href="Sign:out">Odjava</a></li> {else} - <li><a n:href="Sign:in">Sign in</a></li> + <li><a n:href="Sign:in">Prijava</a></li> {/if} </ul> ... ``` -Če uporabnik še ni prijavljen, bomo prikazali povezavo "Prijavite se". V nasprotnem primeru bomo prikazali povezavo "Odjavi se". To dejanje dodamo v SignPresenter. +Če uporabnik ni prijavljen, se prikaže povezava "Prijavi se". V nasprotnem primeru se prikaže povezava "Odjavi se". To dejanje dodamo tudi v `SignPresenter`. -Akcija odjave je videti takole, in ker uporabnika takoj preusmerimo, ne potrebujemo predloge za prikaz. +Ker uporabnika po odjavi takoj preusmerimo, ni potrebna nobena predloga. Odjava izgleda takole: -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} public function actionOut(): void { $this->getUser()->logout(); - $this->flashMessage('You have been signed out.'); + $this->flashMessage('Odjava je bila uspešna.'); $this->redirect('Home:'); } ``` -Samo pokliče metodo `logout()` in nato uporabniku prikaže lepo sporočilo. +Samo pokliče se metoda `logout()` in nato se prikaže lepo sporočilo, ki potrjuje uspešno odjavo. -Povzetek .[#toc-summary] -======================== +Povzetek +======== -Imamo povezavo za prijavo in tudi za odjavo uporabnika. Za preverjanje pristnosti smo uporabili vgrajeni avtentifikator, podatki za prijavo pa so v konfiguracijski datoteki, saj gre za preprosto testno aplikacijo. Zavarovali smo tudi obrazce za urejanje, tako da lahko samo prijavljeni uporabniki dodajajo in urejajo objave. +Imamo povezavo za prijavo in tudi odjavo uporabnika. Za preverjanje smo uporabili vgrajeni avtentikator in prijavne podatke imamo v konfiguracijski datoteki, saj gre za preprosto testno aplikacijo. Prav tako smo zavarovali urejevalne obrazce, tako da lahko prispevke dodajajo in urejajo samo prijavljeni uporabniki. .[note] -Tukaj lahko preberete več o [prijavi |security:authentication] in [avtorizaciji |security:authorization] [uporabnikov |security:authentication]. +Tukaj si lahko preberete več o [prijavljanju uporabnikov |security:authentication] in [Preverjanju dovoljenj |security:authorization]. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/sl/comments.texy b/quickstart/sl/comments.texy index a39c26c096..5fec0f1b3a 100644 --- a/quickstart/sl/comments.texy +++ b/quickstart/sl/comments.texy @@ -1,28 +1,28 @@ Komentarji ********** -Blog je bil uveden, napisali smo nekaj zelo dobrih blogov in jih objavili prek Adminerja. Ljudje berejo blog in so zelo navdušeni nad našimi idejami. Vsak dan prejmemo veliko elektronskih sporočil s pohvalami. Toda čemu so vse te pohvale, če jih dobimo samo v elektronski pošti, tako da jih nihče drug ne more prebrati? Ali ne bi bilo bolje, če bi ljudje lahko komentirali neposredno na blogu, tako da bi vsi drugi lahko prebrali, kako super smo? +Blog smo naložili na spletni strežnik in z uporabo Adminerja objavili nekaj zelo zanimivih prispevkov. Ljudje berejo naš blog in so nad njim zelo navdušeni. Vsak dan prejemamo veliko e-poštnih sporočil s pohvalami. Toda kaj nam pomaga vsa ta pohvala, če jo imamo samo v e-pošti in je nihče ne more prebrati? Bolje bi bilo, če bi bralec lahko neposredno komentiral članek, tako da bi lahko vsak prebral, kako čudoviti smo. -Naj bo mogoče komentirati vse članke. +Torej, programirajmo komentarje. -Ustvarjanje nove tabele .[#toc-creating-a-new-table] -==================================================== +Ustvarjanje nove tabele +======================= -Ponovno zaženite program Adminer in ustvarite novo tabelo z imenom `comments` s temi stolpci: +Zaženimo Adminer in ustvarimo tabelo `comments` z naslednjimi stolpci: -- `id` int, preverite samodejni prirastek (AI) -- `post_id`, tuj ključ, ki se sklicuje na tabelo `posts` -- `name` varchar, dolžina 255 -- `email` varchar, dolžina 255 -- `content` besedilo -- `created_at` časovni žig +- `id` int, označimo autoincrement (AI) +- `post_id`, tuji ključ, ki se nanaša na tabelo `posts` +- `name` varchar, length 255 +- `email` varchar, length 255 +- `content` text +- `created_at` timestamp -Izgledati mora takole: +Tabela bi torej morala izgledati nekako takole: [* adminer-comments.webp *] -Ne pozabite uporabiti shranjevanja tabel InnoDB in pritisnite Shrani. +Ne pozabite ponovno uporabiti shrambe InnoDB. ```sql CREATE TABLE `comments` ( @@ -37,83 +37,83 @@ CREATE TABLE `comments` ( ``` -Obrazec za komentiranje .[#toc-form-for-commenting] -=================================================== +Obrazec za komentiranje +======================= -Najprej moramo ustvariti obrazec, ki bo uporabnikom omogočal komentiranje na naši strani. Okvir Nette ima odlično podporo za obrazce. Lahko jih konfigurirate v predstavniku in prikažete v predlogi. +Najprej moramo ustvariti obrazec, ki bo uporabnikom omogočal komentiranje prispevkov. Nette Framework ima odlično podporo za obrazce. Lahko jih konfiguriramo v presenterju in izrišemo v predlogi. -Nette Framework ima koncept *komponent*. Komponenta** je razred ali del kode, ki ga je mogoče ponovno uporabiti in ga je mogoče priključiti na drugo komponento. Tudi predstavnik je komponenta. Vsaka komponenta je ustvarjena s pomočjo tovarne komponent. Zato definirajmo tovarno obrazca za komentarje v `PostPresenter`. +Nette Framework uporablja koncept *komponent*. **Komponenta** je ponovno uporabljiv razred ali del kode, ki ga je mogoče priložiti drugi komponenti. Celo presenter je komponenta. Vsaka komponenta je ustvarjena preko tovarne. Torej bomo ustvarili tovarno za obrazec za komentarje v presenterju `PostPresenter`. -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} protected function createComponentCommentForm(): Form { $form = new Form; // pomeni Nette\Application\UI\Form - $form->addText('name', 'Your name:') + $form->addText('name', 'Ime:') ->setRequired(); - $form->addEmail('email', 'Email:'); + $form->addEmail('email', 'E-pošta:'); - $form->addTextArea('content', 'Comment:') + $form->addTextArea('content', 'Komentar:') ->setRequired(); - $form->addSubmit('send', 'Publish comment'); + $form->addSubmit('send', 'Objavi komentar'); return $form; } ``` -Malo jo razložimo. Prva vrstica ustvari nov primerek komponente `Form`. Naslednje metode pripenjajo vhode HTML v definicijo obrazca. `->addText` se bo prikazal kot `<input type=text name=name>`z `<label>Your name:</label>`. Kot ste morda že uganili zdaj, `->addTextArea` priklopi `<textarea>` in `->addSubmit` doda vnos `<input type=submit>`. Podobnih metod je še več, vendar je to vse, kar morate zdaj vedeti. [Več |forms:] lahko [izveste v dokumentaciji |forms:]. +Pojasnimo si to malo bolje. Prva vrstica ustvari novo instanco komponente `Form`. Naslednje metode priključijo HTML vnose v definicijo tega obrazca. `->addText` se bo izrisal kot `<input type="text" name="name">` z `<label>Ime:</label>`. Kot verjetno že pravilno ugibate, se `->addTextArea` izriše kot `<textarea>` in `->addSubmit` kot `<input type="submit">`. Obstaja še veliko podobnih metod, vendar te zaenkrat za ta obrazec zadostujejo. Več si lahko [preberete v dokumentaciji|forms:]. -Ko je komponenta obrazca opredeljena v predstavniku, jo lahko upodobimo (prikažemo) v predlogi. To storite tako, da postavite oznako `{control}` na konec predloge podrobnosti prispevka, v `Post/show.latte`. Ker je ime komponente `commentForm` (izhaja iz imena metode `createComponentCommentForm`), bo oznaka videti takole +Če je obrazec že definiran v presenterju, ga lahko izrišemo (prikažemo) v predlogi. To storimo tako, da na konec predloge, ki izrisuje en konkreten prispevek, v `Post/show.latte` postavimo oznako `{control}`. Ker se komponenta imenuje `commentForm` (ime izhaja iz imena metode `createComponentCommentForm`), bo oznaka izgledala takole: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... -<h2>Post new comment</h2> +<h2>Vnesite nov komentar</h2> {control commentForm} ``` -Če si zdaj ogledate podrobnosti neke objave, bo na voljo nov obrazec za objavo komentarjev. +Če si zdaj ogledate stran s podrobnostmi prispevka, boste na njenem koncu videli nov obrazec za komentarje. -Shranjevanje v zbirko podatkov .[#toc-saving-to-database] -========================================================= +Shranjevanje v podatkovno bazo +============================== -Ste poskusili poslati nekaj podatkov? Morda ste opazili, da obrazec ne izvede nobene akcije. Samo tam je, je videti kul in ne naredi ničesar. Nanj moramo priključiti metodo povratnega klica, ki bo shranila poslane podatke. +Ste že poskusili izpolniti obrazec in ga poslati? Verjetno ste opazili, da obrazec pravzaprav nič ne naredi. Povezati moramo callback metodo, ki bo shranila poslana podatke. -Pred vrstico `return` v tovarni komponente za `commentForm` postavite naslednjo vrstico : +Na vrstico pred `return` v tovarni za komponento `commentForm` postavimo naslednjo vrstico: ```php -$form->onSuccess[] = [$this, 'commentFormSucceeded']; +$form->onSuccess[] = $this->commentFormSucceeded(...); ``` -Pomeni "po uspešni oddaji obrazca pokliči metodo `commentFormSucceeded` trenutnega predstavnika". Ta metoda še ne obstaja, zato jo ustvarimo. +Prejšnji zapis pomeni "po uspešnem pošiljanju obrazca pokliči metodo `commentFormSucceeded` iz trenutnega presenterja". Ta metoda pa še ne obstaja. Ustvarimo jo torej: -```php .{file:app/Presenters/PostPresenter.php} -public function commentFormSucceeded(\stdClass $data): void +```php .{file:app/Presentation/Post/PostPresenter.php} +private function commentFormSucceeded(\stdClass $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); $this->database->table('comments')->insert([ - 'post_id' => $postId, + 'post_id' => $id, 'name' => $data->name, 'email' => $data->email, 'content' => $data->content, ]); - $this->flashMessage('Thank you for your comment', 'success'); + $this->flashMessage('Hvala za komentar', 'success'); $this->redirect('this'); } ``` -Postaviti jo morate takoj za tovarno komponent `commentForm`. +To metodo postavimo neposredno za tovarno obrazca `commentForm`. -Nova metoda ima en argument, ki je primerek obrazca, ki se pošilja in ga je ustvarila tovarna komponent. Oddane vrednosti prejmemo v `$data`. Nato pa podatke vstavimo v tabelo podatkovne zbirke `comments`. +Ta nova metoda ima en argument, ki je instanca obrazca, ki je bil poslan - ustvarjen s tovarno. Poslane vrednosti dobimo v `$data`. Nato shranimo podatke v podatkovno tabelo `comments`. -Razložiti je treba še dva klica metod. Preusmeritev dobesedno preusmeri na trenutno stran. To morate storiti vsakič, ko je obrazec oddan, veljaven in je operacija povratnega klica naredila, kar bi morala. Prav tako pri preusmeritvi strani po oddaji obrazca ne boste videli znanega sporočila `Would you like to submit the post data again?`, ki se včasih pojavi v brskalniku. (Na splošno morate po oddaji obrazca z metodo `POST` uporabnika vedno preusmeriti na dejanje `GET`.) +Tu sta še dve metodi, ki si zaslužita pojasnilo. Metoda `redirect` dobesedno preusmeri nazaj na trenutno stran. To je primerno storiti po vsakem pošiljanju obrazca, če je vseboval veljavne podatke in je callback izvedel operacijo, kot je bilo predvideno. Prav tako, če stran preusmerimo po pošiljanju obrazca, ne bomo videli dobro znanega sporočila `Želite ponovno poslati podatke iz obrazca?`, ki ga včasih lahko vidimo v brskalniku. (Na splošno velja, da bi po pošiljanju obrazca z metodo `POST` vedno morala slediti preusmeritev na `GET` akcijo.) -Sporočilo `flashMessage` je namenjeno obveščanju uporabnika o rezultatu neke operacije. Ker gre za preusmeritev, sporočila ni mogoče neposredno posredovati predlogi in ga izrisati. Zato je tu ta metoda, ki ga bo shranila in dala na voljo ob naslednjem nalaganju strani. Sporočila flash so prikazana v privzeti datoteki `app/Presenters/templates/@layout.latte` in so videti takole: +Metoda `flashMessage` je namenjena obveščanju uporabnika o rezultatu neke operacije. Ker preusmerjamo, sporočila ni mogoče preprosto predati predlogi in ga izrisati. Zato je tu ta metoda, ki si to sporočilo shrani in ga naredi dostopnega ob naslednjem nalaganju strani. Flash sporočila se izrisujejo v glavni predlogi `app/Presentation/@layout.latte` in izgledajo takole: ```latte <div n:foreach="$flashes as $flash" n:class="flash, $flash->type"> @@ -121,20 +121,20 @@ Sporočilo `flashMessage` je namenjeno obveščanju uporabnika o rezultatu neke </div> ``` -Kot že vemo, se samodejno posredujejo predlogi, zato vam o tem ni treba preveč razmišljati, preprosto deluje. Za več podrobnosti [si oglejte dokumentacijo |application:presenters#flash-messages]. +Kot že vemo, se flash sporočila samodejno predajo predlogi, zato o tem ni treba veliko razmišljati, preprosto deluje. Za več informacij [obiščite dokumentacijo |application:presenters#Flash sporočila]. -Prikazovanje komentarjev .[#toc-rendering-the-comments] -======================================================= +Izrisovanje komentarjev +======================= -To je ena od stvari, ki vam bo preprosto všeč. Podatkovna zbirka Nette ima to kul funkcijo z imenom [Raziskovalec |database:explorer]. Se spomnite, da smo tabele ustvarili kot InnoDB? Adminer je ustvaril tako imenovane [tuje ključe, |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html] ki nam bodo prihranili ogromno dela. +To je ena tistih stvari, ki jih boste preprosto vzljubili. Nette Database ima odlično funkcijo imenovano [Explorer |database:explorer]. Se še spomnite, da smo tabele v podatkovni bazi namenoma ustvarjali z InnoDB shrambo? Adminer je tako ustvaril nekaj, čemur se reče [tuji ključi |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html], ki nam bodo prihranili veliko dela. -Nette Database Explorer uporablja tuje ključe za reševanje razmerij med tabelami, in ker pozna razmerja, lahko samodejno ustvari poizvedbe za vas. +Nette Database Explorer uporablja tuje ključe za reševanje medsebojnih odnosov med tabelami in na podlagi poznavanja teh odnosov zna samodejno ustvariti podatkovne poizvedbe. -Kot se morda spomnite, smo spremenljivko `$post` posredovali predlogi v `PostPresenter::renderShow()`, zdaj pa želimo iterirati po vseh komentarjih, ki imajo stolpec `post_id` enak našemu `$post->id`. To lahko storite tako, da pokličete `$post->related('comments')`. Tako preprosto je. Oglejte si nastalo kodo. +Kot se zagotovo spomnite, smo v predlogo predali spremenljivko `$post` z metodo `PostPresenter::renderShow()` in zdaj želimo iterirati skozi vse komentarje, ki imajo vrednost stolpca `post_id` enako `$post->id`. To lahko dosežemo s klicem `$post->related('comments')`. Da, tako preprosto. Poglejmo si končno kodo: -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { ... $this->template->post = $post; @@ -144,15 +144,15 @@ public function renderShow(int $postId): void In predlogo: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... -<h2>Comments</h2> +<h2>Komentarji</h2> <div class="comments"> {foreach $comments as $comment} <p><b><a href="mailto:{$comment->email}" n:tag-if="$comment->email"> {$comment->name} - </a></b> said:</p> + </a></b> napisal:</p> <div>{$comment->content}</div> {/foreach} @@ -160,13 +160,12 @@ In predlogo: ... ``` -Opazite poseben atribut `n:tag-if`. Že veste, kako deluje `n: attributes`. Če atributu predpomnite `tag-`, bo ovijal le oznake, ne pa tudi njihove vsebine. Tako lahko ime komentatorja spremenite v povezavo, če je navedel svoj elektronski naslov. Rezultati teh dveh vrstic so enaki: +Opazite poseben atribut `n:tag-if`. Že veste, kako delujejo `n:atributi`. Če atributu dodate predpono `tag-`, se funkcionalnost uporabi samo za HTML oznako, ne pa za njeno vsebino. To nam omogoča, da iz imena komentatorja naredimo povezavo samo v primeru, da je navedel svoj e-poštni naslov. Ti dve vrstici sta identični: ```latte -<strong n:tag-if="$important"> Hello there! </strong> +<strong n:tag-if="$important"> Dober dan! </strong> -{if $important}<strong>{/if} Hello there! {if $important}</strong>{/if} +{if $important}<strong>{/if} Dober dan! {if $important}</strong>{/if} ``` {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/sl/creating-posts.texy b/quickstart/sl/creating-posts.texy index 052be6f029..0c182e9552 100644 --- a/quickstart/sl/creating-posts.texy +++ b/quickstart/sl/creating-posts.texy @@ -1,30 +1,30 @@ -Ustvarjanje in urejanje objav -***************************** +Ustvarjanje in urejanje prispevkov +********************************** -Kako lepo je bilo. Imamo super kul nov blog, ljudje se prepirajo v komentarjih in končno imamo nekaj časa za več programiranja. Čeprav nam je Adminer všeč, pa pisanje blogovskih prispevkov v njem ni tako udobno. Morda je pravi čas, da dodamo preprost obrazec za dodajanje novih objav neposredno iz naše aplikacije. Naredimo to. +To je super! Imamo super kul nov blog, ljudje zavzeto razpravljajo v komentarjih in končno imamo malo časa za nadaljnje programiranje. Čeprav je Adminer odlično orodje, ni povsem idealen za pisanje novih prispevkov na blog. Očitno je pravi čas za ustvarjanje preprostega obrazca za dodajanje novih prispevkov neposredno iz aplikacije. Pojdimo na to. -Začnimo z oblikovanjem uporabniškega vmesnika: +Začnimo z načrtovanjem uporabniškega vmesnika: -1. Na domači strani dodamo povezavo "Napiši novo objavo". -2. Prikazan bo obrazec z naslovom in besedilno površino za vsebino. -3. Ko kliknete gumb Shrani, bo shranil prispevek na spletnem dnevniku. +1. Na uvodni strani dodamo povezavo "Napiši nov prispevek". +2. Ta povezava prikaže obrazec z naslovom in textareo za vsebino prispevka. +3. Ko kliknemo na gumb Shrani, se prispevek shrani v podatkovno bazo. -Pozneje bomo dodali tudi preverjanje pristnosti in omogočili dodajanje novih objav samo prijavljenim uporabnikom. Toda to storimo pozneje. Kakšno kodo bomo morali napisati, da bo to delovalo? +Kasneje bomo dodali tudi prijavo in dodajanje prispevkov omogočili samo prijavljenim uporabnikom. Ampak to kasneje. Kakšno kodo moramo napisati zdaj, da bo vse delovalo? -1. Ustvarite novo predstavitev z obrazcem za dodajanje objav. -2. Opredelite povratni klic, ki se bo sprožil po uspešni oddaji obrazca in ki bo novo objavo shranil v zbirko podatkov. -3. Ustvarite novo predlogo za obrazec. -4. Dodajte povezavo do obrazca v predlogo glavne strani. +1. Ustvarimo nov presenter z obrazcem za dodajanje prispevkov. +2. Definiramo callback, ki se sproži po uspešnem pošiljanju obrazca in ki nov prispevek shrani v podatkovno bazo. +3. Ustvarimo novo predlogo, na kateri bo ta obrazec. +4. Dodamo povezavo na obrazec v predlogo glavne strani. -Nov predvajalnik .[#toc-new-presenter] -====================================== +Nov presenter +============= -Novemu predvajalniku damo ime `EditPresenter` in ga shranimo v `app/Presenters/EditPresenter.php`. Prav tako se mora povezati s podatkovno bazo, zato tudi tu napišemo konstruktor, ki bo zahteval povezavo s podatkovno bazo: +Nov presenter bomo poimenovali `EditPresenter` in ga shranili v `app/Presentation/Edit/`. Prav tako se mora povezati s podatkovno bazo, zato bomo tukaj spet napisali konstruktor, ki bo zahteval povezavo s podatkovno bazo: -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Edit; use Nette; use Nette\Application\UI\Form; @@ -39,95 +39,95 @@ final class EditPresenter extends Nette\Application\UI\Presenter ``` -Obrazec za shranjevanje objav .[#toc-form-for-saving-posts] -=========================================================== +Obrazec za shranjevanje prispevkov +================================== -Obrazce in komponente smo že obravnavali, ko smo dodajali podporo za komentarje. Če ste zmedeni glede te teme, si ponovno oglejte, [kako delujejo obrazci in komponente |comments#form-for-commenting], mi pa bomo počakali tukaj ;) +Obrazce in komponente smo si že pojasnili pri ustvarjanju komentarjev. Če še vedno ni jasno, pojdite skozi [ustvarjanje obrazcev in komponent |comments#Obrazec za komentiranje], mi bomo medtem počakali tukaj ;) -Zdaj dodajte to metodo v `EditPresenter`: +Zdaj dodajmo to metodo v presenter `EditPresenter`: -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} protected function createComponentPostForm(): Form { $form = new Form; - $form->addText('title', 'Title:') + $form->addText('title', 'Naslov:') ->setRequired(); - $form->addTextArea('content', 'Content:') + $form->addTextArea('content', 'Vsebina:') ->setRequired(); - $form->addSubmit('send', 'Save and publish'); - $form->onSuccess[] = [$this, 'postFormSucceeded']; + $form->addSubmit('send', 'Shrani in objavi'); + $form->onSuccess[] = $this->postFormSucceeded(...); return $form; } ``` -Shranjevanje nove objave iz obrazca .[#toc-saving-new-post-from-form] -===================================================================== +Shranjevanje novega prispevka iz obrazca +======================================== -Nadaljujte z dodajanjem metode Handler. +Nadaljujemo z dodajanjem metode, ki bo obdelala podatke iz obrazca: -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { $post = $this->database ->table('posts') ->insert($data); - $this->flashMessage('Post was published', 'success'); + $this->flashMessage("Prispevek je bil uspešno objavljen.", 'success'); $this->redirect('Post:show', $post->id); } ``` -Samo kratka razlaga: iz obrazca pobere vrednosti, jih vstavi v zbirko podatkov, ustvari sporočilo za uporabnika, da je bila objava uspešno shranjena, in preusmeri na stran, kjer je ta objava objavljena, tako da si lahko ogledate, kako izgleda. +Samo hiter povzetek: ta metoda pridobi podatke iz obrazca, jih vstavi v podatkovno bazo, ustvari sporočilo za uporabnika o uspešnem shranjevanju prispevka in preusmeri na stran z novim prispevkom, tako da takoj vidimo, kako izgleda. -Stran za ustvarjanje nove objave .[#toc-page-for-creating-a-new-post] -===================================================================== +Stran za ustvarjanje novega prispevka +===================================== -Ustvarimo samo predlogo `Edit/create.latte`: +Ustvarimo zdaj predlogo `Edit/create.latte`: -```latte .{file:app/Presenters/templates/Edit/create.latte} +```latte .{file:app/Presentation/Edit/create.latte} {block content} -<h1>New post</h1> +<h1>Nov prispevek</h1> {control postForm} ``` -Zdaj bi moralo biti vse jasno. V zadnji vrstici je prikazan obrazec, ki ga bomo ustvarili. +Vse bi moralo biti že jasno. Zadnja vrstica izrisuje obrazec, ki ga bomo šele ustvarili. -Lahko bi ustvarili tudi ustrezno metodo `renderCreate`, vendar to ni potrebno. Ne potrebujemo namreč pridobiti nobenih podatkov iz podatkovne zbirke in jih posredovati predlogi, zato bi bila ta metoda prazna. V takšnih primerih metoda morda sploh ne bo obstajala. +Lahko bi ustvarili tudi ustrezno metodo `renderCreate`, vendar to ni potrebno. Ni nam treba pridobivati nobenih podatkov iz podatkovne baze in jih predajati predlogi, zato bi bila ta metoda prazna. V takih primerih metoda sploh ne rabi obstajati. -Povezava za ustvarjanje objav .[#toc-link-for-creating-posts] -============================================================= +Povezava za ustvarjanje prispevkov +================================== -Verjetno že veste, kako dodati povezavo na `EditPresenter` in njeno akcijo `create`. Preizkusite jo. +Verjetno že veste, kako dodati povezavo na `EditPresenter` in njegovo akcijo `create`. Poskusite sami. -Preprosto dodajte v datoteko `app/Presenters/templates/Home/default.latte`: +Samo v datoteko `app/Presentation/Home/default.latte` dodajte: ```latte -<a n:href="Edit:create">Write new post</a> +<a n:href="Edit:create">Napiši nov prispevek</a> ``` -Urejanje objav .[#toc-editing-posts] -==================================== +Urejanje prispevkov +=================== -Dodajmo tudi možnost urejanja obstoječih objav. To bo precej preprosto - že imamo `postForm` in ga lahko uporabimo tudi za urejanje. +Zdaj bomo dodali tudi možnost urejanja prispevka. To bo zelo preprosto. Že imamo pripravljen obrazec `postForm` in ga lahko uporabimo tudi za urejanje. -Na spletno stran `EditPresenter` bomo dodali novo stran `edit`: +Dodamo novo stran `edit` v presenter `EditPresenter`: -```php .{file:app/Presenters/EditPresenter.php} -public function renderEdit(int $postId): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +public function renderEdit(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { - $this->error('Post not found'); + $this->error('Prispevek ni najden'); } $this->getComponent('postForm') @@ -135,26 +135,26 @@ public function renderEdit(int $postId): void } ``` -In ustvarili predlogo `Edit/edit.latte`: +In ustvarimo še eno predlogo `Edit/edit.latte`: -```latte .{file:app/Presenters/templates/Edit/edit.latte} +```latte .{file:app/Presentation/Edit/edit.latte} {block content} -<h1>Edit post</h1> +<h1>Uredi prispevek</h1> {control postForm} ``` -In posodobite metodo `postFormSucceeded`, s katero bo mogoče dodati novo objavo (kot zdaj) ali urediti obstoječo: +In uredimo metodo `postFormSucceeded`, ki bo sposobna tako dodati nov članek (kot to počne zdaj) kot tudi urejati že obstoječi članek: -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); - if ($postId) { + if ($id) { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); $post->update($data); } else { @@ -163,26 +163,25 @@ public function postFormSucceeded(array $data): void ->insert($data); } - $this->flashMessage('Post was published', 'success'); + $this->flashMessage('Prispevek je bil uspešno objavljen.', 'success'); $this->redirect('Post:show', $post->id); } ``` -Če je podan parameter `postId`, to pomeni, da se prispevek ureja. V takem primeru bomo preverili, ali objava res obstaja, in če je tako, jo bomo posodobili v zbirki podatkov. Če parameter `postId` ni naveden, to pomeni, da bo dodana nova objava. +Če je na voljo parameter `id`, to pomeni, da bomo urejali prispevek. V tem primeru preverimo, ali zahtevani prispevek res obstaja in če da, ga posodobimo v podatkovni bazi. Če parameter `id` ni na voljo, potem to pomeni, da bi moral biti dodan nov prispevek. -Toda od kod prihaja podatek `postId`? To je parameter, ki ga posredujemo metodi `renderEdit`. +Kje pa se vzame ta parameter `id`? Gre za parameter, ki je bil vstavljen v metodo `renderEdit`. -Zdaj lahko dodate povezavo do predloge `app/Presenters/templates/Post/show.latte`: +Zdaj lahko dodamo povezavo v predlogo `app/Presentation/Post/show.latte`: ```latte -<a n:href="Edit:edit $post->id">Edit this post</a> +<a n:href="Edit:edit $post->id">Uredi prispevek</a> ``` -Povzetek .[#toc-summary] -======================== +Povzetek +======== -Blog deluje, ljudje hitro komentirajo in pri dodajanju novih objav nismo več odvisni od Adminerja. Blog je popolnoma neodvisen in vanj lahko objavljajo tudi običajni ljudje. Ampak čakajte, to verjetno ni v redu, da lahko vsakdo, mislim res vsakdo na internetu, objavlja na našem blogu. Potrebna je neka oblika avtentikacije, tako da bi lahko objavljali le prijavljeni uporabniki. To bomo dodali v naslednjem poglavju. +Blog je zdaj funkcionalen, obiskovalci ga aktivno komentirajo in za objavo ne potrebujemo več Adminerja. Aplikacija je popolnoma neodvisna in kdorkoli lahko doda nov prispevek. Počakajte malo, to verjetno ni povsem v redu, da kdorkoli - in s tem mislim res kdorkoli z dostopom do interneta - lahko dodaja nove prispevke. Potrebna je neka varnost, da lahko nov prispevek doda samo prijavljen uporabnik. To si bomo ogledali v naslednjem poglavju. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/sl/home-page.texy b/quickstart/sl/home-page.texy index 64e508ca40..b1e6599c22 100644 --- a/quickstart/sl/home-page.texy +++ b/quickstart/sl/home-page.texy @@ -1,41 +1,41 @@ -Domača stran spletnega dnevnika -******************************* +Uvodna stran bloga +****************** .[perex] -Ustvarimo domačo stran, ki prikazuje vaše zadnje objave. +Zdaj bomo ustvarili uvodno stran, ki prikazuje zadnje prispevke. -Preden začnemo, morate poznati vsaj nekaj osnov o oblikovnem vzorcu Model-View-Presenter (podobno kot MVC((Model-View-Controller))): +Preden začnemo, je treba poznati vsaj osnove načrtovalskega vzorca Model-View-Presenter (podobno kot MVC((Model-View-Controller))): -- **Model** - plast za obdelavo podatkov. Je popolnoma ločen od preostalega dela aplikacije. Komunicira samo s predstavniki. +- **Model** - plast, ki dela s podatki. Je popolnoma ločena od preostanka aplikacije. Komunicira samo s presenterjem. -- **Pogled** - sloj za definicijo sprednjega dela. Z uporabo predlog uporabniku prikazuje zahtevane podatke. +- **View** - front-end plast. Izrisuje zahtevane podatke s pomočjo predlog in jih prikazuje uporabniku. -- **Predstavitelj** (ali kontroler) - povezovalni sloj. Presenter povezuje model in pogled. Obdeluje zahteve, od Modela zahteva podatke in jih nato posreduje trenutnemu Pogledu. +- **Presenter** (ali Controller) - povezovalna plast. Presenter povezuje Model in View. Obdeluje zahteve, sprašuje Model za podatke in jih vrača nazaj v View. -V primeru zelo preproste aplikacije, kot je naš blog, bo plast Modela dejansko sestavljena le iz poizvedb v podatkovno zbirko - zanjo ne potrebujemo nobene dodatne kode PHP. Ustvariti moramo le plasti Presenter in View. V Netteu ima vsak Presenter svoje Poglede, zato bomo nadaljevali z obema hkrati. +V primeru preprostih aplikacij, kot bo naš blog, bodo celotno modelno plast tvorile samo poizvedbe v podatkovno bazo - za to zaenkrat ne potrebujemo nobene dodatne kode. Za začetek si torej ustvarimo samo presenterje in predloge. V Nette ima vsak presenter svoje lastne predloge, zato jih bomo ustvarjali hkrati. -Ustvarjanje podatkovne zbirke s programom Adminer .[#toc-creating-the-database-with-adminer] -============================================================================================ +Ustvarjanje podatkovne baze z Adminerjem +======================================== -Za shranjevanje podatkov bomo uporabili podatkovno zbirko MySQL, saj je najpogostejša izbira med spletnimi razvijalci. Če pa vam ni všeč, lahko uporabite podatkovno zbirko po lastni izbiri. +Za shranjevanje podatkov bomo uporabili podatkovno bazo MySQL, ker je najbolj razširjena med programerji spletnih aplikacij. Če pa je ne želite uporabiti, si mirno izberite podatkovno bazo po lastni presoji. -Pripravimo podatkovno zbirko, v kateri bomo shranjevali objave našega bloga. Začnemo lahko zelo preprosto - z eno samo tabelo za objave. +Zdaj si pripravimo podatkovno strukturo, kjer bodo shranjeni članki našega bloga. Začeli bomo zelo preprosto - ustvarili si bomo samo eno tabelo za prispevke. -Za izdelavo podatkovne zbirke lahko prenesemo [program Adminer |https://www.adminer.org], lahko pa uporabite tudi drugo orodje za upravljanje podatkovnih zbirk. +Za ustvarjanje podatkovne baze si lahko prenesemo [Adminer |https://www.adminer.org] ali drugo vaše priljubljeno orodje za upravljanje podatkovnih baz. -Odpremo program Adminer in ustvarimo novo zbirko podatkov z imenom `quickstart`. +Odprimo Adminer in ustvarimo novo podatkovno bazo z imenom `quickstart`. -Ustvarimo novo tabelo z imenom `posts` in dodamo te stolpce: -- `id` int, kliknite na samodejno povečanje (AI). -- `title` varchar, dolžina 255 -- `content` besedilo -- `created_at` časovni žig +Ustvarimo novo tabelo z imenom `posts` in z naslednjimi stolpci: +- `id` int, označimo samodejno povečevanje (AI) +- `title` varchar, length 255 +- `content` text +- `created_at` timestamp -Izgledati mora takole: +Končna struktura bi morala izgledati takole: [* adminer-posts.webp *] @@ -49,49 +49,46 @@ CREATE TABLE `posts` ( ``` .[caution] -Zelo pomembno je, da uporabite shranjevanje tabele **InnoDB**. Razlog za to boste videli pozneje. Za zdaj izberite to in pošljite. Zdaj lahko pritisnete Shrani. +Res je pomembno uporabiti shrambo **InnoDB**. Čez trenutek si bomo pokazali zakaj. Zaenkrat jo preprosto izberite in kliknite na shrani. -Poskusite dodati nekaj vzorčnih objav na blogu, preden bomo implementirali zmožnost dodajanja novih objav neposredno iz naše aplikacije. +Preden ustvarimo možnost dodajanja člankov v podatkovno bazo s pomočjo aplikacije, ročno dodajte nekaj vzorčnih člankov na blog. ```sql INSERT INTO `posts` (`id`, `title`, `content`, `created_at`) VALUES -(1, 'Article One', 'Lorem ipusm dolor one', CURRENT_TIMESTAMP), -(2, 'Article Two', 'Lorem ipsum dolor two', CURRENT_TIMESTAMP), -(3, 'Article Three', 'Lorem ipsum dolor three', CURRENT_TIMESTAMP); +(1, 'Članek ena', 'Lorem ipusm dolor one', CURRENT_TIMESTAMP), +(2, 'Članek dva', 'Lorem ipsum dolor two', CURRENT_TIMESTAMP), +(3, 'Članek tri', 'Lorem ipsum dolor three', CURRENT_TIMESTAMP); ``` -Povezovanje s podatkovno bazo .[#toc-connecting-to-the-database] -================================================================ +Povezava s podatkovno bazo +========================== -Ko je zbirka podatkov ustvarjena in imamo v njej nekaj objav, je zdaj pravi čas, da jih prikažemo na naši novi svetleči strani. +Zdaj, ko je podatkovna baza že ustvarjena in imamo v njej shranjenih nekaj člankov, je pravi čas, da jih prikažemo na naši lepi novi strani. -Najprej moramo našemu programu povedati, katero podatkovno zbirko naj uporabi. Konfiguracija povezave s podatkovno bazo je shranjena v `config/local.neon`. Nastavite povezavo DSN((Data Source Name)) in svoje poverilnice. Izgledati mora takole: +Najprej moramo aplikaciji povedati, katero podatkovno bazo naj uporabi. Povezavo s podatkovno bazo nastavimo v datoteki `config/common.neon` s pomočjo DSN((Data Source Name)) in prijavnih podatkov. Moralo bi izgledati nekako takole: -```neon .{file:config/local.neon} +```neon .{file:config/common.neon} database: dsn: 'mysql:host=127.0.0.1;dbname=quickstart' - user: *enter user name* - password: *enter password here* + user: *tukaj vnesite uporabniško ime* + password: *tukaj vnesite geslo za podatkovno bazo* ``` .[note] -Pri urejanju te datoteke bodite pozorni na alineje. [Oblika NEON |neon:format] sprejema tako presledke kot tabulatorje, vendar ne obojega skupaj! Konfiguracijska datoteka v spletnem projektu privzeto uporablja tabulatorje. +Pri urejanju te datoteke bodite pozorni na zamik vrstic. Format [NEON |neon:format] sprejema tako zamik s presledki kot zamik s tabulatorji, vendar ne obojega hkrati. Privzeta konfiguracijska datoteka v Web Projectu uporablja tabulatorje. -Celotna konfiguracija je shranjena v `config/` v datotekah `common.neon` in `local.neon`. Datoteka `common.neon` vsebuje globalno konfiguracijo aplikacije, datoteka `local.neon` pa samo parametre, ki so značilni za okolje (npr. razlika med razvojnim in produkcijskim strežnikom). +Predaja podatkovne povezave +=========================== -Vbrizgavanje povezave s podatkovno bazo .[#toc-injecting-the-database-connection] -================================================================================= +Presenter `HomePresenter`, ki bo skrbel za izpis člankov, potrebuje povezavo s podatkovno bazo. Za njeno pridobitev bomo uporabili konstruktor, ki bo izgledal takole: -Predstavitveni program `HomePresenter`, ki bo objavil seznam člankov, potrebuje povezavo s podatkovno bazo. Če jo želite prejeti, napišite konstruktor, kot je ta: - -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; -use Nette\Application\UI\Form; final class HomePresenter extends Nette\Application\UI\Presenter { @@ -105,12 +102,12 @@ final class HomePresenter extends Nette\Application\UI\Presenter ``` -Nalaganje objav iz zbirke podatkov .[#toc-loading-posts-from-the-database] -========================================================================== +Nalaganje prispevkov iz podatkovne baze +======================================= -Zdaj prenesimo objave iz zbirke podatkov in jih posredujemo predlogi, ki bo nato prikazala kodo HTML. Za to je namenjena tako imenovana metoda *render*: +Zdaj bomo naložili prispevke iz podatkovne baze in jih poslali predlogi, ki jih bo nato izrisala kot HTML kodo. Za to je namenjena tako imenovana *render* metoda: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} public function renderDefault(): void { $this->template->posts = $this->database @@ -120,41 +117,41 @@ public function renderDefault(): void } ``` -Predstavitev ima zdaj eno metodo upodabljanja `renderDefault()`, ki posreduje podatke pogledu z imenom `default`. Predloge predstavnikov se nahajajo v `app/Presenters/templates/{PresenterName}/{viewName}.latte`, zato se bo v tem primeru predloga nahajala v `app/Presenters/templates/Home/default.latte`. V predlogi je zdaj na voljo spremenljivka z imenom `$posts`, ki vsebuje objave iz podatkovne zbirke. +Presenter zdaj vsebuje eno render metodo `renderDefault()`, ki predaja podatke iz podatkovne baze predlogi (View). Predloge so nameščene v `app/Presentation/{PresenterName}/{viewName}.latte`, torej je v tem primeru predloga nameščena v `app/Presentation/Home/default.latte`. V predlogi bo zdaj na voljo spremenljivka `$posts`, v kateri so prispevki, pridobljeni iz podatkovne baze. -Predloga .[#toc-template] -========================= +Predloga +======== -Obstaja splošna predloga za celotno stran (imenovana *layout*, z glavo, slogovnimi listi, nogo, ...) in nato posebne predloge za vsak pogled (npr. za prikaz seznama blogovskih objav), ki lahko prekrijejo nekatere dele predloge layout. +Za celotno spletno stran imamo na voljo glavno predlogo (ki se imenuje *layout*, vsebuje glavo, stile, nogo,...) in nadalje specifične predloge za vsak pogled (View) (npr. za prikaz prispevkov na blogu), ki lahko prepišejo nekatere dele glavne predloge. -Privzeto se predloga postavitve nahaja v spletnem mestu `templates/@layout.latte`, ki vsebuje: +V privzetem stanju je layout predloga nameščena v `app/Presentation/@layout.latte` in vsebuje: -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... {include content} ... ``` -`{include content}` v glavno predlogo vstavi blok z imenom `content`. Določite ga lahko v predlogah vsakega pogleda. V tem primeru bomo tako uredili datoteko `Home/default.latte`: +Zapis `{include content}` vstavlja v glavno predlogo blok z imenom `content`. Tega bomo definirali v predlogah posameznih pogledov (View). V našem primeru datoteko `Home/default.latte` uredimo na naslednji način: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} Hello World {/block} ``` -V njej je opredeljen [blok |latte:tags#block]*vsebina*, ki bo vstavljen v postavitev. Če osvežite brskalnik, boste videli stran z besedilom "Hello world" (v izvorni kodi tudi z glavo in nogo HTML, ki sta definirani v `@layout.latte`). +S tem smo definirali [blok |latte:tags#block] *content*, ki bo vstavljen v glavni layout. Če ponovno osvežimo brskalnik, bomo videli stran z besedilom "Hello World" (v izvorni kodi tudi s HTML glavo in nogo, definirano v `@layout.latte`). -Prikažimo blogovske objave - predlogo bomo uredili takole: +Prikažimo prispevke iz bloga - predlogo uredimo na naslednji način: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - <h1>My blog</h1> + <h1>Moj blog</h1> {foreach $posts as $post} <div class="post"> - <div class="date">{$post->created_at|date:'j. n. Y'}</div> + <div class="date">{$post->created_at|date:'F j, Y'}</div> <h2>{$post->title}</h2> @@ -164,42 +161,41 @@ Prikažimo blogovske objave - predlogo bomo uredili takole: {/block} ``` -Če osvežite brskalnik, se bo prikazal seznam vaših blogovskih objav. Seznam ni preveč domišljen ali barvit, zato lahko na `www/css/style.css` dodate nekaj [bleščečih CSS |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] in ga povežete v postavitev: +Če osvežimo brskalnik, bomo videli izpis vseh prispevkov. Izpis zaenkrat ni zelo lep, niti barvit, zato lahko v datoteko `www/css/style.css` dodamo nekaj [CSS stilov |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] in jo povežemo v layoutu: -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <link rel="stylesheet" href="{$basePath}/css/style.css"> </head> ... ``` -Oznaka `{foreach}` iterira čez vse objave, ki so bile posredovane predlogi v spremenljivki `$posts`, in za vsako objavo prikaže del kode HTML. Tako kot bi to storila koda PHP. +Oznaka `{foreach}` iterira skozi vse prispevke, ki smo jih predali predlogi v spremenljivki `$posts`, in za vsakega izriše dani kos HTML. Obnaša se natanko tako kot PHP koda. -Ta `|date` se imenuje filter. Filtri se uporabljajo za oblikovanje izpisa. Ta filter pretvori datum (npr. `2013-04-12`) v bolj berljivo obliko (`12. 4. 2013`). Filter `|truncate` skrajša niz na določeno največjo dolžino in na koncu doda elipso, če je niz skrajšan. Ker gre za predogled, nima smisla prikazovati celotne vsebine članka. Druge privzete filtre najdete [v dokumentaciji |latte:filters], po potrebi pa lahko ustvarite tudi svoje. +Zapisu `|date:` pravimo filter. Filtri so namenjeni formatiranju izpisa. Ta konkreten filter pretvori datum (npr. `2013-04-12`) v njegovo bolj berljivo obliko (`April 12, 2013`). Filter `|truncate` odreže niz na navedeno maksimalno dolžino in v primeru, da niz skrajša, na konec doda tri pike. Ker gre za predogled, nima smisla prikazovati celotne vsebine članka. Druge privzete filtre [najdemo v dokumentaciji |latte:filters] ali pa si lahko ustvarimo lastne, ko je to potrebno. -Še ena stvar. Kodo lahko še nekoliko skrajšamo in s tem poenostavimo. Oznake *Latte* lahko nadomestimo z *n:atributi*, kot sledi: +Še ena stvar. Prejšnjo kodo lahko skrajšamo in poenostavimo. To dosežemo z zamenjavo *Latte oznak* z *n:atributi*: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - <h1>My blog</h1> + <h1>Moj blog</h1> <div n:foreach="$posts as $post" class="post"> <div class="date">{$post->created_at|date:'F j, Y'}</div> <h2>{$post->title}</h2> - <div>{$post->content}</div> + <div>{$post->content|truncate:256}</div> </div> {/block} ``` - `n:foreach`, preprosto ovije *div* z blokom *foreach* (naredi popolnoma enako stvar kot prejšnji blok kode). +Atribut `n:foreach` ovije *div* z blokom *foreach* (deluje popolnoma enako kot prejšnja koda). -Povzetek .[#toc-summary] -======================== +Povzetek +======== -Imamo zelo preprosto podatkovno zbirko MySQL z nekaj objavami v blogih. Aplikacija se poveže s podatkovno bazo in prikaže preprost seznam objav. +Zdaj imamo zelo preprosto podatkovno bazo MySQL z nekaj prispevki. Aplikacija se povezuje s to podatkovno bazo in izpisuje preprost seznam teh prispevkov v predlogi. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/sl/model.texy b/quickstart/sl/model.texy index f712ec3e85..150b0d28b9 100644 --- a/quickstart/sl/model.texy +++ b/quickstart/sl/model.texy @@ -1,13 +1,13 @@ Model ***** -Ko naša aplikacija raste, kmalu ugotovimo, da moramo podobne operacije podatkovne zbirke izvajati na različnih lokacijah in v različnih predvajalnikih, na primer pridobivanje najnovejših objavljenih člankov. Če našo aplikacijo izboljšamo tako, da člankom dodamo zastavico, ki označuje stanje dela v nastajanju, moramo prav tako iti skozi vsa mesta v naši aplikaciji in dodati klavzulo where, da se prepričamo, da so izbrani samo dokončani članki. +Ko aplikacija raste, bomo kmalu ugotovili, da na različnih mestih, v različnih presenterjih, potrebujemo izvajati podobne operacije s podatkovno bazo. Na primer pridobivati najnovejše objavljene članke. Ko aplikacijo izboljšamo, na primer tako, da pri člankih dodamo zastavico, ali je osnutek, moramo potem pregledati tudi vsa mesta v aplikaciji, kjer se članki pridobivajo iz podatkovne baze, in dopolniti pogoj where, da se izbirajo samo članki, ki niso osnutki. -Na tej točki postane neposredno delo s podatkovno bazo nezadostno in pametneje si bo pomagati z novo funkcijo, ki vrača objavljene članke. In ko bomo pozneje dodali še eno klavzulo (na primer, da ne bomo prikazovali člankov s prihodnjim datumom), bomo svojo kodo urejali le na enem mestu. +V tistem trenutku neposredno delo s podatkovno bazo postane nezadostno in bo bolj priročno, če si pomagamo z novo funkcijo, ki nam bo vračala objavljene članke. In ko kasneje dodamo še en pogoj, na primer da se ne prikazujejo članki z datumom v prihodnosti, uredimo kodo samo na enem mestu. -Funkcijo bomo umestili v razred `PostFacade` in jo poimenovali `getPublicArticles()`. +Funkcijo postavimo na primer v razred `PostFacade` in jo poimenujemo `getPublicArticles()`. -V imeniku `app/Model/` bomo ustvarili naš modelni razred `PostFacade`, ki bo skrbel za naše članke: +V imeniku `app/Model/` ustvarimo naš modelni razred `PostFacade`, ki nam bo skrbel za članke: ```php .{file:app/Model/PostFacade.php} <?php @@ -32,13 +32,13 @@ final class PostFacade } ``` -V razredu posredujemo podatkovno zbirko Explorer:[api:Nette\Database\Explorer]. S tem bomo izkoristili moč [vsebnika DI |dependency-injection:passing-dependencies]. +V razredu si s pomočjo konstruktorja pustimo predati podatkovni Explorer:[api:Nette\Database\Explorer]. Izkoristimo tako moč [DI vsebnika |dependency-injection:passing-dependencies]. -Preklopili bomo na `HomePresenter`, ki ga bomo uredili tako, da se bomo znebili odvisnosti od `Nette\Database\Explorer` in jo nadomestili z novo odvisnostjo od našega novega razreda. +Preklopimo na `HomePresenter`, ki ga uredimo tako, da se znebimo odvisnosti od `Nette\Database\Explorer` in jo nadomestimo z novo odvisnostjo od našega novega razreda. -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use App\Model\PostFacade; use Nette; @@ -59,10 +59,9 @@ final class HomePresenter extends Nette\Application\UI\Presenter } ``` -V razdelku uporaba uporabljamo `App\Model\PostFacade`, zato lahko kodo PHP skrajšamo na `PostFacade`. Ta objekt zahtevamo v konstruktorju, ga zapišemo v lastnost `$facade` in ga uporabimo v metodi renderDefault. +V sekciji use imamo `App\Model\PostFacade`, tako da lahko zapis v PHP kodi skrajšamo na `PostFacade`. Za ta objekt zaprosimo v konstruktorju, ga zapišemo v lastnost `$facade` in uporabimo v metodi renderDefault. -Zadnji preostali korak je naučiti vsebnik DI, da ustvari ta objekt. To običajno storimo tako, da v datoteko `config/services.neon` v razdelku `services` dodamo točko z navedbo polnega imena razreda in parametrov konstruktorja. -S tem ga tako rekoč registriramo in objekt se nato imenuje **storitev**. Zahvaljujoč čarovniji, ki se imenuje [samodejna vključitev |dependency-injection:autowiring], nam običajno ni treba navesti parametrov konstruktorja, saj jih DI prepozna in posreduje samodejno. Tako bi bilo dovolj, če bi samo navedli ime razreda: +Ostaja še zadnji korak, in sicer naučiti DI vsebnik ta objekt izdelovati. To se običajno naredi tako, da v datoteko `config/services.neon` v sekcijo `services` dodamo alinejo, navedemo polno ime razreda in parametre konstruktorja. S tem ga tako imenovano registriramo in objekt se potem imenuje **storitev**. Zahvaljujoč čarovniji imenovani [autowiring |dependency-injection:autowiring] nam večinoma ni treba navajati parametrov konstruktorja, ker jih DI sam prepozna in preda. Zadostovalo bi torej navesti samo ime razreda: ```neon .{file:config/services.neon} ... @@ -71,16 +70,15 @@ services: - App\Model\PostFacade ``` -Vendar vam te vrstice tudi ni treba dodati. V razdelku `search` na začetku `services.neon` je opredeljeno, da bo vse razrede, ki se končajo s `-Facade` ali `-Factory`, DI poiskal samodejno, kar velja tudi za `PostFacade`. +Vendar niti te vrstice ni treba dodajati. V sekciji `search` na začetku `services.neon` je definirano, da vse razrede, ki se končajo z besedo `-Facade` ali `-Factory`, DI poišče sam, kar je tudi primer `PostFacade`. -Povzetek .[#toc-summary] -======================== +Povzetek +======== -Razred `PostFacade` v konstruktorju zahteva `Nette\Database\Explorer`, in ker je ta razred registriran v vsebniku DI, vsebnik ustvari ta primerek in ga posreduje. DI nam na ta način ustvari primerek `PostFacade` in ga v konstruktorju posreduje razredu HomePresenter, ki je zanj zaprosil. Nekakšna lutka Matrjoška v kodi :) Vse komponente zahtevajo le tisto, kar potrebujejo, in jim je vseeno, kje in kako se ustvari. Za ustvarjanje poskrbi vsebnik DI. +Razred `PostFacade` si v konstruktorju zahteva predajo `Nette\Database\Explorer` in ker je ta razred v DI vsebniku registriran, vsebnik to instanco ustvari in jo preda. DI za nas tako ustvari instanco `PostFacade` in jo preda v konstruktorju razredu HomePresenter, ki je zanjo zaprosil. Nekakšna matrjoška. :) Vsi si samo povedo, kaj želijo, in jih ne zanima, kje se kaj in kako ustvarja. Za ustvarjanje skrbi DI vsebnik. .[note] -Tukaj si lahko preberete več o [vbrizgavanju odvisnosti |dependency-injection:introduction] in o [konfiguraciji |nette:configuring]. +Tukaj si lahko preberete več o [dependency injection |dependency-injection:introduction] in [konfiguraciji |nette:configuring]. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/sl/single-post.texy b/quickstart/sl/single-post.texy index 0720d19809..bafafeee20 100644 --- a/quickstart/sl/single-post.texy +++ b/quickstart/sl/single-post.texy @@ -1,17 +1,17 @@ -Stran z eno objavo +Stran s prispevkom ****************** .[perex] -Dodajmo še eno stran na naš blog, ki bo prikazovala vsebino ene posamezne objave. +Zdaj si bomo ustvarili še eno stran bloga, ki bo prikazovala en konkreten prispevek. -Ustvariti moramo novo metodo izrisa, ki bo pobrala eno določeno objavo na blogu in jo posredovala predlogi. Imeti ta pogled v naslovu `HomePresenter` ni lepo, saj gre za blogovsko objavo in ne za domačo stran. Zato ustvarimo nov razred `PostPresenter` in ga postavimo v `app/Presenters`. Potreboval bo povezavo s podatkovno bazo, zato tja ponovno postavimo kodo *database injection*. +Ustvariti moramo novo render metodo, ki bo pridobila en konkreten članek in ga predala predlogi. Imeti to metodo v `HomePresenter` ni zelo lepo, saj govorimo o članku in ne o uvodni strani. Ustvarimo si torej `PostPresenter` v `app/Presentation/Post/`. Ta presenter se prav tako mora povezati s podatkovno bazo, zato bomo tukaj spet napisali konstruktor, ki bo zahteval podatkovno povezavo. -Razred `PostPresenter` naj bo videti takole: +`PostPresenter` bi torej lahko izgledal takole: -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Post; use Nette; use Nette\Application\UI\Form; @@ -23,50 +23,50 @@ final class PostPresenter extends Nette\Application\UI\Presenter ) { } - public function renderShow(int $postId): void + public function renderShow(int $id): void { $this->template->post = $this->database ->table('posts') - ->get($postId); + ->get($id); } } ``` -Nastaviti moramo pravilen imenski prostor `App\Presenters` za našega predstavnika. To je odvisno od [preslikave predstavnika |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7]. +Ne smemo pozabiti navesti pravilnega imenskega prostora `App\Presentation\Post`, ki sledi nastavitvam [mapiranja presenterjev |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7]. -Metoda `renderShow` zahteva en argument - ID prispevka, ki ga je treba prikazati. Nato objavo naloži iz podatkovne zbirke in rezultat posreduje predlogi. +Metoda `renderShow` zahteva en argument - ID enega konkretnega članka, ki naj bo prikazan. Nato ta članek naloži iz podatkovne baze in ga preda predlogi. -V predlogi `Home/default.latte` dodamo povezavo do akcije `Post:show`: +V predlogo `Home/default.latte` vstavimo povezavo na akcijo `Post:show`. -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a href="{link Post:show $post->id}">{$post->title}</a></h2> ... ``` -Oznaka `{link}` ustvari naslov URL, ki kaže na akcijo `Post:show`. Ta oznaka kot argument posreduje tudi ID objave. +Oznaka `{link}` generira URL naslov, ki kaže na akcijo `Post:show`. Prav tako preda ID prispevka kot argument. -Enako lahko na kratko zapišemo z uporabo atributa n:attribute: +Enako lahko zapišemo skrajšano s pomočjo n:atributa: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a n:href="Post:show $post->id">{$post->title}</a></h2> ... ``` -Atribut `n:href` je podoben oznaki `{link}`. +Atribut `n:href` je analogija oznake `{link}`. -Predloga za akcijo `Post:show` še ne obstaja. Odpremo lahko povezavo do tega prispevka. [Tracy |tracy:] bo prikazal napako, zakaj `Post/show.latte` ne obstaja. Če se prikaže katero koli drugo poročilo o napaki, morate v spletnem strežniku verjetno vklopiti mod_rewrite. +Za akcijo `Post:show` pa še ne obstaja predloga. Lahko si poskusimo odpreti povezavo na ta prispevek. [Tracy |tracy:] bo prikazala napako, ker predloga `Post/show.latte` še ne obstaja. Če vidite drugačno sporočilo o napaki, boste verjetno morali vklopiti `mod_rewrite` na spletnem strežniku. -Tako bomo ustvarili spletno stran `Post/show.latte` s to vsebino: +Ustvarimo torej predlogo `Post/show.latte` s to vsebino: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} {block content} -<p><a n:href="Home:default">← back to posts list</a></p> +<p><a n:href="Home:default">← nazaj na izpis prispevkov</a></p> <div class="date">{$post->created_at|date:'F j, Y'}</div> @@ -75,51 +75,50 @@ Tako bomo ustvarili spletno stran `Post/show.latte` s to vsebino: <div class="post">{$post->content}</div> ``` -Oglejmo si posamezne dele. +Zdaj si oglejmo posamezne dele predloge. -V prvi vrstici se začne opredelitev *poimenovanega bloka* z imenom "content", ki smo ga videli prej. Prikazan bo v *šabloni za postavitev*. Kot lahko vidite, manjka končna oznaka `{/block}`. Ta je neobvezna. +Prva vrstica začenja definicijo bloka z imenom "content" enako kot je bilo na uvodni strani. Ta blok bo ponovno prikazan v glavni predlogi. Kot vidite, manjka končna oznaka `{/block}`. Ta je namreč neobvezna. -Druga vrstica zagotavlja povratno povezavo na seznam blogovskih objav, tako da se lahko uporabnik nemoteno pomika naprej in nazaj po našem blogu. Ponovno uporabimo atribut `n:href`, zato bo Nette poskrbel za generiranje naslova URL namesto nas. Povezava kaže na akcijo `default` predstavnika `Home` (lahko zapišete tudi `n:href="Home:"`, saj lahko akcijo `default` izpustite). +Na naslednji vrstici je povezava nazaj na izpis člankov bloga, tako da se uporabnik lahko enostavno premika med izpisom člankov in enim konkretnim. Ker uporabljamo atribut `n:href`, bo Nette sam poskrbel za generiranje povezav. Povezava kaže na akcijo `default` presenterja `Home` (lahko napišemo tudi `n:href="Home:"`, ker lahko akcijo z imenom `default` izpustimo, dopolni se samodejno). -Tretja vrstica oblikuje časovni žig objave s filtrom, kot že vemo. +Tretja vrstica formatira izpis datuma s pomočjo filtra, ki ga že poznamo. -Četrta vrstica prikaže *naslov* prispevka na blogu kot `<h1>` naslov. Obstaja del, ki vam morda ni znan, in sicer `n:block="title"`. Ali lahko uganete, kaj počne? Če ste pozorno prebrali prejšnje dele, smo omenili `n: attributes`. To je še en primer. Enakovreden je: +Četrta vrstica prikazuje *naslov* bloga v HTML oznaki `<h1>`. Ta oznaka vsebuje atribut, ki ga morda ne poznate (`n:block="title"`). Uganete, kaj dela? Če ste prejšnji del pozorno prebrali, že veste, da gre za `n:atribut`. To je njihov nadaljnji primer, ki je ekvivalenten: ```latte {block title}<h1>{$post->title}</h1>{/block} ``` -Preprosto povedano, *na novo definira* blok z imenom `title`. Blok je opredeljen v *šabloni za postavitev* (`/app/Presenters/templates/@layout.latte:11`) in tako kot pri OOP overriding, je tudi tukaj prepisan. Zato se na strani `<title>` bo vsebovala naslov prikazane objave. Prekrili smo naslov strani in vse, kar smo potrebovali, je bilo `n:block="title"`. Super, kajne? +Preprosto rečeno, ta blok predifinira blok z imenom `title`. Ta blok je že definiran v glavni *layout* predlogi (`/app/Presentation/@layout.latte:11`) in tako kot pri prekrivanju metod v OOP, se popolnoma enako ta blok v glavni predlogi prekrije. Torej `<title>` strani zdaj vsebuje naslov prikazanega prispevka in za to smo potrebovali uporabiti samo en preprost atribut `n:block="title"`. Super, kajne? -V peti in zadnji vrstici predloge je prikazana celotna vsebina vašega prispevka. +Peta in zadnja vrstica predloge prikazuje celotno vsebino enega konkretnega prispevka. -Preverjanje ID objave .[#toc-checking-post-id] -============================================== +Preverjanje ID prispevka +======================== -Kaj se zgodi, če nekdo spremeni naslov URL in vstavi `postId`, ki ne obstaja? Uporabniku moramo zagotoviti lepo napako "stran ni bila najdena". Posodobimo metodo upodabljanja v `PostPresenter`: +Kaj se zgodi, če nekdo spremeni ID v URL-ju in vstavi nek neobstoječ `id`? Uporabniku bi morali ponuditi lepo napako tipa "stran ni bila najdena". Malo spremenimo torej render metodo v `PostPresenter`: -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { - $this->error('Post not found'); + $this->error('Stran ni bila najdena'); } $this->template->post = $post; } ``` -Če objave ni mogoče najti, bo klic `$this->error(...)` prikazal stran 404 z lepim in razumljivim sporočilom. Upoštevajte, da v razvojnem okolju (na prenosnem računalniku) ne boste videli strani z napako. Namesto tega bo Tracy prikazal izjemo z vsemi podrobnostmi, kar je precej priročno za razvoj. Preverite lahko oba načina, samo spremenite vrednost, ki jo posredujete `setDebugMode` v `Bootstrap.php`. +Če prispevka ni mogoče najti, s klicem `$this->error(...)` prikažemo stran z napako 404 z razumljivim sporočilom. Pozor na to, da v razvijalskem načinu (localhost) te strani z napako ne boste videli. Namesto tega se bo prikazala Tracy s podrobnostmi o izjemi, kar je precej ugodno za razvoj. Če si želimo prikazati oba načina, je dovolj samo spremeniti argument metode `setDebugMode` v datoteki `Bootstrap.php`. -Povzetek .[#toc-summary] -======================== +Povzetek +======== -Imamo podatkovno zbirko z objavami na blogu in spletno aplikacijo z dvema pogledoma - prvi prikazuje povzetek vseh zadnjih objav, drugi pa eno določeno objavo. +Imamo podatkovno bazo s prispevki in spletno aplikacijo, ki ima dva pogleda - prvi prikazuje pregled vseh prispevkov in drugi prikazuje en konkreten prispevek. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/tr/@home.texy b/quickstart/tr/@home.texy index d0643d2efa..f512bd11d4 100644 --- a/quickstart/tr/@home.texy +++ b/quickstart/tr/@home.texy @@ -1,119 +1,119 @@ -İlk Başvurunuzu Oluşturun! -************************** +İlk uygulamamızı yazıyoruz! +*************************** .[perex] -Yorumlarla basit bir blog oluştururken Nette Framework'ü tanıyın. Hadi başlayalım! +Yorumlarla basit bir blog oluştururken Nette Framework'ü birlikte tanıyalım. Hadi başlayalım! -İlk iki bölümden sonra, kendi çalışan blogunuza sahip olacaksınız ve harika gönderilerinizi yayınlamaya hazır olacaksınız, ancak bu iki bölümü tamamladıktan sonra özellikler oldukça sınırlı olacaktır. Kullanıcılarınız için işleri daha güzel hale getirmek için sonraki bölümleri de okumalı ve uygulamanızı geliştirmeye devam etmelisiniz. +İlk iki bölümden sonra kendi işlevsel blogumuza sahip olacağız ve harika gönderilerimizi yayınlayabileceğiz, ancak işlevler şimdilik büyük ölçüde sınırlı olacak. Ayrıca yorum eklemeyi, makaleleri düzenlemeyi ve son olarak blogu güvenli hale getirmeyi programlayacağımız sonraki bölümleri de okumalısınız. .[tip] -Bu eğitimde [Kurulum |nette:installation] belgesini tamamladığınız ve araçlarınızı başarıyla kurduğunuz varsayılmaktadır. +Bu kılavuz, [Kurulum |nette:installation] sayfasını okuduğunuzu ve gerekli araçları başarıyla hazırladığınızı varsayar. Ayrıca [PHP'de nesne yönelimli programlamayı |nette:introduction-to-object-oriented-programming] anladığınızı varsayar. -Lütfen PHP 8.0 veya üstünü kullanın. Uygulamanın tamamını [GitHub |https://github.com/nette-examples/quickstart/tree/v4.0]'da bulabilirsiniz. +Lütfen PHP 8.1 veya daha yenisini kullanın. Tam uygulamayı [GitHub'da |https://github.com/nette-examples/quickstart/tree/v4.0] bulabilirsiniz. -Karşılama Sayfası .[#toc-the-welcome-page] -========================================== +Karşılama sayfası +================= - `nette-blog` dizininde yeni bir proje oluşturarak başlayalım: +`nette-blog` dizinine yeni bir proje oluşturarak başlayalım: ```shell composer create-project nette/web-project nette-blog ``` -Şu anda, Web Projesinin karşılama sayfası çalışıyor olmalıdır. Tarayıcınızı açıp aşağıdaki URL'ye giderek deneyin: +Bu noktada, Web Projesi giriş sayfası zaten çalışıyor olmalıdır. Tarayıcıyı aşağıdaki URL adresinde açarak deneyelim: ``` http://localhost/nette-blog/www/ ``` -ve Nette Framework karşılama sayfasını görmelisiniz: +ve Nette Framework giriş sayfasını göreceğiz: [* qs-welcome.webp .{url: http://localhost/nette-blog/www/} *] -Uygulama çalışır ve artık üzerinde değişiklik yapmaya başlayabilirsiniz. +Uygulama çalışıyor ve düzenlemeler yapmaya başlayabilirsiniz. .[note] -Eğer bir sorununuz varsa, [bu birkaç ipucunu deneyin |nette:troubleshooting#Nette Is Not Working, White Page Is Displayed]. +Bir sorun oluşursa, [bu birkaç ipucunu deneyin |nette:troubleshooting#Nette Çalışmıyor Beyaz Sayfa Görünüyor]. -Web Projesinin İçeriği .[#toc-web-project-s-content] -==================================================== +Web Projesinin İçeriği +====================== Web Projesi aşağıdaki yapıya sahiptir: /--pre <b>nette-blog/</b> -├── <b>app/</b> ← application directory -│ ├── <b>Presenters/</b> ← presenter classes -│ │ └── <b>templates/</b>← templates -│ ├── <b>Router/</b> ← configuration of URL addresses -│ └── <b>Bootstrap.php</b> ← booting class Bootstrap -├── <b>bin/</b> ← scripts for the command line -├── <b>config/</b> ← configuration files -├── <b>log/</b> ← error logs -├── <b>temp/</b> ← temporary files, cache, … -├── <b>vendor/</b> ← libraries installed by Composer -│ └── <b>autoload.php</b> ← autoloading of libraries installed by Composer -└── <b>www/</b> ← public folder - the only place accessible from browser - └── <b>index.php</b> ← initial file that launches the application +├── <b>app/</b> ← uygulama dizini +│ ├── <b>Core/</b> ← çalışması için gerekli temel sınıflar +│ ├── <b>Presentation/</b> ← presenter'lar, şablonlar & vb. +│ │ └── <b>Home/</b> ← Home presenter dizini +│ └── <b>Bootstrap.php</b> ← başlatıcı sınıf Bootstrap +├── <b>assets/</b> ← ham varlıklar (SCSS, TypeScript, kaynak görüntüler) +├── <b>bin/</b> ← komut satırından çalıştırılan betikler +├── <b>config/</b> ← yapılandırma dosyaları +├── <b>log/</b> ← hata günlüğü +├── <b>temp/</b> ← geçici dosyalar, önbellek, … +├── <b>vendor/</b> ← Composer tarafından yüklenen kütüphaneler +│ └── <b>autoload.php</b> ← yüklenen tüm paketlerin otomatik yüklenmesi +└── <b>www/</b> ← genel dizin - tarayıcıdan erişilebilen tek dizin + ├── <b>assets/</b> ← derlenmiş statik dosyalar (CSS, JS, resimler, ...) + └── <b>index.php</b> ← uygulamanın başlatıldığı başlangıç dosyası \-- -`www` dizini resim, JavaScript, CSS ve diğer herkese açık dosyaları depolamak içindir. Bu, tarayıcıdan doğrudan erişilebilen tek dizindir, bu nedenle web sunucunuzun kök dizinini buraya yönlendirebilirsiniz (Apache'de yapılandırabilirsiniz, ancak şu anda önemli olmadığı için bunu daha sonra yapalım). +`www/` dizini, resimleri, JavaScript dosyalarını, CSS stillerini ve diğer genel olarak erişilebilir dosyaları depolamak için tasarlanmıştır. Yalnızca bu dizine internetten erişilebilir, bu nedenle uygulamanızın kök dizinini tam olarak buraya yönlendirecek şekilde ayarlayın (bunu Apache veya nginx yapılandırmasında ayarlayabilirsiniz, ancak bunu daha sonra yapalım, şimdi önemli değil). -Sizin için en önemli dizin `app/`. Orada `Bootstrap.php` dosyasını bulabilirsiniz, içinde çerçeveyi yükleyen ve uygulamayı yapılandıran bir sınıf vardır. [Otomatik yüklemeyi |robot-loader:] etkinleştirir ve [hata ay |tracy:] ıklayıcı ile [rotaları |application:routing] ayarlar. +Bizim için en önemli klasör `app/`'dir. Burada, tüm framework'ü yüklemeye ve uygulamayı ayarlamaya hizmet eden sınıfı içeren `Bootstrap.php` dosyasını bulacağız. Burada [otomatik yükleme |robot-loader:] etkinleştirilir, burada [hata ayıklayıcı |tracy:] ve [yönlendirmeler |application:routing] ayarlanır. -Temizlik .[#toc-cleanup] -======================== +Temizlik +======== -Web Projesi, kaldırabileceğimiz bir karşılama sayfası içerir - `app/Presenters/templates/Home/default.latte` dosyasını silmekten ve "Merhaba dünya!" metniyle değiştirmekten çekinmeyin. +Web Projesi, bir şeyler programlamaya başlamadan önce sileceğimiz bir giriş sayfası içerir. Bu nedenle, `app/Presentation/Home/default.latte` dosyasının içeriğini endişelenmeden "Merhaba dünya!" ile değiştirelim. [* qs-hello.webp .{url:-} *] -Tracy (Hata Ayıklayıcı) .[#toc-tracy-debugger] -============================================== +Tracy (hata ayıklayıcı) +======================= -Geliştirme için son derece önemli bir araç [Tracy adlı bir hata ayıklayıcıdır |tracy:]. `app/Presenters/HomePresenter.php` dosyanızda bazı hatalar yapmaya çalışın (örneğin HomePresenter sınıfının tanımından bir küme parantezini kaldırın) ve ne olacağını görün. Anlaşılabilir bir hata açıklamasıyla birlikte kırmızı ekranlı bir sayfa açılacaktır. +Geliştirme için son derece önemli bir araç [hata ayıklama aracı Tracy |tracy:]'dir. `app/Presentation/Home/HomePresenter.php` dosyasında bir hata oluşturmayı deneyin (ör. HomePresenter sınıf tanımındaki küme parantezini kaldırarak) ve ne olacağına bakın. Hatayı anlaşılır bir şekilde açıklayan bir bildirim sayfası açılacaktır. -[* qs-tracy.webp .{url:-}(debugger screen) *] +[* qs-tracy.avif .{url:-}(hata ayıklayıcı ekranı) *] -Tracy, hataları ararken size önemli ölçüde yardımcı olacaktır. Ayrıca, önemli çalışma zamanı verileri hakkında sizi bilgilendiren sağ alt köşedeki yüzen Tracy Çubuğuna da dikkat edin. +Tracy, uygulamadaki hataları ararken bize çok yardımcı olacaktır. Ayrıca, ekranın sağ alt köşesindeki, uygulama çalışmasından bilgiler içeren kayan Tracy Çubuğuna dikkat edin. [* qs-tracybar.webp .{url:-} *] -Üretim modunda, Tracy elbette devre dışıdır ve herhangi bir hassas bilgiyi açığa çıkarmaz. Bunun yerine tüm hatalar `log/` dizinine kaydedilir. Sadece deneyin. `app/Bootstrap.php` adresinde, aşağıdaki kod parçasını bulun, satırdaki yorumu kaldırın ve yöntem çağrısı parametresini `false` olarak değiştirin, böylece aşağıdaki gibi görünür: +Üretim modunda, Tracy elbette kapalıdır ve hiçbir hassas bilgi göstermez. Bu durumda tüm hatalar `log/` klasöründe saklanır. Hadi deneyelim. `app/Bootstrap.php` dosyasında aşağıdaki satırın yorumunu kaldırın ve kodun şöyle görünmesi için çağrı parametresini `false` olarak değiştirin: ```php .{file:app/Bootstrap.php} ... -$configurator->setDebugMode(false); -$configurator->enableTracy(__DIR__ . '/../log'); +$this->configurator->setDebugMode(false); ... ``` -Web sayfasını yeniledikten sonra, kırmızı ekran sayfasının yerini kullanıcı dostu mesaj alacaktır: +Sayfayı yeniledikten sonra artık Tracy'yi görmeyeceğiz. Bunun yerine, kullanıcı dostu bir mesaj görüntülenecektir: -[* qs-fatal.webp .{url:-}(error screen) *] +[* qs-fatal.webp .{url:-}(hata ekranı) *] -Şimdi, `log/` dizinine bakın. Orada hata günlüğünü (exception.log dosyasında) ve ayrıca hata mesajını içeren sayfayı (adı `exception` ile başlayan bir HTML dosyasına kaydedilmiş olarak) bulabilirsiniz. +Şimdi `log/` dizinine bakalım. Burada ( `exception.log` dosyasında) günlüğe kaydedilen hatayı ve ayrıca zaten bilinen hata mesajı sayfasını ( `exception-` ile başlayan bir adla HTML dosyası olarak kaydedilmiş) bulacağız. -`// $configurator->setDebugMode(false);` satırını tekrar yorumlayın. Tracy, `localhost` ortamında geliştirme modunu otomatik olarak etkinleştirir ve başka yerlerde devre dışı bırakır. +`// $configurator->setDebugMode(false);` satırını tekrar yorumlayalım. Tracy, localhost'ta geliştirici modunu otomatik olarak etkinleştirir ve başka her yerde devre dışı bırakır. -Şimdi hatayı düzeltebilir ve uygulamamızı tasarlamaya devam edebiliriz. +Oluşturduğumuz hatayı düzeltebilir ve uygulamayı yazmaya devam edebiliriz. -Teşekkür gönderin .[#toc-send-thanks] -===================================== +Teşekkür Gönderin +================= -Size açık kaynak yazarlarını mutlu edecek bir numara göstereceğiz. Projenizin kullandığı kütüphanelere GitHub'da kolayca bir yıldız verebilirsiniz. Sadece çalıştırın: +Size açık kaynak yazarlarını memnun edecek bir numara göstereceğiz. Projenizin kullandığı kütüphanelere GitHub'da basit bir şekilde yıldız verebilirsiniz. Sadece şunu çalıştırın: ```shell composer thanks ``` -Dene bakalım! +Deneyin! {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/tr/@left-menu.texy b/quickstart/tr/@left-menu.texy index 06fc7acfd2..34108233e9 100644 --- a/quickstart/tr/@left-menu.texy +++ b/quickstart/tr/@left-menu.texy @@ -1,9 +1,9 @@ Öğretici ******** -- [İlk Başvurunuzu Oluşturun! |@home] -- [Blog Ana Sayfası |home-page] -- [Tek Gönderi Sayfası |single-post] +- [İlk uygulamamızı yazıyoruz! |@home] +- [Blog giriş sayfası |home-page] +- [Gönderi sayfası |single-post] - [Yorumlar |comments] -- [Gönderi Oluşturma ve Düzenleme |creating-posts] +- [Gönderi oluşturma ve düzenleme |creating-posts] - [Model |Model] - [Kimlik Doğrulama |authentication] diff --git a/quickstart/tr/@meta.texy b/quickstart/tr/@meta.texy new file mode 100644 index 0000000000..cdced5a0a3 --- /dev/null +++ b/quickstart/tr/@meta.texy @@ -0,0 +1 @@ +{{sitename: İlk uygulamamızı yazıyoruz!}} diff --git a/quickstart/tr/authentication.texy b/quickstart/tr/authentication.texy index 46f7547744..2247f59fd6 100644 --- a/quickstart/tr/authentication.texy +++ b/quickstart/tr/authentication.texy @@ -1,36 +1,36 @@ Kimlik Doğrulama **************** -Nette, sayfanızda kimlik doğrulamayı nasıl programlayacağınız konusunda size yönergeler sağlar, ancak bunu belirli bir şekilde yapmanız için sizi zorlamaz. Uygulama size bağlıdır. Nette, sizi kullanıcıyı istediğiniz şekilde bulan `authenticate` adlı tek bir yöntemi uygulamaya zorlayan bir `Nette\Security\Authenticator` arayüzüne sahiptir. +Nette, sitelerimizde kimlik doğrulama programlamanın bir yolunu sunar, ancak bizi hiçbir şeye zorlamaz. Uygulama tamamen bize bağlıdır. Nette, yalnızca kullanıcıyı istediğimiz şekilde doğrulayan tek bir `authenticate` metodu gerektiren `Nette\Security\Authenticator` arayüzünü içerir. -Bir kullanıcının kendi kimliğini doğrulamasının birçok yolu vardır. En yaygın yol *parola tabanlı kimlik doğrulamadır* (kullanıcı adını veya e-postasını ve bir parola sağlar), ancak başka yollar da vardır. Birçok web sitesindeki "Facebook ile Giriş Yap" düğmelerine aşina olabilirsiniz veya Google/Twitter/GitHub veya başka bir site üzerinden giriş yapabilirsiniz. Nette ile istediğiniz kimlik doğrulama yöntemine sahip olabilir veya bunları birleştirebilirsiniz. Bu size kalmış bir şey. +Bir kullanıcının doğrulanabileceği birçok olasılık vardır. En yaygın doğrulama yöntemi şifre iledir (kullanıcı adını veya e-postasını ve şifresini sağlar), ancak başka yollar da vardır. Bazı sitelerde "Facebook ile Giriş Yap" veya Google/Twitter/GitHub ile giriş yapma gibi düğmeleri biliyor olabilirsiniz. Nette ile herhangi bir giriş yöntemine sahip olabiliriz veya bunları birleştirebiliriz. Bu tamamen bize bağlıdır. -Normalde kendi kimlik doğrulayıcınızı yazarsınız, ancak bu basit küçük blog için, bir yapılandırma dosyasında depolanan bir parola ve kullanıcı adına dayalı olarak kimlik doğrulaması yapan yerleşik kimlik doğrulayıcıyı kullanacağız. Test amaçları için iyidir. Bu yüzden `config/common.neon` yapılandırma dosyasına aşağıdaki *security* bölümünü ekleyeceğiz: +Normalde kendi kimlik doğrulayıcımızı yazardık, ancak bu basit küçük blog için yapılandırma dosyasında saklanan şifre ve kullanıcı adına göre giriş yapan yerleşik kimlik doğrulayıcıyı kullanacağız. Test amaçları için kullanışlıdır. Bu nedenle, `config/common.neon` yapılandırma dosyasına aşağıdaki *security* bölümünü ekleyeceğiz: ```neon .{file:config/common.neon} security: users: - admin: secret # user 'admin', password 'secret' + admin: secret # kullanıcı 'admin', şifre 'secret' ``` -Nette, DI konteynerinde otomatik olarak bir hizmet oluşturacaktır. +Nette, DI konteynerinde otomatik olarak bir servis oluşturur. -Giriş Formu .[#toc-sign-in-form] -================================ +Giriş Formu +=========== -Artık kimlik doğrulamanın arka uç kısmını hazır hale getirdik ve kullanıcının oturum açacağı bir kullanıcı arayüzü sağlamamız gerekiyor. Şimdi *SignPresenter* adında yeni bir sunucu oluşturalım. +Şimdi kimlik doğrulamamız hazır ve giriş yapmak için kullanıcı arayüzünü hazırlamamız gerekiyor. Bu nedenle, *SignPresenter* adlı yeni bir presenter oluşturalım: -- bir oturum açma formu görüntüleme (kullanıcı adı ve şifre sorma) -- form gönderildiğinde kullanıcının kimliğini doğrulama -- oturum kapatma eylemi sağlayın +- giriş formunu görüntüler (giriş adı ve şifre ile) +- form gönderildikten sonra kullanıcıyı doğrular +- oturumu kapatma seçeneği sunar -Giriş formu ile başlayalım. Sunucuda formların nasıl çalıştığını zaten biliyorsunuz. `SignPresenter` ve `createComponentSignInForm` yöntemini oluşturun. Bu şekilde görünmelidir: +Giriş formuyla başlayalım. Presenter'larda formların nasıl çalıştığını zaten biliyoruz. Bu nedenle bir `SignPresenter` presenter'ı oluşturacağız ve `createComponentSignInForm` metodunu yazacağız. Şöyle görünmelidir: -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Sign; use Nette; use Nette\Application\UI\Form; @@ -40,69 +40,69 @@ final class SignPresenter extends Nette\Application\UI\Presenter protected function createComponentSignInForm(): Form { $form = new Form; - $form->addText('username', 'Username:') - ->setRequired('Please enter your username.'); + $form->addText('username', 'Kullanıcı adı:') + ->setRequired('Lütfen kullanıcı adınızı girin.'); - $form->addPassword('password', 'Password:') - ->setRequired('Please enter your password.'); + $form->addPassword('password', 'Şifre:') + ->setRequired('Lütfen şifrenizi girin.'); - $form->addSubmit('send', 'Sign in'); + $form->addSubmit('send', 'Giriş yap'); - $form->onSuccess[] = [$this, 'signInFormSucceeded']; + $form->onSuccess[] = $this->signInFormSucceeded(...); return $form; } } ``` -Kullanıcı adı ve şifre için bir giriş vardır. +Kullanıcı adı ve şifre için alanlar vardır. -Şablon .[#toc-template] ------------------------ +Şablon +------ -Form şablonda oluşturulacaktır `in.latte` +Form, `in.latte` şablonunda işlenecektir: -```latte .{file:app/Presenters/templates/Sign/in.latte} +```latte .{file:app/Presentation/Sign/in.latte} {block content} -<h1 n:block=title>Sign in</h1> +<h1 n:block=title>Giriş Yap</h1> {control signInForm} ``` -Oturum Açma İşleyicisi .[#toc-login-handler] --------------------------------------------- +Giriş Callback'i +---------------- -Ayrıca, kullanıcının oturum açması için form gönderildikten hemen sonra çağrılan bir *form işleyici* ekliyoruz. +Ardından, form başarıyla gönderildikten hemen sonra çağrılacak olan kullanıcı girişi için callback'i ekleyeceğiz. -İşleyici sadece kullanıcının girdiği kullanıcı adı ve şifreyi alacak ve daha önce tanımlanan kimlik doğrulayıcıya aktaracaktır. Kullanıcı giriş yaptıktan sonra, onu ana sayfaya yönlendireceğiz. +Callback yalnızca kullanıcının doldurduğu kullanıcı adını ve şifreyi alır ve bunları kimlik doğrulayıcıya iletir. Giriş yaptıktan sonra ana sayfaya yönlendiririz. -```php .{file:app/Presenters/SignPresenter.php} -public function signInFormSucceeded(Form $form, \stdClass $data): void +```php .{file:app/Presentation/Sign/SignPresenter.php} +private function signInFormSucceeded(Form $form, \stdClass $data): void { try { $this->getUser()->login($data->username, $data->password); $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { - $form->addError('Incorrect username or password.'); + $form->addError('Yanlış kullanıcı adı veya şifre.'); } } ``` -[User::login() |api:Nette\Security\User::login()] yöntemi, kullanıcı adı veya parola daha önce tanımladıklarımızla eşleşmediğinde bir istisna fırlatmalıdır. Zaten bildiğimiz gibi, bu bir [Tracy |tracy:] kırmızı ekranına veya üretim modunda dahili bir sunucu hatası hakkında bilgi veren bir mesaja neden olur. Bundan hoşlanmayız. Bu yüzden istisnayı yakalıyoruz ve forma güzel ve dostça bir hata mesajı ekliyoruz. +[User::login() |api:Nette\Security\User::login()] metodu, kullanıcı adı ve şifre yapılandırma dosyasındaki verilerle eşleşmezse bir istisna atar. Zaten bildiğimiz gibi, bu kırmızı bir hata sayfasına veya üretim modunda bir sunucu hatası hakkında bilgi veren bir mesaja neden olabilir. Ancak bunu istemiyoruz. Bu nedenle bu istisnayı yakalarız ve forma güzel, kullanıcı dostu bir hata mesajı iletiriz. -Formda hata oluştuğunda, formun bulunduğu sayfa yeniden oluşturulacak ve formun üzerinde, kullanıcıya yanlış bir kullanıcı adı veya şifre girdiğini bildiren güzel bir mesaj olacaktır. +Formda bir hata oluştuğunda, form içeren sayfa yeniden çizilir ve formun üzerinde kullanıcıya yanlış giriş adı veya şifre girdiğini bildiren güzel bir mesaj görüntülenir. -Sunum Yapanların Güvenliği .[#toc-security-of-presenters] -========================================================= +Presenter'ların Güvenliğini Sağlama +=================================== -Gönderi eklemek ve düzenlemek için bir form oluşturacağız. Sunucuda tanımlanmıştır `EditPresenter`. Amaç, oturum açmamış kullanıcıların sayfaya erişmesini engellemektir. +Gönderi ekleme ve düzenleme formunu güvence altına alacağız. Bu, `EditPresenter` presenter'ında tanımlanmıştır. Amaç, oturum açmamış kullanıcıların sayfaya erişimini engellemektir. -[Sunum yapan kişinin yaşam döngüsünün |application:presenters#life-cycle-of-presenter] hemen başında başlatılan bir `startup()` yöntemi oluşturuyoruz. Bu, oturum açmamış kullanıcıları oturum açma formuna yönlendirir. +[Presenter yaşam döngüsünün |application:presenters#Presenter Yaşam Döngüsü] hemen başında çalışan `startup()` metodunu oluşturacağız. Bu metot, oturum açmamış kullanıcıları giriş sayfasına yönlendirir. -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} public function startup(): void { parent::startup(); @@ -114,67 +114,66 @@ public function startup(): void ``` -Bağlantıları Gizle .[#toc-hide-links] -------------------------------------- +Bağlantıları Gizleme +-------------------- -Kimliği doğrulanmamış bir kullanıcı artık *oluştur* veya *sayfa düzenle* sayfalarını göremez, ancak bunlara işaret eden bağlantıları hala görebilir. Bunları da gizleyelim. Böyle bir bağlantı `app/Presenters/templates/Home/default.latte` adresindedir ve yalnızca kullanıcı oturum açtığında görünür olmalıdır. +Yetkisiz bir kullanıcı artık *create* veya *edit* sayfasını göremez, ancak yine de onlara bağlantıları görebilir. Bunları da gizlemeliyiz. Böyle bir bağlantı `app/Presentation/Home/default.latte` şablonundadır ve yalnızca oturum açmış kullanıcılar tarafından görülmelidir. -Bunu `n:if` adlı *n:attribute* kullanarak gizleyebiliriz. Eğer içindeki ifade `false` ise, tüm `<a>` etiketi ve içeriği görüntülenmeyecektir: +Bunu `n:if` adlı bir *n:attribute* kullanarak gizleyebiliriz. Bu koşul `false` ise, içerik dahil tüm `<a>` etiketi gizli kalacaktır. ```latte -<a n:href="Edit:create" n:if="$user->isLoggedIn()">Create post</a> +<a n:href="Edit:create" n:if="$user->isLoggedIn()">Gönderi oluştur</a> ``` -için bir kısayoldur ( `tag-if` ile karıştırmayın): +bu, aşağıdaki gösterimin kısaltmasıdır (`tag-if` ile karıştırılmamalıdır): ```latte -{if $user->isLoggedIn()}<a n:href="Edit:create">Create post</a>{/if} +{if $user->isLoggedIn()}<a n:href="Edit:create">Gönderi oluştur</a>{/if} ``` -`app/Presenters/templates/Post/show.latte` adresinde bulunan düzenleme bağlantısını da benzer şekilde gizlemelisiniz. +Aynı şekilde `app/Presentation/Post/show.latte` şablonundaki bağlantıyı da gizleyeceğiz. -Giriş Formu Bağlantısı .[#toc-login-form-link] -============================================== +Giriş Bağlantısı +================ -Hey, ama giriş sayfasına nasıl ulaşacağız? Onu işaret eden bir bağlantı yok. `@layout.latte` şablon dosyasına bir tane ekleyelim. Güzel bir yer bulmaya çalışın, en çok sevdiğiniz herhangi bir yer olabilir. +Aslında giriş sayfasına nasıl ulaşırız? Ona yönlendiren hiçbir bağlantı yok. Öyleyse onu `@layout.latte` şablonuna ekleyelim. Uygun bir yer bulmaya çalışın - hemen hemen her yerde olabilir. -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <ul class="navig"> - <li><a n:href="Home:">Home</a></li> + <li><a n:href="Home:">Makaleler</a></li> {if $user->isLoggedIn()} - <li><a n:href="Sign:out">Sign out</a></li> + <li><a n:href="Sign:out">Çıkış yap</a></li> {else} - <li><a n:href="Sign:in">Sign in</a></li> + <li><a n:href="Sign:in">Giriş yap</a></li> {/if} </ul> ... ``` -Kullanıcı henüz oturum açmamışsa, "Oturum aç" bağlantısını göstereceğiz. Aksi takdirde, "Oturumu kapat" bağlantısını göstereceğiz. Bu eylemi SignPresenter'a ekliyoruz. +Kullanıcı oturum açmamışsa, "Giriş yap" bağlantısı görüntülenir. Aksi takdirde, "Çıkış yap" bağlantısı görüntülenir. Bu eylemi `SignPresenter`'a da ekleyeceğiz. -Oturumu kapatma eylemi şu şekilde görünür ve kullanıcıyı hemen yeniden yönlendirdiğimiz için bir görünüm şablonuna gerek yoktur. +Kullanıcıyı oturumu kapattıktan hemen sonra yönlendirdiğimiz için herhangi bir şablona gerek yoktur. Oturumu kapatma şöyle görünür: -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} public function actionOut(): void { $this->getUser()->logout(); - $this->flashMessage('You have been signed out.'); + $this->flashMessage('Başarıyla çıkış yapıldı.'); $this->redirect('Home:'); } ``` -Sadece `logout()` yöntemini çağırır ve ardından kullanıcıya güzel bir mesaj gösterir. +Yalnızca `logout()` metodu çağrılır ve ardından başarılı oturum kapatmayı onaylayan güzel bir mesaj görüntülenir. -Özet .[#toc-summary] -==================== +Özet +==== -Giriş yapmak ve ayrıca kullanıcının oturumunu kapatmak için bir bağlantımız var. Kimlik doğrulama için yerleşik kimlik doğrulayıcıyı kullandık ve bu basit bir test uygulaması olduğu için oturum açma ayrıntıları yapılandırma dosyasında yer alıyor. Düzenleme formlarını da güvence altına aldık, böylece yalnızca oturum açmış kullanıcılar gönderi ekleyebilir ve düzenleyebilir. +Giriş yapmak ve ayrıca kullanıcının oturumunu kapatmak için bir bağlantımız var. Doğrulama için yerleşik kimlik doğrulayıcıyı kullandık ve giriş bilgileri yapılandırma dosyasındadır, çünkü bu basit bir test uygulamasıdır. Ayrıca düzenleme formlarını da güvence altına aldık, böylece yalnızca oturum açmış kullanıcılar gönderi ekleyebilir ve düzenleyebilir. .[note] -Burada [kullanıcı girişi |security:authentication] ve [yetkilendirme |security:authorization] hakkında daha fazla bilgi edinebilirsiniz. +Burada [kullanıcı girişi |security:authentication] ve [Yetki Doğrulama |security:authorization] hakkında daha fazla bilgi edinebilirsiniz. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/tr/comments.texy b/quickstart/tr/comments.texy index 14e08396f2..e5865d6051 100644 --- a/quickstart/tr/comments.texy +++ b/quickstart/tr/comments.texy @@ -1,28 +1,28 @@ Yorumlar ******** -Blog faaliyete geçti, çok iyi blog yazıları yazdık ve bunları Adminer üzerinden yayınladık. İnsanlar blogu okuyor ve fikirlerimiz hakkında çok tutkulular. Her gün övgü dolu pek çok e-posta alıyoruz. Ancak tüm bu övgüler sadece e-postada kaldığında ve başka kimse okuyamadığında ne işe yarıyor? İnsanlar doğrudan bloga yorum yapabilse ve böylece herkes ne kadar harika olduğumuzu okuyabilse daha iyi olmaz mıydı? +Blogu web sunucusuna yükledik ve Adminer kullanarak birkaç çok ilginç gönderi yayınladık. İnsanlar blogumuzu okuyor ve bundan çok heyecan duyuyorlar. Her gün övgülerle dolu birçok e-posta alıyoruz. Ama tüm bu övgüler sadece e-postamızda varsa ve kimse okuyamıyorsa ne anlamı var? Okuyucunun makaleye doğrudan yorum yapabilmesi daha iyi olurdu, böylece herkes ne kadar harika olduğumuzu okuyabilirdi. -Tüm makaleleri yorumlanabilir hale getirelim. +Öyleyse yorumları programlayalım. -Yeni Tablo Oluşturma .[#toc-creating-a-new-table] -================================================= +Yeni Tablo Oluşturma +==================== -Adminer'ı tekrar çalıştırın ve bu sütunlarla `comments` adında yeni bir tablo oluşturun: +Adminer'ı çalıştıralım ve şu sütunlarla `comments` tablosunu oluşturalım: -- `id` int, otomatik artırmayı (AI) kontrol edin -- `post_id`, `posts` tablosuna referans veren bir yabancı anahtar +- `id` int, autoincrement (AI) işaretleyin +- `post_id`, `posts` tablosuna başvuran yabancı anahtar - `name` varchar, uzunluk 255 - `email` varchar, uzunluk 255 -- `content` metin -- `created_at` zaman damgası +- `content` text +- `created_at` timestamp -Bu şekilde görünmelidir: +Tablo bu şekilde görünmelidir: [* adminer-comments.webp *] -InnoDB tablo depolamasını kullanmayı unutmayın ve Kaydet'e basın. +Yine InnoDB depolama alanını kullanmayı unutmayın. ```sql CREATE TABLE `comments` ( @@ -37,83 +37,83 @@ CREATE TABLE `comments` ( ``` -Yorum Yapma Formu .[#toc-form-for-commenting] -============================================= +Yorum Formu +=========== -İlk olarak, kullanıcıların sayfamıza yorum yapmasına izin verecek bir form oluşturmamız gerekiyor. Nette Framework formlar için harika bir desteğe sahiptir. Bir sunumcuda yapılandırılabilir ve bir şablonda işlenebilirler. +Öncelikle, kullanıcıların gönderilere yorum yapmasına olanak tanıyan bir form oluşturmamız gerekiyor. Nette Framework, formlar için harika bir desteğe sahiptir. Bunları presenter'da yapılandırabilir ve şablonda oluşturabiliriz. -Nette Framework *bileşenler* kavramına sahiptir. Bir **bileşen**, başka bir bileşene eklenebilen yeniden kullanılabilir bir sınıf veya kod parçasıdır. Sunucu bile bir bileşendir. Her bileşen, bileşen fabrikası kullanılarak oluşturulur. Öyleyse `PostPresenter` adresinde yorum formu fabrikasını tanımlayalım. +Nette Framework, *bileşenler* kavramını kullanır. **Bileşen**, başka bir bileşene eklenebilen yeniden kullanılabilir bir sınıf veya kod parçasıdır. Presenter bile bir bileşendir. Her bileşen bir fabrika aracılığıyla oluşturulur. Bu nedenle, `PostPresenter` presenter'ında yorum formu için bir fabrika oluşturacağız. -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} protected function createComponentCommentForm(): Form { $form = new Form; // Nette\Application\UI\Form anlamına gelir - $form->addText('name', 'Your name:') + $form->addText('name', 'İsim:') ->setRequired(); - $form->addEmail('email', 'Email:'); + $form->addEmail('email', 'E-posta:'); - $form->addTextArea('content', 'Comment:') + $form->addTextArea('content', 'Yorum:') ->setRequired(); - $form->addSubmit('send', 'Publish comment'); + $form->addSubmit('send', 'Yorumu Yayınla'); return $form; } ``` -Bunu biraz açıklayalım. İlk satır `Form` bileşeninin yeni bir örneğini oluşturuyor. Aşağıdaki yöntemler HTML girdilerini form tanımına ekliyor. `->addText` şu şekilde render edilecek `<input type=text name=name>`ile `<label>Your name:</label>`. Şu anda zaten tahmin etmiş olabileceğiniz gibi, `->addTextArea` bir `<textarea>` ve `->addSubmit` bir `<input type=submit>`. Bunun gibi daha fazla yöntem var, ancak şu anda bilmeniz gereken tek şey bu. [Belgelerden daha fazlasını öğrenebilirsiniz |forms:]. +Bunu biraz daha açıklayalım. İlk satır, `Form` bileşeninin yeni bir örneğini oluşturur. Sonraki metotlar, bu form tanımına HTML input elemanları ekler. `->addText`, `<label>İsim:</label>` etiketiyle birlikte `<input type="text" name="name">` olarak oluşturulacaktır. Muhtemelen doğru tahmin ettiğiniz gibi, `->addTextArea` `<textarea>` olarak ve `->addSubmit` `<input type="submit">` olarak oluşturulacaktır. Çok daha fazla benzer metot vardır, ancak bunlar şimdilik bu form için yeterlidir. Daha fazlasını [Formlar belgelerinde |forms:] okuyabilirsiniz. -Form bileşeni bir sunucuda tanımlandıktan sonra, onu bir şablonda oluşturabiliriz (görüntüleyebiliriz). Bunu yapmak için, `{control}` etiketini yazı ayrıntısı şablonunun sonuna, `Post/show.latte` içine yerleştirin. Bileşenin adı `commentForm` olduğundan ( `createComponentCommentForm` yönteminin adından türetilmiştir), etiket aşağıdaki gibi görünecektir +Form presenter'da zaten tanımlanmışsa, onu şablonda oluşturabiliriz (görüntüleyebiliriz). Bunu, şablonun sonuna, tek bir gönderiyi oluşturan `Post/show.latte` içine `{control}` etiketini yerleştirerek yaparız. Bileşenin adı `commentForm` olduğundan (`createComponentCommentForm` metodunun adından türetilmiştir), etiket şöyle görünecektir: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... -<h2>Post new comment</h2> +<h2>Yeni bir yorum ekleyin</h2> {control commentForm} ``` -Şimdi bazı gönderilerin detaylarını kontrol ederseniz, yorum göndermek için yeni bir form olacaktır. +Şimdi gönderi detay sayfasını görüntülerseniz, sonunda yeni yorum formunu göreceksiniz. -Veritabanına Kaydetme .[#toc-saving-to-database] -================================================ +Veritabanına Kaydetme +===================== -Bazı verileri göndermeyi denediniz mi? Formun herhangi bir işlem yapmadığını fark etmiş olabilirsiniz. Sadece orada duruyor, havalı görünüyor ve hiçbir şey yapmıyor. Gönderilen verileri kaydedecek bir geri arama yöntemi eklememiz gerekiyor. +Formu doldurup göndermeyi denediniz mi? Muhtemelen formun aslında hiçbir şey yapmadığını fark ettiniz. Gönderilen verileri kaydedecek bir geri arama metodu eklememiz gerekiyor. -`commentForm` bileşen fabrikasında `return` satırından önce aşağıdaki satırı yerleştirin: +`commentForm` bileşeni için fabrika içindeki `return` satırından önceki satıra aşağıdaki satırı yerleştirin: ```php -$form->onSuccess[] = [$this, 'commentFormSucceeded']; +$form->onSuccess[] = $this->commentFormSucceeded(...); ``` -"Form başarıyla gönderildikten sonra, geçerli sunum yapan kişinin `commentFormSucceeded` yöntemini çağır" anlamına gelir. Bu yöntem henüz mevcut değil, bu yüzden onu oluşturalım. +Önceki yazım "form başarıyla gönderildikten sonra mevcut presenter'dan `commentFormSucceeded` metodunu çağır" anlamına gelir. Ancak bu metot henüz mevcut değil. Öyleyse oluşturalım: -```php .{file:app/Presenters/PostPresenter.php} -public function commentFormSucceeded(\stdClass $data): void +```php .{file:app/Presentation/Post/PostPresenter.php} +private function commentFormSucceeded(\stdClass $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); $this->database->table('comments')->insert([ - 'post_id' => $postId, + 'post_id' => $id, 'name' => $data->name, 'email' => $data->email, 'content' => $data->content, ]); - $this->flashMessage('Thank you for your comment', 'success'); + $this->flashMessage('Yorumunuz için teşekkür ederiz', 'success'); $this->redirect('this'); } ``` -Bunu `commentForm` bileşen fabrikasından hemen sonra yerleştirmelisiniz. +Bu metodu doğrudan `commentForm` form fabrikasının arkasına yerleştirin. -new metodunun bir argümanı vardır; bu argüman, bileşen fabrikası tarafından oluşturulan, gönderilen formun örneğidir. Gönderilen değerleri `$data` adresinden alıyoruz. Ve sonra verileri `comments` veritabanı tablosuna ekliyoruz. +Bu yeni metodun bir argümanı vardır, bu da gönderilen formun örneğidir - fabrika tarafından oluşturulmuştur. Gönderilen değerleri `$data` içinde alırız. Ve ardından verileri `comments` veritabanı tablosuna kaydederiz. -Açıklanması gereken iki yöntem çağrısı daha vardır. Yönlendirme, kelimenin tam anlamıyla geçerli sayfaya yönlendirir. Form gönderildiğinde, geçerli olduğunda ve geri arama işlemi yapması gerekeni yaptığında bunu her seferinde yapmalısınız. Ayrıca, formu gönderdikten sonra sayfayı yeniden yönlendirdiğinizde, bazen tarayıcıda görebileceğiniz iyi bilinen `Would you like to submit the post data again?` mesajını görmezsiniz. (Genel olarak, `POST` yöntemiyle bir form gönderdikten sonra, kullanıcıyı her zaman bir `GET` eylemine yönlendirmelisiniz). +Açıklanması gereken iki metot daha var. Redirect metodu kelimenin tam anlamıyla mevcut sayfaya geri yönlendirir. Bu, geçerli veriler içeriyorsa ve geri arama işlemi gerektiği gibi gerçekleştirdiyse, her form gönderiminden sonra yapılması uygundur. Ayrıca, formu gönderdikten sonra sayfayı yönlendirirsek, tarayıcıda ara sıra görebileceğimiz iyi bilinen `Form verilerini tekrar göndermek istiyor musunuz?` mesajını görmeyiz. (Genel olarak, `POST` metoduyla bir form gönderdikten sonra her zaman bir `GET` eylemine yönlendirme yapılmalıdır.) -`flashMessage`, kullanıcıyı bir işlemin sonucu hakkında bilgilendirmek içindir. Yönlendirme yaptığımız için, mesaj doğrudan şablona aktarılamaz ve işlenemez. Bu yüzden, onu saklayacak ve bir sonraki sayfa yüklemesinde kullanılabilir hale getirecek bu yöntem vardır. Flash mesajları varsayılan `app/Presenters/templates/@layout.latte` dosyasında oluşturulur ve aşağıdaki gibi görünür: +`flashMessage` metodu, kullanıcıyı bir işlemin sonucu hakkında bilgilendirmek içindir. Yönlendirme yaptığımız için, mesaj basitçe şablona aktarılıp oluşturulamaz. Bu nedenle, bu mesajı kaydeden ve bir sonraki sayfa yüklemesinde erişilebilir kılan bu metot vardır. Flash mesajları ana şablon `app/Presentation/@layout.latte` içinde oluşturulur ve şöyle görünür: ```latte <div n:foreach="$flashes as $flash" n:class="flash, $flash->type"> @@ -121,20 +121,20 @@ Açıklanması gereken iki yöntem çağrısı daha vardır. Yönlendirme, kelim </div> ``` -Zaten bildiğimiz gibi, bunlar şablona otomatik olarak aktarılır, bu yüzden çok fazla düşünmenize gerek yoktur, sadece çalışır. Daha fazla ayrıntı için [belgeleri kontrol |application:presenters#flash-messages] edin. +Bildiğimiz gibi, flash mesajları otomatik olarak şablona aktarılır, bu yüzden bunun hakkında fazla düşünmemize gerek yok, sadece çalışır. Daha fazla bilgi için [belgeleri ziyaret edin |application:presenters#Flash Mesajları]. -Yorumların Oluşturulması .[#toc-rendering-the-comments] -======================================================= +Yorumları Oluşturma +=================== -Bu çok seveceğiniz şeylerden biri. Nette Database [Explorer |database:explorer] adında harika bir özelliğe sahip. Tabloları InnoDB olarak oluşturduğumuzu hatırlıyor musunuz? Adminer, bizi bir ton işten kurtaracak sözde [yabancı anahtarları |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html] oluşturdu. +Bu, kesinlikle seveceğiniz şeylerden biri. Nette Database, [Explorer |database:explorer] adlı harika bir özelliğe sahiptir. Veritabanındaki tabloları kasıtlı olarak InnoDB depolama alanını kullanarak oluşturduğumuzu hatırlıyor musunuz? Adminer böylece [yabancı anahtarlar |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html] olarak adlandırılan ve bize çok iş kazandıran bir şey yarattı. -Nette Database Explorer, tablolar arasındaki ilişkileri çözmek için yabancı anahtarları kullanır ve ilişkileri bilerek sizin için otomatik olarak sorgular oluşturabilir. +Nette Database Explorer, tablolar arasındaki karşılıklı ilişkiyi çözmek için yabancı anahtarları kullanır ve bu ilişkilerin bilgisiyle otomatik olarak veritabanı sorguları oluşturabilir. -Hatırlayabileceğiniz gibi, `$post` değişkenini `PostPresenter::renderShow()` adresindeki şablona aktardık ve şimdi `post_id` sütunu `$post->id` sütunumuza eşit olan tüm yorumları yinelemek istiyoruz. Bunu `$post->related('comments')` adresini çağırarak yapabilirsiniz. Bu kadar basit. Ortaya çıkan koda bakın. +Hatırlayacağınız gibi, `$post` değişkenini `PostPresenter::renderShow()` metoduyla şablona aktardık ve şimdi `post_id` sütununun değeri `$post->id` ile eşleşen tüm yorumlar üzerinde yineleme yapmak istiyoruz. Bunu `$post->related('comments')` çağrısıyla başarabiliriz. Evet, bu kadar basit. Sonuç koduna bakalım: -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { ... $this->template->post = $post; @@ -144,15 +144,15 @@ public function renderShow(int $postId): void Ve şablon: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... -<h2>Comments</h2> +<h2>Yorumlar</h2> <div class="comments"> {foreach $comments as $comment} <p><b><a href="mailto:{$comment->email}" n:tag-if="$comment->email"> {$comment->name} - </a></b> said:</p> + </a></b> yazdı:</p> <div>{$comment->content}</div> {/foreach} @@ -160,13 +160,12 @@ Ve şablon: ... ``` -Özel `n:tag-if` niteliğine dikkat edin. `n: attributes` 'un nasıl çalıştığını zaten biliyorsunuz. Eğer özniteliğin başına `tag-` eklerseniz, sadece etiketlerin etrafına sarılır, içeriklerine değil. Bu, yorumcunun e-posta adresini verdiyse adını bir bağlantı haline getirmenize olanak tanır. Bu iki satırın sonuçları aynıdır: +Özel `n:tag-if` niteliğine dikkat edin. `n:attribute`lerin nasıl çalıştığını zaten biliyorsunuz. Niteliğe `tag-` önekini eklerseniz, işlevsellik yalnızca HTML etiketine uygulanır, içeriğine değil. Bu, yorumcunun adını yalnızca e-postasını sağladığı takdirde bir bağlantı yapmamızı sağlar. Bu iki satır aynıdır: ```latte -<strong n:tag-if="$important"> Hello there! </strong> +<strong n:tag-if="$important"> Merhaba! </strong> -{if $important}<strong>{/if} Hello there! {if $important}</strong>{/if} +{if $important}<strong>{/if} Merhaba! {if $important}</strong>{/if} ``` {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/tr/creating-posts.texy b/quickstart/tr/creating-posts.texy index 17a5c33352..100237cee2 100644 --- a/quickstart/tr/creating-posts.texy +++ b/quickstart/tr/creating-posts.texy @@ -1,30 +1,30 @@ Gönderi Oluşturma ve Düzenleme ****************************** -Ne harika bir zaman. Süper havalı yeni bir blogumuz var, insanlar yorumlarda tartışıyor ve nihayet daha fazla programlama için biraz zamanımız var. Adminer'i sevmemize rağmen, blog yazıları yazmak o kadar da rahat değil. Belki de doğrudan uygulamamızdan yeni yazılar eklemek için basit bir form eklemenin tam zamanıdır. Hadi yapalım. +Bu harika! Süper havalı yeni bir blogumuz var, insanlar yorumlarda hararetle tartışıyor ve sonunda biraz daha programlama yapmak için zamanımız var. Adminer harika bir araç olmasına rağmen, yeni blog gönderileri yazmak için pek ideal değil. Muhtemelen uygulamadan doğrudan yeni gönderiler eklemek için basit bir form oluşturmanın tam zamanı. Hadi yapalım. Kullanıcı arayüzünü tasarlayarak başlayalım: -1. Ana sayfaya bir "Yeni yazı yaz" bağlantısı ekleyelim. -2. Başlık ve içerik için textarea içeren bir form gösterecektir. -3. Kaydet düğmesine tıkladığınızda, blog gönderisini kaydedecektir. +1. Ana sayfaya "Yeni gönderi yaz" bağlantısını ekleyin. +2. Bu bağlantı, gönderinin içeriği için bir başlık ve bir metin alanı içeren bir form görüntüleyecektir. +3. Kaydet düğmesine tıkladığımızda, gönderi veritabanına kaydedilecektir. -Daha sonra kimlik doğrulama da ekleyeceğiz ve yalnızca oturum açmış kullanıcıların yeni gönderiler eklemesine izin vereceğiz. Ama bunu daha sonra yapalım. Çalışması için hangi kodu yazmamız gerekecek? +Daha sonra ayrıca giriş yapma ekleyeceğiz ve gönderi eklemeyi yalnızca giriş yapmış kullanıcılara izin vereceğiz. Ama bu daha sonra. Her şeyin çalışması için şimdi hangi kodu yazmamız gerekiyor? -1. Gönderi eklemek için bir form içeren yeni bir sunucu oluşturun. -2. Formun başarılı bir şekilde gönderilmesinden sonra tetiklenecek ve yeni gönderiyi veritabanına kaydedecek bir geri arama tanımlayın. -3. Form için yeni bir şablon oluşturun. -4. Ana sayfa şablonuna form için bir bağlantı ekleyin. +1. Gönderi eklemek için bir form içeren yeni bir presenter oluşturun. +2. Form başarıyla gönderildikten sonra çalışacak ve yeni gönderiyi veritabanına kaydedecek bir geri arama tanımlayın. +3. Bu formun olacağı yeni bir şablon oluşturun. +4. Ana sayfa şablonuna forma bir bağlantı ekleyin. -Yeni Sunucu .[#toc-new-presenter] -================================= +Yeni Presenter +============== -Yeni sunucuya `EditPresenter` adını verin ve `app/Presenters/EditPresenter.php` adresine kaydedin. Ayrıca veritabanına bağlanması gerekir, bu nedenle burada yine bir veritabanı bağlantısı gerektirecek bir kurucu yazıyoruz: +Yeni presenter'ı `EditPresenter` olarak adlandıracağız ve `app/Presentation/Edit/` içine kaydedeceğiz. Ayrıca veritabanına bağlanması gerekiyor, bu yüzden burada yine veritabanı bağlantısı gerektirecek bir yapıcı yazacağız: -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Edit; use Nette; use Nette\Application\UI\Form; @@ -39,95 +39,95 @@ final class EditPresenter extends Nette\Application\UI\Presenter ``` -Gönderileri Kaydetme Formu .[#toc-form-for-saving-posts] -======================================================== +Gönderileri Kaydetme Formu +========================== -Formlar ve bileşenler, yorumlar için destek eklediğimizde zaten ele alındı. Konu hakkında kafanız karıştıysa, formların [ve bileşenlerin nasıl çalıştığını |comments#form-for-commenting] tekrar kontrol edin, biz burada bekleyeceğiz ;) +Formları ve bileşenleri yorumları oluştururken zaten açıklamıştık. Hala net değilse, [form ve bileşen oluşturma |comments#Yorum Formu] bölümüne gidin, biz burada bekleyeceğiz ;) -Şimdi bu yöntemi `EditPresenter` adresine ekleyin: +Şimdi bu metodu `EditPresenter` presenter'ına ekleyelim: -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} protected function createComponentPostForm(): Form { $form = new Form; - $form->addText('title', 'Title:') + $form->addText('title', 'Başlık:') ->setRequired(); - $form->addTextArea('content', 'Content:') + $form->addTextArea('content', 'İçerik:') ->setRequired(); - $form->addSubmit('send', 'Save and publish'); - $form->onSuccess[] = [$this, 'postFormSucceeded']; + $form->addSubmit('send', 'Kaydet ve yayınla'); + $form->onSuccess[] = $this->postFormSucceeded(...); return $form; } ``` -Yeni Gönderiyi Formdan Kaydetme .[#toc-saving-new-post-from-form] -================================================================= +Formdan Yeni Gönderi Kaydetme +============================= -Bir işleyici yöntemi ekleyerek devam edin. +Formdan gelen verileri işleyecek metodu ekleyerek devam ediyoruz: -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { $post = $this->database ->table('posts') ->insert($data); - $this->flashMessage('Post was published', 'success'); + $this->flashMessage("Gönderi başarıyla yayınlandı.", 'success'); $this->redirect('Post:show', $post->id); } ``` -Kısa bir açıklama: formdan değerleri alır, bunları veritabanına ekler, kullanıcı için gönderinin başarıyla kaydedildiğine dair bir mesaj oluşturur ve nasıl göründüğünü görebilmeniz için gönderinin yayınlandığı sayfaya yönlendirir. +Sadece hızlı bir özet: bu metot formdan verileri alır, veritabanına ekler, gönderinin başarılı bir şekilde kaydedildiği hakkında kullanıcıya bir mesaj oluşturur ve yeni gönderiyle sayfaya yönlendirir, böylece nasıl göründüğünü hemen görürüz. -Yeni Gönderi Oluşturma Sayfası .[#toc-page-for-creating-a-new-post] -=================================================================== +Yeni Gönderi Oluşturma Sayfası +============================== -Sadece `Edit/create.latte` şablonunu oluşturalım: +Şimdi `Edit/create.latte` şablonunu oluşturalım: -```latte .{file:app/Presenters/templates/Edit/create.latte} +```latte .{file:app/Presentation/Edit/create.latte} {block content} -<h1>New post</h1> +<h1>Yeni gönderi</h1> {control postForm} ``` -Şimdiye kadar her şey netleşmiş olmalı. Son satır, oluşturmak üzere olduğumuz formu göstermektedir. +Her şey zaten açık olmalı. Son satır, henüz oluşturacağımız formu oluşturur. -Buna karşılık gelen bir `renderCreate` yöntemi de oluşturabiliriz, ancak bu gerekli değildir. Veritabanından herhangi bir veri almamız ve bunu şablona aktarmamız gerekmez, bu nedenle bu yöntem boş olacaktır. Bu gibi durumlarda, yöntem hiç mevcut olmayabilir. +Ayrıca karşılık gelen `renderCreate` metodunu da oluşturabilirdik, ancak gerekli değil. Veritabanından herhangi bir veri alıp şablona aktarmamız gerekmiyor, bu yüzden bu metot boş olurdu. Bu gibi durumlarda, metodun hiç var olması gerekmez. -Gönderi Oluşturma Bağlantısı .[#toc-link-for-creating-posts] -============================================================ +Gönderi Oluşturma Bağlantısı +============================ -Muhtemelen `EditPresenter` adresine ve `create` eylemine nasıl bağlantı ekleyeceğinizi zaten biliyorsunuzdur. Deneyin bakalım. +Muhtemelen `EditPresenter` ve onun `create` eylemine nasıl bağlantı ekleyeceğinizi zaten biliyorsunuzdur. Deneyin. -Sadece `app/Presenters/templates/Home/default.latte` dosyasına ekleyin: +Sadece `app/Presentation/Home/default.latte` dosyasına ekleyin: ```latte -<a n:href="Edit:create">Write new post</a> +<a n:href="Edit:create">Yeni gönderi yaz</a> ``` -Gönderileri Düzenleme .[#toc-editing-posts] -=========================================== +Gönderileri Düzenleme +===================== -Mevcut gönderileri düzenleme özelliğini de ekleyelim. Oldukça basit olacak - zaten `postForm` adresimiz var ve bunu düzenleme için de kullanabiliriz. +Şimdi gönderiyi düzenleme seçeneğini de ekleyeceğiz. Çok basit olacak. Zaten `postForm` formumuz hazır ve onu düzenleme için de kullanabiliriz. -`EditPresenter` adresine yeni bir `edit` sayfası ekleyeceğiz: +`EditPresenter` presenter'ına yeni bir `edit` sayfası ekleyeceğiz: -```php .{file:app/Presenters/EditPresenter.php} -public function renderEdit(int $postId): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +public function renderEdit(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { - $this->error('Post not found'); + $this->error('Gönderi bulunamadı'); } $this->getComponent('postForm') @@ -135,26 +135,26 @@ public function renderEdit(int $postId): void } ``` -Ve `Edit/edit.latte` şablonunu oluşturun: +Ve başka bir şablon `Edit/edit.latte` oluşturacağız: -```latte .{file:app/Presenters/templates/Edit/edit.latte} +```latte .{file:app/Presentation/Edit/edit.latte} {block content} -<h1>Edit post</h1> +<h1>Gönderiyi düzenle</h1> {control postForm} ``` -Ve yeni bir gönderi ekleyebilecek (şimdi olduğu gibi) veya mevcut olanları düzenleyebilecek olan `postFormSucceeded` yöntemini güncelleyin: +Ve hem yeni bir makale ekleyebilecek (şimdi yaptığı gibi) hem de zaten var olan bir makaleyi düzenleyebilecek olan `postFormSucceeded` metodunu düzenleyeceğiz: -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); - if ($postId) { + if ($id) { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); $post->update($data); } else { @@ -163,26 +163,25 @@ public function postFormSucceeded(array $data): void ->insert($data); } - $this->flashMessage('Post was published', 'success'); + $this->flashMessage('Gönderi başarıyla yayınlandı.', 'success'); $this->redirect('Post:show', $post->id); } ``` -`postId` parametresi sağlandığında, bir gönderinin düzenlenmekte olduğu anlamına gelir. Böyle bir durumda, gönderinin gerçekten var olup olmadığını kontrol edeceğiz ve eğer varsa, veritabanında güncelleyeceğiz. `postId` parametresi sağlanmazsa, yeni bir yazı ekleneceği anlamına gelir. +Eğer `id` parametresi mevcutsa, bu gönderiyi düzenleyeceğimiz anlamına gelir. Bu durumda, istenen gönderinin gerçekten var olduğunu doğrularız ve eğer varsa, veritabanında güncelleriz. Eğer `id` parametresi mevcut değilse, bu yeni bir gönderinin eklenmesi gerektiği anlamına gelir. -Peki `postId` nereden geliyor? `renderEdit` yöntemine aktarılan parametredir. +Ancak bu `id` parametresi nereden geliyor? Bu, `renderEdit` metoduna eklenen parametredir. -Artık `app/Presenters/templates/Post/show.latte` şablonuna bir bağlantı ekleyebilirsiniz: +Şimdi `app/Presentation/Post/show.latte` şablonuna bir bağlantı ekleyebiliriz: ```latte -<a n:href="Edit:edit $post->id">Edit this post</a> +<a n:href="Edit:edit $post->id">Gönderiyi düzenle</a> ``` -Özet .[#toc-summary] -==================== +Özet +==== -Blog çalışıyor, insanlar hızla yorum yapıyor ve artık yeni yazılar eklemek için Adminer'a güvenmiyoruz. Tamamen bağımsız ve normal insanlar bile buraya gönderi yapabiliyor. Ama durun, bu muhtemelen doğru değil, herkes, yani gerçekten internetteki herkes blogumuza gönderi ekleyebilir. Yalnızca oturum açmış kullanıcıların gönderi yapabilmesi için bir tür kimlik doğrulama gereklidir. Bunu bir sonraki bölümde ekleyeceğiz. +Blog şimdi işlevsel, ziyaretçiler aktif olarak yorum yapıyor ve artık yayınlamak için Adminer'a ihtiyacımız yok. Uygulama tamamen bağımsız ve herkes yeni bir gönderi ekleyebilir. Bir dakika, herkesin - ve gerçekten internet erişimi olan herkesi kastediyorum - yeni gönderiler ekleyebilmesi muhtemelen pek doğru değil. Yeni bir gönderiyi yalnızca giriş yapmış bir kullanıcının ekleyebilmesi için bir tür güvenlik önlemi gerekiyor. Buna bir sonraki bölümde bakacağız. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/tr/home-page.texy b/quickstart/tr/home-page.texy index a6711306d3..185af8a1da 100644 --- a/quickstart/tr/home-page.texy +++ b/quickstart/tr/home-page.texy @@ -2,40 +2,40 @@ Blog Ana Sayfası **************** .[perex] -Son gönderilerinizi görüntüleyen bir ana sayfa oluşturalım. +Şimdi son gönderileri gösteren bir ana sayfa oluşturacağız. -Başlamadan önce, Model-View-Presenter tasarım deseni (MVC((Model-View-Controller)) ile benzer) hakkında en azından bazı temel bilgileri bilmeniz gerekir: +Başlamadan önce, Model-View-Presenter tasarım deseninin (MVC((Model-View-Controller)) benzeri) en azından temellerini bilmek gerekir: -- Model** - veri manipülasyon katmanı. Uygulamanın geri kalanından tamamen ayrılmıştır. Sadece sunum yapan kişilerle iletişim kurar. +- **Model** - verilerle çalışan katman. Uygulamanın geri kalanından tamamen ayrılmıştır. Yalnızca presenter ile iletişim kurar. -- **View** - bir ön uç tanım katmanı. Şablonları kullanarak istenen verileri kullanıcıya sunar. +- **View** - ön uç katmanı. İstenen verileri şablonlar kullanarak oluşturur ve kullanıcıya gösterir. -- Sunucu** (veya Denetleyici) - bir bağlantı katmanı. Sunucu, Model ve Görünümü birbirine bağlar. İstekleri ele alır, Model'den veri ister ve ardından bunları geçerli Görünüm'e iletir. +- **Presenter** (veya Controller) - bağlantı katmanı. Presenter, Model ve View'u birbirine bağlar. İstekleri işler, Model'den veri sorgular ve bunları View'a geri döndürür. -Blogumuz gibi çok basit bir uygulama söz konusu olduğunda, Model katmanı aslında yalnızca veritabanına yapılan sorgulardan oluşacaktır - bunun için ekstra bir PHP koduna ihtiyacımız yoktur. Sadece Presenter ve View katmanlarını oluşturmamız gerekiyor. Nette, her Presenter'ın kendi View'leri vardır, bu yüzden her ikisiyle de aynı anda devam edeceğiz. +Blogumuz gibi basit uygulamalar durumunda, tüm model katmanı yalnızca veritabanı sorgularından oluşacaktır - bunun için henüz ekstra koda ihtiyacımız yok. Başlangıç olarak, yalnızca presenter'ları ve şablonları oluşturacağız. Nette'de her presenter'ın kendi şablonları vardır, bu yüzden onları aynı anda oluşturacağız. -Adminer ile Veritabanı Oluşturma .[#toc-creating-the-database-with-adminer] -=========================================================================== +Adminer Kullanarak Veritabanı Oluşturma +======================================= -Verileri depolamak için MySQL veritabanını kullanacağız çünkü web geliştiricileri arasında en yaygın seçim bu. Ancak bundan hoşlanmıyorsanız, istediğiniz bir veritabanını kullanmaktan çekinmeyin. +Verileri depolamak için MySQL veritabanını kullanacağız, çünkü web uygulaması programcıları arasında en yaygın olanıdır. Ancak kullanmak istemiyorsanız, kendi seçiminize göre bir veritabanı seçmekten çekinmeyin. -Blog yazılarımızı depolayacak veritabanını hazırlayalım. Çok basit bir şekilde başlayabiliriz - sadece yazılar için tek bir tablo ile. +Şimdi blogumuzun makalelerinin saklanacağı veritabanı yapısını hazırlayacağız. Çok basit başlayacağız - yalnızca gönderiler için bir tablo oluşturacağız. -Veritabanını oluşturmak için [Adminer'i |https://www.adminer.org] indirebiliriz veya veritabanı yönetimi için başka bir araç kullanabilirsiniz. +Veritabanını oluşturmak için [Adminer |https://www.adminer.org]'ı veya veritabanlarını yönetmek için favori aracınızı indirebiliriz. Adminer'ı açalım ve `quickstart` adında yeni bir veritabanı oluşturalım. -`posts` adında yeni bir tablo oluşturun ve şu sütunları ekleyin: -- `id` int, autoincrement (AI) üzerine tıklayın +`posts` adında yeni bir tablo ve şu sütunlarla oluşturalım: +- `id` int, autoincrement (AI) işaretleyin - `title` varchar, uzunluk 255 -- `content` metin -- `created_at` zaman damgası +- `content` text +- `created_at` timestamp -Bu şekilde görünmelidir: +Sonuç yapısı şöyle görünmelidir: [* adminer-posts.webp *] @@ -49,9 +49,9 @@ CREATE TABLE `posts` ( ``` .[caution] -InnoDB** tablo depolama alanını kullanmak çok önemlidir. Nedenini daha sonra göreceksiniz. Şimdilik sadece bunu seçin ve gönderin. Şimdi Kaydet'e basabilirsiniz. +**InnoDB** depolama motorunu kullanmak gerçekten önemlidir. Birazdan nedenini göstereceğiz. Şimdilik, sadece seçin ve kaydet'e tıklayın. -Doğrudan uygulamamızdan yeni yazı ekleme özelliğini uygulamadan önce bazı örnek blog yazıları eklemeyi deneyin. +Uygulama aracılığıyla veritabanına makale ekleme seçeneği oluşturmadan önce, bloga manuel olarak birkaç örnek makale ekleyin. ```sql INSERT INTO `posts` (`id`, `title`, `content`, `created_at`) VALUES @@ -61,37 +61,34 @@ INSERT INTO `posts` (`id`, `title`, `content`, `created_at`) VALUES ``` -Veritabanına Bağlanma .[#toc-connecting-to-the-database] -======================================================== +Veritabanına Bağlanma +===================== -Şimdi, veritabanı oluşturulduğunda ve içinde bazı gönderilerimiz olduğunda, bunları yeni parlak sayfamızda görüntülemenin tam zamanıdır. +Veritabanı oluşturulduğuna ve içinde birkaç makale sakladığımıza göre, onları güzel yeni sayfamızda göstermenin tam zamanı. -İlk olarak, uygulamamıza hangi veritabanını kullanacağını söylememiz gerekir. Veritabanı bağlantı yapılandırması `config/local.neon` adresinde saklanır. DSN((Veri Kaynağı Adı)) bağlantısını ve kimlik bilgilerinizi ayarlayın. Bu şekilde görünmelidir: +Öncelikle uygulamaya hangi veritabanını kullanacağını söylemeliyiz. Veritabanı bağlantısını `config/common.neon` dosyasında DSN((Veri Kaynağı Adı)) ve giriş bilgileri kullanarak ayarlayacağız. Şöyle görünmelidir: -```neon .{file:config/local.neon} +```neon .{file:config/common.neon} database: dsn: 'mysql:host=127.0.0.1;dbname=quickstart' - user: *enter user name* - password: *enter password here* + user: *buraya kullanıcı adını girin* + password: *buraya veritabanı şifresini girin* ``` .[note] -Bu dosyayı düzenlerken girintilere dikkat edin. [NEON formatı |neon:format] hem boşlukları hem de sekmeleri kabul eder ancak ikisini birlikte kabul etmez! Web Projesindeki yapılandırma dosyası varsayılan olarak sekmeleri kullanır. +Bu dosyayı düzenlerken satır girintisine dikkat edin. [NEON |neon:format] formatı hem boşluklarla hem de sekmelerle girintiyi kabul eder, ancak ikisini aynı anda kabul etmez. Web Projesi'ndeki varsayılan yapılandırma dosyası sekmeleri kullanır. -Tüm yapılandırma `config/` adresinde `common.neon` ve `local.neon` dosyalarında saklanır. `common.neon` dosyası uygulamanın genel yapılandırmasını içerirken, `local.neon` dosyası yalnızca ortama özgü parametreleri (örneğin geliştirme ve üretim sunucusu arasındaki fark) içerir. +Veritabanı Bağlantısını Aktarma +=============================== -Veritabanı Bağlantısının Enjekte Edilmesi .[#toc-injecting-the-database-connection] -=================================================================================== +Makalelerin listelenmesinden sorumlu olacak `HomePresenter`, veritabanına bir bağlantıya ihtiyaç duyar. Bunu elde etmek için, şöyle görünecek bir yapıcı (constructor) kullanacağız: -Makaleleri listeleyecek olan `HomePresenter` sunucusunun bir veritabanı bağlantısına ihtiyacı vardır. Bunu almak için aşağıdaki gibi bir kurucu yazın: - -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; -use Nette\Application\UI\Form; final class HomePresenter extends Nette\Application\UI\Presenter { @@ -105,12 +102,12 @@ final class HomePresenter extends Nette\Application\UI\Presenter ``` -Veritabanından Gönderileri Yükleme .[#toc-loading-posts-from-the-database] -========================================================================== +Veritabanından Gönderileri Yükleme +================================== -Şimdi gönderileri veritabanından alalım ve HTML kodunu oluşturacak olan şablona aktaralım. İşte *render* metodu bunun içindir: +Şimdi gönderileri veritabanından yükleyeceğiz ve onları HTML kodu olarak oluşturacak olan şablona göndereceğiz. Bunun için *render* metodu olarak adlandırılan metot kullanılır: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} public function renderDefault(): void { $this->template->posts = $this->database @@ -120,41 +117,41 @@ public function renderDefault(): void } ``` -Sunucu artık `default` adlı bir görünüme veri aktaran bir `renderDefault()` render yöntemine sahiptir. Sunucu şablonları `app/Presenters/templates/{PresenterName}/{viewName}.latte` adresinde bulunabilir, bu nedenle bu durumda şablon `app/Presenters/templates/Home/default.latte` adresinde yer alacaktır. Şablonda, veritabanındaki gönderileri içeren `$posts` adlı bir değişken artık mevcuttur. +Presenter şimdi veritabanından verileri şablona (View) aktaran bir render metodu `renderDefault()` içerir. Şablonlar `app/Presentation/{PresenterName}/{viewName}.latte` içinde bulunur, bu yüzden bu durumda şablon `app/Presentation/Home/default.latte` içinde bulunur. Şablonda şimdi veritabanından alınan gönderileri içeren `$posts` değişkeni mevcut olacaktır. -Şablon .[#toc-template] -======================= +Şablon +====== -Tüm sayfa için genel bir şablon (üstbilgi, stil sayfaları, altbilgi, ... ile *düzen* olarak adlandırılır) ve ardından her görünüm için (örneğin blog yazılarının listesini görüntülemek için) düzen şablonunun bazı bölümlerini geçersiz kılabilen özel şablonlar vardır. +Tüm web sitesi için ana bir şablonumuz (adı *layout*, başlık, stiller, altbilgi,... içerir) ve her görünüm (View) için belirli şablonlarımız (örneğin, blogdaki gönderileri görüntülemek için) vardır, bunlar ana şablonun bazı bölümlerini geçersiz kılabilir. -Varsayılan olarak, düzen şablonu `templates/@layout.latte` adresinde bulunur: +Varsayılan olarak, layout şablonu `app/Presentation/@layout.latte` içinde bulunur ve şunları içerir: -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... {include content} ... ``` -`{include content}` ana şablona `content` adında bir blok ekler. Bunu her görünümün şablonlarında tanımlayabilirsiniz. Bu durumda `Home/default.latte` dosyasını şu şekilde düzenleyeceğiz: +`{include content}` ifadesi, ana şablona `content` adlı bloğu ekler. Bunu bireysel görünümlerin (View) şablonlarında tanımlayacağız. Bizim durumumuzda, `Home/default.latte` dosyasını aşağıdaki gibi düzenleyeceğiz: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - Hello World + Merhaba Dünya {/block} ``` -Düzene eklenecek olan *içerik* [bloğunu |latte:tags#block] tanımlar. Tarayıcıyı yenilediğinizde, "Merhaba dünya" metnini içeren bir sayfa göreceksiniz (kaynak kodda ayrıca `@layout.latte` adresinde tanımlanmış HTML üstbilgisi ve altbilgisi ile birlikte). +Bununla, ana layout'a eklenecek olan *content* [bloğunu |latte:tags#block] tanımladık. Tarayıcıyı tekrar yenilersek, "Merhaba Dünya" metnini içeren bir sayfa göreceğiz (kaynak kodunda `@layout.latte` içinde tanımlanan HTML başlığı ve altbilgisi ile birlikte). -Blog yazılarını görüntüleyelim - şablonu şu şekilde düzenleyeceğiz: +Blog gönderilerini görüntüleyelim - şablonu aşağıdaki gibi düzenleyeceğiz: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - <h1>My blog</h1> + <h1>Blogum</h1> {foreach $posts as $post} <div class="post"> - <div class="date">{$post->created_at|date:'j. n. Y'}</div> + <div class="date">{$post->created_at|date:'F j, Y'}</div> <h2>{$post->title}</h2> @@ -164,42 +161,41 @@ Blog yazılarını görüntüleyelim - şablonu şu şekilde düzenleyeceğiz: {/block} ``` -Tarayıcınızı yenilediğinizde blog yazılarınızın listesini göreceksiniz. Liste çok süslü veya renkli değildir, bu nedenle `www/css/style.css` adresine biraz [parlak CSS |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] eklemekten ve bir düzene bağlamaktan çekinmeyin: +Tarayıcıyı yenilersek, tüm gönderilerin bir listesini göreceğiz. Liste henüz pek güzel veya renkli değil, bu yüzden `www/css/style.css` dosyasına birkaç [CSS stili |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] ekleyebilir ve layout'ta bağlayabiliriz: -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <link rel="stylesheet" href="{$basePath}/css/style.css"> </head> ... ``` -`{foreach}` etiketi, `$posts` değişkeninde şablona aktarılan tüm gönderileri yineler ve her gönderi için bir HTML kodu parçası görüntüler. Tıpkı bir PHP kodunun yapacağı gibi. +`{foreach}` etiketi, `$posts` değişkeninde şablona aktardığımız tüm gönderiler üzerinde yinelenir ve her biri için verilen HTML parçasını oluşturur. Tam olarak PHP kodu gibi davranır. -`|date` şeyine filtre denir. Filtreler çıktıyı biçimlendirmek için kullanılır. Bu özel filtre bir tarihi (örneğin `2013-04-12`) daha okunabilir bir biçime (`12. 4. 2013`) dönüştürür. `|truncate` filtresi, dizeyi belirtilen maksimum uzunlukta keser ve dize kesilmişse sonuna bir üç nokta ekler. Bu bir önizleme olduğundan, makalenin tam içeriğini görüntülemenin bir anlamı yoktur. Diğer varsayılan filtreler [belgelerde bulunabilir |latte:filters] veya gerekirse kendiniz oluşturabilirsiniz. +`|date:` ifadesine filtre diyoruz. Filtreler çıktıyı biçimlendirmek için tasarlanmıştır. Bu özel filtre, tarihi (örneğin `2013-04-12`) daha okunabilir bir biçime (`April 12, 2013`) dönüştürür. `|truncate` filtresi, karakter dizisini belirtilen maksimum uzunluğa kırpar ve karakter dizisi kısaltılırsa sonuna üç nokta ekler. Bu bir önizleme olduğundan, makalenin tüm içeriğini göstermenin bir anlamı yoktur. Diğer varsayılan filtreleri [belgelerde bulabiliriz |latte:filters] veya gerektiğinde kendi filtrelerimizi oluşturabiliriz. -Bir şey daha var. Kodu biraz daha kısaltabilir ve böylece daha basit hale getirebiliriz. Latte etiketlerini* aşağıdaki gibi *n:attributes* ile değiştirebiliriz: +Bir şey daha var. Önceki kodu kısaltabilir ve basitleştirebiliriz. Bunu *Latte etiketlerini* *n:nitelikleri* ile değiştirerek başarırız: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - <h1>My blog</h1> + <h1>Blogum</h1> <div n:foreach="$posts as $post" class="post"> <div class="date">{$post->created_at|date:'F j, Y'}</div> <h2>{$post->title}</h2> - <div>{$post->content}</div> + <div>{$post->content|truncate:256}</div> </div> {/block} ``` -`n:foreach`, basitçe *div*'i bir *foreach* bloğu ile sarar (önceki kod bloğu ile tamamen aynı şeyi yapar). +`n:foreach` niteliği, *div* bloğunu *foreach* ile sarar (önceki kodla tamamen aynı şekilde çalışır). -Özet .[#toc-summary] -==================== +Özet +==== -İçinde bazı blog yazıları olan çok basit bir MySQL veritabanımız var. Uygulama veritabanına bağlanıyor ve yazıların basit bir listesini görüntülüyor. +Şimdi birkaç gönderi içeren çok basit bir MySQL veritabanımız var. Uygulama bu veritabanına bağlanır ve bu gönderilerin basit bir listesini şablonda görüntüler. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/tr/model.texy b/quickstart/tr/model.texy index a0b3ddf58f..19c1f20105 100644 --- a/quickstart/tr/model.texy +++ b/quickstart/tr/model.texy @@ -1,13 +1,13 @@ Model ***** -Uygulamamız büyüdükçe, kısa süre içinde benzer veritabanı işlemlerini çeşitli konumlarda ve çeşitli sunucularda gerçekleştirmemiz gerektiğini fark ederiz, örneğin yayınlanan en yeni makaleleri almak gibi. Devam eden bir çalışma durumunu belirtmek için makalelere bir bayrak ekleyerek uygulamamızı geliştirirsek, uygulamamızdaki tüm konumları gözden geçirmeli ve yalnızca bitmiş makalelerin seçildiğinden emin olmak için bir where cümlesi eklemeliyiz. +Uygulama büyüdükçe, yakında farklı yerlerde, farklı presenter'larda, veritabanıyla benzer işlemler yapmamız gerektiğini fark edeceğiz. Örneğin, en son yayınlanan makaleleri almak. Uygulamayı, örneğin makalelere taslak olup olmadıklarını belirten bir bayrak ekleyerek geliştirirsek, o zaman uygulamada makalelerin veritabanından alındığı tüm yerleri gözden geçirmeli ve yalnızca taslak olmayan makalelerin seçilmesi için where koşulunu eklemeliyiz. -Bu noktada, doğrudan veritabanı ile çalışmak yetersiz kalır ve yayınlanan makaleleri döndüren yeni bir fonksiyonla kendimize yardımcı olmak daha akıllıca olacaktır. Ve daha sonra başka bir cümle eklediğimizde (örneğin ileri tarihli makaleleri görüntülememek için), kodumuzu yalnızca bir yerde düzenleriz. +Bu noktada, veritabanıyla doğrudan çalışmak yetersiz hale gelir ve bize yayınlanmış makaleleri döndürecek yeni bir fonksiyonla yardım etmek daha akıllıca olacaktır. Ve daha sonra başka bir koşul eklediğimizde, örneğin gelecekteki bir tarihe sahip makalelerin gösterilmemesi gerektiğini, kodu yalnızca tek bir yerde düzenleriz. -Fonksiyonu `PostFacade` sınıfına yerleştireceğiz ve `getPublicArticles()` olarak adlandıracağız. +Fonksiyonu örneğin `PostFacade` sınıfına yerleştireceğiz ve `getPublicArticles()` olarak adlandıracağız. -Makalelerimizle ilgilenmek için `app/Model/` dizininde `PostFacade` model sınıfımızı oluşturacağız: +`app/Model/` dizininde, makalelerimizle ilgilenecek olan model sınıfımız `PostFacade`'i oluşturacağız: ```php .{file:app/Model/PostFacade.php} <?php @@ -32,13 +32,13 @@ final class PostFacade } ``` -Sınıfta veritabanı Explorer'ını geçiyoruz:[api:Nette\Database\Explorer]. Bu, [DI konteynerinin |dependency-injection:passing-dependencies] gücünden yararlanacaktır. +Sınıfta, yapıcı aracılığıyla veritabanı Explorer:[api:Nette\Database\Explorer]'ı aktarmasını isteyeceğiz. Böylece [DI konteynerinin|dependency-injection:passing-dependencies] gücünden yararlanacağız. -Düzenleyeceğimiz `HomePresenter` adresine geçeceğiz, böylece `Nette\Database\Explorer` bağımlılığından kurtulacağız ve bunun yerine yeni sınıfımıza yeni bir bağımlılık oluşturacağız. +`HomePresenter`'a geçeceğiz, onu `Nette\Database\Explorer` bağımlılığından kurtulacak ve yeni sınıfımıza olan yeni bağımlılıkla değiştirecek şekilde düzenleyeceğiz. -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use App\Model\PostFacade; use Nette; @@ -59,10 +59,9 @@ final class HomePresenter extends Nette\Application\UI\Presenter } ``` -Kullanım bölümünde `App\Model\PostFacade` kullanıyoruz, bu nedenle PHP kodunu `PostFacade` olarak kısaltabiliriz. Bu nesneyi kurucuda talep ediyoruz, `$facade` özelliğine yazıyoruz ve renderDefault yönteminde kullanıyoruz. +Use bölümünde `App\Model\PostFacade` var, bu yüzden PHP kodundaki yazımı `PostFacade` olarak kısaltabiliriz. Bu nesneyi yapıcıda isteyeceğiz, `$facade` özelliğine yazacağız ve renderDefault metodunda kullanacağız. -Geriye kalan son adım DI konteynerine bu nesneyi üretmeyi öğretmektir. Bu genellikle `services` bölümündeki `config/services.neon` dosyasına tam sınıf adını ve kurucu parametrelerini veren bir madde işareti eklenerek yapılır. -Bu, tabiri caizse onu kaydeder ve nesne daha sonra **service** olarak adlandırılır. [Otomatik |dependency-injection:autowiring] bağlantı adı verilen bazı sihirler sayesinde, genellikle kurucu parametrelerini belirtmemize gerek yoktur çünkü DI bunları tanıyacak ve otomatik olarak aktaracaktır. Bu nedenle, sadece sınıfın adını vermek yeterli olacaktır: +Geriye kalan son adım, DI konteynerine bu nesneyi nasıl üreteceğini öğretmektir. Bu genellikle `config/services.neon` dosyasındaki `services` bölümüne bir madde işareti ekleyerek, sınıfın tam adını ve yapıcı parametrelerini belirterek yapılır. Böylece onu kaydederiz ve nesne daha sonra **servis** olarak adlandırılır. [Autowiring |dependency-injection:autowiring] adlı sihir sayesinde, çoğu zaman yapıcı parametrelerini belirtmemiz gerekmez, çünkü DI onları kendi başına tanır ve aktarır. Bu nedenle yalnızca sınıfın adını belirtmek yeterli olacaktır: ```neon .{file:config/services.neon} ... @@ -71,16 +70,15 @@ services: - App\Model\PostFacade ``` -Ancak, bu satırı eklemenize de gerek yoktur. `services.neon` adresinin başındaki `search` bölümünde, `-Facade` veya `-Factory` ile biten tüm sınıfların DI tarafından otomatik olarak aranacağı tanımlanmıştır, bu durum `PostFacade` için de geçerlidir. +Ancak, bu satırı eklemeniz bile gerekmez. `services.neon` başındaki `search` bölümünde, `-Facade` veya `-Factory` ile biten tüm sınıfların DI tarafından kendi kendine bulunacağı tanımlanmıştır, bu da `PostFacade` durumu için geçerlidir. -Özet .[#toc-summary] -==================== +Özet +==== -`PostFacade` sınıfı bir kurucuda `Nette\Database\Explorer` sınıfını ister ve bu sınıf DI konteynerinde kayıtlı olduğu için konteyner bu örneği oluşturur ve geçirir. DI bu şekilde bizim için bir `PostFacade` örneği yaratır ve bir kurucu içinde bunu isteyen HomePresenter sınıfına geçirir. Bir çeşit Matruşka bebeği kodu :) Tüm bileşenler sadece ihtiyaç duydukları şeyi talep ederler ve nerede ve nasıl oluşturulduğuyla ilgilenmezler. Oluşturma DI container tarafından gerçekleştirilir. +`PostFacade` sınıfı, yapıcıda `Nette\Database\Explorer`'ın aktarılmasını ister ve bu sınıf DI konteynerinde kayıtlı olduğundan, konteyner bu örneği oluşturur ve aktarır. DI bizim için `PostFacade` örneğini bu şekilde oluşturur ve onu isteyen HomePresenter sınıfının yapıcısına aktarır. Böyle bir matruşka. :) Herkes sadece ne istediğini söyler ve neyin nerede ve nasıl yaratıldığıyla ilgilenmez. Oluşturma işini DI konteyneri halleder. .[note] -Burada [bağımlılık enjeksiyonu |dependency-injection:introduction] ve [yapılandırma |nette:configuring] hakkında daha fazla bilgi edinebilirsiniz. +Burada [dependency injection |dependency-injection:introduction] ve [yapılandırma |nette:configuring] hakkında daha fazla bilgi edinebilirsiniz. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/tr/single-post.texy b/quickstart/tr/single-post.texy index eab3d7a9d1..d7ac88c512 100644 --- a/quickstart/tr/single-post.texy +++ b/quickstart/tr/single-post.texy @@ -1,17 +1,17 @@ -Tek Gönderi Sayfası -******************* +Gönderi Sayfası +*************** .[perex] -Blogumuza, belirli bir blog gönderisinin içeriğini görüntüleyecek başka bir sayfa ekleyelim. +Şimdi belirli bir gönderiyi gösterecek başka bir blog sayfası oluşturacağız. -Belirli bir blog gönderisini getirip şablona aktaracak yeni bir render yöntemi oluşturmamız gerekiyor. Bu görünümün `HomePresenter` adresinde olması hoş değil çünkü bu bir blog yazısı ile ilgili, ana sayfa ile değil. Bu nedenle, yeni bir `PostPresenter` sınıfı oluşturalım ve `app/Presenters` adresine yerleştirelim. Bir veritabanı bağlantısına ihtiyaç duyacaktır, bu nedenle *veritabanı enjeksiyonu* kodunu tekrar oraya koyun. +Belirli bir makaleyi alacak ve onu şablona aktaracak yeni bir render metodu oluşturmalıyız. Bu metodu `HomePresenter`'da bulundurmak pek hoş değil, çünkü makaleden bahsediyoruz, ana sayfadan değil. Bu yüzden `app/Presentation/Post/` içinde `PostPresenter` oluşturalım. Bu presenter'ın da veritabanına bağlanması gerekiyor, bu yüzden burada yine veritabanı bağlantısı gerektirecek bir yapıcı yazacağız. -`PostPresenter` bu şekilde görünmelidir: +`PostPresenter` şöyle görünebilir: -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Post; use Nette; use Nette\Application\UI\Form; @@ -23,50 +23,50 @@ final class PostPresenter extends Nette\Application\UI\Presenter ) { } - public function renderShow(int $postId): void + public function renderShow(int $id): void { $this->template->post = $this->database ->table('posts') - ->get($postId); + ->get($id); } } ``` -Sunucumuz için doğru ad alanlarını `App\Presenters` ayarlamamız gerekir. Sunucu [eşlemesine |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7] bağlıdır. +[Presenter eşlemesi |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7] ayarlarına tabi olan doğru `App\Presentation\Post` ad alanını belirtmeyi unutmamalıyız. -`renderShow` yöntemi tek bir bağımsız değişken gerektirir - görüntülenecek gönderinin kimliği. Ardından, gönderiyi veritabanından yükler ve sonucu şablona aktarır. +`renderShow` metodu bir argüman gerektirir - gösterilecek olan belirli bir makalenin ID'si. Ardından bu makaleyi veritabanından yükler ve şablona aktarır. -`Home/default.latte` şablonunda `Post:show` eylemine bir bağlantı ekliyoruz: +`Home/default.latte` şablonuna `Post:show` eylemine bir bağlantı ekleyeceğiz. -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a href="{link Post:show $post->id}">{$post->title}</a></h2> ... ``` -`{link}` etiketi `Post:show` eylemine işaret eden bir URL adresi oluşturur. Bu etiket ayrıca gönderinin kimliğini de bir argüman olarak iletir. +`{link}` etiketi, `Post:show` eylemine yönlendiren bir URL adresi oluşturur. Ayrıca gönderi ID'sini argüman olarak aktarır. -Aynı şeyi n:attribute kullanarak da kısaca yazabiliriz: +Aynısını n:nitelik kullanarak kısaca yazabiliriz: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a n:href="Post:show $post->id">{$post->title}</a></h2> ... ``` -`n:href` özniteliği `{link}` etiketine benzer. +`n:href` niteliği, `{link}` etiketine benzer. -`Post:show` eylemi için şablon henüz mevcut değil. Bu yazıya bir bağlantı açabiliriz. [Tracy |tracy:], `Post/show.latte` 'un neden mevcut olmadığına dair bir hata gösterecektir. Başka bir hata raporu görürseniz, muhtemelen web sunucunuzda mod_rewrite özelliğini açmanız gerekir. +Ancak `Post:show` eylemi için henüz bir şablon yok. Bu gönderiye olan bağlantıyı açmayı deneyebiliriz. [Tracy |tracy:], `Post/show.latte` şablonu henüz mevcut olmadığı için bir hata gösterecektir. Farklı bir hata mesajı görüyorsanız, muhtemelen web sunucusunda `mod_rewrite`'ı etkinleştirmeniz gerekecektir. -Bu içerikle `Post/show.latte` adresini oluşturacağız: +Bu nedenle, şu içerikle `Post/show.latte` şablonunu oluşturacağız: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} {block content} -<p><a n:href="Home:default">← back to posts list</a></p> +<p><a n:href="Home:default">← gönderi listesine geri dön</a></p> <div class="date">{$post->created_at|date:'F j, Y'}</div> @@ -75,51 +75,50 @@ Bu içerikle `Post/show.latte` adresini oluşturacağız: <div class="post">{$post->content}</div> ``` -Şimdi tek tek parçalara bir göz atalım. +Şimdi şablonun bireysel kısımlarını gözden geçirelim. -İlk satır, daha önce gördüğümüz "content" adlı *adlandırılmış bir bloğun* tanımını başlatır. Bu bir *düzen şablonu* içinde görüntülenecektir. Gördüğünüz gibi, `{/block}` bitiş etiketi eksik. Bu isteğe bağlıdır. +İlk satır, ana sayfada olduğu gibi "content" adlı bloğun tanımını başlatır. Bu blok yine ana şablonda gösterilecektir. Gördüğünüz gibi, bitiş etiketi `{/block}` eksik. Bu isteğe bağlıdır. -İkinci satır, blog yazıları listesine bir geri bağlantı sağlar, böylece kullanıcı blogumuzda sorunsuz bir şekilde ileri geri gezinebilir. Yine `n:href` niteliğini kullanıyoruz, bu nedenle Nette URL'yi bizim için oluşturacaktır. Bağlantı, `Home` sunucusunun `default` eylemine işaret eder ( `default` eylemi atlanabileceği için `n:href="Home:"` de yazabilirsiniz). +Bir sonraki satırda, blog makalelerinin listesine geri dönen bir bağlantı bulunur, böylece kullanıcı makale listesi ile belirli bir makale arasında kolayca gezinebilir. `n:href` niteliğini kullandığımız için, Nette bağlantıların oluşturulmasını kendi başına halleder. Bağlantı, `Home` presenter'ının `default` eylemine yönlendirir (ayrıca `n:href="Home:"` yazabiliriz, çünkü `default` adlı eylem atlanabilir, otomatik olarak tamamlanır). -Üçüncü satır, zaten bildiğimiz gibi yayın zaman damgasını bir filtre ile biçimlendirir. +Üçüncü satır, zaten bildiğimiz bir filtre kullanarak tarih çıktısını biçimlendirir. -Dördüncü satır, blog yazısının *başlığını* bir `<h1>` Başlık. Aşina olmayabileceğiniz bir bölüm var ve o da `n:block="title"`. Ne işe yaradığını tahmin edebilir misiniz? Önceki bölümleri dikkatle okuduysanız, `n: attributes` adresinden bahsetmiştik. Bu da başka bir örnek. Şuna eşdeğerdir: +Dördüncü satır, blogun *başlığını* `<h1>` HTML etiketinde gösterir. Bu etiket, bilmeyebileceğiniz bir nitelik içerir (`n:block="title"`). Ne yaptığını tahmin edebilir misiniz? Önceki bölümü dikkatlice okuduysanız, bunun bir `n:attribute` olduğunu zaten biliyorsunuzdur. Bu, şuna eşdeğer olan başka bir örnektir: ```latte {block title}<h1>{$post->title}</h1>{/block} ``` -Basit bir ifadeyle, `title` adlı bir bloğu *yeniden tanımlar*. Bu blok *düzen şablonunda* (`/app/Presenters/templates/@layout.latte:11`) tanımlanmıştır ve OOP geçersiz kılmada olduğu gibi burada da geçersiz kılınır. Bu nedenle, sayfanın `<title>` görüntülenen gönderinin başlığını içerecektir. Sayfanın başlığını geçersiz kıldık ve tek ihtiyacımız olan `n:block="title"` idi. Harika, değil mi? +Basitçe söylemek gerekirse, bu blok `title` adlı bloğu yeniden tanımlar. Bu blok zaten ana *layout* şablonunda (`/app/Presentation/@layout.latte:11`) tanımlanmıştır ve OOP'deki metotları geçersiz kılmada olduğu gibi, bu blok ana şablonda tamamen aynı şekilde geçersiz kılınır. Böylece sayfanın `<title>`'ı şimdi görüntülenen gönderinin başlığını içerir ve bunun için yalnızca basit bir `n:block="title"` niteliği kullanmamız yeterliydi. Harika, değil mi? -Şablonun beşinci ve son satırında gönderinizin tüm içeriği görüntülenir. +Şablonun beşinci ve son satırı, belirli bir gönderinin tüm içeriğini gösterir. -Gönderi Kimliğini Kontrol Etme .[#toc-checking-post-id] -======================================================= +Gönderi ID'sini Kontrol Etme +============================ -Birisi URL'yi değiştirir ve mevcut olmayan `postId` adresini eklerse ne olur? Kullanıcıya güzel bir "sayfa bulunamadı" hatası vermeliyiz. `PostPresenter` adresindeki render yöntemini güncelleyelim: +Birisi URL'deki ID'yi değiştirir ve var olmayan bir `id` girerse ne olur? Kullanıcıya güzel bir "sayfa bulunamadı" türünde bir hata sunmalıyız. Bu nedenle `PostPresenter`'daki render metodunu biraz değiştireceğiz: -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { - $this->error('Post not found'); + $this->error('Sayfa bulunamadı'); } $this->template->post = $post; } ``` -Gönderi bulunamazsa, `$this->error(...)` adresini çağırmak güzel ve anlaşılır bir mesaj içeren bir 404 sayfası gösterecektir. Geliştirme ortamınızda (dizüstü bilgisayarınızda) hata sayfasını görmeyeceğinizi unutmayın. Bunun yerine, Tracy istisnayı tüm ayrıntılarıyla gösterecektir, bu da geliştirme için oldukça uygundur. Her iki modu da kontrol edebilirsiniz, sadece `Bootstrap.php` adresinde `setDebugMode` adresine aktarılan değeri değiştirin. +Gönderi bulunamazsa, `$this->error(...)` çağrısı anlaşılır bir mesajla 404 hata sayfasını gösterir. Geliştirme modunda (localhost) bu hata sayfasını görmeyeceğinize dikkat edin. Bunun yerine, Tracy istisnanın ayrıntılarıyla birlikte gösterilir, bu da geliştirme için oldukça avantajlıdır. Her iki modu da göstermek istiyorsak, `Bootstrap.php` dosyasındaki `setDebugMode` metodunun argümanını değiştirmemiz yeterlidir. -Özet .[#toc-summary] -==================== +Özet +==== -Blog gönderileri içeren bir veritabanımız ve iki görünüm içeren bir web uygulamamız var - birincisi tüm son gönderilerin özetini, ikincisi ise belirli bir gönderiyi gösteriyor. +Gönderileri olan bir veritabanımız ve iki görünümü olan bir web uygulamamız var - ilki tüm gönderilerin bir özetini gösterir ve ikincisi belirli bir gönderiyi gösterir. {{priority: -1}} -{{sitename: Nette Quickstart}} diff --git a/quickstart/uk/@home.texy b/quickstart/uk/@home.texy index 660e3764fc..e37470d832 100644 --- a/quickstart/uk/@home.texy +++ b/quickstart/uk/@home.texy @@ -1,113 +1,114 @@ -Створіть свій перший додаток! -***************************** +Пишемо першу програму! +********************** .[perex] -Познайомтеся з фреймворком Nette, створивши простий блог із коментарями. Давайте почнемо! +Давайте познайомимося з Nette Framework, створюючи простий блог з коментарями. Поїхали! -Після перших двох розділів у вас буде власний робочий блог, і ви будете готові публікувати свої чудові пости, хоча після завершення цих двох розділів функції будуть досить обмежені. Щоб зробити життя ваших користувачів приємнішим, ви також повинні прочитати наступні розділи і продовжувати вдосконалювати свій додаток. +Вже після перших двох розділів ми матимемо власний функціонуючий блог і зможемо публікувати свої чудові дописи, хоча функції поки що будуть значною мірою обмежені. Вам також варто прочитати наступні розділи, де ми запрограмуємо додавання коментарів, редагування статей і на завершення забезпечимо безпеку блогу. .[tip] -Цей посібник передбачає, що ви заповнили документ " [Інсталяція |nette:installation] " і успішно налаштували свої інструменти. +Цей посібник передбачає, що ви прочитали сторінку [Встановлення |nette:installation] та успішно підготували необхідні інструменти. Також передбачається, що ви розумієте [об'єктно-орієнтоване програмування в PHP |nette:introduction-to-object-oriented-programming]. -Будь ласка, використовуйте PHP 8.0 або новішу версію. Ви можете знайти повну версію програми [на GitHub |https://github.com/nette-examples/quickstart/tree/v4.0]. +Будь ласка, використовуйте PHP 8.1 або новішу версію. Повну програму можна знайти [на GitHub |https://github.com/nette-examples/quickstart/tree/v4.0]. -Вітальна сторінка .[#toc-the-welcome-page] -========================================== +Вітальна сторінка +================= -Почнемо зі створення нового проекту у каталозі `nette-blog`: +Почнемо зі створення нового проекту в каталозі `nette-blog`: ```shell composer create-project nette/web-project nette-blog ``` -У цей момент має бути запущена вітальна сторінка веб-проекту. Спробуйте відкрити її, відкривши браузер і перейшовши за наступною URL-адресою: +На даний момент початкова сторінка Web Project вже повинна працювати. Перевіримо це, відкривши браузер за наступною URL-адресою: ``` http://localhost/nette-blog/www/ ``` -і ви побачите сторінку вітання фреймворку: +і побачимо вітальну сторінку Nette Framework: [* qs-welcome.webp .{url: http://localhost/nette-blog/www/} *] -Додаток працює, і тепер можна почати вносити в нього зміни. +Програма працює, і ви можете почати вносити зміни. .[note] -Якщо у вас виникли проблеми, [спробуйте скористатися кількома порадами |nette:troubleshooting#Nette-Is-Not-Working-White-Page-Is-Displayed]. +Якщо виникла проблема, [спробуйте ці поради |nette:troubleshooting#Nette не працює відображається біла сторінка]. -Зміст веб-проекту .[#toc-web-project-s-content] -=============================================== +Вміст Web Project +================= -Наш проект має таку структуру: +Web Project має таку структуру: /--pre <b>nette-blog/</b> -├── <b>app/</b> ← каталог приложения -│ ├── <b>Presenters/</b> ← классы презентеров -│ │ └── <b>templates/</b>← шаблоны -│ ├── <b>Router/</b> ← конфигурация адресов URL -│ └── <b>Bootstrap.php</b> ← загрузочный класс Bootstrap -├── <b>bin/</b> ← скрипты для командной строки -├── <b>config/</b> ← конфигурационные файлы -├── <b>log/</b> ← журналы ошибок -├── <b>temp/</b> ← временные файлы, кэш, … -├── <b>vendor/</b> ← библиотеки, установленные через Composer -│ └── <b>autoload.php</b> ← автозагрузка библиотек, установленных Composer -└── <b>www/</b> ← общая папка — единственное место, доступное из браузера - └── <b>index.php</b> ← начальный файл, запускающий приложение +├── <b>app/</b> ← каталог з програмою +│ ├── <b>Core/</b> ← базові класи, необхідні для роботи +│ ├── <b>Presentation/</b> ← презентери, шаблони та інше +│ │ └── <b>Home/</b> ← каталог презентера Home +│ └── <b>Bootstrap.php</b> ← завантажувальний клас Bootstrap +├── <b>assets/</b> ← сирі ресурси (SCSS, TypeScript, вихідні зображення) +├── <b>bin/</b> ← скрипти, що запускаються з командного рядка +├── <b>config/</b> ← конфігураційні файли +├── <b>log/</b> ← логування помилок +├── <b>temp/</b> ← тимчасові файли, кеш, … +├── <b>vendor/</b> ← бібліотеки, встановлені Composer +│ └── <b>autoload.php</b> ← автозавантаження всіх встановлених пакетів +└── <b>www/</b> ← публічний каталог - єдиний доступний з браузера + ├── <b>assets/</b> ← скомпільовані статичні файли (CSS, JS, зображення, ...) + └── <b>index.php</b> ← початковий файл, яким запускається програма \-- -Каталог `www` призначений для зберігання зображень, JavaScript, CSS та інших загальнодоступних файлів. Це єдиний каталог, доступний безпосередньо з браузера, тому ви можете вказати тут кореневий каталог вашого веб-сервера (можна налаштувати його в Apache, але давайте зробимо це пізніше, оскільки зараз це не важливо). +Каталог `www/` призначений для зберігання зображень, файлів JavaScript, стилів CSS та інших загальнодоступних файлів. Тільки цей каталог доступний з Інтернету, тому налаштуйте кореневий каталог вашої програми так, щоб він вказував саме сюди (це можна налаштувати в конфігурації Apache або nginx, але зробимо це пізніше, зараз це не важливо). -Найбільш важливим каталогом для вас є `app/`. Там можна знайти файл `Bootstrap.php`, усередині якого міститься клас, що завантажує фреймворк і конфігурує додаток. Він активує [автозавантаження |robot-loader:] та встановлює [налагоджувач |tracy:] і [маршрути |application:routing]. +Найважливіша папка для нас — `app/`. Тут ми знайдемо файл `Bootstrap.php`, в якому знаходиться клас, що служить для завантаження всього фреймворку та налаштування програми. Тут активується [автозавантаження |robot-loader:], налаштовується [налагоджувач |tracy:] та [маршрути |application:routing]. -Очищення .[#toc-cleanup] -======================== +Прибирання +========== -Веб-проект містить сторінку привітання, яку ми можемо видалити - сміливо замініть вміст файлу `app/Presenters/templates/Home/default.latte` текстом `Hello world!`. +Web Project містить вітальну сторінку, яку ми видалимо перед тим, як почнемо щось програмувати. Без вагань замінимо вміст файлу `app/Presentation/Home/default.latte` на "Hello world!". [* qs-hello.webp .{url:-} *] -Tracy (відладчик) .[#toc-tracy-debugger] -======================================== +Tracy (налагоджувач) +==================== -Надзвичайно важливим інструментом для розробки є [відладчик під назвою Tracy |tracy:]. Спробуйте зробити кілька помилок у вашому файлі `app/Presenters/HomePresenter.php` (наприклад, видаліть фігурну дужку з визначення класу HomePresenter) і подивіться, що станеться. З'явиться сторінка з червоним екраном і зрозумілим описом помилки. +Надзвичайно важливий інструмент для розробки — це [інструмент налагодження Tracy |tracy:]. Спробуйте викликати якусь помилку у файлі `app/Presentation/Home/HomePresenter.php` (наприклад, видаливши фігурну дужку у визначенні класу HomePresenter) і подивіться, що станеться. Вискочить сторінка сповіщення, яка зрозуміло описує помилку. -[* qs-tracy.webp .{url:-}(debugger screen) *] +[* qs-tracy.avif .{url:-}(екран налагоджувача) *] -Tracy суттєво допоможе вам у пошуку помилок. Також зверніть увагу на плаваючу панель Tracy Bar у правому нижньому кутку, яка інформує вас про важливі дані під час виконання. +Tracy нам дуже допоможе, коли ми будемо шукати помилки в програмі. Також зверніть увагу на плаваючу панель Tracy Bar у правому нижньому куті екрана, яка містить інформацію про виконання програми. [* qs-tracybar.webp .{url:-} *] -У виробничому режимі Tracy, зрозуміло, вимкнена і не розкриває жодної конфіденційної інформації. Усі помилки зберігаються в директорії `log/`. Просто спробуйте. У файлі `app/Bootstrap.php` знайдіть наступний шматок коду, відкоментуйте рядок і змініть параметр виклику методу на `false`, щоб він мав такий вигляд: +У робочому режимі Tracy, звісно, вимкнена і не відображає жодної конфіденційної інформації. Усі помилки в цьому випадку зберігаються в папці `log/`. Давайте спробуємо це зробити. У файлі `app/Bootstrap.php` розкоментуємо наступний рядок і змінимо параметр виклику на `false`, щоб код виглядав так: ```php .{file:app/Bootstrap.php} ... -$configurator->setDebugMode(false); -$configurator->enableTracy(__DIR__ . '/../log'); +$this->configurator->setDebugMode(false); ... ``` -Після оновлення веб-сторінки сторінка з червоним екраном зміниться зручним для користувача повідомленням: +Після оновлення сторінки ми більше не побачимо Tracy. Замість неї відобразиться зручне для користувача повідомлення: -[* qs-fatal.webp .{url:-}(error screen) *] +[* qs-fatal.webp .{url:-}(екран помилки) *] -Тепер загляньте в каталог `log/`. Ви можете знайти там журнал помилок (у файлі exception.log), а також сторінку з повідомленням про помилку (збережену в HTML-файлі з ім'ям, що починається з `exception`). +Тепер поглянемо в каталог `log/`. Тут (у файлі `exception.log`) ми знайдемо залоговану помилку, а також вже знайому сторінку з повідомленням про помилку (збережену як HTML-файл з назвою, що починається на `exception-`). -Прокоментуйте рядок `// $configurator->setDebugMode(false);` ще раз. Tracy автоматично вмикає режим розробки в оточенні `localhost` і вимикає його в інших місцях. +Знову закоментуємо рядок `// $configurator->setDebugMode(false);`. Tracy автоматично вмикає режим розробника на localhost і вимикає його скрізь інде. -Тепер ми можемо виправити помилку і продовжити розробку нашого додатка. +Помилку, яку ми створили, можемо виправити і продовжити писати програму. -Надішліть подяку .[#toc-send-thanks] -==================================== +Надішліть подяку +================ -Ми покажемо вам трюк, який порадує авторів відкритих вихідних кодів. Ви можете легко присвоїти зірку на GitHub бібліотекам, які використовує ваш проект. Просто наберіть у консолі: +Ми покажемо вам трюк, яким ви порадуєте авторів open source. Простим способом ви поставите зірочку на GitHub бібліотекам, які використовує ваш проект. Достатньо запустити: ```shell composer thanks @@ -116,4 +117,3 @@ composer thanks Спробуйте! {{priority: -1}} -{{sitename: Быстрый старт с Nette}} diff --git a/quickstart/uk/@left-menu.texy b/quickstart/uk/@left-menu.texy index 72a87cebe7..2bf62f9d03 100644 --- a/quickstart/uk/@left-menu.texy +++ b/quickstart/uk/@left-menu.texy @@ -1,9 +1,9 @@ -Навчальний посібник -******************* -- [Створіть свій перший додаток! |@home] +Підручник +********* +- [Пишемо першу програму! |@home] - [Головна сторінка блогу |home-page] -- [Сторінка окремого запису |single-post] +- [Сторінка з дописом |single-post] - [Коментарі |comments] -- [Створення та редагування постів |creating-posts] -- [Модель |model] -- [Аутентифікація |authentication] +- [Створення та редагування дописів |creating-posts] +- [Модель |Model] +- [Автентифікація |authentication] diff --git a/quickstart/uk/@meta.texy b/quickstart/uk/@meta.texy new file mode 100644 index 0000000000..6120923e97 --- /dev/null +++ b/quickstart/uk/@meta.texy @@ -0,0 +1 @@ +{{sitename: Пишемо першу програму!}} diff --git a/quickstart/uk/authentication.texy b/quickstart/uk/authentication.texy index db2675d2d8..b4de1844bc 100644 --- a/quickstart/uk/authentication.texy +++ b/quickstart/uk/authentication.texy @@ -1,36 +1,36 @@ -Аутентифікація +Автентифікація ************** -Nette надає спосіб програмування аутентифікації на нашому сайті, але не змушує нас нічого робити. Реалізація залежить від нас. Nette включає інтерфейс `Nette\Security\Authenticator`, який вимагає тільки один метод `authenticate` для аутентифікації користувача, як ми хочемо. +Nette надає спосіб програмування автентифікації на наших сторінках, але ні до чого нас не змушує. Реалізація залежить тільки від нас. Nette містить інтерфейс `Nette\Security\Authenticator`, який вимагає лише одного методу `authenticate`, що перевіряє користувача будь-яким способом, який ми захочемо. -Існує безліч способів, за допомогою яких користувач може аутентифікувати себе. Найпоширенішим є *автентифікація на основі пароля* (користувач вказує своє ім'я або електронну пошту та пароль), але існують і інші способи. Можливо, ви бачили кнопки типу "Увійти за допомогою Facebook/Google/Twitter/GitHub тощо" на багатьох сайтах. З Nette ми можемо використовувати будь-який метод входу в систему, а також комбінувати їх. Це залежить від нас. +Існує багато способів перевірки користувача. Найпоширеніший спосіб - за допомогою пароля (користувач надає своє ім'я або електронну пошту та пароль), але є й інші способи. Можливо, ви знаєте кнопки типу "Увійти через Facebook" або вхід через Google/Twitter/GitHub на деяких сайтах. З Nette ми можемо мати будь-який метод входу, або навіть комбінувати їх. Це залежить тільки від нас. -Зазвичай ми пишемо власний автентифікатор, але для цього простого блогу ми скористаємося вбудованим автентифікатором, який здійснює вхід на основі пароля та імені користувача, що зберігаються в конфігураційному файлі. Це добре підходить для тестування. Додайте секцію *security* до конфігураційного файлу `config/common.neon`: +Зазвичай ми б написали власний автентифікатор, але для цього простого маленького блогу ми використаємо вбудований автентифікатор, який здійснює вхід на основі пароля та імені користувача, збережених у конфігураційному файлі. Він підходить для тестових цілей. Додамо наступну секцію *security* до конфігураційного файлу `config/common.neon`: ```neon .{file:config/common.neon} security: users: - admin: secret # логін 'admin', пароль 'secret' + admin: secret # користувач 'admin', пароль 'secret' ``` -Nette автоматично створить службу в контейнері DI. +Nette автоматично створить сервіс у DI-контейнері. -Форма реєстрації .[#toc-sign-in-form] -===================================== +Форма входу +=========== -Тепер у нас є готова автентифікація, і нам потрібно підготувати користувацький інтерфейс для входу в систему. Тому давайте створимо новий презентер під назвою *SignPresenter*, який буде: +Тепер у нас готова автентифікація, і нам потрібно підготувати користувацький інтерфейс для входу. Створимо новий presenter з назвою *SignPresenter*, який: -- відображати форму входу в систему (запитується ім'я користувача та пароль) -- аутентифікувати користувача під час надсилання форми -- забезпечувати вихід із системи +- відобразить форму входу (з логіном та паролем) +- після відправки форми перевірить користувача +- надасть можливість виходу -Давайте почнемо з форми входу в систему. Ви вже знаєте, як працюють форми в презентері. Створіть `SignPresenter` і метод `createComponentSignInForm`. Це має виглядати таким чином: +Почнемо з форми входу. Ми вже знаємо, як працюють форми в presenter'ах. Створимо presenter `SignPresenter` і напишемо метод `createComponentSignInForm`. Він повинен виглядати приблизно так: -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Sign; use Nette; use Nette\Application\UI\Form; @@ -40,69 +40,69 @@ final class SignPresenter extends Nette\Application\UI\Presenter protected function createComponentSignInForm(): Form { $form = new Form; - $form->addText('username', 'Имя пользователя:') - ->setRequired('Пожалуйста, введите ваше имя.'); + $form->addText('username', 'Ім\'я користувача:') + ->setRequired('Будь ласка, введіть своє ім\'я користувача.'); $form->addPassword('password', 'Пароль:') - ->setRequired('Пожалуйста, введите ваш пароль.'); + ->setRequired('Будь ласка, введіть свій пароль.'); - $form->addSubmit('send', 'Войти'); + $form->addSubmit('send', 'Увійти'); - $form->onSuccess[] = [$this, 'signInFormSucceeded']; + $form->onSuccess[] = $this->signInFormSucceeded(...); return $form; } } ``` -Тепер у нас є введення імені користувача та пароля. +Тут є поля для імені користувача та пароля. -Шаблон .[#toc-template] ------------------------ +Шаблон +------ -Форма відображатиметься в шаблоні `app/Presenters/templates/Sign/in.latte`. +Форма буде рендеритися в шаблоні `in.latte`: -```latte .{file:app/Presenters/templates/Sign/in.latte} +```latte .{file:app/Presentation/Sign/in.latte} {block content} -<h1 n:block=title>Войти</h1> +<h1 n:block=title>Вхід</h1> {control signInForm} ``` -Обробник входу в систему .[#toc-login-handler] ----------------------------------------------- +Callback для входу +------------------ -Далі ми додамо *обробник форми* для входу користувача, який буде викликано відразу після успішного відправлення форми. +Далі доповнимо callback для входу користувача, який буде викликаний одразу після успішної відправки форми. -Обробник просто приймає ім'я користувача та пароль, які користувач ввів, і передає їх аутентифікатору. Після входу в систему ми перенаправимо вас на головну сторінку: +Callback просто візьме ім'я користувача та пароль, які користувач заповнив, і передасть їх автентифікатору. Після входу перенаправимо на головну сторінку. -```php .{file:app/Presenters/SignPresenter.php} -public function signInFormSucceeded(Form $form, \stdClass $data): void +```php .{file:app/Presentation/Sign/SignPresenter.php} +private function signInFormSucceeded(Form $form, \stdClass $data): void { try { $this->getUser()->login($data->username, $data->password); $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { - $form->addError('Неправильные логин или пароль.'); + $form->addError('Неправильне ім\'я користувача або пароль.'); } } ``` -Метод [User::login() |api:Nette\Security\User::login()] має викидати виняток, якщо ім'я користувача або пароль не відповідають тим, які ми визначили раніше. Як ми вже знаємо, це призведе до появи червоної сторінки помилки [Tracy |tracy:] або, у виробничому режимі, повідомлення про внутрішню помилку сервера. Але ми цього не хочемо. Тому ми перехоплюємо виняток і додаємо у форму гарне і доброзичливе повідомлення про помилку. +Метод [User::login() |api:Nette\Security\User::login()] викине виняток, якщо ім'я користувача та пароль не відповідають даним у конфігураційному файлі. Як ми вже знаємо, це може призвести до червоної сторінки помилки або, в робочому режимі, до повідомлення про помилку сервера. Цього ми не хочемо. Тому перехопимо цей виняток і передамо гарне, зрозуміле користувачеві повідомлення про помилку до форми. -Коли у формі станеться помилка, сторінка з формою буде відображена знову, а над формою з'явиться гарне повідомлення, що інформує користувача про те, що він ввів неправильне ім'я користувача або пароль. +Як тільки у формі виникне помилка, сторінка з формою перемалюється, і над формою з'явиться гарне повідомлення, що інформує користувача про те, що він ввів неправильне ім'я користувача або пароль. -Безпека презентерів .[#toc-security-of-presenters] -================================================== +Захист presenter'ів +=================== -Убезпечимо форму для додавання та редагування постів. Мета - запобігти доступу до сторінки неавторизованих користувачів. +Захистимо форму для додавання та редагування дописів. Вона визначена в presenter'і `EditPresenter`. Мета - заборонити доступ до сторінки користувачам, які не увійшли в систему. -Створимо метод `startup()`, який запускається відразу на початку [життєвого циклу презентера |application:presenters#Life-Cycle-of-Presenter]. Це перенаправляє незареєстрованих користувачів на форму входу в систему. +Створимо метод `startup()`, який запускається одразу на початку [життєвого циклу presenter'а |application:presenters#Життєвий цикл презентера]. Він перенаправить неавторизованих користувачів на форму входу. -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} public function startup(): void { parent::startup(); @@ -114,67 +114,66 @@ public function startup(): void ``` -Приховуємо посилання .[#toc-hide-links] ---------------------------------------- +Приховування посилань +--------------------- -Неавторизований користувач більше не може бачити сторінки створення і редагування, але він все ще може бачити посилання, що вказують на них. Давайте сховаємо і їх. Одне з таких посилань міститься в `app/Presenters/templates/Home/default.latte`, і його має бути видно, тільки якщо користувач увійшов у систему. +Неавторизований користувач більше не може бачити сторінки *create* або *edit*, але все ще може бачити посилання на них. Їх також слід приховати. Одне таке посилання знаходиться в шаблоні `app/Presentation/Home/default.latte` і його повинні бачити лише авторизовані користувачі. -Ми можемо приховати його за допомогою *n:атрибута* під назвою `n:if`. Якщо твердження всередині нього `false`, то весь тег `<a>` і його вміст не відображатимуться: +Ми можемо приховати його за допомогою *n:атрибуту* під назвою `n:if`. Якщо ця умова `false`, весь тег `<a>`, включаючи вміст, залишиться прихованим. ```latte -<a n:href="Edit:create" n:if="$user->isLoggedIn()">Создать пост</a> +<a n:href="Edit:create" n:if="$user->isLoggedIn()">Створити допис</a> ``` -це скорочення для (не плутайте з `tag-if`): +що є скороченням наступного запису (не плутати з `tag-if`): ```latte -{if $user->isLoggedIn()}<a n:href="Edit:create">Создать пост</a>{/if} +{if $user->isLoggedIn()}<a n:href="Edit:create">Створити допис</a>{/if} ``` -Аналогічним чином слід приховати посилання редагування, розташоване в `app/Presenters/templates/Post/show.latte`. +Таким же чином приховаємо також посилання в шаблоні `app/Presentation/Post/show.latte`. -Посилання на форму входу .[#toc-login-form-link] -================================================ +Посилання на вхід +================= -Гей, але як нам потрапити на сторінку входу в систему? Немає посилання, що вказує на неї. Давайте додамо його у файл шаблону `app/Presenters/templates/@layout.latte`. Спробуйте знайти гарне місце, це може бути будь-яке місце, яке вам найбільше подобається. +Як взагалі потрапити на сторінку входу? Немає жодного посилання, яке б вело на неї. Тож додамо його до шаблону `@layout.latte`. Спробуйте знайти відповідне місце - це може бути майже будь-де. -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <ul class="navig"> - <li><a n:href="Home:">Главная</a></li> + <li><a n:href="Home:">Статті</a></li> {if $user->isLoggedIn()} - <li><a n:href="Sign:out">Выйти</a></li> + <li><a n:href="Sign:out">Вийти</a></li> {else} - <li><a n:href="Sign:in">Войти</a></li> + <li><a n:href="Sign:in">Увійти</a></li> {/if} </ul> ... ``` -Якщо користувач ще не ввійшов у систему, він побачить посилання "Увійти". В іншому випадку буде видно посилання "Вийти". Додамо цю дію в `SignPresenter`. +Якщо користувач не увійшов, відобразиться посилання "Увійти". В іншому випадку відобразиться посилання "Вийти". Цю дію також додамо до `SignPresenter`. -Дія виходу з системи виглядає наступним чином, і оскільки ми перенаправляємо користувача негайно, немає необхідності в шаблоні подання: +Оскільки ми негайно перенаправляємо користувача після виходу, шаблон не потрібен. Вихід виглядає так: -```php .{file:app/Presenters/SignPresenter.php} +```php .{file:app/Presentation/Sign/SignPresenter.php} public function actionOut(): void { $this->getUser()->logout(); - $this->flashMessage('Вы вышли.'); + $this->flashMessage('Вихід успішно виконано.'); $this->redirect('Home:'); } ``` -Він просто викликає метод `logout()` і потім показує користувачеві гарне повідомлення. +Просто викликається метод `logout()`, а потім відображається гарне повідомлення, що підтверджує успішний вихід. -Підіб'ємо підсумок .[#toc-summary] -================================== +Підсумок +======== -У нас є посилання для входу в систему, а також для виходу користувача з системи. Для автентифікації ми використовували вбудований автентифікатор, а дані для входу містяться в конфігураційному файлі, оскільки це простий тестовий застосунок. Ми також захистили форми редагування, щоб тільки ті користувачі, які увійшли в систему, могли додавати та редагувати повідомлення. +У нас є посилання для входу та виходу користувача. Для перевірки ми використали вбудований автентифікатор, а дані для входу зберігаються в конфігураційному файлі, оскільки це простий тестовий додаток. Також ми захистили форми редагування, тому додавати та редагувати дописи можуть лише авторизовані користувачі. .[note] -Тут ви можете прочитати більше про [вхід користувача |security:authentication] та [авторизація |security:authorization]. +Тут ви можете прочитати більше про [вхід користувачів |security:authentication] та [авторизацію |security:authorization]. {{priority: -1}} -{{sitename: Быстрый старт с Nette}} diff --git a/quickstart/uk/comments.texy b/quickstart/uk/comments.texy index a02d7eef64..f8fbf1d829 100644 --- a/quickstart/uk/comments.texy +++ b/quickstart/uk/comments.texy @@ -1,28 +1,28 @@ Коментарі ********* -Блог був розгорнутий, ми написали кілька дуже хороших постів для блогу і опублікували їх через Adminer. Люди читають блог, і вони дуже захоплені нашими ідеями. Щодня ми отримуємо безліч листів із похвалами. Але до чого всі ці похвали, якщо ми отримуємо їх тільки електронною поштою, і ніхто більше не може їх прочитати? Хіба не краще було б, якби люди могли залишати коментарі прямо в блозі, щоб усі інші могли прочитати, які ми чудові? +Ми завантажили блог на веб-сервер і опублікували кілька дуже цікавих дописів за допомогою Adminer. Люди читають наш блог і дуже ним захоплені. Ми щодня отримуємо багато листів із похвалами. Але яка користь від усієї цієї похвали, якщо вона є лише в електронній пошті й ніхто не може її прочитати? Було б краще, якби читач міг коментувати статтю безпосередньо, щоб кожен міг прочитати, які ми чудові. -Давайте зробимо всі статті коментованими. +Тож давайте запрограмуємо коментарі. -Створення нової таблиці .[#toc-creating-a-new-table] -==================================================== +Створення нової таблиці +======================= -Знову запустіть Adminer і створіть нову таблицю з іменем `comments` з цими стовпцями: +Запустимо Adminer і створимо таблицю `comments` з такими стовпцями: -- `id` int, позначте автоінкремент (AI) -- `post_id`, зовнішній ключ, що посилається на таблицю `posts`. -- `name` varchar, довжина 255 -- `email` varchar, довжина 255 +- `id` int, позначимо autoincrement (AI) +- `post_id`, зовнішній ключ, який посилається на таблицю `posts` +- `name` varchar, length 255 +- `email` varchar, length 255 - `content` text - `created_at` timestamp -Це має виглядати наступним чином: +Отже, таблиця має виглядати приблизно так: [* adminer-comments.webp *] -Не забудьте використовувати сховище таблиць InnoDB і натисніть кнопку Зберегти. +Не забудьте знову використовувати сховище InnoDB. ```sql CREATE TABLE `comments` ( @@ -37,83 +37,83 @@ CREATE TABLE `comments` ( ``` -Форма для коментування .[#toc-form-for-commenting] -================================================== +Форма для коментування +====================== -По-перше, нам потрібно створити форму, яка дозволить користувачам коментувати нашу сторінку. Фреймворк Nette має чудову підтримку форм. Вони можуть бути налаштовані в презентері та відображені в шаблоні. +Спочатку нам потрібно створити форму, яка дозволить користувачам коментувати дописи. Nette Framework має чудову підтримку форм. Ми можемо налаштувати їх у presenter'і та відобразити в шаблоні. -У Nette є поняття *компоненти*. **Компонент** - це багаторазово використовуваний клас або фрагмент коду, який може бути приєднаний до іншого компонента. Навіть презентер є компонентом. Кожен компонент створюється за допомогою фабрики компонентів. Отже, давайте визначимо фабрику форми коментарів у `PostPresenter`. +Nette Framework використовує концепцію *компонентів*. **Компонент** — це клас або частина коду, що повторно використовується і може бути приєднаний до іншого компонента. Навіть presenter є компонентом. Кожен компонент створюється за допомогою фабрики. Отже, створимо фабрику для форми коментарів у presenter'і `PostPresenter`. -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} protected function createComponentCommentForm(): Form { $form = new Form; // означає Nette\Application\UI\Form - $form->addText('name', 'Ваше ім'я:') + $form->addText('name', 'Ім\'я:') ->setRequired(); - $form->addEmail('email', 'Імейл:'); + $form->addEmail('email', 'E-mail:'); $form->addTextArea('content', 'Коментар:') ->setRequired(); - $form->addSubmit('send', 'Відправити'); + $form->addSubmit('send', 'Опублікувати коментар'); return $form; } ``` -Давайте трохи пояснимо це. Перший рядок створює новий екземпляр компонента `Form`. Методи, які вказані нижче, розміщують HTML-елементи *input* всередині форми. `->addText` буде відображатися як `<input type=text name=name>`, с `<label>Ваше имя:</label>`. Як ви вже здогадалися, `->addTextArea` прикріплює `<textarea>`, а `->addSubmit` додає `<input type=submit>`. Подібних методів більше, але це все, що вам потрібно знати просто зараз. Ви можете [дізнатися більше в документації |forms:]. +Давайте знову трохи пояснимо. Перший рядок створює новий екземпляр компонента `Form`. Наступні методи додають HTML-інпути до визначення цієї форми. `->addText` буде відображено як `<input type="text" name="name">` з `<label>Ім'я:</label>`. Як ви, мабуть, уже здогадалися, `->addTextArea` буде відображено як `<textarea>`, а `->addSubmit` як `<input type="submit">`. Існує набагато більше подібних методів, але цих поки що достатньо для цієї форми. Більше ви можете [прочитати в документації|forms:]. -Після того як компонент форми визначено в презентері, ми можемо відобразити його в шаблоні. Для цього помістіть тег `{control}` в кінець шаблону деталізації поста в `app/Presenters/templates/Post/show.latte`. Оскільки ім'я компонента - `commentForm` (воно походить від назви методу `createComponentCommentForm`), тег матиме такий вигляд: +Якщо форма вже визначена в presenter'і, ми можемо її відобразити (показати) в шаблоні. Це робиться шляхом розміщення тегу `{control}` у кінці шаблону, який відображає один конкретний допис, у `Post/show.latte`. Оскільки компонент називається `commentForm` (назва походить від назви методу `createComponentCommentForm`), тег виглядатиме так: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... -<h2>Оставить комментарий</h2> +<h2>Додайте новий коментар</h2> {control commentForm} ``` -Тепер, якщо ви перейдете на окрему сторінку якогось поста, там буде нова форма для розміщення коментарів. +Тепер, якщо ви переглянете сторінку з деталями допису, в кінці ви побачите нову форму для коментарів. -Збереження в базі даних .[#toc-saving-to-database] -================================================== +Збереження в базу даних +======================= -Чи намагалися ви надіслати будь-які дані? Ви могли помітити, що форма не виконує жодних дій. Вона просто є, виглядає круто і нічого не робить. Ми повинні прикріпити до неї метод зворотного виклику, який буде зберігати передані дані. +Ви вже пробували заповнити та надіслати форму? Мабуть, ви помітили, що форма насправді нічого не робить. Нам потрібно приєднати метод-callback, який збереже надіслані дані. -Помістіть наступний рядок перед рядком `return` у фабриці компонентів для `commentForm`: +На рядок перед `return` у фабриці для компонента `commentForm` розмістимо наступний рядок: ```php -$form->onSuccess[] = [$this, 'commentFormSucceeded']; +$form->onSuccess[] = $this->commentFormSucceeded(...); ``` -Це означає "після успішного надсилання форми викликати метод `commentFormSucceeded` поточного презентера". Цього методу ще не існує, тому давайте створимо його. +Попередній запис означає "після успішного надсилання форми викликати метод `commentFormSucceeded` з поточного presenter'а". Однак цей метод ще не існує. Давайте його створимо: -```php .{file:app/Presenters/PostPresenter.php} -public function commentFormSucceeded(\stdClass $data): void +```php .{file:app/Presentation/Post/PostPresenter.php} +private function commentFormSucceeded(\stdClass $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); $this->database->table('comments')->insert([ - 'post_id' => $postId, + 'post_id' => $id, 'name' => $data->name, 'email' => $data->email, 'content' => $data->content, ]); - $this->flashMessage('Спасибо за комментарий!', 'success'); + $this->flashMessage('Дякую за коментар', 'success'); $this->redirect('this'); } ``` -Ви повинні розмістити його відразу після фабрики компонента `commentForm`. +Цей метод розмістимо одразу після фабрики форми `commentForm`. -Новий метод має один аргумент, яким є екземпляр форми, що відправляється, створений фабрикою компонентів. Ми отримуємо передані значення в `$data`. Потім ми вставляємо дані в таблицю бази даних `comments`. +Цей новий метод має один аргумент, який є екземпляром надісланої форми - створеної фабрикою. Надіслані значення ми отримуємо в `$data`. А потім зберігаємо дані в таблицю бази даних `comments`. -Необхідно пояснити ще два виклики методу. `$this->redirect('this')` буквально перенаправляє на поточну сторінку. Ви повинні робити це кожного разу, коли форма відправлена, дійсна, і операція зворотного виклику зробила те, що повинна була зробити. Крім того, коли ви перенаправляєте сторінку після надсилання форми, ви не побачите добре відомого повідомлення `Вы хотите отправить данные сообщения снова?`, яке іноді можна побачити в браузері. (Загалом, після надсилання форми методом `POST`, ви завжди повинні перенаправляти користувача на дію `GET`). +Є ще два методи, які заслуговують на пояснення. Метод `redirect` буквально перенаправляє назад на поточну сторінку. Це доцільно робити після кожного надсилання форми, якщо вона містила валідні дані, і callback виконав операцію так, як мав. Також, якщо ми перенаправляємо сторінку після надсилання форми, ми не побачимо добре відоме повідомлення `Хочете надіслати дані з форми знову?`, яке іноді можна побачити в браузері. (Загалом, після надсилання форми методом `POST` завжди має слідувати перенаправлення на `GET` дію.) -`$this->flashMessage` призначений для інформування користувача про результат деякої операції. Оскільки ми перенаправляємо, повідомлення не може бути безпосередньо передано в шаблон і відображено. Тому існує метод, який збереже його і зробить доступним при наступному завантаженні сторінки. Флеш-повідомлення відображаються в стандартному файлі `app/Presenters/templates/@layout.latte`, і виглядають вони так: +Метод `flashMessage` призначений для інформування користувача про результат якоїсь операції. Оскільки ми перенаправляємо, повідомлення не може бути просто передане шаблону та відображене. Тому існує цей метод, який зберігає це повідомлення та робить його доступним при наступному завантаженні сторінки. Flash-повідомлення відображаються в головному шаблоні `app/Presentation/@layout.latte` і виглядають так: ```latte <div n:foreach="$flashes as $flash" n:class="flash, $flash->type"> @@ -121,20 +121,20 @@ public function commentFormSucceeded(\stdClass $data): void </div> ``` -Як ми вже знаємо, вони автоматично передаються в шаблон, тому вам не потрібно багато думати про це, це просто працює. Для отримання більш детальної інформації [ознайомтеся з документацією |application:presenters#Flash-Messages]. +Як ми вже знаємо, flash-повідомлення автоматично передаються в шаблон, тому нам не потрібно багато про це думати, це просто працює. Для отримання додаткової інформації [відвідайте документацію |application:presenters#Flash-повідомлення]. -Відображення коментарів .[#toc-rendering-the-comments] -====================================================== +Відображення коментарів +======================= -Це одна з тих речей, які вам просто сподобаються. Nette Database має класну функцію під назвою [Explorer |database:explorer]. Чи пам'ятаєте ви, що ми створили таблиці як InnoDB? Adminer створив так звані [зовнішні ключі |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html], які заощадять нам тонну роботи. +Це одна з тих речей, які ви просто полюбите. Nette Database має чудову функцію під назвою [Explorer |database:explorer]. Пам'ятаєте ще, що таблиці в базі даних ми спеціально створювали за допомогою сховища InnoDB? Adminer таким чином створив щось, що називається [зовнішніми ключами |https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html], які заощадять нам багато роботи. -Nette Database Explorer використовує зовнішні ключі для розв'язання відносин між таблицями, а знаючи ці відносини, він може автоматично створювати запити для вас. +Nette Database Explorer використовує зовнішні ключі для вирішення взаємного зв'язку між таблицями і на основі знання цих зв'язків вміє автоматично створювати запити до бази даних. -Як ви пам'ятаєте, ми передали змінну `$post` шаблону в `PostPresenter::renderShow()` і тепер хочемо перебрати всі коментарі, у яких стовпець `post_id` дорівнює нашому `$post->id`. Ви можете зробити це, викликавши `$post->related('comments')`. Це так просто. Подивіться на отриманий код. +Як ви, напевно, пам'ятаєте, ми передали в шаблон змінну `$post` за допомогою методу `PostPresenter::renderShow()`, і тепер ми хочемо ітерувати по всіх коментарях, які мають значення стовпця `post_id`, що збігається з `$post->id`. Цього можна досягти, викликавши `$post->related('comments')`. Так, ось так просто. Погляньмо на кінцевий код: -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { ... $this->template->post = $post; @@ -142,17 +142,17 @@ public function renderShow(int $postId): void } ``` -У шаблоні: +І шаблон: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} ... -<h2>Комментарии</h2> +<h2>Коментарі</h2> <div class="comments"> {foreach $comments as $comment} <p><b><a href="mailto:{$comment->email}" n:tag-if="$comment->email"> {$comment->name} - </a></b>:</p> + </a></b> написав:</p> <div>{$comment->content}</div> {/foreach} @@ -160,13 +160,12 @@ public function renderShow(int $postId): void ... ``` -Зверніть увагу на спеціальний атрибут `n:tag-if`. Ви вже знаєте, як `n: атрибуты` працюють. Якщо ви додасте до атрибута `tag-`, він обводитиме тільки теги, а не їхній вміст. Це дає змогу перетворити ім'я коментатора на посилання, якщо він вказав свою електронну пошту. Ці два рядки ідентичні за результатами: +Зверніть увагу на спеціальний атрибут `n:tag-if`. Ви вже знаєте, як працюють `n:атрибути`. Якщо до атрибута додати префікс `tag-`, функціональність застосовується лише до HTML-тегу, а не до його вмісту. Це дозволяє нам зробити ім'я коментатора посиланням лише в тому випадку, якщо він надав свою електронну пошту. Ці два рядки ідентичні: ```latte -<strong n:tag-if="$important"> Здравствуйте! </strong> +<strong n:tag-if="$important"> Добрий день! </strong> -{if $important}<strong>{/if} Здравствуйте! {if $important}</strong>{/if} +{if $important}<strong>{/if} Добрий день! {if $important}</strong>{/if} ``` {{priority: -1}} -{{sitename: Быстрый старт с Nette}} diff --git a/quickstart/uk/creating-posts.texy b/quickstart/uk/creating-posts.texy index 365cff0a6e..76c2514bd2 100644 --- a/quickstart/uk/creating-posts.texy +++ b/quickstart/uk/creating-posts.texy @@ -1,30 +1,30 @@ -Створення та редагування постів -******************************* +Створення та редагування дописів +******************************** -Який чудовий час. У нас з'явився новий суперкрутий блог, люди сперечаються в коментарях, і у нас нарешті з'явився час для програмування. Хоча нам подобається Adminer, писати в ньому статті для блогу не дуже зручно. Можливо, зараз саме час додати просту форму для додавання нових постів прямо з нашого додатка. Давайте зробимо це. +Це чудово! У нас є супер крутий новий блог, люди активно обговорюють у коментарях, і у нас нарешті є трохи часу на подальше програмування. Хоча Adminer — чудовий інструмент, він не зовсім ідеальний для написання нових дописів у блозі. Мабуть, настав час створити просту форму для додавання нових дописів безпосередньо з застосунку. Давайте зробимо це. -Почнемо з розробки користувацького інтерфейсу: +Почнемо з проектування користувацького інтерфейсу: -1. На головній сторінці додамо посилання "Написати новий пост". -2) Воно відобразить форму із заголовком і текстовою областю для вмісту. -3. Коли ви натиснете кнопку "Зберегти", вона збереже запис у блозі. +1. На головній сторінці додамо посилання "Написати новий допис". +2. Це посилання відобразить форму із заголовком та текстовим полем для вмісту допису. +3. Коли ми натиснемо кнопку Зберегти, допис збережеться в базу даних. -Пізніше ми також додамо аутентифікацію і дозволимо додавати нові повідомлення тільки користувачам, які увійшли в систему. Який код нам потрібно написати, щоб він працював? +Пізніше ми також додамо вхід і дозволимо додавати дописи лише залогіненим користувачам. Але це пізніше. Який код нам потрібно написати зараз, щоб усе працювало? -1. Створіть новий презентер із формою для додавання постів. -2) Визначте зворотний виклик, який буде спрацьовувати після успішного відправлення форми і який збереже нове повідомлення в базі даних. -3. Створіть новий шаблон для форми. -4. Додайте посилання на форму в шаблон головної сторінки. +1. Створимо новий presenter з формою для додавання дописів. +2. Визначимо callback, який запуститься після успішного надсилання форми та збереже новий допис у базу даних. +3. Створимо новий шаблон, на якому буде ця форма. +4. Додамо посилання на форму в шаблон головної сторінки. -Новий презентер .[#toc-new-presenter] -===================================== +Новий presenter +=============== -Назвіть новий презентер `EditPresenter` і збережіть його в `app/Presenters/EditPresenter.php`. Йому також необхідно під'єднатися до бази даних, тому тут ми знову пишемо конструктор, який потребуватиме під'єднання до бази даних: +Новий presenter назвемо `EditPresenter` і збережемо в `app/Presentation/Edit/`. Йому також потрібно підключитися до бази даних, тому ми знову напишемо конструктор, який вимагатиме підключення до бази даних: -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Edit; use Nette; use Nette\Application\UI\Form; @@ -39,95 +39,95 @@ final class EditPresenter extends Nette\Application\UI\Presenter ``` -Форма для збереження повідомлень .[#toc-form-for-saving-posts] -============================================================== +Форма для збереження дописів +============================ -Форми та компоненти вже розглядалися, коли ми додавали підтримку коментарів. Якщо ви заплуталися в темі, подивіться [як працюють форми і компоненти |comments#Form-for-Commenting] ще раз, ми почекаємо тут ;) +Форми та компоненти ми вже пояснювали при створенні коментарів. Якщо це все ще незрозуміло, перегляньте [створення форм та компонентів |comments#Форма для коментування], ми поки що зачекаємо тут ;) -Тепер додайте цей метод у `EditPresenter`: +Тепер додамо цей метод до presenter'а `EditPresenter`: -```php .{file:app/Presenters/EditPresenter.php} +```php .{file:app/Presentation/Edit/EditPresenter.php} protected function createComponentPostForm(): Form { $form = new Form; $form->addText('title', 'Заголовок:') ->setRequired(); - $form->addTextArea('content', 'Содержание:') + $form->addTextArea('content', 'Вміст:') ->setRequired(); - $form->addSubmit('send', 'Сохранить и опубликовать'); - $form->onSuccess[] = [$this, 'postFormSucceeded']; + $form->addSubmit('send', 'Зберегти та опублікувати'); + $form->onSuccess[] = $this->postFormSucceeded(...); return $form; } ``` -Збереження нового поста з форми .[#toc-saving-new-post-from-form] -================================================================= +Збереження нового допису з форми +================================ -Додамо метод обробника: +Продовжуємо, додаючи метод, який обробить дані з форми: -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { $post = $this->database ->table('posts') ->insert($data); - $this->flashMessage('Пост опубликован!', 'success'); + $this->flashMessage("Допис було успішно опубліковано.", 'success'); $this->redirect('Post:show', $post->id); } ``` -Невелике пояснення: цей метод витягує значення з форми, вставляє їх у базу даних, створює повідомлення для користувача про успішну публікацію посту і перенаправляє на сторінку, де цей пост опубліковано, щоб ви могли побачити, який він має вигляд. +Лише короткий підсумок: цей метод отримує дані з форми, вставляє їх у базу даних, створює повідомлення для користувача про успішне збереження допису та перенаправляє на сторінку з новим дописом, щоб ми одразу побачили, як він виглядає. -Сторінка для створення нового поста .[#toc-page-for-creating-a-new-post] -======================================================================== +Сторінка для створення нового допису +==================================== -Давайте просто створимо шаблон (`app/Presenters/templates/Edit/create.latte`): +Тепер створимо шаблон `Edit/create.latte`: -```latte .{file:app/Presenters/templates/Edit/create.latte} +```latte .{file:app/Presentation/Edit/create.latte} {block content} -<h1>Новый пост</h1> +<h1>Новий допис</h1> {control postForm} ``` -Тепер усе має бути зрозуміло. Останній рядок показує форму, яку ми збираємося створити. +Все вже має бути зрозуміло. Останній рядок відображає форму, яку ми створили. -Ми могли б також створити відповідний метод `renderCreate`, але це не обов'язково. Нам не потрібно отримувати дані з бази даних і передавати їх у шаблон, тому цей метод буде порожнім. У таких випадках метод може взагалі не існувати. +Ми могли б створити також відповідний метод `renderCreate`, але це не обов'язково. Нам не потрібно отримувати жодних даних з бази даних і передавати їх у шаблон, тому цей метод був би порожнім. У таких випадках метод може взагалі не існувати. -Посилання для створення постів .[#toc-link-for-creating-posts] -============================================================== +Посилання на створення дописів +============================== -Ви, ймовірно, вже знаєте, як додати посилання на `EditPresenter` та його дію `create`. Спробуйте. +Ви, мабуть, уже знаєте, як додати посилання на `EditPresenter` та його дію `create`. Спробуйте зробити це. -Просто додайте у файл `app/Presenters/templates/Home/default.latte`: +Достатньо додати до файлу `app/Presentation/Home/default.latte`: ```latte -<a n:href="Edit:create">Создать пост</a> +<a n:href="Edit:create">Написати новий допис</a> ``` -Редагування постів .[#toc-editing-posts] -======================================== +Редагування дописів +=================== -Давайте також додамо можливість редагувати наявні повідомлення. Це буде досить просто - у нас уже є `postForm`, і ми можемо використовувати її і для редагування. +Тепер додамо також можливість редагування допису. Це буде дуже просто. У нас вже є готова форма `postForm`, і ми можемо використовувати її також для редагування. -Ми додамо нову сторінку `edit` до `EditPresenter`: +Додамо нову сторінку `edit` до presenter'а `EditPresenter`: -```php .{file:app/Presenters/EditPresenter.php} -public function renderEdit(int $postId): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +public function renderEdit(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { - $this->error('Пост не найден'); + $this->error('Допис не знайдено'); // Translate string } $this->getComponent('postForm') @@ -135,26 +135,26 @@ public function renderEdit(int $postId): void } ``` -І створіть шаблон `Edit/edit.latte`: +І створимо ще один шаблон `Edit/edit.latte`: -```latte .{file:app/Presenters/templates/Edit/edit.latte} +```latte .{file:app/Presentation/Edit/edit.latte} {block content} -<h1>Редактирование поста</h1> +<h1>Редагувати допис</h1> {control postForm} ``` -І поновіть метод `postFormSucceeded`, який зможе або додавати новий пост (як зараз), або редагувати наявні: +І змінимо метод `postFormSucceeded`, який зможе як додавати нову статтю (як він робить це зараз), так і редагувати вже існуючу статтю: -```php .{file:app/Presenters/EditPresenter.php} -public function postFormSucceeded(array $data): void +```php .{file:app/Presentation/Edit/EditPresenter.php} +private function postFormSucceeded(array $data): void { - $postId = $this->getParameter('postId'); + $id = $this->getParameter('id'); - if ($postId) { + if ($id) { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); $post->update($data); } else { @@ -163,26 +163,25 @@ public function postFormSucceeded(array $data): void ->insert($data); } - $this->flashMessage('Пост опубликован', 'success'); + $this->flashMessage('Допис було успішно опубліковано.', 'success'); $this->redirect('Post:show', $post->id); } ``` -Якщо вказано параметр `postId`, це означає, що пост редагується. У цьому випадку ми перевіримо, чи дійсно пост існує, і якщо так, то оновимо його в базі даних. Якщо `postId` не вказано, це означає, що буде додано новий пост. +Якщо є параметр `id`, це означає, що ми будемо редагувати допис. У цьому випадку ми перевіримо, чи дійсно існує потрібний допис, і якщо так, оновимо його в базі даних. Якщо параметр `id` відсутній, це означає, що слід додати новий допис. -Але звідки береться `postId`? Це параметр, що передається методу `renderEdit`. +Але звідки береться цей параметр `id`? Це параметр, який був переданий у метод `renderEdit`. -Тепер ви можете додати посилання для зміни поста в шаблон `app/Presenters/templates/Post/show.latte`: +Тепер ми можемо додати посилання в шаблон `app/Presentation/Post/show.latte`: ```latte -<a n:href="Edit:edit $post->id">Изменить пост</a> +<a n:href="Edit:edit $post->id">Редагувати допис</a> ``` -Підіб'ємо підсумок .[#toc-summary] -================================== +Підсумок +======== -Блог працює, люди швидко коментують, і ми більше не покладаємося на Адміна для додавання нових постів. Блог повністю незалежний, і навіть звичайні люди можуть розміщувати там свої повідомлення. Але зачекайте, це, напевно, не нормально, що будь-хто, я маю на увазі справді будь-яка людина в Інтернеті, може написати в нашому блозі. Потрібна певна форма автентифікації, щоб тільки ті користувачі, які увійшли в систему, могли розміщувати повідомлення. Ми додамо це в наступному розділі. +Блог тепер функціонує, відвідувачі активно коментують його, і нам більше не потрібен Adminer для публікації. Застосунок повністю незалежний, і будь-хто може додати новий допис. Зачекайте, це, мабуть, не зовсім правильно, що будь-хто — і я маю на увазі справді будь-кого з доступом до Інтернету — може додавати нові дописи. Потрібна якась безпека, щоб новий допис міг додати лише залогінений користувач. Про це ми поговоримо в наступному розділі. {{priority: -1}} -{{sitename: Быстрый старт с Nette}} diff --git a/quickstart/uk/home-page.texy b/quickstart/uk/home-page.texy index d93ad3e9ca..9d3fff600e 100644 --- a/quickstart/uk/home-page.texy +++ b/quickstart/uk/home-page.texy @@ -2,40 +2,40 @@ ********************** .[perex] -Давайте створимо головну сторінку, на якій відображатимуться ваші останні пости. +Тепер ми створимо головну сторінку, що відображатиме останні дописи. -Перш ніж ми почнемо, ви маєте знати хоча б деякі основи патерну проєктування Model-View-Presenter (аналогічного MVC((Model-View-Controller)))): +Перш ніж почати, потрібно знати хоча б основи патерну проектування Model-View-Presenter (схожого на MVC((Model-View-Controller))): -- **Модель** - рівень маніпулювання даними. Він повністю відокремлений від іншої частини додатка і спілкується тільки з презентерами. +- **Модель (Model)** - шар, що працює з даними. Він повністю відокремлений від решти застосунку. Спілкується лише з presenter'ом. -- **Вид** (або _Представлення_) - зовнішній рівень визначення. Він відображає запитувані дані користувачеві за допомогою шаблонів. +- **Представлення (View)** - front-end шар. Відображає запитані дані за допомогою шаблонів і показує їх користувачеві. -- **Презентер** (або _Контролер_) - рівень з'єднання. Презентер з'єднує модель і вигляд. Обробляє запити, запитує дані у моделі і потім передає їх поточному поданню. +- **Presenter** (або Controller) - сполучний шар. Presenter пов'язує Модель і Представлення. Обробляє запити, запитує дані у Моделі та повертає їх до Представлення. -У разі дуже простого застосунку, такого як наш блог, шар Model фактично складатиметься тільки із запитів до самої бази даних - нам не потрібен додатковий PHP-код для цього. Нам потрібно створити тільки шари Presenter і View. У Nette у кожного презентера є свої подання, тому ми продовжимо роботу з ними обома одночасно. +У випадку простих застосунків, як наш блог, весь модельний шар складатиметься лише із запитів до бази даних - для цього поки що не потрібен додатковий код. Для початку створимо лише presenter'и та шаблони. У Nette кожен presenter має свої власні шаблони, тому ми будемо створювати їх одночасно. -Створення бази даних за допомогою Adminer .[#toc-creating-the-database-with-adminer] -==================================================================================== +Створення бази даних за допомогою Adminer +========================================= -Для зберігання даних ми будемо використовувати базу даних MySQL, оскільки це найбільш поширений вибір серед веб-розробників. Але якщо вам це не подобається, не соромтеся використовувати базу даних за своїм вибором. +Для зберігання даних ми використаємо базу даних MySQL, оскільки вона найбільш поширена серед програмістів веб-застосунків. Однак, якщо ви не хочете її використовувати, сміливо обирайте базу даних на власний розсуд. -Давайте підготуємо базу даних, у якій зберігатимуться записи нашого блогу. Почнемо з однієї таблиці для постів. +Тепер підготуємо структуру бази даних, де будуть зберігатися статті нашого блогу. Почнемо дуже просто - створимо лише одну таблицю для дописів. -Для створення бази даних ми можемо завантажити [Adminer |https://www.adminer.org], або ви можете використати інший інструмент для управління базами даних. +Для створення бази даних ми можемо завантажити [Adminer |https://www.adminer.org] або інший ваш улюблений інструмент для керування базами даних. -Давайте відкриємо Adminer і створимо нову базу даних під назвою `quickstart`. +Відкриємо Adminer і створимо нову базу даних з назвою `quickstart`. -Створіть нову таблицю з іменем `posts` і додайте до неї ці стовпці: -- `id` int, натисніть на автоінкремент (AI) -- `title` varchar, довжина 255 +Створимо нову таблицю з назвою `posts` та з такими стовпцями: +- `id` int, позначимо autoincrement (AI) +- `title` varchar, length 255 - `content` text - `created_at` timestamp -Це має виглядати наступним чином: +Кінцева структура має виглядати так: [* adminer-posts.webp *] @@ -49,49 +49,46 @@ CREATE TABLE `posts` ( ``` .[caution] -Дуже важливо використовувати сховище таблиць **InnoDB**. Причину ви побачите пізніше. Поки що просто створіть все за інструкцією і натисніть кнопку Зберегти. Або використовуйте повний код створення таблиці та кнопку SQL-запит в Adminer. +Дуже важливо використовувати сховище **InnoDB**. За мить ми покажемо чому. Поки що просто виберіть його та натисніть зберегти. -Спробуйте додати кілька записів до блогу, перш ніж ми реалізуємо можливість додавання нових записів безпосередньо з нашого застосунку. +Перш ніж створити можливість додавати статті до бази даних за допомогою застосунку, додайте кілька зразків статей до блогу вручну. ```sql INSERT INTO `posts` (`id`, `title`, `content`, `created_at`) VALUES -(1, 'Статья первая', 'Lorem ipusm dolor one', CURRENT_TIMESTAMP), -(2, 'Статья вторая', 'Lorem ipsum dolor two', CURRENT_TIMESTAMP), -(3, 'Статья третья', 'Lorem ipsum dolor three', CURRENT_TIMESTAMP); +(1, 'Article One', 'Lorem ipusm dolor one', CURRENT_TIMESTAMP), +(2, 'Article Two', 'Lorem ipsum dolor two', CURRENT_TIMESTAMP), +(3, 'Article Three', 'Lorem ipsum dolor three', CURRENT_TIMESTAMP); ``` -Підключення до бази даних .[#toc-connecting-to-the-database] -============================================================ +Підключення до бази даних +========================= -Тепер, коли база даних створена і в ній є кілька постів, саме час відобразити їх на нашій новій блискучій сторінці. +Тепер, коли база даних вже створена і в ній збережено кілька статей, настав час відобразити їх на нашій чудовій новій сторінці. -По-перше, нам потрібно повідомити нашому додатку, яку базу даних використовувати. Конфігурація підключення до бази даних зберігається у файлі `config/local.neon`. Встановіть з'єднання DSN((Ім'я джерела даних)) і свої облікові дані. Це має виглядати так: +Спочатку ми повинні повідомити застосунку, яку базу даних використовувати. Підключення до бази даних налаштовується у файлі `config/common.neon` за допомогою DSN((Data Source Name)) та облікових даних. Це має виглядати приблизно так: -```neon .{file:config/local.neon} +```neon .{file:config/common.neon} database: dsn: 'mysql:host=127.0.0.1;dbname=quickstart' - user: *укажите здесь имя пользователя* - password: *укажите здесь пароль* + user: *тут вставте ім'я користувача* + password: *тут вставте пароль до бази даних* ``` .[note] -Пам'ятайте про відступи під час редагування цього файлу. [Формат NEON |neon:format] приймає і пробіли, і табуляцію, але не те й інше разом! У файлі конфігурації у веб-проекті за замовчуванням використовується табуляція. +При редагуванні цього файлу будьте уважні з відступами рядків. Формат [NEON |neon:format] приймає як відступи за допомогою пробілів, так і відступи за допомогою табуляції, але не обидва одночасно. Стандартний конфігураційний файл у Web Project використовує табуляцію. -Уся конфігурація зберігається в `config/` у файлах `common.neon` і `local.neon`. Файл `common.neon` містить глобальну конфігурацію додатка, а `local.neon` містить тільки параметри, специфічні для середовища (наприклад, різниця між сервером розробки та робочим сервером). +Передача підключення до бази даних +================================== -Впровадження підключення до бази даних .[#toc-injecting-the-database-connection] -================================================================================ - -Презентер (розташований у `app/Presenters/HomePresenter.php`), який буде перераховувати статті, потребує підключення до бази даних. Для цього змініть конструктор таким чином: +Presenter `HomePresenter`, який буде відповідати за виведення статей, потребує підключення до бази даних. Для його отримання ми використаємо конструктор, який виглядатиме так: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use Nette; -use Nette\Application\UI\Form; final class HomePresenter extends Nette\Application\UI\Presenter { @@ -105,12 +102,12 @@ final class HomePresenter extends Nette\Application\UI\Presenter ``` -Завантаження постів з бази даних .[#toc-loading-posts-from-the-database] -======================================================================== +Завантаження дописів з бази даних +================================= -Тепер давайте витягнемо пости з бази даних і передамо їх у шаблон, який потім відобразить HTML-код. Для цього і призначений так званий метод *render*: +Тепер завантажимо дописи з бази даних і передамо їх до шаблону, який потім відобразить їх як HTML-код. Для цього призначений так званий *render* метод: -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} public function renderDefault(): void { $this->template->posts = $this->database @@ -120,41 +117,41 @@ public function renderDefault(): void } ``` -Тепер у презентері є один метод рендерингу `renderDefault()`, який передає дані в подання під назвою `default`. Шаблони презентера можна знайти в `app/Presenters/templates/{PresenterName}/{viewName}.latte`, тому в даному випадку шаблон буде розташований в `app/Presenters/templates/Home/default.latte`. У шаблоні тепер доступна змінна `$posts`, яка містить пости з бази даних. +Presenter тепер містить один рендер-метод `renderDefault()`, який передає дані з бази даних до шаблону (Представлення). Шаблони розміщені в `app/Presentation/{PresenterName}/{viewName}.latte`, тому в цьому випадку шаблон знаходиться в `app/Presentation/Home/default.latte`. У шаблоні тепер буде доступна змінна `$posts`, в якій містяться дописи, отримані з бази даних. -Шаблон .[#toc-template] -======================= +Шаблон +====== -Існує загальний шаблон для всієї сторінки (називається *layout* (макет), із заголовком, таблицями стилів, нижнім колонтитулом тощо), а також специфічні шаблони для кожного виду (наприклад, для відображення списку записів блогу), які можуть перевизначати деякі частини шаблону макета. +Для всього веб-сайту у нас є головний шаблон (який називається *layout*, містить заголовок, стилі, підвал,...) та конкретні шаблони для кожного представлення (View) (наприклад, для відображення дописів у блозі), які можуть перевизначити деякі частини головного шаблону. -За замовчуванням шаблон макета розташовується у файлі `app/Presenters/templates/@layout.latte`, який містить: +За замовчуванням шаблон layout знаходиться в `app/Presentation/@layout.latte` і містить: -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... {include content} ... ``` -`{include content}` вставляє блок з ім'ям `content` в основний шаблон. Ви можете визначити його в шаблонах кожного подання. У нашому випадку ми відредагуємо файл `app/Presenters/templates/Home/default.latte` таким чином: +Запис `{include content}` вставляє в головний шаблон блок з назвою `content`. Його ми будемо визначати в шаблонах окремих представлень (View). У нашому випадку файл `Home/default.latte` змінимо наступним чином: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - Привет, мир! + Привіт, Світ {/block} ``` -Він визначає [блок |latte:tags#block] *контенту*, який буде вставлено в макет. Якщо ви оновите браузер, то побачите сторінку з текстом "Привіт, світ!" (у вихідному коді також із HTML заголовком і колонтитулом, визначеними в `@layout.latte`). +Цим ми визначили [блок |latte:tags#block] *content*, який буде вставлено в головний layout. Якщо ми знову оновимо браузер, побачимо сторінку з текстом "Привіт, Світ" (у вихідному коді також з HTML-заголовком та підвалом, визначеними в `@layout.latte`). -Давайте відобразимо записи блогу - для цього відредагуємо шаблон таким чином: +Давайте відобразимо дописи з блогу - шаблон змінимо наступним чином: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - <h1 n:block="title">Мой блог</h1> + <h1>Мій блог</h1> {foreach $posts as $post} <div class="post"> - <div class="date">{$post->created_at|date:'j.m.Y'}</div> + <div class="date">{$post->created_at|date:'F j, Y'}</div> <h2>{$post->title}</h2> @@ -164,42 +161,41 @@ public function renderDefault(): void {/block} ``` -Якщо ви оновите браузер, ви побачите список записів вашого блогу. Список не дуже вигадливий або барвистий, тому не соромтеся додати трохи [блискучого CSS |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] у `www/css/style.css`, а потім вставте посилання на цей файл у макет (файл `@layout.latte`): +Якщо ми оновимо браузер, побачимо список усіх дописів. Список поки що не дуже гарний і не кольоровий, тому ми можемо додати до файлу `www/css/style.css` кілька [CSS стилів |https://github.com/nette-examples/quickstart/blob/v4.0/www/css/style.css] і підключити його в layout'і: -```latte .{file:app/Presenters/templates/@layout.latte} +```latte .{file:app/Presentation/@layout.latte} ... <link rel="stylesheet" href="{$basePath}/css/style.css"> </head> ... ``` -Тег `{foreach}` перебирає всі пости, передані шаблону у змінній `$posts`, і виводить фрагмент HTML-коду для кожного поста. Точно так само, як це робиться в PHP-коді. +Тег `{foreach}` ітерує по всіх дописах, які ми передали шаблону в змінній `$posts`, і для кожного відображає відповідний шматок HTML. Він поводиться точно так само, як PHP-код. -Функція `|date` називається фільтром. Фільтри використовуються для форматування виведення. Цей конкретний фільтр перетворює дату (наприклад, `2013-04-12`) у більш читабельну форму (`12.04.2013`). Фільтр `|truncate` усікає рядок до вказаної максимальної довжини і додає три крапки в кінець, якщо рядок усічений. Оскільки це попередній перегляд, немає сенсу відображати повний зміст статті. Інші фільтри за замовчуванням [можна знайти в документації |latte:filters] або ви можете створити свої власні, якщо це необхідно. +Запис `|date:` ми називаємо фільтром. Фільтри призначені для форматування виводу. Цей конкретний фільтр перетворює дату (наприклад, `2013-04-12`) на її більш читабельну форму (`April 12, 2013`). Фільтр `|truncate` обрізає рядок до вказаної максимальної довжини і, якщо рядок скорочується, додає в кінці три крапки. Оскільки це попередній перегляд, немає сенсу відображати весь вміст статті. Інші стандартні фільтри [знайдемо в документації |latte:filters] або можемо створити власні, коли це необхідно. -І ще одне. Ми можемо зробити код трохи коротшим і, отже, простішим. Ми можемо замінити *теги Latte* на *n:attributes* таким чином: +Ще одна річ. Попередній код можна скоротити та спростити. Цього досягнемо заміною *Latte тегів* на *n:атрибути*: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} {block content} - <h1>My blog</h1> + <h1>Мій блог</h1> <div n:foreach="$posts as $post" class="post"> <div class="date">{$post->created_at|date:'F j, Y'}</div> <h2>{$post->title}</h2> - <div>{$post->content}</div> + <div>{$post->content|truncate:256}</div> </div> {/block} ``` -`n:foreach` просто обгортає *div* блоком *foreach* (він робить те саме, що й попередній блок коду). +Атрибут `n:foreach` обгортає *div* блоком *foreach* (працює абсолютно так само, як попередній код). -Підіб'ємо підсумок .[#toc-summary] -================================== +Підсумок +======== -У нас є дуже проста база даних MySQL з деякими записами в блозі. Додаток підключається до бази даних і відображає простий список постів. +Тепер у нас є дуже проста база даних MySQL з кількома дописами. Застосунок підключається до цієї бази даних і виводить простий список цих дописів у шаблон. {{priority: -1}} -{{sitename: Быстрый старт с Nette}} diff --git a/quickstart/uk/model.texy b/quickstart/uk/model.texy index 68b42474ba..a9e46cdd47 100644 --- a/quickstart/uk/model.texy +++ b/quickstart/uk/model.texy @@ -1,13 +1,13 @@ Модель ****** -У міру зростання нашого додатка ми незабаром виявляємо, що нам необхідно виконувати аналогічні операції з базою даних у різних місцях і в різних презентерах. Наприклад, отримувати найновіші опубліковані статті. Якщо ми покращимо наш застосунок, додавши до статей прапор, що вказує на стан готовності, ми також повинні пройтися всіма місцями в нашому застосунку та додати умову *where*, щоб переконатися, що вибираються тільки готові статті. +З ростом застосунку ми скоро виявимо, що в різних місцях, у різних presenter'ах, нам потрібно виконувати подібні операції з базою даних. Наприклад, отримувати найновіші опубліковані статті. Якщо ми вдосконалимо застосунок, наприклад, додавши до статей прапорець, чи вона є чернеткою, ми повинні пройти всі місця в застосунку, де статті отримуються з бази даних, і додати умову where, щоб вибиралися лише не чернетки. -На цьому етапі прямої роботи з базою даних стає недостатньо, і розумніше буде допомогти собі новою функцією, що повертає опубліковані статті. І коли пізніше ми додамо ще один пункт (наприклад, не відображати статті з майбутньою датою), ми редагуватимемо наш код тільки в одному місці. +У цей момент пряма робота з базою даних стає недостатньою, і буде зручніше скористатися новою функцією, яка повертатиме нам опубліковані статті. А коли пізніше ми додамо ще одну умову, наприклад, що не слід відображати статті з майбутньою датою, ми змінимо код лише в одному місці. -Ми помістимо функцію в клас `PostFacade` і назвемо її `getPublicArticles()`. +Функцію розмістимо, наприклад, у класі `PostFacade` і назвемо її `getPublicArticles()`. -Створимо наш клас моделі `PostFacade` у директорії `app/Model/`, щоб подбати про наші статті. Давайте помістимо його у файл `PostFacade.php`. +У директорії `app/Model/` створимо наш модельний клас `PostFacade`, який буде відповідати за статті: ```php .{file:app/Model/PostFacade.php} <?php @@ -32,13 +32,13 @@ final class PostFacade } ``` -У цьому класі ми передаємо базу даних Explorer:[api:Nette\Database\Explorer]. Це дасть змогу використовувати можливості [DI-контейнера |dependency-injection:passing-dependencies]. +У класі за допомогою конструктора ми попросимо передати нам базу даних Explorer:[api:Nette\Database\Explorer]. Таким чином, ми використаємо силу [DI-контейнера|dependency-injection:passing-dependencies]. -Перейдемо до файлу `HomePresenter.php`, який ми відредагуємо так, щоб позбутися залежності від `Nette\Database\Explorer`, замінивши її новою залежністю від нашого створеного класу. +Переключимося на `HomePresenter`, який ми змінимо так, щоб позбутися залежності від `Nette\Database\Explorer` і замінимо її на нову залежність від нашого нового класу. -```php .{file:app/Presenters/HomePresenter.php} +```php .{file:app/Presentation/Home/HomePresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Home; use App\Model\PostFacade; use Nette; @@ -59,10 +59,9 @@ final class HomePresenter extends Nette\Application\UI\Presenter } ``` -У секції `use` ми використовуємо `App\Model\PostFacade`. Таким чином можна скоротити наш PHP-код тільки до PostFacade (не бійтеся, він працює навіть у коментарях, і ваша розумна IDE повинна бути в змозі впоратися з цим). +У секції use ми маємо `App\Model\PostFacade`, тому ми можемо скоротити запис у PHP коді до `PostFacade`. Цей об'єкт ми запитаємо в конструкторі, запишемо його у властивість `$facade` і використаємо в методі renderDefault. -Залишився останній крок - навчити DI-контейнер створювати цей об'єкт. Зазвичай це робиться шляхом додавання пункту до файлу `config/services.neon` у секції `services` із зазначенням повного імені класу та параметрів конструктора. -Це, так би мовити, реєструє його, і об'єкт потім називається **сервіс**. Завдяки деякій магії, яка називається [autowiring |dependency-injection:autowiring], нам зазвичай не потрібно вказувати параметри конструктора, оскільки DI розпізнає їх і передає автоматично. Таким чином, достатньо просто вказати ім'я класу: +Залишився останній крок — навчити DI-контейнер створювати цей об'єкт. Зазвичай це робиться так: до файлу `config/services.neon` у секції `services` додаємо маркер, вказуємо повну назву класу та параметри конструктора. Таким чином ми його так звано зареєструємо, і об'єкт потім називається **сервіс**. Завдяки магії під назвою [autowiring |dependency-injection:autowiring], нам зазвичай не потрібно вказувати параметри конструктора, оскільки DI їх самостійно розпізнає та передає. Достатньо було б вказати лише назву класу: ```neon .{file:config/services.neon} ... @@ -71,16 +70,15 @@ services: - App\Model\PostFacade ``` -Однак, додавати цей рядок також не обов'язково. У секції `search` на початку `services.neon` визначено, що всі класи, які закінчуються на `-Facade` або `-Factory`, шукатимуться DI автоматично, що також стосується і `PostFacade`. +Однак навіть цей рядок додавати не потрібно. У секції `search` на початку `services.neon` визначено, що всі класи, що закінчуються словом `-Facade` або `-Factory`, DI знайде самостійно, що і є випадком `PostFacade`. -Підіб'ємо підсумок .[#toc-summary] -================================== +Підсумок +======== -Клас `PostFacade` запитує `Nette\Database\Explorer` у конструкторі, і оскільки цей клас зареєстрований у контейнері DI, контейнер створює цей екземпляр і передає його. DI таким чином створює для нас екземпляр PostFacade і передає його в конструкторі класу HomePresenter, який його запросив. Свого роду матрьошка коду. :) Усі компоненти запитують тільки те, що їм потрібно, і їх не хвилює, де і як це буде створено. Створенням займається DI-контейнер. +Клас `PostFacade` у конструкторі запитує передачу `Nette\Database\Explorer`, і оскільки цей клас зареєстрований у DI-контейнері, контейнер створює цей екземпляр і передає його. DI таким чином створює для нас екземпляр `PostFacade` і передає його в конструкторі класу HomePresenter, який його запитав. Така собі матрьошка. :) Всі просто кажуть, що їм потрібно, і не цікавляться, де що і як створюється. Створенням займається DI-контейнер. .[note] -Детальніше про впровадження залежностей можна прочитати [тут |dependency-injection:introduction], а про конфігурацію - [тут |nette:configuring] +Тут ви можете прочитати більше про [dependency injection |dependency-injection:introduction] та [конфігурацію |nette:configuring]. {{priority: -1}} -{{sitename: Быстрый старт с Nette}} diff --git a/quickstart/uk/single-post.texy b/quickstart/uk/single-post.texy index 2602e035ab..39b828178e 100644 --- a/quickstart/uk/single-post.texy +++ b/quickstart/uk/single-post.texy @@ -1,17 +1,17 @@ -Сторінка окремого запису -************************ +Сторінка з дописом +****************** .[perex] -Давайте додамо в наш блог ще одну сторінку, на якій відображатиметься вміст одного конкретного запису блогу. +Тепер ми створимо ще одну сторінку блогу, яка буде відображати один конкретний допис. -Нам потрібно створити новий метод render, який буде отримувати один конкретний запис блогу і передавати його в шаблон. Мати це подання в `HomePresenter` не дуже приємно, тому що йдеться про запис у блозі, а не про головну сторінку. Отже, давайте створимо новий клас `PostPresenter` і помістимо його в `app/Presenters`. Йому знадобиться з'єднання з базою даних, тому знову помістіть туди код *впровадження залежності*. +Нам потрібно створити новий render-метод, який отримає одну конкретну статтю і передасть її в шаблон. Мати цей метод у `HomePresenter` не дуже гарно, оскільки ми говоримо про статтю, а не про головну сторінку. Створимо `PostPresenter` у `app/Presentation/Post/`. Цей presenter також потребує підключення до бази даних, тому тут ми знову напишемо конструктор, який вимагатиме підключення до бази даних. -`PostPresenter` має виглядати наступним чином: +`PostPresenter` може виглядати так: -```php .{file:app/Presenters/PostPresenter.php} +```php .{file:app/Presentation/Post/PostPresenter.php} <?php -namespace App\Presenters; +namespace App\Presentation\Post; use Nette; use Nette\Application\UI\Form; @@ -23,103 +23,102 @@ final class PostPresenter extends Nette\Application\UI\Presenter ) { } - public function renderShow(int $postId): void + public function renderShow(int $id): void { $this->template->post = $this->database ->table('posts') - ->get($postId); + ->get($id); } } ``` -Ми повинні встановити правильний простір імен `App\Presenters` для нашого презентера. Це залежить від [mapping presenter |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7]. +Не забудемо вказати правильний простір імен `App\Presentation\Post`, який підпорядковується налаштуванню [мапування presenter'ів |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7]. -Метод `renderShow` вимагає один аргумент - ID відображуваного поста. Потім він завантажує цей пост із бази даних і передає результат у шаблон. +Метод `renderShow` вимагає один аргумент - ID однієї конкретної статті, яка має бути відображена. Потім він завантажує цю статтю з бази даних і передає її в шаблон. -У шаблоні `Home/default.latte` ми додаємо посилання на дію `Post:show`: +У шаблон `Home/default.latte` вставимо посилання на дію `Post:show`. -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a href="{link Post:show $post->id}">{$post->title}</a></h2> ... ``` -Тег `{link}` генерує адресу URL, яка вказує на дію `Post:show`. Цей тег також передає ID поста як аргумент. +Тег `{link}` генерує URL-адресу, яка вказує на дію `Post:show`. Він також передає ID допису як аргумент. -Те ж саме ми можемо написати коротко, використовуючи n:attribute: +Те саме можна записати скорочено за допомогою n:атрибута: -```latte .{file:app/Presenters/templates/Home/default.latte} +```latte .{file:app/Presentation/Home/default.latte} ... <h2><a n:href="Post:show $post->id">{$post->title}</a></h2> ... ``` -Атрибут `n:href` аналогічний тегу `{link}`. +Атрибут `n:href` є аналогом тегу `{link}`. -Шаблон для дії `Post:show` ще не існує. Ми можемо відкрити посилання на цей пост. [Tracy |tracy:] покаже помилку про те, що `app/Presenters/templates/Post/show.latte` не існує. Якщо ви бачите будь-який інший звіт про помилку, ймовірно, вам потрібно увімкнути mod_rewrite у вашому веб-сервері. +Однак для дії `Post:show` ще не існує шаблону. Можемо спробувати відкрити посилання на цей допис. [Tracy |tracy:] відобразить помилку, оскільки шаблон `Post/show.latte` ще не існує. Якщо ви бачите інше повідомлення про помилку, то, ймовірно, вам доведеться увімкнути `mod_rewrite` на веб-сервері. -Тому ми створимо `Post/show.latte` з таким змістом: +Створимо шаблон `Post/show.latte` з таким вмістом: -```latte .{file:app/Presenters/templates/Post/show.latte} +```latte .{file:app/Presentation/Post/show.latte} {block content} -<p><a n:href="Home:default">← вернуться к списку постов</a></p> +<p><a n:href="Home:default">← назад до списку дописів</a></p> -<div class="date">{$post->created_at|date:'j.m.Y'}</div> +<div class="date">{$post->created_at|date:'F j, Y'}</div> <h1 n:block="title">{$post->title}</h1> <div class="post">{$post->content}</div> ``` -Розглянемо деякі моменти. +Тепер розглянемо окремі частини шаблону. -Перший рядок починає визначення *іменованого блоку* під назвою `content`, які ми бачили раніше. Він буде відображатися в *шаблоні макета*. +Перший рядок починає визначення блоку з назвою "content", так само як це було на головній сторінці. Цей блок знову буде відображений у головному шаблоні. Як бачите, відсутній кінцевий тег `{/block}`. Він є необов'язковим. -Другий рядок містить зворотне посилання на список постів блогу, щоб користувач міг плавно переміщатися по нашому блогу вперед і назад. Ми знову використовуємо атрибут `n:href`, тому Nette подбає про генерацію URL для нас. Посилання вказує на дію `default` презентера `Home` (можна просто написати `n:href="Home:"`, оскільки дія `default` може бути опущена). +На наступному рядку є посилання назад на список статей блогу, щоб користувач міг легко переміщатися між списком статей та однією конкретною. Оскільки ми використовуємо атрибут `n:href`, Nette саме подбає про генерацію посилань. Посилання вказує на дію `default` presenter'а `Home` (можна також написати `n:href="Home:"`, оскільки дія з назвою `default` може бути пропущена, вона доповниться автоматично). -Третій рядок форматує тимчасову мітку публікації за допомогою фільтра `date`, як ми вже знаємо. +Третій рядок форматує виведення дати за допомогою фільтра, який ми вже знаємо. -Четвертий рядок відображає *заголовок* запису блогу у вигляді заголовка `<h1>`. Є частина, з якою ви, можливо, не знайомі, це `n:block="title"`. Чи можете ви здогадатися, що вона робить? Якщо ви уважно читали попередні частини, ми згадували `n: атрибуты`. Ось ще один приклад. Це еквівалентно: +Четвертий рядок відображає *заголовок* блогу в HTML-тегу `<h1>`. Цей тег містить атрибут, який ви, можливо, не знаєте (`n:block="title"`). Вгадаєте, що він робить? Якщо ви уважно читали попередню частину, то вже знаєте, що це `n:атрибут`. Це ще один їх приклад, який є еквівалентним до: ```latte {block title}<h1>{$post->title}</h1>{/block} ``` -Простіше кажучи, він *перевизначає* блок під назвою `title`. Блок визначено в *шаблоні макета* (`/app/Presenters/templates/@layout.latte:11`) і, як і у випадку з перевизначенням ООП, він перевизначається тут. Тому `<title>` сторінки буде містити заголовок відображуваного поста. Ми перевизначили заголовок сторінки, і все, що нам було потрібно, це `n:block="title"`. Чудово, чи не так? +Простіше кажучи, цей блок перевизначає блок з назвою `title`. Цей блок вже визначений у головному *layout* шаблоні (`/app/Presentation/@layout.latte:11`), і так само, як при перевизначенні методів в ООП, точно так само цей блок у головному шаблоні буде перевизначений. Отже, `<title>` сторінки тепер містить заголовок відображеного допису, і для цього нам знадобилося використати лише один простий атрибут `n:block="title"`. Чудово, чи не так? -П'ятий і останній рядок шаблону відображає повний зміст вашого поста. +П'ятий і останній рядок шаблону відображає весь вміст одного конкретного допису. -Перевірка ідентифікатора поста .[#toc-checking-post-id] -======================================================= +Перевірка ID допису +=================== -Що станеться, якщо хтось змінить URL і вставить неіснуючий `postId`? Ми повинні надати користувачеві красиву сторінку помилки "сторінку не знайдено". Давайте оновимо метод `render` у файлі `PostPresenter.php`: +Що станеться, якщо хтось змінить ID в URL і вставить якесь неіснуюче `id`? Ми повинні запропонувати користувачеві гарну помилку типу "сторінка не знайдена". Трохи змінимо render-метод у `PostPresenter`: -```php .{file:app/Presenters/PostPresenter.php} -public function renderShow(int $postId): void +```php .{file:app/Presentation/Post/PostPresenter.php} +public function renderShow(int $id): void { $post = $this->database ->table('posts') - ->get($postId); + ->get($id); if (!$post) { - $this->error('Страница не найдена!'); + $this->error('Сторінку не знайдено'); } $this->template->post = $post; } ``` -Якщо пост не може бути знайдений, виклик `$this->error(...)` покаже сторінку 404 з красивим і зрозумілим повідомленням. Зверніть увагу, що в режимі розробки ви не побачите сторінку помилки. Натомість Tracy покаже виняток із повною інформацією, що досить зручно для розробки. Ви можете перевірити обидва режими, просто змінивши значення, що передається в `setDebugMode` на `Bootstrap.php`. +Якщо допис не може бути знайдений, викликом `$this->error(...)` ми відобразимо сторінку помилки 404 зі зрозумілим повідомленням. Зверніть увагу, що в режимі розробки (localhost) ви цю сторінку помилки не побачите. Замість неї з'явиться Tracy з деталями про виняток, що досить зручно для розробки. Якщо ми хочемо побачити обидва режими, достатньо лише змінити аргумент методу `setDebugMode` у файлі `Bootstrap.php`. -Підіб'ємо підсумок .[#toc-summary] -================================== +Підсумок +======== -У нас є база даних із записами блогу і веб-додаток із двома поданнями: перше відображає зведення всіх останніх записів, а друге - один конкретний запис. +У нас є база даних з дописами та веб-застосунок, який має два представлення - перше відображає огляд усіх дописів, а друге - один конкретний допис. {{priority: -1}} -{{sitename: Быстрый старт с Nette}} diff --git a/robot-loader/bg/@home.texy b/robot-loader/bg/@home.texy index 2fdf709047..942d4157a6 100644 --- a/robot-loader/bg/@home.texy +++ b/robot-loader/bg/@home.texy @@ -1,17 +1,20 @@ -RobotLoader: клас Autoloader -**************************** +Nette RobotLoader +***************** <div class=perex> -RobotLoader е инструмент, който осигурява автоматично зареждане на класове за цялото приложение, включително библиотеки на трети страни. +RobotLoader е инструмент, който ще ви осигури комфорта на автоматичното зареждане на класове за цялото ви приложение, включително библиотеки на трети страни. -- да се отървете от всички `require` -- зареждат се само необходимите скриптове -- не изисква строги конвенции за именуване на директориите или файловете. +- ще се отървем от всички `require` +- ще се зареждат само необходимите скриптове +- не изисква строги конвенции за именуване на директории или файлове +- изключително бърз +- никакви ръчни актуализации на кеша, всичко протича автоматично +- зряла, стабилна и широко използвана библиотека </div> -Така че можем да забравим за известните блокове от код: +Следователно можем да забравим за тези познати блокове код: ```php require_once 'Utils/Page.php'; @@ -21,46 +24,55 @@ require_once 'Utils/Paginator.php'; ``` -Инсталиране на .[#toc-installation] ------------------------------------ +Инсталация +---------- -Изтеглете и инсталирайте пакета, като използвате [Composer|best-practices:composer]: +Можете да изтеглите RobotLoader като [един отделен файл `RobotLoader.php` |https://github.com/nette/robot-loader/raw/standalone/src/RobotLoader/RobotLoader.php], който да вмъкнете с `require` във вашия скрипт и веднага имате на разположение комфортен autoloading за цялото приложение. + +```php +require '/path/to/RobotLoader.php'; + +$loader = new Nette\Loaders\RobotLoader; +// ... +``` + +Ако изграждате приложение, използващо [Composer |best-practices:composer], можете да го инсталирате с негова помощ: ```shell composer require nette/robot-loader ``` -Използване на .[#toc-usage] ---------------------------- +Използване +---------- -По същия начин, по който Google претърсва и индексира уебсайтове, [RobotLoader |api:Nette\Loaders\RobotLoader] преглежда всички PHP скриптове и записва кои класове и интерфейси са открити в тях. След това тези записи се кешират и се използват при всяко следващо търсене. Трябва само да посочите кои директории да се индексират и къде да се съхранява кешът: +Подобно на това как Google robot обхожда и индексира уеб страници, така и [RobotLoader |api:Nette\Loaders\RobotLoader] обхожда всички PHP скриптове и записва кои класове, интерфейси, трейтове и енуми е намерил в тях. След това съхранява резултатите от изследването в кеш и ги използва при следващата заявка. Достатъчно е само да определите кои директории да обхожда и къде да съхранява кеша: ```php $loader = new Nette\Loaders\RobotLoader; -// директории, които да бъдат индексирани от RobotLoader (включително поддиректории) +// директории, които RobotLoader трябва да индексира (включително поддиректории) $loader->addDirectory(__DIR__ . '/app'); $loader->addDirectory(__DIR__ . '/libs'); -// използвайте директория 'temp' за кеша +// задаваме кеширане в директория 'temp' $loader->setTempDirectory(__DIR__ . '/temp'); -$loader->register(); // Стартиране на RobotLoader +$loader->register(); // стартираме RobotLoader ``` -И това е всичко. Отсега нататък не е необходимо да използвате `require`. Чудесно, нали? +И това е всичко, от този момент не е необходимо да използваме `require`. Страхотно! -Когато RobotLoader срещне дублирано име на клас по време на индексирането, той хвърля изключение и ви уведомява за това. RobotLoader също така автоматично актуализира кеша си, когато трябва да зареди клас, който не познава. Препоръчваме да забраните това на производствени сървъри, вижте раздел [Кеширане |#Кэширование]. +Ако RobotLoader се натъкне на дублирано име на клас при индексиране, той хвърля изключение и ви информира за това. RobotLoader също така автоматично актуализира кеша, когато трябва да зареди клас, който не познава. Препоръчваме да изключите това на продукционни сървъри, вижте [#Кеширане]. -Ако искате RobotLoader да пропусне някои директории, използвайте `$loader->excludeDirectory('temp')` (може да се извика многократно или да се предадат няколко директории). +Ако искате RobotLoader да пропусне някои директории, използвайте `$loader->excludeDirectory('temp')` (може да се извиква многократно или да се предадат повече директории). -По подразбиране RobotLoader съобщава за грешки в PHP файловете, като хвърля изключение `ParseError`. Това може да бъде деактивирано с помощта на `$loader->reportParseErrors(false)`. +По подразбиране RobotLoader съобщава за грешки в PHP файловете чрез хвърляне на изключение `ParseError`. Това може да бъде потиснато с помощта на `$loader->reportParseErrors(false)`. -Приложение Nette .[#toc-nette-application] ------------------------------------------- +Nette приложение +---------------- -В рамките на приложението Nette, където `$configurator` се използва в `Bootstrap.php`, можете да конфигурирате RobotLoader по този начин: +Вътре в Nette приложение, където се използва обектът `$configurator` в зареждащия файл `Bootstrap.php`, записът може да бъде опростен: ```php $configurator = new Nette\Bootstrap\Configurator; @@ -73,57 +85,58 @@ $configurator->createRobotLoader() ``` -PHP файлов анализатор .[#toc-php-files-analyzer] ------------------------------------------------- +Анализатор на PHP файлове +------------------------- -RobotLoader може да се използва и само за намиране на класове, интерфейси и черти в PHP файлове **без да се използва функцията за автоматично зареждане: +RobotLoader може да се използва и чисто за търсене на класове, интерфейси, трейтове и енуми в PHP файлове **без** използване на функцията за autoloading: ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); -// Търсене на директории за класове / интерфейси / черти +// търси в директориите за класове / интерфейси / трейтове / енуми $loader->rebuild(); -//връща масив от двойки клас => име на файл +// връща масив от двойки клас => име на файл $res = $loader->getIndexedClasses(); ``` -Дори и при тази употреба можете да използвате кеша. В резултат на това немодифицираните файлове няма да бъдат анализирани отново при повторно сканиране: +Дори при такова използване можете да използвате кеша. Благодарение на това при повторно сканиране няма да бъдат повторно анализирани непроменените файлове: ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); + +// задаваме кеширане в директория 'temp' $loader->setTempDirectory(__DIR__ . '/temp'); -// Сканиране на директории с кеш +// търси в директориите с използване на кеша $loader->refresh(); -// Връща масив от двойки клас => име на файл +// връща масив от двойки клас => име на файл $res = $loader->getIndexedClasses(); ``` -Кеширане .[#toc-caching] ------------------------- +Кеширане +-------- -RobotLoader е много бърз, защото използва кеша интелигентно. +RobotLoader е много бърз, защото умело използва кеша. -Когато разработвате с него, почти не забелязвате, че работи във фонов режим. Той постоянно актуализира кеша, защото знае, че класовете и файловете могат да бъдат създавани, изтривани, преименувани и т.н. И не сканира повторно немодифицирани файлове. +При разработка практически не усещате, че работи на заден план. Непрекъснато актуализира кеша си, защото разчита на това, че класове и файлове могат да възникват, изчезват, да се преименуват и т.н. И не сканира повторно файлове, които не са се променили. -От друга страна, когато се използва в производствен сървър, препоръчваме да забраните актуализирането на кеша с `$loader->setAutoRefresh(false)` (това се прави автоматично в приложението Nette), тъй като файловете не се модифицират. В същото време трябва да **изчистите кеша**, когато качвате нова версия на хоста. +При разгръщане на продукционен сървър, напротив, препоръчваме да изключите актуализацията на кеша с помощта на `$loader->setAutoRefresh(false)` (в Nette Приложение това се случва автоматично), защото файловете не се променят. В същото време е необходимо при качване на нова версия на хостинга **да изтриете кеша.** -Разбира се, първоначалното сканиране на файловете, когато кешът все още не съществува, може да отнеме няколко секунди при по-големи приложения. RobotLoader разполага с вградена защита срещу "препускане на кеша":https://en.wikipedia.org/wiki/Cache_stampede. -Това е ситуация, в която производствен сървър получава голям брой едновременни заявки и тъй като кешът на RobotLoader все още не съществува, всички те започват да сканират за файлове. Това увеличава натоварването на процесора и файловата система. -За щастие RobotLoader работи по такъв начин, че ако има няколко едновременни заявки, само първата нишка индексира файловете, създава кеш, а останалите изчакват, след което използват кеша. +Първоначалното сканиране на файлове, когато кешът още не съществува, може при по-обширни приложения разбираемо да отнеме малко време. RobotLoader има вградена превенция срещу "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Става въпрос за ситуация, когато на продукционен сървър се съберат по-голям брой едновременни заявки, които стартират RobotLoader, и тъй като кешът още не съществува, всички биха започнали да сканират файлове. Което би натоварило непропорционално сървъра. За щастие RobotLoader работи така, че при повече едновременни заявки файловете се индексират само от първата нишка, създава се кеш, останалите чакат и впоследствие използват кеша. -PSR-4 .[#toc-psr-4] -------------------- +PSR-4 +----- -Днес Composer може да се използва за [автоматично зареждане в |best-practices:composer#Autoloading] съответствие с PSR-4. Казано по-просто, това е система, в която пространствата от имена и имената на класовете съответстват на структурата на директориите и имената на файловете, т.е. `App\Router\RouterFactory` е във файла `/path/to/App/Router/RouterFactory.php`. +Днес може [да използвате Composer за autoloading |best-practices:composer#Autoloading] при спазване на PSR-4. Просто казано, става въпрос за система, при която пространствата от имена и имената на класовете съответстват на директорийната структура и имената на файловете, т.е. напр. `App\Core\RouterFactory` ще бъде във файла `/path/to/App/Core/RouterFactory.php`. -RobotLoader не е обвързан с никаква фиксирана структура, така че е полезен в ситуации, в които не искате структурата на директориите да бъде представена от пространствата от имена на PHP или когато разработвате приложение, което в миналото не е използвало тези конвенции. Можете също така да използвате и двете зареждащи устройства заедно. +RobotLoader не е свързан с никаква фиксирана структура, затова е подходящ в ситуации, когато не ви е напълно удобно да имате еднакво проектирана директорийна структура като пространствата от имена в PHP, или когато разработвате приложение, което исторически не използва такива конвенции. Възможно е също така да се използват и двата loader-а заедно. {{leftbar: nette:@menu-topics}} +{{sitename: Документация на Nette}} diff --git a/robot-loader/cs/@home.texy b/robot-loader/cs/@home.texy index 13acd5b3e3..4e8b4e2775 100644 --- a/robot-loader/cs/@home.texy +++ b/robot-loader/cs/@home.texy @@ -1,5 +1,5 @@ -RobotLoader: autoloading tříd -***************************** +Nette RobotLoader +***************** <div class=perex> @@ -8,6 +8,9 @@ RobotLoader je nástroj, který vám zajistí komfort automatického načítán - zbavíme se všech `require` - budou se načítat jen potřebné skripty - nevyžaduje striktní konvence pojmenování adresářů či souborů +- extrémně rychlé +- žádné ruční aktualizace mezipaměti, vše probíhá automaticky +- zralá, stabilní a široce používaná knihovna </div> @@ -24,7 +27,16 @@ require_once 'Utils/Paginator.php'; Instalace --------- -Knihovnu stáhěte a nainstalujete pomocí nástroje [Composer|best-practices:composer]: +RobotLoader si můžete stáhnout jako [jeden samostatný soubor `RobotLoader.php` |https://github.com/nette/robot-loader/raw/standalone/src/RobotLoader/RobotLoader.php], který vložíte pomocí `require` do svého skriptu a hned máte k dispozici komfortní autoloading pro celou aplikaci. + +```php +require '/path/to/RobotLoader.php'; + +$loader = new Nette\Loaders\RobotLoader; +// ... +``` + +Pokud stavíte aplikaci využívající [Composer|best-practices:composer], můžete jej nainstalovat pomocí něj: ```shell composer require nette/robot-loader @@ -34,7 +46,7 @@ composer require nette/robot-loader Použití ------- -Podobně, jako Google robot prochází a indexuje webové stránky, tak i [RobotLoader |api:Nette\Loaders\RobotLoader] prochází všechny PHP skripty a zaznamenává si, které třídy, rozhraní a traity v nich našel. Výsledky bádání si poté uloží do cache a použije při dalším požadavku. Stačí tedy určit, které adresáře má procházet a kam ukládat cache: +Podobně, jako Google robot prochází a indexuje webové stránky, tak i [RobotLoader |api:Nette\Loaders\RobotLoader] prochází všechny PHP skripty a zaznamenává si, které třídy, rozhraní, traity a enumy v nich našel. Výsledky bádání si poté uloží do cache a použije při dalším požadavku. Stačí tedy určit, které adresáře má procházet a kam ukládat cache: ```php $loader = new Nette\Loaders\RobotLoader; @@ -76,13 +88,13 @@ $configurator->createRobotLoader() Analyzátor PHP souborů ---------------------- -RobotLoader lze také použít čistě k vyhledávání tříd, rozhraní a trait v souborech PHP **bez** využívání funkce autoloadingu: +RobotLoader lze také použít čistě k vyhledávání tříd, rozhraní, trait a enumů v souborech PHP **bez** využívání funkce autoloadingu: ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); -// prohledá adresáře na třídy / rozhraní / traity +// prohledá adresáře na třídy / rozhraní / traity / enumy $loader->rebuild(); // vrací pole dvojic třída => název souboru @@ -94,6 +106,8 @@ I při takovém použití můžete využít cache. Díky tomu při opětovném s ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); + +// nastavíme cachování do adresáře 'temp' $loader->setTempDirectory(__DIR__ . '/temp'); // prohledá adresáře s využitím cache @@ -113,17 +127,16 @@ Při vývoji s ním prakticky netušíte, že běží na pozadí. Průběžně s Při nasazení na produkčním serveru naopak doporučujeme aktualizaci cache vypnout pomocí `$loader->setAutoRefresh(false)` (v Nette Aplikaci se tak děje automaticky), protože soubory se nemění. Zároveň je pak nutné při nahrání nové verze na hostingu **smazat cache.** -Prvotní scanování souborů, když cache ještě neexistuje, může u rozsáhlejších aplikací pochopitelně chviličku trvat. RobotLoader má vestavěnou prevenci před "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. -Jde o situaci, kdy se na produkčním serveru sejde větší počet souběžných požadavků, které spustí RobotLoader, a protože cache ještě neexistuje, začaly by všechny scanovat soubory. Což by neúměrně zatížilo server. -Naštěstí RobotLoader funguje tak, že při více souběžných požadavcích indexuje soubory pouze první vlákno, vytvoří cache, ostatní čekají a následně cache využíjí. +Prvotní scanování souborů, když cache ještě neexistuje, může u rozsáhlejších aplikací pochopitelně chviličku trvat. RobotLoader má vestavěnou prevenci před "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Jde o situaci, kdy se na produkčním serveru sejde větší počet souběžných požadavků, které spustí RobotLoader, a protože cache ještě neexistuje, začaly by všechny scanovat soubory. Což by neúměrně zatížilo server. Naštěstí RobotLoader funguje tak, že při více souběžných požadavcích indexuje soubory pouze první vlákno, vytvoří cache, ostatní čekají a následně cache využíjí. PSR-4 ----- -Dnes lze pro [autoloading používat Composer|best-practices:composer#autoloading] při dodržení PSR-4. Zjednodušeně řečeno jde o systém, kdy jmenné prostory a názvy tříd odpovídají adresářové struktuře a názvům souborů, tedy např. `App\Router\RouterFactory` bude v souboru `/path/to/App/Router/RouterFactory.php`. +Dnes lze pro [autoloading používat Composer |best-practices:composer#Autoloading] při dodržení PSR-4. Zjednodušeně řečeno jde o systém, kdy jmenné prostory a názvy tříd odpovídají adresářové struktuře a názvům souborů, tedy např. `App\Core\RouterFactory` bude v souboru `/path/to/App/Core/RouterFactory.php`. RobotLoader není s žádnou pevnou strukturou spjat, proto se hodí v situacích, kdy vám úplně nevyhovuje mít stejně navrženou adresářovou strukturu jako jmenné prostory v PHP, nebo když vyvíjíte aplikaci, která historicky takové konvence nevyužívá. Je možné také používat oba loadery dohromady. {{leftbar: nette:@menu-topics}} +{{sitename: Nette Dokumentace}} diff --git a/robot-loader/de/@home.texy b/robot-loader/de/@home.texy index 71dda5a3cb..e52469fff1 100644 --- a/robot-loader/de/@home.texy +++ b/robot-loader/de/@home.texy @@ -1,17 +1,20 @@ -RobotLoader: Klasse Autoloading -******************************* +Nette RobotLoader +***************** <div class=perex> -RobotLoader ist ein Tool, das Ihnen den Komfort des automatischen Ladens von Klassen für Ihre gesamte Anwendung einschließlich der Bibliotheken von Drittanbietern bietet. +RobotLoader ist ein Werkzeug, das Ihnen den Komfort des automatischen Ladens von Klassen für Ihre gesamte Anwendung einschließlich Bibliotheken von Drittanbietern bietet. -- alle loswerden `require` -- nur notwendige Skripte werden geladen -- erfordert keine strengen Verzeichnis- oder Dateinamenskonventionen +- wir werden alle `require` los +- es werden nur die benötigten Skripte geladen +- erfordert keine strengen Konventionen zur Benennung von Verzeichnissen oder Dateien +- extrem schnell +- keine manuellen Cache-Updates, alles geschieht automatisch +- eine ausgereifte, stabile und weit verbreitete Bibliothek </div> -Wir können also diese berühmten Codeblöcke vergessen: +Wir können also diese bekannten Codeblöcke vergessen: ```php require_once 'Utils/Page.php'; @@ -21,46 +24,55 @@ require_once 'Utils/Paginator.php'; ``` -Installation .[#toc-installation] ---------------------------------- +Installation +------------ -Laden Sie das Paket herunter und installieren Sie es mit [Composer |best-practices:composer]: +Sie können RobotLoader als [eine einzelne separate Datei `RobotLoader.php` |https://github.com/nette/robot-loader/raw/standalone/src/RobotLoader/RobotLoader.php] herunterladen, die Sie mit `require` in Ihr Skript einbinden und sofort komfortables Autoloading für die gesamte Anwendung zur Verfügung haben. + +```php +require '/path/to/RobotLoader.php'; + +$loader = new Nette\Loaders\RobotLoader; +// ... +``` + +Wenn Sie eine Anwendung mit [Composer|best-practices:composer] erstellen, können Sie ihn damit installieren: ```shell composer require nette/robot-loader ``` -Verwendung .[#toc-usage] ------------------------- +Verwendung +---------- -Ähnlich wie der Google-Roboter Webseiten crawlt und indiziert, crawlt [RobotLoader |api:Nette\Loaders\RobotLoader] alle PHP-Skripte und zeichnet auf, welche Klassen und Schnittstellen in ihnen gefunden wurden. Diese Aufzeichnungen werden dann im Cache gespeichert und bei allen nachfolgenden Anfragen verwendet. Sie müssen nur angeben, welche Verzeichnisse indiziert werden sollen und wo der Cache gespeichert werden soll: +Ähnlich wie der Google-Roboter Webseiten durchsucht und indiziert, durchsucht auch [RobotLoader |api:Nette\Loaders\RobotLoader] alle PHP-Skripte und merkt sich, welche Klassen, Schnittstellen, Traits und Enums er darin gefunden hat. Die Ergebnisse der Suche speichert er dann im Cache und verwendet sie bei der nächsten Anfrage. Es genügt also, anzugeben, welche Verzeichnisse er durchsuchen und wo er den Cache speichern soll: ```php $loader = new Nette\Loaders\RobotLoader; -// Verzeichnisse, die von RobotLoader indiziert werden sollen (einschließlich Unterverzeichnisse) +// Verzeichnisse, die RobotLoader indizieren soll (einschließlich Unterverzeichnissen) $loader->addDirectory(__DIR__ . '/app'); $loader->addDirectory(__DIR__ . '/libs'); -// 'temp' Verzeichnis für Cache verwenden +// wir stellen das Caching auf das Verzeichnis 'temp' ein $loader->setTempDirectory(__DIR__ . '/temp'); -$loader->register(); // Starten Sie den RobotLoader +$loader->register(); // wir starten RobotLoader ``` -Und das ist alles. Von nun an brauchen Sie `require` nicht mehr zu verwenden. Toll, nicht wahr? +Und das ist alles, von diesem Moment an müssen wir `require` nicht mehr verwenden. Großartig! -Wenn RobotLoader bei der Indizierung auf doppelte Klassennamen stößt, löst er eine Ausnahme aus und informiert Sie darüber. RobotLoader aktualisiert auch automatisch den Cache, wenn er eine Klasse laden muss, die er nicht kennt. Wir empfehlen, dies auf Produktionsservern zu deaktivieren, siehe [Caching |#Caching]. +Wenn RobotLoader bei der Indexierung auf einen doppelten Klassennamen stößt, löst er eine Ausnahme aus und informiert Sie darüber. RobotLoader aktualisiert den Cache auch automatisch, wenn er eine Klasse laden soll, die er nicht kennt. Wir empfehlen, dies auf Produktionsservern zu deaktivieren, siehe [#Caching]. -Wenn Sie möchten, dass RobotLoader einige Verzeichnisse überspringt, verwenden Sie `$loader->excludeDirectory('temp')` (es kann mehrfach aufgerufen werden oder Sie können mehrere Verzeichnisse übergeben). +Wenn Sie möchten, dass RobotLoader einige Verzeichnisse überspringt, verwenden Sie `$loader->excludeDirectory('temp')` (kann mehrmals aufgerufen oder mehrere Verzeichnisse übergeben werden). -Standardmäßig meldet RobotLoader Fehler in PHP-Dateien, indem er die Ausnahme `ParseError` auslöst. Sie kann über `$loader->reportParseErrors(false)` deaktiviert werden. +Standardmäßig meldet RobotLoader Fehler in PHP-Dateien durch Auslösen einer `ParseError`-Ausnahme. Dies kann mit `$loader->reportParseErrors(false)` unterdrückt werden. -Nette Anwendung .[#toc-nette-application] ------------------------------------------ +Nette-Anwendung +--------------- -Innerhalb der Nette-Anwendung, wo `$configurator` in `Bootstrap.php` verwendet wird, können Sie RobotLoader auf diese Weise einrichten: +Innerhalb einer Nette-Anwendung, wo in der Startdatei `Bootstrap.php` das Objekt `$configurator` verwendet wird, kann die Notation vereinfacht werden: ```php $configurator = new Nette\Bootstrap\Configurator; @@ -73,57 +85,58 @@ $configurator->createRobotLoader() ``` -PHP-Dateianalysator .[#toc-php-files-analyzer] ----------------------------------------------- +Analysator für PHP-Dateien +-------------------------- -RobotLoader kann auch verwendet werden, um Klassen, Interfaces und Traits in PHP-Dateien zu finden **ohne** die Autoloading-Funktion zu verwenden: +RobotLoader kann auch ausschließlich zum Suchen von Klassen, Schnittstellen, Traits und Enums in PHP-Dateien verwendet werden, **ohne** die Autoloading-Funktion zu nutzen: ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); -// Durchsucht Verzeichnisse nach Klassen / Schnittstellen / Traits +// durchsucht Verzeichnisse nach Klassen / Schnittstellen / Traits / Enums $loader->rebuild(); -// Gibt Array von Klasse => Dateinamenpaaren zurück +// gibt ein Array von Paaren Klasse => Dateiname zurück $res = $loader->getIndexedClasses(); ``` -Auch bei einer solchen Verwendung können Sie den Cache nutzen. Dadurch werden unveränderte Dateien beim erneuten Scannen nicht wiederholt analysiert: +Auch bei einer solchen Verwendung können Sie den Cache nutzen. Dadurch werden unveränderte Dateien bei einem erneuten Scan nicht erneut analysiert: ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); + +// wir stellen das Caching auf das Verzeichnis 'temp' ein $loader->setTempDirectory(__DIR__ . '/temp'); -// Scannt Verzeichnisse mit Hilfe eines Caches +// durchsucht Verzeichnisse unter Verwendung des Caches $loader->refresh(); -// Rückgabe eines Arrays von Klasse => Dateinamenpaaren +// gibt ein Array von Paaren Klasse => Dateiname zurück $res = $loader->getIndexedClasses(); ``` -Caching .[#toc-caching] ------------------------ +Caching +------- -RobotLoader ist sehr schnell, weil er den Cache geschickt nutzt. +RobotLoader ist sehr schnell, da er den Cache geschickt nutzt. -Wenn Sie mit ihm entwickeln, bemerken Sie praktisch nicht, dass er im Hintergrund läuft. Der Cache wird ständig aktualisiert, da er weiß, dass Klassen und Dateien erstellt, gelöscht, umbenannt usw. werden können. Und es scannt nicht wiederholt unveränderte Dateien. +Während der Entwicklung merkt man praktisch nicht, dass er im Hintergrund läuft. Er aktualisiert den Cache kontinuierlich, da er davon ausgeht, dass Klassen und Dateien entstehen, verschwinden, umbenannt werden usw. Und er scannt Dateien, die sich nicht geändert haben, nicht erneut. -Beim Einsatz auf einem Produktionsserver empfiehlt es sich hingegen, die Cache-Aktualisierung mit `$loader->setAutoRefresh(false)` zu deaktivieren (dies geschieht automatisch in der Nette-Anwendung), da sich die Dateien nicht ändern. Gleichzeitig ist es notwendig, den Cache zu **leeren**, wenn eine neue Version auf das Hosting hochgeladen wird. +Beim Einsatz auf einem Produktionsserver empfehlen wir jedoch, die Cache-Aktualisierung mit `$loader->setAutoRefresh(false)` zu deaktivieren (in einer Nette-Anwendung geschieht dies automatisch), da sich die Dateien nicht ändern. Gleichzeitig ist es dann notwendig, beim Hochladen einer neuen Version auf den Hosting-Server **den Cache zu löschen.** -Natürlich kann das anfängliche Scannen von Dateien, wenn der Cache noch nicht vorhanden ist, bei größeren Anwendungen einige Sekunden dauern. RobotLoader verfügt über einen eingebauten Schutz gegen "Cache-Stampede":https://en.wikipedia.org/wiki/Cache_stampede. -Dabei handelt es sich um eine Situation, in der der Produktionsserver eine große Anzahl gleichzeitiger Anfragen erhält, und da der Cache von RobotLoader noch nicht vorhanden ist, würden alle mit dem Scannen der Dateien beginnen. Dies führt zu einer hohen CPU- und Dateisystemauslastung. -Glücklicherweise funktioniert RobotLoader so, dass bei mehreren gleichzeitigen Anfragen nur der erste Thread die Dateien indiziert und einen Cache erstellt, die anderen warten und verwenden dann den Cache. +Das anfängliche Scannen von Dateien, wenn der Cache noch nicht existiert, kann bei umfangreicheren Anwendungen natürlich einen Moment dauern. RobotLoader verfügt über eine integrierte Prävention gegen "Cache Stampede":https://en.wikipedia.org/wiki/Cache_stampede. Dies ist eine Situation, in der auf einem Produktionsserver eine größere Anzahl gleichzeitiger Anfragen eingeht, die RobotLoader starten, und da der Cache noch nicht existiert, würden alle beginnen, die Dateien zu scannen. Dies würde den Server unverhältnismäßig belasten. Glücklicherweise funktioniert RobotLoader so, dass bei mehreren gleichzeitigen Anfragen nur der erste Thread die Dateien indiziert, den Cache erstellt, die anderen warten und anschließend den Cache nutzen. -PSR-4 .[#toc-psr-4] -------------------- +PSR-4 +----- -Heute kann Composer für das [Autoloading |best-practices:composer#autoloading] gemäß PSR-4 verwendet werden. Einfach gesagt handelt es sich um ein System, bei dem die Namensräume und Klassennamen der Verzeichnisstruktur und den Dateinamen entsprechen, d.h. `App\Router\RouterFactory` befindet sich in der Datei `/path/to/App/Router/RouterFactory.php`. +Heute kann für das [Autoloading Composer verwendet werden |best-practices:composer#Autoloading], wenn PSR-4 eingehalten wird. Vereinfacht gesagt handelt es sich um ein System, bei dem Namensräume und Klassennamen der Verzeichnisstruktur und den Dateinamen entsprechen, d. h. z. B. `App\Core\RouterFactory` befindet sich in der Datei `/path/to/App/Core/RouterFactory.php`. -RobotLoader ist nicht an eine feste Struktur gebunden und ist daher in Situationen nützlich, in denen die Verzeichnisstruktur in PHP nicht als Namespaces ausgelegt ist, oder wenn Sie eine Anwendung entwickeln, die solche Konventionen historisch nicht verwendet hat. Es ist auch möglich, beide Loader zusammen zu verwenden. +RobotLoader ist nicht an eine feste Struktur gebunden und eignet sich daher für Situationen, in denen es Ihnen nicht ganz passt, eine Verzeichnisstruktur zu haben, die wie die Namensräume in PHP aufgebaut ist, oder wenn Sie eine Anwendung entwickeln, die historisch solche Konventionen nicht verwendet. Es ist auch möglich, beide Loader zusammen zu verwenden. {{leftbar: nette:@menu-topics}} +{{sitename: Nette Dokumentation}} diff --git a/robot-loader/el/@home.texy b/robot-loader/el/@home.texy index 4f037f2c0e..dc8bcc0baa 100644 --- a/robot-loader/el/@home.texy +++ b/robot-loader/el/@home.texy @@ -1,17 +1,20 @@ -RobotLoader: Αυτόματη φόρτωση κλάσης -************************************ +Nette RobotLoader +***************** <div class=perex> -Το RobotLoader είναι ένα εργαλείο που σας δίνει την άνεση της αυτοματοποιημένης φόρτωσης κλάσεων για ολόκληρη την εφαρμογή σας, συμπεριλαμβανομένων των βιβλιοθηκών τρίτων κατασκευαστών. +Το RobotLoader είναι ένα εργαλείο που σας εξασφαλίζει την άνεση της αυτόματης φόρτωσης κλάσεων για ολόκληρη την εφαρμογή σας, συμπεριλαμβανομένων των βιβλιοθηκών τρίτων. -- απαλλαγείτε από όλα `require` -- φορτώνονται μόνο τα απαραίτητα σενάρια -- δεν απαιτεί αυστηρές συμβάσεις ονοματοδοσίας καταλόγων ή αρχείων +- θα απαλλαγούμε από όλα τα `require` +- θα φορτώνονται μόνο τα απαραίτητα scripts +- δεν απαιτεί αυστηρές συμβάσεις ονομασίας καταλόγων ή αρχείων +- εξαιρετικά γρήγορο +- καμία χειροκίνητη ενημέρωση της cache, όλα γίνονται αυτόματα +- ώριμη, σταθερή και ευρέως χρησιμοποιούμενη βιβλιοθήκη </div> -Μπορούμε λοιπόν να ξεχάσουμε αυτά τα περίφημα μπλοκ κώδικα: +Μπορούμε λοιπόν να ξεχάσουμε αυτά τα γνωστά μπλοκ κώδικα: ```php require_once 'Utils/Page.php'; @@ -21,46 +24,55 @@ require_once 'Utils/Paginator.php'; ``` -Εγκατάσταση .[#toc-installation] --------------------------------- +Εγκατάσταση +----------- -Κατεβάστε και εγκαταστήστε το πακέτο χρησιμοποιώντας το [Composer |best-practices:composer]: +Μπορείτε να κατεβάσετε το RobotLoader ως [ένα αυτόνομο αρχείο `RobotLoader.php` |https://github.com/nette/robot-loader/raw/standalone/src/RobotLoader/RobotLoader.php], το οποίο εισάγετε με `require` στο script σας και αμέσως έχετε διαθέσιμο άνετο autoloading για ολόκληρη την εφαρμογή. + +```php +require '/path/to/RobotLoader.php'; + +$loader = new Nette\Loaders\RobotLoader; +// ... +``` + +Αν χτίζετε μια εφαρμογή που χρησιμοποιεί [Composer|best-practices:composer], μπορείτε να το εγκαταστήσετε μέσω αυτού: ```shell composer require nette/robot-loader ``` -Χρήση .[#toc-usage] -------------------- +Χρήση +----- -Όπως το ρομπότ της Google ανιχνεύει και ευρετηριάζει ιστότοπους, το [RobotLoader |api:Nette\Loaders\RobotLoader] ανιχνεύει όλα τα σενάρια PHP και καταγράφει ποιες κλάσεις και διεπαφές βρέθηκαν σε αυτά. Αυτές οι εγγραφές αποθηκεύονται στη συνέχεια στην κρυφή μνήμη και χρησιμοποιούνται κατά τη διάρκεια όλων των επόμενων αιτήσεων. Απλά πρέπει να καθορίσετε ποιοι κατάλογοι θα ευρετηριαστούν και πού θα αποθηκευτεί η κρυφή μνήμη: +Παρόμοια με τον τρόπο που το ρομπότ της Google διασχίζει και ευρετηριάζει ιστοσελίδες, έτσι και το [RobotLoader |api:Nette\Loaders\RobotLoader] διασχίζει όλα τα PHP scripts και καταγράφει ποιες κλάσεις, interfaces, traits και enums βρήκε σε αυτά. Στη συνέχεια, αποθηκεύει τα αποτελέσματα της έρευνας στην cache και τα χρησιμοποιεί στο επόμενο request. Αρκεί λοιπόν να καθορίσετε ποιους καταλόγους πρέπει να διασχίσει και πού να αποθηκεύσει την cache: ```php $loader = new Nette\Loaders\RobotLoader; -// κατάλογοι που θα ευρετηριαστεί από το RobotLoader (συμπεριλαμβανομένων των υποκαταλόγων) +// κατάλογοι που θα ευρετηριάσει ο RobotLoader (συμπεριλαμβανομένων των υποκαταλόγων) $loader->addDirectory(__DIR__ . '/app'); $loader->addDirectory(__DIR__ . '/libs'); -// χρήση του καταλόγου 'temp' για την κρυφή μνήμη +// ορίζουμε την αποθήκευση στην cache στον κατάλογο 'temp' $loader->setTempDirectory(__DIR__ . '/temp'); -$loader->register(); // Εκτέλεση του RobotLoader +$loader->register(); // εκκινούμε τον RobotLoader ``` -Και αυτό είναι όλο. Από εδώ και στο εξής, δεν χρειάζεται να χρησιμοποιείτε το `require`. Υπέροχα, έτσι δεν είναι; +Και αυτό είναι όλο, από εδώ και στο εξής δεν χρειάζεται να χρησιμοποιούμε `require`. Υπέροχα! -Όταν το RobotLoader συναντά διπλότυπο όνομα κλάσης κατά τη διάρκεια της ευρετηρίασης, πετάει μια εξαίρεση και σας ενημερώνει σχετικά. Ο RobotLoader ενημερώνει επίσης αυτόματα την κρυφή μνήμη όταν πρέπει να φορτώσει μια κλάση που δεν γνωρίζει. Συνιστούμε να το απενεργοποιήσετε αυτό στους διακομιστές παραγωγής, δείτε την ενότητα [Caching |#Caching]. +Αν το RobotLoader συναντήσει ένα διπλότυπο όνομα κλάσης κατά την ευρετηρίαση, ρίχνει μια εξαίρεση και σας ενημερώνει γι' αυτό. Το RobotLoader ενημερώνει επίσης αυτόματα την cache όταν πρέπει να φορτώσει μια κλάση που δεν γνωρίζει. Συνιστούμε να απενεργοποιήσετε αυτή τη λειτουργία στους διακομιστές παραγωγής, βλ. [#Caching]. -Αν θέλετε ο RobotLoader να παραλείψει κάποιους καταλόγους, χρησιμοποιήστε το `$loader->excludeDirectory('temp')` (μπορεί να κληθεί πολλές φορές ή μπορείτε να περάσετε πολλούς καταλόγους). +Αν θέλετε το RobotLoader να παραλείψει κάποιους καταλόγους, χρησιμοποιήστε το `$loader->excludeDirectory('temp')` (μπορεί να κληθεί πολλαπλές φορές ή να περάσετε περισσότερους καταλόγους). -Από προεπιλογή, ο RobotLoader αναφέρει σφάλματα σε αρχεία PHP με την εμφάνιση εξαιρέσεων `ParseError`. Μπορεί να απενεργοποιηθεί μέσω του `$loader->reportParseErrors(false)`. +Από προεπιλογή, το RobotLoader αναφέρει σφάλματα στα αρχεία PHP ρίχνοντας την εξαίρεση `ParseError`. Αυτό μπορεί να κατασταλεί χρησιμοποιώντας το `$loader->reportParseErrors(false)`. -Εφαρμογή Nette .[#toc-nette-application] ----------------------------------------- +Εφαρμογή Nette +-------------- -Μέσα στην εφαρμογή Nette, όπου το `$configurator` χρησιμοποιείται στο `Bootstrap.php`, μπορείτε να ρυθμίσετε το RobotLoader με αυτόν τον τρόπο: +Μέσα σε μια εφαρμογή Nette, όπου χρησιμοποιείται το αντικείμενο `$configurator` στο αρχείο εκκίνησης `Bootstrap.php`, η σύνταξη μπορεί να απλοποιηθεί: ```php $configurator = new Nette\Bootstrap\Configurator; @@ -73,57 +85,58 @@ $configurator->createRobotLoader() ``` -PHP Files Analyzer .[#toc-php-files-analyzer] ---------------------------------------------- +Αναλυτής αρχείων PHP +-------------------- -Το RobotLoader μπορεί επίσης να χρησιμοποιηθεί καθαρά για την εύρεση κλάσεων, διεπαφών και χαρακτηριστικών σε αρχεία PHP **χωρίς** να χρησιμοποιεί τη δυνατότητα αυτόματης φόρτωσης: +Το RobotLoader μπορεί επίσης να χρησιμοποιηθεί καθαρά για την αναζήτηση κλάσεων, interfaces, traits και enums σε αρχεία PHP **χωρίς** τη χρήση της λειτουργίας autoloading: ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); -// Σαρώνει καταλόγους για κλάσεις / διεπαφές / γνωρίσματα +// σαρώνει τους καταλόγους για κλάσεις / interfaces / traits / enums $loader->rebuild(); -// Επιστρέφει πίνακα με ζεύγη κλάσεων => ονόματα αρχείων +// επιστρέφει έναν πίνακα ζευγών κλάση => όνομα αρχείου $res = $loader->getIndexedClasses(); ``` -Ακόμα και με μια τέτοια χρήση, μπορείτε να χρησιμοποιήσετε την κρυφή μνήμη. Ως αποτέλεσμα, τα μη τροποποιημένα αρχεία δεν θα αναλύονται επανειλημμένα κατά την εκ νέου σάρωση: +Ακόμη και σε αυτή τη χρήση, μπορείτε να χρησιμοποιήσετε την cache. Χάρη σε αυτό, κατά την επαναλαμβανόμενη σάρωση, τα αμετάβλητα αρχεία δεν θα αναλυθούν ξανά: ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); + +// ορίζουμε την αποθήκευση στην cache στον κατάλογο 'temp' $loader->setTempDirectory(__DIR__ . '/temp'); -// Σαρώνει καταλόγους χρησιμοποιώντας μια κρυφή μνήμη +// σαρώνει τους καταλόγους χρησιμοποιώντας την cache $loader->refresh(); -// Επιστρέφει πίνακα ζευγών κλάσης => όνομα αρχείου +// επιστρέφει έναν πίνακα ζευγών κλάση => όνομα αρχείου $res = $loader->getIndexedClasses(); ``` -Caching .[#toc-caching] ------------------------ +Caching +------- -Το RobotLoader είναι πολύ γρήγορο επειδή χρησιμοποιεί έξυπνα την κρυφή μνήμη. +Το RobotLoader είναι πολύ γρήγορο γιατί χρησιμοποιεί έξυπνα την cache. -Όταν αναπτύσσετε με αυτό, δεν έχετε σχεδόν καμία ιδέα ότι εκτελείται στο παρασκήνιο. Ενημερώνει συνεχώς την κρυφή μνήμη επειδή γνωρίζει ότι κλάσεις και αρχεία μπορούν να δημιουργηθούν, να διαγραφούν, να μετονομαστούν κ.λπ. Και δεν σαρώνει επανειλημμένα μη τροποποιημένα αρχεία. +Κατά την ανάπτυξη, πρακτικά δεν έχετε ιδέα ότι τρέχει στο παρασκήνιο. Ενημερώνει συνεχώς την cache, γιατί υπολογίζει ότι οι κλάσεις και τα αρχεία μπορούν να δημιουργηθούν, να εξαφανιστούν, να μετονομαστούν, κ.λπ. Και δεν σαρώνει επανειλημμένα αρχεία που δεν έχουν αλλάξει. -Όταν χρησιμοποιείται σε έναν διακομιστή παραγωγής, από την άλλη πλευρά, συνιστούμε να απενεργοποιήσετε την ενημέρωση της κρυφής μνήμης χρησιμοποιώντας το `$loader->setAutoRefresh(false)` (αυτό γίνεται αυτόματα στην εφαρμογή Nette), επειδή τα αρχεία δεν αλλάζουν. Ταυτόχρονα, είναι απαραίτητο να **καθαρίσετε την προσωρινή μνήμη cache** όταν ανεβάζετε μια νέα έκδοση στη φιλοξενία. +Κατά την ανάπτυξη σε διακομιστή παραγωγής, αντίθετα, συνιστούμε να απενεργοποιήσετε την ενημέρωση της cache χρησιμοποιώντας το `$loader->setAutoRefresh(false)` (στην Εφαρμογή Nette αυτό γίνεται αυτόματα), επειδή τα αρχεία δεν αλλάζουν. Ταυτόχρονα, είναι απαραίτητο **να διαγράψετε την cache** κατά το ανέβασμα μιας νέας έκδοσης στο hosting. -Φυσικά, η αρχική σάρωση των αρχείων, όταν η προσωρινή μνήμη δεν υπάρχει ήδη, μπορεί να διαρκέσει μερικά δευτερόλεπτα για μεγαλύτερες εφαρμογές. Το RobotLoader διαθέτει ενσωματωμένη πρόληψη κατά του "αφηνιασμού της κρυφής μνήμης":https://en.wikipedia.org/wiki/Cache_stampede. -Πρόκειται για μια κατάσταση κατά την οποία ο διακομιστής παραγωγής λαμβάνει μεγάλο αριθμό ταυτόχρονων αιτήσεων και επειδή η προσωρινή μνήμη cache του RobotLoader δεν υπάρχει ακόμη, θα ξεκινούσαν όλες να σαρώνουν τα αρχεία. Πράγμα που αυξάνει τη χρήση της CPU και του συστήματος αρχείων. -Ευτυχώς, το RobotLoader λειτουργεί με τέτοιο τρόπο ώστε για πολλαπλές ταυτόχρονες αιτήσεις, μόνο το πρώτο νήμα ευρετηριάζει τα αρχεία, δημιουργεί μια κρυφή μνήμη, οι υπόλοιποι περιμένουν και στη συνέχεια χρησιμοποιούν την κρυφή μνήμη. +Η αρχική σάρωση των αρχείων, όταν η cache δεν υπάρχει ακόμα, μπορεί φυσικά να διαρκέσει λίγο για μεγαλύτερες εφαρμογές. Το RobotLoader έχει ενσωματωμένη πρόληψη κατά του "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Πρόκειται για μια κατάσταση όπου ένας μεγάλος αριθμός ταυτόχρονων requests συναντιούνται στον διακομιστή παραγωγής, τα οποία εκκινούν το RobotLoader, και επειδή η cache δεν υπάρχει ακόμα, όλα θα άρχιζαν να σαρώνουν αρχεία. Κάτι που θα επιβάρυνε δυσανάλογα τον διακομιστή. Ευτυχώς, το RobotLoader λειτουργεί έτσι ώστε, σε περίπτωση πολλαπλών ταυτόχρονων requests, μόνο το πρώτο νήμα ευρετηριάζει τα αρχεία, δημιουργεί την cache, τα υπόλοιπα περιμένουν και στη συνέχεια χρησιμοποιούν την cache. -PSR-4 .[#toc-psr-4] -------------------- +PSR-4 +----- -Σήμερα, το Composer μπορεί να χρησιμοποιηθεί για [αυτόματη φόρτωση |best-practices:composer#autoloading] σύμφωνα με το PSR-4. Με απλά λόγια, πρόκειται για ένα σύστημα όπου τα namespaces και τα ονόματα των κλάσεων αντιστοιχούν στη δομή των καταλόγων και των ονομάτων των αρχείων, δηλαδή το `App\Router\RouterFactory` βρίσκεται στο αρχείο `/path/to/App/Router/RouterFactory.php`. +Σήμερα, μπορείτε να [χρησιμοποιήσετε το Composer για autoloading |best-practices:composer#Autoloading] τηρώντας το PSR-4. Με απλά λόγια, πρόκειται για ένα σύστημα όπου τα namespaces και τα ονόματα των κλάσεων αντιστοιχούν στη δομή των καταλόγων και στα ονόματα των αρχείων, δηλαδή π.χ. το `App\Core\RouterFactory` θα βρίσκεται στο αρχείο `/path/to/App/Core/RouterFactory.php`. -Το RobotLoader δεν συνδέεται με καμία σταθερή δομή, επομένως, είναι χρήσιμο σε περιπτώσεις όπου δεν σας βολεύει να έχετε τη δομή καταλόγου σχεδιασμένη ως namespaces στην PHP, ή όταν αναπτύσσετε μια εφαρμογή που ιστορικά δεν έχει χρησιμοποιήσει τέτοιες συμβάσεις. Είναι επίσης δυνατό να χρησιμοποιήσετε και τους δύο φορτωτές μαζί. +Το RobotLoader δεν είναι συνδεδεμένο με καμία σταθερή δομή, γι' αυτό ταιριάζει σε καταστάσεις όπου δεν σας ταιριάζει απόλυτα να έχετε την ίδια σχεδιασμένη δομή καταλόγων με τα namespaces στην PHP, ή όταν αναπτύσσετε μια εφαρμογή που ιστορικά δεν χρησιμοποιεί τέτοιες συμβάσεις. Είναι επίσης δυνατό να χρησιμοποιείτε και τους δύο loaders μαζί. {{leftbar: nette:@menu-topics}} +{{sitename: Nette Τεκμηρίωση}} diff --git a/robot-loader/en/@home.texy b/robot-loader/en/@home.texy index 028013bf86..1cdfe209cb 100644 --- a/robot-loader/en/@home.texy +++ b/robot-loader/en/@home.texy @@ -1,17 +1,20 @@ -RobotLoader: Class Autoloading -****************************** +Nette RobotLoader +***************** <div class=perex> -RobotLoader is a tool that gives you comfort of automated class loading for your entire application including third-party libraries. +RobotLoader is a tool that provides the convenience of automatic class loading for your entire application, including third-party libraries. -- get rid of all `require` -- only necessary scripts are loaded -- does not require strict directory or file naming conventions +- Eliminates all `require` statements +- Only necessary scripts are loaded +- Does not require strict naming conventions for directories or files +- Extremely fast +- No manual cache updates; everything happens automatically +- Mature, stable, and widely used library </div> -So we can forget about those famous code blocks: +Thus, we can forget about these familiar code blocks: ```php require_once 'Utils/Page.php'; @@ -24,7 +27,16 @@ require_once 'Utils/Paginator.php'; Installation ------------ -Download and install the package using [Composer|best-practices:composer]: +You can download RobotLoader as a [single standalone file `RobotLoader.php` |https://github.com/nette/robot-loader/raw/standalone/src/RobotLoader/RobotLoader.php], include it using `require` in your script, and instantly enjoy comfortable autoloading for the entire application. + +```php +require '/path/to/RobotLoader.php'; + +$loader = new Nette\Loaders\RobotLoader; +// ... +``` + +If you're building an application using [Composer|best-practices:composer], you can install it via: ```shell composer require nette/robot-loader @@ -34,33 +46,33 @@ composer require nette/robot-loader Usage ----- -Like the Google robot crawls and indexes websites, [RobotLoader |api:Nette\Loaders\RobotLoader] crawls all PHP scripts and records what classes and interfaces were found in them. These records are then saved in cache and used during all subsequent requests. You just need to specify what directories to index and where to save the cache: +Similar to how the Google robot crawls and indexes web pages, [RobotLoader |api:Nette\Loaders\RobotLoader] traverses all PHP scripts and records which classes, interfaces, traits, and enums it found. It then stores these findings in a cache and uses them for subsequent requests. You just need to specify which directories it should scan and where to store the cache: ```php $loader = new Nette\Loaders\RobotLoader; -// directories to be indexed by RobotLoader (including subdirectories) +// Directories for RobotLoader to index (including subdirectories) $loader->addDirectory(__DIR__ . '/app'); $loader->addDirectory(__DIR__ . '/libs'); -// use 'temp' directory for cache +// Set caching to the 'temp' directory $loader->setTempDirectory(__DIR__ . '/temp'); -$loader->register(); // Run the RobotLoader +$loader->register(); // Activate RobotLoader ``` -And that's all. From now on, you don't need to use `require`. Great, isn't it? +And that's it! From this point on, you don't need to use `require`. Awesome! -When RobotLoader encounters duplicate class name during indexing, it throws an exception and informs you about it. RobotLoader also automatically updates the cache when it has to load a class it doesn't know. We recommend disabling this on production servers, see [#Caching]. +If RobotLoader encounters a duplicate class name during indexing, it will throw an exception and notify you. RobotLoader also automatically updates the cache when it needs to load a class it doesn't know. We recommend disabling this on production servers; see [#Caching]. -If you want RobotLoader to skip some directories, use `$loader->excludeDirectory('temp')` (it can be called multiple times or you can pass multiple directories). +If you want RobotLoader to skip certain directories, use `$loader->excludeDirectory('temp')` (can be called multiple times or pass multiple directories). -By default, RobotLoader reports errors in PHP files by throwing exception `ParseError`. It can be disabled via `$loader->reportParseErrors(false)`. +By default, RobotLoader reports errors in PHP files by throwing a `ParseError` exception. This can be suppressed using `$loader->reportParseErrors(false)`. Nette Application ----------------- -Inside Nette application, where `$configurator` is used in `Bootstrap.php`, you can setup RobotLoader this way: +Inside a Nette Application, where the `$configurator` object is used in the `Bootstrap.php` boot file, the setup can be simplified: ```php $configurator = new Nette\Bootstrap\Configurator; @@ -76,30 +88,32 @@ $configurator->createRobotLoader() PHP Files Analyzer ------------------ -RobotLoader can also be used purely to find classes, interfaces, and trait in PHP files **without** using the autoloading feature: +RobotLoader can also be used purely for finding classes, interfaces, traits, and enums in PHP files **without** using the autoloading function: ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); -// Scans directories for classes / interfaces / traits +// Scans directories for classes/interfaces/traits/enums $loader->rebuild(); -// Returns array of class => filename pairs +// Returns an array of class => filename pairs $res = $loader->getIndexedClasses(); ``` -Even with such use, you can use the cache. As a result, unmodified files will not be repeatedly analyzed when rescanning: +Even with such usage, you can utilize caching. This ensures that unchanged files won't be rescanned: ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); + +// Set caching to the 'temp' directory $loader->setTempDirectory(__DIR__ . '/temp'); -// Scans directories using a cache +// Scans directories using cache $loader->refresh(); -// Returns array of class => filename pairs +// Returns an array of class => filename pairs $res = $loader->getIndexedClasses(); ``` @@ -107,23 +121,22 @@ $res = $loader->getIndexedClasses(); Caching ------- -RobotLoader is very fast because it cleverly uses the cache. +RobotLoader is very fast because it cleverly uses caching. -When developing with it, you have practically no idea that it runs on the background. It continuously updates the cache because it knows that classes and files can be created, deleted, renamed, etc. And it doesn't repeatedly scan unmodified files. +During development, you hardly notice it running in the background. It continuously updates its cache, anticipating that classes and files might be created, deleted, renamed, etc. And it doesn't rescan files that haven't changed. -When used on a production server, on the other hand, we recommend disabling the cache update using `$loader->setAutoRefresh(false)` (this is done automatically in the Nette Application), because the files are not changing. At the same time, it is necessary to **clear the cache** when uploading a new version on the hosting. +On a production server, conversely, we recommend disabling cache updates using `$loader->setAutoRefresh(false)` (this happens automatically in a Nette Application), because files don't change. At the same time, it's necessary to **clear the cache** when uploading a new version to the hosting environment. -Of course, the initial scanning of files, when the cache does not already exist, may take a few seconds for larger applications. RobotLoader has built-in prevention against "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. -This is a situation where production server receives a large number of concurrent requests and because RobotLoader's cache does not yet exist, they would all start scanning the files. Which spikes CPU and filesystem usage. -Fortunately, RobotLoader works in such a way that for multiple concurrent requests, only the first thread indexes the files, creates a cache, the others wait, and then use the cache. +The initial scanning of files, when the cache doesn't exist yet, can naturally take a moment for larger applications. RobotLoader has built-in prevention against [cache stampede|https://en.wikipedia.org/wiki/Cache_stampede]. This is a situation where a large number of concurrent requests on a production server trigger RobotLoader, and since the cache doesn't exist yet, they would all start scanning files, potentially overloading the server. Fortunately, RobotLoader works such that with multiple concurrent requests, only the first thread indexes the files and creates the cache, while the others wait and then use the generated cache. PSR-4 ----- -Today, Composer can be used for [autoloading|best-practices:composer#autoloading] in compliance with PSR-4. Simply saying, it is a system where the namespaces and class names correspond to the directory structure and file names, ie `App\Router\RouterFactory` is located in the file `/path/to/App/Router/RouterFactory.php`. +Nowadays, you can use [Composer for autoloading |best-practices:composer#Autoloading] while adhering to PSR-4. Simply put, it's a system where namespaces and class names correspond to the directory structure and file names, e.g., `App\Core\RouterFactory` will be in the file `/path/to/App/Core/RouterFactory.php`. -RobotLoader is not tied to any fixed structure, therefore, it is useful in situations where it does not suit you to have the directory structure designed as namespaces in PHP, or when you are developing an application that has historically not used such conventions. It is also possible to use both loaders together. +RobotLoader isn't tied to any fixed structure, so it's useful in situations where you don't want the directory structure to exactly match the PHP namespaces, or when developing an application that historically doesn't use such conventions. It's also possible to use both loaders together. {{leftbar: nette:@menu-topics}} +{{sitename: Nette Documentation}} diff --git a/robot-loader/es/@home.texy b/robot-loader/es/@home.texy index 365dd5c96f..7928a52f91 100644 --- a/robot-loader/es/@home.texy +++ b/robot-loader/es/@home.texy @@ -1,17 +1,20 @@ -RobotLoader: Carga automática de clases -*************************************** +Nette RobotLoader +***************** <div class=perex> -RobotLoader es una herramienta que le ofrece la comodidad de la carga automática de clases para toda su aplicación, incluidas las bibliotecas de terceros. +RobotLoader es una herramienta que le brinda la comodidad de cargar automáticamente clases para toda su aplicación, incluidas las bibliotecas de terceros. -- deshacerse de todos `require` -- sólo se cargan los scripts necesarios -- no requiere estrictas convenciones de nomenclatura de directorios o archivos +- nos deshacemos de todos los `require` +- solo se cargarán los scripts necesarios +- no requiere convenciones estrictas de nomenclatura de directorios o archivos +- extremadamente rápido +- sin actualizaciones manuales de caché, todo sucede automáticamente +- biblioteca madura, estable y ampliamente utilizada </div> -Así que podemos olvidarnos de los famosos bloques de código: +Podemos olvidarnos de estos bloques de código familiares: ```php require_once 'Utils/Page.php'; @@ -21,46 +24,55 @@ require_once 'Utils/Paginator.php'; ``` -Instalación .[#toc-installation] --------------------------------- +Instalación +----------- -Descargue e instale el paquete utilizando [Composer |best-practices:composer]: +Puede descargar RobotLoader como [un único archivo independiente `RobotLoader.php` |https://github.com/nette/robot-loader/raw/standalone/src/RobotLoader/RobotLoader.php], que incluye usando `require` en su script e inmediatamente tiene disponible un cómodo autoloading para toda la aplicación. + +```php +require '/path/to/RobotLoader.php'; + +$loader = new Nette\Loaders\RobotLoader; +// ... +``` + +Si está construyendo una aplicación que utiliza [Composer|best-practices:composer], puede instalarlo a través de él: ```shell composer require nette/robot-loader ``` -Utilización .[#toc-usage] -------------------------- +Uso +--- -Al igual que el robot de Google rastrea e indexa sitios web, [RobotLoader |api:Nette\Loaders\RobotLoader] rastrea todos los scripts PHP y registra qué clases e interfaces se encontraron en ellos. Estos registros se guardan en la caché y se utilizan durante todas las solicitudes posteriores. Sólo necesita especificar qué directorios indexar y dónde guardar la caché: +De manera similar a como el robot de Google rastrea e indexa páginas web, [RobotLoader |api:Nette\Loaders\RobotLoader] recorre todos los scripts PHP y registra qué clases, interfaces, traits y enums encontró en ellos. Luego guarda los resultados de la investigación en la caché y los usa en la siguiente solicitud. Simplemente especifique qué directorios debe recorrer y dónde guardar la caché: ```php $loader = new Nette\Loaders\RobotLoader; -// directorios a indexar por RobotLoader (incluyendo subdirectorios) +// directorios que RobotLoader debe indexar (incluidos subdirectorios) $loader->addDirectory(__DIR__ . '/app'); $loader->addDirectory(__DIR__ . '/libs'); -// usar directorio 'temp' para caché +// establecemos el almacenamiento en caché en el directorio 'temp' $loader->setTempDirectory(__DIR__ . '/temp'); -$loader->register(); // Ejecutar el RobotLoader +$loader->register(); // iniciamos RobotLoader ``` -Y eso es todo. A partir de ahora, no necesitará utilizar `require`. Estupendo, ¿verdad? +Y eso es todo, a partir de ahora no necesitamos usar `require`. ¡Genial! -Cuando RobotLoader encuentra un nombre de clase duplicado durante la indexación, lanza una excepción y te informa de ello. RobotLoader también actualiza automáticamente la caché cuando tiene que cargar una clase que no conoce. Recomendamos desactivar esto en los servidores de producción, ver [Caché |#Caching]. +Si RobotLoader encuentra un nombre de clase duplicado durante la indexación, lanzará una excepción y le informará al respecto. RobotLoader también actualiza automáticamente la caché cuando tiene que cargar una clase que no conoce. Recomendamos desactivar esto en servidores de producción, consulte [#Cache]. -Si quieres que RobotLoader se salte algunos directorios, utiliza `$loader->excludeDirectory('temp')` (se puede llamar varias veces o puedes pasar varios directorios). +Si desea que RobotLoader omita algunos directorios, use `$loader->excludeDirectory('temp')` (se puede llamar varias veces o pasar varios directorios). -Por defecto, RobotLoader informa de errores en archivos PHP lanzando la excepción `ParseError`. Se puede desactivar a través de `$loader->reportParseErrors(false)`. +De forma predeterminada, RobotLoader informa errores en archivos PHP lanzando una excepción `ParseError`. Esto se puede suprimir usando `$loader->reportParseErrors(false)`. -Aplicación Nette .[#toc-nette-application] ------------------------------------------- +Aplicación Nette +---------------- -Dentro de la aplicación Nette, donde `$configurator` se utiliza en `Bootstrap.php`, puede configurar RobotLoader de esta manera: +Dentro de una aplicación Nette, donde se utiliza el objeto `$configurator` en el archivo de arranque `Bootstrap.php`, la notación se puede simplificar: ```php $configurator = new Nette\Bootstrap\Configurator; @@ -73,57 +85,58 @@ $configurator->createRobotLoader() ``` -Analizador de archivos PHP .[#toc-php-files-analyzer] ------------------------------------------------------ +Analizador de archivos PHP +-------------------------- -RobotLoader también puede ser usado puramente para encontrar clases, interfaces y traits en archivos PHP **sin** usar la característica de autocarga: +RobotLoader también se puede usar puramente para buscar clases, interfaces, traits y enums en archivos PHP **sin** utilizar la función de autoloading: ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); -// Busca clases / interfaces / traits en los directorios +// busca clases / interfaces / traits / enums en los directorios $loader->rebuild(); -// Devuelve un array de pares clase => nombre de fichero +// devuelve un array de pares clase => nombre de archivo $res = $loader->getIndexedClasses(); ``` -Incluso con tal uso, puede utilizar la caché. Como resultado, los archivos no modificados no serán analizados repetidamente al volver a escanear: +Incluso con este uso, puede utilizar la caché. Gracias a esto, al volver a escanear, los archivos sin cambios no se analizarán repetidamente: ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); + +// establecemos el almacenamiento en caché en el directorio 'temp' $loader->setTempDirectory(__DIR__ . '/temp'); -// Escanea directorios usando una caché +// busca en los directorios usando la caché $loader->refresh(); -// Devuelve un array de pares clase => nombre de fichero +// devuelve un array de pares clase => nombre de archivo $res = $loader->getIndexedClasses(); ``` -Almacenamiento en caché .[#toc-caching] ---------------------------------------- +Caché +----- RobotLoader es muy rápido porque utiliza inteligentemente la caché. -Cuando desarrollas con él, prácticamente no tienes ni idea de que se ejecuta en segundo plano. Actualiza continuamente la caché porque sabe que las clases y archivos pueden ser creados, borrados, renombrados, etc. Y no escanea repetidamente archivos no modificados. +Durante el desarrollo, prácticamente no se da cuenta de que se está ejecutando en segundo plano. Actualiza continuamente la caché, porque asume que las clases y los archivos pueden crearse, desaparecer, renombrarse, etc. Y no vuelve a escanear archivos que no han cambiado. -En cambio, cuando se utiliza en un servidor de producción, se recomienda desactivar la actualización de la caché mediante `$loader->setAutoRefresh(false)` (esto se hace automáticamente en la aplicación Nette), porque los ficheros no cambian. Al mismo tiempo, es necesario **limpiar la caché** al subir una nueva versión al hosting. +Al implementar en un servidor de producción, por el contrario, recomendamos desactivar la actualización de la caché usando `$loader->setAutoRefresh(false)` (en una aplicación Nette esto sucede automáticamente), porque los archivos no cambian. Al mismo tiempo, es necesario **eliminar la caché** al cargar una nueva versión en el hosting. -Por supuesto, el escaneo inicial de los archivos, cuando la caché no existe ya, puede tardar unos segundos para las aplicaciones más grandes. RobotLoader tiene incorporada una prevención contra la "estampida de:https://en.wikipedia.org/wiki/Cache_stampede caché". -Se trata de una situación en la que el servidor de producción recibe un gran número de peticiones simultáneas y, debido a que la caché de RobotLoader aún no existe, todas ellas comenzarían a escanear los archivos. Lo que dispara el uso de la CPU y del sistema de archivos. -Afortunadamente, RobotLoader funciona de tal manera que para múltiples peticiones concurrentes, sólo el primer hilo indexa los archivos, crea una caché, los demás esperan, y luego utilizan la caché. +El escaneo inicial de archivos, cuando la caché aún no existe, puede llevar un momento para aplicaciones más grandes. RobotLoader tiene una prevención incorporada contra la "estampida de caché":https://en.wikipedia.org/wiki/Cache_stampede. Esta es una situación en la que un mayor número de solicitudes simultáneas llegan al servidor de producción, inician RobotLoader y, como la caché aún no existe, todas comenzarían a escanear archivos. Lo que sobrecargaría indebidamente el servidor. Afortunadamente, RobotLoader funciona de tal manera que con múltiples solicitudes simultáneas, solo el primer hilo indexa los archivos, crea la caché, los demás esperan y luego usan la caché. -PSR-4 .[#toc-psr-4] -------------------- +PSR-4 +----- -En la actualidad, Composer puede utilizarse para la [autolectura |best-practices:composer#autoloading] de conformidad con PSR-4. En pocas palabras, se trata de un sistema en el que los espacios de nombres y los nombres de las clases se corresponden con la estructura de directorios y los nombres de los archivos, es decir, `App\Router\RouterFactory` se encuentra en el archivo `/path/to/App/Router/RouterFactory.php`. +Hoy en día, se puede [usar Composer para autoloading |best-practices:composer#Autoloading] siguiendo PSR-4. En pocas palabras, es un sistema donde los espacios de nombres y los nombres de las clases corresponden a la estructura de directorios y los nombres de los archivos, es decir, `App\Core\RouterFactory` estará en el archivo `/path/to/App/Core/RouterFactory.php`. -RobotLoader no está atado a ninguna estructura fija, por lo tanto, es útil en situaciones donde no le conviene tener la estructura de directorios diseñada como namespaces en PHP, o cuando está desarrollando una aplicación que históricamente no ha usado tales convenciones. También es posible usar ambos cargadores juntos. +RobotLoader no está vinculado a ninguna estructura fija, por lo que es útil en situaciones en las que no le conviene tener una estructura de directorios diseñada de la misma manera que los espacios de nombres en PHP, o cuando desarrolla una aplicación que históricamente no utiliza tales convenciones. También es posible usar ambos loaders juntos. {{leftbar: nette:@menu-topics}} +{{sitename: Nette Documentación}} diff --git a/robot-loader/fr/@home.texy b/robot-loader/fr/@home.texy index 89109f8308..adf3c98fa1 100644 --- a/robot-loader/fr/@home.texy +++ b/robot-loader/fr/@home.texy @@ -1,17 +1,20 @@ -RobotLoader : Auto-chargement des classes -***************************************** +Nette RobotLoader +***************** <div class=perex> -RobotLoader est un outil qui vous offre le confort du chargement automatique des classes pour l'ensemble de votre application, y compris les bibliothèques tierces. +RobotLoader est un outil qui vous assure le confort du chargement automatique des classes pour toute votre application, y compris les bibliothèques tierces. -- se débarrasser de tout `require` -- seuls les scripts nécessaires sont chargés -- ne nécessite pas de conventions strictes en matière de nom de répertoire ou de fichier +- débarrassez-vous de tous les `require` +- seuls les scripts nécessaires seront chargés +- ne nécessite pas de conventions strictes de nommage des répertoires ou des fichiers +- extrêmement rapide +- aucune mise à jour manuelle du cache, tout se fait automatiquement +- bibliothèque mature, stable et largement utilisée </div> -On peut donc oublier ces fameux blocs de code : +Nous pouvons donc oublier ces blocs de code familiers : ```php require_once 'Utils/Page.php'; @@ -21,46 +24,55 @@ require_once 'Utils/Paginator.php'; ``` -Installation .[#toc-installation] ---------------------------------- +Installation +------------ -Téléchargez et installez le paquet en utilisant [Composer |best-practices:composer]: +Vous pouvez télécharger RobotLoader comme [un seul fichier autonome `RobotLoader.php` |https://github.com/nette/robot-loader/raw/standalone/src/RobotLoader/RobotLoader.php], que vous incluez à l'aide de `require` dans votre script et vous disposez immédiatement d'un autoloading confortable pour toute l'application. + +```php +require '/path/to/RobotLoader.php'; + +$loader = new Nette\Loaders\RobotLoader; +// ... +``` + +Si vous construisez une application utilisant [Composer|best-practices:composer], vous pouvez l'installer via celui-ci : ```shell composer require nette/robot-loader ``` -Utilisation .[#toc-usage] -------------------------- +Utilisation +----------- -Tout comme le robot Google explore et indexe les sites Web, [RobotLoader |api:Nette\Loaders\RobotLoader] explore tous les scripts PHP et enregistre les classes et les interfaces qu'ils contiennent. Ces enregistrements sont ensuite sauvegardés dans le cache et utilisés lors de toutes les requêtes ultérieures. Il vous suffit de spécifier les répertoires à indexer et l'endroit où sauvegarder le cache : +De la même manière qu'un robot Google parcourt et indexe les pages web, [RobotLoader |api:Nette\Loaders\RobotLoader] parcourt tous les scripts PHP et enregistre les classes, interfaces, traits et enums qu'il y trouve. Il stocke ensuite les résultats de ses recherches dans un cache et les utilise lors de la prochaine requête. Il suffit donc de spécifier quels répertoires il doit parcourir et où stocker le cache : ```php $loader = new Nette\Loaders\RobotLoader; -// répertoires à indexer par RobotLoader (y compris les sous-répertoires) +// répertoires que RobotLoader doit indexer (y compris les sous-répertoires) $loader->addDirectory(__DIR__ . '/app'); $loader->addDirectory(__DIR__ . '/libs'); -// utiliser le répertoire 'temp' pour le cache +// nous définissons la mise en cache dans le répertoire 'temp' $loader->setTempDirectory(__DIR__ . '/temp'); -$loader->register(); // Exécution du RobotLoader +$loader->register(); // démarrons RobotLoader ``` -Et c'est tout. A partir de maintenant, vous n'avez plus besoin d'utiliser `require`. Super, n'est-ce pas ? +Et c'est tout, à partir de maintenant, nous n'avons plus besoin d'utiliser `require`. Génial ! -Lorsque RobotLoader rencontre un nom de classe en double pendant l'indexation, il lève une exception et vous en informe. RobotLoader met aussi automatiquement à jour le cache lorsqu'il doit charger une classe qu'il ne connaît pas. Nous recommandons de désactiver cette fonction sur les serveurs de production, voir [Mise en cache |#Caching]. +Si RobotLoader rencontre un nom de classe dupliqué lors de l'indexation, il lèvera une exception et vous en informera. RobotLoader met également à jour automatiquement le cache lorsqu'il doit charger une classe qu'il ne connaît pas. Nous recommandons de désactiver cela sur les serveurs de production, voir [#Mise en cache]. -Si vous voulez que RobotLoader saute certains répertoires, utilisez `$loader->excludeDirectory('temp')` (il peut être appelé plusieurs fois ou vous pouvez passer plusieurs répertoires). +Si vous souhaitez que RobotLoader ignore certains répertoires, utilisez `$loader->excludeDirectory('temp')` (peut être appelé plusieurs fois ou passer plusieurs répertoires). -Par défaut, RobotLoader signale les erreurs dans les fichiers PHP en lançant l'exception `ParseError`. Elle peut être désactivée via `$loader->reportParseErrors(false)`. +Par défaut, RobotLoader signale les erreurs dans les fichiers PHP en levant une exception `ParseError`. Cela peut être supprimé à l'aide de `$loader->reportParseErrors(false)`. -Application Nette .[#toc-nette-application] -------------------------------------------- +Application Nette +----------------- -Dans l'application Nette, où `$configurator` est utilisé dans `Bootstrap.php`, vous pouvez configurer RobotLoader de cette façon : +À l'intérieur d'une application Nette, où l'objet `$configurator` est utilisé dans le fichier de démarrage `Bootstrap.php`, l'écriture peut être simplifiée : ```php $configurator = new Nette\Bootstrap\Configurator; @@ -73,57 +85,58 @@ $configurator->createRobotLoader() ``` -Analyseur de fichiers PHP .[#toc-php-files-analyzer] ----------------------------------------------------- +Analyseur de fichiers PHP +------------------------- -RobotLoader peut aussi être utilisé uniquement pour trouver des classes, des interfaces et des traits dans les fichiers PHP **sans utiliser la fonction de chargement automatique : +RobotLoader peut également être utilisé uniquement pour rechercher des classes, interfaces, traits et enums dans des fichiers PHP **sans** utiliser la fonction d'autoloading : ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); -// Recherche des classes, interfaces et traits dans les répertoires. +// parcourt les répertoires à la recherche de classes / interfaces / traits / enums $loader->rebuild(); -// Retourne un tableau de paires classe => nom de fichier +// retourne un tableau de paires classe => nom de fichier $res = $loader->getIndexedClasses(); ``` -Même avec une telle utilisation, vous pouvez utiliser le cache. Ainsi, les fichiers non modifiés ne seront pas analysés à plusieurs reprises lors d'une nouvelle recherche : +Même avec une telle utilisation, vous pouvez utiliser le cache. Grâce à cela, lors d'une nouvelle analyse, les fichiers inchangés ne seront pas analysés à nouveau : ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); + +// nous définissons la mise en cache dans le répertoire 'temp' $loader->setTempDirectory(__DIR__ . '/temp'); -// Analyse des répertoires en utilisant un cache +// parcourt les répertoires en utilisant le cache $loader->refresh(); -// Retourne un tableau de paires classe => nom de fichier +// retourne un tableau de paires classe => nom de fichier $res = $loader->getIndexedClasses(); ``` -Mise en cache .[#toc-caching] ------------------------------ +Mise en cache +------------- RobotLoader est très rapide car il utilise intelligemment le cache. -Lorsque vous développez avec lui, vous n'avez pratiquement aucune idée qu'il fonctionne en arrière-plan. Il met continuellement à jour le cache car il sait que les classes et les fichiers peuvent être créés, supprimés, renommés, etc. Et il n'analyse pas de manière répétée les fichiers non modifiés. +Pendant le développement, vous ne réalisez pratiquement pas qu'il s'exécute en arrière-plan. Il met à jour continuellement son cache, car il s'attend à ce que des classes et des fichiers puissent être créés, supprimés, renommés, etc. Et il ne scanne pas à plusieurs reprises les fichiers qui n'ont pas changé. -En revanche, lorsqu'il est utilisé sur un serveur de production, nous recommandons de désactiver la mise à jour du cache à l'aide de `$loader->setAutoRefresh(false)` (cela se fait automatiquement dans l'application Nette), car les fichiers ne changent pas. En même temps, il est nécessaire de **nettoyer le cache** lors du téléchargement d'une nouvelle version sur l'hébergement. +Lors du déploiement sur un serveur de production, nous recommandons au contraire de désactiver la mise à jour du cache à l'aide de `$loader->setAutoRefresh(false)` (dans une application Nette, cela se fait automatiquement), car les fichiers ne changent pas. En même temps, il est alors nécessaire de **supprimer le cache** lors du téléversement d'une nouvelle version sur l'hébergement. -Bien sûr, le balayage initial des fichiers, lorsque le cache n'existe pas encore, peut prendre quelques secondes pour les applications plus importantes. RobotLoader a une prévention intégrée contre la "ruée vers le cache":https://en.wikipedia.org/wiki/Cache_stampede. -Il s'agit d'une situation dans laquelle le serveur de production reçoit un grand nombre de demandes simultanées et, comme le cache de RobotLoader n'existe pas encore, elles commencent toutes à analyser les fichiers. Ce qui fait monter en flèche l'utilisation du CPU et du système de fichiers. -Heureusement, RobotLoader fonctionne de telle manière que pour plusieurs demandes simultanées, seul le premier thread indexe les fichiers, crée un cache, les autres attendent, puis utilisent le cache. +L'analyse initiale des fichiers, lorsque le cache n'existe pas encore, peut naturellement prendre un certain temps pour les applications plus volumineuses. RobotLoader intègre une prévention contre le "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Il s'agit d'une situation où un grand nombre de requêtes simultanées arrivent sur le serveur de production, lancent RobotLoader, et comme le cache n'existe pas encore, elles commenceraient toutes à scanner les fichiers. Ce qui surchargerait indûment le serveur. Heureusement, RobotLoader fonctionne de telle sorte que lors de plusieurs requêtes simultanées, seul le premier thread indexe les fichiers, crée le cache, les autres attendent et utilisent ensuite le cache. -PSR-4 .[#toc-psr-4] -------------------- +PSR-4 +----- -Aujourd'hui, Composer peut être utilisé pour l'[autoloading |best-practices:composer#autoloading] en conformité avec PSR-4. En d'autres termes, il s'agit d'un système où les espaces de noms et les noms de classes correspondent à la structure des répertoires et aux noms de fichiers, c'est-à-dire que `App\Router\RouterFactory` est situé dans le fichier `/path/to/App/Router/RouterFactory.php`. +Aujourd'hui, il est possible d'[utiliser Composer pour l'autoloading |best-practices:composer#Autoloading] en respectant PSR-4. En termes simples, il s'agit d'un système où les espaces de noms et les noms de classes correspondent à la structure des répertoires et aux noms de fichiers, par exemple `App\Core\RouterFactory` sera dans le fichier `/path/to/App/Core/RouterFactory.php`. -RobotLoader n'est lié à aucune structure fixe, il est donc utile dans les situations où il ne vous convient pas d'avoir une structure de répertoire conçue comme des espaces de noms en PHP, ou lorsque vous développez une application qui n'a historiquement pas utilisé de telles conventions. Il est également possible d'utiliser les deux chargeurs ensemble. +RobotLoader n'est lié à aucune structure fixe, il est donc utile dans les situations où avoir une structure de répertoires conçue de la même manière que les espaces de noms en PHP ne vous convient pas tout à fait, ou lorsque vous développez une application qui n'utilise historiquement pas de telles conventions. Il est également possible d'utiliser les deux chargeurs ensemble. {{leftbar: nette:@menu-topics}} +{{sitename: Documentation Nette}} diff --git a/robot-loader/hu/@home.texy b/robot-loader/hu/@home.texy index c8538cea54..616b4fd2d1 100644 --- a/robot-loader/hu/@home.texy +++ b/robot-loader/hu/@home.texy @@ -1,17 +1,20 @@ -RobotLoader: Osztály automatikus betöltése -****************************************** +Nette RobotLoader +***************** <div class=perex> -A RobotLoader egy olyan eszköz, amely kényelmet biztosít az automatikus osztálybetöltés számára a teljes alkalmazásod számára, beleértve a harmadik féltől származó könyvtárakat is. +A RobotLoader egy eszköz, amely biztosítja az osztályok automatikus betöltésének kényelmét az egész alkalmazás számára, beleértve a harmadik féltől származó könyvtárakat is. -- megszabadul az összes `require` +- megszabadulunk minden `require`-től - csak a szükséges szkriptek töltődnek be -- nem igényel szigorú könyvtár- vagy fájlnevezési konvenciókat +- nem igényel szigorú elnevezési konvenciókat a könyvtárakra vagy fájlokra +- rendkívül gyors +- nincs szükség a gyorsítótár kézi frissítésére, minden automatikusan történik +- érett, stabil és széles körben használt könyvtár </div> -Tehát elfelejthetjük a híres kódblokkokat: +Tehát elfelejthetjük ezeket az ismert kódblokkokat: ```php require_once 'Utils/Page.php'; @@ -21,46 +24,55 @@ require_once 'Utils/Paginator.php'; ``` -Telepítés .[#toc-installation] ------------------------------- +Telepítés +--------- -Töltse le és telepítse a csomagot a [Composer |best-practices:composer] segítségével: +A RobotLoadert letöltheti [egy önálló `RobotLoader.php` fájlként |https://github.com/nette/robot-loader/raw/standalone/src/RobotLoader/RobotLoader.php], amelyet a `require` segítségével beilleszt a szkriptjébe, és máris rendelkezésére áll a kényelmes autoloading az egész alkalmazáshoz. + +```php +require '/path/to/RobotLoader.php'; + +$loader = new Nette\Loaders\RobotLoader; +// ... +``` + +Ha [Composer|best-practices:composer]-t használó alkalmazást épít, telepítheti vele: ```shell composer require nette/robot-loader ``` -Használat: .[#toc-usage] ------------------------- +Használat +--------- -Ahogy a Google robot feltérképezi és indexeli a weboldalakat, a [RobotLoader |api:Nette\Loaders\RobotLoader] is feltérképezi az összes PHP szkriptet, és rögzíti, hogy milyen osztályokat és interfészeket találtak bennük. Ezek a rekordok a gyorsítótárba kerülnek, és minden későbbi lekérdezés során felhasználásra kerülnek. Csak azt kell megadni, hogy milyen könyvtárakat indexeljen és hova mentse a gyorsítótárat: +Hasonlóan ahhoz, ahogy a Google robot végigjárja és indexeli a weboldalakat, a [RobotLoader |api:Nette\Loaders\RobotLoader] is végigmegy az összes PHP szkripten, és feljegyzi, mely osztályokat, interfészeket, traitteket és enumokat talált bennük. A kutatás eredményeit ezután a gyorsítótárba menti, és a következő kérésnél használja. Csak meg kell határozni, mely könyvtárakat kell átvizsgálnia, és hova mentse a gyorsítótárat: ```php $loader = new Nette\Loaders\RobotLoader; -// a RobotLoader által indexelendő könyvtárak (beleértve az alkönyvtárakat is) +// könyvtárak, amelyeket a RobotLoader indexeljen (beleértve az alkönyvtárakat is) $loader->addDirectory(__DIR__ . '/app'); $loader->addDirectory(__DIR__ . '/libs'); -// 'temp' könyvtár használata a gyorsítótárhoz +// beállítjuk a gyorsítótárazást a 'temp' könyvtárba $loader->setTempDirectory(__DIR__ . '/temp'); -$loader->register(); // RobotLoader futtatása +$loader->register(); // elindítjuk a RobotLoadert ``` -És ez minden. Mostantól kezdve nem kell a `require` címet használnod. Nagyszerű, nem igaz? +És ennyi, ettől a pillanattól kezdve nem kell `require`-t használnunk. Remek! -Ha a RobotLoader az indexelés során duplikált osztálynévvel találkozik, kivételt dob, és tájékoztatja Önt erről. A RobotLoader automatikusan frissíti a gyorsítótárat is, ha olyan osztályt kell betöltenie, amelyet nem ismer. Javasoljuk ennek kikapcsolását a produktív szervereken, lásd a [Caching |#Caching]. +Ha a RobotLoader indexelés közben duplikált osztálynévre bukkan, kivételt dob, és tájékoztatja Önt erről. A RobotLoader automatikusan frissíti a gyorsítótárat is, amikor olyan osztályt kell betöltenie, amelyet nem ismer. Ezt javasoljuk kikapcsolni az éles szervereken, lásd [##Gyorsítótárazás]. -Ha azt szeretné, hogy a RobotLoader kihagyjon néhány könyvtárat, használja a `$loader->excludeDirectory('temp')` címet (többször is meghívható, vagy több könyvtárat is átadhat). +Ha azt szeretné, hogy a RobotLoader kihagyjon néhány könyvtárat, használja a `$loader->excludeDirectory('temp')` parancsot (többször is hívható, vagy több könyvtárat is átadhat). -Alapértelmezés szerint a RobotLoader a PHP-fájlok hibáit a `ParseError` kivétel dobásával jelzi. Ez kikapcsolható a `$loader->reportParseErrors(false)` segítségével. +Alapértelmezés szerint a RobotLoader a PHP fájlokban lévő hibákat a `ParseError` kivétel dobásával jelenti. Ezt a `$loader->reportParseErrors(false)` segítségével lehet letiltani. -Nette alkalmazás .[#toc-nette-application] ------------------------------------------- +Nette alkalmazás +---------------- -A Nette alkalmazáson belül, ahol a `$configurator` a `Bootstrap.php` oldalon található, a RobotLoader-t így állíthatja be: +A Nette alkalmazáson belül, ahol az indítófájlban (`Bootstrap.php`) a `$configurator` objektumot használjuk, a leírás egyszerűsíthető: ```php $configurator = new Nette\Bootstrap\Configurator; @@ -73,57 +85,58 @@ $configurator->createRobotLoader() ``` -PHP Files Analyzer .[#toc-php-files-analyzer] ---------------------------------------------- +PHP fájl elemző +--------------- -A RobotLoader használható pusztán osztályok, interfészek és tulajdonságok keresésére is a PHP fájlokban **az automatikus betöltés funkció használata nélkül**: +A RobotLoadert tisztán arra is lehet használni, hogy osztályokat, interfészeket, traitteket és enumokat keressen PHP fájlokban **anélkül**, hogy az autoloading funkciót használnánk: ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); -// Átvizsgálja a könyvtárakat osztályok / interfészek / tulajdonságok után. +// átvizsgálja a könyvtárakat osztályokra / interfészekre / traittekre / enumokra $loader->rebuild(); -// Visszaadja az osztály => fájlnév párok tömbjét. +// visszaad egy tömböt osztály => fájlnév párokkal $res = $loader->getIndexedClasses(); ``` -Ilyen használat esetén is használhatja a gyorsítótárat. Ennek eredményeképpen a nem módosított fájlok nem kerülnek ismételten elemzésre az újbóli szkennelés során: +Ilyen használat esetén is kihasználhatja a gyorsítótárat. Ennek köszönhetően az újbóli szkenneléskor a változatlan fájlok nem lesznek újra elemezve: ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); + +// beállítjuk a gyorsítótárazást a 'temp' könyvtárba $loader->setTempDirectory(__DIR__ . '/temp'); -// Könyvtárak keresése a gyorsítótár segítségével +// átvizsgálja a könyvtárakat a gyorsítótár használatával $loader->refresh(); -// Visszaadja az osztály => fájlnév párok tömbjét. +// visszaad egy tömböt osztály => fájlnév párokkal $res = $loader->getIndexedClasses(); ``` -Tárolás .[#toc-caching] ------------------------ +Gyorsítótárazás +--------------- -A RobotLoader nagyon gyors, mert okosan használja a gyorsítótárat. +A RobotLoader nagyon gyors, mert ügyesen használja a gyorsítótárat. -A vele való fejlesztés során gyakorlatilag fogalmad sincs arról, hogy a háttérben fut. Folyamatosan frissíti a gyorsítótárat, mert tudja, hogy osztályokat és fájlokat lehet létrehozni, törölni, átnevezni stb. És nem vizsgálja ismételten a nem módosított fájlokat. +Fejlesztés közben gyakorlatilag észre sem vesszük, hogy a háttérben fut. Folyamatosan frissíti a gyorsítótárát, mert számít arra, hogy osztályok és fájlok keletkezhetnek, megszűnhetnek, átneveződhetnek stb. És nem szkenneli újra azokat a fájlokat, amelyek nem változtak. -Termelőszervereken való használat esetén viszont javasoljuk, hogy a `$loader->setAutoRefresh(false)` segítségével tiltsuk le a gyorsítótár frissítését (ez automatikusan megtörténik a Nette alkalmazásban), mert a fájlok nem változnak. Ugyanakkor a tárhelyre történő új verzió feltöltésekor **tisztítani kell a gyorsítótárat**. +Éles szerveren történő telepítéskor viszont javasoljuk a gyorsítótár frissítésének kikapcsolását a `$loader->setAutoRefresh(false)` segítségével (a Nette Alkalmazásban ez automatikusan megtörténik), mivel a fájlok nem változnak. Ugyanakkor ilyenkor **törölni kell a gyorsítótárat** az új verzió hosztingra való feltöltésekor. -Természetesen a fájlok kezdeti beolvasása, ha a gyorsítótár még nem létezik, nagyobb alkalmazásoknál néhány másodpercig is eltarthat. A RobotLoader beépített megelőzéssel rendelkezik a "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede ellen. -Ez az a helyzet, amikor a termelő szerver nagyszámú egyidejű kérést kap, és mivel a RobotLoader gyorsítótár még nem létezik, mindegyik elkezdené a fájlok beolvasását. Ami megnöveli a CPU és a fájlrendszer használatát. -Szerencsére a RobotLoader úgy működik, hogy több egyidejű kérés esetén csak az első szál indexeli a fájlokat, létrehozza a gyorsítótárat, a többiek várnak, majd használják a gyorsítótárat. +A fájlok kezdeti szkennelése, amikor a gyorsítótár még nem létezik, természetesen eltarthat egy ideig a nagyobb alkalmazásoknál. A RobotLoader beépített védelemmel rendelkezik a "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede ellen. Ez egy olyan helyzet, amikor az éles szerveren nagyobb számú egyidejű kérés érkezik, amelyek elindítják a RobotLoadert, és mivel a gyorsítótár még nem létezik, mindegyik elkezdené szkennelni a fájlokat. Ez aránytalanul megterhelné a szervert. Szerencsére a RobotLoader úgy működik, hogy több egyidejű kérés esetén csak az első szál indexeli a fájlokat, létrehozza a gyorsítótárat, a többiek várnak, majd ezt a gyorsítótárat használják. -PSR-4 .[#toc-psr-4] -------------------- +PSR-4 +----- -A Composer ma már a PSR-4 szabványnak megfelelően használható [automatikus betöltésre |best-practices:composer#autoloading]. Egyszerűen fogalmazva, ez egy olyan rendszer, ahol a névterek és az osztálynevek megfelelnek a könyvtárszerkezetnek és a fájlneveknek, azaz a `App\Router\RouterFactory` a `/path/to/App/Router/RouterFactory.php` fájlban található. +Ma már az [autoloadinghoz a Composert használni |best-practices:composer#Autoloading] lehet a PSR-4 betartásával. Egyszerűsítve ez egy olyan rendszer, ahol a névterek és osztálynevek megfelelnek a könyvtárstruktúrának és a fájlneveknek, tehát pl. az `App\Core\RouterFactory` az `/path/to/App/Core/RouterFactory.php` fájlban lesz. -A RobotLoader nem kötődik semmilyen rögzített struktúrához, ezért hasznos olyan helyzetekben, amikor nem felel meg a PHP-ban névterekként kialakított könyvtárszerkezet, vagy amikor olyan alkalmazást fejlesztünk, amely történelmileg nem használ ilyen konvenciókat. Az is lehetséges, hogy mindkét betöltőt együtt használjuk. +A RobotLoader nem kötődik semmilyen fix struktúrához, ezért olyan helyzetekben hasznos, amikor nem teljesen felel meg Önnek, hogy ugyanúgy tervezett könyvtárstruktúrája legyen, mint a PHP névtereknek, vagy amikor olyan alkalmazást fejleszt, amely történelmileg nem használ ilyen konvenciókat. Lehetőség van mindkét loader együttes használatára is. {{leftbar: nette:@menu-topics}} +{{sitename: Nette dokumentáció}} diff --git a/robot-loader/it/@home.texy b/robot-loader/it/@home.texy index 58c8528646..d6dcb1e3ee 100644 --- a/robot-loader/it/@home.texy +++ b/robot-loader/it/@home.texy @@ -1,17 +1,20 @@ -RobotLoader: Caricamento automatico delle classi -************************************************ +Nette RobotLoader +***************** <div class=perex> -RobotLoader è uno strumento che consente il caricamento automatico delle classi per l'intera applicazione, comprese le librerie di terze parti. +RobotLoader è uno strumento che ti offre il comfort dell'autoloading automatico delle classi per l'intera applicazione, comprese le librerie di terze parti. -- sbarazzarsi di tutti `require` -- vengono caricati solo gli script necessari -- non richiede rigide convenzioni di denominazione delle directory o dei file +- ci liberiamo di tutti i `require` +- verranno caricati solo gli script necessari +- non richiede convenzioni rigide per la denominazione di directory o file +- estremamente veloce +- nessun aggiornamento manuale della cache, tutto avviene automaticamente +- libreria matura, stabile e ampiamente utilizzata </div> -Quindi possiamo dimenticare i famosi blocchi di codice: +Possiamo quindi dimenticare questi noti blocchi di codice: ```php require_once 'Utils/Page.php'; @@ -21,46 +24,55 @@ require_once 'Utils/Paginator.php'; ``` -Installazione .[#toc-installation] ----------------------------------- +Installazione +------------- -Scaricare e installare il pacchetto utilizzando [Composer |best-practices:composer]: +Puoi scaricare RobotLoader come [un singolo file autonomo `RobotLoader.php` |https://github.com/nette/robot-loader/raw/standalone/src/RobotLoader/RobotLoader.php], che includi con `require` nel tuo script e hai subito a disposizione un comodo autoloading per l'intera applicazione. + +```php +require '/path/to/RobotLoader.php'; + +$loader = new Nette\Loaders\RobotLoader; +// ... +``` + +Se stai costruendo un'applicazione utilizzando [Composer|best-practices:composer], puoi installarlo tramite esso: ```shell composer require nette/robot-loader ``` -Uso .[#toc-usage] ------------------ +Utilizzo +-------- -Come il robot di Google effettua la scansione e l'indicizzazione dei siti web, [RobotLoader |api:Nette\Loaders\RobotLoader] effettua la scansione di tutti gli script PHP e registra quali classi e interfacce sono state trovate in essi. Questi record vengono poi salvati nella cache e utilizzati durante tutte le richieste successive. È sufficiente specificare quali directory indicizzare e dove salvare la cache: +Similmente a come il robot di Google scansiona e indicizza le pagine web, anche [RobotLoader |api:Nette\Loaders\RobotLoader] scansiona tutti gli script PHP e registra quali classi, interfacce, trait ed enum vi ha trovato. I risultati della ricerca vengono quindi salvati nella cache e utilizzati nella richiesta successiva. Basta quindi specificare quali directory deve scansionare e dove salvare la cache: ```php $loader = new Nette\Loaders\RobotLoader; -// le directory da indicizzare da RobotLoader (incluse le sottodirectory) +// directory che RobotLoader deve indicizzare (incluse le sottodirectory) $loader->addDirectory(__DIR__ . '/app'); $loader->addDirectory(__DIR__ . '/libs'); -// utilizzare la cartella 'temp' per la cache +// impostiamo il caching nella directory 'temp' $loader->setTempDirectory(__DIR__ . '/temp'); -$loader->register(); // Eseguire il RobotLoader +$loader->register(); // avviamo RobotLoader ``` -E questo è tutto. D'ora in poi, non sarà più necessario usare `require`. Fantastico, non è vero? +E questo è tutto, da questo momento non dobbiamo più usare `require`. Fantastico! -Quando RobotLoader incontra un nome di classe duplicato durante l'indicizzazione, lancia un'eccezione e informa l'utente. RobotLoader aggiorna automaticamente la cache quando deve caricare una classe che non conosce. Si consiglia di disabilitare questa funzione sui server di produzione, vedere [Cache |#Caching]. +Se RobotLoader incontra un nome di classe duplicato durante l'indicizzazione, lancerà un'eccezione e ti informerà. RobotLoader aggiorna anche automaticamente la cache quando deve caricare una classe che non conosce. Consigliamo di disattivarlo sui server di produzione, vedi [#Caching]. -Se si desidera che RobotLoader salti alcune directory, utilizzare `$loader->excludeDirectory('temp')` (può essere chiamato più volte o si possono passare più directory). +Se vuoi che RobotLoader salti alcune directory, usa `$loader->excludeDirectory('temp')` (può essere chiamato più volte o passare più directory). -Per impostazione predefinita, RobotLoader segnala gli errori nei file PHP lanciando l'eccezione `ParseError`. Può essere disabilitato tramite `$loader->reportParseErrors(false)`. +Per impostazione predefinita, RobotLoader segnala gli errori nei file PHP lanciando un'eccezione `ParseError`. Questo può essere soppresso usando `$loader->reportParseErrors(false)`. -Applicazione Nette .[#toc-nette-application] --------------------------------------------- +Applicazione Nette +------------------ -All'interno dell'applicazione Nette, dove `$configurator` è utilizzato in `Bootstrap.php`, è possibile impostare RobotLoader in questo modo: +All'interno di un'applicazione Nette, dove nel file di avvio `Bootstrap.php` viene utilizzato l'oggetto `$configurator`, la scrittura può essere semplificata: ```php $configurator = new Nette\Bootstrap\Configurator; @@ -73,57 +85,58 @@ $configurator->createRobotLoader() ``` -Analizzatore di file PHP .[#toc-php-files-analyzer] ---------------------------------------------------- +Analizzatore di file PHP +------------------------ -RobotLoader può essere usato anche solo per trovare classi, interfacce e tratti nei file PHP **senza** usare la funzione di caricamento automatico: +RobotLoader può anche essere utilizzato puramente per cercare classi, interfacce, trait ed enum nei file PHP **senza** utilizzare la funzione di autoloading: ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); -// Esegue la scansione delle directory per classi, interfacce e tratti. +// scansiona le directory per classi / interfacce / trait / enum $loader->rebuild(); -// Restituisce un array di coppie classe => nome file +// restituisce un array di coppie classe => nome file $res = $loader->getIndexedClasses(); ``` -Anche in questo caso, è possibile utilizzare la cache. Di conseguenza, i file non modificati non verranno analizzati ripetutamente durante la ricognizione: +Anche con questo utilizzo, puoi sfruttare la cache. Grazie a ciò, durante la nuova scansione, i file non modificati non verranno analizzati ripetutamente: ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); + +// impostiamo il caching nella directory 'temp' $loader->setTempDirectory(__DIR__ . '/temp'); -// Esegue la scansione delle directory utilizzando una cache +// scansiona le directory utilizzando la cache $loader->refresh(); -// Restituisce un array di coppie classe => nome file +// restituisce un array di coppie classe => nome file $res = $loader->getIndexedClasses(); ``` -Caching .[#toc-caching] ------------------------ +Caching +------- -RobotLoader è molto veloce perché utilizza in modo intelligente la cache. +RobotLoader è molto veloce perché utilizza abilmente la cache. -Quando si sviluppa con esso, non si ha praticamente idea che venga eseguito in background. Aggiorna continuamente la cache perché sa che le classi e i file possono essere creati, cancellati, rinominati, ecc. E non esegue ripetutamente la scansione di file non modificati. +Durante lo sviluppo, praticamente non ti accorgi che gira in background. Aggiorna continuamente la cache, perché tiene conto del fatto che classi e file possono nascere, morire, essere rinominati, ecc. E non scansiona ripetutamente i file che non sono cambiati. -Se si utilizza su un server di produzione, invece, si consiglia di disabilitare l'aggiornamento della cache tramite `$loader->setAutoRefresh(false)` (ciò avviene automaticamente nell'applicazione Nette), perché i file non vengono modificati. Allo stesso tempo, è necessario **pulire la cache** quando si carica una nuova versione sull'hosting. +Al contrario, durante la distribuzione su un server di produzione, consigliamo di disattivare l'aggiornamento della cache usando `$loader->setAutoRefresh(false)` (in un'applicazione Nette questo avviene automaticamente), perché i file non cambiano. Allo stesso tempo, è quindi necessario **cancellare la cache** sull'hosting quando si carica una nuova versione. -Naturalmente, la scansione iniziale dei file, quando la cache non esiste già, può richiedere alcuni secondi per le applicazioni più grandi. RobotLoader ha una prevenzione integrata contro la "fuga dalla cache":https://en.wikipedia.org/wiki/Cache_stampede. -Si tratta di una situazione in cui il server di produzione riceve un gran numero di richieste simultanee e, poiché la cache di RobotLoader non esiste ancora, tutte iniziano a scansionare i file. Il che fa impennare l'utilizzo della CPU e del filesystem. -Fortunatamente, RobotLoader funziona in modo tale che, in caso di più richieste contemporanee, solo il primo thread indicizza i file, crea una cache, gli altri aspettano e poi utilizzano la cache. +La scansione iniziale dei file, quando la cache non esiste ancora, può ovviamente richiedere un po' di tempo per applicazioni più grandi. RobotLoader ha una prevenzione integrata contro la "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Si tratta di una situazione in cui un numero maggiore di richieste simultanee arriva sul server di produzione, che avviano RobotLoader, e poiché la cache non esiste ancora, inizierebbero tutte a scansionare i file. Ciò sovraccaricherebbe eccessivamente il server. Fortunatamente, RobotLoader funziona in modo tale che, in caso di più richieste simultanee, solo il primo thread indicizza i file, crea la cache, gli altri attendono e successivamente utilizzano la cache. -PSR-4 .[#toc-psr-4] -------------------- +PSR-4 +----- -Oggi Composer può essere utilizzato per l'[autocaricamento |best-practices:composer#autoloading] in conformità al PSR-4. In parole povere, si tratta di un sistema in cui gli spazi dei nomi e i nomi delle classi corrispondono alla struttura delle directory e ai nomi dei file, ad esempio `App\Router\RouterFactory` si trova nel file `/path/to/App/Router/RouterFactory.php`. +Oggi è possibile utilizzare [Composer per l'autoloading |best-practices:composer#Autoloading] rispettando PSR-4. In parole povere, si tratta di un sistema in cui i namespace e i nomi delle classi corrispondono alla struttura delle directory e ai nomi dei file, ad esempio `App\Core\RouterFactory` sarà nel file `/path/to/App/Core/RouterFactory.php`. -RobotLoader non è legato a nessuna struttura fissa, quindi è utile in situazioni in cui non è opportuno avere la struttura delle directory progettata come spazi dei nomi in PHP, oppure quando si sta sviluppando un'applicazione che storicamente non ha usato tali convenzioni. È anche possibile utilizzare entrambi i caricatori insieme. +RobotLoader non è legato a nessuna struttura fissa, quindi è utile in situazioni in cui non ti va completamente bene avere la stessa struttura di directory progettata come i namespace in PHP, o quando sviluppi un'applicazione che storicamente non utilizza tali convenzioni. È anche possibile utilizzare entrambi i loader insieme. {{leftbar: nette:@menu-topics}} +{{sitename: Documentazione Nette}} diff --git a/robot-loader/ja/@home.texy b/robot-loader/ja/@home.texy new file mode 100644 index 0000000000..84ee6bea35 --- /dev/null +++ b/robot-loader/ja/@home.texy @@ -0,0 +1,142 @@ +Nette RobotLoader +***************** + +<div class=perex> + +RobotLoaderは、サードパーティのライブラリを含むアプリケーション全体のクラスの自動読み込みを快適に提供するツールです。 + +- すべての `require` を取り除きます +- 必要なスクリプトのみが読み込まれます +- ディレクトリやファイルの命名に関する厳格な規則は必要ありません +- 非常に高速 +- 手動でのキャッシュ更新は不要、すべて自動的に行われます +- 成熟し、安定し、広く使用されているライブラリ + +</div> + +したがって、これらのよく知られたコードブロックを忘れることができます: + +```php +require_once 'Utils/Page.php'; +require_once 'Utils/Style.php'; +require_once 'Utils/Paginator.php'; +// ... +``` + + +インストール +------ + +RobotLoaderは、[単一の独立したファイル `RobotLoader.php` |https://github.com/nette/robot-loader/raw/standalone/src/RobotLoader/RobotLoader.php]としてダウンロードできます。これをスクリプトに`require`で含めるだけで、アプリケーション全体の快適なオートロードがすぐに利用可能になります。 + +```php +require '/path/to/RobotLoader.php'; + +$loader = new Nette\Loaders\RobotLoader; +// ... +``` + +[Composer|best-practices:composer]を使用するアプリケーションを構築している場合は、それを使用してインストールできます: + +```shell +composer require nette/robot-loader +``` + + +使用法 +--- + +GoogleボットがWebページをクロールしてインデックスを作成するのと同様に、[RobotLoader |api:Nette\Loaders\RobotLoader]もすべてのPHPスクリプトをクロールし、それらに含まれるクラス、インターフェース、トレイト、およびenumを記録します。調査結果はキャッシュに保存され、次のリクエストで使用されます。したがって、どのディレクトリをクロールし、どこにキャッシュを保存するかを指定するだけです: + +```php +$loader = new Nette\Loaders\RobotLoader; + +// RobotLoader がインデックスを作成するディレクトリ(サブディレクトリを含む) +$loader->addDirectory(__DIR__ . '/app'); +$loader->addDirectory(__DIR__ . '/libs'); + +// キャッシュを 'temp' ディレクトリに設定します +$loader->setTempDirectory(__DIR__ . '/temp'); +$loader->register(); // RobotLoader を起動します +``` + +これで完了です。これ以降、`require`を使用する必要はありません。素晴らしい! + +RobotLoaderがインデックス作成中に重複したクラス名に遭遇した場合、例外をスローして通知します。RobotLoaderは、知らないクラスを読み込む必要がある場合にもキャッシュを自動的に更新します。本番サーバーではこれを無効にすることをお勧めします。下記[##キャッシュ]を参照してください。 + +RobotLoaderに特定のディレクトリをスキップさせたい場合は、`$loader->excludeDirectory('temp')`を使用します(複数回呼び出すか、複数のディレクトリを渡すことができます)。 + +デフォルトでは、RobotLoaderはPHPファイルの構文エラーを`ParseError`例外をスローして報告します。これは`$loader->reportParseErrors(false)`で抑制できます。 + + +Nette アプリケーション +-------------- + +Netteアプリケーション内では、ブートストラップファイル`Bootstrap.php`で`$configurator`オブジェクトが使用されるため、記述を簡略化できます: + +```php +$configurator = new Nette\Bootstrap\Configurator; +// ... +$configurator->setTempDirectory(__DIR__ . '/../temp'); +$configurator->createRobotLoader() + ->addDirectory(__DIR__) + ->addDirectory(__DIR__ . '/../libs') + ->register(); +``` + + +PHP ファイルアナライザー +-------------- + +RobotLoaderは、オートロード機能を使用せずに、PHPファイル内のクラス、インターフェース、トレイト、およびenumを検索するためだけに使用することもできます: + +```php +$loader = new Nette\Loaders\RobotLoader; +$loader->addDirectory(__DIR__ . '/app'); + +// ディレクトリをクラス/インターフェース/トレイト/enum で検索します +$loader->rebuild(); + +// クラス => ファイル名のペアの配列を返します +$res = $loader->getIndexedClasses(); +``` + +このような使用法でもキャッシュを利用できます。これにより、再スキャン時に変更されていないファイルが繰り返し分析されることはありません: + +```php +$loader = new Nette\Loaders\RobotLoader; +$loader->addDirectory(__DIR__ . '/app'); + +// キャッシュを 'temp' ディレクトリに設定します +$loader->setTempDirectory(__DIR__ . '/temp'); + +// キャッシュを使用してディレクトリを検索します +$loader->refresh(); + +// クラス => ファイル名のペアの配列を返します +$res = $loader->getIndexedClasses(); +``` + + +キャッシュ +----- + +RobotLoaderは、キャッシュを巧みに利用するため、非常に高速です。 + +開発中は、バックグラウンドで実行されていることにほとんど気づきません。クラスやファイルが作成、削除、名前変更される可能性があることを考慮して、キャッシュを継続的に更新します。そして、変更されていないファイルは繰り返しスキャンしません。 + +一方、本番サーバーにデプロイする場合は、ファイルが変更されないため、`$loader->setAutoRefresh(false)`を使用してキャッシュの更新を無効にすることをお勧めします(Netteアプリケーションでは自動的に行われます)。同時に、新しいバージョンをホスティングにアップロードする際には、**キャッシュを削除する必要があります。** + +キャッシュがまだ存在しない場合の最初のファイルスキャンは、より大規模なアプリケーションでは当然少し時間がかかることがあります。RobotLoaderには、「キャッシュスタンピード」:https://en.wikipedia.org/wiki/Cache_stampedeに対する組み込みの防止策があります。 これは、本番サーバーで多数の同時リクエストが発生し、RobotLoaderが起動され、キャッシュがまだ存在しないため、すべてがファイルのスキャンを開始する状況です。これはサーバーに過度の負荷をかけることになります。 幸いなことに、RobotLoaderは、複数の同時リクエストが発生した場合、最初のスレッドのみがファイルをインデックス化し、キャッシュを作成し、他のスレッドは待機してその後キャッシュを利用するように機能します。 + + +PSR-4 +----- + +現在、PSR-4に準拠していれば、[オートロードに Composer を使用できます |best-practices:composer#オートローディング]。簡単に言えば、これは名前空間とクラス名がディレクトリ構造とファイル名に対応するシステムです。つまり、例えば`App\Core\RouterFactory`は`/path/to/App/Core/RouterFactory.php`ファイルにあります。 + +RobotLoaderは固定構造に縛られていないため、PHPの名前空間と同じように設計されたディレクトリ構造を持つことが完全に適していない状況や、歴史的にそのような規則を使用していないアプリケーションを開発している場合に役立ちます。両方のローダーを一緒に使用することも可能です。 + + +{{leftbar: nette:@menu-topics}} +{{sitename: Nette ドキュメンテーション}} diff --git a/robot-loader/pl/@home.texy b/robot-loader/pl/@home.texy index a6fcc7f729..5654e83d73 100644 --- a/robot-loader/pl/@home.texy +++ b/robot-loader/pl/@home.texy @@ -1,17 +1,20 @@ -RobotLoader: autoloadowanie klas -******************************** +Nette RobotLoader +***************** <div class=perex> -RobotLoader to narzędzie, które daje wygodę automatycznego ładowania klas dla całej aplikacji, w tym bibliotek firm trzecich. +RobotLoader to narzędzie, które zapewni Ci komfort automatycznego ładowania klas dla całej Twojej aplikacji, włącznie z bibliotekami stron trzecich. -- Pozbywamy się wszystkich `require` -- zostaną załadowane tylko niezbędne skrypty -- nie wymaga ścisłych konwencji nazewnictwa dla katalogów i plików +- pozbędziemy się wszystkich `require` +- będą ładowane tylko potrzebne skrypty +- nie wymaga ścisłych konwencji nazewnictwa katalogów czy plików +- ekstremalnie szybki +- żadnych ręcznych aktualizacji pamięci podręcznej, wszystko odbywa się automatycznie +- dojrzała, stabilna i szeroko stosowana biblioteka </div> -Możemy więc zapomnieć o tych znanych nam blokach kodu: +Możemy więc zapomnieć o tych znanych blokach kodu: ```php require_once 'Utils/Page.php'; @@ -21,46 +24,55 @@ require_once 'Utils/Paginator.php'; ``` -Instalacja .[#toc-installation] -------------------------------- +Instalacja +---------- -Pobierz i zainstaluj bibliotekę za pomocą [Composera |best-practices:composer]: +RobotLoader można pobrać jako [jeden samodzielny plik `RobotLoader.php` |https://github.com/nette/robot-loader/raw/standalone/src/RobotLoader/RobotLoader.php], który wstawisz za pomocą `require` do swojego skryptu i od razu masz do dyspozycji komfortowy autoloading dla całej aplikacji. + +```php +require '/path/to/RobotLoader.php'; + +$loader = new Nette\Loaders\RobotLoader; +// ... +``` + +Jeśli budujesz aplikację wykorzystującą [Composer|best-practices:composer], możesz go zainstalować za pomocą niego: ```shell composer require nette/robot-loader ``` -Korzystanie z .[#toc-usage] ---------------------------- +Użycie +------ -Podobnie jak robot Google indeksuje strony internetowe, [RobotLoader |api:Nette\Loaders\RobotLoader] przeszukuje wszystkie skrypty PHP i notuje, jakie klasy, interfejsy i cechy w nich znalazł. Następnie buforuje wyniki swojej eksploracji i używa ich w następnym żądaniu. Wystarczy więc określić, które katalogi mają być indeksowane i gdzie mają być buforowane: +Podobnie jak robot Google przeszukuje i indeksuje strony internetowe, tak i [RobotLoader |api:Nette\Loaders\RobotLoader] przeszukuje wszystkie skrypty PHP i zapisuje, które klasy, interfejsy, traity i enumy w nich znalazł. Wyniki badań następnie zapisuje do cache i używa przy następnym żądaniu. Wystarczy więc określić, które katalogi ma przeszukiwać i gdzie zapisywać cache: ```php $loader = new Nette\Loaders\RobotLoader; -// katalogi, które mają być indeksowane przez RobotLoader (łącznie z podkatalogami) +// katalogi, które RobotLoader ma indeksować (włącznie z podkatalogami) $loader->addDirectory(__DIR__ . '/app'); $loader->addDirectory(__DIR__ . '/libs'); -// ustaw buforowanie w katalogu 'temp' +// ustawiamy cachowanie w katalogu 'temp' $loader->setTempDirectory(__DIR__ . '/temp'); -$loader->register(); // uruchom RobotLoader +$loader->register(); // uruchamiamy RobotLoader ``` -To wszystko, od tej pory nie musimy używać `require`. Świetnie! +I to wszystko, od tej chwili nie musimy używać `require`. Super! -Jeśli RobotLoader napotka zduplikowaną nazwę klasy podczas indeksowania, rzuca wyjątek i informuje o tym. RobotLoader również automatycznie aktualizuje pamięć podręczną, gdy musi załadować klasę, której nie zna. Zalecamy wyłączenie tego na serwerach produkcyjnych, zobacz [Caching |#Caching]. +Jeśli RobotLoader napotka podczas indeksowania zduplikowaną nazwę klasy, rzuci wyjątek i poinformuje Cię o tym. RobotLoader również automatycznie aktualizuje cache, gdy ma załadować klasę, której nie zna. Zalecamy wyłączenie tej funkcji na serwerach produkcyjnych, zobacz [#Cachowanie]. -Jeśli chcesz, aby RobotLoader pominął niektóre katalogi, użyj `$loader->excludeDirectory('temp')` (możesz wykonać wiele połączeń lub przekazać wiele katalogów). +Jeśli chcesz, aby RobotLoader pominął niektóre katalogi, użyj `$loader->excludeDirectory('temp')` (można wywoływać wielokrotnie lub przekazać więcej katalogów). -Domyślnie RobotLoader zgłasza błędy w plikach PHP rzucając wyjątek `ParseError`. Można to nadpisać za pomocą `$loader->reportParseErrors(false)`. +Domyślnie RobotLoader zgłasza błędy w plikach PHP rzucając wyjątek `ParseError`. Można to pominąć za pomocą `$loader->reportParseErrors(false)`. -Aplikacje Nette .[#toc-nette-application] ------------------------------------------ +Aplikacja Nette +--------------- -Wewnątrz aplikacji Nette, gdzie w pliku startowym używany jest obiekt `Bootstrap.php` `$configurator` , notacja może być uproszczona: +Wewnątrz aplikacji Nette, gdzie w pliku startowym `Bootstrap.php` używany jest obiekt `$configurator`, zapis można uprościć: ```php $configurator = new Nette\Bootstrap\Configurator; @@ -73,57 +85,58 @@ $configurator->createRobotLoader() ``` -Analizator plików PHP .[#toc-php-files-analyzer] ------------------------------------------------- +Analizator plików PHP +--------------------- -RobotLoader może być również użyty do wyszukiwania klas, interfejsów i cech w plikach PHP **bez** użycia funkcji autoloadingu: +RobotLoader można również użyć wyłącznie do wyszukiwania klas, interfejsów, traitów i enumów w plikach PHP **bez** wykorzystywania funkcji autoloadingu: ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); -// skanuje katalogi w poszukiwaniu klas/interfejsów/traitów +// przeszukuje katalogi w poszukiwaniu klas / interfejsów / traitów / enumów $loader->rebuild(); -// zwraca tablicę par class => filename +// zwraca tablicę par klasa => nazwa pliku $res = $loader->getIndexedClasses(); ``` -W ten sposób można również wykorzystać cache. Dzięki temu niezmienione pliki nie będą ponownie analizowane przy ponownym skanowaniu: +Nawet przy takim użyciu można wykorzystać cache. Dzięki temu przy ponownym skanowaniu nie będą ponownie analizowane niezmienione pliki: ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); + +// ustawiamy cachowanie w katalogu 'temp' $loader->setTempDirectory(__DIR__ . '/temp'); -// skanuje katalogi używając pamięci podręcznej +// przeszukuje katalogi z wykorzystaniem cache $loader->refresh(); -// zwraca tablicę par class => filename +// zwraca tablicę par klasa => nazwa pliku $res = $loader->getIndexedClasses(); ``` -Caching .[#toc-caching] ------------------------ +Cachowanie +---------- -RobotLoader jest bardzo szybki, ponieważ sprytnie wykorzystuje buforowanie. +RobotLoader jest bardzo szybki, ponieważ sprytnie wykorzystuje cache. -Rozwijając się z nim, jesteś praktycznie nieświadomy, że działa on w tle. Stale aktualizuje swoją pamięć podręczną, ponieważ bierze pod uwagę, że klasy i pliki mogą być tworzone, znikać, zmieniać nazwy itp. I nie skanuje wielokrotnie plików, które nie uległy zmianie. +Podczas rozwoju praktycznie nie zauważasz, że działa w tle. Bieżąco aktualizuje cache, ponieważ zakłada, że klasy i pliki mogą powstawać, znikać, zmieniać nazwy itp. I nie skanuje ponownie plików, które się nie zmieniły. -Z drugiej strony, podczas wdrażania na serwerze produkcyjnym, zalecamy wyłączenie aktualizacji pamięci podręcznej za pomocą `$loader->setAutoRefresh(false)` (robi to automatycznie w aplikacji Nette), ponieważ pliki nie ulegają zmianie. Jednocześnie po wgraniu nowej wersji na hosting konieczne jest **wyczyszczenie cache**. +Podczas wdrażania na serwerze produkcyjnym zalecamy natomiast wyłączenie aktualizacji cache za pomocą `$loader->setAutoRefresh(false)` (w aplikacji Nette dzieje się tak automatycznie), ponieważ pliki się nie zmieniają. Jednocześnie konieczne jest wtedy przy wgrywaniu nowej wersji na hosting **usunięcie cache.** -Początkowe skanowanie plików, gdy pamięć podręczna jeszcze nie istnieje, może zająć chwilę w przypadku większych aplikacji. RobotLoader ma wbudowane zapobieganie przed "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. -Jest to sytuacja, w której duża liczba współbieżnych żądań spotyka się na serwerze produkcyjnym i uruchamia RobotLoader, a ponieważ pamięć podręczna jeszcze nie istnieje, wszystkie zaczęłyby skanować pliki. Co spowodowałoby nieproporcjonalne obciążenie serwera. -Na szczęście sposób działania RobotLoader polega na tym, że gdy istnieje wiele współbieżnych żądań, tylko pierwszy wątek indeksuje pliki, tworzy pamięć podręczną, pozostałe czekają, a następnie używają pamięci podręcznej. +Pierwsze skanowanie plików, gdy cache jeszcze nie istnieje, może w przypadku większych aplikacji oczywiście chwilę potrwać. RobotLoader ma wbudowaną prewencję przed "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Jest to sytuacja, gdy na serwerze produkcyjnym pojawi się większa liczba równoczesnych żądań, które uruchomią RobotLoader, a ponieważ cache jeszcze nie istnieje, wszystkie zaczęłyby skanować pliki. Co nieproporcjonalnie obciążyłoby serwer. Na szczęście RobotLoader działa tak, że przy wielu równoczesnych żądaniach pliki indeksuje tylko pierwszy wątek, tworzy cache, pozostałe czekają, a następnie wykorzystują cache. -PSR-4 .[#toc-psr-4] -------------------- +PSR-4 +----- -Dziś [Composer |best-practices:composer#Autoloading] może być [używany |best-practices:composer#Autoloading] do [autoloadingu |best-practices:composer#Autoloading], jeśli przestrzega się PSR-4. Najprościej mówiąc, jest to system, w którym przestrzenie nazw i nazwy klas odpowiadają strukturom katalogów i nazwom plików, więc na przykład `App\Router\RouterFactory` będzie w pliku `/path/to/App/Router/RouterFactory.php`. +Obecnie można do [autoloadingu używać Composera |best-practices:composer#Autoloading] przy zachowaniu PSR-4. Upraszczając, jest to system, w którym przestrzenie nazw i nazwy klas odpowiadają strukturze katalogów i nazwom plików, czyli np. `App\Core\RouterFactory` będzie w pliku `/path/to/App/Core/RouterFactory.php`. -RobotLoader nie jest przywiązany do żadnej stałej struktury, więc jest przydatny w sytuacjach, gdy nie czujesz się komfortowo z posiadaniem struktury katalogów zaprojektowanej w taki sam sposób jak przestrzenie nazw PHP, lub gdy rozwijasz aplikację, która historycznie nie używała takich konwencji. Możliwe jest również stosowanie obu ładowarek razem. +RobotLoader nie jest związany z żadną stałą strukturą, dlatego przydaje się w sytuacjach, gdy nie do końca odpowiada Ci posiadanie tak samo zaprojektowanej struktury katalogów jak przestrzenie nazw w PHP, lub gdy rozwijasz aplikację, która historycznie takich konwencji nie wykorzystuje. Możliwe jest również używanie obu loaderów jednocześnie. {{leftbar: nette:@menu-topics}} +{{sitename: Dokumentacja Nette}} diff --git a/robot-loader/pt/@home.texy b/robot-loader/pt/@home.texy index daf06c0cbb..ea2aa21581 100644 --- a/robot-loader/pt/@home.texy +++ b/robot-loader/pt/@home.texy @@ -1,17 +1,20 @@ -RobotLoader: Carregamento automático de classe -********************************************** +Nette RobotLoader +***************** <div class=perex> -RobotLoader é uma ferramenta que lhe dá conforto de carregamento automatizado de classe para toda a sua aplicação, incluindo bibliotecas de terceiros. +RobotLoader é uma ferramenta que garante o conforto do carregamento automático de classes para toda a sua aplicação, incluindo bibliotecas de terceiros. -- se livrar de todos `require` -- somente os scripts necessários são carregados -- não requer convenções rigorosas de diretório ou de nomes de arquivos +- livramo-nos de todos os `require` +- apenas os scripts necessários serão carregados +- não requer convenções estritas de nomenclatura de diretórios ou arquivos +- extremamente rápido +- nenhuma atualização manual de cache, tudo acontece automaticamente +- biblioteca madura, estável e amplamente utilizada </div> -Assim, podemos esquecer esses famosos blocos de código: +Podemos, portanto, esquecer estes blocos de código familiares: ```php require_once 'Utils/Page.php'; @@ -21,46 +24,55 @@ require_once 'Utils/Paginator.php'; ``` -Instalação .[#toc-installation] -------------------------------- +Instalação +---------- -Baixe e instale o pacote usando [o Composer |best-practices:composer]: +Você pode baixar o RobotLoader como [um único arquivo independente `RobotLoader.php` |https://github.com/nette/robot-loader/raw/standalone/src/RobotLoader/RobotLoader.php], que você insere usando `require` em seu script e imediatamente tem disponível o autoloading confortável para toda a aplicação. + +```php +require '/path/to/RobotLoader.php'; + +$loader = new Nette\Loaders\RobotLoader; +// ... +``` + +Se você está construindo uma aplicação usando [Composer|best-practices:composer], pode instalá-lo através dele: ```shell composer require nette/robot-loader ``` -Utilização .[#toc-usage] ------------------------- +Uso +--- -Como o robô Google rastreia e indexa websites, o [RobotLoader |api:Nette\Loaders\RobotLoader] rastreia todos os scripts PHP e registra quais classes e interfaces foram encontradas neles. Estes registros são então salvos em cache e utilizados durante todas as solicitações subseqüentes. Basta especificar quais diretórios indexar e onde salvar o cache: +Assim como o robô do Google percorre e indexa páginas da web, o [RobotLoader |api:Nette\Loaders\RobotLoader] percorre todos os scripts PHP e registra quais classes, interfaces, traits e enums encontrou neles. Ele então armazena os resultados da pesquisa no cache e os usa na próxima requisição. Basta especificar quais diretórios ele deve percorrer e onde armazenar o cache: ```php $loader = new Nette\Loaders\RobotLoader; -// diretórios a serem indexados pelo RobotLoader (incluindo subdiretórios) +// diretórios que o RobotLoader deve indexar (incluindo subdiretórios) $loader->addDirectory(__DIR__ . '/app'); $loader->addDirectory(__DIR__ . '/libs'); -// usar diretório 'temp' para cache +// definimos o cache para o diretório 'temp' $loader->setTempDirectory(__DIR__ . '/temp'); -$loader->register(); // Executar o RobotLoader +$loader->register(); // iniciamos o RobotLoader ``` -E isso é tudo. De agora em diante, você não precisa usar `require`. Ótimo, não é? +E é isso, a partir deste momento não precisamos usar `require`. Ótimo! -Quando o RobotLoader encontra um nome de classe duplicado durante a indexação, ele lança uma exceção e o informa sobre isso. O RobotLoader também atualiza automaticamente o cache quando ele tem que carregar uma classe que não conhece. Recomendamos desativar isto nos servidores de produção, veja [Caching |#Caching]. +Se o RobotLoader encontrar um nome de classe duplicado durante a indexação, ele lançará uma exceção e informará você sobre isso. O RobotLoader também atualiza automaticamente o cache quando precisa carregar uma classe que não conhece. Recomendamos desativar isso em servidores de produção, veja [#Caching]. -Se você quiser que RobotLoader salte alguns diretórios, use `$loader->excludeDirectory('temp')` (pode ser chamado várias vezes ou você pode passar por vários diretórios). +Se você quiser que o RobotLoader pule alguns diretórios, use `$loader->excludeDirectory('temp')` (pode ser chamado várias vezes ou passar vários diretórios). -Por padrão, o RobotLoader reporta erros em arquivos PHP lançando a exceção `ParseError`. Ele pode ser desativado via `$loader->reportParseErrors(false)`. +Por padrão, o RobotLoader relata erros em arquivos PHP lançando a exceção `ParseError`. Isso pode ser suprimido usando `$loader->reportParseErrors(false)`. -Aplicação Nette .[#toc-nette-application] ------------------------------------------ +Aplicação Nette +--------------- -Dentro da aplicação Nette, onde `$configurator` é usado em `Bootstrap.php`, você pode configurar o RobotLoader desta forma: +Dentro de uma aplicação Nette, onde o objeto `$configurator` é usado no arquivo de inicialização `Bootstrap.php`, a notação pode ser simplificada: ```php $configurator = new Nette\Bootstrap\Configurator; @@ -73,57 +85,58 @@ $configurator->createRobotLoader() ``` -Analisador de arquivos PHP .[#toc-php-files-analyzer] ------------------------------------------------------ +Analisador de arquivos PHP +-------------------------- -RobotLoader também pode ser usado puramente para encontrar classes, interfaces e traços em arquivos PHP **sem*** usando o recurso de auto-carregamento: +O RobotLoader também pode ser usado puramente para pesquisar classes, interfaces, traits e enums em arquivos PHP **sem** usar a função de autoloading: ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); -// Escaneia diretórios de classes / interfaces / traços +// pesquisa diretórios por classes / interfaces / traits / enums $loader->rebuild(); -// Retorna matriz de classe => pares de nomes de arquivo +// retorna um array de pares classe => nome do arquivo $res = $loader->getIndexedClasses(); ``` -Mesmo com tal uso, você pode usar o cache. Como resultado, os arquivos não modificados não serão analisados repetidamente durante a nova digitalização: +Mesmo com tal uso, você pode utilizar o cache. Graças a isso, ao escanear novamente, arquivos inalterados não serão analisados repetidamente: ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); + +// definimos o cache para o diretório 'temp' $loader->setTempDirectory(__DIR__ . '/temp'); -// Escaneia diretórios usando um cache +// pesquisa diretórios usando o cache $loader->refresh(); -// Retorna matriz de classe => pares de nomes de arquivo +// retorna um array de pares classe => nome do arquivo $res = $loader->getIndexedClasses(); ``` -Caching .[#toc-caching] ------------------------ +Caching +------- O RobotLoader é muito rápido porque usa o cache de forma inteligente. -Ao se desenvolver com ele, você não tem praticamente nenhuma idéia de que ele corre sobre o fundo. Ele atualiza continuamente o cache porque sabe que classes e arquivos podem ser criados, apagados, renomeados, etc. E ele não escaneia repetidamente arquivos não modificados. +Durante o desenvolvimento, você praticamente não percebe que ele está rodando em segundo plano. Ele atualiza continuamente o cache, pois espera que classes e arquivos possam surgir, desaparecer, ser renomeados, etc. E não escaneia repetidamente arquivos que não foram alterados. -Quando usado em um servidor de produção, por outro lado, recomendamos desativar a atualização do cache usando `$loader->setAutoRefresh(false)` (isto é feito automaticamente na Aplicação Nette), porque os arquivos não estão mudando. Ao mesmo tempo, é necessário **clarar o cache** ao fazer o upload de uma nova versão na hospedagem. +Ao implantar em um servidor de produção, por outro lado, recomendamos desativar a atualização do cache usando `$loader->setAutoRefresh(false)` (na Aplicação Nette, isso acontece automaticamente), pois os arquivos não mudam. Ao mesmo tempo, é necessário **limpar o cache** ao carregar uma nova versão no hosting. -Naturalmente, a digitalização inicial dos arquivos, quando o cache ainda não existe, pode levar alguns segundos para aplicações maiores. O RobotLoader tem prevenção embutida contra a "debandada do cache:https://en.wikipedia.org/wiki/Cache_stampede". -Esta é uma situação em que o servidor de produção recebe um grande número de solicitações simultâneas e como o cache do RobotLoader ainda não existe, todos eles começariam a escanear os arquivos. O que aumenta o uso da CPU e do sistema de arquivos. -Felizmente, o RobotLoader trabalha de tal forma que para múltiplas solicitações simultâneas, apenas o primeiro thread indexa os arquivos, cria um cache, os outros esperam, e então usam o cache. +A varredura inicial dos arquivos, quando o cache ainda não existe, pode, compreensivelmente, levar um momento para aplicações maiores. O RobotLoader tem prevenção integrada contra "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Esta é uma situação em que um grande número de requisições simultâneas chegam ao servidor de produção, que iniciam o RobotLoader, e como o cache ainda não existe, todas começariam a escanear os arquivos. O que sobrecarregaria desproporcionalmente o servidor. Felizmente, o RobotLoader funciona de forma que, com múltiplas requisições simultâneas, apenas a primeira thread indexa os arquivos, cria o cache, as outras esperam e, subsequentemente, usam o cache. -PSR-4 .[#toc-psr-4] -------------------- +PSR-4 +----- -Hoje, o Composer pode ser usado para [autocarga |best-practices:composer#autoloading] em conformidade com o PSR-4. Dito simplesmente, é um sistema onde os namespaces e nomes de classes correspondem à estrutura do diretório e nomes de arquivos, ou seja, `App\Router\RouterFactory` está localizado no arquivo `/path/to/App/Router/RouterFactory.php`. +Hoje, é possível usar o [Composer para autoloading |best-practices:composer#Autoloading] seguindo o PSR-4. Simplificando, é um sistema onde namespaces e nomes de classes correspondem à estrutura de diretórios e nomes de arquivos, ou seja, por exemplo, `App\Core\RouterFactory` estará no arquivo `/path/to/App/Core/RouterFactory.php`. -O RobotLoader não está ligado a nenhuma estrutura fixa, portanto, é útil em situações em que não lhe convém ter a estrutura de diretório projetada como namespaces em PHP, ou quando você está desenvolvendo uma aplicação que historicamente não utilizou tais convenções. Também é possível utilizar os dois carregadores em conjunto. +O RobotLoader não está vinculado a nenhuma estrutura fixa, por isso é útil em situações em que você não se sente confortável em ter a mesma estrutura de diretórios projetada como namespaces em PHP, ou quando desenvolve uma aplicação que historicamente não usa tais convenções. Também é possível usar ambos os loaders juntos. {{leftbar: nette:@menu-topics}} +{{sitename: Documentação Nette}} diff --git a/robot-loader/ro/@home.texy b/robot-loader/ro/@home.texy index 931005f59e..831fb337b0 100644 --- a/robot-loader/ro/@home.texy +++ b/robot-loader/ro/@home.texy @@ -1,17 +1,20 @@ -RobotLoader: Încărcarea automată a clasei -***************************************** +Nette RobotLoader +***************** <div class=perex> -RobotLoader este un instrument care vă oferă confortul încărcării automate a claselor pentru întreaga dumneavoastră aplicație, inclusiv pentru bibliotecile de la terți. +RobotLoader este un instrument care vă asigură confortul încărcării automate a claselor pentru întreaga dvs. aplicație, inclusiv bibliotecile terțe. -- scăpați de toate `require` -- sunt încărcate doar scripturile necesare -- nu necesită convenții stricte de denumire a directoarelor sau a fișierelor +- scăpăm de toate `require`-urile +- se vor încărca doar scripturile necesare +- nu necesită convenții stricte de denumire a directoarelor sau fișierelor +- extrem de rapid +- nicio actualizare manuală a cache-ului, totul se întâmplă automat +- bibliotecă matură, stabilă și utilizată pe scară largă </div> -Așadar, putem să uităm de acele celebre blocuri de cod: +Putem deci uita de aceste blocuri de cod cunoscute: ```php require_once 'Utils/Page.php'; @@ -21,46 +24,55 @@ require_once 'Utils/Paginator.php'; ``` -Instalare .[#toc-installation] ------------------------------- +Instalare +--------- -Descărcați și instalați pachetul folosind [Composer |best-practices:composer]: +Puteți descărca RobotLoader ca [un singur fișier independent `RobotLoader.php` |https://github.com/nette/robot-loader/raw/standalone/src/RobotLoader/RobotLoader.php], pe care îl includeți folosind `require` în scriptul dvs. și aveți imediat la dispoziție autoloading confortabil pentru întreaga aplicație. + +```php +require '/path/to/RobotLoader.php'; + +$loader = new Nette\Loaders\RobotLoader; +// ... +``` + +Dacă construiți o aplicație folosind [Composer|best-practices:composer], îl puteți instala folosind acesta: ```shell composer require nette/robot-loader ``` -Utilizare .[#toc-usage] ------------------------ +Utilizare +--------- -La fel cum robotul Google răscolește și indexează site-urile web, [RobotLoader |api:Nette\Loaders\RobotLoader] răscolește toate scripturile PHP și înregistrează ce clase și interfețe au fost găsite în ele. Aceste înregistrări sunt apoi salvate în memoria cache și utilizate în timpul tuturor cererilor ulterioare. Trebuie doar să specificați ce directoare să indexați și unde să salvați memoria cache: +Similar cu modul în care robotul Google parcurge și indexează paginile web, și [RobotLoader |api:Nette\Loaders\RobotLoader] parcurge toate scripturile PHP și înregistrează ce clase, interfețe, trait-uri și enumuri a găsit în ele. Rezultatele cercetării le stochează apoi în cache și le utilizează la următoarea cerere. Este suficient deci să specificați ce directoare trebuie să parcurgă și unde să stocheze cache-ul: ```php $loader = new Nette\Loaders\RobotLoader; -// directoare care urmează să fie indexate de RobotLoader (inclusiv subdirectoarele) +// directoarele pe care RobotLoader trebuie să le indexeze (inclusiv subdirectoarele) $loader->addDirectory(__DIR__ . '/app'); $loader->addDirectory(__DIR__ . '/libs'); -// utilizați directorul "temp" pentru memoria cache +// setăm stocarea în cache în directorul 'temp' $loader->setTempDirectory(__DIR__ . '/temp'); -$loader->register(); // Rulați RobotLoader +$loader->register(); // pornim RobotLoader ``` -Și asta e tot. De acum încolo, nu mai este nevoie să utilizați `require`. Minunat, nu-i așa? +Și asta e tot, de acum înainte nu mai trebuie să folosim `require`. Super! -Atunci când RobotLoader întâlnește un nume de clasă duplicat în timpul indexării, acesta aruncă o excepție și vă informează despre aceasta. De asemenea, RobotLoader actualizează automat memoria cache atunci când trebuie să încarce o clasă pe care nu o cunoaște. Vă recomandăm să dezactivați acest lucru pe serverele de producție, consultați [Caching |#Caching]. +Dacă RobotLoader întâlnește un nume de clasă duplicat în timpul indexării, va arunca o excepție și vă va informa despre aceasta. RobotLoader actualizează, de asemenea, automat cache-ul atunci când trebuie să încarce o clasă pe care nu o cunoaște. Vă recomandăm să dezactivați acest lucru pe serverele de producție, vezi [#Stocarea în cache]. -Dacă doriți ca RobotLoader să sară peste unele directoare, utilizați `$loader->excludeDirectory('temp')` (poate fi apelat de mai multe ori sau puteți trece mai multe directoare). +Dacă doriți ca RobotLoader să sară peste anumite directoare, utilizați `$loader->excludeDirectory('temp')` (poate fi apelat de mai multe ori sau puteți transmite mai multe directoare). -În mod implicit, RobotLoader raportează erorile din fișierele PHP prin aruncarea excepției `ParseError`. Aceasta poate fi dezactivată prin intermediul `$loader->reportParseErrors(false)`. +În mod implicit, RobotLoader raportează erorile din fișierele PHP aruncând excepția `ParseError`. Acest lucru poate fi suprimat folosind `$loader->reportParseErrors(false)`. -Aplicația Nette .[#toc-nette-application] ------------------------------------------ +Aplicație Nette +--------------- -În cadrul aplicației Nette, unde `$configurator` este utilizat în `Bootstrap.php`, puteți configura RobotLoader în acest mod: +În interiorul unei aplicații Nette, unde se utilizează obiectul `$configurator` în fișierul de pornire `Bootstrap.php`, scrierea poate fi simplificată: ```php $configurator = new Nette\Bootstrap\Configurator; @@ -73,57 +85,58 @@ $configurator->createRobotLoader() ``` -Analizator de fișiere PHP .[#toc-php-files-analyzer] ----------------------------------------------------- +Analizator de fișiere PHP +------------------------- -RobotLoader poate fi, de asemenea, utilizat pur și simplu pentru a găsi clase, interfețe și trăsături în fișierele PHP **fără** a utiliza funcția de încărcare automată: +RobotLoader poate fi utilizat și pur pentru a căuta clase, interfețe, trait-uri și enumuri în fișiere PHP **fără** a utiliza funcția de autoloading: ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); -// Scanează directoarele pentru clase / interfețe / trăsături +// scanează directoarele pentru clase / interfețe / trait-uri / enumuri $loader->rebuild(); -// Returnează o matrice de perechi clasă => nume de fișier +// returnează un array de perechi clasă => nume fișier $res = $loader->getIndexedClasses(); ``` -Chiar și în cazul unei astfel de utilizări, puteți utiliza memoria cache. Ca urmare, fișierele nemodificate nu vor fi analizate în mod repetat la o nouă scanare: +Chiar și într-o astfel de utilizare, puteți folosi cache-ul. Datorită acestui fapt, la o nouă scanare, fișierele nemodificate nu vor fi analizate din nou: ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); + +// setăm stocarea în cache în directorul 'temp' $loader->setTempDirectory(__DIR__ . '/temp'); -// Scanează directoarele folosind o memorie cache +// scanează directoarele folosind cache-ul $loader->refresh(); -// Returnează o matrice de perechi clasă => nume de fișier +// returnează un array de perechi clasă => nume fișier $res = $loader->getIndexedClasses(); ``` -Caching .[#toc-caching] ------------------------ +Stocarea în cache +----------------- -RobotLoader este foarte rapid pentru că utilizează în mod inteligent memoria cache. +RobotLoader este foarte rapid, deoarece utilizează inteligent cache-ul. -Atunci când dezvoltați cu el, practic nu aveți nicio idee că rulează în fundal. Actualizează continuu memoria cache deoarece știe că clasele și fișierele pot fi create, șterse, redenumite etc. Și nu scanează în mod repetat fișierele nemodificate. +În timpul dezvoltării, practic nu vă dați seama că rulează în fundal. Își actualizează continuu cache-ul, deoarece ia în considerare faptul că clasele și fișierele pot apărea, dispărea, pot fi redenumite etc. Și nu scanează în mod repetat fișierele care nu s-au schimbat. -Pe de altă parte, atunci când este utilizat pe un server de producție, vă recomandăm să dezactivați actualizarea cache-ului utilizând `$loader->setAutoRefresh(false)` (acest lucru se face automat în aplicația Nette), deoarece fișierele nu se modifică. În același timp, este necesar să **curățați memoria cache** atunci când încărcați o nouă versiune pe hosting. +La implementarea pe un server de producție, dimpotrivă, recomandăm dezactivarea actualizării cache-ului folosind `$loader->setAutoRefresh(false)` (în aplicația Nette acest lucru se întâmplă automat), deoarece fișierele nu se schimbă. În același timp, este necesar **să ștergeți cache-ul** la încărcarea unei noi versiuni pe hosting. -Desigur, scanarea inițială a fișierelor, atunci când memoria cache nu există deja, poate dura câteva secunde pentru aplicațiile mari. RobotLoader are încorporată o prevenire împotriva "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. -Aceasta este o situație în care serverul de producție primește un număr mare de cereri concurente și, deoarece memoria cache a RobotLoader nu există încă, toate ar începe să scaneze fișierele. Ceea ce crește brusc utilizarea CPU și a sistemului de fișiere. -Din fericire, RobotLoader funcționează în așa fel încât, pentru mai multe cereri concurente, doar primul fir indexează fișierele, creează o memorie cache, ceilalți așteaptă și apoi folosesc memoria cache. +Scanarea inițială a fișierelor, când cache-ul nu există încă, poate dura, desigur, puțin timp pentru aplicațiile mai mari. RobotLoader are încorporată prevenirea "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Este o situație în care un număr mai mare de cereri concurente ajung pe serverul de producție, care pornesc RobotLoader, și deoarece cache-ul nu există încă, toate ar începe să scaneze fișierele. Ceea ce ar suprasolicita nejustificat serverul. Din fericire, RobotLoader funcționează astfel încât, în cazul mai multor cereri concurente, doar primul fir indexează fișierele, creează cache-ul, celelalte așteaptă și apoi utilizează cache-ul. -PSR-4 .[#toc-psr-4] -------------------- +PSR-4 +----- -În prezent, Composer poate fi utilizat pentru [încărcarea automată |best-practices:composer#autoloading] în conformitate cu PSR-4. Spunând simplu, este un sistem în care spațiile de nume și numele claselor corespund structurii directoarelor și numelor de fișiere, adică `App\Router\RouterFactory` se află în fișierul `/path/to/App/Router/RouterFactory.php`. +Astăzi, se poate utiliza [Composer pentru autoloading |best-practices:composer#Autoloading] respectând PSR-4. Simplificat, este un sistem în care spațiile de nume și numele claselor corespund structurii directoarelor și numelor fișierelor, adică, de exemplu, `App\Core\RouterFactory` va fi în fișierul `/path/to/App/Core/RouterFactory.php`. -RobotLoader nu este legat de nicio structură fixă, prin urmare, este util în situațiile în care nu vă convine ca structura directoarelor să fie concepută ca spații de nume în PHP sau atunci când dezvoltați o aplicație care, din punct de vedere istoric, nu a folosit astfel de convenții. De asemenea, este posibil să se utilizeze ambele încărcătoare împreună. +RobotLoader nu este legat de nicio structură fixă, de aceea este util în situațiile în care nu vă convine complet să aveți o structură de directoare proiectată la fel ca spațiile de nume în PHP, sau când dezvoltați o aplicație care, din punct de vedere istoric, nu utilizează astfel de convenții. Este posibil, de asemenea, să utilizați ambii loaderi împreună. {{leftbar: nette:@menu-topics}} +{{sitename: Documentație Nette}} diff --git a/robot-loader/ru/@home.texy b/robot-loader/ru/@home.texy index 0e2efe5380..75ae1ec3b1 100644 --- a/robot-loader/ru/@home.texy +++ b/robot-loader/ru/@home.texy @@ -1,17 +1,20 @@ -RobotLoader: Класс автозагрузки -******************************* +Nette RobotLoader +***************** <div class=perex> -RobotLoader — это инструмент, обеспечивающий автоматическую загрузку классов для всего приложения, включая библиотеки сторонних разработчиков. +RobotLoader — это инструмент, который обеспечит вам комфорт автоматической загрузки классов для всего вашего приложения, включая сторонние библиотеки. -- избавиться от всех `require` -- загружаются только необходимые скрипты -- не требует строгих соглашений об именовании каталогов или файлов +- избавимся от всех `require` +- будут загружаться только необходимые скрипты +- не требует строгих соглашений по именованию каталогов или файлов +- чрезвычайно быстрый +- никакой ручной актуализации кеша, все происходит автоматически +- зрелая, стабильная и широко используемая библиотека </div> -Так что мы можем забыть об этих знаменитых блоках кода: +Таким образом, мы можем забыть об этих известных блоках кода: ```php require_once 'Utils/Page.php'; @@ -21,46 +24,55 @@ require_once 'Utils/Paginator.php'; ``` -Установка .[#toc-installation] ------------------------------- +Установка +--------- -Загрузите и установите пакет с помощью [Composer|best-practices:composer]: +RobotLoader можно скачать как [один отдельный файл `RobotLoader.php` |https://github.com/nette/robot-loader/raw/standalone/src/RobotLoader/RobotLoader.php], который вы включите с помощью `require` в свой скрипт, и сразу же получите удобную автозагрузку для всего приложения. + +```php +require '/path/to/RobotLoader.php'; + +$loader = new Nette\Loaders\RobotLoader; +// ... +``` + +Если вы создаете приложение, использующее [Composer|best-practices:composer], вы можете установить его с помощью него: ```shell composer require nette/robot-loader ``` -Использование .[#toc-usage] ---------------------------- +Использование +------------- -Подобно тому, как робот Google просматривает и индексирует сайты, [RobotLoader |api:Nette\Loaders\RobotLoader] просматривает все PHP-скрипты и записывает, какие классы и интерфейсы были в них найдены. Затем эти записи сохраняются в кэше и используются при всех последующих запросах. Вам просто нужно указать, какие каталоги индексировать и где сохранять кэш: +Подобно тому, как робот Google обходит и индексирует веб-страницы, так и [RobotLoader |api:Nette\Loaders\RobotLoader] обходит все PHP-скрипты и записывает, какие классы, интерфейсы, трейты и перечисления он в них нашел. Результаты сканирования он затем сохраняет в кеш и использует при следующем запросе. Достаточно лишь указать, какие каталоги он должен обходить и куда сохранять кеш: ```php $loader = new Nette\Loaders\RobotLoader; -// каталоги, которые будут индексироваться RobotLoader (включая подкаталоги) +// каталоги, которые RobotLoader должен индексировать (включая подкаталоги) $loader->addDirectory(__DIR__ . '/app'); $loader->addDirectory(__DIR__ . '/libs'); -// использовать каталог 'temp' для кэша +// установим кеширование в каталог 'temp' $loader->setTempDirectory(__DIR__ . '/temp'); -$loader->register(); // Run the RobotLoader +$loader->register(); // запустим RobotLoader ``` -И это всё. С этого момента вам не нужно использовать `require`. Здорово, не так ли? +И это все, с этого момента нам не нужно использовать `require`. Отлично! -Когда RobotLoader сталкивается с дублированием имени класса во время индексирования, он выбрасывает исключение и сообщает вам об этом. RobotLoader также автоматически обновляет кэш, когда ему нужно загрузить неизвестный ему класс. Мы рекомендуем отключить это на производственных серверах, см. [#Кэширование]. +Если RobotLoader при индексации столкнется с дублирующимся именем класса, он выбросит исключение и сообщит вам об этом. RobotLoader также автоматически обновляет кеш, когда ему нужно загрузить класс, который он не знает. Это рекомендуется отключать на production-серверах, см. [##Кеширование]. -Если вы хотите, чтобы RobotLoader пропускал некоторые каталоги, используйте `$loader->excludeDirectory('temp')` (его можно вызвать несколько раз или передать несколько каталогов). +Если вы хотите, чтобы RobotLoader пропустил какие-то каталоги, используйте `$loader->excludeDirectory('temp')` (можно вызывать многократно или передать несколько каталогов). -По умолчанию RobotLoader сообщает об ошибках в PHP-файлах, бросая исключение `ParseError`. Его можно отключить с помощью `$loader->reportParseErrors(false)`. +По умолчанию RobotLoader сообщает об ошибках в PHP-файлах, выбрасывая исключение `ParseError`. Это можно подавить с помощью `$loader->reportParseErrors(false)`. -Приложение Nette .[#toc-nette-application] ------------------------------------------- +Приложение Nette +---------------- -Внутри приложения Nette, где `$configurator` используется в `Bootstrap.php`, вы можете настроить RobotLoader таким образом: +Внутри приложения Nette, где в загрузочном файле `Bootstrap.php` используется объект `$configurator`, запись можно упростить: ```php $configurator = new Nette\Bootstrap\Configurator; @@ -73,57 +85,58 @@ $configurator->createRobotLoader() ``` -Анализатор файлов PHP .[#toc-php-files-analyzer] ------------------------------------------------- +Анализатор PHP-файлов +--------------------- -RobotLoader также можно использовать чисто для поиска классов, интерфейсов и трейтов в PHP-файлах **без** использования функции автозагрузки: +RobotLoader можно также использовать исключительно для поиска классов, интерфейсов, трейтов и перечислений в PHP-файлах **без** использования функции автозагрузки: ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); -// Сканирует каталоги на наличие классов / интерфейсов / трейтов +// просканирует каталоги на наличие классов / интерфейсов / трейтов / перечислений $loader->rebuild(); -// Возвращает массив пар класс => имя файла +// возвращает массив пар класс => имя файла $res = $loader->getIndexedClasses(); ``` -Даже при таком использовании вы можете использовать кэш. В результате немодифицированные файлы не будут повторно анализироваться при повторном сканировании: +Даже при таком использовании вы можете использовать кеш. Благодаря этому при повторном сканировании не будут повторно анализироваться неизмененные файлы: ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); + +// установим кеширование в каталог 'temp' $loader->setTempDirectory(__DIR__ . '/temp'); -// Сканирование каталогов с использованием кэша +// просканирует каталоги с использованием кеша $loader->refresh(); -// Возвращает массив пар класс => имя файла +// возвращает массив пар класс => имя файла $res = $loader->getIndexedClasses(); ``` -Кэширование .[#toc-caching] ---------------------------- +Кеширование +----------- -RobotLoader работает очень быстро, потому что он разумно использует кэш. +RobotLoader очень быстр, потому что он умно использует кеш. -При разработке с ним вы практически не замечаете, что он работает в фоновом режиме. Он постоянно обновляет кэш, поскольку знает, что классы и файлы могут быть созданы, удалены, переименованы и т. д. И он не сканирует повторно немодифицированные файлы. +При разработке вы практически не замечаете, что он работает в фоновом режиме. Он постоянно обновляет кеш, так как учитывает, что классы и файлы могут создаваться, удаляться, переименовываться и т.д. И он повторно не сканирует файлы, которые не изменились. -При использовании на рабочем сервере, с другой стороны, мы рекомендуем отключить обновление кэша с помощью `$loader->setAutoRefresh(false)` (это делается автоматически в приложении Nette), поскольку файлы не меняются. В то же время, необходимо **очистить кэш** при загрузке новой версии на хостинг. +При развертывании на production-сервере, наоборот, рекомендуется отключить обновление кеша с помощью `$loader->setAutoRefresh(false)` (в приложении Nette это происходит автоматически), так как файлы не меняются. При этом необходимо при загрузке новой версии на хостинг **очистить кеш.** -Конечно, первоначальное сканирование файлов, когда кэш ещё не существует, может занять несколько секунд для больших приложений. RobotLoader имеет встроенную защиту от "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. -Это ситуация, когда производственный сервер получает большое количество одновременных запросов, и поскольку кэш RobotLoader ещё не существует, все они начнут сканировать файлы. Это увеличивает нагрузку на процессор и файловую систему. -К счастью, RobotLoader работает таким образом, что при нескольких одновременных запросах только первый поток индексирует файлы, создает кэш, остальные ждут, а затем используют кэш. +Первоначальное сканирование файлов, когда кеш еще не существует, может у более крупных приложений, естественно, занять некоторое время. RobotLoader имеет встроенную защиту от "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Это ситуация, когда на production-сервере собирается большое количество одновременных запросов, которые запускают RobotLoader, и поскольку кеш еще не существует, все они начали бы сканировать файлы. Что непропорционально нагрузило бы сервер. К счастью, RobotLoader работает так, что при нескольких одновременных запросах файлы индексирует только первый поток, создает кеш, остальные ждут и впоследствии используют кеш. PSR-4 ----- -Сегодня Composer можно использовать для [автозагрузки|best-practices:composer#Autoloading] в соответствии с PSR-4. Проще говоря, это система, в которой пространства имен и имена классов соответствуют структуре каталогов и именам файлов, т. е. `App\Router\RouterFactory` находится в файле `/path/to/App/Router/RouterFactory.php`. +Сегодня для [автозагрузки можно использовать Composer |best-practices:composer#Автозагрузка] при соблюдении PSR-4. Проще говоря, это система, когда пространства имен и имена классов соответствуют структуре каталогов и именам файлов, то есть, например, `App\Core\RouterFactory` будет в файле `/path/to/App/Core/RouterFactory.php`. -RobotLoader не привязан к какой-либо фиксированной структуре, поэтому он полезен в ситуациях, когда вам не подходит структура каталогов, оформленная в виде пространств имен в PHP, или когда вы разрабатываете приложение, в котором исторически не используются такие соглашения. Также можно использовать оба загрузчика вместе. +RobotLoader не связан ни с какой фиксированной структурой, поэтому он подходит в ситуациях, когда вам не совсем удобно иметь одинаково спроектированную структуру каталогов, как пространства имен в PHP, или когда вы разрабатываете приложение, которое исторически таких соглашений не использует. Также возможно использовать оба загрузчика вместе. {{leftbar: nette:@menu-topics}} +{{sitename: Документация Nette}} diff --git a/robot-loader/sl/@home.texy b/robot-loader/sl/@home.texy index 47d3bab6ec..c4e79bac9f 100644 --- a/robot-loader/sl/@home.texy +++ b/robot-loader/sl/@home.texy @@ -1,17 +1,20 @@ -RobotLoader: Samodejno nalaganje razreda -**************************************** +Nette RobotLoader +***************** <div class=perex> -RobotLoader je orodje, ki omogoča samodejno nalaganje razredov za celotno aplikacijo, vključno s knjižnicami tretjih oseb. +RobotLoader je orodje, ki vam zagotavlja udobje samodejnega nalaganja razredov za celotno vašo aplikacijo, vključno s knjižnicami tretjih oseb. -- znebite se vseh `require` -- naložijo se samo potrebni skripti -- ne zahteva strogih konvencij za poimenovanje imenikov ali datotek +- znebimo se vseh `require` +- nalagale se bodo samo potrebne skripte +- ne zahteva strogih konvencij poimenovanja imenikov ali datotek +- izjemno hitro +- brez ročnega posodabljanja predpomnilnika, vse poteka samodejno +- zrela, stabilna in široko uporabljana knjižnica </div> -Zato lahko pozabimo na tiste znamenite bloke kode: +Lahko torej pozabimo na te znane bloke kode: ```php require_once 'Utils/Page.php'; @@ -21,46 +24,55 @@ require_once 'Utils/Paginator.php'; ``` -Namestitev .[#toc-installation] -------------------------------- +Namestitev +---------- -Prenesite in namestite paket s [programom Composer |best-practices:composer]: +RobotLoader si lahko prenesete kot [eno samostojno datoteko `RobotLoader.php` |https://github.com/nette/robot-loader/raw/standalone/src/RobotLoader/RobotLoader.php], ki jo vključite z `require` v svojo skripto in takoj imate na voljo udobno samodejno nalaganje za celotno aplikacijo. + +```php +require '/path/to/RobotLoader.php'; + +$loader = new Nette\Loaders\RobotLoader; +// ... +``` + +Če gradite aplikacijo, ki uporablja [Composer|best-practices:composer], ga lahko namestite z njim: ```shell composer require nette/robot-loader ``` -Uporaba .[#toc-usage] ---------------------- +Uporaba +------- -Podobno kot robot Google preiskuje in indeksira spletna mesta, [RobotLoader |api:Nette\Loaders\RobotLoader] preiskuje vse skripte PHP in beleži, katere razrede in vmesnike je našel v njih. Ti zapisi se nato shranijo v predpomnilnik in uporabijo pri vseh naslednjih zahtevah. Določiti morate le, katere imenike indeksirati in kam shraniti predpomnilnik: +Podobno, kot Google robot pregleduje in indeksira spletne strani, tako tudi [RobotLoader |api:Nette\Loaders\RobotLoader] pregleduje vse PHP skripte in si beleži, katere razrede, vmesnike, lastnosti in enume je v njih našel. Rezultate iskanja si nato shrani v predpomnilnik in jih uporabi pri naslednji zahtevi. Dovolj je torej določiti, katere imenike naj pregleduje in kam naj shranjuje predpomnilnik: ```php $loader = new Nette\Loaders\RobotLoader; -// imeniki, ki jih bo indeksiral RobotLoader (vključno s podimeniki). +// imeniki, ki jih naj RobotLoader indeksira (vključno s podimeniki) $loader->addDirectory(__DIR__ . '/app'); $loader->addDirectory(__DIR__ . '/libs'); -// uporabite imenik 'temp' za predpomnilnik +// nastavimo predpomnjenje v imenik 'temp' $loader->setTempDirectory(__DIR__ . '/temp'); -$loader->register(); // Zaženite RobotLoader +$loader->register(); // zaženemo RobotLoader ``` -In to je vse. Od zdaj naprej vam ni treba več uporabljati `require`. Super, kajne? +In to je vse, od te točke naprej nam ni treba uporabljati `require`. Odlično! -Ko RobotLoader med indeksiranjem naleti na podvojeno ime razreda, vrže izjemo in vas o tem obvesti. RobotLoader tudi samodejno posodobi predpomnilnik, kadar mora naložiti razred, ki ga ne pozna. Priporočamo, da to onemogočite v produkcijskih strežnikih, glejte poglavje [Predpomnilnik |#Caching]. +Če RobotLoader med indeksiranjem naleti na podvojeno ime razreda, vrže izjemo in vas o tem obvesti. RobotLoader tudi samodejno posodobi predpomnilnik, ko mora naložiti razred, ki ga ne pozna. To priporočamo izklopiti na produkcijskih strežnikih, glejte [##Predpomnjenje]. -Če želite, da RobotLoader preskoči nekatere imenike, uporabite `$loader->excludeDirectory('temp')` (lahko ga pokličete večkrat ali posredujete več imenikov). +Če želite, da RobotLoader preskoči nekatere imenike, uporabite `$loader->excludeDirectory('temp')` (lahko kličete večkrat ali predate več imenikov). -Privzeto RobotLoader poroča o napakah v datotekah PHP tako, da vrže izjemo `ParseError`. To lahko onemogočite prek `$loader->reportParseErrors(false)`. +Privzeto RobotLoader poroča o napakah v PHP datotekah z metanjem izjeme `ParseError`. To lahko potlačite z `$loader->reportParseErrors(false)`. -Nette aplikacija .[#toc-nette-application] ------------------------------------------- +Nette aplikacija +---------------- -Znotraj aplikacije Nette, kjer se `$configurator` uporablja v `Bootstrap.php`, lahko RobotLoader nastavite na ta način: +Znotraj Nette aplikacije, kjer se v zagonski datoteki `Bootstrap.php` uporablja objekt `$configurator`, lahko zapis poenostavimo: ```php $configurator = new Nette\Bootstrap\Configurator; @@ -73,57 +85,58 @@ $configurator->createRobotLoader() ``` -PHP Files Analyzer .[#toc-php-files-analyzer] ---------------------------------------------- +Analizator PHP datotek +---------------------- -RobotLoader lahko uporabite tudi za iskanje razredov, vmesnikov in lastnosti v datotekah PHP, ne da bi uporabili funkcijo samodejnega nalaganja: +RobotLoader lahko uporabite tudi zgolj za iskanje razredov, vmesnikov, lastnosti in enumov v PHP datotekah **brez** uporabe funkcije samodejnega nalaganja: ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); -// Pregledovanje imenikov za razrede / vmesnike / lastnosti +// preišče imenike za razrede / vmesnike / lastnosti / enume $loader->rebuild(); -// Vrne polje parov razred => ime datoteke +// vrne polje parov razred => ime datoteke $res = $loader->getIndexedClasses(); ``` -Tudi pri takšni uporabi lahko uporabite predpomnilnik. Zaradi tega se nespremenjene datoteke pri ponovnem iskanju ne bodo večkrat analizirale: +Tudi pri takšni uporabi lahko izkoristite predpomnilnik. Zahvaljujoč temu pri ponovnem skeniranju ne bodo ponovno analizirane nespremenjene datoteke: ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); + +// nastavimo predpomnjenje v imenik 'temp' $loader->setTempDirectory(__DIR__ . '/temp'); -// Pregleduje imenike z uporabo predpomnilnika +// preišče imenike z uporabo predpomnilnika $loader->refresh(); -// Vrne polje parov razred => ime datoteke +// vrne polje parov razred => ime datoteke $res = $loader->getIndexedClasses(); ``` -Predpomnilnik .[#toc-caching] ------------------------------ +Predpomnjenje +------------- -RobotLoader je zelo hiter, ker pametno uporablja predpomnilnik. +RobotLoader je zelo hiter, ker spretno uporablja predpomnilnik. -Pri razvijanju z njim praktično ne veste, da deluje v ozadju. Sprotno posodablja predpomnilnik, saj ve, da se razredi in datoteke lahko ustvarjajo, brišejo, preimenujejo itd. In ne pregleduje nespremenjenih datotek večkrat. +Med razvojem z njim praktično ne veste, da teče v ozadju. Sproti si posodablja predpomnilnik, ker računa s tem, da lahko razredi in datoteke nastajajo, izginjajo, se preimenujejo itd. In ponovno ne skenira datotek, ki se niso spremenile. -Pri uporabi v produkcijskem strežniku pa priporočamo, da posodobitev predpomnilnika onemogočite z uporabo spletne strani `$loader->setAutoRefresh(false)` (to se samodejno izvede v aplikaciji Nette), saj se datoteke ne spreminjajo. Hkrati je treba **izbrisati predpomnilnik** ob prenosu nove različice na gostovanje. +Pri namestitvi na produkcijskem strežniku pa nasprotno priporočamo izklop posodabljanja predpomnilnika z uporabo `$loader->setAutoRefresh(false)` (v Nette Aplikaciji se to zgodi samodejno), ker se datoteke ne spreminjajo. Hkrati je potem treba ob nalaganju nove različice na gostovanje **izbrisati predpomnilnik.** -Seveda lahko začetno pregledovanje datotek, ko predpomnilnik še ne obstaja, pri večjih aplikacijah traja nekaj sekund. RobotLoader ima vgrajeno preprečevanje "stampeda predpomnilnika":https://en.wikipedia.org/wiki/Cache_stampede. -To je situacija, ko produkcijski strežnik prejme veliko število hkratnih zahtevkov in ker predpomnilnik RobotLoader še ne obstaja, bi vsi začeli skenirati datoteke. To poveča porabo procesorja in datotečnega sistema. -Na srečo RobotLoader deluje tako, da pri več hkratnih zahtevah le prva nit indeksira datoteke, ustvari predpomnilnik, druge počakajo in nato uporabijo predpomnilnik. +Prvotno skeniranje datotek, ko predpomnilnik še ne obstaja, lahko pri obsežnejših aplikacijah seveda traja nekaj časa. RobotLoader ima vgrajeno preprečevanje pred "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Gre za situacijo, ko se na produkcijskem strežniku zbere večje število sočasnih zahtev, ki zaženejo RobotLoader, in ker predpomnilnik še ne obstaja, bi vsi začeli skenirati datoteke. Kar bi nesorazmerno obremenilo strežnik. Na srečo RobotLoader deluje tako, da pri več sočasnih zahtevah indeksira datoteke samo prva nit, ustvari predpomnilnik, ostali čakajo in nato predpomnilnik uporabijo. -PSR-4 .[#toc-psr-4] -------------------- +PSR-4 +----- -Danes se lahko program Composer uporablja za [samodejno polnjenje v |best-practices:composer#autoloading] skladu s PSR-4. Preprosto povedano, gre za sistem, v katerem imenska območja in imena razredov ustrezajo imeniški strukturi in imenom datotek, tj. `App\Router\RouterFactory` se nahaja v datoteki `/path/to/App/Router/RouterFactory.php`. +Danes lahko za [samodejno nalaganje uporabljate Composer |best-practices:composer#Samodejno nalaganje] ob upoštevanju PSR-4. Poenostavljeno rečeno gre za sistem, kjer imenski prostori in imena razredov ustrezajo strukturi imenikov in imenom datotek, torej npr. `App\Core\RouterFactory` bo v datoteki `/path/to/App/Core/RouterFactory.php`. -RobotLoader ni vezan na nobeno fiksno strukturo, zato je uporaben v primerih, ko vam ne ustreza, da je struktura imenikov zasnovana kot imenski prostori v PHP, ali ko razvijate aplikacijo, ki v preteklosti ni uporabljala takih konvencij. Oba nalagalnika je mogoče uporabljati tudi skupaj. +RobotLoader ni vezan na nobeno fiksno strukturo, zato je primeren v situacijah, ko vam popolnoma ne ustreza imeti enako zasnovano strukturo imenikov kot imenske prostore v PHP, ali ko razvijate aplikacijo, ki zgodovinsko takšnih konvencij ne uporablja. Možno je tudi uporabljati oba nalagalnika skupaj. {{leftbar: nette:@menu-topics}} +{{sitename: Nette Dokumentacija}} diff --git a/robot-loader/tr/@home.texy b/robot-loader/tr/@home.texy index 67fd5162ed..7b062509a0 100644 --- a/robot-loader/tr/@home.texy +++ b/robot-loader/tr/@home.texy @@ -1,17 +1,20 @@ -RobotLoader: Sınıf Otomatik Yükleme -*********************************** +Nette RobotLoader +***************** <div class=perex> -RobotLoader, üçüncü taraf kütüphaneleri de dahil olmak üzere tüm uygulamanız için otomatik sınıf yükleme rahatlığı sağlayan bir araçtır. +RobotLoader, üçüncü taraf kütüphaneler dahil tüm uygulamanız için sınıfların otomatik olarak yüklenmesi rahatlığını sağlayan bir araçtır. -- hepsinden kurtulun `require` -- sadece gerekli komut dosyaları yüklenir -- katı dizin veya dosya adlandırma kuralları gerektirmez +- tüm `require`'lardan kurtulacağız +- yalnızca gerekli betikler yüklenecek +- dizinlerin veya dosyaların katı adlandırma kuralları gerektirmez +- son derece hızlı +- manuel önbellek güncellemesi yok, her şey otomatik olarak gerçekleşir +- olgun, kararlı ve yaygın olarak kullanılan bir kütüphane </div> -Bu yüzden o meşhur kod bloklarını unutabiliriz: +Bu nedenle, bu bilinen kod bloklarını unutabiliriz: ```php require_once 'Utils/Page.php'; @@ -21,46 +24,55 @@ require_once 'Utils/Paginator.php'; ``` -Kurulum .[#toc-installation] ----------------------------- +Kurulum +------- -[Composer'ı |best-practices:composer] kullanarak paketi indirin ve yükleyin: +RobotLoader'ı [`RobotLoader.php` |https://github.com/nette/robot-loader/raw/standalone/src/RobotLoader/RobotLoader.php] olarak tek bir bağımsız dosya olarak indirebilir, betiğinize `require` ile ekleyebilir ve hemen tüm uygulama için rahat otomatik yüklemeye sahip olabilirsiniz. + +```php +require '/path/to/RobotLoader.php'; + +$loader = new Nette\Loaders\RobotLoader; +// ... +``` + +[Composer|best-practices:composer] kullanan bir uygulama oluşturuyorsanız, onu kullanarak yükleyebilirsiniz: ```shell composer require nette/robot-loader ``` -Kullanım .[#toc-usage] ----------------------- +Kullanım +-------- -Google robotunun web sitelerini taraması ve indekslemesi gibi, [RobotLoader |api:Nette\Loaders\RobotLoader] da tüm PHP betiklerini tarar ve içlerinde hangi sınıfların ve arayüzlerin bulunduğunu kaydeder. Bu kayıtlar daha sonra önbelleğe kaydedilir ve sonraki tüm istekler sırasında kullanılır. Sadece hangi dizinlerin indeksleneceğini ve önbelleğin nereye kaydedileceğini belirtmeniz gerekir: +Google robotunun web sayfalarını tarayıp indekslemesi gibi, [RobotLoader |api:Nette\Loaders\RobotLoader] da tüm PHP betiklerini tarar ve içlerinde hangi sınıfları, arayüzleri, trait'leri ve enumları bulduğunu kaydeder. Araştırmasının sonuçlarını daha sonra önbelleğe kaydeder ve bir sonraki istekte kullanır. Bu nedenle, hangi dizinleri tarayacağını ve önbelleği nereye kaydedeceğini belirtmek yeterlidir: ```php $loader = new Nette\Loaders\RobotLoader; -// RobotLoader tarafından indekslenecek dizinler (alt dizinler dahil) +// RobotLoader'ın indeksleyeceği dizinler (alt dizinler dahil) $loader->addDirectory(__DIR__ . '/app'); $loader->addDirectory(__DIR__ . '/libs'); -// önbellek için 'temp' dizinini kullan +// önbelleklemeyi 'temp' dizinine ayarlayın $loader->setTempDirectory(__DIR__ . '/temp'); -$loader->register(); // RobotLoader'ı çalıştır +$loader->register(); // RobotLoader'ı başlatın ``` -Ve hepsi bu kadar. Şu andan itibaren `require` adresini kullanmanıza gerek yok. Harika, değil mi? +Ve hepsi bu, bu andan itibaren `require` kullanmamıza gerek yok. Harika! -RobotLoader, indeksleme sırasında yinelenen sınıf adıyla karşılaştığında, bir istisna atar ve sizi bu konuda bilgilendirir. RobotLoader ayrıca bilmediği bir sınıfı yüklemek zorunda kaldığında önbelleği otomatik olarak günceller. Üretim sunucularında bunu [devre |#Caching] dışı bırakmanızı öneririz, bkz. +RobotLoader indeksleme sırasında yinelenen bir sınıf adıyla karşılaşırsa, bir istisna fırlatır ve sizi bilgilendirir. RobotLoader ayrıca bilmediği bir sınıfı yüklemesi gerektiğinde önbelleği otomatik olarak günceller. Bunu üretim sunucularında kapatmanızı öneririz, bkz. [##Önbellekleme]. -RobotLoader'ın bazı dizinleri atlamasını istiyorsanız, `$loader->excludeDirectory('temp')` adresini kullanın (birden çok kez çağrılabilir veya birden çok dizin geçebilirsiniz). +RobotLoader'ın bazı dizinleri atlamasını istiyorsanız, `$loader->excludeDirectory('temp')` kullanın (birden çok kez çağrılabilir veya birden çok dizin iletilebilir). -Varsayılan olarak, RobotLoader PHP dosyalarındaki hataları `ParseError` istisnası atarak bildirir. `$loader->reportParseErrors(false)` üzerinden devre dışı bırakılabilir. +Varsayılan olarak, RobotLoader PHP dosyalarındaki hataları `ParseError` istisnası fırlatarak bildirir. Bu, `$loader->reportParseErrors(false)` kullanılarak bastırılabilir. -Nette Uygulama .[#toc-nette-application] ----------------------------------------- +Nette uygulaması +---------------- -Nette uygulaması içinde, `$configurator` adresinin kullanıldığı `Bootstrap.php` adresinde RobotLoader'ı bu şekilde kurabilirsiniz: +Başlatıcı dosya `Bootstrap.php` içinde `$configurator` nesnesinin kullanıldığı Nette uygulamasında, yazım basitleştirilebilir: ```php $configurator = new Nette\Bootstrap\Configurator; @@ -73,57 +85,58 @@ $configurator->createRobotLoader() ``` -PHP Dosya Çözümleyicisi .[#toc-php-files-analyzer] --------------------------------------------------- +PHP Dosya Ayrıştırıcısı +----------------------- -RobotLoader, otomatik yükleme özelliğini kullanmadan ** PHP dosyalarındaki sınıfları, arayüzleri ve özellikleri bulmak için de kullanılabilir: +RobotLoader ayrıca PHP dosyalarında sınıfları, arayüzleri, trait'leri ve enumları bulmak için **otomatik yükleme işlevini kullanmadan** saf olarak da kullanılabilir: ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); -// Sınıflar / arayüzler / özellikler için dizinleri tarar +// sınıflar / arayüzler / trait'ler / enumlar için dizinleri tara $loader->rebuild(); -// Sınıf => dosya adı çiftlerinden oluşan bir dizi döndürür +// sınıf => dosya adı çiftleri dizisi döndürür $res = $loader->getIndexedClasses(); ``` -Böyle bir kullanımda bile önbelleği kullanabilirsiniz. Sonuç olarak, değiştirilmemiş dosyalar yeniden taranırken tekrar tekrar analiz edilmeyecektir: +Böyle bir kullanımda bile önbelleği kullanabilirsiniz. Bu sayede, yeniden tarama sırasında değiştirilmemiş dosyalar tekrar tekrar analiz edilmeyecektir: ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); + +// önbelleklemeyi 'temp' dizinine ayarlayın $loader->setTempDirectory(__DIR__ . '/temp'); -// Bir önbellek kullanarak dizinleri tarar +// önbelleği kullanarak dizinleri tara $loader->refresh(); -// Sınıf => dosya adı çiftlerinden oluşan bir dizi döndürür +// sınıf => dosya adı çiftleri dizisi döndürür $res = $loader->getIndexedClasses(); ``` -Önbellekleme .[#toc-caching] ----------------------------- +Önbellekleme +------------ RobotLoader çok hızlıdır çünkü önbelleği akıllıca kullanır. -Onunla geliştirme yaparken, arka planda çalıştığına dair neredeyse hiçbir fikriniz olmaz. Sınıfların ve dosyaların oluşturulabileceğini, silinebileceğini, yeniden adlandırılabileceğini vb. bildiği için önbelleği sürekli olarak günceller. Ve değiştirilmemiş dosyaları tekrar tekrar taramaz. +Geliştirme sırasında, pratikte arka planda çalıştığını fark etmezsiniz. Sınıfların ve dosyaların oluşturulabileceği, yok olabileceği, yeniden adlandırılabileceği vb. beklentisiyle önbelleği sürekli olarak günceller. Ve değişmemiş dosyaları tekrar tekrar taramaz. -Öte yandan, bir üretim sunucusunda kullanıldığında, dosyalar değişmediği için `$loader->setAutoRefresh(false)` adresini kullanarak önbellek güncellemesini devre dışı bırakmanızı öneririz (bu, Nette Uygulamasında otomatik olarak yapılır). Aynı zamanda, barındırmaya yeni bir sürüm yüklerken **önbelleği temizlemek** gerekir. +Üretim sunucusuna dağıtım yaparken ise, dosyalar değişmediği için önbellek güncellemesini `$loader->setAutoRefresh(false)` kullanarak kapatmanızı öneririz (Nette Uygulamasında bu otomatik olarak yapılır). Aynı zamanda, barındırmaya yeni bir sürüm yüklerken **önbelleği silmek** gerekir. -Elbette, önbellek zaten mevcut olmadığında dosyaların ilk taranması, daha büyük uygulamalar için birkaç saniye sürebilir. RobotLoader, "önbellek izdihamına":https://en.wikipedia.org/wiki/Cache_stampede karşı yerleşik bir önleme sahiptir. -Bu, üretim sunucusunun çok sayıda eşzamanlı istek aldığı bir durumdur ve RobotLoader'ın önbelleği henüz mevcut olmadığından, hepsi dosyaları taramaya başlayacaktır. Bu da CPU ve dosya sistemi kullanımını artırır. -Neyse ki, RobotLoader, birden fazla eşzamanlı istek için, yalnızca ilk iş parçacığı dosyaları indeksleyecek, bir önbellek oluşturacak, diğerleri bekleyecek ve ardından önbelleği kullanacak şekilde çalışır. +Önbellek henüz mevcut olmadığında dosyaların ilk taranması, daha büyük uygulamalar için doğal olarak biraz zaman alabilir. RobotLoader, "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede'ye karşı yerleşik bir önleme sahiptir. Bu, üretim sunucusunda RobotLoader'ı başlatan daha fazla sayıda eşzamanlı isteğin bir araya geldiği ve önbellek henüz mevcut olmadığı için hepsinin dosyaları taramaya başlayacağı bir durumdur. Bu, sunucuyu aşırı derecede yüklerdi. Neyse ki, RobotLoader öyle çalışır ki, birden fazla eşzamanlı istek olduğunda, dosyaları yalnızca ilk iş parçacığı indeksler, önbelleği oluşturur, diğerleri bekler ve ardından önbelleği kullanır. -PSR-4 .[#toc-psr-4] -------------------- +PSR-4 +----- -Günümüzde Composer, PSR-4'e uygun olarak [otomatik yükleme |best-practices:composer#autoloading] için kullanılabilmektedir. Basitçe söylemek gerekirse, isim alanlarının ve sınıf isimlerinin dizin yapısına ve dosya isimlerine karşılık geldiği bir sistemdir, yani `App\Router\RouterFactory` `/path/to/App/Router/RouterFactory.php` dosyasında bulunur. +Bugün, PSR-4'e uyarken [otomatik yükleme için Composer kullanılabilir |best-practices:composer#Otomatik Yükleme Autoloading]. Basitçe söylemek gerekirse, bu, ad alanlarının ve sınıf adlarının dizin yapısına ve dosya adlarına karşılık geldiği bir sistemdir, yani örneğin `App\Core\RouterFactory` `/path/to/App/Core/RouterFactory.php` dosyasında olacaktır. -RobotLoader herhangi bir sabit yapıya bağlı değildir, bu nedenle PHP'de ad alanları olarak tasarlanmış dizin yapısına sahip olmanın size uygun olmadığı durumlarda veya tarihsel olarak bu tür sözleşmeleri kullanmayan bir uygulama geliştirirken kullanışlıdır. Her iki yükleyiciyi birlikte kullanmak da mümkündür. +RobotLoader herhangi bir sabit yapıya bağlı değildir, bu nedenle PHP'deki ad alanlarıyla aynı şekilde tasarlanmış bir dizin yapısına sahip olmanın tam olarak uygun olmadığı veya tarihsel olarak bu tür kuralları kullanmayan bir uygulama geliştirirken kullanışlıdır. Her iki yükleyiciyi birlikte kullanmak da mümkündür. {{leftbar: nette:@menu-topics}} +{{sitename: Nette Dokümantasyonu}} diff --git a/robot-loader/uk/@home.texy b/robot-loader/uk/@home.texy index 1e07799bcc..784c9438ef 100644 --- a/robot-loader/uk/@home.texy +++ b/robot-loader/uk/@home.texy @@ -1,17 +1,20 @@ -RobotLoader: Клас автозавантаження -********************************** +Nette RobotLoader +***************** <div class=perex> -RobotLoader - це інструмент, що забезпечує автоматичне завантаження класів для всієї програми, включно з бібліотеками сторонніх розробників. +RobotLoader — це інструмент, який забезпечить вам комфорт автоматичного завантаження класів для всієї вашої програми, включаючи бібліотеки сторонніх розробників. -- позбутися всіх `require` -- завантажуються тільки необхідні скрипти -- не вимагає суворих угод про іменування каталогів або файлів +- позбудемося всіх `require` +- будуть завантажуватися лише необхідні скрипти +- не вимагає суворих конвенцій іменування каталогів чи файлів +- надзвичайно швидкий +- жодних ручних оновлень кешу, все відбувається автоматично +- зріла, стабільна та широко використовувана бібліотека </div> -Тож ми можемо забути про ці знамениті блоки коду: +Тож ми можемо забути про ці відомі блоки коду: ```php require_once 'Utils/Page.php'; @@ -21,46 +24,55 @@ require_once 'Utils/Paginator.php'; ``` -Встановлення .[#toc-installation] ---------------------------------- +Встановлення +------------ -Завантажте та встановіть пакунок за допомогою [Composer|best-practices:composer]: +RobotLoader можна завантажити як [один окремий файл `RobotLoader.php` |https://github.com/nette/robot-loader/raw/standalone/src/RobotLoader/RobotLoader.php], який ви вставите за допомогою `require` до свого скрипта і одразу матимете зручне автозавантаження для всієї програми. + +```php +require '/path/to/RobotLoader.php'; + +$loader = new Nette\Loaders\RobotLoader; +// ... +``` + +Якщо ви створюєте програму, що використовує [Composer|best-practices:composer], ви можете встановити його за допомогою нього: ```shell composer require nette/robot-loader ``` -Використання .[#toc-usage] --------------------------- +Використання +------------ -Подібно до того, як робот Google переглядає та індексує сайти, [RobotLoader |api:Nette\Loaders\RobotLoader] переглядає всі PHP-скрипти і записує, які класи та інтерфейси були в них знайдені. Потім ці записи зберігаються в кеші та використовуються під час усіх наступних запитів. Вам просто потрібно вказати, які каталоги індексувати і де зберігати кеш: +Подібно до того, як робот Google сканує та індексує веб-сторінки, так і [RobotLoader |api:Nette\Loaders\RobotLoader] сканує всі PHP-скрипти та записує, які класи, інтерфейси, трейти та enum він у них знайшов. Результати дослідження він потім зберігає в кеші та використовує при наступному запиті. Достатньо лише вказати, які каталоги він має сканувати та куди зберігати кеш: ```php $loader = new Nette\Loaders\RobotLoader; -// каталоги, які індексуватимуться RobotLoader (включно з підкаталогами) +// каталоги, які RobotLoader має індексувати (включаючи підкаталоги) $loader->addDirectory(__DIR__ . '/app'); $loader->addDirectory(__DIR__ . '/libs'); -// використовувати каталог 'temp' для кешу +// налаштуємо кешування до каталогу 'temp' $loader->setTempDirectory(__DIR__ . '/temp'); -$loader->register(); // Запустити RobotLoader +$loader->register(); // запустимо RobotLoader ``` -І це все. З цього моменту вам не потрібно використовувати `require`. Чудово, чи не так? +І це все, з цього моменту нам не потрібно використовувати `require`. Чудово! -Коли RobotLoader стикається з дублюванням імені класу під час індексування, він викидає виняток і повідомляє вам про це. RobotLoader також автоматично оновлює кеш, коли йому потрібно завантажити невідомий йому клас. Ми рекомендуємо вимкнути це на виробничих серверах, див. [Кешування |#Кэширование]. +Якщо RobotLoader натрапить під час індексації на дублікат назви класу, він викине виняток і повідомить вас про це. RobotLoader також автоматично оновлює кеш, коли має завантажити клас, який він не знає. Це рекомендується вимкнути на робочих серверах, див. [#Кешування]. -Якщо ви хочете, щоб RobotLoader пропускав деякі каталоги, використовуйте `$loader->excludeDirectory('temp')` (його можна викликати кілька разів або передати кілька каталогів). +Якщо ви хочете, щоб RobotLoader пропустив деякі каталоги, використовуйте `$loader->excludeDirectory('temp')` (можна викликати кілька разів або передати кілька каталогів). -За замовчуванням RobotLoader повідомляє про помилки в PHP-файлах, кидаючи виняток `ParseError`. Його можна відключити за допомогою `$loader->reportParseErrors(false)`. +За замовчуванням RobotLoader повідомляє про помилки у файлах PHP, викидаючи виняток `ParseError`. Це можна придушити за допомогою `$loader->reportParseErrors(false)`. -Додаток Nette .[#toc-nette-application] ---------------------------------------- +Nette програма +-------------- -Усередині програми Nette, де `$configurator` використовується в `Bootstrap.php`, ви можете налаштувати RobotLoader таким чином: +Всередині Nette програми, де використовується в завантажувальному файлі `Bootstrap.php` об'єкт `$configurator`, запис можна спростити: ```php $configurator = new Nette\Bootstrap\Configurator; @@ -73,57 +85,58 @@ $configurator->createRobotLoader() ``` -Аналізатор файлів PHP .[#toc-php-files-analyzer] ------------------------------------------------- +Аналізатор PHP файлів +--------------------- -RobotLoader також можна використовувати суто для пошуку класів, інтерфейсів і трейтів у PHP-файлах **без** використання функції автозавантаження: +RobotLoader можна також використовувати суто для пошуку класів, інтерфейсів, трейтів та enum у файлах PHP **без** використання функції автозавантаження: ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); -// Сканує каталоги на наявність класів / інтерфейсів / трейтів +// просканує каталоги на класи / інтерфейси / трейти / enum $loader->rebuild(); -// Повертає масив пар клас => ім'я файлу +// повертає масив пар клас => назва файлу $res = $loader->getIndexedClasses(); ``` -Навіть при такому використанні ви можете використовувати кеш. У результаті немодифіковані файли не будуть повторно аналізуватися при повторному скануванні: +Навіть при такому використанні ви можете використовувати кеш. Завдяки цьому при повторному скануванні не будуть повторно аналізуватися незмінені файли: ```php $loader = new Nette\Loaders\RobotLoader; $loader->addDirectory(__DIR__ . '/app'); + +// налаштуємо кешування до каталогу 'temp' $loader->setTempDirectory(__DIR__ . '/temp'); -// Сканування каталогів із використанням кешу +// просканує каталоги з використанням кешу $loader->refresh(); -// Повертає масив пар клас => ім'я файлу +// повертає масив пар клас => назва файлу $res = $loader->getIndexedClasses(); ``` -Кешування .[#toc-caching] -------------------------- +Кешування +--------- -RobotLoader працює дуже швидко, тому що він розумно використовує кеш. +RobotLoader дуже швидкий, оскільки вміло використовує кеш. -Розробляючи з ним, ви майже не помічаєте, що він працює у фоновому режимі. Він постійно оновлює кеш, оскільки знає, що класи та файли можуть бути створені, видалені, перейменовані тощо. І він не сканує повторно немодифіковані файли. +Під час розробки ви практично не помічаєте, що він працює у фоновому режимі. Він постійно оновлює свій кеш, оскільки враховує, що класи та файли можуть створюватися, зникати, перейменовуватися тощо. І він не сканує повторно файли, які не змінилися. -При використанні на робочому сервері, з іншого боку, ми рекомендуємо відключити оновлення кешу за допомогою `$loader->setAutoRefresh(false)` (це робиться автоматично в додатку Nette), оскільки файли не змінюються. Водночас, необхідно **очистити кеш** під час завантаження нової версії на хостинг. +При розгортанні на робочому сервері, навпаки, рекомендується вимкнути оновлення кешу за допомогою `$loader->setAutoRefresh(false)` (у Nette Application це відбувається автоматично), оскільки файли не змінюються. Водночас необхідно при завантаженні нової версії на хостинг **видалити кеш.** -Звичайно, початкове сканування файлів, коли кеш ще не існує, може зайняти кілька секунд для великих додатків. RobotLoader має вбудований захист від "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. -Це ситуація, коли виробничий сервер отримує велику кількість одночасних запитів, і оскільки кеш RobotLoader ще не існує, всі вони почнуть сканувати файли. Це збільшує навантаження на процесор і файлову систему. -На щастя, RobotLoader працює таким чином, що за кількох одночасних запитів тільки перший потік індексує файли, створює кеш, решта чекають, а потім використовують кеш. +Початкове сканування файлів, коли кеш ще не існує, може, звісно, зайняти деякий час для великих програм. RobotLoader має вбудований захист від "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Це ситуація, коли на робочому сервері збирається велика кількість одночасних запитів, які запускають RobotLoader, і оскільки кеш ще не існує, всі вони починають сканувати файли. Що б надмірно навантажило сервер. На щастя, RobotLoader працює так, що при кількох одночасних запитах індексує файли лише перший потік, створює кеш, інші чекають і потім використовують кеш. -PSR-4 .[#toc-psr-4] -------------------- +PSR-4 +----- -Сьогодні Composer можна використовувати для [автозавантаження |best-practices:composer#Autoloading] відповідно до PSR-4. Простіше кажучи, це система, у якій простори імен та імена класів відповідають структурі каталогів та іменам файлів, тобто `App\Router\RouterFactory` знаходиться у файлі `/path/to/App/Router/RouterFactory.php`. +Сьогодні для [автозавантаження можна використовувати Composer |best-practices:composer#Автозавантаження] при дотриманні PSR-4. Спрощено кажучи, це система, коли простори імен та назви класів відповідають структурі каталогів та назвам файлів, тобто, наприклад, `App\Core\RouterFactory` буде у файлі `/path/to/App/Core/RouterFactory.php`. -RobotLoader не прив'язаний до будь-якої фіксованої структури, тому він корисний у ситуаціях, коли вам не підходить структура каталогів, оформлена у вигляді просторів імен у PHP, або коли ви розробляєте застосунок, у якому історично не використовуються такі угоди. Також можна використовувати обидва завантажувачі разом. +RobotLoader не пов'язаний з жодною фіксованою структурою, тому він підходить у ситуаціях, коли вам не зовсім зручно мати однаково спроектовану структуру каталогів, як простори імен у PHP, або коли ви розробляєте програму, яка історично не використовує таких конвенцій. Можливо також використовувати обидва завантажувачі разом. {{leftbar: nette:@menu-topics}} +{{sitename: Документація Nette}} diff --git a/safe-stream/bg/@home.texy b/safe-stream/bg/@home.texy index e18f45368e..db5b403872 100644 --- a/safe-stream/bg/@home.texy +++ b/safe-stream/bg/@home.texy @@ -1,62 +1,63 @@ -SafeStream: Защита на файловете -******************************* +Nette SafeStream +**************** .[perex] -Nette\Utils\SafeStream гарантира, че всяко четене и запис в даден файл е изолирано. Това означава, че никоя нишка няма да започне да чете файл, който все още не е напълно записан, или че няколко нишки ще презапишат един и същ файл. +Nette SafeStream гарантира, че всяко четене и запис във файл протича изолирано. Това означава, че никоя нишка няма да започне да чете файл, който още не е изцяло записан, или повече нишки няма да презаписват същия файл. -Монтаж: +Инсталация: ```shell composer require nette/safe-stream ``` -За какво е полезен? .[#toc-what-is-it-good-for] ------------------------------------------------ +За какво е полезно това? +------------------------ -Каква е действителната полезност на изолираните операции? Нека започнем с прост пример, който многократно записва във файл и след това прочита от него същия низ: +За какво са полезни изолираните операции всъщност? Да започнем с прост пример, който многократно записва във файл и впоследствие чете от него същия низ: ```php -$s = str_repeat('Дълъг низ', 10000); +$s = str_repeat('Long String', 10000); $counter = 1000; while ($counter--) { - file_put_contents('file', $s); // пишем - $readed = file_get_contents('file'); // читаем - if ($s !== $readed) { // check it - echo 'низовете са различни!'; + file_put_contents('soubor', $s); // запишете го + $readed = file_get_contents('soubor'); // прочетете го + if ($s !== $readed) { // проверете го + echo 'низовете се различават!'; // низовете се различават! } } ``` -Може да изглежда, че `echo 'strings differ!'` никога не може да се появи. Обратното също е вярно. Опитайте да стартирате този скрипт в два раздела на браузъра едновременно. Грешката ще се появи почти веднага. +Може да изглежда, че извикването `echo 'низовете се различават!'` никога не може да настъпи. Обратното е истина. Нарочно опитайте да стартирате този скрипт в два таба на браузъра едновременно. Грешката ще се появи практически незабавно. -Единият от разделите ще прочете файла, докато другият все още не е имал време да запише всичко, така че съдържанието ще бъде непълно. +Един от табовете ще прочете файла в момент, когато другият още не е успял да го запише изцяло, така че съдържанието няма да бъде пълно. -Следователно кодът не е безопасен, ако се изпълнява многократно едновременно (т.е. в няколко нишки). Това не е рядкост в интернет, тъй като често сървърът отговаря на голям брой потребители едновременно. Ето защо е много важно да се гарантира, че приложението работи надеждно дори когато се изпълнява в няколко нишки (thread-safe). В противен случай ще бъдат загубени данни и ще се появят трудно откриваеми грешки. +Следователно посоченият код не е безопасен, ако се изпълнява повече от веднъж в един момент (т.е. в повече нишки). Което в интернет не е нищо необичайно, често в един момент сървърът отговаря на голям брой потребители. Така че осигуряването на надеждна работа на вашето приложение дори при изпълнение в повече нишки (thread-safe) е много важно. Иначе ще настъпи загуба на данни и възникване на трудно откриваеми грешки. -Но както виждате, вградените функции на PHP за четене и запис на файлове не са изолирани и атомарни. +Както виждате обаче, нативните PHP функции за четене и запис на файлове не са изолирани и атомни. -Как да използвам SafeStream? .[#toc-how-to-use-safestream] ----------------------------------------------------------- +Как да използваме SafeStream? +----------------------------- -SafeStream създава защитен протокол за изолирано четене и писане на файлове с помощта на стандартни функции на PHP. Всичко, което трябва да направите, е да посочите `nette.safe://` преди името на файла: +SafeStream създава безопасен протокол, чрез който може изолирано да се четат и записват файлове посредством стандартни PHP функции. Достатъчно е само да посочите `nette.safe://` пред името на файла: ```php -file_put_contents('nette.safe://file', $s); -$s = file_get_contents('nette.safe://file'); +file_put_contents('nette.safe://soubor', $s); +$s = file_get_contents('nette.safe://soubor'); ``` -SafeStream гарантира, че не повече от една нишка може да записва във файла едновременно. Другите нишки чакат в опашката. Ако никоя нишка не пише, произволен брой нишки могат да четат файла паралелно. +SafeStream осигурява, че в един момент може да записва във файла максимално една нишка. Останалите нишки чакат на опашка. Ако никоя нишка не записва, файлът може да бъде четен паралелно от произволен брой нишки. -Всички нормални функции на PHP могат да се използват с протокола, напр: +С протокола могат да се използват всички обикновени PHP функции, например: ```php -// 'r' означава отворен само за четене +// 'r' означава отваряне само за четене $handle = fopen('nette.safe://file.txt', 'r'); $ini = parse_ini_file('nette.safe://translations.neon'); ``` {{leftbar: nette:@menu-topics}} +{{sitename: Документация на Nette}} diff --git a/safe-stream/cs/@home.texy b/safe-stream/cs/@home.texy index 86360d653f..1352ddd535 100644 --- a/safe-stream/cs/@home.texy +++ b/safe-stream/cs/@home.texy @@ -1,5 +1,5 @@ -SafeStream: bezpečně na soubory -******************************* +Nette SafeStream +**************** .[perex] Nette SafeStream garantuje, že každé čtení a zápis do souboru proběhne izolovaně. To znamená, že žádné vlákno nezačne číst soubor, který ještě není celý zapsán, nebo více vláken nebude přepisovat tentýž soubor. @@ -60,3 +60,4 @@ $ini = parse_ini_file('nette.safe://translations.neon'); ``` {{leftbar: nette:@menu-topics}} +{{sitename: Nette Dokumentace}} diff --git a/safe-stream/de/@home.texy b/safe-stream/de/@home.texy index d7d354ac82..5a971bc87d 100644 --- a/safe-stream/de/@home.texy +++ b/safe-stream/de/@home.texy @@ -1,8 +1,8 @@ -SafeStream: Sicherheit für Dateien -********************************** +Nette SafeStream +**************** .[perex] -Nette SafeStream garantiert, dass jeder Lese- und Schreibvorgang in einer Datei isoliert ist. Das bedeutet, dass kein Thread mit dem Lesen einer Datei beginnt, die noch nicht vollständig geschrieben ist, oder dass mehrere Threads dieselbe Datei nicht überschreiben. +Nette SafeStream garantiert, dass jedes Lesen und Schreiben in eine Datei isoliert erfolgt. Das bedeutet, dass kein Thread beginnt, eine Datei zu lesen, die noch nicht vollständig geschrieben ist, oder dass mehrere Threads nicht dieselbe Datei überschreiben. Installation: @@ -11,52 +11,53 @@ composer require nette/safe-stream ``` -Wozu ist sie gut? .[#toc-what-is-it-good-for] ---------------------------------------------- +Wozu ist das gut? +----------------- -Wozu sind isolierte Operationen eigentlich gut? Beginnen wir mit einem einfachen Beispiel, bei dem wiederholt in eine Datei geschrieben und dann dieselbe Zeichenkette aus ihr gelesen wird: +Wozu sind isolierte Operationen eigentlich gut? Beginnen wir mit einem einfachen Beispiel, das wiederholt in eine Datei schreibt und anschließend denselben String daraus liest: ```php -$s = str_repeat('Long String', 10000); +$s = str_repeat('Langer String', 10000); $counter = 1000; while ($counter--) { - file_put_contents('file', $s); // schreiben - $readed = file_get_contents('file'); // lesen + file_put_contents('datei', $s); // schreiben + $readed = file_get_contents('datei'); // lesen if ($s !== $readed) { // prüfen - echo 'Zeichenketten sind unterschiedlich!'; + echo 'Die Zeichenketten unterscheiden sich!'; } } ``` -Es mag den Anschein haben, dass `echo 'strings differ!'` niemals auftreten kann. Das Gegenteil ist der Fall. Versuchen Sie, dieses Skript in zwei Browser-Registerkarten gleichzeitig auszuführen. Der Fehler wird fast sofort auftreten. +Es mag scheinen, dass der Aufruf `echo 'Die Zeichenketten unterscheiden sich!'` niemals erfolgen kann. Das Gegenteil ist der Fall. Versuchen Sie, dieses Skript gleichzeitig in zwei Browser-Tabs auszuführen. Der Fehler tritt praktisch sofort auf. -Eine der Registerkarten liest die Datei zu einem Zeitpunkt, zu dem die andere noch keine Gelegenheit hatte, sie vollständig zu schreiben, so dass der Inhalt nicht vollständig ist. +Einer der Tabs liest nämlich die Datei zu einem Zeitpunkt, zu dem der andere sie noch nicht vollständig geschrieben hat, sodass der Inhalt unvollständig ist. -Daher ist der Code nicht sicher, wenn er mehrmals gleichzeitig ausgeführt wird (d. h. in mehreren Threads). Das ist im Internet nicht unüblich, da ein Server oft auf eine große Anzahl von Benutzern gleichzeitig reagiert. Daher ist es sehr wichtig, dass Ihre Anwendung auch dann zuverlässig funktioniert, wenn sie in mehreren Threads ausgeführt wird (thread-safe). Andernfalls gehen Daten verloren und es treten schwer zu erkennende Fehler auf. +Der angegebene Code ist also nicht sicher, wenn er mehrmals gleichzeitig ausgeführt wird (also in mehreren Threads). Was im Internet nichts Ungewöhnliches ist, oft bedient ein Server gleichzeitig eine große Anzahl von Benutzern. Daher ist es sehr wichtig sicherzustellen, dass Ihre Anwendung auch bei Ausführung in mehreren Threads zuverlässig funktioniert (thread-safe). Andernfalls kommt es zu Datenverlust und schwer zu entdeckenden Fehlern. -Aber wie Sie sehen, sind die PHP-eigenen Funktionen zum Lesen und Schreiben von Dateien nicht isoliert und atomar. +Wie Sie jedoch sehen, sind die nativen PHP-Funktionen zum Lesen und Schreiben von Dateien nicht isoliert und atomar. -Wie benutzt man SafeStream? .[#toc-how-to-use-safestream] ---------------------------------------------------------- +Wie verwendet man SafeStream? +----------------------------- -SafeStream erstellt ein sicheres Protokoll zum Lesen und Schreiben von Dateien in Isolation unter Verwendung von PHP-Standardfunktionen. Alles, was Sie tun müssen, ist, `nette.safe://` vor dem Dateinamen anzugeben: +SafeStream erstellt ein sicheres Protokoll, mit dem Dateien über Standard-PHP-Funktionen isoliert gelesen und geschrieben werden können. Es genügt, `nette.safe://` vor dem Dateinamen anzugeben: ```php -file_put_contents('nette.safe://file', $s); -$s = file_get_contents('nette.safe://file'); +file_put_contents('nette.safe://datei', $s); +$s = file_get_contents('nette.safe://datei'); ``` -SafeStream sorgt dafür, dass immer nur ein Thread in die Datei schreiben kann. Die anderen Threads warten in der Warteschlange. Wenn kein Thread schreibt, kann eine beliebige Anzahl von Threads die Datei parallel lesen. +SafeStream stellt sicher, dass zu einem Zeitpunkt maximal ein Thread in die Datei schreiben kann. Andere Threads warten in der Warteschlange. Wenn kein Thread schreibt, kann eine beliebige Anzahl von Threads die Datei parallel lesen. -Alle gängigen PHP-Funktionen können mit dem Protokoll verwendet werden, zum Beispiel: +Mit dem Protokoll können alle gängigen PHP-Funktionen verwendet werden, zum Beispiel: ```php -// 'r' bedeutet schreibgeschützt öffnen +// 'r' bedeutet nur zum Lesen öffnen $handle = fopen('nette.safe://file.txt', 'r'); $ini = parse_ini_file('nette.safe://translations.neon'); ``` {{leftbar: nette:@menu-topics}} +{{sitename: Nette Dokumentation}} diff --git a/safe-stream/el/@home.texy b/safe-stream/el/@home.texy index 45a46411f2..24627c67a7 100644 --- a/safe-stream/el/@home.texy +++ b/safe-stream/el/@home.texy @@ -1,8 +1,8 @@ -SafeStream: Ασφάλεια για Αρχεία -******************************* +Nette SafeStream +**************** .[perex] -Το Nette SafeStream εγγυάται ότι κάθε ανάγνωση και εγγραφή σε ένα αρχείο είναι απομονωμένη. Αυτό σημαίνει ότι κανένα νήμα δεν θα αρχίσει να διαβάζει ένα αρχείο που δεν έχει ακόμα γραφτεί πλήρως, ή ότι πολλαπλά νήματα δεν θα αντικαταστήσουν το ίδιο αρχείο. +Το Nette SafeStream εγγυάται ότι κάθε ανάγνωση και εγγραφή σε αρχείο πραγματοποιείται απομονωμένα. Αυτό σημαίνει ότι κανένα νήμα δεν θα αρχίσει να διαβάζει ένα αρχείο που δεν έχει γραφτεί πλήρως ακόμα, ή πολλαπλά νήματα δεν θα αντικαθιστούν το ίδιο αρχείο. Εγκατάσταση: @@ -11,52 +11,53 @@ composer require nette/safe-stream ``` -Για τι είναι καλή; .[#toc-what-is-it-good-for] ----------------------------------------------- +Σε τι χρησιμεύει; +----------------- -Σε τι χρησιμεύουν στην πραγματικότητα οι απομονωμένες λειτουργίες; Ας ξεκινήσουμε με ένα απλό παράδειγμα που γράφει επανειλημμένα σε ένα αρχείο και στη συνέχεια διαβάζει την ίδια συμβολοσειρά από αυτό: +Σε τι χρησιμεύουν οι απομονωμένες λειτουργίες στην πραγματικότητα; Ας ξεκινήσουμε με ένα απλό παράδειγμα που γράφει επανειλημμένα σε ένα αρχείο και στη συνέχεια διαβάζει το ίδιο string από αυτό: ```php $s = str_repeat('Long String', 10000); $counter = 1000; while ($counter--) { - file_put_contents('file', $s); // γράψτε το - $readed = file_get_contents('file'); // να το διαβάσω - if ($s !== $readed) { // να το ελέγχεις - echo 'strings are different!'; + file_put_contents('file', $s); // γράψε το + $readed = file_get_contents('file'); // διάβασε το + if ($s !== $readed) { // έλεγξε το + echo 'τα strings διαφέρουν!'; } } ``` -Μπορεί να φαίνεται ότι το `echo 'strings differ!'` δεν μπορεί να συμβεί ποτέ. Το αντίθετο είναι αλήθεια. Δοκιμάστε να εκτελέσετε αυτό το σενάριο σε δύο καρτέλες του προγράμματος περιήγησης ταυτόχρονα. Το σφάλμα θα εμφανιστεί σχεδόν αμέσως. +Μπορεί να φαίνεται ότι η κλήση `echo 'τα strings διαφέρουν!'` δεν μπορεί ποτέ να συμβεί. Το αντίθετο ισχύει. Δοκιμάστε επίτηδες να εκτελέσετε αυτό το script σε δύο καρτέλες του προγράμματος περιήγησης ταυτόχρονα. Το σφάλμα θα εμφανιστεί πρακτικά αμέσως. -Η μία από τις καρτέλες θα διαβάσει το αρχείο τη στιγμή που η άλλη δεν έχει προλάβει να το γράψει όλο, οπότε το περιεχόμενο δεν θα είναι πλήρες. +Μία από τις καρτέλες θα διαβάσει το αρχείο τη στιγμή που η άλλη δεν έχει προλάβει να το γράψει ολόκληρο, οπότε το περιεχόμενο δεν θα είναι πλήρες. -Επομένως, ο κώδικας δεν είναι ασφαλής αν εκτελεστεί πολλές φορές ταυτόχρονα (δηλαδή σε πολλαπλά νήματα). Κάτι που δεν είναι ασυνήθιστο στο διαδίκτυο, συχνά ένας διακομιστής ανταποκρίνεται σε μεγάλο αριθμό χρηστών ταυτόχρονα. Επομένως, η διασφάλιση ότι η εφαρμογή σας λειτουργεί αξιόπιστα ακόμη και όταν εκτελείται σε πολλαπλά νήματα (thread-safe) είναι πολύ σημαντική. Διαφορετικά, θα χαθούν δεδομένα και θα εμφανιστούν σφάλματα που είναι δύσκολο να εντοπιστούν. +Ο παραπάνω κώδικας λοιπόν δεν είναι ασφαλής αν εκτελείται πολλές φορές ταυτόχρονα (δηλαδή σε πολλαπλά νήματα). Κάτι που στο διαδίκτυο δεν είναι καθόλου ασυνήθιστο, συχνά ο διακομιστής απαντά σε μεγάλο αριθμό χρηστών ταυτόχρονα. Έτσι, η εξασφάλιση ότι η εφαρμογή σας λειτουργεί αξιόπιστα ακόμα και όταν εκτελείται σε πολλαπλά νήματα (thread-safe) είναι πολύ σημαντική. Διαφορετικά, θα υπάρξει απώλεια δεδομένων και εμφάνιση δύσκολα εντοπίσιμων σφαλμάτων. -Αλλά όπως μπορείτε να δείτε, οι εγγενείς λειτουργίες ανάγνωσης και εγγραφής αρχείων της PHP δεν είναι απομονωμένες και ατομικές. +Όπως βλέπετε όμως, οι εγγενείς συναρτήσεις PHP για ανάγνωση και εγγραφή αρχείων δεν είναι απομονωμένες και ατομικές. -Πώς να χρησιμοποιήσετε το SafeStream; .[#toc-how-to-use-safestream] -------------------------------------------------------------------- +Πώς να χρησιμοποιήσετε το SafeStream; +------------------------------------- -Το SafeStream δημιουργεί ένα ασφαλές πρωτόκολλο για την ανάγνωση και εγγραφή αρχείων σε απομόνωση χρησιμοποιώντας τυπικές συναρτήσεις PHP. Το μόνο που χρειάζεται να κάνετε είναι να καθορίσετε το `nette.safe://` πριν από το όνομα του αρχείου: +Το SafeStream δημιουργεί ένα ασφαλές πρωτόκολλο μέσω του οποίου μπορείτε να διαβάζετε και να γράφετε αρχεία απομονωμένα χρησιμοποιώντας τυπικές συναρτήσεις PHP. Αρκεί να αναφέρετε το `nette.safe://` πριν από το όνομα του αρχείου: ```php file_put_contents('nette.safe://file', $s); $s = file_get_contents('nette.safe://file'); ``` -Το SafeStream διασφαλίζει ότι το πολύ ένα νήμα μπορεί να γράψει στο αρχείο κάθε φορά. Τα άλλα νήματα περιμένουν στην ουρά. Εάν κανένα νήμα δεν γράφει, οποιοσδήποτε αριθμός νημάτων μπορεί να διαβάσει το αρχείο παράλληλα. +Το SafeStream εξασφαλίζει ότι μόνο ένα νήμα μπορεί να γράφει στο αρχείο τη φορά. Τα άλλα νήματα περιμένουν στην ουρά. Αν κανένα νήμα δεν γράφει, οποιοσδήποτε αριθμός νημάτων μπορεί να διαβάζει το αρχείο παράλληλα. -Όλες οι κοινές συναρτήσεις της PHP μπορούν να χρησιμοποιηθούν με το πρωτόκολλο, για παράδειγμα: +Με το πρωτόκολλο μπορείτε να χρησιμοποιείτε όλες τις κοινές συναρτήσεις PHP, για παράδειγμα: ```php -// 'r' σημαίνει ανοιχτό μόνο για ανάγνωση +// το 'r' σημαίνει άνοιγμα μόνο για ανάγνωση $handle = fopen('nette.safe://file.txt', 'r'); $ini = parse_ini_file('nette.safe://translations.neon'); ``` {{leftbar: nette:@menu-topics}} +{{sitename: Nette Τεκμηρίωση}} diff --git a/safe-stream/en/@home.texy b/safe-stream/en/@home.texy index 4e16ac4a26..80dab88bd0 100644 --- a/safe-stream/en/@home.texy +++ b/safe-stream/en/@home.texy @@ -1,8 +1,8 @@ -SafeStream: Safety for Files -**************************** +Nette SafeStream +**************** .[perex] -Nette SafeStream guarantees that every read and write to a file is isolated. This means that no thread will start reading a file that is not yet fully written, or multiple threads will not overwrite the same file. +Nette SafeStream guarantees that every file read and write operation occurs in isolation. This means that no thread will start reading a file that hasn't been fully written yet, nor will multiple threads overwrite the same file. Installation: @@ -29,34 +29,35 @@ while ($counter--) { } ``` -It may seem that `echo 'strings differ!'` can never occur. The opposite is true. Try running this script in two browser tabs at the same time. The error will occur almost immediately. +It might seem that the call `echo 'strings are different!'` can never occur. The opposite is true. Try running this script in two browser tabs simultaneously. The error will occur almost immediately. -One of the tabs will read the file at a time when the other hasn't had a chance to write it all, so the content will not be complete. +One of the tabs will read the file at a moment when the other hasn't finished writing it completely, so the content will be incomplete. -Therefore, the code is not safe if it is executed multiple times at the same time (i.e. in multiple threads). Which is not uncommon on the internet, often a server is responding to a large number of users at one time. So ensuring that your application works reliably even when executed in multiple threads (thread-safe) is very important. Otherwise, data will be lost and hard-to-detect errors will occur. +Therefore, the code is not safe if executed multiple times concurrently (i.e., in multiple threads). This is not uncommon on the internet, as servers often respond to a large number of users simultaneously. Ensuring that your application works reliably even when executed in multiple threads (thread-safe) is crucial. Otherwise, data loss and hard-to-detect errors can occur. -But as you can see, PHP's native file read and write functions are not isolated and atomic. +However, as you can see, PHP's native file read and write functions are not isolated or atomic. How to Use SafeStream? ---------------------- -SafeStream creates a secure protocol to read and write files in isolation using standard PHP functions. All you need to do is to specify `nette.safe://` before the file name: +SafeStream creates a secure protocol through which files can be read and written in isolation using standard PHP functions. You just need to prefix the filename with `nette.safe://`: ```php file_put_contents('nette.safe://file', $s); $s = file_get_contents('nette.safe://file'); ``` -SafeStream ensures that at most one thread can write to the file at a time. The other threads are waiting in the queue. If no thread is writing, any number of threads can read the file in parallel. +SafeStream ensures that at most one thread can write to the file at a time. Other threads wait in a queue. If no thread is writing, any number of threads can read the file in parallel. All common PHP functions can be used with the protocol, for example: ```php -// 'r' means open read-only +// 'r' means open for reading only $handle = fopen('nette.safe://file.txt', 'r'); $ini = parse_ini_file('nette.safe://translations.neon'); ``` {{leftbar: nette:@menu-topics}} +{{sitename: Nette Documentation}} diff --git a/safe-stream/es/@home.texy b/safe-stream/es/@home.texy index c0d3eec11a..5217123e94 100644 --- a/safe-stream/es/@home.texy +++ b/safe-stream/es/@home.texy @@ -1,8 +1,8 @@ -SafeStream: Seguridad para los archivos -*************************************** +Nette SafeStream +**************** .[perex] -Nette SafeStream garantiza que cada lectura y escritura en un archivo está aislada. Esto significa que ningún subproceso comenzará a leer un archivo que aún no esté completamente escrito, o que varios subprocesos no sobrescribirán el mismo archivo. +Nette SafeStream garantiza que cada lectura y escritura en un archivo se realice de forma aislada. Esto significa que ningún hilo comenzará a leer un archivo que aún no se ha escrito por completo, o que varios hilos no sobrescribirán el mismo archivo. Instalación: @@ -11,52 +11,53 @@ composer require nette/safe-stream ``` -¿Para qué sirve? .[#toc-what-is-it-good-for] --------------------------------------------- +¿Para qué sirve? +---------------- ¿Para qué sirven realmente las operaciones aisladas? Comencemos con un ejemplo simple que escribe repetidamente en un archivo y luego lee la misma cadena de él: ```php -$s = str_repeat('Cadena larga', 10000); +$s = str_repeat('Long String', 10000); $counter = 1000; while ($counter--) { - file_put_contents('fichero', $s); // escribirlo - $readed = file_get_contents('fichero'); // leerlo - if ($s !== $readed) { // comprobarlo - echo '¡las cadenas son diferentes! + file_put_contents('soubor', $s); // escríbelo + $readed = file_get_contents('soubor'); // léelo + if ($s !== $readed) { // verifícalo + echo '¡las cadenas difieren!'; } } ``` -Puede parecer que `echo 'strings differ!'` no puede ocurrir nunca. Lo cierto es lo contrario. Intente ejecutar este script en dos pestañas del navegador al mismo tiempo. El error se producirá casi inmediatamente. +Puede parecer que la llamada `echo '¡las cadenas difieren!'` nunca puede ocurrir. Lo contrario es cierto. Intente ejecutar este script en dos pestañas del navegador al mismo tiempo. El error ocurrirá prácticamente de inmediato. -Una de las pestañas leerá el archivo en un momento en que la otra no ha tenido oportunidad de escribirlo todo, por lo que el contenido no estará completo. +Una de las pestañas leerá el archivo en un momento en que la otra aún no lo haya escrito por completo, por lo que el contenido no estará completo. -Por lo tanto, el código no es seguro si se ejecuta varias veces al mismo tiempo (es decir, en varios hilos). Lo cual no es raro en Internet, a menudo un servidor está respondiendo a un gran número de usuarios a la vez. Así que es muy importante asegurarse de que su aplicación funciona de forma fiable incluso cuando se ejecuta en múltiples hilos (thread-safe). De lo contrario, se perderán datos y se producirán errores difíciles de detectar. +Por lo tanto, el código proporcionado no es seguro si se ejecuta varias veces al mismo tiempo (es decir, en varios hilos). Lo cual no es nada inusual en Internet, a menudo un servidor responde a un gran número de usuarios al mismo tiempo. Por lo tanto, asegurarse de que su aplicación funcione de manera confiable incluso cuando se ejecuta en varios hilos (thread-safe) es muy importante. De lo contrario, se producirá una pérdida de datos y errores difíciles de detectar. -Pero como puede ver, las funciones nativas de lectura y escritura de archivos de PHP no son aisladas y atómicas. +Pero como puede ver, las funciones nativas de PHP para leer y escribir archivos no son aisladas ni atómicas. -¿Cómo usar SafeStream? .[#toc-how-to-use-safestream] ----------------------------------------------------- +¿Cómo usar SafeStream? +---------------------- -SafeStream crea un protocolo seguro para leer y escribir archivos de forma aislada utilizando funciones estándar de PHP. Todo lo que necesitas hacer es especificar `nette.safe://` antes del nombre del archivo: +SafeStream crea un protocolo seguro mediante el cual se pueden leer y escribir archivos de forma aislada utilizando funciones estándar de PHP. Simplemente agregue `nette.safe://` antes del nombre del archivo: ```php -file_put_contents('nette.safe://file', $s); -$s = file_get_contents('nette.safe://file'); +file_put_contents('nette.safe://soubor', $s); +$s = file_get_contents('nette.safe://soubor'); ``` -SafeStream asegura que sólo un hilo puede escribir en el archivo a la vez. Los demás subprocesos esperan en la cola. Si ningún hilo está escribiendo, cualquier número de hilos puede leer el archivo en paralelo. +SafeStream garantiza que como máximo un hilo pueda escribir en el archivo a la vez. Los otros hilos esperan en la cola. Si ningún hilo está escribiendo, cualquier número de hilos puede leer el archivo en paralelo. -Todas las funciones comunes de PHP pueden ser usadas con el protocolo, por ejemplo: +Se pueden usar todas las funciones PHP comunes con el protocolo, por ejemplo: ```php -// 'r' significa abrir sólo lectura +// 'r' significa abrir solo para lectura $handle = fopen('nette.safe://file.txt', 'r'); $ini = parse_ini_file('nette.safe://translations.neon'); ``` {{leftbar: nette:@menu-topics}} +{{sitename: Nette Documentación}} diff --git a/safe-stream/fr/@home.texy b/safe-stream/fr/@home.texy index 8f9eeeba4c..aab412604a 100644 --- a/safe-stream/fr/@home.texy +++ b/safe-stream/fr/@home.texy @@ -1,8 +1,8 @@ -SafeStream : Sécurité des fichiers -********************************** +Nette SafeStream +**************** .[perex] -Nette SafeStream garantit que chaque lecture et écriture dans un fichier est isolée. Cela signifie qu'aucun thread ne commencera à lire un fichier qui n'est pas encore entièrement écrit, ou que plusieurs threads n'écraseront pas le même fichier. +Nette SafeStream garantit que chaque lecture et écriture de fichier se déroule de manière isolée. Cela signifie qu'aucun thread ne commencera à lire un fichier qui n'est pas encore entièrement écrit, ou que plusieurs threads ne réécriront pas le même fichier. Installation : @@ -11,52 +11,53 @@ composer require nette/safe-stream ``` -A quoi ça sert ? .[#toc-what-is-it-good-for] --------------------------------------------- +À quoi ça sert ? +---------------- -À quoi servent réellement les opérations isolées ? Commençons par un exemple simple qui écrit de manière répétée dans un fichier et lit ensuite la même chaîne de caractères : +À quoi servent réellement les opérations isolées ? Commençons par un exemple simple qui écrit de manière répétée dans un fichier puis lit la même chaîne à partir de celui-ci : ```php $s = str_repeat('Long String', 10000); $counter = 1000; while ($counter--) { - file_put_contents('file', $s); // l'écrire - $readed = file_get_contents('file'); // le lire - if ($s !== $readed) { // vérifiez-le - echo 'les chaînes sont différentes!'; + file_put_contents('fichier', $s); // l'écrire + $readed = file_get_contents('fichier'); // le lire + if ($s !== $readed) { // le vérifier + echo 'les chaînes diffèrent !'; } } ``` -Il peut sembler que `echo 'strings differ!'` ne puisse jamais se produire. C'est pourtant le contraire qui est vrai. Essayez d'exécuter ce script dans deux onglets du navigateur en même temps. L'erreur se produira presque immédiatement. +On pourrait penser que l'appel `echo 'les chaînes diffèrent !'` ne peut jamais se produire, mais c'est le contraire qui est vrai. Essayez d'exécuter ce script dans deux onglets de navigateur en même temps. L'erreur se produira pratiquement immédiatement. -L'un des onglets lira le fichier à un moment où l'autre n'aura pas eu le temps de tout écrire, et le contenu ne sera donc pas complet. +L'un des onglets lira en effet le fichier au moment où l'autre n'aura pas encore fini de l'écrire entièrement, de sorte que le contenu ne sera pas complet. -Par conséquent, le code n'est pas sûr s'il est exécuté plusieurs fois en même temps (c'est-à-dire dans plusieurs threads). Ce qui n'est pas rare sur l'internet, un serveur répondant souvent à un grand nombre d'utilisateurs en même temps. Il est donc très important de s'assurer que votre application fonctionne de manière fiable même lorsqu'elle est exécutée dans plusieurs threads (thread-safe). Sinon, des données seront perdues et des erreurs difficiles à détecter se produiront. +Le code ci-dessus n'est donc pas sûr s'il est exécuté plusieurs fois en même temps (c'est-à-dire dans plusieurs threads). Ce qui n'est pas inhabituel sur Internet, souvent un serveur répond à un grand nombre d'utilisateurs en même temps. Il est donc très important de s'assurer que votre application fonctionne de manière fiable même lorsqu'elle est exécutée dans plusieurs threads (thread-safe). Sinon, des pertes de données et des erreurs difficiles à détecter se produiront. -Mais comme vous pouvez le constater, les fonctions natives de lecture et d'écriture de fichiers de PHP ne sont pas isolées et atomiques. +Mais comme vous pouvez le voir, les fonctions natives PHP pour la lecture et l'écriture de fichiers ne sont pas isolées et atomiques. -Comment utiliser SafeStream ? .[#toc-how-to-use-safestream] ------------------------------------------------------------ +Comment utiliser SafeStream ? +----------------------------- -SafeStream crée un protocole sécurisé pour lire et écrire des fichiers de manière isolée en utilisant des fonctions PHP standard. Tout ce que vous avez à faire est de spécifier `nette.safe://` avant le nom du fichier : +SafeStream crée un protocole sécurisé permettant de lire et d'écrire des fichiers de manière isolée à l'aide des fonctions PHP standard. Il suffit d'ajouter `nette.safe://` avant le nom du fichier : ```php -file_put_contents('nette.safe://file', $s); -$s = file_get_contents('nette.safe://file'); +file_put_contents('nette.safe://fichier', $s); +$s = file_get_contents('nette.safe://fichier'); ``` -SafeStream garantit qu'au maximum un thread peut écrire dans le fichier à la fois. Les autres threads sont en attente dans la file d'attente. Si aucun thread n'écrit, un nombre quelconque de threads peut lire le fichier en parallèle. +SafeStream garantit qu'à un instant donné, un seul thread au maximum peut écrire dans le fichier. Les autres threads attendent dans la file d'attente. Si aucun thread n'écrit, n'importe quel nombre de threads peut lire le fichier en parallèle. Toutes les fonctions PHP courantes peuvent être utilisées avec le protocole, par exemple : ```php -// 'r' signifie ouvrir en lecture seule +// 'r' signifie ouvrir uniquement en lecture $handle = fopen('nette.safe://file.txt', 'r'); $ini = parse_ini_file('nette.safe://translations.neon'); ``` {{leftbar: nette:@menu-topics}} +{{sitename: Documentation Nette}} diff --git a/safe-stream/hu/@home.texy b/safe-stream/hu/@home.texy index c919daa644..becc434093 100644 --- a/safe-stream/hu/@home.texy +++ b/safe-stream/hu/@home.texy @@ -1,8 +1,8 @@ -SafeStream: Fájlok biztonsága -***************************** +Nette SafeStream +**************** .[perex] -A Nette SafeStream garantálja, hogy minden olvasás és írás egy fájlba elszigetelt. Ez azt jelenti, hogy egyetlen szál sem kezd el olvasni egy olyan fájlt, amely még nincs teljesen kiírva, illetve több szál sem írja felül ugyanazt a fájlt. +A Nette SafeStream garantálja, hogy minden fájlolvasás és -írás izoláltan történik. Ez azt jelenti, hogy egyetlen szál sem kezd el olvasni egy olyan fájlt, amely még nincs teljesen megírva, vagy több szál nem írja felül ugyanazt a fájlt. Telepítés: @@ -11,52 +11,53 @@ composer require nette/safe-stream ``` -Mire jó? .[#toc-what-is-it-good-for] ------------------------------------- +Mire jó ez? +----------- -Mire is jók valójában az izolált műveletek? Kezdjük egy egyszerű példával, amely ismételten ír egy fájlba, majd ugyanazt a karakterláncot olvassa ki belőle: +Mire jók valójában az izolált műveletek? Kezdjük egy egyszerű példával, amely ismételten ír egy fájlba, majd ugyanazt a stringet olvassa ki belőle: ```php -$s = str_repeat('Long String', 10000); +$s = str_repeat('Hosszú String', 10000); $counter = 1000; while ($counter--) { - file_put_contents('file', $s); // írja ki. - $readed = file_get_contents('file'); // beolvasás - if ($s !== $readed) { // ellenőrzés - echo 'a karakterláncok különböznek!'; + file_put_contents('fajl', $s); // írja be + $readed = file_get_contents('fajl'); // olvassa ki + if ($s !== $readed) { // ellenőrizze + echo 'a stringek különböznek!'; } } ``` -Úgy tűnhet, hogy a `echo 'strings differ!'` soha nem fordulhat elő. Ennek az ellenkezője igaz. Próbálja meg ezt a szkriptet egyszerre két böngészőfülben futtatni. A hiba szinte azonnal jelentkezni fog. +Úgy tűnhet, hogy az `echo 'a stringek különböznek!'` hívás soha nem fordulhat elő. Az ellenkezője igaz. Próbálja meg ezt a szkriptet egyszerre két böngészőfülön futtatni. A hiba gyakorlatilag azonnal bekövetkezik. -Az egyik fül olyan időpontban olvassa be a fájlt, amikor a másiknak még nem volt lehetősége az egészet kiírni, így a tartalom nem lesz teljes. +Az egyik fül ugyanis akkor olvassa be a fájlt, amikor a másik még nem fejezte be teljesen az írást, így a tartalom nem lesz teljes. -Ezért a kód nem biztonságos, ha egyszerre többször (azaz több szálban) hajtják végre. Ami nem ritka az interneten, gyakran egy szerver egyszerre nagyszámú felhasználónak válaszol. Ezért nagyon fontos annak biztosítása, hogy az alkalmazás több szálban történő végrehajtás esetén is megbízhatóan működjön (szálbiztos). Ellenkező esetben adatok vesznek el, és nehezen észlelhető hibák lépnek fel. +A megadott kód tehát nem biztonságos, ha egyszerre többször hajtják végre (azaz több szálon). Ami az interneten nem szokatlan, gyakran egy időben válaszol a szerver nagyszámú felhasználónak. Tehát annak biztosítása, hogy az alkalmazása megbízhatóan működjön több szálon történő végrehajtás esetén is (thread-safe), nagyon fontos. Ellenkező esetben adatvesztés és nehezen felderíthető hibák keletkeznek. -De mint láthatjuk, a PHP natív fájlolvasási és -írási függvényei nem elszigeteltek és atomikusak. +Ahogy azonban láthatja, a natív PHP fájlolvasási és -írási függvények nem izoláltak és atomiak. -Hogyan használjuk a SafeStreamet? .[#toc-how-to-use-safestream] ---------------------------------------------------------------- +Hogyan használjuk a SafeStream-et? +---------------------------------- -A SafeStream biztonságos protokollt hoz létre a fájlok elszigetelt olvasásához és írásához a szabványos PHP-funkciók segítségével. Mindössze annyit kell tennie, hogy a fájlnév előtt megadja a `nette.safe://` címet: +A SafeStream létrehoz egy biztonságos protokollt, amellyel izoláltan lehet olvasni és írni fájlokat a standard PHP függvények segítségével. Csak annyit kell tenni, hogy a `nette.safe://` előtagot kell a fájlnév elé írni: ```php -file_put_contents('nette.safe://file', $s); -$s = file_get_contents('nette.safe://file'); +file_put_contents('nette.safe://fajl', $s); +$s = file_get_contents('nette.safe://fajl'); ``` -A SafeStream biztosítja, hogy egyszerre legfeljebb egy szál írhat a fájlba. A többi szál a sorban várakozik. Ha egyetlen szál sem ír, akkor tetszőleges számú szál olvashatja a fájlt párhuzamosan. +A SafeStream biztosítja, hogy egyszerre legfeljebb egy szál írhat a fájlba. A többi szál sorban várakozik. Ha egyetlen szál sem ír, akkor tetszőleges számú szál olvashatja párhuzamosan a fájlt. -A protokollal az összes szokásos PHP-funkció használható, például: +A protokollal minden szokásos PHP függvény használható, például: ```php -// 'r' azt jelenti, hogy csak olvasásra nyitva +// 'r' azt jelenti, hogy csak olvasásra nyissa meg $handle = fopen('nette.safe://file.txt', 'r'); $ini = parse_ini_file('nette.safe://translations.neon'); ``` {{leftbar: nette:@menu-topics}} +{{sitename: Nette dokumentáció}} diff --git a/safe-stream/it/@home.texy b/safe-stream/it/@home.texy index 5c250424cb..ded7828524 100644 --- a/safe-stream/it/@home.texy +++ b/safe-stream/it/@home.texy @@ -1,8 +1,8 @@ -SafeStream: Sicurezza per i file -******************************** +Nette SafeStream +**************** .[perex] -Nette SafeStream garantisce che ogni lettura e scrittura di un file sia isolata. Ciò significa che nessun thread inizierà a leggere un file che non è ancora stato completamente scritto, o che più thread non sovrascriveranno lo stesso file. +Nette SafeStream garantisce che ogni lettura e scrittura su file avvenga in modo isolato. Ciò significa che nessun thread inizierà a leggere un file che non è ancora stato scritto completamente, o più thread non sovrascriveranno lo stesso file. Installazione: @@ -11,52 +11,53 @@ composer require nette/safe-stream ``` -A cosa serve? .[#toc-what-is-it-good-for] ------------------------------------------ +A cosa serve? +------------- -A cosa servono le operazioni isolate? Cominciamo con un semplice esempio che scrive ripetutamente su un file e poi legge la stessa stringa da esso: +A cosa servono effettivamente le operazioni isolate? Iniziamo con un semplice esempio che scrive ripetutamente su un file e successivamente legge la stessa stringa da esso: ```php -$s = str_repeat('Stringa lunga', 10000); +$s = str_repeat('Long String', 10000); $counter = 1000; while ($counter--) { - file_put_contents('file', $s); // scriverlo - $readed = file_get_contents('file'); // leggerlo - if ($s !== $readed) { // verifica - echo "Le stringhe sono diverse!"; + file_put_contents('file', $s); // scrivilo + $readed = file_get_contents('file'); // leggilo + if ($s !== $readed) { // controllalo + echo 'le stringhe sono diverse!'; } } ``` -Potrebbe sembrare che `echo 'strings differ!'` non possa mai verificarsi. È vero il contrario. Provate a eseguire questo script in due schede del browser contemporaneamente. L'errore si verificherà quasi immediatamente. +Potrebbe sembrare che la chiamata `echo 'le stringhe sono diverse!'` non possa mai verificarsi. È vero il contrario. Provate ad eseguire questo script in due schede del browser contemporaneamente. L'errore si verificherà praticamente immediatamente. -Una delle schede leggerà il file in un momento in cui l'altra non ha avuto la possibilità di scriverlo tutto, quindi il contenuto non sarà completo. +Una delle schede leggerà infatti il file nel momento in cui l'altra non ha ancora finito di scriverlo completamente, quindi il contenuto non sarà completo. -Pertanto, il codice non è sicuro se viene eseguito più volte contemporaneamente (cioè in più thread). Questo non è raro in Internet, dove spesso un server risponde a un gran numero di utenti contemporaneamente. Pertanto, è molto importante garantire che l'applicazione funzioni in modo affidabile anche quando viene eseguita in più thread (thread-safe). In caso contrario, i dati andranno persi e si verificheranno errori difficili da individuare. +Il codice fornito non è quindi sicuro se viene eseguito più volte contemporaneamente (cioè in più thread). Il che su Internet non è niente di insolito, spesso un server risponde a un gran numero di utenti contemporaneamente. Quindi assicurarsi che la tua applicazione funzioni in modo affidabile anche quando viene eseguita in più thread (thread-safe) è molto importante. Altrimenti si verificherà una perdita di dati e l'insorgere di errori difficili da individuare. -Ma come si può vedere, le funzioni native di lettura e scrittura dei file di PHP non sono isolate e atomiche. +Come puoi vedere, però, le funzioni PHP native per la lettura e la scrittura di file non sono isolate e atomiche. -Come usare SafeStream? .[#toc-how-to-use-safestream] ----------------------------------------------------- +Come usare SafeStream? +---------------------- -SafeStream crea un protocollo sicuro per leggere e scrivere file in isolamento, utilizzando funzioni PHP standard. Tutto ciò che occorre fare è specificare `nette.safe://` prima del nome del file: +SafeStream crea un protocollo sicuro tramite il quale è possibile leggere e scrivere file in modo isolato utilizzando le funzioni PHP standard. Basta solo specificare `nette.safe://` prima del nome del file: ```php file_put_contents('nette.safe://file', $s); $s = file_get_contents('nette.safe://file'); ``` -SafeStream assicura che solo un thread alla volta possa scrivere sul file. Gli altri thread sono in attesa nella coda. Se nessun thread sta scrivendo, qualsiasi numero di thread può leggere il file in parallelo. +SafeStream garantisce che al massimo un thread possa scrivere sul file contemporaneamente. Gli altri thread attendono in coda. Se nessun thread sta scrivendo, qualsiasi numero di thread può leggere il file parallelamente. -Tutte le funzioni PHP comuni possono essere utilizzate con il protocollo, ad esempio: +Con il protocollo è possibile utilizzare tutte le comuni funzioni PHP, ad esempio: ```php -// 'r' significa aprire in sola lettura +// 'r' significa aprire solo per la lettura $handle = fopen('nette.safe://file.txt', 'r'); $ini = parse_ini_file('nette.safe://translations.neon'); ``` {{leftbar: nette:@menu-topics}} +{{sitename: Documentazione Nette}} diff --git a/safe-stream/ja/@home.texy b/safe-stream/ja/@home.texy new file mode 100644 index 0000000000..cd3af788da --- /dev/null +++ b/safe-stream/ja/@home.texy @@ -0,0 +1,63 @@ +Nette SafeStream +**************** + +.[perex] +Nette SafeStreamは、ファイルへのすべての読み取りと書き込みが分離して行われることを保証します。これは、まだ完全に書き込まれていないファイルを読み取り始めるスレッドがないこと、または複数のスレッドが同じファイルを上書きしないことを意味します。 + +インストール: + +```shell +composer require nette/safe-stream +``` + + +何が良いのですか? +--------- + +分離された操作は何が良いのでしょうか? ファイルに繰り返し書き込み、その後同じ文字列を読み取る簡単な例から始めましょう: + +```php +$s = str_repeat('Long String', 10000); + +$counter = 1000; +while ($counter--) { + file_put_contents('soubor', $s); // 書き込みます + $readed = file_get_contents('soubor'); // 読み取ります + if ($s !== $readed) { // チェックします + echo '文字列が異なります!'; + } +} +``` + +`echo '文字列が異なります!'`の呼び出しが決して起こらないように思えるかもしれません。しかし、逆が真実です。このスクリプトをブラウザの2つのタブで同時に実行してみてください。エラーはほぼ即座に発生します。 + +タブの1つが、もう1つがまだ完全に書き込み終えていない瞬間にファイルを読み取るため、内容が完全ではありません。 + +したがって、上記のコードは、同時に複数回(つまり複数のスレッドで)実行される場合、安全ではありません。これはインターネットでは珍しいことではなく、サーバーはしばしば同時に多数のユーザーに応答します。したがって、アプリケーションが複数のスレッドで実行されても(スレッドセーフ)、確実に機能するようにすることは非常に重要です。そうしないと、データが失われ、検出が困難なエラーが発生します。 + +しかし、ご覧のとおり、ファイルの読み取りと書き込みのためのネイティブPHP関数は分離されておらず、アトミックではありません。 + + +SafeStream の使用方法は? +------------------ + +SafeStreamは、標準のPHP関数を使用してファイルを分離して読み書きできる安全なプロトコルを作成します。ファイル名の前に`nette.safe://`を付けるだけです: + +```php +file_put_contents('nette.safe://soubor', $s); +$s = file_get_contents('nette.safe://soubor'); +``` + +SafeStreamは、一度に最大1つのスレッドのみがファイルに書き込むことができることを保証します。他のスレッドはキューで待機します。書き込み中のスレッドがない場合、任意の数のスレッドがファイルを並行して読み取ることができます。 + +このプロトコルでは、すべての一般的なPHP関数を使用できます。例: + +```php +// 'r' は読み取り専用で開くことを意味します +$handle = fopen('nette.safe://file.txt', 'r'); + +$ini = parse_ini_file('nette.safe://translations.neon'); +``` + +{{leftbar: nette:@menu-topics}} +{{sitename: Nette ドキュメンテーション}} diff --git a/safe-stream/pl/@home.texy b/safe-stream/pl/@home.texy index 3d1ad09a98..5a03452c07 100644 --- a/safe-stream/pl/@home.texy +++ b/safe-stream/pl/@home.texy @@ -1,8 +1,8 @@ -SafeStream: bezpiecznie do plików -********************************* +Nette SafeStream +**************** .[perex] -Nette SafeStream gwarantuje, że każdy odczyt i zapis do pliku jest izolowany. Oznacza to, że żaden wątek nie rozpocznie czytania pliku, który nie jest jeszcze w pełni zapisany, lub wiele wątków nadpisze ten sam plik. +Nette SafeStream gwarantuje, że każdy odczyt i zapis do pliku odbędzie się w izolacji. Oznacza to, że żaden wątek nie zacznie czytać pliku, który jeszcze nie został cały zapisany, lub wiele wątków nie będzie nadpisywać tego samego pliku. Instalacja: @@ -11,46 +11,46 @@ composer require nette/safe-stream ``` -Do czego się nadaje? .[#toc-what-is-it-good-for] ------------------------------------------------- +Do czego to służy? +------------------ -Do czego przydaje się izolowana chirurgia? Zacznijmy od prostego przykładu, który wielokrotnie zapisuje do pliku, a następnie odczytuje z niego ten sam ciąg znaków: +Do czego są właściwie dobre operacje izolowane? Zacznijmy od prostego przykładu, który wielokrotnie zapisuje do pliku, a następnie odczytuje z niego ten sam ciąg: ```php $s = str_repeat('Long String', 10000); $counter = 1000; while ($counter--) { - file_put_contents('soubor', $s); // zapisujemy - $readed = file_get_contents('soubor'); // czytaj - if ($s !== $readed) { // sprawdź to - echo 'řetězce se liší!' + file_put_contents('soubor', $s); // zapisz go + $readed = file_get_contents('soubor'); // odczytaj go + if ($s !== $readed) { // sprawdź go + echo 'ciągi się różnią!'; } } ``` -Może się wydawać, że wezwanie na `echo 'řetězce se liší!'` nigdy nie może mieć miejsca. Jest wręcz przeciwnie. Spróbuj uruchomić ten skrypt w dwóch zakładkach przeglądarki jednocześnie. Błąd pojawi się niemal natychmiast. +Może się wydawać, że wywołanie `echo 'ciągi się różnią!'` nigdy nie może nastąpić. Wręcz przeciwnie. Spróbuj uruchomić ten skrypt w dwóch kartach przeglądarki jednocześnie. Błąd pojawi się praktycznie natychmiast. -Jedna z zakładek odczyta plik, gdy druga zakładka nie zapisała jeszcze całego pliku, więc zawartość nie będzie kompletna. +Jedna z zakładek bowiem odczyta plik w momencie, gdy druga jeszcze nie zdążyła go całego zapisać, więc zawartość nie będzie kompletna. -Dlatego kod nie jest bezpieczny, jeśli jest wykonywany wielokrotnie w tym samym czasie (tj. W wielu wątkach). Co nie jest rzadkością w internecie, często serwer odpowiada na dużą liczbę użytkowników w jednym czasie. Tak więc zapewnienie, że twoja aplikacja działa niezawodnie nawet podczas wykonywania w wielu wątkach (thread-safe) jest bardzo ważne. W przeciwnym razie dane zostaną utracone i pojawią się trudne do wykrycia błędy. +Podany kod nie jest więc bezpieczny, jeśli w jednej chwili wykonuje się wielokrotnie (czyli w wielu wątkach). Co w internecie nie jest niczym niezwykłym, często w jednej chwili serwer odpowiada dużej liczbie użytkowników. Zapewnienie, aby Twoja aplikacja działała niezawodnie nawet przy wykonywaniu w wielu wątkach (thread-safe), jest bardzo ważne. W przeciwnym razie dojdzie do utraty danych i powstania trudnych do wykrycia błędów. -Ale jak widać, natywne funkcje odczytu i zapisu plików w PHP nie są izolowane i atomowe. +Jak jednak widzisz, natywne funkcje PHP do odczytu i zapisu plików nie są izolowane ani atomowe. -Jak korzystać z SafeStream? .[#toc-how-to-use-safestream] ---------------------------------------------------------- +Jak używać SafeStream? +---------------------- -SafeStream tworzy bezpieczny protokół, który może być używany do odczytu i zapisu plików w izolacji przy użyciu standardowych funkcji PHP. Wystarczy umieścić `nette.safe://` przed nazwą pliku: +SafeStream tworzy bezpieczny protokół, za pomocą którego można izolowanie czytać i zapisywać pliki za pośrednictwem standardowych funkcji PHP. Wystarczy tylko podać `nette.safe://` przed nazwą pliku: ```php file_put_contents('nette.safe://soubor', $s); $s = file_get_contents('nette.safe://soubor'); ``` -SafeStream zapewnia, że nie więcej niż jeden wątek może pisać do pliku w tym samym czasie. Pozostałe wątki ustawiają się w kolejce. Jeśli żaden wątek nie pisze, dowolna liczba wątków może czytać plik równolegle. +SafeStream zapewnia, że w jednej chwili do pliku może zapisywać maksymalnie jeden wątek. Pozostałe wątki czekają w kolejce. Jeśli żaden wątek nie zapisuje, plik może czytać równolegle dowolna liczba wątków. -Wszystkie popularne funkcje PHP mogą być używane z protokołem, na przykład: +Z protokołem można używać wszystkich powszechnych funkcji PHP, na przykład: ```php // 'r' oznacza otwarcie tylko do odczytu @@ -60,3 +60,4 @@ $ini = parse_ini_file('nette.safe://translations.neon'); ``` {{leftbar: nette:@menu-topics}} +{{sitename: Dokumentacja Nette}} diff --git a/safe-stream/pt/@home.texy b/safe-stream/pt/@home.texy index fb8720d0ed..f82bfe6793 100644 --- a/safe-stream/pt/@home.texy +++ b/safe-stream/pt/@home.texy @@ -1,8 +1,8 @@ -SafeStream: Segurança para arquivos -*********************************** +Nette SafeStream +**************** .[perex] -Nette SafeStream garante que cada leitura e escrita em um arquivo seja isolada. Isto significa que nenhum thread começará a ler um arquivo que ainda não está totalmente escrito, ou múltiplos threads não sobregravarão o mesmo arquivo. +Nette SafeStream garante que cada leitura e escrita em um arquivo ocorra isoladamente. Isso significa que nenhuma thread começará a ler um arquivo que ainda não foi totalmente escrito, ou múltiplas threads não sobrescreverão o mesmo arquivo. Instalação: @@ -11,52 +11,53 @@ composer require nette/safe-stream ``` -Para que é bom? .[#toc-what-is-it-good-for] -------------------------------------------- +Para que serve? +--------------- -Para que servem de fato as operações isoladas? Comecemos com um exemplo simples que escreve repetidamente em um arquivo e depois lê a mesma seqüência a partir dele: +Para que servem as operações isoladas, afinal? Comecemos com um exemplo simples que escreve repetidamente em um arquivo e depois lê a mesma string dele: ```php -$s = str_repeat('String Long String', 10000); +$s = str_repeat('Long String', 10000); $counter = 1000; while ($counter--) { - file_put_contents('file', $s); // escreva-o - $readed = file_get_contents('file'); // leia-o - if ($s !== $readed) { // verifique - echo 'as cordas são diferentes'; + file_put_contents('arquivo', $s); // escreve + $readed = file_get_contents('arquivo'); // lê + if ($s !== $readed) { // verifica + echo 'as strings diferem!'; } } ``` -Pode parecer que `echo 'strings differ!'` nunca poderá ocorrer. O oposto é verdade. Tente executar este script em duas abas do navegador ao mesmo tempo. O erro ocorrerá quase imediatamente. +Pode parecer que a chamada `echo 'as strings diferem!'` nunca pode ocorrer. O oposto é verdadeiro. Tente executar este script em duas abas do navegador ao mesmo tempo. O erro ocorrerá praticamente imediatamente. -Uma das abas lerá o arquivo em um momento em que a outra não teve a chance de escrevê-lo todo, portanto o conteúdo não estará completo. +Uma das abas lerá o arquivo no momento em que a outra ainda não terminou de escrevê-lo completamente, então o conteúdo não estará completo. -Portanto, o código não é seguro se ele for executado várias vezes ao mesmo tempo (ou seja, em vários fios). O que não é incomum na Internet, muitas vezes um servidor responde a um grande número de usuários ao mesmo tempo. Portanto, é muito importante garantir que sua aplicação funcione de forma confiável mesmo quando executada em vários threads (thread-safe). Caso contrário, os dados serão perdidos e erros difíceis de serem detectados ocorrerão. +O código fornecido, portanto, não é seguro se executado várias vezes ao mesmo tempo (ou seja, em múltiplas threads). O que não é nada incomum na internet, muitas vezes um servidor responde a um grande número de usuários ao mesmo tempo. Portanto, garantir que sua aplicação funcione de forma confiável mesmo quando executada em múltiplas threads (thread-safe) é muito importante. Caso contrário, ocorrerá perda de dados e surgirão erros difíceis de detectar. -Mas como você pode ver, as funções de leitura e escrita do arquivo nativo do PHP não são isoladas e atômicas. +Mas, como você pode ver, as funções nativas do PHP para leitura e escrita de arquivos não são isoladas e atômicas. -Como usar o SafeStream? .[#toc-how-to-use-safestream] ------------------------------------------------------ +Como usar SafeStream? +--------------------- -SafeStream cria um protocolo seguro para ler e escrever arquivos isoladamente usando funções PHP padrão. Tudo o que você precisa fazer é especificar `nette.safe://` antes do nome do arquivo: +SafeStream cria um protocolo seguro através do qual arquivos podem ser lidos e escritos isoladamente usando funções PHP padrão. Basta prefixar `nette.safe://` antes do nome do arquivo: ```php -file_put_contents('nette.safe://file', $s); -$s = file_get_contents('nette.safe://file'); +file_put_contents('nette.safe://arquivo', $s); +$s = file_get_contents('nette.safe://arquivo'); ``` -SafeStream garante que no máximo um thread possa escrever no arquivo de cada vez. Os outros threads estão esperando na fila. Se nenhuma thread estiver escrevendo, qualquer número de threads pode ler o arquivo em paralelo. +SafeStream garante que no máximo uma thread possa escrever no arquivo por vez. Outras threads esperam na fila. Se nenhuma thread estiver escrevendo, qualquer número de threads pode ler o arquivo em paralelo. -Todas as funções comuns do PHP podem ser usadas com o protocolo, por exemplo: +Todas as funções PHP comuns podem ser usadas com o protocolo, por exemplo: ```php -// 'r' significa somente leitura aberta +// 'r' significa abrir apenas para leitura $handle = fopen('nette.safe://file.txt', 'r'); $ini = parse_ini_file('nette.safe://translations.neon'); ``` {{leftbar: nette:@menu-topics}} +{{sitename: Documentação Nette}} diff --git a/safe-stream/ro/@home.texy b/safe-stream/ro/@home.texy index ee16685890..ccae399cab 100644 --- a/safe-stream/ro/@home.texy +++ b/safe-stream/ro/@home.texy @@ -1,8 +1,8 @@ -SafeStream: Siguranță pentru fișiere -************************************ +Nette SafeStream +**************** .[perex] -Nette SafeStream garantează că fiecare citire și scriere într-un fișier este izolată. Aceasta înseamnă că niciun fir de execuție nu va începe să citească un fișier care nu este încă complet scris sau că mai multe fire de execuție nu vor suprascrie același fișier. +Nette SafeStream garantează că fiecare citire și scriere într-un fișier se desfășoară izolat. Aceasta înseamnă că niciun fir de execuție nu va începe să citească un fișier care nu este încă complet scris, sau mai multe fire de execuție nu vor suprascrie același fișier. Instalare: @@ -11,52 +11,53 @@ composer require nette/safe-stream ``` -La ce este bun? .[#toc-what-is-it-good-for] -------------------------------------------- +La ce este bun? +--------------- -La ce sunt bune, de fapt, operațiile izolate? Să începem cu un exemplu simplu care scrie în mod repetat într-un fișier și apoi citește același șir de caractere din el: +La ce sunt bune operațiile izolate, de fapt? Să începem cu un exemplu simplu care scrie în mod repetat într-un fișier și apoi citește același șir din el: ```php $s = str_repeat('Long String', 10000); $counter = 1000; while ($counter--) { - file_put_contents('file', $s); // scrieți-o - $readed = file_get_contents('file'); // o citește - if ($s !== $readed) { // o verifică - echo 'strings are different!'; + file_put_contents('fisier', $s); // scrie-l + $readed = file_get_contents('fisier'); // citește-l + if ($s !== $readed) { // verifică-l + echo 'șirurile diferă!'; } } ``` -Se poate părea că `echo 'strings differ!'` nu poate apărea niciodată. Dimpotrivă, este adevărat contrariul. Încercați să rulați acest script în două file de browser în același timp. Eroarea va apărea aproape imediat. +Poate părea că apelul `echo 'șirurile diferă!'` nu poate avea loc niciodată. Opusul este adevărat. Încercați să rulați acest script în două tab-uri de browser simultan. Eroarea va apărea practic imediat. -Una dintre file va citi fișierul într-un moment în care cealaltă nu a apucat să îl scrie în întregime, astfel încât conținutul nu va fi complet. +Unul dintre tab-uri va citi fișierul într-un moment în care celălalt nu a reușit încă să îl scrie complet, astfel încât conținutul nu va fi complet. -Prin urmare, codul nu este sigur dacă este executat de mai multe ori în același timp (adică în mai multe fire de execuție). Ceea ce nu este neobișnuit pe internet, de multe ori un server răspunde la un număr mare de utilizatori în același timp. Așadar, este foarte important să vă asigurați că aplicația dumneavoastră funcționează în mod fiabil chiar și atunci când este executată în mai multe fire de execuție (thread-safe). În caz contrar, se vor pierde date și vor apărea erori greu de detectat. +Codul menționat nu este deci sigur dacă se execută de mai multe ori în același timp (adică în mai multe fire de execuție). Ceea ce pe internet nu este nimic neobișnuit, adesea serverul răspunde unui număr mare de utilizatori în același timp. Deci, asigurarea că aplicația dvs. funcționează fiabil chiar și atunci când este executată în mai multe fire de execuție (thread-safe) este foarte importantă. Altfel, se va ajunge la pierderea datelor și la apariția unor erori greu de detectat. -Dar, după cum puteți vedea, funcțiile native de citire și scriere a fișierelor din PHP nu sunt izolate și atomice. +Dar, după cum puteți vedea, funcțiile native PHP pentru citirea și scrierea fișierelor nu sunt izolate și atomice. -Cum se utilizează SafeStream? .[#toc-how-to-use-safestream] ------------------------------------------------------------ +Cum se utilizează SafeStream? +----------------------------- -SafeStream creează un protocol securizat pentru citirea și scrierea fișierelor în mod izolat, utilizând funcții PHP standard. Tot ce trebuie să faceți este să specificați `nette.safe://` înaintea numelui de fișier: +SafeStream creează un protocol sigur prin care se pot citi și scrie fișiere în mod izolat, folosind funcțiile standard PHP. Este suficient să prefixați numele fișierului cu `nette.safe://`: ```php -file_put_contents('nette.safe://file', $s); -$s = file_get_contents('nette.safe://file'); +file_put_contents('nette.safe://fisier', $s); +$s = file_get_contents('nette.safe://fisier'); ``` -SafeStream se asigură că cel mult un singur fir de execuție poate scrie în fișier la un moment dat. Celelalte fire așteaptă în coadă. Dacă niciun fir nu scrie, orice număr de fire poate citi fișierul în paralel. +SafeStream asigură că, la un moment dat, maxim un fir de execuție poate scrie în fișier. Celelalte fire de execuție așteaptă la coadă. Dacă niciun fir de execuție nu scrie, fișierul poate fi citit în paralel de orice număr de fire de execuție. -Toate funcțiile obișnuite ale PHP pot fi utilizate cu acest protocol, de exemplu: +Cu protocolul se pot utiliza toate funcțiile PHP obișnuite, de exemplu: ```php -// 'r' înseamnă deschis numai pentru citire +// 'r' înseamnă deschidere doar pentru citire $handle = fopen('nette.safe://file.txt', 'r'); $ini = parse_ini_file('nette.safe://translations.neon'); ``` {{leftbar: nette:@menu-topics}} +{{sitename: Documentație Nette}} diff --git a/safe-stream/ru/@home.texy b/safe-stream/ru/@home.texy index c05076b8f0..b0dd29d2fa 100644 --- a/safe-stream/ru/@home.texy +++ b/safe-stream/ru/@home.texy @@ -1,8 +1,8 @@ -SafeStream: Безопасность для файлов -*********************************** +Nette SafeStream +**************** .[perex] -Nette\Utils\SafeStream гарантирует, что каждое чтение и запись в файл изолированы. Это означает, что ни один поток не начнет читать файл, который ещё не полностью записан, или несколько потоков не будут перезаписывать один и тот же файл. +Nette SafeStream гарантирует, что каждое чтение и запись в файл происходят изолированно. Это означает, что ни один поток не начнет читать файл, который еще не полностью записан, или несколько потоков не будут перезаписывать один и тот же файл. Установка: @@ -11,46 +11,46 @@ composer require nette/safe-stream ``` -Для чего это полезно? .[#toc-what-is-it-good-for] -------------------------------------------------- +Зачем это нужно? +---------------- -Чем на самом деле полезны изолированные операции? Начнем с простого примера, который многократно записывает в файл, а затем считывает из него одну и ту же строку: +Зачем нужны изолированные операции? Начнем с простого примера, который многократно записывает в файл и затем читает из него ту же строку: ```php $s = str_repeat('Long String', 10000); $counter = 1000; while ($counter--) { - file_put_contents('file', $s); // пишем - $readed = file_get_contents('file'); // читаем - if ($s !== $readed) { // check it - echo 'strings are different!'; + file_put_contents('file', $s); // запишем это + $readed = file_get_contents('file'); // прочитаем это + if ($s !== $readed) { // проверим это + echo 'строки отличаются!'; } } ``` -Может показаться, что `echo 'strings differ!'` никогда не может возникнуть. Верно и обратное. Попробуйте запустить этот сценарий в двух вкладках браузера одновременно. Ошибка возникнет почти сразу. +Может показаться, что вызов `echo 'строки отличаются!'` никогда не может произойти. На самом деле это не так. Попробуйте запустить этот скрипт в двух вкладках браузера одновременно. Ошибка возникнет практически немедленно. -Одна из вкладок будет читать файл в то время, когда другая ещё не успела всё записать, поэтому содержимое будет неполным. +Одна из вкладок прочитает файл в тот момент, когда вторая еще не успела его полностью записать, поэтому содержимое будет неполным. -Поэтому код небезопасен, если он выполняется несколько раз в одно и то же время (т. е. в нескольких потоках). Что не редкость в интернете, часто сервер отвечает большому количеству пользователей одновременно. Поэтому очень важно обеспечить надежную работу приложения даже при выполнении в несколько потоков (thread-safe). В противном случае данные будут потеряны и возникнут труднообнаруживаемые ошибки. +Приведенный код, таким образом, не является безопасным, если он выполняется одновременно несколько раз (то есть в нескольких потоках). Что в интернете не является чем-то необычным, часто в один момент сервер отвечает большому количеству пользователей. Поэтому обеспечить, чтобы ваше приложение работало надежно даже при выполнении в нескольких потоках (потокобезопасность), очень важно. Иначе произойдет потеря данных и возникновение трудно обнаруживаемых ошибок. Но, как вы видите, встроенные функции PHP для чтения и записи файлов не являются изолированными и атомарными. -Как использовать SafeStream? .[#toc-how-to-use-safestream] ----------------------------------------------------------- +Как использовать SafeStream? +---------------------------- -SafeStream создает безопасный протокол для чтения и записи файлов в изоляции с использованием стандартных функций PHP. Всё, что вам нужно сделать, это указать `nette.safe://` перед именем файла: +SafeStream создает безопасный протокол, с помощью которого можно изолированно читать и записывать файлы посредством стандартных функций PHP. Достаточно просто указать `nette.safe://` перед именем файла: ```php file_put_contents('nette.safe://file', $s); $s = file_get_contents('nette.safe://file'); ``` -SafeStream гарантирует, что одновременно в файл может писать не более одного потока. Остальные потоки ожидают в очереди. Если ни один поток не ведет запись, любое количество потоков может читать файл параллельно. +SafeStream обеспечивает, что в один момент времени в файл может записывать максимум один поток. Остальные потоки ждут в очереди. Если ни один поток не записывает, файл может читать параллельно любое количество потоков. -Все обычные функции PHP могут быть использованы с протоколом, например: +С протоколом можно использовать все обычные функции PHP, например: ```php // 'r' означает открыть только для чтения @@ -60,3 +60,4 @@ $ini = parse_ini_file('nette.safe://translations.neon'); ``` {{leftbar: nette:@menu-topics}} +{{sitename: Документация Nette}} diff --git a/safe-stream/sl/@home.texy b/safe-stream/sl/@home.texy index 69bce004dd..42caec9643 100644 --- a/safe-stream/sl/@home.texy +++ b/safe-stream/sl/@home.texy @@ -1,8 +1,8 @@ -SafeStream: Varnost za datoteke -******************************* +Nette SafeStream +**************** .[perex] -Nette SafeStream zagotavlja, da je vsako branje in pisanje v datoteko izolirano. To pomeni, da nobena nit ne bo začela brati datoteke, ki še ni v celoti zapisana, ali da več niti ne bo prepisalo iste datoteke. +Nette SafeStream zagotavlja, da vsako branje in pisanje v datoteko poteka izolirano. To pomeni, da nobena nit ne začne brati datoteke, ki še ni v celoti zapisana, ali več niti ne bo prepisovalo iste datoteke. Namestitev: @@ -11,52 +11,53 @@ composer require nette/safe-stream ``` -Za kaj je dobra? .[#toc-what-is-it-good-for] --------------------------------------------- +Čemu je to dobro? +----------------- -Za kaj so pravzaprav dobre izolirane operacije? Začnimo s preprostim primerom, ki večkrat zapiše v datoteko in nato iz nje prebere isti niz: +Čemu so izolirane operacije pravzaprav dobre? Začnimo s preprostim primerom, ki ponavljajoče zapisuje v datoteko in nato iz nje bere isti niz: ```php $s = str_repeat('Long String', 10000); $counter = 1000; while ($counter--) { - file_put_contents('file', $s); // napišite ga. - $readed = file_get_contents('file'); // ga preberite. - if ($s !== $readed) { // ga preveri. - echo 'strings are different!'; + file_put_contents('datoteka', $s); // zapiši + $readed = file_get_contents('datoteka'); // preberi + if ($s !== $readed) { // preveri + echo 'niza se razlikujeta!'; } } ``` -Morda se zdi, da se `echo 'strings differ!'` ne more nikoli pojaviti. Res je ravno nasprotno. Poskusite zagnati to skripto v dveh zavihkih brskalnika hkrati. Napaka se bo pojavila skoraj takoj. +Morda se zdi, da se klic `echo 'niza se razlikujeta!'` nikoli ne more zgoditi. Nasprotno je res. Namenoma poskusite ta skript zagnati v dveh zavihkih brskalnika hkrati. Napaka se bo pojavila praktično takoj. -Eden od zavihkov bo prebral datoteko v času, ko drugi še ni imel priložnosti zapisati vsega, zato vsebina ne bo popolna. +Ena od zavihkov namreč prebere datoteko v trenutku, ko je druga še ni uspela v celoti zapisati, zato vsebina ne bo popolna. -Zato koda ni varna, če se izvaja večkrat hkrati (tj. v več niti). Kar v internetu ni redkost, saj se strežnik pogosto odziva na večje število uporabnikov naenkrat. Zato je zelo pomembno zagotoviti, da vaša aplikacija zanesljivo deluje tudi pri izvajanju v več niti (thread-safe). V nasprotnem primeru se bodo izgubili podatki in pojavile se bodo težko zaznavne napake. +Navedena koda torej ni varna, če se izvaja večkrat hkrati (torej v več nitih). Kar na internetu ni nič nenavadnega, pogosto strežnik hkrati odgovarja velikemu številu uporabnikov. Zato je zagotavljanje, da vaša aplikacija deluje zanesljivo tudi pri izvajanju v več nitih (thread-safe), zelo pomembno. Sicer pride do izgube podatkov in nastanka težko odkritih napak. -Toda kot lahko vidite, nativni funkciji PHP za branje in pisanje datotek nista izolirani in atomični. +Kot pa vidite, nativne PHP funkcije za branje in pisanje datotek niso izolirane in atomične. -Kako uporabljati SafeStream? .[#toc-how-to-use-safestream] ----------------------------------------------------------- +Kako uporabljati SafeStream? +---------------------------- -SafeStream ustvari varen protokol za izolirano branje in pisanje datotek z uporabo standardnih funkcij PHP. Vse, kar morate storiti, je, da pred imenom datoteke navedete `nette.safe://`: +SafeStream ustvarja varen protokol, s katerim je mogoče izolirano brati in pisati datoteke prek standardnih PHP funkcij. Dovolj je le navesti `nette.safe://` pred imenom datoteke: ```php -file_put_contents('nette.safe://file', $s); -$s = file_get_contents('nette.safe://file'); +file_put_contents('nette.safe://datoteka', $s); +$s = file_get_contents('nette.safe://datoteka'); ``` -SafeStream zagotavlja, da lahko v datoteko hkrati piše največ ena nit. Druge niti čakajo v čakalni vrsti. Če nobena nit ne piše, lahko datoteko vzporedno bere poljubno število niti. +SafeStream zagotavlja, da lahko v eno datoteko hkrati piše največ ena nit. Ostale niti čakajo v vrsti. Če nobena nit ne piše, lahko datoteko bere vzporedno poljubno število niti. -S protokolom je mogoče uporabljati vse običajne funkcije PHP, npr: +S protokolom lahko uporabljate vse običajne PHP funkcije, na primer: ```php -// 'r' pomeni odprto samo za branje +// 'r' pomeni odpri samo za branje $handle = fopen('nette.safe://file.txt', 'r'); $ini = parse_ini_file('nette.safe://translations.neon'); ``` {{leftbar: nette:@menu-topics}} +{{sitename: Nette Dokumentacija}} diff --git a/safe-stream/tr/@home.texy b/safe-stream/tr/@home.texy index 4352511161..e2e9dc75ca 100644 --- a/safe-stream/tr/@home.texy +++ b/safe-stream/tr/@home.texy @@ -1,8 +1,8 @@ -SafeStream: Dosyalar için Güvenlik -********************************** +Nette SafeStream +**************** .[perex] -Nette SafeStream, bir dosyaya yapılan her okuma ve yazmanın izole edilmesini garanti eder. Bu, hiçbir iş parçacığının henüz tam olarak yazılmamış bir dosyayı okumaya başlamayacağı veya birden fazla iş parçacığının aynı dosyanın üzerine yazmayacağı anlamına gelir. +Nette SafeStream, bir dosyaya her okuma ve yazma işleminin izole olarak gerçekleşmesini garanti eder. Bu, hiçbir iş parçacığının henüz tamamen yazılmamış bir dosyayı okumaya başlamayacağı veya birden fazla iş parçacığının aynı dosyanın üzerine yazmayacağı anlamına gelir. Kurulum: @@ -11,52 +11,53 @@ composer require nette/safe-stream ``` -Ne İşe Yarar? .[#toc-what-is-it-good-for] ------------------------------------------ +Ne işe yarar? +------------- -Yalıtılmış işlemler aslında ne işe yarar? Bir dosyaya tekrar tekrar yazan ve ardından aynı dizeyi dosyadan okuyan basit bir örnekle başlayalım: +İzole operasyonlar aslında ne işe yarar? Bir dosyaya tekrar tekrar yazan ve ardından aynı dizeyi ondan okuyan basit bir örnekle başlayalım: ```php $s = str_repeat('Uzun Dize', 10000); $counter = 1000; while ($counter--) { - file_put_contents('file', $s); // yaz - $readed = file_get_contents('file'); // oku + file_put_contents('dosya', $s); // yaz + $readed = file_get_contents('dosya'); // oku if ($s !== $readed) { // kontrol et echo 'dizeler farklı!'; } } ``` -`echo 'strings differ!'` asla gerçekleşemeyecek gibi görünebilir. Tam tersi doğrudur. Bu betiği aynı anda iki tarayıcı sekmesinde çalıştırmayı deneyin. Hata neredeyse anında ortaya çıkacaktır. +`echo 'dizeler farklı!'` çağrısının asla gerçekleşemeyeceği görünebilir. Tam tersi doğrudur. Bu betiği aynı anda iki tarayıcı sekmesinde çalıştırmayı deneyin. Hata pratikte hemen ortaya çıkar. -Sekmelerden biri dosyayı, diğerinin henüz tamamını yazmaya fırsat bulamadığı bir zamanda okuyacağından içerik tam olmayacaktır. +Sekmelerden biri dosyayı, diğeri henüz tamamen yazmayı bitirmediği bir anda okur, bu nedenle içerik tam olmaz. -Bu nedenle, kod aynı anda birden fazla kez çalıştırılırsa (yani birden fazla iş parçacığında) güvenli değildir. İnternette sık rastlanan bir durumdur, genellikle bir sunucu aynı anda çok sayıda kullanıcıya yanıt verir. Bu nedenle uygulamanızın birden fazla iş parçacığında çalıştırıldığında bile güvenilir bir şekilde çalışmasını sağlamak (thread-safe) çok önemlidir. Aksi takdirde veriler kaybolur ve tespit edilmesi zor hatalar ortaya çıkar. +Bu nedenle, belirtilen kod aynı anda birden çok kez yürütülürse (yani birden çok iş parçacığında) güvenli değildir. Bu internette alışılmadık bir durum değildir, genellikle sunucu aynı anda çok sayıda kullanıcıya yanıt verir. Bu nedenle, uygulamanızın birden çok iş parçacığında yürütüldüğünde bile güvenilir bir şekilde çalışmasını sağlamak (iş parçacığı güvenli) çok önemlidir. Aksi takdirde, veri kaybı ve tespit edilmesi zor hatalar oluşur. -Ancak görebileceğiniz gibi, PHP'nin yerel dosya okuma ve yazma işlevleri izole ve atomik değildir. +Ancak gördüğünüz gibi, dosyaları okumak ve yazmak için yerel PHP fonksiyonları izole ve atomik değildir. -SafeStream Nasıl Kullanılır? .[#toc-how-to-use-safestream] ----------------------------------------------------------- +SafeStream Nasıl Kullanılır? +---------------------------- -SafeStream, standart PHP fonksiyonlarını kullanarak dosyaları izole bir şekilde okumak ve yazmak için güvenli bir protokol oluşturur. Tek yapmanız gereken dosya adından önce `nette.safe://` adresini belirtmektir: +SafeStream, standart PHP fonksiyonları aracılığıyla dosyaları izole olarak okuyup yazabileceğiniz güvenli bir protokol oluşturur. Dosya adının önüne `nette.safe://` eklemeniz yeterlidir: ```php -file_put_contents('nette.safe://file', $s); -$s = file_get_contents('nette.safe://file'); +file_put_contents('nette.safe://dosya', $s); +$s = file_get_contents('nette.safe://dosya'); ``` -SafeStream, bir seferde en fazla bir iş parçacığının dosyaya yazabilmesini sağlar. Diğer iş parçacıkları kuyrukta bekler. Hiçbir iş parçacığı yazmıyorsa, herhangi bir sayıda iş parçacığı dosyayı paralel olarak okuyabilir. +SafeStream, aynı anda en fazla bir iş parçacığının bir dosyaya yazabilmesini sağlar. Diğer iş parçacıkları kuyrukta bekler. Hiçbir iş parçacığı yazmıyorsa, herhangi bir sayıda iş parçacığı dosyayı paralel olarak okuyabilir. -Tüm yaygın PHP fonksiyonları protokol ile kullanılabilir, örneğin: +Protokolle birlikte tüm yaygın PHP fonksiyonları kullanılabilir, örneğin: ```php -// 'r' salt okunur olarak aç anlamına gelir +// 'r', yalnızca okuma için aç anlamına gelir $handle = fopen('nette.safe://file.txt', 'r'); $ini = parse_ini_file('nette.safe://translations.neon'); ``` {{leftbar: nette:@menu-topics}} +{{sitename: Nette Dokümantasyonu}} diff --git a/safe-stream/uk/@home.texy b/safe-stream/uk/@home.texy index af0e9c26f0..9afdeee89d 100644 --- a/safe-stream/uk/@home.texy +++ b/safe-stream/uk/@home.texy @@ -1,8 +1,8 @@ -SafeStream: Безпека для файлів -****************************** +Nette SafeStream +**************** .[perex] -Nette\Utils\SafeStream гарантує, що кожне читання і запис у файл ізольовані. Це означає, що жоден потік не почне читати файл, який ще не повністю записаний, або кілька потоків не будуть перезаписувати один і той самий файл. +Nette SafeStream гарантує, що кожне читання та запис до файлу відбудеться ізольовано. Це означає, що жоден потік не почне читати файл, який ще не повністю записаний, або кілька потоків не будуть перезаписувати той самий файл. Встановлення: @@ -11,52 +11,53 @@ composer require nette/safe-stream ``` -Для чого це корисно? .[#toc-what-is-it-good-for] ------------------------------------------------- +Для чого це потрібно? +--------------------- -Чим насправді корисні ізольовані операції? Почнемо з простого прикладу, який багаторазово записує у файл, а потім зчитує з нього один і той самий рядок: +Для чого власне потрібні ізольовані операції? Почнемо з простого прикладу, який повторно записує до файлу та потім читає з нього той самий рядок: ```php $s = str_repeat('Long String', 10000); $counter = 1000; while ($counter--) { - file_put_contents('file', $s); // пишемо - $readed = file_get_contents('file'); // читаємо - if ($s !== $readed) { // перевіряємо + file_put_contents('soubor', $s); // запис + $readed = file_get_contents('soubor'); // читання + if ($s !== $readed) { // перевірка echo 'рядки відрізняються!'; } } ``` -Може здатися, що `echo 'strings differ!'` ніколи не може виникнути. Вірно і зворотне. Спробуйте запустити цей сценарій у двох вкладках браузера одночасно. Помилка виникне майже відразу. +Може здатися, що виклик `echo 'рядки відрізняються!'` ніколи не може статися. Насправді все навпаки. Спробуйте запустити цей скрипт у двох вкладках браузера одночасно. Помилка з'явиться практично миттєво. -Одна з вкладок читатиме файл у той час, коли інша ще не встигла все записати, тому вміст буде неповним. +Одна з вкладок прочитає файл у момент, коли друга ще не встигла його повністю записати, тому вміст буде неповним. -Тому код небезпечний, якщо він виконується кілька разів в один і той самий час (тобто в декількох потоках). Що не рідкість в інтернеті, часто сервер відповідає великій кількості користувачів одночасно. Тому дуже важливо забезпечити надійну роботу додатка навіть при виконанні в кілька потоків (thread-safe). Інакше дані будуть втрачені і виникнуть помилки, які важко виявити. +Отже, наведений код не є безпечним, якщо він виконується одночасно кілька разів (тобто в кількох потоках). Що в Інтернеті не є чимось незвичайним, часто сервер одночасно відповідає великій кількості користувачів. Тому забезпечення надійної роботи вашої програми навіть при виконанні в кількох потоках (thread-safe) є дуже важливим. Інакше відбудеться втрата даних та виникнення важко виявлених помилок. -Але, як ви бачите, вбудовані функції PHP для читання і запису файлів не є ізольованими і атомарними. +Але, як бачите, нативні функції PHP для читання та запису файлів не є ізольованими та атомарними. -Як використовувати SafeStream? .[#toc-how-to-use-safestream] ------------------------------------------------------------- +Як використовувати SafeStream? +------------------------------ -SafeStream створює безпечний протокол для читання і запису файлів в ізоляції з використанням стандартних функцій PHP. Все, що вам потрібно зробити, це вказати `nette.safe://` перед ім'ям файлу: +SafeStream створює безпечний протокол, за допомогою якого можна ізольовано читати та записувати файли через стандартні функції PHP. Достатньо лише вказати `nette.safe://` перед назвою файлу: ```php -file_put_contents('nette.safe://file', $s); -$s = file_get_contents('nette.safe://file'); +file_put_contents('nette.safe://soubor', $s); +$s = file_get_contents('nette.safe://soubor'); ``` -SafeStream гарантує, що одночасно у файл може писати не більше одного потоку. Решта потоків очікують у черзі. Якщо жоден потік не веде запис, будь-яка кількість потоків може читати файл паралельно. +SafeStream забезпечує, що одночасно до файлу може записувати максимум один потік. Інші потоки чекають у черзі. Якщо жоден потік не записує, файл може читати паралельно будь-яка кількість потоків. -Усі звичайні функції PHP можуть бути використані з протоколом, наприклад: +З протоколом можна використовувати всі звичайні функції PHP, наприклад: ```php -// 'r' означає відкрити тільки для читання +// 'r' означає відкрити лише для читання $handle = fopen('nette.safe://file.txt', 'r'); $ini = parse_ini_file('nette.safe://translations.neon'); ``` {{leftbar: nette:@menu-topics}} +{{sitename: Документація Nette}} diff --git a/schema/bg/@home.texy b/schema/bg/@home.texy index 76d7bab75d..51fee20913 100644 --- a/schema/bg/@home.texy +++ b/schema/bg/@home.texy @@ -1,8 +1,8 @@ -Schema: валидиране на данни -*************************** +Nette Schema +************ .[perex] -Практична библиотека за проверка и нормализиране на структури от данни в съответствие с дадена схема, с интелигентен и лесен за разбиране API. +Практична библиотека за валидация и нормализация на структури от данни спрямо дадена схема с умен и разбираем API. Инсталация: @@ -11,12 +11,12 @@ composer require nette/schema ``` -Използване на .[#toc-ispol-zovanie] ------------------------------------ +Основна употреба +---------------- -В променливата `$schema` имаме схема за валидиране (какво точно означава тя и как да я създадем, ще обясним по-късно), а в променливата `$data` имаме структура от данни, която искаме да валидираме и нормализираме. Това могат да бъдат например данни, изпратени от потребителя чрез API, конфигурационен файл и т.н. +В променливата `$schema` имаме схема за валидация (какво точно означава това и как да създадем такава схема ще кажем веднага) и в променливата `$data` структурата от данни, която искаме да валидираме и нормализираме. Може да става дума например за данни, изпратени от потребител чрез API интерфейс, конфигурационен файл и т.н. -Задачата се изпълнява от класа [api:Nette\Schema\Processor], който обработва входните данни и връща нормализирани данни или при грешка хвърля изключение [api:Nette\Schema\ValidationException]. +Задачата се осигурява от класа [api:Nette\Schema\Processor], който обработва входа и или връща нормализирани данни, или в случай на грешка хвърля изключение [api:Nette\Schema\ValidationException]. ```php $processor = new Nette\Schema\Processor; @@ -24,17 +24,17 @@ $processor = new Nette\Schema\Processor; try { $normalized = $processor->process($schema, $data); } catch (Nette\Schema\ValidationException $e) { - echo 'Data is invalid: ' . $e->getMessage(); + echo 'Данните не са валидни: ' . $e->getMessage(); } ``` -Методът `$e->getMessages()` връща масив от всички низове на съобщенията, а `$e->getMessageObjects()` връща всички съобщения като обекти "Nette\Schema\Message":https://api.nette.org/schema/master/Nette/Schema/Message.html. +Методът `$e->getMessages()` връща масив от всички съобщения като низове, а `$e->getMessageObjects()` връща всички съобщения като обекти "Nette\Schema\Message":https://api.nette.org/schema/master/Nette/Schema/Message.html. -Определяне на схема .[#toc-opredelenie-shemy] ---------------------------------------------- +Дефиниране на схема +------------------- -Сега нека създадем схема. С класа [api:Nette\Schema\Expect] всъщност определяме как трябва да изглеждат данните. Да предположим, че входните данни трябва да са структура (например масив), съдържаща елементите `processRefund` от тип bool и `refundAmount` от тип int. +А сега ще създадем схема. За нейното дефиниране служи класът [api:Nette\Schema\Expect], всъщност дефинираме очакванията как трябва да изглеждат данните. Да кажем, че входните данни трябва да образуват структура (например масив), съдържаща елементи `processRefund` от тип bool и `refundAmount` от тип int. ```php use Nette\Schema\Expect; @@ -45,9 +45,9 @@ $schema = Expect::structure([ ]); ``` -Смятаме, че дефиницията на схемата изглежда ясна, дори ако я виждате за първи път. +Вярваме, че дефиницията на схемата изглежда разбираемо, дори ако я виждате за първи път. -Ще изпратим следните данни за проверка: +Ще изпратим за валидация следните данни: ```php $data = [ @@ -55,10 +55,10 @@ $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // OK, проходит +$normalized = $processor->process($schema, $data); // OK, преминава валидацията ``` -Изходът, т.е. стойността `$normalized`, е обектът `stdClass`. Ако искаме резултатът да бъде масив, добавяме преобразуване на схемата `Expect::structure([...])->castTo('array')`. +Изходът, т.е. стойността `$normalized`, е обект `stdClass`. Ако искахме изходът да бъде масив, ще допълним схемата с преобразуване на тип `Expect::structure([...])->castTo('array')`. Всички елементи на структурата са незадължителни и имат стойност по подразбиране `null`. Пример: @@ -67,15 +67,15 @@ $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // OK, проходит +$normalized = $processor->process($schema, $data); // OK, преминава валидацията // $normalized = {'processRefund' => null, 'refundAmount' => 17} ``` -Фактът, че стойността по подразбиране е `null`, не означава, че тя ще бъде приета във входните данни `'processRefund' => null`. Не, входните данни трябва да са булеви, т.е. само `true` или `false`. Ще трябва изрично да разрешим `null` чрез `Expect::bool()->nullable()`. +Това, че стойността по подразбиране е `null`, не означава, че би се приело във входните данни `'processRefund' => null`. Не, входът трябва да бъде булев тип, т.е. само `true` или `false`. Бихме могли да разрешим `null` изрично с помощта на `Expect::bool()->nullable()`. -Елементът може да бъде направен задължителен с помощта на `Expect::bool()->required()`. Променяме стойността по подразбиране на `false`, като използваме `Expect::bool()->default(false)` или за кратко `Expect::bool(false)`. +Елементът може да бъде направен задължителен с помощта на `Expect::bool()->required()`. Стойността по подразбиране можем да променим например на `false` с помощта на `Expect::bool()->default(false)` или съкратено `Expect::bool(false)`. -Но какво ще стане, ако искаме да приемаме `1` и `0` в допълнение към булевите числа? Нека изброим валидните стойности, които също нормализираме в булеви: +А какво, ако искахме освен булев тип да приемем и `1` и `0`? Тогава ще посочим изброяване на стойности, които освен това ще оставим да бъдат нормализирани до булев тип: ```php $schema = Expect::structure([ @@ -87,13 +87,13 @@ $normalized = $processor->process($schema, $data); is_bool($normalized->processRefund); // true ``` -Вече знаете основите на това как се дефинира една схема и как се държат отделните елементи на структурата. Сега ще ви покажем какви други елементи могат да се използват при дефинирането на схемата. +Сега вече знаете основите на това как се дефинира схема и как се държат отделните елементи на структурата. Сега ще ви покажем какви всички други елементи могат да се използват при дефиниране на схема. -Типове данни: type() .[#toc-tipy-dannyh-type] ---------------------------------------------- +Типове данни: type() +-------------------- -Всички стандартни типове данни на PHP могат да бъдат изброени в схемата: +В схемата могат да се посочат всички стандартни типове данни на PHP: ```php Expect::string($default = null) @@ -104,37 +104,37 @@ Expect::null() Expect::array($default = []) ``` -И след това всички типове, [поддържани от валидаторите |utils:validators#Expected-Types] чрез `Expect::type('scalar')` или съкратено `Expect::scalar()`. Приемат се и имена на класове или интерфейси, например: `Expect::type('AddressEntity')`. +И освен това всички типове, [поддържани от класа Validators |utils:validators#Очаквани типове], например `Expect::type('scalar')` или съкратено `Expect::scalar()`. Също така имена на класове или интерфейси, например `Expect::type('AddressEntity')`. -Можете също така да използвате запис на съюз: +Може да се използва и union запис: ```php Expect::type('bool|string|array') ``` -Стойността по подразбиране винаги е `null`, с изключение на `array` и `list`, където тя е празен масив. (Списъкът е масив, индексиран във възходящ числов ред на ключовете от нула, т.е. неасоциативен масив). +Стойността по подразбиране винаги е `null` с изключение на `array` и `list`, където е празен масив. (Списък е масив, индексиран по възходяща поредица от числови ключове от нула, т.е. неасоциативен масив). -Масив от стойности: arrayOf() listOf() .[#toc-massiv-znacenij-arrayof-listof] ------------------------------------------------------------------------------ +Масиви от стойности: arrayOf() listOf() +--------------------------------------- -Масивът е твърде обща структура, по-полезно е да се посочи точно кои елементи може да съдържа. Например масив, чиито елементи могат да бъдат само низове: +Масивът представлява твърде обща структура, по-полезно е да се специфицира какви точно елементи може да съдържа. Например масив, чиито елементи могат да бъдат само низове: ```php $schema = Expect::arrayOf('string'); $processor->process($schema, ['hello', 'world']); // OK $processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK -$processor->process($schema, ['key' => 123]); // ОШИБКА: 123 не строка +$processor->process($schema, ['key' => 123]); // ГРЕШКА: 123 не е низ ``` -Вторият параметър може да се използва за задаване на ключове (от версия 1.2): +С втория параметър могат да се специфицират ключовете (от версия 1.2): ```php $schema = Expect::arrayOf('string', 'int'); $processor->process($schema, ['hello', 'world']); // OK -$processor->process($schema, ['a' => 'hello']); // ОШИБКА: 'a' не int +$processor->process($schema, ['a' => 'hello']); // ГРЕШКА: 'a' не е int ``` Списъкът е индексиран масив: @@ -143,24 +143,24 @@ $processor->process($schema, ['a' => 'hello']); // ОШИБКА: 'a' не int $schema = Expect::listOf('string'); $processor->process($schema, ['a', 'b']); // OK -$processor->process($schema, ['a', 123]); // ОШИБКА: 123 не строка -$processor->process($schema, ['key' => 'a']); // ОШИБКА: не список -$processor->process($schema, [1 => 'a', 0 => 'b']); // ОШИБКА: не список +$processor->process($schema, ['a', 123]); // ГРЕШКА: 123 не е низ +$processor->process($schema, ['key' => 'a']); // ГРЕШКА: не е списък +$processor->process($schema, [1 => 'a', 0 => 'b']); // ГРЕШКА: също не е списък ``` -Параметърът може да бъде и схема, така че можем да напишем +Параметърът може да бъде и схема, така че можем да запишем: ```php Expect::arrayOf(Expect::bool()) ``` -Стойността по подразбиране е празен масив. Ако посочите стойност по подразбиране, тя ще бъде обединена с предадените данни. Това може да бъде деактивирано с `mergeDefaults(false)` (от версия 1.1 нататък). +Стойността по подразбиране е празен масив. Ако зададете стойност по подразбиране, тя ще бъде слята с предадените данни. Това може да бъде деактивирано с помощта на `mergeDefaults(false)` (от версия 1.1). -Изброяване: anyOf() .[#toc-perecislenie-anyof] ----------------------------------------------- +Изброяване: anyOf() +------------------- -`anyOf()` - е набор от стойности или модели, които могат да бъдат стойност. Ето как да запишете масив от елементи, които могат да бъдат или `'a'`, или `true`, или `null`: +`anyOf()` представлява изброяване на стойности или схеми, които стойността може да приеме. Така записваме масив от елементи, които могат да бъдат или `'a'`, `true` или `null`: ```php $schema = Expect::listOf( @@ -168,10 +168,10 @@ $schema = Expect::listOf( ); $processor->process($schema, ['a', true, null, 'a']); // OK -$processor->process($schema, ['a', false]); // ОШИБКА: false тут не место +$processor->process($schema, ['a', false]); // ГРЕШКА: false не принадлежи там ``` -Елементите на изброяване могат да бъдат и схеми: +Елементите на изброяването могат да бъдат и схеми: ```php $schema = Expect::listOf( @@ -179,12 +179,12 @@ $schema = Expect::listOf( ); $processor->process($schema, ['foo', true, null, 'bar']); // OK -$processor->process($schema, [123]); // ОШИБКА +$processor->process($schema, [123]); // ГРЕШКА ``` -Методът `anyOf()` приема варианти като отделни параметри, а не като масив. За да му предадете масив от стойности, използвайте оператора за разопаковане `anyOf(...$variants)`. +Методът `anyOf()` приема вариантите като отделни параметри, а не като масив. Ако искате да му предадете масив от стойности, използвайте unpacking оператора `anyOf(...$variants)`. -Стойността по подразбиране е `null`. Използвайте метода `firstIsDefault()`, за да направите първия елемент по подразбиране: +Стойността по подразбиране е `null`. С метода `firstIsDefault()` правим първия елемент по подразбиране: ```php // по подразбиране е 'hello' @@ -192,14 +192,14 @@ Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault(); ``` -Структури .[#toc-struktury] ---------------------------- +Структури +--------- -Структурите са обекти с определени ключове. Всяка от тези двойки ключ => стойност се нарича "свойство": +Структурите са обекти с дефинирани ключове. Всяка от двойките ключ => стойност се означава като „свойство“: -Структурите приемат масиви и обекти и връщат обекти `stdClass` (освен ако не ги промените с `castTo('array')` и т.н.). +Структурите приемат масиви и обекти и връщат обекти `stdClass`. -По подразбиране всички свойства са незадължителни и имат стойност по подразбиране `null`. Можете да дефинирате задължителни свойства, като използвате `required()`: +По подразбиране всички свойства са незадължителни и имат стойност по подразбиране `null`. Можете да дефинирате задължителни свойства с помощта на `required()`: ```php $schema = Expect::structure([ @@ -208,13 +208,13 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['optional' => '']); -// Грешка: липсва опцията 'required' +// ГРЕШКА: липсва опция 'required' $processor->process($schema, ['required' => 'foo']); -// ОК, връща {'required' => 'foo', 'optional' => null} +// OK, връща {'required' => 'foo', 'optional' => null} ``` -Ако не искате да извеждате свойствата само със стойността по подразбиране, използвайте `skipDefaults()`: +Ако не искате да имате в изхода свойства със стойност по подразбиране, използвайте `skipDefaults()`: ```php $schema = Expect::structure([ @@ -223,10 +223,10 @@ $schema = Expect::structure([ ])->skipDefaults(); $processor->process($schema, ['required' => 'foo']); -// OK, возвращает {'required' => 'foo'} +// OK, връща {'required' => 'foo'} ``` -Въпреки че `null` е стойността по подразбиране на свойството `optional`, тя не е разрешена във входа (стойността трябва да бъде низ). Свойствата, приемащи стойността `null`, се дефинират с помощта на `nullable()`: +Въпреки че `null` е стойността по подразбиране на свойството `optional`, във входните данни не е разрешен (стойността трябва да бъде низ). Свойства, приемащи `null`, дефинираме с помощта на `nullable()`: ```php $schema = Expect::structure([ @@ -235,13 +235,15 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['optional' => null]); -// ГРЕШКА: очаква се 'optional' да бъде низ, но е предоставен null. +// ГРЕШКА: 'optional' очаква да бъде низ, подадено е null. $processor->process($schema, ['nullable' => null]); -// ОК, връща {'optional' => null, 'nullable' => null} +// OK, връща {'optional' => null, 'nullable' => null} ``` -По подразбиране във входните данни не може да има допълнителни елементи: +Масивът от всички свойства на структурата се връща от метода `getShape()`. + +По подразбиране във входните данни не могат да бъдат никакви допълнителни елементи: ```php $schema = Expect::structure([ @@ -249,10 +251,10 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['additional' => 1]); -// ERROR: Неочакван елемент 'additional' +// ГРЕШКА: Неочакван елемент 'additional' ``` -Променяйте подобни елементи с `otherItems()`. Ще посочим схемата за всеки допълнителен елемент като параметър: +Което можем да променим с помощта на `otherItems()`. Като параметър посочваме схема, според която ще се валидират допълнителните елементи: ```php $schema = Expect::structure([ @@ -260,81 +262,119 @@ $schema = Expect::structure([ ])->otherItems(Expect::int()); $processor->process($schema, ['additional' => 1]); // OK -$processor->process($schema, ['additional' => true]); // ОШИБКА +$processor->process($schema, ['additional' => true]); // ГРЕШКА +``` + +Можете да създадете нова структура чрез извеждане от друга с помощта на `extend()`: + +```php +$dog = Expect::structure([ + 'name' => Expect::string(), + 'age' => Expect::int(), +]); + +$dogWithBreed = $dog->extend([ + 'breed' => Expect::string(), +]); ``` -Остарели елементи .[#toc-ustarevsie-elementy] ---------------------------------------------- +Масиви .{data-version:1.3.2} +---------------------------- -Можете да обявите дадено свойство за неактуално с помощта на `deprecated([string $message])`. Остарелите известия се връщат с помощта на `$processor->getWarnings()`: +Масиви с дефинирани ключове. За тях важи всичко като за [#структури]. + +```php +$schema = Expect::array([ + 'required' => Expect::string()->required(), + 'optional' => Expect::string(), // стойността по подразбиране е null +]); +``` + +Може да се дефинира и индексиран масив, познат като tuple: + +```php +$schema = Expect::array([ + Expect::int(), + Expect::string(), + Expect::bool(), +]); + +$processor->process($schema, [1, 'hello', true]); // OK +``` + + +Остарели свойства +----------------- + +Можете да маркирате свойство като остаряло с помощта на метода `deprecated([string $message])`. Информацията за прекратяване на поддръжката се връща с помощта на `$processor->getWarnings()`: ```php $schema = Expect::structure([ - 'old' => Expect::int()->deprecated('Элемент %path% устарел'), + 'old' => Expect::int()->deprecated('Елементът %path% е остарял'), ]); $processor->process($schema, ['old' => 1]); // OK -$processor->getWarnings(); // ["Элемент 'old' устарел"] +$processor->getWarnings(); // ["Елементът 'old' е остарял"] ``` -Обхвати: min() max() .[#toc-diapazony-min-max] ----------------------------------------------- +Диапазони: min() max() +---------------------- -Използвайте `min()` и `max()`, за да ограничите броя на елементите в масивите: +С помощта на `min()` и `max()` може при масиви да се ограничи броят на елементите: ```php -// масив, минимум 10 елемента, максимум 20 елемента +// масив, поне 10 елемента, най-много 20 елемента Expect::array()->min(10)->max(20); ``` -За низове ограничете дължината им: +При низове да се ограничи тяхната дължина: ```php -// низ, дълъг най-малко 10 символа, максимум 20 символа +// низ, дълъг поне 10 знака, най-много 20 знака Expect::string()->min(10)->max(20); ``` -За числата ограничете тяхната стойност: +При числа да се ограничи тяхната стойност: ```php -// цяло число, от 10 до 20 включително +// цяло число, между 10 и 20 включително Expect::int()->min(10)->max(20); ``` -Разбира се, можете да споменете само `min()`, или само `max()`: +Разбира се, е възможно да се посочи само `min()`, или само `max()`: ```php -// низ, максимум 20 символа +// низ с максимална дължина 20 знака Expect::string()->max(20); ``` -Редовни изрази: pattern() .[#toc-regulyarnye-vyrazeniya-pattern] ----------------------------------------------------------------- +Регулярни изрази: pattern() +--------------------------- -С помощта на `pattern()`, можете да зададете регулярен израз, който да съвпада с **всички** от входния низ (т.е. все едно е обвит в `^` a `$` символи): +С помощта на `pattern()` може да се посочи регулярен израз, на който трябва да отговаря **целият** входен низ (т.е. сякаш е обгърнат със знаците `^` и `$`): ```php -// само 9 цифри +// точно 9 цифри Expect::string()->pattern('\d{9}'); ``` -Потребителски оператори: assert() .[#toc-pol-zovatel-skie-utverzdeniya-assert] ------------------------------------------------------------------------------- +Персонализирани ограничения: assert() +------------------------------------- -Можете да добавите други ограничения, като използвате `assert(callable $fn)`. +Всякакви други ограничения задаваме с помощта на `assert(callable $fn)`. ```php $countIsEven = fn($v) => count($v) % 2 === 0; $schema = Expect::arrayOf('string') - ->assert($countIsEven); // числото трябва да е четно + ->assert($countIsEven); // броят трябва да е четен $processor->process($schema, ['a', 'b']); // OK -$processor->process($schema, ['a', 'b', 'c']); // ERROR: 3 - нечетно число +$processor->process($schema, ['a', 'b', 'c']); // ГРЕШКА: 3 не е четен брой ``` Или @@ -343,95 +383,106 @@ $processor->process($schema, ['a', 'b', 'c']); // ERROR: 3 - нечетно чи Expect::string()->assert('is_file'); // файлът трябва да съществува ``` -Можете да добавите собствено описание за всяко твърдение. Това ще бъде част от съобщението за грешка. +Към всяко ограничение можете да добавите собствено описание. То ще бъде част от съобщението за грешка. ```php $schema = Expect::arrayOf('string') - ->assert($countIsEven, 'Even elements in array'); + ->assert($countIsEven, 'Четен брой елементи в масива'); $processor->process($schema, ['a', 'b', 'c']); -// Неуспешно изпълнение на командата "Дори елементи в масив" за елемент с масив от стойности. +// Failed assertion "Четен брой елементи в масива" for item with value array. ``` -Този метод може да бъде извикан многократно за добавяне на нови твърдения. +Методът може да се извиква многократно и така да се добавят повече ограничения. Може да се редува с извиквания на `transform()` и `castTo()`. -Съпоставяне с обекти: from() .[#toc-sopostavlenie-s-ob-ektami-from] -------------------------------------------------------------------- +Трансформации: transform() .{data-version:1.2.5} +------------------------------------------------ -От класа може да се генерира структурна схема. Пример: +Успешно валидираните данни могат да бъдат редактирани с помощта на собствена функция: ```php -class Config -{ - public string $name; - public string|null $password; - public bool $admin = false; -} +// преобразуване в главни букви: +Expect::string()->transform(fn(string $s) => strtoupper($s)); +``` -$schema = Expect::from(new Config); +Методът може да се извиква многократно и така да се добавят повече трансформации. Може да се редува с извиквания на `assert()` и `castTo()`. Операциите се извършват в реда, в който са декларирани: -$data = [ - 'name' => 'jeff', -]; - -$normalized = $processor->process($schema, $data); -// $normalized instanceof Config -// $normalized = {'name' => 'jeff', 'password' => null, 'admin' => false} +```php +Expect::type('string|int') + ->castTo('string') + ->assert('ctype_lower', 'Всички знаци трябва да са с малки букви') + ->transform(fn(string $s) => strtoupper($s)); // преобразуване в главни букви ``` -Ако използвате PHP 7.4 или по-нова версия, можете да използвате вградените типове: +Методът `transform()` може едновременно да трансформира и валидира стойността. Това често е по-просто и по-малко дублиращо се от веригата `transform()` и `assert()`. За тази цел функцията получава обект [Context |api:Nette\Schema\Context] с метода `addError()`, който може да се използва за добавяне на информация за проблеми с валидацията: ```php -class Config -{ - public string $name; - public ?string $password; - public bool $admin = false; -} +Expect::string() + ->transform(function (string $s, Nette\Schema\Context $context) { + if (!ctype_lower($s)) { + $context->addError('Всички знаци трябва да са с малки букви', 'my.case.error'); + return null; + } -$schema = Expect::from(new Config); + return strtoupper($s); + }); ``` -Поддържат се и анонимни класове: + +Преобразуване на тип: castTo() +------------------------------ + +Успешно валидираните данни могат да бъдат преобразувани по тип: ```php -$schema = Expect::from(new class { - public string $name; - public ?string $password; - public bool $admin = false; -}); +Expect::scalar()->castTo('string'); ``` -Тъй като информацията от дефиницията на класа може да не е достатъчна, можете да добавите потребителска схема за елементите с втори параметър: +Освен нативните PHP типове, може да се преобразува тип и към класове. При това се разграничава дали става дума за прост клас без конструктор, или клас с конструктор. Ако класът няма конструктор, се създава негова инстанция и всички елементи на структурата се записват в свойствата: ```php -$schema = Expect::from(new Config, [ - 'name' => Expect::string()->pattern('\w:.*'), -]); -``` +class Info +{ + public bool $processRefund; + public int $refundAmount; +} +Expect::structure([ + 'processRefund' => Expect::bool(), + 'refundAmount' => Expect::int(), +])->castTo(Info::class); -Заличаване: castTo() .[#toc-privedenie-castto] ----------------------------------------------- +// създава '$obj = new Info' и записва в $obj->processRefund и $obj->refundAmount +``` -Успешно тестваните данни могат да бъдат изхвърлени: +Ако класът има конструктор, елементите на структурата се предават като именувани параметри на конструктора: ```php -Expect::scalar()->castTo('string'); +class Info +{ + public function __construct( + public bool $processRefund, + public int $refundAmount, + ) { + } +} + +// създава $obj = new Info(processRefund: ..., refundAmount: ...) ``` -В допълнение към собствените типове на PHP можете също така да се обръщате към класове: +Преобразуването на тип в комбинация със скаларен параметър създава обект и предава стойността като единствен параметър на конструктора: ```php -Expect::scalar()->castTo('AddressEntity'); +Expect::string()->castTo(DateTime::class); +// създава new DateTime(...) ``` -Нормализация: преди() .[#toc-normalizaciya-before] --------------------------------------------------- +Нормализация: before() +---------------------- -Преди самото валидиране данните могат да бъдат нормализирани с помощта на метода `before()`. Като пример, нека има елемент, който трябва да бъде масив от низове (напр, `['a', 'b', 'c']`), но получава вход като низ `a b c`: +Преди самата валидация данните могат да бъдат нормализирани с помощта на метода `before()`. Като пример да посочим елемент, който трябва да бъде масив от низове (например `['a', 'b', 'c']`), но приема вход във формата на низ `a b c`: ```php $explode = fn($v) => explode(' ', $v); @@ -440,7 +491,48 @@ $schema = Expect::arrayOf('string') ->before($explode); $normalized = $processor->process($schema, 'a b c'); -// OK, возвращает ['a', 'b', 'c'] +// OK и връща ['a', 'b', 'c'] +``` + + +Мапиране към обекти: from() +--------------------------- + +Можем да оставим схемата на структурата да бъде генерирана от клас. Пример: + +```php +class Config +{ + public string $name; + public string|null $password; + public bool $admin = false; +} + +$schema = Expect::from(new Config); + +$data = [ + 'name' => 'franta', +]; + +$normalized = $processor->process($schema, $data); +// $normalized instanceof Config +// $normalized = {'name' => 'franta', 'password' => null, 'admin' => false} +``` + +Поддържат се и анонимни класове: + +```php +$schema = Expect::from(new class { + public string $name; + public ?string $password; + public bool $admin = false; +}); ``` -{{leftbar: nette:@menu-topics}} +Тъй като информацията, получена от дефиницията на класа, може да не е достатъчна, можете с втория параметър да допълните елементите със собствена схема: + +```php +$schema = Expect::from(new Config, [ + 'name' => Expect::string()->pattern('\w:.*'), +]); +``` diff --git a/schema/bg/@meta.texy b/schema/bg/@meta.texy new file mode 100644 index 0000000000..794cbc8522 --- /dev/null +++ b/schema/bg/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Документация на Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/schema/cs/@home.texy b/schema/cs/@home.texy index 4d40dc3cdf..950335f01a 100644 --- a/schema/cs/@home.texy +++ b/schema/cs/@home.texy @@ -1,5 +1,5 @@ -Schema: validace dat -******************** +Nette Schema +************ .[perex] Praktická knihovna pro validaci a normalizaci datových struktur oproti danému schématu s chytrým srozumitelným API. @@ -104,7 +104,7 @@ Expect::null() Expect::array($default = []) ``` -A dále všechny typy, [podporované třídou Validators|utils:validators#Očekávané typy], například `Expect::type('scalar')` nebo zkráceně `Expect::scalar()`. Také názvy tříd či rozhraní, například `Expect::type('AddressEntity')`. +A dále všechny typy, [podporované třídou Validators |utils:validators#Očekávané typy], například `Expect::type('scalar')` nebo zkráceně `Expect::scalar()`. Také názvy tříd či rozhraní, například `Expect::type('AddressEntity')`. Lze použít i union zápis: @@ -197,7 +197,7 @@ Struktury Struktury jsou objekty s definovanými klíči. Každá z dvojic klíč => hodnota je označována jako „vlastnost“: -Struktury přijímají pole a objekty a vrací objekty `stdClass` (pokud to nezměníte pomocí `castTo('array')` apod). +Struktury přijímají pole a objekty a vrací objekty `stdClass`. Ve výchozím nastavení jsou všechny vlastnosti volitelné a mají výchozí hodnotu `null`. Povinné vlastnosti můžete definovat pomocí `required()`: @@ -241,6 +241,8 @@ $processor->process($schema, ['nullable' => null]); // OK, vrací {'optional' => null, 'nullable' => null} ``` +Pole všech vlastností struktury vrací metoda `getShape()`. + Ve výchozím nastavení nemohou být ve vstupních datech žádné položky navíc: ```php @@ -263,6 +265,44 @@ $processor->process($schema, ['additional' => 1]); // OK $processor->process($schema, ['additional' => true]); // CHYBA ``` +Novou strukturu můžete vytvořit odvozením od jiné pomocí `extend()`: + +```php +$dog = Expect::structure([ + 'name' => Expect::string(), + 'age' => Expect::int(), +]); + +$dogWithBreed = $dog->extend([ + 'breed' => Expect::string(), +]); +``` + + +Pole .{data-version:1.3.2} +-------------------------- + +Pole s definovanými klíči. Platí pro něj vše co [#struktury]. + +```php +$schema = Expect::array([ + 'required' => Expect::string()->required(), + 'optional' => Expect::string(), // výchozí hodnota je null +]); +``` + +Lze definovat také indexované pole, známé jako tuple: + +```php +$schema = Expect::array([ + Expect::int(), + Expect::string(), + Expect::bool(), +]); + +$processor->process($schema, [1, 'hello', true]); // OK +``` + Zastaralé vlastnosti -------------------- @@ -353,78 +393,89 @@ $processor->process($schema, ['a', 'b', 'c']); // Failed assertion "Even items in array" for item with value array. ``` -Metodu lze volat opakovaně a tak přidat více omezení. +Metodu lze volat opakovaně a tak přidat více omezení. Lze ji prokládat s voláním `transform()` a `castTo()`. -Mapování na objekty: from() ---------------------------- +Transformace: transform() .{data-version:1.2.5} +----------------------------------------------- -Schéma struktury si můžeme nechat vygenerovat ze třídy. Příklad: +Úspěšně zvalidovaná data lze upravovat pomocí vlastní funkce: ```php -class Config -{ - public string $name; - public string|null $password; - public bool $admin = false; -} - -$schema = Expect::from(new Config); +// převod na velká písmena: +Expect::string()->transform(fn(string $s) => strtoupper($s)); +``` -$data = [ - 'name' => 'franta', -]; +Metodu lze volat opakovaně a tak přidat více transformací. Lze ji prokládat s voláním `assert()` a `castTo()`. Operace se provedou v pořadí, v jakém jsou deklarovány: -$normalized = $processor->process($schema, $data); -// $normalized instanceof Config -// $normalized = {'name' => 'franta', 'password' => null, 'admin' => false} +```php +Expect::type('string|int') + ->castTo('string') + ->assert('ctype_lower', 'All characters must be lowercased') + ->transform(fn(string $s) => strtoupper($s)); // převod na velká písmena ``` -Pokud používáte PHP 7.4 nebo vyšší, můžete využít nativních typů: +Metoda `transform()` může současně transformovat i validovat hodnotu. To je často jednodušší a méně duplicitní než řetězení `transform()` a `assert()`. K tomuto účelu funkce obdrží objekt [Context |api:Nette\Schema\Context] s metodou `addError()`, kterou lze použít k přidání informací o problémech s validací: ```php -class Config -{ - public string $name; - public ?string $password; - public bool $admin = false; -} +Expect::string() + ->transform(function (string $s, Nette\Schema\Context $context) { + if (!ctype_lower($s)) { + $context->addError('All characters must be lowercased', 'my.case.error'); + return null; + } -$schema = Expect::from(new Config); + return strtoupper($s); + }); ``` -Podporovány jsou i anonymní třídy: + +Přetypování: castTo() +--------------------- + +Úspěšně zvalidovaná data lze přetypovat: ```php -$schema = Expect::from(new class { - public string $name; - public ?string $password; - public bool $admin = false; -}); +Expect::scalar()->castTo('string'); ``` -Protože informace získané z definice třídy nemusí být dostačující, můžete druhým parametrem doplnit prvkům vlastní schéma: +Kromě nativních PHP typů lze přetypovávat i na třídy. Přitom se rozlišuje, zda jde o jednoduchou třídu bez kontruktoru, nebo třídu s konstruktorem. Pokud třída nemá konstruktor, vytvoří se její instance a všechny prvky struktury se zapíší do properties: ```php -$schema = Expect::from(new Config, [ - 'name' => Expect::string()->pattern('\w:.*'), -]); -``` +class Info +{ + public bool $processRefund; + public int $refundAmount; +} +Expect::structure([ + 'processRefund' => Expect::bool(), + 'refundAmount' => Expect::int(), +])->castTo(Info::class); -Přetypování: castTo() ---------------------- +// vytvoří '$obj = new Info' a zapíše do $obj->processRefund a $obj->refundAmount +``` -Úspěšně zvalidovaná data lze přetypovat: +Pokud třída konstruktor má, prvky struktury se předají jako pojmenované parametry konstruktoru: ```php -Expect::scalar()->castTo('string'); +class Info +{ + public function __construct( + public bool $processRefund, + public int $refundAmount, + ) { + } +} + +// vytvoří $obj = new Info(processRefund: ..., refundAmount: ...) ``` -Kromě nativních PHP typů lze přetypovávat i na třídy: +Přetypování v kombinaci se skalárním parametrem vytvoří objekt a hodnotu předá jako jediný parametr konstrukturu: ```php -Expect::scalar()->castTo('AddressEntity'); +Expect::string()->castTo(DateTime::class); +// vytvoří new DateTime(...) ``` @@ -444,4 +495,44 @@ $normalized = $processor->process($schema, 'a b c'); ``` -{{leftbar: nette:@menu-topics}} +Mapování na objekty: from() +--------------------------- + +Schéma struktury si můžeme nechat vygenerovat ze třídy. Příklad: + +```php +class Config +{ + public string $name; + public string|null $password; + public bool $admin = false; +} + +$schema = Expect::from(new Config); + +$data = [ + 'name' => 'franta', +]; + +$normalized = $processor->process($schema, $data); +// $normalized instanceof Config +// $normalized = {'name' => 'franta', 'password' => null, 'admin' => false} +``` + +Podporovány jsou i anonymní třídy: + +```php +$schema = Expect::from(new class { + public string $name; + public ?string $password; + public bool $admin = false; +}); +``` + +Protože informace získané z definice třídy nemusí být dostačující, můžete druhým parametrem doplnit prvkům vlastní schéma: + +```php +$schema = Expect::from(new Config, [ + 'name' => Expect::string()->pattern('\w:.*'), +]); +``` diff --git a/schema/cs/@meta.texy b/schema/cs/@meta.texy new file mode 100644 index 0000000000..08edde785b --- /dev/null +++ b/schema/cs/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Dokumentace}} +{{leftbar: nette:@menu-topics}} diff --git a/schema/de/@home.texy b/schema/de/@home.texy index 615b2a8c1b..b737d658a6 100644 --- a/schema/de/@home.texy +++ b/schema/de/@home.texy @@ -1,8 +1,8 @@ -Schema: Validierung von Daten -***************************** +Nette Schema +************ .[perex] -Eine praktische Bibliothek zur Validierung und Normalisierung von Datenstrukturen anhand eines vorgegebenen Schemas mit einer intelligenten und leicht verständlichen API. +Eine praktische Bibliothek zur Validierung und Normalisierung von Datenstrukturen anhand eines gegebenen Schemas mit einer cleveren, verständlichen API. Installation: @@ -11,12 +11,12 @@ composer require nette/schema ``` -Grundlegende Verwendung .[#toc-basic-usage] -------------------------------------------- +Grundlegende Verwendung +----------------------- -In der Variablen `$schema` haben wir ein Validierungsschema (was genau das bedeutet und wie man es erstellt, wird später erklärt) und in der Variablen `$data` haben wir eine Datenstruktur, die wir validieren und normalisieren wollen. Dies können z. B. Daten sein, die vom Benutzer über eine API, eine Konfigurationsdatei usw. gesendet werden. +In der Variablen `$schema` haben wir das Validierungsschema (was das genau bedeutet und wie man ein solches Schema erstellt, erklären wir gleich) und in der Variablen `$data` die Datenstruktur, die wir validieren und normalisieren möchten. Dies können beispielsweise Daten sein, die vom Benutzer über eine API-Schnittstelle gesendet wurden, eine Konfigurationsdatei usw. -Die Aufgabe wird von der Klasse [api:Nette\Schema\Processor] übernommen, die die Eingabe verarbeitet und entweder normalisierte Daten zurückgibt oder im Fehlerfall eine [api:Nette\Schema\ValidationException] Exception wirft. +Die Aufgabe übernimmt die Klasse [api:Nette\Schema\Processor], die die Eingabe verarbeitet und entweder normalisierte Daten zurückgibt oder im Fehlerfall eine Ausnahme [api:Nette\Schema\ValidationException] auslöst. ```php $processor = new Nette\Schema\Processor; @@ -24,17 +24,17 @@ $processor = new Nette\Schema\Processor; try { $normalized = $processor->process($schema, $data); } catch (Nette\Schema\ValidationException $e) { - echo 'Data is invalid: ' . $e->getMessage(); + echo 'Daten sind ungültig: ' . $e->getMessage(); } ``` -Die Methode `$e->getMessages()` liefert ein Array mit allen Nachrichtenstrings und `$e->getMessageObjects()` liefert alle Nachrichten als "Nette\Schema\Message"-Objekte:https://api.nette.org/schema/master/Nette/Schema/Message.html zurück. +Die Methode `$e->getMessages()` gibt ein Array aller Nachrichten als Strings zurück und `$e->getMessageObjects()` gibt alle Nachrichten als Objekte vom Typ [api:Nette\Schema\Message] zurück. -Schema definieren .[#toc-defining-schema] ------------------------------------------ +Schema definieren +----------------- -Und nun erstellen wir ein Schema. Die Klasse [api:Nette\Schema\Expect] wird dazu verwendet, um es zu definieren, d.h. wir definieren Erwartungen, wie die Daten aussehen sollen. Sagen wir, dass die Eingabedaten eine Struktur (z. B. ein Array) sein müssen, die Elemente `processRefund` vom Typ bool und `refundAmount` vom Typ int enthält. +Und nun erstellen wir das Schema. Zu seiner Definition dient die Klasse [api:Nette\Schema\Expect], wir definieren eigentlich die Erwartungen, wie die Daten aussehen sollen. Nehmen wir an, die Eingabedaten müssen eine Struktur (z. B. ein Array) bilden, die die Elemente `processRefund` vom Typ `bool` und `refundAmount` vom Typ `int` enthält. ```php use Nette\Schema\Expect; @@ -45,9 +45,9 @@ $schema = Expect::structure([ ]); ``` -Wir glauben, dass die Schemadefinition klar aussieht, auch wenn Sie sie zum ersten Mal sehen. +Wir glauben, dass die Schemadefinition verständlich aussieht, auch wenn Sie sie zum ersten Mal sehen. -Lassen Sie uns die folgenden Daten zur Validierung senden: +Senden wir folgende Daten zur Validierung: ```php $data = [ @@ -55,27 +55,27 @@ $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // OK, es funktioniert +$normalized = $processor->process($schema, $data); // OK, besteht die Validierung ``` -Die Ausgabe, d. h. der Wert `$normalized`, ist das Objekt `stdClass`. Wenn wir wollen, dass die Ausgabe ein Array ist, fügen wir einen Cast zu schema `Expect::structure([...])->castTo('array')`. +Die Ausgabe, also der Wert `$normalized`, ist ein `stdClass`-Objekt. Wenn wir möchten, dass die Ausgabe ein Array ist, ergänzen wir das Schema um die Typumwandlung `Expect::structure([...])->castTo('array')`. -Alle Elemente der Struktur sind optional und haben einen Standardwert `null`. Beispiel: +Alle Elemente der Struktur sind optional und haben den Standardwert `null`. Beispiel: ```php $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // OK, es funktioniert +$normalized = $processor->process($schema, $data); // OK, besteht die Validierung // $normalized = {'processRefund' => null, 'refundAmount' => 17} ``` -Die Tatsache, dass der Standardwert `null` ist, bedeutet nicht, dass er in den Eingabedaten `'processRefund' => null` akzeptiert würde. Nein, die Eingabe muss boolesch sein, d. h. nur `true` oder `false`. Wir müssten `null` über `Expect::bool()->nullable()` ausdrücklich zulassen. +Dass der Standardwert `null` ist, bedeutet nicht, dass `'processRefund' => null` in den Eingabedaten akzeptiert würde. Nein, die Eingabe muss ein Boolean sein, also nur `true` oder `false`. Um `null` zu erlauben, müssten wir dies explizit mit `Expect::bool()->nullable()` tun. -Ein Element kann mit `Expect::bool()->required()` obligatorisch gemacht werden. Wir ändern den Standardwert auf `false` mit `Expect::bool()->default(false)` oder kurz mit `Expect::bool(false)`. +Ein Element kann mit `Expect::bool()->required()` als erforderlich markiert werden. Den Standardwert ändern wir z. B. auf `false` mit `Expect::bool()->default(false)` oder kurz `Expect::bool(false)`. -Und was, wenn wir neben Booleschen Werten auch `1` and `0` akzeptieren wollen? Dann listen wir die zulässigen Werte auf, die wir ebenfalls zu booleschen Werten normalisieren werden: +Und was wäre, wenn wir neben Boolean auch `1` und `0` akzeptieren wollten? Dann geben wir eine Aufzählung von Werten an, die wir zusätzlich in Boolean normalisieren lassen: ```php $schema = Expect::structure([ @@ -87,13 +87,13 @@ $normalized = $processor->process($schema, $data); is_bool($normalized->processRefund); // true ``` -Jetzt kennen Sie die Grundlagen, wie das Schema definiert ist und wie sich die einzelnen Elemente der Struktur verhalten. Wir werden nun zeigen, was alle anderen Elemente bei der Definition eines Schemas verwendet werden können. +Jetzt kennen Sie die Grundlagen, wie ein Schema definiert wird und wie sich die einzelnen Elemente der Struktur verhalten. Nun zeigen wir Ihnen, welche weiteren Elemente bei der Definition eines Schemas verwendet werden können. -Datentypen: type() .[#toc-data-types-type] ------------------------------------------- +Datentypen: type() +------------------ -Alle Standard-PHP-Datentypen können im Schema aufgeführt werden: +Im Schema können alle Standard-PHP-Datentypen angegeben werden: ```php Expect::string($default = null) @@ -104,63 +104,63 @@ Expect::null() Expect::array($default = []) ``` -Und dann alle Typen [, die von den Validatoren |utils:validators#Expected Types] über `Expect::type('scalar')` oder abgekürzt `Expect::scalar()`[unterstützt werden |utils:validators#Expected Types]. Auch Klassen- oder Schnittstellennamen werden akzeptiert, z.B. `Expect::type('AddressEntity')`. +Und weiterhin alle Typen, die [von der Klasse Validators unterstützt werden |utils:validators#Erwartete Typen], zum Beispiel `Expect::type('scalar')` oder kurz `Expect::scalar()`. Auch Klassen- oder Schnittstellennamen, zum Beispiel `Expect::type('AddressEntity')`. -Sie können auch die Union-Notation verwenden: +Es kann auch eine Union-Notation verwendet werden: ```php Expect::type('bool|string|array') ``` -Der Standardwert ist immer `null`, außer bei `array` und `list`, wo es sich um ein leeres Array handelt. (Eine Liste ist ein Array, das in aufsteigender Reihenfolge der numerischen Schlüssel von Null an indiziert ist, also ein nicht-assoziatives Array). +Der Standardwert ist immer `null`, mit Ausnahme von `array` und `list`, wo es ein leeres Array ist. (Eine Liste ist ein Array, das nach aufsteigender Reihe numerischer Schlüssel ab Null indiziert ist, also ein nicht-assoziatives Array). -Array von Werten: arrayOf() listOf() .[#toc-array-of-values-arrayof-listof] ---------------------------------------------------------------------------- +Wert-Arrays: arrayOf() listOf() +------------------------------- -Das Array ist eine zu allgemeine Struktur, es ist sinnvoller, genau anzugeben, welche Elemente es enthalten kann. Zum Beispiel, ein Array, dessen Elemente nur Strings sein können: +Ein Array stellt eine zu allgemeine Struktur dar, es ist nützlicher anzugeben, welche Elemente es genau enthalten darf. Zum Beispiel ein Array, dessen Elemente nur Strings sein dürfen: ```php $schema = Expect::arrayOf('string'); -$processor->process($schema, ['hallo', 'welt']); // OK -$processor->process($schema, ['a' => 'hallo', 'b' => 'welt']); // OK +$processor->process($schema, ['hello', 'world']); // OK +$processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK $processor->process($schema, ['key' => 123]); // FEHLER: 123 ist kein String ``` -Der zweite Parameter kann zur Angabe von Schlüsseln verwendet werden (seit Version 1.2): +Mit dem zweiten Parameter können Schlüssel spezifiziert werden (ab Version 1.2): ```php $schema = Expect::arrayOf('string', 'int'); -$processor->process($schema, ['hallo', 'welt']); // OK -$processor->process($schema, ['a' => 'hallo']); // ERROR: 'a' ist nicht int +$processor->process($schema, ['hello', 'world']); // OK +$processor->process($schema, ['a' => 'hello']); // FEHLER: 'a' ist kein int ``` -Die Liste ist ein indiziertes Array: +Eine Liste ist ein indiziertes Array: ```php $schema = Expect::listOf('string'); $processor->process($schema, ['a', 'b']); // OK -$processor->process($schema, ['a', 123]); // FEHLER: 123 ist keine Zeichenkette -$processor->process($schema, ['key' => 'a']); // FEHLER: ist keine Liste -$processor->process($schema, [1 => 'a', 0 => 'b']); // FEHLER: ist keine Liste +$processor->process($schema, ['a', 123]); // FEHLER: 123 ist kein String +$processor->process($schema, ['key' => 'a']); // FEHLER: keine Liste +$processor->process($schema, [1 => 'a', 0 => 'b']); // FEHLER: auch keine Liste ``` -Der Parameter kann auch ein Schema sein, so dass wir schreiben können: +Der Parameter kann auch ein Schema sein, wir können also schreiben: ```php Expect::arrayOf(Expect::bool()) ``` -Der Standardwert ist ein leeres Array. Wenn Sie einen Standardwert angeben, wird dieser mit den übergebenen Daten zusammengeführt. Dies kann mit `mergeDefaults(false)` deaktiviert werden (seit Version 1.1). +Der Standardwert ist ein leeres Array. Wenn Sie einen Standardwert angeben, wird dieser mit den übergebenen Daten zusammengeführt. Dies kann mit `mergeDefaults(false)` deaktiviert werden (ab Version 1.1). -Aufzählung: anyOf() .[#toc-enumeration-anyof] ---------------------------------------------- +Aufzählung: anyOf() +------------------- -`anyOf()` ist eine Menge von Werten oder Schemata, die ein Wert sein kann. So schreiben Sie ein Array von Elementen, die entweder `'a'`, `true` oder `null` sein können: +`anyOf()` stellt eine Aufzählung von Werten oder Schemata dar, die der Wert annehmen kann. So schreiben wir ein Array von Elementen, die entweder `'a'`, `true` oder `null` sein können: ```php $schema = Expect::listOf( @@ -168,10 +168,10 @@ $schema = Expect::listOf( ); $processor->process($schema, ['a', true, null, 'a']); // OK -$processor->process($schema, ['a', false]); // ERROR: false gehört nicht dazu +$processor->process($schema, ['a', false]); // FEHLER: false gehört nicht dazu ``` -Die Aufzählungselemente können auch Schemata sein: +Die Elemente der Aufzählung können auch Schemata sein: ```php $schema = Expect::listOf( @@ -182,39 +182,39 @@ $processor->process($schema, ['foo', true, null, 'bar']); // OK $processor->process($schema, [123]); // FEHLER ``` -Die Methode `anyOf()` akzeptiert Varianten als einzelne Parameter, nicht als Array. Um ihr ein Array von Werten zu übergeben, verwenden Sie den Entpackungsoperator `anyOf(...$variants)`. +Die Methode `anyOf()` akzeptiert Varianten als einzelne Parameter, nicht als Array. Wenn Sie ihr ein Array von Werten übergeben möchten, verwenden Sie den Unpacking-Operator `anyOf(...$variants)`. -Der Standardwert ist `null`. Verwenden Sie die Methode `firstIsDefault()`, um das erste Element zum Standardwert zu machen: +Der Standardwert ist `null`. Mit der Methode `firstIsDefault()` machen wir das erste Element zum Standard: ```php -// Standard ist 'hallo' -Expect::anyOf(Expect::string('hallo'), true, null)->firstIsDefault(); +// Standard ist 'hello' +Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault(); ``` -Strukturen .[#toc-structures] ------------------------------ +Strukturen +---------- -Strukturen sind Objekte mit definierten Schlüsseln. Jedes dieser Schlüssel => Wert-Paare wird als "Eigenschaft" bezeichnet: +Strukturen sind Objekte mit definierten Schlüsseln. Jedes Schlüssel-Wert-Paar wird als „Eigenschaft“ bezeichnet. -Strukturen akzeptieren Arrays und Objekte und geben Objekte zurück `stdClass` (es sei denn, Sie ändern dies mit `castTo('array')`, etc.). +Strukturen akzeptieren Arrays und Objekte und geben `stdClass`-Objekte zurück. -Standardmäßig sind alle Eigenschaften optional und haben einen Standardwert von `null`. Obligatorische Eigenschaften können Sie mit `required()` definieren: +Standardmäßig sind alle Eigenschaften optional und haben den Standardwert `null`. Erforderliche Eigenschaften können Sie mit `required()` definieren: ```php $schema = Expect::structure([ 'required' => Expect::string()->required(), - 'optional' => Expect::string(), // der Standardwert ist null + 'optional' => Expect::string(), // Standardwert ist null ]); $processor->process($schema, ['optional' => '']); -// ERROR: option 'required' is missing +// FEHLER: Option 'required' fehlt $processor->process($schema, ['required' => 'foo']); -// OK, liefert {'required' => 'foo', 'optional' => null} +// OK, gibt {'required' => 'foo', 'optional' => null} zurück ``` -Wenn Sie keine Eigenschaften mit nur einem Standardwert ausgeben wollen, verwenden Sie `skipDefaults()`: +Wenn Sie in der Ausgabe keine Eigenschaften mit Standardwerten haben möchten, verwenden Sie `skipDefaults()`: ```php $schema = Expect::structure([ @@ -223,10 +223,10 @@ $schema = Expect::structure([ ])->skipDefaults(); $processor->process($schema, ['required' => 'foo']); -// OK, liefert {'required' => 'foo'} +// OK, gibt {'required' => 'foo'} zurück ``` -Obwohl `null` der Standardwert der Eigenschaft `optional` ist, ist er in den Eingabedaten nicht erlaubt (der Wert muss ein String sein). Eigenschaften, die `null` akzeptieren, werden mit `nullable()` definiert: +Obwohl `null` der Standardwert der Eigenschaft `optional` ist, ist er in den Eingabedaten nicht erlaubt (der Wert muss ein String sein). Eigenschaften, die `null` akzeptieren, definieren wir mit `nullable()`: ```php $schema = Expect::structure([ @@ -235,13 +235,15 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['optional' => null]); -// FEHLER: 'optional' wird als String erwartet, null gegeben. +// FEHLER: 'optional' erwartet einen String, null wurde gegeben. $processor->process($schema, ['nullable' => null]); -// OK, liefert {'optional' => null, 'nullable' => null} +// OK, gibt {'optional' => null, 'nullable' => null} zurück ``` -Standardmäßig können keine zusätzlichen Elemente in den Eingabedaten enthalten sein: +Ein Array aller Eigenschaften einer Struktur gibt die Methode `getShape()` zurück. + +Standardmäßig dürfen in den Eingabedaten keine zusätzlichen Elemente vorhanden sein: ```php $schema = Expect::structure([ @@ -249,10 +251,10 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['additional' => 1]); -// ERROR: Unexpected item 'additional' +// FEHLER: Unerwartetes Element 'additional' ``` -Das können wir mit `otherItems()` ändern. Als Parameter wird das Schema für jedes zusätzliche Element angegeben: +Dies können wir mit `otherItems()` ändern. Als Parameter geben wir ein Schema an, nach dem die zusätzlichen Elemente validiert werden: ```php $schema = Expect::structure([ @@ -260,14 +262,52 @@ $schema = Expect::structure([ ])->otherItems(Expect::int()); $processor->process($schema, ['additional' => 1]); // OK -$processor->process($schema, ['additional' => true]); // ERROR +$processor->process($schema, ['additional' => true]); // FEHLER +``` + +Eine neue Struktur können Sie durch Ableitung von einer anderen mit `extend()` erstellen: + +```php +$dog = Expect::structure([ + 'name' => Expect::string(), + 'age' => Expect::int(), +]); + +$dogWithBreed = $dog->extend([ + 'breed' => Expect::string(), +]); +``` + + +Array .{data-version:1.3.2} +--------------------------- + +Array mit definierten Schlüsseln. Für ihn gilt alles, was auch für [#Strukturen] gilt. + +```php +$schema = Expect::array([ + 'required' => Expect::string()->required(), + 'optional' => Expect::string(), // Standardwert ist null +]); ``` +Es kann auch ein indiziertes Array, bekannt als Tupel, definiert werden: -Verwerfungen .[#toc-deprecations] ---------------------------------- +```php +$schema = Expect::array([ + Expect::int(), + Expect::string(), + Expect::bool(), +]); -Sie können eine Eigenschaft mit der `deprecated([string $message])` Methode. Verwerfungshinweise werden von `$processor->getWarnings()` zurückgegeben: +$processor->process($schema, [1, 'hello', true]); // OK +``` + + +Veraltete Eigenschaften +----------------------- + +Sie können eine Eigenschaft mit der Methode `deprecated([string $message])` als veraltet markieren. Informationen über die Einstellung der Unterstützung werden mit `$processor->getWarnings()` zurückgegeben: ```php $schema = Expect::structure([ @@ -279,53 +319,53 @@ $processor->getWarnings(); // ["Das Element 'old' ist veraltet"] ``` -Bereiche: min() max() .[#toc-ranges-min-max] --------------------------------------------- +Bereiche: min() max() +--------------------- -Verwenden Sie `min()` und `max()`, um die Anzahl der Elemente für Arrays zu begrenzen: +Mit `min()` und `max()` kann die Anzahl der Elemente in Arrays begrenzt werden: ```php // Array, mindestens 10 Elemente, maximal 20 Elemente Expect::array()->min(10)->max(20); ``` -Bei Zeichenketten begrenzen Sie deren Länge: +Bei Strings kann ihre Länge begrenzt werden: ```php // String, mindestens 10 Zeichen lang, maximal 20 Zeichen Expect::string()->min(10)->max(20); ``` -Bei Zahlen begrenzen Sie ihren Wert: +Bei Zahlen kann ihr Wert begrenzt werden: ```php -// Ganzzahl, zwischen 10 und 20 einschließlich +// ganze Zahl, zwischen 10 und 20 einschließlich Expect::int()->min(10)->max(20); ``` -Es ist natürlich möglich, nur `min()` oder nur `max()` zu nennen: +Natürlich ist es möglich, nur `min()` oder nur `max()` anzugeben: ```php -// Zeichenkette, maximal 20 Zeichen +// String maximal 20 Zeichen Expect::string()->max(20); ``` -Reguläre Ausdrücke: pattern() .[#toc-regular-expressions-pattern] ------------------------------------------------------------------ +Reguläre Ausdrücke: pattern() +----------------------------- -Mit `pattern()` können Sie einen regulären Ausdruck angeben, mit dem die **gesamte** Eingabezeichenkette übereinstimmen muss (d.h. als ob sie in Zeichen eingeschlossen wäre `^` a `$`): +Mit `pattern()` kann ein regulärer Ausdruck angegeben werden, dem die **gesamte** Eingabezeichenkette entsprechen muss (d.h. als wäre sie von den Zeichen `^` und `$` umschlossen): ```php -// nur 9 Ziffern +// genau 9 Ziffern Expect::string()->pattern('\d{9}'); ``` -Benutzerdefinierte Assertions: assert() .[#toc-custom-assertions-assert] ------------------------------------------------------------------------- +Eigene Einschränkungen: assert() +-------------------------------- -Sie können weitere Einschränkungen mit `assert(callable $fn)` hinzufügen. +Beliebige weitere Einschränkungen geben wir mit `assert(callable $fn)` an. ```php $countIsEven = fn($v) => count($v) % 2 === 0; @@ -334,104 +374,115 @@ $schema = Expect::arrayOf('string') ->assert($countIsEven); // die Anzahl muss gerade sein $processor->process($schema, ['a', 'b']); // OK -$processor->process($schema, ['a', 'b', 'c']); // ERROR: 3 ist nicht gerade +$processor->process($schema, ['a', 'b', 'c']); // FEHLER: 3 ist keine gerade Anzahl ``` Oder ```php -Expect::string()->assert('is_file'); // die Datei muss existieren +Expect::string()->assert('is_file'); // Datei muss existieren ``` -Sie können Ihre eigene Beschreibung für jede Assertion hinzufügen. Sie wird Teil der Fehlermeldung sein. +Zu jeder Einschränkung können Sie eine eigene Beschreibung hinzufügen. Diese wird Teil der Fehlermeldung sein. ```php $schema = Expect::arrayOf('string') - ->assert($countIsEven, 'Gerade Elemente in Array'); + ->assert($countIsEven, 'Gerade Anzahl Elemente im Array'); $processor->process($schema, ['a', 'b', 'c']); -// Fehlgeschlagene Assertion "Gerade Elemente in Array" für Element mit Wert array. +// Failed assertion "Gerade Anzahl Elemente im Array" for item with value array. ``` -Die Methode kann wiederholt aufgerufen werden, um weitere Behauptungen hinzuzufügen. +Die Methode kann wiederholt aufgerufen werden, um mehrere Einschränkungen hinzuzufügen. Sie kann mit Aufrufen von `transform()` und `castTo()` verschachtelt werden. -Zuordnung zu Objekten: from() .[#toc-mapping-to-objects-from] -------------------------------------------------------------- +Transformationen: transform() .{data-version:1.2.5} +--------------------------------------------------- -Sie können aus der Klasse ein Strukturschema erzeugen. Beispiel: +Erfolgreich validierte Daten können mit einer eigenen Funktion angepasst werden: ```php -class Config -{ - public string $name; - public string|null $password; - public bool $admin = false; -} +// Umwandlung in Großbuchstaben: +Expect::string()->transform(fn(string $s) => strtoupper($s)); +``` -$schema = Expect::from(new Config); +Die Methode kann wiederholt aufgerufen werden, um mehrere Transformationen hinzuzufügen. Sie kann mit Aufrufen von `assert()` und `castTo()` verschachtelt werden. Die Operationen werden in der Reihenfolge ausgeführt, in der sie deklariert sind: -$data = [ - 'name' => 'jeff', -]; - -$normalized = $processor->process($schema, $data); -// $normalized instanceof Config -// $normalized = {'name' => 'jeff', 'password' => null, 'admin' => false} +```php +Expect::type('string|int') + ->castTo('string') + ->assert('ctype_lower', 'Alle Zeichen müssen klein geschrieben sein') + ->transform(fn(string $s) => strtoupper($s)); // Umwandlung in Großbuchstaben ``` -Wenn Sie PHP 7.4 oder höher verwenden, können Sie native Typen verwenden: +Die Methode `transform()` kann gleichzeitig den Wert transformieren und validieren. Dies ist oft einfacher und weniger redundant als die Verkettung von `transform()` und `assert()`. Zu diesem Zweck erhält die Funktion ein [Context |api:Nette\Schema\Context]-Objekt mit der Methode `addError()`, die verwendet werden kann, um Informationen über Validierungsprobleme hinzuzufügen: ```php -class Config -{ - public string $name; - public ?string $password; - public bool $admin = false; -} +Expect::string() + ->transform(function (string $s, Nette\Schema\Context $context) { + if (!ctype_lower($s)) { + $context->addError('Alle Zeichen müssen klein geschrieben sein', 'my.case.error'); + return null; + } -$schema = Expect::from(new Config); + return strtoupper($s); + }); ``` -Anonyme Klassen werden ebenfalls unterstützt: + +Typumwandlung: castTo() +----------------------- + +Erfolgreich validierte Daten können typumgewandelt werden: ```php -$schema = Expect::from(new class { - public string $name; - public ?string $password; - public bool $admin = false; -}); +Expect::scalar()->castTo('string'); ``` -Da die aus der Klassendefinition gewonnenen Informationen möglicherweise nicht ausreichen, können Sie mit dem zweiten Parameter ein benutzerdefiniertes Schema für die Elemente hinzufügen: +Neben nativen PHP-Typen kann auch in Klassen umgewandelt werden. Dabei wird unterschieden, ob es sich um eine einfache Klasse ohne Konstruktor oder eine Klasse mit Konstruktor handelt. Wenn die Klasse keinen Konstruktor hat, wird eine Instanz davon erstellt und alle Elemente der Struktur werden in die Properties geschrieben: ```php -$schema = Expect::from(new Config, [ - 'name' => Expect::string()->pattern('\w:.*'), -]); -``` +class Info +{ + public bool $processRefund; + public int $refundAmount; +} +Expect::structure([ + 'processRefund' => Expect::bool(), + 'refundAmount' => Expect::int(), +])->castTo(Info::class); -Casting: castTo() .[#toc-casting-castto] ----------------------------------------- +// erstellt '$obj = new Info' und schreibt in $obj->processRefund und $obj->refundAmount +``` -Erfolgreich überprüfte Daten können gecastet werden: +Wenn die Klasse einen Konstruktor hat, werden die Elemente der Struktur als benannte Parameter an den Konstruktor übergeben: ```php -Expect::scalar()->castTo('string'); +class Info +{ + public function __construct( + public bool $processRefund, + public int $refundAmount, + ) { + } +} + +// erstellt $obj = new Info(processRefund: ..., refundAmount: ...) ``` -Zusätzlich zu den nativen PHP-Typen können Sie auch auf Klassen casten: +Die Typumwandlung in Kombination mit einem skalaren Parameter erstellt ein Objekt und übergibt den Wert als einzigen Parameter an den Konstruktor: ```php -Expect::scalar()->castTo('AddressEntity'); +Expect::string()->castTo(DateTime::class); +// erstellt new DateTime(...) ``` -Normalisierung: before() .[#toc-normalization-before] ------------------------------------------------------ +Normalisierung: before() +------------------------ -Vor der eigentlichen Validierung können die Daten mit der Methode `before()` normalisiert werden. Als Beispiel nehmen wir ein Element an, das ein Array von Strings sein muss (z.B. `['a', 'b', 'c']`), aber eine Eingabe in Form einer Zeichenkette erhält `a b c`: +Vor der eigentlichen Validierung können die Daten mit der Methode `before()` normalisiert werden. Als Beispiel nehmen wir ein Element, das ein Array von Strings sein muss (zum Beispiel `['a', 'b', 'c']`), aber eine Eingabe in Form des Strings `a b c` akzeptiert: ```php $explode = fn($v) => explode(' ', $v); @@ -440,7 +491,48 @@ $schema = Expect::arrayOf('string') ->before($explode); $normalized = $processor->process($schema, 'a b c'); -// OK, liefert ['a', 'b', 'c'] +// OK und gibt ['a', 'b', 'c'] zurück +``` + + +Mapping auf Objekte: from() +--------------------------- + +Wir können uns das Strukturschema aus einer Klasse generieren lassen. Beispiel: + +```php +class Config +{ + public string $name; + public string|null $password; + public bool $admin = false; +} + +$schema = Expect::from(new Config); + +$data = [ + 'name' => 'franta', +]; + +$normalized = $processor->process($schema, $data); +// $normalized instanceof Config +// $normalized = {'name' => 'franta', 'password' => null, 'admin' => false} ``` -{{leftbar: nette:@menu-topics}} +Auch anonyme Klassen werden unterstützt: + +```php +$schema = Expect::from(new class { + public string $name; + public ?string $password; + public bool $admin = false; +}); +``` + +Da die aus der Klassendefinition gewonnenen Informationen möglicherweise nicht ausreichen, können Sie den Elementen mit dem zweiten Parameter ein eigenes Schema hinzufügen: + +```php +$schema = Expect::from(new Config, [ + 'name' => Expect::string()->pattern('\w:.*'), +]); +``` diff --git a/schema/de/@meta.texy b/schema/de/@meta.texy new file mode 100644 index 0000000000..2cf383a5cf --- /dev/null +++ b/schema/de/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Dokumentation}} +{{leftbar: nette:@menu-topics}} diff --git a/schema/el/@home.texy b/schema/el/@home.texy index 4041fa7bdb..da5349f6bf 100644 --- a/schema/el/@home.texy +++ b/schema/el/@home.texy @@ -1,8 +1,8 @@ -Σχήμα: Επικύρωση δεδομένων -************************** +Nette Schema +************ .[perex] -Μια πρακτική βιβλιοθήκη για την επικύρωση και την κανονικοποίηση δομών δεδομένων έναντι ενός δεδομένου σχήματος με ένα έξυπνο και κατανοητό API. +Μια πρακτική βιβλιοθήκη για την επικύρωση και την κανονικοποίηση δομών δεδομένων έναντι ενός δεδομένου schema με ένα έξυπνο, κατανοητό API. Εγκατάσταση: @@ -11,12 +11,12 @@ composer require nette/schema ``` -Βασική χρήση .[#toc-basic-usage] --------------------------------- +Βασική χρήση +------------ -Στη μεταβλητή `$schema` έχουμε ένα σχήμα επικύρωσης (τι ακριβώς σημαίνει αυτό και πώς να το δημιουργήσουμε θα πούμε αργότερα) και στη μεταβλητή `$data` έχουμε μια δομή δεδομένων που θέλουμε να επικυρώσουμε και να κανονικοποιήσουμε. Αυτό μπορεί να είναι, για παράδειγμα, δεδομένα που αποστέλλονται από τον χρήστη μέσω ενός API, ενός αρχείου ρυθμίσεων κ.λπ. +Στη μεταβλητή `$schema` έχουμε το schema επικύρωσης (τι ακριβώς σημαίνει αυτό και πώς να δημιουργήσετε ένα τέτοιο schema θα πούμε αμέσως) και στη μεταβλητή `$data` τη δομή δεδομένων που θέλουμε να επικυρώσουμε και να κανονικοποιήσουμε. Μπορεί να πρόκειται, για παράδειγμα, για δεδομένα που στάλθηκαν από τον χρήστη μέσω μιας διεπαφής API, ενός αρχείου διαμόρφωσης, κ.λπ. -Το έργο αναλαμβάνει η κλάση [api:Nette\Schema\Processor], η οποία επεξεργάζεται την είσοδο και είτε επιστρέφει κανονικοποιημένα δεδομένα είτε πετάει μια εξαίρεση [api:Nette\Schema\ValidationException] σε περίπτωση σφάλματος. +Την εργασία αναλαμβάνει η κλάση [api:Nette\Schema\Processor], η οποία επεξεργάζεται την είσοδο και είτε επιστρέφει τα κανονικοποιημένα δεδομένα, είτε σε περίπτωση σφάλματος ρίχνει την εξαίρεση [api:Nette\Schema\ValidationException]. ```php $processor = new Nette\Schema\Processor; @@ -24,17 +24,17 @@ $processor = new Nette\Schema\Processor; try { $normalized = $processor->process($schema, $data); } catch (Nette\Schema\ValidationException $e) { - echo 'Data is invalid: ' . $e->getMessage(); + echo 'Τα δεδομένα δεν είναι έγκυρα: ' . $e->getMessage(); } ``` -Η μέθοδος `$e->getMessages()` επιστρέφει πίνακα όλων των συμβολοσειρών μηνυμάτων και η `$e->getMessageObjects()` επιστρέφει όλα τα μηνύματα ως αντικείμενα "Nette\Schema\Message":https://api.nette.org/schema/master/Nette/Schema/Message.html. +Η μέθοδος `$e->getMessages()` επιστρέφει ένα array όλων των μηνυμάτων ως strings και η `$e->getMessageObjects()` επιστρέφει όλα τα μηνύματα ως αντικείμενα "Nette\Schema\Message":https://api.nette.org/schema/master/Nette/Schema/Message.html. -Ορισμός σχήματος .[#toc-defining-schema] ----------------------------------------- +Ορισμός του schema +------------------ -Και τώρα ας δημιουργήσουμε ένα σχήμα. Η κλάση [api:Nette\Schema\Expect] χρησιμοποιείται για τον ορισμό του, ουσιαστικά ορίζουμε τις προσδοκίες για το πώς θα πρέπει να μοιάζουν τα δεδομένα. Ας πούμε ότι τα δεδομένα εισόδου πρέπει να είναι μια δομή (π.χ. ένας πίνακας) που περιέχει στοιχεία `processRefund` τύπου bool και `refundAmount` τύπου int. +Και τώρα θα δημιουργήσουμε το schema. Για τον ορισμό του χρησιμοποιείται η κλάση [api:Nette\Schema\Expect], ορίζουμε στην πραγματικότητα τις προσδοκίες για το πώς πρέπει να μοιάζουν τα δεδομένα. Ας πούμε ότι τα δεδομένα εισόδου πρέπει να αποτελούν μια δομή (για παράδειγμα, ένα array) που περιέχει τα στοιχεία `processRefund` τύπου bool και `refundAmount` τύπου int. ```php use Nette\Schema\Expect; @@ -45,9 +45,9 @@ $schema = Expect::structure([ ]); ``` -Πιστεύουμε ότι ο ορισμός του σχήματος φαίνεται σαφής, ακόμη και αν τον βλέπετε για πρώτη φορά. +Πιστεύουμε ότι ο ορισμός του schema φαίνεται κατανοητός, ακόμα κι αν τον βλέπετε για πρώτη φορά. -Ας στείλουμε τα ακόλουθα δεδομένα για επικύρωση: +Θα στείλουμε τα ακόλουθα δεδομένα για επικύρωση: ```php $data = [ @@ -55,10 +55,10 @@ $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // Εντάξει, περνάει +$normalized = $processor->process($schema, $data); // OK, περνάει την επικύρωση ``` -Η έξοδος, δηλαδή η τιμή `$normalized`, είναι το αντικείμενο `stdClass`. Εάν θέλουμε η έξοδος να είναι ένας πίνακας, προσθέτουμε ένα cast στο schema `Expect::structure([...])->castTo('array')`. +Η έξοδος, δηλαδή η τιμή `$normalized`, είναι ένα αντικείμενο `stdClass`. Αν θέλαμε η έξοδος να είναι ένα array, θα συμπληρώναμε το schema με τη μετατροπή τύπου `Expect::structure([...])->castTo('array')`. Όλα τα στοιχεία της δομής είναι προαιρετικά και έχουν προεπιλεγμένη τιμή `null`. Παράδειγμα: @@ -67,15 +67,15 @@ $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // Εντάξει, περνάει +$normalized = $processor->process($schema, $data); // OK, περνάει την επικύρωση // $normalized = {'processRefund' => null, 'refundAmount' => 17} ``` -Το γεγονός ότι η προεπιλεγμένη τιμή είναι `null` δεν σημαίνει ότι θα γινόταν δεκτή στα δεδομένα εισόδου `'processRefund' => null`. Όχι, η είσοδος πρέπει να είναι boolean, δηλαδή μόνο `true` ή `false`. Θα πρέπει να επιτρέψουμε ρητά το `null` μέσω του `Expect::bool()->nullable()`. +Το ότι η προεπιλεγμένη τιμή είναι `null`, δεν σημαίνει ότι θα γινόταν αποδεκτό το `'processRefund' => null` στα δεδομένα εισόδου. Όχι, η είσοδος πρέπει να είναι boolean, δηλαδή μόνο `true` ή `false`. Θα έπρεπε να επιτρέψουμε το `null` ρητά με το `Expect::bool()->nullable()`. -Ένα στοιχείο μπορεί να καταστεί υποχρεωτικό χρησιμοποιώντας το `Expect::bool()->required()`. Αλλάζουμε την προεπιλεγμένη τιμή σε `false` χρησιμοποιώντας το `Expect::bool()->default(false)` ή σύντομα χρησιμοποιώντας το `Expect::bool(false)`. +Ένα στοιχείο μπορεί να καταστεί υποχρεωτικό με το `Expect::bool()->required()`. Αλλάζουμε την προεπιλεγμένη τιμή, για παράδειγμα, σε `false` με το `Expect::bool()->default(false)` ή εν συντομία `Expect::bool(false)`. -Και τι θα γινόταν αν θέλαμε να δεχτούμε το `1` and `0` εκτός από booleans; Τότε απαριθμούμε τις επιτρεπόμενες τιμές, τις οποίες επίσης θα κανονικοποιήσουμε σε boolean: +Και τι θα γινόταν αν θέλαμε να αποδεχτούμε, εκτός από boolean, και τα `1` και `0`; Τότε θα αναφέραμε μια απαρίθμηση τιμών, τις οποίες επιπλέον θα αφήναμε να κανονικοποιηθούν σε boolean: ```php $schema = Expect::structure([ @@ -87,13 +87,13 @@ $normalized = $processor->process($schema, $data); is_bool($normalized->processRefund); // true ``` -Τώρα γνωρίζετε τα βασικά για το πώς ορίζεται το σχήμα και πώς συμπεριφέρονται τα επιμέρους στοιχεία της δομής. Τώρα θα δείξουμε ποια είναι όλα τα άλλα στοιχεία που μπορούν να χρησιμοποιηθούν στον ορισμό ενός σχήματος. +Τώρα γνωρίζετε ήδη τα βασικά για το πώς ορίζεται ένα schema και πώς συμπεριφέρονται τα μεμονωμένα στοιχεία της δομής. Τώρα θα δείξουμε ποια άλλα στοιχεία μπορούν να χρησιμοποιηθούν κατά τον ορισμό του schema. -Τύποι δεδομένων: type() .[#toc-data-types-type] ------------------------------------------------ +Τύποι δεδομένων: type() +----------------------- -Όλοι οι τυποποιημένοι τύποι δεδομένων PHP μπορούν να αναφερθούν στο σχήμα: +Στο schema μπορείτε να αναφέρετε όλους τους τυπικούς τύπους δεδομένων της PHP: ```php Expect::string($default = null) @@ -104,63 +104,63 @@ Expect::null() Expect::array($default = []) ``` -Και στη συνέχεια όλοι οι τύποι που [υποστηρίζονται από τους επικυρωτές |utils:validators#Expected Types] μέσω του `Expect::type('scalar')` ή της συντομογραφίας `Expect::scalar()`. Επίσης, γίνονται δεκτά ονόματα κλάσεων ή διεπαφών, π.χ. `Expect::type('AddressEntity')`. +Και επιπλέον όλους τους τύπους που [υποστηρίζονται από την κλάση Validators |utils:validators#Αναμενόμενοι τύποι], για παράδειγμα `Expect::type('scalar')` ή εν συντομία `Expect::scalar()`. Επίσης ονόματα κλάσεων ή interfaces, για παράδειγμα `Expect::type('AddressEntity')`. -Μπορείτε επίσης να χρησιμοποιήσετε τον συμβολισμό της ένωσης: +Μπορείτε επίσης να χρησιμοποιήσετε τη σύνταξη union: ```php Expect::type('bool|string|array') ``` -Η προεπιλεγμένη τιμή είναι πάντα `null` εκτός από τις περιπτώσεις `array` και `list`, όπου είναι ένας άδειος πίνακας. (Μια λίστα είναι ένας πίνακας με ευρετηρίαση σε αύξουσα σειρά αριθμητικών κλειδιών από το μηδέν, δηλαδή ένας μη συσχετιστικός πίνακας). +Η προεπιλεγμένη τιμή είναι πάντα `null` με εξαίρεση για τα `array` και `list`, όπου είναι ένας κενός πίνακας. (Ένα List είναι ένα array ευρετηριασμένο σύμφωνα με μια αύξουσα σειρά αριθμητικών κλειδιών από το μηδέν, δηλαδή ένας μη συσχετιστικός πίνακας). -Πίνακας τιμών: arrayOf() listOf() .[#toc-array-of-values-arrayof-listof] ------------------------------------------------------------------------- +Πίνακες τιμών: arrayOf() listOf() +--------------------------------- -Ο πίνακας είναι πολύ γενική δομή, είναι πιο χρήσιμο να καθορίσετε ακριβώς ποια στοιχεία μπορεί να περιέχει. Για παράδειγμα, ένας πίνακας του οποίου τα στοιχεία μπορούν να είναι μόνο συμβολοσειρές: +Ένα Array αντιπροσωπεύει μια πολύ γενική δομή, είναι πιο χρήσιμο να καθορίσετε ποια ακριβώς στοιχεία μπορεί να περιέχει. Για παράδειγμα, ένα array του οποίου τα στοιχεία μπορούν να είναι μόνο strings: ```php $schema = Expect::arrayOf('string'); $processor->process($schema, ['hello', 'world']); // OK $processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK -$processor->process($schema, ['key' => 123]); // ΣΦΑΛΜΑ: Το 123 δεν είναι συμβολοσειρά +$processor->process($schema, ['key' => 123]); // ΣΦΑΛΜΑ: το 123 δεν είναι string ``` -Η δεύτερη παράμετρος μπορεί να χρησιμοποιηθεί για τον προσδιορισμό των κλειδιών (από την έκδοση 1.2): +Με τη δεύτερη παράμετρο μπορείτε να καθορίσετε τα κλειδιά (από την έκδοση 1.2): ```php $schema = Expect::arrayOf('string', 'int'); $processor->process($schema, ['hello', 'world']); // OK -$processor->process($schema, ['a' => 'hello']); // ΣΦΑΛΜΑ: 'a' δεν είναι int +$processor->process($schema, ['a' => 'hello']); // ΣΦΑΛΜΑ: το 'a' δεν είναι int ``` -Η λίστα είναι ένας δεικτοδοτημένος πίνακας: +Ένα List είναι ένας ευρετηριασμένος πίνακας: ```php $schema = Expect::listOf('string'); $processor->process($schema, ['a', 'b']); // OK -$processor->process($schema, ['a', 123]); // ΣΦΑΛΜΑ: Το 123 δεν είναι συμβολοσειρά -$processor->process($schema, ['key' => 'a']); // ΣΦΑΛΜΑ: δεν είναι λίστα -$processor->process($schema, [1 => 'a', 0 => 'b']); // ΣΦΑΛΜΑ: δεν είναι λίστα +$processor->process($schema, ['a', 123]); // ΣΦΑΛΜΑ: το 123 δεν είναι string +$processor->process($schema, ['key' => 'a']); // ΣΦΑΛΜΑ: δεν είναι list +$processor->process($schema, [1 => 'a', 0 => 'b']); // ΣΦΑΛΜΑ: επίσης δεν είναι list ``` -Η παράμετρος μπορεί επίσης να είναι ένα σχήμα, οπότε μπορούμε να γράψουμε: +Η παράμετρος μπορεί να είναι και ένα schema, οπότε μπορούμε να γράψουμε: ```php Expect::arrayOf(Expect::bool()) ``` -Η προεπιλεγμένη τιμή είναι ένας άδειος πίνακας. Εάν καθορίσετε την προεπιλεγμένη τιμή, θα συγχωνευτεί με τα δεδομένα που έχουν περάσει. Αυτό μπορεί να απενεργοποιηθεί χρησιμοποιώντας το `mergeDefaults(false)` (από την έκδοση 1.1). +Η προεπιλεγμένη τιμή είναι ένας κενός πίνακας. Αν καθορίσετε μια προεπιλεγμένη τιμή, θα συγχωνευθεί με τα παρεχόμενα δεδομένα. Αυτό μπορεί να απενεργοποιηθεί χρησιμοποιώντας το `mergeDefaults(false)` (από την έκδοση 1.1). -Απαρίθμηση: anyOf() .[#toc-enumeration-anyof] ---------------------------------------------- +Απαρίθμηση: anyOf() +------------------- -`anyOf()` είναι ένα σύνολο τιμών ή σχημάτων που μπορεί να είναι μια τιμή. Εδώ είναι πώς να γράψετε έναν πίνακα στοιχείων που μπορεί να είναι είτε `'a'`, `true`, είτε `null`: +Το `anyOf()` αντιπροσωπεύει μια απαρίθμηση τιμών ή schemas που μπορεί να λάβει μια τιμή. Έτσι γράφουμε ένα array στοιχείων που μπορούν να είναι είτε `'a'`, `true` ή `null`: ```php $schema = Expect::listOf( @@ -171,7 +171,7 @@ $processor->process($schema, ['a', true, null, 'a']); // OK $processor->process($schema, ['a', false]); // ΣΦΑΛΜΑ: το false δεν ανήκει εκεί ``` -Τα στοιχεία της απαρίθμησης μπορούν επίσης να είναι σχήματα: +Τα στοιχεία της απαρίθμησης μπορούν να είναι και schemas: ```php $schema = Expect::listOf( @@ -182,9 +182,9 @@ $processor->process($schema, ['foo', true, null, 'bar']); // OK $processor->process($schema, [123]); // ΣΦΑΛΜΑ ``` -Η μέθοδος `anyOf()` δέχεται παραλλαγές ως μεμονωμένες παραμέτρους, όχι ως πίνακα. Για να της παραδώσετε έναν πίνακα τιμών, χρησιμοποιήστε τον τελεστή αποσυμπίεσης `anyOf(...$variants)`. +Η μέθοδος `anyOf()` δέχεται τις παραλλαγές ως μεμονωμένες παράμετροι, όχι ως array. Αν θέλετε να της περάσετε ένα array τιμών, χρησιμοποιήστε τον τελεστή unpacking `anyOf(...$variants)`. -Η προεπιλεγμένη τιμή είναι `null`. Χρησιμοποιήστε τη μέθοδο `firstIsDefault()` για να κάνετε το πρώτο στοιχείο προεπιλεγμένο: +Η προεπιλεγμένη τιμή είναι `null`. Με τη μέθοδο `firstIsDefault()` κάνουμε το πρώτο στοιχείο προεπιλογή: ```php // η προεπιλογή είναι 'hello' @@ -192,12 +192,12 @@ Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault(); ``` -Δομές .[#toc-structures] ------------------------- +Δομές +----- -Οι δομές είναι αντικείμενα με καθορισμένα κλειδιά. Κάθε ένα από αυτά τα ζεύγη κλειδί => τιμή αναφέρεται ως "ιδιότητα": +Οι δομές είναι αντικείμενα με ορισμένα κλειδιά. Κάθε ζεύγος κλειδί => τιμή αναφέρεται ως «ιδιότητα»: -Οι δομές δέχονται πίνακες και αντικείμενα και επιστρέφουν αντικείμενα `stdClass` (εκτός αν το αλλάξετε με το `castTo('array')`, κ.λπ.). +Οι δομές δέχονται arrays και αντικείμενα και επιστρέφουν αντικείμενα `stdClass`. Από προεπιλογή, όλες οι ιδιότητες είναι προαιρετικές και έχουν προεπιλεγμένη τιμή `null`. Μπορείτε να ορίσετε υποχρεωτικές ιδιότητες χρησιμοποιώντας το `required()`: @@ -208,13 +208,13 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['optional' => '']); -// ERROR: option 'required' is missing +// ΣΦΑΛΜΑ: η επιλογή 'required' λείπει $processor->process($schema, ['required' => 'foo']); -// OK, returns {'required' => 'foo', 'optional' => null} +// OK, επιστρέφει {'required' => 'foo', 'optional' => null} ``` -Εάν δεν θέλετε να εξάγετε ιδιότητες με μόνο μια προεπιλεγμένη τιμή, χρησιμοποιήστε το `skipDefaults()`: +Αν δεν θέλετε να έχετε στην έξοδο ιδιότητες με προεπιλεγμένη τιμή, χρησιμοποιήστε το `skipDefaults()`: ```php $schema = Expect::structure([ @@ -226,7 +226,7 @@ $processor->process($schema, ['required' => 'foo']); // OK, επιστρέφει {'required' => 'foo'} ``` -Παρόλο που το `null` είναι η προεπιλεγμένη τιμή της ιδιότητας `optional`, δεν επιτρέπεται στα δεδομένα εισόδου (η τιμή πρέπει να είναι συμβολοσειρά). Οι ιδιότητες που δέχονται το `null` ορίζονται χρησιμοποιώντας το `nullable()`: +Αν και το `null` είναι η προεπιλεγμένη τιμή της ιδιότητας `optional`, δεν είναι επιτρεπτό στα δεδομένα εισόδου (η τιμή πρέπει να είναι string). Ορίζουμε ιδιότητες που δέχονται `null` χρησιμοποιώντας το `nullable()`: ```php $schema = Expect::structure([ @@ -235,12 +235,14 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['optional' => null]); -// ERROR: 'optional' αναμένει να είναι string, null given. +// ΣΦΑΛΜΑ: το 'optional' αναμένεται να είναι string, δόθηκε null. $processor->process($schema, ['nullable' => null]); // OK, επιστρέφει {'optional' => null, 'nullable' => null} ``` +Ένα array όλων των ιδιοτήτων της δομής επιστρέφεται από τη μέθοδο `getShape()`. + Από προεπιλογή, δεν μπορούν να υπάρχουν επιπλέον στοιχεία στα δεδομένα εισόδου: ```php @@ -252,7 +254,7 @@ $processor->process($schema, ['additional' => 1]); // ΣΦΑΛΜΑ: Μη αναμενόμενο στοιχείο 'additional' ``` -Το οποίο μπορούμε να αλλάξουμε με το `otherItems()`. Ως παράμετρος, θα καθορίσουμε το σχήμα για κάθε επιπλέον στοιχείο: +Κάτι που μπορούμε να αλλάξουμε χρησιμοποιώντας το `otherItems()`. Ως παράμετρο αναφέρουμε το schema σύμφωνα με το οποίο θα επικυρωθούν τα επιπλέον στοιχεία: ```php $schema = Expect::structure([ @@ -263,78 +265,116 @@ $processor->process($schema, ['additional' => 1]); // OK $processor->process($schema, ['additional' => true]); // ΣΦΑΛΜΑ ``` +Μπορείτε να δημιουργήσετε μια νέα δομή κληρονομώντας από μια άλλη χρησιμοποιώντας το `extend()`: + +```php +$dog = Expect::structure([ + 'name' => Expect::string(), + 'age' => Expect::int(), +]); + +$dogWithBreed = $dog->extend([ + 'breed' => Expect::string(), +]); +``` + + +Πίνακες .{data-version:1.3.2} +----------------------------- + +Ένα Array με ορισμένα κλειδιά. Ισχύουν για αυτό όλα όσα ισχύουν για τις [#δομές]. + +```php +$schema = Expect::array([ + 'required' => Expect::string()->required(), + 'optional' => Expect::string(), // η προεπιλεγμένη τιμή είναι null +]); +``` + +Μπορείτε επίσης να ορίσετε έναν ευρετηριασμένο πίνακα, γνωστό ως tuple: -Αποσβέσεις .[#toc-deprecations] -------------------------------- +```php +$schema = Expect::array([ + Expect::int(), + Expect::string(), + Expect::bool(), +]); -Μπορείτε να καταργήσετε μια ιδιότητα χρησιμοποιώντας την εντολή `deprecated([string $message])` μέθοδο. Οι ειδοποιήσεις κατάργησης επιστρέφονται από το `$processor->getWarnings()`: +$processor->process($schema, [1, 'hello', true]); // OK +``` + + +Καταργημένες ιδιότητες +---------------------- + +Μπορείτε να επισημάνετε μια ιδιότητα ως deprecated χρησιμοποιώντας τη μέθοδο `deprecated([string $message])`. Οι πληροφορίες σχετικά με τη λήξη υποστήριξης επιστρέφονται μέσω του `$processor->getWarnings()`: ```php $schema = Expect::structure([ - 'old' => Expect::int()->deprecated('The item %path% is deprecated'), + 'old' => Expect::int()->deprecated('Το στοιχείο %path% έχει καταργηθεί'), ]); $processor->process($schema, ['old' => 1]); // OK -$processor->getWarnings(); // ["The item 'old' is deprecated"] +$processor->getWarnings(); // ["Το στοιχείο 'old' έχει καταργηθεί"] ``` -Εύρος: min() max() .[#toc-ranges-min-max] ------------------------------------------ +Εύρη: min() max() +----------------- -Χρησιμοποιήστε τα `min()` και `max()` για να περιορίσετε τον αριθμό των στοιχείων των πινάκων: +Χρησιμοποιώντας τα `min()` και `max()`, μπορείτε να περιορίσετε τον αριθμό των στοιχείων σε arrays: ```php -// πίνακας, τουλάχιστον 10 στοιχεία, μέγιστο 20 στοιχεία +// array, τουλάχιστον 10 στοιχεία, το πολύ 20 στοιχεία Expect::array()->min(10)->max(20); ``` -Για συμβολοσειρές, περιορίστε το μήκος τους: +Για strings, περιορίστε το μήκος τους: ```php -// συμβολοσειρά, τουλάχιστον 10 χαρακτήρες, το πολύ 20 χαρακτήρες +// string, τουλάχιστον 10 χαρακτήρες μήκος, το πολύ 20 χαρακτήρες Expect::string()->min(10)->max(20); ``` Για αριθμούς, περιορίστε την τιμή τους: ```php -// ακέραιος αριθμός, μεταξύ 10 και 20 συμπεριλαμβανομένου +// ακέραιος, μεταξύ 10 και 20 συμπεριλαμβανομένων Expect::int()->min(10)->max(20); ``` -Φυσικά, είναι δυνατόν να αναφέρετε μόνο το `min()` ή μόνο το `max()`: +Φυσικά, είναι δυνατόν να αναφέρετε μόνο το `min()`, ή μόνο το `max()`: ```php -// συμβολοσειρά, μέγιστο 20 χαρακτήρες +// string το πολύ 20 χαρακτήρες Expect::string()->max(20); ``` -Κανονικές εκφράσεις: pattern() .[#toc-regular-expressions-pattern] ------------------------------------------------------------------- +Κανονικές εκφράσεις: pattern() +------------------------------ -Χρησιμοποιώντας το `pattern()`, μπορείτε να καθορίσετε μια κανονική έκφραση με την οποία πρέπει να ταιριάζει **όλη** η συμβολοσειρά εισόδου (δηλαδή σαν να ήταν τυλιγμένη σε χαρακτήρες `^` a `$`): +Χρησιμοποιώντας το `pattern()`, μπορείτε να αναφέρετε μια regular expression στην οποία πρέπει να αντιστοιχεί **ολόκληρο** το string εισόδου (δηλαδή, σαν να ήταν περιτυλιγμένο με τους χαρακτήρες `^` και `$`): ```php -// μόλις 9 ψηφία +// ακριβώς 9 ψηφία Expect::string()->pattern('\d{9}'); ``` -assert() .[#toc-custom-assertions-assert] ------------------------------------------ +Προσαρμοσμένοι περιορισμοί: assert() +------------------------------------ -Μπορείτε να προσθέσετε οποιουσδήποτε άλλους περιορισμούς χρησιμοποιώντας το `assert(callable $fn)`. +Οποιουσδήποτε άλλους περιορισμούς εισάγουμε χρησιμοποιώντας το `assert(callable $fn)`. ```php $countIsEven = fn($v) => count($v) % 2 === 0; $schema = Expect::arrayOf('string') - ->assert($countIsEven); // η καταμέτρηση πρέπει να είναι ζυγός + ->assert($countIsEven); // ο αριθμός πρέπει να είναι ζυγός $processor->process($schema, ['a', 'b']); // OK -$processor->process($schema, ['a', 'b', 'c']); // ΣΦΑΛΜΑ: Το 3 δεν είναι άρτιο +$processor->process($schema, ['a', 'b', 'c']); // ΣΦΑΛΜΑ: το 3 δεν είναι ζυγός αριθμός ``` Ή @@ -343,95 +383,106 @@ $processor->process($schema, ['a', 'b', 'c']); // ΣΦΑΛΜΑ: Το 3 δεν ε Expect::string()->assert('is_file'); // το αρχείο πρέπει να υπάρχει ``` -Μπορείτε να προσθέσετε τη δική σας περιγραφή για κάθε ισχυρισμό. Θα είναι μέρος του μηνύματος σφάλματος. +Σε κάθε περιορισμό μπορείτε να προσθέσετε μια προσαρμοσμένη περιγραφή. Αυτή θα είναι μέρος του μηνύματος σφάλματος. ```php $schema = Expect::arrayOf('string') - ->assert($countIsEven, 'Even items in array'); + ->assert($countIsEven, 'Ζυγός αριθμός στοιχείων στον πίνακα'); $processor->process($schema, ['a', 'b', 'c']); -// Αποτυχημένος ισχυρισμός "Even items in array" για στοιχείο με τιμή array. +// Failed assertion "Ζυγός αριθμός στοιχείων στον πίνακα" for item with value array. ``` -Η μέθοδος μπορεί να κληθεί επανειλημμένα για να προσθέσετε περισσότερους ισχυρισμούς. +Η μέθοδος μπορεί να κληθεί επανειλημμένα για να προσθέσετε περισσότερους περιορισμούς. Μπορεί να εναλλάσσεται με κλήσεις των `transform()` και `castTo()`. -Αντιστοίχιση σε αντικείμενα: from() .[#toc-mapping-to-objects-from] -------------------------------------------------------------------- +Μετασχηματισμοί: transform() .{data-version:1.2.5} +-------------------------------------------------- -Μπορείτε να δημιουργήσετε σχήμα δομής από την κλάση. Παράδειγμα: +Τα επιτυχώς επικυρωμένα δεδομένα μπορούν να τροποποιηθούν χρησιμοποιώντας μια προσαρμοσμένη συνάρτηση: ```php -class Config -{ - public string $name; - public string|null $password; - public bool $admin = false; -} +// μετατροπή σε κεφαλαία: +Expect::string()->transform(fn(string $s) => strtoupper($s)); +``` -$schema = Expect::from(new Config); +Η μέθοδος μπορεί να κληθεί επανειλημμένα για να προσθέσετε περισσότερους μετασχηματισμούς. Μπορεί να εναλλάσσεται με κλήσεις των `assert()` και `castTo()`. Οι λειτουργίες εκτελούνται με τη σειρά που δηλώνονται: -$data = [ - 'name' => 'jeff', -]; - -$normalized = $processor->process($schema, $data); -// $normalized instanceof Config -// $normalized = {'name' => 'jeff', 'password' => null, 'admin' => false} +```php +Expect::type('string|int') + ->castTo('string') + ->assert('ctype_lower', 'Όλοι οι χαρακτήρες πρέπει να είναι πεζοί') + ->transform(fn(string $s) => strtoupper($s)); // μετατροπή σε κεφαλαία ``` -Εάν χρησιμοποιείτε PHP 7.4 ή νεότερη έκδοση, μπορείτε να χρησιμοποιήσετε εγγενείς τύπους: +Η μέθοδος `transform()` μπορεί ταυτόχρονα να μετασχηματίσει και να επικυρώσει την τιμή. Αυτό είναι συχνά απλούστερο και λιγότερο διπλότυπο από την αλυσίδωση των `transform()` και `assert()`. Για αυτόν τον σκοπό, η συνάρτηση λαμβάνει ένα αντικείμενο [Context |api:Nette\Schema\Context] με τη μέθοδο `addError()`, η οποία μπορεί να χρησιμοποιηθεί για την προσθήκη πληροφοριών σχετικά με προβλήματα επικύρωσης: ```php -class Config -{ - public string $name; - public ?string $password; - public bool $admin = false; -} +Expect::string() + ->transform(function (string $s, Nette\Schema\Context $context) { + if (!ctype_lower($s)) { + $context->addError('Όλοι οι χαρακτήρες πρέπει να είναι πεζοί', 'my.case.error'); + return null; + } -$schema = Expect::from(new Config); + return strtoupper($s); + }); ``` -Υποστηρίζονται επίσης ανώνυμες κλάσεις: + +Μετατροπή τύπου: castTo() +------------------------- + +Τα επιτυχώς επικυρωμένα δεδομένα μπορούν να μετατραπούν ως προς τον τύπο: ```php -$schema = Expect::from(new class { - public string $name; - public ?string $password; - public bool $admin = false; -}); +Expect::scalar()->castTo('string'); ``` -Μπορείτε να προσθέσετε ένα προσαρμοσμένο σχήμα για τα στοιχεία με τη δεύτερη παράμετρο: +Εκτός από τους εγγενείς τύπους PHP, μπορείτε να μετατρέψετε τον τύπο και σε κλάσεις. Εδώ διακρίνεται αν πρόκειται για μια απλή κλάση χωρίς κατασκευαστή ή μια κλάση με κατασκευαστή. Αν η κλάση δεν έχει κατασκευαστή, δημιουργείται μια instance της και όλα τα στοιχεία της δομής γράφονται στις ιδιότητες: ```php -$schema = Expect::from(new Config, [ - 'name' => Expect::string()->pattern('\w:.*'), -]); -``` +class Info +{ + public bool $processRefund; + public int $refundAmount; +} +Expect::structure([ + 'processRefund' => Expect::bool(), + 'refundAmount' => Expect::int(), +])->castTo(Info::class); -Casting: castTo() .[#toc-casting-castto] ----------------------------------------- +// δημιουργεί '$obj = new Info' και γράφει στα $obj->processRefund και $obj->refundAmount +``` -Τα επιτυχώς επικυρωμένα δεδομένα μπορούν να μεταφερθούν: +Αν η κλάση έχει κατασκευαστή, τα στοιχεία της δομής περνούν ως ονομασμένες παράμετροι στον κατασκευαστή: ```php -Expect::scalar()->castTo('string'); +class Info +{ + public function __construct( + public bool $processRefund, + public int $refundAmount, + ) { + } +} + +// δημιουργεί $obj = new Info(processRefund: ..., refundAmount: ...) ``` -Εκτός από τους εγγενείς τύπους της PHP, μπορείτε επίσης να κάνετε cast σε κλάσεις: +Η μετατροπή τύπου σε συνδυασμό με μια scalar παράμετρο δημιουργεί ένα αντικείμενο και περνά την τιμή ως μοναδική παράμετρο στον κατασκευαστή: ```php -Expect::scalar()->castTo('AddressEntity'); +Expect::string()->castTo(DateTime::class); +// δημιουργεί new DateTime(...) ``` -Κανονικοποίηση: before() .[#toc-normalization-before] ------------------------------------------------------ +Κανονικοποίηση: before() +------------------------ -Πριν από την ίδια την επικύρωση, τα δεδομένα μπορούν να κανονικοποιηθούν χρησιμοποιώντας τη μέθοδο `before()`. Ως παράδειγμα, ας έχουμε ένα στοιχείο που πρέπει να είναι ένας πίνακας συμβολοσειρών (π.χ. `['a', 'b', 'c']`), αλλά δέχεται είσοδο με τη μορφή συμβολοσειράς `a b c`: +Πριν από την ίδια την επικύρωση, τα δεδομένα μπορούν να κανονικοποιηθούν χρησιμοποιώντας τη μέθοδο `before()`. Ως παράδειγμα, ας αναφέρουμε ένα στοιχείο που πρέπει να είναι ένα array από strings (για παράδειγμα `['a', 'b', 'c']`), αλλά δέχεται είσοδο με τη μορφή του string `a b c`: ```php $explode = fn($v) => explode(' ', $v); @@ -440,7 +491,48 @@ $schema = Expect::arrayOf('string') ->before($explode); $normalized = $processor->process($schema, 'a b c'); -// OK, επιστρέφει ['a', 'b', 'c'] +// OK και επιστρέφει ['a', 'b', 'c'] ``` -{{leftbar: nette:@menu-topics}} + +Αντιστοίχιση σε αντικείμενα: from() +----------------------------------- + +Μπορούμε να αφήσουμε το schema της δομής να παραχθεί από μια κλάση. Παράδειγμα: + +```php +class Config +{ + public string $name; + public string|null $password; + public bool $admin = false; +} + +$schema = Expect::from(new Config); + +$data = [ + 'name' => 'franta', +]; + +$normalized = $processor->process($schema, $data); +// $normalized instanceof Config +// $normalized = {'name' => 'franta', 'password' => null, 'admin' => false} +``` + +Υποστηρίζονται επίσης οι ανώνυμες κλάσεις: + +```php +$schema = Expect::from(new class { + public string $name; + public ?string $password; + public bool $admin = false; +}); +``` + +Επειδή οι πληροφορίες που λαμβάνονται από τον ορισμό της κλάσης μπορεί να μην είναι επαρκείς, μπορείτε να συμπληρώσετε τα στοιχεία με ένα προσαρμοσμένο schema με τη δεύτερη παράμετρο: + +```php +$schema = Expect::from(new Config, [ + 'name' => Expect::string()->pattern('\w:.*'), +]); +``` diff --git a/schema/el/@meta.texy b/schema/el/@meta.texy new file mode 100644 index 0000000000..a09ce5fe0d --- /dev/null +++ b/schema/el/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Τεκμηρίωση}} +{{leftbar: nette:@menu-topics}} diff --git a/schema/en/@home.texy b/schema/en/@home.texy index 2d1eb11cb2..2df0609b3d 100644 --- a/schema/en/@home.texy +++ b/schema/en/@home.texy @@ -1,8 +1,8 @@ -Schema: Data Validation -*********************** +Nette Schema +************ .[perex] -A practical library for validation and normalization of data structures against a given schema with a smart & easy-to-understand API. +A practical library for validating and normalizing data structures against a given schema with a smart, easy-to-understand API. Installation: @@ -14,9 +14,9 @@ composer require nette/schema Basic Usage ----------- -In variable `$schema` we have a validation schema (what exactly this means and how to create it we will say later) and in variable `$data` we have a data structure that we want to validate and normalize. This can be, for example, data sent by the user through an API, configuration file, etc. +In the variable `$schema`, we have a validation schema (we'll explain what this means and how to create one in a moment), and in the variable `$data`, we have the data structure we want to validate and normalize. This could be, for example, data submitted by a user via an API, a configuration file, etc. -The task is handled by the [api:Nette\Schema\Processor] class, which processes the input and either returns normalized data or throws an [api:Nette\Schema\ValidationException] exception on error. +The task is handled by the [api:Nette\Schema\Processor] class, which processes the input and either returns normalized data or throws a [api:Nette\Schema\ValidationException] exception if an error occurs. ```php $processor = new Nette\Schema\Processor; @@ -28,13 +28,13 @@ try { } ``` -Method `$e->getMessages()` returns array of all message strings and `$e->getMessageObjects()` return all messages as "Nette\Schema\Message":https://api.nette.org/schema/master/Nette/Schema/Message.html objects. +The method `$e->getMessages()` returns an array of all messages as strings, and `$e->getMessageObjects()` returns all messages as "Nette\Schema\Message":https://api.nette.org/schema/master/Nette/Schema/Message.html objects. -Defining Schema ---------------- +Defining the Schema +------------------- -And now let's create a schema. The class [api:Nette\Schema\Expect] is used to define it, we actually define expectations of what the data should look like. Let's say that the input data must be a structure (e.g. an array) containing elements `processRefund` of type bool and `refundAmount` of type int. +And now let's create the schema. The class [api:Nette\Schema\Expect] is used to define it; we essentially define expectations for what the data should look like. Let's say the input data must be a structure (e.g., an array) containing elements `processRefund` of type bool and `refundAmount` of type int. ```php use Nette\Schema\Expect; @@ -45,9 +45,9 @@ $schema = Expect::structure([ ]); ``` -We believe that the schema definition looks clear, even if you see it for the very first time. +We believe the schema definition looks understandable, even if you're seeing it for the first time. -Lets send the following data for validation: +Let's send the following data for validation: ```php $data = [ @@ -55,27 +55,27 @@ $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // OK, it passes +$normalized = $processor->process($schema, $data); // OK, passes validation ``` -The output, i.e. the value `$normalized`, is the object `stdClass`. If we want the output to be an array, we add a cast to schema `Expect::structure([...])->castTo('array')`. +The output, i.e., the value `$normalized`, is a `stdClass` object. If we wanted the output to be an array, we would add casting `->castTo('array')` to the schema. -All elements of the structure are optional and have a default value `null`. Example: +All elements of the structure are optional and have a default value of `null`. Example: ```php $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // OK, it passes +$normalized = $processor->process($schema, $data); // OK, passes validation // $normalized = {'processRefund' => null, 'refundAmount' => 17} ``` -The fact that the default value is `null` does not mean that it would be accepted in the input data `'processRefund' => null`. No, the input must be boolean, i.e. only `true` or `false`. We would have to explicitly allow `null` via `Expect::bool()->nullable()`. +The fact that the default value is `null` does not mean it would accept `'processRefund' => null` in the input data. No, the input must be a boolean, i.e. `true` or `false` only. We would have to explicitly allow `null` using `Expect::bool()->nullable()`. -An item can be made mandatory using `Expect::bool()->required()`. We change the default value to `false` using `Expect::bool()->default(false)` or shortly using `Expect::bool(false)`. +An item can be made mandatory using `Expect::bool()->required()`. We can change the default value, for example, to `false` using `Expect::bool()->default(false)` or shorthand `Expect::bool(false)`. -And what if we wanted to accept `1` and `0` besides booleans? Then we list the allowed values, which we will also normalize to boolean: +And what if we wanted to accept `1` and `0` in addition to booleans? Then we list the values that we also want to normalize to boolean: ```php $schema = Expect::structure([ @@ -87,13 +87,13 @@ $normalized = $processor->process($schema, $data); is_bool($normalized->processRefund); // true ``` -Now you know the basics of how the schema is defined and how the individual elements of the structure behave. We will now show what all the other elements can be used in defining a schema. +Now you know the basics of defining a schema and how the structure items behave. We will now show what other elements you can use when defining a schema. Data Types: type() ------------------ -All standard PHP data types can be listed in the schema: +All standard PHP data types can be specified in the schema: ```php Expect::string($default = null) @@ -104,21 +104,21 @@ Expect::null() Expect::array($default = []) ``` -And then all types [supported by the Validators |utils:validators#Expected Types] via `Expect::type('scalar')` or abbreviated `Expect::scalar()`. Also class or interface names are accepted, e.g. `Expect::type('AddressEntity')`. +And also all types [supported by the Validators class |utils:validators#Expected Types], for example `Expect::type('scalar')` or shorthand `Expect::scalar()`. Also class or interface names, e.g., `Expect::type('AddressEntity')`. -You can also use union notation: +Union syntax can also be used: ```php Expect::type('bool|string|array') ``` -The default value is always `null` except for `array` and `list`, where it is an empty array. (A list is an array indexed in ascending order of numeric keys from zero, that is, a non-associative array). +The default value is always `null` with the exception of `array` and `list`, where it is an empty array. (A list is an array indexed by a sequence of numeric keys starting from zero, i.e. a non-associative array). Array of Values: arrayOf() listOf() ----------------------------------- -The array is too general structure, it is more useful to specify exactly what elements it can contain. For example, an array whose elements can only be strings: +An array represents a too general structure; it's more useful to specify precisely which elements it may contain. For example, an array whose elements can only be strings: ```php $schema = Expect::arrayOf('string'); @@ -128,24 +128,24 @@ $processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK $processor->process($schema, ['key' => 123]); // ERROR: 123 is not a string ``` -The second parameter can be used to specify keys (since version 1.2): +The second parameter can specify keys (since version 1.2): ```php $schema = Expect::arrayOf('string', 'int'); $processor->process($schema, ['hello', 'world']); // OK -$processor->process($schema, ['a' => 'hello']); // ERROR: 'a' is not int +$processor->process($schema, ['a' => 'hello']); // ERROR: 'a' is not an int ``` -The list is an indexed array: +A list is an indexed array: ```php $schema = Expect::listOf('string'); $processor->process($schema, ['a', 'b']); // OK $processor->process($schema, ['a', 123]); // ERROR: 123 is not a string -$processor->process($schema, ['key' => 'a']); // ERROR: is not a list -$processor->process($schema, [1 => 'a', 0 => 'b']); // ERROR: is not a list +$processor->process($schema, ['key' => 'a']); // ERROR: not a list +$processor->process($schema, [1 => 'a', 0 => 'b']); // ERROR: also not a list ``` The parameter can also be a schema, so we can write: @@ -154,13 +154,13 @@ The parameter can also be a schema, so we can write: Expect::arrayOf(Expect::bool()) ``` -The default value is an empty array. If you specify default value, it will be merged with the passed data. This can be disabled using `mergeDefaults(false)` (since version 1.1). +The default value is an empty array. If you specify a default value, it will be merged with the passed data. This can be disabled using `mergeDefaults(false)` (since version 1.1). Enumeration: anyOf() -------------------- -`anyOf()` is a set of values ​​or schemas that a value can be. Here's how to write an array of elements that can be either `'a'`, `true`, or `null`: +`anyOf()` represents a set of values or schemas that a value can take. Here's how to write an array of elements that can be either `'a'`, `true`, or `null`: ```php $schema = Expect::listOf( @@ -171,7 +171,7 @@ $processor->process($schema, ['a', true, null, 'a']); // OK $processor->process($schema, ['a', false]); // ERROR: false does not belong there ``` -The enumeration elements can also be schemas: +The elements of the enumeration can also be schemas: ```php $schema = Expect::listOf( @@ -182,9 +182,9 @@ $processor->process($schema, ['foo', true, null, 'bar']); // OK $processor->process($schema, [123]); // ERROR ``` -The `anyOf()` method accepts variants as individual parameters, not as array. To pass it an array of values, use the unpacking operator `anyOf(...$variants)`. +The `anyOf()` method accepts variants as separate parameters, not as an array. To pass it an array of values, use the unpack operator `anyOf(...$variants)`. -The default value is `null`. Use the `firstIsDefault()` method to make the first element the default: +The default value is `null`. Use the `firstIsDefault()` method to make the first item the default: ```php // default is 'hello' @@ -195,16 +195,16 @@ Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault(); Structures ---------- -Structures are objects with defined keys. Each of these key => value pairs is referred to as a "property": +Structures are objects with defined keys. Each key-value pair is referred to as a "property". -Structures accept arrays and objects and return objects `stdClass` (unless you change it with `castTo('array')`, etc.). +Structures accept arrays and objects and return `stdClass` objects. By default, all properties are optional and have a default value of `null`. You can define mandatory properties using `required()`: ```php $schema = Expect::structure([ 'required' => Expect::string()->required(), - 'optional' => Expect::string(), // the default value is null + 'optional' => Expect::string(), // default value is null ]); $processor->process($schema, ['optional' => '']); @@ -214,7 +214,7 @@ $processor->process($schema, ['required' => 'foo']); // OK, returns {'required' => 'foo', 'optional' => null} ``` -If you do not want to output properties with only a default value, use `skipDefaults()`: +If you do not want properties with default value in the output, use `skipDefaults()`: ```php $schema = Expect::structure([ @@ -226,7 +226,7 @@ $processor->process($schema, ['required' => 'foo']); // OK, returns {'required' => 'foo'} ``` -Although `null` is the default value of the `optional` property, it is not allowed in the input data (the value must be a string). Properties accepting `null` are defined using `nullable()`: +Although `null` is the default value for the `optional` property, it is not allowed in input data (the value must be a string). Properties accepting `null` are defined using `nullable()`: ```php $schema = Expect::structure([ @@ -241,7 +241,9 @@ $processor->process($schema, ['nullable' => null]); // OK, returns {'optional' => null, 'nullable' => null} ``` -By default, there can be no extra items in the input data: +The array of all structure properties is returned by the `getShape()` method. + +By default, no additional items can be present in the input data: ```php $schema = Expect::structure([ @@ -252,7 +254,7 @@ $processor->process($schema, ['additional' => 1]); // ERROR: Unexpected item 'additional' ``` -Which we can change with `otherItems()`. As a parameter, we will specify the schema for each extra element: +This can be changed using `otherItems()`. As a parameter, pass the schema to validate each extra item: ```php $schema = Expect::structure([ @@ -263,11 +265,49 @@ $processor->process($schema, ['additional' => 1]); // OK $processor->process($schema, ['additional' => true]); // ERROR ``` +You can create a new structure by extending another using `extend()`: + +```php +$dog = Expect::structure([ + 'name' => Expect::string(), + 'age' => Expect::int(), +]); -Deprecations ------------- +$dogWithBreed = $dog->extend([ + 'breed' => Expect::string(), +]); +``` -You can deprecate property using the `deprecated([string $message])` method. Deprecation notices are returned by `$processor->getWarnings()`: + +Array .{data-version:1.3.2} +--------------------------- + +An array with defined keys. Everything that applies to [#structures] applies to it. + +```php +$schema = Expect::array([ + 'required' => Expect::string()->required(), + 'optional' => Expect::string(), // default value is null +]); +``` + +You can also define an indexed array, known as tuple: + +```php +$schema = Expect::array([ + Expect::int(), + Expect::string(), + Expect::bool(), +]); + +$processor->process($schema, [1, 'hello', true]); // OK +``` + + +Deprecated Properties +--------------------- + +You can mark a property as deprecated using the `deprecated([string $message])` method. Information about deprecation is returned using `$processor->getWarnings()`: ```php $schema = Expect::structure([ @@ -282,28 +322,28 @@ $processor->getWarnings(); // ["The item 'old' is deprecated"] Ranges: min() max() ------------------- -Use `min()` and `max()` to limit the number of elements for arrays: +Use `min()` and `max()` to limit the count for arrays: ```php // array, at least 10 items, maximum 20 items Expect::array()->min(10)->max(20); ``` -For strings, limit their length: +For strings, limit its length: ```php // string, at least 10 characters long, maximum 20 characters Expect::string()->min(10)->max(20); ``` -For numbers, limit their value: +For numbers, limit its value: ```php // integer, between 10 and 20 inclusive Expect::int()->min(10)->max(20); ``` -Of course, it is possible to mention only `min()`, or only `max()`: +Of course, it is possible to specify just `min()` or just `max()`: ```php // string, maximum 20 characters @@ -314,10 +354,10 @@ Expect::string()->max(20); Regular Expressions: pattern() ------------------------------ -Using `pattern()`, you can specify a regular expression which the **whole** input string must match (i.e. as if it were wrapped in characters `^` a `$`): +Using `pattern()`, you can specify a regular expression that the **entire** input string must match (i.e. as if it were wrapped in `^` and `$` characters): ```php -// just 9 digits +// exactly 9 digits Expect::string()->pattern('\d{9}'); ``` @@ -325,7 +365,7 @@ Expect::string()->pattern('\d{9}'); Custom Assertions: assert() --------------------------- -You can add any other restrictions using `assert(callable $fn)`. +You can add any other constraints using `assert(callable $fn)`. ```php $countIsEven = fn($v) => count($v) % 2 === 0; @@ -334,16 +374,16 @@ $schema = Expect::arrayOf('string') ->assert($countIsEven); // the count must be even $processor->process($schema, ['a', 'b']); // OK -$processor->process($schema, ['a', 'b', 'c']); // ERROR: 3 is not even +$processor->process($schema, ['a', 'b', 'c']); // ERROR: 3 is not an even count ``` Or ```php -Expect::string()->assert('is_file'); // the file must exist +Expect::string()->assert('is_file'); // file must exist ``` -You can add your own description for each assertions. It will be part of the error message. +You can add a custom description to each assertion. It will be part of the error message. ```php $schema = Expect::arrayOf('string') @@ -353,85 +393,96 @@ $processor->process($schema, ['a', 'b', 'c']); // Failed assertion "Even items in array" for item with value array. ``` -The method can be called repeatedly to add more assertions. +The method can be called repeatedly to add multiple constraints. It can be interleaved with calls to `transform()` and `castTo()`. -Mapping to Objects: from() --------------------------- +Transformation: transform() .{data-version:1.2.5} +------------------------------------------------- -You can generate structure schema from the class. Example: +Successfully validated data can be modified using a custom function: ```php -class Config -{ - public string $name; - public string|null $password; - public bool $admin = false; -} - -$schema = Expect::from(new Config); +// convert to uppercase: +Expect::string()->transform(fn(string $s) => strtoupper($s)); +``` -$data = [ - 'name' => 'jeff', -]; +The method can be called repeatedly to add multiple transformations. It can be interleaved with calls to `assert()` and `castTo()`. The operations are performed in the order in which they are declared: -$normalized = $processor->process($schema, $data); -// $normalized instanceof Config -// $normalized = {'name' => 'jeff', 'password' => null, 'admin' => false} +```php +Expect::type('string|int') + ->castTo('string') + ->assert('ctype_lower', 'All characters must be lowercased') + ->transform(fn(string $s) => strtoupper($s)); // convert to uppercase ``` -If you are using PHP 7.4 or higher, you can use native types: +The `transform()` method can simultaneously transform and validate the value. This is often simpler and less code duplication than chaining `transform()` and `assert()`. For this purpose, the function receives a [Context |api:Nette\Schema\Context] object with an `addError()` method, which can be used to add information about validation problems: ```php -class Config -{ - public string $name; - public ?string $password; - public bool $admin = false; -} +Expect::string() + ->transform(function (string $s, Nette\Schema\Context $context) { + if (!ctype_lower($s)) { + $context->addError('All characters must be lowercased', 'my.case.error'); + return null; + } -$schema = Expect::from(new Config); + return strtoupper($s); + }); ``` -Anonymous classes are also supported: + +Casting: castTo() +----------------- + +Successfully validated data can be cast: ```php -$schema = Expect::from(new class { - public string $name; - public ?string $password; - public bool $admin = false; -}); +Expect::scalar()->castTo('string'); ``` -Because the information obtained from the class definition may not be sufficient, you can add a custom schema for the elements with the second parameter: +In addition to native PHP types, you can also cast to classes. It distinguishes between a simple class without a constructor and a class with a constructor. If the class has no constructor, an instance is created, and all structure elements are written to the properties: ```php -$schema = Expect::from(new Config, [ - 'name' => Expect::string()->pattern('\w:.*'), -]); -``` +class Info +{ + public bool $processRefund; + public int $refundAmount; +} +Expect::structure([ + 'processRefund' => Expect::bool(), + 'refundAmount' => Expect::int(), +])->castTo(Info::class); -Casting: castTo() ------------------ +// creates '$obj = new Info' and writes to $obj->processRefund and $obj->refundAmount +``` -Successfully validated data can be cast: +If the class has a constructor, the structure elements are passed as named arguments to the constructor: ```php -Expect::scalar()->castTo('string'); +class Info +{ + public function __construct( + public bool $processRefund, + public int $refundAmount, + ) { + } +} + +// creates $obj = new Info(processRefund: ..., refundAmount: ...) ``` -In addition to native PHP types, you can also cast to classes: +Casting combined with a scalar parameter creates an object and passes the value as the single argument to the constructor: ```php -Expect::scalar()->castTo('AddressEntity'); +Expect::string()->castTo(DateTime::class); +// creates new DateTime(...) ``` Normalization: before() ----------------------- -Prior to the validation itself, the data can be normalized using the method `before()`. As an example, let's have an element that must be an array of strings (eg `['a', 'b', 'c']`), but receives input in the form of a string `a b c`: +Before the validation itself, the data can be normalized using the `before()` method. As an example, let's take an element that must be an array of strings (e.g., `['a', 'b', 'c']`), but accepts input in the form of string `a b c`: ```php $explode = fn($v) => explode(' ', $v); @@ -440,7 +491,48 @@ $schema = Expect::arrayOf('string') ->before($explode); $normalized = $processor->process($schema, 'a b c'); -// OK, returns ['a', 'b', 'c'] +// OK and returns ['a', 'b', 'c'] +``` + + +Mapping to Objects: from() +-------------------------- + +You can have the structure schema generated from a class. Example: + +```php +class Config +{ + public string $name; + public string|null $password; + public bool $admin = false; +} + +$schema = Expect::from(new Config); + +$data = [ + 'name' => 'Frank', +]; + +$normalized = $processor->process($schema, $data); +// $normalized instanceof Config +// $normalized = {'name' => 'Frank', 'password' => null, 'admin' => false} +``` + +Anonymous classes are also supported: + +```php +$schema = Expect::from(new class { + public string $name; + public ?string $password; + public bool $admin = false; +}); ``` -{{leftbar: nette:@menu-topics}} +Because the information obtained from the class definition may not be sufficient, you can supplement the elements with your own schema using the second parameter: + +```php +$schema = Expect::from(new Config, [ + 'name' => Expect::string()->pattern('\w:.*'), +]); +``` diff --git a/schema/en/@meta.texy b/schema/en/@meta.texy new file mode 100644 index 0000000000..91205786e5 --- /dev/null +++ b/schema/en/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Documentation}} +{{leftbar: nette:@menu-topics}} diff --git a/schema/es/@home.texy b/schema/es/@home.texy index b74af864a3..ef9eb190af 100644 --- a/schema/es/@home.texy +++ b/schema/es/@home.texy @@ -1,8 +1,8 @@ -Schema: Validación de datos -*************************** +Nette Schema +************ .[perex] -Una biblioteca práctica para la validación y normalización de estructuras de datos con respecto a un esquema dado con una API inteligente y fácil de entender. +Una librería práctica para validar y normalizar estructuras de datos contra un esquema dado con una API inteligente y comprensible. Instalación: @@ -11,12 +11,12 @@ composer require nette/schema ``` -Uso básico .[#toc-basic-usage] ------------------------------- +Uso básico +---------- -En la variable `$schema` tenemos un esquema de validación (qué significa esto exactamente y cómo crearlo lo diremos más adelante) y en la variable `$data` tenemos una estructura de datos que queremos validar y normalizar. Pueden ser, por ejemplo, datos enviados por el usuario a través de una API, fichero de configuración, etc. +En la variable `$schema` tenemos el esquema de validación (qué significa exactamente y cómo crear dicho esquema lo diremos en breve) y en la variable `$data` la estructura de datos que queremos validar y normalizar. Pueden ser, por ejemplo, datos enviados por el usuario a través de una interfaz API, un archivo de configuración, etc. -De esta tarea se encarga la clase [api:Nette\Schema\Processor], que procesa la entrada y devuelve los datos normalizados o lanza una excepción [api:Nette\Schema\ValidationException] en caso de error. +La tarea la realiza la clase [api:Nette\Schema\Processor], que procesa la entrada y devuelve los datos normalizados o lanza una excepción [api:Nette\Schema\ValidationException] en caso de error. ```php $processor = new Nette\Schema\Processor; @@ -24,17 +24,17 @@ $processor = new Nette\Schema\Processor; try { $normalized = $processor->process($schema, $data); } catch (Nette\Schema\ValidationException $e) { - echo 'Data is invalid: ' . $e->getMessage(); + echo 'Los datos no son válidos: ' . $e->getMessage(); } ``` -El método `$e->getMessages()` devuelve un array con todas las cadenas de mensajes y `$e->getMessageObjects()` devuelve todos los mensajes como objetos "Nette\Schema\Message":https://api.nette.org/schema/master/Nette/Schema/Message.html. +El método `$e->getMessages()` devuelve un array de todos los mensajes como cadenas y `$e->getMessageObjects()` devuelve todos los mensajes como objetos [api:Nette\Schema\Message]. -Definición del esquema .[#toc-defining-schema] ----------------------------------------------- +Definición del esquema +---------------------- -Y ahora vamos a crear un esquema. La clase [api:Nette\Schema\Expect] se utiliza para definirlo, en realidad definimos las expectativas de cómo deben ser los datos. Digamos que los datos de entrada deben ser una estructura (por ejemplo un array) que contenga elementos `processRefund` de tipo bool y `refundAmount` de tipo int. +Y ahora creemos el esquema. Para definirlo sirve la clase [api:Nette\Schema\Expect], definimos básicamente las expectativas de cómo deben verse los datos. Digamos que los datos de entrada deben formar una estructura (por ejemplo, un array) que contenga los elementos `processRefund` de tipo `bool` y `refundAmount` de tipo `int`. ```php use Nette\Schema\Expect; @@ -45,9 +45,9 @@ $schema = Expect::structure([ ]); ``` -Creemos que la definición del esquema parece clara, incluso si la ves por primera vez. +Creemos que la definición del esquema parece comprensible, incluso si la ve por primera vez. -Enviemos los siguientes datos para su validación: +Enviemos los siguientes datos para validación: ```php $data = [ @@ -55,27 +55,27 @@ $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // OK, pasa +$normalized = $processor->process($schema, $data); // OK, pasa la validación ``` -La salida, es decir, el valor `$normalized`, es el objeto `stdClass`. Si queremos que la salida sea un array, añadimos un cast al esquema `Expect::structure([...])->castTo('array')`. +La salida, es decir, el valor `$normalized`, es un objeto `stdClass`. Si quisiéramos que la salida fuera un array, complementaríamos el esquema con la conversión de tipos `Expect::structure([...])->castTo('array')`. -Todos los elementos de la estructura son opcionales y tienen un valor por defecto `null`. Ejemplo: +Todos los elementos de la estructura son opcionales y tienen un valor predeterminado de `null`. Ejemplo: ```php $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // OK, pasa +$normalized = $processor->process($schema, $data); // OK, pasa la validación // $normalized = {'processRefund' => null, 'refundAmount' => 17} ``` -El hecho de que el valor por defecto sea `null` no significa que se aceptaría en los datos de entrada `'processRefund' => null`. No, la entrada debe ser booleana, es decir, sólo `true` o `false`. Tendríamos que permitir explícitamente `null` a través de `Expect::bool()->nullable()`. +El hecho de que el valor predeterminado sea `null` no significa que se acepte en los datos de entrada `'processRefund' => null`. No, la entrada debe ser un booleano, es decir, solo `true` o `false`. Tendríamos que permitir `null` explícitamente usando `Expect::bool()->nullable()`. -Un elemento puede hacerse obligatorio mediante `Expect::bool()->required()`. Cambiamos el valor por defecto a `false` utilizando `Expect::bool()->default(false)` o en breve utilizando `Expect::bool(false)`. +Un elemento se puede hacer obligatorio usando `Expect::bool()->required()`. Cambiamos el valor predeterminado, por ejemplo, a `false` usando `Expect::bool()->default(false)` o de forma abreviada `Expect::bool(false)`. -¿Y si quisiéramos aceptar `1` and `0` además de booleanos? Entonces enumeramos los valores permitidos, que también normalizaremos a booleanos: +¿Y si quisiéramos aceptar `1` y `0` además de booleanos? Entonces especificamos una enumeración de valores, que además dejamos normalizar a booleano: ```php $schema = Expect::structure([ @@ -87,13 +87,13 @@ $normalized = $processor->process($schema, $data); is_bool($normalized->processRefund); // true ``` -Ahora ya sabes lo básico de cómo se define el esquema y cómo se comportan los elementos individuales de la estructura. Ahora mostraremos qué otros elementos se pueden utilizar para definir un esquema. +Ahora ya conoce los conceptos básicos de cómo se define un esquema y cómo se comportan los elementos individuales de la estructura. Ahora mostraremos qué otros elementos se pueden usar al definir un esquema. -Tipos de datos: type() .[#toc-data-types-type] ----------------------------------------------- +Tipos de datos: type() +---------------------- -Todos los tipos de datos estándar de PHP pueden ser listados en el esquema: +En el esquema se pueden especificar todos los tipos de datos estándar de PHP: ```php Expect::string($default = null) @@ -104,21 +104,21 @@ Expect::null() Expect::array($default = []) ``` -Y luego todos los tipos [soportados por los Validadores |utils:validators#Expected Types] a través de `Expect::type('scalar')` o abreviado `Expect::scalar()`. También se aceptan nombres de clases o interfaces, por ejemplo `Expect::type('AddressEntity')`. +Y además todos los tipos [soportados por la clase Validators |utils:validators#Tipos esperados], por ejemplo `Expect::type('scalar')` o abreviado `Expect::scalar()`. También nombres de clases o interfaces, por ejemplo `Expect::type('AddressEntity')`. -También se puede utilizar la notación de unión: +También se puede usar la notación de unión: ```php Expect::type('bool|string|array') ``` -El valor por defecto es siempre `null` excepto para `array` y `list`, donde es un array vacío. (Una lista es una matriz indexada en orden ascendente de claves numéricas a partir de cero, es decir, una matriz no asociativa). +El valor predeterminado siempre es `null` excepto para `array` y `list`, donde es un array vacío. (Una lista es un array indexado según una serie ascendente de claves numéricas desde cero, es decir, un array no asociativo). -Matriz de valores: arrayOf() listOf() .[#toc-array-of-values-arrayof-listof] ----------------------------------------------------------------------------- +Arrays de valores: arrayOf() listOf() +------------------------------------- -El array es una estructura demasiado general, es más útil especificar exactamente qué elementos puede contener. Por ejemplo, un array cuyos elementos sólo pueden ser cadenas: +Un array representa una estructura demasiado general, es más útil especificar exactamente qué elementos puede contener. Por ejemplo, un array cuyos elementos solo pueden ser cadenas: ```php $schema = Expect::arrayOf('string'); @@ -128,16 +128,16 @@ $processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK $processor->process($schema, ['key' => 123]); // ERROR: 123 no es una cadena ``` -El segundo parámetro puede utilizarse para especificar claves (desde la versión 1.2): +Con el segundo parámetro se pueden especificar las claves (desde la versión 1.2): ```php $schema = Expect::arrayOf('string', 'int'); $processor->process($schema, ['hello', 'world']); // OK -$processor->process($schema, ['a' => 'hello']); // ERROR: 'a' no es int +$processor->process($schema, ['a' => 'hello']); // ERROR: 'a' no es un int ``` -La lista es una matriz indexada: +Una lista es un array indexado: ```php $schema = Expect::listOf('string'); @@ -145,7 +145,7 @@ $schema = Expect::listOf('string'); $processor->process($schema, ['a', 'b']); // OK $processor->process($schema, ['a', 123]); // ERROR: 123 no es una cadena $processor->process($schema, ['key' => 'a']); // ERROR: no es una lista -$processor->process($schema, [1 => 'a', 0 => 'b']); // ERROR: no es una lista +$processor->process($schema, [1 => 'a', 0 => 'b']); // ERROR: tampoco es una lista ``` El parámetro también puede ser un esquema, por lo que podemos escribir: @@ -154,13 +154,13 @@ El parámetro también puede ser un esquema, por lo que podemos escribir: Expect::arrayOf(Expect::bool()) ``` -El valor por defecto es un array vacío. Si especifica el valor por defecto, se fusionará con los datos pasados. Esto puede desactivarse utilizando `mergeDefaults(false)` (desde la versión 1.1). +El valor predeterminado es un array vacío. Si especifica un valor predeterminado, se fusionará con los datos pasados. Esto se puede desactivar usando `mergeDefaults(false)` (desde la versión 1.1). -Enumeración: anyOf() .[#toc-enumeration-anyof] ----------------------------------------------- +Enumeración: anyOf() +-------------------- -`anyOf()` es un conjunto de valores o esquemas que un valor puede ser. A continuación se muestra cómo escribir una matriz de elementos que pueden ser `'a'`, `true`, o `null`: +`anyOf()` representa una enumeración de valores o esquemas que puede tomar un valor. Así escribimos un array de elementos que pueden ser `'a'`, `true` o `null`: ```php $schema = Expect::listOf( @@ -168,7 +168,7 @@ $schema = Expect::listOf( ); $processor->process($schema, ['a', true, null, 'a']); // OK -$processor->process($schema, ['a', false]); // ERROR: false no debe estar ahí +$processor->process($schema, ['a', false]); // ERROR: false no pertenece allí ``` Los elementos de la enumeración también pueden ser esquemas: @@ -182,39 +182,39 @@ $processor->process($schema, ['foo', true, null, 'bar']); // OK $processor->process($schema, [123]); // ERROR ``` -El método `anyOf()` acepta variantes como parámetros individuales, no como array. Para pasarle un array de valores, utilice el operador de desempaquetado `anyOf(...$variants)`. +El método `anyOf()` acepta variantes como parámetros individuales, no un array. Si desea pasarle un array de valores, use el operador de desempaquetado `anyOf(...$variants)`. -El valor por defecto es `null`. Utilice el método `firstIsDefault()` para que el primer elemento sea el valor predeterminado: +El valor predeterminado es `null`. Con el método `firstIsDefault()` hacemos que el primer elemento sea el predeterminado: ```php -// default is 'hello' +// el predeterminado es 'hello' Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault(); ``` -Estructuras .[#toc-structures] ------------------------------- +Estructuras +----------- -Las estructuras son objetos con claves definidas. Cada uno de estos pares clave => valor se denomina "propiedad": +Las estructuras son objetos con claves definidas. Cada uno de los pares clave => valor se denomina „propiedad“: -Las estructuras aceptan arrays y objetos y devuelven objetos `stdClass` (a menos que lo cambies con `castTo('array')`, etc.). +Las estructuras aceptan arrays y objetos y devuelven objetos `stdClass`. -Por defecto, todas las propiedades son opcionales y tienen un valor por defecto de `null`. Puede definir propiedades obligatorias utilizando `required()`: +De forma predeterminada, todas las propiedades son opcionales y tienen un valor predeterminado de `null`. Puede definir propiedades obligatorias usando `required()`: ```php $schema = Expect::structure([ 'required' => Expect::string()->required(), - 'optional' => Expect::string(), // el valor por defecto es null + 'optional' => Expect::string(), // el valor predeterminado es null ]); $processor->process($schema, ['optional' => '']); -// ERROR: falta la opción 'required +// ERROR: la opción 'required' falta $processor->process($schema, ['required' => 'foo']); // OK, devuelve {'required' => 'foo', 'optional' => null} ``` -Si no desea mostrar propiedades con un valor predeterminado, utilice `skipDefaults()`: +Si no desea tener propiedades con el valor predeterminado en la salida, use `skipDefaults()`: ```php $schema = Expect::structure([ @@ -226,7 +226,7 @@ $processor->process($schema, ['required' => 'foo']); // OK, devuelve {'required' => 'foo'} ``` -Aunque `null` es el valor por defecto de la propiedad `optional`, no está permitido en los datos de entrada (el valor debe ser una cadena). Las propiedades que aceptan `null` se definen utilizando `nullable()`: +Aunque `null` es el valor predeterminado de la propiedad `optional`, no está permitido en los datos de entrada (el valor debe ser una cadena). Las propiedades que aceptan `null` se definen usando `nullable()`: ```php $schema = Expect::structure([ @@ -235,13 +235,15 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['optional' => null]); -// ERROR: 'optional' espera ser string, dado null. +// ERROR: 'optional' espera ser una cadena, se dio null. $processor->process($schema, ['nullable' => null]); // OK, devuelve {'optional' => null, 'nullable' => null} ``` -Por defecto, no puede haber elementos adicionales en los datos de entrada: +El método `getShape()` devuelve un array de todas las propiedades de la estructura. + +De forma predeterminada, no puede haber elementos adicionales en los datos de entrada: ```php $schema = Expect::structure([ @@ -249,10 +251,10 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['additional' => 1]); -// ERROR: Unexpected item 'additional' +// ERROR: Elemento inesperado 'additional' ``` -Lo cual podemos cambiar con `otherItems()`. Como parámetro, especificaremos el esquema para cada elemento extra: +Lo cual podemos cambiar usando `otherItems()`. Como parámetro, especificamos un esquema según el cual se validarán los elementos adicionales: ```php $schema = Expect::structure([ @@ -263,175 +265,224 @@ $processor->process($schema, ['additional' => 1]); // OK $processor->process($schema, ['additional' => true]); // ERROR ``` +Puede crear una nueva estructura derivándola de otra usando `extend()`: + +```php +$dog = Expect::structure([ + 'name' => Expect::string(), + 'age' => Expect::int(), +]); + +$dogWithBreed = $dog->extend([ + 'breed' => Expect::string(), +]); +``` -Depreciaciones .[#toc-deprecations] ------------------------------------ -Puedes eliminar una propiedad utilizando el método `deprecated([string $message])` método. Los avisos de desaprobación son devueltos por `$processor->getWarnings()`: +Arrays .{data-version:1.3.2} +---------------------------- + +Arrays con claves definidas. Todo lo que se aplica a las [#estructuras] también se aplica a ellos. + +```php +$schema = Expect::array([ + 'required' => Expect::string()->required(), + 'optional' => Expect::string(), // el valor predeterminado es null +]); +``` + +También se puede definir un array indexado, conocido como tupla: + +```php +$schema = Expect::array([ + Expect::int(), + Expect::string(), + Expect::bool(), +]); + +$processor->process($schema, [1, 'hello', true]); // OK +``` + + +Propiedades obsoletas +--------------------- + +Puede marcar una propiedad como obsoleta usando el método `deprecated([string $message])`. La información sobre el fin del soporte se devuelve mediante `$processor->getWarnings()`: ```php $schema = Expect::structure([ - 'old' => Expect::int()->deprecated('El elemento %ruta% está obsoleto'), + 'old' => Expect::int()->deprecated('El elemento %path% está obsoleto'), ]); $processor->process($schema, ['old' => 1]); // OK -$processor->getWarnings(); // ["El elemento 'old' es obsoleto"] +$processor->getWarnings(); // ["El elemento 'old' está obsoleto"] ``` -Rangos: min() max() .[#toc-ranges-min-max] ------------------------------------------- +Rangos: min() max() +------------------- -Utilice `min()` y `max()` para limitar el número de elementos de las matrices: +Con `min()` y `max()` se puede limitar el número de elementos en los arrays: ```php -// matriz, al menos 10 elementos, máximo 20 elementos +// array, al menos 10 elementos, máximo 20 elementos Expect::array()->min(10)->max(20); ``` -Para cadenas, limita su longitud: +En las cadenas, limitar su longitud: ```php -// cadena, de al menos 10 caracteres, máximo 20 caracteres +// cadena, al menos 10 caracteres de longitud, máximo 20 caracteres Expect::string()->min(10)->max(20); ``` -Para los números, limita su valor: +En los números, limitar su valor: ```php // número entero, entre 10 y 20 inclusive Expect::int()->min(10)->max(20); ``` -Por supuesto, es posible mencionar sólo `min()`, o sólo `max()`: +Por supuesto, es posible especificar solo `min()`, o solo `max()`: ```php -// cadena, máximo 20 caracteres +// cadena máximo 20 caracteres Expect::string()->max(20); ``` -Expresiones regulares: pattern() .[#toc-regular-expressions-pattern] --------------------------------------------------------------------- +Expresiones regulares: pattern() +-------------------------------- -Utilizando `pattern()`, puede especificar una expresión regular con la que debe coincidir **toda** la cadena de entrada (es decir, como si estuviera envuelta en caracteres `^` a `$`): +Con `pattern()` se puede especificar una expresión regular con la que debe coincidir **toda** la cadena de entrada (es decir, como si estuviera envuelta en los caracteres `^` y `$`): ```php -// sólo 9 dígitos +// exactamente 9 números Expect::string()->pattern('\d{9}'); ``` -Aserciones personalizadas: assert() .[#toc-custom-assertions-assert] --------------------------------------------------------------------- +Restricciones personalizadas: assert() +-------------------------------------- -Puede añadir cualquier otra restricción utilizando `assert(callable $fn)`. +Cualquier otra restricción se especifica usando `assert(callable $fn)`. ```php $countIsEven = fn($v) => count($v) % 2 === 0; $schema = Expect::arrayOf('string') - ->assert($countIsEven); // la cuenta debe ser par + ->assert($countIsEven); // el número debe ser par $processor->process($schema, ['a', 'b']); // OK -$processor->process($schema, ['a', 'b', 'c']); // ERROR: 3 no es par +$processor->process($schema, ['a', 'b', 'c']); // ERROR: 3 no es un número par ``` -O: +O ```php Expect::string()->assert('is_file'); // el archivo debe existir ``` -Puede añadir su propia descripción para cada aserción. Formará parte del mensaje de error. +A cada restricción puede agregarle su propia descripción. Esta formará parte del mensaje de error. ```php $schema = Expect::arrayOf('string') ->assert($countIsEven, 'Even items in array'); $processor->process($schema, ['a', 'b', 'c']); -// Fallo en la aserción "Even items in array" para el elemento con valor array. +// Failed assertion "Even items in array" for item with value array. ``` -El método puede ser llamado repetidamente para añadir más aserciones. +El método se puede llamar repetidamente para agregar más restricciones. Se puede intercalar con llamadas a `transform()` y `castTo()`. -Asignación a objetos: from() .[#toc-mapping-to-objects-from] ------------------------------------------------------------- +Transformaciones: transform() .{data-version:1.2.5} +--------------------------------------------------- -Se puede generar esquema de estructura a partir de la clase. Ejemplo: +Los datos validados con éxito se pueden modificar usando una función personalizada: ```php -class Config -{ - public string $name; - public string|null $password; - public bool $admin = false; -} +// conversión a mayúsculas: +Expect::string()->transform(fn(string $s) => strtoupper($s)); +``` -$schema = Expect::from(new Config); +El método se puede llamar repetidamente para agregar más transformaciones. Se puede intercalar con llamadas a `assert()` y `castTo()`. Las operaciones se realizan en el orden en que se declaran: -$data = [ - 'name' => 'jeff', -]; - -$normalized = $processor->process($schema, $data); -// $normalized instanceof Config -// $normalized = {'name' => 'jeff', 'password' => null, 'admin' => false} +```php +Expect::type('string|int') + ->castTo('string') + ->assert('ctype_lower', 'Todos los caracteres deben estar en minúsculas') + ->transform(fn(string $s) => strtoupper($s)); // conversión a mayúsculas ``` -Si está usando PHP 7.4 o superior, puede usar tipos nativos: +El método `transform()` puede transformar y validar simultáneamente el valor. Esto suele ser más simple y menos duplicado que encadenar `transform()` y `assert()`. Para este propósito, la función recibe un objeto [Context |api:Nette\Schema\Context] con el método `addError()`, que se puede usar para agregar información sobre problemas de validación: ```php -class Config -{ - public string $name; - public ?string $password; - public bool $admin = false; -} +Expect::string() + ->transform(function (string $s, Nette\Schema\Context $context) { + if (!ctype_lower($s)) { + $context->addError('Todos los caracteres deben estar en minúsculas', 'my.case.error'); + return null; + } -$schema = Expect::from(new Config); + return strtoupper($s); + }); ``` -Las clases anónimas también están soportadas: + +Conversión de tipos: castTo() +----------------------------- + +Los datos validados con éxito se pueden convertir de tipo: ```php -$schema = Expect::from(new class { - public string $name; - public ?string $password; - public bool $admin = false; -}); +Expect::scalar()->castTo('string'); ``` -Dado que la información obtenida de la definición de la clase puede no ser suficiente, puede añadir un esquema personalizado para los elementos con el segundo parámetro: +Además de los tipos nativos de PHP, también se puede convertir a clases. Aquí se distingue si se trata de una clase simple sin constructor o una clase con constructor. Si la clase no tiene constructor, se crea su instancia y todos los elementos de la estructura se escriben en las propiedades: ```php -$schema = Expect::from(new Config, [ - 'name' => Expect::string()->pattern('\w:.*'), -]); -``` +class Info +{ + public bool $processRefund; + public int $refundAmount; +} +Expect::structure([ + 'processRefund' => Expect::bool(), + 'refundAmount' => Expect::int(), +])->castTo(Info::class); -Casting: castTo() .[#toc-casting-castto] ----------------------------------------- +// crea '$obj = new Info' y escribe en $obj->processRefund y $obj->refundAmount +``` -Los datos validados correctamente pueden ser emitidos: +Si la clase tiene constructor, los elementos de la estructura se pasan como parámetros con nombre al constructor: ```php -Expect::scalar()->castTo('string'); +class Info +{ + public function __construct( + public bool $processRefund, + public int $refundAmount, + ) { + } +} + +// crea $obj = new Info(processRefund: ..., refundAmount: ...) ``` -Además de los tipos nativos de PHP, también se pueden convertir a clases: +La conversión de tipos en combinación con un parámetro escalar crea un objeto y pasa el valor como único parámetro al constructor: ```php -Expect::scalar()->castTo('AddressEntity'); +Expect::string()->castTo(DateTime::class); +// crea new DateTime(...) ``` -Normalización: before() .[#toc-normalization-before] ----------------------------------------------------- +Normalización: before() +----------------------- -Antes de la validación propiamente dicha, los datos pueden normalizarse utilizando el método `before()`. Como ejemplo, tengamos un elemento que debe ser un array de cadenas (ej. `['a', 'b', 'c']`), pero que recibe la entrada en forma de cadena `a b c`: +Antes de la validación misma, los datos se pueden normalizar usando el método `before()`. Como ejemplo, mencionemos un elemento que debe ser un array de cadenas (por ejemplo, `['a', 'b', 'c']`), pero acepta la entrada en forma de cadena `a b c`: ```php $explode = fn($v) => explode(' ', $v); @@ -440,7 +491,48 @@ $schema = Expect::arrayOf('string') ->before($explode); $normalized = $processor->process($schema, 'a b c'); -// OK, devuelve ['a', 'b', 'c'] +// OK y devuelve ['a', 'b', 'c'] ``` -{{leftbar: nette:@menu-topics}} + +Mapeo a objetos: from() +----------------------- + +Podemos hacer que el esquema de la estructura se genere a partir de una clase. Ejemplo: + +```php +class Config +{ + public string $name; + public string|null $password; + public bool $admin = false; +} + +$schema = Expect::from(new Config); + +$data = [ + 'name' => 'franta', +]; + +$normalized = $processor->process($schema, $data); +// $normalized instanceof Config +// $normalized = {'name' => 'franta', 'password' => null, 'admin' => false} +``` + +También se admiten clases anónimas: + +```php +$schema = Expect::from(new class { + public string $name; + public ?string $password; + public bool $admin = false; +}); +``` + +Dado que la información obtenida de la definición de la clase puede no ser suficiente, puede complementar los elementos con su propio esquema usando el segundo parámetro: + +```php +$schema = Expect::from(new Config, [ + 'name' => Expect::string()->pattern('\w:.*'), +]); +``` diff --git a/schema/es/@meta.texy b/schema/es/@meta.texy new file mode 100644 index 0000000000..25d506cde9 --- /dev/null +++ b/schema/es/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Documentación}} +{{leftbar: nette:@menu-topics}} diff --git a/schema/fr/@home.texy b/schema/fr/@home.texy index 06c25f68ef..17d4a7ea80 100644 --- a/schema/fr/@home.texy +++ b/schema/fr/@home.texy @@ -1,8 +1,8 @@ -Schema : Validation des données -******************************* +Nette Schema +************ .[perex] -Une bibliothèque pratique pour la validation et la normalisation des structures de données par rapport à un schéma donné, avec une API intelligente et facile à comprendre. +Bibliothèque pratique pour la validation et la normalisation des structures de données par rapport à un schéma donné avec une API intelligente et compréhensible. Installation : @@ -11,12 +11,12 @@ composer require nette/schema ``` -Utilisation de base .[#toc-basic-usage] ---------------------------------------- +Utilisation de base +------------------- -Dans la variable `$schema` nous avons un schéma de validation (ce que cela signifie exactement et comment le créer nous le dirons plus tard) et dans la variable `$data` nous avons une structure de données que nous voulons valider et normaliser. Cela peut être, par exemple, des données envoyées par l'utilisateur via une API, un fichier de configuration, etc. +Dans la variable `$schema`, nous avons le schéma de validation (ce que cela signifie exactement et comment créer un tel schéma, nous le verrons dans un instant) et dans la variable `$data`, la structure de données que nous voulons valider et normaliser. Il peut s'agir, par exemple, de données envoyées par l'utilisateur via une API, d'un fichier de configuration, etc. -La tâche est gérée par la classe [api:Nette\Schema\Processor], qui traite l'entrée et renvoie les données normalisées ou lève une exception [api:Nette\Schema\ValidationException] en cas d'erreur. +La tâche est assurée par la classe [api:Nette\Schema\Processor], qui traite l'entrée et retourne soit les données normalisées, soit lève une exception [api:Nette\Schema\ValidationException] en cas d'erreur. ```php $processor = new Nette\Schema\Processor; @@ -24,17 +24,17 @@ $processor = new Nette\Schema\Processor; try { $normalized = $processor->process($schema, $data); } catch (Nette\Schema\ValidationException $e) { - echo 'Data is invalid: ' . $e->getMessage(); + echo 'Les données ne sont pas valides : ' . $e->getMessage(); } ``` -La méthode `$e->getMessages()` renvoie un tableau de toutes les chaînes de messages et `$e->getMessageObjects()` renvoie tous les messages sous forme d'objets "Nette\Schema\Message":https://api.nette.org/schema/master/Nette/Schema/Message.html. +La méthode `$e->getMessages()` retourne un tableau de tous les messages sous forme de chaînes et `$e->getMessageObjects()` retourne tous les messages sous forme d'objets [Nette\Schema\Message |https://api.nette.org/schema/master/Nette/Schema/Message.html]. -Définition du schéma .[#toc-defining-schema] --------------------------------------------- +Définition du schéma +-------------------- -Et maintenant, créons un schéma. La classe [api:Nette\Schema\Expect] est utilisée pour le définir, nous définissons en fait les attentes de ce à quoi les données doivent ressembler. Disons que les données d'entrée doivent être une structure (par exemple un tableau) contenant des éléments `processRefund` de type bool et `refundAmount` de type int. +Et maintenant, créons le schéma. La classe [api:Nette\Schema\Expect] sert à le définir, nous définissons en fait les attentes sur l'apparence des données. Disons que les données d'entrée doivent former une structure (par exemple, un tableau) contenant les éléments `processRefund` de type bool et `refundAmount` de type int. ```php use Nette\Schema\Expect; @@ -45,9 +45,9 @@ $schema = Expect::structure([ ]); ``` -Nous pensons que la définition du schéma semble claire, même si vous la voyez pour la toute première fois. +Nous pensons que la définition du schéma semble compréhensible, même si vous la voyez pour la toute première fois. -Envoyons les données suivantes pour validation : +Envoyons les données suivantes à la validation : ```php $data = [ @@ -55,27 +55,27 @@ $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // OK, ça passe. +$normalized = $processor->process($schema, $data); // OK, passe la validation ``` -La sortie, c'est-à-dire la valeur `$normalized`, est l'objet `stdClass`. Si nous voulons que la sortie soit un tableau, nous ajoutons un cast au schéma `Expect::structure([...])->castTo('array')`. +La sortie, c'est-à-dire la valeur `$normalized`, est un objet `stdClass`. Si nous voulions que la sortie soit un tableau, nous ajouterions au schéma un cast `Expect::structure([...])->castTo('array')`. -Tous les éléments de la structure sont facultatifs et ont une valeur par défaut `null`. Exemple : +Tous les éléments de la structure sont facultatifs et ont une valeur par défaut de `null`. Exemple : ```php $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // OK, ça passe +$normalized = $processor->process($schema, $data); // OK, passe la validation // $normalized = {'processRefund' => null, 'refundAmount' => 17} ``` -Le fait que la valeur par défaut soit `null` ne signifie pas qu'elle serait acceptée dans les données d'entrée `'processRefund' => null`. Non, l'entrée doit être booléenne, c'est-à-dire uniquement `true` ou `false`. Il faudrait autoriser explicitement `null` via `Expect::bool()->nullable()`. +Le fait que la valeur par défaut soit `null` ne signifie pas qu'elle serait acceptée dans les données d'entrée `'processRefund' => null`. Non, l'entrée doit être un booléen, c'est-à-dire seulement `true` ou `false`. Pour autoriser `null`, nous devrions l'autoriser explicitement à l'aide de `Expect::bool()->nullable()`. -Un élément peut être rendu obligatoire en utilisant `Expect::bool()->required()`. Nous changeons la valeur par défaut en `false` en utilisant `Expect::bool()->default(false)` ou en peu de temps en utilisant `Expect::bool(false)`. +Un élément peut être rendu obligatoire à l'aide de `Expect::bool()->required()`. Nous changeons la valeur par défaut, par exemple, en `false` à l'aide de `Expect::bool()->default(false)` ou en abrégé `Expect::bool(false)`. -Et si nous voulions accepter `1` and `0` en plus des booléens ? Nous dressons alors la liste des valeurs autorisées, que nous normaliserons également en booléen : +Et si nous voulions accepter `1` et `0` en plus du booléen ? Alors nous indiquons une énumération de valeurs, que nous laissons de plus normaliser en booléen : ```php $schema = Expect::structure([ @@ -87,13 +87,13 @@ $normalized = $processor->process($schema, $data); is_bool($normalized->processRefund); // true ``` -Vous connaissez maintenant les bases de la définition du schéma et du comportement des différents éléments de la structure. Nous allons maintenant montrer quels sont tous les autres éléments qui peuvent être utilisés pour définir un schéma. +Maintenant, vous connaissez les bases de la définition d'un schéma et du comportement des éléments individuels de la structure. Nous allons maintenant montrer quels autres éléments peuvent être utilisés lors de la définition d'un schéma. -Types de données : type() .[#toc-data-types-type] -------------------------------------------------- +Types de données : type() +------------------------- -Tous les types de données standard de PHP peuvent être listés dans le schéma : +Tous les types de données PHP standard peuvent être spécifiés dans le schéma : ```php Expect::string($default = null) @@ -104,63 +104,63 @@ Expect::null() Expect::array($default = []) ``` -Et ensuite tous les types [supportés par les validateurs |utils:validators#Expected Types] via `Expect::type('scalar')` ou en abrégé `Expect::scalar()`. Les noms de classes ou d'interfaces sont également acceptés, par exemple `Expect::type('AddressEntity')`. +Et aussi tous les types [pris en charge par la classe Validators |utils:validators#Types attendus], par exemple `Expect::type('scalar')` ou en abrégé `Expect::scalar()`. Également les noms de classes ou d'interfaces, par exemple `Expect::type('AddressEntity')`. -Vous pouvez également utiliser la notation d'union : +La notation d'union peut également être utilisée : ```php Expect::type('bool|string|array') ``` -La valeur par défaut est toujours `null`, sauf pour `array` et `list`, où il s'agit d'un tableau vide. (Une liste est un tableau indexé dans l'ordre croissant des clés numériques à partir de zéro, c'est-à-dire un tableau non associatif). +La valeur par défaut est toujours `null` sauf pour `array` et `list`, où c'est un tableau vide. (Une liste est un tableau indexé par une série ascendante de clés numériques à partir de zéro, c'est-à-dire un tableau non associatif). -Tableau de valeurs : arrayOf() listOf() .[#toc-array-of-values-arrayof-listof] ------------------------------------------------------------------------------- +Tableaux de valeurs : arrayOf() listOf() +---------------------------------------- -Le tableau est une structure trop générale, il est plus utile de spécifier exactement quels éléments il peut contenir. Par exemple, un tableau dont les éléments ne peuvent être que des chaînes de caractères : +Un tableau représente une structure trop générale, il est plus utile de spécifier exactement quels éléments il peut contenir. Par exemple, un tableau dont les éléments ne peuvent être que des chaînes : ```php $schema = Expect::arrayOf('string'); $processor->process($schema, ['hello', 'world']); // OK $processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK -$processor->process($schema, ['key' => 123]); // ERREUR : 123 n'est pas une chaîne de caractères. +$processor->process($schema, ['key' => 123]); // ERREUR : 123 n'est pas une chaîne ``` -Le second paramètre peut être utilisé pour spécifier les clés (depuis la version 1.2) : +Le deuxième paramètre peut spécifier les clés (depuis la version 1.2) : ```php $schema = Expect::arrayOf('string', 'int'); $processor->process($schema, ['hello', 'world']); // OK -$processor->process($schema, ['a' => 'hello']); // ERREUR : 'a' n'est pas int +$processor->process($schema, ['a' => 'hello']); // ERREUR : 'a' n'est pas un int ``` -La liste est un tableau indexé : +Une liste est un tableau indexé : ```php $schema = Expect::listOf('string'); $processor->process($schema, ['a', 'b']); // OK -$processor->process($schema, ['a', 123]); // ERREUR : 123 n'est pas une chaîne de caractères +$processor->process($schema, ['a', 123]); // ERREUR : 123 n'est pas une chaîne $processor->process($schema, ['key' => 'a']); // ERREUR : n'est pas une liste -$processor->process($schema, [1 => 'a', 0 => 'b']); // ERREUR : n'est pas une liste. +$processor->process($schema, [1 => 'a', 0 => 'b']); // ERREUR : n'est pas non plus une liste ``` -Le paramètre peut aussi être un schéma, on peut donc écrire : +Le paramètre peut également être un schéma, nous pouvons donc écrire : ```php Expect::arrayOf(Expect::bool()) ``` -La valeur par défaut est un tableau vide. Si vous spécifiez la valeur par défaut, elle sera fusionnée avec les données passées. Ceci peut être désactivé en utilisant `mergeDefaults(false)` (depuis la version 1.1). +La valeur par défaut est un tableau vide. Si vous spécifiez une valeur par défaut, elle sera fusionnée avec les données transmises. Cela peut être désactivé à l'aide de `mergeDefaults(false)` (depuis la version 1.1). -Enumération : anyOf() .[#toc-enumeration-anyof] ------------------------------------------------ +Énumération : anyOf() +--------------------- -`anyOf()` est un ensemble de valeurs ou de schémas que peut prendre une valeur. Voici comment écrire un tableau d'éléments qui peuvent être soit `'a'`, `true`, soit `null`: +`anyOf()` représente une énumération de valeurs ou de schémas que la valeur peut prendre. Voici comment écrire un tableau d'éléments qui peuvent être soit `'a'`, `true` ou `null` : ```php $schema = Expect::listOf( @@ -168,7 +168,7 @@ $schema = Expect::listOf( ); $processor->process($schema, ['a', true, null, 'a']); // OK -$processor->process($schema, ['a', false]); // ERREUR : false n'est pas à sa place. +$processor->process($schema, ['a', false]); // ERREUR : false n'appartient pas ici ``` Les éléments de l'énumération peuvent également être des schémas : @@ -182,24 +182,24 @@ $processor->process($schema, ['foo', true, null, 'bar']); // OK $processor->process($schema, [123]); // ERREUR ``` -La méthode `anyOf()` accepte les variantes comme paramètres individuels, et non comme tableau. Pour lui passer un tableau de valeurs, utilisez l'opérateur de décompression `anyOf(...$variants)`. +La méthode `anyOf()` accepte les variantes comme paramètres individuels, pas un tableau. Si vous voulez lui passer un tableau de valeurs, utilisez l'opérateur de décomposition `anyOf(...$variants)`. -La valeur par défaut est `null`. Utilisez la méthode `firstIsDefault()` pour faire du premier élément la valeur par défaut : +La valeur par défaut est `null`. Avec la méthode `firstIsDefault()`, nous faisons du premier élément la valeur par défaut : ```php -// la valeur par défaut est 'hello'. +// la valeur par défaut est 'hello' Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault(); ``` -Structures .[#toc-structures] ------------------------------ +Structures +---------- -Les structures sont des objets avec des clés définies. Chacune de ces paires clé => valeur est appelée "propriété" : +Les structures sont des objets avec des clés définies. Chaque paire clé => valeur est appelée une « propriété » : -Les structures acceptent des tableaux et des objets et renvoient des objets `stdClass` (sauf si vous le changez avec `castTo('array')`, etc.). +Les structures acceptent les tableaux et les objets et retournent des objets `stdClass`. -Par défaut, toutes les propriétés sont facultatives et ont une valeur par défaut de `null`. Vous pouvez définir des propriétés obligatoires en utilisant `required()`: +Par défaut, toutes les propriétés sont facultatives et ont une valeur par défaut de `null`. Vous pouvez définir des propriétés obligatoires à l'aide de `required()` : ```php $schema = Expect::structure([ @@ -208,13 +208,13 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['optional' => '']); -// ERROR: option 'required' is missing +// ERREUR : l'option 'required' est manquante $processor->process($schema, ['required' => 'foo']); -// OK, renvoie {'required' => 'foo', 'optional' => null} +// OK, retourne {'required' => 'foo', 'optional' => null} ``` -Si vous ne souhaitez pas éditer les propriétés ayant uniquement une valeur par défaut, utilisez `skipDefaults()`: +Si vous ne voulez pas avoir de propriétés avec une valeur par défaut dans la sortie, utilisez `skipDefaults()` : ```php $schema = Expect::structure([ @@ -226,7 +226,7 @@ $processor->process($schema, ['required' => 'foo']); // OK, retourne {'required' => 'foo'} ``` -Bien que `null` soit la valeur par défaut de la propriété `optional`, elle n'est pas autorisée dans les données d'entrée (la valeur doit être une chaîne de caractères). Les propriétés acceptant `null` sont définies à l'aide de `nullable()`: +Bien que `null` soit la valeur par défaut de la propriété `optional`, il n'est pas autorisé dans les données d'entrée (la valeur doit être une chaîne). Les propriétés acceptant `null` sont définies à l'aide de `nullable()` : ```php $schema = Expect::structure([ @@ -235,13 +235,15 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['optional' => null]); -// ERROR: 'optional' expects to be string, null given. +// ERREUR : 'optional' s'attend à être une chaîne, null donné. $processor->process($schema, ['nullable' => null]); -// OK, retourne {'optional' => null, 'nullable' => null}. +// OK, retourne {'optional' => null, 'nullable' => null} ``` -Par défaut, il ne peut y avoir aucun élément supplémentaire dans les données d'entrée : +La méthode `getShape()` retourne le tableau de toutes les propriétés de la structure. + +Par défaut, aucun élément supplémentaire ne peut être présent dans les données d'entrée : ```php $schema = Expect::structure([ @@ -249,10 +251,10 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['additional' => 1]); -// ERROR: Unexpected item 'additional' +// ERREUR : Élément inattendu 'additional' ``` -Ce que nous pouvons changer avec `otherItems()`. En tant que paramètre, nous allons spécifier le schéma pour chaque élément supplémentaire : +Ce que nous pouvons changer à l'aide de `otherItems()`. Comme paramètre, nous indiquons le schéma selon lequel les éléments supplémentaires seront validés : ```php $schema = Expect::structure([ @@ -263,78 +265,116 @@ $processor->process($schema, ['additional' => 1]); // OK $processor->process($schema, ['additional' => true]); // ERREUR ``` +Vous pouvez créer une nouvelle structure en dérivant d'une autre à l'aide de `extend()` : -Dépréciations .[#toc-deprecations] ----------------------------------- +```php +$dog = Expect::structure([ + 'name' => Expect::string(), + 'age' => Expect::int(), +]); + +$dogWithBreed = $dog->extend([ + 'breed' => Expect::string(), +]); +``` + + +Tableaux .{data-version:1.3.2} +------------------------------ + +Tableaux avec des clés définies. Tout ce qui s'applique aux [#structures] s'applique également ici. + +```php +$schema = Expect::array([ + 'required' => Expect::string()->required(), + 'optional' => Expect::string(), // la valeur par défaut est null +]); +``` -Vous pouvez déprécier une propriété en utilisant la méthode `deprecated([string $message])` méthode. Les avis de dépréciation sont renvoyés par `$processor->getWarnings()`: +Il est également possible de définir un tableau indexé, connu sous le nom de tuple : + +```php +$schema = Expect::array([ + Expect::int(), + Expect::string(), + Expect::bool(), +]); + +$processor->process($schema, [1, 'hello', true]); // OK +``` + + +Propriétés obsolètes +-------------------- + +Vous pouvez marquer une propriété comme obsolète à l'aide de la méthode `deprecated([string $message])`. Les informations sur la fin de prise en charge sont retournées via `$processor->getWarnings()` : ```php $schema = Expect::structure([ - 'old' => Expect::int()->deprecated('L'élément %path% est obsolète'), + 'old' => Expect::int()->deprecated('L\'élément %path% est obsolète'), ]); $processor->process($schema, ['old' => 1]); // OK -$processor->getWarnings(); // ["L'élément 'old' est obsolète"]. +$processor->getWarnings(); // ["L'élément 'old' est obsolète"] ``` -Plages : min() max() .[#toc-ranges-min-max] -------------------------------------------- +Plages : min() max() +-------------------- -Utilisez `min()` et `max()` pour limiter le nombre d'éléments des tableaux : +À l'aide de `min()` et `max()`, on peut limiter le nombre d'éléments dans les tableaux : ```php -// tableau, au moins 10 éléments, maximum 20 éléments +// tableau, au moins 10 éléments, au plus 20 éléments Expect::array()->min(10)->max(20); ``` -Pour les chaînes de caractères, limitez leur longueur : +Pour les chaînes, limiter leur longueur : ```php -// chaîne de caractères, d'au moins 10 caractères et de 20 caractères maximum +// chaîne, au moins 10 caractères de long, au plus 20 caractères Expect::string()->min(10)->max(20); ``` -Pour les nombres, limitez leur valeur : +Pour les nombres, limiter leur valeur : ```php -// nombre entier, entre 10 et 20 inclus +// entier, entre 10 et 20 inclus Expect::int()->min(10)->max(20); ``` -Bien sûr, il est possible de ne mentionner que `min()`, ou que `max()`: +Bien sûr, il est possible de n'indiquer que `min()`, ou seulement `max()` : ```php -// chaîne de caractères, 20 caractères maximum +// chaîne d'au plus 20 caractères Expect::string()->max(20); ``` -Expressions régulières : pattern() .[#toc-regular-expressions-pattern] ----------------------------------------------------------------------- +Expressions régulières : pattern() +---------------------------------- -En utilisant `pattern()`, vous pouvez spécifier une expression régulière à laquelle la chaîne d'entrée **entière** doit correspondre (c'est-à-dire comme si elle était entourée de caractères `^` a `$`) : +À l'aide de `pattern()`, on peut indiquer une expression régulière à laquelle **toute** la chaîne d'entrée doit correspondre (c'est-à-dire comme si elle était entourée des caractères `^` et `$`): ```php -// seulement 9 chiffres +// exactement 9 chiffres Expect::string()->pattern('\d{9}'); ``` -Assertions personnalisées : assert() .[#toc-custom-assertions-assert] ---------------------------------------------------------------------- +Contraintes personnalisées : assert() +------------------------------------- -Vous pouvez ajouter toute autre restriction en utilisant `assert(callable $fn)`. +Toute autre contrainte peut être spécifiée à l'aide de `assert(callable $fn)`. ```php $countIsEven = fn($v) => count($v) % 2 === 0; $schema = Expect::arrayOf('string') - ->assert($countIsEven); // le nombre doit être pair. + ->assert($countIsEven); // le nombre doit être pair $processor->process($schema, ['a', 'b']); // OK -$processor->process($schema, ['a', 'b', 'c']); // ERREUR : 3 n'est pas pair. +$processor->process($schema, ['a', 'b', 'c']); // ERREUR : 3 n'est pas un nombre pair ``` Ou @@ -343,95 +383,106 @@ Ou Expect::string()->assert('is_file'); // le fichier doit exister ``` -Vous pouvez ajouter votre propre description pour chaque assertion. Elle fera partie du message d'erreur. +À chaque contrainte, vous pouvez ajouter votre propre description. Celle-ci fera partie du message d'erreur. ```php $schema = Expect::arrayOf('string') - ->assert($countIsEven, 'Even items in array'); + ->assert($countIsEven, 'Éléments pairs dans le tableau'); $processor->process($schema, ['a', 'b', 'c']); -// Failed assertion "Even items in array" for item with value array. +// Failed assertion "Éléments pairs dans le tableau" for item with value array. ``` -La méthode peut être appelée à plusieurs reprises pour ajouter d'autres assertions. +La méthode peut être appelée de manière répétée pour ajouter plusieurs contraintes. Elle peut être entrelacée avec les appels `transform()` et `castTo()`. -Mappage vers les objets : from() .[#toc-mapping-to-objects-from] ----------------------------------------------------------------- +Transformations : transform() .{data-version:1.2.5} +--------------------------------------------------- -Vous pouvez générer un schéma de structure à partir de la classe. Exemple : +Les données validées avec succès peuvent être modifiées à l'aide d'une fonction personnalisée : ```php -class Config -{ - public string $name; - public string|null $password; - public bool $admin = false; -} - -$schema = Expect::from(new Config); +// conversion en majuscules : +Expect::string()->transform(fn(string $s) => strtoupper($s)); +``` -$data = [ - 'name' => 'jeff', -]; +La méthode peut être appelée de manière répétée pour ajouter plusieurs transformations. Elle peut être entrelacée avec les appels `assert()` et `castTo()`. Les opérations sont effectuées dans l'ordre où elles sont déclarées : -$normalized = $processor->process($schema, $data); -// $normalized instanceof Config -// $normalized = {'name' => 'jeff', 'password' => null, 'admin' => false} +```php +Expect::type('string|int') + ->castTo('string') + ->assert('ctype_lower', 'Tous les caractères doivent être en minuscules') + ->transform(fn(string $s) => strtoupper($s)); // conversion en majuscules ``` -Si vous utilisez PHP 7.4 ou plus, vous pouvez utiliser les types natifs : +La méthode `transform()` peut simultanément transformer et valider la valeur. C'est souvent plus simple et moins répétitif que d'enchaîner `transform()` et `assert()`. À cette fin, la fonction reçoit un objet [Context |api:Nette\Schema\Context] avec la méthode `addError()`, qui peut être utilisée pour ajouter des informations sur les problèmes de validation : ```php -class Config -{ - public string $name; - public ?string $password; - public bool $admin = false; -} +Expect::string() + ->transform(function (string $s, Nette\Schema\Context $context) { + if (!ctype_lower($s)) { + $context->addError('Tous les caractères doivent être en minuscules', 'my.case.error'); + return null; + } -$schema = Expect::from(new Config); + return strtoupper($s); + }); ``` -Les classes anonymes sont également supportées : + +Conversion de type : castTo() +----------------------------- + +Les données validées avec succès peuvent être converties : ```php -$schema = Expect::from(new class { - public string $name; - public ?string $password; - public bool $admin = false; -}); +Expect::scalar()->castTo('string'); ``` -Comme les informations obtenues à partir de la définition de la classe peuvent ne pas être suffisantes, vous pouvez ajouter un schéma personnalisé pour les éléments avec le deuxième paramètre : +En plus des types PHP natifs, il est possible de convertir vers des classes. On distingue alors s'il s'agit d'une classe simple sans constructeur ou d'une classe avec constructeur. Si la classe n'a pas de constructeur, son instance est créée et tous les éléments de la structure sont écrits dans les propriétés : ```php -$schema = Expect::from(new Config, [ - 'name' => Expect::string()->pattern('\w:.*'), -]); -``` +class Info +{ + public bool $processRefund; + public int $refundAmount; +} +Expect::structure([ + 'processRefund' => Expect::bool(), + 'refundAmount' => Expect::int(), +])->castTo(Info::class); -Casting : castTo() .[#toc-casting-castto] ------------------------------------------ +// crée '$obj = new Info' et écrit dans $obj->processRefund et $obj->refundAmount +``` -Les données validées avec succès peuvent être coulées : +Si la classe a un constructeur, les éléments de la structure sont passés comme paramètres nommés au constructeur : ```php -Expect::scalar()->castTo('string'); +class Info +{ + public function __construct( + public bool $processRefund, + public int $refundAmount, + ) { + } +} + +// crée $obj = new Info(processRefund: ..., refundAmount: ...) ``` -En plus des types natifs de PHP, vous pouvez également effectuer des castings vers des classes : +La conversion de type combinée à un paramètre scalaire crée un objet et passe la valeur comme unique paramètre au constructeur : ```php -Expect::scalar()->castTo('AddressEntity'); +Expect::string()->castTo(DateTime::class); +// crée new DateTime(...) ``` -Normalisation : before() .[#toc-normalization-before] ------------------------------------------------------ +Normalisation : before() +------------------------ -Avant la validation proprement dite, les données peuvent être normalisées à l'aide de la méthode `before()`. À titre d'exemple, prenons un élément qui doit être un tableau de chaînes de caractères (ex. `['a', 'b', 'c']`), mais qui reçoit des données sous la forme d'une chaîne de caractères `a b c`: +Avant la validation elle-même, les données peuvent être normalisées à l'aide de la méthode `before()`. Prenons comme exemple un élément qui doit être un tableau de chaînes (par exemple `['a', 'b', 'c']`), mais qui accepte une entrée sous forme de chaîne `a b c` : ```php $explode = fn($v) => explode(' ', $v); @@ -440,7 +491,48 @@ $schema = Expect::arrayOf('string') ->before($explode); $normalized = $processor->process($schema, 'a b c'); -// OK, renvoie ['a', 'b', 'c'] +// OK et retourne ['a', 'b', 'c'] +``` + + +Mappage vers des objets : from() +-------------------------------- + +Nous pouvons laisser le schéma de structure être généré à partir d'une classe. Exemple : + +```php +class Config +{ + public string $name; + public string|null $password; + public bool $admin = false; +} + +$schema = Expect::from(new Config); + +$data = [ + 'name' => 'franta', +]; + +$normalized = $processor->process($schema, $data); +// $normalized instanceof Config +// $normalized = {'name' => 'franta', 'password' => null, 'admin' => false} +``` + +Les classes anonymes sont également prises en charge : + +```php +$schema = Expect::from(new class { + public string $name; + public ?string $password; + public bool $admin = false; +}); ``` -{{leftbar: nette:@menu-topics}} +Comme les informations obtenues à partir de la définition de la classe peuvent ne pas être suffisantes, vous pouvez compléter les éléments avec votre propre schéma via le deuxième paramètre : + +```php +$schema = Expect::from(new Config, [ + 'name' => Expect::string()->pattern('\w:.*'), +]); +``` diff --git a/schema/fr/@meta.texy b/schema/fr/@meta.texy new file mode 100644 index 0000000000..95ec8a4ef6 --- /dev/null +++ b/schema/fr/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Documentation Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/schema/hu/@home.texy b/schema/hu/@home.texy index bccac8b89b..b2c5ddb911 100644 --- a/schema/hu/@home.texy +++ b/schema/hu/@home.texy @@ -1,8 +1,8 @@ -Schema: Adatérvényesítés -************************ +Nette Schema +************ .[perex] -Egy praktikus könyvtár az adatstruktúrák érvényesítésére és normalizálására egy adott séma alapján, intelligens és könnyen érthető API-val. +Praktikus könyvtár adatszerkezetek validálására és normalizálására egy adott séma alapján, okos, érthető API-val. Telepítés: @@ -11,12 +11,12 @@ composer require nette/schema ``` -Alapvető használat .[#toc-basic-usage] --------------------------------------- +Alapvető használat +------------------ -A `$schema` változóban van egy érvényesítési séma (hogy ez pontosan mit jelent és hogyan kell létrehozni, arról később szólunk), a `$data` változóban pedig egy adatszerkezet, amelyet érvényesíteni és normalizálni szeretnénk. Ez lehet például a felhasználó által egy API-n keresztül küldött adat, konfigurációs fájl stb. +A `$schema` változóban van a validációs séma (hogy ez pontosan mit jelent, és hogyan hozzunk létre ilyen sémát, azt hamarosan elmondjuk), a `$data` változóban pedig az az adatszerkezet, amelyet validálni és normalizálni szeretnénk. Ez lehet például egy felhasználó által API interfészen keresztül küldött adat, konfigurációs fájl stb. -A feladatot a [api:Nette\Schema\Processor] osztály látja el, amely feldolgozza a bemenetet, és vagy normalizált adatokat ad vissza, vagy hiba esetén egy [api:Nette\Schema\ValidationException] kivételt dob. +A feladatot a [api:Nette\Schema\Processor] osztály végzi el, amely feldolgozza a bemenetet, és vagy visszaadja a normalizált adatokat, vagy hiba esetén [api:Nette\Schema\ValidationException] kivételt dob. ```php $processor = new Nette\Schema\Processor; @@ -24,17 +24,17 @@ $processor = new Nette\Schema\Processor; try { $normalized = $processor->process($schema, $data); } catch (Nette\Schema\ValidationException $e) { - echo 'Data is invalid: ' . $e->getMessage(); + echo 'Az adatok érvénytelenek: ' . $e->getMessage(); } ``` -A `$e->getMessages()` módszer az összes üzenetsorozat tömbjét adja vissza, a `$e->getMessageObjects()` pedig az összes üzenetet "Nette\Schema\Message":https://api.nette.org/schema/master/Nette/Schema/Message.html objektumként adja vissza. +Az `$e->getMessages()` metódus visszaadja az összes üzenetet stringként tartalmazó tömböt, az `$e->getMessageObjects()` pedig az összes üzenetet [api:Nette\Schema\Message] objektumként. -Séma definiálása .[#toc-defining-schema] ----------------------------------------- +Séma definiálása +---------------- -És most hozzunk létre egy sémát. Ennek definiálására a [api:Nette\Schema\Expect] osztályt használjuk, tulajdonképpen elvárásokat határozunk meg arra vonatkozóan, hogy az adatoknak hogyan kell kinézniük. Tegyük fel, hogy a bemeneti adatnak egy struktúrának (pl. egy tömbnek) kell lennie, amely `processRefund` típusú bool és `refundAmount` típusú int típusú elemeket tartalmaz. +És most hozzuk létre a sémát. A definiálására a [api:Nette\Schema\Expect] osztály szolgál, tulajdonképpen elvárásokat definiálunk, hogy hogyan kell kinézniük az adatoknak. Tegyük fel, hogy a bemeneti adatoknak egy struktúrát (például tömböt) kell alkotniuk, amely `processRefund` (bool típusú) és `refundAmount` (int típusú) elemeket tartalmaz. ```php use Nette\Schema\Expect; @@ -45,9 +45,9 @@ $schema = Expect::structure([ ]); ``` -Úgy gondoljuk, hogy a sémadefiníció egyértelműnek tűnik, még akkor is, ha először látjuk. +Bízunk benne, hogy a séma definíciója érthetőnek tűnik, még akkor is, ha most látja először. -Küldjük el a következő adatokat érvényesítésre: +Küldjük validálásra a következő adatokat: ```php $data = [ @@ -55,10 +55,10 @@ $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // OK, átment. +$normalized = $processor->process($schema, $data); // OK, átmegy a validáción ``` -A kimenet, azaz a `$normalized` érték a `stdClass` objektum. Ha azt akarjuk, hogy a kimenet egy tömb legyen, akkor a sémához egy castot adunk hozzá `Expect::structure([...])->castTo('array')`. +A kimenet, azaz a `$normalized` érték, egy `stdClass` objektum. Ha azt szeretnénk, hogy a kimenet tömb legyen, kiegészítjük a sémát egy `Expect::structure([...])->castTo('array')` típuskonverzióval. A struktúra minden eleme opcionális, és alapértelmezett értéke `null`. Példa: @@ -67,15 +67,15 @@ $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // OK, átment. +$normalized = $processor->process($schema, $data); // OK, átmegy a validáción // $normalized = {'processRefund' => null, 'refundAmount' => 17} ``` -Az a tény, hogy az alapértelmezett érték `null`, nem jelenti azt, hogy a bemeneti adatokban elfogadható lenne `'processRefund' => null`. Nem, a bemeneti adatnak boolean-nak kell lennie, azaz csak `true` vagy `false`. A `null` -t kifejezetten engedélyeznünk kellene a `Expect::bool()->nullable()`-on keresztül. +Az, hogy az alapértelmezett érték `null`, nem jelenti azt, hogy a bemeneti adatokban elfogadná a `'processRefund' => null`-t. Nem, a bemenetnek boolean-nek kell lennie, tehát csak `true` vagy `false`. A `null` engedélyezéséhez explicit módon kellene használni a `Expect::bool()->nullable()`-t. -Egy elemet a `Expect::bool()->required()` segítségével lehet kötelezővé tenni. Az alapértelmezett értéket a `false` segítségével `Expect::bool()->default(false)` vagy röviden a `Expect::bool(false)` segítségével -ra változtatjuk. +Egy elemet kötelezővé tehetünk a `Expect::bool()->required()` segítségével. Az alapértelmezett értéket megváltoztathatjuk például `false`-ra a `Expect::bool()->default(false)` vagy röviden `Expect::bool(false)` segítségével. -És mi lenne, ha a booleans mellett a `1` and `0` -t is el akarnánk fogadni? Akkor felsoroljuk a megengedett értékeket, amelyeket szintén booleanra normalizálunk: +És mi van, ha a boolean mellett még az `1`-et és `0`-t is el akarjuk fogadni? Akkor megadjuk az értékek felsorolását, amelyeket ráadásul boolean-re normalizálunk: ```php $schema = Expect::structure([ @@ -87,13 +87,13 @@ $normalized = $processor->process($schema, $data); is_bool($normalized->processRefund); // true ``` -Most már ismered az alapokat, hogyan definiálódik a séma, és hogyan viselkednek a struktúra egyes elemei. Most megmutatjuk, hogy a séma definiálásakor milyen egyéb elemeket lehet használni. +Most már ismeri az alapokat arról, hogyan definiáljunk sémát, és hogyan viselkednek a struktúra egyes elemei. Most megmutatjuk, milyen további elemeket lehet használni a séma definiálásakor. -Adattípusok: type() .[#toc-data-types-type] -------------------------------------------- +Adattípusok: type() +------------------- -Az összes szabványos PHP adattípus felsorolható a sémában: +A sémában megadhatók az összes standard PHP adattípus: ```php Expect::string($default = null) @@ -104,37 +104,37 @@ Expect::null() Expect::array($default = []) ``` -És ezután az összes típus [által támogatott validátorok |utils:validators#Expected Types] keresztül `Expect::type('scalar')` vagy rövidített `Expect::scalar()`. Az osztály- vagy interfésznevek is elfogadottak, pl. `Expect::type('AddressEntity')`. +Továbbá minden típus, amelyet [a Validators osztály támogat |utils:validators#Várt típusok], például `Expect::type('scalar')` vagy röviden `Expect::scalar()`. Továbbá osztály- vagy interfésznevek, például `Expect::type('AddressEntity')`. -Használhatjuk az union jelölést is: +Használható union jelölés is: ```php Expect::type('bool|string|array') ``` -Az alapértelmezett érték mindig `null`, kivéve a `array` és `list`, ahol ez egy üres tömb. (A lista a nullától kezdve a numerikus kulcsok növekvő sorrendjében indexelt tömb, azaz nem asszociatív tömb). +Az alapértelmezett érték mindig `null`, kivéve az `array` és `list` esetében, ahol ez egy üres tömb. (A lista egy nullától kezdődő, növekvő sorrendű numerikus kulcsokkal indexelt tömb, azaz nem asszociatív tömb). -Értékek tömbje: arrayOf() listOf() .[#toc-array-of-values-arrayof-listof] -------------------------------------------------------------------------- +Értékek tömbje: arrayOf() listOf() +---------------------------------- -A tömb túl általános struktúra, célszerűbb pontosan megadni, hogy milyen elemeket tartalmazhat. Például egy olyan tömb, amelynek elemei csak karakterláncok lehetnek: +A tömb túl általános struktúra, hasznosabb megadni, hogy pontosan milyen elemeket tartalmazhat. Például egy tömb, amelynek elemei csak stringek lehetnek: ```php $schema = Expect::arrayOf('string'); $processor->process($schema, ['hello', 'world']); // OK $processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK -$processor->process($schema, ['key' => 123]); // HIBA: 123 nem karakterlánc. +$processor->process($schema, ['key' => 123]); // HIBA: 123 nem string ``` -A második paraméterrel megadhatjuk a kulcsokat (az 1.2-es verzió óta): +A második paraméterrel megadhatók a kulcsok (1.2 verziótól): ```php $schema = Expect::arrayOf('string', 'int'); $processor->process($schema, ['hello', 'world']); // OK -$processor->process($schema, ['a' => 'hello']); // ERROR: 'a' nem int +$processor->process($schema, ['a' => 'hello']); // HIBA: 'a' nem int ``` A lista egy indexelt tömb: @@ -143,24 +143,24 @@ A lista egy indexelt tömb: $schema = Expect::listOf('string'); $processor->process($schema, ['a', 'b']); // OK -$processor->process($schema, ['a', 123]); // HIBA: 123 nem karakterlánc. +$processor->process($schema, ['a', 123]); // HIBA: 123 nem string $processor->process($schema, ['key' => 'a']); // HIBA: nem lista -$processor->process($schema, [1 => 'a', 0 => 'b']); // HIBA: nem lista. +$processor->process($schema, [1 => 'a', 0 => 'b']); // HIBA: szintén nem lista ``` -A paraméter lehet egy séma is, így leírhatjuk: +A paraméter lehet séma is, tehát írhatjuk: ```php Expect::arrayOf(Expect::bool()) ``` -Az alapértelmezett érték egy üres tömb. Ha megadjuk az alapértelmezett értéket, akkor az összeolvad az átadott adatokkal. Ez kikapcsolható a `mergeDefaults(false)` segítségével (az 1.1-es verzió óta). +Az alapértelmezett érték egy üres tömb. Ha alapértelmezett értéket ad meg, az összevonásra kerül az átadott adatokkal. Ezt a `mergeDefaults(false)` segítségével lehet letiltani (1.1 verziótól). -Felsorolás: anyOf() .[#toc-enumeration-anyof] ---------------------------------------------- +Felsorolás: anyOf() +------------------- -`anyOf()` azoknak az értékeknek vagy sémáknak a halmaza, amelyek egy érték lehet. Íme, hogyan írjunk egy olyan elemekből álló tömböt, amely lehet `'a'`, `true` vagy `null`: +Az `anyOf()` értékek vagy sémák felsorolását jelenti, amelyeket az érték felvehet. Így írjuk le egy olyan elemekből álló tömböt, amelyek lehetnek `'a'`, `true` vagy `null`: ```php $schema = Expect::listOf( @@ -168,7 +168,7 @@ $schema = Expect::listOf( ); $processor->process($schema, ['a', true, null, 'a']); // OK -$processor->process($schema, ['a', false]); // ERROR: false nem tartozik oda +$processor->process($schema, ['a', false]); // HIBA: a false nem tartozik ide ``` A felsorolás elemei lehetnek sémák is: @@ -182,39 +182,39 @@ $processor->process($schema, ['foo', true, null, 'bar']); // OK $processor->process($schema, [123]); // HIBA ``` -A `anyOf()` módszer a variánsokat egyéni paraméterként fogadja el, nem tömbként. Ha értékekből álló tömböt akarunk átadni neki, használjuk a `anyOf(...$variants)` kicsomagoló operátort. +Az `anyOf()` metódus a változatokat különálló paraméterekként fogadja, nem tömbként. Ha értékek tömbjét szeretné átadni neki, használja az unpacking operátort `anyOf(...$variants)`. -Az alapértelmezett érték a `null`. A `firstIsDefault()` módszerrel az első elemet teheti alapértelmezetté: +Az alapértelmezett érték `null`. A `firstIsDefault()` metódussal az első elemet tesszük alapértelmezetté: ```php -// alapértelmezett a 'hello' +// az alapértelmezett 'hello' Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault(); ``` -Struktúrák .[#toc-structures] ------------------------------ +Struktúrák +---------- -A struktúrák meghatározott kulcsokkal rendelkező objektumok. A kulcs => érték párok mindegyikét "tulajdonságnak" nevezzük: +A struktúrák definiált kulcsokkal rendelkező objektumok. Minden kulcs => érték párt „property”-nek nevezünk: -A struktúrák tömböket és objektumokat fogadnak el, és objektumokat adnak vissza `stdClass` (hacsak nem változtatjuk meg a `castTo('array')`, stb. segítségével). +A struktúrák tömböket és objektumokat fogadnak el, és `stdClass` objektumokat adnak vissza. -Alapértelmezés szerint minden tulajdonság opcionális, és alapértelmezett értéke `null`. Kötelező tulajdonságokat a `required()` segítségével lehet definiálni: +Alapértelmezés szerint minden property opcionális, és alapértelmezett értéke `null`. Kötelező property-ket definiálhat a `required()` segítségével: ```php $schema = Expect::structure([ 'required' => Expect::string()->required(), - 'optional' => Expect::string(), // az alapértelmezett érték null + 'optional' => Expect::string(), // alapértelmezett érték null ]); $processor->process($schema, ['optional' => '']); -// ERROR: az 'required' opció hiányzik. +// HIBA: az 'required' opció hiányzik $processor->process($schema, ['required' => 'foo']); -// OK, visszatér {'required' => 'foo', 'optional' => null} +// OK, visszaadja {'required' => 'foo', 'optional' => null} ``` -Ha nem szeretne csak alapértelmezett értékkel rendelkező tulajdonságokat kiadni, használja a `skipDefaults()`: +Ha nem szeretné, hogy a kimenetben alapértelmezett értékkel rendelkező property-k legyenek, használja a `skipDefaults()`-t: ```php $schema = Expect::structure([ @@ -223,10 +223,10 @@ $schema = Expect::structure([ ])->skipDefaults(); $processor->process($schema, ['required' => 'foo']); -// OK, visszatér {'required' => 'foo'} +// OK, visszaadja {'required' => 'foo'} ``` -Bár a `null` a `optional` tulajdonság alapértelmezett értéke, a bemeneti adatokban nem megengedett (az értéknek egy karakterláncnak kell lennie). A `null` -t elfogadó tulajdonságok a `nullable()` használatával kerülnek definiálásra: +Bár a `null` az `optional` property alapértelmezett értéke, a bemeneti adatokban nem engedélyezett (az értéknek stringnek kell lennie). A `null`-t elfogadó property-ket a `nullable()` segítségével definiáljuk: ```php $schema = Expect::structure([ @@ -235,12 +235,14 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['optional' => null]); -// ERROR: 'optional' elvárja, hogy string legyen, null megadva. +// HIBA: az 'optional' stringet vár, null-t kapott. $processor->process($schema, ['nullable' => null]); -// OK, visszatér {'optional' => null, 'nullable' => null} +// OK, visszaadja {'optional' => null, 'nullable' => null} ``` +A struktúra összes property-jének tömbjét a `getShape()` metódus adja vissza. + Alapértelmezés szerint a bemeneti adatokban nem lehetnek extra elemek: ```php @@ -249,10 +251,10 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['additional' => 1]); -// ERROR: Váratlan 'additional' elem +// HIBA: Váratlan elem 'additional' ``` -Amit a `otherItems()` segítségével megváltoztathatunk. Paraméterként megadjuk az egyes extra elemek sémáját: +Ezt megváltoztathatjuk az `otherItems()` segítségével. Paraméterként adjunk meg egy sémát, amely szerint az extra elemek validálva lesznek: ```php $schema = Expect::structure([ @@ -263,175 +265,224 @@ $processor->process($schema, ['additional' => 1]); // OK $processor->process($schema, ['additional' => true]); // HIBA ``` +Új struktúrát hozhat létre egy másikból származtatva a `extend()` segítségével: + +```php +$dog = Expect::structure([ + 'name' => Expect::string(), + 'age' => Expect::int(), +]); + +$dogWithBreed = $dog->extend([ + 'breed' => Expect::string(), +]); +``` + + +Tömbök .{data-version:1.3.2} +---------------------------- + +Definiált kulcsokkal rendelkező tömbök. Minden vonatkozik rá, ami a [#struktúrák]-ra. + +```php +$schema = Expect::array([ + 'required' => Expect::string()->required(), + 'optional' => Expect::string(), // alapértelmezett érték null +]); +``` + +Definiálható indexelt tömb is, amelyet tuple-ként ismerünk: -Deprecations .[#toc-deprecations] ---------------------------------- +```php +$schema = Expect::array([ + Expect::int(), + Expect::string(), + Expect::bool(), +]); + +$processor->process($schema, [1, 'hello', true]); // OK +``` -A tulajdonságokat a `deprecated([string $message])` módszerrel. A visszavonásról szóló értesítéseket a `$processor->getWarnings()` adja vissza: + +Elavult propertyk +----------------- + +Egy propertyt elavultként jelölhet meg a `deprecated([string $message])` metódussal. A támogatás megszűnéséről szóló információkat a `$processor->getWarnings()` adja vissza: ```php $schema = Expect::structure([ - 'old' => Expect::int()->deprecated('The item %path% is deprecated'), + 'old' => Expect::int()->deprecated('Az elem %path% elavult'), ]); $processor->process($schema, ['old' => 1]); // OK -$processor->getWarnings(); // ["The item 'old' is deprecated"] +$processor->getWarnings(); // ["Az elem 'old' elavult"] ``` -Tartományok: min() max() .[#toc-ranges-min-max] ------------------------------------------------ +Tartományok: min() max() +------------------------ -A `min()` és a `max()` használatával korlátozhatja a tömbök elemeinek számát: +A `min()` és `max()` segítségével korlátozhatjuk a tömbök elemeinek számát: ```php // tömb, legalább 10 elem, legfeljebb 20 elem Expect::array()->min(10)->max(20); ``` -A karakterláncok esetében korlátozza a hosszukat: +Stringeknél korlátozhatjuk a hosszukat: ```php -// legalább 10, legfeljebb 20 karakter hosszúságú karakterlánc +// string, legalább 10 karakter hosszú, legfeljebb 20 karakter Expect::string()->min(10)->max(20); ``` -Számok esetében korlátozza az értéküket: +Számoknál korlátozhatjuk az értéküket: ```php -// egész szám, 10 és 20 között +// egész szám, 10 és 20 között, beleértve Expect::int()->min(10)->max(20); ``` -Természetesen lehetséges, hogy csak a `min()`, vagy csak a `max()`: +Természetesen megadhatjuk csak a `min()`-t vagy csak a `max()`-ot: ```php -// string, maximum 20 karakter +// string legfeljebb 20 karakter Expect::string()->max(20); ``` -Szabályos kifejezések: pattern() .[#toc-regular-expressions-pattern] --------------------------------------------------------------------- +Reguláris kifejezések: pattern() +-------------------------------- -A `pattern()` segítségével megadhatunk egy olyan reguláris kifejezést, amelynek a **összes** bemeneti karakterláncnak meg kell felelnie (azaz mintha a `^` a `$` karakterekbe lenne csomagolva): +A `pattern()` segítségével megadhatunk egy reguláris kifejezést, amelynek a **teljes** bemeneti stringnek meg kell felelnie (azaz mintha `^` és `$` karakterekkel lenne körülvéve): ```php -// csak 9 számjegy +// pontosan 9 számjegy Expect::string()->pattern('\d{9}'); ``` -assert() .[#toc-custom-assertions-assert] ------------------------------------------ +Egyéni megszorítások: assert() +------------------------------ -Bármilyen más korlátozást is hozzáadhat a `assert(callable $fn)` segítségével. +Bármilyen további korlátozást megadhatunk az `assert(callable $fn)` segítségével. ```php $countIsEven = fn($v) => count($v) % 2 === 0; $schema = Expect::arrayOf('string') - ->assert($countIsEven); // a számlálásnak párosnak kell lennie + ->assert($countIsEven); // a számnak párosnak kell lennie $processor->process($schema, ['a', 'b']); // OK -$processor->process($schema, ['a', 'b', 'c']); // ERROR: 3 nem páros +$processor->process($schema, ['a', 'b', 'c']); // HIBA: 3 nem páros szám ``` -Vagy a +Vagy ```php Expect::string()->assert('is_file'); // a fájlnak léteznie kell ``` -Minden egyes állításhoz saját leírást adhat. Ez a hibaüzenet része lesz. +Minden korlátozáshoz hozzáadhat saját leírást. Ez a hibaüzenet része lesz. ```php $schema = Expect::arrayOf('string') ->assert($countIsEven, 'Páros elemek a tömbben'); $processor->process($schema, ['a', 'b', 'c']); -// Sikertelen "Páros elemek a tömbben" állítás a tömb értékű elemre. +// Failed assertion "Páros elemek a tömbben" for item with value array. ``` -A metódus ismételten meghívható további állítások hozzáadásához. +A metódust ismételten hívhatjuk, hogy több korlátozást adjunk hozzá. Váltakozhat a `transform()` és `castTo()` hívásokkal. -Objektumokra való leképezés: from() .[#toc-mapping-to-objects-from] -------------------------------------------------------------------- +Átalakítások: transform() .{data-version:1.2.5} +----------------------------------------------- -Az osztályból struktúra sémát generálhatunk. Példa: +A sikeresen validált adatokat módosíthatjuk egy saját függvénnyel: ```php -class Config -{ - public string $name; - public string|null $password; - public bool $admin = false; -} +// átalakítás nagybetűsre: +Expect::string()->transform(fn(string $s) => strtoupper($s)); +``` -$schema = Expect::from(new Config); +A metódust ismételten hívhatjuk, hogy több átalakítást adjunk hozzá. Váltakozhat az `assert()` és `castTo()` hívásokkal. A műveletek abban a sorrendben hajtódnak végre, ahogyan deklarálva vannak: -$data = [ - 'name' => 'jeff', -]; - -$normalized = $processor->process($schema, $data); -// $normalized instanceof Config -// $normalized = {'name' => 'jeff', 'password' => null, 'admin' => false} +```php +Expect::type('string|int') + ->castTo('string') + ->assert('ctype_lower', 'Minden karakternek kisbetűsnek kell lennie') + ->transform(fn(string $s) => strtoupper($s)); // átalakítás nagybetűsre ``` -Ha a PHP 7.4 vagy magasabb verziószámú PHP-t használ, használhat natív típusokat: +A `transform()` metódus egyszerre átalakíthatja és validálhatja az értéket. Ez gyakran egyszerűbb és kevésbé duplikált, mint a `transform()` és `assert()` láncolása. Ebből a célból a függvény megkapja a [Context |api:Nette\Schema\Context] objektumot a `addError()` metódussal, amelyet a validációs problémákkal kapcsolatos információk hozzáadására lehet használni: ```php -class Config -{ - public string $name; - public ?string $password; - public bool $admin = false; -} +Expect::string() + ->transform(function (string $s, Nette\Schema\Context $context) { + if (!ctype_lower($s)) { + $context->addError('Minden karakternek kisbetűsnek kell lennie', 'my.case.error'); + return null; + } -$schema = Expect::from(new Config); + return strtoupper($s); + }); ``` -A névtelen osztályok is támogatottak: + +Típuskonverzió: castTo() +------------------------ + +A sikeresen validált adatokat át lehet típusolni: ```php -$schema = Expect::from(new class { - public string $name; - public ?string $password; - public bool $admin = false; -}); +Expect::scalar()->castTo('string'); ``` -A második paraméterrel egyéni sémát adhat az elemekhez, mivel az osztálydefinícióból kapott információ nem feltétlenül elegendő: +A natív PHP típusokon kívül osztályokra is át lehet típusolni. Itt különbséget teszünk, hogy egyszerű osztályról van-e szó konstruktor nélkül, vagy konstruktorral rendelkező osztályról. Ha az osztálynak nincs konstruktora, létrejön az példánya, és a struktúra minden eleme beíródik a propertykbe: ```php -$schema = Expect::from(new Config, [ - 'name' => Expect::string()->pattern('\w:.*'), -]); -``` +class Info +{ + public bool $processRefund; + public int $refundAmount; +} +Expect::structure([ + 'processRefund' => Expect::bool(), + 'refundAmount' => Expect::int(), +])->castTo(Info::class); -Casting: castTo() .[#toc-casting-castto] ----------------------------------------- +// létrehozza '$obj = new Info'-t és beírja a $obj->processRefund és $obj->refundAmount-ba +``` -Sikeresen érvényesített adatok önthetők: +Ha az osztálynak van konstruktora, a struktúra elemei névvel ellátott paraméterként kerülnek átadásra a konstruktornak: ```php -Expect::scalar()->castTo('string'); +class Info +{ + public function __construct( + public bool $processRefund, + public int $refundAmount, + ) { + } +} + +// létrehozza $obj = new Info(processRefund: ..., refundAmount: ...) ``` -A natív PHP-típusok mellett osztályokba is átvihetők: +A típuskonverzió skaláris paraméterrel kombinálva létrehoz egy objektumot, és az értéket egyetlen paraméterként adja át a konstruktornak: ```php -Expect::scalar()->castTo('AddressEntity'); +Expect::string()->castTo(DateTime::class); +// létrehozza new DateTime(...) ``` -Normalizálás: before() .[#toc-normalization-before] ---------------------------------------------------- +Normalizálás: before() +---------------------- -Magát az érvényesítést megelőzően az adatok normalizálhatók a `before()` módszerrel. Példaként legyen egy olyan elemünk, amelynek egy stringekből álló tömbnek kell lennie (pl. `['a', 'b', 'c']`), de a bemenetet egy karakterlánc formájában kapja meg `a b c`: +Maga a validáció előtt az adatokat normalizálhatjuk a `before()` metódussal. Példaként említsünk egy elemet, amelynek stringek tömbjének kell lennie (például `['a', 'b', 'c']`), de bemenetként `a b c` string formátumot fogad el: ```php $explode = fn($v) => explode(' ', $v); @@ -440,7 +491,48 @@ $schema = Expect::arrayOf('string') ->before($explode); $normalized = $processor->process($schema, 'a b c'); -// OK, visszaadja ['a', 'b', 'c'] +// OK és visszaadja ['a', 'b', 'c'] +``` + + +Leképezés objektumokra: from() +------------------------------ + +A struktúra sémáját legeneráltathatjuk egy osztályból. Példa: + +```php +class Config +{ + public string $name; + public string|null $password; + public bool $admin = false; +} + +$schema = Expect::from(new Config); + +$data = [ + 'name' => 'franta', +]; + +$normalized = $processor->process($schema, $data); +// $normalized instanceof Config +// $normalized = {'name' => 'franta', 'password' => null, 'admin' => false} ``` -{{leftbar: nette:@menu-topics}} +Támogatottak a névtelen osztályok is: + +```php +$schema = Expect::from(new class { + public string $name; + public ?string $password; + public bool $admin = false; +}); +``` + +Mivel az osztálydefinícióból nyert információk nem feltétlenül elegendőek, a második paraméterrel kiegészítheti az elemeket saját sémával: + +```php +$schema = Expect::from(new Config, [ + 'name' => Expect::string()->pattern('\w:.*'), +]); +``` diff --git a/schema/hu/@meta.texy b/schema/hu/@meta.texy new file mode 100644 index 0000000000..c00a2158aa --- /dev/null +++ b/schema/hu/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette dokumentáció}} +{{leftbar: nette:@menu-topics}} diff --git a/schema/it/@home.texy b/schema/it/@home.texy index 8904af0559..0cdb102f8c 100644 --- a/schema/it/@home.texy +++ b/schema/it/@home.texy @@ -1,8 +1,8 @@ -Schema: Convalida dei dati -************************** +Nette Schema +************ .[perex] -Una libreria pratica per la validazione e la normalizzazione delle strutture di dati rispetto a un determinato schema, con un'API intelligente e di facile comprensione. +Una libreria pratica per la validazione e la normalizzazione delle strutture dati rispetto a uno schema dato con un'API intelligente e comprensibile. Installazione: @@ -11,12 +11,12 @@ composer require nette/schema ``` -Uso di base .[#toc-basic-usage] -------------------------------- +Utilizzo di base +---------------- -Nella variabile `$schema` abbiamo uno schema di validazione (cosa significa esattamente e come crearlo lo diremo più avanti) e nella variabile `$data` abbiamo una struttura di dati che vogliamo validare e normalizzare. Può trattarsi, ad esempio, di dati inviati dall'utente tramite un'API, un file di configurazione, ecc. +Nella variabile `$schema` abbiamo lo schema di validazione (cosa significa esattamente e come creare tale schema lo diremo tra poco) e nella variabile `$data` la struttura dati che vogliamo validare e normalizzare. Può trattarsi, ad esempio, di dati inviati dall'utente tramite un'interfaccia API, un file di configurazione, ecc. -Il compito è gestito dalla classe [api:Nette\Schema\Processor], che elabora l'input e restituisce i dati normalizzati o lancia un'eccezione [api:Nette\Schema\ValidationException] in caso di errore. +Il compito è gestito dalla classe [api:Nette\Schema\Processor], che elabora l'input e restituisce i dati normalizzati oppure, in caso di errore, lancia un'eccezione [api:Nette\Schema\ValidationException]. ```php $processor = new Nette\Schema\Processor; @@ -24,17 +24,17 @@ $processor = new Nette\Schema\Processor; try { $normalized = $processor->process($schema, $data); } catch (Nette\Schema\ValidationException $e) { - echo 'Data is invalid: ' . $e->getMessage(); + echo 'I dati non sono validi: ' . $e->getMessage(); } ``` -Il metodo `$e->getMessages()` restituisce un array di tutte le stringhe di messaggi e `$e->getMessageObjects()` restituisce tutti i messaggi come oggetti "Nette\Schema\Message":https://api.nette.org/schema/master/Nette/Schema/Message.html. +Il metodo `$e->getMessages()` restituisce un array di tutti i messaggi come stringhe e `$e->getMessageObjects()` restituisce tutti i messaggi come oggetti "Nette\Schema\Message":https://api.nette.org/schema/master/Nette/Schema/Message.html. -Definizione dello schema .[#toc-defining-schema] ------------------------------------------------- +Definizione dello schema +------------------------ -Creiamo ora uno schema. Per definirlo, si usa la classe [api:Nette\Schema\Expect]. In pratica, si definiscono le aspettative su come devono apparire i dati. Diciamo che i dati di input devono essere una struttura (ad esempio un array) contenente elementi `processRefund` di tipo bool e `refundAmount` di tipo int. +E ora creiamo lo schema. Per definirlo si usa la classe [api:Nette\Schema\Expect], definiamo in pratica le aspettative su come devono apparire i dati. Diciamo che i dati di input devono formare una struttura (ad esempio un array) contenente gli elementi `processRefund` di tipo bool e `refundAmount` di tipo int. ```php use Nette\Schema\Expect; @@ -45,9 +45,9 @@ $schema = Expect::structure([ ]); ``` -La definizione dello schema appare chiara, anche se la si vede per la prima volta. +Crediamo che la definizione dello schema appaia comprensibile, anche se la vedi per la prima volta. -Inviamo i seguenti dati per la convalida: +Inviamo i seguenti dati per la validazione: ```php $data = [ @@ -55,10 +55,10 @@ $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // OK, passa +$normalized = $processor->process($schema, $data); // OK, passa la validazione ``` -L'output, cioè il valore `$normalized`, è l'oggetto `stdClass`. Se vogliamo che l'output sia un array, aggiungiamo un cast allo schema `Expect::structure([...])->castTo('array')`. +L'output, ovvero il valore `$normalized`, è un oggetto `stdClass`. Se volessimo che l'output fosse un array, aggiungeremmo allo schema il cast `Expect::structure([...])->castTo('array')`. Tutti gli elementi della struttura sono opzionali e hanno un valore predefinito `null`. Esempio: @@ -67,15 +67,15 @@ $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // OK, passa +$normalized = $processor->process($schema, $data); // OK, passa la validazione // $normalized = {'processRefund' => null, 'refundAmount' => 17} ``` -Il fatto che il valore predefinito sia `null` non significa che venga accettato nei dati di input `'processRefund' => null`. No, l'input deve essere booleano, cioè solo `true` o `false`. Dovremmo consentire esplicitamente `null` tramite `Expect::bool()->nullable()`. +Il fatto che il valore predefinito sia `null` non significa che sarebbe accettato nei dati di input `'processRefund' => null`. No, l'input deve essere un booleano, quindi solo `true` o `false`. Dovremmo consentire `null` esplicitamente usando `Expect::bool()->nullable()`. -Un elemento può essere reso obbligatorio utilizzando `Expect::bool()->required()`. Cambiamo il valore predefinito in `false` usando `Expect::bool()->default(false)` o a breve usando `Expect::bool(false)`. +Un elemento può essere reso obbligatorio con `Expect::bool()->required()`. Cambiamo il valore predefinito ad esempio in `false` con `Expect::bool()->default(false)` o abbreviato `Expect::bool(false)`. -E se volessimo accettare `1` and `0` oltre ai booleani? Allora elenchiamo i valori consentiti, che normalizzeremo anch'essi a booleani: +E se volessimo accettare anche `1` e `0` oltre al booleano? Allora specifichiamo un elenco di valori, che inoltre facciamo normalizzare in booleano: ```php $schema = Expect::structure([ @@ -87,13 +87,13 @@ $normalized = $processor->process($schema, $data); is_bool($normalized->processRefund); // true ``` -Ora si conoscono le basi di come viene definito lo schema e come si comportano i singoli elementi della struttura. Ora mostreremo quali sono gli altri elementi che possono essere utilizzati nella definizione di uno schema. +Ora conosci le basi di come si definisce uno schema e come si comportano i singoli elementi della struttura. Ora mostreremo quali altri elementi possono essere utilizzati nella definizione dello schema. -Tipi di dati: tipo() .[#toc-data-types-type] --------------------------------------------- +Tipi di dati: type() +-------------------- -Tutti i tipi di dati standard di PHP possono essere elencati nello schema: +Nello schema è possibile specificare tutti i tipi di dati PHP standard: ```php Expect::string($default = null) @@ -104,21 +104,21 @@ Expect::null() Expect::array($default = []) ``` -E poi tutti i tipi [supportati dai validatori |utils:validators#Expected Types] tramite `Expect::type('scalar')` o `Expect::scalar()` abbreviato. Sono accettati anche nomi di classi o interfacce, ad esempio `Expect::type('AddressEntity')`. +E inoltre tutti i tipi [supportati dalla classe Validators |utils:validators#Tipi attesi], ad esempio `Expect::type('scalar')` o abbreviato `Expect::scalar()`. Anche nomi di classi o interfacce, ad esempio `Expect::type('AddressEntity')`. -Si può anche usare la notazione unione: +È possibile utilizzare anche la notazione union: ```php Expect::type('bool|string|array') ``` -Il valore predefinito è sempre `null`, tranne che per `array` e `list`, dove è un array vuoto. (Un elenco è un array indicizzato in ordine crescente di chiavi numeriche a partire da zero, cioè un array non associativo). +Il valore predefinito è sempre `null` ad eccezione di `array` e `list`, dove è un array vuoto. (List è un array indicizzato secondo una serie crescente di chiavi numeriche a partire da zero, cioè un array non associativo). -Array di valori: arrayOf() listOf() .[#toc-array-of-values-arrayof-listof] --------------------------------------------------------------------------- +Array di valori: arrayOf() listOf() +----------------------------------- -L'array è una struttura troppo generica, è più utile specificare esattamente quali elementi può contenere. Ad esempio, un array i cui elementi possono essere solo stringhe: +Un array rappresenta una struttura troppo generica, è più utile specificare quali elementi può contenere esattamente. Ad esempio, un array i cui elementi possono essere solo stringhe: ```php $schema = Expect::arrayOf('string'); @@ -128,16 +128,16 @@ $processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK $processor->process($schema, ['key' => 123]); // ERRORE: 123 non è una stringa ``` -Il secondo parametro può essere utilizzato per specificare le chiavi (dalla versione 1.2): +Con il secondo parametro è possibile specificare le chiavi (dalla versione 1.2): ```php $schema = Expect::arrayOf('string', 'int'); $processor->process($schema, ['hello', 'world']); // OK -$processor->process($schema, ['a' => 'ciao']); // ERRORE: 'a' non è int +$processor->process($schema, ['a' => 'hello']); // ERRORE: 'a' non è un int ``` -L'elenco è un array indicizzato: +List è un array indicizzato: ```php $schema = Expect::listOf('string'); @@ -145,22 +145,22 @@ $schema = Expect::listOf('string'); $processor->process($schema, ['a', 'b']); // OK $processor->process($schema, ['a', 123]); // ERRORE: 123 non è una stringa $processor->process($schema, ['key' => 'a']); // ERRORE: non è una lista -$processor->process($schema, [1 => 'a', 0 => 'b']); // ERRORE: non è un elenco +$processor->process($schema, [1 => 'a', 0 => 'b']); // ERRORE: anche questo non è una lista ``` -Il parametro può anche essere uno schema, quindi possiamo scrivere: +Il parametro può essere anche uno schema, quindi possiamo scrivere: ```php Expect::arrayOf(Expect::bool()) ``` -Il valore predefinito è un array vuoto. Se si specifica un valore predefinito, questo verrà unito ai dati passati. Questo può essere disabilitato usando `mergeDefaults(false)` (dalla versione 1.1). +Il valore predefinito è un array vuoto. Se specifichi un valore predefinito, verrà unito ai dati passati. Questo può essere disattivato con `mergeDefaults(false)` (dalla versione 1.1). -Enumerazione: anyOf() .[#toc-enumeration-anyof] ------------------------------------------------ +Enumerazione: anyOf() +--------------------- -`anyOf()` è un insieme di valori o schemi che un valore può essere. Ecco come scrivere un array di elementi che possono essere `'a'`, `true`, o `null`: +`anyOf()` rappresenta un elenco di valori o schemi che un valore può assumere. In questo modo scriviamo un array di elementi che possono essere `'a'`, `true` o `null`: ```php $schema = Expect::listOf( @@ -168,10 +168,10 @@ $schema = Expect::listOf( ); $processor->process($schema, ['a', true, null, 'a']); // OK -$processor->process($schema, ['a', false]); // ERRORE: false non appartiene a questa categoria +$processor->process($schema, ['a', false]); // ERRORE: false non appartiene lì ``` -Anche gli elementi dell'enumerazione possono essere schemi: +Gli elementi dell'enumerazione possono essere anche schemi: ```php $schema = Expect::listOf( @@ -182,24 +182,24 @@ $processor->process($schema, ['foo', true, null, 'bar']); // OK $processor->process($schema, [123]); // ERRORE ``` -Il metodo `anyOf()` accetta le varianti come parametri individuali, non come array. Per passargli un array di valori, utilizzare l'operatore di spacchettamento `anyOf(...$variants)`. +Il metodo `anyOf()` accetta le varianti come parametri singoli, non un array. Se vuoi passargli un array di valori, usa l'operatore unpacking `anyOf(...$variants)`. -Il valore predefinito è `null`. Utilizzare il metodo `firstIsDefault()` per rendere il primo elemento il valore predefinito: +Il valore predefinito è `null`. Con il metodo `firstIsDefault()` rendiamo il primo elemento predefinito: ```php -// il valore predefinito è 'ciao' +// il predefinito è 'hello' Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault(); ``` -Strutture .[#toc-structures] ----------------------------- +Strutture +--------- -Le strutture sono oggetti con chiavi definite. Ciascuna di queste coppie chiave => valore viene definita "proprietà": +Le strutture sono oggetti con chiavi definite. Ogni coppia chiave => valore è indicata come "proprietà". -Le strutture accettano array e oggetti e restituiscono oggetti `stdClass` (a meno che non si cambi con `castTo('array')`, ecc.). +Le strutture accettano array e oggetti e restituiscono oggetti `stdClass`. -Per impostazione predefinita, tutte le proprietà sono opzionali e hanno un valore predefinito di `null`. È possibile definire proprietà obbligatorie utilizzando `required()`: +Per impostazione predefinita, tutte le proprietà sono opzionali e hanno un valore predefinito `null`. Puoi definire proprietà obbligatorie usando `required()`: ```php $schema = Expect::structure([ @@ -214,7 +214,7 @@ $processor->process($schema, ['required' => 'foo']); // OK, restituisce {'required' => 'foo', 'optional' => null} ``` -Se non si vuole che vengano emesse proprietà con un valore predefinito, utilizzare `skipDefaults()`: +Se non vuoi avere nell'output proprietà con il valore predefinito, usa `skipDefaults()`: ```php $schema = Expect::structure([ @@ -226,7 +226,7 @@ $processor->process($schema, ['required' => 'foo']); // OK, restituisce {'required' => 'foo'} ``` -Sebbene `null` sia il valore predefinito della proprietà `optional`, non è consentito nei dati di input (il valore deve essere una stringa). Le proprietà che accettano `null` sono definite con `nullable()`: +Sebbene `null` sia il valore predefinito della proprietà `optional`, non è consentito nei dati di input (il valore deve essere una stringa). Definiamo proprietà che accettano `null` usando `nullable()`: ```php $schema = Expect::structure([ @@ -235,13 +235,15 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['optional' => null]); -// ERRORE: 'optional' si aspetta di essere una stringa, ma viene dato null. +// ERRORE: 'optional' si aspetta di essere string, null dato. $processor->process($schema, ['nullable' => null]); // OK, restituisce {'optional' => null, 'nullable' => null} ``` -Per impostazione predefinita, non ci possono essere elementi extra nei dati di input: +Il metodo `getShape()` restituisce un array di tutte le proprietà della struttura. + +Per impostazione predefinita, non possono esserci elementi aggiuntivi nei dati di input: ```php $schema = Expect::structure([ @@ -249,10 +251,10 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['additional' => 1]); -// ERRORE: elemento 'additional' inatteso +// ERRORE: Elemento inaspettato 'additional' ``` -Che possiamo cambiare con `otherItems()`. Come parametro, specificheremo lo schema per ogni elemento extra: +Possiamo cambiarlo usando `otherItems()`. Come parametro specifichiamo lo schema secondo cui verranno validati gli elementi aggiuntivi: ```php $schema = Expect::structure([ @@ -263,78 +265,116 @@ $processor->process($schema, ['additional' => 1]); // OK $processor->process($schema, ['additional' => true]); // ERRORE ``` +Puoi creare una nuova struttura derivandola da un'altra usando `extend()`: + +```php +$dog = Expect::structure([ + 'name' => Expect::string(), + 'age' => Expect::int(), +]); + +$dogWithBreed = $dog->extend([ + 'breed' => Expect::string(), +]); +``` + -Deprecazioni .[#toc-deprecations] ---------------------------------- +Array .{data-version:1.3.2} +--------------------------- -È possibile deprecare una proprietà utilizzando il metodo `deprecated([string $message])` . Gli avvisi di deprecazione sono restituiti da `$processor->getWarnings()`: +Array con chiavi definite. Vale per esso tutto ciò che vale per le [#strutture]. + +```php +$schema = Expect::array([ + 'required' => Expect::string()->required(), + 'optional' => Expect::string(), // il valore predefinito è null +]); +``` + +È possibile definire anche un array indicizzato, noto come tupla: + +```php +$schema = Expect::array([ + Expect::int(), + Expect::string(), + Expect::bool(), +]); + +$processor->process($schema, [1, 'hello', true]); // OK +``` + + +Proprietà deprecate +------------------- + +Puoi contrassegnare una proprietà come deprecata usando il metodo `deprecated([string $message])`. Le informazioni sulla fine del supporto vengono restituite tramite `$processor->getWarnings()`: ```php $schema = Expect::structure([ - 'old' => Expect::int()->deprecated('L'elemento %path% è deprecato'), + 'old' => Expect::int()->deprecated('L\'elemento %path% è deprecato'), ]); $processor->process($schema, ['old' => 1]); // OK -$processor->getWarnings(); // ["L'elemento 'old' è deprecato"]. +$processor->getWarnings(); // ["L'elemento 'old' è deprecato"] ``` -Intervalli: min() max() .[#toc-ranges-min-max] ----------------------------------------------- +Intervalli: min() max() +----------------------- -Utilizzate `min()` e `max()` per limitare il numero di elementi degli array: +Con `min()` e `max()` è possibile limitare il numero di elementi negli array: ```php // array, almeno 10 elementi, massimo 20 elementi Expect::array()->min(10)->max(20); ``` -Per le stringhe, limitare la loro lunghezza: +Nelle stringhe limitare la loro lunghezza: ```php // stringa, lunga almeno 10 caratteri, massimo 20 caratteri Expect::string()->min(10)->max(20); ``` -Per i numeri, limitarne il valore: +Nei numeri limitare il loro valore: ```php -// intero, compreso tra 10 e 20 +// numero intero, tra 10 e 20 inclusi Expect::int()->min(10)->max(20); ``` -Naturalmente, è possibile citare solo `min()`, o solo `max()`: +Naturalmente è possibile specificare solo `min()` o solo `max()`: ```php -// stringa, massimo 20 caratteri +// stringa massimo 20 caratteri Expect::string()->max(20); ``` -Espressioni regolari: pattern() .[#toc-regular-expressions-pattern] -------------------------------------------------------------------- +Espressioni regolari: pattern() +------------------------------- -Utilizzando `pattern()`, è possibile specificare un'espressione regolare a cui deve corrispondere l'intera stringa di input (cioè come se fosse avvolta da caratteri `^` a `$`): +Con `pattern()` è possibile specificare un'espressione regolare a cui deve corrispondere **l'intera** stringa di input (cioè come se fosse racchiusa tra i caratteri `^` e `$`): ```php -// solo 9 cifre +// esattamente 9 numeri Expect::string()->pattern('\d{9}'); ``` -Asserzioni personalizzate: assert() .[#toc-custom-assertions-assert] --------------------------------------------------------------------- +Restrizioni personalizzate: assert() +------------------------------------ -È possibile aggiungere qualsiasi altra restrizione utilizzando `assert(callable $fn)`. +Qualsiasi altra restrizione può essere specificata usando `assert(callable $fn)`. ```php $countIsEven = fn($v) => count($v) % 2 === 0; $schema = Expect::arrayOf('string') - ->assert($countIsEven); // il conteggio deve essere pari + ->assert($countIsEven); // il numero deve essere pari $processor->process($schema, ['a', 'b']); // OK -$processor->process($schema, ['a', 'b', 'c']); // ERRORE: 3 non è pari +$processor->process($schema, ['a', 'b', 'c']); // ERRORE: 3 non è un numero pari ``` Oppure @@ -343,95 +383,106 @@ Oppure Expect::string()->assert('is_file'); // il file deve esistere ``` -È possibile aggiungere la propria descrizione per ogni asserzione. Sarà parte del messaggio di errore. +Ad ogni restrizione puoi aggiungere una descrizione personalizzata. Questa farà parte del messaggio di errore. ```php $schema = Expect::arrayOf('string') - ->assert($countIsEven, 'Elementi pari nell'array'); + ->assert($countIsEven, 'Elementi pari nell\'array'); $processor->process($schema, ['a', 'b', 'c']); // Asserzione fallita "Elementi pari nell'array" per l'elemento con valore array. ``` -Il metodo può essere richiamato più volte per aggiungere altre asserzioni. +Il metodo può essere chiamato ripetutamente per aggiungere più restrizioni. Può essere intervallato con chiamate a `transform()` e `castTo()`. -Mappatura a oggetti: from() .[#toc-mapping-to-objects-from] ------------------------------------------------------------ +Trasformazioni: transform() .{data-version:1.2.5} +------------------------------------------------- -È possibile generare uno schema di struttura dalla classe. Esempio: +I dati validati con successo possono essere modificati utilizzando una funzione personalizzata: ```php -class Config -{ - public string $name; - public string|null $password; - public bool $admin = false; -} - -$schema = Expect::from(new Config); +// conversione in maiuscolo: +Expect::string()->transform(fn(string $s) => strtoupper($s)); +``` -$data = [ - 'name' => 'jeff', -]; +Il metodo può essere chiamato ripetutamente per aggiungere più trasformazioni. Può essere intervallato con chiamate a `assert()` e `castTo()`. Le operazioni vengono eseguite nell'ordine in cui sono dichiarate: -$normalized = $processor->process($schema, $data); -// $normalized instanceof Config -// $normalized = {'name' => 'jeff', 'password' => null, 'admin' => false} +```php +Expect::type('string|int') + ->castTo('string') + ->assert('ctype_lower', 'Tutti i caratteri devono essere minuscoli') + ->transform(fn(string $s) => strtoupper($s)); // conversione in maiuscolo ``` -Se si utilizza PHP 7.4 o superiore, si possono usare i tipi nativi: +Il metodo `transform()` può contemporaneamente trasformare e validare il valore. Questo è spesso più semplice e meno duplicato rispetto alla concatenazione di `transform()` e `assert()`. A tal fine, la funzione riceve un oggetto [Context |api:Nette\Schema\Context] con il metodo `addError()`, che può essere utilizzato per aggiungere informazioni sui problemi di validazione: ```php -class Config -{ - public string $name; - public ?string $password; - public bool $admin = false; -} +Expect::string() + ->transform(function (string $s, Nette\Schema\Context $context) { + if (!ctype_lower($s)) { + $context->addError('Tutti i caratteri devono essere minuscoli', 'my.case.error'); + return null; + } -$schema = Expect::from(new Config); + return strtoupper($s); + }); ``` -Sono supportate anche le classi anonime: + +Casting: castTo() +----------------- + +I dati validati con successo possono essere convertiti: ```php -$schema = Expect::from(new class { - public string $name; - public ?string $password; - public bool $admin = false; -}); +Expect::scalar()->castTo('string'); ``` -Poiché le informazioni ottenute dalla definizione della classe potrebbero non essere sufficienti, è possibile aggiungere uno schema personalizzato per gli elementi con il secondo parametro: +Oltre ai tipi PHP nativi, è possibile eseguire il cast anche a classi. Si distingue se si tratta di una classe semplice senza costruttore o di una classe con costruttore. Se la classe non ha un costruttore, viene creata la sua istanza e tutti gli elementi della struttura vengono scritti nelle proprietà: ```php -$schema = Expect::from(new Config, [ - 'name' => Expect::string()->pattern('\w:.*'), -]); -``` +class Info +{ + public bool $processRefund; + public int $refundAmount; +} +Expect::structure([ + 'processRefund' => Expect::bool(), + 'refundAmount' => Expect::int(), +])->castTo(Info::class); -Casting: castTo() .[#toc-casting-castto] ----------------------------------------- +// crea '$obj = new Info' e scrive in $obj->processRefund e $obj->refundAmount +``` -I dati convalidati con successo possono essere lanciati: +Se la classe ha un costruttore, gli elementi della struttura vengono passati come parametri nominati al costruttore: ```php -Expect::scalar()->castTo('string'); +class Info +{ + public function __construct( + public bool $processRefund, + public int $refundAmount, + ) { + } +} + +// crea $obj = new Info(processRefund: ..., refundAmount: ...) ``` -Oltre ai tipi nativi di PHP, è possibile eseguire il cast su classi: +Il casting in combinazione con un parametro scalare crea un oggetto e passa il valore come unico parametro al costruttore: ```php -Expect::scalar()->castTo('AddressEntity'); +Expect::string()->castTo(DateTime::class); +// crea new DateTime(...) ``` -Normalizzazione: before() .[#toc-normalization-before] ------------------------------------------------------- +Normalizzazione: before() +------------------------- -Prima della validazione vera e propria, i dati possono essere normalizzati con il metodo `before()`. A titolo di esempio, abbiamo un elemento che deve essere un array di stringhe (es. `['a', 'b', 'c']`), ma che riceve un input sotto forma di stringa `a b c`: +Prima della validazione stessa, i dati possono essere normalizzati usando il metodo `before()`. Come esempio, prendiamo un elemento che deve essere un array di stringhe (ad esempio `['a', 'b', 'c']`), ma accetta input sotto forma di stringa `a b c`: ```php $explode = fn($v) => explode(' ', $v); @@ -440,7 +491,48 @@ $schema = Expect::arrayOf('string') ->before($explode); $normalized = $processor->process($schema, 'a b c'); -// OK, restituisce ['a', 'b', 'c'] +// OK e restituisce ['a', 'b', 'c'] ``` -{{leftbar: nette:@menu-topics}} + +Mappatura su oggetti: from() +---------------------------- + +Possiamo far generare lo schema della struttura da una classe. Esempio: + +```php +class Config +{ + public string $name; + public string|null $password; + public bool $admin = false; +} + +$schema = Expect::from(new Config); + +$data = [ + 'name' => 'franta', +]; + +$normalized = $processor->process($schema, $data); +// $normalized instanceof Config +// $normalized = {'name' => 'franta', 'password' => null, 'admin' => false} +``` + +Sono supportate anche le classi anonime: + +```php +$schema = Expect::from(new class { + public string $name; + public ?string $password; + public bool $admin = false; +}); +``` + +Poiché le informazioni ottenute dalla definizione della classe potrebbero non essere sufficienti, puoi aggiungere uno schema personalizzato agli elementi con il secondo parametro: + +```php +$schema = Expect::from(new Config, [ + 'name' => Expect::string()->pattern('\w:.*'), +]); +``` diff --git a/schema/it/@meta.texy b/schema/it/@meta.texy new file mode 100644 index 0000000000..9d19e7312c --- /dev/null +++ b/schema/it/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Documentazione Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/schema/ja/@home.texy b/schema/ja/@home.texy index 76554d052d..92fe3ce33a 100644 --- a/schema/ja/@home.texy +++ b/schema/ja/@home.texy @@ -1,22 +1,22 @@ -スキーマデータバリデーション -************** +Nette Schema +************ .[perex] -与えられたスキーマに対してデータ構造を検証し、正規化するための実用的なライブラリで、スマートで理解しやすいAPIを備えています。 +与えられたスキーマに対してデータ構造を検証および正規化するための、スマートで理解しやすいAPIを備えた実用的なライブラリ。 -インストール方法 +インストール: ```shell composer require nette/schema ``` -基本的な使い方 .[#toc-basic-usage] ---------------------------- +基本的な使用法 +------- -変数`$schema` には検証スキーマ(これが何を意味し、どのように作成するかは後述します)があり、変数`$data` には検証および正規化したいデータ構造があります。これは例えば、APIや設定ファイルなどを通してユーザーから送られたデータであることができます。 +変数`$schema`には検証スキーマがあり(これが正確に何を意味し、どのように作成するかは後ほど説明します)、変数`$data`には検証および正規化したいデータ構造があります。これは、例えば、APIを介してユーザーから送信されたデータ、設定ファイルなどです。 -このタスクは[api:Nette\Schema\Processor] クラスによって処理され、入力を処理して正規化されたデータを返すか、エラー時に[api:Nette\Schema\ValidationException] 例外を投げます。 +タスクは[api:Nette\Schema\Processor]クラスが担当し、入力を処理して正規化されたデータを返すか、エラーの場合は[api:Nette\Schema\ValidationException]例外をスローします。 ```php $processor = new Nette\Schema\Processor; @@ -24,17 +24,17 @@ $processor = new Nette\Schema\Processor; try { $normalized = $processor->process($schema, $data); } catch (Nette\Schema\ValidationException $e) { - echo 'Data is invalid: ' . $e->getMessage(); + echo 'データは有効ではありません: ' . $e->getMessage(); } ``` -Method`$e->getMessages()` は全てのメッセージ文字列の配列を返し、`$e->getMessageObjects()` は全てのメッセージを"NetteSchema FilterMessage":https://api.nette.org/3.1/Nette/Schema/Message.htmlオブジェクトとして返す。 +`$e->getMessages()`メソッドはすべてのメッセージを文字列の配列として返し、`$e->getMessageObjects()`はすべてのメッセージを"Nette\Schema\Message":https://api.nette.org/schema/master/Nette/Schema/Message.htmlオブジェクトとして返します。 -スキーマの定義 .[#toc-defining-schema] -------------------------------- +スキーマの定義 +------- -そして、今度はスキーマを作成してみましょう。[api:Nette\Schema\Expect] というクラスを使って定義します。実際に、データがどのようなものであるべきかという期待値を定義します。入力データは、bool型の要素`processRefund` とint型の要素`refundAmount` を含む構造体(例えば配列)でなければならないとします。 +そして今、スキーマを作成します。その定義には[api:Nette\Schema\Expect]クラスを使用し、データがどのように見えるべきかの期待を定義します。入力データは、bool型の`processRefund`要素とint型の`refundAmount`要素を含む構造(例えば配列)でなければならないとしましょう。 ```php use Nette\Schema\Expect; @@ -45,9 +45,9 @@ $schema = Expect::structure([ ]); ``` -このようにスキーマを定義することで、初めて見る人でもわかりやすくなったと思います。 +初めて見たとしても、スキーマ定義は理解しやすいと信じています。 -以下のデータを送って検証してみましょう。 +次のデータを検証に送信します: ```php $data = [ @@ -55,27 +55,27 @@ $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // OK, it passes +$normalized = $processor->process($schema, $data); // OK、検証を通過します ``` -出力、つまり値`$normalized` は、オブジェクト`stdClass` です。出力を配列にしたい場合は、スキーマにキャストを追加します。 `Expect::structure([...])->castTo('array')`. +出力、つまり`$normalized`の値は`stdClass`オブジェクトです。出力が配列になるようにしたい場合は、スキーマに`Expect::structure([...])->castTo('array')`という型キャストを追加します。 -構造体のすべての要素はオプションであり,デフォルト値`null` を持ちます.例 +構造のすべての要素はオプションであり、デフォルト値は`null`です。例: ```php $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // OK, it passes +$normalized = $processor->process($schema, $data); // OK、検証を通過します // $normalized = {'processRefund' => null, 'refundAmount' => 17} ``` -例:デフォルト値が`null` であることは、入力データ`'processRefund' => null` で受け入れられることを意味しない。いいえ、入力はブーリアンでなければなりません。つまり、`true` または`false` だけです。`null` を`Expect::bool()->nullable()` 経由で明示的に許可しなければならないでしょう。 +デフォルト値が`null`であることは、入力データで`'processRefund' => null`が受け入れられることを意味するわけではありません。いいえ、入力はブール値、つまり`true`または`false`のみでなければなりません。`null`を許可するには、`Expect::bool()->nullable()`を使用して明示的に許可する必要があります。 -項目は`Expect::bool()->required()` を使って必須とすることができる。`Expect::bool()->default(false)` を使ってデフォルト値を`false` に変更したり、`Expect::bool(false)` を使ってデフォルト値を間もなく変更したりします。 +項目は`Expect::bool()->required()`を使用して必須にすることができます。デフォルト値を例えば`false`に変更するには、`Expect::bool()->default(false)`または短縮して`Expect::bool(false)`を使用します。 -そして、ブーリアン以外に`1` and `0` も受け入れたい場合はどうすればよいでしょうか。そこで、許容される値をリストアップし、これもbooleanに正規化することにします。 +ブール値に加えて`1`と`0`も受け入れたい場合はどうでしょうか? その場合、さらにブール値に正規化させる値のリストを指定します: ```php $schema = Expect::structure([ @@ -87,13 +87,13 @@ $normalized = $processor->process($schema, $data); is_bool($normalized->processRefund); // true ``` -これでスキーマがどのように定義され、構造の個々の要素がどのように動作するかの基本がわかったと思います。これから、スキーマを定義する際に、他のすべての要素がどのように使用できるかを紹介します。 +これで、スキーマの定義方法と構造の個々の要素がどのように動作するかの基本を理解しました。次に、スキーマ定義で使用できる他のすべての要素を示します。 -データ型:type() .[#toc-data-types-type] ------------------------------------ +データ型: type() +------------ -PHP の標準的なデータ型は、すべてスキーマにリストアップすることができます。 +スキーマでは、すべての標準PHPデータ型を指定できます: ```php Expect::string($default = null) @@ -104,63 +104,63 @@ Expect::null() Expect::array($default = []) ``` -そして、[Validators がサポートする |utils:en:validators#Expected Types]すべての型を`Expect::type('scalar')` あるいは`Expect::scalar()` のように省略した形式で指定します。また、クラス名やインターフェイス名も使用できます。例えば`Expect::type('AddressEntity')` のようになります。 +さらに、[Validatorsクラスでサポートされているすべての型 |utils:validators#期待される型]、例えば`Expect::type('scalar')`または短縮して`Expect::scalar()`。クラス名やインターフェース名も、例えば`Expect::type('AddressEntity')`。 -また、ユニオン表記も使用できます。 +union表記も使用できます: ```php Expect::type('bool|string|array') ``` -デフォルト値は,`array` と`list` を除いて,常に`null` で,これは空の配列である.(リストは,ゼロから数値キーの昇順でインデックスが付けられた配列,つまり非結合型配列です). +デフォルト値は常に`null`ですが、`array`と`list`の場合は空の配列です。(リストは、ゼロから始まる昇順の数値キーでインデックス付けされた配列、つまり非連想配列です)。 -値の配列: arrayOf() listOf() .[#toc-array-of-values-arrayof-listof] ---------------------------------------------------------------- +値の配列: arrayOf() listOf() +------------------------ -配列はあまりにも一般的な構造なので、どのような要素を含むことができるかを正確に指定する方が便利です。例えば、要素が文字列のみ可能な配列。 +配列はあまりにも一般的な構造であるため、どのような要素を含めることができるかを正確に指定する方が便利です。例えば、要素が文字列のみである配列: ```php $schema = Expect::arrayOf('string'); $processor->process($schema, ['hello', 'world']); // OK $processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK -$processor->process($schema, ['key' => 123]); // ERROR: 123 is not a string +$processor->process($schema, ['key' => 123]); // エラー: 123 は文字列ではありません ``` -2 番目のパラメータで、キーを指定することができます (バージョン 1.2 以降)。 +2番目のパラメータでキーを指定できます(バージョン1.2以降): ```php $schema = Expect::arrayOf('string', 'int'); $processor->process($schema, ['hello', 'world']); // OK -$processor->process($schema, ['a' => 'hello']); // ERROR: 'a' is not int +$processor->process($schema, ['a' => 'hello']); // エラー: 'a' は int ではありません ``` -リストはインデックス付き配列となります。 +リストはインデックス付き配列です: ```php $schema = Expect::listOf('string'); $processor->process($schema, ['a', 'b']); // OK -$processor->process($schema, ['a', 123]); // ERROR: 123 is not a string -$processor->process($schema, ['key' => 'a']); // ERROR: is not a list -$processor->process($schema, [1 => 'a', 0 => 'b']); // ERROR: is not a list +$processor->process($schema, ['a', 123]); // エラー: 123 は文字列ではありません +$processor->process($schema, ['key' => 'a']); // エラー: リストではありません +$processor->process($schema, [1 => 'a', 0 => 'b']); // エラー: これもリストではありません ``` -パラメータはスキーマでもよいので、次のように書きます。 +パラメータはスキーマにすることもできるので、次のように記述できます: ```php Expect::arrayOf(Expect::bool()) ``` -デフォルト値は空の配列です。default value を指定すると、渡されたデータにマージされます。これを無効にするには、`mergeDefaults(false)` (バージョン 1.1 以降) を使用します。 +デフォルト値は空の配列です。デフォルト値を指定すると、渡されたデータとマージされます。これは`mergeDefaults(false)`で無効にできます(バージョン1.1以降)。 -列挙:anyOf() .[#toc-enumeration-anyof] ------------------------------------- +列挙: anyOf() +----------- -`anyOf()` は、ある値が取りうる値やスキーマの集合です。以下は、`'a'`,`true`,`null` のいずれかになりうる要素の配列の書き方です。 +`anyOf()`は、値が取ることができる値またはスキーマのリストを表します。このようにして、要素が`'a'`、`true`、または`null`のいずれかである配列を記述します: ```php $schema = Expect::listOf( @@ -168,10 +168,10 @@ $schema = Expect::listOf( ); $processor->process($schema, ['a', true, null, 'a']); // OK -$processor->process($schema, ['a', false]); // ERROR: false does not belong there +$processor->process($schema, ['a', false]); // エラー: false はそこに含まれません ``` -列挙された要素はスキーマになることもできます。 +列挙の要素はスキーマにすることもできます: ```php $schema = Expect::listOf( @@ -179,42 +179,42 @@ $schema = Expect::listOf( ); $processor->process($schema, ['foo', true, null, 'bar']); // OK -$processor->process($schema, [123]); // ERROR +$processor->process($schema, [123]); // エラー ``` -`anyOf()` メソッドはバリアントを配列としてではなく、個々のパラメータとして受け取ります。値の配列を渡すには、アンパッキングオペレータ`anyOf(...$variants)` を使ってください。 +`anyOf()`メソッドは、バリアントを個別のパラメータとして受け入れ、配列としては受け入れません。値の配列を渡したい場合は、アンパック演算子`anyOf(...$variants)`を使用します。 -デフォルト値は`null` です。最初の要素をデフォルトにするには、`firstIsDefault()` メソッドを使います。 +デフォルト値は`null`です。`firstIsDefault()`メソッドを使用すると、最初の要素がデフォルトになります: ```php -// default is 'hello' +// デフォルトは 'hello' Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault(); ``` -構造体 .[#toc-structures] ----------------------- +構造 +--------- -構造体は、定義されたキーを持つオブジェクトである。このキー => 値のペアをそれぞれ「プロパティ」と呼びます。 +構造は、定義されたキーを持つオブジェクトです。各キー=>値のペアは「プロパティ」と呼ばれます: -構造体は配列やオブジェクトを受け入れ、オブジェクトを返す`stdClass` (`castTo('array')`, などで変更しない限り)。 +構造は配列とオブジェクトを受け入れ、`stdClass`オブジェクトを返します。 -デフォルトでは、すべてのプロパティはオプションであり、デフォルト値は`null` です。必須プロパティは`required()` を使って定義できます。 +デフォルトでは、すべてのプロパティはオプションであり、デフォルト値は`null`です。必須プロパティは`required()`を使用して定義できます: ```php $schema = Expect::structure([ 'required' => Expect::string()->required(), - 'optional' => Expect::string(), // the default value is null + 'optional' => Expect::string(), // デフォルト値は null ]); $processor->process($schema, ['optional' => '']); -// ERROR: option 'required' is missing +// エラー: オプション 'required' がありません $processor->process($schema, ['required' => 'foo']); -// OK, returns {'required' => 'foo', 'optional' => null} +// OK、{'required' => 'foo', 'optional' => null} を返します ``` -デフォルト値のみを持つプロパティを出力したくない場合は、`skipDefaults()` を使用します。 +出力にデフォルト値を持つプロパティを含めたくない場合は、`skipDefaults()`を使用します: ```php $schema = Expect::structure([ @@ -223,10 +223,10 @@ $schema = Expect::structure([ ])->skipDefaults(); $processor->process($schema, ['required' => 'foo']); -// OK, returns {'required' => 'foo'} +// OK、{'required' => 'foo'} を返します ``` -`null` は`optional` プロパティのデフォルト値であるが、入力データでは許されない(値は文字列でなければならない)。`null` を受け入れるプロパティは、`nullable()` を使って定義します。 +`null`は`optional`プロパティのデフォルト値ですが、入力データでは許可されていません(値は文字列でなければなりません)。`null`を受け入れるプロパティは`nullable()`を使用して定義します: ```php $schema = Expect::structure([ @@ -235,13 +235,15 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['optional' => null]); -// ERROR: 'optional' expects to be string, null given. +// エラー: 'optional' は文字列を期待しますが、null が与えられました。 $processor->process($schema, ['nullable' => null]); -// OK, returns {'optional' => null, 'nullable' => null} +// OK、{'optional' => null, 'nullable' => null} を返します ``` -デフォルトでは、入力データに余分な項目は存在できない。 +構造のすべてのプロパティの配列は`getShape()`メソッドで返されます。 + +デフォルトでは、入力データに余分な項目を含めることはできません: ```php $schema = Expect::structure([ @@ -249,10 +251,10 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['additional' => 1]); -// ERROR: Unexpected item 'additional' +// エラー: 予期しない項目 'additional' ``` -これは`otherItems()` で変更可能です。パラメータとして、各追加要素のスキーマを指定します。 +これは`otherItems()`で変更できます。パラメータとして、余分な要素が検証されるスキーマを指定します: ```php $schema = Expect::structure([ @@ -260,178 +262,227 @@ $schema = Expect::structure([ ])->otherItems(Expect::int()); $processor->process($schema, ['additional' => 1]); // OK -$processor->process($schema, ['additional' => true]); // ERROR +$processor->process($schema, ['additional' => true]); // エラー +``` + +`extend()`を使用して、別の構造から派生して新しい構造を作成できます: + +```php +$dog = Expect::structure([ + 'name' => Expect::string(), + 'age' => Expect::int(), +]); + +$dogWithBreed = $dog->extend([ + 'breed' => Expect::string(), +]); +``` + + +配列 .{data-version:1.3.2} +------------------------ + +定義されたキーを持つ配列。[#構造]に適用されるすべてが適用されます。 + +```php +$schema = Expect::array([ + 'required' => Expect::string()->required(), + 'optional' => Expect::string(), // デフォルト値は null +]); ``` +タプルとして知られるインデックス付き配列も定義できます: -非推奨事項 .[#toc-deprecations] --------------------------- +```php +$schema = Expect::array([ + Expect::int(), + Expect::string(), + Expect::bool(), +]); + +$processor->process($schema, [1, 'hello', true]); // OK +``` -非推奨のプロパティは `deprecated([string $message])`メソッドを使って非推奨にすることができます。非推奨の通知は`$processor->getWarnings()` で返されます。 + +非推奨のプロパティ +--------- + +`deprecated([string $message])`メソッドを使用してプロパティを非推奨としてマークできます。サポート終了に関する情報は`$processor->getWarnings()`によって返されます: ```php $schema = Expect::structure([ - 'old' => Expect::int()->deprecated('The item %path% is deprecated'), + 'old' => Expect::int()->deprecated('項目 %path% は非推奨です'), ]); $processor->process($schema, ['old' => 1]); // OK -$processor->getWarnings(); // ["The item 'old' is deprecated"] +$processor->getWarnings(); // ["項目 'old' は非推奨です"] ``` -範囲: min() max() .[#toc-ranges-min-max] --------------------------------------- +範囲: min() max() +--------------- -`min()` と`max()` を使って、配列の要素数を制限します。 +`min()`と`max()`を使用して、配列の要素数を制限できます: ```php -// array, at least 10 items, maximum 20 items +// 配列、少なくとも10項目、最大20項目 Expect::array()->min(10)->max(20); ``` -文字列の場合は、その長さを制限する。 +文字列の場合、その長さを制限します: ```php -// string, at least 10 characters long, maximum 20 characters +// 文字列、少なくとも10文字、最大20文字 Expect::string()->min(10)->max(20); ``` -数値の場合は、値を制限する。 +数値の場合、その値を制限します: ```php -// integer, between 10 and 20 inclusive +// 整数、10から20まで(両端を含む) Expect::int()->min(10)->max(20); ``` -もちろん、`min()` だけ、あるいは`max()` だけに言及することも可能です。 +もちろん、`min()`のみ、または`max()`のみを指定することも可能です: ```php -// string, maximum 20 characters +// 文字列、最大20文字 Expect::string()->max(20); ``` -正規表現: pattern() .[#toc-regular-expressions-pattern] ---------------------------------------------------- +正規表現: pattern() +--------------- -`pattern()` を使うと、入力文字列の **全体** にマッチしなければならない正規表現を指定できます (つまり、入力文字列が`^` a `$` という文字でくくられたようなものです)。 +`pattern()`を使用して、入力文字列**全体**が一致する必要がある正規表現を指定できます(つまり、`^`と`$`文字で囲まれているかのように): ```php -// just 9 digits +// ちょうど9桁の数字 Expect::string()->pattern('\d{9}'); ``` -カスタムアサーション: assert() .[#toc-custom-assertions-assert] ------------------------------------------------------ +カスタム制約: assert() +---------------- -`assert(callable $fn)` を使って、その他の制限を追加することができます。 +その他の制約は`assert(callable $fn)`を使用して指定します。 ```php $countIsEven = fn($v) => count($v) % 2 === 0; $schema = Expect::arrayOf('string') - ->assert($countIsEven); // the count must be even + ->assert($countIsEven); // 個数は偶数でなければなりません $processor->process($schema, ['a', 'b']); // OK -$processor->process($schema, ['a', 'b', 'c']); // ERROR: 3 is not even +$processor->process($schema, ['a', 'b', 'c']); // エラー: 3 は偶数ではありません ``` または ```php -Expect::string()->assert('is_file'); // the file must exist +Expect::string()->assert('is_file'); // ファイルが存在する必要があります ``` -各アサーションに独自の説明を追加することができます。これはエラーメッセージの一部となります。 +各制約にカスタムの説明を追加できます。これはエラーメッセージの一部になります。 ```php $schema = Expect::arrayOf('string') - ->assert($countIsEven, 'Even items in array'); + ->assert($countIsEven, '配列内の偶数項目'); $processor->process($schema, ['a', 'b', 'c']); -// Failed assertion "Even items in array" for item with value array. +// 値配列を持つ項目のアサーション "配列内の偶数項目" に失敗しました。 ``` -このメソッドは、繰り返しコールしてアサーションを追加することができます。 +このメソッドは繰り返し呼び出して、複数の制約を追加できます。`transform()`および`castTo()`の呼び出しと交互に使用できます。 -オブジェクトへのマッピング: from() .[#toc-mapping-to-objects-from] ------------------------------------------------------ +変換: transform() .{data-version:1.2.5} +------------------------------------- -クラスから構造体スキーマを生成することができます。例 +正常に検証されたデータは、カスタム関数を使用して変更できます: ```php -class Config -{ - public string $name; - public string|null $password; - public bool $admin = false; -} +// 大文字への変換: +Expect::string()->transform(fn(string $s) => strtoupper($s)); +``` -$schema = Expect::from(new Config); +このメソッドは繰り返し呼び出して、複数の変換を追加できます。`assert()`および`castTo()`の呼び出しと交互に使用できます。操作は宣言された順序で実行されます: -$data = [ - 'name' => 'jeff', -]; - -$normalized = $processor->process($schema, $data); -// $normalized instanceof Config -// $normalized = {'name' => 'jeff', 'password' => null, 'admin' => false} +```php +Expect::type('string|int') + ->castTo('string') + ->assert('ctype_lower', 'すべての文字は小文字でなければなりません') + ->transform(fn(string $s) => strtoupper($s)); // 大文字への変換 ``` -PHP 7.4 以降を使用している場合は、ネイティブタイプを使用することができます。 +`transform()`メソッドは、同時に値を変換および検証できます。これは、`transform()`と`assert()`を連鎖させるよりも、多くの場合、より簡単で重複が少なくなります。この目的のために、関数は[Context |api:Nette\Schema\Context]オブジェクトを受け取り、`addError()`メソッドを使用して検証の問題に関する情報を追加できます: ```php -class Config -{ - public string $name; - public ?string $password; - public bool $admin = false; -} +Expect::string() + ->transform(function (string $s, Nette\Schema\Context $context) { + if (!ctype_lower($s)) { + $context->addError('すべての文字は小文字でなければなりません', 'my.case.error'); + return null; + } -$schema = Expect::from(new Config); + return strtoupper($s); + }); ``` -匿名クラスもサポートされています。 + +型キャスト: castTo() +--------------- + +正常に検証されたデータは型キャストできます: ```php -$schema = Expect::from(new class { - public string $name; - public ?string $password; - public bool $admin = false; -}); +Expect::scalar()->castTo('string'); ``` -クラス定義から得られる情報だけでは十分でない場合があるため、2番目のパラメータで要素にカスタムスキーマを追加することができます。 +ネイティブPHP型に加えて、クラスに型キャストすることもできます。これは、コンストラクタのない単純なクラスか、コンストラクタを持つクラスかによって区別されます。クラスにコンストラクタがない場合、そのインスタンスが作成され、構造のすべての要素がプロパティに書き込まれます: ```php -$schema = Expect::from(new Config, [ - 'name' => Expect::string()->pattern('\w:.*'), -]); -``` +class Info +{ + public bool $processRefund; + public int $refundAmount; +} +Expect::structure([ + 'processRefund' => Expect::bool(), + 'refundAmount' => Expect::int(), +])->castTo(Info::class); -キャスト: castTo() .[#toc-casting-castto] -------------------------------------- +// '$obj = new Info' を作成し、$obj->processRefund と $obj->refundAmount に書き込みます +``` -バリデーションに成功したデータをキャストすることができます。 +クラスにコンストラクタがある場合、構造の要素は名前付きパラメータとしてコンストラクタに渡されます: ```php -Expect::scalar()->castTo('string'); +class Info +{ + public function __construct( + public bool $processRefund, + public int $refundAmount, + ) { + } +} + +// $obj = new Info(processRefund: ..., refundAmount: ...) を作成します ``` -PHP のネイティブな型だけでなく、クラスへのキャストも可能です。 +スカラーパラメータと組み合わせた型キャストは、オブジェクトを作成し、値を単一のパラメータとしてコンストラクタに渡します: ```php -Expect::scalar()->castTo('AddressEntity'); +Expect::string()->castTo(DateTime::class); +// new DateTime(...) を作成します ``` -正規化: before() .[#toc-normalization-before] ------------------------------------------- +正規化: before() +------------- -バリデーションの前に、`before()` メソッドを使用してデータを正規化することができます。例として、文字列の配列でなければならない要素(例:³³)があるとします。 `['a', 'b', 'c']`が、文字列`a b c` という形式で入力を受け取るとします。 +実際の検証の前に、`before()`メソッドを使用してデータを正規化できます。例として、文字列の配列(例えば`['a', 'b', 'c']`)でなければならないが、文字列`a b c`の形式で入力を受け入れる要素を挙げます: ```php $explode = fn($v) => explode(' ', $v); @@ -440,7 +491,48 @@ $schema = Expect::arrayOf('string') ->before($explode); $normalized = $processor->process($schema, 'a b c'); -// OK, returns ['a', 'b', 'c'] +// OK で ['a', 'b', 'c'] を返します +``` + + +オブジェクトへのマッピング: from() +--------------------- + +構造スキーマをクラスから生成させることができます。例: + +```php +class Config +{ + public string $name; + public string|null $password; + public bool $admin = false; +} + +$schema = Expect::from(new Config); + +$data = [ + 'name' => 'franta', +]; + +$normalized = $processor->process($schema, $data); +// $normalized instanceof Config +// $normalized = {'name' => 'franta', 'password' => null, 'admin' => false} ``` -{{leftbar: nette:en:@menu-topics}} +匿名クラスもサポートされています: + +```php +$schema = Expect::from(new class { + public string $name; + public ?string $password; + public bool $admin = false; +}); +``` + +クラス定義から取得した情報だけでは不十分な場合があるため、2番目のパラメータを使用して要素にカスタムスキーマを追加できます: + +```php +$schema = Expect::from(new Config, [ + 'name' => Expect::string()->pattern('\w:.*'), +]); +``` diff --git a/schema/ja/@meta.texy b/schema/ja/@meta.texy new file mode 100644 index 0000000000..7d67dcb7b8 --- /dev/null +++ b/schema/ja/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette ドキュメンテーション}} +{{leftbar: nette:@menu-topics}} diff --git a/schema/pl/@home.texy b/schema/pl/@home.texy index 4e9930ca5b..fefa0f6e26 100644 --- a/schema/pl/@home.texy +++ b/schema/pl/@home.texy @@ -1,8 +1,8 @@ -Schema: zatwierdzanie danych -**************************** +Nette Schema +************ .[perex] -Poręczna biblioteka do walidacji i normalizacji struktur danych względem danego schematu z inteligentnym, łatwym do zrozumienia API. +Praktyczna biblioteka do walidacji i normalizacji struktur danych względem danego schematu z inteligentnym, zrozumiałym API. Instalacja: @@ -11,12 +11,12 @@ composer require nette/schema ``` -Zastosowanie podstawowe .[#toc-basic-usage] -------------------------------------------- +Podstawowe użycie +----------------- -W zmiennej `$schema` mamy schemat walidacji (o tym, co to dokładnie znaczy i jak stworzyć taki schemat powiemy później), a w zmiennej `$data` mamy strukturę danych, którą chcemy zwalidować i znormalizować. Mogą to być np. dane przesłane przez użytkownika poprzez API, plik konfiguracyjny itp. +W zmiennej `$schema` mamy schemat walidacji (co to dokładnie oznacza i jak taki schemat utworzyć, powiemy za chwilę), a w zmiennej `$data` strukturę danych, którą chcemy walidować i normalizować. Mogą to być na przykład dane przesłane przez użytkownika przez interfejs API, plik konfiguracyjny itp. -Zadanie to jest obsługiwane przez klasę [api:Nette\Schema\Processor], która przetwarza dane wejściowe i albo zwraca znormalizowane dane, albo w przypadku błędu rzuca wyjątek [api:Nette\Schema\ValidationException]. +Zadanie wykonuje klasa [api:Nette\Schema\Processor], która przetwarza wejście i albo zwraca znormalizowane dane, albo w przypadku błędu rzuca wyjątek [api:Nette\Schema\ValidationException]. ```php $processor = new Nette\Schema\Processor; @@ -24,17 +24,17 @@ $processor = new Nette\Schema\Processor; try { $normalized = $processor->process($schema, $data); } catch (Nette\Schema\ValidationException $e) { - echo 'Data nejsou platná: ' . $e->getMessage(); + echo 'Dane są nieprawidłowe: ' . $e->getMessage(); } ``` -Metoda `$e->getMessages()` zwraca tablicę wszystkich wiadomości jako ciągi znaków, a `$e->getMessageObjects()` zwraca wszystkie wiadomości jako obiekty "Nette\Message":https://api.nette.org/schema/master/Nette/Schema/Message.html. +Metoda `$e->getMessages()` zwraca tablicę wszystkich wiadomości jako ciągi znaków, a `$e->getMessageObjects()` zwraca wszystkie wiadomości jako obiekty [Nette\Schema\Message|https://api.nette.org/schema/master/Nette/Schema/Message.html]. -Definiowanie schematu .[#toc-defining-schema] ---------------------------------------------- +Definiowanie schematu +--------------------- -Teraz stwórzmy schemat. Do jego zdefiniowania służy klasa [api:Nette\Schema\Expect], tak naprawdę definiujemy oczekiwania co do tego, jak powinny wyglądać dane. Powiedzmy, że dane wejściowe muszą być strukturą (na przykład tablicą) zawierającą elementy `processRefund` typu bool oraz `refundAmount` typu int. +A teraz utworzymy schemat. Do jego definiowania służy klasa [api:Nette\Schema\Expect], definiujemy właściwie oczekiwania, jak mają wyglądać dane. Powiedzmy, że dane wejściowe muszą tworzyć strukturę (na przykład tablicę) zawierającą elementy `processRefund` typu bool i `refundAmount` typu int. ```php use Nette\Schema\Expect; @@ -45,9 +45,9 @@ $schema = Expect::structure([ ]); ``` -Wierzymy, że definicja schematu wygląda na łatwą do zrozumienia, nawet jeśli widzisz ją po raz pierwszy. +Wierzymy, że definicja schematu wygląda zrozumiale, nawet jeśli widzisz ją po raz pierwszy. -Prześlemy następujące dane do walidacji: +Prześlemy do walidacji następujące dane: ```php $data = [ @@ -55,10 +55,10 @@ $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // OK, projde validací +$normalized = $processor->process($schema, $data); // OK, przejdzie walidację ``` -Wyjściem, czyli wartością `$normalized`, jest obiekt `stdClass`. Jeśli chcielibyśmy, aby wyjście było tablicą, dodamy nadpisanie schematu `Expect::structure([...])->castTo('array')`. +Wyjściem, czyli wartością `$normalized`, jest obiekt `stdClass`. Jeśli chcielibyśmy, aby wyjściem była tablica, uzupełnimy schemat o rzutowanie `Expect::structure([...])->castTo('array')`. Wszystkie elementy struktury są opcjonalne i mają domyślną wartość `null`. Przykład: @@ -67,15 +67,15 @@ $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // OK, projde validací +$normalized = $processor->process($schema, $data); // OK, przejdzie walidację // $normalized = {'processRefund' => null, 'refundAmount' => 17} ``` -Fakt, że wartością domyślną jest `null`, nie oznacza, że `'processRefund' => null` zostałaby zaakceptowana w danych wejściowych. Nie, dane wejściowe muszą być booleanem, czyli tylko `true` lub `false`. Aby dopuścić `null` musielibyśmy wyraźnie użyć `Expect::bool()->nullable()`. +To, że domyślną wartością jest `null`, nie oznacza, że akceptowałoby się w danych wejściowych `'processRefund' => null`. Nie, wejściem musi być boolean, czyli tylko `true` lub `false`. Pozwolenie na `null` musielibyśmy jawnie określić za pomocą `Expect::bool()->nullable()`. -Wpis może być obowiązkowy za pomocą `Expect::bool()->required()`. Możemy zmienić wartość domyślną na `false` za pomocą `Expect::bool()->default(false)` lub w skrócie `Expect::bool(false)`. +Element można uczynić obowiązkowym za pomocą `Expect::bool()->required()`. Wartość domyślną zmienimy na przykład na `false` za pomocą `Expect::bool()->default(false)` lub skrótowo `Expect::bool(false)`. -A co by było gdybyśmy chcieli oprócz boolean przyjąć `1` a `0`? Następnie wymieniamy wartości, które dodatkowo pozwalamy znormalizować do boolean: +A co gdybyśmy chcieli oprócz boolean akceptować jeszcze `1` i `0`? Wtedy podamy wyliczenie wartości, które dodatkowo pozwolimy znormalizować na boolean: ```php $schema = Expect::structure([ @@ -87,13 +87,13 @@ $normalized = $processor->process($schema, $data); is_bool($normalized->processRefund); // true ``` -Teraz znasz już podstawy definiowania schematu i tego, jak zachowują się elementy struktury. Teraz zobaczymy, jakie pozostałe elementy mogą być użyte podczas definiowania schematu. +Teraz już znasz podstawy tego, jak definiuje się schemat i jak zachowują się poszczególne elementy struktury. Teraz pokażemy, jakie wszystkie inne elementy można użyć przy definicji schematu. -Typy danych: type() .[#toc-data-types-type] -------------------------------------------- +Typy danych: type() +------------------- -Wszystkie standardowe typy danych PHP mogą być wymienione w schemacie: +W schemacie można podać wszystkie standardowe typy danych PHP: ```php Expect::string($default = null) @@ -104,63 +104,63 @@ Expect::null() Expect::array($default = []) ``` -Jak również wszystkie typy [obsługiwane przez klasę Validators |utils:validators#Expected-Types], na przykład `Expect::type('scalar')` lub w skrócie `Expect::scalar()`. Również nazwy klas lub interfejsów, na przykład `Expect::type('AddressEntity')`. +A dalej wszystkie typy, [wspierane przez klasę Validators |utils:validators#Oczekiwane typy], na przykład `Expect::type('scalar')` lub skrótowo `Expect::scalar()`. Także nazwy klas czy interfejsów, na przykład `Expect::type('AddressEntity')`. -Można również zastosować notację unijną: +Można użyć również zapisu union: ```php Expect::type('bool|string|array') ``` -Domyślnie zawsze jest to `null` z wyjątkiem `array` i `list`, gdzie jest to puste pole. (Lista to tablica indeksowana przez rosnący szereg kluczy numerycznych od zera, czyli tablica nieasocjacyjna). +Wartość domyślna to zawsze `null` z wyjątkiem dla `array` i `list`, gdzie jest to pusta tablica. (List to tablica indeksowana według rosnącej serii kluczy numerycznych od zera, czyli tablica nieasocjacyjna). -Tablica wartości: arrayOf() listOf() .[#toc-array-of-values-arrayof-listof] ---------------------------------------------------------------------------- +Tablice wartości: arrayOf() listOf() +------------------------------------ -Tablica jest zbyt ogólną strukturą; bardziej przydatne jest określenie dokładnie, jakie elementy może zawierać. Na przykład tablica, której elementami mogą być tylko ciągi znaków: +Tablica reprezentuje zbyt ogólną strukturę, bardziej użyteczne jest określenie, jakie dokładnie może zawierać elementy. Na przykład tablica, której elementy mogą być tylko ciągami znaków: ```php $schema = Expect::arrayOf('string'); $processor->process($schema, ['hello', 'world']); // OK $processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK -$processor->process($schema, ['key' => 123]); // CHYBA: 123 není string +$processor->process($schema, ['key' => 123]); // BŁĄD: 123 nie jest stringiem ``` -Drugi parametr może być użyty do określenia kluczy (od wersji 1.2): +Drugim parametrem można określić klucze (od wersji 1.2): ```php $schema = Expect::arrayOf('string', 'int'); $processor->process($schema, ['hello', 'world']); // OK -$processor->process($schema, ['a' => 'hello']); // CHYBA: 'a' není int +$processor->process($schema, ['a' => 'hello']); // BŁĄD: 'a' nie jest int ``` -Lista jest tablicą indeksowaną: +List to tablica indeksowana: ```php $schema = Expect::listOf('string'); $processor->process($schema, ['a', 'b']); // OK -$processor->process($schema, ['a', 123]); // ERROR: 123 nie jest ciągiem znaków -$processor->process($schema, ['key' => 'a']); // ERROR: not a list -$processor->process($schema, [1 => 'a', 0 => 'b']); // ERROR: również nie lista +$processor->process($schema, ['a', 123]); // BŁĄD: 123 nie jest stringiem +$processor->process($schema, ['key' => 'a']); // BŁĄD: nie jest listą +$processor->process($schema, [1 => 'a', 0 => 'b']); // BŁĄD: również nie jest listą ``` -Parametr może być również schematem, więc możemy napisać: +Parametrem może być również schemat, możemy więc zapisać: ```php Expect::arrayOf(Expect::bool()) ``` -Domyślną wartością jest puste pole. Jeśli określisz wartość domyślną, zostanie ona połączona z przekazanymi danymi. Można to wyłączyć za pomocą `mergeDefaults(false)` (od wersji 1.1). +Wartość domyślna to pusta tablica. Jeśli podasz wartość domyślną, zostanie ona połączona z przekazanymi danymi. Można to zdezaktywować za pomocą `mergeDefaults(false)` (od wersji 1.1). -Wyliczenie: anyOf() .[#toc-enumeration-anyof] ---------------------------------------------- +Wyliczenie: anyOf() +------------------- -`anyOf()` reprezentuje wyliczenie wartości lub schematów, które może przyjąć dana wartość. W ten sposób piszemy tablicę z elementami, które mogą być albo `'a'`, `true`, albo `null`: +`anyOf()` reprezentuje wyliczenie wartości lub schematów, które może przyjmować wartość. W ten sposób zapiszemy tablicę elementów, które mogą być albo `'a'`, `true` lub `null`: ```php $schema = Expect::listOf( @@ -168,10 +168,10 @@ $schema = Expect::listOf( ); $processor->process($schema, ['a', true, null, 'a']); // OK -$processor->process($schema, ['a', false]); // CHYBA: false tam nepatří +$processor->process($schema, ['a', false]); // BŁĄD: false tu nie pasuje ``` -Elementami wyliczenia mogą być również schematy: +Elementy wyliczenia mogą być również schematami: ```php $schema = Expect::listOf( @@ -179,42 +179,42 @@ $schema = Expect::listOf( ); $processor->process($schema, ['foo', true, null, 'bar']); // OK -$processor->process($schema, [123]); // CHYBA +$processor->process($schema, [123]); // BŁĄD ``` -Metoda `anyOf()` przyjmuje warianty jako pojedyncze parametry, a nie tablice. Aby przekazać mu tablicę wartości, użyj operatora rozpakowywania `anyOf(...$variants)`. +Metoda `anyOf()` przyjmuje warianty jako pojedyncze parametry, a nie tablicę. Jeśli chcesz przekazać jej tablicę wartości, użyj operatora unpacking `anyOf(...$variants)`. -Wartość domyślna to `null`. Użyj metody `firstIsDefault()`, aby pierwszy element był domyślny: +Wartość domyślna to `null`. Metodą `firstIsDefault()` uczynimy pierwszy element domyślnym: ```php -// domyślnie jest to "hello +// domyślnie 'hello' Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault(); ``` -Struktury .[#toc-structures] ----------------------------- +Struktury +--------- -Struktury to obiekty o zdefiniowanych kluczach. Każda z par klucz => wartość jest określana jako "właściwość": +Struktury to obiekty z zdefiniowanymi kluczami. Każda z par klucz => wartość jest określana jako „właściwość”: -Struktury przyjmują tablice i obiekty i zwracają `stdClass` obiekty (chyba że zmienisz to za pomocą `castTo('array')` itp.). +Struktury przyjmują tablice i obiekty i zwracają obiekty `stdClass`. -Domyślnie wszystkie właściwości są opcjonalne i domyślnie ustawione na `null`. Możesz zdefiniować właściwości obowiązkowe używając `required()`: +Domyślnie wszystkie właściwości są opcjonalne i mają domyślną wartość `null`. Obowiązkowe właściwości można zdefiniować za pomocą `required()`: ```php $schema = Expect::structure([ 'required' => Expect::string()->required(), - 'optional' => Expect::string(), // výchozí hodnota je null + 'optional' => Expect::string(), // wartość domyślna to null ]); $processor->process($schema, ['optional' => '']); -// CHYBA: brakuje opcji 'required' +// BŁĄD: brakuje opcji 'required' $processor->process($schema, ['required' => 'foo']); -// OK, vrací {'required' => 'foo', 'optional' => null} +// OK, zwraca {'required' => 'foo', 'optional' => null} ``` -Jeśli nie chcesz, aby właściwości z wartością domyślną znalazły się na wyjściu, użyj `skipDefaults()`: +Jeśli nie chcesz mieć na wyjściu właściwości z wartością domyślną, użyj `skipDefaults()`: ```php $schema = Expect::structure([ @@ -223,10 +223,10 @@ $schema = Expect::structure([ ])->skipDefaults(); $processor->process($schema, ['required' => 'foo']); -// OK, vrací {'required' => 'foo'} +// OK, zwraca {'required' => 'foo'} ``` -Chociaż `null` jest domyślną wartością właściwości `optional`, nie jest dozwolone w danych wejściowych (wartość musi być łańcuchem). Właściwości akceptujące `null` są definiowane za pomocą `nullable()`: +Chociaż `null` jest domyślną wartością właściwości `optional`, w danych wejściowych nie jest dozwolony (wartością musi być string). Właściwości akceptujące `null` definiujemy za pomocą `nullable()`: ```php $schema = Expect::structure([ @@ -235,13 +235,15 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['optional' => null]); -// CHYBA: 'optional' expects to be string, null given. +// BŁĄD: 'optional' oczekuje stringa, podano null. $processor->process($schema, ['nullable' => null]); -// OK, vrací {'optional' => null, 'nullable' => null} +// OK, zwraca {'optional' => null, 'nullable' => null} ``` -Domyślnie w danych wejściowych nie może być żadnych dodatkowych elementów: +Tablicę wszystkich właściwości struktury zwraca metoda `getShape()`. + +Domyślnie w danych wejściowych nie mogą znajdować się żadne dodatkowe elementy: ```php $schema = Expect::structure([ @@ -249,10 +251,10 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['additional' => 1]); -// CHYBA: Nieoczekiwany element 'additional' +// BŁĄD: Nieoczekiwany element 'additional' ``` -Które można zmienić za pomocą `otherItems()`. Jako parametr podajemy schemat, według którego będą walidowane dodatkowe elementy: +Co możemy zmienić za pomocą `otherItems()`. Jako parametr podamy schemat, według którego będą walidowane dodatkowe elementy: ```php $schema = Expect::structure([ @@ -260,72 +262,110 @@ $schema = Expect::structure([ ])->otherItems(Expect::int()); $processor->process($schema, ['additional' => 1]); // OK -$processor->process($schema, ['additional' => true]); // CHYBA +$processor->process($schema, ['additional' => true]); // BŁĄD +``` + +Nową strukturę możesz utworzyć, dziedzicząc po innej za pomocą `extend()`: + +```php +$dog = Expect::structure([ + 'name' => Expect::string(), + 'age' => Expect::int(), +]); + +$dogWithBreed = $dog->extend([ + 'breed' => Expect::string(), +]); +``` + + +Tablice .{data-version:1.3.2} +----------------------------- + +Tablice z zdefiniowanymi kluczami. Dotyczy ich wszystko, co [#struktury]. + +```php +$schema = Expect::array([ + 'required' => Expect::string()->required(), + 'optional' => Expect::string(), // wartość domyślna to null +]); ``` +Można zdefiniować również tablicę indeksowaną, znaną jako tuple: -Właściwości przestarzałe .[#toc-deprecations] ---------------------------------------------- +```php +$schema = Expect::array([ + Expect::int(), + Expect::string(), + Expect::bool(), +]); -Możesz oznaczyć właściwość jako przestarzałą używając metody `deprecated([string $message])`. Informacje o deprecjacji są zwracane za pomocą `$processor->getWarnings()`: +$processor->process($schema, [1, 'hello', true]); // OK +``` + + +Przestarzałe właściwości +------------------------ + +Możesz oznaczyć właściwość jako przestarzałą za pomocą metody `deprecated([string $message])`. Informacje o zakończeniu wsparcia są zwracane za pomocą `$processor->getWarnings()`: ```php $schema = Expect::structure([ - 'old' => Expect::int()->deprecated('The item %path% is deprecated'), + 'old' => Expect::int()->deprecated('Element %path% jest przestarzały'), ]); $processor->process($schema, ['old' => 1]); // OK -$processor->getWarnings(); // ["The item 'old' is deprecated"] +$processor->getWarnings(); // ["Element 'old' jest przestarzały"] ``` -Zakresy: min() max() .[#toc-ranges-min-max] -------------------------------------------- +Zakresy: min() max() +-------------------- -Użyj `min()` i `max()`, aby ograniczyć liczbę elementów dla tablic: +Za pomocą `min()` i `max()` można w przypadku tablic ograniczyć liczbę elementów: ```php -// pole, minimum 10 pozycji, maksimum 20 pozycji +// tablica, co najmniej 10 elementów, maksymalnie 20 elementów Expect::array()->min(10)->max(20); ``` -W przypadku ciągów, ogranicz ich długość: +W przypadku ciągów znaków ograniczyć ich długość: ```php -// ciąg znaków o długości co najmniej 10 znaków, maksymalnie 20 znaków +// ciąg znaków, co najmniej 10 znaków długości, maksymalnie 20 znaków Expect::string()->min(10)->max(20); ``` -W przypadku liczb, ogranicz ich wartość: +W przypadku liczb ograniczyć ich wartość: ```php -// liczba całkowita, między 10 a 20 włącznie +// liczba całkowita, od 10 do 20 włącznie Expect::int()->min(10)->max(20); ``` -Oczywiście można wymienić tylko `min()`, lub tylko `max()`: +Oczywiście można podać tylko `min()` lub tylko `max()`: ```php -// ciąg o długości maksymalnie 20 znaków +// ciąg znaków o maksymalnej długości 20 znaków Expect::string()->max(20); ``` -Wyrażenia regularne: pattern() .[#toc-regular-expressions-pattern] ------------------------------------------------------------------- +Wyrażenia regularne: pattern() +------------------------------ -Możesz użyć `pattern()` do określenia wyrażenia regularnego, do którego musi pasować **cały** łańcuch wejściowy (tj. tak jakby zawinięty przez `^` a `$`): +Za pomocą `pattern()` można podać wyrażenie regularne, któremu musi odpowiadać **cały** wejściowy ciąg znaków (tj. jakby był otoczony znakami `^` i `$`): ```php -// tylko 9 liczb +// dokładnie 9 cyfr Expect::string()->pattern('\d{9}'); ``` -Niestandardowe ograniczenie: assert() .[#toc-custom-assertions-assert] ----------------------------------------------------------------------- +Własne ograniczenia: assert() +----------------------------- -Wszelkie dodatkowe ograniczenia można określić za pomocą `assert(callable $fn)`. +Dowolne inne ograniczenia podajemy za pomocą `assert(callable $fn)`. ```php $countIsEven = fn($v) => count($v) % 2 === 0; @@ -334,104 +374,115 @@ $schema = Expect::arrayOf('string') ->assert($countIsEven); // liczba musi być parzysta $processor->process($schema, ['a', 'b']); // OK -$processor->process($schema, ['a', 'b', 'c']); // ERROR: 3 nie jest liczbą parzystą +$processor->process($schema, ['a', 'b', 'c']); // BŁĄD: 3 nie jest liczbą parzystą ``` -Albo +Lub ```php Expect::string()->assert('is_file'); // plik musi istnieć ``` -Do każdego ograniczenia można dodać własny opis. Zostanie to uwzględnione w komunikacie o błędzie. +Do każdego ograniczenia możesz dodać własny opis. Będzie on częścią komunikatu o błędzie. ```php $schema = Expect::arrayOf('string') - ->assert($countIsEven, 'Even items in array'); + ->assert($countIsEven, 'Liczba parzysta elementów w tablicy'); $processor->process($schema, ['a', 'b', 'c']); -// Failed assertion "Even items in array" for item with value array. +// Nieudana asercja "Liczba parzysta elementów w tablicy" dla elementu o wartości array. ``` -Metoda ta może być wywoływana wielokrotnie, aby dodać więcej ograniczeń. +Metodę można wywoływać wielokrotnie, aby dodać więcej ograniczeń. Można ją przeplatać z wywołaniami `transform()` i `castTo()`. -Mapowanie na obiekty: from() .[#toc-mapping-to-objects-from] ------------------------------------------------------------- +Transformacje: transform() .{data-version:1.2.5} +------------------------------------------------ -Możemy mieć schemat struktury wygenerowany z klasy. Przykład: +Pomyślnie zwalidowane dane można modyfikować za pomocą własnej funkcji: ```php -class Config -{ - public string $name; - public string|null $password; - public bool $admin = false; -} +// konwersja na wielkie litery: +Expect::string()->transform(fn(string $s) => strtoupper($s)); +``` -$schema = Expect::from(new Config); +Metodę można wywoływać wielokrotnie, aby dodać więcej transformacji. Można ją przeplatać z wywołaniami `assert()` i `castTo()`. Operacje zostaną wykonane w kolejności, w jakiej są zadeklarowane: -$data = [ - 'name' => 'franta', -]; - -$normalized = $processor->process($schema, $data); -// $normalized instanceof Config -// $normalized = {'name' => 'franta', 'password' => null, 'admin' => false} +```php +Expect::type('string|int') + ->castTo('string') + ->assert('ctype_lower', 'Wszystkie znaki muszą być małymi literami') + ->transform(fn(string $s) => strtoupper($s)); // konwersja na wielkie litery ``` -Jeśli używasz PHP 7.4 lub nowszego, możesz użyć typów natywnych: +Metoda `transform()` może jednocześnie transformować i walidować wartość. Jest to często prostsze i mniej powtarzalne niż łączenie `transform()` i `assert()`. W tym celu funkcja otrzymuje obiekt [Context |api:Nette\Schema\Context] z metodą `addError()`, której można użyć do dodania informacji o problemach z walidacją: ```php -class Config -{ - public string $name; - public ?string $password; - public bool $admin = false; -} +Expect::string() + ->transform(function (string $s, Nette\Schema\Context $context) { + if (!ctype_lower($s)) { + $context->addError('Wszystkie znaki muszą być małymi literami', 'my.case.error'); + return null; + } -$schema = Expect::from(new Config); + return strtoupper($s); + }); ``` -Obsługiwane są również klasy anonimowe: + +Rzutowanie: castTo() +-------------------- + +Pomyślnie zwalidowane dane można rzutować: ```php -$schema = Expect::from(new class { - public string $name; - public ?string $password; - public bool $admin = false; -}); +Expect::scalar()->castTo('string'); ``` -Ponieważ informacje uzyskane z definicji klasy mogą nie być wystarczające, możesz użyć drugiego parametru, aby dodać własny schemat do elementów: +Oprócz natywnych typów PHP można rzutować również na klasy. Rozróżnia się przy tym, czy chodzi o prostą klasę bez konstruktora, czy klasę z konstruktorem. Jeśli klasa nie ma konstruktora, tworzy się jej instancja, a wszystkie elementy struktury zapisuje się do właściwości: ```php -$schema = Expect::from(new Config, [ - 'name' => Expect::string()->pattern('\w:.*'), -]); -``` +class Info +{ + public bool $processRefund; + public int $refundAmount; +} +Expect::structure([ + 'processRefund' => Expect::bool(), + 'refundAmount' => Expect::int(), +])->castTo(Info::class); -Override: castTo() .[#toc-casting-castto] ------------------------------------------ +// tworzy '$obj = new Info' i zapisuje do $obj->processRefund i $obj->refundAmount +``` -Pomyślnie zwalidowane dane mogą zostać poddane repatriacji: +Jeśli klasa ma konstruktor, elementy struktury przekazuje się jako nazwane parametry konstruktora: ```php -Expect::scalar()->castTo('string'); +class Info +{ + public function __construct( + public bool $processRefund, + public int $refundAmount, + ) { + } +} + +// tworzy $obj = new Info(processRefund: ..., refundAmount: ...) ``` -Oprócz natywnych typów PHP, może być również przepisany na klasy: +Rzutowanie w połączeniu z parametrem skalarnym tworzy obiekt i przekazuje wartość jako jedyny parametr konstruktora: ```php -Expect::scalar()->castTo('AddressEntity'); +Expect::string()->castTo(DateTime::class); +// tworzy new DateTime(...) ``` -Normalizacja: before() .[#toc-normalization-before] ---------------------------------------------------- +Normalizacja: before() +---------------------- -Przed walidacją dane mogą zostać znormalizowane przy użyciu metody `before()`. Jako przykład rozważmy element, który musi być tablicą łańcuchów (np. `['a', 'b', 'c']`), ale otrzymuje dane wejściowe w postaci ciągu `a b c`: +Przed samą walidacją dane można znormalizować za pomocą metody `before()`. Jako przykład podajmy element, który musi być tablicą ciągów znaków (na przykład `['a', 'b', 'c']`), ale przyjmuje wejście w formie ciągu `a b c`: ```php $explode = fn($v) => explode(' ', $v); @@ -440,8 +491,48 @@ $schema = Expect::arrayOf('string') ->before($explode); $normalized = $processor->process($schema, 'a b c'); -// OK a vrátí ['a', 'b', 'c'] +// OK i zwraca ['a', 'b', 'c'] +``` + + +Mapowanie na obiekty: from() +---------------------------- + +Schemat struktury możemy wygenerować na podstawie klasy. Przykład: + +```php +class Config +{ + public string $name; + public string|null $password; + public bool $admin = false; +} + +$schema = Expect::from(new Config); + +$data = [ + 'name' => 'franta', +]; + +$normalized = $processor->process($schema, $data); +// $normalized instanceof Config +// $normalized = {'name' => 'franta', 'password' => null, 'admin' => false} +``` + +Wspierane są również klasy anonimowe: + +```php +$schema = Expect::from(new class { + public string $name; + public ?string $password; + public bool $admin = false; +}); ``` +Ponieważ informacje uzyskane z definicji klasy mogą być niewystarczające, możesz drugim parametrem uzupełnić elementy o własny schemat: -{{leftbar: nette:@menu-topics}} +```php +$schema = Expect::from(new Config, [ + 'name' => Expect::string()->pattern('\w:.*'), +]); +``` diff --git a/schema/pl/@meta.texy b/schema/pl/@meta.texy new file mode 100644 index 0000000000..08f2227fb5 --- /dev/null +++ b/schema/pl/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Dokumentacja Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/schema/pt/@home.texy b/schema/pt/@home.texy index 0184591815..ca0c9db605 100644 --- a/schema/pt/@home.texy +++ b/schema/pt/@home.texy @@ -1,8 +1,8 @@ -Schema: Validação de dados -************************** +Nette Schema +************ .[perex] -Uma biblioteca prática para validação e normalização de estruturas de dados contra um determinado esquema com uma API inteligente e fácil de entender. +Biblioteca prática para validação e normalização de estruturas de dados em relação a um esquema fornecido com uma API inteligente e compreensível. Instalação: @@ -11,12 +11,12 @@ composer require nette/schema ``` -Utilização básica .[#toc-basic-usage] -------------------------------------- +Uso básico +---------- -Na variável `$schema` temos um esquema de validação (o que exatamente isto significa e como criá-lo diremos mais tarde) e na variável `$data` temos uma estrutura de dados que queremos validar e normalizar. Estes podem ser, por exemplo, dados enviados pelo usuário através de uma API, arquivo de configuração, etc. +Na variável `$schema`, temos o esquema de validação (o que isso significa exatamente e como criar tal esquema será explicado em breve) e na variável `$data`, a estrutura de dados que queremos validar e normalizar. Podem ser, por exemplo, dados enviados pelo usuário através de uma interface API, um arquivo de configuração, etc. -A tarefa é tratada pela classe [api:Nette\Schema\Processor], que processa a entrada e ou retorna dados normalizados ou lança uma exceção [api:Nette\Schema\ValidationException] sobre erro. +A tarefa é realizada pela classe [api:Nette\Schema\Processor], que processa a entrada e retorna os dados normalizados ou lança uma exceção [api:Nette\Schema\ValidationException] em caso de erro. ```php $processor = new Nette\Schema\Processor; @@ -24,17 +24,17 @@ $processor = new Nette\Schema\Processor; try { $normalized = $processor->process($schema, $data); } catch (Nette\Schema\ValidationException $e) { - echo 'Data is invalid: ' . $e->getMessage(); + echo 'Dados não são válidos: ' . $e->getMessage(); } ``` -O método `$e->getMessages()` retorna a matriz de todas as cadeias de mensagens e `$e->getMessageObjects()` retorna todas as mensagens como objetos "Nette\Schema\Message:https://api.nette.org/schema/master/Nette/Schema/Message.html ". +O método `$e->getMessages()` retorna um array de todas as mensagens como strings e `$e->getMessageObjects()` retorna todas as mensagens como objetos "Nette\Schema\Message":https://api.nette.org/schema/master/Nette/Schema/Message.html. -Definindo o esquema .[#toc-defining-schema] -------------------------------------------- +Definindo o esquema +------------------- -E agora vamos criar um esquema. A classe [api:Nette\Schema\Expect] é usada para defini-lo, na verdade definimos expectativas de como os dados devem ser. Digamos que os dados de entrada devem ser uma estrutura (por exemplo, uma matriz) contendo elementos `processRefund` do tipo bool e `refundAmount` do tipo int. +E agora criaremos o esquema. A classe [api:Nette\Schema\Expect] é usada para defini-lo, definimos na verdade as expectativas de como os dados devem parecer. Digamos que os dados de entrada devem formar uma estrutura (por exemplo, um array) contendo os elementos `processRefund` do tipo bool e `refundAmount` do tipo int. ```php use Nette\Schema\Expect; @@ -45,9 +45,9 @@ $schema = Expect::structure([ ]); ``` -Acreditamos que a definição do esquema parece clara, mesmo que você a veja pela primeira vez. +Acreditamos que a definição do esquema parece compreensível, mesmo que você a veja pela primeira vez. -Vamos enviar os seguintes dados para validação: +Enviaremos os seguintes dados para validação: ```php $data = [ @@ -55,27 +55,27 @@ $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // OK, ele passa +$normalized = $processor->process($schema, $data); // OK, passa na validação ``` -A saída, ou seja, o valor `$normalized`, é o objeto `stdClass`. Se quisermos que a saída seja uma matriz, adicionamos um elenco ao esquema `Expect::structure([...])->castTo('array')`. +A saída, ou seja, o valor `$normalized`, é um objeto `stdClass`. Se quiséssemos que a saída fosse um array, adicionaríamos a conversão de tipo ao esquema `Expect::structure([...])->castTo('array')`. -Todos os elementos da estrutura são opcionais e têm um valor padrão `null`. Exemplo: +Todos os elementos da estrutura são opcionais e têm um valor padrão de `null`. Exemplo: ```php $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // OK, ele passa +$normalized = $processor->process($schema, $data); // OK, passa na validação // $normalized = {'processRefund' => null, 'refundAmount' => 17} ``` -O fato de o valor padrão ser `null` não significa que ele seria aceito nos dados de entrada `'processRefund' => null`. Não, a entrada deve ser booleana, ou seja, apenas `true` ou `false`. Teríamos que permitir explicitamente `null` via `Expect::bool()->nullable()`. +O fato de o valor padrão ser `null` não significa que `'processRefund' => null` seria aceito nos dados de entrada. Não, a entrada deve ser um booleano, ou seja, apenas `true` ou `false`. Teríamos que permitir `null` explicitamente usando `Expect::bool()->nullable()`. -Um item pode ser tornado obrigatório usando `Expect::bool()->required()`. Mudamos o valor padrão para `false` usando `Expect::bool()->default(false)` ou em breve usando `Expect::bool(false)`. +Um item pode ser tornado obrigatório usando `Expect::bool()->required()`. Mudamos o valor padrão para `false`, por exemplo, usando `Expect::bool()->default(false)` ou abreviadamente `Expect::bool(false)`. -E se quiséssemos aceitar `1` and `0` além de booleanos? Então listamos os valores permitidos, que também normalizaremos para booleanos: +E se quiséssemos aceitar `1` e `0` além de booleano? Então listamos os valores, que também deixamos normalizar para booleano: ```php $schema = Expect::structure([ @@ -87,13 +87,13 @@ $normalized = $processor->process($schema, $data); is_bool($normalized->processRefund); // true ``` -Agora você conhece as bases de como o esquema é definido e como os elementos individuais da estrutura se comportam. Agora vamos mostrar o que todos os outros elementos podem ser usados na definição de um esquema. +Agora você conhece os fundamentos de como definir um esquema e como os elementos individuais da estrutura se comportam. Agora mostraremos quais outros elementos podem ser usados na definição do esquema. -Tipos de dados: tipo() .[#toc-data-types-type] ----------------------------------------------- +Tipos de dados: type() +---------------------- -Todos os tipos de dados padrão PHP podem ser listados no esquema: +Todos os tipos de dados PHP padrão podem ser especificados no esquema: ```php Expect::string($default = null) @@ -104,63 +104,63 @@ Expect::null() Expect::array($default = []) ``` -E depois todos os tipos [suportados pelos validadores |utils:validators#Expected Types] via `Expect::type('scalar')` ou abreviado `Expect::scalar()`. Também são aceitos nomes de classes ou interfaces, por exemplo `Expect::type('AddressEntity')`. +E também todos os tipos [suportados pela classe Validators |utils:validators#Tipos Esperados], por exemplo, `Expect::type('scalar')` ou abreviadamente `Expect::scalar()`. Também nomes de classes ou interfaces, por exemplo, `Expect::type('AddressEntity')`. -Você também pode usar a notação sindical: +Também é possível usar a notação de união: ```php Expect::type('bool|string|array') ``` -O valor padrão é sempre `null` exceto para `array` e `list`, onde é uma matriz vazia. (Uma lista é um array indexado em ordem ascendente de chaves numéricas a partir de zero, ou seja, um array não-associativo). +O valor padrão é sempre `null`, exceto para `array` e `list`, onde é um array vazio. (Uma lista é um array indexado por uma série ascendente de chaves numéricas a partir de zero, ou seja, um array não associativo). -Conjunto de valores: arrayOf() listOf() .[#toc-array-of-values-arrayof-listof] ------------------------------------------------------------------------------- +Array de valores: arrayOf() listOf() +------------------------------------ -A matriz é estrutura muito geral, é mais útil especificar exatamente quais elementos ela pode conter. Por exemplo, uma matriz cujos elementos só podem ser cordas: +Um array representa uma estrutura muito geral, é mais útil especificar exatamente quais elementos ele pode conter. Por exemplo, um array cujos elementos só podem ser strings: ```php $schema = Expect::arrayOf('string'); $processor->process($schema, ['hello', 'world']); // OK $processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK -$processor->process($schema, ['key' => 123]); // ERROR: 123 is not a string +$processor->process($schema, ['key' => 123]); // ERRO: 123 não é string ``` -O segundo parâmetro pode ser usado para especificar chaves (desde a versão 1.2): +O segundo parâmetro pode especificar as chaves (desde a versão 1.2): ```php $schema = Expect::arrayOf('string', 'int'); $processor->process($schema, ['hello', 'world']); // OK -$processor->process($schema, ['a' => 'hello']); // ERROR: 'a' is not int +$processor->process($schema, ['a' => 'hello']); // ERRO: 'a' não é int ``` -A lista é uma matriz indexada: +Uma lista é um array indexado: ```php $schema = Expect::listOf('string'); $processor->process($schema, ['a', 'b']); // OK -$processor->process($schema, ['a', 123]); // ERROR: 123 is not a string -$processor->process($schema, ['key' => 'a']); // ERROR: is not a list -$processor->process($schema, [1 => 'a', 0 => 'b']); // ERROR: is not a list +$processor->process($schema, ['a', 123]); // ERRO: 123 não é string +$processor->process($schema, ['key' => 'a']); // ERRO: não é lista +$processor->process($schema, [1 => 'a', 0 => 'b']); // ERRO: também não é lista ``` -O parâmetro também pode ser um esquema, para que possamos escrever: +O parâmetro também pode ser um esquema, então podemos escrever: ```php Expect::arrayOf(Expect::bool()) ``` -O valor padrão é uma matriz vazia. Se você especificar o valor padrão, ele será fundido com os dados passados. Isto pode ser desabilitado usando `mergeDefaults(false)` (desde a versão 1.1). +O valor padrão é um array vazio. Se você especificar um valor padrão, ele será mesclado com os dados passados. Isso pode ser desativado usando `mergeDefaults(false)` (desde a versão 1.1). -Enumeração: anyOf() .[#toc-enumeration-anyof] ---------------------------------------------- +Enumeração: anyOf() +------------------- -`anyOf()` é um conjunto de valores ou esquemas que um valor pode ser. Eis como escrever um conjunto de elementos que podem ser `'a'`, `true`, ou `null`: +`anyOf()` representa uma enumeração de valores ou esquemas que um valor pode assumir. É assim que escrevemos um array de elementos que podem ser `'a'`, `true` ou `null`: ```php $schema = Expect::listOf( @@ -168,10 +168,10 @@ $schema = Expect::listOf( ); $processor->process($schema, ['a', true, null, 'a']); // OK -$processor->process($schema, ['a', false]); // ERROR: false does not belong there +$processor->process($schema, ['a', false]); // ERRO: false não pertence aqui ``` -Os elementos de enumeração também podem ser esquemas: +Os elementos da enumeração também podem ser esquemas: ```php $schema = Expect::listOf( @@ -179,42 +179,42 @@ $schema = Expect::listOf( ); $processor->process($schema, ['foo', true, null, 'bar']); // OK -$processor->process($schema, [123]); // ERROR +$processor->process($schema, [123]); // ERRO ``` -O método `anyOf()` aceita variantes como parâmetros individuais, não como array. Para passar-lhe uma matriz de valores, use o operador de desembalagem `anyOf(...$variants)`. +O método `anyOf()` aceita variantes como parâmetros individuais, não um array. Se você quiser passar um array de valores para ele, use o operador de desempacotamento `anyOf(...$variants)`. -O valor padrão é `null`. Use o método `firstIsDefault()` para tornar o primeiro elemento o padrão: +O valor padrão é `null`. Com o método `firstIsDefault()`, tornamos o primeiro elemento o padrão: ```php -// o padrão é 'olá'. +// o padrão é 'hello' Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault(); ``` -Estruturas .[#toc-structures] ------------------------------ +Estruturas +---------- -As estruturas são objetos com chaves definidas. Cada uma destas chaves => pares de valores é chamada de "propriedade": +Estruturas são objetos com chaves definidas. Cada par chave => valor é referido como uma „propriedade“: -As estruturas aceitam matrizes e objetos e retornam objetos `stdClass` (a menos que você altere com `castTo('array')`, etc.). +Estruturas aceitam arrays e objetos e retornam objetos `stdClass`. Por padrão, todas as propriedades são opcionais e têm um valor padrão de `null`. Você pode definir propriedades obrigatórias usando `required()`: ```php $schema = Expect::structure([ 'required' => Expect::string()->required(), - 'optional' => Expect::string(), // o valor padrão é nulo + 'optional' => Expect::string(), // valor padrão é null ]); $processor->process($schema, ['optional' => '']); -// ERROR: falta a opção 'necessário'. +// ERRO: opção 'required' está faltando $processor->process($schema, ['required' => 'foo']); // OK, retorna {'required' => 'foo', 'optional' => null} ``` -Se você não quiser emitir propriedades com apenas um valor padrão, use `skipDefaults()`: +Se você não quiser ter propriedades com valor padrão na saída, use `skipDefaults()`: ```php $schema = Expect::structure([ @@ -223,10 +223,10 @@ $schema = Expect::structure([ ])->skipDefaults(); $processor->process($schema, ['required' => 'foo']); -// OK, retorna {'necessário' => 'foo'}) +// OK, retorna {'required' => 'foo'} ``` -Embora `null` seja o valor padrão da propriedade `optional`, ele não é permitido nos dados de entrada (o valor deve ser uma string). As propriedades que aceitam `null` são definidas usando `nullable()`: +Embora `null` seja o valor padrão da propriedade `optional`, ele não é permitido nos dados de entrada (o valor deve ser uma string). Propriedades que aceitam `null` são definidas usando `nullable()`: ```php $schema = Expect::structure([ @@ -235,12 +235,14 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['optional' => null]); -// ERROR: 'opcional' espera ser string, dado nulo. +// ERRO: 'optional' espera ser string, null fornecido. $processor->process($schema, ['nullable' => null]); -// OK, retorna {'opcional' => nulo, 'anulável' => nulo} +// OK, retorna {'optional' => null, 'nullable' => null} ``` +O array de todas as propriedades da estrutura é retornado pelo método `getShape()`. + Por padrão, não pode haver itens extras nos dados de entrada: ```php @@ -249,10 +251,10 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['additional' => 1]); -// ERRO: Item inesperado 'adicional'. +// ERRO: Item inesperado 'additional' ``` -Que podemos mudar com `otherItems()`. Como parâmetro, especificaremos o esquema para cada elemento extra: +O que podemos mudar usando `otherItems()`. Como parâmetro, especificamos o esquema segundo o qual os elementos extras serão validados: ```php $schema = Expect::structure([ @@ -260,81 +262,119 @@ $schema = Expect::structure([ ])->otherItems(Expect::int()); $processor->process($schema, ['additional' => 1]); // OK -$processor->process($schema, ['additional' => true]); // ERROR +$processor->process($schema, ['additional' => true]); // ERRO ``` +Você pode criar uma nova estrutura derivando de outra usando `extend()`: + +```php +$dog = Expect::structure([ + 'name' => Expect::string(), + 'age' => Expect::int(), +]); + +$dogWithBreed = $dog->extend([ + 'breed' => Expect::string(), +]); +``` -Depreciações .[#toc-deprecations] ---------------------------------- -Você pode depreciar os bens usando o `deprecated([string $message])` método. Os avisos de depreciação são devolvidos por `$processor->getWarnings()`: +Arrays .{data-version:1.3.2} +---------------------------- + +Arrays com chaves definidas. Tudo o que se aplica a [#estruturas] também se aplica a ele. + +```php +$schema = Expect::array([ + 'required' => Expect::string()->required(), + 'optional' => Expect::string(), // valor padrão é null +]); +``` + +Também é possível definir um array indexado, conhecido como tupla: + +```php +$schema = Expect::array([ + Expect::int(), + Expect::string(), + Expect::bool(), +]); + +$processor->process($schema, [1, 'hello', true]); // OK +``` + + +Propriedades obsoletas +---------------------- + +Você pode marcar uma propriedade como obsoleta usando o método `deprecated([string $message])`. Informações sobre o fim do suporte são retornadas usando `$processor->getWarnings()`: ```php $schema = Expect::structure([ - 'old' => Expect::int()->deprecated('The item %path% is deprecated'), + 'old' => Expect::int()->deprecated('O item %path% está obsoleto'), ]); $processor->process($schema, ['old' => 1]); // OK -$processor->getWarnings(); // ["The item 'old' is deprecated"] +$processor->getWarnings(); // ["O item 'old' está obsoleto"] ``` -Faixas: min() max() .[#toc-ranges-min-max] ------------------------------------------- +Intervalos: min() max() +----------------------- -Use `min()` e `max()` para limitar o número de elementos para arrays: +Usando `min()` e `max()`, é possível limitar o número de elementos em arrays: ```php -// matriz, pelo menos 10 itens, máximo 20 itens +// array, pelo menos 10 itens, no máximo 20 itens Expect::array()->min(10)->max(20); ``` -Para as cordas, limite seu comprimento: +Em strings, limitar seu comprimento: ```php -// string, com pelo menos 10 caracteres, máximo 20 caracteres +// string, pelo menos 10 caracteres de comprimento, no máximo 20 caracteres Expect::string()->min(10)->max(20); ``` -Para os números, limite seu valor: +Em números, limitar seu valor: ```php // inteiro, entre 10 e 20 inclusive Expect::int()->min(10)->max(20); ``` -Naturalmente, é possível mencionar apenas `min()`, ou apenas `max()`: +Claro, é possível especificar apenas `min()`, ou apenas `max()`: ```php -// string, máximo 20 caracteres +// string no máximo 20 caracteres Expect::string()->max(20); ``` -Expressões regulares: padrão() .[#toc-regular-expressions-pattern] ------------------------------------------------------------------- +Expressões regulares: pattern() +------------------------------- -Usando `pattern()`, você pode especificar uma expressão regular que a string de entrada **whole** deve combinar (ou seja, como se estivesse embrulhada em caracteres `^` a `$`): +Usando `pattern()`, é possível especificar uma expressão regular que deve corresponder a **toda** a string de entrada (ou seja, como se estivesse envolvida pelos caracteres `^` e `$`): ```php -// apenas 9 dígitos +// exatamente 9 números Expect::string()->pattern('\d{9}'); ``` -Asserções personalizadas: assert() .[#toc-custom-assertions-assert] -------------------------------------------------------------------- +Restrições personalizadas: assert() +----------------------------------- -Você pode adicionar quaisquer outras restrições usando `assert(callable $fn)`. +Quaisquer outras restrições são especificadas usando `assert(callable $fn)`. ```php $countIsEven = fn($v) => count($v) % 2 === 0; $schema = Expect::arrayOf('string') - ->assert($countIsEven); // a contagem deve ser uniforme + ->assert($countIsEven); // o número deve ser par $processor->process($schema, ['a', 'b']); // OK -$processor->process($schema, ['a', 'b', 'c']); // ERROR: 3 não é nem +$processor->process($schema, ['a', 'b', 'c']); // ERRO: 3 não é um número par ``` Ou @@ -343,95 +383,106 @@ Ou Expect::string()->assert('is_file'); // o arquivo deve existir ``` -Você pode acrescentar sua própria descrição para cada asserção. Ela será parte da mensagem de erro. +Você pode adicionar sua própria descrição a cada restrição. Ela fará parte da mensagem de erro. ```php $schema = Expect::arrayOf('string') - ->assert($countIsEven, 'Even items in array'); + ->assert($countIsEven, 'Itens pares no array'); $processor->process($schema, ['a', 'b', 'c']); -// afirmação falhada "Itens pares na matriz" para item com matriz de valores. +// Falha na asserção "Itens pares no array" para o item com valor array. ``` -O método pode ser chamado repetidamente para acrescentar mais afirmações. +O método pode ser chamado repetidamente para adicionar mais restrições. Pode ser intercalado com chamadas a `transform()` e `castTo()`. -Mapeamento para Objetos: de() .[#toc-mapping-to-objects-from] -------------------------------------------------------------- +Transformações: transform() .{data-version:1.2.5} +------------------------------------------------- -Você pode gerar um esquema de estrutura a partir da classe. Exemplo: +Dados validados com sucesso podem ser modificados usando uma função personalizada: ```php -class Config -{ - public string $name; - public string|null $password; - public bool $admin = false; -} +// conversão para maiúsculas: +Expect::string()->transform(fn(string $s) => strtoupper($s)); +``` -$schema = Expect::from(new Config); +O método pode ser chamado repetidamente para adicionar mais transformações. Pode ser intercalado com chamadas a `assert()` e `castTo()`. As operações são realizadas na ordem em que são declaradas: -$data = [ - 'name' => 'jeff', -]; - -$normalized = $processor->process($schema, $data); -// $normalized instanceof Config -// $normalized = {'name' => 'jeff', 'password' => null, 'admin' => false} +```php +Expect::type('string|int') + ->castTo('string') + ->assert('ctype_lower', 'Todos os caracteres devem estar em minúsculas') + ->transform(fn(string $s) => strtoupper($s)); // conversão para maiúsculas ``` -Se você estiver usando PHP 7.4 ou superior, você pode usar tipos nativos: +O método `transform()` pode transformar e validar o valor simultaneamente. Isso costuma ser mais simples e menos duplicado do que encadear `transform()` e `assert()`. Para este propósito, a função recebe um objeto [Context |api:Nette\Schema\Context] com o método `addError()`, que pode ser usado para adicionar informações sobre problemas de validação: ```php -class Config -{ - public string $name; - public ?string $password; - public bool $admin = false; -} +Expect::string() + ->transform(function (string $s, Nette\Schema\Context $context) { + if (!ctype_lower($s)) { + $context->addError('Todos os caracteres devem estar em minúsculas', 'my.case.error'); + return null; + } -$schema = Expect::from(new Config); + return strtoupper($s); + }); ``` -Aulas anônimas também são suportadas: + +Conversão de tipo: castTo() +--------------------------- + +Dados validados com sucesso podem ser convertidos: ```php -$schema = Expect::from(new class { - public string $name; - public ?string $password; - public bool $admin = false; -}); +Expect::scalar()->castTo('string'); ``` -Como as informações obtidas da definição de classe podem não ser suficientes, pode-se adicionar um esquema personalizado para os elementos com o segundo parâmetro: +Além dos tipos nativos do PHP, também é possível converter para classes. Distingue-se se é uma classe simples sem construtor ou uma classe com construtor. Se a classe não tiver construtor, sua instância é criada e todos os elementos da estrutura são escritos nas propriedades: ```php -$schema = Expect::from(new Config, [ - 'name' => Expect::string()->pattern('\w:.*'), -]); -``` +class Info +{ + public bool $processRefund; + public int $refundAmount; +} +Expect::structure([ + 'processRefund' => Expect::bool(), + 'refundAmount' => Expect::int(), +])->castTo(Info::class); -Fundição: castTo() .[#toc-casting-castto] ------------------------------------------ +// cria '$obj = new Info' e escreve em $obj->processRefund e $obj->refundAmount +``` -Os dados validados com sucesso podem ser lançados: +Se a classe tiver um construtor, os elementos da estrutura são passados como parâmetros nomeados para o construtor: ```php -Expect::scalar()->castTo('string'); +class Info +{ + public function __construct( + public bool $processRefund, + public int $refundAmount, + ) { + } +} + +// cria $obj = new Info(processRefund: ..., refundAmount: ...) ``` -Além dos tipos de PHP nativos, você também pode participar de aulas: +A conversão de tipo em combinação com um parâmetro escalar cria um objeto e passa o valor como o único parâmetro para o construtor: ```php -Expect::scalar()->castTo('AddressEntity'); +Expect::string()->castTo(DateTime::class); +// cria new DateTime(...) ``` -Normalização: antes() .[#toc-normalization-before] --------------------------------------------------- +Normalização: before() +---------------------- -Antes da validação propriamente dita, os dados podem ser normalizados usando o método `before()`. Como exemplo, vamos ter um elemento que deve ser um conjunto de cordas (por exemplo `['a', 'b', 'c']`), mas recebe entrada sob a forma de um cordel `a b c`: +Antes da própria validação, os dados podem ser normalizados usando o método `before()`. Como exemplo, considere um elemento que deve ser um array de strings (por exemplo, `['a', 'b', 'c']`), mas aceita entrada na forma de uma string `a b c`: ```php $explode = fn($v) => explode(' ', $v); @@ -440,7 +491,48 @@ $schema = Expect::arrayOf('string') ->before($explode); $normalized = $processor->process($schema, 'a b c'); -// OK, retorna ['a', 'b', 'c'] +// OK e retorna ['a', 'b', 'c'] ``` -{{leftbar: nette:@menu-topics}} + +Mapeamento para objetos: from() +------------------------------- + +Podemos ter o esquema da estrutura gerado a partir de uma classe. Exemplo: + +```php +class Config +{ + public string $name; + public string|null $password; + public bool $admin = false; +} + +$schema = Expect::from(new Config); + +$data = [ + 'name' => 'franta', +]; + +$normalized = $processor->process($schema, $data); +// $normalized instanceof Config +// $normalized = {'name' => 'franta', 'password' => null, 'admin' => false} +``` + +Classes anônimas também são suportadas: + +```php +$schema = Expect::from(new class { + public string $name; + public ?string $password; + public bool $admin = false; +}); +``` + +Como as informações obtidas da definição da classe podem não ser suficientes, você pode adicionar seu próprio esquema aos elementos com o segundo parâmetro: + +```php +$schema = Expect::from(new Config, [ + 'name' => Expect::string()->pattern('\w:.*'), +]); +``` diff --git a/schema/pt/@meta.texy b/schema/pt/@meta.texy new file mode 100644 index 0000000000..e2566bcb44 --- /dev/null +++ b/schema/pt/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Documentação Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/schema/ro/@home.texy b/schema/ro/@home.texy index 8c257c1e51..0c645e552f 100644 --- a/schema/ro/@home.texy +++ b/schema/ro/@home.texy @@ -1,8 +1,8 @@ -Schema: Validarea datelor -************************* +Nette Schema +************ .[perex] -O bibliotecă practică pentru validarea și normalizarea structurilor de date în raport cu o schemă dată, cu o API inteligentă și ușor de înțeles. +Bibliotecă practică pentru validarea și normalizarea structurilor de date față de o schemă dată, cu o API inteligentă și ușor de înțeles. Instalare: @@ -11,12 +11,12 @@ composer require nette/schema ``` -Utilizare de bază .[#toc-basic-usage] -------------------------------------- +Utilizare de bază +----------------- -În variabila `$schema` avem o schemă de validare (ce înseamnă exact acest lucru și cum să o creăm vom spune mai târziu) și în variabila `$data` avem o structură de date pe care dorim să o validăm și să o normalizăm. Aceasta poate fi, de exemplu, date trimise de utilizator prin intermediul unui API, fișier de configurare etc. +În variabila `$schema` avem schema de validare (ce înseamnă exact și cum să creăm o astfel de schemă vom discuta imediat) și în variabila `$data` structura de date pe care dorim să o validăm și să o normalizăm. Poate fi, de exemplu, date trimise de utilizator prin interfața API, un fișier de configurare etc. -Sarcina este gestionată de clasa [api:Nette\Schema\Processor], care procesează datele de intrare și fie returnează datele normalizate, fie aruncă o excepție [api:Nette\Schema\ValidationException] în caz de eroare. +Sarcina este gestionată de clasa [api:Nette\Schema\Processor], care procesează intrarea și fie returnează date normalizate, fie, în caz de eroare, aruncă excepția [api:Nette\Schema\ValidationException]. ```php $processor = new Nette\Schema\Processor; @@ -24,17 +24,17 @@ $processor = new Nette\Schema\Processor; try { $normalized = $processor->process($schema, $data); } catch (Nette\Schema\ValidationException $e) { - echo 'Data is invalid: ' . $e->getMessage(); + echo 'Datele nu sunt valide: ' . $e->getMessage(); } ``` -Metoda `$e->getMessages()` returnează o matrice cu toate șirurile de mesaje, iar `$e->getMessageObjects()` returnează toate mesajele ca obiecte "Nette\Schema\Message":https://api.nette.org/schema/master/Nette/Schema/Message.html. +Metoda `$e->getMessages()` returnează un array al tuturor mesajelor ca șiruri și `$e->getMessageObjects()` returnează toate mesajele ca obiecte "Nette\Schema\Message":https://api.nette.org/schema/master/Nette/Schema/Message.html. -Definirea schemei .[#toc-defining-schema] ------------------------------------------ +Definirea schemei +----------------- -Și acum să creăm o schemă. Clasa [api:Nette\Schema\Expect] este utilizată pentru a o defini, definim de fapt așteptările cu privire la modul în care ar trebui să arate datele. Să spunem că datele de intrare trebuie să fie o structură (de exemplu, o matrice) care conține elementele `processRefund` de tip bool și `refundAmount` de tip int. +Și acum creăm schema. Pentru definirea ei se utilizează clasa [api:Nette\Schema\Expect], definim de fapt așteptările despre cum ar trebui să arate datele. Să spunem că datele de intrare trebuie să formeze o structură (de exemplu, un array) care conține elementele `processRefund` de tip bool și `refundAmount` de tip int. ```php use Nette\Schema\Expect; @@ -45,9 +45,9 @@ $schema = Expect::structure([ ]); ``` -Credem că definiția schemei pare clară, chiar dacă o vedeți pentru prima dată. +Credem că definiția schemei pare ușor de înțeles, chiar dacă o vedeți pentru prima dată. -Să trimitem următoarele date pentru validare: +Trimitem spre validare următoarele date: ```php $data = [ @@ -55,27 +55,27 @@ $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // Bine, treisprezece. +$normalized = $processor->process($schema, $data); // OK, trece validarea ``` -Rezultatul, adică valoarea `$normalized`, este obiectul `stdClass`. Dacă dorim ca ieșirea să fie o matrice, adăugăm un cast la schema `Expect::structure([...])->castTo('array')`. +Ieșirea, adică valoarea `$normalized`, este un obiect `stdClass`. Dacă am dori ca ieșirea să fie un array, completăm schema cu conversia `Expect::structure([...])->castTo('array')`. -Toate elementele structurii sunt opționale și au o valoare implicită `null`. Exemplu: +Toate elementele structurii sunt opționale și au valoarea implicită `null`. Exemplu: ```php $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // Bine, Trei. +$normalized = $processor->process($schema, $data); // OK, trece validarea // $normalized = {'processRefund' => null, 'refundAmount' => 17} ``` -Faptul că valoarea implicită este `null` nu înseamnă că aceasta ar fi acceptată în datele de intrare `'processRefund' => null`. Nu, datele de intrare trebuie să fie booleene, adică numai `true` sau `false`. Ar trebui să permitem în mod explicit `null` prin `Expect::bool()->nullable()`. +Faptul că valoarea implicită este `null` nu înseamnă că s-ar accepta în datele de intrare `'processRefund' => null`. Nu, intrarea trebuie să fie un boolean, adică doar `true` sau `false`. Pentru a permite `null`, ar trebui să o facem explicit folosind `Expect::bool()->nullable()`. -Un element poate fi făcut obligatoriu folosind `Expect::bool()->required()`. Schimbăm valoarea implicită la `false` folosind `Expect::bool()->default(false)` sau la scurt timp folosind `Expect::bool(false)`. +Elementul poate fi făcut obligatoriu folosind `Expect::bool()->required()`. Valoarea implicită o schimbăm, de exemplu, la `false` folosind `Expect::bool()->default(false)` sau prescurtat `Expect::bool(false)`. -Și dacă am dori să acceptăm `1` and `0` în afară de booleeni? Atunci enumerăm valorile permise, pe care le vom normaliza, de asemenea, la boolean: +Și ce dacă am dori să acceptăm, pe lângă boolean, și `1` și `0`? Atunci specificăm o enumerare a valorilor, pe care le lăsăm, în plus, să fie normalizate la boolean: ```php $schema = Expect::structure([ @@ -87,13 +87,13 @@ $normalized = $processor->process($schema, $data); is_bool($normalized->processRefund); // true ``` -Acum cunoașteți elementele de bază ale modului în care este definită schema și cum se comportă elementele individuale ale structurii. Vom arăta acum ce alte elemente pot fi utilizate în definirea unei scheme. +Acum cunoașteți deja elementele de bază ale modului în care se definește o schemă și cum se comportă elementele individuale ale structurii. Acum vom arăta ce alte elemente pot fi utilizate la definirea schemei. -Tipuri de date: type() .[#toc-data-types-type] ----------------------------------------------- +Tipuri de date: type() +---------------------- -Toate tipurile de date PHP standard pot fi enumerate în schemă: +În schemă se pot specifica toate tipurile de date standard PHP: ```php Expect::string($default = null) @@ -104,63 +104,63 @@ Expect::null() Expect::array($default = []) ``` -Și apoi toate tipurile [acceptate de validatoare |utils:validators#Expected Types] prin `Expect::type('scalar')` sau prin abrevierea `Expect::scalar()`. De asemenea, sunt acceptate și nume de clase sau interfețe, de exemplu `Expect::type('AddressEntity')`. +Și, în plus, toate tipurile [suportate de clasa Validators |utils:validators#Tipuri așteptate], de exemplu `Expect::type('scalar')` sau prescurtat `Expect::scalar()`. De asemenea, numele claselor sau interfețelor, de exemplu `Expect::type('AddressEntity')`. -De asemenea, puteți utiliza notația de uniune: +Se poate utiliza și notația union: ```php Expect::type('bool|string|array') ``` -Valoarea implicită este întotdeauna `null`, cu excepția `array` și `list`, unde este o matrice goală. (O listă este o matrice indexată în ordine crescătoare a cheilor numerice de la zero, adică o matrice neasociativă). +Valoarea implicită este întotdeauna `null`, cu excepția `array` și `list`, unde este un array gol. (List este un array indexat conform unei serii ascendente de chei numerice începând de la zero, adică un array neasociativ). -Array de valori: arrayOf() listOf() .[#toc-array-of-values-arrayof-listof] --------------------------------------------------------------------------- +Array-uri de valori: arrayOf() listOf() +--------------------------------------- -Matricea este o structură prea generală, fiind mai util să se specifice exact ce elemente poate conține. De exemplu, un array ale cărui elemente pot fi doar șiruri de caractere: +Array-ul reprezintă o structură prea generală, este mai util să specificăm exact ce elemente poate conține. De exemplu, un array ale cărui elemente pot fi doar șiruri: ```php $schema = Expect::arrayOf('string'); $processor->process($schema, ['hello', 'world']); // OK $processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK -$processor->process($schema, ['key' => 123]); // ERROR: 123 nu este un șir de caractere +$processor->process($schema, ['key' => 123]); // EROARE: 123 nu este string ``` -Al doilea parametru poate fi utilizat pentru a specifica cheile (începând cu versiunea 1.2): +Al doilea parametru poate specifica cheile (începând cu versiunea 1.2): ```php $schema = Expect::arrayOf('string', 'int'); $processor->process($schema, ['hello', 'world']); // OK -$processor->process($schema, ['a' => 'hello']); // ERROR: 'a' nu este int +$processor->process($schema, ['a' => 'hello']); // EROARE: 'a' nu este int ``` -Lista este o matrice indexată: +List este un array indexat: ```php $schema = Expect::listOf('string'); $processor->process($schema, ['a', 'b']); // OK -$processor->process($schema, ['a', 123]); // ERROR: 123 nu este un șir de caractere -$processor->process($schema, ['key' => 'a']); // ERROR: nu este o listă -$processor->process($schema, [1 => 'a', 0 => 'b']); // ERROR: nu este o listă +$processor->process($schema, ['a', 123]); // EROARE: 123 nu este string +$processor->process($schema, ['key' => 'a']); // EROARE: nu este list +$processor->process($schema, [1 => 'a', 0 => 'b']); // EROARE: de asemenea, nu este list ``` -Parametrul poate fi, de asemenea, o schemă, astfel încât putem scrie: +Parametrul poate fi și o schemă, deci putem scrie: ```php Expect::arrayOf(Expect::bool()) ``` -Valoarea implicită este o matrice goală. Dacă specificați valoarea implicită, aceasta va fi îmbinată cu datele transmise. Acest lucru poate fi dezactivat utilizând `mergeDefaults(false)` (începând cu versiunea 1.1). +Valoarea implicită este un array gol. Dacă specificați o valoare implicită, aceasta va fi combinată cu datele transmise. Acest lucru poate fi dezactivat folosind `mergeDefaults(false)` (începând cu versiunea 1.1). -Enumerare: anyOf() .[#toc-enumeration-anyof] --------------------------------------------- +Enumerare: anyOf() +------------------ -`anyOf()` este un set de valori sau de scheme care pot reprezenta o valoare. Iată cum se scrie o matrice de elemente care pot fi fie `'a'`, `true`, fie `null`: +`anyOf()` reprezintă o enumerare a valorilor sau schemelor pe care le poate lua o valoare. Astfel scriem un array de elemente care pot fi fie `'a'`, `true` sau `null`: ```php $schema = Expect::listOf( @@ -168,10 +168,10 @@ $schema = Expect::listOf( ); $processor->process($schema, ['a', true, null, 'a']); // OK -$processor->process($schema, ['a', false]); // ERROR: false nu are ce căuta acolo +$processor->process($schema, ['a', false]); // EROARE: false nu aparține aici ``` -Elementele enumerării pot fi, de asemenea, scheme: +Elementele enumerării pot fi și scheme: ```php $schema = Expect::listOf( @@ -179,12 +179,12 @@ $schema = Expect::listOf( ); $processor->process($schema, ['foo', true, null, 'bar']); // OK -$processor->process($schema, [123]); // ERROR +$processor->process($schema, [123]); // EROARE ``` -Metoda `anyOf()` acceptă variantele ca parametri individuali, nu ca matrice. Pentru a-i transmite o matrice de valori, utilizați operatorul de despachetare `anyOf(...$variants)`. +Metoda `anyOf()` acceptă variantele ca parametri individuali, nu un array. Dacă doriți să îi transmiteți un array de valori, utilizați operatorul unpacking `anyOf(...$variants)`. -Valoarea implicită este `null`. Utilizați metoda `firstIsDefault()` pentru a face din primul element valoarea implicită: +Valoarea implicită este `null`. Prin metoda `firstIsDefault()` facem primul element implicit: ```php // implicit este 'hello' @@ -192,29 +192,29 @@ Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault(); ``` -Structuri .[#toc-structures] ----------------------------- +Structuri +--------- -Structurile sunt obiecte cu chei definite. Fiecare dintre aceste perechi cheie => valoare este denumită "proprietate": +Structurile sunt obiecte cu chei definite. Fiecare pereche cheie => valoare este denumită „proprietate”: -Structurile acceptă matrici și obiecte și returnează obiecte `stdClass` (dacă nu se modifică cu `castTo('array')`, etc.). +Structurile acceptă array-uri și obiecte și returnează obiecte `stdClass`. -În mod implicit, toate proprietățile sunt opționale și au o valoare implicită de `null`. Puteți defini proprietățile obligatorii utilizând `required()`: +În mod implicit, toate proprietățile sunt opționale și au valoarea implicită `null`. Puteți defini proprietăți obligatorii folosind `required()`: ```php $schema = Expect::structure([ 'required' => Expect::string()->required(), - 'optional' => Expect::string(), // valoarea implicită este nulă + 'optional' => Expect::string(), // valoarea implicită este null ]); $processor->process($schema, ['optional' => '']); -// ERROR: opțiunea 'required' lipsește +// EROARE: opțiunea 'required' lipsește $processor->process($schema, ['required' => 'foo']); // OK, returnează {'required' => 'foo', 'optional' => null} ``` -Dacă nu doriți să afișați proprietăți care au doar o valoare implicită, utilizați `skipDefaults()`: +Dacă nu doriți să aveți în ieșire proprietăți cu valoare implicită, utilizați `skipDefaults()`: ```php $schema = Expect::structure([ @@ -226,7 +226,7 @@ $processor->process($schema, ['required' => 'foo']); // OK, returnează {'required' => 'foo'} ``` -Deși `null` este valoarea implicită a proprietății `optional`, nu este permisă în datele de intrare (valoarea trebuie să fie un șir de caractere). Proprietățile care acceptă `null` sunt definite utilizând `nullable()`: +Deși `null` este valoarea implicită a proprietății `optional`, nu este permis în datele de intrare (valoarea trebuie să fie un șir). Proprietățile care acceptă `null` le definim folosind `nullable()`: ```php $schema = Expect::structure([ @@ -235,12 +235,14 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['optional' => null]); -// ERROR: 'optional' se așteaptă să fie un șir de caractere, dar este nul. +// EROARE: 'optional' se așteaptă să fie string, null dat. $processor->process($schema, ['nullable' => null]); // OK, returnează {'optional' => null, 'nullable' => null} ``` +Array-ul tuturor proprietăților structurii este returnat de metoda `getShape()`. + În mod implicit, nu pot exista elemente suplimentare în datele de intrare: ```php @@ -249,10 +251,10 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['additional' => 1]); -// ERROR: Element neașteptat "suplimentar +// EROARE: Element neașteptat 'additional' ``` -Ceea ce se poate schimba cu `otherItems()`. Ca parametru, vom specifica schema pentru fiecare element suplimentar: +Ceea ce putem schimba folosind `otherItems()`. Ca parametru specificăm schema conform căreia se vor valida elementele suplimentare: ```php $schema = Expect::structure([ @@ -260,72 +262,110 @@ $schema = Expect::structure([ ])->otherItems(Expect::int()); $processor->process($schema, ['additional' => 1]); // OK -$processor->process($schema, ['additional' => true]); // ERROR +$processor->process($schema, ['additional' => true]); // EROARE ``` +Puteți crea o nouă structură derivând dintr-o alta folosind `extend()`: -Deprecieri .[#toc-deprecations] +```php +$dog = Expect::structure([ + 'name' => Expect::string(), + 'age' => Expect::int(), +]); + +$dogWithBreed = $dog->extend([ + 'breed' => Expect::string(), +]); +``` + + +Array-uri .{data-version:1.3.2} ------------------------------- -Puteți deprecia o proprietate folosind opțiunea `deprecated([string $message])` metoda . Notificările de depreciere sunt returnate de `$processor->getWarnings()`: +Array cu chei definite. Pentru el se aplică tot ce [#structuri]. + +```php +$schema = Expect::array([ + 'required' => Expect::string()->required(), + 'optional' => Expect::string(), // valoarea implicită este null +]); +``` + +Se poate defini și un array indexat, cunoscut sub numele de tuple: + +```php +$schema = Expect::array([ + Expect::int(), + Expect::string(), + Expect::bool(), +]); + +$processor->process($schema, [1, 'hello', true]); // OK +``` + + +Proprietăți învechite +--------------------- + +Puteți marca o proprietate ca învechită folosind metoda `deprecated([string $message])`. Informațiile despre încetarea suportului sunt returnate folosind `$processor->getWarnings()`: ```php $schema = Expect::structure([ - 'old' => Expect::int()->deprecated('The item %path% is deprecated'), + 'old' => Expect::int()->deprecated('Elementul %path% este învechit'), ]); $processor->process($schema, ['old' => 1]); // OK -$processor->getWarnings(); // ["The item 'old' is deprecated"] +$processor->getWarnings(); // ["Elementul 'old' este învechit"] ``` -Domenii: min() max() .[#toc-ranges-min-max] -------------------------------------------- +Intervale: min() max() +---------------------- -Utilizați `min()` și `max()` pentru a limita numărul de elemente pentru array-uri: +Folosind `min()` și `max()` se poate limita numărul de elemente la array-uri: ```php -// matrice, cel puțin 10 elemente, maximum 20 elemente +// array, cel puțin 10 elemente, maxim 20 elemente Expect::array()->min(10)->max(20); ``` -Pentru șirurile de caractere, limitați lungimea acestora: +La șiruri se poate limita lungimea lor: ```php -// șir de cel puțin 10 caractere, maxim 20 de caractere +// șir, cel puțin 10 caractere lungime, maxim 20 caractere Expect::string()->min(10)->max(20); ``` -Pentru numere, limitați valoarea acestora: +La numere se poate limita valoarea lor: ```php // număr întreg, între 10 și 20 inclusiv Expect::int()->min(10)->max(20); ``` -Desigur, este posibil să se menționeze doar `min()`, sau doar `max()`: +Desigur, este posibil să specificați doar `min()`, sau doar `max()`: ```php -// șir de caractere, maximum 20 de caractere +// șir maxim 20 caractere Expect::string()->max(20); ``` -Expresii regulate: pattern() .[#toc-regular-expressions-pattern] ----------------------------------------------------------------- +Expresii regulate: pattern() +---------------------------- -Cu ajutorul `pattern()`, puteți specifica o expresie regulată cu care trebuie să se potrivească **întregul** șir de intrare (adică ca și cum ar fi înfășurat în caractere `^` a `$`): +Folosind `pattern()`, puteți specifica o expresie regulată căreia trebuie să îi corespundă **întregul** șir de intrare (adică, ca și cum ar fi încadrat de caracterele `^` și `$`): ```php -// doar 9 cifre +// exact 9 cifre Expect::string()->pattern('\d{9}'); ``` -Aserțiuni personalizate: assert() .[#toc-custom-assertions-assert] ------------------------------------------------------------------- +Restricții personalizate: assert() +---------------------------------- -Puteți adăuga orice alte restricții folosind `assert(callable $fn)`. +Orice alte restricții le specificăm folosind `assert(callable $fn)`. ```php $countIsEven = fn($v) => count($v) % 2 === 0; @@ -334,7 +374,7 @@ $schema = Expect::arrayOf('string') ->assert($countIsEven); // numărul trebuie să fie par $processor->process($schema, ['a', 'b']); // OK -$processor->process($schema, ['a', 'b', 'c']); // ERROR: 3 nu este par +$processor->process($schema, ['a', 'b', 'c']); // EROARE: 3 nu este un număr par ``` Sau @@ -343,95 +383,106 @@ Sau Expect::string()->assert('is_file'); // fișierul trebuie să existe ``` -Puteți adăuga propria descriere pentru fiecare afirmație. Aceasta va face parte din mesajul de eroare. +La fiecare restricție puteți adăuga o descriere personalizată. Aceasta va face parte din mesajul de eroare. ```php $schema = Expect::arrayOf('string') - ->assert($countIsEven, 'Even items in array'); + ->assert($countIsEven, 'Elemente pare în array'); $processor->process($schema, ['a', 'b', 'c']); -// A eșuat aserțiunea "Even items in array" pentru elementul cu valoarea array. +// Aserțiune eșuată "Elemente pare în array" pentru elementul cu valoarea array. ``` -Metoda poate fi apelată în mod repetat pentru a adăuga mai multe afirmații. +Metoda poate fi apelată în mod repetat pentru a adăuga mai multe restricții. Poate fi intercalată cu apeluri `transform()` și `castTo()`. -Maparea în obiecte: from() .[#toc-mapping-to-objects-from] ----------------------------------------------------------- +Transformări: transform() .{data-version:1.2.5} +----------------------------------------------- -Puteți genera o schemă de structură din clasă. Exemplu: +Datele validate cu succes pot fi modificate folosind o funcție personalizată: ```php -class Config -{ - public string $name; - public string|null $password; - public bool $admin = false; -} +// conversie la majuscule: +Expect::string()->transform(fn(string $s) => strtoupper($s)); +``` -$schema = Expect::from(new Config); +Metoda poate fi apelată în mod repetat pentru a adăuga mai multe transformări. Poate fi intercalată cu apeluri `assert()` și `castTo()`. Operațiile se efectuează în ordinea în care sunt declarate: -$data = [ - 'name' => 'jeff', -]; - -$normalized = $processor->process($schema, $data); -// $normalized instanceof Config -// $normalized = {'name' => 'jeff', 'password' => null, 'admin' => false} +```php +Expect::type('string|int') + ->castTo('string') + ->assert('ctype_lower', 'Toate caracterele trebuie să fie minuscule') + ->transform(fn(string $s) => strtoupper($s)); // conversie la majuscule ``` -Dacă utilizați PHP 7.4 sau o versiune mai recentă, puteți utiliza tipuri native: +Metoda `transform()` poate transforma și valida simultan valoarea. Acest lucru este adesea mai simplu și mai puțin duplicat decât înlănțuirea `transform()` și `assert()`. În acest scop, funcția primește un obiect [Context |api:Nette\Schema\Context] cu metoda `addError()`, care poate fi utilizată pentru a adăuga informații despre problemele de validare: ```php -class Config -{ - public string $name; - public ?string $password; - public bool $admin = false; -} +Expect::string() + ->transform(function (string $s, Nette\Schema\Context $context) { + if (!ctype_lower($s)) { + $context->addError('Toate caracterele trebuie să fie minuscule', 'my.case.error'); + return null; + } -$schema = Expect::from(new Config); + return strtoupper($s); + }); ``` -Clasele anonime sunt, de asemenea, acceptate: + +Conversie: castTo() +------------------- + +Datele validate cu succes pot fi convertite: ```php -$schema = Expect::from(new class { - public string $name; - public ?string $password; - public bool $admin = false; -}); +Expect::scalar()->castTo('string'); ``` -Deoarece este posibil ca informațiile obținute din definiția clasei să nu fie suficiente, puteți adăuga o schemă personalizată pentru elemente cu ajutorul celui de-al doilea parametru: +Pe lângă tipurile native PHP, se poate converti și la clase. Se face distincție dacă este o clasă simplă fără constructor sau o clasă cu constructor. Dacă clasa nu are constructor, se creează instanța sa și toate elementele structurii se scriu în proprietăți: ```php -$schema = Expect::from(new Config, [ - 'name' => Expect::string()->pattern('\w:.*'), -]); -``` +class Info +{ + public bool $processRefund; + public int $refundAmount; +} +Expect::structure([ + 'processRefund' => Expect::bool(), + 'refundAmount' => Expect::int(), +])->castTo(Info::class); -Casting: castTo() .[#toc-casting-castto] ----------------------------------------- +// creează '$obj = new Info' și scrie în $obj->processRefund și $obj->refundAmount +``` -Datele validate cu succes pot fi turnate: +Dacă clasa are constructor, elementele structurii se transmit ca parametri numiți constructorului: ```php -Expect::scalar()->castTo('string'); +class Info +{ + public function __construct( + public bool $processRefund, + public int $refundAmount, + ) { + } +} + +// creează $obj = new Info(processRefund: ..., refundAmount: ...) ``` -În plus față de tipurile native PHP, puteți, de asemenea, să faceți cast la clase: +Conversia în combinație cu un parametru scalar creează un obiect și transmite valoarea ca singurul parametru al constructorului: ```php -Expect::scalar()->castTo('AddressEntity'); +Expect::string()->castTo(DateTime::class); +// creează new DateTime(...) ``` -Normalizare: before() .[#toc-normalization-before] --------------------------------------------------- +Normalizare: before() +--------------------- -Înainte de validarea propriu-zisă, datele pot fi normalizate cu ajutorul metodei `before()`. Ca exemplu, să avem un element care trebuie să fie o matrice de șiruri de caractere (de exemplu `['a', 'b', 'c']`), dar care primește datele de intrare sub forma unui șir de caractere `a b c`: +Înainte de validarea propriu-zisă, datele pot fi normalizate folosind metoda `before()`. Ca exemplu, să luăm un element care trebuie să fie un array de șiruri (de exemplu, `['a', 'b', 'c']`), dar acceptă intrarea sub forma șirului `a b c`: ```php $explode = fn($v) => explode(' ', $v); @@ -440,7 +491,48 @@ $schema = Expect::arrayOf('string') ->before($explode); $normalized = $processor->process($schema, 'a b c'); -// OK, returnează ['a', 'b', 'c'] +// OK și returnează ['a', 'b', 'c'] +``` + + +Mapare la obiecte: from() +------------------------- + +Schema structurii o putem lăsa să fie generată dintr-o clasă. Exemplu: + +```php +class Config +{ + public string $name; + public string|null $password; + public bool $admin = false; +} + +$schema = Expect::from(new Config); + +$data = [ + 'name' => 'franta', +]; + +$normalized = $processor->process($schema, $data); +// $normalized instanceof Config +// $normalized = {'name' => 'franta', 'password' => null, 'admin' => false} ``` -{{leftbar: nette:@menu-topics}} +Sunt suportate și clasele anonime: + +```php +$schema = Expect::from(new class { + public string $name; + public ?string $password; + public bool $admin = false; +}); +``` + +Deoarece informațiile obținute din definiția clasei pot să nu fie suficiente, puteți completa elementele cu o schemă proprie folosind al doilea parametru: + +```php +$schema = Expect::from(new Config, [ + 'name' => Expect::string()->pattern('\w:.*'), +]); +``` diff --git a/schema/ro/@meta.texy b/schema/ro/@meta.texy new file mode 100644 index 0000000000..6554692600 --- /dev/null +++ b/schema/ro/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Documentație Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/schema/ru/@home.texy b/schema/ru/@home.texy index a0c7ce1de8..40ea6208f4 100644 --- a/schema/ru/@home.texy +++ b/schema/ru/@home.texy @@ -1,8 +1,8 @@ -Schema: валидация данных -************************ +Nette Schema +************ .[perex] -Практичная библиотека для проверки и нормализации структур данных по заданной схеме с интеллектуальным и простым в понимании API. +Практичная библиотека для валидации и нормализации структур данных по заданной схеме с умным и понятным API. Установка: @@ -11,12 +11,12 @@ composer require nette/schema ``` -Использование -------------- +Основное использование +---------------------- -В переменной `$schema` у нас есть схема валидации (что именно это значит и как её создать, мы расскажем позже), а в переменной `$data` у нас есть структура данных, которую мы хотим валидировать и нормализовать. Это могут быть, например, данные, отправленные пользователем через API, конфигурационный файл и т. д. +В переменной `$schema` у нас есть схема валидации (что это точно означает и как создать такую схему, мы расскажем вскоре), а в переменной `$data` — структура данных, которую мы хотим валидировать и нормализовать. Это могут быть, например, данные, отправленные пользователем через API, конфигурационный файл и т.д. -Задачей занимается класс [api:Nette\Schema\Processor], который обрабатывает входные данные и либо возвращает нормализованные данные, либо выбрасывает исключение [api:Nette\Schema\ValidationException] при ошибке. +Задачу выполнит класс [api:Nette\Schema\Processor], который обработает входные данные и либо вернет нормализованные данные, либо в случае ошибки выбросит исключение [api:Nette\Schema\ValidationException]. ```php $processor = new Nette\Schema\Processor; @@ -24,17 +24,17 @@ $processor = new Nette\Schema\Processor; try { $normalized = $processor->process($schema, $data); } catch (Nette\Schema\ValidationException $e) { - echo 'Data is invalid: ' . $e->getMessage(); + echo 'Данные недействительны: ' . $e->getMessage(); } ``` -Метод `$e->getMessages()` возвращает массив всех строк сообщений, а `$e->getMessageObjects()` возвращает все сообщения в виде объектов "Nette\Schema\Message":https://api.nette.org/schema/master/Nette/Schema/Message.html. +Метод `$e->getMessages()` возвращает массив всех сообщений в виде строк, а `$e->getMessageObjects()` возвращает все сообщения как объекты "Nette\Schema\Message":https://api.nette.org/schema/master/Nette/Schema/Message.html. Определение схемы ----------------- -А теперь давайте создадим схему. С помощью класса [api:Nette\Schema\Expect] мы фактически определяем, как должны выглядеть данные. Предположим, что входные данные должны представлять собой структуру (например, массив), содержащий элементы `processRefund` типа bool и `refundAmount` типа int. +А теперь создадим схему. Для ее определения служит класс [api:Nette\Schema\Expect], мы фактически определяем ожидания, как должны выглядеть данные. Скажем, входные данные должны образовывать структуру (например, массив), содержащую элементы `processRefund` типа bool и `refundAmount` типа int. ```php use Nette\Schema\Expect; @@ -45,9 +45,9 @@ $schema = Expect::structure([ ]); ``` -Мы считаем, что определение схемы выглядит понятным, даже если вы видите его в первый раз. +Мы верим, что определение схемы выглядит понятно, даже если вы видите его впервые. -Отправим следующие данные для проверки: +Отправим на валидацию следующие данные: ```php $data = [ @@ -55,10 +55,10 @@ $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // OK, проходит +$normalized = $processor->process($schema, $data); // OK, пройдет валидацию ``` -Выход, т. е. значением `$normalized`, является объект `stdClass`. Если мы хотим, чтобы результатом был массив, мы добавляем приведение к схеме `Expect::structure([...])->castTo('array')`. +Выводом, то есть значением `$normalized`, является объект `stdClass`. Если бы мы хотели, чтобы выводом был массив, мы бы дополнили схему приведением типов `Expect::structure([...])->castTo('array')`. Все элементы структуры являются необязательными и имеют значение по умолчанию `null`. Пример: @@ -67,15 +67,15 @@ $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // OK, проходит +$normalized = $processor->process($schema, $data); // OK, пройдет валидацию // $normalized = {'processRefund' => null, 'refundAmount' => 17} ``` -Тот факт, что значением по умолчанию является `null`, не означает, что оно будет принято во входных данных `'processRefund' => null`. Нет, входные данные должны быть булевыми, т. е. только `true` или `false`. Нам пришлось бы явно разрешить `null` через `Expect::bool()->nullable()`. +То, что значением по умолчанию является `null`, не означает, что во входных данных будет принято `'processRefund' => null`. Нет, входными данными должен быть boolean, то есть только `true` или `false`. Разрешить `null` нам пришлось бы явно с помощью `Expect::bool()->nullable()`. -Элемент можно сделать обязательным, используя `Expect::bool()->required()`. Мы меняем значение по умолчанию на `false`, используя `Expect::bool()->default(false)` или коротко `Expect::bool(false)`. +Элемент можно сделать обязательным с помощью `Expect::bool()->required()`. Значение по умолчанию изменим, например, на `false` с помощью `Expect::bool()->default(false)` или сокращенно `Expect::bool(false)`. -А что если мы захотим принимать `1` и `0` помимо булевых чисел? Перечислим допустимые значения, которые мы также нормализуем в boolean: +А что, если бы мы хотели помимо boolean принимать еще `1` и `0`? Тогда мы укажем перечисление значений, которые к тому же нормализуем в boolean: ```php $schema = Expect::structure([ @@ -87,13 +87,13 @@ $normalized = $processor->process($schema, $data); is_bool($normalized->processRefund); // true ``` -Теперь вы знаете основы того, как определяется схема и как ведут себя отдельные элементы структуры. Теперь мы покажем, какие ещё элементы могут быть использованы при определении схемы. +Теперь вы знаете основы того, как определяется схема и как ведут себя отдельные элементы структуры. Теперь мы покажем, какие еще элементы можно использовать при определении схемы. Типы данных: type() ------------------- -Все стандартные типы данных PHP могут быть перечислены в схеме: +В схеме можно указать все стандартные типы данных PHP: ```php Expect::string($default = null) @@ -104,63 +104,63 @@ Expect::null() Expect::array($default = []) ``` -А затем все типы, [поддерживаемые валидаторами |utils:validators#Expected-Types] через `Expect::type('scalar')` или сокращенно `Expect::scalar()`. Также принимаются имена классов или интерфейсов, например: `Expect::type('AddressEntity')`. +А также все типы, [поддерживаемые классом Validators |utils:validators#Ожидаемые типы], например `Expect::type('scalar')` или сокращенно `Expect::scalar()`. Также имена классов или интерфейсов, например `Expect::type('AddressEntity')`. -Вы также можете использовать нотацию объединения: +Можно использовать и union-запись: ```php Expect::type('bool|string|array') ``` -Значение по умолчанию всегда `null`, за исключением `array` и `list`, где это пустой массив. (Список — это массив, индексированный в порядке возрастания числовых ключей от нуля, то есть неассоциативный массив). +Значение по умолчанию всегда `null`, за исключением `array` и `list`, где это пустой массив. (List — это массив, индексированный по возрастающей последовательности числовых ключей от нуля, то есть неассоциативный массив). Массив значений: arrayOf() listOf() ----------------------------------- -Массив — слишком общая структура, полезнее указать, какие именно элементы он может содержать. Например, массив, элементами которого могут быть только строки: +Массив представляет собой слишком общую структуру, полезнее указать, какие именно элементы он может содержать. Например, массив, элементы которого могут быть только строками: ```php $schema = Expect::arrayOf('string'); $processor->process($schema, ['hello', 'world']); // OK $processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK -$processor->process($schema, ['key' => 123]); // ОШИБКА: 123 не строка +$processor->process($schema, ['key' => 123]); // ОШИБКА: 123 не является строкой ``` -Второй параметр может использоваться для указания ключей (начиная с версии 1.2): +Вторым параметром можно указать ключи (с версии 1.2): ```php $schema = Expect::arrayOf('string', 'int'); $processor->process($schema, ['hello', 'world']); // OK -$processor->process($schema, ['a' => 'hello']); // ОШИБКА: 'a' не int +$processor->process($schema, ['a' => 'hello']); // ОШИБКА: 'a' не является int ``` -Список представляет собой индексированный массив: +List — это индексированный массив: ```php $schema = Expect::listOf('string'); $processor->process($schema, ['a', 'b']); // OK -$processor->process($schema, ['a', 123]); // ОШИБКА: 123 не строка -$processor->process($schema, ['key' => 'a']); // ОШИБКА: не список -$processor->process($schema, [1 => 'a', 0 => 'b']); // ОШИБКА: не список +$processor->process($schema, ['a', 123]); // ОШИБКА: 123 не является строкой +$processor->process($schema, ['key' => 'a']); // ОШИБКА: не является списком +$processor->process($schema, [1 => 'a', 0 => 'b']); // ОШИБКА: также не является списком ``` -Параметр также может быть схемой, поэтому мы можем написать: +Параметром может быть и схема, таким образом, мы можем записать: ```php Expect::arrayOf(Expect::bool()) ``` -Значение по умолчанию — пустой массив. Если вы укажете значение по умолчанию, оно будет объединено с переданными данными. Это можно отключить с помощью `mergeDefaults(false)` (начиная с версии 1.1). +Значение по умолчанию — пустой массив. Если вы зададите значение по умолчанию, оно будет объединено с переданными данными. Это можно деактивировать с помощью `mergeDefaults(false)` (с версии 1.1). Перечисление: anyOf() --------------------- -`anyOf()` — это набор значений или схем, которыми может быть значение. Вот как записать массив элементов, которые могут быть либо `'a'`, либо `true`, либо `null`: +`anyOf()` представляет собой перечисление значений или схем, которые может принимать значение. Так мы запишем массив элементов, которые могут быть либо `'a'`, `true` или `null`: ```php $schema = Expect::listOf( @@ -168,10 +168,10 @@ $schema = Expect::listOf( ); $processor->process($schema, ['a', true, null, 'a']); // OK -$processor->process($schema, ['a', false]); // ОШИБКА: false тут не место +$processor->process($schema, ['a', false]); // ОШИБКА: false здесь недопустимо ``` -Элементы перечисления также могут быть схемами: +Элементы перечисления могут быть и схемами: ```php $schema = Expect::listOf( @@ -182,12 +182,12 @@ $processor->process($schema, ['foo', true, null, 'bar']); // OK $processor->process($schema, [123]); // ОШИБКА ``` -Метод `anyOf()` принимает варианты как отдельные параметры, а не как массив. Чтобы передать ему массив значений, используйте оператор распаковки `anyOf(...$variants)`. +Метод `anyOf()` принимает варианты как отдельные параметры, а не массив. Если вы хотите передать ему массив значений, используйте оператор распаковки `anyOf(...$variants)`. -Значение по умолчанию — `null`. Используйте метод `firstIsDefault()`, чтобы сделать первый элемент элементом по умолчанию: +Значение по умолчанию — `null`. Методом `firstIsDefault()` мы сделаем первый элемент значением по умолчанию: ```php -// default is 'hello' +// значение по умолчанию 'hello' Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault(); ``` @@ -195,16 +195,16 @@ Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault(); Структуры --------- -Структуры — это объекты с определенными ключами. Каждая из этих пар ключ => значение называется "свойством": +Структуры — это объекты с определенными ключами. Каждая из пар ключ => значение называется «свойством»: -Структуры принимают массивы и объекты и возвращают объекты `stdClass` (если вы не измените его с помощью `castTo('array')` и т. д.). +Структуры принимают массивы и объекты и возвращают объекты `stdClass`. -По умолчанию все свойства являются необязательными и имеют значение по умолчанию `null`. Вы можете определить обязательные свойства, используя `required()`: +По умолчанию все свойства являются необязательными и имеют значение по умолчанию `null`. Обязательные свойства можно определить с помощью `required()`: ```php $schema = Expect::structure([ 'required' => Expect::string()->required(), - 'optional' => Expect::string(), // значение по умолчанию — null + 'optional' => Expect::string(), // значение по умолчанию null ]); $processor->process($schema, ['optional' => '']); @@ -214,7 +214,7 @@ $processor->process($schema, ['required' => 'foo']); // OK, возвращает {'required' => 'foo', 'optional' => null} ``` -Если вы не хотите выводить свойства только со значением по умолчанию, используйте `skipDefaults()`: +Если вы не хотите иметь в выводе свойства со значением по умолчанию, используйте `skipDefaults()`: ```php $schema = Expect::structure([ @@ -226,7 +226,7 @@ $processor->process($schema, ['required' => 'foo']); // OK, возвращает {'required' => 'foo'} ``` -Хотя `null` является значением по умолчанию свойства `optional`, оно не допускается во входных данных (значение должно быть строкой). Свойства, принимающие значение `null`, определяются с помощью `nullable()`: +Хотя `null` является значением по умолчанию для свойства `optional`, во входных данных он не разрешен (значением должна быть строка). Свойства, принимающие `null`, определяем с помощью `nullable()`: ```php $schema = Expect::structure([ @@ -235,13 +235,15 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['optional' => null]); -// ОШИБКА: 'optional' ожидается как строка, а предоставляется null. +// ОШИБКА: 'optional' ожидает строку, получен null. $processor->process($schema, ['nullable' => null]); // OK, возвращает {'optional' => null, 'nullable' => null} ``` -По умолчанию, во входных данных не может быть лишних элементов: +Массив всех свойств структуры возвращает метод `getShape()`. + +По умолчанию во входных данных не может быть лишних элементов: ```php $schema = Expect::structure([ @@ -252,7 +254,7 @@ $processor->process($schema, ['additional' => 1]); // ОШИБКА: Неожиданный элемент 'additional' ``` -Подобные элементы изменить с помощью `otherItems()`. В качестве параметра мы укажем схему для каждого дополнительного элемента: +Что мы можем изменить с помощью `otherItems()`. В качестве параметра укажем схему, по которой будут валидироваться дополнительные элементы: ```php $schema = Expect::structure([ @@ -263,11 +265,49 @@ $processor->process($schema, ['additional' => 1]); // OK $processor->process($schema, ['additional' => true]); // ОШИБКА ``` +Новую структуру можно создать, унаследовав от другой с помощью `extend()`: + +```php +$dog = Expect::structure([ + 'name' => Expect::string(), + 'age' => Expect::int(), +]); + +$dogWithBreed = $dog->extend([ + 'breed' => Expect::string(), +]); +``` + + +Массив .{data-version:1.3.2} +---------------------------- + +Массив с определенными ключами. К нему применимо все то же, что и к [структурам |#Структуры]. + +```php +$schema = Expect::array([ + 'required' => Expect::string()->required(), + 'optional' => Expect::string(), // значение по умолчанию null +]); +``` + +Можно определить также индексированный массив, известный как кортеж (tuple): -Устаревшие элементы .{data-version:1.1} ---------------------------------------- +```php +$schema = Expect::array([ + Expect::int(), + Expect::string(), + Expect::bool(), +]); -Вы можете объявить свойство устаревшим, используя метод `deprecated([string $message])`. Уведомления об устаревании возвращаются с помощью `$processor->getWarnings()`: +$processor->process($schema, [1, 'hello', true]); // OK +``` + + +Устаревшие свойства +------------------- + +Вы можете пометить свойство как устаревшее (deprecated) с помощью метода `deprecated([string $message])`. Информация о прекращении поддержки возвращается с помощью `$processor->getWarnings()`: ```php $schema = Expect::structure([ @@ -282,31 +322,31 @@ $processor->getWarnings(); // ["Элемент 'old' устарел"] Диапазоны: min() max() ---------------------- -Используйте `min()` и `max()` для ограничения количества элементов в массивах: +С помощью `min()` и `max()` можно у массивов ограничить количество элементов: ```php // массив, минимум 10 элементов, максимум 20 элементов Expect::array()->min(10)->max(20); ``` -Для строк ограничивает их длину: +У строк ограничить их длину: ```php -// строка, длиной не менее 10 символов, максимум 20 символов +// строка длиной не менее 10 символов, не более 20 символов Expect::string()->min(10)->max(20); ``` -Для чисел ограничивает их значение: +У чисел ограничить их значение: ```php // целое число, от 10 до 20 включительно Expect::int()->min(10)->max(20); ``` -Конечно, можно упомянуть только `min()`, или только `max()`: +Конечно, можно указать только `min()`, или только `max()`: ```php -// строка, максимум 20 символов +// строка не более 20 символов Expect::string()->max(20); ``` @@ -314,27 +354,27 @@ Expect::string()->max(20); Регулярные выражения: pattern() ------------------------------- -Используя `pattern()`, вы можете указать регулярное выражение, которому должна соответствовать **вся** входная строка (т.е. как если бы она была завернута в символы `^` a `$`): +С помощью `pattern()` можно указать регулярное выражение, которому должна соответствовать **вся** входная строка (то есть, как если бы она была заключена в символы `^` и `$`): ```php -// только 9 цифр +// ровно 9 цифр Expect::string()->pattern('\d{9}'); ``` -Пользовательские утверждения: assert() +Пользовательские ограничения: assert() -------------------------------------- -Вы можете добавить любые другие ограничения, используя `assert(callable $fn)`. +Любые другие ограничения зададим с помощью `assert(callable $fn)`. ```php $countIsEven = fn($v) => count($v) % 2 === 0; $schema = Expect::arrayOf('string') - ->assert($countIsEven); // число должно быть чётным + ->assert($countIsEven); // количество должно быть четным $processor->process($schema, ['a', 'b']); // OK -$processor->process($schema, ['a', 'b', 'c']); // ОШИБКА: 3 - нечётное число +$processor->process($schema, ['a', 'b', 'c']); // ОШИБКА: 3 - нечетное количество ``` Или @@ -343,95 +383,106 @@ $processor->process($schema, ['a', 'b', 'c']); // ОШИБКА: 3 - нечётн Expect::string()->assert('is_file'); // файл должен существовать ``` -Вы можете добавить собственное описание для каждого утверждения. Оно будет частью сообщения об ошибке. +К каждому ограничению можно добавить собственное описание. Оно будет частью сообщения об ошибке. ```php $schema = Expect::arrayOf('string') - ->assert($countIsEven, 'Четные элементы в массиве'); + ->assert($countIsEven, 'Четное количество элементов в массиве'); $processor->process($schema, ['a', 'b', 'c']); -// Неудачное утверждение "Четные элементы в массиве" для элемента с массивом значений. +// Не пройдено утверждение "Четное количество элементов в массиве" для элемента со значением array. ``` -Этот метод можно вызывать многократно для добавления новых утверждений. +Метод можно вызывать повторно и так добавить несколько ограничений. Его можно чередовать с вызовами `transform()` и `castTo()`. -Сопоставление с объектами: from() ---------------------------------- +Трансформации: transform() .{data-version:1.2.5} +------------------------------------------------ -Из класса можно сгенерировать схему структуры. Пример: +Успешно провалидированные данные можно изменять с помощью пользовательской функции: ```php -class Config -{ - public string $name; - public string|null $password; - public bool $admin = false; -} +// преобразование в верхний регистр: +Expect::string()->transform(fn(string $s) => strtoupper($s)); +``` -$schema = Expect::from(new Config); +Метод можно вызывать повторно и так добавить несколько трансформаций. Его можно чередовать с вызовами `assert()` и `castTo()`. Операции выполняются в том порядке, в котором они объявлены: -$data = [ - 'name' => 'jeff', -]; - -$normalized = $processor->process($schema, $data); -// $normalized instanceof Config -// $normalized = {'name' => 'jeff', 'password' => null, 'admin' => false} +```php +Expect::type('string|int') + ->castTo('string') + ->assert('ctype_lower', 'Все символы должны быть в нижнем регистре') + ->transform(fn(string $s) => strtoupper($s)); // преобразование в верхний регистр ``` -Если вы используете PHP 7.4 или выше, вы можете использовать встроенные типы: +Метод `transform()` может одновременно трансформировать и валидировать значение. Это часто проще и менее избыточно, чем цепочка вызовов `transform()` и `assert()`. Для этой цели функция получает объект [Context |api:Nette\Schema\Context] с методом `addError()`, который можно использовать для добавления информации о проблемах валидации: ```php -class Config -{ - public string $name; - public ?string $password; - public bool $admin = false; -} +Expect::string() + ->transform(function (string $s, Nette\Schema\Context $context) { + if (!ctype_lower($s)) { + $context->addError('Все символы должны быть в нижнем регистре', 'my.case.error'); + return null; + } -$schema = Expect::from(new Config); + return strtoupper($s); + }); ``` -Поддерживаются также анонимные классы: + +Приведение типов: castTo() +-------------------------- + +Успешно провалидированные данные можно привести к типу: ```php -$schema = Expect::from(new class { - public string $name; - public ?string $password; - public bool $admin = false; -}); +Expect::scalar()->castTo('string'); ``` -Поскольку информации, полученной из определения класса, может быть недостаточно, вы можете добавить пользовательскую схему для элементов с помощью второго параметра: +Помимо встроенных типов PHP, можно приводить и к классам. При этом различается, является ли это простым классом без конструктора, или классом с конструктором. Если у класса нет конструктора, создается его экземпляр и все элементы структуры записываются в свойства: ```php -$schema = Expect::from(new Config, [ - 'name' => Expect::string()->pattern('\w:.*'), -]); -``` +class Info +{ + public bool $processRefund; + public int $refundAmount; +} +Expect::structure([ + 'processRefund' => Expect::bool(), + 'refundAmount' => Expect::int(), +])->castTo(Info::class); -Приведение: castTo() --------------------- +// создаст '$obj = new Info' и запишет в $obj->processRefund и $obj->refundAmount +``` -Успешно проверенные данные могут быть отброшены: +Если у класса есть конструктор, элементы структуры передаются как именованные параметры конструктору: ```php -Expect::scalar()->castTo('string'); +class Info +{ + public function __construct( + public bool $processRefund, + public int $refundAmount, + ) { + } +} + +// создаст $obj = new Info(processRefund: ..., refundAmount: ...) ``` -В дополнение к собственным типам PHP, вы также можете приводить к классам: +Приведение типов в сочетании со скалярным параметром создает объект и передает значение как единственный параметр конструктору: ```php -Expect::scalar()->castTo('AddressEntity'); +Expect::string()->castTo(DateTime::class); +// создаст new DateTime(...) ``` Нормализация: before() ---------------------- -Перед самой проверкой данные могут быть нормализованы с помощью метода `before()`. В качестве примера, пусть есть элемент, который должен быть массивом строк (например, `['a', 'b', 'c']`), но получает входные данные в виде строки `a b c`: +Перед самой валидацией данные можно нормализовать с помощью метода `before()`. В качестве примера приведем элемент, который должен быть массивом строк (например, `['a', 'b', 'c']`), но принимает ввод в виде строки `a b c`: ```php $explode = fn($v) => explode(' ', $v); @@ -440,7 +491,48 @@ $schema = Expect::arrayOf('string') ->before($explode); $normalized = $processor->process($schema, 'a b c'); -// OK, возвращает ['a', 'b', 'c'] +// OK и вернет ['a', 'b', 'c'] +``` + + +Отображение на объекты: from() +------------------------------ + +Схему структуры можно сгенерировать из класса. Пример: + +```php +class Config +{ + public string $name; + public string|null $password; + public bool $admin = false; +} + +$schema = Expect::from(new Config); + +$data = [ + 'name' => 'franta', +]; + +$normalized = $processor->process($schema, $data); +// $normalized instanceof Config +// $normalized = {'name' => 'franta', 'password' => null, 'admin' => false} ``` -{{leftbar: nette:@menu-topics}} +Поддерживаются и анонимные классы: + +```php +$schema = Expect::from(new class { + public string $name; + public ?string $password; + public bool $admin = false; +}); +``` + +Поскольку информации, полученной из определения класса, может быть недостаточно, вы можете вторым параметром дополнить элементы собственной схемой: + +```php +$schema = Expect::from(new Config, [ + 'name' => Expect::string()->pattern('\w:.*'), +]); +``` diff --git a/schema/ru/@meta.texy b/schema/ru/@meta.texy new file mode 100644 index 0000000000..61577d6323 --- /dev/null +++ b/schema/ru/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Документация Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/schema/sl/@home.texy b/schema/sl/@home.texy index 9af7ea6247..e61da92667 100644 --- a/schema/sl/@home.texy +++ b/schema/sl/@home.texy @@ -1,8 +1,8 @@ -Shema: Potrjevanje podatkov -*************************** +Nette Schema +************ .[perex] -Praktična knjižnica za potrjevanje in normalizacijo podatkovnih struktur glede na dano shemo s pametnim in razumljivim API-jem. +Praktična knjižnica za validacijo in normalizacijo podatkovnih struktur glede na dano shemo s pametnim razumljivim API-jem. Namestitev: @@ -11,12 +11,12 @@ composer require nette/schema ``` -Osnovna uporaba .[#toc-basic-usage] ------------------------------------ +Osnovna uporaba +--------------- -V spremenljivki `$schema` imamo shemo potrjevanja (kaj točno to pomeni in kako jo ustvariti, bomo povedali pozneje), v spremenljivki `$data` pa imamo podatkovno strukturo, ki jo želimo potrditi in normalizirati. To so lahko na primer podatki, ki jih uporabnik pošlje prek vmesnika API, konfiguracijske datoteke itd. +V spremenljivki `$schema` imamo validacijsko shemo (kaj točno to pomeni in kako tako shemo ustvariti, si bomo povedali kmalu) in v spremenljivki `$data` podatkovno strukturo, ki jo želimo validirati in normalizirati. Lahko gre na primer za podatke, ki jih je uporabnik poslal prek vmesnika API, konfiguracijsko datoteko itd. -Nalogo opravi razred [api:Nette\Schema\Processor], ki obdela vhodne podatke in vrne normalizirane podatke ali pa ob napaki vrže izjemo [api:Nette\Schema\ValidationException]. +Nalogo opravi razred [api:Nette\Schema\Processor], ki obdela vhod in bodisi vrne normalizirane podatke, bodisi v primeru napake vrže izjemo [api:Nette\Schema\ValidationException]. ```php $processor = new Nette\Schema\Processor; @@ -24,17 +24,17 @@ $processor = new Nette\Schema\Processor; try { $normalized = $processor->process($schema, $data); } catch (Nette\Schema\ValidationException $e) { - echo 'Data is invalid: ' . $e->getMessage(); + echo 'Podatki niso veljavni: ' . $e->getMessage(); } ``` -Metoda `$e->getMessages()` vrne polje vseh nizov sporočil, metoda `$e->getMessageObjects()` pa vrne vsa sporočila kot objekte "Nette\Schema\Message":https://api.nette.org/schema/master/Nette/Schema/Message.html. +Metoda `$e->getMessages()` vrne polje vseh sporočil kot nize in `$e->getMessageObjects()` vrne vsa sporočila kot objekte "Nette\Schema\Message":https://api.nette.org/schema/master/Nette/Schema/Message.html. -Opredelitev sheme .[#toc-defining-schema] ------------------------------------------ +Definiranje sheme +----------------- -Zdaj pa ustvarimo shemo. Za njeno opredelitev uporabljamo razred [api:Nette\Schema\Expect], s katerim pravzaprav definiramo pričakovanja, kako naj bi bili podatki videti. Recimo, da morajo biti vhodni podatki struktura (npr. polje), ki vsebuje elemente `processRefund` tipa bool in `refundAmount` tipa int. +In zdaj ustvarimo shemo. Za njeno definiranje služi razred [api:Nette\Schema\Expect], definiramo pravzaprav pričakovanja, kako naj podatki izgledajo. Recimo, da morajo vhodni podatki tvoriti strukturo (na primer polje), ki vsebuje elementa `processRefund` tipa bool in `refundAmount` tipa int. ```php use Nette\Schema\Expect; @@ -45,9 +45,9 @@ $schema = Expect::structure([ ]); ``` -Verjamemo, da je opredelitev sheme videti jasna, tudi če jo vidite prvič. +Verjamemo, da definicija sheme izgleda razumljivo, tudi če jo vidite prvič. -V potrditev pošljimo naslednje podatke: +Pošljimo k validaciji naslednje podatke: ```php $data = [ @@ -55,10 +55,10 @@ $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // V redu, je uspešno. +$normalized = $processor->process($schema, $data); // OK, gre skozi validacijo ``` -Izhodni podatek, tj. vrednost `$normalized`, je objekt `stdClass`. Če želimo, da je izhodni podatek polje, dodamo cast v shemo `Expect::structure([...])->castTo('array')`. +Izhod, torej vrednost `$normalized`, je objekt `stdClass`. Če bi želeli, da bi bil izhod polje, dopolnimo shemo s pretvorbo `Expect::structure([...])->castTo('array')`. Vsi elementi strukture so neobvezni in imajo privzeto vrednost `null`. Primer: @@ -67,15 +67,15 @@ $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // V redu, je uspešno. +$normalized = $processor->process($schema, $data); // OK, gre skozi validacijo // $normalized = {'processRefund' => null, 'refundAmount' => 17} ``` -Dejstvo, da je privzeta vrednost `null`, še ne pomeni, da bi bila sprejeta v vhodnih podatkih `'processRefund' => null`. Ne, vhodni podatki morajo biti logične vrednosti, torej samo `true` ali `false`. Izrecno bi morali dovoliti `null` prek `Expect::bool()->nullable()`. +To, da je privzeta vrednost `null`, ne pomeni, da bi se sprejelo v vhodnih podatkih `'processRefund' => null`. Ne, vhod mora biti boolean, torej samo `true` ali `false`. Dovoliti `null` bi morali eksplicitno s pomočjo `Expect::bool()->nullable()`. -Element lahko postane obvezen z uporabo `Expect::bool()->required()`. Privzeto vrednost spremenimo v `false` z uporabo `Expect::bool()->default(false)` ali kratko s `Expect::bool(false)`. +Element lahko naredimo obvezen s pomočjo `Expect::bool()->required()`. Privzeto vrednost spremenimo na primer na `false` s pomočjo `Expect::bool()->default(false)` ali skrajšano `Expect::bool(false)`. -Kaj pa, če bi poleg boolov želeli sprejeti tudi `1` and `0`? Potem naštejemo dovoljene vrednosti, ki jih bomo prav tako normalizirali v boolean: +In kaj, če bi poleg booleana želeli sprejeti še `1` in `0`? Potem navedemo naštevne vrednosti, ki jih poleg tega pustimo normalizirati na boolean: ```php $schema = Expect::structure([ @@ -87,13 +87,13 @@ $normalized = $processor->process($schema, $data); is_bool($normalized->processRefund); // true ``` -Zdaj poznate osnove, kako je shema opredeljena in kako se obnašajo posamezni elementi strukture. Zdaj bomo pokazali, katere vse druge elemente je mogoče uporabiti pri opredeljevanju sheme. +Zdaj že poznate osnove tega, kako se definira shema in kako se obnašajo posamezni elementi strukture. Zdaj si bomo pokazali, katere vse druge elemente lahko uporabimo pri definiciji sheme. -Podatkovni tipi: type() .[#toc-data-types-type] ------------------------------------------------ +Podatkovni tipi: type() +----------------------- -V shemi so lahko navedeni vsi standardni podatkovni tipi PHP: +V shemi lahko navedete vse standardne podatkovne tipe PHP: ```php Expect::string($default = null) @@ -104,71 +104,71 @@ Expect::null() Expect::array($default = []) ``` -Nato pa še vse tipe, ki jih [podpirajo validatorji |utils:validators#Expected Types] prek `Expect::type('scalar')` ali skrajšano `Expect::scalar()`. Sprejemljiva so tudi imena razredov ali vmesnikov, npr. `Expect::type('AddressEntity')`. +In nadalje vse tipe, [podprte s strani razreda Validators |utils:validators#Pričakovani tipi], na primer `Expect::type('scalar')` ali skrajšano `Expect::scalar()`. Tudi imena razredov ali vmesnikov, na primer `Expect::type('AddressEntity')`. -Uporabite lahko tudi zapis union: +Lahko uporabite tudi union zapis: ```php Expect::type('bool|string|array') ``` -Privzeta vrednost je vedno `null`, razen za `array` in `list`, kjer je to prazno polje. (Seznam je polje, indeksirano v naraščajočem vrstnem redu številskih ključev od nič, torej neasociativno polje). +Privzeta vrednost je vedno `null` z izjemo za `array` in `list`, kjer je to prazno polje. (List je polje, indeksirano po naraščajočem zaporedju numeričnih ključev od nič, torej neasociativno polje). -Polje vrednosti: arrayOf() listOf() .[#toc-array-of-values-arrayof-listof] --------------------------------------------------------------------------- +Polja vrednosti: arrayOf() listOf() +----------------------------------- -Polje je preveč splošna struktura, zato je koristneje natančno določiti, katere elemente lahko vsebuje. Na primer polje, katerega elementi so lahko samo nizi: +Polje predstavlja preveč splošno strukturo, bolj uporabno je specificirati, katere točno elemente lahko vsebuje. Na primer polje, katerega elementi so lahko samo nizi: ```php $schema = Expect::arrayOf('string'); -$processor->process($schema, ['hello', 'world']); // V REDU +$processor->process($schema, ['hello', 'world']); // OK $processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK -$processor->process($schema, ['key' => 123]); // ERROR: 123 ni niz +$processor->process($schema, ['key' => 123]); // NAPAKA: 123 ni niz ``` -Drugi parameter se lahko uporabi za določitev ključev (od različice 1.2): +Z drugim parametrom lahko specificirate ključe (od različice 1.2): ```php $schema = Expect::arrayOf('string', 'int'); -$processor->process($schema, ['hello', 'world']); // V REDU +$processor->process($schema, ['hello', 'world']); // OK $processor->process($schema, ['a' => 'hello']); // NAPAKA: 'a' ni int ``` -Seznam je indeksirano polje: +List je indeksirano polje: ```php $schema = Expect::listOf('string'); -$processor->process($schema, ['a', 'b']); // V REDU +$processor->process($schema, ['a', 'b']); // OK $processor->process($schema, ['a', 123]); // NAPAKA: 123 ni niz -$processor->process($schema, ['key' => 'a']); // NAPAKA: ni seznam -$processor->process($schema, [1 => 'a', 0 => 'b']); // ERROR: ni seznam +$processor->process($schema, ['key' => 'a']); // NAPAKA: ni list +$processor->process($schema, [1 => 'a', 0 => 'b']); // NAPAKA: tudi ni list ``` -Parameter je lahko tudi shema, tako da lahko zapišemo: +Parameter je lahko tudi shema, torej lahko zapišemo: ```php Expect::arrayOf(Expect::bool()) ``` -Privzeta vrednost je prazno polje. Če določite privzeto vrednost, bo združena s posredovanimi podatki. To lahko onemogočite z uporabo `mergeDefaults(false)` (od različice 1.1). +Privzeta vrednost je prazno polje. Če podate privzeto vrednost, bo združena s predanimi podatki. To lahko deaktivirate z `mergeDefaults(false)` (od različice 1.1). -Izštevanje: anyOf() .[#toc-enumeration-anyof] ---------------------------------------------- +Naštevanje: anyOf() +------------------- -`anyOf()` je nabor vrednosti ali shem, ki jih lahko ima vrednost. Tukaj je prikazano, kako zapisati polje elementov, ki so lahko `'a'`, `true` ali `null`: +`anyOf()` predstavlja naštevanje vrednosti ali shem, ki jih lahko vrednost prevzame. Tako zapišemo polje elementov, ki so lahko bodisi `'a'`, `true` ali `null`: ```php $schema = Expect::listOf( Expect::anyOf('a', true, null), ); -$processor->process($schema, ['a', true, null, 'a']); // V REDU -$processor->process($schema, ['a', false]); // ERROR: false ne sodi tja +$processor->process($schema, ['a', true, null, 'a']); // OK +$processor->process($schema, ['a', false]); // NAPAKA: false ne spada sem ``` Elementi naštevanja so lahko tudi sheme: @@ -178,13 +178,13 @@ $schema = Expect::listOf( Expect::anyOf(Expect::string(), true, null), ); -$processor->process($schema, ['foo', true, null, 'bar']); // V REDU -$processor->process($schema, [123]); // ERROR +$processor->process($schema, ['foo', true, null, 'bar']); // OK +$processor->process($schema, [123]); // NAPAKA ``` -Metoda `anyOf()` sprejema variante kot posamezne parametre in ne kot polje. Če ji želite posredovati polje vrednosti, uporabite operator razpakiranja `anyOf(...$variants)`. +Metoda `anyOf()` sprejema variante kot posamezne parametre, ne kot polje. Če ji želite predati polje vrednosti, uporabite unpacking operator `anyOf(...$variants)`. -Privzeta vrednost je `null`. Če želite, da je prvi element privzet, uporabite metodo `firstIsDefault()`: +Privzeta vrednost je `null`. Z metodo `firstIsDefault()` naredimo prvi element privzetega: ```php // privzeto je 'hello' @@ -192,29 +192,29 @@ Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault(); ``` -Strukture .[#toc-structures] ----------------------------- +Strukture +--------- -Strukture so predmeti z določenimi ključi. Vsak od teh parov ključ => vrednost se imenuje "lastnost": +Strukture so objekti z definiranimi ključi. Vsak od parov ključ => vrednost je označen kot „lastnost“: -Strukture sprejemajo polja in predmete ter vračajo predmete `stdClass` (razen če jih spremenite s `castTo('array')`, itd.). +Strukture sprejemajo polja in objekte ter vračajo objekte `stdClass`. -Privzeto so vse lastnosti neobvezne in imajo privzeto vrednost `null`. Obvezne lastnosti lahko določite z uporabo `required()`: +Privzeto so vse lastnosti neobvezne in imajo privzeto vrednost `null`. Obvezne lastnosti lahko definirate z `required()`: ```php $schema = Expect::structure([ 'required' => Expect::string()->required(), - 'optional' => Expect::string(), // zasebna vrednost je nič. + 'optional' => Expect::string(), // privzeta vrednost je null ]); $processor->process($schema, ['optional' => '']); -// ERROR: manjka možnost 'required' +// NAPAKA: možnost 'required' manjka $processor->process($schema, ['required' => 'foo']); -// V redu, vrne {'required' => 'foo', 'optional' => null} +// OK, vrne {'required' => 'foo', 'optional' => null} ``` -Če ne želite izpisati lastnosti samo s privzeto vrednostjo, uporabite `skipDefaults()`: +Če na izhodu ne želite imeti lastnosti s privzeto vrednostjo, uporabite `skipDefaults()`: ```php $schema = Expect::structure([ @@ -223,10 +223,10 @@ $schema = Expect::structure([ ])->skipDefaults(); $processor->process($schema, ['required' => 'foo']); -// V redu, vrne {'required' => 'foo'} +// OK, vrne {'required' => 'foo'} ``` -Čeprav je `null` privzeta vrednost lastnosti `optional`, je v vhodnih podatkih ni dovoljeno uporabljati (vrednost mora biti niz). Lastnosti, ki sprejemajo `null`, so opredeljene z uporabo `nullable()`: +Čeprav je `null` privzeta vrednost lastnosti `optional`, v vhodnih podatkih ni dovoljen (vrednost mora biti niz). Lastnosti, ki sprejemajo `null`, definiramo z `nullable()`: ```php $schema = Expect::structure([ @@ -235,13 +235,15 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['optional' => null]); -// ERROR: 'optional' pričakuje, da bo niz, podana je ničla. +// NAPAKA: 'optional' pričakuje niz, podan null. $processor->process($schema, ['nullable' => null]); -// V redu, vrne {'optional' => null, 'nullable' => null} +// OK, vrne {'optional' => null, 'nullable' => null} ``` -Privzeto je, da v vhodnih podatkih ne sme biti nobenih dodatnih elementov: +Polje vseh lastnosti strukture vrne metoda `getShape()`. + +Privzeto v vhodnih podatkih ne sme biti nobenih dodatnih elementov: ```php $schema = Expect::structure([ @@ -249,189 +251,238 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['additional' => 1]); -// ERROR: Nepričakovan element 'additional' +// NAPAKA: Nepričakovan element 'additional' ``` -To lahko spremenimo s `otherItems()`. Kot parameter bomo določili shemo za vsak dodatni element: +Kar lahko spremenimo z `otherItems()`. Kot parameter navedemo shemo, po kateri se bodo dodatni elementi validirali: ```php $schema = Expect::structure([ 'key' => Expect::string(), ])->otherItems(Expect::int()); -$processor->process($schema, ['additional' => 1]); // V REDU -$processor->process($schema, ['additional' => true]); // ERROR +$processor->process($schema, ['additional' => 1]); // OK +$processor->process($schema, ['additional' => true]); // NAPAKA +``` + +Novo strukturo lahko ustvarite z izpeljavo iz druge z uporabo `extend()`: + +```php +$dog = Expect::structure([ + 'name' => Expect::string(), + 'age' => Expect::int(), +]); + +$dogWithBreed = $dog->extend([ + 'breed' => Expect::string(), +]); +``` + + +Polja .{data-version:1.3.2} +--------------------------- + +Polja z definiranimi ključi. Zanje velja vse kot za [#strukture]. + +```php +$schema = Expect::array([ + 'required' => Expect::string()->required(), + 'optional' => Expect::string(), // privzeta vrednost je null +]); ``` +Lahko definirate tudi indeksirano polje, znano kot tuple: -Deprecations .[#toc-deprecations] ---------------------------------- +```php +$schema = Expect::array([ + Expect::int(), + Expect::string(), + Expect::bool(), +]); -Lastnost lahko izločite z uporabo `deprecated([string $message])` metodo. Obvestila o izločitvi vrne `$processor->getWarnings()`: +$processor->process($schema, [1, 'hello', true]); // OK +``` + + +Zastarele lastnosti +------------------- + +Lastnost lahko označite kot zastarelo z metodo `deprecated([string $message])`. Informacije o prenehanju podpore so vrnjene z `$processor->getWarnings()`: ```php $schema = Expect::structure([ - 'old' => Expect::int()->deprecated('The item %path% is deprecated'), + 'old' => Expect::int()->deprecated('Element %path% je zastarel'), ]); $processor->process($schema, ['old' => 1]); // OK -$processor->getWarnings(); // ["The item 'old' is deprecated"] +$processor->getWarnings(); // ["Element 'old' je zastarel"] ``` -Razponi: min() max() .[#toc-ranges-min-max] -------------------------------------------- +Obsegi: min() max() +------------------- -Uporabite `min()` in `max()` za omejitev števila elementov za polja: +Z `min()` in `max()` lahko pri poljih omejite število elementov: ```php -// polje, vsaj 10 elementov, največ 20 elementov +// polje, najmanj 10 elementov, največ 20 elementov Expect::array()->min(10)->max(20); ``` -Za nize omejite njihovo dolžino: +Pri nizih omejite njihovo dolžino: ```php -// niz, dolg vsaj 10 znakov, največ 20 znakov +// niz, najmanj 10 znakov dolg, največ 20 znakov Expect::string()->min(10)->max(20); ``` -Pri številkah omejite njihovo vrednost: +Pri številih omejite njihovo vrednost: ```php -// celo število, med 10 in vključno 20 +// celo število, med 10 in 20 vključno Expect::int()->min(10)->max(20); ``` -Seveda je mogoče navesti samo `min()` ali samo `max()`: +Seveda je mogoče navesti samo `min()`, ali samo `max()`: ```php -// niz, največ 20 znakov +// niz največ 20 znakov Expect::string()->max(20); ``` -Regularni izrazi: pattern() .[#toc-regular-expressions-pattern] ---------------------------------------------------------------- +Regularni izrazi: pattern() +--------------------------- -Z uporabo `pattern()` lahko določite regularni izraz, ki mu mora ustrezati **celoten** vhodni niz (tj. kot da bi bil ovit v znake `^` a `$`): +Z `pattern()` lahko navedete regularni izraz, ki mu mora ustrezati **celoten** vhodni niz (tj. kot da bi bil ovit z znaki `^` in `$`): ```php -// samo 9 številk +// natanko 9 številk Expect::string()->pattern('\d{9}'); ``` -Trditve po meri: assert() .[#toc-custom-assertions-assert] ----------------------------------------------------------- +Lastne omejitve: assert() +------------------------- -Vse druge omejitve lahko dodate z uporabo spletne strani `assert(callable $fn)`. +Poljubne dodatne omejitve podamo z `assert(callable $fn)`. ```php $countIsEven = fn($v) => count($v) % 2 === 0; $schema = Expect::arrayOf('string') - ->assert($countIsEven); // število mora biti sodo. + ->assert($countIsEven); // število mora biti sodo -$processor->process($schema, ['a', 'b']); // V REDU -$processor->process($schema, ['a', 'b', 'c']); // ERROR: 3 ni sodo +$processor->process($schema, ['a', 'b']); // OK +$processor->process($schema, ['a', 'b', 'c']); // NAPAKA: 3 ni sodo število ``` -Ali . +Ali ```php Expect::string()->assert('is_file'); // datoteka mora obstajati ``` -Za vsako trditev lahko dodate svoj opis. Ta bo del sporočila o napaki. +Vsaki omejitvi lahko dodate lasten opis. Ta bo del sporočila o napaki. ```php $schema = Expect::arrayOf('string') - ->assert($countIsEven, 'Even items in array'); + ->assert($countIsEven, 'Sodi elementi v polju'); $processor->process($schema, ['a', 'b', 'c']); -// Neuspešna trditev "Even items in array" za element z vrednostjo array. +// Neuspela asercija "Sodi elementi v polju" za element z vrednostjo array. ``` -Metodo lahko večkrat pokličete, če želite dodati več trditev. +Metodo lahko kličete večkrat in tako dodate več omejitev. Lahko jo prepletate s klici `transform()` in `castTo()`. -Prikazovanje v predmete: from() .[#toc-mapping-to-objects-from] ---------------------------------------------------------------- +Transformacije: transform() .{data-version:1.2.5} +------------------------------------------------- -Iz razreda lahko ustvarite strukturno shemo. Primer: +Uspešno validirane podatke lahko urejate z lastno funkcijo: ```php -class Config -{ - public string $name; - public string|null $password; - public bool $admin = false; -} +// pretvorba v velike črke: +Expect::string()->transform(fn(string $s) => strtoupper($s)); +``` -$schema = Expect::from(new Config); +Metodo lahko kličete večkrat in tako dodate več transformacij. Lahko jo prepletate s klici `assert()` in `castTo()`. Operacije se izvedejo v vrstnem redu, v katerem so deklarirane: -$data = [ - 'name' => 'jeff', -]; - -$normalized = $processor->process($schema, $data); -// $normalized instanceof Config -// $normalized = {'name' => 'jeff', 'password' => null, 'admin' => false} +```php +Expect::type('string|int') + ->castTo('string') + ->assert('ctype_lower', 'Vsi znaki morajo biti male črke') + ->transform(fn(string $s) => strtoupper($s)); // pretvorba v velike črke ``` -Če uporabljate PHP 7.4 ali novejši, lahko uporabite nativne tipe: +Metoda `transform()` lahko hkrati transformira in validira vrednost. To je pogosto enostavnejše in manj podvojeno kot veriženje `transform()` in `assert()`. V ta namen funkcija prejme objekt [Context |api:Nette\Schema\Context] z metodo `addError()`, ki jo lahko uporabite za dodajanje informacij o težavah z validacijo: ```php -class Config -{ - public string $name; - public ?string $password; - public bool $admin = false; -} +Expect::string() + ->transform(function (string $s, Nette\Schema\Context $context) { + if (!ctype_lower($s)) { + $context->addError('Vsi znaki morajo biti male črke', 'my.case.error'); + return null; + } -$schema = Expect::from(new Config); + return strtoupper($s); + }); ``` -Podprti so tudi anonimni razredi: + +Pretvorba: castTo() +------------------- + +Uspešno validirane podatke lahko pretvorite: ```php -$schema = Expect::from(new class { - public string $name; - public ?string $password; - public bool $admin = false; -}); +Expect::scalar()->castTo('string'); ``` -Ker informacije, pridobljene iz definicije razreda, morda ne zadostujejo, lahko z drugim parametrom dodate shemo po meri za elemente: +Poleg nativnih PHP tipov lahko pretvarjate tudi v razrede. Pri tem se razlikuje, ali gre za preprost razred brez konstruktorja ali razred s konstruktorjem. Če razred nima konstruktorja, se ustvari njegova instanca in vsi elementi strukture se zapišejo v lastnosti: ```php -$schema = Expect::from(new Config, [ - 'name' => Expect::string()->pattern('\w:.*'), -]); -``` +class Info +{ + public bool $processRefund; + public int $refundAmount; +} +Expect::structure([ + 'processRefund' => Expect::bool(), + 'refundAmount' => Expect::int(), +])->castTo(Info::class); -Kasting: castTo() .[#toc-casting-castto] ----------------------------------------- +// ustvari '$obj = new Info' in zapiše v $obj->processRefund in $obj->refundAmount +``` -Uspešno potrjene podatke je mogoče oddati: +Če razred ima konstruktor, se elementi strukture predajo kot imenovani parametri konstruktorju: ```php -Expect::scalar()->castTo('string'); +class Info +{ + public function __construct( + public bool $processRefund, + public int $refundAmount, + ) { + } +} + +// ustvari $obj = new Info(processRefund: ..., refundAmount: ...) ``` -Poleg nativnih tipov PHP lahko podatke prelijete tudi v razrede: +Pretvorba v kombinaciji s skalarnim parametrom ustvari objekt in vrednost preda kot edini parameter konstruktorju: ```php -Expect::scalar()->castTo('AddressEntity'); +Expect::string()->castTo(DateTime::class); +// ustvari new DateTime(...) ``` -Normalizacija: before() .[#toc-normalization-before] ----------------------------------------------------- +Normalizacija: before() +----------------------- -Pred samim preverjanjem lahko podatke normaliziramo z metodo `before()`. Kot primer imejmo element, ki mora biti polje nizov (npr. `['a', 'b', 'c']`), vendar prejme vhodne podatke v obliki niza `a b c`: +Pred samo validacijo lahko podatke normalizirate z metodo `before()`. Kot primer navedimo element, ki mora biti polje nizov (na primer `['a', 'b', 'c']`), vendar sprejema vhod v obliki niza `a b c`: ```php $explode = fn($v) => explode(' ', $v); @@ -440,7 +491,48 @@ $schema = Expect::arrayOf('string') ->before($explode); $normalized = $processor->process($schema, 'a b c'); -// V redu, vrne ['a', 'b', 'c'] +// OK in vrne ['a', 'b', 'c'] ``` -{{leftbar: nette:@menu-topics}} + +Preslikava na objekte: from() +----------------------------- + +Shemo strukture si lahko pustimo generirati iz razreda. Primer: + +```php +class Config +{ + public string $name; + public string|null $password; + public bool $admin = false; +} + +$schema = Expect::from(new Config); + +$data = [ + 'name' => 'franta', +]; + +$normalized = $processor->process($schema, $data); +// $normalized instanceof Config +// $normalized = {'name' => 'franta', 'password' => null, 'admin' => false} +``` + +Podprti so tudi anonimni razredi: + +```php +$schema = Expect::from(new class { + public string $name; + public ?string $password; + public bool $admin = false; +}); +``` + +Ker informacije, pridobljene iz definicije razreda, morda niso zadostne, lahko z drugim parametrom dopolnite elementom lastno shemo: + +```php +$schema = Expect::from(new Config, [ + 'name' => Expect::string()->pattern('\w:.*'), +]); +``` diff --git a/schema/sl/@meta.texy b/schema/sl/@meta.texy new file mode 100644 index 0000000000..282883a3d6 --- /dev/null +++ b/schema/sl/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Dokumentacija}} +{{leftbar: nette:@menu-topics}} diff --git a/schema/tr/@home.texy b/schema/tr/@home.texy index 738448d658..ed0547266e 100644 --- a/schema/tr/@home.texy +++ b/schema/tr/@home.texy @@ -1,8 +1,8 @@ -Schema: Veri Doğrulama -********************** +Nette Schema +************ .[perex] -Akıllı ve anlaşılması kolay bir API ile veri yapılarının belirli bir şemaya göre doğrulanması ve normalleştirilmesi için pratik bir kütüphane. +Akıllı, anlaşılır bir API ile belirli bir şemaya göre veri yapılarının doğrulanması ve normalleştirilmesi için pratik bir kütüphane. Kurulum: @@ -11,12 +11,12 @@ composer require nette/schema ``` -Temel Kullanım .[#toc-basic-usage] ----------------------------------- +Temel Kullanım +-------------- -`$schema` değişkeninde bir doğrulama şemamız var (bunun tam olarak ne anlama geldiğini ve nasıl oluşturulacağını daha sonra anlatacağız) ve `$data` değişkeninde doğrulamak ve normalleştirmek istediğimiz bir veri yapımız var. Bu, örneğin, kullanıcı tarafından bir API, yapılandırma dosyası vb. aracılığıyla gönderilen veriler olabilir. +`$schema` değişkeninde doğrulama şemamız var (bunun tam olarak ne anlama geldiğini ve böyle bir şemanın nasıl oluşturulacağını birazdan açıklayacağız) ve `$data` değişkeninde doğrulamak ve normalleştirmek istediğimiz veri yapısı var. Bu, örneğin bir API arayüzü aracılığıyla kullanıcı tarafından gönderilen veriler, bir yapılandırma dosyası vb. olabilir. -Görev, girdiyi işleyen ve normalleştirilmiş verileri döndüren ya da hata durumunda bir [api:Nette\Schema\ValidationException] istisnası atan [api:Nette\Schema\Processor] sınıfı tarafından gerçekleştirilir. +Görevi, girişi işleyen ve ya normalleştirilmiş verileri döndüren ya da bir hata durumunda bir [api:Nette\Schema\ValidationException] istisnası fırlatan [api:Nette\Schema\Processor] sınıfı üstlenir. ```php $processor = new Nette\Schema\Processor; @@ -24,17 +24,17 @@ $processor = new Nette\Schema\Processor; try { $normalized = $processor->process($schema, $data); } catch (Nette\Schema\ValidationException $e) { - echo 'Data is invalid: ' . $e->getMessage(); + echo 'Veriler geçerli değil: ' . $e->getMessage(); } ``` -`$e->getMessages()` yöntemi tüm mesaj dizelerinin dizisini döndürür ve `$e->getMessageObjects()` tüm mesajları "Nette\Schema\Message":https://api.nette.org/schema/master/Nette/Schema/Message.html nesneleri olarak döndürür. +`$e->getMessages()` metodu tüm mesajların dizisini karakter dizisi olarak döndürür ve `$e->getMessageObjects()` tüm mesajları "Nette\Schema\Message":https://api.nette.org/schema/master/Nette/Schema/Message.html nesneleri olarak döndürür. -Şema Tanımlama .[#toc-defining-schema] --------------------------------------- +Şema Tanımlama +-------------- -Ve şimdi bir şema oluşturalım. Bunu tanımlamak için [api:Nette\Schema\Expect] sınıfı kullanılır, aslında verilerin nasıl görünmesi gerektiğine ilişkin beklentileri tanımlarız. Diyelim ki girdi verisi, bool tipinde `processRefund` ve int tipinde `refundAmount` elemanları içeren bir yapı (örneğin bir dizi) olmalıdır. +Ve şimdi şemayı oluşturalım. Tanımlamak için [api:Nette\Schema\Expect] sınıfı kullanılır, aslında verilerin nasıl görünmesi gerektiğine dair beklentileri tanımlarız. Giriş verilerinin, `processRefund` türünde bool ve `refundAmount` türünde int öğeleri içeren bir yapı (örneğin bir dizi) oluşturması gerektiğini varsayalım. ```php use Nette\Schema\Expect; @@ -45,7 +45,7 @@ $schema = Expect::structure([ ]); ``` -Şema tanımının ilk kez görseniz bile net göründüğüne inanıyoruz. +Şema tanımının, ilk kez görseniz bile anlaşılır göründüğüne inanıyoruz. Doğrulama için aşağıdaki verileri gönderelim: @@ -55,27 +55,27 @@ $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // Tamam, geçiyor +$normalized = $processor->process($schema, $data); // OK, doğrulamadan geçer ``` -Çıktı, yani `$normalized` değeri, `stdClass` nesnesidir. Çıktının bir dizi olmasını istiyorsak, şemaya bir döküm ekleriz `Expect::structure([...])->castTo('array')`. +Çıktı, yani `$normalized` değeri, bir `stdClass` nesnesidir. Çıktının bir dizi olmasını isteseydik, şemayı `Expect::structure([...])->castTo('array')` tür dönüşümüyle tamamlardık. -Yapının tüm elemanları isteğe bağlıdır ve `null` varsayılan değerine sahiptir. Örnek: +Yapının tüm öğeleri isteğe bağlıdır ve varsayılan değeri `null`'dur. Örnek: ```php $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // Tamam, geçiyor +$normalized = $processor->process($schema, $data); // OK, doğrulamadan geçer // $normalized = {'processRefund' => null, 'refundAmount' => 17} ``` -Varsayılan değerin `null` olması, `'processRefund' => null` girdi verisinde kabul edileceği anlamına gelmez. Hayır, girdi boolean olmalıdır, yani yalnızca `true` veya `false`. `null` 'ye `Expect::bool()->nullable()` aracılığıyla açıkça izin vermemiz gerekir. +Varsayılan değerin `null` olması, giriş verilerinde `'processRefund' => null`'ın kabul edileceği anlamına gelmez. Hayır, giriş bir boolean olmalıdır, yani yalnızca `true` veya `false`. `null`'a izin vermek için bunu `Expect::bool()->nullable()` ile açıkça belirtmemiz gerekirdi. -Bir öğe `Expect::bool()->required()` kullanılarak zorunlu hale getirilebilir. Varsayılan değeri `Expect::bool()->default(false)` kullanarak `false` olarak veya `Expect::bool(false)` kullanarak kısaca değiştiriyoruz. +Bir öğeyi `Expect::bool()->required()` ile zorunlu kılabiliriz. Varsayılan değeri örneğin `Expect::bool()->default(false)` ile `false` olarak veya kısaca `Expect::bool(false)` ile değiştirebiliriz. -Peki ya boolean dışında `1` and `0` adresini de kabul etmek istersek? O zaman izin verilen değerleri listeleriz ve bunları da boolean olarak normalleştiririz: +Peki ya boolean'a ek olarak `1` ve `0`'ı da kabul etmek isteseydik? O zaman ayrıca boolean'a normalleştirilmesini sağlayacağımız değerlerin bir listesini belirtiriz: ```php $schema = Expect::structure([ @@ -87,13 +87,13 @@ $normalized = $processor->process($schema, $data); is_bool($normalized->processRefund); // true ``` -Artık şemanın nasıl tanımlandığına ve yapının tek tek öğelerinin nasıl davrandığına ilişkin temel bilgileri biliyorsunuz. Şimdi bir şemanın tanımlanmasında kullanılabilecek diğer tüm unsurların neler olduğunu göstereceğiz. +Artık şemanın nasıl tanımlandığının ve yapının bireysel öğelerinin nasıl davrandığının temellerini biliyorsunuz. Şimdi şema tanımlarken kullanılabilecek diğer tüm öğeleri göstereceğiz. -Veri Türleri: type() .[#toc-data-types-type] --------------------------------------------- +Veri Tipleri: type() +-------------------- -Tüm standart PHP veri türleri şemada listelenebilir: +Şemada tüm standart PHP veri tipleri belirtilebilir: ```php Expect::string($default = null) @@ -104,102 +104,102 @@ Expect::null() Expect::array($default = []) ``` -Ve daha sonra [Doğrulayıcılar tarafından |utils:validators#Expected Types] `Expect::type('scalar')` veya kısaltılmış `Expect::scalar()` aracılığıyla [desteklenen |utils:validators#Expected Types] tüm türler. Ayrıca sınıf veya arayüz adları da kabul edilir, örneğin `Expect::type('AddressEntity')`. +Ve ayrıca [Validators sınıfı tarafından desteklenen tüm türler |utils:validators#Beklenen Tipler], örneğin `Expect::type('scalar')` veya kısaca `Expect::scalar()`. Ayrıca sınıf veya arayüz adları, örneğin `Expect::type('AddressEntity')`. -Birlik gösterimini de kullanabilirsiniz: +Union gösterimi de kullanılabilir: ```php Expect::type('bool|string|array') ``` -Varsayılan değer, boş bir dizi olan `array` ve `list` dışında her zaman `null` şeklindedir. (Liste, sıfırdan itibaren sayısal anahtarların artan sırasına göre dizinlenmiş bir dizidir, yani ilişkisel olmayan bir dizidir). +Varsayılan değer, boş bir dizi olduğu `array` ve `list` hariç her zaman `null`'dur. (Liste, sıfırdan başlayarak artan bir dizi sayısal anahtara göre indekslenmiş bir dizidir, yani ilişkisel olmayan bir dizi). -Değerler Dizisi: arrayOf() listOf() .[#toc-array-of-values-arrayof-listof] --------------------------------------------------------------------------- +Değer Dizileri: arrayOf() listOf() +---------------------------------- -Dizi çok genel bir yapıdır, tam olarak hangi elemanları içerebileceğini belirtmek daha yararlıdır. Örneğin, elemanları yalnızca string olabilen bir dizi: +Dizi çok genel bir yapıdır, tam olarak hangi öğeleri içerebileceğini belirtmek daha kullanışlıdır. Örneğin, öğeleri yalnızca karakter dizileri olabilen bir dizi: ```php $schema = Expect::arrayOf('string'); -$processor->process($schema, ['hello', 'world']); // Tamam -$processor->process($schema, ['a' => 'hello', 'b' => 'world']); // Tamam -$processor->process($schema, ['key' => 123]); // ERROR: 123 is not a string +$processor->process($schema, ['hello', 'world']); // OK +$processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK +$processor->process($schema, ['key' => 123]); // HATA: 123 bir karakter dizisi değil ``` -İkinci parametre anahtarları belirtmek için kullanılabilir (sürüm 1.2'den beri): +İkinci parametre ile anahtarlar belirtilebilir (sürüm 1.2'den itibaren): ```php $schema = Expect::arrayOf('string', 'int'); -$processor->process($schema, ['hello', 'world']); // Tamam -$processor->process($schema, ['a' => 'hello']); // ERROR: 'a' is not int +$processor->process($schema, ['hello', 'world']); // OK +$processor->process($schema, ['a' => 'hello']); // HATA: 'a' bir int değil ``` -Liste, dizinlenmiş bir dizidir: +Liste, indekslenmiş bir dizidir: ```php $schema = Expect::listOf('string'); -$processor->process($schema, ['a', 'b']); // Tamam -$processor->process($schema, ['a', 123]); // ERROR: 123 bir dize değil -$processor->process($schema, ['key' => 'a']); // ERROR: is not a list -$processor->process($schema, [1 => 'a', 0 => 'b']); // ERROR: is not a list +$processor->process($schema, ['a', 'b']); // OK +$processor->process($schema, ['a', 123]); // HATA: 123 bir karakter dizisi değil +$processor->process($schema, ['key' => 'a']); // HATA: liste değil +$processor->process($schema, [1 => 'a', 0 => 'b']); // HATA: ayrıca liste değil ``` -Parametre bir şema da olabilir, böylece yazabiliriz: +Parametre bir şema da olabilir, bu yüzden yazabiliriz: ```php Expect::arrayOf(Expect::bool()) ``` -Varsayılan değer boş bir dizidir. Varsayılan değeri belirtirseniz, aktarılan verilerle birleştirilecektir. Bu, `mergeDefaults(false)` kullanılarak devre dışı bırakılabilir (sürüm 1.1'den beri). +Varsayılan değer boş bir dizidir. Bir varsayılan değer belirtirseniz, iletilen verilerle birleştirilecektir. Bu, `mergeDefaults(false)` ile devre dışı bırakılabilir (sürüm 1.1'den itibaren). -Numaralandırma: anyOf() .[#toc-enumeration-anyof] -------------------------------------------------- +Enum: anyOf() +------------- -`anyOf()` bir değerin olabileceği değerler veya şemalar kümesidir. Burada, `'a'`, `true` veya `null` olabilen elemanlardan oluşan bir dizinin nasıl yazılacağı gösterilmektedir: +`anyOf()`, bir değerin alabileceği değerlerin veya şemaların bir listesini temsil eder. Bu şekilde, öğeleri `'a'`, `true` veya `null` olabilen bir öğe dizisi yazarız: ```php $schema = Expect::listOf( Expect::anyOf('a', true, null), ); -$processor->process($schema, ['a', true, null, 'a']); // Tamam -$processor->process($schema, ['a', false]); // ERROR: false oraya ait değil +$processor->process($schema, ['a', true, null, 'a']); // OK +$processor->process($schema, ['a', false]); // HATA: false oraya ait değil ``` -Numaralandırma elemanları şemalar da olabilir: +Enum öğeleri şemalar da olabilir: ```php $schema = Expect::listOf( Expect::anyOf(Expect::string(), true, null), ); -$processor->process($schema, ['foo', true, null, 'bar']); // Tamam -$processor->process($schema, [123]); // ERROR +$processor->process($schema, ['foo', true, null, 'bar']); // OK +$processor->process($schema, [123]); // HATA ``` -`anyOf()` yöntemi varyantları dizi olarak değil, ayrı parametreler olarak kabul eder. Bir değer dizisi iletmek için, `anyOf(...$variants)` paket açma operatörünü kullanın. +`anyOf()` metodu, varyantları bir dizi olarak değil, bireysel parametreler olarak kabul eder. Ona bir değer dizisi iletmek istiyorsanız, unpacking operatörünü `anyOf(...$variants)` kullanın. -Varsayılan değer `null`'dur. İlk öğeyi varsayılan yapmak için `firstIsDefault()` yöntemini kullanın: +Varsayılan değer `null`'dur. `firstIsDefault()` metoduyla ilk öğeyi varsayılan yaparız: ```php -// varsayılan değer 'hello' +// varsayılan 'hello'dur Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault(); ``` -Yapılar .[#toc-structures] --------------------------- +Yapılar +------- -Yapılar, tanımlanmış anahtarları olan nesnelerdir. Bu anahtar => değer çiftlerinin her biri "özellik" olarak adlandırılır: +Yapılar, tanımlanmış anahtarlara sahip nesnelerdir. Her anahtar => değer çifti "özellik" olarak adlandırılır: -Yapılar dizileri ve nesneleri kabul eder ve `stdClass` nesnelerini döndürür ( `castTo('array')`, vb. ile değiştirmediğiniz sürece). +Yapılar dizileri ve nesneleri kabul eder ve `stdClass` nesneleri döndürür. -Varsayılan olarak, tüm özellikler isteğe bağlıdır ve varsayılan değerleri `null` şeklindedir. `required()` adresini kullanarak zorunlu özellikleri tanımlayabilirsiniz: +Varsayılan olarak, tüm özellikler isteğe bağlıdır ve varsayılan değeri `null`'dur. Zorunlu özellikleri `required()` ile tanımlayabilirsiniz: ```php $schema = Expect::structure([ @@ -208,13 +208,13 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['optional' => '']); -// ERROR: 'required' seçeneği eksik +// HATA: 'required' seçeneği eksik $processor->process($schema, ['required' => 'foo']); -// Tamam, {'required' => 'foo', 'optional' => null} döndürür +// OK, {'required' => 'foo', 'optional' => null} döndürür ``` -Özelliklerin çıktısını yalnızca varsayılan bir değerle almak istemiyorsanız `skipDefaults()` adresini kullanın: +Çıktıda varsayılan değere sahip özellikleri istemiyorsanız, `skipDefaults()` kullanın: ```php $schema = Expect::structure([ @@ -223,10 +223,10 @@ $schema = Expect::structure([ ])->skipDefaults(); $processor->process($schema, ['required' => 'foo']); -// Tamam, {'required' => 'foo'} döndürür +// OK, {'required' => 'foo'} döndürür ``` -`null`, `optional` özelliğinin varsayılan değeri olmasına rağmen, girdi verilerinde buna izin verilmez (değer bir dize olmalıdır). `null` adresini kabul eden özellikler `nullable()` kullanılarak tanımlanır: +`null`, `optional` özelliğinin varsayılan değeri olmasına rağmen, giriş verilerinde izin verilmez (değer bir karakter dizisi olmalıdır). `null` kabul eden özellikleri `nullable()` ile tanımlarız: ```php $schema = Expect::structure([ @@ -235,13 +235,15 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['optional' => null]); -// ERROR: 'optional' string olması bekleniyor, null verildi. +// HATA: 'optional' karakter dizisi olması bekleniyor, null verildi. $processor->process($schema, ['nullable' => null]); -// Tamam, {'optional' => null, 'nullable' => null} döndürür +// OK, {'optional' => null, 'nullable' => null} döndürür ``` -Varsayılan olarak, giriş verilerinde fazladan öğe bulunamaz: +Yapının tüm özelliklerinin dizisini `getShape()` metodu döndürür. + +Varsayılan olarak, giriş verilerinde ekstra öğe olamaz: ```php $schema = Expect::structure([ @@ -249,83 +251,121 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['additional' => 1]); -// ERROR: Beklenmeyen 'additional' öğesi +// HATA: Beklenmeyen öğe 'additional' ``` -Bunu `otherItems()` ile değiştirebiliriz. Parametre olarak, her ekstra eleman için şema belirteceğiz: +Bunu `otherItems()` ile değiştirebiliriz. Parametre olarak, ekstra öğelerin doğrulanacağı şemayı belirtiriz: ```php $schema = Expect::structure([ 'key' => Expect::string(), ])->otherItems(Expect::int()); -$processor->process($schema, ['additional' => 1]); // Tamam -$processor->process($schema, ['additional' => true]); // ERROR +$processor->process($schema, ['additional' => 1]); // OK +$processor->process($schema, ['additional' => true]); // HATA +``` + +`extend()` kullanarak başka bir yapıdan türeterek yeni bir yapı oluşturabilirsiniz: + +```php +$dog = Expect::structure([ + 'name' => Expect::string(), + 'age' => Expect::int(), +]); + +$dogWithBreed = $dog->extend([ + 'breed' => Expect::string(), +]); ``` -Kullanımdan kaldırmalar .[#toc-deprecations] --------------------------------------------- +Diziler .{data-version:1.3.2} +----------------------------- -kullanarak özelliği kullanımdan kaldırabilirsiniz. `deprecated([string $message])` yöntem. Kullanımdan kaldırma bildirimleri `$processor->getWarnings()` tarafından döndürülür: +Tanımlanmış anahtarlara sahip dizi. [#Yapılar] için geçerli olan her şey onun için de geçerlidir. + +```php +$schema = Expect::array([ + 'required' => Expect::string()->required(), + 'optional' => Expect::string(), // varsayılan değer null'dur +]); +``` + +Tuple olarak bilinen indekslenmiş bir dizi de tanımlanabilir: + +```php +$schema = Expect::array([ + Expect::int(), + Expect::string(), + Expect::bool(), +]); + +$processor->process($schema, [1, 'hello', true]); // OK +``` + + +Kullanımdan Kaldırılmış Özellikler +---------------------------------- + +Bir özelliği `deprecated([string $message])` metoduyla kullanımdan kaldırılmış olarak işaretleyebilirsiniz. Destek sonu bilgileri `$processor->getWarnings()` ile döndürülür: ```php $schema = Expect::structure([ - 'old' => Expect::int()->deprecated('The item %path% is deprecated'), + 'old' => Expect::int()->deprecated('Öğe %path% kullanımdan kaldırılmıştır'), ]); -$processor->process($schema, ['old' => 1]); // Tamam -$processor->getWarnings(); // ["The item 'old' is deprecated"] +$processor->process($schema, ['old' => 1]); // OK +$processor->getWarnings(); // ["Öğe 'old' kullanımdan kaldırılmıştır"] ``` -Aralıklar: min() max() .[#toc-ranges-min-max] ---------------------------------------------- +Aralıklar: min() max() +---------------------- -Dizilerin öğe sayısını sınırlamak için `min()` ve `max()` adreslerini kullanın: +`min()` ve `max()` kullanarak dizilerdeki öğe sayısını sınırlayabiliriz: ```php // dizi, en az 10 öğe, en fazla 20 öğe Expect::array()->min(10)->max(20); ``` -Dizeler için uzunluklarını sınırlayın: +Karakter dizileri için uzunluklarını sınırlayabiliriz: ```php -// dize, en az 10 karakter uzunluğunda, en fazla 20 karakter +// karakter dizisi, en az 10 karakter uzunluğunda, en fazla 20 karakter Expect::string()->min(10)->max(20); ``` -Sayılar için değerlerini sınırlayın: +Sayılar için değerlerini sınırlayabiliriz: ```php -// tamsayı, 10 ile 20 arasında, dahil +// tam sayı, 10 ile 20 arasında (dahil) Expect::int()->min(10)->max(20); ``` -Elbette sadece `min()` veya sadece `max()` adreslerinden bahsetmek mümkündür: +Elbette, yalnızca `min()` veya yalnızca `max()` belirtmek mümkündür: ```php -// dize, en fazla 20 karakter +// en fazla 20 karakterlik karakter dizisi Expect::string()->max(20); ``` -Düzenli İfadeler: pattern() .[#toc-regular-expressions-pattern] ---------------------------------------------------------------- +Düzenli İfadeler: pattern() +--------------------------- -`pattern()` adresini kullanarak, **tüm** girdi dizesinin eşleşmesi gereken bir düzenli ifade belirtebilirsiniz (yani, `^` a `$` karakterlerine sarılmış gibi): +`pattern()` kullanarak, **tüm** giriş karakter dizisinin eşleşmesi gereken bir düzenli ifade belirtebiliriz (yani, `^` ve `$` karakterleriyle sarılmış gibi): ```php -// sadece 9 hane +// tam olarak 9 rakam Expect::string()->pattern('\d{9}'); ``` -Özel İfadeler: assert() .[#toc-custom-assertions-assert] --------------------------------------------------------- +Özel Kısıtlamalar: assert() +--------------------------- -`assert(callable $fn)` adresini kullanarak başka kısıtlamalar ekleyebilirsiniz. +Diğer tüm kısıtlamaları `assert(callable $fn)` kullanarak belirtebiliriz. ```php $countIsEven = fn($v) => count($v) % 2 === 0; @@ -333,105 +373,116 @@ $countIsEven = fn($v) => count($v) % 2 === 0; $schema = Expect::arrayOf('string') ->assert($countIsEven); // sayı çift olmalıdır -$processor->process($schema, ['a', 'b']); // Tamam -$processor->process($schema, ['a', 'b', 'c']); // HATA: 3 eşit değil +$processor->process($schema, ['a', 'b']); // OK +$processor->process($schema, ['a', 'b', 'c']); // HATA: 3 çift sayı değil ``` -Ya da +Veya ```php Expect::string()->assert('is_file'); // dosya mevcut olmalıdır ``` -Her bir assertion için kendi açıklamanızı ekleyebilirsiniz. Hata mesajının bir parçası olacaktır. +Her kısıtlamaya kendi açıklamanızı ekleyebilirsiniz. Bu, hata mesajının bir parçası olacaktır. ```php $schema = Expect::arrayOf('string') - ->assert($countIsEven, 'Even items in array'); + ->assert($countIsEven, 'Dizideki çift öğeler'); $processor->process($schema, ['a', 'b', 'c']); -// array değerine sahip öğe için "Even items in array" iddiası başarısız oldu. +// Değeri dizi olan öğe için "Dizideki çift öğeler" iddiası başarısız oldu. ``` -Yöntem, daha fazla onay eklemek için tekrar tekrar çağrılabilir. +Metot tekrar tekrar çağrılarak daha fazla kısıtlama eklenebilir. `transform()` ve `castTo()` çağrılarıyla serpiştirilebilir. -Nesnelere Eşleme: from() .[#toc-mapping-to-objects-from] --------------------------------------------------------- +Dönüşümler: transform() .{data-version:1.2.5} +--------------------------------------------- -Sınıftan yapı şeması oluşturabilirsiniz. Örnek: +Başarıyla doğrulanmış veriler özel bir fonksiyon kullanılarak değiştirilebilir: ```php -class Config -{ - public string $name; - public string|null $password; - public bool $admin = false; -} - -$schema = Expect::from(new Config); +// büyük harfe dönüştürme: +Expect::string()->transform(fn(string $s) => strtoupper($s)); +``` -$data = [ - 'name' => 'jeff', -]; +Metot tekrar tekrar çağrılarak daha fazla dönüşüm eklenebilir. `assert()` ve `castTo()` çağrılarıyla serpiştirilebilir. İşlemler, bildirildikleri sırayla gerçekleştirilir: -$normalized = $processor->process($schema, $data); -// $normalized instanceof Config -// $normalized = {'name' => 'jeff', 'password' => null, 'admin' => false} +```php +Expect::type('string|int') + ->castTo('string') + ->assert('ctype_lower', 'Tüm karakterler küçük harf olmalıdır') + ->transform(fn(string $s) => strtoupper($s)); // büyük harfe dönüştürme ``` -PHP 7.4 veya daha üstünü kullanıyorsanız, yerel türleri kullanabilirsiniz: +`transform()` metodu aynı anda değeri hem dönüştürebilir hem de doğrulayabilir. Bu genellikle `transform()` ve `assert()` zincirlemesinden daha basit ve daha az tekrarlıdır. Bu amaçla, fonksiyon, doğrulama sorunları hakkında bilgi eklemek için kullanılabilecek `addError()` metoduna sahip bir [Context |api:Nette\Schema\Context] nesnesi alır: ```php -class Config -{ - public string $name; - public ?string $password; - public bool $admin = false; -} +Expect::string() + ->transform(function (string $s, Nette\Schema\Context $context) { + if (!ctype_lower($s)) { + $context->addError('Tüm karakterler küçük harf olmalıdır', 'my.case.error'); + return null; + } -$schema = Expect::from(new Config); + return strtoupper($s); + }); ``` -Anonim sınıflar da desteklenmektedir: + +Tür Dönüştürme: castTo() +------------------------ + +Başarıyla doğrulanmış veriler tür dönüştürülebilir: ```php -$schema = Expect::from(new class { - public string $name; - public ?string $password; - public bool $admin = false; -}); +Expect::scalar()->castTo('string'); ``` -Sınıf tanımından elde edilen bilgiler yeterli olmayabileceğinden, ikinci parametre ile elemanlar için özel bir şema ekleyebilirsiniz: +Yerel PHP türlerine ek olarak, sınıflara da tür dönüştürme yapılabilir. Bu, basit bir yapıcı olmayan sınıf mı yoksa yapıcı olan bir sınıf mı olduğuna bağlıdır. Sınıfın bir yapıcısı yoksa, örneği oluşturulur ve yapının tüm öğeleri özelliklere yazılır: ```php -$schema = Expect::from(new Config, [ - 'name' => Expect::string()->pattern('\w:.*'), -]); -``` +class Info +{ + public bool $processRefund; + public int $refundAmount; +} +Expect::structure([ + 'processRefund' => Expect::bool(), + 'refundAmount' => Expect::int(), +])->castTo(Info::class); -Döküm: castTo() .[#toc-casting-castto] --------------------------------------- +// '$obj = new Info' oluşturur ve $obj->processRefund ve $obj->refundAmount'a yazar +``` -Başarıyla doğrulanan veriler dökülebilir: +Sınıfın bir yapıcısı varsa, yapının öğeleri yapıcıya adlandırılmış parametreler olarak iletilir: ```php -Expect::scalar()->castTo('string'); +class Info +{ + public function __construct( + public bool $processRefund, + public int $refundAmount, + ) { + } +} + +// $obj = new Info(processRefund: ..., refundAmount: ...) oluşturur ``` -Yerel PHP türlerine ek olarak, sınıflara da döküm yapabilirsiniz: +Skaler bir parametre ile birleştirilmiş tür dönüştürme, bir nesne oluşturur ve değeri yapıcıya tek bir parametre olarak iletir: ```php -Expect::scalar()->castTo('AddressEntity'); +Expect::string()->castTo(DateTime::class); +// new DateTime(...) oluşturur ``` -Normalleştirme: before() .[#toc-normalization-before] ------------------------------------------------------ +Normalizasyon: before() +----------------------- -Doğrulama işleminden önce, veriler `before()` yöntemi kullanılarak normalleştirilebilir. Örnek olarak, dizelerden oluşan bir dizi olması gereken bir öğeye sahip olalım (örn. `['a', 'b', 'c']`), ancak `a b c` dizesi biçiminde bir girdi alır: +Doğrulamadan önce veriler `before()` metodu kullanılarak normalleştirilebilir. Örnek olarak, karakter dizisi dizisi olması gereken (örneğin `['a', 'b', 'c']`) ancak `a b c` karakter dizisi biçiminde girdi kabul eden bir öğeyi ele alalım: ```php $explode = fn($v) => explode(' ', $v); @@ -440,7 +491,48 @@ $schema = Expect::arrayOf('string') ->before($explode); $normalized = $processor->process($schema, 'a b c'); -// Tamam, ['a', 'b', 'c'] döndürür +// OK ve ['a', 'b', 'c'] döndürür +``` + + +Nesnelere Eşleme: from() +------------------------ + +Yapı şemasını bir sınıftan oluşturabiliriz. Örnek: + +```php +class Config +{ + public string $name; + public string|null $password; + public bool $admin = false; +} + +$schema = Expect::from(new Config); + +$data = [ + 'name' => 'franta', +]; + +$normalized = $processor->process($schema, $data); +// $normalized instanceof Config +// $normalized = {'name' => 'franta', 'password' => null, 'admin' => false} +``` + +Anonim sınıflar da desteklenir: + +```php +$schema = Expect::from(new class { + public string $name; + public ?string $password; + public bool $admin = false; +}); ``` -{{leftbar: nette:@menu-topics}} +Sınıf tanımından elde edilen bilgiler yeterli olmayabileceğinden, ikinci parametre ile öğelere kendi şemanızı ekleyebilirsiniz: + +```php +$schema = Expect::from(new Config, [ + 'name' => Expect::string()->pattern('\w:.*'), +]); +``` diff --git a/schema/tr/@meta.texy b/schema/tr/@meta.texy new file mode 100644 index 0000000000..e5c5cea355 --- /dev/null +++ b/schema/tr/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Nette Dokümantasyonu}} +{{leftbar: nette:@menu-topics}} diff --git a/schema/uk/@home.texy b/schema/uk/@home.texy index 38bcea526e..662d822174 100644 --- a/schema/uk/@home.texy +++ b/schema/uk/@home.texy @@ -1,8 +1,8 @@ -Schema: валідація даних -*********************** +Nette Schema +************ .[perex] -Практична бібліотека для перевірки та нормалізації структур даних за заданою схемою з інтелектуальним і простим у розумінні API. +Практична бібліотека для валідації та нормалізації структур даних відповідно до заданої схеми з розумним зрозумілим API. Встановлення: @@ -11,12 +11,12 @@ composer require nette/schema ``` -Використання .[#toc-ispol-zovanie] ----------------------------------- +Базове використання +------------------- -У змінній `$schema` у нас є схема валідації (що саме це значить і як її створити, ми розповімо пізніше), а у змінній `$data` у нас є структура даних, яку ми хочемо валідувати та нормалізувати. Це можуть бути, наприклад, дані, надіслані користувачем через API, конфігураційний файл тощо. +У змінній `$schema` ми маємо схему валідації (що це точно означає і як таку схему створити, ми розповімо згодом), а у змінній `$data` — структуру даних, яку ми хочемо валідувати та нормалізувати. Це можуть бути, наприклад, дані, надіслані користувачем через API, конфігураційний файл тощо. -Завданням займається клас [api:Nette\Schema\Processor], який обробляє вхідні дані та або повертає нормалізовані дані, або викидає виняток [api:Nette\Schema\ValidationException] у разі помилки. +Завдання виконує клас [api:Nette\Schema\Processor], який обробляє вхідні дані і або повертає нормалізовані дані, або у випадку помилки викидає виняток [api:Nette\Schema\ValidationException]. ```php $processor = new Nette\Schema\Processor; @@ -24,17 +24,17 @@ $processor = new Nette\Schema\Processor; try { $normalized = $processor->process($schema, $data); } catch (Nette\Schema\ValidationException $e) { - echo 'Data is invalid: ' . $e->getMessage(); + echo 'Дані недійсні: ' . $e->getMessage(); } ``` -Метод `$e->getMessages()` повертає масив усіх рядків повідомлень, а `$e->getMessageObjects()` повертає всі повідомлення у вигляді об'єктів "Nette\Schema\Message":https://api.nette.org/schema/master/Nette/Schema/Message.html. +Метод `$e->getMessages()` повертає масив усіх повідомлень як рядків, а `$e->getMessageObjects()` повертає всі повідомлення як об'єкти "Nette\Schema\Message":https://api.nette.org/schema/master/Nette/Schema/Message.html. -Визначення схеми .[#toc-opredelenie-shemy] ------------------------------------------- +Визначення схеми +---------------- -А тепер давайте створимо схему. За допомогою класу [api:Nette\Schema\Expect] ми фактично визначаємо, як мають виглядати дані. Припустимо, що вхідні дані повинні являти собою структуру (наприклад, масив), що містить елементи `processRefund` типу bool і `refundAmount` типу int. +А тепер створимо схему. Для її визначення служить клас [api:Nette\Schema\Expect], ми фактично визначаємо очікування, як мають виглядати дані. Скажімо, вхідні дані повинні утворювати структуру (наприклад, масив), що містить елементи `processRefund` типу bool та `refundAmount` типу int. ```php use Nette\Schema\Expect; @@ -45,9 +45,9 @@ $schema = Expect::structure([ ]); ``` -Ми вважаємо, що визначення схеми виглядає зрозумілим, навіть якщо ви бачите його вперше. +Ми віримо, що визначення схеми виглядає зрозуміло, навіть якщо ви бачите його вперше. -Відправимо такі дані для перевірки: +Надішлемо для валідації наступні дані: ```php $data = [ @@ -55,27 +55,27 @@ $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // ОК, проходить +$normalized = $processor->process($schema, $data); // OK, пройде валідацію ``` -Вихід, тобто значенням `$normalized`, є об'єкт `stdClass`. Якщо ми хочемо, щоб результатом був масив, ми додаємо приведення до схеми `Expect::structure([...])->castTo('array')`. +Виходом, тобто значенням `$normalized`, є об'єкт `stdClass`. Якщо б ми хотіли, щоб виходом був масив, доповнимо схему перетворенням типу `Expect::structure([...])->castTo('array')`. -Усі елементи структури є необов'язковими і мають значення за замовчуванням `null`. Приклад: +Усі елементи структури є необов'язковими та мають значення за замовчуванням `null`. Приклад: ```php $data = [ 'refundAmount' => 17, ]; -$normalized = $processor->process($schema, $data); // ОК, проходить -// $normalized = {'processRefund' => null, 'refundAmount' => 17}. +$normalized = $processor->process($schema, $data); // OK, пройде валідацію +// $normalized = {'processRefund' => null, 'refundAmount' => 17} ``` -Той факт, що значенням за замовчуванням є `null`, не означає, що воно буде прийнято у вхідних даних `'processRefund' => null`. Ні, вхідні дані повинні бути булевими, тобто тільки `true` або `false`. Нам довелося б явно дозволити `null` через `Expect::bool()->nullable()`. +Те, що значенням за замовчуванням є `null`, не означає, що у вхідних даних приймалося б `'processRefund' => null`. Ні, входом має бути boolean, тобто лише `true` або `false`. Дозволити `null` ми б мали явно за допомогою `Expect::bool()->nullable()`. -Елемент можна зробити обов'язковим, використовуючи `Expect::bool()->required()`. Ми змінюємо значення за замовчуванням на `false`, використовуючи `Expect::bool()->default(false)` або коротко `Expect::bool(false)`. +Елемент можна зробити обов'язковим за допомогою `Expect::bool()->required()`. Значення за замовчуванням змінимо, наприклад, на `false` за допомогою `Expect::bool()->default(false)` або скорочено `Expect::bool(false)`. -А що якщо ми захочемо приймати `1` и `0` крім булевих чисел? Перелічимо допустимі значення, які ми також нормалізуємо в boolean: +А що, якби ми хотіли крім boolean приймати ще `1` та `0`? Тоді вкажемо перелік значень, які до того ж нормалізуємо до boolean: ```php $schema = Expect::structure([ @@ -87,13 +87,13 @@ $normalized = $processor->process($schema, $data); is_bool($normalized->processRefund); // true ``` -Тепер ви знаєте основи того, як визначається схема і як поводяться окремі елементи структури. Тепер ми покажемо, які ще елементи можуть бути використані при визначенні схеми. +Тепер ви знаєте основи того, як визначається схема та як поводяться окремі елементи структури. Тепер покажемо, які всі інші елементи можна використовувати при визначенні схеми. -Типи даних: type() .[#toc-tipy-dannyh-type] -------------------------------------------- +Типи даних: type() +------------------ -Усі стандартні типи даних PHP можуть бути перераховані в схемі: +У схемі можна вказати всі стандартні типи даних PHP: ```php Expect::string($default = null) @@ -104,117 +104,117 @@ Expect::null() Expect::array($default = []) ``` -А потім всі типи, [підтримувані валідаторами |utils:validators#Expected-Types] через `Expect::type('scalar')` або скорочено `Expect::scalar()`. Також приймаються імена класів або інтерфейсів, наприклад: `Expect::type('AddressEntity')`. +А також усі типи, [підтримувані класом Validators |utils:validators#Очікувані типи], наприклад `Expect::type('scalar')` або скорочено `Expect::scalar()`. Також назви класів чи інтерфейсів, наприклад `Expect::type('AddressEntity')`. -Ви також можете використовувати нотацію об'єднання: +Можна використовувати і union запис: ```php Expect::type('bool|string|array') ``` -Значення за замовчуванням завжди `null`, за винятком `array` і `list`, де це порожній масив. (Список - це масив, індексований у порядку зростання числових ключів від нуля, тобто неасоціативний масив). +Значення за замовчуванням завжди `null`, за винятком `array` та `list`, де це порожній масив. (List — це масив, індексований за зростаючою послідовністю числових ключів від нуля, тобто неасоціативний масив). -Масив значень: arrayOf() listOf() .[#toc-massiv-znacenij-arrayof-listof] ------------------------------------------------------------------------- +Масиви значень: arrayOf() listOf() +---------------------------------- -Масив - занадто загальна структура, корисніше вказати, які саме елементи він може містити. Наприклад, масив, елементами якого можуть бути тільки рядки: +Масив представляє занадто загальну структуру, корисніше вказати, які саме елементи він може містити. Наприклад, масив, елементи якого можуть бути лише рядками: ```php $schema = Expect::arrayOf('string'); $processor->process($schema, ['hello', 'world']); // OK $processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK -$processor->process($schema, ['key' => 123]); // ПОМИЛКА: 123 не рядок +$processor->process($schema, ['key' => 123]); // ПОМИЛКА: 123 не є рядком ``` -Другий параметр може використовуватися для вказівки ключів (починаючи з версії 1.2): +Другим параметром можна вказати ключі (з версії 1.2): ```php $schema = Expect::arrayOf('string', 'int'); -$processor->process($schema, ['hello', 'world']); // ОК -$processor->process($schema, ['a' => 'hello']); // ПОМИЛКА: 'a' не int +$processor->process($schema, ['hello', 'world']); // OK +$processor->process($schema, ['a' => 'hello']); // ПОМИЛКА: 'a' не є int ``` -Список являє собою індексований масив: +List є індексованим масивом: ```php $schema = Expect::listOf('string'); $processor->process($schema, ['a', 'b']); // OK -$processor->process($schema, ['a', 123]); // ERROR: 123 не є рядком -$processor->process($schema, ['key' => 'a']); // ERROR: не список -$processor->process($schema, [1 => 'a', 0 => 'b']); // ERROR: не список +$processor->process($schema, ['a', 123]); // ПОМИЛКА: 123 не є рядком +$processor->process($schema, ['key' => 'a']); // ПОМИЛКА: не є list +$processor->process($schema, [1 => 'a', 0 => 'b']); // ПОМИЛКА: також не є list ``` -Параметр також може бути схемою, тому ми можемо написати: +Параметром може бути і схема, тому ми можемо записати: ```php Expect::arrayOf(Expect::bool()) ``` -Значення за замовчуванням - порожній масив. Якщо ви вкажете значення за замовчуванням, воно буде об'єднано з переданими даними. Це можна відключити за допомогою `mergeDefaults(false)` (починаючи з версії 1.1). +Значення за замовчуванням — порожній масив. Якщо ви вкажете значення за замовчуванням, воно буде об'єднане з переданими даними. Це можна вимкнути за допомогою `mergeDefaults(false)` (з версії 1.1). -Перерахування: anyOf() .[#toc-perecislenie-anyof] -------------------------------------------------- +Перелік: anyOf() +---------------- -`anyOf()` - це набір значень або схем, якими може бути значення. Ось як записати масив елементів, які можуть бути або `'a'`, або `true`, або `null`: +`anyOf()` представляє перелік значень або схем, які може приймати значення. Так ми запишемо масив елементів, які можуть бути або `'a'`, `true`, або `null`: ```php $schema = Expect::listOf( Expect::anyOf('a', true, null), ); -$processor->process($schema, ['a', true, null, 'a']); // ОК +$processor->process($schema, ['a', true, null, 'a']); // OK $processor->process($schema, ['a', false]); // ПОМИЛКА: false тут не місце ``` -Елементи перерахування також можуть бути схемами: +Елементи переліку можуть бути і схемами: ```php $schema = Expect::listOf( Expect::anyOf(Expect::string(), true, null), ); -$processor->process($schema, ['foo', true, null, 'bar']); // ОК +$processor->process($schema, ['foo', true, null, 'bar']); // OK $processor->process($schema, [123]); // ПОМИЛКА ``` -Метод `anyOf()` приймає варіанти як окремі параметри, а не як масив. Щоб передати йому масив значень, використовуйте оператор розпакування `anyOf(...$variants)`. +Метод `anyOf()` приймає варіанти як окремі параметри, а не масив. Якщо ви хочете передати йому масив значень, використовуйте unpacking оператор `anyOf(...$variants)`. -Значення за замовчуванням - `null`. Використовуйте метод `firstIsDefault()`, щоб зробити перший елемент елементом за замовчуванням: +Значення за замовчуванням — `null`. Методом `firstIsDefault()` зробимо перший елемент значенням за замовчуванням: ```php -// за замовчуванням 'hello' +// значення за замовчуванням 'hello' Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault(); ``` -Структури .[#toc-struktury] ---------------------------- +Структури +--------- -Структури - це об'єкти з певними ключами. Кожна з цих пар ключ => значення називається "властивістю": +Структури — це об'єкти з визначеними ключами. Кожна з пар ключ => значення позначається як „властивість“: -Структури приймають масиви та об'єкти і повертають об'єкти `stdClass` (якщо ви не зміните його за допомогою `castTo('array')` тощо). +Структури приймають масиви та об'єкти і повертають об'єкти `stdClass`. -За замовчуванням усі властивості є необов'язковими і мають значення за замовчуванням `null`. Ви можете визначити обов'язкові властивості, використовуючи `required()`: +За замовчуванням усі властивості є необов'язковими та мають значення за замовчуванням `null`. Обов'язкові властивості можете визначити за допомогою `required()`: ```php $schema = Expect::structure([ 'required' => Expect::string()->required(), - 'optional' => Expect::string(), // значення за замовчуванням - null + 'optional' => Expect::string(), // значення за замовчуванням null ]); $processor->process($schema, ['optional' => '']); -// ПОМИЛКА: опція 'required' відсутня +// ПОМИЛКА: option 'required' is missing $processor->process($schema, ['required' => 'foo']); // OK, повертає {'required' => 'foo', 'optional' => null} ``` -Якщо ви не хочете виводити властивості тільки зі значенням за замовчуванням, використовуйте `skipDefaults()`: +Якщо ви не хочете мати на виході властивості зі значенням за замовчуванням, використовуйте `skipDefaults()`: ```php $schema = Expect::structure([ @@ -226,7 +226,7 @@ $processor->process($schema, ['required' => 'foo']); // OK, повертає {'required' => 'foo'} ``` -Хоча `null` є значенням за замовчуванням властивості `optional`, воно не допускається у вхідних даних (значення має бути рядком). Властивості, що приймають значення `null`, визначаються за допомогою `nullable()`: +Хоча `null` є значенням за замовчуванням властивості `optional`, у вхідних даних він не дозволений (значенням має бути рядок). Властивості, що приймають `null`, визначаємо за допомогою `nullable()`: ```php $schema = Expect::structure([ @@ -235,13 +235,15 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['optional' => null]); -// ПОМИЛКА: 'optional' очікується як рядок, а надається null. +// ПОМИЛКА: 'optional' expects to be string, null given. $processor->process($schema, ['nullable' => null]); // OK, повертає {'optional' => null, 'nullable' => null} ``` -За замовчуванням, у вхідних даних не може бути зайвих елементів: +Масив усіх властивостей структури повертає метод `getShape()`. + +За замовчуванням у вхідних даних не можуть бути жодні зайві елементи: ```php $schema = Expect::structure([ @@ -249,10 +251,10 @@ $schema = Expect::structure([ ]); $processor->process($schema, ['additional' => 1]); -// ПОМИЛКА: Несподіваний елемент 'additional' +// ПОМИЛКА: Unexpected item 'additional' ``` -Подібні елементи змінити за допомогою `otherItems()`. Як параметр ми вкажемо схему для кожного додаткового елемента: +Що ми можемо змінити за допомогою `otherItems()`. Як параметр вкажемо схему, за якою будуть валідуватися зайві елементи: ```php $schema = Expect::structure([ @@ -263,175 +265,224 @@ $processor->process($schema, ['additional' => 1]); // OK $processor->process($schema, ['additional' => true]); // ПОМИЛКА ``` +Нову структуру можете створити, успадкувавши від іншої за допомогою `extend()`: + +```php +$dog = Expect::structure([ + 'name' => Expect::string(), + 'age' => Expect::int(), +]); + +$dogWithBreed = $dog->extend([ + 'breed' => Expect::string(), +]); +``` + + +Масиви .{data-version:1.3.2} +---------------------------- + +Масиви з визначеними ключами. Для них діє все те саме, що й для [структур |#Структури]. + +```php +$schema = Expect::array([ + 'required' => Expect::string()->required(), + 'optional' => Expect::string(), // значення за замовчуванням null +]); +``` + +Можна також визначити індексований масив, відомий як кортеж (tuple): + +```php +$schema = Expect::array([ + Expect::int(), + Expect::string(), + Expect::bool(), +]); + +$processor->process($schema, [1, 'hello', true]); // OK +``` + -Застарілі елементи .[#toc-ustarevsie-elementy] ----------------------------------------------- +Застарілі властивості +--------------------- -Ви можете оголосити властивість застарілою, використовуючи метод `deprecated([string $message])`. Повідомлення про застарівання повертаються за допомогою `$processor->getWarnings()`: +Ви можете позначити властивість як застарілу за допомогою методу `deprecated([string $message])`. Інформація про припинення підтримки повертається за допомогою `$processor->getWarnings()`: ```php $schema = Expect::structure([ - 'old' => Expect::int()->deprecated('Елемент %path% застарів'), + 'old' => Expect::int()->deprecated('The item %path% is deprecated'), ]); $processor->process($schema, ['old' => 1]); // OK -$processor->getWarnings(); // ["Елемент 'old' застарів"] +$processor->getWarnings(); // ["The item 'old' is deprecated"] ``` -Діапазони: min() max() .[#toc-diapazony-min-max] ------------------------------------------------- +Діапазони: min() max() +---------------------- -Використовуйте `min()` і `max()` для обмеження кількості елементів у масивах: +За допомогою `min()` та `max()` можна обмежити кількість елементів у масивах: ```php -// масив, мінімум 10 елементів, максимум 20 елементів +// масив, щонайменше 10 елементів, щонайбільше 20 елементів Expect::array()->min(10)->max(20); ``` -Для рядків обмежує їхню довжину: +У рядків обмежити їх довжину: ```php -// рядок, довжиною не менше 10 символів, максимум 20 символів +// рядок, щонайменше 10 символів завдовжки, щонайбільше 20 символів Expect::string()->min(10)->max(20); ``` -Для чисел обмежує їхнє значення: +У чисел обмежити їх значення: ```php -// ціле число, від 10 до 20 включно +// ціле число, між 10 та 20 включно Expect::int()->min(10)->max(20); ``` -Звичайно, можна згадати тільки `min()`, або тільки `max()`: +Звісно, можна вказати лише `min()`, або лише `max()`: ```php -// рядок, максимум 20 символів +// рядок щонайбільше 20 символів Expect::string()->max(20); ``` -Регулярні вирази: pattern() .[#toc-regulyarnye-vyrazeniya-pattern] ------------------------------------------------------------------- +Регулярні вирази: pattern() +--------------------------- -Використовуючи `pattern()`, ви можете вказати регулярний вираз, якому має відповідати **всівся** вхідний рядок (тобто так, ніби він був загорнутий у символи `^` a `$`): +За допомогою `pattern()` можна вказати регулярний вираз, якому має відповідати **весь** вхідний рядок (тобто, ніби він був обгорнутий символами `^` та `$`): ```php -// только 9 цифр +// рівно 9 цифр Expect::string()->pattern('\d{9}'); ``` -Користувацькі твердження: assert() .[#toc-pol-zovatel-skie-utverzdeniya-assert] -------------------------------------------------------------------------------- +Власні обмеження: assert() +-------------------------- -Ви можете додати будь-які інші обмеження, використовуючи `assert(callable $fn)`. +Будь-які інші обмеження задаємо за допомогою `assert(callable $fn)`. ```php $countIsEven = fn($v) => count($v) % 2 === 0; $schema = Expect::arrayOf('string') - ->assert($countIsEven); // число має бути парним + ->assert($countIsEven); // кількість має бути парною $processor->process($schema, ['a', 'b']); // OK -$processor->process($schema, ['a', 'b', 'c']); // ПОМИЛКА: 3 - непарне число +$processor->process($schema, ['a', 'b', 'c']); // ПОМИЛКА: 3 не є парною кількістю ``` Або ```php -Expect::string()->assert('is_file'); // файл повинен існувати +Expect::string()->assert('is_file'); // файл має існувати ``` -Ви можете додати власний опис для кожного твердження. Він буде частиною повідомлення про помилку. +До кожного обмеження можете додати власний опис. Він буде частиною повідомлення про помилку. ```php $schema = Expect::arrayOf('string') - ->assert($countIsEven, 'Парні елементи в масиві'); + ->assert($countIsEven, 'Even items in array'); $processor->process($schema, ['a', 'b', 'c']); -// Невдале твердження "Парні елементи в масиві" для елемента з масивом значень. +// Failed assertion "Even items in array" for item with value array. ``` -Цей метод можна викликати багаторазово для додавання нових тверджень. +Метод можна викликати повторно і так додати більше обмежень. Його можна чергувати з викликами `transform()` та `castTo()`. -Зіставлення з об'єктами: from() .[#toc-sopostavlenie-s-ob-ektami-from] ----------------------------------------------------------------------- +Трансформації: transform() .{data-version:1.2.5} +------------------------------------------------ -З класу можна згенерувати схему структури. Приклад: +Успішно валідовані дані можна модифікувати за допомогою власної функції: ```php -class Config -{ - public string $name; - public string|null $password; - public bool $admin = false; -} +// перетворення на великі літери: +Expect::string()->transform(fn(string $s) => strtoupper($s)); +``` -$schema = Expect::from(new Config); +Метод можна викликати повторно і так додати більше трансформацій. Його можна чергувати з викликами `assert()` та `castTo()`. Операції виконуються в порядку, в якому вони оголошені: -$data = [ - 'name' => 'jeff', -]; - -$normalized = $processor->process($schema, $data); -// $normalized instanceof Config -// $normalized = {'name' => 'jeff', 'password' => null, 'admin' => false} +```php +Expect::type('string|int') + ->castTo('string') + ->assert('ctype_lower', 'All characters must be lowercased') + ->transform(fn(string $s) => strtoupper($s)); // перетворення на великі літери ``` -Якщо ви використовуєте PHP 7.4 або вище, ви можете використовувати вбудовані типи: +Метод `transform()` може одночасно трансформувати та валідувати значення. Це часто простіше та менш дубльовано, ніж ланцюжок `transform()` та `assert()`. Для цього функція отримує об'єкт [Context |api:Nette\Schema\Context] з методом `addError()`, який можна використовувати для додавання інформації про проблеми з валідацією: ```php -class Config -{ - public string $name; - public ?string $password; - public bool $admin = false; -} +Expect::string() + ->transform(function (string $s, Nette\Schema\Context $context) { + if (!ctype_lower($s)) { + $context->addError('All characters must be lowercased', 'my.case.error'); + return null; + } -$schema = Expect::from(new Config); + return strtoupper($s); + }); ``` -Підтримуються також анонімні класи: + +Перетворення типу: castTo() +--------------------------- + +Успішно валідовані дані можна перетворити на інший тип: ```php -$schema = Expect::from(new class { - public string $name; - public ?string $password; - public bool $admin = false; -}); +Expect::scalar()->castTo('string'); ``` -Оскільки інформації, отриманої з визначення класу, може бути недостатньо, ви можете додати користувацьку схему для елементів за допомогою другого параметра: +Крім нативних PHP типів, можна перетворювати і на класи. При цьому розрізняється, чи це простий клас без конструктора, чи клас з конструктором. Якщо клас не має конструктора, створюється його екземпляр, і всі елементи структури записуються у властивості: ```php -$schema = Expect::from(new Config, [ - 'name' => Expect::string()->pattern('\w:.*'), -]); -``` +class Info +{ + public bool $processRefund; + public int $refundAmount; +} +Expect::structure([ + 'processRefund' => Expect::bool(), + 'refundAmount' => Expect::int(), +])->castTo(Info::class); -Приведення: castTo() .[#toc-privedenie-castto] ----------------------------------------------- +// створить '$obj = new Info' і запише в $obj->processRefund та $obj->refundAmount +``` -Успішно перевірені дані можуть бути відкинуті: +Якщо клас має конструктор, елементи структури передаються як іменовані параметри конструктора: ```php -Expect::scalar()->castTo('string'); +class Info +{ + public function __construct( + public bool $processRefund, + public int $refundAmount, + ) { + } +} + +// створить $obj = new Info(processRefund: ..., refundAmount: ...) ``` -На додаток до власних типів PHP, ви також можете приводити до класів: +Перетворення типу в поєднанні зі скалярним параметром створить об'єкт і передасть значення як єдиний параметр конструктора: ```php -Expect::scalar()->castTo('AddressEntity'); +Expect::string()->castTo(DateTime::class); +// створить new DateTime(...) ``` -Нормалізація: before() .[#toc-normalizaciya-before] ---------------------------------------------------- +Нормалізація: before() +---------------------- -Перед самою перевіркою дані можуть бути нормалізовані за допомогою методу `before()`. Як приклад, нехай є елемент, який має бути масивом рядків (наприклад, `['a', 'b', 'c']`), але отримує вхідні дані у вигляді рядка `a b c`: +Перед самою валідацією дані можна нормалізувати за допомогою методу `before()`. Як приклад наведемо елемент, який має бути масивом рядків (наприклад, `['a', 'b', 'c']`), але приймає вхід у формі рядка `a b c`: ```php $explode = fn($v) => explode(' ', $v); @@ -440,7 +491,48 @@ $schema = Expect::arrayOf('string') ->before($explode); $normalized = $processor->process($schema, 'a b c'); -// OK, повертає ['a', 'b', 'c'] +// OK і поверне ['a', 'b', 'c'] +``` + + +Мапування на об'єкти: from() +---------------------------- + +Схему структури ми можемо згенерувати з класу. Приклад: + +```php +class Config +{ + public string $name; + public string|null $password; + public bool $admin = false; +} + +$schema = Expect::from(new Config); + +$data = [ + 'name' => 'franta', +]; + +$normalized = $processor->process($schema, $data); +// $normalized instanceof Config +// $normalized = {'name' => 'franta', 'password' => null, 'admin' => false} ``` -{{leftbar: nette:@menu-topics}} +Підтримуються також анонімні класи: + +```php +$schema = Expect::from(new class { + public string $name; + public ?string $password; + public bool $admin = false; +}); +``` + +Оскільки інформація, отримана з визначення класу, може бути недостатньою, ви можете другим параметром доповнити елементам власну схему: + +```php +$schema = Expect::from(new Config, [ + 'name' => Expect::string()->pattern('\w:.*'), +]); +``` diff --git a/schema/uk/@meta.texy b/schema/uk/@meta.texy new file mode 100644 index 0000000000..083a8ab9f7 --- /dev/null +++ b/schema/uk/@meta.texy @@ -0,0 +1,2 @@ +{{sitename: Документація Nette}} +{{leftbar: nette:@menu-topics}} diff --git a/security/bg/@home.texy b/security/bg/@home.texy index 44a9920db2..eae5cd87d5 100644 --- a/security/bg/@home.texy +++ b/security/bg/@home.texy @@ -1,14 +1,14 @@ -Защита -****** +Nette Security +************** .[perex] -Пакетът `nette/security` отговаря за [удостоверяването на потребителите |authentication], [контрола на достъпа |authorization] и [хеширането на пароли |passwords]. +Пакетът `nette/security` се грижи за [вход на потребители |authentication], [проверка на права |authorization] и [хеширане на пароли |passwords]. -Инсталиране на .[#toc-installation] ------------------------------------ +Инсталация +---------- -Изтеглете и инсталирайте пакета с помощта на [Composer |best-practices:composer]: +Можете да изтеглите и инсталирате библиотеката с помощта на инструмента [Composer |best-practices:composer]: ```shell composer require nette/security diff --git a/security/bg/@left-menu.texy b/security/bg/@left-menu.texy index 5a8288fc99..8e5715dc9f 100644 --- a/security/bg/@left-menu.texy +++ b/security/bg/@left-menu.texy @@ -1,7 +1,7 @@ -Сигурност на Nette -****************** -- [Преглед |@home] -- [Удостоверяване |Authentication] -- [Оторизация |Authorization] +Nette Security +************** +- [Въведение |@home] +- [Автентикация |authentication] +- [Авторизация |authorization] - [Хеширане на пароли |passwords] -- [Конфигурация |Configuration] +- [Конфигурация |configuration] diff --git a/security/bg/@meta.texy b/security/bg/@meta.texy new file mode 100644 index 0000000000..57804a1127 --- /dev/null +++ b/security/bg/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документация на Nette}} diff --git a/security/bg/authentication.texy b/security/bg/authentication.texy index 7545bd6182..2aa7718d36 100644 --- a/security/bg/authentication.texy +++ b/security/bg/authentication.texy @@ -1,48 +1,48 @@ -Удостоверяване на потребителя -***************************** +Вход на потребители (Автентикация) +********************************** <div class=perex> -Малко или много значимите уеб приложения се нуждаят от механизъм за влизане на потребителите в системата или за проверка на техните привилегии. В тази глава ще говорим за: +Почти няма уеб приложение, което да се справи без механизъм за влизане на потребители и проверка на потребителските права. В тази глава ще говорим за: -- влизане и излизане на потребителя -- автентификатори и оторизатори на потребители +- влизане и излизане на потребители +- собствени автентикатори </div> -→ [Монтаж и изисквания |@home#Installation] +→ [Инсталация и изисквания |@home#Инсталация] -В примерите ще използваме обект от клас [api:Nette\Security\User], който представлява текущия потребител и който получавате, като го предавате с помощта на [инжектиране на зависимости |dependency-injection:passing-dependencies]. В презентаторите просто се обадете на `$user = $this->getUser()`. +В примерите ще използваме обект от клас [api:Nette\Security\User], който представлява текущия потребител и до който можете да стигнете, като си го поискате чрез [dependency injection |dependency-injection:passing-dependencies]. В презентерите е достатъчно само да извикате `$user = $this->getUser()`. -Удостоверяване .[#toc-authentication] -===================================== +Автентикация +============ -Удостоверяването се отнася до **входа на потребителя** - процесът, при който се проверява самоличността на потребителя. Потребителят обикновено се идентифицира с потребителско име и парола. Проверката се извършва от т.нар. [автентификатор |#Authenticator]. Ако входът е неуспешен, `Nette\Security\AuthenticationException` се отхвърля. +Автентикацията означава **влизане на потребители**, тоест процес, при който се проверява дали потребителят е наистина този, за когото се представя. Обикновено се доказва с потребителско име и парола. Проверката се извършва от т.нар. [#автентикатор]. Ако влизането се провали, се хвърля `Nette\Security\AuthenticationException`. ```php try { $user->login($username, $password); } catch (Nette\Security\AuthenticationException $e) { - $this->flashMessage('The username or password you entered is incorrect.'); + $this->flashMessage('Потребителското име или паролата са грешни'); } ``` -Ето как да излезете от системата: +По този начин излизате от системата: ```php $user->logout(); ``` -И проверете дали потребителят е влязъл в системата: +И проверка дали е влязъл: ```php -echo $user->isLoggedIn() ? 'yes' : 'no'; +echo $user->isLoggedIn() ? 'да' : 'не'; ``` -Просто, нали? А всички аспекти на сигурността се обработват от Nette за вас. +Много просто, нали? И всички аспекти на сигурността Nette решава за вас. -В Presenter можете да проверявате влизането в системата в метода `startup()` и да пренасочвате невключен потребител към страницата за влизане. +В презентерите можете да проверите влизането в метода `startup()` и да пренасочите невписания потребител към страницата за вход. ```php protected function startup() @@ -55,39 +55,38 @@ protected function startup() ``` -Продължителност .[#toc-expiration] -================================== +Изтичане на валидност +===================== -Срокът на валидност на потребителското име изтича едновременно с [изтичането на срока на валидност на хранилището |#Storage-for-Logged-User], което обикновено е сесия (вж. задаване на [срок на валидност на сесията |http:configuration#Session] ). -Възможно е обаче да се зададе и по-кратък период от време, след който потребителят да излезе от системата. Това се прави с помощта на метода `setExpiration()`, който се извиква преди `login()`. Предоставя като параметър низ с относителното време: +Влизането на потребителя изтича заедно с [изтичането на валидността на хранилището |#Хранилище на влезлия потребител], което обикновено е сесия (вижте настройката на [изтичане на сесията |http:configuration#Сесия]). Възможно е обаче да се зададе и по-кратък времеви интервал, след изтичането на който потребителят ще бъде излязъл от системата. За това служи методът `setExpiration()`, който се извиква преди `login()`. Като параметър посочете низ с относително време: ```php -// срокът за влизане изтича след 30 минути неактивност +// влизането изтича след 30 минути неактивност $user->setExpiration('30 minutes'); -// отмяна на зададената дата на изтичане +// отмяна на зададеното изтичане $user->setExpiration(null); ``` -Методът `$user->getLogoutReason()` определя дали потребителят е излязъл от системата, тъй като е изтекъл интервалът от време. Той връща или константата `Nette\Security\UserStorage::LogoutInactivity`, ако времето е изтекло, или `UserStorage::LogoutManual`, ако е извикан методът `logout()`. +Дали потребителят е бил излязъл поради изтичане на времевия интервал разкрива методът `$user->getLogoutReason()`, който връща или константата `Nette\Security\UserStorage::LogoutInactivity` (изтекъл е времевият лимит) или `UserStorage::LogoutManual` (излязъл с метода `logout()`). -Автентификатор .[#toc-authenticator] -==================================== +Автентикатор +============ -Това е обект, който проверява данните за вход, т.е. обикновено име и парола. Тривиална реализация е класът [api:Nette\Security\SimpleAuthenticator], който може да бъде дефиниран в [конфигурацията |configuration]: +Това е обект, който проверява данните за вход, тоест обикновено име и парола. Тривиална форма е класът [api:Nette\Security\SimpleAuthenticator], който можем да дефинираме в [конфигурацията|configuration]: ```neon security: users: - # name: password - johndoe: secret123 - kathy: evenmoresecretpassword + # име: парола + frantisek: tajneheslo + katka: jestetajnejsiheslo ``` -Това решение е по-подходящо за целите на тестването. Ще ви покажем как да създадете автентификатор, който ще проверява идентификационните данни спрямо таблица от базата данни. +Това решение е подходящо по-скоро за тестови цели. Ще покажем как да създадем автентикатор, който ще проверява данните за вход спрямо таблица в база данни. -Автентификаторът е обект, който имплементира интерфейса [api:Nette\Security\Authenticator] с метода `authenticate()`. Задачата му е или да върне така наречения [идентификатор |#Identity], или да хвърли изключение `Nette\Security\AuthenticationException`. Можете също така да посочите код за грешка `Authenticator::IdentityNotFound` или `Authenticator::InvalidCredential`. +Автентикаторът е обект, имплементиращ интерфейса [api:Nette\Security\Authenticator] с метод `authenticate()`. Неговата задача е или да върне т.нар. [#идентичност] или да хвърли изключение `Nette\Security\AuthenticationException`. Би било възможно при нея още да се посочи код за грешка за по-фино разграничаване на възникналата ситуация: `Authenticator::IdentityNotFound` и `Authenticator::InvalidCredential`. ```php use Nette; @@ -108,25 +107,25 @@ class MyAuthenticator implements Nette\Security\Authenticator ->fetch(); if (!$row) { - throw new Nette\Security\AuthenticationException('User not found.'); + throw new Nette\Security\AuthenticationException('Потребителят не е намерен.'); } if (!$this->passwords->verify($password, $row->password)) { - throw new Nette\Security\AuthenticationException('Invalid password.'); + throw new Nette\Security\AuthenticationException('Невалидна парола.'); } return new SimpleIdentity( $row->id, - $row->role, // или массив ролей + $row->role, // или масив от няколко роли ['name' => $row->username], ); } } ``` -Класът MyAuthenticator комуникира с базата данни чрез [Nette Database Explorer |database:explorer] и работи с таблицата `users`, където колоната `username` съдържа потребителското име за вход, а колоната `password` - [хеш. |passwords] След като провери потребителското име и паролата, тя връща ID с идентификатора на потребителя, ролята (колона `role` в таблицата), която ще споменем [по-късно |#Roles], и масив с допълнителни данни (в нашия случай потребителското име). +Класът MyAuthenticator комуникира с базата данни чрез [Nette Database Explorer|database:explorer] и работи с таблицата `users`, където в колоната `username` е потребителското име на потребителя, а в колоната `password` е [хешът на паролата|passwords]. След проверка на името и паролата връща идентичност, която носи ID на потребителя, неговата роля (колона `role` в таблицата), за която ще говорим повече [по-късно |authorization#Роля], и масив с други данни (в нашия случай потребителско име). -Ще добавим автентификатора към конфигурацията [като услуга на |dependency-injection:services] контейнер DI: +Автентикаторът още ще добавим към конфигурацията [като сървис|dependency-injection:services] на DI контейнера: ```neon services: @@ -134,50 +133,50 @@ services: ``` -$onLoggedIn, $onLoggedOut Събития +Събития $onLoggedIn, $onLoggedOut --------------------------------- -Обектът `Nette\Security\User` има [събития |nette:glossary#Events] `$onLoggedIn` и `$onLoggedOut`, така че можете да добавите обратни извиквания, които се задействат след успешно влизане или след излизане на потребителя. +Обектът `Nette\Security\User` има [събития |nette:glossary#Събития events] `$onLoggedIn` и `$onLoggedOut`, така че можете да добавите callback-ове, които се извикват след успешно влизане респ. след излизане на потребителя. ```php $user->onLoggedIn[] = function () { - // потребителят току-що е влязъл в системата + // потребителят току-що е влязъл }; ``` -Идентичност .[#toc-identity] -============================ +Идентичност +=========== -Идентичността е набор от информация за потребителя, която се връща от удостоверителя и която след това се съхранява в сесия и се извлича с помощта на `$user->getIdentity()`. По този начин можем да извличаме идентификатора, ролите и други потребителски данни, както сме ги предали в автентификатора: +Идентичността представлява набор от информация за потребителя, която връща автентикаторът и която впоследствие се съхранява в сесията и я получаваме с помощта на `$user->getIdentity()`. Можем така да получим id, роли и други потребителски данни, както сме ги предали в автентикатора: ```php $user->getIdentity()->getId(); -// съкращението $user->getId() също работи; +// работи и съкращението $user->getId(); $user->getIdentity()->getRoles(); -// данните на потребителя могат да бъдат достъпни като свойство -// името, което сме предали на MyAuthenticator +// потребителските данни са достъпни като свойства +// името, което сме предали в MyAuthenticator $user->getIdentity()->name; ``` -Важно е да се отбележи, че когато потребителят се отпише, използвайки `$user->logout()`, **личността не се изтрива** и все още е налична. Следователно, ако идентичността съществува, тя сама по себе си не гарантира, че потребителят също е влязъл в системата. Ако искаме изрично да премахнем идентификатора, излизаме от системата, като използваме `logout(true)`. +Важното е, че при излизане с помощта на `$user->logout()` **идентичността не се изтрива** и е все още налична. Така че, въпреки че потребителят има идентичност, не е задължително да е влязъл. Ако искаме изрично да изтрием идентичността, излизаме от системата с извикване на `logout(true)`. -Благодарение на това все още можете да идентифицирате кой потребител е на компютъра и например да показвате персонализирани оферти в онлайн магазина, но можете да показвате личните му данни само след като е влязъл в системата. +Благодарение на това можете да продължите да предполагате кой потребител е пред компютъра и например да му показвате персонализирани оферти в електронния магазин, но можете да му покажете личните му данни едва след като влезе. -Identity е обект, който имплементира интерфейса [api:Nette\Security\IIdentity], като имплементацията по подразбиране е [api:Nette\Security\SimpleIdentity]. Както беше споменато по-горе, идентичността се съхранява в сесия, така че ако например променим ролята на някой влязъл в системата потребител, старите данни ще се съхраняват в идентичността, докато той не влезе отново. +Идентичността е обект, имплементиращ интерфейса [api:Nette\Security\IIdentity], имплементацията по подразбиране е [api:Nette\Security\SimpleIdentity]. И както беше споменато, поддържа се в сесията, така че ако например променим ролята на някой от влезлите потребители, старите данни ще останат в неговата идентичност до следващото му влизане. -Съхраняване на данни за влязъл в системата потребител .[#toc-storage-for-logged-user] -===================================================================================== +Хранилище на влезлия потребител +=============================== -Двете основни части от информацията за даден потребител, т.е. дали е влязъл в системата и неговата [самоличност |#Identity], обикновено се съхраняват в сесия. Което може да бъде променено. Обектът, който реализира интерфейса `Nette\Security\UserStorage`, отговаря за съхраняването на тази информация. Съществуват две стандартни реализации, като първата предава данните в сесия, а втората - в бисквитка. Това са класовете `Nette\Bridges\SecurityHttp\SessionStorage` и `CookieStorage`. Изборът и конфигурирането на хранилището в конфигурацията за [сигурност › удостоверяване |configuration] е много лесно. +Двете основни информации за потребителя, а именно дали е влязъл и неговата [#идентичност], обикновено се пренасят в сесията. Което може да се промени. За съхраняването на тази информация отговаря обект, имплементиращ интерфейса `Nette\Security\UserStorage`. Налични са две стандартни имплементации, първата пренася данни в сесията, а втората в бисквитка. Това са класовете `Nette\Bridges\SecurityHttp\SessionStorage` и `CookieStorage`. Можете да изберете хранилище и да го конфигурирате много удобно в конфигурацията [security › authentication |configuration#Хранилище]. -Можете също така да контролирате как точно ще се съхранява (*спиране*) и възстановява (*събуждане*) удостоверяването на автентичността. Всичко, от което се нуждаете, е автентификаторът да имплементира интерфейса `Nette\Security\IdentityHandler`. Той има два метода: `sleepIdentity()` се извиква преди записването на ID в хранилището, а `wakeupIdentity()` се извиква след прочитането на ID. Тези методи могат да променят съдържанието на идентификатора или да го заменят с нов обект, който се връща. Методът `wakeupIdentity()` може дори да върне `null`, с което потребителят да излезе от системата. +Освен това можете да повлияете на това как точно ще протича съхраняването на идентичността (*sleep*) и възстановяването (*wakeup*). Достатъчно е автентикаторът да имплементира интерфейса `Nette\Security\IdentityHandler`. Той има два метода: `sleepIdentity()` се извиква преди запис на идентичността в хранилището и `wakeupIdentity()` след нейното прочитане. Методите могат да променят съдържанието на идентичността, евентуално да я заменят с нов обект, който връщат. Методът `wakeupIdentity()` може дори да върне `null`, с което потребителят ще бъде излязъл от системата. -Като пример показваме решение на често срещан проблем за това как да се актуализират ролите на идентификаторите веднага след възстановяване от сесия. В метода `wakeupIdentity()` се предават текущите роли на идентификатора, например от базата данни: +Като пример ще покажем решение на често задавания въпрос как да актуализираме ролите в идентичността веднага след зареждане от сесията. В метода `wakeupIdentity()` ще предадем на идентичността актуалните роли, например от базата данни: ```php final class Authenticator implements @@ -185,25 +184,25 @@ final class Authenticator implements { public function sleepIdentity(IIdentity $identity): IIdentity { - // тук можете да промените идентификатора, преди да го съхраните след влизане в системата, - // но сега нямаме нужда от него + // тук може да се промени идентичността преди запис в хранилището след влизане, + // но това сега не ни е необходимо return $identity; } public function wakeupIdentity(IIdentity $identity): ?IIdentity { - // актуализиране на ролите в идентичността + // актуализация на ролите в идентичността $userId = $identity->getId(); $identity->setRoles($this->facade->getUserRoles($userId)); return $identity; } ``` -Сега се връщаме към съхранението, базирано на бисквитки. Тя ни позволява да създадем сайт, в който потребителите могат да влизат, без да се налага да използват сесия. Следователно не е необходимо да се записва на диска. В края на краищата така работи сайтът, който четете в момента, включително форумът. В този случай внедряването на `IdentityHandler` е необходимост. В "бисквитката" ще съхраняваме само произволен символ, представляващ влезлия в системата потребител. +А сега ще се върнем към хранилището, базирано на бисквитки. То ви позволява да създадете уеб сайт, където потребителите могат да влизат, без да са необходими сесии. Тоест, не е необходимо да се записва на диска. Впрочем, така работи и уеб сайтът, който четете в момента, включително форумът. В този случай имплементацията на `IdentityHandler` е задължителна. В бисквитката ще съхраняваме само случаен токен, представляващ влезлия потребител. -Затова първо ще зададем желаното хранилище в конфигурацията с `security › authentication › storage: cookie`. +Първо, в конфигурацията ще зададем желаното хранилище с помощта на `security › authentication › storage: cookie`. -Ще добавим към базата данни колона `authtoken`, в която всеки потребител ще има [напълно случаен, уникален и неразгадаем |utils:random] низ с достатъчна дължина (поне 13 символа). Хранилището `CookieStorage` съхранява само стойността `$identity->getId()` в "бисквитката", така че в метода `sleepIdentity()` ще заменим оригиналната самоличност с прокси с `authtoken` в ID, а в метода `wakeupIdentity()`, обратно, ще възстановим цялата самоличност от базата данни чрез автокент: +В базата данни ще създадем колона `authtoken`, в която всеки потребител ще има [напълно случаен, уникален и невъзможен за отгатване|utils:random] низ с достатъчна дължина (поне 13 знака). Хранилището `CookieStorage` пренася в бисквитката само стойността `$identity->getId()`, така че в `sleepIdentity()` ще заменим оригиналната идентичност със заместваща с `authtoken` в ID, а обратно, в метода `wakeupIdentity()` по authtoken ще прочетем цялата идентичност от базата данни: ```php final class Authenticator implements @@ -212,21 +211,21 @@ final class Authenticator implements public function authenticate(string $username, string $password): SimpleIdentity { $row = $this->db->fetch('SELECT * FROM user WHERE username = ?', $username); - // проверка за парола + // проверяваме паролата ... - // връщане на идентификатор с всички данни от базата данни + // връщаме идентичност с всички данни от базата данни return new SimpleIdentity($row->id, null, (array) $row); } public function sleepIdentity(IIdentity $identity): SimpleIdentity { - // връщаме идентификатора на проксито, където идентификаторът е автотекст + // връщаме заместваща идентичност, където в ID ще бъде authtoken return new SimpleIdentity($identity->authtoken); } public function wakeupIdentity(IIdentity $identity): ?SimpleIdentity { - // заменете идентификатора на проксито с пълния идентификатор, както в authenticate() + // заместващата идентичност заменяме с пълна идентичност, както в authenticate() $row = $this->db->fetch('SELECT * FROM user WHERE authtoken = ?', $identity->getId()); return $row ? new SimpleIdentity($row->id, null, (array) $row) @@ -236,16 +235,16 @@ final class Authenticator implements ``` -Множество независими удостоверявания .[#toc-multiple-independent-authentications] -================================================================================= +Няколко независими влизания +=========================== -Възможно е в рамките на един сайт и една сесия да има едновременно няколко независими регистрирани потребители. Например, ако искаме да имаме отделно удостоверяване за frontend и backend, просто създаваме уникално пространство от имена на сесии за всяко от тях: +Едновременно е възможно в рамките на един уеб сайт и една сесия да има няколко независими влизащи потребители. Ако например искаме да имаме на уеб сайта отделна автентикация за администрацията и публичната част, е достатъчно на всяка от тях да зададем собствено име: ```php $user->getStorage()->setNamespace('backend'); ``` -Не трябва да се забравя, че тя трябва да бъде зададена във всички места, принадлежащи към един и същ сегмент. Когато използваме презентатори, ще зададем пространството от имена в общ предшественик - обикновено BasePresenter. За тази цел ще разширим метода [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()]: +Важно е да помним, че трябва да зададем пространството от имена винаги на всички места, принадлежащи към дадената част. Ако използваме презентери, ще зададем пространството от имена в общия предшественик за дадената част - обикновено BasePresenter. Ще направим това чрез разширяване на метода [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()]: ```php public function checkRequirements($element): void @@ -256,10 +255,10 @@ public function checkRequirements($element): void ``` -Множество автентификатори .[#toc-multiple-authenticators] ---------------------------------------------------------- +Няколко автентикатора +--------------------- -Разделянето на едно приложение на сегменти с независимо удостоверяване обикновено изисква използването на различни удостоверители. Регистрирането на два класа, реализиращи Authenticator, в конфигурационните услуги обаче ще доведе до грешка, тъй като Nette няма да знае кой от тях трябва да бъде [автоматично свързан с |dependency-injection:autowiring] обекта `Nette\Security\User`. Ето защо трябва да ограничим автоматичното свързване за тях с `autowired: self`, така че то да се активира само когато класът им е изрично поискан: +Разделянето на приложението на части с независимо влизане обикновено изисква и различни автентикатори. Веднага щом обаче регистрираме в конфигурацията на услугите два класа, имплементиращи Authenticator, Nette няма да знае кой от тях автоматично да присвои на обекта `Nette\Security\User` и ще покаже грешка. Затова трябва за автентикаторите да ограничим [autowiring |dependency-injection:autowiring] така, че да работи само когато някой поиска конкретен клас, напр. FrontAuthenticator, което ще постигнем с избора `autowired: self`: ```neon services: @@ -278,7 +277,7 @@ class SignPresenter extends Nette\Application\UI\Presenter } ``` -Трябва само да зададем нашия автентификатор на обекта User, преди да извикаме метода [login() |api:Nette\Security\User::login()], което обикновено означава в обратната връзка на формата за вход: +Автентикаторът на обекта User ще зададем преди извикване на метода [login() |api:Nette\Security\User::login()], така че обикновено в кода на формата, който го вписва: ```php $form->onSuccess[] = function (Form $form, \stdClass $data) { diff --git a/security/bg/authorization.texy b/security/bg/authorization.texy index f2c4536561..79f040b2ec 100644 --- a/security/bg/authorization.texy +++ b/security/bg/authorization.texy @@ -1,48 +1,48 @@ -Контрол на достъпа (оторизация) -******************************* +Проверка на правата (Авторизация) +********************************* .[perex] -Оторизацията определя дали потребителят има достатъчно привилегии, например за достъп до определен ресурс или за извършване на действие. Оторизацията предполага успешно удостоверяване, т.е. че потребителят е влязъл в системата. +Авторизацията установява дали потребителят има достатъчно права, например за достъп до определен ресурс или за извършване на някакво действие. Авторизацията предполага предишна успешна автентикация, т.е. че потребителят е влязъл. -→ [Монтаж и изисквания |@home#Installation] +→ [Инсталация и изисквания |@home#Инсталация] -В примерите ще използваме обект от клас [api:Nette\Security\User], който представлява текущия потребител и който получавате, като го предавате чрез [инжектиране на зависимости |dependency-injection:passing-dependencies]. В презентаторите просто се обадете на `$user = $this->getUser()`. +В примерите ще използваме обект от клас [api:Nette\Security\User], който представлява текущия потребител и до който можете да стигнете, като си го поискате чрез [dependency injection |dependency-injection:passing-dependencies]. В презентерите е достатъчно само да извикате `$user = $this->getUser()`. -За много прости сайтове с администрация, където правата на потребителите не са диференцирани, можете да използвате вече познатия метод `isLoggedIn()` като критерий за оторизация . С други думи: щом даден потребител влезе в системата, той има права за всички действия и обратно. +При много прости уеб сайтове с администрация, където не се разграничават правата на потребителите, е възможно като критерий за авторизация да се използва вече познатият метод `isLoggedIn()`. С други думи: щом потребителят е влязъл, има всички права и обратно. ```php -if ($user->isLoggedIn()) { // дали потребителят е влязъл в системата? - deleteItem(); // ако е така, той може да изтрие елемент +if ($user->isLoggedIn()) { // потребителят влязъл ли е? + deleteItem(); // тогава има право на операцията } ``` -Роли .[#toc-roles] ------------------- +Роля +---- -Целта на ролите е да предлагат по-прецизно управление на правата и да останат независими от потребителското име. Веднага след като потребителят влезе в системата, на него се присвояват една или повече роли. Самите роли могат да бъдат прости низове, например `admin`, `member`, `guest` и т.н. Те се посочват във втория аргумент на конструктора `SimpleIdentity`, като низ или масив. +Смисълът на ролите е да предложат по-точно управление на правата и да останат независими от потребителското име. На всеки потребител веднага при влизане приписваме една или повече роли, в които ще действа. Ролите могат да бъдат прости низове, например `admin`, `member`, `guest` и т.н. Посочват се като втори параметър на конструктора `SimpleIdentity`, или като низ, или като масив от низове - роли. -Като критерий за оторизация ще използваме метода `isInRole()`, който проверява дали потребителят е член на дадена роля: +Като критерий за авторизация сега ще използваме метода `isInRole()`, който разкрива дали потребителят действа в дадена роля: ```php -if ($user->isInRole('admin')) { // присвоена ли е на потребителя ролята администратор? - deleteItem(); // ако е така, той може да изтрие елемент +if ($user->isInRole('admin')) { // потребителят в ролята на администратор ли е? + deleteItem(); // тогава има право на операцията } ``` -Както вече знаете, излизането от системата не заличава самоличността на потребителя. Така че методът `getIdentity()` все още връща обекта `SimpleIdentity`, включително всички предоставени роли. Рамката Nette Framework се придържа към принципа "по-малко код, повече сигурност", така че при проверка на ролите не е необходимо да се проверява дали потребителят е влязъл в системата. Методът `isInRole()` работи с **ефективни роли**, т.е. ако потребителят е влязъл в системата, се използват ролите, присвоени на лицето, а ако не е влязъл в системата, вместо това се използва автоматичната специална роля `guest`. +Както вече знаете, след излизане на потребителя не е задължително да се изтрие неговата идентичност. Тоест, методът `getIdentity()` продължава да връща обект `SimpleIdentity`, включително всички предоставени роли. Nette Framework изповядва принципа „less code, more security“, където по-малко писане води до по-сигурен код, затова при установяване на ролите не е необходимо още да проверявате дали потребителят е влязъл. Методът `isInRole()` работи с **ефективни роли,** т.е. ако потребителят е влязъл, изхожда от ролите, посочени в идентичността, ако не е влязъл, има автоматично специална роля `guest`. -Възложител .[#toc-authorizator] -------------------------------- +Авторизатор +----------- -В допълнение към ролите ще въведем термините ресурс и операция: +Освен ролите ще въведем още понятията ресурс и операция: -- **роля** е атрибут на потребителя - например модератор, редактор, посетител, регистриран потребител, администратор, ... -- **Ресурс** е логическа единица на приложение - например статия, страница, потребител, елемент от менюто, проучване, водещ, ... -- **Операция** е конкретно действие, което потребителят може или не може да извърши върху *ресурс* - разглеждане, редактиране, изтриване, гласуване, ... +- **роля** е свойство на потребителя - напр. модератор, редактор, посетител, регистриран потребител, администратор... +- **ресурс** (*resource*) е някакъв логически елемент на уеб сайта - статия, страница, потребител, елемент в менюто, анкета, presenter, ... +- **операция** (*operation*) е някаква конкретна дейност, която потребителят може или не може да извършва с ресурса - например изтриване, редактиране, създаване, гласуване, ... -Упълномощителят е обект, който решава дали дадена *роля* има разрешение да извърши определена *операция* с определен *ресурс*. Това е обект, който имплементира интерфейса [api:Nette\Security\Authorizator] с един-единствен метод `isAllowed()`: +Авторизаторът е обект, който решава дали дадена *роля* има разрешение да извърши определена *операция* с определен *ресурс*. Това е обект, имплементиращ интерфейса [api:Nette\Security\Authorizator] с единствен метод `isAllowed()`: ```php class MyAuthorizator implements Nette\Security\Authorizator @@ -63,50 +63,50 @@ class MyAuthorizator implements Nette\Security\Authorizator } ``` -Добавяме оторизатор към конфигурацията [като услуга на |dependency-injection:services] контейнер DI: +Авторизаторът ще добавим към конфигурацията [като сървис|dependency-injection:services] на DI контейнера: ```neon services: - MyAuthorizator ``` -По-долу е даден примерен случай на употреба. Обърнете внимание, че този път се извиква методът `Nette\Security\User::isAllowed()`, а не методът authorizer, така че няма първи параметър `$role`. Този метод извиква `MyAuthorizator::isAllowed()` последователно за всички потребителски роли и връща true, ако поне една от тях има разрешение. +И следва пример за употреба. Внимание, този път извикваме метода `Nette\Security\User::isAllowed()`, а не авторизатора, така че там няма първи параметър `$role`. Този метод извиква `MyAuthorizator::isAllowed()` последователно за всички роли на потребителя и връща true, ако поне една от тях има разрешение. ```php -ако ($user->isAllowed('file')) { // позволено ли е на потребителя да прави всичко с ресурс 'file'? +if ($user->isAllowed('file')) { // може ли потребителят да прави каквото и да е с ресурс 'file'? useFile(); } -if ($user->isAllowed('file', 'delete')) { // позволено ли е на потребителя да изтрие ресурс 'file'? +if ($user->isAllowed('file', 'delete')) { // може ли над ресурс 'file' да извърши 'delete'? deleteFile(); } ``` -И двата аргумента са незадължителни и по подразбиране са *all*. +И двата параметъра са незадължителни, стойността по подразбиране `null` има значение *каквото и да е*. -Разрешения на ACL .[#toc-permission-acl] ----------------------------------------- +Permission ACL +-------------- -Nette идва с вградена реализация на оторизатор, класът [api:Nette\Security\Permission], който предлага лесен и гъвкав ACL (Access Control List) слой за контрол на разрешенията и достъпа. Когато работим с този клас, определяме роли, ресурси и индивидуални разрешения. Ролите и ресурсите обаче могат да образуват йерархии. За да обясним това, ще покажем пример за уеб приложение: +Nette идва с вградена имплементация на авторизатор, а именно класът [api:Nette\Security\Permission], предоставящ на програмиста лек и гъвкав ACL (Access Control List) слой за управление на правата и достъпите. Работата с него се състои в дефиниране на роли, ресурси и отделни права. При което ролите и ресурсите позволяват създаването на йерархии. За обяснение ще покажем пример за уеб приложение: -- `guest`: посетител, който не е влязъл в системата, но има право да чете и разглежда публичната част на сайта, т.е. да чете статии, да коментира и да гласува в анкети. -- `registered`: влязъл в системата потребител, който може да оставя и коментари. -- `admin`: можете да управлявате статии, коментари и анкети +- `guest`: невписан посетител, който може да чете и разглежда публичната част на уеб сайта, т.е. да чете статии, коментари и да гласува в анкети +- `registered`: вписан регистриран потребител, който освен това може да коментира +- `admin`: може да управлява статии, коментари и анкети -И така, определихме определени роли (`guest`, `registered` и `admin`) и споменахме ресурсите (`article`, `comments`, `poll`), до които потребителите могат да имат достъп или да предприемат действия (`view`, `vote`, `add`, `edit`). +Дефинирахме си определени роли (`guest`, `registered` и `admin`) и споменахме ресурси (`article`, `comment`, `poll`), до които потребителите с някаква роля могат да достъпват или да извършват определени операции (`view`, `vote`, `add`, `edit`). -Създаваме инстанция на класа Permission и дефинираме **роли**. Можем да използваме наследяване на роли, което гарантира, че например потребител с роля `admin` може да прави това, което може да прави обикновен посетител на сайта (и със сигурност повече). +Създаваме екземпляр на класа Permission и дефинираме **ролите**. При това можем да използваме т.нар. наследяване на роли, което гарантира, че напр. потребител с роля администратор (`admin`) може да прави и това, което обикновен посетител на уеб сайта (и разбира се, и повече). ```php $acl = new Nette\Security\Permission; $acl->addRole('guest'); -$acl->addRole('registered', 'guest'); // 'registered' наследява 'guest' -$acl->addRole('admin', 'registered'); // а 'admin' наследява 'registered' +$acl->addRole('registered', 'guest'); // 'registered' наследява от 'guest' +$acl->addRole('admin', 'registered'); // а от него наследява 'admin' ``` -Сега ще дефинираме списък с **ресурси**, до които потребителите имат достъп: +Сега дефинираме и списък с **ресурси**, до които потребителите могат да достъпват. ```php $acl->addResource('article'); @@ -114,49 +114,49 @@ $acl->addResource('comment'); $acl->addResource('poll'); ``` -Ресурсите могат да използват и наследяване, например можем да добавим `$acl->addResource('perex', 'article')`. +И ресурсите могат да използват наследяване, би било възможно например да се зададе `$acl->addResource('perex', 'article')`. -А сега най-важната част. Ще определим **правила** между тях, определящи кой какво може да прави: +А сега най-важното. Дефинираме между тях правила, определящи кой какво може да прави с какво: ```php -// сега всичко е отказано +// първоначално никой не може да прави нищо -// позволете на госта да разглежда статии, коментари и анкети +// нека guest може да преглежда статии, коментари и анкети $acl->allow('guest', ['article', 'comment', 'poll'], 'view'); -// а също и да гласува в анкети +// и в анкетите освен това и да гласува $acl->allow('guest', 'poll', 'vote'); -// регистрираният наследява правата от guesta, ще му позволим и да коментира +// регистрираният наследява правата от guest, даваме му освен това правото да коментира $acl->allow('registered', 'comment', 'add'); -// администраторът може да разглежда и редактира всичко +// администраторът може да преглежда и редактира всичко $acl->allow('admin', $acl::All, ['view', 'edit', 'add']); ``` -Какво да правим, ако искаме да **предотвратим** достъпа на някого до даден ресурс? +Какво ако искаме на някого да **забраним** достъпа до определен ресурс? ```php -// администраторът не може да редактира анкети, това би било недемократично. +// администраторът не може да редактира анкети, това би било недемократично $acl->deny('admin', 'poll', 'edit'); ``` -След като вече сме създали набор от правила, можем просто да зададем заявки за разрешение: +Сега, когато имаме създаден списък с правила, можем лесно да задаваме авторизационни въпроси: ```php -// може ли гост да разглежда статии? +// може ли guest да преглежда статии? $acl->isAllowed('guest', 'article', 'view'); // true -// може ли гост да редактира статия? +// може ли guest да редактира статии? $acl->isAllowed('guest', 'article', 'edit'); // false -// може ли гост да гласува в анкети? +// може ли guest да гласува в анкети? $acl->isAllowed('guest', 'poll', 'vote'); // true -// може ли гостът да добавя коментари? +// може ли guest да коментира? $acl->isAllowed('guest', 'comment', 'add'); // false ``` -Същото важи и за регистриран потребител, но той също може да коментира: +Същото важи и за регистрирания потребител, той обаче може и да коментира: ```php $acl->isAllowed('registered', 'article', 'view'); // true @@ -164,7 +164,7 @@ $acl->isAllowed('registered', 'comment', 'add'); // true $acl->isAllowed('registered', 'comment', 'edit'); // false ``` -Администраторът може да редактира всичко, освен анкетите: +Администраторът може да редактира всичко, освен анкети: ```php $acl->isAllowed('admin', 'poll', 'vote'); // true @@ -172,7 +172,7 @@ $acl->isAllowed('admin', 'poll', 'edit'); // false $acl->isAllowed('admin', 'comment', 'edit'); // true ``` -Разрешенията могат да бъдат оценявани динамично, като можем да оставим решението на нашето собствено обратно извикване, на което се предават всички параметри: +Правата могат също да бъдат оценявани динамично и можем да оставим решението на собствен callback, на който се предават всички параметри: ```php $assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { @@ -182,7 +182,7 @@ $assertion = function (Permission $acl, string $role, string $resource, string $ $acl->allow('registered', 'comment', null, $assertion); ``` -Но как да решим ситуацията, в която имената на ролите и ресурсите не са достатъчни, т.е. искаме да определим, че например ролята `registered` може да редактира ресурса `article` само ако е негов автор? Ще използваме обекти вместо низове, като ролята ще бъде обектът [api:Nette\Security\Role] и източникът [api:Nette\Security\Resource]. Техните методи `getRoleId()` и `getResourceId()` ще върнат изходните низове: +Но как например да решим ситуация, когато не са достатъчни само имената на ролите и ресурсите, а искаме да дефинираме, че например ролята `registered` може да редактира ресурс `article` само ако е негов автор? Вместо низове ще използваме обекти, ролята ще бъде обект [api:Nette\Security\Role], а ресурсът [api:Nette\Security\Resource]. Техните методи `getRoleId()` респ. `getResourceId()` ще връщат оригиналните низове: ```php class Registered implements Nette\Security\Role @@ -207,11 +207,11 @@ class Article implements Nette\Security\Resource } ``` -Сега нека създадем правило: +А сега ще създадем правило: ```php $assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { - $role = $acl->getQueriedRole(); // обект Регистриран + $role = $acl->getQueriedRole(); // обект Registered $resource = $acl->getQueriedResource(); // обект Article return $role->id === $resource->authorId; }; @@ -219,7 +219,7 @@ $assertion = function (Permission $acl, string $role, string $resource, string $ $acl->allow('registered', 'article', 'edit', $assertion); ``` -ACL се заявява чрез предаване на обекти: +И заявката към ACL се извършва чрез предаване на обекти: ```php $user = new Registered(/* ... */); @@ -227,7 +227,7 @@ $article = new Article(/* ... */); $acl->isAllowed($user, $article, 'edit'); ``` -Една роля може да бъде наследена от една или повече други роли. Но какво се случва, ако при един от предците определено действие е разрешено, а при друг - забранено? След това влиза в сила *теглото на ролята* - последната роля в масива от наследени роли има най-голямо тегло, а първата - най-малкото: +Ролята може да наследява от друга роля или от няколко роли. Какво обаче ще се случи, ако един предшественик има забранено действие, а друг разрешено? Какви ще бъдат правата на потомъка? Определя се според тежестта на ролята - последната посочена роля в списъка с предшественици има най-голяма тежест, първата посочена роля - най-малка. По-нагледно е от примера: ```php $acl = new Nette\Security\Permission; @@ -239,22 +239,22 @@ $acl->addResource('backend'); $acl->allow('admin', 'backend'); $acl->deny('guest', 'backend'); -// пример А: ролята admin има по-малка тежест от ролята guest +// случай А: ролята admin има по-малка тежест от ролята guest $acl->addRole('john', ['admin', 'guest']); $acl->isAllowed('john', 'backend'); // false -// пример Б: ролята admin има по-голяма тежест от ролята guest +// случай Б: ролята admin има по-голяма тежест от guest $acl->addRole('mary', ['guest', 'admin']); $acl->isAllowed('mary', 'backend'); // true ``` -Ролите и ресурсите също могат да бъдат премахвани (`removeRole()`, `removeResource()`), правилата също могат да бъдат отменяни (`removeAllow()`, `removeDeny()`). Връща се масив от всички роли на преки родители `getRoleParents()`. Въпросът дали две същности наследяват една от друга връща `roleInheritsFrom()` и `resourceInheritsFrom()`. +Ролите и ресурсите могат и да се премахват (`removeRole()`, `removeResource()`), могат да се ревертират и правилата (`removeAllow()`, `removeDeny()`). Масивът с всички преки родителски роли връща `getRoleParents()`, дали две същности наследяват една от друга връща `roleInheritsFrom()` и `resourceInheritsFrom()`. -Добавяне като услуга .[#toc-add-as-a-service] ---------------------------------------------- +Добавяне като услуги +-------------------- -Трябва да добавим създадения от нас ACL към конфигурацията като услуга, за да може да се използва от обекта `$user`, т.е. за да можем да го използваме в нашия код, например `$user->isAllowed('article', 'view')`. За тази цел ще напишем фабрика за него: +Създадения от нас ACL трябва да предадем на конфигурацията като услуга, за да започне да го използва обектът `$user`, тоест да е възможно да се използва в кода напр. `$user->isAllowed('article', 'view')`. За тази цел ще напишем фабрика за него: ```php namespace App\Model; @@ -272,14 +272,14 @@ class AuthorizatorFactory } ``` -И го добавете към конфигурацията: +И ще я добавим към конфигурацията: ```neon services: - App\Model\AuthorizatorFactory::create ``` -След това в хоста можете да проверите разрешенията в метода `startup()`, напр: +В презентерите след това можете да проверите правата например в метода `startup()`: ```php protected function startup() diff --git a/security/bg/configuration.texy b/security/bg/configuration.texy index aa7943e827..abc7539e74 100644 --- a/security/bg/configuration.texy +++ b/security/bg/configuration.texy @@ -1,71 +1,85 @@ -Настройка на контрол на достъпа -******************************* +Конфигурация на правата за достъп +********************************* .[perex] -Преглед на опциите за конфигуриране за Nette Security. +Преглед на конфигурационните опции за Nette Security. -Ако не използвате цялата рамка, а само тази библиотека, прочетете [как да изтеглите конфигурацията |bootstrap:]. +Ако не използвате целия framework, а само тази библиотека, прочетете [как да заредите конфигурацията|bootstrap:]. -Можете да дефинирате списък с потребители в конфигурацията, за да създадете [прост удостоверител |authentication] (`Nette\Security\SimpleAuthenticator`). Тъй като паролите в конфигурацията могат да бъдат прочетени, това решение е само за тестови цели. +В конфигурацията може да се дефинира списък с потребители и така да се създаде [прост автентикатор|authentication] (`Nette\Security\SimpleAuthenticator`). Тъй като в конфигурацията паролите се посочват в четим вид, това решение е подходящо само за тестови цели. ```neon security: - # покажете потребителския панел в панела на Tracey? + # показва ли се панелът на потребителя в Tracy Bar? debugger: ... # (bool) по подразбиране е true users: # име: парола - johndoe: secret123 + frantisek: tajneheslo - # име, парола, роля и други данни, налични в идентификатора - janedoe: - password: secret123 + # име, парола, роля и други данни, достъпни в идентичността + dobrota: + password: tajneheslo roles: [admin] data: ... ``` -Можете също така да дефинирате роли и ресурси, за да създадете основата за [удостоверителя |authorization] (`Nette\Security\Permission`): +Освен това могат да се дефинират роли и ресурси и така да се създаде основа за [авторизатор|authorization] (`Nette\Security\Permission`): ```neon security: roles: guest: - registered: [guest] # регистрираните наследяват от guest - admin: [registered] # и admin наследява от registered + registered: [guest] # registered наследява от guest + admin: [registered] # а от него наследява admin resources: article: - comment: [article] # ресурсът наследява от article + comment: [article] # ресурсът наследява от article poll: ``` -Потребителско хранилище .[#toc-user-storage] --------------------------------------------- +Хранилище +--------- -Можете да конфигурирате как се съхранява информацията за влезлия в системата потребител: +Може да се конфигурира как да се съхранява информацията за влезлия потребител: ```neon security: authentication: - # след колко време на неактивност потребителят ще бъде изведен от системата - expiration: 30 minutes # (string) няма стойност по подразбиране + # след колко време неактивност потребителят ще бъде излязъл + expiration: 30 minutes # (string) по подразбиране не е зададено - # къде да съхранявате информацията за влезлия потребител - storage: session # (session|cookie) стойността по подразбиране е session + # къде да се съхранява информацията за влезлия потребител + storage: session # (session|cookie) по подразбиране е session ``` -Ако сте избрали `cookie` като хранилище, можете да зададете и следните опции: +Ако изберете като хранилище `cookie`, можете да зададете още тези опции: ```neon security: authentication: - # бисквита име - cookieName: userId # (string) по подразбиране е userid + # име на бисквитката + cookieName: userId # (string) по подразбиране е userid - # на кои хостове е разрешено да получават бисквитката - cookieDomain: 'example.com' # (string|домейн) + # домейни, които приемат бисквитката + cookieDomain: 'example.com' # (string|domain) - # ограничения на кръстосания достъп до заявките - cookieSamesite: None # (Strict|Lax|None) по подразбиране е Lax + # ограничение при достъп от друг домейн + cookieSamesite: None # (Strict|Lax|None) по подразбиране е Lax ``` + + +DI услуги +--------- + +Тези услуги се добавят към DI контейнера: + +| Име | Тип | Описание +|---------------------------------------------------------- +| `security.authenticator` | [api:Nette\Security\Authenticator] | [автентикатор|authentication] +| `security.authorizator` | [api:Nette\Security\Authorizator] | [авторизатор|authorization] +| `security.passwords` | [api:Nette\Security\Passwords] | [хеширане на пароли|passwords] +| `security.user` | [api:Nette\Security\User] | текущ потребител +| `security.userStorage` | [api:Nette\Security\UserStorage] | [#хранилище] diff --git a/security/bg/passwords.texy b/security/bg/passwords.texy index 7b4e8f43a3..5760074202 100644 --- a/security/bg/passwords.texy +++ b/security/bg/passwords.texy @@ -2,11 +2,11 @@ ****************** .[perex] -За да гарантираме сигурността на нашите потребители, ние никога не съхраняваме паролите им в чист текст, а вместо това съхраняваме хеш на паролата. Hashing не е обратима операция, паролата не може да бъде възстановена. Въпреки това паролата може да бъде разбита и за да направим разбиването възможно най-трудно, трябва да използваме сигурен алгоритъм. Класът [api:Nette\Security\Passwords] ще ни помогне в това. +За да осигурим сигурността на нашите потребители, не съхраняваме техните пароли в четим вид, а си запазваме само отпечатък (т.нар. хеш). От отпечатъка не може обратно да се реконструира оригиналният вид на паролата. Важно е да се използва сигурен алгоритъм, с който да създадем отпечатъка. С това ни помага класът [api:Nette\Security\Passwords]. -→ [Монтаж и изисквания |@home#Installation] +→ [Инсталация и изисквания |@home#Инсталация] -Фреймуъркът автоматично добавя услугата `Nette\Security\Passwords` към контейнера DI под името `security.passwords`, което получавате, като го предавате чрез [инжектиране на зависимости |dependency-injection:passing-dependencies]: +Framework-ът автоматично добавя към DI контейнера услуга от тип `Nette\Security\Passwords` под името `security.passwords`, до която можете да стигнете, като си я поискате чрез [dependency injection |dependency-injection:passing-dependencies]. ```php use Nette\Security\Passwords; @@ -24,44 +24,44 @@ class Foo __construct($algo=PASSWORD_DEFAULT, array $options=[]): string .[method] ======================================================================== -Избира кой [сигурен алгоритъм |https://www.php.net/manual/en/password.constants.php] да се използва за хеширане и как да се конфигурира. +Избираме кой [сигурен алгоритъм|https://www.php.net/manual/en/password.constants.php] за генериране на хеш да използваме и конфигурираме неговите параметри. -По подразбиране е `PASSWORD_DEFAULT`, така че изборът на алгоритъм е предоставен на PHP. Алгоритъмът може да се промени в новите версии на PHP, когато се поддържат по-нови и по-силни алгоритми за хеширане. Така че трябва да сте наясно, че дължината на полученото хеш може да се промени. Така че трябва да съхранявате получения хеш по такъв начин, че да може да съхранява разумен брой символи, като препоръчителната ширина е 255. +По подразбиране се използва `PASSWORD_DEFAULT`, тоест изборът на алгоритъм се оставя на PHP. Алгоритъмът може да се промени в по-нови версии на PHP, ако се появят по-нови, по-силни хеширащи алгоритми. Затова трябва да сте наясно, че дължината на получения хеш може да се промени и трябва да го съхранявате по начин, който може да побере достатъчно знаци, 255 е препоръчителната ширина. -Ето как можете да използвате алгоритъма bcrypt и да промените скоростта на хеширане от стойността по подразбиране от 10 с параметъра cost. През 2020 г. при цена 10 хеширането на една парола отнема приблизително 80 ms, при цена 11 - 160 ms, а при цена 12 - 320 ms, като скалата е логаритмична. Колкото по-бавно, толкова по-добре, а цената 10-12 се счита за достатъчно бавна за повечето приложения: +Пример за настройка на скоростта на хеширане с алгоритъм bcrypt чрез промяна на параметъра cost: (през 2020 г. по подразбиране е 10, хеширането на парола отнема около 80ms, за cost 11 е около 160ms, за cost 12 около 320ms, колкото по-бавно, толкова по-добра защита, като скорост 10-12 вече се счита за достатъчна защита) ```php // ще хешираме паролите с 2^12 (2^cost) итерации на алгоритъма bcrypt $passwords = new Passwords(PASSWORD_BCRYPT, ['cost' => 12]); ``` -С инжектиране на зависимости: +С помощта на dependency injection: ```neon services: security.passwords: Nette\Security\Passwords(::PASSWORD_BCRYPT, [cost: 12]) ``` -hash(string $passwords): string .[method] -========================================= +hash(string $password): string .[method] +======================================== -Генерира хеш на парола. +Генерира хеш на паролата. ```php -$res = $passwords->hash($password); // Хашира паролата +$res = $passwords->hash($password); // Хешира паролата ``` -Резултатът `$res` е низ, който освен самия хеш съдържа идентификатора на използвания алгоритъм, неговите настройки и криптографска сол (случайни данни, които гарантират, че за една и съща парола ще бъде генериран различен хеш). По този начин се осигурява обратна съвместимост, например при промяна на параметрите могат да се проверят хешовете, записани с предишните настройки. Целият резултат се съхранява в базата данни, така че не е необходимо да съхранявате солта или настройките поотделно. +Резултатът `$res` е низ, който освен самия хеш съдържа и идентификатор на използвания алгоритъм, неговите настройки и криптографска сол (случайни данни, осигуряващи, че за една и съща парола се генерира различен хеш). Той е обратно съвместим, когато например промените параметрите, така че и хешовете, съхранени с използване на предишните настройки, ще могат да бъдат проверени. Целият този резултат се съхранява в базата данни, така че не е необходимо да се съхраняват солта или настройките отделно. verify(string $password, string $hash): bool .[method] ====================================================== -Открива дали дадена парола съвпада с даден хеш. Извличане на `$hash` от база данни по потребителско име или имейл адрес. +Установява дали дадена парола съответства на дадения отпечатък. `$hash` вземете от базата данни според зададеното потребителско име или имейл адрес. ```php if ($passwords->verify($password, $hash)) { - // Правилна парола + // правилна парола } ``` @@ -69,13 +69,13 @@ if ($passwords->verify($password, $hash)) { needsRehash(string $hash): bool .[method] ========================================= -Открива дали хешът отговаря на параметрите, зададени в конструктора. +Установява дали хешът съответства на зададените в конструктора опции. -Използвайте този метод, когато променяте параметрите на хеша, например. Проверката на паролата ще използва параметрите, записани заедно с хеша, и ако `needsRehash()` върне true, ще трябва отново да изчислите хеша, този път с актуализираните параметри, и да го съхраните отново в базата данни. Това гарантира, че хешовете на паролите се актуализират автоматично, когато потребителите влизат в системата. +Полезно е да се използва в момент, когато напр. променяте скоростта на хеширане. Проверката се извършва според съхранените настройки и ако `needsRehash()` върне `true`, тогава е необходимо отново да се създаде хеш, този път с новите параметри, и да се съхрани отново в базата данни. Така автоматично се "надграждат" съхранените хешове при влизане на потребителите. ```php if ($passwords->needsRehash($hash)) { $hash = $passwords->hash($password); - // съхраняване на $hash в базата данни + // съхрани $hash в базата данни } ``` diff --git a/security/cs/@home.texy b/security/cs/@home.texy index be60a532c8..266334da18 100644 --- a/security/cs/@home.texy +++ b/security/cs/@home.texy @@ -1,5 +1,5 @@ -Zabezpečení -*********** +Nette Security +************** .[perex] Balíček `nette/security` má na starostí [přihlašování uživatelů |authentication], [ověřování oprávnění |authorization] a [hashování hesel |passwords]. diff --git a/security/cs/@meta.texy b/security/cs/@meta.texy new file mode 100644 index 0000000000..462d9add80 --- /dev/null +++ b/security/cs/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokumentace}} diff --git a/security/cs/authentication.texy b/security/cs/authentication.texy index fe27a4563a..bec4b9f528 100644 --- a/security/cs/authentication.texy +++ b/security/cs/authentication.texy @@ -58,8 +58,7 @@ protected function startup() Expirace ======== -Přihlášení uživatele expiruje společně s [expirací úložiště|#uloziste-prihlaseneho-uzivatele], kterým je obvykle session (viz nastavení [expirace session|http:configuration#session]). -Lze ale nastavit i kratší časový interval, po jehož uplynutí dojde k odhlášení uživatele. K tomu slouží metoda `setExpiration()`, která se volá před `login()`. Jako parametr uveďte řetězec s relativním časem: +Přihlášení uživatele expiruje společně s [expirací úložiště |#Úložiště přihlášeného uživatele], kterým je obvykle session (viz nastavení [expirace session |http:configuration#Session]). Lze ale nastavit i kratší časový interval, po jehož uplynutí dojde k odhlášení uživatele. K tomu slouží metoda `setExpiration()`, která se volá před `login()`. Jako parametr uveďte řetězec s relativním časem: ```php // přihlášení vyprší po 30 minutách neaktivity @@ -87,7 +86,7 @@ security: Toto řešení je vhodné spíš pro testovací účely. Ukážeme si, jak vytvořit autentikátor, který bude ověřovat přihlašovací údaje oproti databázové tabulce. -Autentikátor je objekt implementující rozhraní [api:Nette\Security\Authenticator] s metodou `authenticate()`. Jejím úkolem je buď vrátit tzv. [identitu |#identita] nebo vyhodit výjimku `Nette\Security\AuthenticationException`. Bylo by možné u ní ještě uvést chybový kód k jemnějšímu rozlišení vzniklé situace: `Authenticator::IdentityNotFound` a `Authenticator::InvalidCredential`. +Autentikátor je objekt implementující rozhraní [api:Nette\Security\Authenticator] s metodou `authenticate()`. Jejím úkolem je buď vrátit tzv. [identitu |#Identita] nebo vyhodit výjimku `Nette\Security\AuthenticationException`. Bylo by možné u ní ještě uvést chybový kód k jemnějšímu rozlišení vzniklé situace: `Authenticator::IdentityNotFound` a `Authenticator::InvalidCredential`. ```php use Nette; @@ -124,7 +123,7 @@ class MyAuthenticator implements Nette\Security\Authenticator } ``` -Třída MyAuthenticator komunikuje s databází prostřednictvím [Nette Database Explorer|database:explorer] a pracuje s tabulkou `users`, kde je v sloupci `username` přihlašovací jméno uživatele a ve sloupci `password` [otisk hesla|passwords]. Po ověření jména a hesla vrací identitu, která nese ID uživatele, jeho roli (sloupec `role` v tabulce), o které si více řekneme [později |#role], a pole s dalšími daty (v našem případě uživatelské jméno). +Třída MyAuthenticator komunikuje s databází prostřednictvím [Nette Database Explorer|database:explorer] a pracuje s tabulkou `users`, kde je v sloupci `username` přihlašovací jméno uživatele a ve sloupci `password` [otisk hesla|passwords]. Po ověření jména a hesla vrací identitu, která nese ID uživatele, jeho [roli |authorization#Role] (sloupec `role` v tabulce) a pole s dalšími daty (v našem případě uživatelské jméno). Autentikátor ještě přidáme do konfigurace [jako službu|dependency-injection:services] DI kontejneru: @@ -137,7 +136,7 @@ services: Události $onLoggedIn, $onLoggedOut ---------------------------------- -Objekt `Nette\Security\User` má [události|nette:glossary#Události] `$onLoggedIn` a `$onLoggedOut`, můžete tedy přidat callbacky, které se vyvolají po úspěšném přihlášení resp. po odhlášení uživatele. +Objekt `Nette\Security\User` má [události |nette:glossary#události] `$onLoggedIn` a `$onLoggedOut`, můžete tedy přidat callbacky, které se vyvolají po úspěšném přihlášení resp. po odhlášení uživatele. ```php @@ -173,7 +172,7 @@ Identita je objekt implementující rozhraní [api:Nette\Security\IIdentity], v Úložiště přihlášeného uživatele =============================== -Dvě základní informace o uživateli, tedy zda-li je přihlášen a jeho [#identita], se zpravidla přenášejí v session. Což lze změnit. Ukládání těchto informací má na starosti objekt implementující rozhraní `Nette\Security\UserStorage`. K dispozici jsou dvě standardní implementace, první přenáší data v session a druhá v cookie. Jde o třídy `Nette\Bridges\SecurityHttp\SessionStorage` a `CookieStorage`. Zvolit si uložiště a nakonfigurovat jej můžete velmi pohodlně v konfiguraci [security › authentication|configuration#uloziste]. +Dvě základní informace o uživateli, tedy zda-li je přihlášen a jeho [#identita], se zpravidla přenášejí v session. Což lze změnit. Ukládání těchto informací má na starosti objekt implementující rozhraní `Nette\Security\UserStorage`. K dispozici jsou dvě standardní implementace, první přenáší data v session a druhá v cookie. Jde o třídy `Nette\Bridges\SecurityHttp\SessionStorage` a `CookieStorage`. Zvolit si uložiště a nakonfigurovat jej můžete velmi pohodlně v konfiguraci [security › authentication |configuration#Úložiště]. Dále můžete ovlivnit, jak přesně bude probíhat ukládání identity (*sleep*) a obnovování (*wakeup*). Stačí, aby authenticator implementoval rozhraní `Nette\Security\IdentityHandler`. To má dvě metody: `sleepIdentity()` se volá před zápisem identity do úložiště a `wakeupIdentity()` po jejím přečtení. Metody mohou obsah identity upravit, případně ji nahradit novým objektem, který vrátí. Metoda `wakeupIdentity()` může dokonce vrátit `null`, čímž uživatele jej odhlásí. diff --git a/security/cs/configuration.texy b/security/cs/configuration.texy index a06d6bf176..6405e97b07 100644 --- a/security/cs/configuration.texy +++ b/security/cs/configuration.texy @@ -1,5 +1,5 @@ -Konfigurace přístupovch oprávnění -********************************* +Konfigurace přístupových oprávnění +********************************** .[perex] Přehled konfiguračních voleb pro Nette Security. @@ -69,3 +69,17 @@ security: # omezení při přístupu z jiné domény cookieSamesite: None # (Strict|Lax|None) výchozí je Lax ``` + + +Služby DI +--------- + +Tyto služby se přidávají do DI kontejneru: + +| Název | Typ | Popis +|---------------------------------------------------------- +| `security.authenticator` | [api:Nette\Security\Authenticator] | [autentikátor|authentication] +| `security.authorizator` | [api:Nette\Security\Authorizator] | [autorizátor|authorization] +| `security.passwords` | [api:Nette\Security\Passwords] | [hashování hesel|passwords] +| `security.user` | [api:Nette\Security\User] | aktuální uživatel +| `security.userStorage` | [api:Nette\Security\UserStorage] | [#úložiště] diff --git a/security/cs/passwords.texy b/security/cs/passwords.texy index 88ec82aa47..0a1307941a 100644 --- a/security/cs/passwords.texy +++ b/security/cs/passwords.texy @@ -42,8 +42,8 @@ services: ``` -hash(string $passwords): string .[method] -========================================= +hash(string $password): string .[method] +======================================== Vygeneruje hash hesla. diff --git a/security/de/@home.texy b/security/de/@home.texy index 427302a64f..16e25e51c0 100644 --- a/security/de/@home.texy +++ b/security/de/@home.texy @@ -1,14 +1,14 @@ -Sicherheit -********** +Nette Security +************** .[perex] -Das Paket `nette/security` ist für die [Authentifizierung von Benutzern |authentication], die [Zugangskontrolle |authorization] und das [Hashing von Passwörtern |passwords] zuständig. +Das Paket `nette/security` ist für die [Benutzeranmeldung |authentication], die [Berechtigungsprüfung |authorization] und das [Hashing von Passwörtern |passwords] zuständig. -Installation .[#toc-installation] ---------------------------------- +Installation +------------ -Laden Sie das Paket herunter und installieren Sie es mit [Composer |best-practices:composer]: +Sie können die Bibliothek mit dem Werkzeug [Composer|best-practices:composer] herunterladen und installieren: ```shell composer require nette/security diff --git a/security/de/@left-menu.texy b/security/de/@left-menu.texy index a63e9bd05b..b961ddbb0a 100644 --- a/security/de/@left-menu.texy +++ b/security/de/@left-menu.texy @@ -1,7 +1,7 @@ -Nette Sicherheit -**************** -- [Überblick |@home] -- [Authentifizierung |Authentication] -- [Autorisierung |Authorization] +Nette Security +************** +- [Einführung |@home] +- [Authentifizierung |authentication] +- [Autorisierung |authorization] - [Passwort-Hashing |passwords] -- [Konfiguration |Configuration] +- [Konfiguration |configuration] diff --git a/security/de/@meta.texy b/security/de/@meta.texy new file mode 100644 index 0000000000..b3b806b2ca --- /dev/null +++ b/security/de/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokumentation}} diff --git a/security/de/authentication.texy b/security/de/authentication.texy index bbc950dd4d..7baefa951c 100644 --- a/security/de/authentication.texy +++ b/security/de/authentication.texy @@ -1,48 +1,48 @@ -Authentifizierung von Benutzern -******************************* +Benutzeranmeldung (Authentifizierung) +************************************* <div class=perex> -Wenig bis gar keine Webanwendungen benötigen keinen Mechanismus zur Benutzeranmeldung oder zur Überprüfung der Benutzerrechte. In diesem Kapitel werden wir darüber sprechen: +Kaum eine Webanwendung kommt ohne einen Mechanismus zur Benutzeranmeldung und Überprüfung von Benutzerberechtigungen aus. In diesem Kapitel werden wir über Folgendes sprechen: -- Benutzeranmeldung und -abmeldung -- benutzerdefinierte Authentifikatoren und Autorisierer +- An- und Abmelden von Benutzern +- Eigene Authentifikatoren </div> → [Installation und Anforderungen |@home#Installation] -In den Beispielen verwenden wir ein Objekt der Klasse [api:Nette\Security\User], das den aktuellen Benutzer repräsentiert und das Sie durch Übergabe mittels [Dependency Injection |dependency-injection:passing-dependencies] erhalten. In Präsentatoren rufen Sie einfach `$user = $this->getUser()` auf. +In den Beispielen verwenden wir das Objekt der Klasse [api:Nette\Security\User], das den aktuellen Benutzer repräsentiert und auf das Sie zugreifen können, indem Sie es sich mittels [Dependency Injection |dependency-injection:passing-dependencies] übergeben lassen. In Presentern genügt es, `$user = $this->getUser()` aufzurufen. -Authentifizierung .[#toc-authentication] -======================================== +Authentifizierung +================= -Authentifizierung bedeutet **Benutzeranmeldung**, d. h. der Vorgang, bei dem die Identität eines Benutzers überprüft wird. Der Benutzer identifiziert sich in der Regel mit einem Benutzernamen und einem Passwort. Die Verifizierung erfolgt durch den so genannten [Authenticator |#authenticator]. Wenn die Anmeldung fehlschlägt, wird `Nette\Security\AuthenticationException` ausgegeben. +Authentifizierung bedeutet **Benutzeranmeldung**, also der Prozess, bei dem überprüft wird, ob der Benutzer wirklich derjenige ist, für den er sich ausgibt. Üblicherweise weist er sich mit Benutzernamen und Passwort aus. Die Überprüfung führt der sogenannte [#Authenticator] durch. Schlägt die Anmeldung fehl, wird eine `Nette\Security\AuthenticationException` geworfen. ```php try { $user->login($username, $password); } catch (Nette\Security\AuthenticationException $e) { - $this->flashMessage('The username or password you entered is incorrect.'); + $this->flashMessage('Benutzername oder Passwort ist falsch.'); } ``` -So wird der Benutzer abgemeldet: +Auf diese Weise melden Sie den Benutzer ab: ```php $user->logout(); ``` -Und prüfen, ob der Benutzer angemeldet ist: +Und die Feststellung, ob er angemeldet ist: ```php -echo $user->isLoggedIn() ? 'yes' : 'no'; +echo $user->isLoggedIn() ? 'ja' : 'nein'; ``` -Ganz einfach, oder? Und alle Sicherheitsaspekte werden von Nette für Sie erledigt. +Sehr einfach, nicht wahr? Und alle Sicherheitsaspekte übernimmt Nette für Sie. -Im Presenter können Sie die Anmeldung mit der Methode `startup()` überprüfen und einen nicht angemeldeten Benutzer auf die Anmeldeseite umleiten. +In Presentern können Sie die Anmeldung in der Methode `startup()` überprüfen und einen nicht angemeldeten Benutzer zur Anmeldeseite weiterleiten. ```php protected function startup() @@ -55,39 +55,38 @@ protected function startup() ``` -Ablauf .[#toc-expiration] -========================= +Ablauf (Expiration) +=================== -Die Benutzeranmeldung läuft zusammen mit dem [Ablauf des Repositorys |#Storage for Logged User] ab, bei dem es sich in der Regel um eine Session handelt (siehe die Einstellung für den [Ablauf der Session |http:configuration#session] ). -Sie können jedoch auch ein kürzeres Zeitintervall festlegen, nach dem der Benutzer abgemeldet wird. Zu diesem Zweck wird die Methode `setExpiration()` verwendet, die vor `login()` aufgerufen wird. Geben Sie einen String mit einer relativen Zeit als Parameter an: +Die Benutzeranmeldung läuft zusammen mit dem [Ablauf des Speichers |#Speicher für angemeldete Benutzer] ab, der normalerweise die Session ist (siehe Einstellung [Session-Ablauf |http:configuration#Session]). Es kann jedoch auch ein kürzeres Zeitintervall festgelegt werden, nach dessen Ablauf der Benutzer abgemeldet wird. Dazu dient die Methode `setExpiration()`, die vor `login()` aufgerufen wird. Geben Sie als Parameter eine Zeichenkette mit einer relativen Zeit an: ```php -// Die Anmeldung läuft nach 30 Minuten Inaktivität ab +// Anmeldung läuft nach 30 Minuten Inaktivität ab $user->setExpiration('30 minutes'); -// Abbruch des eingestellten Ablaufs +// Aufhebung der eingestellten Expiration $user->setExpiration(null); ``` -Die Methode `$user->getLogoutReason()` gibt an, ob der Benutzer abgemeldet wurde, weil das Zeitintervall abgelaufen ist. Sie gibt entweder die Konstante `Nette\Security\UserStorage::LogoutInactivity` zurück, wenn die Zeit abgelaufen ist, oder `UserStorage::LogoutManual`, wenn die Methode `logout()` aufgerufen wurde. +Ob der Benutzer aufgrund des Ablaufs des Zeitintervalls abgemeldet wurde, verrät die Methode `$user->getLogoutReason()`, die entweder die Konstante `Nette\Security\UserStorage::LogoutInactivity` (Zeitlimit abgelaufen) oder `UserStorage::LogoutManual` (mit der Methode `logout()` abgemeldet) zurückgibt. -Authentifikator .[#toc-authenticator] -===================================== +Authenticator +============= -Es handelt sich um ein Objekt, das die Anmeldedaten, d.h. in der Regel den Namen und das Passwort, überprüft. Die triviale Implementierung ist die Klasse [api:Nette\Security\SimpleAuthenticator], die in der [Konfiguration |configuration] definiert werden kann: +Dies ist ein Objekt, das die Anmeldedaten überprüft, also normalerweise Name und Passwort. Eine triviale Form ist die Klasse [api:Nette\Security\SimpleAuthenticator], die wir in der [Konfiguration|nette:configuring] definieren können: ```neon security: users: - # name: password - johndoe: secret123 - kathy: evenmoresecretpassword + # Benutzername: Passwort + frantisek: tajneheslo + katka: jestetajnejsiheslo ``` -Diese Lösung ist eher für Testzwecke geeignet. Wir zeigen Ihnen, wie Sie einen Authentifikator erstellen, der die Anmeldedaten anhand einer Datenbanktabelle überprüft. +Diese Lösung eignet sich eher für Testzwecke. Wir zeigen Ihnen, wie Sie einen Authenticator erstellen, der Anmeldedaten anhand einer Datenbanktabelle überprüft. -Ein Authenticator ist ein Objekt, das die Schnittstelle [api:Nette\Security\Authenticator] mit der Methode `authenticate()` implementiert. Seine Aufgabe ist es, entweder die so genannte [Identität |#identity] zurückzugeben oder eine Exception `Nette\Security\AuthenticationException` auszulösen. Es wäre auch möglich, einen feinkörnigen Fehlercode `Authenticator::IdentityNotFound` oder `Authenticator::InvalidCredential` bereitzustellen. +Ein Authenticator ist ein Objekt, das das Interface [api:Nette\Security\Authenticator] mit der Methode `authenticate()` implementiert. Ihre Aufgabe ist es, entweder eine sogenannte [#Identität] zurückzugeben oder eine Ausnahme `Nette\Security\AuthenticationException` zu werfen. Es wäre auch möglich, einen Fehlercode anzugeben, um die entstandene Situation genauer zu unterscheiden: `Authenticator::IdentityNotFound` oder `Authenticator::InvalidCredential`. ```php use Nette; @@ -117,16 +116,16 @@ class MyAuthenticator implements Nette\Security\Authenticator return new SimpleIdentity( $row->id, - $row->role, // oder Array von Rollen + $row->role, // oder ein Array mehrerer Rollen ['name' => $row->username], ); } } ``` -Die Klasse MyAuthenticator kommuniziert mit der Datenbank über den [Nette Database Explorer |database:explorer] und arbeitet mit der Tabelle `users`, wobei die Spalte `username` den Anmeldenamen des Benutzers und die Spalte `password` den [Hash |passwords] enthält. Nach der Überprüfung des Namens und des Passworts gibt sie die Identität mit der ID des Benutzers, der Rolle (Spalte `role` in der Tabelle), die wir [später |#roles] erwähnen werden, und einem Array mit zusätzlichen Daten (in unserem Fall der Benutzername) zurück. +Die Klasse `MyAuthenticator` kommuniziert mit der Datenbank über [Nette Database Explorer|database:explorer] und arbeitet mit der Tabelle `users`, wobei in der Spalte `username` der Anmeldename des Benutzers und in der Spalte `password` der [Passwort-Hash|passwords] gespeichert ist. Nach Überprüfung von Name und Passwort gibt sie die Identität zurück, die die ID des Benutzers, seine Rolle (Spalte `role` in der Tabelle), über die wir [später |authorization#Rollen] mehr sprechen werden, und ein Array mit weiteren Daten (in unserem Fall der Benutzername) enthält. -Wir werden den Authenticator [als Dienst |dependency-injection:services] des DI-Containers zur Konfiguration hinzufügen: +Den Authenticator fügen wir noch zur Konfiguration [als Dienst|dependency-injection:services] des DI-Containers hinzu: ```neon services: @@ -134,23 +133,23 @@ services: ``` -$onLoggedIn, $onLoggedOut Ereignisse .[#toc-onloggedin-onloggedout-events] --------------------------------------------------------------------------- +Ereignisse $onLoggedIn, $onLoggedOut +------------------------------------ -Das Objekt `Nette\Security\User` hat die [Ereignisse |nette:glossary#Events] `$onLoggedIn` und `$onLoggedOut`, so dass Sie Rückrufe hinzufügen können, die nach einer erfolgreichen Anmeldung oder nach der Abmeldung des Benutzers ausgelöst werden. +Das Objekt `Nette\Security\User` hat die [Ereignisse |nette:glossary#Events Ereignisse] `$onLoggedIn` und `$onLoggedOut`. Sie können also Callbacks hinzufügen, die nach erfolgreicher Anmeldung bzw. nach Abmeldung des Benutzers aufgerufen werden. ```php $user->onLoggedIn[] = function () { - // Benutzer hat sich gerade angemeldet + // Benutzer wurde gerade angemeldet }; ``` -Identität .[#toc-identity] -========================== +Identität +========= -Eine Identität ist ein Satz von Informationen über einen Benutzer, der vom Authentifikator zurückgegeben wird und der dann in einer Session gespeichert und mit `$user->getIdentity()` abgerufen wird. Wir können also die ID, die Rollen und andere Benutzerdaten so abrufen, wie wir sie im Authentifikator übergeben haben: +Die Identität repräsentiert eine Sammlung von Informationen über den Benutzer, die vom Authenticator zurückgegeben wird und anschließend in der Session gespeichert und mit `$user->getIdentity()` abgerufen wird. Wir können also die ID, Rollen und weitere Benutzerdaten abrufen, so wie wir sie im Authenticator übergeben haben: ```php $user->getIdentity()->getId(); @@ -158,26 +157,26 @@ $user->getIdentity()->getId(); $user->getIdentity()->getRoles(); -// Benutzerdaten können als Eigenschaften abgerufen werden -// der Name, den wir in MyAuthenticator übergeben haben +// Benutzerdaten sind als Eigenschaften verfügbar +// Name, den wir in MyAuthenticator übergeben haben $user->getIdentity()->name; ``` -Wichtig ist, dass beim Abmelden des Benutzers mit `$user->logout()` die **Identität nicht gelöscht** wird und weiterhin verfügbar ist. Wenn also die Identität vorhanden ist, garantiert sie allein nicht, dass der Benutzer auch angemeldet ist. Wenn wir die Identität ausdrücklich löschen wollen, melden wir den Benutzer mit `logout(true)` ab. +Wichtig ist, dass bei der Abmeldung mit `$user->logout()` **die Identität nicht gelöscht wird** und weiterhin verfügbar ist. Auch wenn der Benutzer eine Identität hat, muss er also nicht angemeldet sein. Wenn wir die Identität explizit löschen möchten, melden wir den Benutzer mit dem Aufruf `logout(true)` ab. -Dadurch kann man zwar immer noch davon ausgehen, welcher Benutzer am Computer sitzt und z.B. personalisierte Angebote im E-Shop anzeigen, aber man kann seine persönlichen Daten erst nach dem Einloggen anzeigen. +Dadurch können Sie weiterhin davon ausgehen, welcher Benutzer am Computer ist, und ihm beispielsweise im E-Shop personalisierte Angebote anzeigen. Seine persönlichen Daten können Sie ihm jedoch erst nach der Anmeldung anzeigen. -Identity ist ein Objekt, das die Schnittstelle [api:Nette\Security\IIdentity] implementiert, die Standardimplementierung ist [api:Nette\Security\SimpleIdentity]. Wie bereits erwähnt, wird die Identität in der Session gespeichert. Wenn wir also zum Beispiel die Rolle eines angemeldeten Benutzers ändern, bleiben die alten Daten in der Identität erhalten, bis er sich erneut anmeldet. +Die Identität ist ein Objekt, das das Interface [api:Nette\Security\IIdentity] implementiert, die Standardimplementierung ist [api:Nette\Security\SimpleIdentity]. Und wie erwähnt, wird sie in der Session gehalten. Wenn wir also beispielsweise die Rolle eines angemeldeten Benutzers ändern, bleiben die alten Daten in seiner Identität bis zu seiner erneuten Anmeldung erhalten. -Speicherung für angemeldete Benutzer .[#toc-storage-for-logged-user] -==================================================================== +Speicher für angemeldete Benutzer +================================= -Die beiden grundlegenden Informationen über den Benutzer, d.h. ob er angemeldet ist und seine [Identität |#identity], werden normalerweise in der Session gespeichert. Diese kann geändert werden. Für die Speicherung dieser Informationen ist ein Objekt zuständig, das die Schnittstelle `Nette\Security\UserStorage` implementiert. Es gibt zwei Standardimplementierungen, von denen die erste die Daten in einer Session und die zweite in einem Cookie überträgt. Dies sind die Klassen `Nette\Bridges\SecurityHttp\SessionStorage` und `CookieStorage`. Sie können die Speicherung auswählen und sie sehr bequem in der Konfiguration [Sicherheit › Authentifizierung |configuration] konfigurieren. +Die beiden grundlegenden Informationen über den Benutzer, nämlich ob er angemeldet ist und seine [#Identität], werden in der Regel in der Session übertragen. Dies kann jedoch geändert werden. Für die Speicherung dieser Informationen ist ein Objekt verantwortlich, das das Interface `Nette\Security\UserStorage` implementiert. Es stehen zwei Standardimplementierungen zur Verfügung: die erste überträgt Daten in der Session und die zweite in einem Cookie. Dies sind die Klassen `Nette\Bridges\SecurityHttp\SessionStorage` und `CookieStorage`. Sie können den Speicher auswählen und ihn sehr bequem in der Konfiguration [security › authentication |configuration#Speicher] konfigurieren. -Sie können auch genau steuern, wie das Speichern (*sleep*) und Wiederherstellen (*wakeup*) der Identität erfolgen soll. Alles, was Sie brauchen, ist, dass der Authentifikator die Schnittstelle `Nette\Security\IdentityHandler` implementiert. Diese hat zwei Methoden: `sleepIdentity()` wird aufgerufen, bevor die Identität in den Speicher geschrieben wird, und `wakeupIdentity()` wird aufgerufen, nachdem die Identität gelesen wurde. Die Methoden können den Inhalt der Identität ändern oder sie durch ein neues Objekt ersetzen, das zurückgegeben wird. Die Methode `wakeupIdentity()` kann sogar `null` zurückgeben, wodurch der Benutzer abgemeldet wird. +Weiterhin können Sie beeinflussen, wie genau das Speichern der Identität (*sleep*) und das Wiederherstellen (*wakeup*) ablaufen soll. Es genügt, wenn der Authenticator das Interface `Nette\Security\IdentityHandler` implementiert. Dieses hat zwei Methoden: `sleepIdentity()` wird vor dem Schreiben der Identität in den Speicher aufgerufen und `wakeupIdentity()` nach dem Lesen daraus. Die Methoden können den Inhalt der Identität ändern oder sie durch ein neues Objekt ersetzen, das sie zurückgeben. Die Methode `wakeupIdentity()` kann sogar `null` zurückgeben, wodurch der Benutzer abgemeldet wird. -Als Beispiel zeigen wir eine Lösung für eine häufige Frage, wie man Identitätsrollen direkt nach der Wiederherstellung aus einer Session aktualisiert. In der Methode `wakeupIdentity()` übergeben wir die aktuellen Rollen an die Identität, z. B. aus der Datenbank: +Als Beispiel zeigen wir die Lösung einer häufigen Frage, wie die Rollen in der Identität sofort nach dem Laden aus der Session aktualisiert werden können. In der Methode `wakeupIdentity()` übergeben wir der Identität die aktuellen Rollen, z. B. aus der Datenbank: ```php final class Authenticator implements @@ -185,25 +184,25 @@ final class Authenticator implements { public function sleepIdentity(IIdentity $identity): IIdentity { - // hier können Sie die Identität vor dem Speichern nach dem Einloggen ändern, + // hier kann die Identität vor dem Schreiben in den Speicher nach der Anmeldung geändert werden, // aber das brauchen wir jetzt nicht return $identity; } public function wakeupIdentity(IIdentity $identity): ?IIdentity { - // Aktualisieren der Rollen in der Identität + // Aktualisierung der Rollen in der Identität $userId = $identity->getId(); $identity->setRoles($this->facade->getUserRoles($userId)); return $identity; } ``` -Und nun kehren wir zur Cookie-basierten Speicherung zurück. Sie ermöglicht es, eine Website zu erstellen, auf der sich die Benutzer anmelden können, ohne dass sie Sessionen verwenden müssen. Es muss also nicht auf die Festplatte geschrieben werden. So funktioniert ja auch die Website, die Sie gerade lesen, einschließlich des Forums. In diesem Fall ist die Implementierung von `IdentityHandler` eine Notwendigkeit. Wir werden nur ein zufälliges Token, das den angemeldeten Benutzer repräsentiert, im Cookie speichern. +Und nun kehren wir zum Cookie-basierten Speicher zurück. Er ermöglicht es Ihnen, eine Website zu erstellen, auf der sich Benutzer anmelden können, ohne Sessions zu benötigen. Das heißt, es muss nicht auf die Festplatte geschrieben werden. Übrigens funktioniert auch die Website, die Sie gerade lesen, einschließlich des Forums, auf diese Weise. In diesem Fall ist die Implementierung von `IdentityHandler` eine Notwendigkeit. Im Cookie speichern wir nämlich nur ein zufälliges Token, das den angemeldeten Benutzer repräsentiert. -Also stellen wir zunächst in der Konfiguration mit `security › authentication › storage: cookie` den gewünschten Speicherplatz ein. +Zuerst stellen wir also in der Konfiguration den gewünschten Speicher mit `security › authentication › storage: cookie` ein. -Wir fügen eine Spalte `authtoken` in der Datenbank hinzu, in der jeder Benutzer eine [völlig zufällige, eindeutige und nicht ermittelbare |utils:random] Zeichenfolge von ausreichender Länge (mindestens 13 Zeichen) hat. Das Repository `CookieStorage` speichert nur den Wert `$identity->getId()` im Cookie, so dass wir in `sleepIdentity()` die ursprüngliche Identität durch einen Proxy mit `authtoken` in der ID ersetzen, im Gegensatz dazu stellen wir in der Methode `wakeupIdentity()` die gesamte Identität aus der Datenbank gemäß authtoken wieder her: +In der Datenbank erstellen wir eine Spalte `authtoken`, in der jeder Benutzer eine [völlig zufällige, einzigartige und nicht erratbare|utils:random] Zeichenkette ausreichender Länge (mindestens 13 Zeichen) hat. Der `CookieStorage`-Speicher überträgt im Cookie nur den Wert `$identity->getId()`, sodass wir in `sleepIdentity()` die ursprüngliche Identität durch eine Platzhalter-Identität mit `authtoken` in der ID ersetzen. Umgekehrt lesen wir in der Methode `wakeupIdentity()` anhand des Authtokens die gesamte Identität aus der Datenbank: ```php final class Authenticator implements @@ -212,21 +211,21 @@ final class Authenticator implements public function authenticate(string $username, string $password): SimpleIdentity { $row = $this->db->fetch('SELECT * FROM user WHERE username = ?', $username); - // Passwort prüfen + // Passwort überprüfen ... - // wir geben die Identität mit allen Daten aus der Datenbank zurück + // Identität mit allen Daten aus der Datenbank zurückgeben return new SimpleIdentity($row->id, null, (array) $row); } public function sleepIdentity(IIdentity $identity): SimpleIdentity { - // wir geben eine Proxy-Identität zurück, bei der die ID authtoken ist + // Platzhalter-Identität zurückgeben, wobei die ID das Authtoken ist return new SimpleIdentity($identity->authtoken); } public function wakeupIdentity(IIdentity $identity): ?SimpleIdentity { - // Ersetzen Sie die Proxy-Identität durch eine vollständige Identität, wie in authenticate() + // Platzhalter-Identität durch die vollständige Identität ersetzen, wie in authenticate() $row = $this->db->fetch('SELECT * FROM user WHERE authtoken = ?', $identity->getId()); return $row ? new SimpleIdentity($row->id, null, (array) $row) @@ -236,16 +235,16 @@ final class Authenticator implements ``` -Mehrere unabhängige Authentifizierungen .[#toc-multiple-independent-authentications] -==================================================================================== +Mehrere unabhängige Anmeldungen +=============================== -Es ist möglich, mehrere unabhängige angemeldete Benutzer innerhalb einer Site und jeweils eine Session zu haben. Wenn wir z. B. eine getrennte Authentifizierung für Frontend und Backend haben wollen, legen wir einfach einen eindeutigen Sessionsnamensraum für jeden von ihnen fest: +Es ist möglich, innerhalb einer Website und einer Session mehrere unabhängige angemeldete Benutzer gleichzeitig zu haben. Wenn wir beispielsweise auf einer Website eine getrennte Authentifizierung für die Administration und den öffentlichen Bereich haben möchten, reicht es aus, jeder einen eigenen Namespace zuzuweisen: ```php $user->getStorage()->setNamespace('backend'); ``` -Es ist zu beachten, dass dieser an allen Stellen, die zum selben Segment gehören, gesetzt werden muss. Wenn wir Presenter verwenden, setzen wir den Namespace im gemeinsamen Vorfahren - normalerweise dem BasePresenter. Zu diesem Zweck erweitern wir die Methode [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()]: +Es ist wichtig zu bedenken, dass wir den Namespace immer an allen Stellen festlegen, die zu dem jeweiligen Teil gehören. Wenn wir Presenter verwenden, legen wir den Namespace im gemeinsamen Vorfahren für den jeweiligen Teil fest – normalerweise im `BasePresenter`. Wir tun dies, indem wir die Methode [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()] erweitern: ```php public function checkRequirements($element): void @@ -256,10 +255,10 @@ public function checkRequirements($element): void ``` -Mehrere Authentifikatoren .[#toc-multiple-authenticators] ---------------------------------------------------------- +Mehrere Authentifikatoren +------------------------- -Die Aufteilung einer Anwendung in Segmente mit unabhängiger Authentifizierung erfordert in der Regel unterschiedliche Authenticators. Die Registrierung von zwei Klassen, die Authenticator implementieren, in Konfigurationsdiensten würde jedoch einen Fehler auslösen, da Nette nicht wüsste, welche von ihnen mit dem `Nette\Security\User` Objekt [autowired |dependency-injection:autowiring] werden sollte. Deshalb müssen wir das Autowiring für diese Klassen mit `autowired: self` einschränken, so dass es nur aktiviert wird, wenn die Klasse speziell angefordert wird: +Die Aufteilung der Anwendung in Teile mit unabhängiger Anmeldung erfordert meistens auch unterschiedliche Authentifikatoren. Sobald wir jedoch in der Dienstkonfiguration zwei Klassen registrieren, die `Authenticator` implementieren, wüsste Nette nicht, welche davon dem Objekt `Nette\Security\User` automatisch zugewiesen werden soll, und würde einen Fehler anzeigen. Daher müssen wir für Authentifikatoren das [Autowiring |dependency-injection:autowiring] einschränken, sodass es nur funktioniert, wenn jemand eine bestimmte Klasse anfordert, z. B. `FrontAuthenticator`, was wir durch die Wahl `autowired: self` erreichen: ```neon services: @@ -278,7 +277,7 @@ class SignPresenter extends Nette\Application\UI\Presenter } ``` -Wir müssen unseren Authenticator nur auf das User-Objekt setzen, bevor wir die Methode [login() |api:Nette\Security\User::login()] aufrufen, was typischerweise im Callback des Login-Formulars bedeutet: +Den Authenticator des User-Objekts legen wir vor dem Aufruf der Methode [login() |api:Nette\Security\User::login()] fest, also normalerweise im Code des Formulars, das ihn anmeldet: ```php $form->onSuccess[] = function (Form $form, \stdClass $data) { diff --git a/security/de/authorization.texy b/security/de/authorization.texy index a8f3d2384c..fae588ba18 100644 --- a/security/de/authorization.texy +++ b/security/de/authorization.texy @@ -1,48 +1,48 @@ -Zugriffskontrolle (Autorisierung) -********************************* +Berechtigungsprüfung (Autorisierung) +************************************ .[perex] -Durch die Autorisierung wird festgestellt, ob ein Benutzer über ausreichende Berechtigungen verfügt, um beispielsweise auf eine bestimmte Ressource zuzugreifen oder eine Aktion durchzuführen. Die Autorisierung setzt eine vorherige erfolgreiche Authentifizierung voraus, d.h. dass der Benutzer angemeldet ist. +Autorisierung stellt fest, ob ein Benutzer ausreichende Berechtigungen hat, beispielsweise um auf eine bestimmte Ressource zuzugreifen oder eine bestimmte Aktion auszuführen. Autorisierung setzt eine vorherige erfolgreiche Authentifizierung voraus, d.h., dass der Benutzer angemeldet ist. → [Installation und Anforderungen |@home#Installation] -In den Beispielen verwenden wir ein Objekt der Klasse [api:Nette\Security\User], das den aktuellen Benutzer repräsentiert und das Sie durch Übergabe mittels [Dependency Injection |dependency-injection:passing-dependencies] erhalten. In Presentern rufen Sie einfach `$user = $this->getUser()` auf. +In den Beispielen verwenden wir das Objekt der Klasse [api:Nette\Security\User], das den aktuellen Benutzer repräsentiert und auf das Sie zugreifen können, indem Sie es sich mittels [Dependency Injection |dependency-injection:passing-dependencies] übergeben lassen. In Presentern genügt es, `$user = $this->getUser()` aufzurufen. -Für sehr einfache Websites mit Administration, bei denen nicht nach Benutzerrechten unterschieden wird, kann man die bereits bekannte Methode `isLoggedIn()` als Berechtigungskriterium verwenden. Mit anderen Worten: Sobald ein Benutzer eingeloggt ist, hat er Rechte für alle Aktionen und umgekehrt. +Bei sehr einfachen Websites mit Administration, bei denen keine Benutzerberechtigungen unterschieden werden, kann als Autorisierungskriterium die bereits bekannte Methode `isLoggedIn()` verwendet werden. Mit anderen Worten: Sobald ein Benutzer angemeldet ist, hat er alle Berechtigungen und umgekehrt. ```php -if ($user->isLoggedIn()) { // ist der Benutzer eingeloggt? - deleteItem(); // falls ja, kann er ein Element löschen +if ($user->isLoggedIn()) { // ist der Benutzer angemeldet? + deleteItem(); // dann hat er die Berechtigung für die Operation } ``` -Rollen .[#toc-roles] --------------------- +Rollen +------ -Der Zweck von Rollen ist es, eine genauere Rechteverwaltung zu bieten und unabhängig vom Benutzernamen zu bleiben. Sobald sich ein Benutzer anmeldet, werden ihm eine oder mehrere Rollen zugewiesen. Die Rollen selbst können einfache Zeichenketten sein, z. B. `admin`, `member`, `guest`, usw. Sie werden im zweiten Argument des `SimpleIdentity` -Konstruktors angegeben, entweder als String oder als Array. +Der Sinn von Rollen besteht darin, eine präzisere Steuerung der Berechtigungen zu ermöglichen und unabhängig vom Benutzernamen zu bleiben. Jedem Benutzer weisen wir gleich bei der Anmeldung eine oder mehrere Rollen zu, in denen er auftreten wird. Rollen können einfache Zeichenketten sein, wie z. B. `admin`, `member`, `guest` usw. Sie werden als zweiter Parameter des Konstruktors `SimpleIdentity` angegeben, entweder als Zeichenkette oder als Array von Zeichenketten – Rollen. -Als Berechtigungskriterium wird nun die Methode `isInRole()` verwendet, die prüft, ob der Benutzer in der angegebenen Rolle ist: +Als Autorisierungskriterium verwenden wir nun die Methode `isInRole()`, die angibt, ob der Benutzer in der gegebenen Rolle auftritt: ```php -if ($user->isInRole('admin')) { // ist dem Benutzer die Admin-Rolle zugewiesen? - deleteItem(); // wenn ja, kann er ein Element löschen +if ($user->isInRole('admin')) { // ist der Benutzer in der Rolle admin? + deleteItem(); // dann hat er die Berechtigung für die Operation } ``` -Wie Sie bereits wissen, löscht das Abmelden des Benutzers nicht seine Identität. Die Methode `getIdentity()` liefert also nach wie vor das Objekt `SimpleIdentity`, inklusive aller vergebenen Rollen. Das Nette Framework folgt dem Prinzip "weniger Code, mehr Sicherheit", so dass Sie bei der Überprüfung von Rollen nicht prüfen müssen, ob der Benutzer auch angemeldet ist. Die Methode `isInRole()` arbeitet mit **wirksamen Rollen**, d.h. wenn der Benutzer eingeloggt ist, werden die der Identität zugewiesenen Rollen verwendet, wenn er nicht eingeloggt ist, wird stattdessen eine automatische Sonderrolle `guest` verwendet. +Wie Sie bereits wissen, muss nach der Abmeldung des Benutzers seine Identität nicht gelöscht werden. Das heißt, die Methode `getIdentity()` gibt weiterhin das Objekt `SimpleIdentity` zurück, einschließlich aller gewährten Rollen. Nette Framework folgt dem Prinzip „weniger Code, mehr Sicherheit“, bei dem weniger Schreiben zu sicherem Code führt. Daher müssen Sie bei der Überprüfung von Rollen nicht zusätzlich prüfen, ob der Benutzer angemeldet ist. Die Methode `isInRole()` arbeitet mit **effektiven Rollen**, d. h., wenn der Benutzer angemeldet ist, basiert sie auf den in der Identität angegebenen Rollen; wenn er nicht angemeldet ist, hat er automatisch die spezielle Rolle `guest`. -Bevollmächtigter .[#toc-authorizator] -------------------------------------- +Autorisator +----------- -Neben den Rollen werden wir auch die Begriffe Ressource und Operation einführen: +Neben Rollen führen wir noch die Begriffe Ressource und Operation ein: -- **Rolle** ist ein Benutzerattribut - zum Beispiel Moderator, Redakteur, Besucher, registrierter Benutzer, Administrator, ... -- **Ressource** ist eine logische Einheit der Anwendung - Artikel, Seite, Benutzer, Menüpunkt, Umfrage, Präsentator, ... -- **Operation** ist eine bestimmte Aktivität, die der Benutzer mit der *Ressource* durchführen kann oder nicht - ansehen, bearbeiten, löschen, abstimmen, ... +- **Rolle** ist eine Eigenschaft des Benutzers - z. B. Moderator, Redakteur, Besucher, registrierter Benutzer, Administrator... +- **Ressource** (*resource*) ist ein logisches Element der Website - Artikel, Seite, Benutzer, Menüpunkt, Umfrage, Presenter, ... +- **Operation** (*operation*) ist eine bestimmte Tätigkeit, die der Benutzer mit der Ressource tun oder nicht tun darf - z. B. löschen, bearbeiten, erstellen, abstimmen, ... -Ein Autorisierer ist ein Objekt, das entscheidet, ob eine bestimmte *Rolle* die Erlaubnis hat, eine bestimmte *Operation* mit einer bestimmten *Ressource* durchzuführen. Es ist ein Objekt, das die Schnittstelle [api:Nette\Security\Authorizator] mit nur einer Methode `isAllowed()` implementiert: +Ein Autorisator ist ein Objekt, das entscheidet, ob eine gegebene *Rolle* die Berechtigung hat, eine bestimmte *Operation* mit einer bestimmten *Ressource* durchzuführen. Es handelt sich um ein Objekt, das das Interface [api:Nette\Security\Authorizator] mit einer einzigen Methode `isAllowed()` implementiert: ```php class MyAuthorizator implements Nette\Security\Authorizator @@ -63,50 +63,50 @@ class MyAuthorizator implements Nette\Security\Authorizator } ``` -Wir fügen den Autorisierer [als Dienst |dependency-injection:services] des DI-Containers in die Konfiguration ein: +Den Autorisator fügen wir zur Konfiguration [als Dienst|dependency-injection:services] des DI-Containers hinzu: ```neon services: - MyAuthorizator ``` -Es folgt ein Beispiel für die Verwendung. Beachten Sie, dass wir dieses Mal die Methode `Nette\Security\User::isAllowed()` aufrufen, nicht die des Autorisators, daher gibt es keinen ersten Parameter `$role`. Diese Methode ruft `MyAuthorizator::isAllowed()` nacheinander für alle Benutzerrollen auf und gibt true zurück, wenn mindestens eine von ihnen eine Berechtigung hat. +Und hier ist ein Anwendungsbeispiel. Achtung, diesmal rufen wir die Methode `Nette\Security\User::isAllowed()` auf, nicht den Autorisator direkt, daher fehlt der erste Parameter `$role`. Diese Methode ruft `MyAuthorizator::isAllowed()` nacheinander für alle Rollen des Benutzers auf und gibt `true` zurück, wenn mindestens eine davon die Berechtigung hat. ```php -if ($user->isAllowed('file')) { // Darf der Benutzer alles mit der Ressource 'file' machen? +if ($user->isAllowed('file')) { // darf der Benutzer irgendetwas mit der Ressource 'file' tun? useFile(); } -if ($user->isAllowed('file', 'delete')) { // Darf der Benutzer die Ressource 'file' löschen? +if ($user->isAllowed('file', 'delete')) { // darf er über die Ressource 'file' die Operation 'delete' ausführen? deleteFile(); } ``` -Beide Argumente sind optional und ihr Standardwert bedeutet *Alles*. +Beide Parameter sind optional, der Standardwert `null` bedeutet *alles*. -Erlaubnis ACL .[#toc-permission-acl] ------------------------------------- +Permission ACL +-------------- -Nette verfügt über eine integrierte Implementierung des Berechtigungsgebers, die Klasse [api:Nette\Security\Permission], die eine leichtgewichtige und flexible ACL-Schicht (Access Control List) für die Berechtigungs- und Zugriffskontrolle bietet. Wenn wir mit dieser Klasse arbeiten, definieren wir Rollen, Ressourcen und individuelle Berechtigungen. Und Rollen und Ressourcen können Hierarchien bilden. Zur Erläuterung zeigen wir ein Beispiel für eine Webanwendung: +Nette kommt mit einer eingebauten Implementierung eines Autorisators, der Klasse [api:Nette\Security\Permission], die dem Programmierer eine leichte und flexible ACL (Access Control List) Schicht zur Verwaltung von Berechtigungen und Zugriffen bietet. Die Arbeit damit besteht darin, Rollen, Ressourcen und einzelne Berechtigungen zu definieren. Dabei ermöglichen Rollen und Ressourcen die Erstellung von Hierarchien. Zur Erklärung zeigen wir ein Beispiel einer Webanwendung: -- `guest`: nicht eingeloggter Besucher, der den öffentlichen Teil des Webs lesen und durchsuchen darf, d.h. Artikel lesen, kommentieren und an Umfragen teilnehmen -- `registered`: eingeloggter Benutzer, der darüber hinaus Kommentare abgeben kann +- `guest`: nicht angemeldeter Besucher, der den öffentlichen Teil der Website lesen und durchsuchen kann, d.h. Artikel, Kommentare lesen und in Umfragen abstimmen kann +- `registered`: angemeldeter registrierter Benutzer, der zusätzlich kommentieren kann - `admin`: kann Artikel, Kommentare und Umfragen verwalten -Wir haben also bestimmte Rollen definiert (`guest`, `registered` und `admin`) und Ressourcen erwähnt (`article`, `comments`, `poll`), auf die die Benutzer zugreifen oder Aktionen durchführen können (`view`, `vote`, `add`, `edit`). +Wir haben also bestimmte Rollen (`guest`, `registered` und `admin`) definiert und Ressourcen (`article`, `comment`, `poll`) erwähnt, auf die Benutzer mit einer bestimmten Rolle zugreifen oder bestimmte Operationen (`view`, `vote`, `add`, `edit`) ausführen können. -Wir erstellen eine Instanz der Klasse Permission und definieren **Rollen**. Es ist möglich, die Vererbung von Rollen zu nutzen, wodurch sichergestellt wird, dass z. B. ein Benutzer mit der Rolle `admin` das tun kann, was ein gewöhnlicher Website-Besucher tun kann (und natürlich mehr). +Wir erstellen eine Instanz der Klasse `Permission` und definieren die **Rollen**. Dabei kann die sogenannte Rollenvererbung genutzt werden, die sicherstellt, dass z. B. ein Benutzer mit der Rolle Administrator (`admin`) auch das tun kann, was ein normaler Website-Besucher tun kann (und natürlich noch mehr). ```php $acl = new Nette\Security\Permission; $acl->addRole('guest'); $acl->addRole('registered', 'guest'); // 'registered' erbt von 'guest' -$acl->addRole('admin', 'registered'); // und 'admin' erbt von 'registered' +$acl->addRole('admin', 'registered'); // und davon erbt 'admin' ``` -Wir werden nun eine Liste von **Ressourcen** definieren, auf die Benutzer zugreifen können: +Nun definieren wir auch die Liste der **Ressourcen**, auf die Benutzer zugreifen können. ```php $acl->addResource('article'); @@ -114,49 +114,49 @@ $acl->addResource('comment'); $acl->addResource('poll'); ``` -Ressourcen können auch vererbt werden, zum Beispiel können wir `$acl->addResource('perex', 'article')` hinzufügen. +Auch Ressourcen können Vererbung verwenden, es wäre beispielsweise möglich, `$acl->addResource('perex', 'article')` anzugeben. -Und nun das Wichtigste. Wir werden zwischen ihnen **Regeln** definieren, die festlegen, wer was tun darf: +Und jetzt das Wichtigste. Wir definieren zwischen ihnen Regeln, die festlegen, wer was mit was tun darf: ```php -// jetzt wird alles verweigert +// zuerst darf niemand etwas tun -// dem Gast die Ansicht von Artikeln, Kommentaren und Umfragen erlauben +// Gast darf Artikel, Kommentare und Umfragen anzeigen $acl->allow('guest', ['article', 'comment', 'poll'], 'view'); -// und auch in Umfragen abstimmen +// und in Umfragen zusätzlich abstimmen $acl->allow('guest', 'poll', 'vote'); -// der Registrierte erbt die Rechte des Gastes, wir erlauben ihm auch das Kommentieren +// Registrierter Benutzer erbt Rechte vom Gast, geben wir ihm zusätzlich das Recht zu kommentieren $acl->allow('registered', 'comment', 'add'); -// der Administrator kann alles sehen und bearbeiten +// Administrator kann alles anzeigen und bearbeiten $acl->allow('admin', $acl::All, ['view', 'edit', 'add']); ``` -Was ist, wenn wir **verhindern** wollen, dass jemand auf eine Ressource zugreift? +Was ist, wenn wir jemandem den Zugriff auf eine bestimmte Ressource **verweigern** wollen? ```php -// Administrator kann keine Umfragen bearbeiten, das wäre undemokratisch. +// Administrator kann Umfragen nicht bearbeiten, das wäre undemokratisch $acl->deny('admin', 'poll', 'edit'); ``` -Wenn wir nun das Regelwerk erstellt haben, können wir einfach die Autorisierungsanfragen stellen: +Nun, da wir die Liste der Regeln erstellt haben, können wir einfach Autorisierungsabfragen stellen: ```php -// Können Gäste Artikel sehen? +// darf guest Artikel anzeigen? $acl->isAllowed('guest', 'article', 'view'); // true -// Kann ein Gast einen Artikel bearbeiten? +// darf guest Artikel bearbeiten? $acl->isAllowed('guest', 'article', 'edit'); // false -// Kann ein Gast an Umfragen teilnehmen? +// darf guest in Umfragen abstimmen? $acl->isAllowed('guest', 'poll', 'vote'); // true -// Darf ein Gast Kommentare hinzufügen? +// darf guest kommentieren? $acl->isAllowed('guest', 'comment', 'add'); // false ``` -Das Gleiche gilt für einen registrierten Benutzer, aber er kann auch Kommentare abgeben: +Dasselbe gilt für einen registrierten Benutzer, dieser kann jedoch auch kommentieren: ```php $acl->isAllowed('registered', 'article', 'view'); // true @@ -164,7 +164,7 @@ $acl->isAllowed('registered', 'comment', 'add'); // true $acl->isAllowed('registered', 'comment', 'edit'); // false ``` -Der Administrator kann alles außer Umfragen bearbeiten: +Der Administrator kann alles bearbeiten, außer Umfragen: ```php $acl->isAllowed('admin', 'poll', 'vote'); // true @@ -172,7 +172,7 @@ $acl->isAllowed('admin', 'poll', 'edit'); // false $acl->isAllowed('admin', 'comment', 'edit'); // true ``` -Berechtigungen können auch dynamisch ausgewertet werden und wir können die Entscheidung unserem eigenen Callback überlassen, an den alle Parameter übergeben werden: +Berechtigungen können auch dynamisch ausgewertet werden, und wir können die Entscheidung einem eigenen Callback überlassen, dem alle Parameter übergeben werden: ```php $assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { @@ -182,7 +182,7 @@ $assertion = function (Permission $acl, string $role, string $resource, string $ $acl->allow('registered', 'comment', null, $assertion); ``` -Aber wie löst man eine Situation, in der die Namen von Rollen und Ressourcen nicht ausreichen, d.h. wir möchten festlegen, dass z.B. eine Rolle `registered` eine Ressource `article` nur dann bearbeiten darf, wenn sie deren Autor ist? Wir werden Objekte anstelle von Strings verwenden, die Rolle wird das Objekt [api:Nette\Security\Role] und die Quelle [api:Nette\Security\Resource] sein. Ihre Methoden `getRoleId()` bzw. `getResourceId()` werden die ursprünglichen Zeichenketten zurückgeben: +Wie löst man aber beispielsweise eine Situation, in der die Namen von Rollen und Ressourcen nicht ausreichen, sondern wir definieren möchten, dass beispielsweise die Rolle `registered` die Ressource `article` nur bearbeiten darf, wenn sie ihr Autor ist? Anstelle von Zeichenketten verwenden wir Objekte, die Rolle ist ein Objekt, das [api:Nette\Security\Role] implementiert, und die Ressource ein Objekt, das [api:Nette\Security\Resource] implementiert. Ihre Methoden `getRoleId()` bzw. `getResourceId()` geben die ursprünglichen Zeichenketten zurück: ```php class Registered implements Nette\Security\Role @@ -207,19 +207,19 @@ class Article implements Nette\Security\Resource } ``` -Und nun erstellen wir eine Regel: +Und nun erstellen wir die Regel: ```php $assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { - $role = $acl->getQueriedRole(); // object Registered - $resource = $acl->getQueriedResource(); // object Article + $role = $acl->getQueriedRole(); // Objekt Registered + $resource = $acl->getQueriedResource(); // Objekt Article return $role->id === $resource->authorId; }; $acl->allow('registered', 'article', 'edit', $assertion); ``` -Die ACL wird durch Übergabe von Objekten abgefragt: +Und die Abfrage an die ACL erfolgt durch Übergabe der Objekte: ```php $user = new Registered(/* ... */); @@ -227,7 +227,7 @@ $article = new Article(/* ... */); $acl->isAllowed($user, $article, 'edit'); ``` -Eine Rolle kann von einer oder mehreren anderen Rollen erben. Was passiert aber, wenn ein Vorfahre eine bestimmte Aktion erlaubt und der andere sie verweigert? Dann kommt das *Rollengewicht* ins Spiel - die letzte Rolle in der Reihe der zu vererbenden Rollen hat das größte Gewicht, die erste das niedrigste: +Eine Rolle kann von einer anderen Rolle oder von mehreren Rollen erben. Was passiert aber, wenn ein Vorfahre die Aktion verboten und ein anderer erlaubt hat? Welche Rechte hat der Nachkomme? Dies wird durch das Gewicht der Rolle bestimmt – die zuletzt in der Liste der Vorfahren angegebene Rolle hat das höchste Gewicht, die zuerst angegebene Rolle das niedrigste. Dies wird anhand eines Beispiels deutlicher: ```php $acl = new Nette\Security\Permission; @@ -239,22 +239,22 @@ $acl->addResource('backend'); $acl->allow('admin', 'backend'); $acl->deny('guest', 'backend'); -// Beispiel A: Rolle admin hat geringeres Gewicht als Rolle guest +// Fall A: Rolle 'admin' hat geringeres Gewicht als Rolle 'guest' $acl->addRole('john', ['admin', 'guest']); $acl->isAllowed('john', 'backend'); // false -// Beispiel B: Rolle admin hat größeres Gewicht als Rolle guest +// Fall B: Rolle 'admin' hat höheres Gewicht als 'guest' $acl->addRole('mary', ['guest', 'admin']); $acl->isAllowed('mary', 'backend'); // true ``` -Rollen und Ressourcen können auch entfernt werden (`removeRole()`, `removeResource()`), Regeln können auch rückgängig gemacht werden (`removeAllow()`, `removeDeny()`). Das Array aller direkten Elternrollen liefert `getRoleParents()`. Ob zwei Entitäten voneinander erben, gibt `roleInheritsFrom()` und `resourceInheritsFrom()` zurück. +Rollen und Ressourcen können auch entfernt werden (`removeRole()`, `removeResource()`), Regeln können ebenfalls rückgängig gemacht werden (`removeAllow()`, `removeDeny()`). Das Array aller direkten Elternrollen gibt `getRoleParents()` zurück, ob zwei Entitäten voneinander erben, geben `roleInheritsFrom()` und `resourceInheritsFrom()` zurück. -Hinzufügen als Dienst .[#toc-add-as-a-service] ----------------------------------------------- +Hinzufügen als Dienste +---------------------- -Wir müssen die von uns erstellte ACL als Dienst zur Konfiguration hinzufügen, damit sie vom Objekt `$user` verwendet werden kann, d. h. damit wir sie z. B. im Code verwenden können `$user->isAllowed('article', 'view')`. Zu diesem Zweck werden wir eine Fabrik für sie schreiben: +Wir müssen unsere erstellte ACL als Dienst zur Konfiguration hinzufügen, damit das Objekt `$user` sie verwenden kann, d.h., damit wir im Code z. B. `$user->isAllowed('article', 'view')` verwenden können. Zu diesem Zweck schreiben wir eine Factory dafür: ```php namespace App\Model; @@ -272,14 +272,14 @@ class AuthorizatorFactory } ``` -Und wir fügen sie zur Konfiguration hinzu: +Und fügen sie zur Konfiguration hinzu: ```neon services: - App\Model\AuthorizatorFactory::create ``` -In Präsentatoren können Sie dann z. B. in der Methode `startup()` die Berechtigungen überprüfen: +In Presentern können Sie dann die Berechtigungen beispielsweise in der Methode `startup()` überprüfen: ```php protected function startup() diff --git a/security/de/configuration.texy b/security/de/configuration.texy index e5b75fdc63..8d883c3662 100644 --- a/security/de/configuration.texy +++ b/security/de/configuration.texy @@ -1,71 +1,85 @@ -Konfigurieren der Zugriffskontrolle -*********************************** +Konfiguration der Zugriffsberechtigungen +**************************************** .[perex] -Überblick über die Konfigurationsoptionen für Nette Security. +Übersicht der Konfigurationsoptionen für Nette Security. -Wenn Sie nicht das gesamte Framework, sondern nur diese Bibliothek verwenden, lesen Sie [, wie Sie die Konfiguration laden |bootstrap:]. +Wenn Sie nicht das gesamte Framework verwenden, sondern nur diese Bibliothek, lesen Sie, [wie die Konfiguration geladen wird|bootstrap:]. -Sie können eine Liste von Benutzern in der Konfiguration definieren, um einen [einfachen Authentifikator |authentication] zu erstellen (`Nette\Security\SimpleAuthenticator`). Da Passwörter in der Konfiguration lesbar sind, ist diese Lösung nur für Testzwecke geeignet. +In der Konfiguration kann eine Liste von Benutzern definiert werden, um einen [einfachen Authentifikator|authentication] (`Nette\Security\SimpleAuthenticator`) zu erstellen. Da Passwörter in der Konfiguration im Klartext angegeben werden, eignet sich diese Lösung nur für Testzwecke. ```neon security: - # zeigt Benutzeroberfläche in Tracy Bar? - debugger: ... # (bool) standardmäßig true + # Benutzer-Panel in der Tracy Bar anzeigen? + debugger: ... # (bool) Standard ist true users: - # Name: Passwort - johndoe: geheim123 + # Benutzername: Passwort + frantisek: geheimnispasswort - # Name, Passwort, Rolle und andere in der Identität verfügbare Daten - janedoe: - password: secret123 + # Benutzername, Passwort, Rolle und weitere in der Identität verfügbare Daten + dobrota: + password: geheimnispasswort roles: [admin] data: ... ``` -Sie können auch Rollen und Ressourcen definieren, um eine Grundlage für einen [Autorisierer |authorization] zu schaffen (`Nette\Security\Permission`): +Weiterhin können Rollen und Ressourcen definiert werden, um eine Grundlage für den [Autorisator|authorization] (`Nette\Security\Permission`) zu schaffen: ```neon security: roles: guest: - registered: [guest] # registered erbt von guest - admin: [registered] # und admin erbt von registered + registered: [guest] # registered erbt von guest + admin: [registered] # und davon erbt admin resources: article: - comment: [article] # Ressource erbt von article + comment: [article] # Ressource erbt von article poll: ``` -Speicherung von Benutzern .[#toc-user-storage] ----------------------------------------------- +Speicher +-------- -Sie können konfigurieren, wie Informationen über den angemeldeten Benutzer gespeichert werden sollen: +Es kann konfiguriert werden, wie Informationen über den angemeldeten Benutzer gespeichert werden: ```neon security: authentication: # nach welcher Zeit der Inaktivität wird der Benutzer abgemeldet - expiration: 30 minutes # (string) Standardwert ist nicht gesetzt + expiration: 30 minutes # (string) Standard ist nicht gesetzt - # wo Informationen über den angemeldeten Benutzer gespeichert werden sollen - storage: session # (session|cookie) Voreinstellung ist session + # wohin Informationen über den angemeldeten Benutzer speichern + storage: session # (session|cookie) Standard ist session ``` -Wenn Sie `cookie` als Repository wählen, können Sie auch die folgenden Optionen einstellen: +Wenn Sie `cookie` als Speicher wählen, können Sie noch diese Optionen einstellen: ```neon security: authentication: - # Cookie-Name - cookieName: userId # (string) výchozí je userid + # Name des Cookies + cookieName: userId # (string) Standard ist userid - # welche Hosts dürfen das Cookie erhalten - cookieDomain: 'example.com' # (string|domain) + # Domains, die das Cookie akzeptieren + cookieDomain: 'example.com' # (string|domain) - # Einschränkungen beim Zugriff auf herkunftsübergreifende Anfragen - cookieSamesite: None # (Strict|Lax|None) Standardwert ist Lax + # Einschränkung beim Zugriff von einer anderen Domain + cookieSamesite: None # (Strict|Lax|None) Standard ist Lax ``` + + +DI-Dienste +---------- + +Diese Dienste werden dem DI-Container hinzugefügt: + +| Name | Typ | Beschreibung +|----------------------------------------------------------------------------- +| `security.authenticator` | [api:Nette\Security\Authenticator] | [Authentifikator|authentication] +| `security.authorizator` | [api:Nette\Security\Authorizator] | [Autorisator|authorization] +| `security.passwords` | [api:Nette\Security\Passwords] | [Passwort-Hashing|passwords] +| `security.user` | [api:Nette\Security\User] | aktueller Benutzer +| `security.userStorage` | [api:Nette\Security\UserStorage] | [#Speicher] diff --git a/security/de/passwords.texy b/security/de/passwords.texy index 5d1f1aecd7..8e3fce4e90 100644 --- a/security/de/passwords.texy +++ b/security/de/passwords.texy @@ -2,11 +2,11 @@ Passwort-Hashing **************** .[perex] -Um die Sicherheit unserer Benutzer zu gewährleisten, speichern wir ihre Passwörter nie im Klartext, sondern nur als Hash. Das Hashing ist kein umkehrbarer Vorgang, das Kennwort kann nicht wiederhergestellt werden. Das Passwort kann jedoch geknackt werden, und um das Knacken so schwer wie möglich zu machen, müssen wir einen sicheren Algorithmus verwenden. Die Klasse [api:Nette\Security\Passwords] wird uns dabei helfen. +Um die Sicherheit unserer Benutzer zu gewährleisten, speichern wir ihre Passwörter nicht im Klartext, sondern nur deren Hash (sog. Fingerabdruck). Aus dem Hash lässt sich die ursprüngliche Form des Passworts nicht rekonstruieren. Es ist wichtig, einen sicheren Algorithmus zur Erstellung des Hashs zu verwenden. Dabei hilft uns die Klasse [api:Nette\Security\Passwords]. → [Installation und Anforderungen |@home#Installation] -Das Framework fügt dem DI-Container automatisch einen Dienst `Nette\Security\Passwords` unter dem Namen `security.passwords` hinzu, den man durch Übergabe mittels [Dependency Injection |dependency-injection:passing-dependencies] erhält: +Das Framework fügt dem DI-Container automatisch einen Dienst vom Typ `Nette\Security\Passwords` unter dem Namen `security.passwords` hinzu, auf den Sie zugreifen können, indem Sie ihn sich mittels [Dependency Injection |dependency-injection:passing-dependencies] übergeben lassen. ```php use Nette\Security\Passwords; @@ -21,47 +21,47 @@ class Foo ``` -__construct($algo=PASSWORD_DEFAULT, array $options=[]): string .[method] -======================================================================== +__construct($algo=PASSWORD_DEFAULT, array $options=[]) .[method] +================================================================ -Wählt aus, welcher [sichere Algorithmus |https://www.php.net/manual/en/password.constants.php] für das Hashing verwendet wird und wie er konfiguriert werden soll. +Wir wählen aus, welcher [sichere Algorithmus|https://www.php.net/manual/en/password.constants.php] zur Generierung des Hashs verwendet werden soll, und konfigurieren seine Parameter. -Die Vorgabe ist `PASSWORD_DEFAULT`, so dass die Wahl des Algorithmus PHP überlassen wird. Der Algorithmus kann sich in neueren PHP-Versionen ändern, wenn neuere, stärkere Hashing-Algorithmen unterstützt werden. Sie sollten sich daher bewusst sein, dass sich die Länge des resultierenden Hashes ändern kann. Daher sollten Sie den resultierenden Hash in einer Weise speichern, die genügend Zeichen speichern kann, 255 ist die empfohlene Breite. +Als Standard wird `PASSWORD_DEFAULT` verwendet, d.h., die Wahl des Algorithmus wird PHP überlassen. Der Algorithmus kann sich in neueren PHP-Versionen ändern, wenn neuere, stärkere Hashing-Algorithmen verfügbar werden. Daher sollten Sie sich bewusst sein, dass sich die Länge des resultierenden Hashs ändern kann, und Sie sollten ihn so speichern, dass er genügend Zeichen aufnehmen kann. 255 ist die empfohlene Breite. -So würden Sie den bcrypt-Algorithmus verwenden und die Hash-Geschwindigkeit mit dem Parameter cost von der Standardeinstellung 10 ändern. Im Jahr 2020 dauert das Hashing eines Kennworts mit Kosten 10 etwa 80 ms, mit Kosten 11 160 ms und mit Kosten 12 320 ms, die Skala ist logarithmisch. Je langsamer, desto besser, Kosten 10-12 werden als langsam genug für die meisten Anwendungsfälle angesehen: +Beispiel für die Einstellung der Hashing-Geschwindigkeit des bcrypt-Algorithmus durch Änderung des `cost`-Parameters: (Im Jahr 2020 ist der Standardwert 10, das Hashing eines Passworts dauert etwa 80 ms, bei `cost` 11 sind es ca. 160 ms, bei `cost` 12 etwa 320 ms. Je langsamer, desto besser der Schutz, wobei eine Geschwindigkeit von 10-12 bereits als ausreichender Schutz gilt.) ```php -// Wir werden Passwörter mit 2^12 (2^cost) Iterationen des bcrypt-Algorithmus hashen +// wir werden Passwörter mit 2^12 (2^cost) Iterationen des bcrypt-Algorithmus hashen $passwords = new Passwords(PASSWORD_BCRYPT, ['cost' => 12]); ``` -Mit Dependency Injection: +Mittels Dependency Injection: ```neon services: security.passwords: Nette\Security\Passwords(::PASSWORD_BCRYPT, [cost: 12]) ``` -hash(string $passwords): string .[method] -========================================= +hash(string $password): string .[method] +======================================== -Erzeugt den Hash des Passworts. +Generiert den Hash des Passworts. ```php -$res = $passwords->hash($password); // Hashes des Passworts +$hash = $passwords->hash($password); // Hasht das Passwort ``` -Das Ergebnis `$res` ist eine Zeichenkette, die neben dem Hash selbst auch die Kennung des verwendeten Algorithmus, seine Einstellungen und das kryptografische Salt (Zufallsdaten, die sicherstellen, dass für dasselbe Kennwort ein anderer Hash erzeugt wird) enthält. Es ist daher rückwärtskompatibel, d. h., wenn Sie die Parameter ändern, können die mit den vorherigen Einstellungen gespeicherten Hashes überprüft werden. Das gesamte Ergebnis wird in der Datenbank gespeichert, so dass es nicht notwendig ist, Salt oder Einstellungen separat zu speichern. +Das Ergebnis `$hash` ist eine Zeichenkette, die neben dem eigentlichen Hash auch den Bezeichner des verwendeten Algorithmus, seine Einstellungen und das kryptografische Salt (zufällige Daten, die sicherstellen, dass für dasselbe Passwort ein anderer Hash generiert wird) enthält. Es ist also abwärtskompatibel. Wenn Sie beispielsweise die Parameter ändern, können auch Hashes überprüft werden, die mit den vorherigen Einstellungen gespeichert wurden. Das gesamte Ergebnis wird in der Datenbank gespeichert, sodass Salt oder Einstellungen nicht separat gespeichert werden müssen. verify(string $password, string $hash): bool .[method] ====================================================== -Findet heraus, ob das angegebene Passwort mit dem angegebenen Hash übereinstimmt. Holt die `$hash` aus der Datenbank nach Benutzernamen oder E-Mail Adresse. +Stellt fest, ob das gegebene Passwort dem gegebenen Hash entspricht. `$hash` erhalten Sie aus der Datenbank anhand des eingegebenen Benutzernamens oder der E-Mail-Adresse. ```php if ($passwords->verify($password, $hash)) { - // Richtiges Passwort + // korrektes Passwort } ``` @@ -69,9 +69,9 @@ if ($passwords->verify($password, $hash)) { needsRehash(string $hash): bool .[method] ========================================= -Findet heraus, ob der Hash mit den im Konstruktor angegebenen Optionen übereinstimmt. +Stellt fest, ob der Hash den im Konstruktor angegebenen Optionen entspricht. -Verwenden Sie diese Methode, wenn Sie zum Beispiel Hash-Parameter ändern. Die Passwortüberprüfung verwendet die mit dem Hash gespeicherten Parameter, und wenn `needsRehash()` true zurückgibt, müssen Sie den Hash erneut berechnen, diesmal mit den aktualisierten Parametern, und ihn erneut in der Datenbank speichern. Dadurch wird sichergestellt, dass die Hashes der Passwörter automatisch "aktualisiert" werden, wenn sich die Benutzer anmelden. +Dies ist nützlich, wenn Sie beispielsweise die Hashing-Geschwindigkeit ändern. Die Überprüfung erfolgt gemäß den gespeicherten Einstellungen, und wenn `needsRehash()` `true` zurückgibt, muss der Hash erneut erstellt werden, diesmal mit den neuen Parametern, und erneut in der Datenbank gespeichert werden. Auf diese Weise werden gespeicherte Hashes beim Anmelden der Benutzer automatisch "aktualisiert". ```php if ($passwords->needsRehash($hash)) { diff --git a/security/el/@home.texy b/security/el/@home.texy index 2dbab076d2..360447145b 100644 --- a/security/el/@home.texy +++ b/security/el/@home.texy @@ -1,14 +1,14 @@ -Ασφάλεια -******** +Nette Security +************** .[perex] -Το πακέτο `nette/security` είναι υπεύθυνο για την [πιστοποίηση χρηστών |authentication], τον [έλεγχο πρόσβασης |authorization] και [τον κατακερματισμό κωδικών πρόσβασης |passwords]. +Το πακέτο `nette/security` είναι υπεύθυνο για τη [σύνδεση χρηστών |authentication], την [επαλήθευση αδειών |authorization] και τον [κατακερματισμό κωδικών πρόσβασης |passwords]. -Εγκατάσταση .[#toc-installation] --------------------------------- +Εγκατάσταση +----------- -Κατεβάστε και εγκαταστήστε το πακέτο χρησιμοποιώντας το [Composer |best-practices:composer]: +Μπορείτε να κατεβάσετε και να εγκαταστήσετε τη βιβλιοθήκη χρησιμοποιώντας το [Composer|best-practices:composer]: ```shell composer require nette/security diff --git a/security/el/@left-menu.texy b/security/el/@left-menu.texy index a932344478..8a5b4f9aaf 100644 --- a/security/el/@left-menu.texy +++ b/security/el/@left-menu.texy @@ -1,7 +1,7 @@ Nette Security ************** -- [Επισκόπηση |@home] -- [Πιστοποίηση ταυτότητας |Authentication] -- [Εξουσιοδότηση |Authorization] -- [Κατακερματισμός κωδικού πρόσβασης |passwords] -- [Διαμόρφωση |Configuration] +- [Εισαγωγή |@home] +- [Authentication |authentication] +- [Authorization |authorization] +- [Κατακερματισμός κωδικών πρόσβασης |passwords] +- [Διαμόρφωση |configuration] diff --git a/security/el/@meta.texy b/security/el/@meta.texy new file mode 100644 index 0000000000..88e29852c7 --- /dev/null +++ b/security/el/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Τεκμηρίωση}} diff --git a/security/el/authentication.texy b/security/el/authentication.texy index bc70b8fc2d..f283731914 100644 --- a/security/el/authentication.texy +++ b/security/el/authentication.texy @@ -1,48 +1,48 @@ -Αυθεντικοποίηση χρηστών -*********************** +Σύνδεση χρηστών (Αυθεντικοποίηση) +********************************* <div class=perex> -Οι ελάχιστες έως μηδαμινές εφαρμογές ιστού δεν χρειάζονται μηχανισμό για την είσοδο των χρηστών ή τον έλεγχο των προνομίων τους. Σε αυτό το κεφάλαιο, θα μιλήσουμε για: +Σχεδόν καμία web εφαρμογή δεν μπορεί να λειτουργήσει χωρίς μηχανισμό σύνδεσης χρηστών και επαλήθευσης των δικαιωμάτων τους. Σε αυτό το κεφάλαιο θα μιλήσουμε για: -- την είσοδο και την έξοδο του χρήστη -- προσαρμοσμένους αυθεντικοποιητές και εξουσιοδοτητές +- σύνδεση και αποσύνδεση χρηστών +- προσαρμοσμένους αυθεντικοποιητές </div> -→ [Εγκατάσταση και απαιτήσεις |@home#Installation] +→ [Εγκατάσταση και απαιτήσεις |@home#Εγκατάσταση] -Στα παραδείγματα, θα χρησιμοποιήσουμε ένα αντικείμενο της κλάσης [api:Nette\Security\User], το οποίο αντιπροσωπεύει τον τρέχοντα χρήστη και το οποίο λαμβάνετε περνώντας το με τη χρήση [dependency injection |dependency-injection:passing-dependencies]. Στα presenters απλά καλέστε το `$user = $this->getUser()`. +Στα παραδείγματα θα χρησιμοποιούμε το αντικείμενο της κλάσης [api:Nette\Security\User], το οποίο αντιπροσωπεύει τον τρέχοντα χρήστη και στο οποίο μπορείτε να αποκτήσετε πρόσβαση ζητώντας να σας περαστεί μέσω [dependency injection |dependency-injection:passing-dependencies]. Στους presenters αρκεί απλώς να καλέσετε `$user = $this->getUser()`. -Αυθεντικοποίηση .[#toc-authentication] -====================================== +Αυθεντικοποίηση +=============== -Αυθεντικοποίηση σημαίνει **σύνδεση χρήστη**, δηλαδή η διαδικασία κατά την οποία επαληθεύεται η ταυτότητα ενός χρήστη. Ο χρήστης συνήθως ταυτοποιείται χρησιμοποιώντας όνομα χρήστη και κωδικό πρόσβασης. Η επαλήθευση πραγματοποιείται από τον λεγόμενο [αυθεντικοποιητή |#authenticator]. Εάν η σύνδεση αποτύχει, εκπέμπεται η εντολή `Nette\Security\AuthenticationException`. +Με τον όρο αυθεντικοποίηση εννοείται η **σύνδεση χρηστών**, δηλαδή η διαδικασία κατά την οποία επαληθεύεται αν ο χρήστης είναι όντως αυτός που ισχυρίζεται ότι είναι. Συνήθως αποδεικνύεται με όνομα χρήστη και κωδικό πρόσβασης. Η επαλήθευση γίνεται από τον λεγόμενο [Αυθεντικοποιητή |#Αυθεντικοποιητής]. Εάν η σύνδεση αποτύχει, δημιουργείται μια εξαίρεση `Nette\Security\AuthenticationException`. ```php try { $user->login($username, $password); } catch (Nette\Security\AuthenticationException $e) { - $this->flashMessage('The username or password you entered is incorrect.'); + $this->flashMessage('Το όνομα χρήστη ή ο κωδικός πρόσβασης είναι λανθασμένος'); } ``` -Έτσι γίνεται η αποσύνδεση του χρήστη: +Με αυτόν τον τρόπο αποσυνδέετε τον χρήστη: ```php $user->logout(); ``` -Και ελέγχοντας αν ο χρήστης είναι συνδεδεμένος: +Και η διαπίστωση ότι είναι συνδεδεμένος: ```php -echo $user->isLoggedIn() ? 'yes' : 'no'; +echo $user->isLoggedIn() ? 'ναι' : 'όχι'; ``` -Απλό, έτσι δεν είναι; Και όλες οι πτυχές της ασφάλειας αντιμετωπίζονται από τη Nette για εσάς. +Πολύ απλό, έτσι δεν είναι; Και όλες τις πτυχές ασφαλείας τις διαχειρίζεται το Nette για εσάς. -Στον παρουσιαστή, μπορείτε να επαληθεύσετε τη σύνδεση στη μέθοδο `startup()` και να ανακατευθύνετε έναν μη συνδεδεμένο χρήστη στη σελίδα σύνδεσης. +Στους presenters μπορείτε να επαληθεύσετε τη σύνδεση στη μέθοδο `startup()` και να ανακατευθύνετε τον μη συνδεδεμένο χρήστη στη σελίδα σύνδεσης. ```php protected function startup() @@ -55,39 +55,38 @@ protected function startup() ``` -Λήξη .[#toc-expiration] -======================= +Λήξη +==== -Η σύνδεση χρήστη λήγει μαζί με τη [λήξη του αποθετηρίου |#Storage for Logged User], το οποίο συνήθως είναι μια σύνοδος (βλ. ρύθμιση [λήξης συνόδου |http:configuration#session] ). -Ωστόσο, μπορείτε επίσης να ορίσετε ένα μικρότερο χρονικό διάστημα μετά το οποίο ο χρήστης αποσυνδέεται. Για το σκοπό αυτό χρησιμοποιείται η μέθοδος `setExpiration()`, η οποία καλείται πριν από τη διεύθυνση `login()`. Παρέχετε ως παράμετρο μια συμβολοσειρά με μια σχετική ώρα: +Η σύνδεση του χρήστη λήγει μαζί με τη [λήξη του αποθηκευτικού χώρου |#Αποθηκευτικός χώρος συνδεδεμένου χρήστη], ο οποίος είναι συνήθως η session (βλ. ρύθμιση [λήξης session |http:configuration#Session]). Ωστόσο, μπορεί να οριστεί και ένα μικρότερο χρονικό διάστημα, μετά την παρέλευση του οποίου ο χρήστης αποσυνδέεται. Γι' αυτό χρησιμεύει η μέθοδος `setExpiration()`, η οποία καλείται πριν από την `login()`. Ως παράμετρο δώστε μια συμβολοσειρά με σχετικό χρόνο: ```php // η σύνδεση λήγει μετά από 30 λεπτά αδράνειας $user->setExpiration('30 minutes'); -// Ακύρωση ορισμού λήξης +// ακύρωση της καθορισμένης λήξης $user->setExpiration(null); ``` -Η μέθοδος `$user->getLogoutReason()` ενημερώνει αν ο χρήστης έχει αποσυνδεθεί επειδή έχει λήξει το χρονικό διάστημα. Επιστρέφει είτε τη σταθερά `Nette\Security\UserStorage::LogoutInactivity` εάν έχει λήξει το χρονικό διάστημα είτε `UserStorage::LogoutManual` όταν έχει κληθεί η μέθοδος `logout()`. +Το αν ο χρήστης αποσυνδέθηκε λόγω λήξης του χρονικού διαστήματος αποκαλύπτει η μέθοδος `$user->getLogoutReason()`, η οποία επιστρέφει είτε τη σταθερά `Nette\Security\UserStorage::LogoutInactivity` (έληξε το χρονικό όριο) είτε `UserStorage::LogoutManual` (αποσυνδέθηκε με τη μέθοδο `logout()`). -Αυθεντικοποιητής .[#toc-authenticator] -====================================== +Αυθεντικοποιητής +================ -Είναι ένα αντικείμενο που επαληθεύει τα δεδομένα σύνδεσης, δηλαδή συνήθως το όνομα και τον κωδικό πρόσβασης. Η τετριμμένη υλοποίηση είναι η κλάση [api:Nette\Security\SimpleAuthenticator], η οποία μπορεί να οριστεί στη [διαμόρφωση |configuration]: +Πρόκειται για ένα αντικείμενο που επαληθεύει τα διαπιστευτήρια σύνδεσης, δηλαδή συνήθως το όνομα και τον κωδικό πρόσβασης. Μια απλή μορφή είναι η κλάση [api:Nette\Security\SimpleAuthenticator], την οποία μπορούμε να ορίσουμε στη [διαμόρφωση|configuration]: ```neon security: users: # όνομα: κωδικός πρόσβασης - johndoe: secret123 - kathy: evenmoresecretpassword + frantisek: tajneheslo + katka: jestetajnejsiheslo ``` -Αυτή η λύση είναι πιο κατάλληλη για σκοπούς δοκιμών. Θα σας δείξουμε πώς να δημιουργήσετε έναν επαληθευτή που θα επαληθεύει τα διαπιστευτήρια έναντι ενός πίνακα της βάσης δεδομένων. +Αυτή η λύση είναι κατάλληλη κυρίως για δοκιμαστικούς σκοπούς. Θα δείξουμε πώς να δημιουργήσετε έναν αυθεντικοποιητή που θα επαληθεύει τα διαπιστευτήρια σύνδεσης έναντι ενός πίνακα βάσης δεδομένων. -Ένας αυθεντικοποιητής είναι ένα αντικείμενο που υλοποιεί τη διεπαφή [api:Nette\Security\Authenticator] με τη μέθοδο `authenticate()`. Το έργο του είναι είτε να επιστρέψει τη λεγόμενη [ταυτότητα |#identity] είτε να πετάξει μια εξαίρεση `Nette\Security\AuthenticationException`. Θα ήταν επίσης δυνατό να παρέχει έναν λεπτοµερή κωδικό σφάλµατος `Authenticator::IdentityNotFound` ή `Authenticator::InvalidCredential`. +Ο αυθεντικοποιητής είναι ένα αντικείμενο που υλοποιεί τη διεπαφή [api:Nette\Security\Authenticator] με τη μέθοδο `authenticate()`. Ο ρόλος της είναι είτε να επιστρέψει τη λεγόμενη [#ταυτότητα] είτε να δημιουργήσει μια εξαίρεση `Nette\Security\AuthenticationException`. Θα ήταν επίσης δυνατό να αναφερθεί ένας κωδικός σφάλματος για λεπτομερέστερη διάκριση της κατάστασης που προέκυψε: `Authenticator::IdentityNotFound` και `Authenticator::InvalidCredential`. ```php use Nette; @@ -117,16 +116,16 @@ class MyAuthenticator implements Nette\Security\Authenticator return new SimpleIdentity( $row->id, - $row->role, // ή συστοιχία ρόλων + $row->role, // ή ένας πίνακας με περισσότερους ρόλους ['name' => $row->username], ); } } ``` -Η κλάση MyAuthenticator επικοινωνεί με τη βάση δεδομένων μέσω του [Nette Database |database:explorer] Explorer και λειτουργεί με τον πίνακα `users`, όπου η στήλη `username` περιέχει το όνομα σύνδεσης του χρήστη και η στήλη `password` περιέχει [το hash |passwords]. Μετά την επαλήθευση του ονόματος και του κωδικού πρόσβασης, επιστρέφει την ταυτότητα με το αναγνωριστικό του χρήστη, το ρόλο (στήλη `role` στον πίνακα), τον οποίο θα αναφέρουμε [αργότερα |#roles], και έναν πίνακα με πρόσθετα δεδομένα (στην περίπτωσή μας, το όνομα χρήστη). +Η κλάση MyAuthenticator επικοινωνεί με τη βάση δεδομένων μέσω του [Nette Database Explorer|database:explorer] και εργάζεται με τον πίνακα `users`, όπου στη στήλη `username` βρίσκεται το όνομα σύνδεσης του χρήστη και στη στήλη `password` το [αποτύπωμα του κωδικού πρόσβασης|passwords]. Μετά την επαλήθευση του ονόματος και του κωδικού πρόσβασης, επιστρέφει την ταυτότητα, η οποία φέρει το ID του χρήστη, τον ρόλο του (στήλη `role` στον πίνακα), για τον οποίο θα πούμε περισσότερα [αργότερα |authorization#Ρόλοι], και έναν πίνακα με άλλα δεδομένα (στην περίπτωσή μας το όνομα χρήστη). -Θα προσθέσουμε τον authenticator στη διαμόρφωση [ως υπηρεσία |dependency-injection:services] του DI container: +Θα προσθέσουμε τον αυθεντικοποιητή στη διαμόρφωση [ως υπηρεσία|dependency-injection:services] του DI container: ```neon services: @@ -134,50 +133,50 @@ services: ``` -$onLoggedIn, $onLoggedOut Γεγονότα +Γεγονότα $onLoggedIn, $onLoggedOut ---------------------------------- -Το αντικείμενο `Nette\Security\User` διαθέτει [συμβάντα |nette:glossary#Events] `$onLoggedIn` και `$onLoggedOut`, ώστε να μπορείτε να προσθέσετε ανακλήσεις που ενεργοποιούνται μετά την επιτυχή είσοδο ή μετά την έξοδο του χρήστη. +Το αντικείμενο `Nette\Security\User` έχει [γεγονότα |nette:glossary#Events] `$onLoggedIn` και `$onLoggedOut`, οπότε μπορείτε να προσθέσετε callbacks που θα καλούνται μετά την επιτυχή σύνδεση ή την αποσύνδεση του χρήστη αντίστοιχα. ```php $user->onLoggedIn[] = function () { - // ο χρήστης έχει μόλις συνδεθεί + // ο χρήστης μόλις συνδέθηκε }; ``` -Ταυτότητα .[#toc-identity] -========================== +Ταυτότητα +========= -Η ταυτότητα είναι ένα σύνολο πληροφοριών σχετικά με έναν χρήστη που επιστρέφονται από τον αυθεντικοποιητή και οι οποίες στη συνέχεια αποθηκεύονται σε μια συνεδρία και ανακτώνται με τη χρήση του `$user->getIdentity()`. Έτσι μπορούμε να πάρουμε το id, τους ρόλους και άλλα δεδομένα του χρήστη όπως τα περάσαμε στον authenticator: +Η ταυτότητα αντιπροσωπεύει ένα σύνολο πληροφοριών για τον χρήστη, το οποίο επιστρέφει ο αυθεντικοποιητής και το οποίο στη συνέχεια διατηρείται στη session και το λαμβάνουμε χρησιμοποιώντας το `$user->getIdentity()`. Μπορούμε λοιπόν να λάβουμε το id, τους ρόλους και άλλα δεδομένα χρήστη, όπως τα περάσαμε στον αυθεντικοποιητή: ```php $user->getIdentity()->getId(); -// λειτουργεί επίσης η συντόμευση $user->getId(), +// λειτουργεί και η συντομογραφία $user->getId(); $user->getIdentity()->getRoles(); -// τα δεδομένα του χρήστη μπορούν να είναι προσβάσιμα ως ιδιότητες +// τα δεδομένα χρήστη είναι διαθέσιμα ως properties // το όνομα που περάσαμε στο MyAuthenticator $user->getIdentity()->name; ``` -Είναι σημαντικό ότι όταν ο χρήστης αποσυνδεθεί χρησιμοποιώντας το `$user->logout()`, η **ταυτότητα δεν διαγράφεται** και εξακολουθεί να είναι διαθέσιμη. Έτσι, αν η ταυτότητα υπάρχει, από μόνη της δεν εξασφαλίζει ότι ο χρήστης είναι επίσης συνδεδεμένος. Αν θέλουμε να διαγράψουμε ρητά την ταυτότητα, αποσυνδέουμε τον χρήστη με το `logout(true)`. +Αυτό που είναι σημαντικό είναι ότι κατά την αποσύνδεση με τη χρήση του `$user->logout()` **η ταυτότητα δεν διαγράφεται** και παραμένει διαθέσιμη. Έτσι, παρόλο που ο χρήστης έχει ταυτότητα, μπορεί να μην είναι συνδεδεμένος. Αν θέλαμε να διαγράψουμε ρητά την ταυτότητα, θα αποσυνδέαμε τον χρήστη καλώντας `logout(true)`. -Χάρη σε αυτό, μπορείτε ακόμα να υποθέσετε ποιος χρήστης βρίσκεται στον υπολογιστή και, για παράδειγμα, να εμφανίσετε εξατομικευμένες προσφορές στο ηλεκτρονικό κατάστημα, ωστόσο, μπορείτε να εμφανίσετε τα προσωπικά του δεδομένα μόνο μετά τη σύνδεση. +Χάρη σε αυτό, μπορείτε να συνεχίσετε να υποθέτετε ποιος χρήστης βρίσκεται στον υπολογιστή και, για παράδειγμα, να του εμφανίζετε εξατομικευμένες προσφορές στο e-shop, αλλά μπορείτε να του εμφανίσετε τα προσωπικά του δεδομένα μόνο μετά τη σύνδεση. -Η ταυτότητα είναι ένα αντικείμενο που υλοποιεί τη διεπαφή [api:Nette\Security\IIdentity], η προεπιλεγμένη υλοποίηση είναι η [api:Nette\Security\SimpleIdentity]. Και όπως αναφέρθηκε, η ταυτότητα αποθηκεύεται στη σύνοδο, οπότε αν, για παράδειγμα, αλλάξουμε το ρόλο κάποιου από τους συνδεδεμένους χρήστες, τα παλιά δεδομένα θα διατηρηθούν στην ταυτότητα μέχρι να συνδεθεί ξανά. +Η ταυτότητα είναι ένα αντικείμενο που υλοποιεί τη διεπαφή [api:Nette\Security\IIdentity], η προεπιλεγμένη υλοποίηση είναι η [api:Nette\Security\SimpleIdentity]. Και όπως αναφέρθηκε, διατηρείται στη session, οπότε αν, για παράδειγμα, αλλάξουμε τον ρόλο κάποιου από τους συνδεδεμένους χρήστες, τα παλιά δεδομένα θα παραμείνουν στην ταυτότητά του μέχρι την επόμενη σύνδεσή του. -Αποθήκευση για τον συνδεδεμένο χρήστη .[#toc-storage-for-logged-user] -===================================================================== +Αποθηκευτικός χώρος συνδεδεμένου χρήστη +======================================= -Οι δύο βασικές πληροφορίες σχετικά με τον χρήστη, δηλαδή το αν είναι συνδεδεμένος και η [ταυτότητά |#identity] του, μεταφέρονται συνήθως στη συνεδρία. Η οποία μπορεί να αλλάξει. Για την αποθήκευση αυτών των πληροφοριών είναι υπεύθυνο ένα αντικείμενο που υλοποιεί τη διεπαφή `Nette\Security\UserStorage`. Υπάρχουν δύο τυπικές υλοποιήσεις, η πρώτη μεταφέρει τα δεδομένα σε μια σύνοδο και η δεύτερη σε ένα cookie. Πρόκειται για τις κλάσεις `Nette\Bridges\SecurityHttp\SessionStorage` και `CookieStorage`. Μπορείτε να επιλέξετε την αποθήκευση και να τη ρυθμίσετε πολύ εύκολα στη διαμόρφωση [security › authentication |configuration]. +Οι δύο βασικές πληροφορίες για τον χρήστη, δηλαδή αν είναι συνδεδεμένος και η [#ταυτότητά] του, συνήθως μεταφέρονται στη session. Κάτι που μπορεί να αλλάξει. Για την αποθήκευση αυτών των πληροφοριών είναι υπεύθυνο ένα αντικείμενο που υλοποιεί τη διεπαφή `Nette\Security\UserStorage`. Διατίθενται δύο τυπικές υλοποιήσεις, η πρώτη μεταφέρει δεδομένα στη session και η δεύτερη σε cookie. Πρόκειται για τις κλάσεις `Nette\Bridges\SecurityHttp\SessionStorage` και `CookieStorage`. Μπορείτε να επιλέξετε τον αποθηκευτικό χώρο και να τον διαμορφώσετε πολύ άνετα στη διαμόρφωση [security › authentication |configuration#Αποθηκευτικός χώρος]. -Μπορείτε επίσης να ελέγξετε τον ακριβή τρόπο με τον οποίο θα γίνεται η αποθήκευση (*sleep*) και η επαναφορά (*wakeup*) της ταυτότητας. Το μόνο που χρειάζεστε είναι να υλοποιεί ο αυθεντικοποιητής τη διεπαφή `Nette\Security\IdentityHandler`. Αυτή έχει δύο μεθόδους: η `sleepIdentity()` καλείται πριν από την εγγραφή της ταυτότητας στον αποθηκευτικό χώρο και η `wakeupIdentity()` καλείται μετά την ανάγνωση της ταυτότητας. Οι μέθοδοι μπορούν να τροποποιήσουν τα περιεχόμενα της ταυτότητας ή να την αντικαταστήσουν με ένα νέο αντικείμενο που επιστρέφει. Η μέθοδος `wakeupIdentity()` μπορεί ακόμη και να επιστρέψει το `null`, το οποίο αποσυνδέει τον χρήστη. +Επιπλέον, μπορείτε να επηρεάσετε πώς ακριβώς θα γίνεται η αποθήκευση της ταυτότητας (*sleep*) και η επαναφορά (*wakeup*). Αρκεί ο αυθεντικοποιητής να υλοποιεί τη διεπαφή `Nette\Security\IdentityHandler`. Αυτό έχει δύο μεθόδους: η `sleepIdentity()` καλείται πριν από την εγγραφή της ταυτότητας στον αποθηκευτικό χώρο και η `wakeupIdentity()` μετά την ανάγνωσή της. Οι μέθοδοι μπορούν να τροποποιήσουν το περιεχόμενο της ταυτότητας, ή να την αντικαταστήσουν με ένα νέο αντικείμενο που επιστρέφουν. Η μέθοδος `wakeupIdentity()` μπορεί ακόμη και να επιστρέψει `null`, αποσυνδέοντας έτσι τον χρήστη. -Ως παράδειγμα, θα δείξουμε μια λύση σε μια συνηθισμένη ερώτηση σχετικά με τον τρόπο ενημέρωσης των ρόλων ταυτότητας αμέσως μετά την αποκατάσταση από μια συνεδρία. Στη μέθοδο `wakeupIdentity()` περνάμε τους τρέχοντες ρόλους στην ταυτότητα, π.χ. από τη βάση δεδομένων: +Ως παράδειγμα, θα δείξουμε μια λύση στη συχνή ερώτηση, πώς να ενημερώσετε τους ρόλους στην ταυτότητα αμέσως μετά τη φόρτωση από τη session. Στη μέθοδο `wakeupIdentity()` θα περάσουμε στην ταυτότητα τους τρέχοντες ρόλους π.χ. από τη βάση δεδομένων: ```php final class Authenticator implements @@ -185,25 +184,25 @@ final class Authenticator implements { public function sleepIdentity(IIdentity $identity): IIdentity { - // εδώ μπορείτε να αλλάξετε την ταυτότητα πριν την αποθήκευση μετά τη σύνδεση, - // αλλά δεν το χρειαζόμαστε αυτό τώρα + // εδώ μπορεί να τροποποιηθεί η ταυτότητα πριν από την εγγραφή στον αποθηκευτικό χώρο μετά τη σύνδεση, + // αλλά αυτό δεν το χρειαζόμαστε τώρα return $identity; } public function wakeupIdentity(IIdentity $identity): ?IIdentity { - // ενημέρωση ρόλων στην ταυτότητα + // ενημέρωση των ρόλων στην ταυτότητα $userId = $identity->getId(); $identity->setRoles($this->facade->getUserRoles($userId)); return $identity; } ``` -Και τώρα επιστρέφουμε στην αποθήκευση με βάση τα cookies. Σας επιτρέπει να δημιουργήσετε έναν ιστότοπο όπου οι χρήστες μπορούν να συνδεθούν χωρίς να χρειάζεται να χρησιμοποιούν συνεδρίες. Έτσι, δεν χρειάζεται να γράφει στο δίσκο. Εξάλλου, έτσι λειτουργεί ο ιστότοπος που διαβάζετε τώρα, συμπεριλαμβανομένου του φόρουμ. Σε αυτή την περίπτωση, η υλοποίηση του `IdentityHandler` είναι απαραίτητη. Θα αποθηκεύσουμε μόνο ένα τυχαίο token που αντιπροσωπεύει τον συνδεδεμένο χρήστη στο cookie. +Και τώρα θα επιστρέψουμε στον αποθηκευτικό χώρο που βασίζεται σε cookies. Σας επιτρέπει να δημιουργήσετε έναν ιστότοπο όπου οι χρήστες μπορούν να συνδεθούν χωρίς να χρειάζονται sessions. Δηλαδή, δεν χρειάζεται να γράφει στον δίσκο. Άλλωστε, έτσι λειτουργεί και ο ιστότοπος που διαβάζετε αυτή τη στιγμή, συμπεριλαμβανομένου του φόρουμ. Σε αυτήν την περίπτωση, η υλοποίηση του `IdentityHandler` είναι απαραίτητη. Στο cookie θα αποθηκεύουμε μόνο ένα τυχαίο token που αντιπροσωπεύει τον συνδεδεμένο χρήστη. -Έτσι, πρώτα ορίζουμε την επιθυμητή αποθήκευση στη διαμόρφωση χρησιμοποιώντας το `security › authentication › storage: cookie`. +Πρώτα λοιπόν, στη διαμόρφωση θα ορίσουμε τον επιθυμητό αποθηκευτικό χώρο χρησιμοποιώντας `security › authentication › storage: cookie`. -Θα προσθέσουμε μια στήλη `authtoken` στη βάση δεδομένων, στην οποία ο κάθε χρήστης θα έχει μια [εντελώς τυχαία, μοναδική και μη-αναγνωρίσιμη |utils:random] συμβολοσειρά επαρκούς μήκους (τουλάχιστον 13 χαρακτήρες). Το αποθετήριο `CookieStorage` αποθηκεύει μόνο την τιμή `$identity->getId()` στο cookie, οπότε στο `sleepIdentity()` αντικαθιστούμε την αρχική ταυτότητα με ένα proxy με `authtoken` στο ID, αντίθετα στη μέθοδο `wakeupIdentity()` επαναφέρουμε ολόκληρη την ταυτότητα από τη βάση δεδομένων σύμφωνα με το authtoken: +Στη βάση δεδομένων θα δημιουργήσουμε μια στήλη `authtoken`, στην οποία κάθε χρήστης θα έχει μια [εντελώς τυχαία, μοναδική και μη μαντέψιμη|utils:random] συμβολοσειρά επαρκούς μήκους (τουλάχιστον 13 χαρακτήρες). Ο αποθηκευτικός χώρος `CookieStorage` μεταφέρει στο cookie μόνο την τιμή `$identity->getId()`, οπότε στο `sleepIdentity()` θα αντικαταστήσουμε την αρχική ταυτότητα με μια αναπληρωματική με το `authtoken` στο ID, αντίθετα στη μέθοδο `wakeupIdentity()` με βάση το authtoken θα διαβάσουμε ολόκληρη την ταυτότητα από τη βάση δεδομένων: ```php final class Authenticator implements @@ -212,7 +211,7 @@ final class Authenticator implements public function authenticate(string $username, string $password): SimpleIdentity { $row = $this->db->fetch('SELECT * FROM user WHERE username = ?', $username); - // έλεγχος κωδικού πρόσβασης + // επαληθεύουμε τον κωδικό πρόσβασης ... // επιστρέφουμε την ταυτότητα με όλα τα δεδομένα από τη βάση δεδομένων return new SimpleIdentity($row->id, null, (array) $row); @@ -220,13 +219,13 @@ final class Authenticator implements public function sleepIdentity(IIdentity $identity): SimpleIdentity { - // επιστρέφουμε μια ταυτότητα μεσολάβησης, όπου το ID είναι το authtoken + // επιστρέφουμε μια αναπληρωματική ταυτότητα, όπου στο ID θα βρίσκεται το authtoken return new SimpleIdentity($identity->authtoken); } public function wakeupIdentity(IIdentity $identity): ?SimpleIdentity { - // αντικαθιστούμε την ταυτότητα μεσολάβησης με μια πλήρη ταυτότητα, όπως στην authenticate() + // αντικαθιστούμε την αναπληρωματική ταυτότητα με την πλήρη ταυτότητα, όπως στο authenticate() $row = $this->db->fetch('SELECT * FROM user WHERE authtoken = ?', $identity->getId()); return $row ? new SimpleIdentity($row->id, null, (array) $row) @@ -236,16 +235,16 @@ final class Authenticator implements ``` -Πολλαπλές ανεξάρτητες πιστοποιήσεις .[#toc-multiple-independent-authentications] -================================================================================ +Πολλαπλές ανεξάρτητες συνδέσεις +=============================== -Είναι δυνατό να έχετε πολλούς ανεξάρτητους συνδεδεμένους χρήστες σε έναν ιστότοπο και σε μία συνεδρία κάθε φορά. Για παράδειγμα, αν θέλουμε να έχουμε ξεχωριστό έλεγχο ταυτότητας για το frontend και το backend, απλά θα ορίσουμε ένα μοναδικό χώρο ονομάτων συνόδου για κάθε ένα από αυτά: +Είναι δυνατόν να έχουμε ταυτόχρονα πολλούς ανεξάρτητους συνδεδεμένους χρήστες στο πλαίσιο ενός ιστότοπου και μιας session. Εάν, για παράδειγμα, θέλουμε να έχουμε ξεχωριστή αυθεντικοποίηση για τη διαχείριση και το δημόσιο τμήμα στον ιστότοπο, αρκεί να ορίσουμε σε καθένα από αυτά το δικό του όνομα: ```php $user->getStorage()->setNamespace('backend'); ``` -Είναι απαραίτητο να έχετε κατά νου ότι αυτό πρέπει να οριστεί σε όλες τις θέσεις που ανήκουν στο ίδιο τμήμα. Όταν χρησιμοποιούμε παρουσιαστές, θα ορίσουμε το namespace στον κοινό πρόγονο - συνήθως το BasePresenter. Για να το κάνουμε αυτό θα επεκτείνουμε τη μέθοδο [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()]: +Είναι σημαντικό να θυμόμαστε να ορίζουμε πάντα το namespace σε όλα τα σημεία που ανήκουν στο συγκεκριμένο τμήμα. Εάν χρησιμοποιούμε presenters, θα ορίσουμε το namespace στον κοινό πρόγονο για το συγκεκριμένο τμήμα - συνήθως τον BasePresenter. Θα το κάνουμε επεκτείνοντας τη μέθοδο [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()]: ```php public function checkRequirements($element): void @@ -256,10 +255,10 @@ public function checkRequirements($element): void ``` -Πολλαπλοί επαληθευτές .[#toc-multiple-authenticators] ------------------------------------------------------ +Πολλαπλοί αυθεντικοποιητές +-------------------------- -Ο διαχωρισμός μιας εφαρμογής σε τμήματα με ανεξάρτητη αυθεντικοποίηση απαιτεί γενικά διαφορετικούς αυθεντικοποιητές. Ωστόσο, η καταχώρηση δύο κλάσεων που υλοποιούν Authenticator σε υπηρεσίες config θα προκαλούσε σφάλμα επειδή η Nette δεν θα ήξερε ποια από αυτές θα έπρεπε να [συνδεθεί αυτόματα |dependency-injection:autowiring] με το αντικείμενο `Nette\Security\User`. Γι' αυτό πρέπει να περιορίσουμε την αυτόματη σύνδεση για αυτούς με το `autowired: self`, ώστε να ενεργοποιείται μόνο όταν ζητείται συγκεκριμένα η κλάση τους: +Η διαίρεση της εφαρμογής σε τμήματα με ανεξάρτητη σύνδεση συνήθως απαιτεί επίσης διαφορετικούς αυθεντικοποιητές. Ωστόσο, εάν καταχωρούσαμε δύο κλάσεις που υλοποιούν το Authenticator στη διαμόρφωση των υπηρεσιών, το Nette δεν θα ήξερε ποιον από αυτούς να αντιστοιχίσει αυτόματα στο αντικείμενο `Nette\Security\User` και θα εμφάνιζε σφάλμα. Γι' αυτό πρέπει να περιορίσουμε το [autowiring |dependency-injection:autowiring] για τους αυθεντικοποιητές έτσι ώστε να λειτουργεί μόνο όταν κάποιος ζητήσει μια συγκεκριμένη κλάση, π.χ. FrontAuthenticator, κάτι που επιτυγχάνεται με την επιλογή `autowired: self`: ```neon services: @@ -278,7 +277,7 @@ class SignPresenter extends Nette\Application\UI\Presenter } ``` -Πρέπει να ορίσουμε τον authenticator μας στο αντικείμενο User μόνο πριν από την κλήση της μεθόδου [login() |api:Nette\Security\User::login()], που τυπικά σημαίνει στο callback της φόρμας σύνδεσης: +Θα ορίσουμε τον αυθεντικοποιητή του αντικειμένου User πριν καλέσουμε τη μέθοδο [login() |api:Nette\Security\User::login()], οπότε συνήθως στον κώδικα της φόρμας, που τον συνδέει: ```php $form->onSuccess[] = function (Form $form, \stdClass $data) { diff --git a/security/el/authorization.texy b/security/el/authorization.texy index c6cccbb1b9..b7deca26f8 100644 --- a/security/el/authorization.texy +++ b/security/el/authorization.texy @@ -1,48 +1,48 @@ -Έλεγχος πρόσβασης (εξουσιοδότηση) +Επαλήθευση αδειών (Εξουσιοδότηση) ********************************* .[perex] -Η εξουσιοδότηση καθορίζει αν ένας χρήστης έχει επαρκή προνόμια, για παράδειγμα, για να έχει πρόσβαση σε έναν συγκεκριμένο πόρο ή να εκτελέσει μια ενέργεια. Η εξουσιοδότηση προϋποθέτει προηγούμενη επιτυχή αυθεντικοποίηση, δηλαδή ότι ο χρήστης είναι συνδεδεμένος. +Η εξουσιοδότηση διαπιστώνει εάν ο χρήστης έχει επαρκή δικαιώματα, για παράδειγμα, για πρόσβαση σε έναν συγκεκριμένο πόρο ή για την εκτέλεση κάποιας ενέργειας. Η εξουσιοδότηση προϋποθέτει προηγούμενη επιτυχή αυθεντικοποίηση, δηλαδή ότι ο χρήστης είναι συνδεδεμένος. -→ [Εγκατάσταση και απαιτήσεις |@home#Installation] +→ [Εγκατάσταση και απαιτήσεις |@home#Εγκατάσταση] -Στα παραδείγματα, θα χρησιμοποιήσουμε ένα αντικείμενο της κλάσης [api:Nette\Security\User], το οποίο αντιπροσωπεύει τον τρέχοντα χρήστη και το οποίο λαμβάνετε περνώντας το με τη χρήση [dependency injection |dependency-injection:passing-dependencies]. Στις παρουσιάσεις απλά καλέστε το `$user = $this->getUser()`. +Στα παραδείγματα θα χρησιμοποιούμε το αντικείμενο της κλάσης [api:Nette\Security\User], το οποίο αντιπροσωπεύει τον τρέχοντα χρήστη και στο οποίο μπορείτε να αποκτήσετε πρόσβαση ζητώντας να σας περαστεί μέσω [dependency injection |dependency-injection:passing-dependencies]. Στους presenters αρκεί απλώς να καλέσετε `$user = $this->getUser()`. -Για πολύ απλές ιστοσελίδες με διαχείριση, όπου τα δικαιώματα των χρηστών δεν διακρίνονται, είναι δυνατόν να χρησιμοποιήσετε την ήδη γνωστή μέθοδο ως κριτήριο εξουσιοδότησης `isLoggedIn()`. Με άλλα λόγια: μόλις ένας χρήστης συνδεθεί, έχει δικαιώματα σε όλες τις ενέργειες και το αντίστροφο. +Σε πολύ απλούς ιστότοπους με διαχείριση, όπου δεν διακρίνονται τα δικαιώματα των χρηστών, είναι δυνατόν να χρησιμοποιηθεί ως κριτήριο εξουσιοδότησης η ήδη γνωστή μέθοδος `isLoggedIn()`. Με άλλα λόγια: μόλις ο χρήστης συνδεθεί, έχει όλα τα δικαιώματα και αντίστροφα. ```php -if ($user->isLoggedIn()) { // είναι συνδεδεμένος ο χρήστης; - deleteItem(); // Αν ναι, μπορεί να διαγράψει ένα στοιχείο +if ($user->isLoggedIn()) { // είναι ο χρήστης συνδεδεμένος; + deleteItem(); // τότε έχει δικαίωμα για την ενέργεια } ``` -Ρόλοι .[#toc-roles] -------------------- +Ρόλοι +----- -Ο σκοπός των ρόλων είναι να προσφέρουν μια πιο ακριβή διαχείριση δικαιωμάτων και να παραμένουν ανεξάρτητοι από το όνομα χρήστη. Μόλις ο χρήστης συνδεθεί, του ανατίθεται ένας ή περισσότεροι ρόλοι. Οι ίδιοι οι ρόλοι μπορούν να είναι απλές συμβολοσειρές, για παράδειγμα, `admin`, `member`, `guest`, κ.λπ. Καθορίζονται στο δεύτερο όρισμα του κατασκευαστή `SimpleIdentity`, είτε ως συμβολοσειρά είτε ως πίνακας. +Ο σκοπός των ρόλων είναι να προσφέρουν ακριβέστερο έλεγχο των δικαιωμάτων και να παραμείνουν ανεξάρτητοι από το όνομα χρήστη. Σε κάθε χρήστη, αμέσως κατά τη σύνδεση, αναθέτουμε έναν ή περισσότερους ρόλους, με τους οποίους θα λειτουργεί. Οι ρόλοι μπορεί να είναι απλές συμβολοσειρές όπως `admin`, `member`, `guest`, κ.λπ. Αναφέρονται ως δεύτερη παράμετρος του κατασκευαστή `SimpleIdentity`, είτε ως συμβολοσειρά είτε ως πίνακας συμβολοσειρών - ρόλων. -Ως κριτήριο εξουσιοδότησης, θα χρησιμοποιήσουμε τώρα τη μέθοδο `isInRole()`, η οποία ελέγχει αν ο χρήστης ανήκει στον συγκεκριμένο ρόλο: +Ως κριτήριο εξουσιοδότησης τώρα θα χρησιμοποιήσουμε τη μέθοδο `isInRole()`, η οποία αποκαλύπτει αν ο χρήστης λειτουργεί με τον δεδομένο ρόλο: ```php -if ($user->isInRole('admin')) { // έχει εκχωρηθεί ο ρόλος του διαχειριστή στον χρήστη; - deleteItem(); // αν ναι, μπορεί να διαγράψει ένα στοιχείο +if ($user->isInRole('admin')) { // είναι ο χρήστης στον ρόλο του admin; + deleteItem(); // τότε έχει δικαίωμα για την ενέργεια } ``` -Όπως ήδη γνωρίζετε, η αποσύνδεση του χρήστη δεν διαγράφει την ταυτότητά του. Έτσι, η μέθοδος `getIdentity()` εξακολουθεί να επιστρέφει το αντικείμενο `SimpleIdentity`, συμπεριλαμβανομένων όλων των χορηγηθέντων ρόλων. Το Nette Framework τηρεί την αρχή "λιγότερος κώδικας, περισσότερη ασφάλεια", οπότε όταν ελέγχετε τους ρόλους, δεν χρειάζεται να ελέγχετε αν ο χρήστης είναι επίσης συνδεδεμένος. Η μέθοδος `isInRole()` λειτουργεί με **αποτελεσματικούς ρόλους**, δηλαδή αν ο χρήστης είναι συνδεδεμένος, χρησιμοποιούνται οι ρόλοι που έχουν ανατεθεί στην ταυτότητα, ενώ αν δεν είναι συνδεδεμένος, χρησιμοποιείται αντ' αυτού ένας αυτόματος ειδικός ρόλος `guest`. +Όπως ήδη γνωρίζετε, μετά την αποσύνδεση του χρήστη, η ταυτότητά του δεν χρειάζεται να διαγραφεί. Δηλαδή, η μέθοδος `getIdentity()` εξακολουθεί να επιστρέφει το αντικείμενο `SimpleIdentity`, συμπεριλαμβανομένων όλων των εκχωρημένων ρόλων. Το Nette Framework ακολουθεί την αρχή «less code, more security», όπου λιγότερη γραφή οδηγεί σε πιο ασφαλή κώδικα, γι' αυτό κατά τον έλεγχο των ρόλων δεν χρειάζεται να επαληθεύσετε επιπλέον αν ο χρήστης είναι συνδεδεμένος. Η μέθοδος `isInRole()` λειτουργεί με **ενεργούς ρόλους,** δηλαδή, αν ο χρήστης είναι συνδεδεμένος, βασίζεται στους ρόλους που αναφέρονται στην ταυτότητα, αν δεν είναι συνδεδεμένος, έχει αυτόματα τον ειδικό ρόλο `guest`. -Εξουσιοδότης .[#toc-authorizator] ---------------------------------- +Εξουσιοδοτητής +-------------- -Εκτός από τους ρόλους, θα εισαγάγουμε τους όρους πόρος και λειτουργία: +Εκτός από τους ρόλους, θα εισαγάγουμε επίσης τις έννοιες του πόρου και της λειτουργίας: -- **ρόλος** είναι μια ιδιότητα χρήστη - για παράδειγμα συντονιστής, συντάκτης, επισκέπτης, εγγεγραμμένος χρήστης, διαχειριστής, ... -- **πόρος** είναι μια λογική μονάδα της εφαρμογής - άρθρο, σελίδα, χρήστης, στοιχείο μενού, δημοσκόπηση, παρουσιαστής, ... -- **λειτουργία** είναι μια συγκεκριμένη δραστηριότητα, την οποία ο χρήστης μπορεί ή δεν μπορεί να κάνει με τον *πόρο* - προβολή, επεξεργασία, διαγραφή, ψηφοφορία, ... +- **ρόλος** είναι μια ιδιότητα του χρήστη - π.χ. συντονιστής, συντάκτης, επισκέπτης, εγγεγραμμένος χρήστης, διαχειριστής... +- **πόρος** (*resource*) είναι κάποιο λογικό στοιχείο του ιστότοπου - άρθρο, σελίδα, χρήστης, στοιχείο μενού, δημοσκόπηση, presenter, ... +- **λειτουργία** (*operation*) είναι κάποια συγκεκριμένη δραστηριότητα που ο χρήστης μπορεί ή δεν μπορεί να κάνει με τον πόρο - για παράδειγμα διαγραφή, επεξεργασία, δημιουργία, ψήφος, ... -Ένας εξουσιοδοτητής είναι ένα αντικείμενο που αποφασίζει αν ένας συγκεκριμένος *ρόλος* έχει δικαίωμα να εκτελέσει μια συγκεκριμένη *λειτουργία* με συγκεκριμένο *πόρο*. Είναι ένα αντικείμενο που υλοποιεί τη διεπαφή [api:Nette\Security\Authorizator] με μία μόνο μέθοδο `isAllowed()`: +Ο εξουσιοδοτητής είναι ένα αντικείμενο που αποφασίζει αν ο δεδομένος *role* έχει άδεια να εκτελέσει μια συγκεκριμένη *operation* σε έναν συγκεκριμένο *resource*. Πρόκειται για ένα αντικείμενο που υλοποιεί τη διεπαφή [api:Nette\Security\Authorizator] με μία μόνο μέθοδο `isAllowed()`: ```php class MyAuthorizator implements Nette\Security\Authorizator @@ -63,50 +63,50 @@ class MyAuthorizator implements Nette\Security\Authorizator } ``` -Προσθέτουμε τον authorizator στη διαμόρφωση [ως υπηρεσία |dependency-injection:services] του DI container: +Θα προσθέσουμε τον εξουσιοδοτητή στη διαμόρφωση [ως υπηρεσία|dependency-injection:services] του DI container: ```neon services: - MyAuthorizator ``` -Και το παρακάτω είναι ένα παράδειγμα χρήσης. Σημειώστε ότι αυτή τη φορά καλούμε τη μέθοδο `Nette\Security\User::isAllowed()`, όχι αυτή του εξουσιοδοτητή, οπότε δεν υπάρχει πρώτη παράμετρος `$role`. Η μέθοδος αυτή καλεί διαδοχικά το `MyAuthorizator::isAllowed()` για όλους τους ρόλους χρηστών και επιστρέφει true αν τουλάχιστον ένας από αυτούς έχει δικαίωμα. +Και ακολουθεί ένα παράδειγμα χρήσης. Προσοχή, αυτή τη φορά καλούμε τη μέθοδο `Nette\Security\User::isAllowed()`, όχι τον εξουσιοδοτητή, οπότε δεν υπάρχει η πρώτη παράμετρος `$role`. Αυτή η μέθοδος καλεί την `MyAuthorizator::isAllowed()` διαδοχικά για όλους τους ρόλους του χρήστη και επιστρέφει true, αν τουλάχιστον ένας από αυτούς έχει άδεια. ```php -if ($user->isAllowed('file')) { // επιτρέπεται στον χρήστη να κάνει τα πάντα με τον πόρο 'file'; +if ($user->isAllowed('file')) { // μπορεί ο χρήστης να κάνει οτιδήποτε με τον πόρο 'file'; useFile(); } -if ($user->isAllowed('file', 'delete')) { // επιτρέπεται στον χρήστη να διαγράψει τον πόρο 'αρχείο'; +if ($user->isAllowed('file', 'delete')) { // μπορεί πάνω στον πόρο 'file' να εκτελέσει 'delete'; deleteFile(); } ``` -Και τα δύο ορίσματα είναι προαιρετικά και η προεπιλεγμένη τιμή τους σημαίνει *όλα*. +Και οι δύο παράμετροι είναι προαιρετικές, η προεπιλεγμένη τιμή `null` σημαίνει *οτιδήποτε*. -Άδεια ACL .[#toc-permission-acl] --------------------------------- +Permission ACL +-------------- -Η Nette έρχεται με μια ενσωματωμένη υλοποίηση του authorizer, την κλάση [api:Nette\Security\Permission], η οποία προσφέρει ένα ελαφρύ και ευέλικτο επίπεδο ACL (Access Control List) για έλεγχο δικαιωμάτων και πρόσβασης. Όταν εργαζόμαστε με αυτή την κλάση, ορίζουμε ρόλους, πόρους και μεμονωμένα δικαιώματα. Και οι ρόλοι και οι πόροι μπορούν να σχηματίζουν ιεραρχίες. Για να εξηγήσουμε, θα δείξουμε ένα παράδειγμα μιας εφαρμογής ιστού: +Το Nette έρχεται με μια ενσωματωμένη υλοποίηση του εξουσιοδοτητή, την κλάση [api:Nette\Security\Permission] που παρέχει στον προγραμματιστή ένα ελαφρύ και ευέλικτο επίπεδο ACL (Access Control List) για τον έλεγχο των αδειών και των προσβάσεων. Η εργασία με αυτήν συνίσταται στον ορισμό ρόλων, πόρων και μεμονωμένων αδειών. Οι ρόλοι και οι πόροι επιτρέπουν τη δημιουργία ιεραρχιών. Για να το εξηγήσουμε, θα δείξουμε ένα παράδειγμα web εφαρμογής: -- `guest`: επισκέπτης που δεν είναι συνδεδεμένος, επιτρέπεται να διαβάζει και να περιηγείται στο δημόσιο τμήμα του ιστού, δηλαδή να διαβάζει άρθρα, να σχολιάζει και να ψηφίζει σε δημοσκοπήσεις. -- `registered`: συνδεδεμένος χρήστης, ο οποίος μπορεί επιπλέον να δημοσιεύει σχόλια. -- `admin`: μπορεί να διαχειρίζεται άρθρα, σχόλια και ψηφοφορίες. +- `guest`: μη συνδεδεμένος επισκέπτης, ο οποίος μπορεί να διαβάζει και να περιηγείται στο δημόσιο τμήμα του ιστότοπου, δηλ. να διαβάζει άρθρα, σχόλια και να ψηφίζει σε δημοσκοπήσεις +- `registered`: συνδεδεμένος εγγεγραμμένος χρήστης, ο οποίος επιπλέον μπορεί να σχολιάζει +- `admin`: μπορεί να διαχειρίζεται άρθρα, σχόλια και δημοσκοπήσεις -Έτσι, έχουμε ορίσει ορισμένους ρόλους (`guest`, `registered` και `admin`) και έχουμε αναφέρει πόρους (`article`, `comments`, `poll`), στους οποίους οι χρήστες μπορούν να έχουν πρόσβαση ή να κάνουν ενέργειες (`view`, `vote`, `add`, `edit`). +Ορίσαμε λοιπόν ορισμένους ρόλους (`guest`, `registered` και `admin`) και αναφέραμε πόρους (`article`, `comment`, `poll`), στους οποίους οι χρήστες με κάποιο ρόλο μπορούν να έχουν πρόσβαση ή να εκτελούν ορισμένες λειτουργίες (`view`, `vote`, `add`, `edit`). -Δημιουργούμε μια περίπτωση της κλάσης Permission και ορίζουμε τους **ρόλους**. Είναι δυνατή η χρήση της κληρονομικότητας των ρόλων, η οποία εξασφαλίζει ότι, για παράδειγμα, ένας χρήστης με ρόλο `admin` μπορεί να κάνει ό,τι μπορεί να κάνει ένας απλός επισκέπτης του ιστοτόπου (και φυσικά περισσότερα). +Θα δημιουργήσουμε μια παρουσία της κλάσης Permission και θα ορίσουμε τους **ρόλους**. Μπορούμε να χρησιμοποιήσουμε την λεγόμενη κληρονομικότητα ρόλων, η οποία εξασφαλίζει ότι π.χ. ένας χρήστης με ρόλο διαχειριστή (`admin`) μπορεί να κάνει και ό,τι ένας απλός επισκέπτης του ιστότοπου (και φυσικά περισσότερα). ```php $acl = new Nette\Security\Permission; $acl->addRole('guest'); -$acl->addRole('registered', 'guest'); // 'registered' κληρονομεί από το 'guest' -$acl->addRole('admin', 'registered'); // και το 'admin' κληρονομεί από το 'registered' +$acl->addRole('registered', 'guest'); // ο 'registered' κληρονομεί από τον 'guest' +$acl->addRole('admin', 'registered'); // και από αυτόν κληρονομεί ο 'admin' ``` -Τώρα θα ορίσουμε μια λίστα με **πόρους** στους οποίους οι χρήστες μπορούν να έχουν πρόσβαση: +Τώρα θα ορίσουμε και τη λίστα των **πόρων**, στους οποίους οι χρήστες μπορούν να έχουν πρόσβαση. ```php $acl->addResource('article'); @@ -114,49 +114,49 @@ $acl->addResource('comment'); $acl->addResource('poll'); ``` -Οι πόροι μπορούν επίσης να χρησιμοποιήσουν κληρονομικότητα, για παράδειγμα, μπορούμε να προσθέσουμε το `$acl->addResource('perex', 'article')`. +Και οι πόροι μπορούν να χρησιμοποιούν κληρονομικότητα, θα ήταν δυνατό για παράδειγμα να ορίσουμε `$acl->addResource('perex', 'article')`. -Και τώρα το πιο σημαντικό πράγμα. Θα ορίσουμε μεταξύ τους **κανόνες** που καθορίζουν ποιος μπορεί να κάνει τι: +Και τώρα το πιο σημαντικό. Θα ορίσουμε μεταξύ τους κανόνες που καθορίζουν ποιος μπορεί να κάνει τι με τι: ```php -// όλα αρνούνται τώρα +// αρχικά κανείς δεν μπορεί να κάνει τίποτα -// αφήστε τον επισκέπτη να βλέπει άρθρα, σχόλια και δημοσκοπήσεις +// ας μπορεί ο guest να βλέπει άρθρα, σχόλια και δημοσκοπήσεις $acl->allow('guest', ['article', 'comment', 'poll'], 'view'); -// και επίσης να ψηφίζει στις δημοσκοπήσεις +// και στις δημοσκοπήσεις επιπλέον να ψηφίζει $acl->allow('guest', 'poll', 'vote'); -// ο εγγεγραμμένος κληρονομεί τα δικαιώματα από τον επισκέπτη, θα του επιτρέψουμε επίσης να σχολιάζει +// ο εγγεγραμμένος κληρονομεί δικαιώματα από τον guest, θα του δώσουμε επιπλέον το δικαίωμα να σχολιάζει $acl->allow('registered', 'comment', 'add'); -// ο διαχειριστής μπορεί να βλέπει και να επεξεργάζεται τα πάντα +// ο διαχειριστής μπορεί να βλέπει και να επεξεργάζεται οτιδήποτε $acl->allow('admin', $acl::All, ['view', 'edit', 'add']); ``` -Τι γίνεται αν θέλουμε να **αποτρέψουμε** την πρόσβαση κάποιου σε έναν πόρο; +Τι γίνεται αν θέλουμε να **εμποδίσουμε** κάποιον από την πρόσβαση σε έναν συγκεκριμένο πόρο; ```php -// ο διαχειριστής δεν μπορεί να επεξεργαστεί τις δημοσκοπήσεις, αυτό θα ήταν αντιδεοντολογικό. +// ο διαχειριστής δεν μπορεί να επεξεργάζεται δημοσκοπήσεις, αυτό θα ήταν αντιδημοκρατικό $acl->deny('admin', 'poll', 'edit'); ``` -Τώρα, όταν έχουμε δημιουργήσει το σύνολο των κανόνων, μπορούμε απλά να θέσουμε τα ερωτήματα εξουσιοδότησης: +Τώρα, που έχουμε δημιουργήσει τη λίστα των κανόνων, μπορούμε απλά να θέσουμε ερωτήματα εξουσιοδότησης: ```php -// μπορεί ο επισκέπτης να δει τα άρθρα; +// μπορεί ο guest να βλέπει άρθρα; $acl->isAllowed('guest', 'article', 'view'); // true -// μπορεί ο επισκέπτης να επεξεργαστεί ένα άρθρο; +// μπορεί ο guest να επεξεργάζεται άρθρα; $acl->isAllowed('guest', 'article', 'edit'); // false -// μπορεί ο επισκέπτης να ψηφίσει σε δημοσκοπήσεις; +// μπορεί ο guest να ψηφίζει σε δημοσκοπήσεις; $acl->isAllowed('guest', 'poll', 'vote'); // true -// μπορεί ο επισκέπτης να προσθέσει σχόλια; +// μπορεί ο guest να σχολιάζει; $acl->isAllowed('guest', 'comment', 'add'); // false ``` -Το ίδιο ισχύει και για έναν εγγεγραμμένο χρήστη, αλλά μπορεί επίσης να σχολιάσει: +Το ίδιο ισχύει και για τον εγγεγραμμένο χρήστη, ο οποίος όμως μπορεί και να σχολιάζει: ```php $acl->isAllowed('registered', 'article', 'view'); // true @@ -164,7 +164,7 @@ $acl->isAllowed('registered', 'comment', 'add'); // true $acl->isAllowed('registered', 'comment', 'edit'); // false ``` -Ο διαχειριστής μπορεί να επεξεργαστεί τα πάντα εκτός από τις δημοσκοπήσεις: +Ο διαχειριστής μπορεί να επεξεργάζεται τα πάντα, εκτός από τις δημοσκοπήσεις: ```php $acl->isAllowed('admin', 'poll', 'vote'); // true @@ -172,7 +172,7 @@ $acl->isAllowed('admin', 'poll', 'edit'); // false $acl->isAllowed('admin', 'comment', 'edit'); // true ``` -και μπορούμε να αφήσουμε την απόφαση στο δικό μας callback, στο οποίο μεταβιβάζονται όλες οι παράμετροι: +Οι άδειες μπορούν επίσης να αξιολογούνται δυναμικά και μπορούμε να αφήσουμε την απόφαση σε ένα δικό μας callback, στο οποίο θα περαστούν όλες οι παράμετροι: ```php $assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { @@ -182,7 +182,7 @@ $assertion = function (Permission $acl, string $role, string $resource, string $ $acl->allow('registered', 'comment', null, $assertion); ``` -Πώς όμως να λύσουμε μια κατάσταση όπου τα ονόματα των ρόλων και των πόρων δεν αρκούν, δηλαδή θα θέλαμε να ορίσουμε ότι, για παράδειγμα, ένας ρόλος `registered` μπορεί να επεξεργαστεί έναν πόρο `article` μόνο αν είναι ο συγγραφέας του; Θα χρησιμοποιήσουμε αντικείμενα αντί για συμβολοσειρές, ο ρόλος θα είναι το αντικείμενο [api:Nette\Security\Role] και η πηγή [api:Nette\Security\Resource]. Οι μέθοδοι τους `getRoleId()` και `getResourceId()` θα επιστρέφουν τις αρχικές συμβολοσειρές: +Αλλά πώς να αντιμετωπίσουμε, για παράδειγμα, μια κατάσταση όπου δεν αρκούν μόνο τα ονόματα των ρόλων και των πόρων, αλλά θα θέλαμε να ορίσουμε ότι, για παράδειγμα, ο ρόλος `registered` μπορεί να επεξεργαστεί τον πόρο `article` μόνο αν είναι ο συγγραφέας του; Αντί για συμβολοσειρές θα χρησιμοποιήσουμε αντικείμενα, ο ρόλος θα είναι αντικείμενο [api:Nette\Security\Role] και ο πόρος [api:Nette\Security\Resource]. Οι μέθοδοί τους `getRoleId()` αντίστοιχα `getResourceId()` θα επιστρέφουν τις αρχικές συμβολοσειρές: ```php class Registered implements Nette\Security\Role @@ -207,19 +207,19 @@ class Article implements Nette\Security\Resource } ``` -Και τώρα ας δημιουργήσουμε έναν κανόνα: +Και τώρα θα δημιουργήσουμε τον κανόνα: ```php $assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { - $role = $acl->getQueriedRole(); // αντικείμενο Εγγεγραμμένος - $resource = $acl->getQueriedResource(); // αντικείμενο Άρθρο + $role = $acl->getQueriedRole(); // αντικείμενο Registered + $resource = $acl->getQueriedResource(); // αντικείμενο Article return $role->id === $resource->authorId; }; $acl->allow('registered', 'article', 'edit', $assertion); ``` -Η ACL αναζητείται με τη διαβίβαση αντικειμένων: +Και το ερώτημα στο ACL θα γίνει περνώντας τα αντικείμενα: ```php $user = new Registered(/* ... */); @@ -227,7 +227,7 @@ $article = new Article(/* ... */); $acl->isAllowed($user, $article, 'edit'); ``` -Ένας ρόλος μπορεί να κληρονομήσει έναν ή περισσότερους άλλους ρόλους. Αλλά τι συμβαίνει, αν ένας πρόγονος έχει επιτρέψει μια συγκεκριμένη ενέργεια και ο άλλος την έχει αρνηθεί; Τότε μπαίνει στο παιχνίδι το *βάρος του ρόλου* - ο τελευταίος ρόλος στη σειρά των ρόλων που κληρονομεί έχει το μεγαλύτερο βάρος, ο πρώτος το μικρότερο: +Ένας ρόλος μπορεί να κληρονομεί από έναν άλλο ρόλο ή από πολλούς ρόλους. Τι συμβαίνει όμως αν ένας πρόγονος έχει απαγορευμένη την ενέργεια και ο άλλος επιτρεπόμενη; Ποια θα είναι τα δικαιώματα του απογόνου; Καθορίζεται από το βάρος του ρόλου - ο τελευταίος αναφερόμενος ρόλος στη λίστα των προγόνων έχει το μεγαλύτερο βάρος, ο πρώτος αναφερόμενος ρόλος το μικρότερο. Είναι πιο κατανοητό από το παράδειγμα: ```php $acl = new Nette\Security\Permission; @@ -239,22 +239,22 @@ $acl->addResource('backend'); $acl->allow('admin', 'backend'); $acl->deny('guest', 'backend'); -// παράδειγμα A: ο ρόλος admin έχει μικρότερη βαρύτητα από τον ρόλο guest +// περίπτωση Α: ο ρόλος admin έχει μικρότερο βάρος από τον ρόλο guest $acl->addRole('john', ['admin', 'guest']); $acl->isAllowed('john', 'backend'); // false -// παράδειγμα Β: ο ρόλος admin έχει μεγαλύτερη βαρύτητα από τον ρόλο guest +// περίπτωση Β: ο ρόλος admin έχει μεγαλύτερο βάρος από τον guest $acl->addRole('mary', ['guest', 'admin']); $acl->isAllowed('mary', 'backend'); // true ``` -Οι ρόλοι και οι πόροι μπορούν επίσης να αφαιρεθούν (`removeRole()`, `removeResource()`), οι κανόνες μπορούν επίσης να ανατραπούν (`removeAllow()`, `removeDeny()`). Ο πίνακας όλων των ρόλων άμεσων γονέων επιστρέφει `getRoleParents()`. Το αν δύο οντότητες κληρονομούν η μία από την άλλη επιστρέφει `roleInheritsFrom()` και `resourceInheritsFrom()`. +Οι ρόλοι και οι πόροι μπορούν επίσης να αφαιρεθούν (`removeRole()`, `removeResource()`), μπορούν να αναιρεθούν και οι κανόνες (`removeAllow()`, `removeDeny()`). Ο πίνακας όλων των άμεσων γονικών ρόλων επιστρέφεται από την `getRoleParents()`, το αν δύο οντότητες κληρονομούν η μία από την άλλη επιστρέφεται από τις `roleInheritsFrom()` και `resourceInheritsFrom()`. -Προσθήκη ως υπηρεσία .[#toc-add-as-a-service] ---------------------------------------------- +Προσθήκη ως υπηρεσίες +--------------------- -Πρέπει να προσθέσουμε το ACL που δημιουργήσαμε στη διαμόρφωση ως υπηρεσία ώστε να μπορεί να χρησιμοποιηθεί από το αντικείμενο `$user`, δηλαδή για να μπορούμε να το χρησιμοποιήσουμε στον κώδικα για παράδειγμα `$user->isAllowed('article', 'view')`. Για το σκοπό αυτό, θα γράψουμε ένα εργοστάσιο για αυτό: +Το ACL που δημιουργήσαμε πρέπει να το περάσουμε στη διαμόρφωση ως υπηρεσία, ώστε να αρχίσει να το χρησιμοποιεί το αντικείμενο `$user`, δηλαδή ώστε να είναι δυνατό να χρησιμοποιούμε στον κώδικα π.χ. `$user->isAllowed('article', 'view')`. Γι' αυτόν τον σκοπό θα γράψουμε ένα factory γι' αυτό: ```php namespace App\Model; @@ -279,7 +279,7 @@ services: - App\Model\AuthorizatorFactory::create ``` - `startup()`, για παράδειγμα: +Στους presenters τότε μπορείτε να επαληθεύσετε τα δικαιώματα για παράδειγμα στη μέθοδο `startup()`: ```php protected function startup() diff --git a/security/el/configuration.texy b/security/el/configuration.texy index 301e058873..bc553faaad 100644 --- a/security/el/configuration.texy +++ b/security/el/configuration.texy @@ -1,71 +1,85 @@ -Διαμόρφωση ελέγχου πρόσβασης -**************************** +Διαμόρφωση αδειών πρόσβασης +*************************** .[perex] Επισκόπηση των επιλογών διαμόρφωσης για το Nette Security. -Αν δεν χρησιμοποιείτε ολόκληρο το πλαίσιο, αλλά μόνο αυτή τη βιβλιοθήκη, διαβάστε [πώς να φορτώσετε τη διαμόρφωση |bootstrap:]. +Αν δεν χρησιμοποιείτε ολόκληρο το framework, αλλά μόνο αυτή τη βιβλιοθήκη, διαβάστε [πώς να φορτώσετε τη διαμόρφωση|bootstrap:]. -Μπορείτε να ορίσετε μια λίστα χρηστών στη διαμόρφωση για τη δημιουργία ενός [απλού αυθεντικοποιητή |authentication] (`Nette\Security\SimpleAuthenticator`). Επειδή οι κωδικοί πρόσβασης είναι αναγνώσιμοι στη διαμόρφωση, αυτή η λύση είναι μόνο για δοκιμαστικούς σκοπούς. +Στη διαμόρφωση μπορεί να οριστεί μια λίστα χρηστών, δημιουργώντας έτσι έναν [απλό authenticator|authentication] (`Nette\Security\SimpleAuthenticator`). Επειδή στη διαμόρφωση οι κωδικοί πρόσβασης αναφέρονται σε αναγνώσιμη μορφή, αυτή η λύση είναι κατάλληλη μόνο για δοκιμαστικούς σκοπούς. ```neon security: - # εμφανίζει πίνακα χρήστη στη γραμμή Tracy; - debugger: ... # (bool) προεπιλογή true + # εμφάνιση του πίνακα χρήστη στο Tracy Bar; + debugger: ... # (bool) προεπιλογή είναι true users: # όνομα: κωδικός πρόσβασης - johndoe: secret123 + frantisek: secretpassword - # όνομα, κωδικός πρόσβασης, ρόλος και άλλα δεδομένα που είναι διαθέσιμα στην ταυτότητα - janedoe: - password: secret123 + # όνομα, κωδικός πρόσβασης, ρόλος και άλλα δεδομένα διαθέσιμα στην ταυτότητα + dobrota: + password: secretpassword roles: [admin] data: ... ``` -Μπορείτε επίσης να ορίσετε ρόλους και πόρους για να δημιουργήσετε μια βάση για τον [εξουσιοδοτητή |authorization] (`Nette\Security\Permission`): +Επιπλέον, μπορούν να οριστούν ρόλοι και πόροι, δημιουργώντας έτσι τη βάση για έναν [authorizator|authorization] (`Nette\Security\Permission`): ```neon security: roles: guest: - registered: [guest] # registered κληρονομεί από το guest - admin: [registered] # και το admin κληρονομεί από το registered + registered: [guest] # ο registered κληρονομεί από τον guest + admin: [registered] # και από αυτόν κληρονομεί ο admin resources: article: - comment: [article] # resource κληρονομεί από article + comment: [article] # ο πόρος κληρονομεί από τον article poll: ``` -Αποθήκευση χρηστών .[#toc-user-storage] ---------------------------------------- +Αποθηκευτικός χώρος +------------------- -Μπορείτε να ρυθμίσετε τον τρόπο αποθήκευσης των πληροφοριών σχετικά με τον συνδεδεμένο χρήστη: +Μπορεί να διαμορφωθεί πώς θα διατηρούνται οι πληροφορίες για τον συνδεδεμένο χρήστη: ```neon security: authentication: - # μετά από πόσο χρόνο αδράνειας ο χρήστης θα αποσυνδεθεί - expiration: 30 minutes # (string) η προεπιλογή δεν έχει οριστεί + # μετά από πόσο χρόνο αδράνειας θα αποσυνδεθεί ο χρήστης + expiration: 30 minutes # (string) προεπιλογή είναι μη ορισμένο - # πού θα αποθηκεύονται οι πληροφορίες σχετικά με τον συνδεδεμένο χρήστη - storage: session # (session|cookie) η προεπιλογή είναι session + # πού θα αποθηκεύονται οι πληροφορίες για τον συνδεδεμένο χρήστη + storage: session # (session|cookie) προεπιλογή είναι session ``` -Εάν επιλέξετε το `cookie` ως αποθετήριο, μπορείτε επίσης να ορίσετε τις ακόλουθες επιλογές: +Εάν επιλέξετε ως αποθηκευτικό χώρο το `cookie`, μπορείτε να ορίσετε επιπλέον τις ακόλουθες επιλογές: ```neon security: authentication: # όνομα του cookie - cookieName: userId # (string) výchozí je userid + cookieName: userId # (string) προεπιλογή είναι userid - # ποιοι κεντρικοί υπολογιστές επιτρέπεται να λάβουν το cookie + # domains που δέχονται το cookie cookieDomain: 'example.com' # (string|domain) - # περιορισμοί κατά την πρόσβαση σε cross-origin request - cookieSamesite: None # (Strict|Lax|None) προεπιλογή Lax + # περιορισμός κατά την πρόσβαση από άλλο domain + cookieSamesite: None # (Strict|Lax|None) προεπιλογή είναι Lax ``` + + +Υπηρεσίες DI +------------ + +Αυτές οι υπηρεσίες προστίθενται στον DI container: + +| Όνομα | Τύπος | Περιγραφή +|---------------------------------------------------------- +| `security.authenticator` | [api:Nette\Security\Authenticator] | [authenticator|authentication] +| `security.authorizator` | [api:Nette\Security\Authorizator] | [authorizator|authorization] +| `security.passwords` | [api:Nette\Security\Passwords] | [hash κωδικών πρόσβασης|passwords] +| `security.user` | [api:Nette\Security\User] | τρέχων χρήστης +| `security.userStorage` | [api:Nette\Security\UserStorage] | [#αποθηκευτικός χώρος] diff --git a/security/el/passwords.texy b/security/el/passwords.texy index c3cd6b9e0c..8919124f64 100644 --- a/security/el/passwords.texy +++ b/security/el/passwords.texy @@ -1,12 +1,12 @@ -Κρυπτογράφηση κωδικού πρόσβασης -******************************* +Hash κωδικών πρόσβασης +********************** .[perex] -Για να διαχειριστούμε την ασφάλεια των χρηστών μας, δεν αποθηκεύουμε ποτέ τους κωδικούς τους σε μορφή απλού κειμένου, αλλά αποθηκεύουμε τον κατακερματισμό του κωδικού πρόσβασης. Ο κατακερματισμός δεν είναι αναστρέψιμη λειτουργία, ο κωδικός πρόσβασης δεν μπορεί να ανακτηθεί. Ο κωδικός πρόσβασης μπορεί όμως να σπάσει και για να κάνουμε τη διάσπαση όσο το δυνατόν πιο δύσκολη γίνεται, πρέπει να χρησιμοποιήσουμε έναν ασφαλή αλγόριθμο. Η τάξη [api:Nette\Security\Passwords] θα μας βοηθήσει σε αυτό. +Για να διασφαλίσουμε την ασφάλεια των χρηστών μας, δεν αποθηκεύουμε τους κωδικούς πρόσβασής τους σε αναγνώσιμη μορφή, αλλά αποθηκεύουμε μόνο το αποτύπωμα (το λεγόμενο hash). Από το αποτύπωμα δεν είναι δυνατή η ανακατασκευή της αρχικής μορφής του κωδικού πρόσβασης. Είναι σημαντικό να χρησιμοποιήσουμε έναν ασφαλή αλγόριθμο για τη δημιουργία του αποτυπώματος. Σε αυτό μας βοηθά η κλάση [api:Nette\Security\Passwords]. -→ [Εγκατάσταση και απαιτήσεις |@home#Installation] +→ [Εγκατάσταση και απαιτήσεις |@home#Εγκατάσταση] -Το πλαίσιο προσθέτει αυτόματα μια υπηρεσία `Nette\Security\Passwords` στο DI container με το όνομα `security.passwords`, την οποία παίρνετε περνώντας την χρησιμοποιώντας [dependency injection |dependency-injection:passing-dependencies]: +Το framework προσθέτει αυτόματα στον DI container μια υπηρεσία τύπου `Nette\Security\Passwords` με το όνομα `security.passwords`, στην οποία μπορείτε να αποκτήσετε πρόσβαση ζητώντας να σας περαστεί μέσω [dependency injection |dependency-injection:passing-dependencies]. ```php use Nette\Security\Passwords; @@ -24,44 +24,44 @@ class Foo __construct($algo=PASSWORD_DEFAULT, array $options=[]): string .[method] ======================================================================== -Επιλέγει ποιος [ασφαλής αλγόριθμος |https://www.php.net/manual/en/password.constants.php] χρησιμοποιείται για κατακερματισμό και πώς να τον ρυθμίσετε. +Επιλέγουμε ποιον [ασφαλή αλγόριθμο|https://www.php.net/manual/en/password.constants.php] θα χρησιμοποιήσουμε για τη δημιουργία του hash και διαμορφώνουμε τις παραμέτρους του. -Η προεπιλογή είναι `PASSWORD_DEFAULT`, οπότε η επιλογή του αλγορίθμου επαφίεται στην PHP. Ο αλγόριθμος μπορεί να αλλάξει σε νεότερες εκδόσεις της PHP όταν υποστηρίζονται νεότεροι, ισχυρότεροι αλγόριθμοι κατακερματισμού. Επομένως, θα πρέπει να γνωρίζετε ότι το μήκος του προκύπτοντος κατακερματισμού μπορεί να αλλάξει. Επομένως, θα πρέπει να αποθηκεύετε τον προκύπτοντα κατακερματισμό με τρόπο που να μπορεί να αποθηκεύσει αρκετούς χαρακτήρες, 255 είναι το συνιστώμενο πλάτος. +Ως προεπιλογή χρησιμοποιείται το `PASSWORD_DEFAULT`, δηλαδή η επιλογή του αλγορίθμου αφήνεται στην PHP. Ο αλγόριθμος μπορεί να αλλάξει σε νεότερες εκδόσεις της PHP, εάν εμφανιστούν νεότεροι, ισχυρότεροι αλγόριθμοι hashing. Επομένως, θα πρέπει να γνωρίζετε ότι το μήκος του τελικού hash μπορεί να αλλάξει και θα πρέπει να το αποθηκεύσετε με τρόπο που να μπορεί να χωρέσει αρκετούς χαρακτήρες, 255 είναι το συνιστώμενο πλάτος. -Έτσι θα χρησιμοποιήσετε τον αλγόριθμο bcrypt και θα αλλάξετε την ταχύτητα κατακερματισμού χρησιμοποιώντας την παράμετρο cost από την προεπιλεγμένη 10. Το έτος 2020, με κόστος 10, ο κατακερματισμός ενός κωδικού πρόσβασης διαρκεί περίπου 80ms, με κόστος 11 διαρκεί 160ms, με κόστος 12 τότε 320ms, η κλίμακα είναι λογαριθμική. Όσο πιο αργά τόσο το καλύτερο, το κόστος 10-12 θεωρείται αρκετά αργό για τις περισσότερες περιπτώσεις χρήσης: +Παράδειγμα ρύθμισης της ταχύτητας hashing του αλγορίθμου bcrypt αλλάζοντας την παράμετρο cost: (το 2020 η προεπιλογή είναι 10, το hashing του κωδικού πρόσβασης διαρκεί περίπου 80ms, για cost 11 περίπου 160ms, για cost 12 περίπου 320ms, όσο πιο αργό, τόσο καλύτερη προστασία, ενώ η ταχύτητα 10-12 θεωρείται ήδη επαρκής προστασία) ```php -// θα κατακερματιστούν οι κωδικοί πρόσβασης με 2^12 (2^cost) επαναλήψεις του αλγορίθμου bcrypt +// θα κάνουμε hash τους κωδικούς πρόσβασης με 2^12 (2^cost) επαναλήψεις του αλγορίθμου bcrypt $passwords = new Passwords(PASSWORD_BCRYPT, ['cost' => 12]); ``` -Με έγχυση εξάρτησης: +Μέσω dependency injection: ```neon services: security.passwords: Nette\Security\Passwords(::PASSWORD_BCRYPT, [cost: 12]) ``` -hash(string $passwords): string .[method] -========================================= +hash(string $password): string .[method] +======================================== -Δημιουργεί hash κωδικού πρόσβασης. +Δημιουργεί το hash του κωδικού πρόσβασης. ```php -$res = $passwords->hash($password); // Καταστρέφει τον κωδικό πρόσβασης +$res = $passwords->hash($password); // Κάνει hash τον κωδικό πρόσβασης ``` -Το αποτέλεσμα `$res` είναι μια συμβολοσειρά που, εκτός από το ίδιο το hash, περιέχει το αναγνωριστικό του αλγορίθμου που χρησιμοποιήθηκε, τις ρυθμίσεις του και το κρυπτογραφικό salt (τυχαία δεδομένα που διασφαλίζουν ότι θα παραχθεί διαφορετικό hash για τον ίδιο κωδικό πρόσβασης). Επομένως, είναι συμβατό προς τα πίσω, για παράδειγμα, αν αλλάξετε τις παραμέτρους, μπορούν να επαληθευτούν οι κατακερματισμοί που έχουν αποθηκευτεί με τις προηγούμενες ρυθμίσεις. Όλο αυτό το αποτέλεσμα αποθηκεύεται στη βάση δεδομένων, οπότε δεν υπάρχει ανάγκη να αποθηκεύσετε ξεχωριστά το αλάτι ή τις ρυθμίσεις. +Το αποτέλεσμα `$res` είναι μια συμβολοσειρά που, εκτός από το ίδιο το hash, περιέχει επίσης τον αναγνωριστικό του χρησιμοποιούμενου αλγορίθμου, τις ρυθμίσεις του και το κρυπτογραφικό salt (τυχαία δεδομένα που διασφαλίζουν ότι για τον ίδιο κωδικό πρόσβασης δημιουργείται διαφορετικό hash). Είναι επομένως συμβατό προς τα πίσω, όταν για παράδειγμα αλλάζετε τις παραμέτρους, τα hashes που αποθηκεύτηκαν με τη χρήση προηγούμενων ρυθμίσεων θα μπορούν να επαληθευτούν. Όλο αυτό το αποτέλεσμα αποθηκεύεται στη βάση δεδομένων, οπότε δεν χρειάζεται να αποθηκεύετε το salt ή τις ρυθμίσεις ξεχωριστά. verify(string $password, string $hash): bool .[method] ====================================================== -Βρίσκει αν ο δεδομένος κωδικός πρόσβασης ταιριάζει με τον δεδομένο κατακερματισμό. Λαμβάνει το `$hash` από τη βάση δεδομένων με βάση το όνομα χρήστη ή τη διεύθυνση ηλεκτρονικού ταχυδρομείου. +Διαπιστώνει αν ο δεδομένος κωδικός πρόσβασης αντιστοιχεί στο δεδομένο αποτύπωμα. Λάβετε το `$hash` από τη βάση δεδομένων σύμφωνα με το δοθέν όνομα χρήστη ή τη διεύθυνση email. ```php if ($passwords->verify($password, $hash)) { - // Σωστός κωδικός πρόσβασης + // σωστός κωδικός πρόσβασης } ``` @@ -69,9 +69,9 @@ if ($passwords->verify($password, $hash)) { needsRehash(string $hash): bool .[method] ========================================= -Βρίσκει αν ο κατακερματισμός ταιριάζει με τις επιλογές που δίνονται στον κατασκευαστή. +Διαπιστώνει αν το hash αντιστοιχεί στις επιλογές που δόθηκαν στον κατασκευαστή. -Χρησιμοποιήστε αυτή τη μέθοδο όταν για παράδειγμα αλλάζετε τις παραμέτρους κατακερματισμού. Η επαλήθευση του κωδικού πρόσβασης θα χρησιμοποιήσει τις παραμέτρους που είναι αποθηκευμένες με τον κατακερματισμό και αν το `needsRehash()` επιστρέψει true, θα πρέπει να υπολογίσετε ξανά τον κατακερματισμό, αυτή τη φορά με τις ενημερωμένες παραμέτρους, και να τον αποθηκεύσετε ξανά στη βάση δεδομένων. Αυτό διασφαλίζει ότι οι κατακερματισμοί των κωδικών πρόσβασης θα "αναβαθμίζονται" αυτόματα όταν οι χρήστες εγγράφονται. +Είναι χρήσιμο να το χρησιμοποιείτε όταν, για παράδειγμα, αλλάζετε την ταχύτητα hashing. Η επαλήθευση γίνεται σύμφωνα με τις αποθηκευμένες ρυθμίσεις και αν η `needsRehash()` επιστρέψει `true`, τότε είναι απαραίτητο να δημιουργηθεί ξανά το hash, αυτή τη φορά με τις νέες παραμέτρους, και να αποθηκευτεί ξανά στη βάση δεδομένων. Με αυτόν τον τρόπο "αναβαθμίζονται" αυτόματα τα αποθηκευμένα hashes κατά τη σύνδεση των χρηστών. ```php if ($passwords->needsRehash($hash)) { diff --git a/security/en/@home.texy b/security/en/@home.texy index 9e1e191cd3..e1da9f34a4 100644 --- a/security/en/@home.texy +++ b/security/en/@home.texy @@ -1,8 +1,8 @@ -Security -******** +Nette Security +************** .[perex] -The `nette/security` package is in charge of [authenticating users |authentication], [access control |authorization] and [password hashing |passwords]. +The `nette/security` package handles [authenticating users |authentication], [access control |authorization], and [password hashing |passwords]. Installation diff --git a/security/en/@left-menu.texy b/security/en/@left-menu.texy index 6ce6ccdb12..638bbaf4b6 100644 --- a/security/en/@left-menu.texy +++ b/security/en/@left-menu.texy @@ -3,5 +3,5 @@ Nette Security - [Overview |@home] - [Authentication] - [Authorization] -- [Password hashing |passwords] +- [Password Hashing |passwords] - [Configuration] diff --git a/security/en/@meta.texy b/security/en/@meta.texy new file mode 100644 index 0000000000..42471908b0 --- /dev/null +++ b/security/en/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Documentation}} diff --git a/security/en/authentication.texy b/security/en/authentication.texy index d5469f4987..d15187f3d2 100644 --- a/security/en/authentication.texy +++ b/security/en/authentication.texy @@ -3,22 +3,22 @@ Authenticating Users <div class=perex> -Little to none web applications need no mechanism for user login or checking user privileges. In this chapter, we'll talk about: +Almost no web application can do without a mechanism for logging users in and out and verification of user permissions. In this chapter, we'll talk about: -- user login and logout -- custom authenticators and authorizators +- logging users in and out +- custom authenticators </div> → [Installation and requirements |@home#Installation] -In the examples, we will use an object of class [api:Nette\Security\User], which represents the current user and which you get by passing it using [dependency injection |dependency-injection:passing-dependencies]. In presenters simply call `$user = $this->getUser()`. +In the examples, we will use an object of the class [api:Nette\Security\User], which represents the current user and which you get by having it passed to you using [dependency injection |dependency-injection:passing-dependencies]. In presenters, just call `$user = $this->getUser()`. Authentication ============== -Authentication means **user login**, ie. the process during which a user's identity is verified. The user usually identifies himself using username and password. Verification is performed by the so-called [#authenticator]. If the login fails, it throws `Nette\Security\AuthenticationException`. +Authentication means **user login**, i.e., the process during which a user's identity is verified. The user usually identifies themselves using a username and password. Verification is done by the so-called [#authenticator]. If the login fails, a `Nette\Security\AuthenticationException` is thrown. ```php try { @@ -28,21 +28,21 @@ try { } ``` -This is how to log out the user: +This is how you log out the user: ```php $user->logout(); ``` -And checking if user is logged in: +And to find out if the user is logged in: ```php echo $user->isLoggedIn() ? 'yes' : 'no'; ``` -Simple, right? And all security aspects are handled by Nette for you. +Very simple, isn't it? And Nette takes care of all the security aspects for you. -In presenter, you can verify login in the `startup()` method and redirect a non-logged-in user to the login page. +In presenters, you can verify login in the `startup()` method and redirect non-logged-in users to the login page. ```php protected function startup() @@ -58,36 +58,35 @@ protected function startup() Expiration ========== -The user login expires along with [expiration of repository|#Storage for Logged User], which is usually a session (see the [session expiration|http:configuration#session] setting). -However, you can also set a shorter time interval after which the user is logged out. The `setExpiration()` method, which is called before `login()`, is used for this purpose. Provide a string with a relative time as a parameter: +User login expires together with the [storage expiration |#Storage for Logged-in User], which is usually the session (see the [session expiration |http:configuration#Session] setting). However, you can also set a shorter time interval after which the user is logged out. The `setExpiration()` method, which is called before `login()`, is used for this purpose. Pass a string with a relative time as the argument: ```php // login expires after 30 minutes of inactivity $user->setExpiration('30 minutes'); -// cancel set expiration +// cancel the set expiration $user->setExpiration(null); ``` -The `$user->getLogoutReason()` method tells if the user has been logged out because the time interval has expired. It returns either the constant `Nette\Security\UserStorage::LogoutInactivity` if the time expired or `UserStorage::LogoutManual` when the `logout()` method was called. +The `$user->getLogoutReason()` method reveals whether the user was logged out because the time interval expired. It returns either the constant `Nette\Security\UserStorage::LogoutInactivity` (the time limit expired) or `UserStorage::LogoutManual` (`logout()` method was called). Authenticator ============= -It is an object that verifies the login data, ie usually the name and password. The trivial implementation is the class [api:Nette\Security\SimpleAuthenticator], which can be defined in [configuration]: +This is an object that verifies login credentials, typically username and password. A trivial form is the class [api:Nette\Security\SimpleAuthenticator], which can be defined in the [configuration|configuration]: ```neon security: users: - # name: password + # username: password johndoe: secret123 kathy: evenmoresecretpassword ``` -This solution is more suitable for testing purposes. We will show you how to create an authenticator that will verify credentials against a database table. +This solution is more suitable for testing purposes. We will show you how to create an authenticator that verifies login credentials against a database table. -An authenticator is an object that implements the [api:Nette\Security\Authenticator] interface with method `authenticate()`. Its task is either to return the so-called [#identity] or to throw an exception `Nette\Security\AuthenticationException`. It would also be possible to provide an fine-grain error code `Authenticator::IdentityNotFound` or `Authenticator::InvalidCredential`. +An authenticator is an object implementing the [api:Nette\Security\Authenticator] interface with the `authenticate()` method. Its task is to either return an [#identity] or throw a `Nette\Security\AuthenticationException`. It would also be possible to specify an error code to distinguish the situation more finely: `Authenticator::IdentityNotFound` or `Authenticator::InvalidCredential`. ```php use Nette; @@ -117,14 +116,14 @@ class MyAuthenticator implements Nette\Security\Authenticator return new SimpleIdentity( $row->id, - $row->role, // or array of roles + $row->role, // or an array of roles ['name' => $row->username], ); } } ``` -The MyAuthenticator class communicates with the database through [Nette Database Explorer |database:explorer] and works with table `users`, where column `username` contains the user's login name and column `password` contains [hash|passwords]. After verifying the name and password, it returns the identity with user's ID, role (column `role` in the table), which we will mention [later |#roles], and an array with additional data (in our case, the username). +The `MyAuthenticator` class communicates with the database via [Nette Database Explorer |database:explorer] and works with the `users` table, where the `username` column contains the user's login name and the `password` column contains the [password hash |passwords]. After verifying the name and password, it returns the identity containing the user's ID, their role (the `role` column in the table), which we will discuss more [later |authorization#Roles], and an array with additional data (in our case, the username). We will add the authenticator to the configuration [as a service |dependency-injection:services] of the DI container: @@ -137,7 +136,7 @@ services: $onLoggedIn, $onLoggedOut Events -------------------------------- -Object `Nette\Security\User` has [events|nette:glossary#Events] `$onLoggedIn` and `$onLoggedOut`, so you can add callbacks that are triggered after a successful login or after the user logs out. +The `Nette\Security\User` object has [events |nette:glossary#Events] `$onLoggedIn` and `$onLoggedOut`, so you can add callbacks that are triggered after a successful login or after the user logs out, respectively. ```php @@ -150,34 +149,34 @@ $user->onLoggedIn[] = function () { Identity ======== -An identity is a set of information about a user that is returned by the authenticator and which is then stored in a session and retrieved using `$user->getIdentity()`. So we can get the id, roles and other user data as we passed them in the authenticator: +An identity is a set of information about a user that is returned by the authenticator and which is subsequently stored in the session and can be retrieved using `$user->getIdentity()`. This allows us to get the ID, roles, and other user data, just as we passed them in the authenticator: ```php $user->getIdentity()->getId(); -// also works shortcut $user->getId(); +// shortcut $user->getId() also works $user->getIdentity()->getRoles(); -// user data can be access as properties -// the name we passed on in MyAuthenticator +// user data are accessible as properties +// the username we passed in MyAuthenticator $user->getIdentity()->name; ``` -Importantly, when user logs out using `$user->logout()`, **identity is not deleted** and is still available. So, if identity exists, it by itself does not grant that the user is also logged in. If we want to explicitly delete the identity, we logout the user by `logout(true)`. +What's important is that when logging out using `$user->logout()`, **the identity is not deleted** and it is still available. So, even if a user has an identity, they do not have to be logged in. If we want to delete the identity explicitly, we log the user out by calling `logout(true)`. -Thanks to this, you can still assume which user is at the computer and, for example, display personalized offers in the e-shop, however, you can only display his personal data after logging in. +Thanks to this, you can still assume which user is at the computer and, for example, display personalized offers in an e-shop, but you can only display their personal information after they log in. -Identity is an object that implements the [api:Nette\Security\IIdentity] interface, the default implementation is [api:Nette\Security\SimpleIdentity]. And as mentioned, identity is stored in the session, so if, for example, we change the role of some of the logged-in users, old data will be kept in the identity until he logs in again. +An identity is an object implementing the [api:Nette\Security\IIdentity] interface. The default implementation is [api:Nette\Security\SimpleIdentity]. And as mentioned, it is maintained in the session, so if, for example, we change the role of one of the logged-in users, the old data will remain in their identity until they log in again. -Storage for Logged User -======================= +Storage for Logged-in User +========================== -The two basic pieces of information about the user, i.e., whether they are logged in and their [#identity], are usually carried in the session. Which can be changed. For storing this information is responsible an object implementing the `Nette\Security\UserStorage` interface. There are two standard implementations, the first transmits data in a session and the second in a cookie. These are the `Nette\Bridges\SecurityHttp\SessionStorage` and `CookieStorage` classes. You can choose the storage and configure it very conveniently in configuration [security › authentication|configuration]. +The two basic information about the user, namely whether they are logged in and their [#identity], are usually transmitted in the session. Which can be changed. An object implementing the `Nette\Security\UserStorage` interface is responsible for storing this information. Two standard implementations are available: `Nette\Bridges\SecurityHttp\SessionStorage`, which transmits data in the session, and `CookieStorage`, which transmits data in a cookie. You can choose the storage and configure it very conveniently in the [security › authentication |configuration#User Storage] configuration. -You can also control exactly how identity saving (*sleep*) and restoring (*wakeup*) will take place. All you need is for the authenticator to implement the `Nette\Security\IdentityHandler` interface. This has two methods: `sleepIdentity()` is called before the identity is written to storage, and `wakeupIdentity()` is called after the identity is read. The methods can modify the contents of the identity, or replace it with a new object that returns. The `wakeupIdentity()` method may even return `null`, which logs the user out. +Furthermore, you can influence how exactly the identity saving (*sleep*) and restoring (*wakeup*) will proceed. All that is needed is for the authenticator to implement the `Nette\Security\IdentityHandler` interface. This interface has two methods: `sleepIdentity()` is called before the identity is written to storage, and `wakeupIdentity()` after it's read. These methods can modify the identity content, or replace it with a new object that it returns. The `wakeupIdentity()` method can even return `null`, which logs the user out. -As an example, we will show a solution to a common question on how to update identity roles right after restoring from a session. In the method `wakeupIdentity()` we pass the current roles to the identity, eg from the database: +As an example, let's show a solution to the frequent question of how to update roles in the identity right after loading from the session. In the `wakeupIdentity()` method, we pass the current roles, e.g., from a database, into the identity: ```php final class Authenticator implements @@ -185,25 +184,25 @@ final class Authenticator implements { public function sleepIdentity(IIdentity $identity): IIdentity { - // here you can change the identity before storing after logging in, + // here you can modify the identity before writing it to storage after login, // but we don't need that now return $identity; } public function wakeupIdentity(IIdentity $identity): ?IIdentity { - // updating roles in identity + // update roles in the identity $userId = $identity->getId(); $identity->setRoles($this->facade->getUserRoles($userId)); return $identity; } ``` -And now we return to the cookie-based storage. It allows you to create a website where users can log in without the need to use sessions. So it does not need to write to disk. After all, this is how the website you are now reading works, including the forum. In this case, the implementation of `IdentityHandler` is a necessity. We will only store a random token representing the logged user in the cookie. +Now let's return to storage based on cookies. It allows you to create a website where users can log in while not needing sessions. Thus, it does not need to write to the disk. This is how the website you are currently reading works, including the forum. In this case, the implementation of `IdentityHandler` is a necessity. We will only store a random token representing the logged-in user in the cookie. -So first we set the desired storage in the configuration using `security › authentication › storage: cookie`. +First, set the required storage in the configuration using `security › authentication › storage: cookie`. -We will add a column `authtoken` in the database, in which each user will have a [completely random, unique and unguessable|utils:random] string of sufficient length (at least 13 characters). The repository `CookieStorage` stores only the value `$identity->getId()` in the cookie, so in `sleepIdentity()` we replace the original identity with a proxy with `authtoken` in the ID, on the contrary in the method `wakeupIdentity()` we restore whole identity from the database according authtoken: +In the database, create the `authtoken` column, where each user will have a [completely random, unique, and unguessable |utils:random] string of sufficient length (at least 13 characters). The `CookieStorage` transmits only the value `$identity->getId()` in the cookie, so in `sleepIdentity()`, we replace the original identity with a proxy identity containing the `authtoken` in the ID. Conversely, in the `wakeupIdentity()` method, we read the entire identity from the database based on the authtoken: ```php final class Authenticator implements @@ -212,21 +211,21 @@ final class Authenticator implements public function authenticate(string $username, string $password): SimpleIdentity { $row = $this->db->fetch('SELECT * FROM user WHERE username = ?', $username); - // check password + // verify password ... - // we return the identity with all the data from the database + // return the identity with all data from the database return new SimpleIdentity($row->id, null, (array) $row); } public function sleepIdentity(IIdentity $identity): SimpleIdentity { - // we return a proxy identity, where in the ID is authtoken + // return a proxy identity where the ID contains the authtoken return new SimpleIdentity($identity->authtoken); } public function wakeupIdentity(IIdentity $identity): ?SimpleIdentity { - // replace the proxy identity with a full identity, as in authenticate() + // replace the proxy identity with the full identity, as in authenticate() $row = $this->db->fetch('SELECT * FROM user WHERE authtoken = ?', $identity->getId()); return $row ? new SimpleIdentity($row->id, null, (array) $row) @@ -236,16 +235,16 @@ final class Authenticator implements ``` -Multiple Independent Authentications -==================================== +Multiple Independent Logins +=========================== -It is possible to have several independent logged users within one site and one session at a time. For example, if we want to have separate authentication for frontend and backend, we will just set a unique session namespace for each of them: +It is possible to have multiple independent users logging in within one website and one session simultaneously. For example, if we want to have separate authentication for the administration and the public-facing part of the website, we just need to set a unique namespace for each: ```php $user->getStorage()->setNamespace('backend'); ``` -It's necessary to keep in mind that this must be set at all places belonging to the same segment. When using presenters, we will set the namespace in the common ancestor - usually the BasePresenter. In order to do so we will extend the [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()] method: +It's important to remember to set the namespace always in all places belonging to the respective part. If we use presenters, we set the namespace in the common ancestor for that part - usually BasePresenter. We do this by extending the [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()] method: ```php public function checkRequirements($element): void @@ -259,7 +258,7 @@ public function checkRequirements($element): void Multiple Authenticators ----------------------- -Dividing an application into segments with independent authentication generally requires different authenticators. However, registering two classes that implement Authenticator into config services would trigger an error because Nette wouldn't know which of them should be [autowired |dependency-injection:autowiring] to the `Nette\Security\User` object. Which is why we must limit autowiring for them with `autowired: self` so that it's activated only when their class is specifically requested: +Dividing an application into parts with independent login usually also requires different authenticators. However, if we registered two classes implementing Authenticator in the service configuration, Nette would not know which one to automatically assign to the `Nette\Security\User` object and would display an error. Therefore, we must restrict [autowiring |dependency-injection:autowiring] for authenticators so that it works only when someone requests a specific class, e.g., `FrontAuthenticator`. This is achieved by choosing `autowired: self`: ```neon services: @@ -278,7 +277,7 @@ class SignPresenter extends Nette\Application\UI\Presenter } ``` -We only need to set our authenticator to the User object before calling method [login() |api:Nette\Security\User::login()] which typically means in the login form callback: +We set the authenticator of the User object before calling the [login() |api:Nette\Security\User::login()] method, so usually in the code of the form that logs them in: ```php $form->onSuccess[] = function (Form $form, \stdClass $data) { diff --git a/security/en/authorization.texy b/security/en/authorization.texy index 3ee3a74834..dfcb3a37ba 100644 --- a/security/en/authorization.texy +++ b/security/en/authorization.texy @@ -2,17 +2,17 @@ Access Control (Authorization) ****************************** .[perex] -Authorization determines whether a user has sufficient privileges, for example, to access a specific resource or to perform an action. Authorization assumes previous successful authentication, ie that the user is logged in. +Authorization checks whether a user has sufficient permissions, for example, to access a specific resource or to perform an action. Authorization presupposes prior successful authentication, i.e., that the user is logged in. → [Installation and requirements |@home#Installation] -In the examples, we will use an object of class [api:Nette\Security\User], which represents the current user and which you get by passing it using [dependency injection |dependency-injection:passing-dependencies]. In presenters simply call `$user = $this->getUser()`. +In the examples, we will use an object of the class [api:Nette\Security\User], which represents the current user and which you get by having it passed to you using [dependency injection |dependency-injection:passing-dependencies]. In presenters, just call `$user = $this->getUser()`. -For very simple websites with administration, where user rights are not distinguished, it is possible to use the already known method as an authorization criterion `isLoggedIn()`. In other words: once a user is logged in, he has permissions to all actions and vice versa. +For very simple websites with administration where user permissions are not differentiated, the already known method `isLoggedIn()` can be used as the authorization criterion. In other words: as soon as a user is logged in, they have all permissions, and vice versa. ```php -if ($user->isLoggedIn()) { // is user logged in? - deleteItem(); // if so, he may delete an item +if ($user->isLoggedIn()) { // is the user logged in? + deleteItem(); // then they have permission for the operation } ``` @@ -20,29 +20,29 @@ if ($user->isLoggedIn()) { // is user logged in? Roles ----- -The purpose of roles is to offer a more precise permission management and remain independent on the user name. As soon as user logs in, he is assigned one or more roles. Roles themselves may be simple strings, for example, `admin`, `member`, `guest`, etc. They are specified in the second argument of `SimpleIdentity` constructor, either as a string or an array. +The purpose of roles is to offer more precise control over permissions and remain independent of the username. As soon as a user logs in, they are assigned one or more roles in which they will act. Roles can be simple strings, for example, `admin`, `member`, `guest`, etc. They are specified as the second argument of the `SimpleIdentity` constructor, either as a string or an array of strings - roles. -As an authorization criterion, we will now use the method `isInRole()`, which checks whether the user is in the given role: +As an authorization criterion, we will now use the `isInRole()` method, which reveals whether the user is acting in the given role: ```php -if ($user->isInRole('admin')) { // is the admin role assigned to the user? - deleteItem(); // if so, he may delete an item +if ($user->isInRole('admin')) { // is the user in the admin role? + deleteItem(); // then they have permission for the operation } ``` -As you already know, logging the user out does not erase his identity. Thus, method `getIdentity()` still returns object `SimpleIdentity`, including all granted roles. The Nette Framework adheres to the principle of "less code, more security", so when you are checking roles, you do not have to check whether the user is logged in too. Method `isInRole()` works with **effective roles**, ie if the user is logged in, roles assigned to identity are used, if he is not logged in, an automatic special role `guest` is used instead. +As you already know, logging out the user does not have to delete their identity. Thus, the `getIdentity()` method still returns the `SimpleIdentity` object, including all granted roles. Nette Framework espouses the principle "less code, more security," where less writing leads to more secure code. Therefore, when checking roles, you do not need to verify whether the user is logged in. The `isInRole()` method works with **effective roles**: if the user is logged in, it is based on the roles specified in the identity; if not logged in, they automatically have the special role `guest`. -Authorizator ------------- +Authorizer +---------- In addition to roles, we will introduce the terms resource and operation: -- **role** is a user attribute - for example moderator, editor, visitor, registered user, administrator, ... -- **resource** is a logical unit of the application - article, page, user, menu item, poll, presenter, ... -- **operation** is a specific activity, which user may or may not do with *resource* - view, edit, delete, vote, ... +- **role** is a property of the user - e.g., moderator, editor, visitor, registered user, administrator... +- **resource** is a logical unit of the website - article, page, user, menu item, poll, presenter, ... +- **operation** is a specific activity that the user can or cannot perform with the resource - e.g., view, edit, delete, vote, ... -An authorizer is an object that decides whether a given *role* has permission to perform a certain *operation* with specific *resource*. It is an object implementing the [api:Nette\Security\Authorizator] interface with only one method `isAllowed()`: +An authorizer is an object that decides whether the given *role* has permission to perform a certain *operation* with a specific *resource*. It is an object implementing the [api:Nette\Security\Authorizator] interface with a single method `isAllowed()`: ```php class MyAuthorizator implements Nette\Security\Authorizator @@ -63,50 +63,50 @@ class MyAuthorizator implements Nette\Security\Authorizator } ``` -We add the authorizator to the configuration [as a service |dependency-injection:services] of the DI container: +We add the authorizer to the configuration [as a service |dependency-injection:services] of the DI container: ```neon services: - MyAuthorizator ``` -And the following is an example of use. Note that this time we call the method `Nette\Security\User::isAllowed()`, not the authorizator's one, so there is not first parameter `$role`. This method calls `MyAuthorizator::isAllowed()` sequentially for all user roles and returns true if at least one of them has permission. +And here is an example of usage. Note, this time we call the `Nette\Security\User::isAllowed()` method, not the authorizer's, so the first parameter `$role` is missing. This method calls `MyAuthorizator::isAllowed()` sequentially for all the user's roles and returns true if at least one of them has permission. ```php -if ($user->isAllowed('file')) { // is user allowed to do everything with resource 'file'? +if ($user->isAllowed('file')) { // can the user do anything with the 'file' resource? useFile(); } -if ($user->isAllowed('file', 'delete')) { // is user allowed to delete a resource 'file'? +if ($user->isAllowed('file', 'delete')) { // can the user perform 'delete' on the 'file' resource? deleteFile(); } ``` -Both arguments are optional and their default value means *everything*. +Both arguments are optional; the default value `null` means *anything*. Permission ACL -------------- -Nette comes with a built-in implementation of the authorizer, the [api:Nette\Security\Permission] class, which offers a lightweight and flexible ACL (Access Control List) layer for permission and access control. When we work with this class, we define roles, resources, and individual permissions. And roles and resources may form hierarchies. To explain, we will show an example of a web application: +Nette comes with a built-in implementation of the authorizer, the [api:Nette\Security\Permission] class, providing the programmer with a light and flexible ACL (Access Control List) layer for managing permissions and access. Working with it consists of defining roles, resources, and individual permissions. Roles and resources allow creating hierarchies. To explain, we will show an example of a web application: -- `guest`: visitor that is not logged in, allowed to read and browse public part of the web, ie. read articles, comment and vote in polls -- `registered`: logged-in user, which may on top of that post comments -- `admin`: can manage articles, comments and polls +- `guest`: an unregistered visitor who can read and browse the public section of the website, i.e., read articles, comments, and vote in polls. +- `registered`: a registered user who is logged in, who can also post comments. +- `admin`: can manage articles, comments, and polls. -So we have defined certain roles (`guest`, `registered` and `admin`) and mentioned resources (`article`, `comments`, `poll`), which the users may access or take actions on (`view`, `vote`, `add`, `edit`). +We have defined certain roles (`guest`, `registered`, and `admin`) and mentioned resources (`article`, `comment`, `poll`), to which users with a certain role can access or perform certain operations (`view`, `vote`, `add`, `edit`). -We create an instance of the Permission class and define **roles**. It is possible to use the inheritance of roles, which ensures that, for example, a user with a role `admin` can do what an ordinary website visitor can do (and of course more). +We create an instance of the Permission class and define **roles**. It's possible to use so-called role inheritance, which ensures that, e.g., a user with the `admin` role can also do what an ordinary website visitor can do (and of course, more). ```php $acl = new Nette\Security\Permission; $acl->addRole('guest'); $acl->addRole('registered', 'guest'); // 'registered' inherits from 'guest' -$acl->addRole('admin', 'registered'); // and 'admin' inherits from 'registered' +$acl->addRole('admin', 'registered'); // 'admin' inherits from 'registered' ``` -We will now define a list of **resources** that users can access: +Now we define the list of **resources** that users can access. ```php $acl->addResource('article'); @@ -114,49 +114,49 @@ $acl->addResource('comment'); $acl->addResource('poll'); ``` -Resources can also use inheritance, for example, we can add `$acl->addResource('perex', 'article')`. +Resources can also use inheritance; for example, it would be possible to enter `$acl->addResource('perex', 'article')`. -And now the most important thing. We will define between them **rules** determining who can do what: +And now the most important part. We define rules between them, determining who can do what with what: ```php -// everything is denied now +// initially, nobody can do anything -// let the guest view articles, comments and polls +// let the guest view articles, comments, and polls $acl->allow('guest', ['article', 'comment', 'poll'], 'view'); // and also vote in polls $acl->allow('guest', 'poll', 'vote'); -// the registered inherits the permissions from guesta, we will also let him to comment +// registered inherits permissions from guest, let's give them the right to comment additionally $acl->allow('registered', 'comment', 'add'); // the administrator can view and edit anything $acl->allow('admin', $acl::All, ['view', 'edit', 'add']); ``` -What if we want to **prevent** someone from accessing a resource? +What if we want to **prevent** someone from accessing a certain resource? ```php -// administrator cannot edit polls, that would be undemocractic. +// the administrator cannot edit polls; that would be undemocratic $acl->deny('admin', 'poll', 'edit'); ``` -Now when we have created the set of rules, we may simply ask the authorization queries: +Now that we have created the set of rules, we can simply pose authorization queries: ```php // can guest view articles? $acl->isAllowed('guest', 'article', 'view'); // true -// can guest edit an article? +// can guest edit articles? $acl->isAllowed('guest', 'article', 'edit'); // false // can guest vote in polls? $acl->isAllowed('guest', 'poll', 'vote'); // true -// may guest add comments? +// can guest comment? $acl->isAllowed('guest', 'comment', 'add'); // false ``` -The same applies to a registered user, but he can also comment: +The same applies to a registered user, but they can also comment: ```php $acl->isAllowed('registered', 'article', 'view'); // true @@ -164,7 +164,7 @@ $acl->isAllowed('registered', 'comment', 'add'); // true $acl->isAllowed('registered', 'comment', 'edit'); // false ``` -The administrator can edit everything except polls: +The administrator can edit everything, except for polls: ```php $acl->isAllowed('admin', 'poll', 'vote'); // true @@ -172,7 +172,7 @@ $acl->isAllowed('admin', 'poll', 'edit'); // false $acl->isAllowed('admin', 'comment', 'edit'); // true ``` -Permissions can also be evaluated dynamically and we can leave the decision to our own callback, to which all parameters are passed: +Permissions can also be evaluated dynamically, and we can leave the decision to our own callback, to which all parameters are passed: ```php $assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { @@ -182,7 +182,7 @@ $assertion = function (Permission $acl, string $role, string $resource, string $ $acl->allow('registered', 'comment', null, $assertion); ``` -But how to solve a situation where the names of roles and resources are not enough, ie we would like to define that, for example, a role `registered` can edit a resource `article` only if it is its author? We will use objects instead of strings, the role will be the object [api:Nette\Security\Role] and the source [api:Nette\Security\Resource]. Their methods `getRoleId()` resp. `getResourceId()` will return the original strings: +But how to handle a situation where just the names of roles and resources are not enough, but we would like to define that, for example, the role `registered` can edit the resource `article` only if they are its author? We will use objects instead of strings; the role will be an object [api:Nette\Security\Role] and the resource an object [api:Nette\Security\Resource]. Their methods `getRoleId()` resp. `getResourceId()` will return the original strings: ```php class Registered implements Nette\Security\Role @@ -207,19 +207,19 @@ class Article implements Nette\Security\Resource } ``` -And now let's create a rule: +And now we create the rule: ```php $assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { - $role = $acl->getQueriedRole(); // object Registered - $resource = $acl->getQueriedResource(); // object Article + $role = $acl->getQueriedRole(); // Registered object + $resource = $acl->getQueriedResource(); // Article object return $role->id === $resource->authorId; }; $acl->allow('registered', 'article', 'edit', $assertion); ``` -The ACL is queried by passing objects: +And the ACL query is performed by passing objects: ```php $user = new Registered(/* ... */); @@ -227,7 +227,7 @@ $article = new Article(/* ... */); $acl->isAllowed($user, $article, 'edit'); ``` -A role may inherit form one or more other roles. But what happens, if one ancestor has certain action allowed and the other one has it denied? Then the *role weight* comes into play - the last role in the array of roles to inherit has the greatest weight, first one the lowest: +A role can inherit from one role or multiple roles. But what happens if one ancestor has the action denied and the other allowed? What will the descendant's rights be? This is determined by the weight of the role - the last role listed in the list of ancestors has the highest weight, the first one the lowest. This is more illustrative from the example: ```php $acl = new Nette\Security\Permission; @@ -239,22 +239,22 @@ $acl->addResource('backend'); $acl->allow('admin', 'backend'); $acl->deny('guest', 'backend'); -// example A: role admin has lower weight than role guest +// case A: admin role has lower weight than guest role $acl->addRole('john', ['admin', 'guest']); $acl->isAllowed('john', 'backend'); // false -// example B: role admin has greater weight than role guest +// case B: admin role has greater weight than guest role $acl->addRole('mary', ['guest', 'admin']); $acl->isAllowed('mary', 'backend'); // true ``` -Roles and resources can also be removed (`removeRole()`, `removeResource()`), rules can also be reverted (`removeAllow()`, `removeDeny()`). The array of all direct parent roles returns `getRoleParents()`. Whether two entities inherit from each other returns `roleInheritsFrom()` and `resourceInheritsFrom()`. +Roles and resources can also be removed (`removeRole()`, `removeResource()`), and rules can also be reverted (`removeAllow()`, `removeDeny()`). The array of all direct parent roles is returned by `getRoleParents()`. Whether two entities inherit from each other is returned by `roleInheritsFrom()` and `resourceInheritsFrom()`. -Add as a Service ----------------- +Adding as a Service +------------------- -We need to add the ACL created by us to the configuration as a service so that it can be used by the object `$user`, ie so that we can use in code for example `$user->isAllowed('article', 'view')`. For this purpose, we will write a factory for it: +We need to pass the ACL we created to the configuration as a service so that the `$user` object starts using it, i.e., so that it is possible to use `$user->isAllowed('article', 'view')` in the code. For this purpose, we will write a factory for it: ```php namespace App\Model; @@ -272,14 +272,14 @@ class AuthorizatorFactory } ``` -And we will add it to the configuration: +And add it to the configuration: ```neon services: - App\Model\AuthorizatorFactory::create ``` -In presenters, you can then verify permissions in the `startup()` method, for example: +In presenters, you can then verify permissions, for example, in the `startup()` method: ```php protected function startup() diff --git a/security/en/configuration.texy b/security/en/configuration.texy index b74f2e7296..a6de366f55 100644 --- a/security/en/configuration.texy +++ b/security/en/configuration.texy @@ -2,29 +2,29 @@ Configuring Access Control ************************** .[perex] -Overview of configuration options for the Nette Security. +Overview of configuration options for Nette Security. -If you are not using the whole framework, but only this library, read [how to load the configuration|bootstrap:]. +If you are not using the entire framework, but only this library, read [how to load the configuration|bootstrap:]. -You can define a list of users in the configuration to create a [simple authenticator|authentication] (`Nette\Security\SimpleAuthenticator`). Because passwords are readable in the configuration, this solution is for testing purposes only. +You can define a list of users in the configuration to create a [simple authenticator|authentication] (`Nette\Security\SimpleAuthenticator`). Because passwords are listed in plain text in the configuration, this solution is suitable for testing purposes only. ```neon security: - # shows user panel in Tracy Bar? + # show the user panel in Tracy Bar? debugger: ... # (bool) defaults to true users: # name: password johndoe: secret123 - # name, password, role and other data available in the identity + # name, password, roles, and other data available in the identity janedoe: password: secret123 roles: [admin] data: ... ``` -You can also define roles and resources to create a basis for [authorizer|authorization] (`Nette\Security\Permission`): +Furthermore, you can define roles and resources to create a basis for an [authorizer|authorization] (`Nette\Security\Permission`): ```neon security: @@ -43,29 +43,43 @@ security: User Storage ------------ -You can configure how to store information about the logged in user: +You can configure how to store information about the logged-in user: ```neon security: authentication: - # after how long of inactivity the user will be logged out + # period of inactivity after which the user will be logged out expiration: 30 minutes # (string) default is not set - # where to store information about the logged in user + # where to store information about the logged-in user storage: session # (session|cookie) default is session ``` -If you choose `cookie` as your repository, you can also set the following options: +If you choose `cookie` as the storage, you can also set these options: ```neon security: authentication: - # name of cookie - cookieName: userId # (string) výchozí je userid + # name of the cookie + cookieName: userId # (string) defaults to userid - # which hosts are allowed to receive the cookie + # domains that can receive the cookie cookieDomain: 'example.com' # (string|domain) - # restrictions when accessing cross-origin request + # restriction for cross-origin access cookieSamesite: None # (Strict|Lax|None) defaults to Lax ``` + + +DI Services +----------- + +These services are added to the DI container: + +| Name | Type | Description +|-------------------------|------------------------------------|--------------------------- +| `security.authenticator`| [api:Nette\Security\Authenticator] | [authenticator |authentication] +| `security.authorizator` | [api:Nette\Security\Authorizator] | [authorizer |authorization] +| `security.passwords` | [api:Nette\Security\Passwords] | [password hashing |passwords] +| `security.user` | [api:Nette\Security\User] | current user +| `security.userStorage` | [api:Nette\Security\UserStorage] | [storage |#User Storage] diff --git a/security/en/passwords.texy b/security/en/passwords.texy index f5290fcea4..4c0cb44388 100644 --- a/security/en/passwords.texy +++ b/security/en/passwords.texy @@ -2,11 +2,11 @@ Password Hashing **************** .[perex] -To manage the security of our users, we never store their passwords in plaintext format, we store the password´s hash instead. Hashing is not a reversible operation, the password cannot be recovered. The password can be cracked though and to make cracking as hard as possible we have to use a secure algorithm. Class [api:Nette\Security\Passwords] will help us with that. +To ensure the security of our users, we never store their passwords in readable form, but only store their imprint (called hash). The hash cannot be reverse-engineered back to the original password. It is important to use a secure algorithm to create the hash. The class [api:Nette\Security\Passwords] helps us with this. → [Installation and requirements |@home#Installation] -The framework automatically adds a `Nette\Security\Passwords` service to the DI container under the name `security.passwords`, which you get by passing it using [dependency injection |dependency-injection:passing-dependencies]: +The framework automatically adds a service of type `Nette\Security\Passwords` to the DI container under the name `security.passwords`. You can get by having it passed to you using [dependency injection |dependency-injection:passing-dependencies]. ```php use Nette\Security\Passwords; @@ -24,44 +24,44 @@ class Foo __construct($algo=PASSWORD_DEFAULT, array $options=[]): string .[method] ======================================================================== -Chooses which [secure algorithm|https://www.php.net/manual/en/password.constants.php] is used for hashing and how to configure it. +We choose which [secure algorithm|https://www.php.net/manual/en/password.constants.php] to use for generating the hash and configure its parameters. -The default is `PASSWORD_DEFAULT`, so the algorithm choice is left to PHP. Algorithm may change in newer PHP releases when newer, stronger hashing algorithms are supported. Therefore you should be aware that the length of the resulting hash can change. Therefore you should store the resulting hash in a way that can store enough characters, 255 is the recommended width. +The default is `PASSWORD_DEFAULT`, meaning the choice of algorithm is left up to PHP. The algorithm may change in newer PHP versions if newer, stronger hashing algorithms appear. Therefore, you should be aware that the length of the resulting hash may change, and you should store it in a way that can accommodate enough characters; 255 is the recommended width. -This is how you'd use the bcrypt algorithm and change the hashing speed using the cost parameter from the default 10. In year 2020, with cost 10, the hashing of one password takes roughly 80ms, cost 11 takes 160ms, cost 12 then 320ms, the scale is logarithmic. The slower the better, cost 10-12 is considered slow enough for most use cases: +Example of setting the hashing speed for the bcrypt algorithm by changing the cost parameter: (in 2020, the default is 10, hashing a password takes roughly 80ms; for cost 11, it's approx. 160ms; for cost 12, approx. 320ms; the slower, the better the protection, with speed 10-12 is already considered sufficient protection) ```php // we will hash passwords with 2^12 (2^cost) iterations of the bcrypt algorithm $passwords = new Passwords(PASSWORD_BCRYPT, ['cost' => 12]); ``` -With dependency injection: +Using dependency injection: ```neon services: security.passwords: Nette\Security\Passwords(::PASSWORD_BCRYPT, [cost: 12]) ``` -hash(string $passwords): string .[method] -========================================= +hash(string $password): string .[method] +======================================== -Generates password´s hash. +Generates the password hash. ```php $res = $passwords->hash($password); // Hashes the password ``` -The result `$res` is a string that, in addition to the hash itself, contains the identifier of the algorithm used, its settings, and cryptographic salt (random data to ensure that a different hash is generated for the same password). It is therefore backwards compatible, for example, if you change the parameters, the hashes stored using the previous settings can be verified. This entire result is stored in the database, so there is no need to store salt or settings separately. +The result `$res` is a string that, in addition to the hash itself, contains the identifier of the algorithm used, its settings, and a cryptographic salt (random data ensuring that a different hash is generated for the same password). It is therefore backward compatible; for example, if you change the parameters, hashes stored using previous settings can still be verified. This entire result is stored in the database, so there is no need to store the salt or settings separately. verify(string $password, string $hash): bool .[method] ====================================================== -Finds out, whether the given password matches the given hash. Get the `$hash` from database by username or email address. +Finds out if the given password matches the given hash. `$hash` obtain from the database according to the entered username or e-mail address. ```php if ($passwords->verify($password, $hash)) { - // Correct password + // correct password } ``` @@ -69,13 +69,13 @@ if ($passwords->verify($password, $hash)) { needsRehash(string $hash): bool .[method] ========================================= -Finds out if the hash matches the options given in constructor. +Finds out whether the hash matches the options specified in the constructor. -Use this method when you're for example changing hashing parameters. Password verification will use parameters stored with the hash and if `needsRehash()` returns true, you have to calculate the hash again, this time with the updated parameters, and again store it in database. This ensures that passwords hashes will be automatically "upgraded" when users are signing in. +It is useful to use it when, for example, you change the hashing cost. Verification takes place according to the stored settings, and if `needsRehash()` returns `true`, it is necessary to create the hash again, this time with the new parameters, and save it again in the database. This automatically "upgrades" stored hashes when users log in. ```php if ($passwords->needsRehash($hash)) { $hash = $passwords->hash($password); - // store $hash to database + // store $hash into database } ``` diff --git a/security/es/@home.texy b/security/es/@home.texy index 10417a51c4..8222268bac 100644 --- a/security/es/@home.texy +++ b/security/es/@home.texy @@ -1,14 +1,14 @@ -Seguridad -********* +Nette Security +************** .[perex] -El paquete `nette/security` se encarga de la [autenticación de usuarios |authentication], el [control de acceso |authorization] y [el hash de contraseñas |passwords]. +El paquete `nette/security` se encarga del [inicio de sesión de usuarios |authentication], la [verificación de permisos |authorization] y el [hashing de contraseñas |passwords]. -Instalación .[#toc-installation] --------------------------------- +Instalación +----------- -Descargue e instale el paquete utilizando [Composer |best-practices:composer]: +Descargue e instale la biblioteca usando [Composer|best-practices:composer]: ```shell composer require nette/security diff --git a/security/es/@left-menu.texy b/security/es/@left-menu.texy index 4dd872c445..e7d378d8ba 100644 --- a/security/es/@left-menu.texy +++ b/security/es/@left-menu.texy @@ -1,7 +1,7 @@ -Nette Seguridad -*************** -- [Descripción general |@home] -- [Autentificación |Authentication] -- [Autorización |Authorization] -- [Hashing de contraseña |passwords] -- [Configuración |Configuration] +Nette Security +************** +- [Introducción |@home] +- [Autenticación |authentication] +- [Autorización |authorization] +- [Hashing de contraseñas |passwords] +- [Configuración |configuration] diff --git a/security/es/@meta.texy b/security/es/@meta.texy new file mode 100644 index 0000000000..1670b124ad --- /dev/null +++ b/security/es/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Documentación}} diff --git a/security/es/authentication.texy b/security/es/authentication.texy index 4c8c794d02..97014a6ba8 100644 --- a/security/es/authentication.texy +++ b/security/es/authentication.texy @@ -1,48 +1,48 @@ -Autenticación de usuarios -************************* +Inicio de sesión de usuarios (Autenticación) +******************************************** <div class=perex> -Las aplicaciones web poco o nada necesitan ningún mecanismo para el login de los usuarios o para comprobar sus privilegios. En este capítulo hablaremos de: +Casi ninguna aplicación web puede prescindir de un mecanismo para iniciar sesión de usuarios y verificar los permisos de usuario. En este capítulo hablaremos sobre: -- login y logout de usuario -- autenticadores y autorizadores personalizados +- inicio y cierre de sesión de usuarios +- autenticadores personalizados </div> -→ [Instalación y requisitos |@home#Installation] +→ [Instalación y requisitos |@home#Instalación] -En los ejemplos utilizaremos un objeto de la clase [api:Nette\Security\User], que representa al usuario actual y que se obtiene pasándolo mediante [inyección de dependencia |dependency-injection:passing-dependencies]. En los presentadores basta con llamar a `$user = $this->getUser()`. +En los ejemplos utilizaremos un objeto de la clase [api:Nette\Security\User], que representa al usuario actual y al que puedes acceder solicitándolo mediante [inyección de dependencias |dependency-injection:passing-dependencies]. En los presenters, basta con llamar a `$user = $this->getUser()`. -Autenticación .[#toc-authentication] -==================================== +Autenticación +============= -Autenticación significa **inicio de sesión del usuario**, es decir, el proceso durante el cual se verifica la identidad de un usuario. El usuario suele identificarse mediante un nombre de usuario y una contraseña. La verificación la realiza el llamado [autenticador |#authenticator]. Si el login falla, se lanza `Nette\Security\AuthenticationException`. +Por autenticación se entiende el **inicio de sesión de usuarios**, es decir, el proceso mediante el cual se verifica si un usuario es realmente quien dice ser. Normalmente se demuestra mediante un nombre de usuario y una contraseña. La verificación la realiza el llamado [#autenticador]. Si el inicio de sesión falla, se lanza `Nette\Security\AuthenticationException`. ```php try { $user->login($username, $password); } catch (Nette\Security\AuthenticationException $e) { - $this->flashMessage('The username or password you entered is incorrect.'); + $this->flashMessage('El nombre de usuario o la contraseña son incorrectos'); } ``` -Así se cierra la sesión del usuario: +De esta manera cierras la sesión del usuario: ```php $user->logout(); ``` -Y comprobar si el usuario está conectado: +Y para saber si ha iniciado sesión: ```php -echo $user->isLoggedIn() ? 'yes' : 'no'; +echo $user->isLoggedIn() ? 'sí' : 'no'; ``` -Sencillo, ¿verdad? Y todos los aspectos de seguridad son manejados por Nette para usted. +Muy simple, ¿verdad? Y Nette se encarga de todos los aspectos de seguridad por ti. -En el presentador, puede verificar el inicio de sesión en el método `startup()` y redirigir a un usuario que no haya iniciado sesión a la página de inicio de sesión. +En los presenters, puedes verificar el inicio de sesión en el método `startup()` y redirigir al usuario no autenticado a la página de inicio de sesión. ```php protected function startup() @@ -55,39 +55,38 @@ protected function startup() ``` -Caducidad .[#toc-expiration] -============================ +Expiración +========== -El inicio de sesión del usuario expira junto con [la expiración del repositorio |#Storage for Logged User], que suele ser una sesión (véase el ajuste de [expiración de sesión |http:configuration#session] ). -Sin embargo, también se puede establecer un intervalo de tiempo más corto tras el cual se cierra la sesión del usuario. El método `setExpiration()`, que se llama antes de `login()`, se utiliza para este propósito. Proporcione una cadena con una hora relativa como parámetro: +El inicio de sesión del usuario expira junto con la [expiración del almacenamiento |#Almacenamiento del usuario conectado], que suele ser la sesión (ver configuración de [expiración de sesión |http:configuration#Sesión]). Sin embargo, también se puede establecer un intervalo de tiempo más corto, después del cual el usuario será desconectado. Para esto sirve el método `setExpiration()`, que se llama antes de `login()`. Como parámetro, indica una cadena con tiempo relativo: ```php -// el login expira tras 30 minutos de inactividad +// el inicio de sesión expira después de 30 minutos de inactividad $user->setExpiration('30 minutes'); // cancelar la expiración establecida $user->setExpiration(null); ``` -El método `$user->getLogoutReason()` indica si se ha cerrado la sesión del usuario porque ha expirado el intervalo de tiempo. Devuelve la constante `Nette\Security\UserStorage::LogoutInactivity` si el tiempo expiró o `UserStorage::LogoutManual` cuando se llamó al método `logout()`. +Si el usuario fue desconectado debido a la expiración del intervalo de tiempo, lo indica el método `$user->getLogoutReason()`, que devuelve la constante `Nette\Security\UserStorage::LogoutInactivity` (límite de tiempo expirado) o `UserStorage::LogoutManual` (desconectado por el método `logout()`). -Autenticador .[#toc-authenticator] -================================== +Autenticador +============ -Es un objeto que verifica los datos de acceso, es decir, normalmente el nombre y la contraseña. La implementación trivial es la clase [api:Nette\Security\SimpleAuthenticator], que puede definirse en [configuración |configuration]: +Es un objeto que verifica las credenciales de inicio de sesión, es decir, generalmente el nombre y la contraseña. Una forma trivial es la clase [api:Nette\Security\SimpleAuthenticator], que podemos definir en la [configuración|nette:configuring]: ```neon security: users: - # name: password - johndoe: secret123 - kathy: evenmoresecretpassword + # nombre: contraseña + frantisek: tajneheslo + katka: jestetajnejsiheslo ``` -Esta solución es más adecuada para realizar pruebas. Le mostraremos cómo crear un autenticador que verificará las credenciales contra una tabla de base de datos. +Esta solución es más adecuada para fines de prueba. Mostraremos cómo crear un autenticador que verificará las credenciales de inicio de sesión contra una tabla de base de datos. -Un autenticador es un objeto que implementa la interfaz [api:Nette\Security\Authenticator] con el método `authenticate()`. Su tarea es devolver la llamada [identidad |#identity] o lanzar una excepción `Nette\Security\AuthenticationException`. También sería posible proporcionar un código de error de grano fino `Authenticator::IdentityNotFound` o `Authenticator::InvalidCredential`. +El autenticador es un objeto que implementa la interfaz [api:Nette\Security\Authenticator] con el método `authenticate()`. Su tarea es devolver la llamada [#identidad] o lanzar una excepción `Nette\Security\AuthenticationException`. También sería posible indicar un código de error para distinguir más finamente la situación ocurrida: `Authenticator::IdentityNotFound` y `Authenticator::InvalidCredential`. ```php use Nette; @@ -108,25 +107,25 @@ class MyAuthenticator implements Nette\Security\Authenticator ->fetch(); if (!$row) { - throw new Nette\Security\AuthenticationException('User not found.'); + throw new Nette\Security\AuthenticationException('Usuario no encontrado.'); } if (!$this->passwords->verify($password, $row->password)) { - throw new Nette\Security\AuthenticationException('Invalid password.'); + throw new Nette\Security\AuthenticationException('Contraseña inválida.'); } return new SimpleIdentity( $row->id, - $row->role, // o array de roles + $row->role, // o un array de múltiples roles ['name' => $row->username], ); } } ``` -La clase MyAuthenticator se comunica con la base de datos a través de [Nette Database Explorer |database:explorer] y trabaja con la tabla `users`, donde la columna `username` contiene el nombre de usuario y la columna `password` contiene el [hash |passwords]. Tras verificar el nombre y la contraseña, devuelve la identidad con el ID del usuario, el rol (columna `role` de la tabla), que mencionaremos [más adelante |#roles], y un array con datos adicionales (en nuestro caso, el nombre de usuario). +La clase `MyAuthenticator` se comunica con la base de datos a través de [Nette Database Explorer|database:explorer] y trabaja con la tabla `users`, donde en la columna `username` está el nombre de inicio de sesión del usuario y en la columna `password` el [hash de la contraseña|passwords]. Después de verificar el nombre y la contraseña, devuelve la identidad, que lleva el ID del usuario, su rol (columna `role` en la tabla), del que hablaremos más [adelante |authorization#Roles], y un array con datos adicionales (en nuestro caso, el nombre de usuario). -Añadiremos el autenticador a la configuración [como un servicio |dependency-injection:services] del contenedor DI: +Añadiremos el autenticador a la configuración [como un servicio|dependency-injection:services] del contenedor DI: ```neon services: @@ -134,10 +133,10 @@ services: ``` -Eventos $onLoggedIn, $onLoggedOut .[#toc-onloggedin-onloggedout-events] ------------------------------------------------------------------------ +Eventos $onLoggedIn, $onLoggedOut +--------------------------------- -El objeto `Nette\Security\User` tiene los [eventos |nette:glossary#Events] `$onLoggedIn` y `$onLoggedOut`, por lo que puedes añadir callbacks que se activen después de un login exitoso o después de que el usuario se desconecte. +El objeto `Nette\Security\User` tiene [eventos |nette:glossary#Eventos] `$onLoggedIn` y `$onLoggedOut`, por lo que puedes agregar callbacks que se invocarán después de un inicio de sesión exitoso o después del cierre de sesión del usuario, respectivamente. ```php @@ -147,10 +146,10 @@ $user->onLoggedIn[] = function () { ``` -Identidad .[#toc-identity] -========================== +Identidad +========= -Una identidad es un conjunto de información sobre un usuario que devuelve el autenticador y que luego se almacena en una sesión y se recupera utilizando `$user->getIdentity()`. Así podemos obtener el id, roles y otros datos del usuario tal y como los pasamos en el autenticador: +La identidad representa un conjunto de información sobre el usuario que devuelve el autenticador y que posteriormente se almacena en la sesión y se obtiene mediante `$user->getIdentity()`. Por lo tanto, podemos obtener el id, los roles y otros datos del usuario, tal como los pasamos en el autenticador: ```php $user->getIdentity()->getId(); @@ -158,26 +157,26 @@ $user->getIdentity()->getId(); $user->getIdentity()->getRoles(); -// se puede acceder a los datos del usuario como propiedades +// los datos del usuario están disponibles como propiedades // el nombre que pasamos en MyAuthenticator $user->getIdentity()->name; ``` -Es importante destacar que cuando el usuario cierra la sesión utilizando `$user->logout()`, **la identidad no se borra** y sigue estando disponible. Por lo tanto, si la identidad existe, por sí misma no garantiza que el usuario también haya iniciado sesión. Si queremos borrar explícitamente la identidad, cerramos la sesión del usuario mediante `logout(true)`. +Lo importante es que al cerrar sesión mediante `$user->logout()`, **la identidad no se borra** y sigue estando disponible. Por lo tanto, aunque un usuario tenga una identidad, no tiene por qué haber iniciado sesión. Si quisiéramos borrar explícitamente la identidad, cerraríamos la sesión del usuario llamando a `logout(true)`. -Gracias a esto, todavía se puede suponer qué usuario está en el ordenador y, por ejemplo, mostrar ofertas personalizadas en la tienda electrónica, sin embargo, sólo se pueden mostrar sus datos personales después de iniciar sesión. +Gracias a esto, puedes seguir suponiendo qué usuario está en el ordenador y, por ejemplo, mostrarle ofertas personalizadas en una tienda online, pero solo puedes mostrarle sus datos personales después de iniciar sesión. -Identity es un objeto que implementa la interfaz [api:Nette\Security\IIdentity], la implementación por defecto es [api:Nette\Security\SimpleIdentity]. Y como se ha mencionado, la identidad se almacena en la sesión, por lo que si, por ejemplo, cambiamos el rol de alguno de los usuarios logueados, los datos antiguos se mantendrán en la identidad hasta que vuelva a loguearse. +La identidad es un objeto que implementa la interfaz [api:Nette\Security\IIdentity], la implementación predeterminada es [api:Nette\Security\SimpleIdentity]. Y como se mencionó, se mantiene en la sesión, por lo que si, por ejemplo, cambiamos el rol de alguno de los usuarios que han iniciado sesión, los datos antiguos permanecerán en su identidad hasta que vuelva a iniciar sesión. -Almacenamiento para el usuario conectado .[#toc-storage-for-logged-user] -======================================================================== +Almacenamiento del usuario conectado +==================================== -Los dos datos básicos sobre el usuario, es decir, si ha iniciado sesión y su [identidad |#identity], se suelen guardar en la sesión. La cual puede ser modificada. Para almacenar esta información es responsable un objeto que implemente la interfaz `Nette\Security\UserStorage`. Existen dos implementaciones estándar, la primera transmite los datos en una sesión y la segunda en una cookie. Estas son las clases `Nette\Bridges\SecurityHttp\SessionStorage` y `CookieStorage`. Usted puede elegir el almacenamiento y configurarlo muy convenientemente en la configuración de [seguridad › autenticación |configuration]. +Las dos informaciones básicas sobre el usuario, es decir, si ha iniciado sesión y su [#identidad], generalmente se transmiten en la sesión. Lo cual se puede cambiar. El almacenamiento de esta información está a cargo de un objeto que implementa la interfaz `Nette\Security\UserStorage`. Hay dos implementaciones estándar disponibles, la primera transmite datos en la sesión y la segunda en una cookie. Se trata de las clases `Nette\Bridges\SecurityHttp\SessionStorage` y `CookieStorage`. Puedes elegir el almacenamiento y configurarlo muy cómodamente en la configuración [security › authentication |configuration#Almacenamiento]. -También puedes controlar exactamente cómo se realizará el guardado (*sleep*) y el restablecimiento (*wakeup*) de la identidad. Todo lo que necesitas es que el autenticador implemente la interfaz `Nette\Security\IdentityHandler`. Este tiene dos métodos: `sleepIdentity()` es llamado antes de que la identidad sea escrita en el almacenamiento, y `wakeupIdentity()` es llamado después de que la identidad sea leída. Los métodos pueden modificar el contenido de la identidad, o sustituirla por un nuevo objeto devuelto. El método `wakeupIdentity()` puede incluso devolver `null`, que cierra la sesión del usuario. +Además, puedes influir en cómo se realizará exactamente el almacenamiento de la identidad (*sleep*) y la restauración (*wakeup*). Basta con que el autenticador implemente la interfaz `Nette\Security\IdentityHandler`. Esta tiene dos métodos: `sleepIdentity()` se llama antes de escribir la identidad en el almacenamiento y `wakeupIdentity()` después de leerla. Los métodos pueden modificar el contenido de la identidad o reemplazarla por un nuevo objeto que devuelvan. El método `wakeupIdentity()` puede incluso devolver `null`, lo que desconectará al usuario. -Como ejemplo, mostraremos una solución a una pregunta común sobre cómo actualizar los roles de identidad justo después de restaurar desde una sesión. En el método `wakeupIdentity()` pasamos los roles actuales a la identidad, por ejemplo desde la base de datos: +Como ejemplo, mostraremos la solución a la pregunta frecuente sobre cómo actualizar los roles en la identidad inmediatamente después de cargarla desde la sesión. En el método `wakeupIdentity()`, pasaremos a la identidad los roles actuales, por ejemplo, desde la base de datos: ```php final class Authenticator implements @@ -185,25 +184,25 @@ final class Authenticator implements { public function sleepIdentity(IIdentity $identity): IIdentity { - // aquí puedes cambiar la identidad antes de almacenar después de iniciar sesión, - // pero no lo necesitamos ahora + // aquí se puede modificar la identidad antes de escribirla en el almacenamiento después del inicio de sesión, + // pero ahora no lo necesitamos return $identity; } public function wakeupIdentity(IIdentity $identity): ?IIdentity { - // actualización de funciones en la identidad + // actualización de roles en la identidad $userId = $identity->getId(); $identity->setRoles($this->facade->getUserRoles($userId)); return $identity; } ``` -Y ahora volvemos al almacenamiento basado en cookies. Permite crear un sitio web en el que los usuarios pueden iniciar sesión sin necesidad de utilizar sesiones. Por lo tanto, no necesita escribir en el disco. Después de todo, así es como funciona el sitio web que estás leyendo ahora, incluido el foro. En este caso, la implementación de `IdentityHandler` es una necesidad. Sólo almacenaremos en la cookie un token aleatorio que representa al usuario logueado. +Y ahora volvemos al almacenamiento basado en cookies. Te permite crear un sitio web donde los usuarios pueden iniciar sesión sin necesidad de sesiones. Es decir, no necesita escribir en el disco. De hecho, así funciona también el sitio web que estás leyendo ahora, incluido el foro. En este caso, la implementación de `IdentityHandler` es una necesidad. En la cookie solo almacenaremos un token aleatorio que represente al usuario conectado. -Así que primero establecemos el almacenamiento deseado en la configuración usando `security › authentication › storage: cookie`. +Primero, en la configuración, establecemos el almacenamiento deseado mediante `security › authentication › storage: cookie`. -Añadiremos una columna `authtoken` en la base de datos, en la que cada usuario tendrá una cadena [completamente aleatoria, única e indescifrable|utils:random] de longitud suficiente (al menos 13 caracteres). El repositorio `CookieStorage` almacena sólo el valor `$identity->getId()` en la cookie, así que en `sleepIdentity()` reemplazamos la identidad original con un proxy con `authtoken` en el ID, por el contrario en el método `wakeupIdentity()` restauramos la identidad completa desde la base de datos según authtoken: +En la base de datos, crearemos una columna `authtoken`, en la que cada usuario tendrá una cadena [completamente aleatoria, única e impredecible|utils:random] de longitud suficiente (al menos 13 caracteres). El almacenamiento `CookieStorage` transmite en la cookie solo el valor `$identity->getId()`, por lo que en `sleepIdentity()` reemplazaremos la identidad original por una sustituta con `authtoken` en el ID, mientras que en el método `wakeupIdentity()`, según el authtoken, leeremos toda la identidad de la base de datos: ```php final class Authenticator implements @@ -212,7 +211,7 @@ final class Authenticator implements public function authenticate(string $username, string $password): SimpleIdentity { $row = $this->db->fetch('SELECT * FROM user WHERE username = ?', $username); - // comprobar contraseña + // verificamos la contraseña ... // devolvemos la identidad con todos los datos de la base de datos return new SimpleIdentity($row->id, null, (array) $row); @@ -220,13 +219,13 @@ final class Authenticator implements public function sleepIdentity(IIdentity $identity): SimpleIdentity { - // devolvemos una identidad proxy, donde en el ID es authtoken + // devolvemos una identidad sustituta, donde en el ID estará el authtoken return new SimpleIdentity($identity->authtoken); } public function wakeupIdentity(IIdentity $identity): ?SimpleIdentity { - // sustituir la identidad proxy por una identidad completa, como en authenticate() + // reemplazamos la identidad sustituta por la identidad completa, como en authenticate() $row = $this->db->fetch('SELECT * FROM user WHERE authtoken = ?', $identity->getId()); return $row ? new SimpleIdentity($row->id, null, (array) $row) @@ -236,16 +235,16 @@ final class Authenticator implements ``` -Autenticaciones Múltiples Independientes .[#toc-multiple-independent-authentications] -===================================================================================== +Múltiples inicios de sesión independientes +========================================== -Es posible tener varios usuarios registrados independientes dentro de un mismo sitio y una sesión a la vez. Por ejemplo, si queremos tener una autenticación independiente para el frontend y el backend, simplemente estableceremos un espacio de nombres de sesión único para cada uno de ellos: +Es posible tener varios usuarios conectados de forma independiente dentro de un mismo sitio web y una misma sesión. Si, por ejemplo, queremos tener una autenticación separada para la administración y la parte pública en el sitio web, basta con asignar a cada una su propio nombre: ```php $user->getStorage()->setNamespace('backend'); ``` -Es necesario tener en cuenta que esto debe establecerse en todos los sitios que pertenezcan al mismo segmento. Cuando utilicemos presentadores, estableceremos el espacio de nombres en el ancestro común - normalmente el BasePresenter. Para ello extenderemos el método [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()]: +Es importante recordar establecer el espacio de nombres siempre en todos los lugares pertenecientes a la parte correspondiente. Si usamos presenters, estableceremos el espacio de nombres en el ancestro común para esa parte, generalmente `BasePresenter`. Lo haremos extendiendo el método [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()]: ```php public function checkRequirements($element): void @@ -256,10 +255,10 @@ public function checkRequirements($element): void ``` -Autenticadores múltiples .[#toc-multiple-authenticators] --------------------------------------------------------- +Múltiples autenticadores +------------------------ -Dividir una aplicación en segmentos con autenticación independiente generalmente requiere diferentes autenticadores. Sin embargo, registrar dos clases que implementan Authenticator en config services provocaría un error porque Nette no sabría cuál de ellas debería [autocablearse |dependency-injection:autowiring] al objeto `Nette\Security\User`. Por eso debemos limitar el autocableado para ellos con `autowired: self` de forma que se active sólo cuando se solicite específicamente su clase: +La división de la aplicación en partes con inicio de sesión independiente generalmente requiere también diferentes autenticadores. Sin embargo, si registráramos dos clases que implementan `Authenticator` en la configuración de servicios, Nette no sabría cuál de ellas asignar automáticamente al objeto `Nette\Security\User` y mostraría un error. Por lo tanto, debemos limitar el [autowiring |dependency-injection:autowiring] para los autenticadores de modo que funcione solo cuando alguien solicite una clase específica, por ejemplo, `FrontAuthenticator`, lo cual logramos con la opción `autowired: self`: ```neon services: @@ -278,7 +277,7 @@ class SignPresenter extends Nette\Application\UI\Presenter } ``` -Sólo necesitamos establecer nuestro autenticador al objeto User antes de llamar al método [login() |api:Nette\Security\User::login()] lo que típicamente significa en el callback del formulario de login: +Estableceremos el autenticador del objeto `User` antes de llamar al método [login() |api:Nette\Security\User::login()], por lo que generalmente en el código del formulario que lo inicia sesión: ```php $form->onSuccess[] = function (Form $form, \stdClass $data) { diff --git a/security/es/authorization.texy b/security/es/authorization.texy index 76ac2aba64..6f7043d34d 100644 --- a/security/es/authorization.texy +++ b/security/es/authorization.texy @@ -1,48 +1,48 @@ -Control de acceso (autorización) -******************************** +Verificación de permisos (Autorización) +*************************************** .[perex] -La autorización determina si un usuario tiene privilegios suficientes, por ejemplo, para acceder a un recurso específico o para realizar una acción. La autorización presupone una autenticación previa correcta, es decir, que el usuario ha iniciado sesión. +La autorización determina si un usuario tiene permisos suficientes, por ejemplo, para acceder a un recurso determinado o para realizar alguna acción. La autorización presupone una autenticación previa exitosa, es decir, que el usuario haya iniciado sesión. -→ [Instalación y requisitos |@home#Installation] +→ [Instalación y requisitos |@home#Instalación] -En los ejemplos utilizaremos un objeto de la clase [api:Nette\Security\User], que representa al usuario actual y que se obtiene pasándolo mediante [inyección de dependencia |dependency-injection:passing-dependencies]. En los presentadores basta con llamar a `$user = $this->getUser()`. +En los ejemplos utilizaremos un objeto de la clase [api:Nette\Security\User], que representa al usuario actual y al que puedes acceder solicitándolo mediante [inyección de dependencias |dependency-injection:passing-dependencies]. En los presenters, basta con llamar a `$user = $this->getUser()`. -Para sitios web muy sencillos con administración, en los que no se distinguen derechos de usuario, es posible utilizar el método ya conocido como criterio de autorización `isLoggedIn()`. En otras palabras: una vez que un usuario ha iniciado sesión, tiene permisos para todas las acciones y viceversa. +En sitios web muy simples con administración, donde no se distinguen los permisos de los usuarios, es posible utilizar como criterio de autorización el método ya conocido `isLoggedIn()`. En otras palabras: tan pronto como el usuario inicia sesión, tiene todos los permisos y viceversa. ```php -if ($user->isLoggedIn()) { // is user logged in? - deleteItem(); // if so, he may delete an item +if ($user->isLoggedIn()) { // ¿ha iniciado sesión el usuario? + deleteItem(); // entonces tiene permiso para la operación } ``` -Roles .[#toc-roles] -------------------- +Roles +----- -El propósito de los roles es ofrecer una gestión de permisos más precisa y permanecer independiente del nombre de usuario. En cuanto un usuario se conecta, se le asignan uno o varios roles. Los roles pueden ser simples cadenas, por ejemplo, `admin`, `member`, `guest`, etc. Se especifican en el segundo argumento del constructor `SimpleIdentity`, ya sea como cadena o como matriz. +El propósito de los roles es ofrecer un control de permisos más preciso y permanecer independiente del nombre de usuario. A cada usuario, justo al iniciar sesión, le asignamos uno o más roles en los que actuará. Los roles pueden ser cadenas simples como `admin`, `member`, `guest`, etc. Se indican como segundo parámetro del constructor `SimpleIdentity`, ya sea como una cadena o un array de cadenas - roles. -Como criterio de autorización, utilizaremos ahora el método `isInRole()`, que comprueba si el usuario está en el rol dado: +Como criterio de autorización ahora usaremos el método `isInRole()`, que indica si el usuario actúa en el rol dado: ```php -if ($user->isInRole('admin')) { // is the admin role assigned to the user? - deleteItem(); // if so, he may delete an item +if ($user->isInRole('admin')) { // ¿está el usuario en el rol de administrador? + deleteItem(); // entonces tiene permiso para la operación } ``` -Como ya sabes, cerrar la sesión del usuario no borra su identidad. Por lo tanto, el método `getIdentity()` sigue devolviendo el objeto `SimpleIdentity`, incluyendo todos los roles concedidos. Nette Framework se adhiere al principio de "menos código, más seguridad", por lo que cuando compruebe los roles, no tiene que comprobar también si el usuario ha iniciado sesión. El método `isInRole()` funciona con **roles efectivos**, es decir, si el usuario está conectado, se utilizan los roles asignados a la identidad, si no está conectado, se utiliza en su lugar un rol especial automático `guest`. +Como ya sabes, después de cerrar la sesión del usuario, no es necesario borrar su identidad. Es decir, el método `getIdentity()` sigue devolviendo el objeto `SimpleIdentity`, incluidos todos los roles otorgados. Nette Framework sigue el principio „less code, more security“, donde escribir menos conduce a un código más seguro, por lo tanto, al verificar los roles no necesitas verificar también si el usuario ha iniciado sesión. El método `isInRole()` trabaja con **roles efectivos,** es decir, si el usuario ha iniciado sesión, se basa en los roles indicados en la identidad, si no ha iniciado sesión, tiene automáticamente el rol especial `guest`. -Autorizador .[#toc-authorizator] --------------------------------- +Autorizador +----------- -Además de los roles, introduciremos los términos recurso y operación: +Además de los roles, introduciremos los conceptos de recurso y operación: -- **rol** es un atributo del usuario - por ejemplo moderador, editor, visitante, usuario registrado, administrador, ... -- **recurso** es una unidad lógica de la aplicación - artículo, página, usuario, elemento de menú, encuesta, presentador, ... -- **operación** es una actividad específica, que el usuario puede o no hacer con *recurso* - ver, editar, borrar, votar, ... +- **rol** es una propiedad del usuario - p. ej., moderador, editor, visitante, usuario registrado, administrador... +- **recurso** (*resource*) es algún elemento lógico del sitio web - artículo, página, usuario, elemento de menú, encuesta, presenter, ... +- **operación** (*operation*) es alguna actividad específica que el usuario puede o no puede hacer con el recurso - por ejemplo, borrar, editar, crear, votar, ... -Un autorizador es un objeto que decide si un determinado *rol* tiene permiso para realizar una determinada *operación* con un *recurso* específico. Es un objeto que implementa la interfaz [api:Nette\Security\Authorizator] con un único método `isAllowed()`: +Un autorizador es un objeto que decide si un *rol* dado tiene permiso para realizar una *operación* determinada con un *recurso* específico. Es un objeto que implementa la interfaz [api:Nette\Security\Authorizator] con un único método `isAllowed()`: ```php class MyAuthorizator implements Nette\Security\Authorizator @@ -63,50 +63,50 @@ class MyAuthorizator implements Nette\Security\Authorizator } ``` -Añadimos el autorizador a la configuración [como un servicio |dependency-injection:services] del contenedor DI: +Añadimos el autorizador a la configuración [como un servicio|dependency-injection:services] del contenedor DI: ```neon services: - MyAuthorizator ``` -Y el siguiente es un ejemplo de uso. Nótese que esta vez llamamos al método `Nette\Security\User::isAllowed()`, no al del autorizador, por lo que no hay primer parámetro `$role`. Este método llama a `MyAuthorizator::isAllowed()` secuencialmente para todos los roles de usuario y devuelve true si al menos uno de ellos tiene permiso. +Y sigue un ejemplo de uso. Atención, esta vez llamamos al método `Nette\Security\User::isAllowed()`, no al autorizador, por lo que no está el primer parámetro `$role`. Este método llama a `MyAuthorizator::isAllowed()` secuencialmente para todos los roles del usuario y devuelve true si al menos uno de ellos tiene permiso. ```php -if ($user->isAllowed('file')) { // is user allowed to do everything with resource 'file'? +if ($user->isAllowed('file')) { // ¿puede el usuario hacer cualquier cosa con el recurso 'file'? useFile(); } -if ($user->isAllowed('file', 'delete')) { // is user allowed to delete a resource 'file'? +if ($user->isAllowed('file', 'delete')) { // ¿puede realizar 'delete' sobre el recurso 'file'? deleteFile(); } ``` -Ambos argumentos son opcionales y su valor por defecto significa *todo*. +Ambos parámetros son opcionales, el valor predeterminado `null` significa *cualquier cosa*. -ACL de permisos .[#toc-permission-acl] --------------------------------------- +Permission ACL +-------------- -Nette viene con una implementación incorporada del autorizador, la clase [api:Nette\Security\Permission], que ofrece una capa ACL (Lista de Control de Acceso) ligera y flexible para el control de permisos y accesos. Cuando trabajamos con esta clase, definimos roles, recursos y permisos individuales. Y los roles y recursos pueden formar jerarquías. Para explicarlo, mostraremos un ejemplo de una aplicación web: +Nette viene con una implementación incorporada de autorizador, la clase [api:Nette\Security\Permission] que proporciona al programador una capa ACL (Access Control List) ligera y flexible para gestionar permisos y accesos. Trabajar con ella consiste en definir roles, recursos y permisos individuales. Los roles y recursos permiten crear jerarquías. Para explicarlo, mostraremos un ejemplo de una aplicación web: -- `guest`: visitante que no ha iniciado sesión, con permiso para leer y navegar por la parte pública de la web, es decir, leer artículos, comentar y votar en encuestas -- `registered`: usuario conectado, que además puede publicar comentarios -- `admin`: puede gestionar artículos, comentarios y encuestas +- `guest`: visitante no autenticado, que puede leer y navegar por la parte pública del sitio web, es decir, leer artículos, comentarios y votar en encuestas +- `registered`: usuario registrado y autenticado, que además puede comentar +- `admin`: puede administrar artículos, comentarios y encuestas -Así pues, hemos definido determinados roles (`guest`, `registered` y `admin`) y mencionado recursos (`article`, `comments`, `poll`), a los que los usuarios pueden acceder o sobre los que pueden realizar acciones (`view`, `vote`, `add`, `edit`). +Hemos definido ciertos roles (`guest`, `registered` y `admin`) y mencionado recursos (`article`, `comment`, `poll`), a los que los usuarios con algún rol pueden acceder o realizar ciertas operaciones (`view`, `vote`, `add`, `edit`). -Creamos una instancia de la clase Permission y definimos **roles**. Es posible utilizar la herencia de roles, lo que garantiza que, por ejemplo, un usuario con el rol `admin` pueda hacer lo mismo que un visitante normal del sitio web (y por supuesto más). +Creamos una instancia de la clase Permission y definimos los **roles**. Se puede utilizar la llamada herencia de roles, que asegura que, por ejemplo, un usuario con el rol de administrador (`admin`) pueda hacer también lo que un visitante común del sitio web (y por supuesto, más). ```php $acl = new Nette\Security\Permission; $acl->addRole('guest'); -$acl->addRole('registered', 'guest'); // 'registered' inherits from 'guest' -$acl->addRole('admin', 'registered'); // and 'admin' inherits from 'registered' +$acl->addRole('registered', 'guest'); // 'registered' hereda de 'guest' +$acl->addRole('admin', 'registered'); // y de él hereda 'admin' ``` -Ahora definiremos una lista de **recursos** a los que los usuarios pueden acceder: +Ahora definimos también la lista de **recursos** a los que los usuarios pueden acceder. ```php $acl->addResource('article'); @@ -114,49 +114,49 @@ $acl->addResource('comment'); $acl->addResource('poll'); ``` -Los recursos también pueden utilizar la herencia, por ejemplo, podemos añadir `$acl->addResource('perex', 'article')`. +Los recursos también pueden usar herencia, sería posible, por ejemplo, especificar `$acl->addResource('perex', 'article')`. -Y ahora lo más importante. Definiremos entre ellos **reglas** que determinen quién puede hacer qué: +Y ahora lo más importante. Definimos entre ellos las reglas que determinan quién puede hacer qué con qué: ```php -// everything is denied now +// primero, nadie puede hacer nada -// let the guest view articles, comments and polls +// permitamos que guest pueda ver artículos, comentarios y encuestas $acl->allow('guest', ['article', 'comment', 'poll'], 'view'); -// and also vote in polls +// y en las encuestas, además, votar $acl->allow('guest', 'poll', 'vote'); -// the registered inherits the permissions from guesta, we will also let him to comment +// registrado hereda los derechos de guest, le damos además el derecho a comentar $acl->allow('registered', 'comment', 'add'); -// the administrator can view and edit anything +// el administrador puede ver y editar cualquier cosa $acl->allow('admin', $acl::All, ['view', 'edit', 'add']); ``` -¿Y si queremos **impedir** que alguien acceda a un recurso? +¿Qué pasa si queremos **impedir** a alguien el acceso a un recurso determinado? ```php -// administrator cannot edit polls, that would be undemocractic. +// el administrador no puede editar encuestas, eso sería antidemocrático $acl->deny('admin', 'poll', 'edit'); ``` -Ahora, cuando hayamos creado el conjunto de reglas, podemos simplemente hacer las consultas de autorización: +Ahora que hemos creado la lista de reglas, podemos simplemente hacer consultas de autorización: ```php -// can guest view articles? +// ¿puede guest ver artículos? $acl->isAllowed('guest', 'article', 'view'); // true -// can guest edit an article? +// ¿puede guest editar artículos? $acl->isAllowed('guest', 'article', 'edit'); // false -// can guest vote in polls? +// ¿puede guest votar en encuestas? $acl->isAllowed('guest', 'poll', 'vote'); // true -// may guest add comments? +// ¿puede guest comentar? $acl->isAllowed('guest', 'comment', 'add'); // false ``` -Lo mismo se aplica a un usuario registrado, pero también puede hacer comentarios: +Lo mismo se aplica al usuario registrado, pero este también puede comentar: ```php $acl->isAllowed('registered', 'article', 'view'); // true @@ -164,7 +164,7 @@ $acl->isAllowed('registered', 'comment', 'add'); // true $acl->isAllowed('registered', 'comment', 'edit'); // false ``` -El administrador puede editar todo excepto las encuestas: +El administrador puede editar todo, excepto las encuestas: ```php $acl->isAllowed('admin', 'poll', 'vote'); // true @@ -172,7 +172,7 @@ $acl->isAllowed('admin', 'poll', 'edit'); // false $acl->isAllowed('admin', 'comment', 'edit'); // true ``` -Los permisos también pueden ser evaluados dinámicamente y podemos dejar la decisión a nuestro propio callback, al que se le pasan todos los parámetros: +Los permisos también pueden evaluarse dinámicamente y podemos dejar la decisión a nuestro propio callback, al que se le pasarán todos los parámetros: ```php $assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { @@ -182,7 +182,7 @@ $assertion = function (Permission $acl, string $role, string $resource, string $ $acl->allow('registered', 'comment', null, $assertion); ``` -Pero, ¿cómo resolver una situación en la que los nombres de los roles y los recursos no son suficientes, es decir, nos gustaría definir que, por ejemplo, un rol `registered` puede editar un recurso `article` sólo si es su autor? Utilizaremos objetos en lugar de cadenas, el rol será el objeto [api:Nette\Security\Role] y el recurso [api:Nette\Security\Resource]. Sus métodos `getRoleId()` resp. `getResourceId()` devolverán las cadenas originales: +Pero, ¿cómo resolver, por ejemplo, la situación en la que no basta solo con los nombres de roles y recursos, sino que quisiéramos definir que, por ejemplo, el rol `registered` puede editar el recurso `article` solo si es su autor? En lugar de cadenas, usaremos objetos, el rol será un objeto [api:Nette\Security\Role] y el recurso un objeto [api:Nette\Security\Resource]. Sus métodos `getRoleId()` resp. `getResourceId()` devolverán las cadenas originales: ```php class Registered implements Nette\Security\Role @@ -207,19 +207,19 @@ class Article implements Nette\Security\Resource } ``` -Y ahora vamos a crear una regla: +Y ahora creamos la regla: ```php $assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { - $role = $acl->getQueriedRole(); // object Registered - $resource = $acl->getQueriedResource(); // object Article + $role = $acl->getQueriedRole(); // objeto Registered + $resource = $acl->getQueriedResource(); // objeto Article return $role->id === $resource->authorId; }; $acl->allow('registered', 'article', 'edit', $assertion); ``` -La ACL se consulta pasando objetos: +Y la consulta a la ACL se realiza pasando los objetos: ```php $user = new Registered(/* ... */); @@ -227,7 +227,7 @@ $article = new Article(/* ... */); $acl->isAllowed($user, $article, 'edit'); ``` -Un rol puede heredar de uno o más roles. Pero, ¿qué ocurre si un ancestro tiene una acción permitida y el otro la tiene denegada? Entonces entra en juego el *peso del rol* - el último rol en el conjunto de roles a heredar tiene el mayor peso, el primero el menor: +Un rol puede heredar de otro rol o de varios roles. Pero, ¿qué sucede si un ancestro tiene la acción prohibida y otro permitida? ¿Cuáles serán los derechos del descendiente? Se determina según el peso del rol: el último rol mencionado en la lista de ancestros tiene el mayor peso, el primer rol mencionado el menor. Es más claro con un ejemplo: ```php $acl = new Nette\Security\Permission; @@ -239,22 +239,22 @@ $acl->addResource('backend'); $acl->allow('admin', 'backend'); $acl->deny('guest', 'backend'); -// example A: role admin has lower weight than role guest +// caso A: el rol admin tiene menos peso que el rol guest $acl->addRole('john', ['admin', 'guest']); $acl->isAllowed('john', 'backend'); // false -// example B: role admin has greater weight than role guest +// caso B: el rol admin tiene más peso que guest $acl->addRole('mary', ['guest', 'admin']); $acl->isAllowed('mary', 'backend'); // true ``` -Los roles y recursos también se pueden eliminar (`removeRole()`, `removeResource()`), las reglas también se pueden revertir (`removeAllow()`, `removeDeny()`). El array de todos los roles padre directos devuelve `getRoleParents()`. Si dos entidades heredan una de otra devuelve `roleInheritsFrom()` y `resourceInheritsFrom()`. +Los roles y recursos también se pueden eliminar (`removeRole()`, `removeResource()`), también se pueden revertir las reglas (`removeAllow()`, `removeDeny()`). El array de todos los roles padres directos lo devuelve `getRoleParents()`, si dos entidades heredan una de otra lo devuelven `roleInheritsFrom()` y `resourceInheritsFrom()`. -Añadir como servicio .[#toc-add-as-a-service] ---------------------------------------------- +Añadir como servicios +--------------------- -Necesitamos añadir el ACL creado por nosotros a la configuración como un servicio para que pueda ser utilizado por el objeto `$user`, es decir, para que podamos utilizarlo en código por ejemplo `$user->isAllowed('article', 'view')`. Para ello escribiremos una factoría para ello: +Necesitamos pasar nuestra ACL creada a la configuración como un servicio, para que el objeto `$user` comience a usarla, es decir, para que sea posible usar en el código, por ejemplo, `$user->isAllowed('article', 'view')`. Para ello, escribiremos una fábrica para ella: ```php namespace App\Model; @@ -272,14 +272,14 @@ class AuthorizatorFactory } ``` -Y la añadiremos a la configuración: +Y la añadimos a la configuración: ```neon services: - App\Model\AuthorizatorFactory::create ``` -En presentadores, a continuación, puede verificar los permisos en el método `startup()`, por ejemplo: +En los presenters, puedes verificar los permisos, por ejemplo, en el método `startup()`: ```php protected function startup() diff --git a/security/es/configuration.texy b/security/es/configuration.texy index f2f0ed13df..95a629f851 100644 --- a/security/es/configuration.texy +++ b/security/es/configuration.texy @@ -1,71 +1,85 @@ -Configuración del control de acceso +Configuración de permisos de acceso *********************************** .[perex] -Resumen de las opciones de configuración de Nette Security. +Resumen de las opciones de configuración para Nette Security. -Si usted no está utilizando todo el marco, pero sólo esta biblioteca, lea [cómo cargar la configuración |bootstrap:]. +Si no utilizas todo el framework, sino solo esta librería, lee [cómo cargar la configuración|bootstrap:]. -Puede definir una lista de usuarios en la configuración para crear un [autenticador simple |authentication] (`Nette\Security\SimpleAuthenticator`). Debido a que las contraseñas son legibles en la configuración, esta solución es sólo para propósitos de prueba. +En la configuración se puede definir una lista de usuarios, y así crear un [autenticador simple|authentication] (`Nette\Security\SimpleAuthenticator`). Dado que en la configuración se indican las contraseñas en forma legible, esta solución es adecuada solo para fines de prueba. ```neon security: - # ¿muestra el panel de usuario en Tracy Bar? - debugger: ... # (bool) por defecto true + # ¿mostrar el panel de usuario en Tracy Bar? + debugger: ... # (bool) predeterminado es true users: # nombre: contraseña - johndoe: secreto123 + frantisek: tajneheslo # nombre, contraseña, rol y otros datos disponibles en la identidad - janedoe: - password: secret123 + dobrota: + password: tajneheslo roles: [admin] data: ... ``` -También puedes definir roles y recursos para crear una base de [autorizador |authorization] (`Nette\Security\Permission`): +Además, se pueden definir roles y recursos y crear así la base para un [autorizador|authorization] (`Nette\Security\Permission`): ```neon security: roles: guest: - registered: [guest] # registrado hereda de guest - admin: [registered] # y admin hereda de registered + registered: [guest] # registered hereda de guest + admin: [registered] # y de él hereda admin resources: article: - comment: [article] # hereda del artículo + comment: [article] # el recurso hereda de article poll: ``` -Almacenamiento de usuarios .[#toc-user-storage] ------------------------------------------------ +Almacenamiento +-------------- -Puede configurar cómo almacenar la información sobre el usuario conectado: +Se puede configurar cómo almacenar la información sobre el usuario conectado: ```neon security: authentication: - # después de cuanto tiempo de inactividad el usuario será desconectado - expiration: 30 minutos # (string) por defecto no se establece + # después de cuánto tiempo de inactividad será desconectado el usuario + expiration: 30 minutes # (string) predeterminado no está establecido # dónde almacenar la información sobre el usuario conectado - storage: session # (session|cookie) por defecto es sesión + storage: session # (session|cookie) predeterminado es session ``` -Si elige `cookie` como repositorio, también puede configurar las siguientes opciones: +Si eliges `cookie` como almacenamiento, puedes establecer también estas opciones: ```neon security: authentication: # nombre de la cookie - cookieName: userId # (string) por defecto es userid + cookieName: userId # (string) predeterminado es userid - # qué hosts pueden recibir la cookie + # dominios que aceptan la cookie cookieDomain: 'example.com' # (string|domain) - # restricciones al acceder a peticiones de origen cruzado - cookieSamesite: None # (Strict|Lax|None) por defecto Lax + # restricción al acceder desde otro dominio + cookieSamesite: None # (Strict|Lax|None) predeterminado es Lax ``` + + +Servicios DI +------------ + +Estos servicios se añaden al contenedor DI: + +| Nombre | Tipo | Descripción +|------------------------------------------------------------------------------------ +| `security.authenticator` | [api:Nette\Security\Authenticator] | [autenticador|authentication] +| `security.authorizator` | [api:Nette\Security\Authorizator] | [autorizador|authorization] +| `security.passwords` | [api:Nette\Security\Passwords] | [hashing de contraseñas|passwords] +| `security.user` | [api:Nette\Security\User] | usuario actual +| `security.userStorage` | [api:Nette\Security\UserStorage] | [#almacenamiento] diff --git a/security/es/passwords.texy b/security/es/passwords.texy index 736166fdc7..97c20b21ae 100644 --- a/security/es/passwords.texy +++ b/security/es/passwords.texy @@ -1,12 +1,12 @@ -Cifrado de contraseñas +Hashing de contraseñas ********************** .[perex] -Para gestionar la seguridad de nuestros usuarios, nunca almacenamos sus contraseñas en formato de texto plano, en su lugar almacenamos el hash de la contraseña. El hash no es una operación reversible, la contraseña no puede recuperarse. Sin embargo, la contraseña puede ser descifrada y para hacer que el descifrado sea lo más difícil posible tenemos que utilizar un algoritmo seguro. La clase [api:Nette\Security\Passwords] nos ayudará con eso. +Para garantizar la seguridad de nuestros usuarios, no almacenamos sus contraseñas en forma legible, sino que guardamos solo su huella (el llamado hash). A partir de la huella no se puede reconstruir la forma original de la contraseña. Es importante utilizar un algoritmo seguro para crear la huella. Con esto nos ayuda la clase [api:Nette\Security\Passwords]. -→ [Instalación y requisitos |@home#Installation] +→ [Instalación y requisitos |@home#Instalación] -El framework añade automáticamente un servicio `Nette\Security\Passwords` al contenedor DI bajo el nombre `security.passwords`, que se obtiene pasándolo mediante [inyección de dependencias |dependency-injection:passing-dependencies]: +El framework añade automáticamente al contenedor DI un servicio de tipo `Nette\Security\Passwords` bajo el nombre `security.passwords`, al que puedes acceder solicitándolo mediante [inyección de dependencias |dependency-injection:passing-dependencies]. ```php use Nette\Security\Passwords; @@ -24,44 +24,44 @@ class Foo __construct($algo=PASSWORD_DEFAULT, array $options=[]): string .[method] ======================================================================== -Elige qué [algoritmo seguro |https://www.php.net/manual/en/password.constants.php] se utiliza para el hash y cómo configurarlo. +Elegimos qué [algoritmo seguro|https://www.php.net/manual/en/password.constants.php] usar para generar el hash y configuramos sus parámetros. -Por defecto es `PASSWORD_DEFAULT`, por lo que la elección del algoritmo se deja a PHP. El algoritmo puede cambiar en nuevas versiones de PHP cuando se soporten algoritmos hash más fuertes. Por lo tanto, debe tener en cuenta que la longitud del hash resultante puede cambiar. Por lo tanto debe almacenar el hash resultante de manera que pueda almacenar suficientes caracteres, 255 es el ancho recomendado. +Por defecto se usa `PASSWORD_DEFAULT`, es decir, la elección del algoritmo se deja a PHP. El algoritmo puede cambiar en versiones más recientes de PHP si aparecen algoritmos de hashing más nuevos y fuertes. Por lo tanto, debes ser consciente de que la longitud del hash resultante puede cambiar, y deberías almacenarlo de una manera que pueda acomodar suficientes caracteres, 255 es el ancho recomendado. -Así es como usarías el algoritmo bcrypt y cambiarías la velocidad del hash usando el parámetro coste desde el 10 por defecto. En el año 2020, con coste 10, el hashing de una contraseña tarda aproximadamente 80ms, coste 11 tarda 160ms, coste 12 entonces 320ms, la escala es logarítmica. Cuanto más lento mejor, coste 10-12 se considera lo suficientemente lento para la mayoría de los casos de uso: +Ejemplo de configuración de la velocidad de hashing con el algoritmo bcrypt cambiando el parámetro cost: (en 2020 el valor predeterminado es 10, el hashing de la contraseña tarda aproximadamente 80ms, para cost 11 es aprox. 160ms, para cost 12 aprox. 320ms, cuanto más lento, mejor protección, considerándose ya suficiente una velocidad de 10-12) ```php -// we will hash passwords with 2^12 (2^cost) iterations of the bcrypt algorithm +// hashearemos las contraseñas con 2^12 (2^cost) iteraciones del algoritmo bcrypt $passwords = new Passwords(PASSWORD_BCRYPT, ['cost' => 12]); ``` -Con inyección de dependencia: +Mediante inyección de dependencias: ```neon services: security.passwords: Nette\Security\Passwords(::PASSWORD_BCRYPT, [cost: 12]) ``` -hash(string $passwords): string .[method] -========================================= +hash(string $password): string .[method] +======================================== Genera el hash de la contraseña. ```php -$res = $passwords->hash($password); // Hashes the password +$res = $passwords->hash($password); // Hashea la contraseña ``` -El resultado `$res` es una cadena que, además del propio hash, contiene el identificador del algoritmo utilizado, su configuración y la sal criptográfica (datos aleatorios para garantizar que se genera un hash diferente para la misma contraseña). Por lo tanto, es compatible con versiones anteriores; por ejemplo, si se cambian los parámetros, se pueden verificar los hashes almacenados con la configuración anterior. Todo este resultado se almacena en la base de datos, por lo que no es necesario almacenar la sal o la configuración por separado. +El resultado `$res` es una cadena que, además del propio hash, contiene también el identificador del algoritmo utilizado, su configuración y la sal criptográfica (datos aleatorios que aseguran que para la misma contraseña se genere un hash diferente). Por lo tanto, es compatible hacia atrás, si por ejemplo cambias los parámetros, también se podrán verificar los hashes guardados con la configuración anterior. Todo este resultado se guarda en la base de datos, por lo que no es necesario guardar la sal o la configuración por separado. verify(string $password, string $hash): bool .[method] ====================================================== -Averigua si la contraseña dada coincide con el hash dado. Obtiene la `$hash` de la base de datos por nombre de usuario o dirección de correo electrónico. +Verifica si la contraseña dada corresponde a la huella dada. Obtén `$hash` de la base de datos según el nombre de usuario o la dirección de correo electrónico introducidos. ```php if ($passwords->verify($password, $hash)) { - // Correct password + // contraseña correcta } ``` @@ -69,13 +69,13 @@ if ($passwords->verify($password, $hash)) { needsRehash(string $hash): bool .[method] ========================================= -Averigua si el hash coincide con las opciones dadas en el constructor. +Verifica si el hash corresponde a las opciones especificadas en el constructor. -Utiliza este método cuando, por ejemplo, estés cambiando los parámetros del hash. La verificación de contraseñas utilizará los parámetros almacenados con el hash y si `needsRehash()` devuelve true, tendrás que calcular el hash de nuevo, esta vez con los parámetros actualizados, y almacenarlo de nuevo en la base de datos. Esto asegura que los hashes de las contraseñas se "actualizarán" automáticamente cuando los usuarios estén iniciando sesión. +Es útil usarlo en el momento en que, por ejemplo, cambias la velocidad de hashing. La verificación se realiza según la configuración guardada y si `needsRehash()` devuelve `true`, entonces es necesario volver a crear el hash, esta vez con los nuevos parámetros, y guardarlo de nuevo en la base de datos. De esta manera, los hashes guardados se "actualizan" automáticamente al iniciar sesión los usuarios. ```php if ($passwords->needsRehash($hash)) { $hash = $passwords->hash($password); - // store $hash to database + // guardar $hash en la base de datos } ``` diff --git a/security/fr/@home.texy b/security/fr/@home.texy index 54b8a671d4..a8b59e833c 100644 --- a/security/fr/@home.texy +++ b/security/fr/@home.texy @@ -1,14 +1,14 @@ -Sécurité -******** +Nette Security +************** .[perex] -Le paquet `nette/security` est chargé de l'[authentification des utilisateurs |authentication], du [contrôle d'accès |authorization] et du [hachage des mots de passe |passwords]. +Le paquet `nette/security` est responsable de l'[authentification des utilisateurs |authentication], de la [vérification des autorisations |authorization] et du [hachage des mots de passe |passwords]. -Installation .[#toc-installation] ---------------------------------- +Installation +------------ -Téléchargez et installez le paquet en utilisant [Composer |best-practices:composer]: +Vous pouvez télécharger et installer la bibliothèque à l'aide de [Composer|best-practices:composer] : ```shell composer require nette/security diff --git a/security/fr/@left-menu.texy b/security/fr/@left-menu.texy index d4a126f2bf..42587ba6ad 100644 --- a/security/fr/@left-menu.texy +++ b/security/fr/@left-menu.texy @@ -1,7 +1,7 @@ -Sécurité Nette +Nette Security ************** -- [Vue d'ensemble |@home] -- [Authentification |Authentication] -- [Autorisation |Authorization] -- [Hachage de mot de passe |passwords] -- [Configuration] +- [Introduction |@home] +- [Authentification |authentication] +- [Autorisation |authorization] +- [Hachage de mots de passe |passwords] +- [Configuration |configuration] diff --git a/security/fr/@meta.texy b/security/fr/@meta.texy new file mode 100644 index 0000000000..72ae4b8db8 --- /dev/null +++ b/security/fr/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentation Nette}} diff --git a/security/fr/authentication.texy b/security/fr/authentication.texy index b601418fa1..650c9561f3 100644 --- a/security/fr/authentication.texy +++ b/security/fr/authentication.texy @@ -1,48 +1,48 @@ -Authentification des utilisateurs -********************************* +Connexion des utilisateurs (Authentification) +********************************************* <div class=perex> -Les applications Web peu nombreuses n'ont pas besoin d'un mécanisme de connexion des utilisateurs ou de vérification de leurs privilèges. Dans ce chapitre, nous parlerons de : +Presque aucune application web ne peut se passer d'un mécanisme de connexion des utilisateurs et de vérification des autorisations des utilisateurs. Dans ce chapitre, nous parlerons de : -- la connexion et la déconnexion des utilisateurs -- les authentificateurs et autorisateurs personnalisés +- connexion et déconnexion des utilisateurs +- authentificateurs personnalisés </div> -→ [Installation et exigences |@home#Installation] +→ [Installation et prérequis |@home#Installation] -Dans les exemples, nous utiliserons un objet de classe [api:Nette\Security\User], qui représente l'utilisateur actuel et que vous obtenez en le passant à l'aide de l'[injection de dépendances |dependency-injection:passing-dependencies]. Dans les présentateurs, il suffit d'appeler `$user = $this->getUser()`. +Dans les exemples, nous utiliserons l'objet de la classe [api:Nette\Security\User], qui représente l'utilisateur actuel et auquel vous pouvez accéder en le faisant passer via [l'injection de dépendances |dependency-injection:passing-dependencies]. Dans les presenters, il suffit d'appeler `$user = $this->getUser()`. -Authentification .[#toc-authentication] -======================================= +Authentification +================ -L'authentification signifie **la connexion de l'utilisateur**, c'est-à-dire le processus au cours duquel l'identité d'un utilisateur est vérifiée. L'utilisateur s'identifie généralement à l'aide d'un nom d'utilisateur et d'un mot de passe. La vérification est effectuée par l'[authentificateur |#authenticator]. Si la connexion échoue, le message `Nette\Security\AuthenticationException` est lancé. +L'authentification désigne la **connexion des utilisateurs**, c'est-à-dire le processus par lequel on vérifie si l'utilisateur est bien celui qu'il prétend être. Habituellement, il se prouve par un nom d'utilisateur et un mot de passe. La vérification est effectuée par un soi-disant [#authentificateur]. Si la connexion échoue, une `Nette\Security\AuthenticationException` est levée. ```php try { $user->login($username, $password); } catch (Nette\Security\AuthenticationException $e) { - $this->flashMessage('The username or password you entered is incorrect.'); + $this->flashMessage('Le nom d\'utilisateur ou le mot de passe est incorrect.'); } ``` -Voici comment déconnecter l'utilisateur : +De cette manière, vous déconnectez l'utilisateur : ```php $user->logout(); ``` -Et vérifier si l'utilisateur est connecté : +Et pour savoir s'il est connecté : ```php -echo $user->isLoggedIn() ? 'yes' : 'no'; +echo $user->isLoggedIn() ? 'oui' : 'non'; ``` -Simple, non ? Et tous les aspects de la sécurité sont gérés par Nette pour vous. +Très simple, n'est-ce pas ? Et Nette s'occupe de tous les aspects de sécurité pour vous. -Dans le présentateur, vous pouvez vérifier la connexion dans la méthode `startup()` et rediriger un utilisateur non connecté vers la page de connexion. +Dans les presenters, vous pouvez vérifier la connexion dans la méthode `startup()` et rediriger l'utilisateur non connecté vers la page de connexion. ```php protected function startup() @@ -55,39 +55,38 @@ protected function startup() ``` -Expiration .[#toc-expiration] -============================= +Expiration +========== -La connexion de l'utilisateur expire en même temps que l'[expiration du référentiel |#Storage for Logged User], qui est généralement une session (voir le paramètre d'[expiration de la session |http:configuration#session] ). -Cependant, vous pouvez également définir un intervalle de temps plus court après lequel l'utilisateur est déconnecté. La méthode `setExpiration()`, qui est appelée avant `login()`, est utilisée à cette fin. Fournissez une chaîne de caractères avec une heure relative comme paramètre : +La connexion de l'utilisateur expire en même temps que [l'expiration du stockage |#Stockage de l utilisateur connecté], qui est généralement la session (voir les paramètres [d'expiration de session |http:configuration#Session]). Cependant, il est possible de définir un intervalle de temps plus court, après lequel l'utilisateur sera déconnecté. Pour cela, on utilise la méthode `setExpiration()`, qui est appelée avant `login()`. Comme paramètre, indiquez une chaîne avec un temps relatif : ```php -// la connexion expire après 30 minutes d'inactivité +// la connexion expirera après 30 minutes d'inactivité $user->setExpiration('30 minutes'); -// annuler l'expiration définie +// annulation de l'expiration définie $user->setExpiration(null); ``` -La méthode `$user->getLogoutReason()` indique si l'utilisateur a été déconnecté parce que l'intervalle de temps a expiré. Elle renvoie soit la constante `Nette\Security\UserStorage::LogoutInactivity` si le temps a expiré, soit `UserStorage::LogoutManual` si la méthode `logout()` a été appelée. +Si l'utilisateur a été déconnecté en raison de l'expiration de l'intervalle de temps, la méthode `$user->getLogoutReason()` l'indiquera, renvoyant soit la constante `Nette\Security\UserStorage::LogoutInactivity` (le délai a expiré) soit `UserStorage::LogoutManual` (déconnecté par la méthode `logout()`). -Authentificateur .[#toc-authenticator] -====================================== +Authentificateur +================ -C'est un objet qui vérifie les données de connexion, c'est-à-dire généralement le nom et le mot de passe. L'implémentation triviale est la classe [api:Nette\Security\SimpleAuthenticator], qui peut être définie dans la [configuration]: +Il s'agit d'un objet qui vérifie les informations d'identification, c'est-à-dire généralement le nom et le mot de passe. Une forme triviale est la classe [api:Nette\Security\SimpleAuthenticator], que nous pouvons définir dans la [configuration|configuration] : ```neon security: users: - # name: password - johndoe: secret123 - kathy: evenmoresecretpassword + # nom: mot de passe + frantisek: motdepassesecret + katka: encoremotdepassesecret ``` -Cette solution est plus adaptée à des fins de test. Nous allons vous montrer comment créer un authentificateur qui vérifiera les informations d'identification par rapport à une table de base de données. +Cette solution convient plutôt à des fins de test. Nous allons montrer comment créer un authentificateur qui vérifiera les informations d'identification par rapport à une table de base de données. -Un authentificateur est un objet qui implémente l'interface [api:Nette\Security\Authenticator] avec la méthode `authenticate()`. Sa tâche consiste soit à renvoyer la dite [identité |#identity], soit à lever une exception `Nette\Security\AuthenticationException`. Il serait également possible de fournir un code d'erreur à grain fin `Authenticator::IdentityNotFound` ou `Authenticator::InvalidCredential`. +L'authentificateur est un objet implémentant l'interface [api:Nette\Security\Authenticator] avec la méthode `authenticate()`. Sa tâche est soit de retourner une soi-disant [#identité], soit de lever une exception `Nette\Security\AuthenticationException`. Il serait possible d'y ajouter un code d'erreur pour distinguer plus finement la situation : `Authenticator::IdentityNotFound` et `Authenticator::InvalidCredential`. ```php use Nette; @@ -108,25 +107,25 @@ class MyAuthenticator implements Nette\Security\Authenticator ->fetch(); if (!$row) { - throw new Nette\Security\AuthenticationException('User not found.'); + throw new Nette\Security\AuthenticationException('Utilisateur non trouvé.'); } if (!$this->passwords->verify($password, $row->password)) { - throw new Nette\Security\AuthenticationException('Mot de passe non valide.'); + throw new Nette\Security\AuthenticationException('Mot de passe invalide.'); } return new SimpleIdentity( $row->id, - $row->role, // ou tableau de rôles + $row->role, // ou un tableau de plusieurs rôles ['name' => $row->username], ); } } ``` -La classe MyAuthenticator communique avec la base de données par le biais de [Nette Database Explorer |database:explorer] et travaille avec la table `users`, où la colonne `username` contient le nom de connexion de l'utilisateur et la colonne `password` contient le [hachage |passwords]. Après avoir vérifié le nom et le mot de passe, elle renvoie l'identité avec l'ID de l'utilisateur, le rôle (colonne `role` dans la table), que nous mentionnerons [plus tard |#roles], et un tableau avec des données supplémentaires (dans notre cas, le nom d'utilisateur). +La classe `MyAuthenticator` communique avec la base de données par le biais de [Nette Database Explorer|database:explorer] et travaille avec la table `users`, où la colonne `username` contient le nom de connexion de l'utilisateur et la colonne `password` contient [l'empreinte du mot de passe|passwords]. Après vérification du nom et du mot de passe, elle renvoie l'identité, qui contient l'ID de l'utilisateur, son rôle (colonne `role` dans la table), dont nous parlerons plus en détail [plus tard |authorization#Rôles], et un tableau avec d'autres données (dans notre cas, le nom d'utilisateur). -Nous ajouterons l'authentificateur à la configuration [comme un service |dependency-injection:services] du conteneur DI : +Nous ajoutons encore l'authentificateur à la configuration [en tant que service|dependency-injection:services] du conteneur DI : ```neon services: @@ -134,50 +133,50 @@ services: ``` -Événements $onLoggedIn, $onLoggedOut .[#toc-onloggedin-onloggedout-events] --------------------------------------------------------------------------- +Événements $onLoggedIn, $onLoggedOut +------------------------------------ -L'objet `Nette\Security\User` possède des [événements |nette:glossary#Events] `$onLoggedIn` et `$onLoggedOut`, ce qui vous permet d'ajouter des rappels qui sont déclenchés après une connexion réussie ou après la déconnexion de l'utilisateur. +L'objet `Nette\Security\User` a des [événements |nette:glossary#Événements events] `$onLoggedIn` et `$onLoggedOut`, vous pouvez donc ajouter des rappels qui seront appelés après une connexion réussie ou après la déconnexion de l'utilisateur. ```php $user->onLoggedIn[] = function () { - // l'utilisateur vient de se connecter + // l'utilisateur vient d'être connecté }; ``` -Identité .[#toc-identity] -========================= +Identité +======== -Une identité est un ensemble d'informations sur un utilisateur qui est renvoyé par l'authentificateur et qui est ensuite stocké dans une session et récupéré en utilisant `$user->getIdentity()`. Nous pouvons donc récupérer l'identifiant, les rôles et les autres données de l'utilisateur comme nous les avons passés dans l'authentificateur : +L'identité représente un ensemble d'informations sur l'utilisateur, renvoyé par l'authentificateur et ensuite stocké dans la session, que nous obtenons à l'aide de `$user->getIdentity()`. Nous pouvons ainsi obtenir l'id, les rôles et d'autres données utilisateur, telles que nous les avons transmises dans l'authentificateur : ```php $user->getIdentity()->getId(); -// fonctionne également en raccourci $user->getId(); +// le raccourci $user->getId() fonctionne également ; $user->getIdentity()->getRoles(); -// les données de l'utilisateur peuvent être accessibles en tant que propriétés +// les données utilisateur sont accessibles comme des propriétés // le nom que nous avons transmis dans MyAuthenticator $user->getIdentity()->name; ``` -Il est important de noter que lorsque l'utilisateur se déconnecte en utilisant `$user->logout()`, **l'identité n'est pas supprimée** et est toujours disponible. Donc, si l'identité existe, elle ne garantit pas en soi que l'utilisateur est également connecté. Si nous voulons explicitement supprimer l'identité, nous déconnectons l'utilisateur par `logout(true)`. +Ce qui est important, c'est qu'après la déconnexion via `$user->logout()`, **l'identité n'est pas supprimée** et reste disponible. Ainsi, même si l'utilisateur a une identité, il n'est pas nécessairement connecté. Si nous voulions supprimer explicitement l'identité, nous déconnecterions l'utilisateur en appelant `logout(true)`. -Grâce à cela, vous pouvez toujours supposer quel utilisateur se trouve sur l'ordinateur et, par exemple, afficher des offres personnalisées dans l'e-shop, mais vous ne pouvez afficher ses données personnelles qu'après vous être connecté. +Grâce à cela, vous pouvez continuer à supposer quel utilisateur est devant l'ordinateur et, par exemple, lui afficher des offres personnalisées dans une boutique en ligne, mais vous ne pouvez afficher ses données personnelles qu'après connexion. -L'identité est un objet qui implémente l'interface [api:Nette\Security\IIdentity], l'implémentation par défaut est [api:Nette\Security\SimpleIdentity]. Et comme mentionné, l'identité est stockée dans la session, donc si, par exemple, nous changeons le rôle de certains des utilisateurs connectés, les anciennes données seront conservées dans l'identité jusqu'à ce qu'il se connecte à nouveau. +L'identité est un objet implémentant l'interface [api:Nette\Security\IIdentity], l'implémentation par défaut est [api:Nette\Security\SimpleIdentity]. Et comme mentionné, elle est maintenue dans la session, donc si, par exemple, nous changeons le rôle de l'un des utilisateurs connectés, les anciennes données resteront dans son identité jusqu'à sa prochaine connexion. -Stockage pour l'utilisateur connecté .[#toc-storage-for-logged-user] -==================================================================== +Stockage de l'utilisateur connecté +================================== -Les deux informations de base concernant l'utilisateur, à savoir s'il est connecté et son [identité |#identity], sont généralement transportées dans la session. Celles-ci peuvent être modifiées. Pour stocker ces informations, il faut un objet implémentant l'interface `Nette\Security\UserStorage`. Il existe deux implémentations standard, la première transmet les données dans une session et la seconde dans un cookie. Ce sont les classes `Nette\Bridges\SecurityHttp\SessionStorage` et `CookieStorage`. Vous pouvez choisir le stockage et le configurer de manière très pratique dans la configuration [sécurité › authentification |configuration]. +Deux informations de base sur l'utilisateur, à savoir s'il est connecté et son [#identité], sont généralement transmises dans la session. Ce qui peut être modifié. Le stockage de ces informations est géré par un objet implémentant l'interface `Nette\Security\UserStorage`. Deux implémentations standard sont disponibles, la première transmet les données dans la session et la seconde dans un cookie. Il s'agit des classes `Nette\Bridges\SecurityHttp\SessionStorage` et `CookieStorage`. Vous pouvez choisir le stockage et le configurer très facilement dans la configuration [security › authentication |configuration#Stockage]. -Vous pouvez également contrôler exactement comment la sauvegarde (*sleep*) et la restauration (*wakeup*) de l'identité se dérouleront. Il suffit que l'authentificateur implémente l'interface `Nette\Security\IdentityHandler`. Celle-ci possède deux méthodes : `sleepIdentity()` est appelée avant que l'identité ne soit écrite dans le stockage, et `wakeupIdentity()` est appelée après que l'identité ait été lue. Ces méthodes peuvent modifier le contenu de l'identité, ou la remplacer par un nouvel objet qui renvoie. La méthode `wakeupIdentity()` peut même renvoyer `null`, qui déconnecte l'utilisateur. +De plus, vous pouvez influencer la manière exacte dont le stockage de l'identité (*sleep*) et la restauration (*wakeup*) se dérouleront. Il suffit que l'authentificateur implémente l'interface `Nette\Security\IdentityHandler`. Celle-ci a deux méthodes : `sleepIdentity()` est appelée avant l'écriture de l'identité dans le stockage et `wakeupIdentity()` après sa lecture. Les méthodes peuvent modifier le contenu de l'identité, ou la remplacer par un nouvel objet qu'elles retournent. La méthode `wakeupIdentity()` peut même retourner `null`, ce qui déconnecte l'utilisateur. -À titre d'exemple, nous allons montrer une solution à une question courante sur la façon de mettre à jour les rôles d'une identité juste après la restauration d'une session. Dans la méthode `wakeupIdentity()`, nous transmettons les rôles actuels à l'identité, par exemple depuis la base de données : +À titre d'exemple, montrons la solution à la question fréquente de savoir comment mettre à jour les rôles dans l'identité immédiatement après le chargement depuis la session. Dans la méthode `wakeupIdentity()`, nous transmettons à l'identité les rôles actuels, par exemple depuis la base de données : ```php final class Authenticator implements @@ -185,7 +184,7 @@ final class Authenticator implements { public function sleepIdentity(IIdentity $identity): IIdentity { - // ici, vous pouvez modifier l'identité avant de la stocker après la connexion, + // ici on peut modifier l'identité avant l'écriture dans le stockage après la connexion, // mais nous n'en avons pas besoin maintenant return $identity; } @@ -199,11 +198,11 @@ final class Authenticator implements } ``` -Et maintenant, nous revenons au stockage basé sur les cookies. Il vous permet de créer un site web où les utilisateurs peuvent se connecter sans avoir besoin d'utiliser des sessions. Il n'est donc pas nécessaire d'écrire sur le disque. Après tout, c'est ainsi que fonctionne le site Web que vous êtes en train de lire, y compris le forum. Dans ce cas, l'implémentation de `IdentityHandler` est une nécessité. Nous stockerons seulement un jeton aléatoire représentant l'utilisateur connecté dans le cookie. +Et maintenant, revenons au stockage basé sur les cookies. Il vous permet de créer un site web où les utilisateurs peuvent se connecter sans avoir besoin de sessions. C'est-à-dire qu'il n'a pas besoin d'écrire sur le disque. D'ailleurs, le site web que vous consultez actuellement, y compris le forum, fonctionne de cette manière. Dans ce cas, l'implémentation de `IdentityHandler` est une nécessité. En effet, nous ne stockerons dans le cookie qu'un jeton aléatoire représentant l'utilisateur connecté. -Nous commençons donc par définir le stockage souhaité dans la configuration à l'aide de `security › authentication › storage: cookie`. +Tout d'abord, dans la configuration, nous définissons le stockage souhaité à l'aide de `security › authentication › storage: cookie`. -Nous ajouterons une colonne `authtoken` dans la base de données, dans laquelle chaque utilisateur aura une chaîne de caractères [complètement aléatoire, unique et impossible à deviner, |utils:random] d'une longueur suffisante (au moins 13 caractères). Le référentiel `CookieStorage` ne stocke que la valeur `$identity->getId()` dans le cookie, donc dans `sleepIdentity()` nous remplaçons l'identité originale par un proxy avec `authtoken` dans l'ID, au contraire dans la méthode `wakeupIdentity()` nous restaurons l'identité entière depuis la base de données selon authtoken : +Dans la base de données, nous créons une colonne `authtoken`, dans laquelle chaque utilisateur aura une chaîne [complètement aléatoire, unique et impossible à deviner|utils:random] d'une longueur suffisante (au moins 13 caractères). Le stockage `CookieStorage` ne transmet dans le cookie que la valeur `$identity->getId()`, donc dans `sleepIdentity()`, nous remplaçons l'identité originale par une identité substitutive avec `authtoken` dans l'ID, et inversement, dans la méthode `wakeupIdentity()`, nous lisons l'identité complète depuis la base de données en fonction de l'authtoken : ```php final class Authenticator implements @@ -212,7 +211,7 @@ final class Authenticator implements public function authenticate(string $username, string $password): SimpleIdentity { $row = $this->db->fetch('SELECT * FROM user WHERE username = ?', $username); - // vérifier le mot de passe + // nous vérifions le mot de passe ... // nous retournons l'identité avec toutes les données de la base de données return new SimpleIdentity($row->id, null, (array) $row); @@ -220,13 +219,13 @@ final class Authenticator implements public function sleepIdentity(IIdentity $identity): SimpleIdentity { - // nous renvoyons une identité proxy, où l'ID est l'authtoken + // nous retournons une identité substitutive, où l'ID sera l'authtoken return new SimpleIdentity($identity->authtoken); } public function wakeupIdentity(IIdentity $identity): ?SimpleIdentity { - // remplace l'identité du proxy par une identité complète, comme dans authenticate() + // nous remplaçons l'identité substitutive par l'identité complète, comme dans authenticate() $row = $this->db->fetch('SELECT * FROM user WHERE authtoken = ?', $identity->getId()); return $row ? new SimpleIdentity($row->id, null, (array) $row) @@ -236,16 +235,16 @@ final class Authenticator implements ``` -Authentifications indépendantes multiples .[#toc-multiple-independent-authentications] -====================================================================================== +Plusieurs connexions indépendantes +================================== -Il est possible d'avoir plusieurs utilisateurs connectés indépendants sur un même site et une seule session à la fois. Par exemple, si nous voulons avoir une authentification séparée pour le frontend et le backend, il suffit de définir un espace de nom de session unique pour chacun d'eux : +Il est possible d'avoir plusieurs utilisateurs connectés indépendamment au sein d'un même site web et d'une même session. Si, par exemple, nous voulons avoir une authentification distincte pour l'administration et la partie publique sur le site web, il suffit de définir un nom propre pour chacune d'elles : ```php $user->getStorage()->setNamespace('backend'); ``` -Il est nécessaire de garder à l'esprit que cela doit être défini à tous les endroits appartenant au même segment. Lorsque nous utilisons des présentateurs, nous définissons l'espace de nom dans l'ancêtre commun, généralement le BasePresenter. Pour ce faire, nous allons étendre la méthode [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()]: +Il est important de ne pas oublier de toujours définir l'espace de noms à tous les endroits appartenant à la partie concernée. Si nous utilisons des presenters, nous définissons l'espace de noms dans l'ancêtre commun pour la partie donnée - généralement BasePresenter. Nous le faisons en étendant la méthode [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()] : ```php public function checkRequirements($element): void @@ -256,10 +255,10 @@ public function checkRequirements($element): void ``` -Authentificateurs multiples .[#toc-multiple-authenticators] ------------------------------------------------------------ +Plusieurs authentificateurs +--------------------------- -Diviser une application en segments avec une authentification indépendante nécessite généralement des authentificateurs différents. Cependant, l'enregistrement de deux classes qui implémentent l'Authenticator dans les services de configuration déclencherait une erreur car Nette ne saurait pas laquelle d'entre elles doit être [autowired |dependency-injection:autowiring] à l'objet `Nette\Security\User`. C'est pourquoi nous devons limiter l'autowiring pour eux avec `autowired: self` afin qu'il ne soit activé que lorsque leur classe est spécifiquement demandée : +La division de l'application en parties avec connexion indépendante nécessite généralement également différents authentificateurs. Cependant, si nous enregistrions deux classes implémentant Authenticator dans la configuration des services, Nette ne saurait pas laquelle attribuer automatiquement à l'objet `Nette\Security\User`, et afficherait une erreur. Par conséquent, nous devons limiter [l'autowiring |dependency-injection:autowiring] pour les authentificateurs afin qu'il ne fonctionne que si quelqu'un demande une classe spécifique, par exemple FrontAuthenticator, ce que nous réalisons en choisissant `autowired: self` : ```neon services: @@ -278,7 +277,7 @@ class SignPresenter extends Nette\Application\UI\Presenter } ``` -Nous devons seulement définir notre authentificateur sur l'objet User avant d'appeler la méthode [login() |api:Nette\Security\User::login()], ce qui signifie généralement dans le callback du formulaire de connexion : +Nous définissons l'authentificateur de l'objet User avant d'appeler la méthode [login() |api:Nette\Security\User::login()], donc généralement dans le code du formulaire qui le connecte : ```php $form->onSuccess[] = function (Form $form, \stdClass $data) { diff --git a/security/fr/authorization.texy b/security/fr/authorization.texy index 6d4d25d155..d1292be74b 100644 --- a/security/fr/authorization.texy +++ b/security/fr/authorization.texy @@ -1,48 +1,48 @@ -Contrôle d'accès (autorisation) -******************************* +Vérification des autorisations (Autorisation) +********************************************* .[perex] -L'autorisation détermine si un utilisateur dispose de privilèges suffisants, par exemple pour accéder à une ressource spécifique ou pour effectuer une action. L'autorisation suppose une authentification préalable réussie, c'est-à-dire que l'utilisateur est connecté. +L'autorisation détermine si un utilisateur dispose des autorisations suffisantes, par exemple pour accéder à une ressource spécifique ou pour effectuer une action donnée. L'autorisation présuppose une authentification préalable réussie, c'est-à-dire que l'utilisateur est connecté. -→ [Installation et exigences |@home#Installation] +→ [Installation et prérequis |@home#Installation] -Dans les exemples, nous utiliserons un objet de classe [api:Nette\Security\User], qui représente l'utilisateur actuel et que vous obtenez en le passant à l'aide de l'[injection de dépendances |dependency-injection:passing-dependencies]. Dans les présentateurs, il suffit d'appeler `$user = $this->getUser()`. +Dans les exemples, nous utiliserons l'objet de la classe [api:Nette\Security\User], qui représente l'utilisateur actuel et auquel vous pouvez accéder en le faisant passer via [l'injection de dépendances |dependency-injection:passing-dependencies]. Dans les presenters, il suffit d'appeler `$user = $this->getUser()`. -Pour les sites Web très simples avec administration, où les droits des utilisateurs ne sont pas distingués, il est possible d'utiliser la méthode déjà connue comme critère d'autorisation `isLoggedIn()`. En d'autres termes : une fois qu'un utilisateur est connecté, il a des autorisations pour toutes les actions et vice versa. +Pour les sites web très simples avec une administration, où les autorisations des utilisateurs ne sont pas différenciées, il est possible d'utiliser la méthode déjà connue `isLoggedIn()` comme critère d'autorisation. En d'autres termes : dès que l'utilisateur est connecté, il dispose de toutes les autorisations et inversement. ```php if ($user->isLoggedIn()) { // l'utilisateur est-il connecté ? - deleteItem(); // si oui, il peut supprimer un élément + deleteItem(); // alors il a l'autorisation pour l'opération } ``` -Rôles .[#toc-roles] -------------------- +Rôles +----- -Le but des rôles est d'offrir une gestion plus précise des permissions et de rester indépendant du nom de l'utilisateur. Dès qu'un utilisateur se connecte, il se voit attribuer un ou plusieurs rôles. Les rôles eux-mêmes peuvent être de simples chaînes de caractères, par exemple, `admin`, `member`, `guest`, etc. Ils sont spécifiés dans le second argument du constructeur `SimpleIdentity`, sous la forme d'une chaîne ou d'un tableau. +L'objectif des rôles est d'offrir un contrôle plus précis des autorisations et de rester indépendant du nom d'utilisateur. À chaque utilisateur, dès sa connexion, nous attribuons un ou plusieurs rôles dans lesquels il agira. Les rôles peuvent être de simples chaînes de caractères, par exemple `admin`, `member`, `guest`, etc. Ils sont indiqués comme deuxième paramètre du constructeur `SimpleIdentity`, soit comme une chaîne, soit comme un tableau de chaînes - rôles. -Comme critère d'autorisation, nous allons maintenant utiliser la méthode `isInRole()`, qui vérifie si l'utilisateur est dans le rôle donné : +Comme critère d'autorisation, nous utiliserons maintenant la méthode `isInRole()`, qui indique si l'utilisateur agit dans le rôle donné : ```php -if ($user->isInRole('admin')) { // le rôle d'administrateur est-il attribué à l'utilisateur ? - deleteItem(); // si oui, il peut supprimer un élément +if ($user->isInRole('admin')) { // l'utilisateur est-il dans le rôle admin ? + deleteItem(); // alors il a l'autorisation pour l'opération } ``` -Comme vous le savez déjà, la déconnexion de l'utilisateur n'efface pas son identité. Ainsi, la méthode `getIdentity()` renvoie toujours l'objet `SimpleIdentity`, y compris tous les rôles accordés. Le Nette Framework adhère au principe "moins de code, plus de sécurité", donc lorsque vous vérifiez les rôles, vous ne devez pas vérifier si l'utilisateur est également connecté. La méthode `isInRole()` fonctionne avec des **rôles effectifs**, c'est-à-dire que si l'utilisateur est connecté, les rôles attribués à l'identité sont utilisés, s'il n'est pas connecté, un rôle spécial automatique `guest` est utilisé à la place. +Comme vous le savez déjà, après la déconnexion de l'utilisateur, son identité ne doit pas nécessairement être supprimée. Ainsi, la méthode `getIdentity()` continue de renvoyer l'objet `SimpleIdentity`, y compris tous les rôles attribués. Le Nette Framework adhère au principe « less code, more security », où moins d'écriture conduit à un code plus sécurisé, donc lors de la vérification des rôles, vous n'avez pas besoin de vérifier en plus si l'utilisateur est connecté. La méthode `isInRole()` travaille avec les **rôles effectifs,** c'est-à-dire que si l'utilisateur est connecté, elle se base sur les rôles indiqués dans l'identité, s'il n'est pas connecté, il a automatiquement le rôle spécial `guest`. -Autorisateur .[#toc-authorizator] ---------------------------------- +Autorisateur +------------ -En plus des rôles, nous allons introduire les termes ressource et opération : +En plus des rôles, nous introduirons également les concepts de ressource et d'opération : -- **rôle** est un attribut de l'utilisateur - par exemple modérateur, éditeur, visiteur, utilisateur enregistré, administrateur, ... -- **ressource** est une unité logique de l'application - article, page, utilisateur, élément de menu, sondage, présentateur, ... -- **opération** est une activité spécifique, que l'utilisateur peut ou ne peut pas faire avec la *ressource* - voir, modifier, supprimer, voter, ... +- **rôle** est une propriété de l'utilisateur - par exemple, modérateur, rédacteur, visiteur, utilisateur enregistré, administrateur... +- **ressource** (*resource*) est un élément logique du site web - article, page, utilisateur, élément de menu, sondage, presenter, ... +- **opération** (*operation*) est une activité spécifique que l'utilisateur peut ou ne peut pas effectuer avec la ressource - par exemple, supprimer, modifier, créer, voter, ... -Un autorisateur est un objet qui décide si un *rôle* donné a la permission d'effectuer une certaine *opération* avec une *ressource* spécifique. C'est un objet implémentant l'interface [api:Nette\Security\Authorizator] avec une seule méthode `isAllowed()`: +L'autorisateur est un objet qui décide si un *rôle* donné a la permission d'effectuer une *opération* spécifique sur une *ressource* donnée. Il s'agit d'un objet implémentant l'interface [api:Nette\Security\Authorizator] avec une seule méthode `isAllowed()` : ```php class MyAuthorizator implements Nette\Security\Authorizator @@ -63,50 +63,50 @@ class MyAuthorizator implements Nette\Security\Authorizator } ``` -Nous ajoutons l'authorizator à la configuration [comme un service |dependency-injection:services] du conteneur DI : +Nous ajoutons l'autorisateur à la configuration [en tant que service|dependency-injection:services] du conteneur DI : ```neon services: - MyAuthorizator ``` -Et voici un exemple d'utilisation. Notez que cette fois nous appelons la méthode `Nette\Security\User::isAllowed()`, et non celle de l'autorisateur, donc il n'y a pas de premier paramètre `$role`. Cette méthode appelle `MyAuthorizator::isAllowed()` séquentiellement pour tous les rôles d'utilisateur et renvoie true si au moins un d'entre eux a la permission. +Et voici un exemple d'utilisation. Attention, cette fois nous appelons la méthode `Nette\Security\User::isAllowed()`, et non l'autorisateur, donc le premier paramètre `$role` n'est pas là. Cette méthode appelle `MyAuthorizator::isAllowed()` successivement pour tous les rôles de l'utilisateur et renvoie true si au moins l'un d'eux a la permission. ```php -if ($user->isAllowed('file')) { // L'utilisateur est-il autorisé à tout faire avec la ressource 'file' ? - utilisezFile(); +if ($user->isAllowed('file')) { // l'utilisateur peut-il faire n'importe quoi avec la ressource 'file' ? + useFile(); } -if ($user->isAllowed('file', 'delete')) { // l'utilisateur est-il autorisé à supprimer une ressource 'fichier' ? +if ($user->isAllowed('file', 'delete')) { // peut-il effectuer 'delete' sur la ressource 'file' ? deleteFile(); } ``` -Les deux arguments sont facultatifs et leur valeur par défaut signifie *tout*. +Les deux paramètres sont facultatifs, la valeur par défaut `null` signifie *n'importe quoi*. -Permission ACL .[#toc-permission-acl] -------------------------------------- +Permission ACL +-------------- -Nette est livré avec une implémentation intégrée de l'autorisateur, la classe [api:Nette\Security\Permission], qui offre une couche ACL (Access Control List) légère et flexible pour la permission et le contrôle d'accès. Lorsque nous travaillons avec cette classe, nous définissons des rôles, des ressources et des permissions individuelles. Et les rôles et les ressources peuvent former des hiérarchies. Pour expliquer, nous allons montrer un exemple d'application web : +Nette fournit une implémentation intégrée de l'autorisateur, la classe [api:Nette\Security\Permission] qui fournit au programmeur une couche ACL (Access Control List) légère et flexible pour gérer les autorisations et les accès. Son utilisation consiste à définir des rôles, des ressources et des autorisations individuelles. Les rôles et les ressources permettent de créer des hiérarchies. Pour expliquer, prenons l'exemple d'une application web : -- `guest`: visiteur qui n'est pas connecté, autorisé à lire et à parcourir la partie publique du web, c'est-à-dire à lire des articles, à commenter et à voter dans des sondages. -- `registered`: utilisateur connecté, qui peut en plus poster des commentaires. -- `admin`: peut gérer les articles, les commentaires et les sondages. +- `guest` : visiteur non connecté, qui peut lire et parcourir la partie publique du site, c'est-à-dire lire les articles, les commentaires et voter aux sondages +- `registered` : utilisateur enregistré connecté, qui peut en plus commenter +- `admin` : peut gérer les articles, les commentaires et les sondages -Nous avons donc défini certains rôles (`guest`, `registered` et `admin`) et mentionné des ressources (`article`, `comments`, `poll`), auxquelles les utilisateurs peuvent accéder ou sur lesquelles ils peuvent agir (`view`, `vote`, `add`, `edit`). +Nous avons donc défini certains rôles (`guest`, `registered` et `admin`) et mentionné des ressources (`article`, `comment`, `poll`), auxquelles les utilisateurs avec un certain rôle peuvent accéder ou effectuer certaines opérations (`view`, `vote`, `add`, `edit`). -Nous créons une instance de la classe Permission et définissons des **rôles**. Il est possible d'utiliser l'héritage des rôles, ce qui garantit que, par exemple, un utilisateur ayant le rôle `admin` peut faire ce qu'un visiteur ordinaire du site Web peut faire (et bien sûr plus). +Nous créons une instance de la classe Permission et définissons les **rôles**. Il est possible d'utiliser l'héritage de rôles, qui garantit que, par exemple, un utilisateur avec le rôle d'administrateur (`admin`) peut faire ce qu'un visiteur ordinaire du site peut faire (et bien sûr plus). ```php $acl = new Nette\Security\Permission; $acl->addRole('guest'); -$acl->addRole('registered', 'guest'); // 'registered' hérite de 'guest'. -$acl->addRole('admin', 'registered'); // et 'admin' hérite de 'registered'. +$acl->addRole('registered', 'guest'); // 'registered' hérite de 'guest' +$acl->addRole('admin', 'registered'); // et 'admin' hérite de lui ``` -Nous allons maintenant définir une liste de **ressources** auxquelles les utilisateurs peuvent accéder : +Maintenant, définissons également la liste des **ressources** auxquelles les utilisateurs peuvent accéder. ```php $acl->addResource('article'); @@ -114,49 +114,49 @@ $acl->addResource('comment'); $acl->addResource('poll'); ``` -Les ressources peuvent également utiliser l'héritage, par exemple, nous pouvons ajouter `$acl->addResource('perex', 'article')`. +Les ressources peuvent également utiliser l'héritage, il serait possible par exemple de spécifier `$acl->addResource('perex', 'article')`. -Et maintenant, la chose la plus importante. Nous allons définir entre eux des **règles** déterminant qui peut faire quoi : +Et maintenant, le point le plus important. Définissons entre eux les règles déterminant qui peut faire quoi avec quoi : ```php -// tout est refusé maintenant +// d'abord, personne ne peut rien faire -// laissez l'invité voir les articles, les commentaires et les sondages +// que guest puisse consulter les articles, les commentaires et les sondages $acl->allow('guest', ['article', 'comment', 'poll'], 'view'); -// et aussi voter dans les sondages +// et dans les sondages, en plus, voter $acl->allow('guest', 'poll', 'vote'); -// l'enregistré hérite des permissions de l'invité, nous le laisserons également commenter. +// l'utilisateur enregistré hérite des droits de guest, donnons-lui en plus le droit de commenter $acl->allow('registered', 'comment', 'add'); -// l'administrateur peut voir et modifier tout ce qu'il veut +// l'administrateur peut consulter et modifier n'importe quoi $acl->allow('admin', $acl::All, ['view', 'edit', 'add']); ``` -Et si nous voulons **empêcher** quelqu'un d'accéder à une ressource ? +Et si nous voulons **empêcher** quelqu'un d'accéder à une ressource spécifique ? ```php -// L'administrateur ne peut pas modifier les sondages, ce serait contraire à la pratique. +// l'administrateur ne peut pas modifier les sondages, ce serait antidémocratique $acl->deny('admin', 'poll', 'edit'); ``` -Maintenant que nous avons créé l'ensemble des règles, nous pouvons simplement poser les questions d'autorisation : +Maintenant que nous avons créé la liste des règles, nous pouvons simplement poser des questions d'autorisation : ```php -// les invités peuvent-ils voir les articles ? +// guest peut-il consulter les articles ? $acl->isAllowed('guest', 'article', 'view'); // true -// un invité peut-il modifier un article ? +// guest peut-il modifier les articles ? $acl->isAllowed('guest', 'article', 'edit'); // false -// les invités peuvent-ils voter dans les sondages ? +// guest peut-il voter aux sondages ? $acl->isAllowed('guest', 'poll', 'vote'); // true -// les invités peuvent-ils ajouter des commentaires ? +// guest peut-il commenter ? $acl->isAllowed('guest', 'comment', 'add'); // false ``` -La même chose s'applique à un utilisateur enregistré, mais il peut aussi commenter : +La même chose s'applique à l'utilisateur enregistré, mais il peut aussi commenter : ```php $acl->isAllowed('registered', 'article', 'view'); // true @@ -172,7 +172,7 @@ $acl->isAllowed('admin', 'poll', 'edit'); // false $acl->isAllowed('admin', 'comment', 'edit'); // true ``` -Les permissions peuvent également être évaluées dynamiquement et nous pouvons laisser la décision à notre propre callback, à laquelle tous les paramètres sont passés : +Les autorisations peuvent également être évaluées dynamiquement et nous pouvons laisser la décision à notre propre rappel, auquel tous les paramètres sont transmis : ```php $assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { @@ -182,7 +182,7 @@ $assertion = function (Permission $acl, string $role, string $resource, string $ $acl->allow('registered', 'comment', null, $assertion); ``` -Mais comment résoudre une situation où les noms des rôles et des ressources ne sont pas suffisants, c'est-à-dire que nous voudrions définir que, par exemple, un rôle `registered` peut éditer une ressource `article` seulement s'il en est l'auteur ? Nous utiliserons des objets au lieu de chaînes de caractères, le rôle sera l'objet [api:Nette\Security\Role] et la source [api:Nette\Security\Resource]. Leurs méthodes `getRoleId()` resp. `getResourceId()` retourneront les chaînes de caractères originales : +Mais comment gérer, par exemple, une situation où les noms des rôles et des ressources ne suffisent pas, mais où nous voudrions définir que, par exemple, le rôle `registered` ne peut modifier la ressource `article` que s'il en est l'auteur ? Au lieu de chaînes de caractères, nous utiliserons des objets, le rôle sera un objet [api:Nette\Security\Role] et la ressource un objet [api:Nette\Security\Resource]. Leurs méthodes `getRoleId()` resp. `getResourceId()` renverront les chaînes originales : ```php class Registered implements Nette\Security\Role @@ -207,19 +207,19 @@ class Article implements Nette\Security\Resource } ``` -Et maintenant, créons une règle : +Et maintenant, créons la règle : ```php $assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { - $role = $acl->getQueriedRole(); // object Registered - $resource = $acl->getQueriedResource(); // object Article + $role = $acl->getQueriedRole(); // objet Registered + $resource = $acl->getQueriedResource(); // objet Article return $role->id === $resource->authorId; }; $acl->allow('registered', 'article', 'edit', $assertion); ``` -L'ACL est interrogée en passant des objets : +Et la requête à l'ACL se fait en passant les objets : ```php $user = new Registered(/* ... */); @@ -227,7 +227,7 @@ $article = new Article(/* ... */); $acl->isAllowed($user, $article, 'edit'); ``` -Un rôle peut hériter d'un ou plusieurs autres rôles. Mais que se passe-t-il, si un ancêtre a une certaine action autorisée et l'autre l'a refusée ? C'est alors que le *poids du rôle* entre en jeu - le dernier rôle dans le tableau des rôles à hériter a le plus grand poids, le premier le plus petit : +Un rôle peut hériter d'un autre rôle ou de plusieurs rôles. Mais que se passe-t-il si un ancêtre a une action interdite et un autre autorisée ? Quels seront les droits du descendant ? Ceci est déterminé par le poids du rôle - le dernier rôle mentionné dans la liste des ancêtres a le poids le plus élevé, le premier rôle mentionné a le poids le plus faible. C'est plus clair avec un exemple : ```php $acl = new Nette\Security\Permission; @@ -239,22 +239,22 @@ $acl->addResource('backend'); $acl->allow('admin', 'backend'); $acl->deny('guest', 'backend'); -// exemple A : le rôle admin a moins de poids que le rôle guest +// cas A : le rôle admin a moins de poids que le rôle guest $acl->addRole('john', ['admin', 'guest']); $acl->isAllowed('john', 'backend'); // false -// exemple B : le rôle admin a plus de poids que le rôle guest +// cas B : le rôle admin a plus de poids que guest $acl->addRole('mary', ['guest', 'admin']); $acl->isAllowed('mary', 'backend'); // true ``` -Les rôles et les ressources peuvent également être supprimés (`removeRole()`, `removeResource()`), les règles peuvent également être annulées (`removeAllow()`, `removeDeny()`). Le tableau de tous les rôles parents directs renvoie à `getRoleParents()`. Le fait que deux entités héritent l'une de l'autre renvoie `roleInheritsFrom()` et `resourceInheritsFrom()`. +Les rôles et les ressources peuvent également être supprimés (`removeRole()`, `removeResource()`), les règles peuvent également être annulées (`removeAllow()`, `removeDeny()`). Le tableau de tous les rôles parents directs est renvoyé par `getRoleParents()`, si deux entités héritent l'une de l'autre est renvoyé par `roleInheritsFrom()` et `resourceInheritsFrom()`. -Ajouter en tant que service .[#toc-add-as-a-service] ----------------------------------------------------- +Ajout en tant que services +-------------------------- -Nous devons ajouter l'ACL que nous avons créé à la configuration en tant que service pour qu'il puisse être utilisé par l'objet `$user`, c'est-à-dire pour que nous puissions l'utiliser dans le code par exemple `$user->isAllowed('article', 'view')`. Dans ce but, nous allons écrire une factory pour cela : +Nous devons enregistrer notre ACL créée dans la configuration en tant que service, afin que l'objet `$user` commence à l'utiliser, c'est-à-dire pour pouvoir utiliser dans le code par exemple `$user->isAllowed('article', 'view')`. À cette fin, nous écrirons une factory pour cela : ```php namespace App\Model; @@ -272,14 +272,14 @@ class AuthorizatorFactory } ``` -Et nous allons l'ajouter à la configuration : +Et nous l'ajoutons à la configuration : ```neon services: - App\Model\AuthorizatorFactory::create ``` -Dans les présentateurs, vous pouvez ensuite vérifier les autorisations dans la méthode `startup()`, par exemple : +Dans les presenters, vous pouvez ensuite vérifier les autorisations, par exemple dans la méthode `startup()` : ```php protected function startup() diff --git a/security/fr/configuration.texy b/security/fr/configuration.texy index 82c7e7fa2e..6a5d3e0cf1 100644 --- a/security/fr/configuration.texy +++ b/security/fr/configuration.texy @@ -1,71 +1,85 @@ -Configuration du contrôle d'accès -********************************* +Configuration des autorisations d'accès +*************************************** .[perex] -Aperçu des options de configuration de Nette Security. +Aperçu des options de configuration pour Nette Security. -Si vous n'utilisez pas l'ensemble du framework, mais uniquement cette bibliothèque, lisez [comment charger la configuration |bootstrap:]. +Si vous n'utilisez pas l'ensemble du framework, mais seulement cette bibliothèque, lisez [comment charger la configuration|bootstrap:]. -Vous pouvez définir une liste d'utilisateurs dans la configuration pour créer un [authentificateur simple |authentication] (`Nette\Security\SimpleAuthenticator`). Comme les mots de passe sont lisibles dans la configuration, cette solution est uniquement destinée à des fins de test. +Dans la configuration, il est possible de définir une liste d'utilisateurs, et ainsi de créer un [simple authentificateur|authentication] (`Nette\Security\SimpleAuthenticator`). Comme les mots de passe sont indiqués en clair dans la configuration, cette solution ne convient qu'à des fins de test. ```neon security: - # montre le panneau de l'utilisateur dans Tracy Bar ? - debugger: ... # (bool) par défaut à true + # afficher le panneau utilisateur dans la barre Tracy ? + debugger: ... # (bool) la valeur par défaut est true users: # nom: mot de passe - johndoe: secret123 + frantisek: motdepassesecret # nom, mot de passe, rôle et autres données disponibles dans l'identité - janedoe: - password: secret123 + dobrota: + password: motdepassesecret roles: [admin] data: ... ``` -Vous pouvez également définir des rôles et des ressources pour créer une base d'[autorisation |authorization] (`Nette\Security\Permission`): +Ensuite, il est possible de définir des rôles et des ressources et de créer ainsi la base pour un [autorisateur|authorization] (`Nette\Security\Permission`) : ```neon security: roles: guest: - registered: [guest] # registered hérite de guest - admin: [registered] # et admin hérite de registered + registered: [guest] # registered hérite de guest + admin: [registered] # et admin hérite de lui resources: article: - comment: [article] # ressource hérite de article + comment: [article] # la ressource hérite de article poll: ``` -Stockage des utilisateurs .[#toc-user-storage] ----------------------------------------------- +Stockage +-------- -Vous pouvez configurer la manière de stocker les informations sur l'utilisateur connecté: +Il est possible de configurer comment conserver les informations sur l'utilisateur connecté : ```neon security: authentication: - # après combien de temps d'inactivité l'utilisateur sera déconnecté - expiration: 30 minutes # (string) default is not set + # après combien de temps d'inactivité l'utilisateur sera-t-il déconnecté + expiration: 30 minutes # (string) la valeur par défaut n'est pas définie # où stocker les informations sur l'utilisateur connecté - storage: session # (session|cookie) par défaut: session + storage: session # (session|cookie) la valeur par défaut est session ``` -Si vous choisissez `cookie` comme référentiel, vous pouvez également définir les options suivantes: +Si vous choisissez `cookie` comme stockage, vous pouvez définir ces options supplémentaires : ```neon security: authentication: # nom du cookie - cookieName: userId # (string) výchozí je userid + cookieName: userId # (string) la valeur par défaut est userid - # quels hôtes sont autorisés à recevoir le cookie + # domaines qui acceptent le cookie cookieDomain: 'example.com' # (string|domain) - # restrictions lors de l'accès à une demande d'origine croisée - cookieSamesite: None # (Strict|Lax|None) valeur par défaut: Lax + # restriction lors de l'accès depuis un autre domaine + cookieSamesite: None # (Strict|Lax|None) la valeur par défaut est Lax ``` + + +Services DI +----------- + +Ces services sont ajoutés au conteneur DI : + +| Nom | Type | Description +|-------------------------------------------------------------------------------------| +| `security.authenticator` | [api:Nette\Security\Authenticator] | [authentificateur|authentication] +| `security.authorizator` | [api:Nette\Security\Authorizator] | [autorisateur|authorization] +| `security.passwords` | [api:Nette\Security\Passwords] | [hachage de mots de passe|passwords] +| `security.user` | [api:Nette\Security\User] | utilisateur actuel +| `security.userStorage` | [api:Nette\Security\UserStorage] | [#stockage] diff --git a/security/fr/passwords.texy b/security/fr/passwords.texy index 2788f87e18..c7703d1ed8 100644 --- a/security/fr/passwords.texy +++ b/security/fr/passwords.texy @@ -1,12 +1,12 @@ -Hachage de mot de passe -*********************** +Hachage de mots de passe +************************ .[perex] -Pour gérer la sécurité de nos utilisateurs, nous ne stockons jamais leurs mots de passe en clair, nous stockons plutôt le hachage du mot de passe. Le hachage n'est pas une opération réversible, le mot de passe ne peut pas être récupéré. Le mot de passe peut cependant être craqué et pour rendre le craquage aussi difficile que possible, nous devons utiliser un algorithme sécurisé. La classe [api:Nette\Security\Passwords] nous aidera à le faire. +Pour assurer la sécurité de nos utilisateurs, nous ne stockons pas leurs mots de passe en clair, mais nous enregistrons uniquement leur empreinte (appelée hash). Il est impossible de reconstruire le mot de passe original à partir de cette empreinte. Il est important d'utiliser un algorithme sécurisé pour créer l'empreinte. La classe [api:Nette\Security\Passwords] nous aide pour cela. -→ [Installation et configuration requise |@home#Installation] +→ [Installation et prérequis |@home#Installation] -Le framework ajoute automatiquement un service `Nette\Security\Passwords` au conteneur DI sous le nom `security.passwords`, que vous obtenez en le passant en utilisant l'[injection de dépendance |dependency-injection:passing-dependencies]: +Le framework ajoute automatiquement au conteneur DI un service de type `Nette\Security\Passwords` sous le nom `security.passwords`, auquel vous pouvez accéder en le faisant passer via [l'injection de dépendances |dependency-injection:passing-dependencies]. ```php use Nette\Security\Passwords; @@ -24,44 +24,44 @@ class Foo __construct($algo=PASSWORD_DEFAULT, array $options=[]): string .[method] ======================================================================== -Choisit l'[algorithme sécurisé |https://www.php.net/manual/en/password.constants.php] utilisé pour le hachage et comment le configurer. +Permet de choisir quel [algorithme sécurisé|https://www.php.net/manual/en/password.constants.php] utiliser pour générer le hash et de configurer ses paramètres. -La valeur par défaut est `PASSWORD_DEFAULT`, donc le choix de l'algorithme est laissé à PHP. L'algorithme peut changer dans les nouvelles versions de PHP lorsque de nouveaux algorithmes de hachage plus puissants sont supportés. Vous devez donc être conscient que la longueur du hachage résultant peut changer. Vous devez donc stocker le hachage résultant de manière à pouvoir stocker suffisamment de caractères, 255 étant la largeur recommandée. +Par défaut, `PASSWORD_DEFAULT` est utilisé, c'est-à-dire que le choix de l'algorithme est laissé à PHP. L'algorithme peut changer dans les nouvelles versions de PHP si de nouveaux algorithmes de hachage plus forts apparaissent. Par conséquent, vous devez être conscient que la longueur du hash résultant peut changer, et vous devez le stocker d'une manière qui peut accueillir suffisamment de caractères, 255 est la largeur recommandée. -Voici comment utiliser l'algorithme bcrypt et modifier la vitesse de hachage en utilisant le paramètre cost à partir de la valeur par défaut de 10. En 2020, avec le coût 10, le hachage d'un mot de passe prend environ 80 ms, le coût 11 prend 160 ms, le coût 12 320 ms, l'échelle étant logarithmique. Plus c'est lent, mieux c'est, le coût 10-12 est considéré comme suffisamment lent pour la plupart des cas d'utilisation : +Exemple de réglage de la vitesse de hachage avec l'algorithme bcrypt en modifiant le paramètre cost : (en 2020, la valeur par défaut est 10, le hachage du mot de passe prend environ 80 ms, pour cost 11 c'est environ 160 ms, pour cost 12 environ 320 ms, plus c'est lent, meilleure est la protection, la vitesse 10-12 étant déjà considérée comme une protection suffisante) ```php -// nous allons hacher les mots de passe avec 2^12 (2^cost) itérations de l'algorithme bcrypt +// nous hacherons les mots de passe avec 2^12 (2^cost) itérations de l'algorithme bcrypt $passwords = new Passwords(PASSWORD_BCRYPT, ['cost' => 12]); ``` -Avec l'injection de dépendances : +Via l'injection de dépendances : ```neon services: security.passwords: Nette\Security\Passwords(::PASSWORD_BCRYPT, [cost: 12]) ``` -hash(string $passwords): string .[method] -========================================= +hash(string $password): string .[method] +======================================== -Génère le hachage du mot de passe. +Génère le hash du mot de passe. ```php $res = $passwords->hash($password); // Hache le mot de passe ``` -Le résultat `$res` est une chaîne de caractères qui, en plus du hachage lui-même, contient l'identifiant de l'algorithme utilisé, ses paramètres et le sel cryptographique (données aléatoires permettant de s'assurer qu'un hachage différent est généré pour le même mot de passe). Il est donc rétrocompatible, par exemple, si vous modifiez les paramètres, les hachages stockés à l'aide des réglages précédents peuvent être vérifiés. L'ensemble du résultat est stocké dans la base de données, il n'est donc pas nécessaire de stocker le sel ou les paramètres séparément. +Le résultat `$res` est une chaîne qui, en plus du hash lui-même, contient également l'identifiant de l'algorithme utilisé, ses paramètres et le sel cryptographique (données aléatoires garantissant que pour le même mot de passe, un hash différent sera généré). Il est donc rétrocompatible, par exemple, si vous modifiez les paramètres, les hashes stockés avec les paramètres précédents pourront toujours être vérifiés. L'ensemble de ce résultat est stocké dans la base de données, il n'est donc pas nécessaire de stocker le sel ou les paramètres séparément. verify(string $password, string $hash): bool .[method] ====================================================== -Détermine si le mot de passe donné correspond au hachage donné. Obtenez le `$hash` de la base de données par nom d'utilisateur ou adresse e-mail. +Vérifie si le mot de passe fourni correspond à l'empreinte donnée. Obtenez `$hash` de la base de données en fonction du nom d'utilisateur ou de l'adresse e-mail fournie. ```php if ($passwords->verify($password, $hash)) { - // Mot de passe correct + // mot de passe correct } ``` @@ -69,13 +69,13 @@ if ($passwords->verify($password, $hash)) { needsRehash(string $hash): bool .[method] ========================================= -Vérifie si le hachage correspond aux options données dans le constructeur. +Vérifie si l'empreinte correspond aux options spécifiées dans le constructeur. -Utilisez cette méthode lorsque vous modifiez par exemple les paramètres de hachage. La vérification du mot de passe utilisera les paramètres stockés avec le hachage et si `needsRehash()` retourne vrai, vous devez calculer à nouveau le hachage, cette fois avec les paramètres mis à jour, et le stocker à nouveau dans la base de données. Cela garantit que les hachages de mots de passe seront automatiquement "mis à jour" lorsque les utilisateurs se connecteront. +Cette méthode est utile lorsque, par exemple, vous modifiez la vitesse de hachage. La vérification est effectuée selon les paramètres enregistrés et si `needsRehash()` renvoie `true`, il est nécessaire de recréer le hash, cette fois avec les nouveaux paramètres, et de le réenregistrer dans la base de données. De cette manière, les hashes stockés sont automatiquement "mis à niveau" lors de la connexion des utilisateurs. ```php if ($passwords->needsRehash($hash)) { $hash = $passwords->hash($password); - // stocke $hash dans la base de données + // enregistrer $hash dans la base de données } ``` diff --git a/security/hu/@home.texy b/security/hu/@home.texy index a7d39f4e82..daafeef94d 100644 --- a/security/hu/@home.texy +++ b/security/hu/@home.texy @@ -1,14 +1,14 @@ -Biztonság -********* +Nette Security +************** .[perex] -A `nette/security` csomag felelős a [felhasználók hitelesítéséért |authentication], a [hozzáférés-szabályozásért |authorization] és a [jelszóhamisításért |passwords]. +A `nette/security` csomag felelős a [felhasználói bejelentkezés |authentication], a [jogosultság ellenőrzés |authorization] és a [jelszó hashelés |passwords]ért. -Telepítés .[#toc-installation] ------------------------------- +Telepítés +--------- -Töltse le és telepítse a csomagot a [Composer |best-practices:composer] segítségével: +A könyvtárat a [Composer|best-practices:composer] segítségével töltheti le és telepítheti: ```shell composer require nette/security diff --git a/security/hu/@left-menu.texy b/security/hu/@left-menu.texy index eeb2f754da..ba977e254a 100644 --- a/security/hu/@left-menu.texy +++ b/security/hu/@left-menu.texy @@ -1,7 +1,7 @@ Nette Security ************** -- [Áttekintés |@home] -- [Hitelesítés |Authentication] -- [Engedélyezés |Authorization] -- [Jelszóhamisítás |passwords] -- [Konfiguráció |Configuration] +- [Bevezetés |@home] +- [Azonosítás |authentication] +- [Jogosultságkezelés |authorization] +- [Jelszó hashelés |passwords] +- [Konfiguráció |configuration] diff --git a/security/hu/@meta.texy b/security/hu/@meta.texy new file mode 100644 index 0000000000..c172d1cda5 --- /dev/null +++ b/security/hu/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette dokumentáció}} diff --git a/security/hu/authentication.texy b/security/hu/authentication.texy index cce0d69430..8edb732a60 100644 --- a/security/hu/authentication.texy +++ b/security/hu/authentication.texy @@ -1,48 +1,48 @@ -Felhasználók hitelesítése -************************* +Felhasználói bejelentkezés (Authentikáció) +****************************************** <div class=perex> -A kevés vagy semmilyen webes alkalmazásoknak nincs szükségük a felhasználói bejelentkezéshez vagy a felhasználói jogosultságok ellenőrzéséhez szükséges mechanizmusra. Ebben a fejezetben a következőkről fogunk beszélni: +Szinte egyetlen webalkalmazás sem nélkülözheti a felhasználói bejelentkezési mechanizmust és a felhasználói jogosultságok ellenőrzését. Ebben a fejezetben a következőkről lesz szó: -- felhasználói bejelentkezés és kijelentkezés -- egyéni hitelesítők és engedélyezők +- felhasználók be- és kijelentkeztetése +- saját authentikátorok </div> -→ [Telepítés és követelmények |@home#Installation] +→ [Telepítés és követelmények |@home#Telepítés] -A példákban egy [api:Nette\Security\User] osztályú objektumot fogunk használni, amely az aktuális felhasználót képviseli, és amelyet [függőségi injektálással |dependency-injection:passing-dependencies] átadva kapunk meg. A prezenterekben egyszerűen hívjuk meg a `$user = $this->getUser()`. +A példákban a [api:Nette\Security\User] osztály objektumát fogjuk használni, amely az aktuális felhasználót képviseli, és amelyhez úgy juthat hozzá, hogy [dependency injection |dependency-injection:passing-dependencies] segítségével kéri át. A presenterekben elegendő csak a `$user = $this->getUser()` hívása. -Hitelesítés .[#toc-authentication] -================================== +Authentikáció +============= -A hitelesítés a **felhasználó bejelentkezését** jelenti, azaz azt a folyamatot, amelynek során a felhasználó személyazonosságát ellenőrzik. A felhasználó általában felhasználónév és jelszó segítségével azonosítja magát. Az ellenőrzést az úgynevezett [hitelesítő |#authenticator] végzi. Ha a bejelentkezés sikertelen, akkor a `Nette\Security\AuthenticationException`. +Az authentikáció **felhasználói bejelentkezést** jelent, tehát azt a folyamatot, amely során ellenőrizzük, hogy a felhasználó valóban az-e, akinek kiadja magát. Általában felhasználónévvel és jelszóval igazolja magát. Az ellenőrzést az ún. [#authentikátor] végzi. Ha a bejelentkezés sikertelen, `Nette\Security\AuthenticationException` kivétel dobódik. ```php try { $user->login($username, $password); } catch (Nette\Security\AuthenticationException $e) { - $this->flashMessage('The username or password you entered is incorrect.'); + $this->flashMessage('A felhasználónév vagy jelszó helytelen'); } ``` -Így jelentkezik ki a felhasználó: +Így jelentkezteti ki a felhasználót: ```php $user->logout(); ``` -És ellenőrzi, hogy a felhasználó be van-e jelentkezve: +És annak megállapítása, hogy be van-e jelentkezve: ```php -echo $user->isLoggedIn() ? 'yes' : 'no'; +echo $user->isLoggedIn() ? 'igen' : 'nem'; ``` -Egyszerű, igaz? És minden biztonsági szempontot a Nette kezel az Ön számára. +Nagyon egyszerű, ugye? És minden biztonsági szempontot a Nette kezel Ön helyett. -A prezenterben a `startup()` módszerrel ellenőrizheti a bejelentkezést, és a be nem jelentkezett felhasználót átirányíthatja a bejelentkezési oldalra. +A presenterekben ellenőrizheti a bejelentkezést a `startup()` metódusban, és a be nem jelentkezett felhasználót átirányíthatja a bejelentkezési oldalra. ```php protected function startup() @@ -55,39 +55,38 @@ protected function startup() ``` -Lejárat .[#toc-expiration] -========================== +Lejárat +======= -A felhasználói bejelentkezés a [tároló lejáratával |#Storage for Logged User] együtt jár le, ami általában egy munkamenet (lásd a [munkamenet lejárati |http:configuration#session] beállítását). -Beállíthat azonban rövidebb időintervallumot is, amely után a felhasználó kijelentkezik. Erre a célra a `setExpiration()` metódus szolgál, amelyet a `login()` előtt kell meghívni. Paraméterként egy relatív időt tartalmazó karakterláncot adjon meg: +A felhasználó bejelentkezése a [tároló lejáratával |#A bejelentkezett felhasználó tárolója] együtt jár le, amely általában a session (lásd a [session lejárata |http:configuration#Session] beállítását). De beállítható rövidebb időintervallum is, amelynek lejárta után a felhasználó kijelentkezik. Erre szolgál a `setExpiration()` metódus, amelyet a `login()` előtt kell meghívni. Paraméterként adjon meg egy relatív időt tartalmazó stringet: ```php -// a bejelentkezés 30 perc inaktivitás után lejár. +// a bejelentkezés 30 perc inaktivitás után lejár $user->setExpiration('30 minutes'); -// törli a beállított lejáratot +// a beállított lejárat törlése $user->setExpiration(null); ``` -A `$user->getLogoutReason()` metódus megmondja, hogy a felhasználó kijelentkezett-e, mert az időintervallum lejárt. Vagy a `Nette\Security\UserStorage::LogoutInactivity` konstans értéket adja vissza, ha az idő lejárt, vagy a `UserStorage::LogoutManual` értéket, ha a `logout()` metódust hívta meg. +Azt, hogy a felhasználó az időintervallum lejárta miatt jelentkezett-e ki, a `$user->getLogoutReason()` metódus árulja el, amely vagy a `Nette\Security\UserStorage::LogoutInactivity` konstanst (lejárt az időkorlát) vagy a `UserStorage::LogoutManual` konstanst (a `logout()` metódussal jelentkeztették ki) adja vissza. -Hitelesítő .[#toc-authenticator] -================================ +Authentikátor +============= -Ez egy olyan objektum, amely ellenőrzi a bejelentkezési adatokat, azaz általában a nevet és a jelszót. A triviális megvalósítás a [api:Nette\Security\SimpleAuthenticator] osztály, amely a [konfigurációban |configuration] definiálható: +Ez egy objektum, amely ellenőrzi a bejelentkezési adatokat, tehát általában a nevet és a jelszót. Triviális formája a [api:Nette\Security\SimpleAuthenticator] osztály, amelyet a [konfigurációban|configuration] definiálhatunk: ```neon security: users: - # name: password - johndoe: secret123 - kathy: evenmoresecretpassword + # név: jelszó + frantisek: tajneheslo + katka: jestetajnejsiheslo ``` -Ez a megoldás inkább tesztelési célokra alkalmas. Megmutatjuk, hogyan hozzunk létre egy olyan hitelesítő eszközt, amely egy adatbázis-táblával szemben ellenőrzi a hitelesítő adatokat. +Ez a megoldás inkább tesztelési célokra alkalmas. Megmutatjuk, hogyan hozzunk létre egy authentikátort, amely egy adatbázis tábla alapján ellenőrzi a bejelentkezési adatokat. -A hitelesítő egy olyan objektum, amely a [api:Nette\Security\Authenticator] interfészt valósítja meg a `authenticate()` metódussal. Feladata vagy az úgynevezett [identitás |#identity] visszaadása, vagy egy kivétel dobása `Nette\Security\AuthenticationException`. Lehetséges lenne egy finomhangolt hibakódot is megadni `Authenticator::IdentityNotFound` vagy `Authenticator::InvalidCredential`. +Az authentikátor egy objektum, amely implementálja a [api:Nette\Security\Authenticator] interfészt a `authenticate()` metódussal. Feladata vagy az ún. [identitást |#Identitás] visszaadni, vagy `Nette\Security\AuthenticationException` kivételt dobni. Lehetőség lenne még hibakódot is megadni a helyzet finomabb megkülönböztetésére: `Authenticator::IdentityNotFound` és `Authenticator::InvalidCredential`. ```php use Nette; @@ -117,16 +116,16 @@ class MyAuthenticator implements Nette\Security\Authenticator return new SimpleIdentity( $row->id, - $row->role, // vagy szerepek tömbje. + $row->role, // vagy több szerepkör tömbje ['name' => $row->username], ); } } ``` -A MyAuthenticator osztály a [Nette Database Explorer-en |database:explorer] keresztül kommunikál az adatbázissal, és a `users` táblával dolgozik, ahol a `username` oszlop a felhasználó bejelentkezési nevét, a `password` oszlop pedig a [hash-t |passwords] tartalmazza. A név és a jelszó ellenőrzése után visszaadja az identitást a felhasználó azonosítójával, szerepével (a táblázat `role` oszlopa), amelyet [később |#roles] megemlítünk, és egy további adatokat (esetünkben a felhasználónevet) tartalmazó tömböt. +A MyAuthenticator osztály a [Nette Database Explorer|database:explorer] segítségével kommunikál az adatbázissal, és a `users` táblával dolgozik, ahol a `username` oszlopban a felhasználó bejelentkezési neve, a `password` oszlopban pedig a [jelszó lenyomatát|passwords] tárolja. A név és jelszó ellenőrzése után visszaadja az identitást, amely tartalmazza a felhasználó azonosítóját (ID), szerepkörét (a tábla `role` oszlopa), amelyről [később |authorization#Szerepkörök] többet mondunk, és egy tömböt további adatokkal (esetünkben a felhasználónévvel). -A hitelesítőt a DI konténer [szolgáltatásaként |dependency-injection:services] adjuk hozzá a konfigurációhoz: +Az authentikátort még hozzáadjuk a DI konténer konfigurációjához [szolgáltatásként|dependency-injection:services]: ```neon services: @@ -134,50 +133,50 @@ services: ``` -$onLoggedIn, $onLoggedOut Események +$onLoggedIn, $onLoggedOut események ----------------------------------- -A `Nette\Security\User` objektum rendelkezik `$onLoggedIn` és `$onLoggedOut`[eseményekkel |nette:glossary#Events], így olyan visszahívásokat adhat hozzá, amelyek a sikeres bejelentkezés vagy a felhasználó kijelentkezése után lépnek működésbe. +A `Nette\Security\User` objektumnak vannak [események |nette:glossary#Eventek események] `$onLoggedIn` és `$onLoggedOut`, tehát hozzáadhat callbackeket, amelyek a sikeres bejelentkezés után, illetve a felhasználó kijelentkezése után hívódnak meg. ```php $user->onLoggedIn[] = function () { - // a felhasználó épp most lépett be + // a felhasználó éppen bejelentkezett }; ``` -Identitás .[#toc-identity] -========================== +Identitás +========= -Az identitás a felhasználóra vonatkozó információk összessége, amelyet a hitelesítők visszaküldenek, majd egy munkamenetben tárolnak, és a `$user->getIdentity()` segítségével lekérdezhetők. Így megkaphatjuk az azonosítót, a szerepeket és más felhasználói adatokat, ahogyan azokat az autentikátorban átadtuk: +Az identitás a felhasználóról szóló információk összessége, amelyet az authentikátor ad vissza, és amely ezt követően a sessionben tárolódik, és a `$user->getIdentity()` segítségével érhető el. Tehát lekérhetjük az azonosítót, a szerepköröket és egyéb felhasználói adatokat, ahogyan azokat az authentikátorban átadtuk: ```php $user->getIdentity()->getId(); -// működik a $user->getId() rövidítése is; +// működik a $user->getId() rövidítés is; $user->getIdentity()->getRoles(); -// a felhasználói adatokhoz tulajdonságokként is hozzáférhetünk -// a MyAuthenticatorban átadott név +// a felhasználói adatok property-ként érhetők el +// a név, amelyet a MyAuthenticatorban adtunk át $user->getIdentity()->name; ``` -Fontos, hogy amikor a felhasználó a `$user->logout()` segítségével kijelentkezik, az **azonosság nem törlődik**, és továbbra is elérhető. Tehát, ha az identitás létezik, az önmagában nem biztosítja, hogy a felhasználó be is van jelentkezve. Ha kifejezetten törölni akarjuk az identitást, akkor a `logout(true)` segítségével jelentkezünk ki a felhasználóból. +Ami fontos, az az, hogy a `$user->logout()` segítségével történő kijelentkezéskor **az identitás nem törlődik**, és továbbra is rendelkezésre áll. Tehát bár a felhasználónak van identitása, nem feltétlenül van bejelentkezve. Ha explicit módon szeretnénk törölni az identitást, a `logout(true)` hívásával jelentkeztetjük ki a felhasználót. -Ennek köszönhetően továbbra is feltételezhetjük, hogy melyik felhasználó van a számítógépen, és például személyre szabott ajánlatokat jeleníthetünk meg a webáruházban, azonban személyes adatait csak bejelentkezés után jeleníthetjük meg. +Ennek köszönhetően továbbra is feltételezheti, hogy melyik felhasználó van a számítógépnél, és például személyre szabott ajánlatokat jeleníthet meg neki az e-shopban, de a személyes adatait csak a bejelentkezés után jelenítheti meg. -Az Identity egy olyan objektum, amely megvalósítja a [api:Nette\Security\IIdentity] interfészt, az alapértelmezett megvalósítás a [api:Nette\Security\SimpleIdentity]. És mint említettük, az identitás a munkamenetben tárolódik, így ha például megváltoztatjuk valamelyik bejelentkezett felhasználó szerepét, a régi adatok az identitásban megmaradnak, amíg újra be nem jelentkezik. +Az identitás egy objektum, amely implementálja a [api:Nette\Security\IIdentity] interfészt, az alapértelmezett implementáció a [api:Nette\Security\SimpleIdentity]. És ahogy említettük, a sessionben tárolódik, tehát ha például megváltoztatjuk valamelyik bejelentkezett felhasználó szerepkörét, a régi adatok az identitásában maradnak egészen az újbóli bejelentkezéséig. -A bejelentkezett felhasználó tárolása .[#toc-storage-for-logged-user] -===================================================================== +A bejelentkezett felhasználó tárolója +===================================== -A felhasználóra vonatkozó két alapvető információt, vagyis azt, hogy bejelentkezett-e, és a [személyazonosságát |#identity] általában a munkamenet hordozza. Amelyek megváltoztathatók. Ezen információk tárolásáért egy, a `Nette\Security\UserStorage` interfészt megvalósító objektum felelős. Két szabványos megvalósítás létezik, az első a munkamenetben, a második a cookie-ban továbbítja az adatokat. Ezek a `Nette\Bridges\SecurityHttp\SessionStorage` és a `CookieStorage` osztályok. A tárolást kiválaszthatja és nagyon kényelmesen konfigurálhatja a konfigurációban [security › authentication |configuration]. +A felhasználóról szóló két alapvető információ, tehát hogy be van-e jelentkezve és az ő [identitása |#Identitás], általában a sessionben kerül átvitelre. Ez megváltoztatható. Ezen információk tárolásáért egy objektum felel, amely implementálja a `Nette\Security\UserStorage` interfészt. Két standard implementáció áll rendelkezésre, az első a sessionben, a második a cookie-ban továbbítja az adatokat. Ezek a `Nette\Bridges\SecurityHttp\SessionStorage` és a `CookieStorage` osztályok. A tárolót kiválaszthatja és konfigurálhatja nagyon kényelmesen a [security › authentication |configuration#Tároló] konfigurációban. -Azt is pontosan szabályozhatjuk, hogy az identitás mentése (*sleep*) és visszaállítása (*wakeup*) hogyan történjen. Mindössze arra van szükség, hogy a hitelesítő implementálja a `Nette\Security\IdentityHandler` interfészt. Ennek két metódusa van: a `sleepIdentity()` az identitás tárolóba írása előtt, a `wakeupIdentity()` pedig az identitás beolvasása után hívódik. A metódusok módosíthatják az identitás tartalmát, vagy egy új objektummal helyettesíthetik azt, amely visszatér. A `wakeupIdentity()` metódus akár a `null` metódust is visszaadhatja, amely kijelentkezik a felhasználóból. +Továbbá befolyásolhatja, hogy pontosan hogyan történjen az identitás tárolása (*sleep*) és visszaállítása (*wakeup*). Elegendő, ha az authenticator implementálja a `Nette\Security\IdentityHandler` interfészt. Ennek két metódusa van: a `sleepIdentity()` az identitás tárolóba írása előtt hívódik meg, a `wakeupIdentity()` pedig annak kiolvasása után. A metódusok módosíthatják az identitás tartalmát, vagy helyettesíthetik egy új objektummal, amelyet visszaadnak. A `wakeupIdentity()` metódus akár `null`-t is visszaadhat, ezzel kijelentkeztetve a felhasználót. -Példaként egy gyakori kérdésre mutatunk megoldást, hogy hogyan lehet az identitás szerepeket frissíteni közvetlenül a munkamenetből való visszaállítás után. A `wakeupIdentity()` metódusban átadjuk az aktuális szerepeket az identitásnak, pl. az adatbázisból: +Példaként megmutatjuk a gyakori kérdés megoldását, hogyan frissítsük a szerepköröket az identitásban rögtön a sessionből való betöltés után. A `wakeupIdentity()` metódusban átadjuk az identitásba az aktuális szerepköröket, pl. az adatbázisból: ```php final class Authenticator implements @@ -185,25 +184,25 @@ final class Authenticator implements { public function sleepIdentity(IIdentity $identity): IIdentity { - // itt lehet megváltoztatni az identitást a bejelentkezés utáni tárolás előtt, - // de erre most nincs szükségünk. + // itt lehet módosítani az identitást a tárolóba írás előtt a bejelentkezés után, + // de erre most nincs szükségünk return $identity; } public function wakeupIdentity(IIdentity $identity): ?IIdentity { - // szerepek frissítése az identitásban + // szerepkörök frissítése az identitásban $userId = $identity->getId(); $identity->setRoles($this->facade->getUserRoles($userId)); return $identity; } ``` -És most visszatérünk a cookie-alapú tároláshoz. Ez lehetővé teszi egy olyan weboldal létrehozását, ahol a felhasználók bejelentkezhetnek anélkül, hogy munkameneteket kellene használniuk. Tehát nem kell a lemezre írni. Végül is így működik a weboldal, amit most olvasol, beleértve a fórumot is. Ebben az esetben a `IdentityHandler` megvalósítása szükségszerű. A cookie-ban csak egy véletlenszerű tokent fogunk tárolni, amely a bejelentkezett felhasználót képviseli. +És most visszatérünk a cookie alapú tárolóhoz. Lehetővé teszi egy olyan weboldal létrehozását, ahol a felhasználók bejelentkezhetnek, és ehhez nincs szükség sessionökre. Tehát nincs szükség a lemezre írásra. Egyébként így működik az a weboldal is, amelyet éppen olvas, beleértve a fórumot is. Ebben az esetben az `IdentityHandler` implementálása elengedhetetlen. A cookie-ba ugyanis csak egy véletlenszerű tokent fogunk tárolni, amely a bejelentkezett felhasználót reprezentálja. -Tehát először a konfigurációban a `security › authentication › storage: cookie` segítségével állítjuk be a kívánt tárolást. +Először tehát a konfigurációban beállítjuk a kívánt tárolót a `security › authentication › storage: cookie` segítségével. -Hozzáadunk egy `authtoken` oszlopot az adatbázisban, amelyben minden felhasználóhoz egy [teljesen véletlenszerű, egyedi és megfejthetetlen |utils:random], megfelelő hosszúságú (legalább 13 karakteres) karakterláncot fogunk létrehozni. A `CookieStorage` tároló csak a `$identity->getId()` értéket tárolja a cookie-ban, így a `sleepIdentity()` -ben az eredeti azonosítót egy proxyval helyettesítjük a `authtoken` azonosítóval, ezzel szemben a `wakeupIdentity()` módszerben az authtoken szerint a teljes azonosítót visszaállítjuk az adatbázisból: +Az adatbázisban létrehozunk egy `authtoken` oszlopot, amelyben minden felhasználónak egy [teljesen véletlenszerű, egyedi és kitalálhatatlan|utils:random] stringje lesz, megfelelő hosszúságú (legalább 13 karakter). A `CookieStorage` tároló a cookie-ban csak az `$identity->getId()` értékét továbbítja, tehát a `sleepIdentity()`-ben az eredeti identitást egy helyettesítő identitásra cseréljük, amelynek azonosítójában az `authtoken` van, míg a `wakeupIdentity()` metódusban az authtoken alapján kiolvassuk a teljes identitást az adatbázisból: ```php final class Authenticator implements @@ -212,21 +211,21 @@ final class Authenticator implements public function authenticate(string $username, string $password): SimpleIdentity { $row = $this->db->fetch('SELECT * FROM user WHERE username = ?', $username); - // jelszó ellenőrzése + // ellenőrizzük a jelszót ... - // visszaküldjük az identitást az adatbázisból származó összes adattal. + // visszaadjuk az identitást az adatbázisból származó összes adattal return new SimpleIdentity($row->id, null, (array) $row); } public function sleepIdentity(IIdentity $identity): SimpleIdentity { - // egy proxy identitást adunk vissza, ahol az azonosító az authtoken. + // visszaadjuk a helyettesítő identitást, ahol az ID-ben az authtoken lesz return new SimpleIdentity($identity->authtoken); } public function wakeupIdentity(IIdentity $identity): ?SimpleIdentity { - // a proxy azonosítót a teljes azonosítóval helyettesítjük, mint az authenticate() esetében. + // a helyettesítő identitást lecseréljük a teljes identitásra, mint az authenticate()-ban $row = $this->db->fetch('SELECT * FROM user WHERE authtoken = ?', $identity->getId()); return $row ? new SimpleIdentity($row->id, null, (array) $row) @@ -236,16 +235,16 @@ final class Authenticator implements ``` -Többszörös független hitelesítés .[#toc-multiple-independent-authentications] -============================================================================= +Több független bejelentkezés +============================ -Lehetőség van arra, hogy egy webhelyen belül és egy munkamenetben több független bejelentkezett felhasználó legyen. Ha például külön hitelesítést szeretnénk a frontend és a backend számára, akkor csak beállítunk egy-egy egyedi munkamenet névteret mindkettőhöz: +Egy weboldalon és egy sessionön belül párhuzamosan több független bejelentkező felhasználó is lehet. Ha például egy weboldalon külön authentikációt szeretnénk az adminisztrációhoz és a nyilvános részhez, elegendő mindegyiknek saját nevet beállítani: ```php $user->getStorage()->setNamespace('backend'); ``` -Szükséges szem előtt tartani, hogy ezt minden, ugyanahhoz a szegmenshez tartozó helyen be kell állítani. Előadók használata esetén a névteret a közös ősben - általában a BasePresenterben - állítjuk be. Ehhez a [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()] metódust fogjuk kiterjeszteni: +Fontos megjegyezni, hogy a névteret mindig az adott részhez tartozó összes helyen be kell állítani. Ha presentereket használunk, a névteret az adott rész közös ősében állítjuk be - általában a BasePresenterben. Ezt a [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()] metódus kiterjesztésével tesszük meg: ```php public function checkRequirements($element): void @@ -256,10 +255,10 @@ public function checkRequirements($element): void ``` -Több hitelesítő .[#toc-multiple-authenticators] ------------------------------------------------ +Több authentikátor +------------------ -Egy alkalmazás független hitelesítéssel rendelkező szegmensekre való felosztása általában különböző hitelesítőket igényel. Azonban két Authenticator-t megvalósító osztály regisztrálása a konfigurációs szolgáltatásokba hibát váltana ki, mivel a Nette nem tudná, hogy melyiküknek kell [automatikusan bekötni |dependency-injection:autowiring] a `Nette\Security\User` objektumot. Ezért kell korlátozni az autowiringet számukra a `autowired: self` segítségével, hogy csak akkor aktiválódjon, ha az osztályukat kifejezetten kérik: +Az alkalmazás független bejelentkezéssel rendelkező részekre való felosztása általában különböző authentikátorokat is igényel. Azonban, ha a szolgáltatások konfigurációjában két, az Authenticator interfészt implementáló osztályt regisztrálnánk, a Nette nem tudná, melyiket rendelje automatikusan a `Nette\Security\User` objektumhoz, és hibát jelezne. Ezért az authentikátorokhoz az [autowiring |dependency-injection:autowiring]-ot úgy kell korlátoznunk, hogy csak akkor működjön, ha valaki egy konkrét osztályt kér, pl. FrontAuthenticator, amit az `autowired: self` opcióval érünk el: ```neon services: @@ -278,7 +277,7 @@ class SignPresenter extends Nette\Application\UI\Presenter } ``` -Csak a [login() |api:Nette\Security\User::login()] metódus hívása előtt kell beállítanunk a hitelesítőnket a User objektumra, ami jellemzően a bejelentkezési űrlap visszahívását jelenti: +A User objektum authentikátorát a [login() |api:Nette\Security\User::login()] metódus hívása előtt állítjuk be, tehát általában annak az űrlapnak a kódjában, amely bejelentkezteti: ```php $form->onSuccess[] = function (Form $form, \stdClass $data) { diff --git a/security/hu/authorization.texy b/security/hu/authorization.texy index a6176b6641..5c4804c087 100644 --- a/security/hu/authorization.texy +++ b/security/hu/authorization.texy @@ -1,48 +1,48 @@ -Hozzáférés-szabályozás (engedélyezés) -************************************* +Jogosultságok ellenőrzése (Autorizáció) +*************************************** .[perex] -Az engedélyezés határozza meg, hogy egy felhasználó rendelkezik-e elegendő jogosultsággal például egy adott erőforrás eléréséhez vagy egy művelet végrehajtásához. Az engedélyezés feltételezi a korábbi sikeres hitelesítést, azaz azt, hogy a felhasználó be van jelentkezve. +Az autorizáció azt vizsgálja, zda má uživatel dostatečná oprávnění například pro přístup k určitému zdroji či pro provedení nějaké akce. Autorizace předpokládá předchozí úspěšnou autentizaci, tj. že uživatel je přihlášen. -→ [Telepítés és követelmények |@home#Installation] +→ [Telepítés és követelmények |@home#Telepítés] -A példákban egy [api:Nette\Security\User] osztályú objektumot fogunk használni, amely az aktuális felhasználót reprezentálja, és amelyet [függőségi injektálással |dependency-injection:passing-dependencies] történő átadással kapunk meg. A prezenterekben egyszerűen hívjuk meg a `$user = $this->getUser()`. +A példákban a [api:Nette\Security\User] osztály objektumát fogjuk használni, amely az aktuális felhasználót képviseli, és amelyhez úgy juthat hozzá, hogy [dependency injection |dependency-injection:passing-dependencies] segítségével kéri át. A presenterekben elegendő csak a `$user = $this->getUser()` hívása. -Nagyon egyszerű adminisztrációval rendelkező weboldalak esetében, ahol nem különböztetünk meg felhasználói jogokat, a már ismert `isLoggedIn()` módszert használhatjuk jogosultsági kritériumként. Más szóval: ha egy felhasználó egyszer bejelentkezett, akkor minden művelethez rendelkezik jogosultsággal, és fordítva. +Nagyon egyszerű, adminisztrációval rendelkező weboldalaknál, ahol nem különböztetik meg a felhasználói jogosultságokat, autorizációs kritériumként használható a már ismert `isLoggedIn()` metódus. Más szóval: amint a felhasználó be van jelentkezve, minden jogosultsággal rendelkezik, és fordítva. ```php if ($user->isLoggedIn()) { // be van jelentkezve a felhasználó? - deleteItem(); // ha igen, akkor törölhet egy elemet. + deleteItem(); // akkor van jogosultsága a művelethez } ``` -Szerepkörök .[#toc-roles] -------------------------- +Szerepkörök +----------- -A szerepek célja, hogy pontosabb jogosultságkezelést biztosítsanak, és függetlenek maradjanak a felhasználónévtől. Amint a felhasználó bejelentkezik, egy vagy több szerepet kap. Maguk a szerepkörök egyszerű karakterláncok lehetnek, például `admin`, `member`, `guest`, stb. Ezeket a `SimpleIdentity` konstruktor második argumentumában kell megadni, akár sztringként, akár tömbként. +A szerepkörök célja, hogy pontosabb jogosultságkezelést kínáljanak, és függetlenek maradjanak a felhasználónévtől. Minden felhasználónak a bejelentkezéskor egy vagy több szerepkört rendelünk, amelyekben fellép. A szerepkörök lehetnek egyszerű stringek, például `admin`, `member`, `guest`, stb. A `SimpleIdentity` konstruktorának második paramétereként adjuk meg őket, vagy stringként, vagy stringek tömbjeként - szerepkörökként. -Engedélyezési kritériumként most a `isInRole()` metódust fogjuk használni, amely ellenőrzi, hogy a felhasználó a megadott szerepkörben van-e: +Autorizációs kritériumként most az `isInRole()` metódust használjuk, amely megmondja, hogy a felhasználó az adott szerepkörben lép-e fel: ```php -if ($user->isInRole('admin')) { // admin szerepkör van a felhasználóhoz rendelve? - deleteItem(); // ha igen, akkor törölhet egy elemet. +if ($user->isInRole('admin')) { // a felhasználó admin szerepkörben van? + deleteItem(); // akkor van jogosultsága a művelethez } ``` -Mint már tudjuk, a felhasználó kijelentkezése nem törli a személyazonosságát. Így a `getIdentity()` metódus továbbra is a `SimpleIdentity` objektumot adja vissza, beleértve az összes megadott szerepet. A Nette Framework a "kevesebb kód, több biztonság" elvét követi, így a szerepek ellenőrzése során nem kell ellenőrizni, hogy a felhasználó be van-e jelentkezve is. A `isInRole()` módszer **hatékony szerepekkel** dolgozik, azaz ha a felhasználó be van jelentkezve, akkor az identitáshoz rendelt szerepek kerülnek felhasználásra, ha nincs bejelentkezve, akkor egy automatikus speciális szerepkör, a `guest` kerül felhasználásra. +Ahogy már tudja, a felhasználó kijelentkezése után nem feltétlenül törlődik az identitása. Tehát a `getIdentity()` metódus továbbra is visszaadja a `SimpleIdentity` objektumot, beleértve az összes megadott szerepkört. A Nette Framework a „kevesebb kód, több biztonság” elvét vallja, ahol a kevesebb írás biztonságosabb kódhoz vezet, ezért a szerepkörök ellenőrzésekor nem kell még azt is ellenőriznie, hogy a felhasználó be van-e jelentkezve. Az `isInRole()` metódus az **effektív szerepkörökkel** dolgozik, azaz ha a felhasználó be van jelentkezve, az identitásban megadott szerepkörökből indul ki, ha nincs bejelentkezve, automatikusan a speciális `guest` szerepkört kapja. -Engedélyező .[#toc-authorizator] --------------------------------- +Autorizátor +----------- -A szerepek mellett bevezetjük az erőforrás és a művelet fogalmakat is: +A szerepkörökön kívül bevezetjük még az erőforrás és a művelet fogalmát: -- A **szerep** egy felhasználói attribútum - például moderátor, szerkesztő, látogató, regisztrált felhasználó, adminisztrátor, ... -- **forrás** az alkalmazás logikai egysége - cikk, oldal, felhasználó, menüpont, szavazás, előadó, ... -- **művelet** az a konkrét tevékenység, amelyet a felhasználó a *forrással* végezhet vagy nem végezhet - megtekintés, szerkesztés, törlés, szavazás, ... +- **szerepkör** a felhasználó tulajdonsága - pl. moderátor, szerkesztő, látogató, regisztrált felhasználó, adminisztrátor... +- **erőforrás** (*resource*) a weboldal valamilyen logikai eleme - cikk, oldal, felhasználó, menüpont, szavazás, presenter, ... +- **művelet** (*operation*) valamilyen konkrét tevékenység, amelyet a felhasználó megtehet vagy nem tehet meg az erőforrással - például törlés, szerkesztés, létrehozás, szavazás, ... -Az engedélyező egy olyan objektum, amely eldönti, hogy egy adott *szerep* jogosult-e egy bizonyos *művelet* végrehajtására egy adott *forrással*. Ez egy olyan objektum, amely a [api:Nette\Security\Authorizator] interfészt valósítja meg, egyetlen metódussal: `isAllowed()`: +Az autorizátor egy objektum, amely eldönti, hogy az adott *szerepkörnek* van-e engedélye egy bizonyos *művelet* végrehajtására egy bizonyos *erőforrással*. Ez egy objektum, amely implementálja a [api:Nette\Security\Authorizator] interfészt egyetlen `isAllowed()` metódussal: ```php class MyAuthorizator implements Nette\Security\Authorizator @@ -63,50 +63,50 @@ class MyAuthorizator implements Nette\Security\Authorizator } ``` -Az authorizátort a DI konténer [szolgáltatásaként |dependency-injection:services] adjuk hozzá a konfigurációhoz: +Az autorizátort hozzáadjuk a DI konténer konfigurációjához [szolgáltatásként|dependency-injection:services]: ```neon services: - MyAuthorizator ``` -És a következő egy példa a használatra. Vegyük észre, hogy ezúttal nem az engedélyező, hanem a `Nette\Security\User::isAllowed()` metódust hívjuk meg, így nincs első paramétere a `$role`. Ez a metódus a `MyAuthorizator::isAllowed()` metódust hívja meg szekvenciálisan az összes felhasználói szerepkörre, és igazat ad vissza, ha legalább az egyiküknek van jogosultsága. +És következik a használat példája. Figyelem, ezúttal a `Nette\Security\User::isAllowed()` metódust hívjuk, nem az autorizátort, tehát ott nincs az első `$role` paraméter. Ez a metódus a `MyAuthorizator::isAllowed()`-t hívja meg sorban a felhasználó összes szerepkörére, és true-t ad vissza, ha legalább egyiküknek van engedélye. ```php -if ($user->isAllowed('file')) { // a felhasználó mindent megtehet a 'file' erőforrással? +if ($user->isAllowed('file')) { // a felhasználó bármit megtehet a 'file' erőforrással? useFile(); } -if ($user->isAllowed('file', 'delete')) { // a felhasználó törölheti a 'file' erőforrást? +if ($user->isAllowed('file', 'delete')) { // végrehajthatja a 'delete' műveletet a 'file' erőforráson? deleteFile(); } ``` -Mindkét argumentum opcionális, és alapértelmezett értékük *mindent* jelent. +Mindkét paraméter opcionális, az alapértelmezett `null` érték jelentése *bármi*. -Engedély ACL .[#toc-permission-acl] ------------------------------------ +Permission ACL +-------------- -A Nette az engedélyező beépített implementációjával, a [api:Nette\Security\Permission] osztállyal rendelkezik, amely egy könnyű és rugalmas ACL (Access Control List) réteget kínál az engedélyezéshez és a hozzáférés-szabályozáshoz. Amikor ezzel az osztállyal dolgozunk, szerepeket, erőforrásokat és egyedi jogosultságokat definiálunk. A szerepek és erőforrások pedig hierarchiákat alkothatnak. A magyarázathoz egy webes alkalmazás példáját mutatjuk be: +A Nette egy beépített autorizátor implementációval érkezik, ez a [api:Nette\Security\Permission] osztály, amely a programozónak egy könnyű és rugalmas ACL (Access Control List) réteget biztosít a jogosultságok és hozzáférések kezelésére. A vele való munka a szerepkörök, erőforrások és egyes jogosultságok definiálásából áll. A szerepkörök és erőforrások lehetővé teszik hierarchiák létrehozását. Magyarázatként megmutatunk egy példát egy webalkalmazásra: -- `guest`: bejelentkezés nélküli látogató, aki olvashatja és böngészheti a web nyilvános részét, azaz cikkeket olvashat, kommentálhat és szavazhat szavazásokon. -- `registered`: bejelentkezett felhasználó, aki ezen felül hozzászólásokat is írhat. -- `admin`: cikkeket, hozzászólásokat és szavazásokat kezelhet. +- `guest`: nem bejelentkezett látogató, aki olvashatja és böngészheti a weboldal nyilvános részét, azaz olvashat cikkeket, kommenteket és szavazhat a szavazásokon +- `registered`: bejelentkezett regisztrált felhasználó, aki ráadásul kommentelhet is +- `admin`: kezelheti a cikkeket, kommenteket és szavazásokat -Meghatároztunk tehát bizonyos szerepköröket (`guest`, `registered` és `admin`) és megemlítettük azokat az erőforrásokat (`article`, `comments`, `poll`), amelyekhez a felhasználók hozzáférhetnek, illetve amelyeken műveleteket végezhetnek (`view`, `vote`, `add`, `edit`). +Tehát definiáltunk bizonyos szerepköröket (`guest`, `registered` és `admin`), és említettünk erőforrásokat (`article`, `comment`, `poll`), amelyekhez a felhasználók valamilyen szerepkörrel hozzáférhetnek vagy bizonyos műveleteket végezhetnek (`view`, `vote`, `add`, `edit`). -Létrehozunk egy példányt az Permission osztályból, és definiáljuk a **szerepeket**. Lehetőség van a szerepek öröklődésének használatára, ami biztosítja, hogy például a `admin` szerepkörrel rendelkező felhasználó megtehesse azt, amit egy közönséges honlaplátogató (és persze többet is). +Létrehozzuk a Permission osztály példányát, és definiáljuk a **szerepköröket**. Használhatjuk az ún. szerepkör öröklődést, amely biztosítja, hogy pl. egy adminisztrátor szerepkörrel (`admin`) rendelkező felhasználó megteheti azt is, amit egy átlagos weboldal látogató (és természetesen többet is). ```php $acl = new Nette\Security\Permission; $acl->addRole('guest'); -$acl->addRole('registered', 'guest'); // 'registered' örökölte a 'guest'-től -$acl->addRole('admin', 'registered'); // és az 'admin' örököl a 'registered'-ből. +$acl->addRole('registered', 'guest'); // a 'registered' örököl a 'guest'-től +$acl->addRole('admin', 'registered'); // és tőle örököl az 'admin' ``` -Most definiáljuk a **források** listáját, amelyekhez a felhasználók hozzáférhetnek: +Most definiáljuk az **erőforrások** listáját is, amelyekhez a felhasználók hozzáférhetnek. ```php $acl->addResource('article'); @@ -114,49 +114,49 @@ $acl->addResource('comment'); $acl->addResource('poll'); ``` -Az erőforrások is használhatnak öröklődést, például hozzáadhatjuk a `$acl->addResource('perex', 'article')`. +Az erőforrások is használhatnak öröklődést, lehetne például megadni `$acl->addResource('perex', 'article')`. -És most a legfontosabb dolog. Meghatározzuk közöttük a **szabályokat**, amelyek meghatározzák, hogy ki mit tehet: +És most a legfontosabb. Definiálunk közöttük szabályokat, amelyek meghatározzák, ki mit tehet mivel: ```php -// most már mindent megtagadunk +// először senki sem tehet semmit -// engedd meg a vendégnek, hogy megnézze a cikkeket, hozzászólásokat és szavazásokat +// a guest nézhesse a cikkeket, kommenteket és szavazásokat $acl->allow('guest', ['article', 'comment', 'poll'], 'view'); -// és szavazhat a szavazásokon is +// és a szavazásokon ráadásul szavazhasson is $acl->allow('guest', 'poll', 'vote'); -// a regisztrált örökli a guesta jogosultságait, a kommentelést is megengedjük neki. +// a regisztrált örökli a guest jogait, adjunk neki ráadásul kommentelési jogot $acl->allow('registered', 'comment', 'add'); -// az adminisztrátor bármit megnézhet és szerkeszthet +// az adminisztrátor bármit megtekinthet és szerkeszthet $acl->allow('admin', $acl::All, ['view', 'edit', 'add']); ``` -Mi van akkor, ha meg akarjuk **akadályozni**, hogy valaki hozzáférjen egy erőforráshoz? +Mi van, ha valakinek **meg akarjuk tiltani** a hozzáférést egy bizonyos erőforráshoz? ```php -// Az adminisztrátor nem szerkesztheti a szavazásokat, ez nem lenne tisztességes. +// az adminisztrátor nem szerkesztheti a szavazásokat, az nem lenne demokratikus $acl->deny('admin', 'poll', 'edit'); ``` -Most, hogy létrehoztuk a szabálykészletet, egyszerűen megkérdezhetjük az engedélyezési lekérdezéseket: +Most, hogy létrehoztuk a szabályok listáját, egyszerűen feltehetünk autorizációs kérdéseket: ```php -// Vendégként is megtekintheti a cikkeket? +// a guest megtekintheti a cikkeket? $acl->isAllowed('guest', 'article', 'view'); // true -// szerkeszthet-e a vendég cikket? +// a guest szerkesztheti a cikkeket? $acl->isAllowed('guest', 'article', 'edit'); // false -// szavazhat-e a vendég a szavazáson? +// a guest szavazhat a szavazásokon? $acl->isAllowed('guest', 'poll', 'vote'); // true -// a vendég hozzáfűzhet kommenteket? +// a guest kommentelhet? $acl->isAllowed('guest', 'comment', 'add'); // false ``` -Ugyanez vonatkozik a regisztrált felhasználóra is, de ő is megjegyzést tehet: +Ugyanez érvényes a regisztrált felhasználóra is, ő azonban kommentelhet is: ```php $acl->isAllowed('registered', 'article', 'view'); // true @@ -164,7 +164,7 @@ $acl->isAllowed('registered', 'comment', 'add'); // true $acl->isAllowed('registered', 'comment', 'edit'); // false ``` -A rendszergazda mindent szerkeszthet, kivéve a szavazásokat: +Az adminisztrátor mindent szerkeszthet, kivéve a szavazásokat: ```php $acl->isAllowed('admin', 'poll', 'vote'); // true @@ -172,7 +172,7 @@ $acl->isAllowed('admin', 'poll', 'edit'); // false $acl->isAllowed('admin', 'comment', 'edit'); // true ``` -A jogosultságokat dinamikusan is ki lehet értékelni, és a döntést a saját callbackünkre bízhatjuk, amelynek minden paramétert átadunk: +A jogosultságok dinamikusan is kiértékelhetők, és a döntést saját callbackre bízhatjuk, amelynek átadjuk az összes paramétert: ```php $assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { @@ -182,7 +182,7 @@ $assertion = function (Permission $acl, string $role, string $resource, string $ $acl->allow('registered', 'comment', null, $assertion); ``` -De hogyan oldjuk meg azt a helyzetet, amikor a szerepek és erőforrások nevei nem elegendőek, azaz szeretnénk definiálni, hogy például a `registered` szerepkör csak akkor szerkeszthet egy `article` erőforrást, ha ő a szerzője? Sztringek helyett objektumokat fogunk használni, a szerepkör lesz a [api:Nette\Security\Role] objektum és a forrás [api:Nette\Security\Resource]. A `getRoleId()` illetve `getResourceId()` metódusaik az eredeti karakterláncokat fogják visszaadni: +De hogyan kezeljük például azt a helyzetet, amikor nem elegendőek csak a szerepkörök és erőforrások nevei, hanem definiálni szeretnénk, hogy például a `registered` szerepkör csak akkor szerkesztheti az `article` erőforrást, ha ő a szerzője? Stringek helyett objektumokat használunk, a szerepkör egy [api:Nette\Security\Role] objektum lesz, az erőforrás pedig egy [api:Nette\Security\Resource] objektum. A `getRoleId()` ill. `getResourceId()` metódusaik visszaadják az eredeti stringeket: ```php class Registered implements Nette\Security\Role @@ -207,19 +207,19 @@ class Article implements Nette\Security\Resource } ``` -És most hozzunk létre egy szabályt: +És most létrehozunk egy szabályt: ```php $assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { - $role = $acl->getQueriedRole(); // objektum Registered - $resource = $acl->getQueriedResource(); // object Article + $role = $acl->getQueriedRole(); // Registered objektum + $resource = $acl->getQueriedResource(); // Article objektum return $role->id === $resource->authorId; }; $acl->allow('registered', 'article', 'edit', $assertion); ``` -Az ACL-t objektumok átadásával kérdezzük le: +És az ACL lekérdezése objektumok átadásával történik: ```php $user = new Registered(/* ... */); @@ -227,7 +227,7 @@ $article = new Article(/* ... */); $acl->isAllowed($user, $article, 'edit'); ``` -Egy szerepkör egy vagy több más szerepkörből örökölhet. De mi történik akkor, ha az egyik ősnek engedélyezett egy bizonyos művelet, a másiknak pedig megtagadott? Ekkor a *szerep súlya* lép a játékba - az öröklendő szerepek sorában az utolsó szerepnek van a legnagyobb súlya, az elsőnek a legkisebb: +Egy szerepkör örökölhet egy másik szerepkörtől vagy több szerepkörtől. De mi történik, ha az egyik ősnek tiltva van egy művelet, a másiknak pedig engedélyezve? Milyenek lesznek az utód jogai? Ezt a szerepkör súlya határozza meg - az ősök listájában utoljára említett szerepkörnek van a legnagyobb súlya, az elsőként említett szerepkörnek a legkisebb. Ez a példából jobban látszik: ```php $acl = new Nette\Security\Permission; @@ -239,22 +239,22 @@ $acl->addResource('backend'); $acl->allow('admin', 'backend'); $acl->deny('guest', 'backend'); -// A példa: az admin szerepnek kisebb a súlya, mint a guest szerepnek. +// A eset: az admin szerepkörnek kisebb a súlya, mint a guest szerepkörnek $acl->addRole('john', ['admin', 'guest']); $acl->isAllowed('john', 'backend'); // false -// B példa: az admin szerepnek nagyobb súlya van, mint a guest szerepnek. +// B eset: az admin szerepkörnek nagyobb a súlya, mint a guestnek $acl->addRole('mary', ['guest', 'admin']); $acl->isAllowed('mary', 'backend'); // true ``` -Szerepek és erőforrások is eltávolíthatók (`removeRole()`, `removeResource()`), szabályok is visszafordíthatók (`removeAllow()`, `removeDeny()`). Az összes közvetlen szülői szerepkör tömbje a `getRoleParents()` visszaadja. Az, hogy két entitás örököl-e egymástól, a `roleInheritsFrom()` és a `resourceInheritsFrom()` eredményt adja vissza. +A szerepköröket és erőforrásokat el is lehet távolítani (`removeRole()`, `removeResource()`), a szabályokat is vissza lehet vonni (`removeAllow()`, `removeDeny()`). Az összes közvetlen szülő szerepkör tömbjét a `getRoleParents()` adja vissza, azt, hogy két entitás örököl-e egymástól, a `roleInheritsFrom()` és a `resourceInheritsFrom()` adja vissza. -Hozzáadás szolgáltatásként .[#toc-add-as-a-service] ---------------------------------------------------- +Hozzáadás szolgáltatásként +-------------------------- -Az általunk létrehozott ACL-t szolgáltatásként kell hozzáadnunk a konfigurációhoz, hogy az objektum `$user`, vagyis hogy kódban használhassuk például a `$user->isAllowed('article', 'view')`. Ehhez írunk hozzá egy gyárat: +Az általunk létrehozott ACL-t át kell adnunk a konfigurációnak szolgáltatásként, hogy a `$user` objektum használni kezdje, tehát hogy a kódban használható legyen pl. `$user->isAllowed('article', 'view')`. Ehhez írunk rá egy factory-t: ```php namespace App\Model; @@ -279,7 +279,7 @@ services: - App\Model\AuthorizatorFactory::create ``` -A prezenterekben ezután ellenőrizheti a jogosultságokat például a `startup()` módszerben: +A presenterekben ezután ellenőrizheti a jogosultságokat például a `startup()` metódusban: ```php protected function startup() diff --git a/security/hu/configuration.texy b/security/hu/configuration.texy index 1aa1d8e3ee..9def0a28ee 100644 --- a/security/hu/configuration.texy +++ b/security/hu/configuration.texy @@ -1,71 +1,85 @@ -Hozzáférés-szabályozás konfigurálása -************************************ +Hozzáférési jogosultságok konfigurálása +*************************************** .[perex] -A Nette Security konfigurációs lehetőségeinek áttekintése. +A Nette Security konfigurációs opcióinak áttekintése. -Ha nem a teljes keretrendszert, hanem csak ezt a könyvtárat használja, olvassa el [, hogyan töltse be a konfigurációt |bootstrap:]. +Ha nem a teljes keretrendszert használja, csak ezt a könyvtárat, olvassa el, [hogyan kell betölteni a konfigurációt|bootstrap:]. -A konfigurációban meghatározhatja a felhasználók listáját, hogy létrehozzon egy [egyszerű hitelesítőt |authentication] (`Nette\Security\SimpleAuthenticator`). Mivel a jelszavak olvashatók a konfigurációban, ez a megoldás csak tesztelési célokra szolgál. +A konfigurációban definiálható a felhasználók listája, és így létrehozható egy [egyszerű authenticator|authentication] (`Nette\Security\SimpleAuthenticator`). Mivel a konfigurációban a jelszavak olvasható formában vannak megadva, ez a megoldás csak tesztelési célokra alkalmas. ```neon security: - # felhasználói panel jelenik meg a Tracy Barban? - debugger: ... # (bool) alapértelmezett értéke true + # megjelenítse a felhasználói panelt a Tracy Bar-ban? + debugger: ... # (bool) alapértelmezett az true users: - # name: password - johndoe: secret123 + # név: jelszó + frantisek: tajneheslo - # név, jelszó, szerep és egyéb adatok az identitásban elérhetőek - janedoe: - password: secret123 + # név, jelszó, szerepkör és további adatok elérhetők az identitásban + dobrota: + password: tajneheslo roles: [admin] data: ... ``` -Szerepeket és erőforrásokat is meghatározhat, hogy létrehozza az [engedélyező |authorization] alapját (`Nette\Security\Permission`): +Továbbá definiálhatók szerepkörök és erőforrások, és így létrehozható az alap egy [autorizátor|authorization]-hoz (`Nette\Security\Permission`): ```neon security: roles: guest: - registered: [guest] # registered örököl a guest-től - admin: [registered] # és az admin örököl a registered-től + registered: [guest] # a registered örököl a guest-től + admin: [registered] # és tőle örököl az admin resources: article: - comment: [article] # resource örököl a article-től + comment: [article] # az erőforrás örököl az article-től poll: ``` -Felhasználói tárolás .[#toc-user-storage] ------------------------------------------ +Tároló +------ -Beállíthatja, hogy a bejelentkezett felhasználóra vonatkozó információkat hogyan tárolja: +Konfigurálható, hogyan tároljuk a bejelentkezett felhasználó adatait: ```neon security: authentication: - # mennyi idő inaktivitás után jelentkezik ki a felhasználó - expiration: 30 minutes # (string) alapértelmezett nincs beállítva. + # mennyi inaktivitás után lesz a felhasználó kijelentkeztetve + expiration: 30 minutes # (string) alapértelmezett nincs beállítva - # hol tárolja a bejelentkezett felhasználó adatait - tárolás: session # (session|cookie) alapértelmezett a session + # hova tároljuk a bejelentkezett felhasználó adatait + storage: session # (session|cookie) alapértelmezett a session ``` -Ha a `cookie` oldalt választja tárolóhelynek, a következő beállításokat is megadhatja: +Ha a `cookie` tárolót választja, beállíthatja még ezeket az opciókat: ```neon security: authentication: # cookie neve - cookieName: userId # (string) výchozí je userid + cookieName: userId # (string) alapértelmezett a userid - # mely hosztok kaphatják meg a cookie-t + # domainek, amelyek elfogadják a cookie-t cookieDomain: 'example.com' # (string|domain) - # korlátozások a cross-origin kérés elérésekor - cookieSamesite: None # (Strict|Lax|None) alapértelmezés szerint Lax + # korlátozás más domainről való hozzáférés esetén + cookieSamesite: None # (Strict|Lax|None) alapértelmezett a Lax ``` + + +DI szolgáltatások +----------------- + +Ezek a szolgáltatások kerülnek hozzáadásra a DI konténerhez: + +| Név | Típus | Leírás +|---------------------------------------------------------- +| `security.authenticator` | [api:Nette\Security\Authenticator] | [authentikátor|authentication] +| `security.authorizator` | [api:Nette\Security\Authorizator] | [autorizátor|authorization] +| `security.passwords` | [api:Nette\Security\Passwords] | [jelszó hashelés|passwords] +| `security.user` | [api:Nette\Security\User] | aktuális felhasználó +| `security.userStorage` | [api:Nette\Security\UserStorage] | [#tároló] diff --git a/security/hu/passwords.texy b/security/hu/passwords.texy index c12f5548ff..7f957976a2 100644 --- a/security/hu/passwords.texy +++ b/security/hu/passwords.texy @@ -1,12 +1,12 @@ -Jelszó zárolás -************** +Jelszó hashelés +*************** .[perex] -Felhasználóink biztonsága érdekében soha nem tároljuk jelszavaikat egyszerű szöveges formátumban, helyette a jelszó kivonatát tároljuk. A kivonatolás nem reverzibilis művelet, a jelszó nem állítható vissza. A jelszó azonban feltörhető, és hogy a feltörést a lehető legnehezebbé tegyük, biztonságos algoritmust kell használnunk. A [api:Nette\Security\Passwords] osztály segít ebben. +Annak érdekében, hogy biztosítsuk felhasználóink biztonságát, nem tároljuk jelszavaikat olvasható formában, hanem csak a lenyomatukat (ún. hash) tároljuk. A lenyomatból nem lehet visszafejteni a jelszó eredeti formáját. Fontos, hogy biztonságos algoritmust használjunk a lenyomat létrehozásához. Ebben segít nekünk a [api:Nette\Security\Passwords] osztály. -→ [Telepítés és követelmények |@home#Installation] +→ [Telepítés és követelmények |@home#Telepítés] -A keretrendszer automatikusan hozzáad egy `Nette\Security\Passwords` szolgáltatást a DI konténerhez `security.passwords` néven, amelyet [függőségi injektálással |dependency-injection:passing-dependencies] átadva kapunk meg: +A keretrendszer automatikusan hozzáadja a DI konténerhez a `Nette\Security\Passwords` típusú szolgáltatást `security.passwords` néven, amelyhez úgy juthat hozzá, hogy [dependency injection |dependency-injection:passing-dependencies] segítségével kéri át. ```php use Nette\Security\Passwords; @@ -24,44 +24,44 @@ class Foo __construct($algo=PASSWORD_DEFAULT, array $options=[]): string .[method] ======================================================================== -Kiválasztja, hogy melyik [biztonságos algoritmust |https://www.php.net/manual/en/password.constants.php] használja a hashingoláshoz, és hogyan konfigurálja azt. +Kiválasztjuk, melyik [biztonságos algoritmust|https://www.php.net/manual/en/password.constants.php] használjuk a hash generálásához, és konfiguráljuk annak paramétereit. -Az alapértelmezett a `PASSWORD_DEFAULT`, így az algoritmus kiválasztása a PHP-re marad. Az algoritmus változhat az újabb PHP-kiadásokban, amikor újabb, erősebb hashing algoritmusok támogatottak. Ezért tisztában kell lennie azzal, hogy a kapott hash hossza változhat. Ezért a kapott hash-t úgy kell tárolni, hogy elegendő karaktert tudjon tárolni, 255 az ajánlott szélesség. +Alapértelmezés szerint a `PASSWORD_DEFAULT` használatos, tehát az algoritmus kiválasztása a PHP-ra van bízva. Az algoritmus megváltozhat a PHP újabb verzióiban, ha újabb, erősebb hashelési algoritmusok jelennek meg. Ezért tudatában kell lennie annak, hogy az eredményül kapott hash hossza megváltozhat, és olyan módon kell tárolnia, amely elegendő karaktert képes befogadni, a 255 az ajánlott szélesség. -Így használhatod a bcrypt algoritmust, és a hashelés sebességét a cost paraméter segítségével változtathatod meg az alapértelmezett 10-es értékről. A 2020-as évben, 10-es költséggel egy jelszó hashelése nagyjából 80 ms, 11-es költséggel 160 ms, 12-es költséggel pedig 320 ms, a skála logaritmikus. Minél lassabb, annál jobb, a 10-12-es költség a legtöbb felhasználási esethez elég lassúnak tekinthető: +Példa a bcrypt algoritmus hashelési sebességének beállítására a cost paraméter megváltoztatásával: (2020-ban az alapértelmezett 10, a jelszó hashelése kb. 80ms-ig tart, a cost 11 esetén kb. 160ms, a cost 12 esetén kb. 320ms, minél lassabb, annál jobb a védelem, miközben a 10-12 sebesség már elegendő védelemnek számít) ```php -// a jelszavakat a bcrypt algoritmus 2^12 (2^költség) iterációjával fogjuk hash-olni. +// a jelszavakat a bcrypt algoritmus 2^12 (2^cost) iterációjával fogjuk hashelni $passwords = new Passwords(PASSWORD_BCRYPT, ['cost' => 12]); ``` -Függőségi injektálással: +Dependency injection segítségével: ```neon services: security.passwords: Nette\Security\Passwords(::PASSWORD_BCRYPT, [cost: 12]) ``` -hash(string $passwords): string .[method] -========================================= +hash(string $password): string .[method] +======================================== -Jelszó hash generálása. +Generálja a jelszó hash-ét. ```php -$res = $passwords->hash($password); // A jelszó mása +$res = $passwords->hash($password); // Hasheli a jelszót ``` -Az eredmény `$res` egy karakterlánc, amely a hash mellett tartalmazza a használt algoritmus azonosítóját, a beállításokat és a kriptográfiai sót (véletlenszerű adat, amely biztosítja, hogy ugyanarra a jelszóra más hash generálódjon). Ezért visszafelé kompatibilis, például ha megváltoztatja a paramétereket, a korábbi beállításokkal tárolt hash-ek ellenőrizhetők. Ez a teljes eredmény az adatbázisban tárolódik, így nincs szükség a só vagy a beállítások külön tárolására. +Az `$res` eredmény egy string, amely a maga hash-én kívül tartalmazza a használt algoritmus azonosítóját, annak beállításait és a kriptográfiai salt-ot (véletlenszerű adatok, amelyek biztosítják, hogy ugyanahhoz a jelszóhoz más hash generálódjon). Tehát visszafelé kompatibilis, ha például megváltoztatja a paramétereket, akkor az előző beállításokkal tárolt hash-ek is ellenőrizhetők lesznek. Ez az egész eredmény az adatbázisba kerül mentésre, így nincs szükség a salt vagy a beállítások külön tárolására. verify(string $password, string $hash): bool .[method] ====================================================== -Kideríti, hogy a megadott jelszó megegyezik-e a megadott hash-val. A `$hash` adatbázisból a felhasználónév vagy az e-mail cím alapján. +Megállapítja, hogy az adott jelszó megfelel-e az adott lenyomatnak. Az `$hash`-t az adatbázisból szerezze be a megadott felhasználónév vagy e-mail cím alapján. ```php if ($passwords->verify($password, $hash)) { - // Helyes jelszó + // helyes jelszó } ``` @@ -69,13 +69,13 @@ if ($passwords->verify($password, $hash)) { needsRehash(string $hash): bool .[method] ========================================= -Kideríti, hogy a hash megegyezik-e a konstruktorban megadott beállításokkal. +Megállapítja, hogy a hash megfelel-e a konstruktorban megadott opcióknak. -Ezt a metódust akkor használjuk, ha például hashing paramétereket változtatunk. A jelszó ellenőrzése a hash-sel együtt tárolt paramétereket használja, és ha a `needsRehash()` true-t ad vissza, akkor újra ki kell számítani a hash-t, ezúttal a frissített paraméterekkel, és újra el kell tárolni az adatbázisban. Ez biztosítja, hogy a jelszavak hash-jei automatikusan "frissüljenek", amikor a felhasználók bejelentkeznek. +Hasznos lehet abban a pillanatban, amikor pl. megváltoztatja a hashelési sebességet. Az ellenőrzés a tárolt beállítások szerint történik, és ha a `needsRehash()` `true`-t ad vissza, akkor újra létre kell hozni a hash-t, ezúttal az új paraméterekkel, és újra el kell menteni az adatbázisba. Így automatikusan "frissülnek" a tárolt hash-ek a felhasználók bejelentkezésekor. ```php if ($passwords->needsRehash($hash)) { $hash = $passwords->hash($password); - // $hash tárolása az adatbázisban + // $hash mentése az adatbázisba } ``` diff --git a/security/it/@home.texy b/security/it/@home.texy index f6b690e10d..2b8f47b46e 100644 --- a/security/it/@home.texy +++ b/security/it/@home.texy @@ -1,14 +1,14 @@ -Sicurezza -********* +Nette Security +************** .[perex] -Il pacchetto `nette/security` si occupa dell'[autenticazione degli utenti |authentication], del [controllo degli accessi |authorization] e dell'[hashing delle password |passwords]. +Il pacchetto `nette/security` si occupa del [login degli utenti |authentication], della [verifica delle autorizzazioni |authorization] e dell'[hashing delle password |passwords]. -Installazione .[#toc-installation] ----------------------------------- +Installazione +------------- -Scaricare e installare il pacchetto utilizzando [Composer |best-practices:composer]: +Scarica e installa la libreria utilizzando [Composer|best-practices:composer]: ```shell composer require nette/security diff --git a/security/it/@left-menu.texy b/security/it/@left-menu.texy index 413d4e4ae1..58208be84b 100644 --- a/security/it/@left-menu.texy +++ b/security/it/@left-menu.texy @@ -1,7 +1,7 @@ -Sicurezza Nette -*************** -- [Panoramica |@home] -- [Autenticazione |Authentication] -- [Autorizzazione |Authorization] -- [Hashing della password |passwords] -- [Configurazione |Configuration] +Nette Security +************** +- [Introduzione |@home] +- [Autenticazione |authentication] +- [Autorizzazione |authorization] +- [Hashing delle password |passwords] +- [Configurazione |configuration] diff --git a/security/it/@meta.texy b/security/it/@meta.texy new file mode 100644 index 0000000000..4647d0c8a2 --- /dev/null +++ b/security/it/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentazione Nette}} diff --git a/security/it/authentication.texy b/security/it/authentication.texy index cac605c86d..bdcfe57616 100644 --- a/security/it/authentication.texy +++ b/security/it/authentication.texy @@ -1,48 +1,48 @@ -Autenticazione degli utenti -*************************** +Login utente (Autenticazione) +***************************** <div class=perex> -Le applicazioni Web di piccole dimensioni non necessitano di alcun meccanismo per il login degli utenti o per la verifica dei loro privilegi. In questo capitolo si parlerà di: +Quasi nessuna applicazione web può fare a meno di un meccanismo di login utente e di verifica dei permessi utente. In questo capitolo parleremo di: -- login e logout dell'utente -- autenticatori e autenticatori personalizzati +- login e logout degli utenti +- autenticatori personalizzati </div> -→ [Installazione e requisiti |@home#Installation] +→ [Installazione e requisiti |@home#Installazione] -Negli esempi, utilizzeremo un oggetto di classe [api:Nette\Security\User], che rappresenta l'utente corrente e che si ottiene passandoglielo tramite [dependency injection |dependency-injection:passing-dependencies]. Nei presentatori è sufficiente chiamare `$user = $this->getUser()`. +Negli esempi useremo l'oggetto della classe [api:Nette\Security\User], che rappresenta l'utente corrente e al quale potete accedere facendovelo passare tramite [dependency injection |dependency-injection:passing-dependencies]. Nei presenter basta solo chiamare `$user = $this->getUser()`. -Autenticazione .[#toc-authentication] -===================================== +Autenticazione +============== -Autenticazione significa **accesso dell'utente**, cioè il processo durante il quale viene verificata l'identità di un utente. Di solito l'utente si identifica utilizzando nome utente e password. La verifica viene eseguita dal cosiddetto [autenticatore |#authenticator]. Se il login fallisce, viene lanciato `Nette\Security\AuthenticationException`. +Per autenticazione si intende il **login degli utenti**, ovvero il processo durante il quale si verifica se l'utente è davvero chi dice di essere. Di solito si dimostra con nome utente e password. La verifica viene eseguita dal cosiddetto [#authenticator]. Se il login fallisce, viene lanciata `Nette\Security\AuthenticationException`. ```php try { $user->login($username, $password); } catch (Nette\Security\AuthenticationException $e) { - $this->flashMessage('The username or password you entered is incorrect.'); + $this->flashMessage('Nome utente o password non corretti'); } ``` -Ecco come disconnettere l'utente: +In questo modo si effettua il logout dell'utente: ```php $user->logout(); ``` -E controllare se l'utente è connesso: +E per verificare se è loggato: ```php -echo $user->isLoggedIn() ? 'yes' : 'no'; +echo $user->isLoggedIn() ? 'sì' : 'no'; ``` -Semplice, no? E tutti gli aspetti della sicurezza sono gestiti da Nette per voi. +Molto semplice, vero? E tutti gli aspetti di sicurezza li gestisce Nette per voi. -Nel presenter, è possibile verificare il login nel metodo `startup()` e reindirizzare un utente non loggato alla pagina di login. +Nei presenter potete verificare il login nel metodo `startup()` e reindirizzare l'utente non loggato alla pagina di login. ```php protected function startup() @@ -55,39 +55,38 @@ protected function startup() ``` -Scadenza .[#toc-expiration] -=========================== +Scadenza +======== -Il login dell'utente scade insieme alla [scadenza del repository |#Storage for Logged User], che di solito è una sessione (vedere l'impostazione della [scadenza della sessione |http:configuration#session] ). -Tuttavia, si può anche impostare un intervallo di tempo più breve, dopo il quale l'utente viene disconnesso. A questo scopo si utilizza il metodo `setExpiration()`, che viene richiamato prima di `login()`. Fornire come parametro una stringa con un tempo relativo: +Il login dell'utente scade insieme alla [scadenza dello storage |#Storage dell utente loggato], che di solito è la sessione (vedi impostazione [scadenza della sessione |http:configuration#Sessione]). È però possibile impostare anche un intervallo di tempo più breve, trascorso il quale l'utente viene disconnesso. A questo serve il metodo `setExpiration()`, che viene chiamato prima di `login()`. Come parametro indicate una stringa con il tempo relativo: ```php -// il login scade dopo 30 minuti di inattività +// il login scadrà dopo 30 minuti di inattività $user->setExpiration('30 minutes'); -// annullare la scadenza impostata +// annullamento della scadenza impostata $user->setExpiration(null); ``` -Il metodo `$user->getLogoutReason()` indica se l'utente è stato disconnesso perché l'intervallo di tempo è scaduto. Restituisce la costante `Nette\Security\UserStorage::LogoutInactivity` se il tempo è scaduto o `UserStorage::LogoutManual` se è stato chiamato il metodo `logout()`. +Se l'utente è stato disconnesso a causa della scadenza dell'intervallo di tempo, lo rivela il metodo `$user->getLogoutReason()`, che restituisce o la costante `Nette\Security\UserStorage::LogoutInactivity` (limite di tempo scaduto) o `UserStorage::LogoutManual` (disconnesso dal metodo `logout()`). -Autenticatore .[#toc-authenticator] -=================================== +Authenticator +============= -È un oggetto che verifica i dati di accesso, cioè di solito il nome e la password. L'implementazione banale è la classe [api:Nette\Security\SimpleAuthenticator], che può essere definita nella [configurazione |configuration]: +Si tratta di un oggetto che verifica le credenziali di accesso, ovvero di solito nome e password. Una forma banale è la classe [api:Nette\Security\SimpleAuthenticator], che possiamo definire nella [configurazione|configuration]: ```neon security: users: - # name: password - johndoe: secret123 - kathy: evenmoresecretpassword + # nome: password + francesco: passwordsegreta + caterina: passwordancorapiusegreta ``` -Questa soluzione è più adatta a scopi di test. Verrà mostrato come creare un autenticatore che verifichi le credenziali rispetto a una tabella del database. +Questa soluzione è adatta piuttosto per scopi di test. Vediamo come creare un autenticatore che verifichi le credenziali di accesso rispetto a una tabella del database. -Un autenticatore è un oggetto che implementa l'interfaccia [api:Nette\Security\Authenticator] con il metodo `authenticate()`. Il suo compito è restituire la cosiddetta [identità |#identity] o lanciare un'eccezione `Nette\Security\AuthenticationException`. Sarebbe anche possibile fornire un codice di errore a grana fine `Authenticator::IdentityNotFound` o `Authenticator::InvalidCredential`. +L'autenticatore è un oggetto che implementa l'interfaccia [api:Nette\Security\Authenticator] con il metodo `authenticate()`. Il suo compito è restituire la cosiddetta [#identità] o lanciare un'eccezione `Nette\Security\AuthenticationException`. Sarebbe possibile indicare anche un codice di errore per distinguere più finemente la situazione verificatasi: `Authenticator::IdentityNotFound` e `Authenticator::InvalidCredential`. ```php use Nette; @@ -117,16 +116,16 @@ class MyAuthenticator implements Nette\Security\Authenticator return new SimpleIdentity( $row->id, - $row->role, // o array di ruoli + $row->role, // o array di più ruoli ['name' => $row->username], ); } } ``` -La classe MyAuthenticator comunica con il database attraverso [Nette Database Explorer |database:explorer] e lavora con la tabella `users`, dove la colonna `username` contiene il nome di login dell'utente e la colonna `password` contiene l ['hash |passwords]. Dopo aver verificato il nome e la password, restituisce l'identità con l'ID dell'utente, il ruolo (colonna `role` della tabella), che citeremo [più avanti |#roles], e un array con dati aggiuntivi (nel nostro caso, il nome utente). +La classe MyAuthenticator comunica con il database tramite [Nette Database Explorer|database:explorer] e lavora con la tabella `users`, dove nella colonna `username` c'è il nome di login dell'utente e nella colonna `password` l'[hash della password|passwords]. Dopo aver verificato nome e password, restituisce l'identità, che contiene l'ID dell'utente, il suo ruolo (colonna `role` nella tabella), di cui parleremo [più avanti |authorization#Ruoli], e un array con altri dati (nel nostro caso il nome utente). -Aggiungeremo l'autenticatore alla configurazione [come servizio |dependency-injection:services] del contenitore DI: +Aggiungiamo ancora l'autenticatore alla configurazione [come servizio|dependency-injection:services] del container DI: ```neon services: @@ -134,23 +133,23 @@ services: ``` -$onLoggedIn, $onLoggedOut Eventi +Eventi $onLoggedIn, $onLoggedOut -------------------------------- -L'oggetto `Nette\Security\User` ha gli [eventi |nette:glossary#Events] `$onLoggedIn` e `$onLoggedOut`, per cui è possibile aggiungere callback che vengono attivati dopo un login riuscito o dopo il logout dell'utente. +L'oggetto `Nette\Security\User` ha [eventi |nette:glossary#Eventi] `$onLoggedIn` e `$onLoggedOut`, potete quindi aggiungere callback che vengono invocati dopo il login riuscito rispettivamente dopo il logout dell'utente. ```php $user->onLoggedIn[] = function () { - // l'utente ha appena effettuato l'accesso + // l'utente è stato appena loggato }; ``` -Identità .[#toc-identity] -========================= +Identità +======== -Un'identità è un insieme di informazioni su un utente che viene restituito dall'autenticatore e che viene poi memorizzato in una sessione e recuperato usando `$user->getIdentity()`. Quindi possiamo ottenere l'id, i ruoli e altri dati dell'utente come li abbiamo passati nell'autenticatore: +L'identità rappresenta un insieme di informazioni sull'utente, restituito dall'autenticatore e che viene successivamente conservato nella sessione e ottenuto tramite `$user->getIdentity()`. Possiamo quindi ottenere l'id, i ruoli e altri dati utente, così come li abbiamo passati nell'autenticatore: ```php $user->getIdentity()->getId(); @@ -158,26 +157,26 @@ $user->getIdentity()->getId(); $user->getIdentity()->getRoles(); -// i dati dell'utente possono essere accessibili come proprietà -// il nome che abbiamo passato in MyAuthenticator +// i dati utente sono disponibili come proprietà +// nome, che abbiamo passato in MyAuthenticator $user->getIdentity()->name; ``` -È importante notare che quando l'utente si disconnette usando `$user->logout()`, **l'identità non viene cancellata** ed è ancora disponibile. Quindi, se l'identità esiste, di per sé non garantisce che l'utente sia anche connesso. Se si vuole cancellare esplicitamente l'identità, si effettua il logout dell'utente tramite `logout(true)`. +Ciò che è importante è che al momento del logout tramite `$user->logout()` **l'identità non viene cancellata** ed è ancora disponibile. Quindi, anche se l'utente ha un'identità, non deve necessariamente essere loggato. Se volessimo cancellare esplicitamente l'identità, effettueremo il logout dell'utente chiamando `logout(true)`. -In questo modo, è ancora possibile stabilire quale utente si trovi sul computer e, ad esempio, visualizzare offerte personalizzate nell'e-shop; tuttavia, è possibile visualizzare i suoi dati personali solo dopo aver effettuato il login. +Grazie a ciò potete continuare a presumere quale utente sia al computer e ad esempio mostrargli offerte personalizzate nell'e-shop, tuttavia potete visualizzare i suoi dati personali solo dopo il login. -L'identità è un oggetto che implementa l'interfaccia [api:Nette\Security\IIdentity]; l'implementazione predefinita è [api:Nette\Security\SimpleIdentity]. Come già detto, l'identità è memorizzata nella sessione, quindi se, ad esempio, cambiamo il ruolo di alcuni utenti connessi, i vecchi dati saranno conservati nell'identità fino a quando l'utente non effettuerà nuovamente il login. +L'identità è un oggetto che implementa l'interfaccia [api:Nette\Security\IIdentity], l'implementazione predefinita è [api:Nette\Security\SimpleIdentity]. E come accennato, viene mantenuta nella sessione, quindi se ad esempio cambiamo il ruolo di uno degli utenti loggati, i vecchi dati rimarranno nella sua identità fino al suo successivo login. -Memorizzazione dell'utente registrato .[#toc-storage-for-logged-user] -===================================================================== +Storage dell'utente loggato +=========================== -Le due informazioni fondamentali sull'utente, ossia se è connesso e la sua [identità |#identity], sono solitamente contenute nella sessione. Che può essere modificata. La memorizzazione di queste informazioni è affidata a un oggetto che implementa l'interfaccia `Nette\Security\UserStorage`. Esistono due implementazioni standard, la prima trasmette i dati in una sessione e la seconda in un cookie. Si tratta delle classi `Nette\Bridges\SecurityHttp\SessionStorage` e `CookieStorage`. È possibile scegliere la memorizzazione e configurarla in modo molto comodo nella configurazione [sicurezza › autenticazione |configuration]. +Le due informazioni fondamentali sull'utente, ovvero se è loggato e la sua [#identità], vengono di solito trasmesse nella sessione. Ciò può essere modificato. La memorizzazione di queste informazioni è gestita da un oggetto che implementa l'interfaccia `Nette\Security\UserStorage`. Sono disponibili due implementazioni standard, la prima trasmette i dati nella sessione e la seconda in un cookie. Si tratta delle classi `Nette\Bridges\SecurityHttp\SessionStorage` e `CookieStorage`. Potete scegliere lo storage e configurarlo molto comodamente nella configurazione [security › authentication |configuration#Storage]. -Si può anche controllare esattamente come avverrà il salvataggio (*sleep*) e il ripristino (*wakeup*) dell'identità. Tutto ciò che serve è che l'autenticatore implementi l'interfaccia `Nette\Security\IdentityHandler`. Questa ha due metodi: `sleepIdentity()` è chiamato prima che l'identità sia scritta nella memoria e `wakeupIdentity()` è chiamato dopo che l'identità è stata letta. I metodi possono modificare il contenuto dell'identità o sostituirla con un nuovo oggetto che viene restituito. Il metodo `wakeupIdentity()` può anche restituire `null`, che fa uscire l'utente. +Inoltre, potete influenzare come avverrà esattamente la memorizzazione dell'identità (*sleep*) e il ripristino (*wakeup*). Basta che l'authenticator implementi l'interfaccia `Nette\Security\IdentityHandler`. Questa ha due metodi: `sleepIdentity()` viene chiamato prima della scrittura dell'identità nello storage e `wakeupIdentity()` dopo la sua lettura. I metodi possono modificare il contenuto dell'identità, oppure sostituirla con un nuovo oggetto che restituiscono. Il metodo `wakeupIdentity()` può persino restituire `null`, disconnettendo così l'utente. -Come esempio, mostreremo una soluzione a una domanda comune su come aggiornare i ruoli delle identità subito dopo il ripristino da una sessione. Nel metodo `wakeupIdentity()` si passano i ruoli correnti all'identità, ad esempio dal database: +Come esempio, mostreremo la soluzione a una domanda frequente, ovvero come aggiornare i ruoli nell'identità subito dopo il caricamento dalla sessione. Nel metodo `wakeupIdentity()` passiamo all'identità i ruoli attuali, ad esempio dal database: ```php final class Authenticator implements @@ -185,7 +184,7 @@ final class Authenticator implements { public function sleepIdentity(IIdentity $identity): IIdentity { - // Qui si può cambiare l'identità prima di memorizzarla dopo il login, + // qui si può modificare l'identità prima della scrittura nello storage dopo il login, // ma ora non ne abbiamo bisogno return $identity; } @@ -199,11 +198,11 @@ final class Authenticator implements } ``` -E ora torniamo alla memorizzazione basata sui cookie. Consente di creare un sito web in cui gli utenti possono accedere senza la necessità di utilizzare le sessioni. Quindi non ha bisogno di scrivere su disco. Dopo tutto, è così che funziona il sito web che state leggendo, compreso il forum. In questo caso, l'implementazione di `IdentityHandler` è una necessità. Nel cookie verrà memorizzato solo un token casuale che rappresenta l'utente registrato. +E ora torniamo allo storage basato sui cookie. Vi permette di creare un sito web dove gli utenti possono loggarsi senza bisogno di sessioni. Quindi non ha bisogno di scrivere sul disco. Del resto, così funziona anche il sito che state leggendo, compreso il forum. In questo caso, l'implementazione di `IdentityHandler` è una necessità. Nel cookie, infatti, memorizzeremo solo un token casuale che rappresenta l'utente loggato. -Quindi, per prima cosa impostiamo la memorizzazione desiderata nella configurazione usando `security › authentication › storage: cookie`. +Prima di tutto, quindi, nella configurazione impostiamo lo storage desiderato tramite `security › authentication › storage: cookie`. -Aggiungeremo una colonna `authtoken` nel database, in cui ogni utente avrà una stringa [completamente casuale, unica e indovinabile |utils:random] di lunghezza sufficiente (almeno 13 caratteri). Il repository `CookieStorage` memorizza solo il valore `$identity->getId()` nel cookie, quindi in `sleepIdentity()` sostituiamo l'identità originale con un proxy con `authtoken` nell'ID, mentre nel metodo `wakeupIdentity()` ripristiniamo l'intera identità dal database secondo l'authtoken: +Nel database creiamo una colonna `authtoken`, in cui ogni utente avrà una stringa [completamente casuale, unica e non indovinabile|utils:random] di lunghezza sufficiente (almeno 13 caratteri). Lo storage `CookieStorage` trasmette nel cookie solo il valore `$identity->getId()`, quindi in `sleepIdentity()` sostituiamo l'identità originale con una sostitutiva con `authtoken` nell'ID, al contrario nel metodo `wakeupIdentity()` leggiamo l'intera identità dal database in base all'authtoken: ```php final class Authenticator implements @@ -212,21 +211,21 @@ final class Authenticator implements public function authenticate(string $username, string $password): SimpleIdentity { $row = $this->db->fetch('SELECT * FROM user WHERE username = ?', $username); - // verifica la password + // verifichiamo la password ... - // restituiamo l'identità con tutti i dati del database + // restituiamo l'identità con tutti i dati dal database return new SimpleIdentity($row->id, null, (array) $row); } public function sleepIdentity(IIdentity $identity): SimpleIdentity { - // restituiamo un'identità proxy, dove l'ID è l'authtoken + // restituiamo un'identità sostitutiva, dove nell'ID ci sarà l'authtoken return new SimpleIdentity($identity->authtoken); } public function wakeupIdentity(IIdentity $identity): ?SimpleIdentity { - // sostituisce l'identità proxy con un'identità completa, come in authenticate() + // sostituiamo l'identità sostitutiva con l'identità completa, come in authenticate() $row = $this->db->fetch('SELECT * FROM user WHERE authtoken = ?', $identity->getId()); return $row ? new SimpleIdentity($row->id, null, (array) $row) @@ -236,16 +235,16 @@ final class Authenticator implements ``` -Autenticazioni multiple indipendenti .[#toc-multiple-independent-authentications] -================================================================================= +Più login indipendenti +====================== -È possibile avere più utenti registrati indipendenti all'interno di un sito e una sessione alla volta. Per esempio, se si vuole avere un'autenticazione separata per il frontend e il backend, basterà impostare uno spazio dei nomi di sessione unico per ciascuno di essi: +È possibile avere contemporaneamente, all'interno dello stesso sito web e della stessa sessione, più utenti loggati indipendentemente. Se ad esempio vogliamo avere sul sito un'autenticazione separata per l'amministrazione e la parte pubblica, basta impostare per ognuna un proprio nome: ```php $user->getStorage()->setNamespace('backend'); ``` -È necessario tenere presente che questo deve essere impostato in tutti i luoghi appartenenti allo stesso segmento. Quando si utilizzano i presentatori, si imposterà lo spazio dei nomi nell'antenato comune, di solito BasePresenter. Per farlo, estenderemo il metodo [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()]: +È importante ricordare di impostare sempre il namespace in tutti i punti appartenenti alla parte specifica. Se usiamo i presenter, impostiamo il namespace nel predecessore comune per quella parte - di solito BasePresenter. Lo facciamo estendendo il metodo [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()]: ```php public function checkRequirements($element): void @@ -256,10 +255,10 @@ public function checkRequirements($element): void ``` -Autenticatori multipli .[#toc-multiple-authenticators] ------------------------------------------------------- +Più autenticatori +----------------- -La suddivisione di un'applicazione in segmenti con autenticazione indipendente richiede generalmente autenticatori diversi. Tuttavia, registrare due classi che implementano Authenticator nei servizi di configurazione darebbe luogo a un errore, perché Nette non saprebbe quale di esse dovrebbe essere [autocablata |dependency-injection:autowiring] all'oggetto `Nette\Security\User`. Per questo motivo, dobbiamo limitare il cablaggio automatico con `autowired: self`, in modo che venga attivato solo quando la classe viene richiesta in modo specifico: +La divisione dell'applicazione in parti con login indipendente richiede di solito anche autenticatori diversi. Tuttavia, non appena registrassimo nella configurazione dei servizi due classi che implementano Authenticator, Nette non saprebbe quale assegnare automaticamente all'oggetto `Nette\Security\User`, e mostrerebbe un errore. Pertanto, dobbiamo limitare l'[autowiring |dependency-injection:autowiring] per gli autenticatori in modo che funzioni solo quando qualcuno richiede una classe specifica, ad esempio FrontAuthenticator, cosa che otteniamo scegliendo `autowired: self`: ```neon services: @@ -278,7 +277,7 @@ class SignPresenter extends Nette\Application\UI\Presenter } ``` -Dobbiamo impostare il nostro autenticatore sull'oggetto User solo prima di chiamare il metodo [login() |api:Nette\Security\User::login()], il che di solito significa nel callback del form di login: +Impostiamo l'autenticatore dell'oggetto User prima di chiamare il metodo [login() |api:Nette\Security\User::login()], quindi di solito nel codice del form che lo logga: ```php $form->onSuccess[] = function (Form $form, \stdClass $data) { diff --git a/security/it/authorization.texy b/security/it/authorization.texy index 6ccda5cb47..3cfd43c8c8 100644 --- a/security/it/authorization.texy +++ b/security/it/authorization.texy @@ -1,48 +1,48 @@ -Controllo degli accessi (autorizzazione) -**************************************** +Verifica dei permessi (Autorizzazione) +************************************** .[perex] -L'autorizzazione determina se un utente ha privilegi sufficienti, ad esempio, per accedere a una risorsa specifica o per eseguire un'azione. L'autorizzazione presuppone che l'utente sia stato precedentemente autenticato, cioè che abbia effettuato il login. +L'autorizzazione verifica se un utente ha permessi sufficienti, ad esempio, per accedere a una determinata risorsa o per eseguire una determinata azione. L'autorizzazione presuppone una precedente autenticazione riuscita, ovvero che l'utente sia loggato. -→ [Installazione e requisiti |@home#Installation] +→ [Installazione e requisiti |@home#Installazione] -Negli esempi, utilizzeremo un oggetto di classe [api:Nette\Security\User], che rappresenta l'utente corrente e che si ottiene passandoglielo tramite [dependency injection |dependency-injection:passing-dependencies]. Nei presenter è sufficiente chiamare `$user = $this->getUser()`. +Negli esempi useremo l'oggetto della classe [api:Nette\Security\User], che rappresenta l'utente corrente e al quale potete accedere facendovelo passare tramite [dependency injection |dependency-injection:passing-dependencies]. Nei presenter basta solo chiamare `$user = $this->getUser()`. -Per siti web molto semplici con amministrazione, in cui i diritti degli utenti non sono distinti, è possibile utilizzare il metodo già noto come criterio di autorizzazione `isLoggedIn()`. In altre parole: una volta che un utente è loggato, ha i permessi per tutte le azioni e viceversa. +Per siti web molto semplici con amministrazione, dove non si distinguono i permessi degli utenti, è possibile utilizzare come criterio di autorizzazione il già noto metodo `isLoggedIn()`. In altre parole: non appena l'utente è loggato, ha tutti i permessi e viceversa. ```php -if ($user->isLoggedIn()) { // l'utente è connesso? - deleteItem(); // in caso affermativo, può cancellare un elemento. +if ($user->isLoggedIn()) { // l'utente è loggato? + deleteItem(); // allora ha il permesso per l'operazione } ``` -Ruoli .[#toc-roles] -------------------- +Ruoli +----- -Lo scopo dei ruoli è quello di offrire una gestione più precisa dei permessi e di rimanere indipendenti dal nome dell'utente. Non appena l'utente si collega, gli vengono assegnati uno o più ruoli. I ruoli stessi possono essere semplici stringhe, ad esempio `admin`, `member`, `guest`, ecc. Sono specificati nel secondo parametro del costruttore `SimpleIdentity`, come stringa o come array. +Lo scopo dei ruoli è offrire un controllo più preciso dei permessi e rimanere indipendenti dal nome utente. A ogni utente, subito al momento del login, assegniamo uno o più ruoli in cui opererà. I ruoli possono essere semplici stringhe, ad esempio `admin`, `member`, `guest`, ecc. Vengono indicati come secondo parametro del costruttore `SimpleIdentity`, sia come stringa che come array di stringhe - ruoli. -Come criterio di autorizzazione, si utilizzerà il metodo `isInRole()`, che verifica se l'utente è nel ruolo indicato: +Come criterio di autorizzazione ora useremo il metodo `isInRole()`, che rivela se l'utente opera nel ruolo specificato: ```php -if ($user->isInRole('admin')) { // L'utente ha il ruolo di amministratore? - deleteItem(); // in caso affermativo, l'utente può cancellare un elemento. +if ($user->isInRole('admin')) { // l'utente è nel ruolo di admin? + deleteItem(); // allora ha il permesso per l'operazione } ``` -Come si sa, la disconnessione dell'utente non cancella la sua identità. Pertanto, il metodo `getIdentity()` restituisce ancora l'oggetto `SimpleIdentity`, comprensivo di tutti i ruoli concessi. Il framework Nette aderisce al principio "meno codice, più sicurezza", quindi quando si controllano i ruoli, non è necessario verificare se l'utente è connesso. Il metodo `isInRole()` funziona con **ruoli effettivi**, cioè se l'utente è connesso, vengono utilizzati i ruoli assegnati all'identità, se non è connesso, viene invece utilizzato un ruolo speciale automatico `guest`. +Come già sapete, dopo il logout dell'utente la sua identità non deve necessariamente essere cancellata. Quindi il metodo `getIdentity()` continua a restituire l'oggetto `SimpleIdentity`, inclusi tutti i ruoli assegnati. Nette Framework segue il principio "less code, more security", dove meno scrittura porta a un codice più sicuro, quindi nel verificare i ruoli non è necessario verificare anche se l'utente è loggato. Il metodo `isInRole()` lavora con i **ruoli effettivi,** ovvero se l'utente è loggato, si basa sui ruoli indicati nell'identità, se non è loggato, ha automaticamente il ruolo speciale `guest`. -Autorizzatore .[#toc-authorizator] ----------------------------------- +Autorizzatore +------------- -Oltre ai ruoli, introdurremo i termini risorsa e operazione: +Oltre ai ruoli, introdurremo anche i concetti di risorsa e operazione: -- **ruolo** è un attributo dell'utente - ad esempio moderatore, editore, visitatore, utente registrato, amministratore, ... -- **risorsa** è un'unità logica dell'applicazione (articolo, pagina, utente, voce di menu, sondaggio, presentatore, ...). -- **operazione** è un'attività specifica che l'utente può o non può fare con la *risorsa* - visualizzare, modificare, cancellare, votare, ... +- **ruolo** è una proprietà dell'utente - ad es. moderatore, redattore, visitatore, utente registrato, amministratore... +- **risorsa** (*resource*) è un elemento logico del sito web - articolo, pagina, utente, voce di menu, sondaggio, presenter, ... +- **operazione** (*operation*) è un'attività specifica che l'utente può o non può fare con la risorsa - ad esempio cancellare, modificare, creare, votare, ... -Un autorizzatore è un oggetto che decide se un determinato *ruolo* ha il permesso di eseguire una certa *operazione* con una specifica *risorsa*. È un oggetto che implementa l'interfaccia [api:Nette\Security\Authorizator] con un solo metodo `isAllowed()`: +L'autorizzatore è un oggetto che decide se un dato *ruolo* ha il permesso di eseguire una determinata *operazione* su una determinata *risorsa*. Si tratta di un oggetto che implementa l'interfaccia [api:Nette\Security\Authorizator] con un unico metodo `isAllowed()`: ```php class MyAuthorizator implements Nette\Security\Authorizator @@ -63,50 +63,50 @@ class MyAuthorizator implements Nette\Security\Authorizator } ``` -Aggiungiamo l'autorizzatore alla configurazione [come servizio |dependency-injection:services] del contenitore DI: +Aggiungiamo l'autorizzatore alla configurazione [come servizio|dependency-injection:services] del container DI: ```neon services: - MyAuthorizator ``` -E quello che segue è un esempio di utilizzo. Si noti che questa volta chiamiamo il metodo `Nette\Security\User::isAllowed()`, non quello dell'autenticatore, quindi non c'è il primo parametro `$role`. Questo metodo chiama `MyAuthorizator::isAllowed()` in sequenza per tutti i ruoli utente e restituisce true se almeno uno di essi ha l'autorizzazione. +E segue un esempio di utilizzo. Attenzione, questa volta chiamiamo il metodo `Nette\Security\User::isAllowed()`, non l'autorizzatore, quindi non c'è il primo parametro `$role`. Questo metodo chiama `MyAuthorizator::isAllowed()` progressivamente per tutti i ruoli dell'utente e restituisce true se almeno uno di essi ha il permesso. ```php -if ($user->isAllowed('file')) { // L'utente può fare tutto con la risorsa 'file'? +if ($user->isAllowed('file')) { // l'utente può fare qualsiasi cosa con la risorsa 'file'? useFile(); } -if ($user->isAllowed('file', 'delete')) { // l'utente può cancellare una risorsa 'file'? +if ($user->isAllowed('file', 'delete')) { // può eseguire 'delete' sulla risorsa 'file'? deleteFile(); } ``` -Entrambi gli argomenti sono opzionali e il loro valore predefinito significa *tutto*. +Entrambi i parametri sono facoltativi, il valore predefinito `null` significa *qualsiasi cosa*. -Permessi ACL .[#toc-permission-acl] ------------------------------------ +Permission ACL +-------------- -Nette dispone di un'implementazione integrata dell'autorizzatore, la classe [api:Nette\Security\Permission], che offre un livello ACL (Access Control List) leggero e flessibile per il controllo dei permessi e degli accessi. Quando si lavora con questa classe, si definiscono ruoli, risorse e permessi individuali. I ruoli e le risorse possono formare gerarchie. Per spiegarlo, mostreremo un esempio di applicazione web: +Nette viene fornito con un'implementazione integrata dell'autorizzatore, ovvero la classe [api:Nette\Security\Permission] che fornisce al programmatore uno strato ACL (Access Control List) leggero e flessibile per la gestione dei permessi e degli accessi. Il lavoro con essa consiste nella definizione di ruoli, risorse e singoli permessi. Ruoli e risorse consentono di creare gerarchie. Per spiegare, mostreremo un esempio di applicazione web: -- `guest`: visitatore non loggato, autorizzato a leggere e navigare nella parte pubblica del web, cioè a leggere articoli, commentare e votare nei sondaggi -- `registered`: utente loggato, che può inoltre inserire commenti +- `guest`: visitatore non loggato, che può leggere e navigare nella parte pubblica del sito, ovvero leggere articoli, commenti e votare nei sondaggi +- `registered`: utente registrato loggato, che inoltre può commentare - `admin`: può gestire articoli, commenti e sondaggi -Abbiamo quindi definito alcuni ruoli (`guest`, `registered` e `admin`) e menzionato le risorse (`article`, `comments`, `poll`), alle quali gli utenti possono accedere o compiere azioni (`view`, `vote`, `add`, `edit`). +Abbiamo quindi definito alcuni ruoli (`guest`, `registered` e `admin`) e menzionato risorse (`article`, `comment`, `poll`), alle quali gli utenti con un determinato ruolo possono accedere o eseguire determinate operazioni (`view`, `vote`, `add`, `edit`). -Creiamo un'istanza della classe Permission e definiamo i **ruoli**. È possibile utilizzare l'ereditarietà dei ruoli, che garantisce che, ad esempio, un utente con il ruolo `admin` possa fare ciò che può fare un normale visitatore del sito web (e naturalmente anche di più). +Creiamo un'istanza della classe Permission e definiamo i **ruoli**. È possibile utilizzare la cosiddetta ereditarietà dei ruoli, che garantisce che, ad esempio, un utente con il ruolo di amministratore (`admin`) possa fare anche ciò che fa un normale visitatore del sito (e ovviamente anche di più). ```php $acl = new Nette\Security\Permission; $acl->addRole('guest'); $acl->addRole('registered', 'guest'); // 'registered' eredita da 'guest' -$acl->addRole('admin', 'registered'); // e 'admin' eredita da 'registered' +$acl->addRole('admin', 'registered'); // e da esso eredita 'admin' ``` -Ora definiremo un elenco di **risorse** a cui gli utenti possono accedere: +Ora definiamo anche l'elenco delle **risorse** a cui gli utenti possono accedere. ```php $acl->addResource('article'); @@ -114,49 +114,49 @@ $acl->addResource('comment'); $acl->addResource('poll'); ``` -Le risorse possono anche utilizzare l'ereditarietà, ad esempio possiamo aggiungere `$acl->addResource('perex', 'article')`. +Anche le risorse possono usare l'ereditarietà, sarebbe possibile ad esempio specificare `$acl->addResource('perex', 'article')`. -E ora la cosa più importante. Definiremo tra loro delle **regole** che determinano chi può fare cosa: +E ora la cosa più importante. Definiamo tra loro le regole che determinano chi può fare cosa con cosa: ```php -// tutto è negato ora +// inizialmente nessuno può fare nulla -// permettiamo all'ospite di visualizzare articoli, commenti e sondaggi +// permettiamo a guest di visualizzare articoli, commenti e sondaggi $acl->allow('guest', ['article', 'comment', 'poll'], 'view'); -// e anche votare nei sondaggi +// e nei sondaggi inoltre anche votare $acl->allow('guest', 'poll', 'vote'); -// il registrato eredita i permessi da guesta, gli permetteremo anche di commentare +// registrato eredita i diritti da guest, gli diamo inoltre il diritto di commentare $acl->allow('registered', 'comment', 'add'); -// L'amministratore può vedere e modificare qualsiasi cosa +// l'amministratore può visualizzare e modificare qualsiasi cosa $acl->allow('admin', $acl::All, ['view', 'edit', 'add']); ``` -E se volessimo **impedire** a qualcuno di accedere a una risorsa? +Cosa succede se vogliamo **impedire** a qualcuno l'accesso a una determinata risorsa? ```php -// L'amministratore non può modificare i sondaggi, sarebbe antidemocratico. +// l'amministratore non può modificare i sondaggi, sarebbe antidemocratico $acl->deny('admin', 'poll', 'edit'); ``` -Ora, una volta creato l'insieme di regole, possiamo semplicemente chiedere l'autorizzazione: +Ora, quando abbiamo creato l'elenco delle regole, possiamo semplicemente porre domande di autorizzazione: ```php -// L'ospite può visualizzare gli articoli? +// guest può visualizzare gli articoli? $acl->isAllowed('guest', 'article', 'view'); // true -// L'ospite può modificare un articolo? +// guest può modificare gli articoli? $acl->isAllowed('guest', 'article', 'edit'); // false -// L'ospite può votare nei sondaggi? +// guest può votare nei sondaggi? $acl->isAllowed('guest', 'poll', 'vote'); // true -// l'ospite può aggiungere commenti? +// guest può commentare? $acl->isAllowed('guest', 'comment', 'add'); // false ``` -Lo stesso vale per un utente registrato, ma può anche commentare: +Lo stesso vale per l'utente registrato, che però può anche commentare: ```php $acl->isAllowed('registered', 'article', 'view'); // true @@ -172,7 +172,7 @@ $acl->isAllowed('admin', 'poll', 'edit'); // false $acl->isAllowed('admin', 'comment', 'edit'); // true ``` -I permessi possono anche essere valutati dinamicamente e possiamo lasciare la decisione al nostro callback, al quale vengono passati tutti i parametri: +I permessi possono anche essere valutati dinamicamente e possiamo lasciare la decisione a un nostro callback, al quale vengono passati tutti i parametri: ```php $assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { @@ -182,7 +182,7 @@ $assertion = function (Permission $acl, string $role, string $resource, string $ $acl->allow('registered', 'comment', null, $assertion); ``` -Ma come risolvere una situazione in cui i nomi dei ruoli e delle risorse non sono sufficienti, cioè vorremmo definire che, per esempio, un ruolo `registered` può modificare una risorsa `article` solo se ne è l'autore? Useremo oggetti invece di stringhe, il ruolo sarà l'oggetto [api:Nette\Security\Role] e la risorsa [api:Nette\Security\Resource]. I metodi `getRoleId()` e `getResourceId()` restituiranno le stringhe originali: +Ma come risolvere, ad esempio, la situazione in cui non bastano solo i nomi dei ruoli e delle risorse, ma vorremmo definire che, ad esempio, il ruolo `registered` può modificare la risorsa `article` solo se ne è l'autore? Invece delle stringhe useremo oggetti, il ruolo sarà un oggetto [api:Nette\Security\Role] e la risorsa [api:Nette\Security\Resource]. I loro metodi `getRoleId()` rispettivamente `getResourceId()` restituiranno le stringhe originali: ```php class Registered implements Nette\Security\Role @@ -207,19 +207,19 @@ class Article implements Nette\Security\Resource } ``` -E ora creiamo una regola: +E ora creiamo la regola: ```php $assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { - $role = $acl->getQueriedRole(); // oggetto Registrato - $resource = $acl->getQueriedResource(); // oggetto Articolo + $role = $acl->getQueriedRole(); // oggetto Registered + $resource = $acl->getQueriedResource(); // oggetto Article return $role->id === $resource->authorId; }; $acl->allow('registered', 'article', 'edit', $assertion); ``` -L'ACL viene interrogata passando degli oggetti: +E la query sull'ACL viene eseguita passando gli oggetti: ```php $user = new Registered(/* ... */); @@ -227,7 +227,7 @@ $article = new Article(/* ... */); $acl->isAllowed($user, $article, 'edit'); ``` -Un ruolo può ereditare da uno o più ruoli. Ma cosa succede se a un antenato è consentita una certa azione e all'altro è negata? Allora entra in gioco il *peso del ruolo*: l'ultimo ruolo dell'array di ruoli da ereditare ha il peso maggiore, il primo il minore: +Un ruolo può ereditare da un altro ruolo o da più ruoli. Ma cosa succede se un antenato ha l'azione vietata e l'altro permessa? Quali saranno i diritti del discendente? Si determina in base al peso del ruolo - l'ultimo ruolo indicato nell'elenco degli antenati ha il peso maggiore, il primo ruolo indicato quello minore. È più chiaro con un esempio: ```php $acl = new Nette\Security\Permission; @@ -239,22 +239,22 @@ $acl->addResource('backend'); $acl->allow('admin', 'backend'); $acl->deny('guest', 'backend'); -// esempio A: il ruolo admin ha un peso inferiore al ruolo guest +// caso A: il ruolo admin ha meno peso del ruolo guest $acl->addRole('john', ['admin', 'guest']); $acl->isAllowed('john', 'backend'); // false -// esempio B: il ruolo admin ha un peso maggiore del ruolo guest +// caso B: il ruolo admin ha più peso di guest $acl->addRole('mary', ['guest', 'admin']); $acl->isAllowed('mary', 'backend'); // true ``` -I ruoli e le risorse possono anche essere rimossi (`removeRole()`, `removeResource()`), le regole possono anche essere annullate (`removeAllow()`, `removeDeny()`). L'array di tutti i ruoli genitori diretti restituisce `getRoleParents()`. Se due entità ereditano l'una dall'altra restituisce `roleInheritsFrom()` e `resourceInheritsFrom()`. +Ruoli e risorse possono anche essere rimossi (`removeRole()`, `removeResource()`), è possibile annullare anche le regole (`removeAllow()`, `removeDeny()`). L'array di tutti i ruoli genitore diretti viene restituito da `getRoleParents()`, se due entità ereditano l'una dall'altra viene restituito da `roleInheritsFrom()` e `resourceInheritsFrom()`. -Aggiungere come servizio .[#toc-add-as-a-service] -------------------------------------------------- +Aggiunta come servizi +--------------------- -Dobbiamo aggiungere la ACL da noi creata alla configurazione come servizio, in modo che possa essere utilizzata dall'oggetto `$user`, cioè in modo da poterla usare nel codice, ad esempio `$user->isAllowed('article', 'view')`. A questo scopo, scriveremo un factory per questo servizio: +L'ACL che abbiamo creato deve essere passato alla configurazione come servizio affinché l'oggetto `$user` inizi ad usarlo, ovvero affinché sia possibile usare nel codice ad es. `$user->isAllowed('article', 'view')`. A tal fine, scriveremo una factory per esso: ```php namespace App\Model; @@ -272,14 +272,14 @@ class AuthorizatorFactory } ``` -E lo aggiungeremo alla configurazione: +E la aggiungiamo alla configurazione: ```neon services: - App\Model\AuthorizatorFactory::create ``` -Nei presentatori, è possibile verificare le autorizzazioni nel metodo `startup()`, ad esempio: +Nei presenter potete quindi verificare i permessi ad esempio nel metodo `startup()`: ```php protected function startup() diff --git a/security/it/configuration.texy b/security/it/configuration.texy index beed001f85..d259a74ce7 100644 --- a/security/it/configuration.texy +++ b/security/it/configuration.texy @@ -1,71 +1,85 @@ -Configurazione del controllo degli accessi -****************************************** +Configurazione dei permessi di accesso +************************************** .[perex] -Panoramica delle opzioni di configurazione di Nette Security. +Panoramica delle opzioni di configurazione per Nette Security. -Se non si utilizza l'intero framework, ma solo questa libreria, leggere [come caricare la configurazione |bootstrap:]. +Se non utilizzate l'intero framework, ma solo questa libreria, leggete [come caricare la configurazione|bootstrap:]. -È possibile definire un elenco di utenti nella configurazione per creare un [semplice autenticatore |authentication] (`Nette\Security\SimpleAuthenticator`). Poiché le password sono leggibili nella configurazione, questa soluzione è solo a scopo di test. +Nella configurazione è possibile definire un elenco di utenti, creando così un [semplice autenticatore|authentication] (`Nette\Security\SimpleAuthenticator`). Poiché nella configurazione le password sono indicate in forma leggibile, questa soluzione è adatta solo per scopi di test. ```neon security: - # mostra il pannello utente nella barra Tracy? - debugger: ... # (bool) predefinito a true + # visualizzare il pannello utente nella Tracy Bar? + debugger: ... # (bool) il default è true users: - # name: password - johndoe: secret123 + # nome: password + francesco: passwordsegreta - # name, password, ruolo e altri dati disponibili nell'identità - janedoe: - password: secret123 + # nome, password, ruolo e altri dati disponibili nell'identità + bontà: + password: passwordsegreta roles: [admin] data: ... ``` -Si possono anche definire ruoli e risorse per creare una base per l'[autorizzatore |authorization] (`Nette\Security\Permission`): +Inoltre, è possibile definire ruoli e risorse e creare così la base per un [autorizzatore|authorization] (`Nette\Security\Permission`): ```neon security: roles: guest: - registered: [guest] # registered eredita da guest - admin: [registered] # e admin eredita da registered + registered: [guest] # registered eredita da guest + admin: [registered] # e da esso eredita admin resources: article: - comment: [article] # la risorsa eredita da article + comment: [article] # risorsa eredita da article poll: ``` -Memorizzazione degli utenti .[#toc-user-storage] ------------------------------------------------- +Storage +------- -È possibile configurare la modalità di memorizzazione delle informazioni sull'utente connesso: +È possibile configurare come conservare le informazioni sull'utente loggato: ```neon security: authentication: - # dopo quanto tempo di inattività l'utente sarà disconnesso - expiration: 30 minutes # (string) il valore predefinito non è impostato + # dopo quanto tempo di inattività l'utente verrà disconnesso + expiration: 30 minutes # (string) il default è non impostato - # dove memorizzare le informazioni sull'utente connesso - storage: session # (session|cookie) l'impostazione predefinita è session + # dove memorizzare le informazioni sull'utente loggato + storage: session # (session|cookie) il default è session ``` -Se si sceglie `cookie` come repository, si possono impostare anche le seguenti opzioni: +Se scegliete `cookie` come storage, potete impostare anche queste opzioni: ```neon security: authentication: # nome del cookie - cookieName: userId # (string) výchozí je userid + cookieName: userId # (string) il default è userid - # quali host sono autorizzati a ricevere il cookie - cookieDomain: 'example.com' # (string|dominio) + # domini che accettano il cookie + cookieDomain: 'example.com' # (string|domain) - # restrizioni quando si accede a una richiesta di origine incrociata - cookieSamesite: None # (Strict|Lax|None) predefinito a Lax + # restrizioni sull'accesso da un dominio diverso + cookieSamesite: None # (Strict|Lax|None) il default è Lax ``` + + +Servizi DI +---------- + +Questi servizi vengono aggiunti al container DI: + +| Nome | Tipo | Descrizione +|------------------------------------------------------------------------------------- +| `security.authenticator` | [api:Nette\Security\Authenticator] | [autenticatore|authentication] +| `security.authorizator` | [api:Nette\Security\Authorizator] | [autorizzatore|authorization] +| `security.passwords` | [api:Nette\Security\Passwords] | [hashing delle password|passwords] +| `security.user` | [api:Nette\Security\User] | utente corrente +| `security.userStorage` | [api:Nette\Security\UserStorage] | [#storage] diff --git a/security/it/passwords.texy b/security/it/passwords.texy index e073c1b6b7..8c81f173a6 100644 --- a/security/it/passwords.texy +++ b/security/it/passwords.texy @@ -2,11 +2,11 @@ Hashing delle password ********************** .[perex] -Per gestire la sicurezza dei nostri utenti, non memorizziamo mai le loro password in chiaro, bensì l'hash della password. L'hash non è un'operazione reversibile, la password non può essere recuperata. La password può però essere decifrata e per renderla il più difficile possibile dobbiamo usare un algoritmo sicuro. La classe [api:Nette\Security\Passwords] ci aiuterà in questo. +Per garantire la sicurezza dei nostri utenti, non memorizziamo le loro password in forma leggibile, ma memorizziamo solo l'impronta (il cosiddetto hash). Dall'impronta non è possibile ricostruire la forma originale della password. È importante utilizzare un algoritmo sicuro per creare l'impronta. In questo ci aiuta la classe [api:Nette\Security\Passwords]. -→ [Installazione e requisiti |@home#Installation] +→ [Installazione e requisiti |@home#Installazione] -Il framework aggiunge automaticamente un servizio `Nette\Security\Passwords` al contenitore DI con il nome `security.passwords`, che si ottiene passandoglielo tramite [dependency injection |dependency-injection:passing-dependencies]: +Il framework aggiunge automaticamente al container DI un servizio di tipo `Nette\Security\Passwords` con il nome `security.passwords`, al quale potete accedere facendovelo passare tramite [dependency injection |dependency-injection:passing-dependencies]. ```php use Nette\Security\Passwords; @@ -24,44 +24,44 @@ class Foo __construct($algo=PASSWORD_DEFAULT, array $options=[]): string .[method] ======================================================================== -Sceglie quale [algoritmo sicuro |https://www.php.net/manual/en/password.constants.php] utilizzare per l'hashing e come configurarlo. +Scegliamo quale [algoritmo sicuro|https://www.php.net/manual/en/password.constants.php] utilizzare per generare l'hash e ne configuriamo i parametri. -L'impostazione predefinita è `PASSWORD_DEFAULT`, quindi la scelta dell'algoritmo è lasciata a PHP. L'algoritmo può cambiare nelle nuove versioni di PHP, quando vengono supportati algoritmi di hashing più potenti. Pertanto, si deve essere consapevoli che la lunghezza dell'hash risultante può cambiare. Pertanto, è necessario memorizzare l'hash risultante in un modo che possa memorizzare un numero sufficiente di caratteri; 255 è la larghezza consigliata. +Come predefinito si usa `PASSWORD_DEFAULT`, ovvero la scelta dell'algoritmo viene lasciata a PHP. L'algoritmo può cambiare nelle versioni più recenti di PHP, se compaiono algoritmi di hashing più nuovi e più forti. Pertanto, dovreste essere consapevoli che la lunghezza dell'hash risultante può cambiare e dovreste memorizzarlo in un modo che possa contenere abbastanza caratteri, 255 è la larghezza consigliata. -In questo modo si utilizza l'algoritmo bcrypt e si modifica la velocità di hashing utilizzando il parametro cost rispetto al valore predefinito 10. Nell'anno 2020, con il costo 10, l'hashing di una password richiede circa 80 ms, il costo 11 richiede 160 ms, il costo 12 320 ms, la scala è logaritmica. Più lento è meglio è, il costo 10-12 è considerato abbastanza lento per la maggior parte dei casi d'uso: +Esempio di impostazione della velocità di hashing con l'algoritmo bcrypt modificando il parametro cost: (nel 2020 il valore predefinito è 10, l'hashing della password richiede circa 80 ms, per cost 11 circa 160 ms, per cost 12 circa 320 ms, più è lento, migliore è la protezione, mentre la velocità 10-12 è già considerata una protezione sufficiente) ```php -// faremo l'hash delle password con 2^12 (2^cost) iterazioni dell'algoritmo bcrypt +// hasheremo le password con 2^12 (2^cost) iterazioni dell'algoritmo bcrypt $passwords = new Passwords(PASSWORD_BCRYPT, ['cost' => 12]); ``` -Con l'iniezione di dipendenza: +Tramite dependency injection: ```neon services: security.passwords: Nette\Security\Passwords(::PASSWORD_BCRYPT, [cost: 12]) ``` -hash(string $passwords): string .[method] -========================================= +hash(string $password): string .[method] +======================================== Genera l'hash della password. ```php -$res = $passwords->hash($password); // Fa il hash della password +$res = $passwords->hash($password); // Hasha la password ``` -Il risultato `$res` è una stringa che, oltre all'hash stesso, contiene l'identificatore dell'algoritmo utilizzato, le sue impostazioni e il sale crittografico (dati casuali per garantire che venga generato un hash diverso per la stessa password). È quindi retrocompatibile: ad esempio, se si cambiano i parametri, è possibile verificare gli hash memorizzati con le impostazioni precedenti. L'intero risultato è memorizzato nel database, quindi non è necessario memorizzare separatamente il sale o le impostazioni. +Il risultato `$res` è una stringa che, oltre all'hash stesso, contiene anche l'identificatore dell'algoritmo utilizzato, le sue impostazioni e il salt crittografico (dati casuali che garantiscono che per la stessa password venga generato un hash diverso). È quindi retrocompatibile, ad esempio se cambiate i parametri, anche gli hash memorizzati utilizzando le impostazioni precedenti potranno essere verificati. L'intero risultato viene memorizzato nel database, quindi non è necessario memorizzare separatamente il salt o le impostazioni. verify(string $password, string $hash): bool .[method] ====================================================== -Scopre se la password data corrisponde all'hash dato. Ottiene `$hash` dal database in base al nome utente o all'indirizzo e-mail. +Verifica se la password data corrisponde all'impronta data. Ottenete `$hash` dal database in base al nome utente o all'indirizzo e-mail specificato. ```php if ($passwords->verify($password, $hash)) { - // Password corretta + // password corretta } ``` @@ -69,13 +69,13 @@ if ($passwords->verify($password, $hash)) { needsRehash(string $hash): bool .[method] ========================================= -Scopre se l'hash corrisponde alle opzioni fornite nel costruttore. +Verifica se l'hash corrisponde alle opzioni specificate nel costruttore. -Utilizzare questo metodo quando, ad esempio, si cambiano i parametri dell'hash. La verifica della password utilizzerà i parametri memorizzati con l'hash e se `needsRehash()` restituisce true, è necessario calcolare nuovamente l'hash, questa volta con i parametri aggiornati, e memorizzarlo nuovamente nel database. Questo assicura che gli hash delle password vengano automaticamente "aggiornati" quando gli utenti effettuano l'accesso. +È utile utilizzarlo nel momento in cui, ad esempio, si cambia la velocità di hashing. La verifica avviene in base alle impostazioni memorizzate e se `needsRehash()` restituisce `true`, è necessario creare nuovamente l'hash, questa volta con i nuovi parametri, e memorizzarlo nuovamente nel database. In questo modo gli hash memorizzati vengono automaticamente "aggiornati" al momento del login degli utenti. ```php if ($passwords->needsRehash($hash)) { $hash = $passwords->hash($password); - // memorizza $hash nel database + // memorizzare $hash nel database } ``` diff --git a/security/ja/@home.texy b/security/ja/@home.texy new file mode 100644 index 0000000000..d1e7d01cc3 --- /dev/null +++ b/security/ja/@home.texy @@ -0,0 +1,15 @@ +Nette Security +************** + +.[perex] +`nette/security`パッケージは、[ユーザーログイン |authentication]、[権限の検証 |authorization]、および[パスワードハッシュ化 |passwords]を担当します。 + + +インストール +------ + +ライブラリは[Composer|best-practices:composer]ツールを使用してダウンロードおよびインストールします: + +```shell +composer require nette/security +``` diff --git a/security/ja/@left-menu.texy b/security/ja/@left-menu.texy new file mode 100644 index 0000000000..695ba43277 --- /dev/null +++ b/security/ja/@left-menu.texy @@ -0,0 +1,7 @@ +Nette Security +************** +- [はじめに |@home] +- [認証 |authentication] +- [認可 |authorization] +- [パスワードハッシュ化 |passwords] +- [設定 |configuration] diff --git a/security/ja/@meta.texy b/security/ja/@meta.texy new file mode 100644 index 0000000000..d3c41dc3d7 --- /dev/null +++ b/security/ja/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette ドキュメンテーション}} diff --git a/security/ja/authentication.texy b/security/ja/authentication.texy new file mode 100644 index 0000000000..7a441215e8 --- /dev/null +++ b/security/ja/authentication.texy @@ -0,0 +1,291 @@ +ユーザーログイン(認証) +************ + +<div class=perex> + +ほとんどの Web アプリケーションは、ユーザーログインメカニズムとユーザー権限の検証なしでは成り立ちません。この章では、以下について説明します。 + +- ユーザーのログインとログアウト +- カスタム認証器 + +</div> + +→ [インストールと要件 |@home#インストール] + +例では、現在のユーザーを表す [api:Nette\Security\User] クラスのオブジェクトを使用します。これには、[dependency injection |dependency-injection:passing-dependencies] を使用して渡してもらうことでアクセスできます。Presenter では、単に `$user = $this->getUser()` を呼び出すだけです。 + + +認証 +=========== + +認証とは、**ユーザーログイン**、つまりユーザーが本当に名乗っている人物であるかどうかを確認するプロセスを意味します。通常、ユーザー名とパスワードで証明されます。検証は、いわゆる [#認証器] によって行われます。ログインに失敗した場合、`Nette\Security\AuthenticationException` がスローされます。 + +```php +try { + $user->login($username, $password); +} catch (Nette\Security\AuthenticationException $e) { + $this->flashMessage('ユーザー名またはパスワードが正しくありません'); +} +``` + +このようにしてユーザーをログアウトさせます。 + +```php +$user->logout(); +``` + +そして、ログインしているかどうかを確認します。 + +```php +echo $user->isLoggedIn() ? 'はい' : 'いいえ'; +``` + +非常に簡単ですね?そして、すべてのセキュリティ側面は Nette が処理します。 + +Presenter では、`startup()` メソッドでログインを確認し、ログインしていないユーザーをログインページにリダイレクトできます。 + +```php +protected function startup() +{ + parent::startup(); + if (!$this->getUser()->isLoggedIn()) { + $this->redirect('Sign:in'); + } +} +``` + + +有効期限 +==== + +ユーザーのログインは、通常はセッションである [ストレージの有効期限 |#ログインユーザーのストレージ] とともに期限切れになります([セッションの有効期限 |http:configuration#セッション] の設定を参照)。 ただし、ユーザーがログアウトされるまでのより短い時間間隔を設定することも可能です。これには `setExpiration()` メソッドを使用し、`login()` の前に呼び出します。パラメータとして相対時間の文字列を指定します。 + +```php +// ログインは 30 分間の非アクティブ後に期限切れになります +$user->setExpiration('30 minutes'); + +// 設定された有効期限のキャンセル +$user->setExpiration(null); +``` + +ユーザーが時間間隔の期限切れのためにログアウトされたかどうかは、メソッド `$user->getLogoutReason()` が示します。これは定数 `Nette\Security\UserStorage::LogoutInactivity`(時間制限切れ)または `UserStorage::LogoutManual`(`logout()` メソッドによるログアウト)のいずれかを返します。 + + +認証器 +=== + +これは、ログイン資格情報、つまり通常は名前とパスワードを検証するオブジェクトです。[設定|configuration] で定義できる簡単な形式は、[api:Nette\Security\SimpleAuthenticator] クラスです。 + +```neon +security: + users: + # 名前: パスワード + frantisek: tajneheslo + katka: jestetajnejsiheslo +``` + +このソリューションは、テスト目的には適しています。データベーステーブルに対してログイン資格情報を検証する認証器を作成する方法を示します。 + +認証器は、メソッド `authenticate()` を持つ [api:Nette\Security\Authenticator] インターフェースを実装するオブジェクトです。そのタスクは、いわゆる [#アイデンティティ] を返すか、例外 `Nette\Security\AuthenticationException` をスローすることです。発生した状況をより細かく区別するために、エラーコードを指定することも可能です:`Authenticator::IdentityNotFound` および `Authenticator::InvalidCredential`。 + +```php +use Nette; +use Nette\Security\SimpleIdentity; + +class MyAuthenticator implements Nette\Security\Authenticator +{ + public function __construct( + private Nette\Database\Explorer $database, + private Nette\Security\Passwords $passwords, + ) { + } + + public function authenticate(string $username, string $password): SimpleIdentity + { + $row = $this->database->table('users') + ->where('username', $username) + ->fetch(); + + if (!$row) { + throw new Nette\Security\AuthenticationException('ユーザーが見つかりません。'); + } + + if (!$this->passwords->verify($password, $row->password)) { + throw new Nette\Security\AuthenticationException('パスワードが無効です。'); + } + + return new SimpleIdentity( + $row->id, + $row->role, // または複数のロールの配列 + ['name' => $row->username], + ); + } +} +``` + +MyAuthenticator クラスは、[Nette Database Explorer|database:explorer] を介してデータベースと通信し、テーブル `users` を操作します。このテーブルには、カラム `username` にユーザーのログイン名、カラム `password` に [パスワードハッシュ|passwords] が含まれています。名前とパスワードを確認した後、ユーザー ID、そのロール(テーブルのカラム `role`、これについては [後で |authorization#ロール] 詳しく説明します)、およびその他のデータ(この場合はユーザー名)を含むアイデンティティを返します。 + +認証器を DI コンテナの [サービスとして|dependency-injection:services] 設定に追加します。 + +```neon +services: + - MyAuthenticator +``` + + +イベント $onLoggedIn, $onLoggedOut +------------------------------ + +`Nette\Security\User` オブジェクトには [イベント |nette:glossary#イベント] `$onLoggedIn` と `$onLoggedOut` があります。したがって、正常なログイン後またはユーザーのログアウト後にそれぞれ呼び出されるコールバックを追加できます。 + + +```php +$user->onLoggedIn[] = function () { + // ユーザーはちょうどログインしました +}; +``` + + +アイデンティティ +======== + +アイデンティティは、認証器によって返され、その後セッションに保存され、`$user->getIdentity()` を使用して取得されるユーザーに関する情報のセットを表します。したがって、認証器で渡したように、ID、ロール、およびその他のユーザーデータを取得できます。 + +```php +$user->getIdentity()->getId(); +// ショートカット $user->getId() も機能します + +$user->getIdentity()->getRoles(); + +// ユーザーデータはプロパティとして利用可能です +// MyAuthenticator で渡した名前 +$user->getIdentity()->name; +``` + +重要なことは、`$user->logout()` を使用してログアウトしても、**アイデンティティは削除されず**、引き続き利用可能であることです。したがって、ユーザーがアイデンティティを持っていても、ログインしている必要はありません。アイデンティティを明示的に削除したい場合は、`logout(true)` を呼び出してユーザーをログアウトさせます。 + +これにより、どのユーザーがコンピューターの前にいるかを引き続き想定し、たとえば e ショップでパーソナライズされたオファーを表示できますが、ログイン後にのみ個人データを表示できます。 + +アイデンティティは [api:Nette\Security\IIdentity] インターフェースを実装するオブジェクトであり、デフォルトの実装は [api:Nette\Security\SimpleIdentity] です。そして、前述のように、セッションで維持されるため、たとえばログインしているユーザーのいずれかのロールを変更した場合、古いデータはそのユーザーが再度ログインするまでアイデンティティに残ります。 + + +ログインユーザーのストレージ +============== + +ユーザーに関する 2 つの基本情報、つまりログインしているかどうかとその [#アイデンティティ] は、通常セッションで転送されます。これは変更できます。これらの情報の保存を担当するのは、`Nette\Security\UserStorage` インターフェースを実装するオブジェクトです。2 つの標準的な実装が利用可能で、1 つ目はセッションでデータを転送し、2 つ目は cookie で転送します。これらはクラス `Nette\Bridges\SecurityHttp\SessionStorage` と `CookieStorage` です。[security › authentication |configuration#ストレージ] 設定でストレージを選択し、非常に便利に設定できます。 + +さらに、アイデンティティの保存(*sleep*)と復元(*wakeup*)がどのように行われるかを正確に制御できます。認証器が `Nette\Security\IdentityHandler` インターフェースを実装するだけで十分です。これには 2 つのメソッドがあります:`sleepIdentity()` はアイデンティティをストレージに書き込む前に呼び出され、`wakeupIdentity()` は読み取った後に呼び出されます。メソッドはアイデンティティの内容を変更したり、返す新しいオブジェクトに置き換えたりすることができます。`wakeupIdentity()` メソッドは `null` を返すことさえでき、それによってユーザーをログアウトさせます。 + +例として、セッションから読み込んだ直後にアイデンティティのロールを更新する方法というよくある質問の解決策を示します。`wakeupIdentity()` メソッドで、たとえばデータベースから現在のロールをアイデンティティに渡します。 + +```php +final class Authenticator implements + Nette\Security\Authenticator, Nette\Security\IdentityHandler +{ + public function sleepIdentity(IIdentity $identity): IIdentity + { + // ここでログイン後にストレージに書き込む前にアイデンティティを変更できますが、 + // 今は必要ありません + return $identity; + } + + public function wakeupIdentity(IIdentity $identity): ?IIdentity + { + // アイデンティティのロールの更新 + $userId = $identity->getId(); + $identity->setRoles($this->facade->getUserRoles($userId)); + return $identity; + } +``` + +そして今、cookie ベースのストレージに戻ります。これにより、ユーザーがログインでき、セッションを必要としない Web サイトを作成できます。つまり、ディスクに書き込む必要はありません。結局のところ、フォーラムを含め、現在読んでいる Web サイトもこのように機能します。この場合、`IdentityHandler` の実装は必須です。cookie には、ログインしたユーザーを表すランダムなトークンのみを保存します。 + +したがって、まず設定で `security › authentication › storage: cookie` を使用して目的のストレージを設定します。 + +データベースに `authtoken` カラムを作成します。このカラムには、各ユーザーが十分な長さ(少なくとも 13 文字)の [完全にランダムで、一意で、推測不可能な|utils:random] 文字列を持ちます。`CookieStorage` ストレージは cookie で `$identity->getId()` の値のみを転送するため、`sleepIdentity()` で元のアイデンティティを ID に `authtoken` を持つプレースホルダーアイデンティティに置き換え、逆に `wakeupIdentity()` メソッドで authtoken に基づいてデータベースから完全なアイデンティティを読み取ります。 + +```php +final class Authenticator implements + Nette\Security\Authenticator, Nette\Security\IdentityHandler +{ + public function authenticate(string $username, string $password): SimpleIdentity + { + $row = $this->db->fetch('SELECT * FROM user WHERE username = ?', $username); + // パスワードを確認します + ... + // データベースからのすべてのデータを含むアイデンティティを返します + return new SimpleIdentity($row->id, null, (array) $row); + } + + public function sleepIdentity(IIdentity $identity): SimpleIdentity + { + // ID に authtoken を持つプレースホルダーアイデンティティを返します + return new SimpleIdentity($identity->authtoken); + } + + public function wakeupIdentity(IIdentity $identity): ?SimpleIdentity + { + // authenticate() と同様に、プレースホルダーアイデンティティを完全なアイデンティティに置き換えます + $row = $this->db->fetch('SELECT * FROM user WHERE authtoken = ?', $identity->getId()); + return $row + ? new SimpleIdentity($row->id, null, (array) $row) + : null; + } +} +``` + + +複数の独立したログイン +=========== + +1 つの Web サイトと 1 つのセッション内で、複数の独立したログインユーザーを同時に持つことが可能です。たとえば、Web サイトの管理部分と公開部分で別々の認証を行いたい場合は、それぞれに独自の名前を設定するだけで十分です。 + +```php +$user->getStorage()->setNamespace('backend'); +``` + +特定のセクションに属するすべての場所で常に名前空間を設定することを覚えておくことが重要です。Presenter を使用している場合は、特定のセクションの共通の祖先(通常は BasePresenter)で名前空間を設定します。これは、[checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()] メソッドを拡張することによって行います。 + +```php +public function checkRequirements($element): void +{ + $this->getUser()->getStorage()->setNamespace('backend'); + parent::checkRequirements($element); +} +``` + + +複数の認証器 +------ + +独立したログインを持つセクションにアプリケーションを分割するには、通常、異なる認証器も必要になります。ただし、サービス設定で Authenticator を実装する 2 つのクラスを登録すると、Nette はどちらを `Nette\Security\User` オブジェクトに自動的に割り当てるかわからなくなり、エラーが表示されます。したがって、認証器の [autowiring |dependency-injection:autowiring] を制限して、誰かが特定のクラス、たとえば FrontAuthenticator を要求した場合にのみ機能するようにする必要があります。これは、`autowired: self` オプションを選択することで実現できます。 + +```neon +services: + - + create: FrontAuthenticator + autowired: self +``` + +認証器は、`Nette\Security\User` オブジェクトに直接設定する必要があります。これは、[login() |api:Nette\Security\User::login()] メソッドを呼び出す前に行います。通常、ログインフォームの処理コードで行います。 + +```php +class SignPresenter extends Nette\Application\UI\Presenter +{ + public function __construct( + private FrontAuthenticator $authenticator, + ) { + } +} +``` + +User オブジェクトの認証器は、[login() |api:Nette\Security\User::login()] メソッドを呼び出す前に設定します。したがって、通常はログインさせるフォームのコードで設定します。 + +```php +$form->onSuccess[] = function (Form $form, \stdClass $data) { + $user = $this->getUser(); + $user->setAuthenticator($this->authenticator); + $user->login($data->username, $data->password); + // ... +}; +``` diff --git a/security/ja/authorization.texy b/security/ja/authorization.texy new file mode 100644 index 0000000000..a07533423b --- /dev/null +++ b/security/ja/authorization.texy @@ -0,0 +1,292 @@ +権限の検証(認可) +********* + +.[perex] +認可は、ユーザーが特定のリソースへのアクセスや特定のアクションの実行など、十分な権限を持っているかどうかを確認します。認可は、事前の正常な認証、つまりユーザーがログインしていることを前提としています。 + +→ [インストールと要件 |@home#インストール] + +例では、現在のユーザーを表す [api:Nette\Security\User] クラスのオブジェクトを使用します。これには、[dependency injection |dependency-injection:passing-dependencies] を使用して渡してもらうことでアクセスできます。Presenter では、単に `$user = $this->getUser()` を呼び出すだけです。 + +ユーザー権限が区別されない管理機能を持つ非常に単純な Web サイトの場合、すでに知られている `isLoggedIn()` メソッドを認可基準として使用できます。言い換えれば、ユーザーがログインするとすぐに、すべての権限を持ち、逆もまた同様です。 + +```php +if ($user->isLoggedIn()) { // ユーザーはログインしていますか? + deleteItem(); // その場合、操作を実行する権限があります +} +``` + + +ロール +--- + +ロールの目的は、より正確な権限管理を提供し、ユーザー名から独立した状態を維持することです。各ユーザーには、ログイン時に 1 つ以上のロールが割り当てられ、そのロールで行動します。ロールは、`admin`、`member`、`guest` などの単純な文字列にすることができます。これらは `SimpleIdentity` コンストラクタの 2 番目のパラメータとして、文字列または文字列の配列(ロール)として指定されます。 + +認可基準として、今回は `isInRole()` メソッドを使用します。これは、ユーザーが特定のロールで行動しているかどうかを示します。 + +```php +if ($user->isInRole('admin')) { // ユーザーは admin ロールですか? + deleteItem(); // その場合、操作を実行する権限があります +} +``` + +すでに知っているように、ユーザーがログアウトした後、そのアイデンティティを削除する必要はありません。したがって、`getIdentity()` メソッドは、付与されたすべてのロールを含む `SimpleIdentity` オブジェクトを引き続き返します。Nette Framework は「より少ないコード、より多くのセキュリティ」の原則に従います。これは、記述量が少ないほど、より安全なコードにつながることを意味します。したがって、ロールを確認するときに、ユーザーがログインしているかどうかを確認する必要はありません。`isInRole()` メソッドは **有効なロール** で動作します。つまり、ユーザーがログインしている場合、アイデンティティにリストされているロールに基づいており、ログインしていない場合は、自動的に特別なロール `guest` を持ちます。 + + +認可者 +--- + +ロールに加えて、リソースと操作の概念も導入します。 + +- **ロール** はユーザーのプロパティです - 例:モデレーター、編集者、訪問者、登録ユーザー、管理者... +- **リソース** (*resource*) は、Web サイトの論理的な要素です - 記事、ページ、ユーザー、メニュー項目、投票、Presenter、... +- **操作** (*operation*) は、ユーザーがリソースに対して実行できる、またはできない特定の活動です - 例:削除、編集、作成、投票、... + +認可者は、特定の *ロール* が特定のリソースに対して特定の *操作* を実行する権限を持っているかどうかを決定するオブジェクトです。これは、単一のメソッド `isAllowed()` を持つ [api:Nette\Security\Authorizator] インターフェースを実装するオブジェクトです。 + +```php +class MyAuthorizator implements Nette\Security\Authorizator +{ + public function isAllowed($role, $resource, $operation): bool + { + if ($role === 'admin') { + return true; + } + if ($role === 'user' && $resource === 'article') { + return true; + } + + // ... + + return false; + } +} +``` + +認可者を DI コンテナの [サービスとして|dependency-injection:services] 設定に追加します。 + +```neon +services: + - MyAuthorizator +``` + +そして、使用例が続きます。注意してください、今回は認可者ではなく `Nette\Security\User::isAllowed()` メソッドを呼び出しているため、最初のパラメータ `$role` はありません。このメソッドは、ユーザーのすべてのロールに対して `MyAuthorizator::isAllowed()` を順番に呼び出し、少なくとも 1 つのロールに権限がある場合は true を返します。 + +```php +if ($user->isAllowed('file')) { // ユーザーはリソース 'file' に対して何でもできますか? + useFile(); +} + +if ($user->isAllowed('file', 'delete')) { // ユーザーはリソース 'file' に対して 'delete' を実行できますか? + deleteFile(); +} +``` + +両方のパラメータはオプションであり、デフォルト値 `null` は *何でも* を意味します。 + + +Permission ACL +-------------- + +Nette には、権限とアクセスを管理するための軽量で柔軟な ACL(Access Control List)レイヤーをプログラマに提供する、組み込みの認可者実装、クラス [api:Nette\Security\Permission] が付属しています。これを使用するには、ロール、リソース、および個々の権限を定義します。ロールとリソースは階層を作成できます。説明のために、Web アプリケーションの例を示します。 + +- `guest`: ログインしていない訪問者。Web サイトの公開部分を読み取り、閲覧できます。つまり、記事、コメントを読み、投票で投票できます。 +- `registered`: ログインしている登録ユーザー。さらにコメントすることもできます。 +- `admin`: 記事、コメント、投票を管理できます。 + +したがって、特定のロール(`guest`、`registered`、`admin`)を定義し、ユーザーが特定のロールでアクセスしたり、特定の操作(`view`、`vote`、`add`、`edit`)を実行したりできるリソース(`article`、`comment`、`poll`)について言及しました。 + +Permission クラスのインスタンスを作成し、**ロール** を定義します。ロールの継承を使用できます。これにより、たとえば管理者ロール(`admin`)を持つユーザーは、通常の Web サイト訪問者ができること(そしてもちろんそれ以上)も実行できます。 + +```php +$acl = new Nette\Security\Permission; + +$acl->addRole('guest'); +$acl->addRole('registered', 'guest'); // 'registered' は 'guest' から継承します +$acl->addRole('admin', 'registered'); // そして 'admin' はそれから継承します +``` + +次に、ユーザーがアクセスできる **リソース** のリストも定義します。 + +```php +$acl->addResource('article'); +$acl->addResource('comment'); +$acl->addResource('poll'); +``` + +リソースも継承を使用できます。たとえば、`$acl->addResource('perex', 'article')` を指定できます。 + +そして今、最も重要なこと。誰が何に対して何ができるかを決定するルールを定義します。 + +```php +// 最初は誰も何もできません + +// guest が記事、コメント、投票を閲覧できるようにします +$acl->allow('guest', ['article', 'comment', 'poll'], 'view'); +// そして投票では、さらに投票もできます +$acl->allow('guest', 'poll', 'vote'); + +// 登録ユーザーは guest から権限を継承し、コメントする権限を追加します +$acl->allow('registered', 'comment', 'add'); + +// 管理者は何でも閲覧および編集できます +$acl->allow('admin', $acl::All, ['view', 'edit', 'add']); +``` + +誰かが特定のリソースへのアクセスを **拒否** したい場合はどうすればよいでしょうか? + +```php +// 管理者は投票を編集できません。それは非民主的です +$acl->deny('admin', 'poll', 'edit'); +``` + +ルールのリストを作成したので、認可クエリを簡単に実行できます。 + +```php +// guest は記事を閲覧できますか? +$acl->isAllowed('guest', 'article', 'view'); // true + +// guest は記事を編集できますか? +$acl->isAllowed('guest', 'article', 'edit'); // false + +// guest は投票で投票できますか? +$acl->isAllowed('guest', 'poll', 'vote'); // true + +// guest はコメントできますか? +$acl->isAllowed('guest', 'comment', 'add'); // false +``` + +登録ユーザーについても同様ですが、コメントすることもできます。 + +```php +$acl->isAllowed('registered', 'article', 'view'); // true +$acl->isAllowed('registered', 'comment', 'add'); // true +$acl->isAllowed('registered', 'comment', 'edit'); // false +``` + +管理者は、投票を除いてすべてを編集できます。 + +```php +$acl->isAllowed('admin', 'poll', 'vote'); // true +$acl->isAllowed('admin', 'poll', 'edit'); // false +$acl->isAllowed('admin', 'comment', 'edit'); // true +``` + +権限は動的に評価することもでき、決定を独自のコールバックに任せることができます。このコールバックにはすべてのパラメータが渡されます。 + +```php +$assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { + return /* ... */; +}; + +$acl->allow('registered', 'comment', null, $assertion); +``` + +しかし、たとえばロールとリソースの名前だけでは不十分で、たとえばロール `registered` は、自分が作成者である場合にのみリソース `article` を編集できると定義したい場合はどうすればよいでしょうか?文字列の代わりにオブジェクトを使用します。ロールは [api:Nette\Security\Role] オブジェクト、リソースは [api:Nette\Security\Resource] オブジェクトになります。それらのメソッド `getRoleId()` と `getResourceId()` は、元の文字列を返します。 + +```php +class Registered implements Nette\Security\Role +{ + public $id; + + public function getRoleId(): string + { + return 'registered'; + } +} + + +class Article implements Nette\Security\Resource +{ + public $authorId; + + public function getResourceId(): string + { + return 'article'; + } +} +``` + +そして今、ルールを作成します。 + +```php +$assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { + $role = $acl->getQueriedRole(); // Registered オブジェクト + $resource = $acl->getQueriedResource(); // Article オブジェクト + return $role->id === $resource->authorId; +}; + +$acl->allow('registered', 'article', 'edit', $assertion); +``` + +そして、ACL へのクエリはオブジェクトを渡すことによって実行されます。 + +```php +$user = new Registered(/* ... */); +$article = new Article(/* ... */); +$acl->isAllowed($user, $article, 'edit'); +``` + +ロールは、別のロールまたは複数のロールから継承できます。しかし、一方の祖先がアクションを禁止され、もう一方の祖先が許可されている場合はどうなるでしょうか?子孫の権限はどうなりますか?これはロールの重みによって決定されます - 祖先のリストで最後にリストされたロールが最も重みが高く、最初にリストされたロールが最も重みが低くなります。例からより明確になります。 + +```php +$acl = new Nette\Security\Permission; +$acl->addRole('admin'); +$acl->addRole('guest'); + +$acl->addResource('backend'); + +$acl->allow('admin', 'backend'); +$acl->deny('guest', 'backend'); + +// ケース A: admin ロールは guest ロールよりも重みが低い +$acl->addRole('john', ['admin', 'guest']); +$acl->isAllowed('john', 'backend'); // false + +// ケース B: admin ロールは guest よりも重みが大きい +$acl->addRole('mary', ['guest', 'admin']); +$acl->isAllowed('mary', 'backend'); // true +``` + +ロールとリソースは削除することもできます(`removeRole()`、`removeResource()`)。ルールを元に戻すこともできます(`removeAllow()`、`removeDeny()`)。すべての直接の親ロールの配列は `getRoleParents()` によって返され、2 つのエンティティが互いに継承するかどうかは `roleInheritsFrom()` と `resourceInheritsFrom()` によって返されます。 + + +サービスとしての追加 +---------- + +作成した ACL をサービスとして設定に渡す必要があります。これにより、`$user` オブジェクトがそれを使用し始め、たとえばコードで `$user->isAllowed('article', 'view')` を使用できるようになります。この目的のために、ファクトリを作成します。 + +```php +namespace App\Model; + +class AuthorizatorFactory +{ + public static function create(): Nette\Security\Permission + { + $acl = new Nette\Security\Permission; + $acl->addRole(/* ... */); + $acl->addResource(/* ... */); + $acl->allow(/* ... */); + return $acl; + } +} +``` + +そして、それを設定に追加します。 + +```neon +services: + - App\Model\AuthorizatorFactory::create +``` + +Presenter では、たとえば `startup()` メソッドで権限を確認できます。 + +```php +protected function startup() +{ + parent::startup(); + if (!$this->getUser()->isAllowed('backend')) { + $this->error('Forbidden', 403); + } +} +``` diff --git a/security/ja/configuration.texy b/security/ja/configuration.texy new file mode 100644 index 0000000000..c2af75f438 --- /dev/null +++ b/security/ja/configuration.texy @@ -0,0 +1,85 @@ +アクセス権限の設定 +********* + +.[perex] +Nette Security の設定オプションの概要。 + +フレームワーク全体ではなく、このライブラリのみを使用する場合は、[設定の読み込み方法|bootstrap:] をお読みください。 + +設定でユーザーのリストを定義し、それによって [単純な認証器|authentication] (`Nette\Security\SimpleAuthenticator`) を作成できます。設定ではパスワードが読み取り可能な形式で指定されるため、このソリューションはテスト目的のみに適しています。 + +```neon +security: + # Tracy Bar にユーザーパネルを表示しますか? + debugger: ... # (bool) デフォルトは true + + users: + # 名前: パスワード + frantisek: tajneheslo + + # 名前、パスワード、ロール、およびアイデンティティで利用可能なその他のデータ + dobrota: + password: tajneheslo + roles: [admin] + data: ... +``` + +さらに、ロールとリソースを定義し、それによって [認可者|authorization] (`Nette\Security\Permission`) の基礎を作成できます。 + +```neon +security: + roles: + guest: + registered: [guest] # registered は guest から継承します + admin: [registered] # そして admin はそれから継承します + + resources: + article: + comment: [article] # リソースは article から継承します + poll: +``` + + +ストレージ +----- + +ログインしたユーザーに関する情報をどのように保存するかを設定できます。 + +```neon +security: + authentication: + # 非アクティブ状態がどのくらいの期間続いた後、ユーザーはログアウトされますか + expiration: 30 minutes # (string) デフォルトは設定されていません + + # ログインしたユーザーに関する情報をどこに保存しますか + storage: session # (session|cookie) デフォルトは session +``` + +ストレージとして `cookie` を選択した場合、さらに次のオプションを設定できます。 + +```neon +security: + authentication: + # cookie 名 + cookieName: userId # (string) デフォルトは userid + + # cookie を受け入れるドメイン + cookieDomain: 'example.com' # (string|domain) + + # 別のドメインからのアクセス時の制限 + cookieSamesite: None # (Strict|Lax|None) デフォルトは Lax +``` + + +DI サービス +------- + +これらのサービスは DI コンテナに追加されます。 + +| 名前 | 型 | 説明 +|---------------------------------------------------------- +| `security.authenticator` | [api:Nette\Security\Authenticator] | [認証器|authentication] +| `security.authorizator` | [api:Nette\Security\Authorizator] | [認可者|authorization] +| `security.passwords` | [api:Nette\Security\Passwords] | [パスワードハッシュ化|passwords] +| `security.user` | [api:Nette\Security\User] | 現在のユーザー +| `security.userStorage` | [api:Nette\Security\UserStorage] | [#ストレージ] diff --git a/security/ja/passwords.texy b/security/ja/passwords.texy new file mode 100644 index 0000000000..0208c3b398 --- /dev/null +++ b/security/ja/passwords.texy @@ -0,0 +1,81 @@ +パスワードハッシュ化 +********** + +.[perex] +ユーザーのセキュリティを確保するために、パスワードを読み取り可能な形式で保存するのではなく、そのハッシュ(いわゆるハッシュ)のみを保存します。ハッシュから元のパスワードを逆算することはできません。ハッシュを作成するために安全なアルゴリズムを使用することが重要です。これには [api:Nette\Security\Passwords] クラスが役立ちます。 + +→ [インストールと要件 |@home#インストール] + +フレームワークは、`Nette\Security\Passwords` 型のサービスを `security.passwords` という名前で DI コンテナに自動的に追加します。これには、[dependency injection |dependency-injection:passing-dependencies] を使用して渡してもらうことでアクセスできます。 + +```php +use Nette\Security\Passwords; + +class Foo +{ + public function __construct( + private Passwords $passwords, + ) { + } +} +``` + + +__construct($algo=PASSWORD_DEFAULT, array $options=[]): string .[method] +======================================================================== + +ハッシュ生成に使用する [安全なアルゴリズム|https://www.php.net/manual/en/password.constants.php] を選択し、そのパラメータを設定します。 + +デフォルトでは `PASSWORD_DEFAULT` が使用されます。つまり、アルゴリズムの選択は PHP に任されます。より新しく、より強力なハッシュアルゴリズムが登場した場合、新しいバージョンの PHP でアルゴリズムが変更される可能性があります。したがって、結果のハッシュの長さが変わる可能性があることに注意し、十分な文字数(255 が推奨される幅)を格納できる方法で保存する必要があります。 + +cost パラメータを変更して bcrypt アルゴリズムのハッシュ化速度を設定する例:(2020 年のデフォルトは 10、パスワードのハッシュ化には約 80ms かかります。cost 11 では約 160ms、cost 12 では約 320ms です。遅いほど保護が向上し、速度 10〜12 はすでに十分な保護と見なされます) + +```php +// bcrypt アルゴリズムの 2^12(2^cost)回の反復でパスワードをハッシュ化します +$passwords = new Passwords(PASSWORD_BCRYPT, ['cost' => 12]); +``` + +dependency injection を使用: +```neon +services: + security.passwords: Nette\Security\Passwords(::PASSWORD_BCRYPT, [cost: 12]) +``` + + +hash(string $password): string .[method] +======================================== + +パスワードのハッシュを生成します。 + +```php +$hash = $passwords->hash($password); // パスワードをハッシュ化します +``` + +結果 `$hash` は、ハッシュ自体に加えて、使用されたアルゴリズムの識別子、その設定、および暗号化ソルト(同じパスワードに対して異なるハッシュが生成されることを保証するランダムデータ)を含む文字列です。したがって、たとえばパラメータを変更した場合でも、以前の設定を使用して保存されたハッシュも検証できるため、下位互換性があります。この結果全体がデータベースに保存されるため、ソルトや設定を別途保存する必要はありません。 + + +verify(string $password, string $hash): bool .[method] +====================================================== + +指定されたパスワードが指定されたハッシュに対応するかどうかを確認します。`$hash` は、指定されたユーザー名またはメールアドレスに基づいてデータベースから取得します。 + +```php +if ($passwords->verify($password, $hash)) { + // 正しいパスワード +} +``` + + +needsRehash(string $hash): bool .[method] +========================================= + +ハッシュがコンストラクタで指定されたオプションに対応するかどうかを確認します。 + +これは、たとえばハッシュ化速度を変更する場合に役立ちます。検証は保存された設定に基づいて行われ、`needsRehash()` が `true` を返した場合、今回は新しいパラメータでハッシュを再作成し、データベースに再度保存する必要があります。このようにして、ユーザーがログインするときに保存されたハッシュが自動的に「アップグレード」されます。 + +```php +if ($passwords->needsRehash($hash)) { + $hash = $passwords->hash($password); + // $hash をデータベースに保存します +} +``` diff --git a/security/pl/@home.texy b/security/pl/@home.texy index c57388b42f..478b511afa 100644 --- a/security/pl/@home.texy +++ b/security/pl/@home.texy @@ -1,14 +1,14 @@ -Bezpieczeństwo +Nette Security ************** .[perex] Pakiet `nette/security` odpowiada za [logowanie użytkowników |authentication], [weryfikację uprawnień |authorization] i [haszowanie haseł |passwords]. -Instalacja .[#toc-installation] -------------------------------- +Instalacja +---------- -Pobierz i zainstaluj bibliotekę za pomocą [Composera |best-practices:composer]: +Bibliotekę pobierzesz i zainstalujesz za pomocą narzędzia [Composer|best-practices:composer]: ```shell composer require nette/security diff --git a/security/pl/@left-menu.texy b/security/pl/@left-menu.texy index d289ef468d..4cd92e714e 100644 --- a/security/pl/@left-menu.texy +++ b/security/pl/@left-menu.texy @@ -2,6 +2,6 @@ Nette Security ************** - [Wprowadzenie |@home] - [Uwierzytelnianie |authentication] -- [Upoważnienie |authorization] -- [Hashing hasła |passwords] +- [Autoryzacja |authorization] +- [Haszowanie haseł |passwords] - [Konfiguracja |configuration] diff --git a/security/pl/@meta.texy b/security/pl/@meta.texy new file mode 100644 index 0000000000..61ac92d1af --- /dev/null +++ b/security/pl/@meta.texy @@ -0,0 +1 @@ +{{sitename: Dokumentacja Nette}} diff --git a/security/pl/authentication.texy b/security/pl/authentication.texy index 1bab2efac0..fd9c22dff0 100644 --- a/security/pl/authentication.texy +++ b/security/pl/authentication.texy @@ -1,48 +1,48 @@ -Logowanie użytkownika (uwierzytelnianie) -**************************************** +Logowanie użytkowników (Autentykacja) +************************************* <div class=perex> -Prawie żadna aplikacja internetowa nie może obejść się bez mechanizmu logowania i uwierzytelniania użytkowników. W tym rozdziale omówimy: +Prawie żadna aplikacja internetowa nie obejdzie się bez mechanizmu logowania użytkowników i weryfikacji uprawnień użytkowników. W tym rozdziale omówimy: -- logowanie użytkowników w i z -- niestandardowe uwierzytelniacze +- logowanie i wylogowywanie użytkowników +- własnych autentykatorów </div> -→ [Instalacja i wymagania |@home#Installation] +→ [Instalacja i wymagania |@home#Instalacja] -W przykładach użyjemy obiektu klasy [api:Nette\Security\User], który reprezentuje aktualnego użytkownika i do którego można uzyskać dostęp, zlecając jego przekazanie przez [dependency injection |dependency-injection:passing-dependencies]. W prezenterze wystarczy wywołać `$user = $this->getUser()`. +W przykładach będziemy używać obiektu klasy [api:Nette\Security\User], który reprezentuje aktualnego użytkownika i do którego dostaniesz się, prosząc o jego przekazanie za pomocą [wstrzykiwania zależności |dependency-injection:passing-dependencies]. W presenterach wystarczy tylko wywołać `$user = $this->getUser()`. -Uwierzytelnianie .[#toc-authentication] -======================================= +Autentykacja +============ -Uwierzytelnianie oznacza **logowanie użytkownika**, czyli proces weryfikacji, czy użytkownik jest tym, za kogo się podaje. Zwykle objawia się to nazwą użytkownika i hasłem. Uwierzytelnianie odbywa się za pomocą tzw. [authenticatora |#Authenticator]. Jeśli logowanie nie powiedzie się, `Nette\Security\AuthenticationException` jest wyrzucany. +Autentykacją rozumie się **logowanie użytkowników**, czyli proces, podczas którego weryfikuje się, czy użytkownik jest naprawdę tym, za kogo się podaje. Zwykle udowadnia to nazwą użytkownika i hasłem. Weryfikację przeprowadza tzw. [#Autentykator]. Jeśli logowanie się nie powiedzie, zostanie rzucony wyjątek `Nette\Security\AuthenticationException`. ```php try { $user->login($username, $password); } catch (Nette\Security\AuthenticationException $e) { - $this->flashMessage('Uživatelské jméno nebo heslo je nesprávné'); + $this->flashMessage('Nazwa użytkownika lub hasło są nieprawidłowe'); } ``` -W ten sposób wylogowuje się użytkownika: +W ten sposób wylogujesz użytkownika: ```php $user->logout(); ``` -I stwierdzając, że jest zalogowany: +A sprawdzenie, czy jest zalogowany: ```php -echo $user->isLoggedIn() ? 'ano' : 'ne'; +echo $user->isLoggedIn() ? 'tak' : 'nie'; ``` -Bardzo proste, prawda? A Nette załatwia za Ciebie wszystkie aspekty bezpieczeństwa. +Bardzo proste, prawda? A wszystkie aspekty bezpieczeństwa Nette rozwiązuje za Ciebie. -W presenterech można uwierzytelnić logowanie w metodzie `startup()` i przekierować niezalogowanego użytkownika na stronę logowania. +W presenterach możesz zweryfikować zalogowanie w metodzie `startup()` i niezalogowanego użytkownika przekierować na stronę logowania. ```php protected function startup() @@ -55,39 +55,38 @@ protected function startup() ``` -Wygaśnięcie .[#toc-expiration] -============================== +Wygaśnięcie +=========== -Login użytkownika wygasa wraz z [wygaśnięciem |#Storage-for-Logged-User] pamięci masowej, którą zwykle jest sesja (patrz ustawienia [wygasania |http:configuration#Session] sesji). -Można jednak ustawić krótszy interwał czasowy, po którym użytkownik zostanie wylogowany. Odbywa się to poprzez wywołanie metody `setExpiration()` przed `login()`. Jako parametr podaj ciąg znaków z czasem względnym: +Zalogowanie użytkownika wygasa wraz z [wygaśnięciem przechowywania |#Przechowywanie danych zalogowanego użytkownika], którym zazwyczaj jest sesja (patrz ustawienia [wygaśnięcia sesji |http:configuration#Sesja]). Można jednak ustawić krótszy interwał czasowy, po upływie którego nastąpi wylogowanie użytkownika. Do tego służy metoda `setExpiration()`, która jest wywoływana przed `login()`. Jako parametr podaj ciąg znaków z czasem względnym: ```php -// login wygasa po 30 minutach bezczynności +// zalogowanie wygaśnie po 30 minutach nieaktywności $user->setExpiration('30 minutes'); -// anulowanie wygaśnięcia zestawu +// anulowanie ustawionego wygaśnięcia $user->setExpiration(null); ``` -Metoda `$user->getLogoutReason()` informuje, czy użytkownik został wylogowany z powodu timeoutu i zwraca stałą `Nette\Security\UserStorage::LogoutInactivity` (timeout wygasł) lub `UserStorage::LogoutManual` (wylogowany metodą `logout()`). +Czy użytkownik został wylogowany z powodu upływu interwału czasowego, powie metoda `$user->getLogoutReason()`, która zwraca albo stałą `Nette\Security\UserStorage::LogoutInactivity` (upłynął limit czasu) albo `UserStorage::LogoutManual` (wylogowany metodą `logout()`). -Authenticator .[#toc-authenticator] -=================================== +Autentykator +============ -Jest to obiekt, który uwierzytelnia poświadczenia logowania, zwykle nazwę użytkownika i hasło. Trywialną formą jest klasa [api:Nette\Security\SimpleAuthenticator], którą można zdefiniować w [konfiguracji |configuration]: +Jest to obiekt, który weryfikuje dane logowania, czyli zazwyczaj nazwę i hasło. Trywialną postacią jest klasa [api:Nette\Security\SimpleAuthenticator], którą możemy zdefiniować w [konfiguracji|configuration]: ```neon security: users: # nazwa: hasło - frantisek: tajne hasło + frantisek: tajnehaslo katka: jestetajnejsiheslo ``` -To rozwiązanie jest bardziej odpowiednie do celów testowych. Pokażemy jak stworzyć authenticator, który będzie walidował poświadczenia logowania względem tabeli w bazie danych. +To rozwiązanie jest odpowiednie raczej do celów testowych. Pokażemy, jak stworzyć autentykator, który będzie weryfikował dane logowania w oparciu o tabelę bazy danych. -Autentykator jest obiektem implementującym interfejs [api:Nette\Security\Authenticator] z metodą `authenticate()` Jego zadaniem jest albo zwrócenie tzw. [tożsamości |#Identity], albo rzucenie wyjątku `Nette\Security\AuthenticationException`. Można by jeszcze dołączyć kod błędu, aby dokładniej rozróżnić zaistniałą sytuację: `Authenticator::IdentityNotFound` i `Authenticator::InvalidCredential`. +Autentykator to obiekt implementujący interfejs [api:Nette\Security\Authenticator] z metodą `authenticate()`. Jej zadaniem jest albo zwrócić tzw. [#tożsamość] albo rzucić wyjątek `Nette\Security\AuthenticationException`. Można by było przy niej jeszcze podać kod błędu do dokładniejszego rozróżnienia powstałej sytuacji: `Authenticator::IdentityNotFound` i `Authenticator::InvalidCredential`. ```php use Nette; @@ -108,25 +107,25 @@ class MyAuthenticator implements Nette\Security\Authenticator ->fetch(); if (!$row) { - throw new Nette\Security\AuthenticationException('User not found.'); + throw new Nette\Security\AuthenticationException('Użytkownik nie znaleziony.'); } if (!$this->passwords->verify($password, $row->password)) { - throw new Nette\Security\AuthenticationException('Invalid password.'); + throw new Nette\Security\AuthenticationException('Nieprawidłowe hasło.'); } return new SimpleIdentity( $row->id, - $row->role, // nebo pole více rolí + $row->role, // lub tablica wielu ról ['name' => $row->username], ); } } ``` -Klasa MyAuthenticator komunikuje się z bazą danych poprzez [Nette Database Explorer |database:explorer] i pracuje z tabelą `users`, gdzie kolumna `username` zawiera nazwę logowania użytkownika, a kolumna `password` - [odcisk palca hasła |passwords]. Po sprawdzeniu poprawności nazwy i hasła zwraca tożsamość, która niesie ze sobą identyfikator użytkownika, jego rolę (kolumna `role` w tabeli), o której więcej powiemy [później |#Roles], oraz pole z dodatkowymi danymi (w naszym przypadku nazwę użytkownika). +Klasa MyAuthenticator komunikuje się z bazą danych za pomocą [Nette Database Explorer|database:explorer] i pracuje z tabelą `users`, gdzie w kolumnie `username` znajduje się nazwa logowania użytkownika, a w kolumnie `password` [skrót hasła|passwords]. Po weryfikacji nazwy i hasła zwraca tożsamość, która zawiera ID użytkownika, jego rolę (kolumna `role` w tabeli), o której więcej powiemy [później |authorization#Role], oraz tablicę z dodatkowymi danymi (w naszym przypadku nazwę użytkownika). -Autentykator dodamy jeszcze do konfiguracji [jako usługę |dependency-injection:services] kontenera DI: +Autentykator jeszcze dodamy do konfiguracji [jako usługę|dependency-injection:services] kontenera DI: ```neon services: @@ -134,23 +133,23 @@ services: ``` -Zdarzenia $onLoggedIn, $onLoggedOut .[#toc-onloggedin-onloggedout-events] -------------------------------------------------------------------------- +Zdarzenia $onLoggedIn, $onLoggedOut +----------------------------------- -Obiekt `Nette\Security\User` ma [zdarzenia |nette:glossary#Events] `$onLoggedIn` i `$onLoggedOut`, więc można dodać callbacki, które odpalają się odpowiednio po udanym logowaniu i wylogowaniu. +Obiekt `Nette\Security\User` ma [zdarzenia |nette:glossary#Eventy zdarzenia] `$onLoggedIn` i `$onLoggedOut`, możesz więc dodać callbacki, które zostaną wywołane po pomyślnym zalogowaniu lub po wylogowaniu użytkownika. ```php $user->onLoggedIn[] = function () { - // użytkownik właśnie się zalogował + // użytkownik został właśnie zalogowany }; ``` -Tożsamość .[#toc-identity] -========================== +Tożsamość +========= -Tożsamość to zestaw informacji o użytkowniku zwrócony przez authenticator, który następnie jest przechowywany w sesji i pobierany za pomocą `$user->getIdentity()`. Możemy zatem pobrać id, role i inne dane użytkownika przekazane nam w authenticatorze: +Tożsamość reprezentuje zbiór informacji o użytkowniku, który zwraca autentykator i który następnie jest przechowywany w sesji i uzyskujemy go za pomocą `$user->getIdentity()`. Możemy więc uzyskać id, role i inne dane użytkownika, tak jak je przekazaliśmy w autentykatorze: ```php $user->getIdentity()->getId(); @@ -163,21 +162,21 @@ $user->getIdentity()->getRoles(); $user->getIdentity()->name; ``` -Co ważne, gdy wylogujemy się za pomocą `$user->logout()` **tożsamość nie jest usuwana** i nadal jest dostępna. Więc chociaż użytkownik ma tożsamość, nie musi być zalogowany. Gdybyśmy chcieli jawnie usunąć tożsamość, wylogowalibyśmy użytkownika, wywołując `logout(true)`. +Co jest ważne, to że przy wylogowaniu za pomocą `$user->logout()` **tożsamość się nie kasuje** i jest nadal dostępna. Tak więc, chociaż użytkownik ma tożsamość, nie musi być zalogowany. Jeśli chcielibyśmy tożsamość jawnie skasować, wylogujemy użytkownika wywołaniem `logout(true)`. -W ten sposób można nadal zakładać, który użytkownik znajduje się na komputerze i np. pokazywać mu spersonalizowane oferty w sklepie internetowym, ale można mu pokazać jego dane osobowe dopiero po zalogowaniu. +Dzięki temu możesz nadal zakładać, który użytkownik jest przy komputerze i na przykład w e-sklepie wyświetlać mu spersonalizowane oferty, jednak wyświetlić mu jego dane osobowe możesz dopiero po zalogowaniu. -Tożsamość jest obiektem, który implementuje interfejs [api:Nette\Security\IIdentity], domyślną implementacją jest [api:Nette\Security\SimpleIdentity]. I jak wspomniano, jest utrzymywany w sesji, więc jeśli zmienimy rolę zalogowanego użytkownika, na przykład, stare dane pozostaną w jego tożsamości, dopóki nie zalogują się ponownie. +Tożsamość to obiekt implementujący interfejs [api:Nette\Security\IIdentity], domyślną implementacją jest [api:Nette\Security\SimpleIdentity]. I jak wspomniano, utrzymuje się w sesji, więc jeśli na przykład zmienimy rolę któregoś z zalogowanych użytkowników, stare dane pozostaną w jego tożsamości aż do jego ponownego zalogowania. -Magazyn zalogowanego użytkownika .[#toc-storage-for-logged-user] -================================================================ +Przechowywanie danych zalogowanego użytkownika +============================================== -Dwie podstawowe informacje o użytkowniku, czyli to, czy jest zalogowany i jego [tożsamość |#Identity], są zwykle przekazywane w ramach sesji. Które można zmienić. Przechowywaniem tych informacji zajmuje się obiekt implementujący interfejs `Nette\Security\UserStorage` Istnieją dwie standardowe implementacje, pierwsza przekazuje dane w sesji, a druga w ciasteczku. Są to klasy `Nette\Bridges\SecurityHttp\SessionStorage` i `CookieStorage`. Przechowywanie można wybrać i skonfigurować bardzo wygodnie w konfiguracji [Security › authentication |configuration#User-Storage]. +Dwie podstawowe informacje o użytkowniku, czyli czy jest zalogowany i jego [#Tożsamość], zazwyczaj są przenoszone w sesji. Co można zmienić. Za przechowywanie tych informacji odpowiada obiekt implementujący interfejs `Nette\Security\UserStorage`. Dostępne są dwie standardowe implementacje, pierwsza przenosi dane w sesji, a druga w cookie. Są to klasy `Nette\Bridges\SecurityHttp\SessionStorage` i `CookieStorage`. Wybrać przechowywanie i skonfigurować je możesz bardzo wygodnie w konfiguracji [security › authentication |configuration#Przechowywanie]. -Co więcej, możesz dokładnie kontrolować, jak będzie przebiegać przechowywanie tożsamości (*sleep*) i jej odzyskiwanie (*wakeup*). Potrzebujesz tylko, aby authenticator zaimplementował interfejs `Nette\Security\IdentityHandler`. Ma on dwie metody: `sleepIdentity()` jest wywoływany przed zapisaniem tożsamości do magazynu oraz `wakeupIdentity()` jest wywoływany po jej odczytaniu. Metody mogą modyfikować zawartość tożsamości lub zastąpić ją nowym obiektem, który zwraca. Metoda `wakeupIdentity()` może nawet zwrócić `null`, wylogowując tym samym użytkownika. +Dalej możesz wpłynąć na to, jak dokładnie będzie przebiegać zapisywanie tożsamości (*sleep*) i odtwarzanie (*wakeup*). Wystarczy, aby autentykator implementował interfejs `Nette\Security\IdentityHandler`. Ma on dwie metody: `sleepIdentity()` jest wywoływana przed zapisem tożsamości do przechowywania, a `wakeupIdentity()` po jej odczytaniu. Metody mogą zmodyfikować zawartość tożsamości, ewentualnie zastąpić ją nowym obiektem, który zwrócą. Metoda `wakeupIdentity()` może nawet zwrócić `null`, co spowoduje wylogowanie użytkownika. -Jako przykład pokażemy rozwiązanie częstego pytania, jak zaktualizować role w tożsamości zaraz po jej pobraniu z sesji. W metodzie `wakeupIdentity()` przekazujemy do tożsamości aktualną rolę, np. z bazy danych: +Jako przykład pokażemy rozwiązanie częstego pytania, jak zaktualizować role w tożsamości zaraz po odczytaniu z sesji. W metodzie `wakeupIdentity()` przekażemy do tożsamości aktualne role np. z bazy danych: ```php final class Authenticator implements @@ -185,8 +184,8 @@ final class Authenticator implements { public function sleepIdentity(IIdentity $identity): IIdentity { - // tutaj tożsamość może być zmieniona przed zapisem do repozytorium po zalogowaniu, - // ale nie jest nam to teraz potrzebne. + // tutaj można zmodyfikować tożsamość przed zapisem do przechowywania po zalogowaniu, + // ale tego teraz nie potrzebujemy return $identity; } @@ -199,11 +198,11 @@ final class Authenticator implements } ``` -A teraz wracamy do przechowywania na podstawie plików cookie. Pozwala stworzyć stronę, na której użytkownicy mogą się zalogować, a mimo to nie potrzebuje sesji. To znaczy, że nie musi zapisywać na dysku. Przecież tak właśnie działa strona, którą teraz czytasz, w tym forum. W tym przypadku wdrożenie `IdentityHandler` jest koniecznością. W pliku cookie przechowujemy jedynie losowy token reprezentujący zalogowanego użytkownika. +A teraz wrócimy do przechowywania opartego na cookies. Pozwala ono stworzyć stronę internetową, na której mogą logować się użytkownicy, a przy tym nie potrzebuje sesji. Czyli nie potrzebuje zapisywać na dysku. Zresztą tak działa również strona, którą właśnie czytasz, włącznie z forum. W tym przypadku implementacja `IdentityHandler` jest koniecznością. Do cookie bowiem będziemy zapisywać tylko losowy token reprezentujący zalogowanego użytkownika. -Więc najpierw ustawiamy pożądany magazyn w konfiguracji za pomocą `security › authentication › storage: cookie`. +Najpierw więc w konfiguracji ustawimy wymagane przechowywanie za pomocą `security › authentication › storage: cookie`. -W bazie danych utworzymy kolumnę `authtoken`, w której każdy użytkownik będzie miał [całkowicie losowy, unikalny i nieodgadniony |utils:random] ciąg znaków o odpowiedniej długości (co najmniej 13 znaków). Magazyn `CookieStorage` przenosi tylko wartość `$identity->getId()` w ciasteczku, dlatego w `sleepIdentity()` zastępujemy oryginalną tożsamość placeholderem z `authtoken` w ID, natomiast w metodzie `wakeupIdentity()` odczytujemy z bazy całą tożsamość zgodnie z authtokenem: +W bazie danych stworzymy kolumnę `authtoken`, w której każdy użytkownik będzie miał [całkowicie losowy, unikalny i nie do odgadnięcia|utils:random] ciąg znaków o wystarczającej długości (co najmniej 13 znaków). Przechowywanie `CookieStorage` przenosi w cookie tylko wartość `$identity->getId()`, więc w `sleepIdentity()` oryginalną tożsamość zastąpimy zastępczą z `authtoken` w ID, natomiast w metodzie `wakeupIdentity()` na podstawie authtokenu odczytamy całą tożsamość z bazy danych: ```php final class Authenticator implements @@ -212,21 +211,21 @@ final class Authenticator implements public function authenticate(string $username, string $password): SimpleIdentity { $row = $this->db->fetch('SELECT * FROM user WHERE username = ?', $username); - // ověříme heslo + // zweryfikujemy hasło ... - // w tym celu należy użyć danych z bazy danych. + // zwrócimy tożsamość ze wszystkimi danymi z bazy danych return new SimpleIdentity($row->id, null, (array) $row); } public function sleepIdentity(IIdentity $identity): SimpleIdentity { - // vrátíme zástupnou identitu, kde v ID bude authtoken + // zwrócimy zastępczą tożsamość, gdzie w ID będzie authtoken return new SimpleIdentity($identity->authtoken); } public function wakeupIdentity(IIdentity $identity): ?SimpleIdentity { - // zástupnou identitu nahradíme plnou identitou, jako v authenticate() + // zastępczą tożsamość zastąpimy pełną tożsamością, jak w authenticate() $row = $this->db->fetch('SELECT * FROM user WHERE authtoken = ?', $identity->getId()); return $row ? new SimpleIdentity($row->id, null, (array) $row) @@ -236,16 +235,16 @@ final class Authenticator implements ``` -Wiele niezależnych loginów .[#toc-multiple-independent-authentications] -======================================================================= +Wiele niezależnych logowań +========================== -Możliwe jest posiadanie wielu niezależnych użytkowników logujących się w tym samym czasie w ramach jednej witryny i jednej sesji. Na przykład, jeśli chcesz mieć oddzielne uwierzytelnianie dla części administracyjnej i publicznej witryny, po prostu ustaw oddzielną nazwę dla każdego z nich: +Jednocześnie w ramach jednej strony i jednej sesji może być kilku niezależnych logujących się użytkowników. Jeśli na przykład chcemy mieć na stronie oddzielną autentykację dla administracji i części publicznej, wystarczy każdej z nich ustawić własną nazwę: ```php $user->getStorage()->setNamespace('backend'); ``` -Należy pamiętać, aby zawsze ustawiać przestrzeń nazw na wszystkich stronach należących do tej sekcji. Jeśli używamy prezenterów, ustawiamy przestrzeń nazw we wspólnym przodku dla części - zwykle BasePresenter. Robimy to poprzez rozszerzenie metody [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()]: +Ważne jest, aby pamiętać, aby przestrzeń nazw ustawić zawsze we wszystkich miejscach należących do danej części. Jeśli używamy presenterów, ustawimy przestrzeń nazw we wspólnym przodku dla danej części - zazwyczaj BasePresenter. Uczynimy tak, rozszerzając metodę [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()]: ```php public function checkRequirements($element): void @@ -256,10 +255,10 @@ public function checkRequirements($element): void ``` -Wielokrotne uwierzytelnianie .[#toc-multiple-authenticators] ------------------------------------------------------------- +Wiele autentykatorów +-------------------- -Podzielenie aplikacji na części z niezależnym logowaniem zwykle wymaga również różnych uwierzytelniaczy. Jednak gdy tylko zarejestrowaliśmy w konfiguracji usługi dwie klasy implementujące Authenticator, Nette nie wiedziałoby, którą z nich automatycznie przypisać do obiektu `Nette\Security\User` i wyświetliłoby błąd. Dlatego musimy ograniczyć [autowiring |dependency-injection:autowiring] dla Authenticatorów, aby działał tylko wtedy, gdy ktoś zażąda konkretnej klasy, np. FrontAuthenticator, co uzyskuje się poprzez wybranie `autowired: self`: +Podział aplikacji na części z niezależnym logowaniem zazwyczaj wymaga również różnych autentykatorów. Gdybyśmy jednak w konfiguracji usług zarejestrowali dwie klasy implementujące Authenticator, Nette nie wiedziałoby, którą z nich automatycznie przypisać obiektowi `Nette\Security\User`, i wyświetliłoby błąd. Dlatego musimy dla autentykatorów [autowiring |dependency-injection:autowiring] ograniczyć tak, aby działał tylko wtedy, gdy ktoś zażąda konkretnej klasy, np. FrontAuthenticator, czego dokonamy wyborem `autowired: self`: ```neon services: @@ -278,7 +277,7 @@ class SignPresenter extends Nette\Application\UI\Presenter } ``` -Autentykator obiektu User ustawiamy przed wywołaniem metody [login() |api:Nette\Security\User::login()], a więc zwykle w kodzie formularza, który go loguje: +Autentykator obiektu User ustawimy przed wywołaniem metody [login() |api:Nette\Security\User::login()], więc zazwyczaj w kodzie formularza, który go loguje: ```php $form->onSuccess[] = function (Form $form, \stdClass $data) { diff --git a/security/pl/authorization.texy b/security/pl/authorization.texy index 8467a41d40..760e553ad4 100644 --- a/security/pl/authorization.texy +++ b/security/pl/authorization.texy @@ -1,48 +1,48 @@ -Weryfikacja uprawnień (Authorization) -************************************* +Weryfikacja uprawnień (Autoryzacja) +*********************************** .[perex] -Autoryzacja określa, czy użytkownik ma wystarczające uprawnienia do np. dostępu do zasobu lub wykonania akcji. Autoryzacja zakłada wcześniejsze udane uwierzytelnienie, czyli to, że użytkownik jest zalogowany. +Autoryzacja sprawdza, czy użytkownik ma wystarczające uprawnienia, na przykład do dostępu do określonego zasobu lub do wykonania jakiejś akcji. Autoryzacja zakłada wcześniejszą pomyślną autentykację, tj. że użytkownik jest zalogowany. -→ [Instalacja i wymagania |@home#Installation] +→ [Instalacja i wymagania |@home#Instalacja] -W przykładach użyjemy obiektu klasy [api:Nette\Security\User], który reprezentuje aktualnego użytkownika i do którego można uzyskać dostęp, zlecając jego przekazanie przez [dependency injection |dependency-injection:passing-dependencies]. W prezenterze wystarczy wywołać `$user = $this->getUser()`. +W przykładach będziemy używać obiektu klasy [api:Nette\Security\User], który reprezentuje aktualnego użytkownika i do którego dostaniesz się, prosząc o jego przekazanie za pomocą [wstrzykiwania zależności |dependency-injection:passing-dependencies]. W presenterach wystarczy tylko wywołać `$user = $this->getUser()`. -Dla bardzo prostych witryn z administracją, gdzie uprawnienia użytkowników nie są rozróżniane, jako kryterium autoryzacji można zastosować znaną metodę `isLoggedIn()` Innymi słowy, gdy użytkownik jest zalogowany, ma wszystkie uprawnienia i odwrotnie. +W bardzo prostych stronach internetowych z administracją, gdzie nie rozróżnia się uprawnień użytkowników, można jako kryterium autoryzacji użyć już znanej metody `isLoggedIn()`. Innymi słowy: jak tylko użytkownik jest zalogowany, ma wszelkie uprawnienia i na odwrót. ```php if ($user->isLoggedIn()) { // czy użytkownik jest zalogowany? - deleteItem(); // wtedy ma uprawnienia do wykonania operacji + deleteItem(); // wtedy ma uprawnienia do operacji } ``` -Rola .[#toc-roles] ------------------- +Role +---- -Celem ról jest zaoferowanie bardziej precyzyjnej kontroli uprawnień i zachowanie niezależności od nazwy użytkownika. Każdemu użytkownikowi zaraz po zalogowaniu zostanie przypisana jedna lub więcej ról, w których będzie działał. Role mogą być prostymi ciągami znaków, takimi jak `admin`, `member`, `guest`, itd. Jest on podawany jako drugi parametr konstruktora `SimpleIdentity`, jako łańcuch lub tablica łańcuchów - ról. +Celem ról jest zaoferowanie dokładniejszego zarządzania uprawnieniami i pozostanie niezależnym od nazwy użytkownika. Każdemu użytkownikowi zaraz po zalogowaniu przypisujemy jedną lub więcej ról, w których będzie występował. Role mogą być prostymi ciągami znaków, na przykład `admin`, `member`, `guest`, itp. Podaje się je jako drugi parametr konstruktora `SimpleIdentity`, albo jako ciąg znaków, albo tablicę ciągów - ról. -Jako kryterium autoryzacji używamy teraz metody `isInRole()`, która ujawnia, czy użytkownik jest w danej roli: +Jako kryterium autoryzacji teraz użyjemy metody `isInRole()`, która powie, czy użytkownik występuje w danej roli: ```php -if ($user->isInRole('admin')) { // czy użytkownik jest w roli administratora? - deleteItem(); // wtedy ma uprawnienia do wykonania operacji +if ($user->isInRole('admin')) { // czy użytkownik jest w roli admina? + deleteItem(); // wtedy ma uprawnienia do operacji } ``` -Jak już wiesz, kiedy użytkownik się wylogowuje, jego tożsamość nie musi być usunięta. Tak więc metoda `getIdentity()` nadal zwraca obiekt `SimpleIdentity`, łącznie z przyznanymi rolami. Nette Framework przestrzega zasady "mniej kodu, więcej bezpieczeństwa", gdzie mniej wpisywania prowadzi do bardziej bezpiecznego kodu, więc nie trzeba sprawdzać, czy użytkownik jest nadal zalogowany podczas odkrywania ról. Metoda `isInRole()` działa z **efektywnymi rolami,** czyli jeśli użytkownik jest zalogowany, to bazuje na rolach określonych w tożsamości, jeśli nie jest zalogowany, to automatycznie ma specjalną rolę `guest`. +Jak już wiesz, po wylogowaniu użytkownika nie musi się skasować jego tożsamość. Czyli nadal metoda `getIdentity()` zwraca obiekt `SimpleIdentity`, włącznie ze wszystkimi przyznanymi rolami. Nette Framework wyznaje zasadę „less code, more security”, gdzie mniej pisania prowadzi do bardziej zabezpieczonego kodu, dlatego przy sprawdzaniu ról nie musisz jeszcze weryfikować, czy użytkownik jest zalogowany. Metoda `isInRole()` pracuje z **efektywnymi rolami,** tj. jeśli użytkownik jest zalogowany, opiera się na rolach podanych w tożsamości, jeśli nie jest zalogowany, ma automatycznie specjalną rolę `guest`. -Autor .[#toc-authorizator] --------------------------- +Autoryzator +----------- -Oprócz ról wprowadzamy pojęcia zasobu i działania: +Oprócz ról wprowadzimy jeszcze pojęcia zasobu i operacji: -- **role** to właściwość użytkownika - np. moderator, redaktor, gość, zarejestrowany użytkownik, administrator... -- **zasób** (*zasób*) to logiczny element serwisu - artykuł, strona, użytkownik, pozycja menu, ankieta, prezenter, ... -- **operacja** (*operacja*) to pewne konkretne działanie, które użytkownik może lub nie może wykonać z zasobem - na przykład usunąć, edytować, stworzyć, zagłosować, ... +- **rola** to właściwość użytkownika - np. moderator, redaktor, gość, zarejestrowany użytkownik, administrator... +- **zasób** (*resource*) to jakiś logiczny element strony - post, strona, użytkownik, pozycja w menu, ankieta, presenter, ... +- **operacja** (*operation*) to jakaś konkretna czynność, którą użytkownik może lub nie może wykonać na zasobie - na przykład usunąć, edytować, utworzyć, głosować, ... -Autoryzator to obiekt, który decyduje, czy dana *role* ma uprawnienia do wykonania określonej *operacji* z określonym *zasobem*. Jest to obiekt implementujący interfejs [api:Nette\Security\Authorizator] z jedną metodą `isAllowed()`: +Autoryzator to obiekt, który decyduje, czy dana *rola* ma pozwolenie na wykonanie określonej *operacji* na określonym *zasobie*. Jest to obiekt implementujący interfejs [api:Nette\Security\Authorizator] z jedyną metodą `isAllowed()`: ```php class MyAuthorizator implements Nette\Security\Authorizator @@ -63,50 +63,50 @@ class MyAuthorizator implements Nette\Security\Authorizator } ``` -Dodaj authorizer do konfiguracji [jako usługę |dependency-injection:services] kontenera DI: +Autoryzator dodamy do konfiguracji [jako usługę|dependency-injection:services] kontenera DI: ```neon services: - MyAuthorizator ``` -Poniżej przedstawiono przykładowy przypadek użycia. Zauważ, że tym razem wywołujemy metodę `Nette\Security\User::isAllowed()`, a nie Authorizer, więc pierwszego parametru `$role` nie ma. Metoda ta wywołuje `MyAuthorizator::isAllowed()` sekwencyjnie dla wszystkich ról użytkownika i zwraca true, jeśli przynajmniej jedna z nich ma uprawnienia. +A następuje przykład użycia. Uwaga, tym razem wywołujemy metodę `Nette\Security\User::isAllowed()`, a nie autoryzator, więc nie ma tam pierwszego parametru `$role`. Ta metoda wywołuje `MyAuthorizator::isAllowed()` kolejno dla wszystkich ról użytkownika i zwraca true, jeśli przynajmniej jedna z nich ma pozwolenie. ```php -if ($user->isAllowed('file')) { // czy użytkownik może coś zrobić z zasobem 'plik'? +if ($user->isAllowed('file')) { // czy użytkownik może robić cokolwiek z zasobem 'file'? useFile(); } -if ($user->isAllowed('file', 'delete')) { // może zrobić 'delete' na zasobie 'plik'? +if ($user->isAllowed('file', 'delete')) { // czy może na zasobie 'file' wykonać 'delete'? deleteFile(); } ``` -Oba parametry są opcjonalne, domyślna wartość `null` ma znaczenie *anything*. +Oba parametry są opcjonalne, domyślna wartość `null` ma znaczenie *cokolwiek*. -Pozwolenie ACL .[#toc-permission-acl] -------------------------------------- +Permission ACL +-------------- -Nette posiada wbudowaną implementację autoryzatora, klasę [api:Nette\Security\Permission], dostarczającą programiście lekką i elastyczną warstwę ACL (Access Control List) dla uprawnień i kontroli dostępu. Praca z nim polega na definiowaniu ról, zasobów i indywidualnych uprawnień. Role i zasoby pozwalają na tworzenie hierarchii. Aby to wyjaśnić, pokażemy przykład aplikacji internetowej: +Nette dostarcza wbudowaną implementację autoryzatora, a mianowicie klasę [api:Nette\Security\Permission] zapewniającą programiście lekką i elastyczną warstwę ACL (Access Control List) do zarządzania uprawnieniami i dostępami. Praca z nią polega na definicji ról, zasobów i poszczególnych uprawnień. Przy czym role i zasoby umożliwiają tworzenie hierarchii. Dla wyjaśnienia pokażemy przykład aplikacji internetowej: -- `guest`: niezalogowany gość, który może czytać i przeglądać publiczną część strony, tj. czytać artykuły, komentarze i głosować w ankietach -- `registered`: zalogowany zarejestrowany użytkownik, który może również komentować -- `admin`: może zarządzać artykułami, komentarzami i ankietami +- `guest`: niezalogowany odwiedzający, który może czytać i przeglądać publiczną część strony, tzn. czytać posty, komentarze i głosować w ankietach +- `registered`: zalogowany zarejestrowany użytkownik, który dodatkowo może komentować +- `admin`: może zarządzać postami, komentarzami i ankietami -Mamy więc zdefiniowane pewne role (`guest`, `registered` i `admin`) i wymienione zasoby (`article`, `comment`, `poll`), do których użytkownicy z daną rolą mają dostęp lub wykonują pewne operacje (`view`, `vote`, `add`, `edit`). +Zdefiniowaliśmy więc pewne role (`guest`, `registered` i `admin`) i wspomnieliśmy zasoby (`article`, `comment`, `poll`), do których użytkownicy z jakąś rolą mogą uzyskiwać dostęp lub wykonywać określone operacje (`view`, `vote`, `add`, `edit`). -Utwórz instancję klasy Permission i zdefiniuj **role**. Możemy wykorzystać tzw. dziedziczenie ról, które zapewnia, że np. użytkownik z rolą administratora (`admin`) może robić to, co zwykły odwiedzający stronę (i oczywiście więcej). +Stworzymy instancję klasy Permission i zdefiniujemy **role**. Można przy tym wykorzystać tzw. dziedziczenie ról, które zapewni, że np. użytkownik z rolą administratora (`admin`) może robić również to, co zwykły odwiedzający strony (i oczywiście więcej). ```php $acl = new Nette\Security\Permission; $acl->addRole('guest'); $acl->addRole('registered', 'guest'); // 'registered' dziedziczy po 'guest' -$acl->addRole('admin', 'registered'); // i 'admin' po nim dziedziczy +$acl->addRole('admin', 'registered'); // a po nim dziedziczy 'admin' ``` -Teraz zdefiniujemy również listę **zasobów**, do których użytkownicy mogą mieć dostęp. +Teraz zdefiniujemy również listę **zasobów**, do których użytkownicy mogą uzyskiwać dostęp. ```php $acl->addResource('article'); @@ -114,49 +114,49 @@ $acl->addResource('comment'); $acl->addResource('poll'); ``` -Nawet zasoby mogą używać dziedziczenia, możliwe byłoby na przykład wpisanie `$acl->addResource('perex', 'article')`. +Również zasoby mogą używać dziedziczenia, można by było na przykład podać `$acl->addResource('perex', 'article')`. -A teraz najważniejsza rzecz. Zdefiniujmy zasady między nimi, aby określić, kto może zrobić co z czym: +A teraz najważniejsze. Zdefiniujemy między nimi reguły określające, kto co może z czym robić: ```php -//po pierwsze, nikt nie może nic zrobić +// najpierw nikt nie może robić nic -// Pozwól gościom przeglądać artykuły, komentarze i ankiety +// niech guest może przeglądać posty, komentarze i ankiety $acl->allow('guest', ['article', 'comment', 'poll'], 'view'); -// i głosować w ankietach +// a w ankietach dodatkowo i głosować $acl->allow('guest', 'poll', 'vote'); -// osoba zameldowana dziedziczy prawa po gościu, dajemy jej prawo do komentowania +// zarejestrowany dziedziczy prawa od guesta, damy mu dodatkowo prawo komentowania $acl->allow('registered', 'comment', 'add'); -// administrator może przeglądać i edytować wszystko +// administrator może przeglądać i edytować cokolwiek $acl->allow('admin', $acl::All, ['view', 'edit', 'add']); ``` -A co jeśli chcemy **ograniczać** komuś dostęp do określonego zasobu? +Co jeśli chcemy komuś **zabronić** dostępu do określonego zasobu? ```php -// Administrator nie może edytować ankiet, to byłoby niedemokratyczne +// administrator nie może edytować ankiet, to byłoby niedemokratyczne $acl->deny('admin', 'poll', 'edit'); ``` -Teraz, gdy mamy już listę zasad, możemy po prostu zadać pytania autoryzacyjne: +Teraz, gdy mamy stworzoną listę reguł, możemy łatwo zadawać pytania autoryzacyjne: ```php -// czy goście mogą oglądać artykuły? +// czy guest może przeglądać posty? $acl->isAllowed('guest', 'article', 'view'); // true -// czy goście mogą edytować artykuły? +// czy guest może edytować posty? $acl->isAllowed('guest', 'article', 'edit'); // false -// czy gość może głosować w ankietach? +// czy guest może głosować w ankietach? $acl->isAllowed('guest', 'poll', 'vote'); // true -// czy gość może komentować? +// czy guest może komentować? $acl->isAllowed('guest', 'comment', 'add'); // false ``` -To samo dotyczy zarejestrowanego użytkownika, ale on również może komentować: +To samo dotyczy zarejestrowanego użytkownika, ten jednak może również komentować: ```php $acl->isAllowed('registered', 'article', 'view'); // true @@ -164,7 +164,7 @@ $acl->isAllowed('registered', 'comment', 'add'); // true $acl->isAllowed('registered', 'comment', 'edit'); // false ``` -Administrator może edytować wszystko oprócz ankiet: +Administrator może edytować wszystko, oprócz ankiet: ```php $acl->isAllowed('admin', 'poll', 'vote'); // true @@ -172,7 +172,7 @@ $acl->isAllowed('admin', 'poll', 'edit'); // false $acl->isAllowed('admin', 'comment', 'edit'); // true ``` -Komunikaty mogą być również oceniane dynamicznie, a decyzję możemy pozostawić własnemu callbackowi, do którego przekazywane są wszystkie parametry: +Uprawnienia mogą być również oceniane dynamicznie i możemy pozostawić decyzję własnemu callbackowi, któremu zostaną przekazane wszystkie parametry: ```php $assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { @@ -182,7 +182,7 @@ $assertion = function (Permission $acl, string $role, string $resource, string $ $acl->allow('registered', 'comment', null, $assertion); ``` -Ale jak poradzić sobie z sytuacją, w której np. nie wystarczą tylko nazwy ról i zasobów, ale chcielibyśmy zdefiniować, że np. rola `registered` może edytować zasób `article` tylko wtedy, gdy jest jego autorem? Zamiast używać ciągów znaków będziemy używać obiektów, rolą będzie obiekt [api:Nette\Security\Role] a zasobem [api:Nette\Security\Resource]. Ich metody `getRoleId()` i `getResourceId()` zwrócą odpowiednio oryginalne ciągi znaków: +Jak jednak np. rozwiązać sytuację, gdy nie wystarczą tylko nazwy ról i zasobów, ale chcielibyśmy zdefiniować, że np. rola `registered` może edytować zasób `article` tylko jeśli jest jego autorem? Zamiast ciągów znaków użyjemy obiektów, rola będzie obiektem [api:Nette\Security\Role], a zasób [api:Nette\Security\Resource]. Ich metody `getRoleId()` resp. `getResourceId()` będą zwracać pierwotne ciągi znaków: ```php class Registered implements Nette\Security\Role @@ -207,19 +207,19 @@ class Article implements Nette\Security\Resource } ``` -A teraz tworzymy regułę: +A teraz stworzymy regułę: ```php $assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { - $role = $acl->getQueriedRole(); // objekt Registered - $resource = $acl->getQueriedResource(); // objekt Article + $role = $acl->getQueriedRole(); // obiekt Registered + $resource = $acl->getQueriedResource(); // obiekt Article return $role->id === $resource->authorId; }; $acl->allow('registered', 'article', 'edit', $assertion); ``` -A zapytanie ACL odbywa się poprzez przekazanie obiektów: +A zapytanie do ACL zostanie wykonane przez przekazanie obiektów: ```php $user = new Registered(/* ... */); @@ -227,7 +227,7 @@ $article = new Article(/* ... */); $acl->isAllowed($user, $article, 'edit'); ``` -Rola może dziedziczyć po innej roli lub po wielu rolach. Ale co się stanie, jeśli jeden przodek ma wyłączone działanie, a drugi włączone? Jakie będą prawa zstępnego? Jest to określone przez wagę roli - ostatnia wymieniona rola na liście przodków ma największą wagę, pierwsza wymieniona rola najmniejszą. To jest bardziej obrazowe z przykładu: +Rola może dziedziczyć po innej roli lub po wielu rolach. Co się jednak stanie, jeśli jeden przodek ma akcję zabronioną, a drugi dozwoloną? Jakie będą prawa potomka? Określa się to według wagi roli - ostatnia podana rola w liście przodków ma największą wagę, pierwsza podana rola tę najmniejszą. Bardziej obrazowe jest to z przykładu: ```php $acl = new Nette\Security\Permission; @@ -239,22 +239,22 @@ $acl->addResource('backend'); $acl->allow('admin', 'backend'); $acl->deny('guest', 'backend'); -// przypadek A: rola administratora ma mniejszą wagę niż rola gościa +// przypadek A: rola admin ma mniejszą wagę niż rola guest $acl->addRole('john', ['admin', 'guest']); $acl->isAllowed('john', 'backend'); // false -// przypadek B: rola admin ma większą wagę niż gość +// przypadek B: rola admin ma większą wagę niż guest $acl->addRole('mary', ['guest', 'admin']); $acl->isAllowed('mary', 'backend'); // true ``` -Role i zasoby mogą być również usuwane (`removeRole()`, `removeResource()`), reguły mogą być odwracane (`removeAllow()`, `removeDeny()`). Tablica wszystkich ról bezpośrednio rodzicielskich zwraca `getRoleParents()`, to czy dwie encje dziedziczą po sobie zwraca `roleInheritsFrom()` i `resourceInheritsFrom()`. +Role i zasoby można również usuwać (`removeRole()`, `removeResource()`), można również odwracać reguły (`removeAllow()`, `removeDeny()`). Tablicę wszystkich bezpośrednich ról rodzicielskich zwraca `getRoleParents()`, czy dwie encje dziedziczą po sobie zwraca `roleInheritsFrom()` i `resourceInheritsFrom()`. -Dodawanie jako usługa .[#toc-add-as-a-service] ----------------------------------------------- +Dodawanie jako usługi +--------------------- -Musimy przekazać ACL, który stworzyliśmy jako usługę do konfiguracji, aby mógł być używany przez obiekt `$user`, czyli aby mógł być używany w kodzie takim jak `$user->isAllowed('article', 'view')`. Aby to zrobić, napiszemy na nim fabrykę: +Nasze stworzone ACL musimy przekazać do konfiguracji jako usługę, aby zaczął go używać obiekt `$user`, czyli aby było możliwe używanie w kodzie np. `$user->isAllowed('article', 'view')`. W tym celu napiszemy dla niego fabrykę: ```php namespace App\Model; @@ -272,14 +272,14 @@ class AuthorizatorFactory } ``` -I dodaj go do konfiguracji: +I dodamy ją do konfiguracji: ```neon services: - App\Model\AuthorizatorFactory::create ``` -W presenterech można wtedy zweryfikować uprawnienia, na przykład w metodzie `startup()`: +W presenterach następnie możesz weryfikować uprawnienia na przykład w metodzie `startup()`: ```php protected function startup() diff --git a/security/pl/configuration.texy b/security/pl/configuration.texy index 1f14dcf66e..bc17443aff 100644 --- a/security/pl/configuration.texy +++ b/security/pl/configuration.texy @@ -4,68 +4,82 @@ Konfiguracja uprawnień dostępu .[perex] Przegląd opcji konfiguracyjnych dla Nette Security. -Jeśli nie używasz całego frameworka, a jedynie tej biblioteki, przeczytaj [jak załadować konfigurację |bootstrap:]. +Jeśli nie używasz całego frameworka, ale tylko tej biblioteki, przeczytaj, [jak wczytać konfigurację|bootstrap:]. -Możesz zdefiniować listę użytkowników w konfiguracji, aby utworzyć [prosty authenticator |authentication] (`Nette\Security\SimpleAuthenticator`). Ponieważ konfiguracja wymienia hasła w czytelnej formie, rozwiązanie to nadaje się jedynie do celów testowych. +W konfiguracji można zdefiniować listę użytkowników, tworząc w ten sposób [prosty autentykator|authentication] (`Nette\Security\SimpleAuthenticator`). Ponieważ w konfiguracji podaje się hasła w czytelnej postaci, to rozwiązanie jest odpowiednie tylko do celów testowych. ```neon security: - # pokazać panel użytkownika w Tracy Bar? + # wyświetlić panel użytkownika w Tracy Bar? debugger: ... # (bool) domyślnie jest true users: # nazwa: hasło - frantisek: secretpassword + frantisek: tajnehaslo - # nazwa, hasło, rola i inne dane dostępne w tożsamości + # nazwa, hasło, role i inne dane dostępne w tożsamości dobrota: - password: tajneheslo + password: tajnehaslo roles: [admin] data: ... ``` -Ponadto można zdefiniować role i zasoby, aby stworzyć podstawę dla [autoryzatora |authorization] (`Nette\Security\Permission`): +Dalej można zdefiniować role i zasoby, tworząc w ten sposób podstawę dla [autoryzatora|authorization] (`Nette\Security\Permission`): ```neon security: roles: guest: - registered: [guest] # zarejestrowany dziedziczy po gościu - admin: [registered] # i dziedziczy po adminie + registered: [guest] # registered dziedziczy po guest + admin: [registered] # a po nim dziedziczy admin resources: article: - comment: [article] # zasób dziedziczy po artykule + comment: [article] # zasób dziedziczy po article poll: ``` -Repozytorium .[#toc-user-storage] ---------------------------------- +Przechowywanie +-------------- -Można skonfigurować sposób przechowywania informacji o zalogowanym użytkowniku: +Można skonfigurować, jak przechowywać informacje o zalogowanym użytkowniku: ```neon security: authentication: # po jakim czasie nieaktywności użytkownik zostanie wylogowany - expiration: 30 minutes # (string) default is unset + expiration: 30 minutes # (string) domyślnie nieustawione - # gdzie mają być przechowywane informacje o zalogowanym użytkowniku + # gdzie przechowywać informacje o zalogowanym użytkowniku storage: session # (session|cookie) domyślnie jest session ``` -Jeśli jako miejsce przechowywania wybierzesz `cookie`, możesz również skonfigurować następujące opcje: +Jeśli wybierzesz jako przechowywanie `cookie`, możesz ustawić jeszcze te opcje: ```neon security: authentication: - # nazwa ciasteczka - cookieName: userId # (string) domyślnie userid + # nazwa cookie + cookieName: userId # (string) domyślnie jest userid - # domeny, które akceptują ciasteczka + # domeny, które akceptują cookie cookieDomain: 'example.com' # (string|domain) - # ograniczenia przy dostępie z innej domeny - cookieSamesite: None # (Strict|Lax|None) domyślnie Lax + # ograniczenie przy dostępie z innej domeny + cookieSamesite: None # (Strict|Lax|None) domyślnie jest Lax ``` + + +Usługi DI +--------- + +Te usługi są dodawane do kontenera DI: + +| Nazwa | Typ | Opis +|---------------------------------------------------------- +| `security.authenticator` | [api:Nette\Security\Authenticator] | [autentykator|authentication] +| `security.authorizator` | [api:Nette\Security\Authorizator] | [autoryzator|authorization] +| `security.passwords` | [api:Nette\Security\Passwords] | [hashowanie haseł|passwords] +| `security.user` | [api:Nette\Security\User] | aktualny użytkownik +| `security.userStorage` | [api:Nette\Security\UserStorage] | [#przechowywanie] diff --git a/security/pl/passwords.texy b/security/pl/passwords.texy index 185acb2567..3745bed04e 100644 --- a/security/pl/passwords.texy +++ b/security/pl/passwords.texy @@ -1,12 +1,12 @@ -Haszowanie hasła +Haszowanie haseł **************** .[perex] -W celu zapewnienia bezpieczeństwa naszych użytkowników nie przechowujemy ich haseł w czytelnej formie, a jedynie przechowujemy odcisk palca (tzw. hash). Z odcisku palca nie da się odtworzyć oryginalnej postaci hasła. Ważne jest, aby do stworzenia odcisku palca użyć bezpiecznego algorytmu. Pomoże nam w tym klasa [api:Nette\Security\Passwords]. +Aby zapewnić bezpieczeństwo naszym użytkownikom, nie przechowujemy ich haseł w czytelnej postaci, ale przechowujemy tylko ich skrót (tzw. hash). Ze skrótu nie można odtworzyć pierwotnej postaci hasła. Ważne jest, aby użyć bezpiecznego algorytmu do tworzenia skrótu. Pomoże nam w tym klasa [api:Nette\Security\Passwords]. -→ [Instalacja i wymagania |@home#Installation] +→ [Instalacja i wymagania |@home#Instalacja] -Framework automatycznie dodaje usługę taką jak `Nette\Security\Passwords` do kontenera DI pod nazwą `security.passwords`, do której możesz uzyskać dostęp, zlecając jej przekazanie za pomocą [zastrzyku zależności |dependency-injection:passing-dependencies]. +Framework automatycznie dodaje do kontenera DI usługę typu `Nette\Security\Passwords` pod nazwą `security.passwords`, do której dostaniesz się, prosząc o jej przekazanie za pomocą [wstrzykiwania zależności |dependency-injection:passing-dependencies]. ```php use Nette\Security\Passwords; @@ -24,40 +24,40 @@ class Foo __construct($algo=PASSWORD_DEFAULT, array $options=[]): string .[method] ======================================================================== -Wybieramy, jaki [bezpieczny algorytm |https://www.php.net/manual/en/password.constants.php] generowania hashów zastosować i konfigurujemy jego parametry. +Wybieramy, który [bezpieczny algorytm|https://www.php.net/manual/en/password.constants.php] do generowania hasha użyć i konfigurujemy jego parametry. -Domyślnie jest to `PASSWORD_DEFAULT`, więc wybór algorytmu pozostawiamy PHP. Algorytm ten może ulec zmianie w nowszych wersjach PHP, jeśli pojawią się nowsze, silniejsze algorytmy haszujące. Dlatego należy mieć świadomość, że długość wynikowego hasha może się zmienić i należy go przechowywać w sposób, który może pomieścić wystarczająco dużo znaków, 255 to zalecana szerokość. +Domyślnie używany jest `PASSWORD_DEFAULT`, czyli wybór algorytmu pozostawia się PHP. Algorytm może się zmienić w nowszych wersjach PHP, jeśli pojawią się nowsze, silniejsze algorytmy haszujące. Dlatego powinieneś być świadomy, że długość wynikowego hasha może się zmienić, i powinieneś go przechowywać w sposób, który może pomieścić wystarczającą liczbę znaków, 255 to zalecana szerokość. -Przykład ustawienia prędkości haszowania algorytmu bcrypt poprzez zmianę parametru cost: (w 2020 roku domyślnie 10, haszowanie hasła trwa około 80ms, dla kosztu 11 jest to około 160ms, dla kosztu 12 jest to około 320ms, im wolniej tym lepsze zabezpieczenie, a prędkość 10-12 jest już uważana za wystarczające zabezpieczenie) +Przykład ustawienia szybkości haszowania algorytmem bcrypt przez zmianę parametru cost: (w 2020 roku domyślne jest 10, haszowanie hasła trwa około 80ms, dla cost 11 około 160ms, dla cost 12 około 320ms, im wolniej, tym lepsza ochrona, przy czym szybkość 10-12 jest już uważana za ochronę wystarczającą) ```php -// budeme hesla hashovat 2^12 (2^cost) iteracemi algorytmu bcrypt +// będziemy haszować hasła 2^12 (2^cost) iteracjami algorytmu bcrypt $passwords = new Passwords(PASSWORD_BCRYPT, ['cost' => 12]); ``` -Wykorzystanie wstrzykiwania zależności: +Za pomocą wstrzykiwania zależności: ```neon services: security.passwords: Nette\Security\Passwords(::PASSWORD_BCRYPT, [cost: 12]) ``` -hash(string $passwords): string .[method] -========================================= +hash(string $password): string .[method] +======================================== Generuje hash hasła. ```php -$res = $passwords->hash($password); // Zahashuje heslo +$res = $passwords->hash($password); // Haszuje hasło ``` -Wynik, `$res`, to ciąg znaków, który oprócz samego hasha zawiera identyfikator użytego algorytmu, jego ustawienia oraz sól kryptograficzną (losowe dane zapewniające wygenerowanie innego hasha dla tego samego hasła). Dzięki temu jest on kompatybilny wstecz, na przykład w przypadku zmiany parametrów można zweryfikować hashe zapisane przy użyciu poprzednich ustawień. Cały ten wynik jest przechowywany w bazie danych, więc nie ma potrzeby przechowywania soli czy ustawień osobno. +Wynik `$res` to ciąg znaków, który oprócz samego hasha zawiera również identyfikator użytego algorytmu, jego ustawienia i sól kryptograficzną (losowe dane zapewniające, że dla tego samego hasła zostanie wygenerowany inny hash). Jest więc wstecznie kompatybilny, gdy na przykład zmienisz parametry, to również hashe zapisane z użyciem poprzednich ustawień będą mogły być zweryfikowane. Cały ten wynik zapisuje się do bazy danych, więc nie ma potrzeby zapisywać soli lub ustawień osobno. verify(string $password, string $hash): bool .[method] ====================================================== -Ustalenie, czy hasło pasuje do podanego odcisku palca. `$hash` pobranie z bazy danych według podanej nazwy użytkownika lub adresu e-mail. +Sprawdza, czy podane hasło odpowiada danemu skrótowi. `$hash` pobierz z bazy danych według podanej nazwy użytkownika lub adresu e-mail. ```php if ($passwords->verify($password, $hash)) { @@ -69,13 +69,13 @@ if ($passwords->verify($password, $hash)) { needsRehash(string $hash): bool .[method] ========================================= -Uzyskuje informację czy hash pasuje do opcji określonych w konstruktorze. +Sprawdza, czy hash odpowiada opcjom podanym w konstruktorze. -Przydatne, gdy na przykład zmieniasz szybkość haszowania. Sprawdzi się w stosunku do zapisanych ustawień i jeśli `needsRehash()` zwróci `true`, to musisz odtworzyć hash, tym razem z nowymi parametrami i ponownie zapisać go w bazie danych. W ten sposób zapisane hashe są automatycznie "aktualizowane", gdy użytkownicy się logują. +Przydaje się użyć w momencie, gdy np. zmieniasz szybkość haszowania. Weryfikacja przebiegnie według zapisanych ustawień, a jeśli `needsRehash()` zwróci `true`, to trzeba ponownie utworzyć hash, tym razem z nowymi parametrami, i zapisać go ponownie do bazy danych. W ten sposób automatycznie "aktualizują się" zapisane hashe podczas logowania użytkowników. ```php if ($passwords->needsRehash($hash)) { $hash = $passwords->hash($password); - // uložit $hash do databáze + // zapisać $hash do bazy danych } ``` diff --git a/security/pt/@home.texy b/security/pt/@home.texy index 2ecfc30130..b803493a45 100644 --- a/security/pt/@home.texy +++ b/security/pt/@home.texy @@ -1,14 +1,14 @@ -Segurança -********* +Nette Security +************** .[perex] -O pacote `nette/security` é responsável pela [autenticação dos usuários |authentication], [controle de acesso |authorization] e [hashing da senha |passwords]. +O pacote `nette/security` é responsável pelo [login de usuários |authentication], [verificação de permissões |authorization] e [hashing de senhas |passwords]. -Instalação .[#toc-installation] -------------------------------- +Instalação +---------- -Baixe e instale o pacote usando [o Composer |best-practices:composer]: +Baixe e instale a biblioteca usando o [Composer|best-practices:composer]: ```shell composer require nette/security diff --git a/security/pt/@left-menu.texy b/security/pt/@left-menu.texy index 1035679584..0fb1799b73 100644 --- a/security/pt/@left-menu.texy +++ b/security/pt/@left-menu.texy @@ -1,7 +1,7 @@ -Segurança Nette -*************** -- [Visão geral |@home] -- [Autenticação |Authentication] -- [Autorização |Authorization] -- [Compressão de senha |passwords] -- [Configuração |Configuration] +Nette Security +************** +- [Introdução |@home] +- [Autenticação |authentication] +- [Autorização |authorization] +- [Hashing de senhas |passwords] +- [Configuração |configuration] diff --git a/security/pt/@meta.texy b/security/pt/@meta.texy new file mode 100644 index 0000000000..41a853b6aa --- /dev/null +++ b/security/pt/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentação Nette}} diff --git a/security/pt/authentication.texy b/security/pt/authentication.texy index 4d4544d098..0ae16d3db6 100644 --- a/security/pt/authentication.texy +++ b/security/pt/authentication.texy @@ -1,48 +1,48 @@ -Autenticação de usuários -************************ +Login de usuários (Autenticação) +******************************** <div class=perex> -Poucas ou nenhumas aplicações web não precisam de nenhum mecanismo para login de usuário ou verificação de privilégios de usuário. Neste capítulo, vamos falar sobre isso: +Quase nenhuma aplicação web dispensa um mecanismo de login de usuários e verificação de permissões de usuário. Neste capítulo, falaremos sobre: -- login e logout do usuário -- autenticadores e autorizadores personalizados +- login e logout de usuários +- autenticadores personalizados </div> -→ [Instalação e requisitos |@home#Installation] +→ [Instalação e requisitos |@home#Instalação] -Nos exemplos, usaremos um objeto da classe [api:Nette\Security\User], que representa o usuário atual e que você obtém ao passá-lo usando a [injeção de dependência |dependency-injection:passing-dependencies]. Nos apresentadores, basta ligar para `$user = $this->getUser()`. +Nos exemplos, usaremos o objeto da classe [api:Nette\Security\User], que representa o usuário atual e ao qual você pode acessar solicitando-o através de [injeção de dependência |dependency-injection:passing-dependencies]. Nos presenters, basta chamar `$user = $this->getUser()`. -Autenticação .[#toc-authentication] -=================================== +Autenticação +============ -Autenticação significa ** login de usuário**, ou seja, o processo durante o qual a identidade de um usuário é verificada. O usuário normalmente se identifica usando nome de usuário e senha. A verificação é realizada pelo chamado [autenticador |#authenticator]. Se o login falhar, ele lança `Nette\Security\AuthenticationException`. +Autenticação significa **login de usuários**, ou seja, o processo pelo qual se verifica se o usuário é realmente quem ele diz ser. Geralmente, ele se comprova com nome de usuário e senha. A verificação é realizada pelo chamado [##autenticador]. Se o login falhar, uma `Nette\Security\AuthenticationException` é lançada. ```php try { $user->login($username, $password); } catch (Nette\Security\AuthenticationException $e) { - $this->flashMessage('The username or password you entered is incorrect.'); + $this->flashMessage('Nome de usuário ou senha incorretos'); } ``` -Esta é a forma de efetuar o logout do usuário: +Desta forma, você faz logout do usuário: ```php $user->logout(); ``` -E verificar se o usuário está logado: +E para verificar se ele está logado: ```php -echo $user->isLoggedIn() ? 'yes' : 'no'; +echo $user->isLoggedIn() ? 'sim' : 'não'; ``` -Simples, certo? E todos os aspectos de segurança são tratados pela Nette para você. +Muito simples, não é? E todos os aspectos de segurança são tratados pelo Nette para você. -No apresentador, você pode verificar o login no método `startup()` e redirecionar um usuário sem login para a página de login. +Nos presenters, você pode verificar o login no método `startup()` e redirecionar o usuário não logado para a página de login. ```php protected function startup() @@ -55,39 +55,38 @@ protected function startup() ``` -Validade .[#toc-expiration] -=========================== +Expiração +========= -O login do usuário expira junto com a [expiração do repositório |#Storage for Logged User], que geralmente é uma sessão (veja a configuração de [expiração da sessão |http:configuration#session] ). -No entanto, também é possível definir um intervalo de tempo mais curto após o qual o usuário é desconectado. O método `setExpiration()`, que é chamado antes de `login()`, é usado para este fim. Fornecer uma seqüência com um tempo relativo como parâmetro: +O login do usuário expira junto com a [expiração do armazenamento |#Armazenamento do usuário logado], que geralmente é a sessão (veja a configuração de [expiração da sessão |http:configuration#Sessão]). No entanto, também é possível definir um intervalo de tempo menor, após o qual o usuário será desconectado. Para isso, serve o método `setExpiration()`, que é chamado antes de `login()`. Como parâmetro, forneça uma string com tempo relativo: ```php // o login expira após 30 minutos de inatividade $user->setExpiration('30 minutes'); -// cancelar a expiração do conjunto +// cancelamento da expiração definida $user->setExpiration(null); ``` -O método `$user->getLogoutReason()` informa se o usuário foi desconectado porque o intervalo de tempo expirou. Ele retorna ou a constante `Nette\Security\UserStorage::LogoutInactivity` se o tempo expirou ou `UserStorage::LogoutManual` quando o método `logout()` foi chamado. +Se o usuário foi desconectado devido à expiração do intervalo de tempo, o método `$user->getLogoutReason()` informa, retornando a constante `Nette\Security\UserStorage::LogoutInactivity` (limite de tempo expirado) ou `UserStorage::LogoutManual` (desconectado pelo método `logout()`). -Autenticador .[#toc-authenticator] -================================== +Autenticador +============ -É um objeto que verifica os dados de login, ou seja, geralmente o nome e a senha. A implementação trivial é a classe [api:Nette\Security\SimpleAuthenticator], que pode ser definida na [configuração |configuration]: +É um objeto que verifica as credenciais de login, ou seja, geralmente nome e senha. Uma forma trivial é a classe [api:Nette\Security\SimpleAuthenticator], que podemos definir na [configuração|configuration]: ```neon security: users: - # name: password - johndoe: secret123 - kathy: evenmoresecretpassword + # nome: senha + frantisek: tajneheslo + katka: jestetajnejsiheslo ``` -Esta solução é mais adequada para fins de teste. Mostraremos a você como criar um autenticador que verificará as credenciais em relação a uma tabela de banco de dados. +Esta solução é mais adequada para fins de teste. Mostraremos como criar um autenticador que verificará as credenciais de login em uma tabela do banco de dados. -Um autenticador é um objeto que implementa a interface [api:Nette\Security\Authenticator] com o método `authenticate()`. Sua tarefa é devolver a chamada [identidade |#identity] ou lançar uma exceção `Nette\Security\AuthenticationException`. Também seria possível fornecer um código de erro de grão fino `Authenticator::IdentityNotFound` ou `Authenticator::InvalidCredential`. +O autenticador é um objeto que implementa a interface [api:Nette\Security\Authenticator] com o método `authenticate()`. Sua tarefa é retornar a chamada [#identidade] ou lançar uma exceção `Nette\Security\AuthenticationException`. Seria possível ainda indicar um código de erro para distinguir mais finamente a situação ocorrida: `Authenticator::IdentityNotFound` e `Authenticator::InvalidCredential`. ```php use Nette; @@ -117,16 +116,16 @@ class MyAuthenticator implements Nette\Security\Authenticator return new SimpleIdentity( $row->id, - $row->role, // ou matriz de papéis + $row->role, // ou um array de múltiplos papéis ['name' => $row->username], ); } } ``` -A classe MyAuthenticator se comunica com o banco de dados através do [Nette Database Explorer |database:explorer] e trabalha com a tabela `users`, onde a coluna `username` contém o nome de login do usuário e a coluna `password` contém [hash |passwords]. Após verificar o nome e a senha, ele retorna a identidade com o ID do usuário, função (coluna `role` na tabela), que mencionaremos [mais tarde |#roles], e um array com dados adicionais (no nosso caso, o nome do usuário). +A classe MyAuthenticator comunica com o banco de dados através do [Nette Database Explorer|database:explorer] e trabalha com a tabela `users`, onde na coluna `username` está o nome de login do usuário e na coluna `password` está o [hash da senha|passwords]. Após verificar o nome e a senha, retorna a identidade, que carrega o ID do usuário, seu papel (coluna `role` na tabela), sobre o qual falaremos mais [posteriormente |authorization#Papéis], e um array com outros dados (no nosso caso, o nome de usuário). -Acrescentaremos o autenticador à configuração [como um serviço |dependency-injection:services] do recipiente DI: +Adicionaremos ainda o autenticador à configuração [como um serviço|dependency-injection:services] do contêiner DI: ```neon services: @@ -137,47 +136,47 @@ services: Eventos $onLoggedIn, $onLoggedOut --------------------------------- -O objeto `Nette\Security\User` tem [eventos |nette:glossary#Events] `$onLoggedIn` e `$onLoggedOut`, assim você pode adicionar callbacks que são acionados depois de um login bem sucedido ou depois que o usuário sai do sistema. +O objeto `Nette\Security\User` tem [eventos |nette:glossary#Eventos] `$onLoggedIn` e `$onLoggedOut`, então você pode adicionar callbacks que são chamados após o login bem-sucedido ou após o logout do usuário. ```php $user->onLoggedIn[] = function () { - // o usuário acabou de fazer o login + // o usuário acabou de fazer login }; ``` -Identidade .[#toc-identity] -=========================== +Identidade +========== -Uma identidade é um conjunto de informações sobre um usuário que é devolvido pelo autenticador e que é então armazenado em uma sessão e recuperado usando `$user->getIdentity()`. Assim, podemos obter a identificação, funções e outros dados do usuário à medida que os passamos no autenticador: +A identidade representa um conjunto de informações sobre o usuário, que é retornado pelo autenticador e que é subsequentemente armazenado na sessão e obtido usando `$user->getIdentity()`. Podemos, portanto, obter o id, papéis e outros dados do usuário, da forma como os passamos no autenticador: ```php $user->getIdentity()->getId(); -// também funciona como atalho $user->getId(); +// o atalho $user->getId() também funciona; $user->getIdentity()->getRoles(); -// os dados do usuário podem ser acessados como propriedades -// o nome que passamos no MyAuthenticator +// os dados do usuário estão disponíveis como propriedades +// nome que passamos em MyAuthenticator $user->getIdentity()->name; ``` -É importante ressaltar que quando o usuário faz logout usando `$user->logout()`, **identidade não é apagada** e ainda está disponível. Portanto, se a identidade existe, ela por si só não garante que o usuário também esteja logado. Se quisermos excluir explicitamente a identidade, o usuário sai do sistema pelo endereço `logout(true)`. +O que é importante é que, ao fazer logout usando `$user->logout()`, **a identidade não é apagada** e continua disponível. Portanto, embora o usuário tenha uma identidade, ele pode não estar logado. Se quiséssemos apagar explicitamente a identidade, faríamos logout do usuário chamando `logout(true)`. -Graças a isto, você ainda pode assumir qual usuário está no computador e, por exemplo, exibir ofertas personalizadas na loja virtual, no entanto, você só pode exibir seus dados pessoais após fazer o login. +Graças a isso, você pode continuar a presumir qual usuário está no computador e, por exemplo, exibir ofertas personalizadas em uma loja online, mas só pode exibir seus dados pessoais após o login. -A identidade é um objeto que implementa a interface [api:Nette\Security\IIdentity], a implementação padrão é [api:Nette\Security\SimpleIdentity]. E como mencionado, a identidade é armazenada na sessão, portanto, se, por exemplo, mudarmos o papel de alguns dos usuários logados, os dados antigos serão mantidos na identidade até que ele se logue novamente. +A identidade é um objeto que implementa a interface [api:Nette\Security\IIdentity], a implementação padrão é [api:Nette\Security\SimpleIdentity]. E como mencionado, ela é mantida na sessão, então se, por exemplo, alterarmos o papel de algum dos usuários logados, os dados antigos permanecerão em sua identidade até que ele faça login novamente. -Armazenamento para usuários logados .[#toc-storage-for-logged-user] -=================================================================== +Armazenamento do usuário logado +=============================== -As duas informações básicas sobre o usuário, ou seja, se eles estão logados e sua [identidade |#identity], são geralmente carregadas na sessão. O que pode ser alterado. Para armazenar estas informações é responsável um objeto implementando a interface `Nette\Security\UserStorage`. Há duas implementações padrão, a primeira transmite dados em uma sessão e a segunda em um cookie. Estas são as classes `Nette\Bridges\SecurityHttp\SessionStorage` e `CookieStorage`. Você pode escolher o armazenamento e configurá-lo de forma muito conveniente na configuração [de autenticação de segurança |configuration]. +Duas informações básicas sobre o usuário, ou seja, se ele está logado e sua [#identidade], geralmente são transmitidas na sessão. O que pode ser alterado. O armazenamento dessas informações é responsabilidade de um objeto que implementa a interface `Nette\Security\UserStorage`. Existem duas implementações padrão disponíveis, a primeira transmite dados na sessão e a segunda em um cookie. São as classes `Nette\Bridges\SecurityHttp\SessionStorage` e `CookieStorage`. Você pode escolher o armazenamento e configurá-lo muito convenientemente na configuração [security › authentication |configuration#Armazenamento]. -Você também pode controlar exatamente como será feita a economia de identidade (*sleep*) e o restabelecimento (*despertar*). Tudo o que você precisa é que o autenticador implemente a interface `Nette\Security\IdentityHandler`. Isto tem dois métodos: `sleepIdentity()` é chamada antes que a identidade seja escrita para armazenamento, e `wakeupIdentity()` é chamada após a leitura da identidade. Os métodos podem modificar o conteúdo da identidade, ou substituí-la por um novo objeto que retorne. O método `wakeupIdentity()` pode até mesmo retornar `null`, que registra o usuário fora. +Além disso, você pode influenciar como exatamente o armazenamento da identidade ocorrerá (*sleep*) e a restauração (*wakeup*). Basta que o autenticador implemente a interface `Nette\Security\IdentityHandler`. Ela tem dois métodos: `sleepIdentity()` é chamado antes de escrever a identidade no armazenamento e `wakeupIdentity()` após sua leitura. Os métodos podem modificar o conteúdo da identidade ou substituí-la por um novo objeto que retornam. O método `wakeupIdentity()` pode até retornar `null`, o que desconecta o usuário. -Como exemplo, mostraremos uma solução para uma questão comum sobre como atualizar papéis de identidade logo após o restabelecimento de uma sessão. No método `wakeupIdentity()`, passamos os papéis atuais para a identidade, por exemplo, a partir do banco de dados: +Como exemplo, mostraremos a solução para a questão frequente de como atualizar os papéis na identidade logo após carregá-la da sessão. No método `wakeupIdentity()`, passaremos para a identidade os papéis atuais, por exemplo, do banco de dados: ```php final class Authenticator implements @@ -185,25 +184,25 @@ final class Authenticator implements { public function sleepIdentity(IIdentity $identity): IIdentity { - // aqui você pode mudar a identidade antes de armazenar após o login, + // aqui é possível modificar a identidade antes de escrever no armazenamento após o login, // mas não precisamos disso agora return $identity; } public function wakeupIdentity(IIdentity $identity): ?IIdentity { - // atualizando papéis na identidade + // atualização dos papéis na identidade $userId = $identity->getId(); $identity->setRoles($this->facade->getUserRoles($userId)); return $identity; } ``` -E agora voltamos ao armazenamento baseado em cookies. Ele permite criar um website onde os usuários podem fazer o login sem a necessidade de usar sessões. Portanto, não é necessário escrever em disco. Afinal, é assim que o website que você está lendo agora funciona, incluindo o fórum. Neste caso, a implementação do `IdentityHandler` é uma necessidade. Armazenaremos apenas um token aleatório representando o usuário logado no cookie. +E agora voltaremos ao armazenamento baseado em cookies. Ele permite criar um site onde os usuários podem fazer login sem precisar de sessões. Ou seja, não precisa escrever no disco. Aliás, é assim que funciona o site que você está lendo agora, incluindo o fórum. Neste caso, a implementação de `IdentityHandler` é uma necessidade. No cookie, armazenaremos apenas um token aleatório representando o usuário logado. -Portanto, primeiro definimos o armazenamento desejado na configuração usando `security › authentication › storage: cookie`. +Primeiro, na configuração, definiremos o armazenamento desejado usando `security › authentication › storage: cookie`. -Acrescentaremos uma coluna `authtoken` no banco de dados, na qual cada usuário terá uma seqüência [completamente aleatória, única e indiscutível |utils:random], de comprimento suficiente (pelo menos 13 caracteres). O repositório `CookieStorage` armazena apenas o valor `$identity->getId()` no cookie, assim, em `sleepIdentity()` substituímos a identidade original por um proxy por `authtoken` no ID, ao contrário, no método `wakeupIdentity()` restauramos toda a identidade do banco de dados de acordo com authtoken: +No banco de dados, criaremos uma coluna `authtoken`, na qual cada usuário terá uma string [completamente aleatória, única e imprevisível|utils:random] de comprimento suficiente (pelo menos 13 caracteres). O armazenamento `CookieStorage` transmite no cookie apenas o valor `$identity->getId()`, então em `sleepIdentity()` substituiremos a identidade original por uma substituta com `authtoken` no ID, enquanto no método `wakeupIdentity()`, de acordo com o authtoken, leremos a identidade completa do banco de dados: ```php final class Authenticator implements @@ -212,21 +211,21 @@ final class Authenticator implements public function authenticate(string $username, string $password): SimpleIdentity { $row = $this->db->fetch('SELECT * FROM user WHERE username = ?', $username); - // verificar senha + // verificamos a senha ... - // devolvemos a identidade com todos os dados do banco de dados + // retornamos a identidade com todos os dados do banco de dados return new SimpleIdentity($row->id, null, (array) $row); } public function sleepIdentity(IIdentity $identity): SimpleIdentity { - // devolvemos uma identidade proxy, onde no ID é authtoken + // retornamos a identidade substituta, onde no ID estará o authtoken return new SimpleIdentity($identity->authtoken); } public function wakeupIdentity(IIdentity $identity): ?SimpleIdentity { - // substituir a identidade proxy por uma identidade completa, como em authenticate() + // substituímos a identidade substituta pela identidade completa, como em authenticate() $row = $this->db->fetch('SELECT * FROM user WHERE authtoken = ?', $identity->getId()); return $row ? new SimpleIdentity($row->id, null, (array) $row) @@ -236,16 +235,16 @@ final class Authenticator implements ``` -Autenticações Independentes Múltiplas .[#toc-multiple-independent-authentications] -================================================================================== +Múltiplos logins independentes +============================== -É possível ter vários usuários independentes logados dentro de um site e uma sessão de cada vez. Por exemplo, se quisermos ter uma autenticação separada para o frontend e backend, apenas definiremos um espaço de nome de sessão único para cada um deles: +É possível ter vários usuários logados independentemente dentro de um único site e uma única sessão simultaneamente. Se, por exemplo, quisermos ter autenticação separada para a administração e a parte pública do site, basta definir um nome próprio para cada uma delas: ```php $user->getStorage()->setNamespace('backend'); ``` -É necessário ter em mente que isto deve ser estabelecido em todos os lugares pertencentes ao mesmo segmento. Ao utilizar os apresentadores, colocaremos o namespace no ancestral comum - geralmente o BasePresenter. Para isso, estenderemos o método [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()]: +É importante lembrar de definir o namespace sempre em todos os lugares pertencentes à parte correspondente. Se usarmos presenters, definiremos o namespace no ancestral comum para a parte correspondente - geralmente BasePresenter. Faremos isso estendendo o método [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()]: ```php public function checkRequirements($element): void @@ -256,10 +255,10 @@ public function checkRequirements($element): void ``` -Autenticadores Múltiplos .[#toc-multiple-authenticators] --------------------------------------------------------- +Múltiplos autenticadores +------------------------ -Dividir uma aplicação em segmentos com autenticação independente geralmente requer autenticadores diferentes. Entretanto, o registro de duas classes que implementam o Authenticator em serviços de configuração acionaria um erro porque a Nette não saberia qual delas deveria estar [ligada automaticamente |dependency-injection:autowiring] ao objeto `Nette\Security\User`. É por isso que devemos limitar a auto-cablagem para eles com `autowired: self` para que ela seja ativada somente quando sua classe for especificamente solicitada: +A divisão da aplicação em partes com login independente geralmente requer também autenticadores diferentes. No entanto, se registrássemos duas classes implementando Authenticator na configuração de serviços, o Nette não saberia qual delas atribuir automaticamente ao objeto `Nette\Security\User` e exibiria um erro. Portanto, precisamos restringir o [autowiring |dependency-injection:autowiring] para os autenticadores para que funcione apenas quando alguém solicitar uma classe específica, por exemplo, FrontAuthenticator, o que conseguimos escolhendo `autowired: self`: ```neon services: @@ -278,7 +277,7 @@ class SignPresenter extends Nette\Application\UI\Presenter } ``` -Só precisamos definir nosso autenticador para o objeto Usuário antes de chamar o método de [login() |api:Nette\Security\User::login()] que normalmente significa no formulário de retorno de chamada: +Definiremos o autenticador do objeto User antes de chamar o método [login() |api:Nette\Security\User::login()], então geralmente no código do formulário que o loga: ```php $form->onSuccess[] = function (Form $form, \stdClass $data) { diff --git a/security/pt/authorization.texy b/security/pt/authorization.texy index 1c9df7a82c..ce0dba8a5c 100644 --- a/security/pt/authorization.texy +++ b/security/pt/authorization.texy @@ -1,48 +1,48 @@ -Controle de acesso (Autorização) -******************************** +Verificação de permissões (Autorização) +*************************************** .[perex] -A autorização determina se um usuário tem privilégios suficientes, por exemplo, para acessar um recurso específico ou para executar uma ação. A autorização pressupõe uma autenticação prévia bem sucedida, ou seja, que o usuário está logado. +A autorização verifica se um usuário tem permissões suficientes, por exemplo, para acessar um determinado recurso ou para executar alguma ação. A autorização pressupõe autenticação prévia bem-sucedida, ou seja, que o usuário esteja logado. -→ [Instalação e requisitos |@home#Installation] +→ [Instalação e requisitos |@home#Instalação] -Nos exemplos, usaremos um objeto da classe [api:Nette\Security\User], que representa o usuário atual e que você obtém ao passá-lo usando a [injeção de dependência |dependency-injection:passing-dependencies]. Nos apresentadores, basta ligar para `$user = $this->getUser()`. +Nos exemplos, usaremos o objeto da classe [api:Nette\Security\User], que representa o usuário atual e ao qual você pode acessar solicitando-o através de [injeção de dependência |dependency-injection:passing-dependencies]. Nos presenters, basta chamar `$user = $this->getUser()`. -Para sites muito simples com administração, onde os direitos do usuário não são diferenciados, é possível utilizar o método já conhecido como critério de autorização `isLoggedIn()`. Em outras palavras: uma vez que um usuário esteja logado, ele tem permissões para todas as ações e vice-versa. +Para sites muito simples com administração, onde as permissões dos usuários não são diferenciadas, é possível usar o método já conhecido `isLoggedIn()` como critério de autorização. Em outras palavras: assim que o usuário está logado, ele tem todas as permissões e vice-versa. ```php if ($user->isLoggedIn()) { // o usuário está logado? - deleteItem(); // se estiver, ele pode excluir um item + deleteItem(); // então ele tem permissão para a operação } ``` -Papéis .[#toc-roles] --------------------- +Papéis +------ -O objetivo das funções é oferecer uma gestão de permissão mais precisa e permanecer independente do nome do usuário. Assim que o usuário faz o login, lhe é atribuído um ou mais papéis. Os papéis em si podem ser simples cordas, por exemplo, `admin`, `member`, `guest`, etc. Eles são especificados no segundo argumento do construtor `SimpleIdentity`, seja como uma string ou como uma array. +O objetivo dos papéis é oferecer um controle de permissões mais preciso e permanecer independente do nome de usuário. A cada usuário, logo ao fazer login, atribuímos um ou mais papéis nos quais ele atuará. Os papéis podem ser strings simples, por exemplo, `admin`, `member`, `guest`, etc. Eles são indicados como o segundo parâmetro do construtor `SimpleIdentity`, seja como uma string ou um array de strings - papéis. -Como critério de autorização, utilizaremos agora o método `isInRole()`, que verifica se o usuário está na função em questão: +Como critério de autorização, agora usaremos o método `isInRole()`, que informa se o usuário está atuando no papel especificado: ```php -if ($user->isInRole('admin')) { // a função administrativa é atribuída ao usuário? - deleteItem(); // se for o caso, ele pode apagar um item +if ($user->isInRole('admin')) { // o usuário está no papel de admin? + deleteItem(); // então ele tem permissão para a operação } ``` -Como você já sabe, o log out do usuário não apaga sua identidade. Assim, o método `getIdentity()` ainda devolve o objeto `SimpleIdentity`, incluindo todas as funções concedidas. O Nette Framework adere ao princípio de "menos código, mais segurança", portanto, quando você está verificando funções, não precisa verificar se o usuário também está logado. O método `isInRole()` funciona com ** funções efetivas**, ou seja, se o usuário estiver logado, funções atribuídas à identidade são usadas, se ele não estiver logado, uma função especial automática `guest` é usada em seu lugar. +Como você já sabe, após o logout do usuário, sua identidade não precisa ser apagada. Ou seja, o método `getIdentity()` continua retornando o objeto `SimpleIdentity`, incluindo todos os papéis concedidos. O Nette Framework adota o princípio "menos código, mais segurança", onde menos escrita leva a um código mais seguro, portanto, ao verificar os papéis, você não precisa verificar também se o usuário está logado. O método `isInRole()` trabalha com **papéis efetivos,** ou seja, se o usuário estiver logado, baseia-se nos papéis indicados na identidade; se não estiver logado, tem automaticamente o papel especial `guest`. -Autorizador .[#toc-authorizator] --------------------------------- +Autorizador +----------- -Além das funções, introduziremos os termos recurso e operação: +Além dos papéis, introduziremos também os conceitos de recurso e operação: -- **role** é um atributo do usuário - por exemplo, moderador, editor, visitante, usuário registrado, administrador, ... -- **resource*** é uma unidade lógica da aplicação - artigo, página, usuário, item de menu, enquete, apresentador, ... -- **operação*** é uma atividade específica, que o usuário pode ou não fazer com *recurso* - ver, editar, apagar, votar, ... +- **papel** é uma propriedade do usuário - por exemplo, moderador, editor, visitante, usuário registrado, administrador... +- **recurso** (*resource*) é algum elemento lógico do site - artigo, página, usuário, item de menu, enquete, presenter, ... +- **operação** (*operation*) é alguma atividade específica que o usuário pode ou não fazer com o recurso - por exemplo, excluir, editar, criar, votar, ... -Um autorizador é um objeto que decide se um determinado *role* tem permissão para realizar uma determinada *operação* com *recurso* específico. É um objeto que implementa a interface [api:Nette\Security\Authorizator] com apenas um método `isAllowed()`: +O autorizador é um objeto que decide se um determinado *papel* tem permissão para realizar uma determinada *operação* com um determinado *recurso*. É um objeto que implementa a interface [api:Nette\Security\Authorizator] com um único método `isAllowed()`: ```php class MyAuthorizator implements Nette\Security\Authorizator @@ -63,50 +63,50 @@ class MyAuthorizator implements Nette\Security\Authorizator } ``` -Acrescentamos o autorizador à configuração [como um serviço |dependency-injection:services] do recipiente DI: +Adicionaremos o autorizador à configuração [como um serviço|dependency-injection:services] do contêiner DI: ```neon services: - MyAuthorizator ``` -E o seguinte é um exemplo de uso. Note que desta vez chamamos o método `Nette\Security\User::isAllowed()`, e não o do autorizador, portanto não há o primeiro parâmetro `$role`. Este método chama `MyAuthorizator::isAllowed()` sequencialmente para todas as funções do usuário e retorna verdadeiro se pelo menos uma delas tiver permissão. +E segue um exemplo de uso. Atenção, desta vez chamamos o método `Nette\Security\User::isAllowed()`, não o autorizador, então não há o primeiro parâmetro `$role`. Este método chama `MyAuthorizator::isAllowed()` sequencialmente para todos os papéis do usuário e retorna true se pelo menos um deles tiver permissão. ```php -if ($user->isAllowed('file')) { // o usuário tem permissão para fazer tudo com o recurso 'arquivo'? +if ($user->isAllowed('file')) { // o usuário pode fazer qualquer coisa com o recurso 'file'? useFile(); } -if ($user->isAllowed('file', 'delete')) { // o usuário tem permissão para excluir um 'arquivo' de recurso? +if ($user->isAllowed('file', 'delete')) { // pode realizar 'delete' no recurso 'file'? deleteFile(); } ``` -Ambos os argumentos são opcionais e seu valor padrão significa *todas as coisas*. +Ambos os parâmetros são opcionais, o valor padrão `null` significa *qualquer coisa*. -Permissão ACL .[#toc-permission-acl] ------------------------------------- +Permission ACL +-------------- -Nette vem com uma implementação integrada do autorizador, a classe [api:Nette\Security\Permission], que oferece uma camada leve e flexível de ACL (Access Control List) para permissão e controle de acesso. Quando trabalhamos com esta classe, definimos as funções, os recursos e as permissões individuais. E as funções e recursos podem formar hierarquias. Para explicar, vamos mostrar um exemplo de uma aplicação web: +O Nette vem com uma implementação integrada de autorizador, a classe [api:Nette\Security\Permission] que fornece ao programador uma camada ACL (Access Control List) leve e flexível para gerenciar permissões e acessos. O trabalho com ela consiste em definir papéis, recursos e permissões individuais. Onde papéis e recursos permitem criar hierarquias. Para explicar, mostraremos um exemplo de aplicação web: -- `guest`: visitante que não está logado, autorizado a ler e navegar na parte pública da web, ou seja, ler artigos, comentar e votar em enquetes -- `registered`: usuário logado, que pode, além dos comentários do post +- `guest`: visitante não logado, que pode ler e navegar na parte pública do site, ou seja, ler artigos, comentários e votar em enquetes +- `registered`: usuário registrado logado, que além disso pode comentar - `admin`: pode gerenciar artigos, comentários e enquetes -Assim, definimos certas funções (`guest`, `registered` e `admin`) e mencionamos recursos (`article`, `comments`, `poll`), aos quais os usuários podem acessar ou tomar medidas (`view`, `vote`, `add`, `edit`). +Definimos, portanto, certos papéis (`guest`, `registered` e `admin`) e mencionamos recursos (`article`, `comment`, `poll`), aos quais usuários com algum papel podem acessar ou realizar certas operações (`view`, `vote`, `add`, `edit`). -Criamos uma instância da classe Permission e definimos **roles***. É possível utilizar a herança de funções, o que garante que, por exemplo, um usuário com uma função `admin` possa fazer o que um visitante comum do site pode fazer (e, claro, mais). +Criamos uma instância da classe Permission e definimos os **papéis**. É possível usar a chamada herança de papéis, que garante que, por exemplo, um usuário com o papel de administrador (`admin`) possa fazer também o que um visitante comum do site faz (e, claro, mais). ```php $acl = new Nette\Security\Permission; $acl->addRole('guest'); -$acl->addRole('registered', 'guest'); // 'registrado' herda de 'convidado'; -$acl->addRole('admin', 'registered'); // e 'admin' herda de 'registrado'. +$acl->addRole('registered', 'guest'); // 'registered' herda de 'guest' +$acl->addRole('admin', 'registered'); // e dele herda 'admin' ``` -Vamos agora definir uma lista de **recursos*** que os usuários podem acessar: +Agora definimos também a lista de **recursos**, aos quais os usuários podem acessar. ```php $acl->addResource('article'); @@ -114,49 +114,49 @@ $acl->addResource('comment'); $acl->addResource('poll'); ``` -Os recursos também podem utilizar a herança, por exemplo, podemos acrescentar `$acl->addResource('perex', 'article')`. +Recursos também podem usar herança, seria possível, por exemplo, especificar `$acl->addResource('perex', 'article')`. -E agora a coisa mais importante. Vamos definir entre eles **regras** determinando quem pode fazer o quê: +E agora o mais importante. Definimos entre eles as regras que determinam quem pode fazer o quê com o quê: ```php -// tudo é negado agora +// inicialmente, ninguém pode fazer nada -// deixe o convidado ver artigos, comentários e enquetes +// que o guest possa visualizar artigos, comentários e enquetes $acl->allow('guest', ['article', 'comment', 'poll'], 'view'); -// e também votar nas urnas +// e nas enquetes, além disso, votar $acl->allow('guest', 'poll', 'vote'); -// o registrado herda as permissões do guesta, também o deixaremos comentar +// registrado herda direitos de guest, damos a ele adicionalmente o direito de comentar $acl->allow('registered', 'comment', 'add'); -// o administrador pode visualizar e editar qualquer coisa +// administrador pode visualizar e editar qualquer coisa $acl->allow('admin', $acl::All, ['view', 'edit', 'add']); ``` -E se quisermos **prevenir** alguém de acessar um recurso? +E se quisermos **impedir** alguém de acessar um determinado recurso? ```php -// o administrador não pode editar pesquisas, o que seria antidemocrático. +// administrador não pode editar enquetes, isso seria antidemocrático $acl->deny('admin', 'poll', 'edit'); ``` -Agora, quando tivermos criado o conjunto de regras, podemos simplesmente solicitar as consultas de autorização: +Agora, quando temos a lista de regras criada, podemos simplesmente fazer consultas de autorização: ```php -// os convidados podem ver os artigos? +// guest pode visualizar artigos? $acl->isAllowed('guest', 'article', 'view'); // true -// o convidado pode editar um artigo? +// guest pode editar artigos? $acl->isAllowed('guest', 'article', 'edit'); // false -// os convidados podem votar nas urnas? +// guest pode votar em enquetes? $acl->isAllowed('guest', 'poll', 'vote'); // true -// pode o convidado acrescentar comentários? +// guest pode comentar? $acl->isAllowed('guest', 'comment', 'add'); // false ``` -O mesmo se aplica a um usuário registrado, mas ele também pode comentar: +O mesmo se aplica ao usuário registrado, que, no entanto, também pode comentar: ```php $acl->isAllowed('registered', 'article', 'view'); // true @@ -164,7 +164,7 @@ $acl->isAllowed('registered', 'comment', 'add'); // true $acl->isAllowed('registered', 'comment', 'edit'); // false ``` -O administrador pode editar tudo, exceto as pesquisas: +O administrador pode editar tudo, exceto as enquetes: ```php $acl->isAllowed('admin', 'poll', 'vote'); // true @@ -172,7 +172,7 @@ $acl->isAllowed('admin', 'poll', 'edit'); // false $acl->isAllowed('admin', 'comment', 'edit'); // true ``` -As permissões também podem ser avaliadas dinamicamente e podemos deixar a decisão para nosso próprio retorno de chamada, ao qual todos os parâmetros são passados: +As permissões também podem ser avaliadas dinamicamente e podemos deixar a decisão para um callback próprio, ao qual todos os parâmetros são passados: ```php $assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { @@ -182,7 +182,7 @@ $assertion = function (Permission $acl, string $role, string $resource, string $ $acl->allow('registered', 'comment', null, $assertion); ``` -Mas como resolver uma situação em que os nomes dos papéis e recursos não são suficientes, ou seja, gostaríamos de definir que, por exemplo, um papel `registered` pode editar um recurso `article` somente se for seu autor? Usaremos objetos em vez de cordas, o papel será o objeto [api:Nette\Security\Role] e a fonte [api:Nette\Security\Resource]. Seus métodos `getRoleId()` resp. `getResourceId()` devolverão as cordas originais: +Mas como resolver, por exemplo, a situação em que apenas os nomes dos papéis e recursos não são suficientes, mas gostaríamos de definir que, por exemplo, o papel `registered` pode editar o recurso `article` apenas se for seu autor? Em vez de strings, usaremos objetos, o papel será um objeto [api:Nette\Security\Role] e o recurso um objeto [api:Nette\Security\Resource]. Seus métodos `getRoleId()` resp. `getResourceId()` retornarão as strings originais: ```php class Registered implements Nette\Security\Role @@ -207,19 +207,19 @@ class Article implements Nette\Security\Resource } ``` -E agora vamos criar uma regra: +E agora criamos a regra: ```php $assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { - $role = $acl->getQueriedRole(); // object Registered - $resource = $acl->getQueriedResource(); // object Article + $role = $acl->getQueriedRole(); // objeto Registered + $resource = $acl->getQueriedResource(); // objeto Article return $role->id === $resource->authorId; }; $acl->allow('registered', 'article', 'edit', $assertion); ``` -O ACL é consultado através da passagem de objetos: +E a consulta à ACL é realizada passando os objetos: ```php $user = new Registered(/* ... */); @@ -227,7 +227,7 @@ $article = new Article(/* ... */); $acl->isAllowed($user, $article, 'edit'); ``` -Um papel pode herdar um ou mais papéis. Mas o que acontece, se um antepassado tem determinada ação permitida e o outro a negou? Então o *peso do papel* entra em jogo - o último papel da série de papéis a herdar tem o maior peso, primeiro o mais baixo: +Um papel pode herdar de outro papel ou de múltiplos papéis. Mas o que acontece se um ancestral tiver a ação proibida e outro permitida? Quais serão os direitos do descendente? Isso é determinado pelo peso do papel - o último papel listado na lista de ancestrais tem o maior peso, o primeiro papel listado tem o menor. Isso é mais claro no exemplo: ```php $acl = new Nette\Security\Permission; @@ -239,22 +239,22 @@ $acl->addResource('backend'); $acl->allow('admin', 'backend'); $acl->deny('guest', 'backend'); -// exemplo A: o papel administrador tem menos peso que o papel convidado +// caso A: o papel admin tem menor peso que o papel guest $acl->addRole('john', ['admin', 'guest']); $acl->isAllowed('john', 'backend'); // false -// exemplo B: o papel administrativo tem maior peso do que o papel convidado +// caso B: o papel admin tem maior peso que guest $acl->addRole('mary', ['guest', 'admin']); $acl->isAllowed('mary', 'backend'); // true ``` -Os papéis e recursos também podem ser retirados (`removeRole()`, `removeResource()`), as regras também podem ser revertidas (`removeAllow()`, `removeDeny()`). O conjunto de todos os papéis dos pais diretos retorna `getRoleParents()`. Se duas entidades herdam uma da outra retorna `roleInheritsFrom()` e `resourceInheritsFrom()`. +Papéis e recursos também podem ser removidos (`removeRole()`, `removeResource()`), regras também podem ser revertidas (`removeAllow()`, `removeDeny()`). O array de todos os papéis pais diretos é retornado por `getRoleParents()`, se duas entidades herdam uma da outra é retornado por `roleInheritsFrom()` e `resourceInheritsFrom()`. -Adicionar como um serviço .[#toc-add-as-a-service] --------------------------------------------------- +Adição como serviços +-------------------- -Precisamos adicionar a ACL criada por nós à configuração como um serviço para que ela possa ser usada pelo objeto `$user`, ou seja, para que possamos usar em código, por exemplo `$user->isAllowed('article', 'view')`. Para este fim, escreveremos uma fábrica para ela: +Precisamos passar nossa ACL criada para a configuração como um serviço, para que o objeto `$user` comece a usá-la, ou seja, para que seja possível usar no código, por exemplo, `$user->isAllowed('article', 'view')`. Para esse fim, escreveremos uma fábrica para ela: ```php namespace App\Model; @@ -272,14 +272,14 @@ class AuthorizatorFactory } ``` -E vamos acrescentá-la à configuração: +E a adicionaremos à configuração: ```neon services: - App\Model\AuthorizatorFactory::create ``` -Nos apresentadores, você pode então verificar as permissões no método `startup()`, por exemplo: +Nos presenters, você pode então verificar as permissões, por exemplo, no método `startup()`: ```php protected function startup() diff --git a/security/pt/configuration.texy b/security/pt/configuration.texy index b953732102..a1335847dc 100644 --- a/security/pt/configuration.texy +++ b/security/pt/configuration.texy @@ -1,71 +1,85 @@ -Configurando o controle de acesso -********************************* +Configuração de permissões de acesso +************************************ .[perex] -Visão geral das opções de configuração para a Segurança Nette. +Visão geral das opções de configuração para Nette Security. -Se você não estiver usando toda a estrutura, mas apenas esta biblioteca, leia [como carregar a configuração |bootstrap:]. +Se você não usa o framework completo, mas apenas esta biblioteca, leia [como carregar a configuração|bootstrap:]. -Você pode definir uma lista de usuários na configuração para criar um [autenticador simples |authentication] (`Nette\Security\SimpleAuthenticator`). Como as senhas são legíveis na configuração, esta solução é apenas para fins de teste. +Na configuração, é possível definir uma lista de usuários e, assim, criar um [autenticador simples|authentication] (`Nette\Security\SimpleAuthenticator`). Como as senhas são especificadas na configuração em formato legível, esta solução é adequada apenas para fins de teste. ```neon security: - # mostra o painel do usuário no Tracy Bar? - debugger: ... # (bool) padrão a verdadeiro + # exibir o painel do usuário na Tracy Bar? + debugger: ... # (bool) padrão é true users: # nome: senha - johndoe: secreto123 + frantisek: tajneheslo - # nome, senha, função e outros dados disponíveis na identidade - janedoe: - password: secret123 + # nome, senha, papel e outros dados disponíveis na identidade + dobrota: + password: tajneheslo roles: [admin] data: ... ``` -Você também pode definir papéis e recursos para criar uma base para [autorizador |authorization] (`Nette\Security\Permission`): +Além disso, é possível definir papéis e recursos e, assim, criar a base para um [autorizador|authorization] (`Nette\Security\Permission`): ```neon security: roles: guest: - registered: [guest] # heranças registradas de convidado - admin: [registered] # e admin herda de registrado + registered: [guest] # registered herda de guest + admin: [registered] # e dele herda admin resources: article: - comment: [article] # recursos herdados do artigo + comment: [article] # recurso herda de article poll: ``` -Armazenamento do usuário .[#toc-user-storage] ---------------------------------------------- +Armazenamento +------------- -Você pode configurar como armazenar informações sobre o usuário logado: +É possível configurar como armazenar informações sobre o usuário logado: ```neon security: authentication: # após quanto tempo de inatividade o usuário será desconectado - expiration: 30 minutes # (string) padrão não é definido + expiration: 30 minutes # (string) padrão não está definido # onde armazenar informações sobre o usuário logado - storage: session # (sessão|cookie) padrão é sessão + storage: session # (session|cookie) padrão é session ``` -Se você escolher `cookie` como seu repositório, você também pode definir as seguintes opções: +Se você escolher `cookie` como armazenamento, pode definir ainda estas opções: ```neon security: authentication: # nome do cookie - cookieName: userId # (string) výchozí je userid + cookieName: userId # (string) padrão é userid - # que hospeda é permitido receber o cookie + # domínios que aceitam o cookie cookieDomain: 'example.com' # (string|domain) - # restrições ao acessar pedidos de origem cruzada - cookieSamesite: None # (Strict|Lax|None) tem como padrão Lax + # restrição ao acessar de outro domínio + cookieSamesite: None # (Strict|Lax|None) padrão é Lax ``` + + +Serviços DI +----------- + +Estes serviços são adicionados ao contêiner de DI: + +| Nome | Tipo | Descrição +|------------------------------------------------------------------------------------- +| `security.authenticator` | [api:Nette\Security\Authenticator] | [autenticador|authentication] +| `security.authorizator` | [api:Nette\Security\Authorizator] | [autorizador|authorization] +| `security.passwords` | [api:Nette\Security\Passwords] | [hashing de senhas|passwords] +| `security.user` | [api:Nette\Security\User] | usuário atual +| `security.userStorage` | [api:Nette\Security\UserStorage] | [#armazenamento] diff --git a/security/pt/passwords.texy b/security/pt/passwords.texy index 0bc98bc396..767b481b9a 100644 --- a/security/pt/passwords.texy +++ b/security/pt/passwords.texy @@ -1,12 +1,12 @@ -Hashing de senha -**************** +Hashing de senhas +***************** .[perex] -Para gerenciar a segurança de nossos usuários, nunca armazenamos suas senhas em formato de texto simples, em vez disso, armazenamos o hash da senha. O hash não é uma operação reversível, a senha não pode ser recuperada. No entanto, a senha pode ser quebrada e para tornar a quebra tão difícil quanto possível, temos que usar um algoritmo seguro. A classe [api:Nette\Security\Passwords] nos ajudará com isso. +Para garantir a segurança de nossos usuários, não armazenamos suas senhas em formato legível, mas armazenamos apenas uma impressão digital (o chamado hash). A partir da impressão digital, não é possível reconstruir a forma original da senha. É importante usar um algoritmo seguro para criar a impressão digital. A classe [api:Nette\Security\Passwords] nos ajuda com isso. -→ [Instalação e requisitos |@home#Installation] +→ [Instalação e requisitos |@home#Instalação] -A estrutura adiciona automaticamente um serviço `Nette\Security\Passwords` ao recipiente DI sob o nome `security.passwords`, que você obtém passando-o usando a [injeção de dependência |dependency-injection:passing-dependencies]: +O framework adiciona automaticamente ao contêiner DI um serviço do tipo `Nette\Security\Passwords` com o nome `security.passwords`, ao qual você pode acessar solicitando-o através de [injeção de dependência |dependency-injection:passing-dependencies]. ```php use Nette\Security\Passwords; @@ -24,44 +24,44 @@ class Foo __construct($algo=PASSWORD_DEFAULT, array $options=[]): string .[method] ======================================================================== -Escolhe qual [algoritmo seguro |https://www.php.net/manual/en/password.constants.php] é usado para o hashing e como configurá-lo. +Escolhemos qual [algoritmo seguro|https://www.php.net/manual/en/password.constants.php] usar para gerar o hash e configuramos seus parâmetros. -O padrão é `PASSWORD_DEFAULT`, então a escolha do algoritmo é deixada para PHP. O algoritmo pode mudar nas versões mais recentes do PHP quando algoritmos mais novos e mais fortes são suportados. Portanto, você deve estar ciente de que o comprimento do hash resultante pode mudar. Portanto, você deve armazenar o hash resultante de uma forma que possa armazenar caracteres suficientes, 255 é a largura recomendada. +Por padrão, usa-se `PASSWORD_DEFAULT`, ou seja, a escolha do algoritmo é deixada para o PHP. O algoritmo pode mudar em versões mais recentes do PHP, se algoritmos de hashing mais novos e mais fortes aparecerem. Portanto, você deve estar ciente de que o comprimento do hash resultante pode mudar, e você deve armazená-lo de forma que possa acomodar caracteres suficientes, 255 é a largura recomendada. -É assim que você usaria o algoritmo bcrypt e mudaria a velocidade do hashing usando o parâmetro de custo a partir do padrão 10. No ano 2020, com custo 10, o hashing de uma senha leva cerca de 80ms, custo 11 leva 160ms, custo 12 e depois 320ms, a escala é logarítmica. Quanto mais lento, melhor, o custo 10-12 é considerado lento o suficiente para a maioria dos casos de uso: +Exemplo de configuração da velocidade de hashing com o algoritmo bcrypt alterando o parâmetro cost: (em 2020, o padrão é 10, o hashing da senha leva cerca de 80ms, para cost 11 é cerca de 160ms, para cost 12 cerca de 320ms, quanto mais lento, melhor a proteção, sendo que a velocidade 10-12 já é considerada proteção suficiente) ```php -// vamos hash passwords com 2^12 (2^cost) iterações do algoritmo bcrypt +// vamos hashear as senhas com 2^12 (2^cost) iterações do algoritmo bcrypt $passwords = new Passwords(PASSWORD_BCRYPT, ['cost' => 12]); ``` -Com injeção de dependência: +Usando injeção de dependência: ```neon services: security.passwords: Nette\Security\Passwords(::PASSWORD_BCRYPT, [cost: 12]) ``` -hash(string $passwords): string .[method] -========================================= +hash(string $password): string .[method] +======================================== Gera o hash da senha. ```php -$res = $passwords->hash($password); // Hashes a password +$res = $passwords->hash($password); // Gera o hash da senha ``` -O resultado `$res` é uma string que, além do próprio hash, contém o identificador do algoritmo utilizado, suas configurações e sal criptográfico (dados aleatórios para garantir que um hash diferente seja gerado para a mesma senha). Portanto, é retrocompatível, por exemplo, se você alterar os parâmetros, os hashes armazenados usando as configurações anteriores podem ser verificados. Todo este resultado é armazenado no banco de dados, portanto não há necessidade de armazenar sal ou configurações separadamente. +O resultado `$res` é uma string que, além do próprio hash, contém também o identificador do algoritmo usado, suas configurações e o sal criptográfico (dados aleatórios que garantem que para a mesma senha seja gerado um hash diferente). É, portanto, retrocompatível; se, por exemplo, você alterar os parâmetros, os hashes armazenados usando as configurações anteriores ainda poderão ser verificados. Todo esse resultado é armazenado no banco de dados, portanto, não é necessário armazenar o sal ou as configurações separadamente. verify(string $password, string $hash): bool .[method] ====================================================== -Descobre, se a senha dada corresponde ao hash dado. Obtenha o `$hash` do banco de dados por nome de usuário ou endereço de e-mail. +Verifica se a senha fornecida corresponde à impressão digital fornecida. Obtenha `$hash` do banco de dados de acordo com o nome de usuário ou endereço de e-mail fornecido. ```php if ($passwords->verify($password, $hash)) { - // Corrigir senha + // senha correta } ``` @@ -69,13 +69,13 @@ if ($passwords->verify($password, $hash)) { needsRehash(string $hash): bool .[method] ========================================= -Descobre se o hash corresponde às opções dadas no construtor. +Verifica se o hash corresponde às opções especificadas no construtor. -Use este método quando, por exemplo, você estiver mudando os parâmetros de hashing. A verificação de senha utilizará parâmetros armazenados com o hash e se `needsRehash()` retornar verdadeiro, você terá que calcular o hash novamente, desta vez com os parâmetros atualizados, e novamente armazená-lo no banco de dados. Isto assegura que os hashes de senhas serão automaticamente "atualizados" quando os usuários estiverem fazendo o login. +É útil usar quando, por exemplo, você altera a velocidade de hashing. A verificação ocorre de acordo com as configurações armazenadas e, se `needsRehash()` retornar `true`, é necessário criar novamente o hash, desta vez com os novos parâmetros, e armazená-lo novamente no banco de dados. Desta forma, os hashes armazenados são "atualizados" automaticamente durante o login dos usuários. ```php if ($passwords->needsRehash($hash)) { $hash = $passwords->hash($password); - // armazenar $hash para banco de dados + // armazenar $hash no banco de dados } ``` diff --git a/security/ro/@home.texy b/security/ro/@home.texy index b6d135c6e5..aa3b01bf84 100644 --- a/security/ro/@home.texy +++ b/security/ro/@home.texy @@ -1,14 +1,14 @@ -Securitate -********** +Nette Security +************** .[perex] -Pachetul `nette/security` se ocupă de [autentificarea utilizatorilor |authentication], [controlul accesului |authorization] și [hashing-ul parolelor |passwords]. +Pachetul `nette/security` se ocupă de [autentificarea utilizatorilor |authentication], [verificarea permisiunilor |authorization] și [hash-uirea parolelor |passwords]. -Instalare .[#toc-installation] ------------------------------- +Instalare +--------- -Descărcați și instalați pachetul folosind [Composer |best-practices:composer]: +Descărcați și instalați biblioteca folosind [Composer|best-practices:composer]: ```shell composer require nette/security diff --git a/security/ro/@left-menu.texy b/security/ro/@left-menu.texy index 194e5c141b..030c7be167 100644 --- a/security/ro/@left-menu.texy +++ b/security/ro/@left-menu.texy @@ -1,7 +1,7 @@ Nette Security ************** -- [Prezentare generală |@home] -- [Autentificare |Authentication] -- [Autorizare |Authorization] -- [Hașurarea parolei |passwords] -- [Configurație |Configuration] +- [Introducere |@home] +- [Autentificare |authentication] +- [Autorizare |authorization] +- [Hash-uirea parolelor |passwords] +- [Configurare |configuration] diff --git a/security/ro/@meta.texy b/security/ro/@meta.texy new file mode 100644 index 0000000000..9c744b37d6 --- /dev/null +++ b/security/ro/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentație Nette}} diff --git a/security/ro/authentication.texy b/security/ro/authentication.texy index a7515b94f3..da5c05c2d8 100644 --- a/security/ro/authentication.texy +++ b/security/ro/authentication.texy @@ -3,46 +3,46 @@ Autentificarea utilizatorilor <div class=perex> -Aplicațiile web mici sau deloc nu au nevoie de un mecanism de autentificare a utilizatorilor sau de verificare a privilegiilor acestora. În acest capitol, vom vorbi despre: +Aproape nicio aplicație web nu se poate lipsi de un mecanism de autentificare a utilizatorilor și de verificare a permisiunilor utilizatorilor. În acest capitol vom vorbi despre: -- autentificarea și deconectarea utilizatorului -- autentificatori și autorizatori personalizați +- autentificarea și deconectarea utilizatorilor +- autentificatoare personalizate </div> -→ [Instalare și cerințe |@home#Installation] +→ [Instalare și cerințe |@home#Instalare] -În exemple, vom folosi un obiect din clasa [api:Nette\Security\User], care reprezintă utilizatorul curent și pe care îl obținem prin trecerea acestuia cu ajutorul [injecției de dependență |dependency-injection:passing-dependencies]. În prezentatori, este suficient să apelați `$user = $this->getUser()`. +În exemple vom folosi obiectul clasei [api:Nette\Security\User], care reprezintă utilizatorul curent și la care ajungeți solicitându-l prin [injecția de dependențe |dependency-injection:passing-dependencies]. În presenteri este suficient doar să apelați `$user = $this->getUser()`. -Autentificare .[#toc-authentication] -==================================== +Autentificare +============= -Autentificarea înseamnă **conectarea utilizatorului**, adică procesul în timpul căruia este verificată identitatea unui utilizator. De obicei, utilizatorul se identifică folosind numele de utilizator și parola. Verificarea este efectuată de așa-numitul [autentificator |#authenticator]. În cazul în care autentificarea eșuează, se aruncă `Nette\Security\AuthenticationException`. +Autentificarea se referă la **conectarea utilizatorilor**, adică procesul prin care se verifică dacă utilizatorul este într-adevăr cine pretinde a fi. De obicei, se dovedește prin nume de utilizator și parolă. Verificarea este efectuată de așa-numitul [#autentificator]. Dacă autentificarea eșuează, se aruncă `Nette\Security\AuthenticationException`. ```php try { $user->login($username, $password); } catch (Nette\Security\AuthenticationException $e) { - $this->flashMessage('The username or password you entered is incorrect.'); + $this->flashMessage('Numele de utilizator sau parola este incorectă.'); } ``` -Iată cum se deconectează utilizatorul: +În acest mod deconectați utilizatorul: ```php $user->logout(); ``` -Și verificarea dacă utilizatorul este logat: +Și verificarea dacă este conectat: ```php -echo $user->isLoggedIn() ? 'yes' : 'no'; +echo $user->isLoggedIn() ? 'da' : 'nu'; ``` -Simplu, nu? Și toate aspectele legate de securitate sunt gestionate de Nette pentru dumneavoastră. +Foarte simplu, nu-i așa? Și toate aspectele de securitate sunt gestionate de Nette pentru dvs. -În presenter, puteți verifica autentificarea în metoda `startup()` și puteți redirecționa un utilizator care nu s-a autentificat către pagina de autentificare. +În presenteri puteți verifica autentificarea în metoda `startup()` și redirecționa utilizatorul neconectat către pagina de autentificare. ```php protected function startup() @@ -55,39 +55,38 @@ protected function startup() ``` -Expirare .[#toc-expiration] -=========================== +Expirare +======== -Autentificarea utilizatorului expiră odată cu [expirarea depozitului |#Storage for Logged User], care este de obicei o sesiune (a se vedea setarea de [expirare a sesiunii |http:configuration#session] ). -Cu toate acestea, puteți seta, de asemenea, un interval de timp mai scurt după care utilizatorul este deconectat. În acest scop, se utilizează metoda `setExpiration()`, care este apelată înainte de `login()`. Furnizați ca parametru un șir de caractere cu un timp relativ: +Autentificarea utilizatorului expiră odată cu [expirarea stocării |#Stocarea utilizatorului autentificat], care este de obicei sesiunea (vezi setarea [expirarea sesiunii |http:configuration#Sesiune]). Se poate seta însă și un interval de timp mai scurt, după expirarea căruia utilizatorul va fi deconectat. Pentru aceasta servește metoda `setExpiration()`, care se apelează înainte de `login()`. Ca parametru, introduceți un șir cu timpul relativ: ```php // autentificarea expiră după 30 de minute de inactivitate $user->setExpiration('30 minutes'); -// anulați setul de expirare +// anularea expirării setate $user->setExpiration(null); ``` -Metoda `$user->getLogoutReason()` spune dacă utilizatorul a fost deconectat deoarece intervalul de timp a expirat. Aceasta returnează fie constanta `Nette\Security\UserStorage::LogoutInactivity` dacă timpul a expirat, fie `UserStorage::LogoutManual` atunci când a fost apelată metoda `logout()`. +Dacă utilizatorul a fost deconectat din cauza expirării intervalului de timp, o indică metoda `$user->getLogoutReason()`, care returnează fie constanta `Nette\Security\UserStorage::LogoutInactivity` (a expirat limita de timp), fie `UserStorage::LogoutManual` (deconectat prin metoda `logout()`). -Autentificator .[#toc-authenticator] -==================================== +Autentificator +============== -Este un obiect care verifică datele de autentificare, adică, de obicei, numele și parola. Implementarea trivială este clasa [api:Nette\Security\SimpleAuthenticator], care poate fi definită în [configurare |configuration]: +Este un obiect care verifică datele de autentificare, adică de obicei numele și parola. O formă trivială este clasa [api:Nette\Security\SimpleAuthenticator], pe care o putem defini în [configurație |configuration]: ```neon security: users: # nume: parola - johndoe: secret123 - kathy: evenmoresecretpassword + frantisek: parolasecreta + katka: parolasiamaisecreta ``` -Această soluție este mai potrivită pentru scopuri de testare. Vă vom arăta cum să creați un autentificator care va verifica acreditările în raport cu un tabel de bază de date. +Această soluție este potrivită mai degrabă pentru scopuri de testare. Vom arăta cum să creăm un autentificator care va verifica datele de autentificare față de un tabel din baza de date. -Un autentificator este un obiect care implementează interfața [api:Nette\Security\Authenticator] cu metoda `authenticate()`. Sarcina sa este fie de a returna așa-numita [identitate |#identity], fie de a arunca o excepție `Nette\Security\AuthenticationException`. De asemenea, ar fi posibil să se furnizeze un cod de eroare de grad fin `Authenticator::IdentityNotFound` sau `Authenticator::InvalidCredential`. +Autentificatorul este un obiect care implementează interfața [api:Nette\Security\Authenticator] cu metoda `authenticate()`. Sarcina sa este fie să returneze așa-numita [#identitate], fie să arunce excepția `Nette\Security\AuthenticationException`. Ar fi posibil să se specifice și un cod de eroare pentru o diferențiere mai fină a situației apărute: `Authenticator::IdentityNotFound` și `Authenticator::InvalidCredential`. ```php use Nette; @@ -108,25 +107,25 @@ class MyAuthenticator implements Nette\Security\Authenticator ->fetch(); if (!$row) { - throw new Nette\Security\AuthenticationException('User not found.'); + throw new Nette\Security\AuthenticationException('Utilizator negăsit.'); } if (!$this->passwords->verify($password, $row->password)) { - throw new Nette\Security\AuthenticationException('Invalid password.'); + throw new Nette\Security\AuthenticationException('Parolă invalidă.'); } return new SimpleIdentity( $row->id, - $row->role, // sau o matrice de roluri + $row->role, // sau un array cu mai multe roluri ['name' => $row->username], ); } } ``` -Clasa MyAuthenticator comunică cu baza de date prin intermediul [Nette Database Explorer |database:explorer] și lucrează cu tabelul `users`, în care coloana `username` conține numele de autentificare al utilizatorului, iar coloana `password` conține [hash-ul |passwords]. După ce verifică numele și parola, aceasta returnează identitatea cu ID-ul utilizatorului, rolul (coloana `role` din tabel), pe care îl vom menționa [mai târziu |#roles], și un array cu date suplimentare (în cazul nostru, numele de utilizator). +Clasa MyAuthenticator comunică cu baza de date prin intermediul [Nette Database Explorer|database:explorer] și lucrează cu tabelul `users`, unde în coloana `username` se află numele de utilizator și în coloana `password` [amprenta parolei |passwords]. După verificarea numelui și parolei, returnează identitatea, care conține ID-ul utilizatorului, rolul său (coloana `role` din tabel), despre care vom vorbi mai mult [mai târziu |authorization#Roluri], și un array cu date suplimentare (în cazul nostru, numele de utilizator). -Vom adăuga autentificatorul la configurație [ca serviciu |dependency-injection:services] al containerului DI: +Vom adăuga autentificatorul în configurație [ca serviciu|dependency-injection:services] al containerului DI: ```neon services: @@ -137,47 +136,47 @@ services: Evenimentele $onLoggedIn, $onLoggedOut -------------------------------------- -Obiectul `Nette\Security\User` are [evenimentele |nette:glossary#Events] `$onLoggedIn` și `$onLoggedOut`, astfel încât puteți adăuga callback-uri care sunt declanșate după o autentificare reușită sau după ce utilizatorul se deconectează. +Obiectul `Nette\Security\User` are [evenimente |nette:glossary#Evenimente] `$onLoggedIn` și `$onLoggedOut`, puteți deci adăuga callback-uri care se vor invoca după autentificarea cu succes, respectiv după deconectarea utilizatorului. ```php $user->onLoggedIn[] = function () { - // utilizatorul tocmai s-a logat + // utilizatorul tocmai a fost autentificat }; ``` -Identitate .[#toc-identity] -=========================== +Identitate +========== -O identitate este un set de informații despre un utilizator care este returnat de către autentificator și care este apoi stocat într-o sesiune și recuperat folosind `$user->getIdentity()`. Astfel, putem obține id-ul, rolurile și alte date despre utilizator așa cum le-am transmis în autentificator: +Identitatea reprezintă un set de informații despre utilizator, returnat de autentificator și care se păstrează ulterior în sesiune și îl obținem folosind `$user->getIdentity()`. Putem deci obține id-ul, rolurile și alte date ale utilizatorului, așa cum le-am transmis în autentificator: ```php $user->getIdentity()->getId(); -// funcționează și prescurtarea $user->getId(); +// funcționează și scurtătura $user->getId(); $user->getIdentity()->getRoles(); -// datele utilizatorului pot fi accesate ca proprietăți +// datele utilizatorului sunt disponibile ca proprietăți // numele pe care l-am transmis în MyAuthenticator $user->getIdentity()->name; ``` -Este important de menționat că, atunci când utilizatorul se deconectează folosind `$user->logout()`, **identitatea nu este ștearsă** și este încă disponibilă. Deci, dacă identitatea există, aceasta nu garantează prin ea însăși că utilizatorul este, de asemenea, conectat. Dacă dorim să ștergem în mod explicit identitatea, trebuie să deconectăm utilizatorul prin `logout(true)`. +Ceea ce este important este că la deconectarea folosind `$user->logout()`, **identitatea nu se șterge** și este în continuare disponibilă. Deci, deși utilizatorul are o identitate, nu trebuie să fie conectat. Dacă am dori să ștergem explicit identitatea, deconectăm utilizatorul apelând `logout(true)`. -Datorită acestui lucru, puteți presupune în continuare ce utilizator se află la calculator și, de exemplu, puteți afișa oferte personalizate în magazinul electronic, însă puteți afișa datele sale personale numai după ce s-a logat. +Datorită acestui fapt, puteți în continuare presupune ce utilizator este la calculator și, de exemplu, să îi afișați oferte personalizate în e-shop, însă afișarea datelor sale personale o puteți face doar după autentificare. -Identity este un obiect care implementează interfața [api:Nette\Security\IIdentity], implementarea implicită fiind [api:Nette\Security\SimpleIdentity]. Și, după cum am menționat, identitatea este stocată în sesiune, astfel încât, dacă, de exemplu, schimbăm rolul unora dintre utilizatorii conectați, datele vechi vor fi păstrate în identitate până când acesta se conectează din nou. +Identitatea este un obiect care implementează interfața [api:Nette\Security\IIdentity], implementarea implicită fiind [api:Nette\Security\SimpleIdentity]. Și, așa cum s-a menționat, se menține în sesiune, deci dacă, de exemplu, schimbăm rolul unuia dintre utilizatorii conectați, datele vechi rămân în identitatea sa până la următoarea sa autentificare. -Stocarea pentru utilizatorul conectat .[#toc-storage-for-logged-user] -===================================================================== +Stocarea utilizatorului autentificat +==================================== -Cele două informații de bază despre utilizator, și anume dacă este conectat și [identitatea |#identity] sa, sunt de obicei stocate în sesiune. Care pot fi modificate. Pentru stocarea acestor informații este responsabil un obiect care implementează interfața `Nette\Security\UserStorage`. Există două implementări standard, prima transmite datele într-o sesiune, iar cea de-a doua într-un cookie. Acestea sunt clasele `Nette\Bridges\SecurityHttp\SessionStorage` și `CookieStorage`. Puteți alege stocarea și o puteți configura foarte convenabil în configurația [securitate › autentificare |configuration]. +Două informații de bază despre utilizator, adică dacă este conectat și [identitatea |#Identitate] sa, se transmit de obicei în sesiune. Ceea ce poate fi schimbat. Stocarea acestor informații este responsabilitatea unui obiect care implementează interfața `Nette\Security\UserStorage`. Sunt disponibile două implementări standard, prima transmite datele în sesiune și a doua în cookie. Este vorba despre clasele `Nette\Bridges\SecurityHttp\SessionStorage` și `CookieStorage`. Puteți alege stocarea și o puteți configura foarte comod în configurația [security › authentication |configuration#Stocare]. -De asemenea, puteți controla exact modul în care va avea loc salvarea (*sleep*) și restaurarea (*wakeup*) identității. Tot ce trebuie să faceți este ca autentificatorul să implementeze interfața `Nette\Security\IdentityHandler`. Aceasta are două metode: `sleepIdentity()` este apelată înainte ca identitatea să fie scrisă în memorie, iar `wakeupIdentity()` este apelată după ce identitatea este citită. Metodele pot modifica conținutul identității sau o pot înlocui cu un nou obiect care se întoarce. Metoda `wakeupIdentity()` poate chiar să returneze `null`, care deconectează utilizatorul. +Mai departe, puteți influența exact cum va decurge stocarea identității (*sleep*) și restaurarea (*wakeup*). Este suficient ca autentificatorul să implementeze interfața `Nette\Security\IdentityHandler`. Aceasta are două metode: `sleepIdentity()` se apelează înainte de scrierea identității în stocare și `wakeupIdentity()` după citirea acesteia. Metodele pot modifica conținutul identității, eventual o pot înlocui cu un obiect nou pe care îl returnează. Metoda `wakeupIdentity()` poate chiar returna `null`, ceea ce îl deconectează pe utilizator. -Ca exemplu, vom prezenta o soluție la o întrebare frecventă privind modul de actualizare a rolurilor identității imediat după restaurarea dintr-o sesiune. În metoda `wakeupIdentity()` transmitem rolurile curente către identitate, de exemplu, din baza de date: +Ca exemplu, vom arăta soluția la întrebarea frecventă despre cum să actualizăm rolurile în identitate imediat după încărcarea din sesiune. În metoda `wakeupIdentity()` vom transmite în identitate rolurile actuale, de exemplu, din baza de date: ```php final class Authenticator implements @@ -185,8 +184,8 @@ final class Authenticator implements { public function sleepIdentity(IIdentity $identity): IIdentity { - // aici puteți schimba identitatea înainte de a o stoca după autentificare, - // dar nu avem nevoie de asta acum + // aici se poate modifica identitatea înainte de scrierea în stocare după autentificare, + // dar acum nu avem nevoie de asta return $identity; } @@ -199,11 +198,11 @@ final class Authenticator implements } ``` -Și acum ne întoarcem la stocarea bazată pe cookie-uri. Acesta vă permite să creați un site web în care utilizatorii se pot autentifica fără a fi nevoie să folosiți sesiuni. Deci nu este nevoie să scrie pe disc. La urma urmei, acesta este modul în care funcționează site-ul web pe care îl citiți acum, inclusiv forumul. În acest caz, implementarea `IdentityHandler` este o necesitate. Vom stoca în cookie doar un token aleatoriu reprezentând utilizatorul logat. +Și acum ne întoarcem la stocarea bazată pe cookie-uri. Vă permite să creați un site web unde utilizatorii se pot autentifica fără a avea nevoie de sesiuni. Adică nu are nevoie să scrie pe disc. De altfel, așa funcționează și site-ul pe care îl citiți acum, inclusiv forumul. În acest caz, implementarea `IdentityHandler` este o necesitate. În cookie vom stoca doar un token aleatoriu reprezentând utilizatorul conectat. -Așadar, mai întâi setăm stocarea dorită în configurație folosind `security › authentication › storage: cookie`. +Mai întâi, deci, în configurație setăm stocarea dorită folosind `security › authentication › storage: cookie`. -Vom adăuga o coloană `authtoken` în baza de date, în care fiecare utilizator va avea un șir [complet aleatoriu, unic și imposibil de ghicit |utils:random], de lungime suficientă (cel puțin 13 caractere). Depozitul `CookieStorage` stochează doar valoarea `$identity->getId()` în cookie, astfel că în `sleepIdentity()` înlocuim identitatea originală cu un proxy cu `authtoken` în ID, în schimb în metoda `wakeupIdentity()` restaurăm întreaga identitate din baza de date conform authtoken: +În baza de date vom crea o coloană `authtoken`, în care fiecare utilizator va avea un șir [complet aleatoriu, unic și imposibil de ghicit |utils:random] de lungime suficientă (cel puțin 13 caractere). Stocarea `CookieStorage` transmite în cookie doar valoarea `$identity->getId()`, așa că în `sleepIdentity()` vom înlocui identitatea originală cu una substitutivă cu `authtoken` în ID, în schimb în metoda `wakeupIdentity()` vom citi întreaga identitate din baza de date pe baza authtoken-ului: ```php final class Authenticator implements @@ -212,7 +211,7 @@ final class Authenticator implements public function authenticate(string $username, string $password): SimpleIdentity { $row = $this->db->fetch('SELECT * FROM user WHERE username = ?', $username); - // verificați parola + // verificăm parola ... // returnăm identitatea cu toate datele din baza de date return new SimpleIdentity($row->id, null, (array) $row); @@ -220,13 +219,13 @@ final class Authenticator implements public function sleepIdentity(IIdentity $identity): SimpleIdentity { - // returnăm o identitate proxy, în care ID-ul este authtoken + // returnăm o identitate substitutivă, unde în ID va fi authtoken return new SimpleIdentity($identity->authtoken); } public function wakeupIdentity(IIdentity $identity): ?SimpleIdentity { - // înlocuim identitatea proxy cu o identitate completă, ca în authenticate() + // înlocuim identitatea substitutivă cu identitatea completă, ca în authenticate() $row = $this->db->fetch('SELECT * FROM user WHERE authtoken = ?', $identity->getId()); return $row ? new SimpleIdentity($row->id, null, (array) $row) @@ -236,16 +235,16 @@ final class Authenticator implements ``` -Autentificări independente multiple .[#toc-multiple-independent-authentications] -================================================================================ +Mai multe autentificări independente +==================================== -Este posibil să existe mai mulți utilizatori autentificați independent în cadrul unui site și a unei sesiuni la un moment dat. De exemplu, dacă dorim să avem o autentificare separată pentru frontend și backend, vom seta doar un spațiu de nume de sesiune unic pentru fiecare dintre ele: +Este posibil să aveți mai mulți utilizatori autentificați independent în cadrul aceluiași site web și al aceleiași sesiuni. Dacă, de exemplu, dorim să avem o autentificare separată pentru administrare și partea publică pe site, este suficient să setăm un nume propriu pentru fiecare dintre ele: ```php $user->getStorage()->setNamespace('backend'); ``` -Este necesar să reținem că acesta trebuie setat în toate locurile care aparțin aceluiași segment. Atunci când folosim prezentatori, vom seta spațiul de nume în strămoșul comun - de obicei BasePresenter. Pentru a face acest lucru, vom extinde metoda [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()]: +Este important să ne amintim să setăm spațiul de nume întotdeauna în toate locurile care aparțin părții respective. Dacă folosim presenteri, setăm spațiul de nume în strămoșul comun pentru partea respectivă - de obicei BasePresenter. Facem acest lucru extinzând metoda [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()]: ```php public function checkRequirements($element): void @@ -256,10 +255,10 @@ public function checkRequirements($element): void ``` -Autentificatori multipli .[#toc-multiple-authenticators] --------------------------------------------------------- +Mai mulți autentificatori +------------------------- -Împărțirea unei aplicații în segmente cu autentificare independentă necesită, în general, autentificatori diferiți. Cu toate acestea, înregistrarea a două clase care implementează Authenticator în serviciile de configurare ar declanșa o eroare, deoarece Nette nu ar ști care dintre ele ar trebui să fie [conectată automat |dependency-injection:autowiring] la obiectul `Nette\Security\User`. De aceea, trebuie să limităm autowiring-ul pentru acestea cu `autowired: self`, astfel încât să fie activat numai atunci când clasa lor este solicitată în mod specific: +Împărțirea aplicației în părți cu autentificare independentă necesită de obicei și autentificatori diferiți. Cu toate acestea, de îndată ce am înregistra două clase care implementează Authenticator în configurația serviciilor, Nette nu ar ști pe care dintre ele să o atribuie automat obiectului `Nette\Security\User`, și ar afișa o eroare. De aceea, trebuie să limităm [autowiring-ul |dependency-injection:autowiring] pentru autentificatori astfel încât să funcționeze doar atunci când cineva solicită o clasă specifică, de exemplu, FrontAuthenticator, ceea ce realizăm alegând `autowired: self`: ```neon services: @@ -278,7 +277,7 @@ class SignPresenter extends Nette\Application\UI\Presenter } ``` -Trebuie doar să ne setăm autentificatorul la obiectul User înainte de a apela metoda [login() |api:Nette\Security\User::login()], ceea ce înseamnă, de obicei, în callback-ul formularului de autentificare: +Setăm autentificatorul obiectului User înainte de a apela metoda [login() |api:Nette\Security\User::login()], deci de obicei în codul formularului care îl autentifică: ```php $form->onSuccess[] = function (Form $form, \stdClass $data) { diff --git a/security/ro/authorization.texy b/security/ro/authorization.texy index 5be2b94c59..151c40f68e 100644 --- a/security/ro/authorization.texy +++ b/security/ro/authorization.texy @@ -1,48 +1,48 @@ -Controlul accesului (autorizare) -******************************** +Verificarea permisiunilor (Autorizare) +************************************** .[perex] -Autorizarea determină dacă un utilizator are suficiente privilegii, de exemplu, pentru a accesa o anumită resursă sau pentru a efectua o acțiune. Autorizarea presupune o autentificare anterioară reușită, adică faptul că utilizatorul este logat. +Autorizarea verifică dacă un utilizator are permisiuni suficiente, de exemplu, pentru a accesa o anumită resursă sau pentru a efectua o anumită acțiune. Autorizarea presupune autentificarea prealabilă reușită, adică utilizatorul este conectat. -→ [Instalare și cerințe |@home#Installation] +→ [Instalare și cerințe |@home#Instalare] -În exemple, vom folosi un obiect din clasa [api:Nette\Security\User], care reprezintă utilizatorul curent și pe care îl obțineți prin trecerea acestuia cu ajutorul [injecției de dependență |dependency-injection:passing-dependencies]. În prezentări, este suficient să apelați `$user = $this->getUser()`. +În exemple vom folosi obiectul clasei [api:Nette\Security\User], care reprezintă utilizatorul curent și la care ajungeți solicitându-l prin [injecția de dependențe |dependency-injection:passing-dependencies]. În presenteri este suficient doar să apelați `$user = $this->getUser()`. -Pentru site-uri web foarte simple cu administrare, în care nu se face distincție între drepturile utilizatorilor, este posibil să se utilizeze metoda deja cunoscută ca și criteriu de autorizare `isLoggedIn()`. Cu alte cuvinte: odată ce un utilizator este logat, el are permisiuni pentru toate acțiunile și invers. +Pentru site-uri web foarte simple cu administrare, unde permisiunile utilizatorilor nu sunt diferențiate, se poate folosi ca și criteriu de autorizare metoda deja cunoscută `isLoggedIn()`. Cu alte cuvinte: odată ce utilizatorul este conectat, are toate permisiunile și invers. ```php -if ($user->isLoggedIn()) { // utilizatorul este logat? - deleteItem(); // dacă da, el poate șterge un element +if ($user->isLoggedIn()) { // este utilizatorul conectat? + deleteItem(); // atunci are permisiunea pentru operație } ``` -Roluri .[#toc-roles] --------------------- +Roluri +------ -Scopul rolurilor este de a oferi o gestionare mai precisă a permisiunilor și de a rămâne independent de numele utilizatorului. De îndată ce utilizatorul se conectează, i se atribuie unul sau mai multe roluri. Rolurile în sine pot fi simple șiruri de caractere, de exemplu, `admin`, `member`, `guest`, etc. Acestea sunt specificate în cel de-al doilea argument al constructorului `SimpleIdentity`, fie sub forma unui șir de caractere, fie sub forma unei matrice. +Scopul rolurilor este de a oferi un control mai precis al permisiunilor și de a rămâne independent de numele de utilizator. Fiecărui utilizator, imediat după autentificare, i se atribuie unul sau mai multe roluri în care va acționa. Rolurile pot fi șiruri simple, de exemplu `admin`, `member`, `guest`, etc. Se specifică ca al doilea parametru al constructorului `SimpleIdentity`, fie ca șir, fie ca array de șiruri - roluri. -Ca și criteriu de autorizare, vom folosi acum metoda `isInRole()`, care verifică dacă utilizatorul se află în rolul dat: +Ca și criteriu de autorizare vom folosi acum metoda `isInRole()`, care indică dacă utilizatorul acționează în rolul respectiv: ```php -if ($user->isInRole('admin')) { // este rolul de administrator atribuit utilizatorului? - deleteItem(); // dacă da, acesta poate șterge un element +if ($user->isInRole('admin')) { // este utilizatorul în rolul de admin? + deleteItem(); // atunci are permisiunea pentru operație } ``` -După cum știți deja, deconectarea utilizatorului nu îi șterge identitatea. Astfel, metoda `getIdentity()` returnează în continuare obiectul `SimpleIdentity`, inclusiv toate rolurile acordate. Cadrul Nette Framework aderă la principiul "mai puțin cod, mai multă securitate", astfel încât atunci când verificați rolurile, nu trebuie să verificați și dacă utilizatorul este conectat. Metoda `isInRole()` funcționează cu roluri **efective**, adică dacă utilizatorul este conectat, se utilizează rolurile atribuite identității, iar dacă nu este conectat, se utilizează în schimb un rol special automat `guest`. +După cum știți deja, după deconectarea utilizatorului, identitatea sa nu trebuie neapărat ștearsă. Adică metoda `getIdentity()` returnează în continuare obiectul `SimpleIdentity`, inclusiv toate rolurile acordate. Nette Framework adoptă principiul „less code, more security”, unde mai puțin scris duce la un cod mai sigur, de aceea la verificarea rolurilor nu trebuie să verificați și dacă utilizatorul este conectat. Metoda `isInRole()` lucrează cu **roluri efective,** adică dacă utilizatorul este conectat, se bazează pe rolurile specificate în identitate, dacă nu este conectat, are automat rolul special `guest`. -Autorizator .[#toc-authorizator] --------------------------------- +Autorizator +----------- -În plus față de roluri, vom introduce termenii de resursă și operațiune: +Pe lângă roluri, vom introduce și conceptele de resursă și operație: -- **rolul** este un atribut al utilizatorului - de exemplu, moderator, editor, vizitator, utilizator înregistrat, administrator, ... -- **resursa** este o unitate logică a aplicației - articol, pagină, utilizator, element de meniu, sondaj, prezentator, ... -- **operațiunea** este o activitate specifică, pe care utilizatorul poate sau nu să o facă cu *resursa* - vizualizare, editare, ștergere, vot, ... +- **rol** este o proprietate a utilizatorului - de ex. moderator, redactor, vizitator, utilizator înregistrat, administrator... +- **resursă** (*resource*) este un element logic al site-ului - articol, pagină, utilizator, element de meniu, sondaj, presenter, ... +- **operație** (*operation*) este o activitate specifică pe care utilizatorul o poate sau nu o poate face cu resursa - de exemplu, șterge, edita, crea, vota, ... -Un autorizator este un obiect care decide dacă un anumit *rol* are permisiunea de a efectua o anumită *operațiune* cu o anumită *resursă*. Este un obiect care implementează interfața [api:Nette\Security\Authorizator] cu o singură metodă `isAllowed()`: +Autorizatorul este un obiect care decide dacă *rolul* dat are permisiunea de a efectua o anumită *operație* cu o anumită *resursă*. Este un obiect care implementează interfața [api:Nette\Security\Authorizator] cu o singură metodă `isAllowed()`: ```php class MyAuthorizator implements Nette\Security\Authorizator @@ -63,50 +63,50 @@ class MyAuthorizator implements Nette\Security\Authorizator } ``` -Adăugăm autorizatorul la configurație [ca serviciu |dependency-injection:services] al containerului DI: +Adăugăm autorizatorul în configurație [ca serviciu |dependency-injection:services] al containerului DI: ```neon services: - MyAuthorizator ``` -Iar următorul este un exemplu de utilizare. Rețineți că de data aceasta apelăm metoda `Nette\Security\User::isAllowed()`, nu pe cea a autorizatorului, deci nu există primul parametru `$role`. Această metodă apelează secvențial `MyAuthorizator::isAllowed()` pentru toate rolurile de utilizator și returnează true dacă cel puțin unul dintre ei are autorizație. +Și urmează exemplul de utilizare. Atenție, de data aceasta apelăm metoda `Nette\Security\User::isAllowed()`, nu autorizatorul, deci nu există primul parametru `$role`. Această metodă apelează `MyAuthorizator::isAllowed()` succesiv pentru toate rolurile utilizatorului și returnează true dacă cel puțin unul dintre ele are permisiunea. ```php -if ($user->isAllowed('file')) { // are voie utilizatorul să facă totul cu resursa 'file'? +if ($user->isAllowed('file')) { // poate utilizatorul să facă orice cu resursa 'file'? useFile(); } -if ($user->isAllowed('file', 'delete')) { // Utilizatorul are voie să șteargă o resursă "file"? +if ($user->isAllowed('file', 'delete')) { // poate efectua 'delete' asupra resursei 'file'? deleteFile(); } ``` -Ambele argumente sunt opționale, iar valoarea lor implicită înseamnă *tot*. +Ambii parametri sunt opționali, valoarea implicită `null` are semnificația *orice*. -Permisiune ACL .[#toc-permission-acl] -------------------------------------- +Permission ACL +-------------- -Nette vine cu o implementare încorporată a autorizatorului, clasa [api:Nette\Security\Permission], care oferă un nivel ACL (Access Control List) ușor și flexibil pentru controlul permisiunilor și al accesului. Atunci când lucrăm cu această clasă, definim roluri, resurse și permisiuni individuale. Iar rolurile și resursele pot forma ierarhii. Pentru a explica, vom prezenta un exemplu de aplicație web: +Nette vine cu o implementare încorporată a autorizatorului, și anume clasa [api:Nette\Security\Permission], care oferă programatorului un strat ACL (Access Control List) ușor și flexibil pentru gestionarea permisiunilor și accesului. Lucrul cu aceasta constă în definirea rolurilor, resurselor și permisiunilor individuale. Rolurile și resursele permit crearea de ierarhii. Pentru a explica, vom arăta un exemplu de aplicație web: -- `guest`: vizitator care nu este logat, căruia i se permite să citească și să navigheze în partea publică a web-ului, adică să citească articole, să comenteze și să voteze în sondaje. -- `registered`: utilizator logat, care poate pe deasupra să posteze comentarii -- `admin`: poate gestiona articole, comentarii și sondaje +- `guest`: vizitator neconectat, care poate citi și naviga partea publică a site-ului, adică citi articole, comentarii și vota în sondaje +- `registered`: utilizator înregistrat conectat, care în plus poate comenta +- `admin`: poate administra articole, comentarii și sondaje -Astfel, am definit anumite roluri (`guest`, `registered` și `admin`) și am menționat resurse (`article`, `comments`, `poll`), pe care utilizatorii le pot accesa sau asupra cărora pot întreprinde acțiuni (`view`, `vote`, `add`, `edit`). +Am definit deci anumite roluri (`guest`, `registered` și `admin`) și am menționat resurse (`article`, `comment`, `poll`), la care utilizatorii cu un anumit rol pot accesa sau efectua anumite operații (`view`, `vote`, `add`, `edit`). -Creăm o instanță a clasei Permission și definim **roluri**. Este posibil să se utilizeze moștenirea rolurilor, ceea ce garantează că, de exemplu, un utilizator cu rolul `admin` poate face ceea ce poate face un vizitator obișnuit al site-ului web (și, desigur, mai mult). +Creăm o instanță a clasei Permission și definim **rolurile**. Se poate utiliza așa-numita moștenire a rolurilor, care asigură că, de exemplu, un utilizator cu rolul de administrator (`admin`) poate face și ceea ce poate face un vizitator obișnuit al site-ului (și, desigur, mai mult). ```php $acl = new Nette\Security\Permission; $acl->addRole('guest'); -$acl->addRole('registered', 'guest'); // 'registered' moștenește din 'guest' -$acl->addRole('admin', 'registered'); // iar "admin" moștenește din "registered +$acl->addRole('registered', 'guest'); // 'registered' moștenește de la 'guest' +$acl->addRole('admin', 'registered'); // și de la el moștenește 'admin' ``` -Vom defini acum o listă de **resurse** pe care utilizatorii le pot accesa: +Acum definim și lista de **resurse**, la care utilizatorii pot accesa. ```php $acl->addResource('article'); @@ -114,49 +114,49 @@ $acl->addResource('comment'); $acl->addResource('poll'); ``` -Resursele pot folosi, de asemenea, moștenirea, de exemplu, putem adăuga `$acl->addResource('perex', 'article')`. +Și resursele pot folosi moștenirea, ar fi posibil, de exemplu, să specificăm `$acl->addResource('perex', 'article')`. -Și acum cel mai important lucru. Vom defini între ele **regi** care să determine cine poate face ce: +Și acum cel mai important lucru. Definim între ele regulile care determină cine ce poate face cu ce: ```php -// totul este negat acum +// la început, nimeni nu poate face nimic -// lăsați oaspetele să vadă articolele, comentariile și sondajele +// permite guest să vizualizeze articole, comentarii și sondaje $acl->allow('guest', ['article', 'comment', 'poll'], 'view'); -// și, de asemenea, să voteze în sondaje +// și în sondaje, în plus, să voteze $acl->allow('guest', 'poll', 'vote'); -// cel înregistrat moștenește permisiunile de la invitat, îi vom permite și lui să comenteze +// registered moștenește drepturile de la guest, îi dăm în plus dreptul de a comenta $acl->allow('registered', 'comment', 'add'); // administratorul poate vizualiza și edita orice $acl->allow('admin', $acl::All, ['view', 'edit', 'add']); ``` -Ce se întâmplă dacă vrem să **împiedicăm** pe cineva să acceseze o resursă? +Ce se întâmplă dacă vrem să **interzicem** cuiva accesul la o anumită resursă? ```php -// administratorul nu poate edita sondaje, ar fi nedemocratic. +// administratorul nu poate edita sondaje, ar fi nedemocratic $acl->deny('admin', 'poll', 'edit'); ``` -Acum, după ce am creat setul de reguli, putem pur și simplu să punem întrebări de autorizare: +Acum, când avem creată lista de reguli, putem pune simplu întrebări de autorizare: ```php -// pot vizualiza articolele ca invitat? +// poate guest să vizualizeze articole? $acl->isAllowed('guest', 'article', 'view'); // true -// poate un invitat să editeze un articol? +// poate guest să editeze articole? $acl->isAllowed('guest', 'article', 'edit'); // false -// poate invitatul să voteze în sondaje? +// poate guest să voteze în sondaje? $acl->isAllowed('guest', 'poll', 'vote'); // true -// poate invitatul să adauge comentarii? +// poate guest să comenteze? $acl->isAllowed('guest', 'comment', 'add'); // false ``` -Același lucru este valabil și pentru un utilizator înregistrat, dar acesta poate face și comentarii: +Același lucru este valabil și pentru utilizatorul înregistrat, însă acesta poate și comenta: ```php $acl->isAllowed('registered', 'article', 'view'); // true @@ -172,7 +172,7 @@ $acl->isAllowed('admin', 'poll', 'edit'); // false $acl->isAllowed('admin', 'comment', 'edit'); // true ``` -De asemenea, permisiunile pot fi evaluate în mod dinamic și putem lăsa decizia în seama propriului nostru callback, căruia îi sunt trecuți toți parametrii: +Permisiunile pot fi evaluate și dinamic și putem lăsa decizia pe seama unui callback propriu, căruia i se transmit toți parametrii: ```php $assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { @@ -182,7 +182,7 @@ $assertion = function (Permission $acl, string $role, string $resource, string $ $acl->allow('registered', 'comment', null, $assertion); ``` -Dar cum să rezolvăm o situație în care numele rolurilor și al resurselor nu sunt suficiente, adică am dori să definim că, de exemplu, un rol `registered` poate edita o resursă `article` numai dacă este autorul acesteia? Vom folosi obiecte în loc de șiruri de caractere, rolul va fi obiectul [api:Nette\Security\Role] și sursa [api:Nette\Security\Resource]. Metodele lor `getRoleId()` și `getResourceId()` vor returna șirurile originale: +Dar cum să rezolvăm, de exemplu, situația în care nu sunt suficiente doar numele rolurilor și resurselor, ci am dori să definim că, de exemplu, rolul `registered` poate edita resursa `article` doar dacă este autorul său? În loc de șiruri, vom folosi obiecte, rolul va fi obiectul [api:Nette\Security\Role] și resursa [api:Nette\Security\Resource]. Metodele lor `getRoleId()` respectiv `getResourceId()` vor returna șirurile originale: ```php class Registered implements Nette\Security\Role @@ -196,7 +196,7 @@ class Registered implements Nette\Security\Role } -class Article implements Nette\Security\Resource +class Article implements Resource { public $authorId; @@ -207,19 +207,19 @@ class Article implements Nette\Security\Resource } ``` -Și acum să creăm o regulă: +Și acum creăm regula: ```php $assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { - $role = $acl->getQueriedRole(); // obiect Înregistrat - $resource = $acl->getQueriedResource(); // obiect Articol + $role = $acl->getQueriedRole(); // obiectul Registered + $resource = $acl->getQueriedResource(); // obiectul Article return $role->id === $resource->authorId; }; $acl->allow('registered', 'article', 'edit', $assertion); ``` -ACL este interogat prin trecerea unor obiecte: +Și interogarea ACL se efectuează prin transmiterea obiectelor: ```php $user = new Registered(/* ... */); @@ -227,7 +227,7 @@ $article = new Article(/* ... */); $acl->isAllowed($user, $article, 'edit'); ``` -Un rol poate moșteni unul sau mai multe roluri. Dar ce se întâmplă dacă un strămoș are o anumită acțiune permisă, iar celălalt o are refuzată? Atunci intervine *greutatea rolului* - ultimul rol din lista rolurilor care trebuie moștenite are cea mai mare greutate, iar primul rol are cea mai mică greutate: +Un rol poate moșteni de la alt rol sau de la mai multe roluri. Dar ce se întâmplă dacă un părinte are acțiunea interzisă și altul permisă? Care vor fi drepturile descendentului? Se determină după greutatea rolului - ultimul rol specificat în lista părinților are cea mai mare greutate, primul rol specificat are cea mai mică. Este mai clar din exemplu: ```php $acl = new Nette\Security\Permission; @@ -239,22 +239,22 @@ $acl->addResource('backend'); $acl->allow('admin', 'backend'); $acl->deny('guest', 'backend'); -// exemplu A: rolul admin are o pondere mai mică decât rolul guest +// cazul A: rolul admin are o greutate mai mică decât rolul guest $acl->addRole('john', ['admin', 'guest']); -$acl->isAllowed('john', 'backend'); // fals +$acl->isAllowed('john', 'backend'); // false -// exemplul B: rolul admin are o pondere mai mare decât rolul guest +// cazul B: rolul admin are o greutate mai mare decât guest $acl->addRole('mary', ['guest', 'admin']); -$acl->isAllowed('mary', 'backend'); // adevărat +$acl->isAllowed('mary', 'backend'); // true ``` -Rolurile și resursele pot fi, de asemenea, eliminate (`removeRole()`, `removeResource()`), iar regulile pot fi anulate (`removeAllow()`, `removeDeny()`). Rețeaua tuturor rolurilor părintești directe returnează `getRoleParents()`. Dacă două entități moștenesc una de la cealaltă returnează `roleInheritsFrom()` și `resourceInheritsFrom()`. +Rolurile și resursele pot fi și eliminate (`removeRole()`, `removeResource()`), pot fi anulate și regulile (`removeAllow()`, `removeDeny()`). Array-ul tuturor rolurilor părinte directe este returnat de `getRoleParents()`, dacă două entități moștenesc una de la alta este returnat de `roleInheritsFrom()` și `resourceInheritsFrom()`. -Adăugarea ca serviciu .[#toc-add-as-a-service] ----------------------------------------------- +Adăugarea ca servicii +--------------------- -Trebuie să adăugăm ACL-ul creat de noi la configurație ca serviciu pentru a putea fi utilizat de obiectul `$user`, adică pentru a putea fi folosit în cod, de exemplu `$user->isAllowed('article', 'view')`. În acest scop, vom scrie o fabrică pentru acesta: +ACL-ul creat de noi trebuie să îl transmitem configurației ca serviciu, pentru ca obiectul `$user` să înceapă să îl folosească, adică să fie posibil să folosim în cod, de exemplu, `$user->isAllowed('article', 'view')`. În acest scop, vom scrie o fabrică pentru el: ```php namespace App\Model; @@ -263,7 +263,7 @@ class AuthorizatorFactory { public static function create(): Nette\Security\Permission { - $acl = new Nette\Security\Permission; + $acl = new Permission; $acl->addRole(/* ... */); $acl->addResource(/* ... */); $acl->allow(/* ... */); @@ -272,14 +272,14 @@ class AuthorizatorFactory } ``` -Și o vom adăuga la configurație: +Și o adăugăm în configurație: ```neon services: - App\Model\AuthorizatorFactory::create ``` -În prezentatori, puteți verifica apoi permisiunile în metoda `startup()`, de exemplu: +În presenteri puteți apoi verifica permisiunile, de exemplu, în metoda `startup()`: ```php protected function startup() diff --git a/security/ro/configuration.texy b/security/ro/configuration.texy index 1533efe72c..67fc29d3a9 100644 --- a/security/ro/configuration.texy +++ b/security/ro/configuration.texy @@ -1,71 +1,85 @@ -Configurarea controlului accesului -********************************** +Configurarea permisiunilor de acces +*********************************** .[perex] Prezentare generală a opțiunilor de configurare pentru Nette Security. -Dacă nu utilizați întregul cadru, ci doar această bibliotecă, citiți [cum se încarcă configurația |bootstrap:]. +Dacă nu utilizați întregul framework, ci doar această bibliotecă, citiți [cum se încarcă configurația |bootstrap:]. -Puteți defini o listă de utilizatori în configurație pentru a crea un [autentificator simplu |authentication] (`Nette\Security\SimpleAuthenticator`). Deoarece parolele pot fi citite în configurație, această soluție este doar pentru testare. +În configurație se poate defini o listă de utilizatori și astfel se poate crea un [autentificator simplu |authentication] (`Nette\Security\SimpleAuthenticator`). Deoarece în configurație parolele sunt specificate în formă lizibilă, această soluție este potrivită doar pentru scopuri de testare. ```neon security: # afișează panoul utilizatorului în Tracy Bar? - debugger: ... # (bool) valoarea implicită este true + debugger: ... # (bool) implicit este true users: # nume: parola - johndoe: secret123 + frantisek: parolasecreta - # nume, parolă, rol și alte date disponibile în identitate - janedoe: - password: secret123 + # nume, parola, rol și alte date disponibile în identitate + dobrota: + password: parolasecreta roles: [admin] data: ... ``` -De asemenea, puteți defini roluri și resurse pentru a crea o bază pentru [autorizator |authorization] (`Nette\Security\Permission`): +Mai departe, se pot defini roluri și resurse și astfel se poate crea baza pentru un [autorizator |authorization] (`Nette\Security\Permission`): ```neon security: roles: guest: - registered: [guest] # înregistrat moștenește din guest - admin: [registered] # iar admin moștenește din registered + registered: [guest] # registered moștenește de la guest + admin: [registered] # și de la el moștenește admin resources: article: - comment: [article] # resource moștenește din article + comment: [article] # resursa moștenește de la article poll: ``` -Stocarea utilizatorilor .[#toc-user-storage] --------------------------------------------- +Stocare +------- -Puteți configura modul de stocare a informațiilor despre utilizatorul conectat: +Se poate configura modul de stocare a informațiilor despre utilizatorul conectat: ```neon security: authentication: - # după cât timp de inactivitate utilizatorul va fi deconectat - expiration: 30 minutes # (șir de caractere) implicit nu este setat + # după cât timp de inactivitate va fi utilizatorul deconectat + expiration: 30 minutes # (string) implicit este nesetat # unde se stochează informațiile despre utilizatorul conectat storage: session # (session|cookie) implicit este session ``` -Dacă alegeți `cookie` ca depozit, puteți seta, de asemenea, următoarele opțiuni: +Dacă alegeți ca stocare `cookie`, puteți seta și aceste opțiuni: ```neon security: authentication: # numele cookie-ului - cookieName: userId # (string) výchozí je userid + cookieName: userId # (string) implicit este userid - # ce gazde au voie să primească cookie-ul - cookieDomain: 'example.com' # (șir|domeniu) + # domeniile care acceptă cookie-ul + cookieDomain: 'example.com' # (string|domain) - # restricții la accesarea cererii de origine încrucișată - cookieSamesite: None # (Strict|Lax|None) valoarea implicită este Lax + # restricții la accesul de pe alt domeniu + cookieSamesite: None # (Strict|Lax|None) implicit este Lax ``` + + +Servicii DI +----------- + +Aceste servicii sunt adăugate în containerul DI: + +| Nume | Tip | Descriere +|---------------------------------------------------------- +| `security.authenticator` | [api:Nette\Security\Authenticator] | [autentificator |authentication] +| `security.authorizator` | [api:Nette\Security\Authorizator] | [autorizator |authorization] +| `security.passwords` | [api:Nette\Security\Passwords] | [hashing-ul parolelor |passwords] +| `security.user` | [api:Nette\Security\User] | utilizatorul curent +| `security.userStorage` | [api:Nette\Security\UserStorage] | [#stocare] diff --git a/security/ro/passwords.texy b/security/ro/passwords.texy index b654e82701..b0c9408f43 100644 --- a/security/ro/passwords.texy +++ b/security/ro/passwords.texy @@ -1,12 +1,12 @@ -Hașurarea parolei -***************** +Hashing-ul parolelor +******************** .[perex] -Pentru a gestiona securitatea utilizatorilor noștri, nu stocăm niciodată parolele lor în format text simplu, ci stocăm în schimb hash-ul parolei. Hashing-ul nu este o operațiune reversibilă, parola nu poate fi recuperată. Totuși, parola poate fi spartă, iar pentru a face spargerea cât mai dificilă trebuie să folosim un algoritm sigur. Clasa [api:Nette\Security\Passwords] ne va ajuta în acest sens. +Pentru a asigura securitatea utilizatorilor noștri, nu stocăm parolele lor în formă lizibilă, ci stocăm doar amprenta (așa-numitul hash). Din amprentă nu se poate reconstrui retroactiv forma originală a parolei. Este important să folosim un algoritm sigur pentru a crea amprenta. Cu aceasta ne ajută clasa [api:Nette\Security\Passwords]. -→ [Instalare și cerințe |@home#Installation] +→ [Instalare și cerințe |@home#Instalare] -Cadrul adaugă automat un serviciu `Nette\Security\Passwords` la containerul DI sub numele `security.passwords`, pe care îl obțineți trecându-l folosind [injecția de dependență |dependency-injection:passing-dependencies]: +Framework-ul adaugă automat în containerul DI un serviciu de tip `Nette\Security\Passwords` sub numele `security.passwords`, la care ajungeți solicitându-l prin [injecția de dependențe |dependency-injection:passing-dependencies]. ```php use Nette\Security\Passwords; @@ -24,44 +24,44 @@ class Foo __construct($algo=PASSWORD_DEFAULT, array $options=[]): string .[method] ======================================================================== -Alege ce [algoritm securizat |https://www.php.net/manual/en/password.constants.php] este utilizat pentru hashing și cum să îl configureze. +Alegem ce [algoritm sigur |https://www.php.net/manual/en/password.constants.php] să folosim pentru generarea hash-ului și configurăm parametrii săi. -Valoarea implicită este `PASSWORD_DEFAULT`, astfel încât alegerea algoritmului este lăsată la latitudinea PHP. Algoritmul se poate schimba în versiunile mai noi ale PHP atunci când sunt suportați algoritmi de hashing mai noi și mai puternici. Prin urmare, trebuie să știți că lungimea hash-ului rezultat se poate schimba. Prin urmare, ar trebui să stocați hash-ul rezultat într-un mod care poate stoca suficiente caractere, 255 este lățimea recomandată. +Implicit se folosește `PASSWORD_DEFAULT`, adică alegerea algoritmului este lăsată pe seama PHP. Algoritmul se poate schimba în versiunile mai noi de PHP, dacă apar algoritmi de hashing mai noi și mai puternici. De aceea, ar trebui să fiți conștienți că lungimea hash-ului rezultat se poate schimba și ar trebui să îl stocați într-un mod care poate conține suficiente caractere, 255 fiind lățimea recomandată. -Acesta este modul în care ați utiliza algoritmul bcrypt și ați modifica viteza de hashing folosind parametrul cost de la valoarea implicită 10. În anul 2020, cu costul 10, hashing-ul unei parole durează aproximativ 80ms, costul 11 durează 160ms, costul 12 apoi 320ms, scara este logaritmică. Cu cât este mai lent, cu atât mai bine, costul 10-12 este considerat suficient de lent pentru majoritatea cazurilor de utilizare: +Exemplu de setare a vitezei de hashing cu algoritmul bcrypt prin modificarea parametrului cost: (în 2020, implicit este 10, hashing-ul parolei durează aproximativ 80ms, pentru cost 11 este cca 160ms, pentru cost 12 aproximativ 320ms, cu cât este mai lent, cu atât protecția este mai bună, viteza 10-12 fiind deja considerată o protecție suficientă) ```php -// vom hash parolele cu 2^12 (2^cost) iterații ale algoritmului bcrypt. +// vom hasha parolele cu 2^12 (2^cost) iterații ale algoritmului bcrypt $passwords = new Passwords(PASSWORD_BCRYPT, ['cost' => 12]); ``` -Cu injecție de dependență: +Prin injecție de dependențe: ```neon services: security.passwords: Nette\Security\Passwords(::PASSWORD_BCRYPT, [cost: 12]) ``` -hash(string $passwords): string .[method] -========================================= +hash(string $password): string .[method] +======================================== Generează hash-ul parolei. ```php -$res = $passwords->hash($password); // Hașura parola +$res = $passwords->hash($password); // Hashează parola ``` -Rezultatul `$res` este un șir de caractere care, pe lângă hash-ul propriu-zis, conține identificatorul algoritmului utilizat, setările acestuia și sarea criptografică (date aleatorii pentru a se asigura că pentru aceeași parolă se generează un hash diferit). Prin urmare, este compatibil cu versiunile anterioare, de exemplu, dacă se schimbă parametrii, pot fi verificate hash-urile stocate folosind setările anterioare. Întregul rezultat este stocat în baza de date, deci nu este nevoie să stocați separat sarea sau setările. +Rezultatul `$res` este un șir care, pe lângă hash-ul propriu-zis, conține și identificatorul algoritmului folosit, setările sale și sarea criptografică (date aleatorii care asigură că pentru aceeași parolă se generează un hash diferit). Este deci compatibil retroactiv, de exemplu, dacă schimbați parametrii, hash-urile stocate folosind setările anterioare vor putea fi verificate. Întregul rezultat se stochează în baza de date, deci nu este nevoie să stocați sarea sau setările separat. verify(string $password, string $hash): bool .[method] ====================================================== -Află dacă parola dată se potrivește cu hash-ul dat. Obține `$hash` din baza de date după numele de utilizator sau adresa de e-mail. +Verifică dacă parola dată corespunde amprentei date. Obțineți `$hash` din baza de date pe baza numelui de utilizator sau adresei de e-mail specificate. ```php if ($passwords->verify($password, $hash)) { - // Parola corectă + // parola corectă } ``` @@ -69,13 +69,13 @@ if ($passwords->verify($password, $hash)) { needsRehash(string $hash): bool .[method] ========================================= -Află dacă hash-ul se potrivește cu opțiunile date în constructor. +Verifică dacă hash-ul corespunde opțiunilor specificate în constructor. -Utilizați această metodă atunci când, de exemplu, schimbați parametrii de hashing. Verificarea parolei va utiliza parametrii stocați împreună cu hash-ul și dacă `needsRehash()` returnează true, trebuie să calculați din nou hash-ul, de data aceasta cu parametrii actualizați, și să îl stocați din nou în baza de date. Acest lucru asigură faptul că hash-urile parolelor vor fi "actualizate" automat atunci când utilizatorii se conectează. +Este util de folosit în momentul în care, de exemplu, schimbați viteza de hashing. Verificarea se face conform setărilor stocate și dacă `needsRehash()` returnează `true`, atunci este necesar să se creeze din nou hash-ul, de data aceasta cu noii parametri, și să se salveze din nou în baza de date. Astfel se "actualizează" automat hash-urile stocate la autentificarea utilizatorilor. ```php if ($passwords->needsRehash($hash)) { $hash = $passwords->hash($password); - // stochează $hash în baza de date + // salvează $hash în baza de date } ``` diff --git a/security/ru/@home.texy b/security/ru/@home.texy index 4f397d6934..11978fe748 100644 --- a/security/ru/@home.texy +++ b/security/ru/@home.texy @@ -1,14 +1,14 @@ -Безопасность -************ +Nette Security +************** .[perex] -Пакет `nette/security` отвечает за [аутентификацию пользователей |authentication], [контроль доступа |authorization] и [хэширование паролей |passwords]. +Пакет `nette/security` отвечает за [вход пользователей |authentication], [проверку прав доступа |authorization] и [хеширование паролей |passwords]. -Установка .[#toc-installation] ------------------------------- +Установка +--------- -Загрузите и установите пакет с помощью [Composer |best-practices:composer]: +Вы можете скачать и установить библиотеку с помощью [Composer|best-practices:composer]: ```shell composer require nette/security diff --git a/security/ru/@left-menu.texy b/security/ru/@left-menu.texy index 1d03d7d9f9..45f418903e 100644 --- a/security/ru/@left-menu.texy +++ b/security/ru/@left-menu.texy @@ -1,7 +1,7 @@ -Безопасность Nette -****************** -- [Обзор |@home] -- [Аутентификация |Authentication] -- [Авторизация |Authorization] +Nette Security +************** +- [Введение |@home] +- [Аутентификация |authentication] +- [Авторизация |authorization] - [Хеширование паролей |passwords] -- [Конфигурация |Configuration] +- [Конфигурация |configuration] diff --git a/security/ru/@meta.texy b/security/ru/@meta.texy new file mode 100644 index 0000000000..7f329adfce --- /dev/null +++ b/security/ru/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документация Nette}} diff --git a/security/ru/authentication.texy b/security/ru/authentication.texy index d16a998248..e343d75496 100644 --- a/security/ru/authentication.texy +++ b/security/ru/authentication.texy @@ -1,48 +1,48 @@ -Аутентификация пользователей -**************************** +Вход пользователя (Аутентификация) +********************************** <div class=perex> -Мало-мальски значимые веб-приложения не нуждаются в механизме для входа пользователей в систему или проверки их привилегий. В этой главе мы поговорим о: +Почти ни одно веб-приложение не обходится без механизма входа пользователей и проверки их прав. В этой главе мы поговорим о: -- вход и выход пользователя -- пользовательские аутентификаторы и авторизаторы +- входе и выходе пользователей +- собственных аутентификаторах </div> -→ [Установка и требования |@home#Installation] +→ [Установка и требования |@home#Установка] -В примерах мы будем использовать объект класса [api:Nette\Security\User], который представляет текущего пользователя и который вы получаете, передавая его с помощью [инъекции зависимостей |dependency-injection:passing-dependencies]. В презентаторах просто вызывайте `$user = $this->getUser()`. +В примерах мы будем использовать объект класса [api:Nette\Security\User], который представляет текущего пользователя и к которому вы можете получить доступ, запросив его передачу с помощью [dependency injection |dependency-injection:passing-dependencies]. В презентерах достаточно просто вызвать `$user = $this->getUser()`. -Аутентификация .[#toc-authentication] -===================================== +Аутентификация +============== -Аутентификация означает **вход пользователя в систему**, т.е. процесс, в ходе которого проверяется личность пользователя. Пользователь обычно идентифицирует себя с помощью имени пользователя и пароля. Верификация выполняется так называемым [аутентификатором |#Authenticator]. Если вход в систему не удается, происходит выброс `Nette\Security\AuthenticationException`. +Аутентификацией называется **вход пользователя**, то есть процесс, при котором проверяется, действительно ли пользователь является тем, за кого себя выдает. Обычно он подтверждает свою личность именем пользователя и паролем. Проверку выполняет так называемый [#Аутентификатор]. Если вход не удался, выбрасывается исключение `Nette\Security\AuthenticationException`. ```php try { $user->login($username, $password); } catch (Nette\Security\AuthenticationException $e) { - $this->flashMessage('The username or password you entered is incorrect.'); + $this->flashMessage('Имя пользователя или пароль неверны'); } ``` -Вот как выйти из системы: +Таким образом вы выводите пользователя из системы: ```php $user->logout(); ``` -И проверить, вошел ли пользователь в систему: +А проверка того, что он вошел в систему: ```php -echo $user->isLoggedIn() ? 'yes' : 'no'; +echo $user->isLoggedIn() ? 'да' : 'нет'; ``` -Просто, правда? И все аспекты безопасности обрабатываются Nette за вас. +Очень просто, не правда ли? И все аспекты безопасности Nette решает за вас. -В Presenter вы можете проверить вход в систему в методе `startup()` и перенаправить незалогиненного пользователя на страницу входа. +В презентерах вы можете проверить вход в методе `startup()` и перенаправить не вошедшего пользователя на страницу входа. ```php protected function startup() @@ -55,39 +55,38 @@ protected function startup() ``` -Срок действия .[#toc-expiration] -================================ +Экспирация +========== -Логин пользователя истекает вместе с [истечением срока действия репозитория |#Storage-for-Logged-User], который обычно является сессией (см. настройку [истечения срока действия сессии |http:configuration#Session] ). -Однако можно задать и более короткий промежуток времени, по истечении которого пользователь выходит из системы. Для этого используется метод `setExpiration()`, который вызывается перед `login()`. В качестве параметра предоставьте строку с относительным временем: +Вход пользователя истекает вместе с [экспирацией хранилища |#Хранилище вошедшего пользователя], которым обычно является сессия (см. настройку [экспирации сессии |http:configuration#Сессия]). Однако можно установить и более короткий временной интервал, по истечении которого пользователь будет выведен из системы. Для этого служит метод `setExpiration()`, который вызывается перед `login()`. В качестве параметра укажите строку с относительным временем: ```php -// срок действия логина истекает после 30 минут бездействия +// вход истечет через 30 минут неактивности $user->setExpiration('30 minutes'); -// отмена установленного срока действия +// отмена установленной экспирации $user->setExpiration(null); ``` -Метод `$user->getLogoutReason()` определяет, вышел ли пользователь из системы, поскольку истек временной интервал. Он возвращает либо константу `Nette\Security\UserStorage::LogoutInactivity`, если время истекло, либо `UserStorage::LogoutManual`, если был вызван метод `logout()`. +Был ли пользователь выведен из системы из-за истечения временного интервала, подскажет метод `$user->getLogoutReason()`, который возвращает либо константу `Nette\Security\UserStorage::LogoutInactivity` (истек временной лимит), либо `UserStorage::LogoutManual` (выведен методом `logout()`). -Аутентификатор .[#toc-authenticator] -==================================== +Аутентификатор +============== -Это объект, который проверяет данные для входа в систему, т.е. обычно имя и пароль. Тривиальной реализацией является класс [api:Nette\Security\SimpleAuthenticator], который может быть определен в [конфигурации |configuration]: +Это объект, который проверяет учетные данные, то есть обычно имя и пароль. Тривиальной формой является класс [api:Nette\Security\SimpleAuthenticator], который мы можем определить в [конфигурации|nette:configuring]: ```neon security: users: - # name: password - johndoe: secret123 - kathy: evenmoresecretpassword + # имя: пароль + frantisek: tajneheslo + katka: jestetajnejsiheslo ``` -Это решение больше подходит для целей тестирования. Мы покажем вам, как создать аутентификатор, который будет проверять учетные данные по таблице базы данных. +Это решение подходит скорее для тестовых целей. Покажем, как создать аутентификатор, который будет проверять учетные данные по таблице базы данных. -Аутентификатор - это объект, реализующий интерфейс [api:Nette\Security\Authenticator] с методом `authenticate()`. Его задача - либо вернуть так называемый [идентификатор |#Identity], либо выбросить исключение `Nette\Security\AuthenticationException`. Также можно было бы предоставить код ошибки `Authenticator::IdentityNotFound` или `Authenticator::InvalidCredential`. +Аутентификатор — это объект, реализующий интерфейс [api:Nette\Security\Authenticator] с методом `authenticate()`. Его задача — либо вернуть так называемый [идентификатор |#Идентификатор Identity], либо выбросить исключение `Nette\Security\AuthenticationException`. Можно было бы также указать код ошибки для более точного различения возникшей ситуации: `Authenticator::IdentityNotFound` и `Authenticator::InvalidCredential`. ```php use Nette; @@ -108,25 +107,25 @@ class MyAuthenticator implements Nette\Security\Authenticator ->fetch(); if (!$row) { - throw new Nette\Security\AuthenticationException('User not found.'); + throw new Nette\Security\AuthenticationException('Пользователь не найден.'); } if (!$this->passwords->verify($password, $row->password)) { - throw new Nette\Security\AuthenticationException('Invalid password.'); + throw new Nette\Security\AuthenticationException('Неверный пароль.'); } return new SimpleIdentity( $row->id, - $row->role, // или массив ролей + $row->role, // или массив нескольких ролей ['name' => $row->username], ); } } ``` -Класс MyAuthenticator взаимодействует с базой данных через [Nette Database Explorer |database:explorer] и работает с таблицей `users`, где столбец `username` содержит имя пользователя для входа в систему, а столбец `password` - [хэш |passwords]. После проверки имени и пароля он возвращает идентификатор с ID пользователя, роль (столбец `role` в таблице), которую мы упомянем [позже |#Roles], и массив с дополнительными данными (в нашем случае имя пользователя). +Класс MyAuthenticator общается с базой данных через [Nette Database Explorer|database:explorer] и работает с таблицей `users`, где в столбце `username` находится имя пользователя для входа, а в столбце `password` — [хеш пароля|passwords]. После проверки имени и пароля он возвращает идентификатор, который несет ID пользователя, его роль (столбец `role` в таблице), о которой мы подробнее поговорим [позже |authorization#Роли], и массив с дополнительными данными (в нашем случае имя пользователя). -Мы добавим аутентификатор в конфигурацию [как сервис |dependency-injection:services] контейнера DI: +Аутентификатор еще добавим в конфигурацию [как сервис|dependency-injection:services] DI-контейнера: ```neon services: @@ -134,10 +133,10 @@ services: ``` -$onLoggedIn, $onLoggedOut Events --------------------------------- +События $onLoggedIn, $onLoggedOut +--------------------------------- -Объект `Nette\Security\User` имеет [события |nette:glossary#Events] `$onLoggedIn` и `$onLoggedOut`, поэтому вы можете добавить обратные вызовы, которые срабатывают после успешного входа в систему или после выхода пользователя из системы. +Объект `Nette\Security\User` имеет [события |nette:glossary#События Events] `$onLoggedIn` и `$onLoggedOut`, поэтому вы можете добавить обратные вызовы (callbacks), которые будут вызваны после успешного входа или выхода пользователя соответственно. ```php @@ -147,37 +146,37 @@ $user->onLoggedIn[] = function () { ``` -Идентичность .[#toc-identity] -============================= +Идентификатор (Identity) +======================== -Идентификатор - это набор информации о пользователе, который возвращается аутентификатором и который затем хранится в сессии и извлекается с помощью `$user->getIdentity()`. Таким образом, мы можем получить id, роли и другие данные пользователя в том виде, в котором мы передали их в аутентификаторе: +Идентификатор представляет собой набор информации о пользователе, который возвращает аутентификатор и который затем сохраняется в сессии и получается с помощью `$user->getIdentity()`. Мы можем таким образом получить id, роли и другие данные пользователя, так как мы их передали в аутентификаторе: ```php $user->getIdentity()->getId(); -// также работает сокращение $user->getId(); +// работает и сокращение $user->getId(); $user->getIdentity()->getRoles(); -// данные пользователя могут быть доступны как свойства +// данные пользователя доступны как свойства // имя, которое мы передали в MyAuthenticator $user->getIdentity()->name; ``` -Важно отметить, что когда пользователь выходит из системы с помощью `$user->logout()`, **идентичность не удаляется** и все еще доступна. Таким образом, если идентификатор существует, он сам по себе не гарантирует, что пользователь также вошел в систему. Если мы хотим явным образом удалить идентификатор, мы выходим из системы с помощью `logout(true)`. +Важно то, что при выходе с помощью `$user->logout()` **идентификатор не удаляется** и остается доступным. Так что, хотя у пользователя есть идентификатор, он может быть не вошедшим в систему. Если бы мы хотели явно удалить идентификатор, мы бы вывели пользователя из системы вызовом `logout(true)`. -Благодаря этому вы все еще можете определить, какой пользователь находится за компьютером, и, например, отображать персонализированные предложения в интернет-магазине, однако вы можете отображать его личные данные только после входа в систему. +Благодаря этому вы можете по-прежнему предполагать, какой пользователь находится за компьютером, и, например, показывать ему персонализированные предложения в интернет-магазине, однако отображать его личные данные можно только после входа в систему. -Identity - это объект, реализующий интерфейс [api:Nette\Security\IIdentity], реализация по умолчанию - [api:Nette\Security\SimpleIdentity]. Как уже упоминалось, идентификатор хранится в сессии, поэтому если, например, мы изменим роль какого-то из вошедших в систему пользователей, старые данные будут храниться в идентификаторе до тех пор, пока он снова не войдет в систему. +Идентификатор — это объект, реализующий интерфейс [api:Nette\Security\IIdentity], реализацией по умолчанию является [api:Nette\Security\SimpleIdentity]. И, как было упомянуто, он хранится в сессии, поэтому если, например, мы изменим роль одного из вошедших пользователей, старые данные останутся в его идентификаторе до его повторного входа. -Хранение данных для вошедшего пользователя .[#toc-storage-for-logged-user] -========================================================================== +Хранилище вошедшего пользователя +================================ -Две основные части информации о пользователе, т.е. вошел ли он в систему и его [личность |#Identity], обычно хранятся в сессии. Которая может быть изменена. За хранение этой информации отвечает объект, реализующий интерфейс `Nette\Security\UserStorage`. Существует две стандартные реализации, первая передает данные в сессии, вторая - в cookie. Это классы `Nette\Bridges\SecurityHttp\SessionStorage` и `CookieStorage`. Выбрать хранилище и настроить его очень удобно в конфигурации [security › authentication |configuration]. +Две основные информации о пользователе, то есть вошел ли он в систему и его [#Идентификатор (Identity)], обычно передаются в сессии. Что можно изменить. За хранение этой информации отвечает объект, реализующий интерфейс `Nette\Security\UserStorage`. Доступны две стандартные реализации: первая передает данные в сессии, а вторая — в cookie. Это классы `Nette\Bridges\SecurityHttp\SessionStorage` и `CookieStorage`. Выбрать хранилище и настроить его можно очень удобно в конфигурации [security › authentication |configuration#Хранилище]. -Вы также можете контролировать, как именно будет происходить сохранение (*sleep*) и восстановление (*wakeup*) аутентификации. Все, что вам нужно, это чтобы аутентификатор реализовывал интерфейс `Nette\Security\IdentityHandler`. У него есть два метода: `sleepIdentity()` вызывается перед записью идентификатора в хранилище, а `wakeupIdentity()` - после считывания идентификатора. Эти методы могут изменять содержимое идентификатора или заменять его новым объектом, который возвращается. Метод `wakeupIdentity()` может даже возвращать `null`, который выводит пользователя из системы. +Далее вы можете повлиять на то, как именно будет происходить сохранение идентификатора (*sleep*) и восстановление (*wakeup*). Достаточно, чтобы аутентификатор реализовал интерфейс `Nette\Security\IdentityHandler`. У него есть два метода: `sleepIdentity()` вызывается перед записью идентификатора в хранилище, а `wakeupIdentity()` — после его чтения. Методы могут изменять содержимое идентификатора или заменять его новым объектом, который они вернут. Метод `wakeupIdentity()` может даже вернуть `null`, что приведет к выходу пользователя из системы. -В качестве примера мы покажем решение распространенного вопроса о том, как обновить роли идентификатора сразу после восстановления из сессии. В методе `wakeupIdentity()` мы передаем идентификатору текущие роли, например, из базы данных: +В качестве примера покажем решение частого вопроса, как обновить роли в идентификаторе сразу после загрузки из сессии. В методе `wakeupIdentity()` передадим в идентификатор текущие роли, например, из базы данных: ```php final class Authenticator implements @@ -185,25 +184,25 @@ final class Authenticator implements { public function sleepIdentity(IIdentity $identity): IIdentity { - // здесь вы можете изменить идентификатор перед хранением после входа в систему, + // здесь можно изменить идентификатор перед записью в хранилище после входа, // но сейчас нам это не нужно return $identity; } public function wakeupIdentity(IIdentity $identity): ?IIdentity { - // обновление ролей в идентификации + // обновление ролей в идентификаторе $userId = $identity->getId(); $identity->setRoles($this->facade->getUserRoles($userId)); return $identity; } ``` -А теперь вернемся к хранилищу на основе cookie. Оно позволяет создать сайт, на котором пользователи могут входить в систему без необходимости использования сессий. Поэтому ему не требуется запись на диск. В конце концов, именно так работает сайт, который вы сейчас читаете, включая форум. В этом случае реализация `IdentityHandler` является необходимостью. Мы будем хранить в cookie только случайный токен, представляющий вошедшего пользователя. +А теперь вернемся к хранилищу на основе cookie. Оно позволяет вам создать веб-сайт, где пользователи могут входить в систему, и при этом не требует сессий. То есть не требует записи на диск. Впрочем, так работает и веб-сайт, который вы сейчас читаете, включая форум. В этом случае реализация `IdentityHandler` является необходимостью. В cookie мы будем хранить только случайный токен, представляющий вошедшего пользователя. -Поэтому сначала мы зададим нужное хранилище в конфигурации с помощью `security › authentication › storage: cookie`. +Сначала в конфигурации установим требуемое хранилище с помощью `security › authentication › storage: cookie`. -Мы добавим в базу данных колонку `authtoken`, в которой каждый пользователь будет иметь [совершенно случайную, уникальную и не угадываемую|utils:random] строку достаточной длины (не менее 13 символов). Хранилище `CookieStorage` хранит только значение `$identity->getId()` в cookie, поэтому в методе `sleepIdentity()` мы заменим оригинальную личность на прокси с `authtoken` в ID, а в методе `wakeupIdentity()`, наоборот, восстановим всю личность из базы данных по auttoken: +В базе данных создадим столбец `authtoken`, в котором у каждого пользователя будет [совершенно случайная, уникальная и неугадываемая|utils:random] строка достаточной длины (не менее 13 символов). Хранилище `CookieStorage` передает в cookie только значение `$identity->getId()`, поэтому в `sleepIdentity()` мы заменим оригинальный идентификатор на замещающий с `authtoken` в ID, а в методе `wakeupIdentity()` по authtoken прочитаем весь идентификатор из базы данных: ```php final class Authenticator implements @@ -212,21 +211,21 @@ final class Authenticator implements public function authenticate(string $username, string $password): SimpleIdentity { $row = $this->db->fetch('SELECT * FROM user WHERE username = ?', $username); - // проверка пароля + // проверим пароль ... - // возвращаем идентификатор со всеми данными из базы данных + // вернем идентификатор со всеми данными из базы данных return new SimpleIdentity($row->id, null, (array) $row); } public function sleepIdentity(IIdentity $identity): SimpleIdentity { - // мы возвращаем идентификатор прокси, где в качестве идентификатора выступает authtoken + // вернем замещающий идентификатор, где в ID будет authtoken return new SimpleIdentity($identity->authtoken); } public function wakeupIdentity(IIdentity $identity): ?SimpleIdentity { - // заменить идентификатор прокси на полный идентификатор, как в authenticate() + // замещающий идентификатор заменим полным идентификатором, как в authenticate() $row = $this->db->fetch('SELECT * FROM user WHERE authtoken = ?', $identity->getId()); return $row ? new SimpleIdentity($row->id, null, (array) $row) @@ -236,16 +235,16 @@ final class Authenticator implements ``` -Множественная независимая аутентификация .[#toc-multiple-independent-authentications] -===================================================================================== +Несколько независимых входов +============================ -Можно иметь несколько независимых зарегистрированных пользователей в рамках одного сайта и одной сессии одновременно. Например, если мы хотим иметь отдельную аутентификацию для frontend и backend, мы просто установим уникальное пространство имен сессии для каждого из них: +Одновременно в рамках одного веб-сайта и одной сессии может быть несколько независимых вошедших пользователей. Если, например, мы хотим иметь на веб-сайте отдельную аутентификацию для администрирования и публичной части, достаточно каждой из них установить собственное имя: ```php $user->getStorage()->setNamespace('backend'); ``` -Необходимо помнить, что оно должно быть задано во всех местах, принадлежащих одному сегменту. При использовании презентаторов мы установим пространство имен в общем предке - обычно BasePresenter. Для этого мы расширим метод [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()]: +Важно помнить, что пространство имен нужно устанавливать всегда во всех местах, относящихся к данной части. Если мы используем презентеры, установим пространство имен в общем предке для данной части - обычно BasePresenter. Сделаем это, расширив метод [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()]: ```php public function checkRequirements($element): void @@ -256,10 +255,10 @@ public function checkRequirements($element): void ``` -Множественные аутентификаторы .[#toc-multiple-authenticators] -------------------------------------------------------------- +Несколько аутентификаторов +-------------------------- -Разделение приложения на сегменты с независимой аутентификацией обычно требует использования разных аутентификаторов. Однако регистрация двух классов, реализующих Authenticator, в конфигурационных службах приведет к ошибке, поскольку Nette не будет знать, какой из них должен быть [автоподключен |dependency-injection:autowiring] к объекту `Nette\Security\User`. Вот почему мы должны ограничить автоподключение для них с помощью `autowired: self` так, чтобы оно активировалось только при конкретном запросе их класса: +Разделение приложения на части с независимым входом обычно требует также разных аутентификаторов. Однако, если бы мы зарегистрировали в конфигурации сервисов два класса, реализующих Authenticator, Nette не знало бы, какой из них автоматически присвоить объекту `Nette\Security\User`, и показало бы ошибку. Поэтому для аутентификаторов нам нужно [autowiring |dependency-injection:autowiring] ограничить так, чтобы он работал, только если кто-то запросит конкретный класс, например, FrontAuthenticator, чего мы достигнем выбором `autowired: self`: ```neon services: @@ -278,7 +277,7 @@ class SignPresenter extends Nette\Application\UI\Presenter } ``` -Нам нужно установить наш аутентификатор на объект User только перед вызовом метода [login() |api:Nette\Security\User::login()], что обычно означает в обратном вызове формы входа: +Аутентификатор объекта User установим перед вызовом метода [login() |api:Nette\Security\User::login()], то есть обычно в коде формы, которая его регистрирует: ```php $form->onSuccess[] = function (Form $form, \stdClass $data) { diff --git a/security/ru/authorization.texy b/security/ru/authorization.texy index 33142377a7..f13ba4c2ce 100644 --- a/security/ru/authorization.texy +++ b/security/ru/authorization.texy @@ -1,48 +1,48 @@ -Контроль доступа (авторизация) -****************************** +Проверка разрешений (Авторизация) +********************************* .[perex] -Авторизация определяет, обладает ли пользователь достаточными привилегиями, например, для доступа к определенному ресурсу или выполнения какого-либо действия. Авторизация предполагает успешную аутентификацию, т.е. что пользователь вошел в систему. +Авторизация определяет, имеет ли пользователь достаточные разрешения, например, для доступа к определенному ресурсу или для выполнения какого-либо действия. Авторизация предполагает предыдущую успешную аутентификацию, т.е. что пользователь вошел в систему. -→ [Установка и требования |@home#Installation] +→ [Установка и требования |@home#Установка] -В примерах мы будем использовать объект класса [api:Nette\Security\User], который представляет текущего пользователя и который вы получаете, передавая его с помощью [инъекции зависимостей |dependency-injection:passing-dependencies]. В презентаторах просто вызывайте `$user = $this->getUser()`. +В примерах мы будем использовать объект класса [api:Nette\Security\User], который представляет текущего пользователя и к которому вы можете получить доступ, запросив его передачу с помощью [dependency injection |dependency-injection:passing-dependencies]. В презентерах достаточно просто вызвать `$user = $this->getUser()`. -Для очень простых сайтов с администрированием, где права пользователей не различаются, можно использовать в качестве критерия авторизации уже известный метод `isLoggedIn()`. Другими словами: как только пользователь вошел в систему, он имеет права на все действия и наоборот. +Для очень простых веб-сайтов с администрированием, где не различаются права пользователей, в качестве критерия авторизации можно использовать уже известный метод `isLoggedIn()`. Другими словами: как только пользователь вошел в систему, он имеет все права, и наоборот. ```php -if ($user->isLoggedIn()) { // is user logged in? - deleteItem(); // if so, he may delete an item +if ($user->isLoggedIn()) { // пользователь вошел в систему? + deleteItem(); // тогда у него есть права на операцию } ``` -Роли .[#toc-roles] ------------------- +Роли +---- -Цель ролей - предложить более точное управление правами и оставаться независимыми от имени пользователя. Как только пользователь входит в систему, ему назначается одна или несколько ролей. Сами роли могут быть простыми строками, например, `admin`, `member`, `guest` и т.д. Они указываются во втором аргументе конструктора `SimpleIdentity`, либо как строка, либо как массив. +Смысл ролей в том, чтобы предложить более точное управление разрешениями и оставаться независимым от имени пользователя. Каждому пользователю сразу при входе присваивается одна или несколько ролей, в которых он будет выступать. Роли могут быть простыми строками, например, `admin`, `member`, `guest` и т.д. Они указываются как второй параметр конструктора `SimpleIdentity`, либо как строка, либо как массив строк - ролей. -В качестве критерия авторизации мы будем использовать метод `isInRole()`, который проверяет, входит ли пользователь в заданную роль: +В качестве критерия авторизации теперь используем метод `isInRole()`, который сообщает, выступает ли пользователь в данной роли: ```php -if ($user->isInRole('admin')) { // is the admin role assigned to the user? - deleteItem(); // if so, he may delete an item +if ($user->isInRole('admin')) { // пользователь в роли администратора? + deleteItem(); // тогда у него есть права на операцию } ``` -Как вы уже знаете, выход пользователя из системы не стирает его личность. Таким образом, метод `getIdentity()` по-прежнему возвращает объект `SimpleIdentity`, включая все предоставленные роли. Nette Framework придерживается принципа "меньше кода, больше безопасности", поэтому при проверке ролей не нужно проверять, вошел ли пользователь в систему. Метод `isInRole()` работает с **эффективными ролями**, т.е. если пользователь вошел в систему, то используются роли, назначенные личности, если он не вошел, то вместо них используется автоматическая специальная роль `guest`. +Как вы уже знаете, после выхода пользователя из системы его идентификатор может не удаляться. То есть метод `getIdentity()` по-прежнему возвращает объект `SimpleIdentity`, включая все предоставленные роли. Nette Framework придерживается принципа «less code, more security», когда меньше писанины ведет к более безопасному коду, поэтому при проверке ролей вам не нужно дополнительно проверять, вошел ли пользователь в систему. Метод `isInRole()` работает с **эффективными ролями,** т.е. если пользователь вошел в систему, он исходит из ролей, указанных в идентификаторе, если не вошел, у него автоматически специальная роль `guest`. -Авторизатор .[#toc-authorizator] --------------------------------- +Авторизатор +----------- -В дополнение к ролям мы введем термины ресурс и операция: +Кроме ролей, введем еще понятия ресурс и операция: -- **роль** - это атрибут пользователя - например, модератор, редактор, посетитель, зарегистрированный пользователь, администратор, ... -- **ресурс** - это логическая единица приложения - статья, страница, пользователь, пункт меню, опрос, ведущий, ... -- **операция** - это конкретное действие, которое пользователь может или не может выполнять с *ресурсом* - просмотр, редактирование, удаление, голосование, ... +- **роль** - это свойство пользователя - например, модератор, редактор, посетитель, зарегистрированный пользователь, администратор... +- **ресурс** (*resource*) - это какой-то логический элемент веб-сайта - статья, страница, пользователь, пункт меню, опрос, презентер, ... +- **операция** (*operation*) - это какое-то конкретное действие, которое пользователь может или не может делать с ресурсом - например, удалить, изменить, создать, голосовать, ... -Авторизатор - это объект, который решает, имеет ли данная *роль* разрешение на выполнение определенной *операции* с определенным *ресурсом*. Это объект, реализующий интерфейс [api:Nette\Security\Authorizator] с одним методом `isAllowed()`: +Авторизатор — это объект, который решает, имеет ли данная *роль* разрешение выполнить определенную *операцию* с определенным *ресурсом*. Это объект, реализующий интерфейс [api:Nette\Security\Authorizator] с единственным методом `isAllowed()`: ```php class MyAuthorizator implements Nette\Security\Authorizator @@ -63,50 +63,50 @@ class MyAuthorizator implements Nette\Security\Authorizator } ``` -Мы добавляем авторизатор в конфигурацию [как сервис |dependency-injection:services] контейнера DI: +Авторизатор добавим в конфигурацию [как сервис|dependency-injection:services] DI-контейнера: ```neon services: - MyAuthorizator ``` -И ниже приведен пример использования. Обратите внимание, что в этот раз мы вызываем метод `Nette\Security\User::isAllowed()`, а не метод авторизатора, поэтому нет первого параметра `$role`. Этот метод вызывает `MyAuthorizator::isAllowed()` последовательно для всех ролей пользователей и возвращает true, если хотя бы один из них имеет разрешение. +А далее пример использования. Внимание, на этот раз мы вызываем метод `Nette\Security\User::isAllowed()`, а не авторизатор, поэтому там нет первого параметра `$role`. Этот метод вызывает `MyAuthorizator::isAllowed()` последовательно для всех ролей пользователя и возвращает true, если хотя бы одна из них имеет разрешение. ```php -if ($user->isAllowed('file')) { // is user allowed to do everything with resource 'file'? +if ($user->isAllowed('file')) { // может ли пользователь делать что-либо с ресурсом 'file'? useFile(); } -if ($user->isAllowed('file', 'delete')) { // is user allowed to delete a resource 'file'? +if ($user->isAllowed('file', 'delete')) { // может ли над ресурсом 'file' выполнить 'delete'? deleteFile(); } ``` -Оба аргумента являются необязательными, и их значение по умолчанию означает *все*. +Оба параметра являются необязательными, значение по умолчанию `null` означает *что угодно*. -Разрешение ACL .[#toc-permission-acl] -------------------------------------- +Permission ACL +-------------- -Nette поставляется со встроенной реализацией авторизатора, классом [api:Nette\Security\Permission], который предлагает легкий и гибкий уровень ACL (Access Control List) для разрешения и контроля доступа. Когда мы работаем с этим классом, мы определяем роли, ресурсы и отдельные разрешения. При этом роли и ресурсы могут образовывать иерархии. Чтобы объяснить это, мы покажем пример веб-приложения: +Nette поставляется со встроенной реализацией авторизатора, а именно классом [api:Nette\Security\Permission], предоставляющим программисту легкий и гибкий слой ACL (Access Control List) для управления разрешениями и доступом. Работа с ним заключается в определении ролей, ресурсов и отдельных разрешений. При этом роли и ресурсы позволяют создавать иерархии. Для пояснения покажем пример веб-приложения: -- `guest`: посетитель, не вошедший в систему, которому разрешено читать и просматривать публичную часть сайта, т.е. читать статьи, комментировать и голосовать в опросах. -- `registered`: вошедший в систему пользователь, который, помимо этого, может оставлять комментарии. +- `guest`: не вошедший посетитель, который может читать и просматривать публичную часть веб-сайта, т.е. читать статьи, комментарии и голосовать в опросах +- `registered`: вошедший зарегистрированный пользователь, который дополнительно может комментировать - `admin`: может управлять статьями, комментариями и опросами -Итак, мы определили определенные роли (`guest`, `registered` и `admin`) и упомянули ресурсы (`article`, `comments`, `poll`), к которым пользователи могут получить доступ или предпринять действия (`view`, `vote`, `add`, `edit`). +Мы определили некоторые роли (`guest`, `registered` и `admin`) и упомянули ресурсы (`article`, `comment`, `poll`), к которым пользователи с какой-либо ролью могут получать доступ или выполнять определенные операции (`view`, `vote`, `add`, `edit`). -Мы создаем экземпляр класса Permission и определяем **роли**. Можно использовать наследование ролей, что гарантирует, что, например, пользователь с ролью `admin` может делать то, что может делать обычный посетитель сайта (и, конечно, больше). +Создадим экземпляр класса Permission и определим **роли**. При этом можно использовать так называемое наследование ролей, которое обеспечит, что, например, пользователь с ролью администратора (`admin`) может делать и то, что обычный посетитель веб-сайта (и, конечно, больше). ```php $acl = new Nette\Security\Permission; $acl->addRole('guest'); -$acl->addRole('registered', 'guest'); // 'registered' inherits from 'guest' -$acl->addRole('admin', 'registered'); // and 'admin' inherits from 'registered' +$acl->addRole('registered', 'guest'); // 'registered' наследует от 'guest' +$acl->addRole('admin', 'registered'); // а от него наследует 'admin' ``` -Теперь мы определим список **ресурсов**, к которым пользователи могут получить доступ: +Теперь определим и список **ресурсов**, к которым пользователи могут получать доступ. ```php $acl->addResource('article'); @@ -114,49 +114,49 @@ $acl->addResource('comment'); $acl->addResource('poll'); ``` -Ресурсы также могут использовать наследование, например, мы можем добавить `$acl->addResource('perex', 'article')`. +Ресурсы также могут использовать наследование, можно было бы, например, указать `$acl->addResource('perex', 'article')`. -А теперь самое главное. Мы определим между ними **правила**, определяющие, кто что может делать: +А теперь самое важное. Определим между ними правила, определяющие, кто что может с чем делать: ```php -// everything is denied now +// сначала никто ничего не может делать -// let the guest view articles, comments and polls +// пусть guest может просматривать статьи, комментарии и опросы $acl->allow('guest', ['article', 'comment', 'poll'], 'view'); -// and also vote in polls +// а в опросах дополнительно и голосовать $acl->allow('guest', 'poll', 'vote'); -// the registered inherits the permissions from guesta, we will also let him to comment +// зарегистрированный наследует права от guest, дадим ему дополнительно право комментировать $acl->allow('registered', 'comment', 'add'); -// the administrator can view and edit anything +// администратор может просматривать и редактировать что угодно $acl->allow('admin', $acl::All, ['view', 'edit', 'add']); ``` -Что если мы хотим **препятствовать** кому-то получить доступ к ресурсу? +Что если мы хотим кому-то **запретить** доступ к определенному ресурсу? ```php -// administrator cannot edit polls, that would be undemocractic. +// администратор не может редактировать опросы, это было бы недемократично $acl->deny('admin', 'poll', 'edit'); ``` -Теперь, когда мы создали набор правил, мы можем просто задавать запросы на авторизацию: +Теперь, когда у нас создан список правил, мы можем просто задавать авторизационные запросы: ```php -// can guest view articles? +// может ли guest просматривать статьи? $acl->isAllowed('guest', 'article', 'view'); // true -// can guest edit an article? +// может ли guest редактировать статьи? $acl->isAllowed('guest', 'article', 'edit'); // false -// can guest vote in polls? +// может ли guest голосовать в опросах? $acl->isAllowed('guest', 'poll', 'vote'); // true -// may guest add comments? +// может ли guest комментировать? $acl->isAllowed('guest', 'comment', 'add'); // false ``` -То же самое относится и к зарегистрированному пользователю, но он также может комментировать: +То же самое относится и к зарегистрированному пользователю, однако он может и комментировать: ```php $acl->isAllowed('registered', 'article', 'view'); // true @@ -172,7 +172,7 @@ $acl->isAllowed('admin', 'poll', 'edit'); // false $acl->isAllowed('admin', 'comment', 'edit'); // true ``` -Разрешения также могут оцениваться динамически, и мы можем оставить решение за нашим собственным обратным вызовом, которому передаются все параметры: +Разрешения также могут оцениваться динамически, и мы можем оставить решение на собственном обратном вызове (callback), которому передаются все параметры: ```php $assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { @@ -182,7 +182,7 @@ $assertion = function (Permission $acl, string $role, string $resource, string $ $acl->allow('registered', 'comment', null, $assertion); ``` -Но как решить ситуацию, когда имен ролей и ресурсов недостаточно, т.е. мы хотим определить, что, например, роль `registered` может редактировать ресурс `article` только если она является его автором? Мы будем использовать объекты вместо строк, роль будет объектом [api:Nette\Security\Role] и источником [api:Nette\Security\Resource]. Их методы `getRoleId()` и `getResourceId()` будут возвращать исходные строки: +Как же, например, решить ситуацию, когда недостаточно только названий ролей и ресурсов, но мы хотели бы определить, что, например, роль `registered` может редактировать ресурс `article` только если является его автором? Вместо строк используем объекты, роль будет объектом [api:Nette\Security\Role], а ресурс — объектом [api:Nette\Security\Resource]. Их методы `getRoleId()` соответственно `getResourceId()` будут возвращать исходные строки: ```php class Registered implements Nette\Security\Role @@ -207,19 +207,19 @@ class Article implements Nette\Security\Resource } ``` -А теперь давайте создадим правило: +А теперь создадим правило: ```php $assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { - $role = $acl->getQueriedRole(); // object Registered - $resource = $acl->getQueriedResource(); // object Article + $role = $acl->getQueriedRole(); // объект Registered + $resource = $acl->getQueriedResource(); // объект Article return $role->id === $resource->authorId; }; $acl->allow('registered', 'article', 'edit', $assertion); ``` -ACL запрашивается путем передачи объектов: +И запрос к ACL выполняется передачей объектов: ```php $user = new Registered(/* ... */); @@ -227,7 +227,7 @@ $article = new Article(/* ... */); $acl->isAllowed($user, $article, 'edit'); ``` -Роль может наследоваться от одной или нескольких других ролей. Но что произойдет, если у одного предка определенное действие разрешено, а у другого - запрещено? Тогда в игру вступает *вес роли* - последняя роль в массиве наследуемых ролей имеет наибольший вес, первая - наименьший: +Роль может наследоваться от другой роли или от нескольких ролей. Но что произойдет, если у одного предка действие запрещено, а у другого разрешено? Какими будут права потомка? Это определяется весом роли - последняя указанная роль в списке предков имеет наибольший вес, первая указанная роль - наименьший. Нагляднее это видно из примера: ```php $acl = new Nette\Security\Permission; @@ -239,22 +239,22 @@ $acl->addResource('backend'); $acl->allow('admin', 'backend'); $acl->deny('guest', 'backend'); -// example A: role admin has lower weight than role guest +// случай A: роль admin имеет меньший вес, чем роль guest $acl->addRole('john', ['admin', 'guest']); $acl->isAllowed('john', 'backend'); // false -// example B: role admin has greater weight than role guest +// случай B: роль admin имеет больший вес, чем guest $acl->addRole('mary', ['guest', 'admin']); $acl->isAllowed('mary', 'backend'); // true ``` -Роли и ресурсы также могут быть удалены (`removeRole()`, `removeResource()`), правила также могут быть отменены (`removeAllow()`, `removeDeny()`). Массив всех ролей прямых родителей возвращает `getRoleParents()`. Наследуются ли две сущности друг от друга, возвращается `roleInheritsFrom()` и `resourceInheritsFrom()`. +Роли и ресурсы можно также удалять (`removeRole()`, `removeResource()`), можно отменять и правила (`removeAllow()`, `removeDeny()`). Массив всех прямых родительских ролей возвращает `getRoleParents()`, наследуют ли две сущности друг от друга, возвращают `roleInheritsFrom()` и `resourceInheritsFrom()`. -Добавить как службу .[#toc-add-as-a-service] --------------------------------------------- +Добавление как сервиса +---------------------- -Нам нужно добавить созданный нами ACL в конфигурацию как сервис, чтобы его мог использовать объект `$user`, т.е. чтобы мы могли использовать в коде, например, `$user->isAllowed('article', 'view')`. Для этого мы напишем для него фабрику: +Нами созданный ACL нужно передать в конфигурацию как сервис, чтобы его начал использовать объект `$user`, то есть чтобы можно было использовать в коде, например, `$user->isAllowed('article', 'view')`. Для этой цели напишем для него фабрику: ```php namespace App\Model; @@ -279,7 +279,7 @@ services: - App\Model\AuthorizatorFactory::create ``` -В ведущих вы можете затем проверить разрешения в методе `startup()`, например: +В презентерах затем можно проверить разрешения, например, в методе `startup()`: ```php protected function startup() diff --git a/security/ru/configuration.texy b/security/ru/configuration.texy index edb2c21fd9..6fa7a7cbc4 100644 --- a/security/ru/configuration.texy +++ b/security/ru/configuration.texy @@ -1,71 +1,85 @@ -Настройка контроля доступа -************************** +Конфигурация прав доступа +************************* .[perex] Обзор опций конфигурации для Nette Security. -Если вы используете не весь фреймворк, а только эту библиотеку, прочитайте, [как загрузить конфигурацию |bootstrap:]. +Если вы не используете весь фреймворк, а только эту библиотеку, прочитайте, [как загрузить конфигурацию|bootstrap:]. -Вы можете определить список пользователей в конфигурации для создания [простого аутентификатора |authentication] (`Nette\Security\SimpleAuthenticator`). Поскольку пароли в конфигурации можно прочитать, это решение предназначено только для тестирования. +В конфигурации можно определить список пользователей и таким образом создать [простой аутентификатор|authentication] (`Nette\Security\SimpleAuthenticator`). Поскольку в конфигурации пароли указываются в читаемом виде, это решение подходит только для тестовых целей. ```neon security: - # показывает пользовательскую панель в панели трейси? + # отображать панель пользователя в Tracy Bar? debugger: ... # (bool) по умолчанию true users: # имя: пароль - johndoe: secret123 + frantisek: secretpassword # имя, пароль, роль и другие данные, доступные в идентификаторе - janedoe: - password: secret123 + dobrota: + password: secretpassword roles: [admin] data: ... ``` -Вы также можете определить роли и ресурсы, чтобы создать основу для [авторизатора |authorization] (`Nette\Security\Permission`): +Далее можно определить роли и ресурсы и создать так основу для [авторизатора|authorization] (`Nette\Security\Permission`): ```neon security: roles: guest: - registered: [guest] # registered наследует от guest - admin: [registered] # и admin наследует от registered + registered: [guest] # registered наследует от guest + admin: [registered] # а от него наследует admin resources: article: - comment: [article] # ресурс наследуется от article + comment: [article] # ресурс наследует от article poll: ``` -Хранилище пользователей .[#toc-user-storage] --------------------------------------------- +Хранилище +--------- -Вы можете настроить способ хранения информации о вошедшем в систему пользователе: +Можно настроить, как хранить информацию о вошедшем пользователе: ```neon security: authentication: - # после какого времени бездействия пользователь будет выведен из системы - expiration: 30 minutes # (строка) значение по умолчанию не задано + # через какое время неактивности пользователь будет выведен из системы + expiration: 30 minutes # (string) по умолчанию не установлено - # где хранить информацию о вошедшем в систему пользователе - storage: session # (session|cookie) по умолчанию session + # куда сохранять информацию о вошедшем пользователе + storage: session # (session|cookie) по умолчанию session ``` -Если вы выбрали `cookie` в качестве репозитория, вы также можете установить следующие параметры: +Если вы выберете в качестве хранилища `cookie`, вы можете настроить еще эти опции: ```neon security: authentication: - # печенье jméno - cookieName: userId # (string) по умолчанию - userid + # имя cookie + cookieName: userId # (string) по умолчанию userid - # каким хостам разрешено получать cookie + # домены, которые принимают cookie cookieDomain: 'example.com' # (string|domain) - # ограничения при доступе к кросс-оригинальному запросу + # ограничение при доступе с другого домена cookieSamesite: None # (Strict|Lax|None) по умолчанию Lax ``` + + +Сервисы DI +---------- + +Эти сервисы добавляются в DI-контейнер: + +| Название | Тип | Описание +|---------------------------------------------------------- +| `security.authenticator` | [api:Nette\Security\Authenticator] | [аутентификатор|authentication] +| `security.authorizator` | [api:Nette\Security\Authorizator] | [авторизатор|authorization] +| `security.passwords` | [api:Nette\Security\Passwords] | [хеширование паролей|passwords] +| `security.user` | [api:Nette\Security\User] | текущий пользователь +| `security.userStorage` | [api:Nette\Security\UserStorage] | [#хранилище] diff --git a/security/ru/passwords.texy b/security/ru/passwords.texy index b670774725..4cb5d9ce66 100644 --- a/security/ru/passwords.texy +++ b/security/ru/passwords.texy @@ -2,11 +2,11 @@ ******************* .[perex] -Для обеспечения безопасности наших пользователей мы никогда не храним их пароли в открытом виде, вместо этого мы храним хэш пароля. Хеширование не является обратимой операцией, пароль не может быть восстановлен. Однако пароль можно взломать, и чтобы сделать взлом как можно более трудным, мы должны использовать безопасный алгоритм. Класс [api:Nette\Security\Passwords] поможет нам в этом. +Чтобы обеспечить безопасность наших пользователей, мы не храним их пароли в читаемом виде, а сохраняем только их отпечаток (так называемый хеш). Из отпечатка невозможно восстановить исходный вид пароля. Важно использовать безопасный алгоритм для создания отпечатка. С этим нам поможет класс [api:Nette\Security\Passwords]. -→ [Установка и требования |@home#Installation] +→ [Установка и требования |@home#Установка] -Фреймворк автоматически добавляет сервис `Nette\Security\Passwords` в контейнер DI под именем `security.passwords`, который вы получаете, передавая его с помощью [инъекции зависимостей |dependency-injection:passing-dependencies]: +Фреймворк автоматически добавляет в DI-контейнер сервис типа `Nette\Security\Passwords` под именем `security.passwords`, к которому вы можете получить доступ, запросив его передачу с помощью [dependency injection |dependency-injection:passing-dependencies]. ```php use Nette\Security\Passwords; @@ -21,47 +21,47 @@ class Foo ``` -__construct($algo=PASSWORD_DEFAULT, array $options=[]): string .[method] -======================================================================== +__construct($algo=PASSWORD_DEFAULT, array $options=[]) .[method] +================================================================ -Выбирает, какой [безопасный алгоритм |https://www.php.net/manual/en/password.constants.php] используется для хэширования и как его настроить. +Выбираем, какой [безопасный алгоритм|https://www.php.net/manual/en/password.constants.php] использовать для генерации хеша и конфигурируем его параметры. -По умолчанию используется `PASSWORD_DEFAULT`, поэтому выбор алгоритма остается за PHP. Алгоритм может измениться в новых релизах PHP, когда будут поддерживаться более новые и сильные алгоритмы хэширования. Поэтому вы должны знать, что длина результирующего хэша может измениться. Поэтому вы должны хранить результирующий хэш таким образом, чтобы он мог хранить достаточное количество символов, рекомендуемая ширина 255. +По умолчанию используется `PASSWORD_DEFAULT`, то есть выбор алгоритма оставляется на усмотрение PHP. Алгоритм может измениться в новых версиях PHP, если появятся более новые, более сильные алгоритмы хеширования. Поэтому вы должны быть осведомлены, что длина результирующего хеша может измениться, и вы должны хранить его способом, который может вместить достаточное количество символов, 255 является рекомендуемой шириной. -Вот как можно использовать алгоритм bcrypt и изменить скорость хэширования с помощью параметра cost от значения по умолчанию 10. В 2020 году при стоимости 10 хэширование одного пароля занимает примерно 80 мс, при стоимости 11 - 160 мс, при стоимости 12 - 320 мс, шкала логарифмическая. Чем медленнее, тем лучше, стоимость 10-12 считается достаточно медленной для большинства случаев использования: +Пример настройки скорости хеширования алгоритмом bcrypt изменением параметра cost: (в 2020 году по умолчанию 10, хеширование пароля занимает примерно 80 мс, для cost 11 это около 160 мс, для cost 12 примерно 320 мс, чем медленнее, тем лучше защита, при этом скорость 10-12 уже считается достаточной защитой) ```php -// we will hash passwords with 2^12 (2^cost) iterations of the bcrypt algorithm +// будем хешировать пароли 2^12 (2^cost) итерациями алгоритма bcrypt $passwords = new Passwords(PASSWORD_BCRYPT, ['cost' => 12]); ``` -С инъекцией зависимостей: +С помощью dependency injection: ```neon services: security.passwords: Nette\Security\Passwords(::PASSWORD_BCRYPT, [cost: 12]) ``` -hash(string $passwords): string .[method] -========================================= +hash(string $password): string .[method] +======================================== -Генерирует хэш пароля. +Генерирует хеш пароля. ```php -$res = $passwords->hash($password); // Hashes the password +$res = $passwords->hash($password); // Хеширует пароль ``` -Результат `$res` представляет собой строку, которая, помимо самого хэша, содержит идентификатор используемого алгоритма, его настройки и криптографическую соль (случайные данные, гарантирующие, что для одного и того же пароля будет сгенерирован другой хэш). Таким образом, обеспечивается обратная совместимость, например, при изменении параметров можно проверить хэши, сохраненные с использованием предыдущих настроек. Весь результат сохраняется в базе данных, поэтому нет необходимости хранить соль или настройки отдельно. +Результат `$res` — это строка, которая помимо самого хеша содержит идентификатор использованного алгоритма, его настройки и криптографическую соль (случайные данные, обеспечивающие, что для одного и того же пароля будет сгенерирован разный хеш). Таким образом, он обратно совместим: если, например, вы измените параметры, то и хеши, сохраненные с использованием предыдущих настроек, можно будет проверить. Весь этот результат сохраняется в базе данных, поэтому нет необходимости хранить соль или настройки отдельно. verify(string $password, string $hash): bool .[method] ====================================================== -Выясняет, совпадает ли заданный пароль с заданным хэшем. Получить `$hash` из базы данных по имени пользователя или адресу электронной почты. +Проверяет, соответствует ли данный пароль данному отпечатку. `$hash` получите из базы данных по указанному имени пользователя или адресу электронной почты. ```php if ($passwords->verify($password, $hash)) { - // Correct password + // правильный пароль } ``` @@ -69,13 +69,13 @@ if ($passwords->verify($password, $hash)) { needsRehash(string $hash): bool .[method] ========================================= -Выясняет, соответствует ли хэш параметрам, заданным в конструкторе. +Проверяет, соответствует ли хеш опциям, указанным в конструкторе. -Используйте этот метод, когда вы, например, изменяете параметры хэша. Проверка пароля будет использовать параметры, сохраненные вместе с хэшем, и если `needsRehash()` вернет true, вам придется снова вычислить хэш, на этот раз с обновленными параметрами, и снова сохранить его в базе данных. Это гарантирует, что хэши паролей будут автоматически "обновляться" при входе пользователей в систему. +Полезно использовать в тот момент, когда, например, вы меняете скорость хеширования. Проверка происходит согласно сохраненным настройкам, и если `needsRehash()` возвращает `true`, то необходимо снова создать хеш, на этот раз с новыми параметрами, и снова сохранить его в базе данных. Таким образом автоматически "обновляются" сохраненные хеши при входе пользователей. ```php if ($passwords->needsRehash($hash)) { $hash = $passwords->hash($password); - // store $hash to database + // сохранить $hash в базу данных } ``` diff --git a/security/sl/@home.texy b/security/sl/@home.texy index 6041c37ee1..e60ee63f03 100644 --- a/security/sl/@home.texy +++ b/security/sl/@home.texy @@ -1,14 +1,14 @@ -Varnost -******* +Nette Security +************** .[perex] -Paket `nette/security` je odgovoren za [preverjanje pristnosti uporabnikov |authentication], [nadzor dostopa |authorization] in [hashanje gesel |passwords]. +Paket `nette/security` skrbi za [prijavo uporabnikov |authentication], [preverjanje dovoljenj |authorization] in [zgoščevanje gesel |passwords]. -Namestitev .[#toc-installation] -------------------------------- +Namestitev +---------- -Prenesite in namestite paket s [programom Composer |best-practices:composer]: +Knjižnico prenesete in namestite z orodjem [Composer|best-practices:composer]: ```shell composer require nette/security diff --git a/security/sl/@left-menu.texy b/security/sl/@left-menu.texy index 6b9488351f..fbcf05d429 100644 --- a/security/sl/@left-menu.texy +++ b/security/sl/@left-menu.texy @@ -1,7 +1,7 @@ Nette Security ************** -- [Pregled |@home] -- [Preverjanje pristnosti |Authentication] -- [Avtorizacija |Authorization] -- [Prekrivanje gesel |passwords] -- [Konfiguracija |Configuration] +- [Uvod |@home] +- [Avtentikacija |authentication] +- [Avtorizacija |authorization] +- [Zgoščevanje gesel |passwords] +- [Konfiguracija |configuration] diff --git a/security/sl/@meta.texy b/security/sl/@meta.texy new file mode 100644 index 0000000000..724324bee5 --- /dev/null +++ b/security/sl/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokumentacija}} diff --git a/security/sl/authentication.texy b/security/sl/authentication.texy index 5bc0fb4838..def39def28 100644 --- a/security/sl/authentication.texy +++ b/security/sl/authentication.texy @@ -1,48 +1,48 @@ -Preverjanje pristnosti uporabnikov -********************************** +Prijava uporabnikov (Avtentikacija) +*********************************** <div class=perex> -Malo ali nič spletne aplikacije ne potrebujejo mehanizma za prijavo uporabnika ali preverjanje njegovih pravic. V tem poglavju bomo govorili o: +Skoraj nobena spletna aplikacija se ne more izogniti mehanizmu prijave uporabnikov in preverjanja uporabniških dovoljenj. V tem poglavju bomo govorili o: -- prijavi in odjavi uporabnika -- lastnih avtentifikatorjih in avtorizatorjih +- prijava in odjava uporabnikov +- lastnih avtentikatorjih </div> -→ [Namestitev in zahteve |@home#Installation] +→ [Namestitev in zahteve |@home#Namestitev] -V primerih bomo uporabili objekt razreda [api:Nette\Security\User], ki predstavlja trenutnega uporabnika in ga dobite s posredovanjem z uporabo [vbrizgavanja odvisnosti |dependency-injection:passing-dependencies]. V predstavitvah preprosto pokličite `$user = $this->getUser()`. +V primerih bomo uporabljali objekt razreda [api:Nette\Security\User], ki predstavlja trenutnega uporabnika in do katerega pridete tako, da si ga pustite predati s pomočjo [dependency injection |dependency-injection:passing-dependencies]. V presenterjih je dovolj samo poklicati `$user = $this->getUser()`. -Preverjanje pristnosti .[#toc-authentication] -============================================= +Avtentikacija +============= -Avtentikacija pomeni **prijavo uporabnika**, tj. postopek, med katerim se preveri identiteta uporabnika. Uporabnik se običajno identificira z uporabniškim imenom in geslom. Preverjanje opravi tako imenovani [avtentifikator |#authenticator]. Če prijava ni uspešna, se vrže `Nette\Security\AuthenticationException`. +Avtentikacija pomeni **prijavo uporabnikov**, torej proces, pri katerem se preverja, ali je uporabnik res tisti, za katerega se izdaja. Običajno se dokazuje z uporabniškim imenom in geslom. Preverjanje izvede t.i. [#avtentikator]. Če prijava ne uspe, se sproži `Nette\Security\AuthenticationException`. ```php try { $user->login($username, $password); } catch (Nette\Security\AuthenticationException $e) { - $this->flashMessage('The username or password you entered is incorrect.'); + $this->flashMessage('Uporabniško ime ali geslo je napačno'); } ``` -Tako se uporabnik odjavi: +Na ta način uporabnika odjavite: ```php $user->logout(); ``` -In preverjanje, ali je uporabnik prijavljen: +In ugotavljanje, ali je prijavljen: ```php -echo $user->isLoggedIn() ? 'yes' : 'no'; +echo $user->isLoggedIn() ? 'da' : 'ne'; ``` -Preprosto, kajne? Za vse varnostne vidike poskrbi Nette. +Zelo preprosto, kajne? In vse varnostne vidike rešuje Nette za vas. -V programu Presenter lahko v metodi `startup()` preverite prijavo in neprijavljenega uporabnika preusmerite na prijavno stran. +V presenterjih lahko preverite prijavo v metodi `startup()` in neprijavljenega uporabnika preusmerite na prijavno stran. ```php protected function startup() @@ -55,39 +55,38 @@ protected function startup() ``` -Iztek veljavnosti .[#toc-expiration] -==================================== +Potek +===== -Prijava uporabnika poteče skupaj s [potekom veljavnosti shrambe |#Storage for Logged User], ki je običajno seja (glejte nastavitev [poteka seje |http:configuration#session] ). -Lahko pa nastavite tudi krajši časovni interval, po katerem se uporabnik odjavi. V ta namen se uporablja metoda `setExpiration()`, ki se kliče pred `login()`. Kot parameter navedite niz z relativnim časom: +Prijava uporabnika poteče skupaj s [potekom shrambe |#Shramba prijavljenega uporabnika], ki je običajno seja (glej nastavitve [poteka seje |http:configuration#Seja]). Lahko pa nastavite tudi krajši časovni interval, po preteku katerega pride do odjave uporabnika. Za to služi metoda `setExpiration()`, ki se kliče pred `login()`. Kot parameter navedite niz z relativnim časom: ```php // prijava poteče po 30 minutah neaktivnosti $user->setExpiration('30 minutes'); -// preklicati nastavitev izteka veljavnosti +// preklic nastavljenega poteka $user->setExpiration(null); ``` -Metoda `$user->getLogoutReason()` pove, ali je bil uporabnik odjavljen, ker se je časovni interval iztekel. Vrne bodisi konstanto `Nette\Security\UserStorage::LogoutInactivity`, če se je čas iztekel, bodisi `UserStorage::LogoutManual`, če je bila klicana metoda `logout()`. +Ali je bil uporabnik odjavljen zaradi poteka časovnega intervala, razkrije metoda `$user->getLogoutReason()`, ki vrača bodisi konstanto `Nette\Security\UserStorage::LogoutInactivity` (potekel časovni limit) ali `UserStorage::LogoutManual` (odjavljen z metodo `logout()`). -Avtentifikator .[#toc-authenticator] -==================================== +Avtentikator +============ -To je objekt, ki preveri podatke za prijavo, tj. običajno ime in geslo. Trivialna izvedba je razred [api:Nette\Security\SimpleAuthenticator], ki ga je mogoče opredeliti v [konfiguraciji |configuration]: +Gre za objekt, ki preverja prijavne podatke, torej praviloma ime in geslo. Trivialna oblika je razred [api:Nette\Security\SimpleAuthenticator], ki ga lahko definiramo v [konfiguraciji |configuration]: ```neon security: users: # ime: geslo - johndoe: secret123 - kathy: evenmoresecretpassword + frantisek: tajnegeslo + katka: jestetajnejsigeslo ``` -Ta rešitev je primernejša za namene testiranja. Pokazali vam bomo, kako ustvariti avtentifikator, ki bo preverjal poverilnice glede na tabelo podatkovne zbirke. +Ta rešitev je primerna bolj za testne namene. Pokazali si bomo, kako ustvariti avtentikator, ki bo preverjal prijavne podatke glede na podatkovno tabelo. -Avtentifikator je objekt, ki implementira vmesnik [api:Nette\Security\Authenticator] z metodo `authenticate()`. Njegova naloga je, da vrne tako imenovano [identiteto |#identity] ali pa vrže izjemo `Nette\Security\AuthenticationException`. Prav tako bi bilo mogoče zagotoviti drobno kodo napake `Authenticator::IdentityNotFound` ali `Authenticator::InvalidCredential`. +Avtentikator je objekt, ki implementira vmesnik [api:Nette\Security\Authenticator] z metodo `authenticate()`. Njegova naloga je bodisi vrniti t.i. [identiteto |#Identiteta] ali sprožiti izjemo `Nette\Security\AuthenticationException`. Pri njej bi bilo mogoče še navesti kodo napake za natančnejše razlikovanje nastale situacije: `Authenticator::IdentityNotFound` in `Authenticator::InvalidCredential`. ```php use Nette; @@ -108,25 +107,25 @@ class MyAuthenticator implements Nette\Security\Authenticator ->fetch(); if (!$row) { - throw new Nette\Security\AuthenticationException('User not found.'); + throw new Nette\Security\AuthenticationException('Uporabnik ni najden.'); } if (!$this->passwords->verify($password, $row->password)) { - throw new Nette\Security\AuthenticationException('Invalid password.'); + throw new Nette\Security\AuthenticationException('Neveljavno geslo.'); } return new SimpleIdentity( $row->id, - $row->role, // ali niz vlog + $row->role, // ali polje več vlog ['name' => $row->username], ); } } ``` -Razred MyAuthenticator komunicira s podatkovno zbirko prek [Nette Database Explorerja |database:explorer] in dela s tabelo `users`, kjer stolpec `username` vsebuje uporabniško prijavno ime, stolpec `password` pa [hash |passwords]. Po preverjanju imena in gesla vrne identiteto z ID uporabnika, vlogo (stolpec `role` v tabeli), ki jo bomo omenili [kasneje |#roles], in polje z dodatnimi podatki (v našem primeru uporabniško ime). +Razred MyAuthenticator komunicira s podatkovno bazo preko [Nette Database Explorer |database:explorer] in dela s tabelo `users`, kjer je v stolpcu `username` prijavno ime uporabnika in v stolpcu `password` [odtis gesla |passwords]. Po preverjanju imena in gesla vrača identiteto, ki nosi ID uporabnika, njegovo vlogo (stolpec `role` v tabeli), o kateri si bomo več povedali [kasneje |authorization#Vloge], in polje z dodatnimi podatki (v našem primeru uporabniško ime). -Avtentifikator bomo dodali v konfiguracijo [kot storitev |dependency-injection:services] vsebnika DI: +Avtentikator še dodamo v konfiguracijo [kot storitev |dependency-injection:services] DI vsebnika: ```neon services: @@ -134,23 +133,23 @@ services: ``` -$onLoggedIn, $onLoggedOut Dogodki +Dogodki $onLoggedIn, $onLoggedOut --------------------------------- -Objekt `Nette\Security\User` ima [dogodka |nette:glossary#Events] `$onLoggedIn` in `$onLoggedOut`, zato lahko dodate povratne klice, ki se sprožijo po uspešni prijavi ali po odjavi uporabnika. +Objekt `Nette\Security\User` ima [dogodke |nette:glossary#Dogodki eventi] `$onLoggedIn` in `$onLoggedOut`, lahko torej dodate callbacke, ki se sprožijo po uspešni prijavi oz. po odjavi uporabnika. ```php $user->onLoggedIn[] = function () { - // uporabnik se je pravkar prijavil + // uporabnik je bil pravkar prijavljen }; ``` -Identiteta .[#toc-identity] -=========================== +Identiteta +========== -Identiteta je niz informacij o uporabniku, ki jih vrne avtentifikator in se nato shranijo v sejo ter prikličejo z uporabo `$user->getIdentity()`. Tako lahko dobimo id, vloge in druge podatke o uporabniku, kot smo jih posredovali v avtentifikatorju: +Identiteta predstavlja nabor informacij o uporabniku, ki jih vrača avtentikator in ki se nato shranjujejo v seji ter jih pridobivamo s pomočjo `$user->getIdentity()`. Lahko torej pridobimo id, vloge in druge uporabniške podatke, tako kot smo si jih predali v avtentikatorju: ```php $user->getIdentity()->getId(); @@ -158,26 +157,26 @@ $user->getIdentity()->getId(); $user->getIdentity()->getRoles(); -// do podatkov o uporabniku lahko dostopate kot do lastnosti -// ime, ki smo ga posredovali v MyAuthenticator +// uporabniški podatki so dostopni kot lastnosti +// ime, ki smo si ga predali v MyAuthenticator $user->getIdentity()->name; ``` -Pomembno je, da ko se uporabnik odjavi z uporabo `$user->logout()`, **identiteta ni izbrisana** in je še vedno na voljo. Torej, če identiteta obstaja, sama po sebi ne zagotavlja, da je uporabnik tudi prijavljen. Če želimo identiteto izrecno izbrisati, uporabnika odjavimo s `logout(true)`. +Pomembno je, da se pri odjavi s pomočjo `$user->logout()` **identiteta ne izbriše** in je še naprej na voljo. Torej, čeprav ima uporabnik identiteto, ni nujno prijavljen. Če bi želeli identiteto eksplicitno izbrisati, odjavimo uporabnika s klicem `logout(true)`. -Zaradi tega lahko še vedno predvidevamo, kateri uporabnik je v računalniku, in na primer v e-trgovini prikažemo prilagojene ponudbe, vendar lahko njegove osebne podatke prikažemo šele po prijavi. +Zahvaljujoč temu lahko še naprej predvidevate, kateri uporabnik je za računalnikom in mu na primer v e-trgovini prikazujete personalizirane ponudbe, vendar mu njegove osebne podatke lahko prikažete šele po prijavi. -Identiteta je objekt, ki implementira vmesnik [api:Nette\Security\IIdentity], privzeta implementacija je [api:Nette\Security\SimpleIdentity]. In kot smo že omenili, se identiteta hrani v seji, zato če na primer spremenimo vlogo katerega od prijavljenih uporabnikov, bodo stari podatki ostali v identiteti, dokler se ta ponovno ne prijavi. +Identiteta je objekt, ki implementira vmesnik [api:Nette\Security\IIdentity], privzeta implementacija je [api:Nette\Security\SimpleIdentity]. In kot je bilo omenjeno, se vzdržuje v seji, zato če na primer spremenimo vlogo katerega od prijavljenih uporabnikov, ostanejo stari podatki v njegovi identiteti vse do njegove ponovne prijave. -Shranjevanje za prijavljenega uporabnika .[#toc-storage-for-logged-user] -======================================================================== +Shramba prijavljenega uporabnika +================================ -Dve osnovni informaciji o uporabniku, tj. ali je prijavljen in njegova [identiteta |#identity], sta običajno shranjeni v seji. Ki jih je mogoče spremeniti. Za shranjevanje teh informacij je odgovoren objekt, ki implementira vmesnik `Nette\Security\UserStorage`. Obstajata dve standardni izvedbi, prva prenaša podatke v seji, druga pa v piškotku. To sta razreda `Nette\Bridges\SecurityHttp\SessionStorage` in `CookieStorage`. Shranjevanje lahko izberete in konfigurirate zelo priročno v konfiguraciji [varnost › avtentikacija |configuration]. +Dve osnovni informaciji o uporabniku, torej ali je prijavljen in njegova [#identiteta], se praviloma prenašata v seji. Kar pa je mogoče spremeniti. Za shranjevanje teh informacij skrbi objekt, ki implementira vmesnik `Nette\Security\UserStorage`. Na voljo sta dve standardni implementaciji, prva prenaša podatke v seji in druga v piškotku. Gre za razreda `Nette\Bridges\SecurityHttp\SessionStorage` in `CookieStorage`. Izbrati si shrambo in jo konfigurirati lahko zelo udobno v konfiguraciji [security › authentication |configuration#Shramba]. -Prav tako lahko natančno nadzirate, kako bo potekalo shranjevanje identitete (*sleep*) in obnavljanje (*wakeup*). Vse, kar potrebujete, je, da avtentifikator implementira vmesnik `Nette\Security\IdentityHandler`. Ta ima dve metodi: `sleepIdentity()` se pokliče, preden se identiteta zapiše v pomnilnik, `wakeupIdentity()` pa se pokliče, ko se identiteta prebere. Metodi lahko spremenita vsebino identitete ali jo nadomestita z novim objektom, ki se vrne. Metoda `wakeupIdentity()` lahko vrne celo `null`, ki uporabnika odjavi. +Nadalje lahko vplivate na to, kako natančno bo potekalo shranjevanje identitete (*sleep*) in obnavljanje (*wakeup*). Dovolj je, da avtentikator implementira vmesnik `Nette\Security\IdentityHandler`. Ta ima dve metodi: `sleepIdentity()` se kliče pred zapisom identitete v shrambo in `wakeupIdentity()` po njenem prebranju. Metodi lahko vsebino identitete spremenita, ali pa jo nadomestita z novim objektom, ki ga vrneta. Metoda `wakeupIdentity()` lahko celo vrne `null`, s čimer uporabnika odjavi. -Kot primer bomo prikazali rešitev pogostega vprašanja, kako posodobiti identitetne vloge takoj po obnovitvi iz seje. V metodi `wakeupIdentity()` identiteti posredujemo trenutne vloge, npr. iz zbirke podatkov: +Kot primer si bomo pokazali rešitev pogostega vprašanja, kako posodobiti vloge v identiteti takoj po nalaganju iz seje. V metodi `wakeupIdentity()` predamo v identiteto trenutne vloge npr. iz podatkovne baze: ```php final class Authenticator implements @@ -185,25 +184,25 @@ final class Authenticator implements { public function sleepIdentity(IIdentity $identity): IIdentity { - // tukaj lahko spremenite identiteto pred shranjevanjem po prijavi, - // vendar tega zdaj ne potrebujemo. + // tukaj je mogoče spremeniti identiteto pred zapisom v shrambo po prijavi, + // vendar tega zdaj ne potrebujemo return $identity; } public function wakeupIdentity(IIdentity $identity): ?IIdentity { - // posodabljanje vlog v identiteti + // posodobitev vlog v identiteti $userId = $identity->getId(); $identity->setRoles($this->facade->getUserRoles($userId)); return $identity; } ``` -Zdaj se vrnemo k shranjevanju na podlagi piškotkov. Z njo lahko ustvarite spletno mesto, na katerem se lahko uporabniki prijavijo, ne da bi jim bilo treba uporabljati seje. Torej ni treba pisati na disk. Navsezadnje tako deluje spletno mesto, ki ga zdaj berete, vključno s forumom. V tem primeru je implementacija spletne strani `IdentityHandler` nujna. V piškotek bomo shranili le naključni žeton, ki predstavlja prijavljenega uporabnika. +In zdaj se vrnemo k shrambi na osnovi piškotkov. Dovoljuje vam ustvariti spletno stran, kjer se lahko prijavljajo uporabniki, ne da bi potrebovali seje. Torej ni treba zapisovati na disk. Navsezadnje tako deluje tudi spletna stran, ki jo pravkar berete, vključno s forumom. V tem primeru je implementacija `IdentityHandler` nujna. V piškotek bomo namreč shranjevali samo naključni žeton, ki predstavlja prijavljenega uporabnika. -Zato najprej v konfiguraciji nastavimo želeno shranjevanje z uporabo `security › authentication › storage: cookie`. +Najprej torej v konfiguraciji nastavimo zahtevano shrambo s pomočjo `security › authentication › storage: cookie`. -V zbirko podatkov bomo dodali stolpec `authtoken`, v katerem bo imel vsak uporabnik [popolnoma naključen, edinstven in nezgrešljiv |utils:random] niz zadostne dolžine (vsaj 13 znakov). V shrambi `CookieStorage` je v piškotku shranjena le vrednost `$identity->getId()`, zato v metodi `sleepIdentity()` prvotno identiteto nadomestimo s posrednikom z `authtoken` v ID, nasprotno pa v metodi `wakeupIdentity()` obnovimo celotno identiteto iz zbirke podatkov v skladu z authtokenom: +V podatkovni bazi si ustvarimo stolpec `authtoken`, v katerem bo imel vsak uporabnik [popolnoma naključen, unikaten in neugotovljiv |utils:random] niz zadostne dolžine (vsaj 13 znakov). Shramba `CookieStorage` prenaša v piškotku samo vrednost `$identity->getId()`, zato v `sleepIdentity()` originalno identiteto nadomestimo z nadomestno z `authtoken` v ID, nasprotno pa v metodi `wakeupIdentity()` glede na authtoken preberemo celotno identiteto iz podatkovne baze: ```php final class Authenticator implements @@ -212,21 +211,21 @@ final class Authenticator implements public function authenticate(string $username, string $password): SimpleIdentity { $row = $this->db->fetch('SELECT * FROM user WHERE username = ?', $username); - // preverjanje gesla + // preverimo geslo ... - // vrnemo identiteto z vsemi podatki iz zbirke podatkov + // vrnemo identiteto z vsemi podatki iz podatkovne baze return new SimpleIdentity($row->id, null, (array) $row); } public function sleepIdentity(IIdentity $identity): SimpleIdentity { - // vrnemo proxy identiteto, kjer je ID avtotoken + // vrnemo nadomestno identiteto, kjer bo v ID authtoken return new SimpleIdentity($identity->authtoken); } public function wakeupIdentity(IIdentity $identity): ?SimpleIdentity { - // nadomestimo proxy identiteto s polno identiteto, kot v funkciji authenticate() + // nadomestno identiteto nadomestimo s polno identiteto, kot v authenticate() $row = $this->db->fetch('SELECT * FROM user WHERE authtoken = ?', $identity->getId()); return $row ? new SimpleIdentity($row->id, null, (array) $row) @@ -236,16 +235,16 @@ final class Authenticator implements ``` -Več neodvisnih avtentikacij .[#toc-multiple-independent-authentications] -======================================================================== +Več neodvisnih prijav +===================== -Na enem spletnem mestu in v eni seji je mogoče imeti več neodvisno prijavljenih uporabnikov. Če na primer želimo imeti ločeno avtentikacijo za frontend in backend, bomo za vsakega od njiju samo nastavili edinstven imenski prostor seje: +Hkrati je mogoče v okviru ene spletne strani in ene seje imeti več neodvisnih prijavljenih uporabnikov. Če na primer želimo imeti na spletni strani ločeno avtentikacijo za administracijo in javni del, je dovolj vsaki od njih nastaviti lastno ime: ```php $user->getStorage()->setNamespace('backend'); ``` -Upoštevati je treba, da je to treba nastaviti na vseh mestih, ki pripadajo istemu segmentu. Pri uporabi predstavnikov bomo imenski prostor nastavili v skupnem predniku - običajno je to BasePresenter. V ta namen bomo razširili metodo [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()]: +Pomembno je vedeti, da moramo imenski prostor nastaviti vedno na vseh mestih, ki pripadajo danemu delu. Če uporabljamo presenterje, nastavimo imenski prostor v skupnem predniku za dani del - običajno BasePresenter. To storimo z razširitvijo metode [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()]: ```php public function checkRequirements($element): void @@ -256,10 +255,10 @@ public function checkRequirements($element): void ``` -Več overiteljev .[#toc-multiple-authenticators] ------------------------------------------------ +Več avtentikatorjev +------------------- -Delitev aplikacije na segmente z neodvisnim preverjanjem pristnosti običajno zahteva različne overitelje. Vendar pa bi registracija dveh razredov, ki implementirata avtentifikator, v konfiguracijske storitve sprožila napako, ker Nette ne bi vedel, kateri od njiju naj bo [samodejno povezan |dependency-injection:autowiring] z objektom `Nette\Security\User`. Zato moramo z objektom `autowired: self` omejiti avtentikacijo zanju, tako da se aktivira le, kadar je njun razred posebej zahtevan: +Razdelitev aplikacije na dele z neodvisno prijavo večinoma zahteva tudi različne avtentikatorje. Takoj ko pa bi v konfiguraciji storitev registrirali dva razreda, ki implementirata Authenticator, Nette ne bi vedelo, katerega od njiju samodejno dodeliti objektu `Nette\Security\User`, in bi prikazalo napako. Zato moramo za avtentikatorje [autowiring |dependency-injection:autowiring] omejiti tako, da deluje, samo ko nekdo zahteva konkreten razred, npr. FrontAuthenticator, kar dosežemo z izbiro `autowired: self`: ```neon services: @@ -278,7 +277,7 @@ class SignPresenter extends Nette\Application\UI\Presenter } ``` -Avtentifikator moramo nastaviti objektu User le pred klicem metode [login() |api:Nette\Security\User::login()], kar običajno pomeni v povratnem klicu obrazca za prijavo: +Avtentikator objekta User nastavimo pred klicem metode [login() |api:Nette\Security\User::login()], torej običajno v kodi obrazca, ki ga prijavlja: ```php $form->onSuccess[] = function (Form $form, \stdClass $data) { diff --git a/security/sl/authorization.texy b/security/sl/authorization.texy index c4b1837f1e..7851fd7e4f 100644 --- a/security/sl/authorization.texy +++ b/security/sl/authorization.texy @@ -1,48 +1,48 @@ -Nadzor dostopa (avtorizacija) -***************************** +Preverjanje dovoljenj (Avtorizacija) +************************************ .[perex] -Avtorizacija določa, ali ima uporabnik zadostne pravice, na primer za dostop do določenega vira ali za izvedbo dejanja. Avtorizacija predpostavlja predhodno uspešno preverjanje pristnosti, tj. da je uporabnik prijavljen. +Avtorizacija ugotavlja, ali ima uporabnik zadostna dovoljenja, na primer za dostop do določenega vira ali za izvedbo neke akcije. Avtorizacija predpostavlja predhodno uspešno avtentikacijo, tj. da je uporabnik prijavljen. -→ [Namestitev in zahteve |@home#Installation] +→ [Namestitev in zahteve |@home#Namestitev] -V primerih bomo uporabili objekt razreda [api:Nette\Security\User], ki predstavlja trenutnega uporabnika in ga dobite s posredovanjem z uporabo [vbrizgavanja odvisnosti |dependency-injection:passing-dependencies]. V predstavitvah preprosto pokličite `$user = $this->getUser()`. +V primerih bomo uporabljali objekt razreda [api:Nette\Security\User], ki predstavlja trenutnega uporabnika in do katerega pridete tako, da si ga pustite predati s pomočjo [dependency injection |dependency-injection:passing-dependencies]. V presenterjih je dovolj samo poklicati `$user = $this->getUser()`. -Za zelo preprosta spletna mesta z administracijo, kjer se pravice uporabnikov ne razlikujejo, je mogoče kot merilo za avtorizacijo uporabiti že znano metodo `isLoggedIn()`. Z drugimi besedami: ko je uporabnik enkrat prijavljen, ima dovoljenja za vsa dejanja in obratno. +Pri zelo preprostih spletnih straneh z administracijo, kjer se ne razlikujejo dovoljenja uporabnikov, je mogoče kot avtorizacijski kriterij uporabiti že znano metodo `isLoggedIn()`. Z drugimi besedami: takoj ko je uporabnik prijavljen, ima vsa dovoljenja in obratno. ```php if ($user->isLoggedIn()) { // je uporabnik prijavljen? - deleteItem(); // če je tako, lahko izbriše element. + deleteItem(); // potem ima dovoljenje za operacijo } ``` -Vloge .[#toc-roles] -------------------- +Vloge +----- -Namen vlog je omogočiti natančnejše upravljanje dovoljenj in ostati neodvisen od uporabniškega imena. Takoj ko se uporabnik prijavi, se mu dodeli ena ali več vlog. Same vloge so lahko preprosti nizi, na primer `admin`, `member`, `guest` itd. Navedene so v drugem argumentu konstruktorja `SimpleIdentity`, in sicer kot niz ali polje. +Namen vlog je ponuditi natančnejše upravljanje dovoljenj in ostati neodvisen od uporabniškega imena. Vsakemu uporabniku takoj ob prijavi dodelimo eno ali več vlog, v katerih bo nastopal. Vloge so lahko preprosti nizi, na primer `admin`, `member`, `guest`, ipd. Navajajo se kot drugi parameter konstruktorja `SimpleIdentity`, bodisi kot niz ali polje nizov - vlog. -Kot merilo avtorizacije bomo zdaj uporabili metodo `isInRole()`, ki preveri, ali je uporabnik v dani vlogi: +Kot avtorizacijski kriterij bomo zdaj uporabili metodo `isInRole()`, ki pove, ali uporabnik nastopa v dani vlogi: ```php -if ($user->isInRole('admin')) { // ali je vloga upravitelja dodeljena uporabniku? - deleteItem(); // če je, lahko izbriše element +if ($user->isInRole('admin')) { // je uporabnik v vlogi admina? + deleteItem(); // potem ima dovoljenje za operacijo } ``` -Kot že veste, odjava uporabnika ne izbriše njegove identitete. Tako metoda `getIdentity()` še vedno vrne objekt `SimpleIdentity`, vključno z vsemi dodeljenimi vlogami. Okvir Nette se drži načela "manj kode, več varnosti", zato vam pri preverjanju vlog ni treba preverjati, ali je uporabnik tudi prijavljen. Metoda `isInRole()` deluje z **učinkovitimi vlogami**, tj. če je uporabnik prijavljen, se uporabijo vloge, dodeljene identiteti, če ni prijavljen, se namesto tega uporabi samodejna posebna vloga `guest`. +Kot že veste, se po odjavi uporabnika njegova identiteta ne izbriše nujno. Torej metoda `getIdentity()` še naprej vrača objekt `SimpleIdentity`, vključno z vsemi dodeljenimi vlogami. Nette Framework zagovarja načelo »manj kode, več varnosti«, kjer manj pisanja vodi k bolj varnemu kodu, zato pri ugotavljanju vlog ni treba še preverjati, ali je uporabnik prijavljen. Metoda `isInRole()` dela z **efektivnimi vlogami,** tj. če je uporabnik prijavljen, izhaja iz vlog, navedenih v identiteti, če ni prijavljen, ima samodejno posebno vlogo `guest`. -Avtorizator .[#toc-authorizator] --------------------------------- +Avtorizator +----------- -Poleg vlog bomo uvedli tudi izraza resource in operation: +Poleg vlog bomo uvedli še pojma vir in operacija: -- **vloga** je atribut uporabnika - na primer moderator, urednik, obiskovalec, registrirani uporabnik, administrator, ... -- **vložek** je logična enota aplikacije - članek, stran, uporabnik, element menija, anketa, voditelj, ... -- **operacija** je določena dejavnost, ki jo uporabnik lahko ali ne sme opraviti z *virem* - ogled, urejanje, brisanje, glasovanje, ... +- **vloga** je lastnost uporabnika - npr. moderator, urednik, obiskovalec, registriran uporabnik, skrbnik... +- **vir** (*resource*) je nek logični element spletne strani - članek, stran, uporabnik, postavka v meniju, anketa, presenter, ... +- **operacija** (*operation*) je neka konkretna dejavnost, ki jo uporabnik lahko ali ne more opravljati z virom - na primer izbrisati, urediti, ustvariti, glasovati, ... -Avtorizator je objekt, ki odloča o tem, ali ima določena *role* dovoljenje za izvajanje določene *operacije* z določenim *izdelkom*. To je objekt, ki implementira vmesnik [api:Nette\Security\Authorizator] s samo eno metodo `isAllowed()`: +Avtorizator je objekt, ki odloča, ali ima dana *vloga* dovoljenje za izvedbo določene *operacije* z določenim *virom*. Gre za objekt, ki implementira vmesnik [api:Nette\Security\Authorizator] z edino metodo `isAllowed()`: ```php class MyAuthorizator implements Nette\Security\Authorizator @@ -63,50 +63,50 @@ class MyAuthorizator implements Nette\Security\Authorizator } ``` -Avtorizator dodamo v konfiguracijo [kot storitev |dependency-injection:services] vsebnika DI: +Avtorizator dodamo v konfiguracijo [kot storitev |dependency-injection:services] DI vsebnika: ```neon services: - MyAuthorizator ``` -V nadaljevanju je prikazan primer uporabe. Upoštevajte, da tokrat kličemo metodo `Nette\Security\User::isAllowed()`, in ne avtorizatorjeve, zato ni prvega parametra `$role`. Ta metoda zaporedno kliče `MyAuthorizator::isAllowed()` za vse uporabniške vloge in vrne true, če ima vsaj ena od njih dovoljenje. +In sledi primer uporabe. Pozor, tokrat kličemo metodo `Nette\Security\User::isAllowed()`, ne avtorizatorja, zato tam ni prvega parametra `$role`. Ta metoda kliče `MyAuthorizator::isAllowed()` postopoma za vse uporabnikove vloge in vrača true, če vsaj ena od njih ima dovoljenje. ```php -if ($user->isAllowed('file')) { // ali lahko uporabnik z virom 'file' počne vse? +if ($user->isAllowed('file')) { // lahko uporabnik počne karkoli z virom 'file'? useFile(); } -if ($user->isAllowed('file', 'delete')) { // ali sme uporabnik izbrisati vir 'file'? +if ($user->isAllowed('file', 'delete')) { // lahko nad virom 'file' izvede 'delete'? deleteFile(); } ``` -Oba argumenta sta neobvezna, njuna privzeta vrednost pa pomeni *vse*. +Oba parametra sta neobvezna, privzeta vrednost `null` ima pomen *karkoli*. -Dovoljenje ACL .[#toc-permission-acl] -------------------------------------- +Permission ACL +-------------- -Nette ima vgrajeno implementacijo avtorizatorja, razred [api:Nette\Security\Permission], ki ponuja lahek in prilagodljiv sloj ACL (Access Control List) za nadzor dovoljenj in dostopa. Pri delu s tem razredom določimo vloge, vire in posamezna dovoljenja. Vloge in viri lahko tvorijo hierarhije. Za razlago bomo prikazali primer spletne aplikacije: +Nette prihaja z vgrajeno implementacijo avtorizatorja, in sicer razredom [api:Nette\Security\Permission], ki programerju ponuja lahko in fleksibilno ACL (Access Control List) plast za upravljanje dovoljenj in dostopov. Delo z njo temelji na definiciji vlog, virov in posameznih dovoljenj. Pri čemer vloge in viri omogočajo ustvarjanje hierarhij. Za pojasnilo si bomo pokazali primer spletne aplikacije: -- `guest`: obiskovalec, ki ni prijavljen, lahko bere in brska po javnem delu spleta, tj. bere članke, komentira in glasuje v anketah -- `registered`: prijavljeni uporabnik, ki lahko poleg tega objavlja komentarje +- `guest`: neprijavljen obiskovalec, ki lahko bere in brska po javnem delu spletne strani, tj. bere članke, komentarje in voli v anketah +- `registered`: prijavljen registriran uporabnik, ki dodatno lahko komentira - `admin`: lahko upravlja članke, komentarje in ankete -Tako smo določili določene vloge (`guest`, `registered` in `admin`) in omenili vire (`article`, `comments`, `poll`), do katerih lahko uporabniki dostopajo ali izvajajo dejanja (`view`, `vote`, `add`, `edit`). +Definirali smo si torej določene vloge (`guest`, `registered` in `admin`) in omenili vire (`article`, `comment`, `poll`), do katerih lahko uporabniki z neko vlogo dostopajo ali izvajajo določene operacije (`view`, `vote`, `add`, `edit`). -Ustvarimo primerek razreda Permission in opredelimo **vloge**. Uporabiti je mogoče dedovanje vlog, kar zagotavlja, da lahko na primer uporabnik z vlogo `admin` počne to, kar lahko počne običajni obiskovalec spletnega mesta (in seveda še več). +Ustvarimo instanco razreda Permission in definiramo **vloge**. Pri tem lahko izkoristimo t.i. dedovanje vlog, ki zagotovi, da npr. uporabnik z vlogo administratorja (`admin`) lahko dela tudi to, kar navaden obiskovalec spletne strani (in seveda tudi več). ```php $acl = new Nette\Security\Permission; $acl->addRole('guest'); -$acl->addRole('registered', 'guest'); // 'registered' podeduje od 'guest' -$acl->addRole('admin', 'registered'); // in "admin" podeduje od "registered". +$acl->addRole('registered', 'guest'); // 'registered' deduje od 'guest' +$acl->addRole('admin', 'registered'); // in od njega deduje 'admin' ``` -Zdaj bomo opredelili seznam **vsebin**, do katerih lahko dostopajo uporabniki: +Zdaj definiramo tudi seznam **virov**, do katerih lahko uporabniki dostopajo. ```php $acl->addResource('article'); @@ -114,49 +114,49 @@ $acl->addResource('comment'); $acl->addResource('poll'); ``` -Viri lahko uporabljajo tudi dedovanje, na primer, dodamo lahko `$acl->addResource('perex', 'article')`. +Tudi viri lahko uporabljajo dedovanje, mogoče bi bilo na primer vnesti `$acl->addResource('perex', 'article')`. -In zdaj najpomembnejša stvar. Med njimi bomo določili **pravila**, ki določajo, kdo lahko kaj počne: +In zdaj najpomembnejše. Med njimi definiramo pravila, ki določajo, kdo kaj lahko počne s čim: ```php -// zdaj je vse zanikano +// najprej nihče ne more početi ničesar -// gostom omogočite ogled člankov, komentarjev in anket. +// naj gost lahko pregleduje članke, komentarje in ankete $acl->allow('guest', ['article', 'comment', 'poll'], 'view'); -// in tudi glasovati v anketah. +// in v anketah dodatno tudi glasuje $acl->allow('guest', 'poll', 'vote'); -// registrirani podeduje dovoljenja od gosta, dovolili mu bomo tudi komentiranje +// registrirani deduje pravice od gosta, damo mu dodatno pravico komentiranja $acl->allow('registered', 'comment', 'add'); -// administrator lahko pregleduje in ureja vse. +// administrator lahko pregleduje in ureja karkoli $acl->allow('admin', $acl::All, ['view', 'edit', 'add']); ``` -Kaj pa, če želimo nekomu **preprečiti** dostop do vira? +Kaj pa, če želimo nekomu **preprečiti** dostop do določenega vira? ```php -// administrator ne more urejati anket, to bi bilo nedemokracije. +// administrator ne more urejati anket, to bi bilo nedemokratično $acl->deny('admin', 'poll', 'edit'); ``` -Ko smo ustvarili nabor pravil, lahko preprosto zastavimo poizvedbe o avtorizaciji: +Zdaj, ko imamo ustvarjen seznam pravil, lahko preprosto postavljamo avtorizacijska vprašanja: ```php // lahko gost pregleduje članke? -$acl->isAllowed('guest', 'article', 'view'); // Resnično +$acl->isAllowed('guest', 'article', 'view'); // true -// lahko gost ureja članek? +// lahko gost ureja članke? $acl->isAllowed('guest', 'article', 'edit'); // false // lahko gost glasuje v anketah? $acl->isAllowed('guest', 'poll', 'vote'); // true -// lahko gost dodaja komentarje? +// lahko gost komentira? $acl->isAllowed('guest', 'comment', 'add'); // false ``` -Enako velja za registriranega uporabnika, ki pa lahko tudi komentira: +Enako velja za registriranega uporabnika, ta pa lahko tudi komentira: ```php $acl->isAllowed('registered', 'article', 'view'); // true @@ -164,7 +164,7 @@ $acl->isAllowed('registered', 'comment', 'add'); // true $acl->isAllowed('registered', 'comment', 'edit'); // false ``` -Administrator lahko ureja vse razen anket: +Administrator lahko ureja vse, razen anket: ```php $acl->isAllowed('admin', 'poll', 'vote'); // true @@ -172,7 +172,7 @@ $acl->isAllowed('admin', 'poll', 'edit'); // false $acl->isAllowed('admin', 'comment', 'edit'); // true ``` -Dovoljenja se lahko ocenjujejo tudi dinamično, odločitev pa lahko prepustimo lastnemu povratnemu klicu, ki so mu posredovani vsi parametri: +Dovoljenja se lahko tudi dinamično ocenjujejo in odločitev lahko prepustimo lastnemu callbacku, kateremu se predajo vsi parametri: ```php $assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { @@ -182,7 +182,7 @@ $assertion = function (Permission $acl, string $role, string $resource, string $ $acl->allow('registered', 'comment', null, $assertion); ``` -Kako pa rešiti situacijo, ko imena vlog in virov ne zadoščajo, torej bi radi določili, da lahko na primer vloga `registered` ureja vir `article` le, če je njegov avtor? Namesto nizov bomo uporabili predmete, vloga bo predmet [api:Nette\Security\Role] in vir [api:Nette\Security\Resource]. Njuni metodi `getRoleId()` oziroma `getResourceId()` bosta vrnili izvirne nize: +Kako pa na primer rešiti situacijo, ko ne zadostujejo samo imena vlog in virov, ampak bi želeli definirati, da na primer vloga `registered` lahko ureja vir `article` samo, če je njegov avtor? Namesto nizov bomo uporabili objekte, vloga bo objekt [api:Nette\Security\Role] in vir [api:Nette\Security\Resource]. Njihovi metodi `getRoleId()` oz. `getResourceId()` bosta vračali prvotne nize: ```php class Registered implements Nette\Security\Role @@ -207,19 +207,19 @@ class Article implements Nette\Security\Resource } ``` -Zdaj pa ustvarimo pravilo: +In zdaj ustvarimo pravilo: ```php $assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { - $role = $acl->getQueriedRole(); // predmet Registrirano - $resource = $acl->getQueriedResource(); // predmet Člen + $role = $acl->getQueriedRole(); // objekt Registered + $resource = $acl->getQueriedResource(); // objekt Article return $role->id === $resource->authorId; }; $acl->allow('registered', 'article', 'edit', $assertion); ``` -Po ACL poizvedujemo tako, da posredujemo predmete: +In poizvedba na ACL se izvede s predajo objektov: ```php $user = new Registered(/* ... */); @@ -227,7 +227,7 @@ $article = new Article(/* ... */); $acl->isAllowed($user, $article, 'edit'); ``` -Vloga lahko podeduje eno ali več drugih vlog. Toda kaj se zgodi, če ima en prednik določeno dejanje dovoljeno, drugi pa prepovedano? Takrat pride v poštev *teža vloge* - zadnja vloga v nizu podedovanih vlog ima največjo težo, prva pa najmanjšo: +Vloga lahko deduje od druge vloge ali od več vlog. Kaj pa se zgodi, če ima en prednik akcijo prepovedano in drugi dovoljeno? Kakšne bodo pravice potomca? Določa se glede na težo vloge - zadnja navedena vloga v seznamu prednikov ima največjo težo, prva navedena vloga pa najmanjšo. Bolj nazorno je to iz primera: ```php $acl = new Nette\Security\Permission; @@ -239,22 +239,22 @@ $acl->addResource('backend'); $acl->allow('admin', 'backend'); $acl->deny('guest', 'backend'); -// primer A: vloga admin ima manjšo utež kot vloga guest +// primer A: vloga admin ima manjšo težo kot vloga guest $acl->addRole('john', ['admin', 'guest']); $acl->isAllowed('john', 'backend'); // false -// primer B: vloga admin ima večjo težo kot vloga guest +// primer B: vloga admin ima večjo težo kot guest $acl->addRole('mary', ['guest', 'admin']); $acl->isAllowed('mary', 'backend'); // true ``` -Vloge in vire je mogoče tudi odstraniti (`removeRole()`, `removeResource()`), pravila je mogoče tudi razveljaviti (`removeAllow()`, `removeDeny()`). Polje vseh neposrednih starševskih vlog vrne `getRoleParents()`. Ali dve entiteti dedujeta druga od druge, vrne `roleInheritsFrom()` in `resourceInheritsFrom()`. +Vloge in vire je mogoče tudi odstraniti (`removeRole()`, `removeResource()`), mogoče je revertirati tudi pravila (`removeAllow()`, `removeDeny()`). Polje vseh neposrednih starševskih vlog vrača `getRoleParents()`, ali dve entiteti dedujeta druga od druge, vračata `roleInheritsFrom()` in `resourceInheritsFrom()`. -Dodaj kot storitev .[#toc-add-as-a-service] -------------------------------------------- +Dodajanje kot storitve +---------------------- -ACL, ki smo ga ustvarili, moramo dodati v konfiguracijo kot storitev, da ga lahko uporablja objekt `$user`, tj. da ga lahko uporabimo v kodi, na primer `$user->isAllowed('article', 'view')`. V ta namen bomo zanj napisali tovarno: +Naš ustvarjeni ACL si moramo predati v konfiguracijo kot storitev, da ga začne uporabljati objekt `$user`, torej da bo mogoče uporabljati v kodi npr. `$user->isAllowed('article', 'view')`. V ta namen si zanj napišemo tovarno: ```php namespace App\Model; @@ -272,21 +272,21 @@ class AuthorizatorFactory } ``` -In jo bomo dodali v konfiguracijo: +In jo dodamo v konfiguracijo: ```neon services: - App\Model\AuthorizatorFactory::create ``` -V predstavitvah lahko nato preverite dovoljenja v metodi `startup()`, na primer: +V presenterjih lahko nato preverite dovoljenja na primer v metodi `startup()`: ```php protected function startup() { parent::startup(); if (!$this->getUser()->isAllowed('backend')) { - $this->error('Forbidden', 403); + $this->error('Prepovedano', 403); } } ``` diff --git a/security/sl/configuration.texy b/security/sl/configuration.texy index 9decfda436..ebea4f3e68 100644 --- a/security/sl/configuration.texy +++ b/security/sl/configuration.texy @@ -1,71 +1,85 @@ -Konfiguriranje nadzora dostopa -****************************** +Konfiguracija dostopnih dovoljenj +********************************* .[perex] -Pregled možnosti konfiguracije za Nette Security. +Pregled konfiguracijskih možnosti za Nette Security. -Če ne uporabljate celotnega ogrodja, temveč samo to knjižnico, preberite, [kako naložiti konfiguracijo |bootstrap:]. +Če ne uporabljate celotnega ogrodja, ampak samo to knjižnico, preberite, [kako konfiguracijo naložiti|bootstrap:]. -V konfiguraciji lahko določite seznam uporabnikov in tako ustvarite [preprost avtentikacijski program |authentication] (`Nette\Security\SimpleAuthenticator`). Ker so gesla v konfiguraciji berljiva, je ta rešitev namenjena le za testne namene. +V konfiguraciji je mogoče definirati seznam uporabnikov in tako ustvariti [preprost avtentikator|authentication] (`Nette\Security\SimpleAuthenticator`). Ker se v konfiguraciji navajajo gesla v berljivi obliki, je ta rešitev primerna samo za testne namene. ```neon security: - # prikazuje uporabniško ploščo v Tracy Baru? - debugger: ... # (bool) privzeto true + # prikazati ploščo uporabnika v Tracy Bar? + debugger: ... # (bool) privzeto je true users: - # ime: password - johndoe: secret123 + # ime: geslo + frantisek: tajnegeslo - # ime, geslo, vloga in drugi podatki, ki so na voljo v identiteti - janedoe: - password: secret123 + # ime, geslo, vloga in drugi podatki, dostopni v identiteti + dobrota: + password: tajnegeslo roles: [admin] data: ... ``` -Opredelite lahko tudi vloge in vire ter tako ustvarite osnovo za [avtorizator |authorization] (`Nette\Security\Permission`): +Nadalje je mogoče definirati vloge in vire ter tako ustvariti osnovo za [avtorizator|authorization] (`Nette\Security\Permission`): ```neon security: roles: guest: - registered: [guest] # registered podeduje od guest - admin: [registered] # in admin podeduje od registered + registered: [guest] # registered deduje od guest + admin: [registered] # in od njega deduje admin resources: article: - comment: [article] # resource podeduje od article + comment: [article] # vir deduje od article poll: ``` -Shranjevanje uporabnikov .[#toc-user-storage] ---------------------------------------------- +Shramba +------- -Nastavite lahko način shranjevanja informacij o prijavljenem uporabniku: +Mogoče je konfigurirati, kako shranjevati informacije o prijavljenem uporabniku: ```neon security: authentication: - # po kolikšnem času neaktivnosti bo uporabnik odjavljen. - expiration: 30 minutes # (niz) privzeto ni nastavljeno + # po koliko časa neaktivnosti bo uporabnik odjavljen + expiration: 30 minutes # (string) privzeto ni nastavljeno - # kam shraniti informacije o prijavljenem uporabniku + # kam shranjevati informacije o prijavljenem uporabniku storage: session # (session|cookie) privzeto je session ``` -Če kot skladišče izberete `cookie`, lahko nastavite tudi naslednje možnosti: +Če izberete kot shrambo `cookie`, lahko nastavite še te možnosti: ```neon security: authentication: # ime piškotka - cookieName: userId # (string) výchozí je userid + cookieName: userId # (string) privzeto je userid - # kateri gostitelji lahko prejmejo piškotek - cookieDomain: 'example.com' # (niz|domena) + # domene, ki sprejemajo piškotek + cookieDomain: 'example.com' # (string|domain) - # omejitve pri dostopu do zahteve z navzkrižnim izvorom - cookieSamesite: None # (Strict|Lax|None) privzeta vrednost je Lax + # omejitev pri dostopu z druge domene + cookieSamesite: None # (Strict|Lax|None) privzeto je Lax ``` + + +Storitve DI +----------- + +Te storitve se dodajajo v DI vsebnik: + +| Ime | Tip | Opis +|---------------------------------------------------------- +| `security.authenticator` | [api:Nette\Security\Authenticator] | [avtentikator|authentication] +| `security.authorizator` | [api:Nette\Security\Authorizator] | [avtorizator|authorization] +| `security.passwords` | [api:Nette\Security\Passwords] | [zgoščevanje gesel|passwords] +| `security.user` | [api:Nette\Security\User] | trenutni uporabnik +| `security.userStorage` | [api:Nette\Security\UserStorage] | [#shramba] diff --git a/security/sl/passwords.texy b/security/sl/passwords.texy index 350f6be233..a3ff2cb288 100644 --- a/security/sl/passwords.texy +++ b/security/sl/passwords.texy @@ -1,12 +1,12 @@ -Hashing gesel -************* +Zgoščevanje gesel +***************** .[perex] -Zaradi varnosti naših uporabnikov njihovih gesel nikoli ne shranjujemo v obliki navadnega besedila, temveč shranjujemo njihov hash. Hashing ni reverzibilna operacija, gesla ni mogoče obnoviti. Geslo pa je mogoče razbiti in da bi bilo razbijanje čim težje, moramo uporabiti varen algoritem. Pri tem nam bo pomagal razred [api:Nette\Security\Passwords]. +Da bi zagotovili varnost naših uporabnikov, ne shranjujemo njihovih gesel v berljivi obliki, ampak shranimo samo odtis (t.i. hash). Iz odtisa ni mogoče nazaj rekonstruirati prvotne oblike gesla. Pomembno je uporabiti varen algoritem, s katerim odtis ustvarimo. Pri tem nam pomaga razred [api:Nette\Security\Passwords]. -→ [Namestitev in zahteve |@home#Installation] +→ [Namestitev in zahteve |@home#Namestitev] -Ogrodje samodejno doda storitev `Nette\Security\Passwords` v vsebnik DI pod imenom `security.passwords`, ki jo dobite tako, da jo posredujete z uporabo [vbrizgavanja odvisnosti |dependency-injection:passing-dependencies]: +Ogrodje samodejno dodaja v DI vsebnik storitev tipa `Nette\Security\Passwords` pod imenom `security.passwords`, do katere pridete tako, da si jo pustite predati s pomočjo [dependency injection |dependency-injection:passing-dependencies]. ```php use Nette\Security\Passwords; @@ -24,44 +24,44 @@ class Foo __construct($algo=PASSWORD_DEFAULT, array $options=[]): string .[method] ======================================================================== -Izbere, kateri [varni algoritem |https://www.php.net/manual/en/password.constants.php] se uporablja za hashanje, in kako ga konfigurirati. +Izberemo, kateri [varen algoritem |https://www.php.net/manual/en/password.constants.php] za generiranje hasha uporabiti in konfiguriramo njegove parametre. -Privzeto je `PASSWORD_DEFAULT`, zato je izbira algoritma prepuščena PHP. Algoritem se lahko spremeni v novejših izdajah PHP, ko bodo podprti novejši in močnejši algoritmi za hashanje. Zato se morate zavedati, da se lahko dolžina dobljenega hasha spremeni. Zato morate nastali hash shraniti tako, da lahko shranite dovolj znakov, pri čemer je priporočena širina 255. +Kot privzeto se uporablja `PASSWORD_DEFAULT`, torej se izbira algoritma prepušča PHP-ju. Algoritem se lahko v novejših različicah PHP spremeni, če se pojavijo novejši, močnejši hash algoritmi. Zato se morate zavedati, da se lahko dolžina nastalega hasha spremeni, in ga shranite na način, ki lahko sprejme dovolj znakov, 255 je priporočena širina. -Tako bi uporabili algoritem bcrypt in spremenili hitrost hashanja s parametrom cost s privzete vrednosti 10. V letu 2020 s stroški 10 hashanje enega gesla traja približno 80 ms, s stroški 11 traja 160 ms, s stroški 12 nato 320 ms, lestvica je logaritemska. Čim počasneje, tem bolje, strošek 10-12 velja za dovolj počasnega za večino primerov uporabe: +Primer nastavitve hitrosti zgoščevanja z algoritmom bcrypt s spremembo parametra cost: (leta 2020 je privzeto 10, zgoščevanje gesla traja približno 80ms, za cost 11 je to cca 160ms, za cost 12 približno 320ms, počasneje kot je, boljša je zaščita, pri čemer se hitrost 10-12 že šteje za zadostno zaščito) ```php -// gesla bomo hashali z 2^12 (2^cost) iteracijami algoritma bcrypt +// bomo gesla zgoščevali z 2^12 (2^cost) iteracijami algoritma bcrypt $passwords = new Passwords(PASSWORD_BCRYPT, ['cost' => 12]); ``` -Z vbrizgavanjem odvisnosti: +S pomočjo dependency injection: ```neon services: security.passwords: Nette\Security\Passwords(::PASSWORD_BCRYPT, [cost: 12]) ``` -hash(string $passwords): string .[method] -========================================= +hash(string $password): string .[method] +======================================== -Ustvari hash gesla. +Generira hash gesla. ```php -$res = $passwords->hash($password); // geslo se zgoščuje. +$res = $passwords->hash($password); // Zgošči geslo ``` -Rezultat `$res` je niz, ki poleg samega hasha vsebuje identifikator uporabljenega algoritma, njegove nastavitve in kriptografsko sol (naključni podatki, ki zagotavljajo, da se za isto geslo generira drugačen hash). Zato je združljiv za nazaj, na primer, če spremenite parametre, lahko preverite hashe, shranjene s prejšnjimi nastavitvami. Celoten rezultat je shranjen v zbirki podatkov, zato soli ali nastavitev ni treba shranjevati ločeno. +Rezultat `$res` je niz, ki poleg samega hasha vsebuje tudi identifikator uporabljenega algoritma, njegove nastavitve in kriptografsko sol (naključni podatki, ki zagotavljajo, da se za isto geslo generira drugačen hash). Je torej nazaj združljiv, ko na primer spremenite parametre, tako da se bodo tudi hashi, shranjeni z uporabo prejšnjih nastavitev, lahko preverili. Celoten ta rezultat se shranjuje v podatkovno bazo, zato ni treba shranjevati soli ali nastavitev posebej. verify(string $password, string $hash): bool .[method] ====================================================== -Ugotovi, ali se dano geslo ujema z danim hashem. Pridobi `$hash` iz zbirke podatkov po uporabniškem imenu ali e-poštnem naslovu. +Ugotovi, ali dano geslo ustreza danemu odtisu. `$hash` pridobite iz podatkovne baze glede na vneseno uporabniško ime ali e-poštni naslov. ```php if ($passwords->verify($password, $hash)) { - // Pravilno geslo + // pravilno geslo } ``` @@ -69,13 +69,13 @@ if ($passwords->verify($password, $hash)) { needsRehash(string $hash): bool .[method] ========================================= -Ugotovi, ali se hash ujema z možnostmi, podanimi v konstruktorju. +Ugotovi, ali hash ustreza v konstruktorju navedenim možnostim. -To metodo uporabite, kadar na primer spreminjate parametre hashanja. Pri preverjanju gesla bodo uporabljeni parametri, shranjeni skupaj s hashem, in če `needsRehash()` vrne true, morate hash ponovno izračunati, tokrat s posodobljenimi parametri, in ga ponovno shraniti v zbirko podatkov. S tem zagotovite, da se bodo gesla hashe samodejno "nadgradila", ko se bodo uporabniki vpisovali v sistem. +Uporabno je v trenutku, ko na primer spreminjate hitrost zgoščevanja. Preverjanje poteka glede na shranjene nastavitve in če `needsRehash()` vrne `true`, je treba ponovno ustvariti hash, tokrat z novimi parametri, in ga ponovno shraniti v podatkovno bazo. Tako se samodejno "nadgradijo" shranjeni hashi ob prijavi uporabnikov. ```php if ($passwords->needsRehash($hash)) { $hash = $passwords->hash($password); - // shranjevanje $hash v zbirko podatkov + // shraniti $hash v podatkovno bazo } ``` diff --git a/security/tr/@home.texy b/security/tr/@home.texy index e1dbe2be1d..0e99a07f25 100644 --- a/security/tr/@home.texy +++ b/security/tr/@home.texy @@ -1,14 +1,14 @@ -Güvenlik -******** +Nette Security +************** .[perex] -`nette/security` paketi [kullanıcıların kimliğini doğrulama |authentication], [erişim kontrolü |authorization] ve [parola karma |passwords] işlemlerinden sorumludur. +`nette/security` paketi [kullanıcı girişi |authentication], [yetkilendirme |authorization] ve [şifre hashleme |passwords] işlemlerinden sorumludur. -Kurulum .[#toc-installation] ----------------------------- +Kurulum +------- -[Composer'ı |best-practices:composer] kullanarak paketi indirin ve yükleyin: +Kütüphaneyi [Composer|best-practices:composer] aracını kullanarak indirip kurun: ```shell composer require nette/security diff --git a/security/tr/@left-menu.texy b/security/tr/@left-menu.texy index 7a9a31679a..51585eb3c2 100644 --- a/security/tr/@left-menu.texy +++ b/security/tr/@left-menu.texy @@ -1,7 +1,7 @@ -Nette Güvenlik +Nette Security ************** -- [Genel Bakış |@home] -- [Kimlik Doğrulama |Authentication] -- [Yetkilendirme |Authorization] -- [Parola karması |passwords] -- [Konfigürasyon |Configuration] +- [Giriş |@home] +- [Kimlik Doğrulama |authentication] +- [Yetkilendirme |authorization] +- [Şifre Hashleme |passwords] +- [Yapılandırma |configuration] diff --git a/security/tr/@meta.texy b/security/tr/@meta.texy new file mode 100644 index 0000000000..8dfe82f311 --- /dev/null +++ b/security/tr/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokümantasyonu}} diff --git a/security/tr/authentication.texy b/security/tr/authentication.texy index 8c723f41be..cfaa49e394 100644 --- a/security/tr/authentication.texy +++ b/security/tr/authentication.texy @@ -1,48 +1,48 @@ -Kullanıcıların Kimliğini Doğrulama -********************************** +Kullanıcı Girişi (Kimlik Doğrulama) +*********************************** <div class=perex> -Web uygulamalarının kullanıcı girişi veya kullanıcı ayrıcalıklarını kontrol etmek için herhangi bir mekanizmaya ihtiyacı yoktur. Bu bölümde, aşağıdakiler hakkında konuşacağız: +Neredeyse hiçbir web uygulaması, kullanıcı girişi ve kullanıcı izinlerini doğrulama mekanizması olmadan yapamaz. Bu bölümde şunları ele alacağız: -- kullanıcı girişi ve çıkışı -- özel doğrulayıcılar ve yetkilendiriciler +- kullanıcıların giriş ve çıkış yapması +- özel kimlik doğrulayıcılar </div> -→ [Kurulum ve gereksinimler |@home#Installation] +→ [Kurulum ve Gereksinimler |@home#Kurulum] -Örneklerde, mevcut kullanıcıyı temsil eden ve [bağımlılık enjeksiyonu |dependency-injection:passing-dependencies] kullanarak geçirerek elde ettiğiniz [api:Nette\Security\User] sınıfından bir nesne kullanacağız. Sunucularda `$user = $this->getUser()` adresini çağırmanız yeterlidir. +Örneklerde, mevcut kullanıcıyı temsil eden [api:Nette\Security\User] sınıfının nesnesini kullanacağız ve buna [dependency injection |dependency-injection:passing-dependencies] kullanarak erişebilirsiniz. Presenter'larda sadece `$user = $this->getUser()` çağırmanız yeterlidir. -Kimlik Doğrulama .[#toc-authentication] -======================================= +Kimlik Doğrulama +================ -Kimlik doğrulama, **kullanıcı girişi**, yani bir kullanıcının kimliğinin doğrulandığı süreç anlamına gelir. Kullanıcı genellikle kullanıcı adı ve parola kullanarak kendini tanıtır. Doğrulama, sözde kimlik [doğrulayıcı |#authenticator] tarafından gerçekleştirilir. Oturum açma başarısız olursa, `Nette\Security\AuthenticationException` atar. +Kimlik doğrulama, **kullanıcı girişi** anlamına gelir, yani kullanıcının gerçekten iddia ettiği kişi olup olmadığının doğrulandığı süreçtir. Genellikle kullanıcı adı ve şifre ile kanıtlanır. Doğrulama, sözde [#Authenticator] tarafından gerçekleştirilir. Giriş başarısız olursa, `Nette\Security\AuthenticationException` atılır. ```php try { $user->login($username, $password); } catch (Nette\Security\AuthenticationException $e) { - $this->flashMessage('The username or password you entered is incorrect.'); + $this->flashMessage('Kullanıcı adı veya şifre yanlış'); } ``` -Kullanıcının oturumu bu şekilde kapatılır: +Bu şekilde kullanıcıyı oturumdan çıkarırsınız: ```php $user->logout(); ``` -Ve kullanıcının oturum açıp açmadığını kontrol eder: +Ve giriş yapıp yapmadığını kontrol etme: ```php -echo $user->isLoggedIn() ? 'yes' : 'no'; +echo $user->isLoggedIn() ? 'evet' : 'hayır'; ``` -Basit, değil mi? Ve tüm güvenlik hususları sizin için Nette tarafından ele alınır. +Çok basit, değil mi? Ve tüm güvenlik yönlerini Nette sizin için halleder. -Presenter'da, `startup()` yönteminde oturum açmayı doğrulayabilir ve oturum açmamış bir kullanıcıyı oturum açma sayfasına yönlendirebilirsiniz. +Presenter'larda, `startup()` metodunda girişi doğrulayabilir ve giriş yapmamış kullanıcıyı giriş sayfasına yönlendirebilirsiniz. ```php protected function startup() @@ -55,39 +55,38 @@ protected function startup() ``` -Son kullanma tarihi .[#toc-expiration] -====================================== +Süre Sonu +========= -Kullanıcı oturumu, genellikle bir oturum olan [deponun |#Storage for Logged User] sona ermesiyle birlikte sona erer ( [oturum |http:configuration#session] sona erme ayarına bakın). -Ancak, kullanıcının oturumunun kapatılacağı daha kısa bir zaman aralığı da belirleyebilirsiniz. Bu amaçla `login()` adresinden önce çağrılan `setExpiration()` yöntemi kullanılır. Parametre olarak göreli zaman içeren bir dize girin: +Kullanıcı girişi, genellikle session olan [depolama alanının sona ermesiyle |#Giriş Yapmış Kullanıcının Depolama Alanı] birlikte sona erer (bkz. [session sona erme |http:configuration#Oturum Session] ayarı). Ancak, kullanıcının oturumdan çıkarılacağı daha kısa bir zaman aralığı da ayarlanabilir. Bunun için, `login()` öncesinde çağrılan `setExpiration()` metodu kullanılır. Parametre olarak göreli bir zaman karakter dizisi belirtin: ```php -// oturum açma 30 dakika işlem yapılmadığında sona erer +// giriş 30 dakikalık hareketsizlikten sonra sona erecek $user->setExpiration('30 minutes'); -// setin sona ermesini iptal et +// ayarlanan sona ermeyi iptal etme $user->setExpiration(null); ``` -`$user->getLogoutReason()` yöntemi, zaman aralığı sona erdiği için kullanıcının oturumunun kapatılıp kapatılmadığını belirtir. Süre dolmuşsa `Nette\Security\UserStorage::LogoutInactivity` sabitini veya `logout()` yöntemi çağrıldığında `UserStorage::LogoutManual` sabitini döndürür. +Kullanıcının zaman aralığının sona ermesi nedeniyle oturumdan çıkarılıp çıkarılmadığını `$user->getLogoutReason()` metodu söyler, bu metot ya `Nette\Security\UserStorage::LogoutInactivity` (zaman sınırı aşıldı) sabitini ya da `UserStorage::LogoutManual` (`logout()` metoduyla oturumdan çıkarıldı) sabitini döndürür. -Kimlik Doğrulayıcı .[#toc-authenticator] -======================================== +Authenticator +============= -Oturum açma verilerini, yani genellikle ad ve parolayı doğrulayan bir nesnedir. Önemsiz uygulama, [yapılandırmada |configuration] tanımlanabilen [api:Nette\Security\SimpleAuthenticator] sınıfıdır: +Bu, giriş bilgilerini, yani genellikle adı ve şifreyi doğrulayan bir nesnedir. Basit bir biçim, [yapılandırmada|configuration] tanımlayabileceğimiz [api:Nette\Security\SimpleAuthenticator] sınıfıdır: ```neon security: users: - # name: password - johndoe: secret123 - kathy: evenmoresecretpassword + # kullanıcı adı: şifre + frantisek: gizlisifre + katka: dahadagizlisifre ``` -Bu çözüm test amaçları için daha uygundur. Size bir veritabanı tablosuna karşı kimlik bilgilerini doğrulayacak bir kimlik doğrulayıcının nasıl oluşturulacağını göstereceğiz. +Bu çözüm daha çok test amaçlıdır. Veritabanı tablosuna karşı giriş bilgilerini doğrulayacak bir Authenticator'ın nasıl oluşturulacağını göstereceğiz. -Kimlik doğrulayıcı, [api:Nette\Security\Authenticator] arayüzünü `authenticate()` yöntemiyle uygulayan bir nesnedir. Görevi ya sözde [kimliği |#identity] döndürmek ya da bir istisna fırlatmaktır `Nette\Security\AuthenticationException`. Ayrıca `Authenticator::IdentityNotFound` veya `Authenticator::InvalidCredential` ince taneli bir hata kodu sağlamak da mümkün olabilir. +Authenticator, `authenticate()` metoduna sahip [api:Nette\Security\Authenticator] arayüzünü uygulayan bir nesnedir. Görevi ya sözde [kimliği |#Kimlik] döndürmek ya da `Nette\Security\AuthenticationException` istisnasını atmaktır. Ortaya çıkan durumu daha hassas bir şekilde ayırt etmek için hata kodu da belirtilebilir: `Authenticator::IdentityNotFound` ve `Authenticator::InvalidCredential`. ```php use Nette; @@ -117,16 +116,16 @@ class MyAuthenticator implements Nette\Security\Authenticator return new SimpleIdentity( $row->id, - $row->role, // veya rol dizisi + $row->role, // veya birden fazla rol dizisi ['name' => $row->username], ); } } ``` -MyAuthenticator sınıfı, [Nette Database Explorer |database:explorer] aracılığıyla veritabanı ile iletişim kurar ve `username` sütununun kullanıcının oturum açma adını ve `password` sütununun [hash'i |passwords] içerdiği `users` tablosu ile çalışır. Adı ve parolayı doğruladıktan sonra, kullanıcının kimliğini, [daha sonra |#roles] bahsedeceğimiz rolünü (tablodaki `role` sütunu) ve ek verileri içeren bir diziyi (bizim durumumuzda kullanıcı adı) döndürür. +MyAuthenticator sınıfı, [Nette Database Explorer|database:explorer] aracılığıyla veritabanıyla iletişim kurar ve `username` sütununda kullanıcının giriş adının ve `password` sütununda [şifre özeti|passwords] bulunduğu `users` tablosuyla çalışır. Ad ve şifreyi doğruladıktan sonra, kullanıcının ID'sini, rolünü (tablodaki `role` sütunu, bunun hakkında [daha sonra |authorization#Roller] daha fazla konuşacağız) ve diğer verileri (bizim durumumuzda kullanıcı adı) taşıyan kimliği döndürür. -Kimlik doğrulayıcıyı DI konteynerinin [bir servisi olarak |dependency-injection:services] yapılandırmaya ekleyeceğiz: +Authenticator'ı ayrıca DI konteynerinin [bir servisi olarak|dependency-injection:services] yapılandırmaya ekleyeceğiz: ```neon services: @@ -137,47 +136,47 @@ services: $onLoggedIn, $onLoggedOut Olayları ---------------------------------- -Object `Nette\Security\User`, `$onLoggedIn` ve `$onLoggedOut`[olaylarına |nette:glossary#Events] sahiptir, böylece başarılı bir oturum açma işleminden sonra veya kullanıcı oturumu kapattıktan sonra tetiklenen geri aramalar ekleyebilirsiniz. +`Nette\Security\User` nesnesi, `$onLoggedIn` ve `$onLoggedOut` [olaylarına |nette:glossary#Olaylar Events] sahiptir, bu nedenle başarılı girişten sonra veya kullanıcı oturumdan çıktıktan sonra çağrılacak geri aramalar ekleyebilirsiniz. ```php $user->onLoggedIn[] = function () { - // kullanıcı yeni giriş yaptı + // kullanıcı şimdi giriş yaptı }; ``` -Kimlik .[#toc-identity] -======================= +Kimlik +====== -Kimlik, bir kullanıcı hakkında kimlik doğrulayıcı tarafından döndürülen ve daha sonra bir oturumda saklanan ve `$user->getIdentity()` kullanılarak alınan bir dizi bilgidir. Böylece kimlik doğrulayıcıda ilettiğimiz gibi kimliği, rolleri ve diğer kullanıcı verilerini alabiliriz: +Kimlik, Authenticator tarafından döndürülen ve ardından session'da saklanan ve `$user->getIdentity()` kullanarak aldığımız kullanıcı hakkındaki bilgi kümesini temsil eder. Böylece, Authenticator'da aktardığımız gibi id, roller ve diğer kullanıcı verilerini alabiliriz: ```php $user->getIdentity()->getId(); -// ayrıca $user->getId() kısayolu da çalışır; +// kısayol $user->getId() de çalışır; $user->getIdentity()->getRoles(); -// kullanıcı verilerine özellikler olarak erişilebilir +// kullanıcı verileri özellikler olarak mevcuttur // MyAuthenticator'da aktardığımız isim $user->getIdentity()->name; ``` -Önemli olarak, kullanıcı `$user->logout()` adresini kullanarak oturumu kapattığında **kimlik silinmez** ve hala kullanılabilir durumdadır. Dolayısıyla, kimlik varsa, tek başına kullanıcının da oturum açmış olduğunu göstermez. Eğer kimliği açıkça silmek istiyorsak, `logout(true)` adresinden kullanıcının oturumunu kapatırız. +Önemli olan, `$user->logout()` kullanarak oturumu kapattığınızda **kimliğin silinmemesi** ve hala kullanılabilir olmasıdır. Yani, kullanıcının bir kimliği olsa bile, giriş yapmamış olabilir. Kimliği açıkça silmek istersek, kullanıcıyı `logout(true)` çağrısıyla oturumdan çıkarırız. -Bu sayede, hangi kullanıcının bilgisayar başında olduğunu varsayabilir ve örneğin e-mağazada kişiselleştirilmiş teklifler görüntüleyebilirsiniz, ancak kişisel verilerini yalnızca oturum açtıktan sonra görüntüleyebilirsiniz. +Bu sayede, bilgisayarda hangi kullanıcının olduğunu tahmin etmeye devam edebilir ve örneğin e-ticaret sitesinde ona kişiselleştirilmiş teklifler gösterebilirsiniz, ancak kişisel verilerini yalnızca giriş yaptıktan sonra görüntüleyebilirsiniz. -Identity, [api:Nette\Security\IIdentity] arayüzünü uygulayan bir nesnedir, varsayılan uygulama [api:Nette\Security\SimpleIdentity] şeklindedir. Ve belirtildiği gibi, kimlik oturumda saklanır, bu nedenle, örneğin, oturum açan kullanıcılardan bazılarının rolünü değiştirirsek, eski veriler tekrar oturum açana kadar kimlikte tutulur. +Kimlik, [api:Nette\Security\IIdentity] arayüzünü uygulayan bir nesnedir, varsayılan uygulama [api:Nette\Security\SimpleIdentity]'dir. Ve bahsedildiği gibi, session'da tutulur, bu nedenle örneğin giriş yapmış kullanıcılardan birinin rolünü değiştirirsek, eski veriler tekrar giriş yapana kadar kimliğinde kalır. -Oturum Açan Kullanıcı için Depolama .[#toc-storage-for-logged-user] -=================================================================== +Giriş Yapmış Kullanıcının Depolama Alanı +======================================== -Kullanıcı hakkındaki iki temel bilgi, yani oturum açıp açmadığı ve [kimliği |#identity], genellikle oturumda taşınır. Bu bilgiler değiştirilebilir. Bu bilgilerin saklanması için `Nette\Security\UserStorage` arayüzünü uygulayan bir nesne sorumludur. İki standart uygulama vardır; birincisi verileri bir oturumda, ikincisi ise bir çerezde iletir. Bunlar `Nette\Bridges\SecurityHttp\SessionStorage` ve `CookieStorage` sınıflarıdır. Depolamayı seçebilir ve [güvenlik › kimlik |configuration] doğrulama yapılandırmasında çok uygun bir şekilde yapılandırabilirsiniz. +Kullanıcı hakkındaki iki temel bilgi, yani giriş yapıp yapmadığı ve [kimliği |#Kimlik]'si genellikle session'da aktarılır. Bu değiştirilebilir. Bu bilgilerin saklanmasından `Nette\Security\UserStorage` arayüzünü uygulayan bir nesne sorumludur. İki standart uygulama mevcuttur, ilki verileri session'da, ikincisi cookie'de aktarır. Bunlar `Nette\Bridges\SecurityHttp\SessionStorage` ve `CookieStorage` sınıflarıdır. Depolama alanını seçebilir ve [security › authentication |configuration#Depolama Alanı] yapılandırmasında çok rahat bir şekilde yapılandırabilirsiniz. -Ayrıca kimlik kaydetme (*uyku*) ve geri yüklemenin (*uyanma*) tam olarak nasıl gerçekleşeceğini de kontrol edebilirsiniz. İhtiyacınız olan tek şey kimlik doğrulayıcının `Nette\Security\IdentityHandler` arayüzünü uygulamasıdır. Bunun iki yöntemi vardır: `sleepIdentity()` kimlik depoya yazılmadan önce ve `wakeupIdentity()` kimlik okunduktan sonra çağrılır. Yöntemler kimliğin içeriğini değiştirebilir veya dönen yeni bir nesneyle değiştirebilir. `wakeupIdentity()` yöntemi, kullanıcının oturumunu kapatan `null` yöntemini bile döndürebilir. +Ayrıca, kimliğin nasıl saklanacağını (*sleep*) ve geri yükleneceğini (*wakeup*) tam olarak etkileyebilirsiniz. Authenticator'ın `Nette\Security\IdentityHandler` arayüzünü uygulaması yeterlidir. Bunun iki metodu vardır: `sleepIdentity()` kimliği depolama alanına yazmadan önce çağrılır ve `wakeupIdentity()` okunduktan sonra çağrılır. Metotlar kimliğin içeriğini değiştirebilir veya döndüreceği yeni bir nesneyle değiştirebilir. `wakeupIdentity()` metodu hatta `null` döndürebilir, bu da kullanıcıyı oturumdan çıkarır. -Örnek olarak, bir oturumdan geri yüklendikten hemen sonra kimlik rollerinin nasıl güncelleneceğine ilişkin yaygın bir soruya bir çözüm göstereceğiz. `wakeupIdentity()` yönteminde, örneğin veritabanından mevcut rolleri kimliğe aktarıyoruz: +Örnek olarak, sık sorulan bir soru olan session'dan yüklendikten hemen sonra kimlikteki rollerin nasıl güncelleneceği çözümünü göstereceğiz. `wakeupIdentity()` metodunda, kimliğe örneğin veritabanından güncel rolleri aktaracağız: ```php final class Authenticator implements @@ -185,25 +184,25 @@ final class Authenticator implements { public function sleepIdentity(IIdentity $identity): IIdentity { - // burada oturum açtıktan sonra depolamadan önce kimliği değiştirebilirsiniz, - // ama şimdi buna ihtiyacımız yok + // burada kimliği giriş yaptıktan sonra depolama alanına yazmadan önce değiştirebiliriz, + // ancak şimdi buna ihtiyacımız yok return $identity; } public function wakeupIdentity(IIdentity $identity): ?IIdentity { - // kimlikteki rollerin güncellenmesi + // kimlikteki rolleri güncelleme $userId = $identity->getId(); $identity->setRoles($this->facade->getUserRoles($userId)); return $identity; } ``` -Ve şimdi çerez tabanlı depolamaya geri dönüyoruz. Oturum kullanmaya gerek kalmadan kullanıcıların giriş yapabileceği bir web sitesi oluşturmanızı sağlar. Yani diske yazmaya ihtiyaç duymaz. Sonuçta, forum da dahil olmak üzere şu anda okumakta olduğunuz web sitesi bu şekilde çalışmaktadır. Bu durumda, `IdentityHandler` uygulaması bir gerekliliktir. Sadece oturum açan kullanıcıyı temsil eden rastgele bir belirteci çerezde saklayacağız. +Ve şimdi cookie tabanlı depolama alanına geri dönelim. Kullanıcıların giriş yapabileceği ve session'lara ihtiyaç duymayan bir web sitesi oluşturmanıza olanak tanır. Yani diske yazmaya ihtiyaç duymaz. Sonuçta, forum dahil okuduğunuz web sitesi de bu şekilde çalışır. Bu durumda, `IdentityHandler` uygulaması bir zorunluluktur. Cookie'ye yalnızca giriş yapmış kullanıcıyı temsil eden rastgele bir belirteç kaydedeceğiz. -Bu yüzden önce `security › authentication › storage: cookie` adresini kullanarak yapılandırmada istenen depolama alanını ayarlıyoruz. +Öncelikle, yapılandırmada `security › authentication › storage: cookie` kullanarak istenen depolama alanını ayarlayacağız. -Veritabanına, her kullanıcının yeterli uzunlukta (en az 13 karakter) [tamamen rastgele, benzersiz ve tahmin edilemez |utils:random] bir dizeye sahip olacağı bir `authtoken` sütunu ekleyeceğiz. `CookieStorage` deposu çerezde yalnızca `$identity->getId()` değerini saklar, bu nedenle `sleepIdentity()` 'de orijinal kimliği kimlikte `authtoken` olan bir proxy ile değiştiririz, aksine `wakeupIdentity()` yönteminde authtoken'a göre veritabanından tüm kimliği geri yükleriz: +Veritabanında, her kullanıcının yeterli uzunlukta (en az 13 karakter) [tamamen rastgele, benzersiz ve tahmin edilemez|utils:random] bir karakter dizisine sahip olacağı bir `authtoken` sütunu oluşturacağız. `CookieStorage` depolama alanı, cookie'de yalnızca `$identity->getId()` değerini aktarır, bu nedenle `sleepIdentity()`'de orijinal kimliği ID'de `authtoken` bulunan bir vekil kimlikle değiştireceğiz, tersine `wakeupIdentity()` metodunda authtoken'a göre tüm kimliği veritabanından okuyacağız: ```php final class Authenticator implements @@ -212,21 +211,21 @@ final class Authenticator implements public function authenticate(string $username, string $password): SimpleIdentity { $row = $this->db->fetch('SELECT * FROM user WHERE username = ?', $username); - // şifreyi kontrol et + // şifreyi doğrula ... - // veritabanındaki tüm verilerle birlikte kimliği döndürüyoruz + // veritabanındaki tüm verilerle kimliği döndür return new SimpleIdentity($row->id, null, (array) $row); } public function sleepIdentity(IIdentity $identity): SimpleIdentity { - // ID'nin authtoken olduğu bir proxy kimliği döndürüyoruz + // ID'de authtoken bulunan vekil kimliği döndür return new SimpleIdentity($identity->authtoken); } public function wakeupIdentity(IIdentity $identity): ?SimpleIdentity { - // proxy kimliğini authenticate() işlevinde olduğu gibi tam kimlikle değiştirin + // vekil kimliği authenticate()'deki gibi tam kimlikle değiştir $row = $this->db->fetch('SELECT * FROM user WHERE authtoken = ?', $identity->getId()); return $row ? new SimpleIdentity($row->id, null, (array) $row) @@ -236,16 +235,16 @@ final class Authenticator implements ``` -Çoklu Bağımsız Kimlik Doğrulama .[#toc-multiple-independent-authentications] -============================================================================ +Birden Fazla Bağımsız Giriş +=========================== -Bir site içinde birden fazla bağımsız oturum açmış kullanıcıya ve aynı anda bir oturuma sahip olmak mümkündür. Örneğin, ön uç ve arka uç için ayrı kimlik doğrulamasına sahip olmak istiyorsak, her biri için benzersiz bir oturum ad alanı ayarlayacağız: +Aynı web sitesi ve aynı session içinde aynı anda birkaç bağımsız giriş yapan kullanıcıya sahip olmak mümkündür. Örneğin, web sitesinde yönetim ve genel kısım için ayrı kimlik doğrulaması yapmak istiyorsak, her birine kendi adını ayarlamamız yeterlidir: ```php $user->getStorage()->setNamespace('backend'); ``` -Bunun aynı segmente ait tüm yerlerde ayarlanması gerektiğini akılda tutmak gerekir. Sunucuları kullanırken, isim alanını ortak atada ayarlayacağız - genellikle BasePresenter. Bunu yapmak için [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()] yöntemini genişleteceğiz: +Ad alanını her zaman ilgili kısma ait tüm yerlerde ayarlamayı unutmamak önemlidir. Presenter'ları kullanıyorsak, ad alanını ilgili kısım için ortak atada - genellikle BasePresenter - ayarlayacağız. Bunu [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()] metodunu genişleterek yapacağız: ```php public function checkRequirements($element): void @@ -256,10 +255,10 @@ public function checkRequirements($element): void ``` -Çoklu Kimlik Doğrulayıcılar .[#toc-multiple-authenticators] ------------------------------------------------------------ +Birden Fazla Authenticator +-------------------------- -Bir uygulamayı bağımsız kimlik doğrulama ile segmentlere bölmek genellikle farklı kimlik doğrulayıcılar gerektirir. Ancak, Authenticator uygulayan iki sınıfı yapılandırma hizmetlerine kaydetmek bir hatayı tetikleyecektir çünkü Nette hangisinin `Nette\Security\User` nesnesine [otomatik |dependency-injection:autowiring] bağlanması gerektiğini bilemeyecektir. Bu nedenle, `autowired: self` ile otomatik bağlamayı sınırlamalıyız, böylece yalnızca sınıfları özellikle istendiğinde etkinleştirilir: +Uygulamanın bağımsız girişli kısımlara bölünmesi genellikle farklı Authenticator'lar gerektirir. Ancak, servis yapılandırmasında Authenticator uygulayan iki sınıf kaydedersek, Nette hangisini otomatik olarak `Nette\Security\User` nesnesine atayacağını bilemez ve bir hata gösterirdi. Bu nedenle, Authenticator'lar için [autowiring |dependency-injection:autowiring]'i, yalnızca birisi belirli bir sınıfı, örneğin FrontAuthenticator'ı istediğinde çalışacak şekilde sınırlamamız gerekir, bunu `autowired: self` seçeneğiyle başarırız: ```neon services: @@ -278,7 +277,7 @@ class SignPresenter extends Nette\Application\UI\Presenter } ``` -Yalnızca [login() |api:Nette\Security\User::login()] yöntemini çağırmadan önce kimlik doğrulayıcımızı User nesnesine ayarlamamız gerekir, bu da genellikle oturum açma formu geri aramasında anlamına gelir: +User nesnesinin Authenticator'ını [login() |api:Nette\Security\User::login()] metodunu çağırmadan önce ayarlayacağız, bu nedenle genellikle onu giriş yaptıran formun kodunda: ```php $form->onSuccess[] = function (Form $form, \stdClass $data) { diff --git a/security/tr/authorization.texy b/security/tr/authorization.texy index 20ca8d7bd4..3d1cd9897c 100644 --- a/security/tr/authorization.texy +++ b/security/tr/authorization.texy @@ -1,48 +1,48 @@ -Erişim Kontrolü (Yetkilendirme) -******************************* +İzinleri Doğrulama (Yetkilendirme) +********************************** .[perex] -Yetkilendirme, bir kullanıcının örneğin belirli bir kaynağa erişmek veya bir eylemi gerçekleştirmek için yeterli ayrıcalıklara sahip olup olmadığını belirler. Yetkilendirme, daha önce başarılı bir kimlik doğrulaması yapıldığını, yani kullanıcının oturum açtığını varsayar. +Yetkilendirme, bir kullanıcının örneğin belirli bir kaynağa erişmek veya belirli bir eylemi gerçekleştirmek için yeterli izinlere sahip olup olmadığını belirler. Yetkilendirme, önceki başarılı kimlik doğrulamasını, yani kullanıcının giriş yapmış olmasını varsayar. -→ [Kurulum ve gereksinimler |@home#Installation] +→ [Kurulum ve Gereksinimler |@home#Kurulum] -Örneklerde, mevcut kullanıcıyı temsil eden ve [bağımlılık enjeksiyonu |dependency-injection:passing-dependencies] kullanarak geçirerek elde ettiğiniz [api:Nette\Security\User] sınıfından bir nesne kullanacağız. Sunucularda `$user = $this->getUser()` adresini çağırmanız yeterlidir. +Örneklerde, mevcut kullanıcıyı temsil eden [api:Nette\Security\User] sınıfının nesnesini kullanacağız ve buna [dependency injection |dependency-injection:passing-dependencies] kullanarak erişebilirsiniz. Presenter'larda sadece `$user = $this->getUser()` çağırmanız yeterlidir. -Kullanıcı haklarının ayırt edilmediği çok basit yönetimli web siteleri için, zaten bilinen yöntemi bir yetkilendirme kriteri olarak kullanmak mümkündür `isLoggedIn()`. Başka bir deyişle: bir kullanıcı oturum açtığında, tüm eylemler için izinlere sahip olur ve bunun tersi de geçerlidir. +Kullanıcı izinlerinin ayırt edilmediği, yönetimi olan çok basit web sitelerinde, yetkilendirme kriteri olarak zaten bilinen `isLoggedIn()` metodu kullanılabilir. Başka bir deyişle: kullanıcı giriş yaptığında, tüm izinlere sahiptir ve tersi de geçerlidir. ```php -if ($user->isLoggedIn()) { // kullanıcı giriş yaptı mı? - deleteItem(); // eğer öyleyse, bir öğeyi silebilir +if ($user->isLoggedIn()) { // kullanıcı giriş yapmış mı? + deleteItem(); // o zaman operasyon için izni var } ``` -Roller .[#toc-roles] --------------------- +Roller +------ -Rollerin amacı daha hassas bir izin yönetimi sunmak ve kullanıcı adından bağımsız kalmaktır. Kullanıcı oturum açar açmaz, kendisine bir veya daha fazla rol atanır. Rollerin kendileri basit dizeler olabilir, örneğin, `admin`, `member`, `guest`, vb. Bunlar `SimpleIdentity` kurucusunun ikinci argümanında bir dize ya da dizi olarak belirtilir. +Rollerin amacı, daha hassas izin kontrolü sunmak ve kullanıcı adından bağımsız kalmaktır. Her kullanıcıya giriş yaparken, içinde hareket edeceği bir veya daha fazla rol atanır. Roller, örneğin `admin`, `member`, `guest` gibi basit karakter dizileri olabilir. `SimpleIdentity` yapıcısının ikinci parametresi olarak, ya bir karakter dizisi ya da bir karakter dizisi dizisi - roller - olarak belirtilirler. -Bir yetkilendirme kriteri olarak, şimdi kullanıcının verilen rolde olup olmadığını kontrol eden `isInRole()` yöntemini kullanacağız: +Yetkilendirme kriteri olarak şimdi, kullanıcının belirli bir rolde olup olmadığını söyleyen `isInRole()` metodunu kullanacağız: ```php -if ($user->isInRole('admin')) { // kullanıcıya yönetici rolü atanmış mı? - deleteItem(); // eğer öyleyse, bir öğeyi silebilir +if ($user->isInRole('admin')) { // kullanıcı admin rolünde mi? + deleteItem(); // o zaman operasyon için izni var } ``` -Bildiğiniz gibi, kullanıcının oturumu kapatması kimliğini silmemektedir. Bu nedenle, `getIdentity()` yöntemi, verilen tüm roller de dahil olmak üzere `SimpleIdentity` nesnesini döndürmeye devam eder. Nette Framework "daha az kod, daha fazla güvenlik" ilkesine bağlıdır, bu nedenle rolleri kontrol ederken kullanıcının oturum açıp açmadığını da kontrol etmeniz gerekmez. `isInRole()` yöntemi **etkili roller** ile çalışır, yani kullanıcı oturum açmışsa, kimliğe atanan roller kullanılır, oturum açmamışsa, bunun yerine otomatik bir özel rol `guest` kullanılır. +Bildiğiniz gibi, kullanıcı oturumdan çıktıktan sonra kimliği silinmeyebilir. Yani, `getIdentity()` metodu hala verilen tüm roller dahil olmak üzere `SimpleIdentity` nesnesini döndürür. Nette Framework, "daha az kod, daha fazla güvenlik" ilkesini benimser, burada daha az yazmak daha güvenli koda yol açar, bu nedenle rolleri kontrol ederken kullanıcının giriş yapıp yapmadığını da kontrol etmeniz gerekmez. `isInRole()` metodu **etkin rollerle** çalışır, yani kullanıcı giriş yapmışsa, kimlikte belirtilen rollere dayanır, giriş yapmamışsa otomatik olarak özel `guest` rolüne sahiptir. -Yetkilendirici .[#toc-authorizator] ------------------------------------ +Yetkilendirici +-------------- -Rollere ek olarak, kaynak ve operasyon terimlerini de tanıtacağız: +Rollere ek olarak, kaynak ve operasyon kavramlarını da tanıtacağız: -- rol** bir kullanıcı niteliğidir - örneğin moderatör, editör, ziyaretçi, kayıtlı kullanıcı, yönetici, ... -- kaynak** uygulamanın mantıksal bir birimidir - makale, sayfa, kullanıcı, menü öğesi, anket, sunucu, ... -- işlem** kullanıcının *kaynak* ile yapabileceği veya yapamayacağı belirli bir faaliyettir - görüntüleme, düzenleme, silme, oylama, ... +- **rol** kullanıcının bir özelliğidir - örn. moderatör, editör, ziyaretçi, kayıtlı kullanıcı, yönetici... +- **kaynak** (*resource*) web sitesinin mantıksal bir öğesidir - makale, sayfa, kullanıcı, menü öğesi, anket, presenter, ... +- **operasyon** (*operation*) kullanıcının kaynakla yapabileceği veya yapamayacağı belirli bir faaliyettir - örneğin silme, düzenleme, oluşturma, oy verme, ... -Yetkilendirici, belirli bir *rolün* belirli bir *kaynak* ile belirli bir *işlem* gerçekleştirme iznine sahip olup olmadığına karar veren bir nesnedir. Sadece bir yöntemle [api:Nette\Security\Authorizator] arayüzünü uygulayan bir nesnedir `isAllowed()`: +Yetkilendirici, belirli bir *rolün* belirli bir *kaynakla* belirli bir *operasyonu* gerçekleştirme iznine sahip olup olmadığına karar veren bir nesnedir. Tek bir `isAllowed()` metoduna sahip [api:Nette\Security\Authorizator] arayüzünü uygulayan bir nesnedir: ```php class MyAuthorizator implements Nette\Security\Authorizator @@ -63,50 +63,50 @@ class MyAuthorizator implements Nette\Security\Authorizator } ``` -Yetkilendiriciyi DI konteynerinin [bir servisi olarak |dependency-injection:services] yapılandırmaya ekliyoruz: +Yetkilendiriciyi DI konteynerinin [bir servisi olarak|dependency-injection:services] yapılandırmaya ekleyeceğiz: ```neon services: - MyAuthorizator ``` -Ve aşağıda bir kullanım örneği verilmiştir. Bu kez yetkilendiricinin değil `Nette\Security\User::isAllowed()` yöntemini çağırdığımıza dikkat edin, bu nedenle ilk parametre `$role` değildir. Bu yöntem `MyAuthorizator::isAllowed()` adresini tüm kullanıcı rolleri için sırayla çağırır ve en az birinin izni varsa true değerini döndürür. +Ve kullanım örneği aşağıdadır. Dikkat, bu sefer `Nette\Security\User::isAllowed()` metodunu çağırıyoruz, yetkilendiriciyi değil, bu yüzden ilk parametre `$role` yok. Bu metot, `MyAuthorizator::isAllowed()`'ı kullanıcının tüm rolleri için sırayla çağırır ve en az birinin izni varsa true döndürür. ```php -if ($user->isAllowed('file')) { // kullanıcının 'file' kaynağı ile her şeyi yapmasına izin veriliyor mu? +if ($user->isAllowed('file')) { // kullanıcı 'file' kaynağıyla herhangi bir şey yapabilir mi? useFile(); } -if ($user->isAllowed('file', 'delete')) { // kullanıcının bir 'dosya' kaynağını silmesine izin veriliyor mu? +if ($user->isAllowed('file', 'delete')) { // 'file' kaynağı üzerinde 'delete' gerçekleştirebilir mi? deleteFile(); } ``` -Her iki argüman da isteğe bağlıdır ve varsayılan değerleri *her şey* anlamına gelir. +Her iki parametre de isteğe bağlıdır, varsayılan `null` değeri *herhangi bir şey* anlamına gelir. -İzin ACL .[#toc-permission-acl] -------------------------------- +Permission ACL +-------------- -Nette, izin ve erişim kontrolü için hafif ve esnek bir ACL (Erişim Kontrol Listesi) katmanı sunan [api:Nette\Security\Permission] sınıfı olan yerleşik bir yetkilendirici uygulaması ile birlikte gelir. Bu sınıfla çalıştığımızda, roller, kaynaklar ve bireysel izinler tanımlarız. Ve roller ve kaynaklar hiyerarşiler oluşturabilir. Açıklamak için bir web uygulaması örneği göstereceğiz: +Nette, izinleri ve erişimleri yönetmek için programcıya hafif ve esnek bir ACL (Erişim Kontrol Listesi) katmanı sağlayan yerleşik bir yetkilendirici uygulaması olan [api:Nette\Security\Permission] sınıfıyla birlikte gelir. Onunla çalışmak, rolleri, kaynakları ve bireysel izinleri tanımlamaktan ibarettir. Roller ve kaynaklar hiyerarşiler oluşturmaya izin verir. Açıklamak için bir web uygulaması örneği göstereceğiz: -- `guest`: oturum açmamış, web'in herkese açık bölümünü okumasına ve taramasına izin verilen ziyaretçi, yani makaleleri okumak, yorum yapmak ve anketlerde oy kullanmak -- `registered`: oturum açmış kullanıcı, bunun üzerine yorum gönderebilir +- `guest`: web sitesinin genel bölümünü okuyabilen ve gezebilen, yani makaleleri, yorumları okuyabilen ve anketlerde oy kullanabilen giriş yapmamış ziyaretçi +- `registered`: ayrıca yorum yapabilen giriş yapmış kayıtlı kullanıcı - `admin`: makaleleri, yorumları ve anketleri yönetebilir -Bu nedenle, belirli roller tanımladık (`guest`, `registered` ve `admin`) ve kullanıcıların erişebileceği veya üzerinde işlem yapabileceği kaynakları (`article`, `comments`, `poll`) belirttik (`view`, `vote`, `add`, `edit`). +Böylece belirli roller (`guest`, `registered` ve `admin`) tanımladık ve kullanıcıların belirli bir rolle erişebileceği veya belirli operasyonları (`view`, `vote`, `add`, `edit`) gerçekleştirebileceği kaynakları (`article`, `comment`, `poll`) belirttik. -Permission sınıfının bir örneğini oluşturur ve **rolleri** tanımlarız. Rollerin kalıtımını kullanmak mümkündür, bu da örneğin `admin` rolüne sahip bir kullanıcının sıradan bir web sitesi ziyaretçisinin yapabildiklerini (ve tabii ki daha fazlasını) yapabilmesini sağlar. +Permission sınıfının bir örneğini oluşturacağız ve **rolleri** tanımlayacağız. Rollerin kalıtımını kullanabiliriz, bu da örneğin yönetici (`admin`) rolüne sahip bir kullanıcının sıradan bir web sitesi ziyaretçisinin yapabildiği şeyleri (ve tabii ki daha fazlasını) yapabilmesini sağlar. ```php $acl = new Nette\Security\Permission; $acl->addRole('guest'); -$acl->addRole('registered', 'guest'); // 'registered', 'guest'den miras alınır -$acl->addRole('admin', 'registered'); // ve 'admin' 'registered'dan miras alır +$acl->addRole('registered', 'guest'); // 'registered', 'guest'ten miras alır +$acl->addRole('admin', 'registered'); // ve ondan 'admin' miras alır ``` -Şimdi kullanıcıların erişebileceği **kaynakların** bir listesini tanımlayacağız: +Şimdi kullanıcıların erişebileceği **kaynakların** listesini de tanımlayacağız. ```php $acl->addResource('article'); @@ -114,49 +114,49 @@ $acl->addResource('comment'); $acl->addResource('poll'); ``` -Kaynaklar kalıtım da kullanabilir, örneğin `$acl->addResource('perex', 'article')` adresini ekleyebiliriz. +Kaynaklar da kalıtım kullanabilir, örneğin `$acl->addResource('perex', 'article')` belirtmek mümkün olurdu. -Ve şimdi en önemli şey. Kimin ne yapabileceğini belirleyen **kuralları** aralarında tanımlayacağız: +Ve şimdi en önemli kısım. Kimin neyi neyle yapabileceğini belirleyen kuralları aralarında tanımlayacağız: ```php -// şimdi her şey reddedildi +// başlangıçta kimse hiçbir şey yapamaz -// misafirin makaleleri, yorumları ve anketleri görüntülemesine izin verin +// guest'in makaleleri, yorumları ve anketleri görüntülemesine izin ver $acl->allow('guest', ['article', 'comment', 'poll'], 'view'); -// ve ayrıca anketlerde oy kullanabilir +// ve anketlerde ayrıca oy kullanmasına izin ver $acl->allow('guest', 'poll', 'vote'); -// registered guesta'dan izinleri miras alır, ayrıca yorum yapmasına da izin vereceğiz +// kayıtlı, guest'ten hakları miras alır, ona ayrıca yorum yapma hakkı verelim $acl->allow('registered', 'comment', 'add'); // yönetici her şeyi görüntüleyebilir ve düzenleyebilir $acl->allow('admin', $acl::All, ['view', 'edit', 'add']); ``` -Birinin bir kaynağa erişmesini **engellemek** istersek ne olur? +Birinin belirli bir kaynağa erişimini **engellemek** istersek ne olur? ```php -// administrator anketleri düzenleyemez, bu antidemokratik olur. +// yönetici anketleri düzenleyemez, bu demokratik olmazdı $acl->deny('admin', 'poll', 'edit'); ``` -Şimdi kurallar setini oluşturduğumuzda, basitçe yetkilendirme sorgularını sorabiliriz: +Şimdi kurallar listesini oluşturduğumuza göre, yetkilendirme sorgularını kolayca sorabiliriz: ```php -// misafir makaleleri görüntüleyebilir mi? +// guest makaleleri görüntüleyebilir mi? $acl->isAllowed('guest', 'article', 'view'); // true -// misafir bir makaleyi düzenleyebilir mi? +// guest makaleleri düzenleyebilir mi? $acl->isAllowed('guest', 'article', 'edit'); // false -// misafir anketlerde oy kullanabilir mi? +// guest anketlerde oy kullanabilir mi? $acl->isAllowed('guest', 'poll', 'vote'); // true -// misafir yorum ekleyebilir mi? +// guest yorum yapabilir mi? $acl->isAllowed('guest', 'comment', 'add'); // false ``` -Aynı şey kayıtlı bir kullanıcı için de geçerlidir, ancak o da yorum yapabilir: +Aynısı kayıtlı kullanıcı için de geçerlidir, ancak o yorum da yapabilir: ```php $acl->isAllowed('registered', 'article', 'view'); // true @@ -172,7 +172,7 @@ $acl->isAllowed('admin', 'poll', 'edit'); // false $acl->isAllowed('admin', 'comment', 'edit'); // true ``` -İzinler dinamik olarak da değerlendirilebilir ve kararı tüm parametrelerin aktarıldığı kendi geri çağrımıza bırakabiliriz: +İzinler dinamik olarak da değerlendirilebilir ve kararı tüm parametreleri alan kendi geri aramamıza bırakabiliriz: ```php $assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { @@ -182,7 +182,7 @@ $assertion = function (Permission $acl, string $role, string $resource, string $ $acl->allow('registered', 'comment', null, $assertion); ``` -Ancak, rollerin ve kaynakların adlarının yeterli olmadığı bir durumu nasıl çözebiliriz, yani örneğin, bir rolün `registered` bir kaynağı `article` yalnızca yazarı ise düzenleyebileceğini tanımlamak istiyoruz? Dizeler yerine nesneler kullanacağız, rol [api:Nette\Security\Role] nesnesi ve kaynak [api:Nette\Security\Resource] olacak. Metotları `getRoleId()` resp. `getResourceId()` orijinal dizgileri döndürecektir: +Ancak, örneğin rol ve kaynak adlarının yeterli olmadığı, ancak örneğin `registered` rolünün `article` kaynağını yalnızca yazarıysa düzenleyebileceğini tanımlamak istediğimiz bir durumu nasıl ele alırız? Karakter dizileri yerine nesneler kullanacağız, rol [api:Nette\Security\Role] nesnesi ve kaynak [api:Nette\Security\Resource] nesnesi olacaktır. `getRoleId()` ve `getResourceId()` metotları orijinal karakter dizilerini döndürecektir: ```php class Registered implements Nette\Security\Role @@ -207,19 +207,19 @@ class Article implements Nette\Security\Resource } ``` -Ve şimdi bir kural oluşturalım: +Ve şimdi kuralı oluşturacağız: ```php $assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { - $role = $acl->getQueriedRole(); // nesne Kayıtlı - $resource = $acl->getQueriedResource(); // object Article + $role = $acl->getQueriedRole(); // Registered nesnesi + $resource = $acl->getQueriedResource(); // Article nesnesi return $role->id === $resource->authorId; }; $acl->allow('registered', 'article', 'edit', $assertion); ``` -ACL, nesneler geçirilerek sorgulanır: +Ve ACL sorgusu nesneleri aktararak gerçekleştirilir: ```php $user = new Registered(/* ... */); @@ -227,7 +227,7 @@ $article = new Article(/* ... */); $acl->isAllowed($user, $article, 'edit'); ``` -Bir rol, bir veya daha fazla başka rolden miras alabilir. Ancak, bir atanın belirli bir eyleme izin vermesi ve diğerinin bunu reddetmesi durumunda ne olur? O zaman *rol ağırlığı* devreye girer - miras alınacak roller dizisindeki son rol en büyük, ilk rol ise en düşük ağırlığa sahiptir: +Bir rol, başka bir rolden veya birden fazla rolden miras alabilir. Ancak bir atanın eylemi yasaklanmış ve diğerinin izin verilmişse ne olur? Torunun hakları ne olacak? Rolün ağırlığına göre belirlenir - ata listesinde son belirtilen rol en büyük ağırlığa sahiptir, ilk belirtilen rol en küçük ağırlığa sahiptir. Örnekten daha açıklayıcıdır: ```php $acl = new Nette\Security\Permission; @@ -239,22 +239,22 @@ $acl->addResource('backend'); $acl->allow('admin', 'backend'); $acl->deny('guest', 'backend'); -// örnek A: admin rolü guest rolünden daha düşük ağırlığa sahiptir +// durum A: admin rolü guest rolünden daha az ağırlığa sahip $acl->addRole('john', ['admin', 'guest']); $acl->isAllowed('john', 'backend'); // false -// örnek B: admin rolü guest rolünden daha fazla ağırlığa sahiptir +// durum B: admin rolü guest'ten daha fazla ağırlığa sahip $acl->addRole('mary', ['guest', 'admin']); $acl->isAllowed('mary', 'backend'); // true ``` -Roller ve kaynaklar da kaldırılabilir (`removeRole()`, `removeResource()`), kurallar da geri alınabilir (`removeAllow()`, `removeDeny()`). Tüm doğrudan üst rollerin dizisi `getRoleParents()` döndürür. İki varlığın birbirinden miras alıp almadığı `roleInheritsFrom()` ve `resourceInheritsFrom()` döndürür. +Roller ve kaynaklar da kaldırılabilir (`removeRole()`, `removeResource()`), kurallar da geri alınabilir (`removeAllow()`, `removeDeny()`). Tüm doğrudan ebeveyn rollerinin dizisini `getRoleParents()` döndürür, iki varlığın birbirinden miras alıp almadığını `roleInheritsFrom()` ve `resourceInheritsFrom()` döndürür. -Hizmet Olarak Ekle .[#toc-add-as-a-service] -------------------------------------------- +Servis Olarak Ekleme +-------------------- -Oluşturduğumuz ACL'yi `$user` nesnesi tarafından kullanılabilmesi için yani örneğin `$user->isAllowed('article', 'view')` kodunda kullanabilmemiz için yapılandırmaya bir servis olarak eklememiz gerekiyor. Bu amaçla bunun için bir fabrika yazacağız: +Oluşturduğumuz ACL'yi bir servis olarak yapılandırmaya aktarmamız gerekiyor, böylece `$user` nesnesi onu kullanmaya başlar, yani kodda örneğin `$user->isAllowed('article', 'view')` kullanmak mümkün olur. Bu amaçla onun için bir fabrika yazacağız: ```php namespace App\Model; @@ -272,21 +272,21 @@ class AuthorizatorFactory } ``` -Ve bunu yapılandırmaya ekleyeceğiz: +Ve onu yapılandırmaya ekleyeceğiz: ```neon services: - App\Model\AuthorizatorFactory::create ``` -Sunucularda, örneğin `startup()` yönteminde izinleri doğrulayabilirsiniz: +Presenter'larda daha sonra izinleri örneğin `startup()` metodunda doğrulayabilirsiniz: ```php protected function startup() { parent::startup(); if (!$this->getUser()->isAllowed('backend')) { - $this->error('Forbidden', 403); + $this->error('Yasak', 403); } } ``` diff --git a/security/tr/configuration.texy b/security/tr/configuration.texy index 1aa1bcfa8b..b2565f002a 100644 --- a/security/tr/configuration.texy +++ b/security/tr/configuration.texy @@ -1,71 +1,85 @@ -Erişim Kontrolünü Yapılandırma +Erişim İzinlerini Yapılandırma ****************************** .[perex] Nette Security için yapılandırma seçeneklerine genel bakış. -Tüm çerçeveyi değil, yalnızca bu kütüphaneyi kullanıyorsanız, [yapılandırmayı nasıl yükleyeceğinizi |bootstrap:] okuyun. +Eğer tüm framework'ü değil de yalnızca bu kütüphaneyi kullanıyorsanız, [yapılandırmanın nasıl yükleneceğini|bootstrap:] okuyun. -[Basit |authentication] bir [kimlik doğrulayıcı |authentication] oluşturmak için yapılandırmada bir kullanıcı listesi tanımlayabilirsiniz (`Nette\Security\SimpleAuthenticator`). Parolalar yapılandırmada okunabildiğinden, bu çözüm yalnızca test amaçlıdır. +Yapılandırmada kullanıcı listesi tanımlanabilir ve böylece [basit bir kimlik doğrulayıcı|authentication] (`Nette\Security\SimpleAuthenticator`) oluşturulabilir. Yapılandırmada şifreler okunabilir biçimde belirtildiği için, bu çözüm yalnızca test amaçlıdır. ```neon security: - # Tracy Bar'da kullanıcı panelini gösterir mi? - debugger: ... # (bool) varsayılan değer true + # Tracy Bar'da kullanıcı panelini göster? + debugger: ... # (bool) varsayılan true'dur users: - # isim: şifre - johndoe: secret123 + # kullanıcı adı: şifre + frantisek: gizlisifre - # ad, parola, rol ve kimlikte bulunan diğer veriler - janedoe: - password: secret123 + # kullanıcı adı, şifre, rol ve kimlikte mevcut diğer veriler + dobrota: + password: gizlisifre roles: [admin] data: ... ``` -[Yetkilendirici |authorization] için bir temel oluşturmak üzere rolleri ve kaynakları da tanımlayabilirsiniz (`Nette\Security\Permission`): +Ayrıca roller ve kaynaklar tanımlanabilir ve böylece [yetkilendirici|authorization] (`Nette\Security\Permission`) için temel oluşturulabilir: ```neon security: roles: guest: - registered: [guest] # registered guest'den miras alır - admin: [registered] # ve admin registered'dan miras alır + registered: [guest] # registered, guest'ten miras alır + admin: [registered] # ve ondan admin miras alır resources: article: - comment: [article] # kaynak article'dan miras alır + comment: [article] # kaynak, article'dan miras alır poll: ``` -Kullanıcı Deposu .[#toc-user-storage] -------------------------------------- +Depolama Alanı +-------------- -Oturum açan kullanıcı hakkındaki bilgilerin nasıl saklanacağını yapılandırabilirsiniz: +Giriş yapmış kullanıcı hakkındaki bilgilerin nasıl saklanacağı yapılandırılabilir: ```neon security: authentication: - # ne kadar süre hareketsiz kaldıktan sonra kullanıcının oturumu kapatılacak - expiration: 30 minutes # (string) varsayılan ayarlanmamış + # ne kadar süre hareketsizlikten sonra kullanıcı oturumdan çıkarılacak + expiration: 30 minutes # (string) varsayılan ayarlanmamıştır - # oturum açmış kullanıcı hakkındaki bilgilerin saklanacağı yer - storage: session # (session|cookie) default is session + # giriş yapmış kullanıcı hakkındaki bilgilerin nereye saklanacağı + storage: session # (session|cookie) varsayılan session'dır ``` -Deponuz olarak `cookie` adresini seçerseniz, aşağıdaki seçenekleri de ayarlayabilirsiniz: +Depolama alanı olarak `cookie` seçerseniz, şu seçenekleri de ayarlayabilirsiniz: ```neon security: authentication: - # çerezin adı - cookieName: userId # (string) výchozí je userid + # cookie adı + cookieName: userId # (string) varsayılan userid'dir - # hangi ana bilgisayarların çerezi almasına izin verilir + # cookie'yi kabul eden alan adları cookieDomain: 'example.com' # (string|domain) - # çapraz kökenli isteklere erişirken kısıtlamalar - cookieSamesite: None # (Strict|Lax|None) varsayılan olarak Lax + # başka bir alan adından erişimde kısıtlama + cookieSamesite: None # (Strict|Lax|None) varsayılan Lax'tır ``` + + +DI Servisleri +------------- + +Bu servisler DI konteynerine eklenir: + +| Ad | Tip | Açıklama +|---------------------------------------------------------- +| `security.authenticator` | [api:Nette\Security\Authenticator] | [kimlik doğrulayıcı|authentication] +| `security.authorizator` | [api:Nette\Security\Authorizator] | [yetkilendirici|authorization] +| `security.passwords` | [api:Nette\Security\Passwords] | [şifre karma|passwords] +| `security.user` | [api:Nette\Security\User] | mevcut kullanıcı +| `security.userStorage` | [api:Nette\Security\UserStorage] | [#depolama alanı] diff --git a/security/tr/passwords.texy b/security/tr/passwords.texy index 431eaeffa8..9c04d303f2 100644 --- a/security/tr/passwords.texy +++ b/security/tr/passwords.texy @@ -1,12 +1,12 @@ -Parola Hashing -************** +Şifre Karma İşlemi +****************** .[perex] -Kullanıcılarımızın güvenliğini yönetmek için, şifrelerini asla düz metin biçiminde saklamıyoruz, bunun yerine şifrenin özetini saklıyoruz. Hashing tersine çevrilebilir bir işlem değildir, parola kurtarılamaz. Ancak parola kırılabilir ve kırmayı mümkün olduğunca zorlaştırmak için güvenli bir algoritma kullanmamız gerekir. [api:Nette\Security\Passwords] sınıfı bize bu konuda yardımcı olacaktır. +Kullanıcılarımızın güvenliğini sağlamak için şifrelerini okunabilir biçimde saklamıyoruz, yalnızca özetlerini (hash olarak adlandırılır) saklıyoruz. Özeten orijinal şifreyi geri oluşturmak mümkün değildir. Özeti oluşturmak için güvenli bir algoritma kullanmak önemlidir. [api:Nette\Security\Passwords] sınıfı bu konuda bize yardımcı olur. -→ [Kurulum ve gereksinimler |@home#Installation] +→ [Kurulum ve Gereksinimler |@home#Kurulum] -Çerçeve, DI konteynerine `security.passwords` adı altında otomatik olarak bir `Nette\Security\Passwords` hizmeti ekler ve bunu [bağımlılık enjeksiyonu |dependency-injection:passing-dependencies] kullanarak geçirirsiniz: +Framework, DI konteynerine otomatik olarak `security.passwords` adı altında `Nette\Security\Passwords` türünde bir servis ekler, buna [dependency injection |dependency-injection:passing-dependencies] kullanarak erişebilirsiniz. ```php use Nette\Security\Passwords; @@ -24,44 +24,44 @@ class Foo __construct($algo=PASSWORD_DEFAULT, array $options=[]): string .[method] ======================================================================== -Hashing için hangi [güvenli algoritmanın |https://www.php.net/manual/en/password.constants.php] kullanılacağını ve bunun nasıl yapılandırılacağını seçer. +Hash oluşturmak için hangi [güvenli algoritmanın|https://www.php.net/manual/en/password.constants.php] kullanılacağını seçer ve parametrelerini yapılandırırız. -Varsayılan değer `PASSWORD_DEFAULT` olduğundan algoritma seçimi PHP'ye bırakılmıştır. Algoritma, daha yeni ve daha güçlü hash algoritmalarının desteklendiği yeni PHP sürümlerinde değişebilir. Bu nedenle, elde edilen hash'in uzunluğunun değişebileceğinin farkında olmalısınız. Bu nedenle, elde edilen hash'i yeterli karakteri saklayabilecek şekilde saklamalısınız, 255 önerilen genişliktir. +Varsayılan olarak `PASSWORD_DEFAULT` kullanılır, yani algoritma seçimi PHP'ye bırakılır. Daha yeni, daha güçlü karma algoritmaları ortaya çıkarsa, algoritma daha yeni PHP sürümlerinde değişebilir. Bu nedenle, sonuçta ortaya çıkan hash'in uzunluğunun değişebileceğinin farkında olmalısınız ve yeterli karakter alabilecek bir şekilde saklamalısınız, 255 önerilen genişliktir. -Bu, bcrypt algoritmasını nasıl kullanacağınızı ve varsayılan 10'dan maliyet parametresini kullanarak karma hızını nasıl değiştireceğinizi gösterir. 2020 yılında, maliyet 10 ile bir parolanın hashlenmesi yaklaşık 80 ms, maliyet 11 160 ms, maliyet 12 ise 320 ms sürer, ölçek logaritmiktir. Ne kadar yavaş olursa o kadar iyidir, maliyet 10-12 çoğu kullanım durumu için yeterince yavaş kabul edilir: +Bcrypt algoritmasının karma hızını cost parametresini değiştirerek ayarlama örneği: (2020'de varsayılan 10'dur, şifre karma işlemi yaklaşık 80ms sürer, cost 11 için yaklaşık 160ms, cost 12 için yaklaşık 320ms, ne kadar yavaşsa o kadar iyi koruma, 10-12 hızı zaten yeterli koruma olarak kabul edilir) ```php -// şifreleri bcrypt algoritmasının 2^12 (2^maliyet) iterasyonu ile hash edeceğiz +// şifreleri bcrypt algoritmasının 2^12 (2^cost) iterasyonuyla karma yapacağız $passwords = new Passwords(PASSWORD_BCRYPT, ['cost' => 12]); ``` -Bağımlılık enjeksiyonu ile: +Dependency injection kullanarak: ```neon services: security.passwords: Nette\Security\Passwords(::PASSWORD_BCRYPT, [cost: 12]) ``` -hash(string $passwords): string .[method] -========================================= +hash(string $password): string .[method] +======================================== -Parolanın özetini oluşturur. +Şifrenin hash'ini oluşturur. ```php -$res = $passwords->hash($password); // Parolayı karma hale getirir +$res = $passwords->hash($password); // Şifreyi hash'ler ``` -Sonuç `$res`, hash'in kendisine ek olarak kullanılan algoritmanın tanımlayıcısını, ayarlarını ve kriptografik tuzu (aynı parola için farklı bir hash oluşturulmasını sağlayan rastgele veriler) içeren bir dizedir. Bu nedenle geriye dönük olarak uyumludur, örneğin parametreleri değiştirirseniz, önceki ayarlar kullanılarak saklanan karmalar doğrulanabilir. Bu sonucun tamamı veritabanında saklanır, bu nedenle tuzu veya ayarları ayrı olarak saklamaya gerek yoktur. +`$res` sonucu, hash'in kendisinin yanı sıra kullanılan algoritmanın tanımlayıcısını, ayarlarını ve kriptografik tuzu (aynı şifre için farklı bir hash oluşturulmasını sağlayan rastgele veriler) içeren bir karakter dizisidir. Bu nedenle geriye dönük uyumludur, örneğin parametreleri değiştirirseniz, önceki ayarları kullanarak saklanan hash'ler bile doğrulanabilir. Tüm bu sonuç veritabanına kaydedilir, bu nedenle tuzu veya ayarları ayrı ayrı kaydetmeye gerek yoktur. verify(string $password, string $hash): bool .[method] ====================================================== -Verilen parolanın verilen hash ile eşleşip eşleşmediğini bulur. Veritabanından `$hash` adresini kullanıcı adı veya e-posta adresine göre alır. +Verilen şifrenin verilen özete karşılık gelip gelmediğini kontrol eder. `$hash`'i, girilen kullanıcı adına veya e-posta adresine göre veritabanından alın. ```php if ($passwords->verify($password, $hash)) { - // Doğru şifre + // doğru şifre } ``` @@ -69,9 +69,9 @@ if ($passwords->verify($password, $hash)) { needsRehash(string $hash): bool .[method] ========================================= -Hash'in yapıcıda verilen seçeneklerle eşleşip eşleşmediğini bulur. +Hash'in yapıcıda belirtilen seçeneklere karşılık gelip gelmediğini kontrol eder. -Örneğin hash parametrelerini değiştirirken bu yöntemi kullanın. Parola doğrulama, hash ile birlikte saklanan parametreleri kullanır ve `needsRehash()` true değerini döndürürse, hash'i bu kez güncellenmiş parametrelerle yeniden hesaplamanız ve tekrar veritabanında saklamanız gerekir. Bu, kullanıcılar oturum açarken parola özetlerinin otomatik olarak "yükseltilmesini" sağlar. +Örneğin karma hızını değiştirdiğinizde kullanışlıdır. Doğrulama, saklanan ayarlara göre gerçekleşir ve `needsRehash()` `true` döndürürse, hash'i yeni parametrelerle yeniden oluşturmak ve tekrar veritabanına kaydetmek gerekir. Bu şekilde, saklanan hash'ler kullanıcılar giriş yaptığında otomatik olarak "yükseltilir". ```php if ($passwords->needsRehash($hash)) { diff --git a/security/uk/@home.texy b/security/uk/@home.texy index 2436dc921b..4ba806d2d2 100644 --- a/security/uk/@home.texy +++ b/security/uk/@home.texy @@ -1,14 +1,14 @@ -Безпека -******* +Nette Security +************** .[perex] -Пакет `nette/security` відповідає за [аутентифікацію користувачів |authentication], [контроль доступу |authorization] та [хешування паролів |passwords]. +Пакет `nette/security` відповідає за [вхід користувачів |authentication], [перевірку прав доступу |authorization] та [хешування паролів |passwords]. -Встановлення .[#toc-installation] ---------------------------------- +Встановлення +------------ -Завантажте та встановіть пакет за допомогою [Composer |best-practices:composer]: +Завантажте та встановіть бібліотеку за допомогою [Composer|best-practices:composer]: ```shell composer require nette/security diff --git a/security/uk/@left-menu.texy b/security/uk/@left-menu.texy index f094712ca8..73b7e51f84 100644 --- a/security/uk/@left-menu.texy +++ b/security/uk/@left-menu.texy @@ -1,7 +1,7 @@ -Безпека Nette -************* -- [Огляд |@home] -- [Аутентифікація |Authentication] -- [Авторизація |Authorization] +Nette Security +************** +- [Вступ |@home] +- [Автентифікація |authentication] +- [Авторизація |authorization] - [Хешування паролів |passwords] -- [Конфігурація |Configuration] +- [Конфігурація |configuration] diff --git a/security/uk/@meta.texy b/security/uk/@meta.texy new file mode 100644 index 0000000000..96e2d9752a --- /dev/null +++ b/security/uk/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документація Nette}} diff --git a/security/uk/authentication.texy b/security/uk/authentication.texy index 137f50e631..43f906eccd 100644 --- a/security/uk/authentication.texy +++ b/security/uk/authentication.texy @@ -1,48 +1,48 @@ -Аутентифікація користувачів -*************************** +Вхід користувачів (Автентифікація) +********************************** <div class=perex> -Мало-мальськи значущі веб-додатки не потребують механізму для входу користувачів у систему або перевірки їхніх привілеїв. У цьому розділі ми поговоримо про: +Майже жоден веб-застосунок не обходиться без механізму входу користувачів та перевірки їхніх прав доступу. У цьому розділі ми поговоримо про: -- вхід і вихід користувача -- призначені для користувача аутентифікатори та авторизатори +- вхід та вихід користувачів +- власні автентифікатори </div> -→ [Встановлення та вимоги |@home#Installation] +→ [Встановлення та вимоги |@home#Встановлення] -У прикладах ми будемо використовувати об'єкт класу [api:Nette\Security\User], який представляє поточного користувача і який ви отримуєте, передаючи його за допомогою [ін'єкції залежностей |dependency-injection:passing-dependencies]. У презентаторах просто викликайте `$user = $this->getUser()`. +У прикладах ми будемо використовувати об'єкт класу [api:Nette\Security\User], який представляє поточного користувача і до якого ви можете отримати доступ, попросивши його передати за допомогою [dependency injection |dependency-injection:passing-dependencies]. У presenter'ах достатньо лише викликати `$user = $this->getUser()`. -Аутентифікація .[#toc-authentication] -===================================== +Автентифікація +============== -Аутентифікація означає **вхід користувача в систему**, тобто процес, під час якого перевіряється особистість користувача. Користувач зазвичай ідентифікує себе за допомогою імені користувача та пароля. Верифікація виконується так званим [аутентифікатором |#Authenticator]. Якщо вхід у систему не вдається, відбувається викид `Nette\Security\AuthenticationException`. +Автентифікацією називається **вхід користувачів**, тобто процес, під час якого перевіряється, чи є користувач дійсно тим, за кого себе видає. Зазвичай він підтверджує свою особу за допомогою імені користувача та пароля. Перевірку проводить так званий [#Автентифікатор]. Якщо вхід не вдається, викидається `Nette\Security\AuthenticationException`. ```php try { $user->login($username, $password); } catch (Nette\Security\AuthenticationException $e) { - $this->flashMessage('The username or password you entered is incorrect.'); + $this->flashMessage('Ім\'я користувача або пароль неправильні'); } ``` -Ось як вийти із системи: +Таким чином ви виходите з системи користувача: ```php $user->logout(); ``` -І перевірити, чи увійшов користувач у систему: +А перевірка, чи він залогінений: ```php -echo $user->isLoggedIn() ? 'yes' : 'no'; +echo $user->isLoggedIn() ? 'так' : 'ні'; ``` -Просто, правда? І всі аспекти безпеки обробляються Nette за вас. +Дуже просто, чи не так? А всі аспекти безпеки Nette вирішує за вас. -У Presenter ви можете перевірити вхід у систему в методі `startup()` і перенаправити незалогіненого користувача на сторінку входу. +У presenter'ах ви можете перевірити вхід у методі `startup()` і перенаправити незалогіненого користувача на сторінку входу. ```php protected function startup() @@ -55,39 +55,38 @@ protected function startup() ``` -Термін дії .[#toc-expiration] -============================= +Термін дії +========== -Логін користувача закінчується разом із [закінченням терміну дії сховища |#Storage-for-Logged-User], який зазвичай є сесією (див. налаштування [закінчення терміну дії сесії |http:configuration#Session] ). -Однак можна задати і більш короткий проміжок часу, після закінчення якого користувач виходить із системи. Для цього використовується метод `setExpiration()`, який викликається перед `login()`. Як параметр надайте рядок із відносним часом: +Вхід користувача закінчується разом із [терміном дії сховища |#Сховище залогіненого користувача], яким зазвичай є сесія (див. налаштування [терміну дії сесії |http:configuration#Сесія]). Але можна встановити і коротший часовий інтервал, після закінчення якого користувач буде вилогінений. Для цього служить метод `setExpiration()`, який викликається перед `login()`. Як параметр вкажіть рядок з відносним часом: ```php -// термін дії логіна закінчується після 30 хвилин бездіяльності +// вхід закінчиться після 30 хвилин неактивності $user->setExpiration('30 minutes'); // скасування встановленого терміну дії $user->setExpiration(null); ``` -Метод `$user->getLogoutReason()` визначає, чи вийшов користувач із системи, оскільки минув часовий інтервал. Він повертає або константу `Nette\Security\UserStorage::LogoutInactivity`, якщо час минув, або `UserStorage::LogoutManual`, якщо було викликано метод `logout()`. +Чи був користувач вилогінений через закінчення часового інтервалу, повідомить метод `$user->getLogoutReason()`, який повертає або константу `Nette\Security\UserStorage::LogoutInactivity` (закінчився часовий ліміт), або `UserStorage::LogoutManual` (вилогінений методом `logout()`). -Аутентифікатор .[#toc-authenticator] -==================================== +Автентифікатор +============== -Це об'єкт, який перевіряє дані для входу в систему, тобто зазвичай ім'я та пароль. Тривіальною реалізацією є клас [api:Nette\Security\SimpleAuthenticator], який може бути визначений у [конфігурації |configuration]: +Це об'єкт, який перевіряє облікові дані, тобто зазвичай ім'я та пароль. Тривіальною формою є клас [api:Nette\Security\SimpleAuthenticator], який ми можемо визначити в [конфігурації|configuration]: ```neon security: users: - # name: password - johndoe: secret123 - kathy: evenmoresecretpassword + # ім'я: пароль + frantisek: tajneheslo + katka: jestetajnejsiheslo ``` -Це рішення більше підходить для цілей тестування. Ми покажемо вам, як створити автентифікатор, який перевірятиме облікові дані за таблицею бази даних. +Це рішення підходить скоріше для тестових цілей. Покажемо, як створити автентифікатор, який буде перевіряти облікові дані за таблицею бази даних. -Аутентифікатор - це об'єкт, що реалізує інтерфейс [api:Nette\Security\Authenticator] з методом `authenticate()`. Його завдання - або повернути так званий [ідентифікатор |#Identity], або викинути виняток `Nette\Security\AuthenticationException`. Також можна було б надати код помилки `Authenticator::IdentityNotFound` або `Authenticator::InvalidCredential`. +Автентифікатор — це об'єкт, що реалізує інтерфейс [api:Nette\Security\Authenticator] з методом `authenticate()`. Його завданням є або повернути так звану [#ідентичність], або викинути виняток `Nette\Security\AuthenticationException`. Можна було б ще вказати код помилки для більш точного розрізнення ситуації: `Authenticator::IdentityNotFound` та `Authenticator::InvalidCredential`. ```php use Nette; @@ -108,25 +107,25 @@ class MyAuthenticator implements Nette\Security\Authenticator ->fetch(); if (!$row) { - throw new Nette\Security\AuthenticationException('User not found.'); + throw new Nette\Security\AuthenticationException('Користувача не знайдено.'); } if (!$this->passwords->verify($password, $row->password)) { - throw new Nette\Security\AuthenticationException('Invalid password.'); + throw new Nette\Security\AuthenticationException('Неправильний пароль.'); } return new SimpleIdentity( $row->id, - $row->role, // або масив ролей + $row->role, // або масив кількох ролей ['name' => $row->username], ); } } ``` -Клас MyAuthenticator взаємодіє з базою даних через [Nette Database Explorer |database:explorer] і працює з таблицею `users`, де стовпець `username` містить ім'я користувача для входу в систему, а стовпець `password` - [хеш |passwords]. Після перевірки імені та пароля він повертає ідентифікатор з ID користувача, роль (стовпчик `role` у таблиці), про яку ми згадаємо [пізніше |#Roles], і масив із додатковими даними (у нашому випадку ім'я користувача). +Клас MyAuthenticator спілкується з базою даних за допомогою [Nette Database Explorer|database:explorer] і працює з таблицею `users`, де в стовпці `username` знаходиться логін користувача, а в стовпці `password` — [відбиток пароля|passwords]. Після перевірки імені та пароля він повертає ідентичність, яка містить ID користувача, його роль (стовпець `role` у таблиці), про яку ми детальніше поговоримо [пізніше |authorization#Ролі], та масив з іншими даними (у нашому випадку ім'я користувача). -Ми додамо аутентифікатор у конфігурацію [як сервіс |dependency-injection:services] контейнера DI: +Автентифікатор ще додамо до конфігурації [як сервіс|dependency-injection:services] DI-контейнера: ```neon services: @@ -134,50 +133,50 @@ services: ``` -$onLoggedIn, $onLoggedOut Events --------------------------------- +Події $onLoggedIn, $onLoggedOut +------------------------------- -Об'єкт `Nette\Security\User` має [події |nette:glossary#Events] `$onLoggedIn` і `$onLoggedOut`, тому ви можете додати зворотні виклики, які спрацьовують після успішного входу в систему або після виходу користувача з системи. +Об'єкт `Nette\Security\User` має [події |nette:glossary#Події události] `$onLoggedIn` та `$onLoggedOut`, тому ви можете додати callback'и, які викликаються після успішного входу або виходу користувача відповідно. ```php $user->onLoggedIn[] = function () { - // користувач щойно увійшов у систему + // користувач щойно увійшов }; ``` -Ідентичність .[#toc-identity] -============================= +Ідентичність +============ -Ідентифікатор - це набір інформації про користувача, який повертається аутентифікатором і який потім зберігається в сесії та витягується за допомогою `$user->getIdentity()`. Таким чином, ми можемо отримати id, ролі та інші дані користувача в тому вигляді, в якому ми передали їх в аутентифікаторі: +Ідентичність представляє набір інформації про користувача, який повертає автентифікатор і який потім зберігається в сесії, і ми отримуємо його за допомогою `$user->getIdentity()`. Таким чином, ми можемо отримати id, ролі та інші дані користувача, так як ми їх передали в автентифікаторі: ```php $user->getIdentity()->getId(); -// також працює скорочення $user->getId(); +// працює і скорочення $user->getId(); $user->getIdentity()->getRoles(); -// дані користувача можуть бути доступні як властивості +// дані користувача доступні як властивості // ім'я, яке ми передали в MyAuthenticator $user->getIdentity()->name; ``` -Важливо зазначити, що коли користувач виходить із системи за допомогою `$user->logout()`, **ідентичність не видаляється** і все ще доступна. Таким чином, якщо ідентифікатор існує, він сам по собі не гарантує, що користувач також увійшов у систему. Якщо ми хочемо явно видалити ідентифікатор, ми виходимо з системи за допомогою `logout(true)`. +Важливо, що при виході за допомогою `$user->logout()` **ідентичність не видаляється** і залишається доступною. Отже, хоча користувач має ідентичність, він може бути не залогіненим. Якщо ми хочемо явно видалити ідентичність, ми вилогінимо користувача викликом `logout(true)`. -Завдяки цьому ви все ще можете визначити, який користувач перебуває за комп'ютером, і, наприклад, відображати персоналізовані пропозиції в інтернет-магазині, однак ви можете відображати його особисті дані тільки після входу в систему. +Завдяки цьому ви можете надалі припускати, який користувач знаходиться за комп'ютером, і, наприклад, показувати йому в інтернет-магазині персоналізовані пропозиції, однак відображати його особисті дані можна лише після входу. -Identity - це об'єкт, що реалізує інтерфейс [api:Nette\Security\IIdentity], реалізація за замовчуванням - [api:Nette\Security\SimpleIdentity]. Як уже згадувалося, ідентифікатор зберігається в сесії, тому якщо, наприклад, ми змінимо роль якогось із користувачів, які увійшли в систему, старі дані зберігатимуться в ідентифікаторі доти, доки він знову не увійде в систему. +Ідентичність — це об'єкт, що реалізує інтерфейс [api:Nette\Security\IIdentity], стандартною реалізацією є [api:Nette\Security\SimpleIdentity]. І, як було згадано, вона зберігається в сесії, тому якщо, наприклад, ми змінимо роль одного із залогінених користувачів, старі дані залишаться в його ідентичності до його повторного входу. -Зберігання даних для користувача, який увійшов .[#toc-storage-for-logged-user] -============================================================================== +Сховище залогіненого користувача +================================ -Дві основні частини інформації про користувача, тобто чи увійшов він у систему і його [особистість |#Identity], зазвичай зберігаються в сесії. Яка може бути змінена. За зберігання цієї інформації відповідає об'єкт, що реалізує інтерфейс `Nette\Security\UserStorage`. Існує дві стандартні реалізації, перша передає дані в сесії, друга - в cookie. Це класи `Nette\Bridges\SecurityHttp\SessionStorage` і `CookieStorage`. Вибрати сховище та налаштувати його дуже зручно в конфігурації [security › authentication |configuration]. +Дві основні інформації про користувача, тобто чи він залогінений та його [#Ідентичність], зазвичай передаються в сесії. Це можна змінити. За зберігання цієї інформації відповідає об'єкт, що реалізує інтерфейс `Nette\Security\UserStorage`. Доступні дві стандартні реалізації: перша передає дані в сесії, а друга — в cookie. Це класи `Nette\Bridges\SecurityHttp\SessionStorage` та `CookieStorage`. Вибрати сховище та налаштувати його можна дуже зручно в конфігурації [security › authentication |configuration#Сховище]. -Ви також можете контролювати, як саме відбуватиметься збереження (*sleep*) і відновлення (*wakeup*) аутентифікації. Все, що вам потрібно, це щоб аутентифікатор реалізовував інтерфейс `Nette\Security\IdentityHandler`. У нього є два методи: `sleepIdentity()` викликається перед записом ідентифікатора в сховище, а `wakeupIdentity()` - після зчитування ідентифікатора. Ці методи можуть змінювати вміст ідентифікатора або замінювати його новим об'єктом, який повертається. Метод `wakeupIdentity()` може навіть повертати `null`, який виводить користувача із системи. +Крім того, ви можете впливати на те, як саме буде відбуватися зберігання ідентичності (*sleep*) та відновлення (*wakeup*). Достатньо, щоб автентифікатор реалізував інтерфейс `Nette\Security\IdentityHandler`. Він має два методи: `sleepIdentity()` викликається перед записом ідентичності до сховища, а `wakeupIdentity()` — після її зчитування. Методи можуть змінювати вміст ідентичності або замінювати її новим об'єктом, який вони повернуть. Метод `wakeupIdentity()` може навіть повернути `null`, що призведе до виходу користувача з системи. -Як приклад ми покажемо розв'язання поширеного питання про те, як оновити ролі ідентифікатора відразу після відновлення із сесії. У методі `wakeupIdentity()` ми передаємо ідентифікатору поточні ролі, наприклад, з бази даних: +Як приклад покажемо вирішення частого питання, як оновити ролі в ідентичності одразу після завантаження з сесії. У методі `wakeupIdentity()` передамо до ідентичності актуальні ролі, наприклад, з бази даних: ```php final class Authenticator implements @@ -185,25 +184,25 @@ final class Authenticator implements { public function sleepIdentity(IIdentity $identity): IIdentity { - // тут ви можете змінити ідентифікатор перед зберіганням після входу в систему, + // тут можна змінити ідентичність перед записом до сховища після входу, // але зараз нам це не потрібно return $identity; } public function wakeupIdentity(IIdentity $identity): ?IIdentity { - // оновлення ролей в ідентифікації + // оновлення ролей в ідентичності $userId = $identity->getId(); $identity->setRoles($this->facade->getUserRoles($userId)); return $identity; } ``` -А тепер повернемося до сховища на основі cookie. Воно дає змогу створити сайт, на якому користувачі можуть входити в систему без необхідності використання сесій. Тому йому не потрібен запис на диск. Зрештою, саме так працює сайт, який ви зараз читаєте, включно з форумом. У цьому випадку реалізація `IdentityHandler` є необхідністю. Ми зберігатимемо в cookie тільки випадковий токен, що представляє користувача, який увійшов. +А тепер повернемося до сховища на основі cookie. Воно дозволяє вам створити веб-сайт, де користувачі можуть входити в систему, не потребуючи сесій. Тобто не потрібно записувати на диск. Власне, так працює і веб-сайт, який ви зараз читаєте, включно з форумом. У цьому випадку реалізація `IdentityHandler` є необхідністю. У cookie ми будемо зберігати лише випадковий токен, що представляє залогіненого користувача. -Тому спочатку ми задамо потрібне сховище в конфігурації за допомогою `security › authentication › storage: cookie`. +Спочатку в конфігурації встановимо потрібне сховище за допомогою `security › authentication › storage: cookie`. -Ми додамо в базу даних колонку `authtoken`, в якій кожен користувач матиме [абсолютно випадковий, унікальний і не вгадуваний |utils:random] рядок достатньої довжини (не менше 13 символів). Сховище `CookieStorage` зберігає тільки значення `$identity->getId()` у cookie, тому в методі `sleepIdentity()` ми замінимо оригінальну особистість на проксі з `authtoken` в ID, а в методі `wakeupIdentity()`, навпаки, відновимо всю особистість із бази даних за auttoken: +У базі даних створимо стовпець `authtoken`, в якому кожен користувач матиме [абсолютно випадковий, унікальний і невгадуваний|utils:random] рядок достатньої довжини (принаймні 13 символів). Сховище `CookieStorage` передає в cookie лише значення `$identity->getId()`, тому в `sleepIdentity()` ми замінимо оригінальну ідентичність на замінну з `authtoken` в ID, а навпаки, в методі `wakeupIdentity()` за authtoken'ом зчитаємо повну ідентичність з бази даних: ```php final class Authenticator implements @@ -212,21 +211,21 @@ final class Authenticator implements public function authenticate(string $username, string $password): SimpleIdentity { $row = $this->db->fetch('SELECT * FROM user WHERE username = ?', $username); - // перевірка пароля + // перевіримо пароль ... - // повертаємо ідентифікатор з усіма даними з бази даних + // повернемо ідентичність з усіма даними з бази даних return new SimpleIdentity($row->id, null, (array) $row); } public function sleepIdentity(IIdentity $identity): SimpleIdentity { - // ми повертаємо ідентифікатор проксі, де як ідентифікатор виступає authtoken + // повернемо замінну ідентичність, де в ID буде authtoken return new SimpleIdentity($identity->authtoken); } public function wakeupIdentity(IIdentity $identity): ?SimpleIdentity { - // замінити ідентифікатор проксі на повний ідентифікатор, як в authenticate() + // замінну ідентичність замінимо повною ідентичністю, як в authenticate() $row = $this->db->fetch('SELECT * FROM user WHERE authtoken = ?', $identity->getId()); return $row ? new SimpleIdentity($row->id, null, (array) $row) @@ -236,16 +235,16 @@ final class Authenticator implements ``` -Множинна незалежна аутентифікація .[#toc-multiple-independent-authentications] -============================================================================== +Кілька незалежних входів +======================== -Можна мати кілька незалежних зареєстрованих користувачів у межах одного сайту та однієї сесії одночасно. Наприклад, якщо ми хочемо мати окрему автентифікацію для frontend і backend, ми просто встановимо унікальний простір імен сесії для кожного з них: +Одночасно в рамках одного веб-сайту та однієї сесії може бути кілька незалежних користувачів, що входять у систему. Якщо, наприклад, ми хочемо мати на веб-сайті окрему автентифікацію для адміністрації та публічної частини, достатньо кожній з них встановити власну назву: ```php $user->getStorage()->setNamespace('backend'); ``` -Необхідно пам'ятати, що він має бути заданий у всіх місцях, що належать одному сегменту. У разі використання презентаторів ми встановимо простір імен у спільному предку - зазвичай BasePresenter. Для цього ми розширимо метод [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()]: +Важливо пам'ятати, щоб ми завжди встановлювали простір імен у всіх місцях, що належать до відповідної частини. Якщо ми використовуємо presenter'и, ми встановимо простір імен у спільному предку для даної частини - зазвичай BasePresenter. Зробимо це, розширивши метод [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()]: ```php public function checkRequirements($element): void @@ -256,10 +255,10 @@ public function checkRequirements($element): void ``` -Множинні аутентифікатори .[#toc-multiple-authenticators] --------------------------------------------------------- +Кілька автентифікаторів +----------------------- -Поділ додатка на сегменти з незалежною автентифікацією зазвичай вимагає використання різних автентифікаторів. Однак реєстрація двох класів, що реалізують Authenticator, у конфігураційних службах призведе до помилки, оскільки Nette не знатиме, який із них має бути [автопідключений |dependency-injection:autowiring] до об'єкта `Nette\Security\User`. Ось чому ми повинні обмежити автопідключення для них за допомогою `autowired: self` так, щоб воно активувалося тільки при конкретному запиті їхнього класу: +Розділення застосунку на частини з незалежним входом зазвичай вимагає також різних автентифікаторів. Однак, якби ми зареєстрували в конфігурації сервісів два класи, що реалізують Authenticator, Nette не знало б, який з них автоматично призначити об'єкту `Nette\Security\User`, і відобразило б помилку. Тому ми повинні для автентифікаторів [autowiring |dependency-injection:autowiring] обмежити так, щоб він працював, лише коли хтось запитує конкретний клас, наприклад, FrontAuthenticator, чого досягнемо вибором `autowired: self`: ```neon services: @@ -278,7 +277,7 @@ class SignPresenter extends Nette\Application\UI\Presenter } ``` -Нам потрібно встановити наш аутентифікатор на об'єкт User тільки перед викликом методу [login() |api:Nette\Security\User::login()], що зазвичай означає у зворотному виклику форми входу: +Автентифікатор об'єкта User встановлюємо перед викликом методу [login() |api:Nette\Security\User::login()], тобто зазвичай у коді форми, яка його залогінює: ```php $form->onSuccess[] = function (Form $form, \stdClass $data) { diff --git a/security/uk/authorization.texy b/security/uk/authorization.texy index a55c8789c2..bb4c20b0f3 100644 --- a/security/uk/authorization.texy +++ b/security/uk/authorization.texy @@ -1,48 +1,48 @@ -Контроль доступу (авторизація) -****************************** +Перевірка прав доступу (Авторизація) +************************************ .[perex] -Авторизація визначає, чи володіє користувач достатніми привілеями, наприклад, для доступу до певного ресурсу або виконання будь-якої дії. Авторизація передбачає успішну аутентифікацію, тобто що користувач увійшов у систему. +Авторизація визначає, чи має користувач достатні права доступу, наприклад, для доступу до певного ресурсу або для виконання певної дії. Авторизація передбачає попередню успішну автентифікацію, тобто що користувач залогінений. -→ Встановлення [та вимоги |@home#Installation] +→ [Встановлення та вимоги |@home#Встановлення] -У прикладах ми будемо використовувати об'єкт класу [api:Nette\Security\User], який представляє поточного користувача і який ви отримуєте, передаючи його за допомогою [ін'єкції залежностей |dependency-injection:passing-dependencies]. У презентаторах просто викликайте `$user = $this->getUser()`. +У прикладах ми будемо використовувати об'єкт класу [api:Nette\Security\User], який представляє поточного користувача і до якого ви можете отримати доступ, попросивши його передати за допомогою [dependency injection |dependency-injection:passing-dependencies]. У presenter'ах достатньо лише викликати `$user = $this->getUser()`. -Для дуже простих сайтів з адмініструванням, де права користувачів не різняться, можна використовувати як критерій авторизації вже відомий метод `isLoggedIn()`. Інакше кажучи: щойно користувач увійшов у систему, він має права на всі дії і навпаки. +Для дуже простих веб-сайтів з адміністрацією, де не розрізняються права доступу користувачів, можна як критерій авторизації використовувати вже відомий метод `isLoggedIn()`. Іншими словами: як тільки користувач залогінений, він має всі права доступу, і навпаки. ```php if ($user->isLoggedIn()) { // чи користувач залогінений? - deleteItem(); // якщо так, то він може видалити елемент + deleteItem(); // тоді він має право на операцію } ``` -Ролі .[#toc-roles] ------------------- +Ролі +---- -Мета ролей - запропонувати більш точне управління правами і залишатися незалежними від імені користувача. Щойно користувач входить у систему, йому призначається одна або кілька ролей. Самі ролі можуть бути простими рядками, наприклад, `admin`, `member`, `guest` тощо. Вони вказуються в другому аргументі конструктора `SimpleIdentity`, або як рядок, або як масив. +Сенс ролей полягає в тому, щоб запропонувати точніше керування правами доступу та залишатися незалежним від імені користувача. Кожному користувачеві одразу при вході присвоюється одна або кілька ролей, у яких він буде виступати. Ролі можуть бути простими рядками, наприклад `admin`, `member`, `guest` тощо. Вони вказуються як другий параметр конструктора `SimpleIdentity`, або як рядок, або як масив рядків - ролей. -Як критерій авторизації ми будемо використовувати метод `isInRole()`, який перевіряє, чи входить користувач у задану роль: +Як критерій авторизації тепер використаємо метод `isInRole()`, який повідомляє, чи виступає користувач у даній ролі: ```php -if ($user->isInRole('admin')) { // чи призначена користувачу роль адміністратора? - deleteItem(); // якщо так, то він може видалити елемент +if ($user->isInRole('admin')) { // чи користувач у ролі адміністратора? + deleteItem(); // тоді він має право на операцію } ``` -Як ви вже знаєте, вихід користувача із системи не стирає його особистість. Таким чином, метод `getIdentity()`, як і раніше, повертає об'єкт `SimpleIdentity`, включаючи всі надані ролі. Nette Framework дотримується принципу "менше коду, більше безпеки", тому під час перевірки ролей не потрібно перевіряти, чи увійшов користувач у систему. Метод `isInRole()` працює з **ефективними ролями**, тобто якщо користувач увійшов у систему, то використовуються ролі, призначені особистості, якщо він не увійшов, то замість них використовується автоматична спеціальна роль `guest`. +Як ви вже знаєте, після виходу користувача його ідентичність може не видалятися. Тобто метод `getIdentity()` і надалі повертає об'єкт `SimpleIdentity`, включно з усіма наданими ролями. Nette Framework дотримується принципу „less code, more security“, коли менше писанини призводить до більш безпечного коду, тому при перевірці ролей вам не потрібно додатково перевіряти, чи користувач залогінений. Метод `isInRole()` працює з **ефективними ролями,** тобто якщо користувач залогінений, він базується на ролях, зазначених в ідентичності, якщо не залогінений, він автоматично має спеціальну роль `guest`. -Авторизатор .[#toc-authorizator] --------------------------------- +Авторизатор +----------- -На додаток до ролей ми введемо терміни ресурс і операція: +Крім ролей, ми введемо ще поняття ресурсу та операції: -- **роль** - це атрибут користувача - наприклад, модератор, редактор, відвідувач, зареєстрований користувач, адміністратор, ... -- **ресурс** - це логічна одиниця додатка - стаття, сторінка, користувач, пункт меню, опитування, ведучий, ... -- **операція** - це конкретна дія, яку користувач може або не може виконувати з *ресурсом* - перегляд, редагування, видалення, голосування, ... +- **роль** — це властивість користувача - напр. модератор, редактор, відвідувач, зареєстрований користувач, адміністратор... +- **ресурс** (*resource*) — це якийсь логічний елемент веб-сайту - стаття, сторінка, користувач, елемент меню, опитування, presenter, ... +- **операція** (*operation*) — це якась конкретна діяльність, яку користувач може або не може робити з ресурсом - наприклад, видалити, редагувати, створити, голосувати, ... -Авторизатор - це об'єкт, який вирішує, чи має дана *роль* дозвіл на виконання певної *операції* з певним *ресурсом*. Це об'єкт, що реалізує інтерфейс [api:Nette\Security\Authorizator] з одним методом `isAllowed()`: +Авторизатор — це об'єкт, який вирішує, чи має дана *роль* дозвіл виконати певну *операцію* з певним *ресурсом*. Це об'єкт, що реалізує інтерфейс [api:Nette\Security\Authorizator] з єдиним методом `isAllowed()`: ```php class MyAuthorizator implements Nette\Security\Authorizator @@ -63,50 +63,50 @@ class MyAuthorizator implements Nette\Security\Authorizator } ``` -Ми додаємо авторизатор у конфігурацію [як сервіс |dependency-injection:services] контейнера DI: +Авторизатор додамо до конфігурації [як сервіс|dependency-injection:services] DI-контейнера: ```neon services: - MyAuthorizator ``` -І нижче наведено приклад використання. Зверніть увагу, що цього разу ми викликаємо метод `Nette\Security\User::isAllowed()`, а не метод авторизатора, тому немає першого параметра `$role`. Цей метод викликає `MyAuthorizator::isAllowed()` послідовно для всіх ролей користувачів і повертає true, якщо хоча б один із них має дозвіл. +А далі приклад використання. Увага, цього разу ми викликаємо метод `Nette\Security\User::isAllowed()`, а не авторизатор, тому там немає першого параметра `$role`. Цей метод викликає `MyAuthorizator::isAllowed()` послідовно для всіх ролей користувача і повертає true, якщо хоча б одна з них має дозвіл. ```php -if ($user->isAllowed('file')) { // чи дозволено користувачу робити все з ресурсом 'file'? +if ($user->isAllowed('file')) { // чи може користувач робити будь-що з ресурсом 'file'? useFile(); } -if ($user->isAllowed('file', 'delete')) { // чи дозволено користувачу видаляти ресурс 'file'? +if ($user->isAllowed('file', 'delete')) { // чи може над ресурсом 'file' виконати 'delete'? deleteFile(); } ``` -Обидва аргументи є необов'язковими, і їхнє значення за замовчуванням означає *все*. +Обидва параметри є необов'язковими, стандартне значення `null` означає *будь-що*. -Дозвіл ACL .[#toc-permission-acl] ---------------------------------- +Permission ACL +-------------- -Nette поставляється з вбудованою реалізацією авторизатора, класом [api:Nette\Security\Permission], який пропонує легкий і гнучкий рівень ACL (Access Control List) для дозволу та контролю доступу. Коли ми працюємо з цим класом, ми визначаємо ролі, ресурси та окремі дозволи. При цьому ролі та ресурси можуть утворювати ієрархії. Щоб пояснити це, ми покажемо приклад веб-додатка: +Nette постачається з вбудованою реалізацією авторизатора, а саме класом [api:Nette\Security\Permission], що надає програмісту легкий та гнучкий шар ACL (Access Control List) для керування правами доступу та доступом. Робота з ним полягає у визначенні ролей, ресурсів та окремих прав доступу. При цьому ролі та ресурси дозволяють створювати ієрархії. Для пояснення покажемо приклад веб-застосунку: -- `guest`: відвідувач, який не ввійшов у систему, якому дозволено читати і переглядати публічну частину сайту, тобто читати статті, коментувати і голосувати в опитуваннях. -- `registered`: користувач, що увійшов у систему, який, крім цього, може залишати коментарі. -- `admin`: може керувати статтями, коментарями й опитуваннями +- `guest`: незалогінений відвідувач, який може читати та переглядати публічну частину веб-сайту, тобто читати статті, коментарі та голосувати в опитуваннях +- `registered`: залогінений зареєстрований користувач, який додатково може коментувати +- `admin`: може керувати статтями, коментарями та опитуваннями -Отже, ми визначили певні ролі (`guest`, `registered` і `admin`) і згадали ресурси (`article`, `comments`, `poll`), до яких користувачі можуть отримати доступ або вчинити дії (`view`, `vote`, `add`, `edit`). +Ми визначили певні ролі (`guest`, `registered` та `admin`) і згадали ресурси (`article`, `comment`, `poll`), до яких користувачі з певною роллю можуть отримувати доступ або виконувати певні операції (`view`, `vote`, `add`, `edit`). -Ми створюємо екземпляр класу Permission і визначаємо **ролі**. Можна використовувати успадкування ролей, що гарантує, що, наприклад, користувач із роллю `admin` може робити те, що може робити звичайний відвідувач сайту (і, звісно, більше). +Створимо екземпляр класу Permission і визначимо **ролі**. При цьому можна використовувати так зване успадкування ролей, яке забезпечить, що, наприклад, користувач з роллю адміністратора (`admin`) може робити й те, що звичайний відвідувач веб-сайту (і, звичайно, більше). ```php $acl = new Nette\Security\Permission; $acl->addRole('guest'); $acl->addRole('registered', 'guest'); // 'registered' успадковує від 'guest' -$acl->addRole('admin', 'registered'); // і 'admin' успадковує від 'registered' +$acl->addRole('admin', 'registered'); // а від нього успадковує 'admin' ``` -Тепер ми визначимо список **ресурсів**, до яких користувачі можуть отримати доступ: +Тепер визначимо список **ресурсів**, до яких користувачі можуть отримувати доступ. ```php $acl->addResource('article'); @@ -114,49 +114,49 @@ $acl->addResource('comment'); $acl->addResource('poll'); ``` -Ресурси також можуть використовувати успадкування, наприклад, ми можемо додати `$acl->addResource('perex', 'article')`. +Ресурси також можуть використовувати успадкування, можна було б, наприклад, вказати `$acl->addResource('perex', 'article')`. -А тепер найголовніше. Ми визначимо між ними **правила**, які визначають, хто що може робити: +А тепер найважливіше. Визначимо між ними правила, що визначають, хто що може робити з чим: ```php -// все заперечується тепер +// спочатку ніхто нічого не може робити -// дозволити гостю переглядати статті, коментарі та опитування +// нехай guest може переглядати статті, коментарі та опитування $acl->allow('guest', ['article', 'comment', 'poll'], 'view'); -// а також голосувати в опитуваннях +// а в опитуваннях додатково й голосувати $acl->allow('guest', 'poll', 'vote'); -// зареєстрований успадковує права від guesta, йому також дозволимо коментувати +// зареєстрований успадковує права від guest, дамо йому додатково право коментувати $acl->allow('registered', 'comment', 'add'); // адміністратор може переглядати та редагувати будь-що $acl->allow('admin', $acl::All, ['view', 'edit', 'add']); ``` -Що якщо ми хочемо **перешкодити** комусь отримати доступ до ресурсу? +Що, якщо ми хочемо комусь **заборонити** доступ до певного ресурсу? ```php -// адміністратор не може редагувати опитування, це було б недемократично. +// адміністратор не може редагувати опитування, це було б недемократично $acl->deny('admin', 'poll', 'edit'); ``` -Тепер, коли ми створили набір правил, ми можемо просто задавати запити на авторизацію: +Тепер, коли ми створили список правил, ми можемо просто ставити авторизаційні запити: ```php -// чи може гість переглядати статті? +// чи може guest переглядати статті? $acl->isAllowed('guest', 'article', 'view'); // true -// чи може гість редагувати статтю? +// чи може guest редагувати статті? $acl->isAllowed('guest', 'article', 'edit'); // false -// чи може гість голосувати в опитуваннях? +// чи може guest голосувати в опитуваннях? $acl->isAllowed('guest', 'poll', 'vote'); // true -// чи може гість додавати коментарі? +// чи може guest коментувати? $acl->isAllowed('guest', 'comment', 'add'); // false ``` -Те ж саме стосується і зареєстрованого користувача, але він також може коментувати: +Те саме стосується зареєстрованого користувача, однак він може й коментувати: ```php $acl->isAllowed('registered', 'article', 'view'); // true @@ -172,7 +172,7 @@ $acl->isAllowed('admin', 'poll', 'edit'); // false $acl->isAllowed('admin', 'comment', 'edit'); // true ``` -Дозволи також можуть оцінюватися динамічно, і ми можемо залишити рішення за нашим власним зворотним викликом, якому передаються всі параметри: +Права доступу можуть також оцінюватися динамічно, і ми можемо залишити рішення на власний callback, якому передаються всі параметри: ```php $assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { @@ -182,7 +182,7 @@ $assertion = function (Permission $acl, string $role, string $resource, string $ $acl->allow('registered', 'comment', null, $assertion); ``` -Але як вирішити ситуацію, коли імен ролей і ресурсів недостатньо, тобто ми хочемо визначити, що, наприклад, роль `registered` може редагувати ресурс `article` тільки якщо вона є його автором? Ми будемо використовувати об'єкти замість рядків, роль буде об'єктом [api:Nette\Security\Role] і джерелом [api:Nette\Security\Resource]. Їхні методи `getRoleId()` і `getResourceId()` повертатимуть вихідні рядки: +Але як, наприклад, вирішити ситуацію, коли недостатньо лише назв ролей та ресурсів, але ми хотіли б визначити, що, наприклад, роль `registered` може редагувати ресурс `article` лише якщо є його автором? Замість рядків використаємо об'єкти, роль буде об'єктом [api:Nette\Security\Role], а ресурс — [api:Nette\Security\Resource]. Їхні методи `getRoleId()` відповідно `getResourceId()` повертатимуть початкові рядки: ```php class Registered implements Nette\Security\Role @@ -207,7 +207,7 @@ class Article implements Nette\Security\Resource } ``` -А тепер давайте створимо правило: +А тепер створимо правило: ```php $assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { @@ -219,7 +219,7 @@ $assertion = function (Permission $acl, string $role, string $resource, string $ $acl->allow('registered', 'article', 'edit', $assertion); ``` -ACL запитується шляхом передачі об'єктів: +А запит до ACL виконується передачею об'єктів: ```php $user = new Registered(/* ... */); @@ -227,7 +227,7 @@ $article = new Article(/* ... */); $acl->isAllowed($user, $article, 'edit'); ``` -Роль може успадковуватися від однієї або кількох інших ролей. Але що станеться, якщо в одного предка певна дія дозволена, а в іншого - заборонена? Тоді в гру вступає *вага ролі* - остання роль у масиві успадкованих ролей має найбільшу вагу, перша - найменшу: +Роль може успадковувати від іншої ролі або від кількох ролей. Але що станеться, якщо один предок має заборонену дію, а інший — дозволену? Які будуть права нащадка? Це визначається за вагою ролі — остання зазначена роль у списку предків має найбільшу вагу, перша зазначена роль — найменшу. Більш наочно це видно з прикладу: ```php $acl = new Nette\Security\Permission; @@ -239,22 +239,22 @@ $acl->addResource('backend'); $acl->allow('admin', 'backend'); $acl->deny('guest', 'backend'); -// приклад A: роль admin має меншу вагу, ніж роль guest +// випадок A: роль admin має меншу вагу, ніж роль guest $acl->addRole('john', ['admin', 'guest']); $acl->isAllowed('john', 'backend'); // false -// приклад B: роль admin має більшу вагу, ніж роль guest +// випадок B: роль admin має більшу вагу, ніж guest $acl->addRole('mary', ['guest', 'admin']); $acl->isAllowed('mary', 'backend'); // true ``` -Ролі та ресурси також можуть бути видалені (`removeRole()`, `removeResource()`), правила також можуть бути скасовані (`removeAllow()`, `removeDeny()`). Масив усіх ролей прямих батьків повертає `getRoleParents()`. Чи успадковуються дві сутності одна від одної, повертається `roleInheritsFrom()` і `resourceInheritsFrom()`. +Ролі та ресурси можна також видаляти (`removeRole()`, `removeResource()`), можна скасовувати й правила (`removeAllow()`, `removeDeny()`). Масив усіх прямих батьківських ролей повертає `getRoleParents()`, чи успадковують дві сутності одна від одної, повертає `roleInheritsFrom()` та `resourceInheritsFrom()`. -Додати як службу .[#toc-add-as-a-service] ------------------------------------------ +Додавання як сервіси +-------------------- -Нам потрібно додати створений нами ACL у конфігурацію як сервіс, щоб його міг використовувати об'єкт `$user`, тобто щоб ми могли використовувати в коді, наприклад, `$user->isAllowed('article', 'view')`. Для цього ми напишемо для нього фабрику: +Створений нами ACL потрібно передати до конфігурації як сервіс, щоб його почав використовувати об'єкт `$user`, тобто щоб можна було використовувати в коді, наприклад, `$user->isAllowed('article', 'view')`. Для цього напишемо для нього фабрику: ```php namespace App\Model; @@ -272,14 +272,14 @@ class AuthorizatorFactory } ``` -І додамо її в конфігурацію: +І додамо її до конфігурації: ```neon services: - App\Model\AuthorizatorFactory::create ``` -У провідних ви можете потім перевірити дозволи в методі `startup()`, наприклад: +У presenter'ах потім можна перевіряти права доступу, наприклад, у методі `startup()`: ```php protected function startup() diff --git a/security/uk/configuration.texy b/security/uk/configuration.texy index f10072bba2..708f38ed92 100644 --- a/security/uk/configuration.texy +++ b/security/uk/configuration.texy @@ -1,71 +1,85 @@ -Налаштування контролю доступу -***************************** +Конфігурація прав доступу +************************* .[perex] -Огляд опцій конфігурації для Nette Security. +Огляд конфігураційних опцій для Nette Security. -Якщо ви використовуєте не весь фреймворк, а тільки цю бібліотеку, прочитайте, [як завантажити конфігурацію |bootstrap:]. +Якщо ви не використовуєте весь фреймворк, а лише цю бібліотеку, прочитайте, [як завантажити конфігурацію|bootstrap:]. -Ви можете визначити список користувачів у конфігурації для створення [простого аутентифікатора |authentication] (`Nette\Security\SimpleAuthenticator`). Оскільки паролі в конфігурації можна прочитати, це рішення призначене тільки для тестування. +У конфігурації можна визначити список користувачів і таким чином створити [простий автентифікатор|authentication] (`Nette\Security\SimpleAuthenticator`). Оскільки в конфігурації паролі вказуються в читабельній формі, це рішення підходить лише для тестових цілей. ```neon security: - # показує призначену для користувача панель у панелі трейсі? - debugger: ... # (bool) за замовчуванням true + # відображати панель користувача в Tracy Bar? + debugger: ... # (bool) стандартно true users: # ім'я: пароль - johndoe: secret123 + frantisek: tajneheslo - # ім'я, пароль, роль та інші дані, доступні в ідентифікаторі - janedoe: - password: secret123 + # ім'я, пароль, роль та інші дані, доступні в ідентичності + dobrota: + password: tajneheslo roles: [admin] data: ... ``` -Ви також можете визначити ролі та ресурси, щоб створити основу для [авторизатора |authorization] (`Nette\Security\Permission`): +Далі можна визначити ролі та ресурси і таким чином створити основу для [авторизатора|authorization] (`Nette\Security\Permission`): ```neon security: roles: guest: - registered: [guest] # registered успадковує від guest - admin: [registered] # і admin успадковує від registered + registered: [guest] # registered успадковує від guest + admin: [registered] # а від нього успадковує admin resources: article: - comment: [article] # ресурс успадковується від article + comment: [article] # ресурс успадковує від article poll: ``` -Сховище користувачів .[#toc-user-storage] ------------------------------------------ +Сховище +------- -Ви можете налаштувати спосіб зберігання інформації про користувача, який увійшов у систему: +Можна налаштувати, як зберігати інформацію про залогіненого користувача: ```neon security: authentication: - # після якого часу бездіяльності користувач буде виведений із системи - expiration: 30 minutes # (рядок) значення за замовчуванням не задано + # через який час неактивності користувач буде вилогінений + expiration: 30 minutes # (string) стандартно не встановлено - # де зберігати інформацію про користувача, який увійшов у систему - storage: session # (session|cookie) за замовчуванням session + # де зберігати інформацію про залогіненого користувача + storage: session # (session|cookie) стандартно session ``` -Якщо ви вибрали `cookie` як сховище, ви також можете встановити такі параметри: +Якщо ви оберете як сховище `cookie`, можете налаштувати ще ці опції: ```neon security: authentication: - # печиво jméno - cookieName: userId # (string) за замовчуванням - userid + # ім'я cookie + cookieName: userId # (string) стандартно userid - # яким хостам дозволено отримувати cookie + # домени, які приймають cookie cookieDomain: 'example.com' # (string|domain) - # обмеження при доступі до крос-оригінального запиту - cookieSamesite: None # (Strict|Lax|None) за замовчуванням Lax + # обмеження при доступі з іншого домену + cookieSamesite: None # (Strict|Lax|None) стандартно Lax ``` + + +Сервіси DI +---------- + +Ці сервіси додаються до DI-контейнера: + +| Назва | Тип | Опис +|------------------------------------------------------------------------------------------ +| `security.authenticator` | [api:Nette\Security\Authenticator] | [автентифікатор|authentication] +| `security.authorizator` | [api:Nette\Security\Authorizator] | [авторизатор|authorization] +| `security.passwords` | [api:Nette\Security\Passwords] | [хешування паролів|passwords] +| `security.user` | [api:Nette\Security\User] | поточний користувач +| `security.userStorage` | [api:Nette\Security\UserStorage] | [#сховище] diff --git a/security/uk/passwords.texy b/security/uk/passwords.texy index 28e78237ac..ff391255cb 100644 --- a/security/uk/passwords.texy +++ b/security/uk/passwords.texy @@ -2,11 +2,11 @@ ***************** .[perex] -Для забезпечення безпеки наших користувачів ми ніколи не зберігаємо їхні паролі у відкритому вигляді, натомість ми зберігаємо хеш пароля. Хешування не є оборотною операцією, пароль не може бути відновлений. Однак пароль можна зламати, і щоб зробити злом якомога важчим, ми повинні використовувати безпечний алгоритм. Клас [api:Nette\Security\Passwords] допоможе нам у цьому. +Щоб забезпечити безпеку наших користувачів, ми не зберігаємо їхні паролі в читабельній формі, а зберігаємо лише відбиток (так званий хеш). З відбитка неможливо відновити початкову форму пароля. Важливо використовувати безпечний алгоритм для створення відбитка. З цим нам допоможе клас [api:Nette\Security\Passwords]. -→ Встановлення [та вимоги |@home#Installation] +→ [Встановлення та вимоги |@home#Встановлення] -Фреймворк автоматично додає сервіс `Nette\Security\Passwords` у контейнер DI під іменем `security.passwords`, який ви отримуєте, передаючи його за допомогою [ін'єкції залежностей |dependency-injection:passing-dependencies]: +Фреймворк автоматично додає до DI-контейнера сервіс типу `Nette\Security\Passwords` під назвою `security.passwords`, до якого ви можете отримати доступ, попросивши його передати за допомогою [dependency injection |dependency-injection:passing-dependencies]. ```php use Nette\Security\Passwords; @@ -24,26 +24,26 @@ class Foo __construct($algo=PASSWORD_DEFAULT, array $options=[]): string .[method] ======================================================================== -Вибирає, який [безпечний алгоритм |https://www.php.net/manual/en/password.constants.php] використовується для хешування і як його налаштувати. +Вибираємо, який [безпечний алгоритм|https://www.php.net/manual/en/password.constants.php] для генерації хешу використовувати та конфігуруємо його параметри. -За замовчуванням використовується `PASSWORD_DEFAULT`, тому вибір алгоритму залишається за PHP. Алгоритм може змінитися в нових релізах PHP, коли підтримуватимуться новіші та сильніші алгоритми хешування. Тому ви повинні знати, що довжина результуючого хешу може змінитися. Тому ви повинні зберігати результуючий хеш таким чином, щоб він міг зберігати достатню кількість символів, рекомендована ширина 255. +За замовчуванням використовується `PASSWORD_DEFAULT`, тобто вибір алгоритму залишається на розсуд PHP. Алгоритм може змінитися в новіших версіях PHP, якщо з'являться новіші, сильніші алгоритми хешування. Тому ви повинні бути свідомі того, що довжина отриманого хешу може змінитися, і ви повинні зберігати його способом, який може вмістити достатньо символів, 255 є рекомендованою шириною. -Ось як можна використовувати алгоритм bcrypt і змінити швидкість хешування за допомогою параметра cost від значення за замовчуванням 10. У 2020 році за вартості 10 хешування одного пароля займає приблизно 80 мс, за вартості 11 - 160 мс, за вартості 12 - 320 мс, шкала логарифмічна. Чим повільніше, тим краще, вартість 10-12 вважається досить повільною для більшості випадків використання: +Приклад налаштування швидкості хешування алгоритмом bcrypt зміною параметра cost: (у 2020 році стандартно 10, хешування пароля триває приблизно 80мс, для cost 11 це приблизно 160мс, для cost 12 приблизно 320мс, чим повільніше, тим кращий захист, причому швидкість 10-12 вже вважається достатнім захистом) ```php -// будемо хешувати паролі за 2^12 (2^cost) ітерацій алгоритму bcrypt +// будемо хешувати паролі 2^12 (2^cost) ітераціями алгоритму bcrypt $passwords = new Passwords(PASSWORD_BCRYPT, ['cost' => 12]); ``` -З ін'єкцією залежностей: +За допомогою dependency injection: ```neon services: security.passwords: Nette\Security\Passwords(::PASSWORD_BCRYPT, [cost: 12]) ``` -hash(string $passwords): string .[method] -========================================= +hash(string $password): string .[method] +======================================== Генерує хеш пароля. @@ -51,17 +51,17 @@ hash(string $passwords): string .[method] $res = $passwords->hash($password); // Хешує пароль ``` -Результат `$res` являє собою рядок, який, крім самого хеша, містить ідентифікатор використовуваного алгоритму, його налаштування і криптографічну сіль (випадкові дані, що гарантують, що для одного і того ж пароля буде згенеровано інший хеш). Таким чином, забезпечується зворотна сумісність, наприклад, у разі зміни параметрів можна перевірити хеші, збережені з використанням попередніх налаштувань. Весь результат зберігається в базі даних, тому немає необхідності зберігати сіль або налаштування окремо. +Результат `$res` — це рядок, який крім самого хешу містить також ідентифікатор використаного алгоритму, його налаштування та криптографічну сіль (випадкові дані, що забезпечують, що для однакового пароля генерується різний хеш). Він є зворотно сумісним, тобто коли, наприклад, ви зміните параметри, то й хеші, збережені з використанням попередніх налаштувань, можна буде перевірити. Весь цей результат зберігається в базі даних, тому не потрібно зберігати сіль або налаштування окремо. verify(string $password, string $hash): bool .[method] ====================================================== -З'ясовує, чи збігається заданий пароль із заданим хешем. Отримати `$hash` з бази даних за ім'ям користувача або адресою електронної пошти. +З'ясовує, чи відповідає даний пароль даному відбитку. `$hash` отримайте з бази даних за вказаним ім'ям користувача або адресою електронної пошти. ```php if ($passwords->verify($password, $hash)) { - // Правильний пароль + // правильний пароль } ``` @@ -69,13 +69,13 @@ if ($passwords->verify($password, $hash)) { needsRehash(string $hash): bool .[method] ========================================= -З'ясовує, чи відповідає хеш параметрам, заданим у конструкторі. +З'ясовує, чи відповідає хеш вказаним у конструкторі опціям. -Використовуйте цей метод, коли ви, наприклад, змінюєте параметри хеша. Перевірка пароля буде використовувати параметри, збережені разом із хешем, і якщо `needsRehash()` поверне true, вам доведеться знову обчислити хеш, цього разу з оновленими параметрами, і знову зберегти його в базі даних. Це гарантує, що хеші паролів будуть автоматично "оновлюватися" при вході користувачів у систему. +Це корисно використовувати в момент, коли, наприклад, ви змінюєте швидкість хешування. Перевірка відбудеться за збереженими налаштуваннями, і якщо `needsRehash()` поверне `true`, то потрібно знову створити хеш, цього разу з новими параметрами, і зберегти його знову в базі даних. Таким чином автоматично "оновлюються" збережені хеші при вході користувачів. ```php if ($passwords->needsRehash($hash)) { $hash = $passwords->hash($password); - // зберігаємо $hash в базу даних + // зберегти $hash до бази даних } ``` diff --git a/tester/bg/@home.texy b/tester/bg/@home.texy index a6f71f7a00..c79cf43f5f 100644 --- a/tester/bg/@home.texy +++ b/tester/bg/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Nette Tester - Приятно тестване на единици в PHP}} +{{maintitle: Nette Tester – лесно тестване в PHP}} {{description: Nette Tester е прост, но много удобен инструмент за тестване на PHP код.}} diff --git a/tester/bg/@left-menu.texy b/tester/bg/@left-menu.texy index b7b1fa1008..e42f375d57 100644 --- a/tester/bg/@left-menu.texy +++ b/tester/bg/@left-menu.texy @@ -1,8 +1,8 @@ -- [Започване на работа |guide] -- [Писане на тестове |Writing Tests] -- [Изпълнение на тестове |Running Tests] +- [Първи стъпки с Nette Tester |guide] +- [Писане на тестове |writing-tests] +- [Стартиране на тестове |running-tests] -- [Одобрения |Assertions] -- [Тестови анотации |Test Annotations] -- [Тестов пример |TestCase] -- [Помощници |Helpers] +- [Assertions |assertions] +- [Анотации на тестове |test-annotations] +- [TestCase |TestCase] +- [Помощни класове |helpers] diff --git a/tester/bg/@menu.texy b/tester/bg/@menu.texy index d5536f44f4..1ca109ef89 100644 --- a/tester/bg/@menu.texy +++ b/tester/bg/@menu.texy @@ -1,3 +1,3 @@ -- [Главная |@home] -- [Документация |Guide] +- [Въведение |@home] +- [Документация |guide] - "GitHub .[link-external]":https://github.com/nette/tester diff --git a/tester/bg/@meta.texy b/tester/bg/@meta.texy new file mode 100644 index 0000000000..003bbf9fc5 --- /dev/null +++ b/tester/bg/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документация на Tester}} diff --git a/tester/bg/assertions.texy b/tester/bg/assertions.texy index c4171e0e92..b464cfd317 100644 --- a/tester/bg/assertions.texy +++ b/tester/bg/assertions.texy @@ -1,35 +1,35 @@ -Искове -****** +Assertion +********* .[perex] -Твърденията се използват за потвърждаване, че действителната стойност съответства на очакваната стойност. Това са методи `Tester\Assert`. +Assertion се използват за потвърждаване, че действителната стойност съответства на очакваната стойност. Това са методи на класа `Tester\Assert`. -Изберете най-точните твърдения. По-добре `Assert::same($a, $b)`, отколкото `Assert::true($a === $b)`, защото при неуспех извежда смислено съобщение за грешка. Във втория случай получаваме само `false should be true`, а той не казва нищо за съдържанието на променливите $a и $b. +Избирайте най-подходящите assertion-и. По-добре е `Assert::same($a, $b)` отколкото `Assert::true($a === $b)`, защото при неуспех показва смислено съобщение за грешка. Във втория случай само `false should be true`, което не ни казва нищо за съдържанието на променливите `$a` и `$b`. -Повечето оператори могат да имат и незадължителен `$description`, който се появява в съобщението за грешка, ако не успеят. +Повечето assertion-и също могат да имат незадължителен етикет в параметъра `$description`, който се показва в съобщението за грешка, ако очакването се провали. -Примерите предполагат, че е дефиниран следният псевдоним на клас: +Примерите предполагат създаден псевдоним: ```php use Tester\Assert; ``` -Assert::same($expected, $actual, string $description=null) .[method] --------------------------------------------------------------------- -`$expected` трябва да бъде същото като `$actual`. Това е същото като оператора на PHP `===`. +Assert::same($expected, $actual, ?string $description=null) .[method] +--------------------------------------------------------------------- +`$expected` трябва да бъде идентичен с `$actual`. Същото като PHP оператора `===`. -Assert::notSame($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------ -Противоположен на `Assert::same()`, така че е същият като оператора на PHP `!==`. +Assert::notSame($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------ +Обратното на `Assert::same()`, тоест същото като PHP оператора `!==`. -Assert::equal($expected, $actual, string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] ------------------------------------------------------------------------------------------------------------------------- -`$expected` трябва да бъде същото като `$actual`. За разлика от `Assert::same()`, идентичността на обектите, редът на двойките ключ => стойност в масивите и леко различаващите се десетични числа се игнорират, което може да се промени чрез задаване на `$matchIdentity` и `$matchOrder`. +Assert::equal($expected, $actual, ?string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] +------------------------------------------------------------------------------------------------------------------------- +`$expected` трябва да бъде същият като `$actual`. За разлика от `Assert::same()`, се игнорира идентичността на обектите, редът на двойките ключ => стойност в масивите и маргинално различни десетични числа, което може да се промени чрез настройка на `$matchIdentity` и `$matchOrder`. -Следните случаи са идентични за `equal()`, но не и за `same()`: +Следните случаи са еднакви от гледна точка на `equal()`, но не и на `same()`: ```php Assert::equal(0.3, 0.1 + 0.2); @@ -40,81 +40,81 @@ Assert::equal( ); ``` -Предупреждаваме ви обаче, че масивът `[1, 2]` и `[2, 1]` не са еднакви, тъй като се различава само редът на стойностите, а не двойките ключ => стойност. Масивът `[1, 2]` може да се запише и като `[0 => 1, 1 => 2]` и следователно `[1 => 2, 0 => 1]` ще се считат за равни. +Обаче внимавайте, масивите `[1, 2]` и `[2, 1]` не са еднакви, защото се различават само по реда на стойностите, а не на двойките ключ => стойност. Масивът `[1, 2]` може да се запише също като `[0 => 1, 1 => 2]` и затова за еднакъв ще се счита `[1 => 2, 0 => 1]`. -Можете да използвате и т.нар. [изчакване |#Expectations] в `$expected`. +Освен това в `$expected` може да се използват т.нар. [#очаквания]. -Assert::notEqual($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------- -Контраст `Assert::equal()`. +Assert::notEqual($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------- +Обратното на `Assert::equal()`. -Assert::contains($needle, string|array $actual, string $description=null) .[method] ------------------------------------------------------------------------------------ -Ако `$actual` е низ, той трябва да съдържа подниз от `$needle`. Ако е масив, той трябва да съдържа елемент `$needle` (сравнява се стриктно). +Assert::contains($needle, string|array $actual, ?string $description=null) .[method] +------------------------------------------------------------------------------------ +Ако `$actual` е низ, трябва да съдържа подниз `$needle`. Ако е масив, трябва да съдържа елемент `$needle` (сравнява се стриктно). -Assert::notContains($needle, string|array $actual, string $description=null) .[method] --------------------------------------------------------------------------------------- +Assert::notContains($needle, string|array $actual, ?string $description=null) .[method] +--------------------------------------------------------------------------------------- Обратното на `Assert::contains()`. -Assert::hasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} -------------------------------------------------------------------------------------------------------- -`$actual` трябва да бъде масив и да съдържа ключа `$needle`. +Assert::hasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +-------------------------------------------------------------------------------------------------------- +`$actual` трябва да бъде масив и трябва да съдържа ключ `$needle`. -Assert::notHasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} ----------------------------------------------------------------------------------------------------------- -`$actual` трябва да бъде масив и да не съдържа ключа `$needle`. +Assert::notHasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +----------------------------------------------------------------------------------------------------------- +`$actual` трябва да бъде масив и не трябва да съдържа ключ `$needle`. -Assert::true($value, string $description=null) .[method] --------------------------------------------------------- -`$value` трябва да бъде `true`, така че `$value === true`. +Assert::true($value, ?string $description=null) .[method] +--------------------------------------------------------- +`$value` трябва да бъде `true`, тоест `$value === true`. -Assert::truthy($value, string $description=null) .[method] ----------------------------------------------------------- -`$value` трябва да е вярно, така че то отговаря на условието `if ($value) ...`. +Assert::truthy($value, ?string $description=null) .[method] +----------------------------------------------------------- +`$value` трябва да бъде истинен, тоест ще изпълни условието `if ($value) ...`. -Assert::false($value, string $description=null) .[method] ---------------------------------------------------------- -`$value` трябва да бъде `false`, следователно `$value === false`. +Assert::false($value, ?string $description=null) .[method] +---------------------------------------------------------- +`$value` трябва да бъде `false`, тоест `$value === false`. -Assert::falsey($value, string $description=null) .[method] ----------------------------------------------------------- -`$value` трябва да е false, така че то отговаря на условието `if (!$value) ...`. +Assert::falsey($value, ?string $description=null) .[method] +----------------------------------------------------------- +`$value` трябва да бъде неистинен, тоест ще изпълни условието `if (!$value) ...`. -Assert::null($value, string $description=null) .[method] --------------------------------------------------------- -`$value` трябва да бъде `null`, така че `$value === null`. +Assert::null($value, ?string $description=null) .[method] +--------------------------------------------------------- +`$value` трябва да бъде `null`, тоест `$value === null`. -Assert::notNull($value, string $description=null) .[method] ------------------------------------------------------------ -`$value` не следва да бъде `null`, следователно `$value !== null`. +Assert::notNull($value, ?string $description=null) .[method] +------------------------------------------------------------ +`$value` не трябва да бъде `null`, тоест `$value !== null`. -Assert::nan($value, string $description=null) .[method] -------------------------------------------------------- -`$value` трябва да бъде Не е число. Използвайте `Assert::nan()` само за тестване на NAN. Стойността на NAN е много специфична и изявленията `Assert::same()` или `Assert::equal()` могат да се държат непредсказуемо. +Assert::nan($value, ?string $description=null) .[method] +-------------------------------------------------------- +`$value` трябва да бъде Not a Number. За тестване на NAN стойност използвайте изключително `Assert::nan()`. Стойността NAN е много специфична и assertion-ите `Assert::same()` или `Assert::equal()` могат да работят неочаквано. -Assert::count($count, Countable|array $value, string $description=null) .[method] ---------------------------------------------------------------------------------- -Броят на елементите в `$value` трябва да е равен на `$count`. Това е същото като `count($value) === $count`. +Assert::count($count, Countable|array $value, ?string $description=null) .[method] +---------------------------------------------------------------------------------- +Броят на елементите в `$value` трябва да бъде `$count`. Тоест същото като `count($value) === $count`. -Assert::type(string|object $type, $value, string $description=null) .[method] ------------------------------------------------------------------------------ -`$value` трябва да е от посочения тип. Като `$type` можем да използваме символа: +Assert::type(string|object $type, $value, ?string $description=null) .[method] +------------------------------------------------------------------------------ +`$value` трябва да бъде от дадения тип. Като `$type` можем да използваме низ: - `array` -- `list` е масив, индексиран във възходящ цифров ред на ключовете от нула. +- `list` - масив, индексиран по възходяща поредица от числови ключове от нула - `bool` - `callable` - `float` @@ -124,28 +124,28 @@ Assert::type(string|object $type, $value, string $description=null) .[method] - `resource` - `scalar` - `string` -- директно име на клас или обект, тогава трябва да предадете `$value instanceof $type` +- име на клас или директно обект, тогава трябва да бъде `$value instanceof $type` -Assert::exception(callable $callable, string $class, string $message=null, $code=null) .[method] ------------------------------------------------------------------------------------------------- -При извикване на `$callable` трябва да се хвърли изключение за `$class`. Ако подадем `$message`, съобщението за изключение трябва да [съвпадне |#Assert-match]. И ако подадем `$code`, кодът на изключението трябва да е същият. +Assert::exception(callable $callable, string $class, ?string $message=null, $code=null) .[method] +------------------------------------------------------------------------------------------------- +При извикване на `$callable` трябва да бъде хвърлено изключение от клас `$class`. Ако посочим `$message`, трябва [да съответства на шаблона |#Assert::match] и съобщението на изключението, и ако посочим `$code`, трябва стриктно да съвпадат и кодовете. -Например този тест не успява, защото съобщението за изключение не съвпада: +Следният тест ще се провали, защото съобщението на изключението не съответства: ```php Assert::exception( - fn() => throw new App\InvalidValueException('Нулевое значение'), + fn() => throw new App\InvalidValueException('Zero value'), App\InvalidValueException::class, - 'Значение слишком мало', + 'Value is to low', ); ``` -`Assert::exception()` връща хвърлено изключение, за да можете да проверите вложеното изключение. +`Assert::exception()` връща хвърленото изключение, така че може да се тества и вложено изключение. ```php $e = Assert::exception( - fn() => throw new MyException('Что-то не так', 0, new RuntimeException), + fn() => throw new MyException('Something is wrong', 0, new RuntimeException), MyException::class, 'Something is wrong', ); @@ -154,9 +154,9 @@ Assert::type(RuntimeException::class, $e->getPrevious()); ``` -Assert::error(string $callable, int|string|array $type, string $message=null) .[method] ---------------------------------------------------------------------------------------- -Проверява дали извикването на `$callable` генерира очакваните грешки (т.е. предупреждения, известия и т.н.). Като `$type` посочваме една от константите `E_...`, например `E_WARNING`. А ако подадем `$message`, съобщението за грешка също трябва да [съответства на |#Assert-match] шаблона. Например: +Assert::error(string $callable, int|string|array $type, ?string $message=null) .[method] +---------------------------------------------------------------------------------------- +Проверява дали функцията `$callable` е генерирала очакваните грешки (т.е. предупреждения, известия и т.н.). Като `$type` посочваме една от константите `E_...`, тоест например `E_WARNING`. И ако посочим `$message`, трябва [да съответства на шаблона |#Assert::match] и съобщението за грешка. Например: ```php Assert::error( @@ -166,7 +166,7 @@ Assert::error( ); ``` -Ако обратното извикване генерира повече грешки, трябва да ги очакваме в точен ред. В този случай предаваме масива на `$type`: +Ако callback-ът генерира повече грешки, трябва да ги очакваме всички в точен ред. В такъв случай предаваме в `$type` масив: ```php Assert::error(function () { @@ -179,108 +179,108 @@ Assert::error(function () { ``` .[note] -Ако `$type` е име на клас, това изявление се държи по същия начин като `Assert::exception()`. +Ако като `$type` посочите име на клас, се държи по същия начин като `Assert::exception()`. Assert::noError(callable $callable) .[method] --------------------------------------------- -Проверява дали `$callable` не изхвърля никакви предупреждения/бележки/грешки или изключения на PHP. Това е полезно за проверка на части от кода, в които няма други оператори. +Проверява дали функцията `$callable` не е генерирала никакво предупреждение, грешка или изключение. Полезно е за тестване на части от код, където няма друг assertion. -Assert::match(string $pattern, $actual, string $description=null) .[method] ---------------------------------------------------------------------------- -`$actual` трябва да съвпада с `$pattern`. Можем да използваме две опции за шаблони: регулярни изрази или заместващи символи. +Assert::match(string $pattern, $actual, ?string $description=null) .[method] +---------------------------------------------------------------------------- +`$actual` трябва да отговаря на шаблона `$pattern`. Можем да използваме два варианта на шаблони: регулярни изрази или заместващи знаци. -Ако подадем регулярен израз като `$pattern`, трябва да използваме `~` or `#`, за да го отделим. Други разделители не се поддържат. Например тест, в който `$var` трябва да съдържа само шестнадесетични цифри: +Ако като `$pattern` предадем регулярен израз, за неговото ограничаване трябва да използваме `~` или `#`, други разделители не се поддържат. Например тест, при който `$var` трябва да съдържа само шестнадесетични цифри: ```php Assert::match('#^[0-9a-f]$#i', $var); ``` -Друга възможност е подобна на сравнението на низове, но можем да използваме някои заместващи символи в `$pattern`: - -- `%a%` един или повече символи, с изключение на символите за край на реда -- `%a?%` нула или повече символи, с изключение на символите за край на реда -- `%A%` един или повече от всички символи, включително символите за край на реда -- `%A?%` нула или повече символа, включително символите за край на реда -- `%s%` един или повече интервали, с изключение на символите за край на реда -- `%s?%` нула или повече интервали, с изключение на символите за край на реда -- `%S%` един или повече знаци, с изключение на интервал -- `%S?%` нула или повече знаци, с изключение на интервал -- `%c%` една или повече цифри от всякакъв вид (с изключение на края на реда) +Вторият вариант е подобен на обикновеното сравнение на низове, но в `$pattern` можем да използваме различни заместващи знаци: + +- `%a%` един или повече знаци, освен знаците за край на ред +- `%a?%` нула или повече знаци, освен знаците за край на ред +- `%A%` един или повече знаци, включително знаците за край на ред +- `%A?%` нула или повече знаци, включително знаците за край на ред +- `%s%` един или повече интервали, освен знаците за край на ред +- `%s?%` нула или повече интервали, освен знаците за край на ред +- `%S%` един или повече знаци, освен интервали +- `%S?%` нула или повече знаци, освен интервали +- `%c%` всеки един знак, освен знака за край на ред - `%d%` една или повече цифри - `%d?%` нула или повече цифри -- `%i%` подписана стойност в цяло число +- `%i%` цяло число със знак - `%f%` число с плаваща запетая -- `%h%` една или повече цифри HEX +- `%h%` една или повече шестнадесетични цифри - `%w%` един или повече буквено-цифрови знаци -- `%%` един символ % +- `%%` знак % Примери: ```php -# Again, hexadecimal number test +# Отново тест за шестнадесетично число Assert::match('%h%', $var); -# Generalized path to file and line number +# Обобщение на пътя до файла и номера на реда Assert::match('Error in file %a% on line %i%', $errorMessage); ``` -Assert::matchFile(string $file, $actual, string $description=null) .[method] ----------------------------------------------------------------------------- -Изпълнението е идентично с това на [Assert::match() |#Assert-match], но шаблонът е зареден от `$file`. Това е полезно за тестване на много дълги низове. Тестовият файл става четим. +Assert::matchFile(string $file, $actual, ?string $description=null) .[method] +----------------------------------------------------------------------------- +Assertion-ът е идентичен с [#Assert::match()], но шаблонът се зарежда от файла `$file`. Това е полезно за тестване на много дълги низове. Файлът с теста остава прегледен. Assert::fail(string $message, $actual=null, $expected=null) .[method] --------------------------------------------------------------------- -Това твърдение винаги е неуспешно. Това е просто удобно. Ако е необходимо, можем да предадем очаквани и действителни стойности. +Този assertion винаги се проваля. Понякога това просто е полезно. По желание можем да посочим и очакваната и актуалната стойност. -Очаквания .[#toc-expectations] ------------------------------- -Ако искаме да сравним по-сложни структури с непостоянни елементи, горните твърдения може да не са достатъчни. Например, тестваме метод, който създава нов потребител и връща атрибутите му като масив. Не знаем хеш стойността на паролата, но знаем, че тя трябва да е шестнадесетичен низ. А всичко, което знаем за следващия елемент, е, че той трябва да бъде обектът `DateTime`. +Очаквания +--------- +Когато искаме да сравним по-сложни структури с неконстантни елементи, горните assertion-и може да не са достатъчни. Например тестваме метод, който създава нов потребител и връща неговите атрибути като масив. Стойността на хеша на паролата не я знаем, но знаем, че трябва да бъде шестнадесетичен низ. А за друг елемент знаем само, че трябва да бъде обект `DateTime`. -В тези случаи можем да използваме `Tester\Expect` вътре в параметъра `$expected` методи `Assert::equal()` и `Assert::notEqual()`, с които лесно можем да опишем структурата. +В тези ситуации можем да използваме `Tester\Expect` вътре в `$expected` параметъра на методите `Assert::equal()` и `Assert::notEqual()`, с помощта на които можем лесно да опишем структурата. ```php use Tester\Expect; Assert::equal([ - 'id' => Expect::type('int'), # we expect an integer + 'id' => Expect::type('int'), # очакваме цяло число 'username' => 'milo', - 'password' => Expect::match('%h%'), # we expect a string matching pattern - 'created_at' => Expect::type(DateTime::class), # we expect an instance of the class + 'password' => Expect::match('%h%'), # очакваме низ, отговарящ на шаблона + 'created_at' => Expect::type(DateTime::class), # очакваме екземпляр на класа ], User::create(123, 'milo', 'RandomPaSsWoRd')); ``` -С `Expect` можем да направим почти същите твърдения като с `Assert`. Така че имаме методи като `Expect::same()`, `Expect::match()`, `Expect::count()` и т.н. Освен това можем да ги обединим по следния начин: +С `Expect` можем да извършваме почти същите assertion-и като с `Assert`. Тоест, на разположение са ни методите `Expect::same()`, `Expect::match()`, `Expect::count()` и т.н. Освен това можем да ги верижим: ```php -Expect::type(MyIterator::class)->andCount(5); # we expect MyIterator and items count is 5 +Expect::type(MyIterator::class)->andCount(5); # очакваме MyIterator и брой елементи 5 ``` -Или можем да напишем свои собствени обработчици на изявления. +Или можем да пишем собствени хендлъри за assertion-и. ```php Expect::that(function ($value) { - # return false if expectation fails + # връщаме false, ако очакването се провали }); ``` -Разследване на неуспешни твърдения .[#toc-failed-assertions-investigation] --------------------------------------------------------------------------- -Tester показва къде се намира грешката, когато дадено твърдение не успее. Когато сравняваме сложни структури, Tester създава дъмпове на сравняваните стойности и ги записва в директорията `output`. Например, когато въображаемият тест `Arrays.recursive.phpt` се провали, изхвърлянията ще бъдат записани по следния начин: +Изследване на грешни assertion-и +-------------------------------- +Когато assertion се провали, Tester изписва в какво е грешката. Ако сравняваме по-сложни структури, Tester създава дъмп на сравняваните стойности и ги съхранява в директорията `output`. Например при провал на измисления тест `Arrays.recursive.phpt` дъмп-овете ще бъдат съхранени по следния начин: ``` app/ └── tests/ ├── output/ - │ ├──── Arrays.recursive.actual # фактическое значение - │ └──── Arrays.recursive.expected # ожидаемое значение + │ ├── Arrays.recursive.actual # актуална стойност + │ └── Arrays.recursive.expected # очаквана стойност │ - └── Arrays.recursive.phpt # неудачный тест + └── Arrays.recursive.phpt # провалящ се тест ``` -Можем да променим името на директорията на `Tester\Dumper::$dumpDir`. +Името на директорията можем да променим чрез `Tester\Dumper::$dumpDir`. diff --git a/tester/bg/guide.texy b/tester/bg/guide.texy index b5a0d2a4d7..ce415b7d13 100644 --- a/tester/bg/guide.texy +++ b/tester/bg/guide.texy @@ -1,17 +1,17 @@ -Започване на работа с Tester -**************************** +Започваме с Nette Tester +************************ <div class=perex> -Дори добрите програмисти допускат грешки. Разликата между добрия и лошия програмист е, че добрият програмист ще го направи само веднъж и ще го открие следващия път с автоматизирани тестове. +И добрите програмисти правят грешки. Разликата между добрия и лошия програмист е в това, че добрият я прави само веднъж и следващия път я открива с помощта на автоматизирани тестове. -- "Който не изпитва, е обречен да повтаря собствените си грешки". (мъдра поговорка). -- "Когато се отървем от една грешка, се появява друга." (Законът на Мърфи) -- "Винаги, когато се изкушавате да напишете изявление, напишете го като тест." (Martin Fowler) +- "Който не тества, е осъден да повтаря грешките си." (поговорка) +- "Щом се отървем от една грешка, се появява друга." (Закон на Мърфи) +- "Винаги, когато имате нужда да изпишете на екрана променлива, напишете по-скоро тест." (Мартин Фаулър) </div> -Писали ли сте някога следния код в PHP? +Написали ли сте някога в PHP подобен код? ```php $obj = new MyClass; @@ -20,40 +20,40 @@ $result = $obj->process($input); var_dump($result); ``` -Случвало ли ви се е да нулирате резултата от извикване на функция, само за да проверите на око дали тя връща това, което трябва да върне? Вероятно правите това много пъти на ден. С ръка на сърцето, ако всичко работи, изтривате ли този код и очаквате ли, че класът няма да се счупи в бъдеще? Законът на Мърфи гарантира друго :-) +Тоест, изписали сте резултата от извикването на функция само за да проверите с око дали връща това, което трябва? Със сигурност го правите много пъти на ден. Ръка на сърцето: в случай, че всичко работи правилно, изтривате ли този код? Очаквате ли, че класът няма да се счупи в бъдеще? Законите на Мърфи гарантират обратното :-) -Всъщност сте написали тест. Тя се нуждае от малка промяна, за да не изисква нашата проверка, а да може да се проверява сама. Ако не сте го изтрили, можем да го стартираме по всяко време в бъдеще, за да проверим дали всичко продължава да работи както трябва. С течение на времето може да създадете голям брой такива тестове, така че би било добре да можем да ги изпълняваме автоматично. +Всъщност сте написали тест. Просто трябва леко да го промените, така че да не изисква визуална проверка, а да се проверява сам. И ако не изтриете теста, можете да го стартирате по всяко време в бъдеще и да проверите дали всичко все още работи, както трябва. С времето ще създадете голям брой такива тестове, така че би било добре да ги стартирате автоматизирано. -А Nette Tester просто помага за това. +И с всичко това ще ви помогне именно Nette Tester. -Какво прави Tester уникален? .[#toc-what-makes-tester-unique] -============================================================= +С какво е уникален Tester? +========================== -Писането на тестове за Nette Tester е уникално с това, че **всеки тест е стандартен PHP скрипт, който може да се изпълнява отделно. +Писането на тестове за Nette Tester е уникално с това, че **всеки тест е обикновен PHP скрипт, който може да бъде стартиран самостоятелно.** -Така че, когато напишете тест, можете просто да го стартирате, за да проверите дали има програмна грешка. Ако работи правилно. Ако това не е така, можете лесно да преминете през програмата в IDE и да потърсите грешката. Можете дори да го отворите в браузъра си. +Тоест, когато пишете тест, можете просто да го стартирате и да установите дали например в него няма програмна грешка. Дали работи правилно. Ако не, можете лесно да го дебъгвате във вашето IDE и да търсите грешката. Можете дори да го отворите в браузър. -И най-важното - след като го стартирате, стартирате тест. Веднага ще разберете дали е преминал успешно или не. Как? Нека ви покажем как. Нека напишем тривиален тест на PHP за масиви и да го запишем във файл `ArrayTest.php`: +И преди всичко - с това, че го стартирате, изпълнявате теста. Веднага установявате дали е преминал, или се е провалил. Как? Нека да покажем. Ще напишем тривиален тест за работа с PHP масив и ще го запазим във файла `ArrayTest.php`: ```php .{file:ArrayTest.php} <?php use Tester\Assert; -require __DIR__ . '/vendor/autoload.php'; # загрузить автозагрузку Composer -Tester\Environment::setup(); # инициализация Nette Tester +require __DIR__ . '/vendor/autoload.php'; # зареждане на Composer autoloader +Tester\Environment::setup(); # инициализация на Nette Tester $stack = []; -Assert::same(0, count($stack)); # мы ожидаем, что count() вернет ноль +Assert::same(0, count($stack)); # очакваме, че count() ще върне нула $stack[] = 'foo'; -Assert::same(1, count($stack)); # мы ожидаем, что count() вернет единицу -Assert::contains('foo', $stack); # проверяем, что $stack содержит элемент 'foo' +Assert::same(1, count($stack)); # очакваме, че count() ще върне единица +Assert::contains('foo', $stack); # проверяваме дали $stack съдържа елемент 'foo' ``` -Както можете да видите, [методите за утвърждаване, |Assertions] като например `Assert::same()`, се използват за утвърждаване, че действителната стойност съвпада с очакваната стойност. +Както виждате, т.нар. [асерционни методи |assertions] като `Assert::same()` се използват за потвърждаване, че действителната стойност съответства на очакваната стойност. -Тестът е написан, можем да го стартираме от командния ред. Първото пускане ще разкрие всички грешки в синтаксиса и ако не сте допуснали никакви грешки, ще видите: +Тестът е написан и можем да го стартираме от командния ред. Първото стартиране ще ни разкрие евентуални синтактични грешки и ако не сте направили печатна грешка никъде, ще се изпише: /--pre .[terminal] $ php ArrayTest.php @@ -61,7 +61,7 @@ $ php ArrayTest.php <span style="color:#FFF; background-color:#090">OK</span> \-- -Опитайте да промените декларацията на `Assert::contains('XXX', $stack);` в теста и вижте какво ще се случи, когато го стартирате: +Опитайте в теста да промените твърдението на невярно `Assert::contains('XXX', $stack);` и наблюдавайте какво ще се случи при стартиране: /--pre .[terminal] $ php ArrayTest.php @@ -73,53 +73,53 @@ $ php ArrayTest.php <span style="color: #FFF; background-color: #900">FAILURE</span> \-- -Продължаваме с писането на [тестове |Writing Tests] в главата [Писане на тестове |Writing Tests]. +Продължаваме да пишем за писането в главата [Писане на тестове |writing-tests]. -Монтаж и изисквания .[#toc-installation-and-requirements] -========================================================= +Инсталация и изисквания +======================= -Минималната необходима версия на PHP за Tester е 7.1 (за повече информация вижте таблицата с [поддържаните версии на PHP |#Supported-PHP-Versions] ). Предпочитаният метод за инсталиране е [Composer |best-practices:composer]: +Минималната версия на PHP, изисквана от Tester, е 7.1 (по-подробно в таблицата [#поддържани версии на PHP]). Предпочитаният начин за инсталиране е чрез [Composer |best-practices:composer]: /--pre .[terminal] composer require --dev nette/tester \-- -Опитайте се да стартирате Nette Tester от командния ред (той ще покаже само кратка справка без аргументи): +Опитайте да стартирате Nette Tester от командния ред (без параметри само ще изпише помощ): /--pre .[terminal] vendor/bin/tester \-- -Изпълнение на тестове .[#toc-running-tests] -=========================================== +Стартиране на тестове +===================== -С разрастването на нашето приложение се увеличава и броят на тестовете. Би било непрактично да се изпълняват тестовете един по един. Затова Tester има пакетно стартиране на тестове, което се извиква от командния ред. Параметърът е директорията, в която се намират тестовете. Точката сочи към текущата директория. +С нарастването на приложението броят на тестовете расте с него. Не би било практично да стартирате тестовете един по един. Затова Tester разполага с групов стартер на тестове, който извикваме от командния ред. Като параметър посочваме директорията, в която се намират тестовете. Точката означава текущата директория. /--pre .[terminal] vendor/bin/tester . \-- -Nette Tester претърсва посочената директория и всички поддиректории и търси тестове, които са `*.phpt` и `*Test.php`. Той ще открие и нашия тест `ArrayTest.php`, тъй като той съвпада с маската. +Стартерът на тестове претърсва зададената директория и всички поддиректории и търси тестове, които са файлове `*.phpt` и `*Test.php`. Така намира и нашия тест `ArrayTest.php`, тъй като отговаря на маската. -След това се стартира тестът. Всеки тест се изпълнява като нов процес на PHP, така че се изпълнява напълно изолирано от останалите. Тя работи паралелно в няколко нишки, което я прави изключително бърза. Тестовете, които са се провалили при предишното изпълнение, се изпълняват първи, така че веднага ще разберете дали сте отстранили грешката. +След това стартира тестването. Всеки тест стартира като нов PHP процес, така че протича напълно изолирано от останалите. Стартира ги паралелно в няколко нишки и благодарение на това е изключително бърз. И като първи стартира тестовете, които при предишното изпълнение са се провалили, така че веднага разбирате дали сте успели да поправите грешката. -За всеки завършен тест програмата отпечатва един символ, за да покаже напредъка: +По време на изпълнение на тестовете Tester изписва непрекъснато резултатите на терминала като знаци: -- <code style="color: #CCC; background-color: #000">.</code> - тестът е преминал успешно -- <code style="color: #CCC; background-color: #000">s</code> - тестът е преминал успешно -- <code style="color: #FFF; background-color: #900">F</code> - тестът не е преминат +- <code style="color: #CCC; background-color: #000">.</code> – тестът премина +- <code style="color: #CCC; background-color: #000">s</code> – тестът беше пропуснат (skipped) +- <code style="color: #FFF; background-color: #900">F</code> – тестът се провали (failed) -Изходът може да изглежда по следния начин: +Изходът може да изглежда така: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +PHP 8.3.2 (cli) | php -n | 8 threads ........s................<span style="color: #FFF; background-color: #900">F</span>......... @@ -132,44 +132,44 @@ PHP 7.4.8 (cli) | php -n | 8 threads <span style="color: #FFF; background-color: #900">FAILURES! (35 tests, 1 failures, 1 skipped, 1.7 seconds)</span> \-- -Бяха попълнени 35 теста, един не беше издържан и един беше пропуснат. +Бяха стартирани 35 теста, един се провали, един беше пропуснат. -Ще продължим в главата [Изпълнение на тестове |Running tests]. +Продължаваме по-нататък в главата [Running Tests|running-tests]. -Режим на наблюдение .[#toc-watch-mode] -====================================== +Watch режим +=========== -Рефакторирате ли кода си? Или дори да разработвате по методологията TDD (Test Driven Development)? Тогава режимът за наблюдение ще ви хареса. Tester следи изходния код и се стартира, когато той се промени. +Рефакторирате код? Или дори разработвате според методологията TDD (Test Driven Development)? Тогава ще ви хареса watch режимът. Tester в него следи изходните кодове и при промяна се стартира сам. -По време на разработката имате терминал в ъгъла на монитора, където светва зелената лента за състоянието, и когато тя внезапно стане червена, знаете, че току-що сте направили нещо нежелано. Всъщност това е страхотна игра, в която програмирате и се опитвате да се придържате към цвета. +При разработка така имате в ъгъла на монитора терминал, където ви свети зелен статусен ред, и когато внезапно се промени на червен, знаете, че току-що не сте направили нещо съвсем добре. Това всъщност е страхотна игра, при която програмирате и се опитвате да поддържате цвета. -Режимът на наблюдение се стартира с параметъра [--watch |running-tests#w-watch-path]. +Watch режимът се стартира с параметъра [--watch |running-tests#-w --watch path]. -Доклади CodeCoverage .[#toc-codecoverage-reports] -================================================= +CodeCoverage отчети +=================== -Tester може да генерира отчети с обща информация за това колко изходен код обхващат тестовете. Отчетът може да бъде в разбираем за човека HTML формат или в XML формат на Clover за по-нататъшна машинна обработка. +Tester може да генерира отчети с преглед колко от изходния код покриват тестовете. Отчетът може да бъде или в човешки четим формат HTML, или Clover XML за по-нататъшна машинна обработка. -Вижте пример за HTML отчет:https://files.nette.org/tester/coverage.html с покритие на кода. +Вижте "пример за HTML отчет":attachment:coverage.html с покритие на кода. -Поддържани версии на PHP .[#toc-supported-php-versions] -======================================================= +Поддържани версии на PHP +======================== -| PHP съвместима версия +| версия | съвместима с PHP |------------------|------------------- -| Tester 2.5 | PHP 8.0 - 8.2 -| Tester 2.4 | PHP 7.2 - 8.2 -| Tester 2.3 | PHP 7.1 - 8.0 -| Tester 2.1 - 2.2 | PHP 7.1 - 7.3 -| Tester 2.0 | PHP 5.6 - 7.3 -| Tester 1.7 | PHP 5.3 - 7.3 + HHVM 3.3+ -| Tester 1.6 | PHP 5.3 - 7.0 + HHVM 3+ -| Tester 1.3 - 1.5 | PHP 5.3 - 5.6 + HHVM -| Tester 0.9 - 1.2 | PHP 5.3 - 5.6 - -Отнася се за най-новите версии на кръпките. - -Преди версия 1.7 Tester поддържаше [HHVM |https://hhvm.com] 3.3.0 или по-нова версия (с помощта на `tester -p hhvm`). От версия 2.0 нататък поддръжката е преустановена. Използването е просто: +| Tester 2.5 | PHP 8.0 – 8.3 +| Tester 2.4 | PHP 7.2 – 8.2 +| Tester 2.3 | PHP 7.1 – 8.0 +| Tester 2.1 – 2.2 | PHP 7.1 – 7.3 +| Tester 2.0 | PHP 5.6 – 7.3 +| Tester 1.7 | PHP 5.3 – 7.3 + HHVM 3.3+ +| Tester 1.6 | PHP 5.3 – 7.0 + HHVM +| Tester 1.3 – 1.5 | PHP 5.3 – 5.6 + HHVM +| Tester 0.9 – 1.2 | PHP 5.3 – 5.6 + +Важи за последната пач версия. + +Tester до версия 1.7 поддържаше също [HHVM |https://hhvm.com] 3.3.0 или по-висока (чрез `tester -p hhvm`). Поддръжката беше прекратена от версия Tester 2.0. diff --git a/tester/bg/helpers.texy b/tester/bg/helpers.texy index a233013643..3ebc43fc70 100644 --- a/tester/bg/helpers.texy +++ b/tester/bg/helpers.texy @@ -1,31 +1,45 @@ -Помощници -********* +Помощни класове +*************** -DomQuery .[#toc-domquery] -------------------------- -`Tester\DomQuery` е клас, разширяващ `SimpleXMLElement` с методи за улесняване на тестването на HTML или XML съдържание. +DomQuery +-------- +`Tester\DomQuery` е клас, разширяващ `SimpleXMLElement` с лесно търсене в HTML или XML с помощта на CSS селектори. ```php -# let's have an HTML document in $html that we load -$dom = Tester\DomQuery::fromHtml($html); - -# we can test the presence of elements using CSS selectors -Assert::true($dom->has('form#registration')); -Assert::true($dom->has('input[name="username"]')); -Assert::true($dom->has('input[type="submit"]')); - -# or select elements as array of DomQuery -$elems = $dom->find('input[data-autocomplete]'); +# създаване на DomQuery от HTML низ +$dom = Tester\DomQuery::fromHtml(' + <article class="post"> + <h1>Title</h1> + <div class="content">Text</div> + </article> +'); + +# тест за съществуване на елементи с помощта на CSS селектори +Assert::true($dom->has('article.post')); +Assert::true($dom->has('h1')); + +# намиране на елементи като масив от DomQuery обекти +$headings = $dom->find('h1'); +Assert::same('Title', (string) $headings[0]); + +# тест дали елементът отговаря на селектора (от версия 2.5.3) +$content = $dom->find('.content')[0]; +Assert::true($content->matches('div')); +Assert::false($content->matches('p')); + +# намиране на най-близкия предшественик, отговарящ на селектора (от 2.5.5) +$article = $content->closest('.post'); +Assert::true($article->matches('article')); ``` -FileMock .[#toc-filemock] -------------------------- -`Tester\FileMock` емулира файлове в паметта, за да ви помогне да тествате код, който използва функции като `fopen()`, `file_get_contents()` или `parse_ini_file()`. Например: +FileMock +-------- +`Tester\FileMock` емулира файлове в паметта и така улеснява тестването на код, който използва функции `fopen()`, `file_get_contents()`, `parse_ini_file()` и подобни. Пример за употреба: ```php -# Tested class +# Тестван клас class Logger { public function __construct( @@ -39,21 +53,21 @@ class Logger } } -# New empty file +# Нов празен файл $file = Tester\FileMock::create(''); $logger = new Logger($file); $logger->log('Login'); $logger->log('Logout'); -# Created content testing +# Тестваме създаденото съдържание Assert::same("Login\nLogout\n", file_get_contents($file)); ``` Assert::with() .[filter] ------------------------ -Това не е декларация, а помощник за тестване на частни методи и свойства на обекти. +Не е assertion, а помощник за тестване на частни методи и свойства на обекти. ```php class Entity @@ -72,10 +86,10 @@ Assert::with($ent, function () { Helpers::purge() .[filter] -------------------------- -Методът `purge()` създава зададената директория и, ако тя вече съществува, премахва цялото ѝ съдържание. Тя е полезна за създаване на временни директории. Например в `tests/bootstrap.php`: +Методът `purge()` създава зададената директория и ако вече съществува, изтрива цялото й съдържание. Полезен е за създаване на временна директория. Например в `tests/bootstrap.php`: ```php -@mkdir(__DIR__ . '/tmp'); # @ - directory may already exist +@mkdir(__DIR__ . '/tmp'); # @ - директорията вече може да съществува define('TempDir', __DIR__ . '/tmp/' . getmypid()); Tester\Helpers::purge(TempDir); @@ -84,25 +98,25 @@ Tester\Helpers::purge(TempDir); Environment::lock() .[filter] ----------------------------- -Тестовете се изпълняват паралелно. Понякога не е необходимо да припокриваме времето за изпълнение на теста. Обикновено тестовете за бази данни трябва да подготвят съдържанието на базата данни и не трябва да се намесват в него, докато тестът работи. В тези случаи използваме `Tester\Environment::lock($name, $dir)`: +Тестовете се стартират паралелно. Понякога обаче се нуждаем изпълнението на тестовете да не се припокрива. Типично при тестове на бази данни е необходимо тестът да подготви съдържанието на базата данни и друг тест да не я докосва по време на изпълнението му. В тези тестове използваме `Tester\Environment::lock($name, $dir)`: ```php Tester\Environment::lock('database', __DIR__ . '/tmp'); ``` -Първият аргумент е името на ключалката. Вторият е пътят до директорията, в която да се запази заключването. Тестът, който получава заключването, се изпълнява първи. Другите тестове трябва да изчакат завършването му. +Първият параметър е името на заключването, вторият е пътят до директорията за съхранение на заключването. Тестът, който получи заключването първи, ще се изпълни, останалите тестове трябва да изчакат неговото завършване. Environment::bypassFinals() .[filter] ------------------------------------- -Класовете или методите, отбелязани като `final`, са трудни за тестване. Извикването на `Tester\Environment::bypassFinals()` в началото на тестването води до премахване на ключовите думи `final` при зареждане на кода. +Класове или методи, означени като `final`, се тестват трудно. Извикването на `Tester\Environment::bypassFinals()` в началото на теста кара ключовите думи `final` да бъдат пропуснати по време на зареждане на кода. ```php require __DIR__ . '/bootstrap.php'; Tester\Environment::bypassFinals(); -class MyClass extends NormallyFinalClass # <-- NormallyFinalClass is not final anymore +class MyClass extends NormallyFinalClass # <-- NormallyFinalClass вече не е final { // ... } @@ -111,18 +125,18 @@ class MyClass extends NormallyFinalClass # <-- NormallyFinalClass is not final Environment::setup() .[filter] ------------------------------ -- подобрява четливостта на изхвърлянето на грешки (оцветяването е разрешено), в противен случай се отпечатва стандартна PHP стекова следа. -- позволява да се провери дали в даден тест са извикани оператори, в противен случай тестовете без (например забравени) оператори също ще преминат успешно -- автоматично стартира конструктора за покритие на кода, когато използвате `--coverage` (описано по-късно). -- отпечатва статус OK или FAILURE в края на скрипта +- подобрява четимостта на извеждането на грешки (включително оцветяване), иначе се изписва стандартният PHP stack trace +- включва проверка дали в теста са извикани assertion-и, иначе тест без assertion-и (например забравени) също ще премине +- при използване на `--coverage` стартира автоматично събиране на информация за изпълнения код (описано по-нататък) +- изписва състояние OK или FAILURE в края на скрипта Environment::setupFunctions() .[filter]{data-version:2.5} --------------------------------------------------------- -Създава глобални функции `test()`, `setUp()` и `tearDown()`, на които могат да се разделят тестовете. +Създава глобални функции `test()`, `testException()`, `setUp()` и `tearDown()`, в които можете да структурирате тестовете. ```php -test('test description', function () { +test('описание на теста', function () { Assert::same(123, foo()); Assert::false(bar()); // ... @@ -132,21 +146,21 @@ test('test description', function () { Environment::VariableRunner .[filter] ------------------------------------- -Позволяет узнать, был ли тест запущен напрямую или через Tester. +Позволява да се установи дали тестът е стартиран директно, или чрез Tester. ```php if (getenv(Tester\Environment::VariableRunner)) { - # { изпълнява се от Tester -} иначе { - # друг начин + # стартирано от Tester +} else { + # стартирано по друг начин } ``` Environment::VariableThread .[filter] ------------------------------------- -Tester запускает тесты параллельно в заданном количестве потоков. Мы найдем номер потока в переменной окружения, когда нас это заинтересует: +Tester стартира тестовете паралелно в зададения брой нишки. Ако ни интересува номерът на нишката, ще го установим от променливата на средата: ```php -echo "Работя в нишка с номер " . getenv(Tester\Environment::VariableThread); +echo "Изпълнявам се в нишка номер " . getenv(Tester\Environment::VariableThread); ``` diff --git a/tester/bg/running-tests.texy b/tester/bg/running-tests.texy index 5fe8d2728f..1f4b7eece1 100644 --- a/tester/bg/running-tests.texy +++ b/tester/bg/running-tests.texy @@ -1,83 +1,83 @@ -Изпълнение на тестове +Стартиране на тестове ********************* .[perex] -Най-видимата част на Nette Tester е програмата за изпълнение на тестове от командния ред. Той е изключително бърз и надежден, тъй като автоматично стартира всички тестове като отделни процеси паралелно в няколко нишки. Той може да се стартира и сам в така наречения режим на наблюдение. +Най-видимата част на Nette Tester е стартерът на тестове от командния ред. Той е изключително бърз и стабилен, тъй като автоматично стартира всички тестове като самостоятелни процеси и то паралелно в няколко нишки. Също така може да се стартира сам в т.нар. watch режим. -Тестовият програматор на Nette Tester се извиква от командния ред. Като параметър ще подадем директорията на теста. За текущата директория просто въведете точка: +Стартерът на тестове се извиква от командния ред. Като параметър посочваме директорията с тестовете. За текущата директория е достатъчно да зададем точка: /--pre .[terminal] vendor/bin/tester . \-- -Когато бъде извикан, test runner ще сканира посочената директория и всички поддиректории и ще търси тестове, които са файловете `*.phpt` и `*Test.php`. Той също така чете и оценява техните [анотации, |test-annotations] за да знае кои от тях и как да стартира. +Стартерът на тестове претърсва зададената директория и всички поддиректории и търси тестове, които са файлове `*.phpt` и `*Test.php`. Същевременно чете и оценява техните [анотации|test-annotations], за да знае кои от тях и как да стартира. -След това ще изпълни тестовете. За всеки извършен тест, runner-ът отпечатва по един символ, за да покаже напредъка: +След това стартира тестовете. По време на изпълнение на тестовете изписва непрекъснато резултатите на терминала като знаци: -- <code style="color: #CCC; background-color: #000">.</code> - тестът е преминал -- <code style="color: #CCC; background-color: #000">s</code> - тестът е пропуснат -- <code style="color: #FFF; background-color: #900">F</code> - тестът е неуспешен +- <code style="color: #CCC; background-color: #000">.</code> – тестът премина +- <code style="color: #CCC; background-color: #000">s</code> – тестът беше пропуснат (skipped) +- <code style="color: #FFF; background-color: #900">F</code> – тестът се провали (failed) -Изходът може да изглежда така: +Изходът може да изглежда например така: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +PHP 8.3.2 (cli) | php -n | 8 threads ........s.......................... <span style="color: #FFF; background-color: #090">OK (35 tests, 1 skipped, 1.7 seconds)</span> \-- -Когато го стартирате отново, първо се изпълняват тестовете, които не са успели при предишното стартиране, така че веднага ще разберете дали сте отстранили грешката. +При повторно стартиране първо изпълнява тестовете, които при предишното изпълнение са се провалили, така че веднага разбирате дали сте успели да поправите грешката. -Кодът за изход на тестера е нула, ако никой не се е провалил. В противен случай е различен от нула. +Ако никой тест не се провали, кодът на връщане на Tester е нула. В противен случай кодът на връщане е ненулев. .[warning] -Тестерът стартира PHP процеси без `php.ini`. Повече подробности в раздела [Собствен php.ini |#Own php.ini]. +Tester стартира PHP процеси без `php.ini`. По-подробно в частта [#Собствен php.ini]. -Опции на командния ред .[#toc-command-line-options] -=================================================== +Параметри на командния ред +========================== -Получаваме преглед на опциите на командния ред, като стартираме Тестера без параметри или с опция `-h`: +Преглед на всички опции на командния ред получаваме, като стартираме Tester без параметри или с параметър `-h`: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 - -Использование: - tester [параметры] [<тестовый файл> | <каталог>]... - -Параметры: - -p <путь> Укажите интерпретатор PHP для запуска (по умолчанию: php). - -c <path> Искать php.ini файл (или искать в директории) <path>. - -C Использовать общесистемный php.ini. - -l | --log <путь> Запись журнала в файл <путь>. - -d <ключ=значение>... Определить INI-запись 'key' со значением 'value'. - -s Показать информацию о пропущенных тестах. - --stop-on-fail Остановить выполнение при первом сбое. - -j <num> Выполнять <num> заданий параллельно (по умолчанию: 8). - -o <console|tap|junit|none> Указать формат вывода. - -w | --watch <путь> Каталог просмотра. - -i | --info Показать информацию об окружении тестов и выйти. - --setup <путь> Сценарий для настройки бегущей строки. - --temp <путь> Путь к временному каталогу. По умолчанию: sys_get_temp_dir(). - --colors [1|0] Включить или отключить цвета. - --coverage <путь> Генерировать отчет о покрытии кода в файл. - --coverage-src <путь> Путь к исходному коду. - -h | --help Это справка. + |_| \___ /___) |_| \___ |_|_\ v2.5.2 + +Usage: + tester [options] [<test file> | <directory>]... + +Options: + -p <path> Specify PHP interpreter to run (default: php). + -c <path> Look for php.ini file (or look in directory) <path>. + -C Use system-wide php.ini. + -d <key=value>... Define INI entry 'key' with value 'value'. + -s Show information about skipped tests. + --stop-on-fail Stop execution upon the first failure. + -j <num> Run <num> jobs in parallel (default: 8). + -o <console|console-lines|tap|junit|log|none> (e.g. -o junit:output.xml) + Specify one or more output formats with optional file name. + -w | --watch <path> Watch directory. + -i | --info Show tests environment info and exit. + --setup <path> Script for runner setup. + --temp <path> Path to temporary directory. Default by sys_get_temp_dir(). + --colors [1|0] Enable or disable colors. + --coverage <path> Generate code coverage report to file. + --coverage-src <path> Path to source code. + -h | --help This help. \-- -p <path> .[filter] ------------------- -Указва двоичния файл на PHP, който ще се използва за изпълнение на тестовете. По подразбиране това е `php`. +Указва PHP бинарния файл, който ще се използва за стартиране на тестовете. По подразбиране е `php`. /--pre .[terminal] tester -p /home/user/php-7.2.0-beta/php-cgi tests @@ -86,26 +86,17 @@ tester -p /home/user/php-7.2.0-beta/php-cgi tests -c <path> .[filter] ------------------- -Указва кой `php.ini` ще се използва при изпълнение на тестовете. По подразбиране не се използва php.ini. Вижте [Собствен php.ini |#Own php.ini] за повече информация. +Указва кой `php.ini` ще се използва при стартиране на тестовете. По подразбиране не се използва никакъв php.ini. Повече в частта [#Собствен php.ini]. -C .[filter] ------------ -Използва се общосистемният `php.ini`. Така че на платформата UNIX се използват и всички файлове `/etc/php/{sapi}/conf.d/*.ini`. Вижте раздела [Собствен php.ini |#Own php.ini]. - - -''-l | --log <path>'' .[filter] -------------------------------- -Напредъкът на тестването се записва във файл. Всички неуспешни, пропуснати, а също и успешни тестове: - -/--pre .[terminal] -tester --log /var/log/tests.log tests -\-- +Използва се системният `php.ini`. На UNIX също всички съответни INI файлове `/etc/php/{sapi}/conf.d/*.ini`. Повече в частта [#Собствен php.ini]. -d <key=value> .[filter] ------------------------ -Задава стойността на конфигурационната директива на PHP за тестове. Параметърът може да се използва многократно. +Задава стойността на PHP конфигурационна директива за тестовете. Параметърът може да бъде използван многократно. /--pre .[terminal] tester -d max_execution_time=20 @@ -114,34 +105,36 @@ tester -d max_execution_time=20 -s --- -Ще бъде показана информация за пропуснатите тестове. +Показва се информация за пропуснатите тестове. --stop-on-fail .[filter] ------------------------ -Тестерът спира тестването при първия неуспешен тест. +Tester спира тестването при първия провален тест. -j <num> .[filter] ------------------ -Тестовете се изпълняват в `<num>` паралелни прецеси. Стойността по подразбиране е 8. Ако искаме да изпълняваме тестовете последователно, използваме стойност 1. +Указва колко паралелни процеса с тестове да се стартират. Стойността по подразбиране е 8. Ако искаме всички тестове да се изпълнят последователно, използваме стойност 1. --o <console|tap|junit|none> .[filter] -------------------------------------- -Формат на изхода. По подразбиране е конзолният формат. +-o <console|console-lines|tap|junit|log|none> .[filter] +------------------------------------------------------- +Задава формата на изхода. По подразбиране е формат за конзола. Можете да посочите име на файл, в който да се запише изходът (напр. `-o junit:output.xml`). Опцията `-o` може да се повтори многократно и така да се генерират няколко формата едновременно. -- `console`: същият като по подразбиране, но в този случай не се отпечатва ASCII логото -- `tap`: [TAP формат, |https://en.wikipedia.org/wiki/Test_Anything_Protocol] подходящ за машинна обработка -- `junit`: JUnit XML формат, подходящ и за машинна обработка -- `none`: не се отпечатва нищо +- `console`: идентично с формата по подразбиране, но в този случай не се показва ASCII логото +- `console-lines`: подобно на console, но резултатът от всеки тест е посочен на отделен ред с допълнителна информация +- `tap`: [TAP формат |https://en.wikipedia.org/wiki/Test_Anything_Protocol] подходящ за машинна обработка +- `junit`: XML формат JUnit, също подходящ за машинна обработка +- `log`: Изходи от процеса на тестване. Всички неуспешни, пропуснати, както и успешни тестове +- `none`: нищо не се изписва ''-w | --watch <path>'' .[filter] --------------------------------- -Тестерът не приключва след приключване на тестовете, а продължава да работи и да наблюдава PHP файловете в дадената директория. При промяна той изпълнява тестовете отново. Параметърът може да се използва многократно, ако искаме да наблюдаваме няколко директории. +След завършване на тестването Tester не спира, а остава да работи и следи PHP файловете в указаната директория. При промяна стартира тестовете отново. Параметърът може да бъде използван многократно, ако искаме да следим няколко директории. -Удобно е при рефакториране на библиотека или при отстраняване на грешки в тестовете. +Полезно е при рефакторинг на библиотека или дебъгване на тестовете. /--pre .[terminal] tester --watch src tests @@ -150,7 +143,7 @@ tester --watch src tests ''-i | --info'' .[filter] ------------------------- -Показва информация за средата, в която се изпълнява тестът. Например: +Показва информация за средата на изпълнение за тестовете. Например: /--pre .[terminal] tester -p /usr/bin/php7.1 -c tests/php.ini --info @@ -177,13 +170,13 @@ Core, ctype, date, dom, ereg, fileinfo, filter, hash, ... --setup <path> .[filter] ------------------------ -Тестерът зарежда дадения PHP скрипт при стартиране. В него е налична променливата `Tester\Runner\Runner $runner`. Нека приемем, че файлът `tests/runner-setup.php`: +Tester при стартиране зарежда зададения PHP скрипт. В него е налична променливата `Tester\Runner\Runner $runner`. Да предположим файл `tests/runner-setup.php` със съдържание: ```php $runner->outputHandlers[] = new MyOutputHandler; ``` -и стартираме Тестера: +Стартираме Tester: /--pre .[terminal] tester --setup tests/runner-setup.php tests @@ -192,47 +185,47 @@ tester --setup tests/runner-setup.php tests --temp <path> .[filter] ----------------------- -Задава път до директория за временни файлове на Tester. Стойността по подразбиране се връща от `sys_get_temp_dir()`. Когато стойността по подразбиране не е валидна, ще бъдете уведомени. +Задава пътя до директорията за временни файлове на Tester. Стойността по подразбиране се връща от `sys_get_temp_dir()`. Ако стойността по подразбиране не е валидна, ще бъдете предупредени. -Ако не сме сигурни коя директория се използва, можем да стартираме Tester с параметъра `--info`. +Ако не сме сигурни коя директория се използва, стартираме Tester с параметър `--info`. --colors 1|0 .[filter] ---------------------- -Тестерът разпознава цветен терминал по подразбиране и оцветява своя изход. Тази опция е над автоматичното откриване. Можем да зададем оцветяването глобално чрез системната променлива на средата `NETTE_TESTER_COLORS`. +По подразбиране Tester открива цветен терминал и оцветява своя изход. Тази опция презаписва автодетекцията. Глобално можем да настроим оцветяването със системната променлива на средата `NETTE_TESTER_COLORS`. --coverage <path> .[filter] --------------------------- -Тестерът ще генерира отчет с преглед на това колко е покрит изходният код от тестовете. Тази опция изисква включено PHP разширение [Xdebug |https://xdebug.org/] или [PCOV |https://github.com/krakjoe/pcov], или PHP 7 с PHPDBG SAPI, което е по-бързо. Разширението на целевия файл определя формата на съдържанието. HTML или Clover XML. +Tester генерира отчет с преглед колко от изходния код покриват тестовете. Тази опция изисква инсталирано PHP разширение [Xdebug |https://xdebug.org/], или [PCOV |https://github.com/krakjoe/pcov], или PHP 7 с PHPDBG SAPI, което е по-бързо. Разширението на целевия файл определя неговия формат. Или HTML, или Clover XML. /--pre .[terminal] -tester tests --coverage coverage.html # HTML report -tester tests --coverage coverage.xml # Clover XML report +tester tests --coverage coverage.html # HTML отчет +tester tests --coverage coverage.xml # Clover XML отчет \-- -Приоритетът за избор на механизъм за събиране е следният: +Приоритетът при избор на механизъм е следният: 1) PCOV 2) PHPDBG 3) Xdebug -Обширните тестове могат да се провалят по време на изпълнение от PHPDBG поради изчерпване на паметта. Събирането на данни за покритието е операция, която отнема много памет. В този случай извикването на `Tester\CodeCoverage\Collector::flush()` вътре в теста може да помогне. То ще промие събраните данни във файл и ще освободи памет. Когато събирането на данни не е в ход или се използва Xdebug, извикването няма ефект. +При използване на PHPDBG можем при обемни тестове да срещнем провал на теста поради изчерпване на паметта. Събирането на информация за покритоя код е паметоемко. В този случай ни помага извикването на `Tester\CodeCoverage\Collector::flush()` вътре в теста. Записва събраните данни на диска и освобождава паметта. Ако събирането на данни не протича или се използва Xdebug, извикването няма никакъв ефект. -"Пример за HTML отчет":https://files.nette.org/tester/coverage.html с покритие на кода. +"Пример за HTML отчет":attachment:coverage.html с покритие на кода. --coverage-src <path> .[filter] ------------------------------- -Използваме я едновременно с опцията `--coverage`. Опцията `<path>` е път до изходния код, за който генерираме отчета. Той може да се използва многократно. +Използваме едновременно с опцията `--coverage`. `<path>` е пътят до изходните кодове, за които се генерира отчетът. Може да се използва многократно. -Собствен php.ini .[#toc-own-php-ini] -==================================== -Тестерът стартира PHP процеси с опцията `-n`, което означава, че не се зарежда `php.ini` (дори този от `/etc/php/conf.d/*.ini` в UNIX). Това осигурява една и съща среда за изпълняваните тестове, но също така деактивира всички външни PHP разширения, които обикновено се зареждат от системния PHP. +Собствен php.ini +================ +Tester стартира PHP процеси с параметър `-n`, което означава, че не се зарежда никакъв `php.ini`. На UNIX дори и тези от `/etc/php/conf.d/*.ini`. Това осигурява еднаква среда за изпълнение на тестовете, но също така изключва всички PHP разширения, обикновено заредени от системния PHP. -Ако искате да запазите системната конфигурация, използвайте параметъра `-C`. +Ако искате да запазите зареждането на системните php.ini файлове, използвайте параметър `-C`. -Ако се нуждаете от някои разширения или специални INI настройки, препоръчваме да създадете собствен `php.ini` файл и да го разпределите между тестовете. След това стартираме Tester с опция `-c`, например `tester -c tests/php.ini`. Файлът INI може да изглежда по следния начин: +Ако се нуждаете от някакви разширения или специални INI настройки за тестовете, препоръчваме създаването на собствен `php.ini` файл, който ще бъде разпространяван с тестовете. Tester след това стартираме с параметър `-c`, например `tester -c tests/php.ini tests`, където INI файлът може да изглежда така: ```ini [PHP] @@ -243,4 +236,4 @@ extension=php_pdo_pgsql.dll memory_limit=512M ``` -При стартиране на Тестера със системна опция `php.ini` в UNIX, например `tester -c /etc/php/cgi/php.ini`, не се зареждат други INI от `/etc/php/conf.d/*.ini`. Това е поведението на PHP, а не на Тестера. +Стартирането на Tester в UNIX със системен `php.ini`, например `tester -c /etc/php/cli/php.ini`, не зарежда останалите INI от `/etc/php/conf.d/*.ini`. Това е свойство на PHP, не на Tester. diff --git a/tester/bg/test-annotations.texy b/tester/bg/test-annotations.texy index e0c6106178..37d38f3490 100644 --- a/tester/bg/test-annotations.texy +++ b/tester/bg/test-annotations.texy @@ -1,16 +1,16 @@ -Резюмета на тестовете -********************* +Анотации на тестове +******************* .[perex] -Анотациите определят начина, по който тестовете ще се обработват от [програмата за стартиране на тестове от командния ред |running-tests]. Те се записват в началото на тестовия файл. +Анотациите определят как ще се третират тестовете от [стартера на тестове от командния ред|running-tests]. Записват се в началото на файла с теста. -Анотациите не се различават по големина на буквите. Те нямат ефект и ако тестът се изпълнява ръчно като обикновен PHP скрипт. +При анотациите не се взема предвид размерът на буквите. Също така нямат никакво влияние, ако тестът се стартира ръчно като обикновен PHP скрипт. Пример: ```php /** - * TEST: Basic database query test. + * ТЕСТ: Основен тест за заявка към база данни. * * @dataProvider files/databases.ini * @exitCode 56 @@ -21,19 +21,19 @@ require __DIR__ . '/../bootstrap.php'; ``` -Test .[filter] +TEST .[filter] -------------- -Всъщност това не е анотация. Той задава само заглавието на теста, което се показва при неуспех или в регистрите. +Това всъщност дори не е анотация, само определя заглавието на теста, което се изписва при провал или в лога. @skip .[filter] --------------- -Тестът се прескача. Това е удобно за временно деактивиране на теста. +Тестът се пропуска. Полезно е за временно изключване на тестове. @phpVersion .[filter] --------------------- -Тестът ще бъде пропуснат, ако не е изпълнен с подходящата версия на PHP. Записваме анотацията като `@phpVersion [operator] version`. Можем да не посочим оператор, по подразбиране е `>=`. Примери: +Тестът се пропуска, ако не е стартиран със съответната версия на PHP. Анотацията записваме като `@phpVersion [оператор] версия`. Операторът можем да пропуснем, по подразбиране е `>=`. Примери: ```php /** @@ -46,7 +46,7 @@ Test .[filter] @phpExtension .[filter] ----------------------- -Тестът ще бъде пропуснат, ако не са заредени всички посочени PHP разширения. В една анотация могат да бъдат записани няколко разширения или анотацията може да се използва многократно. +Тестът се пропуска, ако не са заредени всички посочени PHP разширения. Повече разширения можем да посочим в една анотация или да я използваме многократно. ```php /** @@ -58,9 +58,9 @@ Test .[filter] @dataProvider .[filter] ----------------------- -Тази анотация е подходяща, когато искаме да стартираме даден тест няколко пъти, но с различни данни. (Да не се бърка с анотацията със същото име за [TestCase |TestCase#dataProvider]). +Ако искаме тестов файл да се стартира многократно, но с различни входни данни, е полезна именно тази анотация. (Не бъркайте със същата анотация за [TestCase |TestCase#dataProvider].) -Записваме анотацията като `@dataProvider file.ini`. Пътят до файла INI е относителен спрямо тестовия файл. Тестът се изпълнява толкова пъти, колкото секции има в INI файла. Да предположим, че INI файлът е `databases.ini`: +Записваме като `@dataProvider file.ini`, пътят до файла се взема относително спрямо файла с теста. Тестът ще бъде стартиран толкова пъти, колкото секции има в INI файла. Да предположим INI файл `databases.ini`: ```ini [mysql] @@ -77,7 +77,7 @@ password = ****** dsn = "sqlite::memory:" ``` -и файла `database.phpt` в същата директория: +и в същата директория тест `database.phpt` : ```php /** @@ -87,11 +87,11 @@ dsn = "sqlite::memory:" $args = Tester\Environment::loadData(); ``` -Тестът се изпълнява три пъти и `$args` ще съдържа стойности от разделите `mysql`, `postgresql` или `sqlite`. +Тестът ще бъде стартиран три пъти и `$args` ще съдържа винаги стойности от секцията `mysql`, `postgresql` или `sqlite`. -Съществува и друга възможност, при която записваме анотациите с въпросителен знак като `@dataProvider? file.ini`. В този случай тестът ще бъде пропуснат, ако файлът INI не съществува. +Съществува още вариант, при който анотацията записваме с въпросителен знак като `@dataProvider? file.ini`. В този случай тестът се пропуска, ако INI файлът не съществува. -Все още не са споменати всички възможности за анотации. Можем да напишем условия след файла INI. Тестът ще се изпълни за даден раздел само ако всички условия съвпадат. Нека разширим файла INI: +С това възможностите на анотацията не свършват. След името на INI файла можем да специфицираме условия, при които тестът за дадена секция ще бъде стартиран. Ще разширим INI файла: ```ini [mysql] @@ -113,7 +113,7 @@ password = ****** dsn = "sqlite::memory:" ``` -и използвайте анотация с условие: +и ще използваме анотация с условие: ```php /** @@ -121,9 +121,9 @@ dsn = "sqlite::memory:" */ ``` -Тестът се прави само веднъж за раздела `postgresql 9.1`. Другите раздели не отговарят на условията. +Тестът ще бъде стартиран само веднъж и то за секцията `postgresql 9.1`. Останалите секции не преминават през филтъра на условието. -По същия начин можем да предадем път до PHP скрипт вместо INI. Тя трябва да връща масив или Traversable. Файл `databases.php`: +Подобно можем вместо INI файл да препратим към PHP скрипт. Той трябва да върне масив или Traversable. Файл `databases.php`: ```php return [ @@ -142,29 +142,29 @@ return [ @multiple .[filter] ------------------- -Запишете го като `@multiple N`, където `N` е цяло число. Тестът се изпълнява точно N пъти. +Записваме като `@multiple N`, където `N` е цяло число. Тестът ще бъде стартиран точно N пъти. @testCase .[filter] ------------------- -Анотацията няма параметри. Използваме го, когато записваме тестовете като класове [TestCase |TestCase]. В този случай програмата за стартиране на тестове от командния ред ще изпълнява отделни методи в отделни процеси и паралелно в няколко нишки. Това може значително да ускори целия процес на тестване. +Анотацията няма параметри. Използваме я, ако тестовете пишем като [TestCase | TestCase] класове. В този случай стартерът на тестове от командния ред ще стартира отделните методи в самостоятелни процеси и паралелно в няколко нишки. Това може значително да ускори целия процес на тестване. @exitCode .[filter] ------------------- -Записваме го като `@exitCode N`, където `N` is the exit code of the test. For example if `exit(10)` се извиква в теста, а анотацията записваме като `@exitCode 10`. Ако тестът завърши с различен код, това се счита за неуспех. Изходният код 0 (нула) се проверява, ако пропуснем анотацията +Записваме като `@exitCode N`, където `N` е кодът на връщане на стартирания тест. Ако в теста например се извиква `exit(10)`, анотацията записваме като `@exitCode 10` и ако тестът завърши с друг код, това се счита за провал. Ако анотацията не се посочи, се проверява код на връщане 0 (нула). @httpCode .[filter] ------------------- -Анотацията се оценява само ако двоичният файл на PHP е CGI. В противен случай се игнорира. Записваме го като `@httpCode NNN`, където `NNN` е очакваният HTTP код. HTTP код 200 ще бъде проверен, ако пропуснем анотацията. Ако напишем `NNN` като низ, оценен като нула, например `any`, HTTP кодът изобщо няма да бъде проверен. +Анотацията се прилага само ако PHP бинарният файл е CGI. Иначе се игнорира. Записваме като `@httpCode NNN`, където `NNN` е очакваният HTTP код. Ако анотацията не се посочи, се проверява HTTP код 200. Ако `NNN` запишем като низ, оценен на нула, например `any`, HTTP кодът не се проверява. -@outputMatch a @outputMatchFile .[filter] +@outputMatch и @outputMatchFile .[filter] ----------------------------------------- -Поведението на анотациите е в съответствие с `Assert::match()` и `Assert::matchFile()`. Но в стандартния тестови изход се среща един модел. Подходящ случай на употреба е, когато предполагаме, че тестът ще завърши с фатална грешка и трябва да проверим неговия изход. +Функцията на анотациите е идентична с assertion-ите `Assert::match()` и `Assert::matchFile()`. Шаблонът (pattern) обаче се търси в текста, който тестът е изпратил на своя стандартен изход. Приложение намира, ако предполагаме, че тестът ще завърши с фатална грешка и трябва да проверим неговия изход. @phpIni .[filter] ----------------- -Задава стойностите на конфигурацията INI за теста. Например, записваме го като `@phpIni precision=20` и той работи по същия начин, както ако предадем стойността от командния ред с параметъра `-d precision=20`. +За теста задава конфигурационни INI стойности. Записваме например като `@phpIni precision=20` и работи по същия начин, както ако бяхме задали стойността от командния ред чрез параметър `-d precision=20`. diff --git a/tester/bg/testcase.texy b/tester/bg/testcase.texy index ce9ce228d5..3d534897c9 100644 --- a/tester/bg/testcase.texy +++ b/tester/bg/testcase.texy @@ -2,9 +2,9 @@ TestCase ******** .[perex] -В простите тестове твърденията могат да следват едно след друго. Понякога обаче е полезно да се затворят твърденията в тестови клас и да се структурират по този начин. +В прости тестове assertion-ите могат да следват една след друга. Понякога обаче е по-удобно assertion-ите да се опаковат в тестов клас и така да се структурират. -Класът трябва да бъде потомък на `Tester\TestCase`, а ние го наричаме просто **testcase**. +Класът трябва да бъде наследник на `Tester\TestCase` и опростено го наричаме **testcase**. Класът трябва да съдържа тестови методи, започващи с `test`. Тези методи ще бъдат стартирани като тестове: ```php use Tester\Assert; @@ -22,11 +22,11 @@ class RectangleTest extends Tester\TestCase } } -# Run testing methods +# Стартиране на тестовите методи (new RectangleTest)->run(); ``` -Можем да обогатим тестовия пример с методите `setUp()` и `tearDown()`. Те се извикват преди/след всеки метод за изпитване: +Така написаният тест може да бъде допълнително обогатен с методите `setUp()` и `tearDown()`. Те се извикват преди, респ. след всеки тестов метод: ```php use Tester\Assert; @@ -35,12 +35,12 @@ class NextTest extends Tester\TestCase { public function setUp() { - # Preparation + # Подготовка } public function tearDown() { - # Clean-up + # Почистване } public function testOne() @@ -54,14 +54,14 @@ class NextTest extends Tester\TestCase } } -# Run testing methods +# Стартиране на тестовите методи (new NextTest)->run(); /* -Method Calls Order ------------------- +Ред на извикване на методите +---------------------------- setUp() testOne() tearDown() @@ -72,9 +72,9 @@ tearDown() */ ``` -Ако се появи грешка във фазата `setUp()` или `tearDown()`, тестът ще се провали. Ако в тестовия метод възникне грешка, методът `tearDown()` се извиква въпреки това, но с потиснати в него грешки. +Ако възникне грешка във фазата `setUp()` или `tearDown()`, тестът като цяло се проваля. Ако възникне грешка в тестовия метод, въпреки това методът `tearDown()` се стартира, но с потискане на грешките в него. -Препоръчваме анотацията [@testCase |test-annotations#testCase] да се запише в началото на теста, след което програмата за стартиране на тестове от командния ред ще изпълнява отделните методи на тестовия случай в отделни процеси и паралелно в няколко нишки. Това може значително да ускори целия процес на тестване. +Препоръчваме в началото на теста да напишете анотацията [@testCase |test-annotations#testCase], тогава стартерът на тестове от командния ред ще стартира отделните методи на testcase в самостоятелни процеси и паралелно в няколко нишки. Това може значително да ускори целия процес на тестване. /--php <?php @@ -82,15 +82,15 @@ tearDown() \-- -Анотиране на методи .[#toc-annotation-of-methods] -================================================= +Анотации на методи +================== -Има няколко анотации, които ни помагат при тестването на методите. Записваме ги в посока на метода за изпитване. +При тестовите методи имаме на разположение няколко анотации, които ни улесняват тестването. Записваме ги към тестовия метод. @throws .[filter] ----------------- -Това е същото използване на `Assert::exception()` в рамките на метод за изпитване. Но обозначението е по-четивно: +Е еквивалент на използването на `Assert::exception()` вътре в тестовия метод. Записът обаче е по-прегледен: ```php /** @@ -103,7 +103,7 @@ public function testOne() /** - * @throws LogicException Грешен ред на аргументите + * @throws LogicException Wrong argument order */ public function testTwo() { @@ -114,9 +114,9 @@ public function testTwo() @dataProvider .[filter] ----------------------- -Тази анотация е подходяща, когато искаме да стартираме даден тестови метод многократно, но с различни аргументи. (Да не се бърка с анотацията на [файла |test-annotations#dataProvider] със същото име). +Ако искаме тестов метод да се стартира многократно, но с различни параметри, е полезна именно тази анотация. (Не бъркайте със същата анотация за [файлове |test-annotations#dataProvider].) -Като аргумент записваме името на метода, който връща параметрите на тестовия метод. Методът трябва да връща масив или Traversable. Един прост пример: +След нея посочваме името на метода, който връща аргументите за тестовия метод. Методът трябва да върне масив или Traversable. Прост пример: ```php public function getLoopArgs() @@ -138,7 +138,7 @@ public function testLoop($a, $b, $c) } ``` -Друга разновидност на анотацията **@dataProvider** приема като аргумент пътя до файла INI (относително към тестовия файл). Методът се извиква толкова пъти, колкото секции има в INI файла. Файл `loop-args.ini`: +Втората варианта на анотацията **@dataProvider** приема като параметър пътя до INI файл (относително спрямо файла с теста). Методът се извиква толкова пъти, колкото секции има в INI файла. Файл `loop-args.ini`: ```ini [one] @@ -157,7 +157,7 @@ b=8 c=9 ``` -и метода, използващ файла INI: +и методът, който използва INI файла: ```php /** @@ -169,7 +169,7 @@ public function testLoop($a, $b, $c) } ``` -По същия начин можем да предадем път до PHP скрипт вместо INI. Тя трябва да връща масив или Traversable. Файл `loop-args.php`: +Подобно можем вместо INI файл да препратим към PHP скрипт. Той трябва да върне масив или Traversable. Файл `loop-args.php`: ```php return [ diff --git a/tester/bg/writing-tests.texy b/tester/bg/writing-tests.texy index f1ec9d5517..764449201d 100644 --- a/tester/bg/writing-tests.texy +++ b/tester/bg/writing-tests.texy @@ -1,21 +1,20 @@ -Писмени тестове -*************** +Писане на тестове +***************** .[perex] -Писането на тестове за Nette Tester е уникално с това, че всеки тест е PHP скрипт, който може да се изпълнява отделно. Това има голям потенциал. -След като сте написали тест, можете просто да го стартирате, за да проверите дали работи правилно. Ако това не е така, можете лесно да преминете през теста в IDE и да потърсите грешка. +Писането на тестове за Nette Tester е уникално с това, че всеки тест е PHP скрипт, който може да бъде стартиран самостоятелно. Това крие голям потенциал. Още докато пишете теста, можете просто да го стартирате и да установите дали работи правилно. Ако не, може лесно да го дебъгвате в IDE и да търсите грешката. -Можете дори да отворите теста в браузър. Но най-важното е, че след като го стартирате, стартирате теста. Веднага ще разберете дали е преминал успешно или не. +Можете дори да отворите теста в браузър. Но преди всичко - с това, че го стартирате, изпълнявате теста. Веднага установявате дали е преминал, или се е провалил. -В уводната глава [показахме |guide#What-Makes-Tester-Unique] един наистина тривиален тест за масиви на PHP. Сега ще създадем наш собствен клас, който ще тестваме, въпреки че той също е прост. +В уводната глава [показахме |guide#С какво е уникален Tester] наистина тривиален тест за работа с масив. Сега вече ще създадем собствен клас, който ще тестваме, макар и той да е прост. -Ще започнем с типична схема на директория на библиотека или проект. Важно е да се отделят тестовете от останалата част от кода, например заради внедряването, тъй като не искаме да качваме тестовете на сървъра. Структурата може да бъде следната: +Ще започнем от типичната структура на директориите за библиотека или проект. Важно е да отделим тестовете от останалия код, например заради разгръщането, защото тестовете не искаме да качваме на продукционния сървър. Структурата може да бъде например такава: ``` -├── src/ # code that we will test +├── src/ # код, който ще тестваме │ ├── Rectangle.php │ └── ... -├── tests/ # tests +├── tests/ # тестове │ ├── bootstrap.php │ ├── RectangleTest.php │ └── ... @@ -23,7 +22,7 @@ └── composer.json ``` -Сега ще създадем отделни файлове. Нека започнем с тествания клас, който ще поставим във файла `src/Rectangle.php` +А сега ще създадем отделните файлове. Ще започнем от тествания клас, който ще поставим във файла `src/Rectangle.php` ```php .{file:src/Rectangle.php} <?php @@ -35,7 +34,7 @@ class Rectangle public function __construct(float $width, float $height) { if ($width < 0 || $height < 0) { - throw new InvalidArgumentException('Размерность не должна быть отрицательной.'); + throw new InvalidArgumentException('The dimension must not be negative.'); } $this->width = $width; $this->height = $height; @@ -53,7 +52,7 @@ class Rectangle } ``` -И създайте тест за него. Името на тестовия файл трябва да съответства на маската `*Test.php` или `*.phpt`, ние ще изберем `RectangleTest.php`: +И ще създадем тест за него. Името на файла с теста трябва да отговаря на маската `*Test.php` или `*.phpt`, ще изберем например варианта `RectangleTest.php`: ```php .{file:tests/RectangleTest.php} @@ -62,31 +61,31 @@ use Tester\Assert; require __DIR__ . '/bootstrap.php'; -//общ правоъгълник +// общ правоъгълник $rect = new Rectangle(10, 20); -Assert::same(200.0, $rect->getArea()); # проверяваме очакваните резултати +Assert::same(200.0, $rect->getArea()); # проверяваме очакваните резултати Assert::false($rect->isSquare()); ``` -Както можете да видите, [методите за утвърждаване, |Assertions] като например `Assert::same()`, се използват за утвърждаване, че действителната стойност съвпада с очакваната стойност. +Както виждате, т.нар. [assertion методи|assertions] като `Assert::same()` се използват за потвърждаване, че действителната стойност съответства на очакваната стойност. -Последната стъпка е да се създаде файлът `bootstrap.php`. Тя съдържа общ код за всички тестове. Например, автоматично зареждане на класове, конфигурация на средата, създаване на временна директория, помощни програми и други подобни. Всеки тест зарежда bootstrap и се фокусира само върху тестването. Един bootstrap може да изглежда по следния начин: +Остава последната стъпка, а именно файлът `bootstrap.php`. Той съдържа код, общ за всички тестове, например автоматично зареждане на класове, конфигурация на средата, създаване на временна директория, помощни функции и подобни. Всички тестове зареждат bootstrap и по-нататък се занимават само с тестване. Bootstrap може да изглежда по следния начин: ```php .{file:tests/bootstrap.php} <?php -require __DIR__ . '/vendor/autoload.php'; # зареждане на Composer autoloader +require __DIR__ . '/vendor/autoload.php'; # зарежда Composer autoloader -Tester\Environment::setup(); # инициализиране на Nette Tester +Tester\Environment::setup(); # инициализация на Nette Tester -//и други конфигурации (само като пример, те не са необходими в нашия случай) +// и друга конфигурация (това е само пример, в нашия случай не са необходими) date_default_timezone_set('Europe/Prague'); define('TmpDir', '/tmp/app-tests'); ``` .[note] -Този bootstrap предполага, че автозареждащият модул на Composer също ще може да зареди класа `Rectangle.php`. Това може да се постигне например [чрез задаване на секцията за автоматично зареждане на |best-practices:composer#Autoloading] `composer.json`, и т.н. +Посоченият bootstrap предполага, че autoloader-ът на Composer ще може да зареди и класа `Rectangle.php`. Това може да се постигне например чрез [настройване на секцията autoload |best-practices:composer#Autoloading] в `composer.json` и т.н. -Сега можем да стартираме теста от командния ред като всеки друг PHP скрипт. Първото стартиране ще открие всички синтактични грешки и ако не сте допуснали никакви грешки, ще видите +Тестът можем сега да стартираме от командния ред като всеки друг самостоятелен PHP скрипт. Първото стартиране ще ни разкрие евентуални синтактични грешки и ако никъде няма печатна грешка, ще се изпише: /--pre .[terminal] $ php RectangleTest.php @@ -94,7 +93,7 @@ $ php RectangleTest.php <span style="color:#FFF; background-color:#090">OK</span> \-- -Ако променим твърдението в теста на false `Assert::same(123, $rect->getArea());`, ще се случи следното: +Ако променим в теста твърдението на невярно `Assert::same(123, $rect->getArea());`, ще се случи това: /--pre .[terminal] $ php RectangleTest.php @@ -107,35 +106,35 @@ $ php RectangleTest.php \-- -Когато пишете тестове, е полезно да уловите всички екстремни ситуации. Например, ако входът е нула, отрицателно число, в други случаи - празен низ, нула и т.н. Всъщност тя ви принуждава да мислите и да решавате как да се държи кодът в такива ситуации. След това тестовете коригират поведението. +При писане на тестове е добре да се обхванат всички крайни ситуации. Например, когато входът е нула, отрицателно число, в други случаи например празен низ, null и т.н. Всъщност това ви кара да се замислите и да решите как трябва да се държи кодът в такива ситуации. Тестовете след това фиксират поведението. -В нашия случай отрицателната стойност трябва да предизвика изключение, което проверяваме с [Assert::exception() |Assertions#Assert-exception]: +В нашия случай отрицателната стойност трябва да хвърли изключение, което ще проверим с помощта на [Assert::exception() |Assertions#Assert::exception]: ```php .{file:tests/RectangleTest.php} -// ширината не трябва да е отрицателно число +// ширината не трябва да бъде отрицателна Assert::exception( fn() => new Rectangle(-1, 20), InvalidArgumentException::class, - 'Размерът не трябва да е отрицателно число, + 'The dimension must not be negative.', ); ``` -Добавяме и подобен тест за височина. Накрая проверяваме дали `isSquare()` връща `true`, ако двете измервания са еднакви. Опитайте се да напишете такива тестове като упражнение. +И подобен тест ще добавим за височината. Накрая ще тестваме дали `isSquare()` връща `true`, ако и двете измерения са еднакви. Опитайте като упражнение да напишете такива тестове. -Добре организирани тестове .[#toc-well-arranged-tests] -====================================================== +По-прегледни тестове +==================== -Размерът на тестовия файл може да се увеличи и бързо да стане претрупан. Поради това е препоръчително да се групират отделните области на изпитване в отделни функции. +Размерът на файла с теста може да нараства и бързо да стане непрегледен. Затова е практично отделните тествани области да се групират в самостоятелни функции. -Първо ще покажем по-прост, но по-елегантен вариант, като използваме глобалната функция `test()`. Tester не я създава автоматично, за да избегне колизия, ако в кода ви има функция със същото име. Той се създава само чрез метода `setupFunctions()`, който се извиква във файла `bootstrap.php`: +Първо ще покажем по-прост, но елегантен вариант, а именно с помощта на глобалната функция `test()`. Tester не я създава автоматично, за да не възникне колизия, ако имате в кода функция със същото име. Създава я едва методът `setupFunctions()`, който извикайте във файла `bootstrap.php`: ```php .{file:tests/bootstrap.php} Tester\Environment::setup(); Tester\Environment::setupFunctions(); ``` -С помощта на тази функция можем да разделим тестовия файл на именувани блокове. Когато функцията се изпълни, етикетите ще се показват един по един. +С помощта на тази функция можем да разчленим тестовия файл на именувани единици. При стартиране ще се изписват последователно етикетите. ```php .{file:tests/RectangleTest.php} <?php @@ -143,19 +142,19 @@ use Tester\Assert; require __DIR__ . '/bootstrap.php'; -test('general oblong', function () { +test('общ правоъгълник', function () { $rect = new Rectangle(10, 20); Assert::same(200.0, $rect->getArea()); Assert::false($rect->isSquare()); }); -test('general square', function () { +test('общ квадрат', function () { $rect = new Rectangle(5, 5); Assert::same(25.0, $rect->getArea()); Assert::true($rect->isSquare()); }); -test('размеры не должны быть отрицательными', function () { +test('размерите не трябва да бъдат отрицателни', function () { Assert::exception( fn() => new Rectangle(-1, 20), InvalidArgumentException::class, @@ -168,65 +167,65 @@ test('размеры не должны быть отрицательными', f }); ``` -Ако трябва да стартирате код преди или след всеки тест, предайте го на `setUp()` или `tearDown()`: +Ако трябва да стартирате код преди или след всеки тест, предайте го на функцията `setUp()` респ. `tearDown()`: ```php setUp(function () { - // инициализиране на код, който да се изпълнява преди всеки тест() + // инициализационен код, който се стартира преди всеки test() }); ``` -Второй вариант - объектный. Мы создадим так называемый TestCase, который представляет собой класс, где отдельные единицы представлены методами, имена которых начинаются с test-. +Вторият вариант е обектен. Ще си създадем т.нар. TestCase, което е клас, където отделните единици представляват методи, чиито имена започват с test–. ```php .{file:tests/RectangleTest.php} -клас RectangleTest разширява Tester\TestCase +class RectangleTest extends Tester\TestCase { - публична функция testGeneralOblong() + public function testGeneralOblong() { - $rect = нов правоъгълник(10, 20); + $rect = new Rectangle(10, 20); Assert::same(200.0, $rect->getArea()); Assert::false($rect->isSquare()); } - публична функция testGeneralSquare() + public function testGeneralSquare() { - $rect = нов правоъгълник(5, 5); + $rect = new Rectangle(5, 5); Assert::same(25.0, $rect->getArea()); Assert::true($rect->isSquare()); } /** @throws InvalidArgumentException */ - публична функция testWidthMustNotBeNegative() + public function testWidthMustNotBeNegative() { - $rect = нов правоъгълник(-1, 20); + $rect = new Rectangle(-1, 20); } /** @throws InvalidArgumentException */ - публична функция testHeightMustNotBeNegative() + public function testHeightMustNotBeNegative() { - $rect = нов правоъгълник(10, -1); + $rect = new Rectangle(10, -1); } } -// Изпълнение на тестови методи -(нов RectangleTest)->run(); +// Стартиране на тестовите методи +(new RectangleTest)->run(); ``` -На этот раз мы использовали аннотацию `@throw` для проверки на исключения. Более подробную информацию смотрите в главе [TestCase]. +За тестване на изключения този път използвахме анотацията `@throw`. Повече ще научите в главата [TestCase | TestCase]. -Функции-помощники .[#toc-helpers-functions] -=========================================== +Помощни функции +=============== -Nette Tester включает в себя несколько классов и функций, которые могут облегчить вам тестирование, например, помощники для тестирования содержимого HTML-документа, для тестирования функций работы с файлами и так далее. +Nette Tester съдържа няколко класа и функции, които могат да ви улеснят например тестването на съдържанието на HTML документ, тестването на функции, работещи с файлове и така нататък. -Их описание вы можете найти на странице [Helpers]. +Тяхното описание ще намерите на страницата [Помощни класове|helpers]. -Аннотирование и пропуск тестов .[#toc-annotation-and-skipping-tests] -==================================================================== +Анотации и пропускане на тестове +================================ -На выполнение тестов могут влиять аннотации в комментарии phpDoc в начале файла. Например, он может выглядеть следующим образом: +Стартирането на тестове може да бъде повлияно от анотации под формата на phpDoc коментар в началото на файла. Може да изглежда например така: ```php .{file:tests/RectangleTest.php} /** @@ -235,56 +234,56 @@ Nette Tester включает в себя несколько классов и */ ``` -Аннотации гласят, что тест должен выполняться только с PHP версии 7.2 или выше и при наличии PHP расширений pdo и pdo_pgsql. Эти аннотации контролируются [программой запуска тестов командной строки |running-tests], которая, если условия не выполняются, пропускает тест и помечает его буквой `s` - пропущен. Однако они не имеют никакого эффекта, когда тест выполняется вручную. +Посочените анотации казват, че тестът трябва да бъде стартиран само с PHP версия 7.2 или по-висока и ако са налични PHP разширенията pdo и pdo_pgsql. С тези анотации се ръководи [стартерът на тестове от командния ред|running-tests], който в случай, че условията не са изпълнени, пропуска теста и в изхода го означава с буквата `s` - skipped. Обаче при ръчно стартиране на теста нямат никакво влияние. -Описание аннотаций приведено в разделе [Аннотации тестов |Test Annotations]. +Описание на анотациите ще намерите на страницата [Анотации на тестове|test-annotations]. -Тест также может быть пропущен на основании собственного условия с помощью `Environment::skip()`. Например, мы пропустим этот тест на Windows: +Тестът може да бъде пропуснат също въз основа на изпълнение на собствено условие с помощта на `Environment::skip()`. Например тази ще пропусне тестовете на Windows: ```php -if (defined('PHP_WINDOWS_VERSION_BUILD') { - Tester\Environment::skip('Изисква UNIX.'); +if (defined('PHP_WINDOWS_VERSION_BUILD')) { + Tester\Environment::skip('Requires UNIX.'); } ``` -Структура каталогов .[#toc-directory-structure] -=============================================== +Структура на директориите +========================= -Для немного больших библиотек или проектов мы рекомендуем разделить тестовый каталог на подкаталоги в соответствии с пространством имен тестируемого класса: +Препоръчваме при малко по-големи библиотеки или проекти да разделите директорията с тестове още на поддиректории според пространството от имена на тествания клас: ``` -└── тестове/. +└── tests/ ├── NamespaceOne/ - │ ├── MyClass.getUsers.phpt - │ ├── MyClass.setUsers.phpt - │ └── ... + │ ├── MyClass.getUsers.phpt + │ ├── MyClass.setUsers.phpt + │ └── ... │ ├── NamespaceTwo/ - │ ├── MyClass.creating.phpt - │ ├── MyClass.dropping.phpt - │ └── ... + │ ├── MyClass.creating.phpt + │ ├── MyClass.dropping.phpt + │ └── ... │ ├── bootstrap.php └── ... ``` -Ще можете да стартирате тестове от същото пространство от имена, т.е. от поддиректория: +Така ще можете да стартирате тестове от едно единствено пространство от имена, т.е. поддиректория: /--pre .[terminal] tester tests/NamespaceOne \-- -Крайни случаи .[#toc-edge-cases] -================================ +Специални ситуации +================== -Тест, който не извиква нито един метод за утвърждаване, е подозрителен и ще бъде оценен като грешен: +Тест, който не извиква нито един assertion метод, е подозрителен и се оценява като грешен: /--pre .[terminal] <span style="color: #FFF; background-color: #900">Error: This test forgets to execute an assertion.</span> \-- -Ако един тест без извикване на твърдения наистина трябва да се счита за правилен, извикайте например `Assert::true(true)`. +Ако наистина тестът без извикване на assertions трябва да се счита за валиден, извикайте например `Assert::true(true)`. -Коварно може да бъде и използването на `exit()` и `die()`, за да се завърши тестът със съобщение за грешка. Например `exit('Error in connection')` завършва теста с код на излизане 0, което е сигнал за успех. Използвайте `Assert::fail('Error in connection')`. +Също така може да бъде коварно да се използват `exit()` и `die()` за прекратяване на теста със съобщение за грешка. Например `exit('Error in connection')` прекратява теста с код на връщане 0, което сигнализира за успех. Използвайте `Assert::fail('Error in connection')`. diff --git a/tester/cs/@meta.texy b/tester/cs/@meta.texy new file mode 100644 index 0000000000..cbfa8c8eb9 --- /dev/null +++ b/tester/cs/@meta.texy @@ -0,0 +1 @@ +{{sitename: Tester Dokumentace}} diff --git a/tester/cs/assertions.texy b/tester/cs/assertions.texy index b574efdac7..5722368dfa 100644 --- a/tester/cs/assertions.texy +++ b/tester/cs/assertions.texy @@ -15,18 +15,18 @@ use Tester\Assert; ``` -Assert::same($expected, $actual, string $description=null) .[method] --------------------------------------------------------------------- +Assert::same($expected, $actual, ?string $description=null) .[method] +--------------------------------------------------------------------- `$expected` musí být totožný s `$actual`. To samé jako PHP operátor `===`. -Assert::notSame($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------ +Assert::notSame($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------ Opak `Assert::same()`, tedy to samé jako PHP operátor `!==`. -Assert::equal($expected, $actual, string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] ------------------------------------------------------------------------------------------------------------------------- +Assert::equal($expected, $actual, ?string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] +------------------------------------------------------------------------------------------------------------------------- `$expected` musí být stejný s `$actual`. Na rozdíl od `Assert::same()` se ignoruje identita objektů, pořadí dvojic klíčů => hodnota v polích a marginálně odlišná desetinná čísla, což lze změnit nastavením `$matchIdentity` a `$matchOrder`. Následující případy jsou shodné z pohledu `equal()`, ale nikoliv `same()`: @@ -45,73 +45,73 @@ Ovšem pozor, pole `[1, 2]` a `[2, 1]` stejné nejsou, protože se liší jen po Dále lze v `$expected` použít tzv. [#očekávání]. -Assert::notEqual($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------- +Assert::notEqual($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------- Opak `Assert::equal()`. -Assert::contains($needle, string|array $actual, string $description=null) .[method] ------------------------------------------------------------------------------------ +Assert::contains($needle, string|array $actual, ?string $description=null) .[method] +------------------------------------------------------------------------------------ Pokud je `$actual` řetězec, musí obsahovat podřetězec `$needle`. Pokud je pole, musí obsahovat prvek `$needle` (porovnává se striktně). -Assert::notContains($needle, string|array $actual, string $description=null) .[method] --------------------------------------------------------------------------------------- +Assert::notContains($needle, string|array $actual, ?string $description=null) .[method] +--------------------------------------------------------------------------------------- Opak `Assert::contains()`. -Assert::hasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} -------------------------------------------------------------------------------------------------------- +Assert::hasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +-------------------------------------------------------------------------------------------------------- `$actual` musí být pole a musí obsahovat klíč `$needle`. -Assert::notHasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} ----------------------------------------------------------------------------------------------------------- +Assert::notHasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +----------------------------------------------------------------------------------------------------------- `$actual` musí být pole a nesmí obsahovat klíč `$needle`. -Assert::true($value, string $description=null) .[method] --------------------------------------------------------- +Assert::true($value, ?string $description=null) .[method] +--------------------------------------------------------- `$value` musí být `true`, tedy `$value === true`. -Assert::truthy($value, string $description=null) .[method] ----------------------------------------------------------- +Assert::truthy($value, ?string $description=null) .[method] +----------------------------------------------------------- `$value` musí být pravdivý, tedy splní podmínku `if ($value) ...`. -Assert::false($value, string $description=null) .[method] ---------------------------------------------------------- +Assert::false($value, ?string $description=null) .[method] +---------------------------------------------------------- `$value` musí být `false`, tedy `$value === false`. -Assert::falsey($value, string $description=null) .[method] ----------------------------------------------------------- +Assert::falsey($value, ?string $description=null) .[method] +----------------------------------------------------------- `$value` musí být nepravdivý, tedy splní podmínku `if (!$value) ...`. -Assert::null($value, string $description=null) .[method] --------------------------------------------------------- +Assert::null($value, ?string $description=null) .[method] +--------------------------------------------------------- `$value` musí být `null`, tedy `$value === null`. -Assert::notNull($value, string $description=null) .[method] ------------------------------------------------------------ +Assert::notNull($value, ?string $description=null) .[method] +------------------------------------------------------------ `$value` nesmí být `null`, tedy `$value !== null`. -Assert::nan($value, string $description=null) .[method] -------------------------------------------------------- +Assert::nan($value, ?string $description=null) .[method] +-------------------------------------------------------- `$value` musí být Not a Number. Pro testování NAN hodnoty používejte vyhradně `Assert::nan()`. Hodnota NAN je velmi specifická a aserce `Assert::same()` nebo `Assert::equal()` mohou fungovat neočekávaně. -Assert::count($count, Countable|array $value, string $description=null) .[method] ---------------------------------------------------------------------------------- +Assert::count($count, Countable|array $value, ?string $description=null) .[method] +---------------------------------------------------------------------------------- Počet prvků ve `$value` musí být `$count`. Tedy to samé jako `count($value) === $count`. -Assert::type(string|object $type, $value, string $description=null) .[method] ------------------------------------------------------------------------------ +Assert::type(string|object $type, $value, ?string $description=null) .[method] +------------------------------------------------------------------------------ `$value` musí být daného typu. Jako `$type` můžeme použít řetězec: - `array` - `list` - pole indexované podle vzestupné řady numerických klíčů od nuly @@ -127,9 +127,9 @@ Assert::type(string|object $type, $value, string $description=null) .[method] - název třídy nebo přímo objekt, potom musí být `$value instanceof $type` -Assert::exception(callable $callable, string $class, string $message=null, $code=null) .[method] ------------------------------------------------------------------------------------------------- -Při zavolání `$callable` musí být vyhozena výjimka třídy `$class`. Pokud uvedeme `$message`, musí [odpovídat vzoru|#assert-match] i zpráva výjimky a pokud uvedeme `$code`, musí se striktně shodovat i kódy. +Assert::exception(callable $callable, string $class, ?string $message=null, $code=null) .[method] +------------------------------------------------------------------------------------------------- +Při zavolání `$callable` musí být vyhozena výjimka třídy `$class`. Pokud uvedeme `$message`, musí [odpovídat vzoru |#Assert::match] i zpráva výjimky a pokud uvedeme `$code`, musí se striktně shodovat i kódy. Následující test selže, protože neodpovídá zpráva výjimky: @@ -137,7 +137,7 @@ Následující test selže, protože neodpovídá zpráva výjimky: Assert::exception( fn() => throw new App\InvalidValueException('Zero value'), App\InvalidValueException::class, - 'Value is to low', + 'Value is too low', ); ``` @@ -154,9 +154,9 @@ Assert::type(RuntimeException::class, $e->getPrevious()); ``` -Assert::error(string $callable, int|string|array $type, string $message=null) .[method] ---------------------------------------------------------------------------------------- -Kontroluje, že funkce `$callable` vygenerovala očekávané chyby (tj. varování, notices atd). Jako `$type` uvedeme jednu z konstant `E_...`, tedy například `E_WARNING`. A pokud uvedeme `$message`, musí [odpovídat vzoru|#assert-match] i chybová zpráva. Například: +Assert::error(string $callable, int|string|array $type, ?string $message=null) .[method] +---------------------------------------------------------------------------------------- +Kontroluje, že funkce `$callable` vygenerovala očekávané chyby (tj. varování, notices atd). Jako `$type` uvedeme jednu z konstant `E_...`, tedy například `E_WARNING`. A pokud uvedeme `$message`, musí [odpovídat vzoru |#Assert::match] i chybová zpráva. Například: ```php Assert::error( @@ -187,8 +187,8 @@ Assert::noError(callable $callable) .[method] Kontroluje, že funkce `$callable` nevygenerovala žádné varování, chybu nebo výjimku. Hodí se pro testování kousků kódu, kde není žádná další aserce. -Assert::match(string $pattern, $actual, string $description=null) .[method] ---------------------------------------------------------------------------- +Assert::match(string $pattern, $actual, ?string $description=null) .[method] +---------------------------------------------------------------------------- `$actual` musí vyhovět vzoru `$pattern`. Můžeme použít dvě varianty vzorů: regulární výrazy nebo zástupné znaky. Pokud jako `$pattern` předáme regulární výraz, k jeho ohraničení musíme použít `~` nebo `#`, jiné oddělovače nejsou podporovány. Například test, kdy `$var` musí obsahovat pouze hexadecimální číslice: @@ -227,9 +227,14 @@ Assert::match('Error in file %a% on line %i%', $errorMessage); ``` -Assert::matchFile(string $file, $actual, string $description=null) .[method] ----------------------------------------------------------------------------- -Aserce je totožná s [Assert::match() |#assert-match], ale vzor se načítá ze souboru `$file`. To je užitečné pro testování velmi dlouhých řetězců. Soubor s testem zůstane přehledný. +Assert::notMatch(string $pattern, $actual, ?string $description=null) .[method]{data-version:2.5.6} +--------------------------------------------------------------------------------------------------- +Opak `Assert::match()`. + + +Assert::matchFile(string $file, $actual, ?string $description=null) .[method] +----------------------------------------------------------------------------- +Aserce je totožná s [#Assert::match()], ale vzor se načítá ze souboru `$file`. To je užitečné pro testování velmi dlouhých řetězců. Soubor s testem zůstane přehledný. Assert::fail(string $message, $actual=null, $expected=null) .[method] diff --git a/tester/cs/guide.texy b/tester/cs/guide.texy index 4b7e290a36..bc75c64ec9 100644 --- a/tester/cs/guide.texy +++ b/tester/cs/guide.texy @@ -116,10 +116,9 @@ Výstup může vypadat takto: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.6.0 -Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +PHP 8.5.2 (cli) | php | 8 threads ........s................<span style="color: #FFF; background-color: #900">F</span>......... @@ -144,7 +143,7 @@ Refaktorujete kód? Nebo dokonce vyvíjíte podle metodiky TDD (Test Driven Deve Při vývoji tak máte v rohu monitoru terminál, kde na vás svítí zelený stavový řádek, a když se náhle změní na červený, víte, že jste právě něco neudělali úplně dobře. Je to vlastně skvělá hra, kdy programujete a snažíte se držet barvu. -Watch režim se spouští parametrem [--watch|running-tests#w-watch-path]. +Watch režim se spouští parametrem [--watch |running-tests#-w --watch path]. CodeCoverage reporty @@ -152,7 +151,7 @@ CodeCoverage reporty Tester umí generovat reporty s přehledem, kolik zdrojového kódu testy pokrývají. Report může být buď v lidsky čitelném formátu HTML, nebo Clover XML pro další strojové zpracování. -Podívejte se na "ukázku HTML reportu":https://files.nette.org/tester/coverage.html s pokrytím kódu. +Podívejte se na "ukázku HTML reportu":attachment:coverage.html s pokrytím kódu. Podporované verze PHP @@ -160,7 +159,8 @@ Podporované verze PHP | verze | kompatibilní s PHP |------------------|------------------- -| Tester 2.5 | PHP 8.0 – 8.2 +| Tester 2.6 | PHP 8.0 – 8.5 +| Tester 2.5 | PHP 8.0 – 8.5 | Tester 2.4 | PHP 7.2 – 8.2 | Tester 2.3 | PHP 7.1 – 8.0 | Tester 2.1 – 2.2 | PHP 7.1 – 7.3 diff --git a/tester/cs/helpers.texy b/tester/cs/helpers.texy index 2677074240..df4c28e9b8 100644 --- a/tester/cs/helpers.texy +++ b/tester/cs/helpers.texy @@ -2,21 +2,116 @@ Pomocné třídy ************* -DomQuery --------- -`Tester\DomQuery` je třída rozšiřující `SimpleXMLElement` o metody usnadňující testování obsahu HTML nebo XML. +HttpAssert .{data-version:2.5.6} +-------------------------------- +Třída `Tester\HttpAssert` slouží k testování HTTP serveru. Umožňuje jednoduše provádět HTTP požadavky a ověřovat jejich stavové kódy, hlavičky i obsah odpovědi pomocí fluent interface. + +```php +# Základní HTTP požadavek a ověření odpovědi +$response = Tester\HttpAssert::fetch('https://example.com/api/users'); +$response + ->expectCode(200) + ->expectHeader('Content-Type', contains: 'json') + ->expectBody(contains: 'users'); +``` + +Metoda `fetch()` standardně vytváří GET požadavek, ale všechny parametry lze přizpůsobit: + +```php +HttpAssert::fetch( + 'https://api.example.com/users', + method: 'POST', + headers: [ + 'Authorization' => 'Bearer token123', # asociativní pole + 'Accept: application/json', # nebo string formát + ], + cookies: ['session' => 'abc123'], + follow: false, # nesledovat redirecty + body: '{"name": "John"}' +) + ->expectCode(201); +``` + +Stavové kódy můžete ověřovat metodami `expectCode()` a `denyCode()`. Jako parametr předáte buď konkrétní číslo, nebo validační funkci: ```php -# mějme v $html HTML dokument, který načteme -$dom = Tester\DomQuery::fromHtml($html); +$response + ->expectCode(200) # přesný kód + ->expectCode(fn($code) => $code < 400) # vlastní validace + ->denyCode(404) # nesmí být 404 + ->denyCode(fn($code) => $code >= 500); # nesmí být chyba serveru +``` -# můžeme testovat přítomnost elementů podle CSS selektorů -Assert::true($dom->has('form#registration')); -Assert::true($dom->has('input[name="username"]')); -Assert::true($dom->has('input[type="submit"]')); +Pro práci s hlavičkami slouží metody `expectHeader()` a `denyHeader()`. Můžete kontrolovat existenci hlavičky, její přesnou hodnotu nebo jen část obsahu: + +```php +$response + ->expectHeader('Content-Type') # hlavička musí existovat + ->expectHeader('Content-Type', 'application/json') # přesná hodnota + ->expectHeader('Content-Type', contains: 'json') # obsahuje text + ->expectHeader('Server', matches: 'nginx %a%') # odpovídá vzoru + ->denyHeader('X-Powered-By') # hlavička nesmí existovat + ->denyHeader('X-Debug', contains: 'sensitive') # nesmí obsahovat text + ->denyHeader('X-Debug', matches: '~debug~i'); # nesmí odpovídat vzoru +``` -# nebo vybrat elementy jako pole DomQuery -$elems = $dom->find('input[data-autocomplete]'); +Obdobně funguje ověřování těla odpovědi prostřednictvím metod `expectBody()` a `denyBody()`: + +```php +$response + ->expectBody('OK') # přesná hodnota + ->expectBody(contains: '"status": "success"') # obsahuje část JSON + ->expectBody(matches: '%A% hello %A%') # odpovídá vzoru + ->expectBody(fn($body) => json_decode($body)) # vlastní validace + ->denyBody('Error occurred') # nesmí mít přesnou hodnotu + ->denyBody(contains: 'error') # nesmí obsahovat text + ->denyBody(matches: '~exception|fatal~i'); # nesmí odpovídat vzoru +``` + +Parametr `follow` řídí, jak HttpAssert zachází s HTTP přesměrováními: + +```php +# Testování redirectu bez následování (výchozí) +HttpAssert::fetch('https://example.com/redirect', follow: false) + ->expectCode(301) + ->expectHeader('Location', 'https://example.com/new-url'); + +# Následování všech redirectů až do konečné odpovědi +HttpAssert::fetch('https://example.com/redirect', follow: true) + ->expectCode(200) + ->expectBody(contains: 'final content'); +``` + + +DomQuery +-------- +`Tester\DomQuery` je třída rozšiřující `SimpleXMLElement` o snadné vyhledávání v HTML nebo XML pomocí CSS selektorů. + +```php +# vytvoření DomQuery z HTML řetězce +$dom = Tester\DomQuery::fromHtml(' + <article class="post"> + <h1>Title</h1> + <div class="content">Text</div> + </article> +'); + +# test existence elementů pomocí CSS selektorů +Assert::true($dom->has('article.post')); +Assert::true($dom->has('h1')); + +# nalezení elementů jako pole DomQuery objektů +$headings = $dom->find('h1'); +Assert::same('Title', (string) $headings[0]); + +# test, zda element odpovídá selektoru (od verze 2.5.3) +$content = $dom->find('.content')[0]; +Assert::true($content->matches('div')); +Assert::false($content->matches('p')); + +# nalezení nejbližšího předka odpovídajícího selektoru (od 2.5.5) +$article = $content->closest('.post'); +Assert::true($article->matches('article')); ``` @@ -119,7 +214,7 @@ Environment::setup() .[filter] Environment::setupFunctions() .[filter]{data-version:2.5} --------------------------------------------------------- -Vytvoří globální funkce `test()`, `setUp()` a `tearDown()`, do kterých můžete členit testy. +Vytvoří globální funkce `test()`, `testException()`, `setUp()` a `tearDown()`, do kterých můžete členit testy. ```php test('popis testu', function () { diff --git a/tester/cs/running-tests.texy b/tester/cs/running-tests.texy index ee894e59c7..667d8253de 100644 --- a/tester/cs/running-tests.texy +++ b/tester/cs/running-tests.texy @@ -23,10 +23,9 @@ Výstup může vypadat třeba takto: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.6.0 -Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +PHP 8.5.2 (cli) | php | 8 threads ........s.......................... @@ -37,9 +36,6 @@ Při opakovaném spuštění nejprve provádí testy, které při předchozím b Pokud žádný test neselže, návratový kód Testeru je nula. Jinak je návratový kód nenulový. -.[warning] -Tester spouští PHP procesy bez `php.ini`. Detailněji v části [#Vlastní php.ini]. - Parametry příkazové řádky ========================= @@ -49,25 +45,25 @@ Přehled všech voleb příkazové řádky získáme spuštěním Testeru bez pa /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Usage: tester [options] [<test file> | <directory>]... Options: -p <path> Specify PHP interpreter to run (default: php). - -c <path> Look for php.ini file (or look in directory) <path>. - -C Use system-wide php.ini. - -l | --log <path> Write log to file <path>. + -c <path> Use custom php.ini, ignore system configuration. + -C With -c, include system configuration as well. -d <key=value>... Define INI entry 'key' with value 'value'. -s Show information about skipped tests. --stop-on-fail Stop execution upon the first failure. -j <num> Run <num> jobs in parallel (default: 8). - -o <console|tap|junit|none> Specify output format. + -o <console|console-lines|tap|junit|log|none> (e.g. -o junit:output.xml) + Specify one or more output formats with optional file name. -w | --watch <path> Watch directory. -i | --info Show tests environment info and exit. --setup <path> Script for runner setup. - --temp <path> Path to temporary directory. Default: sys_get_temp_dir(). + --temp <path> Path to temporary directory. Default by sys_get_temp_dir(). --colors [1|0] Enable or disable colors. --coverage <path> Generate code coverage report to file. --coverage-src <path> Path to source code. @@ -86,21 +82,12 @@ tester -p /home/user/php-7.2.0-beta/php-cgi tests -c <path> .[filter] ------------------- -Určuje, který `php.ini` se bude používat při spouštění testů. Ve výchozím stavu se žádný php.ini nepoužije. Více v části [#Vlastní php.ini]. +Použije vlastní `php.ini` soubor a ignoruje systémovou konfiguraci. Více v části [#Vlastní php.ini]. -C .[filter] ------------ -Použije se systémové `php.ini`. Na UNIXu také všechny příslušné INI soubory `/etc/php/{sapi}/conf.d/*.ini`. Více v části [#Vlastní php.ini]. - - -''-l | --log <path>'' .[filter] -------------------------------- -Do uvedeného souboru bude zapsán průběh testování. Všechny selhané, přeskočené, ale i úspěšné testy: - -/--pre .[terminal] -tester --log /var/log/tests.log tests -\-- +Při použití společně s `-c` zachová i systémovou konfiguraci (neignoruje ji). Více v části [#Vlastní php.ini]. -d <key=value> .[filter] @@ -127,13 +114,15 @@ Tester zastaví testování u prvního selhávajícího testu. Určuje, kolik paralelních procesů s testy se spustí. Výchozí hodnota je 8. Chceme-li, aby všechny testy proběhly v sérii, použijeme hodnotu 1. --o <console|tap|junit|none> .[filter] -------------------------------------- -Nastaví formát výstupu. Výchozí je formát pro konzoli. +-o <console|console-lines|tap|junit|log|none> .[filter] +------------------------------------------------------- +Nastaví formát výstupu. Výchozí je formát pro konzoli. Můžete uvést jméno souboru, do kterého se výstup zapíše (např `-o junit:output.xml`). Volbu `-o` lze zopakovat vícekrát a vygenerovat tak více formátů najednou. - `console`: shodné s výchozím formátem, ale v tomto případě se nezobrazí ASCII logo +- `console-lines`: podobné jako console, ale výsledek každého testu je uveden na samostatném řádku s dalšími informacemi - `tap`: [TAP formát |https://en.wikipedia.org/wiki/Test_Anything_Protocol] vhodný pro strojové zpracování - `junit`: XML formát JUnit, taktéž vhodný pro strojové zpracování +- `log`: Výstupy průběhu testování. Všechny neúspěšné, přeskočené a také úspěšné testy - `none`: nic se nevypisuje @@ -218,7 +207,7 @@ Priorita výběru mechanismu je následující: Při použití PHPDBG můžeme u obsáhlých testů narazit na selhání testu kvůli vyčerpání paměti. Sběr informací o pokrytém kódu je paměťově náročný. V tomto případě nám pomůže volání `Tester\CodeCoverage\Collector::flush()` uvnitř testu. Zapíše nasbíraná data na disk a paměť uvolní. Pokud sběr dat neprobíhá, nebo je použit Xdebug, volání nemá žádný efekt. -"Ukázka HTML reportu":https://files.nette.org/tester/coverage.html s pokrytím kódu. +"Ukázka HTML reportu":attachment:coverage.html s pokrytím kódu. --coverage-src <path> .[filter] @@ -228,11 +217,7 @@ Použijeme současně s volbou `--coverage`. `<path>` je cesta ke zdrojovým kó Vlastní php.ini =============== -Tester spouští PHP procesy s parametrem `-n`, což znamená, že žádné `php.ini` není načteno. V UNIXu ani ty z `/etc/php/conf.d/*.ini`. To zajistí shodné prostředí pro běh testů, ale také vyřadí všechna PHP rozšíření běžně načtená systémovým PHP. - -Chcete-li načítání systémových php.ini souborů zachovat, použijte parametr `-C`. - -Pokud nějaká rozšíření nebo speciální INI nastavení pro testy potřebujete, doporučujeme vytvoření vlastního `php.ini` souboru, který bude distribuován s testy. Tester pak spouštíme s parametrem `-c`, například `tester -c tests/php.ini tests`, kde INI soubor může vypadat takto: +Pro testy můžete použít vlastní `php.ini` soubor. Pokud potřebujete specifická rozšíření nebo speciální INI nastavení, doporučujeme vytvořit vlastní `php.ini`, který bude distribuován s testy. Tester pak spouštíme s parametrem `-c`, například `tester -c tests/php.ini tests`, kde INI soubor může vypadat takto: ```ini [PHP] @@ -243,4 +228,7 @@ extension=php_pdo_pgsql.dll memory_limit=512M ``` -Spuštění Testeru v UNIXu se systémovým `php.ini`, například `tester -c /etc/php/cli/php.ini` nenačte ostatní INI z `/etc/php/conf.d/*.ini`. To je vlastnost PHP, ne Testeru. +Při použití `-c` Tester **ignoruje systémovou konfiguraci** (spouští PHP s příznakem `-n`). Chcete-li zachovat i systémovou konfiguraci, přidejte volbu `-C`: `tester -c tests/php.ini -C tests`. Ani při kombinaci `-c` a `-C` se nenačtou v UNIXu ostatní INI soubory z `/etc/php/conf.d/*.ini`. To je vlastnost PHP, ne Testeru. + +.[note] +Do verze 2.6 Tester bez uvedení `-c` spouštěl PHP s parametrem `-n`, tedy bez php.ini; volba `-C` toto potlačila. Od verze 2.6 se systémové php.ini načítá automaticky. Chování při použití `-c` zůstává stejné. diff --git a/tester/cs/test-annotations.texy b/tester/cs/test-annotations.texy index 369d3d3f96..95e319fe93 100644 --- a/tester/cs/test-annotations.texy +++ b/tester/cs/test-annotations.texy @@ -58,7 +58,7 @@ Test se přeskočí, pokud nejsou načtena všechna uvedená PHP rozšíření. @dataProvider .[filter] ----------------------- -Chceme-li testovací soubor spustit vícekrát, ale s jinými vstupními daty, hodí se právě tato anotace. (Nezaměňujte se stejnojmennou anotací pro [TestCase|TestCase#dataProvider].) +Chceme-li testovací soubor spustit vícekrát, ale s jinými vstupními daty, hodí se právě tato anotace. (Nezaměňujte se stejnojmennou anotací pro [TestCase |TestCase#dataProvider].) Zapisujeme jako `@dataProvider file.ini`, cesta k souboru se bere relativně k souboru s testem. Test bude spuštěn tolikrát, kolik je sekcí v INI souboru. Předpokládejme INI soubor `databases.ini`: diff --git a/tester/cs/testcase.texy b/tester/cs/testcase.texy index 9f28f05b12..64899d8a13 100644 --- a/tester/cs/testcase.texy +++ b/tester/cs/testcase.texy @@ -74,7 +74,7 @@ tearDown() Pokud dojde k chybě v `setUp()` nebo `tearDown()` fázi, test celkově selže. Pokud dojde k chybě v testovací metodě, i přes to se metoda `tearDown()` spustí, avšak s potlačením chyb v ní. -Doporučujeme na začátek testu napsat anotaci [@testCase|test-annotations#@testCase], potom bude spouštěč testů z příkazové řádky pouštět jednotlivé metody testcase v samostatných procesech a paralelně ve více vláknech. To může výrazně urychlit celý proces testování. +Doporučujeme na začátek testu napsat anotaci [@testCase |test-annotations#testCase], potom bude spouštěč testů z příkazové řádky pouštět jednotlivé metody testcase v samostatných procesech a paralelně ve více vláknech. To může výrazně urychlit celý proces testování. /--php <?php @@ -114,7 +114,7 @@ public function testTwo() @dataProvider .[filter] ----------------------- -Chceme-li testovací metodu spustit vícekrát, ale s jinými parametry, hodí se právě tato anotace. (Nezaměňujte se stejnojmennou anotací pro [soubory|test-annotations#dataProvider].) +Chceme-li testovací metodu spustit vícekrát, ale s jinými parametry, hodí se právě tato anotace. (Nezaměňujte se stejnojmennou anotací pro [soubory |test-annotations#dataProvider].) Za ní uvedeme název metody, která vrací argumenty pro testovací metodu. Metoda musí vrátit pole nebo Traversable. Jednoduchý příklad: diff --git a/tester/cs/writing-tests.texy b/tester/cs/writing-tests.texy index 7d36cb195b..04d0616d14 100644 --- a/tester/cs/writing-tests.texy +++ b/tester/cs/writing-tests.texy @@ -2,12 +2,11 @@ Psaní testů *********** .[perex] -Psaní testů pro Nette Tester je unikátní v tom, že každý test je PHP skript, který lze samostatně spustit. To ukrývá velký potenciál. -Už když test píšete, můžete jej jednoduše spouštět a zjišťovat, jestli funguje správně. Pokud ne, lze jej snadno krokovat v IDE a hledat chybu. +Psaní testů pro Nette Tester je unikátní v tom, že každý test je PHP skript, který lze samostatně spustit. To ukrývá velký potenciál. Už když test píšete, můžete jej jednoduše spouštět a zjišťovat, jestli funguje správně. Pokud ne, lze jej snadno krokovat v IDE a hledat chybu. Test můžete dokonce otevřít v prohlížeči. Ale především - tím, že jej spustíte, tak test vykonáte. Okamžitě zjistíte, jestli prošel, nebo selhal. -V úvodní kapitole jsme si [ukázali|guide#Čím je Tester unikátní?] opravdu triviální test práce s polem. Teď už si vytvoříme vlastní třídu, kterou budeme testovat, byť bude také jednoduchá. +V úvodní kapitole jsme si [ukázali |guide#Čím je Tester unikátní] opravdu triviální test práce s polem. Teď už si vytvoříme vlastní třídu, kterou budeme testovat, byť bude také jednoduchá. Začneme od typické adresářové struktury pro knihovnu nebo projekt. Důležité je oddělit testy od zbytku kódu, například kvůli deploymentu, protože testy na ostrý server nahrávat nechceme. Struktura může být třeba taková: @@ -84,7 +83,7 @@ define('TmpDir', '/tmp/app-tests'); ``` .[note] -Uvedený bootstrap předpokládá, že autoloader Composeru bude schopný načíst i třídu `Rectangle.php`. Toho lze docílit například [nastavením sekce autoload|best-practices:composer#autoloading] v `composer.json` apod. +Uvedený bootstrap předpokládá, že autoloader Composeru bude schopný načíst i třídu `Rectangle.php`. Toho lze docílit například [nastavením sekce autoload |best-practices:composer#Autoloading] v `composer.json` apod. Test můžeme nyní spustit z příkazové řádky jako jakýkoliv jiný samostatný PHP skript. První spuštění nám odhalí případné syntaktické chyby a pokud nikde není překlep, vypíše se: @@ -109,7 +108,7 @@ $ php RectangleTest.php Při psaní testů je dobré podchytit všechny krajní situace. Například když bude vstupem nula, záporné číslo, v jiných případech třeba prázdný řetězec, null atd. Vlastně vás to nutí zamyslet se a rozhodnout, jak se v takových situacích má kód chovat. Testy potom chování zafixují. -V našem případě má záporná hodnota vyhodit výjimku, což ověříme pomocí [Assert::exception()|Assertions#Assert::exception]: +V našem případě má záporná hodnota vyhodit výjimku, což ověříme pomocí [Assert::exception() |Assertions#Assert::exception]: ```php .{file:tests/RectangleTest.php} // šířka nesmí být záporná diff --git a/tester/de/@home.texy b/tester/de/@home.texy index 1ba259a79e..1b4439ca96 100644 --- a/tester/de/@home.texy +++ b/tester/de/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Nette Tester - Unterhaltsame Unit-Tests in PHP}} -{{description: Nette Tester ist ein einfaches und dennoch sehr praktisches Tool zum Testen von PHP-Code.}} +{{maintitle: Nette Tester – entspanntes Testen in PHP}} +{{description: Nette Tester ist ein einfaches und dennoch sehr cleveres Werkzeug zum Testen von PHP-Code.}} diff --git a/tester/de/@left-menu.texy b/tester/de/@left-menu.texy index 56a4aec2b6..892490dd9c 100644 --- a/tester/de/@left-menu.texy +++ b/tester/de/@left-menu.texy @@ -1,8 +1,8 @@ -- [Erste Schritte |guide] -- [Tests schreiben |Writing Tests] -- [Tests ausführen |Running Tests] +- [Einstieg in Nette Tester |guide] +- [Tests schreiben |writing-tests] +- [Tests ausführen |running-tests] -- [Behauptungen |Assertions] -- [Test-Anmerkungen |Test Annotations] -- [Testfall |TestCase] -- [Helfer |Helpers] +- [Assertions |assertions] +- [Test-Annotationen |test-annotations] +- [TestCase |TestCase] +- [Hilfsklassen |helpers] diff --git a/tester/de/@menu.texy b/tester/de/@menu.texy index 5f2d3dfb99..46dff80192 100644 --- a/tester/de/@menu.texy +++ b/tester/de/@menu.texy @@ -1,3 +1,3 @@ -- [Startseite |@home] -- [Dokumentation |Guide] +- [Einführung |@home] +- [Dokumentation |guide] - "GitHub .[link-external]":https://github.com/nette/tester diff --git a/tester/de/@meta.texy b/tester/de/@meta.texy new file mode 100644 index 0000000000..9b41511f5e --- /dev/null +++ b/tester/de/@meta.texy @@ -0,0 +1 @@ +{{sitename: Tester Dokumentation}} diff --git a/tester/de/assertions.texy b/tester/de/assertions.texy index 18e2daa58c..5ab13b4957 100644 --- a/tester/de/assertions.texy +++ b/tester/de/assertions.texy @@ -1,35 +1,35 @@ -Behauptungen -************ +Assertions (Zusicherungen) +************************** .[perex] -Aassertions werden verwendet, um zu behaupten, dass ein tatsächlicher Wert mit einem erwarteten Wert übereinstimmt. Sie sind Methoden des `Tester\Assert`. +Assertions werden verwendet, um zu bestätigen, dass ein tatsächlicher Wert einem erwarteten Wert entspricht. Dies sind Methoden der Klasse `Tester\Assert`. -Wählen Sie die genauesten Behauptungen. `Assert::same($a, $b)` ist besser als `Assert::true($a === $b)`, weil es bei Fehlern eine sinnvolle Fehlermeldung anzeigt. Im zweiten Fall erhalten wir nur `false should be true` und es sagt nichts über den Inhalt der Variablen $a und $b aus. +Wählen Sie die am besten geeigneten Assertions. Es ist besser, `Assert::same($a, $b)` zu verwenden als `Assert::true($a === $b)`, da im Fehlerfall eine aussagekräftige Fehlermeldung angezeigt wird. Im zweiten Fall nur `false should be true`, was uns nichts über den Inhalt der Variablen `$a` und `$b` sagt. -Die meisten Assertions können auch eine optionale `$description` haben, die in der Fehlermeldung erscheint, wenn die Erwartung fehlschlägt. +Die meisten Assertions können auch eine optionale Beschreibung im Parameter `$description` haben, die in der Fehlermeldung angezeigt wird, wenn die Erwartung fehlschlägt. -Beispiele nehmen an, dass der folgende Klassenalias definiert ist: +Die Beispiele setzen voraus, dass ein Alias erstellt wurde: ```php use Tester\Assert; ``` -Assert::same($expected, $actual, string $description=null) .[method] --------------------------------------------------------------------- -`$expected` muss mit `$actual` identisch sein. Er ist identisch mit dem PHP-Operator `===`. +Assert::same($expected, $actual, ?string $description=null) .[method] +--------------------------------------------------------------------- +`$expected` muss identisch mit `$actual` sein. Dasselbe wie der PHP-Operator `===`. -Assert::notSame($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------ +Assert::notSame($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------ Das Gegenteil von `Assert::same()`, also dasselbe wie der PHP-Operator `!==`. -Assert::equal($expected, $actual, string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] ------------------------------------------------------------------------------------------------------------------------- -`$expected` muss mit `$actual` identisch sein. Im Gegensatz zu `Assert::same()` werden Objektidentität, Reihenfolge von Schlüsselpaaren => Wert in Arrays und geringfügig unterschiedliche Dezimalzahlen ignoriert, was durch Setzen von `$matchIdentity` und `$matchOrder` geändert werden kann. +Assert::equal($expected, $actual, ?string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] +------------------------------------------------------------------------------------------------------------------------- +`$expected` muss gleich `$actual` sein. Im Gegensatz zu `Assert::same()` werden die Identität von Objekten, die Reihenfolge von Schlüssel-Wert-Paaren in Arrays und geringfügig unterschiedliche Dezimalzahlen ignoriert, was durch Setzen von `$matchIdentity` und `$matchOrder` geändert werden kann. -Die folgenden Fälle sind aus der Sicht von `equal()` identisch, aber nicht für `same()`: +Die folgenden Fälle sind aus Sicht von `equal()` gleich, aber nicht aus Sicht von `same()`: ```php Assert::equal(0.3, 0.1 + 0.2); @@ -40,81 +40,81 @@ Assert::equal( ); ``` -Beachten Sie jedoch, dass das Array `[1, 2]` und `[2, 1]` sind nicht gleich, da sich nur die Reihenfolge der Werte unterscheidet, nicht aber die Schlüssel => Wertpaare. Das Array `[1, 2]` kann auch geschrieben werden als `[0 => 1, 1 => 2]` geschrieben werden und daher `[1 => 2, 0 => 1]` als gleich betrachtet werden. +Aber Vorsicht, die Arrays `[1, 2]` und `[2, 1]` sind nicht gleich, da sie sich nur in der Reihenfolge der Werte unterscheiden, nicht der Schlüssel-Wert-Paare. Das Array `[1, 2]` kann auch als `[0 => 1, 1 => 2]` geschrieben werden, daher wird `[1 => 2, 0 => 1]` als gleich betrachtet. -Sie können auch die sogenannten [Erwartungen |#expectations] in `$expected` verwenden. +Weiterhin kann in `$expected` die sogenannte [Erwartung (Expectation) |#Erwartungen] verwendet werden. -Assert::notEqual($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------- -Im Gegensatz zu `Assert::equal()`. +Assert::notEqual($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------- +Das Gegenteil von `Assert::equal()`. -Assert::contains($needle, string|array $actual, string $description=null) .[method] ------------------------------------------------------------------------------------ -Wenn `$actual` eine Zeichenkette ist, muss sie die Teilzeichenkette `$needle` enthalten. Wenn es sich um ein Array handelt, muss es das Element `$needle` enthalten (es wird streng verglichen). +Assert::contains($needle, string|array $actual, ?string $description=null) .[method] +------------------------------------------------------------------------------------ +Wenn `$actual` eine Zeichenkette ist, muss sie die Teilzeichenkette `$needle` enthalten. Wenn es ein Array ist, muss es das Element `$needle` enthalten (strikt verglichen mit `===`). -Assert::notContains($needle, string|array $actual, string $description=null) .[method] --------------------------------------------------------------------------------------- -Im Gegensatz zu `Assert::contains()`. +Assert::notContains($needle, string|array $actual, ?string $description=null) .[method] +--------------------------------------------------------------------------------------- +Das Gegenteil von `Assert::contains()`. -Assert::hasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} -------------------------------------------------------------------------------------------------------- +Assert::hasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +-------------------------------------------------------------------------------------------------------- `$actual` muss ein Array sein und den Schlüssel `$needle` enthalten. -Assert::notHasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} ----------------------------------------------------------------------------------------------------------- +Assert::notHasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +----------------------------------------------------------------------------------------------------------- `$actual` muss ein Array sein und darf den Schlüssel `$needle` nicht enthalten. -Assert::true($value, string $description=null) .[method] --------------------------------------------------------- +Assert::true($value, ?string $description=null) .[method] +--------------------------------------------------------- `$value` muss `true` sein, also `$value === true`. -Assert::truthy($value, string $description=null) .[method] ----------------------------------------------------------- -`$value` muss wahrheitsgemäß sein, also erfüllt es die Bedingung `if ($value) ...`. +Assert::truthy($value, ?string $description=null) .[method] +----------------------------------------------------------- +`$value` muss wahrheitsgemäß sein, also die Bedingung `if ($value) ...` erfüllen. -Assert::false($value, string $description=null) .[method] ---------------------------------------------------------- +Assert::false($value, ?string $description=null) .[method] +---------------------------------------------------------- `$value` muss `false` sein, also `$value === false`. -Assert::falsey($value, string $description=null) .[method] ----------------------------------------------------------- -`$value` muss falsch sein, also erfüllt es die Bedingung `if (!$value) ...`. +Assert::falsey($value, ?string $description=null) .[method] +----------------------------------------------------------- +`$value` muss falsch sein, also die Bedingung `if (!$value) ...` erfüllen. -Assert::null($value, string $description=null) .[method] --------------------------------------------------------- +Assert::null($value, ?string $description=null) .[method] +--------------------------------------------------------- `$value` muss `null` sein, also `$value === null`. -Assert::notNull($value, string $description=null) .[method] ------------------------------------------------------------ +Assert::notNull($value, ?string $description=null) .[method] +------------------------------------------------------------ `$value` darf nicht `null` sein, also `$value !== null`. -Assert::nan($value, string $description=null) .[method] -------------------------------------------------------- -`$value` muss Not a Number sein. Verwenden Sie für NAN-Tests nur die `Assert::nan()`. Der NAN-Wert ist sehr spezifisch und die Assertions `Assert::same()` oder `Assert::equal()` können sich unvorhersehbar verhalten. +Assert::nan($value, ?string $description=null) .[method] +-------------------------------------------------------- +`$value` muss Not a Number sein. Zum Testen von NAN-Werten verwenden Sie ausschließlich `Assert::nan()`. Der NAN-Wert ist sehr spezifisch und die Assertions `Assert::same()` oder `Assert::equal()` können unerwartet funktionieren. -Assert::count($count, Countable|array $value, string $description=null) .[method] ---------------------------------------------------------------------------------- +Assert::count($count, Countable|array $value, ?string $description=null) .[method] +---------------------------------------------------------------------------------- Die Anzahl der Elemente in `$value` muss `$count` sein. Also dasselbe wie `count($value) === $count`. -Assert::type(string|object $type, $value, string $description=null) .[method] ------------------------------------------------------------------------------ -`$value` muss von einem bestimmten Typ sein. Als `$type` können wir String verwenden: +Assert::type(string|object $type, $value, ?string $description=null) .[method] +------------------------------------------------------------------------------ +`$value` muss vom angegebenen Typ sein. Als `$type` können wir eine Zeichenkette verwenden: - `array` -- `list` - Array, das in aufsteigender Reihenfolge der numerischen Schlüssel von Null an indiziert ist +- `list` - Array, das nach einer aufsteigenden Reihe numerischer Schlüssel ab Null indiziert ist - `bool` - `callable` - `float` @@ -124,24 +124,24 @@ Assert::type(string|object $type, $value, string $description=null) .[method] - `resource` - `scalar` - `string` -- Klassenname oder Objekt direkt, dann müssen `$value instanceof $type` +- Klassenname oder direkt Objekt, dann muss `$value instanceof $type` sein -Assert::exception(callable $callable, string $class, string $message=null, $code=null) .[method] ------------------------------------------------------------------------------------------------- -Beim Aufruf von `$callable` muss eine Ausnahme der Instanz `$class` ausgelöst werden. Wenn wir `$message` übergeben, muss die Nachricht der Ausnahme [übereinstimmen |#assert-match]. Und wenn wir `$code` übergeben, muss der Code der Ausnahme derselbe sein. +Assert::exception(callable $callable, string $class, ?string $message=null, $code=null) .[method] +------------------------------------------------------------------------------------------------- +Beim Aufruf von `$callable` muss eine Ausnahme der Klasse `$class` geworfen werden. Wenn wir `$message` angeben, muss auch die Ausnahmemeldung [dem Muster entsprechen |#Assert::match], und wenn wir `$code` angeben, müssen auch die Codes strikt übereinstimmen (`===`). -Dieser Test schlägt zum Beispiel fehl, weil die Meldung der Ausnahme nicht übereinstimmt: +Der folgende Test schlägt fehl, da die Ausnahmemeldung nicht übereinstimmt: ```php Assert::exception( fn() => throw new App\InvalidValueException('Zero value'), App\InvalidValueException::class, - 'Value is to low', + 'Value is too low', // Erwartete Nachricht stimmt nicht überein ); ``` -Die `Assert::exception()` gibt eine ausgelöste Ausnahme zurück, so dass Sie eine verschachtelte Ausnahme testen können. +`Assert::exception()` gibt die geworfene Ausnahme zurück, sodass auch eine verschachtelte Ausnahme getestet werden kann. ```php $e = Assert::exception( @@ -154,9 +154,9 @@ Assert::type(RuntimeException::class, $e->getPrevious()); ``` -Assert::error(string $callable, int|string|array $type, string $message=null) .[method] ---------------------------------------------------------------------------------------- -Überprüft, ob der `$callable` -Aufruf die erwarteten Fehler erzeugt (d.h. Warnungen, Hinweise usw.). Als `$type` geben wir eine der Konstanten `E_...` an, zum Beispiel `E_WARNING`. Und wenn wir `$message` übergeben, muss die Fehlermeldung auch [dem |#assert-match] Muster [entsprechen |#assert-match]. Zum Beispiel: +Assert::error(callable $callable, int|string|array $type, ?string $message=null) .[method] +------------------------------------------------------------------------------------------ +Überprüft, ob die Funktion `$callable` die erwarteten PHP-Fehler (d. h. Warnungen, Hinweise usw.) generiert hat. Als `$type` geben wir eine der `E_...`-Konstanten an, z. B. `E_WARNING`. Und wenn wir `$message` angeben, muss auch die Fehlermeldung [dem Muster entsprechen |#Assert::match]. Zum Beispiel: ```php Assert::error( @@ -166,7 +166,7 @@ Assert::error( ); ``` -Wenn der Rückruf mehrere Fehler erzeugt, müssen wir alle in der genauen Reihenfolge erwarten. In diesem Fall übergeben wir das Array in `$type`: +Wenn der Callback mehrere Fehler generiert, müssen wir sie alle in der genauen Reihenfolge erwarten. In diesem Fall übergeben wir in `$type` ein Array: ```php Assert::error(function () { @@ -179,108 +179,108 @@ Assert::error(function () { ``` .[note] -Wenn `$type` ein Klassenname ist, verhält sich diese Assertion genauso wie `Assert::exception()`. +Wenn Sie als `$type` einen Klassennamen angeben, verhält es sich wie `Assert::exception()`. Assert::noError(callable $callable) .[method] --------------------------------------------- -Überprüft, dass die Funktion `$callable` keine PHP-Warnung/Hinweis/Fehler oder Ausnahme auslöst. Sie ist nützlich, um ein Stück Code zu testen, für das es keine andere Assertion gibt. +Überprüft, ob die Funktion `$callable` keine PHP-Warnung, keinen Fehler oder keine Ausnahme generiert hat. Eignet sich zum Testen von Codeabschnitten, in denen keine weitere Assertion vorhanden ist. -Assert::match(string $pattern, $actual, string $description=null) .[method] ---------------------------------------------------------------------------- -`$actual` muss mit `$pattern` übereinstimmen. Wir können zwei Varianten von Mustern verwenden: reguläre Ausdrücke oder Wildcards. +Assert::match(string $pattern, $actual, ?string $description=null) .[method] +---------------------------------------------------------------------------- +`$actual` muss dem Muster `$pattern` entsprechen. Wir können zwei Varianten von Mustern verwenden: reguläre Ausdrücke oder Platzhalter. -Wenn wir einen regulären Ausdruck als `$pattern` übergeben, müssen wir `~` or `#` verwenden, um ihn abzugrenzen. Andere Begrenzungszeichen werden nicht unterstützt. Zum Beispiel test, bei dem `$var` nur hexadezimale Ziffern enthalten darf: +Wenn wir als `$pattern` einen regulären Ausdruck übergeben, müssen wir ihn mit `~` oder `#` begrenzen, andere Trennzeichen werden nicht unterstützt. Zum Beispiel ein Test, bei dem `$var` nur hexadezimale Ziffern enthalten darf: ```php Assert::match('#^[0-9a-f]$#i', $var); ``` -Die andere Variante ähnelt dem Vergleich von Zeichenketten, aber wir können einige Platzhalterzeichen in `$pattern` verwenden: - -- `%a%` ein oder mehrere beliebige Zeichen außer den Zeilenendezeichen -- `%a?%` null oder mehr von irgendetwas, außer den Zeilenendezeichen -- `%A%` ein oder mehrere beliebige Zeichen einschließlich der Zeilenendezeichen -- `%A?%` null oder mehr von irgendetwas, einschließlich der Zeilenendezeichen -- `%s%` ein oder mehrere Leerzeichen mit Ausnahme der Zeilenendezeichen -- `%s?%` keine oder mehrere Leerzeichen, ausgenommen Zeilenende-Zeichen -- `%S%` ein oder mehrere Zeichen mit Ausnahme des Leerzeichens -- `%S?%` keine oder mehrere Zeichen außer dem Leerzeichen -- `%c%` ein einzelnes Zeichen beliebiger Art (außer dem Zeilenende) +Die zweite Variante ähnelt dem normalen Zeichenkettenvergleich, aber in `$pattern` können wir verschiedene Platzhalter verwenden: + +- `%a%` ein oder mehrere Zeichen, außer Zeilenendezeichen +- `%a?%` kein oder mehrere Zeichen, außer Zeilenendezeichen +- `%A%` ein oder mehrere Zeichen, einschließlich Zeilenendezeichen +- `%A?%` kein oder mehrere Zeichen, einschließlich Zeilenendezeichen +- `%s%` ein oder mehrere Leerzeichen, außer Zeilenendezeichen +- `%s?%` kein oder mehrere Leerzeichen, außer Zeilenendezeichen +- `%S%` ein oder mehrere Zeichen, außer Leerzeichen +- `%S?%` kein oder mehrere Zeichen, außer Leerzeichen +- `%c%` jedes einzelne Zeichen, außer dem Zeilenendezeichen - `%d%` eine oder mehrere Ziffern - `%d?%` keine oder mehrere Ziffern -- `%i%` vorzeichenbehafteter Integer-Wert -- `%f%` Gleitkommazahl -- `%h%` eine oder mehrere HEX-Ziffern +- `%i%` vorzeichenbehafteter ganzzahliger Wert +- `%f%` Fließkommazahl +- `%h%` eine oder mehrere hexadezimale Ziffern - `%w%` ein oder mehrere alphanumerische Zeichen -- `%%` ein %-Zeichen +- `%%` Zeichen % Beispiele: ```php -# Again, hexadecimal number test +# Erneut Test auf hexadezimale Zahl Assert::match('%h%', $var); -# Generalized path to file and line number +# Verallgemeinerung des Dateipfads und der Zeilennummer Assert::match('Error in file %a% on line %i%', $errorMessage); ``` -Assert::matchFile(string $file, $actual, string $description=null) .[method] ----------------------------------------------------------------------------- -Die Assertion ist identisch mit [Assert::match() |#assert-match], aber das Muster wird von `$file` geladen. Sie ist nützlich für das Testen sehr langer Strings. Die Testdatei ist lesbar. +Assert::matchFile(string $file, $actual, ?string $description=null) .[method] +----------------------------------------------------------------------------- +Die Assertion ist identisch mit [#Assert::match()], aber das Muster wird aus der Datei `$file` geladen. Dies ist nützlich zum Testen sehr langer Zeichenketten. Die Testdatei bleibt übersichtlich. Assert::fail(string $message, $actual=null, $expected=null) .[method] --------------------------------------------------------------------- -Diese Behauptung schlägt immer fehl. Sie ist einfach praktisch. Wir können optional erwartete und tatsächliche Werte übergeben. +Diese Assertion schlägt immer fehl. Manchmal ist das einfach nützlich. Optional können wir auch den erwarteten und den tatsächlichen Wert angeben. -Erwartungen .[#toc-expectations] --------------------------------- -Wenn wir komplexere Strukturen mit nicht konstanten Elementen vergleichen wollen, sind die obigen Aussagen möglicherweise nicht ausreichend. Wir testen zum Beispiel eine Methode, die einen neuen Benutzer erstellt und seine Attribute als Array zurückgibt. Wir kennen den Hashwert des Kennworts nicht, aber wir wissen, dass es eine hexadezimale Zeichenkette sein muss. Und das einzige, was wir über das nächste Element wissen, ist, dass es ein Objekt `DateTime` sein muss. +Erwartungen +----------- +Wenn wir komplexere Strukturen mit nicht-konstanten Elementen vergleichen möchten, reichen die oben genannten Assertions möglicherweise nicht aus. Zum Beispiel testen wir eine Methode, die einen neuen Benutzer erstellt und seine Attribute als Array zurückgibt. Den Wert des Passwort-Hashes kennen wir nicht, aber wir wissen, dass es eine hexadezimale Zeichenkette sein muss. Und über ein weiteres Element wissen wir nur, dass es ein `DateTime`-Objekt sein muss. -In diesen Fällen können wir `Tester\Expect` innerhalb des `$expected` -Parameters der `Assert::equal()` - und `Assert::notEqual()` -Methoden verwenden, mit denen sich die Struktur leicht beschreiben lässt. +In diesen Situationen können wir `Tester\Expect` innerhalb des `$expected`-Parameters der Methoden `Assert::equal()` und `Assert::notEqual()` verwenden, mit denen sich die Struktur leicht beschreiben lässt. ```php use Tester\Expect; Assert::equal([ - 'id' => Expect::type('int'), # we expect an integer + 'id' => Expect::type('int'), # wir erwarten eine ganze Zahl 'username' => 'milo', - 'password' => Expect::match('%h%'), # we expect a string matching pattern - 'created_at' => Expect::type(DateTime::class), # we expect an instance of the class + 'password' => Expect::match('%h%'), # wir erwarten eine Zeichenkette, die dem Muster entspricht + 'created_at' => Expect::type(DateTime::class), # wir erwarten eine Instanz der Klasse ], User::create(123, 'milo', 'RandomPaSsWoRd')); ``` -Mit `Expect` können wir fast die gleichen Behauptungen aufstellen wie mit `Assert`. Wir haben also Methoden wie `Expect::same()`, `Expect::match()`, `Expect::count()`, usw. Darüber hinaus können wir sie wie folgt verketten: +Mit `Expect` können wir fast die gleichen Assertions wie mit `Assert` durchführen. Das heißt, uns stehen Methoden wie `Expect::same()`, `Expect::match()`, `Expect::count()` usw. zur Verfügung. Außerdem können wir sie verketten: ```php -Expect::type(MyIterator::class)->andCount(5); # we expect MyIterator and items count is 5 +Expect::type(MyIterator::class)->andCount(5); # wir erwarten MyIterator und die Anzahl der Elemente 5 ``` -Oder wir können eigene Assertion Handler schreiben. +Oder wir können eigene Assertion-Handler schreiben. ```php Expect::that(function ($value) { - # return false if expectation fails + # geben false zurück, wenn die Erwartung fehlschlägt }); ``` -Untersuchung fehlgeschlagener Assertions .[#toc-failed-assertions-investigation] --------------------------------------------------------------------------------- -Der Tester zeigt an, wo der Fehler liegt, wenn eine Assertion fehlschlägt. Wenn wir komplexe Strukturen vergleichen, erstellt der Tester Dumps der verglichenen Werte und speichert sie im Verzeichnis `output`. Wenn zum Beispiel der imaginäre Test `Arrays.recursive.phpt` fehlschlägt, werden die Dumps wie folgt gespeichert: +Untersuchung fehlgeschlagener Assertions +---------------------------------------- +Wenn eine Assertion fehlschlägt, gibt Tester aus, worin der Fehler besteht. Wenn wir komplexere Strukturen vergleichen, erstellt Tester Dumps der verglichenen Werte und speichert sie im Verzeichnis `output`. Zum Beispiel werden bei Fehlschlagen des fiktiven Tests `Arrays.recursive.phpt` die Dumps wie folgt gespeichert: ``` app/ └── tests/ ├── output/ - │ ├── Arrays.recursive.actual # actual value - │ └── Arrays.recursive.expected # expected value + │ ├── Arrays.recursive.actual # tatsächlicher Wert + │ └── Arrays.recursive.expected # erwarteter Wert │ - └── Arrays.recursive.phpt # failing test + └── Arrays.recursive.phpt # fehlgeschlagener Test ``` -Wir können den Namen des Verzeichnisses mit `Tester\Dumper::$dumpDir` ändern. +Den Verzeichnisnamen können wir über `Tester\Dumper::$dumpDir` ändern. diff --git a/tester/de/guide.texy b/tester/de/guide.texy index 31b57967bc..1e82606acd 100644 --- a/tester/de/guide.texy +++ b/tester/de/guide.texy @@ -1,17 +1,17 @@ -Erste Schritte mit Tester -************************* +Erste Schritte mit Nette Tester +******************************* <div class=perex> -Auch gute Programmierer machen Fehler. Der Unterschied zwischen einem guten und einem schlechten Programmierer besteht darin, dass ein guter Programmierer den Fehler nur einmal macht und ihn beim nächsten Mal mit Hilfe automatisierter Tests entdeckt. +Auch gute Programmierer machen Fehler. Der Unterschied zwischen einem guten und einem schlechten Programmierer besteht darin, dass der gute ihn nur einmal macht und ihn das nächste Mal mit automatisierten Tests aufdeckt. -- "Wer nicht testet, ist dazu verdammt, seine eigenen Fehler zu wiederholen." (weises Sprichwort) -- "Wenn wir einen Fehler beseitigt haben, taucht ein neuer auf." (Murphy's Law) -- "Wann immer Sie in Versuchung kommen, eine Anweisung zu drucken, schreiben Sie sie stattdessen als Test." (Martin Fowler) +- "Wer nicht testet, ist dazu verdammt, seine Fehler zu wiederholen." (Sprichwort) +- "Sobald wir einen Fehler beseitigen, taucht ein weiterer auf." (Murphys Gesetz) +- "Wann immer Sie den Drang verspüren, eine Variable auf dem Bildschirm auszugeben, schreiben Sie lieber einen Test." (Martin Fowler) </div> -Haben Sie jemals den folgenden Code in PHP geschrieben? +Haben Sie schon einmal einen ähnlichen Code in PHP geschrieben? ```php $obj = new MyClass; @@ -20,40 +20,40 @@ $result = $obj->process($input); var_dump($result); ``` -Haben Sie schon einmal das Ergebnis eines Funktionsaufrufs ausgegeben, nur um mit dem Auge zu prüfen, ob er das liefert, was er liefern sollte? Sie tun das sicherlich viele Male am Tag. Hand aufs Herz, wenn alles funktioniert, löschen Sie dann diesen Code und erwarten, dass die Klasse in Zukunft nicht mehr kaputt geht? Murphy's Law garantiert das Gegenteil :-) +Also haben Sie das Ergebnis eines Funktionsaufrufs ausgegeben, nur um mit bloßem Auge zu überprüfen, ob es das zurückgibt, was es soll? Sicherlich tun Sie das mehrmals täglich. Hand aufs Herz: Wenn alles richtig funktioniert, löschen Sie diesen Code? Erwarten Sie, dass die Klasse in Zukunft nicht kaputt geht? Murphys Gesetze garantieren das Gegenteil :-) -Tatsächlich haben Sie den Test geschrieben. Er muss nur geringfügig geändert werden, damit er nicht von uns überprüft werden muss, sondern sich einfach selbst überprüfen kann. Und wenn Sie ihn nicht gelöscht haben, können wir ihn jederzeit in der Zukunft ausführen, um zu überprüfen, ob alles noch so funktioniert, wie es sollte. Möglicherweise erstellen Sie im Laufe der Zeit eine große Anzahl dieser Tests, so dass es schön wäre, wenn wir sie automatisch ausführen könnten. +Im Grunde haben Sie einen Test geschrieben. Sie müssen ihn nur geringfügig anpassen, damit er keine Sichtprüfung erfordert, sondern sich selbst überprüft. Und wenn Sie den Test nicht löschen, können Sie ihn jederzeit in Zukunft ausführen und überprüfen, ob alles noch so funktioniert, wie es soll. Mit der Zeit werden Sie eine große Anzahl solcher Tests erstellen, daher wäre es nützlich, sie automatisiert auszuführen. -Und genau dabei hilft Nette Tester. +Und genau dabei hilft Ihnen Nette Tester. -Was macht Tester so einzigartig? .[#toc-what-makes-tester-unique] -================================================================= +Was macht Tester einzigartig? +============================= -Das Schreiben von Tests für Nette Tester ist einzigartig, da **jeder Test ein Standard-PHP-Skript ist, das eigenständig ausgeführt werden kann**. +Das Schreiben von Tests für Nette Tester ist einzigartig, denn **jeder Test ist ein gewöhnliches PHP-Skript, das separat ausgeführt werden kann.** -Wenn Sie also einen Test schreiben, können Sie ihn einfach ausführen, um zu sehen, ob es einen Programmierfehler gibt. Wenn er richtig funktioniert. Wenn nicht, können Sie das Programm einfach in Ihrer IDE durchgehen und nach einem Fehler suchen. Sie können es sogar in einem Browser öffnen. +Das heißt, wenn Sie einen Test schreiben, können Sie ihn einfach ausführen und feststellen, ob darin vielleicht ein Programmierfehler steckt. Ob er richtig funktioniert. Wenn nicht, können Sie ihn leicht in Ihrer IDE debuggen und den Fehler suchen. Sie können ihn sogar im Browser öffnen. -Und das Wichtigste - wenn Sie es ausführen, führen Sie den Test durch. Sie werden sofort erfahren, ob er bestanden hat oder nicht. Wie das geht? Schauen wir uns das an. Schreiben wir einen trivialen Test für die Verwendung eines PHP-Arrays und speichern wir ihn in der Datei `ArrayTest.php`: +Und vor allem – indem Sie ihn ausführen, führen Sie den Test durch. Sie erfahren sofort, ob er bestanden hat oder fehlgeschlagen ist. Wie? Lassen Sie es uns zeigen. Wir schreiben einen trivialen Test zur Arbeit mit einem PHP-Array und speichern ihn in der Datei `ArrayTest.php`: ```php .{file:ArrayTest.php} <?php use Tester\Assert; -require __DIR__ . '/vendor/autoload.php'; # load Composer autoloader -Tester\Environment::setup(); # initialization of Nette Tester +require __DIR__ . '/vendor/autoload.php'; # Composer Autoloader laden +Tester\Environment::setup(); # Initialisierung von Nette Tester $stack = []; -Assert::same(0, count($stack)); # we expect count() to return zero +Assert::same(0, count($stack)); # wir erwarten, dass count() Null zurückgibt $stack[] = 'foo'; -Assert::same(1, count($stack)); # we expect count() to return one -Assert::contains('foo', $stack); # verify that the $stack contains the item 'foo' +Assert::same(1, count($stack)); # wir erwarten, dass count() Eins zurückgibt +Assert::contains('foo', $stack); # überprüfen, ob $stack das Element 'foo' enthält ``` -Wie Sie sehen können, werden [Assertion-Methoden |Assertions] wie `Assert::same()` verwendet, um zu bestätigen, dass ein tatsächlicher Wert mit einem erwarteten Wert übereinstimmt. +Wie Sie sehen, werden sogenannte [Assertion-Methoden|assertions] wie `Assert::same()` verwendet, um zu bestätigen, dass der tatsächliche Wert dem erwarteten Wert entspricht. -Der Test ist geschrieben, wir können ihn über die Befehlszeile ausführen. Beim ersten Durchlauf werden alle Syntaxfehler aufgedeckt, und wenn Sie keinen Tippfehler gemacht haben, werden Sie sehen: +Der Test ist geschrieben und wir können ihn von der Kommandozeile aus starten. Der erste Start deckt eventuelle Syntaxfehler auf, und wenn Sie keinen Tippfehler gemacht haben, wird Folgendes ausgegeben: /--pre .[terminal] $ php ArrayTest.php @@ -61,7 +61,7 @@ $ php ArrayTest.php <span style="color:#FFF; background-color:#090">OK</span> \-- -Versuchen Sie, die Anweisung im Test in `Assert::contains('XXX', $stack);` zu ändern und beobachten Sie, was bei der Ausführung passiert: +Versuchen Sie, im Test die Assertion auf einen falschen Wert zu ändern: `Assert::contains('XXX', $stack);` und beobachten Sie, was beim Start passiert: /--pre .[terminal] $ php ArrayTest.php @@ -73,53 +73,53 @@ $ php ArrayTest.php <span style="color: #FFF; background-color: #900">FAILURE</span> \-- -Wir fahren mit dem Kapitel Schreiben [von Tests |Writing Tests] fort. +Weiter geht es mit dem Schreiben im Kapitel [Tests schreiben|writing-tests]. -Installation und Anforderungen .[#toc-installation-and-requirements] -==================================================================== +Installation und Anforderungen +============================== -Die minimal erforderliche PHP-Version für Tester ist 7.1 (weitere Details finden Sie in der Tabelle der [unterstützten PHP-Versionen |#supported PHP versions] ). Die bevorzugte Art der Installation ist die über [Composer |best-practices:composer]: +Die minimale PHP-Version, die von Tester benötigt wird, ist 7.1 (genauer in der Tabelle [#unterstützte PHP-Versionen]). Die bevorzugte Installationsmethode ist über [Composer |best-practices:composer]: /--pre .[terminal] composer require --dev nette/tester \-- -Versuchen Sie, den Nette Tester von der Kommandozeile aus zu starten (ohne Argumente wird nur eine Hilfezusammenfassung angezeigt): +Versuchen Sie, Nette Tester von der Kommandozeile aus zu starten (ohne Parameter wird nur die Hilfe ausgegeben): /--pre .[terminal] vendor/bin/tester \-- -Tests ausführen .[#toc-running-tests] -===================================== +Tests ausführen +=============== -Wenn unsere Anwendung wächst, wächst auch die Anzahl der Tests. Es wäre unpraktisch, die Tests einzeln auszuführen. Daher verfügt der Tester über einen Bulk-Test-Runner, den wir über die Befehlszeile aufrufen. Der Parameter ist das Verzeichnis, in dem sich die Tests befinden. Der Punkt zeigt das aktuelle Verzeichnis an. +Mit wachsender Anwendung wächst auch die Anzahl der Tests. Es wäre unpraktisch, Tests einzeln auszuführen. Daher verfügt Tester über einen Massen-Test-Runner, den wir von der Kommandozeile aus aufrufen. Als Parameter geben wir das Verzeichnis an, in dem sich die Tests befinden. Ein Punkt bedeutet das aktuelle Verzeichnis. /--pre .[terminal] vendor/bin/tester . \-- -Der Nette-Tester-Läufer durchsucht das angegebene Verzeichnis und alle Unterverzeichnisse und sucht nach Tests, d. h. nach den Dateien `*.phpt` und `*Test.php`. Er findet auch unseren Test `ArrayTest.php`, da er der Maske entspricht. +Der Test-Runner durchsucht das angegebene Verzeichnis und alle Unterverzeichnisse und sucht nach Tests, d. h. Dateien `*.phpt` und `*Test.php`. Er findet also auch unseren Test `ArrayTest.php`, da er dem Muster entspricht. -Dann beginnt er mit dem Testen. Jeder Test wird als neuer PHP-Prozess ausgeführt, so dass er völlig isoliert von den anderen läuft. Er wird parallel in mehreren Threads ausgeführt, was ihn extrem schnell macht. Und er führt zuerst die Tests aus, die beim vorherigen Durchlauf fehlgeschlagen sind, so dass Sie sofort wissen, ob Sie den Fehler behoben haben. +Dann startet er das Testen. Jeder Test wird als neuer PHP-Prozess gestartet, sodass er völlig isoliert von den anderen abläuft. Er führt sie parallel in mehreren Threads aus und ist dadurch extrem schnell. Und er führt zuerst die Tests aus, die beim vorherigen Durchlauf fehlgeschlagen sind, sodass Sie sofort erfahren, ob es Ihnen gelungen ist, den Fehler zu beheben. -Für jeden durchgeführten Test gibt der Läufer ein Zeichen aus, um den Fortschritt anzuzeigen: +Während der Durchführung der Tests gibt Tester die Ergebnisse kontinuierlich als Zeichen auf dem Terminal aus: -- <code style="color: #CCC; background-color: #000">.</code> - Test bestanden -- <code style="color: #CCC; background-color: #000">s</code> - Test wurde übersprungen -- <code style="color: #FFF; background-color: #900">F</code> - Test fehlgeschlagen +- <code style="color: #CCC; background-color: #000">.</code> – Test bestanden +- <code style="color: #CCC; background-color: #000">s</code> – Test übersprungen (skipped) +- <code style="color: #FFF; background-color: #900">F</code> – Test fehlgeschlagen (failed) -Die Ausgabe kann wie folgt aussehen: +Die Ausgabe kann so aussehen: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +PHP 8.3.2 (cli) | php -n | 8 threads ........s................<span style="color: #FFF; background-color: #900">F</span>......... @@ -132,43 +132,44 @@ PHP 7.4.8 (cli) | php -n | 8 threads <span style="color: #FFF; background-color: #900">FAILURES! (35 tests, 1 failures, 1 skipped, 1.7 seconds)</span> \-- -35 Tests wurden durchgeführt, einer ist fehlgeschlagen, einer wurde übersprungen. +Es wurden 35 Tests ausgeführt, einer ist fehlgeschlagen, einer wurde übersprungen. -Wir fahren im Kapitel [Tests durchführen |Running tests] fort. +Weiter geht es im Kapitel [Tests ausführen|running-tests]. -Überwachungsmodus .[#toc-watch-mode] -==================================== +Watch-Modus +=========== -Refaktorieren Sie den Code? Oder entwickeln Sie gar nach der TDD-Methodik (Test Driven Development)? Dann wird Ihnen der Watch Mode gefallen. Der Tester überwacht den Quellcode und führt sich selbst aus, wenn er geändert wird. +Refaktorieren Sie Code? Oder entwickeln Sie sogar nach der TDD-Methodik (Test Driven Development)? Dann wird Ihnen der Watch-Modus gefallen. Tester überwacht darin die Quellcodes und startet sich bei Änderungen selbst. -Während der Entwicklung haben Sie ein Terminal in der Ecke des Monitors, auf dem die grüne Statusleiste aufleuchtet, und wenn sie plötzlich rot wird, wissen Sie, dass Sie gerade etwas Unerwünschtes getan haben. Es ist eigentlich ein tolles Spiel, bei dem man programmiert und versucht, sich an die Farbe zu halten. +Während der Entwicklung haben Sie also in der Ecke des Monitors ein Terminal, auf dem die grüne Statusleiste leuchtet, und wenn sie plötzlich rot wird, wissen Sie, dass Sie gerade etwas nicht ganz richtig gemacht haben. Es ist eigentlich ein tolles Spiel, bei dem Sie programmieren und versuchen, die Farbe zu halten. -Der Watch-Modus wird mit dem Parameter [--watch |running-tests#w-watch-path] gestartet. +Der Watch-Modus wird mit dem Parameter [--watch |running-tests#-w --watch path] gestartet. -CodeCoverage Berichte .[#toc-codecoverage-reports] -================================================== +Code-Coverage-Berichte +====================== -Der Tester kann Berichte erstellen, die einen Überblick darüber geben, wie viel Quellcode die Tests abdecken. Der Bericht kann entweder im menschenlesbaren HTML-Format oder im Clover-XML-Format für die maschinelle Weiterverarbeitung erstellt werden. +Tester kann Berichte mit einer Übersicht darüber generieren, wie viel Quellcode die Tests abdecken (Code Coverage). Der Bericht kann entweder im menschenlesbaren HTML-Format oder als Clover XML zur weiteren maschinellen Verarbeitung vorliegen. -Siehe den "Beispiel-HTML-Bericht":https://files.nette.org/tester/coverage.html mit Code-Abdeckung. +Sehen Sie sich ein "Beispiel für einen HTML-Bericht":attachment:coverage.html mit Codeabdeckung an. -Unterstützte PHP-Versionen .[#toc-supported-php-versions] -========================================================= +Unterstützte PHP-Versionen +========================== -| Version | kompatibel mit PHP +| Version | Kompatibel mit PHP |------------------|------------------- -| Tester 2.4 | PHP 7.2 - 8.2 -| Tester 2.3 | PHP 7.1 - 8.0 -| Tester 2.1 - 2.2 | PHP 7.1 - 7.3 -| Tester 2.0 | PHP 5.6 - 7.3 -| Tester 1.7 | PHP 5.3 - 7.3 + HHVM 3.3+ -| Tester 1.6 | PHP 5.3 - 7.0 + HHVM -| Tester 1.3 - 1.5 | PHP 5.3 - 5.6 + HHVM -| Tester 0.9 - 1.2 | PHP 5.3 - 5.6 - -Gilt für die neuesten Patch-Versionen. - -Bis Version 1.7 unterstützte Tester [HHVM |https://hhvm.com] 3.3.0 oder neuer (mit `tester -p hhvm`). Die Unterstützung wurde seit Tester 2.0 eingestellt. Die Verwendung war einfach: +| Tester 2.5 | PHP 8.0 – 8.3 +| Tester 2.4 | PHP 7.2 – 8.2 +| Tester 2.3 | PHP 7.1 – 8.0 +| Tester 2.1 – 2.2 | PHP 7.1 – 7.3 +| Tester 2.0 | PHP 5.6 – 7.3 +| Tester 1.7 | PHP 5.3 – 7.3 + HHVM 3.3+ +| Tester 1.6 | PHP 5.3 – 7.0 + HHVM +| Tester 1.3 – 1.5 | PHP 5.3 – 5.6 + HHVM +| Tester 0.9 – 1.2 | PHP 5.3 – 5.6 + +Gilt für die letzte Patch-Version. + +Tester bis Version 1.7 unterstützte auch [HHVM |https://hhvm.com] 3.3.0 oder höher (über `tester -p hhvm`). Die Unterstützung wurde ab Tester Version 2.0 eingestellt. diff --git a/tester/de/helpers.texy b/tester/de/helpers.texy index 5bacb62f16..2185f81fbb 100644 --- a/tester/de/helpers.texy +++ b/tester/de/helpers.texy @@ -1,31 +1,45 @@ -Helfer -****** +Hilfsklassen +************ DomQuery -------- -`Tester\DomQuery` ist eine Klasse, die `SimpleXMLElement` mit Methoden erweitert, die das Testen von HTML- oder XML-Inhalten erleichtern. +`Tester\DomQuery` ist eine Klasse, die `SimpleXMLElement` um die einfache Suche in HTML oder XML mittels CSS-Selektoren erweitert. ```php -# let's have an HTML document in $html that we load -$dom = Tester\DomQuery::fromHtml($html); - -# we can test the presence of elements using CSS selectors -Assert::true($dom->has('form#registration')); -Assert::true($dom->has('input[name="username"]')); -Assert::true($dom->has('input[type="submit"]')); - -# or select elements as array of DomQuery -$elems = $dom->find('input[data-autocomplete]'); +# Erstellung von DomQuery aus einer HTML-Zeichenkette +$dom = Tester\DomQuery::fromHtml(' + <article class="post"> + <h1>Titel</h1> + <div class="content">Text</div> + </article> +'); + +# Test der Existenz von Elementen mittels CSS-Selektoren +Assert::true($dom->has('article.post')); +Assert::true($dom->has('h1')); + +# Finden von Elementen als Array von DomQuery-Objekten +$headings = $dom->find('h1'); +Assert::same('Titel', (string) $headings[0]); + +# Test, ob ein Element einem Selektor entspricht (ab Version 2.5.3) +$content = $dom->find('.content')[0]; +Assert::true($content->matches('div')); +Assert::false($content->matches('p')); + +# Finden des nächsten Vorfahren, der dem Selektor entspricht (ab 2.5.5) +$article = $content->closest('.post'); +Assert::true($article->matches('article')); ``` FileMock -------- -`Tester\FileMock` emuliert Dateien im Speicher, um Ihnen zu helfen, einen Code zu testen, der Funktionen wie `fopen()`, `file_get_contents()` oder `parse_ini_file()` verwendet. Zum Beispiel: +`Tester\FileMock` emuliert Dateien im Speicher und erleichtert so das Testen von Code, der Funktionen wie `fopen()`, `file_get_contents()`, `parse_ini_file()` und ähnliche verwendet. Anwendungsbeispiel: ```php -# Tested class +# Getestete Klasse class Logger { public function __construct( @@ -39,21 +53,21 @@ class Logger } } -# New empty file +# Neue leere Datei $file = Tester\FileMock::create(''); $logger = new Logger($file); $logger->log('Login'); $logger->log('Logout'); -# Created content testing +# Testen des erstellten Inhalts Assert::same("Login\nLogout\n", file_get_contents($file)); ``` Assert::with() .[filter] ------------------------ -Dies ist keine Behauptung, sondern ein Hilfsmittel zum Testen privater Methoden und Eigenschaftsobjekte. +Dies ist keine Assertion, sondern ein Helfer zum Testen privater Methoden und Eigenschaften von Objekten. ```php class Entity @@ -65,17 +79,17 @@ class Entity $ent = new Entity; Assert::with($ent, function () { - Assert::true($this->enabled); // zugänglich privat $ent->enabled + Assert::true($this->enabled); // zugänglich gemachte private $ent->enabled }); ``` Helpers::purge() .[filter] -------------------------- -Die Methode `purge()` erstellt das angegebene Verzeichnis und löscht, falls es bereits existiert, seinen gesamten Inhalt. Sie ist praktisch für die Erstellung temporärer Verzeichnisse. Zum Beispiel in `tests/bootstrap.php`: +Die Methode `purge()` erstellt das angegebene Verzeichnis und löscht, falls es bereits existiert, seinen gesamten Inhalt. Sie eignet sich zur Erstellung eines temporären Verzeichnisses. Zum Beispiel in `tests/bootstrap.php`: ```php -@mkdir(__DIR__ . '/tmp'); # @ - directory may already exist +@mkdir(__DIR__ . '/tmp'); # @ - Verzeichnis kann bereits existieren define('TempDir', __DIR__ . '/tmp/' . getmypid()); Tester\Helpers::purge(TempDir); @@ -84,18 +98,18 @@ Tester\Helpers::purge(TempDir); Environment::lock() .[filter] ----------------------------- -Tests laufen parallel. Manchmal ist es notwendig, dass sich die Testläufe nicht überschneiden. Typischerweise müssen Datenbanktests den Datenbankinhalt vorbereiten und dürfen während der Laufzeit des Tests nicht gestört werden. In diesen Fällen verwenden wir `Tester\Environment::lock($name, $dir)`: +Tests werden parallel ausgeführt. Manchmal benötigen wir jedoch, dass sich die Ausführung von Tests nicht überschneidet. Typischerweise ist es bei Datenbanktests notwendig, dass ein Test den Inhalt der Datenbank vorbereitet und ein anderer Test während seiner Laufzeit nicht auf die Datenbank zugreift. In diesen Tests verwenden wir `Tester\Environment::lock($name, $dir)`: ```php Tester\Environment::lock('database', __DIR__ . '/tmp'); ``` -Das erste Argument ist der Name einer Sperre. Das zweite Argument ist ein Pfad zum Verzeichnis, in dem die Sperre gespeichert wird. Der Test, der die Sperre erwirbt, wird zuerst ausgeführt. Die anderen Tests müssen warten, bis er abgeschlossen ist. +Der erste Parameter ist der Name der Sperre, der zweite der Pfad zum Verzeichnis zum Speichern der Sperrdatei. Der Test, der die Sperre zuerst erhält, wird ausgeführt, andere Tests müssen auf dessen Abschluss warten. Environment::bypassFinals() .[filter] ------------------------------------- -Klassen oder Methoden, die als `final` gekennzeichnet sind, sind schwer zu testen. Der Aufruf von `Tester\Environment::bypassFinals()` in einem Testbeginn bewirkt, dass die Schlüsselwörter `final` beim Laden des Codes entfernt werden. +Klassen oder Methoden, die als `final` markiert sind, sind schwer zu testen. Der Aufruf von `Tester\Environment::bypassFinals()` zu Beginn des Tests bewirkt, dass die Schlüsselwörter `final` während des Ladens des Codes entfernt werden. ```php require __DIR__ . '/bootstrap.php'; @@ -111,18 +125,18 @@ class MyClass extends NormallyFinalClass # <-- NormallyFinalClass ist nicht meh Environment::setup() .[filter] ------------------------------ -- verbessert die Lesbarkeit von Fehlerdumps (mit Farbgebung), ansonsten wird der Standard-PHP-Stacktrace ausgegeben -- ermöglicht die Überprüfung, ob Assertions im Test aufgerufen wurden, ansonsten werden auch Tests ohne (z.B. vergessene) Assertions bestanden -- startet automatisch den Code Coverage Collector, wenn `--coverage` verwendet wird (später beschrieben) +- verbessert die Lesbarkeit der Fehlerausgabe (einschließlich Farbgebung), andernfalls wird der standardmäßige PHP-Stack-Trace ausgegeben +- aktiviert die Überprüfung, ob im Test Assertions aufgerufen wurden, andernfalls würde ein Test ohne Assertions (z. B. vergessene) ebenfalls bestehen +- bei Verwendung von `--coverage` startet automatisch die Sammlung von Informationen über den ausgeführten Code (weiter unten beschrieben) - gibt am Ende des Skripts den Status OK oder FAILURE aus Environment::setupFunctions() .[filter]{data-version:2.5} --------------------------------------------------------- -Erzeugt die globalen Funktionen `test()`, `setUp()` und `tearDown()`, in die Sie Tests aufteilen können. +Erstellt globale Funktionen `test()`, `testException()`, `setUp()` und `tearDown()`, in die Sie Tests gliedern können. ```php -test('Testbeschreibung', function () { +test('Beschreibung des Tests', function () { Assert::same(123, foo()); Assert::false(bar()); // ... @@ -132,21 +146,21 @@ test('Testbeschreibung', function () { Environment::VariableRunner .[filter] ------------------------------------- -Ermöglicht es Ihnen herauszufinden, ob der Test direkt oder über den Tester ausgeführt wurde. +Ermöglicht die Feststellung, ob der Test direkt oder über Tester gestartet wurde. ```php if (getenv(Tester\Environment::VariableRunner)) { - # run by Tester + # von Tester gestartet } else { - # another way + # anders gestartet } ``` Environment::VariableThread .[filter] ------------------------------------- -Tester führt Tests parallel in einer bestimmten Anzahl von Threads aus. Die Anzahl der Threads finden wir in einer Environmentsvariablen, wenn wir daran interessiert sind: +Tester führt Tests parallel in der angegebenen Anzahl von Threads aus. Wenn uns die Thread-Nummer interessiert, erfahren wir sie aus der Umgebungsvariable: ```php -echo "I'm running in a thread number " . getenv(Tester\Environment::VariableThread); +echo "Ich laufe in Thread Nummer " . getenv(Tester\Environment::VariableThread); ``` diff --git a/tester/de/running-tests.texy b/tester/de/running-tests.texy index 1e5cd5098a..a874480d7f 100644 --- a/tester/de/running-tests.texy +++ b/tester/de/running-tests.texy @@ -1,55 +1,55 @@ -Laufende Tests -************** +Tests ausführen +*************** .[perex] -Der sichtbarste Teil von Nette Tester ist der Kommandozeilen-Test-Runner. Er ist extrem schnell und robust, weil er automatisch alle Tests als separate Prozesse parallel in mehreren Threads startet. Er kann auch selbst im sogenannten Watch-Modus laufen. +Der sichtbarste Teil von Nette Tester ist der Test-Runner für die Kommandozeile. Er ist außergewöhnlich schnell und robust, da er alle Tests automatisch als separate Prozesse startet, und das parallel in mehreren Threads. Er kann sich auch selbst im sogenannten Watch-Modus starten. -Der Nette Tester Test Runner wird über die Kommandozeile aufgerufen. Als Parameter übergeben wir das Testverzeichnis. Für das aktuelle Verzeichnis geben wir einfach einen Punkt ein: +Der Test-Runner wird von der Kommandozeile aus aufgerufen. Als Parameter geben wir das Verzeichnis mit den Tests an. Für das aktuelle Verzeichnis genügt ein Punkt: /--pre .[terminal] vendor/bin/tester . \-- -Beim Aufruf durchsucht der Testrunner das angegebene Verzeichnis und alle Unterverzeichnisse und sucht nach Tests, d. h. nach den Dateien `*.phpt` und `*Test.php`. Er liest auch deren [Anmerkungen |test-annotations] und wertet sie aus, um zu wissen, welche und wie sie ausgeführt werden sollen. +Der Test-Runner durchsucht das angegebene Verzeichnis und alle Unterverzeichnisse und sucht nach Tests, das sind Dateien `*.phpt` und `*Test.php`. Gleichzeitig liest und wertet er deren [Annotationen |test-annotations] aus, um zu wissen, welche davon und wie sie ausgeführt werden sollen. -Anschließend führt er die Tests aus. Für jeden durchgeführten Test gibt der Läufer ein Zeichen aus, um den Fortschritt anzuzeigen: +Danach führt er die Tests aus. Während der Ausführung der Tests gibt er die Ergebnisse kontinuierlich als Zeichen auf dem Terminal aus: -- <code style="color: #CCC; background-color: #000">.</code> - Test bestanden -- <code style="color: #CCC; background-color: #000">s</code> - Test wurde übersprungen -- <code style="color: #FFF; background-color: #900">F</code> - Test fehlgeschlagen +- <code style="color: #CCC; background-color: #000">.</code> – Test bestanden +- <code style="color: #CCC; background-color: #000">s</code> – Test übersprungen (skipped) +- <code style="color: #FFF; background-color: #900">F</code> – Test fehlgeschlagen (failed) -Die Ausgabe kann wie folgt aussehen: +Die Ausgabe kann beispielsweise so aussehen: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +PHP 8.3.2 (cli) | php -n | 8 threads ........s.......................... <span style="color: #FFF; background-color: #090">OK (35 tests, 1 skipped, 1.7 seconds)</span> \-- -Bei einem erneuten Durchlauf werden zuerst die Tests ausgeführt, die beim vorherigen Durchlauf fehlgeschlagen sind, so dass Sie sofort wissen, ob Sie den Fehler behoben haben. +Bei wiederholter Ausführung führt er zuerst die Tests aus, die beim vorherigen Durchlauf fehlgeschlagen sind, sodass Sie sofort erfahren, ob es Ihnen gelungen ist, den Fehler zu beheben. -Der Exit-Code des Testers ist Null, wenn keiner der Tests fehlschlägt. Andernfalls ungleich Null. +Wenn kein Test fehlschlägt, ist der Rückgabecode von Tester Null. Andernfalls ist der Rückgabecode ungleich Null. .[warning] -Der Tester führt PHP-Prozesse ohne `php.ini` aus. Weitere Einzelheiten finden Sie im Abschnitt [Eigene php.ini |#Own php.ini]. +Tester startet PHP-Prozesse ohne `php.ini`. Details im Abschnitt [#Eigene php.ini]. -Kommandozeilen-Optionen .[#toc-command-line-options] -==================================================== +Kommandozeilenparameter +======================= -Einen Überblick über die Kommandozeilenoptionen erhalten wir, indem wir den Tester ohne Parameter oder mit der Option `-h` ausführen: +Eine Übersicht aller Kommandozeilenoptionen erhalten Sie, indem Sie Tester ohne Parameter oder mit dem Parameter `-h` starten: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Usage: tester [options] [<test file> | <directory>]... @@ -58,16 +58,16 @@ Options: -p <path> Specify PHP interpreter to run (default: php). -c <path> Look for php.ini file (or look in directory) <path>. -C Use system-wide php.ini. - -l | --log <path> Write log to file <path>. -d <key=value>... Define INI entry 'key' with value 'value'. -s Show information about skipped tests. --stop-on-fail Stop execution upon the first failure. -j <num> Run <num> jobs in parallel (default: 8). - -o <console|tap|junit|none> Specify output format. + -o <console|console-lines|tap|junit|log|none> (e.g. -o junit:output.xml) + Specify one or more output formats with optional file name. -w | --watch <path> Watch directory. -i | --info Show tests environment info and exit. --setup <path> Script for runner setup. - --temp <path> Path to temporary directory. Default: sys_get_temp_dir(). + --temp <path> Path to temporary directory. Default by sys_get_temp_dir(). --colors [1|0] Enable or disable colors. --coverage <path> Generate code coverage report to file. --coverage-src <path> Path to source code. @@ -77,7 +77,7 @@ Options: -p <path> .[filter] ------------------- -Gibt die PHP-Binärdatei an, die für die Ausführung der Tests verwendet wird. Standardmäßig ist dies `php`. +Gibt den PHP-Interpreter an, der zum Ausführen der Tests verwendet wird. Standardmäßig ist dies `php`. /--pre .[terminal] tester -p /home/user/php-7.2.0-beta/php-cgi tests @@ -86,26 +86,17 @@ tester -p /home/user/php-7.2.0-beta/php-cgi tests -c <path> .[filter] ------------------- -Gibt an, welche `php.ini` bei der Ausführung von Tests verwendet wird. Standardmäßig wird keine php.ini verwendet. Siehe [Eigene php.ini |#Own php.ini] für weitere Informationen. +Gibt an, welche `php.ini` beim Ausführen der Tests verwendet wird. Standardmäßig wird keine php.ini verwendet. Mehr im Abschnitt [#Eigene php.ini]. -C .[filter] ------------ -Es wird eine systemweite `php.ini` verwendet. Auf der UNIX-Plattform also auch alle `/etc/php/{sapi}/conf.d/*.ini` -Dateien. Siehe Abschnitt [Eigene php.ini |#Own php.ini]. - - -''-l | --log <path>'' .[filter] -------------------------------- -Der Testfortschritt wird in eine Datei geschrieben. Alle fehlgeschlagenen, übersprungenen und auch erfolgreichen Tests: - -/--pre .[terminal] -tester --log /var/log/tests.log tests -\-- +Verwendet die systemweite `php.ini`. Unter UNIX auch alle relevanten INI-Dateien `/etc/php/{sapi}/conf.d/*.ini`. Mehr im Abschnitt [#Eigene php.ini]. -d <key=value> .[filter] ------------------------ -Legt den Wert der PHP-Konfigurationsrichtlinie für Tests fest. Der Parameter kann mehrfach verwendet werden. +Setzt den Wert einer PHP-Konfigurationsdirektive für die Tests. Der Parameter kann mehrfach verwendet werden. /--pre .[terminal] tester -d max_execution_time=20 @@ -114,34 +105,36 @@ tester -d max_execution_time=20 -s --- -Es werden Informationen über übersprungene Tests angezeigt. +Zeigt Informationen über übersprungene Tests an. --stop-on-fail .[filter] ------------------------ -Der Tester bricht den Test ab, sobald der erste Test fehlschlägt. +Tester stoppt die Testausführung beim ersten fehlgeschlagenen Test. -j <num> .[filter] ------------------ -Tests laufen in einem `<num>` parallel abläuft. Der Standardwert ist 8. Wenn wir die Tests in Serie laufen lassen wollen, verwenden wir den Wert 1. +Gibt an, wie viele parallele Prozesse mit Tests gestartet werden. Der Standardwert ist 8. Wenn wir möchten, dass alle Tests seriell ausgeführt werden, verwenden wir den Wert 1. --o <console|tap|junit|none> .[filter] -------------------------------------- -Ausgabeformat. Standard ist das Konsolenformat. +-o <console|console-lines|tap|junit|log|none> .[filter] +------------------------------------------------------- +Legt das Ausgabeformat fest. Standard ist das Konsolenformat. Sie können einen Dateinamen angeben, in den die Ausgabe geschrieben wird (z. B. `-o junit:output.xml`). Die Option `-o` kann mehrfach wiederholt werden, um mehrere Formate gleichzeitig zu generieren. -- `console`: wie Standard, aber das ASCII-Logo wird in diesem Fall nicht gedruckt -- `tap`: [TAP-Format |https://en.wikipedia.org/wiki/Test_Anything_Protocol], geeignet für die maschinelle Verarbeitung -- `junit`: JUnit XML-Format, auch für die maschinelle Verarbeitung geeignet -- `none`: es wird nichts gedruckt +- `console`: Identisch mit dem Standardformat, aber in diesem Fall wird das ASCII-Logo nicht angezeigt. +- `console-lines`: Ähnlich wie console, aber das Ergebnis jedes Tests wird in einer separaten Zeile mit weiteren Informationen angezeigt. +- `tap`: [TAP-Format |https://en.wikipedia.org/wiki/Test_Anything_Protocol], geeignet für maschinelle Verarbeitung. +- `junit`: JUnit-XML-Format, ebenfalls für maschinelle Verarbeitung geeignet. +- `log`: Ausgaben des Testverlaufs. Alle fehlgeschlagenen, übersprungenen und auch erfolgreichen Tests. +- `none`: Es wird nichts ausgegeben. ''-w | --watch <path>'' .[filter] --------------------------------- -Der Tester wird nicht beendet, wenn die Tests abgeschlossen sind, sondern er führt die PHP-Dateien im angegebenen Verzeichnis weiter aus und beobachtet sie. Bei einer Änderung führt er die Tests erneut aus. Der Parameter kann mehrfach verwendet werden, wenn wir mehrere Verzeichnisse überwachen wollen. +Nach Abschluss der Tests beendet sich Tester nicht, sondern bleibt laufen und überwacht PHP-Dateien im angegebenen Verzeichnis. Bei einer Änderung führt er die Tests erneut aus. Der Parameter kann mehrfach verwendet werden, wenn wir mehrere Verzeichnisse überwachen möchten. -Dies ist praktisch beim Refactoring einer Bibliothek oder beim Debuggen von Tests. +Nützlich beim Refactoring einer Bibliothek oder beim Debuggen von Tests. /--pre .[terminal] tester --watch src tests @@ -150,7 +143,7 @@ tester --watch src tests ''-i | --info'' .[filter] ------------------------- -Zeigt Informationen über eine Testumgebung an. Zum Beispiel: +Zeigt Informationen zur Laufzeitumgebung für die Tests an. Zum Beispiel: /--pre .[terminal] tester -p /usr/bin/php7.1 -c tests/php.ini --info @@ -177,13 +170,13 @@ Core, ctype, date, dom, ereg, fileinfo, filter, hash, ... --setup <path> .[filter] ------------------------ -Der Tester lädt das angegebene PHP-Skript beim Start. Darin ist die Variable `Tester\Runner\Runner $runner` verfügbar. Nehmen wir an, die Datei `tests/runner-setup.php`: +Tester lädt beim Start das angegebene PHP-Skript. Darin steht die Variable `Tester\Runner\Runner $runner` zur Verfügung. Angenommen, die Datei `tests/runner-setup.php` hat folgenden Inhalt: ```php $runner->outputHandlers[] = new MyOutputHandler; ``` -und wir führen den Tester aus: +Wir starten Tester: /--pre .[terminal] tester --setup tests/runner-setup.php tests @@ -192,47 +185,47 @@ tester --setup tests/runner-setup.php tests --temp <path> .[filter] ----------------------- -Legt einen Pfad zum Verzeichnis für temporäre Dateien des Testers fest. Der Standardwert wird von `sys_get_temp_dir()` zurückgegeben. Wenn der Standardwert nicht gültig ist, werden Sie darauf hingewiesen. +Legt den Pfad zum Verzeichnis für temporäre Dateien von Tester fest. Der Standardwert wird von `sys_get_temp_dir()` zurückgegeben. Wenn der Standardwert ungültig ist, werden Sie benachrichtigt. -Wenn wir nicht sicher sind, welches Verzeichnis verwendet wird, können wir Tester mit dem Parameter `--info` ausführen. +Wenn wir uns nicht sicher sind, welches Verzeichnis verwendet wird, starten wir Tester mit dem Parameter `--info`. ---Farben 1|0 .[filter] +--colors 1|0 .[filter] ---------------------- -Der Tester erkennt standardmäßig ein farbfähiges Terminal und färbt seine Ausgabe ein. Diese Option ist über die automatische Erkennung. Wir können die Farbgebung global durch eine Systemumgebungsvariable `NETTE_TESTER_COLORS` einstellen. +Standardmäßig erkennt Tester ein farbiges Terminal und färbt seine Ausgabe ein. Diese Option überschreibt die Autodetektion. Global können wir die Farbgebung über die Systemumgebungsvariable `NETTE_TESTER_COLORS` einstellen. --coverage <path> .[filter] --------------------------- -Der Tester generiert einen Bericht mit einer Übersicht, wie weit der Quellcode von den Tests abgedeckt ist. Für diese Option muss die PHP-Erweiterung [Xdebug |https://xdebug.org/] oder [PCOV |https://github.com/krakjoe/pcov] aktiviert sein, oder PHP 7 mit dem PHPDBG SAPI, das schneller ist. Die Zieldateierweiterung bestimmt das Format des Inhalts. HTML oder Clover XML. +Tester generiert einen Bericht mit einer Übersicht darüber, wie viel Quellcode die Tests abdecken. Diese Option erfordert die installierte PHP-Erweiterung [Xdebug |https://xdebug.org/] oder [PCOV |https://github.com/krakjoe/pcov] oder PHP 7 mit PHPDBG SAPI, das schneller ist. Die Dateierweiterung der Zieldatei bestimmt ihr Format. Entweder HTML oder Clover XML. /--pre .[terminal] -tester tests --coverage coverage.html # HTML report -tester tests --coverage coverage.xml # Clover XML report +tester tests --coverage coverage.html # HTML-Bericht +tester tests --coverage coverage.xml # Clover XML-Bericht \-- -Die Priorität für die Auswahl des Sammelmechanismus ist die folgende: +Die Priorität bei der Auswahl des Mechanismus ist wie folgt: 1) PCOV 2) PHPDBG 3) Xdebug -Umfangreiche Tests können während der Ausführung durch PHPDBG aufgrund von Speichererschöpfung fehlschlagen. Das Sammeln von Abdeckungsdaten ist ein speicherintensiver Vorgang. In diesem Fall kann der Aufruf von `Tester\CodeCoverage\Collector::flush()` innerhalb eines Tests helfen. Dadurch werden die gesammelten Daten in eine Datei geschrieben und der Speicher wird freigegeben. Wenn die Datenerfassung nicht läuft oder Xdebug verwendet wird, hat der Aufruf keine Auswirkungen. +Bei Verwendung von PHPDBG können bei umfangreichen Tests Fehler aufgrund von Speichermangel auftreten. Die Sammlung von Informationen über abgedeckten Code ist speicherintensiv. In diesem Fall hilft der Aufruf von `Tester\CodeCoverage\Collector::flush()` innerhalb des Tests. Er schreibt die gesammelten Daten auf die Festplatte und gibt Speicher frei. Wenn keine Datensammlung stattfindet oder Xdebug verwendet wird, hat der Aufruf keine Auswirkung. -"Ein Beispiel für einen HTML-Bericht":https://files.nette.org/tester/coverage.html mit Codeabdeckung. +"Beispiel für einen HTML-Bericht":attachment:coverage.html mit Codeabdeckung. --coverage-src <path> .[filter] ------------------------------- -Wir verwenden sie gleichzeitig mit der Option `--coverage`. Die `<path>` ist ein Pfad zum Quellcode, für den wir den Bericht erstellen. Er kann wiederholt verwendet werden. +Wird gleichzeitig mit der Option `--coverage` verwendet. `<path>` ist der Pfad zu den Quellcodes, für die der Bericht generiert wird. Kann wiederholt verwendet werden. -Eigene php.ini .[#toc-own-php-ini] -================================== -Der Tester führt PHP-Prozesse mit der Option `-n` aus, was bedeutet, dass keine `php.ini` geladen wird (nicht einmal die von `/etc/php/conf.d/*.ini` in UNIX). Dies gewährleistet die gleiche Umgebung für die ausgeführten Tests, aber es deaktiviert auch alle externen PHP-Erweiterungen, die üblicherweise von System-PHP geladen werden. +Eigene php.ini +============== +Tester startet PHP-Prozesse mit dem Parameter `-n`, was bedeutet, dass keine `php.ini` geladen wird. Unter UNIX auch nicht die aus `/etc/php/conf.d/*.ini`. Dies gewährleistet eine einheitliche Umgebung für die Ausführung der Tests, deaktiviert aber auch alle PHP-Erweiterungen, die normalerweise vom System-PHP geladen werden. -Wenn Sie die Systemkonfiguration beibehalten wollen, verwenden Sie den Parameter `-C`. +Wenn Sie das Laden der systemweiten php.ini-Dateien beibehalten möchten, verwenden Sie den Parameter `-C`. -Wenn Sie einige Erweiterungen oder spezielle INI-Einstellungen benötigen, empfehlen wir Ihnen, eine eigene `php.ini` Datei zu erstellen und diese auf die Tests zu verteilen. Dann führen wir Tester mit der Option `-c` aus, z.B. `tester -c tests/php.ini`. Die INI-Datei kann wie folgt aussehen: +Wenn Sie Erweiterungen oder spezielle INI-Einstellungen für Tests benötigen, empfehlen wir die Erstellung einer eigenen `php.ini`-Datei, die mit den Tests verteilt wird. Tester wird dann mit dem Parameter `-c` gestartet, zum Beispiel `tester -c tests/php.ini tests`, wobei die INI-Datei wie folgt aussehen kann: ```ini [PHP] @@ -243,4 +236,4 @@ extension=php_pdo_pgsql.dll memory_limit=512M ``` -Wenn Sie den Tester mit einem System `php.ini` unter UNIX ausführen, z. B. `tester -c /etc/php/cgi/php.ini`, wird keine andere INI-Datei von `/etc/php/conf.d/*.ini` geladen. Das ist das Verhalten von PHP, nicht das des Testers. +Das Starten von Tester unter UNIX mit einer systemweiten `php.ini`, zum Beispiel `tester -c /etc/php/cli/php.ini`, lädt nicht die anderen INIs aus `/etc/php/conf.d/*.ini`. Dies ist eine Eigenschaft von PHP, nicht von Tester. diff --git a/tester/de/test-annotations.texy b/tester/de/test-annotations.texy index 211a74528e..47c506e361 100644 --- a/tester/de/test-annotations.texy +++ b/tester/de/test-annotations.texy @@ -1,16 +1,16 @@ -Test-Anmerkungen -**************** +Test-Annotationen +***************** .[perex] -Anmerkungen legen fest, wie die Tests von der [Befehlszeile des Testläufers |running-tests] behandelt werden sollen. Sie werden an den Anfang der Testdatei geschrieben. +Annotationen bestimmen, wie Tests vom [Kommandozeilen-Teststarter |running-tests] behandelt werden. Sie werden am Anfang der Testdatei geschrieben. -Bei den Anmerkungen wird die Groß- und Kleinschreibung nicht berücksichtigt. Sie haben auch keine Auswirkungen, wenn der Test manuell als normales PHP-Skript ausgeführt wird. +Bei Annotationen wird die Groß-/Kleinschreibung nicht berücksichtigt. Sie haben auch keine Auswirkung, wenn der Test manuell als normales PHP-Skript ausgeführt wird. Beispiel: ```php /** - * TEST: Basic database query test. + * TEST: Grundlegender Datenbankabfragetest. * * @dataProvider files/databases.ini * @exitCode 56 @@ -23,17 +23,17 @@ require __DIR__ . '/../bootstrap.php'; TEST .[filter] -------------- -Es handelt sich nicht um eine eigentliche Anmerkung. Er legt nur den Testtitel fest, der bei Fehlschlägen oder in Protokollen ausgegeben wird. +Dies ist eigentlich keine Annotation, sondern bestimmt nur den Titel des Tests, der bei einem Fehlschlag oder im Protokoll ausgegeben wird. @skip .[filter] --------------- -Der Test wird übersprungen. Dies ist praktisch für die vorübergehende Deaktivierung von Tests. +Der Test wird übersprungen. Nützlich zum vorübergehenden Deaktivieren von Tests. @phpVersion .[filter] --------------------- -Der Test wird übersprungen, wenn er nicht mit der entsprechenden PHP-Version ausgeführt wird. Wir schreiben die Annotation als `@phpVersion [operator] version`. Wir können den Operator weglassen, Standard ist `>=`. Beispiele: +Der Test wird übersprungen, wenn er nicht mit der entsprechenden PHP-Version ausgeführt wird. Die Annotation wird als `@phpVersion [Operator] Version` geschrieben. Der Operator kann weggelassen werden, der Standard ist `>=`. Beispiele: ```php /** @@ -46,7 +46,7 @@ Der Test wird übersprungen, wenn er nicht mit der entsprechenden PHP-Version au @phpExtension .[filter] ----------------------- -Der Test wird übersprungen, wenn nicht alle genannten PHP-Erweiterungen geladen sind. Mehrere Erweiterungen können in eine einzige Anmerkung geschrieben werden, oder wir können die Anmerkung mehrfach verwenden. +Der Test wird übersprungen, wenn nicht alle angegebenen PHP-Erweiterungen geladen sind. Mehrere Erweiterungen können in einer Annotation angegeben oder die Annotation kann mehrfach verwendet werden. ```php /** @@ -58,9 +58,9 @@ Der Test wird übersprungen, wenn nicht alle genannten PHP-Erweiterungen geladen @dataProvider .[filter] ----------------------- -Diese Annotation eignet sich, wenn der Test mehrfach, aber mit unterschiedlichen Daten ausgeführt werden soll. (Nicht zu verwechseln mit der gleichnamigen Annotation für [TestCase |TestCase#dataProvider]). +Wenn wir eine Testdatei mehrfach, aber mit unterschiedlichen Eingabedaten ausführen möchten, ist diese Annotation nützlich. (Nicht zu verwechseln mit der gleichnamigen Annotation für [TestCase |TestCase#dataProvider].) -Wir schreiben die Anmerkung als `@dataProvider file.ini`. Der Pfad der INI-Datei ist relativ zur Testdatei. Der Test wird so oft ausgeführt, wie die Anzahl der in der INI-Datei enthaltenen Abschnitte. Nehmen wir an, die INI-Datei `databases.ini`: +Wir schreiben sie als `@dataProvider file.ini`, der Pfad zur Datei wird relativ zur Testdatei interpretiert. Der Test wird so oft ausgeführt, wie es Abschnitte in der INI-Datei gibt. Angenommen, die INI-Datei `databases.ini` lautet: ```ini [mysql] @@ -77,7 +77,7 @@ password = ****** dsn = "sqlite::memory:" ``` -und die Datei `database.phpt` befinden sich im selben Verzeichnis: +und im selben Verzeichnis der Test `database.phpt`: ```php /** @@ -87,11 +87,11 @@ und die Datei `database.phpt` befinden sich im selben Verzeichnis: $args = Tester\Environment::loadData(); ``` -Der Test läuft dreimal und `$args` enthält Werte aus den Abschnitten `mysql`, `postgresql` oder `sqlite`. +Der Test wird dreimal ausgeführt, und `$args` enthält jeweils die Werte aus den Abschnitten `mysql`, `postgresql` oder `sqlite`. -Es gibt noch eine weitere Variante, wenn wir Anmerkungen mit einem Fragezeichen als `@dataProvider? file.ini` schreiben. In diesem Fall wird der Test übersprungen, wenn die INI-Datei nicht vorhanden ist. +Es gibt noch eine Variante, bei der wir die Annotation mit einem Fragezeichen als `@dataProvider? file.ini` schreiben. In diesem Fall wird der Test übersprungen, wenn die INI-Datei nicht existiert. -Es wurden noch nicht alle Möglichkeiten für Anmerkungen erwähnt. Wir können Bedingungen hinter die INI-Datei schreiben. Der Test wird nur dann für den angegebenen Abschnitt ausgeführt, wenn alle Bedingungen erfüllt sind. Erweitern wir die INI-Datei: +Damit sind die Möglichkeiten der Annotation noch nicht erschöpft. Hinter dem Namen der INI-Datei können wir Bedingungen angeben, unter denen der Test für den jeweiligen Abschnitt ausgeführt wird. Erweitern wir die INI-Datei: ```ini [mysql] @@ -113,7 +113,7 @@ password = ****** dsn = "sqlite::memory:" ``` -und wir werden eine Anmerkung mit Bedingung verwenden: +und verwenden die Annotation mit einer Bedingung: ```php /** @@ -121,9 +121,9 @@ und wir werden eine Anmerkung mit Bedingung verwenden: */ ``` -Der Test läuft nur einmal für den Abschnitt `postgresql 9.1`. Andere Abschnitte erfüllen die Bedingungen nicht. +Der Test wird nur einmal ausgeführt, und zwar für den Abschnitt `postgresql 9.1`. Die anderen Abschnitte bestehen den Bedingungsfilter nicht. -Auf ähnliche Weise können wir den Pfad zu einem PHP-Skript anstelle von INI übergeben. Es muss Array oder Traversable zurückgeben. Datei `databases.php`: +Ähnlich können wir anstelle einer INI-Datei auf ein PHP-Skript verweisen. Dieses muss ein Array oder Traversable zurückgeben. Datei `databases.php`: ```php return [ @@ -142,29 +142,29 @@ return [ @multiple .[filter] ------------------- -Wir schreiben es als `@multiple N`, wobei `N` eine ganze Zahl ist. Der Test läuft genau N-mal. +Wird als `@multiple N` geschrieben, wobei `N` eine ganze Zahl ist. Der Test wird genau N-mal ausgeführt. @testCase .[filter] ------------------- -Die Annotation hat keine Parameter. Wir verwenden sie, wenn wir einen Test als [TestCase-Klassen |TestCase] schreiben. In diesem Fall führt der Befehlszeilen-Testrunner die einzelnen Methoden in separaten Prozessen und parallel in mehreren Threads aus. Dadurch kann der gesamte Testprozess erheblich beschleunigt werden. +Die Annotation hat keine Parameter. Wir verwenden sie, wenn wir Tests als [TestCase |TestCase]-Klassen schreiben. In diesem Fall führt der Kommandozeilen-Teststarter die einzelnen Methoden in separaten Prozessen und parallel in mehreren Threads aus. Dies kann den gesamten Testprozess erheblich beschleunigen. @exitCode .[filter] ------------------- -Wir schreiben es als `@exitCode N`, wenn `N` is the exit code of the test. For example if `exit(10)` im Test aufgerufen wird, schreiben wir die Annotation als `@exitCode 10`. Wenn der Test mit einem anderen Code endet, gilt er als fehlgeschlagen. Der Exit-Code 0 (Null) wird verifiziert, wenn wir die Annotation weglassen +Wird als `@exitCode N` geschrieben, wobei `N` der Rückgabecode des ausgeführten Tests ist. Wenn im Test beispielsweise `exit(10)` aufgerufen wird, schreiben wir die Annotation als `@exitCode 10`, und wenn der Test mit einem anderen Code endet, wird dies als Fehlschlag betrachtet. Wenn die Annotation nicht angegeben wird, wird der Rückgabecode 0 (Null) überprüft. @httpCode .[filter] ------------------- -Der Vermerk wird nur ausgewertet, wenn das PHP-Binary CGI ist. Ansonsten wird sie ignoriert. Wir schreiben sie als `@httpCode NNN`, wobei `NNN` der erwartete HTTP-Code ist. Der HTTP-Code 200 wird überprüft, wenn wir die Anmerkung weglassen. Wenn wir `NNN` als String schreiben, der als Null ausgewertet wird, z. B. `any`, wird der HTTP-Code überhaupt nicht überprüft. +Die Annotation wird nur angewendet, wenn die PHP-Binärdatei CGI ist. Andernfalls wird sie ignoriert. Wird als `@httpCode NNN` geschrieben, wobei `NNN` der erwartete HTTP-Code ist. Wenn die Annotation nicht angegeben wird, wird der HTTP-Code 200 überprüft. Wenn `NNN` als Zeichenkette geschrieben wird, die zu Null ausgewertet wird, z. B. `any`, wird der HTTP-Code nicht überprüft. -@outputMatch a @outputMatchFile .[filter] ------------------------------------------ -Das Verhalten der Annotationen ist konsistent mit den Assertionen `Assert::match()` und `Assert::matchFile()`. Das Muster wird jedoch in der Standardausgabe des Tests gefunden. Ein geeigneter Anwendungsfall ist, wenn wir davon ausgehen, dass der Test mit einem schwerwiegenden Fehler endet und wir seine Ausgabe überprüfen müssen. +@outputMatch und @outputMatchFile .[filter] +------------------------------------------- +Die Funktion der Annotationen ist identisch mit den Assertions `Assert::match()` und `Assert::matchFile()`. Das Muster (Pattern) wird jedoch im Text gesucht, den der Test an seine Standardausgabe gesendet hat. Dies findet Anwendung, wenn wir davon ausgehen, dass der Test mit einem fatalen Fehler endet und wir dessen Ausgabe überprüfen müssen. @phpIni .[filter] ----------------- -Es setzt INI-Konfigurationswerte für den Test. Wir schreiben es zum Beispiel als `@phpIni precision=20` und es funktioniert genauso, wie wenn wir den Wert von der Kommandozeile mit dem Parameter `-d precision=20` übergeben. +Legt für den Test Konfigurations-INI-Werte fest. Wird beispielsweise als `@phpIni precision=20` geschrieben und funktioniert genauso, als ob wir den Wert von der Kommandozeile über den Parameter `-d precision=20` angegeben hätten. diff --git a/tester/de/testcase.texy b/tester/de/testcase.texy index 33db097623..ba3469c8c5 100644 --- a/tester/de/testcase.texy +++ b/tester/de/testcase.texy @@ -2,9 +2,9 @@ TestCase ******** .[perex] -In einfachen Tests können die Assertions einzeln folgen. Manchmal ist es jedoch sinnvoll, die Aussagen in eine Testklasse einzuschließen und sie auf diese Weise zu strukturieren. +In einfachen Tests können Assertions aufeinander folgen. Manchmal ist es jedoch vorteilhafter, Assertions in einer Testklasse zu verpacken und sie so zu strukturieren. -Die Klasse muss von `Tester\TestCase` abstammen und wir sprechen einfach von **testcase**. +Die Klasse muss von `Tester\TestCase` erben und wird vereinfacht als **testcase** bezeichnet. Die Klasse muss Testmethoden enthalten, die mit `test` beginnen. Diese Methoden werden als Tests ausgeführt: ```php use Tester\Assert; @@ -22,11 +22,11 @@ class RectangleTest extends Tester\TestCase } } -# Run testing methods +# Ausführung der Testmethoden (new RectangleTest)->run(); ``` -Wir können einen Testfall um die Methoden `setUp()` und `tearDown()` erweitern. Sie werden vor/nach jeder Testmethode aufgerufen: +Ein so geschriebener Test kann weiter um Methoden `setUp()` und `tearDown()` erweitert werden. Sie werden vor bzw. nach jeder Testmethode aufgerufen: ```php use Tester\Assert; @@ -35,12 +35,12 @@ class NextTest extends Tester\TestCase { public function setUp() { - # Preparation + # Vorbereitung } public function tearDown() { - # Clean-up + # Aufräumen } public function testOne() @@ -54,14 +54,14 @@ class NextTest extends Tester\TestCase } } -# Run testing methods +# Ausführung der Testmethoden (new NextTest)->run(); /* -Method Calls Order ------------------- +Reihenfolge der Methodenaufrufe +------------------------------- setUp() testOne() tearDown() @@ -72,9 +72,9 @@ tearDown() */ ``` -Wenn in einer `setUp()` oder `tearDown()` Phase ein Fehler auftritt, wird der Test fehlschlagen. Tritt ein Fehler in der Testmethode auf, wird die Methode `tearDown()` trotzdem aufgerufen, allerdings mit unterdrückten Fehlern. +Wenn in der `setUp()`- oder `tearDown()`-Phase ein Fehler auftritt, schlägt der Test insgesamt fehl. Wenn in der Testmethode ein Fehler auftritt, wird die Methode `tearDown()` dennoch ausgeführt, jedoch mit unterdrückten Fehlern darin. -Wir empfehlen Ihnen, die Annotation [@testCase |test-annotations#@testCase] an den Anfang des Tests zu schreiben, dann führt der Kommandozeilen-Testrunner die einzelnen Testfallmethoden in separaten Prozessen und parallel in mehreren Threads aus. Dies kann den gesamten Testprozess erheblich beschleunigen. +Wir empfehlen, am Anfang des Tests die Annotation [@testCase |test-annotations#testCase] zu schreiben. Dann führt der Kommandozeilen-Teststarter die einzelnen Methoden des Testcases in separaten Prozessen und parallel in mehreren Threads aus. Dies kann den gesamten Testprozess erheblich beschleunigen. /--php <?php @@ -82,15 +82,15 @@ Wir empfehlen Ihnen, die Annotation [@testCase |test-annotations#@testCase] an d \-- -Annotation von Methoden .[#toc-annotation-of-methods] -===================================================== +Annotationen von Methoden +========================= -Es gibt ein paar Anmerkungen, die uns beim Testen von Methoden helfen. Wir schreiben sie in Richtung der Testmethode. +Für Testmethoden stehen uns mehrere Annotationen zur Verfügung, die uns das Testen erleichtern. Wir schreiben sie zur Testmethode. @throws .[filter] ----------------- -Es ist die gleiche Verwendung von `Assert::exception()` innerhalb einer Testmethode. Die Schreibweise ist jedoch lesbarer: +Ist äquivalent zur Verwendung von `Assert::exception()` innerhalb der Testmethode. Die Schreibweise ist jedoch übersichtlicher: ```php /** @@ -103,7 +103,7 @@ public function testOne() /** - * @throws LogicException Falsche Reihenfolge der Argumente + * @throws LogicException Wrong argument order */ public function testTwo() { @@ -114,9 +114,9 @@ public function testTwo() @dataProvider .[filter] ----------------------- -Diese Anmerkung eignet sich, wenn wir die Testmethode mehrfach, aber mit unterschiedlichen Argumenten ausführen wollen. (Nicht zu verwechseln mit der gleichnamigen Annotation für [Dateien |test-annotations#dataProvider]). +Wenn wir eine Testmethode mehrfach, aber mit unterschiedlichen Parametern ausführen möchten, ist diese Annotation nützlich. (Nicht zu verwechseln mit der gleichnamigen Annotation für [Dateien |test-annotations#dataProvider].) -Als Argument geben wir den Namen der Methode an, die Parameter für die Testmethode zurückgibt. Die Methode muss ein Array oder Traversable zurückgeben. Einfaches Beispiel: +Dahinter geben wir den Namen der Methode an, die die Argumente für die Testmethode zurückgibt. Die Methode muss ein Array oder Traversable zurückgeben. Ein einfaches Beispiel: ```php public function getLoopArgs() @@ -138,7 +138,7 @@ public function testLoop($a, $b, $c) } ``` -Die andere Variante der Annotation **@dataProvider** akzeptiert einen Pfad zur INI-Datei (relativ zur Testdatei) als Argument. Die Methode wird so oft aufgerufen, wie die Anzahl der in der INI-Datei enthaltenen Abschnitte. Datei `loop-args.ini`: +Die zweite Variante der Annotation **@dataProvider** akzeptiert als Parameter den Pfad zu einer INI-Datei (relativ zur Testdatei). Die Methode wird so oft aufgerufen, wie es Abschnitte in der INI-Datei gibt. Datei `loop-args.ini`: ```ini [one] @@ -169,7 +169,7 @@ public function testLoop($a, $b, $c) } ``` -In ähnlicher Weise können wir den Pfad zu einem PHP-Skript anstelle der INI-Datei übergeben. Es muss Array oder Traversable zurückgeben. Datei `loop-args.php`: +Ähnlich können wir anstelle einer INI-Datei auf ein PHP-Skript verweisen. Dieses muss ein Array oder Traversable zurückgeben. Datei `loop-args.php`: ```php return [ diff --git a/tester/de/writing-tests.texy b/tester/de/writing-tests.texy index 68c6884b72..2c5b02a29e 100644 --- a/tester/de/writing-tests.texy +++ b/tester/de/writing-tests.texy @@ -1,21 +1,20 @@ -Tests zum Schreiben -******************* +Tests schreiben +*************** .[perex] -Das Schreiben von Tests für Nette Tester ist einzigartig, da jeder Test ein PHP-Skript ist, das eigenständig ausgeführt werden kann. Das hat großes Potenzial. -Während Sie den Test schreiben, können Sie ihn einfach ausführen, um zu sehen, ob er richtig funktioniert. Wenn nicht, können Sie ihn in der IDE einfach durchgehen und nach einem Fehler suchen. +Das Schreiben von Tests für Nette Tester ist einzigartig, da jeder Test ein PHP-Skript ist, das separat ausgeführt werden kann. Dies birgt großes Potenzial. Schon beim Schreiben des Tests können Sie ihn einfach ausführen und feststellen, ob er korrekt funktioniert. Wenn nicht, können Sie ihn leicht in der IDE debuggen und den Fehler suchen. -Sie können den Test sogar in einem Browser öffnen. Vor allem aber führen Sie den Test aus, indem Sie ihn ausführen. Sie werden sofort erfahren, ob er bestanden hat oder nicht. +Sie können den Test sogar im Browser öffnen. Aber vor allem - indem Sie ihn ausführen, führen Sie den Test durch. Sie erfahren sofort, ob er bestanden hat oder fehlgeschlagen ist. -Im Einführungskapitel haben wir einen wirklich trivialen Test zur Verwendung eines PHP-Arrays [gezeigt |guide#What Makes Tester Unique?]. Jetzt werden wir unsere eigene Klasse erstellen, die wir testen werden, obwohl sie auch einfach sein wird. +Im Einführungskapitel haben wir einen wirklich trivialen Test zur Arbeit mit einem Array [gezeigt |guide#Was macht Tester einzigartig]. Jetzt erstellen wir unsere eigene Klasse, die wir testen werden, auch wenn sie ebenfalls einfach sein wird. -Beginnen wir mit einem typischen Verzeichnislayout für eine Bibliothek oder ein Projekt. Es ist wichtig, die Tests vom Rest des Codes zu trennen, z. B. aus Gründen der Bereitstellung, da wir die Tests nicht auf den Server hochladen wollen. Die Struktur könnte wie folgt aussehen: +Beginnen wir mit einer typischen Verzeichnisstruktur für eine Bibliothek oder ein Projekt. Es ist wichtig, die Tests vom restlichen Code zu trennen, beispielsweise wegen des Deployments, da wir Tests nicht auf den Produktionsserver hochladen möchten. Die Struktur könnte etwa so aussehen: ``` -├── src/ # code that we will test +├── src/ # Code, den wir testen werden │ ├── Rectangle.php │ └── ... -├── tests/ # tests +├── tests/ # Tests │ ├── bootstrap.php │ ├── RectangleTest.php │ └── ... @@ -23,7 +22,7 @@ Beginnen wir mit einem typischen Verzeichnislayout für eine Bibliothek oder ein └── composer.json ``` -Und nun werden wir einzelne Dateien erstellen. Wir beginnen mit der getesteten Klasse, die wir in der Datei `src/Rectangle.php` +Und nun erstellen wir die einzelnen Dateien. Beginnen wir mit der zu testenden Klasse, die wir in der Datei `src/Rectangle.php` platzieren: ```php .{file:src/Rectangle.php} <?php @@ -35,7 +34,7 @@ class Rectangle public function __construct(float $width, float $height) { if ($width < 0 || $height < 0) { - throw new InvalidArgumentException('The dimension must not be negative.'); + throw new InvalidArgumentException('Die Abmessung darf nicht negativ sein.'); } $this->width = $width; $this->height = $height; @@ -53,7 +52,7 @@ class Rectangle } ``` -Und wir erstellen einen Test für sie. Der Name der Testdatei sollte mit der Maske `*Test.php` oder `*.phpt` übereinstimmen, wir werden die Variante `RectangleTest.php` wählen: +Und wir erstellen einen Test dafür. Der Dateiname des Tests sollte dem Muster `*Test.php` oder `*.phpt` entsprechen, wählen wir beispielsweise die Variante `RectangleTest.php`: ```php .{file:tests/RectangleTest.php} @@ -64,29 +63,29 @@ require __DIR__ . '/bootstrap.php'; // allgemeines Rechteck $rect = new Rectangle(10, 20); -Assert::same(200.0, $rect->getArea()); # wir werden die erwarteten Ergebnisse überprüfen +Assert::same(200.0, $rect->getArea()); # überprüfen die erwarteten Ergebnisse Assert::false($rect->isSquare()); ``` -Wie Sie sehen, werden [Assertion-Methoden |Assertions] wie `Assert::same()` verwendet, um sicherzustellen, dass ein tatsächlicher Wert mit einem erwarteten Wert übereinstimmt. +Wie Sie sehen, werden sogenannte [Assertion-Methoden |assertions] wie `Assert::same()` verwendet, um zu bestätigen, dass der tatsächliche Wert dem erwarteten Wert entspricht. -Der letzte Schritt besteht darin, die Datei `bootstrap.php` zu erstellen. Sie enthält einen gemeinsamen Code für alle Tests. Zum Beispiel das automatische Laden von Klassen, die Umgebungskonfiguration, die Erstellung temporärer Verzeichnisse, Hilfsprogramme und ähnliches. Jeder Test lädt den Bootstrap und kümmert sich nur um den Test. Der Bootstrap kann wie folgt aussehen: +Der letzte Schritt ist die Datei `bootstrap.php`. Sie enthält Code, der für alle Tests gemeinsam ist, z. B. Autoloading von Klassen, Umgebungskonfiguration, Erstellung eines temporären Verzeichnisses, Hilfsfunktionen und Ähnliches. Alle Tests laden den Bootstrap und widmen sich dann nur noch dem Testen. Der Bootstrap kann wie folgt aussehen: ```php .{file:tests/bootstrap.php} <?php -require __DIR__ . '/vendor/autoload.php'; # Composer Autoloader laden +require __DIR__ . '/vendor/autoload.php'; # lädt den Composer Autoloader -Tester\Environment::setup(); # Initialisierung des Nette-Testers +Tester\Environment::setup(); # Initialisierung von Nette Tester -// und andere Konfigurationen (nur ein Beispiel, in unserem Fall werden sie nicht benötigt) +// und weitere Konfigurationen (dies ist nur ein Beispiel, in unserem Fall nicht benötigt) date_default_timezone_set('Europe/Prague'); define('TmpDir', '/tmp/app-tests'); ``` .[note] -Dieser Bootstrap geht davon aus, dass der Composer Autoloader auch die Klasse `Rectangle.php` laden kann. Dies kann z.B. durch das [Setzen der Autoload-Sektion |best-practices:composer#autoloading] in `composer.json` erreicht werden, usw. +Der angegebene Bootstrap geht davon aus, dass der Composer Autoloader auch die Klasse `Rectangle.php` laden kann. Dies kann beispielsweise durch [Einstellen des autoload-Abschnitts |best-practices:composer#Autoloading] in `composer.json` usw. erreicht werden. -Wir können den Test nun wie jedes andere eigenständige PHP-Skript von der Kommandozeile aus ausführen. Beim ersten Durchlauf werden alle Syntaxfehler aufgedeckt, und wenn Sie keinen Tippfehler gemacht haben, werden Sie sehen: +Den Test können wir nun von der Kommandozeile aus starten, wie jedes andere eigenständige PHP-Skript. Der erste Start deckt eventuelle Syntaxfehler auf, und wenn nirgends ein Tippfehler ist, wird Folgendes ausgegeben: /--pre .[terminal] $ php RectangleTest.php @@ -94,7 +93,7 @@ $ php RectangleTest.php <span style="color:#FFF; background-color:#090">OK</span> \-- -Wenn wir im Test die Anweisung in false `Assert::same(123, $rect->getArea());` ändern, wird dies geschehen: +Wenn wir im Test die Assertion auf einen falschen Wert ändern `Assert::same(123, $rect->getArea());`, passiert Folgendes: /--pre .[terminal] $ php RectangleTest.php @@ -107,35 +106,35 @@ $ php RectangleTest.php \-- -Beim Schreiben von Tests ist es gut, alle Extremsituationen abzufangen. Zum Beispiel, wenn die Eingabe Null ist, eine negative Zahl, in anderen Fällen eine leere Zeichenkette, null, usw. Das zwingt Sie dazu, darüber nachzudenken und zu entscheiden, wie sich der Code in solchen Situationen verhalten soll. Die Tests korrigieren dann das Verhalten. +Beim Schreiben von Tests ist es gut, alle Grenzfälle abzudecken. Zum Beispiel, wenn die Eingabe Null, eine negative Zahl ist, in anderen Fällen vielleicht eine leere Zeichenkette, null usw. Tatsächlich zwingt es Sie, darüber nachzudenken und zu entscheiden, wie sich der Code in solchen Situationen verhalten soll. Die Tests fixieren dann das Verhalten. -In unserem Fall sollte ein negativer Wert eine Ausnahme auslösen, die wir mit [Assert::exception() |Assertions#Assert::exception] überprüfen: +In unserem Fall soll ein negativer Wert eine Ausnahme auslösen, was wir mit [Assert::exception() |Assertions#Assert::exception] überprüfen: ```php .{file:tests/RectangleTest.php} -// die Breite darf keine negative Zahl sein +// Breite darf nicht negativ sein Assert::exception( fn() => new Rectangle(-1, 20), InvalidArgumentException::class, - 'Die Dimension darf nicht negativ sein.', + 'Die Abmessung darf nicht negativ sein.', ); ``` -Und wir fügen einen ähnlichen Test für die Höhe hinzu. Schließlich testen wir, dass `isSquare()` `true` zurückgibt, wenn beide Dimensionen gleich sind. Versuchen Sie, solche Tests als Übung zu schreiben. +Und einen ähnlichen Test fügen wir für die Höhe hinzu. Schließlich testen wir, ob `isSquare()` `true` zurückgibt, wenn beide Dimensionen gleich sind. Versuchen Sie als Übung, solche Tests zu schreiben. -Gut angeordnete Tests .[#toc-well-arranged-tests] -================================================= +Übersichtlichere Tests +====================== -Die Größe der Testdatei kann zunehmen und schnell unübersichtlich werden. Daher ist es sinnvoll, einzelne Testbereiche in separaten Funktionen zu gruppieren. +Die Größe der Testdatei kann zunehmen und schnell unübersichtlich werden. Daher ist es praktisch, einzelne Testbereiche in separate Funktionen zu gruppieren. -Zunächst zeigen wir eine einfachere, aber elegante Variante, die die globale Funktion "test()" verwendet. Der Tester erstellt sie nicht automatisch, um eine Kollision zu vermeiden, wenn Sie eine Funktion mit demselben Namen in Ihrem Code haben. Sie wird nur durch die Methode `setupFunctions()` erstellt, die Sie in der Datei `bootstrap.php` aufrufen: +Zuerst zeigen wir eine einfachere, aber elegante Variante, und zwar mithilfe der globalen Funktion `test()`. Tester erstellt sie nicht automatisch, um Kollisionen zu vermeiden, falls Sie eine Funktion mit demselben Namen im Code haben. Sie wird erst von der Methode `setupFunctions()` erstellt, die Sie in der Datei `bootstrap.php` aufrufen: ```php .{file:tests/bootstrap.php} Tester\Environment::setup(); Tester\Environment::setupFunctions(); ``` -Mit dieser Funktion können wir die Testdatei schön in benannte Einheiten unterteilen. Bei der Ausführung werden die Bezeichnungen nacheinander angezeigt. +Mit dieser Funktion können wir die Testdatei schön in benannte Einheiten unterteilen. Beim Ausführen werden die Beschreibungen nacheinander ausgegeben. ```php .{file:tests/RectangleTest.php} <?php @@ -143,19 +142,19 @@ use Tester\Assert; require __DIR__ . '/bootstrap.php'; -test('general oblong', function () { +test('allgemeines Rechteck', function () { $rect = new Rectangle(10, 20); Assert::same(200.0, $rect->getArea()); Assert::false($rect->isSquare()); }); -test('general square', function () { +test('allgemeines Quadrat', function () { $rect = new Rectangle(5, 5); Assert::same(25.0, $rect->getArea()); Assert::true($rect->isSquare()); }); -test('dimensions must not be negative', function () { +test('Abmessungen dürfen nicht negativ sein', function () { Assert::exception( fn() => new Rectangle(-1, 20), InvalidArgumentException::class, @@ -168,15 +167,15 @@ test('dimensions must not be negative', function () { }); ``` -Wenn Sie den Code vor oder nach jedem Test ausführen müssen, übergeben Sie ihn an `setUp()` oder `tearDown()`: +Wenn Sie vor oder nach jedem Test Code ausführen müssen, übergeben Sie ihn der Funktion `setUp()` bzw. `tearDown()`: ```php setUp(function () { - // Initialisierungscode, der vor jedem test() auszuführen ist + // Initialisierungscode, der vor jedem test() ausgeführt wird }); ``` -Die zweite Variante ist das Objekt. Wir werden den so genannten TestCase erstellen, eine Klasse, in der einzelne Einheiten durch Methoden dargestellt werden, deren Namen mit test- beginnen. +Die zweite Variante ist objektorientiert. Wir erstellen einen sogenannten TestCase, eine Klasse, in der einzelne Einheiten Methoden darstellen, deren Namen mit `test` beginnen. ```php .{file:tests/RectangleTest.php} class RectangleTest extends Tester\TestCase @@ -208,25 +207,25 @@ class RectangleTest extends Tester\TestCase } } -// Testmethoden ausführen +// Ausführung der Testmethoden (new RectangleTest)->run(); ``` -Dieses Mal haben wir eine Anmerkung `@throw` verwendet, um auf Ausnahmen zu testen. Weitere Informationen finden Sie im Kapitel [TestCase]. +Zum Testen von Ausnahmen haben wir diesmal die Annotation `@throws` verwendet. Mehr dazu erfahren Sie im Kapitel [TestCase |TestCase]. -Helfer-Funktionen .[#toc-helpers-functions] -=========================================== +Hilfsfunktionen +=============== -Nette Tester enthält mehrere Klassen und Funktionen, die Ihnen das Testen erleichtern können, z.B. Helfer zum Testen des Inhalts eines HTML-Dokuments, zum Testen der Funktionen der Arbeit mit Dateien usw. +Nette Tester enthält mehrere Klassen und Funktionen, die Ihnen beispielsweise das Testen des Inhalts eines HTML-Dokuments, das Testen von Funktionen, die mit Dateien arbeiten, und so weiter erleichtern können. -Eine Beschreibung dieser Funktionen finden Sie auf der Seite [Helpers]. +Ihre Beschreibung finden Sie auf der Seite [Hilfsklassen |helpers]. -Kommentierung und Überspringen von Tests .[#toc-annotation-and-skipping-tests] -============================================================================== +Annotationen und Überspringen von Tests +======================================= -Die Testausführung kann durch Anmerkungen im phpDoc-Kommentar am Anfang der Datei beeinflusst werden. Er könnte zum Beispiel so aussehen: +Die Ausführung von Tests kann durch Annotationen in Form eines phpDoc-Kommentars am Anfang der Datei beeinflusst werden. Sie kann beispielsweise so aussehen: ```php .{file:tests/RectangleTest.php} /** @@ -235,23 +234,23 @@ Die Testausführung kann durch Anmerkungen im phpDoc-Kommentar am Anfang der Dat */ ``` -Die Anmerkungen besagen, dass der Test nur mit PHP Version 7.2 oder höher ausgeführt werden sollte und wenn die PHP-Erweiterungen pdo und pdo_pgsql vorhanden sind. Diese Anmerkungen werden vom [Befehlszeilen-Testrunner |running-tests] kontrolliert, der, wenn die Bedingungen nicht erfüllt sind, den Test überspringt und ihn mit dem Buchstaben "s" markiert - übersprungen. Sie haben jedoch keine Auswirkungen, wenn der Test manuell ausgeführt wird. +Die angegebenen Annotationen besagen, dass der Test nur mit PHP Version 7.2 oder höher und nur dann ausgeführt werden soll, wenn die PHP-Erweiterungen pdo und pdo_pgsql vorhanden sind. Diese Annotationen werden vom [Kommandozeilen-Teststarter |running-tests] berücksichtigt, der den Test überspringt, wenn die Bedingungen nicht erfüllt sind, und ihn in der Ausgabe mit dem Buchstaben `s` - skipped - markiert. Bei manueller Ausführung des Tests haben sie jedoch keine Auswirkung. -Eine Beschreibung der Annotationen finden Sie unter [Testannotationen |Test Annotations]. +Die Beschreibung der Annotationen finden Sie auf der Seite [Test-Annotationen |test-annotations]. -Der Test kann auch auf der Grundlage einer eigenen Bedingung mit `Environment::skip()` übersprungen werden. Zum Beispiel wird dieser Test unter Windows übersprungen: +Ein Test kann auch basierend auf der Erfüllung einer eigenen Bedingung mit `Environment::skip()` übersprungen werden. Zum Beispiel überspringen wir Tests unter Windows: ```php if (defined('PHP_WINDOWS_VERSION_BUILD')) { - Tester\Environment::skip('Requires UNIX.'); + Tester\Environment::skip('Benötigt UNIX.'); } ``` -Verzeichnisstruktur .[#toc-directory-structure] -=============================================== +Verzeichnisstruktur +=================== -Bei nur wenig größeren Bibliotheken oder Projekten empfiehlt es sich, das Testverzeichnis in Unterverzeichnisse entsprechend dem Namensraum der getesteten Klasse aufzuteilen: +Wir empfehlen, bei etwas größeren Bibliotheken oder Projekten das Testverzeichnis noch in Unterverzeichnisse nach dem Namespace der getesteten Klasse aufzuteilen: ``` └── tests/ @@ -276,15 +275,15 @@ tester tests/NamespaceOne \-- -Edge Cases .[#toc-edge-cases] -============================= +Spezielle Situationen +===================== -Ein Test, der keine Assertion-Methode aufruft, ist verdächtig und wird als fehlerhaft bewertet: +Ein Test, der keine einzige Assertionsmethode aufruft, ist verdächtig und wird als fehlerhaft bewertet: /--pre .[terminal] <span style="color: #FFF; background-color: #900">Error: This test forgets to execute an assertion.</span> \-- -Wenn der Test ohne Aufruf von Assertions wirklich als gültig angesehen werden soll, rufen Sie zum Beispiel `Assert::true(true)` auf. +Wenn ein Test wirklich ohne Aufruf von Assertions als gültig betrachtet werden soll, rufen Sie beispielsweise `Assert::true(true)` auf. -Es kann auch tückisch sein, `exit()` und `die()` zu verwenden, um den Test mit einer Fehlermeldung zu beenden. Zum Beispiel beendet `exit('Error in connection')` den Test mit dem Exit-Code 0, was einen Erfolg signalisiert. Verwenden Sie `Assert::fail('Error in connection')`. +Es kann auch trügerisch sein, `exit()` und `die()` zu verwenden, um einen Test mit einer Fehlermeldung zu beenden. Zum Beispiel beendet `exit('Fehler bei der Verbindung')` den Test mit dem Rückgabecode 0, was Erfolg signalisiert. Verwenden Sie `Assert::fail('Fehler bei der Verbindung')`. diff --git a/tester/el/@home.texy b/tester/el/@home.texy index a584cabb17..e7fb2d0daf 100644 --- a/tester/el/@home.texy +++ b/tester/el/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: PHP}} -{{description: Το Nette Tester είναι ένα απλό και συνάμα πολύ εύχρηστο εργαλείο ελέγχου κώδικα PHP.}} +{{maintitle: Nette Tester – άνετο testing σε PHP}} +{{description: Το Nette Tester είναι ένα απλό αλλά πολύ εύχρηστο εργαλείο για το testing κώδικα PHP.}} diff --git a/tester/el/@left-menu.texy b/tester/el/@left-menu.texy index fe056f2f24..298af12cf0 100644 --- a/tester/el/@left-menu.texy +++ b/tester/el/@left-menu.texy @@ -1,8 +1,8 @@ -- [Ξεκινώντας |guide] -- [Γράφοντας δοκιμές |Writing Tests] -- [Εκτέλεση δοκιμών |Running Tests] +- [Ξεκινώντας με το Nette Tester |guide] +- [Γράφοντας tests |writing-tests] +- [Εκτελώντας tests |running-tests] -- [Βεβαιώσεις |Assertions] -- [Σημειώσεις δοκιμών |Test Annotations] -- [TestCase |TestCase] -- [Βοηθοί |Helpers] +- [Assertions |assertions] +- [Σχολιασμοί test |test-annotations] +- [TestCase] +- [Βοηθητικές κλάσεις |helpers] diff --git a/tester/el/@menu.texy b/tester/el/@menu.texy index a85e45aa19..b67d22693c 100644 --- a/tester/el/@menu.texy +++ b/tester/el/@menu.texy @@ -1,3 +1,3 @@ -- [Αρχική σελίδα |@home] -- [Τεκμηρίωση |Guide] +- [Εισαγωγή |@home] +- [Τεκμηρίωση |guide] - "GitHub .[link-external]":https://github.com/nette/tester diff --git a/tester/el/@meta.texy b/tester/el/@meta.texy new file mode 100644 index 0000000000..8af91aed6c --- /dev/null +++ b/tester/el/@meta.texy @@ -0,0 +1 @@ +{{sitename: Tester Τεκμηρίωση}} diff --git a/tester/el/assertions.texy b/tester/el/assertions.texy index 9bba24f7aa..d9e4e27d69 100644 --- a/tester/el/assertions.texy +++ b/tester/el/assertions.texy @@ -2,34 +2,34 @@ ********** .[perex] -Οι ισχυρισμοί χρησιμοποιούνται για να βεβαιώνουν ότι μια πραγματική τιμή ταιριάζει με μια αναμενόμενη τιμή. Είναι μέθοδοι της `Tester\Assert`. +Οι ισχυρισμοί χρησιμοποιούνται για να επιβεβαιώσουν ότι η πραγματική τιμή αντιστοιχεί στην αναμενόμενη τιμή. Πρόκειται για μεθόδους της κλάσης `Tester\Assert`. -Επιλέξτε τους πιο ακριβείς ισχυρισμούς. Είναι καλύτερο `Assert::same($a, $b)` από το `Assert::true($a === $b)` επειδή εμφανίζει σημαντικό μήνυμα σφάλματος σε περίπτωση αποτυχίας. Στη δεύτερη περίπτωση παίρνουμε μόνο το `false should be true` και δεν λέει τίποτα για τα περιεχόμενα των μεταβλητών $a και $b. +Επιλέξτε τους καταλληλότερους ισχυρισμούς. Είναι καλύτερο το `Assert::same($a, $b)` από το `Assert::true($a === $b)`, επειδή σε περίπτωση αποτυχίας εμφανίζει ένα ουσιαστικό μήνυμα σφάλματος. Στη δεύτερη περίπτωση, μόνο το `false should be true` το οποίο δεν μας λέει τίποτα για το περιεχόμενο των μεταβλητών `$a` και `$b`. -Οι περισσότεροι ισχυρισμοί μπορούν επίσης να έχουν ένα προαιρετικό `$description` που εμφανίζεται στο μήνυμα σφάλματος αν η προσδοκία αποτύχει. +Οι περισσότεροι ισχυρισμοί μπορούν επίσης να έχουν μια προαιρετική περιγραφή στην παράμετρο `$description`, η οποία εμφανίζεται στο μήνυμα σφάλματος εάν η προσδοκία αποτύχει. -Τα παραδείγματα υποθέτουν ότι έχει οριστεί το ακόλουθο ψευδώνυμο κλάσης: +Τα παραδείγματα προϋποθέτουν τη δημιουργία ενός alias: ```php use Tester\Assert; ``` -Assert::same($expected, $actual, string $description=null) .[method] --------------------------------------------------------------------- -`$expected` πρέπει να είναι το ίδιο με το `$actual`. Είναι το ίδιο με τον τελεστή PHP `===`. +Assert::same($expected, $actual, ?string $description=null) .[method] +--------------------------------------------------------------------- +Το `$expected` πρέπει να είναι ταυτόσημο με το `$actual`. Το ίδιο με τον τελεστή PHP `===`. -Assert::notSame($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------ -Αντίθετος του `Assert::same()`, άρα είναι ίδιος με τον τελεστή PHP `!==`. +Assert::notSame($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------ +Το αντίθετο του `Assert::same()`, δηλαδή το ίδιο με τον τελεστή PHP `!==`. -Assert::equal($expected, $actual, string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] ------------------------------------------------------------------------------------------------------------------------- -`$expected` πρέπει να είναι το ίδιο με το `$actual`. Σε αντίθεση με το `Assert::same()`, η ταυτότητα αντικειμένων, η σειρά των ζευγών κλειδιών => τιμή σε πίνακες και οι οριακά διαφορετικοί δεκαδικοί αριθμοί αγνοούνται, οι οποίοι μπορούν να αλλάξουν με τη ρύθμιση των `$matchIdentity` και `$matchOrder`. +Assert::equal($expected, $actual, ?string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] +------------------------------------------------------------------------------------------------------------------------- +Το `$expected` πρέπει να είναι ίδιο με το `$actual`. Σε αντίθεση με το `Assert::same()`, αγνοείται η ταυτότητα των αντικειμένων, η σειρά των ζευγών κλειδιού => τιμής στους πίνακες και οι οριακά διαφορετικοί δεκαδικοί αριθμοί, κάτι που μπορεί να αλλάξει ρυθμίζοντας τα `$matchIdentity` και `$matchOrder`. -Οι ακόλουθες περιπτώσεις είναι ταυτόσημες από την άποψη του `equal()`, αλλά όχι για το `same()`: +Οι ακόλουθες περιπτώσεις είναι ίδιες από την άποψη του `equal()`, αλλά όχι του `same()`: ```php Assert::equal(0.3, 0.1 + 0.2); @@ -40,81 +40,81 @@ Assert::equal( ); ``` -Ωστόσο, προσέξτε, ο πίνακας `[1, 2]` και `[2, 1]` δεν είναι ίσες, επειδή διαφέρει μόνο η σειρά των τιμών, όχι τα ζεύγη κλειδί => τιμή. Ο πίνακας `[1, 2]` μπορεί επίσης να γραφτεί ως `[0 => 1, 1 => 2]` και επομένως `[1 => 2, 0 => 1]` θα θεωρηθεί ίσος. +Ωστόσο, προσοχή, οι πίνακες `[1, 2]` και `[2, 1]` δεν είναι ίδιοι, επειδή διαφέρουν μόνο στη σειρά των τιμών, όχι των ζευγών κλειδιού => τιμής. Ο πίνακας `[1, 2]` μπορεί επίσης να γραφτεί ως `[0 => 1, 1 => 2]` και επομένως ως ίδιος θα θεωρηθεί ο `[1 => 2, 0 => 1]`. -Μπορείτε επίσης να χρησιμοποιήσετε τις λεγόμενες [προσδοκίες |#expectations] στο `$expected`. +Επιπλέον, στο `$expected` μπορούν να χρησιμοποιηθούν οι λεγόμενες [#Προσδοκίες]. -Assert::notEqual($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------- -Σε αντίθεση με το `Assert::equal()`. +Assert::notEqual($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------- +Το αντίθετο του `Assert::equal()`. -Assert::contains($needle, string|array $actual, string $description=null) .[method] ------------------------------------------------------------------------------------ -Εάν το `$actual` είναι μια συμβολοσειρά, πρέπει να περιέχει την υποσυμβολοσειρά `$needle`. Εάν είναι πίνακας, πρέπει να περιέχει το στοιχείο `$needle` (συγκρίνεται αυστηρά). +Assert::contains($needle, string|array $actual, ?string $description=null) .[method] +------------------------------------------------------------------------------------ +Εάν το `$actual` είναι string, πρέπει να περιέχει το substring `$needle`. Εάν είναι array, πρέπει να περιέχει το στοιχείο `$needle` (συγκρίνεται αυστηρά). -Assert::notContains($needle, string|array $actual, string $description=null) .[method] --------------------------------------------------------------------------------------- -Αντίθετο από το `Assert::contains()`. +Assert::notContains($needle, string|array $actual, ?string $description=null) .[method] +--------------------------------------------------------------------------------------- +Το αντίθετο του `Assert::contains()`. -Assert::hasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} -------------------------------------------------------------------------------------------------------- -`$actual` πρέπει να είναι ένας πίνακας και να περιέχει το κλειδί `$needle`. +Assert::hasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +-------------------------------------------------------------------------------------------------------- +Το `$actual` πρέπει να είναι array και πρέπει να περιέχει το κλειδί `$needle`. -Assert::notHasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} ----------------------------------------------------------------------------------------------------------- -`$actual` πρέπει να είναι πίνακας και να μην περιέχει το κλειδί `$needle`. +Assert::notHasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +----------------------------------------------------------------------------------------------------------- +Το `$actual` πρέπει να είναι array και δεν πρέπει να περιέχει το κλειδί `$needle`. -Assert::true($value, string $description=null) .[method] --------------------------------------------------------- -`$value` πρέπει να είναι `true`, οπότε `$value === true`. +Assert::true($value, ?string $description=null) .[method] +--------------------------------------------------------- +Το `$value` πρέπει να είναι `true`, δηλαδή `$value === true`. -Assert::truthy($value, string $description=null) .[method] ----------------------------------------------------------- -`$value` πρέπει να είναι αληθές, άρα ικανοποιεί τη συνθήκη `if ($value) ...`. +Assert::truthy($value, ?string $description=null) .[method] +----------------------------------------------------------- +Το `$value` πρέπει να είναι αληθές (truthy), δηλαδή να ικανοποιεί τη συνθήκη `if ($value) ...`. -Assert::false($value, string $description=null) .[method] ---------------------------------------------------------- -`$value` πρέπει να είναι `false`, άρα `$value === false`. +Assert::false($value, ?string $description=null) .[method] +---------------------------------------------------------- +Το `$value` πρέπει να είναι `false`, δηλαδή `$value === false`. -Assert::falsey($value, string $description=null) .[method] ----------------------------------------------------------- -`$value` πρέπει να είναι ψευδής, άρα ικανοποιεί τη συνθήκη `if (!$value) ...`. +Assert::falsey($value, ?string $description=null) .[method] +----------------------------------------------------------- +Το `$value` πρέπει να είναι ψευδές (falsey), δηλαδή να ικανοποιεί τη συνθήκη `if (!$value) ...`. -Assert::null($value, string $description=null) .[method] --------------------------------------------------------- -`$value` πρέπει να είναι `null`, άρα `$value === null`. +Assert::null($value, ?string $description=null) .[method] +--------------------------------------------------------- +Το `$value` πρέπει να είναι `null`, δηλαδή `$value === null`. -Assert::notNull($value, string $description=null) .[method] ------------------------------------------------------------ -`$value` δεν πρέπει να είναι `null`, οπότε `$value !== null`. +Assert::notNull($value, ?string $description=null) .[method] +------------------------------------------------------------ +Το `$value` δεν πρέπει να είναι `null`, δηλαδή `$value !== null`. -Assert::nan($value, string $description=null) .[method] -------------------------------------------------------- -`$value` πρέπει να είναι Not a Number. Χρησιμοποιήστε μόνο το `Assert::nan()` για δοκιμές NAN. Η τιμή NAN είναι πολύ συγκεκριμένη και οι ισχυρισμοί `Assert::same()` ή `Assert::equal()` μπορεί να συμπεριφέρονται απρόβλεπτα. +Assert::nan($value, ?string $description=null) .[method] +-------------------------------------------------------- +Το `$value` πρέπει να είναι Not a Number. Για τον έλεγχο της τιμής NAN χρησιμοποιείτε αποκλειστικά το `Assert::nan()`. Η τιμή NAN είναι πολύ συγκεκριμένη και οι assertions `Assert::same()` ή `Assert::equal()` μπορεί να λειτουργήσουν απροσδόκητα. -Assert::count($count, Countable|array $value, string $description=null) .[method] ---------------------------------------------------------------------------------- -Ο αριθμός των στοιχείων στο `$value` πρέπει να είναι `$count`. Άρα το ίδιο με το `count($value) === $count`. +Assert::count($count, Countable|array $value, ?string $description=null) .[method] +---------------------------------------------------------------------------------- +Ο αριθμός των στοιχείων στο `$value` πρέπει να είναι `$count`. Δηλαδή το ίδιο με `count($value) === $count`. -Assert::type(string|object $type, $value, string $description=null) .[method] ------------------------------------------------------------------------------ -`$value` πρέπει να είναι συγκεκριμένου τύπου. Ως `$type` μπορούμε να χρησιμοποιήσουμε το string: +Assert::type(string|object $type, $value, ?string $description=null) .[method] +------------------------------------------------------------------------------ +Το `$value` πρέπει να είναι του δεδομένου τύπου. Ως `$type` μπορούμε να χρησιμοποιήσουμε μια συμβολοσειρά: - `array` -- `list` - πίνακας με ευρετήριο σε αύξουσα σειρά αριθμητικών κλειδιών από το μηδέν +- `list` - πίνακας με δείκτες σε αύξουσα σειρά αριθμητικών κλειδιών από το μηδέν - `bool` - `callable` - `float` @@ -124,14 +124,14 @@ Assert::type(string|object $type, $value, string $description=null) .[method] - `resource` - `scalar` - `string` -- το όνομα της κλάσης ή του αντικειμένου απευθείας, τότε πρέπει να περάσει `$value instanceof $type` +- όνομα κλάσης ή απευθείας αντικείμενο, τότε το `$value` πρέπει να είναι `instanceof $type` -Assert::exception(callable $callable, string $class, string $message=null, $code=null) .[method] ------------------------------------------------------------------------------------------------- -Κατά την κλήση του `$callable` πρέπει να εκπέμπεται μια εξαίρεση της περίπτωσης `$class`. Αν περάσουμε το `$message`, το μήνυμα της εξαίρεσης πρέπει να [ταιριάζει |#assert-match]. Και αν περάσουμε το `$code`, ο κωδικός της εξαίρεσης πρέπει να είναι ο ίδιος. +Assert::exception(callable $callable, string $class, ?string $message=null, $code=null) .[method] +------------------------------------------------------------------------------------------------- +Κατά την κλήση του `$callable` πρέπει να δημιουργηθεί μια εξαίρεση της κλάσης `$class`. Εάν αναφέρουμε `$message`, πρέπει να [αντιστοιχεί στο πρότυπο |#Assert::match] και το μήνυμα της εξαίρεσης, και αν αναφέρουμε `$code`, πρέπει να ταιριάζουν αυστηρά και οι κωδικοί. -Για παράδειγμα, αυτή η δοκιμή αποτυγχάνει επειδή το μήνυμα της εξαίρεσης δεν ταιριάζει: +Η ακόλουθη δοκιμή αποτυγχάνει, επειδή το μήνυμα της εξαίρεσης δεν αντιστοιχεί: ```php Assert::exception( @@ -141,7 +141,7 @@ Assert::exception( ); ``` -Το `Assert::exception()` επιστρέφει μια πεταμένη εξαίρεση, οπότε μπορείτε να ελέγξετε μια εμφωλευμένη εξαίρεση. +Το `Assert::exception()` επιστρέφει την εξαίρεση που δημιουργήθηκε, οπότε μπορεί να ελεγχθεί και μια ένθετη εξαίρεση. ```php $e = Assert::exception( @@ -154,9 +154,9 @@ Assert::type(RuntimeException::class, $e->getPrevious()); ``` -Assert::error(string $callable, int|string|array $type, string $message=null) .[method] ---------------------------------------------------------------------------------------- -Ελέγχει ότι η κλήση του `$callable` παράγει τα αναμενόμενα σφάλματα (δηλαδή προειδοποιήσεις, ειδοποιήσεις κ.λπ.). Ως `$type` καθορίζουμε μία από τις σταθερές `E_...`, για παράδειγμα `E_WARNING`. Και αν περάσει το `$message`, το μήνυμα σφάλματος πρέπει επίσης να [ταιριάζει με |#assert-match] το πρότυπο. Για παράδειγμα: +Assert::error(string $callable, int|string|array $type, ?string $message=null) .[method] +---------------------------------------------------------------------------------------- +Ελέγχει ότι η συνάρτηση `$callable` δημιούργησε τα αναμενόμενα σφάλματα (δηλ. προειδοποιήσεις, ειδοποιήσεις κ.λπ.). Ως `$type` αναφέρουμε μία από τις σταθερές `E_...`, δηλαδή για παράδειγμα `E_WARNING`. Και αν αναφέρουμε `$message`, πρέπει να [αντιστοιχεί στο πρότυπο |#Assert::match] και το μήνυμα σφάλματος. Για παράδειγμα: ```php Assert::error( @@ -166,7 +166,7 @@ Assert::error( ); ``` -Εάν η επανάκληση παράγει περισσότερα σφάλματα, πρέπει να τα περιμένουμε όλα με την ακριβή σειρά. Σε αυτή την περίπτωση περνάμε τον πίνακα στο `$type`: +Εάν το callback δημιουργήσει περισσότερα σφάλματα, πρέπει να τα περιμένουμε όλα με την ακριβή σειρά. Σε αυτήν την περίπτωση, περνάμε στο `$type` έναν πίνακα: ```php Assert::error(function () { @@ -179,108 +179,108 @@ Assert::error(function () { ``` .[note] -Εάν το `$type` είναι όνομα κλάσης, αυτή η δήλωση συμπεριφέρεται όπως το `Assert::exception()`. +Εάν ως `$type` αναφέρετε όνομα κλάσης, συμπεριφέρεται το ίδιο με το `Assert::exception()`. Assert::noError(callable $callable) .[method] --------------------------------------------- -Ελέγχει ότι η συνάρτηση `$callable` δεν πετάει καμία προειδοποίηση/ειδοποίηση/σφάλμα ή εξαίρεση της PHP. Είναι χρήσιμη για τον έλεγχο ενός κομματιού κώδικα όπου δεν υπάρχει άλλος ισχυρισμός. +Ελέγχει ότι η συνάρτηση `$callable` δεν δημιούργησε καμία προειδοποίηση, σφάλμα ή εξαίρεση. Είναι χρήσιμο για τον έλεγχο κομματιών κώδικα όπου δεν υπάρχει καμία άλλη assertion. -Assert::match(string $pattern, $actual, string $description=null) .[method] ---------------------------------------------------------------------------- -`$actual` πρέπει να ταιριάζει με το `$pattern`. Μπορούμε να χρησιμοποιήσουμε δύο παραλλαγές προτύπων: κανονικές εκφράσεις ή μπαλαντέρ. +Assert::match(string $pattern, $actual, ?string $description=null) .[method] +---------------------------------------------------------------------------- +Το `$actual` πρέπει να ταιριάζει με το πρότυπο `$pattern`. Μπορούμε να χρησιμοποιήσουμε δύο παραλλαγές προτύπων: κανονικές εκφράσεις ή μπαλαντέρ. -Εάν περάσουμε μια κανονική έκφραση ως `$pattern`, πρέπει να χρησιμοποιήσουμε το `~` or `#` για να την οριοθετήσουμε. Άλλα διαχωριστικά δεν υποστηρίζονται. Για παράδειγμα, το test όπου το `$var` πρέπει να περιέχει μόνο δεκαεξαδικά ψηφία: +Εάν ως `$pattern` περάσουμε μια κανονική έκφραση, για τον οριοθέτησή της πρέπει να χρησιμοποιήσουμε `~` ή `#`, άλλοι οριοθέτες δεν υποστηρίζονται. Για παράδειγμα, μια δοκιμή όπου το `$var` πρέπει να περιέχει μόνο δεκαεξαδικούς αριθμούς: ```php Assert::match('#^[0-9a-f]$#i', $var); ``` -Η άλλη παραλλαγή είναι παρόμοια με τη σύγκριση συμβολοσειρών, αλλά μπορούμε να χρησιμοποιήσουμε κάποια άγρια σύμβολα στο `$pattern`: - -- `%a%` ένα ή περισσότερα από οτιδήποτε εκτός από τους χαρακτήρες τέλους γραμμής -- `%a?%` μηδέν ή περισσότερα από οτιδήποτε εκτός από τους χαρακτήρες τέλους γραμμής -- `%A%` ένα ή περισσότερα από οτιδήποτε, συμπεριλαμβανομένων των χαρακτήρων τέλους γραμμής -- `%A?%` μηδέν ή περισσότερα από οτιδήποτε, συμπεριλαμβανομένων των χαρακτήρων τέλους γραμμής -- `%s%` ένας ή περισσότεροι χαρακτήρες λευκού διαστήματος εκτός από τους χαρακτήρες τέλους γραμμής -- `%s?%` μηδέν ή περισσότεροι λευκοί χαρακτήρες εκτός από τους χαρακτήρες τέλους γραμμής -- `%S%` ένας ή περισσότεροι χαρακτήρες εκτός από το λευκό διάστημα -- `%S?%` μηδέν ή περισσότεροι χαρακτήρες εκτός από το λευκό διάστημα -- `%c%` ένας μόνο χαρακτήρας οποιουδήποτε είδους (εκτός από το τέλος της γραμμής) +Η δεύτερη παραλλαγή είναι παρόμοια με τη συνήθη σύγκριση συμβολοσειρών, αλλά στο `$pattern` μπορούμε να χρησιμοποιήσουμε διάφορους μπαλαντέρ: + +- `%a%` ένας ή περισσότεροι χαρακτήρες, εκτός από τους χαρακτήρες τέλους γραμμής +- `%a?%` κανένας ή περισσότεροι χαρακτήρες, εκτός από τους χαρακτήρες τέλους γραμμής +- `%A%` ένας ή περισσότεροι χαρακτήρες, συμπεριλαμβανομένων των χαρακτήρων τέλους γραμμής +- `%A?%` κανένας ή περισσότεροι χαρακτήρες, συμπεριλαμβανομένων των χαρακτήρων τέλους γραμμής +- `%s%` ένας ή περισσότεροι λευκοί χαρακτήρες, εκτός από τους χαρακτήρες τέλους γραμμής +- `%s?%` κανένας ή περισσότεροι λευκοί χαρακτήρες, εκτός από τους χαρακτήρες τέλους γραμμής +- `%S%` ένας ή περισσότεροι χαρακτήρες, εκτός από τους λευκούς χαρακτήρες +- `%S?%` κανένας ή περισσότεροι χαρακτήρες, εκτός από τους λευκούς χαρακτήρες +- `%c%` οποιοσδήποτε ένας χαρακτήρας, εκτός από τον χαρακτήρα τέλους γραμμής - `%d%` ένα ή περισσότερα ψηφία -- `%d?%` μηδέν ή περισσότερα ψηφία +- `%d?%` κανένα ή περισσότερα ψηφία - `%i%` προσημασμένη ακέραια τιμή -- `%f%` αριθμός κινητής υποδιαστολής -- `%h%` ένα ή περισσότερα ψηφία HEX +- `%f%` αριθμός με δεκαδικό σημείο +- `%h%` ένα ή περισσότερα δεκαεξαδικά ψηφία - `%w%` ένας ή περισσότεροι αλφαριθμητικοί χαρακτήρες -- `%%` ένας χαρακτήρας % +- `%%` ο χαρακτήρας % Παραδείγματα: ```php -# Again, hexadecimal number test +# Πάλι έλεγχος για δεκαεξαδικό αριθμό Assert::match('%h%', $var); -# Generalized path to file and line number +# Γενίκευση της διαδρομής αρχείου και του αριθμού γραμμής Assert::match('Error in file %a% on line %i%', $errorMessage); ``` -Assert::matchFile(string $file, $actual, string $description=null) .[method] ----------------------------------------------------------------------------- -Ο ισχυρισμός είναι πανομοιότυπος με την [Assert::match() |#assert-match] αλλά το μοτίβο φορτώνεται από το `$file`. Είναι χρήσιμος για τον έλεγχο πολύ μεγάλων συμβολοσειρών. Το αρχείο δοκιμής είναι αναγνώσιμο. +Assert::matchFile(string $file, $actual, ?string $description=null) .[method] +----------------------------------------------------------------------------- +Η assertion είναι ταυτόσημη με την [#Assert::match()], αλλά το πρότυπο φορτώνεται από το αρχείο `$file`. Αυτό είναι χρήσιμο για τον έλεγχο πολύ μεγάλων συμβολοσειρών. Το αρχείο με τη δοκιμή παραμένει ευανάγνωστο. Assert::fail(string $message, $actual=null, $expected=null) .[method] --------------------------------------------------------------------- -Αυτός ο ισχυρισμός πάντα αποτυγχάνει. Είναι απλά πρακτικό. Μπορούμε να περάσουμε προαιρετικά αναμενόμενες και πραγματικές τιμές. +Αυτή η assertion αποτυγχάνει πάντα. Μερικές φορές αυτό είναι απλά χρήσιμο. Προαιρετικά, μπορούμε να αναφέρουμε και την αναμενόμενη και την πραγματική τιμή. -Προσδοκίες .[#toc-expectations] -------------------------------- -Αν θέλουμε να συγκρίνουμε πιο σύνθετες δομές με μη σταθερά στοιχεία, οι παραπάνω ισχυρισμοί μπορεί να μην είναι επαρκείς. Για παράδειγμα, δοκιμάζουμε μια μέθοδο που δημιουργεί έναν νέο χρήστη και επιστρέφει τα χαρακτηριστικά του ως πίνακα. Δεν γνωρίζουμε την τιμή κατακερματισμού του κωδικού πρόσβασης, αλλά γνωρίζουμε ότι πρέπει να είναι ένα δεκαεξαδικό αλφαριθμητικό. Και το μόνο πράγμα που γνωρίζουμε για το επόμενο στοιχείο είναι ότι πρέπει να είναι ένα αντικείμενο `DateTime`. +Προσδοκίες +---------- +Όταν θέλουμε να συγκρίνουμε πιο σύνθετες δομές με μη σταθερά στοιχεία, οι παραπάνω assertions μπορεί να μην είναι επαρκείς. Για παράδειγμα, ελέγχουμε μια μέθοδο που δημιουργεί έναν νέο χρήστη και επιστρέφει τα χαρακτηριστικά του ως πίνακα. Την τιμή του hash του κωδικού πρόσβασης δεν τη γνωρίζουμε, αλλά ξέρουμε ότι πρέπει να είναι μια δεκαεξαδική συμβολοσειρά. Και για ένα άλλο στοιχείο ξέρουμε μόνο ότι πρέπει να είναι ένα αντικείμενο `DateTime`. -Σε αυτές τις περιπτώσεις, μπορούμε να χρησιμοποιήσουμε το `Tester\Expect` μέσα στην παράμετρο `$expected` των μεθόδων `Assert::equal()` και `Assert::notEqual()`, το οποίο μπορεί να χρησιμοποιηθεί για την εύκολη περιγραφή της δομής. +Σε αυτές τις καταστάσεις μπορούμε να χρησιμοποιήσουμε το `Tester\Expect` μέσα στην παράμετρο `$expected` των μεθόδων `Assert::equal()` και `Assert::notEqual()`, με τις οποίες μπορούμε εύκολα να περιγράψουμε τη δομή. ```php use Tester\Expect; Assert::equal([ - 'id' => Expect::type('int'), # we expect an integer + 'id' => Expect::type('int'), # περιμένουμε ακέραιο αριθμό 'username' => 'milo', - 'password' => Expect::match('%h%'), # we expect a string matching pattern - 'created_at' => Expect::type(DateTime::class), # we expect an instance of the class + 'password' => Expect::match('%h%'), # περιμένουμε συμβολοσειρά που ταιριάζει με το πρότυπο + 'created_at' => Expect::type(DateTime::class), # περιμένουμε παρουσία της κλάσης ], User::create(123, 'milo', 'RandomPaSsWoRd')); ``` -Με το `Expect`, μπορούμε να κάνουμε σχεδόν τους ίδιους ισχυρισμούς με το `Assert`. Έτσι έχουμε μεθόδους όπως `Expect::same()`, `Expect::match()`, `Expect::count()`, κ.λπ. Επιπλέον, μπορούμε να τις αλυσοδέσουμε όπως: +Με το `Expect` μπορούμε να εκτελέσουμε σχεδόν τις ίδιες assertions όπως με το `Assert`. Δηλαδή, έχουμε στη διάθεσή μας τις μεθόδους `Expect::same()`, `Expect::match()`, `Expect::count()` κ.λπ. Επιπλέον, μπορούμε να τις συνδέσουμε: ```php -Expect::type(MyIterator::class)->andCount(5); # we expect MyIterator and items count is 5 +Expect::type(MyIterator::class)->andCount(5); # περιμένουμε MyIterator και αριθμό στοιχείων 5 ``` -Ή, μπορούμε να γράψουμε δικούς μας χειριστές ισχυρισμών. +Ή μπορούμε να γράψουμε τους δικούς μας handlers assertions. ```php Expect::that(function ($value) { - # return false if expectation fails + # επιστρέφουμε false, εάν η προσδοκία αποτύχει }); ``` -Διερεύνηση αποτυχημένων ισχυρισμών .[#toc-failed-assertions-investigation] --------------------------------------------------------------------------- -Ο ελεγκτής δείχνει πού βρίσκεται το σφάλμα όταν ένας ισχυρισμός αποτυγχάνει. Όταν συγκρίνουμε σύνθετες δομές, ο Tester δημιουργεί dumps των συγκρινόμενων τιμών και τις αποθηκεύει στον κατάλογο `output`. Για παράδειγμα, όταν η φανταστική δοκιμή `Arrays.recursive.phpt` αποτύχει, τα dumps θα αποθηκευτούν ως εξής: +Διερεύνηση λανθασμένων assertions +--------------------------------- +Όταν μια assertion αποτυγχάνει, ο Tester εκτυπώνει πού είναι το λάθος. Εάν συγκρίνουμε πιο σύνθετες δομές, ο Tester δημιουργεί dumps των συγκρινόμενων τιμών και τις αποθηκεύει στον κατάλογο `output`. Για παράδειγμα, κατά την αποτυχία της φανταστικής δοκιμής `Arrays.recursive.phpt`, τα dumps θα αποθηκευτούν ως εξής: ``` app/ └── tests/ ├── output/ - │ ├── Arrays.recursive.actual # actual value - │ └── Arrays.recursive.expected # expected value + │ ├── Arrays.recursive.actual # πραγματική τιμή + │ └── Arrays.recursive.expected # αναμενόμενη τιμή │ - └── Arrays.recursive.phpt # failing test + └── Arrays.recursive.phpt # αποτυχημένη δοκιμή ``` -Μπορούμε να αλλάξουμε το όνομα του καταλόγου με `Tester\Dumper::$dumpDir`. +Το όνομα του καταλόγου μπορούμε να το αλλάξουμε μέσω του `Tester\Dumper::$dumpDir`. diff --git a/tester/el/guide.texy b/tester/el/guide.texy index 24bfd0da62..dccfe5b28c 100644 --- a/tester/el/guide.texy +++ b/tester/el/guide.texy @@ -1,17 +1,17 @@ -Ξεκινώντας με το Tester -*********************** +Ξεκινώντας με το Nette Tester +***************************** <div class=perex> -Ακόμη και οι καλοί προγραμματιστές κάνουν λάθη. Η διαφορά μεταξύ ενός καλού προγραμματιστή και ενός κακού είναι ότι ο καλός θα το κάνει μόνο μία φορά και την επόμενη φορά θα το εντοπίσει χρησιμοποιώντας αυτοματοποιημένες δοκιμές. +Ακόμα και οι καλοί προγραμματιστές κάνουν λάθη. Η διαφορά μεταξύ ενός καλού και ενός κακού προγραμματιστή είναι ότι ο καλός το κάνει μόνο μία φορά και την επόμενη φορά το εντοπίζει με αυτοματοποιημένες δοκιμές. -- "Αυτός που δεν κάνει δοκιμές είναι καταδικασμένος να επαναλάβει τα ίδια του τα λάθη". (σοφή παροιμία) -- "Όταν ξεφορτωνόμαστε ένα λάθος, εμφανίζεται ένα άλλο". (Νόμος του Μέρφι) -- "Κάθε φορά που μπαίνετε στον πειρασμό να εκτυπώσετε δήλωση, γράψτε την αντ' αυτού ως δοκιμή". (Martin Fowler) +- "Όποιος δεν δοκιμάζει, είναι καταδικασμένος να επαναλαμβάνει τα λάθη του." (παροιμία) +- "Μόλις απαλλαγούμε από ένα λάθος, εμφανίζεται ένα άλλο." (Νόμος του Murphy) +- "Κάθε φορά που έχετε την ανάγκη να εκτυπώσετε μια μεταβλητή στην οθόνη, γράψτε καλύτερα μια δοκιμή." (Martin Fowler) </div> -Έχετε γράψει ποτέ τον ακόλουθο κώδικα σε PHP; +Έχετε γράψει ποτέ παρόμοιο κώδικα σε PHP; ```php $obj = new MyClass; @@ -20,40 +20,40 @@ $result = $obj->process($input); var_dump($result); ``` -Έτσι, έχετε ποτέ απορρίψει το αποτέλεσμα μιας κλήσης συνάρτησης μόνο και μόνο για να ελέγξετε με το μάτι ότι επιστρέφει αυτό που πρέπει να επιστρέψει; Σίγουρα το κάνετε πολλές φορές την ημέρα. Με το χέρι στην καρδιά, αν όλα δουλεύουν, διαγράφετε αυτόν τον κώδικα και περιμένετε ότι η κλάση δεν θα χαλάσει στο μέλλον; Ο νόμος του Μέρφι εγγυάται το αντίθετο :-) +Δηλαδή, εκτυπώσατε το αποτέλεσμα της κλήσης της συνάρτησης μόνο για να επαληθεύσετε με το μάτι ότι επιστρέφει αυτό που πρέπει; Σίγουρα το κάνετε πολλές φορές την ημέρα. Με το χέρι στην καρδιά: στην περίπτωση που όλα λειτουργούν σωστά, διαγράφετε αυτόν τον κώδικα; Περιμένετε ότι η κλάση δεν θα χαλάσει στο μέλλον; Οι νόμοι του Murphy εγγυώνται το αντίθετο :-) -Στην πραγματικότητα, εσείς γράψατε το τεστ. Χρειάζεται μια μικρή τροποποίηση για να μην απαιτεί τον έλεγχό μας, απλά για να μπορεί να ελέγχει τον εαυτό του. Και αν δεν το διαγράψατε θα μπορούσαμε να το τρέξουμε οποιαδήποτε στιγμή στο μέλλον για να ελέγξουμε ότι όλα εξακολουθούν να λειτουργούν όπως πρέπει. Ενδέχεται να δημιουργήσετε μεγάλο αριθμό τέτοιων δοκιμών με την πάροδο του χρόνου, οπότε θα ήταν καλό αν μπορούσαμε να τις εκτελούμε αυτόματα. +Βασικά, γράψατε μια δοκιμή. Απλά πρέπει να την τροποποιήσετε ελαφρώς, ώστε να μην απαιτεί οπτικό έλεγχο, αλλά να ελέγχεται μόνη της. Και αν δεν διαγράψετε τη δοκιμή, μπορείτε να την εκτελέσετε οποιαδήποτε στιγμή στο μέλλον και να επαληθεύσετε ότι όλα εξακολουθούν να λειτουργούν όπως πρέπει. Με τον καιρό θα δημιουργήσετε μεγάλο αριθμό τέτοιων δοκιμών, οπότε θα ήταν χρήσιμο να τις εκτελείτε αυτοματοποιημένα. -Και το Nette Tester βοηθά ακριβώς σε αυτό. +Και σε όλα αυτά θα σας βοηθήσει ακριβώς το Nette Tester. -Τι κάνει το Tester μοναδικό; .[#toc-what-makes-tester-unique] -============================================================= +Τι κάνει τον Tester μοναδικό; +============================= -Η συγγραφή δοκιμών για το Nette Tester είναι μοναδική στο ότι **κάθε δοκιμή είναι ένα τυπικό PHP script που μπορεί να εκτελεστεί αυτόνομα**. +Η συγγραφή δοκιμών για το Nette Tester είναι μοναδική στο ότι **κάθε δοκιμή είναι ένα συνηθισμένο PHP script που μπορεί να εκτελεστεί αυτόνομα.** -Έτσι, όταν γράφετε μια δοκιμή, μπορείτε απλά να την εκτελέσετε για να δείτε αν υπάρχει κάποιο προγραμματιστικό σφάλμα. Εάν λειτουργεί σωστά. Εάν όχι, μπορείτε εύκολα να περάσετε το πρόγραμμα στο IDE σας και να αναζητήσετε ένα σφάλμα. Μπορείτε ακόμη και να το ανοίξετε σε ένα πρόγραμμα περιήγησης. +Δηλαδή, όταν γράφετε μια δοκιμή, μπορείτε απλά να την εκτελείτε και να διαπιστώνετε αν υπάρχει, για παράδειγμα, κάποιο προγραμματιστικό λάθος σε αυτήν. Αν λειτουργεί σωστά. Αν όχι, μπορείτε εύκολα να την κάνετε βήμα-βήμα στο IDE σας και να αναζητήσετε το λάθος. Μπορείτε ακόμη και να την ανοίξετε στον browser. -Και το πιο σημαντικό - εκτελώντας το, θα εκτελέσετε τη δοκιμή. Θα μάθετε αμέσως αν πέρασε ή απέτυχε. Πώς; Ας εμφανιστεί εδώ. Ας γράψουμε ένα τετριμμένο τεστ για τη χρήση του PHP array και ας το αποθηκεύσουμε στο αρχείο `ArrayTest.php`: +Και κυρίως - με την εκτέλεσή της, εκτελείτε τη δοκιμή. Διαπιστώνετε αμέσως αν πέρασε ή απέτυχε. Πώς; Ας το δείξουμε. Θα γράψουμε μια τετριμμένη δοκιμή εργασίας με έναν πίνακα PHP και θα την αποθηκεύσουμε στο αρχείο `ArrayTest.php`: ```php .{file:ArrayTest.php} <?php use Tester\Assert; -require __DIR__ . '/vendor/autoload.php'; # load Composer autoloader -Tester\Environment::setup(); # initialization of Nette Tester +require __DIR__ . '/vendor/autoload.php'; # φόρτωση του Composer autoloader +Tester\Environment::setup(); # αρχικοποίηση του Nette Tester $stack = []; -Assert::same(0, count($stack)); # we expect count() to return zero +Assert::same(0, count($stack)); # περιμένουμε ότι το count() θα επιστρέψει μηδέν $stack[] = 'foo'; -Assert::same(1, count($stack)); # we expect count() to return one -Assert::contains('foo', $stack); # verify that the $stack contains the item 'foo' +Assert::same(1, count($stack)); # περιμένουμε ότι το count() θα επιστρέψει ένα +Assert::contains('foo', $stack); # επαληθεύουμε ότι το $stack περιέχει το στοιχείο 'foo' ``` -Όπως μπορείτε να δείτε, οι [μέθοδοι ισχυρισμού |Assertions] όπως η `Assert::same()` χρησιμοποιούνται για να βεβαιώσουν ότι μια πραγματική τιμή ταιριάζει με μια αναμενόμενη τιμή. +Όπως βλέπετε, οι λεγόμενες [μέθοδοι assertion|assertions] όπως `Assert::same()` χρησιμοποιούνται για να επιβεβαιώσουν ότι η πραγματική τιμή αντιστοιχεί στην αναμενόμενη τιμή. -Η δοκιμή έχει γραφτεί, μπορούμε να την εκτελέσουμε από τη γραμμή εντολών. Η πρώτη εκτέλεση θα αποκαλύψει τυχόν συντακτικά λάθη, και αν δεν κάνατε κάποιο τυπογραφικό λάθος, θα δείτε: +Έχουμε γράψει τη δοκιμή και μπορούμε να την εκτελέσουμε από τη γραμμή εντολών. Η πρώτη εκτέλεση θα μας αποκαλύψει πιθανά συντακτικά λάθη και αν δεν κάνατε πουθενά ορθογραφικό λάθος, θα εκτυπωθεί: /--pre .[terminal] $ php ArrayTest.php @@ -61,7 +61,7 @@ $ php ArrayTest.php <span style="color:#FFF; background-color:#090">OK</span> \-- -Δοκιμάστε να αλλάξετε τη δήλωση σε `Assert::contains('XXX', $stack);` στη δοκιμή και παρακολουθήστε τι θα συμβεί κατά την εκτέλεση: +Δοκιμάστε στη δοκιμή να αλλάξετε τον ισχυρισμό σε ψευδή `Assert::contains('XXX', $stack);` και παρακολουθήστε τι θα συμβεί κατά την εκτέλεση: /--pre .[terminal] $ php ArrayTest.php @@ -73,53 +73,53 @@ $ php ArrayTest.php <span style="color: #FFF; background-color: #900">FAILURE</span> \-- -Συνεχίζουμε για τη συγγραφή στο κεφάλαιο [Συγγραφή δοκιμών |Writing Tests]. +Περαιτέρω για τη συγγραφή συνεχίζουμε στο κεφάλαιο [Συγγραφή δοκιμών|writing-tests]. -Εγκατάσταση και απαιτήσεις .[#toc-installation-and-requirements] -================================================================ +Εγκατάσταση και απαιτήσεις +========================== -Η ελάχιστη απαιτούμενη έκδοση PHP από τον Tester είναι η 7.1 (για περισσότερες λεπτομέρειες, ανατρέξτε στον πίνακα με τις [υποστηριζόμενες εκδόσεις PHP |#supported PHP versions] ). Ο προτιμώμενος τρόπος εγκατάστασης είναι ο [Composer |best-practices:composer]: +Η ελάχιστη έκδοση PHP που απαιτείται από τον Tester είναι 7.1 (λεπτομερέστερα στον πίνακα [#Υποστηριζόμενες εκδόσεις PHP]). Ο προτιμώμενος τρόπος εγκατάστασης είναι μέσω [Composer |best-practices:composer]: /--pre .[terminal] composer require --dev nette/tester \-- -Δοκιμάστε να εκτελέσετε το Nette Tester από τη γραμμή εντολών (χωρίς ορίσματα θα εμφανίσει μόνο μια περίληψη βοήθειας): +Δοκιμάστε να εκτελέσετε τον Nette Tester από τη γραμμή εντολών (χωρίς παραμέτρους εκτυπώνει μόνο τη βοήθεια): /--pre .[terminal] vendor/bin/tester \-- -Εκτέλεση δοκιμών .[#toc-running-tests] -====================================== +Εκτέλεση δοκιμών +================ -Καθώς η εφαρμογή μας μεγαλώνει, ο αριθμός των δοκιμών μεγαλώνει μαζί της. Δεν θα ήταν πρακτικό να εκτελούμε τις δοκιμές μία προς μία. Για το λόγο αυτό, ο Tester διαθέτει έναν εκτελεστή μαζικών δοκιμών, τον οποίο καλούμε από τη γραμμή εντολών. Η παράμετρος είναι ο κατάλογος στον οποίο βρίσκονται οι δοκιμές. Η τελεία υποδεικνύει τον τρέχοντα κατάλογο. +Καθώς η εφαρμογή μεγαλώνει, ο αριθμός των δοκιμών αυξάνεται μαζί της. Δεν θα ήταν πρακτικό να εκτελούμε τις δοκιμές μία προς μία. Γι' αυτό ο Tester διαθέτει έναν μαζικό εκτελεστή δοκιμών, τον οποίο καλούμε από τη γραμμή εντολών. Ως παράμετρο δίνουμε τον κατάλογο στον οποίο βρίσκονται οι δοκιμές. Η τελεία σημαίνει τον τρέχοντα κατάλογο. /--pre .[terminal] vendor/bin/tester . \-- -Ο δρομέας Nette Tester αναζητά τον καθορισμένο κατάλογο και όλους τους υποκαταλόγους και αναζητά τις δοκιμές, οι οποίες είναι τα αρχεία `*.phpt` και `*Test.php`. Θα βρει επίσης τη δοκιμή μας `ArrayTest.php`, καθώς ταιριάζει με τη μάσκα. +Ο εκτελεστής δοκιμών θα σαρώσει τον καθορισμένο κατάλογο και όλους τους υποκαταλόγους και θα αναζητήσει δοκιμές, οι οποίες είναι αρχεία `*.phpt` και `*Test.php`. Θα βρει έτσι και τη δοκιμή μας `ArrayTest.php`, καθώς ταιριάζει με τη μάσκα. -Στη συνέχεια, ξεκινά τη δοκιμή. Εκτελεί κάθε δοκιμή ως μια νέα διεργασία PHP, έτσι ώστε να εκτελείται εντελώς απομονωμένη από τις άλλες. Τρέχει παράλληλα σε πολλαπλά νήματα, γεγονός που την καθιστά εξαιρετικά γρήγορη. Και εκτελεί πρώτα τις δοκιμές που απέτυχαν κατά την προηγούμενη εκτέλεση, ώστε να γνωρίζετε αμέσως αν διορθώσατε το σφάλμα. +Στη συνέχεια, θα ξεκινήσει τις δοκιμές. Κάθε δοκιμή την εκτελεί ως νέα διεργασία PHP, οπότε εκτελείται εντελώς απομονωμένα από τις άλλες. Τις εκτελεί παράλληλα σε πολλαπλά νήματα και χάρη σε αυτό είναι εξαιρετικά γρήγορος. Και ως πρώτες εκτελεί τις δοκιμές που απέτυχαν στην προηγούμενη εκτέλεση, οπότε μαθαίνετε αμέσως αν καταφέρατε να διορθώσετε το σφάλμα. -Για κάθε δοκιμή που εκτελείται, ο δρομέας εκτυπώνει έναν χαρακτήρα για να υποδείξει την πρόοδο: +Κατά την εκτέλεση των δοκιμών, ο Tester εκτυπώνει συνεχώς τα αποτελέσματα στο τερματικό ως χαρακτήρες: -- <code style="color: #CCC; background-color: #000">.</code> - η δοκιμή πέρασε -- <code style="color: #CCC; background-color: #000">s</code> - η δοκιμή έχει παραλειφθεί -- <code style="color: #FFF; background-color: #900">F</code> - η δοκιμή απέτυχε +- <code style="color: #CCC; background-color: #000">.</code> – η δοκιμή πέρασε +- <code style="color: #CCC; background-color: #000">s</code> – η δοκιμή παραλείφθηκε (skipped) +- <code style="color: #FFF; background-color: #900">F</code> – η δοκιμή απέτυχε (failed) -Η έξοδος μπορεί να μοιάζει ως εξής: +Η έξοδος μπορεί να μοιάζει κάπως έτσι: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +PHP 8.3.2 (cli) | php -n | 8 threads ........s................<span style="color: #FFF; background-color: #900">F</span>......... @@ -132,44 +132,44 @@ PHP 7.4.8 (cli) | php -n | 8 threads <span style="color: #FFF; background-color: #900">FAILURES! (35 tests, 1 failures, 1 skipped, 1.7 seconds)</span> \-- -35 δοκιμές εκτελέστηκαν, μία απέτυχε, μία παραλείφθηκε. +Εκτελέστηκαν 35 δοκιμές, μία απέτυχε, μία παραλείφθηκε. -Συνεχίζουμε στο κεφάλαιο [Εκτέλεση δοκιμών |Running tests]. +Περαιτέρω συνεχίζουμε στο κεφάλαιο [Εκτέλεση δοκιμών|running-tests]. -Λειτουργία παρακολούθησης .[#toc-watch-mode] -============================================ +Λειτουργία Watch +================ -Ανασχεδιάζετε τον κώδικα; Ή μήπως αναπτύσσετε ακόμη και σύμφωνα με τη μεθοδολογία TDD (Test Driven Development); Τότε θα σας αρέσει η λειτουργία παρακολούθησης. Ο Tester παρακολουθεί τους πηγαίους κώδικες και εκτελείται μόνος του όταν αλλάζει. +Κάνετε refactoring στον κώδικα; Ή μήπως αναπτύσσετε σύμφωνα με τη μεθοδολογία TDD (Test Driven Development); Τότε θα σας αρέσει η λειτουργία watch. Ο Tester σε αυτήν παρακολουθεί τους πηγαίους κώδικες και κατά την αλλαγή εκτελείται μόνος του. -Κατά τη διάρκεια της ανάπτυξης, έχετε ένα τερματικό στη γωνία της οθόνης, όπου η πράσινη γραμμή κατάστασης ανάβει πάνω σας, και όταν ξαφνικά γίνεται κόκκινη, ξέρετε ότι μόλις κάνατε κάτι ανεπιθύμητο. Είναι στην πραγματικότητα ένα σπουδαίο παιχνίδι όπου προγραμματίζετε και προσπαθείτε να τηρήσετε το χρώμα. +Κατά την ανάπτυξη, έχετε λοιπόν στη γωνία της οθόνης ένα τερματικό, όπου σας φωτίζει μια πράσινη γραμμή κατάστασης, και όταν ξαφνικά αλλάξει σε κόκκινη, ξέρετε ότι μόλις κάνατε κάτι όχι εντελώς καλά. Είναι στην πραγματικότητα ένα υπέροχο παιχνίδι, όπου προγραμματίζετε και προσπαθείτε να κρατήσετε το χρώμα. -Η λειτουργία παρακολούθησης ξεκινάει με τη χρήση της παραμέτρου [--watch |running-tests#w-watch-path]. +Η λειτουργία Watch ξεκινά με την παράμετρο [--watch |running-tests#-w --watch path]. -Αναφορές CodeCoverage .[#toc-codecoverage-reports] -================================================== +Αναφορές CodeCoverage +===================== -Ο ελεγκτής μπορεί να δημιουργήσει αναφορές με μια επισκόπηση του πόση ποσότητα πηγαίου κώδικα καλύπτουν οι δοκιμές. Η αναφορά μπορεί να είναι είτε σε μορφή HTML αναγνώσιμη από τον άνθρωπο είτε σε μορφή Clover XML για περαιτέρω μηχανική επεξεργασία. +Ο Tester μπορεί να δημιουργήσει αναφορές με επισκόπηση του πόσο πηγαίου κώδικα καλύπτουν οι δοκιμές. Η αναφορά μπορεί να είναι είτε σε μορφή HTML αναγνώσιμη από τον άνθρωπο, είτε σε Clover XML για περαιτέρω μηχανική επεξεργασία. -Δείτε το "δείγμα αναφοράς HTML":https://files.nette.org/tester/coverage.html με την κάλυψη κώδικα. +Δείτε ένα "δείγμα αναφοράς HTML":attachment:coverage.html με κάλυψη κώδικα. -Υποστηριζόμενες εκδόσεις PHP .[#toc-supported-php-versions] -=========================================================== +Υποστηριζόμενες εκδόσεις PHP +============================ -| έκδοση | συμβατή με PHP +| έκδοση | συμβατή με PHP |------------------|------------------- -| Tester 2.5 | PHP 8.0 - 8.2 -| Tester 2.4 | PHP 7.2 - 8.2 -| Tester 2.3 | PHP 7.1 - 8.0 -| Tester 2.1 - 2.2 | PHP 7.1 - 7.3 -| Tester 2.0 | PHP 5.6 - 7.3 -| Tester 1.7 | PHP 5.3 - 7.3 + HHVM 3.3+ -| Tester 1.6 | PHP 5.3 - 7.0 + HHVM -| Tester 1.3 - 1.5 | PHP 5.3 - 5.6 + HHVM -| Tester 0.9 - 1.2 | PHP 5.3 - 5.6 - -Ισχύει για τις τελευταίες εκδόσεις διορθώσεων. - -Μέχρι την έκδοση 1.7 ο Tester υποστήριζε [το HHVM |https://hhvm.com] 3.3.0 ή νεότερη έκδοση (χρησιμοποιώντας το `tester -p hhvm`). Η υποστήριξη έχει διακοπεί από το Tester 2.0. Η χρήση ήταν απλή: +| Tester 2.5 | PHP 8.0 – 8.3 +| Tester 2.4 | PHP 7.2 – 8.2 +| Tester 2.3 | PHP 7.1 – 8.0 +| Tester 2.1 – 2.2 | PHP 7.1 – 7.3 +| Tester 2.0 | PHP 5.6 – 7.3 +| Tester 1.7 | PHP 5.3 – 7.3 + HHVM 3.3+ +| Tester 1.6 | PHP 5.3 – 7.0 + HHVM +| Tester 1.3 – 1.5 | PHP 5.3 – 5.6 + HHVM +| Tester 0.9 – 1.2 | PHP 5.3 – 5.6 + +Ισχύει για την τελευταία έκδοση patch. + +Ο Tester μέχρι την έκδοση 1.7 υποστήριζε επίσης το [HHVM |https://hhvm.com] 3.3.0 ή νεότερη (μέσω `tester -p hhvm`). Η υποστήριξη διακόπηκε από την έκδοση Tester 2.0. diff --git a/tester/el/helpers.texy b/tester/el/helpers.texy index 98e9a306d1..baf01f0ceb 100644 --- a/tester/el/helpers.texy +++ b/tester/el/helpers.texy @@ -1,31 +1,45 @@ -Βοηθοί -****** +Βοηθητικές κλάσεις +****************** -DomQuery .[#toc-domquery] -------------------------- -`Tester\DomQuery` είναι μια κλάση που επεκτείνει το `SimpleXMLElement` με μεθόδους που διευκολύνουν τον έλεγχο περιεχομένου HTML ή XML. +DomQuery +-------- +Η `Tester\DomQuery` είναι μια κλάση που επεκτείνει την `SimpleXMLElement` με εύκολη αναζήτηση σε HTML ή XML χρησιμοποιώντας CSS selectors. ```php -# let's have an HTML document in $html that we load -$dom = Tester\DomQuery::fromHtml($html); - -# we can test the presence of elements using CSS selectors -Assert::true($dom->has('form#registration')); -Assert::true($dom->has('input[name="username"]')); -Assert::true($dom->has('input[type="submit"]')); - -# or select elements as array of DomQuery -$elems = $dom->find('input[data-autocomplete]'); +# δημιουργία DomQuery από συμβολοσειρά HTML +$dom = Tester\DomQuery::fromHtml(' + <article class="post"> + <h1>Title</h1> + <div class="content">Text</div> + </article> +'); + +# έλεγχος ύπαρξης στοιχείων με χρήση CSS selectors +Assert::true($dom->has('article.post')); +Assert::true($dom->has('h1')); + +# εύρεση στοιχείων ως πίνακας αντικειμένων DomQuery +$headings = $dom->find('h1'); +Assert::same('Title', (string) $headings[0]); + +# έλεγχος αν το στοιχείο ταιριάζει με τον selector (από την έκδοση 2.5.3) +$content = $dom->find('.content')[0]; +Assert::true($content->matches('div')); +Assert::false($content->matches('p')); + +# εύρεση του πλησιέστερου προγόνου που ταιριάζει με τον selector (από 2.5.5) +$article = $content->closest('.post'); +Assert::true($article->matches('article')); ``` -FileMock .[#toc-filemock] -------------------------- -`Tester\FileMock` προσομοιώνει αρχεία στη μνήμη για να σας βοηθήσει να ελέγξετε έναν κώδικα που χρησιμοποιεί συναρτήσεις όπως `fopen()`, `file_get_contents()` ή `parse_ini_file()`. Για παράδειγμα: +FileMock +-------- +Η `Tester\FileMock` εξομοιώνει αρχεία στη μνήμη και διευκολύνει έτσι τον έλεγχο κώδικα που χρησιμοποιεί συναρτήσεις όπως `fopen()`, `file_get_contents()`, `parse_ini_file()` και παρόμοιες. Παράδειγμα χρήσης: ```php -# Tested class +# Ελεγχόμενη κλάση class Logger { public function __construct( @@ -39,21 +53,21 @@ class Logger } } -# New empty file +# Νέο κενό αρχείο $file = Tester\FileMock::create(''); $logger = new Logger($file); $logger->log('Login'); $logger->log('Logout'); -# Created content testing +# Ελέγχουμε το δημιουργημένο περιεχόμενο Assert::same("Login\nLogout\n", file_get_contents($file)); ``` Assert::with() .[filter] ------------------------ -Αυτό δεν είναι ένας ισχυρισμός, αλλά ένα βοήθημα για τον έλεγχο ιδιωτικών μεθόδων και αντικειμένων ιδιοτήτων. +Δεν πρόκειται για assertion, αλλά για βοηθό για τον έλεγχο ιδιωτικών μεθόδων και ιδιοτήτων αντικειμένων. ```php class Entity @@ -65,17 +79,17 @@ class Entity $ent = new Entity; Assert::with($ent, function () { - Assert::true($this->enabled); // accessible private $ent->enabled + Assert::true($this->enabled); // προσβάσιμη ιδιωτική $ent->enabled }); ``` Helpers::purge() .[filter] -------------------------- -Η μέθοδος `purge()` δημιουργεί τον καθορισμένο κατάλογο και, αν υπάρχει ήδη, διαγράφει ολόκληρο το περιεχόμενό του. Είναι χρήσιμη για τη δημιουργία προσωρινών καταλόγων. Για παράδειγμα στην `tests/bootstrap.php`: +Η μέθοδος `purge()` δημιουργεί τον καθορισμένο κατάλογο, και αν υπάρχει ήδη, διαγράφει ολόκληρο το περιεχόμενό του. Είναι χρήσιμη για τη δημιουργία ενός προσωρινού καταλόγου. Για παράδειγμα στο `tests/bootstrap.php`: ```php -@mkdir(__DIR__ . '/tmp'); # @ - directory may already exist +@mkdir(__DIR__ . '/tmp'); # @ - ο κατάλογος μπορεί ήδη να υπάρχει define('TempDir', __DIR__ . '/tmp/' . getmypid()); Tester\Helpers::purge(TempDir); @@ -84,25 +98,25 @@ Tester\Helpers::purge(TempDir); Environment::lock() .[filter] ----------------------------- -Οι δοκιμές εκτελούνται παράλληλα. Μερικές φορές δεν χρειάζεται να επικαλύψουμε την εκτέλεση των δοκιμών. Συνήθως οι δοκιμές βάσεων δεδομένων πρέπει να προετοιμάζουν το περιεχόμενο της βάσης δεδομένων και δεν χρειάζεται να τις διαταράσσει τίποτα κατά τη διάρκεια της εκτέλεσης της δοκιμής. Σε αυτές τις περιπτώσεις χρησιμοποιούμε το `Tester\Environment::lock($name, $dir)`: +Οι δοκιμές εκτελούνται παράλληλα. Μερικές φορές όμως χρειαζόμαστε η εκτέλεση των δοκιμών να μην επικαλύπτεται. Τυπικά σε δοκιμές βάσεων δεδομένων είναι απαραίτητο η δοκιμή να προετοιμάσει το περιεχόμενο της βάσης δεδομένων και άλλη δοκιμή να μην παρεμβαίνει στη βάση δεδομένων κατά τη διάρκεια της εκτέλεσής της. Σε αυτές τις δοκιμές χρησιμοποιούμε το `Tester\Environment::lock($name, $dir)`: ```php Tester\Environment::lock('database', __DIR__ . '/tmp'); ``` -Το πρώτο όρισμα είναι ένα όνομα κλειδώματος. Το δεύτερο είναι η διαδρομή προς τον κατάλογο για την αποθήκευση της κλειδαριάς. Εκτελείται πρώτα η δοκιμή που αποκτά την κλειδαριά. Οι άλλες δοκιμές πρέπει να περιμένουν μέχρι να ολοκληρωθεί. +Η πρώτη παράμετρος είναι το όνομα του κλειδώματος, η δεύτερη είναι η διαδρομή προς τον κατάλογο για την αποθήκευση του κλειδώματος. Η δοκιμή που αποκτά το κλείδωμα πρώτη εκτελείται, οι υπόλοιπες δοκιμές πρέπει να περιμένουν την ολοκλήρωσή της. Environment::bypassFinals() .[filter] ------------------------------------- -Οι κλάσεις ή οι μέθοδοι που χαρακτηρίζονται ως `final` είναι δύσκολο να δοκιμαστούν. Η κλήση του `Tester\Environment::bypassFinals()` σε μια αρχή δοκιμής προκαλεί την αφαίρεση των λέξεων-κλειδιών `final` κατά τη φόρτωση του κώδικα. +Κλάσεις ή μέθοδοι που έχουν σημανθεί ως `final` είναι δύσκολο να ελεγχθούν. Η κλήση `Tester\Environment::bypassFinals()` στην αρχή της δοκιμής προκαλεί την παράλειψη των λέξεων-κλειδιών `final` κατά τη φόρτωση του κώδικα. ```php require __DIR__ . '/bootstrap.php'; Tester\Environment::bypassFinals(); -class MyClass extends NormallyFinalClass # <-- NormallyFinalClass is not final anymore +class MyClass extends NormallyFinalClass # <-- Η NormallyFinalClass δεν είναι πλέον final { // ... } @@ -111,18 +125,18 @@ class MyClass extends NormallyFinalClass # <-- NormallyFinalClass is not final Environment::setup() .[filter] ------------------------------ -- βελτιώνει την αναγνωσιμότητα της απόρριψης σφαλμάτων (περιλαμβάνεται χρωματισμός), διαφορετικά, εκτυπώνεται το προεπιλεγμένο ίχνος στοίβας PHP -- επιτρέπει τον έλεγχο ότι οι ισχυρισμοί έχουν κληθεί στη δοκιμή, διαφορετικά, οι δοκιμές χωρίς (π.χ. ξεχασμένους) ισχυρισμούς περνούν επίσης -- εκκινεί αυτόματα τον συλλέκτη κάλυψης κώδικα όταν χρησιμοποιείται το `--coverage` (περιγράφεται αργότερα) -- εκτυπώνει την κατάσταση OK ή FAILURE στο τέλος του σεναρίου. +- βελτιώνει την αναγνωσιμότητα της εξόδου σφαλμάτων (συμπεριλαμβανομένου του χρωματισμού), διαφορετικά εκτυπώνεται το προεπιλεγμένο PHP stack trace +- ενεργοποιεί τον έλεγχο ότι κλήθηκαν assertions στη δοκιμή, διαφορετικά μια δοκιμή χωρίς assertions (για παράδειγμα ξεχασμένες) περνάει επίσης +- κατά τη χρήση του `--coverage` ξεκινά αυτόματα τη συλλογή πληροφοριών για τον εκτελεσμένο κώδικα (περιγράφεται παρακάτω) +- εκτυπώνει την κατάσταση OK ή FAILURE στο τέλος του script Environment::setupFunctions() .[filter]{data-version:2.5} --------------------------------------------------------- -Δημιουργεί τις παγκόσμιες συναρτήσεις `test()`, `setUp()` και `tearDown()` στις οποίες μπορείτε να χωρίσετε τις δοκιμές. +Δημιουργεί τις καθολικές συναρτήσεις `test()`, `testException()`, `setUp()` και `tearDown()`, στις οποίες μπορείτε να διαρθρώσετε τις δοκιμές. ```php -test('test description', function () { +test('περιγραφή δοκιμής', function () { Assert::same(123, foo()); Assert::false(bar()); // ... @@ -132,21 +146,21 @@ test('test description', function () { Environment::VariableRunner .[filter] ------------------------------------- -Σας επιτρέπει να μάθετε αν η δοκιμή εκτελέστηκε απευθείας ή μέσω του Tester. +Επιτρέπει να διαπιστωθεί αν η δοκιμή εκτελέστηκε απευθείας ή μέσω του Tester. ```php if (getenv(Tester\Environment::VariableRunner)) { - # run by Tester + # εκτελέστηκε από τον Tester } else { - # another way + # εκτελέστηκε διαφορετικά } ``` Environment::VariableThread .[filter] ------------------------------------- -Ο Tester εκτελεί δοκιμές παράλληλα σε συγκεκριμένο αριθμό νημάτων. Θα βρούμε έναν αριθμό νημάτων σε μια περιβαλλοντική μεταβλητή όταν μας ενδιαφέρει: +Ο Tester εκτελεί τις δοκιμές παράλληλα στον καθορισμένο αριθμό νημάτων. Αν μας ενδιαφέρει ο αριθμός του νήματος, τον βρίσκουμε από τη μεταβλητή περιβάλλοντος: ```php -echo "I'm running in a thread number " . getenv(Tester\Environment::VariableThread); +echo "Τρέχω στο νήμα νούμερο " . getenv(Tester\Environment::VariableThread); ``` diff --git a/tester/el/running-tests.texy b/tester/el/running-tests.texy index 7584b9b4c7..ef02d4fa1e 100644 --- a/tester/el/running-tests.texy +++ b/tester/el/running-tests.texy @@ -2,54 +2,54 @@ **************** .[perex] -Το πιο ορατό μέρος του Nette Tester είναι ο εκτελεστή δοκιμών γραμμής εντολών. Είναι εξαιρετικά γρήγορος και ισχυρός, επειδή ξεκινά αυτόματα όλες τις δοκιμές ως ξεχωριστές διεργασίες παράλληλα σε πολλαπλά νήματα. Μπορεί επίσης να εκτελείται μόνος του στη λεγόμενη λειτουργία παρακολούθησης. +Το πιο ορατό μέρος του Nette Tester είναι ο εκτελεστής δοκιμών από τη γραμμή εντολών. Είναι εξαιρετικά γρήγορος και στιβαρός, καθώς εκτελεί αυτόματα όλες τις δοκιμές ως ξεχωριστές διεργασίες και μάλιστα παράλληλα σε πολλαπλά νήματα. Μπορεί επίσης να εκτελείται μόνος του στη λεγόμενη λειτουργία watch. -Ο εκτελεστή δοκιμών του Nette Tester καλείται από τη γραμμή εντολών. Ως παράμετρος, θα περάσουμε τον κατάλογο των δοκιμών. Για τον τρέχοντα κατάλογο απλώς πληκτρολογούμε μια τελεία: +Τον εκτελεστή δοκιμών τον καλούμε από τη γραμμή εντολών. Ως παράμετρο δίνουμε τον κατάλογο με τις δοκιμές. Για τον τρέχοντα κατάλογο αρκεί να δώσουμε μια τελεία: /--pre .[terminal] vendor/bin/tester . \-- -Κατά την κλήση, ο test runner θα σαρώσει τον καθορισμένο κατάλογο και όλους τους υποκαταλόγους και θα αναζητήσει δοκιμές, οι οποίες είναι τα αρχεία `*.phpt` και `*Test.php`. Επίσης, διαβάζει και αξιολογεί τις [επισημάνσεις |test-annotations] τους για να ξέρει ποιες και πώς να τις εκτελέσει. +Ο εκτελεστής δοκιμών σαρώνει τον καθορισμένο κατάλογο και όλους τους υποκαταλόγους και αναζητά δοκιμές, οι οποίες είναι αρχεία `*.phpt` και `*Test.php`. Ταυτόχρονα διαβάζει και αξιολογεί τις [annotations|test-annotations] τους, για να ξέρει ποιες από αυτές και πώς να τις εκτελέσει. -Στη συνέχεια θα εκτελέσει τις δοκιμές. Για κάθε τεστ που εκτελείται, ο runner εκτυπώνει έναν χαρακτήρα για να δείξει την πρόοδο: +Στη συνέχεια εκτελεί τις δοκιμές. Κατά την εκτέλεση των δοκιμών εκτυπώνει συνεχώς τα αποτελέσματα στο τερματικό ως χαρακτήρες: -- <code style="color: #CCC; background-color: #000">.</code> - η δοκιμή πέρασε -- <code style="color: #CCC; background-color: #000">s</code> - η δοκιμή έχει παραλειφθεί -- <code style="color: #FFF; background-color: #900">F</code> - η δοκιμή απέτυχε +- <code style="color: #CCC; background-color: #000">.</code> – η δοκιμή πέρασε +- <code style="color: #CCC; background-color: #000">s</code> – η δοκιμή παραλείφθηκε (skipped) +- <code style="color: #FFF; background-color: #900">F</code> – η δοκιμή απέτυχε (failed) -Η έξοδος μπορεί να μοιάζει με: +Η έξοδος μπορεί να μοιάζει κάπως έτσι: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +PHP 8.3.2 (cli) | php -n | 8 threads ........s.......................... <span style="color: #FFF; background-color: #090">OK (35 tests, 1 skipped, 1.7 seconds)</span> \-- -Έτσι θα ξέρετε αμέσως αν διορθώσατε το σφάλμα. +Κατά την επαναλαμβανόμενη εκτέλεση, εκτελεί πρώτα τις δοκιμές που απέτυχαν στην προηγούμενη εκτέλεση, οπότε μαθαίνετε αμέσως αν καταφέρατε να διορθώσετε το σφάλμα. -Ο κωδικός εξόδου του Tester είναι μηδέν, αν καμία δεν αποτύχει. Διαφορετικά, μη μηδενικός. +Εάν καμία δοκιμή δεν αποτύχει, ο κωδικός επιστροφής του Tester είναι μηδέν. Διαφορετικά, ο κωδικός επιστροφής είναι μη μηδενικός. .[warning] -Ο Tester εκτελεί διεργασίες PHP χωρίς `php.ini`. Περισσότερες λεπτομέρειες στην ενότητα [Own php.ini |#Own php.ini]. +Ο Tester εκτελεί τις διεργασίες PHP χωρίς `php.ini`. Λεπτομερέστερα στην ενότητα [#Προσαρμοσμένο php.ini]. -Επιλογές γραμμής εντολών .[#toc-command-line-options] -===================================================== +Παράμετροι γραμμής εντολών +========================== -Παίρνουμε επισκόπηση των επιλογών γραμμής εντολών εκτελώντας τον ελεγκτή χωρίς παραμέτρους ή με την επιλογή `-h`: +Μια επισκόπηση όλων των επιλογών της γραμμής εντολών λαμβάνουμε εκτελώντας τον Tester χωρίς παραμέτρους ή με την παράμετρο `-h`: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Usage: tester [options] [<test file> | <directory>]... @@ -58,16 +58,16 @@ Options: -p <path> Specify PHP interpreter to run (default: php). -c <path> Look for php.ini file (or look in directory) <path>. -C Use system-wide php.ini. - -l | --log <path> Write log to file <path>. -d <key=value>... Define INI entry 'key' with value 'value'. -s Show information about skipped tests. --stop-on-fail Stop execution upon the first failure. -j <num> Run <num> jobs in parallel (default: 8). - -o <console|tap|junit|none> Specify output format. + -o <console|console-lines|tap|junit|log|none> (e.g. -o junit:output.xml) + Specify one or more output formats with optional file name. -w | --watch <path> Watch directory. -i | --info Show tests environment info and exit. --setup <path> Script for runner setup. - --temp <path> Path to temporary directory. Default: sys_get_temp_dir(). + --temp <path> Path to temporary directory. Default by sys_get_temp_dir(). --colors [1|0] Enable or disable colors. --coverage <path> Generate code coverage report to file. --coverage-src <path> Path to source code. @@ -77,7 +77,7 @@ Options: -p <path> .[filter] ------------------- -Καθορίζει το δυαδικό αρχείο PHP που θα χρησιμοποιηθεί για την εκτέλεση των δοκιμών. Από προεπιλογή είναι το `php`. +Καθορίζει το PHP binary που θα χρησιμοποιηθεί για την εκτέλεση των δοκιμών. Η προεπιλογή είναι `php`. /--pre .[terminal] tester -p /home/user/php-7.2.0-beta/php-cgi tests @@ -86,26 +86,17 @@ tester -p /home/user/php-7.2.0-beta/php-cgi tests -c <path> .[filter] ------------------- -Καθορίζει ποιο `php.ini` θα χρησιμοποιείται κατά την εκτέλεση των δοκιμών. Από προεπιλογή, δεν χρησιμοποιείται php.ini. Ανατρέξτε στην ενότητα [Own php.ini |#Own php.ini] για περισσότερες πληροφορίες. +Καθορίζει ποιο `php.ini` θα χρησιμοποιηθεί κατά την εκτέλεση των δοκιμών. Στην προεπιλεγμένη κατάσταση, δεν χρησιμοποιείται κανένα php.ini. Περισσότερα στην ενότητα [#Προσαρμοσμένο php.ini]. -C .[filter] ------------ -Χρησιμοποιείται ένα `php.ini` σε όλο το σύστημα. Έτσι, στην πλατφόρμα UNIX, όλα τα αρχεία `/etc/php/{sapi}/conf.d/*.ini` επίσης. Δείτε την ενότητα [Own php.ini |#Own php.ini]. - - -''-l | --log <path>'' .[filter] -------------------------------- -Η πρόοδος των δοκιμών γράφεται σε αρχείο. Όλες οι αποτυχημένες, παραλειφθείσες και επιτυχημένες δοκιμές: - -/--pre .[terminal] -tester --log /var/log/tests.log tests -\-- +Χρησιμοποιείται το `php.ini` του συστήματος. Στο UNIX, επίσης όλα τα σχετικά αρχεία INI `/etc/php/{sapi}/conf.d/*.ini`. Περισσότερα στην ενότητα [#Προσαρμοσμένο php.ini]. -d <key=value> .[filter] ------------------------ -Ορίζει την τιμή της οδηγίας διαμόρφωσης PHP για δοκιμές. Η παράμετρος μπορεί να χρησιμοποιηθεί πολλές φορές. +Ορίζει την τιμή της οδηγίας διαμόρφωσης PHP για τις δοκιμές. Η παράμετρος μπορεί να χρησιμοποιηθεί πολλές φορές. /--pre .[terminal] tester -d max_execution_time=20 @@ -114,34 +105,36 @@ tester -d max_execution_time=20 -s --- -Θα εμφανιστούν πληροφορίες σχετικά με τις δοκιμές που παραλείφθηκαν. +Εμφανίζονται πληροφορίες για τις παραλειφθείσες δοκιμές. --stop-on-fail .[filter] ------------------------ -Ο ελεγκτής σταματά τη δοκιμή μετά την πρώτη αποτυχημένη δοκιμή. +Ο Tester σταματά τις δοκιμές στην πρώτη αποτυχημένη δοκιμή. -j <num> .[filter] ------------------ -Οι δοκιμές εκτελούνται σε ένα `<num>` παράλληλες διεργασίες. Η προεπιλεγμένη τιμή είναι 8. Αν θέλουμε να εκτελούμε δοκιμές σε σειρά, χρησιμοποιούμε την τιμή 1. +Καθορίζει πόσες παράλληλες διεργασίες με δοκιμές θα εκκινηθούν. Η προεπιλεγμένη τιμή είναι 8. Αν θέλουμε όλες οι δοκιμές να εκτελεστούν σειριακά, χρησιμοποιούμε την τιμή 1. --o <console|tap|junit|none> .[filter] -------------------------------------- -Μορφή εξόδου. Η προεπιλογή είναι η μορφή κονσόλας. +-o <console|console-lines|tap|junit|log|none> .[filter] +------------------------------------------------------- +Ορίζει τη μορφή εξόδου. Η προεπιλογή είναι η μορφή για την κονσόλα. Μπορείτε να δώσετε το όνομα του αρχείου στο οποίο θα γραφτεί η έξοδος (π.χ. `-o junit:output.xml`). Η επιλογή `-o` μπορεί να επαναληφθεί πολλές φορές για να δημιουργηθούν ταυτόχρονα πολλαπλές μορφές. -- `console`: η ίδια με την προεπιλογή, αλλά το λογότυπο ASCII δεν εκτυπώνεται σε αυτή την περίπτωση. -- `tap`: [Μορφή TAP |https://en.wikipedia.org/wiki/Test_Anything_Protocol] κατάλληλη για μηχανική επεξεργασία. -- `junit`: Μορφή JUnit XML, κατάλληλη και για μηχανική επεξεργασία. -- `none`: δεν εκτυπώνεται τίποτα +- `console`: πανομοιότυπο με την προεπιλεγμένη μορφή, αλλά σε αυτήν την περίπτωση δεν εμφανίζεται το λογότυπο ASCII +- `console-lines`: παρόμοιο με το console, αλλά το αποτέλεσμα κάθε δοκιμής αναφέρεται σε ξεχωριστή γραμμή με περαιτέρω πληροφορίες +- `tap`: [μορφή TAP |https://en.wikipedia.org/wiki/Test_Anything_Protocol] κατάλληλη για μηχανική επεξεργασία +- `junit`: μορφή XML JUnit, επίσης κατάλληλη για μηχανική επεξεργασία +- `log`: Έξοδοι της πορείας των δοκιμών. Όλες οι αποτυχημένες, παραλειφθείσες και επίσης οι επιτυχημένες δοκιμές +- `none`: τίποτα δεν εκτυπώνεται ''-w | --watch <path>'' .[filter] --------------------------------- -Ο Tester δεν τερματίζει μετά την ολοκλήρωση των δοκιμών αλλά συνεχίζει να τρέχει και να παρακολουθεί τα αρχεία PHP στον συγκεκριμένο κατάλογο. Όταν αλλάξει, εκτελεί ξανά τις δοκιμές. Η παράμετρος μπορεί να χρησιμοποιηθεί πολλές φορές αν θέλουμε να παρακολουθούμε πολλούς καταλόγους. +Μετά την ολοκλήρωση των δοκιμών, ο Tester δεν τερματίζει, αλλά παραμένει σε λειτουργία και παρακολουθεί τα αρχεία PHP στον καθορισμένο κατάλογο. Κατά την αλλαγή, εκτελεί ξανά τις δοκιμές. Η παράμετρος μπορεί να χρησιμοποιηθεί πολλές φορές, αν θέλουμε να παρακολουθούμε πολλούς καταλόγους. -Είναι χρήσιμο κατά τη διάρκεια της αναδιαμόρφωσης μιας βιβλιοθήκης ή της αποσφαλμάτωσης δοκιμών. +Είναι χρήσιμο κατά το refactoring μιας βιβλιοθήκης ή τον εντοπισμό σφαλμάτων στις δοκιμές. /--pre .[terminal] tester --watch src tests @@ -150,7 +143,7 @@ tester --watch src tests ''-i | --info'' .[filter] ------------------------- -Εμφανίζει πληροφορίες σχετικά με ένα περιβάλλον εκτέλεσης δοκιμής. Για παράδειγμα: +Εμφανίζει πληροφορίες για το περιβάλλον εκτέλεσης των δοκιμών. Για παράδειγμα: /--pre .[terminal] tester -p /usr/bin/php7.1 -c tests/php.ini --info @@ -175,15 +168,15 @@ Core, ctype, date, dom, ereg, fileinfo, filter, hash, ... \-- --- <path> .[filter] -------------------- -Ο ελεγκτής φορτώνει το συγκεκριμένο σενάριο PHP κατά την εκκίνηση. Η μεταβλητή `Tester\Runner\Runner $runner` είναι διαθέσιμη σε αυτό. Ας υποθέσουμε ότι το αρχείο `tests/runner-setup.php`: +--setup <path> .[filter] +------------------------ +Ο Tester κατά την εκκίνηση φορτώνει το καθορισμένο PHP script. Σε αυτό είναι διαθέσιμη η μεταβλητή `Tester\Runner\Runner $runner`. Ας υποθέσουμε το αρχείο `tests/runner-setup.php` με περιεχόμενο: ```php $runner->outputHandlers[] = new MyOutputHandler; ``` -και εκτελούμε τον ελεγκτή: +Εκκινούμε τον Tester: /--pre .[terminal] tester --setup tests/runner-setup.php tests @@ -192,47 +185,47 @@ tester --setup tests/runner-setup.php tests --temp <path> .[filter] ----------------------- -Ορίζει μια διαδρομή στον κατάλογο για τα προσωρινά αρχεία του Tester. Η προεπιλεγμένη τιμή επιστρέφεται από το `sys_get_temp_dir()`. Όταν η προεπιλεγμένη τιμή δεν είναι έγκυρη, θα λάβετε ειδοποίηση. +Ορίζει τη διαδρομή προς τον κατάλογο για τα προσωρινά αρχεία του Tester. Η προεπιλεγμένη τιμή επιστρέφεται από την `sys_get_temp_dir()`. Εάν η προεπιλεγμένη τιμή δεν είναι έγκυρη, θα ειδοποιηθείτε. -Εάν δεν είμαστε σίγουροι για τον κατάλογο που χρησιμοποιείται, μπορούμε να εκτελέσουμε το Tester με την παράμετρο `--info`. +Εάν δεν είμαστε σίγουροι ποιος κατάλογος χρησιμοποιείται, εκκινούμε τον Tester με την παράμετρο `--info`. --colors 1|0 .[filter] ---------------------- -Ο Tester ανιχνεύει ένα τερματικό με δυνατότητα χρωματισμού από προεπιλογή και χρωματίζει την έξοδό του. Αυτή η επιλογή είναι πάνω από την αυτόματη ανίχνευση. Μπορούμε να ορίσουμε τον χρωματισμό συνολικά με μια μεταβλητή περιβάλλοντος συστήματος `NETTE_TESTER_COLORS`. +Στην προεπιλεγμένη κατάσταση, ο Tester ανιχνεύει το έγχρωμο τερματικό και χρωματίζει την έξοδό του. Αυτή η επιλογή υπερισχύει της αυτόματης ανίχνευσης. Μπορούμε να ορίσουμε τον χρωματισμό καθολικά με τη μεταβλητή περιβάλλοντος συστήματος `NETTE_TESTER_COLORS`. --coverage <path> .[filter] --------------------------- -Ο ελεγκτής θα δημιουργήσει μια αναφορά με επισκόπηση του πόσο καλύπτεται ο πηγαίος κώδικας από τις δοκιμές. Αυτή η επιλογή απαιτεί ενεργοποιημένη την επέκταση PHP [Xdebug |https://xdebug.org/] ή [PCOV |https://github.com/krakjoe/pcov] ή την PHP 7 με το SAPI PHPDBG, το οποίο είναι ταχύτερο. Η επέκταση του αρχείου προορισμού καθορίζει τη μορφή του περιεχομένου. HTML ή Clover XML. +Ο Tester θα δημιουργήσει μια αναφορά με επισκόπηση του πόσο πηγαίου κώδικα καλύπτουν οι δοκιμές. Αυτή η επιλογή απαιτεί εγκατεστημένη την επέκταση PHP [Xdebug |https://xdebug.org/], ή [PCOV |https://github.com/krakjoe/pcov], ή PHP 7 με PHPDBG SAPI, το οποίο είναι ταχύτερο. Η επέκταση του αρχείου προορισμού καθορίζει τη μορφή του. Είτε HTML είτε Clover XML. /--pre .[terminal] -tester tests --coverage coverage.html # HTML report -tester tests --coverage coverage.xml # Clover XML report +tester tests --coverage coverage.html # Αναφορά HTML +tester tests --coverage coverage.xml # Αναφορά Clover XML \-- -Η προτεραιότητα για την επιλογή του μηχανισμού συλλογής είναι η ακόλουθη: +Η προτεραιότητα επιλογής μηχανισμού είναι η ακόλουθη: 1) PCOV 2) PHPDBG 3) Xdebug -Οι εκτεταμένες δοκιμές ενδέχεται να αποτύχουν κατά την εκτέλεση από την PHPDBG λόγω εξάντλησης της μνήμης. Η συλλογή δεδομένων κάλυψης είναι λειτουργία που καταναλώνει μνήμη. Σε αυτή την περίπτωση, η κλήση του `Tester\CodeCoverage\Collector::flush()` μέσα σε μια δοκιμή μπορεί να βοηθήσει. Θα μεταφέρει τα δεδομένα που συλλέχθηκαν σε αρχείο και θα ελευθερώσει τη μνήμη. Όταν η συλλογή δεδομένων δεν είναι σε εξέλιξη ή χρησιμοποιείται το Xdebug, η κλήση δεν έχει κανένα αποτέλεσμα. +Κατά τη χρήση του PHPDBG, σε εκτενείς δοκιμές μπορεί να συναντήσουμε αποτυχία της δοκιμής λόγω εξάντλησης μνήμης. Η συλλογή πληροφοριών για τον καλυμμένο κώδικα είναι απαιτητική σε μνήμη. Σε αυτήν την περίπτωση, μας βοηθά η κλήση `Tester\CodeCoverage\Collector::flush()` μέσα στη δοκιμή. Γράφει τα συλλεγμένα δεδομένα στον δίσκο και απελευθερώνει τη μνήμη. Εάν η συλλογή δεδομένων δεν εκτελείται ή χρησιμοποιείται το Xdebug, η κλήση δεν έχει κανένα αποτέλεσμα. -"Ένα παράδειγμα αναφοράς HTML":https://files.nette.org/tester/coverage.html με κάλυψη κώδικα. +"Δείγμα αναφοράς HTML":attachment:coverage.html με κάλυψη κώδικα. --coverage-src <path> .[filter] ------------------------------- -Τη χρησιμοποιούμε ταυτόχρονα με την επιλογή `--coverage`. Το `<path>` είναι μια διαδρομή προς τον πηγαίο κώδικα για τον οποίο δημιουργούμε την αναφορά. Μπορεί να χρησιμοποιηθεί επανειλημμένα. +Χρησιμοποιούμε ταυτόχρονα με την επιλογή `--coverage`. Το `<path>` είναι η διαδρομή προς τους πηγαίους κώδικες για τους οποίους δημιουργείται η αναφορά. Μπορεί να χρησιμοποιηθεί επανειλημμένα. -Δικό σας php.ini .[#toc-own-php-ini] -==================================== -Ο Tester εκτελεί διεργασίες PHP με την επιλογή `-n`, πράγμα που σημαίνει ότι δεν φορτώνεται κανένα `php.ini` (ούτε καν αυτό από το `/etc/php/conf.d/*.ini` στο UNIX). Εξασφαλίζει το ίδιο περιβάλλον για τις δοκιμές που εκτελούνται, αλλά απενεργοποιεί επίσης όλες τις εξωτερικές επεκτάσεις PHP που συνήθως φορτώνονται από την PHP του συστήματος. +Προσαρμοσμένο php.ini +===================== +Ο Tester εκτελεί τις διεργασίες PHP με την παράμετρο `-n`, πράγμα που σημαίνει ότι δεν φορτώνεται κανένα `php.ini`. Στο UNIX, ούτε αυτά από το `/etc/php/conf.d/*.ini`. Αυτό εξασφαλίζει ένα ομοιόμορφο περιβάλλον για την εκτέλεση των δοκιμών, αλλά επίσης απενεργοποιεί όλες τις επεκτάσεις PHP που φορτώνονται συνήθως από το PHP του συστήματος. -Αν θέλετε να διατηρήσετε τις ρυθμίσεις του συστήματος, χρησιμοποιήστε την παράμετρο `-C`. +Αν θέλετε να διατηρήσετε τη φόρτωση των αρχείων php.ini του συστήματος, χρησιμοποιήστε την παράμετρο `-C`. -Αν χρειάζεστε κάποιες επεκτάσεις ή κάποιες ειδικές ρυθμίσεις INI, σας συνιστούμε να δημιουργήσετε το δικό σας αρχείο `php.ini` και να το διανείμετε στις δοκιμές. Στη συνέχεια, εκτελούμε το Tester με την επιλογή `-c`, π.χ. `tester -c tests/php.ini`. Το αρχείο INI μπορεί να μοιάζει ως εξής: +Αν χρειάζεστε κάποιες επεκτάσεις ή ειδικές ρυθμίσεις INI για τις δοκιμές, συνιστούμε τη δημιουργία ενός δικού σας αρχείου `php.ini`, το οποίο θα διανέμεται με τις δοκιμές. Τον Tester τον εκκινούμε τότε με την παράμετρο `-c`, για παράδειγμα `tester -c tests/php.ini tests`, όπου το αρχείο INI μπορεί να μοιάζει κάπως έτσι: ```ini [PHP] @@ -243,4 +236,4 @@ extension=php_pdo_pgsql.dll memory_limit=512M ``` -Η εκτέλεση του Tester με ένα σύστημα `php.ini` στο UNIX, π.χ. `tester -c /etc/php/cgi/php.ini`, δεν φορτώνει άλλο INI από το `/etc/php/conf.d/*.ini`. Αυτή είναι η συμπεριφορά της PHP, όχι του Tester. +Η εκκίνηση του Tester στο UNIX με το `php.ini` του συστήματος, για παράδειγμα `tester -c /etc/php/cli/php.ini` δεν φορτώνει τα υπόλοιπα INI από το `/etc/php/conf.d/*.ini`. Αυτή είναι μια ιδιότητα της PHP, όχι του Tester. diff --git a/tester/el/test-annotations.texy b/tester/el/test-annotations.texy index c13134b4b5..d1f83f912d 100644 --- a/tester/el/test-annotations.texy +++ b/tester/el/test-annotations.texy @@ -1,10 +1,10 @@ -Σημειώσεις δοκιμής -****************** +Annotations δοκιμών +******************* .[perex] -Οι επισημειώσεις καθορίζουν τον τρόπο με τον οποίο οι δοκιμές θα αντιμετωπίζονται από τον [εκτελεστή δοκιμών γραμμής εντολών |running-tests]. Γράφονται στην αρχή του αρχείου δοκιμής. +Οι annotations καθορίζουν πώς θα χειριστεί τις δοκιμές ο [εκτελεστής δοκιμών από τη γραμμή εντολών|running-tests]. Γράφονται στην αρχή του αρχείου με τη δοκιμή. -Οι σημειώσεις δεν επηρεάζουν την πεζότητα. Επίσης, δεν έχουν καμία επίδραση αν η δοκιμή εκτελεστεί χειροκίνητα ως κανονικό σενάριο PHP. +Στις annotations δεν λαμβάνεται υπόψη η διάκριση πεζών-κεφαλαίων. Επίσης, δεν έχουν καμία επίδραση εάν η δοκιμή εκτελεστεί χειροκίνητα ως ένα συνηθισμένο PHP script. Παράδειγμα: @@ -23,7 +23,7 @@ require __DIR__ . '/../bootstrap.php'; TEST .[filter] -------------- -Στην πραγματικότητα δεν πρόκειται για σχολιασμό. Ορίζει μόνο τον τίτλο της δοκιμής που εκτυπώνεται στην αποτυχία ή στα αρχεία καταγραφής. +Αυτό στην πραγματικότητα δεν είναι καν annotation, απλώς καθορίζει τον τίτλο της δοκιμής, ο οποίος εκτυπώνεται κατά την αποτυχία ή στο log. @skip .[filter] @@ -33,7 +33,7 @@ TEST .[filter] @phpVersion .[filter] --------------------- -Η δοκιμή παραλείπεται εάν δεν εκτελείται από την αντίστοιχη έκδοση PHP. Γράφουμε τον σχολιασμό ως `@phpVersion [operator] version`. Μπορούμε να παραλείψουμε τον τελεστή, η προεπιλογή είναι `>=`. Παραδείγματα: +Η δοκιμή παραλείπεται εάν δεν εκτελείται με την αντίστοιχη έκδοση PHP. Την annotation την γράφουμε ως `@phpVersion [operator] version`. Τον τελεστή μπορούμε να τον παραλείψουμε, η προεπιλογή είναι `>=`. Παραδείγματα: ```php /** @@ -46,7 +46,7 @@ TEST .[filter] @phpExtension .[filter] ----------------------- -Η δοκιμή παραλείπεται εάν δεν έχουν φορτωθεί όλες οι αναφερόμενες επεκτάσεις PHP. Πολλαπλές επεκτάσεις μπορούν να γραφτούν σε ένα μόνο σχόλιο ή μπορούμε να χρησιμοποιήσουμε το σχόλιο πολλές φορές. +Η δοκιμή παραλείπεται εάν δεν έχουν φορτωθεί όλες οι αναφερόμενες επεκτάσεις PHP. Περισσότερες επεκτάσεις μπορούμε να αναφέρουμε σε μία annotation ή να την χρησιμοποιήσουμε πολλές φορές. ```php /** @@ -58,9 +58,9 @@ TEST .[filter] @dataProvider .[filter] ----------------------- -Αυτός ο σχολιασμός ταιριάζει όταν θέλουμε να εκτελέσουμε τη δοκιμή πολλές φορές αλλά με διαφορετικά δεδομένα. (Δεν πρέπει να συγχέεται με τον ομώνυμο σχολιασμό για [το TestCase |TestCase#dataProvider]). +Αν θέλουμε να εκτελέσουμε το αρχείο δοκιμής πολλές φορές, αλλά με διαφορετικά δεδομένα εισόδου, αυτή η annotation είναι χρήσιμη. (Μην τη συγχέετε με την ομώνυμη annotation για το [TestCase |TestCase#dataProvider].) -Γράφουμε τον σχολιασμό ως `@dataProvider file.ini`. Η διαδρομή του αρχείου INI είναι σχετική με το αρχείο δοκιμής. Το τεστ εκτελείται τόσες φορές όσες και ο αριθμός των τμημάτων που περιέχονται στο αρχείο INI. Ας υποθέσουμε ότι το αρχείο INI `databases.ini`: +Τη γράφουμε ως `@dataProvider file.ini`, η διαδρομή προς το αρχείο λαμβάνεται σχετικά με το αρχείο της δοκιμής. Η δοκιμή θα εκτελεστεί τόσες φορές όσες είναι οι ενότητες στο αρχείο INI. Ας υποθέσουμε το αρχείο INI `databases.ini`: ```ini [mysql] @@ -77,7 +77,7 @@ password = ****** dsn = "sqlite::memory:" ``` -και το αρχείο `database.phpt` στον ίδιο κατάλογο: +και στον ίδιο κατάλογο τη δοκιμή `database.phpt`: ```php /** @@ -87,11 +87,11 @@ dsn = "sqlite::memory:" $args = Tester\Environment::loadData(); ``` -Η δοκιμή εκτελείται τρεις φορές και το `$args` θα περιέχει τιμές από τα τμήματα `mysql`, `postgresql` ή `sqlite`. +Η δοκιμή θα εκτελεστεί τρεις φορές και το `$args` θα περιέχει πάντα τις τιμές από την ενότητα `mysql`, `postgresql` ή `sqlite`. -Υπάρχει μια ακόμη παραλλαγή όταν γράφουμε σχόλια με ένα ερωτηματικό ως `@dataProvider? file.ini`. Σε αυτή την περίπτωση, η δοκιμή παραλείπεται εάν το αρχείο INI δεν υπάρχει. +Υπάρχει ακόμα μια παραλλαγή, όπου γράφουμε την annotation με ερωτηματικό ως `@dataProvider? file.ini`. Σε αυτήν την περίπτωση, η δοκιμή παραλείπεται εάν το αρχείο INI δεν υπάρχει. -Οι δυνατότητες σχολιασμού δεν έχουν αναφερθεί ακόμη όλες. Μπορούμε να γράψουμε συνθήκες μετά το αρχείο INI. Η δοκιμή εκτελείται για το συγκεκριμένο τμήμα μόνο εάν όλες οι συνθήκες ταιριάζουν. Ας επεκτείνουμε το αρχείο INI: +Με αυτό οι δυνατότητες της annotation δεν τελειώνουν. Μετά το όνομα του αρχείου INI μπορούμε να καθορίσουμε συνθήκες υπό τις οποίες θα εκτελεστεί η δοκιμή για τη δεδομένη ενότητα. Θα επεκτείνουμε το αρχείο INI: ```ini [mysql] @@ -113,7 +113,7 @@ password = ****** dsn = "sqlite::memory:" ``` -και θα χρησιμοποιήσουμε σχολιασμό με συνθήκη: +και θα χρησιμοποιήσουμε την annotation με συνθήκη: ```php /** @@ -121,9 +121,9 @@ dsn = "sqlite::memory:" */ ``` -Η δοκιμή εκτελείται μόνο μία φορά για το τμήμα `postgresql 9.1`. Τα άλλα τμήματα δεν ταιριάζουν με τις συνθήκες. +Η δοκιμή θα εκτελεστεί μόνο μία φορά και αυτό για την ενότητα `postgresql 9.1`. Οι υπόλοιπες ενότητες δεν περνούν από το φίλτρο της συνθήκης. -Ομοίως, μπορούμε να περάσουμε τη διαδρομή σε ένα PHP script αντί για INI. Πρέπει να επιστρέφει array ή Traversable. Αρχείο `databases.php`: +Παρόμοια, αντί για αρχείο INI μπορούμε να παραπέμψουμε σε ένα PHP script. Αυτό πρέπει να επιστρέψει έναν πίνακα ή Traversable. Αρχείο `databases.php`: ```php return [ @@ -142,29 +142,29 @@ return [ @multiple .[filter] ------------------- -Το γράφουμε ως `@multiple N` όπου το "Ν" είναι ένας ακέραιος αριθμός. Η δοκιμή εκτελείται ακριβώς N-φορές. +Γράφουμε ως `@multiple N`, όπου `N` είναι ένας ακέραιος αριθμός. Η δοκιμή θα εκτελεστεί ακριβώς N φορές. @testCase .[filter] ------------------- -Ο σχολιασμός δεν έχει παραμέτρους. Τη χρησιμοποιούμε όταν γράφουμε μια δοκιμή ως κλάσεις [TestCase |TestCase]. Σε αυτή την περίπτωση, ο test runner της γραμμής εντολών θα εκτελέσει τις επιμέρους μεθόδους σε ξεχωριστές διεργασίες και παράλληλα σε πολλαπλά νήματα. Αυτό μπορεί να επιταχύνει σημαντικά ολόκληρη τη διαδικασία δοκιμής. +Η annotation δεν έχει παραμέτρους. Τη χρησιμοποιούμε εάν γράφουμε τις δοκιμές ως κλάσεις [TestCase]. Σε αυτήν την περίπτωση, ο εκτελεστής δοκιμών από τη γραμμή εντολών θα εκτελεί τις επιμέρους μεθόδους σε ξεχωριστές διεργασίες και παράλληλα σε πολλαπλά νήματα. Αυτό μπορεί να επιταχύνει σημαντικά ολόκληρη τη διαδικασία δοκιμών. @exitCode .[filter] ------------------- -Το γράφουμε ως `@exitCode N` όπου το `N` is the exit code of the test. For example if `exit(10)` καλείται στη δοκιμή, γράφουμε τον σχολιασμό ως `@exitCode 10`. Θεωρείται ότι αποτυγχάνει αν η δοκιμή τελειώνει με διαφορετικό κωδικό. Ο κωδικός εξόδου 0 (μηδέν) επαληθεύεται αν παραλείψουμε το σχόλιο +Γράφουμε ως `@exitCode N`, όπου `N` είναι ο κωδικός επιστροφής της εκτελεσμένης δοκιμής. Εάν στη δοκιμή καλείται για παράδειγμα `exit(10)`, την annotation την γράφουμε ως `@exitCode 10` και εάν η δοκιμή τελειώσει με διαφορετικό κωδικό, θεωρείται αποτυχία. Εάν δεν αναφέρουμε την annotation, επαληθεύεται ο κωδικός επιστροφής 0 (μηδέν). @httpCode .[filter] ------------------- -Η σημείωση αξιολογείται μόνο αν το δυαδικό PHP είναι CGI. Διαφορετικά αγνοείται. Το γράφουμε ως `@httpCode NNN` όπου `NNN` είναι ο αναμενόμενος κωδικός HTTP. Ο κωδικός HTTP 200 επαληθεύεται αν παραλείψουμε το σχόλιο. Εάν γράψουμε το `NNN` ως συμβολοσειρά που αξιολογείται ως μηδέν, για παράδειγμα `any`, ο κωδικός HTTP δεν ελέγχεται καθόλου. +Η annotation εφαρμόζεται μόνο εάν το PHP binary είναι CGI. Διαφορετικά αγνοείται. Γράφουμε ως `@httpCode NNN` όπου `NNN` είναι ο αναμενόμενος κωδικός HTTP. Εάν δεν αναφέρουμε την annotation, επαληθεύεται ο κωδικός HTTP 200. Εάν το `NNN` το γράψουμε ως συμβολοσειρά που αξιολογείται σε μηδέν, για παράδειγμα `any`, ο κωδικός HTTP δεν επαληθεύεται. -@outputMatch a @outputMatchFile .[filter] ------------------------------------------ -Η συμπεριφορά των σχολίων είναι σύμφωνη με τους ισχυρισμούς `Assert::match()` και `Assert::matchFile()`. Αλλά το μοτίβο εντοπίζεται στην τυπική έξοδο της δοκιμής. Μια κατάλληλη περίπτωση χρήσης είναι όταν υποθέτουμε ότι η δοκιμή τερματίζεται με μοιραίο σφάλμα και πρέπει να επαληθεύσουμε την έξοδό της. +@outputMatch και @outputMatchFile .[filter] +------------------------------------------- +Η λειτουργία των annotations είναι πανομοιότυπη με τις assertions `Assert::match()` και `Assert::matchFile()`. Το πρότυπο (pattern) όμως αναζητείται στο κείμενο που η δοκιμή έστειλε στην τυπική της έξοδο. Βρίσκει εφαρμογή εάν υποθέτουμε ότι η δοκιμή θα τελειώσει με fatal error και χρειαζόμαστε να επαληθεύσουμε την έξοδό της. @phpIni .[filter] ----------------- -Ορίζει τις τιμές διαμόρφωσης INI για τη δοκιμή. Για παράδειγμα το γράφουμε ως `@phpIni precision=20` και λειτουργεί με τον ίδιο τρόπο όπως αν περνούσαμε την τιμή από τη γραμμή εντολών με την παράμετρο `-d precision=20`. +Για τη δοκιμή ορίζει τιμές διαμόρφωσης INI. Γράφουμε για παράδειγμα ως `@phpIni precision=20` και λειτουργεί το ίδιο σαν να είχαμε δώσει την τιμή από τη γραμμή εντολών μέσω της παραμέτρου `-d precision=20`. diff --git a/tester/el/testcase.texy b/tester/el/testcase.texy index d62a9fc63b..625b0d9b35 100644 --- a/tester/el/testcase.texy +++ b/tester/el/testcase.texy @@ -2,9 +2,9 @@ TestCase ******** .[perex] -Οι ισχυρισμοί μπορούν να ακολουθούν έναν προς έναν σε απλές δοκιμές. Αλλά μερικές φορές είναι χρήσιμο να περικλείονται οι ισχυρισμοί σε κλάση δοκιμής και να δομούνται με αυτόν τον τρόπο. +Σε απλές δοκιμές, οι assertions μπορούν να ακολουθούν η μία την άλλη. Μερικές φορές όμως είναι πιο βολικό να ομαδοποιούμε τις assertions σε μια κλάση δοκιμής και έτσι να τις δομούμε. -Η κλάση πρέπει να είναι απόγονος της `Tester\TestCase` και μιλάμε για αυτήν απλά ως **testcase**. +Η κλάση πρέπει να είναι απόγονος της `Tester\TestCase` και απλουστευμένα την αποκαλούμε **testcase**. Η κλάση πρέπει να περιέχει μεθόδους δοκιμής που ξεκινούν με `test`. Αυτές οι μέθοδοι θα εκτελεστούν ως δοκιμές: ```php use Tester\Assert; @@ -22,11 +22,11 @@ class RectangleTest extends Tester\TestCase } } -# Run testing methods +# Εκτέλεση των μεθόδων δοκιμής (new RectangleTest)->run(); ``` -Μπορούμε να εμπλουτίσουμε ένα testcase με τις μεθόδους `setUp()` και `tearDown()`. Καλούνται πριν/μετά από κάθε μέθοδο δοκιμής: +Μια τέτοια γραμμένη δοκιμή μπορεί επιπλέον να εμπλουτιστεί με τις μεθόδους `setUp()` και `tearDown()`. Καλούνται πριν, αντίστοιχα μετά από κάθε μέθοδο δοκιμής: ```php use Tester\Assert; @@ -35,12 +35,12 @@ class NextTest extends Tester\TestCase { public function setUp() { - # Preparation + # Προετοιμασία } public function tearDown() { - # Clean-up + # Καθαρισμός } public function testOne() @@ -54,14 +54,14 @@ class NextTest extends Tester\TestCase } } -# Run testing methods +# Εκτέλεση των μεθόδων δοκιμής (new NextTest)->run(); /* -Method Calls Order ------------------- +Σειρά κλήσης μεθόδων +-------------------- setUp() testOne() tearDown() @@ -72,9 +72,9 @@ tearDown() */ ``` -Εάν προκύψει σφάλμα σε μια φάση `setUp()` ή `tearDown()`, η δοκιμή θα αποτύχει. Εάν το σφάλμα εμφανιστεί στη μέθοδο δοκιμής, η μέθοδος `tearDown()` καλείται ούτως ή άλλως, αλλά με κατασταλμένα σφάλματα σε αυτήν. +Εάν προκύψει σφάλμα στη φάση `setUp()` ή `tearDown()`, η δοκιμή αποτυγχάνει συνολικά. Εάν προκύψει σφάλμα στη μέθοδο δοκιμής, παρόλα αυτά η μέθοδος `tearDown()` εκτελείται, αλλά με καταστολή των σφαλμάτων σε αυτήν. -Συνιστούμε να γράφετε τον σχολιασμό [@testCase |test-annotations#@testCase] στην αρχή της δοκιμής, τότε ο test runner της γραμμής εντολών θα εκτελέσει τις επιμέρους μεθόδους testcase σε ξεχωριστές διεργασίες και παράλληλα σε πολλαπλά νήματα. Αυτό μπορεί να επιταχύνει σημαντικά ολόκληρη τη διαδικασία δοκιμής. +Συνιστούμε στην αρχή της δοκιμής να γράψετε την annotation [@testCase |test-annotations#testCase], τότε ο εκτελεστής δοκιμών από τη γραμμή εντολών θα εκτελεί τις επιμέρους μεθόδους του testcase σε ξεχωριστές διεργασίες και παράλληλα σε πολλαπλά νήματα. Αυτό μπορεί να επιταχύνει σημαντικά ολόκληρη τη διαδικασία δοκιμών. /--php <?php @@ -82,15 +82,15 @@ tearDown() \-- -Σχολιασμός μεθόδων .[#toc-annotation-of-methods] -================================================ +Annotations μεθόδων +=================== -Υπάρχουν μερικοί σχολιασμοί που είναι διαθέσιμοι για να μας βοηθήσουν με τον έλεγχο των μεθόδων. Τις γράφουμε προς τη μέθοδο δοκιμής. +Για τις μεθόδους δοκιμής έχουμε στη διάθεσή μας αρκετές annotations που μας διευκολύνουν στις δοκιμές. Τις γράφουμε στη μέθοδο δοκιμής. @throws .[filter] ----------------- -Πρόκειται για ισότιμη χρήση του `Assert::exception()` μέσα σε μια μέθοδο δοκιμής. Αλλά η σημειογραφία είναι πιο ευανάγνωστη: +Είναι ισοδύναμο με τη χρήση του `Assert::exception()` μέσα στη μέθοδο δοκιμής. Η σύνταξη όμως είναι πιο ευανάγνωστη: ```php /** @@ -114,9 +114,9 @@ public function testTwo() @dataProvider .[filter] ----------------------- -Αυτός ο σχολιασμός ταιριάζει όταν θέλουμε να εκτελέσουμε τη μέθοδο δοκιμής πολλές φορές αλλά με διαφορετικά ορίσματα. (Δεν πρέπει να συγχέεται με τον σχολιασμό με το ίδιο όνομα για [αρχεία |test-annotations#dataProvider]). +Αν θέλουμε να εκτελέσουμε τη μέθοδο δοκιμής πολλές φορές, αλλά με διαφορετικές παραμέτρους, αυτή η annotation είναι χρήσιμη. (Μην τη συγχέετε με την ομώνυμη annotation για [αρχεία |test-annotations#dataProvider].) -Ως όρισμα γράφουμε το όνομα της μεθόδου που επιστρέφει τις παραμέτρους για τη μέθοδο δοκιμής. Η μέθοδος πρέπει να επιστρέφει έναν πίνακα ή ένα Traversable. Απλό παράδειγμα: +Μετά από αυτήν αναφέρουμε το όνομα της μεθόδου που επιστρέφει τα ορίσματα για τη μέθοδο δοκιμής. Η μέθοδος πρέπει να επιστρέψει έναν πίνακα ή Traversable. Ένα απλό παράδειγμα: ```php public function getLoopArgs() @@ -138,7 +138,7 @@ public function testLoop($a, $b, $c) } ``` -Η άλλη παραλλαγή **@dataProvider** δέχεται ως όρισμα τη διαδρομή προς το αρχείο INI (σχετικά με το αρχείο δοκιμής). Η μέθοδος καλείται τόσες φορές όσες είναι ο αριθμός των τμημάτων που περιέχονται στο αρχείο INI. Αρχείο `loop-args.ini`: +Η δεύτερη παραλλαγή της annotation **@dataProvider** δέχεται ως παράμετρο τη διαδρομή προς ένα αρχείο INI (σχετικά με το αρχείο της δοκιμής). Η μέθοδος καλείται τόσες φορές όσες είναι οι ενότητες στο αρχείο INI. Αρχείο `loop-args.ini`: ```ini [one] @@ -169,7 +169,7 @@ public function testLoop($a, $b, $c) } ``` -Ομοίως, μπορούμε να περάσουμε τη διαδρομή σε ένα σενάριο PHP αντί για INI. Πρέπει να επιστρέφει array ή Traversable. Αρχείο `loop-args.php`: +Παρόμοια, αντί για αρχείο INI μπορούμε να παραπέμψουμε σε ένα PHP script. Αυτό πρέπει να επιστρέψει έναν πίνακα ή Traversable. Αρχείο `loop-args.php`: ```php return [ diff --git a/tester/el/writing-tests.texy b/tester/el/writing-tests.texy index 01a1e25d58..1c087078ea 100644 --- a/tester/el/writing-tests.texy +++ b/tester/el/writing-tests.texy @@ -1,21 +1,20 @@ -Δοκιμές γραφής -************** +Συγγραφή δοκιμών +**************** .[perex] -Η συγγραφή δοκιμών για το Nette Tester είναι μοναδική στο ότι κάθε δοκιμή είναι ένα PHP script που μπορεί να εκτελεστεί αυτόνομα.. Αυτό έχει μεγάλες δυνατότητες. -Καθώς γράφετε τη δοκιμή, μπορείτε απλά να την εκτελέσετε για να δείτε αν λειτουργεί σωστά. Αν όχι, μπορείτε εύκολα να το εξετάσετε στο IDE και να αναζητήσετε ένα σφάλμα. +Η συγγραφή δοκιμών για το Nette Tester είναι μοναδική στο ότι κάθε δοκιμή είναι ένα PHP script που μπορεί να εκτελεστεί αυτόνομα. Αυτό κρύβει μεγάλο δυναμικό. Ήδη όταν γράφετε τη δοκιμή, μπορείτε απλά να την εκτελείτε και να διαπιστώνετε αν λειτουργεί σωστά. Αν όχι, μπορείτε εύκολα να την κάνετε βήμα-βήμα στο IDE και να αναζητήσετε το σφάλμα. -Μπορείτε ακόμη και να ανοίξετε τη δοκιμή σε ένα πρόγραμμα περιήγησης. Αλλά πάνω απ' όλα - εκτελώντας το, θα εκτελέσετε τη δοκιμή. Θα μάθετε αμέσως αν πέρασε ή απέτυχε. +Μπορείτε ακόμη και να ανοίξετε τη δοκιμή στον browser. Αλλά κυρίως - με την εκτέλεσή της, εκτελείτε τη δοκιμή. Διαπιστώνετε αμέσως αν πέρασε ή απέτυχε. -Στο εισαγωγικό κεφάλαιο, [δείξαμε |guide#What Makes Tester Unique?] ένα πραγματικά τετριμμένο τεστ χρήσης του πίνακα της PHP. Τώρα θα δημιουργήσουμε τη δική μας κλάση, την οποία θα δοκιμάσουμε, αν και θα είναι επίσης απλή. +Στο εισαγωγικό κεφάλαιο [δείξαμε |guide#Τι κάνει τον Tester μοναδικό] μια πραγματικά τετριμμένη δοκιμή εργασίας με πίνακα. Τώρα θα δημιουργήσουμε τη δική μας κλάση που θα δοκιμάσουμε, αν και θα είναι επίσης απλή. -Ας ξεκινήσουμε με μια τυπική διάταξη καταλόγου για μια βιβλιοθήκη ή ένα έργο. Είναι σημαντικό να διαχωρίσουμε τις δοκιμές από τον υπόλοιπο κώδικα, για παράδειγμα λόγω ανάπτυξης, επειδή δεν θέλουμε να ανεβάζουμε τις δοκιμές στον διακομιστή. Η δομή μπορεί να είναι η εξής: +Θα ξεκινήσουμε από την τυπική δομή καταλόγων για μια βιβλιοθήκη ή ένα project. Είναι σημαντικό να διαχωρίσουμε τις δοκιμές από τον υπόλοιπο κώδικα, για παράδειγμα λόγω του deployment, επειδή δεν θέλουμε να ανεβάσουμε τις δοκιμές στον παραγωγικό server. Η δομή μπορεί να είναι κάπως έτσι: ``` -├── src/ # code that we will test +├── src/ # κώδικας που θα δοκιμάσουμε │ ├── Rectangle.php │ └── ... -├── tests/ # tests +├── tests/ # δοκιμές │ ├── bootstrap.php │ ├── RectangleTest.php │ └── ... @@ -23,7 +22,7 @@ └── composer.json ``` -Και τώρα θα δημιουργήσουμε μεμονωμένα αρχεία. Θα ξεκινήσουμε με τη δοκιμασμένη κλάση, την οποία θα τοποθετήσουμε στο αρχείο `src/Rectangle.php` +Και τώρα θα δημιουργήσουμε τα επιμέρους αρχεία. Θα ξεκινήσουμε από την κλάση που θα δοκιμάσουμε, την οποία θα τοποθετήσουμε στο αρχείο `src/Rectangle.php` ```php .{file:src/Rectangle.php} <?php @@ -35,7 +34,7 @@ class Rectangle public function __construct(float $width, float $height) { if ($width < 0 || $height < 0) { - throw new InvalidArgumentException('The dimension must not be negative.'); + throw new InvalidArgumentException('Η διάσταση δεν πρέπει να είναι αρνητική.'); } $this->width = $width; $this->height = $height; @@ -53,7 +52,7 @@ class Rectangle } ``` -Και θα δημιουργήσουμε μια δοκιμή για αυτήν. Το όνομα του αρχείου δοκιμής θα πρέπει να ταιριάζει με τη μάσκα `*Test.php` ή `*.phpt`, εμείς θα επιλέξουμε την παραλλαγή `RectangleTest.php`: +Και θα δημιουργήσουμε μια δοκιμή γι' αυτήν. Το όνομα του αρχείου με τη δοκιμή θα πρέπει να αντιστοιχεί στη μάσκα `*Test.php` ή `*.phpt`, θα επιλέξουμε για παράδειγμα την παραλλαγή `RectangleTest.php`: ```php .{file:tests/RectangleTest.php} @@ -62,31 +61,31 @@ use Tester\Assert; require __DIR__ . '/bootstrap.php'; -// γενική επιμήκης +// γενικό ορθογώνιο $rect = new Rectangle(10, 20); -Assert::same(200.0, $rect->getArea()); # we will verify the expected results +Assert::same(200.0, $rect->getArea()); # επαληθεύουμε τα αναμενόμενα αποτελέσματα Assert::false($rect->isSquare()); ``` -Όπως μπορείτε να δείτε, [μέθοδοι ισχυρισμού |Assertions] όπως η `Assert::same()` χρησιμοποιούνται για να βεβαιώσουν ότι μια πραγματική τιμή ταιριάζει με μια αναμενόμενη τιμή. +Όπως βλέπετε, οι λεγόμενες [μέθοδοι assertion|assertions] όπως `Assert::same()` χρησιμοποιούνται για να επιβεβαιώσουν ότι η πραγματική τιμή αντιστοιχεί στην αναμενόμενη τιμή. -Το τελευταίο βήμα είναι η δημιουργία του αρχείου `bootstrap.php`. Περιέχει έναν κοινό κώδικα για όλες τις δοκιμές. Για παράδειγμα, κλάσεις αυτόματης φόρτωσης, διαμόρφωση περιβάλλοντος, δημιουργία προσωρινού καταλόγου, βοηθητικά προγράμματα και παρόμοια. Κάθε δοκιμή φορτώνει το bootstrap και δίνει προσοχή μόνο στον έλεγχο. Το bootstrap μπορεί να μοιάζει ως εξής: +Απομένει το τελευταίο βήμα και αυτό είναι το αρχείο `bootstrap.php`. Αυτό περιέχει κώδικα κοινό για όλες τις δοκιμές, για παράδειγμα autoloading κλάσεων, διαμόρφωση περιβάλλοντος, δημιουργία προσωρινού καταλόγου, βοηθητικές συναρτήσεις και παρόμοια. Όλες οι δοκιμές φορτώνουν το bootstrap και στη συνέχεια ασχολούνται μόνο με τις δοκιμές. Το Bootstrap μπορεί να μοιάζει ως εξής: ```php .{file:tests/bootstrap.php} <?php -require __DIR__ . '/vendor/autoload.php'; # load Composer autoloader +require __DIR__ . '/../vendor/autoload.php'; # φορτώνει τον Composer autoloader -Tester\Environment::setup(); # initialization of Nette Tester +Tester\Environment::setup(); # αρχικοποίηση του Nette Tester -// και άλλες ρυθμίσεις (απλώς ένα παράδειγμα, στην περίπτωσή μας δεν χρειάζονται) +// και άλλη διαμόρφωση (είναι μόνο παράδειγμα, στην περίπτωσή μας δεν χρειάζονται) date_default_timezone_set('Europe/Prague'); define('TmpDir', '/tmp/app-tests'); ``` .[note] -Αυτό το bootstrap υποθέτει ότι ο αυτόματος φορτωτής Composer θα είναι σε θέση να φορτώσει και την κλάση `Rectangle.php`. Αυτό μπορεί να επιτευχθεί, για παράδειγμα, [θέτοντας το τμήμα autoload |best-practices:composer#autoloading] στο `composer.json`, κ.λπ. +Το αναφερόμενο bootstrap προϋποθέτει ότι ο autoloader του Composer θα είναι σε θέση να φορτώσει και την κλάση `Rectangle.php`. Αυτό μπορεί να επιτευχθεί για παράδειγμα με [ρύθμιση της ενότητας autoload |best-practices:composer#Autoloading] στο `composer.json` κ.λπ. -Μπορούμε τώρα να εκτελέσουμε το τεστ από τη γραμμή εντολών όπως κάθε άλλο αυτόνομο PHP script. Η πρώτη εκτέλεση θα αποκαλύψει τυχόν συντακτικά λάθη, και αν δεν κάνατε κάποιο τυπογραφικό λάθος, θα δείτε: +Μπορούμε τώρα να εκτελέσουμε τη δοκιμή από τη γραμμή εντολών όπως οποιοδήποτε άλλο αυτόνομο PHP script. Η πρώτη εκτέλεση θα μας αποκαλύψει πιθανά συντακτικά λάθη και αν δεν υπάρχει πουθενά ορθογραφικό λάθος, θα εκτυπωθεί: /--pre .[terminal] $ php RectangleTest.php @@ -94,7 +93,7 @@ $ php RectangleTest.php <span style="color:#FFF; background-color:#090">OK</span> \-- -Αν αλλάξουμε στη δοκιμή τη δήλωση σε false `Assert::same(123, $rect->getArea());`, θα συμβεί αυτό: +Αν αλλάζαμε στη δοκιμή τον ισχυρισμό σε ψευδή `Assert::same(123, $rect->getArea());` θα συνέβαινε αυτό: /--pre .[terminal] $ php RectangleTest.php @@ -107,35 +106,35 @@ $ php RectangleTest.php \-- -Όταν γράφετε δοκιμές, είναι καλό να πιάνετε όλες τις ακραίες καταστάσεις. Για παράδειγμα, αν η είσοδος είναι μηδέν, ένας αρνητικός αριθμός, σε άλλες περιπτώσεις ένα κενό αλφαριθμητικό, null, κ.λπ. Στην πραγματικότητα, σας αναγκάζει να σκεφτείτε και να αποφασίσετε πώς πρέπει να συμπεριφέρεται ο κώδικας σε τέτοιες καταστάσεις. Οι δοκιμές στη συνέχεια διορθώνουν τη συμπεριφορά. +Κατά τη συγγραφή δοκιμών είναι καλό να καλύπτονται όλες οι ακραίες περιπτώσεις. Για παράδειγμα, όταν η είσοδος είναι μηδέν, αρνητικός αριθμός, σε άλλες περιπτώσεις ίσως κενή συμβολοσειρά, null κ.λπ. Στην πραγματικότητα, σας αναγκάζει να σκεφτείτε και να αποφασίσετε πώς πρέπει να συμπεριφέρεται ο κώδικας σε τέτοιες καταστάσεις. Οι δοκιμές στη συνέχεια σταθεροποιούν τη συμπεριφορά. -Στην περίπτωσή μας, μια αρνητική τιμή θα πρέπει να πετάξει μια εξαίρεση, την οποία επαληθεύουμε με την [Assert::exception() |Assertions#Assert::exception]: +Στην περίπτωσή μας, η αρνητική τιμή πρέπει να δημιουργήσει μια εξαίρεση, την οποία επαληθεύουμε χρησιμοποιώντας το [Assert::exception() |Assertions#Assert::exception]: ```php .{file:tests/RectangleTest.php} -// το πλάτος δεν πρέπει να είναι αρνητικός αριθμός +// το πλάτος δεν πρέπει να είναι αρνητικό Assert::exception( fn() => new Rectangle(-1, 20), InvalidArgumentException::class, - 'The dimension must not be negative.', + 'Η διάσταση δεν πρέπει να είναι αρνητική.', ); ``` -Και προσθέτουμε έναν παρόμοιο έλεγχο για το ύψος. Τέλος, ελέγχουμε ότι το `isSquare()` επιστρέφει `true` αν και οι δύο διαστάσεις είναι ίδιες. Προσπαθήστε να γράψετε τέτοιες δοκιμές ως άσκηση. +Και μια παρόμοια δοκιμή προσθέτουμε για το ύψος. Τέλος, ελέγχουμε ότι το `isSquare()` επιστρέφει `true`, εάν και οι δύο διαστάσεις είναι ίδιες. Δοκιμάστε ως άσκηση να γράψετε τέτοιες δοκιμές. -Καλά οργανωμένες δοκιμές .[#toc-well-arranged-tests] -==================================================== +Πιο ευανάγνωστες δοκιμές +======================== -Το μέγεθος του αρχείου δοκιμών μπορεί να αυξηθεί και να γίνει γρήγορα ακατάστατο. Ως εκ τούτου, είναι πρακτικό να ομαδοποιούνται οι επιμέρους δοκιμαζόμενες περιοχές σε ξεχωριστές λειτουργίες. +Το μέγεθος του αρχείου με τη δοκιμή μπορεί να αυξηθεί και γρήγορα να γίνει δυσανάγνωστο. Γι' αυτό είναι πρακτικό να ομαδοποιούμε τις επιμέρους δοκιμαζόμενες περιοχές σε ξεχωριστές συναρτήσεις. -Αρχικά, θα παρουσιάσουμε μια απλούστερη αλλά κομψή παραλλαγή, χρησιμοποιώντας την παγκόσμια συνάρτηση `test()`. Ο ελεγκτής δεν τη δημιουργεί αυτόματα, για να αποφευχθεί η σύγκρουση αν είχατε μια συνάρτηση με το ίδιο όνομα στον κώδικά σας. Δημιουργείται μόνο από τη μέθοδο `setupFunctions()`, την οποία καλείτε στο αρχείο `bootstrap.php`: +Πρώτα θα δείξουμε μια απλούστερη, αλλά κομψή παραλλαγή, και αυτό με τη χρήση της καθολικής συνάρτησης `test()`. Ο Tester δεν τη δημιουργεί αυτόματα, για να μην υπάρξει σύγκρουση, αν είχατε στον κώδικα μια συνάρτηση με το ίδιο όνομα. Τη δημιουργεί η μέθοδος `setupFunctions()`, την οποία καλέστε στο αρχείο `bootstrap.php`: ```php .{file:tests/bootstrap.php} Tester\Environment::setup(); Tester\Environment::setupFunctions(); ``` -Χρησιμοποιώντας αυτή τη συνάρτηση, μπορούμε να χωρίσουμε όμορφα το αρχείο δοκιμής σε ονομαστικές μονάδες. Κατά την εκτέλεση, οι ετικέτες θα εμφανίζονται η μία μετά την άλλη. +Με τη βοήθεια αυτής της συνάρτησης μπορούμε να διαρθρώσουμε όμορφα το αρχείο δοκιμής σε ονομασμένες ενότητες. Κατά την εκτέλεση θα εκτυπώνονται διαδοχικά οι περιγραφές. ```php .{file:tests/RectangleTest.php} <?php @@ -143,19 +142,19 @@ use Tester\Assert; require __DIR__ . '/bootstrap.php'; -test('general oblong', function () { +test('γενικό ορθογώνιο', function () { $rect = new Rectangle(10, 20); Assert::same(200.0, $rect->getArea()); Assert::false($rect->isSquare()); }); -test('general square', function () { +test('γενικό τετράγωνο', function () { $rect = new Rectangle(5, 5); Assert::same(25.0, $rect->getArea()); Assert::true($rect->isSquare()); }); -test('dimensions must not be negative', function () { +test('οι διαστάσεις δεν πρέπει να είναι αρνητικές', function () { Assert::exception( fn() => new Rectangle(-1, 20), InvalidArgumentException::class, @@ -168,15 +167,15 @@ test('dimensions must not be negative', function () { }); ``` -Αν χρειάζεται να εκτελέσετε τον κώδικα πριν ή μετά από κάθε δοκιμή, περάστε τον στις διευθύνσεις `setUp()` ή `tearDown()`: +Εάν χρειάζεστε να εκτελέσετε κώδικα πριν ή μετά από κάθε δοκιμή, περάστε τον στη συνάρτηση `setUp()` αντίστοιχα `tearDown()`: ```php setUp(function () { - // κώδικας αρχικοποίησης που θα εκτελείται πριν από κάθε test() + // κώδικας αρχικοποίησης που εκτελείται πριν από κάθε test() }); ``` -Η δεύτερη παραλλαγή είναι αντικείμενο. Θα δημιουργήσουμε τη λεγόμενη TestCase, η οποία είναι μια κλάση όπου οι επιμέρους μονάδες αναπαρίστανται από μεθόδους των οποίων τα ονόματα αρχίζουν με test-. +Η δεύτερη παραλλαγή είναι αντικειμενοστραφής. Θα δημιουργήσουμε το λεγόμενο TestCase, το οποίο είναι μια κλάση όπου οι επιμέρους ενότητες αντιπροσωπεύουν μεθόδους, τα ονόματα των οποίων ξεκινούν με test–. ```php .{file:tests/RectangleTest.php} class RectangleTest extends Tester\TestCase @@ -208,25 +207,25 @@ class RectangleTest extends Tester\TestCase } } -// Εκτέλεση μεθόδων δοκιμής +// Εκτέλεση των μεθόδων δοκιμής (new RectangleTest)->run(); ``` -Αυτή τη φορά χρησιμοποιήσαμε ένα σχόλιο `@throw` για να ελέγξουμε για εξαιρέσεις. Δείτε το κεφάλαιο [TestCase |TestCase] για περισσότερες πληροφορίες. +Για τον έλεγχο των εξαιρέσεων χρησιμοποιήσαμε αυτή τη φορά την annotation `@throws`. Περισσότερα θα μάθετε στο κεφάλαιο [TestCase]. -Συναρτήσεις βοήθειας .[#toc-helpers-functions] -============================================== +Βοηθητικές συναρτήσεις +====================== -Το Nette Tester περιλαμβάνει αρκετές κλάσεις και συναρτήσεις που μπορούν να σας διευκολύνουν τον έλεγχο, για παράδειγμα, βοηθητικά προγράμματα για τον έλεγχο του περιεχομένου ενός εγγράφου HTML, για τον έλεγχο των λειτουργιών εργασίας με αρχεία κ.ο.κ. +Το Nette Tester περιέχει αρκετές κλάσεις και συναρτήσεις που μπορούν να σας διευκολύνουν, για παράδειγμα, στον έλεγχο του περιεχομένου ενός εγγράφου HTML, στον έλεγχο συναρτήσεων που εργάζονται με αρχεία και ούτω καθεξής. -Μπορείτε να βρείτε μια περιγραφή τους στη σελίδα [Helpers |Helpers]. +Η περιγραφή τους βρίσκεται στη σελίδα [Βοηθητικές κλάσεις|helpers]. -Σχολιασμός και παράλειψη δοκιμών .[#toc-annotation-and-skipping-tests] -====================================================================== +Annotations και παράλειψη δοκιμών +================================= -Η εκτέλεση των δοκιμών μπορεί να επηρεαστεί από τις σημειώσεις στο σχόλιο phpDoc στην αρχή του αρχείου. Για παράδειγμα, μπορεί να μοιάζει με αυτό: +Η εκτέλεση των δοκιμών μπορεί να επηρεαστεί από annotations με τη μορφή σχολίου phpDoc στην αρχή του αρχείου. Μπορεί να μοιάζει για παράδειγμα έτσι: ```php .{file:tests/RectangleTest.php} /** @@ -235,23 +234,23 @@ class RectangleTest extends Tester\TestCase */ ``` -Οι σχολιασμοί λένε ότι η δοκιμή πρέπει να εκτελείται μόνο με PHP έκδοση 7.2 ή νεότερη και αν υπάρχουν οι επεκτάσεις PHP pdo και pdo_pgsql. Αυτές οι επισημάνσεις ελέγχονται από τον [εκτελεστή δοκιμών γραμμής εντολών |running-tests], ο οποίος, αν δεν πληρούνται οι προϋποθέσεις, παραλείπει τη δοκιμή και τη σημειώνει με το γράμμα `s` - skipped. Ωστόσο, δεν έχουν καμία επίδραση όταν η δοκιμή εκτελείται χειροκίνητα. +Οι αναφερόμενες annotations λένε ότι η δοκιμή πρέπει να εκτελεστεί μόνο με την έκδοση PHP 7.2 ή νεότερη και εάν είναι παρούσες οι επεκτάσεις PHP pdo και pdo_pgsql. Αυτές οι annotations καθοδηγούν τον [εκτελεστή δοκιμών από τη γραμμή εντολών|running-tests], ο οποίος στην περίπτωση που οι συνθήκες δεν πληρούνται, παραλείπει τη δοκιμή και στην έξοδο την επισημαίνει με το γράμμα `s` - skipped. Ωστόσο, κατά τη χειροκίνητη εκτέλεση της δοκιμής δεν έχουν καμία επίδραση. -Για μια περιγραφή των σχολίων, ανατρέξτε στην ενότητα [Σχόλια δοκιμών |Test Annotations]. +Η περιγραφή των annotations βρίσκεται στη σελίδα [Annotations δοκιμών|test-annotations]. -Η δοκιμή μπορεί επίσης να παραλειφθεί με βάση τη δική της συνθήκη με το `Environment::skip()`. Για παράδειγμα, θα παραλείψουμε αυτή τη δοκιμή στα Windows: +Μια δοκιμή μπορεί να παραλειφθεί επίσης βάσει της εκπλήρωσης μιας δικής της συνθήκης χρησιμοποιώντας το `Environment::skip()`. Για παράδειγμα, αυτή παραλείπει τις δοκιμές στα Windows: ```php if (defined('PHP_WINDOWS_VERSION_BUILD')) { - Tester\Environment::skip('Requires UNIX.'); + Tester\Environment::skip('Απαιτεί UNIX.'); } ``` -Δομή καταλόγου .[#toc-directory-structure] -========================================== +Δομή καταλόγων +============== -Για λίγο μεγαλύτερες βιβλιοθήκες ή έργα, συνιστούμε να χωρίσετε τον κατάλογο δοκιμών σε υποκαταλόγους ανάλογα με το χώρο ονομάτων της δοκιμαζόμενης κλάσης: +Συνιστούμε σε λίγο μεγαλύτερες βιβλιοθήκες ή projects να χωρίσετε τον κατάλογο με τις δοκιμές επιπλέον σε υποκαταλόγους σύμφωνα με το namespace της δοκιμαζόμενης κλάσης: ``` └── tests/ @@ -269,22 +268,22 @@ if (defined('PHP_WINDOWS_VERSION_BUILD')) { └── ... ``` -Θα είστε σε θέση να εκτελείτε δοκιμές από έναν μόνο χώρο ονομάτων δηλαδή υποκατάλογο: +Έτσι θα μπορείτε να εκτελείτε δοκιμές από ένα μόνο namespace δηλαδή υποκατάλογο: /--pre .[terminal] tester tests/NamespaceOne \-- -Περιπτώσεις αιχμής .[#toc-edge-cases] -===================================== +Ειδικές καταστάσεις +=================== -Μια δοκιμή που δεν καλεί καμία μέθοδο επιβεβαίωσης είναι ύποπτη και θα αξιολογηθεί ως λανθασμένη: +Μια δοκιμή που δεν καλεί ούτε μία μέθοδο assertion είναι ύποπτη και αξιολογείται ως λανθασμένη: /--pre .[terminal] <span style="color: #FFF; background-color: #900">Error: This test forgets to execute an assertion.</span> \-- -Εάν η δοκιμή χωρίς κλήση των ισχυρισμών πρέπει πραγματικά να θεωρηθεί έγκυρη, καλέστε για παράδειγμα το `Assert::true(true)`. +Εάν πραγματικά μια δοκιμή χωρίς κλήση assertions πρέπει να θεωρηθεί έγκυρη, καλέστε για παράδειγμα `Assert::true(true)`. -Μπορεί επίσης να είναι ύπουλη η χρήση των `exit()` και `die()` για να τελειώσει η δοκιμή με ένα μήνυμα σφάλματος. Για παράδειγμα, το `exit('Error in connection')` τερματίζει τη δοκιμή με κωδικό εξόδου 0, ο οποίος σηματοδοτεί επιτυχία. Χρησιμοποιήστε το `Assert::fail('Error in connection')`. +Επίσης, μπορεί να είναι παραπλανητική η χρήση των `exit()` και `die()` για τον τερματισμό της δοκιμής με μήνυμα σφάλματος. Για παράδειγμα, το `exit('Error in connection')` τερματίζει τη δοκιμή με κωδικό επιστροφής 0, που σηματοδοτεί επιτυχία. Χρησιμοποιήστε το `Assert::fail('Error in connection')`. diff --git a/tester/en/@home.texy b/tester/en/@home.texy index a21e109859..59b602e779 100644 --- a/tester/en/@home.texy +++ b/tester/en/@home.texy @@ -1,2 +1,2 @@ {{maintitle: Nette Tester – Enjoyable Unit Testing in PHP}} -{{description: Nette Tester is a simple and yet very handy PHP code testing tool.}} +{{description: Nette Tester is a simple yet very handy tool for testing PHP code.}} diff --git a/tester/en/@meta.texy b/tester/en/@meta.texy new file mode 100644 index 0000000000..835585c85e --- /dev/null +++ b/tester/en/@meta.texy @@ -0,0 +1 @@ +{{sitename: Tester Documentation}} diff --git a/tester/en/assertions.texy b/tester/en/assertions.texy index 6a6b61f1f3..cb8558551e 100644 --- a/tester/en/assertions.texy +++ b/tester/en/assertions.texy @@ -2,34 +2,34 @@ Assertions ********** .[perex] -Aassertions are used to assert that an actual value matches an expected value. They are methods of the `Tester\Assert`. +Assertions are used to confirm that the actual value matches the expected value. They are methods of the `Tester\Assert` class. -Choose the most accurate assertions. Is better `Assert::same($a, $b)` than `Assert::true($a === $b)` because it displays meaningful error message on failure. In the second case we get `false should be true` only and it says nothing about $a and $b variables contents. +Choose the most suitable assertions. `Assert::same($a, $b)` is better than `Assert::true($a === $b)`, because it displays a meaningful error message upon failure. In the second case, we only get `false should be true` which tells us nothing about the contents of the variables `$a` and `$b`. -Most assertions can also have an optional `$description` that appears in the error message if the expectation fails. +Most assertions can also have an optional description in the `$description` parameter, which is displayed in the error message if the expectation fails. -Examples assume the following class alias is defined: +Examples assume the following alias is created: ```php use Tester\Assert; ``` -Assert::same($expected, $actual, string $description=null) .[method] --------------------------------------------------------------------- -`$expected` must be the same as `$actual`. It is the same as PHP operator `===`. +Assert::same($expected, $actual, ?string $description=null) .[method] +--------------------------------------------------------------------- +`$expected` must be identical to `$actual`. It's the same as the PHP operator `===`. -Assert::notSame($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------ -Opposite to `Assert::same()`, so it is the same as PHP operator `!==`. +Assert::notSame($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------ +Opposite of `Assert::same()`, meaning it's the same as the PHP operator `!==`. -Assert::equal($expected, $actual, string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] ------------------------------------------------------------------------------------------------------------------------- -`$expected` must be the same as `$actual`. Unlike `Assert::same()`, object identity, order of key pairs => value in arrays, and marginally different decimal numbers are ignored, which can be changed by setting `$matchIdentity` and `$matchOrder`. +Assert::equal($expected, $actual, ?string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] +------------------------------------------------------------------------------------------------------------------------- +`$expected` must be equal to `$actual`. Unlike `Assert::same()`, object identity, the order of key => value pairs in arrays, and marginally different decimal numbers are ignored, which can be changed by setting `$matchIdentity` and `$matchOrder`. -The following cases are identical from the point of view of `equal()`, but not for `same()`: +The following cases are equal from the perspective of `equal()`, but not `same()`: ```php Assert::equal(0.3, 0.1 + 0.2); @@ -40,81 +40,81 @@ Assert::equal( ); ``` -However, beware, the array `[1, 2]` and `[2, 1]` are not equal, because only the order of values differs, not the key => value pairs. The array `[1, 2]` can also be written as `[0 => 1, 1 => 2]` and therefore `[1 => 2, 0 => 1]` will be considered equal. +But beware, the arrays `[1, 2]` and `[2, 1]` are not the same, because only the order of values differs, not the key => value pairs. The array `[1, 2]` can also be written as `[0 => 1, 1 => 2]`, and `[1 => 2, 0 => 1]` will therefore be considered the same. -You can also use the so-called [#expectations] in `$expected`. +You can also use so-called [#Expectations] in `$expected`. -Assert::notEqual($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------- -Opposite to `Assert::equal()`. +Assert::notEqual($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------- +Opposite of `Assert::equal()`. -Assert::contains($needle, string|array $actual, string $description=null) .[method] ------------------------------------------------------------------------------------ -If `$actual` is a string, it must contain the substring `$needle`. If it is an array, it must contain the element `$needle` (it is compared strictly). +Assert::contains($needle, string|array $actual, ?string $description=null) .[method] +------------------------------------------------------------------------------------ +If `$actual` is a string, it must contain the substring `$needle`. If it is an array, it must contain the element `$needle` (compared strictly). -Assert::notContains($needle, string|array $actual, string $description=null) .[method] --------------------------------------------------------------------------------------- -Opposite to `Assert::contains()`. +Assert::notContains($needle, string|array $actual, ?string $description=null) .[method] +--------------------------------------------------------------------------------------- +Opposite of `Assert::contains()`. -Assert::hasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} -------------------------------------------------------------------------------------------------------- +Assert::hasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +-------------------------------------------------------------------------------------------------------- `$actual` must be an array and must contain the key `$needle`. -Assert::notHasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} ----------------------------------------------------------------------------------------------------------- +Assert::notHasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +----------------------------------------------------------------------------------------------------------- `$actual` must be an array and must not contain the key `$needle`. -Assert::true($value, string $description=null) .[method] --------------------------------------------------------- -`$value` must be `true`, so `$value === true`. +Assert::true($value, ?string $description=null) .[method] +--------------------------------------------------------- +`$value` must be `true`, i.e., `$value === true`. -Assert::truthy($value, string $description=null) .[method] ----------------------------------------------------------- -`$value` must be truthy, so it satisfies the condition `if ($value) ...`. +Assert::truthy($value, ?string $description=null) .[method] +----------------------------------------------------------- +`$value` must be truthy, i.e., it satisfies the condition `if ($value) ...`. -Assert::false($value, string $description=null) .[method] ---------------------------------------------------------- -`$value` must be `false`, so `$value === false`. +Assert::false($value, ?string $description=null) .[method] +---------------------------------------------------------- +`$value` must be `false`, i.e., `$value === false`. -Assert::falsey($value, string $description=null) .[method] ----------------------------------------------------------- -`$value` must be falsey, so it satisfies the condition `if (!$value) ...`. +Assert::falsey($value, ?string $description=null) .[method] +----------------------------------------------------------- +`$value` must be falsey, i.e., it satisfies the condition `if (!$value) ...`. -Assert::null($value, string $description=null) .[method] --------------------------------------------------------- -`$value` must be `null`, so `$value === null`. +Assert::null($value, ?string $description=null) .[method] +--------------------------------------------------------- +`$value` must be `null`, i.e., `$value === null`. -Assert::notNull($value, string $description=null) .[method] ------------------------------------------------------------ -`$value` must not be `null`, so `$value !== null`. +Assert::notNull($value, ?string $description=null) .[method] +------------------------------------------------------------ +`$value` must not be `null`, i.e., `$value !== null`. -Assert::nan($value, string $description=null) .[method] -------------------------------------------------------- -`$value` must be Not a Number. Use only the `Assert::nan()` for NAN testing. NAN value is very specific and assertions `Assert::same()` or `Assert::equal()` can behave unpredictably. +Assert::nan($value, ?string $description=null) .[method] +-------------------------------------------------------- +`$value` must be Not a Number. For testing NAN values, use exclusively `Assert::nan()`. The NAN value is very specific, and assertions like `Assert::same()` or `Assert::equal()` may behave unexpectedly. -Assert::count($count, Countable|array $value, string $description=null) .[method] ---------------------------------------------------------------------------------- -Number of elements in `$value` must be `$count`. So the same as `count($value) === $count`. +Assert::count($count, Countable|array $value, ?string $description=null) .[method] +---------------------------------------------------------------------------------- +The number of elements in `$value` must be `$count`. It's the same as `count($value) === $count`. -Assert::type(string|object $type, $value, string $description=null) .[method] ------------------------------------------------------------------------------ -`$value` must be of a given type. As `$type` we can use string: +Assert::type(string|object $type, $value, ?string $description=null) .[method] +------------------------------------------------------------------------------ +`$value` must be of the given type. As `$type` we can use a string: - `array` -- `list` - array indexed in ascending order of numeric keys from zero +- `list` - array indexed according to an ascending series of numeric keys from zero - `bool` - `callable` - `float` @@ -124,24 +124,24 @@ Assert::type(string|object $type, $value, string $description=null) .[method] - `resource` - `scalar` - `string` -- class name or object directly then must pass `$value instanceof $type` +- class name or directly an object, then `$value instanceof $type` must be -Assert::exception(callable $callable, string $class, string $message=null, $code=null) .[method] ------------------------------------------------------------------------------------------------- -On `$callable` invocation an exception of `$class` instance must be thrown. If we pass `$message`, the message of the exception must [match|#assert-match]. And if we pass `$code`, code of the exception must be the same. +Assert::exception(callable $callable, string $class, ?string $message=null, $code=null) .[method] +------------------------------------------------------------------------------------------------- +When calling `$callable`, an exception of class `$class` must be thrown. If we specify `$message`, the exception message must also [match the pattern |#Assert::match]. And if we specify `$code`, the codes must also strictly match. -For example, this test fails because message of the exception does not match: +For example, the following test will fail because the exception message does not match: ```php Assert::exception( fn() => throw new App\InvalidValueException('Zero value'), App\InvalidValueException::class, - 'Value is to low', + 'Value is too low', ); ``` -The `Assert::exception()` returns a thrown exception, so you can test a nested exception. +`Assert::exception()` returns the thrown exception, allowing you to test a nested exception as well. ```php $e = Assert::exception( @@ -154,9 +154,9 @@ Assert::type(RuntimeException::class, $e->getPrevious()); ``` -Assert::error(string $callable, int|string|array $type, string $message=null) .[method] ---------------------------------------------------------------------------------------- -Checks that the `$callable` invocation generates the expected errors (ie warnings, notices, etc.). As `$type` we specify one of the constants `E_...`, for example `E_WARNING`. And if pass `$message`, the error message must also [match|#assert-match] pattern. For example: +Assert::error(string $callable, int|string|array $type, ?string $message=null) .[method] +---------------------------------------------------------------------------------------- +Checks that the function `$callable` generated the expected errors (ie warnings, notices, etc). As `$type` specify one of the `E_...` constants, for example `E_WARNING`. And if we specify `$message`, the error message must also [match the pattern |#Assert::match]. For example: ```php Assert::error( @@ -166,7 +166,7 @@ Assert::error( ); ``` -If the callback generates more errors, we must expect all of them in the exact order. In this case we pass the array in `$type`: +If the callback generates more errors, we must expect them all in the exact order. In this case, pass an array in `$type`: ```php Assert::error(function () { @@ -179,69 +179,74 @@ Assert::error(function () { ``` .[note] -If `$type` is class name, this assertion behaves same as `Assert::exception()`. +If you specify the class name as `$type`, it behaves the same as `Assert::exception()`. Assert::noError(callable $callable) .[method] --------------------------------------------- -Checks that the function `$callable` does not throw any PHP warning/notice/error or exception. It is useful for testing a piece of code where is no other assertion. +Checks that the function `$callable` did not generate any warning, error or exception. It is useful for testing pieces of code where there is no other assertion. -Assert::match(string $pattern, $actual, string $description=null) .[method] ---------------------------------------------------------------------------- -`$actual` must match to `$pattern`. We can use two variants of patterns: regular expressions or wildcards. +Assert::match(string $pattern, $actual, ?string $description=null) .[method] +---------------------------------------------------------------------------- +`$actual` must match the pattern `$pattern`. We can use two variants of patterns: regular expressions or wildcards. -If we pass a regular expression as `$pattern`, we must use `~` or `#` to delimit it. Other delimiters are not supported. For example test where `$var` must contain only hexadecimal digits: +If we pass a regular expression as `$pattern`, we must use `~` or `#` to delimit it. Other delimiters are not supported. For example, a test where `$var` must contain only hexadecimal digits: ```php Assert::match('#^[0-9a-f]$#i', $var); ``` -The other variant is similar to string comparing but we can use some wild chars in `$pattern`: - -- `%a%` one or more of anything except for the end of line characters -- `%a?%` zero or more of anything except for the end of line characters -- `%A%` one or more of anything including the end of line characters -- `%A?%` zero or more of anything including the end of line characters -- `%s%` one or more white space characters except for the end of line characters -- `%s?%` zero or more white space characters except for the end of line characters -- `%S%` one or more of characters except for the white space -- `%S?%` zero or more of characters except for the white space -- `%c%` a single character of any sort (except for the end of line) +The second variant is similar to comparing ordinary strings, but we can use various wildcards in `$pattern`: + +- `%a%` one or more of anything except line ending characters +- `%a?%` zero or more of anything except line ending characters +- `%A%` one or more of anything including line ending characters +- `%A?%` zero or more of anything including line ending characters +- `%s%` one or more whitespace characters except line ending characters +- `%s?%` zero or more whitespace characters except line ending characters +- `%S%` one or more characters except whitespace characters +- `%S?%` zero or more characters except whitespace characters +- `%c%` a single character of any sort (except line ending) - `%d%` one or more digits - `%d?%` zero or more digits - `%i%` signed integer value -- `%f%` floating point number -- `%h%` one or more HEX digits +- `%f%` floating-point number +- `%h%` one or more hexadecimal digits - `%w%` one or more alphanumeric characters - `%%` one % character Examples: ```php -# Again, hexadecimal number test +# Again, test for a hexadecimal number Assert::match('%h%', $var); -# Generalized path to file and line number +# Generalization of file path and line number Assert::match('Error in file %a% on line %i%', $errorMessage); ``` -Assert::matchFile(string $file, $actual, string $description=null) .[method] ----------------------------------------------------------------------------- -The assertion is identical to [Assert::match() |#assert-match] but the pattern is loaded from `$file`. It is useful for very long strings testing. Test file stands readable. +Assert::notMatch(string $pattern, $actual, ?string $description=null) .[method]{data-version:2.5.6} +--------------------------------------------------------------------------------------------------- +Opposite of `Assert::match()`. + + +Assert::matchFile(string $file, $actual, ?string $description=null) .[method] +----------------------------------------------------------------------------- +This assertion is identical to [#Assert::match()], but the pattern is loaded from the file `$file`. This is useful for testing very long strings. The test file remains clear. Assert::fail(string $message, $actual=null, $expected=null) .[method] --------------------------------------------------------------------- -This assertion always fails. It is just handy. We can optionally pass expected and actual values. +This assertion always fails. Sometimes it's just useful. Optionally, we can specify the expected and actual value. Expectations ------------ -If we want to compare more complex structures with non-constant elements, the above assertions may not be sufficient. For example, we test a method that creates a new user and returns its attributes as an array. We do not know the password hash value, but we do know that it must be a hexadecimal string. And the only thing we know about the next element is that it must be an object `DateTime`. +When we want to compare more complex structures with non-constant elements, the assertions mentioned above may not be sufficient. For example, we are testing a method that creates a new user and returns its attributes as an array. We do not know the value of the password hash, but we know that it must be a hexadecimal string. And about the next element, we only know that it must be an object `DateTime`. -In these cases, we can use the `Tester\Expect` inside the `$expected` parameter of the `Assert::equal()` and `Assert::notEqual()` methods, which can be used to easily describe the structure. +In these situations, we can use `Tester\Expect` inside the `$expected` parameter of the `Assert::equal()` and `Assert::notEqual()` methods, using which the structure can be easily described. ```php use Tester\Expect; @@ -249,29 +254,29 @@ use Tester\Expect; Assert::equal([ 'id' => Expect::type('int'), # we expect an integer 'username' => 'milo', - 'password' => Expect::match('%h%'), # we expect a string matching pattern + 'password' => Expect::match('%h%'), # we expect a string matching the pattern 'created_at' => Expect::type(DateTime::class), # we expect an instance of the class ], User::create(123, 'milo', 'RandomPaSsWoRd')); ``` -With `Expect`, we can make almost the same assertions as with `Assert`. So we have methods like `Expect::same()`, `Expect::match()`, `Expect::count()`, etc. In addition, we can chain them like: +With `Expect`, we can perform almost the same assertions as with `Assert`. Thus, methods `Expect::same()`, `Expect::match()`, `Expect::count()`, etc. are available to us. In addition, we can chain them: ```php -Expect::type(MyIterator::class)->andCount(5); # we expect MyIterator and items count is 5 +Expect::type(MyIterator::class)->andCount(5); # we expect MyIterator and the number of elements is 5 ``` -Or, we can write own assertion handlers. +Alternatively, we can write our own assertion handlers. ```php Expect::that(function ($value) { - # return false if expectation fails + # return false if the expectation fails }); ``` -Failed Assertions Investigation -------------------------------- -The Tester shows where the error is when an assertion fails. When we compare complex structures, the Tester creates dumps of compared values and saves them into directory `output`. For example when imaginary test `Arrays.recursive.phpt` fails the dumps will be saved as follows: +Exploring Failed Assertions +--------------------------- +When an assertion fails, Tester prints what the error is. If we compare complex structures, Tester creates dumps of the compared values and saves them to the `output` directory. For example, if the fictional test `Arrays.recursive.phpt` fails, the dumps will be stored as follows: ``` app/ @@ -283,4 +288,4 @@ app/ └── Arrays.recursive.phpt # failing test ``` -We can change the name of the directory by `Tester\Dumper::$dumpDir`. +We can change the directory name via `Tester\Dumper::$dumpDir`. diff --git a/tester/en/guide.texy b/tester/en/guide.texy index 6795bc07b4..e5361308ac 100644 --- a/tester/en/guide.texy +++ b/tester/en/guide.texy @@ -1,17 +1,17 @@ -Getting Started with Tester -*************************** +Getting Started with Nette Tester +********************************* <div class=perex> -Even good programmers make mistakes. The difference between a good programmer and a bad one is that the good one will do it only once and next time detect it using automated tests. +Even good programmers make mistakes. The difference between a good programmer and a bad one is that the good one makes a mistake only once and the next time they detect it using automated tests. -- "One who doesn't test is doomed to repeat his or her own mistakes." (wise proverb) -- "When we get rid of one error, another one appears." (Murphy's Law) -- "Whenever you are tempted to print statement, write it as a test instead." (Martin Fowler) +- "Those who do not test are doomed to repeat their mistakes." (proverb) +- "As soon as we get rid of one error, another one appears." (Murphy's Law) +- "Whenever you are tempted to write a print statement, write it as a test instead." (Martin Fowler) </div> -Have you ever written the following code in PHP? +Have you ever written code like this in PHP? ```php $obj = new MyClass; @@ -20,40 +20,40 @@ $result = $obj->process($input); var_dump($result); ``` -So, have you ever dumped a function call result just to check by eye that it returns what it should return? You surely do it many times per day. With hand on your heart, if everything works, do you delete this code and expect that the class will not be broken in the future? Murphy's Law guarantees the opposite :-) +That is, have you printed out the result of a function call just to visually check if it returns what it should? You probably do this many times a day. Hand on heart: if everything works correctly, do you delete this code? Do you expect the class not to break in the future? Murphy's laws guarantee the opposite :-) -In fact, you wrote the test. It needs slight modification to not require our inspection, simply to be able to check itself. And if you didn't delete it we could run it any time in the future to verify that everything still works as it should. You may create a large amount of these tests over time, so it would be nice if we were able to run them automatically. +Basically, you wrote a test. It just needs a slight modification so that it does not require visual inspection but checks itself. And if you don't delete the test, you can run it at any time in the future to verify that everything still works as it should. Over time, you'll create a large number of such tests, so it would be useful to run them automatically. -And Nette Tester helps exactly with that. +And Nette Tester will help you with all this. What Makes Tester Unique? ========================= -Writing tests for Nette Tester is unique in that **each test is a standard PHP script that can be run standalone.** +Writing tests for Nette Tester is unique in that **each test is a standard PHP script** that can be run standalone. -So when you write a test, you can simply run it to see if there is a programming error. If it works properly. If not, you can easily step through program in your IDE and look for a bug. You can even open it in a browser. +So, when you write a test, you can simply run it and find out if there is, for example, a programming error in it. If it works correctly. If not, you can easily step through it in your IDE and look for the bug. You can even open it in a browser. -And most importantly - by running it, you will perform the test. You will immediately find out if it passed or failed. How? Let's show up here. Let's write a trivial test for using PHP array and save it to the file `ArrayTest.php`: +And most importantly - by running it, you perform the test. You immediately find out whether it passed or failed. How? Let's show it. We'll write a trivial test of working with a PHP array and save it to the file `ArrayTest.php`: ```php .{file:ArrayTest.php} <?php use Tester\Assert; require __DIR__ . '/vendor/autoload.php'; # load Composer autoloader -Tester\Environment::setup(); # initialization of Nette Tester +Tester\Environment::setup(); # initialize Nette Tester $stack = []; Assert::same(0, count($stack)); # we expect count() to return zero $stack[] = 'foo'; Assert::same(1, count($stack)); # we expect count() to return one -Assert::contains('foo', $stack); # verify that the $stack contains the item 'foo' +Assert::contains('foo', $stack); # verify that $stack contains the item 'foo' ``` -As you can see, [assertion methods|Assertions] such as `Assert::same()` are used to assert that an actual value matches an expected value. +As you can see, so-called [assertion methods|assertions] like `Assert::same()` are used to confirm that the actual value matches the expected value. -The test is written, we can run it from command-line. The first run will reveal any syntax errors, and if you didn't make a typo, you will see: +We have the test written, and we can run it from the command line. The first run will reveal possible syntax errors, and if you did not make a typo anywhere, it will print: /--pre .[terminal] $ php ArrayTest.php @@ -61,7 +61,7 @@ $ php ArrayTest.php <span style="color:#FFF; background-color:#090">OK</span> \-- -Try changing the statement to `Assert::contains('XXX', $stack);` in the test and watch what happens when run: +Try changing the assertion in the test to a false one, like `Assert::contains('XXX', $stack);`, and watch what happens when running: /--pre .[terminal] $ php ArrayTest.php @@ -73,19 +73,19 @@ $ php ArrayTest.php <span style="color: #FFF; background-color: #900">FAILURE</span> \-- -We continue about writing in the [Writing Tests] chapter. +We continue about writing tests in the [Writing Tests |writing-tests] chapter. Installation and Requirements ============================= -Minimal required PHP version by Tester is 7.1 (for more details, see the [#supported PHP versions] table). The preferred way of installation is by [Composer |best-practices:composer]: +The minimum PHP version required by Tester is 7.1 (more details in the table [#Supported PHP versions]). The preferred way of installation is using [Composer |best-practices:composer]: /--pre .[terminal] composer require --dev nette/tester \-- -Try to run the Nette Tester from the command line (without any arguments it will only show a help summary): +Try running Nette Tester from the command line (without arguments it will only print help): /--pre .[terminal] vendor/bin/tester @@ -95,31 +95,30 @@ vendor/bin/tester Running Tests ============= -As our application grows, a number of tests grows with it. It would not be practical to run tests one by one. Therefore, the Tester has a bulk test runner, which we invoke from the command line. The parameter is the directory in which the tests are located. The dot indicates the current directory. +As the application grows, the number of tests grows with it. It would not be practical to run tests one by one. Therefore, Tester has a bulk test runner, which we call from the command line. As a parameter, we specify the directory in which the tests are located. A dot means the current directory. /--pre .[terminal] vendor/bin/tester . \-- -The Nette Tester runner searches the specified directory and all subdirectories and searches for tests, which are files `*.phpt` and `*Test.php`. It will also find our test `ArrayTest.php`, as it matches the mask. +The test runner searches the specified directory and all subdirectories and looks for tests, which are files `*.phpt` and `*Test.php`. It will also find our test `ArrayTest.php`, since it matches the mask. -Then it starts testing. It runs each test as a new PHP process, so it runs completely isolated from the others. It runs in parallel in multiple threads, making it extremely fast. And first runs tests that failed during the previous run, so you'll know right away if you fixed the error. +Then it starts testing. Each test is run as a new PHP process, so it runs completely isolated from the others. It runs them in parallel in multiple threads, making it extremely fast. And it first runs the tests that failed during the previous run, so you immediately find out if you managed to fix the bug. -For each done test, the runner prints one character to indicate progress: +During test execution, Tester continuously prints the results to the terminal as characters: - <code style="color: #CCC; background-color: #000">.</code> – test passed - <code style="color: #CCC; background-color: #000">s</code> – test has been skipped - <code style="color: #FFF; background-color: #900">F</code> – test failed -The output can look like this: +The output may look like this: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.6.0 -Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +PHP 8.5.2 (cli) | php | 8 threads ........s................<span style="color: #FFF; background-color: #900">F</span>......... @@ -134,33 +133,34 @@ PHP 7.4.8 (cli) | php -n | 8 threads 35 tests were run, one failed, one was skipped. -We continue in the [Running tests] chapter. +We continue in the [Running Tests |running-tests] chapter. Watch Mode ========== -Are you refactoring the code? Or do you even develop according to the TDD (Test Driven Development) methodology? Then you will like the watch mode. The Tester monitors the source codes and runs itself when changed. +Are you refactoring code? Or perhaps even developing according to the TDD (Test Driven Development) methodology? Then you'll like the watch mode. In this mode, Tester monitors the source codes and automatically runs itself when changed. -During development, you have a terminal in the corner of the monitor, where the green status bar lights up on you, and when it suddenly turns red, you know that you just did do something unwanted. It's actually a great game where you program and try to stick to the color. +During development, you have a terminal in the corner of your monitor where a green status bar shines at you, and when it suddenly changes to red, you know that you have just done something not quite right. It's actually a great game where you program and try to keep the color. -Watch mode is started using the parameter [--watch|running-tests#w-watch-path]. +Watch mode is started with the parameter [--watch |running-tests#-w --watch path]. -CodeCoverage Reports -==================== +Code Coverage Reports +===================== -The Tester can generate reports with an overview of how much source code the tests cover. The report can be either in human readable HTML format or Clover XML for further machine processing. +Tester can generate reports with an overview of how much source code the tests cover. The report can be either in human-readable HTML format or Clover XML for further machine processing. -See the "sample HTML report":https://files.nette.org/tester/coverage.html with code coverage. +See a "sample HTML report":attachment:coverage.html with code coverage. Supported PHP versions ====================== -| version | compatible with PHP +| Version | Compatible with PHP |------------------|------------------- -| Tester 2.5 | PHP 8.0 – 8.2 +| Tester 2.6 | PHP 8.0 – 8.5 +| Tester 2.5 | PHP 8.0 – 8.5 | Tester 2.4 | PHP 7.2 – 8.2 | Tester 2.3 | PHP 7.1 – 8.0 | Tester 2.1 – 2.2 | PHP 7.1 – 7.3 @@ -170,6 +170,6 @@ Supported PHP versions | Tester 1.3 – 1.5 | PHP 5.3 – 5.6 + HHVM | Tester 0.9 – 1.2 | PHP 5.3 – 5.6 -Applies to the latest patch versions. +Applies to the latest patch version. -Till version 1.7 Tester supported [HHVM |https://hhvm.com] 3.3.0 or newer (using `tester -p hhvm`). Support has been dropped since Tester 2.0. Usage was simple: +Tester up to version 1.7 also supported [HHVM |https://hhvm.com] 3.3.0 or higher (via `tester -p hhvm`). Support was discontinued from Tester version 2.0. diff --git a/tester/en/helpers.texy b/tester/en/helpers.texy index d6107abf65..479c08b1da 100644 --- a/tester/en/helpers.texy +++ b/tester/en/helpers.texy @@ -2,27 +2,122 @@ Helpers ******* -DomQuery --------- -`Tester\DomQuery` is a class that extends `SimpleXMLElement` with methods that make it easier to test HTML or XML content. +HttpAssert .{data-version:2.5.6} +-------------------------------- +The `Tester\HttpAssert` class provides tools for testing HTTP servers. It allows you to easily perform HTTP requests and verify status codes, headers, and response body content using a fluent interface. + +```php +# Basic HTTP request and response verification +$response = Tester\HttpAssert::fetch('https://example.com/api/users'); +$response + ->expectCode(200) + ->expectHeader('Content-Type', contains: 'json') + ->expectBody(contains: 'users'); +``` + +The `fetch()` method creates a GET request by default, but all parameters can be customized: + +```php +HttpAssert::fetch( + 'https://api.example.com/users', + method: 'POST', + headers: [ + 'Authorization' => 'Bearer token123', # associative array + 'Accept: application/json', # or string format + ], + cookies: ['session' => 'abc123'], + follow: false, # do not follow redirects + body: '{"name": "John"}' +) + ->expectCode(201); +``` + +Status codes can be verified using `expectCode()` and `denyCode()` methods. You can pass either a specific number or a validation function: ```php -# let's have an HTML document in $html that we load -$dom = Tester\DomQuery::fromHtml($html); +$response + ->expectCode(200) # exact code + ->expectCode(fn($code) => $code < 400) # custom validation + ->denyCode(404) # must not be 404 + ->denyCode(fn($code) => $code >= 500); # must not be server error +``` -# we can test the presence of elements using CSS selectors -Assert::true($dom->has('form#registration')); -Assert::true($dom->has('input[name="username"]')); -Assert::true($dom->has('input[type="submit"]')); +For header verification, use `expectHeader()` and `denyHeader()` methods. You can check whether a header exists, verify its exact value, or match part of its content: + +```php +$response + ->expectHeader('Content-Type') # header must exist + ->expectHeader('Content-Type', 'application/json') # exact value + ->expectHeader('Content-Type', contains: 'json') # contains text + ->expectHeader('Server', matches: 'nginx %a%') # matches pattern + ->denyHeader('X-Powered-By') # header must not exist + ->denyHeader('X-Debug', contains: 'sensitive') # must not contain text + ->denyHeader('X-Debug', matches: '~debug~i'); # must not match pattern +``` -# or select elements as array of DomQuery -$elems = $dom->find('input[data-autocomplete]'); +Response body verification works similarly with `expectBody()` and `denyBody()` methods: + +```php +$response + ->expectBody('OK') # exact value + ->expectBody(contains: '"status": "success"') # contains JSON fragment + ->expectBody(matches: '%A% hello %A%') # matches pattern + ->expectBody(fn($body) => json_decode($body)) # custom validation + ->denyBody('Error occurred') # must not have exact value + ->denyBody(contains: 'error') # must not contain text + ->denyBody(matches: '~exception|fatal~i'); # must not match pattern +``` + +The `follow` parameter controls how HttpAssert handles HTTP redirects: + +```php +# Testing redirect without following (default) +HttpAssert::fetch('https://example.com/redirect', follow: false) + ->expectCode(301) + ->expectHeader('Location', 'https://example.com/new-url'); + +# Following all redirects to the final response +HttpAssert::fetch('https://example.com/redirect', follow: true) + ->expectCode(200) + ->expectBody(contains: 'final content'); +``` + + +DomQuery +-------- +`Tester\DomQuery` is a class extending `SimpleXMLElement` with easy querying in HTML or XML using CSS selectors. + +```php +# create DomQuery from HTML string +$dom = Tester\DomQuery::fromHtml(' + <article class="post"> + <h1>Title</h1> + <div class="content">Text</div> + </article> +'); + +# test element existence using CSS selectors +Assert::true($dom->has('article.post')); +Assert::true($dom->has('h1')); + +# find elements as an array of DomQuery objects +$headings = $dom->find('h1'); +Assert::same('Title', (string) $headings[0]); + +# test if element matches selector (since version 2.5.3) +$content = $dom->find('.content')[0]; +Assert::true($content->matches('div')); +Assert::false($content->matches('p')); + +# find the closest ancestor matching the selector (since 2.5.5) +$article = $content->closest('.post'); +Assert::true($article->matches('article')); ``` FileMock -------- -`Tester\FileMock` emulates files in memory to help you to test a code which uses functions like `fopen()`, `file_get_contents()` or `parse_ini_file()`. For example: +`Tester\FileMock` emulates files in memory and facilitates testing code that uses functions like `fopen()`, `file_get_contents()`, `parse_ini_file()` and similar. Example usage: ```php # Tested class @@ -46,14 +141,14 @@ $logger = new Logger($file); $logger->log('Login'); $logger->log('Logout'); -# Created content testing +# Test the created content Assert::same("Login\nLogout\n", file_get_contents($file)); ``` Assert::with() .[filter] ------------------------ -This is not an assertion, but a helper for testing private methods and property objects. +This is not an assertion, but a helper for testing private methods and properties of objects. ```php class Entity @@ -72,7 +167,7 @@ Assert::with($ent, function () { Helpers::purge() .[filter] -------------------------- -The `purge()` method creates the specified directory and, if it already exists, deletes its entire contents. It is handy for temporary directory creation. For example in `tests/bootstrap.php`: +The `purge()` method creates the specified directory, and if it already exists, it deletes its entire content. It is useful for creating a temporary directory. For example, in `tests/bootstrap.php`: ```php @mkdir(__DIR__ . '/tmp'); # @ - directory may already exist @@ -84,25 +179,25 @@ Tester\Helpers::purge(TempDir); Environment::lock() .[filter] ----------------------------- -Tests run in parallel. Sometimes we need not to overlap the test running. Typically database tests need to prepare database content and they need nothing disturbs them during running time of the test. In these cases we use `Tester\Environment::lock($name, $dir)`: +Tests run in parallel. Sometimes, however, we need for the tests' execution not to overlap. Typically, database tests require preparing the database content and ensuring no other test interferes with the database during its execution. In these tests, we use `Tester\Environment::lock($name, $dir)`: ```php Tester\Environment::lock('database', __DIR__ . '/tmp'); ``` -The first argument is a lock name. The second one is a path to directory for saving the lock. The test which acquires the lock first runs. Other tests must wait till it is completed. +The first parameter is the name of the lock, the second is the path to the directory for storing the lock. The test that acquires the lock first proceeds, other tests must wait for it to complete. Environment::bypassFinals() .[filter] ------------------------------------- -Classes or methods marked as `final` are hard to test. Calling the `Tester\Environment::bypassFinals()` in a test beginning causes that keywords `final` are removed during the code loading. +Classes or methods marked as `final` are difficult to test. Calling `Tester\Environment::bypassFinals()` at the beginning of a test causes the `final` keywords to be omitted during code loading. ```php require __DIR__ . '/bootstrap.php'; Tester\Environment::bypassFinals(); -class MyClass extends NormallyFinalClass # <-- NormallyFinalClass is not final anymore +class MyClass extends NormallyFinalClass # <-- NormallyFinalClass is no longer final { // ... } @@ -111,15 +206,15 @@ class MyClass extends NormallyFinalClass # <-- NormallyFinalClass is not final Environment::setup() .[filter] ------------------------------ -- improves error dump readability (coloring included), otherwise, default PHP stack trace is printed -- enables check that assertions have been called in test, otherwise, tests without (e.g. forgotten) assertions pass too -- automatically starts code coverage collector when `--coverage` is used (described later) +- improves the readability of error dumps (including coloring); otherwise, the default PHP stack trace is printed +- enables checking that assertions were called in the test; otherwise, tests without assertions (e.g., forgotten ones) also pass +- automatically starts collecting information about the executed code (when `--coverage` is used) (described further) - prints the status OK or FAILURE at the end of the script Environment::setupFunctions() .[filter]{data-version:2.5} --------------------------------------------------------- -Creates the global functions `test()`, `setUp()` and `tearDown()` into which you can split tests. +Creates the global functions `test()`, `testException()`, `setUp()`, and `tearDown()`, into which you can structure your tests. ```php test('test description', function () { @@ -132,21 +227,21 @@ test('test description', function () { Environment::VariableRunner .[filter] ------------------------------------- -Lets you find out if the test was run directly or via the Tester. +Allows you to determine whether the test was run directly or via the Tester. ```php if (getenv(Tester\Environment::VariableRunner)) { # run by Tester } else { - # another way + # run some other way } ``` Environment::VariableThread .[filter] ------------------------------------- -Tester runs tests in parallel in a given number of threads. We will find a thread number in an environmental variable when we are interested: +Tester runs tests in parallel in the specified number of threads. If we are interested in the thread number, we find it from the environment variable: ```php -echo "I'm running in a thread number " . getenv(Tester\Environment::VariableThread); +echo "Running in thread number " . getenv(Tester\Environment::VariableThread); ``` diff --git a/tester/en/running-tests.texy b/tester/en/running-tests.texy index 201f33718f..0f89235870 100644 --- a/tester/en/running-tests.texy +++ b/tester/en/running-tests.texy @@ -2,72 +2,68 @@ Running Tests ************* .[perex] -The most visible part of Nette Tester is the command-line test runner. It is extremely fast and robust because it starts automatically all tests as separate processes in parallel in multiple threads. It can also run itself in the so-called watch mode. +The most visible part of Nette Tester is the command-line test runner. It is extremely fast and robust because it automatically runs all tests as separate processes in parallel using multiple threads. It can also run itself in watch mode. -The Nette Tester test runner is invoked from the command-line. As a parameter, we will pass the test directory. For the current directory just enter a dot: +The test runner is invoked from the command line. Pass the directory containing the tests as a parameter. For the current directory, simply enter a dot: /--pre .[terminal] vendor/bin/tester . \-- -When invoked, the test runner will scan the specified directory and all subdirectories and looks for tests, which are the `*.phpt` and `*Test.php` files. It also reads and evaluates their [annotations|test-annotations] to know which ones and how to run them. +The test runner scans the specified directory and all its subdirectories, looking for tests, which are files ending in `*.phpt` or `*Test.php`. It also reads and evaluates their [annotations |test-annotations] to determine which tests to run and how. -It will then execute the tests. For each done test, the runner prints one character to indicate progress: +It then executes the tests. During execution, it prints characters to the terminal to indicate the progress: - <code style="color: #CCC; background-color: #000">.</code> – test passed - <code style="color: #CCC; background-color: #000">s</code> – test has been skipped - <code style="color: #FFF; background-color: #900">F</code> – test failed -The output may look like: +The output may look like this: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.6.0 -Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +PHP 8.5.2 (cli) | php | 8 threads ........s.......................... <span style="color: #FFF; background-color: #090">OK (35 tests, 1 skipped, 1.7 seconds)</span> \-- -When you run it again, it first runs tests that failed during the previous run, so you'll know right away if you fixed the error. +When run again, it first executes the tests that failed in the previous run, so you immediately know if you have fixed the error. -Tester's exit code is zero if no one fails. Non-zero otherwise. - -.[warning] -The Tester runs PHP processes without `php.ini`. More details in the [#Own php.ini] section. +The Tester's exit code is zero if no test fails. Otherwise, it is non-zero. Command-Line Options ==================== -We obtain command-line options overview by running the Tester without parameters or with option `-h`: +You can get an overview of command-line options by running Tester without parameters or with the `-h` option: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Usage: tester [options] [<test file> | <directory>]... Options: -p <path> Specify PHP interpreter to run (default: php). - -c <path> Look for php.ini file (or look in directory) <path>. - -C Use system-wide php.ini. - -l | --log <path> Write log to file <path>. + -c <path> Use custom php.ini, ignore system configuration. + -C With -c, include system configuration as well. -d <key=value>... Define INI entry 'key' with value 'value'. -s Show information about skipped tests. --stop-on-fail Stop execution upon the first failure. -j <num> Run <num> jobs in parallel (default: 8). - -o <console|tap|junit|none> Specify output format. + -o <console|console-lines|tap|junit|log|none> (e.g. -o junit:output.xml) + Specify one or more output formats with optional file name. -w | --watch <path> Watch directory. -i | --info Show tests environment info and exit. --setup <path> Script for runner setup. - --temp <path> Path to temporary directory. Default: sys_get_temp_dir(). + --temp <path> Path to temporary directory. Default by sys_get_temp_dir(). --colors [1|0] Enable or disable colors. --coverage <path> Generate code coverage report to file. --coverage-src <path> Path to source code. @@ -77,7 +73,7 @@ Options: -p <path> .[filter] ------------------- -Specifies the PHP binary that will be used to run tests. By default it is `php`. +Specifies the PHP binary that will be used to run tests. By default, it is `php`. /--pre .[terminal] tester -p /home/user/php-7.2.0-beta/php-cgi tests @@ -86,26 +82,17 @@ tester -p /home/user/php-7.2.0-beta/php-cgi tests -c <path> .[filter] ------------------- -Specifies which `php.ini` will be used when running tests. By default, no php.ini is used. See [#Own php.ini] for more information. +Uses a custom `php.ini` file and ignores the system configuration. This is useful for running tests with specific settings. See [#Own php.ini] for more information. -C .[filter] ------------ -A system-wide `php.ini` is used. So on UNIX platform, all the `/etc/php/{sapi}/conf.d/*.ini` files too. See [#Own php.ini] section. - - -''-l | --log <path>'' .[filter] -------------------------------- -Testing progress is written into file. All failed, skipped and also successful tests: - -/--pre .[terminal] -tester --log /var/log/tests.log tests -\-- +When used together with `-c`, includes the system configuration as well (does not ignore it). See [#Own php.ini] section. -d <key=value> .[filter] ------------------------ -Sets the value of the PHP configuration directive for tests. The parameter can be used multiple times. +Sets the value of a PHP configuration directive for the tests. This parameter can be used multiple times. /--pre .[terminal] tester -d max_execution_time=20 @@ -114,7 +101,7 @@ tester -d max_execution_time=20 -s --- -Information about skipped tests will be shown. +Displays information about skipped tests. --stop-on-fail .[filter] @@ -124,24 +111,26 @@ Tester stops testing upon the first failing test. -j <num> .[filter] ------------------ -Tests run in a `<num>` parallel precesses. Default value is 8. If we wish to run tests in series, we use value 1. +Specifies the number of parallel processes to run tests in. The default value is 8. To run tests sequentially, use the value 1. --o <console|tap|junit|none> .[filter] -------------------------------------- -Output format. Default is the console format. +-o <console|console-lines|tap|junit|log|none> .[filter] +------------------------------------------------------- +Sets the output format. The default is the console format. You can specify the name of the file into which the output will be written (e.g., `-o junit:output.xml`). The `-o` option can be repeated multiple times to generate multiple formats at once. -- `console`: the same as default, but the ASCII logo is not printed in this case -- `tap`: [TAP format |https://en.wikipedia.org/wiki/Test_Anything_Protocol] appropriate for machine processing -- `junit`: JUnit XML format, appropriate for machine processing too +- `console`: same as the default format, but the ASCII logo is not printed in this case +- `console-lines`: similar to console, but the result of each test is listed on a separate line with additional information +- `tap`: [TAP format |https://en.wikipedia.org/wiki/Test_Anything_Protocol] suitable for machine processing +- `junit`: JUnit XML format, also suitable for machine processing +- `log`: Outputs the testing progress. Includes all failed, skipped, and also successful tests - `none`: nothing is printed ''-w | --watch <path>'' .[filter] --------------------------------- -The Tester doesn't end after tests are completed but it keeps running and watching PHP files in given directory. When changed, it runs the tests again. Parameter can be used multiple times if we want to monitor multiple directories. +After completing the tests, Tester does not exit but continues to run and watch PHP files in the specified directory. When a file changes, it runs the tests again. This parameter can be used multiple times if you want to watch multiple directories. -It is handy during refactoring a library or tests debugging. +Useful when refactoring a library or debugging tests. /--pre .[terminal] tester --watch src tests @@ -150,7 +139,7 @@ tester --watch src tests ''-i | --info'' .[filter] ------------------------- -It shows information about a test running environment. For example: +Shows information about the runtime environment for tests. For example: /--pre .[terminal] tester -p /usr/bin/php7.1 -c tests/php.ini --info @@ -177,13 +166,13 @@ Core, ctype, date, dom, ereg, fileinfo, filter, hash, ... --setup <path> .[filter] ------------------------ -The Tester loads given PHP script on start. Variable `Tester\Runner\Runner $runner` is available in it. Let's assume file `tests/runner-setup.php`: +Tester loads the specified PHP script at startup. The variable `Tester\Runner\Runner $runner` is available within this script. Assume a file `tests/runner-setup.php` with the following content: ```php $runner->outputHandlers[] = new MyOutputHandler; ``` -and we run the Tester: +We run Tester with: /--pre .[terminal] tester --setup tests/runner-setup.php tests @@ -192,47 +181,43 @@ tester --setup tests/runner-setup.php tests --temp <path> .[filter] ----------------------- -Sets a path to directory for temporary files of Tester. Default value is returned by `sys_get_temp_dir()`. When default value is not valid, you will be noticed. +Sets the path to the directory for Tester's temporary files. The default value is returned by `sys_get_temp_dir()`. You will be notified if the default value is not valid. -If we are not sure which directory is used, we can run Tester with the `--info` parameter. +If you are unsure which directory is being used, run Tester with the `--info` parameter. --colors 1|0 .[filter] ---------------------- -The Tester detects a color-able terminal in default and colorizes its output. This option is over the autodetection. We can set coloring globally by a system environment variable `NETTE_TESTER_COLORS`. +By default, Tester detects if the terminal supports colors and colorizes its output accordingly. This option overrides the auto-detection. You can set coloring globally using the `NETTE_TESTER_COLORS` environment variable. --coverage <path> .[filter] --------------------------- -Tester will generate a report with overview how much is the source code coveraged by tests. This option requires PHP extension [Xdebug |https://xdebug.org/] or [PCOV |https://github.com/krakjoe/pcov] enabled, or PHP 7 with the PHPDBG SAPI, which is faster. The destination file extension determines the format of the content. HTML or Clover XML. +Tester generates a report showing how much of the source code is covered by tests. This option requires the [Xdebug |https://xdebug.org/] or [PCOV |https://github.com/krakjoe/pcov] PHP extension to be installed, or PHP 7+ with the PHPDBG SAPI, which is faster. The extension of the target file determines its format: HTML or Clover XML. /--pre .[terminal] tester tests --coverage coverage.html # HTML report tester tests --coverage coverage.xml # Clover XML report \-- -Priority to choose collecting mechanism is following: +The priority for selecting the coverage engine is as follows: 1) PCOV 2) PHPDBG 3) Xdebug -Extensive tests may fail during run by PHPDBG due to memory exhaustion. Coverage data collecting is memory consuming operation. In that case, calling the `Tester\CodeCoverage\Collector::flush()` inside a test may help. It will flush collected data into file and frees memory. When data collection is not in progress, or Xdebug is used, calling has no effect. +When using PHPDBG, extensive tests might fail due to memory exhaustion. Collecting code coverage information is memory-intensive. In this case, calling `Tester\CodeCoverage\Collector::flush()` within your test can help. It writes the collected data to disk and frees up memory. If data collection is not running or if Xdebug is used, the call has no effect. -"An example of HTML report":https://files.nette.org/tester/coverage.html with code coverage. +See an `"example HTML report":attachment:coverage.html with code coverage. --coverage-src <path> .[filter] ------------------------------- -We use it with the option `--coverage` simultaneously. The `<path>` is a path to source code for which we generate the report. It can be used repeatedly. +Used in conjunction with the `--coverage` option. `<path>` is the path to the source code for which the report is generated. Can be used multiple times. Own php.ini =========== -The Tester runs PHP processes with `-n` option, which means no `php.ini` is loaded (not even that from `/etc/php/conf.d/*.ini` in UNIX). It ensures the same environment for the tests run, but it also deactivates all the external PHP extensions commonly loaded by system PHP. - -If you want to keep system configuration, use the `-C` parameter. - -If you need some extensions or some special INI settings, we recommend to create own `php.ini` file and distribute it among the tests. Then we run Tester with `-c` option, e.g. `tester -c tests/php.ini`. The INI file may look like: +You can use a custom `php.ini` file for your tests. If you need specific extensions or special INI settings, we recommend creating your own `php.ini` file and distributing it with your tests. Then, run Tester with the `-c` option, for example, `tester -c tests/php.ini tests`. The INI file might look like this: ```ini [PHP] @@ -243,4 +228,7 @@ extension=php_pdo_pgsql.dll memory_limit=512M ``` -Running the Tester with a system `php.ini` in UNIX, e.g. `tester -c /etc/php/cgi/php.ini`, does not load other INI from `/etc/php/conf.d/*.ini`. That's the PHP behavior, not the Tester's. +When using `-c`, Tester **ignores the system configuration** (runs PHP with `-n` flag). If you want to include system configuration as well, add the `-C` option: `tester -c tests/php.ini -C tests`. Even when combining `-c` and `-C`, other INI files from `/etc/php/conf.d/*.ini` are not loaded on UNIX. This is a PHP behavior, not specific to Tester. + +.[note] +Prior to version 2.6, without `-c`, Tester ran PHP with `-n` flag, i.e., without php.ini; the `-C` option suppressed this. From version 2.6, system php.ini is loaded by default. The behavior when using `-c` remains unchanged. diff --git a/tester/en/test-annotations.texy b/tester/en/test-annotations.texy index a5ec64ac75..1cc85632bc 100644 --- a/tester/en/test-annotations.texy +++ b/tester/en/test-annotations.texy @@ -2,9 +2,9 @@ Test Annotations **************** .[perex] -Annotations determine how tests will be handled by [command-line test runner|running-tests]. They are written at the beginning of the test file. +Annotations determine how tests will be handled by the [command-line test runner |running-tests]. They are written at the beginning of the test file. -Annotations are case insensitive. They also have no effect if the test is run manually as a regular PHP script. +Annotations are case-insensitive. They also have no effect if the test is run manually as a regular PHP script. Example: @@ -23,17 +23,17 @@ require __DIR__ . '/../bootstrap.php'; TEST .[filter] -------------- -It is not an annotation actually. It only sets the test title which is printed on fail or into logs. +This isn't actually an annotation. It simply specifies the test title, which is displayed in case of failure or in logs. @skip .[filter] --------------- -Test is skipped. It is handy for temporary test deactivation. +The test is skipped. Useful for temporarily disabling tests. @phpVersion .[filter] --------------------- -Test is skipped if is not run by the corresponding PHP version. We write annotation as `@phpVersion [operator] version`. We can leave out the operator, default is `>=`. Examples: +The test is skipped if it is not run with the corresponding PHP version. Write the annotation as `@phpVersion [operator] version`. The operator can be omitted; the default is `>=`. Examples: ```php /** @@ -46,7 +46,7 @@ Test is skipped if is not run by the corresponding PHP version. We write annotat @phpExtension .[filter] ----------------------- -Test is skipped if all mentioned PHP extension are not loaded. Multiple extensions can be written in a single annotation, or we can use the annotation multiple times. +The test is skipped if not all the specified PHP extensions are loaded. Multiple extensions can be listed in a single annotation, or the annotation can be used multiple times. ```php /** @@ -58,9 +58,9 @@ Test is skipped if all mentioned PHP extension are not loaded. Multiple extensio @dataProvider .[filter] ----------------------- -This annotation suits when we want to run the test multiple times but with different data. (Not to be confused with the annotation of the same name for [TestCase|TestCase#dataProvider].) +This annotation is useful when you want to run the test file multiple times with different input data. (Do not confuse it with the annotation of the same name for [TestCase |TestCase#dataProvider].) -We write annotation as `@dataProvider file.ini`. INI file path is relative to the test file. Test runs as many times as number of sections contained in the INI file. Let's assume the INI file `databases.ini`: +Write it as `@dataProvider file.ini`. The file path is relative to the test file. The test will be run as many times as there are sections in the INI file. Assume the INI file `databases.ini`: ```ini [mysql] @@ -87,11 +87,11 @@ and the file `database.phpt` in the same directory: $args = Tester\Environment::loadData(); ``` -The test runs three times and `$args` will contain values from sections `mysql`, `postgresql` or `sqlite`. +The test will run three times, and `$args` will contain the values from the `mysql`, `postgresql`, or `sqlite` section, respectively. -There is one more variation when we write annotations with a question mark as `@dataProvider? file.ini`. In this case, test is skipped if the INI file doesn't exist. +There is another variation where you write the annotation with a question mark: `@dataProvider? file.ini`. In this case, the test is skipped if the INI file does not exist. -Annotation possibilities have not been mentioned all yet. We can write conditions after the INI file. Test runs for the given section only if all conditions match. Let's extend the INI file: +The possibilities of this annotation don't end here. You can specify conditions after the INI file name that determine whether the test runs for a specific section. Let's extend the INI file: ```ini [mysql] @@ -113,7 +113,7 @@ password = ****** dsn = "sqlite::memory:" ``` -and we will use annotation with condition: +and use the annotation with a condition: ```php /** @@ -121,9 +121,9 @@ and we will use annotation with condition: */ ``` -The test runs only once for section `postgresql 9.1`. Other sections don’t match the conditions. +The test will run only once, for the `postgresql 9.1` section. Other sections do not meet the condition filter. -Similarly, we can pass path to a PHP script instead of INI. It must return array or Traversable. File `databases.php`: +Similarly, instead of an INI file, you can reference a PHP script. It must return an array or a Traversable object. File `databases.php`: ```php return [ @@ -142,29 +142,29 @@ return [ @multiple .[filter] ------------------- -We write it as `@multiple N` where `N` is an integer. Test runs exactly N-times. +Write it as `@multiple N`, where `N` is an integer. The test will run exactly N times. @testCase .[filter] ------------------- -Annotation has no parameters. We use it when we write a test as [TestCase] classes. In this case, the command-line test runner will run the individual methods in separate processes and in parallel in multiple threads. This can significantly speed up the entire testing process. +This annotation has no parameters. Use it when writing tests as [TestCase |TestCase] classes. In this case, the command-line test runner will execute individual methods in separate processes and in parallel using multiple threads. This can significantly speed up the entire testing process. @exitCode .[filter] ------------------- -We write it as `@exitCode N` where `N` is the exit code of the test. For example if `exit(10)` is called in the test, we write annotation as `@exitCode 10`. It is considered to fail if the test ends with a different code. Exit code 0 (zero) is verified if we leave out the annotation +Write it as `@exitCode N`, where `N` is the expected exit code of the test. For example, if `exit(10)` is called in the test, write the annotation as `@exitCode 10`. If the test ends with a different code, it is considered a failure. If the annotation is omitted, an exit code of 0 (zero) is verified. @httpCode .[filter] ------------------- -Annotation is evaluated only if PHP binary is CGI. It is ignored otherwise. We write it as `@httpCode NNN` where `NNN` is expected HTTP code. HTTP code 200 is verified if we leave out the annotation. If we write `NNN` as a string evaluated as zero, for example `any`, HTTP code is not checked at all. +This annotation applies only if the PHP binary is CGI; otherwise, it is ignored. Write it as `@httpCode NNN`, where `NNN` is the expected HTTP code. If the annotation is omitted, an HTTP code of 200 is verified. If `NNN` is written as a string that evaluates to zero (e.g., `any`), the HTTP code is not checked. -@outputMatch a @outputMatchFile .[filter] ------------------------------------------ -The behavior of annotations is consistent with `Assert::match()` and `Assert::matchFile()` assertions. But pattern is found in test’s standard output. A suitable use case is when we assume the test to end by fatal error and we need to verify its output. +@outputMatch and @outputMatchFile .[filter] +------------------------------------------- +The function of these annotations is identical to the `Assert::match()` and `Assert::matchFile()` assertions. However, the pattern is searched for in the text that the test sent to its standard output. This is useful when you expect a test to end with a fatal error and need to verify its output. @phpIni .[filter] ----------------- -It sets INI configuration values for test. For example we write it as `@phpIni precision=20` and it works in the same way as if we passed value from the command line by parameter `-d precision=20`. +Sets INI configuration values for the test. For example, write it as `@phpIni precision=20`. It works the same way as if you specified the value from the command line using the `-d precision=20` parameter. diff --git a/tester/en/testcase.texy b/tester/en/testcase.texy index d3a83cf71b..eea342fa03 100644 --- a/tester/en/testcase.texy +++ b/tester/en/testcase.texy @@ -2,9 +2,9 @@ TestCase ******** .[perex] -Assertions may follow one by one in simple tests. But sometimes it is useful to enclose the assertions to test class and structure them in this way. +In simple tests, assertions can follow one after another. However, sometimes it's advantageous to wrap assertions in a test class to structure them. -The class must be descendant of `Tester\TestCase` and we talk about it simply as about **testcase**. +The class must extend `Tester\TestCase`, and we refer to it simply as a **TestCase**. The class must contain test methods starting with `test`. These methods will be executed as tests: ```php use Tester\Assert; @@ -22,11 +22,11 @@ class RectangleTest extends Tester\TestCase } } -# Run testing methods +# Run test methods (new RectangleTest)->run(); ``` -We can enrich a testcase by `setUp()` and `tearDown()` methods. They are called before/after every testing method: +A TestCase written this way can be further enhanced with `setUp()` and `tearDown()` methods. They are called before and after each test method, respectively: ```php use Tester\Assert; @@ -54,14 +54,14 @@ class NextTest extends Tester\TestCase } } -# Run testing methods +# Run test methods (new NextTest)->run(); /* -Method Calls Order ------------------- +Method Call Order +----------------- setUp() testOne() tearDown() @@ -72,9 +72,9 @@ tearDown() */ ``` -If error occurs in a `setUp()` or `tearDown()` phase, test will fail. If error occurs in the testing method, the `tearDown()` method is called anyway, but with suppressed errors in it. +If an error occurs during the `setUp()` or `tearDown()` phase, the test will fail overall. If an error occurs in the test method itself, the `tearDown()` method is still executed, but any errors within it are suppressed. -We recommend that you write the annotation [@testCase|test-annotations#@testCase] at the beginning of the test, then the command-line test runner will run the individual testcase methods in separate processes and in parallel in multiple threads. This can significantly speed up the entire testing process. +We recommend writing the [@testCase |test-annotations#testCase] annotation at the beginning of the test file. The command-line test runner will then execute the individual TestCase methods in separate processes and in parallel using multiple threads. This can significantly speed up the entire testing process. /--php <?php @@ -82,15 +82,15 @@ We recommend that you write the annotation [@testCase|test-annotations#@testCase \-- -Annotation of Methods -===================== +Method Annotations +================== -There are a few annotations available to help us with testing methods. We write them toward the testing method. +Several annotations are available for test methods to facilitate testing. Write them above the test method. @throws .[filter] ----------------- -It is equal usage of `Assert::exception()` inside a testing method. But notation is more readable: +It is equivalent to using `Assert::exception()` inside the test method, but the notation is clearer: ```php /** @@ -114,9 +114,9 @@ public function testTwo() @dataProvider .[filter] ----------------------- -This annotation suits when we want to run the testing method multiple times but with different arguments. (Not to be confused with the annotation of the same name for [files |test-annotations#dataProvider].) +This annotation is useful when you want to run the test method multiple times with different parameters. (Do not confuse it with the annotation of the same name for [test files |test-annotations#dataProvider].) -As an argument we write method name which returns parameters for the testing method. The method must return an array or Traversable. Simple example: +After it, specify the name of a method that returns the arguments for the test method. This method must return an array or a Traversable object. A simple example: ```php public function getLoopArgs() @@ -138,7 +138,7 @@ public function testLoop($a, $b, $c) } ``` -The other annotation **@dataProvider** variation accepts a path to INI file (relatively to test file) as an argument. The method is called so many times as the number of sections contained in INI file. File `loop-args.ini`: +The second variation of the **@dataProvider** annotation accepts a path to an INI file (relative to the test file) as a parameter. The method is called as many times as there are sections in the INI file. File `loop-args.ini`: ```ini [one] @@ -157,7 +157,7 @@ b=8 c=9 ``` -and the method which uses the INI file: +and the method that uses the INI file: ```php /** @@ -169,7 +169,7 @@ public function testLoop($a, $b, $c) } ``` -Similarly, we can pass path to a PHP script instead of INI. It must return array or Traversable. File `loop-args.php`: +Similarly, instead of an INI file, you can reference a PHP script. It must return an array or a Traversable object. File `loop-args.php`: ```php return [ diff --git a/tester/en/writing-tests.texy b/tester/en/writing-tests.texy index 1bef2c9c88..5c49570949 100644 --- a/tester/en/writing-tests.texy +++ b/tester/en/writing-tests.texy @@ -2,14 +2,13 @@ Writing Tests ************* .[perex] -Writing tests for Nette Tester is unique in that each test is a PHP script that can be run standalone.. This has great potential. -As you write the test, you can simply run it to see if it works properly. If not, you can easily step through in the IDE and look for a bug. +Writing tests for Nette Tester is unique because each test is a PHP script that can be run standalone. This holds great potential. While writing a test, you can simply run it to check if it works correctly. If it doesn't, you can easily step through it in your IDE to find the bug. -You can even open the test in a browser. But above all - by running it, you will perform the test. You will immediately find out if it passed or failed. +You can even open the test in a browser. But most importantly, by running it, you execute the test. You immediately find out whether it passed or failed. -In the introductory chapter, we [showed |guide#What Makes Tester Unique?] a really trivial test of using PHP array. Now we will create our own class, which we will test, although it will also be simple. +In the introductory chapter, we [showed |guide#What Makes Tester Unique] a very trivial test involving array manipulation. Now, we'll create our own class to test, although it will also be simple. -Let's start with a typical directory layout for a library or project. It is important to separate the tests from the rest of the code, for example due to deployment, because we do not want to upload tests to server. The structure may be as follows: +Let's start with a typical directory structure for a library or project. It's important to separate tests from the rest of the code, for example, for deployment purposes, as we don't want to upload tests to the production server. The structure might look like this: ``` ├── src/ # code that we will test @@ -23,7 +22,7 @@ Let's start with a typical directory layout for a library or project. It is impo └── composer.json ``` -And now we will create individual files. We will start with the tested class, which we will place in the file `src/Rectangle.php` +Now, let's create the individual files. We'll start with the class being tested, placing it in the file `src/Rectangle.php`: ```php .{file:src/Rectangle.php} <?php @@ -53,7 +52,7 @@ class Rectangle } ``` -And we'll create a test for it. The name of the test file should match mask `*Test.php` or `*.phpt`, we will choose the variant `RectangleTest.php`: +And we'll create a test for it. The test filename should match the pattern `*Test.php` or `*.phpt`; we'll choose the `RectangleTest.php` variant: ```php .{file:tests/RectangleTest.php} @@ -62,15 +61,15 @@ use Tester\Assert; require __DIR__ . '/bootstrap.php'; -// general oblong +// general rectangle $rect = new Rectangle(10, 20); -Assert::same(200.0, $rect->getArea()); # we will verify the expected results +Assert::same(200.0, $rect->getArea()); # verify the expected results Assert::false($rect->isSquare()); ``` -As you can see, [assertion methods|Assertions] such as `Assert::same()` are used to assert that an actual value matches an expected value. +As you can see, [assertion methods |assertions] such as `Assert::same()` are used to assert that an actual value matches an expected value. -The last step is to create file `bootstrap.php`. It contains a common code for all tests. For example classes autoloading, environment configuration, temporary directory creation, helpers and similar. Every test loads the bootstrap and pays attention to testing only. The bootstrap can look like: +The final step is the `bootstrap.php` file. It contains code common to all tests, such as class autoloading, environment configuration, temporary directory creation, helper functions, and the like. All tests load the bootstrap and then focus solely on testing. The bootstrap might look like this: ```php .{file:tests/bootstrap.php} <?php @@ -78,15 +77,15 @@ require __DIR__ . '/vendor/autoload.php'; # load Composer autoloader Tester\Environment::setup(); # initialization of Nette Tester -// and other configurations (just an example, in our case they are not needed) +// and other configurations (just an example, not needed in our case) date_default_timezone_set('Europe/Prague'); define('TmpDir', '/tmp/app-tests'); ``` .[note] -This bootstrap assumes that the Composer autoloader will be able to load the class `Rectangle.php` as well. This can be achieved, for example, by [setting the autoload section |best-practices:composer#autoloading] in `composer.json`, etc. +This bootstrap assumes that the Composer autoloader will be able to load the `Rectangle.php` class as well. This can be achieved, for example, by [setting the autoload section |best-practices:composer#Autoloading] in `composer.json`, etc. -We can now run the test from the command line like any other standalone PHP script. The first run will reveal any syntax errors, and if you didn't make a typo, you will see: +We can now run the test from the command line like any other standalone PHP script. The first run will reveal any syntax errors, and if there are no typos, it will output: /--pre .[terminal] $ php RectangleTest.php @@ -94,7 +93,7 @@ $ php RectangleTest.php <span style="color:#FFF; background-color:#090">OK</span> \-- -If we change in the test the statement to false `Assert::same(123, $rect->getArea());`, this will happen: +If we change the assertion in the test to something incorrect, like `Assert::same(123, $rect->getArea());`, this happens: /--pre .[terminal] $ php RectangleTest.php @@ -107,12 +106,12 @@ $ php RectangleTest.php \-- -When writing tests, it is good to catch all extreme situations. For example, if the input is zero, a negative number, in other cases an empty string, null, etc. In fact, it forces you to think and decide how the code should behave in such situations. The tests then fix the behavior. +When writing tests, it's good practice to cover all edge cases. For example, inputs like zero, negative numbers, or in other scenarios, empty strings, null, etc. This actually forces you to think and decide how the code should behave in such situations. Tests then solidify this behavior. -In our case, a negative value should throw an exception, which we verify with [Assert::exception()|Assertions#Assert::exception]: +In our case, a negative value should throw an exception, which we verify with [Assert::exception() |Assertions#Assert::exception]: ```php .{file:tests/RectangleTest.php} -// the width must not be negative number +// width must not be negative Assert::exception( fn() => new Rectangle(-1, 20), InvalidArgumentException::class, @@ -128,14 +127,14 @@ Well-Arranged Tests The size of the test file can increase and quickly become cluttered. Therefore, it is practical to group individual tested areas into separate functions. -First, we will show a simpler but elegant variant, using the global function `test()`. The tester doesn't create it automatically, to avoid a collision if you had a function with the same name in your code. It is only created by the `setupFunctions()` method, which you call in the `bootstrap.php` file: +First, let's look at a simpler yet elegant option using the global `test()` function. Tester doesn't create this function automatically to avoid collisions if you have a function with the same name in your code. It's created by the `setupFunctions()` method, which you should call in your `bootstrap.php` file: ```php .{file:tests/bootstrap.php} Tester\Environment::setup(); Tester\Environment::setupFunctions(); ``` -Using this function, we can nicely divide the test file into named units. When executed, labels will be displayed one after the other. +Using this function, we can nicely structure the test file into named units. When executed, the labels will be printed sequentially. ```php .{file:tests/RectangleTest.php} <?php @@ -143,7 +142,7 @@ use Tester\Assert; require __DIR__ . '/bootstrap.php'; -test('general oblong', function () { +test('general rectangle', function () { $rect = new Rectangle(10, 20); Assert::same(200.0, $rect->getArea()); Assert::false($rect->isSquare()); @@ -168,15 +167,15 @@ test('dimensions must not be negative', function () { }); ``` -If you need to run the code before or after each test, pass it to `setUp()` or `tearDown()`: +If you need to run code before or after each `test()`, pass it to the `setUp()` or `tearDown()` function, respectively: ```php setUp(function () { - // initialization code to run before each test() + // initialization code that runs before each test() }); ``` -The second variant is object. We will create the so-called TestCase, which is a class where individual units are represented by methods whose names begin with test–. +The second variant is object-oriented. We create a so-called TestCase, which is a class where individual units are represented by methods whose names start with `test`. ```php .{file:tests/RectangleTest.php} class RectangleTest extends Tester\TestCase @@ -212,19 +211,19 @@ class RectangleTest extends Tester\TestCase (new RectangleTest)->run(); ``` -This time we used an annotation `@throw` to test for exceptions. See the [TestCase] ​​chapter for more information. +This time, we used the `@throws` annotation to test for exceptions. You can learn more in the [TestCase |TestCase] chapter. -Helpers Functions -================= +Helper Functions +================ -Nette Tester includes several classes and functions that can make testing easier for you, for example, helpers to test the content of an HTML document, to test the functions of working with files, and so on. +Nette Tester includes several classes and functions that can make testing easier, for example, testing HTML document content, testing functions that work with files, and so on. -You can find a description of them on the page [Helpers]. +You can find their description on the [Helper Classes |helpers] page. -Annotation and Skipping Tests -============================= +Annotations and Skipping Tests +============================== Test execution can be affected by annotations in the phpDoc comment at the beginning of the file. For example, it might look like this: @@ -235,11 +234,11 @@ Test execution can be affected by annotations in the phpDoc comment at the begin */ ``` -The annotations say that the test should only be run with PHP version 7.2 or higher and if the PHP extensions pdo and pdo_pgsql are present. These annotations are controlled by [command line test runner |running-tests], which, if the conditions are not met, skips the test and marks it with the letter `s` - skipped. However, they have no effect when the test is run manually. +The annotations shown indicate that the test should only be run with PHP version 7.2 or higher and only if the `pdo` and `pdo_pgsql` PHP extensions are present. These annotations are interpreted by the [command-line test runner |running-tests], which skips the test if the conditions are not met and marks it with the letter `s` (skipped) in the output. However, these annotations have no effect when the test is run manually. -For a description of annotations, see [Test Annotations]. +You can find a description of the annotations on the [Test Annotations |test-annotations] page. -The test can also be skipped based on own condition with `Environment::skip()`. For example, we will skip this test on Windows: +A test can also be skipped based on a custom condition using `Environment::skip()`. For example, this skips the test on Windows: ```php if (defined('PHP_WINDOWS_VERSION_BUILD')) { @@ -251,7 +250,7 @@ if (defined('PHP_WINDOWS_VERSION_BUILD')) { Directory Structure =================== -For only slightly larger libraries or projects, we recommend dividing the test directory into subdirectories according to the namespace of the tested class: +For libraries or projects that are even slightly larger, we recommend dividing the test directory into subdirectories according to the namespace of the tested class: ``` └── tests/ @@ -269,22 +268,22 @@ For only slightly larger libraries or projects, we recommend dividing the test d └── ... ``` -You will be able to run tests from a single namespace ie subdirectory: +This allows you to run tests from a single namespace, i.e., a subdirectory: /--pre .[terminal] tester tests/NamespaceOne \-- -Edge Cases -========== +Special Situations +================== -A test that does not call any assertion method is suspicious and will be evaluated as erroneous: +A test that does not call any assertion method is considered suspicious and will be evaluated as an error: /--pre .[terminal] <span style="color: #FFF; background-color: #900">Error: This test forgets to execute an assertion.</span> \-- -If the test without calling assertions is really to be considered valid, call for example `Assert::true(true)`. +If a test without assertions is intentionally valid, call `Assert::true(true)` to mark it as such. -It can also be treacherous to use `exit()` and `die()` to end the test with an error message. For example, `exit('Error in connection')` ends the test with a exit code 0, which signals success. Use `Assert::fail('Error in connection')`. +Using `exit()` or `die()` to terminate a test with an error message can be misleading. For example, `exit('Error in connection')` terminates the test with an exit code of 0, which signals success. Use `Assert::fail('Error in connection')` instead. diff --git a/tester/es/@home.texy b/tester/es/@home.texy index 727c20e5b7..f36bc19e89 100644 --- a/tester/es/@home.texy +++ b/tester/es/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Nette Tester - Pruebas unitarias divertidas en PHP}} -{{description: Nette Tester es una herramienta de comprobación de código PHP sencilla y muy práctica.}} +{{maintitle: Nette Tester – pruebas cómodas en PHP}} +{{description: Nette Tester es una herramienta simple pero muy útil para probar código PHP.}} diff --git a/tester/es/@left-menu.texy b/tester/es/@left-menu.texy index 5c7bc933ea..7d63707748 100644 --- a/tester/es/@left-menu.texy +++ b/tester/es/@left-menu.texy @@ -1,8 +1,8 @@ -- [Primeros pasos |guide] -- [Escribir pruebas |Writing Tests] -- [Ejecución de pruebas |Running Tests] +- [Empezando con Nette Tester |guide] +- [Escribiendo pruebas |writing-tests] +- [Ejecutando pruebas |running-tests] -- [Aserciones |Assertions] -- [Anotaciones de pruebas |Test Annotations] -- [Casos de prueba |TestCase] -- [Ayudantes |Helpers] +- [Aserciones |assertions] +- [Anotaciones de prueba |test-annotations] +- [TestCase |TestCase] +- [Clases auxiliares |helpers] diff --git a/tester/es/@menu.texy b/tester/es/@menu.texy index 3e73817d26..8945cf54f1 100644 --- a/tester/es/@menu.texy +++ b/tester/es/@menu.texy @@ -1,3 +1,3 @@ -- [Inicio |@home] -- [Documentación |Guide] +- [Introducción |@home] +- [Documentación |guide] - "GitHub .[link-external]":https://github.com/nette/tester diff --git a/tester/es/@meta.texy b/tester/es/@meta.texy new file mode 100644 index 0000000000..2576347006 --- /dev/null +++ b/tester/es/@meta.texy @@ -0,0 +1 @@ +{{sitename: Tester Documentación}} diff --git a/tester/es/assertions.texy b/tester/es/assertions.texy index 5774fdb256..c15c28d7b5 100644 --- a/tester/es/assertions.texy +++ b/tester/es/assertions.texy @@ -1,120 +1,120 @@ -Afirmaciones -************ +Aserciones +********** .[perex] -Las aserciones se utilizan para afirmar que un valor real coincide con un valor esperado. Son métodos de `Tester\Assert`. +Las aserciones se utilizan para confirmar que el valor real corresponde al valor esperado. Son métodos de la clase `Tester\Assert`. -Elija las aserciones más precisas. Es mejor `Assert::same($a, $b)` que `Assert::true($a === $b)` porque muestra un mensaje de error significativo en caso de fallo. En el segundo caso sólo obtenemos `false should be true` y no dice nada sobre el contenido de las variables $a y $b. +Elige las aserciones más adecuadas. Es mejor `Assert::same($a, $b)` que `Assert::true($a === $b)`, porque en caso de fallo muestra un mensaje de error significativo. En el segundo caso, solo `false should be true`, lo que no nos dice nada sobre el contenido de las variables `$a` y `$b`. -La mayoría de las aserciones también pueden tener un `$description` opcional que aparece en el mensaje de error si la expectativa falla. +La mayoría de las aserciones también pueden tener una descripción opcional en el parámetro `$description`, que se mostrará en el mensaje de error si la expectativa falla. -Los ejemplos asumen que el siguiente alias de clase está definido: +Los ejemplos presuponen la creación de un alias: ```php use Tester\Assert; ``` -Assert::same($expected, $actual, string $description=null) .[method] --------------------------------------------------------------------- -`$expected` debe ser el mismo que `$actual`. Es el mismo que el operador PHP `===`. +Assert::same($expected, $actual, ?string $description=null) .[method] +--------------------------------------------------------------------- +`$expected` debe ser idéntico a `$actual`. Lo mismo que el operador PHP `===`. -Assert::notSame($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------ -Opuesto a `Assert::same()`, por lo que es el mismo que el operador PHP `!==`. +Assert::notSame($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------ +Opuesto a `Assert::same()`, es decir, lo mismo que el operador PHP `!==`. -Assert::equal($expected, $actual, string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] ------------------------------------------------------------------------------------------------------------------------- -`$expected` debe ser igual a `$actual`. A diferencia de `Assert::same()`, se ignoran la identidad del objeto, el orden de los pares clave => valor en matrices, y los números decimales marginalmente diferentes, que pueden cambiarse configurando `$matchIdentity` y `$matchOrder`. +Assert::equal($expected, $actual, ?string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] +------------------------------------------------------------------------------------------------------------------------- +`$expected` debe ser igual a `$actual`. A diferencia de `Assert::same()`, se ignora la identidad de los objetos, el orden de los pares clave => valor en los arrays y los números decimales marginalmente diferentes, lo que se puede cambiar estableciendo `$matchIdentity` y `$matchOrder`. -Los siguientes casos son idénticos desde el punto de vista de `equal()`, pero no para `same()`: +Los siguientes casos son iguales desde el punto de vista de `equal()`, pero no de `same()`: ```php Assert::equal(0.3, 0.1 + 0.2); Assert::equal($obj, clone $obj); Assert::equal( - ['primero' => 11, 'segundo' => 22], - ['segundo' => 22, 'primero' => 11], + ['first' => 11, 'second' => 22], + ['second' => 22, 'first' => 11], ); ``` -Sin embargo, cuidado, la matriz `[1, 2]` y `[2, 1]` no son iguales, porque sólo difiere el orden de los valores, no los pares clave => valor. El array `[1, 2]` también puede escribirse como `[0 => 1, 1 => 2]` y por tanto `[1 => 2, 0 => 1]` se considerará igual. +Sin embargo, ten cuidado, los arrays `[1, 2]` y `[2, 1]` no son iguales, porque solo difieren en el orden de los valores, no en los pares clave => valor. El array `[1, 2]` también se puede escribir como `[0 => 1, 1 => 2]` y, por lo tanto, se considerará igual `[1 => 2, 0 => 1]`. -También puede utilizar las llamadas [expectativas |#expectations] en `$expected`. +Además, en `$expected` se pueden usar las llamadas [#expectativas]. -Assert::notEqual($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------- +Assert::notEqual($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------- Opuesto a `Assert::equal()`. -Assert::contains($needle, string|array $actual, string $description=null) .[method] ------------------------------------------------------------------------------------ -Si `$actual` es una cadena, debe contener la subcadena `$needle`. Si es una matriz, debe contener el elemento `$needle` (se compara estrictamente). +Assert::contains($needle, string|array $actual, ?string $description=null) .[method] +------------------------------------------------------------------------------------ +Si `$actual` es una cadena, debe contener la subcadena `$needle`. Si es un array, debe contener el elemento `$needle` (se compara estrictamente). -Assert::notContains($needle, string|array $actual, string $description=null) .[method] --------------------------------------------------------------------------------------- +Assert::notContains($needle, string|array $actual, ?string $description=null) .[method] +--------------------------------------------------------------------------------------- Opuesto a `Assert::contains()`. -Assert::hasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} -------------------------------------------------------------------------------------------------------- +Assert::hasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +-------------------------------------------------------------------------------------------------------- `$actual` debe ser un array y debe contener la clave `$needle`. -Assert::notHasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} ----------------------------------------------------------------------------------------------------------- -`$actual` debe ser una matriz y no debe contener la clave `$needle`. +Assert::notHasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +----------------------------------------------------------------------------------------------------------- +`$actual` debe ser un array y no debe contener la clave `$needle`. -Assert::true($value, string $description=null) .[method] --------------------------------------------------------- -`$value` debe ser `true`, por lo que `$value === true`. +Assert::true($value, ?string $description=null) .[method] +--------------------------------------------------------- +`$value` debe ser `true`, es decir, `$value === true`. -Assert::truthy($value, string $description=null) .[method] ----------------------------------------------------------- -`$value` debe ser verdadero, por lo que satisface la condición `if ($value) ...`. +Assert::truthy($value, ?string $description=null) .[method] +----------------------------------------------------------- +`$value` debe ser verdadero (truthy), es decir, cumplirá la condición `if ($value) ...`. -Assert::false($value, string $description=null) .[method] ---------------------------------------------------------- -`$value` debe ser `false`, por lo que `$value === false`. +Assert::false($value, ?string $description=null) .[method] +---------------------------------------------------------- +`$value` debe ser `false`, es decir, `$value === false`. -Assert::falsey($value, string $description=null) .[method] ----------------------------------------------------------- -`$value` debe ser falsa, por lo que satisface la condición `if (!$value) ...`. +Assert::falsey($value, ?string $description=null) .[method] +----------------------------------------------------------- +`$value` debe ser falso (falsey), es decir, cumplirá la condición `if (!$value) ...`. -Assert::null($value, string $description=null) .[method] --------------------------------------------------------- -`$value` debe ser `null`, por lo que `$value === null`. +Assert::null($value, ?string $description=null) .[method] +--------------------------------------------------------- +`$value` debe ser `null`, es decir, `$value === null`. -Assert::notNull($value, string $description=null) .[method] ------------------------------------------------------------ -`$value` no debe ser `null`, entonces `$value !== null`. +Assert::notNull($value, ?string $description=null) .[method] +------------------------------------------------------------ +`$value` no debe ser `null`, es decir, `$value !== null`. -Assert::nan($value, string $description=null) .[method] -------------------------------------------------------- -`$value` debe ser Not a Number. Utilice únicamente `Assert::nan()` para las pruebas NAN. El valor NAN es muy específico y las aserciones `Assert::same()` o `Assert::equal()` pueden comportarse de forma impredecible. +Assert::nan($value, ?string $description=null) .[method] +-------------------------------------------------------- +`$value` debe ser Not a Number. Para probar valores NAN, utiliza exclusivamente `Assert::nan()`. El valor NAN es muy específico y las aserciones `Assert::same()` o `Assert::equal()` pueden funcionar de manera inesperada. -Assert::count($count, Countable|array $value, string $description=null) .[method] ---------------------------------------------------------------------------------- -El número de elementos en `$value` debe ser `$count`. Por tanto, igual que `count($value) === $count`. +Assert::count($count, Countable|array $value, ?string $description=null) .[method] +---------------------------------------------------------------------------------- +El número de elementos en `$value` debe ser `$count`. Es decir, lo mismo que `count($value) === $count`. -Assert::type(string|object $type, $value, string $description=null) .[method] ------------------------------------------------------------------------------ -`$value` debe ser de un tipo determinado. Como `$type` podemos utilizar string: +Assert::type(string|object $type, $value, ?string $description=null) .[method] +------------------------------------------------------------------------------ +`$value` debe ser del tipo dado. Como `$type` podemos usar una cadena: - `array` -- `list` - array indexado en orden ascendente de claves numéricas desde cero +- `list` - array indexado según una serie ascendente de claves numéricas desde cero - `bool` - `callable` - `float` @@ -124,39 +124,39 @@ Assert::type(string|object $type, $value, string $description=null) .[method] - `resource` - `scalar` - `string` -- nombre de clase u objeto directamente entonces debe pasar `$value instanceof $type` +- nombre de clase o directamente un objeto, entonces `$value` debe ser `instanceof $type` -Assert::exception(callable $callable, string $class, string $message=null, $code=null) .[method] ------------------------------------------------------------------------------------------------- -Al invocar `$callable` debe lanzarse una excepción de instancia `$class`. Si pasamos `$message`, el mensaje de la excepción debe [coincidir |#assert-match]. Y si pasamos `$code`, código de la excepción debe ser el mismo. +Assert::exception(callable $callable, string $class, ?string $message=null, $code=null) .[method] +------------------------------------------------------------------------------------------------- +Al llamar a `$callable`, se debe lanzar una excepción de la clase `$class`. Si especificamos `$message`, el mensaje de la excepción también debe [coincidir con el patrón |#Assert::match] y si especificamos `$code`, los códigos también deben coincidir estrictamente. -Por ejemplo, esta prueba falla porque el mensaje de la excepción no coincide: +La siguiente prueba fallará porque el mensaje de la excepción no coincide: ```php Assert::exception( - fn() => throw new App\InvalidValueException('Valor cero'), + fn() => throw new App\InvalidValueException('Zero value'), App\InvalidValueException::class, - 'El valor es demasiado bajo', + 'Value is to low', ); ``` -El `Assert::exception()` devuelve una excepción lanzada, por lo que puede probar una excepción anidada. +`Assert::exception()` devuelve la excepción lanzada, por lo que también se puede probar una excepción anidada. ```php $e = Assert::exception( - fn() => throw new MyException('Algo va mal', 0, new RuntimeException), + fn() => throw new MyException('Something is wrong', 0, new RuntimeException), MyException::class, - 'Algo va mal', + 'Something is wrong', ); Assert::type(RuntimeException::class, $e->getPrevious()); ``` -Assert::error(string $callable, int|string|array $type, string $message=null) .[method] ---------------------------------------------------------------------------------------- -Comprueba que la invocación `$callable` genera los errores esperados (es decir, advertencias, avisos, etc.). Como `$type` especificamos una de las constantes `E_...`, por ejemplo `E_WARNING`. Y si pasamos `$message`, el mensaje de error también debe [coincidir |#assert-match] con el patrón. Por ejemplo +Assert::error(string $callable, int|string|array $type, ?string $message=null) .[method] +---------------------------------------------------------------------------------------- +Comprueba que la función `$callable` generó los errores esperados (es decir, advertencias, avisos, etc.). Como `$type` indicaremos una de las constantes `E_...`, por ejemplo `E_WARNING`. Y si especificamos `$message`, el mensaje de error también debe [coincidir con el patrón |#Assert::match]. Por ejemplo: ```php Assert::error( @@ -166,7 +166,7 @@ Assert::error( ); ``` -Si el callback genera más errores, debemos esperarlos todos en el orden exacto. En este caso pasamos el array en `$type`: +Si el callback genera múltiples errores, debemos esperarlos todos en el orden exacto. En tal caso, pasamos un array en `$type`: ```php Assert::error(function () { @@ -179,108 +179,108 @@ Assert::error(function () { ``` .[note] -Si `$type` es el nombre de la clase, esta aserción se comporta igual que `Assert::exception()`. +Si como `$type` indicas un nombre de clase, se comporta igual que `Assert::exception()`. Assert::noError(callable $callable) .[method] --------------------------------------------- -Comprueba que la función `$callable` no lanza ningún warning/notice/error o excepción de PHP. Es útil para probar un fragmento de código en el que no hay ninguna otra aserción. +Comprueba que la función `$callable` no generó ninguna advertencia, error o excepción. Es útil para probar fragmentos de código donde no hay ninguna otra aserción. -Assert::match(string $pattern, $actual, string $description=null) .[method] ---------------------------------------------------------------------------- -`$actual` debe coincidir con `$pattern`. Podemos utilizar dos variantes de patrones: expresiones regulares o comodines. +Assert::match(string $pattern, $actual, ?string $description=null) .[method] +---------------------------------------------------------------------------- +`$actual` debe cumplir con el patrón `$pattern`. Podemos usar dos variantes de patrones: expresiones regulares o comodines. -Si pasamos una expresión regular como `$pattern`, debemos utilizar `~` or `#` para delimitarla. No se admiten otros delimitadores. Por ejemplo test donde `$var` debe contener sólo dígitos hexadecimales: +Si como `$pattern` pasamos una expresión regular, debemos usar `~` o `#` para delimitarla, no se admiten otros delimitadores. Por ejemplo, una prueba donde `$var` debe contener solo dígitos hexadecimales: ```php Assert::match('#^[0-9a-f]$#i', $var); ``` -La otra variante es similar a la comparación de cadenas pero podemos utilizar algunos caracteres comodín en `$pattern`: - -- `%a%` uno o más de cualquier cosa excepto los caracteres de fin de línea -- `%a?%` cero o más de cualquier cosa excepto los caracteres de fin de línea -- `%A%` uno o más de cualquier cosa incluyendo los caracteres de fin de línea -- `%A?%` cero o más caracteres, incluidos los de final de línea -- `%s%` uno o más caracteres de espacio en blanco excepto los caracteres de final de línea -- `%s?%` cero o más caracteres de espacio en blanco excepto los caracteres de final de línea -- `%S%` uno o más de caracteres excepto el espacio en blanco -- `%S?%` cero o más de caracteres excepto el espacio en blanco -- `%c%` un solo carácter de cualquier tipo (excepto el final de línea) +La segunda variante es similar a la comparación de cadenas normal, pero en `$pattern` podemos usar varios comodines: + +- `%a%` uno o más caracteres, excepto los caracteres de fin de línea +- `%a?%` cero o más caracteres, excepto los caracteres de fin de línea +- `%A%` uno o más caracteres, incluidos los caracteres de fin de línea +- `%A?%` cero o más caracteres, incluidos los caracteres de fin de línea +- `%s%` uno o más caracteres de espacio en blanco, excepto los caracteres de fin de línea +- `%s?%` cero o más caracteres de espacio en blanco, excepto los caracteres de fin de línea +- `%S%` uno o más caracteres, excepto los caracteres de espacio en blanco +- `%S?%` cero o más caracteres, excepto los caracteres de espacio en blanco +- `%c%` cualquier carácter individual, excepto el carácter de fin de línea - `%d%` uno o más dígitos - `%d?%` cero o más dígitos - `%i%` valor entero con signo -- `%f%` número en coma flotante -- `%h%` uno o más dígitos HEX +- `%f%` número de punto flotante +- `%h%` uno o más dígitos hexadecimales - `%w%` uno o más caracteres alfanuméricos -- `%%` un carácter % +- `%%` el carácter % Ejemplos: ```php -# Again, hexadecimal number test +# Nuevamente, prueba para número hexadecimal Assert::match('%h%', $var); -# Generalized path to file and line number +# Generalización de la ruta del archivo y el número de línea Assert::match('Error in file %a% on line %i%', $errorMessage); ``` -Assert::matchFile(string $file, $actual, string $description=null) .[method] ----------------------------------------------------------------------------- -La aserción es idéntica a [Assert::match() |#assert-match] pero el patrón se carga desde `$file`. Es útil para probar cadenas muy largas. El archivo de prueba es legible. +Assert::matchFile(string $file, $actual, ?string $description=null) .[method] +----------------------------------------------------------------------------- +La aserción es idéntica a [#Assert::match()], pero el patrón se carga desde el archivo `$file`. Esto es útil para probar cadenas muy largas. El archivo con la prueba permanecerá claro. Assert::fail(string $message, $actual=null, $expected=null) .[method] --------------------------------------------------------------------- -Esta afirmación siempre falla. Sólo es útil. Podemos pasar opcionalmente valores esperados y reales. +Esta aserción siempre falla. A veces simplemente es útil. Opcionalmente, también podemos indicar el valor esperado y el actual. -Expectativas .[#toc-expectations] ---------------------------------- -Si queremos comparar estructuras más complejas con elementos no constantes, las aserciones anteriores pueden no ser suficientes. Por ejemplo, probamos un método que crea un nuevo usuario y devuelve sus atributos como un array. No conocemos el valor hash de la contraseña, pero sabemos que debe ser una cadena hexadecimal. Y lo único que sabemos sobre el siguiente elemento es que debe ser un objeto `DateTime`. +Expectativas +------------ +Cuando queremos comparar estructuras más complejas con elementos no constantes, las aserciones anteriores pueden no ser suficientes. Por ejemplo, probamos un método que crea un nuevo usuario y devuelve sus atributos como un array. No conocemos el valor del hash de la contraseña, pero sabemos que debe ser una cadena hexadecimal. Y sobre otro elemento solo sabemos que debe ser un objeto `DateTime`. -En estos casos, podemos utilizar el `Tester\Expect` dentro del parámetro `$expected` de los métodos `Assert::equal()` y `Assert::notEqual()`, que pueden utilizarse para describir fácilmente la estructura. +En estas situaciones, podemos usar `Tester\Expect` dentro del parámetro `$expected` de los métodos `Assert::equal()` y `Assert::notEqual()`, con los que se puede describir fácilmente la estructura. ```php use Tester\Expect; Assert::equal([ - 'id' => Expect::type('int'), # we expect an integer + 'id' => Expect::type('int'), # esperamos un número entero 'username' => 'milo', - 'password' => Expect::match('%h%'), # we expect a string matching pattern - 'created_at' => Expect::type(DateTime::class), # we expect an instance of the class + 'password' => Expect::match('%h%'), # esperamos una cadena que coincida con el patrón + 'created_at' => Expect::type(DateTime::class), # esperamos una instancia de la clase ], User::create(123, 'milo', 'RandomPaSsWoRd')); ``` -Con `Expect`, podemos hacer casi las mismas afirmaciones que con `Assert`. Así que tenemos métodos como `Expect::same()`, `Expect::match()`, `Expect::count()`, etc. Además, podemos encadenarlos como: +Con `Expect` podemos realizar casi las mismas aserciones que con `Assert`. Es decir, tenemos a nuestra disposición los métodos `Expect::same()`, `Expect::match()`, `Expect::count()`, etc. Además, podemos encadenarlos: ```php -Expect::type(MyIterator::class)->andCount(5); # we expect MyIterator and items count is 5 +Expect::type(MyIterator::class)->andCount(5); # esperamos MyIterator y un número de elementos de 5 ``` -O, podemos escribir nuestros propios manejadores de aserción. +O podemos escribir nuestros propios manejadores de aserciones. ```php Expect::that(function ($value) { - # return false if expectation fails + # devolvemos false si la expectativa falla }); ``` -Investigación de aserciones fallidas .[#toc-failed-assertions-investigation] ----------------------------------------------------------------------------- -El Comprobador muestra dónde está el error cuando falla una aserción. Cuando comparamos estructuras complejas, el Probador crea volcados de los valores comparados y los guarda en el directorio `output`. Por ejemplo, cuando la prueba imaginaria `Arrays.recursive.phpt` falla, los volcados se guardan de la siguiente manera: +Examinando aserciones erróneas +------------------------------ +Cuando una aserción falla, Tester imprime cuál es el error. Si comparamos estructuras más complejas, Tester crea volcados de los valores comparados y los guarda en el directorio `output`. Por ejemplo, si falla la prueba ficticia `Arrays.recursive.phpt`, los volcados se guardarán de la siguiente manera: ``` app/ └── tests/ ├── output/ - │ ├── Arrays.recursive.actual # valor real. - │ └── Arrays.recursive.expected # valor esperado + │ ├── Arrays.recursive.actual # valor actual + │ └── Arrays.recursive.expected # valor esperado │ - └── Arrays.recursive.phpt # prueba fallida + └── Arrays.recursive.phpt # prueba fallida ``` -Podemos cambiar el nombre del directorio por `Tester\Dumper::$dumpDir`. +Podemos cambiar el nombre del directorio a través de `Tester\Dumper::$dumpDir`. diff --git a/tester/es/guide.texy b/tester/es/guide.texy index 03a800b4df..9e7894c0c2 100644 --- a/tester/es/guide.texy +++ b/tester/es/guide.texy @@ -1,17 +1,17 @@ -Primeros pasos con Tester -************************* +Empezando con Nette Tester +************************** <div class=perex> -Incluso los buenos programadores cometen errores. La diferencia entre un buen programador y uno malo es que el bueno lo hará una sola vez y la próxima vez lo detectará usando pruebas automatizadas. +Incluso los buenos programadores cometen errores. La diferencia entre un buen y un mal programador es que el bueno solo lo comete una vez y la próxima vez lo detecta mediante pruebas automatizadas. -- "Quien no prueba está condenado a repetir sus propios errores". (sabio proverbio) -- "Cuando nos libramos de un error, aparece otro". (Ley de Murphy) -- "Siempre que tengas la tentación de imprimir una declaración, escríbela en su lugar como una prueba". (Martin Fowler) +- "Quien no prueba, está condenado a repetir sus errores." (proverbio) +- "Tan pronto como nos deshacemos de un error, aparece otro." (Ley de Murphy) +- "Cada vez que tengas la tentación de imprimir una variable en la pantalla, escribe mejor una prueba." (Martin Fowler) </div> -¿Has escrito alguna vez el siguiente código en PHP? +¿Alguna vez has escrito un código similar en PHP? ```php $obj = new MyClass; @@ -20,40 +20,40 @@ $result = $obj->process($input); var_dump($result); ``` -¿Has volcado alguna vez el resultado de la llamada a una función sólo para comprobar a ojo que devuelve lo que debe devolver? Seguro que lo haces muchas veces al día. Con la mano en el corazón, si todo funciona, ¿borras este código y esperas que la clase no se rompa en el futuro? La Ley de Murphy garantiza lo contrario :-) +Es decir, ¿has impreso el resultado de la llamada a una función solo para verificar visualmente si devuelve lo que debería? Seguramente lo haces muchas veces al día. Seamos sinceros: en caso de que todo funcione correctamente, ¿borras este código? ¿Esperas que la clase no se rompa en el futuro? Las leyes de Murphy garantizan lo contrario :-) -De hecho, tú escribiste la prueba. Necesita una ligera modificación para no requerir nuestra inspección, simplemente para poder comprobarse a sí mismo. Y si no lo borraste podríamos ejecutarlo en cualquier momento en el futuro para verificar que todo sigue funcionando como debería. Es posible que crees una gran cantidad de estas pruebas a lo largo del tiempo, por lo que estaría bien que pudiéramos ejecutarlas automáticamente. +Básicamente, has escrito una prueba. Solo necesitas modificarla ligeramente para que no requiera una inspección visual, sino que se verifique a sí misma. Y si no borras la prueba, puedes ejecutarla en cualquier momento en el futuro y verificar que todo sigue funcionando como debería. Con el tiempo, crearás una gran cantidad de tales pruebas, por lo que sería útil ejecutarlas de forma automatizada. -Y Nette Tester ayuda precisamente con eso. +Y con todo esto te ayudará precisamente Nette Tester. -¿Qué hace que Tester sea único? .[#toc-what-makes-tester-unique] -================================================================ +¿Qué hace único a Tester? +========================= -Escribir pruebas para Nette Tester es único en el sentido de que **cada prueba es un script PHP estándar que puede ejecutarse de forma autónoma.** +Escribir pruebas para Nette Tester es único porque **cada prueba es un script PHP normal que se puede ejecutar de forma independiente.** -Así que cuando usted escribe una prueba, puede simplemente ejecutarla para ver si hay un error de programación. Si funciona correctamente. Si no, puedes recorrer fácilmente el programa en tu IDE y buscar un error. Incluso puedes abrirlo en un navegador. +Por lo tanto, cuando escribes una prueba, puedes ejecutarla fácilmente y averiguar si, por ejemplo, no tiene un error de programación. Si funciona correctamente. Si no, puedes depurarla fácilmente en tu IDE y buscar el error. Incluso puedes abrirla en el navegador. -Y lo más importante - ejecutándolo, realizarás la prueba. Usted sabrá inmediatamente si pasó o falló. ¿Cómo? Vamos a mostrar aquí. Vamos a escribir una prueba trivial para el uso de PHP matriz y guardarlo en el archivo `ArrayTest.php`: +Y sobre todo, al ejecutarla, realizas la prueba. Inmediatamente descubres si pasó o falló. ¿Cómo? Vamos a mostrarlo. Escribiremos una prueba trivial del trabajo con un array PHP y la guardaremos en el archivo `ArrayTest.php`: ```php .{file:ArrayTest.php} <?php use Tester\Assert; -require __DIR__ . '/vendor/autoload.php'; # cargar Composer autoloader -Tester\Environment::setup(); # inicialización de Nette Tester +require __DIR__ . '/vendor/autoload.php'; # carga del autoloader de Composer +Tester\Environment::setup(); # inicialización de Nette Tester $stack = []; -Assert::same(0, count($stack)); # esperamos que count() devuelva cero +Assert::same(0, count($stack)); # esperamos que count() devuelva cero $stack[] = 'foo'; -Assert::same(1, count($stack)); # esperamos que count() devuelva uno -Assert::contains('foo', $stack); # verifica que $stack contiene el elemento 'foo' +Assert::same(1, count($stack)); # esperamos que count() devuelva uno +Assert::contains('foo', $stack); # verificamos que $stack contiene el elemento 'foo' ``` -Como puedes ver, los [métodos de aserción |Assertions] como `Assert::same()` se utilizan para afirmar que un valor real coincide con un valor esperado. +Como puedes ver, los llamados [métodos de aserción|assertions] como `Assert::same()` se utilizan para confirmar que el valor real corresponde al valor esperado. -La prueba está escrita, podemos ejecutarla desde la línea de comandos. La primera ejecución revelará cualquier error de sintaxis, y si no cometió un error tipográfico, verá: +Tenemos la prueba escrita y podemos ejecutarla desde la línea de comandos. La primera ejecución nos revelará posibles errores de sintaxis y si no has cometido ningún error tipográfico, se imprimirá: /--pre .[terminal] $ php ArrayTest.php @@ -61,7 +61,7 @@ $ php ArrayTest.php <span style="color:#FFF; background-color:#090">OK</span> \-- -Prueba a cambiar la sentencia a `Assert::contains('XXX', $stack);` en la prueba y observa lo que ocurre al ejecutarla: +Intenta cambiar la afirmación en la prueba a una falsa `Assert::contains('XXX', $stack);` y observa qué sucede al ejecutarla: /--pre .[terminal] $ php ArrayTest.php @@ -73,53 +73,53 @@ $ php ArrayTest.php <span style="color: #FFF; background-color: #900">FAILURE</span> \-- -Continuamos sobre la escritura en el capítulo [Escribiendo Pruebas |Writing Tests]. +Continuamos escribiendo sobre esto en el capítulo [Escribiendo pruebas|writing-tests]. -Instalación y requisitos .[#toc-installation-and-requirements] -============================================================== +Instalación y requisitos +======================== -La versión mínima de PHP requerida por Tester es la 7.1 (para más detalles, consulte la tabla de [versiones de PHP soportadas |#supported PHP versions] ). La forma preferida de instalación es por [Composer |best-practices:composer]: +La versión mínima de PHP requerida por Tester es 7.1 (más detalladamente en la tabla [#Versiones de PHP compatibles]). La forma preferida de instalación es mediante [Composer |best-practices:composer]: /--pre .[terminal] composer require --dev nette/tester \-- -Intente ejecutar Nette Tester desde la línea de comandos (sin argumentos sólo mostrará un resumen de ayuda): +Intenta ejecutar Nette Tester desde la línea de comandos (sin parámetros solo mostrará la ayuda): /--pre .[terminal] vendor/bin/tester \-- -Ejecutar pruebas .[#toc-running-tests] -====================================== +Ejecución de pruebas +==================== -A medida que nuestra aplicación crece, el número de pruebas crece con ella. No sería práctico ejecutar las pruebas una a una. Por ello, el Comprobador dispone de un ejecutor de pruebas masivo, que invocamos desde la línea de comandos. El parámetro es el directorio en el que se encuentran las pruebas. El punto indica el directorio actual. +A medida que la aplicación crece, el número de pruebas crece con ella. No sería práctico ejecutar las pruebas una por una. Por eso, Tester dispone de un ejecutor masivo de pruebas, que llamamos desde la línea de comandos. Como parámetro indicamos el directorio en el que se encuentran las pruebas. El punto significa el directorio actual. /--pre .[terminal] vendor/bin/tester . \-- -El ejecutor de Nette Tester busca en el directorio especificado y en todos los subdirectorios y busca las pruebas, que son los archivos `*.phpt` y `*Test.php`. También encontrará nuestra prueba `ArrayTest.php`, ya que coincide con la máscara. +El ejecutor de pruebas buscará en el directorio especificado y en todos los subdirectorios y encontrará las pruebas, que son los archivos `*.phpt` y `*Test.php`. Así encontrará también nuestra prueba `ArrayTest.php`, ya que cumple con la máscara. -A continuación, inicia las pruebas. Ejecuta cada prueba como un nuevo proceso PHP, por lo que se ejecuta completamente aislado de los demás. Se ejecuta en paralelo en múltiples hilos, por lo que es extremadamente rápido. Y primero ejecuta las pruebas que fallaron durante la ejecución anterior, por lo que sabrá de inmediato si solucionó el error. +Luego iniciará las pruebas. Cada prueba se ejecuta como un nuevo proceso PHP, por lo que se ejecuta de forma completamente aislada de las demás. Las ejecuta en paralelo en múltiples hilos y gracias a eso es extremadamente rápido. Y como primero ejecuta las pruebas que fallaron en la ejecución anterior, te enterarás inmediatamente si lograste corregir el error. -Por cada prueba realizada, el ejecutor imprime un carácter para indicar el progreso: +Durante la ejecución de las pruebas, Tester imprime continuamente los resultados en el terminal como caracteres: -- <code style="color: #CCC; background-color: #000">.</code> - prueba superada -- <code style="color: #CCC; background-color: #000">s</code> - prueba omitida -- <code style="color: #FFF; background-color: #900">F</code> - prueba fallida +- <code style="color: #CCC; background-color: #000">.</code> – la prueba pasó +- <code style="color: #CCC; background-color: #000">s</code> – la prueba fue omitida (skipped) +- <code style="color: #FFF; background-color: #900">F</code> – la prueba falló (failed) -La salida puede tener este aspecto: +La salida puede verse así: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +PHP 8.3.2 (cli) | php -n | 8 threads ........s................<span style="color: #FFF; background-color: #900">F</span>......... @@ -132,34 +132,35 @@ PHP 7.4.8 (cli) | php -n | 8 threads <span style="color: #FFF; background-color: #900">FAILURES! (35 tests, 1 failures, 1 skipped, 1.7 seconds)</span> \-- -Se ejecutaron 35 pruebas, una falló, una se omitió. +Se ejecutaron 35 pruebas, una falló, una fue omitida. -Continuamos en el capítulo [Ejecución de pruebas |Running tests]. +Continuamos en el capítulo [Ejecución de pruebas|running-tests]. -Modo vigilancia .[#toc-watch-mode] -================================== +Modo Watch +========== -¿Está refactorizando el código? ¿O incluso desarrollas según la metodología TDD (Test Driven Development)? Entonces te gustará el modo vigilancia. El Probador monitoriza los códigos fuente y se ejecuta a sí mismo cuando se producen cambios. +¿Estás refactorizando código? ¿O incluso desarrollando según la metodología TDD (Test Driven Development)? Entonces te gustará el modo watch. Tester en él vigila los códigos fuente y al cambiar se ejecuta solo. -Durante el desarrollo, tienes un terminal en la esquina del monitor, donde se te ilumina la barra de estado verde, y cuando de repente se vuelve roja, sabes que acabas de hacer algo no deseado. En realidad es un gran juego en el que programas e intentas ceñirte al color. +Durante el desarrollo, tienes en la esquina del monitor un terminal donde te ilumina una barra de estado verde, y cuando de repente cambia a rojo, sabes que acabas de hacer algo no del todo bien. Es en realidad un juego genial, donde programas e intentas mantener el color. -El modo Watch se inicia usando el parámetro [--watch |running-tests#w-watch-path]. +El modo Watch se inicia con el parámetro [--watch |running-tests#-w --watch path]. -Informes de CodeCoverage .[#toc-codecoverage-reports] -===================================================== +Informes de CodeCoverage +======================== -El Comprobador puede generar informes con una visión general de cuánto código fuente cubren las pruebas. El informe puede estar en formato HTML legible por humanos o en Clover XML para su posterior procesamiento automático. +Tester puede generar informes con un resumen de cuánto código fuente cubren las pruebas. El informe puede estar en formato HTML legible por humanos, o Clover XML para su posterior procesamiento automático. -Vea el "ejemplo de informe HTML":https://files.nette.org/tester/coverage.html con cobertura de código. +Echa un vistazo a la "muestra de informe HTML":attachment:coverage.html con cobertura de código. -Versiones de PHP soportadas .[#toc-supported-php-versions] -========================================================== +Versiones de PHP compatibles +============================ -| versión | compatible con PHP +| versión | compatible con PHP |------------------|------------------- +| Tester 2.5 | PHP 8.0 – 8.3 | Tester 2.4 | PHP 7.2 – 8.2 | Tester 2.3 | PHP 7.1 – 8.0 | Tester 2.1 – 2.2 | PHP 7.1 – 7.3 @@ -169,6 +170,6 @@ Versiones de PHP soportadas .[#toc-supported-php-versions] | Tester 1.3 – 1.5 | PHP 5.3 – 5.6 + HHVM | Tester 0.9 – 1.2 | PHP 5.3 – 5.6 -Se aplica a las últimas versiones de parches. +Válido para la última versión de parche. -Hasta la versión 1.7 Tester soportaba [HHVM |https://hhvm.com] 3.3.0 o superior (usando `tester -p hhvm`). El soporte ha sido eliminado desde Tester 2.0. El uso era simple: +Tester hasta la versión 1.7 también soportaba [HHVM |https://hhvm.com] 3.3.0 o superior (a través de `tester -p hhvm`). El soporte se interrumpió a partir de la versión 2.0 de Tester. diff --git a/tester/es/helpers.texy b/tester/es/helpers.texy index 158466bf52..f887881a23 100644 --- a/tester/es/helpers.texy +++ b/tester/es/helpers.texy @@ -1,31 +1,45 @@ -Ayudantes -********* +Clases auxiliares +***************** DomQuery -------- -`Tester\DomQuery` es una clase que amplía `SimpleXMLElement` con métodos que facilitan la comprobación de contenidos HTML o XML. +`Tester\DomQuery` es una clase que extiende `SimpleXMLElement` con una fácil búsqueda en HTML o XML mediante selectores CSS. ```php -# tengamos un documento HTML en $html que cargamos -$dom = Tester\DomQuery::fromHtml($html); - -# podemos comprobar la presencia de elementos utilizando selectores CSS -Assert::true($dom->has('form#registration')); -Assert::true($dom->has('input[name="username"]')); -Assert::true($dom->has('input[type="submit"]')); - -# o seleccionar elementos como array de DomQuery -$elems = $dom->find('input[data-autocomplete]'); +# creación de DomQuery a partir de una cadena HTML +$dom = Tester\DomQuery::fromHtml(' + <article class="post"> + <h1>Title</h1> + <div class="content">Text</div> + </article> +'); + +# prueba de existencia de elementos mediante selectores CSS +Assert::true($dom->has('article.post')); +Assert::true($dom->has('h1')); + +# encontrar elementos como un array de objetos DomQuery +$headings = $dom->find('h1'); +Assert::same('Title', (string) $headings[0]); + +# prueba si un elemento coincide con un selector (desde la versión 2.5.3) +$content = $dom->find('.content')[0]; +Assert::true($content->matches('div')); +Assert::false($content->matches('p')); + +# encontrar el ancestro más cercano que coincida con el selector (desde 2.5.5) +$article = $content->closest('.post'); +Assert::true($article->matches('article')); ``` FileMock -------- -`Tester\FileMock` emula archivos en memoria para ayudarle a probar un código que utilice funciones como `fopen()`, `file_get_contents()` o `parse_ini_file()`. Por ejemplo +`Tester\FileMock` emula archivos en memoria y facilita así la prueba de código que utiliza funciones como `fopen()`, `file_get_contents()`, `parse_ini_file()` y similares. Ejemplo de uso: ```php -# Tested class +# Clase probada class Logger { public function __construct( @@ -39,21 +53,21 @@ class Logger } } -# New empty file +# Nuevo archivo vacío $file = Tester\FileMock::create(''); $logger = new Logger($file); $logger->log('Login'); $logger->log('Logout'); -# Created content testing +# Probamos el contenido creado Assert::same("Login\nLogout\n", file_get_contents($file)); ``` Assert::with() .[filter] ------------------------ -Esto no es una aserción, sino un ayudante para probar métodos privados y objetos de propiedad. +No es una aserción, sino un ayudante para probar métodos y propiedades privadas de objetos. ```php class Entity @@ -65,17 +79,17 @@ class Entity $ent = new Entity; Assert::with($ent, function () { - Assert::true($this->habilitado); // accesible privado $ent->habilitado + Assert::true($this->enabled); // $ent->enabled privada accesible }); ``` Helpers::purge() .[filter] -------------------------- -El método `purge()` crea el directorio especificado y, si ya existe, borra todo su contenido. Es útil para la creación de directorios temporales. Por ejemplo en `tests/bootstrap.php`: +El método `purge()` crea el directorio especificado y, si ya existe, elimina todo su contenido. Es útil para crear un directorio temporal. Por ejemplo, en `tests/bootstrap.php`: ```php -@mkdir(__DIR__ . '/tmp'); # @ - es posible que el directorio ya exista +@mkdir(__DIR__ . '/tmp'); # @ - el directorio ya puede existir define('TempDir', __DIR__ . '/tmp/' . getmypid()); Tester\Helpers::purge(TempDir); @@ -84,25 +98,25 @@ Tester\Helpers::purge(TempDir); Environment::lock() .[filter] ----------------------------- -Las pruebas se ejecutan en paralelo. A veces necesitamos no solapar la ejecución de las pruebas. Típicamente las pruebas de base de datos necesitan preparar el contenido de la base de datos y necesitan que nada las perturbe durante el tiempo de ejecución de la prueba. En estos casos utilizamos `Tester\Environment::lock($name, $dir)`: +Las pruebas se ejecutan en paralelo. Sin embargo, a veces necesitamos que la ejecución de las pruebas no se superponga. Típicamente, en las pruebas de base de datos es necesario que una prueba prepare el contenido de la base de datos y que otra prueba no acceda a la base de datos durante su ejecución. En estas pruebas usamos `Tester\Environment::lock($name, $dir)`: ```php Tester\Environment::lock('database', __DIR__ . '/tmp'); ``` -El primer argumento es un nombre de bloqueo. El segundo es una ruta al directorio para guardar el bloqueo. La prueba que adquiere el bloqueo se ejecuta primero. Las demás pruebas deben esperar a que se complete. +El primer parámetro es el nombre del bloqueo, el segundo es la ruta al directorio para guardar el bloqueo. La prueba que obtiene el bloqueo primero se ejecuta, las demás pruebas deben esperar a que termine. Environment::bypassFinals() .[filter] ------------------------------------- -Las clases o métodos marcados como `final` son difíciles de probar. Llamar a `Tester\Environment::bypassFinals()` en un inicio de prueba provoca que las palabras clave `final` sean eliminadas durante la carga del código. +Las clases o métodos marcados como `final` son difíciles de probar. La llamada a `Tester\Environment::bypassFinals()` al principio de la prueba hace que las palabras clave `final` se omitan durante la carga del código. ```php require __DIR__ . '/bootstrap.php'; Tester\Environment::bypassFinals(); -class MyClass extends NormallyFinalClass # <-- NormallyFinalClass ya no es final +class MyClass extends NormallyFinalClass # <-- NormallyFinalClass ya no es final { // ... } @@ -111,15 +125,15 @@ class MyClass extends NormallyFinalClass # <-- NormallyFinalClass ya no es final Environment::setup() .[filter] ------------------------------ -- mejora la legibilidad del volcado de errores (coloreado incluido), de lo contrario, se imprime el seguimiento de pila de PHP por defecto -- permite comprobar que las aserciones han sido invocadas en la prueba, de lo contrario, las pruebas sin (p.e. olvidadas) aserciones pasan también -- inicia automáticamente el colector de cobertura de código cuando se utiliza `--coverage` (descrito más adelante) +- mejora la legibilidad de la salida de errores (incluida la coloración), de lo contrario se muestra el stack trace predeterminado de PHP +- activa la comprobación de que se llamaron aserciones en la prueba, de lo contrario, una prueba sin aserciones (por ejemplo, olvidadas) también pasará +- al usar `--coverage`, inicia automáticamente la recopilación de información sobre el código ejecutado (descrito más adelante) - imprime el estado OK o FAILURE al final del script Environment::setupFunctions() .[filter]{data-version:2.5} --------------------------------------------------------- -Crea las funciones globales `test()`, `setUp()` y `tearDown()` en las que se pueden dividir las pruebas. +Crea las funciones globales `test()`, `testException()`, `setUp()` y `tearDown()`, en las que puedes estructurar las pruebas. ```php test('descripción de la prueba', function () { @@ -132,21 +146,21 @@ test('descripción de la prueba', function () { Environment::VariableRunner .[filter] ------------------------------------- -Le permite averiguar si la prueba se ejecutó directamente o a través del Comprobador. +Permite averiguar si la prueba se ejecutó directamente o mediante Tester. ```php if (getenv(Tester\Environment::VariableRunner)) { - # run by Tester + # ejecutado por Tester } else { - # another way + # ejecutado de otra manera } ``` Environment::VariableThread .[filter] ------------------------------------- -Tester ejecuta pruebas en paralelo en un número determinado de hilos. Encontraremos un número de hilos en una variable de entorno cuando nos interese: +Tester ejecuta las pruebas en paralelo en el número especificado de hilos. Si nos interesa el número del hilo, lo averiguamos a partir de la variable de entorno: ```php -echo "I'm running in a thread number " . getenv(Tester\Environment::VariableThread); +echo "Ejecutando en el hilo número " . getenv(Tester\Environment::VariableThread); ``` diff --git a/tester/es/running-tests.texy b/tester/es/running-tests.texy index 571b97135b..32ada1d5a5 100644 --- a/tester/es/running-tests.texy +++ b/tester/es/running-tests.texy @@ -2,76 +2,76 @@ Ejecución de pruebas ******************** .[perex] -La parte más visible de Nette Tester es el ejecutor de pruebas de línea de comandos. Es extremadamente rápido y robusto porque inicia automáticamente todas las pruebas como procesos separados en paralelo en múltiples hilos. También puede ejecutarse a sí mismo en el llamado modo de vigilancia. +La parte más visible de Nette Tester es el ejecutor de pruebas desde la línea de comandos. Es extraordinariamente rápido y robusto, ya que ejecuta automáticamente todas las pruebas como procesos separados y en paralelo en múltiples hilos. También puede ejecutarse a sí mismo en el llamado modo watch. -El ejecutor de pruebas de Nette Tester se invoca desde la línea de comandos. Como parámetro, pasaremos el directorio de pruebas. Para el directorio actual basta con introducir un punto: +El ejecutor de pruebas se llama desde la línea de comandos. Como parámetro indicamos el directorio con las pruebas. Para el directorio actual basta con indicar un punto: /--pre .[terminal] vendor/bin/tester . \-- -Al invocarlo, el ejecutor de pruebas explorará el directorio especificado y todos los subdirectorios y buscará las pruebas, que son los archivos `*.phpt` y `*Test.php`. También lee y evalúa sus [anotaciones |test-annotations] para saber cuáles y cómo ejecutarlas. +El ejecutor de pruebas buscará en el directorio especificado y en todos los subdirectorios y encontrará las pruebas, que son los archivos `*.phpt` y `*Test.php`. Al mismo tiempo, lee y evalúa sus [anotaciones|test-annotations], para saber cuáles de ellas y cómo ejecutarlas. -A continuación, ejecuta las pruebas. Por cada prueba realizada, el ejecutor imprime un carácter para indicar el progreso: +Luego ejecuta las pruebas. Durante la ejecución de las pruebas, imprime continuamente los resultados en el terminal como caracteres: -- <code style="color: #CCC; background-color: #000">.</code> - prueba superada -- <code style="color: #CCC; background-color: #000">s</code> - prueba omitida -- <code style="color: #FFF; background-color: #900">F</code> - prueba fallida +- <code style="color: #CCC; background-color: #000">.</code> – la prueba pasó +- <code style="color: #CCC; background-color: #000">s</code> – la prueba fue omitida (skipped) +- <code style="color: #FFF; background-color: #900">F</code> – la prueba falló (failed) -La salida puede tener el siguiente aspecto: +La salida puede verse así: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +PHP 8.3.2 (cli) | php -n | 8 threads ........s.......................... <span style="color: #FFF; background-color: #090">OK (35 tests, 1 skipped, 1.7 seconds)</span> \-- -Cuando vuelva a ejecutarlo, primero ejecutará las pruebas que fallaron durante la ejecución anterior, de modo que sabrá inmediatamente si ha solucionado el error. +En ejecuciones repetidas, primero ejecuta las pruebas que fallaron en la ejecución anterior, por lo que te enterarás inmediatamente si lograste corregir el error. -El código de salida del comprobador es cero si no falla ninguna. No cero en caso contrario. +Si ninguna prueba falla, el código de retorno de Tester es cero. De lo contrario, el código de retorno es distinto de cero. .[warning] -El Probador ejecuta procesos PHP sin `php.ini`. Más detalles en la sección [Propio php.ini |#Own php.ini]. +Tester ejecuta los procesos PHP sin `php.ini`. Más detalladamente en la sección [#php.ini personalizado]. -Opciones de Línea de Comandos .[#toc-command-line-options] -========================================================== +Parámetros de la línea de comandos +================================== -Obtenemos una visión general de las opciones de la línea de comandos ejecutando el Comprobador sin parámetros o con la opción `-h`: +Obtenemos un resumen de todas las opciones de la línea de comandos ejecutando Tester sin parámetros, o con el parámetro `-h`: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 - -Uso: - tester [opciones] [<archivo de prueba> | <directorio>]... - -Opciones: - -p <ruta> Especificar intérprete PHP a ejecutar (por defecto: php). - -c <ruta> Buscar el archivo php.ini (o buscar en el directorio) <ruta>. - -C Usar php.ini para todo el sistema. - -l | --log <ruta> Escribe el registro en el archivo <ruta>. - -d <clave=valor>... Definir entrada INI <clave> con valor <valor>. - -s Mostrar información sobre las pruebas omitidas. - --stop-on-fail Detener la ejecución al primer fallo. - -j <num> Ejecutar <num> trabajos en paralelo (por defecto: 8). - -o <console|tap|junit|none> Especifica el formato de salida. - -w | --watch <ruta> Directorio de vigilancia. - -i | --info Mostrar información del entorno de pruebas y salir. - --setup <ruta> Script para la configuración del runner. - --temp <ruta> Ruta al directorio temporal. Por defecto: sys_get_temp_dir(). - --colors [1|0] Activa o desactiva los colores. - --coverage <ruta> Generar informe de cobertura de código a archivo. - --coverage-src <ruta> Ruta al código fuente. - -h | --help Esta ayuda. + |_| \___ /___) |_| \___ |_|_\ v2.5.2 + +Usage: + tester [options] [<test file> | <directory>]... + +Options: + -p <path> Specify PHP interpreter to run (default: php). + -c <path> Look for php.ini file (or look in directory) <path>. + -C Use system-wide php.ini. + -d <key=value>... Define INI entry 'key' with value 'value'. + -s Show information about skipped tests. + --stop-on-fail Stop execution upon the first failure. + -j <num> Run <num> jobs in parallel (default: 8). + -o <console|console-lines|tap|junit|log|none> (e.g. -o junit:output.xml) + Specify one or more output formats with optional file name. + -w | --watch <path> Watch directory. + -i | --info Show tests environment info and exit. + --setup <path> Script for runner setup. + --temp <path> Path to temporary directory. Default by sys_get_temp_dir(). + --colors [1|0] Enable or disable colors. + --coverage <path> Generate code coverage report to file. + --coverage-src <path> Path to source code. + -h | --help This help. \-- @@ -86,26 +86,17 @@ tester -p /home/user/php-7.2.0-beta/php-cgi tests -c <path> .[filter] ------------------- -Especifica qué `php.ini` se utilizará al ejecutar las pruebas. Por defecto, no se utiliza php.ini. Consulte [php.ini propio |#Own php.ini] para obtener más información. +Especifica qué `php.ini` se utilizará al ejecutar las pruebas. Por defecto, no se utiliza ningún php.ini. Más en la sección [#php.ini personalizado]. -C .[filter] ------------ -Se utiliza `php.ini` para todo el sistema. Así que en la plataforma UNIX, todos los archivos `/etc/php/{sapi}/conf.d/*.ini` también. Véase la sección [php.ini propia |#Own php.ini]. - - -''-l | --log <path>'' .[filter] -------------------------------- -El progreso de las pruebas se escribe en el archivo. Todas las pruebas fallidas, omitidas y también las exitosas: - -/--pre .[terminal] -tester --log /var/log/tests.log tests -\-- +Se utiliza el `php.ini` del sistema. En UNIX también todos los archivos INI relevantes `/etc/php/{sapi}/conf.d/*.ini`. Más en la sección [#php.ini personalizado]. -d <key=value> .[filter] ------------------------ -Establece el valor de la directiva de configuración PHP para las pruebas. El parámetro se puede utilizar varias veces. +Establece el valor de la directiva de configuración de PHP para las pruebas. El parámetro puede usarse varias veces. /--pre .[terminal] tester -d max_execution_time=20 @@ -119,29 +110,31 @@ Se mostrará información sobre las pruebas omitidas. --stop-on-fail .[filter] ------------------------ -El probador detiene la prueba al fallar la primera prueba. +Tester detendrá las pruebas en la primera prueba fallida. -j <num> .[filter] ------------------ -Las pruebas se ejecutan en `<num>` precesos en paralelo. El valor por defecto es 8. Si deseamos ejecutar las pruebas en serie, utilizaremos el valor 1. +Especifica cuántos procesos paralelos con pruebas se ejecutarán. El valor predeterminado es 8. Si queremos que todas las pruebas se ejecuten en serie, usamos el valor 1. --o <console|tap|junit|none> .[filter] -------------------------------------- -Formato de salida. Por defecto es el formato de consola. +-o <console|console-lines|tap|junit|log|none> .[filter] +------------------------------------------------------- +Establece el formato de salida. El predeterminado es el formato para la consola. Puedes indicar un nombre de archivo en el que se escribirá la salida (p. ej., `-o junit:output.xml`). La opción `-o` se puede repetir varias veces para generar múltiples formatos a la vez. -- `console`: igual que por defecto, pero en este caso no se imprime el logotipo ASCII -- `tap`: formato [TAP |https://en.wikipedia.org/wiki/Test_Anything_Protocol] apropiado para el procesamiento en máquina -- `junit`: formato JUnit XML, apropiado también para el procesamiento en máquina +- `console`: idéntico al formato predeterminado, pero en este caso no se muestra el logo ASCII +- `console-lines`: similar a console, pero el resultado de cada prueba se indica en una línea separada con información adicional +- `tap`: [formato TAP |https://en.wikipedia.org/wiki/Test_Anything_Protocol] adecuado para el procesamiento automático +- `junit`: formato XML JUnit, también adecuado para el procesamiento automático +- `log`: Salidas del progreso de las pruebas. Todas las pruebas fallidas, omitidas y también las exitosas - `none`: no se imprime nada ''-w | --watch <path>'' .[filter] --------------------------------- -El Probador no termina después de que las pruebas son completadas sino que continúa corriendo y observando los archivos PHP en el directorio dado. Cuando se cambia, ejecuta las pruebas de nuevo. El parámetro puede ser usado múltiples veces si queremos monitorear múltiples directorios. +Después de completar las pruebas, Tester no termina, sino que permanece en ejecución y vigila los archivos PHP en el directorio especificado. Al cambiar, vuelve a ejecutar las pruebas. El parámetro puede usarse varias veces si queremos vigilar múltiples directorios. -Es útil durante la refactorización de una librería o la depuración de pruebas. +Es útil al refactorizar una librería o depurar pruebas. /--pre .[terminal] tester --watch src tests @@ -150,7 +143,7 @@ tester --watch src tests ''-i | --info'' .[filter] ------------------------- -Muestra información sobre el entorno de ejecución de una prueba. Por ejemplo: +Muestra información sobre el entorno de ejecución para las pruebas. Por ejemplo: /--pre .[terminal] tester -p /usr/bin/php7.1 -c tests/php.ini --info @@ -177,13 +170,13 @@ Core, ctype, date, dom, ereg, fileinfo, filter, hash, ... --setup <path> .[filter] ------------------------ -El Probador carga el script PHP dado al inicio. La variable `Tester\Runner\Runner $runner` está disponible en él. Supongamos que el archivo `tests/runner-setup.php`: +Tester al iniciar carga el script PHP especificado. En él está disponible la variable `Tester\Runner\Runner $runner`. Supongamos el archivo `tests/runner-setup.php` con el contenido: ```php $runner->outputHandlers[] = new MyOutputHandler; ``` -y ejecutamos el Probador: +Ejecutamos Tester: /--pre .[terminal] tester --setup tests/runner-setup.php tests @@ -192,47 +185,47 @@ tester --setup tests/runner-setup.php tests --temp <path> .[filter] ----------------------- -Establece una ruta al directorio para los archivos temporales de Tester. El valor por defecto es devuelto por `sys_get_temp_dir()`. Cuando el valor por defecto no es válido, se le avisará. +Establece la ruta al directorio para los archivos temporales de Tester. El valor predeterminado lo devuelve `sys_get_temp_dir()`. Si el valor predeterminado no es válido, serás advertido. -Si no estamos seguros del directorio utilizado, podemos ejecutar Tester con el parámetro `--info`. +Si no estamos seguros de qué directorio se utiliza, ejecutamos Tester con el parámetro `--info`. --colors 1|0 .[filter] ---------------------- -El Tester detecta un terminal coloreable por defecto y colorea su salida. Esta opción está por encima de la autodetección. Podemos establecer la coloración globalmente mediante una variable de entorno del sistema `NETTE_TESTER_COLORS`. +Por defecto, Tester detecta un terminal a color y colorea su salida. Esta opción anula la autodetección. Globalmente podemos configurar la coloración con la variable de entorno del sistema `NETTE_TESTER_COLORS`. ---cobertura <path> .[filter] ----------------------------- -Tester generará un informe con una visión general de la cobertura del código fuente por las pruebas. Esta opción requiere la extensión PHP [Xdebug |https://xdebug.org/] o [PCOV |https://github.com/krakjoe/pcov] habilitada, o PHP 7 con el PHPDBG SAPI, que es más rápido. La extensión del archivo de destino determina el formato del contenido. HTML o Clover XML. +--coverage <path> .[filter] +--------------------------- +Tester generará un informe con un resumen de cuánto código fuente cubren las pruebas. Esta opción requiere la extensión PHP instalada [Xdebug |https://xdebug.org/], o [PCOV |https://github.com/krakjoe/pcov], o PHP 7 con PHPDBG SAPI, que es más rápido. La extensión del archivo de destino determina su formato. Ya sea HTML o Clover XML. /--pre .[terminal] -tester tests --coverage coverage.html # HTML report -tester tests --coverage coverage.xml # Clover XML report +tester tests --coverage coverage.html # Informe HTML +tester tests --coverage coverage.xml # Informe Clover XML \-- -La prioridad para elegir el mecanismo de recogida es la siguiente +La prioridad de selección del mecanismo es la siguiente: 1) PCOV 2) PHPDBG 3) Xdebug -Las pruebas extensas pueden fallar durante su ejecución por PHPDBG debido al agotamiento de la memoria. La recogida de datos de cobertura es una operación que consume memoria. En ese caso, llamar a `Tester\CodeCoverage\Collector::flush()` dentro de una prueba puede ayudar. Esto vaciará los datos recolectados en un archivo y liberará memoria. Cuando la recopilación de datos no está en curso, o se utiliza Xdebug, la llamada no tiene ningún efecto. +Al usar PHPDBG, en pruebas extensas podemos encontrar un fallo de la prueba debido al agotamiento de la memoria. La recopilación de información sobre el código cubierto consume mucha memoria. En este caso, nos ayuda la llamada a `Tester\CodeCoverage\Collector::flush()` dentro de la prueba. Escribe los datos recopilados en el disco y libera la memoria. Si la recopilación de datos no se está realizando, o se utiliza Xdebug, la llamada no tiene ningún efecto. -"Un ejemplo de informe HTML":https://files.nette.org/tester/coverage.html con cobertura de código. +[Muestra de informe HTML |attachment:coverage.html] con cobertura de código. ---cobertura-src <path> .[filter] --------------------------------- -Lo utilizamos con la opción `--coverage` simultáneamente. El `<path>` es una ruta al código fuente para el que generamos el informe. Se puede utilizar repetidamente. +--coverage-src <path> .[filter] +------------------------------- +Se utiliza junto con la opción `--coverage`. `<path>` es la ruta a los códigos fuente para los que se genera el informe. Se puede usar repetidamente. -Propio php.ini .[#toc-own-php-ini] -================================== -El Probador ejecuta procesos PHP con la opción `-n`, lo que significa que no se carga `php.ini` (ni siquiera el de `/etc/php/conf.d/*.ini` en UNIX). Esto asegura el mismo entorno para las pruebas ejecutadas, pero también desactiva todas las extensiones externas de PHP comúnmente cargadas por el sistema PHP. +php.ini personalizado +===================== +Tester ejecuta los procesos PHP con el parámetro `-n`, lo que significa que no se carga ningún `php.ini`. En UNIX, tampoco los de `/etc/php/conf.d/*.ini`. Esto asegura un entorno idéntico para la ejecución de las pruebas, pero también deshabilita todas las extensiones PHP normalmente cargadas por el PHP del sistema. -Si desea mantener la configuración del sistema, utilice el parámetro `-C`. +Si deseas conservar la carga de los archivos php.ini del sistema, utiliza el parámetro `-C`. -Si necesita algunas extensiones o algunas configuraciones INI especiales, le recomendamos crear su propio archivo `php.ini` y distribuirlo entre las pruebas. Luego ejecutamos Tester con la opción `-c`, por ejemplo `tester -c tests/php.ini`. El archivo INI puede tener el siguiente aspecto +Si necesitas algunas extensiones o configuraciones INI especiales para las pruebas, recomendamos crear tu propio archivo `php.ini`, que se distribuirá con las pruebas. Luego, ejecutamos Tester con el parámetro `-c`, por ejemplo `tester -c tests/php.ini tests`, donde el archivo INI puede verse así: ```ini [PHP] @@ -243,4 +236,4 @@ extension=php_pdo_pgsql.dll memory_limit=512M ``` -Ejecutar el Tester con un sistema `php.ini` en UNIX, por ejemplo `tester -c /etc/php/cgi/php.ini`, no carga otros INI de `/etc/php/conf.d/*.ini`. Ese es el comportamiento de PHP, no del Tester. +La ejecución de Tester en UNIX con el `php.ini` del sistema, por ejemplo `tester -c /etc/php/cli/php.ini`, no carga los demás INI de `/etc/php/conf.d/*.ini`. Esta es una característica de PHP, no de Tester. diff --git a/tester/es/test-annotations.texy b/tester/es/test-annotations.texy index 7e4b7b18ea..de6516e33d 100644 --- a/tester/es/test-annotations.texy +++ b/tester/es/test-annotations.texy @@ -1,10 +1,10 @@ -Anotaciones de prueba -********************* +Anotaciones de pruebas +********************** .[perex] -Las anotaciones determinan cómo serán manejadas las pruebas por [el ejecutor de pruebas de línea de comandos |running-tests]. Se escriben al principio del archivo de prueba. +Las anotaciones determinan cómo tratará las pruebas el [ejecutor de pruebas desde la línea de comandos|running-tests]. Se escriben al principio del archivo con la prueba. -Las anotaciones no distinguen entre mayúsculas y minúsculas. Tampoco tienen efecto si la prueba se ejecuta manualmente como un script PHP normal. +En las anotaciones no se distingue entre mayúsculas y minúsculas. Tampoco tienen ningún efecto si la prueba se ejecuta manualmente como un script PHP normal. Ejemplo: @@ -23,17 +23,17 @@ require __DIR__ . '/../bootstrap.php'; TEST .[filter] -------------- -En realidad no es una anotación. Sólo establece el título de la prueba que se imprime al fallar o en los registros. +Esto en realidad ni siquiera es una anotación, solo determina el título de la prueba, que se imprime en caso de fallo o en el log. @skip .[filter] --------------- -Se omite la prueba. Es útil para la desactivación temporal de la prueba. +La prueba se omite. Es útil para deshabilitar temporalmente las pruebas. @phpVersion .[filter] --------------------- -La prueba es omitida si no es ejecutada por la versión PHP correspondiente. Escribimos la anotación como `@phpVersion [operator] version`. Podemos omitir el operador, por defecto es `>=`. Ejemplos: +La prueba se omite si no se ejecuta con la versión de PHP correspondiente. La anotación la escribimos como `@phpVersion [operador] versión`. Podemos omitir el operador, el predeterminado es `>=`. Ejemplos: ```php /** @@ -46,7 +46,7 @@ La prueba es omitida si no es ejecutada por la versión PHP correspondiente. Esc @phpExtension .[filter] ----------------------- -La prueba se salta si todas las extensiones PHP mencionadas no están cargadas. Múltiples extensiones pueden ser escritas en una sola anotación, o podemos usar la anotación múltiples veces. +La prueba se omite si no están cargadas todas las extensiones PHP indicadas. Podemos indicar múltiples extensiones en una anotación, o usarla varias veces. ```php /** @@ -58,9 +58,9 @@ La prueba se salta si todas las extensiones PHP mencionadas no están cargadas. @dataProvider .[filter] ----------------------- -Esta anotación es adecuada cuando queremos ejecutar la prueba varias veces pero con datos diferentes. (No confundir con la anotación del mismo nombre para [TestCase |TestCase#dataProvider]). +Si queremos ejecutar el archivo de prueba varias veces, pero con diferentes datos de entrada, esta anotación es útil. (No confundir con la anotación del mismo nombre para [TestCase |TestCase#dataProvider].) -Escribimos la anotación como `@dataProvider file.ini`. La ruta del archivo INI es relativa al archivo de prueba. La prueba se ejecuta tantas veces como secciones contenga el archivo INI. Supongamos que el archivo INI `databases.ini`: +La escribimos como `@dataProvider file.ini`, la ruta al archivo se toma relativa al archivo con la prueba. La prueba se ejecutará tantas veces como secciones haya en el archivo INI. Supongamos el archivo INI `databases.ini`: ```ini [mysql] @@ -77,7 +77,7 @@ password = ****** dsn = "sqlite::memory:" ``` -y el fichero `database.phpt` en el mismo directorio: +y en el mismo directorio la prueba `database.phpt`: ```php /** @@ -87,11 +87,11 @@ y el fichero `database.phpt` en el mismo directorio: $args = Tester\Environment::loadData(); ``` -La prueba se ejecuta tres veces y `$args` contendrá valores de las secciones `mysql`, `postgresql` o `sqlite`. +La prueba se ejecutará tres veces y `$args` contendrá siempre los valores de la sección `mysql`, `postgresql` o `sqlite`. -Hay una variación más cuando escribimos anotaciones con un signo de interrogación como `@dataProvider? file.ini`. En este caso, la prueba se salta si el archivo INI no existe. +Existe aún una variante donde escribimos la anotación con un signo de interrogación como `@dataProvider? file.ini`. En este caso, la prueba se omitirá si el archivo INI no existe. -Aún no se han mencionado todas las posibilidades de anotación. Podemos escribir condiciones después del archivo INI. La prueba se ejecuta para la sección dada sólo si todas las condiciones coinciden. Vamos a ampliar el archivo INI: +Con esto no terminan las posibilidades de la anotación. Después del nombre del archivo INI podemos especificar las condiciones bajo las cuales se ejecutará la prueba para la sección dada. Ampliemos el archivo INI: ```ini [mysql] @@ -113,7 +113,7 @@ password = ****** dsn = "sqlite::memory:" ``` -y utilizaremos la anotación con la condición: +y usemos la anotación con condición: ```php /** @@ -121,9 +121,9 @@ y utilizaremos la anotación con la condición: */ ``` -La prueba se ejecuta sólo una vez para la sección `postgresql 9.1`. Las demás secciones no cumplen las condiciones. +La prueba se ejecutará solo una vez y para la sección `postgresql 9.1`. Las demás secciones no pasarán el filtro de la condición. -Del mismo modo, podemos pasar la ruta a un script PHP en lugar de INI. Debe devolver array o Traversable. Archivo `databases.php`: +De manera similar, en lugar de un archivo INI podemos referenciar un script PHP. Este debe devolver un array o Traversable. Archivo `databases.php`: ```php return [ @@ -142,29 +142,29 @@ return [ @multiple .[filter] ------------------- -Lo escribimos como `@multiple N` donde `N` es un número entero. La prueba se ejecuta exactamente N veces. +Lo escribimos como `@multiple N`, donde `N` es un número entero. La prueba se ejecutará exactamente N veces. @testCase .[filter] ------------------- -La anotación no tiene parámetros. La utilizamos cuando escribimos una prueba como clases [TestCase]. En este caso, el ejecutor de pruebas de línea de comandos ejecutará los métodos individuales en procesos separados y en paralelo en múltiples hilos. Esto puede acelerar significativamente todo el proceso de prueba. +La anotación no tiene parámetros. La usamos si escribimos las pruebas como [clases TestCase |TestCase]. En ese caso, el ejecutor de pruebas desde la línea de comandos ejecutará los métodos individuales en procesos separados y en paralelo en múltiples hilos. Esto puede acelerar significativamente todo el proceso de prueba. @exitCode .[filter] ------------------- -Lo escribimos como `@exitCode N` donde `N` is the exit code of the test. For example if `exit(10)` es llamado en la prueba, escribimos la anotación como `@exitCode 10`. Se considera que falla si la prueba termina con un código diferente. El código de salida 0 (cero) se verifica si omitimos la anotación +Lo escribimos como `@exitCode N`, donde `N` es el código de retorno de la prueba ejecutada. Si en la prueba se llama, por ejemplo, a `exit(10)`, escribimos la anotación como `@exitCode 10` y si la prueba termina con otro código, se considera un fallo. Si no indicamos la anotación, se verifica el código de retorno 0 (cero). @httpCode .[filter] ------------------- -La anotación es evaluada sólo si el binario PHP es CGI. En caso contrario, se ignora. Lo escribimos como `@httpCode NNN` donde `NNN` es el código HTTP esperado. El código HTTP 200 se verifica si omitimos la anotación. Si escribimos `NNN` como una cadena evaluada como cero, por ejemplo `any`, el código HTTP no es verificado en absoluto. +La anotación solo se aplica si el binario PHP es CGI. De lo contrario, se ignora. La escribimos como `@httpCode NNN` donde `NNN` es el código HTTP esperado. Si no indicamos la anotación, se verifica el código HTTP 200. Si `NNN` lo escribimos como una cadena evaluada a cero, por ejemplo `any`, el código HTTP no se verifica. -@outputMatch a @outputMatchFile .[filter] +@outputMatch y @outputMatchFile .[filter] ----------------------------------------- -El comportamiento de las anotaciones es coherente con las aserciones `Assert::match()` y `Assert::matchFile()`. Pero el patrón se encuentra en la salida estándar de la prueba. Un caso de uso adecuado es cuando suponemos que la prueba finaliza por error fatal y necesitamos verificar su salida. +La función de las anotaciones es idéntica a las aserciones `Assert::match()` y `Assert::matchFile()`. Pero el patrón se busca en el texto que la prueba envió a su salida estándar. Encuentra aplicación si suponemos que la prueba terminará con un error fatal y necesitamos verificar su salida. @phpIni .[filter] ----------------- -Establece valores de configuración INI para el test. Por ejemplo lo escribimos como `@phpIni precision=20` y funciona de la misma manera que si pasáramos el valor desde la línea de comandos por el parámetro `-d precision=20`. +Para la prueba establece valores de configuración INI. Lo escribimos, por ejemplo, como `@phpIni precision=20` y funciona igual que si hubiéramos especificado el valor desde la línea de comandos a través del parámetro `-d precision=20`. diff --git a/tester/es/testcase.texy b/tester/es/testcase.texy index 7eee728a5e..41bcc6dfb9 100644 --- a/tester/es/testcase.texy +++ b/tester/es/testcase.texy @@ -1,10 +1,10 @@ -Caso de prueba -************** +TestCase +******** .[perex] -Las aserciones pueden seguirse una a una en pruebas simples. Pero a veces es útil encerrar las aserciones a la clase de prueba y estructurarlas de esta manera. +En pruebas simples, las aserciones pueden seguir una tras otra. Sin embargo, a veces es más conveniente empaquetar las aserciones en una clase de prueba y así estructurarlas. -La clase debe ser descendiente de `Tester\TestCase` y hablamos de ella simplemente como de **testcase**. +La clase debe ser descendiente de `Tester\TestCase` y, de forma simplificada, nos referimos a ella como **testcase**. La clase debe contener métodos de prueba que comiencen con `test`. Estos métodos se ejecutarán como pruebas: ```php use Tester\Assert; @@ -22,11 +22,11 @@ class RectangleTest extends Tester\TestCase } } -# Ejecutar métodos de prueba +# Ejecución de los métodos de prueba (new RectangleTest)->run(); ``` -Podemos enriquecer un testcase con los métodos `setUp()` y `tearDown()`. Se llaman antes/después de cada método de prueba: +Una prueba escrita de esta manera puede enriquecerse aún más con los métodos `setUp()` y `tearDown()`. Se llaman antes y después de cada método de prueba, respectivamente: ```php use Tester\Assert; @@ -35,12 +35,12 @@ class NextTest extends Tester\TestCase { public function setUp() { - # Preparation + # Preparación } public function tearDown() { - # Clean-up + # Limpieza } public function testOne() @@ -54,14 +54,14 @@ class NextTest extends Tester\TestCase } } -# Ejecutar métodos de prueba +# Ejecución de los métodos de prueba (new NextTest)->run(); /* -Método Llamadas Orden .[#toc-method-calls-order] ------------------------------------------------- +Orden de llamada de los métodos +------------------------------- setUp() testOne() tearDown() @@ -72,9 +72,9 @@ tearDown() */ ``` -Si se produce un error en una fase de `setUp()` o `tearDown()`, la prueba fallará. Si el error se produce en el método de prueba, el método `tearDown()` se llama de todos modos, pero con errores suprimidos en el mismo. +Si ocurre un error en la fase `setUp()` o `tearDown()`, la prueba falla en general. Si ocurre un error en el método de prueba, a pesar de eso, el método `tearDown()` se ejecuta, pero con la supresión de errores en él. -Le recomendamos que escriba la anotación [@testCase |test-annotations#@testCase] al principio de la prueba, entonces el ejecutor de pruebas de línea de comandos ejecutará los métodos individuales del testcase en procesos separados y en paralelo en múltiples hilos. Esto puede acelerar significativamente todo el proceso de prueba. +Recomendamos escribir la anotación [@testCase |test-annotations#testCase] al principio de la prueba, entonces el ejecutor de pruebas desde la línea de comandos ejecutará los métodos individuales del testcase en procesos separados y en paralelo en múltiples hilos. Esto puede acelerar significativamente todo el proceso de prueba. /--php <?php @@ -82,15 +82,15 @@ Le recomendamos que escriba la anotación [@testCase |test-annotations#@testCase \-- -Anotación de métodos .[#toc-annotation-of-methods] -================================================== +Anotaciones de métodos +====================== -Hay algunas anotaciones disponibles para ayudarnos con los métodos de prueba. Las escribimos hacia el método de prueba. +Para los métodos de prueba tenemos disponibles varias anotaciones que nos facilitan las pruebas. Las escribimos junto al método de prueba. @throws .[filter] ----------------- -Es el mismo uso de `Assert::exception()` dentro de un método de prueba. Pero la notación es más legible: +Es equivalente al uso de `Assert::exception()` dentro del método de prueba. Pero la escritura es más clara: ```php /** @@ -114,9 +114,9 @@ public function testTwo() @dataProvider .[filter] ----------------------- -Esta anotación sirve cuando queremos ejecutar el método de prueba varias veces pero con diferentes argumentos. (No confundir con la anotación del mismo nombre para [archivos |test-annotations#dataProvider]). +Si queremos ejecutar el método de prueba varias veces, pero con diferentes parámetros, esta anotación es útil. (No confundir con la anotación del mismo nombre para [archivos |test-annotations#dataProvider].) -Como argumento escribimos nombre del método que devuelve parámetros para el método de prueba. El método debe devolver un array o Traversable. Ejemplo sencillo: +Después de ella indicamos el nombre del método que devuelve los argumentos para el método de prueba. El método debe devolver un array o Traversable. Un ejemplo simple: ```php public function getLoopArgs() @@ -138,7 +138,7 @@ public function testLoop($a, $b, $c) } ``` -La otra variación de la anotación **@dataProvider** acepta como argumento una ruta al fichero INI (relativamente al fichero de prueba). El método es llamado tantas veces como el número de secciones contenidas en el fichero INI. Archivo `loop-args.ini`: +La segunda variante de la anotación **@dataProvider** acepta como parámetro la ruta a un archivo INI (relativa al archivo con la prueba). El método se llama tantas veces como secciones haya en el archivo INI. Archivo `loop-args.ini`: ```ini [one] @@ -157,7 +157,7 @@ b=8 c=9 ``` -y el método que utiliza el fichero INI: +y el método que utiliza el archivo INI: ```php /** @@ -169,7 +169,7 @@ public function testLoop($a, $b, $c) } ``` -Del mismo modo, podemos pasar la ruta a un script PHP en lugar del INI. Debe devolver array o Traversable. Archivo `loop-args.php`: +De manera similar, en lugar de un archivo INI podemos referenciar un script PHP. Este debe devolver un array o Traversable. Archivo `loop-args.php`: ```php return [ diff --git a/tester/es/writing-tests.texy b/tester/es/writing-tests.texy index d3a95f9b19..854d54423b 100644 --- a/tester/es/writing-tests.texy +++ b/tester/es/writing-tests.texy @@ -1,21 +1,20 @@ -Pruebas de redacción -******************** +Escribiendo pruebas +******************* .[perex] -Escribir pruebas para Nette Tester es único en que cada prueba es un script PHP que se puede ejecutar de forma independiente.. Esto tiene un gran potencial. -Mientras escribes la prueba, puedes simplemente ejecutarla para ver si funciona correctamente. Si no, usted puede fácilmente pasar a través en el IDE y buscar un error. +Escribir pruebas para Nette Tester es único porque cada prueba es un script PHP que se puede ejecutar de forma independiente. Esto esconde un gran potencial. Ya cuando escribes la prueba, puedes ejecutarla fácilmente y averiguar si funciona correctamente. Si no, puedes depurarla fácilmente en el IDE y buscar el error. -Incluso puedes abrir la prueba en un navegador. Pero sobre todo - al ejecutarlo, realizarás la prueba. Usted sabrá inmediatamente si pasó o falló. +Incluso puedes abrir la prueba en el navegador. Pero sobre todo, al ejecutarla, realizas la prueba. Inmediatamente descubres si pasó o falló. -En el capítulo introductorio, [mostramos |guide#What Makes Tester Unique?] una prueba realmente trivial del uso de un array PHP. Ahora crearemos nuestra propia clase, que probaremos, aunque también será simple. +En el capítulo introductorio [mostramos |guide#Qué hace único a Tester] una prueba realmente trivial del trabajo con arrays. Ahora crearemos nuestra propia clase que probaremos, aunque también será simple. -Empecemos con una distribución típica de directorios para una librería o proyecto. Es importante separar las pruebas del resto del código, por ejemplo debido al despliegue, ya que no queremos subir las pruebas al servidor. La estructura puede ser la siguiente: +Comenzaremos con una estructura de directorios típica para una librería o proyecto. Es importante separar las pruebas del resto del código, por ejemplo, debido al deployment, porque no queremos subir las pruebas al servidor de producción. La estructura puede ser, por ejemplo, así: ``` -├── src/ # code that we will test +├── src/ # código que probaremos │ ├── Rectangle.php │ └── ... -├── tests/ # tests +├── tests/ # pruebas │ ├── bootstrap.php │ ├── RectangleTest.php │ └── ... @@ -23,7 +22,7 @@ Empecemos con una distribución típica de directorios para una librería o proy └── composer.json ``` -Y ahora crearemos archivos individuales. Empezaremos con la clase probada, que colocaremos en el archivo `src/Rectangle.php` +Y ahora crearemos los archivos individuales. Comenzaremos con la clase probada, que colocaremos en el archivo `src/Rectangle.php` ```php .{file:src/Rectangle.php} <?php @@ -35,7 +34,7 @@ class Rectangle public function __construct(float $width, float $height) { if ($width < 0 || $height < 0) { - throw new InvalidArgumentException('La dimensión no debe ser negativa.'); + throw new InvalidArgumentException('The dimension must not be negative.'); } $this->width = $width; $this->height = $height; @@ -53,7 +52,7 @@ class Rectangle } ``` -Y crearemos una prueba para ella. El nombre del archivo de prueba debe coincidir con la máscara `*Test.php` o `*.phpt`, elegiremos la variante `RectangleTest.php`: +Y crearemos una prueba para ella. El nombre del archivo con la prueba debe coincidir con la máscara `*Test.php` o `*.phpt`, elegiremos, por ejemplo, la variante `RectangleTest.php`: ```php .{file:tests/RectangleTest.php} @@ -62,31 +61,31 @@ use Tester\Assert; require __DIR__ . '/bootstrap.php'; -// general oblong +// rectángulo general $rect = new Rectangle(10, 20); -Assert::same(200.0, $rect->getArea()); # comprobaremos los resultados esperados +Assert::same(200.0, $rect->getArea()); # verificamos los resultados esperados Assert::false($rect->isSquare()); ``` -Como puede ver, los [métodos de aserción |Assertions] como `Assert::same()` se utilizan para afirmar que un valor real coincide con un valor esperado. +Como puedes ver, los llamados [métodos de aserción|assertions] como `Assert::same()` se utilizan para confirmar que el valor real corresponde al valor esperado. -El último paso es crear el archivo `bootstrap.php`. Contiene un código común para todas las pruebas. Por ejemplo autoloading de clases, configuración del entorno, creación de directorios temporales, helpers y similares. Cada prueba carga el bootstrap y sólo presta atención a las pruebas. El bootstrap puede parecerse a: +Queda el último paso y es el archivo `bootstrap.php`. Este contiene código común para todas las pruebas, por ejemplo, autoloading de clases, configuración del entorno, creación de un directorio temporal, funciones auxiliares y similares. Todas las pruebas cargan el bootstrap y luego se dedican solo a las pruebas. El bootstrap puede verse de la siguiente manera: ```php .{file:tests/bootstrap.php} <?php -require __DIR__ . '/vendor/autoload.php'; # cargar Composer autoloader +require __DIR__ . '/vendor/autoload.php'; # carga el autoloader de Composer -Tester\Environment::setup(); # inicialización de Nette Tester +Tester\Environment::setup(); # inicialización de Nette Tester -// y otras configuraciones (sólo un ejemplo, en nuestro caso no son necesarias) +// y otras configuraciones (es solo un ejemplo, en nuestro caso no son necesarias) date_default_timezone_set('Europe/Prague'); define('TmpDir', '/tmp/app-tests'); ``` .[note] -Este bootstrap asume que el autocargador de Composer será capaz de cargar la clase `Rectangle.php` también. Esto se puede conseguir, por ejemplo, [estableciendo la sección autoload |best-practices:composer#autoloading] en `composer.json`, etc. +El bootstrap indicado presupone que el autoloader de Composer será capaz de cargar también la clase `Rectangle.php`. Esto se puede lograr, por ejemplo, [configurando la sección autoload |best-practices:composer#Autoloading] en `composer.json`, etc. -Ahora podemos ejecutar la prueba desde la línea de comandos como cualquier otro script PHP independiente. La primera ejecución revelará cualquier error de sintaxis, y si no cometiste un error tipográfico, lo verás: +Ahora podemos ejecutar la prueba desde la línea de comandos como cualquier otro script PHP independiente. La primera ejecución nos revelará posibles errores de sintaxis y si no hay ningún error tipográfico, se imprimirá: /--pre .[terminal] $ php RectangleTest.php @@ -94,7 +93,7 @@ $ php RectangleTest.php <span style="color:#FFF; background-color:#090">OK</span> \-- -Si cambiamos en el test la sentencia a false `Assert::same(123, $rect->getArea());`, ocurrirá lo siguiente: +Si cambiáramos la afirmación en la prueba a una falsa `Assert::same(123, $rect->getArea());` sucedería esto: /--pre .[terminal] $ php RectangleTest.php @@ -107,35 +106,35 @@ $ php RectangleTest.php \-- -Al escribir pruebas, es bueno capturar todas las situaciones extremas. Por ejemplo, si la entrada es cero, un número negativo, en otros casos una cadena vacía, null, etc. De hecho, te obliga a pensar y decidir cómo debe comportarse el código en esas situaciones. A continuación, las pruebas corrigen el comportamiento. +Al escribir pruebas, es bueno cubrir todas las situaciones límite. Por ejemplo, cuando la entrada sea cero, un número negativo, en otros casos, por ejemplo, una cadena vacía, null, etc. En realidad, te obliga a reflexionar y decidir cómo debe comportarse el código en tales situaciones. Las pruebas luego fijan el comportamiento. -En nuestro caso, un valor negativo debería lanzar una excepción, que verificamos con [Assert::exception() |Assertions#Assert::exception]: +En nuestro caso, un valor negativo debe lanzar una excepción, lo cual verificamos mediante [Assert::exception() |Assertions#Assert::exception]: ```php .{file:tests/RectangleTest.php} -// la anchura no debe ser un número negativo +// el ancho no debe ser negativo Assert::exception( fn() => new Rectangle(-1, 20), InvalidArgumentException::class, - 'La dimensión no debe ser negativa', + 'The dimension must not be negative.', ); ``` -Y añadimos una prueba similar para la altura. Por último, comprobamos que `isSquare()` devuelve `true` si ambas dimensiones son iguales. Intenta escribir estas pruebas como ejercicio. +Y agregamos una prueba similar para la altura. Finalmente, probamos que `isSquare()` devuelva `true` si ambas dimensiones son iguales. Intenta escribir tales pruebas como ejercicio. -Pruebas bien organizadas .[#toc-well-arranged-tests] -==================================================== +Pruebas más claras +================== -El tamaño del archivo de pruebas puede aumentar y saturarse rápidamente. Por lo tanto, es práctico agrupar las áreas probadas individuales en funciones separadas. +El tamaño del archivo con la prueba puede crecer y volverse rápidamente confuso. Por eso es práctico agrupar las áreas probadas individuales en funciones separadas. -En primer lugar, mostraremos una variante más sencilla pero elegante, utilizando la función global `test()`. El comprobador no la crea automáticamente, para evitar una colisión si tuvieras una función con el mismo nombre en tu código. Sólo se crea con el método `setupFunctions()`, que se llama en el archivo `bootstrap.php`: +Primero mostraremos una variante más simple, pero elegante, y es mediante la función global `test()`. Tester no la crea automáticamente para evitar colisiones si tuvieras una función con el mismo nombre en el código. La crea el método `setupFunctions()`, que debes llamar en el archivo `bootstrap.php`: ```php .{file:tests/bootstrap.php} Tester\Environment::setup(); Tester\Environment::setupFunctions(); ``` -Usando esta función, podemos dividir agradablemente el archivo de prueba en unidades con nombre. Cuando se ejecute, las etiquetas se mostrarán una tras otra. +Mediante esta función podemos dividir el archivo de prueba de forma ordenada en unidades con nombre. Al ejecutarse, se imprimirán secuencialmente las descripciones. ```php .{file:tests/RectangleTest.php} <?php @@ -143,7 +142,7 @@ use Tester\Assert; require __DIR__ . '/bootstrap.php'; -test('oblongo general', function () { +test('rectángulo general', function () { $rect = new Rectangle(10, 20); Assert::same(200.0, $rect->getArea()); Assert::false($rect->isSquare()); @@ -168,15 +167,15 @@ test('las dimensiones no deben ser negativas', function () { }); ``` -Si necesita ejecutar el código antes o después de cada prueba, páselo a `setUp()` o `tearDown()`: +Si necesitas ejecutar código antes o después de cada prueba, pásalo a la función `setUp()` o `tearDown()` respectivamente: ```php setUp(function () { - // código de inicialización a ejecutar antes de cada test() + // código de inicialización que se ejecuta antes de cada test() }); ``` -La segunda variante es objeto. Crearemos el llamado TestCase, que es una clase donde las unidades individuales están representadas por métodos cuyos nombres empiezan por test-. +La segunda variante es orientada a objetos. Creamos el llamado TestCase, que es una clase donde las unidades individuales representan métodos cuyos nombres comienzan con test–. ```php .{file:tests/RectangleTest.php} class RectangleTest extends Tester\TestCase @@ -208,25 +207,25 @@ class RectangleTest extends Tester\TestCase } } -// Run test methods +// Ejecución de los métodos de prueba (new RectangleTest)->run(); ``` -Esta vez utilizamos una anotación `@throw` para comprobar si hay excepciones. Ver el capítulo [TestCase] para más información. +Para probar las excepciones, esta vez usamos la anotación `@throw`. Aprenderás más en el [capítulo TestCase |TestCase]. -Funciones de ayuda .[#toc-helpers-functions] -============================================ +Funciones auxiliares +==================== -Nette Tester incluye varias clases y funciones que pueden facilitarle las pruebas, por ejemplo, ayudantes para probar el contenido de un documento HTML, para probar las funciones de trabajo con archivos, etc. +Nette Tester contiene varias clases y funciones que pueden facilitarte, por ejemplo, la prueba del contenido de un documento HTML, la prueba de funciones que trabajan con archivos, etc. -Puede encontrar una descripción de los mismos en la página [Ayudantes |Helpers]. +Su descripción la encontrarás en la página [Clases auxiliares|helpers]. -Anotación y omisión de pruebas .[#toc-annotation-and-skipping-tests] -==================================================================== +Anotaciones y omisión de pruebas +================================ -La ejecución de pruebas puede verse afectada por anotaciones en el comentario phpDoc al principio del archivo. Por ejemplo, podría verse así: +La ejecución de las pruebas puede verse influenciada por anotaciones en forma de comentario phpDoc al principio del archivo. Puede verse, por ejemplo, así: ```php .{file:tests/RectangleTest.php} /** @@ -235,11 +234,11 @@ La ejecución de pruebas puede verse afectada por anotaciones en el comentario p */ ``` -Las anotaciones dicen que la prueba sólo debe ejecutarse con PHP versión 7.2 o superior y si las extensiones PHP pdo y pdo_pgsql están presentes. Estas anotaciones son controladas por [el ejecutor de pruebas de la línea de comandos |running-tests], el cual, si no se cumplen las condiciones, se salta la prueba y la marca con la letra `s` - skipped. Sin embargo, no tienen ningún efecto cuando la prueba se ejecuta manualmente. +Las anotaciones indicadas dicen que la prueba debe ejecutarse solo con la versión de PHP 7.2 o superior y si están presentes las extensiones PHP pdo y pdo_pgsql. Estas anotaciones son seguidas por el [ejecutor de pruebas desde la línea de comandos|running-tests], que en caso de que las condiciones no se cumplan, omite la prueba y la marca en la salida con la letra `s` - skipped. Sin embargo, al ejecutar manualmente la prueba no tienen ningún efecto. -Para obtener una descripción de las anotaciones, consulte [Anotaciones de prueba |Test Annotations]. +La descripción de las anotaciones la encontrarás en la página [Anotaciones de pruebas|test-annotations]. -La prueba también puede omitirse en función de una condición propia con `Environment::skip()`. Por ejemplo, omitiremos esta prueba en Windows: +La prueba también se puede omitir en función del cumplimiento de una condición propia mediante `Environment::skip()`. Por ejemplo, esta omitirá las pruebas en Windows: ```php if (defined('PHP_WINDOWS_VERSION_BUILD')) { @@ -248,10 +247,10 @@ if (defined('PHP_WINDOWS_VERSION_BUILD')) { ``` -Estructura de directorios .[#toc-directory-structure] -===================================================== +Estructura de directorios +========================= -En el caso de bibliotecas o proyectos un poco más grandes, recomendamos dividir el directorio de prueba en subdirectorios según el espacio de nombres de la clase probada: +Recomendamos que en librerías o proyectos un poco más grandes se divida el directorio con las pruebas en subdirectorios según el espacio de nombres de la clase probada: ``` └── tests/ @@ -269,22 +268,22 @@ En el caso de bibliotecas o proyectos un poco más grandes, recomendamos dividir └── ... ``` -Podrá ejecutar pruebas desde un único espacio de nombres, es decir, subdirectorio: +Así podrás ejecutar las pruebas de un solo espacio de nombres o subdirectorio: /--pre .[terminal] tester tests/NamespaceOne \-- -Casos prácticos .[#toc-edge-cases] -================================== +Situaciones especiales +====================== -Una prueba que no llame a ningún método de aserción es sospechosa y se evaluará como errónea: +Una prueba que no llama a ningún método de aserción es sospechosa y se evaluará como errónea: /--pre .[terminal] <span style="color: #FFF; background-color: #900">Error: This test forgets to execute an assertion.</span> \-- -Si la prueba sin llamar a aserciones debe considerarse realmente válida, llame por ejemplo a `Assert::true(true)`. +Si realmente se considera válida una prueba sin llamadas a aserciones, llama por ejemplo a `Assert::true(true)`. -También puede ser traicionero utilizar `exit()` y `die()` para finalizar la prueba con un mensaje de error. Por ejemplo, `exit('Error in connection')` finaliza la prueba con un código de salida 0, que indica éxito. Utilice `Assert::fail('Error in connection')`. +También puede ser engañoso usar `exit()` y `die()` para terminar la prueba con un mensaje de error. Por ejemplo, `exit('Error in connection')` termina la prueba con el código de retorno 0, lo que indica éxito. Usa `Assert::fail('Error in connection')`. diff --git a/tester/files/coverage.html b/tester/files/coverage.html new file mode 100644 index 0000000000..0264ce2b84 --- /dev/null +++ b/tester/files/coverage.html @@ -0,0 +1,525 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <meta name="robots" content="noindex,noarchive"> + <meta name="generator" content="Nette Tester"> + + <title>Code coverage + + + + + + + +

                                                                                                                                        + Code coverage 52 % + sources have 6,180 lines of code in 32 files +

                                                                                                                                        + + + +
                                                                                                                                        +
                                                                                                                                        +
                                                                                                                                        + + + + + + + +
                                                                                                                                        +  % + +
                                                                                                                                        +
                                                                                                                                        + path  +
                                                                                                                                        +
                                                                                                                                        +
                                                                                                                                    + +
                                                                                                                            + + + + + + diff --git a/tester/fr/@home.texy b/tester/fr/@home.texy index 50bc5c7418..d92027bedb 100644 --- a/tester/fr/@home.texy +++ b/tester/fr/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Nette Tester - Des tests unitaires agréables en PHP}} -{{description: Nette Tester est un outil de test de code PHP simple et pourtant très pratique.}} +{{maintitle: Nette Tester – tests agréables en PHP}} +{{description: Nette Tester est un outil simple et pourtant très pratique pour tester le code PHP.}} diff --git a/tester/fr/@left-menu.texy b/tester/fr/@left-menu.texy index d0059eeecd..c17a495c6c 100644 --- a/tester/fr/@left-menu.texy +++ b/tester/fr/@left-menu.texy @@ -1,8 +1,8 @@ -- [Commencer à travailler |guide] -- [Rédaction de tests |Writing Tests] -- [Exécution des tests |Running Tests] +- [Commencer avec Nette Tester |guide] +- [Écrire des tests |writing-tests] +- [Exécuter des tests |running-tests] -- [Assertions] -- [Annotations de test |Test Annotations] -- [TestCase] -- [Aides |Helpers] +- [Assertions |assertions] +- [Annotations de test |test-annotations] +- [TestCase |TestCase] +- [Classes d'aide |helpers] diff --git a/tester/fr/@menu.texy b/tester/fr/@menu.texy index cffbef7c09..4829ca52f4 100644 --- a/tester/fr/@menu.texy +++ b/tester/fr/@menu.texy @@ -1,3 +1,3 @@ -- [Accueil |@home] -- [Documentation |Guide] +- [Introduction |@home] +- [Documentation |guide] - "GitHub .[link-external]":https://github.com/nette/tester diff --git a/tester/fr/@meta.texy b/tester/fr/@meta.texy new file mode 100644 index 0000000000..bf776a55f4 --- /dev/null +++ b/tester/fr/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentation Tester}} diff --git a/tester/fr/assertions.texy b/tester/fr/assertions.texy index 550cec2931..f9a4797e0b 100644 --- a/tester/fr/assertions.texy +++ b/tester/fr/assertions.texy @@ -2,34 +2,34 @@ Assertions ********** .[perex] -Les assertions sont utilisées pour affirmer qu'une valeur réelle correspond à une valeur attendue. Ce sont des méthodes de l'application `Tester\Assert`. +Les assertions sont utilisées pour confirmer qu'une valeur réelle correspond à une valeur attendue. Ce sont des méthodes de la classe `Tester\Assert`. -Choisissez les assertions les plus précises. Il est préférable d'utiliser `Assert::same($a, $b)` plutôt que `Assert::true($a === $b)` car il affiche un message d'erreur significatif en cas d'échec. Dans le deuxième cas, nous obtenons uniquement `false should be true` et il ne dit rien sur le contenu des variables $a et $b. +Choisissez les assertions les plus pertinentes. Il est préférable d'utiliser `Assert::same($a, $b)` plutôt que `Assert::true($a === $b)`, car en cas d'échec, elle affiche un message d'erreur significatif. Dans le second cas, seulement `false should be true` ce qui ne nous dit rien sur le contenu des variables `$a` et `$b`. -La plupart des assertions peuvent également avoir une option `$description` qui apparaît dans le message d'erreur si l'attente échoue. +La plupart des assertions peuvent également avoir une description facultative dans le paramètre `$description`, qui sera affichée dans le message d'erreur si l'attente échoue. -Les exemples supposent que l'alias de classe suivant est défini : +Les exemples supposent la création d'un alias : ```php use Tester\Assert; ``` -Assert::same($expected, $actual, string $description=null) .[method] --------------------------------------------------------------------- -`$expected` doit être le même que `$actual`. Il est identique à l'opérateur PHP `===`. +Assert::same($expected, $actual, ?string $description=null) .[method] +--------------------------------------------------------------------- +`$expected` doit être identique à `$actual`. C'est la même chose que l'opérateur PHP `===`. -Assert::notSame($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------ -Opposé à `Assert::same()`, il est donc identique à l'opérateur PHP `!==`. +Assert::notSame($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------ +L'opposé de `Assert::same()`, donc la même chose que l'opérateur PHP `!==`. -Assert::equal($expected, $actual, string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] ------------------------------------------------------------------------------------------------------------------------- -`$expected` doit être le même que `$actual`. Contrairement à `Assert::same()`, l'identité de l'objet, l'ordre des paires de clés => valeur dans les tableaux, et les nombres décimaux légèrement différents sont ignorés, ce qui peut être modifié en paramétrant `$matchIdentity` et `$matchOrder`. +Assert::equal($expected, $actual, ?string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] +------------------------------------------------------------------------------------------------------------------------- +`$expected` doit être égal à `$actual`. Contrairement à `Assert::same()`, l'identité des objets, l'ordre des paires clé => valeur dans les tableaux et les nombres décimaux légèrement différents sont ignorés, ce qui peut être modifié en définissant `$matchIdentity` et `$matchOrder`. -Les cas suivants sont identiques du point de vue de `equal()`, mais pas pour `same()`: +Les cas suivants sont considérés comme identiques par `equal()`, mais pas par `same()` : ```php Assert::equal(0.3, 0.1 + 0.2); @@ -40,81 +40,81 @@ Assert::equal( ); ``` -Cependant, attention, le tableau `[1, 2]` et `[2, 1]` ne sont pas égaux, car seul l'ordre des valeurs diffère, pas les paires clé => valeur. Le tableau `[1, 2]` peut aussi être écrit comme `[0 => 1, 1 => 2]` et donc `[1 => 2, 0 => 1]` seront considérés comme égaux. +Attention cependant, les tableaux `[1, 2]` et `[2, 1]` ne sont pas considérés comme identiques, car seul l'ordre des valeurs diffère, pas celui des paires clé => valeur. Le tableau `[1, 2]` peut aussi s'écrire `[0 => 1, 1 => 2]` et sera donc considéré comme identique à `[1 => 2, 0 => 1]`. -Vous pouvez également utiliser ce que l'on appelle les [attentes |#expectations] dans `$expected`. +De plus, dans `$expected`, on peut utiliser ce que l'on appelle des [#attentes]. -Assert::notEqual($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------- -A l'opposé de `Assert::equal()`. +Assert::notEqual($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------- +L'opposé de `Assert::equal()`. -Assert::contains($needle, string|array $actual, string $description=null) .[method] ------------------------------------------------------------------------------------ -Si `$actual` est une chaîne de caractères, elle doit contenir la sous-chaîne `$needle`. Si c'est un tableau, il doit contenir l'élément `$needle` (il est comparé strictement). +Assert::contains($needle, string|array $actual, ?string $description=null) .[method] +------------------------------------------------------------------------------------ +Si `$actual` est une chaîne de caractères, elle doit contenir la sous-chaîne `$needle`. Si c'est un tableau, il doit contenir l'élément `$needle` (comparaison stricte). -Assert::notContains($needle, string|array $actual, string $description=null) .[method] --------------------------------------------------------------------------------------- -Opposé à `Assert::contains()`. +Assert::notContains($needle, string|array $actual, ?string $description=null) .[method] +--------------------------------------------------------------------------------------- +L'opposé de `Assert::contains()`. -Assert::hasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} -------------------------------------------------------------------------------------------------------- +Assert::hasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +-------------------------------------------------------------------------------------------------------- `$actual` doit être un tableau et doit contenir la clé `$needle`. -Assert::notHasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} ----------------------------------------------------------------------------------------------------------- +Assert::notHasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +----------------------------------------------------------------------------------------------------------- `$actual` doit être un tableau et ne doit pas contenir la clé `$needle`. -Assert::true($value, string $description=null) .[method] --------------------------------------------------------- -`$value` doit être `true`, donc `$value === true`. +Assert::true($value, ?string $description=null) .[method] +--------------------------------------------------------- +`$value` doit être `true`, c'est-à-dire `$value === true`. -Assert::truthy($value, string $description=null) .[method] ----------------------------------------------------------- -`$value` doit être véridique, donc il satisfait la condition `if ($value) ...`. +Assert::truthy($value, ?string $description=null) .[method] +----------------------------------------------------------- +`$value` doit être vrai (truthy), c'est-à-dire qu'elle satisfait la condition `if ($value) ...`. -Assert::false($value, string $description=null) .[method] ---------------------------------------------------------- -`$value` doit être `false`, donc `$value === false`. +Assert::false($value, ?string $description=null) .[method] +---------------------------------------------------------- +`$value` doit être `false`, c'est-à-dire `$value === false`. -Assert::falsey($value, string $description=null) .[method] ----------------------------------------------------------- -`$value` doit être faux, donc il remplit la condition `if (!$value) ...`. +Assert::falsey($value, ?string $description=null) .[method] +----------------------------------------------------------- +`$value` doit être faux (falsey), c'est-à-dire qu'elle satisfait la condition `if (!$value) ...`. -Assert::null($value, string $description=null) .[method] --------------------------------------------------------- -`$value` doit être `null`, donc `$value === null`. +Assert::null($value, ?string $description=null) .[method] +--------------------------------------------------------- +`$value` doit être `null`, c'est-à-dire `$value === null`. -Assert::notNull($value, string $description=null) .[method] ------------------------------------------------------------ -`$value` ne doit pas être `null`, donc `$value !== null`. +Assert::notNull($value, ?string $description=null) .[method] +------------------------------------------------------------ +`$value` ne doit pas être `null`, c'est-à-dire `$value !== null`. -Assert::nan($value, string $description=null) .[method] -------------------------------------------------------- -`$value` doit être Not a Number. Utilisez uniquement le site `Assert::nan()` pour les tests NAN. La valeur NAN est très spécifique et les assertions `Assert::same()` ou `Assert::equal()` peuvent se comporter de manière imprévisible. +Assert::nan($value, ?string $description=null) .[method] +-------------------------------------------------------- +`$value` doit être Not a Number. Pour tester la valeur NAN, utilisez exclusivement `Assert::nan()`. La valeur NAN est très spécifique et les assertions `Assert::same()` ou `Assert::equal()` peuvent fonctionner de manière inattendue. -Assert::count($count, Countable|array $value, string $description=null) .[method] ---------------------------------------------------------------------------------- -Le nombre d'éléments dans `$value` doit être `$count`. Donc identique à `count($value) === $count`. +Assert::count($count, Countable|array $value, ?string $description=null) .[method] +---------------------------------------------------------------------------------- +Le nombre d'éléments dans `$value` doit être `$count`. C'est donc la même chose que `count($value) === $count`. -Assert::type(string|object $type, $value, string $description=null) .[method] ------------------------------------------------------------------------------ -`$value` doit être d'un type donné. Comme `$type` nous pouvons utiliser une chaîne de caractères : +Assert::type(string|object $type, $value, ?string $description=null) .[method] +------------------------------------------------------------------------------ +`$value` doit être du type donné. Comme `$type`, nous pouvons utiliser une chaîne : - `array` -- `list` - tableau indexé dans l'ordre croissant des clés numériques à partir de zéro. +- `list` - tableau indexé par une série ascendante de clés numériques à partir de zéro - `bool` - `callable` - `float` @@ -124,24 +124,24 @@ Assert::type(string|object $type, $value, string $description=null) .[method] - `resource` - `scalar` - `string` -- nom de la classe ou de l'objet directement, alors il faut passer `$value instanceof $type` +- nom de classe ou directement un objet, alors `$value instanceof $type` doit être vrai -Assert::exception(callable $callable, string $class, string $message=null, $code=null) .[method] ------------------------------------------------------------------------------------------------- -Lors de l'invocation de `$callable`, une exception de l'instance `$class` doit être levée. Si nous passons `$message`, le message de l'exception doit [correspondre |#assert-match]. Et si nous passons `$code`, le code de l'exception doit être le même. +Assert::exception(callable $callable, string $class, ?string $message=null, $code=null) .[method] +------------------------------------------------------------------------------------------------- +Lors de l'appel de `$callable`, une exception de la classe `$class` doit être levée. Si nous spécifions `$message`, le message de l'exception doit également [correspondre au modèle |#Assert::match] et si nous spécifions `$code`, les codes doivent également correspondre strictement. -Par exemple, ce test échoue car le message de l'exception ne correspond pas : +Le test suivant échouera car le message de l'exception ne correspond pas : ```php Assert::exception( fn() => throw new App\InvalidValueException('Zero value'), App\InvalidValueException::class, - 'Value is to low', + 'Value is too low', ); ``` -Le site `Assert::exception()` renvoie une exception levée, vous pouvez donc tester une exception imbriquée. +`Assert::exception()` retourne l'exception levée, ce qui permet de tester également une exception imbriquée. ```php $e = Assert::exception( @@ -154,9 +154,9 @@ Assert::type(RuntimeException::class, $e->getPrevious()); ``` -Assert::error(string $callable, int|string|array $type, string $message=null) .[method] ---------------------------------------------------------------------------------------- -Vérifie que l'invocation de `$callable` génère les erreurs attendues (c'est-à-dire les avertissements, les avis, etc.). Comme `$type` nous spécifions une des constantes `E_...`, par exemple `E_WARNING`. Et si on passe `$message`, le message d'erreur doit également [correspondre au |#assert-match] modèle. Par exemple : +Assert::error(string $callable, int|string|array $type, ?string $message=null) .[method] +---------------------------------------------------------------------------------------- +Vérifie que l'appelable `$callable` a généré les erreurs attendues (c.-à-d. avertissements, notices, etc.). Comme `$type`, nous indiquons l'une des constantes `E_...`, par exemple `E_WARNING`. Et si nous indiquons `$message`, le message d'erreur doit également [correspondre au modèle |#Assert::match]. Par exemple : ```php Assert::error( @@ -166,7 +166,7 @@ Assert::error( ); ``` -Si le callback génère plus d'erreurs, nous devons les attendre toutes dans l'ordre exact. Dans ce cas, nous passons le tableau à `$type`: +Si l'appelable génère plusieurs erreurs, nous devons toutes les attendre dans l'ordre exact. Dans ce cas, nous passons un tableau dans `$type` : ```php Assert::error(function () { @@ -179,108 +179,108 @@ Assert::error(function () { ``` .[note] -Si `$type` est le nom de la classe, cette assertion se comporte de la même manière que `Assert::exception()`. +Si vous indiquez un nom de classe comme `$type`, elle se comporte comme `Assert::exception()`. Assert::noError(callable $callable) .[method] --------------------------------------------- -Vérifie que la fonction `$callable` ne lève aucun avertissement/notice/erreur ou exception PHP. Elle est utile pour tester un morceau de code où il n'y a pas d'autre assertion. +Vérifie que l'appelable `$callable` n'a généré aucun avertissement, erreur ou exception. Utile pour tester des fragments de code où aucune autre assertion n'est présente. -Assert::match(string $pattern, $actual, string $description=null) .[method] ---------------------------------------------------------------------------- -`$actual` doit correspondre à `$pattern`. Nous pouvons utiliser deux variantes de motifs : les expressions régulières ou les caractères génériques. +Assert::match(string $pattern, $actual, ?string $description=null) .[method] +---------------------------------------------------------------------------- +`$actual` doit correspondre au pattern `$pattern`. Nous pouvons utiliser deux variantes de patterns : les expressions régulières ou les caractères génériques. -Si nous transmettons une expression régulière à `$pattern`, nous devons utiliser `~` or `#` pour la délimiter. Les autres délimiteurs ne sont pas pris en charge. Par exemple, test où `$var` ne doit contenir que des chiffres hexadécimaux : +Si nous passons une expression régulière comme `$pattern`, nous devons utiliser `~` ou `#` pour la délimiter, les autres délimiteurs ne sont pas pris en charge. Par exemple, un test où `$var` ne doit contenir que des chiffres hexadécimaux : ```php Assert::match('#^[0-9a-f]$#i', $var); ``` -L'autre variante est similaire à la comparaison de chaînes de caractères, mais nous pouvons utiliser certains caractères génériques dans `$pattern`: +La seconde variante est similaire à la comparaison de chaînes classique, mais dans `$pattern`, nous pouvons utiliser divers caractères génériques : -- `%a%` un ou plusieurs caractères, à l'exception des caractères de fin de ligne -- `%a?%` zéro ou plus de tout sauf les caractères de fin de ligne +- `%a%` un ou plusieurs caractères, sauf les caractères de fin de ligne +- `%a?%` zéro ou plusieurs caractères, sauf les caractères de fin de ligne - `%A%` un ou plusieurs caractères, y compris les caractères de fin de ligne -- `%A?%` zéro ou plus de tout, y compris les caractères de fin de ligne -- `%s%` un ou plusieurs caractères d'espace blanc, à l'exception des caractères de fin de ligne -- `%s?%` zéro ou plusieurs caractères d'espace blanc, à l'exception des caractères de fin de ligne -- `%S%` un ou plusieurs caractères sauf l'espace blanc -- `%S?%` zéro ou plus de caractères sauf l'espace blanc -- `%c%` un seul caractère de n'importe quelle sorte (sauf les caractères de fin de ligne) +- `%A?%` zéro ou plusieurs caractères, y compris les caractères de fin de ligne +- `%s%` un ou plusieurs espaces blancs, sauf les caractères de fin de ligne +- `%s?%` zéro ou plusieurs espaces blancs, sauf les caractères de fin de ligne +- `%S%` un ou plusieurs caractères, sauf les espaces blancs +- `%S?%` zéro ou plusieurs caractères, sauf les espaces blancs +- `%c%` n'importe quel caractère unique, sauf le caractère de fin de ligne - `%d%` un ou plusieurs chiffres - `%d?%` zéro ou plusieurs chiffres - `%i%` valeur entière signée -- `%f%` un nombre à virgule flottante -- `%h%` un ou plusieurs chiffres HEX +- `%f%` nombre à virgule flottante +- `%h%` un ou plusieurs chiffres hexadécimaux - `%w%` un ou plusieurs caractères alphanumériques -- `%%` un caractère %. +- `%%` le caractère % Exemples : ```php -# Again, hexadecimal number test +# Encore un test pour un nombre hexadécimal Assert::match('%h%', $var); -# Generalized path to file and line number +# Généralisation du chemin du fichier et du numéro de ligne Assert::match('Error in file %a% on line %i%', $errorMessage); ``` -Assert::matchFile(string $file, $actual, string $description=null) .[method] ----------------------------------------------------------------------------- -L'assertion est identique à [Assert::match() |#assert-match] mais le motif est chargé depuis `$file`. Elle est utile pour tester des chaînes très longues. Le fichier de test est lisible. +Assert::matchFile(string $file, $actual, ?string $description=null) .[method] +----------------------------------------------------------------------------- +Cette assertion est identique à [#Assert::match()], mais le pattern est chargé depuis le fichier `$file`. C'est utile pour tester des chaînes très longues. Le fichier de test reste clair. Assert::fail(string $message, $actual=null, $expected=null) .[method] --------------------------------------------------------------------- -Cette assertion échoue toujours. Elle est simplement pratique. Nous pouvons optionnellement passer les valeurs attendues et réelles. +Cette assertion échoue systématiquement. C'est parfois utile. Facultativement, nous pouvons également indiquer la valeur attendue et la valeur réelle. -Attentes .[#toc-expectations] ------------------------------ -Si nous voulons comparer des structures plus complexes avec des éléments non constants, les assertions ci-dessus peuvent ne pas être suffisantes. Par exemple, nous testons une méthode qui crée un nouvel utilisateur et renvoie ses attributs sous forme de tableau. Nous ne connaissons pas la valeur de hachage du mot de passe, mais nous savons qu'il doit être une chaîne hexadécimale. Et la seule chose que nous savons de l'élément suivant est qu'il doit être un objet `DateTime`. +Attentes +-------- +Lorsque nous voulons comparer des structures plus complexes contenant des éléments non constants, les assertions ci-dessus peuvent ne pas être suffisantes. Par exemple, nous testons une méthode qui crée un nouvel utilisateur et renvoie ses attributs sous forme de tableau. Nous ne connaissons pas la valeur du hash du mot de passe, mais nous savons qu'il doit s'agir d'une chaîne hexadécimale. Et pour un autre élément, nous savons seulement qu'il doit s'agir d'un objet `DateTime`. -Dans ces cas, nous pouvons utiliser le paramètre `Tester\Expect` à l'intérieur du paramètre `$expected` des méthodes `Assert::equal()` et `Assert::notEqual()`, qui peuvent être utilisées pour décrire facilement la structure. +Dans ces situations, nous pouvons utiliser `Tester\Expect` à l'intérieur du paramètre `$expected` des méthodes `Assert::equal()` et `Assert::notEqual()`, avec lesquelles la structure peut être facilement décrite. ```php use Tester\Expect; Assert::equal([ - 'id' => Expect::type('int'), # we expect an integer + 'id' => Expect::type('int'), # nous attendons un entier 'username' => 'milo', - 'password' => Expect::match('%h%'), # we expect a string matching pattern - 'created_at' => Expect::type(DateTime::class), # we expect an instance of the class + 'password' => Expect::match('%h%'), # nous attendons une chaîne correspondant au pattern + 'created_at' => Expect::type(DateTime::class), # nous attendons une instance de la classe ], User::create(123, 'milo', 'RandomPaSsWoRd')); ``` -Avec `Expect`, nous pouvons faire presque les mêmes affirmations qu'avec `Assert`. Nous avons donc des méthodes comme `Expect::same()`, `Expect::match()`, `Expect::count()`, etc. De plus, nous pouvons les enchaîner comme suit : +Avec `Expect`, nous pouvons effectuer quasiment les mêmes assertions qu'avec `Assert`. Ainsi, les méthodes `Expect::same()`, `Expect::match()`, `Expect::count()`, etc. sont à notre disposition. De plus, nous pouvons les chaîner : ```php -Expect::type(MyIterator::class)->andCount(5); # we expect MyIterator and items count is 5 +Expect::type(MyIterator::class)->andCount(5); # nous attendons MyIterator et un nombre d'éléments de 5 ``` Ou bien, nous pouvons écrire nos propres gestionnaires d'assertions. ```php Expect::that(function ($value) { - # return false if expectation fails + # nous retournons false si l'attente échoue }); ``` -Enquête sur les assertions échouées .[#toc-failed-assertions-investigation] ---------------------------------------------------------------------------- -Le testeur montre où se trouve l'erreur lorsqu'une assertion échoue. Lorsque nous comparons des structures complexes, le testeur crée des dumps des valeurs comparées et les enregistre dans le répertoire `output`. Par exemple, lorsque le test imaginaire `Arrays.recursive.phpt` échoue, les vidages seront enregistrés comme suit : +Examen des assertions erronées +------------------------------ +Lorsqu'une assertion échoue, Tester indique où se situe l'erreur. Si nous comparons des structures plus complexes, Tester crée des dumps des valeurs comparées et les enregistre dans le répertoire `output`. Par exemple, en cas d'échec du test fictif `Arrays.recursive.phpt`, les dumps seront enregistrés comme suit : ``` app/ └── tests/ ├── output/ - │ ├── Arrays.recursive.actual # actual value - │ └── Arrays.recursive.expected # expected value + │ ├── Arrays.recursive.actual # valeur actuelle + │ └── Arrays.recursive.expected # valeur attendue │ - └── Arrays.recursive.phpt # failing test + └── Arrays.recursive.phpt # test échouant ``` -Nous pouvons changer le nom du répertoire par `Tester\Dumper::$dumpDir`. +Le nom du répertoire peut être modifié via `Tester\Dumper::$dumpDir`. diff --git a/tester/fr/guide.texy b/tester/fr/guide.texy index 97457945fa..a1dfae8b35 100644 --- a/tester/fr/guide.texy +++ b/tester/fr/guide.texy @@ -1,17 +1,17 @@ -Démarrer avec Tester -******************** +Commencer avec Nette Tester +***************************
                                                                                                                            -Même les bons programmeurs font des erreurs. La différence entre un bon programmeur et un mauvais programmeur est que le bon programmeur ne le fera qu'une fois et le détectera la fois suivante à l'aide de tests automatisés. +Même les bons programmeurs font des erreurs. La différence entre un bon et un mauvais programmeur est que le bon ne la fait qu'une fois et la prochaine fois, il la détecte à l'aide de tests automatisés. -- "Celui qui ne teste pas est condamné à répéter ses propres erreurs". (sage proverbe) -- "Quand on se débarrasse d'une erreur, une autre apparaît". (Loi de Murphy) -- "Chaque fois que vous êtes tenté d'imprimer une déclaration, écrivez-la plutôt comme un test." (Martin Fowler) +- "Celui qui ne teste pas est condamné à répéter ses erreurs." (proverbe) +- "Dès qu'on se débarrasse d'une erreur, une autre apparaît." (Loi de Murphy) +- "Chaque fois que vous avez envie d'afficher une variable à l'écran, écrivez plutôt un test." (Martin Fowler)
                                                                                                                            -Avez-vous déjà écrit le code suivant en PHP ? +Avez-vous déjà écrit un code similaire en PHP ? ```php $obj = new MyClass; @@ -20,40 +20,40 @@ $result = $obj->process($input); var_dump($result); ``` -Avez-vous déjà jeté le résultat d'un appel de fonction juste pour vérifier à l'œil qu'il renvoie ce qu'il doit renvoyer ? Vous le faites sûrement plusieurs fois par jour. La main sur le cœur, si tout fonctionne, supprimez-vous ce code en espérant que la classe ne sera pas cassée à l'avenir ? La loi de Murphy vous garantit le contraire :-) +C'est-à-dire, avez-vous affiché le résultat de l'appel de fonction juste pour vérifier visuellement s'il renvoie ce qu'il devrait ? Vous le faites certainement plusieurs fois par jour. Main sur le cœur : dans le cas où tout fonctionne correctement, supprimez-vous ce code ? Vous attendez-vous à ce que la classe ne se casse pas à l'avenir ? Les lois de Murphy garantissent le contraire :-) -En fait, c'est vous qui avez écrit le test. Il a besoin d'une légère modification pour ne pas nécessiter notre inspection, simplement pour être capable de se vérifier lui-même. Et si vous ne l'avez pas supprimé, nous pourrons l'exécuter à tout moment à l'avenir pour vérifier que tout fonctionne toujours comme il se doit. Il se peut que vous créiez un grand nombre de ces tests au fil du temps, il serait donc agréable que nous puissions les exécuter automatiquement. +Fondamentalement, vous avez écrit un test. Il suffit de le modifier légèrement pour qu'il ne nécessite pas de contrôle visuel, mais qu'il puisse se vérifier lui-même. Et si vous ne supprimez pas le test, vous pouvez l'exécuter à tout moment à l'avenir et vérifier que tout fonctionne toujours comme prévu. Avec le temps, vous créerez un grand nombre de ces tests, il serait donc utile de les exécuter automatiquement. -Et Nette Tester aide exactement à cela. +Et c'est précisément là que Nette Tester intervient. -Qu'est-ce qui rend Tester unique ? .[#toc-what-makes-tester-unique] -=================================================================== +Qu'est-ce qui rend Tester unique ? +================================== -L'écriture de tests pour Nette Tester est unique en ce sens que **chaque test est un script PHP standard qui peut être exécuté de manière autonome.** +L'écriture de tests pour Nette Tester est unique en ce sens que **chaque test est un script PHP ordinaire qui peut être exécuté de manière autonome.** -Ainsi, lorsque vous écrivez un test, vous pouvez simplement l'exécuter pour voir s'il y a une erreur de programmation. S'il fonctionne correctement. Dans le cas contraire, vous pouvez facilement parcourir le programme dans votre IDE et rechercher un bug. Vous pouvez même l'ouvrir dans un navigateur. +Ainsi, lorsque vous écrivez un test, vous pouvez simplement l'exécuter et vérifier s'il contient, par exemple, une erreur de programmation. S'il fonctionne correctement. Sinon, vous pouvez facilement le déboguer pas à pas dans votre IDE pour rechercher l'erreur. Vous pouvez même l'ouvrir dans un navigateur. -Et surtout, en l'exécutant, vous effectuerez le test. Vous saurez immédiatement s'il a réussi ou échoué. Comment ? Montrons-nous ici. Écrivons un test trivial pour utiliser un tableau PHP et sauvegardons-le dans le fichier `ArrayTest.php`: +Et surtout, en l'exécutant, vous réalisez le test. Vous savez immédiatement s'il a réussi ou échoué. Comment ? Montrons-le. Écrivons un test trivial de manipulation de tableau PHP et enregistrons-le dans le fichier `ArrayTest.php` : ```php .{file:ArrayTest.php} OK \-- -Essayez de changer l'instruction en `Assert::contains('XXX', $stack);` dans le test et regardez ce qui se passe lors de l'exécution : +Essayez de modifier l'assertion dans le test en une affirmation fausse `Assert::contains('XXX', $stack);` et observez ce qui se passe lors de l'exécution : /--pre .[terminal] $ php ArrayTest.php @@ -73,53 +73,53 @@ $ php ArrayTest.php FAILURE \-- -Nous continuons sur l'écriture dans le chapitre sur l'[écriture de tests |Writing Tests]. +Pour en savoir plus sur l'écriture des tests, consultez le chapitre [Écriture des tests|writing-tests]. -Installation et configuration requise .[#toc-installation-and-requirements] -=========================================================================== +Installation et prérequis +========================= -La version minimale de PHP requise par Tester est 7.1 (pour plus de détails, voir le tableau des [versions de PHP prises en charge |#supported PHP versions] ). La méthode d'installation préférée est celle de [Composer |best-practices:composer]: +La version minimale de PHP requise par Tester est 7.1 (détaillée dans le tableau [#versions PHP supportées]). La méthode d'installation préférée est via [Composer |best-practices:composer] : /--pre .[terminal] composer require --dev nette/tester \-- -Essayez de lancer le Nette Tester à partir de la ligne de commande (sans aucun argument, il n'affichera qu'un résumé de l'aide) : +Essayez d'exécuter Nette Tester depuis la ligne de commande (sans paramètres, il affiche simplement l'aide) : /--pre .[terminal] vendor/bin/tester \-- -Exécution des tests .[#toc-running-tests] -========================================= +Exécution des tests +=================== -À mesure que notre application se développe, le nombre de tests augmente avec elle. Il ne serait pas pratique d'exécuter les tests un par un. C'est pourquoi le Testeur dispose d'un exécuteur de tests en masse, que nous invoquons depuis la ligne de commande. Le paramètre est le répertoire dans lequel se trouvent les tests. Le point indique le répertoire courant. +À mesure que l'application grandit, le nombre de tests augmente également. Il ne serait pas pratique d'exécuter les tests un par un. C'est pourquoi Tester fournit un lanceur de tests en masse, que nous appelons depuis la ligne de commande. Comme paramètre, nous indiquons le répertoire dans lequel se trouvent les tests. Le point signifie le répertoire actuel. /--pre .[terminal] vendor/bin/tester . \-- -Le programme d'exécution de Nette Tester recherche le répertoire spécifié et tous les sous-répertoires et recherche les tests, qui sont les fichiers `*.phpt` et `*Test.php`. Il trouvera également notre test `ArrayTest.php`, car il correspond au masque. +Le lanceur de tests parcourt le répertoire spécifié et tous les sous-répertoires et recherche les tests, qui sont les fichiers `*.phpt` et `*Test.php`. Il trouve ainsi également notre test `ArrayTest.php`, car il correspond au pattern. -Puis il commence les tests. Il exécute chaque test comme un nouveau processus PHP, de sorte qu'il est complètement isolé des autres. Il s'exécute en parallèle dans plusieurs threads, ce qui le rend extrêmement rapide. Il exécute d'abord les tests qui ont échoué lors de l'exécution précédente, ce qui vous permet de savoir immédiatement si vous avez corrigé l'erreur. +Ensuite, il démarre les tests. Chaque test est exécuté comme un nouveau processus PHP, il se déroule donc de manière totalement isolée des autres. Il les exécute en parallèle dans plusieurs threads et est donc extrêmement rapide. Et il exécute d'abord les tests qui ont échoué lors de l'exécution précédente, de sorte que vous savez immédiatement si vous avez réussi à corriger l'erreur. -Pour chaque test effectué, le runner imprime un caractère pour indiquer la progression : +Pendant l'exécution des tests, Tester affiche continuellement les résultats sur le terminal sous forme de caractères : -- . - test réussi -- s - le test a été ignoré -- F - le test a échoué +- . – le test a réussi +- s – le test a été sauté (skipped) +- F – le test a échoué (failed) La sortie peut ressembler à ceci : /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +PHP 8.3.2 (cli) | php -n | 8 threads ........s................F......... @@ -132,44 +132,44 @@ PHP 7.4.8 (cli) | php -n | 8 threads FAILURES! (35 tests, 1 failures, 1 skipped, 1.7 seconds) \-- -35 tests ont été exécutés, un a échoué, un a été ignoré. +35 tests ont été exécutés, 1 a échoué, 1 a été sauté. -Nous continuons dans le chapitre [Exécution des tests |Running tests]. +Nous continuons ensuite dans le chapitre [Exécution des tests|running-tests]. -Mode veille .[#toc-watch-mode] -============================== +Mode Watch +========== -Est-ce que vous refactorez le code ? Ou encore, vous développez selon la méthodologie TDD (Test Driven Development) ? Alors vous aimerez le mode veille. Le testeur surveille les codes sources et s'exécute lui-même en cas de modification. +Vous refactorisez du code ? Ou développez-vous même selon la méthodologie TDD (Test Driven Development) ? Alors vous aimerez le mode watch. Dans ce mode, Tester surveille les fichiers sources et se lance automatiquement en cas de modification. -Pendant le développement, vous avez un terminal dans le coin du moniteur, où la barre d'état verte s'allume sur vous, et quand elle devient soudainement rouge, vous savez que vous venez de faire quelque chose d'indésirable. C'est en fait un grand jeu où vous programmez et essayez de respecter la couleur. +Pendant le développement, vous avez donc dans un coin de votre moniteur un terminal affichant une barre d'état verte, et quand elle passe soudainement au rouge, vous savez que vous venez de faire quelque chose pas tout à fait bien. C'est en fait un jeu amusant où vous programmez en essayant de maintenir la couleur verte. -Le mode veille est lancé à l'aide du paramètre [--watch |running-tests#w-watch-path]. +Le mode Watch est lancé avec le paramètre [--watch |running-tests#-w --watch path]. -Rapports de CodeCoverage .[#toc-codecoverage-reports] -===================================================== +Rapports CodeCoverage +===================== -Le testeur peut générer des rapports donnant un aperçu de la quantité de code source couverte par les tests. Le rapport peut être soit au format HTML lisible par l'homme, soit au format Clover XML pour un traitement automatique ultérieur. +Tester peut générer des rapports donnant un aperçu de la quantité de code source couverte par les tests. Le rapport peut être soit au format HTML lisible par l'homme, soit en XML Clover pour un traitement machine ultérieur. -Voir l '"exemple de rapport HTML":https://files.nette.org/tester/coverage.html avec la couverture du code. +Consultez l'[exemple de rapport HTML |attachment:coverage.html] avec la couverture de code. -Versions PHP prises en charge .[#toc-supported-php-versions] -============================================================ +Versions PHP supportées +======================= -| version | compatible avec PHP +| version | compatible avec PHP |------------------|------------------- -| Tester 2.5 | PHP 8.0 - 8.2 -| Tester 2.4 | PHP 7.2 - 8.2 -| Tester 2.3 | PHP 7.1 - 8.0 -| Tester 2.1 - 2.2 | PHP 7.1 - 7.3 -| Tester 2.0 | PHP 5.6 - 7.3 -| Tester 1.7 | PHP 5.3 - 7.3 + HHVM 3.3+. -| Tester 1.6 | PHP 5.3 - 7.0 + HHVM -| Tester 1.3 - 1.5 | PHP 5.3 - 5.6 + HHVM -| Tester 0.9 - 1.2 | PHP 5.3 - 5.6 - -S'applique aux dernières versions des correctifs. - -Jusqu'à la version 1.7, Tester supportait [HHVM |https://hhvm.com] 3.3.0 ou plus récent (en utilisant `tester -p hhvm`). Le support a été abandonné depuis Tester 2.0. L'utilisation était simple : +| Tester 2.5 | PHP 8.0 – 8.3 +| Tester 2.4 | PHP 7.2 – 8.2 +| Tester 2.3 | PHP 7.1 – 8.0 +| Tester 2.1 – 2.2 | PHP 7.1 – 7.3 +| Tester 2.0 | PHP 5.6 – 7.3 +| Tester 1.7 | PHP 5.3 – 7.3 + HHVM 3.3+ +| Tester 1.6 | PHP 5.3 – 7.0 + HHVM +| Tester 1.3 – 1.5 | PHP 5.3 – 5.6 + HHVM +| Tester 0.9 – 1.2 | PHP 5.3 – 5.6 + +Valable pour la dernière version patch. + +Jusqu'à la version 1.7, Tester supportait également [HHVM |https://hhvm.com] 3.3.0 ou supérieur (via `tester -p hhvm`). Le support a été interrompu à partir de la version Tester 2.0. diff --git a/tester/fr/helpers.texy b/tester/fr/helpers.texy index 3e0ea8efe8..b1915178fd 100644 --- a/tester/fr/helpers.texy +++ b/tester/fr/helpers.texy @@ -1,31 +1,45 @@ -Aides -***** +Classes d'aide +************** DomQuery -------- -`Tester\DomQuery` est une classe qui étend `SimpleXMLElement` avec des méthodes qui facilitent le test du contenu HTML ou XML. +`Tester\DomQuery` est une classe qui étend `SimpleXMLElement` et facilite la recherche dans du HTML ou XML à l'aide de sélecteurs CSS. ```php -# let's have an HTML document in $html that we load -$dom = Tester\DomQuery::fromHtml($html); - -# we can test the presence of elements using CSS selectors -Assert::true($dom->has('form#registration')); -Assert::true($dom->has('input[name="username"]')); -Assert::true($dom->has('input[type="submit"]')); - -# or select elements as array of DomQuery -$elems = $dom->find('input[data-autocomplete]'); +# création de DomQuery à partir d'une chaîne HTML +$dom = Tester\DomQuery::fromHtml(' +
                                                                                                                            +

                                                                                                                            Titre

                                                                                                                            +
                                                                                                                            Texte
                                                                                                                            +
                                                                                                                            +'); + +# test d'existence d'éléments à l'aide de sélecteurs CSS +Assert::true($dom->has('article.post')); +Assert::true($dom->has('h1')); + +# recherche d'éléments sous forme de tableau d'objets DomQuery +$headings = $dom->find('h1'); +Assert::same('Titre', (string) $headings[0]); + +# test si l'élément correspond au sélecteur (depuis la version 2.5.3) +$content = $dom->find('.content')[0]; +Assert::true($content->matches('div')); +Assert::false($content->matches('p')); + +# recherche de l'ancêtre le plus proche correspondant au sélecteur (depuis 2.5.5) +$article = $content->closest('.post'); +Assert::true($article->matches('article')); ``` FileMock -------- -`Tester\FileMock` émule des fichiers en mémoire pour vous aider à tester un code qui utilise des fonctions comme `fopen()`, `file_get_contents()` ou `parse_ini_file()`. Par exemple : +`Tester\FileMock` émule des fichiers en mémoire, facilitant ainsi le test de code qui utilise des fonctions comme `fopen()`, `file_get_contents()`, `parse_ini_file()`, etc. Exemple d'utilisation : ```php -# Tested class +# Classe testée class Logger { public function __construct( @@ -39,21 +53,21 @@ class Logger } } -# New empty file +# Nouveau fichier vide $file = Tester\FileMock::create(''); $logger = new Logger($file); $logger->log('Login'); $logger->log('Logout'); -# Created content testing +# Nous testons le contenu créé Assert::same("Login\nLogout\n", file_get_contents($file)); ``` Assert::with() .[filter] ------------------------ -Il ne s'agit pas d'une assertion, mais d'une aide pour tester les méthodes privées et les objets de propriété. +Ce n'est pas une assertion, mais un helper pour tester les méthodes et propriétés privées des objets. ```php class Entity @@ -65,17 +79,17 @@ class Entity $ent = new Entity; Assert::with($ent, function () { - Assert::true($this->enabled); // accessible en privé $ent->enabled + Assert::true($this->enabled); // $ent->enabled privé rendu accessible }); ``` Helpers::purge() .[filter] -------------------------- -La méthode `purge()` crée le répertoire spécifié et, s'il existe déjà, supprime tout son contenu. Elle est pratique pour la création de répertoires temporaires. Par exemple, dans `tests/bootstrap.php`: +La méthode `purge()` crée le répertoire spécifié et, s'il existe déjà, supprime tout son contenu. C'est utile pour créer un répertoire temporaire. Par exemple dans `tests/bootstrap.php` : ```php -@mkdir(__DIR__ . '/tmp'); # @ - directory may already exist +@mkdir(__DIR__ . '/tmp'); # @ - le répertoire peut déjà exister define('TempDir', __DIR__ . '/tmp/' . getmypid()); Tester\Helpers::purge(TempDir); @@ -84,25 +98,25 @@ Tester\Helpers::purge(TempDir); Environment::lock() .[filter] ----------------------------- -Les tests s'exécutent en parallèle. Parfois, nous n'avons pas besoin de faire se chevaucher l'exécution des tests. Typiquement, les tests de base de données doivent préparer le contenu de la base de données et rien ne doit les perturber pendant l'exécution du test. Dans ces cas, nous utilisons `Tester\Environment::lock($name, $dir)`: +Les tests s'exécutent en parallèle. Parfois, cependant, nous avons besoin que les exécutions de tests ne se chevauchent pas. Typiquement, pour les tests de base de données, il est nécessaire qu'un test prépare le contenu de la base de données et qu'aucun autre test n'y interfère pendant son exécution. Dans ces tests, nous utilisons `Tester\Environment::lock($name, $dir)` : ```php Tester\Environment::lock('database', __DIR__ . '/tmp'); ``` -Le premier argument est un nom de verrou. Le second est un chemin vers un répertoire pour sauvegarder le verrou. Le test qui acquiert le verrou s'exécute en premier. Les autres tests doivent attendre qu'il soit terminé. +Le premier argument est le nom du verrou, le second est le chemin vers le répertoire où stocker le verrou. Le test qui obtient le verrou en premier s'exécute ; les autres tests doivent attendre sa fin. Environment::bypassFinals() .[filter] ------------------------------------- -Les classes ou méthodes marquées comme `final` sont difficiles à tester. L'appel de `Tester\Environment::bypassFinals()` dans un début de test fait que les mots-clés `final` sont supprimés lors du chargement du code. +Les classes ou méthodes marquées comme `final` sont difficiles à tester. L'appel à `Tester\Environment::bypassFinals()` au début du test provoque l'omission des mots-clés `final` lors du chargement du code. ```php require __DIR__ . '/bootstrap.php'; Tester\Environment::bypassFinals(); -class MyClass extends NormallyFinalClass # <-- NormallyFinalClass n'est plus final +class MyClass extends NormallyFinalClass # <-- NormallyFinalClass n'est plus final { // ... } @@ -111,18 +125,18 @@ class MyClass extends NormallyFinalClass # <-- NormallyFinalClass n'est plus fin Environment::setup() .[filter] ------------------------------ -- améliore la lisibilité du dump d'erreur (coloration incluse), sinon, la trace de pile PHP par défaut est imprimée. -- permet de vérifier que les assertions ont été appelées dans le test, sinon les tests sans assertions (par exemple oubliées) passent aussi -- lance automatiquement le collecteur de couverture de code lorsque `--coverage` est utilisé (décrit plus tard) -- affiche le statut OK ou FAILURE à la fin du script. +- améliore la lisibilité des rapports d'erreurs (y compris la coloration syntaxique), sinon la trace d'appels PHP par défaut est affichée +- active la vérification que des assertions ont été appelées dans le test ; sinon, un test sans assertions (par exemple, oubliées) serait considéré comme réussi +- lors de l'utilisation de `--coverage`, démarre automatiquement la collecte d'informations sur la couverture de code (décrite plus loin) +- affiche le statut OK ou FAILURE à la fin du script Environment::setupFunctions() .[filter]{data-version:2.5} --------------------------------------------------------- -Crée les fonctions globales `test()`, `setUp()` et `tearDown()` dans lesquelles vous pouvez diviser les tests. +Crée les fonctions globales `test()`, `testException()`, `setUp()` et `tearDown()`, que vous pouvez utiliser pour structurer vos tests. ```php -test('test description', function () { +test('description du test', function () { Assert::same(123, foo()); Assert::false(bar()); // ... @@ -132,21 +146,21 @@ test('test description', function () { Environment::VariableRunner .[filter] ------------------------------------- -Permet de savoir si le test a été exécuté directement ou via le Testeur. +Permet de déterminer si le test a été lancé directement ou via Tester. ```php if (getenv(Tester\Environment::VariableRunner)) { - # run by Tester + # lancé par Tester } else { - # another way + # lancé autrement } ``` Environment::VariableThread .[filter] ------------------------------------- -Tester exécute les tests en parallèle dans un nombre donné de threads. Nous trouverons un numéro de thread dans une variable Environmentale lorsque nous serons intéressés : +Tester exécute les tests en parallèle sur un nombre de threads spécifié. Si le numéro du thread nous intéresse, nous pouvons le récupérer depuis la variable d'environnement : ```php -echo "I'm running in a thread number " . getenv(Tester\Environment::VariableThread); +echo "Je tourne dans le thread numéro " . getenv(Tester\Environment::VariableThread); ``` diff --git a/tester/fr/running-tests.texy b/tester/fr/running-tests.texy index a7fee934e3..aa15d8d55b 100644 --- a/tester/fr/running-tests.texy +++ b/tester/fr/running-tests.texy @@ -1,55 +1,55 @@ -Tests en cours -************** +Exécution des tests +******************* .[perex] -La partie la plus visible de Nette Tester est le gestionnaire de tests en ligne de commande. Il est extrêmement rapide et robuste parce qu'il démarre automatiquement tous les tests en tant que processus séparés en parallèle dans plusieurs threads. Il peut également s'exécuter en mode veille. +La partie la plus visible de Nette Tester est le lanceur de tests depuis la ligne de commande. Il est extraordinairement rapide et robuste, car il lance automatiquement tous les tests comme des processus distincts et ce, en parallèle dans plusieurs threads. Il sait aussi se lancer lui-même en mode 'watch'. -L'exécuteur de test Nette Tester est invoqué à partir de la ligne de commande. Comme paramètre, on passe le répertoire de test. Pour le répertoire actuel, il suffit d'entrer un point : +Nous appelons le lanceur de tests depuis la ligne de commande. Comme paramètre, nous indiquons le répertoire contenant les tests. Pour le répertoire actuel, il suffit d'entrer un point : /--pre .[terminal] vendor/bin/tester . \-- -Lorsqu'il est invoqué, le programme d'exécution des tests parcourt le répertoire spécifié et tous les sous-répertoires et recherche les tests, qui sont les fichiers `*.phpt` et `*Test.php`. Il lit et évalue également leurs [annotations |test-annotations] pour savoir lesquels et comment les exécuter. +Le lanceur de tests parcourt le répertoire spécifié et tous les sous-répertoires et recherche les tests, qui sont les fichiers `*.phpt` et `*Test.php`. En même temps, il lit et évalue leurs [annotations|test-annotations], pour savoir lesquels et comment les lancer. -Il exécute ensuite les tests. Pour chaque test effectué, le runner imprime un caractère pour indiquer la progression : +Ensuite, il lance les tests. Pendant l'exécution des tests, il affiche en continu les résultats sur le terminal sous forme de caractères : -- . - test passé -- s - le test a été ignoré -- F - le test a échoué +- . – le test a réussi +- s – le test a été sauté (skipped) +- F – le test a échoué (failed) -La sortie peut ressembler à ceci : +La sortie peut ressembler par exemple à ceci : /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +PHP 8.3.2 (cli) | php -n | 8 threads ........s.......................... OK (35 tests, 1 skipped, 1.7 seconds) \-- -Lorsque vous l'exécutez à nouveau, il exécute d'abord les tests qui ont échoué lors de l'exécution précédente, de sorte que vous saurez immédiatement si vous avez corrigé l'erreur. +Lors d'une exécution répétée, il exécute d'abord les tests qui ont échoué lors de l'exécution précédente, de sorte que vous savez immédiatement si vous avez réussi à corriger l'erreur. -Le code de sortie de Tester est zéro si aucun test n'a échoué. Non-zéro sinon. +Si aucun test n'échoue, le code de retour de Tester est zéro. Sinon, le code de retour est non nul. .[warning] -Le Testeur exécute les processus PHP sans `php.ini`. Plus de détails dans la section [Own php.ini |#Own php.ini]. +Tester lance les processus PHP sans `php.ini`. Plus de détails dans la [section php.ini personnalisé |#php.ini personnalisé]. -Options en ligne de commande .[#toc-command-line-options] -========================================================= +Paramètres de la ligne de commande +================================== -Nous obtenons un aperçu des options de la ligne de commande en exécutant le Testeur sans paramètres ou avec l'option `-h`: +Nous obtenons un aperçu de toutes les options de la ligne de commande en lançant Tester sans paramètres, ou avec le paramètre `-h` : /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Usage: tester [options] [ | ]... @@ -58,16 +58,16 @@ Options: -p Specify PHP interpreter to run (default: php). -c Look for php.ini file (or look in directory) . -C Use system-wide php.ini. - -l | --log Write log to file . -d ... Define INI entry 'key' with value 'value'. -s Show information about skipped tests. --stop-on-fail Stop execution upon the first failure. -j Run jobs in parallel (default: 8). - -o Specify output format. + -o (e.g. -o junit:output.xml) + Specify one or more output formats with optional file name. -w | --watch Watch directory. -i | --info Show tests environment info and exit. --setup Script for runner setup. - --temp Path to temporary directory. Default: sys_get_temp_dir(). + --temp Path to temporary directory. Default by sys_get_temp_dir(). --colors [1|0] Enable or disable colors. --coverage Generate code coverage report to file. --coverage-src Path to source code. @@ -86,21 +86,12 @@ tester -p /home/user/php-7.2.0-beta/php-cgi tests -c .[filter] ------------------- -Spécifie quel `php.ini` sera utilisé lors de l'exécution des tests. Par défaut, aucun php.ini n'est utilisé. Voir [Own php.ini |#Own php.ini] pour plus d'informations. +Spécifie quel `php.ini` sera utilisé lors de l'exécution des tests. Par défaut, aucun php.ini n'est utilisé. Plus d'informations dans la [section php.ini personnalisé |#php.ini personnalisé]. -C .[filter] ------------ -Un `php.ini` à l'échelle du système est utilisé. Donc, sur une plateforme UNIX, tous les fichiers `/etc/php/{sapi}/conf.d/*.ini` aussi. Voir la section [Own php.ini |#Own php.ini]. - - -''-l | --log '' .[filter] -------------------------------- -La progression des tests est écrite dans le fichier. Tous les tests échoués, ignorés et aussi les tests réussis : - -/--pre .[terminal] -tester --log /var/log/tests.log tests -\-- +Le `php.ini` système sera utilisé. Sous UNIX, également tous les fichiers INI pertinents `/etc/php/{sapi}/conf.d/*.ini`. Plus d'informations dans la [section php.ini personnalisé |#php.ini personnalisé]. -d .[filter] @@ -114,43 +105,45 @@ tester -d max_execution_time=20 -s --- -Des informations sur les tests ignorés seront affichées. +Affiche des informations sur les tests sautés. --stop-on-fail .[filter] ------------------------ -Le testeur arrête les tests au premier échec. +Tester arrête les tests au premier test échouant. -j .[filter] ------------------ -Les tests s'exécutent dans un `` parallèles. La valeur par défaut est 8. Si nous souhaitons exécuter les tests en série, nous utilisons la valeur 1. +Spécifie combien de processus parallèles avec des tests seront lancés. La valeur par défaut est 8. Si nous voulons que tous les tests s'exécutent en série, nous utilisons la valeur 1. --o .[filter] -------------------------------------- -Format de sortie. Le format par défaut est celui de la console. +-o .[filter] +------------------------------------------------------- +Définit le format de sortie. Le format par défaut est pour la console. Vous pouvez spécifier un nom de fichier dans lequel la sortie sera écrite (par exemple `-o junit:output.xml`). L'option `-o` peut être répétée plusieurs fois pour générer plusieurs formats à la fois. -- `console`: identique à celui par défaut, mais le logo ASCII n'est pas imprimé dans ce cas -- `tap`: [format TAP |https://en.wikipedia.org/wiki/Test_Anything_Protocol] approprié au traitement machine -- `junit`: format XML de JUnit, également approprié au traitement par machine -- `none`: rien n'est imprimé +- `console` : identique au format par défaut, mais dans ce cas, le logo ASCII n'est pas affiché +- `console-lines` : similaire à console, mais le résultat de chaque test est indiqué sur une ligne distincte avec des informations supplémentaires +- `tap` : [format TAP |https://en.wikipedia.org/wiki/Test_Anything_Protocol] adapté au traitement machine +- `junit` : format XML JUnit, également adapté au traitement machine +- `log` : Sorties du déroulement des tests. Tous les tests échoués, sautés et aussi réussis +- `none` : rien n'est affiché ''-w | --watch '' .[filter] --------------------------------- -Le testeur ne se termine pas après la fin des tests, mais il continue à exécuter et à surveiller les fichiers PHP dans le répertoire donné. Lorsqu'ils sont modifiés, il exécute à nouveau les tests. Le paramètre peut être utilisé plusieurs fois si nous voulons surveiller plusieurs répertoires. +Après la fin des tests, Tester ne se termine pas, mais reste en cours d'exécution et surveille les fichiers PHP dans le répertoire spécifié. En cas de changement, il relance les tests. Le paramètre peut être utilisé plusieurs fois si nous voulons surveiller plusieurs répertoires. -C'est pratique pendant le remaniement d'une bibliothèque ou le débogage des tests. +Utile lors du refactoring d'une bibliothèque ou du débogage de tests. /--pre .[terminal] tester --watch src tests \-- --i | --info .[filter] ---------------------- -Elle affiche des informations sur l'environnement d'exécution d'un test. Par exemple : +''-i | --info'' .[filter] +------------------------- +Affiche des informations sur l'environnement d'exécution des tests. Par exemple : /--pre .[terminal] tester -p /usr/bin/php7.1 -c tests/php.ini --info @@ -177,13 +170,13 @@ Core, ctype, date, dom, ereg, fileinfo, filter, hash, ... --setup .[filter] ------------------------ -Le testeur charge le script PHP donné au démarrage. La variable `Tester\Runner\Runner $runner` est disponible dans ce script. Supposons que le fichier `tests/runner-setup.php`: +Tester charge au démarrage le script PHP spécifié. Dans celui-ci, la variable `Tester\Runner\Runner $runner` est disponible. Supposons un fichier `tests/runner-setup.php` avec le contenu : ```php $runner->outputHandlers[] = new MyOutputHandler; ``` -et nous exécutons le Testeur : +Nous lançons Tester : /--pre .[terminal] tester --setup tests/runner-setup.php tests @@ -192,47 +185,47 @@ tester --setup tests/runner-setup.php tests --temp .[filter] ----------------------- -Définit un chemin vers le répertoire pour les fichiers temporaires de Tester. La valeur par défaut est retournée par `sys_get_temp_dir()`. Si la valeur par défaut n'est pas valide, vous en serez averti. +Définit le chemin vers le répertoire pour les fichiers temporaires de Tester. La valeur par défaut est renvoyée par `sys_get_temp_dir()`. Si la valeur par défaut n'est pas valide, vous serez averti. -Si nous ne sommes pas sûrs du répertoire utilisé, nous pouvons exécuter Tester avec le paramètre `--info`. +Si nous ne sommes pas sûrs du répertoire utilisé, nous lançons Tester avec le paramètre `--info`. --colors 1|0 .[filter] ---------------------- -Le Tester détecte par défaut un terminal colorable et colorie sa sortie. Cette option est supérieure à l'autodétection. Nous pouvons définir la coloration de manière globale par une variable d'environnement système `NETTE_TESTER_COLORS`. +Par défaut, Tester détecte un terminal couleur et colore sa sortie. Cette option supplante l'auto-détection. Globalement, nous pouvons définir la coloration avec la variable d'environnement système `NETTE_TESTER_COLORS`. --coverage .[filter] --------------------------- -Le testeur va générer un rapport avec un aperçu de la couverture du code source par les tests. Cette option nécessite l'activation de l'extension PHP [Xdebug |https://xdebug.org/] ou [PCOV |https://github.com/krakjoe/pcov], ou PHP 7 avec la SAPI PHPDBG, qui est plus rapide. L'extension du fichier de destination détermine le format du contenu. HTML ou Clover XML. +Tester génère un rapport avec un aperçu de la quantité de code source couverte par les tests. Cette option nécessite l'extension PHP [Xdebug |https://xdebug.org/] installée, ou [PCOV |https://github.com/krakjoe/pcov], ou PHP 7 avec PHPDBG SAPI, qui est plus rapide. L'extension du fichier cible détermine son format. Soit HTML, soit Clover XML. /--pre .[terminal] -tester tests --coverage coverage.html # HTML report -tester tests --coverage coverage.xml # Clover XML report +tester tests --coverage coverage.html # Rapport HTML +tester tests --coverage coverage.xml # Rapport Clover XML \-- -La priorité pour choisir le mécanisme de collecte est la suivante : +La priorité de sélection du mécanisme est la suivante : 1) PCOV 2) PHPDBG 3) Xdebug -Les tests étendus peuvent échouer lors de l'exécution par PHPDBG en raison de l'épuisement de la mémoire. La collecte des données de couverture est une opération qui consomme de la mémoire. Dans ce cas, l'appel à `Tester\CodeCoverage\Collector::flush()` dans un test peut aider. Il va vider les données collectées dans un fichier et libérer de la mémoire. Lorsque la collecte de données n'est pas en cours, ou que Xdebug est utilisé, l'appel n'a aucun effet. +Lors de l'utilisation de PHPDBG, nous pouvons rencontrer un échec de test sur des tests volumineux en raison de l'épuisement de la mémoire. La collecte d'informations sur le code couvert est gourmande en mémoire. Dans ce cas, l'appel `Tester\CodeCoverage\Collector::flush()` à l'intérieur du test nous aide. Il écrit les données collectées sur le disque et libère la mémoire. Si la collecte de données n'a pas lieu, ou si Xdebug est utilisé, l'appel n'a aucun effet. -"Un exemple de rapport HTML":https://files.nette.org/tester/coverage.html avec couverture de code. +"Exemple de rapport HTML":attachment:coverage.html avec la couverture de code. --coverage-src .[filter] ------------------------------- -Nous l'utilisons simultanément avec l'option `--coverage`. L'option `` est un chemin vers le code source pour lequel nous générons le rapport. Il peut être utilisé à plusieurs reprises. +À utiliser en même temps que l'option `--coverage`. `` est le chemin vers les codes sources pour lesquels le rapport est généré. Peut être utilisé de manière répétée. -Propre php.ini .[#toc-own-php-ini] -================================== -Le Testeur exécute les processus PHP avec l'option `-n`, ce qui signifie qu'aucun `php.ini` n'est chargé (pas même celui de `/etc/php/conf.d/*.ini` sous UNIX). Cela garantit le même environnement pour les tests exécutés, mais cela désactive également toutes les extensions PHP externes couramment chargées par le système PHP. +php.ini personnalisé +==================== +Tester lance les processus PHP avec le paramètre `-n`, ce qui signifie qu'aucun `php.ini` n'est chargé. Sous UNIX, même ceux de `/etc/php/conf.d/*.ini`. Cela garantit un environnement identique pour l'exécution des tests, mais désactive également toutes les extensions PHP normalement chargées par le PHP système. -Si vous souhaitez conserver la configuration du système, utilisez le paramètre `-C`. +Si vous souhaitez conserver le chargement des fichiers php.ini système, utilisez le paramètre `-C`. -Si vous avez besoin de certaines extensions ou de paramètres INI spéciaux, nous vous recommandons de créer votre propre fichier `php.ini` et de le distribuer parmi les tests. Ensuite, on exécute Tester avec l'option `-c`, par exemple `tester -c tests/php.ini`. Le fichier INI peut ressembler à : +Si vous avez besoin de certaines extensions ou de paramètres INI spéciaux pour les tests, nous vous recommandons de créer votre propre fichier `php.ini`, qui sera distribué avec les tests. Tester est ensuite lancé avec le paramètre `-c`, par exemple `tester -c tests/php.ini tests`, où le fichier INI peut ressembler à ceci : ```ini [PHP] @@ -243,4 +236,4 @@ extension=php_pdo_pgsql.dll memory_limit=512M ``` -L'exécution de Tester avec un système `php.ini` sous UNIX, par exemple `tester -c /etc/php/cgi/php.ini`, ne charge pas les autres INI de `/etc/php/conf.d/*.ini`. C'est le comportement de PHP, pas celui de Tester. +Lancer Tester sous UNIX avec le `php.ini` système, par exemple `tester -c /etc/php/cli/php.ini`, ne chargera pas les autres INI de `/etc/php/conf.d/*.ini`. C'est une caractéristique de PHP, pas de Tester. diff --git a/tester/fr/test-annotations.texy b/tester/fr/test-annotations.texy index 19bfad044f..9fa2d25ed4 100644 --- a/tester/fr/test-annotations.texy +++ b/tester/fr/test-annotations.texy @@ -2,15 +2,15 @@ Annotations de test ******************* .[perex] -Les annotations déterminent comment les tests seront traités par le [gestionnaire de tests en ligne de commande |running-tests]. Elles sont écrites au début du fichier de test. +Les annotations déterminent comment les tests seront traités par le [lanceur de tests depuis la ligne de commande|running-tests]. Elles sont écrites au début du fichier de test. -Les annotations ne sont pas sensibles à la casse. Elles n'ont également aucun effet si le test est exécuté manuellement comme un script PHP ordinaire. +Les annotations ne tiennent pas compte de la casse. Elles n'ont également aucun effet si le test est exécuté manuellement comme un script PHP ordinaire. Exemple : ```php /** - * TEST: Basic database query test. + * TEST: Test de requête de base de données de base. * * @dataProvider files/databases.ini * @exitCode 56 @@ -23,17 +23,17 @@ require __DIR__ . '/../bootstrap.php'; TEST .[filter] -------------- -Il ne s'agit pas d'une annotation en fait. Elle définit seulement le titre du test qui est imprimé sur les échecs ou dans les journaux. +Ce n'est en fait pas une annotation, elle détermine seulement le titre du test, qui est affiché en cas d'échec ou dans le journal. @skip .[filter] --------------- -Le test est ignoré. C'est pratique pour désactiver temporairement un test. +Le test est sauté. Utile pour désactiver temporairement des tests. @phpVersion .[filter] --------------------- -Le test est ignoré s'il n'est pas exécuté par la version PHP correspondante. Nous écrivons l'annotation comme `@phpVersion [operator] version`. Nous pouvons ne pas utiliser l'opérateur, la valeur par défaut est `>=`. Exemples : +Le test est sauté s'il n'est pas exécuté avec la version PHP correspondante. Nous écrivons l'annotation comme `@phpVersion [opérateur] version`. Nous pouvons omettre l'opérateur, la valeur par défaut est `>=`. Exemples : ```php /** @@ -46,7 +46,7 @@ Le test est ignoré s'il n'est pas exécuté par la version PHP correspondante. @phpExtension .[filter] ----------------------- -Le test est ignoré si toutes les extensions PHP mentionnées ne sont pas chargées. Plusieurs extensions peuvent être écrites dans une seule annotation, ou nous pouvons utiliser l'annotation plusieurs fois. +Le test est sauté si toutes les extensions PHP spécifiées ne sont pas chargées. Nous pouvons spécifier plusieurs extensions dans une seule annotation, ou l'utiliser plusieurs fois. ```php /** @@ -58,9 +58,9 @@ Le test est ignoré si toutes les extensions PHP mentionnées ne sont pas charg @dataProvider .[filter] ----------------------- -Cette annotation convient lorsque l'on veut exécuter le test plusieurs fois mais avec des données différentes. (A ne pas confondre avec l'annotation du même nom pour [TestCase |TestCase#dataProvider]). +Si nous voulons exécuter le fichier de test plusieurs fois, mais avec des données d'entrée différentes, cette annotation est utile. (Ne pas confondre avec l'annotation du même nom pour [TestCase |TestCase#dataProvider].) -Nous écrivons l'annotation comme `@dataProvider file.ini`. Le chemin du fichier INI est relatif au fichier de test. Le test s'exécute autant de fois que le nombre de sections contenues dans le fichier INI. Supposons que le fichier INI `databases.ini`: +Nous écrivons comme `@dataProvider file.ini`, le chemin vers le fichier est relatif au fichier de test. Le test sera exécuté autant de fois qu'il y a de sections dans le fichier INI. Supposons le fichier INI `databases.ini` : ```ini [mysql] @@ -77,7 +77,7 @@ password = ****** dsn = "sqlite::memory:" ``` -et le fichier `database.phpt` dans le même répertoire : +et dans le même répertoire, le test `database.phpt` : ```php /** @@ -87,11 +87,11 @@ et le fichier `database.phpt` dans le même répertoire : $args = Tester\Environment::loadData(); ``` -Le test s'exécute trois fois et `$args` contiendra les valeurs des sections `mysql`, `postgresql` ou `sqlite`. +Le test sera exécuté trois fois et `$args` contiendra toujours les valeurs de la section `mysql`, `postgresql` ou `sqlite`. -Il existe une autre variante lorsque nous écrivons des annotations avec un point d'interrogation comme `@dataProvider? file.ini`. Dans ce cas, le test est ignoré si le fichier INI n'existe pas. +Il existe une autre variante où nous écrivons l'annotation avec un point d'interrogation comme `@dataProvider? file.ini`. Dans ce cas, le test est sauté si le fichier INI n'existe pas. -Les possibilités d'annotation n'ont pas encore été toutes mentionnées. Nous pouvons écrire des conditions après le fichier INI. Le test s'exécute pour la section donnée seulement si toutes les conditions correspondent. Étendons le fichier INI : +Les possibilités de l'annotation ne s'arrêtent pas là. Après le nom du fichier INI, nous pouvons spécifier des conditions sous lesquelles le test sera exécuté pour la section donnée. Élargissons le fichier INI : ```ini [mysql] @@ -113,7 +113,7 @@ password = ****** dsn = "sqlite::memory:" ``` -et nous utiliserons l'annotation avec la condition : +et utilisons l'annotation avec une condition : ```php /** @@ -121,9 +121,9 @@ et nous utiliserons l'annotation avec la condition : */ ``` -Le test s'exécute une seule fois pour la section `postgresql 9.1`. Les autres sections ne correspondent pas aux conditions. +Le test sera exécuté une seule fois et ce, pour la section `postgresql 9.1`. Les autres sections ne passeront pas le filtre de la condition. -De même, nous pouvons passer le chemin d'un script PHP au lieu de l'INI. Il doit retourner un tableau ou un Traversable. Fichier `databases.php`: +De même, au lieu d'un fichier INI, nous pouvons faire référence à un script PHP. Celui-ci doit retourner un tableau ou un Traversable. Fichier `databases.php` : ```php return [ @@ -142,29 +142,29 @@ return [ @multiple .[filter] ------------------- -Nous l'écrivons comme `@multiple N` où `N` est un nombre entier. Le test s'exécute exactement N fois. +Nous écrivons comme `@multiple N`, où `N` est un entier. Le test sera exécuté exactement N fois. @testCase .[filter] ------------------- -L'annotation n'a pas de paramètres. Nous l'utilisons lorsque nous écrivons un test en tant que classes [TestCase]. Dans ce cas, l'exécuteur de test en ligne de commande exécutera les méthodes individuelles dans des processus séparés et en parallèle dans plusieurs threads. Cela peut accélérer considérablement l'ensemble du processus de test. +L'annotation n'a pas de paramètres. Nous l'utilisons si nous écrivons les tests comme des classes [TestCase |TestCase]. Dans ce cas, le lanceur de tests depuis la ligne de commande exécutera les méthodes individuelles dans des processus séparés et en parallèle dans plusieurs threads. Cela peut accélérer considérablement l'ensemble du processus de test. @exitCode .[filter] ------------------- -Nous l'écrivons comme `@exitCode N` où `N` is the exit code of the test. For example if `exit(10)` est appelé dans le test, nous écrivons l'annotation comme `@exitCode 10`. Il est considéré comme un échec si le test se termine par un code différent. Le code de sortie 0 (zéro) est vérifié si nous omettons l'annotation +Nous écrivons comme `@exitCode N`, où `N` est le code de retour du test exécuté. Si, par exemple, `exit(10)` est appelé dans le test, nous écrivons l'annotation comme `@exitCode 10` et si le test se termine avec un code différent, cela est considéré comme un échec. Si nous n'indiquons pas l'annotation, le code de retour 0 (zéro) est vérifié. @httpCode .[filter] ------------------- -L'annotation est évaluée seulement si le binaire PHP est CGI. Sinon, elle est ignorée. Nous l'écrivons sous la forme `@httpCode NNN` où `NNN` est le code HTTP attendu. Le code HTTP 200 est vérifié si nous ne tenons pas compte de l'annotation. Si nous écrivons `NNN` comme une chaîne évaluée à zéro, par exemple `any`, le code HTTP n'est pas vérifié du tout. +L'annotation ne s'applique que si le binaire PHP est CGI. Sinon, elle est ignorée. Nous écrivons comme `@httpCode NNN` où `NNN` est le code HTTP attendu. Si nous n'indiquons pas l'annotation, le code HTTP 200 est vérifié. Si `NNN` est écrit comme une chaîne évaluée à zéro, par exemple `any`, le code HTTP n'est pas vérifié. -@outputMatch a @outputMatchFile .[filter] ------------------------------------------ -Le comportement des annotations est cohérent avec les assertions `Assert::match()` et `Assert::matchFile()`. Mais le modèle est trouvé dans la sortie standard du test. Un cas d'utilisation approprié est lorsque nous supposons que le test se termine par une erreur fatale et que nous devons vérifier sa sortie. +@outputMatch et @outputMatchFile .[filter] +------------------------------------------ +La fonction des annotations est identique aux assertions `Assert::match()` et `Assert::matchFile()`. Le modèle (pattern) est cependant recherché dans le texte que le test a envoyé sur sa sortie standard. Elle trouve son utilité si nous supposons que le test se terminera par une erreur fatale et que nous devons vérifier sa sortie. @phpIni .[filter] ----------------- -Il définit les valeurs de configuration INI pour le test. Par exemple, nous l'écrivons comme `@phpIni precision=20` et il fonctionne de la même manière que si nous passions la valeur de la ligne de commande par le paramètre `-d precision=20`. +Définit les valeurs INI de configuration pour le test. Nous écrivons par exemple comme `@phpIni precision=20` et cela fonctionne de la même manière que si nous avions entré la valeur depuis la ligne de commande via le paramètre `-d precision=20`. diff --git a/tester/fr/testcase.texy b/tester/fr/testcase.texy index e302604070..8d5359351c 100644 --- a/tester/fr/testcase.texy +++ b/tester/fr/testcase.texy @@ -2,9 +2,9 @@ TestCase ******** .[perex] -Les assertions peuvent se suivre une par une dans les tests simples. Mais parfois il est utile d'enfermer les assertions dans une classe de test et de les structurer de cette façon. +Dans les tests simples, les assertions peuvent se succéder. Parfois, cependant, il est plus avantageux d'emballer les assertions dans une classe de test et de les structurer ainsi. -La classe doit être un descendant de `Tester\TestCase` et nous en parlons simplement comme de **testcase**. +La classe doit hériter de `Tester\TestCase` et nous en parlons simplement comme d'un **testcase**. La classe doit contenir des méthodes de test commençant par `test`. Ces méthodes seront exécutées comme des tests : ```php use Tester\Assert; @@ -22,11 +22,11 @@ class RectangleTest extends Tester\TestCase } } -# Run testing methods +# Exécution des méthodes de test (new RectangleTest)->run(); ``` -Nous pouvons enrichir un testcase par les méthodes `setUp()` et `tearDown()`. Elles sont appelées avant/après chaque méthode de test : +Un test écrit de cette manière peut être enrichi par les méthodes `setUp()` et `tearDown()`. Elles sont appelées avant, resp. après chaque méthode de test : ```php use Tester\Assert; @@ -35,12 +35,12 @@ class NextTest extends Tester\TestCase { public function setUp() { - # Preparation + # Préparation } public function tearDown() { - # Clean-up + # Nettoyage } public function testOne() @@ -54,14 +54,14 @@ class NextTest extends Tester\TestCase } } -# Run testing methods +# Exécution des méthodes de test (new NextTest)->run(); /* -Method Calls Order ------------------- +Ordre d'appel des méthodes +-------------------------- setUp() testOne() tearDown() @@ -72,9 +72,9 @@ tearDown() */ ``` -Si une erreur se produit dans une phase de `setUp()` ou `tearDown()`, le test échouera. Si une erreur se produit dans la méthode de test, la méthode `tearDown()` est quand même appelée, mais avec des erreurs supprimées. +Si une erreur se produit dans la phase `setUp()` ou `tearDown()`, le test échoue globalement. Si une erreur se produit dans la méthode de test, la méthode `tearDown()` est quand même exécutée, mais avec suppression des erreurs qu'elle contient. -Nous vous recommandons d'écrire l'annotation [@testCase |test-annotations#@testCase] au début du test. Le gestionnaire de test en ligne de commande exécutera alors les différentes méthodes de test dans des processus séparés et en parallèle dans plusieurs threads. Cela peut accélérer considérablement l'ensemble du processus de test. +Nous recommandons d'écrire l'annotation [@testCase |test-annotations#testCase] au début du test, alors le lanceur de tests depuis la ligne de commande exécutera les méthodes individuelles du testcase dans des processus séparés et en parallèle dans plusieurs threads. Cela peut accélérer considérablement l'ensemble du processus de test. /--php width = $width; $this->height = $height; @@ -53,7 +52,7 @@ class Rectangle } ``` -Et nous allons créer un test pour elle. Le nom du fichier de test doit correspondre au masque `*Test.php` ou `*.phpt`, nous choisirons la variante `RectangleTest.php`: +Et créons un test pour elle. Le nom du fichier de test doit correspondre au masque `*Test.php` ou `*.phpt`, choisissons par exemple la variante `RectangleTest.php` : ```php .{file:tests/RectangleTest.php} @@ -62,31 +61,31 @@ use Tester\Assert; require __DIR__ . '/bootstrap.php'; -// oblong général +// rectangle général $rect = new Rectangle(10, 20); -Assert::same(200.0, $rect->getArea()); # nous allons vérifier les résultats attendus +Assert::same(200.0, $rect->getArea()); # nous vérifions les résultats attendus Assert::false($rect->isSquare()); ``` -Comme vous pouvez le constater, les [méthodes d'assertion |Assertions] telles que `Assert::same()` sont utilisées pour affirmer qu'une valeur réelle correspond à une valeur attendue. +Comme vous pouvez le voir, les soi-disant [méthodes d'assertion|assertions] comme `Assert::same()` sont utilisées pour confirmer qu'une valeur réelle correspond à une valeur attendue. -La dernière étape consiste à créer le fichier `bootstrap.php`. Il contient un code commun pour tous les tests. Par exemple, le chargement automatique des classes, la configuration de l'environnement, la création d'un répertoire temporaire, les aides et autres. Chaque test charge le bootstrap et ne s'occupe que du test. Le bootstrap peut ressembler à : +Il reste une dernière étape, le fichier `bootstrap.php`. Il contient le code commun à tous les tests, par exemple l'autoloading des classes, la configuration de l'environnement, la création d'un répertoire temporaire, les fonctions d'aide et similaires. Tous les tests chargent le bootstrap et se consacrent ensuite uniquement aux tests. Le bootstrap peut ressembler à ceci : ```php .{file:tests/bootstrap.php} OK \-- -Si nous changeons dans le test la déclaration en false `Assert::same(123, $rect->getArea());`, ceci se produira : +Si nous changions l'assertion dans le test en une affirmation fausse `Assert::same(123, $rect->getArea());`, voici ce qui se passerait : /--pre .[terminal] $ php RectangleTest.php @@ -107,35 +106,35 @@ $ php RectangleTest.php \-- -Lorsque l'on écrit des tests, il est bon d'attraper toutes les situations extrêmes. Par exemple, si l'entrée est zéro, un nombre négatif, dans d'autres cas une chaîne vide, null, etc. En fait, cela vous oblige à réfléchir et à décider comment le code doit se comporter dans de telles situations. Les tests corrigent ensuite le comportement. +Lors de l'écriture des tests, il est bon de couvrir toutes les situations limites. Par exemple, lorsque l'entrée est zéro, un nombre négatif, dans d'autres cas peut-être une chaîne vide, null, etc. En fait, cela vous oblige à réfléchir et à décider comment le code doit se comporter dans de telles situations. Les tests fixent ensuite le comportement. -Dans notre cas, une valeur négative devrait lever une exception, que nous vérifions avec [Assert::exception() |Assertions#Assert::exception]: +Dans notre cas, une valeur négative doit lever une exception, ce que nous vérifions à l'aide de [Assert::exception() |Assertions#Assert::exception] : ```php .{file:tests/RectangleTest.php} -// la largeur ne doit pas être un nombre négatif +// la largeur ne doit pas être négative Assert::exception( fn() => new Rectangle(-1, 20), InvalidArgumentException::class, - 'La dimension ne doit pas être négative', + 'La dimension ne doit pas être négative.', ); ``` -Et nous ajoutons un test similaire pour la hauteur. Enfin, nous testons que `isSquare()` renvoie `true` si les deux dimensions sont identiques. Essayez d'écrire de tels tests à titre d'exercice. +Et nous ajoutons un test similaire pour la hauteur. Enfin, nous testons que `isSquare()` renvoie `true` si les deux dimensions sont identiques. Essayez d'écrire de tels tests comme exercice. -Tests bien agencés .[#toc-well-arranged-tests] -============================================== +Tests plus clairs +================= -La taille du fichier de test peut augmenter et devenir rapidement encombrante. Il est donc pratique de regrouper les différentes zones testées dans des fonctions distinctes. +La taille du fichier de test peut augmenter et devenir rapidement illisible. C'est pourquoi il est pratique de regrouper les différentes zones testées dans des fonctions distinctes. -Tout d'abord, nous allons montrer une variante plus simple mais élégante, en utilisant la fonction globale `test()`. Le testeur ne la crée pas automatiquement, pour éviter une collision si vous aviez une fonction avec le même nom dans votre code. Il est uniquement créé par la méthode `setupFunctions()`, que vous appelez dans le fichier `bootstrap.php` : +Montrons d'abord une variante plus simple, mais élégante, à l'aide de la fonction globale `test()`. Tester ne la crée pas automatiquement, pour éviter les collisions si vous aviez une fonction du même nom dans votre code. Elle est créée par la méthode `setupFunctions()`, que vous appelez dans le fichier `bootstrap.php` : ```php .{file:tests/bootstrap.php} Tester\Environment::setup(); Tester\Environment::setupFunctions(); ``` -Grâce à cette fonction, nous pouvons diviser joliment le fichier de test en unités nommées. Lors de l'exécution, les étiquettes seront affichées l'une après l'autre. +À l'aide de cette fonction, nous pouvons bien structurer le fichier de test en ensembles nommés. Lors de l'exécution, les descriptions seront affichées progressivement. ```php .{file:tests/RectangleTest.php} getArea()); Assert::false($rect->isSquare()); }); -test('general square', function () { +test('carré général', function () { $rect = new Rectangle(5, 5); Assert::same(25.0, $rect->getArea()); Assert::true($rect->isSquare()); }); -test('dimensions must not be negative', function () { +test('les dimensions ne doivent pas être négatives', function () { Assert::exception( fn() => new Rectangle(-1, 20), InvalidArgumentException::class, @@ -168,15 +167,15 @@ test('dimensions must not be negative', function () { }); ``` -Si vous devez exécuter le code avant ou après chaque test, passez-le à `setUp()` ou `tearDown()` : +Si vous avez besoin d'exécuter du code avant ou après chaque test, passez-le à la fonction `setUp()` resp. `tearDown()` : ```php -setUp(fonction () { - // code d'initialisation à exécuter avant chaque test() -}) ; +setUp(function () { + // code d'initialisation qui s'exécute avant chaque test() +}); ``` -La deuxième variante est l'objet. Nous allons créer ce qu'on appelle un TestCase, qui est une classe où les unités individuelles sont représentées par des méthodes dont le nom commence par test-. +La deuxième variante est orientée objet. Nous créons un soi-disant TestCase, qui est une classe où les ensembles individuels représentent des méthodes dont les noms commencent par test–. ```php .{file:tests/RectangleTest.php} class RectangleTest extends Tester\TestCase @@ -212,21 +211,21 @@ class RectangleTest extends Tester\TestCase (new RectangleTest)->run(); ``` -Cette fois, nous avons utilisé une annotation `@throw` pour tester les exceptions. Consultez le chapitre [TestCase] pour plus d'informations. +Pour tester les exceptions, nous avons cette fois utilisé l'annotation `@throw`. Vous en apprendrez plus dans le chapitre [TestCase |TestCase]. -Fonctions d'aide .[#toc-helpers-functions] -========================================== +Fonctions d'aide +================ -Nette Tester comprend plusieurs classes et fonctions qui peuvent vous faciliter les tests, par exemple, des aides pour tester le contenu d'un document HTML, pour tester les fonctions de travail avec les fichiers, etc. +Nette Tester contient plusieurs classes et fonctions qui peuvent vous faciliter par exemple le test du contenu d'un document HTML, le test de fonctions travaillant avec des fichiers, etc. -Vous pouvez trouver une description de ces fonctions sur la page [Helpers]. +Leur description se trouve sur la page [Classes d'aide|helpers]. -Annotation et saut de tests .[#toc-annotation-and-skipping-tests] -================================================================= +Annotations et saut de tests +============================ -L'exécution des tests peut être affectée par des annotations dans le commentaire phpDoc au début du fichier. Par exemple, cela pourrait ressembler à ceci : +L'exécution des tests peut être influencée par des annotations sous forme de commentaire phpDoc au début du fichier. Elle peut ressembler par exemple à ceci : ```php .{file:tests/RectangleTest.php} /** @@ -235,11 +234,11 @@ L'exécution des tests peut être affectée par des annotations dans le commenta */ ``` -Les annotations indiquent que le test ne doit être exécuté qu'avec la version 7.2 ou supérieure de PHP et si les extensions PHP pdo et pdo_pgsql sont présentes. Ces annotations sont contrôlées par l'[exécuteur de test en ligne de commande |running-tests], qui, si les conditions ne sont pas remplies, saute le test et le marque avec la lettre `s` - sauté. Cependant, elles n'ont aucun effet lorsque le test est exécuté manuellement. +Les annotations indiquées disent que le test doit être exécuté uniquement avec la version PHP 7.2 ou supérieure et si les extensions PHP pdo et pdo_pgsql sont présentes. Ces annotations sont suivies par le [lanceur de tests depuis la ligne de commande|running-tests], qui, dans le cas où les conditions ne sont pas remplies, saute le test et le marque dans la sortie par la lettre `s` - skipped. Cependant, lors de l'exécution manuelle du test, elles n'ont aucune influence. -Pour une description des annotations, voir [Test Annotations |Test Annotations]. +La description des annotations se trouve sur la page [Annotations de test|test-annotations]. -Le test peut également être ignoré en fonction de sa propre condition avec `Environment::skip()`. Par exemple, nous allons ignorer ce test sous Windows : +Un test peut également être sauté en fonction de la satisfaction d'une condition personnalisée à l'aide de `Environment::skip()`. Par exemple, celle-ci saute les tests sous Windows : ```php if (defined('PHP_WINDOWS_VERSION_BUILD')) { @@ -248,10 +247,10 @@ if (defined('PHP_WINDOWS_VERSION_BUILD')) { ``` -Structure des répertoires .[#toc-directory-structure] -===================================================== +Structure des répertoires +========================= -Pour les bibliothèques ou les projets un peu plus importants, nous recommandons de diviser le répertoire de test en sous-répertoires en fonction de l'espace de noms de la classe testée : +Nous recommandons, pour les bibliothèques ou projets un peu plus grands, de diviser le répertoire de tests en sous-répertoires selon l'espace de noms de la classe testée : ``` └── tests/ @@ -269,15 +268,15 @@ Pour les bibliothèques ou les projets un peu plus importants, nous recommandons └── ... ``` -Vous pourrez ainsi exécuter des tests à partir d'un seul sous-répertoire de l'espace de noms : +Vous pourrez ainsi exécuter les tests d'un seul espace de noms, c'est-à-dire d'un sous-répertoire : /--pre .[terminal] tester tests/NamespaceOne \-- -Cas limites .[#toc-edge-cases] -============================== +Situations spéciales +==================== Un test qui n'appelle aucune méthode d'assertion est suspect et sera évalué comme erroné : @@ -285,6 +284,6 @@ Un test qui n'appelle aucune méthode d'assertion est suspect et sera évalué c Error: This test forgets to execute an assertion. \-- -Si le test sans appel aux assertions doit vraiment être considéré comme valide, appelez par exemple `Assert::true(true)`. +Si un test sans appel d'assertions doit vraiment être considéré comme valide, appelez par exemple `Assert::true(true)`. -Il peut également être perfide d'utiliser `exit()` et `die()` pour terminer le test avec un message d'erreur. Par exemple, `exit('Error in connection')` termine le test avec un code de sortie 0, qui signale le succès. Utilisez `Assert::fail('Error in connection')`. +Il peut également être délicat d'utiliser `exit()` et `die()` pour terminer un test avec un message d'erreur. Par exemple, `exit('Error in connection')` termine le test avec le code de retour 0, ce qui signale un succès. Utilisez `Assert::fail('Error in connection')`. diff --git a/tester/hu/@home.texy b/tester/hu/@home.texy index 08f721a3b6..02e8ffde59 100644 --- a/tester/hu/@home.texy +++ b/tester/hu/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Nette Tester - Élvezetes egységtesztelés PHP-ban}} -{{description: A Nette Tester egy egyszerű és mégis nagyon praktikus PHP kódtesztelő eszköz.}} +{{maintitle: Nette Tester – kényelmes tesztelés PHP-ban}} +{{description: A Nette Tester egy egyszerű, mégis nagyon ügyes eszköz a PHP kód tesztelésére.}} diff --git a/tester/hu/@left-menu.texy b/tester/hu/@left-menu.texy index afebbaf9df..9bd90409b5 100644 --- a/tester/hu/@left-menu.texy +++ b/tester/hu/@left-menu.texy @@ -1,8 +1,8 @@ -- [Kezdő lépések |guide] -- [Tesztek írása |Writing Tests] -- [Tesztek futtatása |Running Tests] +- [Első lépések a Nette Testerrel |guide] +- [Tesztek írása |writing-tests] +- [Tesztek futtatása |running-tests] -- [Állítások |Assertions] -- [Teszt megjegyzések |Test Annotations] +- [Assertek |assertions] +- [Teszt annotációk |test-annotations] - [TestCase |TestCase] -- [Segédprogramok |Helpers] +- [Segédosztályok |helpers] diff --git a/tester/hu/@menu.texy b/tester/hu/@menu.texy index 54c35fe758..b4f6c448e0 100644 --- a/tester/hu/@menu.texy +++ b/tester/hu/@menu.texy @@ -1,3 +1,3 @@ -- [Otthon |@home] -- [Dokumentáció |Guide] +- [Bevezetés |@home] +- [Dokumentáció |guide] - "GitHub .[link-external]":https://github.com/nette/tester diff --git a/tester/hu/@meta.texy b/tester/hu/@meta.texy new file mode 100644 index 0000000000..6be4d2d1ff --- /dev/null +++ b/tester/hu/@meta.texy @@ -0,0 +1 @@ +{{sitename: Tester dokumentáció}} diff --git a/tester/hu/assertions.texy b/tester/hu/assertions.texy index 5ca1ceecb2..af37dd75b0 100644 --- a/tester/hu/assertions.texy +++ b/tester/hu/assertions.texy @@ -1,35 +1,35 @@ -Állítások -********* +Assertok +******** .[perex] -Az állítások azt bizonyítják, hogy egy tényleges érték megegyezik egy elvárt értékkel. Ezek a `Tester\Assert` metódusai. +Az assertok arra szolgálnak, hogy megerősítsék, hogy a tényleges érték megfelel a várt értéknek. Ezek a `Tester\Assert` osztály metódusai. -Válassza ki a legpontosabb állításokat. Jobb a `Assert::same($a, $b)` mint a `Assert::true($a === $b)`, mert hiba esetén értelmes hibaüzenetet jelenít meg. A második esetben csak `false should be true` kapunk, és ez semmit sem mond a $a és $b változók tartalmáról. +Válassza ki a legmegfelelőbb assertokat. Jobb az `Assert::same($a, $b)`, mint az `Assert::true($a === $b)`, mert hiba esetén értelmes hibaüzenetet jelenít meg. A második esetben csak `false should be true`, ami semmit sem mond nekünk az `$a` és `$b` változók tartalmáról. -A legtöbb állításnak lehet egy opcionális `$description` is, amely megjelenik a hibaüzenetben, ha az elvárás sikertelen. +A legtöbb assertnak lehet egy opcionális leírása is a `$description` paraméterben, amely a hibaüzenetben jelenik meg, ha az elvárás meghiúsul. -A példák feltételezik, hogy a következő osztály alias van definiálva: +A példák feltételezik egy alias létrehozását: ```php use Tester\Assert; ``` -Assert::same($expected, $actual, string $description=null) .[method] --------------------------------------------------------------------- -`$expected` azonosnak kell lennie a `$actual` címmel. Ez megegyezik a `===` PHP-operátorral. +Assert::same($expected, $actual, ?string $description=null) .[method] +--------------------------------------------------------------------- +Az `$expected`-nek azonosnak kell lennie az `$actual`-lal. Ugyanaz, mint a PHP `===` operátora. -Assert::notSame($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------ -Ellentétes a `Assert::same()`-val , tehát megegyezik a `!==` PHP-operátorral. +Assert::notSame($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------ +Az `Assert::same()` ellentéte, tehát ugyanaz, mint a PHP `!==` operátora. -Assert::equal($expected, $actual, string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] ------------------------------------------------------------------------------------------------------------------------- -`$expected` ugyanannak kell lennie, mint a `$actual`. A `Assert::same()`-től eltérően az objektum azonosságát, a kulcspárok => érték sorrendjét a tömbökben, valamint a minimálisan eltérő decimális számokat figyelmen kívül hagyja, ami a `$matchIdentity` és a `$matchOrder` beállításával módosítható. +Assert::equal($expected, $actual, ?string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] +------------------------------------------------------------------------------------------------------------------------- +Az `$expected`-nek egyenlőnek kell lennie az `$actual`-lal. Az `Assert::same()`-től eltérően figyelmen kívül hagyja az objektumok identitását, a kulcs => érték párok sorrendjét a tömbökben és a marginálisan eltérő tizedes számokat, amit a `$matchIdentity` és `$matchOrder` beállításával lehet megváltoztatni. -A következő esetek a `equal()` szempontjából azonosak, de a `same()` szempontjából nem: +A következő esetek az `equal()` szempontjából megegyeznek, de a `same()` szempontjából nem: ```php Assert::equal(0.3, 0.1 + 0.2); @@ -40,81 +40,81 @@ Assert::equal( ); ``` -Azonban vigyázat, a tömb `[1, 2]` és a `[2, 1]` nem egyenlőek, mert csak az értékek sorrendje különbözik, a kulcs => érték párok nem. A tömb `[1, 2]` írható úgy is, hogy `[0 => 1, 1 => 2]` és így `[1 => 2, 0 => 1]` egyenlőnek tekintjük. +Azonban vigyázat, az `[1, 2]` és `[2, 1]` tömbök nem egyenlőek, mert csak az értékek sorrendje különbözik, nem a kulcs => érték pároké. Az `[1, 2]` tömböt `[0 => 1, 1 => 2]`-ként is fel lehet írni, ezért az `[1 => 2, 0 => 1]` fog egyenlőnek számítani. -Használhatjuk az úgynevezett [elvárásokat |#expectations] is a `$expected`. +Továbbá az `$expected`-ben használhatók az ún. [#elvárások]. -Assert::notEqual($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------- -Ellentétben a `Assert::equal()`. +Assert::notEqual($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------- +Az `Assert::equal()` ellentéte. -Assert::contains($needle, string|array $actual, string $description=null) .[method] ------------------------------------------------------------------------------------ -Ha a `$actual` egy karakterlánc, akkor tartalmaznia kell a `$needle` részláncot. Ha tömb, akkor a `$needle` elemet kell tartalmaznia (szigorúan összehasonlításra kerül). +Assert::contains($needle, string|array $actual, ?string $description=null) .[method] +------------------------------------------------------------------------------------ +Ha az `$actual` egy string, tartalmaznia kell a `$needle` részstringet. Ha tömb, tartalmaznia kell a `$needle` elemet (szigorúan hasonlítva). -Assert::notContains($needle, string|array $actual, string $description=null) .[method] --------------------------------------------------------------------------------------- -A `Assert::contains()` ellentéte. +Assert::notContains($needle, string|array $actual, ?string $description=null) .[method] +--------------------------------------------------------------------------------------- +Az `Assert::contains()` ellentéte. -Assert::hasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} -------------------------------------------------------------------------------------------------------- -`$actual` tömbnek kell lennie, és tartalmaznia kell a `$needle` kulcsot. +Assert::hasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +-------------------------------------------------------------------------------------------------------- +Az `$actual`-nak tömbnek kell lennie, és tartalmaznia kell a `$needle` kulcsot. -Assert::notHasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} ----------------------------------------------------------------------------------------------------------- -`$actual` tömbnek kell lennie, és nem tartalmazhatja a `$needle` kulcsot. +Assert::notHasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +----------------------------------------------------------------------------------------------------------- +Az `$actual`-nak tömbnek kell lennie, és nem tartalmazhatja a `$needle` kulcsot. -Assert::true($value, string $description=null) .[method] --------------------------------------------------------- -`$value` a `true` kell, hogy legyen, tehát `$value === true`. +Assert::true($value, ?string $description=null) .[method] +--------------------------------------------------------- +Az `$value`-nak `true`-nak kell lennie, tehát `$value === true`. -Assert::truthy($value, string $description=null) .[method] ----------------------------------------------------------- -`$value` igaznak kell lennie, tehát teljesíti a `if ($value) ...` feltételt. +Assert::truthy($value, ?string $description=null) .[method] +----------------------------------------------------------- +Az `$value`-nak igaznak kell lennie (truthy), tehát teljesíti az `if ($value) ...` feltételt. -Assert::false($value, string $description=null) .[method] ---------------------------------------------------------- -`$value` kell lennie `false`, tehát `$value === false`. +Assert::false($value, ?string $description=null) .[method] +---------------------------------------------------------- +Az `$value`-nak `false`-nak kell lennie, tehát `$value === false`. -Assert::falsey($value, string $description=null) .[method] ----------------------------------------------------------- -`$value` hamisnak kell lennie, tehát teljesíti a `if (!$value) ...` feltételt. +Assert::falsey($value, ?string $description=null) .[method] +----------------------------------------------------------- +Az `$value`-nak hamisnak kell lennie (falsey), tehát teljesíti az `if (!$value) ...` feltételt. -Assert::null($value, string $description=null) .[method] --------------------------------------------------------- -`$value` a `null` kell, hogy legyen, tehát `$value === null`. +Assert::null($value, ?string $description=null) .[method] +--------------------------------------------------------- +Az `$value`-nak `null`-nak kell lennie, tehát `$value === null`. -Assert::notNull($value, string $description=null) .[method] ------------------------------------------------------------ -`$value` nem lehet `null`, tehát `$value !== null`. +Assert::notNull($value, ?string $description=null) .[method] +------------------------------------------------------------ +Az `$value`-nak nem szabad `null`-nak lennie, tehát `$value !== null`. -Assert::nan($value, string $description=null) .[method] -------------------------------------------------------- -`$value` nem lehet szám. A NAN teszteléshez csak a `Assert::nan()` címet használja. A NAN érték nagyon specifikus, és a `Assert::same()` vagy a `Assert::equal()` állítások kiszámíthatatlanul viselkedhetnek. +Assert::nan($value, ?string $description=null) .[method] +-------------------------------------------------------- +Az `$value`-nak Not a Number-nek kell lennie. NAN értékek tesztelésére kizárólag az `Assert::nan()`-t használja. A NAN érték nagyon specifikus, és az `Assert::same()` vagy `Assert::equal()` asszerciók váratlanul működhetnek. -Assert::count($count, Countable|array $value, string $description=null) .[method] ---------------------------------------------------------------------------------- -A `$value` elemszámának a `$count` kell lennie. Tehát ugyanaz, mint a `count($value) === $count`. +Assert::count($count, Countable|array $value, ?string $description=null) .[method] +---------------------------------------------------------------------------------- +Az elemek száma az `$value`-ban `$count` kell, hogy legyen. Tehát ugyanaz, mint `count($value) === $count`. -Assert::type(string|object $type, $value, string $description=null) .[method] ------------------------------------------------------------------------------ -`$value` adott típusúnak kell lennie. Mint `$type` használhatjuk a stringet: +Assert::type(string|object $type, $value, ?string $description=null) .[method] +------------------------------------------------------------------------------ +Az `$value`-nak adott típusúnak kell lennie. `$type`-ként használhatunk stringet: - `array` -- `list` - nullától kezdve a numerikus kulcsok növekvő sorrendjében indexelt tömb. +- `list` - nullától kezdődő, növekvő sorrendű numerikus kulcsokkal indexelt tömb - `bool` - `callable` - `float` @@ -124,14 +124,14 @@ Assert::type(string|object $type, $value, string $description=null) .[method] - `resource` - `scalar` - `string` -- osztály nevét vagy objektumot közvetlenül, akkor át kell adnia `$value instanceof $type` +- osztálynév vagy közvetlenül objektum, ekkor az `$value instanceof $type` kell, hogy legyen -Assert::exception(callable $callable, string $class, string $message=null, $code=null) .[method] ------------------------------------------------------------------------------------------------- -A `$callable` meghívásakor egy `$class` példányú kivételt kell dobni. Ha átadjuk a `$message` címet, a kivétel üzenetének [meg |#assert-match] kell [egyeznie |#assert-match]. És ha átadjuk a `$code`, a kivétel kódjának meg kell egyeznie. +Assert::exception(callable $callable, string $class, ?string $message=null, $code=null) .[method] +------------------------------------------------------------------------------------------------- +A `$callable` hívásakor a `$class` osztályú kivételnek kell dobódnia. Ha megadjuk a `$message`-t, a kivétel üzenetének is [meg kell felelnie a mintának |#Assert::match], és ha megadjuk a `$code`-ot, a kódoknak is szigorúan meg kell egyezniük. -Például ez a teszt sikertelen, mert a kivétel üzenete nem egyezik: +A következő teszt meghiúsul, mert a kivétel üzenete nem felel meg: ```php Assert::exception( @@ -141,7 +141,7 @@ Assert::exception( ); ``` -A `Assert::exception()` egy dobott kivételt ad vissza, így tesztelhet egy beágyazott kivételt. +Az `Assert::exception()` visszaadja a dobott kivételt, így a beágyazott kivételt is tesztelhetjük. ```php $e = Assert::exception( @@ -154,9 +154,9 @@ Assert::type(RuntimeException::class, $e->getPrevious()); ``` -Assert::error(string $callable, int|string|array $type, string $message=null) .[method] ---------------------------------------------------------------------------------------- -Ellenőrzi, hogy a `$callable` meghívása a várt hibákat (azaz figyelmeztetéseket, értesítéseket stb.) generálja. Mint `$type` megadjuk a `E_...`, például a `E_WARNING` konstansok egyikét. És ha átadjuk a `$message`, a hibaüzenetnek is [meg |#assert-match] kell [felelnie |#assert-match] a mintának. Például: +Assert::error(string $callable, int|string|array $type, ?string $message=null) .[method] +---------------------------------------------------------------------------------------- +Ellenőrzi, hogy a `$callable` függvény generálta-e a várt hibákat (azaz warningokat, notice-okat stb.). `$type`-ként adjuk meg az `E_...` konstansok egyikét, például `E_WARNING`. És ha megadjuk a `$message`-t, a hibaüzenetnek is [meg kell felelnie a mintának |#Assert::match]. Például: ```php Assert::error( @@ -166,7 +166,7 @@ Assert::error( ); ``` -Ha a visszahívás több hibát generál, akkor mindegyiket a pontos sorrendben kell várnunk. Ebben az esetben a tömböt a `$type` címen adjuk át: +Ha a callback több hibát generál, mindegyiket pontos sorrendben kell várnunk. Ebben az esetben a `$type`-ban egy tömböt adunk át: ```php Assert::error(function () { @@ -179,108 +179,108 @@ Assert::error(function () { ``` .[note] -Ha a `$type` az osztály neve, akkor ez az állítás ugyanúgy viselkedik, mint a `Assert::exception()`. +Ha `$type`-ként egy osztálynevet ad meg, ugyanúgy viselkedik, mint az `Assert::exception()`. Assert::noError(callable $callable) .[method] --------------------------------------------- -Ellenőrzi, hogy a `$callable` függvény nem dob-e PHP figyelmeztetést/értesítést/hibát vagy kivételt. Hasznos egy olyan kódrészlet teszteléséhez, ahol nincs más állítás. +Ellenőrzi, hogy a `$callable` függvény nem generált-e semmilyen warningot, hibát vagy kivételt. Hasznos olyan kódrészletek tesztelésére, ahol nincs más assert. -Assert::match(string $pattern, $actual, string $description=null) .[method] ---------------------------------------------------------------------------- -`$actual` kell, hogy egyezzen a `$pattern`. A minták két változatát használhatjuk: a reguláris kifejezéseket vagy a vadkártyákat. +Assert::match(string $pattern, $actual, ?string $description=null) .[method] +---------------------------------------------------------------------------- +Az `$actual`-nak meg kell felelnie a `$pattern` mintának. Kétféle mintát használhatunk: reguláris kifejezéseket vagy helyettesítő karaktereket. -Ha egy reguláris kifejezést adunk át a `$pattern` címen, akkor a `~` or `#` címet kell használnunk az elhatároláshoz. Más elválasztójelek nem támogatottak. Például a teszt, ahol a `$var` csak hexadecimális számjegyeket tartalmazhat: +Ha `$pattern`-ként reguláris kifejezést adunk át, annak határolására `~` vagy `#` kell használni, más elválasztók nem támogatottak. Például egy teszt, ahol a `$var`-nak csak hexadecimális számjegyeket kell tartalmaznia: ```php Assert::match('#^[0-9a-f]$#i', $var); ``` -A másik változat hasonló a string-összehasonlításhoz, de használhatunk néhány vad karaktert a `$pattern`: - -- `%a%` egy vagy több bármi, kivéve a sor végi karaktereket. -- `%a?%` nulla vagy több bármi más, kivéve a sor végi karaktereket. -- `%A%` egy vagy több bármi, beleértve a sor végi karaktereket is. -- `%A?%` nulla vagy több bármiből, beleértve a sor végi karaktereket is. -- `%s%` egy vagy több szóköz karakter, kivéve a sor végi karaktereket. -- `%s?%` nulla vagy több szóköz karakter, kivéve a sor végi karaktereket -- `%S%` egy vagy több karakter, kivéve a szóközöket. -- `%S?%` nulla vagy több karakter, kivéve a szóközöket. -- `%c%` egyetlen bármilyen karakter (kivéve a sor végét) +A második változat hasonló a szokásos string összehasonlításhoz, de a `$pattern`-ben különböző helyettesítő karaktereket használhatunk: + +- `%a%` egy vagy több karakter, kivéve a sorvégi karaktereket +- `%a?%` nulla vagy több karakter, kivéve a sorvégi karaktereket +- `%A%` egy vagy több karakter, beleértve a sorvégi karaktereket +- `%A?%` nulla vagy több karakter, beleértve a sorvégi karaktereket +- `%s%` egy vagy több szóköz karakter, kivéve a sorvégi karaktereket +- `%s?%` nulla vagy több szóköz karakter, kivéve a sorvégi karaktereket +- `%S%` egy vagy több karakter, kivéve a szóköz karaktereket +- `%S?%` nulla vagy több karakter, kivéve a szóköz karaktereket +- `%c%` bármilyen egy karakter, kivéve a sorvégi karaktert - `%d%` egy vagy több számjegy - `%d?%` nulla vagy több számjegy -- `%i%` előjeles egész érték -- `%f%` lebegőpontos szám -- `%h%` egy vagy több HEX számjegy +- `%i%` előjeles egész szám érték +- `%f%` tizedesvesszős szám +- `%h%` egy vagy több hexadecimális számjegy - `%w%` egy vagy több alfanumerikus karakter -- `%%` egy % karakter +- `%%` a % karakter Példák: ```php -# Again, hexadecimal number test +# Ismét teszt hexadecimális számra Assert::match('%h%', $var); -# Generalized path to file and line number +# Fájl elérési útjának és sorszámának általánosítása Assert::match('Error in file %a% on line %i%', $errorMessage); ``` -Assert::matchFile(string $file, $actual, string $description=null) .[method] ----------------------------------------------------------------------------- -Az állítás megegyezik az [Assert::match()-vel |#assert-match], de a minta a `$file` oldalról töltődik be. Nagyon hosszú karakterláncok teszteléséhez hasznos. A tesztfájl olvashatóan áll. +Assert::matchFile(string $file, $actual, ?string $description=null) .[method] +----------------------------------------------------------------------------- +Az assert azonos az [#Assert::match()]-csal, de a minta a `$file` fájlból töltődik be. Ez hasznos nagyon hosszú stringek tesztelésére. A tesztfájl áttekinthető marad. Assert::fail(string $message, $actual=null, $expected=null) .[method] --------------------------------------------------------------------- -Ez az állítás mindig sikertelen. Csak praktikus. Opcionálisan átadhatjuk a várható és a tényleges értékeket. +Ez az assert mindig meghiúsul. Néha egyszerűen hasznos. Opcionálisan megadhatjuk a várt és a tényleges értéket is. -Várakozások .[#toc-expectations] --------------------------------- -Ha összetettebb, nem konstans elemeket tartalmazó struktúrákat akarunk összehasonlítani, a fenti állítások nem biztos, hogy elegendőek. Például tesztelünk egy olyan metódust, amely létrehoz egy új felhasználót, és az attribútumait tömbként adja vissza. A jelszó hash-értékét nem ismerjük, de azt tudjuk, hogy annak hexadecimális karakterláncnak kell lennie. A következő elemről pedig csak annyit tudunk, hogy annak egy objektumnak kell lennie: `DateTime`. +Elvárások +--------- +Ha összetettebb struktúrákat szeretnénk összehasonlítani nem konstans elemekkel, a fenti assertok nem biztos, hogy elegendőek. Például tesztelünk egy metódust, amely új felhasználót hoz létre, és annak attribútumait tömbként adja vissza. A jelszó hash értékét nem ismerjük, de tudjuk, hogy hexadecimális stringnek kell lennie. És egy másik elemről csak annyit tudunk, hogy `DateTime` objektumnak kell lennie. -Ezekben az esetekben a `Assert::equal()` és a `Assert::notEqual()` metódusok `$expected` paraméterén belül használhatjuk a `Tester\Expect` -t, amivel könnyen leírhatjuk a struktúrát. +Ezekben a helyzetekben használhatjuk a `Tester\Expect`-et az `$expected` paraméteren belül az `Assert::equal()` és `Assert::notEqual()` metódusokban, amelyek segítségével a struktúra könnyen leírható. ```php use Tester\Expect; Assert::equal([ - 'id' => Expect::type('int'), # we expect an integer + 'id' => Expect::type('int'), # egész számot várunk 'username' => 'milo', - 'password' => Expect::match('%h%'), # we expect a string matching pattern - 'created_at' => Expect::type(DateTime::class), # we expect an instance of the class + 'password' => Expect::match('%h%'), # mintának megfelelő stringet várunk + 'created_at' => Expect::type(DateTime::class), # osztálypéldányt várunk ], User::create(123, 'milo', 'RandomPaSsWoRd')); ``` -A `Expect` segítségével szinte ugyanazokat az állításokat tehetjük, mint a `Assert` segítségével. Így olyan metódusaink vannak, mint a `Expect::same()`, `Expect::match()`, `Expect::count()`, stb. Ezen kívül láncolhatjuk őket, mint: +Az `Expect`-tel szinte ugyanazokat az assertokat végezhetjük el, mint az `Assert`-tel. Tehát rendelkezésünkre állnak az `Expect::same()`, `Expect::match()`, `Expect::count()` stb. metódusok. Ráadásul láncolhatjuk őket: ```php -Expect::type(MyIterator::class)->andCount(5); # we expect MyIterator and items count is 5 +Expect::type(MyIterator::class)->andCount(5); # MyIterator-t és 5 elemet várunk ``` -Vagy írhatunk saját állításkezelőket. +Vagy írhatunk saját assert kezelőket. ```php Expect::that(function ($value) { - # return false if expectation fails + # false-t adunk vissza, ha az elvárás meghiúsul }); ``` -Sikertelen állítások vizsgálata .[#toc-failed-assertions-investigation] ------------------------------------------------------------------------ -A Tester megmutatja, hol van a hiba, ha egy állítás sikertelen. Amikor összetett struktúrákat hasonlítunk össze, a Tester dumps-ot készít az összehasonlított értékekről, és elmenti azokat a `output` könyvtárba. Például amikor a képzeletbeli `Arrays.recursive.phpt` teszt sikertelen, a dumps a következőképpen kerül elmentésre: +Hibás assertok vizsgálata +------------------------- +Ha egy assert meghiúsul, a Tester kiírja, mi a hiba. Ha összetettebb struktúrákat hasonlítunk össze, a Tester dumpokat hoz létre az összehasonlított értékekről, és elmenti őket az `output` könyvtárba. Például egy képzeletbeli `Arrays.recursive.phpt` teszt meghiúsulása esetén a dumpok a következőképpen lesznek elmentve: ``` app/ └── tests/ ├── output/ - │ ├── Arrays.recursive.actual # actual value - │ └── Arrays.recursive.expected # expected value + │ ├── Arrays.recursive.actual # tényleges érték + │ └── Arrays.recursive.expected # várt érték │ - └── Arrays.recursive.phpt # failing test + └── Arrays.recursive.phpt # hibás teszt ``` -A könyvtár nevét a `Tester\Dumper::$dumpDir` címen változtathatjuk meg. +A könyvtár nevét a `Tester\Dumper::$dumpDir` segítségével módosíthatjuk. diff --git a/tester/hu/guide.texy b/tester/hu/guide.texy index d11610bf65..d3ae1172b0 100644 --- a/tester/hu/guide.texy +++ b/tester/hu/guide.texy @@ -1,17 +1,17 @@ -A Testerrel való kezdés -*********************** +Kezdjük a Nette Testerrel +*************************
                                                                                                                            -Még a jó programozók is követnek el hibákat. A különbség a jó és a rossz programozó között az, hogy a jó programozó csak egyszer követi el, és legközelebb automatizált tesztek segítségével észleli a hibát. +Még a jó programozók is követnek el hibákat. A különbség a jó és a rossz programozó között az, hogy a jó csak egyszer követi el, és legközelebb automatizált tesztek segítségével fedezi fel. -- "Aki nem tesztel, arra van ítélve, hogy megismételje a saját hibáit." (bölcs közmondás) -- "Amikor megszabadulunk egy hibától, megjelenik egy másik." (Murphy törvénye) -- "Amikor kísértésbe esel, hogy kinyomtasd a kijelentést, írd meg helyette tesztként." (Martin Fowler) +- "Aki nem tesztel, arra van ítélve, hogy megismételje hibáit." (közmondás) +- "Amint megszabadulunk egy hibától, megjelenik egy másik." (Murphy törvénye) +- "Amikor késztetést érzel arra, hogy kiírj egy változót a képernyőre, inkább írj egy tesztet." (Martin Fowler)
                                                                                                                            -Írtad már a következő kódot PHP-ben? +Írtál már valaha ehhez hasonló kódot PHP-ban? ```php $obj = new MyClass; @@ -20,40 +20,40 @@ $result = $obj->process($input); var_dump($result); ``` -Szóval, dobtál már ki egy függvényhívás eredményét, csak azért, hogy szemrevételezéssel ellenőrizd, hogy azt adja-e vissza, amit vissza kell adnia? Bizonyára naponta többször is megteszed. Tenyeret a szívedre téve, ha minden működik, törlöd ezt a kódot, és elvárod, hogy az osztály a jövőben nem fog elromlani? Murphy törvénye garantálja az ellenkezőjét :-) +Tehát kiírtad a függvényhívás eredményét csak azért, hogy szemmel ellenőrizd, hogy azt adja-e vissza, amit kell? Biztosan ezt teszed naponta sokszor. Őszintén: abban az esetben, ha minden rendben működik, törlöd ezt a kódot? Arra számítasz, hogy az osztály a jövőben nem fog elromlani? Murphy törvényei az ellenkezőjét garantálják :-) -Tulajdonképpen te írtad a tesztet. Kisebb módosításra szorul, hogy ne legyen szükség a mi ellenőrzésünkre, egyszerűen csak képes legyen önmagát ellenőrizni. És ha nem törölted, akkor a jövőben bármikor lefuttathatjuk, hogy ellenőrizzük, hogy még mindig minden úgy működik, ahogyan kell. Idővel nagy mennyiségű ilyen tesztet hozhatsz létre, ezért jó lenne, ha automatikusan lefuttathatnánk őket. +Lényegében írtál egy tesztet. Csak kissé módosítani kell, hogy ne igényeljen szemrevételezést, hanem önmagát ellenőrizze. És ha nem törlöd a tesztet, bármikor futtathatod a jövőben, és ellenőrizheted, hogy minden továbbra is úgy működik-e, ahogy kell. Idővel sok ilyen tesztet hozol létre, ezért jó lenne automatizáltan futtatni őket. -A Nette Tester pedig pontosan ebben segít. +És mindebben segít neked a Nette Tester. -Mi teszi egyedivé a Testert? .[#toc-what-makes-tester-unique] -============================================================= +Mitől egyedi a Tester? +====================== -A Nette Tester tesztek írása abban egyedülálló, hogy **minden teszt egy szabványos PHP szkript, amely önállóan is futtatható.** +A Nette Testerhez való tesztek írása abban egyedi, hogy **minden teszt egy szokásos PHP szkript, amelyet önállóan lehet futtatni.** -Így amikor tesztet ír, egyszerűen lefuttathatja, hogy megnézze, van-e programozási hiba. Ha megfelelően működik. Ha nem, akkor egyszerűen végigléphetsz a programon az IDE-ben, és megkeresheted a hibát. Akár meg is nyithatja egy böngészőben. +Tehát amikor tesztet írsz, egyszerűen futtathatod, és megállapíthatod, hogy nincs-e benne például programozási hiba. Hogy helyesen működik-e. Ha nem, könnyen lépésenként végigmehetsz rajta az IDE-dben, és keresheted a hibát. Akár meg is nyithatod a böngészőben. -És ami a legfontosabb - a futtatásával elvégzi a tesztet. Azonnal megtudod, hogy átment-e vagy nem ment át. Hogyan? Mutassuk meg itt. Írjunk egy triviális tesztet a PHP tömb használatára, és mentsük el a `ArrayTest.php` fájlba: +És mindenekelőtt - azzal, hogy futtatod, végrehajtod a tesztet. Azonnal megtudod, hogy átment-e vagy meghiúsult. Hogyan? Mutassuk meg. Írunk egy triviális tesztet a PHP tömbökkel való munkára, és elmentjük az `ArrayTest.php` fájlba: ```php .{file:ArrayTest.php} OK \-- -Próbáljuk meg a tesztben az utasítást `Assert::contains('XXX', $stack);` címre változtatni, és figyeljük meg, mi történik a futtatáskor: +Próbáld meg a tesztben az állítást hamisra változtatni: `Assert::contains('XXX', $stack);`, és figyeld meg, mi történik futtatáskor: /--pre .[terminal] $ php ArrayTest.php @@ -73,53 +73,53 @@ $ php ArrayTest.php FAILURE \-- -Az írásról a [Tesztek írása |Writing Tests] fejezetben folytatjuk. +A további írásról a [Tesztek írása|writing-tests] fejezetben folytatjuk. -Telepítés és követelmények .[#toc-installation-and-requirements] -================================================================ +Telepítés és követelmények +========================== -A Tester által megkövetelt minimális PHP-verzió a 7.1 (további részletekért lásd a [támogatott PHP-verziók |#supported PHP versions] táblázatot). A telepítés előnyben részesített módja a [Composer |best-practices:composer]: +A Tester által igényelt minimális PHP verzió 7.1 (részletesebben a [#támogatott-php-verziók] táblázatban). Az előnyben részesített telepítési mód a [Composer |best-practices:composer] segítségével történik: /--pre .[terminal] composer require --dev nette/tester \-- -Próbálja meg futtatni a Nette Testert a parancssorból (argumentumok nélkül csak egy súgóösszefoglalót fog mutatni): +Próbáld meg a parancssorból futtatni a Nette Testert (paraméterek nélkül csak a súgót írja ki): /--pre .[terminal] vendor/bin/tester \-- -A tesztek futtatása .[#toc-running-tests] -========================================= +Tesztek futtatása +================= -Ahogy alkalmazásunk növekszik, úgy növekszik vele együtt a tesztek száma is. Nem lenne praktikus a teszteket egyenként futtatni. Ezért a Tester rendelkezik egy tömeges tesztfuttatóval, amelyet a parancssorból hívunk meg. A paraméter az a könyvtár, amelyben a tesztek találhatók. A pont az aktuális könyvtárat jelzi. +Ahogy az alkalmazás növekszik, a tesztek száma is növekszik vele. Nem lenne praktikus a teszteket egyenként futtatni. Ezért a Tester rendelkezik egy tömeges tesztfuttatóval, amelyet a parancssorból hívunk meg. Paraméterként megadjuk azt a könyvtárat, amelyben a tesztek találhatók. A pont az aktuális könyvtárat jelenti. /--pre .[terminal] vendor/bin/tester . \-- -A Nette Tester futóprogram átnézi a megadott könyvtárat és az összes alkönyvtárat, és megkeresi a teszteket, amelyek a `*.phpt` és a `*Test.php` fájlok. A `ArrayTest.php` tesztünket is megtalálja, mivel az megfelel a maszknak. +A tesztfuttató átvizsgálja a megadott könyvtárat és az összes alkönyvtárat, és megkeresi a teszteket, amelyek `*.phpt` és `*Test.php` fájlok. Így megtalálja a mi `ArrayTest.php` tesztünket is, mivel megfelel a maszknak. -Ezután megkezdi a tesztelést. Minden egyes tesztet új PHP-folyamatként futtat, így az a többitől teljesen elszigetelten fut. Párhuzamosan fut több szálon, így rendkívül gyors. És először azokat a teszteket futtatja le, amelyek az előző futtatás során nem sikerültek, így rögtön tudni fogja, hogy kijavította-e a hibát. +Ezután elindítja a tesztelést. Minden tesztet új PHP folyamatként futtat, így teljesen izoláltan zajlik a többitől. Párhuzamosan futtatja őket több szálon, és ennek köszönhetően rendkívül gyors. És elsőként azokat a teszteket futtatja, amelyek az előző futtatáskor meghiúsultak, így azonnal megtudod, hogy sikerült-e kijavítanod a hibát. -A futó minden egyes elvégzett teszt után kiír egy karaktert a haladás jelzésére: +A tesztek végrehajtása során a Tester folyamatosan kiírja az eredményeket a terminálra karakterekként: -- . - teszt átment -- s - a tesztet kihagyta -- F - a teszt sikertelen +- . – a teszt sikeres volt (passed) +- s – a teszt kihagyva (skipped) +- F – a teszt meghiúsult (failed) A kimenet így nézhet ki: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +PHP 8.3.2 (cli) | php -n | 8 threads ........s................F......... @@ -132,44 +132,44 @@ PHP 7.4.8 (cli) | php -n | 8 threads FAILURES! (35 tests, 1 failures, 1 skipped, 1.7 seconds) \-- -A tesztek lefutottak, egy nem sikerült, egyet kihagytak. +35 teszt futott le, egy meghiúsult, egyet kihagytak. -Folytatjuk a [tesztek futtatása |Running tests] fejezetben. +Tovább folytatjuk a [Tesztek futtatása|running-tests] fejezetben. -Figyelő üzemmód .[#toc-watch-mode] -================================== +Watch mód +========= -Refaktorálod a kódot? Vagy egyáltalán a TDD (Test Driven Development) módszertan szerint fejlesztesz? Akkor tetszeni fog a figyelési mód. A Tester figyeli a forráskódokat, és lefuttatja magát, ha változik. +Refaktorálod a kódot? Vagy akár a TDD (Test Driven Development) módszertan szerint fejlesztesz? Akkor tetszeni fog a watch mód. A Tester ebben figyeli a forráskódokat, és változás esetén önmagát futtatja. -Fejlesztés közben van egy terminál a monitor sarkában, ahol a zöld státuszsáv világít rád, és amikor hirtelen pirosra vált, tudod, hogy éppen valami nem kívánt dolgot csináltál. Ez tulajdonképpen egy remek játék, ahol programozol, és próbálod betartani a színt. +Fejlesztés közben így a monitor sarkában van egy terminál, ahol zöld állapotjelző sor világít rád, és amikor hirtelen pirosra vált, tudod, hogy éppen valamit nem csináltál teljesen jól. Ez tulajdonképpen egy nagyszerű játék, ahol programozol, és próbálod tartani a színt. -A Watch módot a [--watch |running-tests#w-watch-path] paraméterrel indíthatod el. +A Watch mód a [--watch |running-tests#-w --watch path] paraméterrel indítható. -CodeCoverage jelentések .[#toc-codecoverage-reports] -==================================================== +Kódlefedettségi jelentések +========================== -A Tester jelentéseket készíthet, amelyek áttekintik, hogy a tesztek mennyi forráskódot fednek le. A jelentés készülhet ember által olvasható HTML formátumban vagy Clover XML formátumban a további gépi feldolgozáshoz. +A Tester képes jelentéseket generálni arról, hogy a tesztek mennyi forráskódot fednek le. A jelentés lehet ember által olvasható HTML formátumban, vagy Clover XML formátumban további gépi feldolgozáshoz. -Lásd a "minta HTML-jelentést":https://files.nette.org/tester/coverage.html a kódlefedettséggel. +Nézze meg a "HTML jelentés példája":attachment:coverage.html a kódlefedettséggel. -Támogatott PHP verziók .[#toc-supported-php-versions] -===================================================== +Támogatott PHP verziók +====================== -| verzió | kompatibilis a PHP-vel +| verzió | kompatibilis PHP verzió |------------------|------------------- -| Tester 2.5 | PHP 8.0 - 8.2 -| Tester 2.4 | PHP 7.2 - 8.2 -| Tester 2.3 | PHP 7.1 - 8.0 -| Tester 2.1 - 2.2 | PHP 7.1 - 7.3 -| Tester 2.0 | PHP 5.6 - 7.3 -| Tester 1.7 | PHP 5.3 - 7.3 + HHVM 3.3+ -| Tester 1.6 | PHP 5.3 - 7.0 + HHVM -| Tester 1.3 - 1.5 | PHP 5.3 - 5.6 + HHVM -| Tester 0.9 - 1.2 | PHP 5.3 - 5.6 - -A legújabb javítási verziókra vonatkozik. - -Az 1.7-es verzióig a Tester támogatta [a HHVM |https://hhvm.com] 3.3.0 vagy újabb verziót (a `tester -p hhvm` segítségével). A Tester 2.0 óta a támogatás megszűnt. A használat egyszerű volt: +| Tester 2.5 | PHP 8.0 – 8.3 +| Tester 2.4 | PHP 7.2 – 8.2 +| Tester 2.3 | PHP 7.1 – 8.0 +| Tester 2.1 – 2.2 | PHP 7.1 – 7.3 +| Tester 2.0 | PHP 5.6 – 7.3 +| Tester 1.7 | PHP 5.3 – 7.3 + HHVM 3.3+ +| Tester 1.6 | PHP 5.3 – 7.0 + HHVM +| Tester 1.3 – 1.5 | PHP 5.3 – 5.6 + HHVM +| Tester 0.9 – 1.2 | PHP 5.3 – 5.6 + +Az utolsó patch verzióra érvényes. + +A Tester 1.7-es verzióig támogatta a [HHVM |https://hhvm.com] 3.3.0 vagy újabb verzióját is (a `tester -p hhvm` segítségével). A támogatás a Tester 2.0 verziójától megszűnt. diff --git a/tester/hu/helpers.texy b/tester/hu/helpers.texy index 13e4a27c04..7879108fdb 100644 --- a/tester/hu/helpers.texy +++ b/tester/hu/helpers.texy @@ -1,31 +1,45 @@ -Segítők -******* +Segédosztályok +************** -DomQuery .[#toc-domquery] -------------------------- -`Tester\DomQuery` egy olyan osztály, amely a `SimpleXMLElement` címet bővíti olyan metódusokkal, amelyek megkönnyítik a HTML- vagy XML-tartalom tesztelését. +DomQuery +-------- +A `Tester\DomQuery` egy osztály, amely kiterjeszti a `SimpleXMLElement`-et a HTML vagy XML dokumentumokban való könnyű kereséshez CSS szelektorok segítségével. ```php -# let's have an HTML document in $html that we load -$dom = Tester\DomQuery::fromHtml($html); - -# we can test the presence of elements using CSS selectors -Assert::true($dom->has('form#registration')); -Assert::true($dom->has('input[name="username"]')); -Assert::true($dom->has('input[type="submit"]')); - -# or select elements as array of DomQuery -$elems = $dom->find('input[data-autocomplete]'); +# DomQuery létrehozása HTML stringből +$dom = Tester\DomQuery::fromHtml(' +
                                                                                                                            +

                                                                                                                            Title

                                                                                                                            +
                                                                                                                            Text
                                                                                                                            +
                                                                                                                            +'); + +# Elemek létezésének tesztelése CSS szelektorokkal +Assert::true($dom->has('article.post')); +Assert::true($dom->has('h1')); + +# Elemek megtalálása DomQuery objektumok tömbjeként +$headings = $dom->find('h1'); +Assert::same('Title', (string) $headings[0]); + +# Teszt, hogy az elem megfelel-e a szelektornak (2.5.3 verziótól) +$content = $dom->find('.content')[0]; +Assert::true($content->matches('div')); +Assert::false($content->matches('p')); + +# Legközelebbi, szelektornak megfelelő ős megtalálása (2.5.5-től) +$article = $content->closest('.post'); +Assert::true($article->matches('article')); ``` -FileMock .[#toc-filemock] -------------------------- -`Tester\FileMock` fájlokat emulál a memóriában, hogy segítsen tesztelni egy olyan kódot, amely olyan függvényeket használ, mint a `fopen()`, `file_get_contents()` vagy `parse_ini_file()`. Például: +FileMock +-------- +A `Tester\FileMock` memóriában emulálja a fájlokat, és így megkönnyíti az olyan kód tesztelését, amely a `fopen()`, `file_get_contents()`, `parse_ini_file()` és hasonló függvényeket használja. Használati példa: ```php -# Tested class +# Tesztelt osztály class Logger { public function __construct( @@ -39,21 +53,21 @@ class Logger } } -# New empty file +# Új üres fájl $file = Tester\FileMock::create(''); $logger = new Logger($file); $logger->log('Login'); $logger->log('Logout'); -# Created content testing +# Teszteljük a létrehozott tartalmat Assert::same("Login\nLogout\n", file_get_contents($file)); ``` Assert::with() .[filter] ------------------------ -Ez nem egy állítás, hanem egy segédprogram a privát metódusok és tulajdonságobjektumok teszteléséhez. +Ez nem egy assert, hanem egy segítő az objektumok privát metódusainak és property-jeinek tesztelésére. ```php class Entity @@ -65,17 +79,17 @@ class Entity $ent = new Entity; Assert::with($ent, function () { - Assert::true($this->enabled); // hozzáférhető privát $ent->enabled + Assert::true($this->enabled); // hozzáférhetővé tett privát $ent->enabled }); ``` Helpers::purge() .[filter] -------------------------- -A `purge()` metódus létrehozza a megadott könyvtárat, és ha már létezik, törli annak teljes tartalmát. Ez praktikus az ideiglenes könyvtárak létrehozásához. Például a `tests/bootstrap.php`: +A `purge()` metódus létrehozza a megadott könyvtárat, és ha már létezik, törli annak teljes tartalmát. Hasznos ideiglenes könyvtár létrehozására. Például a `tests/bootstrap.php`-ban: ```php -@mkdir(__DIR__ . '/tmp'); # @ - directory may already exist +@mkdir(__DIR__ . '/tmp'); # @ - a könyvtár már létezhet define('TempDir', __DIR__ . '/tmp/' . getmypid()); Tester\Helpers::purge(TempDir); @@ -84,25 +98,25 @@ Tester\Helpers::purge(TempDir); Environment::lock() .[filter] ----------------------------- -A tesztek párhuzamosan futnak. Néha nincs szükségünk arra, hogy a tesztek futása ne fedje egymást. Jellemzően az adatbázis-teszteknek elő kell készíteniük az adatbázis tartalmát, és nem kell, hogy a teszt futási ideje alatt semmi ne zavarja őket. Ezekben az esetekben a `Tester\Environment::lock($name, $dir)` címet használjuk: +A tesztek párhuzamosan futnak. Néha azonban szükségünk van arra, hogy a tesztek futása ne fedje át egymást. Tipikusan adatbázis teszteknél szükséges, hogy a teszt előkészítse az adatbázis tartalmát, és egy másik teszt a futása alatt ne nyúljon az adatbázishoz. Ezekben a tesztekben használjuk a `Tester\Environment::lock($name, $dir)`-t: ```php Tester\Environment::lock('database', __DIR__ . '/tmp'); ``` -Az első argumentum a zár neve. A második a zárolás mentésére szolgáló könyvtár elérési útvonala. A zárat megszerző teszt fut le először. A többi tesztnek meg kell várnia, amíg a lezárás befejeződik. +Az első paraméter a zár neve, a második az elérési út a zár tárolására szolgáló könyvtárhoz. Az a teszt, amelyik először megszerzi a zárat, lefut, a többi tesztnek várnia kell a befejezésére. Environment::bypassFinals() .[filter] ------------------------------------- -A `final` címmel jelölt osztályokat vagy metódusokat nehéz tesztelni. A `Tester\Environment::bypassFinals()` meghívása egy teszt kezdetén azt okozza, hogy a `final` kulcsszavak a kód betöltése során eltávolításra kerülnek. +A `final`-ként megjelölt osztályokat vagy metódusokat nehéz tesztelni. A `Tester\Environment::bypassFinals()` hívása a teszt elején azt eredményezi, hogy a `final` kulcsszavak a kód betöltése során kimaradnak. ```php require __DIR__ . '/bootstrap.php'; Tester\Environment::bypassFinals(); -class MyClass extends NormallyFinalClass # <-- NormallyFinalClass is not final anymore +class MyClass extends NormallyFinalClass # <-- A NormallyFinalClass már nem final { // ... } @@ -111,18 +125,18 @@ class MyClass extends NormallyFinalClass # <-- NormallyFinalClass is not final Environment::setup() .[filter] ------------------------------ -- javítja a hibadump olvashatóságát (színezéssel együtt), egyébként alapértelmezett PHP stack trace kerül kiírásra. -- lehetővé teszi annak ellenőrzését, hogy az állítások meg lettek-e hívva a tesztben, különben a (pl. elfelejtett) állítások nélküli tesztek is átmennek. -- automatikusan elindítja a kódlefedettségi gyűjtőt, ha a `--coverage` címet használja (később ismertetjük). -- kiírja az OK vagy FAILURE állapotot a szkript végén. +- javítja a hibakiírás olvashatóságát (beleértve a színezést), különben az alapértelmezett PHP stack trace jelenik meg +- bekapcsolja annak ellenőrzését, hogy a tesztben hívtak-e assertokat, különben az assertok nélküli teszt (például elfelejtett) is sikeres lesz +- a `--coverage` használatakor automatikusan elindítja a futtatott kódról szóló információk gyűjtését (tovább leírva) +- kiírja az OK vagy FAILURE állapotot a szkript végén Environment::setupFunctions() .[filter]{data-version:2.5} --------------------------------------------------------- -Létrehozza a `test()`, `setUp()` és `tearDown()` globális függvényeket, amelyekre a teszteket feloszthatja. +Létrehozza a globális `test()`, `testException()`, `setUp()` és `tearDown()` függvényeket, amelyekbe tagolhatja a teszteket. ```php -test('test description', function () { +test('teszt leírása', function () { Assert::same(123, foo()); Assert::false(bar()); // ... @@ -132,21 +146,21 @@ test('test description', function () { Environment::VariableRunner .[filter] ------------------------------------- -Lehetővé teszi, hogy megtudja, hogy a tesztet közvetlenül vagy a Tester segítségével futtatták-e. +Lehetővé teszi annak megállapítását, hogy a tesztet közvetlenül futtatták-e, vagy a Tester segítségével. ```php if (getenv(Tester\Environment::VariableRunner)) { - # run by Tester + # Tester által futtatva } else { - # another way + # Másképp futtatva } ``` Environment::VariableThread .[filter] ------------------------------------- -A Tester párhuzamosan futtatja a teszteket adott számú szálon. A szálszámot egy környezeti változóban találjuk meg, ha érdekel minket: +A Tester párhuzamosan futtatja a teszteket a megadott számú szálon. Ha érdekel minket a szál száma, azt a környezeti változóból tudjuk meg: ```php -echo "I'm running in a thread number " . getenv(Tester\Environment::VariableThread); +echo "A " . getenv(Tester\Environment::VariableThread) . ". számú szálban futok"; ``` diff --git a/tester/hu/running-tests.texy b/tester/hu/running-tests.texy index a44e03b88a..e97e081cca 100644 --- a/tester/hu/running-tests.texy +++ b/tester/hu/running-tests.texy @@ -2,54 +2,54 @@ Tesztek futtatása ***************** .[perex] -A Nette Tester leglátványosabb része a parancssori tesztfutó. Rendkívül gyors és robusztus, mert automatikusan elindítja az összes tesztet különálló folyamatként, párhuzamosan, több szálban. Képes önmagát is futtatni az úgynevezett watch módban. +A Nette Tester leglátványosabb része a parancssori tesztfuttató. Rendkívül gyors és robusztus, mivel automatikusan minden tesztet külön folyamatként indít el, és ezt párhuzamosan, több szálon teszi. Képes önmagát is futtatni az ún. watch módban. -A Nette Tester tesztfutó a parancssorból hívható meg. Paraméterként átadjuk a tesztkönyvtárat. Az aktuális könyvtárhoz csak egy pontot adjunk meg: +A tesztfuttatót a parancssorból hívjuk meg. Paraméterként megadjuk a teszteket tartalmazó könyvtárat. Az aktuális könyvtárhoz elegendő egy pontot megadni: /--pre .[terminal] vendor/bin/tester . \-- -Amikor meghívjuk, a tesztfutó átvizsgálja a megadott könyvtárat és az összes alkönyvtárat, és teszteket keres, amelyek a `*.phpt` és a `*Test.php` fájlok. Elolvassa és kiértékeli azok [megjegyzéseit |test-annotations] is, hogy tudja, melyiket és hogyan kell futtatni. +A tesztfuttató átvizsgálja a megadott könyvtárat és az összes alkönyvtárat, és megkeresi a teszteket, amelyek `*.phpt` és `*Test.php` fájlok. Egyúttal olvassa és kiértékeli azok [annotációit|test-annotations], hogy tudja, melyiket és hogyan kell futtatni. -Ezután végrehajtja a teszteket. A futó minden egyes elvégzett teszt után kiír egy karaktert a haladás jelzésére: +Ezután elindítja a teszteket. A tesztek végrehajtása során folyamatosan kiírja az eredményeket a terminálra karakterekként: -- . - teszt átment -- s - a tesztet kihagyta -- F - a teszt sikertelen +- . – a teszt sikeres volt (passed) +- s – a teszt kihagyva (skipped) +- F – a teszt meghiúsult (failed) -A kimenet így nézhet ki: +A kimenet például így nézhet ki: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +PHP 8.3.2 (cli) | php -n | 8 threads ........s.......................... OK (35 tests, 1 skipped, 1.7 seconds) \-- -Ha újra futtatja a programot, először azokat a teszteket futtatja le, amelyek az előző futtatás során sikertelenek voltak, így rögtön tudni fogja, hogy kijavította-e a hibát. +Ismételt futtatáskor először azokat a teszteket hajtja végre, amelyek az előző futtatáskor meghiúsultak, így azonnal megtudhatja, hogy sikerült-e kijavítania a hibát. -A tesztelő kilépési kódja nulla, ha egyik sem hibázik. Egyébként nem nulla. +Ha egyetlen teszt sem hiúsul meg, a Tester visszatérési értéke nulla. Ellenkező esetben a visszatérési érték nem nulla. .[warning] -A Tester a PHP-folyamatokat a `php.ini` nélkül futtatja. További részletek a [Saját php.ini |#Own php.ini] szakaszban. +A Tester a PHP folyamatokat `php.ini` nélkül indítja. Részletesebben a [#Saját php.ini] részben. -Parancssori beállítások .[#toc-command-line-options] -==================================================== +Parancssori paraméterek +======================= -A parancssori opciók áttekintését a Tester paraméterek nélküli vagy a `-h` opcióval történő futtatásával kapjuk meg: +Az összes parancssori opció áttekintését a Tester paraméterek nélküli, vagy a `-h` paraméterrel történő futtatásával kapjuk meg: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Usage: tester [options] [ | ]... @@ -58,16 +58,16 @@ Options: -p Specify PHP interpreter to run (default: php). -c Look for php.ini file (or look in directory) . -C Use system-wide php.ini. - -l | --log Write log to file . -d ... Define INI entry 'key' with value 'value'. -s Show information about skipped tests. --stop-on-fail Stop execution upon the first failure. -j Run jobs in parallel (default: 8). - -o Specify output format. + -o (e.g. -o junit:output.xml) + Specify one or more output formats with optional file name. -w | --watch Watch directory. -i | --info Show tests environment info and exit. --setup Script for runner setup. - --temp Path to temporary directory. Default: sys_get_temp_dir(). + --temp Path to temporary directory. Default by sys_get_temp_dir(). --colors [1|0] Enable or disable colors. --coverage Generate code coverage report to file. --coverage-src Path to source code. @@ -77,7 +77,7 @@ Options: -p .[filter] ------------------- -Megadja a tesztek futtatásához használt PHP bináris programot. Alapértelmezés szerint ez a `php`. +Meghatározza a PHP binárist, amelyet a tesztek futtatásához használnak. Alapértelmezés szerint ez `php`. /--pre .[terminal] tester -p /home/user/php-7.2.0-beta/php-cgi tests @@ -86,26 +86,17 @@ tester -p /home/user/php-7.2.0-beta/php-cgi tests -c .[filter] ------------------- -Megadja, hogy a tesztek futtatásakor melyik `php.ini` oldalt használja a rendszer. Alapértelmezés szerint nem használ php.ini-t. További információért lásd a [Saját php.ini-t |#Own php.ini]. +Meghatározza, melyik `php.ini` kerül felhasználásra a tesztek futtatásakor. Alapértelmezett állapotban egyetlen php.ini sem kerül felhasználásra. Több információ a [#Saját php.ini] részben. -C .[filter] ------------ -Egy rendszerszintű `php.ini` kerül felhasználásra. Tehát UNIX platformon az összes `/etc/php/{sapi}/conf.d/*.ini` fájl is. Lásd a [saját php.ini |#Own php.ini] részt. - - -''-l | --log '' .[filter] -------------------------------- -A tesztelés előrehaladása kiíródik a fájlba. Minden sikertelen, kihagyott és sikeres tesztet: - -/--pre .[terminal] -tester --log /var/log/tests.log tests -\-- +A rendszer `php.ini`-je kerül felhasználásra. UNIX-on az összes vonatkozó INI fájl is `/etc/php/{sapi}/conf.d/*.ini`. Több információ a [#Saját php.ini] részben. -d .[filter] ------------------------ -A tesztek PHP konfigurációs direktíva értékének beállítása. A paraméter többször is használható. +Beállítja a PHP konfigurációs direktíva értékét a tesztekhez. A paraméter többször is használható. /--pre .[terminal] tester -d max_execution_time=20 @@ -114,34 +105,36 @@ tester -d max_execution_time=20 -s --- -A kihagyott tesztekről szóló információk megjelennek. +Információ jelenik meg a kihagyott tesztekről. --stop-on-fail .[filter] ------------------------ -A tesztelő az első sikertelen tesztnél leállítja a tesztelést. +A Tester leállítja a tesztelést az első meghiúsult tesztnél. -j .[filter] ------------------ -A tesztek futtatása egy `` párhuzamos előfeldolgozásban. Az alapértelmezett érték 8. Ha a teszteket sorban szeretnénk futtatni, akkor az 1 értéket használjuk. +Meghatározza, hány párhuzamos folyamat induljon a tesztekkel. Az alapértelmezett érték 8. Ha azt szeretnénk, hogy az összes teszt sorozatban fusson le, használjuk az 1 értéket. --o .[filter] -------------------------------------- -Kimeneti formátum. Az alapértelmezett a konzol formátum. +-o .[filter] +------------------------------------------------------- +Beállítja a kimeneti formátumot. Az alapértelmezett a konzol formátum. Megadhat egy fájlnevet, amelybe a kimenet íródik (pl. `-o junit:output.xml`). Az `-o` opció többször is megismételhető, így egyszerre több formátumot is generálhat. -- `console`: ugyanaz, mint az alapértelmezett, de az ASCII logó ebben az esetben nem kerül kiírásra. -- `tap`: gépi feldolgozásra alkalmas [TAP formátum |https://en.wikipedia.org/wiki/Test_Anything_Protocol]. -- `junit`: JUnit XML formátum, gépi feldolgozásra is alkalmas. -- `none`: semmi sem kerül kinyomtatásra +- `console`: megegyezik az alapértelmezett formátummal, de ebben az esetben nem jelenik meg az ASCII logó +- `console-lines`: hasonló a console-hoz, de minden teszt eredménye külön sorban jelenik meg további információkkal +- `tap`: [TAP formátum |https://en.wikipedia.org/wiki/Test_Anything_Protocol] alkalmas gépi feldolgozásra +- `junit`: JUnit XML formátum, szintén alkalmas gépi feldolgozásra +- `log`: A tesztelés folyamatának kimenetei. Minden meghiúsult, kihagyott és sikeres teszt +- `none`: semmi sem íródik ki ''-w | --watch '' .[filter] --------------------------------- -A Tester nem ér véget a tesztek befejezése után, hanem tovább fut és figyeli a PHP fájlokat a megadott könyvtárban. Ha változtat, újra lefuttatja a teszteket. A paraméter többször is használható, ha több könyvtárat szeretnénk figyelni. +A tesztelés befejezése után a Tester nem fejeződik be, hanem tovább fut, és figyeli a PHP fájlokat a megadott könyvtárban. Változás esetén újra elindítja a teszteket. A paraméter többször is használható, ha több könyvtárat szeretnénk figyelni. -Ez praktikus egy könyvtár refaktorálása vagy tesztek hibakeresése során. +Hasznos egy könyvtár refaktorálásakor vagy tesztek debuggolásakor. /--pre .[terminal] tester --watch src tests @@ -150,7 +143,7 @@ tester --watch src tests ''-i | --info'' .[filter] ------------------------- -Információkat mutat a teszt futtatási környezetéről. Például: +Információkat jelenít meg a tesztek futási környezetéről. Például: /--pre .[terminal] tester -p /usr/bin/php7.1 -c tests/php.ini --info @@ -175,64 +168,64 @@ Core, ctype, date, dom, ereg, fileinfo, filter, hash, ... \-- --- .[filter] -------------------- -A Tester indításkor betölti a megadott PHP-szkriptet. A `Tester\Runner\Runner $runner` változó elérhető benne. Tegyük fel, hogy a `tests/runner-setup.php` fájl : +--setup .[filter] +------------------------ +A Tester indításkor betölti a megadott PHP szkriptet. Ebben elérhető a `Tester\Runner\Runner $runner` változó. Tegyük fel, hogy van egy `tests/runner-setup.php` fájlunk a következő tartalommal: ```php $runner->outputHandlers[] = new MyOutputHandler; ``` -és futtassuk a Testert: +Indítsuk el a Testert: /--pre .[terminal] tester --setup tests/runner-setup.php tests \-- --- .[filter] -------------------- -Beállítja a Tester ideiglenes fájljainak könyvtárához vezető elérési utat. Az alapértelmezett értéket a `sys_get_temp_dir()` adja vissza. Ha az alapértelmezett érték nem érvényes, akkor észreveszi. +--temp .[filter] +----------------------- +Beállítja az elérési utat a Tester ideiglenes fájljainak könyvtárához. Az alapértelmezett értéket a `sys_get_temp_dir()` adja vissza. Ha az alapértelmezett érték nem érvényes, figyelmeztetést kap. -Ha nem vagyunk biztosak abban, hogy melyik könyvtárat használja, akkor a `--info` paraméterrel futtathatjuk a Testert. +Ha nem vagyunk biztosak abban, hogy melyik könyvtár használatos, indítsuk el a Testert a `--info` paraméterrel. --colors 1|0 .[filter] ---------------------- -A Tester alapértelmezésben felismeri a színezhető terminált, és színezi a kimenetét. Ez az opció az automatikus felismerés felett áll. A színezést globálisan a `NETTE_TESTER_COLORS` rendszerkörnyezeti változóval állíthatjuk be. +Alapértelmezés szerint a Tester észleli a színes terminált, és színezi a kimenetét. Ez az opció felülbírálja az automatikus észlelést. Globálisan a színezést a `NETTE_TESTER_COLORS` rendszer környezeti változóval állíthatjuk be. --coverage .[filter] --------------------------- -A tesztelő egy jelentést fog generálni, amely áttekintést ad arról, hogy a tesztek mennyire fedik le a forráskódot. Ez az opció PHP [Xdebug |https://xdebug.org/] vagy [PCOV |https://github.com/krakjoe/pcov] kiterjesztést igényel, vagy PHP 7-et a PHPDBG SAPI-val, ami gyorsabb. A célfájl kiterjesztése határozza meg a tartalom formátumát. HTML vagy Clover XML. +A Tester jelentést generál arról, hogy a tesztek mennyi forráskódot fednek le. Ehhez az opcióhoz telepített [Xdebug |https://xdebug.org/] PHP kiterjesztés, vagy [PCOV |https://github.com/krakjoe/pcov], vagy PHP 7 PHPDBG SAPI szükséges, amely gyorsabb. A célfájl kiterjesztése határozza meg annak formátumát. Vagy HTML, vagy Clover XML. /--pre .[terminal] -tester tests --coverage coverage.html # HTML report -tester tests --coverage coverage.xml # Clover XML report +tester tests --coverage coverage.html # HTML jelentés +tester tests --coverage coverage.xml # Clover XML jelentés \-- -A gyűjtési mechanizmus kiválasztásának prioritása a következő: +A mechanizmus kiválasztásának prioritása a következő: 1) PCOV 2) PHPDBG 3) Xdebug -A kiterjedt tesztek a PHPDBG futtatása során a memória kimerülése miatt meghiúsulhatnak. A lefedettségi adatok gyűjtése memóriaigényes művelet. Ebben az esetben a `Tester\CodeCoverage\Collector::flush()` meghívása a teszten belül segíthet. Ez az összegyűjtött adatokat fájlba mossa, és felszabadítja a memóriát. Ha az adatgyűjtés nem folyik, vagy az Xdebug használatban van, a hívásnak nincs hatása. +PHPDBG használatakor terjedelmes teszteknél előfordulhat, hogy a teszt memóriahiány miatt meghiúsul. A lefedett kódról szóló információk gyűjtése memóriaigényes. Ebben az esetben segít a `Tester\CodeCoverage\Collector::flush()` hívása a teszten belül. Kiírja az összegyűjtött adatokat a lemezre, és felszabadítja a memóriát. Ha az adatgyűjtés nem zajlik, vagy Xdebugot használunk, a hívásnak nincs hatása. -"Egy példa a HTML-jelentésre":https://files.nette.org/tester/coverage.html kódlefedettséggel. +"HTML jelentés példája":attachment:coverage.html a kódlefedettséggel. --coverage-src .[filter] ------------------------------- -A `--coverage` opcióval egyidejűleg használjuk. A `` a forráskód elérési útvonala, amelyhez a jelentést generáljuk. Többször is használható. +A `--coverage` opcióval együtt használjuk. A `` az elérési út a forráskódokhoz, amelyekhez a jelentés generálódik. Többször is használható. -Saját php.ini .[#toc-own-php-ini] -================================= -A Tester a PHP-folyamatokat a `-n` opcióval futtatja, ami azt jelenti, hogy a `php.ini` nem töltődik be (UNIX-ban még a `/etc/php/conf.d/*.ini` sem). Ez biztosítja ugyanazt a környezetet a futtatott tesztek számára, de kikapcsolja a rendszer PHP által általában betöltött összes külső PHP-bővítményt. +Saját php.ini +============= +A Tester a PHP folyamatokat a `-n` paraméterrel indítja, ami azt jelenti, hogy egyetlen `php.ini` sem töltődik be. UNIX-on még a `/etc/php/conf.d/*.ini` fájlok sem. Ez biztosítja a tesztek futtatásához azonos környezetet, de kikapcsolja az összes, a rendszer PHP által általában betöltött PHP kiterjesztést is. -Ha meg akarja tartani a rendszerkonfigurációt, használja a `-C` paramétert. +Ha meg akarja őrizni a rendszer `php.ini` fájljainak betöltését, használja a `-C` paramétert. -Ha szüksége van néhány kiterjesztésre vagy speciális INI-beállításra, javasoljuk, hogy hozzon létre saját `php.ini` fájlt, és ossza szét a tesztek között. Ezután futtassuk a Tester-t a `-c` opcióval, pl. `tester -c tests/php.ini`. Az INI fájl így nézhet ki: +Ha szüksége van néhány kiterjesztésre vagy speciális INI beállításra a tesztekhez, javasoljuk saját `php.ini` fájl létrehozását, amelyet a tesztekkel együtt terjesztenek. A Testert ezután a `-c` paraméterrel indítjuk, például `tester -c tests/php.ini tests`, ahol az INI fájl így nézhet ki: ```ini [PHP] @@ -243,4 +236,4 @@ extension=php_pdo_pgsql.dll memory_limit=512M ``` -A Tester futtatása UNIX rendszerben `php.ini`, pl. `tester -c /etc/php/cgi/php.ini`, nem tölt be más INI-t a `/etc/php/conf.d/*.ini`. Ez a PHP viselkedése, nem a Testeré. +A Tester futtatása UNIX-on a rendszer `php.ini`-jével, például `tester -c /etc/php/cli/php.ini`, nem tölti be a többi INI-t a `/etc/php/conf.d/*.ini`-ből. Ez a PHP tulajdonsága, nem a Testeré. diff --git a/tester/hu/test-annotations.texy b/tester/hu/test-annotations.texy index c99730cac9..6644b3f0ab 100644 --- a/tester/hu/test-annotations.texy +++ b/tester/hu/test-annotations.texy @@ -1,16 +1,16 @@ -Teszt megjegyzések -****************** +Teszt annotációk +**************** .[perex] -A megjegyzések határozzák meg, hogy a teszteket hogyan kezelje a [parancssori tesztfutó |running-tests]. A tesztfájl elejére íródnak. +Az annotációk határozzák meg, hogyan kezelje a teszteket a [parancssori tesztfuttató|running-tests]. A tesztfájl elejére íródnak. -A megjegyzések nem érzékenyek a nagy- és kisbetűkre. Nincs hatásuk akkor sem, ha a tesztet kézzel, hagyományos PHP szkriptként futtatjuk. +Az annotációknál nem számít a kis- és nagybetűk mérete. Nincs hatásuk sem, ha a tesztet manuálisan futtatják, mint egy szokásos PHP szkriptet. Példa: ```php /** - * TEST: Basic database query test. + * TEST: Alapvető adatbázis-lekérdezési teszt. * * @dataProvider files/databases.ini * @exitCode 56 @@ -23,17 +23,17 @@ require __DIR__ . '/../bootstrap.php'; TEST .[filter] -------------- -Ez valójában nem egy megjegyzés. Csak a teszt címét állítja be, amely a hiba esetén vagy a naplókba kerül kiírásra. +Ez valójában nem is annotáció, csupán a teszt címét határozza meg, amely hiba esetén vagy a naplóba íródik ki. @skip .[filter] --------------- -A teszt kihagyásra kerül. Praktikus a teszt ideiglenes kikapcsolásához. +A teszt kihagyásra kerül. Hasznos a tesztek ideiglenes kikapcsolására. @phpVersion .[filter] --------------------- -A teszt kihagyásra kerül, ha nem a megfelelő PHP-verzióval fut. A megjegyzést a következőképpen írjuk `@phpVersion [operator] version`. Az operátort elhagyhatjuk, alapértelmezett a `>=`. Példák: +A teszt kihagyásra kerül, ha nem a megfelelő PHP verzióval futtatják. Az annotációt `@phpVersion [operator] verzió` formában írjuk. Az operátort elhagyhatjuk, az alapértelmezett `>=`. Példák: ```php /** @@ -46,7 +46,7 @@ A teszt kihagyásra kerül, ha nem a megfelelő PHP-verzióval fut. A megjegyzé @phpExtension .[filter] ----------------------- -A teszt kihagyásra kerül, ha az összes említett PHP-bővítmény nincs betöltve. Több kiterjesztést is írhatunk egyetlen annotációba, vagy többször is használhatjuk az annotációt. +A teszt kihagyásra kerül, ha nem töltődnek be az összes megadott PHP kiterjesztés. Több kiterjesztést is megadhatunk egy annotációban, vagy használhatjuk többször is. ```php /** @@ -58,9 +58,9 @@ A teszt kihagyásra kerül, ha az összes említett PHP-bővítmény nincs betö @dataProvider .[filter] ----------------------- -Ez az annotáció akkor illik, ha a tesztet többször, de különböző adatokkal szeretnénk lefuttatni. (Nem tévesztendő össze a [TestCase |TestCase#dataProvider] azonos nevű annotációjával.) +Ha a tesztfájlt többször szeretnénk futtatni, de más bemeneti adatokkal, akkor ez az annotáció hasznos. (Ne keverjük össze az azonos nevű annotációval a [TestCase |TestCase#dataProvider] számára.) -Az annotációt úgy írjuk, hogy `@dataProvider file.ini`. Az INI fájl elérési útja a tesztfájlhoz képest relatív. A teszt annyiszor fut le, ahány szakasz található az INI fájlban. Tegyük fel, hogy az INI fájl `databases.ini`: +`@dataProvider file.ini` formában írjuk, a fájl elérési útja a tesztfájlhoz képest relatív. A teszt annyiszor fut le, ahány szekció van az INI fájlban. Tegyük fel, hogy van egy `databases.ini` INI fájlunk: ```ini [mysql] @@ -77,7 +77,7 @@ password = ****** dsn = "sqlite::memory:" ``` -és a `database.phpt` fájl ugyanabban a könyvtárban található: +és ugyanabban a könyvtárban a `database.phpt` teszt: ```php /** @@ -87,11 +87,11 @@ dsn = "sqlite::memory:" $args = Tester\Environment::loadData(); ``` -A teszt háromszor fut le, és a `$args` tartalmazni fogja a `mysql`, `postgresql` vagy `sqlite` szakaszok értékeit. +A teszt háromszor fut le, és az `$args` mindig a `mysql`, `postgresql` vagy `sqlite` szekció értékeit fogja tartalmazni. -Van még egy variáció, amikor kérdőjellel írunk megjegyzéseket, mint `@dataProvider? file.ini`. Ebben az esetben a teszt kihagyásra kerül, ha az INI fájl nem létezik. +Létezik még egy változat, amikor az annotációt kérdőjellel írjuk, mint `@dataProvider? file.ini`. Ebben az esetben a teszt kihagyásra kerül, ha az INI fájl nem létezik. -Az összes megjegyzési lehetőséget még nem említettük. Az INI fájl után feltételeket is írhatunk. A teszt csak akkor fut le az adott szakaszra, ha minden feltétel megfelel. Bővítsük ki az INI fájlt: +Ezzel az annotáció lehetőségei nem érnek véget. Az INI fájl neve után megadhatunk feltételeket, amelyek mellett a teszt az adott szekcióhoz fut le. Bővítsük ki az INI fájlt: ```ini [mysql] @@ -113,7 +113,7 @@ password = ****** dsn = "sqlite::memory:" ``` -és használjuk a feltételes megjegyzést: +és használjunk annotációt feltétellel: ```php /** @@ -121,9 +121,9 @@ dsn = "sqlite::memory:" */ ``` -A teszt csak egyszer fut le a `postgresql 9.1` szakaszra. A többi szakasz nem felel meg a feltételeknek. +A teszt csak egyszer fut le, és csak a `postgresql 9.1` szekcióhoz. A többi szekció nem megy át a feltétel szűrőjén. -Hasonlóképpen, INI helyett átadhatjuk egy PHP-szkript elérési útvonalát is. Ennek tömböt vagy Traversable-t kell visszaadnia. Fájl `databases.php`: +Hasonlóképpen, INI fájl helyett hivatkozhatunk PHP szkriptre is. Ennek tömböt vagy Traversable-t kell visszaadnia. `databases.php` fájl: ```php return [ @@ -142,29 +142,29 @@ return [ @multiple .[filter] ------------------- -Ezt a következőképpen írjuk: `@multiple N`, ahol `N` egy egész szám. A teszt pontosan N-szer fut le. +`@multiple N` formában írjuk, ahol `N` egy egész szám. A teszt pontosan N-szer fut le. @testCase .[filter] ------------------- -Az annotációnak nincsenek paraméterei. Akkor használjuk, amikor egy tesztet [TestCase |TestCase] osztályokként írunk. Ebben az esetben a parancssori tesztfutó az egyes metódusokat külön folyamatokban és párhuzamosan, több szálban fogja futtatni. Ez jelentősen felgyorsíthatja a teljes tesztelési folyamatot. +Az annotációnak nincsenek paraméterei. Akkor használjuk, ha a teszteket [TestCase |TestCase] osztályokként írjuk. Ebben az esetben a parancssori tesztfuttató az egyes metódusokat külön folyamatokban és párhuzamosan, több szálon futtatja. Ez jelentősen felgyorsíthatja az egész tesztelési folyamatot. @exitCode .[filter] ------------------- -Írjuk `@exitCode N` néven, ahol a tesztben a `N` is the exit code of the test. For example if `exit(10)` meghívásra kerül, ott az annotációt `@exitCode 10` néven írjuk. Sikertelennek tekintjük, ha a teszt más kóddal végződik. A 0 (nulla) kilépési kódot akkor ellenőrizzük, ha elhagyjuk a megjegyzést. +`@exitCode N` formában írjuk, ahol `N` a futtatott teszt visszatérési értéke. Ha a tesztben például `exit(10)` hívás van, az annotációt `@exitCode 10`-ként írjuk, és ha a teszt más kóddal fejeződik be, az hibának minősül. Ha az annotációt nem adjuk meg, a 0 (nulla) visszatérési érték kerül ellenőrzésre. @httpCode .[filter] ------------------- -A megjegyzés csak akkor kerül kiértékelésre, ha a PHP bináris CGI. Ellenkező esetben figyelmen kívül hagyja. Ezt `@httpCode NNN` -ként írjuk, ahol a `NNN` a várt HTTP-kód. A 200-as HTTP-kódot akkor ellenőrizzük, ha elhagyjuk a megjegyzést. Ha a `NNN` karakterláncot nullaként értékelt karakterláncként írjuk, például `any`, a HTTP-kódot egyáltalán nem ellenőrzi. +Az annotáció csak akkor érvényesül, ha a PHP bináris CGI. Egyébként figyelmen kívül hagyódik. `@httpCode NNN` formában írjuk, ahol `NNN` a várt HTTP kód. Ha az annotációt nem adjuk meg, a 200 HTTP kód kerül ellenőrzésre. Ha az `NNN`-t nullára kiértékelődő stringként írjuk, például `any`, a HTTP kód nem kerül ellenőrzésre. -@outputMatch a @outputMatchFile .[filter] ------------------------------------------ -Az annotációk viselkedése összhangban van a `Assert::match()` és a `Assert::matchFile()` állításokkal. De a minta megtalálható a teszt standard kimenetén. Megfelelő felhasználási eset, amikor feltételezzük, hogy a teszt végzetes hibával ér véget, és ellenőriznünk kell a kimenetét. +@outputMatch és @outputMatchFile .[filter] +------------------------------------------ +Az annotációk funkciója megegyezik az `Assert::match()` és `Assert::matchFile()` asszerciókéval. A minta (pattern) azonban abban a szövegben keresendő, amelyet a teszt a standard kimenetére küldött. Akkor hasznos, ha feltételezzük, hogy a teszt fatális hibával ér véget, és ellenőriznünk kell annak kimenetét. @phpIni .[filter] ----------------- -A teszt INI konfigurációs értékeit állítja be. Például a `@phpIni precision=20` címre írjuk, és ugyanúgy működik, mintha a parancssorból adnánk át értéket a `-d precision=20` paraméterrel. +A teszthez beállítja a konfigurációs INI értékeket. Például `@phpIni precision=20` formában írjuk, és ugyanúgy működik, mintha a parancssorból adtuk volna meg az értéket a `-d precision=20` paraméterrel. diff --git a/tester/hu/testcase.texy b/tester/hu/testcase.texy index e11b9777ae..026705788a 100644 --- a/tester/hu/testcase.texy +++ b/tester/hu/testcase.texy @@ -2,9 +2,9 @@ TestCase ******** .[perex] -Az egyszerű tesztekben az állítások egyenként követhetik egymást. Néha azonban hasznos az állításokat tesztosztályba foglalni és így strukturálni. +Egyszerű tesztekben az asszertek egymás után következhetnek. Néha azonban előnyösebb az asszerteket egy tesztosztályba csomagolni, és így strukturálni őket. -Az osztálynak a `Tester\TestCase` leszármazottjának kell lennie, és egyszerűen **testcase**-ként beszélünk róla. +Az osztálynak a `Tester\TestCase` leszármazottjának kell lennie, és egyszerűsítve **testcase**-nek nevezzük. Az osztálynak tartalmaznia kell `test`-tel kezdődő tesztmetódusokat. Ezek a metódusok tesztekként lesznek futtatva: ```php use Tester\Assert; @@ -22,11 +22,11 @@ class RectangleTest extends Tester\TestCase } } -# Run testing methods +# Tesztmetódusok futtatása (new RectangleTest)->run(); ``` -A `setUp()` és a `tearDown()` metódusokkal gazdagíthatjuk a teszteseteket. Ezeket minden tesztelési metódus előtt/után hívjuk meg: +Az így megírt tesztet tovább lehet gazdagítani `setUp()` és `tearDown()` metódusokkal. Ezek minden tesztmetódus előtt, illetve után hívódnak meg: ```php use Tester\Assert; @@ -35,12 +35,12 @@ class NextTest extends Tester\TestCase { public function setUp() { - # Preparation + # Előkészítés } public function tearDown() { - # Clean-up + # Takarítás } public function testOne() @@ -54,14 +54,14 @@ class NextTest extends Tester\TestCase } } -# Run testing methods +# Tesztmetódusok futtatása (new NextTest)->run(); /* -Method Calls Order ------------------- +Metódushívások sorrendje +------------------------ setUp() testOne() tearDown() @@ -72,9 +72,9 @@ tearDown() */ ``` -Ha hiba lép fel a `setUp()` vagy a `tearDown()` fázisban, a teszt sikertelen lesz. Ha a tesztelési metódusban történik hiba, a `tearDown()` metódus mindenképpen meghívásra kerül, de elnyomott hibákkal. +Ha hiba történik a `setUp()` vagy `tearDown()` fázisban, a teszt összességében meghiúsul. Ha hiba történik a tesztmetódusban, a `tearDown()` metódus ennek ellenére lefut, de a benne lévő hibák elnyomásával. -Javasoljuk, hogy a [@testCase |test-annotations#@testCase] annotációt írjuk a teszt elejére, ekkor a parancssori tesztfutó az egyes teszteset metódusokat külön folyamatokban és párhuzamosan, több szálban futtatja. Ez jelentősen felgyorsíthatja a teljes tesztelési folyamatot. +Javasoljuk, hogy a teszt elejére írjon [@testCase |test-annotations#testCase] annotációt, ekkor a parancssori tesztfuttató az egyes tesztcase metódusokat külön folyamatokban és párhuzamosan, több szálon futtatja. Ez jelentősen felgyorsíthatja az egész tesztelési folyamatot. /--php width = $width; $this->height = $height; @@ -53,7 +52,7 @@ class Rectangle } ``` -És létrehozunk hozzá egy tesztet. A tesztfájl nevének meg kell egyeznie a `*Test.php` vagy a `*.phpt` maszkkal, mi a `RectangleTest.php` változatot választjuk: +És létrehozunk hozzá egy tesztet. A tesztfájl nevének meg kell felelnie a `*Test.php` vagy `*.phpt` maszknak, válasszuk például a `RectangleTest.php` változatot: ```php .{file:tests/RectangleTest.php} @@ -62,31 +61,31 @@ use Tester\Assert; require __DIR__ . '/bootstrap.php'; -// általános hosszúkás +// általános téglalap $rect = new Rectangle(10, 20); -Assert::same(200.0, $rect->getArea()); # ellenőrizzük a várt eredményt. +Assert::same(200.0, $rect->getArea()); # ellenőrizzük a várt eredményeket Assert::false($rect->isSquare()); ``` -Mint látható, az olyan [állítási módszerek |Assertions], mint a `Assert::same()`, arra szolgálnak, hogy egy tényleges érték egyezzen egy elvárt értékkel. +Ahogy látod, az ún. [assert metódusok|assertions], mint az `Assert::same()`, arra szolgálnak, hogy megerősítsék, hogy a tényleges érték megfelel a várt értéknek. -Az utolsó lépés a `bootstrap.php` fájl létrehozása. Ez tartalmazza az összes teszt közös kódját. Például az osztályok automatikus betöltése, a környezet konfigurálása, ideiglenes könyvtár létrehozása, segédprogramok és hasonlók. Minden teszt betölti a bootstrapet és csak a tesztelésre figyel. A bootstrap így nézhet ki: +Már csak az utolsó lépés van hátra, ez a `bootstrap.php` fájl. Ez tartalmazza az összes teszthez közös kódot, például az osztályok autoloadingját, a környezet konfigurálását, ideiglenes könyvtár létrehozását, segédfüggvényeket és hasonlókat. Minden teszt betölti a bootstrapot, és tovább csak a teszteléssel foglalkozik. A bootstrap például így nézhet ki: ```php .{file:tests/bootstrap.php} OK \-- -Ha a tesztben megváltoztatjuk az utasítást false `Assert::same(123, $rect->getArea());`, ez fog történni: +Ha a tesztben az állítást hamisra változtatnánk: `Assert::same(123, $rect->getArea());`, ez történne: /--pre .[terminal] $ php RectangleTest.php @@ -107,12 +106,12 @@ $ php RectangleTest.php \-- -A tesztek írásakor jó, ha minden szélsőséges helyzetet elkapunk. Például, ha a bemenet nulla, negatív szám, más esetekben üres karakterlánc, null stb. Valójában arra kényszerít, hogy gondolkodjunk és eldöntsük, hogyan viselkedjen a kód ilyen helyzetekben. A tesztek aztán rögzítik a viselkedést. +Tesztek írásakor jó lefedni az összes szélsőséges helyzetet. Például, ha a bemenet nulla, negatív szám, más esetekben például üres string, null stb. Valójában ez arra kényszerít, hogy elgondolkodj, és eldöntsd, hogyan kell a kódnak viselkednie ilyen helyzetekben. A tesztek ezután rögzítik a viselkedést. -Esetünkben egy negatív értéknek kivételt kell dobnia, amit az [Assert::exception() |Assertions#Assert::exception] segítségével ellenőrizünk: +Esetünkben a negatív értéknek kivételt kell dobnia, amit az [Assert::exception() |Assertions#Assert::exception] segítségével ellenőrzünk: ```php .{file:tests/RectangleTest.php} -// a szélesség nem lehet negatív szám +// a szélesség nem lehet negatív Assert::exception( fn() => new Rectangle(-1, 20), InvalidArgumentException::class, @@ -120,22 +119,22 @@ Assert::exception( ); ``` -És hozzáadunk egy hasonló tesztet a magasságra. Végül teszteljük, hogy a `isSquare()` a `true` értéket adja vissza, ha mindkét dimenzió megegyezik. Próbáljunk meg gyakorlatként ilyen teszteket írni. +És hasonló tesztet adunk hozzá a magassághoz. Végül teszteljük, hogy az `isSquare()` `true`-t ad-e vissza, ha mindkét méret azonos. Próbálja meg gyakorlásként megírni ezeket a teszteket. -Jól elrendezett tesztek .[#toc-well-arranged-tests] -=================================================== +Áttekinthetőbb tesztek +====================== -A tesztfájl mérete megnőhet, és gyorsan zsúfolttá válhat. Ezért praktikus az egyes tesztelt területeket külön függvényekbe csoportosítani. +A tesztfájl mérete növekedhet, és gyorsan áttekinthetetlenné válhat. Ezért praktikus az egyes tesztelt területeket különálló függvényekbe csoportosítani. -Először egy egyszerűbb, de elegáns változatot mutatunk be, a `test()` globális függvényt használva. A tesztelő nem hozza létre automatikusan, hogy elkerüljük az ütközést, ha a kódunkban volt egy azonos nevű függvény. Csak a `setupFunctions()` metódus hozza létre, amelyet a `bootstrap.php` fájlban hívunk meg: +Először egy egyszerűbb, de elegánsabb változatot mutatunk be, a globális `test()` függvény segítségével. A Tester nem hozza létre automatikusan, hogy ne legyen ütközés, ha a kódban azonos nevű függvény lenne. Csak a `setupFunctions()` metódus hozza létre, amelyet a `bootstrap.php` fájlban hívjon meg: ```php .{file:tests/bootstrap.php} Tester\Environment::setup(); Tester\Environment::setupFunctions(); ``` -Ezzel a függvénnyel szépen fel tudjuk osztani a tesztfájlt névre szóló egységekre. Végrehajtáskor a címkék egymás után jelennek meg. +Ezzel a függvénnyel szépen feloszthatjuk a tesztfájlt elnevezett egységekre. Futtatáskor a leírások sorban kiíródnak. ```php .{file:tests/RectangleTest.php} getArea()); Assert::false($rect->isSquare()); }); -test('general square', function () { +test('általános négyzet', function () { $rect = new Rectangle(5, 5); Assert::same(25.0, $rect->getArea()); Assert::true($rect->isSquare()); }); -test('dimensions must not be negative', function () { +test('a méretek nem lehetnek negatívak', function () { Assert::exception( fn() => new Rectangle(-1, 20), InvalidArgumentException::class, @@ -168,15 +167,15 @@ test('dimensions must not be negative', function () { }); ``` -Ha az egyes tesztek előtt vagy után kell futtatni a kódot, adja át a `setUp()` vagy a `tearDown()` címre: +Ha minden teszt előtt vagy után kódot kell futtatnia, adja át azt a `setUp()` ill. `tearDown()` függvénynek: ```php setUp(function () { - // inicializáló kód, amely minden egyes teszt() előtt lefut. + // inicializációs kód, amely minden test() előtt lefut }); ``` -A második változat az objektum. Létrehozzuk az úgynevezett TestCase-t, amely egy olyan osztály, amelyben az egyes egységeket olyan metódusok reprezentálják, amelyek neve test- kezdetű. +A második változat objektumorientált. Létrehozunk egy ún. TestCase-t, ami egy osztály, ahol az egyes egységeket metódusok képviselik, amelyek nevei test– kezdetűek. ```php .{file:tests/RectangleTest.php} class RectangleTest extends Tester\TestCase @@ -208,25 +207,25 @@ class RectangleTest extends Tester\TestCase } } -// Tesztmódszerek futtatása +// Tesztmetódusok futtatása (new RectangleTest)->run(); ``` -Ezúttal a `@throw` megjegyzést használtuk a kivételek tesztelésére. További információért lásd a [TestCase |TestCase] fejezetet. +A kivételek tesztelésére ezúttal a `@throws` annotációt használtuk. Többet a [TestCase |TestCase] fejezetben tudhat meg. -Segédfunkciók .[#toc-helpers-functions] -======================================= +Segédfüggvények +=============== -A Nette Tester számos olyan osztályt és függvényt tartalmaz, amelyek megkönnyíthetik a tesztelést, például a HTML-dokumentum tartalmának teszteléséhez, a fájlokkal való munka funkcióinak teszteléséhez és így tovább. +A Nette Tester több osztályt és függvényt tartalmaz, amelyek megkönnyíthetik például a HTML dokumentum tartalmának tesztelését, a fájlokkal dolgozó függvények tesztelését és így tovább. -Ezek leírását a [Súgók |Helpers] oldalon találja. +Leírásukat a [Segédosztályok|helpers] oldalon találja. -Megjegyzések és tesztek kihagyása .[#toc-annotation-and-skipping-tests] -======================================================================= +Annotációk és tesztek kihagyása +=============================== -A tesztek végrehajtását befolyásolhatják a fájl elején található phpDoc megjegyzésben szereplő megjegyzések. Ez például így nézhet ki: +A tesztek futtatását befolyásolhatják a fájl elején lévő phpDoc kommentár formájában megadott annotációk. Például így nézhetnek ki: ```php .{file:tests/RectangleTest.php} /** @@ -235,11 +234,11 @@ A tesztek végrehajtását befolyásolhatják a fájl elején található phpDoc */ ``` -A megjegyzések szerint a teszt csak a 7.2-es vagy magasabb PHP-verzióval és a pdo és pdo_pgsql PHP-kiterjesztések megléte esetén futtatható. Ezeket az annotációkat a [parancssori tesztfutó |running-tests] vezérli, amely, ha a feltételek nem teljesülnek, kihagyja a tesztet, és `s` betűvel jelöli - kihagyva. A teszt kézi futtatásakor azonban nincs hatásuk. +A megadott annotációk azt mondják, hogy a tesztet csak PHP 7.2 vagy újabb verzióval kell futtatni, és ha a pdo és pdo_pgsql PHP kiterjesztések jelen vannak. Ezeket az annotációkat a [parancssori tesztfuttató|running-tests] veszi figyelembe, amely abban az esetben, ha a feltételek nem teljesülnek, kihagyja a tesztet, és a kimenetben `s` - skipped betűvel jelöli. -A megjegyzések leírását lásd a [Teszt megjegyzések |Test Annotations] című fejezetben. +Azonban a teszt manuális futtatásakor nincs hatásuk. -A teszt saját feltétel alapján is kihagyható a `Environment::skip()` segítségével. Például Windows esetén kihagyjuk ezt a tesztet: +A tesztet saját feltétel teljesülése alapján is ki lehet hagyni az `Environment::skip()` segítségével. Például ez kihagyja a teszteket Windows rendszeren: ```php if (defined('PHP_WINDOWS_VERSION_BUILD')) { @@ -248,10 +247,10 @@ if (defined('PHP_WINDOWS_VERSION_BUILD')) { ``` -Könyvtárszerkezet .[#toc-directory-structure] -============================================= +Könyvtárstruktúra +================= -Csak kicsit nagyobb könyvtárak vagy projektek esetén javasoljuk, hogy a tesztkönyvtárat a tesztelt osztály névterének megfelelően osszuk alkönyvtárakra: +Javasoljuk, hogy már kicsit nagyobb könyvtáraknál vagy projekteknél ossza fel a teszteket tartalmazó könyvtárat még alkönyvtárakra a tesztelt osztály névtere szerint: ``` └── tests/ @@ -269,22 +268,22 @@ Csak kicsit nagyobb könyvtárak vagy projektek esetén javasoljuk, hogy a teszt └── ... ``` -A teszteket egyetlen névtérből, azaz alkönyvtárból tudja majd futtatni: +Így futtathatja a teszteket egyetlen névtérből, azaz alkönyvtárból: /--pre .[terminal] tester tests/NamespaceOne \-- -Edge Cases .[#toc-edge-cases] -============================= +Speciális helyzetek +=================== -Egy olyan teszt, amely nem hív meg egyetlen állítás metódust sem, gyanús, és hibásan kerül kiértékelésre: +Az a teszt, amely egyetlen assert metódust sem hív meg, gyanús, és hibásnak minősül: /--pre .[terminal] Error: This test forgets to execute an assertion. \-- -Ha az állítások meghívása nélküli tesztet valóban érvényesnek akarjuk tekinteni, hívjuk meg például a `Assert::true(true)`. +Ha valóban azt szeretné, hogy az assert hívások nélküli teszt érvényesnek minősüljön, hívja meg például az `Assert::true(true)`-t. -Árulkodó lehet a `exit()` és a `die()` használata is, ha a tesztet hibaüzenettel zárjuk le. A `exit('Error in connection')` például 0 kilépési kóddal fejezi be a tesztet, ami sikert jelez. Használja a `Assert::fail('Error in connection')` címet. +Szintén félrevezető lehet az `exit()` és `die()` használata a teszt hibaüzenettel történő leállítására. Például az `exit('Hiba a kapcsolatban')` a tesztet 0 visszatérési értékkel fejezi be, ami sikert jelez. Használja az `Assert::fail('Hiba a kapcsolatban')`-t. diff --git a/tester/it/@home.texy b/tester/it/@home.texy index cfe0225f7a..af18103552 100644 --- a/tester/it/@home.texy +++ b/tester/it/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Nette Tester - Test unitari divertenti in PHP}} -{{description: Nette Tester è uno strumento di verifica del codice PHP semplice e molto pratico.}} +{{maintitle: Nette Tester – test confortevoli in PHP}} +{{description: Nette Tester è uno strumento semplice ma molto pratico per testare il codice PHP.}} diff --git a/tester/it/@left-menu.texy b/tester/it/@left-menu.texy index 8a34c40718..c417051ed3 100644 --- a/tester/it/@left-menu.texy +++ b/tester/it/@left-menu.texy @@ -1,8 +1,8 @@ -- [Per iniziare |guide] -- [Scrivere i test |Writing Tests] -- [Esecuzione dei test |Running Tests] +- [Iniziare con Nette Tester |guide] +- [Scrivere test |writing-tests] +- [Eseguire test |running-tests] -- [Asserzioni |Assertions] -- [Annotazioni dei test |Test Annotations] +- [Asserzioni |assertions] +- [Annotazioni dei test |test-annotations] - [TestCase |TestCase] -- [Aiutanti |Helpers] +- [Classi ausiliarie |helpers] diff --git a/tester/it/@menu.texy b/tester/it/@menu.texy index 553950081c..9d097a4912 100644 --- a/tester/it/@menu.texy +++ b/tester/it/@menu.texy @@ -1,3 +1,3 @@ -- [Casa |@home] -- [Documentazione |Guide] +- [Introduzione |@home] +- [Documentazione |guide] - "GitHub .[link-external]":https://github.com/nette/tester diff --git a/tester/it/@meta.texy b/tester/it/@meta.texy new file mode 100644 index 0000000000..ecccb16258 --- /dev/null +++ b/tester/it/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentazione Tester}} diff --git a/tester/it/assertions.texy b/tester/it/assertions.texy index f7b9b7859a..2cc0936f89 100644 --- a/tester/it/assertions.texy +++ b/tester/it/assertions.texy @@ -2,34 +2,34 @@ Asserzioni ********** .[perex] -Le asserzioni sono utilizzate per affermare che un valore reale corrisponde a un valore atteso. Sono metodi della classe `Tester\Assert`. +Le asserzioni vengono utilizzate per confermare che il valore effettivo corrisponde al valore atteso. Si tratta di metodi della classe `Tester\Assert`. -Scegliere le asserzioni più accurate. È meglio `Assert::same($a, $b)` di `Assert::true($a === $b)` perché visualizza un messaggio di errore significativo in caso di fallimento. Nel secondo caso si ottiene solo `false should be true` e non dice nulla sul contenuto delle variabili $a e $b. +Scegliete le asserzioni più appropriate. È meglio `Assert::same($a, $b)` che `Assert::true($a === $b)`, perché in caso di fallimento visualizza un messaggio di errore significativo. Nel secondo caso, solo `false should be true` che non ci dice nulla sul contenuto delle variabili `$a` e `$b`. -La maggior parte delle asserzioni può avere anche un'opzione `$description` che appare nel messaggio di errore se l'aspettativa fallisce. +La maggior parte delle asserzioni può anche avere una descrizione opzionale nel parametro `$description`, che viene visualizzata nel messaggio di errore se l'aspettativa fallisce. -Gli esempi assumono che sia definito il seguente alias di classe: +Gli esempi presuppongono la creazione di un alias: ```php use Tester\Assert; ``` -Assert::same($expected, $actual, string $description=null) .[method] --------------------------------------------------------------------- -`$expected` deve essere uguale a `$actual`. È uguale all'operatore PHP `===`. +Assert::same($expected, $actual, ?string $description=null) .[method] +--------------------------------------------------------------------- +`$expected` deve essere identico a `$actual`. Lo stesso dell'operatore PHP `===`. -Assert::notSame($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------ -Opposto a `Assert::same()`, quindi uguale all'operatore PHP `!==`. +Assert::notSame($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------ +L'opposto di `Assert::same()`, quindi lo stesso dell'operatore PHP `!==`. -Assert::equal($expected, $actual, string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] ------------------------------------------------------------------------------------------------------------------------- -`$expected` deve essere uguale a `$actual`. A differenza di `Assert::same()`, vengono ignorati l'identità degli oggetti, l'ordine delle coppie chiave => valore negli array e i numeri decimali marginalmente diversi, che possono essere modificati impostando `$matchIdentity` e `$matchOrder`. +Assert::equal($expected, $actual, ?string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] +------------------------------------------------------------------------------------------------------------------------- +`$expected` deve essere uguale a `$actual`. A differenza di `Assert::same()`, si ignora l'identità degli oggetti, l'ordine delle coppie chiave => valore negli array e numeri decimali marginalmente diversi, il che può essere modificato impostando `$matchIdentity` e `$matchOrder`. -I casi seguenti sono identici dal punto di vista di `equal()`, ma non per `same()`: +I seguenti casi sono uguali dal punto di vista di `equal()`, ma non di `same()`: ```php Assert::equal(0.3, 0.1 + 0.2); @@ -40,81 +40,81 @@ Assert::equal( ); ``` -Tuttavia, attenzione, l'array `[1, 2]` e `[2, 1]` non sono uguali, perché solo l'ordine dei valori differisce, non le coppie chiave => valore. L'array `[1, 2]` può anche essere scritto come `[0 => 1, 1 => 2]` e quindi `[1 => 2, 0 => 1]` sarà considerato uguale. +Attenzione però, gli array `[1, 2]` e `[2, 1]` non sono uguali, perché differisce solo l'ordine dei valori, non delle coppie chiave => valore. L'array `[1, 2]` può essere scritto anche come `[0 => 1, 1 => 2]` e quindi sarà considerato uguale `[1 => 2, 0 => 1]`. -È inoltre possibile utilizzare le cosiddette [aspettative |#expectations] in `$expected`. +Inoltre, in `$expected` è possibile utilizzare le cosiddette [#aspettative]. -Assert::notEqual($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------- -Opposto a `Assert::equal()`. +Assert::notEqual($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------- +L'opposto di `Assert::equal()`. -Assert::contains($needle, string|array $actual, string $description=null) .[method] ------------------------------------------------------------------------------------ -Se `$actual` è una stringa, deve contenere la sottostringa `$needle`. Se è una matrice, deve contenere l'elemento `$needle` (viene confrontato strettamente). +Assert::contains($needle, string|array $actual, ?string $description=null) .[method] +------------------------------------------------------------------------------------ +Se `$actual` è una stringa, deve contenere la sottostringa `$needle`. Se è un array, deve contenere l'elemento `$needle` (il confronto è rigoroso). -Assert::notContains($needle, string|array $actual, string $description=null) .[method] --------------------------------------------------------------------------------------- -Opposto a `Assert::contains()`. +Assert::notContains($needle, string|array $actual, ?string $description=null) .[method] +--------------------------------------------------------------------------------------- +L'opposto di `Assert::contains()`. -Assert::hasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} -------------------------------------------------------------------------------------------------------- +Assert::hasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +-------------------------------------------------------------------------------------------------------- `$actual` deve essere un array e deve contenere la chiave `$needle`. -Assert::notHasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} ----------------------------------------------------------------------------------------------------------- +Assert::notHasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +----------------------------------------------------------------------------------------------------------- `$actual` deve essere un array e non deve contenere la chiave `$needle`. -Assert::true($value, string $description=null) .[method] --------------------------------------------------------- -`$value` deve essere `true`, quindi `$value === true`. +Assert::true($value, ?string $description=null) .[method] +--------------------------------------------------------- +`$value` deve essere `true`, ovvero `$value === true`. -Assert::truthy($value, string $description=null) .[method] ----------------------------------------------------------- -`$value` deve essere vero, quindi soddisfa la condizione `if ($value) ...`. +Assert::truthy($value, ?string $description=null) .[method] +----------------------------------------------------------- +`$value` deve essere truthy, ovvero soddisfa la condizione `if ($value) ...`. -Assert::false($value, string $description=null) .[method] ---------------------------------------------------------- -`$value` deve essere `false`, quindi `$value === false`. +Assert::false($value, ?string $description=null) .[method] +---------------------------------------------------------- +`$value` deve essere `false`, ovvero `$value === false`. -Assert::falsey($value, string $description=null) .[method] ----------------------------------------------------------- -`$value` deve essere falso, quindi soddisfa la condizione `if (!$value) ...`. +Assert::falsey($value, ?string $description=null) .[method] +----------------------------------------------------------- +`$value` deve essere falsy, ovvero soddisfa la condizione `if (!$value) ...`. -Assert::null($value, string $description=null) .[method] --------------------------------------------------------- -`$value` deve essere `null`, quindi `$value === null`. +Assert::null($value, ?string $description=null) .[method] +--------------------------------------------------------- +`$value` deve essere `null`, ovvero `$value === null`. -Assert::notNull($value, string $description=null) .[method] ------------------------------------------------------------ -`$value` non deve essere `null`, quindi `$value !== null`. +Assert::notNull($value, ?string $description=null) .[method] +------------------------------------------------------------ +`$value` non deve essere `null`, ovvero `$value !== null`. -Assert::nan($value, string $description=null) .[method] -------------------------------------------------------- -`$value` deve essere Not a Number. Utilizzare solo `Assert::nan()` per i test NAN. Il valore NAN è molto specifico e le asserzioni `Assert::same()` o `Assert::equal()` possono comportarsi in modo imprevedibile. +Assert::nan($value, ?string $description=null) .[method] +-------------------------------------------------------- +`$value` deve essere Not a Number. Per testare il valore NAN, utilizzare esclusivamente `Assert::nan()`. Il valore NAN è molto specifico e le asserzioni `Assert::same()` o `Assert::equal()` possono funzionare in modo imprevisto. -Assert::count($count, Countable|array $value, string $description=null) .[method] ---------------------------------------------------------------------------------- -Il numero di elementi in `$value` deve essere `$count`. Quindi lo stesso di `count($value) === $count`. +Assert::count($count, Countable|array $value, ?string $description=null) .[method] +---------------------------------------------------------------------------------- +Il numero di elementi in `$value` deve essere `$count`. Ovvero lo stesso di `count($value) === $count`. -Assert::type(string|object $type, $value, string $description=null) .[method] ------------------------------------------------------------------------------ -`$value` deve essere di un determinato tipo. Come `$type` possiamo usare stringa: +Assert::type(string|object $type, $value, ?string $description=null) .[method] +------------------------------------------------------------------------------ +`$value` deve essere del tipo specificato. Come `$type` possiamo usare una stringa: - `array` -- `list` - array indicizzato in ordine crescente di chiavi numeriche a partire da zero +- `list` - array indicizzato secondo una serie crescente di chiavi numeriche a partire da zero - `bool` - `callable` - `float` @@ -124,14 +124,14 @@ Assert::type(string|object $type, $value, string $description=null) .[method] - `resource` - `scalar` - `string` -- nome della classe o dell'oggetto direttamente, allora deve passare `$value instanceof $type` +- nome della classe o direttamente l'oggetto, quindi `$value` deve essere `instanceof $type` -Assert::exception(callable $callable, string $class, string $message=null, $code=null) .[method] ------------------------------------------------------------------------------------------------- -All'invocazione di `$callable` deve essere lanciata un'eccezione dell'istanza `$class`. Se si passa `$message`, il messaggio dell'eccezione deve [corrispondere |#assert-match]. E se si passa `$code`, il codice dell'eccezione deve essere lo stesso. +Assert::exception(callable $callable, string $class, ?string $message=null, $code=null) .[method] +------------------------------------------------------------------------------------------------- +Alla chiamata di `$callable` deve essere lanciata un'eccezione della classe `$class`. Se specifichiamo `$message`, anche il messaggio dell'eccezione deve [corrispondere al pattern |#Assert::match] e se specifichiamo `$code`, anche i codici devono corrispondere rigorosamente. -Ad esempio, questo test fallisce perché il messaggio dell'eccezione non corrisponde: +Il seguente test fallirà perché il messaggio dell'eccezione non corrisponde: ```php Assert::exception( @@ -141,7 +141,7 @@ Assert::exception( ); ``` -`Assert::exception()` restituisce un'eccezione lanciata, quindi è possibile testare un'eccezione annidata. +`Assert::exception()` restituisce l'eccezione lanciata, è quindi possibile testare anche un'eccezione annidata. ```php $e = Assert::exception( @@ -154,9 +154,9 @@ Assert::type(RuntimeException::class, $e->getPrevious()); ``` -Assert::error(string $callable, int|string|array $type, string $message=null) .[method] ---------------------------------------------------------------------------------------- -Controlla che l'invocazione di `$callable` generi gli errori previsti (cioè avvisi, notifiche, ecc.). Come `$type` si specifica una delle costanti `E_...`, ad esempio `E_WARNING`. E se si supera `$message`, anche il messaggio di errore deve [corrispondere al |#assert-match] modello. Ad esempio: +Assert::error(string $callable, int|string|array $type, ?string $message=null) .[method] +---------------------------------------------------------------------------------------- +Controlla che la funzione `$callable` abbia generato gli errori attesi (cioè warning, notice, ecc.). Come `$type` indichiamo una delle costanti `E_...`, ad esempio `E_WARNING`. E se specifichiamo `$message`, anche il messaggio di errore deve [corrispondere al pattern |#Assert::match]. Ad esempio: ```php Assert::error( @@ -166,7 +166,7 @@ Assert::error( ); ``` -Se il callback genera più errori, dobbiamo aspettarceli tutti nell'ordine esatto. In questo caso, passiamo l'array in `$type`: +Se il callback genera più errori, dobbiamo aspettarceli tutti nell'ordine esatto. In tal caso, passiamo un array in `$type`: ```php Assert::error(function () { @@ -179,108 +179,108 @@ Assert::error(function () { ``` .[note] -Se `$type` è un nome di classe, questa asserzione si comporta come `Assert::exception()`. +Se come `$type` indicate il nome di una classe, si comporta come `Assert::exception()`. Assert::noError(callable $callable) .[method] --------------------------------------------- -Controlla che la funzione `$callable` non lanci alcun avviso/nota/errore o eccezione PHP. È utile per testare un pezzo di codice in cui non ci sono altre asserzioni. +Controlla che la funzione `$callable` non abbia generato alcun warning, errore o eccezione. È utile per testare porzioni di codice dove non c'è nessun'altra asserzione. -Assert::match(string $pattern, $actual, string $description=null) .[method] ---------------------------------------------------------------------------- -`$actual` deve corrispondere a `$pattern`. Si possono usare due varianti di pattern: espressioni regolari o caratteri jolly. +Assert::match(string $pattern, $actual, ?string $description=null) .[method] +---------------------------------------------------------------------------- +`$actual` deve corrispondere al pattern `$pattern`. Possiamo usare due varianti di pattern: espressioni regolari o caratteri jolly. -Se si passa un'espressione regolare come `$pattern`, si deve usare `~` or `#` per delimitarla. Altri delimitatori non sono supportati. Ad esempio, il test in cui `$var` deve contenere solo cifre esadecimali: +Se come `$pattern` passiamo un'espressione regolare, per delimitarla dobbiamo usare `~` o `#`, altri delimitatori non sono supportati. Ad esempio, un test in cui `$var` deve contenere solo cifre esadecimali: ```php Assert::match('#^[0-9a-f]$#i', $var); ``` -L'altra variante è simile al confronto tra stringhe, ma possiamo usare alcuni caratteri jolly in `$pattern`: - -- `%a%` uno o più di qualsiasi cosa tranne i caratteri di fine riga -- `%a?%` zero o più di qualsiasi cosa ad eccezione dei caratteri di fine riga -- `%A%` uno o più di qualsiasi cosa compresi i caratteri di fine riga -- `%A?%` zero o più di qualsiasi cosa, compresi i caratteri di fine riga -- `%s%` uno o più caratteri di spazio bianco, eccetto i caratteri di fine riga -- `%s?%` zero o più caratteri di spazio bianco, eccetto i caratteri di fine riga -- `%S%` uno o più caratteri tranne lo spazio bianco -- `%S?%` zero o più caratteri ad eccezione dello spazio bianco -- `%c%` un singolo carattere di qualsiasi tipo (eccetto la fine della riga) +La seconda variante è simile al confronto di stringhe comune, ma in `$pattern` possiamo usare diversi caratteri jolly: + +- `%a%` uno o più caratteri, eccetto i caratteri di fine riga +- `%a?%` zero o più caratteri, eccetto i caratteri di fine riga +- `%A%` uno o più caratteri, inclusi i caratteri di fine riga +- `%A?%` zero o più caratteri, inclusi i caratteri di fine riga +- `%s%` uno o più spazi bianchi, eccetto i caratteri di fine riga +- `%s?%` zero o più spazi bianchi, eccetto i caratteri di fine riga +- `%S%` uno o più caratteri, eccetto gli spazi bianchi +- `%S?%` zero o più caratteri, eccetto gli spazi bianchi +- `%c%` qualsiasi carattere singolo, eccetto il carattere di fine riga - `%d%` una o più cifre - `%d?%` zero o più cifre -- `%i%` valore intero firmato -- `%f%` numero in virgola mobile -- `%h%` una o più cifre HEX +- `%i%` valore intero con segno +- `%f%` numero con virgola mobile +- `%h%` una o più cifre esadecimali - `%w%` uno o più caratteri alfanumerici -- `%%` un carattere % +- `%%` il carattere % Esempi: ```php -# Again, hexadecimal number test +# Di nuovo test per numero esadecimale Assert::match('%h%', $var); -# Generalized path to file and line number +# Generalizzazione del percorso del file e del numero di riga Assert::match('Error in file %a% on line %i%', $errorMessage); ``` -Assert::matchFile(string $file, $actual, string $description=null) .[method] ----------------------------------------------------------------------------- -L'asserzione è identica a [Assert::match() |#assert-match], ma il modello viene caricato da `$file`. È utile per testare stringhe molto lunghe. Il file di test è leggibile. +Assert::matchFile(string $file, $actual, ?string $description=null) .[method] +----------------------------------------------------------------------------- +L'asserzione è identica a [#Assert::match()], ma il pattern viene caricato dal file `$file`. Questo è utile per testare stringhe molto lunghe. Il file con il test rimane leggibile. Assert::fail(string $message, $actual=null, $expected=null) .[method] --------------------------------------------------------------------- -Questa asserzione fallisce sempre. È solo comoda. Si possono passare facoltativamente i valori attesi e quelli effettivi. +Questa asserzione fallisce sempre. A volte è semplicemente utile. Opzionalmente possiamo indicare anche il valore atteso e quello attuale. -Aspettative .[#toc-expectations] --------------------------------- -Se si vogliono confrontare strutture più complesse con elementi non costanti, le asserzioni precedenti potrebbero non essere sufficienti. Per esempio, testiamo un metodo che crea un nuovo utente e restituisce i suoi attributi come array. Non conosciamo il valore hash della password, ma sappiamo che deve essere una stringa esadecimale. L'unica cosa che sappiamo dell'elemento successivo è che deve essere un oggetto `DateTime`. +Aspettative +----------- +Quando vogliamo confrontare strutture più complesse con elementi non costanti, le asserzioni sopra menzionate potrebbero non essere sufficienti. Ad esempio, testiamo un metodo che crea un nuovo utente e restituisce i suoi attributi come array. Non conosciamo il valore dell'hash della password, ma sappiamo che deve essere una stringa esadecimale. E di un altro elemento sappiamo solo che deve essere un oggetto `DateTime`. -In questi casi, possiamo usare il parametro `Tester\Expect` all'interno del parametro `$expected` dei metodi `Assert::equal()` e `Assert::notEqual()`, che possono essere usati per descrivere facilmente la struttura. +In queste situazioni possiamo usare `Tester\Expect` all'interno del parametro `$expected` dei metodi `Assert::equal()` e `Assert::notEqual()`, con cui è possibile descrivere facilmente la struttura. ```php use Tester\Expect; Assert::equal([ - 'id' => Expect::type('int'), # we expect an integer + 'id' => Expect::type('int'), // ci aspettiamo un numero intero 'username' => 'milo', - 'password' => Expect::match('%h%'), # we expect a string matching pattern - 'created_at' => Expect::type(DateTime::class), # we expect an instance of the class + 'password' => Expect::match('%h%'), // ci aspettiamo una stringa che corrisponda al pattern + 'created_at' => Expect::type(DateTime::class), // ci aspettiamo un'istanza della classe ], User::create(123, 'milo', 'RandomPaSsWoRd')); ``` -Con `Expect` possiamo fare quasi le stesse asserzioni di `Assert`. Abbiamo quindi metodi come `Expect::same()`, `Expect::match()`, `Expect::count()`, ecc. Inoltre, possiamo concatenarli come: +Con `Expect` possiamo eseguire quasi le stesse asserzioni di `Assert`. Quindi abbiamo a disposizione i metodi `Expect::same()`, `Expect::match()`, `Expect::count()` ecc. Inoltre, possiamo concatenarli: ```php -Expect::type(MyIterator::class)->andCount(5); # we expect MyIterator and items count is 5 +Expect::type(MyIterator::class)->andCount(5); // ci aspettiamo MyIterator e un numero di elementi pari a 5 ``` -Oppure, possiamo scrivere i nostri gestori di asserzioni. +Oppure possiamo scrivere i nostri gestori di asserzioni personalizzati. ```php Expect::that(function ($value) { - # return false if expectation fails + // restituiamo false se l'aspettativa fallisce }); ``` -Indagine sulle asserzioni fallite .[#toc-failed-assertions-investigation] -------------------------------------------------------------------------- -Il Tester mostra dove si trova l'errore quando un'asserzione fallisce. Quando si confrontano strutture complesse, il Tester crea dei dump dei valori confrontati e li salva nella directory `output`. Ad esempio, quando il test immaginario `Arrays.recursive.phpt` fallisce, i dump vengono salvati come segue: +Esame delle asserzioni errate +----------------------------- +Quando un'asserzione fallisce, Tester visualizza dov'è l'errore. Se confrontiamo strutture più complesse, Tester crea dump dei valori confrontati e li salva nella directory `output`. Ad esempio, in caso di fallimento del test fittizio `Arrays.recursive.phpt`, i dump verranno salvati come segue: ``` app/ └── tests/ ├── output/ - │ ├── Arrays.recursive.actual # actual value - │ └── Arrays.recursive.expected # expected value + │ ├── Arrays.recursive.actual # valore attuale + │ └── Arrays.recursive.expected # valore atteso │ - └── Arrays.recursive.phpt # failing test + └── Arrays.recursive.phpt # test fallito ``` -Possiamo cambiare il nome della directory con `Tester\Dumper::$dumpDir`. +Il nome della directory può essere modificato tramite `Tester\Dumper::$dumpDir`. diff --git a/tester/it/guide.texy b/tester/it/guide.texy index 33811b8bfb..3336dcbe27 100644 --- a/tester/it/guide.texy +++ b/tester/it/guide.texy @@ -1,17 +1,17 @@ -Come iniziare con Tester -************************ +Iniziare con Nette Tester +*************************
                                                                                                                            -Anche i bravi programmatori commettono errori. La differenza tra un buon programmatore e uno scadente è che quello bravo lo fa una sola volta e la volta successiva lo rileva usando test automatizzati. +Anche i bravi programmatori commettono errori. La differenza tra un buon programmatore e uno cattivo è che quello buono lo commette solo una volta e la prossima volta lo rileva tramite test automatizzati. -- "Chi non fa test è destinato a ripetere i propri errori". (proverbio saggio) -- "Quando ci liberiamo di un errore, ne compare un altro". (Legge di Murphy) -- "Ogni volta che siete tentati di stampare una dichiarazione, scrivetela invece come test". (Martin Fowler) +- "Chi non testa, è condannato a ripetere i propri errori." (proverbio) +- "Non appena ci liberiamo di un errore, ne appare un altro." (Legge di Murphy) +- "Ogni volta che senti il bisogno di stampare una variabile sullo schermo, scrivi piuttosto un test." (Martin Fowler)
                                                                                                                            -Avete mai scritto il seguente codice in PHP? +Avete mai scritto in PHP un codice simile? ```php $obj = new MyClass; @@ -20,40 +20,40 @@ $result = $obj->process($input); var_dump($result); ``` -Avete mai scaricato il risultato di una chiamata di funzione solo per verificare a occhio che restituisca ciò che dovrebbe restituire? Sicuramente lo fate molte volte al giorno. Con la mano sul cuore, se tutto funziona, cancellate questo codice e vi aspettate che la classe non si rompa in futuro? La legge di Murphy garantisce il contrario :-) +Cioè, avete stampato il risultato della chiamata di una funzione solo per verificare a occhio se restituisce ciò che dovrebbe? Sicuramente lo fate molte volte al giorno. Mano sul cuore: nel caso in cui tutto funzioni correttamente, cancellate questo codice? Vi aspettate che la classe non si rompa in futuro? Le leggi di Murphy garantiscono il contrario :-) -In effetti, il test l'avete scritto voi. Ha bisogno di una piccola modifica per non richiedere la nostra ispezione, semplicemente per essere in grado di controllare se stesso. E se non lo avete cancellato, potremo eseguirlo in qualsiasi momento in futuro per verificare che tutto funzioni ancora come dovrebbe. È possibile che nel corso del tempo si crei una grande quantità di questi test, quindi sarebbe bello poterli eseguire automaticamente. +In sostanza, avete scritto un test. Basta solo modificarlo leggermente affinché non richieda un controllo visivo, ma si controlli da solo. E se non cancellate il test, potete eseguirlo in qualsiasi momento in futuro e verificare che tutto funzioni ancora come dovrebbe. Con il tempo creerete un gran numero di tali test, quindi sarebbe utile eseguirli in modo automatizzato. -Nette Tester ci aiuta proprio in questo. +E con tutto questo vi aiuterà proprio Nette Tester. -Cosa rende unico Tester? .[#toc-what-makes-tester-unique] -========================================================= +Cosa rende Tester unico? +======================== -La scrittura di test per Nette Tester è unica nel suo genere, in quanto **ogni test è uno script PHP standard che può essere eseguito autonomamente.** +Scrivere test per Nette Tester è unico nel senso che **ogni test è uno script PHP comune che può essere eseguito separatamente.** -Quindi, quando si scrive un test, si può semplicemente eseguirlo per vedere se c'è un errore di programmazione. Se funziona correttamente. In caso contrario, si può facilmente analizzare il programma nell'IDE e cercare un bug. È anche possibile aprirlo in un browser. +Quindi, quando scrivete un test, potete semplicemente eseguirlo e scoprire se, ad esempio, contiene un errore di programmazione. Se funziona correttamente. In caso contrario, potete facilmente eseguirlo passo passo nel vostro IDE e cercare l'errore. Potete persino aprirlo nel browser. -E soprattutto, eseguendo il programma, si esegue il test. Scoprirete immediatamente se è stato superato o meno. Come? Vediamo qui di seguito. Scriviamo un test banale per l'utilizzo di un array in PHP e salviamolo nel file `ArrayTest.php`: +E soprattutto - eseguendolo, eseguite il test. Scoprite immediatamente se è passato o fallito. Come? Vediamolo. Scriveremo un test banale sul lavoro con un array PHP e lo salveremo nel file `ArrayTest.php`: ```php .{file:ArrayTest.php} OK \-- -Provate a cambiare l'istruzione in `Assert::contains('XXX', $stack);` nel test e guardate cosa succede quando viene eseguito: +Provate a cambiare nel test l'asserzione in una falsa `Assert::contains('XXX', $stack);` e osservate cosa succede all'esecuzione: /--pre .[terminal] $ php ArrayTest.php @@ -73,53 +73,53 @@ $ php ArrayTest.php FAILURE \-- -Continuiamo a parlare di scrittura nel capitolo [Scrivere i test |Writing Tests]. +Continuiamo a scrivere nel capitolo [Scrittura dei test|writing-tests]. -Installazione e requisiti .[#toc-installation-and-requirements] -=============================================================== +Installazione e requisiti +========================= -La versione minima di PHP richiesta da Tester è la 7.1 (per maggiori dettagli, consultare la tabella delle [versioni PHP supportate |#supported PHP versions] ). Il metodo di installazione preferito è [Composer |best-practices:composer]: +La versione minima di PHP richiesta da Tester è 7.1 (più dettagliatamente nella tabella [#versioni PHP supportate]). Il metodo di installazione preferito è tramite [Composer |best-practices:composer]: /--pre .[terminal] composer require --dev nette/tester \-- -Provate a eseguire Nette Tester dalla riga di comando (senza argomenti mostrerà solo un riassunto della guida): +Provate ad eseguire Nette Tester dalla riga di comando (senza parametri visualizzerà solo l'help): /--pre .[terminal] vendor/bin/tester \-- -Esecuzione dei test .[#toc-running-tests] -========================================= +Esecuzione dei test +=================== -Man mano che la nostra applicazione cresce, cresce anche il numero di test. Non sarebbe pratico eseguire i test uno per uno. Per questo motivo, il Tester dispone di un'esecuzione massiva dei test, che viene invocata dalla riga di comando. Il parametro è la directory in cui si trovano i test. Il punto indica la directory corrente. +Man mano che l'applicazione cresce, il numero di test cresce con essa. Non sarebbe pratico eseguire i test uno per uno. Pertanto, Tester dispone di un esecutore di test di massa, che chiamiamo dalla riga di comando. Come parametro indichiamo la directory in cui si trovano i test. Il punto significa la directory corrente. /--pre .[terminal] vendor/bin/tester . \-- -Il runner Nette Tester cerca nella directory specificata e in tutte le sottodirectory i test, che sono i file `*.phpt` e `*Test.php`. Troverà anche il nostro test `ArrayTest.php`, poiché corrisponde alla maschera. +L'esecutore di test esamina la directory specificata e tutte le sottodirectory e cerca i test, che sono i file `*.phpt` e `*Test.php`. Trova così anche il nostro test `ArrayTest.php`, poiché corrisponde alla maschera. -Quindi inizia a eseguire i test. Ogni test viene eseguito come un nuovo processo PHP, in modo da essere completamente isolato dagli altri. Viene eseguito in parallelo su più thread, il che lo rende estremamente veloce. Esegue prima i test che non sono riusciti durante l'esecuzione precedente, in modo da sapere subito se l'errore è stato risolto. +Successivamente avvia il test. Ogni test viene eseguito come un nuovo processo PHP, quindi si svolge completamente isolato dagli altri. Li esegue in parallelo in più thread e grazie a ciò è estremamente veloce. E come primi esegue i test che sono falliti nell'esecuzione precedente, così scoprirete immediatamente se siete riusciti a correggere l'errore. -Per ogni test eseguito, il runner stampa un carattere per indicare il progresso: +Durante l'esecuzione dei test, Tester visualizza continuamente i risultati sul terminale come caratteri: -- . - test superato -- s - il test è stato saltato -- F - test fallito +- . – test superato +- s – test saltato (skipped) +- F – test fallito (failed) -L'output può essere simile a questo: +L'output può apparire così: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +PHP 8.3.2 (cli) | php -n | 8 threads ........s................F......... @@ -134,42 +134,42 @@ PHP 7.4.8 (cli) | php -n | 8 threads Sono stati eseguiti 35 test, uno è fallito, uno è stato saltato. -Continuiamo nel capitolo [Esecuzione dei test |Running tests]. +Continuiamo nel capitolo [Esecuzione dei test|running-tests]. -Modalità di osservazione .[#toc-watch-mode] -=========================================== +Modalità Watch +============== -State rifattorizzando il codice? Oppure sviluppate secondo la metodologia TDD (Test Driven Development)? Allora vi piacerà la modalità di controllo. Il Tester monitora il codice sorgente ed esegue se stesso quando viene modificato. +State rifattorizzando il codice? O addirittura sviluppate secondo la metodologia TDD (Test Driven Development)? Allora vi piacerà la modalità watch. Tester in essa monitora i codici sorgente e all'occorrenza si avvia da solo. -Durante lo sviluppo, avete un terminale nell'angolo del monitor, dove la barra di stato verde si illumina e quando diventa improvvisamente rossa, sapete che avete appena fatto qualcosa di indesiderato. In realtà è un grande gioco in cui si programma e si cerca di attenersi al colore. +Durante lo sviluppo avete quindi nell'angolo del monitor un terminale dove brilla su di voi una barra di stato verde, e quando improvvisamente diventa rossa, sapete che avete appena fatto qualcosa non del tutto correttamente. È in realtà un gioco fantastico, in cui programmate e cercate di mantenere il colore. -La modalità Watch si avvia con il parametro [--watch |running-tests#w-watch-path]. +La modalità Watch si avvia con il parametro [--watch |running-tests#-w --watch path]. -Rapporti di CodeCoverage .[#toc-codecoverage-reports] -===================================================== +Report di Code Coverage +======================= -Il Tester può generare rapporti con una panoramica della copertura del codice sorgente da parte dei test. I rapporti possono essere in formato HTML leggibile dall'uomo o Clover XML per un'ulteriore elaborazione meccanica. +Tester può generare report con una panoramica di quanto codice sorgente coprono i test. Il report può essere sia in formato HTML leggibile dall'uomo, sia in Clover XML per un'ulteriore elaborazione automatica. -Vedere il "rapporto HTML di esempio":https://files.nette.org/tester/coverage.html con la copertura del codice. +Date un'occhiata a "un esempio di report HTML":attachment:coverage.html con la copertura del codice. -Versioni PHP supportate .[#toc-supported-php-versions] -====================================================== +Versioni PHP supportate +======================= -| Versione compatibile con PHP +| versione | compatibile con PHP |------------------|------------------- -| Tester 2.5 | PHP 8.0 - 8.2 -| Tester 2.4 | PHP 7.2 - 8.2 -| Tester 2.3 | PHP 7.1 - 8.0 -| Tester 2.1 - 2.2 | PHP 7.1 - 7.3 -| Tester 2.0 | PHP 5.6 - 7.3 -| Tester 1.7 | PHP 5.3 - 7.3 + HHVM 3.3+ -| Tester 1.6 | PHP 5.3 - 7.0 + HHVM -| Tester 1.3 - 1.5 | PHP 5.3 - 5.6 + HHVM -| Tester 0.9 - 1.2 | PHP 5.3 - 5.6 - -Si applica alle ultime versioni della patch. - -Fino alla versione 1.7 Tester supportava [HHVM |https://hhvm.com] 3.3.0 o più recente (usando `tester -p hhvm`). Il supporto è stato abbandonato da Tester 2.0. L'uso era semplice: +| Tester 2.5 | PHP 8.0 – 8.3 +| Tester 2.4 | PHP 7.2 – 8.2 +| Tester 2.3 | PHP 7.1 – 8.0 +| Tester 2.1 – 2.2 | PHP 7.1 – 7.3 +| Tester 2.0 | PHP 5.6 – 7.3 +| Tester 1.7 | PHP 5.3 – 7.3 + HHVM 3.3+ +| Tester 1.6 | PHP 5.3 – 7.0 + HHVM +| Tester 1.3 – 1.5 | PHP 5.3 – 5.6 + HHVM +| Tester 0.9 – 1.2 | PHP 5.3 – 5.6 + +Vale per l'ultima versione patch. + +Tester fino alla versione 1.7 supportava anche [HHVM |https://hhvm.com] 3.3.0 o superiore (tramite `tester -p hhvm`). Il supporto è stato interrotto dalla versione Tester 2.0. diff --git a/tester/it/helpers.texy b/tester/it/helpers.texy index 2f48325d7f..9c74f8d861 100644 --- a/tester/it/helpers.texy +++ b/tester/it/helpers.texy @@ -1,31 +1,45 @@ -Aiutanti -******** +Classi di aiuto +*************** -DomQuery .[#toc-domquery] -------------------------- -`Tester\DomQuery` è una classe che estende `SimpleXMLElement` con metodi che facilitano il test di contenuti HTML o XML. +DomQuery +-------- +`Tester\DomQuery` è una classe che estende `SimpleXMLElement` con una facile ricerca in HTML o XML tramite selettori CSS. ```php -# let's have an HTML document in $html that we load -$dom = Tester\DomQuery::fromHtml($html); - -# we can test the presence of elements using CSS selectors -Assert::true($dom->has('form#registration')); -Assert::true($dom->has('input[name="username"]')); -Assert::true($dom->has('input[type="submit"]')); - -# or select elements as array of DomQuery -$elems = $dom->find('input[data-autocomplete]'); +# creazione di DomQuery da una stringa HTML +$dom = Tester\DomQuery::fromHtml(' +
                                                                                                                            +

                                                                                                                            Titolo

                                                                                                                            +
                                                                                                                            Testo
                                                                                                                            +
                                                                                                                            +'); + +# test dell'esistenza di elementi tramite selettori CSS +Assert::true($dom->has('article.post')); +Assert::true($dom->has('h1')); + +# ricerca di elementi come array di oggetti DomQuery +$headings = $dom->find('h1'); +Assert::same('Titolo', (string) $headings[0]); + +# test se l'elemento corrisponde al selettore (dalla versione 2.5.3) +$content = $dom->find('.content')[0]; +Assert::true($content->matches('div')); +Assert::false($content->matches('p')); + +# ricerca del predecessore più vicino corrispondente al selettore (da 2.5.5) +$article = $content->closest('.post'); +Assert::true($article->matches('article')); ``` -FileMock .[#toc-filemock] -------------------------- -`Tester\FileMock` emula i file in memoria per aiutare a testare un codice che utilizza funzioni come `fopen()`, `file_get_contents()` o `parse_ini_file()`. Ad esempio: +FileMock +-------- +`Tester\FileMock` emula file in memoria e facilita così il test del codice che utilizza funzioni come `fopen()`, `file_get_contents()`, `parse_ini_file()` e simili. Esempio di utilizzo: ```php -# Tested class +# Classe testata class Logger { public function __construct( @@ -39,21 +53,21 @@ class Logger } } -# New empty file +# Nuovo file vuoto $file = Tester\FileMock::create(''); $logger = new Logger($file); $logger->log('Login'); $logger->log('Logout'); -# Created content testing +# Testiamo il contenuto creato Assert::same("Login\nLogout\n", file_get_contents($file)); ``` Assert::with() .[filter] ------------------------ -Non si tratta di un'asserzione, ma di un aiuto per testare metodi privati e oggetti di proprietà. +Non è un'asserzione, ma un aiutante per testare metodi e proprietà private degli oggetti. ```php class Entity @@ -65,17 +79,17 @@ class Entity $ent = new Entity; Assert::with($ent, function () { - Assert::true($this->enabled); // accessibile privatamente $ent->enabled + Assert::true($this->enabled); // resa accessibile la privata $ent->enabled }); ``` Helpers::purge() .[filter] -------------------------- -Il metodo `purge()` crea la directory specificata e, se esiste già, ne cancella l'intero contenuto. È utile per la creazione di directory temporanee. Ad esempio in `tests/bootstrap.php`: +Il metodo `purge()` crea la directory specificata e, se esiste già, cancella l'intero suo contenuto. È utile per creare una directory temporanea. Ad esempio in `tests/bootstrap.php`: ```php -@mkdir(__DIR__ . '/tmp'); # @ - directory may already exist +@mkdir(__DIR__ . '/tmp'); // @ - la directory potrebbe già esistere define('TempDir', __DIR__ . '/tmp/' . getmypid()); Tester\Helpers::purge(TempDir); @@ -84,25 +98,25 @@ Tester\Helpers::purge(TempDir); Environment::lock() .[filter] ----------------------------- -I test vengono eseguiti in parallelo. A volte è necessario non sovrapporre l'esecuzione dei test. In genere i test sui database devono preparare il contenuto del database e non devono essere disturbati durante l'esecuzione del test. In questi casi si usa `Tester\Environment::lock($name, $dir)`: +I test vengono eseguiti in parallelo. A volte, però, abbiamo bisogno che l'esecuzione dei test non si sovrapponga. Tipicamente nei test di database è necessario che un test prepari il contenuto del database e che un altro test non acceda al database durante la sua esecuzione. In questi test usiamo `Tester\Environment::lock($name, $dir)`: ```php Tester\Environment::lock('database', __DIR__ . '/tmp'); ``` -Il primo argomento è il nome di un blocco. Il secondo è il percorso della directory in cui salvare il blocco. Il test che acquisisce il blocco viene eseguito per primo. Gli altri test devono attendere il suo completamento. +Il primo parametro è il nome del lock, il secondo è il percorso della directory per salvare il lock. Il test che ottiene il lock per primo viene eseguito, gli altri test devono attendere il suo completamento. Environment::bypassFinals() .[filter] ------------------------------------- -Le classi o i metodi contrassegnati da `final` sono difficili da testare. La chiamata di `Tester\Environment::bypassFinals()` in un inizio di test fa sì che le parole chiave `final` vengano rimosse durante il caricamento del codice. +Classi o metodi contrassegnati come `final` sono difficili da testare. La chiamata `Tester\Environment::bypassFinals()` all'inizio del test fa sì che le parole chiave `final` vengano omesse durante il caricamento del codice. ```php require __DIR__ . '/bootstrap.php'; Tester\Environment::bypassFinals(); -class MyClass extends NormallyFinalClass # <-- NormallyFinalClass is not final anymore +class MyClass extends NormallyFinalClass // <-- NormallyFinalClass non è più final { // ... } @@ -111,18 +125,18 @@ class MyClass extends NormallyFinalClass # <-- NormallyFinalClass is not final Environment::setup() .[filter] ------------------------------ -- migliora la leggibilità del dump degli errori (colorazione inclusa), altrimenti viene stampato lo stack trace PHP predefinito -- consente di verificare che le asserzioni siano state chiamate nei test, altrimenti passano anche i test senza asserzioni (ad esempio dimenticate) -- avvia automaticamente il raccoglitore di copertura del codice quando viene usato `--coverage` (descritto più avanti) -- stampa lo stato OK o FAILURE alla fine dello script +- migliora la leggibilità dell'output degli errori (inclusa la colorazione), altrimenti viene visualizzato lo stack trace PHP predefinito +- abilita il controllo che siano state chiamate asserzioni nel test, altrimenti un test senza asserzioni (ad esempio dimenticate) passa ugualmente +- quando si usa `--coverage` avvia automaticamente la raccolta di informazioni sul codice eseguito (descritto più avanti) +- visualizza lo stato OK o FAILURE alla fine dello script Environment::setupFunctions() .[filter]{data-version:2.5} --------------------------------------------------------- -Crea le funzioni globali `test()`, `setUp()` e `tearDown()` in cui è possibile suddividere i test. +Crea le funzioni globali `test()`, `testException()`, `setUp()` e `tearDown()`, in cui è possibile strutturare i test. ```php -test('test description', function () { +test('descrizione del test', function () { Assert::same(123, foo()); Assert::false(bar()); // ... @@ -132,21 +146,21 @@ test('test description', function () { Environment::VariableRunner .[filter] ------------------------------------- -Permette di sapere se il test è stato eseguito direttamente o tramite il Tester. +Consente di determinare se il test è stato eseguito direttamente o tramite Tester. ```php if (getenv(Tester\Environment::VariableRunner)) { - # run by Tester + // eseguito da Tester } else { - # another way + // eseguito diversamente } ``` Environment::VariableThread .[filter] ------------------------------------- -Il Tester esegue i test in parallelo in un determinato numero di thread. Il numero di thread viene indicato in una variabile ambientale quando si è interessati: +Tester esegue i test in parallelo nel numero specificato di thread. Se siamo interessati al numero del thread, lo otteniamo dalla variabile d'ambiente: ```php -echo "I'm running in a thread number " . getenv(Tester\Environment::VariableThread); +echo "Sto eseguendo nel thread numero " . getenv(Tester\Environment::VariableThread); ``` diff --git a/tester/it/running-tests.texy b/tester/it/running-tests.texy index e3d685a801..1ef2648157 100644 --- a/tester/it/running-tests.texy +++ b/tester/it/running-tests.texy @@ -1,55 +1,55 @@ -Esecuzione di test -****************** +Esecuzione dei test +******************* .[perex] -La parte più visibile di Nette Tester è il test runner a riga di comando. È estremamente veloce e robusto perché avvia automaticamente tutti i test come processi separati in parallelo su più thread. Può anche essere eseguito da solo nella cosiddetta modalità watch. +La parte più visibile di Nette Tester è l'esecutore di test dalla riga di comando. È straordinariamente veloce e robusto, poiché avvia automaticamente tutti i test come processi separati e in parallelo su più thread. Sa anche avviarsi da solo nella cosiddetta modalità watch. -Il test runner di Nette Tester viene invocato dalla riga di comando. Come parametro, si passerà la directory del test. Per la directory corrente è sufficiente inserire un punto: +L'esecutore di test viene chiamato dalla riga di comando. Come parametro indichiamo la directory con i test. Per la directory corrente basta inserire un punto: /--pre .[terminal] vendor/bin/tester . \-- -Quando viene invocato, il test runner scansiona la directory specificata e tutte le sottodirectory e cerca i test, che sono i file `*.phpt` e `*Test.php`. Legge e valuta anche le loro [annotazioni |test-annotations] per sapere quali e come eseguirli. +L'esecutore di test esamina la directory specificata e tutte le sottodirectory e cerca i test, che sono i file `*.phpt` e `*Test.php`. Allo stesso tempo legge e valuta le loro [annotazioni|test-annotations], per sapere quali di essi e come eseguirli. -Quindi esegue i test. Per ogni test eseguito, il runner stampa un carattere per indicare il progresso: +Successivamente esegue i test. Durante l'esecuzione dei test visualizza continuamente i risultati sul terminale come caratteri: -- . - test superato -- s - il test è stato saltato -- F - test fallito +- . – test superato +- s – test saltato (skipped) +- F – test fallito (failed) -L'output può apparire come: +L'output può apparire ad esempio così: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +PHP 8.3.2 (cli) | php -n | 8 threads ........s.......................... OK (35 tests, 1 skipped, 1.7 seconds) \-- -Quando si esegue di nuovo, vengono prima eseguiti i test falliti durante l'esecuzione precedente, in modo da sapere subito se l'errore è stato risolto. +All'esecuzione ripetuta, esegue prima i test che sono falliti nell'esecuzione precedente, così scoprirete immediatamente se siete riusciti a correggere l'errore. -Il codice di uscita del tester è zero se nessuno fallisce. Altrimenti non è zero. +Se nessun test fallisce, il codice di ritorno di Tester è zero. Altrimenti, il codice di ritorno è diverso da zero. .[warning] -Il Tester esegue processi PHP senza `php.ini`. Maggiori dettagli nella sezione [Own php.ini |#Own php.ini]. +Tester avvia i processi PHP senza `php.ini`. Più dettagliatamente nella sezione [#php.ini personalizzato]. -Opzioni della riga di comando .[#toc-command-line-options] -========================================================== +Parametri della riga di comando +=============================== -Otteniamo una panoramica delle opzioni da riga di comando eseguendo il Tester senza parametri o con l'opzione `-h`: +Otteniamo una panoramica di tutte le opzioni della riga di comando eseguendo Tester senza parametri, oppure con il parametro `-h`: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Usage: tester [options] [ | ]... @@ -58,16 +58,16 @@ Options: -p Specify PHP interpreter to run (default: php). -c Look for php.ini file (or look in directory) . -C Use system-wide php.ini. - -l | --log Write log to file . -d ... Define INI entry 'key' with value 'value'. -s Show information about skipped tests. --stop-on-fail Stop execution upon the first failure. -j Run jobs in parallel (default: 8). - -o Specify output format. + -o (e.g. -o junit:output.xml) + Specify one or more output formats with optional file name. -w | --watch Watch directory. -i | --info Show tests environment info and exit. --setup Script for runner setup. - --temp Path to temporary directory. Default: sys_get_temp_dir(). + --temp Path to temporary directory. Default by sys_get_temp_dir(). --colors [1|0] Enable or disable colors. --coverage Generate code coverage report to file. --coverage-src Path to source code. @@ -77,7 +77,7 @@ Options: -p .[filter] ------------------- -Specifica il binario PHP che sarà usato per eseguire i test. Per impostazione predefinita è `php`. +Specifica il binario PHP che verrà utilizzato per eseguire i test. Il default è `php`. /--pre .[terminal] tester -p /home/user/php-7.2.0-beta/php-cgi tests @@ -86,26 +86,17 @@ tester -p /home/user/php-7.2.0-beta/php-cgi tests -c .[filter] ------------------- -Specifica quale `php.ini` sarà usato durante l'esecuzione dei test. Per impostazione predefinita, non viene usato alcun php.ini. Per ulteriori informazioni, vedere [Proprio php.ini |#Own php.ini]. +Specifica quale `php.ini` verrà utilizzato durante l'esecuzione dei test. Per impostazione predefinita non viene utilizzato alcun php.ini. Maggiori informazioni nella sezione [#php.ini personalizzato]. -C .[filter] ------------ -Viene utilizzato un `php.ini` a livello di sistema. Quindi, su piattaforma UNIX, anche tutti i file `/etc/php/{sapi}/conf.d/*.ini`. Vedere la sezione [php.ini |#Own php.ini]. - - -''-l | --log '' .[filter] -------------------------------- -Il progresso dei test viene scritto nel file. Tutti i test falliti, saltati e anche quelli riusciti: - -/--pre .[terminal] -tester --log /var/log/tests.log tests -\-- +Viene utilizzato il `php.ini` di sistema. Su UNIX anche tutti i file INI pertinenti `/etc/php/{sapi}/conf.d/*.ini`. Maggiori informazioni nella sezione [#php.ini personalizzato]. -d .[filter] ------------------------ -Imposta il valore della direttiva di configurazione PHP per i test. Il parametro può essere usato più volte. +Imposta il valore della direttiva di configurazione PHP per i test. Il parametro può essere utilizzato più volte. /--pre .[terminal] tester -d max_execution_time=20 @@ -114,32 +105,34 @@ tester -d max_execution_time=20 -s --- -Verranno mostrate le informazioni sui test saltati. +Vengono visualizzate informazioni sui test saltati. --stop-on-fail .[filter] ------------------------ -Il tester interrompe il test al primo fallimento. +Tester interrompe il test al primo test fallito. -j .[filter] ------------------ -I test vengono eseguiti in un `` in parallelo. Il valore predefinito è 8. Se si desidera eseguire i test in serie, si utilizza il valore 1. +Specifica quanti processi paralleli con i test verranno avviati. Il valore predefinito è 8. Se vogliamo che tutti i test vengano eseguiti in serie, utilizziamo il valore 1. --o .[filter] -------------------------------------- -Formato di output. Il formato predefinito è quello della console. +-o .[filter] +------------------------------------------------------- +Imposta il formato dell'output. Il predefinito è il formato per la console. Potete specificare il nome del file in cui verrà scritto l'output (ad esempio `-o junit:output.xml`). L'opzione `-o` può essere ripetuta più volte per generare più formati contemporaneamente. -- `console`: uguale a quello predefinito, ma in questo caso il logo ASCII non viene stampato +- `console`: identico al formato predefinito, ma in questo caso non viene visualizzato il logo ASCII +- `console-lines`: simile a console, ma il risultato di ogni test è indicato su una riga separata con ulteriori informazioni - `tap`: [formato TAP |https://en.wikipedia.org/wiki/Test_Anything_Protocol] adatto all'elaborazione automatica -- `junit`: formato XML JUnit, appropriato anche per l'elaborazione meccanica -- `none`: non viene stampato nulla +- `junit`: formato XML JUnit, anch'esso adatto all'elaborazione automatica +- `log`: Output dell'avanzamento dei test. Tutti i test falliti, saltati e anche quelli riusciti +- `none`: non viene visualizzato nulla ''-w | --watch '' .[filter] --------------------------------- -Il Tester non termina al termine dei test, ma continua a eseguire e a osservare i file PHP nella directory indicata. Quando vengono modificati, esegue nuovamente i test. Il parametro può essere usato più volte se si vogliono monitorare più directory. +Al termine del test, Tester non termina, ma rimane in esecuzione e monitora i file PHP nella directory specificata. In caso di modifica, esegue nuovamente i test. Il parametro può essere utilizzato più volte se si desidera monitorare più directory. È utile durante il refactoring di una libreria o il debug dei test. @@ -150,7 +143,7 @@ tester --watch src tests ''-i | --info'' .[filter] ------------------------- -Mostra informazioni sull'ambiente di esecuzione del test. Ad esempio: +Visualizza informazioni sull'ambiente di esecuzione per i test. Ad esempio: /--pre .[terminal] tester -p /usr/bin/php7.1 -c tests/php.ini --info @@ -177,13 +170,13 @@ Core, ctype, date, dom, ereg, fileinfo, filter, hash, ... --setup .[filter] ------------------------ -Il Tester carica lo script PHP dato all'avvio. La variabile `Tester\Runner\Runner $runner` è disponibile in esso. Supponiamo che il file `tests/runner-setup.php`: +Tester all'avvio carica lo script PHP specificato. In esso è disponibile la variabile `Tester\Runner\Runner $runner`. Supponiamo il file `tests/runner-setup.php` con il contenuto: ```php $runner->outputHandlers[] = new MyOutputHandler; ``` -ed eseguiamo il Tester: +Avviamo Tester: /--pre .[terminal] tester --setup tests/runner-setup.php tests @@ -192,47 +185,47 @@ tester --setup tests/runner-setup.php tests --temp .[filter] ----------------------- -Imposta il percorso della directory per i file temporanei di Tester. Il valore predefinito è restituito da `sys_get_temp_dir()`. Quando il valore predefinito non è valido, viene segnalato. +Imposta il percorso della directory per i file temporanei di Tester. Il valore predefinito viene restituito da `sys_get_temp_dir()`. Se il valore predefinito non è valido, sarete avvisati. -Se non si è sicuri della directory utilizzata, si può eseguire Tester con il parametro `--info`. +Se non siamo sicuri di quale directory venga utilizzata, avviamo Tester con il parametro `--info`. ---colori 1|0 .[filter] +--colors 1|0 .[filter] ---------------------- -Il Tester rileva un terminale colorabile in modo predefinito e colora il suo output. Questa opzione sostituisce il rilevamento automatico. È possibile impostare la colorazione a livello globale tramite una variabile d'ambiente di sistema `NETTE_TESTER_COLORS`. +Per impostazione predefinita Tester rileva un terminale a colori e colora il suo output. Questa opzione sovrascrive l'autodetection. Globalmente possiamo impostare la colorazione tramite la variabile d'ambiente di sistema `NETTE_TESTER_COLORS`. ---copertura .[filter] ----------------------------- -Il tester genererà un rapporto con una panoramica di quanto il codice sorgente è coperto dai test. Questa opzione richiede l'abilitazione dell'estensione PHP [Xdebug |https://xdebug.org/] o [PCOV |https://github.com/krakjoe/pcov], oppure PHP 7 con PHPDBG SAPI, che è più veloce. L'estensione del file di destinazione determina il formato del contenuto. HTML o Clover XML. +--coverage .[filter] +--------------------------- +Tester genererà un report con una panoramica di quanto codice sorgente coprono i test. Questa opzione richiede l'estensione PHP installata [Xdebug |https://xdebug.org/], o [PCOV |https://github.com/krakjoe/pcov], oppure PHP 7 con PHPDBG SAPI, che è più veloce. L'estensione del file di destinazione determina il suo formato. O HTML o Clover XML. /--pre .[terminal] -tester tests --coverage coverage.html # HTML report -tester tests --coverage coverage.xml # Clover XML report +tester tests --coverage coverage.html # Report HTML +tester tests --coverage coverage.xml # Report Clover XML \-- -La priorità di scelta del meccanismo di raccolta è la seguente: +La priorità di selezione del meccanismo è la seguente: 1) PCOV 2) PHPDBG 3) Xdebug -I test più estesi possono fallire durante l'esecuzione con PHPDBG a causa dell'esaurimento della memoria. La raccolta dei dati di copertura è un'operazione che consuma memoria. In questo caso, può essere utile richiamare `Tester\CodeCoverage\Collector::flush()` all'interno di un test. Essa riverserà i dati raccolti in un file e libererà la memoria. Quando la raccolta dei dati non è in corso o si utilizza Xdebug, la chiamata non ha alcun effetto. +Utilizzando PHPDBG, possiamo riscontrare il fallimento del test a causa dell'esaurimento della memoria nei test estesi. La raccolta di informazioni sulla copertura del codice richiede molta memoria. In questo caso, ci aiuta la chiamata `Tester\CodeCoverage\Collector::flush()` all'interno del test. Scrive i dati raccolti su disco e libera la memoria. Se la raccolta dati non è in corso, o viene utilizzato Xdebug, la chiamata non ha alcun effetto. -"Un esempio di report HTML":https://files.nette.org/tester/coverage.html con copertura del codice. +"Esempio di report HTML":attachment:coverage.html con la copertura del codice. ---copertura-src .[filter] --------------------------------- -La utilizziamo contemporaneamente all'opzione `--coverage`. L'opzione `` è un percorso al codice sorgente per il quale si genera il rapporto. Può essere usato ripetutamente. +--coverage-src .[filter] +------------------------------- +Utilizziamo contemporaneamente all'opzione `--coverage`. `` è il percorso dei codici sorgente per i quali viene generato il report. Può essere utilizzato ripetutamente. -Proprio php.ini .[#toc-own-php-ini] -=================================== -Il Tester esegue i processi PHP con l'opzione `-n`, il che significa che non viene caricato `php.ini` (nemmeno quello di `/etc/php/conf.d/*.ini` in UNIX). Questo garantisce lo stesso ambiente per i test eseguiti, ma disattiva anche tutte le estensioni PHP esterne comunemente caricate da PHP di sistema. +php.ini personalizzato +====================== +Tester avvia i processi PHP con il parametro `-n`, il che significa che nessun `php.ini` viene caricato. Su UNIX nemmeno quelli da `/etc/php/conf.d/*.ini`. Ciò garantisce un ambiente identico per l'esecuzione dei test, ma disabilita anche tutte le estensioni PHP normalmente caricate dal PHP di sistema. -Se si vuole mantenere la configurazione di sistema, utilizzare il parametro `-C`. +Se desiderate mantenere il caricamento dei file php.ini di sistema, utilizzate il parametro `-C`. -Se si ha bisogno di alcune estensioni o di alcune impostazioni INI speciali, si consiglia di creare un proprio file `php.ini` e di distribuirlo tra i test. Poi si esegue Tester con l'opzione `-c`, ad esempio `tester -c tests/php.ini`. Il file INI può essere simile a: +Se avete bisogno di alcune estensioni o impostazioni INI speciali per i test, consigliamo di creare un proprio file `php.ini`, che sarà distribuito con i test. Tester viene quindi avviato con il parametro `-c`, ad esempio `tester -c tests/php.ini tests`, dove il file INI può apparire così: ```ini [PHP] @@ -243,4 +236,4 @@ extension=php_pdo_pgsql.dll memory_limit=512M ``` -L'esecuzione del Tester con un sistema `php.ini` in UNIX, ad esempio `tester -c /etc/php/cgi/php.ini`, non carica altri INI da `/etc/php/conf.d/*.ini`. Questo è il comportamento di PHP, non del Tester. +L'avvio di Tester su UNIX con il `php.ini` di sistema, ad esempio `tester -c /etc/php/cli/php.ini` non carica gli altri INI da `/etc/php/conf.d/*.ini`. Questa è una caratteristica di PHP, non di Tester. diff --git a/tester/it/test-annotations.texy b/tester/it/test-annotations.texy index 51d6f682c4..52aeed4461 100644 --- a/tester/it/test-annotations.texy +++ b/tester/it/test-annotations.texy @@ -1,16 +1,16 @@ -Annotazioni del test +Annotazioni dei test ******************** .[perex] -Le annotazioni determinano il modo in cui i test saranno gestiti dal [test runner a riga di comando |running-tests]. Vengono scritte all'inizio del file di test. +Le annotazioni determinano come i test verranno gestiti dall'[esecutore di test dalla riga di comando|running-tests]. Vengono scritte all'inizio del file con il test. -Le annotazioni sono insensibili alle maiuscole e alle minuscole. Inoltre, non hanno alcun effetto se il test viene eseguito manualmente come un normale script PHP. +Nelle annotazioni non si tiene conto delle maiuscole/minuscole. Inoltre non hanno alcun effetto se il test viene eseguito manualmente come uno script PHP comune. Esempio: ```php /** - * TEST: Basic database query test. + * TEST: Test di query di base sul database. * * @dataProvider files/databases.ini * @exitCode 56 @@ -23,17 +23,17 @@ require __DIR__ . '/../bootstrap.php'; TEST .[filter] -------------- -In realtà non è un'annotazione. Imposta solo il titolo del test che viene stampato in caso di fallimento o nei log. +Questa in realtà non è nemmeno un'annotazione, specifica solo il titolo del test, che viene visualizzato in caso di fallimento o nel log. @skip .[filter] --------------- -Il test viene saltato. È utile per disattivare temporaneamente il test. +Il test viene saltato. Utile per disabilitare temporaneamente i test. @phpVersion .[filter] --------------------- -Il test viene saltato se non viene eseguito dalla versione PHP corrispondente. Scriviamo l'annotazione come `@phpVersion [operator] version`. Si può omettere l'operatore, l'impostazione predefinita è `>=`. Esempi: +Il test viene saltato se non viene eseguito con la versione PHP corrispondente. L'annotazione la scriviamo come `@phpVersion [operatore] versione`. L'operatore può essere omesso, il predefinito è `>=`. Esempi: ```php /** @@ -46,7 +46,7 @@ Il test viene saltato se non viene eseguito dalla versione PHP corrispondente. S @phpExtension .[filter] ----------------------- -Il test viene saltato se tutte le estensioni PHP menzionate non sono caricate. È possibile scrivere più estensioni in una singola annotazione, oppure utilizzare l'annotazione più volte. +Il test viene saltato se non sono caricate tutte le estensioni PHP specificate. Più estensioni possono essere specificate in una singola annotazione, oppure può essere utilizzata più volte. ```php /** @@ -58,9 +58,9 @@ Il test viene saltato se tutte le estensioni PHP menzionate non sono caricate. @dataProvider .[filter] ----------------------- -Questa annotazione è adatta quando si vuole eseguire il test più volte, ma con dati diversi. (Da non confondere con l'annotazione omonima per [TestCase |TestCase#dataProvider]). +Se vogliamo eseguire il file di test più volte, ma con dati di input diversi, questa annotazione è utile. (Non confondere con l'annotazione omonima per [TestCase |TestCase#dataProvider].) -Scriviamo l'annotazione come `@dataProvider file.ini`. Il percorso del file INI è relativo al file di test. Il test viene eseguito tante volte quante sono le sezioni contenute nel file INI. Supponiamo che il file INI `databases.ini`: +La scriviamo come `@dataProvider file.ini`, il percorso del file viene considerato relativo al file con il test. Il test verrà eseguito tante volte quante sono le sezioni nel file INI. Supponiamo il file INI `databases.ini`: ```ini [mysql] @@ -77,7 +77,7 @@ password = ****** dsn = "sqlite::memory:" ``` -e il file `database.phpt` nella stessa directory: +e nella stessa directory il test `database.phpt`: ```php /** @@ -87,11 +87,11 @@ e il file `database.phpt` nella stessa directory: $args = Tester\Environment::loadData(); ``` -Il test viene eseguito tre volte e `$args` conterrà i valori delle sezioni `mysql`, `postgresql` o `sqlite`. +Il test verrà eseguito tre volte e `$args` conterrà sempre i valori dalla sezione `mysql`, `postgresql` o `sqlite`. -Esiste un'ulteriore variante quando si scrivono annotazioni con un punto interrogativo come `@dataProvider? file.ini`. In questo caso, il test viene saltato se il file INI non esiste. +Esiste ancora una variante in cui scriviamo l'annotazione con un punto interrogativo come `@dataProvider? file.ini`. In questo caso, il test viene saltato se il file INI non esiste. -Le possibilità di annotazione non sono state ancora menzionate tutte. È possibile scrivere condizioni dopo il file INI. Il test viene eseguito per la sezione indicata solo se tutte le condizioni corrispondono. Estendiamo il file INI: +Le possibilità dell'annotazione non finiscono qui. Dopo il nome del file INI possiamo specificare le condizioni in base alle quali il test verrà eseguito per la sezione specificata. Estendiamo il file INI: ```ini [mysql] @@ -113,7 +113,7 @@ password = ****** dsn = "sqlite::memory:" ``` -e utilizzeremo l'annotazione con la condizione: +e usiamo l'annotazione con la condizione: ```php /** @@ -121,9 +121,9 @@ e utilizzeremo l'annotazione con la condizione: */ ``` -Il test viene eseguito solo una volta per la sezione `postgresql 9.1`. Le altre sezioni non corrispondono alle condizioni. +Il test verrà eseguito solo una volta e per la sezione `postgresql 9.1`. Le altre sezioni non superano il filtro della condizione. -Allo stesso modo, si può passare il percorso a uno script PHP invece di INI. Deve restituire un array o un Traversable. File `databases.php`: +Analogamente, invece di un file INI possiamo fare riferimento a uno script PHP. Questo deve restituire un array o Traversable. File `databases.php`: ```php return [ @@ -140,31 +140,31 @@ return [ ``` -@multiplo .[filter] +@multiple .[filter] ------------------- -Si scrive come `@multiple N` dove `N` è un numero intero. Il test viene eseguito esattamente N volte. +Scriviamo come `@multiple N`, dove `N` è un numero intero. Il test verrà eseguito esattamente N volte. @testCase .[filter] ------------------- -L'annotazione non ha parametri. Si usa quando si scrive un test come classi [TestCase |TestCase]. In questo caso, il test runner a riga di comando eseguirà i singoli metodi in processi separati e in parallelo in più thread. Questo può accelerare notevolmente l'intero processo di test. +L'annotazione non ha parametri. La usiamo se scriviamo i test come classi [TestCase]. In tal caso, l'esecutore di test dalla riga di comando eseguirà i singoli metodi in processi separati e in parallelo su più thread. Questo può accelerare notevolmente l'intero processo di test. @exitCode .[filter] ------------------- -Lo scriviamo come `@exitCode N` dove `N` is the exit code of the test. For example if `exit(10)` viene chiamato nel test, scriviamo l'annotazione come `@exitCode 10`. Il test viene considerato fallito se termina con un codice diverso. Il codice di uscita 0 (zero) è verificato se si tralascia l'annotazione +Scriviamo come `@exitCode N`, dove `N` è il codice di ritorno del test eseguito. Se nel test viene ad esempio chiamato `exit(10)`, scriviamo l'annotazione come `@exitCode 10` e se il test termina con un codice diverso, viene considerato un fallimento. Se l'annotazione non viene specificata, viene verificato il codice di ritorno 0 (zero). @httpCode .[filter] ------------------- -L'annotazione viene valutata solo se il binario PHP è CGI. Altrimenti viene ignorata. Si scrive come `@httpCode NNN`, dove `NNN` è il codice HTTP atteso. Il codice HTTP 200 viene verificato se si omette l'annotazione. Se si scrive `NNN` come una stringa valutata come zero, per esempio `any`, il codice HTTP non viene verificato affatto. +L'annotazione si applica solo se il binario PHP è CGI. Altrimenti viene ignorata. Scriviamo come `@httpCode NNN` dove `NNN` è il codice HTTP atteso. Se l'annotazione non viene specificata, viene verificato il codice HTTP 200. Se `NNN` viene scritto come una stringa valutata a zero, ad esempio `any`, il codice HTTP non viene verificato. -@outputMatch a @outputMatchFile .[filter] +@outputMatch e @outputMatchFile .[filter] ----------------------------------------- -Il comportamento delle annotazioni è coerente con le asserzioni `Assert::match()` e `Assert::matchFile()`. Ma lo schema si trova nell'output standard del test. Un caso d'uso appropriato è quello in cui si suppone che il test termini con un errore fatale e che sia necessario verificare il suo output. +La funzione delle annotazioni è identica alle asserzioni `Assert::match()` e `Assert::matchFile()`. Il pattern viene però cercato nel testo che il test ha inviato al suo output standard. Trova applicazione se prevediamo che il test termini con un errore fatale e abbiamo bisogno di verificare il suo output. @phpIni .[filter] ----------------- -Imposta i valori di configurazione INI per il test. Ad esempio, lo scriviamo come `@phpIni precision=20` e funziona come se avessimo passato il valore dalla riga di comando con il parametro `-d precision=20`. +Per il test imposta i valori INI di configurazione. Scriviamo ad esempio come `@phpIni precision=20` e funziona come se avessimo specificato il valore dalla riga di comando tramite il parametro `-d precision=20`. diff --git a/tester/it/testcase.texy b/tester/it/testcase.texy index 17bc9e0294..ef343ed2d8 100644 --- a/tester/it/testcase.texy +++ b/tester/it/testcase.texy @@ -1,10 +1,10 @@ -Caso di prova -************* +TestCase +******** .[perex] -Le asserzioni possono seguire una per una nei test semplici. Ma a volte è utile racchiudere le asserzioni in una classe di test e strutturarle in questo modo. +Nei test semplici, le asserzioni possono seguire una dopo l'altra. A volte, però, è più vantaggioso racchiudere le asserzioni in una classe di test e strutturarle così. -La classe deve essere discendente di `Tester\TestCase` e se ne parla semplicemente come di **testcase**. +La classe deve essere un discendente di `Tester\TestCase` e ne parliamo semplificando come **testcase**. La classe deve contenere metodi di test che iniziano con `test`. Questi metodi verranno eseguiti come test: ```php use Tester\Assert; @@ -22,11 +22,11 @@ class RectangleTest extends Tester\TestCase } } -# Run testing methods +# Esecuzione dei metodi di test (new RectangleTest)->run(); ``` -Possiamo arricchire un testcase con i metodi `setUp()` e `tearDown()`. Essi vengono chiamati prima/dopo ogni metodo di test: +Un test scritto in questo modo può essere ulteriormente arricchito con i metodi `setUp()` e `tearDown()`. Vengono chiamati prima, rispettivamente dopo, ogni metodo di test: ```php use Tester\Assert; @@ -35,12 +35,12 @@ class NextTest extends Tester\TestCase { public function setUp() { - # Preparation + # Preparazione } public function tearDown() { - # Clean-up + # Pulizia } public function testOne() @@ -54,14 +54,14 @@ class NextTest extends Tester\TestCase } } -# Run testing methods +# Esecuzione dei metodi di test (new NextTest)->run(); /* -Method Calls Order ------------------- +Ordine di chiamata dei metodi +----------------------------- setUp() testOne() tearDown() @@ -72,9 +72,9 @@ tearDown() */ ``` -Se si verifica un errore in una fase di `setUp()` o `tearDown()`, il test fallisce. Se si verifica un errore nel metodo di test, il metodo `tearDown()` viene chiamato comunque, ma con gli errori soppressi. +Se si verifica un errore nella fase `setUp()` o `tearDown()`, il test fallisce complessivamente. Se si verifica un errore nel metodo di test, nonostante ciò il metodo `tearDown()` viene eseguito, ma con la soppressione degli errori al suo interno. -Si consiglia di scrivere l'annotazione [@testCase |test-annotations#@testCase] all'inizio del test, in modo che il test runner a riga di comando esegua i singoli metodi del testcase in processi separati e in parallelo in più thread. Questo può accelerare notevolmente l'intero processo di test. +Consigliamo di scrivere all'inizio del test l'annotazione [@testCase |test-annotations#testCase], così l'esecutore di test dalla riga di comando eseguirà i singoli metodi del testcase in processi separati e in parallelo su più thread. Questo può accelerare notevolmente l'intero processo di test. /--php width = $width; $this->height = $height; @@ -53,7 +52,7 @@ class Rectangle } ``` -E creeremo un test per essa. Il nome del file di test deve corrispondere alla maschera `*Test.php` o `*.phpt`, noi sceglieremo la variante `RectangleTest.php`: +E creiamo un test per essa. Il nome del file con il test dovrebbe corrispondere alla maschera `*Test.php` o `*.phpt`, scegliamo ad esempio la variante `RectangleTest.php`: ```php .{file:tests/RectangleTest.php} @@ -62,31 +61,31 @@ use Tester\Assert; require __DIR__ . '/bootstrap.php'; -// oblungo generico +// rettangolo generico $rect = new Rectangle(10, 20); -Assert::same(200.0, $rect->getArea()); # verificheremo i risultati attesi +Assert::same(200.0, $rect->getArea()); # verifichiamo i risultati attesi Assert::false($rect->isSquare()); ``` -Come si può vedere, i [metodi di asserzione |Assertions] come `Assert::same()` sono usati per affermare che un valore reale corrisponde a un valore atteso. +Come vedete, i cosiddetti [metodi di asserzione|assertions] come `Assert::same()` vengono utilizzati per confermare che il valore effettivo corrisponde al valore atteso. -L'ultimo passo consiste nel creare il file `bootstrap.php`. Esso contiene un codice comune per tutti i test. Ad esempio, il caricamento automatico delle classi, la configurazione dell'ambiente, la creazione di una cartella temporanea, gli helper e simili. Ogni test carica il bootstrap e presta attenzione solo ai test. Il bootstrap può essere simile a: +Resta l'ultimo passo, ovvero il file `bootstrap.php`. Questo contiene il codice comune a tutti i test, ad esempio l'autoloading delle classi, la configurazione dell'ambiente, la creazione di una directory temporanea, funzioni di aiuto e simili. Tutti i test caricano il bootstrap e si dedicano poi solo al test. Il bootstrap può apparire come segue: ```php .{file:tests/bootstrap.php} OK \-- -Se nel test cambiamo l'affermazione in false `Assert::same(123, $rect->getArea());`, si verificherà quanto segue: +Se cambiassimo nel test l'asserzione in una falsa `Assert::same(123, $rect->getArea());` succederebbe questo: /--pre .[terminal] $ php RectangleTest.php @@ -107,35 +106,35 @@ $ php RectangleTest.php \-- -Quando si scrivono i test, è bene catturare tutte le situazioni estreme. Ad esempio, se l'input è zero, un numero negativo, in altri casi una stringa vuota, null, ecc. In effetti, questo costringe a pensare e a decidere come il codice dovrebbe comportarsi in queste situazioni. I test poi correggono il comportamento. +Durante la scrittura dei test è bene coprire tutte le situazioni limite. Ad esempio, quando l'input sarà zero, un numero negativo, in altri casi ad esempio una stringa vuota, null ecc. In realtà vi costringe a riflettere e decidere come il codice dovrebbe comportarsi in tali situazioni. I test poi fissano il comportamento. -Nel nostro caso, un valore negativo dovrebbe lanciare un'eccezione, che verifichiamo con [Assert::exception() |Assertions#Assert::exception]: +Nel nostro caso, un valore negativo deve lanciare un'eccezione, cosa che verifichiamo tramite [Assert::exception() |Assertions#Assert::exception]: ```php .{file:tests/RectangleTest.php} -// la larghezza non deve essere un numero negativo +// la larghezza non deve essere negativa Assert::exception( fn() => new Rectangle(-1, 20), InvalidArgumentException::class, - 'La dimensione non deve essere negativa', + 'La dimensione non deve essere negativa.', ); ``` -E aggiungiamo un test simile per l'altezza. Infine, verifichiamo che `isSquare()` restituisca `true` se entrambe le dimensioni sono uguali. Provate a scrivere questi test come esercizio. +E aggiungiamo un test simile per l'altezza. Infine, testiamo che `isSquare()` restituisca `true` se entrambe le dimensioni sono uguali. Provate a scrivere tali test come esercizio. -Test ben organizzati .[#toc-well-arranged-tests] -================================================ +Test più chiari +=============== -Le dimensioni del file di test possono aumentare e diventare rapidamente ingombranti. Pertanto, è pratico raggruppare le singole aree testate in funzioni separate. +La dimensione del file con il test può aumentare e diventare rapidamente poco chiara. Pertanto, è pratico raggruppare le singole aree testate in funzioni separate. -Per prima cosa, mostreremo una variante più semplice ma elegante, utilizzando la funzione globale `test()`. Il tester non la crea automaticamente, per evitare collisioni nel caso in cui si abbia una funzione con lo stesso nome nel codice. Viene creata solo dal metodo `setupFunctions()`, che si chiama nel file `bootstrap.php`: +Prima mostreremo una variante più semplice, ma elegante, ovvero tramite la funzione globale `test()`. Tester non la crea automaticamente per evitare collisioni nel caso aveste nel codice una funzione con lo stesso nome. La crea invece il metodo `setupFunctions()`, che chiamate nel file `bootstrap.php`: ```php .{file:tests/bootstrap.php} Tester\Environment::setup(); Tester\Environment::setupFunctions(); ``` -Utilizzando questa funzione, possiamo suddividere il file di test in unità denominate. Quando viene eseguita, le etichette vengono visualizzate una dopo l'altra. +Tramite questa funzione possiamo suddividere piacevolmente il file di test in unità nominate. All'esecuzione verranno visualizzate progressivamente le descrizioni. ```php .{file:tests/RectangleTest.php} getArea()); Assert::false($rect->isSquare()); }); -test('general square', function () { +test('quadrato generico', function () { $rect = new Rectangle(5, 5); Assert::same(25.0, $rect->getArea()); Assert::true($rect->isSquare()); }); -test('dimensions must not be negative', function () { +test('le dimensioni non devono essere negative', function () { Assert::exception( fn() => new Rectangle(-1, 20), InvalidArgumentException::class, @@ -168,15 +167,15 @@ test('dimensions must not be negative', function () { }); ``` -Se è necessario eseguire il codice prima o dopo ogni test, passarlo a `setUp()` o `tearDown()`: +Se avete bisogno di eseguire codice prima o dopo ogni test, passatelo alla funzione `setUp()` rispettivamente `tearDown()`: ```php setUp(function () { - // codice di inizializzazione da eseguire prima di ogni test() + // codice di inizializzazione che viene eseguito prima di ogni test() }); ``` -La seconda variante è l'oggetto. Creeremo il cosiddetto TestCase, che è una classe in cui le singole unità sono rappresentate da metodi il cui nome inizia con test-. +La seconda variante è orientata agli oggetti. Creiamo un cosiddetto TestCase, che è una classe in cui le singole unità rappresentano metodi i cui nomi iniziano con test–. ```php .{file:tests/RectangleTest.php} class RectangleTest extends Tester\TestCase @@ -208,25 +207,25 @@ class RectangleTest extends Tester\TestCase } } -// Eseguire i metodi di test +// Esecuzione dei metodi di test (new RectangleTest)->run(); ``` -Questa volta abbiamo utilizzato l'annotazione `@throw` per verificare la presenza di eccezioni. Per ulteriori informazioni, si veda il capitolo [TestCase |TestCase]. +Per testare le eccezioni abbiamo usato questa volta l'annotazione `@throws`. Maggiori informazioni nel capitolo [TestCase]. -Funzioni Helpers .[#toc-helpers-functions] -========================================== +Funzioni di aiuto +================= -Nette Tester include diverse classi e funzioni che possono facilitare l'esecuzione dei test, ad esempio gli helper per testare il contenuto di un documento HTML, per testare le funzioni di lavoro con i file e così via. +Nette Tester contiene diverse classi e funzioni che possono facilitarvi, ad esempio, il test del contenuto di un documento HTML, il test di funzioni che lavorano con file e così via. -Una loro descrizione è disponibile alla pagina [Helpers |Helpers]. +La loro descrizione la trovate nella pagina [Classi di aiuto|helpers]. -Annotazione e salto dei test .[#toc-annotation-and-skipping-tests] -================================================================== +Annotazioni e salto dei test +============================ -L'esecuzione dei test può essere influenzata dalle annotazioni nel commento phpDoc all'inizio del file. Per esempio, potrebbe essere così: +L'esecuzione dei test può essere influenzata da annotazioni sotto forma di commento phpDoc all'inizio del file. Può apparire ad esempio così: ```php .{file:tests/RectangleTest.php} /** @@ -235,23 +234,23 @@ L'esecuzione dei test può essere influenzata dalle annotazioni nel commento php */ ``` -Le annotazioni dicono che il test deve essere eseguito solo con PHP versione 7.2 o superiore e se sono presenti le estensioni PHP pdo e pdo_pgsql. Queste annotazioni sono controllate dal [test runner a riga di comando |running-tests] che, se le condizioni non sono soddisfatte, salta il test e lo contrassegna con la lettera `s` - saltato. Tuttavia, non hanno alcun effetto quando il test viene eseguito manualmente. +Le annotazioni indicate dicono che il test deve essere eseguito solo con la versione PHP 7.2 o superiore e se sono presenti le estensioni PHP pdo e pdo_pgsql. Queste annotazioni sono seguite dall'[esecutore di test dalla riga di comando|running-tests], che nel caso in cui le condizioni non siano soddisfatte, salta il test e nell'output lo contrassegna con la lettera `s` - skipped. Tuttavia, all'esecuzione manuale del test non hanno alcun effetto. -Per una descrizione delle annotazioni, vedere [Annotazioni dei test |Test Annotations]. +La descrizione delle annotazioni la trovate nella pagina [Annotazioni dei test|test-annotations]. -Il test può anche essere saltato in base a una propria condizione con `Environment::skip()`. Ad esempio, salteremo questo test su Windows: +È possibile far saltare un test anche in base al soddisfacimento di una condizione personalizzata tramite `Environment::skip()`. Ad esempio, questo salta i test su Windows: ```php if (defined('PHP_WINDOWS_VERSION_BUILD')) { - Tester\Environment::skip('Requires UNIX.'); + Tester\Environment::skip('Richiede UNIX.'); } ``` -Struttura delle directory .[#toc-directory-structure] -===================================================== +Struttura delle directory +========================= -Per librerie o progetti solo leggermente più grandi, si consiglia di dividere la directory di test in sottodirectory in base allo spazio dei nomi della classe testata: +Consigliamo, per librerie o progetti anche solo leggermente più grandi, di suddividere ulteriormente la directory con i test in sottodirectory secondo il namespace della classe testata: ``` └── tests/ @@ -269,22 +268,22 @@ Per librerie o progetti solo leggermente più grandi, si consiglia di dividere l └── ... ``` -In questo modo sarà possibile eseguire i test da un singolo spazio dei nomi, ovvero da una sottodirectory: +Potrete così eseguire i test da un singolo namespace ovvero sottodirectory: /--pre .[terminal] tester tests/NamespaceOne \-- -Casi limite .[#toc-edge-cases] -============================== +Situazioni speciali +=================== -Un test che non chiama alcun metodo di asserzione è sospetto e sarà valutato come errato: +Un test che non chiama nemmeno un metodo di asserzione è sospetto e viene valutato come errato: /--pre .[terminal] -Error: This test forgets to execute an assertion. +Errore: Questo test dimentica di eseguire un'asserzione. \-- -Se il test senza chiamare le asserzioni deve essere considerato valido, chiamare ad esempio `Assert::true(true)`. +Se davvero un test senza chiamate di asserzioni deve essere considerato valido, chiamate ad esempio `Assert::true(true)`. -Può anche essere insidioso usare `exit()` e `die()` per terminare il test con un messaggio di errore. Ad esempio, `exit('Error in connection')` termina il test con un codice di uscita 0, che segnala il successo. Utilizzare `Assert::fail('Error in connection')`. +Può anche essere insidioso usare `exit()` e `die()` per terminare un test con un messaggio di errore. Ad esempio `exit('Errore nella connessione')` termina il test con il codice di ritorno 0, che segnala successo. Usate `Assert::fail('Errore nella connessione')`. diff --git a/tester/ja/@home.texy b/tester/ja/@home.texy new file mode 100644 index 0000000000..722097f647 --- /dev/null +++ b/tester/ja/@home.texy @@ -0,0 +1,2 @@ +{{maintitle: Nette Tester – PHPでの快適なテスト}} +{{description: Nette Testerは、PHPコードをテストするためのシンプルでありながら非常に便利なツールです。}} diff --git a/tester/ja/@left-menu.texy b/tester/ja/@left-menu.texy new file mode 100644 index 0000000000..2b52b53bfd --- /dev/null +++ b/tester/ja/@left-menu.texy @@ -0,0 +1,8 @@ +- [Nette Tester 入門 |guide] +- [テストの作成 |writing-tests] +- [テストの実行 |running-tests] + +- [アサーション |assertions] +- [テストアノテーション |test-annotations] +- [TestCase |TestCase] +- [ヘルパークラス |helpers] diff --git a/tester/ja/@menu.texy b/tester/ja/@menu.texy new file mode 100644 index 0000000000..cd3e18ab29 --- /dev/null +++ b/tester/ja/@menu.texy @@ -0,0 +1,3 @@ +- [はじめに |@home] +- [ドキュメント |guide] +- "GitHub .[link-external]":https://github.com/nette/tester diff --git a/tester/ja/@meta.texy b/tester/ja/@meta.texy new file mode 100644 index 0000000000..aa196b919b --- /dev/null +++ b/tester/ja/@meta.texy @@ -0,0 +1 @@ +{{sitename: Tester ドキュメンテーション}} diff --git a/tester/ja/assertions.texy b/tester/ja/assertions.texy new file mode 100644 index 0000000000..c305ef1a0c --- /dev/null +++ b/tester/ja/assertions.texy @@ -0,0 +1,286 @@ +アサーション +****** + +.[perex] +アサーションは、実際の値が期待値に対応することを確認するために使用されます。これらは `Tester\Assert` クラスのメソッドです。 + +最も適切なアサーションを選択してください。`Assert::same($a, $b)` は `Assert::true($a === $b)` よりも優れています。なぜなら、失敗した場合に意味のあるエラーメッセージが表示されるからです。2 番目のケースでは、`false should be true` のみが表示され、変数 `$a` と `$b` の内容については何もわかりません。 + +ほとんどのアサーションには、オプションの `$description` パラメータを含めることもできます。これは、期待が失敗した場合にエラーメッセージに表示されます。 + +例では、エイリアスが作成されていることを前提としています。 + +```php +use Tester\Assert; +``` + + +Assert::same($expected, $actual, ?string $description=null) .[method] +--------------------------------------------------------------------- +`$expected` は `$actual` と同一でなければなりません。PHP 演算子 `===` と同じです。 + + +Assert::notSame($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------ +`Assert::same()` の逆。つまり、PHP 演算子 `!==` と同じです。 + + +Assert::equal($expected, $actual, ?string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] +------------------------------------------------------------------------------------------------------------------------- +`$expected` は `$actual` と同じでなければなりません。`Assert::same()` とは異なり、オブジェクトの同一性、配列内のキー => 値ペアの順序、およびわずかに異なる 10 進数は無視されます。これは `$matchIdentity` と `$matchOrder` を設定することで変更できます。 + +次のケースは `equal()` の観点からは同じですが、`same()` の観点からは同じではありません。 + +```php +Assert::equal(0.3, 0.1 + 0.2); +Assert::equal($obj, clone $obj); +Assert::equal( + ['first' => 11, 'second' => 22], + ['second' => 22, 'first' => 11], +); +``` + +ただし、注意してください。配列 `[1, 2]` と `[2, 1]` は同じではありません。なぜなら、キー => 値ペアではなく、値の順序のみが異なるからです。配列 `[1, 2]` は `[0 => 1, 1 => 2]` と書くこともでき、したがって `[1 => 2, 0 => 1]` が同じと見なされます。 + +さらに、`$expected` でいわゆる [#期待値] を使用できます。 + + +Assert::notEqual($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------- +`Assert::equal()` の逆。 + + +Assert::contains($needle, string|array $actual, ?string $description=null) .[method] +------------------------------------------------------------------------------------ +`$actual` が文字列の場合、部分文字列 `$needle` を含まなければなりません。配列の場合、要素 `$needle` を含まなければなりません(厳密に比較されます)。 + + +Assert::notContains($needle, string|array $actual, ?string $description=null) .[method] +--------------------------------------------------------------------------------------- +`Assert::contains()` の逆。 + + +Assert::hasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +-------------------------------------------------------------------------------------------------------- +`$actual` は配列でなければならず、キー `$needle` を含まなければなりません。 + + +Assert::notHasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +----------------------------------------------------------------------------------------------------------- +`$actual` は配列でなければならず、キー `$needle` を含んではいけません。 + + +Assert::true($value, ?string $description=null) .[method] +--------------------------------------------------------- +`$value` は `true` でなければなりません。つまり、`$value === true` です。 + + +Assert::truthy($value, ?string $description=null) .[method] +----------------------------------------------------------- +`$value` は真でなければなりません。つまり、条件 `if ($value) ...` を満たします。 + + +Assert::false($value, ?string $description=null) .[method] +---------------------------------------------------------- +`$value` は `false` でなければなりません。つまり、`$value === false` です。 + + +Assert::falsey($value, ?string $description=null) .[method] +----------------------------------------------------------- +`$value` は偽でなければなりません。つまり、条件 `if (!$value) ...` を満たします。 + + +Assert::null($value, ?string $description=null) .[method] +--------------------------------------------------------- +`$value` は `null` でなければなりません。つまり、`$value === null` です。 + + +Assert::notNull($value, ?string $description=null) .[method] +------------------------------------------------------------ +`$value` は `null` であってはなりません。つまり、`$value !== null` です。 + + +Assert::nan($value, ?string $description=null) .[method] +-------------------------------------------------------- +`$value` は Not a Number でなければなりません。NAN 値のテストには、`Assert::nan()` のみを使用してください。NAN 値は非常に特殊であり、アサーション `Assert::same()` または `Assert::equal()` は予期せず動作する可能性があります。 + + +Assert::count($count, Countable|array $value, ?string $description=null) .[method] +---------------------------------------------------------------------------------- +`$value` 内の要素の数は `$count` でなければなりません。つまり、`count($value) === $count` と同じです。 + + +Assert::type(string|object $type, $value, ?string $description=null) .[method] +------------------------------------------------------------------------------ +`$value` は指定された型でなければなりません。`$type` として文字列を使用できます。 +- `array` +- `list` - ゼロから始まる昇順の数値キーでインデックス付けされた配列 +- `bool` +- `callable` +- `float` +- `int` +- `null` +- `object` +- `resource` +- `scalar` +- `string` +- クラス名または直接オブジェクト。その場合、`$value instanceof $type` でなければなりません。 + + +Assert::exception(callable $callable, string $class, ?string $message=null, $code=null) .[method] +------------------------------------------------------------------------------------------------- +`$callable` を呼び出すと、クラス `$class` の例外がスローされなければなりません。`$message` を指定した場合、例外メッセージも [パターンに一致 |#Assert::match] しなければならず、`$code` を指定した場合、コードも厳密に一致しなければなりません。 + +次のテストは、例外メッセージが一致しないため失敗します。 + +```php +Assert::exception( + fn() => throw new App\InvalidValueException('ゼロ値'), + App\InvalidValueException::class, + '値が低すぎます', +); +``` + +`Assert::exception()` はスローされた例外を返します。したがって、ネストされた例外もテストできます。 + +```php +$e = Assert::exception( + fn() => throw new MyException('何かが間違っています', 0, new RuntimeException), + MyException::class, + '何かが間違っています', +); + +Assert::type(RuntimeException::class, $e->getPrevious()); +``` + + +Assert::error(callable $callable, int|string|array $type, ?string $message=null) .[method] +------------------------------------------------------------------------------------------ +関数 `$callable` が期待されるエラー(つまり、警告、通知など)を生成したことを確認します。`$type` として、`E_...` 定数の 1 つ、たとえば `E_WARNING` を指定します。そして、`$message` を指定した場合、エラーメッセージも [パターンに一致 |#Assert::match] しなければなりません。例: + +```php +Assert::error( + fn() => $i++, + E_NOTICE, + '未定義の変数: i', +); +``` + +コールバックが複数のエラーを生成する場合、それらすべてを正確な順序で期待する必要があります。その場合、`$type` に配列を渡します。 + +```php +Assert::error(function () { + $a++; + $b++; +}, [ + [E_NOTICE, '未定義の変数: a'], + [E_NOTICE, '未定義の変数: b'], +]); +``` + +.[note] +`$type` としてクラス名を指定した場合、`Assert::exception()` と同じように動作します。 + + +Assert::noError(callable $callable) .[method] +--------------------------------------------- +関数 `$callable` が警告、エラー、または例外を生成しなかったことを確認します。他のアサーションがないコード片をテストする場合に役立ちます。 + + +Assert::match(string $pattern, $actual, ?string $description=null) .[method] +---------------------------------------------------------------------------- +`$actual` はパターン `$pattern` に一致しなければなりません。2 つのバリアントのパターンを使用できます:正規表現またはワイルドカード。 + +`$pattern` として正規表現を渡す場合、その区切り文字として `~` または `#` を使用する必要があります。他の区切り文字はサポートされていません。たとえば、$var が 16 進数のみを含む必要がある場合のテスト: + +```php +Assert::match('#^[0-9a-f]$#i', $var); +``` + +2 番目のバリアントは通常の文字列比較に似ていますが、`$pattern` でさまざまなワイルドカードを使用できます。 + +- `%a%` 1 つ以上の文字、改行文字を除く +- `%a?%` ゼロ個以上の文字、改行文字を除く +- `%A%` 1 つ以上の文字、改行文字を含む +- `%A?%` ゼロ個以上の文字、改行文字を含む +- `%s%` 1 つ以上の空白文字、改行文字を除く +- `%s?%` ゼロ個以上の空白文字、改行文字を除く +- `%S%` 1 つ以上の空白文字以外の文字 +- `%S?%` ゼロ個以上の空白文字以外の文字 +- `%c%` 任意の 1 文字、改行文字を除く +- `%d%` 1 つ以上の数字 +- `%d?%` ゼロ個以上の数字 +- `%i%` 符号付き整数値 +- `%f%` 浮動小数点数 +- `%h%` 1 つ以上の 16 進数 +- `%w%` 1 つ以上の英数字 +- `%%` % 文字 + +例: + +```php +# 再び 16 進数のテスト +Assert::match('%h%', $var); + +# ファイルパスと行番号の一般化 +Assert::match('ファイル %a% の %i% 行目でエラー', $errorMessage); // 'Error in file %a% on line %i%' を日本語に +``` + + +Assert::matchFile(string $file, $actual, ?string $description=null) .[method] +----------------------------------------------------------------------------- +アサーションは [#Assert::match()] と同じですが、パターンはファイル `$file` から読み込まれます。これは、非常に長い文字列をテストする場合に役立ちます。テストファイルは明確なままです。 + + +Assert::fail(string $message, $actual=null, $expected=null) .[method] +--------------------------------------------------------------------- +このアサーションは常に失敗します。時にはそれが役立ちます。オプションで、期待値と実際の値を指定することもできます。 + + +期待値 +--- +非定数要素を持つより複雑な構造を比較したい場合、上記のアサーションでは不十分な場合があります。たとえば、新しいユーザーを作成し、その属性を配列として返すメソッドをテストします。パスワードハッシュの値はわかりませんが、それが 16 進文字列でなければならないことはわかっています。そして、別の要素については、それが `DateTime` オブジェクトでなければならないことしかわかりません。 + +これらの状況では、`Assert::equal()` および `Assert::notEqual()` メソッドの `$expected` パラメータ内で `Tester\Expect` を使用できます。これにより、構造を簡単に記述できます。 + +```php +use Tester\Expect; + +Assert::equal([ + 'id' => Expect::type('int'), # 整数を期待します + 'username' => 'milo', + 'password' => Expect::match('%h%'), # パターンに一致する文字列を期待します + 'created_at' => Expect::type(DateTime::class), # クラスのインスタンスを期待します +], User::create(123, 'milo', 'RandomPaSsWoRd')); +``` + +`Expect` を使用すると、`Assert` とほぼ同じアサーションを実行できます。つまり、メソッド `Expect::same()`、`Expect::match()`、`Expect::count()` などが利用可能です。さらに、それらを連鎖させることができます。 + +```php +Expect::type(MyIterator::class)->andCount(5); # MyIterator と要素数 5 を期待します +``` + +または、独自のアサーションハンドラを作成することもできます。 + +```php +Expect::that(function ($value) { + # 期待が失敗した場合に false を返します +}); +``` + + +失敗したアサーションの調査 +------------- +アサーションが失敗すると、Tester は何が間違っているかを出力します。より複雑な構造を比較する場合、Tester は比較された値のダンプを作成し、それらを `output` ディレクトリに保存します。たとえば、架空のテスト `Arrays.recursive.phpt` が失敗した場合、ダンプは次のように保存されます。 + +``` +app/ +└── tests/ + ├── output/ + │ ├── Arrays.recursive.actual # 実際の値 + │ └── Arrays.recursive.expected # 期待値 + │ + └── Arrays.recursive.phpt # 失敗したテスト +``` + +ディレクトリ名は `Tester\Dumper::$dumpDir` を介して変更できます。 diff --git a/tester/ja/guide.texy b/tester/ja/guide.texy new file mode 100644 index 0000000000..5d802b977e --- /dev/null +++ b/tester/ja/guide.texy @@ -0,0 +1,175 @@ +Nette Tester を始める +***************** + +
                                                                                                                            + +優れたプログラマでも間違いを犯します。優れたプログラマとそうでないプログラマの違いは、優れたプログラマは一度だけ間違いを犯し、次回は自動テストでそれを検出することです。 + +- 「テストしない者は、自分の過ちを繰り返す運命にある。」(ことわざ) +- 「1 つのエラーを取り除くと、別のエラーが現れる。」(マーフィーの法則) +- 「画面に変数を表示したくなったら、代わりにテストを書いてください。」(マーティン・ファウラー) + +
                                                                                                                            + +PHP でこのようなコードを書いたことがありますか? + +```php +$obj = new MyClass; +$result = $obj->process($input); + +var_dump($result); +``` + +つまり、関数の呼び出し結果を表示して、それが期待どおりのものを返すかどうかを目で確認するためだけに?きっと毎日何度もやっているでしょう。正直に言って:すべてが正しく機能する場合、このコードを削除しますか?クラスが将来壊れないと期待しますか?マーフィーの法則は逆を保証します :-) + +基本的に、あなたはテストを書きました。それを少し修正して、目視検査を必要とせず、自己チェックするようにするだけです。そして、テストを削除しなければ、将来いつでも実行して、すべてがまだ期待どおりに機能することを確認できます。時間が経つにつれて、そのようなテストを多数作成することになるため、それらを自動的に実行できると便利です。 + +そして、これらすべてを Nette Tester が支援します。 + + +Tester は何がユニークなのか? +================== + +Nette Tester のテスト作成がユニークなのは、**各テストが個別に実行できる通常の PHP スクリプトである** ことです。 + +したがって、テストを作成するときに、それを簡単に実行して、たとえばプログラミングエラーがないかどうかを確認できます。正しく機能するかどうか。そうでない場合は、IDE で簡単にステップ実行してエラーを探すことができます。ブラウザで開くことさえできます。 + +そして何よりも - それを実行することによって、テストを実行します。合格したか失敗したかをすぐに確認できます。どのように?見てみましょう。PHP 配列を操作する簡単なテストを作成し、`ArrayTest.php` ファイルに保存します。 + +```php .{file:ArrayTest.php} +OK +\-- + +テストでアサーションを偽の `Assert::contains('XXX', $stack);` に変更し、実行時に何が起こるかを見てください。 + +/--pre .[terminal] +$ php ArrayTest.php + +Failed: ['foo'] should contain 'XXX' + +in ArrayTest.php(11) Assert::contains('XXX', $stack); + +FAILURE +\-- + +テストの作成については、[テストの作成|writing-tests] の章で続行します。 + + +インストールと要件 +========= + +Tester が必要とする最小 PHP バージョンは 7.1 です(詳細は [#サポートされている PHP バージョン] 表を参照)。推奨されるインストール方法は [Composer |best-practices:composer] を使用することです。 + +/--pre .[terminal] +composer require --dev nette/tester +\-- + +コマンドラインから Nette Tester を実行してみてください(パラメータなしではヘルプのみが表示されます)。 + +/--pre .[terminal] +vendor/bin/tester +\-- + + +テストの実行 +====== + +アプリケーションが成長するにつれて、テストの数も増えます。テストを 1 つずつ実行するのは実用的ではありません。そのため、Tester にはコマンドラインから呼び出す一括テストランナーが用意されています。パラメータとして、テストが配置されているディレクトリを指定します。現在のディレクトリの場合はドットを指定します。 + +/--pre .[terminal] +vendor/bin/tester . +\-- + +テストランナーは、指定されたディレクトリとすべてのサブディレクトリを検索し、テスト(`*.phpt` および `*Test.php` ファイル)を探します。これにより、マスクに一致するため、私たちのテスト `ArrayTest.php` も見つかります。 + +次に、テストを開始します。各テストは新しい PHP プロセスとして実行されるため、他のテストから完全に分離されます。複数のスレッドで並行して実行されるため、非常に高速です。そして、前回の実行で失敗したテストを最初に実行するため、エラーを修正できたかどうかをすぐに確認できます。 + +テストの実行中、Tester は結果を文字として継続的にターミナルに出力します。 + +- . – テスト合格 +- s – テストスキップ (skipped) +- F – テスト失敗 (failed) + +出力は次のようになります。 + +/--pre .[terminal] + _____ ___ ___ _____ ___ ___ +|_ _/ __)( __/_ _/ __)| _ ) + |_| \___ /___) |_| \___ |_|_\ v2.5.2 + +Note: No php.ini is used. +PHP 8.3.2 (cli) | php -n | 8 threads + +........s................F......... + +-- FAILED: greeting.phpt + Failed: 'Hello John' should be + ... 'Hello Peter' + + in greeting.phpt(19) Assert::same('Hello Peter', $o->say('John')); + +FAILURES! (35 tests, 1 failures, 1 skipped, 1.7 seconds) +\-- + +35 個のテストが実行され、1 個が失敗し、1 個がスキップされました。 + +次に、[テストの実行|running-tests] の章で続行します。 + + +Watch モード +========= + +コードをリファクタリングしていますか?それとも TDD(テスト駆動開発)手法に従って開発していますか?それなら、watch モードが気に入るでしょう。このモードでは、Tester はソースコードを監視し、変更があると自動的に実行されます。 + +開発中は、モニターの隅にターミナルがあり、緑色のステータスバーが表示されています。突然赤色に変わると、何かを完全に正しく行わなかったことがわかります。これは実際には素晴らしいゲームで、プログラミングしながら色を維持しようとします。 + +Watch モードは [--watch |running-tests#-w --watch path] パラメータで開始されます。 + + +コードカバレッジレポート +============ + +Tester は、テストがソースコードのどれだけをカバーしているかの概要を示すレポートを生成できます。レポートは、人間が読める HTML 形式、またはさらなる機械処理のための Clover XML 形式のいずれかになります。 + +コードカバレッジの "HTML レポートのサンプル":attachment:coverage.html をご覧ください。 + + +サポートされている PHP バージョン +=================== + +| バージョン | PHP との互換性 +|------------------|------------------- +| Tester 2.5 | PHP 8.0 – 8.3 +| Tester 2.4 | PHP 7.2 – 8.2 +| Tester 2.3 | PHP 7.1 – 8.0 +| Tester 2.1 – 2.2 | PHP 7.1 – 7.3 +| Tester 2.0 | PHP 5.6 – 7.3 +| Tester 1.7 | PHP 5.3 – 7.3 + HHVM 3.3+ +| Tester 1.6 | PHP 5.3 – 7.0 + HHVM +| Tester 1.3 – 1.5 | PHP 5.3 – 5.6 + HHVM +| Tester 0.9 – 1.2 | PHP 5.3 – 5.6 + +最新のパッチバージョンに適用されます。 + +バージョン 1.7 までの Tester は [HHVM |https://hhvm.com] 3.3.0 以降もサポートしていました(`tester -p hhvm` 経由)。サポートは Tester バージョン 2.0 から終了しました。 diff --git a/tester/ja/helpers.texy b/tester/ja/helpers.texy new file mode 100644 index 0000000000..752f48118d --- /dev/null +++ b/tester/ja/helpers.texy @@ -0,0 +1,166 @@ +ヘルパークラス +******* + + +DomQuery +-------- +`Tester\DomQuery` は、CSS セレクタを使用して HTML または XML を簡単に検索するために `SimpleXMLElement` を拡張するクラスです。 + +```php +# HTML 文字列から DomQuery を作成 +$dom = Tester\DomQuery::fromHtml(' +
                                                                                                                            +

                                                                                                                            Title

                                                                                                                            +
                                                                                                                            Text
                                                                                                                            +
                                                                                                                            +'); + +# CSS セレクタを使用して要素の存在をテスト +Assert::true($dom->has('article.post')); +Assert::true($dom->has('h1')); + +# DomQuery オブジェクトの配列として要素を検索 +$headings = $dom->find('h1'); +Assert::same('Title', (string) $headings[0]); + +# 要素がセレクタに一致するかどうかをテスト(バージョン 2.5.3 以降) +$content = $dom->find('.content')[0]; +Assert::true($content->matches('div')); +Assert::false($content->matches('p')); + +# セレクタに一致する最も近い祖先を検索(2.5.5 以降) +$article = $content->closest('.post'); +Assert::true($article->matches('article')); +``` + + +FileMock +-------- +`Tester\FileMock` はメモリ内でファイルをエミュレートし、`fopen()`、`file_get_contents()`、`parse_ini_file()` などの関数を使用するコードのテストを容易にします。使用例: + +```php +# テスト対象クラス +class Logger +{ + public function __construct( + private string $logFile, + ) { + } + + public function log(string $message): void + { + file_put_contents($this->logFile, $message . "\n", FILE_APPEND); + } +} + +# 新しい空のファイル +$file = Tester\FileMock::create(''); + +$logger = new Logger($file); +$logger->log('Login'); +$logger->log('Logout'); + +# 作成された内容をテスト +Assert::same("Login\nLogout\n", file_get_contents($file)); +``` + + +Assert::with() .[filter] +------------------------ +これはアサーションではなく、オブジェクトのプライベートメソッドとプロパティをテストするためのヘルパーです。 + +```php +class Entity +{ + private $enabled; + // ... +} + +$ent = new Entity; + +Assert::with($ent, function () { + Assert::true($this->enabled); // アクセス可能なプライベート $ent->enabled +}); +``` + + +Helpers::purge() .[filter] +-------------------------- +`purge()` メソッドは指定されたディレクトリを作成し、すでに存在する場合はその内容全体を削除します。一時ディレクトリを作成するのに役立ちます。たとえば、`tests/bootstrap.php` で: + +```php +@mkdir(__DIR__ . '/tmp'); # @ - ディレクトリはすでに存在する可能性があります + +define('TempDir', __DIR__ . '/tmp/' . getmypid()); +Tester\Helpers::purge(TempDir); +``` + + +Environment::lock() .[filter] +----------------------------- +テストは並行して実行されます。ただし、テストの実行が重複しないようにする必要がある場合があります。通常、データベーステストでは、テストがデータベースの内容を準備し、実行中に別のテストがデータベースにアクセスしないようにする必要があります。これらのテストでは、`Tester\Environment::lock($name, $dir)` を使用します。 + +```php +Tester\Environment::lock('database', __DIR__ . '/tmp'); +``` + +最初のパラメータはロックの名前、2 番目はロックを保存するディレクトリへのパスです。最初にロックを取得したテストが実行され、他のテストはその完了を待つ必要があります。 + + +Environment::bypassFinals() .[filter] +------------------------------------- +`final` としてマークされたクラスまたはメソッドはテストが困難です。テストの開始時に `Tester\Environment::bypassFinals()` を呼び出すと、コードの読み込み中に `final` キーワードが省略されます。 + +```php +require __DIR__ . '/bootstrap.php'; + +Tester\Environment::bypassFinals(); + +class MyClass extends NormallyFinalClass # <-- NormallyFinalClass はもはや final ではありません +{ + // ... +} +``` + + +Environment::setup() .[filter] +------------------------------ +- エラー出力の可読性を向上させます(色付けを含む)。そうでない場合は、デフォルトの PHP スタックトレースが出力されます。 +- テストでアサーションが呼び出されたかどうかのチェックを有効にします。そうでない場合、アサーションのないテスト(たとえば、忘れられたもの)も合格します。 +- `--coverage` を使用する場合、実行されたコードに関する情報の収集を自動的に開始します +- スクリプトの最後に OK または FAILURE ステータスを出力します。 + + +Environment::setupFunctions() .[filter]{data-version:2.5} +--------------------------------------------------------- +テストを構造化できるグローバル関数 `test()`、`testException()`、`setUp()`、`tearDown()` を作成します。 + +```php +test('テストの説明', function () { + Assert::same(123, foo()); + Assert::false(bar()); + // ... +}); +``` + + +Environment::VariableRunner .[filter] +------------------------------------- +テストが直接実行されたか、Tester を介して実行されたかを判断できます。 + +```php +if (getenv(Tester\Environment::VariableRunner)) { + # Tester によって実行されました +} else { + # 他の方法で実行されました +} +``` + + +Environment::VariableThread .[filter] +------------------------------------- +Tester は、指定された数のスレッドでテストを並行して実行します。スレッド番号に関心がある場合は、環境変数からそれを取得します。 + +```php +echo "スレッド番号 " . getenv(Tester\Environment::VariableThread) . " で実行中"; +``` diff --git a/tester/ja/running-tests.texy b/tester/ja/running-tests.texy new file mode 100644 index 0000000000..6a1ab46702 --- /dev/null +++ b/tester/ja/running-tests.texy @@ -0,0 +1,239 @@ +テストの実行 +****** + +.[perex] +Nette Tester の最も目に見える部分は、コマンドラインからのテストランナーです。すべてのテストを個別のプロセスとして自動的に実行し、複数のスレッドで並行して実行するため、非常に高速で堅牢です。また、いわゆるウォッチモードで自己実行することもできます。 + +テストランナーはコマンドラインから呼び出します。パラメータとして、テストを含むディレクトリを指定します。現在のディレクトリの場合は、ドットを入力するだけです。 + +/--pre .[terminal] +vendor/bin/tester . +\-- + +テストランナーは、指定されたディレクトリとすべてのサブディレクトリを検索し、テスト(`*.phpt` および `*Test.php` ファイル)を探します。同時に、それらの [アノテーション|test-annotations] を読み取り、評価して、どのテストをどのように実行するかを判断します。 + +次に、テストを実行します。テストの実行中、結果を文字として継続的にターミナルに出力します。 + +- . – テスト合格 +- s – テストスキップ (skipped) +- F – テスト失敗 (failed) + +出力は次のようになります。 + +/--pre .[terminal] + _____ ___ ___ _____ ___ ___ +|_ _/ __)( __/_ _/ __)| _ ) + |_| \___ /___) |_| \___ |_|_\ v2.5.2 + +Note: No php.ini is used. +PHP 8.3.2 (cli) | php -n | 8 threads + +........s.......................... + +OK (35 tests, 1 skipped, 1.7 seconds) +\-- + +繰り返し実行すると、前回の実行で失敗したテストが最初に実行されるため、エラーを修正できたかどうかをすぐに確認できます。 + +テストが失敗しなかった場合、Tester のリターンコードはゼロです。それ以外の場合、リターンコードはゼロ以外です。 + +.[warning] +Tester は `php.ini` なしで PHP プロセスを実行します。詳細は [#カスタム php.ini] セクションを参照してください。 + + +コマンドラインパラメータ +============ + +すべてのコマンドラインオプションの概要は、パラメータなしで Tester を実行するか、`-h` パラメータを使用して取得できます。 + +/--pre .[terminal] + _____ ___ ___ _____ ___ ___ +|_ _/ __)( __/_ _/ __)| _ ) + |_| \___ /___) |_| \___ |_|_\ v2.5.2 + +Usage: + tester [options] [ | ]... + +Options: + -p Specify PHP interpreter to run (default: php). + -c Look for php.ini file (or look in directory) . + -C Use system-wide php.ini. + -d ... Define INI entry 'key' with value 'value'. + -s Show information about skipped tests. + --stop-on-fail Stop execution upon the first failure. + -j Run jobs in parallel (default: 8). + -o (e.g. -o junit:output.xml) + Specify one or more output formats with optional file name. + -w | --watch Watch directory. + -i | --info Show tests environment info and exit. + --setup Script for runner setup. + --temp Path to temporary directory. Default by sys_get_temp_dir(). + --colors [1|0] Enable or disable colors. + --coverage Generate code coverage report to file. + --coverage-src Path to source code. + -h | --help This help. +\-- + + +-p .[filter] +------------------- +テストの実行に使用する PHP バイナリを指定します。デフォルトは `php` です。 + +/--pre .[terminal] +tester -p /home/user/php-7.2.0-beta/php-cgi tests +\-- + + +-c .[filter] +------------------- +テスト実行時に使用する `php.ini` を指定します。デフォルトでは、php.ini は使用されません。詳細は [#カスタム php.ini] セクションを参照してください。 + + +-C .[filter] +------------ +システム全体の `php.ini` が使用されます。UNIX では、関連するすべての INI ファイル `/etc/php/{sapi}/conf.d/*.ini` も使用されます。詳細は [#カスタム php.ini] セクションを参照してください。 + + +-d .[filter] +------------------------ +テスト用の PHP 設定ディレクティブの値を設定します。パラメータは複数回使用できます。 + +/--pre .[terminal] +tester -d max_execution_time=20 +\-- + + +-s +--- +スキップされたテストに関する情報が表示されます。 + + +--stop-on-fail .[filter] +------------------------ +Tester は最初の失敗したテストでテストを停止します。 + + +-j .[filter] +------------------ +実行するテストの並列プロセスの数を指定します。デフォルト値は 8 です。すべてのテストを直列に実行したい場合は、値 1 を使用します。 + + +-o .[filter] +------------------------------------------------------- +出力形式を設定します。デフォルトはコンソール形式です。出力が書き込まれるファイル名を指定できます(例:`-o junit:output.xml`)。`-o` オプションを複数回繰り返して、一度に複数の形式を生成できます。 + +- `console`: デフォルト形式と同じですが、この場合 ASCII ロゴは表示されません。 +- `console-lines`: console に似ていますが、各テストの結果は追加情報とともに別の行に表示されます。 +- `tap`: 機械処理に適した [TAP 形式 |https://en.wikipedia.org/wiki/Test_Anything_Protocol]。 +- `junit`: JUnit XML 形式。これも機械処理に適しています。 +- `log`: テスト実行の出力。失敗したテスト、スキップされたテスト、および成功したテストすべて。 +- `none`: 何も出力されません。 + + +''-w | --watch '' .[filter] +--------------------------------- +テストが完了した後、Tester は終了せず、実行を続け、指定されたディレクトリ内の PHP ファイルを監視します。変更があると、テストを再度実行します。パラメータは、複数のディレクトリを監視したい場合に複数回使用できます。 + +ライブラリのリファクタリングやテストのデバッグに役立ちます。 + +/--pre .[terminal] +tester --watch src tests +\-- + + +''-i | --info'' .[filter] +------------------------- +テストの実行環境に関する情報を表示します。例: + +/--pre .[terminal] +tester -p /usr/bin/php7.1 -c tests/php.ini --info + +PHP binary: +/usr/bin/php7.1 + +PHP version: +7.1.7-1+0~20170711133844.5+jessie~1.gbp5284f4 (cli) + +Code coverage engines: +(not available) + +Loaded php.ini files: +/var/www/dev/demo/tests/php.ini + +PHP temporary directory: +/tmp + +Loaded extensions: +Core, ctype, date, dom, ereg, fileinfo, filter, hash, ... +\-- + + +--setup .[filter] +------------------------ +Tester は起動時に指定された PHP スクリプトを読み込みます。その中で、変数 `Tester\Runner\Runner $runner` が利用可能です。ファイル `tests/runner-setup.php` に次の内容が含まれていると仮定します。 + +```php +$runner->outputHandlers[] = new MyOutputHandler; +``` + +Tester を実行します: + +/--pre .[terminal] +tester --setup tests/runner-setup.php tests +\-- + + +--temp .[filter] +----------------------- +Tester の一時ファイル用のディレクトリへのパスを設定します。デフォルト値は `sys_get_temp_dir()` によって返されます。デフォルト値が無効な場合は、警告が表示されます。 + +どのディレクトリが使用されているかわからない場合は、`--info` パラメータを指定して Tester を実行します。 + + +--colors 1|0 .[filter] +---------------------- +デフォルトでは、Tester はカラーターミナルを検出し、出力を色付けします。このオプションは自動検出を上書きします。システム環境変数 `NETTE_TESTER_COLORS` を使用して、グローバルに色付けを設定できます。 + + +--coverage .[filter] +--------------------------- +Tester は、テストがソースコードのどれだけをカバーしているかの概要を示すレポートを生成します。このオプションには、インストールされた PHP 拡張機能 [Xdebug |https://xdebug.org/] または [PCOV |https://github.com/krakjoe/pcov]、あるいはより高速な PHP 7 と PHPDBG SAPI が必要です。ターゲットファイルの拡張子は、その形式を決定します。HTML または Clover XML のいずれかです。 + +/--pre .[terminal] +tester tests --coverage coverage.html # HTML レポート +tester tests --coverage coverage.xml # Clover XML レポート +\-- + +メカニズム選択の優先順位は次のとおりです。 +1) PCOV +2) PHPDBG +3) Xdebug + +PHPDBG を使用する場合、大規模なテストではメモリ不足のためにテストが失敗することがあります。コードカバレッジ情報の収集はメモリを大量に消費します。この場合、テスト内で `Tester\CodeCoverage\Collector::flush()` を呼び出すと役立ちます。収集されたデータをディスクに書き込み、メモリを解放します。データ収集が実行されていない場合、または Xdebug が使用されている場合、呼び出しは何の効果もありません。 + +コードカバレッジの [HTML レポートのサンプル|attachment:coverage.html]。 + + +--coverage-src .[filter] +------------------------------- +`--coverage` オプションと同時に使用します。`` は、レポートが生成されるソースコードへのパスです。繰り返し使用できます。 + + +カスタム php.ini +============ +Tester は `-n` パラメータを指定して PHP プロセスを実行します。これは、`php.ini` が読み込まれないことを意味します。UNIX では、`/etc/php/conf.d/*.ini` からのものも読み込まれません。これにより、テスト実行のための同一の環境が保証されますが、システム PHP によって通常読み込まれるすべての PHP 拡張機能も無効になります。 + +システム php.ini ファイルの読み込みを維持したい場合は、`-C` パラメータを使用します。 + +テストに拡張機能や特別な INI 設定が必要な場合は、テストとともに配布される独自の `php.ini` ファイルを作成することをお勧めします。次に、`-c` パラメータを指定して Tester を実行します。たとえば、`tester -c tests/php.ini tests` のようにします。ここで、INI ファイルは次のようになります。 + +```ini +[PHP] + +extension=php_pdo_mysql.dll +extension=php_pdo_pgsql.dll + +memory_limit=512M +``` + +システム `php.ini` を使用して UNIX で Tester を実行する場合、たとえば `tester -c /etc/php/cli/php.ini` は `/etc/php/conf.d/*.ini` から他の INI を読み込みません。これは Tester ではなく PHP の機能です。 diff --git a/tester/ja/test-annotations.texy b/tester/ja/test-annotations.texy new file mode 100644 index 0000000000..a565fa3d69 --- /dev/null +++ b/tester/ja/test-annotations.texy @@ -0,0 +1,170 @@ +テストアノテーション +********** + +.[perex] +アノテーションは、テストが [コマンドラインテストランナー |running-tests] によってどのように扱われるかを決定します。これらはテストファイルの先頭に記述されます。 + +アノテーションでは大文字と小文字は区別されません。また、テストが通常の PHP スクリプトとして手動で実行される場合、何の効果もありません。 + +例: + +```php +/** + * TEST: Basic database query test. + * + * @dataProvider files/databases.ini + * @exitCode 56 + * @phpVersion < 5.5 + */ + +require __DIR__ . '/../bootstrap.php'; +``` + + +TEST .[filter] +-------------- +これは実際にはアノテーションではなく、失敗時またはログに出力されるテストのタイトルを指定するだけです。 + + +@skip .[filter] +--------------- +テストはスキップされます。テストを一時的に無効にするのに役立ちます。 + + +@phpVersion .[filter] +--------------------- +対応する PHP バージョンで実行されていない場合、テストはスキップされます。アノテーションは `@phpVersion [operator] version` として記述します。演算子は省略でき、デフォルトは `>=` です。例: + +```php +/** + * @phpVersion 5.3.3 + * @phpVersion < 5.5 + * @phpVersion != 5.4.5 + */ +``` + + +@phpExtension .[filter] +----------------------- +リストされているすべての PHP 拡張機能が読み込まれていない場合、テストはスキップされます。1 つのアノテーションに複数の拡張機能をリストするか、複数回使用できます。 + +```php +/** + * @phpExtension pdo, pdo_pgsql, pdo_mysql + * @phpExtension json + */ +``` + + +@dataProvider .[filter] +----------------------- +テストファイルを複数回実行したいが、異なる入力データで実行したい場合、このアノテーションが役立ちます。([TestCase |TestCase#dataProvider] の同名のアノテーションと混同しないでください。) + +`@dataProvider file.ini` として記述します。ファイルへのパスはテストファイルからの相対パスと見なされます。テストは INI ファイル内のセクションの数だけ実行されます。INI ファイル `databases.ini` を想定します。 + +```ini +[mysql] +dsn = "mysql:host=127.0.0.1" +user = root +password = ****** + +[postgresql] +dsn = "pgsql:host=127.0.0.1;dbname=test" +user = postgres +password = ****** + +[sqlite] +dsn = "sqlite::memory:" +``` + +そして、同じディレクトリにあるテスト `database.phpt`: + +```php +/** + * @dataProvider databases.ini + */ + +$args = Tester\Environment::loadData(); +``` + +テストは 3 回実行され、`$args` には常に `mysql`、`postgresql`、または `sqlite` セクションの値が含まれます。 + +アノテーションを疑問符付きで `@dataProvider? file.ini` として記述するバリアントもあります。この場合、INI ファイルが存在しない場合、テストはスキップされます。 + +アノテーションの可能性はこれで終わりではありません。INI ファイル名の後に、特定のセクションに対してテストが実行される条件を指定できます。INI ファイルを拡張します。 + +```ini +[mysql] +dsn = "mysql:host=127.0.0.1" +user = root +password = ****** + +[postgresql 8.4] +dsn = "pgsql:host=127.0.0.1;dbname=test" +user = postgres +password = ****** + +[postgresql 9.1] +dsn = "pgsql:host=127.0.0.1;dbname=test;port=5433" +user = postgres +password = ****** + +[sqlite] +dsn = "sqlite::memory:" +``` + +そして、条件付きのアノテーションを使用します。 + +```php +/** + * @dataProvider databases.ini postgresql, >=9.0 + */ +``` + +テストは `postgresql 9.1` セクションに対して 1 回だけ実行されます。他のセクションは条件フィルタを通過しません。 + +同様に、INI ファイルの代わりに PHP スクリプトを参照できます。これは配列または Traversable を返す必要があります。ファイル `databases.php`: + +```php +return [ + 'postgresql 8.4' => [ + 'dsn' => '...', + 'user' => '...', + ], + + 'postgresql 9.1' => [ + 'dsn' => '...', + 'user' => '...', + ], +]; +``` + + +@multiple .[filter] +------------------- +`@multiple N` として記述します。ここで `N` は整数です。テストは正確に N 回実行されます。 + + +@testCase .[filter] +------------------- +アノテーションにはパラメータがありません。[TestCase |TestCase] クラスとしてテストを作成する場合に使用します。この場合、コマンドラインテストランナーは個々のメソッドを個別のプロセスで並行して複数のスレッドで実行します。これにより、テストプロセス全体が大幅に高速化される可能性があります。 + + +@exitCode .[filter] +------------------- +`@exitCode N` として記述します。ここで `N` は実行されたテストのリターンコードです。たとえば、テストで `exit(10)` が呼び出された場合、アノテーションを `@exitCode 10` として記述し、テストが別のコードで終了した場合、それは失敗と見なされます。アノテーションが指定されていない場合、リターンコード 0(ゼロ)が検証されます。 + + +@httpCode .[filter] +------------------- +アノテーションは、PHP バイナリが CGI の場合にのみ適用されます。それ以外の場合は無視されます。`@httpCode NNN` として記述します。ここで `NNN` は期待される HTTP コードです。アノテーションが指定されていない場合、HTTP コード 200 が検証されます。`NNN` がゼロに評価される文字列、たとえば `any` として記述された場合、HTTP コードは検証されません。 + + +@outputMatch および @outputMatchFile .[filter] +------------------------------------------- +アノテーションの機能は、アサーション `Assert::match()` および `Assert::matchFile()` と同じです。ただし、パターンはテストが標準出力に出力したテキスト内で検索されます。これは、テストが致命的なエラーで終了すると予想され、その出力を検証する必要がある場合に役立ちます。 + + +@phpIni .[filter] +----------------- +テストの INI 設定値を設定します。たとえば、`@phpIni precision=20` として記述し、コマンドラインからパラメータ `-d precision=20` を介して値を指定した場合と同じように機能します。 diff --git a/tester/ja/testcase.texy b/tester/ja/testcase.texy new file mode 100644 index 0000000000..83b60b81b8 --- /dev/null +++ b/tester/ja/testcase.texy @@ -0,0 +1,180 @@ +TestCase +******** + +.[perex] +単純なテストでは、アサーションを次々に続けることができます。しかし、時にはアサーションをテストクラスにラップして構造化する方が有利な場合があります。 + +クラスは `Tester\TestCase` の子孫でなければならず、簡単に **testcase** と呼ばれます。クラスには `test` で始まるテストメソッドが含まれている必要があります。これらのメソッドはテストとして実行されます。 + +```php +use Tester\Assert; + +class RectangleTest extends Tester\TestCase +{ + public function testOne() + { + Assert::same(/* ... */); + } + + public function testTwo() + { + Assert::match(/* ... */); + } +} + +# テストメソッドの実行 +(new RectangleTest)->run(); +``` + +このように書かれたテストは、メソッド `setUp()` と `tearDown()` でさらに強化できます。これらは、各テストメソッドの前後にそれぞれ呼び出されます。 + +```php +use Tester\Assert; + +class NextTest extends Tester\TestCase +{ + public function setUp() + { + # 準備 + } + + public function tearDown() + { + # クリーンアップ + } + + public function testOne() + { + Assert::same(/* ... */); + } + + public function testTwo() + { + Assert::match(/* ... */); + } +} + +# テストメソッドの実行 +(new NextTest)->run(); + +/* + + +メソッド呼び出し順序 +---------- +setUp() +testOne() +tearDown() + +setUp() +testTwo() +tearDown() +*/ +``` + +`setUp()` または `tearDown()` フェーズでエラーが発生した場合、テストは全体として失敗します。テストメソッドでエラーが発生した場合でも、`tearDown()` メソッドは実行されますが、その中のエラーは抑制されます。 + +テストの開始時に [@testCase |test-annotations#testCase] アノテーションを記述することをお勧めします。そうすれば、コマンドラインテストランナーは testcase の個々のメソッドを個別のプロセスで並行して複数のスレッドで実行します。これにより、テストプロセス全体が大幅に高速化される可能性があります。 + +/--php + 1, 'b' => 2, 'c' => 3], + ['a' => 4, 'b' => 5, 'c' => 6], + ['a' => 7, 'b' => 8, 'c' => 9], +]; +``` diff --git a/tester/ja/writing-tests.texy b/tester/ja/writing-tests.texy new file mode 100644 index 0000000000..83e5fbc8cb --- /dev/null +++ b/tester/ja/writing-tests.texy @@ -0,0 +1,289 @@ +テストの作成 +****** + +.[perex] +Nette Tester のテスト作成がユニークなのは、各テストが個別に実行できる PHP スクリプトであることです。これには大きな可能性があります。 テストを作成しているときでさえ、それを簡単に実行して、正しく機能するかどうかを確認できます。そうでない場合は、IDE で簡単にステップ実行してエラーを探すことができます。 + +テストをブラウザで開くことさえできます。しかし何よりも - それを実行することによって、テストを実行します。合格したか失敗したかをすぐに確認できます。 + +導入章では、配列を操作する本当に簡単なテストを [示しました |guide#Tester は何がユニークなのか]。今回は、テストする独自のクラスを作成しますが、それも単純なものになります。 + +ライブラリまたはプロジェクトの典型的なディレクトリ構造から始めます。テストをコードの残りの部分から分離することが重要です。たとえば、デプロイメントのためです。なぜなら、テストを本番サーバーにアップロードしたくないからです。構造は次のようになります。 + +``` +├── src/ # テストするコード +│ ├── Rectangle.php +│ └── ... +├── tests/ # テスト +│ ├── bootstrap.php +│ ├── RectangleTest.php +│ └── ... +├── vendor/ +└── composer.json +``` + +そして今、個々のファイルを作成します。テスト対象のクラスから始め、それを `src/Rectangle.php` ファイルに配置します。 + +```php .{file:src/Rectangle.php} +width = $width; + $this->height = $height; + } + + public function getArea(): float + { + return $this->width * $this->height; + } + + public function isSquare(): bool + { + return $this->width === $this->height; + } +} +``` + +そして、それに対するテストを作成します。テストファイルの名前は `*Test.php` または `*.phpt` のマスクに一致する必要があります。たとえば、`RectangleTest.php` バリアントを選択します。 + + +```php .{file:tests/RectangleTest.php} +getArea()); # 期待される結果を確認します +Assert::false($rect->isSquare()); +``` + +ご覧のとおり、`Assert::same()` のような、いわゆる [アサーションメソッド |assertions] は、実際の値が期待値に対応することを確認するために使用されます。 + +残りの最後のステップは、`bootstrap.php` ファイルです。これには、すべてのテストに共通のコードが含まれています。たとえば、クラスのオートロード、環境設定、一時ディレクトリの作成、ヘルパー関数などです。すべてのテストはブートストラップを読み込み、その後はテストのみに専念します。ブートストラップは次のようになります。 + +```php .{file:tests/bootstrap.php} +OK +\-- + +テストでアサーションを偽の `Assert::same(123, $rect->getArea());` に変更すると、次のようになります。 + +/--pre .[terminal] +$ php tests/RectangleTest.php + +Failed: 200.0 should be 123 + +in RectangleTest.php(8) Assert::same(123, $rect->getArea()); + +FAILURE +\-- + + +テストを作成するときは、すべての境界状況をカバーすることをお勧めします。たとえば、入力がゼロ、負の数、他のケースでは空の文字列、null などです。実際には、そのような状況でコードがどのように動作するかを考え、決定することを強制します。テストはその後、その動作を固定します。 + +私たちの場合、負の値は例外をスローする必要があり、これは [Assert::exception() |Assertions#Assert::exception] を使用して検証します。 + +```php .{file:tests/RectangleTest.php} +// 幅は負であってはなりません +Assert::exception( + fn() => new Rectangle(-1, 20), + InvalidArgumentException::class, + 'The dimension must not be negative.', +); +``` + +そして、高さに対して同様のテストを追加します。最後に、両方の寸法が同じ場合に `isSquare()` が `true` を返すことをテストします。演習として、そのようなテストを作成してみてください。 + + +より明確なテスト +======== + +テストファイルのサイズは大きくなり、すぐにわかりにくくなる可能性があります。したがって、個々のテスト領域を個別の関数にグループ化することが実用的です。 + +まず、より単純ですがエレガントなバリアント、つまりグローバル関数 `test()` を使用する方法を示します。Tester は、コード内に同じ名前の関数がある場合に衝突を避けるために、自動的に作成しません。`bootstrap.php` ファイルで呼び出す `setupFunctions()` メソッドによって作成されます。 + +```php .{file:tests/bootstrap.php} +Tester\Environment::setup(); +Tester\Environment::setupFunctions(); +``` + +この関数を使用すると、テストファイルを名前付きのユニットにきれいに分割できます。実行すると、説明が順番に出力されます。 + +```php .{file:tests/RectangleTest.php} +getArea()); + Assert::false($rect->isSquare()); +}); + +test('一般的な正方形', function () { + $rect = new Rectangle(5, 5); + Assert::same(25.0, $rect->getArea()); + Assert::true($rect->isSquare()); +}); + +test('寸法は負であってはなりません', function () { + Assert::exception( + fn() => new Rectangle(-1, 20), + InvalidArgumentException::class, + ); + + Assert::exception( + fn() => new Rectangle(10, -1), + InvalidArgumentException::class, + ); +}); +``` + +各テストの前後にコードを実行する必要がある場合は、それを関数 `setUp()` または `tearDown()` に渡します。 + +```php +setUp(function () { + // 各 test() の前に実行される初期化コード +}); +``` + +2 番目のバリアントはオブジェクト指向です。いわゆる TestCase を作成します。これは、個々のユニットが `test` で始まる名前を持つメソッドであるクラスです。 + +```php .{file:tests/RectangleTest.php} +class RectangleTest extends Tester\TestCase +{ + public function testGeneralOblong() + { + $rect = new Rectangle(10, 20); + Assert::same(200.0, $rect->getArea()); + Assert::false($rect->isSquare()); + } + + public function testGeneralSquare() + { + $rect = new Rectangle(5, 5); + Assert::same(25.0, $rect->getArea()); + Assert::true($rect->isSquare()); + } + + /** @throws InvalidArgumentException */ + public function testWidthMustNotBeNegative() + { + $rect = new Rectangle(-1, 20); + } + + /** @throws InvalidArgumentException */ + public function testHeightMustNotBeNegative() + { + $rect = new Rectangle(10, -1); + } +} + +// テストメソッドの実行 +(new RectangleTest)->run(); +``` + +今回は例外をテストするために `@throws` アノテーションを使用しました。詳細は [TestCase |TestCase] の章で確認できます。 + + +ヘルパー関数 +====== + +Nette Tester には、たとえば HTML ドキュメントの内容のテスト、ファイルを操作する関数のテストなどを容易にするいくつかのクラスと関数が含まれています。 + +それらの説明は [ヘルパークラス |helpers] ページにあります。 + + +アノテーションとテストのスキップ +================ + +テストの実行は、ファイルの先頭にある phpDoc コメント形式のアノテーションによって影響を受ける可能性があります。たとえば、次のようになります。 + +```php .{file:tests/RectangleTest.php} +/** + * @phpExtension pdo, pdo_pgsql + * @phpVersion >= 7.2 + */ +``` + +リストされているアノテーションは、テストが PHP バージョン 7.2 以降でのみ実行され、PHP 拡張機能 pdo と pdo_pgsql が存在する場合にのみ実行されることを示します。これらのアノテーションは [コマンドラインテストランナー |running-tests] によって従われ、条件が満たされない場合、テストをスキップし、出力で文字 `s` - skipped でマークします。ただし、テストを手動で実行する場合、何の効果もありません。 + +アノテーションの説明は [テストアノテーション |test-annotations] ページにあります。 + +`Environment::skip()` を使用して、独自の条件に基づいてテストをスキップすることもできます。たとえば、これは Windows でのテストをスキップします。 + +```php +if (defined('PHP_WINDOWS_VERSION_BUILD')) { + Tester\Environment::skip('Requires UNIX.'); +} +``` + + +ディレクトリ構造 +======== + +少し大きなライブラリやプロジェクトでは、テスト対象クラスの名前空間に従って、テストを含むディレクトリをサブディレクトリに分割することをお勧めします。 + +``` +└── tests/ + ├── NamespaceOne/ + │ ├── MyClass.getUsers.phpt + │ ├── MyClass.setUsers.phpt + │ └── ... + │ + ├── NamespaceTwo/ + │ ├── MyClass.creating.phpt + │ ├── MyClass.dropping.phpt + │ └── ... + │ + ├── bootstrap.php + └── ... +``` + +これにより、単一の名前空間、つまりサブディレクトリからテストを実行できます。 + +/--pre .[terminal] +vendor/bin/tester tests/NamespaceOne +\-- + + +特殊な状況 +===== + +アサーションメソッドを 1 つも呼び出さないテストは疑わしく、エラーとして評価されます。 + +/--pre .[terminal] +Error: This test forgets to execute an assertion. +\-- + +アサーション呼び出しなしのテストが本当に有効と見なされる必要がある場合は、たとえば `Assert::true(true)` を呼び出します。 + +また、エラーメッセージでテストを終了するために `exit()` と `die()` を使用するのは危険な場合があります。たとえば、`exit('Error in connection')` はリターンコード 0 でテストを終了します。これは成功を示します。`Assert::fail('Error in connection')` を使用してください。 diff --git a/tester/pl/@home.texy b/tester/pl/@home.texy index 3460e838be..85234696a8 100644 --- a/tester/pl/@home.texy +++ b/tester/pl/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Nette Tester - fajne testy w PHP}} -{{description: Nette Tester to proste, ale bardzo poręczne narzędzie do testowania kodu PHP.}} +{{maintitle: Nette Tester – wygodne testowanie w PHP}} +{{description: Nette Tester to proste, a jednocześnie bardzo sprytne narzędzie do testowania kodu PHP.}} diff --git a/tester/pl/@left-menu.texy b/tester/pl/@left-menu.texy index 55d1de5ce3..42f10ab298 100644 --- a/tester/pl/@left-menu.texy +++ b/tester/pl/@left-menu.texy @@ -1,8 +1,8 @@ -- [Rozpoczęcie pracy z Nette Tester |guide] -- [Testy pisarskie |writing-tests] -- [Przeprowadzanie testów |running-tests] +- [Zaczynamy z Nette Tester |guide] +- [Pisanie testów |writing-tests] +- [Uruchamianie testów |running-tests] -- [Zapewnienie |assertions] -- [Dodawanie adnotacji do testów |test-annotations] +- [Asercje |assertions] +- [Adnotacje testów |test-annotations] - [TestCase |TestCase] -- [Zajęcia pomocnicze |helpers] +- [Klasy pomocnicze |helpers] diff --git a/tester/pl/@meta.texy b/tester/pl/@meta.texy new file mode 100644 index 0000000000..c818b06e79 --- /dev/null +++ b/tester/pl/@meta.texy @@ -0,0 +1 @@ +{{sitename: Dokumentacja Tester}} diff --git a/tester/pl/assertions.texy b/tester/pl/assertions.texy index 04e9b5e33a..2793bf0e57 100644 --- a/tester/pl/assertions.texy +++ b/tester/pl/assertions.texy @@ -1,35 +1,35 @@ -Asercja +Asercje ******* .[perex] -Asercje są używane do potwierdzenia, że rzeczywista wartość pasuje do wartości oczekiwanej. Są to metody klasy `Tester\Assert`. +Asercje służą do potwierdzenia, że rzeczywista wartość odpowiada oczekiwanej wartości. Są to metody klasy `Tester\Assert`. -Wybierz najbardziej odpowiednie twierdzenia. Jest to lepsze `Assert::same($a, $b)` niż `Assert::true($a === $b)`, ponieważ wyświetli sensowny komunikat o błędzie, gdy się nie powiedzie. W tym drugim przypadku tylko `false should be true`, który nie mówi nam nic o zawartości zmiennych `$a` i `$b`. +Wybieraj jak najbardziej odpowiednie asercje. Lepsze jest `Assert::same($a, $b)` niż `Assert::true($a === $b)`, ponieważ przy niepowodzeniu wyświetli sensowny komunikat błędu. W drugim przypadku tylko `false should be true`, co nic nam nie mówi o zawartości zmiennych `$a` i `$b`. -Również większość asercji może mieć opcjonalną etykietę w parametrze `$description`, która zostanie wyświetlona w komunikacie o błędzie, jeśli oczekiwanie nie powiedzie się. +Większość asercji może również mieć opcjonalny opis w parametrze `$description`, który zostanie wyświetlony w komunikacie błędu, jeśli oczekiwanie zawiedzie. -W przykładach założono, że alias został utworzony: +Przykłady zakładają utworzony alias: ```php use Tester\Assert; ``` -Assert::same($expected, $actual, string $description=null) .[method] --------------------------------------------------------------------- -`$expected` musi być identyczny z `$actual`. Identyczny jak operator PHP `===`. +Assert::same($expected, $actual, ?string $description=null) .[method] +--------------------------------------------------------------------- +`$expected` musi być identyczny z `$actual`. To samo co operator PHP `===`. -Assert::notSame($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------ +Assert::notSame($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------ Przeciwieństwo `Assert::same()`, czyli to samo co operator PHP `!==`. -Assert::equal($expected, $actual, string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] ------------------------------------------------------------------------------------------------------------------------- -`$expected` musi być taka sama jak `$actual`. W przeciwieństwie do `Assert::same()`, tożsamość obiektu, kolejność par kluczy => wartości w polach i marginalnie różne liczby dziesiętne są ignorowane, co można zmienić poprzez ustawienie `$matchIdentity` i `$matchOrder`. +Assert::equal($expected, $actual, ?string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] +------------------------------------------------------------------------------------------------------------------------- +`$expected` musi być taki sam jak `$actual`. W przeciwieństwie do `Assert::same()` ignoruje się tożsamość obiektów, kolejność par klucz => wartość w tablicach i marginalnie różne liczby dziesiętne, co można zmienić ustawieniem `$matchIdentity` i `$matchOrder`. -Poniższe przypadki są identyczne z perspektywy `equal()`, ale nie `same()`: +Następujące przypadki są zgodne z punktu widzenia `equal()`, ale nie `same()`: ```php Assert::equal(0.3, 0.1 + 0.2); @@ -40,81 +40,81 @@ Assert::equal( ); ``` -Jednakże, uważaj, pole `[1, 2]` a `[2, 1]` nie są takie same, ponieważ tylko kolejność wartości jest inna, a nie pary klucz => wartość. Pole `[1, 2]` można również zapisać jako `[0 => 1, 1 => 2]` i dlatego będą uważane za takie same `[1 => 2, 0 => 1]`. +Jednak uwaga, tablice `[1, 2]` i `[2, 1]` nie są takie same, ponieważ różnią się tylko kolejnością wartości, a nie par klucz => wartość. Tablicę `[1, 2]` można zapisać również jako `[0 => 1, 1 => 2]` i za taką samą będzie uważana `[1 => 2, 0 => 1]`. -Ponadto w `$expected` można wykorzystać tzw. [oczekiwania |#Expectations]. +Dalej w `$expected` można użyć tzw. [#Oczekiwania]. -Assert::notEqual($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------- +Assert::notEqual($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------- Przeciwieństwo `Assert::equal()`. -Assert::contains($needle, string|array $actual, string $description=null) .[method] ------------------------------------------------------------------------------------ -Jeśli `$actual` jest ciągiem, musi zawierać podłańcuch `$needle`. Jeśli jest to tablica, musi zawierać element `$needle` (dopasowany ściśle). +Assert::contains($needle, string|array $actual, ?string $description=null) .[method] +------------------------------------------------------------------------------------ +Jeśli `$actual` jest ciągiem znaków, musi zawierać podciąg `$needle`. Jeśli jest tablicą, musi zawierać element `$needle` (porównuje się ściśle). -Assert::notContains($needle, string|array $actual, string $description=null) .[method] --------------------------------------------------------------------------------------- +Assert::notContains($needle, string|array $actual, ?string $description=null) .[method] +--------------------------------------------------------------------------------------- Przeciwieństwo `Assert::contains()`. -Assert::hasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} -------------------------------------------------------------------------------------------------------- +Assert::hasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +-------------------------------------------------------------------------------------------------------- `$actual` musi być tablicą i musi zawierać klucz `$needle`. -Assert::notHasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} ----------------------------------------------------------------------------------------------------------- +Assert::notHasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +----------------------------------------------------------------------------------------------------------- `$actual` musi być tablicą i nie może zawierać klucza `$needle`. -Assert::true($value, string $description=null) .[method] --------------------------------------------------------- +Assert::true($value, ?string $description=null) .[method] +--------------------------------------------------------- `$value` musi być `true`, czyli `$value === true`. -Assert::truthy($value, string $description=null) .[method] ----------------------------------------------------------- -`$value` musi być prawdziwa, czyli spełnia warunek `if ($value) ...`. +Assert::truthy($value, ?string $description=null) .[method] +----------------------------------------------------------- +`$value` musi być prawdziwy, czyli spełni warunek `if ($value) ...`. -Assert::false($value, string $description=null) .[method] ---------------------------------------------------------- +Assert::false($value, ?string $description=null) .[method] +---------------------------------------------------------- `$value` musi być `false`, czyli `$value === false`. -Assert::falsey($value, string $description=null) .[method] ----------------------------------------------------------- -`$value` musi być fałszywy, czyli spełnia warunek `if (!$value) ...`. +Assert::falsey($value, ?string $description=null) .[method] +----------------------------------------------------------- +`$value` musi być fałszywy, czyli spełni warunek `if (!$value) ...`. -Assert::null($value, string $description=null) .[method] --------------------------------------------------------- +Assert::null($value, ?string $description=null) .[method] +--------------------------------------------------------- `$value` musi być `null`, czyli `$value === null`. -Assert::notNull($value, string $description=null) .[method] ------------------------------------------------------------ +Assert::notNull($value, ?string $description=null) .[method] +------------------------------------------------------------ `$value` nie może być `null`, czyli `$value !== null`. -Assert::nan($value, string $description=null) .[method] -------------------------------------------------------- -`$value` musi być "Not a Number". Do testowania wartości NAN używaj wyłącznie `Assert::nan()` Wartość NAN jest bardzo specyficzna i asercje `Assert::same()` lub `Assert::equal()` mogą działać nieoczekiwanie. +Assert::nan($value, ?string $description=null) .[method] +-------------------------------------------------------- +`$value` musi być Not a Number. Do testowania wartości NAN używaj wyłącznie `Assert::nan()`. Wartość NAN jest bardzo specyficzna i asercje `Assert::same()` lub `Assert::equal()` mogą działać nieoczekiwanie. -Assert::count($count, Countable|array $value, string $description=null) .[method] ---------------------------------------------------------------------------------- -Liczba elementów w `$value` musi być `$count`. Zatem taka sama jak `count($value) === $count`. +Assert::count($count, Countable|array $value, ?string $description=null) .[method] +---------------------------------------------------------------------------------- +Liczba elementów w `$value` musi być `$count`. Czyli to samo co `count($value) === $count`. -Assert::type(string|object $type, $value, string $description=null) .[method] ------------------------------------------------------------------------------ +Assert::type(string|object $type, $value, ?string $description=null) .[method] +------------------------------------------------------------------------------ `$value` musi być danego typu. Jako `$type` możemy użyć ciągu znaków: - `array` -- `list` - tablica indeksowana rosnącym szeregiem kluczy numerycznych od zera +- `list` - tablica indeksowana według rosnącej serii kluczy numerycznych od zera - `bool` - `callable` - `float` @@ -124,14 +124,14 @@ Assert::type(string|object $type, $value, string $description=null) .[method] - `resource` - `scalar` - `string` -- nazwa klasy lub samego obiektu, to musi być `$value instanceof $type` +- nazwa klasy lub bezpośrednio obiekt, wtedy musi być `$value instanceof $type` -Assert::exception(callable $callable, string $class, string $message=null, $code=null) .[method] ------------------------------------------------------------------------------------------------- -Po wywołaniu `$callable` musi zostać rzucony wyjątek klasowy `$class` Jeśli podamy `$message`, komunikat wyjątku musi [pasować do wzorca |#Assert-match], a jeśli podamy `$code`, kody muszą być ściśle [dopasowane |#Assert-match]. +Assert::exception(callable $callable, string $class, ?string $message=null, $code=null) .[method] +------------------------------------------------------------------------------------------------- +Przy wywołaniu `$callable` musi zostać rzucony wyjątek klasy `$class`. Jeśli podamy `$message`, musi [odpowiadać wzorowi |#Assert::match] również komunikat wyjątku, a jeśli podamy `$code`, muszą się ściśle zgadzać również kody. -Poniższy test kończy się niepowodzeniem, ponieważ komunikat wyjątku nie pasuje: +Następujący test zawiedzie, ponieważ nie odpowiada komunikat wyjątku: ```php Assert::exception( @@ -141,7 +141,7 @@ Assert::exception( ); ``` -`Assert::exception()` zwraca rzucony wyjątek, więc można przetestować zagnieżdżony wyjątek. +`Assert::exception()` zwraca rzucony wyjątek, można więc przetestować również wyjątek zagnieżdżony. ```php $e = Assert::exception( @@ -154,9 +154,9 @@ Assert::type(RuntimeException::class, $e->getPrevious()); ``` -Assert::error(string $callable, int|string|array $type, string $message=null) .[method] ---------------------------------------------------------------------------------------- -Sprawdza, czy funkcja `$callable` wygenerowała oczekiwane błędy (tj. Ostrzeżenia, powiadomienia itp.). Jako `$type` podajemy jedną ze stałych `E_...`, czyli np. `E_WARNING`. A jeśli określimy `$message`, to komunikat o błędzie musi [pasować do wzorca |#Assert-match]. Na przykład: +Assert::error(string $callable, int|string|array $type, ?string $message=null) .[method] +---------------------------------------------------------------------------------------- +Sprawdza, czy funkcja `$callable` wygenerowała oczekiwane błędy (tj. ostrzeżenia, notices itp.). Jako `$type` podamy jedną ze stałych `E_...`, czyli na przykład `E_WARNING`. A jeśli podamy `$message`, musi [odpowiadać wzorowi |#Assert::match] również komunikat błędu. Na przykład: ```php Assert::error( @@ -166,7 +166,7 @@ Assert::error( ); ``` -Jeśli callback generuje wiele błędów, musimy oczekiwać ich wszystkich w dokładnej kolejności. W tym przypadku przekazujemy pole `$type`: +Jeśli callback wygeneruje więcej błędów, musimy je wszystkie oczekiwać w dokładnej kolejności. W takim przypadku przekażemy w `$type` tablicę: ```php Assert::error(function () { @@ -179,108 +179,108 @@ Assert::error(function () { ``` .[note] -Jeśli określisz nazwę klasy jako `$type`, zachowuje się ona tak samo jak `Assert::exception()`. +Jeśli jako `$type` podasz nazwę klasy, zachowuje się tak samo jak `Assert::exception()`. Assert::noError(callable $callable) .[method] --------------------------------------------- -Sprawdza, czy funkcja `$callable` nie wygenerowała żadnych ostrzeżeń, błędów lub wyjątków. Jest to przydatne do testowania fragmentów kodu, w których nie ma innej asercji. +Sprawdza, czy funkcja `$callable` nie wygenerowała żadnego ostrzeżenia, błędu ani wyjątku. Przydaje się do testowania fragmentów kodu, gdzie nie ma żadnej innej asercji. -Assert::match(string $pattern, $actual, string $description=null) .[method] ---------------------------------------------------------------------------- -`$actual` musi pasować do wzorca `$pattern`. Możemy użyć dwóch wariantów wzorców: wyrażeń regularnych lub symboli wieloznacznych. +Assert::match(string $pattern, $actual, ?string $description=null) .[method] +---------------------------------------------------------------------------- +`$actual` musi pasować do wzoru `$pattern`. Możemy użyć dwóch wariantów wzorów: wyrażeń regularnych lub symboli wieloznacznych. -Jeśli przekażemy wyrażenie regularne jako `$pattern`, musimy użyć `~` nebo `#` do jego delimitacji, inne delimitery nie są obsługiwane. Na przykład test, w którym `$var` musi zawierać tylko cyfry szesnastkowe: +Jeśli jako `$pattern` przekażemy wyrażenie regularne, do jego ograniczenia musimy użyć `~` lub `#`, inne ograniczniki nie są obsługiwane. Na przykład test, gdy `$var` musi zawierać tylko cyfry heksadecymalne: ```php Assert::match('#^[0-9a-f]$#i', $var); ``` -Druga opcja jest podobna do dopasowywania ciągów regularnych, ale możemy używać różnych znaków wieloznacznych w `$pattern`: - -- `%a%` jeden lub więcej znaków, z wyjątkiem znaków końca linii -- `%a?%` żaden lub więcej znaków, z wyjątkiem znaków końca linii -- `%A%` jeden lub więcej znaków, w tym znaki końca linii -- `%A?%` żaden lub więcej znaków, w tym znaki końca linii -- `%s%` jeden lub więcej białych znaków, z wyłączeniem znaków końca linii -- `%s?%` żaden lub więcej białych znaków, z wyjątkiem znaków końca linii -- `%S%` jeden lub więcej znaków, z wyłączeniem białych znaków -- `%S?%` żaden lub więcej znaków, z wyjątkiem białych znaków -- `%c%` dowolny znak, z wyjątkiem znaków przerwy w linii +Druga warianta jest podobna do zwykłego porównania ciągów znaków, ale w `$pattern` możemy użyć różnych symboli wieloznacznych: + +- `%a%` jeden lub więcej znaków, oprócz znaków końca linii +- `%a?%` zero lub więcej znaków, oprócz znaków końca linii +- `%A%` jeden lub więcej znaków, włącznie ze znakami końca linii +- `%A?%` zero lub więcej znaków, włącznie ze znakami końca linii +- `%s%` jeden lub więcej białych znaków, oprócz znaków końca linii +- `%s?%` zero lub więcej białych znaków, oprócz znaków końca linii +- `%S%` jeden lub więcej znaków, oprócz białych znaków +- `%S?%` zero lub więcej znaków, oprócz białych znaków +- `%c%` jakikolwiek jeden znak, oprócz znaku końca linii - `%d%` jedna lub więcej cyfr -- `%d?%` brak lub więcej cyfr -- `%i%` podpisana wartość całkowita -- `%f%` liczba z kropką dziesiętną -- `%h%` jedna lub więcej cyfr szesnastkowych +- `%d?%` zero lub więcej cyfr +- `%i%` wartość całkowita ze znakiem +- `%f%` liczba z przecinkiem dziesiętnym +- `%h%` jedna lub więcej cyfr heksadecymalnych - `%w%` jeden lub więcej znaków alfanumerycznych - `%%` znak % Przykłady: ```php -# Opět test na hexadecimální číslo +# Ponownie test na liczbę heksadecymalną Assert::match('%h%', $var); -# Zobecnění cesty k souboru a čísla řádky +# Uogólnienie ścieżki do pliku i numeru linii Assert::match('Error in file %a% on line %i%', $errorMessage); ``` -Assert::matchFile(string $file, $actual, string $description=null) .[method] ----------------------------------------------------------------------------- -Asercja jest identyczna jak [Assert::match() |#Assert-match], ale wzór jest odczytywany z `$file`. Jest to przydatne do testowania bardzo długich łańcuchów. Plik testowy pozostaje przezroczysty. +Assert::matchFile(string $file, $actual, ?string $description=null) .[method] +----------------------------------------------------------------------------- +Asercja jest identyczna z [#Assert::match()], ale wzór jest wczytywany z pliku `$file`. Jest to przydatne do testowania bardzo długich ciągów znaków. Plik z testem pozostanie przejrzysty. Assert::fail(string $message, $actual=null, $expected=null) .[method] --------------------------------------------------------------------- -To twierdzenie zawsze się nie sprawdza. Czasami po prostu się przydaje. Opcjonalnie możemy dołączyć wartość oczekiwaną i rzeczywistą. +Ta asercja zawsze zawiedzie. Czasami po prostu się przydaje. Opcjonalnie możemy podać również oczekiwaną i aktualną wartość. -Oczekiwania .[#toc-expectations] --------------------------------- -Gdy chcemy porównywać bardziej złożone struktury z elementami niestałymi, powyższe twierdzenia mogą okazać się niewystarczające. Na przykład testujemy metodę, która tworzy nowego użytkownika i zwraca jego atrybuty jako tablicę. Nie znamy wartości hashowej hasła, ale wiemy, że musi to być ciąg szesnastkowy. A o następnym elemencie wiemy tylko tyle, że musi to być obiekt `DateTime`. +Oczekiwania +----------- +Gdy chcemy porównać bardziej złożone struktury z niestałymi elementami, powyższe asercje mogą nie być wystarczające. Na przykład testujemy metodę, która tworzy nowego użytkownika i zwraca jego atrybuty jako tablicę. Wartości hasha hasła nie znamy, ale wiemy, że musi to być ciąg heksadecymalny. A o kolejnym elemencie wiemy tylko, że musi to być obiekt `DateTime`. -W takich sytuacjach możemy użyć `Tester\Expect` wewnątrz parametru `$expected` metod `Assert::equal()` i `Assert::notEqual()`, za pomocą których możemy w prosty sposób opisać strukturę. +W tych sytuacjach możemy użyć `Tester\Expect` wewnątrz parametru `$expected` metod `Assert::equal()` i `Assert::notEqual()`, za pomocą których można łatwo opisać strukturę. ```php use Tester\Expect; Assert::equal([ - 'id' => Expect::type('int'), # očekáváme celé číslo + 'id' => Expect::type('int'), # oczekujemy liczby całkowitej 'username' => 'milo', - 'password' => Expect::match('%h%'), # očekáváme řetězec vyhovující vzoru - 'created_at' => Expect::type(DateTime::class), # očekáváme instanci třídy + 'password' => Expect::match('%h%'), # oczekujemy ciągu pasującego do wzoru + 'created_at' => Expect::type(DateTime::class), # oczekujemy instancji klasy ], User::create(123, 'milo', 'RandomPaSsWoRd')); ``` -Za pomocą `Expect` możemy wykonać prawie takie same asercje jak za pomocą `Assert`. Tym samym dostępne są dla nas metody `Expect::same()`, `Expect::match()`, `Expect::count()`, itd. Co więcej, możemy je konkatenować: +Z `Expect` możemy wykonywać prawie takie same asercje jak z `Assert`. Czyli mamy do dyspozycji metody `Expect::same()`, `Expect::match()`, `Expect::count()` itd. Ponadto możemy je łączyć w łańcuchy: ```php -Expect::type(MyIterator::class)->andCount(5); # očekáváme MyIterator a počet prvků 5 +Expect::type(MyIterator::class)->andCount(5); # oczekujemy MyIterator i liczby elementów 5 ``` -Możemy też napisać własne assertion handlers. +Albo możemy pisać własne handlery asercji. ```php Expect::that(function ($value) { - # vrátíme false, pokud očekávání selže + # zwrócimy false, jeśli oczekiwanie zawiedzie }); ``` -Badanie błędnych twierdzeń .[#toc-failed-assertions-investigation] ------------------------------------------------------------------- -Kiedy asercja się nie powiedzie, Tester wymienia, co to za błąd. Przy porównywaniu bardziej złożonych struktur Tester tworzy atrapy porównywanych wartości i przechowuje je w katalogu `output`. Na przykład, jeśli test manekinów `Arrays.recursive.phpt` nie powiedzie się, manekiny zostaną zapisane w następujący sposób: +Badanie błędnych asercji +------------------------ +Gdy asercja zawiedzie, Tester wypisze, na czym polega błąd. Jeśli porównujemy bardziej złożone struktury, Tester utworzy zrzuty porównywanych wartości i zapisze je w katalogu `output`. Na przykład przy niepowodzeniu fikcyjnego testu `Arrays.recursive.phpt` zrzuty zostaną zapisane następująco: ``` app/ └── tests/ ├── output/ - │ ├── Arrays.recursive.actual # aktuální hodnota - │ └── Arrays.recursive.expected # očekávaná hodnota + │ ├── Arrays.recursive.actual # aktualna wartość + │ └── Arrays.recursive.expected # oczekiwana wartość │ - └── Arrays.recursive.phpt # selhávající test + └── Arrays.recursive.phpt # zawodzący test ``` -Nazwę katalogu możemy zmienić poprzez stronę `Tester\Dumper::$dumpDir`. +Nazwę katalogu możemy zmienić przez `Tester\Dumper::$dumpDir`. diff --git a/tester/pl/guide.texy b/tester/pl/guide.texy index ef741f36ad..876d446302 100644 --- a/tester/pl/guide.texy +++ b/tester/pl/guide.texy @@ -1,17 +1,17 @@ -Rozpoczęcie pracy z Nette Tester -******************************** +Pierwsze kroki z Nette Tester +*****************************
                                                                                                                            -Nawet dobrzy programiści popełniają błędy. Różnica między dobrym programistą a złym polega na tym, że ten dobry robi to tylko raz, a następnym razem jest wykrywany przez testy automatyczne. +Nawet dobrzy programiści popełniają błędy. Różnica między dobrym a złym programistą polega na tym, że ten dobry popełni go tylko raz, a następnym razem wykryje go za pomocą zautomatyzowanych testów. -- "Ci, którzy nie testują, są skazani na powtarzanie swoich błędów". (przysłowie) -- "Jak tylko pozbędziemy się jednego błędu, pojawia się kolejny". (Prawo Murphy'ego) -- "Zawsze, gdy czujesz potrzebę napisania transformacji na ekranie, napisz zamiast tego test". (Martin Fowler) +- "Kto nie testuje, jest skazany na powtarzanie swoich błędów." (przysłowie) +- "Jak tylko pozbędziemy się jednego błędu, pojawi się kolejny." (prawo Murphy'ego) +- "Kiedykolwiek masz ochotę wypisać sobie na ekran zmienną, napisz raczej test." (Martin Fowler)
                                                                                                                            -Czy kiedykolwiek pisałeś podobny kod w PHP? +Czy kiedykolwiek napisałeś w PHP podobny kod? ```php $obj = new MyClass; @@ -20,40 +20,40 @@ $result = $obj->process($input); var_dump($result); ``` -To znaczy, czy wypisałeś wynik wywołania funkcji tylko po to, by sprawdzić na oko, czy zwraca to, co powinno? Jestem pewien, że robisz to wiele razy dziennie. Ręka na sercu: jeśli wszystko działa poprawnie, czy usunąć ten kod? Czy spodziewasz się, że w przyszłości klasa nie będzie się łamać? Prawa Murphy'ego gwarantują coś wręcz przeciwnego :-) +Czyli wypisałeś sobie wynik wywołania funkcji tylko po to, aby okiem zweryfikować, czy zwraca to, co powinna? Na pewno robisz to wiele razy dziennie. Ręka na serce: w przypadku, gdy wszystko działa poprawnie, usuwasz ten kod? Oczekujesz, że klasa w przyszłości się nie zepsuje? Prawa Murphy'ego gwarantują coś przeciwnego :-) -Zasadniczo napisałeś test. Trzeba go tylko lekko zmodyfikować, żeby nie wymagał sprawdzania na oko, tylko sprawdzał się sam. A jeśli nie usuniesz testu, możesz go uruchomić w dowolnym momencie w przyszłości i sprawdzić, czy wszystko nadal działa tak, jak powinno. Z czasem stworzysz dużą liczbę takich testów, więc przydałoby się je uruchamiać automatycznie. +W zasadzie napisałeś test. Wystarczy go tylko nieznacznie zmodyfikować, aby nie wymagał kontroli wzrokowej, ale aby sprawdził się sam. A jeśli testu nie usuniesz, możesz go uruchomić kiedykolwiek w przyszłości i zweryfikować, czy wszystko nadal działa, jak powinno. Z czasem stworzysz dużą liczbę takich testów, więc przydałoby się je uruchamiać automatycznie. -W tym wszystkim może pomóc Nette Tester. +A z tym wszystkim pomoże Ci właśnie Nette Tester. -Co sprawia, że Tester jest wyjątkowy? .[#toc-what-makes-tester-unique] -====================================================================== +Co wyróżnia Testera? +==================== -Pisanie testów dla Nette Tester jest o tyle wyjątkowe, że **każdy test jest zwykłym skryptem PHP, który może być uruchamiany niezależnie**. +Pisanie testów dla Nette Tester jest unikalne w tym, że **każdy test jest zwykłym skryptem PHP, który można uruchomić samodzielnie.** -Więc kiedy napiszesz test, możesz go po prostu uruchomić i zobaczyć, czy istnieje błąd programistyczny. Jeśli działa prawidłowo. Jeśli nie, możesz łatwo przejść przez to w swoim IDE i poszukać błędu. Można go nawet otworzyć w przeglądarce. +Czyli gdy piszesz test, możesz go po prostu uruchamiać i sprawdzać, czy na przykład nie ma w nim błędu programistycznego. Czy działa poprawnie. Jeśli nie, możesz go łatwo krokować w swoim IDE i szukać błędu. Możesz go nawet otworzyć w przeglądarce. -I co najważniejsze, uruchamiając go, wykonujesz test. Od razu dowiesz się, czy przeszedł, czy nie. Jak? Zobaczmy. Napiszmy trywialny test pracy z tablicami w PHP i zapiszmy go do pliku `ArrayTest.php`: +A przede wszystkim - tym, że go uruchomisz, wykonasz test. Natychmiast dowiesz się, czy przeszedł, czy zawiódł. Jak? Pokażmy to sobie. Napiszemy trywialny test pracy z tablicą PHP i zapiszemy do pliku `ArrayTest.php`: ```php .{file:ArrayTest.php} OK \-- -Spróbuj zmienić oświadczenie w teście na fałszywe `Assert::contains('XXX', $stack);` i obserwuj, co się stanie, gdy go uruchomisz: +Spróbuj w teście zmienić twierdzenie na nieprawdziwe `Assert::contains('XXX', $stack);` i obserwuj, co się stanie przy uruchomieniu: /--pre .[terminal] $ php ArrayTest.php @@ -73,53 +73,53 @@ $ php ArrayTest.php FAILURE \-- -Więcej informacji na ten temat znajduje się w rozdziale Pisanie [testów |writing-tests]. +Dalej o pisaniu kontynuujemy w rozdziale [Pisanie testów|writing-tests]. -Instalacja i wymagania .[#toc-installation-and-requirements] -============================================================ +Instalacja i wymagania +====================== -Minimalna wersja PHP wymagana przez Tester to 7.1 (szczegóły w tabeli [obsługiwanych wersji PHP |#Supported-PHP-Versions]). Preferowaną metodą instalacji jest użycie [Composera |best-practices:composer]: +Minimalna wersja PHP wymagana przez Testera to 7.1 (szczegółowiej w tabeli [#Obsługiwane wersje PHP]). Preferowany sposób instalacji to za pomocą [Composer |best-practices:composer]: /--pre .[terminal] composer require --dev nette/tester \-- -Spróbuj uruchomić Nette Tester z linii poleceń (bez parametrów wypisuje tylko pomoc): +Spróbuj z wiersza poleceń uruchomić Nette Tester (bez parametrów wypisze tylko pomoc): /--pre .[terminal] vendor/bin/tester \-- -Przeprowadzanie testów .[#toc-running-tests] -============================================ +Uruchamianie testów +=================== -Wraz z rozwojem aplikacji rośnie liczba testów. Przeprowadzanie testów po kolei nie byłoby praktyczne. Dlatego Tester posiada masowy launcher testów, który wywołujemy z linii poleceń. Jako parametr podajemy katalog, w którym znajdują się testy. Kropka wskazuje na bieżący katalog. +Jak aplikacja rośnie, liczba testów rośnie wraz z nią. Nie byłoby praktyczne uruchamiać testów pojedynczo. Dlatego Tester dysponuje masowym narzędziem do uruchamiania testów, które wywołujemy z wiersza poleceń. Jako parametr podajemy katalog, w którym znajdują się testy. Kropka oznacza bieżący katalog. /--pre .[terminal] vendor/bin/tester . \-- -Test runner przeskanuje podany katalog i wszystkie podkatalogi i znajdzie testy, którymi są pliki `*.phpt` i `*Test.php`. Znajdzie również nasz test `ArrayTest.php`, ponieważ pasuje on do maski. +Narzędzie do uruchamiania testów przeszuka podany katalog i wszystkie podkatalogi i wyszuka testy, czyli pliki `*.phpt` i `*Test.php`. Znajdzie więc również nasz test `ArrayTest.php`, ponieważ pasuje do maski. -Następnie rozpocznie się testowanie. Każdy test rozpoczyna się jako nowy proces PHP, więc działa całkowicie odizolowany od pozostałych. Uruchamia je równolegle w wielu wątkach, dzięki czemu jest niezwykle szybki. I najpierw uruchamia testy, które nie powiodły się w poprzednim uruchomieniu, więc od razu będziesz wiedział, czy udało Ci się naprawić błąd. +Następnie rozpocznie testowanie. Każdy test uruchomi jako nowy proces PHP, więc przebiega całkowicie odizolowany od pozostałych. Uruchamia je równolegle w wielu wątkach, dzięki czemu jest niezwykle szybki. A jako pierwsze uruchamia testy, które przy poprzednim uruchomieniu zawiodły, więc natychmiast dowiesz się, czy udało Ci się naprawić błąd. -Podczas wykonywania testów, Tester stale drukuje wyniki do terminala jako znaki: +Podczas wykonywania testów Tester wypisuje na bieżąco wyniki na terminal jako znaki: -- . - test zaliczony -- s - test został pominięty -- F - test nie powiódł się +- . – test przeszedł +- s – test został pominięty (skipped) +- F – test zawiódł (failed) -Dane wyjściowe mogą wyglądać tak: +Wyjście może wyglądać tak: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 -Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +Uwaga: Nie użyto pliku php.ini. +PHP 8.3.2 (cli) | php -n | 8 wątków ........s................F......... @@ -132,44 +132,44 @@ PHP 7.4.8 (cli) | php -n | 8 threads FAILURES! (35 tests, 1 failures, 1 skipped, 1.7 seconds) \-- -Przeprowadzono 35 testów, jeden się nie powiódł, jeden został pominięty. +Uruchomiono 35 testów, jeden zawiódł, jeden został pominięty. -Kontynuujemy w rozdziale [Uruchamianie testów |running-tests]. +Dalej kontynuujemy w rozdziale [Uruchamianie testów|running-tests]. -Tryb oglądania .[#toc-watch-mode] -================================= +Tryb Watch +========== -Refaktoryzacja kodu? Albo nawet rozwijanie się zgodnie z metodologią TDD (Test Driven Development)? Wtedy spodoba ci się tryb zegarka. W tym trybie tester obserwuje kod źródłowy i uruchamia się sam, gdy wprowadzane są zmiany. +Refaktoryzujesz kod? A może nawet rozwijasz według metodyki TDD (Test Driven Development)? W takim razie spodoba Ci się tryb watch. Tester w nim śledzi kody źródłowe i przy zmianie sam się uruchamia. -W ten sposób, gdy się rozwijasz, masz terminal w rogu monitora z zielonym paskiem stanu, który świeci, a kiedy nagle zmienia się na czerwony, wiesz, że właśnie zrobiłeś coś złego. To faktycznie świetna gra, w której programujesz i próbujesz trzymać się koloru. +Podczas rozwoju masz więc w rogu monitora terminal, gdzie świeci na Ciebie zielony pasek stanu, a gdy nagle zmieni się na czerwony, wiesz, że właśnie czegoś nie zrobiłeś do końca dobrze. To właściwie świetna gra, w której programujesz i starasz się utrzymać kolor. -Tryb Watch jest uruchamiany przez parametr [--watch |running-tests#w-watch-path]. +Tryb watch uruchamia się parametrem [--watch |running-tests#-w --watch path]. -Raporty CodeCoverage .[#toc-codecoverage-reports] -================================================= +Raporty CodeCoverage +==================== -Tester może generować raporty z przeglądem tego, ile kodu źródłowego obejmują testy. Raport może być w formacie HTML czytelnym dla człowieka lub Clover XML do dalszego przetwarzania maszynowego. +Tester potrafi generować raporty z przeglądem, ile kodu źródłowego pokrywają testy. Raport może być albo w czytelnym dla człowieka formacie HTML, albo Clover XML do dalszego przetwarzania maszynowego. -Zobacz "przykładowy raport HTML":https://files.nette.org/tester/coverage.html z pokryciem kodu. +Zobacz "przykładowy raport HTML":attachment:coverage.html z pokryciem kodu. -Obsługiwane wersje PHP .[#toc-supported-php-versions] -===================================================== +Obsługiwane wersje PHP +====================== -| wersje | kompatybilne z PHP +| wersja | kompatybilna z PHP |------------------|------------------- -| Tester 2.5 | PHP 8.0 - 8.2 -| Tester 2.4 | PHP 7.2 - 8.2 -| Tester 2.3 | PHP 7.1 - 8.0 -| Tester 2.1 - 2.2 | PHP 7.1 - 7.3 -| Tester 2.0 | PHP 5.6 - 7.3 -| Tester 1.7 | PHP 5.3 - 7.3 + HHVM 3.3+ -| Tester 1.6 | PHP 5.3 - 7.0 + HHVM -| Tester 1.3 - 1.5 | PHP 5.3 - 5.6 + HHVM -| Tester 0.9 - 1.2 | PHP 5.3 - 5.6 - -Ważne dla najnowszych wersji poprawek. - -Do wersji 1.7 Tester wspierał również [HHVM |https://hhvm.com] 3.3.0 lub wyższy (poprzez `tester -p hhvm`). Wsparcie zostało przerwane od wersji Tester 2.0. +| Tester 2.5 | PHP 8.0 – 8.3 +| Tester 2.4 | PHP 7.2 – 8.2 +| Tester 2.3 | PHP 7.1 – 8.0 +| Tester 2.1 – 2.2 | PHP 7.1 – 7.3 +| Tester 2.0 | PHP 5.6 – 7.3 +| Tester 1.7 | PHP 5.3 – 7.3 + HHVM 3.3+ +| Tester 1.6 | PHP 5.3 – 7.0 + HHVM +| Tester 1.3 – 1.5 | PHP 5.3 – 5.6 + HHVM +| Tester 0.9 – 1.2 | PHP 5.3 – 5.6 + +Dotyczy ostatniej wersji poprawki. + +Tester do wersji 1.7 obsługiwał również [HHVM |https://hhvm.com] 3.3.0 lub wyższy (przez `tester -p hhvm`). Wsparcie zostało zakończone od wersji Testera 2.0. diff --git a/tester/pl/helpers.texy b/tester/pl/helpers.texy index 72bd8b7b52..0ba64c1941 100644 --- a/tester/pl/helpers.texy +++ b/tester/pl/helpers.texy @@ -1,31 +1,45 @@ -Zajęcia pomocnicze -****************** +Klasy pomocnicze +**************** -DomQuery .[#toc-domquery] -------------------------- -`Tester\DomQuery` jest klasą rozszerzającą `SimpleXMLElement` o metody ułatwiające testowanie zawartości HTML lub XML. +DomQuery +-------- +`Tester\DomQuery` to klasa rozszerzająca `SimpleXMLElement` o łatwe wyszukiwanie w HTML lub XML za pomocą selektorów CSS. ```php -# mějme v $html HTML dokument, který načteme -$dom = Tester\DomQuery::fromHtml($html); - -# můžeme testovat přítomnost elementů podle CSS selektorů -Assert::true($dom->has('form#registration')); -Assert::true($dom->has('input[name="username"]')); -Assert::true($dom->has('input[type="submit"]')); - -# nebo vybrat elementy jako pole DomQuery -$elems = $dom->find('input[data-autocomplete]'); +# utworzenie DomQuery z ciągu HTML +$dom = Tester\DomQuery::fromHtml(' +
                                                                                                                            +

                                                                                                                            Title

                                                                                                                            +
                                                                                                                            Text
                                                                                                                            +
                                                                                                                            +'); + +# test istnienia elementów za pomocą selektorów CSS +Assert::true($dom->has('article.post')); +Assert::true($dom->has('h1')); + +# znalezienie elementów jako tablica obiektów DomQuery +$headings = $dom->find('h1'); +Assert::same('Title', (string) $headings[0]); + +# test, czy element odpowiada selektorowi (od wersji 2.5.3) +$content = $dom->find('.content')[0]; +Assert::true($content->matches('div')); +Assert::false($content->matches('p')); + +# znalezienie najbliższego przodka odpowiadającego selektorowi (od 2.5.5) +$article = $content->closest('.post'); +Assert::true($article->matches('article')); ``` -FileMock .[#toc-filemock] -------------------------- -`Tester\FileMock` emuluje pliki w pamięci, ułatwiając testowanie kodu, który używa funkcji `fopen()`, `file_get_contents()`, `parse_ini_file()` i podobnych. Przykład zastosowania: +FileMock +-------- +`Tester\FileMock` emuluje w pamięci pliki i ułatwia w ten sposób testowanie kodu, który używa funkcji `fopen()`, `file_get_contents()`, `parse_ini_file()` i podobnych. Przykład użycia: ```php -# Testovaná třída +# Testowana klasa class Logger { public function __construct( @@ -39,21 +53,21 @@ class Logger } } -# Nový prázdný soubor +# Nowy pusty plik $file = Tester\FileMock::create(''); $logger = new Logger($file); $logger->log('Login'); $logger->log('Logout'); -# Testujeme vytvořený obsah +# Testujemy utworzoną zawartość Assert::same("Login\nLogout\n", file_get_contents($file)); ``` Assert::with() .[filter] ------------------------ -To nie jest asercja, ale pomocnik do testowania prywatnych metod i obiektów własności. +Nie jest to asercja, ale pomocnik do testowania prywatnych metod i właściwości obiektów. ```php class Entity @@ -65,17 +79,17 @@ class Entity $ent = new Entity; Assert::with($ent, function () { - Assert::true($this->enabled); // zpřístupněná privátní $ent->enabled + Assert::true($this->enabled); // udostępniona prywatna $ent->enabled }); ``` Helpers::purge() .[filter] -------------------------- -Metoda `purge()` tworzy podany katalog i usuwa całą jego zawartość, jeśli już istnieje. Przydaje się do tworzenia katalogu tymczasowego. Na przykład w `tests/bootstrap.php`: +Metoda `purge()` tworzy podany katalog, a jeśli już istnieje, usuwa całą jego zawartość. Przydaje się do tworzenia tymczasowego katalogu. Na przykład w `tests/bootstrap.php`: ```php -@mkdir(__DIR__ . '/tmp'); # @ - adresář již může existovat +@mkdir(__DIR__ . '/tmp'); # @ - katalog już może istnieć define('TempDir', __DIR__ . '/tmp/' . getmypid()); Tester\Helpers::purge(TempDir); @@ -84,25 +98,25 @@ Tester\Helpers::purge(TempDir); Environment::lock() .[filter] ----------------------------- -Testy są przeprowadzane równolegle. Czasami jednak potrzebujemy, aby testy nie nakładały się na siebie. Zazwyczaj w przypadku testów baz danych konieczne jest, aby jeden test przygotował zawartość bazy danych, a inny test nie dotykał bazy danych podczas jej działania. W tych testach używamy `Tester\Environment::lock($name, $dir)`: +Testy uruchamiane są równolegle. Czasami jednak potrzebujemy, aby przebieg testów się nie nakładał. Typowo w testach bazodanowych konieczne jest, aby test przygotował zawartość bazy danych, a inny test w czasie jego trwania nie ingerował w bazę danych. W tych testach użyjemy `Tester\Environment::lock($name, $dir)`: ```php Tester\Environment::lock('database', __DIR__ . '/tmp'); ``` -Pierwszy parametr to nazwa zamka, drugi to ścieżka do katalogu, w którym ma być przechowywany zamek. Test, który dostanie blokadę jako pierwszy, przejdzie, pozostałe testy muszą czekać na zakończenie blokady. +Pierwszy parametr to nazwa blokady, drugi to ścieżka do katalogu do przechowywania blokady. Test, który uzyska blokadę jako pierwszy, zostanie wykonany, pozostałe testy muszą poczekać na jego zakończenie. Environment::bypassFinals() .[filter] ------------------------------------- -Klasy lub metody oznaczone jako `final` są trudne do przetestowania. Wywołanie `Tester\Environment::bypassFinals()` na początku testu powoduje, że podczas ładowania kodu odpadają słowa kluczowe `final`. +Klasy lub metody oznaczone jako `final` są trudne do testowania. Wywołanie `Tester\Environment::bypassFinals()` na początku testu powoduje, że słowa kluczowe `final` podczas wczytywania kodu są pomijane. ```php require __DIR__ . '/bootstrap.php'; Tester\Environment::bypassFinals(); -class MyClass extends NormallyFinalClass # <-- NormallyFinalClass už není final +class MyClass extends NormallyFinalClass # <-- NormallyFinalClass już nie jest final { // ... } @@ -111,18 +125,18 @@ class MyClass extends NormallyFinalClass # <-- NormallyFinalClass už není fin Environment::setup() .[filter] ------------------------------ -- poprawia czytelność listy błędów (w tym kolorowanie), w przeciwnym razie wyświetlany jest domyślny ślad stosu PHP -- włącza sprawdzanie czy asercje zostały wywołane w teście, w przeciwnym razie test bez asercji (np. zapomniany) również przejdzie -- podczas korzystania z `--coverage`, automatycznie rozpoczyna zbieranie informacji o uruchomionym kodzie (opisane poniżej) -- drukuj status OK lub FAILURE na końcu skryptu +- poprawia czytelność wypisu błędów (włącznie z kolorowaniem), inaczej jest wypisany domyślny PHP stack trace +- włącza kontrolę, czy w teście zostały wywołane asercje, inaczej test bez asercji (na przykład zapomnianych) przejdzie również +- przy użyciu `--coverage` uruchamia automatycznie zbieranie informacji o uruchomionym kodzie (opisane dalej) +- wypisuje stan OK lub FAILURE na końcu skryptu Environment::setupFunctions() .[filter]{data-version:2.5} --------------------------------------------------------- -Tworzy globalne funkcje `test()`, `setUp()`, i `tearDown()`, na które można podzielić swoje testy. +Tworzy globalne funkcje `test()`, `testException()`, `setUp()` i `tearDown()`, w które możesz dzielić testy. ```php -test('popis testu', function () { +test('opis testu', function () { Assert::same(123, foo()); Assert::false(bar()); // ... @@ -132,21 +146,21 @@ test('popis testu', function () { Environment::VariableRunner .[filter] ------------------------------------- -Umożliwia sprawdzenie, czy test został uruchomiony bezpośrednio czy za pomocą Testera. +Umożliwia sprawdzenie, czy test został uruchomiony bezpośrednio, czy za pomocą Testera. ```php if (getenv(Tester\Environment::VariableRunner)) { - # spuštěno Testerem + # uruchomiono Testerem } else { - # spuštěno jinak + # uruchomiono inaczej } ``` Environment::VariableThread .[filter] ------------------------------------- -Tester uruchamia testy równolegle w określonej liczbie wątków. Jeśli interesuje nas numer wątku, możemy go uzyskać ze zmiennej środowiskowej: +Tester uruchamia testy równolegle w podanej liczbie wątków. Jeśli interesuje nas numer wątku, dowiemy się go ze zmiennej środowiskowej: ```php -echo "Běžím ve vlákně číslo " . getenv(Tester\Environment::VariableThread); +echo "Działam w wątku numer " . getenv(Tester\Environment::VariableThread); ``` diff --git a/tester/pl/running-tests.texy b/tester/pl/running-tests.texy index f4f3a9625e..bc8f9fb9b0 100644 --- a/tester/pl/running-tests.texy +++ b/tester/pl/running-tests.texy @@ -1,83 +1,83 @@ -Przeprowadzanie testów -********************** +Uruchamianie testów +******************* .[perex] -Najbardziej widoczną częścią Nette Tester jest uruchamianie testów z linii poleceń. Jest on niezwykle szybki i wytrzymały, ponieważ automatycznie uruchamia wszystkie testy jako osobne procesy równolegle w wielu wątkach. Może też uruchamiać się sam w tzw. trybie watch. +Najbardziej widocznym elementem Nette Tester jest narzędzie do uruchamiania testów z wiersza poleceń. Jest niezwykle szybkie i solidne, ponieważ automatycznie uruchamia wszystkie testy jako oddzielne procesy i to równolegle w wielu wątkach. Potrafi również uruchamiać się samo w tzw. trybie watch. -Test runner Nette Tester jest wywoływany z linii poleceń. Jako parametr przekazujemy katalog z testami. Dla katalogu bieżącego wystarczy wpisać kropkę: +Narzędzie do uruchamiania testów wywołujemy z wiersza poleceń. Jako parametr podajemy katalog z testami. Dla bieżącego katalogu wystarczy podać kropkę: /--pre .[terminal] vendor/bin/tester . \-- -Po wywołaniu, test runner skanuje podany katalog i wszystkie podkatalogi i szuka testów, którymi są pliki `*.phpt` i `*Test.php`. Czyta również i ocenia ich [adnotacje |test-annotations], aby wiedzieć, które z nich i jak uruchomić. +Narzędzie do uruchamiania testów przeszuka podany katalog i wszystkie podkatalogi i wyszuka testy, czyli pliki `*.phpt` i `*Test.php`. Jednocześnie czyta i ocenia ich [adnotacje|test-annotations], aby wiedzieć, które z nich i jak uruchamiać. -Następnie wykonuje testy. Dla każdego wykonanego testu, runner drukuje jeden znak, aby wskazać postęp: +Następnie uruchomi testy. Podczas wykonywania testów wypisuje na bieżąco wyniki na terminal jako znaki: -- . - test przeszedł -- s - test został pominięty -- F - test nie powiódł się +- . – test przeszedł +- s – test został pominięty (skipped) +- F – test nie powiódł się (failed) -Dane wyjściowe mogą wyglądać jak: +Wyjście może wyglądać na przykład tak: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +PHP 8.3.2 (cli) | php -n | 8 threads ........s.......................... OK (35 tests, 1 skipped, 1.7 seconds) \-- -Kiedy uruchomisz go ponownie, najpierw uruchamia on testy, które nie powiodły się podczas poprzedniego uruchomienia, więc od razu będziesz wiedział, czy naprawiłeś błąd. +Przy ponownym uruchomieniu najpierw wykonuje testy, które przy poprzednim uruchomieniu nie powiodły się, więc natychmiast dowiesz się, czy udało Ci się naprawić błąd. -Kod wyjścia testera jest zerowy, jeśli żaden z nich się nie powiódł. W przeciwnym razie niezerowy. +Jeśli żaden test się nie powiedzie, kod powrotu Testera wynosi zero. W przeciwnym razie kod powrotu jest niezerowy. .[warning] -Tester uruchamia procesy PHP bez `php.ini`. Więcej szczegółów w sekcji [Własny php.ini |#Own php.ini]. +Tester uruchamia procesy PHP bez `php.ini`. Szczegółowiej w części [#Własny php.ini]. -Opcje wiersza poleceń .[#toc-command-line-options] -================================================== +Parametry wiersza poleceń +========================= -Przegląd opcji wiersza poleceń uzyskujemy uruchamiając Tester bez parametrów lub z opcją `-h`: +Przegląd wszystkich opcji wiersza poleceń uzyskamy, uruchamiając Testera bez parametrów lub z parametrem `-h`: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Usage: tester [options] [ | ]... Options: - -p Specify PHP interpreter to run (default: php). - -c Look for php.ini file (or look in directory) . - -C Use system-wide php.ini. - -l | --log Write log to file . - -d ... Define INI entry 'key' with value 'value'. - -s Show information about skipped tests. - --stop-on-fail Stop execution upon the first failure. - -j Run jobs in parallel (default: 8). - -o Specify output format. - -w | --watch Watch directory. - -i | --info Show tests environment info and exit. - --setup Script for runner setup. - --temp Path to temporary directory. Default: sys_get_temp_dir(). - --colors [1|0] Enable or disable colors. - --coverage Generate code coverage report to file. - --coverage-src Path to source code. - -h | --help This help. + -p Określ interpreter PHP do uruchomienia (domyślnie: php). + -c Szukaj pliku php.ini (lub szukaj w katalogu) . + -C Użyj systemowego php.ini. + -d ... Zdefiniuj wpis INI 'key' z wartością 'value'. + -s Pokaż informacje o pominiętych testach. + --stop-on-fail Zatrzymaj wykonywanie przy pierwszym niepowodzeniu. + -j Uruchom zadań równolegle (domyślnie: 8). + -o (np. -o junit:output.xml) + Określ jeden lub więcej formatów wyjściowych z opcjonalną nazwą pliku. + -w | --watch Obserwuj katalog. + -i | --info Pokaż informacje o środowisku testów i zakończ. + --setup Skrypt do konfiguracji runnera. + --temp Ścieżka do katalogu tymczasowego. Domyślnie przez sys_get_temp_dir(). + --colors [1|0] Włącz lub wyłącz kolory. + --coverage Generuj raport pokrycia kodu do pliku. + --coverage-src Ścieżka do kodu źródłowego. + -h | --help Ta pomoc. \-- -p .[filter] ------------------- -Określa binarkę PHP, która będzie używana do uruchamiania testów. Domyślnie jest to `php`. +Określa binarkę PHP, która będzie używana do uruchamiania testów. Standardowo jest to `php`. /--pre .[terminal] tester -p /home/user/php-7.2.0-beta/php-cgi tests @@ -86,21 +86,12 @@ tester -p /home/user/php-7.2.0-beta/php-cgi tests -c .[filter] ------------------- -Określa, która strona `php.ini` będzie używana podczas uruchamiania testów. Domyślnie, nie jest używany żaden php.ini. Zobacz [Własne php.ini |#Own php.ini], aby uzyskać więcej informacji. +Określa, który `php.ini` będzie używany podczas uruchamiania testów. Domyślnie żaden php.ini nie jest używany. Więcej w części [#Własny php.ini]. -C .[filter] ------------ -Używany jest ogólnosystemowy `php.ini`. Czyli na platformie UNIX wszystkie pliki `/etc/php/{sapi}/conf.d/*.ini` również. Patrz rozdział [Własny php.ini |#Own php.ini]. - - -''-l | --log '' .[filter] -------------------------------- -Postęp testów jest zapisywany do pliku. Wszystkie nieudane, pominięte, a także udane testy: - -/--pre .[terminal] -tester --log /var/log/tests.log tests -\-- +Użyty zostanie systemowy `php.ini`. Na UNIXie również wszystkie odpowiednie pliki INI `/etc/php/{sapi}/conf.d/*.ini`. Więcej w części [#Własny php.ini]. -d .[filter] @@ -114,34 +105,36 @@ tester -d max_execution_time=20 -s --- -Zostanie wyświetlona informacja o pominiętych testach. +Wyświetlone zostaną informacje o pominiętych testach. --stop-on-fail .[filter] ------------------------ -Tester zatrzymuje testowanie po pierwszym nieudanym teście. +Tester zatrzyma testowanie przy pierwszym niepowodzeniu testu. -j .[filter] ------------------ -Testy są uruchamiane w `` równoległych procesach. Domyślna wartość to 8. Jeśli chcemy uruchamiać testy szeregowo, używamy wartości 1. +Określa, ile równoległych procesów z testami zostanie uruchomionych. Domyślna wartość to 8. Jeśli chcemy, aby wszystkie testy przebiegły seryjnie, użyjemy wartości 1. --o .[filter] -------------------------------------- -Format wyjścia. Domyślnie jest to format konsoli. +-o .[filter] +------------------------------------------------------- +Ustawia format wyjścia. Domyślny jest format dla konsoli. Możesz podać nazwę pliku, do którego zostanie zapisane wyjście (np. `-o junit:output.xml`). Opcję `-o` można powtórzyć wielokrotnie, aby wygenerować więcej formatów naraz. -- `console`: taki sam jak domyślny, ale w tym przypadku nie jest wypisywane logo ASCII +- `console`: zgodne z domyślnym formatem, ale w tym przypadku nie wyświetli się logo ASCII +- `console-lines`: podobne do console, ale wynik każdego testu jest podany w osobnej linii z dodatkowymi informacjami - `tap`: [format TAP |https://en.wikipedia.org/wiki/Test_Anything_Protocol] odpowiedni do przetwarzania maszynowego -- `junit`: format JUnit XML, odpowiedni również do przetwarzania maszynowego -- `none`: nic nie jest drukowane +- `junit`: format XML JUnit, również odpowiedni do przetwarzania maszynowego +- `log`: Wyjścia przebiegu testowania. Wszystkie nieudane, pominięte, a także udane testy +- `none`: nic się nie wypisuje ''-w | --watch '' .[filter] --------------------------------- -Tester nie kończy pracy po zakończeniu testów, ale cały czas uruchamia i obserwuje pliki PHP w danym katalogu. W przypadku zmiany, uruchamia testy ponownie. Parametr może być użyty wielokrotnie, jeśli chcemy monitorować wiele katalogów. +Po zakończeniu testowania Tester nie kończy pracy, ale pozostaje uruchomiony i śledzi pliki PHP w podanym katalogu. Przy zmianie uruchomi testy ponownie. Parametr może być użyty wielokrotnie, jeśli chcemy śledzić więcej katalogów. -Jest to przydatne podczas refaktoryzacji biblioteki lub debugowania testów. +Przydaje się przy refaktoryzacji biblioteki lub debugowaniu testów. /--pre .[terminal] tester --watch src tests @@ -150,7 +143,7 @@ tester --watch src tests ''-i | --info'' .[filter] ------------------------- -Pokazuje informacje o środowisku uruchomionego testu. Na przykład: +Wyświetla informacje o środowisku uruchomieniowym dla testów. Na przykład: /--pre .[terminal] tester -p /usr/bin/php7.1 -c tests/php.ini --info @@ -177,13 +170,13 @@ Core, ctype, date, dom, ereg, fileinfo, filter, hash, ... --setup .[filter] ------------------------ -Tester przy starcie ładuje podany skrypt PHP. Dostępna jest w nim zmienna `Tester\Runner\Runner $runner`. Załóżmy, że plik `tests/runner-setup.php`: +Tester przy starcie wczytuje podany skrypt PHP. W nim dostępna jest zmienna `Tester\Runner\Runner $runner`. Załóżmy plik `tests/runner-setup.php` z zawartością: ```php $runner->outputHandlers[] = new MyOutputHandler; ``` -i uruchamiamy Tester: +Testera uruchomimy: /--pre .[terminal] tester --setup tests/runner-setup.php tests @@ -192,47 +185,47 @@ tester --setup tests/runner-setup.php tests --temp .[filter] ----------------------- -Ustawia ścieżkę do katalogu dla plików tymczasowych Testera. Domyślna wartość jest zwracana przez `sys_get_temp_dir()`. Gdy wartość domyślna nie jest prawidłowa, zostaniesz o tym poinformowany. +Ustawia ścieżkę do katalogu dla tymczasowych plików Testera. Domyślną wartość zwraca `sys_get_temp_dir()`. Jeśli domyślna wartość nie jest poprawna, zostaniesz powiadomiony. -Jeśli nie jesteśmy pewni, który katalog jest używany, możemy uruchomić Tester z parametrem `--info`. +Jeśli nie jesteśmy pewni, jaki katalog jest używany, uruchomimy Testera z parametrem `--info`. --colors 1|0 .[filter] ---------------------- -Tester domyślnie wykrywa terminal obsługujący kolory i koloruje swoje wyjście. Ta opcja jest nad autodetekcją. Możemy ustawić kolorowanie globalnie przez systemową zmienną środowiskową `NETTE_TESTER_COLORS`. +Domyślnie Tester wykrywa kolorowy terminal i koloruje swoje wyjście. Ta opcja nadpisuje autodetekcję. Globalnie możemy kolorowanie ustawić zmienną środowiskową systemu `NETTE_TESTER_COLORS`. --coverage .[filter] --------------------------- -Tester wygeneruje raport z przeglądem tego, jak bardzo kod źródłowy jest pokryty przez testy. Opcja ta wymaga włączonego rozszerzenia PHP [Xdebug |https://xdebug.org/] lub [PCOV |https://github.com/krakjoe/pcov], albo PHP 7 z PHPDBG SAPI, które jest szybsze. Rozszerzenie pliku docelowego określa format zawartości. HTML lub Clover XML. +Tester wygeneruje raport z przeglądem, ile kodu źródłowego pokrywają testy. Ta opcja wymaga zainstalowanego rozszerzenia PHP [Xdebug |https://xdebug.org/], lub [PCOV |https://github.com/krakjoe/pcov], albo PHP 7 z PHPDBG SAPI, które jest szybsze. Rozszerzenie pliku docelowego określa jego format. Albo HTML, albo Clover XML. /--pre .[terminal] -tester tests --coverage coverage.html # HTML report -tester tests --coverage coverage.xml # Clover XML report +tester tests --coverage coverage.html # raport HTML +tester tests --coverage coverage.xml # raport Clover XML \-- -Priorytet wyboru mechanizmu zbierania jest następujący: +Priorytet wyboru mechanizmu jest następujący: 1) PCOV 2) PHPDBG 3) Xdebug -Rozległe testy mogą się nie powieść podczas uruchamiania przez PHPDBG z powodu wyczerpania pamięci. Zbieranie danych o pokryciu jest operacją zużywającą pamięć. W takim przypadku, wywołanie `Tester\CodeCoverage\Collector::flush()` wewnątrz testu może pomóc. Spłucze ona zebrane dane do pliku i zwolni pamięć. Jeśli zbieranie danych nie jest w toku lub używany jest Xdebug, wywołanie nie ma żadnego efektu. +Przy użyciu PHPDBG możemy przy obszernych testach napotkać na niepowodzenie testu z powodu wyczerpania pamięci. Zbieranie informacji o pokrytym kodzie jest pamięciochłonne. W tym przypadku pomoże nam wywołanie `Tester\CodeCoverage\Collector::flush()` wewnątrz testu. Zapisze zebrane dane na dysk i zwolni pamięć. Jeśli zbieranie danych nie przebiega lub jest używany Xdebug, wywołanie nie ma żadnego efektu. -"Przykład raportu HTML":https://files.nette.org/tester/coverage.html z pokryciem kodu. +"Przykładowy raport HTML":attachment:coverage.html z pokryciem kodu. --coverage-src .[filter] ------------------------------- -Używamy go jednocześnie z opcją `--coverage`. Opcja `` jest ścieżką do kodu źródłowego, dla którego generujemy raport. Może być używana wielokrotnie. +Użyjemy jednocześnie z opcją `--coverage`. `` to ścieżka do kodów źródłowych, dla których raport jest generowany. Może być użyty wielokrotnie. -Własny php.ini .[#toc-own-php-ini] -================================== -Tester uruchamia procesy PHP z opcją `-n`, co oznacza, że nie jest ładowany żaden `php.ini` (nawet ten z `/etc/php/conf.d/*.ini` w UNIX). Zapewnia to to samo środowisko dla uruchamianych testów, ale też dezaktywuje wszystkie zewnętrzne rozszerzenia PHP powszechnie ładowane przez systemowe PHP. +Własny php.ini +============== +Tester uruchamia procesy PHP z parametrem `-n`, co oznacza, że żaden `php.ini` nie jest wczytywany. W UNIXie również te z `/etc/php/conf.d/*.ini`. To zapewnia jednakowe środowisko dla przebiegu testów, ale również wyłącza wszystkie rozszerzenia PHP normalnie wczytywane przez systemowe PHP. -Jeśli chcesz zachować konfigurację systemową, użyj parametru `-C`. +Jeśli chcesz zachować wczytywanie systemowych plików php.ini, użyj parametru `-C`. -Jeśli potrzebujesz jakichś rozszerzeń lub specjalnych ustawień INI, zalecamy stworzenie własnego pliku `php.ini` i rozesłanie go pomiędzy testami. Następnie uruchamiamy Tester z opcją `-c`, np. `tester -c tests/php.ini`. Plik INI może wyglądać tak: +Jeśli potrzebujesz jakichś rozszerzeń lub specjalnych ustawień INI dla testów, zalecamy utworzenie własnego pliku `php.ini`, który będzie dystrybuowany z testami. Testera następnie uruchamiamy z parametrem `-c`, na przykład `tester -c tests/php.ini tests`, gdzie plik INI może wyglądać tak: ```ini [PHP] @@ -243,4 +236,4 @@ extension=php_pdo_pgsql.dll memory_limit=512M ``` -Uruchomienie Testera z systemem `php.ini` w systemie UNIX, np. `tester -c /etc/php/cgi/php.ini`, nie wczytuje innych INI z `/etc/php/conf.d/*.ini`. To jest zachowanie PHP, a nie Testera. +Uruchomienie Testera w UNIXie z systemowym `php.ini`, na przykład `tester -c /etc/php/cli/php.ini` nie wczyta pozostałych INI z `/etc/php/conf.d/*.ini`. To jest właściwość PHP, nie Testera. diff --git a/tester/pl/test-annotations.texy b/tester/pl/test-annotations.texy index f514e65da2..1910604ec9 100644 --- a/tester/pl/test-annotations.texy +++ b/tester/pl/test-annotations.texy @@ -1,16 +1,16 @@ -Adnotacja o testach -******************* +Adnotacje testów +**************** .[perex] -Adnotacje określają, jak [biegacz |running-tests] testowy będzie obsługiwał testy [z linii poleceń |running-tests]. Są one zapisywane na początku pliku testowego. +Adnotacje określają, jak z testami będzie postępować [narzędzie do uruchamiania testów z wiersza poleceń|running-tests]. Zapisuje się je na początku pliku z testem. -Adnotacje nie uwzględniają wielkości liter. Nie mają one również wpływu, jeśli test jest uruchamiany ręcznie jako zwykły skrypt PHP. +W adnotacjach nie bierze się pod uwagę wielkości liter. Również nie mają żadnego wpływu, jeśli test jest uruchamiany ręcznie jako zwykły skrypt PHP. Przykład: ```php /** - * TEST: Basic database query test. + * TEST: Podstawowy test zapytania do bazy danych. * * @dataProvider files/databases.ini * @exitCode 56 @@ -23,17 +23,17 @@ require __DIR__ . '/../bootstrap.php'; TEST .[filter] -------------- -To nie jest właściwie adnotacja, po prostu określa tytuł testu, który jest drukowany na niepowodzeniach lub do dziennika. +To właściwie nawet nie jest adnotacja, jedynie określa tytuł testu, który jest wypisywany przy niepowodzeniu lub do logu. @skip .[filter] --------------- -Test jest pomijany. Dobre do tymczasowego odrzucania testów. +Test zostanie pominięty. Przydaje się do tymczasowego wyłączenia testów. @phpVersion .[filter] --------------------- -Test zostanie pominięty, jeśli nie zostanie uruchomiony z odpowiednią wersją PHP. Adnotację zapisujemy jako. `@phpVersion [operator] verze`. Operator może być pominięty, domyślnie jest to `>=`. Przykłady: +Test zostanie pominięty, jeśli nie jest uruchomiony z odpowiednią wersją PHP. Adnotację zapisujemy jako `@phpVersion [operator] wersja`. Operator możemy pominąć, domyślny to `>=`. Przykłady: ```php /** @@ -46,7 +46,7 @@ Test zostanie pominięty, jeśli nie zostanie uruchomiony z odpowiednią wersją @phpExtension .[filter] ----------------------- -Test zostanie pominięty, jeśli nie zostaną załadowane wszystkie wymienione rozszerzenia PHP. Wiele rozszerzeń może być wymienionych w jednej adnotacji lub używanych wielokrotnie. +Test zostanie pominięty, jeśli nie są wczytane wszystkie podane rozszerzenia PHP. Więcej rozszerzeń możemy podać w jednej adnotacji lub użyć jej wielokrotnie. ```php /** @@ -58,9 +58,9 @@ Test zostanie pominięty, jeśli nie zostaną załadowane wszystkie wymienione r @dataProvider .[filter] ----------------------- -Jeśli chcesz uruchomić plik testowy wiele razy, ale z różnymi danymi wejściowymi, ta adnotacja jest przydatna. (Nie należy mylić z tą samą adnotacją dla [TestCase |TestCase#dataProvider]). +Jeśli chcemy plik testowy uruchomić wielokrotnie, ale z innymi danymi wejściowymi, przyda się właśnie ta adnotacja. (Nie mylić z adnotacją o tej samej nazwie dla [TestCase |TestCase#dataProvider].) -Zapisujemy go jako `@dataProvider file.ini`, ścieżka do pliku jest przyjmowana względem pliku testowego. Test zostanie uruchomiony tyle razy, ile jest sekcji w pliku INI. Załóżmy, że plik INI to `databases.ini`: +Zapisujemy jako `@dataProvider file.ini`, ścieżka do pliku jest brana relatywnie do pliku z testem. Test zostanie uruchomiony tyle razy, ile jest sekcji w pliku INI. Załóżmy plik INI `databases.ini`: ```ini [mysql] @@ -77,7 +77,7 @@ password = ****** dsn = "sqlite::memory:" ``` -i w tym samym katalogu test `database.phpt`: +a w tym samym katalogu test `database.phpt` : ```php /** @@ -87,11 +87,11 @@ i w tym samym katalogu test `database.phpt`: $args = Tester\Environment::loadData(); ``` -Test zostanie uruchomiony trzy razy, a `$args` zawsze będzie zawierał wartości z `mysql`, `postgresql` lub `sqlite`. +Test zostanie uruchomiony trzykrotnie, a `$args` będzie zawierać zawsze wartości z sekcji `mysql`, `postgresql` lub `sqlite`. -Istnieje również wariant, w którym zapisujemy adnotację ze znakiem zapytania jako `@dataProvider? file.ini`. W tym przypadku test zostanie pominięty, jeśli plik INI nie istnieje. +Istnieje jeszcze wariant, gdy adnotację zapiszemy ze znakiem zapytania jako `@dataProvider? file.ini`. W tym przypadku test zostanie pominięty, jeśli plik INI nie istnieje. -To nie koniec opcji związanych z adnotacjami. Po nazwie pliku INI możemy określić warunki, w jakich test będzie uruchamiany dla danej sekcji. Rozwiń plik INI: +Tym możliwości adnotacji nie kończą się. Za nazwę pliku INI możemy specyfikować warunki, przy których test dla danej sekcji zostanie uruchomiony. Rozszerzymy plik INI: ```ini [mysql] @@ -113,7 +113,7 @@ password = ****** dsn = "sqlite::memory:" ``` -i użyć adnotacji o stanie: +i użyjemy adnotacji z warunkiem: ```php /** @@ -121,9 +121,9 @@ i użyć adnotacji o stanie: */ ``` -Test zostanie przeprowadzony tylko raz dla odcinka `postgresql 9.1`. Pozostałe odcinki nie przejdą przez filtr warunków. +Test zostanie uruchomiony tylko raz i to dla sekcji `postgresql 9.1`. Pozostałe sekcje filtrem warunku nie przejdą. -Podobnie możemy odwołać się do skryptu PHP zamiast do pliku INI. Musi to zwrócić tablicę lub Traversable. Plik `databases.php`: +Podobnie możemy zamiast pliku INI odwołać się do skryptu PHP. Musi on zwrócić tablicę lub Traversable. Plik `databases.php`: ```php return [ @@ -142,29 +142,29 @@ return [ @multiple .[filter] ------------------- -Zapisujemy ją jako `@multiple N`, gdzie `N` jest liczbą całkowitą. Test zostanie przeprowadzony dokładnie N razy. +Zapisujemy jako `@multiple N`, gdzie `N` jest liczbą całkowitą. Test zostanie uruchomiony dokładnie N razy. @testCase .[filter] ------------------- -Adnotacja nie posiada żadnych parametrów. Używamy go, gdy piszemy testy jako klasy [TestCase |TestCase]. W takim przypadku biegacz testowy linii poleceń uruchomi każdą metodę w oddzielnych procesach i równolegle w wielu wątkach. Może to znacznie przyspieszyć cały proces testowania. +Adnotacja nie ma parametrów. Użyjemy jej, jeśli testy piszemy jako klasy [TestCase | TestCase]. W tym przypadku narzędzie do uruchamiania testów z wiersza poleceń będzie uruchamiać poszczególne metody w oddzielnych procesach i równolegle w wielu wątkach. Może to znacznie przyspieszyć cały proces testowania. @exitCode .[filter] ------------------- -Zapisujemy go jako `@exitCode N`, gdzie `N` je návratový kód spuštěného testu. Je-li v testu například voláno `exit(10)`, adnotację zapisujemy jako `@exitCode 10` i jeśli test zakończy się innym kodem, jest to traktowane jako niepowodzenie. Jeśli nie podano adnotacji, kod zwrotny jest walidowany jako 0 (zero). +Zapisujemy jako `@exitCode N`, gdzie `N` jest kodem powrotu uruchomionego testu. Jeśli w teście jest na przykład wywołane `exit(10)`, adnotację zapiszemy jako `@exitCode 10`, a jeśli test zakończy się z innym kodem, jest to uważane za niepowodzenie. Jeśli adnotacji nie podamy, jest weryfikowany kod powrotu 0 (zero). @httpCode .[filter] ------------------- -Adnotacja jest stosowana tylko wtedy, gdy PHP jest binarnym CGI. W przeciwnym razie jest ignorowany. Zapisujemy go jako `@httpCode NNN` gdzie `NNN` to oczekiwany kod HTTP. Jeśli nie podano adnotacji, zatwierdzany jest kod HTTP 200. Jeśli `NNN` jest zapisany jako ciąg o wartości zero, na przykład `any`, kod HTTP nie jest weryfikowany. +Adnotacja ma zastosowanie tylko jeśli binarka PHP jest CGI. W przeciwnym razie jest ignorowana. Zapisujemy jako `@httpCode NNN`, gdzie `NNN` jest oczekiwanym kodem HTTP. Jeśli adnotacji nie podamy, weryfikuje się kod HTTP 200. Jeśli `NNN` zapiszemy jako ciąg znaków ewaluowany do zera, na przykład `any`, kod HTTP nie jest weryfikowany. @outputMatch i @outputMatchFile .[filter] ----------------------------------------- -Funkcja adnotacji jest identyczna jak w przypadku asercji `Assert::match()` i `Assert::matchFile()`. Jednak wzorzec jest szukany w tekście, który test wysłał na swoje standardowe wyjście. Jest to przydatne, jeśli spodziewamy się, że test zakończy się fatalnym błędem i musimy zatwierdzić jego wyjście. +Funkcja adnotacji jest zgodna z asercjami `Assert::match()` i `Assert::matchFile()`. Wzór (pattern) jest jednak szukany w tekście, który test wysłał na swoje standardowe wyjście. Zastosowanie znajdzie, jeśli zakładamy, że test zakończy się błędem fatalnym i potrzebujemy zweryfikować jego wyjście. @phpIni .[filter] ----------------- -Ustawia wartości INI konfiguracji dla testu. Na przykład piszemy go jako `@phpIni precision=20` i działa tak samo, jakbyśmy wprowadzili wartość z linii poleceń poprzez parametr `-d precision=20`. +Dla testu ustawia wartości konfiguracyjne INI. Zapisujemy na przykład jako `@phpIni precision=20` i działa tak samo, jakbyśmy podali wartość z wiersza poleceń przez parametr `-d precision=20`. diff --git a/tester/pl/testcase.texy b/tester/pl/testcase.texy index 8308daa621..a26643ef36 100644 --- a/tester/pl/testcase.texy +++ b/tester/pl/testcase.texy @@ -2,9 +2,9 @@ TestCase ******** .[perex] -W prostych testach asercje mogą następować jedna po drugiej. Ale czasami wygodniej jest zawinąć asercje w klasie testowej, aby je ustrukturyzować. +W prostych testach asercje mogą następować jedna po drugiej. Czasami jednak wygodniej jest asercje opakować w klasę testową i w ten sposób je ustrukturyzować. -Klasa musi być potomkiem `Tester\TestCase`, a my nazywamy ją w uproszczeniu **testcase**. Klasa musi zawierać metody testowe zaczynające się od `test`. Metody te będą uruchamiane jako testy: +Klasa musi być potomkiem `Tester\TestCase` i w uproszczeniu nazywamy ją **testcase**. Klasa musi zawierać metody testowe zaczynające się na `test`. Te metody będą uruchamiane jako testy: ```php use Tester\Assert; @@ -22,11 +22,11 @@ class RectangleTest extends Tester\TestCase } } -# Spuštění testovacích metod +# Uruchomienie metod testowych (new RectangleTest)->run(); ``` -Tak napisany test można dodatkowo wzbogacić o metody `setUp()` i `tearDown()`. Są one wywoływane odpowiednio przed i po każdej metodzie testowej: +Tak napisany test można dalej wzbogacić o metody `setUp()` i `tearDown()`. Są wywoływane przed, resp. za każdą metodą testową: ```php use Tester\Assert; @@ -35,12 +35,12 @@ class NextTest extends Tester\TestCase { public function setUp() { - # Příprava + # Przygotowanie } public function tearDown() { - # Úklid + # Sprzątanie } public function testOne() @@ -54,14 +54,14 @@ class NextTest extends Tester\TestCase } } -# Spuštění testovacích metod +# Uruchomienie metod testowych (new NextTest)->run(); /* -Pořadí volání metod .[#toc-method-calls-order] ----------------------------------------------- +Kolejność wywoływania metod +--------------------------- setUp() testOne() tearDown() @@ -72,9 +72,9 @@ tearDown() */ ``` -Jeśli w fazie `setUp()` lub `tearDown()` wystąpi błąd, test zakończy się ogólnym niepowodzeniem. Jeśli błąd wystąpi w metodzie testowej, metoda `tearDown()` nadal działa, ale z tłumieniem błędów w tej metodzie. +Jeśli dojdzie do błędu w fazie `setUp()` lub `tearDown()`, test ogólnie zawiedzie. Jeśli dojdzie do błędu w metodzie testowej, mimo to metoda `tearDown()` zostanie uruchomiona, jednak z pominięciem błędów w niej. -Zaleca się napisanie adnotacji [@testCase |test-annotations#testCase] na początku testu, wtedy runner testowy linii poleceń uruchomi każdą metodę testcase w oddzielnych procesach i równolegle w wielu wątkach. Może to znacznie przyspieszyć cały proces testowania. +Zalecamy na początek testu napisać adnotację [@testCase |test-annotations#testCase], wtedy narzędzie do uruchamiania testów z wiersza poleceń będzie uruchamiać poszczególne metody testcase w oddzielnych procesach i równolegle w wielu wątkach. Może to znacznie przyspieszyć cały proces testowania. /--php getArea()); # walidacja oczekiwanych wyników +Assert::same(200.0, $rect->getArea()); # zweryfikujemy oczekiwane wyniki Assert::false($rect->isSquare()); ``` -Jak widać, [metody asercji |assertions] takie jak `Assert::same()` są używane do potwierdzenia, że rzeczywista wartość pasuje do wartości oczekiwanej. +Jak widzisz, tzw. [metody asercji|assertions] jak `Assert::same()` służą do potwierdzenia, że rzeczywista wartość odpowiada oczekiwanej wartości. -Pozostaje ostatni krok, którym jest plik `bootstrap.php`. Zawiera on kod wspólny dla wszystkich testów, taki jak autoloading klas, konfiguracja środowiska, tworzenie katalogu tymczasowego, funkcje pomocnicze i tak dalej. Wszystkie testy ładują bootstrap i kontynuują tylko testowanie. Bootstrap może wyglądać jak poniżej: +Pozostaje ostatni krok, a tym jest plik `bootstrap.php`. Zawiera on kod wspólny dla wszystkich testów, na przykład autoloading klas, konfigurację środowiska, tworzenie tymczasowego katalogu, funkcje pomocnicze i podobne. Wszystkie testy wczytują bootstrap i dalej zajmują się tylko testowaniem. Bootstrap może wyglądać następująco: ```php .{file:tests/bootstrap.php} OK \-- -Gdybyśmy zmienili stwierdzenie w teście na false `Assert::same(123, $rect->getArea());` to co by się stało: +Jeśli zmienilibyśmy w teście twierdzenie na nieprawdziwe `Assert::same(123, $rect->getArea());` stanie się to: /--pre .[terminal] $ php RectangleTest.php @@ -107,35 +106,35 @@ $ php RectangleTest.php \-- -Podczas pisania testów dobrze jest objąć wszystkie skrajności. Na przykład, jeśli dane wejściowe są zerowe, liczba ujemna, w innych przypadkach pusty ciąg, null, itp. W rzeczywistości zmusza cię do myślenia i decydowania, jak kod powinien zachowywać się w takich sytuacjach. Testy następnie naprawiają zachowanie. +Przy pisaniu testów dobrze jest uchwycić wszystkie skrajne sytuacje. Na przykład gdy wejściem będzie zero, liczba ujemna, w innych przypadkach na przykład pusty ciąg znaków, null itp. Właściwie zmusza to do zastanowienia się i zdecydowania, jak w takich sytuacjach ma zachowywać się kod. Testy następnie utrwalają zachowanie. -W naszym przypadku wartość ujemna ma rzucić wyjątek, co sprawdzamy za pomocą [Assert::exception() |Assertions#Assert-exception]: +W naszym przypadku wartość ujemna ma rzucić wyjątek, co zweryfikujemy za pomocą [Assert::exception() |Assertions#Assert::exception]: ```php .{file:tests/RectangleTest.php} // szerokość nie może być ujemna Assert::exception( fn() => new Rectangle(-1, 20), InvalidArgumentException::class, - 'Wymiar nie może być ujemny', + 'The dimension must not be negative.', ); ``` -I dodać podobny test dla wzrostu. Na koniec testujemy, że `isSquare()` zwraca `true`, jeśli oba wymiary są takie same. Spróbuj napisać takie testy jako ćwiczenie. +I podobny test dodamy dla wysokości. Na koniec przetestujemy, że `isSquare()` zwróci `true`, jeśli oba wymiary są takie same. Spróbuj jako ćwiczenie napisać takie testy. -Bardziej przejrzyste testy .[#toc-well-arranged-tests] -====================================================== +Bardziej przejrzyste testy +========================== -Rozmiar pliku testowego może rosnąć i szybko stać się niezorganizowany. Dlatego praktycznym rozwiązaniem jest pogrupowanie różnych obszarów testowych w osobne funkcje. +Rozmiar pliku z testem może rosnąć i szybko stać się nieprzejrzystym. Dlatego praktyczne jest grupowanie poszczególnych testowanych obszarów w oddzielne funkcje. -Najpierw pokażemy prostszy, ale elegancki wariant, wykorzystujący funkcję globalną `test()`. Tester nie tworzy go automatycznie, aby uniknąć kolizji, jeśli miałeś funkcję o tej samej nazwie w swoim kodzie. Tworzy go dopiero metoda `setupFunctions()`, którą wywołujesz w pliku `bootstrap.php`: +Najpierw pokażemy prostszą, ale elegancką wariantę, a mianowicie za pomocą globalnej funkcji `test()`. Tester nie tworzy jej automatycznie, aby nie doszło do kolizji, gdybyś miał w kodzie funkcję o tej samej nazwie. Utworzy ją dopiero metoda `setupFunctions()`, którą wywołaj w pliku `bootstrap.php`: ```php .{file:tests/bootstrap.php} Tester\Environment::setup(); Tester\Environment::setupFunctions(); ``` -Dzięki tej funkcji możemy ładnie podzielić plik testowy na nazwane jednostki. Po uruchomieniu etykiety zostaną wymienione jedna po drugiej. +Za pomocą tej funkcji możemy plik testowy ładnie podzielić na nazwane całości. Przy uruchomieniu będą kolejno wypisywane opisy. ```php .{file:tests/RectangleTest.php} getArea()); Assert::false($rect->isSquare()); }); -test('obecný čtverec', function () { +test('ogólny kwadrat', function () { $rect = new Rectangle(5, 5); Assert::same(25.0, $rect->getArea()); Assert::true($rect->isSquare()); }); -test('rozměry nesmí být záporné', function () { +test('wymiary nie mogą być ujemne', function () { Assert::exception( fn() => new Rectangle(-1, 20), InvalidArgumentException::class, @@ -168,15 +167,15 @@ test('rozměry nesmí být záporné', function () { }); ``` -Jeśli potrzebujesz uruchomić kod przed lub po każdym teście, przekaż go do funkcji `setUp()` lub `tearDown()`: +Jeśli potrzebujesz przed lub po każdym teście uruchomić kod, przekaż go funkcji `setUp()` resp. `tearDown()`: ```php setUp(function () { - // kod inicjalizacyjny, który uruchamia się przed każdym testem() + // kod inicjalizacyjny, który uruchomi się przed każdym test() }); ``` -Drugą opcją jest orientacja obiektowa. Tworzymy coś, co nazywa się TestCase, czyli klasę, w której jednostki reprezentują metody, których nazwy zaczynają się od test-. +Druga warianta jest obiektowa. Stworzymy sobie tzw. TestCase, czyli klasę, gdzie poszczególne całości reprezentują metody, których nazwy zaczynają się na test–. ```php .{file:tests/RectangleTest.php} class RectangleTest extends Tester\TestCase @@ -208,25 +207,25 @@ class RectangleTest extends Tester\TestCase } } -// Spuścizna testowa metody +// Uruchomienie metod testowych (new RectangleTest)->run(); ``` -Tym razem do testowania wyjątków wykorzystaliśmy adnotację `@throw`. Zobacz rozdział [TestCase |TestCase], aby dowiedzieć się więcej. +Do testowania wyjątków tym razem użyliśmy adnotacji `@throw`. Więcej dowiesz się w rozdziale [TestCase | TestCase]. -Funkcje pomocnicze .[#toc-annotation-and-skipping-tests] -======================================================== +Funkcje pomocnicze +================== -Nette Tester zawiera kilka klas i funkcji, które mogą ułatwić Ci np. testowanie zawartości dokumentu HTML, testowanie funkcji pracujących z plikami itd. +Nette Tester zawiera kilka klas i funkcji, które mogą ułatwić na przykład testowanie zawartości dokumentu HTML, testowanie funkcji pracujących z plikami i tak dalej. -Ich opis można znaleźć na stronie [Klasy pomocnicze |helpers]. +Ich opis znajdziesz na stronie [Klasy pomocnicze|helpers]. -Dodawanie adnotacji i pomijanie testów .[#toc-anotace-a-preskakovani-testu] -=========================================================================== +Adnotacje i pomijanie testów +============================ -Na uruchomienie testów mogą mieć wpływ adnotacje w postaci komentarzy phpDoc na początku pliku. Na przykład może to wyglądać tak: +Uruchamianie testów może być wpływane przez adnotacje w postaci komentarza phpDoc na początku pliku. Może wyglądać na przykład tak: ```php .{file:tests/RectangleTest.php} /** @@ -235,11 +234,11 @@ Na uruchomienie testów mogą mieć wpływ adnotacje w postaci komentarzy phpDoc */ ``` -Te adnotacje mówią, że test powinien być uruchomiony tylko z PHP w wersji 7.2 lub wyższej i jeśli rozszerzenia PHP pdo i pdo_pgsql są obecne. Za tymi adnotacjami podąża [runner testów z linii poleceń |running-tests], który pominie test, jeśli warunki nie są spełnione i oznaczy wyjście za pomocą `s` - skipped. Jednak podczas ręcznego uruchamiania testu nie mają one żadnego efektu. +Podane adnotacje mówią, że test ma być uruchomiony tylko z wersją PHP 7.2 lub wyższą i jeśli obecne są rozszerzenia PHP pdo i pdo_pgsql. Tymi adnotacjami kieruje się [narzędzie do uruchamiania testów z wiersza poleceń|running-tests], które w przypadku, gdy warunki nie są spełnione, test pomija i w wypisie oznacza literą `s` - skipped. Jednak przy ręcznym uruchomieniu testu nie mają żadnego wpływu. -Opis adnotacji znajduje się na stronie Adnotacje do [testów |test-annotations]. +Opis adnotacji znajdziesz na stronie [Adnotacje testów|test-annotations]. -Możesz również zlecić pominięcie testu w oparciu o spełnienie niestandardowego warunku, używając `Environment::skip()`. Na przykład pomija to testy w systemie Windows: +Test można również pominąć na podstawie spełnienia własnego warunku za pomocą `Environment::skip()`. Na przykład ta pominie testy na Windows: ```php if (defined('PHP_WINDOWS_VERSION_BUILD')) { @@ -248,10 +247,10 @@ if (defined('PHP_WINDOWS_VERSION_BUILD')) { ``` -Struktura katalogu .[#toc-adresarova-struktura] -=============================================== +Struktura katalogów +=================== -Zalecamy, aby w przypadku nieco większych bibliotek lub projektów, katalog testowy był dodatkowo podzielony na podkatalogi według przestrzeni nazw testowanej klasy: +Zalecamy przy tylko trochę większych bibliotekach lub projektach podzielić katalog z testami jeszcze na podkatalogi według przestrzeni nazw testowanej klasy: ``` └── tests/ @@ -269,22 +268,22 @@ Zalecamy, aby w przypadku nieco większych bibliotek lub projektów, katalog tes └── ... ``` -W ten sposób możesz uruchomić testy z pojedynczej przestrzeni nazw lub podkatalogu: +Będziesz mógł wtedy uruchamiać testy z pojedynczej przestrzeni nazw, czyli podkatalogu: /--pre .[terminal] tester tests/NamespaceOne \-- -Sytuacje szczególne .[#toc-specialni-situace] -============================================= +Sytuacje specjalne +================== -Test, który nie wywołuje żadnej z metod asercji jest podejrzany i zostanie oceniony jako niepowodzenie: +Test, który nie wywoła ani jednej metody asercji, jest podejrzany i zostanie oceniony jako błędny: /--pre .[terminal] Error: This test forgets to execute an assertion. \-- -Jeśli test bez wywoływania asercji ma być naprawdę uznany za ważny, wywołaj na przykład `Assert::true(true)`. +Jeśli naprawdę test bez wywoływania asercji ma być uważany za ważny, wywołaj na przykład `Assert::true(true)`. -Podstępne może być również użycie `exit()` i `die()`, aby wyjść z testu z komunikatem o błędzie. Na przykład `exit('Error in connection')` zakończy test z kodem zwrotnym 0, oznaczającym sukces. Skorzystaj z `Assert::fail('Error in connection')`. +Również zdradliwe może być używanie `exit()` i `die()` do zakończenia testu z komunikatem błędu. Na przykład `exit('Error in connection')` zakończy test z kodem powrotu 0, co sygnalizuje sukces. Użyj `Assert::fail('Error in connection')`. diff --git a/tester/pt/@home.texy b/tester/pt/@home.texy index ad110898db..dcc09eb481 100644 --- a/tester/pt/@home.texy +++ b/tester/pt/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Nette Tester - Teste de unidade agradável em PHP}} -{{description: Nette Tester é uma ferramenta de teste de código PHP simples e ao mesmo tempo muito útil.}} +{{maintitle: Nette Tester – testes agradáveis em PHP}} +{{description: Nette Tester é uma ferramenta simples e muito útil para testar código PHP.}} diff --git a/tester/pt/@left-menu.texy b/tester/pt/@left-menu.texy index 0d54b03c05..a84705150d 100644 --- a/tester/pt/@left-menu.texy +++ b/tester/pt/@left-menu.texy @@ -1,8 +1,8 @@ -- [Como Começar |guide] -- [Testes de escrita |Writing Tests] -- [Testes em andamento |Running Tests] +- [Começando com Nette Tester |guide] +- [Escrevendo testes |writing-tests] +- [Executando testes |running-tests] -- [Assertions |Assertions] -- [Anotações de teste |Test Annotations] +- [Asserções |assertions] +- [Anotações de teste |test-annotations] - [TestCase |TestCase] -- [Ajudantes |Helpers] +- [Classes auxiliares |helpers] diff --git a/tester/pt/@menu.texy b/tester/pt/@menu.texy index 1899d8971f..76fefa299f 100644 --- a/tester/pt/@menu.texy +++ b/tester/pt/@menu.texy @@ -1,3 +1,3 @@ -- [Início |@home] -- [Documentação |Guide] +- [Introdução |@home] +- [Documentação |guide] - "GitHub .[link-external]":https://github.com/nette/tester diff --git a/tester/pt/@meta.texy b/tester/pt/@meta.texy new file mode 100644 index 0000000000..f3aa8429c5 --- /dev/null +++ b/tester/pt/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentação Tester}} diff --git a/tester/pt/assertions.texy b/tester/pt/assertions.texy index 8cf920b3b5..87bcadce6b 100644 --- a/tester/pt/assertions.texy +++ b/tester/pt/assertions.texy @@ -1,35 +1,35 @@ -Assertions -********** +Asserções +********* .[perex] -As afirmações são usadas para afirmar que um valor real corresponde a um valor esperado. Eles são métodos do `Tester\Assert`. +As asserções são usadas para confirmar que o valor real corresponde ao valor esperado. São métodos da classe `Tester\Assert`. -Escolha as afirmações mais precisas. É melhor `Assert::same($a, $b)` do que `Assert::true($a === $b)` porque exibe uma mensagem de erro significativa em caso de falha. No segundo caso, obtemos apenas `false should be true` e não diz nada sobre o conteúdo das variáveis $a e $b. +Escolha as asserções mais adequadas. É melhor `Assert::same($a, $b)` do que `Assert::true($a === $b)`, porque em caso de falha exibe uma mensagem de erro significativa. No segundo caso, apenas `false should be true`, o que não nos diz nada sobre o conteúdo das variáveis `$a` e `$b`. -A maioria das afirmações também pode ter um `$description` opcional que aparece na mensagem de erro se a expectativa falhar. +A maioria das asserções também pode ter uma descrição opcional no parâmetro `$description`, que será exibida na mensagem de erro se a expectativa falhar. -Os exemplos assumem que a seguinte classe está definida: +Os exemplos pressupõem a criação de um alias: ```php use Tester\Assert; ``` -Assert::same($expected, $actual, string $description=null) .[method] --------------------------------------------------------------------- -`$expected` deve ser o mesmo que `$actual`. É o mesmo que o operador de PHP `===`. +Assert::same($expected, $actual, ?string $description=null) .[method] +--------------------------------------------------------------------- +`$expected` deve ser idêntico a `$actual`. O mesmo que o operador PHP `===`. -Assert::notSame($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------ -Ao contrário de `Assert::same()`, então é o mesmo que o operador PHP `!==`. +Assert::notSame($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------ +O oposto de `Assert::same()`, ou seja, o mesmo que o operador PHP `!==`. -Assert::equal($expected, $actual, string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] ------------------------------------------------------------------------------------------------------------------------- -`$expected` deve ser o mesmo que `$actual`. Ao contrário de `Assert::same()`, a identidade do objeto, a ordem dos pares de chaves => valor em arrays e números decimais marginalmente diferentes são ignorados, o que pode ser alterado através da configuração `$matchIdentity` e `$matchOrder`. +Assert::equal($expected, $actual, ?string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] +------------------------------------------------------------------------------------------------------------------------- +`$expected` deve ser igual a `$actual`. Ao contrário de `Assert::same()`, a identidade dos objetos, a ordem dos pares chave => valor em arrays e números decimais marginalmente diferentes são ignorados, o que pode ser alterado definindo `$matchIdentity` e `$matchOrder`. -Os seguintes casos são idênticos do ponto de vista de `equal()`, mas não para `same()`: +Os seguintes casos são idênticos do ponto de vista de `equal()`, mas não de `same()`: ```php Assert::equal(0.3, 0.1 + 0.2); @@ -40,81 +40,81 @@ Assert::equal( ); ``` -Entretanto, tenha cuidado, a matriz `[1, 2]` e `[2, 1]` não são iguais, porque apenas a ordem dos valores é diferente, não a chave => pares de valores. A matriz `[1, 2]` também pode ser escrito como `[0 => 1, 1 => 2]` e, portanto `[1 => 2, 0 => 1]` serão considerados iguais. +No entanto, atenção, os arrays `[1, 2]` e `[2, 1]` não são iguais, porque diferem apenas na ordem dos valores, não nos pares chave => valor. O array `[1, 2]` também pode ser escrito como `[0 => 1, 1 => 2]` e, portanto, `[1 => 2, 0 => 1]` será considerado igual. -Você também pode usar as chamadas [expectativas |#expectations] em `$expected`. +Além disso, em `$expected` pode-se usar as chamadas [##expectations]. -Assert::notEqual($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------- -Oposto a `Assert::equal()`. +Assert::notEqual($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------- +O oposto de `Assert::equal()`. -Assert::contains($needle, string|array $actual, string $description=null) .[method] ------------------------------------------------------------------------------------ -Se `$actual` for um fio, ele deve conter o substrato `$needle`. Se for uma matriz, deve conter o elemento `$needle` (é comparado estritamente). +Assert::contains($needle, string|array $actual, ?string $description=null) .[method] +------------------------------------------------------------------------------------ +Se `$actual` for uma string, deve conter a substring `$needle`. Se for um array, deve conter o elemento `$needle` (comparado estritamente). -Assert::notContains($needle, string|array $actual, string $description=null) .[method] --------------------------------------------------------------------------------------- -Oposto a `Assert::contains()`. +Assert::notContains($needle, string|array $actual, ?string $description=null) .[method] +--------------------------------------------------------------------------------------- +O oposto de `Assert::contains()`. -Assert::hasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} -------------------------------------------------------------------------------------------------------- -`$actual` deve ser uma matriz e deve conter a chave `$needle`. +Assert::hasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +-------------------------------------------------------------------------------------------------------- +`$actual` deve ser um array e deve conter a chave `$needle`. -Assert::notHasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} ----------------------------------------------------------------------------------------------------------- -`$actual` deve ser uma matriz e não deve conter a chave `$needle`. +Assert::notHasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +----------------------------------------------------------------------------------------------------------- +`$actual` deve ser um array e não deve conter a chave `$needle`. -Assert::true($value, string $description=null) .[method] --------------------------------------------------------- -`$value` deve ser `true`, portanto `$value === true`. +Assert::true($value, ?string $description=null) .[method] +--------------------------------------------------------- +`$value` deve ser `true`, ou seja, `$value === true`. -Assert::truthy($value, string $description=null) .[method] ----------------------------------------------------------- -`$value` deve ser verdadeira, portanto satisfaz a condição `if ($value) ...`. +Assert::truthy($value, ?string $description=null) .[method] +----------------------------------------------------------- +`$value` deve ser verdadeiro, ou seja, satisfaz a condição `if ($value) ...`. -Assert::false($value, string $description=null) .[method] ---------------------------------------------------------- -`$value` deve ser `false`, portanto `$value === false`. +Assert::false($value, ?string $description=null) .[method] +---------------------------------------------------------- +`$value` deve ser `false`, ou seja, `$value === false`. -Assert::falsey($value, string $description=null) .[method] ----------------------------------------------------------- -`$value` deve ser falso, por isso satisfaz a condição `if (!$value) ...`. +Assert::falsey($value, ?string $description=null) .[method] +----------------------------------------------------------- +`$value` deve ser falso, ou seja, satisfaz a condição `if (!$value) ...`. -Assert::null($value, string $description=null) .[method] --------------------------------------------------------- -`$value` deve ser `null`, portanto `$value === null`. +Assert::null($value, ?string $description=null) .[method] +--------------------------------------------------------- +`$value` deve ser `null`, ou seja, `$value === null`. -Assert::notNull($value, string $description=null) .[method] ------------------------------------------------------------ -`$value` não deve ser `null`, portanto `$value !== null`. +Assert::notNull($value, ?string $description=null) .[method] +------------------------------------------------------------ +`$value` não deve ser `null`, ou seja, `$value !== null`. -Assert::nan($value, string $description=null) .[method] -------------------------------------------------------- -`$value` não deve ser um número. Use apenas o `Assert::nan()` para testes NAN. O valor do NAN é muito específico e as afirmações `Assert::same()` ou `Assert::equal()` podem se comportar de forma imprevisível. +Assert::nan($value, ?string $description=null) .[method] +-------------------------------------------------------- +`$value` deve ser Not a Number. Para testar valores NAN, use exclusivamente `Assert::nan()`. O valor NAN é muito específico e as asserções `Assert::same()` ou `Assert::equal()` podem funcionar inesperadamente. -Assert::count($count, Countable|array $value, string $description=null) .[method] ---------------------------------------------------------------------------------- -O número de elementos em `$value` deve ser `$count`. Portanto, o mesmo que `count($value) === $count`. +Assert::count($count, Countable|array $value, ?string $description=null) .[method] +---------------------------------------------------------------------------------- +O número de elementos em `$value` deve ser `$count`. Ou seja, o mesmo que `count($value) === $count`. -Assert::type(string|object $type, $value, string $description=null) .[method] ------------------------------------------------------------------------------ -`$value` deve ser de um determinado tipo. Como `$type`, podemos usar cordel: +Assert::type(string|object $type, $value, ?string $description=null) .[method] +------------------------------------------------------------------------------ +`$value` deve ser do tipo especificado. Como `$type`, podemos usar uma string: - `array` -- `list` - matriz indexada em ordem ascendente de chaves numéricas a partir de zero +- `list` - array indexado por uma sequência crescente de chaves numéricas a partir de zero - `bool` - `callable` - `float` @@ -124,14 +124,14 @@ Assert::type(string|object $type, $value, string $description=null) .[method] - `resource` - `scalar` - `string` -- nome da classe ou objeto deve então passar diretamente `$value instanceof $type` +- nome da classe ou diretamente o objeto, então `$value instanceof $type` deve ser verdadeiro -Assert::exception(callable $callable, string $class, string $message=null, $code=null) .[method] ------------------------------------------------------------------------------------------------- -Em `$callable` deve ser lançada uma exceção de instância `$class`. Se passarmos `$message`, a mensagem da exceção deve [coincidir |#assert-match]. E se passarmos `$code`, o código da exceção deve ser o mesmo. +Assert::exception(callable $callable, string $class, ?string $message=null, $code=null) .[method] +------------------------------------------------------------------------------------------------- +Ao chamar `$callable`, uma exceção da classe `$class` deve ser lançada. Se especificarmos `$message`, a mensagem da exceção também deve [corresponder ao padrão |#Assert::match] e, se especificarmos `$code`, os códigos também devem corresponder estritamente. -Por exemplo, este teste falha porque a mensagem da exceção não corresponde: +O seguinte teste falhará porque a mensagem da exceção não corresponde: ```php Assert::exception( @@ -141,7 +141,7 @@ Assert::exception( ); ``` -O `Assert::exception()` retorna uma exceção lançada, para que você possa testar uma exceção aninhada. +`Assert::exception()` retorna a exceção lançada, permitindo testar também exceções aninhadas. ```php $e = Assert::exception( @@ -154,9 +154,9 @@ Assert::type(RuntimeException::class, $e->getPrevious()); ``` -Assert::error(string $callable, int|string|array $type, string $message=null) .[method] ---------------------------------------------------------------------------------------- -Verifica se a invocação `$callable` gera os erros esperados (ou seja, avisos, avisos, etc.). Como `$type`, especificamos uma das constantes `E_...`, por exemplo `E_WARNING`. E, se passar `$message`, a mensagem de erro também deve [corresponder |#assert-match] ao padrão. Por exemplo: +Assert::error(string $callable, int|string|array $type, ?string $message=null) .[method] +---------------------------------------------------------------------------------------- +Verifica se a função `$callable` gerou os erros esperados (ou seja, avisos, notices, etc.). Como `$type`, especificamos uma das constantes `E_...`, por exemplo, `E_WARNING`. E se especificarmos `$message`, a mensagem de erro também deve [corresponder ao padrão |#Assert::match]. Por exemplo: ```php Assert::error( @@ -166,7 +166,7 @@ Assert::error( ); ``` -Se a ligação de retorno gera mais erros, devemos esperar todos eles na ordem exata. Neste caso, passamos a matriz em `$type`: +Se o callback gerar múltiplos erros, devemos esperá-los todos na ordem exata. Nesse caso, passamos um array em `$type`: ```php Assert::error(function () { @@ -179,108 +179,108 @@ Assert::error(function () { ``` .[note] -Se `$type` é nome de classe, esta afirmação se comporta da mesma forma que `Assert::exception()`. +Se você especificar um nome de classe como `$type`, ele se comporta da mesma forma que `Assert::exception()`. Assert::noError(callable $callable) .[method] --------------------------------------------- -Verifica se a função `$callable` não lança nenhum aviso/notificação/erro ou exceção em PHP. É útil para testar um pedaço de código onde não há outra asserção. +Verifica se a função `$callable` não gerou nenhum aviso, erro ou exceção. É útil para testar pedaços de código onde não há outra asserção. -Assert::match(string $pattern, $actual, string $description=null) .[method] ---------------------------------------------------------------------------- -`$actual` Devemos corresponder a `$pattern`. Podemos utilizar duas variantes de padrões: expressões regulares ou wildcards. +Assert::match(string $pattern, $actual, ?string $description=null) .[method] +---------------------------------------------------------------------------- +`$actual` deve corresponder ao padrão `$pattern`. Podemos usar duas variantes de padrões: expressões regulares ou caracteres curinga. -Se passarmos uma expressão regular como `$pattern`, devemos usar `~` or `#` para delimitá-la. Outros delimitadores não são suportados. Por exemplo, teste onde `$var` deve conter apenas dígitos hexadecimais: +Se passarmos uma expressão regular como `$pattern`, devemos usar `~` ou `#` para delimitá-la, outros delimitadores não são suportados. Por exemplo, um teste onde `$var` deve conter apenas dígitos hexadecimais: ```php Assert::match('#^[0-9a-f]$#i', $var); ``` -A outra variante é semelhante à comparação de cordas, mas podemos usar alguns chars selvagens em `$pattern`: - -- `%a%` um ou mais de qualquer coisa, exceto os caracteres de fim de linha -- `%a?%` zero ou mais de qualquer coisa, exceto os caracteres de fim de linha -- `%A%` um ou mais de qualquer coisa, incluindo os caracteres de fim de linha -- `%A?%` zero ou mais de qualquer coisa, incluindo os caracteres de fim de linha -- `%s%` um ou mais caracteres de espaço branco, exceto os caracteres de fim de linha -- `%s?%` zero ou mais caracteres de espaço branco, exceto os caracteres de fim de linha -- `%S%` um ou mais caracteres, exceto o espaço branco -- `%S?%` zero ou mais caracteres, exceto para o espaço branco -- `%c%` um único personagem de qualquer tipo (exceto para o final da linha) +A segunda variante é semelhante à comparação normal de strings, mas em `$pattern` podemos usar vários caracteres curinga: + +- `%a%` um ou mais caracteres, exceto caracteres de fim de linha +- `%a?%` nenhum ou mais caracteres, exceto caracteres de fim de linha +- `%A%` um ou mais caracteres, incluindo caracteres de fim de linha +- `%A?%` nenhum ou mais caracteres, incluindo caracteres de fim de linha +- `%s%` um ou mais espaços em branco, exceto caracteres de fim de linha +- `%s?%` nenhum ou mais espaços em branco, exceto caracteres de fim de linha +- `%S%` um ou mais caracteres, exceto espaços em branco +- `%S?%` nenhum ou mais caracteres, exceto espaços em branco +- `%c%` qualquer caractere único, exceto o caractere de fim de linha - `%d%` um ou mais dígitos -- `%d?%` zero ou mais dígitos -- `%i%` valor inteiro assinado -- `%f%` número do ponto flutuante -- `%h%` um ou mais dígitos do HEX +- `%d?%` nenhum ou mais dígitos +- `%i%` valor inteiro com sinal +- `%f%` número de ponto flutuante +- `%h%` um ou mais dígitos hexadecimais - `%w%` um ou mais caracteres alfanuméricos -- `%%` um % de caráter +- `%%` o caractere % Exemplos: ```php -# Again, hexadecimal number test +# Novamente, teste para número hexadecimal Assert::match('%h%', $var); -# Generalized path to file and line number +# Generalização do caminho do arquivo e número da linha Assert::match('Error in file %a% on line %i%', $errorMessage); ``` -Assert::matchFile(string $file, $actual, string $description=null) .[method] ----------------------------------------------------------------------------- -A afirmação é idêntica a [Assert::match() |#assert-match] mas o padrão é carregado a partir de `$file`. É útil para testes de cordas muito longas. Os arquivos de teste podem ser lidos. +Assert::matchFile(string $file, $actual, ?string $description=null) .[method] +----------------------------------------------------------------------------- +A asserção é idêntica a [#Assert::match()], mas o padrão é carregado do arquivo `$file`. Isso é útil para testar strings muito longas. O arquivo com o teste permanecerá claro. Assert::fail(string $message, $actual=null, $expected=null) .[method] --------------------------------------------------------------------- -Esta afirmação sempre falha. É apenas útil. Opcionalmente, podemos passar os valores esperados e reais. +Esta asserção sempre falha. Às vezes, isso é útil. Opcionalmente, podemos especificar também o valor esperado e o atual. -Expectativas .[#toc-expectations] ---------------------------------- -Se quisermos comparar estruturas mais complexas com elementos não-constantes, as afirmações acima podem não ser suficientes. Por exemplo, testamos um método que cria um novo usuário e devolve seus atributos como uma matriz. Não sabemos o valor do hash da senha, mas sabemos que ela deve ser uma string hexadecimal. E a única coisa que sabemos sobre o próximo elemento é que ele deve ser um objeto `DateTime`. +Expectations +------------ +Quando queremos comparar estruturas mais complexas com elementos não constantes, as asserções acima podem não ser suficientes. Por exemplo, testamos um método que cria um novo usuário e retorna seus atributos como um array. Não conhecemos o valor do hash da senha, mas sabemos que deve ser uma string hexadecimal. E sobre outro elemento, sabemos apenas que deve ser um objeto `DateTime`. -Nestes casos, podemos usar o `Tester\Expect` dentro do parâmetro `$expected` dos métodos `Assert::equal()` e `Assert::notEqual()`, que podem ser usados para descrever facilmente a estrutura. +Nessas situações, podemos usar `Tester\Expect` dentro do parâmetro `$expected` dos métodos `Assert::equal()` e `Assert::notEqual()`, com os quais a estrutura pode ser facilmente descrita. ```php use Tester\Expect; Assert::equal([ - 'id' => Expect::type('int'), # we expect an integer + 'id' => Expect::type('int'), # esperamos um número inteiro 'username' => 'milo', - 'password' => Expect::match('%h%'), # we expect a string matching pattern - 'created_at' => Expect::type(DateTime::class), # we expect an instance of the class + 'password' => Expect::match('%h%'), # esperamos uma string que corresponda ao padrão + 'created_at' => Expect::type(DateTime::class), # esperamos uma instância da classe ], User::create(123, 'milo', 'RandomPaSsWoRd')); ``` -Com `Expect`, podemos fazer quase as mesmas afirmações que com `Assert`. Portanto, temos métodos como `Expect::same()`, `Expect::match()`, `Expect::count()`, etc. Além disso, podemos encadeá-los como: +Com `Expect`, podemos realizar quase as mesmas asserções que com `Assert`. Ou seja, temos à disposição os métodos `Expect::same()`, `Expect::match()`, `Expect::count()`, etc. Além disso, podemos encadeá-los: ```php -Expect::type(MyIterator::class)->andCount(5); # we expect MyIterator and items count is 5 +Expect::type(MyIterator::class)->andCount(5); # esperamos MyIterator e número de elementos 5 ``` -Ou, podemos escrever os próprios responsáveis pelas asserções. +Ou podemos escrever nossos próprios manipuladores de asserções. ```php Expect::that(function ($value) { - # return false if expectation fails + # retornamos false se a expectativa falhar }); ``` -Investigação de Assertions Failed .[#toc-failed-assertions-investigation] -------------------------------------------------------------------------- -O Tester mostra onde está o erro quando uma asserção falha. Quando comparamos estruturas complexas, o Tester cria despejos de valores comparados e os salva no diretório `output`. Por exemplo, quando o teste imaginário `Arrays.recursive.phpt` falha, os lixões serão salvos da seguinte forma: +Investigando asserções falhas +----------------------------- +Quando uma asserção falha, o Tester exibe onde está o erro. Se compararmos estruturas mais complexas, o Tester criará dumps dos valores comparados e os salvará no diretório `output`. Por exemplo, em caso de falha do teste fictício `Arrays.recursive.phpt`, os dumps serão salvos da seguinte forma: ``` app/ └── tests/ ├── output/ - │ ├── Arrays.recursive.actual # actual value - │ └── Arrays.recursive.expected # expected value + │ ├── Arrays.recursive.actual # valor atual + │ └── Arrays.recursive.expected # valor esperado │ - └── Arrays.recursive.phpt # failing test + └── Arrays.recursive.phpt # teste falho ``` -Podemos mudar o nome do diretório por `Tester\Dumper::$dumpDir`. +O nome do diretório pode ser alterado através de `Tester\Dumper::$dumpDir`. diff --git a/tester/pt/guide.texy b/tester/pt/guide.texy index edbe36e3b3..9147968185 100644 --- a/tester/pt/guide.texy +++ b/tester/pt/guide.texy @@ -1,17 +1,17 @@ -Começando com o Tester -********************** +Começando com Nette Tester +**************************
                                                                                                                            -Mesmo os bons programadores cometem erros. A diferença entre um bom programador e um mau programador é que o bom só o fará uma vez e da próxima vez o detectará usando testes automatizados. +Até bons programadores cometem erros. A diferença entre um bom e um mau programador é que o bom comete o erro apenas uma vez e da próxima vez o detecta usando testes automatizados. -- "Aquele que não testa está condenado a repetir seus próprios erros". (provérbio sábio) -- "Quando nos livramos de um erro, aparece outro". (Lei de Murphy) -- "Sempre que você se sentir tentado a imprimir uma declaração, escreva-a como um teste". (Martin Fowler) +- "Quem não testa, está condenado a repetir seus erros." (provérbio) +- "Assim que nos livramos de um erro, outro aparece." (Lei de Murphy) +- "Sempre que você sentir vontade de imprimir uma variável na tela, escreva um teste em vez disso." (Martin Fowler)
                                                                                                                            -Você já escreveu o seguinte código em PHP? +Você já escreveu um código semelhante em PHP? ```php $obj = new MyClass; @@ -20,40 +20,40 @@ $result = $obj->process($input); var_dump($result); ``` -Então, alguma vez você já deixou uma chamada de função só para verificar a olho se ela retorna o que deveria retornar? Você certamente o faz muitas vezes por dia. Com a mão no coração, se tudo funcionar, você apaga este código e espera que a classe não seja quebrada no futuro? A Lei de Murphy garante o oposto :-) +Ou seja, você imprimiu o resultado da chamada da função apenas para verificar visualmente se ela retorna o que deveria? Certamente você faz isso muitas vezes por dia. Mão na consciência: caso tudo funcione corretamente, você apaga este código? Você espera que a classe não quebre no futuro? As leis de Murphy garantem o contrário :-) -Na verdade, você escreveu o teste. Precisa de pequenas modificações para não exigir nossa inspeção, simplesmente para poder verificar a si mesmo. E se você não o apagasse, poderíamos executá-lo a qualquer momento no futuro para verificar se tudo ainda funciona como deveria. Você pode criar uma grande quantidade destes testes ao longo do tempo, então seria bom se pudéssemos executá-los automaticamente. +Basicamente, você escreveu um teste. Basta modificá-lo ligeiramente para que não exija verificação visual, mas que se verifique sozinho. E se você não apagar o teste, poderá executá-lo a qualquer momento no futuro e verificar se tudo ainda funciona como deveria. Com o tempo, você criará um grande número desses testes, então seria útil executá-los automaticamente. -E o Nette Tester ajuda exatamente com isso. +E com tudo isso, o Nette Tester o ajudará. -O que torna o testador único? .[#toc-what-makes-tester-unique] -============================================================== +O que torna o Tester único? +=========================== -Os testes de escrita para Nette Tester são únicos, pois ** cada teste é um script PHP padrão que pode ser executado de forma autônoma.** +Escrever testes para o Nette Tester é único porque **cada teste é um script PHP comum que pode ser executado independentemente.** -Assim, quando você escreve um teste, você pode simplesmente executá-lo para ver se há um erro de programação. Se ele funcionar corretamente. Caso contrário, você pode passar facilmente pelo programa em sua IDE e procurar por um bug. Você pode até mesmo abri-lo em um navegador. +Ou seja, quando você escreve um teste, pode simplesmente executá-lo e verificar se, por exemplo, não há um erro de programação nele. Se funciona corretamente. Se não, você pode facilmente depurá-lo em seu IDE e procurar o erro. Você pode até abri-lo no navegador. -E o mais importante - ao executá-lo, você realizará o teste. Você descobrirá imediatamente se ele passou ou não. Como? Vamos aparecer aqui. Vamos escrever um teste trivial para usar o array PHP e salvá-lo no arquivo `ArrayTest.php`: +E acima de tudo - ao executá-lo, você executa o teste. Você descobre imediatamente se ele passou ou falhou. Como? Vamos mostrar. Escreveremos um teste trivial de trabalho com um array PHP e o salvaremos no arquivo `ArrayTest.php`: ```php .{file:ArrayTest.php} OK \-- -Tente mudar a declaração para `Assert::contains('XXX', $stack);` no teste e veja o que acontece quando é executado: +Tente alterar a afirmação no teste para a falsa `Assert::contains('XXX', $stack);` e observe o que acontece ao executar: /--pre .[terminal] $ php ArrayTest.php @@ -73,53 +73,53 @@ $ php ArrayTest.php FAILURE \-- -Continuamos a escrever no capítulo [Testes de Escrita |Writing Tests]. +Continuamos sobre a escrita no capítulo [Escrita de testes|writing-tests]. -Instalação e requisitos .[#toc-installation-and-requirements] -============================================================= +Instalação e requisitos +======================= -A versão mínima exigida pelo Tester é 7.1 (para mais detalhes, veja a tabela de [versões em PHP suportadas |#supported PHP versions] ). A forma preferida de instalação é por [Composer |best-practices:composer]: +A versão mínima do PHP exigida pelo Tester é 7.1 (detalhado na tabela [supported PHP versions |#Versões do PHP suportadas]). A forma preferida de instalação é usando o [Composer |best-practices:composer]: /--pre .[terminal] composer require --dev nette/tester \-- -Tente executar o Nette Tester a partir da linha de comando (sem nenhum argumento, ele mostrará apenas um resumo da ajuda): +Tente executar o Nette Tester a partir da linha de comando (sem parâmetros, ele apenas exibirá a ajuda): /--pre .[terminal] vendor/bin/tester \-- -Testes em andamento .[#toc-running-tests] -========================================= +Execução de testes +================== -À medida que nossa aplicação cresce, uma série de testes cresce com ela. Não seria prático realizar testes um a um. Portanto, o Tester tem um testador em série, que nós invocamos da linha de comando. O parâmetro é o diretório no qual os testes estão localizados. O ponto indica o diretório atual. +À medida que a aplicação cresce, o número de testes cresce com ela. Não seria prático executar os testes um por um. Por isso, o Tester possui um executor de testes em massa, que chamamos a partir da linha de comando. Como parâmetro, indicamos o diretório onde os testes estão localizados. O ponto significa o diretório atual. /--pre .[terminal] vendor/bin/tester . \-- -O corredor do Nette Tester procura no diretório especificado e em todos os subdiretórios e procura por testes, que são arquivos `*.phpt` e `*Test.php`. Ele também encontrará nosso teste `ArrayTest.php`, pois ele combina com a máscara. +O executor de testes pesquisa o diretório especificado e todos os subdiretórios e procura por testes, que são os arquivos `*.phpt` e `*Test.php`. Ele também encontra nosso teste `ArrayTest.php`, pois corresponde à máscara. -Em seguida, começa os testes. Ele executa cada teste como um novo processo PHP, de modo que ele corre completamente isolado dos outros. Ele roda em paralelo em vários fios, tornando-o extremamente rápido. E primeiro executa testes que falharam durante a execução anterior, assim você saberá imediatamente se corrigiu o erro. +Em seguida, inicia os testes. Cada teste é executado como um novo processo PHP, ocorrendo de forma totalmente isolada dos outros. Ele os executa em paralelo em múltiplas threads e, graças a isso, é extremamente rápido. E executa primeiro os testes que falharam na execução anterior, para que você saiba imediatamente se conseguiu corrigir o erro. -Para cada teste feito, o corredor imprime um caractere para indicar o progresso: +Durante a execução dos testes, o Tester exibe continuamente os resultados no terminal como caracteres: -- . - teste aprovado -- s - o teste foi pulado -- F - teste falhou +- . – teste passou +- s – teste foi pulado (skipped) +- F – teste falhou (failed) -A saída pode ser assim: +A saída pode ter a seguinte aparência: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +PHP 8.3.2 (cli) | php -n | 8 threads ........s................F......... @@ -132,44 +132,44 @@ PHP 7.4.8 (cli) | php -n | 8 threads FAILURES! (35 tests, 1 failures, 1 skipped, 1.7 seconds) \-- -Foram realizados 35 testes, um falhou, outro foi pulado. +Foram executados 35 testes, um falhou, um foi pulado. -Continuamos no capítulo [Testes em andamento |Running tests]. +Continuamos no capítulo [Execução de testes|running-tests]. -Modo de relógio .[#toc-watch-mode] -================================== +Modo Watch +========== -Você está refatorando o código? Ou você está mesmo desenvolvendo de acordo com a metodologia TDD (Test Driven Development)? Então você vai gostar do modo relógio. O Tester monitora os códigos fonte e executa-se a si mesmo quando é alterado. +Está refatorando o código? Ou até mesmo desenvolvendo de acordo com a metodologia TDD (Test Driven Development)? Então você vai gostar do modo watch. O Tester nele monitora os códigos-fonte e, ao detectar uma alteração, executa-se automaticamente. -Durante o desenvolvimento, você tem um terminal no canto do monitor, onde a barra de status verde acende em você, e quando de repente fica vermelha, você sabe que acabou de fazer algo indesejado. Na verdade, é um grande jogo onde você programa e tenta se manter fiel à cor. +Durante o desenvolvimento, você tem no canto do monitor um terminal onde uma barra de status verde está acesa, e quando ela de repente muda para vermelho, você sabe que acabou de fazer algo não totalmente correto. É na verdade um ótimo jogo, onde você programa e tenta manter a cor. -O modo de relógio é iniciado usando o parâmetro [--watch |running-tests#w-watch-path]. +O modo Watch é iniciado com o parâmetro [--watch |running-tests#-w --watch path]. -Relatórios de Cobertura de Código .[#toc-codecoverage-reports] -============================================================== +Relatórios de Cobertura de Código +================================= -O Tester pode gerar relatórios com uma visão geral de quanto o código fonte os testes cobrem. O relatório pode ser em formato HTML legível por humanos ou em XML Clover para processamento posterior na máquina. +O Tester pode gerar relatórios com uma visão geral de quanto do código-fonte os testes cobrem. O relatório pode ser em formato HTML legível por humanos ou Clover XML para processamento posterior por máquina. -Veja o "relatório HTML de amostra":https://files.nette.org/tester/coverage.html com cobertura de código. +Veja um "exemplo de relatório HTML":attachment:coverage.html com cobertura de código. -Versões PHP suportadas .[#toc-supported-php-versions] -===================================================== +Versões do PHP suportadas +========================= -| versão | compatível com PHP +| versão | compatível com PHP |------------------|------------------- -| Tester 2.5 | PHP 8.0 - 8.2 -| Tester 2.4 | PHP 7.2 - 8.2 -| Tester 2.3 | PHP 7.1 - 8.0 -| Tester 2.1 - 2.2 | PHP 7.1 - 7.3 -| Tester 2.0 | PHP 5.6 - 7.3 -| Tester 1.7 | PHP 5.3 - 7.3 + HHVM 3.3+ -| Tester 1.6 | PHP 5.3 - 7.0 + HHVM -| Tester 1.3 - 1.5 | PHP 5.3 - 5.6 + HHVM -| Tester 0,9 - 1,2 | PHP 5,3 - 5,6 - -Aplica-se às últimas versões de remendos. - -Até a versão 1.7 Tester suportada [HHVM |https://hhvm.com] 3.3.0 ou mais recente (usando `tester -p hhvm`). O suporte foi abandonado desde o Tester 2.0. O uso era simples: +| Tester 2.5 | PHP 8.0 – 8.3 +| Tester 2.4 | PHP 7.2 – 8.2 +| Tester 2.3 | PHP 7.1 – 8.0 +| Tester 2.1 – 2.2 | PHP 7.1 – 7.3 +| Tester 2.0 | PHP 5.6 – 7.3 +| Tester 1.7 | PHP 5.3 – 7.3 + HHVM 3.3+ +| Tester 1.6 | PHP 5.3 – 7.0 + HHVM +| Tester 1.3 – 1.5 | PHP 5.3 – 5.6 + HHVM +| Tester 0.9 – 1.2 | PHP 5.3 – 5.6 + +Válido para a última versão de patch. + +O Tester até a versão 1.7 também suportava [HHVM |https://hhvm.com] 3.3.0 ou superior (via `tester -p hhvm`). O suporte foi encerrado a partir da versão 2.0 do Tester. diff --git a/tester/pt/helpers.texy b/tester/pt/helpers.texy index 4b20d97442..fbcdce46f0 100644 --- a/tester/pt/helpers.texy +++ b/tester/pt/helpers.texy @@ -1,31 +1,45 @@ -Ajudantes -********* +Classes Auxiliares +****************** -DomQuery .[#toc-domquery] -------------------------- -`Tester\DomQuery` é uma classe que se estende `SimpleXMLElement` com métodos que facilitam o teste de conteúdo HTML ou XML. +DomQuery +-------- +`Tester\DomQuery` é uma classe que estende `SimpleXMLElement` com busca fácil em HTML ou XML usando seletores CSS. ```php -# let's have an HTML document in $html that we load -$dom = Tester\DomQuery::fromHtml($html); - -# we can test the presence of elements using CSS selectors -Assert::true($dom->has('form#registration')); -Assert::true($dom->has('input[name="username"]')); -Assert::true($dom->has('input[type="submit"]')); - -# or select elements as array of DomQuery -$elems = $dom->find('input[data-autocomplete]'); +# criação de DomQuery a partir de uma string HTML +$dom = Tester\DomQuery::fromHtml(' +
                                                                                                                            +

                                                                                                                            Title

                                                                                                                            +
                                                                                                                            Text
                                                                                                                            +
                                                                                                                            +'); + +# teste de existência de elementos usando seletores CSS +Assert::true($dom->has('article.post')); +Assert::true($dom->has('h1')); + +# encontrar elementos como um array de objetos DomQuery +$headings = $dom->find('h1'); +Assert::same('Title', (string) $headings[0]); + +# teste se o elemento corresponde ao seletor (a partir da versão 2.5.3) +$content = $dom->find('.content')[0]; +Assert::true($content->matches('div')); +Assert::false($content->matches('p')); + +# encontrar o ancestral mais próximo correspondente ao seletor (a partir de 2.5.5) +$article = $content->closest('.post'); +Assert::true($article->matches('article')); ``` -FileMock .[#toc-filemock] -------------------------- -`Tester\FileMock` emula arquivos em memória para ajudá-lo a testar um código que utiliza funções como `fopen()`, `file_get_contents()` ou `parse_ini_file()`. Por exemplo: +FileMock +-------- +`Tester\FileMock` emula arquivos na memória e facilita o teste de código que usa funções como `fopen()`, `file_get_contents()`, `parse_ini_file()` e similares. Exemplo de uso: ```php -# Tested class +# Classe testada class Logger { public function __construct( @@ -39,21 +53,21 @@ class Logger } } -# New empty file +# Novo arquivo vazio $file = Tester\FileMock::create(''); $logger = new Logger($file); $logger->log('Login'); $logger->log('Logout'); -# Created content testing +# Testamos o conteúdo criado Assert::same("Login\nLogout\n", file_get_contents($file)); ``` Assert::with() .[filter] ------------------------ -Isto não é uma afirmação, mas uma ajuda para testar métodos privados e objetos de propriedade. +Não é uma asserção, mas um auxiliar para testar métodos e propriedades privadas de objetos. ```php class Entity @@ -65,17 +79,17 @@ class Entity $ent = new Entity; Assert::with($ent, function () { - Assert::true($this->enabled); // acessível privado $ent->enabled + Assert::true($this->enabled); // $ent->enabled privado tornado acessível }); ``` Helpers::purge() .[filter] -------------------------- -O método `purge()` cria o diretório especificado e, se ele já existir, apaga todo o seu conteúdo. Ele é útil para a criação de diretórios temporários. Por exemplo, em `tests/bootstrap.php`: +O método `purge()` cria o diretório especificado e, se já existir, apaga todo o seu conteúdo. É útil para criar um diretório temporário. Por exemplo, em `tests/bootstrap.php`: ```php -@mkdir(__DIR__ . '/tmp'); # @ - directory may already exist +@mkdir(__DIR__ . '/tmp'); # @ - o diretório já pode existir define('TempDir', __DIR__ . '/tmp/' . getmypid()); Tester\Helpers::purge(TempDir); @@ -84,25 +98,25 @@ Tester\Helpers::purge(TempDir); Environment::lock() .[filter] ----------------------------- -Os testes são realizados em paralelo. Às vezes, não precisamos sobrepor o teste em execução. Normalmente, os testes de banco de dados precisam preparar o conteúdo do banco e nada os perturba durante o tempo de execução do teste. Nesses casos, usamos `Tester\Environment::lock($name, $dir)`: +Os testes são executados em paralelo. No entanto, às vezes precisamos que a execução dos testes não se sobreponha. Tipicamente em testes de banco de dados, é necessário que um teste prepare o conteúdo do banco de dados e outro teste não acesse o banco de dados durante sua execução. Nesses testes, usamos `Tester\Environment::lock($name, $dir)`: ```php Tester\Environment::lock('database', __DIR__ . '/tmp'); ``` -O primeiro argumento é o nome de um cadeado. O segundo é um caminho para o diretório para salvar o cadeado. O teste que adquire a eclusa é o primeiro. Outros testes devem esperar até que seja concluído. +O primeiro parâmetro é o nome da trava, o segundo é o caminho para o diretório para armazenar a trava. O teste que obtém a trava primeiro é executado, os outros testes devem esperar sua conclusão. Environment::bypassFinals() .[filter] ------------------------------------- -As classes ou métodos marcados como `final` são difíceis de serem testados. Chamar o `Tester\Environment::bypassFinals()` em um início de teste faz com que as palavras-chave `final` sejam removidas durante o carregamento do código. +Classes ou métodos marcados como `final` são difíceis de testar. A chamada `Tester\Environment::bypassFinals()` no início do teste faz com que as palavras-chave `final` sejam omitidas durante o carregamento do código. ```php require __DIR__ . '/bootstrap.php'; Tester\Environment::bypassFinals(); -class MyClass extends NormallyFinalClass # <-- NormallyFinalClass is not final anymore +class MyClass extends NormallyFinalClass # <-- NormallyFinalClass não é mais final { // ... } @@ -111,18 +125,18 @@ class MyClass extends NormallyFinalClass # <-- NormallyFinalClass is not final Environment::setup() .[filter] ------------------------------ -- melhora a legibilidade de erros de despejo (coloração incluída), caso contrário, o traço padrão da pilha PHP é impresso -- permite verificar se as afirmações foram chamadas em teste, caso contrário, testes sem (por exemplo, esquecidos) as afirmações passam também -- inicia automaticamente o coletor de código de cobertura quando `--coverage` é utilizado (descrito mais tarde) -- imprime o status OK ou FAILURE no final do roteiro +- melhora a legibilidade da saída de erros (incluindo coloração), caso contrário, é exibido o stack trace padrão do PHP +- ativa a verificação de que asserções foram chamadas no teste, caso contrário, um teste sem asserções (por exemplo, esquecidas) também passará +- ao usar `--coverage`, inicia automaticamente a coleta de informações sobre o código executado (descrito adiante) +- exibe o status OK ou FAILURE no final do script Environment::setupFunctions() .[filter]{data-version:2.5} --------------------------------------------------------- -Cria as funções globais `test()`, `setUp()` e `tearDown()`, nas quais é possível dividir os testes. +Cria as funções globais `test()`, `testException()`, `setUp()` e `tearDown()`, nas quais você pode dividir os testes. ```php -test('test description', function () { +test('descrição do teste', function () { Assert::same(123, foo()); Assert::false(bar()); // ... @@ -132,21 +146,21 @@ test('test description', function () { Environment::VariableRunner .[filter] ------------------------------------- -Permite descobrir se o teste foi realizado diretamente ou através do Tester. +Permite verificar se o teste foi executado diretamente ou através do Tester. ```php if (getenv(Tester\Environment::VariableRunner)) { - # run by Tester + # executado pelo Tester } else { - # another way + # executado de outra forma } ``` Environment::VariableThread .[filter] ------------------------------------- -O testador realiza testes em paralelo em um determinado número de roscas. Encontraremos um número de roscas em uma variável ambiental quando estivermos interessados: +O Tester executa os testes em paralelo no número especificado de threads. Se estivermos interessados no número da thread, obtemo-lo da variável de ambiente: ```php -echo "I'm running in a thread number " . getenv(Tester\Environment::VariableThread); +echo "Executando na thread número " . getenv(Tester\Environment::VariableThread); ``` diff --git a/tester/pt/running-tests.texy b/tester/pt/running-tests.texy index 0621d3f451..b07806393c 100644 --- a/tester/pt/running-tests.texy +++ b/tester/pt/running-tests.texy @@ -1,55 +1,55 @@ -Testes em andamento -******************* +Executando Testes +***************** .[perex] -A parte mais visível do Nette Tester é o corredor de teste da linha de comando. Ele é extremamente rápido e robusto porque inicia automaticamente todos os testes como processos separados em paralelo em várias roscas. Ele também pode funcionar sozinho no chamado modo relógio. +A parte mais visível do Nette Tester é o executor de testes a partir da linha de comando. É extraordinariamente rápido e robusto, pois executa automaticamente todos os testes como processos separados e em paralelo em múltiplas threads. Também pode se executar sozinho no chamado modo watch. -O teste de teste Nette Tester é invocado a partir da linha de comando. Como parâmetro, passaremos no diretório de testes. Para o diretório atual, basta entrar um ponto: +Chamamos o executor de testes a partir da linha de comando. Como parâmetro, indicamos o diretório com os testes. Para o diretório atual, basta digitar um ponto: /--pre .[terminal] vendor/bin/tester . \-- -Quando invocado, o test runner escaneará o diretório especificado e todos os subdiretórios e procurará por testes, que são os arquivos `*.phpt` e `*Test.php`. Ele também lê e avalia suas [anotações |test-annotations] para saber quais delas e como executá-las. +O executor de testes pesquisa o diretório especificado e todos os subdiretórios e procura por testes, que são os arquivos `*.phpt` e `*Test.php`. Ao mesmo tempo, lê e avalia suas [anotações|test-annotations], para saber quais deles e como executar. -Em seguida, ele executará os testes. Para cada teste feito, o corredor imprime um caractere para indicar o progresso: +Em seguida, executa os testes. Durante a execução dos testes, exibe continuamente os resultados no terminal como caracteres: -- . - teste aprovado -- s - o teste foi pulado -- F - teste falhou +- . – teste passou +- s – teste foi pulado (skipped) +- F – teste falhou (failed) -O resultado pode ser parecido: +A saída pode ter a seguinte aparência: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +PHP 8.3.2 (cli) | php -n | 8 threads ........s.......................... OK (35 tests, 1 skipped, 1.7 seconds) \-- -Quando você o executa novamente, ele faz os primeiros testes que falharam durante a execução anterior, assim você saberá imediatamente se corrigiu o erro. +Ao executar repetidamente, ele primeiro executa os testes que falharam na execução anterior, para que você saiba imediatamente se conseguiu corrigir o erro. -O código de saída do testador é zero se ninguém falhar. Caso contrário, não é zero. +Se nenhum teste falhar, o código de retorno do Tester é zero. Caso contrário, o código de retorno é diferente de zero. .[warning] -O Tester executa processos PHP sem `php.ini`. Mais detalhes na seção [php.ini próprio |#Own php.ini]. +O Tester executa os processos PHP sem `php.ini`. Detalhado na seção [#php.ini personalizado]. -Opções da Linha de Comando .[#toc-command-line-options] -======================================================= +Parâmetros da linha de comando +============================== -Obtemos uma visão geral das opções de linha de comando executando o Tester sem parâmetros ou com a opção `-h`: +Obtemos uma visão geral de todas as opções da linha de comando executando o Tester sem parâmetros ou com o parâmetro `-h`: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Usage: tester [options] [ | ]... @@ -58,16 +58,16 @@ Options: -p Specify PHP interpreter to run (default: php). -c Look for php.ini file (or look in directory) . -C Use system-wide php.ini. - -l | --log Write log to file . -d ... Define INI entry 'key' with value 'value'. -s Show information about skipped tests. --stop-on-fail Stop execution upon the first failure. -j Run jobs in parallel (default: 8). - -o Specify output format. + -o (e.g. -o junit:output.xml) + Specify one or more output formats with optional file name. -w | --watch Watch directory. -i | --info Show tests environment info and exit. --setup Script for runner setup. - --temp Path to temporary directory. Default: sys_get_temp_dir(). + --temp Path to temporary directory. Default by sys_get_temp_dir(). --colors [1|0] Enable or disable colors. --coverage Generate code coverage report to file. --coverage-src Path to source code. @@ -77,7 +77,7 @@ Options: -p .[filter] ------------------- -Especifica o binário PHP que será usado para executar testes. Por padrão, ele é `php`. +Especifica o binário PHP que será usado para executar os testes. O padrão é `php`. /--pre .[terminal] tester -p /home/user/php-7.2.0-beta/php-cgi tests @@ -86,26 +86,17 @@ tester -p /home/user/php-7.2.0-beta/php-cgi tests -c .[filter] ------------------- -Especifica qual `php.ini` será usado ao executar testes. Por padrão, nenhum php.ini é usado. Consulte o [próprio php.ini |#Own php.ini] para mais informações. +Especifica qual `php.ini` será usado ao executar os testes. Por padrão, nenhum php.ini é usado. Mais na seção [#php.ini personalizado]. -C .[filter] ------------ -É utilizado um sistema de todo o sistema `php.ini`. Assim, na plataforma UNIX, todos os arquivos `/etc/php/{sapi}/conf.d/*.ini` também são utilizados. Veja a seção [php.ini Próprio |#Own php.ini]. - - -"'-l | --log "'". .[filter] ---------------------------------- -Os testes de progresso são escritos em arquivo. Todos os testes falharam, saltaram e também foram bem-sucedidos: - -/--pre .[terminal] -tester --log /var/log/tests.log tests -\-- +Usa o `php.ini` do sistema. No UNIX, também todos os arquivos INI relevantes `/etc/php/{sapi}/conf.d/*.ini`. Mais na seção [#php.ini personalizado]. -d .[filter] ------------------------ -Define o valor da diretiva de configuração PHP para testes. O parâmetro pode ser usado várias vezes. +Define o valor da diretiva de configuração PHP para os testes. O parâmetro pode ser usado várias vezes. /--pre .[terminal] tester -d max_execution_time=20 @@ -114,43 +105,45 @@ tester -d max_execution_time=20 -s --- -Serão mostradas informações sobre testes pulados. +Exibe informações sobre os testes pulados. --stop-on-fail .[filter] ------------------------ -O testador interrompe os testes após o primeiro teste reprovado. +O Tester interrompe os testes no primeiro teste que falhar. -j .[filter] ------------------ -Os testes são realizados em um `` precessos paralelos. O valor padrão é 8. Se desejarmos realizar testes em série, usamos o valor 1. +Especifica quantos processos paralelos com testes serão iniciados. O valor padrão é 8. Se quisermos que todos os testes sejam executados em série, usamos o valor 1. --o .[filter] -------------------------------------- -Formato de saída. O formato padrão é o formato do console. +-o .[filter] +------------------------------------------------------- +Define o formato da saída. O padrão é o formato para console. Você pode especificar o nome do arquivo no qual a saída será escrita (por exemplo, `-o junit:output.xml`). A opção `-o` pode ser repetida várias vezes para gerar vários formatos de uma vez. -- `console`: o mesmo que o padrão, mas o logotipo ASCII não é impresso neste caso -- `tap`: [Formato TAP |https://en.wikipedia.org/wiki/Test_Anything_Protocol] apropriado para o processamento de máquinas -- `junit`: formato JUnit XML, apropriado também para processamento em máquinas -- `none`: nada é impresso +- `console`: idêntico ao formato padrão, mas neste caso o logo ASCII não é exibido +- `console-lines`: semelhante ao console, mas o resultado de cada teste é listado em uma linha separada com informações adicionais +- `tap`: [Formato TAP |https://en.wikipedia.org/wiki/Test_Anything_Protocol] adequado para processamento por máquina +- `junit`: formato XML JUnit, também adequado para processamento por máquina +- `log`: Saídas do andamento dos testes. Todos os testes malsucedidos, pulados e também bem-sucedidos +- `none`: nada é exibido -"'-w | --watch '' .[filter] +''-w | --watch '' .[filter] --------------------------------- -O Tester não termina após a conclusão dos testes, mas ele continua rodando e observando arquivos PHP em determinado diretório. Quando alterado, ele executa os testes novamente. O parâmetro pode ser usado várias vezes se quisermos monitorar vários diretórios. +Após a conclusão dos testes, o Tester não termina, mas permanece em execução e monitora os arquivos PHP no diretório especificado. Ao detectar uma alteração, executa os testes novamente. O parâmetro pode ser usado várias vezes se quisermos monitorar vários diretórios. -É útil durante a refatoração de uma biblioteca ou a depuração de testes. +É útil ao refatorar uma biblioteca ou depurar testes. /--pre .[terminal] tester --watch src tests \-- -"'-i | --info' .[filter] ------------------------- -Ele mostra informações sobre um ambiente de teste em execução. Por exemplo: +''-i | --info'' .[filter] +------------------------- +Exibe informações sobre o ambiente de execução para os testes. Por exemplo: /--pre .[terminal] tester -p /usr/bin/php7.1 -c tests/php.ini --info @@ -177,13 +170,13 @@ Core, ctype, date, dom, ereg, fileinfo, filter, hash, ... --setup .[filter] ------------------------ -O Tester carrega dado script PHP no início. A variável `Tester\Runner\Runner $runner` está disponível nela. Vamos assumir o arquivo `tests/runner-setup.php`: +O Tester, ao iniciar, carrega o script PHP especificado. Nele, a variável `Tester\Runner\Runner $runner` está disponível. Suponha o arquivo `tests/runner-setup.php` com o conteúdo: ```php $runner->outputHandlers[] = new MyOutputHandler; ``` -e nós dirigimos o Tester: +Executamos o Tester: /--pre .[terminal] tester --setup tests/runner-setup.php tests @@ -192,47 +185,47 @@ tester --setup tests/runner-setup.php tests --temp .[filter] ----------------------- -Define um caminho para o diretório de arquivos temporários do Tester. O valor padrão é retornado por `sys_get_temp_dir()`. Quando o valor padrão não for válido, você será notado. +Define o caminho para o diretório de arquivos temporários do Tester. O valor padrão é retornado por `sys_get_temp_dir()`. Se o valor padrão não for válido, você será avisado. -Se não tivermos certeza de qual diretório é utilizado, podemos executar o Tester com o parâmetro `--info`. +Se não tivermos certeza de qual diretório está sendo usado, executamos o Tester com o parâmetro `--info`. --colors 1|0 .[filter] ---------------------- -O Tester detecta um terminal colorível por padrão e coloriza sua saída. Esta opção está sobre a autodetecção. Podemos definir a coloração globalmente através de uma variável de ambiente de sistema `NETTE_TESTER_COLORS`. +--colors 1|0 .[filter] +---------------------- +Por padrão, o Tester detecta um terminal colorido e colore sua saída. Esta opção substitui a autodeteção. Globalmente, podemos definir a coloração com a variável de ambiente do sistema `NETTE_TESTER_COLORS`. --coverage .[filter] --------------------------- -O teste irá gerar um relatório com uma visão geral de quanto o código fonte é coberto pelos testes. Esta opção requer a extensão PHP [Xdebug |https://xdebug.org/] ou [PCOV |https://github.com/krakjoe/pcov] habilitada, ou PHP 7 com o PHPDBG SAPI, que é mais rápido. A extensão do arquivo de destino determina o formato do conteúdo. HTML ou Clover XML. +O Tester gera um relatório com uma visão geral de quanto do código-fonte os testes cobrem. Esta opção requer a extensão PHP instalada [Xdebug |https://xdebug.org/], ou [PCOV |https://github.com/krakjoe/pcov], ou PHP 7 com PHPDBG SAPI, que é mais rápido. A extensão do arquivo de destino determina seu formato. Seja HTML ou Clover XML. /--pre .[terminal] -tester tests --coverage coverage.html # HTML report -tester tests --coverage coverage.xml # Clover XML report +tester tests --coverage coverage.html # Relatório HTML +tester tests --coverage coverage.xml # Relatório Clover XML \-- -A prioridade para escolher o mecanismo de coleta é seguir: +A prioridade de seleção do mecanismo é a seguinte: 1) PCOV 2) PHPDBG 3) Xdebug -Testes extensivos podem falhar durante a execução do PHPDBG devido à exaustão da memória. A coleta de dados de cobertura é uma operação que consome memória. Nesse caso, chamar o `Tester\CodeCoverage\Collector::flush()` dentro de um teste pode ajudar. Ele irá lavar os dados coletados em um arquivo e liberar a memória. Quando a coleta de dados não está em andamento, ou Xdebug é usado, a chamada não tem efeito. +Ao usar PHPDBG, podemos encontrar falhas nos testes devido ao esgotamento da memória em testes extensos. A coleta de informações sobre o código coberto consome muita memória. Neste caso, a chamada `Tester\CodeCoverage\Collector::flush()` dentro do teste nos ajuda. Ela escreve os dados coletados no disco e libera a memória. Se a coleta de dados não estiver ocorrendo, ou se o Xdebug estiver sendo usado, a chamada não tem efeito. -"Um exemplo de relatório HTML":https://files.nette.org/tester/coverage.html com cobertura de código. +"Exemplo de relatório HTML"|attachment:coverage.html com cobertura de código. --coverage-src .[filter] ------------------------------- -Usamo-lo com a opção `--coverage` simultaneamente. A opção `` é um caminho para o código fonte para o qual geramos o relatório. Ele pode ser usado repetidamente. +Usamos simultaneamente com a opção `--coverage`. `` é o caminho para os códigos-fonte para os quais o relatório é gerado. Pode ser usado repetidamente. -Php.ini próprio .[#toc-own-php-ini] -=================================== -O Tester executa processos PHP com a opção `-n`, o que significa que nenhum `php.ini` é carregado (nem mesmo o de `/etc/php/conf.d/*.ini` em UNIX). Ele garante o mesmo ambiente para a execução dos testes, mas também desativa todas as extensões externas do PHP comumente carregadas pelo sistema PHP. +php.ini personalizado +===================== +O Tester executa os processos PHP com o parâmetro `-n`, o que significa que nenhum `php.ini` é carregado. No UNIX, nem mesmo os de `/etc/php/conf.d/*.ini`. Isso garante um ambiente consistente para a execução dos testes, mas também desativa todas as extensões PHP normalmente carregadas pelo PHP do sistema. -Se você quiser manter a configuração do sistema, use o parâmetro `-C`. +Se você deseja manter o carregamento dos arquivos php.ini do sistema, use o parâmetro `-C`. -Se você precisar de algumas extensões ou algumas configurações especiais do INI, recomendamos criar um arquivo próprio `php.ini` e distribuí-lo entre os testes. Em seguida, executamos o Tester com a opção `-c`, por exemplo, `tester -c tests/php.ini`. O arquivo INI pode ser parecido: +Se você precisar de algumas extensões ou configurações INI especiais para os testes, recomendamos a criação de seu próprio arquivo `php.ini`, que será distribuído com os testes. O Tester é então executado com o parâmetro `-c`, por exemplo, `tester -c tests/php.ini tests`, onde o arquivo INI pode ter a seguinte aparência: ```ini [PHP] @@ -243,4 +236,4 @@ extension=php_pdo_pgsql.dll memory_limit=512M ``` -Executando o Tester com um sistema `php.ini` em UNIX, por exemplo `tester -c /etc/php/cgi/php.ini`, não carrega outros INI de `/etc/php/conf.d/*.ini`. Esse é o comportamento do PHP, não o do Tester. +A execução do Tester no UNIX com o `php.ini` do sistema, por exemplo, `tester -c /etc/php/cli/php.ini` não carrega outros INIs de `/etc/php/conf.d/*.ini`. Isso é uma característica do PHP, não do Tester. diff --git a/tester/pt/test-annotations.texy b/tester/pt/test-annotations.texy index cbf998196b..d64c74a30d 100644 --- a/tester/pt/test-annotations.texy +++ b/tester/pt/test-annotations.texy @@ -1,10 +1,10 @@ -Anotações de teste +Anotações de Teste ****************** .[perex] -As anotações determinam como os testes serão tratados pelo [corredor de teste da linha de comando |running-tests]. Elas são escritas no início do arquivo de teste. +As anotações determinam como os testes serão tratados pelo [executor de testes da linha de comando|running-tests]. Elas são escritas no início do arquivo de teste. -As anotações são insensíveis a maiúsculas e minúsculas. Elas também não têm efeito se o teste for executado manualmente como um script PHP regular. +As anotações não diferenciam maiúsculas de minúsculas. Elas também não têm efeito se o teste for executado manualmente como um script PHP comum. Exemplo: @@ -23,17 +23,17 @@ require __DIR__ . '/../bootstrap.php'; TEST .[filter] -------------- -Na verdade, não é uma anotação. Ela apenas define o título do teste que é impresso em caso de falha ou em registros. +Na verdade, isso nem é uma anotação, apenas define o título do teste, que é exibido em caso de falha ou no log. @skip .[filter] --------------- -O teste é pulado. É útil para a desativação temporária do teste. +O teste é pulado. É útil para desativar temporariamente os testes. @phpVersion .[filter] --------------------- -O teste é pulado se não for executado pela versão PHP correspondente. Nós escrevemos anotações como `@phpVersion [operator] version`. Podemos deixar de fora o operador, o padrão é `>=`. Exemplos: +O teste é pulado se não for executado com a versão correspondente do PHP. Escrevemos a anotação como `@phpVersion [operador] versão`. Podemos omitir o operador, o padrão é `>=`. Exemplos: ```php /** @@ -44,9 +44,9 @@ O teste é pulado se não for executado pela versão PHP correspondente. Nós es ``` -@phpExtensão .[filter] ----------------------- -O teste é pulado se todas as extensões PHP mencionadas não forem carregadas. As extensões múltiplas podem ser escritas em uma única anotação, ou podemos usar a anotação várias vezes. +@phpExtension .[filter] +----------------------- +O teste é pulado se todas as extensões PHP especificadas não estiverem carregadas. Podemos especificar várias extensões em uma anotação ou usá-la várias vezes. ```php /** @@ -58,9 +58,9 @@ O teste é pulado se todas as extensões PHP mencionadas não forem carregadas. @dataProvider .[filter] ----------------------- -Esta anotação serve quando queremos fazer o teste várias vezes, mas com dados diferentes. (Não confundir com a anotação com o mesmo nome para [TestCase |TestCase#dataProvider]). +Se quisermos executar o arquivo de teste várias vezes, mas com dados de entrada diferentes, esta anotação é útil. (Não confunda com a anotação de mesmo nome para [TestCase |TestCase#dataProvider].) -Escrevemos anotações como `@dataProvider file.ini`. O caminho do arquivo INI é relativo ao arquivo de teste. O teste é executado tantas vezes quanto o número de seções contidas no arquivo INI. Vamos supor que o arquivo INI `databases.ini`: +Escrevemos como `@dataProvider file.ini`, o caminho para o arquivo é relativo ao arquivo de teste. O teste será executado tantas vezes quantas seções houver no arquivo INI. Suponha o arquivo INI `databases.ini`: ```ini [mysql] @@ -77,7 +77,7 @@ password = ****** dsn = "sqlite::memory:" ``` -e o arquivo `database.phpt` no mesmo diretório: +e no mesmo diretório, o teste `database.phpt`: ```php /** @@ -87,11 +87,11 @@ e o arquivo `database.phpt` no mesmo diretório: $args = Tester\Environment::loadData(); ``` -O teste será executado três vezes e `$args` conterá valores das seções `mysql`, `postgresql` ou `sqlite`. +O teste será executado três vezes e `$args` conterá sempre os valores da seção `mysql`, `postgresql` ou `sqlite`. -Há mais uma variação quando escrevemos anotações com um ponto de interrogação como `@dataProvider? file.ini`. Neste caso, o teste é pulado se o arquivo INI não existir. +Existe ainda uma variante onde escrevemos a anotação com um ponto de interrogação como `@dataProvider? file.ini`. Neste caso, o teste é pulado se o arquivo INI não existir. -As possibilidades de anotação ainda não foram todas mencionadas. Podemos escrever condições após o arquivo INI. Os testes para a seção dada só serão realizados se todas as condições coincidirem. Vamos estender o arquivo INI: +As possibilidades da anotação não terminam aqui. Após o nome do arquivo INI, podemos especificar condições sob as quais o teste será executado para a seção correspondente. Estenderemos o arquivo INI: ```ini [mysql] @@ -113,7 +113,7 @@ password = ****** dsn = "sqlite::memory:" ``` -e usaremos anotações com condições: +e usaremos a anotação com condição: ```php /** @@ -121,9 +121,9 @@ e usaremos anotações com condições: */ ``` -O teste é executado apenas uma vez para a seção `postgresql 9.1`. As outras seções não correspondem às condições. +O teste será executado apenas uma vez e para a seção `postgresql 9.1`. As outras seções não passarão pelo filtro da condição. -Da mesma forma, podemos passar para um script PHP ao invés de INI. Ele deve retornar array ou Traversable. Arquivo `databases.php`: +Da mesma forma, em vez de um arquivo INI, podemos referenciar um script PHP. Ele deve retornar um array ou Traversable. Arquivo `databases.php`: ```php return [ @@ -142,29 +142,29 @@ return [ @multiple .[filter] ------------------- -Escrevemos como `@multiple N` onde `N` é um número inteiro. O teste é exatamente N-times. +Escrevemos como `@multiple N`, onde `N` é um número inteiro. O teste será executado exatamente N vezes. @testCase .[filter] ------------------- -A anotação não tem parâmetros. Nós a usamos quando escrevemos um teste como classes [TestCase |TestCase]. Neste caso, o executor do teste de linha de comando executará os métodos individuais em processos separados e em paralelo em várias roscas. Isto pode acelerar significativamente todo o processo de teste. +A anotação não tem parâmetros. Usamos quando escrevemos testes como classes [TestCase |TestCase]. Nesse caso, o executor de testes da linha de comando executará os métodos individuais em processos separados e em paralelo em múltiplas threads. Isso pode acelerar significativamente todo o processo de teste. @exitCode .[filter] ------------------- -Escrevemos como `@exitCode N` onde `N` is the exit code of the test. For example if `exit(10)` é chamado no teste, escrevemos a anotação como `@exitCode 10`. É considerado reprovado se o teste terminar com um código diferente. O código de saída 0 (zero) é verificado se deixarmos de fora a anotação +Escrevemos como `@exitCode N`, onde `N` é o código de retorno do teste executado. Se, por exemplo, `exit(10)` for chamado no teste, escrevemos a anotação como `@exitCode 10` e se o teste terminar com um código diferente, é considerado uma falha. Se a anotação não for especificada, o código de retorno 0 (zero) é verificado. @httpCode .[filter] ------------------- -A anotação é avaliada somente se o binário PHP for CGI. É ignorado de outra forma. Nós a escrevemos como `@httpCode NNN` onde se espera que `NNN` seja o código HTTP. O código HTTP 200 é verificado se deixarmos de fora a anotação. Se escrevermos `NNN` como uma string avaliada como zero, por exemplo `any`, o código HTTP não é verificado de forma alguma. +A anotação se aplica apenas se o binário PHP for CGI. Caso contrário, é ignorada. Escrevemos como `@httpCode NNN` onde `NNN` é o código HTTP esperado. Se a anotação não for especificada, o código HTTP 200 é verificado. Se `NNN` for escrito como uma string avaliada como zero, por exemplo, `any`, o código HTTP não é verificado. -@outputMatch a @outputMatchFile .[filter] +@outputMatch e @outputMatchFile .[filter] ----------------------------------------- -O comportamento das anotações é consistente com as afirmações `Assert::match()` e `Assert::matchFile()`. Mas o padrão é encontrado na saída padrão do teste. Um caso de uso adequado é quando assumimos que o teste termina por erro fatal e precisamos verificar seu resultado. +A função das anotações é idêntica às asserções `Assert::match()` e `Assert::matchFile()`. O padrão (pattern), no entanto, é procurado no texto que o teste enviou para sua saída padrão. Encontra aplicação se supormos que o teste terminará com um erro fatal e precisarmos verificar sua saída. @phpIni .[filter] ----------------- -Define os valores de configuração INI para teste. Por exemplo, nós o escrevemos como `@phpIni precision=20` e funciona da mesma forma como se passássemos o valor da linha de comando pelo parâmetro `-d precision=20`. +Define valores de configuração INI para o teste. Escrevemos, por exemplo, como `@phpIni precision=20` e funciona da mesma forma como se tivéssemos especificado o valor a partir da linha de comando através do parâmetro `-d precision=20`. diff --git a/tester/pt/testcase.texy b/tester/pt/testcase.texy index 2e03f46901..5ca6813b13 100644 --- a/tester/pt/testcase.texy +++ b/tester/pt/testcase.texy @@ -2,9 +2,9 @@ TestCase ******** .[perex] -As afirmações podem seguir uma a uma em testes simples. Mas às vezes é útil incluir as afirmações para testar a classe e estruturá-las desta forma. +Em testes simples, as asserções podem seguir uma após a outra. No entanto, às vezes é mais vantajoso agrupar as asserções em uma classe de teste e, assim, estruturá-las. -A classe deve ser descendente de `Tester\TestCase` e nós falamos sobre isso simplesmente como sobre **testcase***. +A classe deve ser descendente de `Tester\TestCase` e, simplificadamente, nos referimos a ela como **testcase**. A classe deve conter métodos de teste que começam com `test`. Esses métodos serão executados como testes: ```php use Tester\Assert; @@ -22,11 +22,11 @@ class RectangleTest extends Tester\TestCase } } -# Run testing methods +# Execução dos métodos de teste (new RectangleTest)->run(); ``` -Podemos enriquecer um testcase pelos métodos `setUp()` e `tearDown()`. Eles são chamados antes/depois de cada método de teste: +Um teste escrito desta forma pode ser enriquecido com os métodos `setUp()` e `tearDown()`. Eles são chamados antes e depois de cada método de teste, respectivamente: ```php use Tester\Assert; @@ -35,12 +35,12 @@ class NextTest extends Tester\TestCase { public function setUp() { - # Preparation + # Preparação } public function tearDown() { - # Clean-up + # Limpeza } public function testOne() @@ -54,14 +54,14 @@ class NextTest extends Tester\TestCase } } -# Run testing methods +# Execução dos métodos de teste (new NextTest)->run(); /* -Method Calls Order ------------------- +Ordem de chamada dos métodos +---------------------------- setUp() testOne() tearDown() @@ -72,9 +72,9 @@ tearDown() */ ``` -Se o erro ocorrer em uma fase `setUp()` ou `tearDown()`, o teste falhará. Se ocorrer erro no método de teste, o método `tearDown()` é chamado de qualquer forma, mas com erros suprimidos nele. +Se ocorrer um erro na fase `setUp()` ou `tearDown()`, o teste falha no geral. Se ocorrer um erro no método de teste, mesmo assim o método `tearDown()` será executado, mas com a supressão de erros nele. -Recomendamos que você escreva a anotação [@testCase |test-annotations#@testCase] no início do teste, então o executor do teste de linha de comando executará os métodos individuais do teste em processos separados e em paralelo em várias linhas. Isto pode acelerar significativamente todo o processo de teste. +Recomendamos escrever a anotação [@testCase |test-annotations#testCase] no início do teste, então o executor de testes da linha de comando executará os métodos individuais do testcase em processos separados e em paralelo em múltiplas threads. Isso pode acelerar significativamente todo o processo de teste. /--php getArea()); # vamos verificar os resultados esperados +Assert::same(200.0, $rect->getArea()); # verificamos os resultados esperados Assert::false($rect->isSquare()); ``` -Como você pode ver, [métodos de afirmação |Assertions] como `Assert::same()` são usados para afirmar que um valor real corresponde a um valor esperado. +Como você pode ver, os chamados [métodos de asserção|assertions] como `Assert::same()` são usados para confirmar que o valor real corresponde ao valor esperado. -O último passo é criar o arquivo `bootstrap.php`. Ele contém um código comum para todos os testes. Por exemplo, classes auto-carga, configuração de ambiente, criação de diretório temporário, helpers e similares. Cada teste carrega o bootstrap e presta atenção apenas aos testes. O bootstrap pode ser parecido: +Resta o último passo, que é o arquivo `bootstrap.php`. Ele contém código comum a todos os testes, como autoloading de classes, configuração de ambiente, criação de diretório temporário, funções auxiliares e similares. Todos os testes carregam o bootstrap e se dedicam apenas aos testes. O bootstrap pode ter a seguinte aparência: ```php .{file:tests/bootstrap.php} OK \-- -Se mudarmos no teste a declaração para falso `Assert::same(123, $rect->getArea());`, isso acontecerá: +Se alterarmos a afirmação no teste para a falsa `Assert::same(123, $rect->getArea());`, acontecerá o seguinte: /--pre .[terminal] $ php RectangleTest.php @@ -107,35 +106,35 @@ $ php RectangleTest.php \-- -Ao escrever testes, é bom pegar todas as situações extremas. Por exemplo, se a entrada for zero, um número negativo, em outros casos um fio vazio, nulo, etc. Na verdade, isso força você a pensar e decidir como o código deve se comportar em tais situações. Os testes então corrigem o comportamento. +Ao escrever testes, é bom cobrir todas as situações extremas. Por exemplo, quando a entrada for zero, um número negativo, em outros casos, talvez uma string vazia, null, etc. Na verdade, isso o força a pensar e decidir como o código deve se comportar nessas situações. Os testes então fixam o comportamento. -Em nosso caso, um valor negativo deve lançar uma exceção, o que verificamos com [Assert::exception() |Assertions#Assert::exception]: +No nosso caso, um valor negativo deve lançar uma exceção, o que verificamos usando [Assert::exception() |Assertions#Assert::exception]: ```php .{file:tests/RectangleTest.php} -// a largura não deve ser um número negativo +// a largura não pode ser negativa Assert::exception( fn() => new Rectangle(-1, 20), InvalidArgumentException::class, - "A dimensão não deve ser negativa", + 'The dimension must not be negative.', ); ``` -E acrescentamos um teste semelhante para a altura. Finalmente, testamos que `isSquare()` retorna `true` se as duas dimensões forem as mesmas. Tente escrever tais testes como um exercício. +E adicionamos um teste semelhante para a altura. Finalmente, testamos se `isSquare()` retorna `true` se ambas as dimensões forem iguais. Tente escrever esses testes como exercício. -Testes Bem-Arrangidos .[#toc-well-arranged-tests] -================================================= +Testes mais claros +================== -O tamanho do arquivo de teste pode aumentar e tornar-se rapidamente desorganizado. Portanto, é prático agrupar áreas individuais testadas em funções separadas. +O tamanho do arquivo de teste pode aumentar e rapidamente se tornar confuso. Portanto, é prático agrupar as áreas testadas individuais em funções separadas. -Primeiro, mostraremos uma variante mais simples, mas elegante, usando a função global `test()`. O testador não a cria automaticamente, para evitar uma colisão se você tivesse uma função com o mesmo nome em seu código. Ela só é criada pelo método `setupFunctions()`, que você chama no arquivo `bootstrap.php`: +Primeiro, mostraremos uma variante mais simples, mas elegante, usando a função global `test()`. O Tester não a cria automaticamente para evitar colisões caso você tenha uma função com o mesmo nome em seu código. Ela é criada pelo método `setupFunctions()`, que você chama no arquivo `bootstrap.php`: ```php .{file:tests/bootstrap.php} Tester\Environment::setup(); Tester\Environment::setupFunctions(); ``` -Usando esta função, podemos dividir bem o arquivo de teste em unidades nomeadas. Quando executado, as etiquetas serão exibidas uma após a outra. +Usando esta função, podemos dividir o arquivo de teste de forma organizada em unidades nomeadas. Durante a execução, os rótulos serão exibidos sequencialmente. ```php .{file:tests/RectangleTest.php} getArea()); Assert::false($rect->isSquare()); }); -test('general square', function () { +test('quadrado geral', function () { $rect = new Rectangle(5, 5); Assert::same(25.0, $rect->getArea()); Assert::true($rect->isSquare()); }); -test('dimensions must not be negative', function () { +test('dimensões não podem ser negativas', function () { Assert::exception( fn() => new Rectangle(-1, 20), InvalidArgumentException::class, @@ -168,15 +167,15 @@ test('dimensions must not be negative', function () { }); ``` -Se você precisar executar o código antes ou depois de cada teste, passe-o para `setUp()` ou `tearDown()`: +Se você precisar executar código antes ou depois de cada teste, passe-o para a função `setUp()` ou `tearDown()`, respectivamente: ```php setUp(function () { - // código de inicialização a ser executado antes de cada teste() + // código de inicialização que é executado antes de cada test() }); ``` -A segunda variante é o objeto. Vamos criar o chamado TestCase, que é uma classe onde as unidades individuais são representadas por métodos cujos nomes começam com teste. +A segunda variante é orientada a objetos. Criamos o chamado TestCase, que é uma classe onde as unidades individuais são representadas por métodos cujos nomes começam com `test`. ```php .{file:tests/RectangleTest.php} class RectangleTest extends Tester\TestCase @@ -208,25 +207,25 @@ class RectangleTest extends Tester\TestCase } } -// Métodos de teste de funcionamento +// Execução dos métodos de teste (new RectangleTest)->run(); ``` -Desta vez usamos uma anotação `@throw` para testar as exceções. Veja o capítulo [TestCase |TestCase] para mais informações. +Para testar exceções, desta vez usamos a anotação `@throws`. Você aprenderá mais no capítulo [TestCase |TestCase]. -Funções dos auxiliares .[#toc-helpers-functions] -================================================ +Funções Auxiliares +================== -Nette Tester inclui várias classes e funções que podem facilitar os testes, por exemplo, ajudantes para testar o conteúdo de um documento HTML, para testar as funções de trabalhar com arquivos, e assim por diante. +O Nette Tester contém várias classes e funções que podem facilitar, por exemplo, o teste do conteúdo de um documento HTML, o teste de funções que trabalham com arquivos e assim por diante. -Você pode encontrar uma descrição deles na página [Ajudantes |Helpers]. +Sua descrição pode ser encontrada na página [Classes Auxiliares|helpers]. -Anotação e testes de pular .[#toc-annotation-and-skipping-tests] -================================================================ +Anotações e Pular Testes +======================== -A execução do teste pode ser afetada por anotações no comentário do phpDoc no início do arquivo. Por exemplo, pode parecer assim: +A execução dos testes pode ser influenciada por anotações na forma de comentário phpDoc no início do arquivo. Pode ter a seguinte aparência: ```php .{file:tests/RectangleTest.php} /** @@ -235,11 +234,11 @@ A execução do teste pode ser afetada por anotações no comentário do phpDoc */ ``` -As anotações dizem que o teste só deve ser executado com PHP versão 7.2 ou superior e se as extensões PHP pdo e pdo_pgsql estiverem presentes. Estas anotações são controladas por um [corredor de teste de linha de comando |running-tests], que, se as condições não forem atendidas, pula o teste e o marca com a letra `s' - pulada. Entretanto, elas não têm efeito quando o teste é executado manualmente. +As anotações fornecidas dizem que o teste deve ser executado apenas com a versão PHP 7.2 ou superior e se as extensões PHP pdo e pdo_pgsql estiverem presentes. Essas anotações são seguidas pelo [executor de testes da linha de comando|running-tests], que, caso as condições não sejam atendidas, pula o teste e o marca na saída com a letra `s` - skipped. No entanto, na execução manual do teste, elas não têm efeito. -Para uma descrição das anotações, ver [Anotações de teste |Test Annotations]. +A descrição das anotações pode ser encontrada na página [Anotações de Teste|test-annotations]. -O teste também pode ser pulado com base na própria condição com `Environment::skip()`. Por exemplo, nós pularemos este teste no Windows: +Um teste pode ser pulado com base no cumprimento de uma condição própria usando `Environment::skip()`. Por exemplo, isso pulará os testes no Windows: ```php if (defined('PHP_WINDOWS_VERSION_BUILD')) { @@ -248,10 +247,10 @@ if (defined('PHP_WINDOWS_VERSION_BUILD')) { ``` -Estrutura do diretório .[#toc-directory-structure] -================================================== +Estrutura de Diretórios +======================= -Para bibliotecas ou projetos apenas ligeiramente maiores, recomendamos dividir o diretório de testes em subdiretórios de acordo com o espaço de nomes da classe testada: +Recomendamos, para bibliotecas ou projetos um pouco maiores, dividir o diretório de testes em subdiretórios de acordo com o namespace da classe testada: ``` └── tests/ @@ -269,22 +268,22 @@ Para bibliotecas ou projetos apenas ligeiramente maiores, recomendamos dividir o └── ... ``` -Você será capaz de executar testes a partir de um único espaço de nomes, ou seja, subdiretório: +Você poderá então executar testes de um único namespace ou subdiretório: /--pre .[terminal] tester tests/NamespaceOne \-- -Estojos de borda .[#toc-edge-cases] -=================================== +Situações Especiais +=================== -Um teste que não chama nenhum método de asserção é suspeito e será avaliado como errado: +Um teste que não chama nenhum método de asserção é suspeito e será avaliado como errôneo: /--pre .[terminal] Error: This test forgets to execute an assertion. \-- -Se o teste sem fazer afirmações é realmente para ser considerado válido, ligue, por exemplo, para `Assert::true(true)`. +Se realmente um teste sem chamadas de asserção deve ser considerado válido, chame, por exemplo, `Assert::true(true)`. -Também pode ser traiçoeiro usar `exit()` e `die()` para terminar o teste com uma mensagem de erro. Por exemplo, `exit('Error in connection')` termina o teste com um código de saída 0, o que sinaliza sucesso. Use `Assert::fail('Error in connection')`. +Também pode ser traiçoeiro usar `exit()` e `die()` para encerrar um teste com uma mensagem de erro. Por exemplo, `exit('Error in connection')` encerra o teste com o código de retorno 0, o que sinaliza sucesso. Use `Assert::fail('Error in connection')`. diff --git a/tester/ro/@home.texy b/tester/ro/@home.texy index d34a06ecd1..e2e3321869 100644 --- a/tester/ro/@home.texy +++ b/tester/ro/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Nette Tester - Testare unitară plăcută în PHP}} -{{description: Nette Tester este un instrument de testare a codului PHP simplu și totuși foarte util.}} +{{maintitle: Nette Tester – testare relaxată în PHP}} +{{description: Nette Tester este un instrument simplu și totuși foarte util pentru testarea codului PHP.}} diff --git a/tester/ro/@left-menu.texy b/tester/ro/@left-menu.texy index 973eed464d..f4c2e232e4 100644 --- a/tester/ro/@left-menu.texy +++ b/tester/ro/@left-menu.texy @@ -1,8 +1,8 @@ -- [Noțiuni introductive |guide] -- [Scrierea testelor |Writing Tests] -- [Rularea testelor |Running Tests] +- [Începem cu Nette Tester |guide] +- [Scrierea testelor |writing-tests] +- [Rularea testelor |running-tests] -- [Aserțiuni |Assertions] -- [Adnotări de test |Test Annotations] +- [Aserțiuni |assertions] +- [Adnotări de test |test-annotations] - [TestCase |TestCase] -- [Ajutători |Helpers] +- [Clase ajutătoare |helpers] diff --git a/tester/ro/@menu.texy b/tester/ro/@menu.texy index 98f37f1554..e6fe5d9118 100644 --- a/tester/ro/@menu.texy +++ b/tester/ro/@menu.texy @@ -1,3 +1,3 @@ -- [Acasă |@home] -- [Documentație |Guide] +- [Introducere |@home] +- [Documentație |guide] - "GitHub .[link-external]":https://github.com/nette/tester diff --git a/tester/ro/@meta.texy b/tester/ro/@meta.texy new file mode 100644 index 0000000000..786a1fcbaa --- /dev/null +++ b/tester/ro/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentație Tester}} diff --git a/tester/ro/assertions.texy b/tester/ro/assertions.texy index ac720308aa..013db44a0b 100644 --- a/tester/ro/assertions.texy +++ b/tester/ro/assertions.texy @@ -1,35 +1,35 @@ -Afirmații +Aserțiuni ********* .[perex] -Afirmațiile sunt utilizate pentru a afirma că o valoare reală corespunde unei valori așteptate. Acestea sunt metode ale aplicației `Tester\Assert`. +Aserțiunile sunt folosite pentru a confirma că valoarea reală corespunde valorii așteptate. Acestea sunt metode ale clasei `Tester\Assert`. -Alegeți cele mai exacte aserțiuni. Este mai bună `Assert::same($a, $b)` decât `Assert::true($a === $b)`, deoarece afișează un mesaj de eroare semnificativ în caz de eșec. În cel de-al doilea caz, obținem doar `false should be true` și nu spune nimic despre conținutul variabilelor $a și $b. +Alegeți aserțiunile cele mai potrivite. Este mai bine `Assert::same($a, $b)` decât `Assert::true($a === $b)`, deoarece în caz de eșec afișează un mesaj de eroare semnificativ. În al doilea caz, doar `false should be true`, ceea ce nu ne spune nimic despre conținutul variabilelor `$a` și `$b`. -Cele mai multe aserțiuni pot avea, de asemenea, un `$description` opțional care apare în mesajul de eroare în cazul în care așteptarea eșuează. +Majoritatea aserțiunilor pot avea și o descriere opțională în parametrul `$description`, care se va afișa în mesajul de eroare dacă așteptarea eșuează. -Exemplele presupun că este definit următorul alias de clasă: +Exemplele presupun crearea unui alias: ```php use Tester\Assert; ``` -Assert::same($expected, $actual, string $description=null) .[method] --------------------------------------------------------------------- -`$expected` trebuie să fie aceeași cu `$actual`. Este același lucru cu operatorul PHP `===`. +Assert::same($expected, $actual, ?string $description=null) .[method] +--------------------------------------------------------------------- +`$expected` trebuie să fie identic cu `$actual`. Același lucru ca operatorul PHP `===`. -Assert::notSame($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------ -Opusul lui `Assert::same()`, deci este același lucru cu operatorul PHP `!==`. +Assert::notSame($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------ +Opusul `Assert::same()`, adică același lucru ca operatorul PHP `!==`. -Assert::equal($expected, $actual, string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] ------------------------------------------------------------------------------------------------------------------------- -`$expected` trebuie să fie același cu `$actual`. Spre deosebire de `Assert::same()`, identitatea obiectului, ordinea perechilor cheie => valoare în array-uri și numerele zecimale marginal diferite sunt ignorate, care pot fi modificate prin setarea `$matchIdentity` și `$matchOrder`. +Assert::equal($expected, $actual, ?string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] +------------------------------------------------------------------------------------------------------------------------- +`$expected` trebuie să fie egal cu `$actual`. Spre deosebire de `Assert::same()`, se ignoră identitatea obiectelor, ordinea perechilor cheie => valoare în array-uri și numerele zecimale marginal diferite, ceea ce poate fi schimbat prin setarea `$matchIdentity` și `$matchOrder`. -Următoarele cazuri sunt identice din punctul de vedere al `equal()`, dar nu și pentru `same()`: +Următoarele cazuri sunt egale din punctul de vedere al `equal()`, dar nu și `same()`: ```php Assert::equal(0.3, 0.1 + 0.2); @@ -40,81 +40,81 @@ Assert::equal( ); ``` -Cu toate acestea, atenție, matricea `[1, 2]` și `[2, 1]` nu sunt egale, deoarece doar ordinea valorilor diferă, nu și perechile cheie => valoare. Tabloul `[1, 2]` poate fi scris și sub forma `[0 => 1, 1 => 2]` și deci `[1 => 2, 0 => 1]` va fi considerat egal. +Atenție însă, array-urile `[1, 2]` și `[2, 1]` nu sunt egale, deoarece diferă doar ordinea valorilor, nu și a perechilor cheie => valoare. Array-ul `[1, 2]` poate fi scris și ca `[0 => 1, 1 => 2]` și, prin urmare, `[1 => 2, 0 => 1]` va fi considerat egal. -Puteți utiliza, de asemenea, așa-numitele [așteptări |#expectations] în `$expected`. +Mai departe, în `$expected` se pot folosi așa-numitele [#așteptări]. -Assert::notEqual($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------- -Opusul lui `Assert::equal()`. +Assert::notEqual($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------- +Opusul `Assert::equal()`. -Assert::contains($needle, string|array $actual, string $description=null) .[method] ------------------------------------------------------------------------------------ -Dacă `$actual` este un șir de caractere, acesta trebuie să conțină subșirul `$needle`. Dacă este o matrice, trebuie să conțină elementul `$needle` (acesta este comparat strict). +Assert::contains($needle, string|array $actual, ?string $description=null) .[method] +------------------------------------------------------------------------------------ +Dacă `$actual` este un șir, trebuie să conțină subșirul `$needle`. Dacă este un array, trebuie să conțină elementul `$needle` (se compară strict). -Assert::notContains($needle, string|array $actual, string $description=null) .[method] --------------------------------------------------------------------------------------- -Opusul lui `Assert::contains()`. +Assert::notContains($needle, string|array $actual, ?string $description=null) .[method] +--------------------------------------------------------------------------------------- +Opusul `Assert::contains()`. -Assert::hasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} -------------------------------------------------------------------------------------------------------- -`$actual` trebuie să fie o matrice și să conțină cheia `$needle`. +Assert::hasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +-------------------------------------------------------------------------------------------------------- +`$actual` trebuie să fie un array și trebuie să conțină cheia `$needle`. -Assert::notHasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} ----------------------------------------------------------------------------------------------------------- -`$actual` trebuie să fie o matrice și să nu conțină cheia `$needle`. +Assert::notHasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +----------------------------------------------------------------------------------------------------------- +`$actual` trebuie să fie un array și nu trebuie să conțină cheia `$needle`. -Assert::true($value, string $description=null) .[method] --------------------------------------------------------- -`$value` trebuie să fie `true`, deci `$value === true`. +Assert::true($value, ?string $description=null) .[method] +--------------------------------------------------------- +`$value` trebuie să fie `true`, adică `$value === true`. -Assert::truthy($value, string $description=null) .[method] ----------------------------------------------------------- -`$value` trebuie să fie adevărat, deci satisface condiția `if ($value) ...`. +Assert::truthy($value, ?string $description=null) .[method] +----------------------------------------------------------- +`$value` trebuie să fie adevărat (truthy), adică îndeplinește condiția `if ($value) ...`. -Assert::false($value, string $description=null) .[method] ---------------------------------------------------------- -`$value` trebuie să fie `false`, deci `$value === false`. +Assert::false($value, ?string $description=null) .[method] +---------------------------------------------------------- +`$value` trebuie să fie `false`, adică `$value === false`. -Assert::falsey($value, string $description=null) .[method] ----------------------------------------------------------- -`$value` trebuie să fie falsey, deci îndeplinește condiția `if (!$value) ...`. +Assert::falsey($value, ?string $description=null) .[method] +----------------------------------------------------------- +`$value` trebuie să fie fals (falsey), adică îndeplinește condiția `if (!$value) ...`. -Assert::null($value, string $description=null) .[method] --------------------------------------------------------- -`$value` trebuie să fie `null`, deci `$value === null`. +Assert::null($value, ?string $description=null) .[method] +--------------------------------------------------------- +`$value` trebuie să fie `null`, adică `$value === null`. -Assert::notNull($value, string $description=null) .[method] ------------------------------------------------------------ -`$value` nu trebuie să fie `null`, deci `$value !== null`. +Assert::notNull($value, ?string $description=null) .[method] +------------------------------------------------------------ +`$value` nu trebuie să fie `null`, adică `$value !== null`. -Assert::nan($value, string $description=null) .[method] -------------------------------------------------------- -`$value` trebuie să nu fie un număr. Utilizați numai `Assert::nan()` pentru testarea NAN. Valoarea NAN este foarte specifică, iar afirmațiile `Assert::same()` sau `Assert::equal()` se pot comporta în mod imprevizibil. +Assert::nan($value, ?string $description=null) .[method] +-------------------------------------------------------- +`$value` trebuie să fie Not a Number. Pentru testarea valorii NAN folosiți exclusiv `Assert::nan()`. Valoarea NAN este foarte specifică și aserțiunile `Assert::same()` sau `Assert::equal()` pot funcționa neașteptat. -Assert::count($count, Countable|array $value, string $description=null) .[method] ---------------------------------------------------------------------------------- -Numărul de elemente din `$value` trebuie să fie `$count`. Deci la fel ca `count($value) === $count`. +Assert::count($count, Countable|array $value, ?string $description=null) .[method] +---------------------------------------------------------------------------------- +Numărul de elemente din `$value` trebuie să fie `$count`. Adică același lucru ca `count($value) === $count`. -Assert::type(string|object $type, $value, string $description=null) .[method] ------------------------------------------------------------------------------ -`$value` trebuie să fie de un anumit tip. La fel ca `$type` putem folosi string: +Assert::type(string|object $type, $value, ?string $description=null) .[method] +------------------------------------------------------------------------------ +`$value` trebuie să fie de tipul dat. Ca `$type` putem folosi un șir: - `array` -- `list` - matrice indexată în ordine crescătoare a cheilor numerice de la zero. +- `list` - array indexat după o serie ascendentă de chei numerice începând de la zero - `bool` - `callable` - `float` @@ -124,163 +124,163 @@ Assert::type(string|object $type, $value, string $description=null) .[method] - `resource` - `scalar` - `string` -- numele clasei sau obiectul direct, atunci trebuie să treacă `$value instanceof $type` +- numele clasei sau direct obiectul, atunci `$value` trebuie să fie `instanceof $type` -Assert::exception(callable $callable, string $class, string $message=null, $code=null) .[method] ------------------------------------------------------------------------------------------------- -La invocarea `$callable` trebuie să se arunce o excepție de instanță `$class`. Dacă se trece `$message`, mesajul excepției trebuie să [corespundă |#assert-match]. Iar dacă se trece `$code`, codul excepției trebuie să fie același. +Assert::exception(callable $callable, string $class, ?string $message=null, $code=null) .[method] +------------------------------------------------------------------------------------------------- +La apelarea `$callable` trebuie să fie aruncată o excepție de clasa `$class`. Dacă specificăm `$message`, trebuie să [corespundă modelului |#Assert::match] și mesajul excepției, iar dacă specificăm `$code`, trebuie să se potrivească strict și codurile. -De exemplu, acest test eșuează deoarece mesajul excepției nu se potrivește: +Următorul test va eșua, deoarece mesajul excepției nu corespunde: ```php Assert::exception( - fn() => throw new App\InvalidValueException('Zero value'), + fn() => throw new App\InvalidValueException('Valoare zero'), App\InvalidValueException::class, - 'Value is to low', + 'Valoarea este prea mică', ); ``` -Site-ul `Assert::exception()` returnează o excepție lansată, astfel încât puteți testa o excepție imbricata. +`Assert::exception()` returnează excepția aruncată, astfel se poate testa și o excepție imbricată. ```php $e = Assert::exception( - fn() => throw new MyException('Something is wrong', 0, new RuntimeException), + fn() => throw new MyException('Ceva este în neregulă', 0, new RuntimeException), MyException::class, - 'Something is wrong', + 'Ceva este în neregulă', ); Assert::type(RuntimeException::class, $e->getPrevious()); ``` -Assert::error(string $callable, int|string|array $type, string $message=null) .[method] ---------------------------------------------------------------------------------------- -Verifică dacă invocarea `$callable` generează erorile așteptate (adică avertismente, notificări etc.). Ca `$type` se specifică una dintre constantele `E_...`, de exemplu `E_WARNING`. Iar dacă se trece `$message`, mesajul de eroare trebuie să [corespundă |#assert-match] și el modelului. De exemplu: +Assert::error(string $callable, int|string|array $type, ?string $message=null) .[method] +---------------------------------------------------------------------------------------- +Verifică dacă funcția `$callable` a generat erorile așteptate (adică avertismente, notificări etc.). Ca `$type` specificăm una dintre constantele `E_...`, de exemplu `E_WARNING`. Și dacă specificăm `$message`, trebuie să [corespundă modelului |#Assert::match] și mesajul de eroare. De exemplu: ```php Assert::error( fn() => $i++, E_NOTICE, - 'Undefined variable: i', + 'Variabilă nedefinită: i', ); ``` -Dacă callback-ul generează mai multe erori, trebuie să le așteptăm pe toate în ordinea exactă. În acest caz, trecem matricea în `$type`: +Dacă callback-ul generează mai multe erori, trebuie să le așteptăm pe toate în ordinea exactă. În acest caz, transmitem în `$type` un array: ```php Assert::error(function () { $a++; $b++; }, [ - [E_NOTICE, 'Undefined variable: a'], - [E_NOTICE, 'Undefined variable: b'], + [E_NOTICE, 'Variabilă nedefinită: a'], + [E_NOTICE, 'Variabilă nedefinită: b'], ]); ``` .[note] -Dacă `$type` este numele clasei, această afirmație se comportă la fel ca `Assert::exception()`. +Dacă specificați ca `$type` numele unei clase, se comportă la fel ca `Assert::exception()`. Assert::noError(callable $callable) .[method] --------------------------------------------- -Verifică dacă funcția `$callable` nu aruncă niciun avertisment/notificare/error sau excepție PHP. Este utilă pentru testarea unei bucăți de cod în care nu există nicio altă afirmație. +Verifică dacă funcția `$callable` nu a generat niciun avertisment, eroare sau excepție. Este util pentru testarea bucăților de cod unde nu există nicio altă aserțiune. -Assert::match(string $pattern, $actual, string $description=null) .[method] ---------------------------------------------------------------------------- -`$actual` trebuie să se potrivească cu `$pattern`. Putem utiliza două variante de tipare: expresii regulate sau caractere sălbatice. +Assert::match(string $pattern, $actual, ?string $description=null) .[method] +---------------------------------------------------------------------------- +`$actual` trebuie să corespundă modelului `$pattern`. Putem folosi două variante de modele: expresii regulate sau substituenți. -Dacă trecem o expresie regulată ca `$pattern`, trebuie să folosim `~` or `#` pentru a o delimita. Alte delimitatoare nu sunt acceptate. De exemplu, testul în care `$var` trebuie să conțină numai cifre hexazecimale: +Dacă transmitem ca `$pattern` o expresie regulată, pentru delimitarea sa trebuie să folosim `~` sau `#`, alți delimitatori nu sunt suportați. De exemplu, testul în care `$var` trebuie să conțină doar cifre hexazecimale: ```php Assert::match('#^[0-9a-f]$#i', $var); ``` -Cealaltă variantă este similară cu compararea șirurilor de caractere, dar putem folosi unele caractere wild în `$pattern`: - -- `%a%` unul sau mai multe din orice, cu excepția caracterelor de sfârșit de linie -- `%a?%` zero sau mai multe de orice, cu excepția caracterelor de sfârșit de linie -- `%A%` unul sau mai multe din orice, inclusiv caracterele de sfârșit de linie -- `%A?%` zero sau mai multe de orice, inclusiv caracterele de sfârșit de linie -- `%s%` unul sau mai multe caractere de spațiu alb, cu excepția caracterelor de sfârșit de rând -- `%s?%` zero sau mai multe caractere de spațiu alb, cu excepția caracterelor de sfârșit de rând -- `%S%` unul sau mai multe caractere, cu excepția spațiului alb -- `%S?%` zero sau mai multe caractere, cu excepția spațiului alb -- `%c%` un singur caracter de orice fel (cu excepția celor de sfârșit de rând) +A doua variantă este similară cu compararea obișnuită a șirurilor, dar în `$pattern` putem folosi diferiți substituenți: + +- `%a%` unul sau mai multe caractere, cu excepția caracterelor de sfârșit de linie +- `%a?%` zero sau mai multe caractere, cu excepția caracterelor de sfârșit de linie +- `%A%` unul sau mai multe caractere, inclusiv caracterele de sfârșit de linie +- `%A?%` zero sau mai multe caractere, inclusiv caracterele de sfârșit de linie +- `%s%` unul sau mai multe caractere spațiu alb, cu excepția caracterelor de sfârșit de linie +- `%s?%` zero sau mai multe caractere spațiu alb, cu excepția caracterelor de sfârșit de linie +- `%S%` unul sau mai multe caractere, cu excepția caracterelor spațiu alb +- `%S?%` zero sau mai multe caractere, cu excepția caracterelor spațiu alb +- `%c%` orice caracter unic, cu excepția caracterului de sfârșit de linie - `%d%` una sau mai multe cifre - `%d?%` zero sau mai multe cifre - `%i%` valoare întreagă cu semn -- `%f%` număr în virgulă mobilă -- `%h%` una sau mai multe cifre HEX +- `%f%` număr zecimal +- `%h%` una sau mai multe cifre hexazecimale - `%w%` unul sau mai multe caractere alfanumerice -- `%%` un caracter % +- `%%` caracterul % Exemple: ```php -# Again, hexadecimal number test +# Din nou test pentru număr hexazecimal Assert::match('%h%', $var); -# Generalized path to file and line number -Assert::match('Error in file %a% on line %i%', $errorMessage); +# Generalizarea căii către fișier și a numărului liniei +Assert::match('Eroare în fișierul %a% la linia %i%', $errorMessage); ``` -Assert::matchFile(string $file, $actual, string $description=null) .[method] ----------------------------------------------------------------------------- -Afirmația este identică cu [Assert::match() |#assert-match], dar modelul este încărcat din `$file`. Este utilă pentru testarea șirurilor foarte lungi. Fișierul de test stă în picioare lizibil. +Assert::matchFile(string $file, $actual, ?string $description=null) .[method] +----------------------------------------------------------------------------- +Aserțiunea este identică cu [#Assert::match()], dar modelul se încarcă din fișierul `$file`. Acest lucru este util pentru testarea șirurilor foarte lungi. Fișierul cu testul rămâne lizibil. Assert::fail(string $message, $actual=null, $expected=null) .[method] --------------------------------------------------------------------- -Această afirmație eșuează întotdeauna. Este doar la îndemână. Putem trece opțional valorile așteptate și cele reale. +Această aserțiune eșuează întotdeauna. Uneori este pur și simplu util. Opțional, putem specifica și valoarea așteptată și cea reală. -Așteptări .[#toc-expectations] ------------------------------- -Dacă dorim să comparăm structuri mai complexe, cu elemente neconstante, afirmațiile de mai sus ar putea să nu fie suficiente. De exemplu, testăm o metodă care creează un nou utilizator și îi returnează atributele sub forma unui tablou. Nu cunoaștem valoarea hash a parolei, dar știm că aceasta trebuie să fie un șir hexazecimal. Și singurul lucru pe care îl știm despre următorul element este că trebuie să fie un obiect `DateTime`. +Așteptări +--------- +Când dorim să comparăm structuri mai complexe cu elemente neconstante, aserțiunile de mai sus s-ar putea să nu fie suficiente. De exemplu, testăm o metodă care creează un nou utilizator și returnează atributele sale ca array. Nu cunoaștem valoarea hash-ului parolei, dar știm că trebuie să fie un șir hexazecimal. Și despre un alt element știm doar că trebuie să fie un obiect `DateTime`. -În aceste cazuri, putem utiliza `Tester\Expect` în interiorul parametrului `$expected` al metodelor `Assert::equal()` și `Assert::notEqual()`, care poate fi folosit pentru a descrie cu ușurință structura. +În aceste situații putem folosi `Tester\Expect` în interiorul parametrului `$expected` al metodelor `Assert::equal()` și `Assert::notEqual()`, cu ajutorul cărora structura poate fi descrisă ușor. ```php use Tester\Expect; Assert::equal([ - 'id' => Expect::type('int'), # we expect an integer + 'id' => Expect::type('int'), # așteptăm un număr întreg 'username' => 'milo', - 'password' => Expect::match('%h%'), # we expect a string matching pattern - 'created_at' => Expect::type(DateTime::class), # we expect an instance of the class + 'password' => Expect::match('%h%'), # așteptăm un șir care corespunde modelului + 'created_at' => Expect::type(DateTime::class), # așteptăm o instanță a clasei ], User::create(123, 'milo', 'RandomPaSsWoRd')); ``` -Cu `Expect`, putem face aproape aceleași afirmații ca și cu `Assert`. Astfel, avem metode precum `Expect::same()`, `Expect::match()`, `Expect::count()`, etc. În plus, putem să le înlănțuim astfel: +Cu `Expect` putem efectua aproape aceleași aserțiuni ca și cu `Assert`. Adică avem la dispoziție metodele `Expect::same()`, `Expect::match()`, `Expect::count()` etc. În plus, le putem înlănțui: ```php -Expect::type(MyIterator::class)->andCount(5); # we expect MyIterator and items count is 5 +Expect::type(MyIterator::class)->andCount(5); # așteptăm MyIterator și numărul de elemente 5 ``` -Sau, putem scrie propriile gestionari de afirmații. +Sau putem scrie proprii handleri de aserțiuni. ```php Expect::that(function ($value) { - # return false if expectation fails + # returnăm false dacă așteptarea eșuează }); ``` -Investigarea aserțiunilor eșuate .[#toc-failed-assertions-investigation] ------------------------------------------------------------------------- -Tester arată unde se află eroarea atunci când o aserțiune eșuează. Atunci când comparăm structuri complexe, Tester creează descărcări ale valorilor comparate și le salvează în directorul `output`. De exemplu, atunci când testul imaginar `Arrays.recursive.phpt` eșuează, dumps-urile vor fi salvate după cum urmează: +Examinarea aserțiunilor eșuate +------------------------------ +Când o aserțiune eșuează, Tester afișează unde este greșeala. Dacă comparăm structuri mai complexe, Tester creează dump-uri ale valorilor comparate și le salvează în directorul `output`. De exemplu, la eșecul testului fictiv `Arrays.recursive.phpt`, dump-urile vor fi salvate astfel: ``` app/ └── tests/ ├── output/ - │ ├── Arrays.recursive.actual # actual value - │ └── Arrays.recursive.expected # expected value + │ ├── Arrays.recursive.actual # valoarea actuală + │ └── Arrays.recursive.expected # valoarea așteptată │ - └── Arrays.recursive.phpt # failing test + └── Arrays.recursive.phpt # testul eșuat ``` -Putem schimba numele directorului prin `Tester\Dumper::$dumpDir`. +Numele directorului poate fi schimbat prin `Tester\Dumper::$dumpDir`. diff --git a/tester/ro/guide.texy b/tester/ro/guide.texy index da369c5d74..4afff18e8a 100644 --- a/tester/ro/guide.texy +++ b/tester/ro/guide.texy @@ -1,17 +1,17 @@ -Noțiuni de bază pentru a începe cu Tester -***************************************** +Începeți cu Nette Tester +************************
                                                                                                                            -Chiar și programatorii buni fac greșeli. Diferența dintre un programator bun și unul rău este că cel bun o va face o singură dată, iar data viitoare o va detecta cu ajutorul testelor automate. +Chiar și programatorii buni fac greșeli. Diferența dintre un programator bun și unul rău este că cel bun o face doar o dată și data viitoare o detectează folosind teste automatizate. -- "Cel care nu testează este condamnat să își repete propriile greșeli." (proverb înțelept) -- "Când scăpăm de o eroare, apare alta." (Legea lui Murphy) -- "Ori de câte ori ești tentat să tipărești o afirmație, scrie-o în schimb ca pe un test." (Martin Fowler) +- "Cine nu testează, este condamnat să-și repete greșelile." (proverb) +- "De îndată ce ne debarasăm de o greșeală, apare alta." (Legea lui Murphy) +- "Oricând aveți impulsul de a afișa o variabilă pe ecran, scrieți mai degrabă un test." (Martin Fowler)
                                                                                                                            -Ați scris vreodată următorul cod în PHP? +Ați scris vreodată în PHP un cod similar? ```php $obj = new MyClass; @@ -20,40 +20,40 @@ $result = $obj->process($input); var_dump($result); ``` -Deci, ați aruncat vreodată rezultatul apelului unei funcții doar pentru a verifica cu ochiul liber dacă returnează ceea ce ar trebui să returneze? Cu siguranță că o faceți de multe ori pe zi. Cu mâna pe inimă, dacă totul funcționează, ștergeți acest cod și vă așteptați ca clasa să nu fie stricată în viitor? Legea lui Murphy garantează contrariul :-) +Adică ați afișat rezultatul apelului funcției doar pentru a verifica cu ochiul liber dacă returnează ceea ce trebuie? Sigur faceți asta de multe ori pe zi. Mâna pe inimă: în cazul în care totul funcționează corect, ștergeți acest cod? Vă așteptați ca clasa să nu se strice în viitor? Legile lui Murphy garantează contrariul :-) -De fapt, dumneavoastră ați scris testul. Acesta are nevoie de o ușoară modificare pentru a nu necesita inspecția noastră, ci pur și simplu pentru a se putea verifica singur. Iar dacă nu l-ați șters, am putea să-l rulăm oricând în viitor pentru a verifica dacă totul funcționează în continuare așa cum trebuie. Este posibil să creați o cantitate mare de astfel de teste de-a lungul timpului, așa că ar fi bine dacă am putea să le rulăm automat. +În esență, ați scris un test. Trebuie doar să îl modificați ușor, astfel încât să nu necesite verificare vizuală, ci să se verifice singur. Și dacă nu ștergeți testul, îl puteți rula oricând în viitor și verifica dacă totul funcționează în continuare așa cum trebuie. Cu timpul veți crea un număr mare de astfel de teste, deci ar fi util să le rulați automatizat. -Iar Nette Tester vă ajută exact în acest sens. +Și cu toate acestea vă ajută tocmai Nette Tester. -Ce face ca Tester să fie unic? .[#toc-what-makes-tester-unique] -=============================================================== +Prin ce este unic Tester? +========================= -Scrierea testelor pentru Nette Tester este unică prin faptul că **fiecare test este un script PHP standard care poate fi rulat de sine stătător.** +Scrierea testelor pentru Nette Tester este unică prin faptul că **fiecare test este un script PHP obișnuit, care poate fi rulat independent.** -Astfel, atunci când scrieți un test, îl puteți rula pur și simplu pentru a vedea dacă există o eroare de programare. Dacă funcționează corect. Dacă nu, puteți trece cu ușurință prin program în IDE-ul dvs. și căutați o eroare. Puteți chiar să-l deschideți într-un browser. +Deci, când scrieți un test, îl puteți rula simplu și afla dacă, de exemplu, nu conține o eroare de programare. Dacă funcționează corect. Dacă nu, îl puteți depana ușor pas cu pas în IDE-ul dvs. și căuta eroarea. Îl puteți chiar deschide în browser. -Și cel mai important - prin rularea acestuia, veți efectua testul. Veți afla imediat dacă a trecut sau a eșuat. Cum? Să ne arătăm aici. Să scriem un test trivial pentru utilizarea matricei PHP și să-l salvăm în fișierul `ArrayTest.php`: +Și, mai presus de toate - prin rularea sa, efectuați testul. Aflați imediat dacă a trecut sau a eșuat. Cum? Să arătăm. Scriem un test trivial pentru lucrul cu array-uri PHP și îl salvăm în fișierul `ArrayTest.php`: ```php .{file:ArrayTest.php} OK \-- -Încercați să schimbați declarația în `Assert::contains('XXX', $stack);` în test și urmăriți ce se întâmplă la execuție: +Încercați să schimbați în test afirmația într-una falsă `Assert::contains('XXX', $stack);` și urmăriți ce se întâmplă la rulare: /--pre .[terminal] $ php ArrayTest.php @@ -73,53 +73,53 @@ $ php ArrayTest.php FAILURE \-- -Continuăm despre scriere în capitolul [Scrierea testelor |Writing Tests]. +Continuăm despre scriere în capitolul [Scrierea testelor|writing-tests]. -Instalare și cerințe .[#toc-installation-and-requirements] -========================================================== +Instalare și cerințe +==================== -Versiunea minimă de PHP necesară pentru Tester este 7.1 (pentru mai multe detalii, consultați tabelul cu [versiunile PHP acceptate |#supported PHP versions] ). Modalitatea preferată de instalare este prin [Composer |best-practices:composer]: +Versiunea minimă de PHP necesară pentru Tester este 7.1 (mai detaliat în tabelul [#versiuni PHP suportate]). Metoda preferată de instalare este prin [Composer |best-practices:composer]: /--pre .[terminal] composer require --dev nette/tester \-- -Încercați să rulați Nette Tester din linia de comandă (fără argumente va afișa doar un rezumat de ajutor): +Încercați să rulați Nette Tester din linia de comandă (fără parametri va afișa doar ajutorul): /--pre .[terminal] vendor/bin/tester \-- -Rularea testelor .[#toc-running-tests] -====================================== +Rularea testelor +================ -Pe măsură ce aplicația noastră crește, numărul de teste crește odată cu ea. Nu ar fi practic să executăm testele unul câte unul. Prin urmare, Tester dispune de un rulator de teste în masă, pe care îl invocăm din linia de comandă. Parametrul este directorul în care se află testele. Punctul indică directorul curent. +Pe măsură ce aplicația crește, numărul de teste crește odată cu ea. Nu ar fi practic să rulăm testele unul câte unul. De aceea, Tester dispune de un rulator de teste în masă, pe care îl apelăm din linia de comandă. Ca parametru specificăm directorul în care se află testele. Punctul înseamnă directorul curent. /--pre .[terminal] vendor/bin/tester . \-- -Executantul Nette Tester caută în directorul specificat și în toate subdirectoarele și caută testele, care sunt fișierele `*.phpt` și `*Test.php`. Acesta va găsi, de asemenea, testul nostru `ArrayTest.php`, deoarece se potrivește cu masca. +Rulatorul de teste va căuta în directorul specificat și în toate subdirectoarele și va găsi testele, care sunt fișiere `*.phpt` și `*Test.php`. Va găsi astfel și testul nostru `ArrayTest.php`, deoarece corespunde măștii. -Apoi începe testarea. Rulează fiecare test ca un nou proces PHP, astfel încât acesta se execută complet izolat de celelalte. Se execută în paralel în mai multe fire de execuție, ceea ce îl face extrem de rapid. Și rulează mai întâi testele care au eșuat în timpul rulării anterioare, astfel încât veți ști imediat dacă ați remediat eroarea. +Apoi va începe testarea. Fiecare test îl rulează ca un nou proces PHP, astfel încât se desfășoară complet izolat de celelalte. Le rulează în paralel în mai multe fire de execuție și datorită acestui fapt este extrem de rapid. Și rulează mai întâi testele care au eșuat la rularea anterioară, astfel încât aflați imediat dacă ați reușit să reparați eroarea. -Pentru fiecare test efectuat, executorul tipărește un caracter pentru a indica progresul: +În timpul executării testelor, Tester afișează continuu rezultatele pe terminal sub formă de caractere: -- . - test trecut -- s - testul a fost sărit -- F - testul a eșuat +- . – testul a trecut +- s – testul a fost omis (skipped) +- F – testul a eșuat (failed) -Rezultatul poate arăta astfel: +Ieșirea poate arăta astfel: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +PHP 8.3.2 (cli) | php -n | 8 threads ........s................F......... @@ -134,42 +134,42 @@ PHP 7.4.8 (cli) | php -n | 8 threads Au fost rulate 35 de teste, unul a eșuat, unul a fost omis. -Continuăm în capitolul [Rularea testelor |Running tests]. +Continuăm în capitolul [Rularea testelor|running-tests]. -Modul de supraveghere .[#toc-watch-mode] -======================================== +Modul Watch +=========== -Refaceți codul? Sau chiar dezvoltați în conformitate cu metodologia TDD (Test Driven Development)? Atunci vă va plăcea modul de supraveghere. Tester monitorizează codurile sursă și se execută singur atunci când sunt modificate. +Refactorizați codul? Sau chiar dezvoltați conform metodologiei TDD (Test Driven Development)? Atunci vă va plăcea modul watch. Tester în acest mod urmărește codurile sursă și la modificare se rulează singur. -În timpul dezvoltării, aveți un terminal în colțul monitorului, unde bara de stare verde se aprinde la dumneavoastră, iar când devine brusc roșie, știți că tocmai ați făcut ceva nedorit. Este de fapt un joc grozav în care programezi și încerci să te ții de culoare. +La dezvoltare aveți deci în colțul monitorului un terminal, unde vă luminează o bară de stare verde, și când se schimbă brusc în roșu, știți că tocmai ați făcut ceva nu tocmai bine. Este de fapt un joc grozav, în care programați și încercați să mențineți culoarea. -Modul Watch se pornește cu ajutorul parametrului [--watch |running-tests#w-watch-path]. +Modul watch se pornește cu parametrul [--watch |running-tests#-w --watch path]. -Rapoarte CodeCoverage .[#toc-codecoverage-reports] -================================================== +Rapoarte CodeCoverage +===================== -Testerul poate genera rapoarte cu o imagine de ansamblu a cantității de cod sursă pe care o acoperă testele. Raportul poate fi fie în format HTML lizibil pentru oameni, fie în format Clover XML pentru procesarea ulterioară de către mașină. +Tester poate genera rapoarte cu o prezentare generală a cât de mult cod sursă acoperă testele. Raportul poate fi fie în format HTML lizibil pentru oameni, fie Clover XML pentru prelucrare automată ulterioară. -Consultați "exemplul de raport HTML":https://files.nette.org/tester/coverage.html cu acoperirea codului. +Consultați "exemplul de raport HTML":attachment:coverage.html cu acoperirea codului. -Versiuni PHP acceptate .[#toc-supported-php-versions] -===================================================== +Versiuni PHP suportate +====================== -| versiune | compatibil cu PHP +| versiune | compatibil cu PHP |------------------|------------------- -| Tester 2.5 | PHP 8.0 - 8.2 -| Tester 2.4 | PHP 7.2 - 8.2 -| Tester 2.3 | PHP 7.1 - 8.0 -| Tester 2.1 - 2.2 | PHP 7.1 - 7.3 -| Tester 2.0 | PHP 5.6 - 7.3 -| Tester 1.7 | PHP 5.3 - 7.3 + HHVM 3.3+ -| Tester 1.6 | PHP 5.3 - 7.0 + HHVM -| Tester 1.3 - 1.5 | PHP 5.3 - 5.6 + HHVM -| Tester 0.9 - 1.2 | PHP 5.3 - 5.6 - -Se aplică la cele mai recente versiuni de patch-uri. - -Până la versiunea 1.7 Tester a acceptat [HHVM |https://hhvm.com] 3.3.0 sau mai nou (utilizând `tester -p hhvm`). Suportul a fost abandonat începând cu Tester 2.0. Utilizarea era simplă: +| Tester 2.5 | PHP 8.0 – 8.3 +| Tester 2.4 | PHP 7.2 – 8.2 +| Tester 2.3 | PHP 7.1 – 8.0 +| Tester 2.1 – 2.2 | PHP 7.1 – 7.3 +| Tester 2.0 | PHP 5.6 – 7.3 +| Tester 1.7 | PHP 5.3 – 7.3 + HHVM 3.3+ +| Tester 1.6 | PHP 5.3 – 7.0 + HHVM +| Tester 1.3 – 1.5 | PHP 5.3 – 5.6 + HHVM +| Tester 0.9 – 1.2 | PHP 5.3 – 5.6 + +Valabil pentru ultima versiune patch. + +Tester până la versiunea 1.7 a suportat și [HHVM |https://hhvm.com] 3.3.0 sau mai recent (prin `tester -p hhvm`). Suportul a fost întrerupt începând cu versiunea Tester 2.0. diff --git a/tester/ro/helpers.texy b/tester/ro/helpers.texy index 9a53f54e34..252ebbc450 100644 --- a/tester/ro/helpers.texy +++ b/tester/ro/helpers.texy @@ -1,31 +1,45 @@ -Ajutoarele -********** +Clase ajutătoare +**************** -DomQuery .[#toc-domquery] -------------------------- -`Tester\DomQuery` este o clasă care extinde `SimpleXMLElement` cu metode care facilitează testarea conținutului HTML sau XML. +DomQuery +-------- +`Tester\DomQuery` este o clasă care extinde `SimpleXMLElement` cu căutare ușoară în HTML sau XML folosind selectori CSS. ```php -# let's have an HTML document in $html that we load -$dom = Tester\DomQuery::fromHtml($html); - -# we can test the presence of elements using CSS selectors -Assert::true($dom->has('form#registration')); -Assert::true($dom->has('input[name="username"]')); -Assert::true($dom->has('input[type="submit"]')); - -# or select elements as array of DomQuery -$elems = $dom->find('input[data-autocomplete]'); +# crearea DomQuery dintr-un șir HTML +$dom = Tester\DomQuery::fromHtml(' +
                                                                                                                            +

                                                                                                                            Titlu

                                                                                                                            +
                                                                                                                            Text
                                                                                                                            +
                                                                                                                            +'); + +# testarea existenței elementelor folosind selectori CSS +Assert::true($dom->has('article.post')); +Assert::true($dom->has('h1')); + +# găsirea elementelor ca array de obiecte DomQuery +$headings = $dom->find('h1'); +Assert::same('Titlu', (string) $headings[0]); + +# testarea dacă elementul corespunde selectorului (de la versiunea 2.5.3) +$content = $dom->find('.content')[0]; +Assert::true($content->matches('div')); +Assert::false($content->matches('p')); + +# găsirea celui mai apropiat strămoș care corespunde selectorului (de la 2.5.5) +$article = $content->closest('.post'); +Assert::true($article->matches('article')); ``` -FileMock .[#toc-filemock] -------------------------- -`Tester\FileMock` emulează fișiere în memorie pentru a vă ajuta să testați un cod care utilizează funcții precum `fopen()`, `file_get_contents()` sau `parse_ini_file()`. De exemplu: +FileMock +-------- +`Tester\FileMock` emulează fișiere în memorie și facilitează astfel testarea codului care folosește funcții precum `fopen()`, `file_get_contents()`, `parse_ini_file()` și altele similare. Exemplu de utilizare: ```php -# Tested class +# Clasa testată class Logger { public function __construct( @@ -39,21 +53,21 @@ class Logger } } -# New empty file +# Fișier nou gol $file = Tester\FileMock::create(''); $logger = new Logger($file); $logger->log('Login'); $logger->log('Logout'); -# Created content testing +# Testăm conținutul creat Assert::same("Login\nLogout\n", file_get_contents($file)); ``` Assert::with() .[filter] ------------------------ -Aceasta nu este o afirmație, ci un ajutor pentru testarea metodelor private și a obiectelor de proprietate. +Nu este o aserțiune, ci un ajutor pentru testarea metodelor și proprietăților private ale obiectelor. ```php class Entity @@ -65,17 +79,17 @@ class Entity $ent = new Entity; Assert::with($ent, function () { - Assert::true($this->enabled); // accesibil private $ent->enabled + Assert::true($this->enabled); // proprietatea privată $ent->enabled este accesibilă }); ``` Helpers::purge() .[filter] -------------------------- -Metoda `purge()` creează directorul specificat și, dacă acesta există deja, șterge întregul său conținut. Această metodă este utilă pentru crearea de directoare temporare. De exemplu, în `tests/bootstrap.php`: +Metoda `purge()` creează directorul specificat și, dacă există deja, șterge întregul său conținut. Este utilă pentru crearea unui director temporar. De exemplu, în `tests/bootstrap.php`: ```php -@mkdir(__DIR__ . '/tmp'); # @ - directory may already exist +@mkdir(__DIR__ . '/tmp'); # @ - directorul poate exista deja define('TempDir', __DIR__ . '/tmp/' . getmypid()); Tester\Helpers::purge(TempDir); @@ -84,25 +98,25 @@ Tester\Helpers::purge(TempDir); Environment::lock() .[filter] ----------------------------- -Testele se execută în paralel. Uneori este nevoie să nu suprapunem rularea testelor. De obicei, testele bazelor de date trebuie să pregătească conținutul bazei de date și nu trebuie să fie perturbate de nimic în timpul rulării testului. În aceste cazuri, folosim `Tester\Environment::lock($name, $dir)`: +Testele se rulează în paralel. Uneori, însă, avem nevoie ca rularea testelor să nu se suprapună. Tipic la testele de baze de date, este necesar ca un test să pregătească conținutul bazei de date și alt test să nu intervină în baza de date pe durata rulării sale. În aceste teste folosim `Tester\Environment::lock($name, $dir)`: ```php Tester\Environment::lock('database', __DIR__ . '/tmp'); ``` -Primul argument este un nume de blocare. Al doilea este o cale către directorul în care se salvează blocajul. Testul care obține primul blocajul se execută. Celelalte teste trebuie să aștepte până la finalizarea acestuia. +Primul parametru este numele blocării, al doilea este calea către directorul pentru stocarea blocării. Testul care obține blocarea primul rulează, celelalte teste trebuie să aștepte finalizarea sa. Environment::bypassFinals() .[filter] ------------------------------------- -Clasele sau metodele marcate ca `final` sunt greu de testat. Apelarea `Tester\Environment::bypassFinals()` într-un început de test determină eliminarea cuvintelor cheie `final` în timpul încărcării codului. +Clasele sau metodele marcate ca `final` sunt dificil de testat. Apelul `Tester\Environment::bypassFinals()` la începutul testului face ca cuvintele cheie `final` să fie omise în timpul încărcării codului. ```php require __DIR__ . '/bootstrap.php'; Tester\Environment::bypassFinals(); -class MyClass extends NormallyFinalClass # <-- NormallyFinalClass is not final anymore +class MyClass extends NormallyFinalClass # <-- NormallyFinalClass nu mai este final { // ... } @@ -111,18 +125,18 @@ class MyClass extends NormallyFinalClass # <-- NormallyFinalClass is not final Environment::setup() .[filter] ------------------------------ -- îmbunătățește lizibilitatea descărcării erorilor (colorare inclusă), în caz contrar, se tipărește implicit stack trace-ul PHP -- permite verificarea faptului că aserțiunile au fost apelate în test, în caz contrar, testele fără aserțiuni (de exemplu, uitate) trec și ele -- pornește automat colectorul de acoperire a codului atunci când se utilizează `--coverage` (descris mai târziu) -- tipărește starea OK sau FAILURE la sfârșitul scriptului. +- îmbunătățește lizibilitatea afișării erorilor (inclusiv colorarea), altfel este afișat stack trace-ul PHP implicit +- activează verificarea că au fost apelate aserțiuni în test, altfel un test fără aserțiuni (de exemplu, uitate) trece de asemenea +- la utilizarea `--coverage`, pornește automat colectarea informațiilor despre codul rulat (descris mai jos) +- afișează starea OK sau FAILURE la sfârșitul scriptului Environment::setupFunctions() .[filter]{data-version:2.5} --------------------------------------------------------- -Creează funcțiile globale `test()`, `setUp()` și `tearDown()` în care puteți împărți testele. +Creează funcțiile globale `test()`, `testException()`, `setUp()` și `tearDown()`, în care puteți structura testele. ```php -test('test description', function () { +test('descrierea testului', function () { Assert::same(123, foo()); Assert::false(bar()); // ... @@ -132,21 +146,21 @@ test('test description', function () { Environment::VariableRunner .[filter] ------------------------------------- -Vă permite să aflați dacă testul a fost executat direct sau prin intermediul Tester. +Permite să se determine dacă testul a fost rulat direct sau prin intermediul Testerului. ```php if (getenv(Tester\Environment::VariableRunner)) { - # run by Tester + # rulat de Tester } else { - # another way + # rulat altfel } ``` Environment::VariableThread .[filter] ------------------------------------- -Tester execută testele în paralel într-un anumit număr de fire de execuție. Vom găsi un număr de fire într-o variabilă de mediu atunci când suntem interesați: +Tester rulează testele în paralel într-un număr specificat de fire de execuție. Dacă ne interesează numărul firului de execuție, îl aflăm din variabila de mediu: ```php -echo "I'm running in a thread number " . getenv(Tester\Environment::VariableThread); +echo "Rulez în firul de execuție numărul " . getenv(Tester\Environment::VariableThread); ``` diff --git a/tester/ro/running-tests.texy b/tester/ro/running-tests.texy index 5563f703e0..37e379bb83 100644 --- a/tester/ro/running-tests.texy +++ b/tester/ro/running-tests.texy @@ -1,55 +1,55 @@ -Executarea testelor -******************* +Rularea testelor +**************** .[perex] -Partea cea mai vizibilă a Nette Tester este programul de execuție a testelor din linia de comandă. Acesta este extrem de rapid și robust, deoarece pornește automat toate testele ca procese separate, în paralel, în mai multe fire de execuție. De asemenea, poate rula singur în așa-numitul mod de supraveghere. +Partea cea mai vizibilă a Nette Tester este rulatorul de teste din linia de comandă. Este neobișnuit de rapid și robust, deoarece rulează automat toate testele ca procese separate și în paralel în mai multe fire de execuție. De asemenea, se poate rula singur în așa-numitul mod watch. -Executantul de teste Nette Tester este invocat din linia de comandă. Ca parametru, vom trece directorul de testare. Pentru directorul curent este suficient să introduceți un punct: +Rulatorul de teste îl apelăm din linia de comandă. Ca parametru specificăm directorul cu testele. Pentru directorul curent este suficient să specificăm un punct: /--pre .[terminal] vendor/bin/tester . \-- -Atunci când este invocat, Test Runner va scana directorul specificat și toate subdirectoarele și va căuta teste, care sunt fișierele `*.phpt` și `*Test.php`. De asemenea, citește și evaluează [adnotările |test-annotations] acestora pentru a ști care dintre ele și cum să le execute. +Rulatorul de teste caută în directorul specificat și în toate subdirectoarele și găsește testele, care sunt fișiere `*.phpt` și `*Test.php`. În același timp, citește și evaluează [adnotările |test-annotations] lor, pentru a ști care dintre ele și cum să le ruleze. -Apoi va executa testele. Pentru fiecare test efectuat, runner tipărește un caracter pentru a indica progresul: +Apoi rulează testele. În timpul executării testelor, afișează continuu rezultatele pe terminal sub formă de caractere: -- ... - test trecut -- s - testul a fost sărit -- F - testul a eșuat +- . – testul a trecut +- s – testul a fost omis (skipped) +- F – testul a eșuat (failed) -Rezultatul poate arăta astfel: +Ieșirea poate arăta, de exemplu, astfel: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +PHP 8.3.2 (cli) | php -n | 8 threads ........s.......................... OK (35 tests, 1 skipped, 1.7 seconds) \-- -Când îl rulați din nou, acesta rulează mai întâi testele care au eșuat în timpul rulării anterioare, astfel încât veți ști imediat dacă ați remediat eroarea. +La rularea repetată, execută mai întâi testele care au eșuat la rularea anterioară, astfel încât aflați imediat dacă ați reușit să reparați eroarea. -Codul de ieșire al testerului este zero dacă niciunul nu dă greș. În caz contrar, este diferit de zero. +Dacă niciun test nu eșuează, codul de retur al Testerului este zero. Altfel, codul de retur este nenul. .[warning] -Tester rulează procesele PHP fără `php.ini`. Mai multe detalii în secțiunea [Own php.ini |#Own php.ini]. +Tester rulează procesele PHP fără `php.ini`. Mai detaliat în secțiunea [#php.ini personalizat]. -Opțiuni de linie de comandă .[#toc-command-line-options] -======================================================== +Parametrii liniei de comandă +============================ -Obținem prezentarea generală a opțiunilor din linia de comandă prin rularea Tester fără parametri sau cu opțiunea `-h`: +Prezentarea generală a tuturor opțiunilor liniei de comandă o obținem rulând Tester fără parametri sau cu parametrul `-h`: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Usage: tester [options] [ | ]... @@ -58,16 +58,16 @@ Options: -p Specify PHP interpreter to run (default: php). -c Look for php.ini file (or look in directory) . -C Use system-wide php.ini. - -l | --log Write log to file . -d ... Define INI entry 'key' with value 'value'. -s Show information about skipped tests. --stop-on-fail Stop execution upon the first failure. -j Run jobs in parallel (default: 8). - -o Specify output format. + -o (e.g. -o junit:output.xml) + Specify one or more output formats with optional file name. -w | --watch Watch directory. -i | --info Show tests environment info and exit. --setup Script for runner setup. - --temp Path to temporary directory. Default: sys_get_temp_dir(). + --temp Path to temporary directory. Default by sys_get_temp_dir(). --colors [1|0] Enable or disable colors. --coverage Generate code coverage report to file. --coverage-src Path to source code. @@ -77,7 +77,7 @@ Options: -p .[filter] ------------------- -Specifică binarul PHP care va fi utilizat pentru a rula testele. În mod implicit este `php`. +Specifică binarul PHP care va fi folosit pentru rularea testelor. Implicit este `php`. /--pre .[terminal] tester -p /home/user/php-7.2.0-beta/php-cgi tests @@ -86,26 +86,17 @@ tester -p /home/user/php-7.2.0-beta/php-cgi tests -c .[filter] ------------------- -Specifică ce `php.ini` va fi utilizat la rularea testelor. În mod implicit, nu se utilizează niciun php.ini. Consultați [Own php.ini |#Own php.ini] pentru mai multe informații. +Specifică ce `php.ini` va fi folosit la rularea testelor. Implicit, nu se folosește niciun php.ini. Mai multe în secțiunea [#php.ini personalizat]. -C .[filter] ------------ -Se utilizează un sistem la nivel de sistem `php.ini`. Deci, pe platforma UNIX, toate fișierele `/etc/php/{sapi}/conf.d/*.ini`. A se vedea secțiunea [Own php.ini |#Own php.ini]. - - -''-l | --log '' .[filter] -------------------------------- -Progresul testării este scris în fișier. Toate testele eșuate, sărite, dar și cele reușite: - -/--pre .[terminal] -tester --log /var/log/tests.log tests -\-- +Se va folosi `php.ini` de sistem. Pe UNIX, de asemenea, toate fișierele INI relevante `/etc/php/{sapi}/conf.d/*.ini`. Mai multe în secțiunea [#php.ini personalizat]. -d .[filter] ------------------------ -Stabilește valoarea directivei de configurare PHP pentru teste. Parametrul poate fi utilizat de mai multe ori. +Setează valoarea directivei de configurare PHP pentru teste. Parametrul poate fi folosit de mai multe ori. /--pre .[terminal] tester -d max_execution_time=20 @@ -114,43 +105,45 @@ tester -d max_execution_time=20 -s --- -Vor fi afișate informații despre testele omise. +Se vor afișa informații despre testele omise. --stop-on-fail .[filter] ------------------------ -Testerul oprește testarea la primul test eșuat. +Tester va opri testarea la primul test eșuat. -j .[filter] ------------------ -Testele se execută în `` precesiuni paralele. Valoarea implicită este 8. Dacă dorim să rulăm testele în serie, folosim valoarea 1. +Specifică câte procese paralele cu teste se vor rula. Valoarea implicită este 8. Dacă dorim ca toate testele să ruleze în serie, folosim valoarea 1. --o .[filter] -------------------------------------- -Format de ieșire. Formatul implicit este cel de consolă. +-o .[filter] +------------------------------------------------------- +Setează formatul de ieșire. Implicit este formatul pentru consolă. Puteți specifica numele fișierului în care se va scrie ieșirea (de ex. `-o junit:output.xml`). Opțiunea `-o` poate fi repetată de mai multe ori pentru a genera mai multe formate simultan. -- `console`: la fel ca în mod implicit, dar logo-ul ASCII nu este tipărit în acest caz. -- `tap`: [format TAP |https://en.wikipedia.org/wiki/Test_Anything_Protocol] adecvat pentru procesarea pe mașină -- `junit`: format JUnit XML, adecvat și pentru procesarea pe mașină -- `none`: nu se tipărește nimic +- `console`: identic cu formatul implicit, dar în acest caz nu se afișează logo-ul ASCII +- `console-lines`: similar cu console, dar rezultatul fiecărui test este specificat pe o linie separată cu informații suplimentare +- `tap`: [format TAP |https://en.wikipedia.org/wiki/Test_Anything_Protocol] potrivit pentru prelucrare automată +- `junit`: format XML JUnit, de asemenea potrivit pentru prelucrare automată +- `log`: Ieșiri ale progresului testării. Toate testele eșuate, omise și, de asemenea, cele reușite +- `none`: nu se afișează nimic ''-w | --watch '' .[filter] --------------------------------- -Tester nu se termină după ce testele sunt finalizate, ci continuă să ruleze și să urmărească fișierele PHP din directorul dat. În cazul în care se modifică, testele se execută din nou. Parametrul poate fi utilizat de mai multe ori dacă dorim să monitorizăm mai multe directoare. +După finalizarea testării, Tester nu se închide, ci rămâne să ruleze și urmărește fișierele PHP din directorul specificat. La modificare, rulează din nou testele. Parametrul poate fi folosit de mai multe ori, dacă dorim să urmărim mai multe directoare. -Este la îndemână în timpul refactorizării unei biblioteci sau a depanării testelor. +Este util la refactorizarea unei biblioteci sau la depanarea testelor. /--pre .[terminal] tester --watch src tests \-- -''-i | --info''' .[filter] --------------------------- -Afișează informații despre un mediu de execuție a unui test. De exemplu: +''-i | --info'' .[filter] +------------------------- +Afișează informații despre mediul de rulare pentru teste. De exemplu: /--pre .[terminal] tester -p /usr/bin/php7.1 -c tests/php.ini --info @@ -177,13 +170,13 @@ Core, ctype, date, dom, ereg, fileinfo, filter, hash, ... --setup .[filter] ------------------------ -Tester încarcă scriptul PHP dat la pornire. Variabila `Tester\Runner\Runner $runner` este disponibilă în acesta. Să presupunem că fișierul `tests/runner-setup.php`: +Tester la pornire încarcă scriptul PHP specificat. În acesta este disponibilă variabila `Tester\Runner\Runner $runner`. Presupunem fișierul `tests/runner-setup.php` cu conținutul: ```php $runner->outputHandlers[] = new MyOutputHandler; ``` -și executăm Tester: +Rulăm Tester: /--pre .[terminal] tester --setup tests/runner-setup.php tests @@ -192,47 +185,47 @@ tester --setup tests/runner-setup.php tests --temp .[filter] ----------------------- -Stabilește o cale către directorul pentru fișierele temporare ale Tester. Valoarea implicită este returnată de `sys_get_temp_dir()`. Atunci când valoarea implicită nu este validă, veți fi anunțat. +Setează calea către directorul pentru fișierele temporare ale Testerului. Valoarea implicită este returnată de `sys_get_temp_dir()`. Dacă valoarea implicită nu este validă, veți fi avertizat. -Dacă nu suntem siguri ce director este utilizat, putem rula Tester cu parametrul `--info`. +Dacă nu suntem siguri ce director se folosește, rulăm Tester cu parametrul `--info`. --colors 1|0 .[filter] ---------------------- -Tester detectează în mod implicit un terminal colorabil și își colorează ieșirea. Această opțiune se suprapune detecției automate. Putem seta colorarea la nivel global printr-o variabilă de mediu de sistem `NETTE_TESTER_COLORS`. +Implicit, Tester detectează terminalul color și își colorează ieșirea. Această opțiune suprascrie autodetecția. Global, putem seta colorarea prin variabila de mediu de sistem `NETTE_TESTER_COLORS`. --coverage .[filter] --------------------------- -Tester va genera un raport cu o imagine de ansamblu a gradului de acoperire a codului sursă de către teste. Această opțiune necesită activarea extensiei PHP [Xdebug |https://xdebug.org/] sau [PCOV |https://github.com/krakjoe/pcov] sau PHP 7 cu PHPDBG SAPI, care este mai rapid. Extensia fișierului de destinație determină formatul conținutului. HTML sau Clover XML. +Tester va genera un raport cu o prezentare generală a cât de mult cod sursă acoperă testele. Această opțiune necesită extensia PHP instalată [Xdebug |https://xdebug.org/], sau [PCOV |https://github.com/krakjoe/pcov], sau PHP 7 cu PHPDBG SAPI, care este mai rapid. Extensia fișierului țintă determină formatul său. Fie HTML, fie Clover XML. /--pre .[terminal] -tester tests --coverage coverage.html # HTML report -tester tests --coverage coverage.xml # Clover XML report +tester tests --coverage coverage.html # raport HTML +tester tests --coverage coverage.xml # raport Clover XML \-- -Prioritatea pentru alegerea mecanismului de colectare este următoarea: +Prioritatea alegerii mecanismului este următoarea: 1) PCOV 2) PHPDBG 3) Xdebug -Testele extinse pot eșua în timpul rulării de către PHPDBG din cauza epuizării memoriei. Colectarea datelor de acoperire este o operațiune consumatoare de memorie. În acest caz, apelarea `Tester\CodeCoverage\Collector::flush()` în cadrul unui test poate fi de ajutor. Aceasta va arunca datele colectate în fișier și va elibera memorie. Atunci când colectarea datelor nu este în curs de desfășurare sau când se utilizează Xdebug, apelarea nu are niciun efect. +La utilizarea PHPDBG, la testele extinse putem întâmpina eșecul testului din cauza epuizării memoriei. Colectarea informațiilor despre codul acoperit este consumatoare de memorie. În acest caz ne ajută apelul `Tester\CodeCoverage\Collector::flush()` în interiorul testului. Scrie datele colectate pe disc și eliberează memoria. Dacă colectarea datelor nu are loc sau se folosește Xdebug, apelul nu are niciun efect. -"Un exemplu de raport HTML":https://files.nette.org/tester/coverage.html cu acoperire de cod. +"Exemplu de raport HTML":attachment:coverage.html cu acoperirea codului. --coverage-src .[filter] ------------------------------- -O folosim simultan cu opțiunea `--coverage`. Opțiunea `` este o cale către codul sursă pentru care generăm raportul. Poate fi utilizată în mod repetat. +Se folosește împreună cu opțiunea `--coverage`. `` este calea către codurile sursă pentru care se generează raportul. Se poate folosi în mod repetat. -Propriu php.ini .[#toc-own-php-ini] -=================================== -Tester rulează procesele PHP cu opțiunea `-n`, ceea ce înseamnă că nu se încarcă `php.ini` (nici măcar cea de la `/etc/php/conf.d/*.ini` în UNIX). Aceasta asigură același mediu pentru testele rulate, dar dezactivează și toate extensiile PHP externe încărcate în mod obișnuit de PHP de sistem. +php.ini personalizat +==================== +Tester rulează procesele PHP cu parametrul `-n`, ceea ce înseamnă că niciun `php.ini` nu este încărcat. Pe UNIX, nici cele din `/etc/php/conf.d/*.ini`. Acest lucru asigură un mediu identic pentru rularea testelor, dar elimină și toate extensiile PHP încărcate în mod normal de PHP-ul de sistem. -Dacă doriți să păstrați configurația sistemului, utilizați parametrul `-C`. +Dacă doriți să păstrați încărcarea fișierelor php.ini de sistem, folosiți parametrul `-C`. -Dacă aveți nevoie de anumite extensii sau de anumite setări INI speciale, vă recomandăm să creați propriul fișier `php.ini` și să-l distribuiți între teste. Apoi executăm Tester cu opțiunea `-c`, de exemplu `tester -c tests/php.ini`. Fișierul INI poate arăta astfel: +Dacă aveți nevoie de anumite extensii sau setări INI speciale pentru teste, recomandăm crearea propriului fișier `php.ini`, care va fi distribuit cu testele. Tester îl rulăm apoi cu parametrul `-c`, de exemplu `tester -c tests/php.ini tests`, unde fișierul INI poate arăta astfel: ```ini [PHP] @@ -243,4 +236,4 @@ extension=php_pdo_pgsql.dll memory_limit=512M ``` -Rularea Tester cu un sistem `php.ini` în UNIX, de exemplu `tester -c /etc/php/cgi/php.ini`, nu încarcă alte INI din `/etc/php/conf.d/*.ini`. Acesta este comportamentul PHP, nu al Testerului. +Rularea Testerului pe UNIX cu `php.ini` de sistem, de exemplu `tester -c /etc/php/cli/php.ini`, nu încarcă celelalte INI din `/etc/php/conf.d/*.ini`. Aceasta este o caracteristică a PHP, nu a Testerului. diff --git a/tester/ro/test-annotations.texy b/tester/ro/test-annotations.texy index 13806519eb..39fc7a18c0 100644 --- a/tester/ro/test-annotations.texy +++ b/tester/ro/test-annotations.texy @@ -1,10 +1,10 @@ -Adnotări de testare -******************* +Adnotări de test +**************** .[perex] -Adnotările determină modul în care testele vor fi gestionate de către [programul de execuție a testelor din linia de comandă |running-tests]. Ele sunt scrise la începutul fișierului de test. +Adnotările determină cum vor fi tratate testele de către [rulatorul de teste din linia de comandă |running-tests]. Se scriu la începutul fișierului cu testul. -Adnotările nu țin cont de majuscule și minuscule. De asemenea, acestea nu au niciun efect dacă testul este rulat manual ca un script PHP obișnuit. +La adnotări nu se ține cont de majuscule. De asemenea, nu au niciun efect dacă testul este rulat manual ca un script PHP obișnuit. Exemplu: @@ -23,17 +23,17 @@ require __DIR__ . '/../bootstrap.php'; TEST .[filter] -------------- -De fapt, nu este o adnotare. Setează doar titlul testului care este tipărit la eșec sau în jurnale. +Aceasta nu este de fapt o adnotare, ci doar specifică titlul testului, care se afișează la eșec sau în log. @skip .[filter] --------------- -Testul este omis. Este utilă pentru dezactivarea temporară a testului. +Testul va fi omis. Este util pentru dezactivarea temporară a testelor. @phpVersion .[filter] --------------------- -Testul este omis dacă nu este executat de versiunea PHP corespunzătoare. Scriem adnotarea ca `@phpVersion [operator] version`. Putem omite operatorul, implicit este `>=`. Exemple: +Testul va fi omis dacă nu este rulat cu versiunea PHP corespunzătoare. Adnotarea o scriem ca `@phpVersion [operator] versiune`. Operatorul poate fi omis, implicit este `>=`. Exemple: ```php /** @@ -44,9 +44,9 @@ Testul este omis dacă nu este executat de versiunea PHP corespunzătoare. Scrie ``` -@phpExtensiune .[filter] ------------------------- -Testul este omis dacă toate extensiile PHP menționate nu sunt încărcate. Într-o singură adnotare pot fi scrise mai multe extensii sau putem folosi adnotarea de mai multe ori. +@phpExtension .[filter] +----------------------- +Testul va fi omis dacă nu sunt încărcate toate extensiile PHP specificate. Mai multe extensii pot fi specificate într-o singură adnotare sau o putem folosi de mai multe ori. ```php /** @@ -58,9 +58,9 @@ Testul este omis dacă toate extensiile PHP menționate nu sunt încărcate. În @dataProvider .[filter] ----------------------- -Această adnotare se potrivește atunci când dorim să executăm testul de mai multe ori, dar cu date diferite. (A nu se confunda cu adnotarea cu același nume pentru [TestCase |TestCase#dataProvider]). +Dacă dorim să rulăm fișierul de test de mai multe ori, dar cu date de intrare diferite, această adnotare este potrivită. (Nu confundați cu adnotarea omonimă pentru [TestCase |TestCase#dataProvider].) -Scriem adnotarea ca `@dataProvider file.ini`. Calea fișierului INI este relativă la fișierul de test. Testul se execută de atâtea ori cât numărul de secțiuni conținute în fișierul INI. Să presupunem că fișierul INI `databases.ini`: +O scriem ca `@dataProvider file.ini`, calea către fișier se consideră relativă la fișierul cu testul. Testul va fi rulat de atâtea ori câte secțiuni sunt în fișierul INI. Presupunem fișierul INI `databases.ini`: ```ini [mysql] @@ -77,7 +77,7 @@ password = ****** dsn = "sqlite::memory:" ``` -și fișierul `database.phpt` în același director: +și în același director testul `database.phpt`: ```php /** @@ -87,11 +87,11 @@ dsn = "sqlite::memory:" $args = Tester\Environment::loadData(); ``` -Testul se execută de trei ori, iar `$args` va conține valori din secțiunile `mysql`, `postgresql` sau `sqlite`. +Testul va fi rulat de trei ori și `$args` va conține de fiecare dată valorile din secțiunea `mysql`, `postgresql` sau `sqlite`. -Mai există o variantă atunci când scriem adnotările cu semnul întrebării ca `@dataProvider? file.ini`. În acest caz, testul este sărit dacă fișierul INI nu există. +Există și o variantă în care scriem adnotarea cu un semn de întrebare ca `@dataProvider? file.ini`. În acest caz, testul va fi omis dacă fișierul INI nu există. -Posibilitățile de adnotare nu au fost menționate încă toate. Putem scrie condiții după fișierul INI. Testul se execută pentru secțiunea dată numai dacă toate condițiile corespund. Să extindem fișierul INI: +Posibilitățile adnotării nu se termină aici. După numele fișierului INI putem specifica condiții sub care testul va fi rulat pentru secțiunea respectivă. Extindem fișierul INI: ```ini [mysql] @@ -113,7 +113,7 @@ password = ****** dsn = "sqlite::memory:" ``` -și vom folosi adnotarea cu condiția: +și folosim adnotarea cu condiție: ```php /** @@ -121,9 +121,9 @@ dsn = "sqlite::memory:" */ ``` -Testul se execută o singură dată pentru secțiunea `postgresql 9.1`. Celelalte secțiuni nu corespund condițiilor. +Testul va fi rulat doar o singură dată și anume pentru secțiunea `postgresql 9.1`. Celelalte secțiuni nu trec filtrul condiției. -În mod similar, putem trece calea către un script PHP în loc de INI. Acesta trebuie să returneze array sau Traversable. Fișierul `databases.php`: +În mod similar, în loc de fișierul INI putem face referire la un script PHP. Acesta trebuie să returneze un array sau Traversable. Fișierul `databases.php`: ```php return [ @@ -142,29 +142,29 @@ return [ @multiple .[filter] ------------------- -O scriem ca `@multiple N` unde `N` este un număr întreg. Testul se execută exact de N ori. +O scriem ca `@multiple N`, unde `N` este un număr întreg. Testul va fi rulat exact de N ori. @testCase .[filter] ------------------- -Adnotarea nu are parametri. O folosim atunci când scriem un test sub formă de clase [TestCase |TestCase]. În acest caz, executorul de teste din linia de comandă va rula metodele individuale în procese separate și în paralel în mai multe fire de execuție. Acest lucru poate accelera semnificativ întregul proces de testare. +Adnotarea nu are parametri. O folosim dacă scriem testele ca clase [TestCase |TestCase]. În acest caz, rulatorul de teste din linia de comandă va rula metodele individuale în procese separate și în paralel în mai multe fire de execuție. Acest lucru poate accelera semnificativ întregul proces de testare. @exitCode .[filter] ------------------- -Îl scriem ca `@exitCode N` în cazul în care `N` is the exit code of the test. For example if `exit(10)` este apelat în test, scriem adnotarea ca `@exitCode 10`. Se consideră că testul eșuează dacă se încheie cu un cod diferit. Codul de ieșire 0 (zero) este verificat dacă omitem adnotarea +O scriem ca `@exitCode N`, unde `N` este codul de retur al testului rulat. Dacă în test se apelează, de exemplu, `exit(10)`, scriem adnotarea ca `@exitCode 10` și dacă testul se termină cu un alt cod, este considerat eșec. Dacă nu specificăm adnotarea, se verifică codul de retur 0 (zero). @httpCode .[filter] ------------------- -Adnotarea este evaluată numai dacă binarul PHP este CGI. În caz contrar, este ignorată. O scriem ca `@httpCode NNN` unde `NNN` este codul HTTP așteptat. Codul HTTP 200 este verificat dacă nu includem adnotarea. Dacă scriem `NNN` ca un șir de caractere evaluat ca zero, de exemplu `any`, codul HTTP nu este verificat deloc. +Adnotarea se aplică doar dacă binarul PHP este CGI. Altfel se ignoră. O scriem ca `@httpCode NNN` unde `NNN` este codul HTTP așteptat. Dacă nu specificăm adnotarea, se verifică codul HTTP 200. Dacă `NNN` îl scriem ca un șir evaluat la zero, de exemplu `any`, codul HTTP nu se verifică. -@outputMatch a @outputMatchFile .[filter] ------------------------------------------ -Comportamentul adnotărilor este în concordanță cu aserțiunile `Assert::match()` și `Assert::matchFile()`. Dar modelul se găsește în ieșirea standard a testului. Un caz de utilizare adecvat este atunci când presupunem că testul se încheie cu o eroare fatală și trebuie să verificăm ieșirea acestuia. +@outputMatch și @outputMatchFile .[filter] +------------------------------------------ +Funcția adnotărilor este identică cu aserțiunile `Assert::match()` și `Assert::matchFile()`. Modelul (pattern) se caută însă în textul pe care testul l-a trimis la ieșirea sa standard. Găsește aplicare dacă presupunem că testul se va termina cu o eroare fatală și avem nevoie să verificăm ieșirea sa. @phpIni .[filter] ----------------- -Setează valorile de configurare INI pentru test. De exemplu, îl scriem ca `@phpIni precision=20` și funcționează în același mod ca și cum am trece valoarea din linia de comandă prin parametrul `-d precision=20`. +Pentru test setează valorile de configurare INI. O scriem, de exemplu, ca `@phpIni precision=20` și funcționează la fel ca și cum am fi specificat valoarea din linia de comandă prin parametrul `-d precision=20`. diff --git a/tester/ro/testcase.texy b/tester/ro/testcase.texy index 1a0c39a6f2..e996f98987 100644 --- a/tester/ro/testcase.texy +++ b/tester/ro/testcase.texy @@ -2,9 +2,9 @@ TestCase ******** .[perex] -În testele simple, afirmațiile pot urma una câte una. Dar, uneori, este utilă includerea aserțiunilor în clasa de testare și structurarea lor în acest mod. +În testele simple, aserțiunile pot urma una după alta. Uneori, însă, este mai avantajos să împachetăm aserțiunile într-o clasă de test și astfel să le structurăm. -Clasa trebuie să fie descendentă din `Tester\TestCase` și vorbim despre ea pur și simplu ca despre **testcase**. +Clasa trebuie să fie descendentă a `Tester\TestCase` și, simplificat, vorbim despre ea ca despre un **testcase**. Clasa trebuie să conțină metode de testare care încep cu `test`. Aceste metode vor fi rulate ca teste: ```php use Tester\Assert; @@ -22,11 +22,11 @@ class RectangleTest extends Tester\TestCase } } -# Run testing methods +# Rularea metodelor de testare (new RectangleTest)->run(); ``` -Putem îmbogăți un testcase prin metodele `setUp()` și `tearDown()`. Acestea sunt apelate înainte/după fiecare metodă de testare: +Un test scris astfel poate fi îmbogățit ulterior cu metodele `setUp()` și `tearDown()`. Acestea sunt apelate înainte, respectiv după fiecare metodă de testare: ```php use Tester\Assert; @@ -35,12 +35,12 @@ class NextTest extends Tester\TestCase { public function setUp() { - # Preparation + # Pregătire } public function tearDown() { - # Clean-up + # Curățenie } public function testOne() @@ -54,14 +54,14 @@ class NextTest extends Tester\TestCase } } -# Run testing methods +# Rularea metodelor de testare (new NextTest)->run(); /* -Method Calls Order ------------------- +Ordinea apelării metodelor +-------------------------- setUp() testOne() tearDown() @@ -72,9 +72,9 @@ tearDown() */ ``` -Dacă apare o eroare într-o fază `setUp()` sau `tearDown()`, testul va eșua. În cazul în care apare o eroare în metoda de testare, metoda `tearDown()` este oricum apelată, dar cu erori eliminate. +Dacă apare o eroare în faza `setUp()` sau `tearDown()`, testul eșuează în totalitate. Dacă apare o eroare în metoda de testare, chiar și așa metoda `tearDown()` se va rula, însă cu suprimarea erorilor din ea. -Vă recomandăm să scrieți adnotarea [@testCase |test-annotations#@testCase] la începutul testului, după care executantul de teste în linie de comandă va rula metodele individuale ale cazului de test în procese separate și în paralel în mai multe fire de execuție. Acest lucru poate accelera semnificativ întregul proces de testare. +Recomandăm să scrieți la începutul testului adnotarea [@testCase |test-annotations#testCase], atunci rulatorul de teste din linia de comandă va rula metodele individuale ale testcase-ului în procese separate și în paralel în mai multe fire de execuție. Acest lucru poate accelera semnificativ întregul proces de testare. /--php getArea()); # we will verify the expected results +Assert::same(200.0, $rect->getArea()); # verificăm rezultatele așteptate Assert::false($rect->isSquare()); ``` -După cum puteți vedea, [metodele de aserțiune |Assertions], cum ar fi `Assert::same()`, sunt utilizate pentru a afirma că o valoare reală corespunde unei valori așteptate. +După cum vedeți, așa-numitele [metode de aserțiune |assertions] precum `Assert::same()` sunt folosite pentru a confirma că valoarea reală corespunde valorii așteptate. -Ultimul pas este crearea fișierului `bootstrap.php`. Acesta conține un cod comun pentru toate testele. De exemplu, clasele de încărcare automată, configurarea mediului, crearea de directoare temporare, ajutoare și altele similare. Fiecare test încarcă bootstrap-ul și acordă atenție doar testării. Bootstrap-ul poate arăta astfel: +Rămâne ultimul pas și anume fișierul `bootstrap.php`. Acesta conține cod comun pentru toate testele, de exemplu, autoloading-ul claselor, configurarea mediului, crearea unui director temporar, funcții ajutătoare și altele asemenea. Toate testele încarcă bootstrap-ul și apoi se dedică doar testării. Bootstrap-ul poate arăta astfel: ```php .{file:tests/bootstrap.php} OK \-- -Dacă schimbăm în test declarația în fals `Assert::same(123, $rect->getArea());`, se va întâmpla acest lucru: +Dacă am schimba în test afirmația într-una falsă `Assert::same(123, $rect->getArea());`, se va întâmpla asta: /--pre .[terminal] $ php RectangleTest.php @@ -107,12 +106,12 @@ $ php RectangleTest.php \-- -Atunci când se scriu teste, este bine să se surprindă toate situațiile extreme. De exemplu, dacă intrarea este zero, un număr negativ, în alte cazuri un șir gol, null etc. De fapt, acest lucru vă obligă să vă gândiți și să decideți cum ar trebui să se comporte codul în astfel de situații. Testele fixează apoi comportamentul. +La scrierea testelor este bine să acoperim toate situațiile limită. De exemplu, când intrarea va fi zero, un număr negativ, în alte cazuri, de exemplu, un șir gol, null etc. De fapt, vă obligă să vă gândiți și să decideți cum ar trebui să se comporte codul în astfel de situații. Testele apoi fixează comportamentul. -În cazul nostru, o valoare negativă ar trebui să arunce o excepție, pe care o verificăm cu [Assert::exception() |Assertions#Assert::exception]: +În cazul nostru, o valoare negativă ar trebui să arunce o excepție, ceea ce verificăm folosind [Assert::exception() |Assertions#Assert::exception]: ```php .{file:tests/RectangleTest.php} -// lățimea nu trebuie să fie un număr negativ +// lățimea nu trebuie să fie negativă Assert::exception( fn() => new Rectangle(-1, 20), InvalidArgumentException::class, @@ -120,22 +119,22 @@ Assert::exception( ); ``` -Și adăugăm un test similar pentru înălțime. În cele din urmă, testăm că `isSquare()` returnează `true` dacă ambele dimensiuni sunt identice. Încercați să scrieți astfel de teste ca exercițiu. +Și adăugăm un test similar pentru înălțime. În final, testăm că `isSquare()` returnează `true`, dacă ambele dimensiuni sunt egale. Încercați să scrieți astfel de teste ca exercițiu. -Teste bine aranjate .[#toc-well-arranged-tests] -=============================================== +Teste mai lizibile +================== -Dimensiunea fișierului de test poate crește și poate deveni rapid aglomerat. Prin urmare, este practic să se grupeze domeniile testate individual în funcții separate. +Dimensiunea fișierului cu testul poate crește și devine rapid neclar. De aceea, este practic să grupăm zonele testate individuale în funcții separate. -Mai întâi, vom prezenta o variantă mai simplă, dar elegantă, utilizând funcția globală `test()`. Testerul nu o creează automat, pentru a evita o coliziune în cazul în care ați avut o funcție cu același nume în codul dumneavoastră. Ea este creată doar de metoda `setupFunctions()`, pe care o apelați în fișierul `bootstrap.php`: +Mai întâi vom arăta o variantă mai simplă, dar elegantă, și anume folosind funcția globală `test()`. Tester nu o creează automat, pentru a evita coliziunea, dacă ați avea în cod o funcție cu același nume. O creează abia metoda `setupFunctions()`, pe care o apelați în fișierul `bootstrap.php`: ```php .{file:tests/bootstrap.php} Tester\Environment::setup(); Tester\Environment::setupFunctions(); ``` -Folosind această funcție, putem împărți frumos fișierul de testare în unități denumite. La execuție, etichetele vor fi afișate una după alta. +Cu ajutorul acestei funcții putem împărți frumos fișierul de testare în unități numite. La rulare, descrierile se vor afișa succesiv. ```php .{file:tests/RectangleTest.php} getArea()); Assert::false($rect->isSquare()); }); -test('general square', function () { +test('pătrat general', function () { $rect = new Rectangle(5, 5); Assert::same(25.0, $rect->getArea()); Assert::true($rect->isSquare()); }); -test('dimensions must not be negative', function () { +test('dimensiunile nu trebuie să fie negative', function () { Assert::exception( fn() => new Rectangle(-1, 20), InvalidArgumentException::class, @@ -168,15 +167,15 @@ test('dimensions must not be negative', function () { }); ``` -Dacă aveți nevoie să executați codul înainte sau după fiecare test, treceți-l la `setUp()` sau `tearDown()`: +Dacă aveți nevoie să rulați cod înainte sau după fiecare test, transmiteți-l funcției `setUp()` respectiv `tearDown()`: ```php setUp(function () { - // codul de inițializare care trebuie executat înainte de fiecare test() + // cod de inițializare care se rulează înainte de fiecare test() }); ``` -A doua variantă este obiect. Vom crea așa-numitul TestCase, care este o clasă în care unitățile individuale sunt reprezentate de metode ale căror nume încep cu test-. +A doua variantă este orientată pe obiecte. Ne creăm așa-numitul TestCase, care este o clasă în care unitățile individuale reprezintă metode, ale căror nume încep cu test–. ```php .{file:tests/RectangleTest.php} class RectangleTest extends Tester\TestCase @@ -208,25 +207,25 @@ class RectangleTest extends Tester\TestCase } } -// Executarea metodelor de testare +// Rularea metodelor de testare (new RectangleTest)->run(); ``` -De data aceasta am folosit o adnotare `@throw` pentru a testa excepțiile. Pentru mai multe informații, consultați capitolul [TestCase |TestCase]. +Pentru testarea excepțiilor am folosit de data aceasta adnotarea `@throws`. Veți afla mai multe în capitolul [TestCase |TestCase]. -Funcții ajutătoare .[#toc-helpers-functions] -============================================ +Funcții ajutătoare +================== -Nette Tester include mai multe clase și funcții care vă pot ușura testarea, de exemplu, ajutători pentru a testa conținutul unui document HTML, pentru a testa funcțiile de lucru cu fișiere și așa mai departe. +Nette Tester conține câteva clase și funcții care vă pot facilita, de exemplu, testarea conținutului documentului HTML, testarea funcțiilor care lucrează cu fișiere și așa mai departe. -Puteți găsi o descriere a acestora pe pagina [Helpers |Helpers]. +Descrierea lor o găsiți pe pagina [Clase ajutătoare |helpers]. -Adnotare și săritură de teste .[#toc-annotation-and-skipping-tests] -=================================================================== +Adnotări și omiterea testelor +============================= -Executarea testelor poate fi afectată de adnotările din comentariul phpDoc de la începutul fișierului. De exemplu, ar putea arăta astfel: +Rularea testelor poate fi influențată de adnotări sub forma unui comentariu phpDoc la începutul fișierului. Poate arăta, de exemplu, astfel: ```php .{file:tests/RectangleTest.php} /** @@ -235,11 +234,11 @@ Executarea testelor poate fi afectată de adnotările din comentariul phpDoc de */ ``` -Adnotările spun că testul ar trebui să fie executat numai cu versiunea PHP 7.2 sau mai mare și dacă extensiile PHP pdo și pdo_pgsql sunt prezente. Aceste adnotări sunt controlate de [linia de comandă test runner |running-tests], care, în cazul în care condițiile nu sunt îndeplinite, sare testul și îl marchează cu litera `s` - skipped. Cu toate acestea, ele nu au niciun efect atunci când testul este rulat manual. +Adnotările specificate spun că testul trebuie rulat doar cu versiunea PHP 7.2 sau mai recentă și dacă sunt prezente extensiile PHP pdo și pdo_pgsql. De aceste adnotări se ghidează [rulatorul de teste din linia de comandă |running-tests], care, în cazul în care condițiile nu sunt îndeplinite, omite testul și în afișare îl marchează cu litera `s` - skipped. Însă, la rularea manuală a testului, nu au niciun efect. -Pentru o descriere a adnotărilor, consultați secțiunea [Adnotări de test |Test Annotations]. +Descrierea adnotărilor o găsiți pe pagina [Adnotări de test |test-annotations]. -Testul poate fi, de asemenea, sărit pe baza unei condiții proprii cu `Environment::skip()`. De exemplu, vom sări peste acest test pe Windows: +Testul poate fi lăsat să fie omis și pe baza îndeplinirii unei condiții proprii folosind `Environment::skip()`. De exemplu, aceasta omite testele pe Windows: ```php if (defined('PHP_WINDOWS_VERSION_BUILD')) { @@ -248,10 +247,10 @@ if (defined('PHP_WINDOWS_VERSION_BUILD')) { ``` -Structura directoarelor .[#toc-directory-structure] -=================================================== +Structura directoarelor +======================= -Pentru biblioteci sau proiecte puțin mai mari, se recomandă împărțirea directorului de testare în subdirectoare în funcție de spațiul de nume al clasei testate: +Recomandăm ca la bibliotecile sau proiectele puțin mai mari să împărțiți directorul cu teste și în subdirectoare după spațiul de nume al clasei testate: ``` └── tests/ @@ -269,22 +268,22 @@ Pentru biblioteci sau proiecte puțin mai mari, se recomandă împărțirea dire └── ... ``` -Veți putea executa testele dintr-un singur subdirectorat namespace ie: +Veți putea astfel rula teste dintr-un singur spațiu de nume, adică subdirector: /--pre .[terminal] tester tests/NamespaceOne \-- -Edge Cases .[#toc-edge-cases] -============================= +Situații speciale +================= -Un test care nu apelează nicio metodă de aserțiune este suspect și va fi evaluat ca fiind eronat: +Un test care nu apelează nicio metodă de aserțiune este suspect și va fi evaluat ca eronat: /--pre .[terminal] Error: This test forgets to execute an assertion. \-- -Dacă testul fără apelarea aserțiunilor trebuie într-adevăr să fie considerat valid, apelați, de exemplu, `Assert::true(true)`. +Dacă într-adevăr testul fără apeluri de aserțiuni trebuie considerat valid, apelați, de exemplu, `Assert::true(true)`. -De asemenea, poate fi înșelător să se utilizeze `exit()` și `die()` pentru a încheia testul cu un mesaj de eroare. De exemplu, `exit('Error in connection')` încheie testul cu un cod de ieșire 0, care semnalează succesul. Utilizați `Assert::fail('Error in connection')`. +De asemenea, poate fi înșelător să folosiți `exit()` și `die()` pentru a termina testul cu un mesaj de eroare. De exemplu, `exit('Error in connection')` termină testul cu codul de retur 0, ceea ce semnalează succes. Folosiți `Assert::fail('Error in connection')`. diff --git a/tester/ru/@home.texy b/tester/ru/@home.texy index 17a6108d0e..5d8bf192d0 100644 --- a/tester/ru/@home.texy +++ b/tester/ru/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Nette Tester - приятное модульное тестирование в PHP}} -{{description: Nette Tester - это простой и очень удобный инструмент для тестирования PHP-кода.}} +{{maintitle: Nette Tester – удобное тестирование в PHP}} +{{description: Nette Tester — это простой и в то же время очень удобный инструмент для тестирования PHP-кода.}} diff --git a/tester/ru/@left-menu.texy b/tester/ru/@left-menu.texy index c95414b5a2..01a2b556ab 100644 --- a/tester/ru/@left-menu.texy +++ b/tester/ru/@left-menu.texy @@ -1,8 +1,8 @@ -- [Начало работы |guide] -- [Написание тестов |Writing Tests] -- [Запуск тестов |Running Tests] +- [Начинаем работать с Nette Tester |guide] +- [Написание тестов |writing-tests] +- [Запуск тестов |running-tests] -- [Утверждения |Assertions] -- [Аннотации тестов |Test Annotations] -- [Тестовый пример |TestCase] -- [Помощники |Helpers] +- [Утверждения |assertions] +- [Аннотации тестов |test-annotations] +- [TestCase |TestCase] +- [Вспомогательные классы |helpers] diff --git a/tester/ru/@menu.texy b/tester/ru/@menu.texy index d5536f44f4..03cb9c49ad 100644 --- a/tester/ru/@menu.texy +++ b/tester/ru/@menu.texy @@ -1,3 +1,3 @@ -- [Главная |@home] -- [Документация |Guide] +- [Введение |@home] +- [Документация |guide] - "GitHub .[link-external]":https://github.com/nette/tester diff --git a/tester/ru/@meta.texy b/tester/ru/@meta.texy new file mode 100644 index 0000000000..1e1abb2619 --- /dev/null +++ b/tester/ru/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документация Tester}} diff --git a/tester/ru/assertions.texy b/tester/ru/assertions.texy index 493cb21305..ce85891219 100644 --- a/tester/ru/assertions.texy +++ b/tester/ru/assertions.texy @@ -1,35 +1,35 @@ -Утверждения -*********** +Утверждения (Assert) +******************** .[perex] -Утверждения используются для подтверждения того, что фактическое значение соответствует ожидаемому значению. Они являются методами `Tester\Assert`. +Утверждения используются для подтверждения того, что фактическое значение соответствует ожидаемому значению. Это методы класса `Tester\Assert`. -Выберите наиболее точные утверждения. Лучше `Assert::same($a, $b)`, чем `Assert::true($a === $b)`, потому что он выводит осмысленное сообщение об ошибке при неудаче. Во втором случае мы получаем только `false should be true`, и оно ничего не говорит о содержимом переменных $a и $b. +Выбирайте наиболее подходящие утверждения. Лучше `Assert::same($a, $b)`, чем `Assert::true($a === $b)`, потому что при сбое оно отобразит осмысленное сообщение об ошибке. Во втором случае только `false should be true`, что ничего не говорит нам о содержимом переменных `$a` и `$b`. -Большинство утверждений могут также иметь необязательный `$description`, который появляется в сообщении об ошибке в случае неудачи. +Большинство утверждений также могут иметь необязательное описание в параметре `$description`, которое будет отображено в сообщении об ошибке, если ожидание не оправдается. -Примеры предполагают, что определен следующий псевдоним класса: +Примеры предполагают созданный псевдоним: ```php use Tester\Assert; ``` -Assert::same($expected, $actual, string $description=null) .[method] --------------------------------------------------------------------- -`$expected` должно быть то же самое, что и `$actual`. Это то же самое, что и оператор PHP `===`. +Assert::same($expected, $actual, ?string $description=null) .[method] +--------------------------------------------------------------------- +`$expected` должен быть идентичен `$actual`. То же самое, что и PHP оператор `===`. -Assert::notSame($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------ -Противоположен оператору `Assert::same()`, поэтому совпадает с оператором PHP `!==`. +Assert::notSame($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------ +Противоположность `Assert::same()`, то есть то же самое, что и PHP оператор `!==`. -Assert::equal($expected, $actual, string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] ------------------------------------------------------------------------------------------------------------------------- -`$expected` должно быть таким же, как `$actual`. В отличие от `Assert::same()`, идентичность объектов, порядок пар ключ => значение в массивах и незначительно отличающиеся десятичные числа игнорируются, что можно изменить, задав `$matchIdentity` и `$matchOrder`. +Assert::equal($expected, $actual, ?string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] +------------------------------------------------------------------------------------------------------------------------- +`$expected` должен быть равен `$actual`. В отличие от `Assert::same()`, игнорируется идентичность объектов, порядок пар ключ => значение в массивах и незначительно отличающиеся десятичные числа, что можно изменить настройкой `$matchIdentity` и `$matchOrder`. -Следующие случаи идентичны с точки зрения `equal()`, но не для `same()`: +Следующие случаи считаются равными с точки зрения `equal()`, но не `same()`: ```php Assert::equal(0.3, 0.1 + 0.2); @@ -40,81 +40,81 @@ Assert::equal( ); ``` -Однако, будьте внимательны, массив `[1, 2]` и `[2, 1]` не равны, поскольку различается только порядок значений, а не пары ключ => значение. Массив `[1, 2]` также может быть записан как `[0 => 1, 1 => 2]` и поэтому `[1 => 2, 0 => 1]` будет считаться равным. +Однако будьте осторожны, массивы `[1, 2]` и `[2, 1]` не равны, потому что отличается только порядок значений, а не пар ключ => значение. Массив `[1, 2]` можно также записать как `[0 => 1, 1 => 2]`, и поэтому равным будет считаться `[1 => 2, 0 => 1]`. -Вы также можете использовать так называемые [ожидания |#Expectations] в `$expected`. +Далее в `$expected` можно использовать так называемые [#Ожидания]. -Assert::notEqual($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------- +Assert::notEqual($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------- Противоположность `Assert::equal()`. -Assert::contains($needle, string|array $actual, string $description=null) .[method] ------------------------------------------------------------------------------------ -Если `$actual` - строка, то она должна содержать подстроку `$needle`. Если это массив, то он должен содержать элемент `$needle` (он сравнивается строго). +Assert::contains($needle, string|array $actual, ?string $description=null) .[method] +------------------------------------------------------------------------------------ +Если `$actual` — строка, она должна содержать подстроку `$needle`. Если это массив, он должен содержать элемент `$needle` (сравнивается строго). -Assert::notContains($needle, string|array $actual, string $description=null) .[method] --------------------------------------------------------------------------------------- +Assert::notContains($needle, string|array $actual, ?string $description=null) .[method] +--------------------------------------------------------------------------------------- Противоположность `Assert::contains()`. -Assert::hasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} -------------------------------------------------------------------------------------------------------- -`$actual` должен быть массивом и содержать ключ `$needle`. +Assert::hasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +-------------------------------------------------------------------------------------------------------- +`$actual` должен быть массивом и должен содержать ключ `$needle`. -Assert::notHasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} ----------------------------------------------------------------------------------------------------------- +Assert::notHasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +----------------------------------------------------------------------------------------------------------- `$actual` должен быть массивом и не должен содержать ключ `$needle`. -Assert::true($value, string $description=null) .[method] --------------------------------------------------------- -`$value` должен быть `true`, поэтому `$value === true`. +Assert::true($value, ?string $description=null) .[method] +--------------------------------------------------------- +`$value` должен быть `true`, то есть `$value === true`. -Assert::truthy($value, string $description=null) .[method] ----------------------------------------------------------- -`$value` должно быть истинным, поэтому оно удовлетворяет условию `if ($value) ...`. +Assert::truthy($value, ?string $description=null) .[method] +----------------------------------------------------------- +`$value` должен быть истинным, то есть выполнит условие `if ($value) ...`. -Assert::false($value, string $description=null) .[method] ---------------------------------------------------------- -`$value` должно быть `false`, поэтому `$value === false`. +Assert::false($value, ?string $description=null) .[method] +---------------------------------------------------------- +`$value` должен быть `false`, то есть `$value === false`. -Assert::falsey($value, string $description=null) .[method] ----------------------------------------------------------- -`$value` должен быть ложным, поэтому он удовлетворяет условию `if (!$value) ...`. +Assert::falsey($value, ?string $description=null) .[method] +----------------------------------------------------------- +`$value` должен быть ложным, то есть выполнит условие `if (!$value) ...`. -Assert::null($value, string $description=null) .[method] --------------------------------------------------------- -`$value` должно быть `null`, поэтому `$value === null`. +Assert::null($value, ?string $description=null) .[method] +--------------------------------------------------------- +`$value` должен быть `null`, то есть `$value === null`. -Assert::notNull($value, string $description=null) .[method] ------------------------------------------------------------ -`$value` не должно быть `null`, поэтому `$value !== null`. +Assert::notNull($value, ?string $description=null) .[method] +------------------------------------------------------------ +`$value` не должен быть `null`, то есть `$value !== null`. -Assert::nan($value, string $description=null) .[method] -------------------------------------------------------- -`$value` должен быть Not a Number. Используйте только `Assert::nan()` для тестирования NAN. Значение NAN очень специфично, и утверждения `Assert::same()` или `Assert::equal()` могут повести себя непредсказуемо. +Assert::nan($value, ?string $description=null) .[method] +-------------------------------------------------------- +`$value` должен быть Not a Number. Для тестирования значения NAN используйте исключительно `Assert::nan()`. Значение NAN очень специфично, и утверждения `Assert::same()` или `Assert::equal()` могут работать неожиданно. -Assert::count($count, Countable|array $value, string $description=null) .[method] ---------------------------------------------------------------------------------- -Количество элементов в `$value` должно быть равно `$count`. То есть то же самое, что и `count($value) === $count`. +Assert::count($count, Countable|array $value, ?string $description=null) .[method] +---------------------------------------------------------------------------------- +Количество элементов в `$value` должно быть `$count`. То есть то же самое, что и `count($value) === $count`. -Assert::type(string|object $type, $value, string $description=null) .[method] ------------------------------------------------------------------------------ -`$value` должен быть заданного типа. В качестве `$type` мы можем использовать строку: +Assert::type(string|object $type, $value, ?string $description=null) .[method] +------------------------------------------------------------------------------ +`$value` должен быть данного типа. В качестве `$type` мы можем использовать строку: - `array` -- `list` - массив, индексированный в порядке возрастания числовых ключей от нуля. +- `list` - массив, индексированный по возрастающей последовательности числовых ключей от нуля - `bool` - `callable` - `float` @@ -124,28 +124,28 @@ Assert::type(string|object $type, $value, string $description=null) .[method] - `resource` - `scalar` - `string` -- имя класса или объекта напрямую, то необходимо передать `$value instanceof $type` +- имя класса или непосредственно объект, тогда `$value` должен быть `instanceof $type` -Assert::exception(callable $callable, string $class, string $message=null, $code=null) .[method] ------------------------------------------------------------------------------------------------- -При вызове `$callable` должно быть выброшено исключение экземпляра `$class`. Если мы передаем `$message`, то сообщение исключения должно [совпадать |#Assert-match]. А если мы передаем `$code`, то код исключения должен быть таким же. +Assert::exception(callable $callable, string $class, ?string $message=null, $code=null) .[method] +------------------------------------------------------------------------------------------------- +При вызове `$callable` должно быть выброшено исключение класса `$class`. Если мы укажем `$message`, сообщение исключения также должно [соответствовать шаблону |#Assert::match], и если мы укажем `$code`, коды также должны строго совпадать. -Например, этот тест не пройден, потому что сообщение исключения не совпадает: +Следующий тест не пройдет, так как сообщение исключения не соответствует: ```php Assert::exception( - fn() => throw new App\InvalidValueException('Нулевое значение'), + fn() => throw new App\InvalidValueException('Zero value'), App\InvalidValueException::class, - 'Значение слишком мало', + 'Value is to low', ); ``` -Сайт `Assert::exception()` возвращает брошенное исключение, поэтому вы можете проверить вложенное исключение. +`Assert::exception()` возвращает выброшенное исключение, так что можно протестировать и вложенное исключение. ```php $e = Assert::exception( - fn() => throw new MyException('Что-то не так', 0, new RuntimeException), + fn() => throw new MyException('Something is wrong', 0, new RuntimeException), MyException::class, 'Something is wrong', ); @@ -154,9 +154,9 @@ Assert::type(RuntimeException::class, $e->getPrevious()); ``` -Assert::error(string $callable, int|string|array $type, string $message=null) .[method] ---------------------------------------------------------------------------------------- -Проверяет, что вызов `$callable` генерирует ожидаемые ошибки (т.е. предупреждения, уведомления и т.д.). В качестве `$type` мы указываем одну из констант `E_...`, например `E_WARNING`. И если передаем `$message`, то сообщение об ошибке также должно [соответствовать |#Assert-match] шаблону. Например: +Assert::error(string $callable, int|string|array $type, ?string $message=null) .[method] +---------------------------------------------------------------------------------------- +Проверяет, что функция `$callable` сгенерировала ожидаемые ошибки (т.е. предупреждения, уведомления и т.д.). В качестве `$type` укажем одну из констант `E_...`, то есть, например, `E_WARNING`. И если мы укажем `$message`, сообщение об ошибке также должно [соответствовать шаблону |#Assert::match]. Например: ```php Assert::error( @@ -166,7 +166,7 @@ Assert::error( ); ``` -Если обратный вызов генерирует больше ошибок, мы должны ожидать их все в точном порядке. В этом случае мы передаем массив в `$type`: +Если обратный вызов генерирует несколько ошибок, мы должны ожидать их все в точном порядке. В таком случае передадим в `$type` массив: ```php Assert::error(function () { @@ -179,108 +179,108 @@ Assert::error(function () { ``` .[note] -Если `$type` - имя класса, то это утверждение ведет себя так же, как и `Assert::exception()`. +Если в качестве `$type` вы укажете имя класса, он ведет себя так же, как `Assert::exception()`. Assert::noError(callable $callable) .[method] --------------------------------------------- -Проверяет, что функция `$callable` не выбрасывает никаких предупреждений/замечаний/ошибок или исключений PHP. Это полезно для проверки части кода, где нет других утверждений. +Проверяет, что функция `$callable` не сгенерировала никаких предупреждений, ошибок или исключений. Полезно для тестирования фрагментов кода, где нет других утверждений. -Assert::match(string $pattern, $actual, string $description=null) .[method] ---------------------------------------------------------------------------- -`$actual` должен соответствовать `$pattern`. Мы можем использовать два варианта шаблонов: регулярные выражения или подстановочные знаки. +Assert::match(string $pattern, $actual, ?string $description=null) .[method] +---------------------------------------------------------------------------- +`$actual` должен соответствовать шаблону `$pattern`. Мы можем использовать два варианта шаблонов: регулярные выражения или подстановочные знаки. -Если мы передаем регулярное выражение как `$pattern`, мы должны использовать `~` or `#` для его разделения. Другие разделители не поддерживаются. Например, тест, где `$var` должен содержать только шестнадцатеричные цифры: +Если в качестве `$pattern` мы передаем регулярное выражение, для его ограничения мы должны использовать `~` или `#`, другие разделители не поддерживаются. Например, тест, когда `$var` должен содержать только шестнадцатеричные цифры: ```php Assert::match('#^[0-9a-f]$#i', $var); ``` -Другой вариант похож на сравнение строк, но мы можем использовать некоторые дикие символы в `$pattern`: - -- `%a%` один или более любых символов, кроме символов конца строки -- `%a?%` ноль или более из чего угодно, кроме символов конца строки -- `%A%` один или более из всего, включая символы конца строки -- `%A?%` ноль или более любых символов, включая символы конца строки -- `%s%` один или более символов пробела, за исключением символов конца строки -- `%s?%` ноль или более символов пробела, за исключением символов конца строки -- `%S%` один или более символов, за исключением пробела -- `%S?%` ноль или более символов, за исключением пробела -- `%c%` один символ любого вида (кроме конца строки) +Второй вариант похож на обычное сравнение строк, но в `$pattern` мы можем использовать различные подстановочные знаки: + +- `%a%` один или несколько символов, кроме символов конца строки +- `%a?%` ноль или несколько символов, кроме символов конца строки +- `%A%` один или несколько символов, включая символы конца строки +- `%A?%` ноль или несколько символов, включая символы конца строки +- `%s%` один или несколько пробельных символов, кроме символов конца строки +- `%s?%` ноль или несколько пробельных символов, кроме символов конца строки +- `%S%` один или несколько символов, кроме пробельных символов +- `%S?%` ноль или несколько символов, кроме пробельных символов +- `%c%` любой один символ, кроме символа конца строки - `%d%` одна или несколько цифр -- `%d?%` ноль или более цифр +- `%d?%` ноль или несколько цифр - `%i%` знаковое целочисленное значение -- `%f%` число с плавающей запятой -- `%h%` одна или несколько HEX-цифр +- `%f%` число с плавающей точкой +- `%h%` одна или несколько шестнадцатеричных цифр - `%w%` один или несколько буквенно-цифровых символов -- `%%` один символ % +- `%%` символ % Примеры: ```php -# Again, hexadecimal number test +# Снова тест на шестнадцатеричное число Assert::match('%h%', $var); -# Generalized path to file and line number +# Обобщение пути к файлу и номера строки Assert::match('Error in file %a% on line %i%', $errorMessage); ``` -Assert::matchFile(string $file, $actual, string $description=null) .[method] ----------------------------------------------------------------------------- -Утверждение идентично [Assert::match() |#Assert-match], но шаблон загружается из `$file`. Это полезно для тестирования очень длинных строк. Тестовый файл становится читабельным. +Assert::matchFile(string $file, $actual, ?string $description=null) .[method] +----------------------------------------------------------------------------- +Утверждение идентично [#Assert::match()], но шаблон загружается из файла `$file`. Это полезно для тестирования очень длинных строк. Файл с тестом останется читаемым. Assert::fail(string $message, $actual=null, $expected=null) .[method] --------------------------------------------------------------------- -Это утверждение всегда терпит неудачу. Это просто удобно. По желанию мы можем передавать ожидаемые и фактические значения. +Это утверждение всегда не выполняется. Иногда это просто полезно. Опционально мы можем указать ожидаемое и фактическое значение. -Ожидания .[#toc-expectations] ------------------------------ -Если мы хотим сравнить более сложные структуры с непостоянными элементами, приведенных выше утверждений может быть недостаточно. Например, мы тестируем метод, который создает нового пользователя и возвращает его атрибуты в виде массива. Мы не знаем хэш-значения пароля, но знаем, что он должен быть шестнадцатеричной строкой. А о следующем элементе мы знаем только то, что это должен быть объект `DateTime`. +Ожидания +-------- +Когда мы хотим сравнить сложные структуры с неконстантными элементами, вышеуказанных утверждений может быть недостаточно. Например, мы тестируем метод, который создает нового пользователя и возвращает его атрибуты в виде массива. Значение хеша пароля мы не знаем, но знаем, что это должна быть шестнадцатеричная строка. А о другом элементе мы знаем только то, что это должен быть объект `DateTime`. -В этих случаях мы можем использовать `Tester\Expect` внутри параметра `$expected` методов `Assert::equal()` и `Assert::notEqual()`, с помощью которых можно легко описать структуру. +В этих ситуациях мы можем использовать `Tester\Expect` внутри параметра `$expected` методов `Assert::equal()` и `Assert::notEqual()`, с помощью которых можно легко описать структуру. ```php use Tester\Expect; Assert::equal([ - 'id' => Expect::type('int'), # we expect an integer + 'id' => Expect::type('int'), # ожидаем целое число 'username' => 'milo', - 'password' => Expect::match('%h%'), # we expect a string matching pattern - 'created_at' => Expect::type(DateTime::class), # we expect an instance of the class + 'password' => Expect::match('%h%'), # ожидаем строку, соответствующую шаблону + 'created_at' => Expect::type(DateTime::class), # ожидаем экземпляр класса ], User::create(123, 'milo', 'RandomPaSsWoRd')); ``` -С помощью `Expect` мы можем делать почти те же утверждения, что и с помощью `Assert`. Поэтому у нас есть такие методы, как `Expect::same()`, `Expect::match()`, `Expect::count()` и т.д. Кроме того, мы можем соединить их в цепочку следующим образом: +С `Expect` мы можем выполнять почти те же утверждения, что и с `Assert`. То есть нам доступны методы `Expect::same()`, `Expect::match()`, `Expect::count()` и т.д. Кроме того, их можно объединять в цепочку: ```php -Expect::type(MyIterator::class)->andCount(5); # we expect MyIterator and items count is 5 +Expect::type(MyIterator::class)->andCount(5); # ожидаем MyIterator и количество элементов 5 ``` -Или мы можем написать собственные обработчики утверждений. +Или мы можем писать собственные обработчики утверждений. ```php Expect::that(function ($value) { - # return false if expectation fails + # вернем false, если ожидание не оправдается }); ``` -Расследование неудачных утверждений .[#toc-failed-assertions-investigation] ---------------------------------------------------------------------------- -Tester показывает, где находится ошибка, когда утверждение терпит неудачу. Когда мы сравниваем сложные структуры, Tester создает дампы сравниваемых значений и сохраняет их в директории `output`. Например, когда воображаемый тест `Arrays.recursive.phpt` терпит неудачу, дампы будут сохранены следующим образом: +Исследование ошибочных утверждений +---------------------------------- +Когда утверждение не выполняется, Tester выводит, в чем ошибка. Если мы сравниваем сложные структуры, Tester создает дампы сравниваемых значений и сохраняет их в каталоге `output`. Например, при сбое вымышленного теста `Arrays.recursive.phpt` дампы будут сохранены следующим образом: ``` app/ └── tests/ ├── output/ - │ ├──── Arrays.recursive.actual # фактическое значение - │ └──── Arrays.recursive.expected # ожидаемое значение + │ ├── Arrays.recursive.actual # фактическое значение + │ └── Arrays.recursive.expected # ожидаемое значение │ - └── Arrays.recursive.phpt # неудачный тест + └── Arrays.recursive.phpt # неработающий тест ``` -Мы можем изменить имя директории на `Tester\Dumper::$dumpDir`. +Название каталога можно изменить через `Tester\Dumper::$dumpDir`. diff --git a/tester/ru/guide.texy b/tester/ru/guide.texy index fc73305514..e390c0a949 100644 --- a/tester/ru/guide.texy +++ b/tester/ru/guide.texy @@ -1,17 +1,17 @@ -Начало работы с Tester -********************** +Начинаем работать с Nette Tester +********************************
                                                                                                                            -Даже хорошие программисты допускают ошибки. Разница между хорошим программистом и плохим в том, что хороший сделает это только один раз и в следующий раз обнаружит это с помощью автоматизированных тестов. +Даже хорошие программисты делают ошибки. Разница между хорошим и плохим программистом в том, что хороший сделает ее только один раз, а в следующий раз обнаружит с помощью автоматизированных тестов. -- "Тот, кто не тестирует, обречен повторять собственные ошибки". (мудрая пословица). -- "Когда мы избавляемся от одной ошибки, появляется другая". (закон Мерфи) -- "Всякий раз, когда у вас возникает соблазн напечатать утверждение, вместо этого напишите его как тест". (Мартин Фаулер) +- "Кто не тестирует, обречен повторять свои ошибки." (пословица) +- "Как только мы избавляемся от одной ошибки, появляется другая." (закон Мерфи) +- "Всякий раз, когда у вас возникает желание вывести на экран переменную, лучше напишите тест." (Мартин Фаулер)
                                                                                                                            -Вы когда-нибудь писали следующий код на PHP? +Вы когда-нибудь писали в PHP подобный код? ```php $obj = new MyClass; @@ -20,40 +20,40 @@ $result = $obj->process($input); var_dump($result); ``` -Вы когда-нибудь сбрасывали результат вызова функции, просто чтобы проверить на глаз, что она возвращает то, что должна возвращать? Наверняка вы делаете это много раз в день. Положа руку на сердце, если все работает, вы удаляете этот код и ожидаете, что класс не будет сломан в будущем? Закон Мерфи гарантирует обратное :-) +То есть вы выводили результат вызова функции только для того, чтобы глазами проверить, возвращает ли она то, что должна? Наверняка вы делаете это много раз в день. Положа руку на сердце: в случае, если все работает правильно, удаляете ли вы этот код? Ожидаете ли вы, что класс в будущем не сломается? Законы Мерфи гарантируют обратное :-) -Фактически, вы написали тест. Она нуждается в небольшой модификации, чтобы не требовать нашей проверки, а просто уметь проверять себя. И если вы не удалили его, мы можем запустить его в любое время в будущем, чтобы проверить, что все по-прежнему работает так, как нужно. Со временем вы можете создать большое количество таких тестов, поэтому было бы неплохо, если бы мы могли запускать их автоматически. +По сути, вы написали тест. Его нужно лишь немного изменить, чтобы он не требовал визуальной проверки, а проверял себя сам. И если тест не удалить, его можно будет запустить в любое время в будущем и проверить, что все по-прежнему работает, как надо. Со временем таких тестов накопится большое количество, поэтому было бы полезно запускать их автоматически. -И Nette Tester как раз помогает в этом. +И со всем этим вам поможет именно Nette Tester. -Что делает Tester уникальным? .[#toc-what-makes-tester-unique] -============================================================== +Чем уникален Tester? +==================== -Написание тестов для Nette Tester уникально тем, что **каждый тест представляет собой стандартный PHP-скрипт, который может быть запущен отдельно.**. +Написание тестов для Nette Tester уникально тем, что **каждый тест — это обычный PHP-скрипт, который можно запустить самостоятельно.** -Поэтому, когда вы пишете тест, вы можете просто запустить его, чтобы проверить, есть ли ошибка в программировании. Если все работает правильно. Если нет, вы можете легко пройтись по программе в вашей IDE и поискать ошибку. Вы даже можете открыть ее в браузере. +То есть, когда вы пишете тест, вы можете его просто запустить и выяснить, нет ли в нем, например, ошибки программирования. Правильно ли он работает. Если нет, вы можете легко отладить его в своей IDE и найти ошибку. Вы даже можете открыть его в браузере. -И самое главное - запустив ее, вы выполните тест. Вы сразу же узнаете, прошел он или не прошел. Как? Давайте покажем здесь. Напишем тривиальный тест на использование массива PHP и сохраним его в файл `ArrayTest.php`: +И прежде всего — запустив его, вы выполните тест. Вы сразу узнаете, прошел он или не удался. Как? Давайте покажем. Напишем тривиальный тест работы с PHP-массивом и сохраним в файл `ArrayTest.php`: ```php .{file:ArrayTest.php} OK \-- -Попробуйте изменить утверждение на `Assert::contains('XXX', $stack);` в тесте и посмотрите, что произойдет при запуске: +Попробуйте в тесте изменить утверждение на неверное `Assert::contains('XXX', $stack);` и посмотрите, что произойдет при запуске: /--pre .[terminal] $ php ArrayTest.php @@ -73,53 +73,53 @@ $ php ArrayTest.php FAILURE \-- -Мы продолжаем рассказывать о написании [тестов |Writing Tests] в главе [Написание тестов |Writing Tests]. +Далее о написании продолжаем в главе [Написание тестов|writing-tests]. -Установка и требования .[#toc-installation-and-requirements] -============================================================ +Установка и требования +====================== -Минимальная требуемая версия PHP для Tester - 7.1 (подробнее см. таблицу [поддерживаемых версий PHP |#Supported-PHP-Versions] ). Предпочтительным способом установки является [Composer |best-practices:composer]: +Минимальная версия PHP, требуемая Tester, — 7.1 (подробнее в таблице [#Поддерживаемые версии PHP]). Предпочтительный способ установки — с помощью [Composer |best-practices:composer]: /--pre .[terminal] composer require --dev nette/tester \-- -Попробуйте запустить Nette Tester из командной строки (без аргументов он покажет только краткую справку): +Попробуйте запустить Nette Tester из командной строки (без параметров он просто выведет справку): /--pre .[terminal] vendor/bin/tester \-- -Запуск тестов .[#toc-running-tests] -=================================== +Запуск тестов +============= -По мере роста нашего приложения растет и количество тестов. Было бы непрактично запускать тесты по одному. Поэтому в Tester есть пакетный запуск тестов, который мы вызываем из командной строки. Параметром является каталог, в котором находятся тесты. Точка указывает на текущий каталог. +По мере роста приложения количество тестов растет вместе с ним. Было бы непрактично запускать тесты по одному. Поэтому Tester располагает пакетным средством запуска тестов, которое мы вызываем из командной строки. В качестве параметра указываем каталог, в котором находятся тесты. Точка означает текущий каталог. /--pre .[terminal] vendor/bin/tester . \-- -Программа Nette Tester перебирает указанный каталог и все подкаталоги и ищет тесты, которыми являются файлы `*.phpt` и `*Test.php`. Она также найдет наш тест `ArrayTest.php`, поскольку он соответствует маске. +Средство запуска тестов просканирует указанный каталог и все подкаталоги и найдет тесты, то есть файлы `*.phpt` и `*Test.php`. Он найдет и наш тест `ArrayTest.php`, поскольку он соответствует маске. -Затем он начинает тестирование. Каждый тест запускается как новый процесс PHP, поэтому он выполняется полностью изолированно от других. Он работает параллельно в нескольких потоках, что делает его чрезвычайно быстрым. Сначала запускаются тесты, которые не прошли во время предыдущего запуска, так что вы сразу узнаете, исправили ли вы ошибку. +Затем он начнет тестирование. Каждый тест запускается как новый процесс PHP, так что он выполняется полностью изолированно от остальных. Он запускает их параллельно в нескольких потоках, и благодаря этому он чрезвычайно быстр. И первыми он запускает тесты, которые при предыдущем запуске не удались, так что вы сразу узнаете, удалось ли вам исправить ошибку. -Для каждого выполненного теста бегунок печатает один символ, чтобы показать прогресс: +Во время выполнения тестов Tester непрерывно выводит результаты на терминал в виде символов: -- . - тест пройден -- s - тест пропущен -- F - тест не пройден +- . – тест пройден +- s – тест был пропущен (skipped) +- F – тест не удался (failed) -Вывод может выглядеть следующим образом: +Вывод может выглядеть так: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +PHP 8.3.2 (cli) | php -n | 8 threads ........s................F......... @@ -132,44 +132,44 @@ PHP 7.4.8 (cli) | php -n | 8 threads FAILURES! (35 tests, 1 failures, 1 skipped, 1.7 seconds) \-- -Было выполнено 35 тестов, один не прошел, один был пропущен. +Было запущено 35 тестов, один не удался, один был пропущен. -Мы продолжим в главе [Выполнение тестов |Running tests]. +Далее продолжаем в главе [Запуск тестов|running-tests]. -Режим наблюдения .[#toc-watch-mode] -=================================== +Режим Watch +=========== -Проводите ли вы рефакторинг кода? Или даже разрабатываете по методологии TDD (Test Driven Development)? Тогда вам понравится режим наблюдения. Tester следит за исходными кодами и запускает себя при их изменении. +Рефакторите код? Или даже разрабатываете по методике TDD (Test Driven Development)? Тогда вам понравится режим watch. Tester в нем отслеживает исходные коды и при изменении сам запускается. -Во время разработки у вас есть терминал в углу монитора, где на вас загорается зеленая строка состояния, и когда она вдруг становится красной, вы знаете, что только что сделали что-то нежелательное. На самом деле это отличная игра, в которой вы программируете и стараетесь придерживаться цвета. +При разработке у вас в углу монитора терминал, где на вас светит зеленая строка состояния, и когда она внезапно меняется на красную, вы знаете, что только что что-то сделали не совсем правильно. Это на самом деле отличная игра, когда вы программируете и стараетесь удержать цвет. -Режим Watch запускается с помощью параметра [--watch |running-tests#w-watch-path]. +Режим Watch запускается параметром [--watch |running-tests#-w --watch path]. -Отчеты CodeCoverage .[#toc-codecoverage-reports] -================================================ +Отчеты CodeCoverage +=================== -Tester может генерировать отчеты с обзором того, какой объем исходного кода охватывают тесты. Отчет может быть либо в человекочитаемом формате HTML, либо в формате Clover XML для дальнейшей машинной обработки. +Tester умеет генерировать отчеты с обзором того, сколько исходного кода покрывают тесты. Отчет может быть либо в читаемом человеком формате HTML, либо в Clover XML для дальнейшей машинной обработки. -См. "пример HTML-отчета":https://files.nette.org/tester/coverage.html с покрытием кода. +Посмотрите "пример HTML отчета":attachment:coverage.html с покрытием кода. -Поддерживаемые версии PHP .[#toc-supported-php-versions] -======================================================== +Поддерживаемые версии PHP +========================= -| Версия | совместимая с PHP +| версия | совместима с PHP |------------------|------------------- -| Tester 2.5 | PHP 8.0 - 8.2 -| Tester 2.4 | PHP 7.2 - 8.2 -| Tester 2.3 | PHP 7.1 - 8.0 -| Tester 2.1 - 2.2 | PHP 7.1 - 7.3 -| Tester 2.0 | PHP 5.6 - 7.3 -| Tester 1.7 | PHP 5.3 - 7.3 + HHVM 3.3+ -| Tester 1.6 | PHP 5.3 - 7.0 + HHVM -| Tester 1.3 - 1.5 | PHP 5.3 - 5.6 + HHVM -| Tester 0.9 - 1.2 | PHP 5.3 - 5.6 - -Применимо к последним версиям патчей. - -До версии 1.7 Tester поддерживал [HHVM |https://hhvm.com] 3.3.0 или новее (используя `tester -p hhvm`). Начиная с версии Tester 2.0 поддержка была прекращена. Использование было простым: +| Tester 2.5 | PHP 8.0 – 8.3 +| Tester 2.4 | PHP 7.2 – 8.2 +| Tester 2.3 | PHP 7.1 – 8.0 +| Tester 2.1 – 2.2 | PHP 7.1 – 7.3 +| Tester 2.0 | PHP 5.6 – 7.3 +| Tester 1.7 | PHP 5.3 – 7.3 + HHVM 3.3+ +| Tester 1.6 | PHP 5.3 – 7.0 + HHVM +| Tester 1.3 – 1.5 | PHP 5.3 – 5.6 + HHVM +| Tester 0.9 – 1.2 | PHP 5.3 – 5.6 + +Действительно для последней версии патча. + +Tester до версии 1.7 поддерживал также [HHVM |https://hhvm.com] 3.3.0 или выше (через `tester -p hhvm`). Поддержка была прекращена с версии Tester 2.0. diff --git a/tester/ru/helpers.texy b/tester/ru/helpers.texy index 87d907ec78..39ded31d56 100644 --- a/tester/ru/helpers.texy +++ b/tester/ru/helpers.texy @@ -1,31 +1,45 @@ -Помощники -********* +Вспомогательные классы +********************** DomQuery -------- -`Tester\DomQuery` это класс, расширяющий `SimpleXMLElement` с методами, облегчающими тестирование содержимого HTML или XML. +`Tester\DomQuery` — это класс, расширяющий `SimpleXMLElement` для легкого поиска в HTML или XML с помощью CSS-селекторов. ```php -# let's have an HTML document in $html that we load -$dom = Tester\DomQuery::fromHtml($html); - -# we can test the presence of elements using CSS selectors -Assert::true($dom->has('form#registration')); -Assert::true($dom->has('input[name="username"]')); -Assert::true($dom->has('input[type="submit"]')); - -# or select elements as array of DomQuery -$elems = $dom->find('input[data-autocomplete]'); +# создание DomQuery из HTML строки +$dom = Tester\DomQuery::fromHtml(' +
                                                                                                                            +

                                                                                                                            Title

                                                                                                                            +
                                                                                                                            Text
                                                                                                                            +
                                                                                                                            +'); + +# тест существования элементов с помощью CSS селекторов +Assert::true($dom->has('article.post')); +Assert::true($dom->has('h1')); + +# нахождение элементов как массива объектов DomQuery +$headings = $dom->find('h1'); +Assert::same('Title', (string) $headings[0]); + +# тест, соответствует ли элемент селектору (с версии 2.5.3) +$content = $dom->find('.content')[0]; +Assert::true($content->matches('div')); +Assert::false($content->matches('p')); + +# нахождение ближайшего предка, соответствующего селектору (с 2.5.5) +$article = $content->closest('.post'); +Assert::true($article->matches('article')); ``` FileMock -------- -`Tester\FileMock` эмулирует файлы в памяти, чтобы помочь вам протестировать код, который использует функции типа `fopen()`, `file_get_contents()` или `parse_ini_file()`. Например: +`Tester\FileMock` эмулирует файлы в памяти и облегчает таким образом тестирование кода, который использует функции `fopen()`, `file_get_contents()`, `parse_ini_file()` и подобные. Пример использования: ```php -# Tested class +# Тестируемый класс class Logger { public function __construct( @@ -39,14 +53,14 @@ class Logger } } -# New empty file +# Новый пустой файл $file = Tester\FileMock::create(''); $logger = new Logger($file); $logger->log('Login'); $logger->log('Logout'); -# Created content testing +# Тестируем созданное содержимое Assert::same("Login\nLogout\n", file_get_contents($file)); ``` @@ -65,17 +79,17 @@ class Entity $ent = new Entity; Assert::with($ent, function () { - Assert::true($this->enabled); // accessible private $ent->enabled + Assert::true($this->enabled); // доступное приватное $ent->enabled }); ``` Helpers::purge() .[filter] -------------------------- -Метод `purge()` создает указанный каталог и, если он уже существует, удаляет все его содержимое. Он удобен для создания временных каталогов. Например, в `tests/bootstrap.php`: +Метод `purge()` создает указанный каталог, а если он уже существует, удаляет все его содержимое. Полезен для создания временного каталога. Например, в `tests/bootstrap.php`: ```php -@mkdir(__DIR__ . '/tmp'); # @ - directory may already exist +@mkdir(__DIR__ . '/tmp'); # @ - каталог уже может существовать define('TempDir', __DIR__ . '/tmp/' . getmypid()); Tester\Helpers::purge(TempDir); @@ -84,25 +98,25 @@ Tester\Helpers::purge(TempDir); Environment::lock() .[filter] ----------------------------- -Тесты запускаются параллельно. Иногда нам не нужно перекрывать время выполнения тестов. Обычно тесты баз данных нуждаются в подготовке содержимого базы данных, и им нужно, чтобы ничто не мешало им во время выполнения теста. В этих случаях мы используем `Tester\Environment::lock($name, $dir)`: +Тесты запускаются параллельно. Иногда, однако, нам нужно, чтобы выполнение тестов не перекрывалось. Типично для тестов баз данных необходимо, чтобы тест подготовил содержимое базы данных, и другой тест во время его выполнения в базу данных не вмешивался. В этих тестах используем `Tester\Environment::lock($name, $dir)`: ```php Tester\Environment::lock('database', __DIR__ . '/tmp'); ``` -Первый аргумент - имя блокировки. Второй - путь к каталогу для сохранения блокировки. Сначала запускается тест, который получает блокировку. Остальные тесты должны подождать, пока он завершится. +Первый параметр — это имя замка, второй — путь к каталогу для сохранения замка. Тест, который получит замок первым, выполнится, остальные тесты должны подождать его завершения. Environment::bypassFinals() .[filter] ------------------------------------- -Классы или методы, помеченные как `final`, трудно тестировать. Вызов `Tester\Environment::bypassFinals()` в начале тестирования приводит к тому, что ключевые слова `final` удаляются при загрузке кода. +Классы или методы, помеченные как `final`, трудно тестировать. Вызов `Tester\Environment::bypassFinals()` в начале теста приводит к тому, что ключевые слова `final` во время загрузки кода опускаются. ```php require __DIR__ . '/bootstrap.php'; Tester\Environment::bypassFinals(); -class MyClass extends NormallyFinalClass # <-- NormallyFinalClass is not final anymore +class MyClass extends NormallyFinalClass # <-- NormallyFinalClass уже не final { // ... } @@ -111,15 +125,15 @@ class MyClass extends NormallyFinalClass # <-- NormallyFinalClass is not final Environment::setup() .[filter] ------------------------------ -- улучшает читабельность дампа ошибок (включена раскраска), иначе выводится стандартная трассировка стека PHP -- позволяет проверить, что утверждения были вызваны в тесте, иначе тесты без (например, забытых) утверждений тоже пройдут -- автоматически запускает сборщик покрытия кода при использовании `--coverage` (описано позже) -- печатает статус OK или FAILURE в конце скрипта +- улучшает читаемость вывода ошибок (включая раскрашивание), иначе выводится стандартный PHP stack trace +- включает проверку, что в тесте были вызваны утверждения, иначе тест без утверждений (например, забытых) также пройдет +- при использовании `--coverage` автоматически запускает сбор информации о выполненном коде (описано далее) +- выводит статус OK или FAILURE в конце скрипта Environment::setupFunctions() .[filter]{data-version:2.5} --------------------------------------------------------- -Создает глобальные функции `test()`, `setUp()` и `tearDown()`, на которые можно разбить тесты. +Создает глобальные функции `test()`, `testException()`, `setUp()` и `tearDown()`, на которые можно разбить тесты. ```php test('описание теста', function () { @@ -132,21 +146,21 @@ test('описание теста', function () { Environment::VariableRunner .[filter] ------------------------------------- -Позволяет узнать, был ли тест запущен напрямую или через Tester. +Позволяет узнать, был ли тест запущен напрямую или с помощью Tester. ```php if (getenv(Tester\Environment::VariableRunner)) { - # run by Tester + # запущено Tester } else { - # another way + # запущено иначе } ``` Environment::VariableThread .[filter] ------------------------------------- -Tester запускает тесты параллельно в заданном количестве потоков. Мы найдем номер потока в переменной окружения, когда нас это заинтересует: +Tester запускает тесты параллельно в заданном количестве потоков. Если нас интересует номер потока, мы узнаем его из переменной окружения: ```php -echo "I'm running in a thread number " . getenv(Tester\Environment::VariableThread); +echo "Выполняюсь в потоке номер " . getenv(Tester\Environment::VariableThread); ``` diff --git a/tester/ru/running-tests.texy b/tester/ru/running-tests.texy index 3ab154cdae..1d5a03df43 100644 --- a/tester/ru/running-tests.texy +++ b/tester/ru/running-tests.texy @@ -1,77 +1,77 @@ -Выполнение тестов -***************** +Запуск тестов +************* .[perex] -Наиболее заметной частью Nette Tester является программа запуска тестов из командной строки. Он чрезвычайно быстр и надежен, поскольку автоматически запускает все тесты как отдельные процессы параллельно в нескольких потоках. Он также может работать самостоятельно в так называемом сторожевом режиме. +Самой заметной частью Nette Tester является средство запуска тестов из командной строки. Оно необычайно быстрое и надежное, поскольку автоматически запускает все тесты как отдельные процессы и делает это параллельно в нескольких потоках. Также оно умеет запускаться само в так называемом режиме watch. -Программа запуска тестов Nette Tester вызывается из командной строки. В качестве параметра мы передадим каталог с тестами. Для текущего каталога достаточно ввести точку: +Средство запуска тестов вызываем из командной строки. В качестве параметра указываем каталог с тестами. Для текущего каталога достаточно указать точку: /--pre .[terminal] vendor/bin/tester . \-- -При вызове программа запуска тестов сканирует указанную директорию и все поддиректории и ищет тесты, которые представляют собой файлы `*.phpt` и `*Test.php`. Он также читает и оценивает их [аннотации |test-annotations], чтобы знать, какие из них и как запускать. +Средство запуска тестов просканирует указанный каталог и все подкаталоги и найдет тесты, то есть файлы `*.phpt` и `*Test.php`. Одновременно оно читает и оценивает их [аннотации|test-annotations], чтобы знать, какие из них и как запускать. -Затем он выполняет тесты. Для каждого выполненного теста бегунок печатает один символ, указывающий на прогресс: +Затем он запускает тесты. Во время выполнения тестов он непрерывно выводит результаты на терминал в виде символов: -- . - тест пройден -- s - тест пропущен -- F - тест не пройден +- . – тест пройден +- s – тест был пропущен (skipped) +- F – тест не удался (failed) -Вывод может выглядеть следующим образом: +Вывод может выглядеть, например, так: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +PHP 8.3.2 (cli) | php -n | 8 threads ........s.......................... OK (35 tests, 1 skipped, 1.7 seconds) \-- -При повторном запуске сначала запускаются тесты, которые не прошли во время предыдущего запуска, поэтому вы сразу узнаете, исправили ли вы ошибку. +При повторном запуске он сначала выполняет тесты, которые при предыдущем запуске не удались, так что вы сразу узнаете, удалось ли вам исправить ошибку. -Код выхода тестера равен нулю, если ни один тест не сработал. В противном случае - ненулевой. +Если ни один тест не завершился неудачей, код возврата Tester равен нулю. В противном случае код возврата ненулевой. .[warning] -Тестер запускает процессы PHP без `php.ini`. Более подробная информация в разделе [Собственный php.ini |#Own php.ini]. +Tester запускает процессы PHP без `php.ini`. Подробнее в разделе [#Собственный php.ini]. -Параметры командной строки .[#toc-command-line-options] -======================================================= +Параметры командной строки +========================== -Мы получаем обзор опций командной строки, запуская Тестер без параметров или с опцией `-h`: +Обзор всех опций командной строки мы получим, запустив Tester без параметров или с параметром `-h`: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 - -Использование: - tester [параметры] [<тестовый файл> | <каталог>]... - -Параметры: - -p <путь> Укажите интерпретатор PHP для запуска (по умолчанию: php). - -c Искать php.ini файл (или искать в директории) . - -C Использовать общесистемный php.ini. - -l | --log <путь> Запись журнала в файл <путь>. - -d <ключ=значение>... Определить INI-запись 'key' со значением 'value'. - -s Показать информацию о пропущенных тестах. - --stop-on-fail Остановить выполнение при первом сбое. - -j Выполнять заданий параллельно (по умолчанию: 8). - -o Указать формат вывода. - -w | --watch <путь> Каталог просмотра. - -i | --info Показать информацию об окружении тестов и выйти. - --setup <путь> Сценарий для настройки бегущей строки. - --temp <путь> Путь к временному каталогу. По умолчанию: sys_get_temp_dir(). - --colors [1|0] Включить или отключить цвета. - --coverage <путь> Генерировать отчет о покрытии кода в файл. - --coverage-src <путь> Путь к исходному коду. - -h | --help Это справка. + |_| \___ /___) |_| \___ |_|_\ v2.5.2 + +Usage: + tester [options] [ | ]... + +Options: + -p Specify PHP interpreter to run (default: php). + -c Look for php.ini file (or look in directory) . + -C Use system-wide php.ini. + -d ... Define INI entry 'key' with value 'value'. + -s Show information about skipped tests. + --stop-on-fail Stop execution upon the first failure. + -j Run jobs in parallel (default: 8). + -o (e.g. -o junit:output.xml) + Specify one or more output formats with optional file name. + -w | --watch Watch directory. + -i | --info Show tests environment info and exit. + --setup Script for runner setup. + --temp Path to temporary directory. Default by sys_get_temp_dir(). + --colors [1|0] Enable or disable colors. + --coverage Generate code coverage report to file. + --coverage-src Path to source code. + -h | --help This help. \-- @@ -86,26 +86,17 @@ tester -p /home/user/php-7.2.0-beta/php-cgi tests -c .[filter] ------------------- -Указывает, какой `php.ini` будет использоваться при выполнении тестов. По умолчанию php.ini не используется. Для получения дополнительной информации смотрите [Собственный php.ini |#Own php.ini]. +Указывает, какой `php.ini` будет использоваться при запуске тестов. По умолчанию никакой php.ini не используется. Подробнее в разделе [#Собственный php.ini]. -C .[filter] ------------ -Используется общесистемный `php.ini`. Поэтому на платформе UNIX все файлы `/etc/php/{sapi}/conf.d/*.ini` тоже. См. раздел [Собственный php.ini |#Own php.ini]. - - -'' -l | --log '' .[filter] --------------------------------- -Ход тестирования записывается в файл. Все неудачные, пропущенные, а также успешные тесты: - -/--pre .[terminal] -tester --log /var/log/tests.log tests -\-- +Используется системный `php.ini`. На UNIX также все соответствующие INI-файлы `/etc/php/{sapi}/conf.d/*.ini`. Подробнее в разделе [#Собственный php.ini]. -d .[filter] ------------------------ -Устанавливает значение директивы конфигурации PHP для тестов. Параметр может быть использован несколько раз. +Устанавливает значение PHP конфигурационной директивы для тестов. Параметр может быть использован несколько раз. /--pre .[terminal] tester -d max_execution_time=20 @@ -114,34 +105,36 @@ tester -d max_execution_time=20 -s --- -Будет показана информация о пропущенных тестах. +Отображается информация о пропущенных тестах. --stop-on-fail .[filter] ------------------------ -Тестер прекращает тестирование при первом неудачном тесте. +Tester останавливает тестирование при первом неудачном тесте. -j .[filter] ------------------ -Тесты запускаются в `` параллельно. Значение по умолчанию - 8. Если мы хотим запускать тесты последовательно, мы используем значение 1. +Указывает, сколько параллельных процессов с тестами будет запущено. Значение по умолчанию — 8. Если мы хотим, чтобы все тесты выполнялись последовательно, используем значение 1. --o .[filter] -------------------------------------- -Формат вывода. По умолчанию - консольный формат. +-o .[filter] +------------------------------------------------------- +Устанавливает формат вывода. По умолчанию используется формат для консоли. Вы можете указать имя файла, в который будет записан вывод (например, `-o junit:output.xml`). Опцию `-o` можно повторить несколько раз и сгенерировать таким образом несколько форматов одновременно. -- `console`: то же, что и по умолчанию, но ASCII-логотип в этом случае не печатается. +- `console`: совпадает с форматом по умолчанию, но в этом случае не отображается ASCII-логотип +- `console-lines`: похоже на console, но результат каждого теста указывается на отдельной строке с дополнительной информацией - `tap`: [формат TAP |https://en.wikipedia.org/wiki/Test_Anything_Protocol], подходящий для машинной обработки -- `junit`: формат JUnit XML, также подходящий для машинной обработки. -- `none`: ничего не печатается +- `junit`: XML-формат JUnit, также подходящий для машинной обработки +- `log`: Выводы хода тестирования. Все неудачные, пропущенные, а также успешные тесты +- `none`: ничего не выводится -'''-w | --watch ''' .[filter] ------------------------------------ -Тестер не завершается после завершения тестов, а продолжает запускать и наблюдать за PHP-файлами в заданном каталоге. При изменении параметров он запускает тесты снова. Параметр может быть использован несколько раз, если мы хотим контролировать несколько директорий. +''-w | --watch '' .[filter] +--------------------------------- +После завершения тестирования Tester не завершает работу, а остается запущенным и отслеживает PHP-файлы в указанном каталоге. При изменении он снова запускает тесты. Параметр может быть использован несколько раз, если мы хотим отслеживать несколько каталогов. -Это удобно при рефакторинге библиотеки или отладке тестов. +Полезно при рефакторинге библиотеки или отладке тестов. /--pre .[terminal] tester --watch src tests @@ -150,7 +143,7 @@ tester --watch src tests ''-i | --info'' .[filter] ------------------------- -Показывает информацию о среде выполнения теста. Например: +Отображает информацию о среде выполнения для тестов. Например: /--pre .[terminal] tester -p /usr/bin/php7.1 -c tests/php.ini --info @@ -177,13 +170,13 @@ Core, ctype, date, dom, ereg, fileinfo, filter, hash, ... --setup .[filter] ------------------------ -Тестер при запуске загружает заданный PHP-скрипт. В нем имеется переменная `Tester\Runner\Runner $runner`. Допустим, файл `tests/runner-setup.php`: +Tester при старте загружает указанный PHP-скрипт. В нем доступна переменная `Tester\Runner\Runner $runner`. Предположим, файл `tests/runner-setup.php` с содержимым: ```php $runner->outputHandlers[] = new MyOutputHandler; ``` -и запустим Тестер: +Запустим Tester: /--pre .[terminal] tester --setup tests/runner-setup.php tests @@ -192,47 +185,47 @@ tester --setup tests/runner-setup.php tests --temp .[filter] ----------------------- -Задает путь к директории для временных файлов тестера. Значение по умолчанию возвращает `sys_get_temp_dir()`. Если значение по умолчанию недействительно, вы будете уведомлены об этом. +Устанавливает путь к каталогу для временных файлов Tester. Значение по умолчанию возвращает `sys_get_temp_dir()`. Если значение по умолчанию недействительно, вы будете уведомлены. -Если мы не уверены, какой каталог используется, мы можем запустить Tester с параметром `--info`. +Если мы не уверены, какой каталог используется, запустим Tester с параметром `--info`. --colors 1|0 .[filter] ---------------------- -Тестер по умолчанию определяет терминал с поддержкой цвета и окрашивает его вывод. Эта опция дополняет автоопределение. Мы можем задать раскраску глобально с помощью системной переменной окружения `NETTE_TESTER_COLORS`. +По умолчанию Tester определяет цветной терминал и раскрашивает свой вывод. Эта опция переопределяет автоопределение. Глобально раскрашивание можно установить системной переменной окружения `NETTE_TESTER_COLORS`. --coverage .[filter] --------------------------- -Тестер сгенерирует отчет с обзором того, насколько исходный код покрыт тестами. Эта опция требует включенного расширения PHP [Xdebug |https://xdebug.org/] или [PCOV |https://github.com/krakjoe/pcov], или PHP 7 с PHPDBG SAPI, что быстрее. Расширение конечного файла определяет формат содержимого. HTML или Clover XML. +Tester сгенерирует отчет с обзором того, сколько исходного кода покрывают тесты. Эта опция требует установленного PHP-расширения [Xdebug |https://xdebug.org/], или [PCOV |https://github.com/krakjoe/pcov], или PHP 7 с PHPDBG SAPI, которое быстрее. Расширение целевого файла определяет его формат. Либо HTML, либо Clover XML. /--pre .[terminal] -tester tests --coverage coverage.html # HTML report -tester tests --coverage coverage.xml # Clover XML report +tester tests --coverage coverage.html # HTML отчет +tester tests --coverage coverage.xml # Clover XML отчет \-- -Приоритет при выборе механизма сбора следующий: +Приоритет выбора механизма следующий: 1) PCOV 2) PHPDBG 3) Xdebug -Обширные тесты могут выйти из строя во время выполнения PHPDBG из-за исчерпания памяти. Сбор данных о покрытии является операцией, занимающей много памяти. В этом случае может помочь вызов `Tester\CodeCoverage\Collector::flush()` внутри теста. Это позволит выгрузить собранные данные в файл и освободить память. Если сбор данных не выполняется или используется Xdebug, вызов не имеет никакого эффекта. +При использовании PHPDBG на объемных тестах можно столкнуться с отказом теста из-за исчерпания памяти. Сбор информации о покрытом коде требует много памяти. В этом случае нам поможет вызов `Tester\CodeCoverage\Collector::flush()` внутри теста. Он запишет собранные данные на диск и освободит память. Если сбор данных не происходит или используется Xdebug, вызов не имеет никакого эффекта. -"Пример HTML-отчета":https://files.nette.org/tester/coverage.html с покрытием кода. +"Пример HTML отчета":attachment:coverage.html с покрытием кода. --coverage-src .[filter] ------------------------------- -Мы используем его одновременно с опцией `--coverage`. Параметр `` это путь к исходному коду, для которого мы генерируем отчет. Его можно использовать многократно. +Используем одновременно с опцией `--coverage`. `` — это путь к исходным кодам, для которых генерируется отчет. Может использоваться повторно. -Собственный php.ini .[#toc-own-php-ini] -======================================= -Тестер запускает процессы PHP с опцией `-n`, что означает, что никакой `php.ini` не загружается (даже `/etc/php/conf.d/*.ini` в UNIX). Это обеспечивает одинаковое окружение для запуска тестов, но также деактивирует все внешние расширения PHP, обычно загружаемые системным PHP. +Собственный php.ini +=================== +Tester запускает процессы PHP с параметром `-n`, что означает, что никакой `php.ini` не загружается. В UNIX даже те из `/etc/php/conf.d/*.ini`. Это обеспечивает одинаковую среду для выполнения тестов, но также отключает все PHP-расширения, обычно загружаемые системным PHP. -Если вы хотите сохранить системную конфигурацию, используйте параметр `-C`. +Если вы хотите сохранить загрузку системных php.ini файлов, используйте параметр `-C`. -Если вам нужны какие-то расширения или специальные настройки INI, мы рекомендуем создать собственный файл `php.ini` и распределить его между тестами. Затем запускаем Тестер с параметром `-c`, например, `tester -c tests/php.ini`. INI-файл может выглядеть следующим образом: +Если вам нужны какие-либо расширения или специальные настройки INI для тестов, рекомендуем создать собственный файл `php.ini`, который будет распространяться с тестами. Tester затем запускаем с параметром `-c`, например, `tester -c tests/php.ini tests`, где INI-файл может выглядеть так: ```ini [PHP] @@ -243,4 +236,4 @@ extension=php_pdo_pgsql.dll memory_limit=512M ``` -Запуск Тестера с системой `php.ini` в UNIX, например, `tester -c /etc/php/cgi/php.ini`, не загружает другие INI из `/etc/php/conf.d/*.ini`. Это поведение PHP, а не Тестера. +Запуск Tester в UNIX с системным `php.ini`, например, `tester -c /etc/php/cli/php.ini`, не загрузит остальные INI из `/etc/php/conf.d/*.ini`. Это особенность PHP, а не Tester. diff --git a/tester/ru/test-annotations.texy b/tester/ru/test-annotations.texy index ccbf5d63e2..bcf106381c 100644 --- a/tester/ru/test-annotations.texy +++ b/tester/ru/test-annotations.texy @@ -1,10 +1,10 @@ -Аннотации к тестам -****************** +Аннотации тестов +**************** .[perex] -Аннотации определяют, как тесты будут обрабатываться [программой запуска тестов командной строки |running-tests]. Они записываются в начале файла теста. +Аннотации определяют, как будут обрабатываться тесты [средством запуска тестов из командной строки|running-tests]. Они записываются в начале файла с тестом. -Аннотации не чувствительны к регистру. Они также не имеют никакого эффекта, если тест запускается вручную как обычный PHP-скрипт. +В аннотациях не учитывается регистр букв. Также они не имеют никакого влияния, если тест запущен вручную как обычный PHP-скрипт. Пример: @@ -21,19 +21,19 @@ require __DIR__ . '/../bootstrap.php'; ``` -Test .[filter] +TEST .[filter] -------------- -На самом деле это не аннотация. Он только задает заголовок теста, который выводится при отказе или в логах. +Это, собственно, даже не аннотация, она просто определяет заголовок теста, который выводится при сбое или в лог. @skip .[filter] --------------- -Тест пропускается. Это удобно для временной деактивации теста. +Тест будет пропущен. Полезно для временного отключения тестов. @phpVersion .[filter] --------------------- -Тест будет пропущен, если он не запущен соответствующей версией PHP. Мы пишем аннотацию как `@phpVersion [operator] version`. Мы можем не указывать оператор, по умолчанию это `>=`. Примеры: +Тест будет пропущен, если он не запущен с соответствующей версией PHP. Аннотацию записываем как `@phpVersion [оператор] версия`. Оператор можно опустить, по умолчанию `>=`. Примеры: ```php /** @@ -46,7 +46,7 @@ Test .[filter] @phpExtension .[filter] ----------------------- -Тест будет пропущен, если все указанные расширения PHP не загружены. Несколько расширений могут быть записаны в одной аннотации, или мы можем использовать аннотацию несколько раз. +Тест будет пропущен, если не загружены все указанные PHP-расширения. Несколько расширений можно указать в одной аннотации или использовать ее несколько раз. ```php /** @@ -58,9 +58,9 @@ Test .[filter] @dataProvider .[filter] ----------------------- -Эта аннотация подходит, когда мы хотим запустить тест несколько раз, но с разными данными. (Не путать с одноименной аннотацией для [TestCase |TestCase#dataProvider]). +Если мы хотим запустить тестовый файл несколько раз, но с разными входными данными, полезна именно эта аннотация. (Не путайте с одноименной аннотацией для [TestCase |TestCase#dataProvider].) -Мы пишем аннотацию как `@dataProvider file.ini`. Путь к файлу INI является относительным к файлу теста. Тест запускается столько раз, сколько секций содержится в INI-файле. Предположим, что INI-файл `databases.ini`: +Записываем как `@dataProvider file.ini`, путь к файлу берется относительно файла с тестом. Тест будет запущен столько раз, сколько секций в INI-файле. Предположим, INI-файл `databases.ini`: ```ini [mysql] @@ -77,7 +77,7 @@ password = ****** dsn = "sqlite::memory:" ``` -и файл `database.phpt` в одном каталоге: +и в том же каталоге тест `database.phpt`: ```php /** @@ -87,11 +87,11 @@ dsn = "sqlite::memory:" $args = Tester\Environment::loadData(); ``` -Тест выполняется три раза, и `$args` будет содержать значения из секций `mysql`, `postgresql` или `sqlite`. +Тест будет запущен трижды, и `$args` будет содержать всегда значения из секции `mysql`, `postgresql` или `sqlite`. -Есть еще один вариант, когда мы пишем аннотации со знаком вопроса, как `@dataProvider? file.ini`. В этом случае тест будет пропущен, если INI-файл не существует. +Существует еще вариант, когда аннотацию записываем с вопросительным знаком как `@dataProvider? file.ini`. В этом случае тест будет пропущен, если INI-файл не существует. -Возможности аннотаций еще не все упомянуты. Мы можем написать условия после INI-файла. Тест запускается для заданной секции только в том случае, если все условия совпадают. Давайте расширим INI-файл: +Этим возможности аннотации не исчерпываются. За именем INI-файла мы можем указать условия, при которых тест для данной секции будет запущен. Расширим INI-файл: ```ini [mysql] @@ -113,7 +113,7 @@ password = ****** dsn = "sqlite::memory:" ``` -и будем использовать аннотацию с условием: +и используем аннотацию с условием: ```php /** @@ -121,9 +121,9 @@ dsn = "sqlite::memory:" */ ``` -Тест выполняется только один раз для секции `postgresql 9.1`. Другие разделы не соответствуют условиям. +Тест будет запущен только один раз, и то для секции `postgresql 9.1`. Остальные секции фильтром условия не пройдут. -Аналогично, мы можем передать путь к PHP-скрипту вместо INI. Он должен возвращать массив или Traversable. Файл `databases.php`: +Аналогично, вместо INI-файла мы можем сослаться на PHP-скрипт. Он должен вернуть массив или Traversable. Файл `databases.php`: ```php return [ @@ -142,29 +142,29 @@ return [ @multiple .[filter] ------------------- -Запишем это как `@multiple N`, где `N` - целое число. Тест выполняется ровно N раз. +Записываем как `@multiple N`, где `N` — целое число. Тест будет запущен ровно N раз. @testCase .[filter] ------------------- -Аннотация не имеет параметров. Мы используем ее, когда пишем тест в виде классов [TestCase]. В этом случае программа запуска тестов командной строки будет запускать отдельные методы в отдельных процессах и параллельно в нескольких потоках. Это может значительно ускорить весь процесс тестирования. +Аннотация не имеет параметров. Используем ее, если тесты пишем как классы [TestCase]. В этом случае средство запуска тестов из командной строки будет запускать отдельные методы в самостоятельных процессах и параллельно в нескольких потоках. Это может значительно ускорить весь процесс тестирования. @exitCode .[filter] ------------------- -Мы пишем его как `@exitCode N`, где `N` is the exit code of the test. For example if `exit(10)` вызывается в тесте, мы пишем аннотацию как `@exitCode 10`. Считается неудачей, если тест завершается с другим кодом. Код выхода 0 (ноль) проверяется, если мы опустим аннотацию +Записываем как `@exitCode N`, где `N` — код возврата запущенного теста. Если в тесте, например, вызывается `exit(10)`, аннотацию запишем как `@exitCode 10`, и если тест завершится с другим кодом, это считается сбоем. Если аннотацию не указать, проверяется код возврата 0 (ноль). @httpCode .[filter] ------------------- -Аннотация оценивается только в том случае, если бинарный PHP является CGI. В противном случае она игнорируется. Мы записываем ее как `@httpCode NNN`, где `NNN` - ожидаемый HTTP-код. HTTP код 200 будет проверен, если мы опустим аннотацию. Если мы запишем `NNN` как строку, оцениваемую как ноль, например, `any`, HTTP-код не будет проверяться вообще. +Аннотация применяется только если бинарный файл PHP — CGI. В противном случае игнорируется. Записываем как `@httpCode NNN`, где `NNN` — ожидаемый HTTP-код. Если аннотацию не указать, проверяется HTTP-код 200. Если `NNN` записать как строку, вычисляемую в ноль, например, `any`, HTTP-код не проверяется. -@outputMatch a @outputMatchFile .[filter] +@outputMatch и @outputMatchFile .[filter] ----------------------------------------- -Поведение аннотаций соответствует утверждениям `Assert::match()` и `Assert::matchFile()`. Но в стандартном выводе теста встречается паттерн. Подходящий случай использования - когда мы предполагаем, что тест завершится фатальной ошибкой, и нам нужно проверить его вывод. +Функции аннотаций совпадают с утверждениями `Assert::match()` и `Assert::matchFile()`. Шаблон (pattern) ищется в тексте, который тест отправил на свой стандартный вывод. Применение находит, если мы предполагаем, что тест завершится фатальной ошибкой, и нам нужно проверить его вывод. @phpIni .[filter] ----------------- -Устанавливает значения конфигурации INI для теста. Например, мы записываем его как `@phpIni precision=20` и он работает так же, как если бы мы передали значение из командной строки параметром `-d precision=20`. +Для теста устанавливает конфигурационные значения INI. Записываем, например, как `@phpIni precision=20` и работает так же, как если бы мы задали значение из командной строки через параметр `-d precision=20`. diff --git a/tester/ru/testcase.texy b/tester/ru/testcase.texy index 33f063b149..b29d7afe9f 100644 --- a/tester/ru/testcase.texy +++ b/tester/ru/testcase.texy @@ -2,9 +2,9 @@ TestCase ******** .[perex] -В простых тестах утверждения могут следовать одно за другим. Но иногда полезно заключить утверждения в тестовый класс и структурировать их таким образом. +В простых тестах утверждения могут следовать одно за другим. Иногда, однако, выгоднее упаковать утверждения в тестовый класс и таким образом их структурировать. -Класс должен быть потомком `Tester\TestCase`, и мы говорим о нем просто как о **testcase**. +Класс должен быть потомком `Tester\TestCase`, и упрощенно мы говорим о нем как о **testcase**. Класс должен содержать тестовые методы, начинающиеся на `test`. Эти методы будут запущены как тесты: ```php use Tester\Assert; @@ -22,11 +22,11 @@ class RectangleTest extends Tester\TestCase } } -# Run testing methods +# Запуск тестовых методов (new RectangleTest)->run(); ``` -Мы можем обогатить тесткейс методами `setUp()` и `tearDown()`. Они вызываются до/после каждого метода тестирования: +Так написанный тест можно далее обогатить методами `setUp()` и `tearDown()`. Они вызываются перед, соответственно, после каждого тестового метода: ```php use Tester\Assert; @@ -35,12 +35,12 @@ class NextTest extends Tester\TestCase { public function setUp() { - # Preparation + # Подготовка } public function tearDown() { - # Clean-up + # Очистка } public function testOne() @@ -54,14 +54,14 @@ class NextTest extends Tester\TestCase } } -# Run testing methods +# Запуск тестовых методов (new NextTest)->run(); /* -Method Calls Order ------------------- +Порядок вызова методов +---------------------- setUp() testOne() tearDown() @@ -72,9 +72,9 @@ tearDown() */ ``` -Если ошибка произойдет в фазе `setUp()` или `tearDown()`, тест будет провален. Если ошибка возникает в методе тестирования, то метод `tearDown()` вызывается в любом случае, но с подавленными в нем ошибками. +Если произойдет ошибка на этапе `setUp()` или `tearDown()`, тест в целом завершится неудачей. Если произойдет ошибка в тестовом методе, несмотря на это, метод `tearDown()` запустится, однако с подавлением ошибок в нем. -Мы рекомендуем писать аннотацию [@testCase |test-annotations#testCase] в начале теста, тогда программа запуска тестов командной строки будет запускать отдельные методы тесткейса в отдельных процессах и параллельно в нескольких потоках. Это может значительно ускорить весь процесс тестирования. +Рекомендуем в начало теста написать аннотацию [@testCase |test-annotations#testCase], тогда средство запуска тестов из командной строки будет запускать отдельные методы testcase в самостоятельных процессах и параллельно в нескольких потоках. Это может значительно ускорить весь процесс тестирования. /--php width = $width; $this->height = $height; @@ -53,7 +52,7 @@ class Rectangle } ``` -И создадим для него тест. Имя файла теста должно соответствовать маске `*Test.php` или `*.phpt`, мы выберем вариант `RectangleTest.php`: +И создадим для него тест. Имя файла с тестом должно соответствовать маске `*Test.php` или `*.phpt`, выберем, например, вариант `RectangleTest.php`: ```php .{file:tests/RectangleTest.php} @@ -62,31 +61,31 @@ use Tester\Assert; require __DIR__ . '/bootstrap.php'; -// общий продолговатый +// общий прямоугольник $rect = new Rectangle(10, 20); -Assert::same(200.0, $rect->getArea()); # мы проверим ожидаемые результаты +Assert::same(200.0, $rect->getArea()); # проверим ожидаемые результаты Assert::false($rect->isSquare()); ``` -Как вы видите, [методы утверждения |Assertions], такие как `Assert::same()`, используются для утверждения того, что фактическое значение совпадает с ожидаемым. +Как видите, так называемые [методы утверждений|assertions] вроде `Assert::same()` используются для подтверждения того, что фактическое значение соответствует ожидаемому значению. -Последний шаг - создание файла `bootstrap.php`. Он содержит общий код для всех тестов. Например, автозагрузка классов, конфигурация окружения, создание временной директории, хелперы и тому подобное. Каждый тест загружает бутстрап и уделяет внимание только тестированию. Бутстрап может выглядеть следующим образом: +Остался последний шаг, и это файл `bootstrap.php`. Он содержит код, общий для всех тестов, например, автозагрузку классов, конфигурацию среды, создание временного каталога, вспомогательные функции и тому подобное. Все тесты загружают bootstrap и далее занимаются только тестированием. Bootstrap может выглядеть следующим образом: ```php .{file:tests/bootstrap.php} OK \-- -Если мы изменим в тесте утверждение на false `Assert::same(123, $rect->getArea());`, произойдет следующее: +Если бы мы изменили в тесте утверждение на неверное `Assert::same(123, $rect->getArea());`, произошло бы следующее: /--pre .[terminal] $ php RectangleTest.php @@ -107,35 +106,35 @@ $ php RectangleTest.php \-- -При написании тестов полезно отлавливать все экстремальные ситуации. Например, если на входе ноль, отрицательное число, в других случаях пустая строка, null и т.д. Фактически, это заставляет вас думать и решать, как должен вести себя код в таких ситуациях. Затем тесты исправляют поведение. +При написании тестов хорошо охватить все крайние ситуации. Например, когда входом будет ноль, отрицательное число, в других случаях, например, пустая строка, null и т.д. На самом деле это заставляет вас задуматься и решить, как должен вести себя код в таких ситуациях. Тесты затем фиксируют поведение. -В нашем случае отрицательное значение должно вызвать исключение, которое мы проверяем с помощью [Assert::exception() |Assertions#Assert-exception]: +В нашем случае отрицательное значение должно выбросить исключение, что мы проверим с помощью [Assert::exception() |Assertions#Assert::exception]: ```php .{file:tests/RectangleTest.php} -// ширина не должна быть отрицательным числом +// ширина не должна быть отрицательной Assert::exception( fn() => new Rectangle(-1, 20), InvalidArgumentException::class, - 'Размер не должен быть отрицательным', + 'The dimension must not be negative.', ); ``` -И мы добавляем аналогичный тест для высоты. Наконец, мы проверяем, что `isSquare()` возвращает `true`, если оба измерения одинаковы. Попробуйте написать такие тесты в качестве упражнения. +И аналогичный тест добавим для высоты. Наконец, протестируем, что `isSquare()` вернет `true`, если оба размера одинаковы. Попробуйте в качестве упражнения написать такие тесты. -Хорошо организованные тесты .[#toc-well-arranged-tests] -======================================================= +Более читаемые тесты +==================== -Размер файла с тестами может увеличиться и быстро стать загроможденным. Поэтому целесообразно группировать отдельные тестируемые области в отдельные функции. +Размер файла с тестом может расти и быстро стать нечитаемым. Поэтому практично отдельные тестируемые области сгруппировать в самостоятельные функции. -Сначала мы покажем более простой, но элегантный вариант, используя глобальную функцию `test()`. Tester не создает ее автоматически, чтобы избежать коллизии, если в вашем коде есть функция с таким же именем. Он создается только методом `setupFunctions()`, который вы вызываете в файле `bootstrap.php`: +Сначала покажем более простой, но элегантный вариант, а именно с помощью глобальной функции `test()`. Tester ее не создает автоматически, чтобы не произошло коллизии, если бы у вас в коде была функция с таким же именем. Ее создаст метод `setupFunctions()`, который вызовите в файле `bootstrap.php`: ```php .{file:tests/bootstrap.php} Tester\Environment::setup(); Tester\Environment::setupFunctions(); ``` -Используя эту функцию, мы можем красиво разделить тестовый файл на именованные блоки. При выполнении функции метки будут отображаться одна за другой. +С помощью этой функции мы можем красиво разбить тестовый файл на именованные части. При запуске будут последовательно выводиться описания. ```php .{file:tests/RectangleTest.php} getArea()); Assert::false($rect->isSquare()); }); -test('general square', function () { +test('общий квадрат', function () { $rect = new Rectangle(5, 5); Assert::same(25.0, $rect->getArea()); Assert::true($rect->isSquare()); @@ -168,15 +167,15 @@ test('размеры не должны быть отрицательными', f }); ``` -Если вам нужно запустить код до или после каждого теста, передайте его в `setUp()` или `tearDown()`: +Если вам нужно перед или после каждого теста запустить код, передайте его функции `setUp()` соответственно `tearDown()`: ```php setUp(function () { - // код инициализации для запуска перед каждым test() + // инициализационный код, который запустится перед каждым test() }); ``` -Второй вариант - объектный. Мы создадим так называемый TestCase, который представляет собой класс, где отдельные единицы представлены методами, имена которых начинаются с test-. +Второй вариант — объектно-ориентированный. Мы создадим так называемый TestCase, то есть класс, где отдельные части представляют собой методы, названия которых начинаются на test–. ```php .{file:tests/RectangleTest.php} class RectangleTest extends Tester\TestCase @@ -208,25 +207,25 @@ class RectangleTest extends Tester\TestCase } } -// Run test methods +// Запуск тестовых методов (new RectangleTest)->run(); ``` -На этот раз мы использовали аннотацию `@throw` для проверки на исключения. Более подробную информацию смотрите в главе [TestCase]. +Для тестирования исключений мы на этот раз использовали аннотацию `@throws`. Больше вы узнаете в главе [TestCase]. -Функции-помощники .[#toc-helpers-functions] -=========================================== +Вспомогательные функции +======================= -Nette Tester включает в себя несколько классов и функций, которые могут облегчить вам тестирование, например, помощники для тестирования содержимого HTML-документа, для тестирования функций работы с файлами и так далее. +Nette Tester содержит несколько классов и функций, которые могут вам облегчить, например, тестирование содержимого HTML-документа, тестирование функций, работающих с файлами, и так далее. -Их описание вы можете найти на странице [Helpers]. +Их описание вы найдете на странице [Вспомогательные классы | helpers]. -Аннотирование и пропуск тестов .[#toc-annotation-and-skipping-tests] -==================================================================== +Аннотации и пропуск тестов +========================== -На выполнение тестов могут влиять аннотации в комментарии phpDoc в начале файла. Например, он может выглядеть следующим образом: +Выполнение тестов может быть подвержено влиянию аннотаций в виде phpDoc-комментария в начале файла. Он может выглядеть, например, так: ```php .{file:tests/RectangleTest.php} /** @@ -235,11 +234,11 @@ Nette Tester включает в себя несколько классов и */ ``` -Аннотации гласят, что тест должен выполняться только с PHP версии 7.2 или выше и при наличии PHP расширений pdo и pdo_pgsql. Эти аннотации контролируются [программой запуска тестов командной строки |running-tests], которая, если условия не выполняются, пропускает тест и помечает его буквой `s` - пропущен. Однако они не имеют никакого эффекта, когда тест выполняется вручную. +Указанные аннотации говорят, что тест должен быть запущен только с PHP версии 7.2 или выше и если присутствуют PHP-расширения pdo и pdo_pgsql. Этими аннотациями руководствуется [средство запуска тестов из командной строки|running-tests], которое в случае, если условия не выполнены, тест пропускает и в выводе помечает буквой `s` - skipped. Однако при ручном запуске теста они не имеют никакого влияния. -Описание аннотаций приведено в разделе [Аннотации тестов |Test Annotations]. +Описание аннотаций вы найдете на странице [Аннотации тестов|test-annotations]. -Тест также может быть пропущен на основании собственного условия с помощью `Environment::skip()`. Например, мы пропустим этот тест на Windows: +Тест можно пропустить также на основании выполнения собственного условия с помощью `Environment::skip()`. Например, так пропустим тесты на Windows: ```php if (defined('PHP_WINDOWS_VERSION_BUILD')) { @@ -248,10 +247,10 @@ if (defined('PHP_WINDOWS_VERSION_BUILD')) { ``` -Структура каталогов .[#toc-directory-structure] -=============================================== +Структура каталогов +=================== -Для немного больших библиотек или проектов мы рекомендуем разделить тестовый каталог на подкаталоги в соответствии с пространством имен тестируемого класса: +Рекомендуем у хотя бы немного больших библиотек или проектов разделить каталог с тестами еще на подкаталоги по пространству имен тестируемого класса: ``` └── tests/ @@ -269,22 +268,22 @@ if (defined('PHP_WINDOWS_VERSION_BUILD')) { └── ... ``` -Вы сможете запускать тесты из одного пространства имен т.е. подкаталога: +Вы сможете так запускать тесты из одного пространства имен, то есть подкаталога: /--pre .[terminal] tester tests/NamespaceOne \-- -Edge Cases .[#toc-edge-cases] -============================= +Специальные ситуации +==================== -Тест, который не вызывает ни одного метода утверждения, является подозрительным и будет оценен как ошибочный: +Тест, который не вызвал ни одного метода утверждения, подозрителен и будет оценен как ошибочный: /--pre .[terminal] Error: This test forgets to execute an assertion. \-- -Если тест без вызова утверждений действительно должен считаться корректным, вызовите, например, `Assert::true(true)`. +Если действительно тест без вызова утверждений должен считаться действительным, вызовите, например, `Assert::true(true)`. -Также коварным может быть использование `exit()` и `die()` для завершения теста с сообщением об ошибке. Например, `exit('Error in connection')` завершает тест с кодом выхода 0, который сигнализирует об успехе. Используйте `Assert::fail('Error in connection')`. +Также может быть коварно использовать `exit()` и `die()` для завершения теста с сообщением об ошибке. Например, `exit('Error in connection')` завершит тест с кодом возврата 0, что сигнализирует об успехе. Используйте `Assert::fail('Error in connection')`. diff --git a/tester/sl/@home.texy b/tester/sl/@home.texy index 2b320a5191..5b513c0c62 100644 --- a/tester/sl/@home.texy +++ b/tester/sl/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Nette Tester - Prijetno testiranje enot v PHP}} -{{description: Nette Tester je preprosto in hkrati zelo priročno orodje za testiranje kode PHP.}} +{{maintitle: Nette Tester – udobno testiranje v PHP}} +{{description: Nette Tester je preprosto, a zelo priročno orodje za testiranje PHP kode.}} diff --git a/tester/sl/@left-menu.texy b/tester/sl/@left-menu.texy index 6405722b38..b665b4b67e 100644 --- a/tester/sl/@left-menu.texy +++ b/tester/sl/@left-menu.texy @@ -1,8 +1,8 @@ -- [Začetek |guide] -- [Pisanje testov |Writing Tests] -- [Izvajanje testov |Running Tests] +- [Začenjamo z Nette Tester |guide] +- [Pisanje testov |writing-tests] +- [Zagon testov |running-tests] -- [Zagotovitve |Assertions] -- [Anotacije testov |Test Annotations] +- [Asercije |assertions] +- [Anotacije testov |test-annotations] - [TestCase |TestCase] -- [Pomočniki |Helpers] +- [Pomožni razredi |helpers] diff --git a/tester/sl/@menu.texy b/tester/sl/@menu.texy index a6e66a0bc1..5936b39d41 100644 --- a/tester/sl/@menu.texy +++ b/tester/sl/@menu.texy @@ -1,3 +1,3 @@ -- [Domov |@home] -- [Dokumentacija |Guide] +- [Uvod |@home] +- [Dokumentacija |guide] - "GitHub .[link-external]":https://github.com/nette/tester diff --git a/tester/sl/@meta.texy b/tester/sl/@meta.texy new file mode 100644 index 0000000000..e7b3539264 --- /dev/null +++ b/tester/sl/@meta.texy @@ -0,0 +1 @@ +{{sitename: Tester Dokumentacija}} diff --git a/tester/sl/assertions.texy b/tester/sl/assertions.texy index 1b1c792bae..c2d00f0df1 100644 --- a/tester/sl/assertions.texy +++ b/tester/sl/assertions.texy @@ -1,35 +1,35 @@ -Trditve -******* +Asercije +******** .[perex] -Trditve se uporabljajo za potrjevanje, da se dejanska vrednost ujema s pričakovano vrednostjo. To so metode `Tester\Assert`. +Asercije se uporabljajo za potrditev, da dejanska vrednost ustreza pričakovani vrednosti. Gre za metode razreda `Tester\Assert`. -Izberite najnatančnejše trditve. Je boljša `Assert::same($a, $b)` kot `Assert::true($a === $b)`, ker ob neuspehu prikaže smiselno sporočilo o napaki. V drugem primeru dobimo samo `false should be true` in ta ne pove ničesar o vsebini spremenljivk $a in $b. +Izbirajte čim bolj primerne asercije. Bolje je `Assert::same($a, $b)` kot `Assert::true($a === $b)`, ker ob neuspehu prikaže smiselno sporočilo o napaki. V drugem primeru samo `false should be true`, kar nam o vsebini spremenljivk `$a` in `$b` nič ne pove. -Večina trditev ima lahko tudi neobvezno `$description`, ki se prikaže v sporočilu o napaki, če pričakovanje ni uspešno. +Večina asercij lahko ima tudi neobvezen opis v parametru `$description`, ki se prikaže v sporočilu o napaki, če pričakovanje ne uspe. -Primeri predpostavljajo, da je definiran naslednji vzdevek razreda: +Primeri predpostavljajo ustvarjen alias: ```php use Tester\Assert; ``` -Assert::same($expected, $actual, string $description=null) .[method] --------------------------------------------------------------------- -`$expected` mora biti enak kot `$actual`. Je enak kot operator PHP `===`. +Assert::same($expected, $actual, ?string $description=null) .[method] +--------------------------------------------------------------------- +`$expected` mora biti identičen z `$actual`. Enako kot PHP operator `===`. -Assert::notSame($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------ -Nasproten kot `Assert::same()`, zato je enak operatorju PHP `!==`. +Assert::notSame($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------ +Nasprotje `Assert::same()`, torej enako kot PHP operator `!==`. -Assert::equal($expected, $actual, string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] ------------------------------------------------------------------------------------------------------------------------- -`$expected` mora biti enak kot `$actual`. Za razliko od `Assert::same()` se identiteta predmetov, vrstni red parov ključ => vrednost v poljih in mejno različna decimalna števila ne upoštevajo, kar je mogoče spremeniti z nastavitvijo `$matchIdentity` in `$matchOrder`. +Assert::equal($expected, $actual, ?string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] +------------------------------------------------------------------------------------------------------------------------- +`$expected` mora biti enak z `$actual`. Za razliko od `Assert::same()` se ignorira identiteta objektov, vrstni red parov ključ => vrednost v poljih in marginalno različna decimalna števila, kar je mogoče spremeniti z nastavitvijo `$matchIdentity` in `$matchOrder`. -Naslednji primeri so enaki z vidika `equal()`, ne pa tudi za `same()`: +Naslednji primeri so enaki z vidika `equal()`, vendar ne `same()`: ```php Assert::equal(0.3, 0.1 + 0.2); @@ -40,81 +40,81 @@ Assert::equal( ); ``` -Vendar pazite, da se polje `[1, 2]` in `[2, 1]` nista enaki, saj se razlikuje le vrstni red vrednosti, ne pa tudi pari ključ => vrednost. Polje `[1, 2]` lahko zapišemo tudi kot `[0 => 1, 1 => 2]` in zato `[1 => 2, 0 => 1]` bo veljalo za enako. +Vendar pozor, polji `[1, 2]` in `[2, 1]` nista enaki, ker se razlikujeta samo v vrstnem redu vrednosti, ne pa parov ključ => vrednost. Polje `[1, 2]` lahko zapišemo tudi kot `[0 => 1, 1 => 2]` in za enako se zato bo štelo `[1 => 2, 0 => 1]`. -Uporabite lahko tudi tako imenovana [pričakovanja |#expectations] v `$expected`. +Nadalje je mogoče v `$expected` uporabiti t.i. [#pričakovanja]. -Assert::notEqual($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------- -Nasprotno od `Assert::equal()`. +Assert::notEqual($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------- +Nasprotje `Assert::equal()`. -Assert::contains($needle, string|array $actual, string $description=null) .[method] ------------------------------------------------------------------------------------ -Če je `$actual` niz, mora vsebovati podredni niz `$needle`. Če je polje, mora vsebovati element `$needle` (primerja se strogo). +Assert::contains($needle, string|array $actual, ?string $description=null) .[method] +------------------------------------------------------------------------------------ +Če je `$actual` niz, mora vsebovati podniz `$needle`. Če je polje, mora vsebovati element `$needle` (primerja se strogo). -Assert::notContains($needle, string|array $actual, string $description=null) .[method] --------------------------------------------------------------------------------------- +Assert::notContains($needle, string|array $actual, ?string $description=null) .[method] +--------------------------------------------------------------------------------------- Nasprotje `Assert::contains()`. -Assert::hasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} -------------------------------------------------------------------------------------------------------- +Assert::hasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +-------------------------------------------------------------------------------------------------------- `$actual` mora biti polje in mora vsebovati ključ `$needle`. -Assert::notHasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} ----------------------------------------------------------------------------------------------------------- +Assert::notHasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +----------------------------------------------------------------------------------------------------------- `$actual` mora biti polje in ne sme vsebovati ključa `$needle`. -Assert::true($value, string $description=null) .[method] --------------------------------------------------------- +Assert::true($value, ?string $description=null) .[method] +--------------------------------------------------------- `$value` mora biti `true`, torej `$value === true`. -Assert::truthy($value, string $description=null) .[method] ----------------------------------------------------------- -`$value` mora biti resničen, zato izpolnjuje pogoj `if ($value) ...`. +Assert::truthy($value, ?string $description=null) .[method] +----------------------------------------------------------- +`$value` mora biti resničen, torej izpolni pogoj `if ($value) ...`. -Assert::false($value, string $description=null) .[method] ---------------------------------------------------------- +Assert::false($value, ?string $description=null) .[method] +---------------------------------------------------------- `$value` mora biti `false`, torej `$value === false`. -Assert::falsey($value, string $description=null) .[method] ----------------------------------------------------------- -`$value` mora biti falsey, zato izpolnjuje pogoj `if (!$value) ...`. +Assert::falsey($value, ?string $description=null) .[method] +----------------------------------------------------------- +`$value` mora biti neresničen, torej izpolni pogoj `if (!$value) ...`. -Assert::null($value, string $description=null) .[method] --------------------------------------------------------- +Assert::null($value, ?string $description=null) .[method] +--------------------------------------------------------- `$value` mora biti `null`, torej `$value === null`. -Assert::notNull($value, string $description=null) .[method] ------------------------------------------------------------ +Assert::notNull($value, ?string $description=null) .[method] +------------------------------------------------------------ `$value` ne sme biti `null`, torej `$value !== null`. -Assert::nan($value, string $description=null) .[method] -------------------------------------------------------- -`$value` ne sme biti številka. Za testiranje NAN uporabite samo `Assert::nan()`. Vrednost NAN je zelo specifična in trditve `Assert::same()` ali `Assert::equal()` se lahko obnašajo nepredvidljivo. +Assert::nan($value, ?string $description=null) .[method] +-------------------------------------------------------- +`$value` mora biti Not a Number. Za testiranje NAN vrednosti uporabljajte izključno `Assert::nan()`. Vrednost NAN je zelo specifična in aserciji `Assert::same()` ali `Assert::equal()` lahko delujeta nepričakovano. -Assert::count($count, Countable|array $value, string $description=null) .[method] ---------------------------------------------------------------------------------- +Assert::count($count, Countable|array $value, ?string $description=null) .[method] +---------------------------------------------------------------------------------- Število elementov v `$value` mora biti `$count`. Torej enako kot `count($value) === $count`. -Assert::type(string|object $type, $value, string $description=null) .[method] ------------------------------------------------------------------------------ -`$value` mora biti določene vrste. Kot `$type` lahko uporabimo niz: +Assert::type(string|object $type, $value, ?string $description=null) .[method] +------------------------------------------------------------------------------ +`$value` mora biti danega tipa. Kot `$type` lahko uporabimo niz: - `array` -- `list` - polje, indeksirano v naraščajočem vrstnem redu številskih ključev od nič +- `list` - polje, indeksirano po naraščajočem zaporedju numeričnih ključev od nič - `bool` - `callable` - `float` @@ -124,39 +124,39 @@ Assert::type(string|object $type, $value, string $description=null) .[method] - `resource` - `scalar` - `string` -- ime razreda ali predmeta neposredno, potem mora posredovati `$value instanceof $type` +- ime razreda ali neposredno objekt, potem mora biti `$value instanceof $type` -Assert::exception(callable $callable, string $class, string $message=null, $code=null) .[method] ------------------------------------------------------------------------------------------------- -Ob klicu `$callable` je treba zavreči izjemo primera `$class`. Če posredujemo `$message`, [se |#assert-match] mora sporočilo izjeme [ujemati |#assert-match]. In če posredujemo `$code`, mora biti koda izjeme enaka. +Assert::exception(callable $callable, string $class, ?string $message=null, $code=null) .[method] +------------------------------------------------------------------------------------------------- +Pri klicu `$callable` mora biti sprožena izjema razreda `$class`. Če navedemo `$message`, mora [ustrezati vzorcu |#Assert::match] tudi sporočilo izjeme in če navedemo `$code`, se morata strogo ujemati tudi kodi. -Na primer, ta preskus ni uspešen, ker se sporočilo izjeme ne ujema: +Naslednji test ne uspe, ker ne ustreza sporočilo izjeme: ```php Assert::exception( - fn() => throw new App\InvalidValueException('Zero value'), + fn() => throw new App\InvalidValueException('Vrednost je prenizka'), App\InvalidValueException::class, - 'Value is to low', + 'Vrednost je prenizka', ); ``` - `Assert::exception()` vrne vrnjeno izjemo, zato lahko testirate vgnezdeno izjemo. +`Assert::exception()` vrača sproženo izjemo, lahko tako testiramo tudi ugnezdeno izjemo. ```php $e = Assert::exception( - fn() => throw new MyException('Something is wrong', 0, new RuntimeException), + fn() => throw new MyException('Nekaj je narobe', 0, new RuntimeException), MyException::class, - 'Something is wrong', + 'Nekaj je narobe', ); Assert::type(RuntimeException::class, $e->getPrevious()); ``` -Assert::error(string $callable, int|string|array $type, string $message=null) .[method] ---------------------------------------------------------------------------------------- -Preveri, ali klic `$callable` ustvari pričakovane napake (tj. opozorila, obvestila itd.). Kot `$type` navedemo eno od konstant `E_...`, na primer `E_WARNING`. In če predamo `$message`, mora tudi sporočilo o napaki [ustrezati |#assert-match] vzorcu. For example: +Assert::error(string $callable, int|string|array $type, ?string $message=null) .[method] +---------------------------------------------------------------------------------------- +Preverja, da funkcija `$callable` generira pričakovane napake (tj. opozorila, obvestila itd). Kot `$type` navedemo eno od konstant `E_...`, torej na primer `E_WARNING`. In če navedemo `$message`, mora [ustrezati vzorcu |#Assert::match] tudi sporočilo o napaki. Na primer: ```php Assert::error( @@ -166,7 +166,7 @@ Assert::error( ); ``` -Če povratni klic ustvari več napak, moramo pričakovati vse napake v točno določenem vrstnem redu. V tem primeru posredujemo polje v `$type`: +Če callback generira več napak, jih moramo vse pričakovati v točnem vrstnem redu. V takem primeru predamo v `$type` polje: ```php Assert::error(function () { @@ -179,108 +179,108 @@ Assert::error(function () { ``` .[note] -Če je `$type` ime razreda, se ta trditev obnaša enako kot `Assert::exception()`. +Če kot `$type` navedete ime razreda, se obnaša enako kot `Assert::exception()`. Assert::noError(callable $callable) .[method] --------------------------------------------- -Preveri, ali funkcija `$callable` ne vrže nobenega opozorila/opozorila/pomote ali izjeme PHP. Uporabna je za testiranje dela kode, v katerem ni nobene druge trditve. +Preverja, da funkcija `$callable` ni generirala nobenega opozorila, napake ali izjeme. Uporabno za testiranje koščkov kode, kjer ni nobene druge asercije. -Assert::match(string $pattern, $actual, string $description=null) .[method] ---------------------------------------------------------------------------- -`$actual` se mora ujemati z `$pattern`. Uporabimo lahko dve različici vzorcev: regularne izraze ali nadomestne znake. +Assert::match(string $pattern, $actual, ?string $description=null) .[method] +---------------------------------------------------------------------------- +`$actual` mora ustrezati vzorcu `$pattern`. Uporabimo lahko dve varianti vzorcev: regularne izraze ali nadomestne znake. -Če posredujemo regularni izraz kot `$pattern`, moramo za njegovo razmejitev uporabiti `~` or `#`. Drugi delilniki niso podprti. Na primer test, pri katerem mora `$var` vsebovati samo šestnajstiške številke: +Če kot `$pattern` predamo regularni izraz, moramo za njegovo omejitev uporabiti `~` ali `#`, drugi ločilniki niso podprti. Na primer test, kjer `$var` mora vsebovati samo šestnajstiške števke: ```php Assert::match('#^[0-9a-f]$#i', $var); ``` -Druga različica je podobna primerjanju nizov, vendar lahko v `$pattern` uporabimo nekatere divje znake: - -- `%a%` enega ali več znakov, razen znakov za konec vrstice -- `%a?%` nič ali več znakov, razen znakov za konec vrstice -- `%A%` en ali več česar koli, vključno z znaki za konec vrstice -- `%A?%` nič ali več znakov, vključno z znaki za konec vrstice -- `%s%` en ali več znakov belega prostora, razen znakov za konec vrstice -- `%s?%` nič ali več znakov belega prostora, razen znakov za konec vrstice -- `%S%` en ali več znakov, razen belega presledka -- `%S?%` nič ali več znakov, razen belega presledka -- `%c%` en sam znak katere koli vrste (razen konca vrstice) -- `%d%` ena ali več številk -- `%d?%` nič ali več številk -- `%i%` podpisana celoštevilska vrednost -- `%f%` število s plavajočo vejico -- `%h%` ena ali več številk HEX +Druga varianta je podobna običajnemu primerjanju nizov, vendar lahko v `$pattern` uporabimo različne nadomestne znake: + +- `%a%` en ali več znakov, razen znakov konca vrstice +- `%a?%` noben ali več znakov, razen znakov konca vrstice +- `%A%` en ali več znakov, vključno z znaki konca vrstice +- `%A?%` noben ali več znakov, vključno z znaki konca vrstice +- `%s%` en ali več belih znakov, razen znakov konca vrstice +- `%s?%` noben ali več belih znakov, razen znakov konca vrstice +- `%S%` en ali več znakov, razen belih znakov +- `%S?%` noben ali več znakov, razen belih znakov +- `%c%` katerikoli en znak, razen znaka konca vrstice +- `%d%` ena ali več števk +- `%d?%` nobena ali več števk +- `%i%` predznačena celoštevilska vrednost +- `%f%` število z decimalno vejico +- `%h%` ena ali več šestnajstiških števk - `%w%` en ali več alfanumeričnih znakov -- `%%` en znak % +- `%%` znak % Primeri: ```php -# Again, hexadecimal number test +# Ponovno test za šestnajstiško število Assert::match('%h%', $var); -# Generalized path to file and line number -Assert::match('Error in file %a% on line %i%', $errorMessage); +# Posplošitev poti do datoteke in številke vrstice +Assert::match('Napaka v datoteki %a% v vrstici %i%', $errorMessage); ``` -Assert::matchFile(string $file, $actual, string $description=null) .[method] ----------------------------------------------------------------------------- -Trditev je enaka kot [Assert::match( |#assert-match] ), vendar se vzorec naloži iz `$file`. Uporabna je za testiranje zelo dolgih nizov. Testna datoteka stoji berljivo. +Assert::matchFile(string $file, $actual, ?string $description=null) .[method] +----------------------------------------------------------------------------- +Asercija je identična z [#Assert::match()], vendar se vzorec nalaga iz datoteke `$file`. To je uporabno za testiranje zelo dolgih nizov. Datoteka s testom ostane pregledna. Assert::fail(string $message, $actual=null, $expected=null) .[method] --------------------------------------------------------------------- -Ta trditev je vedno neuspešna. Je pač priročna. Po želji lahko posredujemo pričakovane in dejanske vrednosti. +Ta asercija vedno ne uspe. Včasih je to preprosto uporabno. Neobvezno lahko navedemo tudi pričakovano in dejansko vrednost. -Pričakovanja .[#toc-expectations] ---------------------------------- -Če želimo primerjati bolj zapletene strukture z nekonstantnimi elementi, zgornje trditve morda ne bodo zadostovale. Preizkušamo na primer metodo, ki ustvari novega uporabnika in vrne njegove atribute kot polje. Ne poznamo vrednosti hasha gesla, vemo pa, da mora biti to šestnajstiški niz. O naslednjem elementu pa vemo le to, da mora biti objekt `DateTime`. +Pričakovanja +------------ +Ko želimo primerjati bolj zapletene strukture z nekonstantnimi elementi, morda zgornje asercije ne bodo zadostovale. Na primer testiramo metodo, ki ustvari novega uporabnika in vrne njegove atribute kot polje. Vrednosti hasha gesla ne poznamo, vendar vemo, da mora biti šestnajstiški niz. In o drugem elementu vemo samo, da mora biti objekt `DateTime`. -V teh primerih lahko znotraj parametra `$expected` metod `Assert::equal()` in `Assert::notEqual()` uporabimo `Tester\Expect`, s katerim lahko preprosto opišemo strukturo. +V teh situacijah lahko uporabimo `Tester\Expect` znotraj `$expected` parametra metod `Assert::equal()` in `Assert::notEqual()`, s pomočjo katerih lahko strukturo enostavno opišemo. ```php use Tester\Expect; Assert::equal([ - 'id' => Expect::type('int'), # we expect an integer + 'id' => Expect::type('int'), # pričakujemo celo število 'username' => 'milo', - 'password' => Expect::match('%h%'), # we expect a string matching pattern - 'created_at' => Expect::type(DateTime::class), # we expect an instance of the class + 'password' => Expect::match('%h%'), # pričakujemo niz, ki ustreza vzorcu + 'created_at' => Expect::type(DateTime::class), # pričakujemo instanco razreda ], User::create(123, 'milo', 'RandomPaSsWoRd')); ``` -Z metodo `Expect` lahko podamo skoraj enake trditve kot z metodo `Assert`. Tako imamo metode, kot so `Expect::same()`, `Expect::match()`, `Expect::count()` itd. Poleg tega jih lahko verižimo, kot npr: +Z `Expect` lahko izvajamo skoraj enake asercije kot z `Assert`. Torej so nam na voljo metode `Expect::same()`, `Expect::match()`, `Expect::count()` itd. Poleg tega jih lahko verižimo: ```php -Expect::type(MyIterator::class)->andCount(5); # we expect MyIterator and items count is 5 +Expect::type(MyIterator::class)->andCount(5); # pričakujemo MyIterator in število elementov 5 ``` -Lahko pa napišemo tudi lastne upravljavce trditev. +Ali pa lahko pišemo lastne obdelovalce asercij. ```php Expect::that(function ($value) { - # return false if expectation fails + # vrnemo false, če pričakovanje ne uspe }); ``` -Preiskava neuspešnih trditev .[#toc-failed-assertions-investigation] --------------------------------------------------------------------- -Tester pokaže, kje je napaka, ko trditev ni uspešna. Ko primerjamo kompleksne strukture, Tester ustvari izpise primerjanih vrednosti in jih shrani v imenik `output`. Na primer, ko namišljeni test `Arrays.recursive.phpt` ne uspe, se izpisi shranijo na naslednji način: +Raziskovanje napačnih asercij +----------------------------- +Ko asercija ne uspe, Tester izpiše, v čem je napaka. Če primerjamo bolj zapletene strukture, Tester ustvari izpise primerjanih vrednosti in jih shrani v imenik `output`. Na primer pri neuspehu izmišljenega testa `Arrays.recursive.phpt` bodo izpisi shranjeni na naslednji način: ``` app/ └── tests/ ├── output/ - │ ├── Arrays.recursive.actual # actual value - │ └── Arrays.recursive.expected # expected value + │ ├── Arrays.recursive.actual # dejanska vrednost + │ └── Arrays.recursive.expected # pričakovana vrednost │ - └── Arrays.recursive.phpt # failing test + └── Arrays.recursive.phpt # neuspešen test ``` -Ime imenika lahko spremenimo z `Tester\Dumper::$dumpDir`. +Ime imenika lahko spremenimo preko `Tester\Dumper::$dumpDir`. diff --git a/tester/sl/guide.texy b/tester/sl/guide.texy index a7d4c6ead5..e384cc4d88 100644 --- a/tester/sl/guide.texy +++ b/tester/sl/guide.texy @@ -1,17 +1,17 @@ -Začetek dela s Testerjem +Začenjamo z Nette Tester ************************
                                                                                                                            -Tudi dobri programerji delajo napake. Razlika med dobrim in slabim programerjem je v tem, da jih bo dober programer naredil le enkrat, naslednjič pa jih bo odkril z uporabo avtomatiziranih testov. +Tudi dobri programerji delajo napake. Razlika med dobrim in slabim programerjem je v tem, da jo dober naredi samo enkrat in jo naslednjič odkrije s pomočjo avtomatiziranih testov. -- "Kdor ne testira, je obsojen na ponavljanje lastnih napak." (modri pregovor) -- "Ko se znebimo ene napake, se pojavi druga." (Murphyjev zakon) -- "Kadarkoli vas mika, da bi natisnili izjavo, jo namesto tega napišite kot test." (Martin Fowler) +- "Kdor ne testira, je obsojen ponavljati svoje napake." (pregovor) +- "Takoj ko se znebimo ene napake, se pojavi druga." (Murphyjev zakon) +- "Kadarkoli imate potrebo izpisati si na zaslon spremenljivko, raje napišite test." (Martin Fowler)
                                                                                                                            -Ste v PHP kdaj napisali naslednjo kodo? +Ste že kdaj napisali v PHP podobno kodo? ```php $obj = new MyClass; @@ -20,40 +20,40 @@ $result = $obj->process($input); var_dump($result); ``` -Ste že kdaj izpisali rezultat klica funkcije samo zato, da bi na oko preverili, ali vrne tisto, kar bi morala vrniti? Zagotovo to počnete večkrat na dan. Z roko na srcu, če vse deluje, izbrišete to kodo in pričakujete, da se razred v prihodnosti ne bo pokvaril? Murphyjev zakon zagotavlja nasprotno :-) +Torej ste si izpisali rezultat klica funkcije samo zato, da bi z očmi preverili, ali vrača to, kar naj bi? Zagotovo to počnete večkrat na dan. Bodimo iskreni: v primeru, da vse deluje pravilno, izbrišete to kodo? Pričakujete, da se razred v prihodnosti ne bo pokvaril? Murphyjevi zakoni zagotavljajo nasprotno :-) -Pravzaprav ste test napisali vi. Potrebuje rahlo spremembo, da ne bi zahteval našega pregleda, temveč da bi se preprosto lahko preveril sam. In če ga ne bi izbrisali, bi ga lahko kadar koli v prihodnosti zagnali in preverili, ali vse še vedno deluje, kot mora. Sčasoma lahko ustvarite veliko število teh testov, zato bi bilo lepo, če bi jih lahko izvajali samodejno. +V bistvu ste napisali test. Samo malo ga je treba urediti, da ne bo zahteval očesne kontrole, ampak da se bo preveril sam. In če testa ne izbrišete, ga lahko zaženete kadarkoli v prihodnosti in preverite, ali vse še vedno deluje, kot mora. Sčasoma boste ustvarili veliko takšnih testov, zato bi bilo dobro, da jih zaženete avtomatizirano. -In Nette Tester pomaga prav pri tem. +In pri vsem tem vam bo pomagal prav Nette Tester. -V čem je Tester edinstven? .[#toc-what-makes-tester-unique] -=========================================================== +V čem je Tester edinstven? +========================== -Pisanje testov za Nette Tester je edinstveno, saj je **kakršen test standardna skripta PHP, ki jo lahko zaženete samostojno.** +Pisanje testov za Nette Tester je edinstveno v tem, da **vsak test je običajen PHP skript, ki ga je mogoče samostojno zagnati.** -Ko napišete test, ga lahko preprosto zaženete in preverite, ali je prišlo do programske napake. Če deluje pravilno. Če ne, lahko program preprosto pregledate v svojem IDE in poiščete napako. Lahko ga celo odprete v brskalniku. +Torej, ko pišete test, ga lahko preprosto zaženete in ugotovite, ali je v njem morda programska napaka. Ali deluje pravilno. Če ne, ga lahko enostavno korakate v svojem IDE in iščete napako. Lahko ga celo odprete v brskalniku. -In kar je najpomembneje - z njegovim zagonom boste opravili test. Takoj boste izvedeli, ali je bil uspešno ali neuspešno opravljen. Kako? Pokažimo tukaj. Napišimo trivialen test za uporabo polja PHP in ga shranimo v datoteko `ArrayTest.php`: +In predvsem - s tem, ko ga zaženete, test izvedete. Takoj ugotovite, ali je uspel ali ne. Kako? Poglejmo si. Napišimo trivialen test dela s PHP poljem in ga shranimo v datoteko `ArrayTest.php`: ```php .{file:ArrayTest.php} OK \-- -Poskusite v testu spremeniti izjavo na `Assert::contains('XXX', $stack);` in opazujte, kaj se bo zgodilo ob zagonu: +Poskusite v testu spremeniti trditev na neresnično `Assert::contains('XXX', $stack);` in opazujte, kaj se zgodi ob zagonu: /--pre .[terminal] $ php ArrayTest.php -Failed: ['foo'] should contain 'XXX' +Neuspeh: ['foo'] bi moral vsebovati 'XXX' -in ArrayTest.php(17) Assert::contains('XXX', $stack); +v ArrayTest.php(17) Assert::contains('XXX', $stack); FAILURE \-- -O pisanju nadaljujemo v poglavju [Pisanje testov |Writing Tests]. +Nadalje o pisanju nadaljujemo v poglavju [Pisanje testov |writing-tests]. -Namestitev in zahteve .[#toc-installation-and-requirements] -=========================================================== +Namestitev in zahteve +===================== -Najmanjša zahtevana različica PHP, ki jo zahteva Tester, je 7.1 (za več podrobnosti glejte preglednico [podprtih različic PHP |#supported PHP versions] ). Najprimernejši način namestitve je [Composer |best-practices:composer]: +Minimalna različica PHP, ki jo zahteva Tester, je 7.1 (podrobneje v tabeli [#Podprte različice PHP]). Prednostni način namestitve je s pomočjo [Composer |best-practices:composer]: /--pre .[terminal] composer require --dev nette/tester \-- -Poskusite zagnati Nette Tester iz ukazne vrstice (brez argumentov bo prikazan le povzetek pomoči): +Poskusite si iz ukazne vrstice zagnati Nette Tester (brez parametrov samo izpiše pomoč): /--pre .[terminal] vendor/bin/tester \-- -Zagon testov .[#toc-running-tests] -================================== +Zagon testov +============ -Z rastjo naše aplikacije raste tudi število testov. Izvajanje testov enega za drugim ne bi bilo praktično. Zato ima Tester na voljo množični izvajalec testov, ki ga prikličemo iz ukazne vrstice. Parameter je imenik, v katerem se nahajajo testi. Pika označuje trenutni imenik. +Ko aplikacija raste, število testov raste z njo. Ne bi bilo praktično zaganjati testov enega za drugim. Zato Tester razpolaga z množičnim zaganjalnikom testov, ki ga kličemo iz ukazne vrstice. Kot parameter navedemo imenik, v katerem se nahajajo testi. Pika pomeni trenutni imenik. /--pre .[terminal] vendor/bin/tester . \-- -Prožilec Nette Tester preišče navedeni imenik in vse podimenike ter poišče teste, ki so datoteke `*.phpt` in `*Test.php`. Našel bo tudi naš test `ArrayTest.php`, saj se ujema z masko. +Zaganjalnik testov preišče navedeni imenik in vse podimenike ter poišče teste, kar so datoteke `*.phpt` in `*Test.php`. Najde tako tudi naš test `ArrayTest.php`, ker ustreza maski. -Nato začne s testiranjem. Vsak test zažene kot nov proces PHP, tako da teče popolnoma ločeno od drugih. Deluje vzporedno v več niti, zato je izjemno hiter. In najprej izvede teste, ki so bili med prejšnjim zagonom neuspešni, tako da boste takoj vedeli, ali ste napako odpravili. +Nato zažene testiranje. Vsak test zažene kot nov PHP proces, tako da poteka popolnoma izolirano od ostalih. Zažene jih vzporedno v več nitih in zato je izjemno hiter. In kot prve zažene teste, ki so pri prejšnjem zagonu spodleteli, tako da takoj izveste, ali ste napako uspeli popraviti. -Za vsak opravljen test izvajalec izpiše en znak, ki označuje napredek: +Med izvajanjem testov Tester sproti izpisuje rezultate na terminal kot znake: -- . - test je bil opravljen -- s - test je bil preskočen -- F - test ni uspel +- . – test je uspel +- s – test je bil preskočen (skipped) +- F – test je spodletel (failed) -Izpis je lahko videti takole: +Izpis lahko izgleda takole: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +PHP 8.3.2 (cli) | php -n | 8 threads ........s................F......... -- FAILED: greeting.phpt - Failed: 'Hello John' should be + Neuspeh: 'Hello John' bi moral biti ... 'Hello Peter' - in greeting.phpt(19) Assert::same('Hello Peter', $o->say('John')); + v greeting.phpt(19) Assert::same('Hello Peter', $o->say('John')); FAILURES! (35 tests, 1 failures, 1 skipped, 1.7 seconds) \-- -Izvedenih je bilo 35 testov, eden ni uspel, eden je bil preskočen. +Zagnanih je bilo 35 testov, eden je spodletel, eden je bil preskočen. -Nadaljujemo v poglavju [Izvajanje testov |Running tests]. +Nadalje nadaljujemo v poglavju [Zagon testov |running-tests]. -Način opazovanja .[#toc-watch-mode] -=================================== +Watch način +=========== -Ali kodo refaktorirate? Ali sploh razvijate po metodologiji TDD (Test Driven Development)? Potem vam bo všeč način opazovanja. Tester spremlja izvorno kodo in se sam zažene, ko se ta spremeni. +Refaktorirate kodo? Ali celo razvijate po metodologiji TDD (Test Driven Development)? Potem vam bo všeč watch način. Tester v njem spremlja izvorne kode in ob spremembi se sam zažene. -Med razvojem imate v kotu monitorja terminal, v katerem se vam prižge zelena vrstica stanja, in ko nenadoma postane rdeča, veste, da ste pravkar naredili nekaj neželenega. To je pravzaprav odlična igra, v kateri programirate in se poskušate držati barve. +Pri razvoju tako imate v kotu monitorja terminal, kjer na vas sveti zelena statusna vrstica, in ko se nenadoma spremeni v rdečo, veste, da ste pravkar nekaj naredili ne povsem dobro. Je pravzaprav zanimiva igra, kjer programirate in poskušate ohraniti barvo. -Način opazovanja zaženete s parametrom [--watch |running-tests#w-watch-path]. +Watch način se zažene s parametrom [--watch |running-tests#-w --watch path]. -Poročila CodeCoverage .[#toc-codecoverage-reports] -================================================== +CodeCoverage poročila +===================== -Tester lahko ustvari poročila s pregledom, koliko izvorne kode pokrivajo testi. Poročilo je lahko v človeku berljivem formatu HTML ali Clover XML za nadaljnjo strojno obdelavo. +Tester zna generirati poročila s pregledom, koliko izvorne kode testi pokrivajo. Poročilo je lahko bodisi v človeško berljivem formatu HTML ali Clover XML za nadaljnjo strojno obdelavo. -Oglejte si "vzorec poročila HTML":https://files.nette.org/tester/coverage.html s pokritostjo kode. +Poglejte si "vzorec HTML poročila":attachment:coverage.html s pokritostjo kode. -Podprte različice PHP .[#toc-supported-php-versions] -==================================================== +Podprte različice PHP +===================== -| različica | združljiva s PHP +| različica | združljivo s PHP |------------------|------------------- -| Tester 2.5 | PHP 8.0 - 8.2 -| Tester 2.4 | PHP 7.2 - 8.2 -| Tester 2.3 | PHP 7.1 - 8.0 -| Tester 2.1 - 2.2 | PHP 7.1 - 7.3 -| Tester 2.0 | PHP 5.6 - 7.3 -| Tester 1.7 | PHP 5.3 - 7.3 + HHVM 3.3+ -| Tester 1.6 | PHP 5.3 - 7.0 + HHVM -| Tester 1.3 - 1.5 | PHP 5.3 - 5.6 + HHVM -| Tester 0.9 - 1.2 | PHP 5.3 - 5.6 - -Velja za najnovejše različice popravkov. - -Do različice 1.7 je Tester podpiral [HHVM |https://hhvm.com] 3.3.0 ali novejšo različico (z uporabo `tester -p hhvm`). Od različice Tester 2.0 je podpora ukinjena. Uporaba je bila preprosta: +| Tester 2.5 | PHP 8.0 – 8.3 +| Tester 2.4 | PHP 7.2 – 8.2 +| Tester 2.3 | PHP 7.1 – 8.0 +| Tester 2.1 – 2.2 | PHP 7.1 – 7.3 +| Tester 2.0 | PHP 5.6 – 7.3 +| Tester 1.7 | PHP 5.3 – 7.3 + HHVM 3.3+ +| Tester 1.6 | PHP 5.3 – 7.0 + HHVM +| Tester 1.3 – 1.5 | PHP 5.3 – 5.6 + HHVM +| Tester 0.9 – 1.2 | PHP 5.3 – 5.6 + +Velja za zadnjo patch različico. + +Tester do različice 1.7 je podpiral tudi [HHVM |https://hhvm.com] 3.3.0 ali višji (preko `tester -p hhvm`). Podpora je bila od različice Testerja 2.0 ukinjena. diff --git a/tester/sl/helpers.texy b/tester/sl/helpers.texy index 87c3e3ef08..9bc2c5f10a 100644 --- a/tester/sl/helpers.texy +++ b/tester/sl/helpers.texy @@ -1,31 +1,45 @@ -Pomočniki -********* +Pomožni razredi +*************** -DomQuery .[#toc-domquery] -------------------------- -`Tester\DomQuery` je razred, ki razširja `SimpleXMLElement` z metodami, ki olajšajo testiranje vsebine HTML ali XML. +DomQuery +-------- +`Tester\DomQuery` je razred, ki razširja `SimpleXMLElement` z enostavnim iskanjem v HTML ali XML s pomočjo CSS selektorjev. ```php -# let's have an HTML document in $html that we load -$dom = Tester\DomQuery::fromHtml($html); - -# we can test the presence of elements using CSS selectors -Assert::true($dom->has('form#registration')); -Assert::true($dom->has('input[name="username"]')); -Assert::true($dom->has('input[type="submit"]')); - -# or select elements as array of DomQuery -$elems = $dom->find('input[data-autocomplete]'); +# ustvarjanje DomQuery iz HTML niza +$dom = Tester\DomQuery::fromHtml(' +
                                                                                                                            +

                                                                                                                            Naslov

                                                                                                                            +
                                                                                                                            Besedilo
                                                                                                                            +
                                                                                                                            +'); + +# test obstoja elementov s pomočjo CSS selektorjev +Assert::true($dom->has('article.post')); +Assert::true($dom->has('h1')); + +# iskanje elementov kot polje DomQuery objektov +$headings = $dom->find('h1'); +Assert::same('Naslov', (string) $headings[0]); + +# test, ali element ustreza selektorju (od različice 2.5.3) +$content = $dom->find('.content')[0]; +Assert::true($content->matches('div')); +Assert::false($content->matches('p')); + +# iskanje najbližjega prednika, ki ustreza selektorju (od 2.5.5) +$article = $content->closest('.post'); +Assert::true($article->matches('article')); ``` -FileMock .[#toc-filemock] -------------------------- -`Tester\FileMock` emulira datoteke v pomnilniku in vam pomaga pri testiranju kode, ki uporablja funkcije, kot so `fopen()`, `file_get_contents()` ali `parse_ini_file()`. Na primer: +FileMock +-------- +`Tester\FileMock` emulira datoteke v pomnilniku in tako olajša testiranje kode, ki uporablja funkcije `fopen()`, `file_get_contents()`, `parse_ini_file()` in podobne. Primer uporabe: ```php -# Tested class +# Testirani razred class Logger { public function __construct( @@ -39,21 +53,21 @@ class Logger } } -# New empty file +# Nova prazna datoteka $file = Tester\FileMock::create(''); $logger = new Logger($file); -$logger->log('Login'); -$logger->log('Logout'); +$logger->log('Prijava'); +$logger->log('Odjava'); -# Created content testing -Assert::same("Login\nLogout\n", file_get_contents($file)); +# Testiramo ustvarjeno vsebino +Assert::same("Prijava\nOdjava\n", file_get_contents($file)); ``` Assert::with() .[filter] ------------------------ -To ni trditev, temveč pomočnik za testiranje zasebnih metod in lastnosti objektov. +Ne gre za asercijo, ampak pomočnika za testiranje zasebnih metod in lastnosti objektov. ```php class Entity @@ -65,17 +79,17 @@ class Entity $ent = new Entity; Assert::with($ent, function () { - Assert::true($this->enabled); // dostopno zasebno $ent->enabled + Assert::true($this->enabled); // dostopna zasebna $ent->enabled }); ``` Helpers::purge() .[filter] -------------------------- -Metoda `purge()` ustvari določen imenik in, če ta že obstaja, izbriše njegovo celotno vsebino. Metoda je priročna za ustvarjanje začasnih imenikov. Na primer v `tests/bootstrap.php`: +Metoda `purge()` ustvari navedeni imenik, in če že obstaja, izbriše celotno njegovo vsebino. Uporabno za ustvarjanje začasnega imenika. Na primer v `tests/bootstrap.php`: ```php -@mkdir(__DIR__ . '/tmp'); # @ - directory may already exist +@mkdir(__DIR__ . '/tmp'); # @ - imenik že lahko obstaja define('TempDir', __DIR__ . '/tmp/' . getmypid()); Tester\Helpers::purge(TempDir); @@ -84,25 +98,25 @@ Tester\Helpers::purge(TempDir); Environment::lock() .[filter] ----------------------------- -Testi se izvajajo vzporedno. Včasih ni treba, da se izvajanje testov prekriva. Običajno morajo testi podatkovne zbirke pripraviti vsebino podatkovne zbirke in jih med izvajanjem testa ne sme nič motiti. V teh primerih uporabimo `Tester\Environment::lock($name, $dir)`: +Testi se izvajajo vzporedno. Včasih pa potrebujemo, da se izvajanje testov ne prekriva. Tipično pri podatkovnih testih je nujno, da si test pripravi vsebino podatkovne baze in da mu drug test med izvajanjem ne posega v podatkovno bazo. V teh testih uporabimo `Tester\Environment::lock($name, $dir)`: ```php Tester\Environment::lock('database', __DIR__ . '/tmp'); ``` -Prvi argument je ime ključavnice. Drugi je pot do imenika za shranjevanje ključavnice. Najprej se izvede test, ki pridobi ključavnico. Drugi testi morajo počakati, da se zaključi. +Prvi parameter je ime ključavnice, drugi je pot do imenika za shranjevanje ključavnice. Test, ki prvi pridobi ključavnico, se izvede, ostali testi morajo počakati na njegovo dokončanje. Environment::bypassFinals() .[filter] ------------------------------------- -Razrede ali metode, označene kot `final`, je težko testirati. Klicanje `Tester\Environment::bypassFinals()` v testnem začetku povzroči, da se ključne besede `final` med nalaganjem kode odstranijo. +Razredi ali metode, označene kot `final`, se težko testirajo. Klic `Tester\Environment::bypassFinals()` na začetku testa povzroči, da se ključne besede `final` med nalaganjem kode izpustijo. ```php require __DIR__ . '/bootstrap.php'; Tester\Environment::bypassFinals(); -class MyClass extends NormallyFinalClass # <-- NormallyFinalClass is not final anymore +class MyClass extends NormallyFinalClass # <-- NormallyFinalClass ni več final { // ... } @@ -111,18 +125,18 @@ class MyClass extends NormallyFinalClass # <-- NormallyFinalClass is not final Environment::setup() .[filter] ------------------------------ -- izboljša berljivost izpisa napak (vključena obarvanost), sicer se privzeto izpiše sled sklada PHP -- omogoča preverjanje, ali so bile v testu poklicane trditve, sicer testi brez (npr. pozabljenih) trditev tudi preidejo -- samodejno zažene zbiralnik pokritosti kode, če se uporablja `--coverage` (opisano pozneje) -- na koncu skripte izpiše stanje OK ali FAILURE +- izboljša berljivost izpisa napak (vključno z barvanjem), sicer je izpisan privzeti PHP stack trace +- vklopi preverjanje, ali so bile v testu klicane asercije, sicer test brez asercij (na primer pozabljenih) prav tako uspe +- pri uporabi `--coverage` samodejno zažene zbiranje informacij o zagnani kodi (opisano naprej) +- izpiše stanje OK ali FAILURE na koncu skripta Environment::setupFunctions() .[filter]{data-version:2.5} --------------------------------------------------------- -Ustvari globalne funkcije `test()`, `setUp()` in `tearDown()`, v katere lahko razdelite teste. +Ustvari globalne funkcije `test()`, `testException()`, `setUp()` in `tearDown()`, v katere lahko členite teste. ```php -test('test description', function () { +test('opis testa', function () { Assert::same(123, foo()); Assert::false(bar()); // ... @@ -132,21 +146,21 @@ test('test description', function () { Environment::VariableRunner .[filter] ------------------------------------- -Omogoča ugotoviti, ali je bil test izveden neposredno ali prek preizkuševalnika. +Omogoča ugotoviti, ali je bil test zagnan neposredno ali s pomočjo Testerja. ```php if (getenv(Tester\Environment::VariableRunner)) { - # run by Tester + # zagnano s Testerjem } else { - # another way + # zagnano drugače } ``` Environment::VariableThread .[filter] ------------------------------------- -Tester izvaja teste vzporedno v danem številu niti. Število niti bomo poiskali v okoljski spremenljivki, ko nas bo to zanimalo: +Tester zažene teste vzporedno v navedenem številu niti. Če nas zanima številka niti, jo ugotovimo iz spremenljivke okolja: ```php -echo "I'm running in a thread number " . getenv(Tester\Environment::VariableThread); +echo "Tečem v niti številka " . getenv(Tester\Environment::VariableThread); ``` diff --git a/tester/sl/running-tests.texy b/tester/sl/running-tests.texy index 716d247f7c..3c173a5039 100644 --- a/tester/sl/running-tests.texy +++ b/tester/sl/running-tests.texy @@ -1,55 +1,55 @@ -Izvajanje testov -**************** +Zagon testov +************ .[perex] -Najbolj viden del programa Nette Tester je program za izvajanje testov v ukazni vrstici. Je izjemno hiter in robusten, saj samodejno zažene vse teste kot ločene procese vzporedno v več niti. Prav tako se lahko sam izvaja v tako imenovanem načinu opazovanja. +Najbolj vidna komponenta Nette Testerja je zaganjalnik testov iz ukazne vrstice. Je izjemno hiter in robusten, saj samodejno zažene vse teste kot ločene procese in to vzporedno v več nitih. Prav tako se zna zagnati sam v t.i. načinu `watch`. -Nette Tester Test Runner se sproži iz ukazne vrstice. Kot parameter bomo posredovali imenik testov. Za trenutni imenik vpišemo samo piko: +Zaganjalnik testov kličemo iz ukazne vrstice. Kot parameter navedemo imenik s testi. Za trenutni imenik je dovolj vnesti piko: /--pre .[terminal] vendor/bin/tester . \-- -Ko ga prikličemo, bo testni izvajalec pregledal navedeni imenik in vse podimenike ter poiskal teste, ki so datoteke `*.phpt` in `*Test.php`. Prav tako prebere in oceni njihove [opombe, |test-annotations] da ve, katere in kako naj jih izvede. +Zaganjalnik testov preišče navedeni imenik in vse podimenike ter poišče teste, kar so datoteke `*.phpt` in `*Test.php`. Hkrati bere in vrednoti njihove [opombe |test-annotations], da ve, katere in kako jih zagnati. -Nato izvede teste. Za vsak opravljen test izvajalec izpiše en znak, ki označuje napredek: +Nato zažene teste. Med izvajanjem testov sproti izpisuje rezultate na terminal kot znake: -- . - test je bil opravljen -- s - test je bil preskočen -- F - test ni uspel +- . – test je uspešen +- s – test je bil preskočen (skipped) +- F – test je neuspešen (failed) -Izpis je lahko videti takole: +Izpis lahko izgleda na primer takole: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +PHP 8.3.2 (cli) | php -n | 8 threads ........s.......................... OK (35 tests, 1 skipped, 1.7 seconds) \-- -Ob ponovnem zagonu se najprej izvedejo testi, ki so bili med prejšnjim zagonom neuspešni, tako da boste takoj vedeli, ali ste napako odpravili. +Pri ponovnem zagonu najprej izvaja teste, ki so pri prejšnjem zagonu bili neuspešni, tako da takoj izveste, ali ste napako uspeli odpraviti. -Izhodna koda testerja je nič, če noben test ni uspešen. V nasprotnem primeru je neničelna. +Če noben test ni neuspešen, je izhodna koda Testerja nič. Sicer je izhodna koda neničelna. .[warning] -Tester izvaja procese PHP brez `php.ini`. Več podrobnosti najdete v razdelku [Lastni php.ini |#Own php.ini]. +Tester zažene procese PHP brez `php.ini`. Podrobneje v delu [#Lasten php.ini]. -Možnosti ukazne vrstice .[#toc-command-line-options] -==================================================== +Parametri ukazne vrstice +======================== -Pregled možnosti ukazne vrstice dobimo tako, da zaženemo Tester brez parametrov ali z možnostjo `-h`: +Pregled vseh možnosti ukazne vrstice dobimo z zagonom Testerja brez parametrov ali s parametrom `-h`: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Usage: tester [options] [ | ]... @@ -58,16 +58,16 @@ Options: -p Specify PHP interpreter to run (default: php). -c Look for php.ini file (or look in directory) . -C Use system-wide php.ini. - -l | --log Write log to file . -d ... Define INI entry 'key' with value 'value'. -s Show information about skipped tests. --stop-on-fail Stop execution upon the first failure. -j Run jobs in parallel (default: 8). - -o Specify output format. + -o (e.g. -o junit:output.xml) + Specify one or more output formats with optional file name. -w | --watch Watch directory. -i | --info Show tests environment info and exit. --setup Script for runner setup. - --temp Path to temporary directory. Default: sys_get_temp_dir(). + --temp Path to temporary directory. Default by sys_get_temp_dir(). --colors [1|0] Enable or disable colors. --coverage Generate code coverage report to file. --coverage-src Path to source code. @@ -77,7 +77,7 @@ Options: -p .[filter] ------------------- -Določa binarni strežnik PHP, ki se bo uporabljal za izvajanje testov. Privzeto je `php`. +Določa izvedljivo datoteko PHP, ki se bo uporabljala za zagon testov. Standardno je to `php`. /--pre .[terminal] tester -p /home/user/php-7.2.0-beta/php-cgi tests @@ -86,26 +86,17 @@ tester -p /home/user/php-7.2.0-beta/php-cgi tests -c .[filter] ------------------- -Določa, kateri `php.ini` se bo uporabljal pri izvajanju testov. Privzeto se ne uporablja php.ini. Za več informacij glejte [Lastni php.ini |#Own php.ini]. +Določa, kateri `php.ini` se bo uporabljal pri zagonu testov. Privzeto se noben `php.ini` ne uporabi. Več v delu [#Lasten php.ini]. -C .[filter] ------------ -Uporabi se sistemski `php.ini`. Tako so na platformi UNIX uporabljene tudi vse datoteke `/etc/php/{sapi}/conf.d/*.ini`. Oglejte si razdelek [Lastni php.ini |#Own php.ini]. - - -''-l | --log '' .[filter] -------------------------------- -Potek testiranja se zapiše v datoteko. Vsi neuspešni, preskočeni in tudi uspešni testi: - -/--pre .[terminal] -tester --log /var/log/tests.log tests -\-- +Uporabi se sistemski `php.ini`. V sistemu UNIX tudi vse ustrezne datoteke INI `/etc/php/{sapi}/conf.d/*.ini`. Več v delu [#Lasten php.ini]. -d .[filter] ------------------------ -Nastavi vrednost konfiguracijske direktive PHP za teste. Parameter lahko uporabite večkrat. +Nastavi vrednost konfiguracijske direktive PHP za teste. Parameter se lahko uporabi večkrat. /--pre .[terminal] tester -d max_execution_time=20 @@ -114,7 +105,7 @@ tester -d max_execution_time=20 -s --- -Prikazane bodo informacije o preskočenih testih. +Prikaže informacije o preskočenih testih. --stop-on-fail .[filter] @@ -124,24 +115,26 @@ Tester ustavi testiranje ob prvem neuspešnem testu. -j .[filter] ------------------ -Testi se izvajajo v `` vzporednih procesih. Privzeta vrednost je 8. Če želimo teste izvajati zaporedno, uporabimo vrednost 1. +Določa, koliko vzporednih procesov s testi se zažene. Privzeta vrednost je 8. Če želimo, da vsi testi potekajo zaporedno, uporabimo vrednost 1. --o .[filter] -------------------------------------- -Izhodna oblika. Privzeta je konzolna oblika. +-o .[filter] +------------------------------------------------------- +Nastavi izhodni format. Privzeti format je za konzolo. Lahko navedete ime datoteke, v katero se izpis zapiše (npr. `-o junit:output.xml`). Možnost `-o` lahko ponovite večkrat in tako generirate več formatov hkrati. -- `console`: enako kot privzeto, vendar se v tem primeru logotip ASCII ne izpiše. -- `tap`: [format TAP, |https://en.wikipedia.org/wiki/Test_Anything_Protocol] primeren za strojno obdelavo -- `junit`: format JUnit XML, primeren tudi za strojno obdelavo +- `console`: enak privzetemu formatu, vendar se v tem primeru ne prikaže logotip ASCII +- `console-lines`: podobno kot `console`, vendar je rezultat vsakega testa naveden v ločeni vrstici z dodatnimi informacijami +- `tap`: [TAP format |https://en.wikipedia.org/wiki/Test_Anything_Protocol] primeren za strojno obdelavo +- `junit`: format JUnit XML, prav tako primeren za strojno obdelavo +- `log`: Dnevniki poteka testiranja. Vsi neuspešni, preskočeni in tudi uspešni testi - `none`: nič se ne izpiše ''-w | --watch '' .[filter] --------------------------------- -Tester se po končanih testih ne konča, temveč se še naprej izvaja in opazuje datoteke PHP v danem imeniku. Ob spremembi ponovno izvede teste. Parameter lahko uporabimo večkrat, če želimo spremljati več imenikov. +Po končanem testiranju Tester ne konča, ampak ostane zagnan in spremlja datoteke PHP v navedenem imeniku. Ob spremembi ponovno zažene teste. Parameter lahko uporabite večkrat, če želite spremljati več imenikov. -To je priročno med refaktorizacijo knjižnice ali odpravljanjem napak pri testih. +Uporabno pri refaktoriranju knjižnice ali razhroščevanju testov. /--pre .[terminal] tester --watch src tests @@ -150,7 +143,7 @@ tester --watch src tests ''-i | --info'' .[filter] ------------------------- -Prikaže informacije o okolju, v katerem se izvaja test. Na primer: +Prikaže informacije o okolju izvajanja testov. Na primer: /--pre .[terminal] tester -p /usr/bin/php7.1 -c tests/php.ini --info @@ -177,13 +170,13 @@ Core, ctype, date, dom, ereg, fileinfo, filter, hash, ... --setup .[filter] ------------------------ -Tester ob zagonu naloži dano skripto PHP. V njej je na voljo spremenljivka `Tester\Runner\Runner $runner`. Predpostavimo datoteko `tests/runner-setup.php`: +Tester ob zagonu naloži navedeni skript PHP. V njem je na voljo spremenljivka `Tester\Runner\Runner $runner`. Predpostavimo datoteko `tests/runner-setup.php` z vsebino: ```php $runner->outputHandlers[] = new MyOutputHandler; ``` -in zaženemo Tester: +Tester zaženemo: /--pre .[terminal] tester --setup tests/runner-setup.php tests @@ -192,47 +185,47 @@ tester --setup tests/runner-setup.php tests --temp .[filter] ----------------------- -Nastavi pot do imenika za začasne datoteke Testerja. Privzeto vrednost vrne `sys_get_temp_dir()`. Če privzeta vrednost ni veljavna, boste na to opozorjeni. +Nastavi pot do imenika za začasne datoteke Testerja. Privzeto vrednost vrne `sys_get_temp_dir()`. Če privzeta vrednost ni veljavna, boste opozorjeni. -Če nismo prepričani, kateri imenik se uporablja, lahko zaženemo Tester s parametrom `--info`. +Če niste prepričani, kateri imenik se uporablja, zaženite Tester s parametrom `--info`. --colors 1|0 .[filter] ---------------------- -Tester privzeto zazna terminal z možnostjo obarvanja in obarva svoj izhod. Ta možnost je nad samodejnim zaznavanjem. Obarvanje lahko globalno nastavimo s sistemsko okoljsko spremenljivko `NETTE_TESTER_COLORS`. +Privzeto Tester zazna barvni terminal in obarva svoj izpis. Ta možnost preglasi samodejno zaznavanje. Globalno lahko barvanje nastavite s sistemsko okoljsko spremenljivko `NETTE_TESTER_COLORS`. --coverage .[filter] --------------------------- -Tester bo ustvaril poročilo s pregledom, koliko je izvorna koda pokrita s testi. Ta možnost zahteva omogočeno razširitev PHP [Xdebug |https://xdebug.org/] ali [PCOV |https://github.com/krakjoe/pcov] ali PHP 7 s PHPDBG SAPI, ki je hitrejši. Ciljna končnica datoteke določa obliko vsebine. HTML ali Clover XML. +Tester ustvari poročilo s pregledom, koliko izvorne kode pokrivajo testi. Ta možnost zahteva nameščeno razširitev PHP [Xdebug |https://xdebug.org/] ali [PCOV |https://github.com/krakjoe/pcov] ali PHP 7 s PHPDBG SAPI, ki je hitrejši. Končnica ciljne datoteke določa njen format. Bodisi HTML ali Clover XML. /--pre .[terminal] -tester tests --coverage coverage.html # HTML report -tester tests --coverage coverage.xml # Clover XML report +tester tests --coverage coverage.html # HTML poročilo +tester tests --coverage coverage.xml # Clover XML poročilo \-- -Prednostna izbira mehanizma za zbiranje je naslednja: +Prioriteta izbire mehanizma je naslednja: 1) PCOV 2) PHPDBG 3) Xdebug -Obsežni testi lahko med izvajanjem s PHPDBG zaradi izčrpanosti pomnilnika ne uspejo. Zbiranje podatkov o pokritosti je operacija, ki porabi veliko pomnilnika. V tem primeru lahko pomaga klicanje `Tester\CodeCoverage\Collector::flush()` znotraj testa. Ta bo zbrane podatke spraznil v datoteko in sprostil pomnilnik. Če zbiranje podatkov ni v teku ali če se uporablja Xdebug, klic nima učinka. +Pri uporabi PHPDBG lahko pri obsežnih testih naletite na neuspeh testa zaradi izčrpanja pomnilnika. Zbiranje informacij o pokritosti kode je pomnilniško zahtevno. V tem primeru vam pomaga klic `Tester\CodeCoverage\Collector::flush()` znotraj testa. Zapiše zbrane podatke na disk in sprosti pomnilnik. Če zbiranje podatkov ne poteka ali se uporablja Xdebug, klic nima učinka. -"Primer poročila HTML":https://files.nette.org/tester/coverage.html s pokritostjo kode. +[Primer poročila HTML |attachment:coverage.html] s pokritostjo kode. --coverage-src .[filter] ------------------------------- -Uporabljamo jo hkrati z možnostjo `--coverage`. Možnost `` je pot do izvorne kode, za katero ustvarjamo poročilo. Uporabimo jo lahko večkrat. +Uporabite hkrati z možnostjo `--coverage`. `` je pot do izvorne kode, za katero se ustvari poročilo. Lahko se uporabi večkrat. -Lastni php.ini .[#toc-own-php-ini] -================================== -Tester izvaja procese PHP z možnostjo `-n`, kar pomeni, da se ne naloži noben `php.ini` (niti tisti iz `/etc/php/conf.d/*.ini` v sistemu UNIX). S tem je zagotovljeno enako okolje za zagon testov, hkrati pa so deaktivirane vse zunanje razširitve PHP, ki jih sistemski PHP običajno naloži. +Lasten php.ini +============== +Tester zažene procese PHP s parametrom `-n`, kar pomeni, da se ne naloži noben `php.ini`. V sistemu UNIX niti tisti iz `/etc/php/conf.d/*.ini`. To zagotavlja enako okolje za izvajanje testov, vendar tudi izključi vse razširitve PHP, ki jih običajno naloži sistemski PHP. -Če želite ohraniti sistemsko konfiguracijo, uporabite parameter `-C`. +Če želite ohraniti nalaganje sistemskih datotek `php.ini`, uporabite parameter `-C`. -Če potrebujete nekatere razširitve ali posebne nastavitve INI, priporočamo, da ustvarite lastno datoteko `php.ini` in jo razdelite med teste. Nato zaženemo tester z možnostjo `-c`, npr. `tester -c tests/php.ini`. Datoteka INI je lahko videti takole: +Če za teste potrebujete kakšne razširitve ali posebne nastavitve INI, priporočamo ustvarjanje lastne datoteke `php.ini`, ki bo distribuirana s testi. Tester nato zaženite s parametrom `-c`, na primer `tester -c tests/php.ini tests`, kjer lahko datoteka INI izgleda takole: ```ini [PHP] @@ -243,4 +236,4 @@ extension=php_pdo_pgsql.dll memory_limit=512M ``` -Zagon Testerja s sistemsko možnostjo `php.ini` v sistemu UNIX, npr. `tester -c /etc/php/cgi/php.ini`, ne naloži drugih INI iz `/etc/php/conf.d/*.ini`. To je obnašanje PHP in ne Testerja. +Zagon Testerja v sistemu UNIX s sistemskim `php.ini`, na primer `tester -c /etc/php/cli/php.ini`, ne naloži drugih INI iz `/etc/php/conf.d/*.ini`. To je lastnost PHP, ne Testerja. diff --git a/tester/sl/test-annotations.texy b/tester/sl/test-annotations.texy index 6aa95ddfdd..2853765c89 100644 --- a/tester/sl/test-annotations.texy +++ b/tester/sl/test-annotations.texy @@ -1,10 +1,10 @@ -Anotacije testov -**************** +Opombe testov +************* .[perex] -Anotacije določajo, kako bo teste obravnaval [program za izvajanje testov v ukazni vrstici |running-tests]. Zapisane so na začetku testne datoteke. +Opombe določajo, kako bo teste obravnaval [zaganjalnik testov iz ukazne vrstice |running-tests]. Zapisujejo se na začetek datoteke s testom. -Anotacije ne upoštevajo velikih in malih črk. Prav tako nimajo učinka, če se test zažene ročno kot običajna skripta PHP. +Pri opombah se velikost črk ne upošteva. Prav tako nimajo vpliva, če se test zažene ročno kot običajen skript PHP. Primer: @@ -23,17 +23,17 @@ require __DIR__ . '/../bootstrap.php'; TEST .[filter] -------------- -To pravzaprav ni anotacija. Določa le naslov testa, ki se izpiše pri neuspehu ali v dnevnike. +To pravzaprav sploh ni opomba, ampak samo določa naslov testa, ki se izpiše ob neuspehu ali v dnevnik. @skip .[filter] --------------- -Test se preskoči. To je priročno za začasno deaktivacijo testa. +Test se preskoči. Uporabno za začasno izključitev testov. @phpVersion .[filter] --------------------- -Test se preskoči, če ga ustrezna različica PHP ne zažene. Anotacijo zapišemo kot `@phpVersion [operator] version`. Operator lahko izpustimo, privzeto je `>=`. Primeri: +Test se preskoči, če ni zagnan z ustrezno različico PHP. Opombo zapišemo kot `@phpVersion [operator] različica`. Operator lahko izpustite, privzeti je `>=`. Primeri: ```php /** @@ -46,7 +46,7 @@ Test se preskoči, če ga ustrezna različica PHP ne zažene. Anotacijo zapišem @phpExtension .[filter] ----------------------- -Test se preskoči, če niso naložene vse navedene razširitve PHP. Več razširitev lahko zapišemo v eno samo opombo ali pa opombo uporabimo večkrat. +Test se preskoči, če niso naložene vse navedene razširitve PHP. Več razširitev lahko navedete v eni opombi ali jo uporabite večkrat. ```php /** @@ -58,9 +58,9 @@ Test se preskoči, če niso naložene vse navedene razširitve PHP. Več razšir @dataProvider .[filter] ----------------------- -Ta opomba je primerna, kadar želimo test zagnati večkrat, vendar z različnimi podatki. (Ne smemo ga zamenjati z istoimensko opombo za [TestCase |TestCase#dataProvider].) +Če želite testno datoteko zagnati večkrat, vendar z drugačnimi vhodnimi podatki, je uporabna prav ta opomba. (Ne zamenjujte z istoimensko opombo za [TestCase |TestCase#dataProvider].) -Anotacijo zapišemo kot `@dataProvider file.ini`. Pot do datoteke INI je relativna glede na datoteko testa. Test se izvede tolikokrat, kolikor odsekov vsebuje datoteka INI. Predpostavimo, da je datoteka INI `databases.ini`: +Zapišemo kot `@dataProvider file.ini`, pot do datoteke se jemlje relativno glede na datoteko s testom. Test se bo zagnal tolikokrat, kolikor je odsekov v datoteki INI. Predpostavimo datoteko INI `databases.ini`: ```ini [mysql] @@ -77,7 +77,7 @@ password = ****** dsn = "sqlite::memory:" ``` -in datoteko `database.phpt` v istem imeniku: +in v istem imeniku test `database.phpt`: ```php /** @@ -87,11 +87,11 @@ in datoteko `database.phpt` v istem imeniku: $args = Tester\Environment::loadData(); ``` -Test se izvede trikrat, datoteka `$args` pa bo vsebovala vrednosti iz razdelkov `mysql`, `postgresql` ali `sqlite`. +Test se bo zagnal trikrat in `$args` bo vedno vseboval vrednosti iz odseka `mysql`, `postgresql` ali `sqlite`. -Obstaja še ena različica, ko anotacije zapišemo z vprašalnim znakom kot `@dataProvider? file.ini`. V tem primeru se test preskoči, če datoteka INI ne obstaja. +Obstaja še različica, ko opombo zapišemo z vprašajem kot `@dataProvider? file.ini`. V tem primeru se test preskoči, če datoteka INI ne obstaja. -Vse možnosti anotacij še niso bile omenjene. Za datoteko INI lahko zapišemo pogoje. Test se za dani razdelek izvede le, če se vsi pogoji ujemajo. Razširimo datoteko INI: +S tem možnosti opombe še niso izčrpane. Za ime datoteke INI lahko določite pogoje, pod katerimi se bo test za dani odsek zagnal. Razširimo datoteko INI: ```ini [mysql] @@ -121,9 +121,9 @@ in uporabimo opombo s pogojem: */ ``` -Test se izvede samo enkrat za odsek `postgresql 9.1`. Druga poglavja ne ustrezajo pogojem. +Test se bo zagnal samo enkrat in sicer za odsek `postgresql 9.1`. Drugi odseki ne bodo šli skozi filter pogoja. -Podobno lahko namesto INI posredujemo pot do skripte PHP. Ta mora vrniti polje ali Traversable. Datoteka `databases.php`: +Podobno lahko namesto datoteke INI pokažete na skript PHP. Ta mora vrniti polje ali `Traversable`. Datoteka `databases.php`: ```php return [ @@ -142,29 +142,29 @@ return [ @multiple .[filter] ------------------- -Zapišemo ga kot `@multiple N`, kjer je `N` celo število. Test se izvede natanko N-krat. +Zapišemo kot `@multiple N`, kjer je `N` celo število. Test se bo zagnal natanko N-krat. @testCase .[filter] ------------------- -Anotacija nima parametrov. Uporabimo jo, ko test zapišemo kot razred [TestCase |TestCase]. V tem primeru bo izvajalec testov v ukazni vrstici izvajal posamezne metode v ločenih procesih in vzporedno v več niti. S tem lahko bistveno pospešimo celoten postopek testiranja. +Opomba nima parametrov. Uporabite jo, če teste pišete kot razrede [TestCase |TestCase]. V tem primeru bo zaganjalnik testov iz ukazne vrstice zaganjal posamezne metode v ločenih procesih in vzporedno v več nitih. To lahko znatno pospeši celoten proces testiranja. @exitCode .[filter] ------------------- -Zapišemo jo kot `@exitCode N`, kjer se v testu kliče `N` is the exit code of the test. For example if `exit(10)`, anotacijo zapišemo kot `@exitCode 10`. Šteje se, da je test neuspešen, če se konča z drugačno kodo. Koda izhoda 0 (nič) se preveri, če izpustimo opombo +Zapišemo kot `@exitCode N`, kjer je `N` izhodna koda zagnanega testa. Če je v testu na primer klicano `exit(10)`, opombo zapišemo kot `@exitCode 10` in če se test konča z drugo kodo, se to šteje za neuspeh. Če opombe ne navedete, se preveri izhodna koda 0 (nič). @httpCode .[filter] ------------------- -Opomba se oceni samo, če je binarni program PHP CGI. V nasprotnem primeru se ne upošteva. Zapišemo jo kot `@httpCode NNN`, kjer je `NNN` pričakovana koda HTTP. Koda HTTP 200 se preveri, če opusti anotacijo. Če zapišemo `NNN` kot niz, ovrednoten kot nič, na primer `any`, se koda HTTP sploh ne preveri. +Opomba se uporabi samo, če je izvedljiva datoteka PHP CGI. Sicer se ignorira. Zapišemo kot `@httpCode NNN`, kjer je `NNN` pričakovana koda HTTP. Če opombe ne navedete, se preverja koda HTTP 200. Če `NNN` zapišete kot niz, ki se ovrednoti na nič, na primer `any`, se koda HTTP ne preverja. -@outputMatch a @outputMatchFile .[filter] ------------------------------------------ -Obnašanje pripisov je skladno s trditvami `Assert::match()` in `Assert::matchFile()`. Vzorec pa najdemo v standardnem izpisu testa. Primeren primer uporabe je, ko predvidevamo, da se test konča s usodno napako, in moramo preveriti njegov izhod. +@outputMatch in @outputMatchFile .[filter] +------------------------------------------ +Funkcija opomb je enaka asercijam `Assert::match()` in `Assert::matchFile()`. Vzorec (pattern) pa se išče v besedilu, ki ga je test poslal na svoj standardni izhod. Uporabo najde, če predpostavljate, da se bo test končal s fatalno napako in morate preveriti njegov izhod. @phpIni .[filter] ----------------- -Nastavi konfiguracijske vrednosti INI za test. Zapišemo ga na primer kot `@phpIni precision=20` in deluje enako, kot če bi vrednost iz ukazne vrstice posredovali s parametrom `-d precision=20`. +Za test nastavi konfiguracijske vrednosti INI. Zapišemo na primer kot `@phpIni precision=20` in deluje enako, kot če bi vrednost vnesli iz ukazne vrstice prek parametra `-d precision=20`. diff --git a/tester/sl/testcase.texy b/tester/sl/testcase.texy index 0e2aa78669..e6bf920207 100644 --- a/tester/sl/testcase.texy +++ b/tester/sl/testcase.texy @@ -2,9 +2,9 @@ TestCase ******** .[perex] -V preprostih testih si lahko trditve sledijo ena za drugo. Včasih pa je koristno trditve zapreti v testni razred in jih tako strukturirati. +V preprostih testih lahko asercije sledijo ena za drugo. Včasih pa je bolj ugodno asercije zapakirati v testni razred in jih tako strukturirati. -Razred mora biti potomec `Tester\TestCase` in o njem govorimo preprosto kot o **testcase**. +Razred mora biti potomec `Tester\TestCase` in poenostavljeno o njem govorimo kot o **testcase**. Razred mora vsebovati testne metode, ki se začnejo s `test`. Te metode se bodo zagnale kot testi: ```php use Tester\Assert; @@ -22,11 +22,11 @@ class RectangleTest extends Tester\TestCase } } -# Run testing methods +# Zagon testnih metod (new RectangleTest)->run(); ``` -Testni primer lahko obogatimo z metodami `setUp()` in `tearDown()`. Pokličemo jih pred/za vsako testno metodo: +Tako napisan test lahko nadalje obogatite z metodama `setUp()` in `tearDown()`. Klicani sta pred oz. za vsako testno metodo: ```php use Tester\Assert; @@ -35,12 +35,12 @@ class NextTest extends Tester\TestCase { public function setUp() { - # Preparation + # Priprava } public function tearDown() { - # Clean-up + # Čiščenje } public function testOne() @@ -54,14 +54,14 @@ class NextTest extends Tester\TestCase } } -# Run testing methods +# Zagon testnih metod (new NextTest)->run(); /* -Method Calls Order ------------------- +Vrstni red klica metod +---------------------- setUp() testOne() tearDown() @@ -72,9 +72,9 @@ tearDown() */ ``` -Če pride do napake v fazi `setUp()` ali `tearDown()`, bo test neuspešen. Če se napaka pojavi v testni metodi, se metoda `tearDown()` vseeno pokliče, vendar z zatrtimi napakami v njej. +Če pride do napake v fazi `setUp()` ali `tearDown()`, test na splošno ne uspe. Če pride do napake v testni metodi, se kljub temu metoda `tearDown()` zažene, vendar z zatrtjem napak v njej. -Priporočamo, da anotacijo [@testCase |test-annotations#@testCase] zapišete na začetku testa, potem bo izvajalec testov v ukazni vrstici izvajal posamezne metode testne zadeve v ločenih procesih in vzporedno v več nitih. To lahko znatno pospeši celoten postopek testiranja. +Priporočamo, da na začetek testa napišete opombo [@testCase |test-annotations#testCase], potem bo zaganjalnik testov iz ukazne vrstice zaganjal posamezne metode testcase v ločenih procesih in vzporedno v več nitih. To lahko znatno pospeši celoten proces testiranja. /--php getArea()); # we will verify the expected results +Assert::same(200.0, $rect->getArea()); # preverimo pričakovane rezultate Assert::false($rect->isSquare()); ``` -Kot lahko vidite, se [metode trditev, |Assertions] kot je `Assert::same()`, uporabljajo za trditev, da se dejanska vrednost ujema s pričakovano vrednostjo. +Kot vidite, se t.i. [asercijske metode |assertions] kot `Assert::same()` uporabljajo za potrditev, da dejanska vrednost ustreza pričakovani vrednosti. -Zadnji korak je ustvarjanje datoteke `bootstrap.php`. Ta vsebuje skupno kodo za vse teste. Na primer razredi samodejnega zagona, konfiguracija okolja, ustvarjanje začasnega imenika, pomočniki in podobno. Vsak test naloži zagonsko datoteko in se posveti samo testiranju. Zagonska datoteka je lahko videti takole: +Ostaja še zadnji korak in to je datoteka `bootstrap.php`. Ta vsebuje kodo, skupno vsem testom, na primer samodejno nalaganje razredov, konfiguracijo okolja, ustvarjanje začasnega imenika, pomožne funkcije in podobno. Vsi testi naložijo bootstrap in se nato posvetijo samo testiranju. Bootstrap lahko izgleda na naslednji način: ```php .{file:tests/bootstrap.php} OK \-- -Če v testu spremenimo izjavo v false `Assert::same(123, $rect->getArea());`, se bo zgodilo naslednje: +Če bi v testu spremenili trditev na neresnično `Assert::same(123, $rect->getArea());`, se zgodi tole: /--pre .[terminal] $ php RectangleTest.php @@ -107,12 +106,12 @@ $ php RectangleTest.php \-- -Pri pisanju testov je dobro ujeti vse skrajne situacije. Na primer, če je vhodni podatek nič, negativno število, v drugih primerih pa prazen niz, ničla itd. Pravzaprav vas to prisili v razmišljanje in odločanje, kako naj se koda obnaša v takšnih situacijah. S testi se nato obnašanje popravi. +Pri pisanju testov je dobro zajeti vse skrajne situacije. Na primer, ko bo vhod nič, negativno število, v drugih primerih morda prazen niz, `null` itd. Pravzaprav vas to sili, da se zamislite in odločite, kako naj se koda v takšnih situacijah obnaša. Testi nato obnašanje fiksirajo. -V našem primeru bi morala negativna vrednost vreči izjemo, kar preverimo z [Assert::exception() |Assertions#Assert::exception]: +V našem primeru mora negativna vrednost sprožiti izjemo, kar preverimo s pomočjo [Assert::exception() |Assertions#Assert::exception]: ```php .{file:tests/RectangleTest.php} -// širina ne sme biti negativno število +// širina ne sme biti negativna Assert::exception( fn() => new Rectangle(-1, 20), InvalidArgumentException::class, @@ -120,22 +119,22 @@ Assert::exception( ); ``` -In dodamo podoben test za višino. Na koncu preverimo, ali `isSquare()` vrne `true`, če sta obe dimenziji enaki. Poskusite napisati takšne teste kot vajo. +In podoben test dodamo za višino. Na koncu testiramo, da `isSquare()` vrne `true`, če sta obe dimenziji enaki. Poskusite kot vajo napisati takšne teste. -Dobro urejeni testi .[#toc-well-arranged-tests] -=============================================== +Preglednejši testi +================== -Velikost testne datoteke se lahko poveča in hitro postane nepregledna. Zato je praktično, da posamezna testna področja združite v ločene funkcije. +Velikost datoteke s testom lahko narašča in hitro postane nepregledna. Zato je praktično posamezna testirana področja združiti v ločene funkcije. -Najprej bomo prikazali preprostejšo, a elegantno različico, ki uporablja globalno funkcijo `test()`. Tester je ne ustvari samodejno, da bi se izognil trku, če bi imeli v kodi funkcijo z istim imenom. Ustvari jo šele metoda `setupFunctions()`, ki jo pokličete v datoteki `bootstrap.php`: +Najprej si bomo pokazali enostavnejšo, vendar elegantno različico, in sicer s pomočjo globalne funkcije `test()`. Tester je ne ustvarja samodejno, da ne bi prišlo do kolizije, če bi imeli v kodi funkcijo z istim imenom. Ustvari jo šele metoda `setupFunctions()`, ki jo pokličite v datoteki `bootstrap.php`: ```php .{file:tests/bootstrap.php} Tester\Environment::setup(); Tester\Environment::setupFunctions(); ``` -S to funkcijo lahko testno datoteko lepo razdelimo na poimenovane enote. Ob izvajanju se bodo oznake prikazale ena za drugo. +S pomočjo te funkcije lahko testno datoteko lepo razčlenite na poimenovane celote. Ob zagonu se bodo postopoma izpisovali opisi. ```php .{file:tests/RectangleTest.php} getArea()); Assert::false($rect->isSquare()); }); -test('general square', function () { +test('splošni kvadrat', function () { $rect = new Rectangle(5, 5); Assert::same(25.0, $rect->getArea()); Assert::true($rect->isSquare()); }); -test('dimensions must not be negative', function () { +test('dimenzije ne smejo biti negativne', function () { Assert::exception( fn() => new Rectangle(-1, 20), InvalidArgumentException::class, @@ -168,15 +167,15 @@ test('dimensions must not be negative', function () { }); ``` -Če morate kodo zagnati pred ali po vsakem testu, jo predajte funkciji `setUp()` ali `tearDown()`: +Če potrebujete pred ali po vsakem testu zagnati kodo, jo predajte funkciji `setUp()` oz. `tearDown()`: ```php setUp(function () { - // inicializacijska koda, ki se zažene pred vsakim testom() + // inicializacijska koda, ki se zažene pred vsakim `test()` }); ``` -Druga različica je objekt. Ustvarili bomo tako imenovani TestCase, ki je razred, v katerem so posamezne enote predstavljene z metodami, katerih imena se začnejo s test-. +Druga različica je objektna. Ustvarimo si t.i. TestCase, kar je razred, kjer posamezne celote predstavljajo metode, katerih imena se začnejo s `test-`. ```php .{file:tests/RectangleTest.php} class RectangleTest extends Tester\TestCase @@ -208,25 +207,25 @@ class RectangleTest extends Tester\TestCase } } -// Izvedba testnih metod +// Zagon testnih metod (new RectangleTest)->run(); ``` -Tokrat smo uporabili pripis `@throw` za testiranje izjem. Za več informacij glejte poglavje [TestCase |TestCase]. +Za testiranje izjem smo tokrat uporabili opombo `@throws`. Več se boste naučili v poglavju [TestCase |TestCase]. -Pomožne funkcije .[#toc-helpers-functions] -========================================== +Pomožne funkcije +================ -Nette Tester vključuje več razredov in funkcij, ki vam lahko olajšajo testiranje, na primer pomočnike za testiranje vsebine dokumenta HTML, za testiranje funkcij za delo z datotekami itd. +Nette Tester vsebuje več razredov in funkcij, ki vam lahko olajšajo na primer testiranje vsebine dokumenta HTML, testiranje funkcij, ki delajo z datotekami in tako naprej. -Njihov opis najdete na strani [Pomočniki |Helpers]. +Njihov opis najdete na strani [Pomožni razredi |helpers]. -Pripisovanje in preskakovanje testov .[#toc-annotation-and-skipping-tests] -========================================================================== +Opombe in preskakovanje testov +============================== -Na izvajanje testov lahko vplivajo opombe v komentarju phpDoc na začetku datoteke. Na primer, lahko je videti takole: +Na zagon testov lahko vplivajo opombe v obliki komentarja phpDoc na začetku datoteke. Lahko izgleda na primer takole: ```php .{file:tests/RectangleTest.php} /** @@ -235,11 +234,11 @@ Na izvajanje testov lahko vplivajo opombe v komentarju phpDoc na začetku datote */ ``` -V opombah je navedeno, da se test lahko izvede le z različico PHP 7.2 ali višjo in če sta prisotni razširitvi PHP pdo in pdo_pgsql. Te opombe nadzoruje [testni pogon v ukazni vrstici |running-tests], ki, če pogoji niso izpolnjeni, preskoči test in ga označi s črko `s` - preskočen. Vendar pa nimajo nobenega učinka, če test zaženete ročno. +Navedene opombe pravijo, da naj se test zažene samo z različico PHP 7.2 ali višjo in če so prisotne razširitve PHP `pdo` in `pdo_pgsql`. S temi opombami se ravna [zaganjalnik testov iz ukazne vrstice |running-tests], ki v primeru, da pogoji niso izpolnjeni, test preskoči in v izpisu označi s črko `s` - skipped. Vendar pri ročnem zagonu testa nimajo vpliva. -Za opis opomb glejte [Test Annotations |Test Annotations]. +Opis opomb najdete na strani [Opombe testov |test-annotations]. -Test lahko preskočite tudi na podlagi lastnega pogoja s `Environment::skip()`. Ta test bomo na primer preskočili pri operacijskem sistemu Windows: +Test lahko preskočite tudi na podlagi izpolnitve lastnega pogoja s pomočjo `Environment::skip()`. Na primer, ta preskoči teste v sistemu Windows: ```php if (defined('PHP_WINDOWS_VERSION_BUILD')) { @@ -248,10 +247,10 @@ if (defined('PHP_WINDOWS_VERSION_BUILD')) { ``` -Struktura imenikov .[#toc-directory-structure] -============================================== +Struktura imenikov +================== -Pri le nekoliko večjih knjižnicah ali projektih priporočamo razdelitev imenika za testiranje v podimenike glede na imenski prostor testiranega razreda: +Priporočamo, da pri le malo večjih knjižnicah ali projektih imenik s testi razdelite še v podimenike glede na imenski prostor testiranega razreda: ``` └── tests/ @@ -269,22 +268,22 @@ Pri le nekoliko večjih knjižnicah ali projektih priporočamo razdelitev imenik └── ... ``` -Teste boste lahko izvajali iz enega samega imenskega prostora, tj. podimenikov: +Tako boste lahko zaganjali teste iz enega samega imenskega prostora oz. podimenika: /--pre .[terminal] tester tests/NamespaceOne \-- -Edge Cases .[#toc-edge-cases] -============================= +Posebne situacije +================= -Test, ki ne kliče nobene metode za potrjevanje, je sumljiv in bo ocenjen kot napačen: +Test, ki ne pokliče niti ene asercijske metode, je sumljiv in se oceni kot napačen: /--pre .[terminal] Error: This test forgets to execute an assertion. \-- -Če naj se test brez klicanja trditev res šteje za veljavnega, pokličite na primer `Assert::true(true)`. +Če res mora biti test brez klica asercij veljaven, pokličite na primer `Assert::true(true)`. -Prav tako je lahko zahrbtna uporaba `exit()` in `die()`, s katerima se test konča s sporočilom o napaki. Na primer, `exit('Error in connection')` konča test z izhodno kodo 0, ki sporoča uspeh. Uporabite `Assert::fail('Error in connection')`. +Prav tako je lahko zavajajoče uporabljati `exit()` in `die()` za končanje testa s sporočilom o napaki. Na primer `exit('Napaka pri povezavi')` konča test z izhodno kodo 0, kar signalizira uspeh. Uporabite `Assert::fail('Napaka pri povezavi')`. diff --git a/tester/tr/@home.texy b/tester/tr/@home.texy index f5fb2b1a0d..4c2866dacd 100644 --- a/tester/tr/@home.texy +++ b/tester/tr/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Nette Tester - PHP'de Keyifli Birim Testi}} -{{description: Nette Tester basit ama çok kullanışlı bir PHP kod test aracıdır.}} +{{maintitle: Nette Tester – PHP'de Keyifli Test Etme}} +{{description: Nette Tester, PHP kodunu test etmek için basit ama çok kullanışlı bir araçtır.}} diff --git a/tester/tr/@left-menu.texy b/tester/tr/@left-menu.texy index 392c50ebb6..46f0f652f1 100644 --- a/tester/tr/@left-menu.texy +++ b/tester/tr/@left-menu.texy @@ -1,8 +1,8 @@ -- [Başlarken |guide] -- [Yazma Testleri |Writing Tests] -- [Çalışan Testler |Running Tests] +- [Nette Tester ile Başlarken |guide] +- [Test Yazma |writing-tests] +- [Testleri Çalıştırma |running-tests] -- [İddialar |Assertions] -- [Test Ek Açıklamaları |Test Annotations] +- [Doğrulama İfadeleri |assertions] +- [Test Ek Açıklamaları |test-annotations] - [TestCase |TestCase] -- [Yardımcılar |Helpers] +- [Yardımcı Sınıflar |helpers] diff --git a/tester/tr/@menu.texy b/tester/tr/@menu.texy index 24dc1261e9..c19a5f8252 100644 --- a/tester/tr/@menu.texy +++ b/tester/tr/@menu.texy @@ -1,3 +1,3 @@ -- [Ev |@home] -- [Dokümantasyon |Guide] +- [Giriş |@home] +- [Dokümantasyon |guide] - "GitHub .[link-external]":https://github.com/nette/tester diff --git a/tester/tr/@meta.texy b/tester/tr/@meta.texy new file mode 100644 index 0000000000..6da801025d --- /dev/null +++ b/tester/tr/@meta.texy @@ -0,0 +1 @@ +{{sitename: Tester Dokümantasyonu}} diff --git a/tester/tr/assertions.texy b/tester/tr/assertions.texy index 41e50c6e0f..7d217b9a37 100644 --- a/tester/tr/assertions.texy +++ b/tester/tr/assertions.texy @@ -1,35 +1,35 @@ -İddialar -******** +Doğrulama İfadeleri (Assertion) +******************************* .[perex] -Aassertions, gerçek bir değerin beklenen bir değerle eşleştiğini iddia etmek için kullanılır. Bunlar `Tester\Assert`'un yöntemleridir. +Doğrulama ifadeleri, gerçek değerin beklenen değere karşılık geldiğini onaylamak için kullanılır. Bunlar `Tester\Assert` sınıfının metotlarıdır. -En doğru iddiaları seçin. `Assert::same($a, $b)` , `Assert::true($a === $b)` 'den daha iyidir çünkü başarısızlık durumunda anlamlı bir hata mesajı görüntüler. İkinci durumda sadece `false should be true` alırız ve $a ve $b değişkenlerinin içeriği hakkında hiçbir şey söylemez. +En uygun doğrulama ifadelerini seçin. `Assert::same($a, $b)`, `Assert::true($a === $b)`'den daha iyidir, çünkü başarısız olduğunda anlamlı bir hata mesajı gösterir. İkinci durumda, yalnızca `$a` ve `$b` değişkenlerinin içeriği hakkında hiçbir şey söylemeyen `false should be true` mesajı gösterilir. -Çoğu iddia, beklentinin başarısız olması durumunda hata mesajında görünen isteğe bağlı bir `$description` adresine de sahip olabilir. +Çoğu doğrulama ifadesi ayrıca, beklenti başarısız olursa hata mesajında gösterilecek olan isteğe bağlı bir `$description` parametresine sahip olabilir. -Örnekler aşağıdaki sınıf takma adının tanımlandığını varsayar: +Örnekler, bir takma adın oluşturulduğunu varsayar: ```php use Tester\Assert; ``` -Assert::same($expected, $actual, string $description=null) .[method] --------------------------------------------------------------------- -`$expected` `$actual` ile aynı olmalıdır. PHP operatörü ile aynıdır `===`. +Assert::same($expected, $actual, ?string $description=null) .[method] +--------------------------------------------------------------------- +`$expected`, `$actual` ile aynı olmalıdır. PHP `===` operatörü ile aynıdır. -Assert::notSame($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------ - `Assert::same()`'un tersidir, bu nedenle PHP operatörü `!==` ile aynıdır. +Assert::notSame($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------ +`Assert::same()`'in tersi, yani PHP `!==` operatörü ile aynıdır. -Assert::equal($expected, $actual, string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] ------------------------------------------------------------------------------------------------------------------------- -`$expected` `$actual` ile aynı olmalıdır. `Assert::same()` adresinden farklı olarak, nesne kimliği, dizilerdeki anahtar çiftleri => değer sırası ve marjinal olarak farklı ondalık sayılar göz ardı edilir; bunlar `$matchIdentity` ve `$matchOrder` adresleri ayarlanarak değiştirilebilir. +Assert::equal($expected, $actual, ?string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] +------------------------------------------------------------------------------------------------------------------------- +`$expected`, `$actual` ile aynı olmalıdır. `Assert::same()`'den farklı olarak, nesnelerin kimliği, dizilerdeki anahtar => değer çiftlerinin sırası ve marjinal olarak farklı ondalık sayılar göz ardı edilir, bu `$matchIdentity` ve `$matchOrder` ayarlanarak değiştirilebilir. -Aşağıdaki durumlar `equal()` açısından aynıdır, ancak `same()` için aynı değildir: +Aşağıdaki durumlar `equal()` açısından aynıdır, ancak `same()` açısından değildir: ```php Assert::equal(0.3, 0.1 + 0.2); @@ -40,81 +40,81 @@ Assert::equal( ); ``` -Ancak, dikkat edin, dizi `[1, 2]` ve `[2, 1]` eşit değildir, çünkü anahtar => değer çiftleri değil, yalnızca değerlerin sırası farklıdır. Dizi `[1, 2]` olarak da yazılabilir `[0 => 1, 1 => 2]` ve bu nedenle `[1 => 2, 0 => 1]` eşit kabul edilecektir. +Ancak dikkat, `[1, 2]` ve `[2, 1]` dizileri aynı değildir, çünkü yalnızca değerlerin sırası farklıdır, anahtar => değer çiftlerinin sırası değil. `[1, 2]` dizisi ayrıca `[0 => 1, 1 => 2]` olarak da yazılabilir ve bu nedenle `[1 => 2, 0 => 1]` aynı kabul edilecektir. -Sözde [beklentileri |#expectations] `$expected` adresinde de kullanabilirsiniz. +Ayrıca `$expected` içinde sözde [#beklentiler] kullanılabilir. -Assert::notEqual($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------- -Karşısında `Assert::equal()`. +Assert::notEqual($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------- +`Assert::equal()`'in tersi. -Assert::contains($needle, string|array $actual, string $description=null) .[method] ------------------------------------------------------------------------------------ -`$actual` bir dizeyse, `$needle` alt dizesini içermelidir. Bir dizi ise, `$needle` öğesini içermelidir (kesinlikle karşılaştırılır). +Assert::contains($needle, string|array $actual, ?string $description=null) .[method] +------------------------------------------------------------------------------------ +Eğer `$actual` bir karakter dizisi ise, `$needle` alt karakter dizisini içermelidir. Eğer bir dizi ise, `$needle` öğesini içermelidir (kesin olarak karşılaştırılır). -Assert::notContains($needle, string|array $actual, string $description=null) .[method] --------------------------------------------------------------------------------------- -Karşısında `Assert::contains()`. +Assert::notContains($needle, string|array $actual, ?string $description=null) .[method] +--------------------------------------------------------------------------------------- +`Assert::contains()`'in tersi. -Assert::hasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} -------------------------------------------------------------------------------------------------------- +Assert::hasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +-------------------------------------------------------------------------------------------------------- `$actual` bir dizi olmalı ve `$needle` anahtarını içermelidir. -Assert::notHasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} ----------------------------------------------------------------------------------------------------------- +Assert::notHasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +----------------------------------------------------------------------------------------------------------- `$actual` bir dizi olmalı ve `$needle` anahtarını içermemelidir. -Assert::true($value, string $description=null) .[method] --------------------------------------------------------- -`$value` `true` , yani `$value === true` olmalıdır. +Assert::true($value, ?string $description=null) .[method] +--------------------------------------------------------- +`$value` `true` olmalıdır, yani `$value === true`. -Assert::truthy($value, string $description=null) .[method] ----------------------------------------------------------- -`$value` doğru olmalıdır, bu nedenle `if ($value) ...` koşulunu karşılar. +Assert::truthy($value, ?string $description=null) .[method] +----------------------------------------------------------- +`$value` doğru olmalıdır, yani `if ($value) ...` koşulunu karşılamalıdır. -Assert::false($value, string $description=null) .[method] ---------------------------------------------------------- -`$value` `false` , yani `$value === false` olmalıdır. +Assert::false($value, ?string $description=null) .[method] +---------------------------------------------------------- +`$value` `false` olmalıdır, yani `$value === false`. -Assert::falsey($value, string $description=null) .[method] ----------------------------------------------------------- -`$value` falsey olmalıdır, bu nedenle `if (!$value) ...` koşulunu karşılar. +Assert::falsey($value, ?string $description=null) .[method] +----------------------------------------------------------- +`$value` yanlış olmalıdır, yani `if (!$value) ...` koşulunu karşılamalıdır. -Assert::null($value, string $description=null) .[method] --------------------------------------------------------- -`$value` `null` , yani `$value === null` olmalıdır. +Assert::null($value, ?string $description=null) .[method] +--------------------------------------------------------- +`$value` `null` olmalıdır, yani `$value === null`. -Assert::notNull($value, string $description=null) .[method] ------------------------------------------------------------ -`$value` `null` olmamalıdır, bu yüzden `$value !== null`. +Assert::notNull($value, ?string $description=null) .[method] +------------------------------------------------------------ +`$value` `null` olmamalıdır, yani `$value !== null`. -Assert::nan($value, string $description=null) .[method] -------------------------------------------------------- -`$value` Bir Numara Değil olmalıdır. NAN testi için yalnızca `Assert::nan()` adresini kullanın. NAN değeri çok spesifiktir ve `Assert::same()` veya `Assert::equal()` iddiaları tahmin edilemeyecek şekilde davranabilir. +Assert::nan($value, ?string $description=null) .[method] +-------------------------------------------------------- +`$value` Not a Number olmalıdır. NAN değerlerini test etmek için yalnızca `Assert::nan()` kullanın. NAN değeri çok özeldir ve `Assert::same()` veya `Assert::equal()` doğrulama ifadeleri beklenmedik şekilde çalışabilir. -Assert::count($count, Countable|array $value, string $description=null) .[method] ---------------------------------------------------------------------------------- -`$value` adresindeki öğe sayısı `$count` olmalıdır. Yani `count($value) === $count` ile aynı. +Assert::count($count, Countable|array $value, ?string $description=null) .[method] +---------------------------------------------------------------------------------- +`$value` içindeki öğelerin sayısı `$count` olmalıdır. Yani `count($value) === $count` ile aynıdır. -Assert::type(string|object $type, $value, string $description=null) .[method] ------------------------------------------------------------------------------ -`$value` belirli bir tipte olmalıdır. `$type` olarak string kullanabiliriz: +Assert::type(string|object $type, $value, ?string $description=null) .[method] +------------------------------------------------------------------------------ +`$value` belirtilen türde olmalıdır. `$type` olarak bir karakter dizisi kullanabiliriz: - `array` -- `list` - sıfırdan itibaren sayısal anahtarların artan sırasına göre dizinlenmiş dizi +- `list` - sıfırdan başlayarak artan sayısal anahtarlara göre indekslenmiş dizi - `bool` - `callable` - `float` @@ -124,163 +124,163 @@ Assert::type(string|object $type, $value, string $description=null) .[method] - `resource` - `scalar` - `string` -- sınıf adını veya nesnesini doğrudan geçmelidir. `$value instanceof $type` +- bir sınıf adı veya doğrudan bir nesne, o zaman `$value instanceof $type` olmalıdır -Assert::exception(callable $callable, string $class, string $message=null, $code=null) .[method] ------------------------------------------------------------------------------------------------- -`$callable` çağrıldığında `$class` örneğinin bir istisnası fırlatılmalıdır. Eğer `$message` adresini geçersek, istisnanın mesajı [eşleşmelidir |#assert-match]. Ve eğer `$code` adresini geçersek, istisnanın kodu aynı olmalıdır. +Assert::exception(callable $callable, string $class, ?string $message=null, $code=null) .[method] +------------------------------------------------------------------------------------------------- +`$callable` çağrıldığında, `$class` sınıfının bir istisnası atılmalıdır. Eğer `$message` belirtirsek, istisna mesajı da [desene uymalıdır |#Assert::match] ve eğer `$code` belirtirsek, kodlar da kesin olarak eşleşmelidir. -Örneğin, istisnanın mesajı eşleşmediği için bu test başarısız olur: +Aşağıdaki test, istisna mesajı eşleşmediği için başarısız olur: ```php Assert::exception( - fn() => throw new App\InvalidValueException('Zero value'), + fn() => throw new App\InvalidValueException('Sıfır değeri'), App\InvalidValueException::class, - 'Value is to low', + 'Değer çok düşük', ); ``` -`Assert::exception()` atılan bir istisna döndürür, böylece iç içe geçmiş bir istisnayı test edebilirsiniz. +`Assert::exception()` atılan istisnayı döndürür, böylece iç içe geçmiş istisnaları da test edebilirsiniz. ```php $e = Assert::exception( - fn() => throw new MyException('Something is wrong', 0, new RuntimeException), + fn() => throw new MyException('Bir şeyler yanlış', 0, new RuntimeException), MyException::class, - 'Something is wrong', + 'Bir şeyler yanlış', ); Assert::type(RuntimeException::class, $e->getPrevious()); ``` -Assert::error(string $callable, int|string|array $type, string $message=null) .[method] ---------------------------------------------------------------------------------------- -`$callable` çağrısının beklenen hataları (yani uyarılar, bildirimler, vb.) oluşturup oluşturmadığını kontrol eder. `$type` olarak `E_...` sabitlerinden birini belirtiriz, örneğin `E_WARNING`. Ve eğer `$message` geçilirse, hata mesajı da kalıpla [eşleşmelidir |#assert-match]. Örneğin: +Assert::error(string $callable, int|string|array $type, ?string $message=null) .[method] +---------------------------------------------------------------------------------------- +`$callable` fonksiyonunun beklenen hataları (yani uyarılar, bildirimler vb.) oluşturup oluşturmadığını kontrol eder. `$type` olarak `E_...` sabitlerinden birini, örneğin `E_WARNING` belirtiriz. Ve eğer `$message` belirtirsek, hata mesajı da [desene uymalıdır |#Assert::match]. Örneğin: ```php Assert::error( fn() => $i++, E_NOTICE, - 'Undefined variable: i', + 'Tanımsız değişken: i', ); ``` -Geri arama daha fazla hata üretirse, hepsini tam sırayla beklemeliyiz. Bu durumda diziyi `$type` adresine aktarırız: +Eğer geri arama birden fazla hata oluşturursa, hepsini tam sırada beklemeliyiz. Bu durumda `$type` içinde bir dizi aktarırız: ```php Assert::error(function () { $a++; $b++; }, [ - [E_NOTICE, 'Undefined variable: a'], - [E_NOTICE, 'Undefined variable: b'], + [E_NOTICE, 'Tanımsız değişken: a'], + [E_NOTICE, 'Tanımsız değişken: b'], ]); ``` .[note] -Eğer `$type` sınıf adı ise, bu iddia `Assert::exception()` ile aynı şekilde davranır. +Eğer `$type` olarak bir sınıf adı belirtirseniz, `Assert::exception()` ile aynı şekilde davranır. Assert::noError(callable $callable) .[method] --------------------------------------------- -`$callable` işlevinin herhangi bir PHP uyarısı/bildirimi/hatası veya istisnası atmadığını kontrol eder. Başka bir iddianın olmadığı bir kod parçasını test etmek için kullanışlıdır. +`$callable` fonksiyonunun herhangi bir uyarı, hata veya istisna oluşturmadığını kontrol eder. Başka bir doğrulama ifadesi olmayan kod parçalarını test etmek için kullanışlıdır. -Assert::match(string $pattern, $actual, string $description=null) .[method] ---------------------------------------------------------------------------- -`$actual` `$pattern` ile eşleşmelidir. İki çeşit kalıp kullanabiliriz: düzenli ifadeler veya joker karakterler. +Assert::match(string $pattern, $actual, ?string $description=null) .[method] +---------------------------------------------------------------------------- +`$actual`, `$pattern` desenine uymalıdır. İki desen türü kullanabiliriz: düzenli ifadeler veya joker karakterler. -Bir düzenli ifadeyi `$pattern` olarak geçirirsek, sınırlandırmak için `~` or `#` kullanmalıyız. Diğer sınırlayıcılar desteklenmez. Örneğin, `$var` adresinin yalnızca onaltılık basamaklar içermesi gereken test: +Eğer `$pattern` olarak bir düzenli ifade aktarırsak, onu sınırlamak için `~` veya `#` kullanmalıyız, diğer sınırlayıcılar desteklenmez. Örneğin, `$var`'ın yalnızca onaltılık rakamlar içermesi gereken test: ```php Assert::match('#^[0-9a-f]$#i', $var); ``` -Diğer varyant dize karşılaştırmaya benzer ancak `$pattern` adresinde bazı vahşi karakterler kullanabiliriz: - -- `%a%` satır sonu karakterleri hariç herhangi bir şeyden bir veya daha fazlası -- `%a?%` satır sonu karakterleri dışında herhangi bir şeyden sıfır veya daha fazlası -- `%A%` satır sonu karakterleri de dahil olmak üzere herhangi bir şeyden bir veya daha fazlası -- `%A?%` satır sonu karakterleri de dahil olmak üzere herhangi bir şeyden sıfır veya daha fazlası -- `%s%` satır sonu karakterleri hariç bir veya daha fazla beyaz boşluk karakteri -- `%s?%` satır sonu karakterleri hariç sıfır veya daha fazla beyaz boşluk karakteri -- `%S%` beyaz boşluk hariç bir veya daha fazla karakter -- `%S?%` beyaz boşluk dışında sıfır veya daha fazla karakter -- `%c%` herhangi bir türden tek bir karakter (satır sonu hariç) +İkinci varyant, sıradan karakter dizisi karşılaştırmasına benzer, ancak `$pattern` içinde farklı joker karakterler kullanabiliriz: + +- `%a%` satır sonu karakterleri hariç bir veya daha fazla karakter +- `%a?%` satır sonu karakterleri hariç sıfır veya daha fazla karakter +- `%A%` satır sonu karakterleri dahil bir veya daha fazla karakter +- `%A?%` satır sonu karakterleri dahil sıfır veya daha fazla karakter +- `%s%` satır sonu karakterleri hariç bir veya daha fazla boşluk karakteri +- `%s?%` satır sonu karakterleri hariç sıfır veya daha fazla boşluk karakteri +- `%S%` boşluk karakterleri hariç bir veya daha fazla karakter +- `%S?%` boşluk karakterleri hariç sıfır veya daha fazla karakter +- `%c%` satır sonu karakteri hariç herhangi bir tek karakter - `%d%` bir veya daha fazla rakam -- `%d?%` sıfır veya daha fazla basamak +- `%d?%` sıfır veya daha fazla rakam - `%i%` işaretli tamsayı değeri -- `%f%` kayan nokta sayısı -- `%h%` bir veya daha fazla HEX rakamı -- `%w%` bir veya daha fazla alfanümerik karakter -- `%%` bir % karakteri +- `%f%` ondalık noktalı sayı +- `%h%` bir veya daha fazla onaltılık rakam +- `%w%` bir veya daha fazla alfasayısal karakter +- `%%` % karakteri Örnekler: ```php -# Again, hexadecimal number test +# Yine onaltılık sayı testi Assert::match('%h%', $var); -# Generalized path to file and line number -Assert::match('Error in file %a% on line %i%', $errorMessage); +# Dosya yolunun ve satır numarasının genelleştirilmesi +Assert::match('Dosyadaki hata %a% satır %i%', $errorMessage); ``` -Assert::matchFile(string $file, $actual, string $description=null) .[method] ----------------------------------------------------------------------------- -[Assert::match() |#assert-match] ile aynıdır ancak kalıp `$file` adresinden yüklenir. Çok uzun dizeleri test etmek için kullanışlıdır. Test dosyası okunabilir duruyor. +Assert::matchFile(string $file, $actual, ?string $description=null) .[method] +----------------------------------------------------------------------------- +Doğrulama ifadesi [#Assert::match()] ile aynıdır, ancak desen `$file` dosyasından yüklenir. Bu, çok uzun karakter dizilerini test etmek için kullanışlıdır. Test dosyası düzenli kalır. Assert::fail(string $message, $actual=null, $expected=null) .[method] --------------------------------------------------------------------- -Bu iddia her zaman başarısız olur. Sadece kullanışlıdır. İsteğe bağlı olarak beklenen ve gerçek değerleri geçebiliriz. +Bu doğrulama ifadesi her zaman başarısız olur. Bazen bu sadece kullanışlıdır. İsteğe bağlı olarak beklenen ve gerçek değerleri de belirtebiliriz. -Beklentiler .[#toc-expectations] --------------------------------- -Sabit olmayan elemanlara sahip daha karmaşık yapıları karşılaştırmak istiyorsak, yukarıdaki ifadeler yeterli olmayabilir. Örneğin, yeni bir kullanıcı oluşturan ve özniteliklerini bir dizi olarak döndüren bir yöntemi test ediyoruz. Parola karma değerini bilmiyoruz, ancak onaltılık bir dize olması gerektiğini biliyoruz. Ve bir sonraki eleman hakkında bildiğimiz tek şey, bunun bir nesne olması gerektiğidir `DateTime`. +Beklentiler +----------- +Sabit olmayan öğelerle daha karmaşık yapıları karşılaştırmak istediğimizde, yukarıdaki doğrulama ifadeleri yeterli olmayabilir. Örneğin, yeni bir kullanıcı oluşturan ve niteliklerini bir dizi olarak döndüren bir metodu test ediyoruz. Şifre hash değerini bilmiyoruz, ancak onaltılık bir karakter dizisi olması gerektiğini biliyoruz. Ve başka bir öğe hakkında sadece bir `DateTime` nesnesi olması gerektiğini biliyoruz. -Bu durumlarda, yapıyı kolayca tanımlamak için kullanılabilecek `Assert::equal()` ve `Assert::notEqual()` yöntemlerinin `$expected` parametresinin içindeki `Tester\Expect` 'u kullanabiliriz. +Bu durumlarda, `Assert::equal()` ve `Assert::notEqual()` metotlarının `$expected` parametresi içinde `Tester\Expect` kullanabiliriz, bu sayede yapıyı kolayca tanımlayabiliriz. ```php use Tester\Expect; Assert::equal([ - 'id' => Expect::type('int'), # we expect an integer + 'id' => Expect::type('int'), # tamsayı bekliyoruz 'username' => 'milo', - 'password' => Expect::match('%h%'), # we expect a string matching pattern - 'created_at' => Expect::type(DateTime::class), # we expect an instance of the class + 'password' => Expect::match('%h%'), # desene uyan karakter dizisi bekliyoruz + 'created_at' => Expect::type(DateTime::class), # sınıfın bir örneğini bekliyoruz ], User::create(123, 'milo', 'RandomPaSsWoRd')); ``` -`Expect` ile, `Assert` ile hemen hemen aynı iddialarda bulunabiliriz. Yani `Expect::same()`, `Expect::match()`, `Expect::count()`, vb. gibi metotlarımız var. Ek olarak, bunları şu şekilde zincirleyebiliriz: +`Expect` ile `Assert` ile neredeyse aynı doğrulama ifadelerini yapabiliriz. Yani, `Expect::same()`, `Expect::match()`, `Expect::count()` vb. metotlar bizim için kullanılabilir. Ayrıca, onları zincirleyebiliriz: ```php -Expect::type(MyIterator::class)->andCount(5); # we expect MyIterator and items count is 5 +Expect::type(MyIterator::class)->andCount(5); # MyIterator ve 5 öğe sayısı bekliyoruz ``` -Ya da kendi onaylama işleyicilerini yazabiliriz. +Veya kendi doğrulama ifadesi işleyicilerimizi yazabiliriz. ```php Expect::that(function ($value) { - # return false if expectation fails + # beklenti başarısız olursa false döndürün }); ``` -Başarısız İddialar Soruşturması .[#toc-failed-assertions-investigation] ------------------------------------------------------------------------ -Tester, bir iddia başarısız olduğunda hatanın nerede olduğunu gösterir. Karmaşık yapıları karşılaştırdığımızda, Tester karşılaştırılan değerlerin dökümlerini oluşturur ve bunları `output` dizinine kaydeder. Örneğin hayali test `Arrays.recursive.phpt` başarısız olduğunda dökümler aşağıdaki gibi kaydedilecektir: +Hatalı Doğrulama İfadelerini İnceleme +------------------------------------- +Bir doğrulama ifadesi başarısız olduğunda, Tester hatanın ne olduğunu yazdırır. Daha karmaşık yapıları karşılaştırıyorsak, Tester karşılaştırılan değerlerin dökümlerini oluşturur ve bunları `output` dizinine kaydeder. Örneğin, hayali `Arrays.recursive.phpt` testinin başarısız olması durumunda, dökümler aşağıdaki gibi kaydedilecektir: ``` app/ └── tests/ ├── output/ - │ ├── Arrays.recursive.actual # actual value - │ └── Arrays.recursive.expected # expected value + │ ├── Arrays.recursive.actual # gerçek değer + │ └── Arrays.recursive.expected # beklenen değer │ - └── Arrays.recursive.phpt # failing test + └── Arrays.recursive.phpt # başarısız olan test ``` -Dizinin adını `Tester\Dumper::$dumpDir` adresinden değiştirebiliriz. +Dizin adını `Tester\Dumper::$dumpDir` aracılığıyla değiştirebiliriz. diff --git a/tester/tr/guide.texy b/tester/tr/guide.texy index 1dcee56145..55ad374648 100644 --- a/tester/tr/guide.texy +++ b/tester/tr/guide.texy @@ -1,17 +1,17 @@ -Tester ile Başlarken -******************** +Nette Tester ile Başlarken +**************************
                                                                                                                            -İyi programcılar bile hata yapar. İyi bir programcı ile kötü bir programcı arasındaki fark, iyi programcının hatayı yalnızca bir kez yapması ve bir sonraki sefer otomatik testler kullanarak tespit etmesidir. +İyi programcılar bile hata yapar. İyi ve kötü bir programcı arasındaki fark, iyinin hatayı yalnızca bir kez yapması ve bir dahaki sefere otomatik testlerle tespit etmesidir. -- "Test etmeyen kişi kendi hatalarını tekrarlamaya mahkumdur." (bilge atasözü) -- "Bir hatadan kurtulduğumuzda, başka bir hata ortaya çıkar." (Murphy Kanunu) -- "Bir ifadeyi yazdırmak istediğinizde, bunun yerine bir test olarak yazın." (Martin Fowler) +- "Test etmeyen, hatalarını tekrarlamaya mahkumdur." (atasözü) +- "Bir hatadan kurtulduğumuzda, bir başkası ortaya çıkar." (Murphy Kanunu) +- "Ne zaman ekrana bir değişken yazdırma dürtüsü hissederseniz, bunun yerine bir test yazın." (Martin Fowler)
                                                                                                                            -PHP'de hiç aşağıdaki kodu yazdınız mı? +PHP'de hiç böyle bir kod yazdınız mı? ```php $obj = new MyClass; @@ -20,40 +20,40 @@ $result = $obj->process($input); var_dump($result); ``` -Peki, hiç bir fonksiyon çağrısı sonucunu sadece dönmesi gerekeni döndürüp döndürmediğini gözle kontrol etmek için döktünüz mü? Bunu mutlaka her gün birçok kez yapıyorsunuzdur. Elinizi kalbinizin üzerine koyarak, her şey çalışırsa, bu kodu siler ve sınıfın gelecekte bozulmayacağını mı beklersiniz? Murphy Yasası tam tersini garanti eder :-) +Yani, fonksiyonun çağrısının sonucunu sadece gözle kontrol etmek için mi yazdınız, olması gerekeni döndürüp döndürmediğini görmek için mi? Eminim bunu günde birçok kez yapıyorsunuzdur. Elinizi vicdanınıza koyun: her şey yolunda giderse, bu kodu siler misiniz? Sınıfın gelecekte bozulmayacağını mı bekliyorsunuz? Murphy Kanunları tersini garanti eder :-) -Aslında testi siz yazdınız. Bizim denetimimizi gerektirmemesi, sadece kendi kendini kontrol edebilmesi için küçük bir değişikliğe ihtiyacı var. Ve eğer silmediyseniz, her şeyin hala olması gerektiği gibi çalıştığını doğrulamak için gelecekte herhangi bir zamanda çalıştırabiliriz. Zaman içinde bu testlerden çok sayıda oluşturabilirsiniz, bu nedenle bunları otomatik olarak çalıştırabilmemiz iyi olurdu. +Aslında bir test yazdınız. Sadece gözle kontrol gerektirmemesi için biraz değiştirmeniz yeterli, böylece kendi kendini kontrol edebilir. Ve testi silmezseniz, gelecekte istediğiniz zaman çalıştırabilir ve her şeyin hala olması gerektiği gibi çalıştığını doğrulayabilirsiniz. Zamanla bu tür testlerden çok sayıda oluşturacaksınız, bu yüzden onları otomatik olarak çalıştırmak iyi olurdu. -Ve Nette Tester tam da bu konuda yardımcı oluyor. +Ve tüm bunlarda Nette Tester size yardımcı olacaktır. -Tester'ı Benzersiz Kılan Nedir? .[#toc-what-makes-tester-unique] -================================================================ +Tester'ı Benzersiz Kılan Nedir? +=============================== -Nette Tester için test yazmak benzersizdir, çünkü **her test bağımsız olarak çalıştırılabilen standart bir PHP betiğidir.** +Nette Tester için test yazmak benzersizdir çünkü **her test ayrı ayrı çalıştırılabilen normal bir PHP betiğidir.** -Böylece bir test yazdığınızda, bir programlama hatası olup olmadığını görmek için basitçe çalıştırabilirsiniz. Eğer düzgün çalışıyorsa. Çalışmıyorsa, IDE'nizde programı kolayca adımlayabilir ve bir hata arayabilirsiniz. Hatta bir tarayıcıda bile açabilirsiniz. +Yani, bir test yazdığınızda, onu kolayca çalıştırabilir ve örneğin içinde bir programlama hatası olup olmadığını öğrenebilirsiniz. Doğru çalışıp çalışmadığını. Çalışmıyorsa, IDE'nizde kolayca adım adım ilerleyebilir ve hatayı arayabilirsiniz. Hatta tarayıcıda bile açabilirsiniz. -Ve en önemlisi - çalıştırarak testi gerçekleştireceksiniz. Geçip geçmediğini veya başarısız olup olmadığını hemen öğreneceksiniz. Nasıl mı? Burada gösterelim. PHP dizisi kullanmak için önemsiz bir test yazalım ve bunu `ArrayTest.php` dosyasına kaydedelim: +Ve en önemlisi - onu çalıştırarak testi gerçekleştirirsiniz. Geçip geçmediğini veya başarısız olup olmadığını hemen öğrenirsiniz. Nasıl mı? Hadi gösterelim. PHP dizisiyle çalışmanın basit bir testini yazalım ve `ArrayTest.php` dosyasına kaydedelim: ```php .{file:ArrayTest.php} OK \-- -Testteki ifadeyi `Assert::contains('XXX', $stack);` olarak değiştirmeyi deneyin ve çalıştırıldığında ne olacağını izleyin: +Testte iddiayı yanlış `Assert::contains('XXX', $stack);` olarak değiştirmeyi deneyin ve çalıştırmada ne olacağını izleyin: /--pre .[terminal] $ php ArrayTest.php -Failed: ['foo'] should contain 'XXX' +Başarısız: ['foo'] içermeli 'XXX' -in ArrayTest.php(17) Assert::contains('XXX', $stack); +içinde ArrayTest.php(17) Assert::contains('XXX', $stack); -FAILURE +BAŞARISIZLIK \-- -Yazma [Testleri |Writing Tests] bölümünde yazma konusuna devam ediyoruz. +Yazmaya [Test Yazma|writing-tests] bölümünde devam ediyoruz. -Kurulum ve Gereksinimler .[#toc-installation-and-requirements] -============================================================== +Kurulum ve Gereksinimler +======================== -Tester tarafından istenen minimum PHP sürümü 7.1'dir (daha fazla ayrıntı için [desteklenen PHP sürümleri |#supported PHP versions] tablosuna bakın). Tercih edilen kurulum yöntemi [Composer'dır |best-practices:composer]: +Tester tarafından gereken minimum PHP sürümü 7.1'dir (daha ayrıntılı olarak [#Desteklenen PHP Sürümleri] tablosunda). Tercih edilen kurulum yöntemi [Composer |best-practices:composer] kullanmaktır: /--pre .[terminal] composer require --dev nette/tester \-- -Nette Tester'ı komut satırından çalıştırmayı deneyin (herhangi bir argüman olmadan sadece bir yardım özeti gösterecektir): +Komut satırından Nette Tester'ı çalıştırmayı deneyin (parametre olmadan yalnızca yardımı yazdırır): /--pre .[terminal] vendor/bin/tester \-- -Çalışan Testler .[#toc-running-tests] -===================================== +Testleri Çalıştırma +=================== -Uygulamamız büyüdükçe, test sayısı da onunla birlikte artar. Testleri tek tek çalıştırmak pratik olmayacaktır. Bu nedenle, Tester'ın komut satırından çağırdığımız bir toplu test çalıştırıcısı vardır. Parametre, testlerin bulunduğu dizindir. Nokta geçerli dizini gösterir. +Uygulama büyüdükçe, testlerin sayısı da onunla birlikte artar. Testleri tek tek çalıştırmak pratik olmazdı. Bu nedenle Tester, komut satırından çağırdığımız toplu bir test çalıştırıcısına sahiptir. Parametre olarak testlerin bulunduğu dizini belirtiriz. Geçerli dizin için nokta yeterlidir. /--pre .[terminal] vendor/bin/tester . \-- -Nette Tester çalıştırıcısı belirtilen dizini ve tüm alt dizinleri arar ve `*.phpt` ve `*Test.php` dosyaları olan testleri arar. Maske ile eşleştiği için `ArrayTest.php` testimizi de bulacaktır. +Test çalıştırıcısı belirtilen dizini ve tüm alt dizinleri tarar ve `*.phpt` ve `*Test.php` dosyaları olan testleri arar. Böylece maskeye uyduğu için `ArrayTest.php` testimizi de bulur. -Sonra test etmeye başlar. Her testi yeni bir PHP süreci olarak çalıştırır, böylece diğerlerinden tamamen yalıtılmış olarak çalışır. Birden fazla iş parçacığında paralel olarak çalışır, bu da onu son derece hızlı hale getirir. Ve ilk olarak önceki çalıştırma sırasında başarısız olan testleri çalıştırır, böylece hatayı düzeltip düzeltmediğinizi hemen anlarsınız. +Ardından test etmeye başlar. Her testi yeni bir PHP süreci olarak çalıştırır, böylece diğerlerinden tamamen izole edilmiş olarak çalışır. Onları birden fazla iş parçacığında paralel olarak çalıştırır ve bu sayede son derece hızlıdır. Ve ilk olarak önceki çalıştırmada başarısız olan testleri çalıştırır, böylece hatayı düzeltip düzeltmediğinizi hemen öğrenirsiniz. -Tamamlanan her test için, çalıştırıcı ilerlemeyi göstermek için bir karakter yazdırır: +Testlerin yürütülmesi sırasında, Tester sonuçları sürekli olarak terminale karakterler olarak yazdırır: -- . - test geçti -- s - test atlandı -- F - test başarısız +- . – test geçti +- s – test atlandı (skipped) +- F – test başarısız oldu (failed) -Çıktı şu şekilde görünebilir: +Çıktı şöyle görünebilir: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 -Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +Not: php.ini kullanılmıyor. +PHP 8.3.2 (cli) | php -n | 8 iş parçacığı ........s................F......... --- FAILED: greeting.phpt - Failed: 'Hello John' should be - ... 'Hello Peter' +-- BAŞARISIZ: greeting.phpt + Başarısız: 'Merhaba John' olmalı + ... 'Merhaba Peter' - in greeting.phpt(19) Assert::same('Hello Peter', $o->say('John')); + içinde greeting.phpt(19) Assert::same('Merhaba Peter', $o->say('John')); -FAILURES! (35 tests, 1 failures, 1 skipped, 1.7 seconds) +BAŞARISIZLIKLAR! (35 test, 1 başarısızlık, 1 atlandı, 1.7 saniye) \-- 35 test çalıştırıldı, biri başarısız oldu, biri atlandı. -[Çalıştırma testleri |Running tests] bölümünde devam ediyoruz. +Daha sonra [Testleri Çalıştırma|running-tests] bölümünde devam ediyoruz. -İzleme Modu .[#toc-watch-mode] -============================== +İzleme Modu +=========== -Kodu yeniden düzenliyor musunuz? Ya da TDD (Test Güdümlü Geliştirme) metodolojisine göre mi geliştiriyorsunuz? O zaman izleme modunu seveceksiniz. Tester kaynak kodlarını izler ve değiştirildiğinde kendini çalıştırır. +Kodu yeniden düzenliyor musunuz? Veya TDD (Test Driven Development) metodolojisine göre mi geliştiriyorsunuz? O zaman izleme modunu seveceksiniz. Tester bu modda kaynak kodlarını izler ve değişiklik olduğunda kendini yeniden başlatır. -Geliştirme sırasında, monitörün köşesinde bir terminaliniz vardır, burada yeşil durum çubuğu yanar ve aniden kırmızıya döndüğünde, istenmeyen bir şey yaptığınızı bilirsiniz. Aslında programladığınız ve renge sadık kalmaya çalıştığınız harika bir oyun. +Geliştirme sırasında monitörünüzün köşesinde yeşil durum çubuğu yanan bir terminaliniz olur ve aniden kırmızıya döndüğünde, bir şeyi tam olarak doğru yapmadığınızı bilirsiniz. Aslında programlama yaparken rengi korumaya çalıştığınız harika bir oyundur. -İzleme modu [--watch |running-tests#w-watch-path] parametresi kullanılarak başlatılır. +İzleme modu [--watch |running-tests#-w --watch path] parametresiyle başlatılır. -CodeCoverage Raporları .[#toc-codecoverage-reports] -=================================================== +Kod Kapsamı Raporları +===================== -Test Uzmanı, testlerin ne kadar kaynak kodu kapsadığına dair genel bir bakış içeren raporlar oluşturabilir. Rapor, insan tarafından okunabilir HTML biçiminde veya daha fazla makine işlemi için Clover XML biçiminde olabilir. +Tester, testlerin ne kadar kaynak kodunu kapsadığına dair genel bir bakış içeren raporlar oluşturabilir. Rapor, ya insan tarafından okunabilir HTML formatında ya da daha fazla makine işleme için Clover XML formatında olabilir. -Kod kapsamıyla birlikte "örnek HTML raporuna":https://files.nette.org/tester/coverage.html bakın. +Kod kapsamıyla ilgili "HTML raporu örneğine":attachment:coverage.html bakın. -Desteklenen PHP sürümleri .[#toc-supported-php-versions] -======================================================== +Desteklenen PHP Sürümleri +========================= -| sürüm | PHP ile uyumlu +| sürüm | PHP ile uyumlu |------------------|------------------- -| Tester 2.5 | PHP 8.0 - 8.2 -| Tester 2.4 | PHP 7.2 - 8.2 -| Tester 2.3 | PHP 7.1 - 8.0 -| Tester 2.1 - 2.2 | PHP 7.1 - 7.3 -| Tester 2.0 | PHP 5.6 - 7.3 -| Tester 1.7 | PHP 5.3 - 7.3 + HHVM 3.3+ -| Tester 1.6 | PHP 5.3 - 7.0 + HHVM -| Tester 1.3 - 1.5 | PHP 5.3 - 5.6 + HHVM -| Tester 0.9 - 1.2 | PHP 5.3 - 5.6 - -En son yama sürümleri için geçerlidir. - -Tester 1.7 sürümüne kadar [HHVM |https://hhvm.com] 3.3.0 veya daha yenisini destekliyordu ( `tester -p hhvm` adresini kullanarak). Tester 2.0'dan beri destek kesilmiştir. Kullanımı basitti: +| Tester 2.5 | PHP 8.0 – 8.3 +| Tester 2.4 | PHP 7.2 – 8.2 +| Tester 2.3 | PHP 7.1 – 8.0 +| Tester 2.1 – 2.2 | PHP 7.1 – 7.3 +| Tester 2.0 | PHP 5.6 – 7.3 +| Tester 1.7 | PHP 5.3 – 7.3 + HHVM 3.3+ +| Tester 1.6 | PHP 5.3 – 7.0 + HHVM +| Tester 1.3 – 1.5 | PHP 5.3 – 5.6 + HHVM +| Tester 0.9 – 1.2 | PHP 5.3 – 5.6 + +Son yama sürümü için geçerlidir. + +Tester sürüm 1.7'ye kadar [HHVM |https://hhvm.com] 3.3.0 veya üstünü de destekliyordu (`tester -p hhvm` aracılığıyla). Destek Tester sürüm 2.0'dan itibaren sonlandırıldı. diff --git a/tester/tr/helpers.texy b/tester/tr/helpers.texy index 7c53678321..db78739c92 100644 --- a/tester/tr/helpers.texy +++ b/tester/tr/helpers.texy @@ -1,31 +1,45 @@ -Yardımcılar -*********** +Yardımcı Sınıflar +***************** -DomQuery .[#toc-domquery] -------------------------- -`Tester\DomQuery` HTML veya XML içeriğini test etmeyi kolaylaştıran yöntemlerle `SimpleXMLElement` adresini genişleten bir sınıftır. +DomQuery +-------- +`Tester\DomQuery`, `SimpleXMLElement`'i CSS seçicileri kullanarak HTML veya XML'de kolay arama yapma özelliğiyle genişleten bir sınıftır. ```php -# let's have an HTML document in $html that we load -$dom = Tester\DomQuery::fromHtml($html); - -# we can test the presence of elements using CSS selectors -Assert::true($dom->has('form#registration')); -Assert::true($dom->has('input[name="username"]')); -Assert::true($dom->has('input[type="submit"]')); - -# or select elements as array of DomQuery -$elems = $dom->find('input[data-autocomplete]'); +# HTML dizesinden DomQuery oluşturma +$dom = Tester\DomQuery::fromHtml(' +
                                                                                                                            +

                                                                                                                            Başlık

                                                                                                                            +
                                                                                                                            Metin
                                                                                                                            +
                                                                                                                            +'); + +# CSS seçicileri kullanarak öğelerin varlığını test etme +Assert::true($dom->has('article.post')); +Assert::true($dom->has('h1')); + +# Öğeleri DomQuery nesneleri dizisi olarak bulma +$headings = $dom->find('h1'); +Assert::same('Başlık', (string) $headings[0]); + +# Öğenin seçiciyle eşleşip eşleşmediğini test etme (sürüm 2.5.3'ten itibaren) +$content = $dom->find('.content')[0]; +Assert::true($content->matches('div')); +Assert::false($content->matches('p')); + +# Seçiciyle eşleşen en yakın atayı bulma (2.5.5'ten itibaren) +$article = $content->closest('.post'); +Assert::true($article->matches('article')); ``` -FileMock .[#toc-filemock] -------------------------- -`Tester\FileMock` `fopen()` , `file_get_contents()` veya `parse_ini_file()` gibi işlevleri kullanan bir kodu test etmenize yardımcı olmak için bellekteki dosyaları taklit eder. Örneğin: +FileMock +-------- +`Tester\FileMock`, bellekte dosyaları taklit eder ve `fopen()`, `file_get_contents()`, `parse_ini_file()` ve benzeri fonksiyonları kullanan kodun test edilmesini kolaylaştırır. Kullanım örneği: ```php -# Tested class +# Test edilen sınıf class Logger { public function __construct( @@ -39,21 +53,21 @@ class Logger } } -# New empty file +# Yeni boş dosya $file = Tester\FileMock::create(''); $logger = new Logger($file); -$logger->log('Login'); -$logger->log('Logout'); +$logger->log('Giriş'); +$logger->log('Çıkış'); -# Created content testing -Assert::same("Login\nLogout\n", file_get_contents($file)); +# Oluşturulan içeriği test ediyoruz +Assert::same("Giriş\nÇıkış\n", file_get_contents($file)); ``` Assert::with() .[filter] ------------------------ -Bu bir iddia değil, özel yöntemleri ve özellik nesnelerini test etmek için bir yardımcıdır. +Bu bir doğrulama ifadesi değil, nesnelerin özel metotlarını ve özelliklerini test etmek için bir yardımcıdır. ```php class Entity @@ -65,17 +79,17 @@ class Entity $ent = new Entity; Assert::with($ent, function () { - Assert::true($this->enabled); // erişilebilir özel $ent->enabled + Assert::true($this->enabled); // erişilebilir hale getirilen özel $ent->enabled }); ``` Helpers::purge() .[filter] -------------------------- -`purge()` yöntemi belirtilen dizini oluşturur ve zaten mevcutsa tüm içeriğini siler. Geçici dizin oluşturmak için kullanışlıdır. Örneğin `tests/bootstrap.php`: +`purge()` metodu belirtilen dizini oluşturur ve eğer zaten varsa, tüm içeriğini siler. Geçici bir dizin oluşturmak için kullanışlıdır. Örneğin `tests/bootstrap.php` içinde: ```php -@mkdir(__DIR__ . '/tmp'); # @ - directory may already exist +@mkdir(__DIR__ . '/tmp'); # @ - dizin zaten var olabilir define('TempDir', __DIR__ . '/tmp/' . getmypid()); Tester\Helpers::purge(TempDir); @@ -84,25 +98,25 @@ Tester\Helpers::purge(TempDir); Environment::lock() .[filter] ----------------------------- -Testler paralel olarak çalışır. Bazen çalışan testlerin çakışmaması gerekir. Genellikle veritabanı testlerinin veritabanı içeriğini hazırlaması gerekir ve testin çalışma süresi boyunca hiçbir şeyin onları rahatsız etmemesi gerekir. Bu durumlarda `Tester\Environment::lock($name, $dir)` adresini kullanırız: +Testler paralel olarak çalıştırılır. Ancak bazen testlerin çalışmasının çakışmamasını isteriz. Tipik olarak veritabanı testlerinde, testin veritabanı içeriğini hazırlaması ve başka bir testin çalışma süresi boyunca veritabanına dokunmaması gerekir. Bu testlerde `Tester\Environment::lock($name, $dir)` kullanırız: ```php Tester\Environment::lock('database', __DIR__ . '/tmp'); ``` -İlk bağımsız değişken bir kilit adıdır. İkincisi ise kilidin kaydedileceği dizine giden yoldur. İlk olarak kilidi alan test çalışır. Diğer testler tamamlanana kadar beklemelidir. +İlk parametre kilit adıdır, ikincisi kilidi saklamak için dizin yoludur. Kilidi ilk alan test çalışır, diğer testler onun tamamlanmasını beklemek zorundadır. Environment::bypassFinals() .[filter] ------------------------------------- -`final` olarak işaretlenen sınıfların veya yöntemlerin test edilmesi zordur. Bir test başlangıcında `Tester\Environment::bypassFinals()` adresinin çağrılması, kod yüklemesi sırasında `final` anahtar sözcüklerinin kaldırılmasına neden olur. +`final` olarak işaretlenmiş sınıflar veya metotlar test edilmesi zordur. Testin başında `Tester\Environment::bypassFinals()` çağrısı, kod yüklenirken `final` anahtar kelimelerinin atlanmasına neden olur. ```php require __DIR__ . '/bootstrap.php'; Tester\Environment::bypassFinals(); -class MyClass extends NormallyFinalClass # <-- NormallyFinalClass is not final anymore +class MyClass extends NormallyFinalClass # <-- NormallyFinalClass artık final değil { // ... } @@ -111,15 +125,15 @@ class MyClass extends NormallyFinalClass # <-- NormallyFinalClass is not final Environment::setup() .[filter] ------------------------------ -- hata dökümünün okunabilirliğini artırır (renklendirme dahil), aksi takdirde varsayılan PHP yığın izi yazdırılır -- assertion'ların testte çağrıldığını kontrol etmeyi sağlar, aksi takdirde assertion'ları olmayan (örneğin unutulmuş) testler de geçer -- `--coverage` kullanıldığında kod kapsamı toplayıcısını otomatik olarak başlatır (daha sonra açıklanacaktır) -- kodun sonunda OK veya FAILURE durumunu yazdırır +- hata çıktısının okunabilirliğini artırır (renklendirme dahil), aksi takdirde varsayılan PHP yığın izi yazdırılır +- testte doğrulama ifadelerinin çağrılıp çağrılmadığını kontrol etmeyi etkinleştirir, aksi takdirde doğrulama ifadesi olmayan (örneğin unutulmuş) bir test de geçer +- `--coverage` kullanıldığında, çalıştırılan kod hakkındaki bilgilerin toplanmasını otomatik olarak başlatır (daha sonra açıklanmıştır) +- betiğin sonunda OK veya FAILURE durumunu yazdırır Environment::setupFunctions() .[filter]{data-version:2.5} --------------------------------------------------------- -Testleri bölebileceğiniz `test()`, `setUp()` ve `tearDown()` global fonksiyonlarını oluşturur. +Testleri bölebileceğiniz `test()`, `testException()`, `setUp()` ve `tearDown()` global fonksiyonlarını oluşturur. ```php test('test açıklaması', function () { @@ -132,21 +146,21 @@ test('test açıklaması', function () { Environment::VariableRunner .[filter] ------------------------------------- -Testin doğrudan mı yoksa Tester aracılığıyla mı çalıştırıldığını öğrenmenizi sağlar. +Testin doğrudan mı yoksa Tester aracılığıyla mı çalıştırıldığını belirlemeyi sağlar. ```php if (getenv(Tester\Environment::VariableRunner)) { - # run by Tester + # Tester tarafından çalıştırıldı } else { - # another way + # Başka bir şekilde çalıştırıldı } ``` Environment::VariableThread .[filter] ------------------------------------- -Tester, testleri belirli sayıda iş parçacığında paralel olarak çalıştırır. İlgilendiğimizde çevresel bir değişkende bir iş parçacığı numarası bulacağız: +Tester, testleri belirtilen sayıda iş parçacığında paralel olarak çalıştırır. İş parçacığı numarasını merak ediyorsak, onu ortam değişkeninden öğreniriz: ```php -echo "I'm running in a thread number " . getenv(Tester\Environment::VariableThread); +echo "Çalıştığım iş parçacığı numarası " . getenv(Tester\Environment::VariableThread); ``` diff --git a/tester/tr/running-tests.texy b/tester/tr/running-tests.texy index 33eb45386c..e0108e3a3a 100644 --- a/tester/tr/running-tests.texy +++ b/tester/tr/running-tests.texy @@ -1,83 +1,83 @@ -Çalışan Testler -*************** +Testleri Çalıştırma +******************* .[perex] -Nette Tester'ın en görünür kısmı komut satırı test çalıştırıcısıdır. Son derece hızlı ve sağlamdır, çünkü tüm testleri otomatik olarak birden fazla iş parçacığında paralel olarak ayrı işlemler olarak başlatır. Ayrıca kendini izleme modunda da çalıştırabilir. +Nette Tester'ın en belirgin parçası, komut satırından çalışan test çalıştırıcısıdır. Tüm testleri otomatik olarak ayrı işlemler halinde ve paralel olarak birden fazla iş parçacığında çalıştırdığı için son derece hızlı ve sağlamdır. Ayrıca, sözde izleme modunda kendi kendini de çalıştırabilir. -Nette Tester test çalıştırıcısı komut satırından çağrılır. Parametre olarak test dizinini geçeceğiz. Geçerli dizin için sadece bir nokta girin: +Test çalıştırıcısını komut satırından çağırırız. Parametre olarak testlerin bulunduğu dizini belirtiriz. Mevcut dizin için sadece bir nokta girmek yeterlidir: /--pre .[terminal] vendor/bin/tester . \-- -Çağrıldığında, test koşucusu belirtilen dizini ve tüm alt dizinleri tarar ve `*.phpt` ve `*Test.php` dosyaları olan testleri arar. Ayrıca hangilerinin ve nasıl çalıştırılacağını bilmek için [ek açıklamalarını |test-annotations] okur ve değerlendirir. +Test çalıştırıcısı belirtilen dizini ve tüm alt dizinleri tarayarak `*.phpt` ve `*Test.php` dosyaları olan testleri arar. Aynı zamanda [ek açıklamalarını |test-annotations] okur ve değerlendirir, böylece hangilerini ve nasıl çalıştıracağını bilir. -Daha sonra testleri yürütecektir. Tamamlanan her test için, koşucu ilerlemeyi göstermek için bir karakter yazdırır: +Ardından testleri çalıştırır. Testlerin yürütülmesi sırasında, sonuçları sürekli olarak terminale karakterler olarak yazdırır: -- . - test geçti -- s - test atlandı -- F - test başarısız +- . – test geçti +- s – test atlandı (skipped) +- F – test başarısız oldu (failed) -Çıktı aşağıdaki gibi görünebilir: +Çıktı örneğin şöyle görünebilir: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 -Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +Not: php.ini kullanılmıyor. +PHP 8.3.2 (cli) | php -n | 8 iş parçacığı ........s.......................... -OK (35 tests, 1 skipped, 1.7 seconds) +OK (35 test, 1 atlandı, 1.7 saniye) \-- -Tekrar çalıştırdığınızda, ilk olarak önceki çalıştırma sırasında başarısız olan testleri çalıştırır, böylece hatayı düzeltip düzeltmediğinizi hemen anlarsınız. +Tekrarlanan çalıştırmada, önce önceki çalıştırmada başarısız olan testleri yürütür, böylece hatayı düzeltip düzeltmediğinizi hemen öğrenirsiniz. -Hiç kimse başarısız olmazsa test cihazının çıkış kodu sıfırdır. Aksi takdirde sıfır değildir. +Hiçbir test başarısız olmazsa, Tester'ın dönüş kodu sıfırdır. Aksi takdirde, dönüş kodu sıfır değildir. .[warning] -Tester PHP süreçlerini `php.ini` olmadan çalıştırır. Daha fazla ayrıntı [Kendi php.ini |#Own php.ini] bölümünde. +Tester, PHP işlemlerini `php.ini` olmadan çalıştırır. Daha ayrıntılı bilgi [#Özel php.ini] bölümünde. -Komut Satırı Seçenekleri .[#toc-command-line-options] -===================================================== +Komut Satırı Parametreleri +========================== -Tester'ı parametresiz veya `-h` seçeneği ile çalıştırarak komut satırı seçeneklerine genel bakış elde ederiz: +Tüm komut satırı seçeneklerine genel bir bakış, Tester'ı parametresiz veya `-h` parametresiyle çalıştırarak elde edilir: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 - -Usage: - tester [options] [ | ]... - -Options: - -p Specify PHP interpreter to run (default: php). - -c Look for php.ini file (or look in directory) . - -C Use system-wide php.ini. - -l | --log Write log to file . - -d ... Define INI entry 'key' with value 'value'. - -s Show information about skipped tests. - --stop-on-fail Stop execution upon the first failure. - -j Run jobs in parallel (default: 8). - -o Specify output format. - -w | --watch Watch directory. - -i | --info Show tests environment info and exit. - --setup Script for runner setup. - --temp Path to temporary directory. Default: sys_get_temp_dir(). - --colors [1|0] Enable or disable colors. - --coverage Generate code coverage report to file. - --coverage-src Path to source code. - -h | --help This help. + |_| \___ /___) |_| \___ |_|_\ v2.5.2 + +Kullanım: + tester [seçenekler] [ | ]... + +Seçenekler: + -p Çalıştırılacak PHP yorumlayıcısını belirtin (varsayılan: php). + -c php.ini dosyasını içinde arayın (veya dizine bakın). + -C Sistem genelindeki php.ini'yi kullanın. + -d ... 'anahtar' INI girişini 'değer' değeriyle tanımlayın. + -s Atlanan testler hakkında bilgi gösterin. + --stop-on-fail İlk başarısızlıkta yürütmeyi durdurun. + -j Paralel olarak iş çalıştırın (varsayılan: 8). + -o (örneğin -o junit:output.xml) + İsteğe bağlı dosya adıyla bir veya daha fazla çıktı biçimi belirtin. + -w | --watch Dizini izleyin. + -i | --info Test ortamı bilgilerini gösterin ve çıkın. + --setup Çalıştırıcı kurulumu için betik. + --temp Geçici dizin yolu. Varsayılan olarak sys_get_temp_dir() kullanılır. + --colors [1|0] Renkleri etkinleştirin veya devre dışı bırakın. + --coverage Dosyaya kod kapsamı raporu oluşturun. + --coverage-src Kaynak kodunun yolu. + -h | --help Bu yardım. \-- -p .[filter] ------------------- -Testleri çalıştırmak için kullanılacak PHP ikilisini belirtir. Varsayılan olarak `php` şeklindedir. +Testleri çalıştırmak için kullanılacak PHP ikili dosyasını belirtir. Varsayılan olarak `php` kullanılır. /--pre .[terminal] tester -p /home/user/php-7.2.0-beta/php-cgi tests @@ -86,26 +86,17 @@ tester -p /home/user/php-7.2.0-beta/php-cgi tests -c .[filter] ------------------- -Testleri çalıştırırken hangi `php.ini` adresinin kullanılacağını belirtir. Varsayılan olarak php.ini kullanılmaz. Daha fazla bilgi için [Own php.ini |#Own php.ini] bölümüne bakın. +Testleri çalıştırırken hangi `php.ini` dosyasının kullanılacağını belirtir. Varsayılan olarak hiçbir php.ini kullanılmaz. Daha fazla bilgi için [#Özel php.ini] bölümüne bakın. -C .[filter] ------------ -Sistem genelinde bir `php.ini` kullanılır. Yani UNIX platformunda, tüm `/etc/php/{sapi}/conf.d/*.ini` dosyaları da. [Kendi php.ini |#Own php.ini] bölümüne bakın. - - -''-l | --log '' .[filter] -------------------------------- -Test ilerlemesi dosyaya yazılır. Tüm başarısız, atlanmış ve ayrıca başarılı testler: - -/--pre .[terminal] -tester --log /var/log/tests.log tests -\-- +Sistem genelindeki `php.ini` kullanılır. UNIX üzerinde ayrıca ilgili tüm INI dosyaları `/etc/php/{sapi}/conf.d/*.ini` kullanılır. Daha fazla bilgi için [#Özel php.ini] bölümüne bakın. -d .[filter] ------------------------ -Sınamalar için PHP yapılandırma yönergesinin değerini ayarlar. Değiştirge birden çok kez kullanılabilir. +Testler için PHP yapılandırma yönergesinin değerini ayarlar. Parametre birden çok kez kullanılabilir. /--pre .[terminal] tester -d max_execution_time=20 @@ -114,34 +105,36 @@ tester -d max_execution_time=20 -s --- -Atlanan testler hakkında bilgi gösterilecektir. +Atlanan testler hakkında bilgi gösterilir. ---başarısızlık durumunda durdur .[filter] ------------------------------------------ -Test cihazı ilk başarısız testte testi durdurur. +--stop-on-fail .[filter] +------------------------ +Tester, ilk başarısız olan testte test işlemini durdurur. -j .[filter] ------------------ -Testler bir `` paralel ön işlemler. Varsayılan değer 8'dir. Testleri seri olarak çalıştırmak istiyorsak 1 değerini kullanırız. +Kaç tane paralel test işleminin başlatılacağını belirtir. Varsayılan değer 8'dir. Tüm testlerin seri olarak çalışmasını istiyorsak, 1 değerini kullanırız. --o .[filter] -------------------------------------- -Çıktı biçimi. Varsayılan biçim konsol biçimidir. +-o .[filter] +------------------------------------------------------- +Çıktı biçimini ayarlar. Varsayılan biçim konsol biçimidir. Çıktının yazılacağı bir dosya adı belirtebilirsiniz (örneğin `-o junit:output.xml`). `-o` seçeneği birden çok kez tekrarlanabilir ve böylece aynı anda birden çok biçim oluşturulabilir. -- `console`: varsayılan ile aynı, ancak ASCII logosu bu durumda yazdırılmaz -- `tap`: Makine işlemesi için uygun [TAP formatı |https://en.wikipedia.org/wiki/Test_Anything_Protocol] -- `junit`: JUnit XML formatı, makine işlemesi için de uygun +- `console`: varsayılan biçimle aynıdır, ancak bu durumda ASCII logosu gösterilmez +- `console-lines`: console'a benzer, ancak her testin sonucu ek bilgilerle ayrı bir satırda listelenir +- `tap`: makine işleme için uygun [TAP formatı |https://en.wikipedia.org/wiki/Test_Anything_Protocol] +- `junit`: JUnit XML formatı, aynı zamanda makine işleme için uygundur +- `log`: Test ilerlemesinin çıktıları. Tüm başarısız, atlanmış ve ayrıca başarılı testler - `none`: hiçbir şey yazdırılmaz ''-w | --watch '' .[filter] --------------------------------- -Tester, testler tamamlandıktan sonra sona ermez, ancak verilen dizindeki PHP dosyalarını çalıştırmaya ve izlemeye devam eder. Değiştirildiğinde testleri tekrar çalıştırır. Birden fazla dizini izlemek istiyorsak parametre birden fazla kez kullanılabilir. +Test tamamlandıktan sonra Tester sonlanmaz, çalışmaya devam eder ve belirtilen dizindeki PHP dosyalarını izler. Değişiklik olduğunda testleri yeniden başlatır. Parametre, birden fazla dizini izlemek istiyorsak birden çok kez kullanılabilir. -Bir kütüphanenin yeniden düzenlenmesi veya testlerde hata ayıklama sırasında kullanışlıdır. +Bir kütüphaneyi yeniden düzenlerken veya testlerde hata ayıklarken kullanışlıdır. /--pre .[terminal] tester --watch src tests @@ -150,40 +143,40 @@ tester --watch src tests ''-i | --info'' .[filter] ------------------------- -Bir test çalıştırma ortamı hakkındaki bilgileri gösterir. Örneğin: +Testler için çalışma zamanı ortamı hakkındaki bilgileri gösterir. Örneğin: /--pre .[terminal] tester -p /usr/bin/php7.1 -c tests/php.ini --info -PHP binary: +PHP ikili dosyası: /usr/bin/php7.1 -PHP version: +PHP sürümü: 7.1.7-1+0~20170711133844.5+jessie~1.gbp5284f4 (cli) -Code coverage engines: -(not available) +Kod kapsamı motorları: +(mevcut değil) -Loaded php.ini files: +Yüklenen php.ini dosyaları: /var/www/dev/demo/tests/php.ini -PHP temporary directory: +PHP geçici dizini: /tmp -Loaded extensions: +Yüklenen uzantılar: Core, ctype, date, dom, ereg, fileinfo, filter, hash, ... \-- ---kurulum .[filter] --------------------------- -Tester başlangıçta verilen PHP betiğini yükler. İçinde `Tester\Runner\Runner $runner` değişkeni mevcuttur. `tests/runner-setup.php` dosyasını varsayalım: +--setup .[filter] +------------------------ +Tester başlangıçta belirtilen PHP betiğini yükler. Bu betikte `Tester\Runner\Runner $runner` değişkeni mevcuttur. İçeriği aşağıdaki gibi olan `tests/runner-setup.php` dosyasını varsayalım: ```php $runner->outputHandlers[] = new MyOutputHandler; ``` -ve Testernı çalıştırıyoruz: +Tester'ı çalıştırırız: /--pre .[terminal] tester --setup tests/runner-setup.php tests @@ -192,47 +185,47 @@ tester --setup tests/runner-setup.php tests --temp .[filter] ----------------------- -Tester'ın geçici dosyaları için bir dizin yolu ayarlar. Varsayılan değer `sys_get_temp_dir()` tarafından döndürülür. Varsayılan değer geçerli olmadığında, fark edilirsiniz. +Tester'ın geçici dosyaları için dizin yolunu ayarlar. Varsayılan değeri `sys_get_temp_dir()` fonksiyonu döndürür. Varsayılan değer geçerli değilse, bir uyarı alırsınız. -Hangi dizinin kullanıldığından emin değilsek Tester'ı `--info` parametresiyle çalıştırabiliriz. +Hangi dizinin kullanıldığından emin değilsek, Tester'ı `--info` parametresiyle çalıştırırız. --colors 1|0 .[filter] ---------------------- -Tester varsayılan olarak renklendirilebilir bir terminali algılar ve çıkışını renklendirir. Bu seçenek otomatik algılamanın üzerindedir. Renklendirmeyi global olarak bir sistem ortam değişkeni ile ayarlayabiliriz `NETTE_TESTER_COLORS`. +Varsayılan olarak Tester renkli bir terminal algılar ve çıktısını renklendirir. Bu seçenek otomatik algılamayı geçersiz kılar. Renklendirmeyi genel olarak `NETTE_TESTER_COLORS` sistem ortam değişkeniyle ayarlayabiliriz. ---kapsam .[filter] -------------------------- -Test cihazı, kaynak kodun testler tarafından ne kadar kapsandığını gösteren bir rapor oluşturacaktır. Bu seçenek, PHP uzantısı [Xdebug |https://xdebug.org/] veya [PCOV |https://github.com/krakjoe/pcov] 'un etkinleştirilmesini veya daha hızlı olan PHPDBG SAPI ile PHP 7'yi gerektirir. Hedef dosya uzantısı içeriğin formatını belirler. HTML veya Clover XML. +--coverage .[filter] +--------------------------- +Tester, testlerin kaynak kodunun ne kadarını kapsadığına dair bir rapor oluşturur. Bu seçenek, kurulu [Xdebug |https://xdebug.org/] PHP uzantısını veya [PCOV |https://github.com/krakjoe/pcov]'u ya da daha hızlı olan PHPDBG SAPI'li PHP 7'yi gerektirir. Hedef dosyanın uzantısı, biçimini belirler: HTML veya Clover XML. /--pre .[terminal] -tester tests --coverage coverage.html # HTML report -tester tests --coverage coverage.xml # Clover XML report +tester tests --coverage coverage.html # HTML raporu +tester tests --coverage coverage.xml # Clover XML raporu \-- -Toplama mekanizmasını seçme önceliği aşağıdaki gibidir: +Mekanizma seçim önceliği şöyledir: 1) PCOV 2) PHPDBG 3) Xdebug -Kapsamlı testler PHPDBG tarafından çalıştırılırken bellek tükenmesi nedeniyle başarısız olabilir. Kapsama veri toplama işlemi bellek tüketen bir işlemdir. Bu durumda, bir testin içinde `Tester\CodeCoverage\Collector::flush()` adresini çağırmak yardımcı olabilir. Toplanan verileri dosyaya aktarır ve belleği boşaltır. Veri toplama işlemi devam etmediğinde veya Xdebug kullanıldığında, çağrının bir etkisi yoktur. +PHPDBG kullanırken, kapsamlı testlerde bellek tükenmesi nedeniyle test hatasıyla karşılaşabiliriz. Kod kapsamı bilgilerinin toplanması bellek yoğundur. Bu durumda, test içinde `Tester\CodeCoverage\Collector::flush()` çağrısı yardımcı olur. Bu çağrı, toplanan verileri diske yazar ve belleği serbest bırakır. Veri toplama işlemi gerçekleşmiyorsa veya Xdebug kullanılıyorsa, çağrının hiçbir etkisi olmaz. -Kod kapsamı ile"Bir HTML raporu örneği":https://files.nette.org/tester/coverage.html. +Kod kapsamıyla ilgili "HTML raporu örneği":attachment:coverage.html. --coverage-src .[filter] ------------------------------- -Bunu `--coverage` seçeneği ile aynı anda kullanıyoruz. Bu `` raporu oluşturduğumuz kaynak koda giden bir yoldur. Tekrar tekrar kullanılabilir. +`--coverage` seçeneğiyle birlikte kullanılır. ``, raporun oluşturulduğu kaynak kodlarının yoludur. Bu parametre birden çok kez kullanılabilir. -Kendi php.ini .[#toc-own-php-ini] -================================= -Tester PHP süreçlerini `-n` seçeneği ile çalıştırır, bu da `php.ini` 'un yüklenmediği anlamına gelir (UNIX'te `/etc/php/conf.d/*.ini` 'dan bile). Bu, çalıştırılan testler için aynı ortamı sağlar, ancak aynı zamanda sistem PHP'si tarafından yaygın olarak yüklenen tüm harici PHP uzantılarını devre dışı bırakır. +Özel php.ini +============ +Tester, PHP işlemlerini `-n` parametresiyle çalıştırır, bu da hiçbir `php.ini` dosyasının yüklenmediği anlamına gelir. UNIX üzerinde `/etc/php/conf.d/*.ini` içindekiler bile yüklenmez. Bu, testlerin çalışması için tutarlı bir ortam sağlar, ancak aynı zamanda sistem PHP'si tarafından normalde yüklenen tüm PHP uzantılarını da devre dışı bırakır. -Sistem yapılandırmasını saklamak istiyorsanız `-C` parametresini kullanın. +Sistem php.ini dosyalarının yüklenmesini sürdürmek istiyorsanız, `-C` parametresini kullanın. -Bazı uzantılara veya bazı özel INI ayarlarına ihtiyacınız varsa, kendi `php.ini` dosyanızı oluşturmanızı ve bunu testler arasında dağıtmanızı öneririz. Daha sonra Tester'ı `-c` seçeneği ile çalıştırıyoruz, örneğin `tester -c tests/php.ini`. INI dosyası aşağıdaki gibi görünebilir: +Testler için bazı uzantılara veya özel INI ayarlarına ihtiyacınız varsa, testlerle birlikte dağıtılacak kendi `php.ini` dosyanızı oluşturmanız önerilir. Tester'ı daha sonra `-c` parametresiyle çalıştırırız, örneğin `tester -c tests/php.ini tests`. INI dosyası şöyle görünebilir: ```ini [PHP] @@ -243,4 +236,4 @@ extension=php_pdo_pgsql.dll memory_limit=512M ``` -Tester'ı UNIX'te bir sistem `php.ini` ile çalıştırmak, örneğin `tester -c /etc/php/cgi/php.ini`, `/etc/php/conf.d/*.ini` adresinden diğer INI'leri yüklemez. Bu PHP'nin davranışıdır, Tester'ın değil. +Tester'ı UNIX üzerinde sistem `php.ini` ile çalıştırmak, örneğin `tester -c /etc/php/cli/php.ini`, `/etc/php/conf.d/*.ini` içindeki diğer INI dosyalarını yüklemez. Bu, Tester'ın değil, PHP'nin bir özelliğidir. diff --git a/tester/tr/test-annotations.texy b/tester/tr/test-annotations.texy index 05ec2baa1e..629cde03d1 100644 --- a/tester/tr/test-annotations.texy +++ b/tester/tr/test-annotations.texy @@ -4,13 +4,13 @@ Test Ek Açıklamaları .[perex] Ek açıklamalar, testlerin [komut satırı test çalıştırıcısı |running-tests] tarafından nasıl ele alınacağını belirler. Test dosyasının başına yazılırlar. -Ek açıklamalar büyük/küçük harfe duyarlı değildir. Ayrıca, test normal bir PHP betiği olarak manuel olarak çalıştırılırsa hiçbir etkisi olmaz. +Ek açıklamalarda büyük/küçük harf duyarlılığı yoktur. Ayrıca, test manuel olarak normal bir PHP betiği olarak çalıştırılırsa hiçbir etkileri olmaz. Örnek: ```php /** - * TEST: Basic database query test. + * TEST: Temel veritabanı sorgu testi. * * @dataProvider files/databases.ini * @exitCode 56 @@ -23,17 +23,17 @@ require __DIR__ . '/../bootstrap.php'; TEST .[filter] -------------- -Aslında bir açıklama değildir. Yalnızca başarısız olduğunda veya günlüklere yazdırılan test başlığını ayarlar. +Bu aslında bir ek açıklama değildir, yalnızca bir başarısızlık durumunda veya günlüğe yazdırılan test başlığını belirler. @skip .[filter] --------------- -Test atlanır. Geçici test devre dışı bırakma için kullanışlıdır. +Test atlanır. Testleri geçici olarak devre dışı bırakmak için kullanışlıdır. @phpVersion .[filter] --------------------- -İlgili PHP sürümü tarafından çalıştırılmazsa test atlanır. Ek açıklamayı şu şekilde yazıyoruz `@phpVersion [operator] version`. Operatörü dışarıda bırakabiliriz, varsayılan değer `>=`. Örnekler: +Test, uygun PHP sürümüyle çalıştırılmazsa atlanır. Ek açıklamayı `@phpVersion [operatör] sürüm` olarak yazarız. Operatörü atlayabiliriz, varsayılan `>=` operatörüdür. Örnekler: ```php /** @@ -46,7 +46,7 @@ Test atlanır. Geçici test devre dışı bırakma için kullanışlıdır. @phpExtension .[filter] ----------------------- -Bahsedilen tüm PHP uzantıları yüklenmemişse test atlanır. Tek bir ek açıklamada birden fazla uzantı yazılabilir veya ek açıklamayı birden fazla kez kullanabiliriz. +Test, belirtilen tüm PHP uzantıları yüklenmezse atlanır. Bir ek açıklamada birden fazla uzantı belirtebilir veya ek açıklamayı birden çok kez kullanabiliriz. ```php /** @@ -58,9 +58,9 @@ Bahsedilen tüm PHP uzantıları yüklenmemişse test atlanır. Tek bir ek açı @dataProvider .[filter] ----------------------- -Bu ek açıklama, testi birden çok kez ancak farklı verilerle çalıştırmak istediğimizde uygundur. ( [TestCase |TestCase#dataProvider] için aynı adı taşıyan ek açıklama ile karıştırılmamalıdır). +Test dosyasını birden çok kez, ancak farklı girdi verileriyle çalıştırmak istiyorsak, bu ek açıklama kullanışlıdır. ([TestCase için aynı adlı ek açıklamayla karıştırmayın |TestCase#dataProvider].) -Ek açıklamayı `@dataProvider file.ini` olarak yazıyoruz. INI dosya yolu test dosyasına görelidir. Test, INI dosyasında bulunan bölüm sayısı kadar çalışır. INI dosyasının `databases.ini` olduğunu varsayalım: +`@dataProvider file.ini` olarak yazarız, dosya yolu test dosyasına göreceli olarak alınır. Test, INI dosyasındaki bölüm sayısı kadar çalıştırılacaktır. `databases.ini` INI dosyasını varsayalım: ```ini [mysql] @@ -77,7 +77,7 @@ password = ****** dsn = "sqlite::memory:" ``` -ve aynı dizindeki `database.phpt` dosyası: +ve aynı dizinde `database.phpt` testi: ```php /** @@ -87,11 +87,11 @@ ve aynı dizindeki `database.phpt` dosyası: $args = Tester\Environment::loadData(); ``` -Test üç kez çalışır ve `$args`, `mysql`, `postgresql` veya `sqlite` bölümlerinden değerler içerir. +Test üç kez çalıştırılacak ve `$args` değişkeni her zaman `mysql`, `postgresql` veya `sqlite` bölümündeki değerleri içerecektir. -`@dataProvider? file.ini` şeklinde bir soru işareti ile ek açıklamalar yazdığımızda bir varyasyon daha vardır. Bu durumda, INI dosyası mevcut değilse test atlanır. +Ek açıklamayı soru işaretiyle `@dataProvider? file.ini` olarak yazdığımız bir varyant daha vardır. Bu durumda, INI dosyası mevcut değilse test atlanır. -Ek açıklama olanaklarının hepsinden henüz bahsedilmedi. INI dosyasından sonra koşulları yazabiliriz. Test, yalnızca tüm koşullar eşleşirse verilen bölüm için çalışır. INI dosyasını genişletelim: +Ek açıklamanın olanakları burada bitmiyor. INI dosyasının adından sonra, testin belirli bir bölüm için hangi koşullar altında çalıştırılacağını belirtebiliriz. INI dosyasını genişletelim: ```ini [mysql] @@ -113,7 +113,7 @@ password = ****** dsn = "sqlite::memory:" ``` -ve koşul ile ek açıklama kullanacağız: +ve koşullu ek açıklamayı kullanalım: ```php /** @@ -121,9 +121,9 @@ ve koşul ile ek açıklama kullanacağız: */ ``` -Test `postgresql 9.1` bölümü için yalnızca bir kez çalışır. Diğer bölümler koşullarla eşleşmiyor. +Test yalnızca bir kez ve `postgresql 9.1` bölümü için çalıştırılacaktır. Diğer bölümler koşul filtresinden geçmez. -Benzer şekilde, INI yerine bir PHP betiğine yol aktarabiliriz. Dizi veya Traversable döndürmelidir. Dosya `databases.php`: +Benzer şekilde, INI dosyası yerine bir PHP betiğine başvurabiliriz. Bu betik bir dizi veya Traversable döndürmelidir. `databases.php` dosyası: ```php return [ @@ -142,29 +142,29 @@ return [ @multiple .[filter] ------------------- -Bunu `@multiple N` şeklinde yazıyoruz, burada `N` bir tam sayıdır. Test tam olarak N kez çalışır. +`@multiple N` olarak yazarız, burada `N` bir tamsayıdır. Test tam olarak N kez çalıştırılacaktır. -testCase .[filter] ------------------- -Annotation'ın parametresi yoktur. [TestCase |TestCase] sınıfları olarak bir test yazdığımızda kullanırız. Bu durumda, komut satırı test koşucusu, ayrı yöntemleri ayrı işlemlerde ve birden çok iş parçacığında paralel olarak çalıştıracaktır. Bu, tüm test sürecini önemli ölçüde hızlandırabilir. +@testCase .[filter] +------------------- +Bu ek açıklamanın parametresi yoktur. Testleri [TestCase |TestCase] sınıfları olarak yazarsak kullanırız. Bu durumda, komut satırı test çalıştırıcısı bireysel metotları ayrı işlemlerde ve paralel olarak birden fazla iş parçacığında çalıştıracaktır. Bu, tüm test sürecini önemli ölçüde hızlandırabilir. -ExitCode .[filter] ------------------- -Testte `N` is the exit code of the test. For example if `exit(10)` çağrıldığı yerde `@exitCode N` olarak yazıyoruz, ek açıklamayı `@exitCode 10` olarak yazıyoruz. Test farklı bir kod ile biterse başarısız kabul edilir. Ek açıklamayı dışarıda bırakırsak çıkış kodu 0 (sıfır) doğrulanır +@exitCode .[filter] +------------------- +`@exitCode N` olarak yazarız, burada `N` çalıştırılan testin dönüş kodudur. Örneğin, testte `exit(10)` çağrılırsa, ek açıklamayı `@exitCode 10` olarak yazarız ve test farklı bir kodla biterse, bu bir başarısızlık olarak kabul edilir. Ek açıklamayı belirtmezsek, 0 (sıfır) dönüş kodu doğrulanır. @httpCode .[filter] ------------------- -Ek açıklama sadece PHP ikilisi CGI ise değerlendirilir. Aksi takdirde yok sayılır. Bunu `@httpCode NNN` olarak yazıyoruz, burada `NNN` beklenen HTTP kodudur. Ek açıklamayı dışarıda bırakırsak HTTP kodu 200 doğrulanır. Eğer `NNN` adresini sıfır olarak değerlendirilen bir dize olarak yazarsak, örneğin `any`, HTTP kodu hiç kontrol edilmez. +Bu ek açıklama yalnızca PHP ikili dosyası CGI ise geçerlidir. Aksi takdirde yok sayılır. `@httpCode NNN` olarak yazarız, burada `NNN` beklenen HTTP kodudur. Ek açıklamayı belirtmezsek, 200 HTTP kodu doğrulanır. Eğer `NNN`'yi sıfıra değerlendirilen bir karakter dizisi olarak yazarsak, örneğin `any`, HTTP kodu doğrulanmaz. -outputMatch a @outputMatchFile .[filter] ----------------------------------------- -Ek açıklamaların davranışı `Assert::match()` ve `Assert::matchFile()` assertions ile tutarlıdır. Ancak model testin standart çıktısında bulunur. Uygun bir kullanım durumu, testin ölümcül bir hata ile sona erdiğini varsaydığımız ve çıktısını doğrulamamız gereken durumdur. +@outputMatch ve @outputMatchFile .[filter] +------------------------------------------ +Ek açıklamaların işlevi `Assert::match()` ve `Assert::matchFile()` doğrulama ifadeleriyle aynıdır. Ancak desen (pattern), testin standart çıktısına gönderdiği metinde aranır. Testin ölümcül bir hatayla sonlanacağını varsaydığımızda ve çıktısını doğrulamamız gerektiğinde kullanışlıdır. @phpIni .[filter] ----------------- -Test için INI yapılandırma değerlerini ayarlar. Örneğin `@phpIni precision=20` şeklinde yazıyoruz ve komut satırından `-d precision=20` parametresi ile değer aktarmışız gibi çalışıyor. +Test için yapılandırma INI değerlerini ayarlar. Örneğin `@phpIni precision=20` olarak yazarız ve bu, değeri komut satırından `-d precision=20` parametresiyle belirtmişiz gibi çalışır. diff --git a/tester/tr/testcase.texy b/tester/tr/testcase.texy index 6a9a705167..32d1e43b40 100644 --- a/tester/tr/testcase.texy +++ b/tester/tr/testcase.texy @@ -2,9 +2,9 @@ TestCase ******** .[perex] -Basit testlerde assertion'lar tek tek takip edilebilir. Ancak bazen assertion'ları test sınıfına dahil etmek ve bu şekilde yapılandırmak faydalı olabilir. +Basit testlerde, doğrulama ifadeleri birbiri ardına gelebilir. Ancak bazen doğrulama ifadelerini bir test sınıfına sarmak ve böylece yapılandırmak daha avantajlıdır. -Sınıf, `Tester\TestCase` adresinin soyundan gelmelidir ve biz bundan basitçe **testcase** olarak bahsederiz. +Sınıf `Tester\TestCase` sınıfından türetilmiş olmalı ve kısaca **testcase** olarak adlandırılır. Sınıf, `test` ile başlayan test metotlarını içermelidir. Bu metotlar test olarak çalıştırılacaktır: ```php use Tester\Assert; @@ -22,11 +22,11 @@ class RectangleTest extends Tester\TestCase } } -# Run testing methods +# Test metotlarını çalıştırma (new RectangleTest)->run(); ``` -Bir test durumunu `setUp()` ve `tearDown()` yöntemleriyle zenginleştirebiliriz. Her test yönteminden önce/sonra çağrılırlar: +Bu şekilde yazılmış bir test, `setUp()` ve `tearDown()` metotlarıyla daha da zenginleştirilebilir. Bunlar her test metodundan önce ve sonra sırasıyla çağrılır: ```php use Tester\Assert; @@ -35,12 +35,12 @@ class NextTest extends Tester\TestCase { public function setUp() { - # Preparation + # Hazırlık } public function tearDown() { - # Clean-up + # Temizlik } public function testOne() @@ -54,13 +54,13 @@ class NextTest extends Tester\TestCase } } -# Run testing methods +# Test metotlarını çalıştırma (new NextTest)->run(); /* -Method Calls Order +Metot Çağrı Sırası ------------------ setUp() testOne() @@ -72,9 +72,9 @@ tearDown() */ ``` -Bir `setUp()` veya `tearDown()` aşamasında hata oluşursa, test başarısız olur. Test yönteminde hata oluşursa, `tearDown()` yöntemi yine de çağrılır, ancak içindeki hatalar bastırılır. +`setUp()` veya `tearDown()` aşamasında bir hata oluşursa, test genel olarak başarısız olur. Test metodunda bir hata oluşursa, `tearDown()` metodu yine de çalıştırılır, ancak içindeki hatalar bastırılır. -Testin başına [@testCase |test-annotations#@testCase] ek açıklamasını yazmanızı öneririz, ardından komut satırı test koşucusu ayrı test senaryosu yöntemlerini ayrı süreçlerde ve birden çok iş parçacığında paralel olarak çalıştıracaktır. Bu, tüm test sürecini önemli ölçüde hızlandırabilir. +Testin başına [@testCase |test-annotations#testCase] ek açıklamasını yazmanızı öneririz. Bu durumda komut satırı test çalıştırıcısı, testcase'in bireysel metotlarını ayrı işlemlerde ve paralel olarak birden fazla iş parçacığında çalıştıracaktır. Bu, tüm test sürecini önemli ölçüde hızlandırabilir. /--php width = $width; $this->height = $height; @@ -53,7 +52,7 @@ class Rectangle } ``` -Ve bunun için bir test oluşturacağız. Test dosyasının adı `*Test.php` veya `*.phpt` maskesiyle eşleşmelidir, biz `RectangleTest.php` varyantını seçeceğiz: +Ve onun için bir test oluşturacağız. Test dosyasının adı `*Test.php` veya `*.phpt` maskesine uymalıdır, örneğin `RectangleTest.php` varyantını seçeceğiz: ```php .{file:tests/RectangleTest.php} @@ -64,29 +63,29 @@ require __DIR__ . '/bootstrap.php'; // genel dikdörtgen $rect = new Rectangle(10, 20); -Assert::same(200.0, $rect->getArea()); # beklenen sonuçları doğrulayacağız +Assert::same(200.0, $rect->getArea()); # beklenen sonuçları doğrula Assert::false($rect->isSquare()); ``` -Gördüğünüz gibi, `Assert::same()` gibi [onaylama yöntemleri |Assertions], gerçek bir değerin beklenen bir değerle eşleştiğini onaylamak için kullanılır. +Gördüğünüz gibi, `Assert::same()` gibi sözde [doğrulama ifadesi metotları |assertions], gerçek değerin beklenen değere karşılık geldiğini onaylamak için kullanılır. -Son adım `bootstrap.php` dosyasını oluşturmaktır. Tüm testler için ortak bir kod içerir. Örneğin sınıfların otomatik yüklenmesi, ortam yapılandırması, geçici dizin oluşturma, yardımcılar ve benzerleri. Her test bootstrap'ı yükler ve sadece test etmeye dikkat eder. Bootstrap aşağıdaki gibi görünebilir: +Geriye kalan son adım `bootstrap.php` dosyasıdır. Bu, tüm testler için ortak olan kodu içerir, örneğin sınıfların otomatik yüklenmesi, ortam yapılandırması, geçici bir dizin oluşturma, yardımcı fonksiyonlar vb. Tüm testler bootstrap'ı yükler ve ardından yalnızca test etmeye odaklanır. Bootstrap şöyle görünebilir: ```php .{file:tests/bootstrap.php} OK \-- -Testte ifadeyi yanlış olarak değiştirirsek `Assert::same(123, $rect->getArea());`, bu gerçekleşecektir: +Eğer testteki iddiayı yanlış `Assert::same(123, $rect->getArea());` olarak değiştirirsek, şu olur: /--pre .[terminal] $ php RectangleTest.php -Failed: 200.0 should be 123 +Başarısız: 200.0 olmalı 123 -in RectangleTest.php(5) Assert::same(123, $rect->getArea()); +içinde RectangleTest.php(5) Assert::same(123, $rect->getArea()); -FAILURE +BAŞARISIZLIK \-- -Test yazarken, tüm uç durumları yakalamak iyidir. Örneğin, giriş sıfırsa, negatif bir sayı ise, diğer durumlarda boş bir dize, null vb. Aslında, sizi bu tür durumlarda kodun nasıl davranması gerektiğini düşünmeye ve karar vermeye zorlar. Testler daha sonra davranışı düzeltir. +Test yazarken tüm uç durumları yakalamak iyidir. Örneğin, girdi sıfır, negatif bir sayı olduğunda, diğer durumlarda örneğin boş bir karakter dizisi, null vb. olduğunda. Aslında sizi düşünmeye ve bu tür durumlarda kodun nasıl davranması gerektiğine karar vermeye zorlar. Testler daha sonra davranışı sabitler. -Bizim durumumuzda, negatif bir değer [Assert::exception() |Assertions#Assert::exception] ile doğruladığımız bir istisna oluşturmalıdır: +Bizim durumumuzda, negatif bir değer bir istisna atmalıdır, bunu [Assert::exception() |Assertions#Assert::exception] kullanarak doğrularız: ```php .{file:tests/RectangleTest.php} -// genişlik negatif sayı olmamalıdır +// genişlik negatif olmamalıdır Assert::exception( fn() => new Rectangle(-1, 20), InvalidArgumentException::class, @@ -120,22 +119,22 @@ Assert::exception( ); ``` -Boy için de benzer bir test ekliyoruz. Son olarak, her iki boyut da aynıysa `isSquare()` 'un `true` döndürdüğünü test ediyoruz. Bu tür testleri alıştırma olarak yazmaya çalışın. +Ve yükseklik için benzer bir test ekleriz. Son olarak, her iki boyut da aynıysa `isSquare()` metodunun `true` döndürdüğünü test ederiz. Alıştırma olarak bu tür testleri yazmayı deneyin. -İyi Düzenlenmiş Testler .[#toc-well-arranged-tests] -=================================================== +Daha Okunabilir Testler +======================= -Test dosyasının boyutu artabilir ve hızla dağınık hale gelebilir. Bu nedenle, test edilen alanları ayrı işlevler halinde gruplamak pratiktir. +Test dosyasının boyutu büyüyebilir ve hızla okunaksız hale gelebilir. Bu nedenle, bireysel test edilen alanları ayrı fonksiyonlarda gruplamak pratiktir. -İlk olarak, `test()` global fonksiyonunu kullanarak daha basit ama zarif bir varyant göstereceğiz. Kodunuzda aynı isimde bir fonksiyon varsa çakışmayı önlemek için test cihazı bunu otomatik olarak oluşturmaz. Yalnızca `bootstrap.php` dosyasında çağırdığınız `setupFunctions()` yöntemi tarafından oluşturulur: +Önce daha basit, ancak zarif bir varyantı göstereceğiz: global `test()` fonksiyonunu kullanarak. Tester bunu otomatik olarak oluşturmaz, böylece kodunuzda aynı ada sahip bir fonksiyonunuz varsa çakışma olmaz. Onu, `bootstrap.php` dosyasında çağıracağınız `setupFunctions()` metodu oluşturur: ```php .{file:tests/bootstrap.php} Tester\Environment::setup(); Tester\Environment::setupFunctions(); ``` -Bu fonksiyonu kullanarak, test dosyasını güzel bir şekilde adlandırılmış birimlere bölebiliriz. Çalıştırıldığında, etiketler birbiri ardına görüntülenecektir. +Bu fonksiyonu kullanarak, test dosyasını adlandırılmış birimlere güzelce bölebiliriz. Çalıştırıldığında, açıklamalar sırayla yazdırılacaktır. ```php .{file:tests/RectangleTest.php} getArea()); Assert::false($rect->isSquare()); }); -test('general square', function () { +test('genel kare', function () { $rect = new Rectangle(5, 5); Assert::same(25.0, $rect->getArea()); Assert::true($rect->isSquare()); }); -test('dimensions must not be negative', function () { +test('boyutlar negatif olmamalıdır', function () { Assert::exception( fn() => new Rectangle(-1, 20), InvalidArgumentException::class, @@ -168,15 +167,15 @@ test('dimensions must not be negative', function () { }); ``` -Kodu her testten önce veya sonra çalıştırmanız gerekiyorsa, `setUp()` veya `tearDown()` adresine iletin: +Her testten önce veya sonra kod çalıştırmanız gerekiyorsa, onu `setUp()` veya `tearDown()` fonksiyonuna aktarın: ```php setUp(function () { - // her test() öncesinde çalıştırılacak başlatma kodu + // her test() öncesinde çalışacak başlatma kodu }); ``` -İkinci varyant nesnedir. Tek tek birimlerin adları test- ile başlayan yöntemlerle temsil edildiği bir sınıf olan TestCase'i oluşturacağız. +İkinci varyant nesne yönelimlidir. Bir TestCase oluştururuz; bu, bireysel birimlerin `test` ile başlayan adlara sahip metotları temsil ettiği bir sınıftır. ```php .{file:tests/RectangleTest.php} class RectangleTest extends Tester\TestCase @@ -208,25 +207,25 @@ class RectangleTest extends Tester\TestCase } } -// Test yöntemlerini çalıştırın +// Test metotlarını çalıştırma (new RectangleTest)->run(); ``` -Bu sefer istisnaları test etmek için `@throw` ek açıklamasını kullandık. Daha fazla bilgi için [TestCase |TestCase] bölümüne bakın. +İstisnaları test etmek için bu sefer `@throws` ek açıklamasını kullandık. Daha fazlasını [TestCase |TestCase] bölümünde öğreneceksiniz. -Yardımcı Fonksiyonlar .[#toc-helpers-functions] -=============================================== +Yardımcı Fonksiyonlar +===================== -Nette Tester, testi sizin için kolaylaştırabilecek çeşitli sınıflar ve işlevler içerir; örneğin, bir HTML belgesinin içeriğini test etmek için yardımcılar, dosyalarla çalışma işlevlerini test etmek vb. +Nette Tester, örneğin HTML belgesinin içeriğini test etme, dosyalarla çalışan fonksiyonları test etme vb. konularda size yardımcı olabilecek birkaç sınıf ve fonksiyon içerir. -Bunların açıklamasını [Yardımcılar |Helpers] sayfasında bulabilirsiniz. +Açıklamalarını [Yardımcı Sınıflar |helpers] sayfasında bulabilirsiniz. -Ek Açıklama ve Atlama Testleri .[#toc-annotation-and-skipping-tests] -==================================================================== +Ek Açıklamalar ve Testleri Atlama +================================= -Test yürütmesi, dosyanın başındaki phpDoc yorumundaki ek açıklamalardan etkilenebilir. Örneğin, şöyle görünebilir: +Testlerin çalıştırılması, dosyanın başındaki phpDoc yorumu şeklindeki ek açıklamalarla etkilenebilir. Örneğin şöyle görünebilir: ```php .{file:tests/RectangleTest.php} /** @@ -235,23 +234,23 @@ Test yürütmesi, dosyanın başındaki phpDoc yorumundaki ek açıklamalardan e */ ``` -Ek açıklamalar, testin yalnızca PHP sürüm 7.2 veya üstü ile ve PHP uzantıları pdo ve pdo_pgsql mevcutsa çalıştırılması gerektiğini söyler. Bu ek açıklamalar, koşullar karşılanmazsa testi atlayan ve `s` harfi ile işaretleyen [komut satırı test çalıştırıcısı |running-tests] tarafından kontrol edilir - atlandı. Ancak, test manuel olarak çalıştırıldığında hiçbir etkisi yoktur. +Belirtilen ek açıklamalar, testin yalnızca PHP sürüm 7.2 veya üstüyle ve pdo ve pdo_pgsql PHP uzantıları mevcutsa çalıştırılması gerektiğini söyler. Bu ek açıklamalar [komut satırı test çalıştırıcısı |running-tests] tarafından yönetilir; bu çalıştırıcı, koşullar karşılanmazsa testi atlar ve çıktıda `s` (skipped) harfiyle işaretler. Ancak, testin manuel olarak çalıştırılmasında bu ek açıklamaların hiçbir etkisi yoktur. -Ek açıklamaların açıklaması için [Test Ek |Test Annotations] Açıklamaları bölümüne bakın. +Ek açıklamaların açıklamalarını [Test Ek Açıklamaları |test-annotations] sayfasında bulabilirsiniz. -Test ayrıca `Environment::skip()` ile kendi koşuluna göre de atlanabilir. Örneğin, Windows üzerinde bu testi atlayacağız: +Test, `Environment::skip()` kullanarak kendi koşulunun karşılanmasına bağlı olarak da atlanabilir. Örneğin, bu, Windows'taki testleri atlar: ```php if (defined('PHP_WINDOWS_VERSION_BUILD')) { - Tester\Environment::skip('Requires UNIX.'); + Tester\Environment::skip('UNIX gerektirir.'); } ``` -Dizin Yapısı .[#toc-directory-structure] -======================================== +Dizin Yapısı +============ -Sadece biraz daha büyük kütüphaneler veya projeler için, test dizinini test edilen sınıfın ad alanına göre alt dizinlere bölmenizi öneririz: +Biraz daha büyük kütüphanelerde veya projelerde, test dizinini test edilen sınıfın ad alanına göre alt dizinlere ayırmanızı öneririz: ``` └── tests/ @@ -269,22 +268,22 @@ Sadece biraz daha büyük kütüphaneler veya projeler için, test dizinini test └── ... ``` -Testleri tek bir isim alanından yani alt dizinden çalıştırabileceksiniz: +Böylece testleri tek bir ad alanından, yani alt dizinden çalıştırabileceksiniz: /--pre .[terminal] tester tests/NamespaceOne \-- -Uç Vakalar .[#toc-edge-cases] -============================= +Özel Durumlar +============= -Herhangi bir assertion yöntemini çağırmayan bir test şüphelidir ve hatalı olarak değerlendirilecektir: +Tek bir doğrulama ifadesi metodu bile çağırmayan bir test şüphelidir ve hatalı olarak değerlendirilir: /--pre .[terminal] -Error: This test forgets to execute an assertion. +Hata: Bu test bir doğrulama ifadesi yürütmeyi unutuyor. \-- -İddiaları çağırmadan yapılan test gerçekten geçerli kabul edilecekse, örneğin `Assert::true(true)` adresini çağırın. +Eğer testin gerçekten doğrulama ifadesi çağrısı olmadan geçerli kabul edilmesi gerekiyorsa, örneğin `Assert::true(true)` çağırın. -Testi bir hata mesajıyla sonlandırmak için `exit()` ve `die()` adreslerini kullanmak da tehlikeli olabilir. Örneğin, `exit('Error in connection')` testi başarıya işaret eden 0 çıkış koduyla sonlandırır. `Assert::fail('Error in connection')` kullanın. +Ayrıca, testi bir hata mesajıyla sonlandırmak için `exit()` ve `die()` kullanmak da yanıltıcı olabilir. Örneğin, `exit('Bağlantıda hata')` testi 0 dönüş koduyla sonlandırır, bu da başarıyı gösterir. `Assert::fail('Bağlantıda hata')` kullanın. diff --git a/tester/uk/@home.texy b/tester/uk/@home.texy index ee66fc5973..15d273651e 100644 --- a/tester/uk/@home.texy +++ b/tester/uk/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Nette Tester - приємне модульне тестування на PHP}} -{{description: Nette Tester - простий і водночас дуже зручний інструмент для тестування PHP коду.}} +{{maintitle: Nette Tester – зручне тестування в PHP}} +{{description: Nette Tester — це простий, але дуже зручний інструмент для тестування PHP-коду.}} diff --git a/tester/uk/@left-menu.texy b/tester/uk/@left-menu.texy index 2dec7ab70e..c68347dd62 100644 --- a/tester/uk/@left-menu.texy +++ b/tester/uk/@left-menu.texy @@ -1,8 +1,8 @@ -- [Початок роботи |guide] -- [Написання тестів |Writing Tests] -- [Запуск тестів |Running Tests] +- [Починаємо з Nette Tester |guide] +- [Написання тестів |writing-tests] +- [Запуск тестів |running-tests] -- [Затвердження |Assertions] -- [Анотації тестів |Test Annotations] -- [Тестовий приклад |TestCase] -- [Помічники |Helpers] +- [Асерції |assertions] +- [Анотації тестів |test-annotations] +- [TestCase |TestCase] +- [Допоміжні класи |helpers] diff --git a/tester/uk/@menu.texy b/tester/uk/@menu.texy index 7259bad64e..2d5e8002e3 100644 --- a/tester/uk/@menu.texy +++ b/tester/uk/@menu.texy @@ -1,3 +1,3 @@ -- [Головна сторінка|@home] -- [Документація |Guide] +- [Вступ |@home] +- [Документація |guide] - "GitHub .[link-external]":https://github.com/nette/tester diff --git a/tester/uk/@meta.texy b/tester/uk/@meta.texy new file mode 100644 index 0000000000..3c05dc3e34 --- /dev/null +++ b/tester/uk/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документація Tester}} diff --git a/tester/uk/assertions.texy b/tester/uk/assertions.texy index 8d29bcdd6d..5d73e416e1 100644 --- a/tester/uk/assertions.texy +++ b/tester/uk/assertions.texy @@ -1,35 +1,35 @@ -Затвердження -************ +Assertion +********* .[perex] -Твердження використовуються для підтвердження того, що фактичне значення відповідає очікуваному значенню. Вони є методами `Tester\Assert`. +Assertion використовуються для підтвердження того, що фактичне значення відповідає очікуваному значенню. Це методи класу `Tester\Assert`. -Виберіть найбільш точні твердження. Краще `Assert::same($a, $b)`, ніж `Assert::true($a === $b)`, тому що він виводить осмислене повідомлення про помилку при невдачі. У другому випадку ми отримуємо тільки `false should be true`, і воно нічого не говорить про вміст змінних $a і $b. +Вибирайте найбільш підходящі assertion. Краще `Assert::same($a, $b)`, ніж `Assert::true($a === $b)`, оскільки при невдачі вона відобразить змістовне повідомлення про помилку. У другому випадку лише `false should be true`, що нічого не говорить нам про вміст змінних `$a` та `$b`. -Більшість тверджень можуть також мати необов'язковий `$description`, який з'являється в повідомленні про помилку в разі невдачі. +Більшість assertion також можуть мати необов'язковий опис у параметрі `$description`, який відобразиться в повідомленні про помилку, якщо очікування не виправдається. -Приклади припускають, що визначено наступний псевдонім класу: +Приклади передбачають створений псевдонім: ```php use Tester\Assert; ``` -Assert::same($expected, $actual, string $description=null) .[method] --------------------------------------------------------------------- -`$expected` має бути те саме, що й `$actual`. Це те саме, що й оператор PHP `===`. +Assert::same($expected, $actual, ?string $description=null) .[method] +--------------------------------------------------------------------- +`$expected` має бути тотожним `$actual`. Те саме, що й PHP оператор `===`. -Assert::notSame($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------ -Протилежний оператору `Assert::same()`, тому збігається з оператором PHP `!==`. +Assert::notSame($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------ +Протилежність `Assert::same()`, тобто те саме, що й PHP оператор `!==`. -Assert::equal($expected, $actual, string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] ------------------------------------------------------------------------------------------------------------------------- -`$expected` має бути таким самим, як `$actual`. На відміну від `Assert::same()`, ідентичність об'єктів, порядок пар ключ => значення в масивах і незначно відмінні десяткові числа ігноруються, що можна змінити, задавши `$matchIdentity` і `$matchOrder`. +Assert::equal($expected, $actual, ?string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method] +------------------------------------------------------------------------------------------------------------------------- +`$expected` має бути однаковим з `$actual`. На відміну від `Assert::same()`, ігнорується ідентичність об'єктів, порядок пар ключ => значення в масивах та незначно відмінні десяткові числа, що можна змінити налаштуванням `$matchIdentity` та `$matchOrder`. -Наступні випадки ідентичні з точки зору `equal()`, але не для `same()`: +Наступні випадки є однаковими з точки зору `equal()`, але не `same()`: ```php Assert::equal(0.3, 0.1 + 0.2); @@ -40,81 +40,81 @@ Assert::equal( ); ``` -Однак, будьте уважні, масив `[1, 2]` и `[2, 1]` не рівні, оскільки різниться тільки порядок значень, а не пари ключ => значення. Масив `[1, 2]` також може бути записаний як `[0 => 1, 1 => 2]` і тому `[1 => 2, 0 => 1]` вважатиметься рівним. +Однак увага, масиви `[1, 2]` та `[2, 1]` не є однаковими, оскільки відрізняється лише порядок значень, а не пар ключ => значення. Масив `[1, 2]` можна записати також як `[0 => 1, 1 => 2]`, і тому однаковим вважатиметься `[1 => 2, 0 => 1]`. -Ви також можете використовувати так звані [очікування |#Expectations] в `$expected`. +Далі в `$expected` можна використовувати так звані [#очікування]. -Assert::notEqual($expected, $actual, string $description=null) .[method] ------------------------------------------------------------------------- +Assert::notEqual($expected, $actual, ?string $description=null) .[method] +------------------------------------------------------------------------- Протилежність `Assert::equal()`. -Assert::contains($needle, string|array $actual, string $description=null) .[method] ------------------------------------------------------------------------------------ -Якщо `$actual` - рядок, то він повинен містити підрядок `$needle`. Якщо це масив, то він має містити елемент `$needle` (він порівнюється строго). +Assert::contains($needle, string|array $actual, ?string $description=null) .[method] +------------------------------------------------------------------------------------ +Якщо `$actual` є рядком, він повинен містити підрядок `$needle`. Якщо це масив, він повинен містити елемент `$needle` (порівнюється строго). -Assert::notContains($needle, string|array $actual, string $description=null) .[method] --------------------------------------------------------------------------------------- +Assert::notContains($needle, string|array $actual, ?string $description=null) .[method] +--------------------------------------------------------------------------------------- Протилежність `Assert::contains()`. -Assert::hasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} -------------------------------------------------------------------------------------------------------- -`$actual` має бути масивом і містити ключ `$needle`. +Assert::hasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +-------------------------------------------------------------------------------------------------------- +`$actual` має бути масивом і повинен містити ключ `$needle`. -Assert::notHasKey(string|int $needle, array $actual, string $description=null) .[method]{data-version:2.4} ----------------------------------------------------------------------------------------------------------- +Assert::notHasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4} +----------------------------------------------------------------------------------------------------------- `$actual` має бути масивом і не повинен містити ключ `$needle`. -Assert::true($value, string $description=null) .[method] --------------------------------------------------------- -`$value` має бути `true`, тому `$value === true`. +Assert::true($value, ?string $description=null) .[method] +--------------------------------------------------------- +`$value` має бути `true`, тобто `$value === true`. -Assert::truthy($value, string $description=null) .[method] ----------------------------------------------------------- -`$value` має бути істинним, тому воно задовольняє умову `if ($value) ...`. +Assert::truthy($value, ?string $description=null) .[method] +----------------------------------------------------------- +`$value` має бути істинним, тобто виконає умову `if ($value) ...`. -Assert::false($value, string $description=null) .[method] ---------------------------------------------------------- -`$value` має бути `false`, тому `$value === false`. +Assert::false($value, ?string $description=null) .[method] +---------------------------------------------------------- +`$value` має бути `false`, тобто `$value === false`. -Assert::falsey($value, string $description=null) .[method] ----------------------------------------------------------- -`$value` має бути хибним, тому він задовольняє умову `if (!$value) ...`. +Assert::falsey($value, ?string $description=null) .[method] +----------------------------------------------------------- +`$value` має бути хибним, тобто виконає умову `if (!$value) ...`. -Assert::null($value, string $description=null) .[method] --------------------------------------------------------- -`$value` має бути `null`, тому `$value === null`. +Assert::null($value, ?string $description=null) .[method] +--------------------------------------------------------- +`$value` має бути `null`, тобто `$value === null`. -Assert::notNull($value, string $description=null) .[method] ------------------------------------------------------------ -`$value` не повинно бути `null`, тому `$value !== null`. +Assert::notNull($value, ?string $description=null) .[method] +------------------------------------------------------------ +`$value` не має бути `null`, тобто `$value !== null`. -Assert::nan($value, string $description=null) .[method] -------------------------------------------------------- -`$value` має бути Not a Number. Використовуйте тільки `Assert::nan()` для тестування NAN. Значення NAN дуже специфічне, і твердження `Assert::same()` або `Assert::equal()` можуть повести себе непередбачувано. +Assert::nan($value, ?string $description=null) .[method] +-------------------------------------------------------- +`$value` має бути Not a Number. Для тестування значення NAN використовуйте виключно `Assert::nan()`. Значення NAN є дуже специфічним, і assertion `Assert::same()` або `Assert::equal()` можуть працювати неочікувано. -Assert::count($count, Countable|array $value, string $description=null) .[method] ---------------------------------------------------------------------------------- -Кількість елементів у `$value` має дорівнювати `$count`. Тобто те саме, що й `count($value) === $count`. +Assert::count($count, Countable|array $value, ?string $description=null) .[method] +---------------------------------------------------------------------------------- +Кількість елементів у `$value` має бути `$count`. Тобто те саме, що й `count($value) === $count`. -Assert::type(string|object $type, $value, string $description=null) .[method] ------------------------------------------------------------------------------ -`$value` має бути заданого типу. Як `$type` ми можемо використовувати рядок: +Assert::type(string|object $type, $value, ?string $description=null) .[method] +------------------------------------------------------------------------------ +`$value` має бути даного типу. Як `$type` можемо використати рядок: - `array` -- `list` - масив, індексований у порядку зростання числових ключів від нуля. +- `list` - масив, індексований за зростаючим рядом числових ключів від нуля - `bool` - `callable` - `float` @@ -124,28 +124,28 @@ Assert::type(string|object $type, $value, string $description=null) .[method] - `resource` - `scalar` - `string` -- ім'я класу або об'єкта безпосередньо, то необхідно передати `$value instanceof $type` +- назва класу або безпосередньо об'єкт, тоді `$value` має бути `instanceof $type` -Assert::exception(callable $callable, string $class, string $message=null, $code=null) .[method] ------------------------------------------------------------------------------------------------- -При виклику `$callable` має бути викинуто виключення екземпляра `$class`. Якщо ми передаємо `$message`, то повідомлення виключення має [збігатися |#Assert-match]. А якщо ми передаємо `$code`, то код виключення має бути таким самим. +Assert::exception(callable $callable, string $class, ?string $message=null, $code=null) .[method] +------------------------------------------------------------------------------------------------- +При виклику `$callable` має бути викинуто виняток класу `$class`. Якщо вкажемо `$message`, повідомлення винятку також має [відповідати патерну |#Assert::match], а якщо вкажемо `$code`, коди також повинні строго збігатися. -Наприклад, цей тест не пройдено, тому що повідомлення виключення не збігається: +Наступний тест зазнає невдачі, оскільки повідомлення винятку не відповідає: ```php Assert::exception( - fn() => throw new App\InvalidValueException('Нулевое значение'), + fn() => throw new App\InvalidValueException('Zero value'), App\InvalidValueException::class, - 'Значение слишком мало', + 'Value is to low', ); ``` -Сайт `Assert::exception()` повертає кинуте виключення, тому ви можете перевірити вкладений виняток. +`Assert::exception()` повертає викинутий виняток, тому можна протестувати й вкладений виняток. ```php $e = Assert::exception( - fn() => throw new MyException('Что-то не так', 0, new RuntimeException), + fn() => throw new MyException('Something is wrong', 0, new RuntimeException), MyException::class, 'Something is wrong', ); @@ -154,9 +154,9 @@ Assert::type(RuntimeException::class, $e->getPrevious()); ``` -Assert::error(string $callable, int|string|array $type, string $message=null) .[method] ---------------------------------------------------------------------------------------- -Перевіряє, що виклик `$callable` генерує очікувані помилки (тобто попередження, повідомлення тощо). Як `$type` ми вказуємо одну з констант `E_...`, наприклад `E_WARNING`. І якщо передаємо `$message`, то повідомлення про помилку також має [відповідати |#Assert-match] шаблону. Наприклад: +Assert::error(string $callable, int|string|array $type, ?string $message=null) .[method] +---------------------------------------------------------------------------------------- +Перевіряє, чи функція `$callable` згенерувала очікувані помилки (тобто попередження, повідомлення тощо). Як `$type` вкажемо одну з констант `E_...`, тобто, наприклад, `E_WARNING`. А якщо вкажемо `$message`, повідомлення про помилку також має [відповідати патерну |#Assert::match]. Наприклад: ```php Assert::error( @@ -166,7 +166,7 @@ Assert::error( ); ``` -Якщо зворотний виклик генерує більше помилок, ми повинні очікувати їх усі в точному порядку. У цьому випадку ми передаємо масив у `$type`: +Якщо callback генерує більше помилок, ми повинні очікувати їх усі в точному порядку. У такому випадку передамо в `$type` масив: ```php Assert::error(function () { @@ -179,108 +179,108 @@ Assert::error(function () { ``` .[note] -Якщо `$type` - ім'я класу, то це твердження поводиться так само, як і `Assert::exception()`. +Якщо як `$type` вказати назву класу, поводиться так само, як `Assert::exception()`. Assert::noError(callable $callable) .[method] --------------------------------------------- -Перевіряє, що функція `$callable` не викидає жодних попереджень/зауважень/помилок або виключень PHP. Це корисно для перевірки частини коду, де немає інших тверджень. +Перевіряє, чи функція `$callable` не згенерувала жодного попередження, помилки або винятку. Корисно для тестування фрагментів коду, де немає жодного іншого assertion. -Assert::match(string $pattern, $actual, string $description=null) .[method] ---------------------------------------------------------------------------- -`$actual` повинен відповідати `$pattern`. Ми можемо використовувати два варіанти шаблонів: регулярні вирази або знаки підстановки. +Assert::match(string $pattern, $actual, ?string $description=null) .[method] +---------------------------------------------------------------------------- +`$actual` має відповідати патерну `$pattern`. Ми можемо використовувати два варіанти патернів: регулярні вирази або placeholder'и/wildcard'и. -Якщо ми передаємо регулярний вираз як `$pattern`, ми повинні використовувати `~` or `#` для його поділу. Інші роздільники не підтримуються. Наприклад, тест, де `$var` повинен містити тільки шістнадцяткові цифри: +Якщо як `$pattern` передамо регулярний вираз, для його розділення ми повинні використовувати `~` або `#`, інші роздільники не підтримуються. Наприклад, тест, коли `$var` має містити лише шістнадцяткові цифри: ```php Assert::match('#^[0-9a-f]$#i', $var); ``` -Інший варіант схожий на порівняння рядків, але ми можемо використовувати деякі дикі символи в `$pattern`: - -- `%a%` один або більше будь-яких символів, крім символів кінця рядка -- `%a?%` нуль або більше з чого завгодно, крім символів кінця рядка -- `%A%` один або більше з усього, включаючи символи кінця рядка -- `%A?%` нуль або більше будь-яких символів, включаючи символи кінця рядка -- `%s%` один або більше символів пробілу, за винятком символів кінця рядка -- `%s?%` нуль або більше символів пробілу, за винятком символів кінця рядка -- `%S%` один або більше символів, за винятком пробілу -- `%S?%` нуль або більше символів, за винятком пробілу -- `%c%` один символ будь-якого виду (крім кінця рядка) -- `%d%` одна або кілька цифр +Другий варіант схожий на звичайне порівняння рядків, але в `$pattern` ми можемо використовувати різні placeholder'и/wildcard'и: + +- `%a%` один або більше символів, крім символів кінця рядка +- `%a?%` нуль або більше символів, крім символів кінця рядка +- `%A%` один або більше символів, включно з символами кінця рядка +- `%A?%` нуль або більше символів, включно з символами кінця рядка +- `%s%` один або більше пробілів, крім символів кінця рядка +- `%s?%` нуль або більше пробілів, крім символів кінця рядка +- `%S%` один або більше символів, крім пробілів +- `%S?%` нуль або більше символів, крім пробілів +- `%c%` будь-який один символ, крім символу кінця рядка +- `%d%` одна або більше цифр - `%d?%` нуль або більше цифр - `%i%` знакове цілочисельне значення -- `%f%` число з плаваючою комою -- `%h%` одна або кілька HEX-цифр -- `%w%` один або кілька буквено-цифрових символів -- `%%` один символ % +- `%f%` число з десятковою комою +- `%h%` одна або більше шістнадцяткових цифр +- `%w%` один або більше буквено-цифрових символів +- `%%` символ % Приклади: ```php -# Again, hexadecimal number test +# Знову тест на шістнадцяткове число Assert::match('%h%', $var); -# Generalized path to file and line number +# Узагальнення шляху до файлу та номера рядка Assert::match('Error in file %a% on line %i%', $errorMessage); ``` -Assert::matchFile(string $file, $actual, string $description=null) .[method] ----------------------------------------------------------------------------- -Твердження ідентичне [Assert::match() |#Assert-match], але шаблон завантажується з `$file`. Це корисно для тестування дуже довгих рядків. Тестовий файл стає читабельним. +Assert::matchFile(string $file, $actual, ?string $description=null) .[method] +----------------------------------------------------------------------------- +Assertion тотожна [#Assert::match()], але патерн завантажується з файлу `$file`. Це корисно для тестування дуже довгих рядків. Файл з тестом залишиться зрозумілим. Assert::fail(string $message, $actual=null, $expected=null) .[method] --------------------------------------------------------------------- -Це твердження завжди зазнає невдачі. Це просто зручно. За бажанням ми можемо передавати очікувані та фактичні значення. +Ця assertion завжди зазнає невдачі. Іноді це просто корисно. За бажанням можемо вказати й очікуване та фактичне значення. -Очікування .[#toc-expectations] -------------------------------- -Якщо ми хочемо порівняти більш складні структури з непостійними елементами, наведених вище тверджень може бути недостатньо. Наприклад, ми тестуємо метод, який створює нового користувача і повертає його атрибути у вигляді масиву. Ми не знаємо хеш-значення пароля, але знаємо, що він має бути шістнадцятковим рядком. А про наступний елемент ми знаємо тільки те, що це має бути об'єкт `DateTime`. +Очікування +---------- +Коли ми хочемо порівняти складніші структури з неконстантними елементами, вищезазначених assertion може бути недостатньо. Наприклад, тестуємо метод, який створює нового користувача і повертає його атрибути як масив. Значення хешу пароля ми не знаємо, але знаємо, що це має бути шістнадцятковий рядок. А про інший елемент знаємо лише, що це має бути об'єкт `DateTime`. -У цих випадках ми можемо використовувати `Tester\Expect` всередині параметра `$expected` методів `Assert::equal()` і `Assert::notEqual()`, за допомогою яких можна легко описати структуру. +У цих ситуаціях ми можемо використовувати `Tester\Expect` всередині параметра `$expected` методів `Assert::equal()` та `Assert::notEqual()`, за допомогою яких можна легко описати структуру. ```php use Tester\Expect; Assert::equal([ - 'id' => Expect::type('int'), # we expect an integer + 'id' => Expect::type('int'), # очікуємо ціле число 'username' => 'milo', - 'password' => Expect::match('%h%'), # we expect a string matching pattern - 'created_at' => Expect::type(DateTime::class), # we expect an instance of the class + 'password' => Expect::match('%h%'), # очікуємо рядок, що відповідає патерну + 'created_at' => Expect::type(DateTime::class), # очікуємо екземпляр класу ], User::create(123, 'milo', 'RandomPaSsWoRd')); ``` -За допомогою `Expect` ми можемо робити майже ті самі твердження, що і за допомогою `Assert`. Тому в нас є такі методи, як `Expect::same()`, `Expect::match()`, `Expect::count()` тощо. Крім того, ми можемо з'єднати їх у ланцюжок таким чином: +З `Expect` ми можемо виконувати майже ті самі assertion, що й з `Assert`. Тобто нам доступні методи `Expect::same()`, `Expect::match()`, `Expect::count()` тощо. Крім того, їх можна об'єднувати в ланцюжок: ```php -Expect::type(MyIterator::class)->andCount(5); # we expect MyIterator and items count is 5 +Expect::type(MyIterator::class)->andCount(5); # очікуємо MyIterator та кількість елементів 5 ``` -Або ми можемо написати власні обробники тверджень. +Або можемо писати власні обробники assertion. ```php Expect::that(function ($value) { - # return false if expectation fails + # повернемо false, якщо очікування не виправдається }); ``` -Розслідування невдалих тверджень .[#toc-failed-assertions-investigation] ------------------------------------------------------------------------- -Tester показує, де знаходиться помилка, коли твердження зазнає невдачі. Коли ми порівнюємо складні структури, Tester створює дампи порівнюваних значень і зберігає їх у директорії `output`. Наприклад, коли уявний тест `Arrays.recursive.phpt` зазнає невдачі, дампи будуть збережені таким чином: +Дослідження помилкових assertion +-------------------------------- +Коли assertion зазнає невдачі, Tester виводить, у чому полягає помилка. Якщо порівнюємо складніші структури, Tester створює дампи порівнюваних значень і зберігає їх у директорії `output`. Наприклад, при невдачі вигаданого тесту `Arrays.recursive.phpt` дампи будуть збережені наступним чином: ``` app/ └── tests/ ├── output/ - │ ├──── Arrays.recursive.actual # фактическое значение - │ └──── Arrays.recursive.expected # ожидаемое значение + │ ├── Arrays.recursive.actual # фактичне значення + │ └── Arrays.recursive.expected # очікуване значення │ - └── Arrays.recursive.phpt # неудачный тест + └── Arrays.recursive.phpt # тест, що зазнав невдачі ``` -Ми можемо змінити ім'я директорії на `Tester\Dumper::$dumpDir`. +Назву директорії можемо змінити через `Tester\Dumper::$dumpDir`. diff --git a/tester/uk/guide.texy b/tester/uk/guide.texy index ca06db82f4..8afc8428cd 100644 --- a/tester/uk/guide.texy +++ b/tester/uk/guide.texy @@ -1,17 +1,17 @@ -Початок роботи з Tester -*********************** +Починаємо з Nette Tester +************************
                                                                                                                            -Навіть хороші програмісти припускаються помилок. Різниця між хорошим програмістом і поганим у тому, що хороший зробить це тільки один раз і наступного разу виявить це за допомогою автоматизованих тестів. +Навіть хороші програмісти роблять помилки. Різниця між хорошим і поганим програмістом полягає в тому, що хороший зробить її лише раз, а наступного разу виявить її за допомогою автоматизованих тестів. -- "Той, хто не тестує, приречений повторювати власні помилки". (мудре прислів'я). -- "Коли ми позбуваємося однієї помилки, з'являється інша". (закон Мерфі) -- "Щоразу, коли у вас виникає спокуса надрукувати твердження, замість цього напишіть його як тест". (Мартін Фаулер) +- "Хто не тестує, той приречений повторювати свої помилки." (прислів'я) +- "Щойно ми позбудемося однієї помилки, з'явиться інша." (Закон Мерфі) +- "Щоразу, коли у вас виникає бажання вивести на екран змінну, напишіть краще тест." (Мартін Фаулер)
                                                                                                                            -Ви коли-небудь писали наступний код на PHP? +Ви коли-небудь писали в PHP подібний код? ```php $obj = new MyClass; @@ -20,40 +20,40 @@ $result = $obj->process($input); var_dump($result); ``` -Ви коли-небудь скидали результат виклику функції, просто щоб перевірити на око, що вона повертає те, що повинна повертати? Напевно ви робите це багато разів на день. Поклавши руку на серце, якщо все працює, ви видаляєте цей код і очікуєте, що клас не буде зламаний у майбутньому? Закон Мерфі гарантує зворотне :-) +Тобто ви виводили результат виклику функції лише для того, щоб очима перевірити, чи повертає він те, що має? Напевно, ви робите це багато разів на день. Руку на серце: у випадку, якщо все працює правильно, ви видаляєте цей код? Очікуєте, що клас у майбутньому не зламається? Закони Мерфі гарантують протилежне :-) -Фактично, ви написали тест. Він потребує невеликої модифікації, щоб не вимагати нашої перевірки, а просто вміти перевіряти себе. І якщо ви не видалили його, ми можемо запустити його в будь-який час у майбутньому, щоб перевірити, що все як і раніше працює так, як потрібно. З часом ви можете створити велику кількість таких тестів, тому було б непогано, якби ми могли запускати їх автоматично. +По суті, ви написали тест. Його лише потрібно трохи змінити, щоб він не вимагав візуальної перевірки, а перевірявся сам. А якщо тест не видаляти, його можна запустити будь-коли в майбутньому і перевірити, що все ще працює, як має. З часом таких тестів ви створите велику кількість, тому було б добре запускати їх автоматизовано. -І Nette Tester якраз допомагає в цьому. +І з усім цим вам допоможе саме Nette Tester. -Що робить Tester унікальним? .[#toc-what-makes-tester-unique] -============================================================= +Чим Tester унікальний? +====================== -Написання тестів для Nette Tester унікальне тим, що **кожен тест являє собою стандартний PHP-скрипт, який може бути запущений окремо.**. +Написання тестів для Nette Tester унікальне тим, що **кожен тест — це звичайний PHP-скрипт, який можна запустити окремо.** -Тому, коли ви пишете тест, ви можете просто запустити його, щоб перевірити, чи є помилка в програмуванні. Якщо все працює правильно. Якщо ні, ви можете легко пройтись по програмі у вашій IDE та пошукати помилку. Ви навіть можете відкрити її в браузері. +Тобто, коли ви пишете тест, ви можете його просто запускати та з'ясовувати, чи немає в ньому, наприклад, програмістської помилки. Чи працює він правильно. Якщо ні, ви можете легко крокувати його у своєму IDE та шукати помилку. Ви можете навіть відкрити його в браузері. -І найголовніше - запустивши її, ви виконаєте тест. Ви відразу ж дізнаєтеся, пройшов він чи не пройшов. Як? Давайте покажемо тут. Напишемо тривіальний тест на використання масиву PHP і збережемо його у файл `ArrayTest.php`: +А головне — тим, що ви його запускаєте, ви виконуєте тест. Ви миттєво дізнаєтеся, чи він пройшов, чи зазнав невдачі. Як? Давайте покажемо. Напишемо тривіальний тест роботи з PHP-масивом і збережемо у файл `ArrayTest.php`: ```php .{file:ArrayTest.php} OK \-- -Спробуйте змінити твердження на `Assert::contains('XXX', $stack);` у тесті та подивіться, що станеться під час запуску: +Спробуйте в тесті змінити твердження на хибне `Assert::contains('XXX', $stack);` і спостерігайте, що станеться при запуску: /--pre .[terminal] $ php ArrayTest.php @@ -73,53 +73,53 @@ $ php ArrayTest.php FAILURE \-- -Ми продовжуємо розповідати про написання [тестів |Writing Tests] у розділі [Написання тестів |Writing Tests]. +Далі про написання продовжуємо в розділі [Написання тестів|writing-tests]. -Встановлення та вимоги .[#toc-installation-and-requirements] -============================================================ +Встановлення та вимоги +====================== -Мінімальна необхідна версія PHP для Tester - 7.1 (докладніше див. таблицю [підтримуваних версій PHP |#Supported-PHP-Versions] ). Кращим способом встановлення є [Composer |best-practices:composer]: +Мінімальна версія PHP, що вимагається Tester'ом, — 7.1 (детальніше в таблиці [#Підтримувані версії PHP]). Переважний спосіб встановлення — за допомогою [Composer |best-practices:composer]: /--pre .[terminal] composer require --dev nette/tester \-- -Спробуйте запустити Nette Tester з командного рядка (без аргументів він покаже тільки коротку довідку): +Спробуйте запустити Nette Tester з командного рядка (без параметрів він лише виведе довідку): /--pre .[terminal] vendor/bin/tester \-- -Запуск тестів .[#toc-running-tests] -=================================== +Запуск тестів +============= -У міру зростання нашого додатка зростає і кількість тестів. Було б непрактично запускати тести по одному. Тому в Tester є пакетний запуск тестів, який ми викликаємо з командного рядка. Параметром є каталог, у якому знаходяться тести. Крапка вказує на поточний каталог. +З ростом застосунку кількість тестів зростає разом з ним. Було б непрактично запускати тести по одному. Тому Tester має пакетний запускач тестів, який ми викликаємо з командного рядка. Як параметр вкажемо директорію, в якій знаходяться тести. Крапка означає поточну директорію. /--pre .[terminal] vendor/bin/tester . \-- -Програма Nette Tester перебирає вказаний каталог і всі підкаталоги та шукає тести, якими є файли `*.phpt` і `*Test.php`. Вона також знайде наш тест `ArrayTest.php`, оскільки він відповідає масці. +Запускач тестів просканує вказану директорію та всі піддиректорії та знайде тести, тобто файли `*.phpt` та `*Test.php`. Він знайде й наш тест `ArrayTest.php`, оскільки він відповідає масці. -Потім він починає тестування. Кожен тест запускається як новий процес PHP, тому він виконується повністю ізольовано від інших. Він працює паралельно в декількох потоках, що робить його надзвичайно швидким. Спочатку запускаються тести, які не пройшли під час попереднього запуску, тож ви одразу дізнаєтеся, чи виправили ви помилку. +Потім він розпочне тестування. Кожен тест запускається як новий PHP-процес, тому він відбувається повністю ізольовано від інших. Він запускає їх паралельно в кількох потоках, і завдяки цьому він надзвичайно швидкий. І першими він запускає тести, які зазнали невдачі під час попереднього запуску, тому ви миттєво дізнаєтеся, чи вдалося вам виправити помилку. -Для кожного виконаного тесту бігунок друкує один символ, щоб показати прогрес: +Під час виконання тестів Tester постійно виводить результати на термінал у вигляді символів: -- . - тест пройдено -- s - тест пропущено -- F - тест не пройдено +- . – тест пройшов +- s – тест був пропущений (skipped) +- F – тест зазнав невдачі (failed) -Висновок може мати такий вигляд: +Вивід може виглядати так: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +PHP 8.3.2 (cli) | php -n | 8 threads ........s................F......... @@ -132,44 +132,44 @@ PHP 7.4.8 (cli) | php -n | 8 threads FAILURES! (35 tests, 1 failures, 1 skipped, 1.7 seconds) \-- -Було виконано 35 тестів, один не пройшов, один був пропущений. +Було запущено 35 тестів, один зазнав невдачі, один був пропущений. -Ми продовжимо в розділі [Виконання тестів |Running tests]. +Далі продовжуємо в розділі [Запуск тестів|running-tests]. -Режим спостереження .[#toc-watch-mode] -====================================== +Режим Watch +=========== -Чи проводите ви рефакторинг коду? Або навіть розробляєте за методологією TDD (Test Driven Development)? Тоді вам сподобається режим спостереження. Tester стежить за вихідними кодами і запускає себе при їхній зміні. +Рефакторите код? Або навіть розробляєте за методикою TDD (Test Driven Development)? Тоді вам сподобається режим watch. Tester у ньому відстежує вихідні коди і при зміні сам запускається. -Під час розробки у вас є термінал у кутку монітора, де на вас спалахує зелений рядок стану, і коли він раптом стає червоним, ви знаєте, що щойно зробили щось небажане. Насправді це чудова гра, у якій ви програмуєте і намагаєтеся дотримуватися кольору. +При розробці у вас в кутку монітора термінал, де на вас світить зелений рядок стану, і коли він раптом змінюється на червоний, ви знаєте, що щойно щось зробили не зовсім добре. Це насправді чудова гра, коли ви програмуєте і намагаєтеся тримати колір. -Режим Watch запускається за допомогою параметра [--watch |running-tests#w-watch-path]. +Режим Watch запускається параметром [--watch |running-tests#-w --watch path]. -Звіти CodeCoverage .[#toc-codecoverage-reports] -=============================================== +Звіти CodeCoverage +================== -Tester може генерувати звіти з оглядом того, який обсяг вихідного коду охоплюють тести. Звіт може бути або в людинозчитуваному форматі HTML, або у форматі Clover XML для подальшого машинного опрацювання. +Tester вміє генерувати звіти з оглядом того, скільки вихідного коду покривають тести. Звіт може бути або в людсько-читабельному форматі HTML, або Clover XML для подальшої машинної обробки. -Див. "приклад HTML-звіту":https://files.nette.org/tester/coverage.html з покриттям коду. +Подивіться на "приклад HTML-звіту":attachment:coverage.html з покриттям коду. -Підтримувані версії PHP .[#toc-supported-php-versions] -====================================================== +Підтримувані версії PHP +======================= -| Версія | сумісна з PHP +| версія | сумісна з PHP |------------------|------------------- -| Tester 2.5 | PHP 8.0 - 8.2 -| Tester 2.4 | PHP 7.2 - 8.2 -| Tester 2.3 | PHP 7.1 - 8.0 -| Tester 2.1 - 2.2 | PHP 7.1 - 7.3 -| Tester 2.0 | PHP 5.6 - 7.3 -| Tester 1.7 | PHP 5.3 - 7.3 + HHVM 3.3+ -| Tester 1.6 | PHP 5.3 - 7.0 + HHVM -| Tester 1.3 - 1.5 | PHP 5.3 - 5.6 + HHVM -| Tester 0.9 - 1.2 | PHP 5.3 - 5.6 - -Застосовується до останніх версій патчів. - -До версії 1.7 Tester підтримував [HHVM |https://hhvm.com] 3.3.0 або новіше (використовуючи `tester -p hhvm`). Починаючи з версії Tester 2.0 підтримку було припинено. Використання було простим: +| Tester 2.5 | PHP 8.0 – 8.3 +| Tester 2.4 | PHP 7.2 – 8.2 +| Tester 2.3 | PHP 7.1 – 8.0 +| Tester 2.1 – 2.2 | PHP 7.1 – 7.3 +| Tester 2.0 | PHP 5.6 – 7.3 +| Tester 1.7 | PHP 5.3 – 7.3 + HHVM 3.3+ +| Tester 1.6 | PHP 5.3 – 7.0 + HHVM +| Tester 1.3 – 1.5 | PHP 5.3 – 5.6 + HHVM +| Tester 0.9 – 1.2 | PHP 5.3 – 5.6 + +Застосовується до останньої версії патча. + +Tester до версії 1.7 підтримував також [HHVM |https://hhvm.com] 3.3.0 або вище (через `tester -p hhvm`). Підтримка була припинена з версії Tester 2.0. diff --git a/tester/uk/helpers.texy b/tester/uk/helpers.texy index 397dc430d4..dbb4f23a4b 100644 --- a/tester/uk/helpers.texy +++ b/tester/uk/helpers.texy @@ -1,31 +1,45 @@ -Помічники -********* +Допоміжні класи +*************** -DomQuery .[#toc-domquery] -------------------------- -`Tester\DomQuery` це клас, що розширює `SimpleXMLElement` з методами, які полегшують тестування вмісту HTML або XML. +DomQuery +-------- +`Tester\DomQuery` — це клас, що розширює `SimpleXMLElement` для легкого пошуку в HTML або XML за допомогою CSS-селекторів. ```php -# let's have an HTML document in $html that we load -$dom = Tester\DomQuery::fromHtml($html); - -# we can test the presence of elements using CSS selectors -Assert::true($dom->has('form#registration')); -Assert::true($dom->has('input[name="username"]')); -Assert::true($dom->has('input[type="submit"]')); - -# or select elements as array of DomQuery -$elems = $dom->find('input[data-autocomplete]'); +# створення DomQuery з HTML рядка +$dom = Tester\DomQuery::fromHtml(' +
                                                                                                                            +

                                                                                                                            Title

                                                                                                                            +
                                                                                                                            Text
                                                                                                                            +
                                                                                                                            +'); + +# тест існування елементів за допомогою CSS селекторів +Assert::true($dom->has('article.post')); +Assert::true($dom->has('h1')); + +# знаходження елементів як масиву об'єктів DomQuery +$headings = $dom->find('h1'); +Assert::same('Title', (string) $headings[0]); + +# тест, чи елемент відповідає селектору (з версії 2.5.3) +$content = $dom->find('.content')[0]; +Assert::true($content->matches('div')); +Assert::false($content->matches('p')); + +# знаходження найближчого предка, що відповідає селектору (з 2.5.5) +$article = $content->closest('.post'); +Assert::true($article->matches('article')); ``` -FileMock .[#toc-filemock] -------------------------- -`Tester\FileMock` емулює файли в пам'яті, щоб допомогти вам протестувати код, який використовує функції на кшталт `fopen()`, `file_get_contents()` або `parse_ini_file()`. Наприклад: +FileMock +-------- +`Tester\FileMock` емулює файли в пам'яті та полегшує тестування коду, який використовує функції `fopen()`, `file_get_contents()`, `parse_ini_file()` тощо. Приклад використання: ```php -# Tested class +# Тестований клас class Logger { public function __construct( @@ -39,21 +53,21 @@ class Logger } } -# New empty file +# Новий порожній файл $file = Tester\FileMock::create(''); $logger = new Logger($file); $logger->log('Login'); $logger->log('Logout'); -# Created content testing +# Тестуємо створений вміст Assert::same("Login\nLogout\n", file_get_contents($file)); ``` Assert::with() .[filter] ------------------------ -Це не твердження, а помічник для тестування приватних методів і властивостей об'єктів. +Це не assertion, а помічник для тестування приватних методів та властивостей об'єктів. ```php class Entity @@ -65,17 +79,17 @@ class Entity $ent = new Entity; Assert::with($ent, function () { - Assert::true($this->enabled); // доступний private $ent->enabled + Assert::true($this->enabled); // доступна приватна $ent->enabled }); ``` Helpers::purge() .[filter] -------------------------- -Метод `purge()` створює вказаний каталог і, якщо він уже існує, видаляє весь його вміст. Він зручний для створення тимчасових каталогів. Наприклад, у `tests/bootstrap.php`: +Метод `purge()` створює вказану директорію, а якщо вона вже існує, видаляє весь її вміст. Корисно для створення тимчасової директорії. Наприклад, у `tests/bootstrap.php`: ```php -@mkdir(__DIR__ . '/tmp'); # @ - directory may already exist +@mkdir(__DIR__ . '/tmp'); # @ - директорія вже може існувати define('TempDir', __DIR__ . '/tmp/' . getmypid()); Tester\Helpers::purge(TempDir); @@ -84,25 +98,25 @@ Tester\Helpers::purge(TempDir); Environment::lock() .[filter] ----------------------------- -Тести запускаються паралельно. Іноді нам не потрібно перекривати час виконання тестів. Зазвичай тести баз даних потребують підготовки вмісту бази даних, і їм потрібно, щоб ніщо не заважало їм під час виконання тесту. У цих випадках ми використовуємо `Tester\Environment::lock($name, $dir)`: +Тести запускаються паралельно. Іноді, однак, нам потрібно, щоб виконання тестів не перекривалося. Типово для тестів баз даних необхідно, щоб тест підготував вміст бази даних, і інший тест під час його виконання не втручався в базу даних. У цих тестах використовуємо `Tester\Environment::lock($name, $dir)`: ```php Tester\Environment::lock('database', __DIR__ . '/tmp'); ``` -Перший аргумент - ім'я блокування. Другий - шлях до каталогу для збереження блокування. Спочатку запускається тест, який отримує блокування. Решта тестів повинні почекати, поки він завершиться. +Перший параметр — це ім'я блокування, другий — шлях до директорії для збереження блокування. Тест, який отримає блокування першим, виконається, інші тести повинні чекати його завершення. Environment::bypassFinals() .[filter] ------------------------------------- -Класи або методи, позначені як `final`, важко тестувати. Виклик `Tester\Environment::bypassFinals()` на початку тестування призводить до того, що ключові слова `final` видаляються під час завантаження коду. +Класи або методи, позначені як `final`, важко тестувати. Виклик `Tester\Environment::bypassFinals()` на початку тесту призводить до того, що ключові слова `final` під час завантаження коду пропускаються. ```php require __DIR__ . '/bootstrap.php'; Tester\Environment::bypassFinals(); -class MyClass extends NormallyFinalClass # <-- NormallyFinalClass is not final anymore +class MyClass extends NormallyFinalClass # <-- NormallyFinalClass вже не final { // ... } @@ -111,15 +125,15 @@ class MyClass extends NormallyFinalClass # <-- NormallyFinalClass is not final Environment::setup() .[filter] ------------------------------ -- покращує читабельність дампа помилок (увімкнено розфарбування), інакше виводиться стандартне трасування стека PHP -- дозволяє перевірити, що твердження були викликані в тесті, інакше тести без (наприклад, забутих) тверджень теж пройдуть -- автоматично запускає збирач покриття коду під час використання `--coverage` (описано пізніше) -- друкує статус OK або FAILURE наприкінці скрипта +- покращує читабельність виводу помилок (включно з підсвічуванням), інакше виводиться стандартне PHP трасування стека +- вмикає перевірку, що в тесті були викликані assertion, інакше тест без assertion (наприклад, забутих) також пройде +- при використанні `--coverage` автоматично запускає збір інформації про виконаний код (описано далі) +- виводить стан OK або FAILURE в кінці скрипта Environment::setupFunctions() .[filter]{data-version:2.5} --------------------------------------------------------- -Створює глобальні функції `test()`, `setUp()` і `tearDown()`, на які можна розбити тести. +Створює глобальні функції `test()`, `testException()`, `setUp()` та `tearDown()`, за допомогою яких ви можете структурувати тести. ```php test('опис тесту', function () { @@ -132,21 +146,21 @@ test('опис тесту', function () { Environment::VariableRunner .[filter] ------------------------------------- -Позволяет узнать, был ли тест запущен напрямую или через Tester. +Дозволяє з'ясувати, чи був тест запущений безпосередньо, чи за допомогою Tester'а. ```php if (getenv(Tester\Environment::VariableRunner)) { - # запускається Tester + # запущено Tester'ом } else { - # інший спосіб + # запущено інакше } ``` Environment::VariableThread .[filter] ------------------------------------- -Tester запускает тесты параллельно в заданном количестве потоков. Мы найдем номер потока в переменной окружения, когда нас это заинтересует: +Tester запускає тести паралельно у вказаній кількості потоків. Якщо нас цікавить номер потоку, ми дізнаємося його зі змінної середовища: ```php -echo "I'm running in a thread number " . getenv(Tester\Environment::VariableThread); +echo "Працюю в потоці номер " . getenv(Tester\Environment::VariableThread); ``` diff --git a/tester/uk/running-tests.texy b/tester/uk/running-tests.texy index 51c1835d90..4e903c15d4 100644 --- a/tester/uk/running-tests.texy +++ b/tester/uk/running-tests.texy @@ -1,83 +1,83 @@ -Бігові тести -************ +Запуск тестів +************* .[perex] -Найбільш помітною частиною Nette Tester є запуск тестів з командного рядка. Він надзвичайно швидкий і надійний, оскільки автоматично запускає всі тести як окремі процеси паралельно в декількох потоках. Він також може запускати себе в так званому режимі спостереження. +Найпомітнішою частиною Nette Tester є запускач тестів з командного рядка. Він надзвичайно швидкий та надійний, оскільки автоматично запускає всі тести як окремі процеси, причому паралельно в кількох потоках. Також він вміє запускатися сам у так званому режимі спостереження (watch). -Запуск тестового бігуна Nette Tester здійснюється з командного рядка. В якості параметра ми передамо директорію тесту. Для поточного каталогу просто введіть крапку: +Запускач тестів викликаємо з командного рядка. Як параметр вкажемо директорію з тестами. Для поточної директорії достатньо вказати крапку: /--pre .[terminal] vendor/bin/tester . \-- -Після запуску програма виконання тестів просканує вказаний каталог і всі підкаталоги та знайде тести, якими є файли `*.phpt` і `*Test.php`. Він також прочитає і оцінить їхні [анотації |test-annotations], щоб знати, які з них і як запускати. +Запускач тестів просканує вказану директорію та всі піддиректорії і знайде тести, тобто файли `*.phpt` та `*Test.php`. Водночас він читає та оцінює їхні [анотації|test-annotations], щоб знати, які з них і як запускати. -Після цього він виконує тести. Для кожного виконаного тесту бігун виводить один символ, щоб показати прогрес: +Потім він запустить тести. Під час виконання тестів він постійно виводить результати на термінал у вигляді символів: -- . - тест пройдено -- s - тест пропущено -- F - тест не пройдено +- . – тест пройшов +- s – тест був пропущений (skipped) +- F – тест зазнав невдачі (failed) -Виведення може мати вигляд: +Вивід може виглядати, наприклад, так: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 + |_| \___ /___) |_| \___ |_|_\ v2.5.2 Note: No php.ini is used. -PHP 7.4.8 (cli) | php -n | 8 threads +PHP 8.3.2 (cli) | php -n | 8 threads ........s.......................... OK (35 tests, 1 skipped, 1.7 seconds) \-- -Коли ви запустите його знову, він спочатку запустить тести, які не вдалось виконати під час попереднього запуску, тому ви одразу дізнаєтесь, чи виправили помилку. +При повторному запуску спочатку виконуються тести, які зазнали невдачі під час попереднього запуску, тому ви миттєво дізнаєтеся, чи вдалося вам виправити помилку. -Код виходу тестера дорівнює нулю, якщо жоден тест не завершився невдачею. Ненульовий в іншому випадку. +Якщо жоден тест не зазнав невдачі, код повернення Tester'а дорівнює нулю. В іншому випадку код повернення ненульовий. .[warning] -Тестер запускає PHP-процеси без `php.ini`. Більш детально в розділі [Власний php.ini |#Own php.ini]. +Tester запускає PHP-процеси без `php.ini`. Детальніше в розділі [#Власний php.ini]. -Параметри командного рядка .[#toc-command-line-options] -======================================================= +Параметри командного рядка +========================== -Огляд параметрів командного рядка можна отримати, запустивши тестер без параметрів або з опцією `-h`: +Огляд усіх опцій командного рядка отримаємо, запустивши Tester без параметрів або з параметром `-h`: /--pre .[terminal] _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) - |_| \___ /___) |_| \___ |_|_\ v2.3.3 - -Использование: - tester [параметры] [<тестовый файл> | <каталог>]... - -Параметры: - -p <путь> Укажите интерпретатор PHP для запуска (по умолчанию: php). - -c Искать php.ini файл (или искать в директории) . - -C Использовать общесистемный php.ini. - -l | --log <путь> Запись журнала в файл <путь>. - -d <ключ=значение>... Определить INI-запись 'key' со значением 'value'. - -s Показать информацию о пропущенных тестах. - --stop-on-fail Остановить выполнение при первом сбое. - -j Выполнять заданий параллельно (по умолчанию: 8). - -o Указать формат вывода. - -w | --watch <путь> Каталог просмотра. - -i | --info Показать информацию об окружении тестов и выйти. - --setup <путь> Сценарий для настройки бегущей строки. - --temp <путь> Путь к временному каталогу. По умолчанию: sys_get_temp_dir(). - --colors [1|0] Включить или отключить цвета. - --coverage <путь> Генерировать отчет о покрытии кода в файл. - --coverage-src <путь> Путь к исходному коду. - -h | --help Это справка. + |_| \___ /___) |_| \___ |_|_\ v2.5.2 + +Usage: + tester [options] [ | ]... + +Options: + -p Specify PHP interpreter to run (default: php). + -c Look for php.ini file (or look in directory) . + -C Use system-wide php.ini. + -d ... Define INI entry 'key' with value 'value'. + -s Show information about skipped tests. + --stop-on-fail Stop execution upon the first failure. + -j Run jobs in parallel (default: 8). + -o (e.g. -o junit:output.xml) + Specify one or more output formats with optional file name. + -w | --watch Watch directory. + -i | --info Show tests environment info and exit. + --setup Script for runner setup. + --temp Path to temporary directory. Default by sys_get_temp_dir(). + --colors [1|0] Enable or disable colors. + --coverage Generate code coverage report to file. + --coverage-src Path to source code. + -h | --help This help. \-- -p .[filter] ------------------- -Вказує двійковий файл PHP, який буде використано для запуску тестів. За замовчуванням це `php`. +Визначає бінарний файл PHP, який буде використовуватися для запуску тестів. Стандартно це `php`. /--pre .[terminal] tester -p /home/user/php-7.2.0-beta/php-cgi tests @@ -86,26 +86,17 @@ tester -p /home/user/php-7.2.0-beta/php-cgi tests -c .[filter] ------------------- -Вказує, який `php.ini` буде використовуватися під час запуску тестів. За замовчуванням php.ini не використовується. Докладнішу інформацію наведено у розділі Власний [php.ini |#Own php.ini]. +Визначає, який `php.ini` буде використовуватися при запуску тестів. За замовчуванням жоден php.ini не використовується. Більше в розділі [#Власний php.ini]. -C .[filter] ------------ -Використовується загальносистемний `php.ini`. Отже, на платформі UNIX всі файли `/etc/php/{sapi}/conf.d/*.ini` також. Див. розділ [Власний php.ini |#Own php.ini]. - - -''-l | --log '' .[filter] -------------------------------- -Прогрес тестування записується у файл. Всі невдалі, пропущені, а також успішні тести: - -/--pre .[terminal] -tester --log /var/log/tests.log tests -\-- +Використовується системний `php.ini`. На UNIX також усі відповідні INI-файли `/etc/php/{sapi}/conf.d/*.ini`. Більше в розділі [#Власний php.ini]. -d .[filter] ------------------------ -Задає значення директиви конфігурації PHP для тестів. Параметр можна використовувати декілька разів. +Встановлює значення конфігураційної директиви PHP для тестів. Параметр може бути використаний кілька разів. /--pre .[terminal] tester -d max_execution_time=20 @@ -114,34 +105,36 @@ tester -d max_execution_time=20 -s --- -Буде показано інформацію про пропущені тести. +Відображається інформація про пропущені тести. --stop-on-fail .[filter] ------------------------ -Тестер припиняє тестування після першого невдалого тесту. +Tester зупинить тестування при першому невдалому тесті. -j .[filter] ------------------ -Тести виконуються в `` паралельними прецесіями. Значення за замовчуванням 8. Якщо ми хочемо запускати тести послідовно, ми використовуємо значення 1. +Визначає, скільки паралельних процесів з тестами буде запущено. Значення за замовчуванням — 8. Якщо ми хочемо, щоб усі тести пройшли послідовно, використаємо значення 1. --o .[filter] -------------------------------------- -Формат виводу. За замовчуванням - формат консолі. +-o .[filter] +------------------------------------------------------- +Встановлює формат виводу. Стандартний формат — для консолі. Ви можете вказати ім'я файлу, до якого буде записано вивід (напр. `-o junit:output.xml`). Опцію `-o` можна повторити кілька разів і згенерувати так кілька форматів одночасно. -- `console`: те саме, що і за замовчуванням, але у цьому випадку не виводиться ASCII-логотип -- `tap`: [формат TAP |https://en.wikipedia.org/wiki/Test_Anything_Protocol], придатний для машинної обробки -- `junit`: формат JUnit XML, також придатний для машинної обробки -- `none`: нічого не друкується +- `console`: аналогічно стандартному формату, але в цьому випадку не відображається ASCII логотип +- `console-lines`: схоже на console, але результат кожного тесту вказано на окремому рядку з додатковою інформацією +- `tap`: [TAP формат |https://en.wikipedia.org/wiki/Test_Anything_Protocol] підходить для машинної обробки +- `junit`: XML формат JUnit, також підходить для машинної обробки +- `log`: Виводи перебігу тестування. Всі неуспішні, пропущені, а також успішні тести +- `none`: нічого не виводиться ''-w | --watch '' .[filter] --------------------------------- -Тестер не завершує роботу після завершення тестів, а продовжує працювати і переглядати PHP-файли у вказаному каталозі. При зміні параметра він запускає тести знову. Параметр можна використовувати декілька разів, якщо ми хочемо відстежувати декілька каталогів. +Після завершення тестування Tester не завершує роботу, а залишається працювати і відстежує PHP-файли у вказаній директорії. При зміні запускає тести знову. Параметр може бути використаний кілька разів, якщо ми хочемо відстежувати кілька директорій. -Це зручно під час рефакторингу бібліотеки або налагодження тестів. +Корисно при рефакторингу бібліотеки або налагодженні тестів. /--pre .[terminal] tester --watch src tests @@ -150,7 +143,7 @@ tester --watch src tests ''-i | --info'' .[filter] ------------------------- -Показує інформацію про середовище виконання тесту. Наприклад: +Відображає інформацію про середовище виконання для тестів. Наприклад: /--pre .[terminal] tester -p /usr/bin/php7.1 -c tests/php.ini --info @@ -177,13 +170,13 @@ Core, ctype, date, dom, ereg, fileinfo, filter, hash, ... --setup .[filter] ------------------------ -При запуску тестер завантажує даний PHP-скрипт. У ньому доступна змінна `Tester\Runner\Runner $runner`. Нехай це буде файл `tests/runner-setup.php`: +Tester при старті завантажує вказаний PHP-скрипт. У ньому доступна змінна `Tester\Runner\Runner $runner`. Припустимо файл `tests/runner-setup.php` з вмістом: ```php $runner->outputHandlers[] = new MyOutputHandler; ``` -і запустимо тестер: +Tester запустимо: /--pre .[terminal] tester --setup tests/runner-setup.php tests @@ -192,47 +185,47 @@ tester --setup tests/runner-setup.php tests --temp .[filter] ----------------------- -Задає шлях до каталогу для тимчасових файлів Тестера. Значення за замовчуванням повертається `sys_get_temp_dir()`. Якщо значення за замовчуванням невірне, ви отримаєте відповідне повідомлення. +Встановлює шлях до директорії для тимчасових файлів Tester'а. Значення за замовчуванням повертає `sys_get_temp_dir()`. Якщо значення за замовчуванням не є валідним, ви будете попереджені. -Якщо ми не впевнені, який каталог використовується, ми можемо запустити Tester з параметром `--info`. +Якщо ми не впевнені, яка директорія використовується, запустимо Tester з параметром `--info`. --colors 1|0 .[filter] ---------------------- -За замовчуванням тестер виявляє термінал з підтримкою кольорів і розфарбовує його вивід. Цей параметр перекриває автоматичне визначення. Ми можемо глобально налаштувати розфарбування за допомогою системної змінної оточення `NETTE_TESTER_COLORS`. +За замовчуванням Tester визначає кольоровий термінал і розфарбовує свій вивід. Ця опція перекриває автовизначення. Глобально можна налаштувати розфарбовування системною змінною середовища `NETTE_TESTER_COLORS`. --coverage .[filter] --------------------------- -Тестер згенерує звіт з оглядом того, яку частину вихідного коду покрито тестами. Ця опція вимагає розширення PHP [Xdebug |https://xdebug.org/] або увімкненого [PCOV |https://github.com/krakjoe/pcov], або PHP 7 з PHPDBG SAPI, що є швидшим. Розширення файлу призначення визначає формат вмісту. HTML або Clover XML. +Tester згенерує звіт з оглядом того, скільки вихідного коду покривають тести. Ця опція вимагає встановленого розширення PHP [Xdebug |https://xdebug.org/], або [PCOV |https://github.com/krakjoe/pcov], або PHP 7 з PHPDBG SAPI, яке є швидшим. Розширення цільового файлу визначає його формат. Або HTML, або Clover XML. /--pre .[terminal] -tester tests --coverage coverage.html # HTML report -tester tests --coverage coverage.xml # Clover XML report +tester tests --coverage coverage.html # HTML звіт +tester tests --coverage coverage.xml # Clover XML звіт \-- -Пріоритетність вибору механізму збору наступна: +Пріоритет вибору механізму наступний: 1) PCOV 2) PHPDBG 3) Xdebug -Під час запуску PHPDBG обширні тести можуть завершитися невдачею через вичерпання пам'яті. Збір даних про покриття є операцією, що споживає багато пам'яті. У цьому випадку може допомогти виклик `Tester\CodeCoverage\Collector::flush()` всередині тесту. Він збереже зібрані дані у файл і звільнить пам'ять. Коли збір даних не виконується або використовується Xdebug, виклик не має ніякого ефекту. +При використанні PHPDBG ми можемо зіткнутися з невдачею тесту через вичерпання пам'яті у випадку великих тестів. Збір інформації про покритий код є ресурсомістким за пам'яттю. У цьому випадку нам допоможе виклик `Tester\CodeCoverage\Collector::flush()` всередині тесту. Він запише зібрані дані на диск і звільнить пам'ять. Якщо збір даних не відбувається або використовується Xdebug, виклик не має жодного ефекту. -"Приклад HTML-звіту":https://files.nette.org/tester/coverage.html з покриттям коду. +"Приклад HTML-звіту":attachment:coverage.html з покриттям коду. --coverage-src .[filter] ------------------------------- -Використовуємо одночасно з опцією `--coverage`. Параметр `` це шлях до вихідного коду, для якого ми генеруємо звіт. Його можна використовувати багаторазово. +Використовуємо одночасно з опцією `--coverage`. `` — це шлях до вихідних кодів, для яких генерується звіт. Може використовуватися повторно. -Власний php.ini .[#toc-own-php-ini] -=================================== -Тестер запускає PHP-процеси з опцією `-n`, що означає, що ніяких `php.ini` не завантажується (навіть з `/etc/php/conf.d/*.ini` в UNIX). Це забезпечує однакове середовище для запуску тестів, але також деактивує всі зовнішні розширення PHP, які зазвичай завантажуються системним PHP. +Власний php.ini +=============== +Tester запускає PHP-процеси з параметром `-n`, що означає, що жоден `php.ini` не завантажується. На UNIX навіть ті з `/etc/php/conf.d/*.ini`. Це забезпечує однакове середовище для запуску тестів, але також виключає всі розширення PHP, що зазвичай завантажуються системним PHP. -Якщо ви хочете зберегти конфігурацію системи, використовуйте параметр `-C`. +Якщо ви хочете зберегти завантаження системних файлів php.ini, використовуйте параметр `-C`. -Якщо вам потрібні деякі розширення або якісь особливі налаштування INI, ми рекомендуємо створити власний файл `php.ini` і розповсюдити його серед тестів. Після цього запускаємо тестер з параметром `-c`, наприклад, `tester -c tests/php.ini`. INI-файл може мати такий вигляд: +Якщо вам потрібні якісь розширення або спеціальні налаштування INI для тестів, рекомендуємо створити власний файл `php.ini`, який буде розповсюджуватися з тестами. Tester потім запускаємо з параметром `-c`, наприклад `tester -c tests/php.ini tests`, де INI-файл може виглядати так: ```ini [PHP] @@ -243,4 +236,4 @@ extension=php_pdo_pgsql.dll memory_limit=512M ``` -Запуск тестера з системним `php.ini` в UNIX, наприклад, `tester -c /etc/php/cgi/php.ini`, не завантажує інші INI-файли з `/etc/php/conf.d/*.ini`. Це поведінка PHP, а не тестера. +Запуск Tester'а на UNIX із системним `php.ini`, наприклад `tester -c /etc/php/cli/php.ini`, не завантажить інші INI з `/etc/php/conf.d/*.ini`. Це властивість PHP, а не Tester'а. diff --git a/tester/uk/test-annotations.texy b/tester/uk/test-annotations.texy index 05c3af820b..dcf517022f 100644 --- a/tester/uk/test-annotations.texy +++ b/tester/uk/test-annotations.texy @@ -1,10 +1,10 @@ -Анотації до тестів -****************** +Анотації тестів +*************** .[perex] -Анотації визначають, як тести будуть оброблятися [програмою запуску тестів командного рядка |running-tests]. Вони записуються на початку файлу тесту. +Анотації визначають, як тести будуть оброблятися [запускачем тестів з командного рядка|running-tests]. Вони записуються на початку файлу з тестом. -Анотації не чутливі до регістру. Вони також не мають жодного ефекту, якщо тест запускається вручну як звичайний PHP-скрипт. +При анотаціях не враховується регістр літер. Також вони не мають жодного впливу, якщо тест запущений вручну як звичайний PHP-скрипт. Приклад: @@ -21,19 +21,19 @@ require __DIR__ . '/../bootstrap.php'; ``` -Test .[filter] +TEST .[filter] -------------- -Насправді це не анотація. Він тільки задає заголовок тесту, який виводиться при відмові або в логах. +Це насправді навіть не анотація, вона лише визначає заголовок тесту, який виводиться при невдачі або в лог. @skip .[filter] --------------- -Тест пропускається. Це зручно для тимчасової деактивації тесту. +Тест буде пропущений. Корисно для тимчасового виключення тестів. @phpVersion .[filter] --------------------- -Тест буде пропущено, якщо він не запущений відповідною версією PHP. Ми пишемо анотацію як `@phpVersion [operator] version`. Ми можемо не вказувати оператор, за замовчуванням це `>=`. Приклади: +Тест буде пропущений, якщо він не запущений з відповідною версією PHP. Анотацію записуємо як `@phpVersion [оператор] версія`. Оператор можна пропустити, стандартний — `>=`. Приклади: ```php /** @@ -46,7 +46,7 @@ Test .[filter] @phpExtension .[filter] ----------------------- -Тест буде пропущено, якщо всі зазначені розширення PHP не завантажені. Кілька розширень можуть бути записані в одній анотації, або ми можемо використовувати анотацію кілька разів. +Тест буде пропущений, якщо не завантажені всі вказані розширення PHP. Більше розширень можна вказати в одній анотації або використати її кілька разів. ```php /** @@ -58,9 +58,9 @@ Test .[filter] @dataProvider .[filter] ----------------------- -Ця анотація підходить, коли ми хочемо запустити тест кілька разів, але з різними даними. (Не плутати з однойменною анотацією для [TestCase |TestCase#dataProvider]). +Якщо ми хочемо запустити тестовий файл кілька разів, але з різними вхідними даними, ця анотація буде корисною. (Не плутайте з однойменною анотацією для [TestCase |TestCase#dataProvider].) -Ми пишемо анотацію як `@dataProvider file.ini`. Шлях до файлу INI є відносним до файлу тесту. Тест запускається стільки разів, скільки секцій міститься в INI-файлі. Припустимо, що INI-файл `databases.ini`: +Записуємо як `@dataProvider file.ini`, шлях до файлу береться відносно файлу з тестом. Тест буде запущений стільки разів, скільки секцій у INI-файлі. Припустимо INI-файл `databases.ini`: ```ini [mysql] @@ -77,7 +77,7 @@ password = ****** dsn = "sqlite::memory:" ``` -і файл `database.phpt` в одному каталозі: +і в тій самій директорії тест `database.phpt`: ```php /** @@ -87,11 +87,11 @@ dsn = "sqlite::memory:" $args = Tester\Environment::loadData(); ``` -Тест виконується тричі, і `$args` міститиме значення із секцій `mysql`, `postgresql` або `sqlite`. +Тест буде запущений тричі, і `$args` завжди міститиме значення з секції `mysql`, `postgresql` або `sqlite`. -Є ще один варіант, коли ми пишемо анотації зі знаком питання, як `@dataProvider? file.ini`. У цьому випадку тест буде пропущено, якщо INI-файл не існує. +Існує ще варіант, коли анотацію запишемо зі знаком питання як `@dataProvider? file.ini`. У цьому випадку тест буде пропущений, якщо INI-файл не існує. -Можливості анотацій ще не всі згадані. Ми можемо написати умови після INI-файлу. Тест запускається для заданої секції тільки в тому випадку, якщо всі умови збігаються. Давайте розширимо INI-файл: +Цим можливості анотації не обмежуються. За назвою INI-файлу ми можемо вказати умови, за яких буде запущений тест для даної секції. Розширимо INI-файл: ```ini [mysql] @@ -113,7 +113,7 @@ password = ****** dsn = "sqlite::memory:" ``` -і будемо використовувати анотацію з умовою: +і використаємо анотацію з умовою: ```php /** @@ -121,9 +121,9 @@ dsn = "sqlite::memory:" */ ``` -Тест виконується тільки один раз для секції `postgresql 9.1`. Інші розділи не відповідають умовам. +Тест буде запущений лише один раз, і то для секції `postgresql 9.1`. Інші секції фільтром умови не пройдуть. -Аналогічно, ми можемо передати шлях до PHP-скрипту замість INI. Він повинен повертати масив або Traversable. Файл `databases.php`: +Аналогічно, замість INI-файлу ми можемо посилатися на PHP-скрипт. Він повинен повернути масив або Traversable. Файл `databases.php`: ```php return [ @@ -142,29 +142,29 @@ return [ @multiple .[filter] ------------------- -Запишемо це як `@multiple N`, де `N` - ціле число. Тест виконується рівно N разів. +Записуємо як `@multiple N`, де `N` — ціле число. Тест буде запущений рівно N разів. @testCase .[filter] ------------------- -Анотація не має параметрів. Ми використовуємо її, коли пишемо тест у вигляді класів [TestCase |TestCase]. У цьому випадку програма запуску тестів командного рядка запускатиме окремі методи в окремих процесах і паралельно в декількох потоках. Це може значно прискорити весь процес тестування. +Анотація не має параметрів. Використовуємо її, якщо тести пишемо як класи [TestCase |TestCase]. У цьому випадку запускач тестів з командного рядка запускатиме окремі методи в самостійних процесах і паралельно в кількох потоках. Це може значно прискорити весь процес тестування. @exitCode .[filter] ------------------- -Ми пишемо його як `@exitCode N`, де `N` is the exit code of the test. For example if `exit(10)` викликається в тесті, ми пишемо анотацію як `@exitCode 10`. Вважається невдачею, якщо тест завершується з іншим кодом. Код виходу 0 (нуль) перевіряється, якщо ми опустимо анотацію +Записуємо як `@exitCode N`, де `N` — код повернення запущеного тесту. Якщо в тесті, наприклад, викликається `exit(10)`, анотацію запишемо як `@exitCode 10`, і якщо тест завершиться з іншим кодом, це вважається невдачею. Якщо анотацію не вказати, перевіряється код повернення 0 (нуль). @httpCode .[filter] ------------------- -Анотація оцінюється тільки в тому випадку, якщо бінарний PHP є CGI. В іншому випадку вона ігнорується. Ми записуємо її як `@httpCode NNN`, де `NNN` - очікуваний HTTP-код. HTTP-код 200 буде перевірено, якщо ми опустимо анотацію. Якщо ми запишемо `NNN` як рядок, що оцінюється як нуль, наприклад, `any`, HTTP-код не перевірятиметься взагалі. +Анотація застосовується лише якщо бінарний файл PHP є CGI. В іншому випадку вона ігнорується. Записуємо як `@httpCode NNN`, де `NNN` — очікуваний HTTP-код. Якщо анотацію не вказати, перевіряється HTTP-код 200. Якщо `NNN` запишемо як рядок, що обчислюється в нуль, наприклад `any`, HTTP-код не перевіряється. -@outputMatch a @outputMatchFile .[filter] ------------------------------------------ -Поведінка анотацій відповідає твердженням `Assert::match()` і `Assert::matchFile()`. Але в стандартному виведенні тесту зустрічається патерн. Відповідний випадок використання - коли ми припускаємо, що тест завершиться фатальною помилкою, і нам потрібно перевірити його виведення. +@outputMatch та @outputMatchFile .[filter] +------------------------------------------ +Функція анотацій збігається з assertion `Assert::match()` та `Assert::matchFile()`. Патерн (шаблон) шукається в тексті, який тест надіслав на свій стандартний вивід. Застосування знайде, якщо ми припускаємо, що тест завершиться фатальною помилкою, і нам потрібно перевірити його вивід. @phpIni .[filter] ----------------- -Встановлює значення конфігурації INI для тесту. Наприклад, ми записуємо його як `@phpIni precision=20` і він працює так само, як якщо б ми передали значення з командного рядка параметром `-d precision=20`. +Для тесту встановлює конфігураційні значення INI. Записуємо, наприклад, як `@phpIni precision=20` і працює так само, якби ми вказали значення з командного рядка через параметр `-d precision=20`. diff --git a/tester/uk/testcase.texy b/tester/uk/testcase.texy index 27fb797e9e..744bd8030f 100644 --- a/tester/uk/testcase.texy +++ b/tester/uk/testcase.texy @@ -2,9 +2,9 @@ TestCase ******** .[perex] -У простих тестах твердження можуть слідувати одне за одним. Але іноді корисно укласти твердження в тестовий клас і структурувати їх таким чином. +У простих тестах assertion можуть слідувати одна за одною. Іноді, однак, вигідніше запакувати assertion у тестовий клас і таким чином їх структурувати. -Клас має бути нащадком `Tester\TestCase`, і ми говоримо про нього просто як про **testcase**. +Клас має бути нащадком `Tester\TestCase`, і спрощено ми говоримо про нього як про **testcase**. Клас повинен містити тестові методи, що починаються на `test`. Ці методи будуть запущені як тести: ```php use Tester\Assert; @@ -22,11 +22,11 @@ class RectangleTest extends Tester\TestCase } } -# Run testing methods +# Запуск тестових методів (new RectangleTest)->run(); ``` -Ми можемо збагатити тесткейс методами `setUp()` і `tearDown()`. Вони викликаються до/після кожного методу тестування: +Так написаний тест можна далі доповнити методами `setUp()` та `tearDown()`. Вони викликаються перед, відповідно, після кожного тестового методу: ```php use Tester\Assert; @@ -35,12 +35,12 @@ class NextTest extends Tester\TestCase { public function setUp() { - # Preparation + # Підготовка } public function tearDown() { - # Clean-up + # Прибирання } public function testOne() @@ -54,14 +54,14 @@ class NextTest extends Tester\TestCase } } -# Run testing methods +# Запуск тестових методів (new NextTest)->run(); /* -Method Calls Order ------------------- +Порядок виклику методів +----------------------- setUp() testOne() tearDown() @@ -72,9 +72,9 @@ tearDown() */ ``` -Якщо помилка станеться у фазі `setUp()` або `tearDown()`, тест буде провалено. Якщо помилка виникає в методі тестування, то метод `tearDown()` викликається в будь-якому разі, але з пригніченими в ньому помилками. +Якщо виникає помилка на етапі `setUp()` або `tearDown()`, тест загалом зазнає невдачі. Якщо виникає помилка в тестовому методі, незважаючи на це, метод `tearDown()` запуститься, однак з придушенням помилок у ньому. -Ми рекомендуємо писати анотацію [@testCase |test-annotations#testCase] на початку тесту, тоді програма запуску тестів командного рядка запускатиме окремі методи тесткейсу в окремих процесах і паралельно в декількох потоках. Це може значно прискорити весь процес тестування. +Рекомендуємо на початок тесту написати анотацію [@testCase |test-annotations#testCase], тоді запускач тестів з командного рядка запускатиме окремі методи testcase в самостійних процесах і паралельно в кількох потоках. Це може значно прискорити весь процес тестування. /--php width = $width; $this->height = $height; @@ -53,7 +52,7 @@ class Rectangle } ``` -І створимо для нього тест. Ім'я файлу тесту має відповідати масці `*Test.php` або `*.phpt`, ми виберемо варіант `RectangleTest.php`: +І створимо для нього тест. Назва файлу з тестом має відповідати масці `*Test.php` або `*.phpt`, виберемо, наприклад, варіант `RectangleTest.php`: ```php .{file:tests/RectangleTest.php} @@ -62,31 +61,31 @@ use Tester\Assert; require __DIR__ . '/bootstrap.php'; -// загальний довгастий +// загальний прямокутник $rect = new Rectangle(10, 20); -Assert::same(200.0, $rect->getArea()); # ми перевіримо очікувані результати +Assert::same(200.0, $rect->getArea()); # перевіримо очікувані результати Assert::false($rect->isSquare()); ``` -Як бачите, [методи твердження |Assertions], такі як `Assert::same()`, використовуються для твердження того, що фактичне значення збігається з очікуваним. +Як бачите, так звані [методи assertion|assertions] як `Assert::same()` використовуються для підтвердження того, що фактичне значення відповідає очікуваному значенню. -Останній крок - створення файлу `bootstrap.php`. Він містить загальний код для всіх тестів. Наприклад, автозавантаження класів, конфігурація оточення, створення тимчасової директорії, хелпери тощо. Кожен тест завантажує бутстрап і приділяє увагу лише тестуванню. Бутстрап може виглядати таким чином: +Залишився останній крок — це файл `bootstrap.php`. Він містить код, спільний для всіх тестів, наприклад, автозавантаження класів, конфігурацію середовища, створення тимчасової директорії, допоміжні функції тощо. Всі тести завантажують bootstrap і далі займаються лише тестуванням. Bootstrap може виглядати наступним чином: ```php .{file:tests/bootstrap.php} OK \-- -Якщо ми змінимо в тесті твердження на false `Assert::same(123, $rect->getArea());`, відбудеться наступне: +Якщо ми змінимо в тесті твердження на хибне `Assert::same(123, $rect->getArea());`, станеться ось що: /--pre .[terminal] $ php RectangleTest.php @@ -107,35 +106,35 @@ $ php RectangleTest.php \-- -Під час написання тестів корисно відловлювати всі екстремальні ситуації. Наприклад, якщо на вході нуль, від'ємне число, в інших випадках порожній рядок, null тощо. Фактично, це змушує вас думати і вирішувати, як має поводитися код у таких ситуаціях. Потім тести виправляють поведінку. +При написанні тестів добре врахувати всі граничні ситуації. Наприклад, коли входом буде нуль, від'ємне число, в інших випадках, наприклад, порожній рядок, null тощо. Власне, це змушує вас замислитися і вирішити, як має поводитися код у таких ситуаціях. Тести потім фіксують поведінку. -У нашому випадку від'ємне значення має викликати виняток, який ми перевіряємо за допомогою [Assert::exception() |Assertions#Assert-exception]: +У нашому випадку від'ємне значення має викинути виняток, що ми перевіримо за допомогою [Assert::exception() |Assertions#Assert::exception]: ```php .{file:tests/RectangleTest.php} -// ширина не повинна бути від'ємним числом +// ширина не має бути від'ємною Assert::exception( fn() => new Rectangle(-1, 20), InvalidArgumentException::class, - 'Розмір не повинен бути від'ємним', + 'The dimension must not be negative.', ); ``` -І ми додаємо аналогічний тест для висоти. Нарешті, ми перевіряємо, що `isSquare()` повертає `true`, якщо обидва виміри однакові. Спробуйте написати такі тести як вправу. +І аналогічний тест додамо для висоти. Нарешті, протестуємо, що `isSquare()` поверне `true`, якщо обидва розміри однакові. Спробуйте як вправу написати такі тести. -Добре організовані тести .[#toc-well-arranged-tests] -==================================================== +Зрозуміліші тести +================= -Розмір файлу з тестами може збільшитися і швидко стати захаращеним. Тому доцільно групувати окремі тестовані області в окремі функції. +Розмір файлу з тестом може зростати і швидко стати незрозумілим. Тому практично згрупувати окремі тестовані області в самостійні функції. -Спочатку ми покажемо простіший, але елегантніший варіант, використовуючи глобальну функцію `test()`. Tester не створює її автоматично, щоб уникнути колізії, якщо у вашому коді є функція з таким самим ім'ям. Він створюється тільки методом `setupFunctions()`, який ви викликаєте у файлі `bootstrap.php`: +Спочатку покажемо простіший, проте елегантний варіант, а саме за допомогою глобальної функції `test()`. Tester її не створює автоматично, щоб не виникло колізії, якби у вас у коді була функція з такою ж назвою. Її створить лише метод `setupFunctions()`, який викличте у файлі `bootstrap.php`: ```php .{file:tests/bootstrap.php} Tester\Environment::setup(); Tester\Environment::setupFunctions(); ``` -Використовуючи цю функцію, ми можемо красиво розділити тестовий файл на іменовані блоки. Під час виконання функції мітки відображатимуться одна за одною. +За допомогою цієї функції ми можемо гарно розділити тестовий файл на пойменовані частини. При запуску будуть послідовно виводитися описи. ```php .{file:tests/RectangleTest.php} getArea()); Assert::false($rect->isSquare()); }); -test('general square', function () { +test('загальний квадрат', function () { $rect = new Rectangle(5, 5); Assert::same(25.0, $rect->getArea()); Assert::true($rect->isSquare()); }); -test('размеры не должны быть отрицательными', function () { +test('розміри не мають бути від\'ємними', function () { Assert::exception( fn() => new Rectangle(-1, 20), InvalidArgumentException::class, @@ -168,15 +167,15 @@ test('размеры не должны быть отрицательными', f }); ``` -Якщо вам потрібно запустити код до або після кожного тесту, передайте його в `setUp()` або `tearDown()`: +Якщо вам потрібно запустити код перед або після кожного тесту, передайте його функції `setUp()` відповідно `tearDown()`: ```php setUp(function () { - // код ініціалізації для запуску перед кожним test() + // ініціалізаційний код, який запуститься перед кожним test() }); ``` -Второй вариант - объектный. Мы создадим так называемый TestCase, который представляет собой класс, где отдельные единицы представлены методами, имена которых начинаются с test-. +Другий варіант — об'єктний. Створимо так званий TestCase, тобто клас, де окремі частини представляють методи, назви яких починаються на `test-`. ```php .{file:tests/RectangleTest.php} class RectangleTest extends Tester\TestCase @@ -208,25 +207,25 @@ class RectangleTest extends Tester\TestCase } } -// Запуск методів тестування +// Запуск тестових методів (new RectangleTest)->run(); ``` -На этот раз мы использовали аннотацию `@throw` для проверки на исключения. Более подробную информацию смотрите в главе [TestCase]. +Для тестування винятків ми цього разу використали анотацію `@throw`. Більше ви дізнаєтеся в розділі [TestCase |TestCase]. -Функции-помощники .[#toc-helpers-functions] -=========================================== +Допоміжні функції +================= -Nette Tester включает в себя несколько классов и функций, которые могут облегчить вам тестирование, например, помощники для тестирования содержимого HTML-документа, для тестирования функций работы с файлами и так далее. +Nette Tester містить кілька класів та функцій, які можуть полегшити вам, наприклад, тестування вмісту HTML-документа, тестування функцій, що працюють із файлами, тощо. -Их описание вы можете найти на странице [Helpers]. +Їхній опис знайдете на сторінці [Допоміжні класи|helpers]. -Аннотирование и пропуск тестов .[#toc-annotation-and-skipping-tests] -==================================================================== +Анотації та пропуск тестів +========================== -На выполнение тестов могут влиять аннотации в комментарии phpDoc в начале файла. Например, он может выглядеть следующим образом: +Запуск тестів може бути вплинутий анотаціями у вигляді phpDoc коментаря на початку файлу. Він може виглядати, наприклад, так: ```php .{file:tests/RectangleTest.php} /** @@ -235,11 +234,11 @@ Nette Tester включает в себя несколько классов и */ ``` -Аннотации гласят, что тест должен выполняться только с PHP версии 7.2 или выше и при наличии PHP расширений pdo и pdo_pgsql. Эти аннотации контролируются [программой запуска тестов командной строки |running-tests], которая, если условия не выполняются, пропускает тест и помечает его буквой `s` - пропущен. Однако они не имеют никакого эффекта, когда тест выполняется вручную. +Наведені анотації говорять, що тест має бути запущений лише з PHP версії 7.2 або вище і якщо присутні розширення PHP pdo та pdo_pgsql. Цими анотаціями керується [запускач тестів з командного рядка|running-tests], який у випадку, якщо умови не виконані, тест пропустить і у виводі позначить літерою `s` - skipped. Однак при ручному запуску тесту вони не мають жодного впливу. -Описание аннотаций приведено в разделе [Аннотации тестов |Test Annotations]. +Опис анотацій знайдете на сторінці [Анотації тестів|test-annotations]. -Тест также может быть пропущен на основании собственного условия с помощью `Environment::skip()`. Например, мы пропустим этот тест на Windows: +Тест можна пропустити також на основі виконання власної умови за допомогою `Environment::skip()`. Наприклад, ця пропустить тести на Windows: ```php if (defined('PHP_WINDOWS_VERSION_BUILD')) { @@ -248,43 +247,43 @@ if (defined('PHP_WINDOWS_VERSION_BUILD')) { ``` -Структура каталогов .[#toc-directory-structure] -=============================================== +Структура директорій +==================== -Для немного больших библиотек или проектов мы рекомендуем разделить тестовый каталог на подкаталоги в соответствии с пространством имен тестируемого класса: +Рекомендуємо для трохи більших бібліотек або проєктів розділити директорію з тестами ще на піддиректорії за простором імен тестованого класу: ``` └── tests/ ├── NamespaceOne/ - │ ├── MyClass.getUsers.phpt - │ ├── MyClass.setUsers.phpt - │ └── ... + │ ├── MyClass.getUsers.phpt + │ ├── MyClass.setUsers.phpt + │ └── ... │ ├── NamespaceTwo/ - │ ├── MyClass.creating.phpt - │ ├── MyClass.dropping.phpt - │ └── ... + │ ├── MyClass.creating.phpt + │ ├── MyClass.dropping.phpt + │ └── ... │ ├── bootstrap.php └── ... ``` -Ви зможете запускати тести з одного простору імен тобто підкаталогу: +Ви зможете запускати тести з єдиного простору імен, тобто піддиректорії: /--pre .[terminal] tester tests/NamespaceOne \-- -Edge Cases .[#toc-edge-cases] -============================= +Спеціальні ситуації +=================== -Тест, який не викликає жодного методу твердження, є підозрілим і буде оцінено як помилковий: +Тест, який не викликає жодного методу assertion, є підозрілим і оцінюється як помилковий: /--pre .[terminal] Error: This test forgets to execute an assertion. \-- -Якщо тест без виклику тверджень дійсно має вважатися коректним, викличте, наприклад, `Assert::true(true)`. +Якщо дійсно тест без виклику assertion має вважатися валідним, викличте, наприклад, `Assert::true(true)`. -Також підступним може бути використання `exit()` і `die()` для завершення тесту з повідомленням про помилку. Наприклад, `exit('Error in connection')` завершує тест із кодом виходу 0, який сигналізує про успіх. Використовуйте `Assert::fail('Error in connection')`. +Також може бути підступним використовувати `exit()` та `die()` для завершення тесту з повідомленням про помилку. Наприклад, `exit('Error in connection')` завершить тест з кодом повернення 0, що сигналізує про успіх. Використовуйте `Assert::fail('Error in connection')`. diff --git a/texy/cs/@home.texy b/texy/cs/@home.texy new file mode 100644 index 0000000000..cf88e684e4 --- /dev/null +++ b/texy/cs/@home.texy @@ -0,0 +1,120 @@ +Texy! je sexy! +************** + +.[perex] +Texy je **výkonný a bezpečný markup procesor** pro PHP, který převádí jednoduchý text do validního HTML. Na rozdíl od jiných markup jazyků není Texy jen další variantou Markdown – je to **plně konfigurovatelný systém**, který můžete přizpůsobit prakticky jakékoliv syntaxi. + + +Proč Texy? +========== + + +Bezpečnost na prvním místě +-------------------------- + +Texy je navrženo s důrazem na bezpečnost. Automaticky **chrání před XSS útoky**, validuje URL adresy a filtruje nebezpečné HTML značky. Vestavěný `safeMode()` je ideální pro zpracování uživatelského obsahu v komentářích nebo na fórech. + +```php +Texy\Configurator::safeMode($texy); +// Nyní je Texy bezpečné pro obsah od uživatelů +``` + + +Konfigurovatelnost bez kompromisů +--------------------------------- + +Chcete používat Markdown syntaxi? Nebo potřebujete úplně vlastní markup? **Texy to zvládne.** Můžete: + +- Vypnout nebo zapnout libovolné části syntaxe +- Změnit výchozí chování pomocí handlerů +- Přidat zcela vlastní syntaktické prvky +- Nakonfigurovat Texy tak, aby zpracovávalo Markdown nebo jakýkoliv jiný formát + +```php +$texy = new Texy; +$texy->allowed['image'] = false; // vypnout obrázky +$texy->allowed['phrase/strong'] = false; // vypnout tučné písmo +``` + + +České typografické speciality +----------------------------- + +Texy **dokonale rozumí češtině**. Automaticky: + +- Vkládá **pevné mezery** za jednopísmenné předložky a spojky: v autě, u okna, s kamarádem +- Rozděluje **dlouhá slova** podle slabik: nejneobhospodařovávatelnějšími +- Používá správné **typografické uvozovky**: „dvojité" a ‚jednoduché' +- Zaměňuje **spojovník za pomlčku**: 10–15 vs. česko-slovenský +- Přidává **nezalomitelné mezery** u telefonních čísel: +420 776 552 046 + + +Validní a wellformed HTML +------------------------- + +Texy generuje **vždy validní HTML5 kód**. Automaticky opravuje chybně vnořené značky, uzavírá nezavřené elementy a dbá na správnou strukturu dokumentu. Výstup je nejen validní, ale i **pěkně naformátovaný** s odsazením. + + +Co je Texy? +=========== + +Texy je **obecný procesor markup textu**. To znamená, že má sice svou výchozí syntaxi (podobnou Markdown, ale mnohem bohatší), ale můžete ji kompletně změnit nebo rozšířit. + +**Není to jen parser** – Texy je komplexní systém s modulární architekturou, kde každý modul zpracovává konkrétní část syntaxe (nadpisy, odkazy, obrázky, tabulky...). Díky systému handlerů můžete zasáhnout do libovolného bodu zpracování a změnit výsledek podle svých potřeb. + + +Texy vs. Markdown +================= + +Základní syntaxe je podobná, ale Texy nabízí mnohem více: + +|--------------------------- +| Funkce | Markdown | Texy +|--------------------------- +| Tučné písmo | `**text**` | `**text**` +| Kurzíva | `*text*` nebo `_text_` | `*text*` nebo `//text//` +| Nadpisy | `# Nadpis` | `# Nadpis` nebo podtržení +| Obrázky | `![alt](url)` | `[* url *]` +| Tabulky | omezené | plná podpora včetně sloučení +| Modifikátory | ne | ano – `.{color:red}[class]` +| Typografie | ne | ano – uvozovky, pomlčky, mezery +| Dělení slov | ne | ano – podle slabik +| Konfigurovatelnost | omezená | úplná – vlastní syntaxe +| Bezpečnost | závisí na impl. | vestavěná (safeMode) + +**Příklad rozdílů:** + +```texy +Markdown: +![Obrázek](image.jpg) + +Texy: +[* image.jpg 300x200 .(Popisek obrázku)[photo] <] +``` + +Texy umožňuje definovat rozměry, třídy, zarovnání a mnoho dalšího přímo v syntaxi. + + +Kdy použít Texy? +================ + +Texy je ideální pro: + +**CMS systémy** Potřebujete bezpečně zpracovávat obsah od editorů? Texy nabízí granulární kontrolu nad tím, co mohou uživatelé použít. + +**Blogy a dokumentace** Bohatá syntaxe pro tabulky, obrázky s popiskami, typografii a kód s syntax highlightingem. + +**Komentáře a diskuzní fóra** SafeMode zajistí, že uživatelé nemohou vložit nebezpečný kód, ale zároveň mají k dispozici formátování textu. + +**Projekty s vlastními požadavky** Potřebujete embed YouTube videí? Speciální syntax pro vaše makra? Vlastní markup jazyk? S Texy to vytvoříte snadno. + + +Historie +======== + +Texy vytvořil David Grudl před **20 lety** v roce 2004 jako jeden z prvních markup procesorů pro PHP. Původně bylo vyvinuto pro **PHP 4**, ale během své dlouhé historie prošlo mnoha aktualizacemi a dnes plně využívá všech možností **PHP 8**. + +Přes dvě dekády aktivního vývoje znamenají **vyzkoušenou a stabilní** knihovnu, které důvěřují stovky projektů. Texy je dnes **mature řešení** s velkou historií, ale stále aktivně udržované a moderní. + + +{{maintitle: Texy – formátovač textů pro PHP}} diff --git a/texy/cs/@menu.texy b/texy/cs/@menu.texy new file mode 100644 index 0000000000..bc703bedfb --- /dev/null +++ b/texy/cs/@menu.texy @@ -0,0 +1,7 @@ +- [úvod | @home] +- [syntaxe | syntax] +- [pro programátory | develop] +- "blog .[link-external]":https://phpfashion.com/category/texy +- "hřiště .[link-external]":https://fiddle.nette.org/texy/ +- "API .[link-external]":https://api.nette.org/texy/ +- "GitHub .[link-external]":https://github.com/dg/texy diff --git a/texy/cs/@meta.texy b/texy/cs/@meta.texy new file mode 100644 index 0000000000..41e2da6970 --- /dev/null +++ b/texy/cs/@meta.texy @@ -0,0 +1 @@ +{{sitename: Texy Dokumentace}} diff --git a/texy/cs/architecture.texy b/texy/cs/architecture.texy new file mode 100644 index 0000000000..bf213b08f0 --- /dev/null +++ b/texy/cs/architecture.texy @@ -0,0 +1,425 @@ +Architektura a principy +####################### + +.[perex] +Texy je nástroj pro převod textu napsaného ve vlastním markup jazyce do HTML. Na rozdíl od jednoduchých převodníků, které text zpracovávají lineárně pomocí série náhrad, používá Texy sofistikovaný systém založený na parsování, modulární architektuře a postupném budování DOM stromu. + +Základní tok zpracování probíhá ve čtyřech hlavních fázích: + +1. Předzpracování textu - normalizace, úprava mezer a tabulátorů, volání notification handlerů pro přípravu +2. Parsování - rozpoznání syntaxí pomocí regulárních výrazů a postupné budování DOM stromu +3. Post-processing - typografické úpravy, zpracování dlouhých slov, wellforming HTML +4. Finální sestavení - konverze DOM stromu do HTML řetězce + +Klíčovým rozdílem oproti naivním přístupům je oddělení fáze rozpoznávání syntaxí od jejich zpracování. Parser nejprve identifikuje, kde se v textu nachází která syntaktická konstrukce, a teprve poté předává nalezené části jednotlivým modulům ke zpracování. To umožňuje vnořování syntaxí a jejich postupné rozbalování. + +*Poznámka: všechny třídy se nacházejí ve jmenném prostoru `Texy`, takže pokud dokument zmiňuje například třídu `HtmlElement`, její plný název je `Texy\HtmlElement`. Moduly jsou ve jmenném prostoru `Texy\Modules`* + + +Klíčové komponenty +================== + +Architektura Texy se skládá z několika hlavních komponent, z nichž každá má jasně vymezenou zodpovědnost: + +Třída Texy funguje jako centrální orchestrátor celého systému. Obsahuje odkazy na všechny moduly, spravuje registrované syntaxe a handlery, udržuje stav zpracování a koordinuje jednotlivé fáze konverze. Je to jediné místo, kde se jednotlivé komponenty propojují. + +**[#Moduly]** představují funkční jednotky zodpovědné za konkrétní oblasti markup jazyka. Každý modul při své konstrukci registruje syntaxe, které rozpoznává, a element handlery, které je zpracovávají. Například PhraseModule se stará o inline formátování jako tučný nebo kurzívou psaný text, zatímco TableModule zpracovává tabulky. Moduly jsou navrženy jako samostatné, znovupoužitelné jednotky s vlastní konfigurací přístupnou přes veřejné properties. + +**[#Parsery]** existují ve dvou variantách podle typu zpracovávaného obsahu. BlockParser zpracovává blokové struktury jako odstavce, nadpisy, seznamy nebo tabulky. Prochází text po řádcích, hledá začátky blokových konstrukcí a předává je *syntax handlerům*. LineParser se stará o inline syntaxe uvnitř řádků - odkazy, obrázky, formátování textu. Na rozdíl od BlockParser umožňuje vnořování syntaxí a jejich postupné rozbalování. + + +Základní terminologie +===================== + +Pro správné pochopení fungování Texy je nutné rozlišovat několik klíčových pojmů, které se v dokumentaci často objevují. + +**Syntax** označuje pojmenovanou syntaktickou konstrukci markup jazyka. Každá syntax má jedinečný název, například `phrase/strong` pro tučný text nebo `image` pro obrázky. Název syntaxe se používá pro její zapnutí či vypnutí v poli `Texy::$allowed` a předává se jako parametr do syntax handlerů pro rozlišení, která konkrétní syntax byla nalezena. + +**Pattern** je regulární výraz, který definuje, jak syntax vypadá v textu. Pattern je implementační detail syntaxe - autor syntaxe musí napsat regex, který ji rozpozná, ale z pohledu uživatele Texy je podstatnější název syntaxe a její význam. Jeden modul typicky registruje více syntaxí s různými patterny. + +**Syntax handler** je funkce volaná parserem ve chvíli, kdy najde výskyt syntaxe v textu. Dostává nalezený text a vrací `HtmlElement` nebo řetězec, který se vloží na původní místo. Syntax handler je místem, kde se rozhoduje, co se s nalezenou syntaxí stane - typicky vyvolá element handler pro vlastní zpracování. + +**Element** je prvek, pro který se generuje HTML reprezentace. Například `image` je element pro obrázky, `linkURL` pro odkazy, `phrase` pro inline formátování. Každý element má svůj výchozí element handler, který se stará o standardní zpracování. + +**Element handler** je funkce registrovaná pro určitý typ prvků a volaná přes systém HandlerInvocation. Charakteristické je použití metody `proceed()`, která umožňuje delegovat zpracování na další handler v řetězu nebo na výchozí handler modulu. Element handlery slouží k modifikaci nebo nahrazení výchozího chování. + +**Notification handler** je funkce volaná pro notifikaci o určité události. Na rozdíl od element handlerů nevrací žádnou hodnotu a nemůže ovlivnit výsledek zpracování. Používá se pro přípravu dat, logování nebo modifikace již vytvořeného DOM stromu. + +Rozdíl mezi jednotlivými handlery je klíčový pro pochopení architektury. Syntax handler je těsně svázán s parserem a konkrétním patternem - řeší otázku *co dělat, když parser najde tento pattern*. Element handlery jsou na vyšší úrovni abstrakce - řeší otázku *jak zpracovat tento typ prvku*, bez ohledu na to, která konkrétní syntax ji vytvořila. + + +Celkový tok zpracování +====================== + +Když Texy dostane vstupní text, projde následujícím procesem zpracování. + +V předzpracování dochází k normalizaci textu. Koncové značky řádků se sjednotí na Unix formát, mezery se standardizují a tabulátory se případně nahradí mezerami. Následně se vyvolají *notification handlery* registrované pro událost `beforeParse`. Tyto handlery mohou provést přípravu dat, například načíst definice referencí nebo upravit konfiguraci podle obsahu textu. + +Samotné parsování začíná vytvořením kořenového `HtmlElement`, který reprezentuje dokument. Texy pak rozhodne, zda text zpracovat jako jeden řádek nebo jako kompletní dokument s blokovými strukturami. V případě blokového zpracování se vytvoří BlockParser, který postupně prochází text a hledá jednotlivé blokové konstrukce. + +LineParser pracuje jinak než BlockParser. Neprochází text lineárně, ale postupně hledá nejbližší výskyt jakékoliv registrované syntaxe. Když nějakou najde, zavolá příslušný syntax handler, který vytvoří odpovídající HTML element. Tento element se pomocí speciálního maskování vloží zpět do textu a parser pokračuje dál. Díky tomu může najít a zpracovat syntaxe vnořené uvnitř již zpracovaných konstrukcí. + +Po dokončení parsování vznikne kompletní DOM strom reprezentující strukturu dokumentu. Texy vyvolá notification handlery pro událost `afterParse`, které mohou provést závěrečné úpravy stromu, například doplnit identifikátory nadpisů nebo sestavit obsah. + +Post-processing probíhá během konverze DOM stromu na HTML řetězec. Každý element se rekurzivně převádí na HTML kód, přičemž se aplikují typografické úpravy jako nahrazení uvozovek, pomlček nebo vkládání nezlomitelných mezer. Dále se provádí wellforming HTML - automatické uzavírání tagů, oprava špatně vnořených elementů, formátování a odsazování kódu. + +Finální fází je dekódování všech maskovaných částí zpět na HTML tagy, odstranění pomocných značek a sestavení výsledného HTML řetězce. + + +Systém syntaxí +************** + +Syntax v terminologii Texy představuje pojmenovanou syntaktickou konstrukci markup jazyka. Je to abstraktní koncept spojující několik prvků: unikátní název, regulární výraz pro rozpoznání a způsob zpracování. Název syntaxe slouží jako identifikátor v celém systému - používá se v poli `Texy::$allowed` pro zapnutí či vypnutí, předává se do handlerů pro rozlišení typu konstrukce a objevuje se v dokumentaci a konfiguračních souborech. + +Jmenné konvence syntaxí následují dva hlavní vzory. Jednodušší syntaxe mají jednoslovný název odpovídající jejich účelu, například `image`, `table` nebo `script`. Složitější oblasti používají hierarchické pojmenování se lomítkem, například `phrase/strong`, `phrase/em` nebo `link/reference`. Lomítko slouží k logickému seskupení souvisejících syntaxí a usnadňuje hromadné operace s nimi. + + +Line syntaxe +============ + +Line syntaxe slouží k rozpoznávání inline prvků uvnitř řádků textu. Typicky jde o formátování jako tučný nebo kurzívou psaný text, odkazy, obrázky nebo inline kód. Charakteristické pro line syntaxe je, že mohou být vnořené do sebe a parser je postupně rozbaluje. + +Registrace line syntaxe probíhá voláním `Texy::registerLinePattern()` s několika parametry. První je syntax handler, tedy callback volaný při nálezu. Druhý parametr je regulární výraz definující podobu syntaxe v textu. Třetí parametr je název syntaxe používaný v celém systému. Volitelný čtvrtý parametr je další regex pro test, zda má smysl pattern vůbec hledat - používá se pro optimalizaci, aby se nespouštěl komplexní pattern na textu, který určitě nemůže matchnout. + +Pattern jako regulární výraz musí splňovat určitá pravidla. Nesmí být kotvený na začátek textu, protože se hledá kdekoliv v řádku. Měl by být co nejkonkrétnější, aby nedošlo k falešným matchům. + +Inline syntaxe uvnitř řádků textu zpracovává [#LineParser]. Když najde match, zavolá příslušný syntax handler. Ten dostává tři parametry. První je instance LineParser, která poskytuje přístup k Texy objektu a dalším informacím o kontextu. Druhý parametr je pole s výsledky regex matche včetně podvýrazů. Třetí parametr je název syntaxe, který je užitečný, když stejný callback obsluhuje více syntaxí. Handler musí vrátit buď `HtmlElement`, nebo řetězec, nebo null pokud zpracování odmítá. + + +Block syntaxe +============= + +Block syntaxe rozpoznávají víceřádkové blokové konstrukce jako nadpisy, seznamy, tabulky, citace nebo speciální bloky. Na rozdíl od line syntaxí se block syntaxe nikdy nepřekrývají - každý řádek textu patří maximálně do jedné blokové konstrukce. + +Registrace block syntaxe používá `Texy::registerBlockPattern()` se třemi parametry. Syntax handler, regulární výraz a název syntaxe. Pattern jako regulární výraz musí splňovat určitá pravidla. Musí matchnout od začátku řádku a často obsahuje kotvu pro konec řádku. BlockParser automaticky přidává modifikátory Am, takže pattern by je neměl obsahovat. + +Block syntaxe uvnitř dokumentu zpracovává [#BlockParser]. Když najde match, zavolá příslušný syntax handler. Ten dostává podobné parametry jako u line syntaxí - BlockParser instanci, pole s matchem a název syntaxe. Vrací `HtmlElement` reprezentující celý zpracovaný blok, nebo null při odmítnutí zpracování. + + +Zapnutí a vypnutí syntaxe +========================= + +Pole `Texy::$allowed` poskytuje jemně granulární kontrolu nad tím, které syntaxe jsou v Texy aktivní. Je to jednoduchý, ale mocný mechanismus pro konfiguraci chování bez nutnosti měnit kód modulů. Když zakážete syntaxi `phrase/strong` tímto nastavením, parser přestane hledat konstrukci tučného textu: + +```php +$texy->allowed['phrase/strong'] = false; +``` + +Kontrola probíhá jednou při začátku parsování, takže dynamická změna `$allowed` během zpracování nemá efekt. + +Při konstrukci modulů se pro většinu syntaxí nastavuje výchozí hodnota `$allowed`. Některé syntaxe jsou ve výchozím stavu zapnuté, protože tvoří základ markup jazyka. Jiné jsou vypnuté, protože jsou pokročilé nebo potenciálně nebezpečné. Například emotikony jsou vypnuté, protože ne každý dokument je potřebuje, zatímco základní formátování je zapnuté. + +Bezpečný režim je situace, kdy zpracováváte nedůvěryhodný vstup, například komentáře od uživatelů. Chcete povolit základní formátování, ale zakázat obrázky, skripty nebo HTML tagy. `Texy\Configurator::safeMode()` nastaví `$allowed` pro bezpečnou kombinaci syntaxí. Typicky zakáže image, figure, script a HTML tagy, ale ponechá odkazy a formátování. + + +Parsery +******* + + +Syntax handler +============== + +Jak jsme si říkali v předchozí části, LineParser nebo BlockParser prochází text a hledají všechny registrované patterny. Když najde match, zavolá příslušný syntax handler a předá mu informace o nálezu - zejména pole s výsledky regex matche. + +Syntax handler analyzuje nalezený text a připravuje data pro zpracování. Může extrahovat části textu z regex skupin, vytvořit pomocné objekty jako `Link` nebo `Image`, parsovat modifikátory. Rozhoduje také, jaký element handler vyvolat. Zavolá `Texy::invokeAroundHandlers()` s názvem elementu a připravenými parametry. Tím začne jejich vykonávání. Vrácený výsledek se dostává zpět do syntax handleru, který ho vrátí parseru. + + +Element handler +=============== + +Element handlery implementují vzor chain of responsibility, který umožňuje složit výsledné chování z více vrstev. + +Registrace element handleru probíhá voláním `Texy::addHandler()` se dvěma parametry - názvem elementu a funkcí handleru. Jeden název elementu může mít zaregistrováno více handlerů, které se pak vykonávají v pořadí od posledně registrovaného k prvnímu. + +Název elementu identifikuje, o jaký typ zpracování jde, například `phrase` pro formátování, `image` pro obrázky nebo `link` pro odkazy (pozor: jde o něco jiného než názvy syntaxe). Někdy se používají složené názvy jako `linkReference` nebo `linkEmail` pro rozlišení různých druhů odkazů. Názvy jsou obecnější než názvy syntaxí - zatímco syntaxe `phrase/strong` je specifická konstrukce, element `phrase` pokrývá všechny druhy inline formátování. + +Vyvolání element handleru používá metodu `Texy::invokeAroundHandlers()`. Tato metoda dostává název prvku, instanci parseru a pole parametrů. Vytvoří HandlerInvocation objekt, který zapouzdřuje celý řetěz zaregistrovaných handlerů. První handler v řetězu dostane kontrolu a rozhoduje, zda zavolat `HandlerInvocation::proceed()` pro pokračování na další handler, nebo vrátit vlastní výsledek. + +HandlerInvocation objekt je klíčem k pochopení, jak řetězení funguje. Obsahuje zásobník všech handlerů pro daný prvek a aktuální pozici v tomto zásobníku. Když handler zavolá `proceed()`, HandlerInvocation posune pozici o jedno místo zpět v zásobníku a zavolá další handler. Pokud handler zavolá `proceed()` s modifikovanými parametry, tyto nové parametry se předají všem následujícím handlerům. Pokud handler vůbec nezavolá `proceed()`, řetěz se přeruší a jeho návratová hodnota se stane výsledkem celého zpracování. + +Pořadí vykonávání handlerů je od posledně zaregistrovaného k prvnímu. To znamená, že uživatelský handler zaregistrovaný dodatečně dostane kontrolu první a může rozhodnout, zda vůbec zavolá výchozí handler modulu. Toto pořadí umožňuje uživatelům přepsat výchozí chování bez nutnosti měnit kód modulu. + +Typické použití element handleru vypadá následovně. Handler zkontroluje vstupní parametry a rozhodne, zda chce zasáhnout do zpracování. Pokud ano, upraví data, zavolá `proceed()` s novými parametry a případně ještě upraví vrácený výsledek. Pokud chce handler úplně nahradit výchozí zpracování, vytvoří vlastní výsledek a vrátí ho bez volání `proceed()`. + + +Notification handler +==================== + +Notification handlery představují jednodušší, jednosměrný komunikační mechanismus. Na rozdíl od element handlerů neslouží k transformaci dat, ale k provedení vedlejších akcí. + +Registrace notification handleru používá stejnou metodu `Texy::addHandler()` jako element handlery. Rozdíl je v tom, jak se handler používá - notification handler nevrací žádnou hodnotu a nemá přístup k HandlerInvocation. První parametr je název události. Používají se popisné názvy jako `beforeParse` a `afterParse` pro globální události okolo parsování, nebo specifičtější jako `afterTable`, `afterList`, `afterBlockquote` pro události po vytvoření konkrétní struktury. Prefix before/after jasně indikuje časování události. + +Vyvolání notification handlerů používá metodu `Texy::invokeHandlers()`. Tato metoda jednoduše zavolá všechny zaregistrované handlery v pořadí a ignoruje jejich návratové hodnoty. Notification handlery dostanou parametry předané při vyvolání, ale nemohou je měnit pro další handlery v řadě. + +Typické použití notification handlerů zahrnuje několik scénářů. Handler pro událost `beforeParse` může načíst definice referencí z textu ještě před začátkem parsování. Handler `afterParse` může projít vytvořený DOM strom a doplnit chybějící atributy nebo sestavit tabulku obsahu. Handlery jako `afterTable` nebo `afterList` umožňují modulům provést závěrečné úpravy vytvořených struktur. + +Důležitý rozdíl oproti element handlerům je v tom, že notification handlery nemohou zabránit dalšímu zpracování. Všechny zaregistrované handlery se vždy vykonají, žádný nemůže přerušit řetěz. To je zamýšlené chování - notification handlery jsou o vedlejších efektech, ne o kontrole toku. + + +LineParser +========== + +LineParser zpracovává inline syntaxe uvnitř řádků textu postupným způsobem, který umožňuje vnořování a složité interakce mezi syntaxemi. + +Základní princip spočívá v hledání prvního výskytu jakékoliv syntaxe. V každé iteraci projde všechny syntaxe a zjistí, která z nich matchuje nejblíže aktuální pozici v textu. Tato syntax *vyhrává* a zpracuje se. Pokud více syntaxí matchuje na stejné pozici, vyhrává ta, která byla registrována dříve - to je priorita podle pořadí registrace. + +Když parser najde nejbližší match, zavolá příslušný syntax handler. Ten vrátí výsledek, který může být `HtmlElement` nebo řetězec. A tímto výsledkem se přepíše nalezený match v textu. + +Poté hledá znovu od aktuální pozice. Tento systém zajišťuje, že parser vždy vidí aktuální stav textu. Když nahradíme match novým textem, který může obsahovat další syntaxe, tyto syntaxe se najdou v příští iteraci. + +Property `$again` na LineParser objektu slouží k jemnému řízení toho, zda by se měla právě matchnutá syntaxe hledat znovu na stejné pozici po zpracování aktuálního matche. Výchozí hodnota je false, která říká: *Na této pozici už nemá smysl hledat tuto stejnou syntaxi. Posuň se dál.* + +Průchod končí, když parser dojde na konec textu nebo když už žádná syntaxe nemá další match. Výsledkem je text, kde všechny rozpoznatelné syntaxe byly zpracovány a nahrazeny výsledky, připravený pro finální konverzi. + + +Vnořování +--------- + +Schopnost zpracovat vnořené syntaxe je jednou z klíčových vlastností LineParser a představuje základní výzvu - jak zabránit tomu, aby již zpracované HTML tagy byly omylem interpretovány jako další syntax k zpracování. + +Když parser zpracovává text obsahující vnořené syntaxe, nejprve najde vnější konstrukci. Například v textu `"odkaz **tučný** text":URL` parser nejprve najde syntaxi pro odkaz s uvozovkami. Pattern pro tuto syntaxi matchuje celý řetězec od první uvozovky po dvojtečku a URL. Syntax handler vytvoří `HtmlElement` pro tag `` a obsah `odkaz **tučný** text` se přidá jako potomek elementu. Tento řetězec vloží zpět do textu a pokračuje v hledání dalších syntaxí (`**tučný**`, který představuje tučný text). + +Ale teď má problém - v textu jsou také HTML značky, která může matchnout jako začátek jiné syntaxe. Parser by začal zpracovávat již hotové HTML tagy jako kdyby byly část původního textu. + +Nechceme, aby parser viděl HTML tagy. Potřebujeme nějaký způsob, jak rozlišit již zpracované části od částí čekajících na zpracování. Metoda `Texy::protect()` řeší tyto problémy elegantním způsobem - nahradí HTML tagy unikátním placeholderem složeným z control characters - speciálních bytů mimo tisknutelné ASCII. + +Když se tedy `HtmlElement` převádí na řetězec (pomocí `toString()`), výsledek nevypadá jako `odkaz **tučný** text`, ale například jako `\x17\x18\x19\x17odkaz **tučný** text\x17\x18\x1A\x17`. + +V textu během parsování tak nejsou nikdy přítomny skutečné HTML tagy. Místo nich jsou pouze placeholdery. Ale vnitřní text zůstává a parser ho normálně vidí a může v něm hledat další syntaxe. To umožňuje postupné vnořování - vnější syntax se zamaskuje, ale její obsah je stále přístupný pro vnitřní syntaxe. + +Na konci zpracování metoda `Texy::unProtect()` projde výsledný HTML řetězec a nahradí všechny placeholdery jejich skutečnými hodnotami. Teprve v tomto okamžiku se do výstupu dostanou skutečné HTML tagy. + + +Úrovně maskování +---------------- + +Různé druhy obsahu používají různé control characters pro své placeholdery, což umožňuje syntaxím selektivně rozhodnout, co mohou obsahovat. + +- `Patterns::CONTENT_MARKUP` označuje běžný HTML markup jako tagy pro formátování nebo odkazy. Je to nejběžnější typ a používá ho většina inline elementů. Placeholder začíná a končí `\x17`. +- `Patterns::CONTENT_REPLACED` označuje obsah, který byl nahrazen něčím jiným, typicky obrázky nebo jiné replaced elementy. Používá `\x16` jako marker. +- `Patterns::CONTENT_TEXTUAL` označuje text, který byl escapován nebo jinak ošetřen, aby se nezpracovával. Používá se pro konstrukce jako code nebo notexy, kde chceme zobrazit původní text včetně markup symbolů, ne jejich interpretaci. +- `Patterns::CONTENT_BLOCK` označuje blokové elementy. Je to nejnižší úroveň v hierarchii. Používá `\x14` jako marker. + +Hierarchie těchto typů není jen konvence, ale má praktický důsledek. Konstantní Patterns::MARK je definována jako `\x14-\x1F`, tedy rozsah pokrývající všechny tyto typy plus rezervu. Syntaxe používají tuto konstantu ve svých patterns pro vyloučení maskovaných částí. + +Různé syntaxe mohou mít různé požadavky na to, co mohou obsahovat za placeholdery. Pattern, který chce vidět pouze čistý text bez jakýchkoliv maskovaných částí, použije vyloučení `[^\x14-\x1F]`. To odmítne všechny placeholdery všech typů. Příkladem je pattern pro obrázky - URL obrázku by neměla obsahovat žádné HTML tagy ani bloky. + +Pattern, který akceptuje nižší úrovně, ale odmítá vyšší, použije užší rozsah. Například `[^\x17-\x1F]` odmítne pouze `CONTENT_MARKUP` a výš, ale akceptuje `CONTENT_BLOCK`, `CONTENT_TEXTUAL` a `CONTENT_REPLACED`. To je užitečné, pokud chceme povolit bloky, ale ne inline markup. Praktickým příkladem je TypographyModule, který provádí typografické úpravy jako nahrazení uvozovek nebo vkládání nezlomitelných mezer. Tyto úpravy by se měly aplikovat na běžný text, ale ne uvnitř bloků kódu nebo preformátovaného textu. + + +Kolize syntaxí +-------------- + +Kolize nastává, když více syntaxí může matchnout na stejné pozici, a systém musí vybrat jednu z nich. + +Typickým příkladem jsou různé délky stejného symbolu. Syntaxe `phrase/strong+em` používá tři hvězdičky pro kombinaci tučného a kurzívy. Syntaxe `phrase/strong` používá dvě hvězdičky pro samotný tučný text. Syntaxe `phrase/em-alt` používá jednu hvězdičku pro kurzívu. Když parser najde text začínající třemi hvězdičkami, všechny tři syntaxe technicky mohou matchnout. + +PhraseModule řeší tuto kolizi registrací syntaxí v pořadí od nejdelší po nejkratší. Nejprve registruje `phrase/strong+em` s patternem pro tři hvězdičky. Pak `phrase/strong` s patternem pro dvě hvězdičky. Nakonec `phrase/em-alt` s patternem pro jednu hvězdičku. Díky tomuto pořadí se při nálezu tří hvězdiček zpracuje nejprve `phrase/strong+em` a kratší syntaxe nedostanou šanci. + +Další příklad jsou odkazy v různých formátech. Syntaxe `phrase/wikilink` používá pattern pro `[text|url]`. Syntaxe `link/reference` používá pattern pro `[ref]`. Oba začínají otevírací hranatou závorkou. Pokud je v textu `[text|url]`, oba patterns technicky mohou začít matchnout. + +Řešením je opět specifičnost patterns. Pattern pro `phrase/wikilink` je specifičtější - vyžaduje svislítko uvnitř závorek. Pokud text obsahuje svislítko, matchne `phrase/wikilink`. Pokud ne, pattern selže a `link/reference` má šanci. Pořadí registrace zde také hraje roli - `phrase/wikilink` by měla být registrována dříve než `link/reference`. + + +BlockParser +=========== + +BlockParser používá fundamentálně odlišný přístup k zpracování, který reflektuje povahu blokových konstrukcí. Základní rozdíl je v absenci prolínání. Zatímco LineParser umožňuje, aby syntaxe byly vnořené do sebe a postupně se rozbalovaly, BlockParser pracuje s předpokladem, že každý blok je samostatná jednotka. Jeden řádek nebo skupina řádků patří k maximálně jednomu bloku. Bloky se nepřekrývají, nekříží a nevnoří na úrovni BlockParser. + +BlockParser začíná vyhledáním všech bloků, respektive jejich začátků. Parser projde všechny registrované block syntaxe a najde všechny jejich výskyty. Pokud více syntaxí matchuje na stejné pozici, použije se pořadí registrace - dříve registrovaná syntaxe má přednost. + + +API pro syntax handler +---------------------- + +BlockParser poskytuje syntax handlerům API pro práci s víceřádkovými strukturami. + +Metoda `BlockParser::moveBackward()` slouží k návratu na předchozí řádky. Přijímá počet řádků, o které se má vrátit. Parser posune svou interní pozici směrem k začátku textu, dokud nepřejde přes specifikovaný počet konců řádků. To umožňuje callbacku začít číst od začátku struktury, i když pattern matchnul až uprostřed nebo na konci. + +Metoda `BlockParser::next()` slouží k čtení dalšího řádku odpovídajícího určitému patternu. Přijímá regex pattern (automaticky přidá modifikátory `Am`) a referenci na proměnnou pro výsledek matche. Pokud další řádek v textu matchuje poskytnutý pattern, metoda naplní výsledek, posune interní pozici za tento řádek a vrátí true. Pokud další řádek nematchuje, metoda vrátí false a pozice se nezmění. + + +Moduly +****** + +Moduly jsou základní organizační jednotkou v architektuře Texy. Každý modul zapouzdřuje kompletní funkcionalitu pro určitou oblast markup jazyka. + +Primární zodpovědností modulu je registrace syntaxí. V konstruktoru modul volá `Texy::registerLinePattern()` nebo `registerBlockPattern()` pro všechny syntaxe, které chce zpracovávat. Tím říká parseru: *Když najdeš tyto patterny, zavolej mě.* Modul tak definuje, které konstrukce v textu rozpoznává. + +Druhá zodpovědnost je implementace element handlerů. Modul registruje handlery pro elementy, které jeho syntaxe vyvolávají. Tyto handlery obsahují logiku pro převod nalezených konstrukcí na HTML elementy. Element handler rozhoduje, jaký element vytvořit, jaké atributy nastavit a jak zpracovat obsah. + +Třetí zodpovědnost je poskytnutí konfigurace. Moduly mají veřejné properties, které umožňují uživatelům Texy upravit chování modulu bez nutnosti měnit jeho kód. Například ImageModule má properties pro nastavení root cesty k obrázkům nebo výchozího alt textu. + +Čtvrtá zodpovědnost je správa stavu specifického pro modul. Například HeadingModule sleduje všechny nalezené nadpisy v poli TOC pro sestavení obsahu. LinkModule spravuje slovník referencí pro odkazy. Tento stav je soukromý pro modul a ostatní části systému k němu nepřistupují přímo. + +Moduly jsou navrženy jako nezávislé jednotky. Každý modul může fungovat samostatně a neměl by záviset na implementačních detailech jiných modulů. Komunikace mezi moduly probíhá přes sdílené objekty jako `Link` nebo `Image`, ne přes přímé volání metod. + + +Struktura typického modulu +========================== + +Většina modulů v Texy následuje podobnou strukturu, která reflektuje jejich úlohu v systému. + +Modul dědí od základní třídy Module, která poskytuje přístup k Texy objektu přes protected property `$texy`. Konstruktor modulu přijímá instanci Texy a uloží si ji. To umožňuje modulu přistupovat ke konfiguraci a volat metody na Texy objektu. + +V konstruktoru probíhá veškerá inicializace. Modul nastaví výchozí hodnoty konfiguračních properties, případně nastaví výchozí hodnoty v poli `Texy::$allowed` pro své syntaxe. Pak registruje své syntaxe voláním `registerLinePattern()` nebo `registerBlockPattern()`. Každá registrace spojuje pattern, syntax handler a název syntaxe. Nakonec modul registruje své element handlery voláním `addHandler()`. + +Syntax handlery jsou metody modulu, které parser volá při nálezu syntaxe. Tyto metody typicky extrahují části z regex matche, vytvářejí pomocné objekty a vyvolávají element handlery. Syntax handler rozhoduje, jaký element handler vyvolat a jaké parametry předat. + +Element handlery jsou metody implementující skutečné zpracování. Dostávají HandlerInvocation objekt jako první parametr, následovaný parametry specifickými pro daný element. Element handler vytváří `HtmlElement`, aplikuje modifikátory, zpracovává obsah a vrací výsledek. Je to místo, kde se rozhoduje o finální podobě HTML. + +Veřejné properties slouží jako rozhraní pro konfiguraci. Uživatel Texy může nastavit tyto properties pro přizpůsobení chování modulu. Properties jsou typicky primitivní typy nebo pole, ne složité objekty, aby konfigurace byla jednoduchá. + + +Přehled klíčových modulů +======================== + +Standardní distribuce Texy obsahuje několik modulů pokrývajících různé aspekty markup jazyka. + +- **PhraseModule** zpracovává inline formátování textu. Registruje syntaxe pro tučný text, kurzívu, podtržení, horní a dolní index, kód a další. Všechny tyto syntaxe vyvolávají společný handler pro element `phrase` a handler rozlišuje podle názvu syntaxe, jaký tag vytvořit. Modul umožňuje konfigurovat, které tagy se použijí pro jednotlivé druhy formátování. + +- **LinkModule** spravuje odkazy v dokumentu. Registruje syntaxe pro různé formáty odkazů - explicitní URL, emailové adresy, reference na definované odkazy. Poskytuje factory metody pro vytváření `Link` objektů a spravuje slovník referencí. Modul umožňuje konfigurovat root pro relativní odkazy, automatické `rel="nofollow"` pro externí odkazy a zkracování dlouhých URL. + +- **ImageModule** zpracovává obrázky podobným způsobem jako LinkModule odkazy. Registruje syntaxi pro inline obrázky a spravuje slovník referencí na definované obrázky. Poskytuje factory metody pro vytváření `Image` objektů a automatickou detekci rozměrů obrázků. Konfigurovatelné jsou cesty k obrázkům, výchozí alt text a CSS třídy pro zarovnání. + +- **HeadingModule** rozpoznává nadpisy v různých formátech - podtržené pomlčkami nebo rovnítky, obklopené mřížkami. Shromažďuje všechny nadpisy do pole TOC pro možné sestavení obsahu. Umožňuje konfigurovat generování ID, top úroveň nadpisů a režim balancování úrovní. + +- **ListModule** zpracovává seznamy - nečíslované, číslované a definiční. Rozpoznává různé typy odrážek a automaticky detekuje vnořování podle odsazení. Umožňuje konfigurovat, které znaky slouží jako odrážky a jaké HTML listy generovat. + +- **TableModule** je jedním z nejkomplexnějších modulů. Rozpoznává tabulky s hlavičkami, těly, titulky a podporuje colspan a rowspan. Zpracovává modifikátory pro řádky i buňky. + +- **BlockModule** zpracovává speciální bloky ohraničené `/--` a `\--`. Podporuje různé typy bloků - code pro kód, html pro přímé HTML, div pro generický kontejner. Umožňuje uživatelům definovat vlastní handlery pro vlastní typy bloků. + +- **TypographyModule** provádí post-processing pro typografické úpravy. Nahrazuje tři tečky elipsou, dvojité pomlčky en-dash, přímé uvozovky typografickými a vkládá nezlomitelné mezery. Pracuje na úrovni finálního řetězce mezi blokovými elementy. + +- **HtmlOutputModule** formátuje finální HTML výstup. Zajišťuje wellformed HTML automatickým zavíráním tagů, opravou nesprávného vnořování, odsazením kódu a zalamováním dlouhých řádků. Umožňuje konfigurovat úroveň odsazení a šířku řádků. + + +Interakce mezi moduly +===================== + +Ačkoliv jsou moduly navrženy jako nezávislé, v některých případech musí spolupracovat. + +Sdílené objekty jsou hlavní mechanismus komunikace. `Link` objekt vytvořený LinkModule může být předán ImageModule pro vytvoření obrázkového odkazu. `Image` objekt vytvořený ImageModule může být předán FigureModule pro vytvoření obrázku s popiskem. Tyto objekty zapouzdřují veškeré potřebné informace a poskytují společné rozhraní. + +Reference systém umožňuje oddělit definici od použití. LinkModule poskytuje metody `addReference()` a `getReference()` pro správu slovníku pojmenovaných odkazů. Uživatel může v jedné části dokumentu definovat referenci a v jiné ji použít. ImageModule má analogický systém pro reference na obrázky. Moduly používající reference volají factory metody, které samy kontrolují, zda jde o referenci nebo přímou hodnotu. + +Element handlers mohou volat jiné element handlery. PhraseModule při zpracování `phrase/span` s odkazem vytvoří `Link` objekt a zavolá element handler LinkModule pro vytvoření odkazu. Tím deleguje odpovědnost za vytvoření a konfiguraci odkazu na specializovaný modul. + +Vztahy mezi moduly jsou typicky jednostranné. PhraseModule zná LinkModule a ImageModule, protože vytváří odkazy a obrázky. Ale LinkModule a ImageModule neznají PhraseModule. To udržuje závislosti jednoduché a umožňuje snadné nahrazení nebo rozšíření modulů. + + +DOM reprezentace +**************** + +`HtmlElement` reprezentuje jeden uzel v DOM stromu a poskytuje rozhraní pro jeho manipulaci a zpracování. + +Základní struktura elementu obsahuje název tagu, asociativní pole atributů a pole potomků. Potomci mohou být další instance `HtmlElement` nebo prostě textové řetězce. Tato kombinace umožňuje reprezentovat libovolnou HTML strukturu. + +Název elementu se nastavuje a získává přes metody `setName()` a `getName()`. Speciální hodnota null jako název znamená transparentní element, který nemá tagy, jen jeho obsah. + +Atributy jsou veřejně přístupné přes property `$attrs` jako asociativní pole. Hodnoty mohou být řetězce, čísla, boolean nebo pole. Boolean true znamená atribut bez hodnoty (jako checked), false nebo null znamená atribut se vůbec nevykreslí. Pokud je hodnota pole, různé prvky se spojí podle typu atributu - pro class mezerami, pro style středníky. Metoda `setAttribute()` nastaví hodnotu atributu. Metoda `getAttribute()` vrací hodnotu atributu nebo null. + +Potomci se spravují přes několik metod. Metoda `add()` přidává potomka na konec. Metoda `insert()` vkládá potomka na specifikovanou pozici, volitelně nahrazuje existujícího potomka. Metoda `create()` vytváří nový `HtmlElement` jako potomka a vrací ho pro další manipulaci. Metoda `removeChildren()` odstraní všechny potomky. + +Element implementuje ArrayAccess interface, takže s potomky lze pracovat jako s polem. Zápis `$el[0]` vrací prvního potomka, `$el[0] = $child` nastaví prvního potomka. Tento přístup je pohodlný pro rychlou manipulaci s konkrétními potomky. + +Metoda `toString()` prochází element a jeho potomky rekurzivně a sestavuje řetězcovou reprezentaci. HTML tagy se okamžitě zamaskují pomocí `Texy::protect()`, takže do výsledku jde placeholder místo skutečných HTML znaků. + +Metody `toHtml()` a `toText()` vrací výsledek nemaskovaný včetně post-processingu. + + +Parsování obsahu +================ + +`HtmlElement` může rekurzivně parsovat svůj obsah, čímž umožňuje postupné budování DOM stromu. + +Metoda `parseLine()` slouží k parsování inline syntaxí v řetězci. Vytvoří novou instanci LineParser s aktuálním elementem jako kontejnerem. Zavolá `parse()` na parseru s poskytnutým textem. LineParser postupně najde a zpracuje všechny inline syntaxe a výsledné elementy nebo řetězce přidá jako potomky aktuálního elementu. Metoda vrací použitý LineParser pro případné další použití. + +Metoda `parseBlock()` parsuje text jako blokový obsah. Vytvoří BlockParser a zavolá na něm `parse()`. BlockParser najde všechny blokové konstrukce v textu, zpracuje je a přidá jako potomky elementu. Text mezi bloky se zpracuje jako odstavce, které interně používají LineParser. Metoda přijímá boolean parametr indikující, zda text pochází z odsazeného bloku, což ovlivňuje zpracování odstavců. + +Tyto parsovací metody umožňují rekurzivní zpracování. Syntax handler může vytvořit element, nastavit jeho základní vlastnosti a pak zavolat `parseLine()` nebo `parseBlock()` pro zpracování obsahu. Výsledkem je, že obsah elementu prochází stejným procesem parsování jako hlavní dokument, včetně rozpoznávání syntaxí a vyvolávání handlerů. + + +Validace +======== + +`HtmlElement` poskytuje mechanismy pro validaci atributů a obsahu podle HTML DTD (Document Type Definition). + +DTD je statické pole definující pro každý HTML tag, které atributy jsou povolené a jaký obsah může obsahovat. Texy načítá DTD ze souboru při inicializaci a uloží ho do statického pole. Struktura DTD mapuje název tagu na dvojici - pole povolených atributů a pole povoleného obsahu. + +Metoda `validateAttrs()` kontroluje atributy elementu podle DTD. Pro daný tag získá seznam povolených atributů. Prochází všechny atributy elementu a ty, které nejsou v seznamu, odstraní. Speciální případy jsou atributy začínající data- nebo aria-, které jsou povolené, pokud je v DTD zástupný záznam `data-*` nebo `aria-*`. + +Tato validace se typicky volá při aplikaci modifikátorů metodou `decorate()`. Zajišťuje, že i když uživatel zadá modifikátor s neplatným atributem pro daný tag, atribut se do finálního HTML nedostane. To je důležité pro bezpečnost a správnost HTML. + +Metoda `validateChild()` kontroluje, zda daný potomek může být obsahem elementu. Přijímá potomka (`HtmlElement` nebo název tagu) a DTD. Pokud je element v DTD definován, metoda zkontroluje, zda potomek je v seznamu povoleného obsahu. Pokud ano, vrací true. Pokud ne, vrací false. + +Tato validace se může použít při dynamickém sestavování DOM stromu pro zajištění korektní struktury. Například paragraph element nesmí obsahovat blokové elementy, takže `validateChild()` by odmítlo přidat div do p. V praxi Texy tuto validaci používá omezeně, protože struktura generovaná moduly je typicky správná by design. + +Kombinace `validateAttrs()` a `validateChild()` poskytuje mechanismus pro zajištění validního HTML, i když vstup obsahuje nedůvěryhodná data nebo špatně formované konstrukce. Texy může být nakonfigurováno pro striktní validaci nebo může validaci vypnout pro maximální flexibilitu. + + +Modifikátory +************ + +Modifikátory poskytují způsob, jak přidat elementům dodatečné atributy, třídy, styly a zarovnání bez nutnosti psát přímé HTML. + +Základní formát modifikátoru je tečka následovaná kombinací různých částí v kulatých, hranatých a složených závorkách: `.(title)[class1 class2 #id]{style:value}^valign`. Celý modifikátor se píše před nebo na konec konstrukce, na kterou se aplikuje. Například `"**text** .(Důležité)[highlight]{color:red}"` vytvoří tučný text se třídou highlight, červenou barvou a title atributem Důležité. + +Kulaté závorky obsahují title atribut nebo alt text. Text uvnitř se použije jako hodnota title atributu na výsledném elementu. Pokud element je obrázek, může se použít jako alt text. Uvnitř kulatých závorek je možné escapovat závorku zpětným lomítkem. + +Hranaté závorky obsahují CSS třídy a volitelně ID. Třídy se píší jako slova oddělená mezerami. ID se píše s prefixem mřížky. Například `[main-content selected #article-5]` nastaví dvě třídy a jedno ID. Pokud je ID uvedeno vícekrát, použije se poslední. + +Složené závorky obsahují CSS styly nebo HTML atributy. Styly se píší ve standardním CSS formátu property:value. Více stylů se odděluje středníky. Některé property jsou rozpoznány jako HTML atributy - například `{href:url}` se převede na atribut href, ne na CSS style. To umožňuje nastavit atributy, které není možné vyjádřit jinak. + +Zarovnání se zadává pomocí speciálních znaků. `<` znamená vlevo, `>` vpravo, `=` pro do bloku, `<>` pro na střed. Vertikální zarovnání používá `^` pro nahoru, `-` pro střed a `_` pro dolů. Tyto zkratky se převádějí buď na CSS třídy nebo inline styly podle konfigurace. + +Části modifikátoru mohou být v libovolném pořadí a některé mohou být vynechány. Platný je modifikátor obsahující jen třídy `.[highlight]`, jen title `.(Poznámka)` nebo jen styl `.{color:blue}`. Parser rozpozná jednotlivé části podle ohraničujících znaků. + + +Modifier třída +============== + +Třída `Modifier` slouží k parsování a uchovávání informací z modifikátoru. + +Instance `Modifier` se typicky vytváří syntax handler, který předá konstruktoru text modifikátoru extrahovaný z regex matche. Konstruktor zavolá metodu `setProperties()`, která parsuje text a naplní properties objektu. + +Veřejné properties obsahují jednotlivé části modifikátoru. Property `$id` obsahuje ID elementu jako řetězec nebo null. Property `$classes` je asociativní pole, kde klíče jsou názvy tříd a hodnoty jsou true. Property `$styles` je asociativní pole mapující CSS property na hodnoty. Property `$attrs` je asociativní pole s HTML atributy, které nejsou styly ani třídy. + +Dvě speciální properties `$hAlign` a `$vAlign` obsahují horizontální a vertikální zarovnání jako řetězce `left`, `right`, `center`, `justify` nebo `top`, `middle`, `bottom`. Tyto hodnoty se později převádějí na CSS třídy nebo styly podle konfigurace Texy. + +Property `$title` obsahuje text z kulatých závorek, který se použije jako title atribut nebo alt text u obrázků. Text je automaticky unescapován z HTML entit a zbaven escapovaných závorek. + + +Aplikace na elementy +==================== + +`Modifier` objekt se aplikuje na `HtmlElement` pomocí metody `Modifier::decorate()`. + +Metoda `decorate()` přijímá instanci Texy a `HtmlElement` jako parametry. Postupně aplikuje jednotlivé části modifikátoru na element s ohledem na konfiguraci Texy, která může některé části zakázat nebo omezit. + +Aplikace atributů kontroluje, které atributy jsou povolené pro daný tag podle `Texy::$allowedTags` konfigurace. Pokud jsou všechny atributy povolené, zkopírují se všechny atributy z `Modifier` do elementu. Pokud je povolen jen seznam konkrétních atributů, zkopírují se pouze ty, které jsou na seznamu. + +Title atribut se vždy aplikuje, pokud je nastaven, ale text prochází typografickým post-processingem pro nahrazení uvozovek a dalších úprav. + +Aplikace tříd a ID kontroluje konfiguraci `Texy::$allowedClasses`. Pokud jsou všechny třídy povolené, přidají se všechny třídy z `Modifier` do elementu a nastaví se ID. Pokud je povolen jen seznam konkrétních tříd, přidají se pouze ty, které jsou na seznamu. ID se přidá, jen pokud je v seznamu povolen řetězec začínající mřížkou. + +Aplikace stylů probíhá podobně s kontrolou `Texy::$allowedStyles`. Povolené CSS properties se přidají do style atributu elementu. Pokud element již měl nějaké styly, modifikátorové styly se přidají nebo přepíšou existující. + +Zarovnání se aplikuje buď jako CSS třída nebo inline style. Pokud je v Texy konfigurováno `Texy::$alignClasses` mapování pro daný typ zarovnání, přidá se odpovídající CSS třída. Pokud ne, přidá se inline style s text-align nebo vertical-align property. + +Výsledkem je, že element má všechny atributy, třídy, styly a další vlastnosti z modifikátoru, ale pouze ty, které jsou povoleny aktuální konfigurací Texy. To zajišťuje bezpečnost při zpracování nedůvěryhodného vstupu. + + +Propagace modifikátorů +====================== + +Modifikátory procházejí systémem v několika fázích, přičemž si zachovávají flexibilitu a umožňují úpravy na různých úrovních. + +Syntax handler extrahuje text modifikátoru z regex matche a vytvoří novou instanci `Modifier` a naplní se jeho properties. + +`Modifier` objekt se předává jako parametr do element handlerů. Handler dostává již parsovaný objekt, ne surový text. To umožňuje handleru snadno přistupovat k jednotlivým částem modifikátoru - třídám, stylům, zarovnání. Handler může modifikátor upravit před aplikací, například přidat další třídy nebo změnit styly. + +Element handler vytváří `HtmlElement` a předá jej metodě `Modifier::decorate()`. V tomto okamžiku se modifikátor aplikuje na element. Metoda `decorate()` kontroluje konfigurace Texy a zajišťuje, že se aplikují pouze povolené části. + +V některých případech modul kombinuje více modifikátorů. Například TableModule parsuje modifikátory na úrovni tabulky, řádků i buněk. Modifikátor buňky je vlastně klony modifikátoru sloupce, na kterém se pak aplikují dodatečné úpravy z modifikátoru konkrétní buňky. To umožňuje výchozí styly pro celý sloupec s možností přepsání v jednotlivých buňkách. diff --git a/texy/cs/configuration.texy b/texy/cs/configuration.texy new file mode 100644 index 0000000000..39f95f7469 --- /dev/null +++ b/texy/cs/configuration.texy @@ -0,0 +1,719 @@ +Konfigurace +*********** + +.[perex] +Kompletní průvodce konfigurací Texy. Naučíte se ovládat všechny moduly, nastavit bezpečnost a přizpůsobit Texy vašim potřebám. + +Texy se konfiguruje pomocí **public properties** hlavní třídy `Texy\Texy` a jejích **modulů**. Každý modul je zodpovědný za zpracování konkrétní části syntaxe (obrázky, odkazy, nadpisy...). + +Základní přístup: + +```php +$texy = new Texy\Texy; + +// Konfigurace hlavní třídy +$texy->allowedTags = Texy\Texy::NONE; + +// Konfigurace modulu +$texy->imageModule->root = '/images/'; +``` + + +Třída Texy\Texy .[#texy-class] +============================== + +Hlavní třída obsahuje globální nastavení a vlastnosti ovlivňující celé zpracování. + + +Povolené syntaxe ($allowed) .{toc: $allowed} +-------------------------------------------- + +Pole `$allowed` kontroluje, které části Texy syntaxe jsou aktivní: + +```php +// Výchozí: všechny syntaxe povoleny (kromě emotikonů) +$texy->allowed['image'] = true; +$texy->allowed['emoticon'] = false; + +// Vypnout obrázky +$texy->allowed['image'] = false; + +// Vypnout HTML značky ve vstupu +$texy->allowed['html/tag'] = false; +$texy->allowed['html/comment'] = false; + +// Vypnout různé typy odkazů +$texy->allowed['link/reference'] = false; +$texy->allowed['link/email'] = false; +$texy->allowed['link/url'] = false; +``` + +**Kompletní seznam syntaxí:** + +|--- +| Klíč | Výchozí | Popis +|--- +| `image` | `true` | Obrázky `[* img.jpg *]` +| `figure` | `true` | Obrázky s popiskou +| `link/reference` | `true` | Reference `[ref]` +| `link/email` | `true` | Email adresy +| `link/url` | `true` | Automatické URL +| `link/definition` | `true` | Definice referencí +| `heading/underlined` | `true` | Podtržené nadpisy +| `heading/surrounded` | `true` | Ohraničené nadpisy +| `horizline` | `true` | Horizontální čáry +| `blockquote` | `true` | Citace +| `list` | `true` | Seznamy +| `list/definition` | `true` | Definiční seznamy +| `table` | `true` | Tabulky +| `phrase/strong` | `true` | Tučné písmo `**text**` +| `phrase/em` | `true` | Kurzíva `//text//` +| `phrase/em-alt` | `true` | Kurzíva `*text*` +| `phrase/code` | `true` | Kód ```text``` +| `phrase/ins` | `false` | Vložený text `++text++` +| `phrase/del` | `false` | Smazaný text `--text--` +| `phrase/sup` | `false` | Horní index `^^text^^` +| `phrase/sub` | `false` | Dolní index `__text__` +| `html/tag` | `true` | HTML značky ve vstupu +| `html/comment` | `true` | HTML komentáře +| `emoticon` | `false` | Emotikony `:-)`, `:-(` +| `blocks` | `true` | Bloky `/-- \--` +| `typography` | `true` | Typografické úpravy +| `longwords` | `true` | Dělení dlouhých slov + + +Povolené HTML značky ($allowedTags) .{toc: $allowedTags} +-------------------------------------------------------- + +Kontroluje, které HTML značky mohou být ve výstupu (a na vstupu): + +```php +// Výchozí: všechny validní HTML5 značky povoleny +$texy->allowedTags = Texy\Texy::ALL; + +// Zakázat všechny HTML značky +$texy->allowedTags = Texy\Texy::NONE; + +// Povolit jen konkrétní značky +$texy->allowedTags = [ + 'strong' => [], // bez atributů + 'a' => ['href', 'title'], // s atributy + 'img' => Texy\Texy::ALL, // s jakýmikoliv atributy +]; +``` + +**Formáty:** +- `Texy::ALL` – všechny značky povoleny +- `Texy::NONE` – žádné značky povoleny +- Pole – povolené značky jako klíče, povolené atributy jako hodnoty + + +Povolené CSS třídy ($allowedClasses) .{toc: $allowedClasses} +------------------------------------------------------------ + +Kontroluje, které CSS třídy a ID mohou být použity: + +```php +// Výchozí: všechny třídy a ID povoleny +$texy->allowedClasses = Texy\Texy::ALL; + +// Zakázat třídy a ID +$texy->allowedClasses = Texy\Texy::NONE; + +// Povolit konkrétní třídy a ID +$texy->allowedClasses = [ + 'highlight', + 'important', + '#main', // ID začínají # + '#sidebar', +]; +``` + +Použití: +```texy +Text s třídou .[highlight] + +Text s ID .{toc: main} +``` + + +Povolené CSS styly ($allowedStyles) .{toc: $allowedStyles} +---------------------------------------------------------- + +Kontroluje, které inline CSS vlastnosti mohou být použity: + +```php +// Výchozí: všechny styly povoleny +$texy->allowedStyles = Texy\Texy::ALL; + +// Zakázat inline styly +$texy->allowedStyles = Texy\Texy::NONE; + +// Povolit konkrétní CSS vlastnosti +$texy->allowedStyles = [ + 'color', + 'background-color', + 'font-size', +]; +``` + +Použití: +```texy +Text s barvou .{color: red} +``` + + +CSS třídy pro zarovnání ($alignClasses) .{toc: $alignClasses} +------------------------------------------------------------- + +Místo inline stylů `style="text-align:left"` můžete použít CSS třídy: + +```php +// Výchozí: prázdné pole (použijí se inline styly) +$texy->alignClasses = [ + 'left' => null, + 'right' => null, + 'center' => null, + 'justify' => null, + 'top' => null, + 'middle' => null, + 'bottom' => null, +]; + +// Nastavit třídy +$texy->alignClasses['left'] = 'text-left'; +$texy->alignClasses['right'] = 'text-right'; +$texy->alignClasses['center'] = 'text-center'; +``` + +Použití: +```texy +Text zarovnaný doleva . + +Text zarovnaný doprava .> +``` + +S nastaveným `alignClasses` vygeneruje `

                                                                                                                            ` místo `

                                                                                                                            `. + + +Další vlastnosti +---------------- + +```php +// Spojování řádků do odstavců (výchozí: true) +$texy->mergeLines = true; + +// Šířka tabulátoru (výchozí: 8) +$texy->tabWidth = 8; + +// Maskování emailů před roboty (výchozí: true) +$texy->obfuscateEmail = true; + +// Odstraňování měkkých spojovníků (výchozí: true) +$texy->removeSoftHyphens = true; + +// Element pro netextové odstavce (výchozí: 'div') +$texy->nontextParagraph = 'div'; +``` + + +Moduly +====== + +Každý modul zpracovává konkrétní část syntaxe. Moduly jsou přístupné jako public properties třídy `Texy\Texy`. + + +HeadingModule +------------- + +Zpracovává nadpisy (podtržené i ohraničené). + +```php +// Úroveň nejvyššího nadpisu (výchozí: 1) +$texy->headingModule->top = 1; //

                                                                                                                            + +// Generovat automatická ID (výchozí: false) +$texy->headingModule->generateID = true; + +// Prefix pro generovaná ID (výchozí: 'toc-') +$texy->headingModule->idPrefix = 'section-'; + +// Více znaků = vyšší nadpis? (výchozí: true) +$texy->headingModule->moreMeansHigher = true; + +// Režim vyvažování (výchozí: DYNAMIC) +$texy->headingModule->balancing = Texy\Modules\HeadingModule::DYNAMIC; +``` + +Po zpracování: + +```php +// První nadpis (pro ) +echo $texy->headingModule->title; + +// Obsah (Table of Contents) +print_r($texy->headingModule->TOC); +``` + + +PhraseModule +------------ + +Zpracovává inline formátování (tučné, kurzíva, odkazy v textu...). + +```php +// HTML značky pro jednotlivé fráze (výchozí: viz níže) +$texy->phraseModule->tags = [ + 'phrase/strong' => 'strong', + 'phrase/em' => 'em', + 'phrase/code' => 'code', + // ... další +]; + +// Povolit odkazy ve frázích (výchozí: true) +$texy->phraseModule->linksAllowed = true; +``` + + +LinkModule +---------- + +Zpracovává odkazy, reference a URL. + +```php +// Kořenová cesta pro odkazy (výchozí: '') +$texy->linkModule->root = '/articles/'; + +// CSS třída pro odkazy na obrázky (výchozí: null) +$texy->linkModule->imageClass = 'image-link'; + +// Vždy přidávat rel="nofollow" (výchozí: false) +$texy->linkModule->forceNoFollow = false; + +// Zkracovat URL na čitelnější formu (výchozí: true) +$texy->linkModule->shorten = true; +``` + +**Reference:** + +```php +// Přidat referenci +$link = new Texy\Link('https://example.com'); +$link->modifier->title = 'Ukázková stránka'; +$link->label = 'Příklad'; +$texy->linkModule->addReference('example', $link); +``` + +Použití: +```texy +Odkaz na [example] +``` + + +ImageModule +----------- + +Zpracovává obrázky. + +```php +// Kořenová cesta pro obrázky (výchozí: 'images/') +$texy->imageModule->root = '/assets/images/'; + +// Kořenová cesta pro linkované obrázky (výchozí: 'images/') +$texy->imageModule->linkedRoot = '/assets/images/full/'; + +// Fyzická cesta na disku (pro zjištění rozměrů) +$texy->imageModule->fileRoot = __DIR__ . '/public/images/'; + +// CSS třída pro plovoucí obrázky (výchozí: null) +$texy->imageModule->leftClass = 'float-left'; +$texy->imageModule->rightClass = 'float-right'; + +// Výchozí alternativní text (výchozí: '') +$texy->imageModule->defaultAlt = 'Obrázek'; +``` + +**Reference:** + +```php +// Přidat referenci +$image = new Texy\Image; +$image->URL = 'photo.jpg'; +$image->modifier->title = 'Fotografie'; +$texy->imageModule->addReference('photo', $image); +``` + + +FigureModule +------------ + +Zpracovává obrázky s popiskou. + +```php +// HTML element (výchozí: 'div') +$texy->figureModule->tagName = 'figure'; + +// CSS třída (výchozí: 'figure') +$texy->figureModule->class = 'photo-figure'; + +// Třídy pro plovoucí obrázky (výchozí: null) +$texy->figureModule->leftClass = 'figure-left'; +$texy->figureModule->rightClass = 'figure-right'; + +// Offset pro výpočet šířky (výchozí: 10) +$texy->figureModule->widthDelta = 20; + +// Vyžadovat popisku (výchozí: true) +$texy->figureModule->requireCaption = true; +``` + + +ListModule +---------- + +Zpracovává odrážkové, číslované a definiční seznamy. + +```php +// Definice odrážek a stylu (výchozí: viz zdrojový kód) +$texy->listModule->bullets = [ + '*' => ['\*[\ \t]', 0, ''], + '-' => ['[\x{2013}-](?![>-])', 0, ''], + // ... další +]; +``` + + +TableModule +----------- + +Zpracovává tabulky. + +```php +// CSS třídy pro řádky (výchozí: null) +$texy->tableModule->oddClass = 'odd'; +$texy->tableModule->evenClass = 'even'; +``` + +*Poznámka: `oddClass` a `evenClass` jsou deprecated.* + + +HorizLineModule +--------------- + +Zpracovává horizontální čáry. + +```php +// CSS třídy podle typu (výchozí: null) +$texy->horizLineModule->classes = [ + '-' => 'hr-line', + '*' => 'hr-star', +]; +``` + + +TypographyModule +---------------- + +Zpracovává typografické úpravy. + +```php +// Locale (výchozí: 'cs') +$texy->typographyModule->locale = 'en'; +``` + +**Podporované locales:** +- `cs` – české uvozovky „text" a ‚text' +- `en` – anglické uvozovky "text" a 'text' +- `fr` – francouzské uvozovky «text» a ‹text› +- `de` – německé uvozovky „text" a ‚text' +- `pl` – polské uvozovky „text" a ‚text' + + +LongWordsModule +--------------- + +Rozděluje dlouhá slova pomocí `­`. + +```php +// Maximální délka slova (výchozí: 20) +$texy->longWordsModule->wordLimit = 25; +``` + + +EmoticonModule +-------------- + +Nahrazuje emotikony za obrázky nebo Unicode znaky. + +```php +// CSS třída (výchozí: null) +$texy->emoticonModule->class = 'emoji'; + +// Cesta k obrázkům (výchozí: null = použije imageModule->root) +$texy->emoticonModule->root = '/images/smilies/'; +$texy->emoticonModule->fileRoot = __DIR__ . '/public/smilies/'; + +// Definice emotikonů (výchozí: základní sada) +$texy->emoticonModule->icons = [ + ':-)' => '🙂', + ':-(' => '☹', + ';-)' => '😉', + // ... nebo cesty k obrázkům + ':cool:' => 'cool.gif', +]; +``` + + +HtmlModule +---------- + +Zpracovává HTML značky a komentáře ve vstupním textu. + +```php +// Zobrazit HTML komentáře na výstupu (výchozí: true) +$texy->htmlModule->passComment = true; +``` + + +HtmlOutputModule +---------------- + +Formátuje výstupní HTML. + +```php +// Formátovat výstup (odsazení) (výchozí: true) +$texy->htmlOutputModule->indent = true; + +// Základní odsazení (výchozí: 0) +$texy->htmlOutputModule->baseIndent = 0; + +// Maximální šířka řádku (výchozí: 80) +$texy->htmlOutputModule->lineWrap = 100; + +// Zachovat mezery v těchto elementech (výchozí: seznam) +$texy->htmlOutputModule->preserveSpaces = [ + 'textarea', 'pre', 'script', 'code', +]; +``` + + +ScriptModule +------------ + +Zpracovává volání `{{makro}}`. + +```php +// Oddělovač argumentů (výchozí: ',') +$texy->scriptModule->separator = ';'; +``` + + +Třída Texy\Configurator .{toc: Texy\Configurator} +================================================= + +Předpřipravené konfigurační sety pro časté use-case. + + +safeMode() – Bezpečný režim .{toc: safeMode()} +---------------------------------------------- + +Konfigurace pro zpracování **nedůvěryhodného obsahu** od uživatelů. + +```php +Texy\Configurator::safeMode($texy); +``` + +**Co dělá:** +- Zakáže třídy a ID (`$allowedClasses = NONE`) +- Zakáže inline styly (`$allowedStyles = NONE`) +- Povolí jen bezpečné HTML značky: + +```php +[ + 'a' => ['href', 'title'], + 'abbr' => ['title'], + 'b' => [], + 'br' => [], + 'cite' => [], + 'code' => [], + 'em' => [], + 'i' => [], + 'strong' => [], + 'sub' => [], + 'sup' => [], + 'q' => [], + 'small' => [], +] +``` + +- Filtruje URL schémata (jen `http:`, `https:`, `ftp:`, `mailto:`) +- Zakáže obrázky +- Zakáže definice referencí +- Zakáže HTML komentáře +- Přidá `rel="nofollow"` ke všem odkazům + + +disableLinks() – Vypnout odkazy .{toc: disableLinks()} +------------------------------------------------------ + +Zakáže všechny typy odkazů. + +```php +Texy\Configurator::disableLinks($texy); +``` + +**Co dělá:** +- Zakáže všechny typy odkazů (`link/reference`, `link/email`, `link/url`, `link/definition`) +- Zakáže odkazy ve frázích (`phraseModule->linksAllowed = false`) +- Odebere `<a>` z povolených značek + + +disableImages() – Vypnout obrázky .{toc: disableImages()} +--------------------------------------------------------- + +Zakáže všechny typy obrázků. + +```php +Texy\Configurator::disableImages($texy); +``` + +**Co dělá:** +- Zakáže obrázky (`image`, `figure`, `image/definition`) +- Odebere `<img>`, `<object>`, `<embed>`, `<applet>` z povolených značek + + +Bezpečnost +========== + +Texy je navrženo s důrazem na bezpečnost. Automaticky chrání před běžnými útoky. + + +Ochrana proti XSS +----------------- + +Cross-Site Scripting (XSS) je útok, kdy útočník vloží škodlivý JavaScript do stránky. + +**Příklad útoků, které Texy zablokuje:** + +```texy +Pokus o útok: <script>alert('XSS')</script> + +Pokus o útok: <img src=x onerror="alert('XSS')"> + +Pokus o útok: "klikni":javascript:alert('XSS') + +Pokus o útok: [* image.jpg onload="alert('XSS')" *] +``` + +Texy automaticky: +- **Validuje HTML** – odstraní nepovolené značky a atributy +- **Filtruje URL** – povolí jen bezpečná schémata (`http:`, `https:`, `mailto:`, `ftp:`) +- **Escapuje obsah** – správně escapuje text v atributech +- **Sanitizuje atributy** – odstraní event handlery (`onclick`, `onerror`, ...) + +```php +$texy = new Texy\Texy; +Texy\Configurator::safeMode($texy); + +$input = '<script>alert("XSS")</script>'; +$output = $texy->process($input); + +// Výstup: prázdný (script tag odstraněn) +``` + + +Validace URL +------------ + +Texy kontroluje URL ve všech odkazech a obrázcích: + +```php +$texy = new Texy\Texy; + +// Nastavit povolená schémata (výchozí v safeMode) +$texy->urlSchemeFilters[Texy\Texy::FILTER_ANCHOR] = + '#https?:|ftp:|mailto:#Ai'; +$texy->urlSchemeFilters[Texy\Texy::FILTER_IMAGE] = + '#https?:#Ai'; +``` + +**Příklady blokovaných URL:** + +```texy +"útok":javascript:alert('XSS') // blokováno +"útok":data:text/html,<script> // blokováno +[* javascript:alert() *] // blokováno +``` + + +Filtrování HTML značek +---------------------- + +Kontrola přes `$allowedTags`: + +```php +$texy = new Texy\Texy; + +// Povolit jen bezpečné značky +$texy->allowedTags = [ + 'p' => [], + 'strong' => [], + 'em' => [], + 'a' => ['href', 'title'], // jen tyto atributy +]; + +$input = '<p>Text <script>alert()</script></p>'; +$output = $texy->process($input); + +// Výstup: <p>Text alert()</p> +// (script tag odstraněn) +``` + + +Praktický příklad +----------------- + +```php +function processComment(string $userInput): string +{ + $texy = new Texy\Texy; + + // Bezpečný režim + Texy\Configurator::safeMode($texy); + + // Dodatečná omezení + $texy->allowed['link/url'] = false; // zakázat auto-linky + $texy->allowed['html/tag'] = false; // zakázat HTML + + // Zpracovat + return $texy->process($userInput); +} + +// Použití +$comment = $_POST['comment']; +$html = processComment($comment); +echo $html; // bezpečný výstup +``` + + +Best practices +-------------- + +1. **Vždy použijte safeMode()** pro uživatelský obsah +2. **Validujte vstup** před předáním Texy (délka, formát) +3. **Limitujte HTML značky** podle potřeby +4. **Kontrolujte výstup** – i když Texy je bezpečné, double-check nikdy neuškodí +5. **Logujte podezřelé pokusy** – může vám pomoci identifikovat útočníky + +```php +$texy = new Texy\Texy; +Texy\Configurator::safeMode($texy); + +// Logování +$texy->addHandler('htmlTag', function($invocation, $el, $isStart) { + if ($el->getName() === 'script') { + error_log('XSS attempt detected!'); + } + return $invocation->proceed(); +}); +``` diff --git a/texy/cs/custom-handlers.texy b/texy/cs/custom-handlers.texy new file mode 100644 index 0000000000..3cc43d4b78 --- /dev/null +++ b/texy/cs/custom-handlers.texy @@ -0,0 +1,850 @@ +Úprava chování prvků +******************** + +.[perex] +Tato kapitola popisuje, jak můžete změnit chování **existujících prvků** v Texy - například upravit, jak se zpracovávají obrázky, odkazy nebo formátování. Pokud chcete přidat **zcela novou syntaxi**, kterou Texy standardně nezná, přečtěte si kapitolu [Přidání vlastní syntaxe |custom-syntax]. + +Představte si, že chcete, aby standardní syntaxe pro obrázky `[* URL *]` rozpoznávala speciální adresu `[* youtube:dQw4w9WgXcQ *]` a místo běžného obrázku vytvořila embedded přehrávač. + +Nebo chcete obarvovat výpisy zdrojového kódu pomocí syntax highlighteru. A tak dále. Přesně k tomu slouží **element handlery** - funkce, které Texy volá při zpracování konkrétních prvků. Například zaregistrujete handler pro element `image`, který zkontroluje URL, a pokud začíná `youtube:`, vrátí iframe místo standardního obrázku. Neměníte syntaxi, jen upravujete, co se s nalezenou konstrukcí stane. + + +Elementy a jejich handlery +========================== + +V terminologii Texy je **element** název pro typ prvku, který může být v dokumentu zpracován. Například `image` je element pro obrázky, `linkURL` pro odkazy, viz [#výchozí elementy]. Každý element má svůj **výchozí handler**, který je implementován v příslušném modulu a stará se o standardní zpracování. + +Když napíšete v textu `[* image.jpg *]`, parser najde tuto syntaxi, vytvoří objekt `Texy\Image` s daty o obrázku a zavolá všechny handlery zaregistrované pro element `image`. Pokud žádný vlastní handler není, zavolá se pouze výchozí handler z `ImageModule`, který vytvoří HTML tag `<img>`. + +Handler zaregistrujete voláním metody `addHandler()`: + +```php +$texy->addHandler('image', function( + Texy\HandlerInvocation $invocation, + Texy\Image $image, + ?Texy\Link $link, +) { + // zde bude vaše logika +}); +``` + +První parametr je název elementu, druhý je callback funkce. Callback dostává jako první parametr vždy objekt `Texy\HandlerInvocation`, následují parametry jsou specifické pro daný element. + +.[note] +Podrobné vysvětlení všech typů handlerů najdete v kapitole [Architektura a principy |architecture]. + + +Jak funguje zpracování +====================== + +Když Texy potřebuje zpracovat element, vytvoří objekt `HandlerInvocation` obsahující všechny zaregistrované handlery pro tento typ prvku. **Váš handler se zavolá jako první** a může: + +- **Delegovat** na další handler voláním `$invocation->proceed()` +- **Upravit vstup** voláním `proceed()` s modifikovanými parametry +- **Upravit výstup** zpracováním výsledku z `proceed()` +- **Přerušit řetěz** vrácením vlastního výsledku bez volání `proceed()` + +Metoda `proceed()` posune zpracování na další handler v řetězu. Pokud už žádný vlastní handler není, zavolá se výchozí implementace z modulu. To znamená, že váš handler má absolutní kontrolu - může rozhodnout, zda se vůbec zavolá výchozí logika. + +Tento mechanismus se nazývá **chain of responsibility** (řetěz zodpovědnosti): + +```php +$texy->addHandler('image', function( + Texy\HandlerInvocation $invocation, + Texy\Image $image, + ?Texy\Link $link, +) { + // 1. Upravíme vstupní data před zpracováním + $image->modifier->title = 'Modified title'; + + // 2. Zavoláme další handler nebo výchozí zpracování + $element = $invocation->proceed($image, $link); + + // 3. Upravíme výsledný HTML element + $element->attrs['loading'] = 'lazy'; + + return $element; +}); +``` + +Pořadí vykonávání je od **posledně registrovaného k prvnímu**. Pokud modul zaregistruje svůj výchozí handler při konstrukci a vy pak zaregistrujete vlastní handler, váš handler se zavolá první. To vám umožňuje přepsat nebo obalit výchozí chování. + + +Výchozí elementy +================ + +Texy poskytuje několik předpřipravených elementů, pro které můžete registrovat vlastní handlery. Zde je jejich kompletní seznam s parametry, které dostává handler. + + +image +----- + +Zpracovává obrázky. + +```php +function( + Texy\HandlerInvocation $invocation, + Texy\Image $image, + ?Texy\Link $link, +): Texy\HtmlElement|string|null +``` + +Parametr `$image` obsahuje URL, rozměry a modifikátory. Parametr `$link` je zadán, pokud je obrázek odkazem (syntaxe `[* img *]:url`). + + +linkReference +------------- + +Zpracovává referenční odkazy typu `[ref]`. + +```php +function( + Texy\HandlerInvocation $invocation, + Texy\Link $link, + string $content, +): Texy\HtmlElement|string|null +``` + +Parametr `$link` obsahuje URL a modifikátory načtené z definice reference. Parametr `$content` je HTML obsah odkazu (již zpracovaný parsováním inline syntaxí). + + +linkEmail +--------- + +Zpracovává automaticky rozpoznané emailové adresy v textu. + +```php +function( + Texy\HandlerInvocation $invocation, + Texy\Link $link, +): Texy\HtmlElement|string|null +``` + +Parametr `$link` obsahuje emailovou adresu v property `URL`. + + +linkURL +------- + +Zpracovává automaticky rozpoznané URL v textu. + +```php +function( + Texy\HandlerInvocation $invocation, + Texy\Link $link, +): Texy\HtmlElement|string|null +``` + +Parametr `$link` obsahuje nalezenou URL. + + +phrase +------ + +Zpracovává inline formátování. + +```php +function( + Texy\HandlerInvocation $invocation, + string $phrase, + string $content, + Texy\Modifier $modifier, + ?Texy\Link $link, +): Texy\HtmlElement|string|null +``` + +Parametr `$phrase` je název syntaxe jako `phrase/strong` nebo `phrase/em`. Parametr `$content` je text uvnitř formátování. Parametr `$modifier` obsahuje CSS třídy, styly a další modifikátory. Parametr `$link` je zadán, pokud má formátování připojený odkaz. + + +newReference +------------ + +Volá se, když parser najde referenci, která není definovaná. + +```php +function( + Texy\HandlerInvocation $invocation, + string $name, +): Texy\HtmlElement|string|null +``` + +Parametr `$name` je název reference. Handler může vytvořit odkaz dynamicky nebo vrátit `null` pro odmítnutí. + + +htmlComment +----------- + +Zpracovává HTML komentáře. + +```php +function( + Texy\HandlerInvocation $invocation, + string $content, +): string +``` + +Parametr `$content` je text mezi `<!--` a `-->`. + + +htmlTag +------- + +Zpracovává HTML tagy v textu. + +```php +function( + Texy\HandlerInvocation $invocation, + Texy\HtmlElement $el, + bool $isStart, + ?bool $forceEmpty, +): Texy\HtmlElement|string|null +``` + +Parametr `$el` je element s názvem a atributy. Parametr `$isStart` určuje, zda jde o otevírací tag. Parametr `$forceEmpty` vynutí prázdný element. + + +script +------ + +Zpracovává skripty `{{command: args}}`. + +```php +function( + Texy\HandlerInvocation $invocation, + string $command, + array $args, + ?string $raw, +): Texy\HtmlElement|string|null +``` + +Parametr `$command` je název příkazu. Parametr `$args` je pole argumentů. Parametr `$raw` je původní neparsovaný řetězec argumentů. + + +figure +------ + +Zpracovává obrázky s popiskou. + +```php +function( + Texy\HandlerInvocation $invocation, + Texy\Image $image, + ?Texy\Link $link, + string $content, + Texy\Modifier $modifier, +): Texy\HtmlElement|null +``` + +Parametr `$content` je text popisky pod obrázkem. + + +heading +------- + +Zpracovává nadpisy. + +```php +function( + Texy\HandlerInvocation $invocation, + int $level, + string $content, + Texy\Modifier $modifier, + bool $isSurrounded, +): Texy\HtmlElement +``` + +Parametr `$level` je úroveň nadpisu (0-6). Parametr `$content` je text nadpisu. Parametr `$isSurrounded` určuje, zda jde o ohraničený nadpis (`###`) nebo podtržený. + + +horizline +--------- + +Zpracovává horizontální čáry. + +```php +function( + Texy\HandlerInvocation $invocation, + string $type, + Texy\Modifier $modifier, +): Texy\HtmlElement +``` + +Parametr `$type` je řetězec znaků použitých pro čáru (`---` nebo `***`). + + +block +----- + +Zpracovává speciální bloky `/--type` až `\--`. + +```php +function( + Texy\HandlerInvocation $invocation, + string $blocktype, + string $content, + ?string $param, + Texy\Modifier $modifier, +): Texy\HtmlElement|string +``` + +Parametr `$blocktype` je typ bloku s prefixem `block/`, např. `block/code` nebo `block/html`. Parametr `$content` je obsah bloku. Parametr `$param` je volitelný parametr za typem (např. jazyk u kódu). + + +emoticon +-------- + +Zpracovává emotikony (smajlíky). + +```php +function( + Texy\HandlerInvocation $invocation, + string $emoticon, + string $raw, +): Texy\HtmlElement|string +``` + +Parametr `$emoticon` je rozpoznaný emotikon (např. `:-)` nebo `:-(` ). Parametr `$raw` je původní text včetně případných opakujících se znaků (např. `:-)))))` ). + +.[note] +Emotikony jsou ve výchozím nastavení **vypnuté**. Zapnete je pomocí `$texy->allowed['emoticon'] = true;` + + +Výchozí eventy +============== + +Texy poskytuje několik předpřipravených eventů, pro které můžete registrovat handlery. Říká se jim **notification handlery**. Na rozdíl od element handlerů tyto handlery **nic nevrací**. Používají se pro vedlejší efekty jako logování, sběr statistik nebo úpravy již vytvořeného DOM stromu. + + +beforeParse +----------- + +Volá se před začátkem parsování textu. Umožňuje provést předzpracování nebo načíst definice. + +```php +function( + Texy\Texy $texy, + string &$text, + bool $isSingleLine, +): void +``` + +Parametr `$text` je předán referencí, takže ho můžete upravit. Parametr `$isSingleLine` určuje, zda se parsuje jeden řádek nebo celý dokument. + + +afterParse +---------- + +Volá se po dokončení parsování, před konverzí DOM stromu na HTML. Umožňuje upravit vytvořený DOM. + +```php +function( + Texy\Texy $texy, + Texy\HtmlElement $DOM, + bool $isSingleLine, +): void +``` + +Parametr `$DOM` je kořenový element dokumentu, který můžete procházet a upravovat. + + +afterList +--------- + +Volá se po vytvoření seznamu (číslovaného nebo nečíslovaného). + +```php +function( + Texy\BlockParser $parser, + Texy\HtmlElement $element, + Texy\Modifier $modifier, +): void +``` + +Parametr `$element` je vytvořený element `<ul>` nebo `<ol>`. Parametr `$modifier` obsahuje modifikátory aplikované na celý seznam. + + +afterDefinitionList +------------------- + +Volá se po vytvoření definičního seznamu. + +```php +function( + Texy\BlockParser $parser, + Texy\HtmlElement $element, + Texy\Modifier $modifier, +): void +``` + +Parametr `$element` je vytvořený element `<dl>`. + + +afterTable +---------- + +Volá se po vytvoření tabulky. + +```php +function( + Texy\BlockParser $parser, + Texy\HtmlElement $element, + Texy\Modifier $modifier, +): void +``` + +Parametr `$element` je vytvořený element `<table>`. + + +afterBlockquote +--------------- + +Volá se po vytvoření citace. + +```php +function( + Texy\BlockParser $parser, + Texy\HtmlElement $element, + Texy\Modifier $modifier, +): void +``` + +Parametr `$element` je vytvořený element `<blockquote>`. + + +Základní použití +================ + +Nejjednodušší element handler jen deleguje na výchozí zpracování: + +```php +$texy->addHandler('image', function( + Texy\HandlerInvocation $invocation, + Texy\Image $image, + ?Texy\Link $link, +) { + return $invocation->proceed(); +}); +``` + +Tento handler nic nemění, ale ukazuje základní kostru. Všechny parametry předá dál a vrátí výsledek. + + +Úprava vstupních dat +-------------------- + +Handler může upravit data před jejich zpracováním: + +```php +$texy->addHandler('image', function( + Texy\HandlerInvocation $invocation, + Texy\Image $image, + ?Texy\Link $link, +) { + // přidáme default rozměry, pokud nejsou zadané + $image->width ??= 800; + $image->height ??= 600; + return $invocation->proceed(); +}); +``` + +Změny provedené na objektech `$image` nebo `$link` se projeví v dalším zpracování, včetně výchozího handleru. + + +Úprava výstupního elementu +-------------------------- + +Handler může upravit HTML element vrácený z `proceed()`: + +```php +$texy->addHandler('image', function( + Texy\HandlerInvocation $invocation, + Texy\Image $image, + ?Texy\Link $link, +) { + $element = $invocation->proceed(); + + if ($element) { + // přidáme lazy loading + $element->attrs['loading'] = 'lazy'; + + // přidáme CSS třídu + $element->attrs['class'][] = 'responsive'; + } + + return $element; +}); +``` + + +Podmíněné zpracování +-------------------- + +Handler může zpracovat pouze určité případy a ostatní delegovat: + +```php +$texy->addHandler('image', function( + Texy\HandlerInvocation $invocation, + Texy\Image $image, + ?Texy\Link $link, +) { + // speciální zpracování pro YouTube videa + if (str_starts_with($image->URL, 'youtube:')) { + $id = substr($image->URL, 8); + $iframe = sprintf( + '<iframe src="https://youtube.com/embed/%s"></iframe>', + htmlspecialchars($id) + ); + return $invocation->getTexy() + ->protect($iframe, Texy\Texy::CONTENT_BLOCK); + } + + // ostatní obrázky zpracujeme standardně + return $invocation->proceed(); +}); +``` + + +Přerušení zpracování +-------------------- + +Handler může odmítnout zpracování vrácením `null`: + +```php +$texy->addHandler('image', function( + Texy\HandlerInvocation $invocation, + Texy\Image $image, + ?Texy\Link $link, +) { + // zakážeme externí obrázky + if (str_contains($image->URL, '://')) { + return null; + } + + return $invocation->proceed(); +}); +``` + + +Praktické příklady +================== + +Následující příklady ukazují reálné use-case pro element handlery. + + +YouTube embed +------------- + +Převod speciální syntaxe na embedded video: + +```php +$texy->addHandler('image', function( + Texy\HandlerInvocation $invocation, + Texy\Image $image, + ?Texy\Link $link, +) { + if (str_starts_with($image->URL, 'youtube:')) { + $id = substr($image->URL, 8); + $width = $image->width ?: 560; + $height = $image->height ?: 315; + + $iframe = sprintf( + '<iframe width="%d" height="%d" ' + . 'src="https://youtube.com/embed/%s" ' + . 'frameborder="0" allowfullscreen></iframe>', + $width, $height, htmlspecialchars($id) + ); + + $texy = $invocation->getTexy(); + return $texy->protect($iframe, $texy::CONTENT_BLOCK); + } + + return $invocation->proceed(); +}); +``` + +Použití v textu: + +```texy +[* youtube:dQw4w9WgXcQ 640x360 *] +``` + + +Galerie obrázků +--------------- + +Obalení obrázků do speciálního divu pro lightbox: + +```php +$texy->addHandler('image', function( + Texy\HandlerInvocation $invocation, + Texy\Image $image, + ?Texy\Link $link, +) { + $element = $invocation->proceed(); + + // pokud má obrázek třídu 'gallery' + if (isset($image->modifier->classes['gallery'])) { + // obalíme do divu s lightbox atributy + $wrapper = new Texy\HtmlElement('div'); + $wrapper->attrs['class'][] = 'lightbox-item'; + $wrapper->attrs['data-src'] = $image->URL; + $wrapper->add($element); + + return $wrapper; + } + + return $element; +}); +``` + +Použití: + +```texy +[* image.jpg .[gallery] *] +``` + + +Validace odkazů +--------------- + +Kontrola, zda odkazy nalezené v textu vedou na povolené domény: + +```php +$allowedDomains = ['example.com', 'trusted.org']; + +$texy->addHandler('linkURL', function( + Texy\HandlerInvocation $invocation, + Texy\Link $link, +) use ($allowedDomains) { + $host = parse_url($link->URL, PHP_URL_HOST); + + // pokud doména není v whitelistu, nepovolíme odkaz + if ($host && !in_array($host, $allowedDomains, true)) { + return null; + } + + return $invocation->proceed(); +}); +``` + + +Automatické rel="nofollow" +-------------------------- + +Přidání `nofollow` všem externím odkazům nalezeným v textu: + +```php +$texy->addHandler('linkURL', function( + Texy\HandlerInvocation $invocation, + Texy\Link $link, +) { + $element = $invocation->proceed(); + + // pokud odkaz obsahuje // (tedy je externí) + if (str_contains($link->URL, '://')) { + $element->attrs['rel'] = 'nofollow'; + } + + return $element; +}); +``` + + +Syntax highlighting +------------------- + +Integrace knihovny pro zvýraznění syntaxe: + +```php +$texy->addHandler('block', function( + Texy\HandlerInvocation $invocation, + string $blocktype, + string $content, + ?string $param, + Texy\Modifier $modifier, +) { + // zpracujeme pouze bloky typu 'code' + if ($blocktype !== 'block/code') { + return $invocation->proceed(); + } + + // aplikujeme syntax highlighting + $highlighter = new MyHighlighter(); + $highlighted = $highlighter->highlight($content, $param); + + $el = new Texy\HtmlElement('pre'); + $modifier->decorate($invocation->getTexy(), $el); + $el->attrs['class'][] = 'language-' . $param; + + $code = new Texy\HtmlElement('code'); + $code->add($highlighted); + $el->add($code); + + return $el; +}); +``` + + +Lazy loading +------------ + +Projdeme všechny obrázky a přidáme lazy loading: + +```php +$texy->addHandler('afterParse', function( + Texy\Texy $texy, + Texy\HtmlElement $DOM, + bool $isSingleLine, +) { + foreach ($DOM->getIterator() as $child) { + if ($child instanceof Texy\HtmlElement + && $child->getName() === 'img' + ) { + $child->attrs['loading'] = 'lazy'; + } + } +}); +``` + + +Logování použitých prvků +------------------------ + +Sběr statistik o použitých prvcích v dokumentu: + +```php +$stats = []; + +$texy->addHandler('beforeParse', function( + Texy\Texy $texy, + string &$text, + bool $isSingleLine, +) use (&$stats) { + $stats = ['images' => 0, 'links' => 0, 'headings' => 0]; +}); + +$texy->addHandler('image', function( + Texy\HandlerInvocation $invocation, + Texy\Image $image, + ?Texy\Link $link, +) use (&$stats) { + $stats['images']++; + return $invocation->proceed(); +}); + +$texy->addHandler('linkURL', function( + Texy\HandlerInvocation $invocation, + Texy\Link $link, +) use (&$stats) { + $stats['links']++; + return $invocation->proceed(); +}); + +$texy->addHandler('heading', function( + Texy\HandlerInvocation $invocation, + int $level, + string $content, + Texy\Modifier $modifier, + bool $isSurrounded, +) use (&$stats) { + $stats['headings']++; + return $invocation->proceed(); +}); +``` + + +Pomocné třídy +============= + +Při práci s handlery budete pracovat s několika důležitými třídami. Zde je jejich přehled s nejdůležitějšími vlastnostmi. + + +Texy\Image +---------- + +Reprezentuje obrázek s jeho parametry: + +```php +$image->URL; // string - cesta k obrázku +$image->linkedURL; // ?string - URL odkazu (pokud je obrázek odkazem) +$image->width; // ?int - šířka v pixelech +$image->height; // ?int - výška v pixelech +$image->asMax; // bool - zda jsou rozměry maximální +$image->modifier; // Modifier - CSS třídy, styly, atributy +$image->name; // ?string - název reference +``` + + +Texy\Link +--------- + +Reprezentuje odkaz s jeho parametry: + +```php +$link->URL; // string - cílová URL +$link->raw; // string - původní URL (před normalizací) +$link->modifier; // Modifier - CSS třídy, styly, atributy +$link->type; // string - typ odkazu (COMMON, BRACKET, IMAGE) +$link->label; // ?string - text odkazu (u referencí) +$link->name; // ?string - název reference +``` + +Konstanty pro typ odkazu: + +```php +Texy\Link::COMMON; // běžný odkaz +Texy\Link::BRACKET; // referenční odkaz [ref] +Texy\Link::IMAGE; // odkaz z obrázku [* img *] +``` + + +Texy\HtmlElement +---------------- + +Reprezentuje HTML element s jeho atributy a obsahem: + +```php +$el = new Texy\HtmlElement('div'); + +// práce s názvem elementu +$el->getName(); // vrací 'div' +$el->setName('section'); // změní na 'section' + +// práce s atributy +$el->attrs['id'] = 'main'; +$el->attrs['class'][] = 'container'; +$el->attrs['style']['color'] = 'red'; + +// práce s obsahem +$el->setText('text'); // nastaví textový obsah +$el->getText(); // vrací textový obsah +$el->add($child); // přidá potomka +$el->insert(0, $child); // vloží potomka na pozici + +// parsování obsahu +$el->parseLine($texy, $text); // parsuje inline text +$el->parseBlock($texy, $text); // parsuje blokový text + +// konverze na HTML +$el->toString($texy); // internal reprezentace +$el->toHtml($texy); // finální HTML +``` + + +Texy\Modifier +------------- + +Reprezentuje modifikátory CSS tříd, stylů a atributů: + +```php +$mod->id; // ?string - HTML id +$mod->classes; // array - pole CSS tříd +$mod->styles; // array - pole CSS stylů +$mod->attrs; // array - HTML atributy +$mod->hAlign; // ?string - horizontální zarovnání (left, right, center, justify) +$mod->vAlign; // ?string - vertikální zarovnání (top, middle, bottom) +$mod->title; // ?string - title atribut nebo alt pro obrázky + +// aplikace modifikátoru na element +$mod->decorate($texy, $element); +``` diff --git a/texy/cs/custom-syntax.texy b/texy/cs/custom-syntax.texy new file mode 100644 index 0000000000..f8f7634b5c --- /dev/null +++ b/texy/cs/custom-syntax.texy @@ -0,0 +1,519 @@ +Přidání vlastní syntaxe +*********************** + +.[perex] +Tato kapitola popisuje, jak přidat do Texy **zcela nové markup konstrukce**, které standardně neexistují. Pokud chcete pouze změnit chování existujících prvků (například upravit zpracování obrázků nebo odkazů), přečtěte si kapitolu [Úprava chování prvků |custom-handlers]. + +Představte si, že chcete v dokumentaci automaticky vytvářet odkazy na uživatelské profily zápisem `@@username`. Nebo potřebujete speciální bloky pro upozornění typu `:::warning`. Texy tyto konstrukce nezná a nemůžete je vytvořit úpravou existujících prvků. + +Vlastní syntaxe vám umožní definovat nové markup konstrukce. Zadáte, jak má konstrukce vypadat (pomocí regulárního výrazu), a napíšete funkci, která ji zpracuje. Texy pak vaši syntaxi rozpozná stejně jako své standardní konstrukce. + + +Registrace syntaxe +================== + +Texy poskytuje dvě metody pro registraci vlastní syntaxe podle toho, zda jde o inline nebo blokový prvek. + + +Line syntaxe +------------ + +Line syntaxe slouží pro inline konstrukce uvnitř řádků textu. Registrujete ji metodou `registerLinePattern()`: + +```php +$texy->registerLinePattern( + callable $handler, + string $pattern, + string $name, + ?string $againTest = null, +); +``` + +**Parametr `$handler`** je callback funkce, která se zavolá při nálezu syntaxe. Může to být název funkce, anonymní funkce nebo pole `[$object, 'method']`. + +**Parametr `$pattern`** je regulární výraz (PCRE), který definuje, jak vaše syntaxe vypadá v textu. Pattern by **neměl být kotvený** na začátek řádku (`^`), protože se hledá kdekoliv v textu. Použijte capturing groups pro zachycení dat, která potřebujete zpracovat. + +**Parametr `$name`** je unikátní název syntaxe. Používá se v poli `$texy->allowed` pro zapnutí/vypnutí a předává se do handleru pro identifikaci. Doporučujeme používat prefixový styl jako `custom/username` nebo `myapp/profile`. + +**Parametr `$againTest`** je volitelný regex pro optimalizaci. Pokud je zadán, Texy nejprve zkontroluje, zda text vůbec obsahuje něco, co by mohlo matchnout váš pattern. Teprve pokud `$againTest` uspěje, spustí se komplexnější pattern. To výrazně zrychlí zpracování, pokud máte složitý pattern a používá se jen zřídka. + +Příklad registrace: + +```php +$texy->registerLinePattern( + 'usernameHandler', + '#@@([a-z0-9_]+)#i', + 'custom/username', +); +``` + + +Block syntaxe +------------- + +Block syntaxe slouží pro víceřádkové blokové konstrukce. Registrujete ji metodou `registerBlockPattern()`: + +```php +$texy->registerBlockPattern( + callable $handler, + string $pattern, + string $name, +); +``` + +Parametry `$handler` a `$name` mají stejný význam jako u line syntaxí. + +**Parametr `$pattern`** je regulární výraz, který **musí být kotvený** na začátek řádku (`^`) a často i na konec (`$`). BlockParser automaticky přidá modifikátory `Am` (anchored, multiline), takže je do patternu nepřidávejte. Pattern by měl matchnout celý blok nebo alespoň jeho začátek. + +Příklad registrace: + +```php +$texy->registerBlockPattern( + 'alertHandler', + '#^:::(warning|info|danger)\n(.+)$#s', + 'custom/alert', +); +``` + + +Syntax handler +============== + +Syntax handler je funkce volaná parserem, když najde výskyt vaší syntaxe v textu. Jeho úkolem je zpracovat nalezená data a vrátit HTML element nebo řetězec. + +Podrobné vysvětlení role syntax handleru v architektuře Texy najdete v kapitole [Architektura a principy |architecture#syntax-handler]. + + +Pro line syntaxe +---------------- + +Signatura syntax handleru pro line syntaxe: + +```php +function( + Texy\LineParser $parser, + array $matches, + string $name, +): Texy\HtmlElement|string|null +``` + +**Parametr `$parser`** poskytuje přístup k parseru a Texy objektu. Nejčastěji použijete `$parser->getTexy()` pro získání Texy instance. + +**Parametr `$matches`** obsahuje výsledky regex matche. `$matches[0]` je celý matchnutý řetězec, `$matches[1]`, `$matches[2]` atd. jsou capturing groups z vašeho patternu. + +**Parametr `$name`** je název syntaxe, který jste zadali při registraci. Užitečné, pokud jeden handler zpracovává více syntaxí. + +**Návratová hodnota** může být `Texy\HtmlElement` pro strukturovaný HTML výstup, `string` pro přímý HTML kód (který musíte protectovat), nebo `null` pro odmítnutí zpracování. + +Handler může nastavit `$parser->again = true`, pokud chce, aby se obsah vytvořeného elementu znovu parsoval pro nalezení vnořených syntaxí. + + +Pro block syntaxe +----------------- + +Signatura syntax handleru pro block syntaxe: + +```php +function( + Texy\BlockParser $parser, + array $matches, + string $name, +): Texy\HtmlElement|string|null +``` + +Parametry mají stejný význam jako u line syntaxí, jen dostáváte `Texy\BlockParser` místo `LineParser`. + +BlockParser poskytuje metody pro práci s víceřádkovými strukturami: + +- **`$parser->next($pattern, &$matches)`** - matchne další řádek proti patternu a vrátí true/false +- **`$parser->moveBackward($lines)`** - vrátí se o zadaný počet řádků zpět +- **`$parser->isIndented()`** - vrací true, pokud je aktuální blok odsazený + + +LineParser API +============== + +Při práci s line syntaxemi máte k dispozici několik užitečných vlastností a metod. + +**Property `$again`** řídí, zda se má právě zpracovaná syntaxe hledat znovu na stejné pozici po zpracování. Výchozí hodnota je `false`. Nastavte na `true`, pokud vytváříte element s obsahem, který může obsahovat další syntaxe: + +```php +function( + Texy\LineParser $parser, + array $matches, + string $name, +): Texy\HtmlElement +{ + $el = new Texy\HtmlElement('span'); + $el->setText($matches[1]); + + // obsah může obsahovat další formátování + $parser->again = true; + + return $el; +} +``` + +**Metoda `getTexy()`** vrací instanci Texy objektu, což potřebujete pro práci s `protect()` nebo přístup ke konfiguraci. + + +BlockParser API +=============== + +Při práci s block syntaxemi máte k dispozici metody pro práce s víceřádkovými strukturami. + +**Metoda `next($pattern, &$matches)`** zkusí matchnout další řádek v textu proti zadanému patternu. Pokud uspěje, naplní `$matches` výsledkem a posune interní pozici za tento řádek. Vrací `true` při úspěchu, `false` při neúspěchu: + +```php +while ($parser->next('#^\-\s+(.+)$#', $matches)) { + // zpracuj další položku seznamu + $item = $matches[1]; +} +``` + +**Metoda `moveBackward($lines = 1)`** vrátí interní pozici o zadaný počet řádků zpět. Užitečné, když váš pattern matchnul víc než začátek bloku a chcete se vrátit na začátek: + +```php +// pattern matchnul 3 řádky, ale chceme číst od prvního +$parser->moveBackward(2); +``` + +**Metoda `isIndented()`** vrací `true`, pokud je aktuální blok odsazený (začíná mezerou nebo tabulátorem). To naznačuje, že jde o vnořený obsah. + + +Praktické příklady +================== + +Následující příklady ukazují reálné use-case pro vlastní syntaxe. + + +Uživatelské profily +------------------- + +Automatické vytváření odkazů na profily zápisem `@@username`: + +```php +$texy->registerLinePattern( + function( + Texy\LineParser $parser, + array $matches, + string $name, + ): Texy\HtmlElement + { + $username = $matches[1]; + + $el = new Texy\HtmlElement('a'); + $el->attrs['href'] = '/user/' . urlencode($username); + $el->attrs['class'][] = 'user-profile'; + $el->setText('@' . $username); + + return $el; + }, + '#@@([a-z0-9_]+)#i', + 'custom/username' +); +``` + +Použití v textu: + +```texy +Podívejte se na profil @@johndoe nebo @@jane_smith. +``` + + +Alert boxy +---------- + +Speciální bloky pro upozornění s různými typy: + +```php +$texy->registerBlockPattern( + function( + Texy\BlockParser $parser, + array $matches, + string $name, + ): Texy\HtmlElement + { + $type = $matches[1]; // warning, info, danger + $content = $matches[2]; + + $el = new Texy\HtmlElement('div'); + $el->attrs['class'][] = 'alert'; + $el->attrs['class'][] = 'alert-' . $type; + + $texy = $parser->getTexy(); + $el->parseBlock($texy, trim($content)); + + return $el; + }, + '#^:::(warning|info|danger)\n(.+?)(?=\n:::|$)#s', + 'custom/alert' +); +``` + +Použití v textu: + +```texy +:::warning +Toto je důležité upozornění! +::: + +:::info +Pro informaci: aktualizace proběhne zítra. +::: +``` + + +Hashtagy +-------- + +Automatické vytváření odkazů z hashtagů: + +```php +$texy->registerLinePattern( + function( + Texy\LineParser $parser, + array $matches, + string $name, + ): Texy\HtmlElement + { + $tag = $matches[1]; + + $el = new Texy\HtmlElement('a'); + $el->attrs['href'] = '/tag/' . urlencode($tag); + $el->attrs['class'][] = 'hashtag'; + $el->setText('#' . $tag); + + return $el; + }, + '#\#([a-z0-9_]+)#i', + 'custom/hashtag', + '#\##' // optimalizace - hledej jen pokud je # v textu +); +``` + +Použití: + +```texy +Článek o #php a #webdesign. +``` + + +Zkratky +------- + +Automatické rozbalení zkratek s vysvětlením: + +```php +$abbreviations = [ + 'HTML' => 'HyperText Markup Language', + 'CSS' => 'Cascading Style Sheets', + 'PHP' => 'PHP: Hypertext Preprocessor', +]; + +$texy->registerLinePattern( + function( + Texy\LineParser $parser, + array $matches, + string $name + ) use ($abbreviations): ?Texy\HtmlElement + { + $abbr = $matches[1]; + + if (!isset($abbreviations[$abbr])) { + return null; // neznámá zkratka + } + + $el = new Texy\HtmlElement('abbr'); + $el->attrs['title'] = $abbreviations[$abbr]; + $el->setText($abbr); + + return $el; + }, + '#\b([A-Z]{2,})\b#', + 'custom/abbreviation' +); +``` + + +Inline ikony +------------ + +Vkládání ikon pomocí speciální syntaxe: + +```php +$texy->registerLinePattern( + function( + Texy\LineParser $parser, + array $matches, + string $name, + ): Texy\HtmlElement + { + $icon = $matches[1]; + + $el = new Texy\HtmlElement('i'); + $el->attrs['class'][] = 'icon'; + $el->attrs['class'][] = 'icon-' . $icon; + $el->attrs['aria-hidden'] = 'true'; + + return $el; + }, + '#:icon-([a-z-]+):#', + 'custom/icon' +); +``` + +Použití: + +```texy +Klikněte na tlačítko :icon-download: pro stažení. +``` + + +Poznámkový blok +--------------- + +Blok pro poznámky pod čarou: + +```php +$texy->registerBlockPattern( + function( + Texy\BlockParser $parser, + array $matches, + string $name + ): Texy\HtmlElement + { + $parser->moveBackward(); + + $content = ''; + while ($parser->next('#^NOTE:\s*(.+)$#', $matches)) { + $content .= $matches[1] . "\n"; + } + + $el = new Texy\HtmlElement('aside'); + $el->attrs['class'][] = 'note'; + + $texy = $parser->getTexy(); + $el->parseBlock($texy, trim($content)); + + return $el; + }, + '#^NOTE:\s*(.+)$#m', + 'custom/note' +); +``` + +Použití: + +```texy +NOTE: Toto je důležitá poznámka. +NOTE: Může být víceřádková. +``` + + +Vlastní citace s autorem +------------------------ + +Rozšířená syntaxe pro citace s uvedením autora: + +```php +$texy->registerBlockPattern( + function( + Texy\BlockParser $parser, + array $matches, + string $name, + ): Texy\HtmlElement + { + $author = $matches[1]; + $quote = $matches[2]; + + $blockquote = new Texy\HtmlElement('blockquote'); + + $texy = $parser->getTexy(); + $blockquote->parseBlock($texy, trim($quote)); + + $cite = new Texy\HtmlElement('cite'); + $cite->setText($author); + $blockquote->add($cite); + + return $blockquote; + }, + '#^QUOTE\[([^\]]+)\]:\n(.+?)(?=\n\n|$)#s', + 'custom/quote' +); +``` + +Použití: + +```texy +QUOTE[Albert Einstein]: +Fantazie je důležitější než vědění, +protože vědění je omezené. +``` + + +Galerie obrázků +--------------- + +Speciální blok pro vytvoření galerie z více obrázků: + +```php +$texy->registerBlockPattern( + function( + Texy\BlockParser $parser, + array $matches, + string $name, + ): Texy\HtmlElement + { + $parser->moveBackward(); + + $gallery = new Texy\HtmlElement('div'); + $gallery->attrs['class'][] = 'gallery'; + + while ($parser->next('#^\[G\]\s*(.+)$#', $matches)) { + $img = new Texy\HtmlElement('img'); + $img->attrs['src'] = trim($matches[1]); + $img->attrs['loading'] = 'lazy'; + $gallery->add($img); + } + + return $gallery; + }, + '#^\[G\]\s*(.+)$#m', + 'custom/gallery' +); +``` + +Použití: + +```texy +[G] image1.jpg +[G] image2.jpg +[G] image3.jpg +``` + + +Kolize syntaxí +============== + +Když registrujete vlastní syntaxi, musíte dávat pozor, aby nekolidovala s existujícími syntaxemi Texy nebo s jinými vlastními syntaxemi. + +**Pořadí registrace záleží.** Line syntaxe se hledají v pořadí, jak byly registrovány. Pokud více syntaxí může matchnout na stejné pozici, vyhrává ta, která byla registrována dříve. Proto registrujte specifičtější syntaxe před obecnějšími. + +**Buďte specifičtí v patterns.** Čím konkrétnější je váš pattern, tím menší je riziko kolize. Pattern `#\#\w+#` matchne i `#hashtag`, což by mohlo kolidovat s nadpisy. Lepší je `#(?<=\s)\#[a-z0-9_]+#i`, který vyžaduje mezeru před hashtagem. + +**Testujte kombinace.** Vyzkoušejte, jak vaše syntaxe funguje v kombinaci s existujícími konstrukcemi. Co se stane, když je váš markup uvnitř odkazu? Co když je uvnitř code bloku? + +**Používejte prefixované názvy.** Místo `username` použijte `custom/username` nebo `myapp/username`. To zabrání konfliktům, pokud by Texy v budoucnu přidalo syntaxi stejného názvu. + + +Best practices +============== + +**Vracejte `null` při neúspěchu.** Pokud handler zjistí, že nemůže nebo nechce zpracovat daný match (například neznámá zkratka), vraťte `null`. Parser pak zkusí další syntaxe. + +**Používejte `protect()` pro HTML.** Pokud vracíte raw HTML string místo `HtmlElement`, musíte ho protectovat pomocí `$texy->protect($html, Texy::CONTENT_...)`. Jinak bude escapován. + +**Nastavte `$parser->again` správně.** Pro line syntaxe, které vytváří element s textovým obsahem, který může obsahovat další syntaxe (formátování, odkazy), nastavte `$parser->again = true`. + +**Respektujte `$texy->allowed`.** Pokud vytváříte modul s více syntaxemi, kontrolujte `$texy->allowed[$name]` před registrací patternu nebo v handleru před zpracováním. diff --git a/texy/cs/develop.texy b/texy/cs/develop.texy new file mode 100644 index 0000000000..cc388cd830 --- /dev/null +++ b/texy/cs/develop.texy @@ -0,0 +1,35 @@ +Pro programátory +**************** + +.[perex] +Vítejte v programátorské dokumentaci Texy! Tato sekce vás provede od základního použití až po pokročilé techniky rozšíření a vlastní syntaxe. + + +[Rychlý start | quickstart] +--------------------------- + +Instalace, první použití a základní konfigurace. Za 5 minut budete mít Texy funkční. + + +[Konfigurace | configuration] +----------------------------- + +Kompletní přehled všech modulů, jejich vlastností a konfiguračních možností. Nastavení bezpečnosti, povolených značek, stylů a tříd. + + +[Úprava chování prvků | custom-handlers] +---------------------------------------- + +Naučte se měnit chování existující syntaxe. YouTube embedování, syntax highlighting, custom validace. + + +[Přidání vlastní syntaxe | custom-syntax] +----------------------------------------- + +Vytvoření zcela nových syntaktických prvků. + + +[Architektura a principy | architecture] +---------------------------------------- + +Pochopení toho, jak Texy interně funguje. Parsing flow, moduly, pattern matching, protect/unprotect mechanismus. diff --git a/texy/cs/napsali-o-texy.texy b/texy/cs/napsali-o-texy.texy new file mode 100644 index 0000000000..c5906f0ba8 --- /dev/null +++ b/texy/cs/napsali-o-texy.texy @@ -0,0 +1,160 @@ +Napsali o Texy +************** + +{{nofollow:yes}} + +> Při volbě formátovače textu pro poslední dva weby jsme zkusili oproti dříve používaným WYSIWYG editorům implementovat systém Texy Davida Grudla, a nestačili jsme se divit. Neuvěřitelně komplexní formátovací možnosti, úžasná podpora české typografie a předem připravené instalační balíčky dělají Texy vynikajícím publikačním doplňkem. +> +> [Jan Brašna Alphanumeric | http://www.alphanumeric.cz] (3. 5. 2005) + + +> Dobrý den, s Texy jsem neuvěřitelně spokojen. Mnohokráte děkuji. +> +> Využíváme Texy jak na firemních stránkách www.logio.cz v sekci Novinky tak na našem novém projektu www.skladuj.cz. +> +> Dokonce si někteří kolegové navykli posílat příspěvky již předformátované v emailu. Což je neuvěřitelné. +> +> [Tomáš Formánek | http://www.skladuj.cz] (10. 4. 2006) + + +> Zdravím. Texy jsem začal používat na nokturno.net - je to luxusní věcička, hlavně dá minimálně práce zakomponovat jej do systému. +> +> [Jiří Reiter | http://www.nokturno.net] (5. 3. 2006) + + +> Použil jsem jej pro formátování aktuálních zpráviček na našem firemním webu. Celé zahrnutí trvalo cca hodinu, včetně pochopení o co jde a zavedení příznaku pro přepínání pro starší zprávy v HTML. Zadávání je teď výrazně intuitivnější a je menší riziko "rozhození" formátování stránek při ev. chybě v textu zprávy. +> +> Brilantní kus kódu, jak návrh, tak realizace. +> +> [Ing. Zdeněk Trojánek | http://www.daisy.cz] (3. 3. 2006) + + +> Nejprve jsem chtěl do svého implementovat nějaký wysiwyg editor, ale našel jsem Texy. +> +> Něco podobného jsem zatím nevidel, uplně nadchnul. Dokud nevyzkoušíte neuvěříte. +> +> [Petr Čada | http://error414.php5.cz/] (24. 11. 2005) + + + +> Texy je velmi šikovná a praktická věc, která dokáže i překvapit... spokojenost je určitě na místě. +> +> [Petr Vlček | http://saabinfo.net] (4. 10. 2005) + + +> Na Texy ma prekvapila jeho komplexnosť a sila. Umožňuje formátovať text akokoľvek len chcete, pritom dbá aj na správne postavenie predložiek a rozdelenie dlhých slov atď. Rozhodne Texy vyskúšajte. +> +> [Michal Poppe | http://www.mipo.ssag.sk/zapisnik/webowiny/2005-02-08-texy-konecne-vonku.html] (8. 2. 2005) + + +> Texy jsem implementoval do nekomerční obrázkové encyklopedie. Texy se mi stará o doprovodné texty k tématickým sekcím a já mám z toho, jak to dělá (dělá to hezky sexy ;-) ), **velikou radost**. Texy mi ušetří čas, a tak mám Texy rád a autorovi za něj děkuji tímto a ikonkou. +> +> Hlavně oceňuji logickou a jenoduchou syntaxi, jednoduchou implementaci do jiných php systémů, komplexnost. :) +> +> [Robert Nový | http://www.jablicko.cz] (18. 4. 2005) + + +> Texy používám už delší dobu jako svou hlavní pomůcku pro převod textů do HTML, což dělám v práci vlastně denně. +> +> [Jirka Chomát | http://www.chomat.net/articles/trublog] (5. 3. 2005) + + +> Ano je to tak. Skutečně používáme tento skvělý "převaděč textu do formátovaného HTML kódu". +> +> [WordPress CZ | http://wordpress.cz] (24. 2. 2005) + + +> **Elegance v phpRS .. to je Texy** Osobně mi vůbec nevadí psát příspěvky včetně TAGů, mám potom vše pod kontrolou a vím "wo co de !". Proč si ale neušetřit práci použitím formátovače Texy. Přeci jen jsem občas líný tvor a Texy je podle mě opravdu super. Celá operace "zasunutí" Texy do phpRS je velice jednoduchá +> +> [Pykaso | http://pykaso.net/?article=elegance-v-phprs-to-je-texy] (11. 3. 2005) + + +> Texy, skvělý nástroj od Davida Grudla (dgx), který usnadní práci nejen pisálkům, ale i těm, kteří komentují, jsem původně zamýšlel používat pouze k formátování komentářů - ode dneška jej používám i k formátování mnou napsaných článků. +> +> A co mě k tomu vedlo? Pohodlost! Člověk by nevěřil, jak je všechno najednou jednoduché +> +> [Luboš Bretschneider | http://www.bretik.com/?page=article&article=Texy-je-sexy-Texy-je-cool] (2. 3. 2005) + + +> Texy jsem si zaimplementoval do webu spíš jen tak. Chtěl jsem ho vyzkoušet a napadlo mě, že to můžu zkusit rovnou v reálu. Nečekal jsem nic nepřevratného, a to byla chyba. Texy mě úplně vzalo. +> +> Dnes už vůbec neuvažuji nad WYSIWYG editorem. Texy je pro mě jasná volba pro jednoduché, ale i složité formátování textů do XHTML. Což je věc, kterou Texy zvládá na jedničku! +> +> [Lukáš Knop, Knopdesign | http://www.knopdesign.net] (17. 3. 2005) + + +> **Texy je opravdu sexy** Texy využívám i já. Texy totiž není program jen pro neznalé HTML a počítačové analfabety, ale i pro ostřílené webdevelopery. Dnes jsem ho využil k převedení dlouhého textu do kódu pro jedny stránky, na kterých teď usilovně pracuji. Usnadní mi nudnou práci a tím pádem jsem o něco efektivnější... +> +> [Ondřej Kůrka | http://bernardyn.bloguje.cz/109088_item.php/] (1. 2. 2005) + + +> Články se publikují v podstatě sami, protože využívám Texy, jehož stvořitelem je David Grudl. +> +> [Lazy Byte | http://lazybyte.wz.cz/blog/za%c4%8dinam-blogovat] (6. 3. 2005) + + +> Kód je napsán hezky objektově a přehledně, navíc řekl bych i hodně univerzálně, autor s řadou věcí počítá a tak je radost s Texy pracovat. Podpora UTF-8 hned v základu a hlavně dgx vážně nekecal, když psal, že zapracování do kódu bude snadné. +> +> [Vojtěch Schlesinger | http://www.php-weblog.com] (1. 2. 2005) + + +> Budu se ale muset hodně ovládat, abych nepsal s dokonalou Texy syntaxí ... tedy ne že bych ji já psal dokonale, ale že ona je dokonalá. S klidem přiznám, že jsem z Texy už několik dní nepokrytě nadšený. Takový vybroušený kousek php kódu jsem ještě neviděl. +> +> [Juneau | http://reality-show.net/?text=486-a-tak-si-mezi-programovanim-blognu] (26. 2. 2005) + + +> Texy hodnotím výborně, velice mi usnadňuje psaní článků. A plugin pro BLOG:CMS je taky super věc! :-) DĚKUJU ZA TEXY! :-) +> +> [Miroslav Navrátil | http://kanevinternetu.blacksuns.net/] (14. 3. 2005) + + +> ... umožní jednoduché a intuitivní formátování textu bez znalosti HTML, čistě za použití plain textu. ... Implementace systému do existující PHP aplikace už snad ani nemůže být jednodušší, stačí includovat jeden soubor a vytvořit jeden objekt, Texy se postará o zbytek +> +> [Adam Šindelář, Root.cz | http://www.root.cz/clanky/nova-softwarova-sklizen-16-3-2005/] (16. 3. 2005) + + +> Jsem se zas jednou nudil, serfoval po netu a narazil na Texy, mno a to mě tak nadchlo až jsem z toho začal předělávat celej webík. Texy vřele doporučuji všem, kteří jsou aspoň z poloviny tak líní jako já. Je vhodný jak pro laiky tak pro zkušené programátory. Je prostě sexy! +> +> [Rozi.net | http://www.rozi.net/text-32.html] (11. 3. 2005) + + +> Izsak's Weblog používa na formátovanie článkov a komentárov nový, jednoduchý a komplexný systém Texy. +> +> [Jozef Izso | http://www.izsak.net/weblog/47/prechod-na-textpattern] (23. 2. 2005) + + +> Zdravím! Texy využívám v mém blogu, sám bych texy asi nedokázal zasadit do nějakého rs, ale RS2 ho obsahuje a tak ho využívám, jsem naprosto spokejen, texy mi vyhovuje, práce s ním je hned příjemnější. Přeji hodně úspěchů. +> +> [Martin Světlík alias QuickShare | http://blog.msvetlik.com] (20. 4. 2006) + +> Cau, ted sem se dostal k Texy! Zatim pouzivam WYSIWYG editor napsany v JS. Dobry, jen nekdy pomaly a ten kod taky nic moc. Navic JS pouzivam nerad. Cetl sem si jak je to udelany a musim rict ze uvazuju nad tim ze bych to cely nahradil :o) +> +> Kanadsky bod pro tebe...kdyz sem se dival na formatovani tabulek... no musel jsi s tim mit strasnou praci. +> +> Keep on ;) +> +> [Marek | liq@quick.cz] (17. 11. 2005) + + +> Konečně mám (po hokeji) zase jednou důvod být hrdý, že jsem Čech, stejně jako autor Texy ;) Moc pěkný kousek software! Smekám... +> +> [Pavel Beníšek | http://www.3dgrafika.cz] (17. 3. 2005) + + +Dále Texy používá +----------------- + +- síť obchodů [Internet Mall | http://www.mall.cz/] +- [H1.cz | http://www.h1.cz/] +- [Vitalita | http://www.vitalita.cz] +- [Václavák | http://www.vaclavak.net] +- [Rarouš weblog | http://rarous.net/] (běží na ASP.NET) +- [La Trine | https://www.latrine.cz] +- **...a stovky dalších webů** + + +Texy najdete v sytémech: +------------------------ + +- [Český TextPattern | http://www.vaclavak.net/weblog/23/textpattern-pro-ceske-prostredi] +- Český WordPress diff --git a/texy/cs/quickstart.texy b/texy/cs/quickstart.texy new file mode 100644 index 0000000000..631e336406 --- /dev/null +++ b/texy/cs/quickstart.texy @@ -0,0 +1,205 @@ +Rychlý start +************ + +.[perex] +Naučte se pracovat s Texy za pár minut. Tato stránka vás provede instalací, prvním použitím a základní konfigurací. + + +Instalace +========= + +Texy využívá moderních vlastností PHP a vyžaduje minimálně verzi 8.1. + +Nejjednodušší způsob instalace je přes Composer: + +```bash +composer require texy/texy +``` + +Composer automaticky stáhne Texy a všechny závislosti. + + +První použití +============= + + +Základní zpracování textu +------------------------- + +Vytvoření instance Texy a zpracování textu je extrémně jednoduché: + +```php +require __DIR__ . '/vendor/autoload.php'; + +$texy = new Texy\Texy; + +$text = 'Toto je **tučný text** a toto //kurzíva//.'; +$html = $texy->process($text); + +echo $html; +``` + +Výstup: +```html +<p>Toto je <strong>tučný text</strong> a toto <em>kurzíva</em>.</p> +``` + +Metoda `process()` zpracuje celý text včetně blokových elementů (odstavce, nadpisy, seznamy, tabulky...). + + +Jednořádkový text +----------------- + +Pokud zpracováváte pouze jednořádkový text bez blokových elementů (například nadpisy v databázi, krátké popisky): + +```php +$texy = new Texy\Texy; + +$text = 'Odkaz na "homepage":https://example.com'; +$html = $texy->processLine($text); + +echo $html; +``` + +Výstup: +```html +Odkaz na <a href="https://example.com">homepage</a> +``` + +Metoda `processLine()` nezabaluje výstup do odstavce `<p>` a zpracuje pouze inline elementy. + + +Základní konfigurace +==================== + +Texy funguje "out of the box", ale často budete chtít upravit základní nastavení. + + +Nastavení cest k obrázkům +------------------------- + +Pokud používáte relativní cesty k obrázkům, nastavte kořenový adresář: + +```php +$texy = new Texy\Texy; + +// Cesta na webu (přidá se před relativní URL) +$texy->imageModule->root = '/images/'; + +// Fyzická cesta na disku (pro zjištění rozměrů) +$texy->imageModule->fileRoot = __DIR__ . '/public/images/'; +``` + +Teď když napíšete `[* photo.jpg *]`, Texy vygeneruje `<img src="/images/photo.jpg">` a automaticky zjistí rozměry obrázku. + + +Nastavení cest k odkazům +------------------------ + +Podobně můžete nastavit kořenový adresář pro odkazy: + +```php +$texy->linkModule->root = '/articles/'; +``` + + +Povolení a zakázání syntaxí +--------------------------- + +Každá část Texy syntaxe lze vypnout nebo zapnout pomocí pole `$allowed`: + +```php +$texy = new Texy\Texy; + +// Vypnout obrázky +$texy->allowed['image'] = false; + +// Vypnout HTML značky ve vstupu +$texy->allowed['html/tag'] = false; + +// Povolit emotikony (ve výchozím stavu vypnuté) +$texy->allowed['emoticon'] = true; +``` + +Kompletní seznam syntaxí najdete v [konfiguraci | configuration#allowed]. + + +Bezpečný režim pro uživatelský obsah +------------------------------------ + +Pokud zpracováváte obsah od uživatelů (komentáře, příspěvky na fóru), použijte bezpečný režim: + +```php +$texy = new Texy\Texy; +Texy\Configurator::safeMode($texy); + +$userInput = $_POST['comment']; +$html = $texy->process($userInput); +``` + +SafeMode: +- Povolí jen **bezpečné HTML značky** (`<strong>`, `<em>`, `<a>`, ...) +- Zakáže **třídy a ID** +- Zakáže **inline styly** +- Zakáže **obrázky** +- Přidá `rel="nofollow"` ke všem odkazům +- Filtruje **URL schémata** (jen `http:`, `https:`, `ftp:`, `mailto:`) + +Více o bezpečnosti v kapitole [Konfigurace – Bezpečnost |configuration#Bezpečnost]. + + +Kompletní příklad +================= + +```php +require __DIR__ . '/vendor/autoload.php'; + +$texy = new Texy\Texy; + +// Konfigurace +$texy->imageModule->root = '/images/'; +$texy->linkModule->root = '/'; +$texy->allowed['html/tag'] = false; + +// Text k zpracování +$text = ' + + +Nadpis článku +============= + +Toto je **úvodní odstavec** s odkazem na "homepage":https://example.com. + +- První položka +- Druhá položka +- Třetí položka + +[* photo.jpg .(Fotografie) *] +'; + +// Zpracování +$html = $texy->process($text); + +// Výstup +echo $html; + +// Dodatečné informace +echo "Titulek stránky: " . $texy->headingModule->title; +print_r($texy->summary['links']); +print_r($texy->summary['images']); +``` + +Po zpracování máte k dispozici: +- `$texy->headingModule->title` – první nadpis (vhodné pro `<title>`) +- `$texy->summary['links']` – pole všech použitých odkazů +- `$texy->summary['images']` – pole všech použitých obrázků + + +Další kroky +=========== + +Teď už víte, jak Texy používat. Pokračujte: + +- **[Konfigurace | configuration]** – podrobné nastavení všech modulů +- **[Syntaxe | syntax]** – naučte se Texy markup +- **[Architektura | architecture]** – pochopte, jak Texy funguje uvnitř diff --git a/texy/cs/syntax.texy b/texy/cs/syntax.texy new file mode 100644 index 0000000000..7c481b8468 --- /dev/null +++ b/texy/cs/syntax.texy @@ -0,0 +1,891 @@ +Syntaxe +******* + +.[perex] +Texy vznikl proto, aby nezkušeným uživatelům umožnil snadno editovat obsah webových stránek. Proto je i syntaxe intuitivní a přehledná. + + +Cheat Sheet +=========== + +| [#Formátování textu] | Syntax +|----------------------------------------------- +| [Tučný text |#Formátování textu] | .[text-code] ''**tučný text**'' +| [Kurzíva |#Formátování textu] | ''*kurzíva*'' nebo ''//kurzíva//'' +| [Inline code |#Formátování textu] | ''`kód`'' +| [#Odkazy] | ''"text":URL'' nebo ''[text](URL)'' +| [#Obrázky] | ''[* image.jpg *]'' +| [#Vypnutí formátování] | ''specialní znaky'' +|----------------------------------------------- +| Elementy +|----------------------------------------------- +| [#Podtržené nadpisy] | H1 <br> === +| [#Ohraničené nadpisy] | ''### H1'' <br> ## H2 +| [#Odrážkové seznamy] | ''- první'' <br> ''- druhá'' +| [#Číslované seznamy] | ''1) první'' <br> ''2) druhá'' +| [#Seznamy definic] | term: <br>   ''- první'' +| [#Citace] | ''> blockquote'' +| [#Horizontální čáry] | ''---'' +| [#Tabulky] | ''\| buňka \| buňka \|'' +| [Bloky kódu|#Předformátovaný text] | ''/--'' <br> ... <br> ''\--'' +|----------------------------------------------- +| Modifikátory .[#toc-modifikatory] +|-------------------------------------------------------- +| titulek | ''.(titulek)'' +| CSS třída | ''.[btn btn-primary]'' +| ID | ''.[#id]'' +| CSS styl nebo HTML atribut | ''.{color: blue}'' nebo ''.{target: _blank}'' +| horizontální zarovnání | ''.< .> .<> .='' +| vertikální zarovnání | ''.^ .- ._'' + + +Odstavce textu +============== + +Za odstavec považuje Texy jeden nebo více řádků textu, které následují těsně za sebou. Jakmile mezi nimi necháte **jeden prázdný řádek**, Texy automaticky pochopí, že má začít nový odstavec. + +To znamená, že Texy spojí řádky, které patří k sobě. Nemusíte se tak bát, že se vám věta zalomí uprostřed, když si zmenšíte okno editoru. + +```texy +Toto je první odstavec. Může mít klidně více řádků +a Texy je spojí do jednoho souvislého bloku textu. + +Až tady, po prázdném řádku, začíná úplně nový, druhý odstavec. +``` + +Spojování řádků lze nicméně vypnout v konfiguraci a pak se každý řádek považuje za samostatný odstavec: + +/--php +$texy->mergeLines = false; +\-- + + +Zalomení řádků +-------------- + +Co když ale potřebujete text jen odřádkovat, aniž byste vytvářeli celý nový odstavec? To se typicky hodí u básní, textů písní nebo při psaní adresy. **Začněte nový řádek jednou mezerou**. + +```texy +Karel Novák, + U Tiché pošty 5 + 150 00 Praha 5 +``` + + +Stylování odstavců +------------------ + +Někdy potřebujete celý odstavec nějak odlišit – například z něj udělat úvodní perex článku, vycentrovat ho nebo mu přiřadit specifický styl pro rámeček. K tomu slouží [#modifikátory], které můžete umístit buď na samostatný řádek **před** odstavec, nebo na konec jeho posledního řádku. + +```texy +.[perex] +Toto je úvodní odstavec článku, který díky modifikátoru +dostane CSS třídu "perex" a může tak vypadat jinak než zbytek textu. + +Tento odstavec má zase přiřazené unikátní ID. .[#sekce-uvod] + +A tento odstavec bude vycentrován. .<> +``` + + +Formátování textu +================= + +| syntax | výstup | ID syntaxe +|----------------------------------------------------------------------------- +| .[text-code] ''**tučný text**'' | **tučný text** | `phrase/strong` +| ''*kurzíva* nebo //kurzíva//'' | *kurzíva* | `phrase/em-alt`, `phrase/em` +| ''***tučná kurzíva***'' | ***tučná kurzíva*** | `phrase/strong+em` +| ''`inline kód`'' | `inline kód` | `phrase/code` +| ''x^2 … O_2'' | x^2 … O_2 | `phrase/sup-alt`, `phrase/sub-alt` +| ''x^^2^^ … O__2__'' | x^2 … O_2 | `phrase/sup`🔸, `phrase/sup`🔸 +| ''++vložený text++'' | <ins>vložený text</ins> | `phrase/ins`🔸 +| ''--smazaný text--'' | <del>smazaný text</del> | `phrase/del`🔸 +| ''>>citovaný text<<'' | >>citovaný text<< | `phrase/quote` +| ''"modrý text .{color: blue}"'' | "modrý text .{color: blue}" | `phrase/span` +| ''~modrý text .{color: blue}~'' | ~modrý text .{color: blue}~ | `phrase/span-alt` +| ''"et al."((a další))'' | "et al."((a další)) | `phrase/acronym` +| ''NBA((National Basketball Association))'' | NBA((National Basketball Association)) | `phrase/acronym-alt` + +Syntaxe označené 🔸 nejsou ve výchozím stavu povolené a musíte je zapnout. Příklad: + +/--php +$texy->allowed['phrase/ins'] = true; +\-- + +Pro jednoduché číselné indexy můžete použít zkrácenou syntaxi `x^2` a `O_2`, ale pro složitější případy je robustnější varianta s dvojitými znaky, nebo můžete použít HTML značky `<sup>` a `<sub>`. + +Uvnitř syntaktických znaků **nesmí být mezery**: + +```texy +Špatně: ** toto nebude tučné ** +Správně: **toto bude tučné** +``` + + +Stylování textu +--------------- + +Tohle je jedna z nejsilnějších vlastností Texy. Ke každému formátovanému textu můžete "přilepit" [#modifikátory] a přidat mu tak CSS třídu, ID nebo přímý styl. Modifikátor se vždy vkládá **těsně před uzavírací značku**: + +```texy +Tento text je **silný a zelený .{color:green}** jako Hulk. + +Upozornění: --Tato funkce je zastaralá .[deprecated]-- +``` + +Pokud chcete aplikovat modifikátor na text, ale nechcete ho zároveň dělat tučným nebo kurzívou, použijte jako obalovací značku uvozovky `"` nebo vlnovky `~`. Texy z toho vytvoří univerzální HTML značku `<span>` s vašimi styly: + +```texy +Běžný text, ale "tento kousek je červený .{color: red}", a zbytek už ne. +``` + + +Formátování a odkazy v jednom +----------------------------- + +Z formátovaného textu můžete udělat odkaz - jednoduše přidejte dvojtečku a URL adresu: + +```texy +Navštivte naši **novou galerii**:https://example.com/gallery +``` + +Toto funguje pro tučný text, kurzívu, inline kód. + + +Psaní speciálních znaků +----------------------- + +Co když chcete napsat doslova `**text**` včetně hvězdiček, aniž by se z něj stal tučný text? Máte tři možnosti: + +- zpětné lomítko je nejrychlejší způsob, jak "zneplatnit" jeden speciální znak `\**text\**` +- dvojité apostrofy [vypnou Texy|#Vypnutí formátování] pro celou frázi `''**text**''` +- můžete použít standardní HTML entity `**text**` + + +Odkazy +====== + +Odkazy jsou duší internetu. V Texy je jejich tvorba navržena tak, aby byla co nejpřirozenější a nejpřehlednější přímo v textu. + +Základní syntaxe pro odkaz je jednoduchá a skvěle čitelná. Odkazovaný text uzavřete do `"` (nebo jiných znaků pro [#formátování textu]) a hned připojíte dvojtečku a cílovou URL adresu: + +```texy +Navštivte oficiální stránky projektu "Nette Framework":https://nette.org. + +Pokud máte dotaz, "napište nám":info@example.com. +``` + +Výhodou je, že Texy je inteligentní a samo pozná, kde URL končí. Nemusíte se tedy bát, že by do odkazu omylem zahrnulo tečku nebo čárku na konci věty. Pokud ale URL obsahuje nestandardní znaky, můžete je uzavřít do hranatých závorek a tím přesně řeknete, kde adresa začíná a končí: + +```texy +"Přečtěte si náš článek":[https://example.com/novinky?id=1&kategorie=články] +``` + +ID syntaxe `phrase/span`, `phrase/span-alt` | [PhraseModule |configuration#phrasemodule] a [LinkModule |configuration#linkmodule] + + +Alternativní syntaxe odkazů +--------------------------- + +Jste zvyklí na formát, který používá Markdown nebo Wikipedia? Texy rozumí i jim. Můžete si vybrat styl, který vám nejvíce vyhovuje. + +```texy +[Text odkazu](https://adresa.cz) // Styl známý z Markdownu +[Text odkazu | https://adresa.cz] // Styl známý z MediaWiki +text:[cílová URL nebo reference] // Jednoslovný odkaz +``` + +ID syntaxe `phrase/markdown`, `phrase/wikilink`, `phrase/quicklink` | [PhraseModule |configuration#phrasemodule] + + +Udržujte si pořádek s referencemi +--------------------------------- + +Při psaní delších textů může být nepohodlné vkládat dlouhé URL adresy přímo do odstavců – zhoršuje to čitelnost a přehlednost. Pro tyto případy má Texy **referenční odkazy**. + +V textu použijete pouze krátký, snadno zapamatovatelný název reference. A na konci dokumentu pak všechny tyto reference přehledně definujete. + +```texy +Doporučujeme si prostudovat "oficiální dokumentaci":[doc] a projít si "příklady syntaxe":[syntax]. +Celý projekt je postaven na [Nette]. + +​[doc]: https://texy.nette.org/cs/ "Dokumentace Texy!" +​[syntax]: https://texy.nette.org/cs/syntax +​[Nette]: https://nette.org +``` + +ID syntaxe `link/reference`, `link/definition` | [LinkModule |configuration#linkmodule] + + +Automatické odkazy +------------------ + +Kdykoli do textu napíšete URL adresu (začínající na `http://`, `https://`, `www.`) nebo e-mail, Texy ji automaticky rozpozná a převede na klikatelný odkaz. Nemusíte dělat vůbec nic. + +```texy +Náš web najdete na adrese www.example.com. +Pro podporu pište na support@example.com. +``` + +ID syntaxe `link/url`, `link/email` | [LinkModule |configuration#linkmodule] + + +Stylování odkazů +---------------- + +S [#modifikátory] můžete odkazům snadno přidávat další vlastnosti: + +```texy +"Externí odkaz .[external](Otevře se v novém okně){target:_blank}":https://google.com +``` + +Speciální třída `nofollow` přidá odkazu atribut `rel="nofollow"`, čímž dáváte vyhledávačům signál, aby tento odkaz nesledovaly. To se hodí například u odkazů v komentářích. + +```texy +"Odkaz, kterému nedůvěřuji .[nofollow]":https://example.com +``` + + +Automatické maskování e-mailů +----------------------------- + +Texy automaticky obfuskuje (maskuje) emailové adresy před spamboty: + +```html +<a href="mailto:info@example.com">info@<!-- -->example.com</a> +``` + +Toto chování můžete vypnout: + +/--php +$texy->obfuscateEmail = false; +\-- + + +Přímé HTML +========== + +Texy je navržen tak, abyste HTML nemuseli psát vůbec. Ale co když narazíte na situaci, kdy je přímé vložení HTML značky jednodušší, nebo potřebujete vytvořit něco, na co syntaxe Texy nestačí? Žádný problém. Texy vám dává naprostou svobodu kombinovat oba světy. + +Můžete plynule přecházet mezi Texy syntaxí a čistým HTML, kdykoli se vám to hodí. + +```texy +Toto je **tučný text** v Texy a toto je <strong>tučný text</strong> pomocí HTML. + +<div class="info-box"> + <h3>Můžete vkládat i celé komplexní bloky</h3> +</div> +``` + +Možná si říkáte, že vkládání přímého HTML může být riskantní. Co když uděláte chybu nebo někdo vloží škodlivý kód? Texy na to myslí a funguje jako inteligentní filtr a pomocník: + +- **Opravuje chyby:** Texy zajistí, aby byl výsledný kód vždy validní a nerozbil vám stránku. +- **Hlídá bezpečnost:** Texy má ve výchozím stavu seznam povolených značek a jejich atributů. Pokud se v kódu objeví neznámá značka nebo potenciálně nebezpečný atribut (např. `onclick`), Texy ho bezpečně odstraní. Chrání tak váš web před XSS útoky. +- **Zajišťuje konzistentní výstup:** Bez ohledu na to, jaký HTML kód vložíte, Texy se postará, aby byl výsledek vždy správně strukturovaný (well-formed). + +Tento ochranný štít si můžete přizpůsobit. Pomocí konfigurace `$texy->allowedTags` můžete přesně definovat, které HTML značky a atributy jsou na vašem webu povoleny a které ne. + +Máte tak plnou kontrolu nad tím, jaké HTML mohou například redaktoři používat, a zajišťujete tak konzistenci a bezpečnost celého webu. Více informací naleznete v sekci "konfigurace":configuration#allowedtags. + +ID syntaxe `html/tag`, `html/comment` | [HtmlModule |configuration#htmlmodule] + + +Nadpisy +======= + +Texy vám nabízí dva elegantní a intuitivní způsoby, jak nadpisy vytvářet: **podtržené** a **ohraničené**. + + +Podtržené nadpisy +----------------- + +Tento styl připomíná psací stroj. Jednoduše pod nadpis vložte podtržení (alespoň 3 znaky). O důležitosti titulku rozhoduje podtrhávací znak. Od nejvyšší po nejnižší jsou to tyto: `#` `*` `=` `-` + +```texy +Toto je nejdůležitější nadpis celého dokumentu +​################################################ + +A toto je nadpis druhé úrovně +​****************************** +``` + +ID syntaxe `heading/underlined` | [HeadingModule |configuration#headingmodule] + + +Ohraničené nadpisy +------------------ + +Tento způsob je velmi rychlý na psaní. Text nadpisu "zabalíte" mezi znaky `#` nebo `=`. Zde o úrovni nadpisu rozhoduje **počet** použitých znaků (2 až 7). Čím více znaků, tím důležitější nadpis. + +```texy +=== Nejdůležitější nadpis (H1) + +== Méně důležitý (H2) + +# Ještě méně důležitý (H3) +``` + +Můžete použít ohraničení na obou stranách (pro lepší vizuální přehlednost) nebo jen na začátku. Texy si s oběma variantami poradí. + +ID syntaxe `heading/surrounded` | [HeadingModule |configuration#headingmodule] + + +Stylování nadpisů +----------------- + +Ke každému nadpisu můžete přidat [#modifikátory]. To vám umožní přiřadit mu konkrétní CSS třídu pro stylování nebo unikátní ID, na které pak můžete odkazovat. + +```texy +Nadpis s červenou barvou .[cerveny-nadpis] +​========================================== + +### Nadpis s unikátním ID pro odkazování .[#kontakt] +``` + + +Automatické kotvy pro snadnou navigaci +-------------------------------------- + +Nechcete vymýšlet ID pro každý nadpis ručně? Texy to umí udělat za vás! V konfiguraci můžete zapnout automatické generování ID pro všechny nadpisy. To je neuvěřitelně užitečné pro přímé odkazování na konkrétní sekce. + +```php +// Povolit automatické generování ID +$texy->headingModule->generateID = true; + +// Volitelně nastavit předponu pro generovaná ID (např. "sekce-") +$texy->headingModule->idPrefix = 'toc-'; +``` + +S tímto nastavením nadpis `## Moje kapitola` automaticky dostane například ID `id="toc-moje-kapitola"`, aniž byste museli cokoliv psát navíc. + + +Seznamy +======= + + +Odrážkové seznamy +----------------- + +Pro rychlý výčet položek, u kterých nezáleží na pořadí, se skvěle hodí odrážkový seznam. Stačí každý řádek začít pomlčkou `-`, hvězdičkou `*` nebo pluskem `+` a mezerou. Všechny tři znaky fungují stejně, takže si můžete vybrat ten, který je vám nejsympatičtější. + +```texy +Co je potřeba nakoupit: + +- Mléko +- Chleba +* Vejce ++ Máslo +``` + +ID syntaxe `list` | [ListModule |configuration#listmodule] + + +Číslované seznamy +----------------- + +Texy podporuje různé styly číslování: + +| `1.` | Arabské číslice (s tečkou) +| `1)` | Arabské číslice (se závorkou) +| `a)` | Malá písmena abecedy +| `A)` | Velká písmena abecedy +| `I)` | Římské číslice + +Kouzlo spočívá v tom, že se vůbec nemusíte starat o správné číslování. I když všechny řádky očíslujete jedničkou, Texy je automaticky přečísluje za vás. To je obrovská výhoda, když později potřebujete nějakou položku přidat, smazat nebo přesunout. + + +Vnořené a kombinované seznamy +----------------------------- + +Síla seznamů se naplno projeví, když je začnete kombinovat a vnořovat. Můžete tak vytvářet přehledné, víceúrovňové struktury. Vnoření vytvoříte jednoduše tak, že daný řádek odsadíte alespoň o **dvě mezery** (nebo jeden tabulátor). + +```texy +1) První kapitola + a) Podkapitola 1.1 + - První bod + - Druhý bod + b) Podkapitola 1.2 +2) Druhá kapitola + - Hlavní myšlenka + - Další poznámka +``` + + +Seznamy definic +--------------- + +Pro případy, kdy potřebujete vytvořit slovníček pojmů nebo přehledně vysvětlit několik termínů, je ideální definiční seznam. + +Na první řádek napište termín, který chcete definovat, a zakončete ho dvojtečkou. Na další řádky pište jeho definici, přičemž každý řádek odsaďte a začněte pomlčkou `-` + +```texy +HTML: + - Značkovací jazyk pro tvorbu webových stránek. + - Zkratka pro HyperText Markup Language. + +CSS: + - Jazyk pro popis způsobu zobrazení (stylování) stránek. + - Zkratka pro Cascading Style Sheets. +``` + +ID syntaxe `list/definition` | [ListModule |configuration#listmodule] + + +Stylování seznamů +----------------- + +Stejně jako u ostatních prvků v Texy, i seznamům můžete snadno přidávat [#modifikátory] pro změnu vzhledu. + +**Celý seznam:** Modifikátor napište na řádek **před** začátkem seznamu. + +```texy +.[barevny-seznam] +- První položka +- Druhá položka +``` + +**Jednotlivá položka:** Modifikátor přidejte na **konec** řádku dané položky nebo definičního termínu. + +```texy +- Běžná položka +- Tato položka je důležitá! .{font-weight: bold} +- Další běžná položka +``` + + +Obrázky +======= + +Základní syntaxe je velmi jednoduchá. Cestu k obrázku (ať už lokálnímu souboru, nebo URL adrese) stačí uzavřít do hranatých závorek s hvězdičkou: + +```texy +[* obrazek.jpg *] +[* https://domena.cz/logo.png *] +``` + +Často budete chtít, aby text obrázek obtékal. K tomu slouží jednoduché zarovnávací značky, které se vkládají před uzavírací závorku: + +```texy +[* obrazek.jpg <] Tento text bude plynule obtékat obrázek z pravé strany. + +[* obrazek.jpg >] V tomto případě bude text naopak obtékat obrázek z levé strany. + +[* velky-obrazek.jpg <>] +Tento text bude pokračovat až pod vycentrovaným obrázkem. +``` + +Správně vložený obrázek by měl mít i tzv. "alternativní text", který se zobrazí, pokud se obrázek nenačte. Pomocí [modifikátoru|#modifikátory] můžete přidat tento text i další prvky pro stylování. + +```texy +[* fotka-krajiny.jpg .(Krásná horská krajina při západu slunce)[main-photo] *] +``` + + +Rozměry obrázků +--------------- + +Texy umí u lokálních obrázků automaticky zjistit jejich rozměry (pokud je nastavena cesta `$texy->imageModule->fileRoot`) a doplnit je do HTML, což zrychluje načítání stránky. Pokud ale chcete rozměry nastavit ručně, máte několik možností: + +| `[* img.jpg 150x100 *]` | Přesná šířka 150px a výška 100px +| `[* img.jpg 150 *]` | Šířka bude 150px, výška se automaticky dopočítá se zachováním poměru stran +| `[* img.jpg ?x100 *]` | Výška bude 100px, šířka se automaticky dopočítá + + +Klikatelné obrázky +------------------ + +Chcete, aby se po kliknutí na malý náhled zobrazil velký obrázek? Nebo aby obrázek odkazoval na jinou stránku? Stačí za syntaxi obrázku přidat dvojtečku a cílovou URL. + +```texy +[* nahled.jpg *]:velky.jpg +[* logo-nette.png *]:https://nette.org +``` + +Pro galerie existuje i šikovná zkratka `::`. Ta automaticky vytvoří odkaz na stejný soubor umístěný na `$texy->imageModule->linkedRoot`. + + +Viditelný popisek pod obrázkem +------------------------------ + +Pokud chcete pod obrázek přidat viditelný popisek (např. jméno autora nebo popis scény), napište za něj tři hvězdičky `***` a text popisku. Texy z toho automaticky vytvoří sémanticky správnou HTML strukturu `<figure>` a `<figcaption>`. + +```texy +[* fotka.jpg <> *] *** Toto je popisek. Může obsahovat i **další formátování**. +``` + + +Udržujte si pořádek s referencemi +--------------------------------- + +Pokud v textu používáte jeden obrázek vícekrát nebo chcete mít všechny definice obrázků přehledně na jednom místě, můžete použít reference. V textu použijete jen zástupný název a na konci souboru pak definujete, co tento název znamená. + +```texy +V našem logu [* firemni-logo *] je vidět symbol naší vize. + +​[* firemni-logo *]: /images/logo.svg 200x50 .(Logo naší společnosti) +``` + +Tento přístup výrazně zpřehledňuje hlavní text a usnadňuje správu obrázků. + +ID syntaxe `image/definition` | [ImageModule |configuration#imagemodule] + + +Předformátovaný text +==================== + +V Texy můžete snadno vložit bloky kódu nebo jakýkoli předformátovaný text, u kterého chcete zajistit, aby se zobrazil přesně tak, jak ho napíšete – včetně všech mezer a konců řádků. To je ideální pro ukázky zdrojových kódů, logů nebo ASCII artu. + +Pro vložení takového bloku použijte ohraničení `/--` a `\--`: + +```texy +/-- +function hello() { + echo 'Hello World'; +} +\-- +``` + +Aby byl váš kód ještě čitelnější, můžete Texy sdělit, v jakém programovacím jazyce je napsaný a vytvořit si handler, který například obarví syntaxi, viz "ukázka":custom-handlers#syntax-highlighting. Stačí za úvodní značku `/--` přidat klíčové slovo `code` a název jazyka: + +```texy +/--code javascript +console.log('JavaScript'); +\-- + +/--code html +<div>Tohle je HTML kód</div> +\-- +``` + + +Obsahové bloky (divy) +===================== + +Texy umožňuje vytvářet obecné `<div>` bloky, díky kterým můžete snadno seskupovat obsah do logických celků a následně je stylovat:. + +Blok vytvoříte pomocí značek `/--div` a `\--`. Navíc můžete snadno přidat [#modifikátory]: + +```texy +/--div .[important] +## Důležité upozornění + +Tento text bude uzavřen v bloku `<div class="important">`. +Díky tomu ho můžete pomocí CSS nastylovat, aby byl výraznější. +\-- +``` + +Síla `<div>` bloků spočívá také v možnosti je vnořovat do sebe. Tím můžete vytvářet i složitější struktury přímo v Texy, aniž byste museli psát HTML ručně. + +```texy +/--div .[outer] + Toto je vnější blok. + + /--div .[inner] + A toto je vnořený, vnitřní blok. + \-- + + Zde jsme opět ve vnějším bloku. +\-- +``` +Díky této jednoduché syntaxi můžete udržovat svůj obsah přehledný a sémanticky správně strukturovaný. + + +Vypnutí formátování +=================== + +Někdy se může hodit Texy na chvíli "vypnout" a vložit kus textu, kde nemá Texy zpracovávat své značky. + +Pokud potřebujete vložit komplexnější HTML strukturu bez parsování Texy značek, použijte blok `/--html`: + +```texy +/--html +<em>Tento text bude zpracován jako HTML, takže bude kurzívou.</em> + +**Ale tyto hvězdičky Texy ignoruje, takže tučné nebudou.** +\-- +``` + +V případě, že chcete zobrazit text přesně tak, jak je napsán, a ignorovat veškeré značky (jak Texy, tak HTML), použijte blok `/--text`. Vše uvnitř tohoto bloku se zobrazí jako obyčejný text. + +```texy +/--text +<em>Tento text se zobrazí i se značkami, kurzívou ale nebude.</em> + +**Ani toto nebude tučné.** +\-- +``` + +Co když ale nechcete vypínat Texy pro celý blok textu, ale jen pro krátkou frázi uprostřed věty? Pro tyto případy existuje elegantní a rychlé řešení: obalte daný text do **dvojitých apostrofů** `''`: + +```texy +Pokud chcete ukázat, jak se píše tučný text, napíšete: Syntaxe je ''**tučný text**''. +``` + +Výsledkem nebude tučný text, ale doslova se vypíše řetězec `**tučný text**`. + + +Tabulky +======= + +Pro vytvoření tabulky začněte každý řádek znakem `|` a jednotlivé buňky oddělujte také tímto znakem. Texy si už samo pohlídá zarovnání a správné HTML. + +```texy +| Jan | Novák | Praha +| Eva | Svobodová | Brno +``` + +Výsledek bude přehledná a správně naformátovaná tabulka. + +ID syntaxe `table` | [TableModule |configuration#tablemodule] + + +Hlavička tabulky +---------------- + +Každá správná tabulka by měla mít hlavičku, která popisuje, co se v jednotlivých sloupcích nachází. Hlavičku vytvoříte tak, že ji od zbytku tabulky oddělíte řádkem obsahujícím pomlčky `-`. + +```texy +| Jméno | Věk | Město +|----------|-----|------- +| Jan | 25 | Praha +| Eva | 30 | Brno +``` + +Alternativně můžete definovat záhlaví pro jednotlivé řádky (například pokud máte v prvním sloupci popisky). Toho dosáhnete přidáním hvězdičky `*` hned za úvodní `|`. + +```texy +|* Jméno | Jan | Eva +|* Věk | 25 | 30 +|* Město | Praha | Brno +``` + + +Sloučení buněk +-------------- + +Někdy je potřeba spojit několik buněk dohromady, ať už ve sloupcích nebo v řádcích. + +**Sloučení sloupců:** Pro horizontální spojení buněk jednoduše vynechejte oddělovač a místo něj použijte zdvojenou svislou čáru `||`. Buňka napravo se tím sloučí s buňkou nalevo od ní. + +```texy +| Jméno || Věk +|---------------------------- +| Jan | Novák | 25 +``` + +**Sloučení řádků:** Pro vertikální spojení buněk použijte v buňce, kterou chcete připojit k té nad ní, symbol stříšky `^`. Ta Texy říká: "Tuto buňku spoj s tou nad ní." + +```texy +| Měsíc | Prodeje | +|---------|---------- +| Leden | 150 ks | +| Únor | ^| +| Březen | 210 ks | +``` + +V tomto příkladu bude buňka s prodeji pro leden a únor spojená. + +Takto lze sloučit několik buňek napříč řádky a sloupci: + +```texy +| First Name | Last Name | Age +|---------------------------- +| Bill || 50 +| ^| 52 +| Jim | Beam | 70 +``` + + +Stylování tabulek +----------------- + +Stejně jako u jiných prvků v Texy můžete i tabulkám a jejich částem přidávat [#modifikátory] pro změnu vzhledu (např. CSS třídy, styly nebo ID). + +**Celá tabulka:** Modifikátor pro celou tabulku umístěte na samostatný řádek těsně před ni. + +```texy +.[data-table table-striped] +| Hlavička 1 | Hlavička 2 +|------------|------------ +| data | data +``` + +**Jednotlivé řádky:** Chcete-li nastylovat konkrétní řádek, přidejte modifikátor na jeho konec. + +```texy +| Jméno | Stav +|-------|-------------- +| Petr | Schváleno +| Jana | Zamítnuto | .{background: #ffdddd} +``` + +**Jednotlivé sloupce:** Pro nastylování celého sloupce vložte modifikátor na začátek první buňky daného sloupce. + +```texy +| Jméno | .> Cena | Skladem +|----------------|-----------|--------- +| Produkt A | 1 200 Kč | Ano +| Produkt B | 850 Kč | Ne +``` + +**Konkrétní buňka:** Modifikátor pro jednu buňku napište přímo do ní, obvykle na konec jejího obsahu. + +```texy +| Úkol | Status +|----------------------|------------------------------------- +| Připravit podklady | Hotovo +| Zkontrolovat data | Probíhá .{color: orange; font-weight: bold} +``` + + +Citace +====== + +Potřebujete-li ve svém textu zdůraznit myšlenku někoho jiného, ocitovat zdroj nebo jen vizuálně oddělit blok textu, stačí začít řádek znakem `>`. + +```texy +> Toto je citace. Slouží ke zvýraznění důležité myšlenky nebo úryvku z jiného zdroje. +``` + +Citace nemusí být jen jeden odstavec. Pokud chcete pokračovat dalším odstavcem v rámci stejné citace, jednoduše vložte prázdný řádek, který také začíná znakem `>`. + +```texy +> Toto je první odstavec citace. Lorem ipsum dolor sit amet. +> +> A toto je druhý odstavec, který stále patří do stejné citace. +> Tímto způsobem můžete strukturovat i delší texty. +``` + +Texy dokonce podporuje vnořené citace, což se hodí, pokud citujete někoho, kdo sám někoho cituje. Pro každou další úroveň vnoření přidejte další znak `>`. + +```texy +> Toto je vnější, hlavní citace. +> +> > A toto je už vnořená citace druhé úrovně. +> +> Zde se text vrací zpět do hlavní citace. +``` + +Uvnitř citací můžete samozřejmě používat i další formátování, jako je **tučný text** nebo *kurzíva*. + + +Horizontální čáry +================= + +Někdy je potřeba text vizuálně rozdělit. K tomu skvěle slouží horizontální čára. Na samostatný řádek napište tři nebo více pomlček `---` nebo hvězdiček `***`. + +```texy +První část textu o nějakém tématu. + +*** + +Druhá část textu, která začíná po vizuálním oddělení. +``` + +Abyste vytvořili horizontální čáru, **musí jí předcházet prázdný řádek**. Pokud byste ji napsali hned pod text, Texy by si myslelo, že chcete vytvořit podtržený nadpis. + +ID syntaxe `horizline` | [HorizLineModule |configuration#horizlinemodule] + + +Typografie +========== + +Síla Texy nespočívá jen ve formátování, ale také v automatických typografických korekcích. Texy se postará o detaily, které dělají text profesionálním a dobře čitelným, a to vše podle českých typografických pravidel. Vy se tak můžete soustředit jen na obsah. + +**Uvozovky:** Nemusíte řešit, jak na klávesnici napsat správné typografické uvozovky. Texy to udělá za vás. + +Klasické ''"strojopisné uvozovky"'' automaticky převede na správné české „uvozovky“ a vnořené ‚uvozovky‘. Typ uvozovek závisí na nastavení locale: + +```php +$texy->typographyModule->locale = 'cs'; // české +$texy->typographyModule->locale = 'en'; // anglické +``` + +**Pomlčky a spojovníky:** Inteligentně rozpozná, kdy použít krátký spojovník (v dělených slovech), a kdy delší pomlčku – například v rozsazích (10–15) nebo mezi slovy. + +```texy +10-15 → 10–15 (en dash pro rozsahy) +česko-slovenský → česko-slovenský (spojovník zůstává) +slovo -- slovo → slovo – slovo (en dash mezi slovy) +slovo --- slovo → slovo — slovo (em dash) +``` + +**Nezlomitelné mezery**: Jednou z největších výhod je automatické vkládání pevných (nezlomitelných) mezer tam, kde je to potřeba. Tím zabraňuje, aby na konci řádku zůstala osamocená jednopísmenná slova (jako `k`, `s`, `v`, `z`), což je častý typografický prohřešek. + +```texy +// Vy napíšete: +Navštívil jsem hrad v Praze. + +// Texy zajistí, aby "v" nikdy nezůstalo na konci řádku: +Navštívil jsem hrad v Praze. +``` + +Stejně tak se postará o správné mezery v telefonních číslech nebo datech, aby se nezalamovala. + +```texy ++420 776 552 046 → +420 776 552 046 (všechny mezery pevné) +``` + +**Automatické symboly:** Texy vám usnadní i psaní často používaných symbolů. + +| Napíšete | Texy vygeneruje | Popis +|----- +| `...` | … | Výpustka +| `(c)` | © | Copyright +| `(r)` | ® | Registrovaná známka +| `(tm)` | ™ | Trademark +| `10 x 5` | 10 × 5 | Znak násobení +| `+-` | ± | Plus-mínus +| `<-` `->` `<->` | ← → ↔ | Šipky + +Díky těmto automatickým úpravám bude váš text vždy vypadat profesionálně, aniž byste museli znát složité klávesové zkratky nebo HTML entity. + + +Dělení dlouhých slov +-------------------- + +Znáte to – v textu se objeví dlouhé slovo, jako například "nejneobhospodařovávatelnějšími", a na úzké obrazovce mobilního telefonu rozbije celý layout stránky. Texy naštěstí nabízí elegantní řešení: dokáže do slova vložit neviditelné "měkké rozdělovníky" (`­`). Tyto rozdělovníky prohlížeči napoví, na kterých místech (mezi slabikami) může slovo bezpečně zalomit, pokud se na konec řádku nevejde. Pokud se slovo na řádek vejde celé, rozdělovníky zůstanou skryté a nic se nestane. + +```html +nejneobhospoda­řovávatelnějšími +``` + +Díky tomu se váš text vždy krásně přizpůsobí jakékoliv šířce obrazovky bez nechtěného horizontálního posouvání. + +Protože se tato funkce nehodí pro všechny typy webů, je ve výchozím stavu vypnutá. Aktivovat ji můžete v konfiguraci: + +```php +$texy->allowed['longwords'] = true; + +// Nastavit minimální délku slova, od které se má dělit (např. 20 znaků) +$texy->longWordsModule->wordLimit = 20; +``` + +ID syntaxe `longwords` | [LongWordsModule |configuration#long-wordsmodule] + + +Emotikony +========= + +Texy umí automaticky převádět klasické textové smajlíky na grafické emotikony. Jednoduše napište smajlíka tak, jak jste zvyklí, a Texy se postará o zbytek. + +| Napíšete | Texy vygeneruje +|----- +| `:-)` | 🙂 +| `:-(` | ☹ +| `;-)` | 😉 +| `:-D` | 😀 +| `:-*` | 😘 + +Podle konfigurace může Texy tyto zkratky převádět buď na moderní Unicode emoji (jako v tabulce výše), nebo na malé obrázky (`<img>`). + +Aby se předešlo nechtěným převodům například v technických textech, je tato funkce ve výchozím nastavení vypnutá. Pokud ji chcete používat, stačí ji jednoduše povolit: + +```php +$texy->allowed['emoticon'] = true; +``` + +Více informací o dostupných emotikonech a možnostech nastavení naleznete v [konfiguraci EmoticonModule |configuration#emoticonmodul]. + +ID syntaxe `emoticon` | [EmoticonModule |configuration#emoticonmodule] diff --git a/texy/cs/try-settings.texy b/texy/cs/try-settings.texy new file mode 100644 index 0000000000..66be081ed6 --- /dev/null +++ b/texy/cs/try-settings.texy @@ -0,0 +1,55 @@ +Konfigurace dema +**************** + +Toto nastavení se používá v [demu | https://fiddle.nette.org/texy/]: + +/--php +$texy = new Texy\Texy; + +$texy->imageModule->root = '/images/'; +$texy->imageModule->linkedRoot = '/images/'; +$texy->headingModule->generateID = true; + +// syntax highlighting +$texy->addHandler('block', 'blockHandler'); +\-- + +Handler pro zvýrazňování syntaxe používá knihovnu Prism.js: + +/--php +function blockHandler( + Texy\HandlerInvocation $invocation, + string $blocktype, + string $content, + string $lang, + Texy\Modifier $modifier +): Texy\HtmlElement +{ + if ($blocktype !== 'block/code') { + // nothing to do + return $invocation->proceed(); + } + + $texy = $invocation->getTexy(); + $elPre = Texy\HtmlElement::el('pre'); + if ($modifier) { + $modifier->decorate($texy, $elPre); + } + $elPre->attrs['class'] = 'language-' . $lang; + $content = $texy->protect(htmlspecialchars($content), $texy::CONTENT_BLOCK); + $elPre->create('code', $content); + return $elPre; +} +\-- + +Po dokončení konverze se navíc v textu zamění některé znaky za entity, aby byly lépe patrné: + +/--php +$html = $texy->process($text); + +$html = str_replace( + ["\xc2\xa0", "\xc2\xad", "\xe2\x80\x93", "\xe2\x80\x94"], + [' ', '­', '–', '—'], + $html +); +\-- diff --git a/texy/en/@home.texy b/texy/en/@home.texy new file mode 100644 index 0000000000..27b8c786c9 --- /dev/null +++ b/texy/en/@home.texy @@ -0,0 +1,120 @@ +Texy! is sexy! +************** + +.[perex] +Texy is a **powerful and secure markup processor** for PHP that converts simple text into valid HTML. Unlike other markup languages, Texy isn't just another Markdown variant – it's a **fully configurable system** that you can adapt to virtually any syntax. + + +Why Texy? +========= + + +Security as a Priority +---------------------- + +Texy is designed with security in mind. It automatically **protects against XSS attacks**, validates URLs, and filters dangerous HTML tags. The built-in `safeMode()` is ideal for processing user-generated content in comments or forums. + +```php +Texy\Configurator::safeMode($texy); +// Texy is now safe for user-generated content +``` + + +Full Configurability +-------------------- + +Want to use Markdown syntax? Or need completely custom markup? **Texy can handle it.** You can: + +- Disable or enable any parts of the syntax +- Change default behavior using handlers +- Add entirely custom syntax elements +- Configure Texy to process Markdown or any other format + +```php +$texy = new Texy; +$texy->allowed['image'] = false; // disable images +$texy->allowed['phrase/strong'] = false; // disable bold text +``` + + +Advanced Typography +------------------- + +Texy provides **sophisticated typographic processing** that can be tailored to different languages. Depending on the configured locale, it automatically: + +- Inserts **non-breaking spaces** after single-letter prepositions and conjunctions +- Applies **word hyphenation** according to syllable rules +- Uses proper **typographic quotation marks** based on language conventions +- Applies proper **dash and hyphen distinction**: 10–15 (dash) vs. multi-word (hyphen) +- Adds **non-breaking spaces** in phone numbers: +420 776 552 046 + + +Valid and Well-Formed HTML +-------------------------- + +Texy always generates **valid HTML5 code**. It automatically corrects improperly nested tags, closes unclosed elements, and ensures proper document structure. The output is not only valid but also **beautifully formatted** with proper indentation. + + +What is Texy? +============= + +Texy is a **general-purpose markup text processor**. While it has its default syntax (similar to Markdown but much richer), you can completely change or extend it. + +**It's not just a parser** – Texy is a comprehensive system with modular architecture, where each module processes a specific part of the syntax (headings, links, images, tables...). Thanks to the handler system, you can intervene at any point in the processing and modify the result according to your needs. + + +Texy vs. Markdown +================= + +The basic syntax is similar, but Texy offers much more: + +|--------------------------- +| Feature | Markdown | Texy +|--------------------------- +| Bold text | `**text**` | `**text**` +| Italic | `*text*` or `_text_` | `*text*` or `//text//` +| Headings | `# Heading` | `# Heading` or underline +| Images | `![alt](url)` | `[* url *]` +| Tables | limited | full support including merging +| Modifiers | no | yes – `.{color:red}[class]` +| Typography | no | yes – quotes, dashes, spaces +| Word hyphenation | no | yes – by syllables +| Configurability | limited | complete – custom syntax +| Security | depends on impl. | built-in (safeMode) + +**Example of differences:** + +```texy +Markdown: +![Image](image.jpg) + +Texy: +[* image.jpg 300x200 .(Image caption)[photo] <] +``` + +Texy allows you to define dimensions, classes, alignment, and much more directly in the syntax. + + +When to Use Texy? +================= + +Texy is ideal for: + +**CMS systems** Need to safely process content from editors? Texy offers granular control over what users can use. + +**Blogs and documentation** Rich syntax for tables, images with captions, typography, and code with syntax highlighting. + +**Comments and discussion forums** SafeMode ensures users cannot insert dangerous code while still having text formatting available. + +**Projects with custom requirements** Need to embed YouTube videos? Special syntax for your macros? Custom markup language? With Texy, you can create it easily. + + +History +======= + +Texy was created by David Grudl **20 years ago** in 2004 as one of the first markup processors for PHP. Originally developed for **PHP 4**, it has undergone many updates throughout its long history and today fully leverages all the capabilities of **PHP 8**. + +Over two decades of active development mean a **proven and stable** library trusted by hundreds of projects. Texy is now a **mature solution** with extensive history, yet still actively maintained and modern. + + +{{maintitle: Texy – human friendy markup for PHP}} diff --git a/texy/en/@menu.texy b/texy/en/@menu.texy new file mode 100644 index 0000000000..66743bb1f1 --- /dev/null +++ b/texy/en/@menu.texy @@ -0,0 +1,6 @@ +- [home | @home] +- [syntax | syntax] +- [for developers | develop] +- "Playground .[link-external]":https://fiddle.nette.org/texy/ +- "API .[link-external]":https://api.nette.org/texy/ +- "GitHub .[link-external]":https://github.com/dg/texy diff --git a/texy/en/@meta.texy b/texy/en/@meta.texy new file mode 100644 index 0000000000..4ec8857a1e --- /dev/null +++ b/texy/en/@meta.texy @@ -0,0 +1 @@ +{{sitename: Texy Documentation}} diff --git a/texy/en/architecture.texy b/texy/en/architecture.texy new file mode 100644 index 0000000000..5e8bf97c11 --- /dev/null +++ b/texy/en/architecture.texy @@ -0,0 +1,425 @@ +Architecture and Principles +########################### + +.[perex] +Texy is a tool for converting text written in its own markup language into HTML. Unlike simple converters that process text linearly through a series of replacements, Texy uses a sophisticated system based on parsing, a modular architecture, and the gradual construction of a DOM tree. + +The basic processing flow consists of four main phases: + +1. Text preprocessing - normalization, adjustment of spaces and tabs, calling notification handlers for preparation +2. Parsing - recognizing syntaxes using regular expressions and gradually building the DOM tree +3. Post-processing - typographic adjustments, handling long words, well-forming HTML +4. Final assembly - converting the DOM tree into an HTML string + +The key difference from naive approaches is the separation of the syntax recognition phase from the processing phase. The parser first identifies where each syntactic construct is located in the text, and only then passes the found parts to individual modules for processing. This allows for nesting syntaxes and their gradual expansion. + +*Note: all classes are in the `Texy` namespace, so if the document mentions a class like `HtmlElement`, its full name is `Texy\HtmlElement`. Modules are in the `Texy\Modules` namespace* + + +Key Components +============== + +The Texy architecture consists of several main components, each with a clearly defined responsibility: + +The Texy class acts as the central orchestrator of the entire system. It contains references to all modules, manages registered syntaxes and handlers, maintains the processing state, and coordinates the individual conversion phases. It is the only place where the individual components are interconnected. + +**[Modules|#Moduly]** represent functional units responsible for specific areas of the markup language. Each module, upon its construction, registers the syntaxes it recognizes and the element handlers that process them. For example, PhraseModule handles inline formatting like bold or italic text, while TableModule processes tables. Modules are designed as separate, reusable units with their own configuration accessible through public properties. + +**[Parsers|#Parsery]** exist in two variants depending on the type of content being processed. BlockParser processes block structures like paragraphs, headings, lists, or tables. It goes through the text line by line, looking for the beginnings of block constructs and passing them to *syntax handlers*. LineParser handles inline syntaxes within lines - links, images, text formatting. Unlike BlockParser, it allows for nesting syntaxes and their gradual expansion. + + +Basic Terminology +================= + +To correctly understand how Texy works, it is necessary to distinguish between several key concepts that frequently appear in the documentation. + +**Syntax** refers to a named syntactic construct of the markup language. Each syntax has a unique name, for example, `phrase/strong` for bold text or `image` for images. The syntax name is used to enable or disable it in the `Texy::$allowed` array and is passed as a parameter to syntax handlers to distinguish which specific syntax was found. + +**Pattern** is a regular expression that defines what the syntax looks like in the text. The pattern is an implementation detail of the syntax - the author of the syntax must write a regex that recognizes it, but from the perspective of a Texy user, the syntax name and its meaning are more important. One module typically registers multiple syntaxes with different patterns. + +**Syntax handler** is a function called by the parser when it finds an occurrence of a syntax in the text. It receives the found text and returns an `HtmlElement` or a string, which is inserted in the original place. The syntax handler is where the decision is made about what to do with the found syntax - it typically invokes an element handler for the actual processing. + +**Element** is an item for which an HTML representation is generated. For example, `image` is an element for images, `linkURL` for links, `phrase` for inline formatting. Each element has its default element handler that takes care of standard processing. + +**Element handler** is a function registered for a certain type of element and called through the HandlerInvocation system. A characteristic feature is the use of the `proceed()` method, which allows delegating processing to the next handler in the chain or to the module's default handler. Element handlers are used to modify or replace the default behavior. + +**Notification handler** is a function called to notify about a certain event. Unlike element handlers, it does not return any value and cannot influence the processing result. It is used for data preparation, logging, or modifying the already created DOM tree. + +The difference between the various handlers is key to understanding the architecture. A syntax handler is tightly coupled with the parser and a specific pattern - it addresses the question of *what to do when the parser finds this pattern*. Element handlers are at a higher level of abstraction - they address the question of *how to process this type of element*, regardless of which specific syntax created it. + + +Overall Processing Flow +======================= + +When Texy receives input text, it goes through the following processing procedure. + +During preprocessing, the text is normalized. Line endings are unified to the Unix format, spaces are standardized, and tabs are optionally replaced with spaces. Subsequently, *notification handlers* registered for the `beforeParse` event are invoked. These handlers can perform data preparation, such as loading reference definitions or adjusting the configuration based on the text content. + +The parsing itself begins with the creation of a root `HtmlElement`, which represents the document. Texy then decides whether to process the text as a single line or as a complete document with block structures. In the case of block processing, a BlockParser is created, which sequentially goes through the text and looks for individual block constructs. + +LineParser works differently than BlockParser. It does not traverse the text linearly but progressively searches for the nearest occurrence of any registered syntax. When it finds one, it calls the corresponding syntax handler, which creates the appropriate HTML element. This element is inserted back into the text using special masking, and the parser continues. This allows it to find and process syntaxes nested inside already processed constructs. + +After parsing is complete, a full DOM tree representing the document's structure is created. Texy invokes notification handlers for the `afterParse` event, which can perform final modifications to the tree, such as adding identifiers to headings or building a table of contents. + +Post-processing occurs during the conversion of the DOM tree to an HTML string. Each element is recursively converted to HTML code, during which typographic adjustments like replacing quotes, dashes, or inserting non-breaking spaces are applied. Furthermore, HTML well-forming is performed - automatic closing of tags, correction of improperly nested elements, and formatting and indentation of the code. + +The final phase is decoding all masked parts back to HTML tags, removing helper markers, and assembling the resulting HTML string. + + +Syntax System +************* + +In Texy terminology, a syntax represents a named syntactic construct of the markup language. It is an abstract concept connecting several elements: a unique name, a regular expression for recognition, and a method of processing. The syntax name serves as an identifier throughout the system - it is used in the `Texy::$allowed` array for enabling or disabling, passed to handlers to distinguish the type of construct, and appears in documentation and configuration files. + +Syntax naming conventions follow two main patterns. Simpler syntaxes have a single-word name corresponding to their purpose, for example, `image`, `table`, or `script`. More complex areas use hierarchical naming with a slash, for example, `phrase/strong`, `phrase/em`, or `link/reference`. The slash serves to logically group related syntaxes and facilitates bulk operations with them. + + +Line Syntax +=========== + +Line syntaxes are used to recognize inline elements within lines of text. Typically, this includes formatting like bold or italic text, links, images, or inline code. A characteristic of line syntaxes is that they can be nested within each other, and the parser expands them sequentially. + +A line syntax is registered by calling `Texy::registerLinePattern()` with several parameters. The first is the syntax handler, i.e., the callback called upon finding a match. The second parameter is the regular expression defining the syntax's appearance in the text. The third parameter is the syntax name used throughout the system. An optional fourth parameter is another regex to test if it's even worth searching for the pattern - it's used for optimization to avoid running a complex pattern on text that definitely cannot match. + +The pattern as a regular expression must adhere to certain rules. It must not be anchored to the beginning of the text because it is searched for anywhere in the line. It should be as specific as possible to avoid false matches. + +Inline syntaxes within lines of text are processed by the [LineParser|#LineParser]. When it finds a match, it calls the appropriate syntax handler. This handler receives three parameters. The first is the LineParser instance, which provides access to the Texy object and other contextual information. The second parameter is an array with the results of the regex match, including sub-expressions. The third parameter is the syntax name, which is useful when the same callback handles multiple syntaxes. The handler must return either an `HtmlElement`, a string, or null if it refuses to process. + + +Block Syntax +============ + +Block syntaxes recognize multi-line block constructs such as headings, lists, tables, quotes, or special blocks. Unlike line syntaxes, block syntaxes never overlap - each line of text belongs to at most one block construct. + +Registering a block syntax uses `Texy::registerBlockPattern()` with three parameters: a syntax handler, a regular expression, and the syntax name. The pattern as a regular expression must adhere to certain rules. It must match from the beginning of the line and often contains an anchor for the end of the line. BlockParser automatically adds the `Am` modifiers, so the pattern should not contain them. + +Block syntaxes within a document are processed by the [BlockParser|#BlockParser]. When it finds a match, it calls the appropriate syntax handler. This handler receives similar parameters as with line syntaxes - a BlockParser instance, an array with the match, and the syntax name. It returns an `HtmlElement` representing the entire processed block, or null if it refuses processing. + + +Enabling and Disabling Syntax +============================= + +The `Texy::$allowed` array provides fine-grained control over which syntaxes are active in Texy. It is a simple yet powerful mechanism for configuring behavior without needing to change the modules' code. When you disable the `phrase/strong` syntax with this setting, the parser stops looking for the bold text construct: + +```php +$texy->allowed['phrase/strong'] = false; +``` + +The check is performed once at the beginning of parsing, so dynamically changing `$allowed` during processing has no effect. + +When constructing modules, a default value is set in `$allowed` for most syntaxes. Some syntaxes are enabled by default because they form the basis of the markup language. Others are disabled because they are advanced or potentially dangerous. For example, emoticons are disabled because not every document needs them, while basic formatting is enabled. + +Safe mode is a situation where you are processing untrusted input, such as user comments. You want to allow basic formatting but disable images, scripts, or HTML tags. `Texy\Configurator::safeMode()` sets `$allowed` for a safe combination of syntaxes. It typically disables image, figure, script, and HTML tags, but leaves links and formatting enabled. + + +Parsers +******* + + +Syntax Handler +============== + +As we mentioned in the previous section, LineParser or BlockParser goes through the text and looks for all registered patterns. When it finds a match, it calls the appropriate syntax handler and passes it information about the find - particularly an array with the results of the regex match. + +The syntax handler analyzes the found text and prepares the data for processing. It can extract parts of the text from regex groups, create helper objects like `Link` or `Image`, and parse modifiers. It also decides which element handler to invoke. It calls `Texy::invokeAroundHandlers()` with the element name and the prepared parameters. This begins their execution. The returned result is passed back to the syntax handler, which returns it to the parser. + + +Element Handler +=============== + +Element handlers implement the chain of responsibility pattern, which allows the final behavior to be composed from multiple layers. + +An element handler is registered by calling `Texy::addHandler()` with two parameters - the element name and the handler function. A single element name can have multiple handlers registered, which are then executed in order from the last registered to the first. + +The element name identifies the type of processing, for example, `phrase` for formatting, `image` for images, or `link` for links (note: this is different from syntax names). Sometimes, composite names like `linkReference` or `linkEmail` are used to distinguish different kinds of links. The names are more general than syntax names - while the `phrase/strong` syntax is a specific construct, the `phrase` element covers all kinds of inline formatting. + +Invoking an element handler uses the `Texy::invokeAroundHandlers()` method. This method receives the element name, the parser instance, and an array of parameters. It creates a HandlerInvocation object that encapsulates the entire chain of registered handlers. The first handler in the chain gets control and decides whether to call `HandlerInvocation::proceed()` to continue to the next handler or to return its own result. + +The HandlerInvocation object is key to understanding how the chaining works. It contains a stack of all handlers for the given element and the current position in this stack. When a handler calls `proceed()`, HandlerInvocation moves the position back one place in the stack and calls the next handler. If a handler calls `proceed()` with modified parameters, these new parameters are passed to all subsequent handlers. If a handler does not call `proceed()` at all, the chain is interrupted, and its return value becomes the result of the entire processing. + +The order of handler execution is from the last registered to the first. This means that a user-defined handler registered additionally gets control first and can decide whether to call the module's default handler at all. This order allows users to override the default behavior without needing to change the module's code. + +A typical use of an element handler looks like this. The handler checks the input parameters and decides if it wants to intervene in the processing. If so, it modifies the data, calls `proceed()` with the new parameters, and possibly modifies the returned result further. If the handler wants to completely replace the default processing, it creates its own result and returns it without calling `proceed()`. + + +Notification Handler +==================== + +Notification handlers represent a simpler, one-way communication mechanism. Unlike element handlers, they are not used for data transformation but for performing side actions. + +Registering a notification handler uses the same `Texy::addHandler()` method as element handlers. The difference is in how the handler is used - a notification handler returns no value and does not have access to HandlerInvocation. The first parameter is the event name. Descriptive names like `beforeParse` and `afterParse` are used for global events around parsing, or more specific ones like `afterTable`, `afterList`, `afterBlockquote` for events after a specific structure is created. The before/after prefix clearly indicates the timing of the event. + +Invoking notification handlers uses the `Texy::invokeHandlers()` method. This method simply calls all registered handlers in order and ignores their return values. Notification handlers receive the parameters passed during invocation but cannot change them for other handlers in the chain. + +Typical uses for notification handlers include several scenarios. A handler for the `beforeParse` event can load reference definitions from the text before parsing begins. A handler for `afterParse` can traverse the created DOM tree and add missing attributes or build a table of contents. Handlers like `afterTable` or `afterList` allow modules to perform final adjustments to the created structures. + +An important difference from element handlers is that notification handlers cannot prevent further processing. All registered handlers are always executed; none can break the chain. This is intended behavior - notification handlers are about side effects, not flow control. + + +LineParser +========== + +LineParser processes inline syntaxes within lines of text in a sequential manner that allows for nesting and complex interactions between syntaxes. + +The basic principle lies in finding the first occurrence of any syntax. In each iteration, it goes through all syntaxes and determines which one matches closest to the current position in the text. This syntax *wins* and is processed. If multiple syntaxes match at the same position, the one that was registered earlier wins - this is a priority based on registration order. + +When the parser finds the nearest match, it calls the corresponding syntax handler. This handler returns a result, which can be an `HtmlElement` or a string. This result then overwrites the found match in the text. + +Then, it searches again from the current position. This system ensures that the parser always sees the current state of the text. When we replace a match with new text that may contain other syntaxes, these syntaxes will be found in the next iteration. + +The `$again` property on the LineParser object is used for fine-grained control over whether the just-matched syntax should be searched for again at the same position after processing the current match. The default value is false, which says: *It no longer makes sense to look for this same syntax at this position. Move on.* + +The traversal ends when the parser reaches the end of the text or when no syntax has any more matches. The result is text where all recognizable syntaxes have been processed and replaced with their results, ready for final conversion. + + +Nesting +------- + +The ability to process nested syntaxes is one of the key features of LineParser and presents a fundamental challenge - how to prevent already processed HTML tags from being mistakenly interpreted as another syntax to be processed. + +When the parser processes text containing nested syntaxes, it first finds the outer construct. For example, in the text `"link **bold** text":URL`, the parser first finds the syntax for a link with quotes. The pattern for this syntax matches the entire string from the first quote to the colon and URL. The syntax handler creates an `HtmlElement` for the `<a>` tag, and the content `link **bold** text` is added as a child of the element. This string is inserted back into the text, and the parser continues searching for other syntaxes (`**bold**`, which represents bold text). + +But now it has a problem - there are also HTML tags in the text, which could match as the beginning of another syntax. The parser would start processing the already finished HTML tags as if they were part of the original text. + +We don't want the parser to see HTML tags. We need some way to distinguish already processed parts from parts waiting to be processed. The `Texy::protect()` method solves these problems in an elegant way - it replaces HTML tags with a unique placeholder composed of control characters - special bytes outside of printable ASCII. + +So, when an `HtmlElement` is converted to a string (using `toString()`), the result doesn't look like `<a href="...">link **bold** text</a>`, but for example, like `\x17\x18\x19\x17link **bold** text\x17\x18\x1A\x17`. + +Thus, during parsing, there are never actual HTML tags present in the text. Instead, there are only placeholders. But the inner text remains, and the parser sees it normally and can search for other syntaxes within it. This allows for gradual nesting - the outer syntax is masked, but its content is still accessible for inner syntaxes. + +At the end of processing, the `Texy::unProtect()` method goes through the resulting HTML string and replaces all placeholders with their actual values. Only at this moment do the actual HTML tags get into the output. + + +Masking Levels +-------------- + +Different types of content use different control characters for their placeholders, which allows syntaxes to selectively decide what they can contain. + +- `Patterns::CONTENT_MARKUP` denotes regular HTML markup like tags for formatting or links. It is the most common type and is used by most inline elements. The placeholder begins and ends with `\x17`. +- `Patterns::CONTENT_REPLACED` denotes content that has been replaced by something else, typically images or other replaced elements. It uses `\x16` as a marker. +- `Patterns::CONTENT_TEXTUAL` denotes text that has been escaped or otherwise treated to prevent processing. It is used for constructs like code or notexy, where we want to display the original text including markup symbols, not their interpretation. +- `Patterns::CONTENT_BLOCK` denotes block elements. It is the lowest level in the hierarchy. It uses `\x14` as a marker. + +The hierarchy of these types is not just a convention but has a practical consequence. The constant Patterns::MARK is defined as `\x14-\x1F`, i.e., a range covering all these types plus a reserve. Syntaxes use this constant in their patterns to exclude masked parts. + +Different syntaxes may have different requirements for what placeholders they can contain. A pattern that wants to see only plain text without any masked parts will use the exclusion `[^\x14-\x1F]`. This will reject all placeholders of all types. An example is the pattern for images - an image URL should not contain any HTML tags or blocks. + +A pattern that accepts lower levels but rejects higher ones will use a narrower range. For example, `[^\x17-\x1F]` will only reject `CONTENT_MARKUP` and above, but will accept `CONTENT_BLOCK`, `CONTENT_TEXTUAL`, and `CONTENT_REPLACED`. This is useful if we want to allow blocks but not inline markup. A practical example is TypographyModule, which performs typographic adjustments like replacing quotes or inserting non-breaking spaces. These adjustments should be applied to regular text, but not inside code blocks or preformatted text. + + +Syntax Collisions +----------------- + +A collision occurs when multiple syntaxes can match at the same position, and the system must choose one of them. + +A typical example is different lengths of the same symbol. The `phrase/strong+em` syntax uses three asterisks for a combination of bold and italics. The `phrase/strong` syntax uses two asterisks for bold text alone. The `phrase/em-alt` syntax uses one asterisk for italics. When the parser finds text starting with three asterisks, all three syntaxes can technically match. + +PhraseModule resolves this collision by registering syntaxes in order from longest to shortest. First, it registers `phrase/strong+em` with a pattern for three asterisks. Then `phrase/strong` with a pattern for two asterisks. Finally, `phrase/em-alt` with a pattern for one asterisk. Thanks to this order, when three asterisks are found, `phrase/strong+em` is processed first, and the shorter syntaxes don't get a chance. + +Another example is links in different formats. The `phrase/wikilink` syntax uses a pattern for `[text|url]`. The `link/reference` syntax uses a pattern for `[ref]`. Both start with an opening square bracket. If the text contains `[text|url]`, both patterns can technically start to match. + +The solution, again, is the specificity of the patterns. The pattern for `phrase/wikilink` is more specific - it requires a vertical bar inside the brackets. If the text contains a vertical bar, `phrase/wikilink` will match. If not, the pattern will fail, and `link/reference` gets a chance. The order of registration also plays a role here - `phrase/wikilink` should be registered before `link/reference`. + + +BlockParser +=========== + +BlockParser uses a fundamentally different approach to processing that reflects the nature of block constructs. The basic difference is the absence of intertwining. While LineParser allows syntaxes to be nested within each other and gradually expanded, BlockParser works with the assumption that each block is a separate unit. A single line or a group of lines belongs to at most one block. Blocks do not overlap, cross, or nest at the BlockParser level. + +BlockParser starts by finding all blocks, or rather their beginnings. The parser goes through all registered block syntaxes and finds all their occurrences. If multiple syntaxes match at the same position, the registration order is used - the earlier registered syntax takes precedence. + + +API for Syntax Handler +---------------------- + +BlockParser provides syntax handlers with an API for working with multi-line structures. + +The `BlockParser::moveBackward()` method is used to return to previous lines. It accepts the number of lines to go back. The parser moves its internal position towards the beginning of the text until it passes the specified number of line endings. This allows the callback to start reading from the beginning of the structure, even if the pattern matched in the middle or at the end. + +The `BlockParser::next()` method is used to read the next line matching a certain pattern. It accepts a regex pattern (it automatically adds the `Am` modifiers) and a reference to a variable for the match result. If the next line in the text matches the provided pattern, the method fills the result, moves the internal position past this line, and returns true. If the next line does not match, the method returns false, and the position does not change. + + +Modules +******* + +Modules are the basic organizational unit in the Texy architecture. Each module encapsulates the complete functionality for a specific area of the markup language. + +The primary responsibility of a module is to register syntaxes. In its constructor, the module calls `Texy::registerLinePattern()` or `registerBlockPattern()` for all the syntaxes it wants to process. This tells the parser: *When you find these patterns, call me.* The module thus defines which constructs in the text it recognizes. + +The second responsibility is the implementation of element handlers. The module registers handlers for the elements that its syntaxes invoke. These handlers contain the logic for converting the found constructs into HTML elements. The element handler decides what element to create, what attributes to set, and how to process the content. + +The third responsibility is to provide configuration. Modules have public properties that allow Texy users to modify the module's behavior without needing to change its code. For example, ImageModule has properties for setting the root path to images or the default alt text. + +The fourth responsibility is managing module-specific state. For example, HeadingModule keeps track of all found headings in the TOC array for building a table of contents. LinkModule manages a dictionary of references for links. This state is private to the module, and other parts of the system do not access it directly. + +Modules are designed as independent units. Each module can function on its own and should not depend on the implementation details of other modules. Communication between modules occurs through shared objects like `Link` or `Image`, not through direct method calls. + + +Structure of a Typical Module +============================= + +Most modules in Texy follow a similar structure that reflects their role in the system. + +The module inherits from the base class Module, which provides access to the Texy object via the protected property `$texy`. The module's constructor accepts a Texy instance and stores it. This allows the module to access the configuration and call methods on the Texy object. + +All initialization takes place in the constructor. The module sets the default values of its configuration properties, and possibly sets default values in the `Texy::$allowed` array for its syntaxes. Then it registers its syntaxes by calling `registerLinePattern()` or `registerBlockPattern()`. Each registration associates a pattern, a syntax handler, and a syntax name. Finally, the module registers its element handlers by calling `addHandler()`. + +Syntax handlers are methods of the module that the parser calls when it finds a syntax. These methods typically extract parts from the regex match, create helper objects, and invoke element handlers. The syntax handler decides which element handler to invoke and what parameters to pass. + +Element handlers are methods that implement the actual processing. They receive a HandlerInvocation object as the first parameter, followed by parameters specific to the given element. The element handler creates an `HtmlElement`, applies modifiers, processes the content, and returns the result. This is where the final form of the HTML is decided. + +Public properties serve as the interface for configuration. A Texy user can set these properties to customize the module's behavior. The properties are typically primitive types or arrays, not complex objects, to keep configuration simple. + + +Overview of Key Modules +======================= + +The standard distribution of Texy includes several modules covering various aspects of the markup language. + +- **PhraseModule** processes inline text formatting. It registers syntaxes for bold text, italics, underline, superscript, subscript, code, and more. All these syntaxes invoke a common handler for the `phrase` element, and the handler distinguishes which tag to create based on the syntax name. The module allows configuring which tags are used for each type of formatting. + +- **LinkModule** manages links in the document. It registers syntaxes for various link formats - explicit URLs, email addresses, references to defined links. It provides factory methods for creating `Link` objects and manages a dictionary of references. The module allows configuring the root for relative links, automatic `rel="nofollow"` for external links, and shortening of long URLs. + +- **ImageModule** processes images in a similar way to how LinkModule handles links. It registers syntax for inline images and manages a dictionary of references to defined images. It provides factory methods for creating `Image` objects and automatic detection of image dimensions. Configurable options include paths to images, default alt text, and CSS classes for alignment. + +- **HeadingModule** recognizes headings in various formats - underlined with dashes or equal signs, surrounded by hash marks. It collects all headings into a TOC array for a possible table of contents. It allows configuring the generation of IDs, the top level of headings, and the level balancing mode. + +- **ListModule** processes lists - unordered, ordered, and definition lists. It recognizes different types of bullets and automatically detects nesting based on indentation. It allows configuring which characters serve as bullets and what HTML lists to generate. + +- **TableModule** is one of the most complex modules. It recognizes tables with headers, bodies, captions, and supports colspan and rowspan. It processes modifiers for both rows and cells. + +- **BlockModule** processes special blocks delimited by `/--` and `\--`. It supports various block types - code for code, html for direct HTML, div for a generic container. It allows users to define custom handlers for their own block types. + +- **TypographyModule** performs post-processing for typographic adjustments. It replaces three dots with an ellipsis, double dashes with an en-dash, straight quotes with typographic ones, and inserts non-breaking spaces. It operates at the level of the final string between block elements. + +- **HtmlOutputModule** formats the final HTML output. It ensures well-formed HTML by automatically closing tags, correcting incorrect nesting, indenting the code, and wrapping long lines. It allows configuring the indentation level and line width. + + +Interaction Between Modules +=========================== + +Although modules are designed to be independent, in some cases they need to cooperate. + +Shared objects are the main communication mechanism. A `Link` object created by LinkModule can be passed to ImageModule to create an image link. An `Image` object created by ImageModule can be passed to FigureModule to create an image with a caption. These objects encapsulate all necessary information and provide a common interface. + +The reference system allows separating definition from use. LinkModule provides `addReference()` and `getReference()` methods for managing a dictionary of named links. A user can define a reference in one part of the document and use it in another. ImageModule has an analogous system for image references. Modules using references call factory methods that themselves check whether it is a reference or a direct value. + +Element handlers can call other element handlers. When PhraseModule processes a `phrase/span` with a link, it creates a `Link` object and calls the LinkModule's element handler to create the link. This delegates the responsibility for creating and configuring the link to the specialized module. + +Relationships between modules are typically one-sided. PhraseModule knows about LinkModule and ImageModule because it creates links and images. But LinkModule and ImageModule do not know about PhraseModule. This keeps dependencies simple and allows for easy replacement or extension of modules. + + +DOM Representation +****************** + +`HtmlElement` represents a single node in the DOM tree and provides an interface for its manipulation and processing. + +The basic structure of an element includes a tag name, an associative array of attributes, and an array of children. The children can be other `HtmlElement` instances or simply text strings. This combination allows for representing any HTML structure. + +The element name is set and retrieved via the `setName()` and `getName()` methods. A special value of null as the name means a transparent element, which has no tags, only its content. + +Attributes are publicly accessible via the `$attrs` property as an associative array. Values can be strings, numbers, booleans, or arrays. A boolean `true` means an attribute without a value (like `checked`), while `false` or `null` means the attribute will not be rendered at all. If the value is an array, the different elements are joined according to the attribute type - for `class` with spaces, for `style` with semicolons. The `setAttribute()` method sets the value of an attribute. The `getAttribute()` method returns the value of an attribute or null. + +Children are managed through several methods. The `add()` method adds a child to the end. The `insert()` method inserts a child at a specified position, optionally replacing an existing child. The `create()` method creates a new `HtmlElement` as a child and returns it for further manipulation. The `removeChildren()` method removes all children. + +The element implements the ArrayAccess interface, so children can be worked with like an array. The notation `$el[0]` returns the first child, `$el[0] = $child` sets the first child. This approach is convenient for quick manipulation of specific children. + +The `toString()` method recursively traverses the element and its children and builds a string representation. HTML tags are immediately masked using `Texy::protect()`, so a placeholder is inserted into the result instead of actual HTML characters. + +The `toHtml()` and `toText()` methods return the unmasked result including post-processing. + + +Parsing Content +=============== + +`HtmlElement` can recursively parse its content, allowing for the gradual building of the DOM tree. + +The `parseLine()` method is used to parse inline syntaxes in a string. It creates a new instance of LineParser with the current element as the container. It calls `parse()` on the parser with the provided text. LineParser sequentially finds and processes all inline syntaxes, and the resulting elements or strings are added as children of the current element. The method returns the used LineParser for possible further use. + +The `parseBlock()` method parses text as block content. It creates a BlockParser and calls `parse()` on it. BlockParser finds all block constructs in the text, processes them, and adds them as children of the element. Text between blocks is processed as paragraphs, which internally use LineParser. The method accepts a boolean parameter indicating whether the text comes from an indented block, which affects the processing of paragraphs. + +These parsing methods allow for recursive processing. A syntax handler can create an element, set its basic properties, and then call `parseLine()` or `parseBlock()` to process the content. The result is that the element's content goes through the same parsing process as the main document, including syntax recognition and handler invocation. + + +Validation +========== + +`HtmlElement` provides mechanisms for validating attributes and content according to the HTML DTD (Document Type Definition). + +The DTD is a static array defining for each HTML tag which attributes are allowed and what content it can contain. Texy loads the DTD from a file upon initialization and stores it in a static array. The DTD structure maps a tag name to a pair - an array of allowed attributes and an array of allowed content. + +The `validateAttrs()` method checks the element's attributes against the DTD. For a given tag, it gets the list of allowed attributes. It goes through all the element's attributes and removes those that are not on the list. Special cases are attributes starting with `data-` or `aria-`, which are allowed if a placeholder entry `data-*` or `aria-*` is in the DTD. + +This validation is typically called when applying modifiers with the `decorate()` method. It ensures that even if a user specifies a modifier with an invalid attribute for a given tag, the attribute does not get into the final HTML. This is important for security and HTML correctness. + +The `validateChild()` method checks whether a given child can be the content of the element. It accepts a child (`HtmlElement` or a tag name) and the DTD. If the element is defined in the DTD, the method checks if the child is in the list of allowed content. If so, it returns true. If not, it returns false. + +This validation can be used when dynamically building a DOM tree to ensure a correct structure. For example, a paragraph element must not contain block elements, so `validateChild()` would refuse to add a `div` into a `p`. In practice, Texy uses this validation to a limited extent, as the structure generated by the modules is typically correct by design. + +The combination of `validateAttrs()` and `validateChild()` provides a mechanism for ensuring valid HTML, even if the input contains untrusted data or poorly formed constructs. Texy can be configured for strict validation or can disable validation for maximum flexibility. + + +Modifiers +********* + +Modifiers provide a way to add additional attributes, classes, styles, and alignment to elements without having to write direct HTML. + +The basic format of a modifier is a dot followed by a combination of different parts in round, square, and curly brackets: `.(title)[class1 class2 #id]{style:value}<align>^valign`. The entire modifier is written before or at the end of the construct to which it applies. For example, `"**text** .(Important)[highlight]{color:red}"` creates bold text with the class `highlight`, red color, and a title attribute "Important". + +Round brackets contain the title attribute or alt text. The text inside is used as the value of the title attribute on the resulting element. If the element is an image, it can be used as alt text. Inside the round brackets, it is possible to escape a bracket with a backslash. + +Square brackets contain CSS classes and optionally an ID. Classes are written as words separated by spaces. An ID is written with a hash prefix. For example, `[main-content selected #article-5]` sets two classes and one ID. If an ID is specified multiple times, the last one is used. + +Curly brackets contain CSS styles or HTML attributes. Styles are written in the standard CSS format `property:value`. Multiple styles are separated by semicolons. Some properties are recognized as HTML attributes - for example, `{href:url}` is converted to an `href` attribute, not a CSS style. This allows setting attributes that cannot be expressed otherwise. + +Alignment is specified using special characters. `<` means left, `>` right, `=` for justify, `<>` for center. Vertical alignment uses `^` for top, `-` for middle, and `_` for bottom. These shortcuts are converted to either CSS classes or inline styles depending on the configuration. + +The parts of the modifier can be in any order, and some can be omitted. A modifier containing only classes `.[highlight]`, only a title `.(Note)`, or only a style `.{color:blue}` is valid. The parser recognizes the individual parts by their delimiting characters. + + +Modifier Class +============== + +The `Modifier` class is used to parse and store information from a modifier. + +An instance of `Modifier` is typically created by a syntax handler, which passes the modifier text extracted from a regex match to the constructor. The constructor calls the `setProperties()` method, which parses the text and populates the object's properties. + +Public properties contain the individual parts of the modifier. The `$id` property contains the element's ID as a string or null. The `$classes` property is an associative array where keys are class names and values are true. The `$styles` property is an associative array mapping CSS properties to values. The `$attrs` property is an associative array with HTML attributes that are not styles or classes. + +Two special properties, `$hAlign` and `$vAlign`, contain the horizontal and vertical alignment as strings `left`, `right`, `center`, `justify` or `top`, `middle`, `bottom`. These values are later converted to CSS classes or styles according to the Texy configuration. + +The `$title` property contains the text from the round brackets, which is used as the title attribute or alt text for images. The text is automatically unescaped from HTML entities and stripped of escaped brackets. + + +Application to Elements +======================= + +A `Modifier` object is applied to an `HtmlElement` using the `Modifier::decorate()` method. + +The `decorate()` method accepts a Texy instance and an `HtmlElement` as parameters. It sequentially applies the individual parts of the modifier to the element, taking into account the Texy configuration, which may prohibit or restrict some parts. + +The application of attributes checks which attributes are allowed for the given tag according to the `Texy::$allowedTags` configuration. If all attributes are allowed, all attributes from the `Modifier` are copied to the element. If only a list of specific attributes is allowed, only those that are on the list are copied. + +The title attribute is always applied if it is set, but the text undergoes typographic post-processing to replace quotes and other adjustments. + +The application of classes and ID checks the `Texy::$allowedClasses` configuration. If all classes are allowed, all classes from the `Modifier` are added to the element, and the ID is set. If only a list of specific classes is allowed, only those that are on the list are added. The ID is added only if a string starting with a hash is on the allowed list. + +The application of styles proceeds similarly, with a check of `Texy::$allowedStyles`. Allowed CSS properties are added to the element's style attribute. If the element already had some styles, the modifier's styles are added or overwrite existing ones. + +Alignment is applied either as a CSS class or an inline style. If a mapping is configured in Texy's `Texy::$alignClasses` for the given alignment type, the corresponding CSS class is added. If not, an inline style with the `text-align` or `vertical-align` property is added. + +The result is that the element has all the attributes, classes, styles, and other properties from the modifier, but only those that are allowed by the current Texy configuration. This ensures safety when processing untrusted input. + + +Propagation of Modifiers +======================== + +Modifiers pass through the system in several phases, maintaining flexibility and allowing for modifications at different levels. + +The syntax handler extracts the modifier text from the regex match and creates a new `Modifier` instance, populating its properties. + +The `Modifier` object is passed as a parameter to element handlers. The handler receives the already parsed object, not the raw text. This allows the handler to easily access the individual parts of the modifier - classes, styles, alignment. The handler can modify the modifier before application, for example, by adding more classes or changing styles. + +The element handler creates an `HtmlElement` and passes it to the `Modifier::decorate()` method. At this point, the modifier is applied to the element. The `decorate()` method checks the Texy configurations and ensures that only allowed parts are applied. + +In some cases, a module combines multiple modifiers. For example, TableModule parses modifiers at the table, row, and cell levels. A cell's modifier is actually a clone of the column's modifier, to which additional modifications from the specific cell's modifier are then applied. This allows for default styles for an entire column with the possibility of overriding them in individual cells. diff --git a/texy/en/configuration.texy b/texy/en/configuration.texy new file mode 100644 index 0000000000..eee1a1a890 --- /dev/null +++ b/texy/en/configuration.texy @@ -0,0 +1,719 @@ +Configuration +************* + +.[perex] +A complete guide to configuring Texy. Learn how to control all modules, set up security, and customize Texy to your needs. + +Texy is configured using **public properties** of the main `Texy\Texy` class and its **modules**. Each module is responsible for processing a specific part of the syntax (images, links, headings...). + +Basic approach: + +```php +$texy = new Texy\Texy; + +// Configuration of the main class +$texy->allowedTags = Texy\Texy::NONE; + +// Configuration of a module +$texy->imageModule->root = '/images/'; +``` + + +Texy\Texy Class .[#texy-class] +============================== + +The main class contains global settings and properties affecting the entire processing. + + +Allowed Syntax ($allowed) .{toc: $allowed} +------------------------------------------ + +The `$allowed` array controls which parts of Texy syntax are active: + +```php +// Default: all syntax features allowed (except emoticons) +$texy->allowed['image'] = true; +$texy->allowed['emoticon'] = false; + +// Disable images +$texy->allowed['image'] = false; + +// Disable HTML tags in input +$texy->allowed['html/tag'] = false; +$texy->allowed['html/comment'] = false; + +// Disable various types of links +$texy->allowed['link/reference'] = false; +$texy->allowed['link/email'] = false; +$texy->allowed['link/url'] = false; +``` + +**Complete list of syntax features:** + +|--- +| Key | Default | Description +|--- +| `image` | `true` | Images `[* img.jpg *]` +| `figure` | `true` | Images with caption +| `link/reference` | `true` | References `[ref]` +| `link/email` | `true` | Email addresses +| `link/url` | `true` | Automatic URLs +| `link/definition` | `true` | Reference definitions +| `heading/underlined` | `true` | Underlined headings +| `heading/surrounded` | `true` | Surrounded headings +| `horizline` | `true` | Horizontal lines +| `blockquote` | `true` | Quotes +| `list` | `true` | Lists +| `list/definition` | `true` | Definition lists +| `table` | `true` | Tables +| `phrase/strong` | `true` | Bold text `**text**` +| `phrase/em` | `true` | Italic `//text//` +| `phrase/em-alt` | `true` | Italic `*text*` +| `phrase/code` | `true` | Code ```text``` +| `phrase/ins` | `false` | Inserted text `++text++` +| `phrase/del` | `false` | Deleted text `--text--` +| `phrase/sup` | `false` | Superscript `^^text^^` +| `phrase/sub` | `false` | Subscript `__text__` +| `html/tag` | `true` | HTML tags in input +| `html/comment` | `true` | HTML comments +| `emoticon` | `false` | Emoticons `:-)`, `:-(` +| `blocks` | `true` | Blocks `/-- \--` +| `typography` | `true` | Typographic adjustments +| `longwords` | `true` | Breaking long words + + +Allowed HTML Tags ($allowedTags) .{toc: $allowedTags} +----------------------------------------------------- + +Controls which HTML tags are allowed in output (and input): + +```php +// Default: all valid HTML5 tags allowed +$texy->allowedTags = Texy\Texy::ALL; + +// Disable all HTML tags +$texy->allowedTags = Texy\Texy::NONE; + +// Allow only specific tags +$texy->allowedTags = [ + 'strong' => [], // <strong> without attributes + 'a' => ['href', 'title'], // <a> with attributes + 'img' => Texy\Texy::ALL, // <img> with any attributes +]; +``` + +**Formats:** +- `Texy::ALL` – all tags allowed +- `Texy::NONE` – no tags allowed +- Array – allowed tags as keys, allowed attributes as values + + +Allowed CSS Classes ($allowedClasses) .{toc: $allowedClasses} +------------------------------------------------------------- + +Controls which CSS classes and IDs can be used: + +```php +// Default: all classes and IDs allowed +$texy->allowedClasses = Texy\Texy::ALL; + +// Disable classes and IDs +$texy->allowedClasses = Texy\Texy::NONE; + +// Allow specific classes and IDs +$texy->allowedClasses = [ + 'highlight', + 'important', + '#main', // IDs start with # + '#sidebar', +]; +``` + +Usage: +```texy +Text with class .[highlight] + +Text with ID .{toc: main} +``` + + +Allowed CSS Styles ($allowedStyles) .{toc: $allowedStyles} +---------------------------------------------------------- + +Controls which inline CSS properties can be used: + +```php +// Default: all styles allowed +$texy->allowedStyles = Texy\Texy::ALL; + +// Disable inline styles +$texy->allowedStyles = Texy\Texy::NONE; + +// Allow specific CSS properties +$texy->allowedStyles = [ + 'color', + 'background-color', + 'font-size', +]; +``` + +Usage: +```texy +Text with color .{color: red} +``` + + +CSS Classes for Alignment ($alignClasses) .{toc: $alignClasses} +--------------------------------------------------------------- + +As an alternative to inline styles `style="text-align:left"`, you can use CSS classes: + +```php +// Default: empty array (uses inline styles) +$texy->alignClasses = [ + 'left' => null, + 'right' => null, + 'center' => null, + 'justify' => null, + 'top' => null, + 'middle' => null, + 'bottom' => null, +]; + +// Set classes +$texy->alignClasses['left'] = 'text-left'; +$texy->alignClasses['right'] = 'text-right'; +$texy->alignClasses['center'] = 'text-center'; +``` + +Usage: +```texy +Text aligned to the left . + +Text aligned to the right .> +``` + +With `alignClasses` set, it generates `<p class="text-left">` instead of `<p style="text-align:left">`. + + +Additional Properties +--------------------- + +```php +// Merge lines into paragraphs (default: true) +$texy->mergeLines = true; + +// Tab width (default: 8) +$texy->tabWidth = 8; + +// Obfuscate emails from bots (default: true) +$texy->obfuscateEmail = true; + +// Remove soft hyphens (default: true) +$texy->removeSoftHyphens = true; + +// Element for non-textual paragraphs (default: 'div') +$texy->nontextParagraph = 'div'; +``` + + +Modules +======= + +Each module processes a specific part of the syntax. Modules are accessible as public properties of the `Texy\Texy` class. + + +HeadingModule +------------- + +Processes headings (both underlined and surrounded). + +```php +// Top heading level (default: 1) +$texy->headingModule->top = 1; // <h1> + +// Generate automatic IDs (default: false) +$texy->headingModule->generateID = true; + +// Prefix for generated IDs (default: 'toc-') +$texy->headingModule->idPrefix = 'section-'; + +// More characters = higher heading? (default: true) +$texy->headingModule->moreMeansHigher = true; + +// Balancing mode (default: DYNAMIC) +$texy->headingModule->balancing = Texy\Modules\HeadingModule::DYNAMIC; +``` + +After processing: + +```php +// First heading (for <title>) +echo $texy->headingModule->title; + +// Table of Contents +print_r($texy->headingModule->TOC); +``` + + +PhraseModule +------------ + +Processes inline formatting (bold, italic, links within text...). + +```php +// HTML tags for individual phrases (default: see below) +$texy->phraseModule->tags = [ + 'phrase/strong' => 'strong', + 'phrase/em' => 'em', + 'phrase/code' => 'code', + // ... more +]; + +// Allow links in phrases (default: true) +$texy->phraseModule->linksAllowed = true; +``` + + +LinkModule +---------- + +Processes links, references, and URLs. + +```php +// Root path for links (default: '') +$texy->linkModule->root = '/articles/'; + +// CSS class for image links (default: null) +$texy->linkModule->imageClass = 'image-link'; + +// Always add rel="nofollow" (default: false) +$texy->linkModule->forceNoFollow = false; + +// Shorten URLs to a more readable form (default: true) +$texy->linkModule->shorten = true; +``` + +**References:** + +```php +// Add a reference +$link = new Texy\Link('https://example.com'); +$link->modifier->title = 'Example page'; +$link->label = 'Example'; +$texy->linkModule->addReference('example', $link); +``` + +Usage: +```texy +Link to [example] +``` + + +ImageModule +----------- + +Processes images. + +```php +// Root path for images (default: 'images/') +$texy->imageModule->root = '/assets/images/'; + +// Root path for linked images (default: 'images/') +$texy->imageModule->linkedRoot = '/assets/images/full/'; + +// Physical path on disk (to determine dimensions) +$texy->imageModule->fileRoot = __DIR__ . '/public/images/'; + +// CSS class for floating images (default: null) +$texy->imageModule->leftClass = 'float-left'; +$texy->imageModule->rightClass = 'float-right'; + +// Default alternative text (default: '') +$texy->imageModule->defaultAlt = 'Image'; +``` + +**References:** + +```php +// Add a reference +$image = new Texy\Image; +$image->URL = 'photo.jpg'; +$image->modifier->title = 'Photo'; +$texy->imageModule->addReference('photo', $image); +``` + + +FigureModule +------------ + +Processes images with captions. + +```php +// HTML element (default: 'div') +$texy->figureModule->tagName = 'figure'; + +// CSS class (default: 'figure') +$texy->figureModule->class = 'photo-figure'; + +// Classes for floating images (default: null) +$texy->figureModule->leftClass = 'figure-left'; +$texy->figureModule->rightClass = 'figure-right'; + +// Offset for width calculation (default: 10) +$texy->figureModule->widthDelta = 20; + +// Require caption (default: true) +$texy->figureModule->requireCaption = true; +``` + + +ListModule +---------- + +Processes bulleted, numbered, and definition lists. + +```php +// Patterns for list bullets (default: see source code) +$texy->listModule->bullets = [ + '*' => ['\*[\ \t]', 0, ''], + '-' => ['[\x{2013}-](?![>-])', 0, ''], + // ... more +]; +``` + + +TableModule +----------- + +Processes tables. + +```php +// CSS classes for rows (default: null) +$texy->tableModule->oddClass = 'odd'; +$texy->tableModule->evenClass = 'even'; +``` + +*Note: `oddClass` and `evenClass` are deprecated.* + + +HorizLineModule +--------------- + +Processes horizontal lines. + +```php +// CSS classes by type (default: null) +$texy->horizLineModule->classes = [ + '-' => 'hr-line', + '*' => 'hr-star', +]; +``` + + +TypographyModule +---------------- + +Processes typographic adjustments. + +```php +// Locale (default: 'cs') +$texy->typographyModule->locale = 'en'; +``` + +**Supported locales:** +- `cs` – Czech quotes „text" and ‚text' +- `en` – English quotes "text" and 'text' +- `fr` – French quotes «text» and ‹text› +- `de` – German quotes „text" and ‚text' +- `pl` – Polish quotes „text" and ‚text' + + +LongWordsModule +--------------- + +Breaks long words using `­`. + +```php +// Maximum word length (default: 20) +$texy->longWordsModule->wordLimit = 25; +``` + + +EmoticonModule +-------------- + +Replaces emoticons with images or Unicode characters. + +```php +// CSS class (default: null) +$texy->emoticonModule->class = 'emoji'; + +// Path to images (default: null = uses imageModule->root) +$texy->emoticonModule->root = '/images/smilies/'; +$texy->emoticonModule->fileRoot = __DIR__ . '/public/smilies/'; + +// Emoticon definitions (default: basic set) +$texy->emoticonModule->icons = [ + ':-)' => '🙂', + ':-(' => '☹', + ';-)' => '😉', + // ... or paths to images + ':cool:' => 'cool.gif', +]; +``` + + +HtmlModule +---------- + +Processes HTML tags and comments in the input text. + +```php +// Display HTML comments in output (default: true) +$texy->htmlModule->passComment = true; +``` + + +HtmlOutputModule +---------------- + +Formats the output HTML. + +```php +// Format output with indentation (default: true) +$texy->htmlOutputModule->indent = true; + +// Base indentation level (default: 0) +$texy->htmlOutputModule->baseIndent = 0; + +// Maximum line width (default: 80) +$texy->htmlOutputModule->lineWrap = 100; + +// Preserve whitespace in these elements (default: list shown) +$texy->htmlOutputModule->preserveSpaces = [ + 'textarea', 'pre', 'script', 'code', +]; +``` + + +ScriptModule +------------ + +Processes `{{macro}}` calls. + +```php +// Argument separator (default: ',') +$texy->scriptModule->separator = ';'; +``` + + +Texy\Configurator Class .{toc: Texy\Configurator} +================================================= + +Ready-made configuration sets for common use cases. + + +safeMode() – Safe Mode .{toc: safeMode()} +----------------------------------------- + +Configuration for processing **untrusted content** from users. + +```php +Texy\Configurator::safeMode($texy); +``` + +**What it does:** +- Disables classes and IDs (`$allowedClasses = NONE`) +- Disables inline styles (`$allowedStyles = NONE`) +- Allows only safe HTML tags: + +```php +[ + 'a' => ['href', 'title'], + 'abbr' => ['title'], + 'b' => [], + 'br' => [], + 'cite' => [], + 'code' => [], + 'em' => [], + 'i' => [], + 'strong' => [], + 'sub' => [], + 'sup' => [], + 'q' => [], + 'small' => [], +] +``` + +- Filters URL schemes (only `http:`, `https:`, `ftp:`, `mailto:`) +- Disables images +- Disables reference definitions +- Disables HTML comments +- Adds `rel="nofollow"` to all links + + +disableLinks() – Disable Links .{toc: disableLinks()} +----------------------------------------------------- + +Disables all types of links. + +```php +Texy\Configurator::disableLinks($texy); +``` + +**What it does:** +- Disables all types of links (`link/reference`, `link/email`, `link/url`, `link/definition`) +- Disables links in phrases (`phraseModule->linksAllowed = false`) +- Removes `<a>` from allowed tags + + +disableImages() – Disable Images .{toc: disableImages()} +-------------------------------------------------------- + +Disables all types of images. + +```php +Texy\Configurator::disableImages($texy); +``` + +**What it does:** +- Disables images (`image`, `figure`, `image/definition`) +- Removes `<img>`, `<object>`, `<embed>`, `<applet>` from allowed tags + + +Security +======== + +Texy is designed with security in mind. It automatically protects against common attacks. + + +Protection Against XSS +---------------------- + +Cross-Site Scripting (XSS) is an attack where an attacker injects malicious JavaScript into a page. + +**Examples of attacks that Texy will block:** + +```texy +Attack attempt: <script>alert('XSS')</script> + +Attack attempt: <img src=x onerror="alert('XSS')"> + +Attack attempt: "click":javascript:alert('XSS') + +Attack attempt: [* image.jpg onload="alert('XSS')" *] +``` + +Texy automatically: +- **Validates HTML** – removes disallowed tags and attributes +- **Filters URLs** – allows only safe schemes (`http:`, `https:`, `mailto:`, `ftp:`) +- **Escapes content** – properly escapes text in attributes +- **Sanitizes attributes** – removes event handlers (`onclick`, `onerror`, ...) + +```php +$texy = new Texy\Texy; +Texy\Configurator::safeMode($texy); + +$input = '<script>alert("XSS")</script>'; +$output = $texy->process($input); + +// Output: empty (script tag removed) +``` + + +URL Validation +-------------- + +Texy checks URLs in all links and images: + +```php +$texy = new Texy\Texy; + +// Set allowed schemes (default in safeMode) +$texy->urlSchemeFilters[Texy\Texy::FILTER_ANCHOR] = + '#https?:|ftp:|mailto:#Ai'; +$texy->urlSchemeFilters[Texy\Texy::FILTER_IMAGE] = + '#https?:#Ai'; +``` + +**Examples of blocked URLs:** + +```texy +"attack":javascript:alert('XSS') // blocked +"attack":data:text/html,<script> // blocked +[* javascript:alert() *] // blocked +``` + + +Filtering HTML Tags +------------------- + +Control via `$allowedTags`: + +```php +$texy = new Texy\Texy; + +// Allow only safe tags +$texy->allowedTags = [ + 'p' => [], + 'strong' => [], + 'em' => [], + 'a' => ['href', 'title'], // only these attributes +]; + +$input = '<p>Text <script>alert()</script></p>'; +$output = $texy->process($input); + +// Output: <p>Text alert()</p> +// (script tag removed) +``` + + +Practical Example +----------------- + +```php +function processComment(string $userInput): string +{ + $texy = new Texy\Texy; + + // Safe mode + Texy\Configurator::safeMode($texy); + + // Additional restrictions + $texy->allowed['link/url'] = false; // disable auto-links + $texy->allowed['html/tag'] = false; // disable HTML + + // Process + return $texy->process($userInput); +} + +// Usage +$comment = $_POST['comment']; +$html = processComment($comment); +echo $html; // safe output +``` + + +Best Practices +-------------- + +1. **Always use safeMode()** for user content +2. **Validate input** before passing to Texy (length, format) +3. **Limit HTML tags** as needed +4. **Check output** – even though Texy is safe, double-checking never hurts +5. **Log suspicious attempts** – can help you identify attackers + +```php +$texy = new Texy\Texy; +Texy\Configurator::safeMode($texy); + +// Logging +$texy->addHandler('htmlTag', function($invocation, $el, $isStart) { + if ($el->getName() === 'script') { + error_log('XSS attempt detected!'); + } + return $invocation->proceed(); +}); +``` diff --git a/texy/en/custom-handlers.texy b/texy/en/custom-handlers.texy new file mode 100644 index 0000000000..c5e49dcf8b --- /dev/null +++ b/texy/en/custom-handlers.texy @@ -0,0 +1,850 @@ +Modifying Element Behavior +************************** + +.[perex] +This chapter describes how you can change the behavior of **existing elements** in Texy - for example, modify how images, links or formatting are processed. If you want to add **completely new syntax** that Texy doesn't know by default, read the chapter [Adding Custom Syntax |custom-syntax]. + +Imagine you want the standard image syntax `[* URL *]` to recognize a special address `[* youtube:dQw4w9WgXcQ *]` and create an embedded player instead of a regular image. + +Or you want to colorize source code listings using a syntax highlighter. And so on. This is exactly what **element handlers** are for - functions that Texy calls when processing specific elements. For example, you register a handler for the `image` element that checks the URL, and if it starts with `youtube:`, returns an iframe instead of a standard image. You don't change the syntax, you just modify what happens with the found construct. + + +Elements and Their Handlers +=========================== + +In Texy terminology, an **element** is the name for a type of item that can be processed in a document. For example, `image` is an element for images, `linkURL` for links, see [default elements |#Default Elements]. Each element has its **default handler**, which is implemented in the corresponding module and handles standard processing. + +When you write `[* image.jpg *]` in text, the parser finds this syntax, creates a `Texy\Image` object with data about the image and calls all handlers registered for the `image` element. If there is no custom handler, only the default handler from `ImageModule` is called, which creates an HTML `<img>` tag. + +You register a handler by calling the `addHandler()` method: + +```php +$texy->addHandler('image', function( + Texy\HandlerInvocation $invocation, + Texy\Image $image, + ?Texy\Link $link, +) { + // your logic here +}); +``` + +The first parameter is the element name, the second is a callback function. The callback always receives a `Texy\HandlerInvocation` object as the first parameter, followed by parameters specific to the given element. + +.[note] +A detailed explanation of all handler types can be found in the chapter [Architecture and Principles |architecture]. + + +How Processing Works +==================== + +When Texy needs to process an element, it creates a `HandlerInvocation` object containing all registered handlers for this type of element. **Your handler is called first** and can: + +- **Delegate** to the next handler by calling `$invocation->proceed()` +- **Modify input** by calling `proceed()` with modified parameters +- **Modify output** by processing the result from `proceed()` +- **Break the chain** by returning its own result without calling `proceed()` + +The `proceed()` method moves processing to the next handler in the chain. If there are no more custom handlers, the default implementation from the module is called. This means your handler has absolute control - it can decide whether the default logic is called at all. + +This mechanism is called **chain of responsibility**: + +```php +$texy->addHandler('image', function( + Texy\HandlerInvocation $invocation, + Texy\Image $image, + ?Texy\Link $link, +) { + // 1. Modify input data before processing + $image->modifier->title = 'Modified title'; + + // 2. Call next handler or default processing + $element = $invocation->proceed($image, $link); + + // 3. Modify resulting HTML element + $element->attrs['loading'] = 'lazy'; + + return $element; +}); +``` + +The execution order is from **last registered to first**. If a module registers its default handler during construction and you then register a custom handler, your handler is called first. This allows you to override or wrap the default behavior. + + +Default Elements +================ + +Texy provides several predefined elements for which you can register custom handlers. Here is their complete list with parameters that the handler receives. + + +image +----- + +Processes images. + +```php +function( + Texy\HandlerInvocation $invocation, + Texy\Image $image, + ?Texy\Link $link, +): Texy\HtmlElement|string|null +``` + +The `$image` parameter contains URL, dimensions and modifiers. The `$link` parameter is provided if the image is a link (syntax `[* img *]:url`). + + +linkReference +------------- + +Processes reference links of type `[ref]`. + +```php +function( + Texy\HandlerInvocation $invocation, + Texy\Link $link, + string $content, +): Texy\HtmlElement|string|null +``` + +The `$link` parameter contains the URL and modifiers loaded from the reference definition. The `$content` parameter is the HTML content of the link (already processed by parsing inline syntax). + + +linkEmail +--------- + +Processes automatically recognized email addresses in text. + +```php +function( + Texy\HandlerInvocation $invocation, + Texy\Link $link, +): Texy\HtmlElement|string|null +``` + +The `$link` parameter contains the email address in the `URL` property. + + +linkURL +------- + +Processes automatically recognized URLs in text. + +```php +function( + Texy\HandlerInvocation $invocation, + Texy\Link $link, +): Texy\HtmlElement|string|null +``` + +The `$link` parameter contains the found URL. + + +phrase +------ + +Processes inline formatting. + +```php +function( + Texy\HandlerInvocation $invocation, + string $phrase, + string $content, + Texy\Modifier $modifier, + ?Texy\Link $link, +): Texy\HtmlElement|string|null +``` + +The `$phrase` parameter is the syntax name like `phrase/strong` or `phrase/em`. The `$content` parameter is the text inside the formatting. The `$modifier` parameter contains CSS classes, styles and other modifiers. The `$link` parameter is provided if the formatting has an attached link. + + +newReference +------------ + +Called when the parser finds a reference that is not defined. + +```php +function( + Texy\HandlerInvocation $invocation, + string $name, +): Texy\HtmlElement|string|null +``` + +The `$name` parameter is the reference name. The handler can create a link dynamically or return `null` to reject. + + +htmlComment +----------- + +Processes HTML comments. + +```php +function( + Texy\HandlerInvocation $invocation, + string $content, +): string +``` + +The `$content` parameter is the text between `<!--` and `-->`. + + +htmlTag +------- + +Processes HTML tags in text. + +```php +function( + Texy\HandlerInvocation $invocation, + Texy\HtmlElement $el, + bool $isStart, + ?bool $forceEmpty, +): Texy\HtmlElement|string|null +``` + +The `$el` parameter is an element with name and attributes. The `$isStart` parameter determines whether it is an opening tag. The `$forceEmpty` parameter forces an empty element. + + +script +------ + +Processes scripts `{{command: args}}`. + +```php +function( + Texy\HandlerInvocation $invocation, + string $command, + array $args, + ?string $raw, +): Texy\HtmlElement|string|null +``` + +The `$command` parameter is the command name. The `$args` parameter is an array of arguments. The `$raw` parameter is the original unparsed argument string. + + +figure +------ + +Processes images with captions. + +```php +function( + Texy\HandlerInvocation $invocation, + Texy\Image $image, + ?Texy\Link $link, + string $content, + Texy\Modifier $modifier, +): Texy\HtmlElement|null +``` + +The `$content` parameter is the caption text below the image. + + +heading +------- + +Processes headings. + +```php +function( + Texy\HandlerInvocation $invocation, + int $level, + string $content, + Texy\Modifier $modifier, + bool $isSurrounded, +): Texy\HtmlElement +``` + +The `$level` parameter is the heading level (0-6). The `$content` parameter is the heading text. The `$isSurrounded` parameter determines whether it is a delimited heading (`###`) or underlined. + + +horizline +--------- + +Processes horizontal lines. + +```php +function( + Texy\HandlerInvocation $invocation, + string $type, + Texy\Modifier $modifier, +): Texy\HtmlElement +``` + +The `$type` parameter is the string of characters used for the line (`---` or `***`). + + +block +----- + +Processes special blocks `/--type` to `\--`. + +```php +function( + Texy\HandlerInvocation $invocation, + string $blocktype, + string $content, + ?string $param, + Texy\Modifier $modifier, +): Texy\HtmlElement|string +``` + +The `$blocktype` parameter is the block type with the `block/` prefix, e.g. `block/code` or `block/html`. The `$content` parameter is the block content. The `$param` parameter is an optional parameter after the type (e.g. language for code). + + +emoticon +-------- + +Processes emoticons (smileys). + +```php +function( + Texy\HandlerInvocation $invocation, + string $emoticon, + string $raw, +): Texy\HtmlElement|string +``` + +The `$emoticon` parameter is the recognized emoticon (e.g. `:-)` or `:-(` ). The `$raw` parameter is the original text including any repeating characters (e.g. `:-)))))` ). + +.[note] +Emoticons are **disabled** by default. You can enable them using `$texy->allowed['emoticon'] = true;` + + +Default Events +============== + +Texy provides several predefined events for which you can register handlers. These are called **notification handlers**. Unlike element handlers, these handlers **don't return anything**. They are used for side effects such as logging, collecting statistics, or modifying the already created DOM tree. + + +beforeParse +----------- + +Called before text parsing begins. Allows you to perform preprocessing or load definitions. + +```php +function( + Texy\Texy $texy, + string &$text, + bool $isSingleLine, +): void +``` + +The `$text` parameter is passed by reference, so you can modify it. The `$isSingleLine` parameter determines whether a single line or the entire document is being parsed. + + +afterParse +---------- + +Called after parsing is complete, before converting the DOM tree to HTML. Allows you to modify the created DOM. + +```php +function( + Texy\Texy $texy, + Texy\HtmlElement $DOM, + bool $isSingleLine, +): void +``` + +The `$DOM` parameter is the root element of the document, which you can traverse and modify. + + +afterList +--------- + +Called after a list (numbered or unnumbered) is created. + +```php +function( + Texy\BlockParser $parser, + Texy\HtmlElement $element, + Texy\Modifier $modifier, +): void +``` + +The `$element` parameter is the created `<ul>` or `<ol>` element. The `$modifier` parameter contains modifiers applied to the entire list. + + +afterDefinitionList +------------------- + +Called after a definition list is created. + +```php +function( + Texy\BlockParser $parser, + Texy\HtmlElement $element, + Texy\Modifier $modifier, +): void +``` + +The `$element` parameter is the created `<dl>` element. + + +afterTable +---------- + +Called after a table is created. + +```php +function( + Texy\BlockParser $parser, + Texy\HtmlElement $element, + Texy\Modifier $modifier, +): void +``` + +The `$element` parameter is the created `<table>` element. + + +afterBlockquote +--------------- + +Called after a blockquote is created. + +```php +function( + Texy\BlockParser $parser, + Texy\HtmlElement $element, + Texy\Modifier $modifier, +): void +``` + +The `$element` parameter is the created `<blockquote>` element. + + +Basic Usage +=========== + +The simplest element handler just delegates to the default processing: + +```php +$texy->addHandler('image', function( + Texy\HandlerInvocation $invocation, + Texy\Image $image, + ?Texy\Link $link, +) { + return $invocation->proceed(); +}); +``` + +This handler doesn't change anything, but shows the basic skeleton. It passes all parameters forward and returns the result. + + +Modifying Input Data +-------------------- + +A handler can modify data before processing: + +```php +$texy->addHandler('image', function( + Texy\HandlerInvocation $invocation, + Texy\Image $image, + ?Texy\Link $link, +) { + // add default dimensions if not specified + $image->width ??= 800; + $image->height ??= 600; + return $invocation->proceed(); +}); +``` + +Changes made to `$image` or `$link` objects will be reflected in further processing, including the default handler. + + +Modifying Output Element +------------------------ + +A handler can modify the HTML element returned from `proceed()`: + +```php +$texy->addHandler('image', function( + Texy\HandlerInvocation $invocation, + Texy\Image $image, + ?Texy\Link $link, +) { + $element = $invocation->proceed(); + + if ($element) { + // add lazy loading + $element->attrs['loading'] = 'lazy'; + + // add CSS class + $element->attrs['class'][] = 'responsive'; + } + + return $element; +}); +``` + + +Conditional Processing +---------------------- + +A handler can process only certain cases and delegate others: + +```php +$texy->addHandler('image', function( + Texy\HandlerInvocation $invocation, + Texy\Image $image, + ?Texy\Link $link, +) { + // special processing for YouTube videos + if (str_starts_with($image->URL, 'youtube:')) { + $id = substr($image->URL, 8); + $iframe = sprintf( + '<iframe src="https://youtube.com/embed/%s"></iframe>', + htmlspecialchars($id) + ); + return $invocation->getTexy() + ->protect($iframe, Texy\Texy::CONTENT_BLOCK); + } + + // process other images normally + return $invocation->proceed(); +}); +``` + + +Interrupting Processing +----------------------- + +A handler can refuse processing by returning `null`: + +```php +$texy->addHandler('image', function( + Texy\HandlerInvocation $invocation, + Texy\Image $image, + ?Texy\Link $link, +) { + // disallow external images + if (str_contains($image->URL, '://')) { + return null; + } + + return $invocation->proceed(); +}); +``` + + +Practical Examples +================== + +The following examples show real use cases for element handlers. + + +YouTube Embed +------------- + +Converting special syntax to embedded video: + +```php +$texy->addHandler('image', function( + Texy\HandlerInvocation $invocation, + Texy\Image $image, + ?Texy\Link $link, +) { + if (str_starts_with($image->URL, 'youtube:')) { + $id = substr($image->URL, 8); + $width = $image->width ?: 560; + $height = $image->height ?: 315; + + $iframe = sprintf( + '<iframe width="%d" height="%d" ' + . 'src="https://youtube.com/embed/%s" ' + . 'frameborder="0" allowfullscreen></iframe>', + $width, $height, htmlspecialchars($id) + ); + + $texy = $invocation->getTexy(); + return $texy->protect($iframe, $texy::CONTENT_BLOCK); + } + + return $invocation->proceed(); +}); +``` + +Usage in text: + +```texy +[* youtube:dQw4w9WgXcQ 640x360 *] +``` + + +Image Gallery +------------- + +Wrapping images in a special div for lightbox: + +```php +$texy->addHandler('image', function( + Texy\HandlerInvocation $invocation, + Texy\Image $image, + ?Texy\Link $link, +) { + $element = $invocation->proceed(); + + // if image has 'gallery' class + if (isset($image->modifier->classes['gallery'])) { + // wrap in div with lightbox attributes + $wrapper = new Texy\HtmlElement('div'); + $wrapper->attrs['class'][] = 'lightbox-item'; + $wrapper->attrs['data-src'] = $image->URL; + $wrapper->add($element); + + return $wrapper; + } + + return $element; +}); +``` + +Usage: + +```texy +[* image.jpg .[gallery] *] +``` + + +Link Validation +--------------- + +Checking whether links found in text lead to allowed domains: + +```php +$allowedDomains = ['example.com', 'trusted.org']; + +$texy->addHandler('linkURL', function( + Texy\HandlerInvocation $invocation, + Texy\Link $link, +) use ($allowedDomains) { + $host = parse_url($link->URL, PHP_URL_HOST); + + // if domain is not in whitelist, disallow link + if ($host && !in_array($host, $allowedDomains, true)) { + return null; + } + + return $invocation->proceed(); +}); +``` + + +Automatic rel="nofollow" +------------------------ + +Adding `nofollow` to all external links found in text: + +```php +$texy->addHandler('linkURL', function( + Texy\HandlerInvocation $invocation, + Texy\Link $link, +) { + $element = $invocation->proceed(); + + // if link contains :// (i.e. is external) + if (str_contains($link->URL, '://')) { + $element->attrs['rel'] = 'nofollow'; + } + + return $element; +}); +``` + + +Syntax Highlighting +------------------- + +Integrating a syntax highlighting library: + +```php +$texy->addHandler('block', function( + Texy\HandlerInvocation $invocation, + string $blocktype, + string $content, + ?string $param, + Texy\Modifier $modifier, +) { + // process only 'code' type blocks + if ($blocktype !== 'block/code') { + return $invocation->proceed(); + } + + // apply syntax highlighting + $highlighter = new MyHighlighter(); + $highlighted = $highlighter->highlight($content, $param); + + $el = new Texy\HtmlElement('pre'); + $modifier->decorate($invocation->getTexy(), $el); + $el->attrs['class'][] = 'language-' . $param; + + $code = new Texy\HtmlElement('code'); + $code->add($highlighted); + $el->add($code); + + return $el; +}); +``` + + +Lazy Loading +------------ + +Iterating through all images and adding lazy loading: + +```php +$texy->addHandler('afterParse', function( + Texy\Texy $texy, + Texy\HtmlElement $DOM, + bool $isSingleLine, +) { + foreach ($DOM->getIterator() as $child) { + if ($child instanceof Texy\HtmlElement + && $child->getName() === 'img' + ) { + $child->attrs['loading'] = 'lazy'; + } + } +}); +``` + + +Logging Used Elements +--------------------- + +Collecting statistics about used elements in the document: + +```php +$stats = []; + +$texy->addHandler('beforeParse', function( + Texy\Texy $texy, + string &$text, + bool $isSingleLine, +) use (&$stats) { + $stats = ['images' => 0, 'links' => 0, 'headings' => 0]; +}); + +$texy->addHandler('image', function( + Texy\HandlerInvocation $invocation, + Texy\Image $image, + ?Texy\Link $link, +) use (&$stats) { + $stats['images']++; + return $invocation->proceed(); +}); + +$texy->addHandler('linkURL', function( + Texy\HandlerInvocation $invocation, + Texy\Link $link, +) use (&$stats) { + $stats['links']++; + return $invocation->proceed(); +}); + +$texy->addHandler('heading', function( + Texy\HandlerInvocation $invocation, + int $level, + string $content, + Texy\Modifier $modifier, + bool $isSurrounded, +) use (&$stats) { + $stats['headings']++; + return $invocation->proceed(); +}); +``` + + +Helper Classes +============== + +When working with handlers, you will work with several important classes. Here is an overview with their most important properties. + + +Texy\Image +---------- + +Represents an image with its parameters: + +```php +$image->URL; // string - path to image +$image->linkedURL; // ?string - link URL (if image is a link) +$image->width; // ?int - width in pixels +$image->height; // ?int - height in pixels +$image->asMax; // bool - whether dimensions are maximum +$image->modifier; // Modifier - CSS classes, styles, attributes +$image->name; // ?string - reference name +``` + + +Texy\Link +--------- + +Represents a link with its parameters: + +```php +$link->URL; // string - target URL +$link->raw; // string - original URL (before normalization) +$link->modifier; // Modifier - CSS classes, styles, attributes +$link->type; // string - link type (COMMON, BRACKET, IMAGE) +$link->label; // ?string - link text (for references) +$link->name; // ?string - reference name +``` + +Constants for link type: + +```php +Texy\Link::COMMON; // common link +Texy\Link::BRACKET; // reference link [ref] +Texy\Link::IMAGE; // link from image [* img *] +``` + + +Texy\HtmlElement +---------------- + +Represents an HTML element with its attributes and content: + +```php +$el = new Texy\HtmlElement('div'); + +// working with element name +$el->getName(); // returns 'div' +$el->setName('section'); // changes to 'section' + +// working with attributes +$el->attrs['id'] = 'main'; +$el->attrs['class'][] = 'container'; +$el->attrs['style']['color'] = 'red'; + +// working with content +$el->setText('text'); // sets text content +$el->getText(); // returns text content +$el->add($child); // adds child +$el->insert(0, $child); // inserts child at position + +// parsing content +$el->parseLine($texy, $text); // parses inline text +$el->parseBlock($texy, $text); // parses block text + +// conversion to HTML +$el->toString($texy); // internal representation +$el->toHtml($texy); // final HTML +``` + + +Texy\Modifier +------------- + +Represents modifiers for CSS classes, styles and attributes: + +```php +$mod->id; // ?string - HTML id +$mod->classes; // array - array of CSS classes +$mod->styles; // array - array of CSS styles +$mod->attrs; // array - HTML attributes +$mod->hAlign; // ?string - horizontal alignment (left, right, center, justify) +$mod->vAlign; // ?string - vertical alignment (top, middle, bottom) +$mod->title; // ?string - title attribute or alt for images + +// applying modifier to element +$mod->decorate($texy, $element); +``` diff --git a/texy/en/custom-syntax.texy b/texy/en/custom-syntax.texy new file mode 100644 index 0000000000..9a8365fc43 --- /dev/null +++ b/texy/en/custom-syntax.texy @@ -0,0 +1,519 @@ +Adding Custom Syntax +******************** + +.[perex] +This chapter describes how to add **completely new markup constructs** to Texy that don't exist by default. If you only want to modify the behavior of existing elements (for example, adjust image or link processing), read the chapter [Custom Element Behavior |custom-handlers]. + +Imagine you want to automatically create links to user profiles in documentation by writing `@@username`. Or you need special alert blocks like `:::warning`. Texy doesn't recognize these constructs, and you can't create them by modifying existing elements. + +Custom syntax allows you to define new markup constructs. You specify what the construct should look like (using a regular expression) and write a function to process it. Texy will then recognize your syntax just like its standard constructs. + + +Syntax Registration +=================== + +Texy provides two methods for registering custom syntax, depending on whether it's an inline or block element. + + +Line Syntax +----------- + +Line syntax is used for inline constructs within text lines. You register it using the `registerLinePattern()` method: + +```php +$texy->registerLinePattern( + callable $handler, + string $pattern, + string $name, + ?string $againTest = null, +); +``` + +**The `$handler` parameter** is a callback function that gets called when the syntax is found. It can be a function name, anonymous function, or array `[$object, 'method']`. + +**The `$pattern` parameter** is a regular expression (PCRE) that defines what your syntax looks like in the text. The pattern **should not be anchored** to the start of a line (`^`), since it's searched for anywhere in the text. Use capturing groups to capture the data you need to process. + +**The `$name` parameter** is a unique syntax name. It's used in the `$texy->allowed` array for enabling/disabling and passed to the handler for identification. We recommend using a prefix style like `custom/username` or `myapp/profile`. + +**The `$againTest` parameter** is an optional regex for optimization. If specified, Texy first checks whether the text contains anything that could match your pattern. Only if `$againTest` succeeds does it run the more complex pattern. This significantly speeds up processing if you have a complex pattern that's rarely used. + +Registration example: + +```php +$texy->registerLinePattern( + 'usernameHandler', + '#@@([a-z0-9_]+)#i', + 'custom/username', +); +``` + + +Block Syntax +------------ + +Block syntax is used for multi-line block constructs. You register it using the `registerBlockPattern()` method: + +```php +$texy->registerBlockPattern( + callable $handler, + string $pattern, + string $name, +); +``` + +The `$handler` and `$name` parameters have the same meaning as for line syntax. + +**The `$pattern` parameter** is a regular expression that **must be anchored** to the start of a line (`^`) and often to the end (`$`) as well. BlockParser automatically adds the `Am` modifiers (anchored, multiline), so don't add them to the pattern. The pattern should match the entire block or at least its beginning. + +Registration example: + +```php +$texy->registerBlockPattern( + 'alertHandler', + '#^:::(warning|info|danger)\n(.+)$#s', + 'custom/alert', +); +``` + + +Syntax Handler +============== + +A syntax handler is a function called by the parser when it finds an occurrence of your syntax in the text. Its job is to process the found data and return an HTML element or string. + +A detailed explanation of the syntax handler's role in Texy's architecture can be found in the chapter [Architecture and Principles |architecture#syntax-handler]. + + +For Line Syntax +--------------- + +Syntax handler signature for line syntax: + +```php +function( + Texy\LineParser $parser, + array $matches, + string $name, +): Texy\HtmlElement|string|null +``` + +**The `$parser` parameter** provides access to the parser and Texy object. You'll most often use `$parser->getTexy()` to get the Texy instance. + +**The `$matches` parameter** contains the regex match results. `$matches[0]` is the entire matched string, `$matches[1]`, `$matches[2]` etc. are the capturing groups from your pattern. + +**The `$name` parameter** is the syntax name you specified during registration. Useful if one handler processes multiple syntaxes. + +**The return value** can be `Texy\HtmlElement` for structured HTML output, `string` for direct HTML code (which you must protect), or `null` to refuse processing. + +The handler can set `$parser->again = true` if it wants the content of the created element to be parsed again to find nested syntaxes. + + +For Block Syntax +---------------- + +Syntax handler signature for block syntax: + +```php +function( + Texy\BlockParser $parser, + array $matches, + string $name, +): Texy\HtmlElement|string|null +``` + +The parameters have the same meaning as for line syntax, except you receive `Texy\BlockParser` instead of `LineParser`. + +BlockParser provides methods for working with multi-line structures: + +- **`$parser->next($pattern, &$matches)`** - matches the next line against the pattern and returns true/false +- **`$parser->moveBackward($lines)`** - moves back the specified number of lines +- **`$parser->isIndented()`** - returns true if the current block is indented + + +LineParser API +============== + +When working with line syntax, you have several useful properties and methods available. + +**The `$again` property** controls whether the currently processed syntax should be searched for again at the same position after processing. The default value is `false`. Set to `true` if you're creating an element with content that may contain other syntaxes: + +```php +function( + Texy\LineParser $parser, + array $matches, + string $name, +): Texy\HtmlElement +{ + $el = new Texy\HtmlElement('span'); + $el->setText($matches[1]); + + // content may contain additional formatting + $parser->again = true; + + return $el; +} +``` + +**The `getTexy()` method** returns the Texy object instance, which you need for working with `protect()` or accessing configuration. + + +BlockParser API +=============== + +When working with block syntax, you have methods available for working with multi-line structures. + +**The `next($pattern, &$matches)` method** tries to match the next line in the text against the specified pattern. If successful, it fills `$matches` with the result and moves the internal position past this line. Returns `true` on success, `false` on failure: + +```php +while ($parser->next('#^\-\s+(.+)$#', $matches)) { + // process next list item + $item = $matches[1]; +} +``` + +**The `moveBackward($lines = 1)` method** moves the internal position back the specified number of lines. Useful when your pattern matched more than the block's start and you want to return to the beginning: + +```php +// pattern matched 3 lines, but we want to read from the first +$parser->moveBackward(2); +``` + +**The `isIndented()` method** returns `true` if the current block is indented (starts with a space or tab). This indicates that it's nested content. + + +Practical Examples +================== + +The following examples demonstrate real use cases for custom syntax. + + +User Profiles +------------- + +Automatic creation of profile links by writing `@@username`: + +```php +$texy->registerLinePattern( + function( + Texy\LineParser $parser, + array $matches, + string $name, + ): Texy\HtmlElement + { + $username = $matches[1]; + + $el = new Texy\HtmlElement('a'); + $el->attrs['href'] = '/user/' . urlencode($username); + $el->attrs['class'][] = 'user-profile'; + $el->setText('@' . $username); + + return $el; + }, + '#@@([a-z0-9_]+)#i', + 'custom/username' +); +``` + +Usage in text: + +```texy +Check out the profile of @@johndoe or @@jane_smith. +``` + + +Alert Boxes +----------- + +Special alert blocks with different types: + +```php +$texy->registerBlockPattern( + function( + Texy\BlockParser $parser, + array $matches, + string $name, + ): Texy\HtmlElement + { + $type = $matches[1]; // warning, info, danger + $content = $matches[2]; + + $el = new Texy\HtmlElement('div'); + $el->attrs['class'][] = 'alert'; + $el->attrs['class'][] = 'alert-' . $type; + + $texy = $parser->getTexy(); + $el->parseBlock($texy, trim($content)); + + return $el; + }, + '#^:::(warning|info|danger)\n(.+?)(?=\n:::|$)#s', + 'custom/alert' +); +``` + +Usage in text: + +```texy +:::warning +This is an important warning! +::: + +:::info +For your information: the update will take place tomorrow. +::: +``` + + +Hashtags +-------- + +Automatic creation of links from hashtags: + +```php +$texy->registerLinePattern( + function( + Texy\LineParser $parser, + array $matches, + string $name, + ): Texy\HtmlElement + { + $tag = $matches[1]; + + $el = new Texy\HtmlElement('a'); + $el->attrs['href'] = '/tag/' . urlencode($tag); + $el->attrs['class'][] = 'hashtag'; + $el->setText('#' . $tag); + + return $el; + }, + '#\#([a-z0-9_]+)#i', + 'custom/hashtag', + '#\##' // optimization - search only if # is in text +); +``` + +Usage: + +```texy +Article about #php and #webdesign. +``` + + +Abbreviations +------------- + +Automatic expansion of abbreviations with explanation: + +```php +$abbreviations = [ + 'HTML' => 'HyperText Markup Language', + 'CSS' => 'Cascading Style Sheets', + 'PHP' => 'PHP: Hypertext Preprocessor', +]; + +$texy->registerLinePattern( + function( + Texy\LineParser $parser, + array $matches, + string $name + ) use ($abbreviations): ?Texy\HtmlElement + { + $abbr = $matches[1]; + + if (!isset($abbreviations[$abbr])) { + return null; // unknown abbreviation + } + + $el = new Texy\HtmlElement('abbr'); + $el->attrs['title'] = $abbreviations[$abbr]; + $el->setText($abbr); + + return $el; + }, + '#\b([A-Z]{2,})\b#', + 'custom/abbreviation' +); +``` + + +Inline Icons +------------ + +Inserting icons using special syntax: + +```php +$texy->registerLinePattern( + function( + Texy\LineParser $parser, + array $matches, + string $name, + ): Texy\HtmlElement + { + $icon = $matches[1]; + + $el = new Texy\HtmlElement('i'); + $el->attrs['class'][] = 'icon'; + $el->attrs['class'][] = 'icon-' . $icon; + $el->attrs['aria-hidden'] = 'true'; + + return $el; + }, + '#:icon-([a-z-]+):#', + 'custom/icon' +); +``` + +Usage: + +```texy +Click the button :icon-download: to download. +``` + + +Note Block +---------- + +Block for footnotes: + +```php +$texy->registerBlockPattern( + function( + Texy\BlockParser $parser, + array $matches, + string $name + ): Texy\HtmlElement + { + $parser->moveBackward(); + + $content = ''; + while ($parser->next('#^NOTE:\s*(.+)$#', $matches)) { + $content .= $matches[1] . "\n"; + } + + $el = new Texy\HtmlElement('aside'); + $el->attrs['class'][] = 'note'; + + $texy = $parser->getTexy(); + $el->parseBlock($texy, trim($content)); + + return $el; + }, + '#^NOTE:\s*(.+)$#m', + 'custom/note' +); +``` + +Usage: + +```texy +NOTE: This is an important note. +NOTE: It can be multi-line. +``` + + +Custom Quotations with Author +----------------------------- + +Extended syntax for quotations with author attribution: + +```php +$texy->registerBlockPattern( + function( + Texy\BlockParser $parser, + array $matches, + string $name, + ): Texy\HtmlElement + { + $author = $matches[1]; + $quote = $matches[2]; + + $blockquote = new Texy\HtmlElement('blockquote'); + + $texy = $parser->getTexy(); + $blockquote->parseBlock($texy, trim($quote)); + + $cite = new Texy\HtmlElement('cite'); + $cite->setText($author); + $blockquote->add($cite); + + return $blockquote; + }, + '#^QUOTE\[([^\]]+)\]:\n(.+?)(?=\n\n|$)#s', + 'custom/quote' +); +``` + +Usage: + +```texy +QUOTE[Albert Einstein]: +Imagination is more important than knowledge, +because knowledge is limited. +``` + + +Image Gallery +------------- + +Special block for creating a gallery from multiple images: + +```php +$texy->registerBlockPattern( + function( + Texy\BlockParser $parser, + array $matches, + string $name, + ): Texy\HtmlElement + { + $parser->moveBackward(); + + $gallery = new Texy\HtmlElement('div'); + $gallery->attrs['class'][] = 'gallery'; + + while ($parser->next('#^\[G\]\s*(.+)$#', $matches)) { + $img = new Texy\HtmlElement('img'); + $img->attrs['src'] = trim($matches[1]); + $img->attrs['loading'] = 'lazy'; + $gallery->add($img); + } + + return $gallery; + }, + '#^\[G\]\s*(.+)$#m', + 'custom/gallery' +); +``` + +Usage: + +```texy +[G] image1.jpg +[G] image2.jpg +[G] image3.jpg +``` + + +Syntax Collisions +================= + +When registering custom syntax, you must be careful that it doesn't collide with existing Texy syntaxes or other custom syntaxes. + +**Registration order matters.** Line syntaxes are searched in the order they were registered. If multiple syntaxes can match at the same position, the one registered earlier wins. Therefore, register more specific syntaxes before more general ones. + +**Be specific in patterns.** The more concrete your pattern is, the lower the risk of collision. The pattern `#\#\w+#` also matches `#heading`, which could collide with headings. Better is `#(?<=\s)\#[a-z0-9_]+#i`, which requires a space before the hashtag. + +**Test combinations.** Try how your syntax works in combination with existing constructs. What happens when your markup is inside a link? What if it's inside a code block? + +**Use prefixed names.** Instead of `username`, use `custom/username` or `myapp/username`. This prevents conflicts if Texy adds syntax with the same name in the future. + + +Best Practices +============== + +**Return `null` on failure.** If the handler determines it can't or doesn't want to process the given match (for example, an unknown abbreviation), return `null`. The parser will then try other syntaxes. + +**Use `protect()` for HTML.** If you're returning a raw HTML string instead of `HtmlElement`, you must protect it using `$texy->protect($html, Texy::CONTENT_...)`. Otherwise it will be escaped. + +**Set `$parser->again` correctly.** For line syntaxes that create an element with text content that may contain other syntaxes (formatting, links), set `$parser->again = true`. + +**Respect `$texy->allowed`.** If you're creating a module with multiple syntaxes, check `$texy->allowed[$name]` before registering the pattern or in the handler before processing. diff --git a/texy/en/develop.texy b/texy/en/develop.texy new file mode 100644 index 0000000000..1b3c0eda6c --- /dev/null +++ b/texy/en/develop.texy @@ -0,0 +1,35 @@ +For Developers +************** + +.[perex] +Welcome to the Texy programmer documentation! This section will guide you from basic usage to advanced techniques of extension and custom syntax. + + +[Quick Start | quickstart] +-------------------------- + +Installation, first use and basic configuration. In 5 minutes you will have Texy functional. + + +[Configuration | configuration] +------------------------------- + +Complete overview of all modules, their properties and configuration options. Security settings, allowed tags, styles and classes. + + +[Custom Element Behavior | custom-handlers] +------------------------------------------- + +Learn how to change the behavior of existing syntax. YouTube embedding, syntax highlighting, custom validation. + + +[Adding Custom Syntax | custom-syntax] +-------------------------------------- + +Creating completely new syntactic elements. + + +[Architecture and Principles | architecture] +-------------------------------------------- + +Understanding how Texy works internally. Parsing flow, modules, pattern matching, protect/unprotect mechanism. diff --git a/texy/en/quickstart.texy b/texy/en/quickstart.texy new file mode 100644 index 0000000000..e51ba7af7c --- /dev/null +++ b/texy/en/quickstart.texy @@ -0,0 +1,205 @@ +Quick Start +*********** + +.[perex] +Learn how to work with Texy in just a few minutes. This page guides you through installation, first use, and basic configuration. + + +Installation +============ + +Texy leverages modern PHP features and requires at least version 8.1. + +The simplest installation method is via Composer: + +```bash +composer require texy/texy +``` + +Composer will automatically download Texy and all its dependencies. + + +First Use +========= + + +Basic Text Processing +--------------------- + +Creating a Texy instance and processing text is extremely simple: + +```php +require __DIR__ . '/vendor/autoload.php'; + +$texy = new Texy\Texy; + +$text = 'This is **bold text** and this is //italic//.'; +$html = $texy->process($text); + +echo $html; +``` + +Output: +```html +<p>This is <strong>bold text</strong> and this is <em>italic</em>.</p> +``` + +The `process()` method processes the entire text including block elements (paragraphs, headings, lists, tables...). + + +Single-line Text +---------------- + +If you're processing only single-line text without block elements (such as database headings or short descriptions): + +```php +$texy = new Texy\Texy; + +$text = 'Link to "homepage":https://example.com'; +$html = $texy->processLine($text); + +echo $html; +``` + +Output: +```html +Link to <a href="https://example.com">homepage</a> +``` + +The `processLine()` method doesn't wrap the output in a `<p>` paragraph and processes only inline elements. + + +Basic Configuration +=================== + +Texy works out of the box, but you'll often want to adjust the basic settings. + + +Setting Image Paths +------------------- + +If you're using relative paths to images, set the root directory: + +```php +$texy = new Texy\Texy; + +// Web path (prepended to relative URLs) +$texy->imageModule->root = '/images/'; + +// Physical disk path (for determining dimensions) +$texy->imageModule->fileRoot = __DIR__ . '/public/images/'; +``` + +Now when you write `[* photo.jpg *]`, Texy will generate `<img src="/images/photo.jpg">` and automatically determine the image dimensions. + + +Setting Link Paths +------------------ + +Similarly, you can set the root directory for links: + +```php +$texy->linkModule->root = '/articles/'; +``` + + +Enabling and Disabling Syntaxes +------------------------------- + +Each part of the Texy syntax can be disabled or enabled using the `$allowed` array: + +```php +$texy = new Texy\Texy; + +// Disable images +$texy->allowed['image'] = false; + +// Disable HTML tags in input +$texy->allowed['html/tag'] = false; + +// Enable emoticons (disabled by default) +$texy->allowed['emoticon'] = true; +``` + +A complete list of syntax options can be found in [configuration | configuration#allowed]. + + +Safe Mode for User Content +-------------------------- + +If you're processing content from users (comments, forum posts), use safe mode: + +```php +$texy = new Texy\Texy; +Texy\Configurator::safeMode($texy); + +$userInput = $_POST['comment']; +$html = $texy->process($userInput); +``` + +SafeMode: +- Allows only **safe HTML tags** (`<strong>`, `<em>`, `<a>`, ...) +- Disables **classes and IDs** +- Disables **inline styles** +- Disables **images** +- Adds `rel="nofollow"` to all links +- Filters **URL schemes** (only `http:`, `https:`, `ftp:`, `mailto:`) + +More about security in the chapter [Configuration – Security |configuration#Security]. + + +Complete Example +================ + +```php +require __DIR__ . '/vendor/autoload.php'; + +$texy = new Texy\Texy; + +// Configuration +$texy->imageModule->root = '/images/'; +$texy->linkModule->root = '/'; +$texy->allowed['html/tag'] = false; + +// Text to process +$text = ' + + +Article Heading +=============== + +This is an **introductory paragraph** with a link to "homepage":https://example.com. + +- First item +- Second item +- Third item + +[* photo.jpg .(Photograph) *] +'; + +// Processing +$html = $texy->process($text); + +// Output +echo $html; + +// Additional information +echo "Page title: " . $texy->headingModule->title; +print_r($texy->summary['links']); +print_r($texy->summary['images']); +``` + +After processing, you have access to: +- `$texy->headingModule->title` – first heading (suitable for `<title>`) +- `$texy->summary['links']` – array of all used links +- `$texy->summary['images']` – array of all used images + + +Next Steps +========== + +Now you know how to use Texy. Continue with: + +- **[Configuration | configuration]** – detailed settings for all modules +- **[Syntax | syntax]** – learn the Texy markup +- **[Architecture | architecture]** – understand how Texy works internally diff --git a/texy/en/syntax.texy b/texy/en/syntax.texy new file mode 100644 index 0000000000..5888ec4a7a --- /dev/null +++ b/texy/en/syntax.texy @@ -0,0 +1,891 @@ +Syntax +****** + +.[perex] +Texy was created to allow inexperienced users to easily edit website content. Therefore, its syntax is intuitive and clear. + + +Cheat Sheet +=========== + +| [Text Formatting |#Text formatting] | Syntax +|----------------------------------------------- +| [Bold text |#Text formatting] | .[text-code] ''**bold text**'' +| [Italics |#Text formatting] | ''*italics*'' or ''//italics//'' +| [Inline code |#Text formatting] | ''`code`'' +| [#Links] | ''"text":URL'' or ''[text](URL)'' +| [#Images] | ''[* image.jpg *]'' +| [#Disabling Formatting] | ''special characters'' +|----------------------------------------------- +| Elements +|----------------------------------------------- +| [#Underlined Headings] | H1 <br> === +| [#Surrounded Headings] | ''### H1'' <br> ## H2 +| [#Bulleted Lists] | ''- first'' <br> ''- second'' +| [#Numbered Lists] | ''1) first'' <br> ''2) second'' +| [#Definition Lists] | term: <br>   ''- first'' +| [#Blockquotes] | ''> blockquote'' +| [#Horizontal Rules] | ''---'' +| [#Tables] | ''\| cell \| cell \|'' +| [Code Blocks |#Preformatted text] | ''/--'' <br> ... <br> ''\--'' +|----------------------------------------------- +| Modifiers .[#toc-modifiers] +|-------------------------------------------------------- +| title | ''.(title)'' +| CSS class | ''.[btn btn-primary]'' +| ID | ''.[#id]'' +| CSS style or HTML attribute | ''.{color: blue}'' or ''.{target: _blank}'' +| horizontal alignment | ''.< .> .<> .='' +| vertical alignment | ''.^ .- ._'' + + +Paragraphs of text +================== + +Texy considers a paragraph to be one or more consecutive lines of text. As soon as you leave **one blank line** between them, Texy automatically understands that it should start a new paragraph. + +This means that Texy will join lines that belong together. You don't have to worry about a sentence breaking in the middle when you shrink the editor window. + +```texy +This is the first paragraph. It can easily have multiple lines, +and Texy will join them into one continuous block of text. + +Only here, after a blank line, does a completely new, second paragraph begin. +``` + +However, line merging can be disabled in the configuration, after which each line is considered a separate paragraph: + +/--php +$texy->mergeLines = false; +\-- + + +Line Breaks +----------- + +But what if you just need to break a line without creating a whole new paragraph? This is typically useful for poems, song lyrics, or when writing an address. **Start the new line with a single space**. + +```texy +Karel Novák, + U Tiché pošty 5 + 150 00 Praha 5 +``` + + +Styling Paragraphs +------------------ + +Sometimes you need to distinguish an entire paragraph—for example, to make it a lead paragraph of an article, center it, or assign it a specific style for a border. This is where [#modifiers] come in, which you can place either on a separate line **before** the paragraph or at the end of its last line. + +```texy +.[perex] +This is the lead paragraph of the article, which, thanks to the modifier, +will get the CSS class "perex" and can thus look different from the rest of the text. + +This paragraph, in turn, has a unique ID assigned. .[#section-intro] + +And this paragraph will be centered. .<> +``` + + +Text formatting +=============== + +| syntax | output | Syntax ID +|----------------------------------------------------------------------------- +| .[text-code] ''**bold text**'' | **bold text** | `phrase/strong` +| ''*italics* or //italics//'' | *italics* | `phrase/em-alt`, `phrase/em` +| ''***bold italics***'' | ***bold italics*** | `phrase/strong+em` +| ''`inline code`'' | `inline code` | `phrase/code` +| ''x^2 … O_2'' | x^2 … O_2 | `phrase/sup-alt`, `phrase/sub-alt` +| ''x^^2^^ … O__2__'' | x^2 … O_2 | `phrase/sup`🔸, `phrase/sup`🔸 +| ''++inserted text++'' | <ins>inserted text</ins> | `phrase/ins`🔸 +| ''--deleted text--'' | <del>deleted text</del> | `phrase/del`🔸 +| ''>>quoted text<<'' | >>quoted text<< | `phrase/quote` +| ''"blue text .{color: blue}"'' | "blue text .{color: blue}" | `phrase/span` +| ''~blue text .{color: blue}~'' | ~blue text .{color: blue}~ | `phrase/span-alt` +| ''"et al."((and others))'' | "et al."((and others)) | `phrase/acronym` +| ''NBA((National Basketball Association))'' | NBA((National Basketball Association)) | `phrase/acronym-alt` + +Syntaxes marked with 🔸 are not enabled by default and you must turn them on. For example: + +/--php +$texy->allowed['phrase/ins'] = true; +\-- + +For simple numerical indices, you can use the shorthand syntax `x^2` and `O_2`, but for more complex cases, the double-character variant is more robust, or you can use the HTML tags `<sup>` and `<sub>`. + +There **must not be spaces** inside the syntax characters: + +```texy +Wrong: ** this will not be bold ** +Correct: **this will be bold** +``` + + +Styling Text +------------ + +This is one of Texy's most powerful features. You can "attach" [#modifiers] to any formatted text to add a CSS class, ID, or direct style. The modifier is always inserted **just before the closing tag**: + +```texy +This text is **strong and green .{color:green}** like the Hulk. + +Warning: --This feature is deprecated .[deprecated]-- +``` + +If you want to apply a modifier to text without making it bold or italic, use quotation marks `"` or tildes `~` as the enclosing characters. Texy will then create a universal HTML `<span>` tag with your styles: + +```texy +Regular text, but "this piece is red .{color: red}", and the rest is not. +``` + + +Formatting and Links in One +--------------------------- + +You can turn formatted text into a link - simply add a colon and the URL: + +```texy +Visit our **new gallery**:https://example.com/gallery +``` + +This works for bold text, italics, and inline code. + + +Writing Special Characters +-------------------------- + +What if you want to write `**text**` literally, including the asterisks, without it becoming bold text? You have three options: + +- a backslash is the quickest way to escape a single special character `\**text\**` +- double apostrophes [disable Texy|#Disabling Formatting] for the entire phrase `''**text**''` +- you can use standard HTML entities `**text**` + + +Links +===== + +Links are the soul of the internet. In Texy, their creation is designed to be as natural and clear as possible directly within the text. + +The basic syntax for a link is simple and highly readable. Enclose the linked text in `"` (or other characters for [#text formatting]) and immediately append a colon and the target URL: + +```texy +Visit the official website of the "Nette Framework":https://nette.org. + +If you have a question, "email us":info@example.com. +``` + +The advantage is that Texy is intelligent and automatically recognizes where the URL ends. So you don't have to worry about it accidentally including a period or comma at the end of a sentence in the link. However, if the URL contains non-standard characters, you can enclose it in square brackets to precisely define the start and end of the address: + +```texy +"Read our article":[https://example.com/news?id=1&category=articles] +``` + +Syntax ID `phrase/span`, `phrase/span-alt` | [PhraseModule |configuration#phrasemodule] and [LinkModule |configuration#linkmodule] + + +Alternative Link Syntax +----------------------- + +Are you used to the format used by Markdown or Wikipedia? Texy understands them too. You can choose the style that suits you best. + +```texy +[Link text](https://address.com) // Style known from Markdown +[Link text | https://address.com] // Style known from MediaWiki +text:[target URL or reference] // Single-word link +``` + +Syntax ID `phrase/markdown`, `phrase/wikilink`, `phrase/quicklink` | [PhraseModule |configuration#phrasemodule] + + +Organizing Links with References +-------------------------------- + +When writing longer texts, it can be inconvenient to insert long URLs directly into paragraphs—it can harm readability and clarity. For these cases, Texy has **reference links**. + +In the text, you use only a short, easily memorable reference name. And at the end of the document, you define all these references clearly. + +```texy +We recommend studying the "official documentation":[doc] and going through the "syntax examples":[syntax]. +The entire project is built on [Nette]. + +​[doc]: https://texy.nette.org/en/ "Texy Documentation!" +​[syntax]: https://texy.nette.org/en/syntax +​[Nette]: https://nette.org +``` + +Syntax ID `link/reference`, `link/definition` | [LinkModule |configuration#linkmodule] + + +Automatic Links +--------------- + +Whenever you write a URL (starting with `http://`, `https://`, `www.`) or an email address in the text, Texy will automatically recognize it and convert it into a clickable link. You don't have to do anything at all. + +```texy +You can find our website at www.example.com. +For support, write to support@example.com. +``` + +Syntax ID `link/url`, `link/email` | [LinkModule |configuration#linkmodule] + + +Styling Links +------------- + +With [#modifiers], you can easily add other properties to links: + +```texy +"External link .[external](Opens in a new window){target:_blank}":https://google.com +``` + +The special class `nofollow` adds the `rel="nofollow"` attribute to the link, signaling to search engines not to follow this link. This is useful, for example, for links in comments. + +```texy +"A link I don't trust .[nofollow]":https://example.com +``` + + +Automatic Email Masking +----------------------- + +Texy automatically obfuscates (masks) email addresses from spambots: + +```html +<a href="mailto:info@example.com">info@<!-- -->example.com</a> +``` + +You can disable this behavior: + +/--php +$texy->obfuscateEmail = false; +\-- + + +Direct HTML +=========== + +Texy is designed so that you don't have to write HTML at all. But what if you encounter a situation where inserting a direct HTML tag is simpler, or you need to create something that Texy's syntax doesn't cover? No problem. Texy gives you complete freedom to combine both worlds. + +You can seamlessly switch between Texy syntax and pure HTML whenever it suits you. + +```texy +This is **bold text** in Texy and this is <strong>bold text</strong> using HTML. + +<div class="info-box"> + <h3>You can also insert entire complex blocks</h3> +</div> +``` + +You might think that inserting direct HTML can be risky. What if you make a mistake or someone inserts malicious code? Texy thinks about this and acts as an intelligent filter and helper: + +- **Corrects errors:** Texy ensures that the resulting code is always valid and won't break your page. +- **Monitors security:** By default, Texy has a list of allowed tags and their attributes. If an unknown tag or a potentially dangerous attribute (e.g., `onclick`) appears in the code, Texy will safely remove it. This protects your website from XSS attacks. +- **Ensures a consistent output:** No matter what HTML code you insert, Texy will make sure the result is always well-formed. + +You can customize this protective shield. Using the `$texy->allowedTags` configuration, you can precisely define which HTML tags and attributes are allowed on your website and which are not. + +This gives you full control over what HTML, for example, editors can use, ensuring the consistency and security of the entire site. For more information, see the "configuration":configuration#allowedtags section. + +Syntax ID `html/tag`, `html/comment` | [HtmlModule |configuration#htmlmodule] + + +Headings +======== + +Texy offers you two elegant and intuitive ways to create headings: **underlined** and **surrounded**. + + +Underlined Headings +------------------- + +This style is reminiscent of a typewriter. Simply place an underline (at least 3 characters) below the heading. The importance of the title is determined by the underlining character. From highest to lowest, these are: `#` `*` `=` `-` + +```texy +This is the most important heading of the entire document +​################################################ + +And this is a second-level heading +​****************************** +``` + +Syntax ID `heading/underlined` | [HeadingModule |configuration#headingmodule] + + +Surrounded Headings +------------------- + +This method is very quick to write. You "wrap" the heading text between `#` or `=` characters. Here, the level of the heading is determined by the **number** of characters used (2 to 7). The more characters, the more important the heading. + +```texy +=== Most important heading (H1) + +== Less important (H2) + +# Even less important (H3) +``` + +You can use borders on both sides (for better visual clarity) or just at the beginning. Texy can handle both variants. + +Syntax ID `heading/surrounded` | [HeadingModule |configuration#headingmodule] + + +Styling Headings +---------------- + +You can add [#modifiers] to any heading. This allows you to assign it a specific CSS class for styling or a unique ID that you can then link to. + +```texy +Heading with red color .[red-heading] +​========================================== + +### Heading with a unique ID for linking .[#contact] +``` + + +Automatic Anchors for Easy Navigation +------------------------------------- + +Don't want to come up with an ID for every heading manually? Texy can do it for you! In the configuration, you can enable automatic ID generation for all headings. This is incredibly useful for directly linking to specific sections. + +```php +// Enable automatic ID generation +$texy->headingModule->generateID = true; + +// Optionally set a prefix for generated IDs (e.g., "toc-") +$texy->headingModule->idPrefix = 'toc-'; +``` + +With this setting, the heading `## My Chapter` will automatically get an ID like `id="toc-my-chapter"` without you having to write anything extra. + + +Lists +===== + + +Bulleted Lists +-------------- + +For a quick list of items where the order doesn't matter, a bulleted list is perfect. Just start each line with a hyphen `-`, an asterisk `*`, or a plus sign `+`, followed by a space. All three characters work the same, so you can choose the one you prefer. + +```texy +What needs to be bought: + +- Milk +- Bread +* Eggs ++ Butter +``` + +ID syntaxe `list` | [ListModule |configuration#listmodule] + + +Numbered Lists +-------------- + +Texy supports various numbering styles: + +| `1.` | Arabic numerals (with a period) +| `1)` | Arabic numerals (with a parenthesis) +| `a)` | Lowercase letters of the alphabet +| `A)` | Uppercase letters of the alphabet +| `I)` | Roman numerals + +The beauty of this is that you don't have to worry about correct numbering at all. Even if you number all the lines with a one, Texy will automatically renumber them for you. This is a huge advantage when you later need to add, delete, or move an item. + + +Nested and Combined Lists +------------------------- + +The power of lists truly shines when you combine and nest them. This allows you to create clear, multi-level structures. You create nesting simply by indenting the line by at least **two spaces** (or one tab). + +```texy +1) First chapter + a) Subchapter 1.1 + - First point + - Second point + b) Subchapter 1.2 +2) Second chapter + - Main idea + - Another note +``` + + +Definition Lists +---------------- + +For cases where you need to create a glossary of terms or clearly explain several terms, a definition list is ideal. + +On the first line, write the term you want to define and end it with a colon. On the following lines, write its definition, indenting each line and starting it with a hyphen `-`. + +```texy +HTML: + - Markup language for creating web pages. + - Abbreviation for HyperText Markup Language. + +CSS: + - Language for describing the presentation (styling) of pages. + - Abbreviation for Cascading Style Sheets. +``` + +Syntax ID `list/definition` | [ListModule |configuration#listmodule] + + +Styling Lists +------------- + +Just like with other elements in Texy, you can easily add [#modifiers] to lists to change their appearance. + +**Entire list:** Write the modifier on the line **before** the start of the list. + +```texy +.[colored-list] +- First item +- Second item +``` + +**Individual item:** Add the modifier to the **end** of the line of the given item or definition term. + +```texy +- Regular item +- This item is important! .{font-weight: bold} +- Another regular item +``` + + +Images +====== + +The basic syntax is very simple. Just enclose the path to the image (whether a local file or a URL) in square brackets with an asterisk: + +```texy +[* image.jpg *] +[* https://domain.com/logo.png *] +``` + +Often you will want the text to wrap around the image. For this, there are simple alignment tags that are inserted before the closing bracket: + +```texy +[* image.jpg <] This text will flow smoothly around the image from the right side. + +[* image.jpg >] In this case, the text will instead flow around the image from the left side. + +[* large-image.jpg <>] +This text will continue below the centered image. +``` + +A properly inserted image should also have "alternative text," which is displayed if the image fails to load. Using a [modifier|#modifiers], you can add this text and other elements for styling. + +```texy +[* landscape-photo.jpg .(A beautiful mountain landscape at sunset)[main-photo] *] +``` + + +Image Dimensions +---------------- + +Texy can automatically detect the dimensions of local images (if the `$texy->imageModule->fileRoot` path is set) and add them to the HTML, which speeds up page loading. However, if you want to set the dimensions manually, you have several options: + +| `[* img.jpg 150x100 *]` | Exact width 150px and height 100px +| `[* img.jpg 150 *]` | Width will be 150px, height will be automatically calculated while maintaining the aspect ratio +| `[* img.jpg ?x100 *]` | Height will be 100px, width will be automatically calculated + + +Clickable Images +---------------- + +Do you want a large image to be displayed when a small thumbnail is clicked? Or for an image to link to another page? Just add a colon and the target URL after the image syntax. + +```texy +[* thumbnail.jpg *]:large.jpg +[* nette-logo.png *]:https://nette.org +``` + +For galleries, there is also a handy shortcut `::`. This automatically creates a link to the same file located at `$texy->imageModule->linkedRoot`. + + +Visible Caption Below the Image +------------------------------- + +If you want to add a visible caption under an image (e.g., the author's name or a description of the scene), write three asterisks `***` and the caption text after it. Texy will automatically create a semantically correct HTML structure `<figure>` and `<figcaption>` from this. + +```texy +[* photo.jpg <> *] *** This is a caption. It can also contain **other formatting**. +``` + + +Organizing Images with References +--------------------------------- + +If you use one image multiple times in the text or want to have all image definitions neatly in one place, you can use references. In the text, you use only a placeholder name, and at the end of the file, you define what this name means. + +```texy +In our logo [* company-logo *] you can see the symbol of our vision. + +​[* company-logo *]: /images/logo.svg 200x50 .(Our company logo) +``` + +This approach significantly clarifies the main text and simplifies image management. + +Syntax ID `image/definition` | [ImageModule |configuration#imagemodule] + + +Preformatted text +================= + +In Texy, you can easily insert blocks of code or any preformatted text where you want to ensure it is displayed exactly as you write it—including all spaces and line breaks. This is ideal for examples of source code, logs, or ASCII art. + +To insert such a block, use the `/--` and `\--` delimiters: + +```texy +/-- +function hello() { + echo 'Hello World'; +} +\-- +``` + +To make your code even more readable, you can tell Texy which programming language it is written in and create a handler that, for example, syntax highlights it, see the "example":custom-handlers#syntax-highlighting. Just add the keyword `code` and the language name after the initial `/--` tag: + +```texy +/--code javascript +console.log('JavaScript'); +\-- + +/--code html +<div>This is HTML code</div> +\-- +``` + + +Content Blocks (divs) +===================== + +Texy allows you to create generic `<div>` blocks, thanks to which you can easily group content into logical units and then style them. + +You create a block using the `/--div` and `\--` tags. In addition, you can easily add [#modifiers]: + +```texy +/--div .[important] +## Important Notice + +This text will be enclosed in a `<div class="important">` block. +This allows you to style it with CSS to make it stand out. +\-- +``` + +The power of `<div>` blocks also lies in the ability to nest them. This allows you to create even more complex structures directly in Texy without having to write HTML manually. + +```texy +/--div .[outer] + This is the outer block. + + /--div .[inner] + And this is a nested, inner block. + \-- + + Here we are back in the outer block. +\-- +``` +Thanks to this simple syntax, you can keep your content clear and semantically well-structured. + + +Disabling Formatting +==================== + +Sometimes it can be useful to "turn off" Texy for a moment and insert a piece of text where Texy should not process its tags. + +If you need to insert a more complex HTML structure without Texy parsing the tags, use the `/--html` block: + +```texy +/--html +<em>This text will be processed as HTML, so it will be in italics.</em> + +**But Texy ignores these asterisks, so they won't be bold.** +\-- +``` + +If you want to display text exactly as it is written, ignoring all tags (both Texy and HTML), use the `/--text` block. Everything inside this block will be displayed as plain text. + +```texy +/--text +<em>This text will be displayed with the tags, but it will not be in italics.</em> + +**This won't be bold either.** +\-- +``` + +But what if you don't want to disable Texy for an entire block of text, but just for a short phrase in the middle of a sentence? For these cases, there is an elegant and quick solution: wrap the text in **double apostrophes** `''`: + +```texy +If you want to show how to write bold text, you would write: The syntax is ''**bold text**''. +``` + +The result will not be bold text; instead, the string `**bold text**` will be printed literally. + + +Tables +====== + +To create a table, start each row with a `|` character and also separate the individual cells with this character. Texy will take care of the alignment and correct HTML on its own. + +```texy +| Jan | Novák | Praha +| Eva | Svobodová | Brno +``` + +The result will be a clear and correctly formatted table. + +Syntax ID `table` | [TableModule |configuration#tablemodule] + + +Table Header +------------ + +Every proper table should have a header that describes what is in each column. You create the header by separating it from the rest of the table with a line containing hyphens `-`. + +```texy +| Name | Age | City +|----------|-----|------- +| Jan | 25 | Prague +| Eva | 30 | Brno +``` + +Alternatively, you can define headers for individual rows (for example, if you have labels in the first column). You achieve this by adding an asterisk `*` immediately after the initial `|`. + +```texy +|* Name | Jan | Eva +|* Age | 25 | 30 +|* City | Praha | Brno +``` + + +Merging Cells +------------- + +Sometimes it is necessary to merge several cells together, either in columns or in rows. + +**Merging columns:** For horizontal cell merging, simply omit the separator and use a double vertical bar `||` instead. The cell to the right will be merged with the cell to its left. + +```texy +| First Name || Age +|---------------------------- +| Jan | Novák | 25 +``` + +**Merging rows:** For vertical cell merging, use the caret symbol `^` in the cell you want to attach to the one above it. This tells Texy: "Merge this cell with the one above it." + +```texy +| Month | Sales | +|---------|---------- +| January | 150 pcs | +| February | ^| +| March | 210 pcs | +``` + +In this example, the cell with sales for January and February will be merged. + +This way, several cells can be merged across rows and columns: + +```texy +| First Name | Last Name | Age +|---------------------------- +| Bill || 50 +| ^| 52 +| Jim | Beam | 70 +``` + + +Styling Tables +-------------- + +As with other elements in Texy, you can also add [#modifiers] to tables and their parts to change their appearance (e.g., CSS classes, styles, or IDs). + +**Entire table:** Place the modifier for the entire table on a separate line just before it. + +```texy +.[data-table table-striped] +| Header 1 | Header 2 +|------------|------------ +| data | data +``` + +**Individual rows:** If you want to style a specific row, add the modifier to its end. + +```texy +| Name | Status +|-------|-------------- +| Petr | Approved +| Jana | Rejected | .{background: #ffdddd} +``` + +**Individual columns:** To style an entire column, insert the modifier at the beginning of the first cell of that column. + +```texy +| Name | .> Price | In Stock +|----------------|-----------|--------- +| Product A | 1 200 Kč | Yes +| Product B | 850 Kč | No +``` + +**Specific cell:** Write the modifier for a single cell directly in it, usually at the end of its content. + +```texy +| Task | Status +|----------------------|------------------------------------- +| Prepare documents | Done +| Check data | In progress .{color: orange; font-weight: bold} +``` + + +Blockquotes +=========== + +If you need to emphasize someone else's idea in your text, cite a source, or just visually separate a block of text, simply start the line with the `>` character. + +```texy +> This is a blockquote. It serves to highlight an important idea or an excerpt from another source. +``` + +A blockquote doesn't have to be just one paragraph. If you want to continue with another paragraph within the same blockquote, simply insert a blank line that also starts with the `>` character. + +```texy +> This is the first paragraph of the blockquote. Lorem ipsum dolor sit amet. +> +> And this is the second paragraph, which still belongs to the same blockquote. +> This way you can structure even longer texts. +``` + +Texy even supports nested blockquotes, which is useful if you are quoting someone who is quoting someone else. For each additional level of nesting, add another `>` character. + +```texy +> This is the outer, main blockquote. +> +> > And this is a nested, second-level blockquote. +> +> Here the text returns to the main blockquote. +``` + +Inside blockquotes, you can of course use other formatting, such as **bold text** or *italics*. + + +Horizontal Rules +================ + +Sometimes it is necessary to visually divide the text. A horizontal rule is great for this. On a separate line, write three or more hyphens `---` or asterisks `***`. + +```texy +The first part of the text on a certain topic. + +*** + +The second part of the text, which begins after the visual separation. +``` + +To create a horizontal rule, it **must be preceded by a blank line**. If you were to write it immediately after the text, Texy would think you wanted to create an underlined heading. + +Syntax ID `horizline` | [HorizLineModule |configuration#horizlinemodule] + + +Typography +========== + +The power of Texy lies not only in formatting, but also in automatic typographic corrections. Texy takes care of the details that make text professional and easy to read, all according to Czech typographic rules. This allows you to focus solely on the content. + +**Quotes:** You don't have to worry about how to type correct typographic quotes on the keyboard. Texy will do it for you. + +It automatically converts classic ''"typewriter quotes"'' into the correct English “quotes” and nested ‘quotes’. The type of quotes depends on the locale setting: + +```php +$texy->typographyModule->locale = 'cs'; // Czech +$texy->typographyModule->locale = 'en'; // English +``` + +**Dashes and hyphens:** It intelligently recognizes when to use a short hyphen (in hyphenated words) and when to use a longer en dash—for example, in ranges (10–15) or between words. + +```texy +10-15 → 10–15 (en dash for ranges) +czech-slovak → czech-slovak (hyphen remains) +word -- word → word – word (en dash between words) +word --- word → word — word (em dash) +``` + +**Non-breaking spaces**: One of the biggest advantages is the automatic insertion of non-breaking spaces where needed. This prevents single-letter words (like `a` or `I`) from being left alone at the end of a line, which is a common typographic error. + +```texy +// You write: +I visited a castle in Prague. + +// Texy ensures that "a" never remains at the end of a line: +I visited a castle in Prague. +``` + +It also takes care of correct spacing in phone numbers or dates to prevent them from breaking. + +```texy ++420 776 552 046 → +420 776 552 046 (all spaces non-breaking) +``` + +**Automatic symbols:** Texy also makes it easier for you to write frequently used symbols. + +| You write | Texy generates | Description +|----- +| `...` | … | Ellipsis +| `(c)` | © | Copyright +| `(r)` | ® | Registered trademark +| `(tm)` | ™ | Trademark +| `10 x 5` | 10 × 5 | Multiplication sign +| `+-` | ± | Plus-minus +| `<-` `->` `<->` | ← → ↔ | Arrows + +Thanks to these automatic adjustments, your text will always look professional without you having to know complex keyboard shortcuts or HTML entities. + + +Hyphenation of Long Words +------------------------- + +You know it—a long word appears in the text, such as "antidisestablishmentarianism," and on a narrow mobile phone screen, it breaks the entire page layout. Fortunately, Texy offers an elegant solution: it can insert invisible "soft hyphens" (`­`) into the word. These hyphens tell the browser where (between syllables) it can safely break the word if it doesn't fit at the end of the line. If the word fits on the line, the hyphens remain hidden and nothing happens. + +```html +antidisestablish­mentarianism +``` + +Thanks to this, your text will always adapt beautifully to any screen width without unwanted horizontal scrolling. + +Because this feature is not suitable for all types of websites, it is disabled by default. You can activate it in the configuration: + +```php +$texy->allowed['longwords'] = true; + +// Set the minimum word length from which to hyphenate (e.g., 20 characters) +$texy->longWordsModule->wordLimit = 20; +``` + +Syntax ID `longwords` | [LongWordsModule |configuration#long-wordsmodule] + + +Emoticons +========= + +Texy can automatically convert classic text smileys into graphical emoticons. Simply type the smiley as you are used to, and Texy will take care of the rest. + +| You write | Texy generates +|----- +| `:-)` | 🙂 +| `:-(` | ☹ +| `;-)` | 😉 +| `:-D` | 😀 +| `:-*` | 😘 + +Depending on the configuration, Texy can convert these shortcuts either to modern Unicode emoji (as in the table above) or to small images (`<img>`). + +To prevent unwanted conversions, for example in technical texts, this feature is disabled by default. If you want to use it, you just need to enable it: + +```php +$texy->allowed['emoticon'] = true; +``` + +More information about available emoticons and configuration options can be found in the [EmoticonModule configuration |configuration#emoticonmodul]. + +Syntax ID `emoticon` | [EmoticonModule |configuration#emoticonmodule] diff --git a/texy/en/try-settings.texy b/texy/en/try-settings.texy new file mode 100644 index 0000000000..0b8706a6a8 --- /dev/null +++ b/texy/en/try-settings.texy @@ -0,0 +1,55 @@ +Demo Configuration +****************** + +This setting is used in [demo | https://fiddle.nette.org/texy/]: + +/--php +$texy = new Texy\Texy; + +$texy->imageModule->root = '/images/'; +$texy->imageModule->linkedRoot = '/images/'; +$texy->headingModule->generateID = true; + +// syntax highlighting +$texy->addHandler('block', 'blockHandler'); +\-- + +The handler uses Prism.js to highlight syntax: + +/--php +function blockHandler( + Texy\HandlerInvocation $invocation, + string $blocktype, + string $content, + string $lang, + Texy\Modifier $modifier +): Texy\HtmlElement +{ + if ($blocktype !== 'block/code') { + // nothing to do + return $invocation->proceed(); + } + + $texy = $invocation->getTexy(); + $elPre = Texy\HtmlElement::el('pre'); + if ($modifier) { + $modifier->decorate($texy, $elPre); + } + $elPre->attrs['class'] = 'language-' . $lang; + $content = $texy->protect(htmlspecialchars($content), $texy::CONTENT_BLOCK); + $elPre->create('code', $content); + return $elPre; +} +\-- + +In addition, when the conversion is complete, some characters are replaced by entities in the text to make them more visible: + +/--php +$html = $texy->process($text); + +$html = str_replace( + ["\xc2\xa0", "\xc2\xad", "\xe2\x80\x93", "\xe2\x80\x94"], + [' ', '­', '–', '—'], + $html +); +\-- diff --git a/texy/files/image.gif b/texy/files/image.gif new file mode 100644 index 0000000000..b99eabeaf8 Binary files /dev/null and b/texy/files/image.gif differ diff --git a/texy/meta.json b/texy/meta.json new file mode 100644 index 0000000000..27991fd5a8 --- /dev/null +++ b/texy/meta.json @@ -0,0 +1,5 @@ +{ + "version": "3.x", + "repo": "dg/texy", + "composer": "texy/texy" +} diff --git a/tokenizer/cs/@home.texy b/tokenizer/cs/@home.texy index bf178a5fd2..1ff068a35d 100644 --- a/tokenizer/cs/@home.texy +++ b/tokenizer/cs/@home.texy @@ -203,3 +203,4 @@ Poslední metoda `reset()` vrací kurzor na začátek, takže můžete opakovat {{leftbar: utils:@left-menu}} +{{sitename: Nette Dokumentace}} diff --git a/tokenizer/en/@home.texy b/tokenizer/en/@home.texy index 19d1a378a3..165db1ca95 100644 --- a/tokenizer/en/@home.texy +++ b/tokenizer/en/@home.texy @@ -203,3 +203,4 @@ And the last method `reset()` resets the cursor, so you can iterate the token st {{leftbar: utils:@left-menu}} +{{sitename: Nette Documentation}} diff --git a/tracy/bg/@home.texy b/tracy/bg/@home.texy index 32bc180cb0..6d688e7681 100644 --- a/tracy/bg/@home.texy +++ b/tracy/bg/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Tracy - задължителен инструмент за отстраняване на грешки за всички разработчици на PHP}} -{{description: Tracy е инструмент, предназначен за улесняване на отстраняването на грешки в кода на PHP. Той е полезен помощник на всички PHP програмисти, който помага за ясно визуализиране и регистриране на грешки, дъмпинг на променливи и много други. Предупреждение: Tracy е пристрастяващ!}} +{{maintitle: Tracy – инструмент за отстраняване на грешки, с който е удоволствие да се греши}} +{description: Tracy е инструмент, предназначен да улесни отстраняването на грешки в PHP код. Той е полезен помощник за всички PHP програмисти, като им помага с визуализацията и логването на грешки, дъмпването на променливи и много повече. Предупреждение: Tracy води до пристрастяване!}} diff --git a/tracy/bg/@left-menu.texy b/tracy/bg/@left-menu.texy index b986d35302..88983f2460 100644 --- a/tracy/bg/@left-menu.texy +++ b/tracy/bg/@left-menu.texy @@ -1,7 +1,7 @@ -- [Започване на работа |Guide] -- [Самосвал |Dumper] -- [Хронометър |Stopwatch] -- [Конфигурация |Configuring] -- [Рецепти |Recipes] +- [Първи стъпки с Tracy |guide] +- [Дъмпване |dumper] +- [Измерване на време |stopwatch] +- [Конфигурация |configuring] +- [Ръководства |recipes] - [Интеграция с IDE |open-files-in-ide] -- [Създаване на разширение |extensions] +- [Създаване на разширения |extensions] diff --git a/tracy/bg/@menu.texy b/tracy/bg/@menu.texy index 678c28c303..fe681f945a 100644 --- a/tracy/bg/@menu.texy +++ b/tracy/bg/@menu.texy @@ -1,3 +1,3 @@ -- [Главная |@home] -- [Документация |Guide] +- [Въведение |@home] +- [Документация |guide] - "GitHub .[link-external]":https://github.com/nette/tracy diff --git a/tracy/bg/@meta.texy b/tracy/bg/@meta.texy new file mode 100644 index 0000000000..c9a4dccb91 --- /dev/null +++ b/tracy/bg/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документация на Tracy}} diff --git a/tracy/bg/configuring.texy b/tracy/bg/configuring.texy index 29ee6eacd8..3a7b956c4a 100644 --- a/tracy/bg/configuring.texy +++ b/tracy/bg/configuring.texy @@ -1,142 +1,141 @@ -Конфигурация на Трейси -********************** +Конфигурация на Tracy +********************* -Следващите примери предполагат, че е дефиниран следният псевдоним на клас: +Всички примери предполагат създаден псевдоним (alias): ```php use Tracy\Debugger; ``` -Регистриране на грешки .[#toc-error-logging] --------------------------------------------- +Логване на грешки +----------------- ```php $logger = Debugger::getLogger(); -// ако възникне грешка, на този имейл се изпраща известие. -$logger->email = 'dev@example.com'; // (string|string[]) по подразбиране е изключен +// имейл, на който се изпращат известия, че е възникнала грешка +$logger->email = 'dev@example.com'; // (string|string[]) по подразбиране не е зададено -// изпращач на електронна поща -$logger->fromEmail = 'me@example.com'; // (string) по подразбиране не е зададен +// изпращач на имейла +$logger->fromEmail = 'me@example.com'; // (string) по подразбиране не е зададено -// процедура за изпращане на имейл -$logger->mailer = /* ... */; // (извиква се) по подразбиране се изпраща mail() +// рутина, осигуряваща изпращането на имейл +$logger->mailer = /* ... */; // (callable) по подразбиране е изпращане чрез функцията mail() -// кое е най-краткото време за изпращане на друга поща? -$logger->emailSnooze = /* ... */; // (string) по подразбиране е "2 дни +// след колко най-кратко време да се изпрати следващият имейл? +$logger->emailSnooze = /* ... */; // (string) по подразбиране е '2 days' -// кои нива на грешки в BlueScreen също се регистрират? -Debugger::$logSeverity = E_WARNING | E_NOTICE; // по подразбиране 0 (няма ниво на грешка) +// за кои нива на грешки се логва и BlueScreen? +Debugger::$logSeverity = E_WARNING | E_NOTICE; // по подразбиране е 0 (никакви нива на грешки) ``` -`dump()` Поведение .[#toc-dump-behavior] ----------------------------------------- +Поведение на `dump()` +--------------------- ```php -// максимална дължина на низ -Debugger::$maxLength = 150; // (int) по подразбиране според Трейси +// максимална дължина на низа +Debugger::$maxLength = 150; // (int) по подразбиране според версията на Tracy -// колко дълбок ще бъде списъкът -Debugger::$maxDepth = 10; // (int) по подразбиране според Tracy +// максимална дълбочина на влагане +Debugger::$maxDepth = 10; // (int) по подразбиране според версията на Tracy -// скриване на тези ключови стойности (от Tracy 2.8) -Debugger::$keysToHide = ['password', /* ... */]; // (string[]) по подразбиране [] +// скриване на стойностите на тези ключове (от Tracy 2.8) +Debugger::$keysToHide = ['password', /* ... */]; // (string[]) по подразбиране е [] -// визуална тема (от версия 2.8 на Tracy) -Debugger::$dumpTheme = 'dark'; // (light|dark) по подразбиране е 'light' +// визуална тема (от Tracy 2.8) +Debugger::$dumpTheme = 'dark'; // (light|dark) по подразбиране е 'light' -// показване на местоположението, където е извикана функцията dump()? -Debugger::$showLocation = /* ... */; // (bool) по подразбиране е Tracy +// показване на мястото, където е извикана функцията dump()? +Debugger::$showLocation = /* ... */; // (bool) по подразбиране според версията на Tracy ``` -Други .[#toc-others] --------------------- +Други +----- ```php -// в режим на разработка ще виждате известия или предупреждения за грешки като BlueScreen -Debugger::$strictMode = /* ... */; // (bool|int) по подразбиране е false, можете да изберете само конкретни нива на грешка (напр. E_USER_DEPRECATED | E_DEPRECATED) +// в режим на разработка показва грешки от тип notice или warning като BlueScreen +Debugger::$strictMode = /* ... */; // (bool|int) по подразбиране е false, възможно е да се изберат само някои нива на грешки (напр. E_USER_DEPRECATED | E_DEPRECATED) -//показва беззвучни (@) съобщения за грешки -Debugger::$scream = /* ... */; // (bool|int) по подразбиране е false, тъй като във версия 2.9 могат да се избират само определени нива на грешки (напр. E_USER_DEPRECATED | E_DEPRECATED) +// показване на заглушени (@) съобщения за грешки? +Debugger::$scream = /* ... */; // (bool|int) по подразбиране е false, от версия 2.9 е възможно да се изберат само някои нива на грешки (напр. E_USER_DEPRECATED | E_DEPRECATED) -// формат на връзката, която ще се отваря в редактора -Debugger::$editor = /* ... */; // (string|null) по подразбиране е 'editor://open/?file=%file&line=%line' +// формат на връзката за отваряне в редактор +Debugger::$editor = /* ... */; // (string|null) по подразбиране е 'editor://open/?file=%file&line=%line' -//път към шаблона на потребителската страница за грешка 500 -Debugger::$errorTemplate = /* ... */; // (string) не е зададен по подразбиране +// път до шаблон със собствена страница за грешка 500 +Debugger::$errorTemplate = /* ... */; // (string) по подразбиране не е зададено -// покажете лентата за проследяване? -Debugger::$showBar = /* ... */; // (bool) по подразбиране е true +// показване на Tracy Bar? +Debugger::$showBar = /* ... */; // (bool) по подразбиране е true Debugger::$editorMapping = [ - // оригинален => нов + // оригинал => нов '/var/www/html' => '/data/web', - /home/web' => '/srv/html', + '/home/web' => '/srv/html', ]; ``` -Рамка на Nette .[#toc-nette-framework] --------------------------------------- +Nette Framework +--------------- -Ако използвате Nette Framework, можете също така да конфигурирате Tracy и да добавяте нови панели в лентата Tracy, като използвате конфигурационния файл. -В конфигурацията можете да зададете параметрите на Tracy и да добавите нови панели в лентата Tracy. Тези параметри се прилагат само след създаването на контейнера DI, така че грешки, възникнали по-рано, не могат да бъдат отразени. +Ако използвате Nette Framework, можете да конфигурирате Tracy и да добавяте нови панели към Tracy Bar също чрез конфигурационния файл. В конфигурацията могат да се задават параметри, както и да се добавят нови панели към Tracy Bar. Тези настройки се прилагат едва след създаването на DI контейнера, така че грешките, възникнали преди това, не могат да ги отразят. -Конфигурация за регистриране на грешки: +Конфигурация на логването на грешки: ```neon tracy: - # ако има грешка, на този имейл се изпраща известие - email: dev@example.com # (string|string[]) по подразбиране е неустановен + # имейл, на който се изпращат известия, че е възникнала грешка + email: dev@example.com # (string|string[]) по подразбиране не е зададено - # изпращач на имейл - fromEmail: robot@example.com # (string) по подразбиране е неустановен + # изпращач на имейла + fromEmail: robot@example.com # (string) по подразбиране не е зададено - # период на таймаут за изпращане на имейли (от Tracy 2.8.8) - emailSnooze: ... # (string) по подразбиране е '2 дни' + # време за отлагане на изпращането на имейли (от Tracy 2.8.8) + emailSnooze: ... # (string) по подразбиране е '2 days' - # използвайте пощенски оператор, дефиниран в конфигурацията? (като се започне с Tracy 2.5) + # използване на Nette mailer за изпращане на имейли? (от Tracy 2.5) netteMailer: ... # (bool) по подразбиране е true - # За кои нива на грешки се регистрира и BlueScreen? - logSeverity: [E_WARNING, E_NOTICE] # по подразбиране [] + # за кои нива на грешки се логва и BlueScreen? + logSeverity: [E_WARNING, E_NOTICE] # по подразбиране е [] ``` -Конфигурация за функцията `dump()`: +Конфигурация на поведението на функцията `dump()`: ```neon tracy: - # максимална дължина на низ - maxLength: 150 # (int) по подразбиране е Tracy + # максимална дължина на низа + maxLength: 150 # (int) по подразбиране според версията на Tracy - # колко дълбок ще бъде списъкът - maxDepth: 10 # (int) е стойността по подразбиране според Tracy + # максимална дълбочина на влагане + maxDepth: 10 # (int) по подразбиране според версията на Tracy - # за скриване на тези клавиши (от версия 2.8 на Tracy) - keysToHide: [password, pass] # (string[]) по подразбиране [] + # скриване на стойностите на тези ключове (от Tracy 2.8) + keysToHide: [password, pass] # (string[]) по подразбиране е [] # визуална тема (от Tracy 2.8) - dumpTheme: dark # (light|dark) по подразбиране е 'light' + dumpTheme: dark # (light|dark) по подразбиране е 'light' - # покажете мястото, където е извикана функцията dump()? - showLocation: ... # (bool) по подразбиране е Tracy + # показване на мястото, където е извикана функцията dump()? + showLocation: ... # (bool) по подразбиране според версията на Tracy ``` -За да инсталирате разширението Tracy: +Инсталиране на разширения за Tracy: ```neon tracy: - # добавя барове към Tracy Bar + # добавя панели към Tracy Bar bar: - Nette\Bridges\DITracy\ContainerPanel - IncludePanel - XDebugHelper('myIdeKey') - MyPanel(@MyService) - # добавяне на панели към BlueScreen + # добавя панели към BlueScreen blueScreen: - DoctrinePanel::renderException ``` @@ -145,19 +144,19 @@ tracy: ```neon tracy: - # в режим на разработка ще виждате известия или предупреждения за грешки като BlueScreen + # в режим на разработка показва грешки от тип notice или warning като BlueScreen strictMode: ... # по подразбиране е true - # извежда беззвучни (@) съобщения за грешки + # показване на заглушени (@) съобщения за грешки? scream: ... # по подразбиране е false - # формат на връзката, която ще се отваря в редактора + # формат на връзката за отваряне в редактор editor: ... # (string) по подразбиране е 'editor://open/?file=%file&line=%line' - # път до шаблона на потребителската страница за грешка 500 - errorTemplate: ... # (string) не е зададено по подразбиране + # път до шаблон със собствена страница за грешка 500 + errorTemplate: ... # (string) по подразбиране не е зададено - # покажете лентата за проследяване? + # показване на Tracy Bar? showBar: ... # (bool) по подразбиране е true editorMapping: @@ -166,4 +165,16 @@ tracy: /home/web: /srv/html ``` -Стойностите на опциите `logSeverity`, `strictMode` и `scream` могат да бъдат записани като масив от нива на грешки (напр. `[E_WARNING, E_NOTICE]`) или като израз, използван в PHP (напр. `E_ALL & ~E_NOTICE`). +Стойностите на опциите `logSeverity`, `strictMode` и `scream` могат да се записват като масив от нива на грешки (напр. `[E_WARNING, E_NOTICE]`) или като израз, използван в езика PHP (напр. `E_ALL & ~E_NOTICE`). + + +DI Сървиси +---------- + +Тези сървиси се добавят към DI контейнера: + +| Име | Тип | Описание +|---------------------------------------------------------- +| `tracy.logger` | [api:Tracy\ILogger] | логър +| `tracy.blueScreen` | [api:Tracy\BlueScreen] | BlueScreen +| `tracy.bar` | [api:Tracy\Bar] | Tracy Bar diff --git a/tracy/bg/dumper.texy b/tracy/bg/dumper.texy index a92eb54611..98fd667756 100644 --- a/tracy/bg/dumper.texy +++ b/tracy/bg/dumper.texy @@ -1,7 +1,7 @@ -Самосвал +Дъмпване ******** -Всеки дебъгер е запознат с функцията `var_dump`, която подробно изброява цялото съдържание на всяка променлива. За съжаление, изходът му не е форматиран в HTML и извежда изхвърлянето на данни в един ред HTML код, без да се споменава ескапирането на контекста. Трябва да заменим `var_dump` с по-удобна функция. Точно това е функцията `dump()`. +Всеки дебъгър е добър приятел с функцията [php:var_dump], която подробно извежда съдържанието на променлива. За съжаление, в HTML среда изходът губи форматирането си и се слива в един ред, да не говорим за санирането на HTML кода. На практика е необходимо `var_dump` да се замени с по-удобна функция. Това е именно `dump()`. ```php $arr = [10, 20.2, true, null, 'hello']; @@ -10,11 +10,11 @@ dump($arr); // или Debugger::dump($arr); ``` -генерира изхода: +генерира изход: [* dump-basic.webp *] -Можете да промените светлата тема по подразбиране в тъмна: +Можете да промените светлата тема по подразбиране на тъмна: ```php Debugger::$dumpTheme = 'dark'; @@ -22,27 +22,27 @@ Debugger::$dumpTheme = 'dark'; [* dump-dark.webp *] -Можете също така да промените дълбочината на вмъкване на `Debugger::$maxDepth` и дължината на показваните редове на `Debugger::$maxLength`. Естествено, по-малките стойности ускоряват изобразяването на Трейси. +Освен това можем да променим дълбочината на влагане с помощта на [Debugger::$maxDepth |api:Tracy\Debugger::$maxDepth] и дължината на показваните описания с помощта на [Debugger::$maxLength |api:Tracy\Debugger::$maxLength]. По-ниските стойности естествено ще ускорят Tracy. ```php Debugger::$maxDepth = 2; // по подразбиране: 3 Debugger::$maxLength = 50; // по подразбиране: 150 ``` -Функцията `dump()` може да показва и друга полезна информация. `Tracy\Dumper::LOCATION_SOURCE` добавя подсказка с пътя до файла, в който е извикана функцията. `Tracy\Dumper::LOCATION_LINK` добавя връзка към файла. `Tracy\Dumper::LOCATION_CLASS` добавя подсказка към всеки обект за изхвърляне, съдържаща пътя до файла, в който е дефиниран класът на обекта. Всички тези константи могат да бъдат зададени в променливата `Debugger::$showLocation`, преди да се извика функцията `dump()`. Можете да зададете няколко стойности едновременно, като използвате оператора `|`. +Функцията `dump()` може да изведе и друга полезна информация. Константата `Tracy\Dumper::LOCATION_SOURCE` добавя подсказка (tooltip) с пътя до мястото, където е извикана функцията. `Tracy\Dumper::LOCATION_LINK` ни предоставя връзка към това място. `Tracy\Dumper::LOCATION_CLASS` при всеки дъмпнат обект извежда подсказка с пътя до файла, в който е дефиниран неговият клас. Константите се задават в променливата `Debugger::$showLocation` преди извикването на `dump()`. Ако искаме да зададем няколко стойности едновременно, ги свързваме с оператора `|`. ```php -Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // Показва пътя до мястото, където е извикан dump() -Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Показва както пътя до класовете, така и връзката към мястото, където е извикан dump() -Debugger::$showLocation = false; // Скрива допълнителна информация за местоположението -Debugger::$showLocation = true; // Показва цялата допълнителна информация за местоположението +Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // Задава само извеждане на мястото на извикване на функцията +Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Задава едновременно извеждане на връзка и път до класа +Debugger::$showLocation = false; // Изключва извеждането на допълнителна информация +Debugger::$showLocation = true; // Включва извеждането на цялата допълнителна информация ``` -Много удобна алтернатива на `dump()` са `dumpe()` (т.е. dump and exit) и `bdump()`. Това ни позволява да въвеждаме променливи в лентата Tracy Bar. Това е полезно, тъй като дамп-ите не развалят изхода, а и можем да добавим заглавие към дамп-а. +Практична алтернатива на `dump()` са `dumpe()` (dump & exit) и `bdump()`. Последният ни позволява да изведем стойността на променлива в панела на Tracy Bar. Това е много удобно, тъй като дъмповете са отделени от лейаута на страницата и можем да добавим коментар към тях. ```php -bdump([2, 4, 6, 8], 'even numbers up to ten'); -bdump([1, 3, 5, 7, 9], 'odd numbers up to ten'); +bdump([2, 4, 6, 8], 'четни числа до десет'); +bdump([1, 3, 5, 7, 9], 'нечетни числа до десет'); ``` -[* bardump-en.webp *] +[* bardump-cs.webp *] diff --git a/tracy/bg/extensions.texy b/tracy/bg/extensions.texy index 31b25edbb5..44ab8549c9 100644 --- a/tracy/bg/extensions.texy +++ b/tracy/bg/extensions.texy @@ -1,23 +1,23 @@ -Създаване на разширения на Трейси -********************************* +Създаване на разширения за Tracy +******************************** <div class=perex> -Tracy е чудесен инструмент за отстраняване на грешки в приложението. Понякога обаче се нуждаете от повече информация, отколкото предлага Трейси. Ще научите за: +Tracy предоставя чудесен инструмент за дебъгване на вашето приложение. Понякога обаче бихте искали да имате под ръка и друга информация. Ще ви покажем как да напишете собствено разширение за Tracy Bar, за да направите разработката още по-приятна. -- Създаване на собствени панели Tracy Bar -- Създаване на персонализирани разширения на Bluescreen +- Създаване на собствен панел за Tracy Bar +- Създаване на собствено разширение за Bluescreen </div> .[tip] -Можете да намерите полезни разширения за Tracy в "Componette":https://componette.org/search/tracy. +Хранилище с готови разширения за Tracy можете да намерите на "Componette":https://componette.org/search/tracy. -Удължения за лоста Трейси .[#toc-tracy-bar-extensions] -====================================================== +Разширения за Tracy Bar +======================= -Създаването на ново разширение за Tracy Bar е много лесно. Трябва да имплементирате интерфейс `Tracy\IBarPanel` с методи `getTab()` и `getPanel()`. Тези методи трябва да връщат HTML кода на таб (малък пряк път в лентата Tracy Bar) и панел (изскачащ прозорец, който се показва след щракване върху таб). Ако `getPanel()` не върне нищо, ще бъде показан само табът. Ако `getTab()` не върне нищо, не се показва нищо и `getPanel()` няма да бъде извикан. +Създаването на ново разширение за Tracy Bar не е никак сложно. Създавате обект, имплементиращ интерфейса `Tracy\IBarPanel`, който има два метода `getTab()` и `getPanel()`. Методите трябва да върнат HTML кода на таба (малко описание, показано директно в Bar-а) и панела. Ако `getPanel()` не върне нищо, ще се покаже само описанието. Ако `getTab()` не върне нищо, няма да се покаже нищо и `getPanel()` дори няма да бъде извикан. ```php class ExamplePanel implements Tracy\IBarPanel @@ -35,16 +35,16 @@ class ExamplePanel implements Tracy\IBarPanel ``` -Регистрация .[#toc-registration] --------------------------------- +Регистрация +----------- -Регистрацията е по телефона `Tracy\Bar::addPanel()`: +Регистрацията се извършва с помощта на `Tracy\Bar::addPanel()`: ```php Tracy\Debugger::getBar()->addPanel(new ExamplePanel); ``` -или можете просто да регистрирате панела си в конфигурацията на приложението: +Или можете да регистрирате панела директно в конфигурацията на приложението: ```neon tracy: @@ -53,70 +53,70 @@ tracy: ``` -HTML код на раздела .[#toc-tab-html-code] ------------------------------------------ +HTML код на таба +---------------- -Тя трябва да изглежда по следния начин: +Трябва да изглежда приблизително така: ```latte -<span title="Explaining tooltip"> +<span title="Обяснително описание"> <svg>...</svg> - <span class="tracy-label">Title</span> + <span class="tracy-label">Заглавие</span> </span> ``` -Изображението трябва да е във формат SVG. Ако не се нуждаете от подсказка, можете да оставите `<span>` изключва. +Изображението трябва да бъде във формат SVG. Ако обяснителното описание не е необходимо, `<span>` може да бъде пропуснато. -HTML код на панела .[#toc-panel-html-code] ------------------------------------------- +HTML код на панела +------------------ -Тя трябва да изглежда по следния начин: +Трябва да изглежда приблизително така: ```latte -<h1>Title</h1> +<h1>Заглавие</h1> <div class="tracy-inner"> <div class="tracy-inner-container"> - ... content ... + ... съдържание ... </div> </div> ``` -Заглавието трябва да е същото като раздела или да съдържа допълнителна информация. +Заглавието трябва да бъде същото като заглавието на таба или може да съдържа допълнителни данни. -Едно разширение може да бъде регистрирано повече от веднъж, затова се препоръчва да не използвате атрибута `id` за стилизиране. Можете да използвате класове, за предпочитане в `tracy-addons-<class-name>[-<optional>]` формат. Когато създавате CSS, е по-добре да използвате `#tracy-debug .class`, тъй като такова правило има по-висок приоритет от нулирането. +Трябва да се има предвид, че едно разширение може да бъде регистрирано няколко пъти, например с различни настройки, така че за стилизиране не може да се използва CSS id, а само class, и то във формата `tracy-addons-<ИмеНаКлас>[-<незадължително>]`. След това запишете класа в div-а заедно с класа `tracy-inner`. При писане на CSS е полезно да се пише `#tracy-debug .клас`, тъй като правилото тогава има по-висок приоритет от reset. -Стилове по подразбиране .[#toc-default-styles] ----------------------------------------------- +Стилове по подразбиране +----------------------- -В панела Елементи `<a>`, `<table>`, `<pre>`, `<code>` имат стилове по подразбиране. За да създадете връзка за скриване или показване на друг елемент, свържете ги с атрибутите `href` и `id` и класа `tracy-toggle`. +В панела са предварително стилизирани `<a>`, `<table>`, `<pre>`, `<code>`. Ако искате да създадете връзка, която скрива и показва друг елемент, свържете ги с атрибутите `href` и `id` и класа `tracy-toggle`: ```latte -<a href="#tracy-addons-className-{$counter}" class="tracy-toggle">Detail</a> +<a href="#tracy-addons-NazevTridy-{$counter}" class="tracy-toggle">Детайли</a> -<div id="tracy-addons-className-{$counter}">...</div> +<div id="tracy-addons-NazevTridy-{$counter}">...</div> ``` -Ако състоянието по подразбиране е сгънато, добавете класа `tracy-collapsed` и към двата елемента. +Ако състоянието по подразбиране трябва да бъде свито, добавете клас `tracy-collapsed` към двата елемента. -Използвайте статичен брояч, за да предотвратите дублирането на идентификатори на една и съща страница. +Използвайте статичен брояч (counter), за да не се създават дублиращи се ID на една страница. -Разширения за син екран .[#toc-bluescreen-extensions] -===================================================== +Разширения за Bluescreen +======================== -Можете да добавяте свои собствени визуализации на изключенията или панели, които да се показват в проекта. +По този начин могат да се добавят собствени визуализации на изключения или панели, които ще се показват на bluescreen. -Разширението се извършва по следния начин: +Разширението се създава с тази инструкция: ```php -Tracy\Debugger::getBlueScreen()->addPanel(function (?Throwable $e) { // уловено изключение +Tracy\Debugger::getBlueScreen()->addPanel(function (?Throwable $e) { // прихванато изключение return [ - 'tab' => '...Title...', - 'panel' => '...content...', + 'tab' => '...Етикет...', + 'panel' => '...HTML код на панела...', ]; }); ``` -Функцията се извиква два пъти, като първо се предава самото изключение в параметъра `$e`, а върнатият панел се показва в началото на страницата. Ако не бъде върнато нищо, панелът не се визуализира. След това се извиква с параметъра `null`, а върнатият панел се визуализира под стека на повикванията. Ако функцията върне `'bottom' => true` в масива, панелът се визуализира най-отдолу. +Функцията се извиква два пъти: първо, в параметъра `$e` се предава самото изключение и върнатият панел се изобразява в началото на страницата. Ако не върне нищо, панелът не се изобразява. След това се извиква с параметър `null` и върнатият панел се изобразява под стека на извикванията (callstack). Ако функцията връща ключ `'bottom' => true` в масива, панелът се изобразява най-отдолу. diff --git a/tracy/bg/guide.texy b/tracy/bg/guide.texy index 4a74e8c5b3..6a788462be 100644 --- a/tracy/bg/guide.texy +++ b/tracy/bg/guide.texy @@ -1,213 +1,212 @@ -Започване с Трейси -****************** +Първи стъпки с Tracy +******************** <div class=perex> -Библиотеката Tracy е полезен помощник за обикновените PHP програмисти. Тя ще ви помогне: +Библиотеката Tracy е полезен ежедневен помощник на PHP програмиста. Ще ви помогне да: -- бързо намиране и отстраняване на грешки -- грешки в дневника -- променливи за изхвърляне -- измерване на времето за изпълнение на скрипт/заявка -- преглед на използването на паметта +- бързо откривате и коригирате грешки +- логвате грешки +- извеждате променливи +- измервате времето на скриптове и заявки към базата данни +- следите изискванията за памет </div> -PHP е идеален език за генериране на фини грешки, тъй като предоставя на програмистите голяма гъвкавост. Поради това Tracy\Debugger е по-ценен. Това е най-добрият инструмент сред диагностичните. +PHP е език, създаден за допускане на трудно откриваеми грешки, тъй като дава на разработчиците значителна свобода. Толкова по-ценен е инструментът за дебъгване Tracy. Сред диагностичните инструменти за PHP той представлява абсолютния връх. -Ако срещнете Трейси за първи път, повярвайте ми, животът ви ще се раздели на този преди Трейси и този с нея. Добре дошли в добрата част! +Ако днес се срещате с Tracy за първи път, повярвайте, че животът ви ще започне да се дели на този преди Tracy и този с нея. Добре дошли в по-добрата част! -Монтаж и изисквания .[#toc-installation-and-requirements] -========================================================= +Инсталация +========== -Най-добрият начин за инсталиране на Tracy е да [изтеглите най-новия пакет |https://github.com/nette/tracy/releases] или да използвате Composer: +Най-добрият начин да инсталирате Tracy е да [изтеглите най-новия пакет |https://github.com/nette/tracy/releases] или да използвате Composer: ```shell composer require tracy/tracy ``` -Можете също така да изтеглите целия пакет или файла [tracy.phar |https://github.com/nette/tracy/releases]. +Можете също да изтеглите целия пакет като файл [tracy.phar |https://github.com/nette/tracy/releases]. -Използване на .[#toc-usage] -=========================== +Използване +========== -Tracy се активира чрез извикване на метода `Tracy\Debugger::enable()' възможно най-скоро в началото на програмата, преди да се изпрати какъвто и да е изход: +Активираме Tracy чрез извикване на метода `Tracy\Debugger::enable()` възможно най-рано в началото на програмата, преди изпращането на какъвто и да е изход: ```php use Tracy\Debugger; -require 'vendor/autoload.php'; // алтернативно tracy.phar +require 'vendor/autoload.php'; // или tracy.phar Debugger::enable(); ``` -Първото нещо, което ще забележите на страницата, е лентата на Tracy в долния десен ъгъл. Ако не я виждате, това може да означава, че Tracy работи в производствен режим. -Това е така, защото Tracy е видим само на localhost от съображения за сигурност. За да проверите дали работи, можете временно да го поставите в режим на разработка, като използвате параметъра `Debugger::enable(Debugger::Development)`. +Първото нещо, което ще забележите на страницата, е Tracy Bar в долния десен ъгъл. Ако не го виждате, това може да означава, че Tracy работи в продукционен режим. От съображения за сигурност Tracy е видима само на localhost. За да тествате дали работи, можете временно да я превключите в режим на разработка с помощта на параметъра `Debugger::enable(Debugger::Development)`. -Бар Трейси .[#toc-tracy-bar] -============================ +Tracy Bar +========= -Бар "Трейси" е плаващ бар. Той се показва в долния десен ъгъл на страницата. Можете да го преместите с мишката. Той запаметява позицията си след презареждане на страницата. +Tracy Bar е плаващ панел, който се показва в долния десен ъгъл на страницата. Можем да го преместваме с мишката и след презареждане на страницата той ще запомни позицията си. [* tracy-bar.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html -Можете да добавите други полезни панели към лентата Tracy Bar. Можете да намерите интересни [добавки |https://componette.org] или да [създадете свои собствени |extensions]. +Към Tracy Bar могат да се добавят други полезни панели. Много от тях можете да намерите в [добавките |https://componette.org] или дори [можете да напишете свои собствени |extensions]. -Ако не искате да показвате лентата на Tracy, инсталирайте я: +Ако не искате да показвате Tracy Bar, задайте: ```php Debugger::$showBar = false; ``` -Визуализация на грешки и изключения .[#toc-visualization-of-errors-and-exceptions] -================================================================================== +Визуализация на грешки и изключения +=================================== -Вероятно знаете как PHP съобщава за грешки: в изходния код на страницата има нещо подобно: +Със сигурност знаете добре как PHP съобщава за грешки: в изходния код на страницата извежда нещо такова: /--pre .{font-size: 90%} <b>Parse error</b>: syntax error, unexpected '}' in <b>HomePresenter.php</b> on line <b>15</b> \-- -или неполучено изключение: +или при неприхванато изключение: /--pre .{font-size: 90%} <b>Fatal error</b>: Uncaught Nette\MemberAccessException: Call to undefined method Nette\Application\UI\Form::addTest()? in /sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php:100 Stack trace: #0 /sandbox/vendor/nette/utils/src/Utils/Object.php(75): Nette\Utils\ObjectMixin::call(Object(Nette\Application\UI\Form), 'addTest', Array) -#1 /sandbox/app/forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) -#2 /sandbox/app/presenters/SignPresenter.php(21): App\Forms\SignFormFactory->create() -#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presenters\SignPresenter->createComponentSignInForm('signInForm') +#1 /sandbox/app/Forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) +#2 /sandbox/app/Presentation/Sign/SignPresenter.php(21): App\Forms\SignFormFactory->create() +#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presentation\Sign\SignPresenter->createComponentSignInForm('signInForm') #4 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(139): Nette\ComponentModel\Container->createComponent('signInForm') #5 /sandbox/temp/cache/latte/15206b353f351f6bfca2c36cc.php(17): Nette\ComponentModel\Co in <b>/sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php</b> on line <b>100</b><br /> \-- -Не е лесно да се ориентирате в този изход. Ако активирате функцията Tracy, грешките и изключенията се показват по съвсем различен начин: +Ориентирането в такъв изход не е никак лесно. Ако включим Tracy, грешката или изключението ще се покажат в съвсем различна форма: [* tracy-exception.webp .{url:-} *] -Съобщението за грешка буквално крещи. Можете да видите част от изходния код с подчертания ред, на който е възникнала грешката. В съобщението ясно се обяснява грешката. Целият сайт [е интерактивен, опитайте го |https://nette.github.io/tracy/tracy-exception.html]. +Съобщението за грешка буквално крещи. Виждаме част от изходния код с подчертан ред, където е възникнала грешката, а информацията *Call to undefined method Nette\Http\User::isLogedIn()* ясно обяснява каква е грешката. Освен това цялата страница е интерактивна, можем да кликваме за повече подробности. [Опитайте |https://nette.github.io/tracy/tracy-exception.html]. -И познайте какво? Фаталните грешки се улавят и показват по същия начин. Не е необходимо да се инсталират разширения (щракнете за пример на живо): +И знаете ли какво? По този начин се прихващат и показват дори фатални грешки. Без необходимост от инсталиране на каквито и да било разширения. [* tracy-error.webp .{url:-} *] -Грешки, като например печатна грешка в името на променлива или опит за отваряне на несъществуващ файл, генерират отчети от ниво E_NOTICE или E_WARNING. Те могат лесно да бъдат пропуснати и/или да бъдат напълно скрити в графичното оформление на уеб страницата. Позволете на Трейси да ги управлява: +Грешки като печатна грешка в името на променлива или опит за отваряне на несъществуващ файл генерират съобщения от ниво E_NOTICE или E_WARNING. Те лесно могат да бъдат пропуснати в графиката на страницата, дори може изобщо да не са видими (освен ако не погледнете кода на страницата). [* tracy-notice2.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html -Или могат да се показват като грешки: +Или могат да бъдат показани по същия начин като грешките: ```php -Debugger::$strictMode = true; // показване на всички грешки -Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // всички грешки, с изключение на известията за изчерпване +Debugger::$strictMode = true; // покажи всички грешки +Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // всички грешки освен известията за отхвърляне (deprecated) ``` [* tracy-notice.webp .{url:-} *] -Забележка: Когато е активирана функцията Tracy, нивото на отчитане на грешки се променя на E_ALL. Ако искате да промените това, направете го, след като извикате `enable()`. +Забележка: След активиране Tracy променя нивото на докладване на грешки на E_ALL. Ако искате да промените тази стойност, направете го след извикването на `enable()`. -Разработване срещу производствен режим .[#toc-development-vs-production-mode] -============================================================================= +Режим на разработка срещу продукционен режим +============================================ -Както можете да видите, Трейси е доста разговорлив, което може да се оцени в среда за разработка, докато на производствен сървър това би довело до катастрофа. Това е така, защото там не трябва да се показва информация за отстраняване на грешки. Поради това Tracy има **автоматично откриване на средата** и ако примерът се изпълнява на реален сървър, грешката ще се регистрира, вместо да се показва, а посетителят ще вижда само удобно за потребителя съобщение: +Както виждате, Tracy е доста „приказлива“, което може да се оцени в среда за разработка, докато на продукционен сървър това би причинило истинско бедствие. Там не трябва да се извежда никаква информация за дебъгване. Затова Tracy разполага с **автоматично откриване на средата** и ако стартираме примера на реален сървър, грешката ще бъде логната вместо показана, а посетителят ще види само разбираемо за потребителя съобщение: [* tracy-error2.webp .{url:-} *] -Производственият режим потиска показването на цялата информация за отстраняване на грешки, изпратена с помощта на [dump( |dumper]), и разбира се, на всички съобщения за грешки, генерирани от PHP. Така че, ако сте забравили някои `dump($obj)` в кода, не трябва да се притеснявате, нищо няма да бъде показано на производствения сървър. +Продукционният режим потиска показването на цялата информация за дебъгване, която изпращаме навън с помощта на [dump() |dumper], и разбира се, всички съобщения за грешки, генерирани от PHP. Така че, ако сте забравили някое `dump($obj)` в кода, не се притеснявайте, нищо няма да се изведе на продукционния сървър. -Как работи автоматичното откриване на режими? Режимът е за разработка, ако приложението работи на localhost (т.е. IP адрес `127.0.0.1` или `::1`) и няма прокси (т.е. HTTP заглавието му). В противен случай то се изпълнява в производствен режим. +Как работи автоматичното откриване на режима? Режимът е за разработка, ако приложението се изпълнява на localhost (т.е. IP адрес `127.0.0.1` или `::1`) и няма налично прокси (т.е. негов HTTP хедър). В противен случай работи в продукционен режим. -Ако искате да активирате режим на разработка в други случаи, например за разработчици, които осъществяват достъп от определен IP адрес, можете да го зададете като параметър на метода `enable()`: +Ако искаме да разрешим режима за разработка и в други случаи, например за програмисти, достъпващи от конкретен IP адрес, го посочваме като параметър на метода `enable()`: ```php -Debugger::enable('23.75.345.200'); // можете също така да предоставите масив от IP адреси +Debugger::enable('23.75.345.200'); // може да се посочи и масив от IP адреси ``` -Определено препоръчваме да комбинирате IP адреса с бисквитка. Съхранявайте таен токен, например `secret1234`, в бисквитката `tracy-debug` и по този начин активирайте режима за разработка само за разработчици, осъществяващи достъп от определен IP адрес, които имат споменатия токен в бисквитката: +Определено препоръчваме да комбинирате IP адреса с бисквитка (cookie). В бисквитката `tracy-debug` съхраняваме таен токен, напр. `secret1234`, и по този начин активираме режима за разработка само за програмисти, достъпващи от конкретен IP адрес, които имат споменатия токен в бисквитката: ```php Debugger::enable('secret1234@23.75.345.200'); ``` -Можете също така директно да зададете режима на разработка/производство, като използвате константите `Debugger::Development` или `Debugger::Production` като параметър на метода `enable()`. +Можем също така директно да зададем режима за разработка/продукционен режим, като използваме константата `Debugger::Development` или `Debugger::Production` като параметър на метода `enable()`. .[note] -Ако използвате Nette Framework, погледнете как да [зададете режима за него |application:bootstrap#Development vs Production Mode] и след това той ще се използва и за Tracy. +Ако използвате Nette Framework, вижте как да [зададете режима за него |application:bootstrapping#Режим за разработка срещу продукционен режим] и той впоследствие ще се използва и за Tracy. -Регистриране на грешки .[#toc-error-logging] -============================================ +Логване на грешки +================= -В производствен режим Tracy автоматично записва всички грешки и изключения в текстов дневник. За да се извършва записването, трябва да зададете абсолютния път до директорията за запис в променливата `$logDirectory` или да я предадете като втори параметър на метода `enable()`: +В продукционен режим Tracy автоматично записва всички грешки и прихванати изключения в текстов лог. За да може логването да се осъществява, трябва да зададем абсолютния път до директорията за логване в променливата `$logDirectory` или да го предадем като втори параметър на метода `enable()`: ```php Debugger::$logDirectory = __DIR__ . '/log'; ``` -Регистрирането на грешки е изключително полезно. Представете си, че всички потребители на вашето приложение всъщност са бета тестери, които безплатно вършат първокласна работа по откриването на грешки, и би било глупаво да изхвърлите техните ценни доклади незабелязано в кошчето за боклук. +Логването на грешки е изключително полезно. Представете си, че всички потребители на вашето приложение всъщност са бета тестери, които безплатно вършат отлична работа в намирането на грешки и бихте направили глупост, ако изхвърлите техните ценни доклади в кошчето за боклук, без да им обърнете внимание. -Ако трябва да регистрирате собствени съобщения или прихванати изключения, използвайте метода `log()`: +Ако трябва да логнем собствено съобщение или прихванато от нас изключение, използваме за това метода `log()`: ```php -Debugger::log('Unexpected error'); // текстово съобщение +Debugger::log('Възникна неочаквана грешка'); // текстово съобщение try { - criticalOperation(); + kritickaOperace(); } catch (Exception $e) { - Debugger::log($e); // запис на изключението + Debugger::log($e); // може да се логва и изключение // или - Debugger::log($e, Debugger::ERROR); // също така изпраща уведомление по имейл + Debugger::log($e, Debugger::ERROR); // изпраща и известие по имейл } ``` -If you want Tracy to log PHP errors like `E_NOTICE` or `E_WARNING` with detailed information (HTML report), set `Debugger::$logSeverity`: +Ако искате Tracy да логва PHP грешки като `E_NOTICE` или `E_WARNING` с подробна информация (HTML отчет), задайте `Debugger::$logSeverity`: ```php Debugger::$logSeverity = E_NOTICE | E_WARNING; ``` -За един професионалист регистрирането на грешки е най-важният източник на информация и той иска да бъде уведомяван за всяка нова грешка незабавно. Трейси му помага. Тя може да изпраща имейл за всеки нов доклад за грешка. Променливата $email определя къде да се изпращат тези имейли: +За истинския професионалист логът на грешките е ключов източник на информация и той иска да бъде незабавно информиран за всяка нова грешка. Tracy му помага в това, тъй като може да информира за нов запис в лога по имейл. Къде да се изпращат имейлите, определяме с променливата $email: ```php Debugger::$email = 'admin@example.com'; ``` -Ако използвате цялата Nette Framework, можете да зададете тази и други променливи в [конфигурационния файл |nette:configuring]. +Ако използвате целия Nette Framework, това и други настройки могат да бъдат зададени в [конфигурационния файл |nette:configuring]. -За да предпази пощенската ви кутия от препълване, Трейси изпраща **само едно съобщение** и създава файл `email-sent`. Когато разработчикът получи известие по имейл, той проверява дневника, поправя приложението си и изтрива файла за наблюдение `email-sent`. Това активира отново изпращането на имейла. +За да не препълни обаче имейл кутията ви, тя винаги изпраща **само едно съобщение** и създава файл `email-sent`. След получаване на известието по имейл, разработчикът проверява лога, коригира приложението и изтрива файла за наблюдение, с което отново се активира изпращането на имейли. -Отваряне на файлове в редактора .[#toc-opening-files-in-the-editor] -=================================================================== +Отваряне в редактор +=================== -Когато се покаже страницата с грешките, можете да щракнете върху имената на файловете и те ще се отворят в редактора с курсор на съответния ред. Можете също така да създавате файлове (действие `create file`) или да поправяте грешки в тях (действие `fix it`). За целта трябва да [конфигурирате браузъра и системата си |open-files-in-ide]. +При показване на страницата с грешка можете да кликнете върху имената на файловете и те ще се отворят във вашия редактор с курсора на съответния ред. Също така можете да създавате файлове (действие `create file`) или да коригирате грешки в тях (действие `fix it`). За да работи това, е достатъчно да [конфигурирате браузъра и системата |open-files-in-ide]. -Поддържани версии на PHP .[#toc-supported-php-versions] -======================================================= +Поддържани версии на PHP +======================== -| Tracy | съвместим с PHP -|-----------|-------------------- -| Tracy 2.10 – 3.0 | PHP 8.0 - 8.2 -| Tracy 2.9 | PHP 7.2 - 8.2 -| Tracy 2.8 | PHP 7.2 - 8.1 -| Tracy 2.6 - 2.7 | PHP 7.1 - 8.0 -| Tracy 2.5 | PHP 5.4 - 7.4 -| Tracy 2.4 | PHP 5.4 - 7.2 +| Tracy | съвместим с PHP +|-----------|------------------- +| Tracy 2.10 – 3.0 | PHP 8.0 – 8.4 +| Tracy 2.9 | PHP 7.2 – 8.2 +| Tracy 2.8 | PHP 7.2 – 8.1 +| Tracy 2.6 – 2.7 | PHP 7.1 – 8.0 +| Tracy 2.5 | PHP 5.4 – 7.4 +| Tracy 2.4 | PHP 5.4 – 7.2 -Отнася се за най-новите версии на кръпките. +Важи за последната пач версия. -Портове .[#toc-ports] -===================== +Портове +======= -Това е списък с неофициални преноси към други рамки и CMS-и: +Това е списък с неофициални портове за други framework-ове и CMS: - [Drupal 7](https://www.drupal.org/project/traced) - Laravel framework: [recca0120/laravel-tracy](https://github.com/recca0120/laravel-tracy), [whipsterCZ/laravel-tracy](https://github.com/whipsterCZ/laravel-tracy) diff --git a/tracy/bg/open-files-in-ide.texy b/tracy/bg/open-files-in-ide.texy index 2dc107b45e..974457a01f 100644 --- a/tracy/bg/open-files-in-ide.texy +++ b/tracy/bg/open-files-in-ide.texy @@ -1,20 +1,20 @@ -Как да отворите файл в редактор от Tracy? (Интеграция с IDE) -************************************************************ +Как да отворите файл в редактора от Tracy? (Интеграция с IDE) +************************************************************* .[perex] -Когато се покаже страницата с грешките, можете да щракнете върху имената на файловете и те ще се отворят в редактора с курсор на съответния ред. Файловете могат също да бъдат създавани (действие `create file`) или коригирани (действие `fix it`). За целта трябва да конфигурирате браузъра и системата си. +При показване на страницата с грешка можете да кликнете върху имената на файловете и те ще се отворят във вашия редактор с курсора на съответния ред. Също така можете да създавате файлове (действие `create file`) или да коригирате грешки в тях (действие `fix it`). За да се случи това, е необходимо да конфигурирате браузъра и системата. -Tracy отваря файлове чрез URL като `editor://open/?file=%file&line=%line`, т.е. използвайки протокола `editor://`. За тази цел ще регистрираме свой собствен манипулатор. Това може да бъде всеки изпълним файл, който ще обработи параметрите и ще стартира любимия ни редактор. +Tracy отваря файлове чрез URL във формата `editor://open/?file=%file&line=%line`, т.е. с протокол `editor://`. За него ще регистрираме собствен handler. Това може да бъде произволен изпълним файл, който ще "сдъвче" параметрите и ще стартира любимия ни редактор. -Можете да промените URL адреса в променливата `Tracy\Debugger::$editor` или да деактивирате критерия за щракване, като зададете `Tracy\Debugger::$editor = null`. +Можете да промените URL адреса в променливата `Tracy\Debugger::$editor` или да изключите кликването, като зададете `Tracy\Debugger::$editor = null`. -Windows .[#toc-windows] -======================= +Windows +======= -1. Изтеглете съответните файлове "от хранилището Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/windows на устройството. +1. Изтеглете съответните файлове от "хранилището на Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/windows на диска. -2. редактирайте `open-editor.js` и коментирайте или редактирайте пътя до вашия редактор в `settings`: +2. Редактирайте файла `open-editor.js` и в масива `settings` разкоментирайте и евентуално променете пътя до вашия редактор: ```js var settings = { @@ -35,19 +35,28 @@ var settings = { ... ``` -Бъдете внимателни и запазете двойните наклонени черти в пътеките. +Внимание, запазете двойните наклонени черти в пътищата. -3. Регистриране на обслужващото устройство за протокола `editor://` в системата. +3. Регистрирайте handler за протокола `editor://` в системата. -Това става, като се стартира скриптът `install.cmd`. ** Трябва да го стартирате като администратор.** Скриптът `open-editor.js` сега ще обслужва протокола `editor://`. +Това става чрез стартиране на файла `install.cmd`. **Трябва да го стартирате като Администратор.** Скриптът `open-editor.js` вече ще обслужва протокола `editor://`. +За да можете да отваряте връзки, генерирани на други сървъри, като например на реален сървър или в Docker, добавете в `open-editor.js` и съпоставяне на отдалечен URL към локален: -Linux .[#toc-linux] -=================== +```js + mappings: { + // отдалечен път: локален път + '/var/www/nette.app': 'W:\\Nette.web\\_web', + } +``` + + +Linux +===== -1. Изтеглете съответните файлове "от хранилището на Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux на адрес `~/bin`. +1. Изтеглете съответните файлове от "хранилището на Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux в директорията `~/bin`. -2. редактирайте `open-editor.sh` и коментирайте или редактирайте пътя на редактора си в променливата `editor`: +2. Редактирайте файла `open-editor.sh` и разкоментирайте и евентуално променете пътя до вашия редактор в променливата `editor`. ```shell #!/bin/bash @@ -67,24 +76,25 @@ Linux .[#toc-linux] ... ``` -Направете го изпълним: +Направете файла изпълним: ```shell chmod +x ~/bin/open-editor.sh ``` -Ако редакторът, който използвате, не е инсталиран от пакета, в двоичния файл най-вероятно няма да има път до `$PATH`. Това може лесно да се поправи. В директорията `~/bin` създайте симлинк към двоичния файл на редактора. .[note] +.[note] +Ако използваният редактор не е инсталиран от пакет, вероятно изпълнимият файл няма да има път в $PATH. Това може лесно да се поправи. В директорията `~/bin` създайте символна връзка (symlink) към изпълнимия файл на редактора. -3. Регистриране на обработващ за протокола `editor://` в системата. +3. Регистрирайте handler за протокола `editor://` в системата. -Това става, като се стартира скриптът `install.sh`. Скриптът `open-editor.js` вече ще обслужва протокола `editor://`. +Това става чрез стартиране на файла `install.sh`. Скриптът `open-editor.sh` вече ще обслужва протокола `editor://`. -macOS .[#toc-macos] -=================== +macOS +===== -Редактори като PhpStorm, TextMate и др. позволяват отваряне на файлове чрез специален URL адрес, който просто трябва да се посочи: +Редактори като PhpStorm, TextMate и др. позволяват отваряне на файлове чрез специален URL, който е достатъчно да се зададе: ```php // PhpStorm @@ -92,44 +102,43 @@ Tracy\Debugger::$editor = 'phpstorm://open?file=%file&line=%line'; // TextMate Tracy\Debugger::$editor = 'txmt://open/?url=file://%file&line=%line'; // MacVim -Tracy\Debugger::$editor = 'mvim://open/?url=file://%file&line=%line'; +Tracy\Debugger::$editor = 'mvim://open?url=file:///%file&line=%line'; // Visual Studio Code Tracy\Debugger::$editor = 'vscode://file/%file:%line'; ``` -Ако използвате самостоятелен Tracy, поставете ред пред `Tracy\Debugger::enable()`, ако Nette, пред `$configurator->enableTracy()` в `Bootstrap.php`. +Ако използвате самостоятелна Tracy, поставете реда преди `Tracy\Debugger::enable()`, ако използвате Nette, тогава преди `$configurator->enableTracy()` в `Bootstrap.php`. -За съжаление действията `create file` или `fix it` не работят в macOS. +Действията `create file` или `fix it` за съжаление не работят на macOS. -Демонстрации .[#toc-demos] -========================== +Примери +======= -Коригиране на грешки: +Корекция на грешка: <iframe width="560" height="315" src="https://www.youtube.com/embed/3ITT4mC0Eq4?rel=0&showinfo=0" frameborder="0" allow="encrypted-media" allowfullscreen></iframe> -Създаване на нов файл: +Създаване на файл: <iframe width="560" height="315" src="https://www.youtube.com/embed/AJ_FUivAGZQ?rel=0&showinfo=0" frameborder="0" allow="encrypted-media" allowfullscreen></iframe> -Отстраняване на неизправности .[#toc-troubleshooting] -===================================================== +Отстраняване на проблеми +======================== -- Във Firefox може да се наложи да [разрешите |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] изпълнението на потребителския протокол в about:config, като зададете `network.protocol-handler.expose.editor` на `false` и `network.protocol-handler.expose-all` на `true`. Това обаче трябва да е разрешено по подразбиране. -- Ако нещата не се получат веднага, не изпадайте в паника. Опитайте да опресните страницата, да рестартирате браузъра или компютъра. Това би трябвало да помогне. -- Вижте [тук |https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/], за да поправите: - Входна грешка: Може би сте свързали файла ".js" с друго приложение, а не с JScript енджина. +- Във Firefox може да се наложи да разрешите протокола чрез [настройка |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] на `network.protocol-handler.expose.editor` на `false` и `network.protocol-handler.expose-all` на `true` в about:config. +- Ако не тръгне веднага, не се паникьосвайте и опитайте да презаредите страницата няколко пъти, преди да кликнете върху връзката. Ще тръгне! +- Ето [връзка |https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/] за коригиране на евентуална грешка: `Input Error: There is no script engine for file extension ".js"`, `Maybe you associated ".js" file to another app, not JScript engine.` съответно `за разширение .js не е наличен скриптов двигател`. -От версия 77 на Google Chrome вече няма да се показва квадратчето "Винаги отваряй този тип връзка в свързаното приложение", когато редакторът се отваря чрез връзка. Обходно решение за Windows: Създайте файл `fix.reg`: +В Google Chrome от версия 77 вече няма да виждате отметката „Винаги отваряй тези типове връзки в свързаното приложение“, когато редакторът се стартира чрез връзка. Решение за Windows: създайте файл `fix.reg`: ``` Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\URLWhitelist] "123"="editor://*" ``` -Импортирайте го с двукратно щракване и рестартирайте Chrome. +Импортирайте го с двойно кликване и рестартирайте браузъра Chrome. -Ако имате допълнителни проблеми или въпроси, задайте ги във [форума |https://forum.nette.org]. +С евентуални въпроси или коментари, моля, обърнете се към [форума |https://forum.nette.org]. diff --git a/tracy/bg/recipes.texy b/tracy/bg/recipes.texy index 03187fdca1..cf938653fd 100644 --- a/tracy/bg/recipes.texy +++ b/tracy/bg/recipes.texy @@ -1,12 +1,11 @@ -Рецепти -******* +Ръководства +*********** -Политика за сигурност на съдържанието .[#toc-content-security-policy] -===================================================================== +Content Security Policy +======================= -Ако сайтът ви използва политика за сигурност на съдържанието, ще трябва да добавите `'nonce-<value>'` и `'strict-dynamic'` към `script-src`, за да може Tracy да работи правилно. Някои приставки на трети страни може да изискват допълнителни директиви. -Nonce не се поддържа в директивата `style-src`, ако използвате тази директива, трябва да добавите `'unsafe-inline'`, но това трябва да се избягва в производствен режим. +Ако вашият уебсайт използва Content Security Policy, ще трябва да добавите същите `'nonce-<value>'` и `'strict-dynamic'` към `script-src`, за да работи Tracy правилно. Някои добавки от трети страни може да изискват допълнителни настройки. Nonce не се поддържа в директивата `style-src`; ако използвате тази директива, трябва да добавите `'unsafe-inline'`, но в продукционен режим трябва да избягвате това. Пример за конфигурация за [Nette Framework |nette:configuring]: @@ -24,11 +23,10 @@ header("Content-Security-Policy: script-src 'nonce-$nonce' 'strict-dynamic';"); ``` -По-бързо зареждане .[#toc-faster-loading] -========================================= +По-бързо зареждане +================== -Основната интеграция е проста, но ако имате бавно блокиращи скриптове в уеб страницата, те могат да забавят зареждането на Tracy. -Решението е да поставите `<?php Tracy\Debugger::renderLoader() ?>` в шаблона си преди всички скриптове: +Стартирането е лесно, но ако имате бавно зареждащи се блокиращи скриптове на уеб страницата, те могат да забавят зареждането на Tracy. Решението е да поставите `<?php Tracy\Debugger::renderLoader() ?>` във вашия шаблон преди всички скриптове: ```latte <!DOCTYPE html> @@ -42,12 +40,37 @@ header("Content-Security-Policy: script-src 'nonce-$nonce' 'strict-dynamic';"); ``` -AJAX и пренасочени заявки .[#toc-ajax-and-redirected-requests] -============================================================== +Дебъгване на AJAX заявки +======================== -Трейси може да показва Debug bar и Bluescreens за AJAX заявки и пренасочвания. Tracy създава свои собствени сесии, съхранява данни в свои собствени временни файлове и използва бисквитка `tracy-session`. +Tracy автоматично прихваща AJAX заявки, създадени с помощта на jQuery или нативното API `fetch`. Заявките се показват в лентата на Tracy като допълнителни редове, което позволява лесно и удобно дебъгване на AJAX. -Tracy може също така да бъде конфигуриран да използва собствена PHP сесия, която се стартира преди Tracy да бъде включен: +Ако не искате AJAX заявките да се прихващат автоматично, можете да деактивирате тази функция, като зададете JavaScript променлива: + +```js +window.TracyAutoRefresh = false; +``` + +За ръчно наблюдение на специфични AJAX заявки добавете HTTP хедър `X-Tracy-Ajax` със стойността, върната от `Tracy.getAjaxHeader()`. Ето пример за използване с функцията `fetch`: + +```js +fetch(url, { + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Tracy-Ajax': Tracy.getAjaxHeader(), + } +}) +``` + +Този подход позволява селективно дебъгване на AJAX заявки. + + +Съхранение на данни +=================== + +Tracy може да показва панели в Tracy Bar и Bluescreens за AJAX заявки и пренасочвания. Tracy създава собствена сесия, съхранява данни в собствени временни файлове и използва бисквитката `tracy-session`. + +Tracy може да бъде конфигурирана също така да използва нативната PHP сесия, която стартираме преди да включим Tracy: ```php session_start(); @@ -55,44 +78,44 @@ Debugger::setSessionStorage(new Tracy\NativeSession); Debugger::enable(); ``` -В случай че стартирането на сесия изисква по-сложна инициализация, можете да стартирате Tracy веднага (така че да може да се справи с всички възникнали грешки) и след това да инициализирате обработчика на сесията и накрая да информирате Tracy, че сесията е готова за използване, като използвате функцията `dispatch()`: +В случай, че стартирането на сесията изисква по-сложна инициализация, можете да стартирате Tracy веднага (за да може да обработи евентуално възникнали грешки), след това да инициализирате обработката на сесията и накрая да информирате Tracy, че сесията е готова за използване с помощта на функцията `dispatch()`: ```php Debugger::setSessionStorage(new Tracy\NativeSession); Debugger::enable(); -// последвано от инициализиране на сесията +// следва инициализация на сесията // и стартиране на сесията session_start(); Debugger::dispatch(); ``` -Функцията `setSessionStorage()` съществува от версия 2.9 насам, като преди това Tracy винаги е използвал родната PHP сесия. +Функцията `setSessionStorage()` съществува от версия 2.9, преди това Tracy винаги използваше нативната PHP сесия. -Потребителски скрубер .[#toc-custom-scrubber] -============================================= +Собствен scrubber +================= -Scrubber е филтър, който предотвратява изтичането на чувствителни данни от дъмповете, като например пароли или пълномощни. Филтърът се извиква за всеки елемент от изхвърления масив или обект и връща `true`, ако стойността е чувствителна. В този случай вместо стойността се извежда `*****`. +Scrubber е филтър, който предотвратява изтичането на чувствителни данни при дъмпване, като пароли или данни за достъп. Филтърът се извиква за всеки елемент от дъмпнатия масив или обект и връща `true`, ако стойността е чувствителна. В такъв случай вместо стойността се извежда `*****`. ```php -// избягва изхвърлянето на стойности на ключове и свойства като `password`, +// предотвратява извеждането на стойности на ключове и свойства като `password`, // `password_repeat`, `check_password`, `DATABASE_PASSWORD` и др. $scrubber = function(string $key, $value, ?string $class): bool { return preg_match('#password#i', $key) && $value !== null; }; -// използваме го за всички дъмпове в BlueScreen +// използваме го за всички дъмпове вътре в BlueScreen Tracy\Debugger::getBlueScreen()->scrubber = $scrubber; ``` -Потребителски регистратор .[#toc-custom-logger] -=============================================== +Собствен логър +============== -Можем да създадем потребителски регистратор, който да регистрира грешки, незасегнати изключения, а също и да бъде извикан от `Tracy\Debugger::log()`. Logger имплементира интерфейса [api:Tracy\ILogger]. +Можем да създадем собствен логър, който ще логва грешки, неприхванати изключения, а също така ще бъде извикан от метода `Tracy\Debugger::log()`. Логърът имплементира интерфейса [api:Tracy\ILogger]. ```php use Tracy\ILogger; @@ -112,7 +135,7 @@ class SlackLogger implements ILogger Tracy\Debugger::setLogger(new SlackLogger); ``` -Ако използваме пълната Nette Framework, можем да я зададем в конфигурационния файл на NEON: +Ако използваме пълния Nette Framework, можете да го настроите в конфигурационния NEON файл: ```neon services: @@ -120,10 +143,10 @@ services: ``` -Monolog Integration .[#toc-monolog-integration] ------------------------------------------------ +Интеграция с Monolog +-------------------- -Пакетът Tracy предоставя PSR-3 адаптер, който позволява интегрирането на [monolog/monolog](https://github.com/Seldaek/monolog). +Пакетът Tracy предоставя PSR-3 адаптер, който позволява интеграция с [monolog/monolog |https://github.com/Seldaek/monolog]. ```php $monolog = new Monolog\Logger('main-channel'); @@ -133,21 +156,21 @@ $tracyLogger = new Tracy\Bridges\Psr\PsrToTracyLoggerAdapter($monolog); Debugger::setLogger($tracyLogger); Debugger::enable(); -Debugger::log('info'); // пише: [<TIMESTAMP>] main-channel.INFO: info [] [] -Debugger::log('warning', Debugger::WARNING); // записва: [<TIMESTAMP>] main-channel.WARNING: предупреждение [] [] +Debugger::log('info'); // записва: [<TIMESTAMP>] main-channel.INFO: info [] [] +Debugger::log('warning', Debugger::WARNING); // записва: [<TIMESTAMP>] main-channel.WARNING: warning [] [] ``` -nginx .[#toc-nginx] -=================== +nginx +===== -Ако Tracy не работи на nginx, вероятно е неправилно конфигуриран. Ако има нещо като +Ако Tracy не работи на nginx сървър, вероятно той е неправилно конфигуриран. Ако в конфигурацията има нещо като: ```nginx try_files $uri $uri/ /index.php; ``` -да го промените на +променете го на: ```nginx try_files $uri $uri/ /index.php$is_args$args; diff --git a/tracy/bg/stopwatch.texy b/tracy/bg/stopwatch.texy index 0a6f388370..b1df9a6652 100644 --- a/tracy/bg/stopwatch.texy +++ b/tracy/bg/stopwatch.texy @@ -1,19 +1,19 @@ -Хронометър -********** +Измерване на време +****************** -Друг полезен инструмент е хронометърът на дебъгъра с точност до микросекунди: +Друг полезен инструмент на дебъгъра е хронометър с точност до микросекунди: ```php Debugger::timer(); -// сладки сънища, моята дъщеря +// спи, малки принце мой, птичките сладко вече спят... sleep(2); $elapsed = Debugger::timer(); // $elapsed = 2 ``` -С помощта на допълнителен параметър е възможно да се извършат няколко измервания едновременно. +С незадължителен параметър могат да се постигнат многократни измервания. ```php Debugger::timer('page-generating'); @@ -27,9 +27,9 @@ $pageElapsed = Debugger::timer('page-generating'); ``` ```php -Debugger::timer(); // стартира таймера +Debugger::timer(); // стартира хронометъра -... // някаква отнемаща време операция +... // времеемка операция -echo Debugger::timer(); // изминало време в секунди +echo Debugger::timer(); // извежда изминалото време в секунди ``` diff --git a/tracy/cs/@meta.texy b/tracy/cs/@meta.texy new file mode 100644 index 0000000000..97ca57995d --- /dev/null +++ b/tracy/cs/@meta.texy @@ -0,0 +1 @@ +{{sitename: Tracy Dokumentace}} diff --git a/tracy/cs/configuring.texy b/tracy/cs/configuring.texy index 043b7b1c30..85c1a31087 100644 --- a/tracy/cs/configuring.texy +++ b/tracy/cs/configuring.texy @@ -82,8 +82,7 @@ Debugger::$editorMapping = [ Nette Framework --------------- -Pokud používáte Nette Framework, můžete konfigurovat Tracy a přidávat nové panely do Tracy Bar také pomocí konfiguračního souboru. -V konfiguraci lze nastavovat parametry a také přidávat nové panely do Tracy Bar. Tyto nastavení se aplikují teprve po vytvoření DI kontejneru, takže chyby vzniklé předtím je nemohou reflektovat. +Pokud používáte Nette Framework, můžete konfigurovat Tracy a přidávat nové panely do Tracy Bar také pomocí konfiguračního souboru. V konfiguraci lze nastavovat parametry a také přidávat nové panely do Tracy Bar. Tyto nastavení se aplikují teprve po vytvoření DI kontejneru, takže chyby vzniklé předtím je nemohou reflektovat. Konfigurace logování chyb: @@ -167,3 +166,15 @@ tracy: ``` Hodnoty voleb `logSeverity`, `strictMode` a `scream` lze zapisovat jako pole chybových úrovní (např. `[E_WARNING, E_NOTICE]`), nebo jako výraz používaný v jazyce PHP (např. `E_ALL & ~E_NOTICE`). + + +Služby DI +--------- + +Tyto služby se přidávají do DI kontejneru: + +| Název | Typ | Popis +|---------------------------------------------------------- +| `tracy.logger` | [api:Tracy\ILogger] | logger +| `tracy.blueScreen` | [api:Tracy\BlueScreen] | BlueScreen +| `tracy.bar` | [api:Tracy\Bar] | Tracy Bar diff --git a/tracy/cs/guide.texy b/tracy/cs/guide.texy index c8e7a2ff9e..23343cc56a 100644 --- a/tracy/cs/guide.texy +++ b/tracy/cs/guide.texy @@ -44,8 +44,7 @@ require 'vendor/autoload.php'; // případně tracy.phar Debugger::enable(); ``` -První, čeho si na stránce všimnete, je Tracy Bar v pravém dolním rohu. Pokud jej nevidíte, může to znamenat, že Tracy běží v produkčním režimu. -Tracy je totiž z bezpečnostních důvodů viditelná pouze na localhost. Pro otestování, zda funguje, ji můžete dočasně přepnout do vývojovém režimu pomocí parametru `Debugger::enable(Debugger::Development)`. +První, čeho si na stránce všimnete, je Tracy Bar v pravém dolním rohu. Pokud jej nevidíte, může to znamenat, že Tracy běží v produkčním režimu. Tracy je totiž z bezpečnostních důvodů viditelná pouze na localhost. Pro otestování, zda funguje, ji můžete dočasně přepnout do vývojovém režimu pomocí parametru `Debugger::enable(Debugger::Development)`. Tracy Bar @@ -79,9 +78,9 @@ nebo při nezachycené výjimce: <b>Fatal error</b>: Uncaught Nette\MemberAccessException: Call to undefined method Nette\Application\UI\Form::addTest()? in /sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php:100 Stack trace: #0 /sandbox/vendor/nette/utils/src/Utils/Object.php(75): Nette\Utils\ObjectMixin::call(Object(Nette\Application\UI\Form), 'addTest', Array) -#1 /sandbox/app/forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) -#2 /sandbox/app/presenters/SignPresenter.php(21): App\Forms\SignFormFactory->create() -#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presenters\SignPresenter->createComponentSignInForm('signInForm') +#1 /sandbox/app/Forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) +#2 /sandbox/app/Presentation/Sign/SignPresenter.php(21): App\Forms\SignFormFactory->create() +#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presentation\Sign\SignPresenter->createComponentSignInForm('signInForm') #4 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(139): Nette\ComponentModel\Container->createComponent('signInForm') #5 /sandbox/temp/cache/latte/15206b353f351f6bfca2c36cc.php(17): Nette\ComponentModel\Co in <b>/sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php</b> on line <b>100</b><br /> \-- @@ -119,7 +118,7 @@ Jak vidíte, Laděnka je poměrně výřečná, což lze ocenit ve vývojovém p [* tracy-error2.webp .{url:-} *] -Produkční režim potlačí zobrazování všech ladících informacích, které posíláme ven pomocí [dump() |dumper], a samozřejmě také všech chybových zpráv, které generuje PHP. Pokud jste tedy v kódu zapomněli nějaké `dump($obj)`, nemusíte se obávat, na produkčním serveru se nic nevypíše. +Produkční režim potlačí zobrazování všech ladících informací, které posíláme ven pomocí [dump() |dumper], a samozřejmě také všech chybových zpráv, které generuje PHP. Pokud jste tedy v kódu zapomněli nějaké `dump($obj)`, nemusíte se obávat, na produkčním serveru se nic nevypíše. Jak funguje autodetekce režimu? Režim je vývojářský tehdy, pokud je aplikace spuštěna na localhostu (tj. IP adresa `127.0.0.1` nebo `::1`) a není přitomna proxy (tj. její HTTP hlavička). Jinak běží v produkčním režimu. @@ -138,7 +137,7 @@ Debugger::enable('secret1234@23.75.345.200'); Vývojářský/produkční režim můžeme také přímo nastavit použitím konstanty `Debugger::Development` nebo `Debugger::Production` jako parametru metody `enable()`. .[note] -Pokud používát Nette Framework, podívejte se, jak [nastavit režim pro něj |application:bootstrap#Vývojářský vs produkční režim] a ten se následně použije i pro Tracy. +Pokud používát Nette Framework, podívejte se, jak [nastavit režim pro něj |application:bootstrapping#Vývojářský vs produkční režim] a ten se následně použije i pro Tracy. Logování chyb @@ -194,7 +193,7 @@ Podporované verze PHP | Tracy | kompatibilní s PHP |-----------|------------------- -| Tracy 2.10 – 3.0 | PHP 8.0 – 8.2 +| Tracy 2.10 – 3.0 | PHP 8.0 – 8.4 | Tracy 2.9 | PHP 7.2 – 8.2 | Tracy 2.8 | PHP 7.2 – 8.1 | Tracy 2.6 – 2.7 | PHP 7.1 – 8.0 diff --git a/tracy/cs/open-files-in-ide.texy b/tracy/cs/open-files-in-ide.texy index 2483f08ae5..37ce131750 100644 --- a/tracy/cs/open-files-in-ide.texy +++ b/tracy/cs/open-files-in-ide.texy @@ -41,6 +41,15 @@ Pozor, ponechávejte dvojitá lomítka v cestách. To uděláte spuštěním soubor `install.cmd`. **Je potřeba ho spustit jako Správce.** Skript `open-editor.js` bude nyní obsluhovat protokol `editor://`. +Aby bylo možné otevírat odkazy vygenerované na jiných serverech, jako třeba na ostrém serveru nebo v Dockeru, doplňte do `open-editor.js` ještě mapování vzdálené URL na lokální: + +```js + mappings: { + // vzdálená cesta: lokální cesta + '/var/www/nette.app': 'W:\\Nette.web\\_web', + } +``` + Linux ===== @@ -73,7 +82,8 @@ Učiňte soubor spustitelným: chmod +x ~/bin/open-editor.sh ``` -Pokud používaný editor nemáte nainstalovaný z balíku, pravděpodobně nebude mít binárka cestu v $PATH. To lze jednoduše napravit. V adresáři `~/bin` si vytvořte symlink na binárku editoru. .[note] +.[note] +Pokud používaný editor nemáte nainstalovaný z balíku, pravděpodobně nebude mít binárka cestu v $PATH. To lze jednoduše napravit. V adresáři `~/bin` si vytvořte symlink na binárku editoru. 3. Zaregistrujte handler protokolu `editor://` v systému. @@ -119,8 +129,7 @@ Vytvoření souboru: - Ve Firefoxu může být potřeba protokol povolit [nastavením |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] `network.protocol-handler.expose.editor` na `false` a `network.protocol-handler.expose-all` na `true` v about:config. - Pokud vám to hned nepůjde, nepanikařte a zkuste párkrát refreshnout stránku před klikem na onen odkaz. Rozjede se to! -- Zde je [link|https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/] na opravu případné chyby: - Input Error: There is no script engine for file extension ".js" Maybe you associated ".js" file to another app, not JScript engine. +- Zde je [link|https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/] na opravu případné chyby: `Input Error: There is no script engine for file extension ".js"`, `Maybe you associated ".js" file to another app, not JScript engine.` respektive `pro příponu .js není k dispozici žádný skriptovací stroj`. V Google Chrome od verze 77 již neuvidíte zatržítko „Tento typ odkazů vždy otevírat v přidružené aplikaci“, když je editor spuštěný prostřednictvím odkazu. Řešení pro Windows: vytvořte soubor `fix.reg`: diff --git a/tracy/cs/recipes.texy b/tracy/cs/recipes.texy index ccbe9f02b5..f8e13f0f47 100644 --- a/tracy/cs/recipes.texy +++ b/tracy/cs/recipes.texy @@ -5,8 +5,7 @@ Návody Content Security Policy ======================= -Pokud váš web používá Content Security Policy, budete muset přidat stejné `'nonce-<value>'` a `'strict-dynamic'` do `script-src`, aby Tracy správně fungovala. Některé doplňky třetích stran mohou vyžadovat další nastavení. -Nonce není podporována v direktivě `style-src`, pokud tuto direktivu používáte, musíte přidat `'unsafe-inline'`, ale v produkčním režimu byste se tomu měli vyhnout. +Pokud váš web používá Content Security Policy, budete muset přidat stejné `'nonce-<value>'` a `'strict-dynamic'` do `script-src`, aby Tracy správně fungovala. Některé doplňky třetích stran mohou vyžadovat další nastavení. Nonce není podporována v direktivě `style-src`, pokud tuto direktivu používáte, musíte přidat `'unsafe-inline'`, ale v produkčním režimu byste se tomu měli vyhnout. Příklad konfigurace pro [Nette Framework |nette:configuring]: @@ -27,8 +26,7 @@ header("Content-Security-Policy: script-src 'nonce-$nonce' 'strict-dynamic';"); Rychlejší načítání ================== -Spuštění je přímočaré, pokud však máte na webové stránce pomalu načítající se blokující skripty, mohou zpomalit načítání Tracy. -Řešením je umístit `<?php Tracy\Debugger::renderLoader() ?>` do vaší šablony před všechny skripty: +Spuštění je přímočaré, pokud však máte na webové stránce pomalu načítající se blokující skripty, mohou zpomalit načítání Tracy. Řešením je umístit `<?php Tracy\Debugger::renderLoader() ?>` do vaší šablony před všechny skripty: ```latte <!DOCTYPE html> @@ -42,10 +40,35 @@ Spuštění je přímočaré, pokud však máte na webové stránce pomalu nač ``` -AJAX a přesměrování -=================== +Ladění AJAXový požadavků +======================== -Tracy umí zobrazit Debug bar a Bluescreeny pro AJAXové požadavky a přesměrovaní. Tracy si vytváří vlastní session, data uchovává ve vlastních dočasných souborech a používá cookie `tracy-session`. +Tracy automaticky zachycuje AJAXové požadavky vytvořené pomocí jQuery nebo nativního API `fetch`. Požadavky jsou v liště Tracy zobrazeny jako další řádky, což umožňuje snadné a pohodlné ladění AJAXu. + +Pokud nechcete AJAXové požadavky zachycovat automaticky, můžete tuto funkci zakázat nastavením JavaScriptové proměnné: + +```js +window.TracyAutoRefresh = false; +``` + +Pro ruční monitorování specifických AJAX požadavků přidejte HTTP hlavičku `X-Tracy-Ajax` s hodnotou, kterou vrátí `Tracy.getAjaxHeader()`. Zde je příklad použití s funkcí `fetch`: + +```js +fetch(url, { + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Tracy-Ajax': Tracy.getAjaxHeader(), + } +}) +``` + +Tento přístup umožňuje selektivní ladění AJAX požadavků. + + +Datové úložiště +=============== + +Tracy umí zobrazit panely v Tracy baru a Bluescreeny pro AJAXové požadavky a přesměrovaní. Tracy si vytváří vlastní session, data uchovává ve vlastních dočasných souborech a používá cookie `tracy-session`. Tracy lze nakonfigurovat také tak, aby používala nativní PHP session, kterou nastartujeme ještě před zapnutím Tracy: diff --git a/tracy/de/@home.texy b/tracy/de/@home.texy index d2b0a017e2..f95c8d713c 100644 --- a/tracy/de/@home.texy +++ b/tracy/de/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Tracy - Ein unverzichtbares Debugging-Tool für alle PHP-Entwickler}} -{{description: Tracy ist ein Tool, das das Debuggen von PHP-Code erleichtert. Es ist ein nützlicher Assistent für alle PHP-Programmierer, der bei der übersichtlichen Visualisierung und Protokollierung von Fehlern, dem Dumping von Variablen und vielem mehr hilft. Warnung! Tracy macht süchtig!}} +{{maintitle: Tracy – das Debugging-Tool, mit dem Fehler Spaß machen}} +{description: Tracy ist ein Werkzeug zur Erleichterung des Debuggings von PHP-Code. Es ist ein nützlicher Helfer für alle PHP-Programmierer, der bei der Visualisierung und Protokollierung von Fehlern, dem Dumpen von Variablen und vielem mehr hilft. Warnung: Tracy macht süchtig!}} diff --git a/tracy/de/@left-menu.texy b/tracy/de/@left-menu.texy index 6e9d5af0fc..82c1e33e24 100644 --- a/tracy/de/@left-menu.texy +++ b/tracy/de/@left-menu.texy @@ -1,7 +1,7 @@ -- [Erste Schritte |Guide] -- [Kipper |Dumper] -- [Stoppuhr |Stopwatch] -- [Konfigurieren |Configuring] -- [Rezepte |Recipes] -- [IDE-Einbindung |open-files-in-ide] -- [Erstellen von Erweiterungen |extensions] +- [Einstieg in Tracy |guide] +- [Dumpen |dumper] +- [Zeitmessung |stopwatch] +- [Konfiguration |configuring] +- [Anleitungen |recipes] +- [Integration mit IDE |open-files-in-ide] +- [Erweiterungen erstellen |extensions] diff --git a/tracy/de/@menu.texy b/tracy/de/@menu.texy index 2ee7e40d00..f3e797081f 100644 --- a/tracy/de/@menu.texy +++ b/tracy/de/@menu.texy @@ -1,3 +1,3 @@ -- [Startseite |@home] -- [Dokumentation |Guide] +- [Einführung |@home] +- [Dokumentation |guide] - "GitHub .[link-external]":https://github.com/nette/tracy diff --git a/tracy/de/@meta.texy b/tracy/de/@meta.texy new file mode 100644 index 0000000000..6ed3b370dd --- /dev/null +++ b/tracy/de/@meta.texy @@ -0,0 +1 @@ +{{sitename: Tracy Dokumentation}} diff --git a/tracy/de/configuring.texy b/tracy/de/configuring.texy index bf424c4cae..7c8cdc43e0 100644 --- a/tracy/de/configuring.texy +++ b/tracy/de/configuring.texy @@ -1,142 +1,141 @@ -Tracy-Konfiguration +Tracy Konfiguration ******************* -Die folgenden Beispiele gehen davon aus, dass der folgende Klassenalias definiert ist: +Alle Beispiele gehen von einem erstellten Alias aus: ```php use Tracy\Debugger; ``` -Fehlerprotokollierung .[#toc-error-logging] -------------------------------------------- +Fehlerprotokollierung +--------------------- ```php $logger = Debugger::getLogger(); -// if error has occurred the notification is sent to this email -$logger->email = 'dev@example.com'; // (string|string[]) defaults to unset +// E-Mail, an die Benachrichtigungen über aufgetretene Fehler gesendet werden +$logger->email = 'dev@example.com'; // (string|string[]) Standardmäßig nicht festgelegt -// email sender -$logger->fromEmail = 'me@example.com'; // (string) defaults to unset +// Absender der E-Mail +$logger->fromEmail = 'me@example.com'; // (string) Standardmäßig nicht festgelegt -// routine for sending email -$logger->mailer = /* ... */; // (callable) default it sending by mail() +// Routine, die das Senden der E-Mail sicherstellt +$logger->mailer = /* ... */; // (callable) Standardmäßig wird mit der mail()-Funktion gesendet -// after what shortest time to send another email? -$logger->emailSnooze = /* ... */; // (string) default is '2 days' +// Nach welcher kürzesten Zeit die nächste E-Mail senden? +$logger->emailSnooze = /* ... */; // (string) Standard ist '2 days' -// for which error levels is BlueScreen also logged? -Debugger::$logSeverity = E_WARNING | E_NOTICE; // defaults to 0 (no error level) +// Für welche Fehlerstufen wird auch der BlueScreen protokolliert? +Debugger::$logSeverity = E_WARNING | E_NOTICE; // Standard ist 0 (keine Fehlerstufen) ``` -`dump()` Verhalten .[#toc-dump-behavior] ----------------------------------------- +Verhalten von `dump()` +---------------------- ```php -// maximum string length -Debugger::$maxLength = 150; // (int) default according to Tracy +// Maximale Länge der Zeichenkette +Debugger::$maxLength = 150; // (int) Standard gemäß Tracy-Version -// how deep will list -Debugger::$maxDepth = 10; // (int) default according to Tracy +// Maximale Verschachtelungstiefe +Debugger::$maxDepth = 10; // (int) Standard gemäß Tracy-Version -// hide values of these keys (since Tracy 2.8) -Debugger::$keysToHide = ['password', /* ... */]; // (string[]) defaults to [] +// Werte dieser Schlüssel ausblenden (ab Tracy 2.8) +Debugger::$keysToHide = ['password', /* ... */]; // (string[]) Standard ist [] -// visual theme (since Tracy 2.8) -Debugger::$dumpTheme = 'dark'; // (light|dark) defaults to 'light' +// Visuelles Thema (ab Tracy 2.8) +Debugger::$dumpTheme = 'dark'; // (light|dark) Standard ist 'light' -// displays the location where dump() was called? -Debugger::$showLocation = /* ... */; // (bool) default according to Tracy +// Ort anzeigen, an dem die dump()-Funktion aufgerufen wurde? +Debugger::$showLocation = /* ... */; // (bool) Standard gemäß Tracy-Version ``` -Andere .[#toc-others] ---------------------- +Sonstiges +--------- ```php -// in Development mode, you will see notice or error warnings as BlueScreen -Debugger::$strictMode = /* ... */; // (bool|int) defaults to false, you can select only specific error levels (e.g. E_USER_DEPRECATED | E_DEPRECATED) +// Im Entwicklungsmodus werden Fehler vom Typ Notice oder Warning als BlueScreen angezeigt +Debugger::$strictMode = /* ... */; // (bool|int) Standard ist false, es können nur bestimmte Fehlerstufen ausgewählt werden (z.B. E_USER_DEPRECATED | E_DEPRECATED) -// displays silent (@) error messages -Debugger::$scream = /* ... */; // (bool|int) defaults to false, since version 2.9 it is possible to select only specific error levels (e.g. E_USER_DEPRECATED | E_DEPRECATED) +// Unterdrückte (@) Fehlermeldungen anzeigen? +Debugger::$scream = /* ... */; // (bool|int) Standard ist false, ab Version 2.9 können nur bestimmte Fehlerstufen ausgewählt werden (z.B. E_USER_DEPRECATED | E_DEPRECATED) -// link format to open in the editor -Debugger::$editor = /* ... */; // (string|null) defaults to 'editor://open/?file=%file&line=%line' +// Linkformat zum Öffnen im Editor +Debugger::$editor = /* ... */; // (string|null) Standard ist 'editor://open/?file=%file&line=%line' -// path to template with custom page for error 500 -Debugger::$errorTemplate = /* ... */; // (string) defaults to unset +// Pfad zum Template mit einer benutzerdefinierten Seite für Fehler 500 +Debugger::$errorTemplate = /* ... */; // (string) Standardmäßig nicht festgelegt -// shows Tracy Bar? -Debugger::$showBar = /* ... */; // (bool) defaults to true +// Tracy Bar anzeigen? +Debugger::$showBar = /* ... */; // (bool) Standard ist true Debugger::$editorMapping = [ - // original => new + // Original => Neu '/var/www/html' => '/data/web', '/home/web' => '/srv/html', ]; ``` -Nette Rahmenwerk .[#toc-nette-framework] ----------------------------------------- +Nette Framework +--------------- -Wenn Sie das Nette Framework verwenden, können Sie Tracy auch über die Konfigurationsdatei konfigurieren und der Tracy-Leiste neue Felder hinzufügen. -Sie können die Tracy-Parameter in der Konfiguration einstellen und der Tracy-Leiste auch neue Panels hinzufügen. Diese Einstellungen werden erst nach der Erstellung des DI-Containers angewendet, so dass Fehler, die vorher aufgetreten sind, nicht berücksichtigt werden können. +Wenn Sie das Nette Framework verwenden, können Sie Tracy konfigurieren und neue Panels zur Tracy Bar auch über die Konfigurationsdatei hinzufügen. In der Konfiguration können Parameter eingestellt und auch neue Panels zur Tracy Bar hinzugefügt werden. Diese Einstellungen werden erst nach der Erstellung des DI-Containers angewendet, sodass Fehler, die davor auftreten, diese nicht widerspiegeln können. Konfiguration der Fehlerprotokollierung: ```neon tracy: - # if error has occurred the notification is sent to this email - email: dev@example.com # (string|string[]) defaults to unset + # E-Mail, an die Benachrichtigungen über aufgetretene Fehler gesendet werden + email: dev@example.com # (string|string[]) Standardmäßig nicht festgelegt - # email sender - fromEmail: robot@example.com # (string) defaults to unset + # Absender der E-Mail + fromEmail: robot@example.com # (string) Standardmäßig nicht festgelegt - # period of postponement of emails sending (since Tracy 2.8.8) - emailSnooze: ... # (string) defaults to '2 days' + # Verzögerungszeit für den E-Mail-Versand (ab Tracy 2.8.8) + emailSnooze: ... # (string) Standard ist '2 days' - # to use a mailer defined in the configuration? (since Tracy 2.5) - netteMailer: ... # (bool) defaults to true + # Nette Mailer zum Senden von E-Mails verwenden? (ab Tracy 2.5) + netteMailer: ... # (bool) Standard ist true - # for which error levels is BlueScreen also logged? - logSeverity: [E_WARNING, E_NOTICE] # defaults to [] + # Für welche Fehlerstufen wird auch der BlueScreen protokolliert? + logSeverity: [E_WARNING, E_NOTICE] # Standard ist [] ``` -Konfiguration für die Funktion `dump()`: +Konfiguration des Verhaltens der `dump()`-Funktion: ```neon tracy: - # maximum string length - maxLength: 150 # (int) default according to Tracy + # Maximale Länge der Zeichenkette + maxLength: 150 # (int) Standard gemäß Tracy-Version - # how deep will list - maxDepth: 10 # (int) default according to Tracy + # Maximale Verschachtelungstiefe + maxDepth: 10 # (int) Standard gemäß Tracy-Version - # hide values of these keys (since Tracy 2.8) - keysToHide: [password, pass] # (string[]) defaults to [] + # Werte dieser Schlüssel ausblenden (ab Tracy 2.8) + keysToHide: [password, pass] # (string[]) Standard ist [] - # visual theme (since Tracy 2.8) - dumpTheme: dark # (light|dark) defaults to 'light' + # Visuelles Thema (ab Tracy 2.8) + dumpTheme: dark # (light|dark) Standard ist 'light' - # displays the location where dump() was called? - showLocation: ... # (bool) default according to Tracy + # Ort anzeigen, an dem die dump()-Funktion aufgerufen wurde? + showLocation: ... # (bool) Standard gemäß Tracy-Version ``` -So installieren Sie die Tracy-Erweiterung: +Installation von Tracy-Erweiterungen: ```neon tracy: - # appends bars to Tracy Bar + # Fügt Panels zur Tracy Bar hinzu bar: - Nette\Bridges\DITracy\ContainerPanel - IncludePanel - XDebugHelper('myIdeKey') - MyPanel(@MyService) - # append panels to BlueScreen + # Fügt Panels zum BlueScreen hinzu blueScreen: - DoctrinePanel::renderException ``` @@ -145,25 +144,37 @@ Andere Optionen: ```neon tracy: - # in Development mode, you will see notice or error warnings as BlueScreen - strictMode: ... # defaults to true + # Im Entwicklungsmodus werden Fehler vom Typ Notice oder Warning als BlueScreen angezeigt + strictMode: ... # Standard ist true - # displays silent (@) error messages - scream: ... # defaults to false + # Unterdrückte (@) Fehlermeldungen anzeigen? + scream: ... # Standard ist false - # link format to open in the editor - editor: ... # (string) defaults to 'editor://open/?file=%file&line=%line' + # Linkformat zum Öffnen im Editor + editor: ... # (string) Standard ist 'editor://open/?file=%file&line=%line' - # path to template with custom page for error 500 - errorTemplate: ... # (string) defaults to unset + # Pfad zum Template mit einer benutzerdefinierten Seite für Fehler 500 + errorTemplate: ... # (string) Standardmäßig nicht festgelegt - # shows Tracy Bar? - showBar: ... # (bool) defaults to true + # Tracy Bar anzeigen? + showBar: ... # (bool) Standard ist true editorMapping: - # original: new + # Original: Neu /var/www/html: /data/web /home/web: /srv/html ``` -Die Werte der Optionen `logSeverity`, `strictMode` und `scream` können als ein Array von Fehlerstufen (z.B. `[E_WARNING, E_NOTICE]`) oder als ein in PHP verwendeter Ausdruck (z.B. `E_ALL & ~E_NOTICE`). +Die Werte der Optionen `logSeverity`, `strictMode` und `scream` können als Array von Fehlerebenen (z.B. `[E_WARNING, E_NOTICE]`) oder als in PHP verwendeter Ausdruck (z.B. `E_ALL & ~E_NOTICE`) geschrieben werden. + + +DI-Dienste +---------- + +Diese Dienste werden dem DI-Container hinzugefügt: + +| Name | Typ | Beschreibung +|---------------------------------------------------------- +| `tracy.logger` | [api:Tracy\ILogger] | Logger +| `tracy.blueScreen` | [api:Tracy\BlueScreen] | BlueScreen +| `tracy.bar` | [api:Tracy\Bar] | Tracy Bar diff --git a/tracy/de/dumper.texy b/tracy/de/dumper.texy index 34be4718ea..5a8809e558 100644 --- a/tracy/de/dumper.texy +++ b/tracy/de/dumper.texy @@ -1,20 +1,20 @@ -Dumper -****** +Dumping +******* -Jeder Debugging-Entwickler ist ein guter Freund der Funktion `var_dump`, die alle Inhalte beliebiger Variablen im Detail auflistet. Leider ist ihre Ausgabe ohne HTML-Formatierung und gibt den Dump in einer einzigen Zeile HTML-Code aus, ganz zu schweigen vom Kontext-Escaping. Es ist notwendig, die `var_dump` durch eine praktischere Funktion zu ersetzen. Und genau das ist `dump()`. +Jeder Debugger ist ein guter Freund der Funktion [php:var_dump], die den Inhalt einer Variablen detailliert ausgibt. Leider verliert die Ausgabe in einer HTML-Umgebung ihre Formatierung und verschmilzt zu einer einzigen Zeile, ganz zu schweigen von der Bereinigung des HTML-Codes. In der Praxis ist es unerlässlich, `var_dump` durch eine geschicktere Funktion zu ersetzen. Das ist genau `dump()`. ```php $arr = [10, 20.2, true, null, 'hello']; dump($arr); -// or Debugger::dump($arr); +// oder Debugger::dump($arr); ``` erzeugt die Ausgabe: [* dump-basic.webp *] -Sie können das Standardthema "hell" in "dunkel" ändern: +Das standardmäßige helle Thema können Sie in ein dunkles ändern: ```php Debugger::$dumpTheme = 'dark'; @@ -22,27 +22,27 @@ Debugger::$dumpTheme = 'dark'; [* dump-dark.webp *] -Sie können auch die Verschachtelungstiefe mit `Debugger::$maxDepth` und die Länge der angezeigten Strings mit `Debugger::$maxLength` ändern. Niedrigere Werte beschleunigen natürlich das Rendering von Tracy. +Weiterhin können wir die Verschachtelungstiefe mit [Debugger::$maxDepth |api:Tracy\Debugger::$maxDepth] und die Länge der angezeigten Beschriftungen mit [Debugger::$maxLength |api:Tracy\Debugger::$maxLength] ändern. Niedrigere Werte beschleunigen den Debugger natürlich. ```php -Debugger::$maxDepth = 2; // default: 3 -Debugger::$maxLength = 50; // default: 150 +Debugger::$maxDepth = 2; // Standard: 3 +Debugger::$maxLength = 50; // Standard: 150 ``` -Die Funktion `dump()` kann weitere nützliche Informationen anzeigen. `Tracy\Dumper::LOCATION_SOURCE` fügt einen Tooltip mit dem Pfad zu der Datei hinzu, in der die Funktion aufgerufen wurde. `Tracy\Dumper::LOCATION_LINK` fügt einen Link zu der Datei hinzu. `Tracy\Dumper::LOCATION_CLASS` fügt einen Tooltip zu jedem ausgegebenen Objekt hinzu, der den Pfad zu der Datei enthält, in der die Klasse des Objekts definiert ist. Alle diese Konstanten können in der Variablen `Debugger::$showLocation` gesetzt werden, bevor die Funktion `dump()` aufgerufen wird. Mit dem Operator `|` können Sie mehrere Werte auf einmal setzen. +Die Funktion `dump()` kann auch weitere nützliche Informationen ausgeben. Die Konstante `Tracy\Dumper::LOCATION_SOURCE` fügt einen Tooltip mit dem Pfad zu der Stelle hinzu, an der die Funktion aufgerufen wurde. `Tracy\Dumper::LOCATION_LINK` stellt uns einen Link zu dieser Stelle zur Verfügung. `Tracy\Dumper::LOCATION_CLASS` gibt bei jedem gedumpten Objekt einen Tooltip mit dem Pfad zur Datei aus, in der seine Klasse definiert ist. Die Konstanten werden der Variablen `Debugger::$showLocation` zugewiesen, bevor `dump()` aufgerufen wird. Wenn wir mehrere Werte gleichzeitig setzen wollen, verbinden wir sie mit dem Operator `|`. ```php -Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // Shows path to where the dump() was called -Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Shows both paths to the classes and link to where the dump() was called -Debugger::$showLocation = false; // Hides additional location information -Debugger::$showLocation = true; // Shows all additional location information +Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // Stellt nur die Ausgabe über den Aufrufort der Funktion ein +Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Stellt gleichzeitig die Ausgabe des Links und den Pfad zur Klasse ein +Debugger::$showLocation = false; // Schaltet die Ausgabe zusätzlicher Informationen aus +Debugger::$showLocation = true; // Schaltet die Ausgabe aller zusätzlichen Informationen ein ``` -Eine sehr praktische Alternative zu `dump()` ist `dumpe()` (d.h. dump and exit) und `bdump()`. Damit können wir die Variablen in Tracy Bar ausgeben. Dies ist nützlich, weil Dumps die Ausgabe nicht durcheinander bringen und wir dem Dump auch einen Titel hinzufügen können. +Eine praktische Alternative zu `dump()` ist `dumpe()` (dump & exit) und `bdump()`. Letzteres ermöglicht es uns, den Wert einer Variablen im Tracy Bar Panel auszugeben. Das ist sehr praktisch, da die Dumps vom Seitenlayout getrennt sind und wir ihnen auch einen Kommentar hinzufügen können. ```php -bdump([2, 4, 6, 8], 'even numbers up to ten'); -bdump([1, 3, 5, 7, 9], 'odd numbers up to ten'); +bdump([2, 4, 6, 8], 'gerade Zahlen bis zehn'); +bdump([1, 3, 5, 7, 9], 'ungerade Zahlen bis zehn'); ``` -[* bardump-en.webp *] +[* bardump-cs.webp *] diff --git a/tracy/de/extensions.texy b/tracy/de/extensions.texy index 1392a08005..7961b4f9e1 100644 --- a/tracy/de/extensions.texy +++ b/tracy/de/extensions.texy @@ -1,23 +1,23 @@ -Tracy-Erweiterungen erstellen -***************************** +Erstellung von Erweiterungen für Tracy +************************************** <div class=perex> -Tracy ist ein hervorragendes Werkzeug zur Fehlersuche in Ihrer Anwendung. Manchmal benötigen Sie jedoch mehr Informationen als Tracy bietet. Sie werden mehr darüber erfahren: +Tracy bietet ein großartiges Werkzeug zum Debuggen Ihrer Anwendung. Manchmal möchten Sie jedoch auch einige zusätzliche Informationen zur Hand haben. Wir zeigen Ihnen, wie Sie Ihre eigenen Erweiterungen für die Tracy Bar schreiben, um die Entwicklung noch angenehmer zu gestalten. -- Deine eigenen Tracy Bar Panels zu erstellen -- Erstellen Ihrer eigenen Bluescreen-Erweiterungen +- Erstellung eines eigenen Panels für die Tracy Bar +- Erstellung einer eigenen Erweiterung für den Bluescreen </div> .[tip] -Nützliche Erweiterungen für Tracy finden Sie auf "Componette":https://componette.org/search/tracy. +Ein Repository mit fertigen Erweiterungen für Tracy finden Sie auf "Componette":https://componette.org/search/tracy. -Tracy Bar Erweiterungen .[#toc-tracy-bar-extensions] -==================================================== +Erweiterungen für die Tracy Bar +=============================== -Das Erstellen einer neuen Erweiterung für Tracy Bar ist einfach. Sie müssen die Schnittstelle `Tracy\IBarPanel` mit den Methoden `getTab()` und `getPanel()` implementieren. Die Methoden müssen den HTML-Code eines Tabs (kleines Etikett auf der Tracy Bar) und eines Panels (Pop-up, das nach dem Anklicken des Tabs angezeigt wird) zurückgeben. Wenn `getPanel()` nichts zurückgibt, wird nur die Registerkarte angezeigt. Wenn `getTab()` nichts zurückgibt, wird nichts angezeigt und `getPanel()` wird nicht aufgerufen. +Eine neue Erweiterung für die Tracy Bar zu erstellen ist nicht kompliziert. Sie erstellen ein Objekt, das das Interface `Tracy\IBarPanel` implementiert, welches zwei Methoden `getTab()` und `getPanel()` hat. Die Methoden müssen den HTML-Code des Tabs (eine kleine Beschriftung, die direkt auf der Bar angezeigt wird) und des Panels zurückgeben. Wenn `getPanel()` nichts zurückgibt, wird nur die Beschriftung selbst angezeigt. Wenn `getTab()` nichts zurückgibt, wird gar nichts angezeigt und `getPanel()` wird auch nicht mehr aufgerufen. ```php class ExamplePanel implements Tracy\IBarPanel @@ -35,16 +35,16 @@ class ExamplePanel implements Tracy\IBarPanel ``` -Registrierung .[#toc-registration] ----------------------------------- +Registrierung +------------- -Die Anmeldung erfolgt unter `Tracy\Bar::addPanel()`: +Die Registrierung erfolgt über `Tracy\Bar::addPanel()`: ```php Tracy\Debugger::getBar()->addPanel(new ExamplePanel); ``` -oder Sie können Ihr Panel einfach in der Anwendungskonfiguration registrieren: +Oder Sie können das Panel direkt in der Anwendungskonfiguration registrieren: ```neon tracy: @@ -53,70 +53,70 @@ tracy: ``` -Registerkarte HTML-Code .[#toc-tab-html-code] ---------------------------------------------- +HTML-Code des Tabs +------------------ -Sollte in etwa so aussehen: +Er sollte ungefähr so aussehen: ```latte -<span title="Explaining tooltip"> +<span title="Erläuternde Beschriftung"> <svg>...</svg> - <span class="tracy-label">Title</span> + <span class="tracy-label">Titel</span> </span> ``` -Das Bild sollte das Format SVG haben. Wenn Sie keinen Tooltip benötigen, können Sie `<span>` weglassen. +Das Bild sollte im SVG-Format sein. Wenn keine erläuternde Beschriftung benötigt wird, kann `<span>` weggelassen werden. -HTML-Code für das Panel .[#toc-panel-html-code] ------------------------------------------------ +HTML-Code des Panels +-------------------- -Sollte in etwa so aussehen: +Er sollte ungefähr so aussehen: ```latte -<h1>Title</h1> +<h1>Titel</h1> <div class="tracy-inner"> <div class="tracy-inner-container"> - ... content ... + ... Inhalt ... </div> </div> ``` -Der Titel sollte entweder derselbe sein wie in der Registerkarte oder zusätzliche Informationen enthalten. +Der Titel sollte entweder derselbe sein wie der Titel des Tabs oder er kann zusätzliche Daten enthalten. -Eine Erweiterung kann mehrfach registriert werden, daher wird empfohlen, das Attribut `id` nicht für das Styling zu verwenden. Sie können Klassen verwenden, vorzugsweise im `tracy-addons-<class-name>[-<optional>]` Format. Bei der Erstellung von CSS ist es besser, `#tracy-debug .class` zu verwenden, da diese Regel eine höhere Priorität als Reset hat. +Es muss berücksichtigt werden, dass eine Erweiterung auch mehrmals registriert werden kann, beispielsweise mit unterschiedlichen Einstellungen. Daher können für das Styling keine CSS-IDs verwendet werden, sondern nur Klassen, und zwar in der Form `tracy-addons-<Klassenname>[-<optional>]`. Die Klasse schreiben Sie dann zusammen mit der Klasse `tracy-inner` in das Div. Beim Schreiben von CSS ist es nützlich, `#tracy-debug .klasse` zu schreiben, da die Regel dann eine höhere Priorität als der Reset hat. -Standard-Stile .[#toc-default-styles] -------------------------------------- +Standardstile +------------- -Im Panel werden die Elemente `<a>`, `<table>`, `<pre>`, `<code>` über Standardstile. Um einen Link zum Ausblenden oder Anzeigen eines anderen Elements zu erstellen, verbinden Sie sie mit den Attributen `href` und `id` und der Klasse `tracy-toggle`. +Im Panel sind `<a>`, `<table>`, `<pre>`, `<code>` vordefiniert. Wenn Sie einen Link erstellen möchten, der ein anderes Element verbirgt und anzeigt, verbinden Sie sie über die Attribute `href` und `id` sowie die Klasse `tracy-toggle`: ```latte -<a href="#tracy-addons-className-{$counter}" class="tracy-toggle">Detail</a> +<a href="#tracy-addons-Klassenname-{$counter}" class="tracy-toggle">Details</a> -<div id="tracy-addons-className-{$counter}">...</div> +<div id="tracy-addons-Klassenname-{$counter}">...</div> ``` -Wenn der Standardzustand eingeklappt ist, fügen Sie die Klasse `tracy-collapsed` zu beiden Elementen hinzu. +Wenn der Standardzustand eingeklappt sein soll, fügen Sie beiden Elementen die Klasse `tracy-collapsed` hinzu. -Verwenden Sie einen statischen Zähler, um doppelte IDs auf einer Seite zu vermeiden. +Verwenden Sie einen statischen Zähler, damit keine doppelten IDs auf derselben Seite erstellt werden. -Bluescreen-Erweiterungen .[#toc-bluescreen-extensions] -====================================================== +Erweiterungen für den Bluescreen +================================ -Sie können Ihre eigenen Ausnahmevisualisierungen oder Panels hinzufügen, die auf dem Bluescreen erscheinen. +Auf diese Weise können benutzerdefinierte Visualisierungen von Ausnahmen oder Panels hinzugefügt werden, die im Bluescreen angezeigt werden. -Die Erweiterung ist wie folgt aufgebaut: +Die Erweiterung wird mit diesem Befehl erstellt: ```php -Tracy\Debugger::getBlueScreen()->addPanel(function (?Throwable $e) { // catched exception +Tracy\Debugger::getBlueScreen()->addPanel(function (?Throwable $e) { // abgefangene Ausnahme return [ - 'tab' => '...Title...', - 'panel' => '...content...', + 'tab' => '...Beschriftung...', + 'panel' => '...HTML-Code des Panels...', ]; }); ``` -Die Funktion wird zweimal aufgerufen, zuerst wird die Ausnahme selbst im Parameter `$e` übergeben und das zurückgegebene Panel wird am Anfang der Seite gerendert. Wenn nichts zurückgegeben wird, wird das Panel nicht gerendert. Dann wird die Funktion mit dem Parameter `null` aufgerufen und das zurückgegebene Panel wird unterhalb des Aufrufstapels gerendert. Wenn die Funktion `'bottom' => true` im Array zurückgibt, wird das Panel ganz unten gerendert. +Die Funktion wird zweimal aufgerufen. Zuerst wird im Parameter `$e` die Ausnahme selbst übergeben, und das zurückgegebene Panel wird am Anfang der Seite gerendert. Wenn nichts zurückgegeben wird, wird das Panel nicht gerendert. Danach wird sie mit dem Parameter `null` aufgerufen, und das zurückgegebene Panel wird unter dem Callstack gerendert. Wenn die Funktion im Array den Schlüssel `'bottom' => true` zurückgibt, wird das Panel ganz unten gerendert. diff --git a/tracy/de/guide.texy b/tracy/de/guide.texy index 453e31a580..3dc5397886 100644 --- a/tracy/de/guide.texy +++ b/tracy/de/guide.texy @@ -3,59 +3,58 @@ Erste Schritte mit Tracy <div class=perex> -Die Tracy-Bibliothek ist ein nützliches Hilfsmittel für PHP-Programmierer. Sie hilft Ihnen dabei: +Die Bibliothek Tracy ist eine nützliche tägliche Helferin für PHP-Programmierer. Sie hilft Ihnen: -- Fehler schnell zu erkennen und zu korrigieren +- Fehler schnell zu erkennen und zu beheben - Fehler zu protokollieren -- Variablen auszulagern -- die Ausführungszeit von Skripten/Abfragen zu messen -- Speicherverbrauch anzeigen +- Variablen auszugeben +- die Ausführungszeit von Skripten und Datenbankabfragen zu messen +- den Speicherbedarf zu überwachen </div> -PHP ist eine perfekte Sprache, um kaum erkennbare Fehler zu machen, weil sie Programmierern große Flexibilität bietet. Tracy\Debugger ist deshalb besonders wertvoll. Er ist ein ultimatives Werkzeug unter den Diagnosewerkzeugen. +PHP ist eine Sprache, die wie geschaffen ist, um schwer aufzudeckende Fehler zu produzieren, da sie Entwicklern erhebliche Freiheit lässt. Umso wertvoller ist das Debugging-Werkzeug Tracy. Unter den Diagnosewerkzeugen für PHP stellt es die absolute Spitze dar. -Wenn Sie Tracy zum ersten Mal treffen, glauben Sie mir, Ihr Leben wird sich in ein Leben vor Tracy und ein Leben mit ihr aufteilen. Willkommen zum guten Teil! +Wenn Sie heute zum ersten Mal auf Tracy treffen, dann glauben Sie mir, Ihr Leben wird sich in das vor Tracy und das mit ihr teilen. Willkommen im besseren Teil! -Installation und Anforderungen .[#toc-installation-and-requirements] -==================================================================== +Installation +============ -Der beste Weg, Tracy zu installieren, ist, [das neueste Paket herunterzuladen](https://github.com/nette/tracy/releases) oder Composer zu verwenden: +Der beste Weg, Tracy zu installieren, ist [das neueste Paket herunterzuladen |https://github.com/nette/tracy/releases] oder Composer zu verwenden: ```shell composer require tracy/tracy ``` -Alternativ können Sie auch das gesamte Paket oder die Datei [tracy.phar |https://github.com/nette/tracy/releases] herunterladen. +Sie können auch das gesamte Paket als Datei [tracy.phar |https://github.com/nette/tracy/releases] herunterladen. -Verwendung .[#toc-usage] -======================== +Verwendung +========== -Tracy wird durch den Aufruf der Methode `Tracy\Debugger::enable()' so früh wie möglich zu Beginn des Programms aktiviert, bevor irgendeine Ausgabe gesendet wird: +Tracy aktivieren wir durch Aufruf der Methode `Tracy\Debugger::enable()` so früh wie möglich am Anfang des Programms, bevor irgendeine Ausgabe gesendet wird: ```php use Tracy\Debugger; -require 'vendor/autoload.php'; // alternativ tracy.phar +require 'vendor/autoload.php'; // oder tracy.phar Debugger::enable(); ``` -Das erste, was Ihnen auf der Seite auffällt, ist die Tracy-Leiste in der unteren rechten Ecke. Wenn Sie sie nicht sehen, kann das bedeuten, dass Tracy im Produktionsmodus läuft. -Das liegt daran, dass Tracy aus Sicherheitsgründen nur auf localhost sichtbar ist. Um zu testen, ob es funktioniert, können Sie es mit dem Parameter `Debugger::enable(Debugger::Development)` vorübergehend in den Entwicklungsmodus versetzen. +Das Erste, was Ihnen auf der Seite auffallen wird, ist die Tracy Bar in der rechten unteren Ecke. Wenn Sie sie nicht sehen, kann das bedeuten, dass Tracy im Produktionsmodus läuft. Tracy ist nämlich aus Sicherheitsgründen nur auf Localhost sichtbar. Um zu testen, ob es funktioniert, können Sie es vorübergehend in den Entwicklungsmodus umschalten, indem Sie den Parameter `Debugger::enable(Debugger::Development)` verwenden. -Tracy Bar .[#toc-tracy-bar] -=========================== +Tracy Bar +========= -Die Tracy-Leiste ist ein schwebendes Panel. Sie wird in der rechten unteren Ecke einer Seite angezeigt. Sie können sie mit der Maus verschieben. Sie merkt sich ihre Position nach dem Neuladen der Seite. +Die Tracy Bar ist ein schwebendes Panel, das in der rechten unteren Ecke der Seite angezeigt wird. Wir können es mit der Maus verschieben, und nach dem Neuladen der Seite merkt es sich seine Position. [* tracy-bar.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html -Sie können der Tracy Bar weitere nützliche Panels hinzufügen. Sie können interessante in [Addons |https://componette.org] finden oder [Ihre eigenen erstellen |extensions]. +Zur Tracy Bar können weitere nützliche Panels hinzugefügt werden. Viele davon finden Sie in den [Add-ons |https://componette.org], oder Sie [können Ihre eigenen schreiben |extensions]. Wenn Sie die Tracy Bar nicht anzeigen möchten, setzen Sie: @@ -64,150 +63,150 @@ Debugger::$showBar = false; ``` -Visualisierung von Fehlern und Ausnahmen .[#toc-visualization-of-errors-and-exceptions] -======================================================================================= +Visualisierung von Fehlern und Ausnahmen +======================================== -Sicherlich wissen Sie, wie PHP Fehler meldet: Im Quellcode der Seite steht etwas Ähnliches: +Sie wissen sicher gut, wie PHP Fehler meldet: Es gibt etwas Ähnliches in den Quellcode der Seite aus: /--pre .{font-size: 90%} <b>Parse error</b>: syntax error, unexpected '}' in <b>HomePresenter.php</b> on line <b>15</b> \-- -oder nicht gefangene Ausnahme: +oder bei einer nicht abgefangenen Ausnahme: /--pre .{font-size: 90%} <b>Fatal error</b>: Uncaught Nette\MemberAccessException: Call to undefined method Nette\Application\UI\Form::addTest()? in /sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php:100 Stack trace: #0 /sandbox/vendor/nette/utils/src/Utils/Object.php(75): Nette\Utils\ObjectMixin::call(Object(Nette\Application\UI\Form), 'addTest', Array) -#1 /sandbox/app/forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) -#2 /sandbox/app/presenters/SignPresenter.php(21): App\Forms\SignFormFactory->create() -#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presenters\SignPresenter->createComponentSignInForm('signInForm') +#1 /sandbox/app/Forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) +#2 /sandbox/app/Presentation/Sign/SignPresenter.php(21): App\Forms\SignFormFactory->create() +#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presentation\Sign\SignPresenter->createComponentSignInForm('signInForm') #4 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(139): Nette\ComponentModel\Container->createComponent('signInForm') #5 /sandbox/temp/cache/latte/15206b353f351f6bfca2c36cc.php(17): Nette\ComponentModel\Co in <b>/sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php</b> on line <b>100</b><br /> \-- -Es ist nicht so einfach, durch diese Ausgabe zu navigieren. Wenn Sie Tracy aktivieren, werden sowohl Fehler als auch Ausnahmen in einer völlig anderen Form angezeigt: +Sich in einer solchen Ausgabe zu orientieren, ist nicht gerade einfach. Wenn wir Tracy einschalten, werden Fehler oder Ausnahmen in einer völlig anderen Form angezeigt: [* tracy-exception.webp .{url:-} *] -Die Fehlermeldungen schreien förmlich. Sie können einen Teil des Quellcodes mit der hervorgehobenen Zeile sehen, in der der Fehler aufgetreten ist. Eine Meldung erklärt den Fehler deutlich. Die gesamte Website ist [interaktiv, versuchen Sie es](https://nette.github.io/tracy/tracy-exception.html). +Die Fehlermeldung schreit förmlich. Wir sehen einen Teil des Quellcodes mit der hervorgehobenen Zeile, in der der Fehler aufgetreten ist, und die Information *Call to undefined method Nette\Http\User::isLogedIn()* erklärt verständlich, um welchen Fehler es sich handelt. Die gesamte Seite ist außerdem interaktiv, wir können uns zu weiteren Details durchklicken. [Probieren Sie es aus |https://nette.github.io/tracy/tracy-exception.html]. -Und wissen Sie was? Fatale Fehler werden auf die gleiche Weise erfasst und angezeigt. Es ist nicht nötig, eine Erweiterung zu installieren (klicken Sie für ein Live-Beispiel): +Und wissen Sie was? Auf diese Weise fängt und zeigt es auch fatale Fehler an. Ohne die Notwendigkeit, irgendeine Erweiterung zu installieren. [* tracy-error.webp .{url:-} *] -Fehler wie ein Tippfehler in einem Variablennamen oder der Versuch, eine nicht existierende Datei zu öffnen, erzeugen Berichte der Stufe E_NOTICE oder E_WARNING. Diese können leicht übersehen werden und/oder in einem grafischen Layout einer Webseite völlig versteckt sein. Überlassen Sie Tracy die Verwaltung dieser Meldungen: +Fehler wie ein Tippfehler im Variablennamen oder der Versuch, eine nicht existierende Datei zu öffnen, erzeugen Meldungen der Ebene E_NOTICE oder E_WARNING. Diese können in der Seitengrafik leicht übersehen werden, oder sie sind möglicherweise gar nicht sichtbar (außer durch einen Blick in den Seitencode). [* tracy-notice2.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html Oder sie können wie Fehler angezeigt werden: ```php -Debugger::$strictMode = true; // display all errors -Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // all errors except deprecated notices +Debugger::$strictMode = true; // alle Fehler anzeigen +Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // alle Fehler außer Deprecation-Meldungen ``` [* tracy-notice.webp .{url:-} *] -Hinweis: Wenn Tracy aktiviert ist, wird die Fehlerberichtsebene auf E_ALL geändert. Wenn Sie dies ändern wollen, tun Sie dies nach dem Aufruf von `enable()`. +Anmerkung: Tracy ändert nach der Aktivierung die Fehlerberichterstattungsebene auf E_ALL. Wenn Sie diesen Wert ändern möchten, tun Sie dies nach dem Aufruf von `enable()`. -Entwicklungs- vs. Produktionsmodus .[#toc-development-vs-production-mode] -========================================================================= +Entwicklungs- vs. Produktionsmodus +================================== -Wie Sie sehen können, ist Tracy recht gesprächig, was in der Entwicklungsumgebung durchaus zu begrüßen ist, während es auf dem Produktionsserver zu einer Katastrophe führen würde. Das liegt daran, dass dort keine Debugging-Informationen angezeigt werden sollten. Tracy verfügt daher über eine **Umgebungserkennung**, und wenn das Beispiel auf einem Live-Server ausgeführt wird, wird der Fehler protokolliert und nicht angezeigt, und der Besucher sieht nur eine benutzerfreundliche Meldung: +Wie Sie sehen, ist Tracy ziemlich gesprächig, was in einer Entwicklungsumgebung geschätzt werden kann, während es auf einem Produktionsserver ein regelrechtes Unglück verursachen würde. Dort dürfen nämlich keine Debugging-Informationen ausgegeben werden. Tracy verfügt daher über eine **automatische Umgebungserkennung**, und wenn das Beispiel auf einem Live-Server ausgeführt wird, wird der Fehler statt angezeigt zu werden protokolliert, und der Besucher sieht nur eine benutzerfreundliche Meldung: [* tracy-error2.webp .{url:-} *] -Der Produktionsmodus unterdrückt die Anzeige aller Debugging-Informationen, die mit [dump() |dumper] gesendet werden, und natürlich auch alle von PHP generierten Fehlermeldungen. Wenn Sie also einige `dump($obj)` im Code vergessen haben, brauchen Sie sich keine Sorgen zu machen, auf dem Produktionsserver wird nichts angezeigt. +Der Produktionsmodus unterdrückt die Anzeige aller Debugging-Informationen, die wir mit [dump() |dumper] ausgeben, und natürlich auch aller Fehlermeldungen, die PHP generiert. Wenn Sie also irgendwo im Code ein `dump($obj)` vergessen haben, brauchen Sie sich keine Sorgen zu machen, auf dem Produktionsserver wird nichts ausgegeben. -Wie funktioniert die automatische Modus-Erkennung? Der Modus ist Entwicklung, wenn die Anwendung auf localhost läuft (d.h. IP-Adresse `127.0.0.1` oder `::1`) und kein Proxy vorhanden ist (d.h. sein HTTP-Header). Andernfalls läuft sie im Produktionsmodus. +Wie funktioniert die automatische Moduserkennung? Der Modus ist Entwicklung, wenn die Anwendung auf Localhost ausgeführt wird (d.h. IP-Adresse `127.0.0.1` oder `::1`) und kein Proxy vorhanden ist (d.h. sein HTTP-Header). Andernfalls läuft sie im Produktionsmodus. -Wenn Sie den Entwicklungsmodus in anderen Fällen aktivieren möchten, z. B. für Entwickler, die von einer bestimmten IP-Adresse aus zugreifen, können Sie ihn als Parameter der Methode `enable()` angeben: +Wenn wir den Entwicklungsmodus auch in anderen Fällen aktivieren möchten, beispielsweise für Programmierer, die von einer bestimmten IP-Adresse zugreifen, geben wir diese als Parameter der Methode `enable()` an: ```php -Debugger::enable('23.75.345.200'); // Sie können auch eine Reihe von IP-Adressen angeben +Debugger::enable('23.75.345.200'); // ein Array von IP-Adressen kann ebenfalls angegeben werden ``` -Wir empfehlen unbedingt, die IP-Adresse mit einem Cookie zu kombinieren. Speichern Sie ein geheimes Token, z.B. `secret1234`, im Cookie `tracy-debug`, und aktivieren Sie auf diese Weise den Entwicklungsmodus nur für Entwickler, die von einer bestimmten IP-Adresse aus zugreifen und das genannte Token im Cookie haben: +Wir empfehlen auf jeden Fall, die IP-Adresse mit einem Cookie zu kombinieren. Im Cookie `tracy-debug` speichern wir ein geheimes Token, z.B. `secret1234`, und aktivieren auf diese Weise den Entwicklungsmodus nur für Programmierer, die von einer bestimmten IP-Adresse zugreifen und das erwähnte Token im Cookie haben: ```php Debugger::enable('secret1234@23.75.345.200'); ``` -Sie können den Entwicklungs-/Produktionsmodus auch direkt mit den Konstanten `Debugger::Development` oder `Debugger::Production` als Parameter der Methode `enable()` einstellen. +Wir können den Entwicklungs-/Produktionsmodus auch direkt durch Verwendung der Konstanten `Debugger::Development` oder `Debugger::Production` als Parameter der Methode `enable()` einstellen. .[note] -Wenn Sie das Nette Framework verwenden, sehen Sie sich an, wie Sie [den Modus dafür einstellen |application:bootstrap#Development vs Production Mode], der dann auch für Tracy verwendet wird. +Wenn Sie das Nette Framework verwenden, sehen Sie nach, wie Sie [den Modus dafür einstellen |application:bootstrapping#Entwicklungs- vs. Produktionsmodus], und dieser wird dann auch für Tracy verwendet. -Fehlerprotokollierung .[#toc-error-logging] -=========================================== +Fehlerprotokollierung +===================== -Im Produktionsmodus protokolliert Tracy automatisch alle Fehler und Ausnahmen in einem Textprotokoll. Damit die Protokollierung erfolgt, müssen Sie den absoluten Pfad zum Protokollverzeichnis in der Variablen `$logDirectory` angeben oder ihn als zweiten Parameter an die Methode `enable()` übergeben: +Im Produktionsmodus protokolliert Tracy automatisch alle Fehler und abgefangenen Ausnahmen in einem Textprotokoll. Damit die Protokollierung stattfinden kann, müssen wir den absoluten Pfad zum Protokollverzeichnis in der Variablen `$logDirectory` festlegen oder als zweiten Parameter der Methode `enable()` übergeben: ```php Debugger::$logDirectory = __DIR__ . '/log'; ``` -Die Fehlerprotokollierung ist äußerst nützlich. Stellen Sie sich vor, dass alle Benutzer Ihrer Anwendung eigentlich Betatester sind, die kostenlos erstklassige Arbeit bei der Fehlersuche leisten, und Sie wären dumm, ihre wertvollen Berichte unbemerkt in den Mülleimer zu werfen. +Die Fehlerprotokollierung ist dabei äußerst nützlich. Stellen Sie sich vor, alle Benutzer Ihrer Anwendung sind eigentlich Betatester, die kostenlos Spitzenarbeit bei der Fehlersuche leisten, und Sie wären dumm, ihre wertvollen Berichte unbeachtet in den Mülleimer zu werfen. -Wenn Sie Ihre eigenen Meldungen oder abgefangenen Ausnahmen protokollieren müssen, verwenden Sie die Methode `log()`: +Wenn wir eine eigene Nachricht oder eine von Ihnen abgefangene Ausnahme protokollieren müssen, verwenden wir dazu die Methode `log()`: ```php -Debugger::log('Unexpected error'); // text message +Debugger::log('Ein unerwarteter Fehler ist aufgetreten'); // Textnachricht try { - criticalOperation(); + kritischeOperation(); } catch (Exception $e) { - Debugger::log($e); // log exception - // or - Debugger::log($e, Debugger::ERROR); // also sends an email notification + Debugger::log($e); // auch eine Ausnahme kann protokolliert werden + // oder + Debugger::log($e, Debugger::ERROR); // sendet auch eine E-Mail-Benachrichtigung } ``` -If you want Tracy to log PHP errors like `E_NOTICE` or `E_WARNING` with detailed information (HTML report), set `Debugger::$logSeverity`: +Wenn Sie möchten, dass Tracy PHP-Fehler wie `E_NOTICE` oder `E_WARNING` mit detaillierten Informationen (HTML-Bericht) protokolliert, setzen Sie `Debugger::$logSeverity`: ```php Debugger::$logSeverity = E_NOTICE | E_WARNING; ``` -Für einen echten Profi ist das Fehlerprotokoll eine wichtige Informationsquelle, und er oder sie möchte über jeden neuen Fehler sofort informiert werden. Tracy hilft ihm dabei. Sie ist in der Lage, für jeden neuen Fehlereintrag eine E-Mail zu versenden. Die Variable $email gibt an, wohin diese E-Mails zu senden sind: +Für einen echten Profi ist das Fehlerprotokoll eine wichtige Informationsquelle, und er möchte sofort über jeden neuen Fehler informiert werden. Tracy kommt ihm dabei entgegen, sie kann nämlich über einen neuen Eintrag im Protokoll per E-Mail informieren. Wohin E-Mails gesendet werden sollen, bestimmen wir mit der Variablen `$email`: ```php Debugger::$email = 'admin@example.com'; ``` -Wenn Sie das gesamte Nette Framework verwenden, können Sie diese und andere Einstellungen in der [Konfigurationsdatei |nette:configuring] vornehmen. +Wenn Sie das gesamte Nette Framework verwenden, können Sie dies und Weiteres in der [Konfigurationsdatei |nette:configuring] einstellen. -Um Ihr E-Mail-Postfach vor Überflutung zu schützen, sendet Tracy **nur eine Nachricht** und erstellt eine Datei `email-sent`. Wenn ein Entwickler die E-Mail-Benachrichtigung erhält, überprüft er das Protokoll, korrigiert seine Anwendung und löscht die Überwachungsdatei `email-sent`. Dadurch wird der E-Mail-Versand wieder aktiviert. +Damit Ihr E-Mail-Postfach jedoch nicht überflutet wird, sendet sie immer **nur eine Nachricht** und erstellt die Datei `email-sent`. Der Entwickler überprüft nach Erhalt der E-Mail-Benachrichtigung das Protokoll, korrigiert die Anwendung und löscht die Überwachungsdatei, wodurch der E-Mail-Versand wieder aktiviert wird. -Öffnen von Dateien im Editor .[#toc-opening-files-in-the-editor] -================================================================ +Öffnen im Editor +================ -Wenn die Fehlerseite angezeigt wird, können Sie auf Dateinamen klicken und sie werden in Ihrem Editor geöffnet, wobei der Cursor auf der entsprechenden Zeile steht. Es können auch Dateien erstellt werden (Aktion `create file`) oder Fehler in ihnen behoben werden (Aktion `fix it`). Hierfür müssen Sie [den Browser und das System konfigurieren |open-files-in-ide]. +Bei der Anzeige der Fehlerseite können Sie auf Dateinamen klicken, und diese öffnen sich in Ihrem Editor mit dem Cursor in der entsprechenden Zeile. Es ist auch möglich, Dateien zu erstellen (Aktion `create file`) oder Fehler darin zu korrigieren (Aktion `fix it`). Damit dies funktioniert, müssen Sie nur [den Browser und das System konfigurieren |open-files-in-ide]. -Unterstützte PHP-Versionen .[#toc-supported-php-versions] -========================================================= +Unterstützte PHP-Versionen +========================== -| Tracy ist kompatibel mit PHP -|-----------|-------------------- -| Tracy 2.10 – 3.0 | PHP 8.0 - 8.2 -| Tracy 2.9 | PHP 7.2 - 8.2 -| Tracy 2.8 | PHP 7.2 - 8.1 -| Tracy 2.6 - 2.7 | PHP 7.1 - 8.0 -| Tracy 2.5 | PHP 5.4 - 7.4 -| Tracy 2.4 | PHP 5.4 - 7.2 +| Tracy | kompatibel mit PHP +|-----------|------------------- +| Tracy 2.10 – 3.0 | PHP 8.0 – 8.4 +| Tracy 2.9 | PHP 7.2 – 8.2 +| Tracy 2.8 | PHP 7.2 – 8.1 +| Tracy 2.6 – 2.7 | PHP 7.1 – 8.0 +| Tracy 2.5 | PHP 5.4 – 7.4 +| Tracy 2.4 | PHP 5.4 – 7.2 -Gilt für die neuesten Patch-Versionen. +Gilt für die letzte Patch-Version. -Ports .[#toc-ports] -=================== +Ports +===== -Dies ist eine Liste von inoffiziellen Portierungen auf andere Frameworks und CMS: +Dies ist eine Liste inoffizieller Ports für andere Frameworks und CMS: - [Drupal 7](https://www.drupal.org/project/traced) - Laravel framework: [recca0120/laravel-tracy](https://github.com/recca0120/laravel-tracy), [whipsterCZ/laravel-tracy](https://github.com/whipsterCZ/laravel-tracy) diff --git a/tracy/de/open-files-in-ide.texy b/tracy/de/open-files-in-ide.texy index cb31a42091..d53b6822be 100644 --- a/tracy/de/open-files-in-ide.texy +++ b/tracy/de/open-files-in-ide.texy @@ -1,20 +1,20 @@ -Wie öffnet man eine Datei im Editor von Tracy aus? (IDE-Integration) +Wie öffnet man eine Datei aus Tracy im Editor? (Integration mit IDE) ******************************************************************** .[perex] -Wenn die Fehlerseite angezeigt wird, können Sie auf Dateinamen klicken und sie werden in Ihrem Editor geöffnet, wobei der Cursor auf der entsprechenden Zeile steht. Es können auch Dateien erstellt werden (Aktion `create file`) oder Fehler in ihnen behoben werden (Aktion `fix it`). Hierfür müssen Sie den Browser und das System konfigurieren. +Bei der Anzeige der Fehlerseite können Sie auf Dateinamen klicken, und diese öffnen sich in Ihrem Editor mit dem Cursor in der entsprechenden Zeile. Es ist auch möglich, Dateien zu erstellen (Aktion `create file`) oder Fehler darin zu korrigieren (Aktion `fix it`). Dazu müssen Browser und System konfiguriert werden. -Tracy öffnet Dateien über URLs der Form `editor://open/?file=%file&line=%line`, d.h. mit dem Protokoll `editor://`. Hierfür werden wir einen eigenen Handler registrieren. Dies kann eine beliebige ausführbare Datei sein, die die Parameter verarbeitet und unseren Lieblingseditor startet. +Tracy öffnet Dateien über URLs der Form `editor://open/?file=%file&line=%line`, d.h. mit dem Protokoll `editor://`. Dafür registrieren wir einen eigenen Handler. Dies kann jede ausführbare Datei sein, die die Parameter verarbeitet und unseren bevorzugten Editor startet. -Sie können die URL in der Variablen `Tracy\Debugger::$editor` ändern oder den Click-through durch Setzen von `Tracy\Debugger::$editor = null` deaktivieren. +Die URL können Sie in der Variablen `Tracy\Debugger::$editor` ändern oder das Durchklicken durch Setzen von `Tracy\Debugger::$editor = null` deaktivieren. -Windows .[#toc-windows] -======================= +Windows +======= -1. Laden Sie die entsprechenden Dateien "aus dem Tracy-Repository":https://github.com/nette/tracy/tree/master/tools/open-in-editor/windows auf die Festplatte herunter. +1. Laden Sie die entsprechenden Dateien aus dem "Tracy-Repository":https://github.com/nette/tracy/tree/master/tools/open-in-editor/windows auf Ihre Festplatte herunter. -2. Bearbeiten Sie `open-editor.js` und entfernen Sie die Kommentare oder bearbeiten Sie den Pfad zu Ihrem Editor in `settings`: +2. Bearbeiten Sie die Datei `open-editor.js` und kommentieren Sie im Array `settings` den Pfad zu Ihrem Editor aus und passen Sie ihn gegebenenfalls an: ```js var settings = { @@ -35,19 +35,28 @@ var settings = { ... ``` -Seien Sie vorsichtig und behalten Sie die doppelten Schrägstriche in den Pfaden bei. +Achtung, behalten Sie die doppelten Schrägstriche in den Pfaden bei. 3. Registrieren Sie den Handler für das `editor://` Protokoll im System. -Dazu führen Sie `install.cmd` aus. **Sie müssen es als Administrator ausführen**. Das Skript `open-editor.js` bedient nun das Protokoll `editor://`. +Dies tun Sie durch Ausführen der Datei `install.cmd`. **Es muss als Administrator ausgeführt werden.** Das Skript `open-editor.js` wird nun das Protokoll `editor://` behandeln. +Um Links öffnen zu können, die auf anderen Servern generiert wurden, wie z.B. auf einem Live-Server oder in Docker, fügen Sie in `open-editor.js` noch das Mapping von Remote-URLs zu lokalen URLs hinzu: -Linux .[#toc-linux] -=================== +```js + mappings: { + // Remote-Pfad: lokaler Pfad + '/var/www/nette.app': 'W:\\Nette.web\\_web', + } +``` + + +Linux +===== -1. Laden Sie die entsprechenden Dateien "aus dem Tracy-Repository":https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux in das Verzeichnis `~/bin` herunter. +1. Laden Sie die entsprechenden Dateien aus dem "Tracy-Repository":https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux in das Verzeichnis `~/bin` herunter. -2. Bearbeiten Sie `open-editor.sh` und entfernen Sie die Kommentare oder ändern Sie den Pfad zu Ihrem Editor in der Variablen `editor`: +2. Bearbeiten Sie die Datei `open-editor.sh` und kommentieren Sie den Pfad zu Ihrem Editor in der Variablen `editor` aus und passen Sie ihn gegebenenfalls an. ```shell #!/bin/bash @@ -67,24 +76,25 @@ Linux .[#toc-linux] ... ``` -Machen Sie ihn ausführbar: +Machen Sie die Datei ausführbar: ```shell chmod +x ~/bin/open-editor.sh ``` -Wenn der Editor, den Sie verwenden, nicht aus dem Paket installiert ist, hat die Binärdatei wahrscheinlich keinen Pfad in `$PATH`. Dies kann leicht korrigiert werden. Erstellen Sie im Verzeichnis `~/bin` einen Symlink auf die Editor-Binärdatei. .[note] +.[note] +Wenn der verwendete Editor nicht aus einem Paket installiert wurde, hat die Binärdatei wahrscheinlich keinen Pfad in `$PATH`. Das lässt sich leicht beheben. Erstellen Sie im Verzeichnis `~/bin` einen Symlink zur Editor-Binärdatei. 3. Registrieren Sie den Handler für das `editor://` Protokoll im System. -Dazu führen Sie `install.sh` aus. Das Skript `open-editor.js` wird nun das Protokoll `editor://` bedienen. +Dies tun Sie durch Ausführen der Datei `install.sh`. Das Skript `open-editor.sh` wird nun das Protokoll `editor://` behandeln. -macOS .[#toc-macos] -=================== +macOS +===== -Editoren wie PhpStorm, TextMate usw. ermöglichen es Ihnen, Dateien über eine spezielle URL zu öffnen, die Sie nur angeben müssen: +Editoren wie PhpStorm, TextMate usw. ermöglichen das Öffnen von Dateien über eine spezielle URL, die nur eingestellt werden muss: ```php // PhpStorm @@ -92,44 +102,43 @@ Tracy\Debugger::$editor = 'phpstorm://open?file=%file&line=%line'; // TextMate Tracy\Debugger::$editor = 'txmt://open/?url=file://%file&line=%line'; // MacVim -Tracy\Debugger::$editor = 'mvim://open/?url=file://%file&line=%line'; +Tracy\Debugger::$editor = 'mvim://open?url=file:///%file&line=%line'; // Visual Studio Code Tracy\Debugger::$editor = 'vscode://file/%file:%line'; ``` -Wenn Sie den Standalone-Editor Tracy verwenden, fügen Sie die Zeile vor `Tracy\Debugger::enable()` ein, wenn Sie Nette verwenden, vor `$configurator->enableTracy()` in `Bootstrap.php`. +Wenn Sie Tracy eigenständig verwenden, fügen Sie die Zeile vor `Tracy\Debugger::enable()` ein, wenn Sie Nette verwenden, dann vor `$configurator->enableTracy()` in `Bootstrap.php`. -Leider funktionieren die Aktionen `create file` oder `fix it` nicht unter macOS. +Die Aktionen `create file` oder `fix it` funktionieren leider unter macOS nicht. -Demos .[#toc-demos] -=================== +Beispiele +========= -Fehler behoben: +Fehlerbehebung: <iframe width="560" height="315" src="https://www.youtube.com/embed/3ITT4mC0Eq4?rel=0&showinfo=0" frameborder="0" allow="encrypted-media" allowfullscreen></iframe> -Erstellen einer neuen Datei: +Dateierstellung: <iframe width="560" height="315" src="https://www.youtube.com/embed/AJ_FUivAGZQ?rel=0&showinfo=0" frameborder="0" allow="encrypted-media" allowfullscreen></iframe> -Fehlersuche .[#toc-troubleshooting] -=================================== +Fehlerbehebung +============== -- In Firefox müssen Sie möglicherweise die Ausführung von benutzerdefinierten Protokollen in about:config [zulassen |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above], indem Sie `network.protocol-handler.expose.editor` auf `false` und `network.protocol-handler.expose-all` auf `true` setzen. Es sollte jedoch standardmäßig zugelassen sein. -- Wenn nicht sofort alles funktioniert, geraten Sie nicht in Panik. Versuchen Sie, die Seite zu aktualisieren, den Browser oder den Computer neu zu starten. Das sollte helfen. -- Siehe [hier |https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/] zur Behebung: - Eingabefehler: Es gibt keine Skript-Engine für die Dateierweiterung ".js" Vielleicht haben Sie die ".js"-Datei mit einer anderen Anwendung verknüpft, nicht mit der JScript-Engine. +- In Firefox muss das Protokoll möglicherweise durch [Einstellung |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] von `network.protocol-handler.expose.editor` auf `false` und `network.protocol-handler.expose-all` auf `true` in about:config aktiviert werden. +- Wenn es nicht sofort funktioniert, keine Panik und versuchen Sie, die Seite ein paar Mal zu aktualisieren, bevor Sie auf den Link klicken. Es wird losgehen! +- Hier ist ein [Link |https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/] zur Behebung eines möglichen Fehlers: `Input Error: There is no script engine for file extension ".js"`, `Maybe you associated ".js" file to another app, not JScript engine.` bzw. `für die Dateiendung .js ist keine Skript-Engine verfügbar`. -Ab Google Chrome Version 77 wird das Kontrollkästchen "Diese Art von Links immer in der zugehörigen App öffnen" nicht mehr angezeigt, wenn der Editor über einen Link geöffnet wird. Abhilfe für Windows: Erstellen Sie die Datei `fix.reg`: +In Google Chrome ab Version 77 sehen Sie das Kontrollkästchen „Diesen Linktyp immer in der zugehörigen Anwendung öffnen“ nicht mehr, wenn der Editor über einen Link gestartet wird. Lösung für Windows: Erstellen Sie eine Datei `fix.reg`: ``` Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\URLWhitelist] "123"="editor://*" ``` -Importieren Sie sie per Doppelklick und starten Sie Chrome neu. +Importieren Sie sie durch Doppelklick und starten Sie Chrome neu. -Falls Sie weitere Probleme oder Fragen haben, fragen Sie im [Forum |https://forum.nette.org]. +Bei Fragen oder Anmerkungen wenden Sie sich bitte an das [Forum |https://forum.nette.org]. diff --git a/tracy/de/recipes.texy b/tracy/de/recipes.texy index 497f2e82c6..561a09fd50 100644 --- a/tracy/de/recipes.texy +++ b/tracy/de/recipes.texy @@ -1,14 +1,13 @@ -Rezepte -******* +Anleitungen +*********** -Sicherheitsrichtlinien für Inhalte .[#toc-content-security-policy] -================================================================== +Content Security Policy +======================= -Wenn Ihre Website die Content Security Policy verwendet, müssen Sie Folgendes hinzufügen `'nonce-<value>'` und `'strict-dynamic'` zu `script-src` hinzufügen, damit Tracy richtig funktioniert. Einige Plugins von Drittanbietern können zusätzliche Direktiven erfordern. -Nonce wird in der Direktive `style-src` nicht unterstützt. Wenn Sie diese Direktive verwenden, müssen Sie `'unsafe-inline'` hinzufügen, aber dies sollte im Produktionsmodus vermieden werden. +Wenn Ihre Website Content Security Policy verwendet, müssen Sie dieselbe `'nonce-<value>'` und `'strict-dynamic'` zu `script-src` hinzufügen, damit Tracy ordnungsgemäß funktioniert. Einige Add-ons von Drittanbietern erfordern möglicherweise zusätzliche Einstellungen. Nonce wird in der `style-src`-Direktive nicht unterstützt. Wenn Sie diese Direktive verwenden, müssen Sie `'unsafe-inline'` hinzufügen, sollten dies jedoch im Produktionsmodus vermeiden. -Konfigurationsbeispiel für [Nette Framework |nette:configuring]: +Konfigurationsbeispiel für das [Nette Framework |nette:configuring]: ```neon http: @@ -24,11 +23,10 @@ header("Content-Security-Policy: script-src 'nonce-$nonce' 'strict-dynamic';"); ``` -Schnelleres Laden .[#toc-faster-loading] -======================================== +Schnelleres Laden +================= -Die grundlegende Integration ist einfach, aber wenn Sie langsame blockierende Skripte in der Webseite haben, können diese das Laden des Tracy verlangsamen. -Die Lösung ist, dass Sie `<?php Tracy\Debugger::renderLoader() ?>` in Ihre Vorlage vor den Skripten einzufügen: +Der Start ist einfach, aber wenn Sie langsam ladende, blockierende Skripte auf Ihrer Webseite haben, können diese das Laden von Tracy verlangsamen. Die Lösung besteht darin, `<?php Tracy\Debugger::renderLoader() ?>` in Ihr Template vor allen Skripten zu platzieren: ```latte <!DOCTYPE html> @@ -42,12 +40,37 @@ Die Lösung ist, dass Sie `<?php Tracy\Debugger::renderLoader() ?>` in Ihre Vorl ``` -AJAX und umgeleitete Anfragen .[#toc-ajax-and-redirected-requests] -================================================================== +Debugging von AJAX-Anfragen +=========================== -Tracy kann Debug-Balken und Bluescreens für AJAX-Anfragen und Redirects anzeigen. Tracy erstellt seine eigenen Sitzungen, speichert Daten in seinen eigenen temporären Dateien und verwendet ein `tracy-session` Cookie. +Tracy fängt automatisch AJAX-Anfragen ab, die mit jQuery oder der nativen `fetch`-API erstellt wurden. Anfragen werden in der Tracy-Leiste als zusätzliche Zeilen angezeigt, was ein einfaches und bequemes AJAX-Debugging ermöglicht. -Tracy kann auch so konfiguriert werden, dass es eine native PHP-Sitzung verwendet, die gestartet wird, bevor Tracy eingeschaltet wird: +Wenn Sie AJAX-Anfragen nicht automatisch abfangen möchten, können Sie diese Funktion durch Setzen einer JavaScript-Variable deaktivieren: + +```js +window.TracyAutoRefresh = false; +``` + +Für die manuelle Überwachung spezifischer AJAX-Anfragen fügen Sie den HTTP-Header `X-Tracy-Ajax` mit dem Wert hinzu, den `Tracy.getAjaxHeader()` zurückgibt. Hier ist ein Beispiel für die Verwendung mit der `fetch`-Funktion: + +```js +fetch(url, { + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Tracy-Ajax': Tracy.getAjaxHeader(), + } +}) +``` + +Dieser Ansatz ermöglicht ein selektives Debugging von AJAX-Anfragen. + + +Datenspeicher +============= + +Tracy kann Panels in der Tracy Bar und Bluescreens für AJAX-Anfragen und Weiterleitungen anzeigen. Tracy erstellt eine eigene Session, speichert Daten in eigenen temporären Dateien und verwendet das Cookie `tracy-session`. + +Tracy kann auch so konfiguriert werden, dass es die native PHP-Session verwendet, die wir noch vor dem Einschalten von Tracy starten: ```php session_start(); @@ -55,44 +78,44 @@ Debugger::setSessionStorage(new Tracy\NativeSession); Debugger::enable(); ``` -Falls das Starten einer Sitzung eine komplexere Initialisierung erfordert, können Sie Tracy sofort starten (so dass es mit auftretenden Fehlern umgehen kann), dann den Sitzungshandler initialisieren und schließlich Tracy über die Funktion `dispatch()` mitteilen, dass die Sitzung zur Verwendung bereit ist: +Falls das Starten der Session eine komplexere Initialisierung erfordert, können Sie Tracy sofort starten (damit es eventuell auftretende Fehler verarbeiten kann), dann den Session-Handler initialisieren und schließlich Tracy über die Funktion `dispatch()` informieren, dass die Session einsatzbereit ist: ```php Debugger::setSessionStorage(new Tracy\NativeSession); Debugger::enable(); -// gefolgt von der Sitzungsinitialisierung -// und starten Sie die Sitzung +// hier folgt die Session-Initialisierung +// und der Start der Session session_start(); Debugger::dispatch(); ``` -Die Funktion `setSessionStorage()` gibt es seit Version 2.9, davor hat Tracy immer die native PHP-Sitzung verwendet. +Die Funktion `setSessionStorage()` existiert seit Version 2.9, davor hat Tracy immer die native PHP-Session verwendet. -Benutzerdefinierter Scrubber .[#toc-custom-scrubber] -==================================================== +Eigener Scrubber +================ -Scrubber ist ein Filter, der verhindert, dass sensible Daten wie Passwörter oder Anmeldedaten aus Dumps durchsickern. Der Filter wird für jedes Element des gedumpten Arrays oder Objekts aufgerufen und gibt `true` zurück, wenn der Wert sensibel ist. In diesem Fall wird `*****` anstelle des Wertes ausgegeben. +Ein Scrubber ist ein Filter, der das Durchsickern sensibler Daten beim Dumping verhindert, wie z. B. Passwörter oder Zugangsdaten. Der Filter wird für jedes Element des gedumpten Arrays oder Objekts aufgerufen und gibt `true` zurück, wenn der Wert sensibel ist. In diesem Fall wird anstelle des Wertes `*****` ausgegeben. ```php -// vermeidet das Dumping von Schlüsselwerten und Eigenschaften wie `Passwort`, -// Passwort_Wiederholung", "Passwort_Prüfung", "Datenbank-Passwort", usw. +// verhindert die Ausgabe von Werten von Schlüsseln und Eigenschaften wie `password`, +// `password_repeat`, `check_password`, `DATABASE_PASSWORD`, usw. $scrubber = function(string $key, $value, ?string $class): bool { return preg_match('#password#i', $key) && $value !== null; }; -// wir verwenden es für alle Dumps innerhalb von BlueScreen +// wir verwenden ihn für alle Dumps innerhalb des BlueScreen Tracy\Debugger::getBlueScreen()->scrubber = $scrubber; ``` -Benutzerdefinierter Logger .[#toc-custom-logger] -================================================ +Eigener Logger +============== -Wir können einen benutzerdefinierten Logger erstellen, der Fehler und nicht abgefangene Ausnahmen protokolliert und auch von `Tracy\Debugger::log()` aufgerufen werden kann. Logger implementiert die Schnittstelle [api:Tracy\ILogger]. +Wir können einen eigenen Logger erstellen, der Fehler und nicht abgefangene Ausnahmen protokolliert und auch von der Methode `Tracy\Debugger::log()` aufgerufen wird. Der Logger implementiert das Interface [api:Tracy\ILogger]. ```php use Tracy\ILogger; @@ -106,13 +129,13 @@ class SlackLogger implements ILogger } ``` -Und dann aktivieren wir ihn: +Und aktivieren ihn anschließend: ```php Tracy\Debugger::setLogger(new SlackLogger); ``` -Wenn wir das vollständige Nette Framework verwenden, können wir es in der NEON-Konfigurationsdatei einstellen: +Wenn wir das vollständige Nette Framework verwenden, können Sie es in der NEON-Konfigurationsdatei einstellen: ```neon services: @@ -120,8 +143,8 @@ services: ``` -Monolog-Integration .[#toc-monolog-integration] ------------------------------------------------ +Monolog-Integration +------------------- Das Tracy-Paket bietet einen PSR-3-Adapter, der die Integration von [monolog/monolog](https://github.com/Seldaek/monolog) ermöglicht. @@ -134,20 +157,20 @@ Debugger::setLogger($tracyLogger); Debugger::enable(); Debugger::log('info'); // schreibt: [<TIMESTAMP>] main-channel.INFO: info [] [] -Debugger::log('warning', Debugger::WARNING); // writes: [<TIMESTAMP>] main-channel.WARNING: warning [] [] +Debugger::log('warning', Debugger::WARNING); // schreibt: [<TIMESTAMP>] main-channel.WARNING: warning [] [] ``` -nginx .[#toc-nginx] -=================== +nginx +===== -Wenn Tracy unter nginx nicht funktioniert, ist es wahrscheinlich falsch konfiguriert. Wenn es etwas gibt wie +Wenn Tracy auf einem Nginx-Server nicht funktioniert, ist er wahrscheinlich falsch konfiguriert. Wenn in der Konfiguration etwas wie folgt steht: ```nginx try_files $uri $uri/ /index.php; ``` -ändern Sie es in +ändern Sie es in: ```nginx try_files $uri $uri/ /index.php$is_args$args; diff --git a/tracy/de/stopwatch.texy b/tracy/de/stopwatch.texy index 1d3cc8456b..d2c450f729 100644 --- a/tracy/de/stopwatch.texy +++ b/tracy/de/stopwatch.texy @@ -1,35 +1,35 @@ -Stoppuhr -******** +Zeitmessung +*********** -Ein weiteres nützliches Werkzeug ist die Debugger-Stoppuhr mit einer Genauigkeit von Mikrosekunden: +Ein weiteres nützliches Werkzeug für den Debugger ist eine Stoppuhr mit Mikrosekundengenauigkeit: ```php Debugger::timer(); -// sweet dreams my cherrie +// zeitaufwändige Operation... sleep(2); $elapsed = Debugger::timer(); // $elapsed = 2 ``` -Mit einem optionalen Parameter können mehrere Messungen auf einmal durchgeführt werden. +Mit einem optionalen Parameter können mehrere Messungen durchgeführt werden. ```php Debugger::timer('page-generating'); -// some code +// irgendein Code Debugger::timer('rss-generating'); -// some code +// irgendein Code $rssElapsed = Debugger::timer('rss-generating'); $pageElapsed = Debugger::timer('page-generating'); ``` ```php -Debugger::timer(); // runs the timer +Debugger::timer(); // startet die Stoppuhr -... // some time-consuming operation +... // zeitaufwändige Operation -echo Debugger::timer(); // elapsed time in seconds +echo Debugger::timer(); // gibt die verstrichene Zeit in Sekunden aus ``` diff --git a/tracy/el/@home.texy b/tracy/el/@home.texy index 5cee831230..77772058d0 100644 --- a/tracy/el/@home.texy +++ b/tracy/el/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: για όλους τους προγραμματιστές PHP}} -{{description: Το Tracy είναι ένα εργαλείο που έχει σχεδιαστεί για να διευκολύνει την αποσφαλμάτωση κώδικα PHP. Είναι ένας χρήσιμος βοηθός για όλους τους προγραμματιστές PHP, ο οποίος βοηθάει στη σαφή απεικόνιση και καταγραφή σφαλμάτων, στην απόρριψη μεταβλητών και σε πολλά άλλα. Προειδοποίηση: Το Tracy προκαλεί εθισμό!}} +{{maintitle: Tracy – το εργαλείο debugging που κάνει τα σφάλματα απόλαυση}} +{description: Το Tracy είναι ένα εργαλείο σχεδιασμένο για να διευκολύνει το debugging κώδικα PHP. Είναι ένας χρήσιμος βοηθός για όλους τους προγραμματιστές PHP, βοηθώντας τους με την οπτικοποίηση και την καταγραφή σφαλμάτων, το dumping μεταβλητών και πολλά άλλα. Προειδοποίηση: Το Tracy είναι εθιστικό!}} diff --git a/tracy/el/@left-menu.texy b/tracy/el/@left-menu.texy index d9cf49d0bb..43e56933f1 100644 --- a/tracy/el/@left-menu.texy +++ b/tracy/el/@left-menu.texy @@ -1,7 +1,7 @@ -- [Ξεκινώντας |Guide] -- [Απορριμματοφόρος |Dumper] -- [Χρονόμετρο |Stopwatch] -- [Διαμόρφωση |Configuring] -- [Συνταγές |Recipes] -- [Ενσωμάτωση IDE |open-files-in-ide] +- [Ξεκινώντας με το Tracy |guide] +- [Dumping |dumper] +- [Μέτρηση χρόνου |stopwatch] +- [Διαμόρφωση |configuring] +- [Οδηγοί |recipes] +- [Ενσωμάτωση με IDE |open-files-in-ide] - [Δημιουργία επεκτάσεων |extensions] diff --git a/tracy/el/@menu.texy b/tracy/el/@menu.texy index 64164552e6..aa4da2cedd 100644 --- a/tracy/el/@menu.texy +++ b/tracy/el/@menu.texy @@ -1,3 +1,3 @@ -- [Αρχική σελίδα |@home] -- [Τεκμηρίωση |Guide] +- [Εισαγωγή |@home] +- [Τεκμηρίωση |guide] - "GitHub .[link-external]":https://github.com/nette/tracy diff --git a/tracy/el/@meta.texy b/tracy/el/@meta.texy new file mode 100644 index 0000000000..93980491e0 --- /dev/null +++ b/tracy/el/@meta.texy @@ -0,0 +1 @@ +{{sitename: Tracy Τεκμηρίωση}} diff --git a/tracy/el/configuring.texy b/tracy/el/configuring.texy index 6e271fd4ba..b2c42e8d20 100644 --- a/tracy/el/configuring.texy +++ b/tracy/el/configuring.texy @@ -1,142 +1,141 @@ Διαμόρφωση Tracy **************** -Τα ακόλουθα παραδείγματα υποθέτουν ότι έχει οριστεί το ακόλουθο ψευδώνυμο κλάσης: +Όλα τα παραδείγματα προϋποθέτουν τη δημιουργία ενός ψευδώνυμου: ```php use Tracy\Debugger; ``` -Καταγραφή σφαλμάτων .[#toc-error-logging] ------------------------------------------ +Καταγραφή σφαλμάτων +------------------- ```php $logger = Debugger::getLogger(); -// εάν έχει συμβεί σφάλμα, η ειδοποίηση αποστέλλεται σε αυτό το email -$logger->email = 'dev@example.com'; // (string|string[]) προεπιλογή unset +// e-mail στο οποίο αποστέλλονται οι ειδοποιήσεις για την εμφάνιση σφάλματος +$logger->email = 'dev@example.com'; // (string|string[]) προεπιλογή είναι μη ορισμένο -// αποστολέας email -$logger->fromEmail = 'me@example.com'; // (string) προεπιλογή unset +// αποστολέας του e-mail +$logger->fromEmail = 'me@example.com'; // (string) προεπιλογή είναι μη ορισμένο -// ρουτίνα για την αποστολή email -$logger->mailer = /* ... */; // (callable) προεπιλογή αποστολής με mail() +// ρουτίνα που εξασφαλίζει την αποστολή του email +$logger->mailer = /* ... */; // (callable) προεπιλογή είναι η αποστολή με τη συνάρτηση mail() -// μετά από ποιο συντομότερο χρονικό διάστημα να στείλει άλλο email; -$logger->emailSnooze = /* ... */; // (string) η προεπιλογή είναι '2 ημέρες' +// μετά από πόσο συντομότερο χρονικό διάστημα να αποσταλεί το επόμενο email; +$logger->emailSnooze = /* ... */; // (string) προεπιλογή είναι '2 days' -// για ποια επίπεδα σφάλματος καταγράφεται επίσης το BlueScreen; -Debugger::$logSeverity = E_WARNING | E_NOTICE; // Προεπιλογή 0 (κανένα επίπεδο σφάλματος) +// για ποια επίπεδα σφαλμάτων καταγράφεται και το BlueScreen; +Debugger::$logSeverity = E_WARNING | E_NOTICE; // προεπιλογή είναι 0 (κανένα επίπεδο σφάλματος) ``` -`dump()` Συμπεριφορά +Συμπεριφορά `dump()` -------------------- ```php -// μέγιστο μήκος συμβολοσειράς -Debugger::$maxLength = 150; // (int) προεπιλογή σύμφωνα με το Tracy +// μέγιστο μήκος του string +Debugger::$maxLength = 150; // (int) προεπιλογή ανάλογα με την έκδοση Tracy -// πόσο βαθιά θα είναι η λίστα -Debugger::$maxDepth = 10; // (int) προεπιλογή σύμφωνα με την Tracy +// μέγιστο βάθος εμφώλευσης +Debugger::$maxDepth = 10; // (int) προεπιλογή ανάλογα με την έκδοση Tracy -// απόκρυψη των τιμών αυτών των κλειδιών (από το Tracy 2.8) -Debugger::$keysToHide = ['password', /* ... */]; // (string[]) προεπιλογή σε [] +// απόκρυψη τιμών αυτών των κλειδιών (από Tracy 2.8) +Debugger::$keysToHide = ['password', /* ... */]; // (string[]) προεπιλογή είναι [] -// οπτικό θέμα (από το Tracy 2.8) -Debugger::$dumpTheme = 'dark'; // (light|dark) προεπιλογή σε 'light' +// οπτικό θέμα (από Tracy 2.8) +Debugger::$dumpTheme = 'dark'; // (light|dark) προεπιλογή είναι 'light' -// εμφανίζει τη θέση στην οποία κλήθηκε η dump()? -Debugger::$showLocation = /* ... */; // (bool) προεπιλογή σύμφωνα με το Tracy +// εμφάνιση του μέρους όπου κλήθηκε η συνάρτηση dump(); +Debugger::$showLocation = /* ... */; // (bool) προεπιλογή ανάλογα με την έκδοση Tracy ``` -Άλλοι .[#toc-others] --------------------- +Άλλα +---- ```php -// στη λειτουργία ανάπτυξης, θα δείτε προειδοποιήσεις ειδοποίησης ή σφάλματος ως BlueScreen -Debugger::$strictMode = /* ... */; // (bool|int) προεπιλογή false, μπορείτε να επιλέξετε μόνο συγκεκριμένα επίπεδα σφάλματος (π.χ. E_USER_DEPRECATED | E_DEPRECATED) +// σε λειτουργία ανάπτυξης, εμφανίζει σφάλματα τύπου notice ή warning ως BlueScreen +Debugger::$strictMode = /* ... */; // (bool|int) προεπιλογή είναι false, είναι δυνατό να επιλεγούν μόνο ορισμένα επίπεδα σφαλμάτων (π.χ. E_USER_DEPRECATED | E_DEPRECATED) -// εμφανίζει σιωπηλά (@) μηνύματα σφάλματος -Debugger::$scream = /* ... */; // (bool|int) προεπιλογή false, από την έκδοση 2.9 είναι δυνατή η επιλογή μόνο συγκεκριμένων επιπέδων σφάλματος (π.χ. E_USER_DEPRECATED | E_DEPRECATED) +// εμφάνιση σιωπηλών (@) μηνυμάτων σφάλματος; +Debugger::$scream = /* ... */; // (bool|int) προεπιλογή είναι false, από την έκδοση 2.9 είναι δυνατό να επιλεγούν μόνο ορισμένα επίπεδα σφαλμάτων (π.χ. E_USER_DEPRECATED | E_DEPRECATED) -// μορφή συνδέσμου για άνοιγμα στον επεξεργαστή -Debugger::$editor = /* ... */; // (string|null) προεπιλογή: 'editor://open/?file=%file&line=%line' +// μορφή συνδέσμου για άνοιγμα στον editor +Debugger::$editor = /* ... */; // (string|null) προεπιλογή είναι 'editor://open/?file=%file&line=%line' -// διαδρομή προς το πρότυπο με την προσαρμοσμένη σελίδα για το σφάλμα 500 -Debugger::$errorTemplate = /* ... */; // (string) με προεπιλογή unset +// διαδρομή προς το template με προσαρμοσμένη σελίδα για το σφάλμα 500 +Debugger::$errorTemplate = /* ... */; // (string) προεπιλογή είναι μη ορισμένο -// shows Tracy Bar? -Debugger::$showBar = /* ... */; // (bool) προεπιλογή true +// εμφάνιση του Tracy Bar; +Debugger::$showBar = /* ... */; // (bool) προεπιλογή είναι true Debugger::$editorMapping = [ - // original => new + // αρχικό => νέο '/var/www/html' => '/data/web', '/home/web' => '/srv/html', ]; ``` -Πλαίσιο Nette .[#toc-nette-framework] -------------------------------------- +Nette Framework +--------------- -Εάν χρησιμοποιείτε το Nette Framework, μπορείτε επίσης να ρυθμίσετε το Tracy και να προσθέσετε νέα πάνελ στη γραμμή Tracy χρησιμοποιώντας το αρχείο ρυθμίσεων. -Μπορείτε να ορίσετε τις παραμέτρους του Tracy στη διαμόρφωση και επίσης να προσθέσετε νέους πίνακες στη γραμμή Tracy. Αυτές οι ρυθμίσεις εφαρμόζονται μόνο μετά τη δημιουργία του δοχείου DI, οπότε τα σφάλματα που προέκυψαν νωρίτερα δεν μπορούν να τις αντικατοπτρίζουν. +Αν χρησιμοποιείτε το Nette Framework, μπορείτε να διαμορφώσετε την Tracy και να προσθέσετε νέα πάνελ στο Tracy Bar επίσης μέσω του αρχείου διαμόρφωσης. Στη διαμόρφωση, μπορείτε να ορίσετε παραμέτρους και επίσης να προσθέσετε νέα πάνελ στο Tracy Bar. Αυτές οι ρυθμίσεις εφαρμόζονται μόνο μετά τη δημιουργία του DI container, οπότε τα σφάλματα που προκύπτουν πριν από αυτό δεν μπορούν να τις αντικατοπτρίζουν. -Ρύθμιση παραμέτρων καταγραφής σφαλμάτων: +Διαμόρφωση καταγραφής σφαλμάτων: ```neon tracy: - # αν έχει συμβεί σφάλμα, η ειδοποίηση αποστέλλεται σε αυτό το email - email: dev@example.com # (string|string[]) προεπιλογή unset + # e-mail στο οποίο αποστέλλονται οι ειδοποιήσεις για την εμφάνιση σφάλματος + email: dev@example.com # (string|string[]) προεπιλογή είναι μη ορισμένο - # αποστολέας email - fromEmail: robot@example.com # (string) προεπιλογή unset + # αποστολέας του e-mail + fromEmail: robot@example.com # (string) προεπιλογή είναι μη ορισμένο - # περίοδος αναβολής της αποστολής email (από το Tracy 2.8.8) - emailSnooze: ... # (string) προεπιλογή '2 ημέρες' + # χρόνος αναβολής αποστολής e-mail (από Tracy 2.8.8) + emailSnooze: ... # (string) προεπιλογή είναι '2 days' - # για να χρησιμοποιήσετε έναν αποστολέα αλληλογραφίας που έχει οριστεί στη ρύθμιση παραμέτρων? (από το Tracy 2.5) - netteMailer: ... # (bool) προεπιλογή σε true + # χρήση του Nette mailer για την αποστολή e-mail; (από Tracy 2.5) + netteMailer: ... # (bool) προεπιλογή είναι true - # για ποια επίπεδα σφαλμάτων καταγράφεται επίσης το BlueScreen; - logSeverity: [E_WARNING, E_NOTICE] # defaults to [] + # για ποια επίπεδα σφαλμάτων καταγράφεται και το BlueScreen; + logSeverity: [E_WARNING, E_NOTICE] # προεπιλογή είναι [] ``` -Διαμόρφωση για τη λειτουργία `dump()`: +Διαμόρφωση συμπεριφοράς της συνάρτησης `dump()`: ```neon tracy: - # μέγιστο μήκος συμβολοσειράς - maxLength: 150 # (int) προεπιλογή σύμφωνα με το Tracy + # μέγιστο μήκος του string + maxLength: 150 # (int) προεπιλογή ανάλογα με την έκδοση Tracy - # πόσο βαθιά θα είναι η λίστα - maxDepth: 10 # (int) προεπιλογή σύμφωνα με την Tracy + # μέγιστο βάθος εμφώλευσης + maxDepth: 10 # (int) προεπιλογή ανάλογα με την έκδοση Tracy - # απόκρυψη των τιμών αυτών των κλειδιών (από το Tracy 2.8) - keysToHide: [password, pass] # (string[]) προεπιλογή σε [] + # απόκρυψη τιμών αυτών των κλειδιών (από Tracy 2.8) + keysToHide: [password, pass] # (string[]) προεπιλογή είναι [] - # οπτικό θέμα (από το Tracy 2.8) - dumpTheme: dark # (light|dark) προεπιλογή σε 'light' + # οπτικό θέμα (από Tracy 2.8) + dumpTheme: dark # (light|dark) προεπιλογή είναι 'light' - # εμφανίζει τη θέση στην οποία κλήθηκε η dump()? - showLocation: ... # (bool) προεπιλογή σύμφωνα με το Tracy + # εμφάνιση του μέρους όπου κλήθηκε η συνάρτηση dump(); + showLocation: ... # (bool) προεπιλογή ανάλογα με την έκδοση Tracy ``` -Για να εγκαταστήσετε την επέκταση Tracy: +Εγκατάσταση επεκτάσεων Tracy: ```neon tracy: - # προσθέτει ράβδους στο Tracy Bar + # προσθέτει πάνελ στο Tracy Bar bar: - Nette\Bridges\DITracy\ContainerPanel - IncludePanel - XDebugHelper('myIdeKey') - MyPanel(@MyService) - # Προσαρτά πάνελ στο BlueScreen + # προσθέτει πάνελ στο BlueScreen blueScreen: - DoctrinePanel::renderException ``` @@ -145,25 +144,37 @@ tracy: ```neon tracy: - # στη λειτουργία ανάπτυξης, θα δείτε προειδοποιήσεις ειδοποίησης ή σφάλματος ως BlueScreen - strictMode: ... # είναι προεπιλεγμένη σε true + # σε λειτουργία ανάπτυξης, εμφανίζει σφάλματα τύπου notice ή warning ως BlueScreen + strictMode: ... # προεπιλογή είναι true - # εμφανίζει σιωπηλά (@) μηνύματα σφάλματος - scream: ... # προεπιλογή σε false + # εμφάνιση σιωπηλών (@) μηνυμάτων σφάλματος; + scream: ... # προεπιλογή είναι false - # μορφή συνδέσμου για άνοιγμα στον επεξεργαστή - editor: ... # (string) προεπιλογή 'editor://open/?file=%file&line=%line' + # μορφή συνδέσμου για άνοιγμα στον editor + editor: ... # (string) προεπιλογή είναι 'editor://open/?file=%file&line=%line' - # διαδρομή προς το πρότυπο με την προσαρμοσμένη σελίδα για το σφάλμα 500 - errorTemplate: ... # (string) προεπιλογή unset + # διαδρομή προς το template με προσαρμοσμένη σελίδα για το σφάλμα 500 + errorTemplate: ... # (string) προεπιλογή είναι μη ορισμένο - # shows Tracy Bar? - showBar: ... # (bool) προεπιλογή true + # εμφάνιση του Tracy Bar; + showBar: ... # (bool) προεπιλογή είναι true editorMapping: - # original: new + # αρχικό: νέο /var/www/html: /data/web /home/web: /srv/html ``` -Οι τιμές των επιλογών `logSeverity`, `strictMode` και `scream` μπορούν να γραφούν ως πίνακας επιπέδων σφάλματος (π.χ. `[E_WARNING, E_NOTICE]`) ή ως έκφραση που χρησιμοποιείται στην PHP (π.χ. `E_ALL & ~E_NOTICE`). +Οι τιμές των επιλογών `logSeverity`, `strictMode` και `scream` μπορούν να γραφτούν ως πίνακας επιπέδων σφαλμάτων (π.χ. `[E_WARNING, E_NOTICE]`), ή ως έκφραση που χρησιμοποιείται στη γλώσσα PHP (π.χ. `E_ALL & ~E_NOTICE`). + + +Υπηρεσίες DI +------------ + +Αυτές οι υπηρεσίες προστίθενται στο DI container: + +| Όνομα | Τύπος | Περιγραφή +|---------------------------------------------------------- +| `tracy.logger` | [api:Tracy\ILogger] | logger +| `tracy.blueScreen` | [api:Tracy\BlueScreen] | BlueScreen +| `tracy.bar` | [api:Tracy\Bar] | Tracy Bar diff --git a/tracy/el/dumper.texy b/tracy/el/dumper.texy index ae31712df2..6514dc5585 100644 --- a/tracy/el/dumper.texy +++ b/tracy/el/dumper.texy @@ -1,7 +1,7 @@ -Dumper -****** +Dumping +******* -Κάθε προγραμματιστής αποσφαλμάτωσης είναι καλός φίλος με τη συνάρτηση `var_dump`, η οποία παραθέτει λεπτομερώς όλα τα περιεχόμενα οποιασδήποτε μεταβλητής. Δυστυχώς, η έξοδός της είναι χωρίς μορφοποίηση HTML και εξάγει την απόρριψη σε μία μόνο γραμμή κώδικα HTML, για να μην αναφέρουμε την αποφυγή συμφραζομένων. Είναι απαραίτητο να αντικατασταθεί η `var_dump` με μια πιο εύχρηστη συνάρτηση. Αυτό ακριβώς είναι το `dump()`. +Κάθε debugger είναι καλός φίλος με τη συνάρτηση [php:var_dump], η οποία εκτυπώνει λεπτομερώς το περιεχόμενο μιας μεταβλητής. Δυστυχώς, στο περιβάλλον HTML, η έξοδος χάνει τη μορφοποίησή της και συγχωνεύεται σε μία γραμμή, για να μην αναφέρουμε την απολύμανση του κώδικα HTML. Στην πράξη, είναι απαραίτητο να αντικαταστήσετε το `var_dump` με μια πιο έξυπνη συνάρτηση. Αυτή είναι ακριβώς η `dump()`. ```php $arr = [10, 20.2, true, null, 'hello']; @@ -10,11 +10,11 @@ dump($arr); // ή Debugger::dump($arr); ``` -παράγει την έξοδο: +δημιουργεί την έξοδο: [* dump-basic.webp *] -Μπορείτε να αλλάξετε το προεπιλεγμένο ανοιχτό θέμα σε σκούρο: +Μπορείτε να αλλάξετε το προεπιλεγμένο φωτεινό θέμα σε σκοτεινό: ```php Debugger::$dumpTheme = 'dark'; @@ -22,27 +22,27 @@ Debugger::$dumpTheme = 'dark'; [* dump-dark.webp *] -Μπορείτε επίσης να αλλάξετε το βάθος φωλιασμού με `Debugger::$maxDepth` και το μήκος των εμφανιζόμενων συμβολοσειρών με `Debugger::$maxLength`. Φυσικά, οι χαμηλότερες τιμές επιταχύνουν την απόδοση του Tracy. +Επιπλέον, μπορούμε να αλλάξουμε το βάθος εμφώλευσης χρησιμοποιώντας το [Debugger::$maxDepth |api:Tracy\Debugger::$maxDepth] και το μήκος των εμφανιζόμενων ετικετών χρησιμοποιώντας το [Debugger::$maxLength |api:Tracy\Debugger::$maxLength]. Οι χαμηλότερες τιμές φυσικά επιταχύνουν την Tracy. ```php Debugger::$maxDepth = 2; // προεπιλογή: 3 Debugger::$maxLength = 50; // προεπιλογή: 150 ``` -Η συνάρτηση `dump()` μπορεί να εμφανίσει και άλλες χρήσιμες πληροφορίες. `Tracy\Dumper::LOCATION_SOURCE` προσθέτει ένα tooltip με τη διαδρομή προς το αρχείο, στο οποίο κλήθηκε η συνάρτηση. `Tracy\Dumper::LOCATION_LINK` προσθέτει έναν σύνδεσμο προς το αρχείο. `Tracy\Dumper::LOCATION_CLASS` προσθέτει ένα tooltip σε κάθε αντικείμενο που απορρίπτεται και περιέχει τη διαδρομή προς το αρχείο, στο οποίο ορίζεται η κλάση του αντικειμένου. Όλες αυτές οι σταθερές μπορούν να οριστούν στη μεταβλητή `Debugger::$showLocation` πριν από την κλήση της `dump()`. Μπορείτε να ορίσετε πολλαπλές τιμές ταυτόχρονα χρησιμοποιώντας τον τελεστή `|`. +Η συνάρτηση `dump()` μπορεί να εκτυπώσει και άλλες χρήσιμες πληροφορίες. Η σταθερά `Tracy\Dumper::LOCATION_SOURCE` προσθέτει ένα tooltip με τη διαδρομή προς το σημείο όπου κλήθηκε η συνάρτηση. Το `Tracy\Dumper::LOCATION_LINK` μας παρέχει έναν σύνδεσμο προς αυτό το σημείο. Το `Tracy\Dumper::LOCATION_CLASS` για κάθε dump-αρισμένο αντικείμενο εκτυπώνει ένα tooltip με τη διαδρομή προς το αρχείο όπου ορίζεται η κλάση του. Οι σταθερές ορίζονται στη μεταβλητή `Debugger::$showLocation` πριν από την κλήση της `dump()`. Αν θέλουμε να ορίσουμε πολλές τιμές ταυτόχρονα, τις συνδυάζουμε χρησιμοποιώντας τον τελεστή `|`. ```php -Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // Δείχνει τη διαδρομή στην οποία κλήθηκε η dump() -Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Εμφανίζει τόσο τις διαδρομές προς τις κλάσεις όσο και το σύνδεσμο προς το σημείο κλήσης της dump() -Debugger::$showLocation = false; // Αποκρύπτει πρόσθετες πληροφορίες τοποθεσίας -Debugger::$showLocation = true; // Εμφανίζει όλες τις πρόσθετες πληροφορίες τοποθεσίας +Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // Ορίζει μόνο την εκτύπωση για το σημείο κλήσης της συνάρτησης +Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Ορίζει ταυτόχρονα την εκτύπωση του συνδέσμου και τη διαδρομή προς την κλάση +Debugger::$showLocation = false; // Απενεργοποιεί την εκτύπωση πρόσθετων πληροφοριών +Debugger::$showLocation = true; // Ενεργοποιεί την εκτύπωση όλων των πρόσθετων πληροφοριών ``` -Πολύ εύχρηστη εναλλακτική λύση για το `dump()` είναι το `dumpe()` (δηλ. dump και exit) και το `bdump()`. Αυτό μας επιτρέπει να κάνουμε ντάμπινγκ μεταβλητών στο Tracy Bar. Αυτό είναι χρήσιμο, επειδή οι απορρίψεις δεν μπερδεύουν την έξοδο και μπορούμε επίσης να προσθέσουμε έναν τίτλο στην απόρριψη. +Μια πρακτική εναλλακτική λύση στο `dump()` είναι το `dumpe()` (dump & exit) και το `bdump()`. Αυτό μας επιτρέπει να εκτυπώσουμε την τιμή μιας μεταβλητής στο πάνελ του Tracy Bar. Αυτό είναι πολύ χρήσιμο, καθώς τα dumps είναι ξεχωριστά από τη διάταξη της σελίδας και μπορούμε επίσης να τοποθετήσουμε ένα σχόλιο δίπλα τους. ```php -bdump([2, 4, 6, 8], 'even numbers up to ten'); -bdump([1, 3, 5, 7, 9], 'odd numbers up to ten'); +bdump([2, 4, 6, 8], 'άρτιοι αριθμοί μέχρι το δέκα'); +bdump([1, 3, 5, 7, 9], 'περιττοί αριθμοί μέχρι το δέκα'); ``` -[* bardump-en.webp *] +[* bardump-cs.webp *] diff --git a/tracy/el/extensions.texy b/tracy/el/extensions.texy index 711c2b3672..aabbca39c1 100644 --- a/tracy/el/extensions.texy +++ b/tracy/el/extensions.texy @@ -1,23 +1,23 @@ -Δημιουργία επεκτάσεων Tracy -*************************** +Δημιουργία επεκτάσεων για την Tracy +*********************************** <div class=perex> -Το Tracy είναι ένα εξαιρετικό εργαλείο για την αποσφαλμάτωση της εφαρμογής σας. Ωστόσο, μερικές φορές χρειάζεστε περισσότερες πληροφορίες από αυτές που προσφέρει το Tracy. Θα μάθετε σχετικά με: +Η Tracy παρέχει ένα εξαιρετικό εργαλείο για τον εντοπισμό σφαλμάτων της εφαρμογής σας. Μερικές φορές, όμως, θα θέλατε να έχετε εύκαιρες και κάποιες άλλες πληροφορίες. Θα δείξουμε πώς να γράψετε τις δικές σας επεκτάσεις για το Tracy Bar, ώστε η ανάπτυξη να γίνει ακόμα πιο ευχάριστη. -- Τη δημιουργία των δικών σας πλαισίων της γραμμής Tracy -- Δημιουργία των δικών σας επεκτάσεων Bluescreen +- Δημιουργία προσαρμοσμένου πάνελ για το Tracy Bar +- Δημιουργία προσαρμοσμένης επέκτασης για το Bluescreen </div> .[tip] -Μπορείτε να βρείτε χρήσιμες επεκτάσεις για το Tracy στο "Componette:https://componette.org/search/tracy". +Μπορείτε να βρείτε ένα αποθετήριο έτοιμων επεκτάσεων για την Tracy στο "Componette":https://componette.org/search/tracy. -Επεκτάσεις της μπάρας Tracy .[#toc-tracy-bar-extensions] -======================================================== +Επεκτάσεις για το Tracy Bar +=========================== -Η δημιουργία μιας νέας επέκτασης για το Tracy Bar είναι απλή. Πρέπει να υλοποιήσετε τη διεπαφή `Tracy\IBarPanel` με τις μεθόδους `getTab()` και `getPanel()`. Οι μέθοδοι πρέπει να επιστρέφουν τον κώδικα HTML μιας καρτέλας (μικρή ετικέτα στο Tracy Bar) και ενός πίνακα (αναδυόμενο παράθυρο που εμφανίζεται αφού κάνετε κλικ στην καρτέλα). Εάν η `getPanel()` δεν επιστρέψει τίποτα, θα εμφανιστεί μόνο η καρτέλα. Εάν το `getTab()` δεν επιστρέψει τίποτα, δεν εμφανίζεται τίποτα και το `getPanel()` δεν θα κληθεί. +Η δημιουργία μιας νέας επέκτασης για το Tracy Bar δεν είναι καθόλου πολύπλοκη. Δημιουργείτε ένα αντικείμενο που υλοποιεί το interface `Tracy\IBarPanel`, το οποίο έχει δύο μεθόδους `getTab()` και `getPanel()`. Οι μέθοδοι πρέπει να επιστρέψουν τον κώδικα HTML της καρτέλας (μια μικρή ετικέτα που εμφανίζεται απευθείας στο Bar) και του πάνελ. Αν η `getPanel()` δεν επιστρέψει τίποτα, εμφανίζεται μόνο η ίδια η ετικέτα. Αν η `getTab()` δεν επιστρέψει τίποτα, δεν εμφανίζεται τίποτα απολύτως και ούτε η getPanel() καλείται πλέον. ```php class ExamplePanel implements Tracy\IBarPanel @@ -35,16 +35,16 @@ class ExamplePanel implements Tracy\IBarPanel ``` -Εγγραφή .[#toc-registration] ----------------------------- +Εγγραφή +------- -Η εγγραφή γίνεται καλώντας στο `Tracy\Bar::addPanel()`: +Η εγγραφή πραγματοποιείται με τη χρήση της `Tracy\Bar::addPanel()`: ```php Tracy\Debugger::getBar()->addPanel(new ExamplePanel); ``` -ή μπορείτε απλά να καταχωρήσετε τον πίνακα σας στη διαμόρφωση της εφαρμογής: +Ή μπορείτε να εγγράψετε το πάνελ απευθείας στη διαμόρφωση της εφαρμογής: ```neon tracy: @@ -53,70 +53,70 @@ tracy: ``` -Κωδικός HTML καρτέλας .[#toc-tab-html-code] -------------------------------------------- +Κώδικας HTML της καρτέλας +------------------------- -Θα πρέπει να μοιάζει κάπως έτσι: +Θα πρέπει να μοιάζει περίπου έτσι: ```latte -<span title="Explaining tooltip"> +<span title="Επεξηγηματική ετικέτα"> <svg>...</svg> - <span class="tracy-label">Title</span> + <span class="tracy-label">Τίτλος</span> </span> ``` -Η εικόνα θα πρέπει να είναι σε μορφή SVG. Αν δεν χρειάζεστε tooltip, μπορείτε να αφήσετε το `<span>` έξω. +Η εικόνα θα πρέπει να είναι σε μορφή SVG. Αν δεν χρειάζεται επεξηγηματική ετικέτα, το `<span>` μπορεί να παραλειφθεί. -Κώδικας HTML του πίνακα .[#toc-panel-html-code] ------------------------------------------------ +Κώδικας HTML του πάνελ +---------------------- -Θα πρέπει να μοιάζει κάπως έτσι: +Θα πρέπει να μοιάζει περίπου έτσι: ```latte -<h1>Title</h1> +<h1>Τίτλος</h1> <div class="tracy-inner"> <div class="tracy-inner-container"> - ... content ... + ... περιεχόμενο ... </div> </div> ``` -Ο τίτλος θα πρέπει είτε να είναι ο ίδιος όπως στην καρτέλα είτε να περιέχει πρόσθετες πληροφορίες. +Ο τίτλος θα πρέπει να είναι είτε ο ίδιος με τον τίτλο της καρτέλας, είτε μπορεί να περιέχει πρόσθετα δεδομένα. -Μια επέκταση μπορεί να καταχωρηθεί πολλές φορές, γι' αυτό συνιστάται να μην χρησιμοποιείτε το χαρακτηριστικό `id` για τη διαμόρφωση. Μπορείτε να χρησιμοποιήσετε κλάσεις, κατά προτίμηση σε `tracy-addons-<class-name>[-<optional>]` μορφή. Κατά τη δημιουργία CSS, είναι προτιμότερο να χρησιμοποιείτε το `#tracy-debug .class`, επειδή ένας τέτοιος κανόνας έχει υψηλότερη προτεραιότητα από την επαναφορά. +Πρέπει να λάβετε υπόψη ότι μια επέκταση μπορεί να εγγραφεί πολλές φορές, για παράδειγμα με διαφορετικές ρυθμίσεις, οπότε για το styling δεν μπορείτε να χρησιμοποιήσετε CSS id, αλλά μόνο class, και μάλιστα με τη μορφή `tracy-addons-<NazevTridy>[-<volitelné>]`. Στη συνέχεια, γράψτε την κλάση στο div μαζί με την κλάση `tracy-inner`. Κατά τη συγγραφή CSS, είναι χρήσιμο να γράφετε `#tracy-debug .trida`, επειδή ο κανόνας τότε έχει υψηλότερη προτεραιότητα από το reset. -Προεπιλεγμένα στυλ .[#toc-default-styles] ------------------------------------------ +Προεπιλεγμένα στυλ +------------------ -Στον πίνακα, τα στοιχεία `<a>`, `<table>`, `<pre>`, `<code>` έχουν προεπιλεγμένο στυλ. Για τη δημιουργία ενός συνδέσμου για την απόκρυψη ή την εμφάνιση άλλου στοιχείου, συνδέστε τα με τα χαρακτηριστικά `href` και `id` και την κλάση `tracy-toggle`. +Στο πάνελ είναι προ-στυλιζαρισμένα τα `<a>`, `<table>`, `<pre>`, `<code>`. Αν θέλετε να δημιουργήσετε έναν σύνδεσμο που κρύβει και εμφανίζει ένα άλλο στοιχείο, συνδέστε τα με τα χαρακτηριστικά `href` και `id` και την κλάση `tracy-toggle`: ```latte -<a href="#tracy-addons-className-{$counter}" class="tracy-toggle">Detail</a> +<a href="#tracy-addons-NazevTridy-{$counter}" class="tracy-toggle">Λεπτομέρειες</a> -<div id="tracy-addons-className-{$counter}">...</div> +<div id="tracy-addons-NazevTridy-{$counter}">...</div> ``` -Εάν η προεπιλεγμένη κατάσταση είναι η αναδίπλωση, προσθέστε την κλάση `tracy-collapsed` και στα δύο στοιχεία. +Αν η προεπιλεγμένη κατάσταση πρέπει να είναι συμπτυγμένη, προσθέστε και στα δύο στοιχεία την κλάση `tracy-collapsed`. -Χρησιμοποιήστε έναν στατικό μετρητή για να αποφύγετε τα διπλά αναγνωριστικά σε μια σελίδα. +Χρησιμοποιήστε έναν στατικό counter, ώστε να μην δημιουργούνται διπλότυπα ID στην ίδια σελίδα. -Επεκτάσεις Bluescreen .[#toc-bluescreen-extensions] -=================================================== +Επεκτάσεις για το Bluescreen +============================ -Μπορείτε να προσθέσετε τις δικές σας οπτικοποιήσεις ή πίνακες εξαιρέσεων, οι οποίοι θα εμφανίζονται στην οθόνη bluescreen. +Με αυτόν τον τρόπο μπορείτε να προσθέσετε προσαρμοσμένες οπτικοποιήσεις εξαιρέσεων ή πάνελ που θα εμφανίζονται στο bluescreen. -Η επέκταση γίνεται ως εξής: +Η επέκταση δημιουργείται με αυτήν την εντολή: ```php -Tracy\Debugger::getBlueScreen()->addPanel(function (?Throwable $e) { // πιασμένη εξαίρεση +Tracy\Debugger::getBlueScreen()->addPanel(function (?Throwable $e) { // παγιδευμένη εξαίρεση return [ - 'tab' => '...Title...', - 'panel' => '...content...', + 'tab' => '...Ετικέτα...', + 'panel' => '...Κώδικας HTML του πάνελ...', ]; }); ``` -Η συνάρτηση καλείται δύο φορές, πρώτα περνάει η ίδια η εξαίρεση στην παράμετρο `$e` και το πάνελ που επιστρέφεται απεικονίζεται στην αρχή της σελίδας. Εάν δεν επιστραφεί τίποτα, ο πίνακας δεν αποδίδεται. Στη συνέχεια καλείται με την παράμετρο `null` και ο επιστρεφόμενος πίνακας αποδίδεται κάτω από το callstack. Εάν η συνάρτηση επιστρέψει `'bottom' => true` στον πίνακα, το πάνελ απεικονίζεται στο κάτω μέρος. +Η συνάρτηση καλείται δύο φορές, πρώτα περνιέται η ίδια η εξαίρεση στην παράμετρο `$e` και το επιστρεφόμενο πάνελ απεικονίζεται στην αρχή της σελίδας. Αν δεν επιστρέψει τίποτα, το πάνελ δεν απεικονίζεται. Στη συνέχεια, καλείται με την παράμετρο `null` και το επιστρεφόμενο πάνελ απεικονίζεται κάτω από το callstack. Αν η συνάρτηση επιστρέφει στο πεδίο το κλειδί `'bottom' => true`, το πάνελ απεικονίζεται στο κάτω μέρος. diff --git a/tracy/el/guide.texy b/tracy/el/guide.texy index f942fa71e8..5ca373e5da 100644 --- a/tracy/el/guide.texy +++ b/tracy/el/guide.texy @@ -3,38 +3,38 @@ <div class=perex> -Η βιβλιοθήκη Tracy είναι ένα χρήσιμο βοήθημα για τους καθημερινούς προγραμματιστές PHP. Σας βοηθάει να: +Η βιβλιοθήκη Tracy είναι ένας χρήσιμος καθημερινός βοηθός για τον προγραμματιστή PHP. Θα σας βοηθήσει να: -- να εντοπίζετε και να διορθώνετε γρήγορα σφάλματα -- να καταγράφετε σφάλματα -- να απορρίπτετε μεταβλητές -- να μετράτε το χρόνο εκτέλεσης των σεναρίων/ερωτήσεων -- δείτε την κατανάλωση μνήμης +- εντοπίσετε και να διορθώσετε γρήγορα σφάλματα +- καταγράψετε σφάλματα +- εκτυπώσετε μεταβλητές +- μετρήσετε τον χρόνο εκτέλεσης σεναρίων και ερωτημάτων βάσης δεδομένων +- παρακολουθήσετε τις απαιτήσεις μνήμης </div> -Η PHP είναι μια τέλεια γλώσσα για την κατασκευή δύσκολα ανιχνεύσιμων σφαλμάτων, επειδή δίνει μεγάλη ευελιξία στους προγραμματιστές. Το Tracy\Debugger είναι πιο πολύτιμο εξαιτίας αυτού. Είναι ένα απόλυτο εργαλείο μεταξύ των διαγνωστικών εργαλείων. +Η PHP είναι μια γλώσσα ιδανική για τη δημιουργία δύσκολα εντοπίσιμων σφαλμάτων, καθώς δίνει στους προγραμματιστές σημαντική ελευθερία. Γι' αυτό, το εργαλείο εντοπισμού σφαλμάτων Tracy είναι ακόμα πιο πολύτιμο. Μεταξύ των διαγνωστικών εργαλείων για PHP, αντιπροσωπεύει την απόλυτη κορυφή. -Αν συναντάτε την Tracy για πρώτη φορά, πιστέψτε με, η ζωή σας αρχίζει να χωρίζεται σε μία πριν την Tracy και σε μία μαζί της. Καλώς ήρθατε στο καλό μέρος! +Αν συναντάτε την Tracy για πρώτη φορά σήμερα, πιστέψτε ότι η ζωή σας θα αρχίσει να χωρίζεται σε αυτήν πριν την Tracy και σε αυτήν με αυτήν. Καλώς ήρθατε στο καλύτερο μέρος! -Εγκατάσταση και απαιτήσεις .[#toc-installation-and-requirements] -================================================================ +Εγκατάσταση +=========== -Ο καλύτερος τρόπος για να εγκαταστήσετε το Tracy είναι να [κατεβάσετε το τελευταίο πακέτο](https://github.com/nette/tracy/releases) ή να χρησιμοποιήσετε το Composer: +Ο καλύτερος τρόπος για να εγκαταστήσετε την Tracy είναι να [κατεβάσετε το τελευταίο πακέτο](https://github.com/nette/tracy/releases), ή να χρησιμοποιήσετε τον Composer: ```shell composer require tracy/tracy ``` -Εναλλακτικά, μπορείτε να κατεβάσετε ολόκληρο το πακέτο ή το αρχείο [tracy.phar |https://github.com/nette/tracy/releases]. +Μπορείτε επίσης να κατεβάσετε ολόκληρο το πακέτο ως αρχείο [tracy.phar |https://github.com/nette/tracy/releases]. -Χρήση .[#toc-usage] -=================== +Χρήση +===== -Το Tracy ενεργοποιείται με την κλήση της μεθόδου `Tracy\Debugger::enable()' το συντομότερο δυνατό στην αρχή του προγράμματος, πριν από την αποστολή οποιασδήποτε εξόδου: +Ενεργοποιούμε την Tracy καλώντας τη μέθοδο `Tracy\Debugger::enable()' το συντομότερο δυνατό στην αρχή του προγράμματος, πριν από την αποστολή οποιασδήποτε εξόδου: ```php use Tracy\Debugger; @@ -44,18 +44,17 @@ require 'vendor/autoload.php'; // εναλλακτικά tracy.phar Debugger::enable(); ``` -Το πρώτο πράγμα που θα παρατηρήσετε στη σελίδα είναι η μπάρα Tracy στην κάτω δεξιά γωνία. Αν δεν την βλέπετε, αυτό μπορεί να σημαίνει ότι το Tracy λειτουργεί σε κατάσταση παραγωγής. -Αυτό συμβαίνει επειδή το Tracy είναι ορατό μόνο στο localhost για λόγους ασφαλείας. Για να ελέγξετε αν λειτουργεί, μπορείτε να το θέσετε προσωρινά σε κατάσταση ανάπτυξης χρησιμοποιώντας την παράμετρο `Debugger::enable(Debugger::Development)`. +Το πρώτο πράγμα που θα παρατηρήσετε στη σελίδα είναι το Tracy Bar στην κάτω δεξιά γωνία. Αν δεν το βλέπετε, μπορεί να σημαίνει ότι η Tracy τρέχει σε λειτουργία παραγωγής. Η Tracy, για λόγους ασφαλείας, είναι ορατή μόνο στο localhost. Για να δοκιμάσετε αν λειτουργεί, μπορείτε να την αλλάξετε προσωρινά σε λειτουργία ανάπτυξης χρησιμοποιώντας την παράμετρο `Debugger::enable(Debugger::Development)`. -Γραμμή Tracy .[#toc-tracy-bar] -============================== +Tracy Bar +========= -Το Tracy Bar είναι ένας πλωτός πίνακας. Εμφανίζεται στην κάτω δεξιά γωνία μιας σελίδας. Μπορείτε να το μετακινήσετε χρησιμοποιώντας το ποντίκι. Θα θυμάται τη θέση της μετά την επαναφόρτωση της σελίδας. +Το Tracy Bar είναι ένα πλωτό πάνελ που εμφανίζεται στην κάτω δεξιά γωνία της σελίδας. Μπορούμε να το μετακινήσουμε με το ποντίκι και μετά την επαναφόρτωση της σελίδας θα θυμάται τη θέση του. [* tracy-bar.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html -Μπορείτε να προσθέσετε και άλλα χρήσιμα πάνελ στη γραμμή Tracy Bar. Μπορείτε να βρείτε ενδιαφέροντα σε [πρόσθετα |https://componette.org] ή να [δημιουργήσετε τα δικά σας |extensions]. +Στο Tracy Bar μπορούν να προστεθούν και άλλα χρήσιμα πάνελ. Πολλά από αυτά θα τα βρείτε στα [πρόσθετα |https://componette.org], ή μπορείτε ακόμη και να [γράψετε τα δικά σας |extensions]. Αν δεν θέλετε να εμφανίζεται το Tracy Bar, ορίστε: @@ -64,150 +63,150 @@ Debugger::$showBar = false; ``` -Οπτικοποίηση σφαλμάτων και εξαιρέσεων .[#toc-visualization-of-errors-and-exceptions] -==================================================================================== +Οπτικοποίηση σφαλμάτων και εξαιρέσεων +===================================== -Σίγουρα, γνωρίζετε πώς η PHP αναφέρει τα σφάλματα: υπάρχει κάτι τέτοιο στον πηγαίο κώδικα της σελίδας: +Σίγουρα γνωρίζετε καλά πώς η PHP ανακοινώνει τα σφάλματα: εκτυπώνει κάτι τέτοιο στον πηγαίο κώδικα της σελίδας: /--pre .{font-size: 90%} <b>Parse error</b>: syntax error, unexpected '}' in <b>HomePresenter.php</b> on line <b>15</b> \-- -or uncaught exception: +ή σε περίπτωση μη παγιδευμένης εξαίρεσης: /--pre .{font-size: 90%} <b>Fatal error</b>: Uncaught Nette\MemberAccessException: Call to undefined method Nette\Application\UI\Form::addTest()? in /sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php:100 Stack trace: #0 /sandbox/vendor/nette/utils/src/Utils/Object.php(75): Nette\Utils\ObjectMixin::call(Object(Nette\Application\UI\Form), 'addTest', Array) -#1 /sandbox/app/forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) -#2 /sandbox/app/presenters/SignPresenter.php(21): App\Forms\SignFormFactory->create() -#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presenters\SignPresenter->createComponentSignInForm('signInForm') +#1 /sandbox/app/Forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) +#2 /sandbox/app/Presentation/Sign/SignPresenter.php(21): App\Forms\SignFormFactory->create() +#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presentation\Sign\SignPresenter->createComponentSignInForm('signInForm') #4 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(139): Nette\ComponentModel\Container->createComponent('signInForm') #5 /sandbox/temp/cache/latte/15206b353f351f6bfca2c36cc.php(17): Nette\ComponentModel\Co in <b>/sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php</b> on line <b>100</b><br /> \-- -Δεν είναι τόσο εύκολο να πλοηγηθείτε σε αυτή την έξοδο. Εάν ενεργοποιήσετε την επιλογή Tracy, τόσο τα σφάλματα όσο και οι εξαιρέσεις εμφανίζονται με εντελώς διαφορετική μορφή: +Η κατανόηση μιας τέτοιας εξόδου δεν είναι ακριβώς εύκολη. Αν ενεργοποιήσουμε την Tracy, το σφάλμα ή η εξαίρεση εμφανίζεται σε εντελώς διαφορετική μορφή: [* tracy-exception.webp .{url:-} *] -Το μήνυμα σφάλματος κυριολεκτικά φωνάζει. Μπορείτε να δείτε ένα μέρος του πηγαίου κώδικα με την υπογραμμισμένη γραμμή όπου εμφανίστηκε το σφάλμα. Ένα μήνυμα εξηγεί με σαφήνεια ένα σφάλμα. Ολόκληρος ο ιστότοπος είναι [διαδραστικός, δοκιμάστε τον](https://nette.github.io/tracy/tracy-exception.html). +Το μήνυμα σφάλματος κυριολεκτικά φωνάζει. Βλέπουμε ένα μέρος του πηγαίου κώδικα με την επισημασμένη γραμμή όπου προέκυψε το σφάλμα και η πληροφορία *Call to undefined method Nette\Http\User::isLogedIn()* εξηγεί κατανοητά τι είδους σφάλμα είναι. Επιπλέον, ολόκληρη η σελίδα είναι ζωντανή, μπορούμε να κάνουμε κλικ για περισσότερες λεπτομέρειες. [Δοκιμάστε το |https://nette.github.io/tracy/tracy-exception.html]. -Και ξέρετε κάτι; Τα μοιραία σφάλματα καταγράφονται και εμφανίζονται με τον ίδιο τρόπο. Δεν χρειάζεται να εγκαταστήσετε καμία επέκταση (κάντε κλικ για ζωντανό παράδειγμα): +Και ξέρετε τι; Με αυτόν τον τρόπο παγιδεύει και εμφανίζει ακόμη και τα μοιραία σφάλματα. Χωρίς την ανάγκη εγκατάστασης οποιασδήποτε επέκτασης. [* tracy-error.webp .{url:-} *] -Σφάλματα όπως ένα τυπογραφικό λάθος στο όνομα μιας μεταβλητής ή μια προσπάθεια ανοίγματος ενός ανύπαρκτου αρχείου δημιουργούν αναφορές επιπέδου E_NOTICE ή E_WARNING. Αυτά μπορούν εύκολα να παραβλεφθούν ή/και να κρυφτούν εντελώς στη γραφική διάταξη μιας ιστοσελίδας. Αφήστε την Tracy να τα διαχειριστεί: +Σφάλματα όπως ένα τυπογραφικό λάθος στο όνομα μιας μεταβλητής ή η προσπάθεια ανοίγματος ενός μη υπάρχοντος αρχείου δημιουργούν αναφορές επιπέδου E_NOTICE ή E_WARNING. Αυτά μπορούν εύκολα να παραβλεφθούν στα γραφικά της σελίδας, μπορεί ακόμη και να μην είναι καθόλου ορατά (εκτός αν κοιτάξετε τον κώδικα της σελίδας). [* tracy-notice2.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html -Ή μπορεί να εμφανίζονται σαν σφάλματα: +Ή μπορούν να εμφανιστούν όπως και τα σφάλματα: ```php Debugger::$strictMode = true; // εμφάνιση όλων των σφαλμάτων -Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // όλα τα σφάλματα εκτός από τις απαρχαιωμένες ειδοποιήσεις +Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // όλα τα σφάλματα εκτός από τις ειδοποιήσεις απόσυρσης ``` [* tracy-notice.webp .{url:-} *] -Σημείωση: Το Tracy όταν ενεργοποιείται αλλάζει το επίπεδο αναφοράς σφαλμάτων σε E_ALL. Αν θέλετε να το αλλάξετε αυτό, κάντε το μετά την κλήση του `enable()`. +Σημείωση: Η Tracy μετά την ενεργοποίηση αλλάζει το επίπεδο αναφοράς σφαλμάτων σε E_ALL. Αν θέλετε να αλλάξετε αυτήν την τιμή, κάντε το μετά την κλήση της `enable()`. -Λειτουργία ανάπτυξης έναντι λειτουργίας παραγωγής .[#toc-development-vs-production-mode] -======================================================================================== +Λειτουργία ανάπτυξης έναντι παραγωγής +===================================== -Όπως μπορείτε να δείτε, η Tracy είναι αρκετά ομιλητική, κάτι που μπορεί να εκτιμηθεί στο περιβάλλον ανάπτυξης, ενώ στον διακομιστή παραγωγής θα προκαλούσε καταστροφή. Αυτό συμβαίνει επειδή εκεί δεν θα έπρεπε να εμφανίζονται πληροφορίες εντοπισμού σφαλμάτων. Επομένως, το Tracy διαθέτει **αυτόματη ανίχνευση περιβάλλοντος** και αν το παράδειγμα εκτελεστεί σε έναν πραγματικό διακομιστή, το σφάλμα θα καταγραφεί αντί να εμφανιστεί και ο επισκέπτης θα δει μόνο ένα φιλικό προς το χρήστη μήνυμα: +Όπως βλέπετε, η Tracy είναι αρκετά φλύαρη, κάτι που μπορεί να εκτιμηθεί στο περιβάλλον ανάπτυξης, ενώ στον διακομιστή παραγωγής θα προκαλούσε πραγματική καταστροφή. Εκεί, καμία πληροφορία εντοπισμού σφαλμάτων δεν πρέπει να εκτυπωθεί. Γι' αυτό η Tracy διαθέτει **αυτόματη ανίχνευση περιβάλλοντος** και αν εκτελέσουμε το παράδειγμα σε έναν ζωντανό διακομιστή, το σφάλμα αντί να εμφανιστεί, θα καταγραφεί και ο επισκέπτης θα δει μόνο ένα κατανοητό μήνυμα χρήστη: [* tracy-error2.webp .{url:-} *] -Η λειτουργία παραγωγής καταστέλλει την εμφάνιση όλων των πληροφοριών εντοπισμού σφαλμάτων που αποστέλλονται με τη χρήση της [dump() |dumper] και φυσικά όλων των μηνυμάτων σφάλματος που παράγει η PHP. Έτσι, αν έχετε ξεχάσει κάποιο `dump($obj)` στον κώδικα, δεν χρειάζεται να ανησυχείτε, τίποτα δεν θα εμφανιστεί στον διακομιστή παραγωγής. +Η λειτουργία παραγωγής καταστέλλει την εμφάνιση όλων των πληροφοριών εντοπισμού σφαλμάτων που στέλνουμε προς τα έξω με το [dump() |dumper], και φυσικά επίσης όλων των μηνυμάτων σφάλματος που δημιουργεί η PHP. Έτσι, αν ξεχάσατε κάποιο `dump($obj)` στον κώδικα, δεν χρειάζεται να ανησυχείτε, τίποτα δεν θα εκτυπωθεί στον διακομιστή παραγωγής. -Πώς λειτουργεί η αυτόματη ανίχνευση λειτουργίας; Ο τρόπος λειτουργίας είναι η ανάπτυξη, εάν η εφαρμογή εκτελείται σε localhost (δηλ. διεύθυνση IP `127.0.0.1` ή `::1`) και δεν υπάρχει proxy (δηλ. η επικεφαλίδα HTTP του). Διαφορετικά, εκτελείται σε κατάσταση παραγωγής. +Πώς λειτουργεί η αυτόματη ανίχνευση λειτουργίας; Η λειτουργία είναι ανάπτυξης όταν η εφαρμογή εκτελείται στο localhost (δηλ. διεύθυνση IP `127.0.0.1` ή `::1`) και δεν υπάρχει διακομιστής μεσολάβησης (δηλ. η HTTP κεφαλίδα του). Αλλιώς, τρέχει σε λειτουργία παραγωγής. -Αν θέλετε να ενεργοποιήσετε τη λειτουργία ανάπτυξης σε άλλες περιπτώσεις, για παράδειγμα για προγραμματιστές που έχουν πρόσβαση από μια συγκεκριμένη διεύθυνση IP, μπορείτε να την καθορίσετε ως παράμετρο της μεθόδου `enable()`: +Αν θέλουμε να επιτρέψουμε τη λειτουργία ανάπτυξης και σε άλλες περιπτώσεις, για παράδειγμα για προγραμματιστές που έχουν πρόσβαση από μια συγκεκριμένη διεύθυνση IP, την αναφέρουμε ως παράμετρο της μεθόδου `enable()`: ```php -Debugger::enable('23.75.345.200'); // μπορείτε επίσης να δώσετε μια σειρά διευθύνσεων IP +Debugger::enable('23.75.345.200'); // μπορείτε επίσης να καθορίσετε έναν πίνακα διευθύνσεων IP ``` -Συνιστούμε οπωσδήποτε τον συνδυασμό της διεύθυνσης IP με ένα cookie. Αποθηκεύστε ένα μυστικό κουπόνι, π.χ. `secret1234`, στο cookie `tracy-debug` και, με αυτόν τον τρόπο, ενεργοποιήστε τη λειτουργία ανάπτυξης μόνο για προγραμματιστές που έχουν πρόσβαση από συγκεκριμένη διεύθυνση IP και διαθέτουν το αναφερόμενο κουπόνι στο cookie: +Σίγουρα συνιστούμε να συνδυάσετε τη διεύθυνση IP με ένα cookie. Στο cookie `tracy-debug` αποθηκεύουμε ένα μυστικό διακριτικό, π.χ. `secret1234`, και με αυτόν τον τρόπο ενεργοποιούμε τη λειτουργία ανάπτυξης μόνο για προγραμματιστές που έχουν πρόσβαση από μια συγκεκριμένη διεύθυνση IP και έχουν το αναφερόμενο διακριτικό στο cookie: ```php Debugger::enable('secret1234@23.75.345.200'); ``` -Μπορείτε επίσης να ορίσετε απευθείας τη λειτουργία ανάπτυξης/παραγωγής χρησιμοποιώντας τις σταθερές `Debugger::Development` ή `Debugger::Production` ως παράμετρο της μεθόδου `enable()`. +Μπορούμε επίσης να ορίσουμε απευθείας τη λειτουργία ανάπτυξης/παραγωγής χρησιμοποιώντας τη σταθερά `Debugger::Development` ή `Debugger::Production` ως παράμετρο της μεθόδου `enable()`. .[note] -Αν χρησιμοποιείτε το Nette Framework, ρίξτε μια ματιά στον τρόπο [ορισμού της λειτουργίας για αυτό |application:bootstrap#Development vs Production Mode], και στη συνέχεια θα χρησιμοποιηθεί και για το Tracy. +Αν χρησιμοποιείτε το Nette Framework, ρίξτε μια ματιά στο πώς να [ρυθμίσετε τη λειτουργία για αυτό |application:bootstrapping#Λειτουργία Ανάπτυξης vs Παραγωγής] και αυτό θα χρησιμοποιηθεί στη συνέχεια και για την Tracy. -Καταγραφή σφαλμάτων .[#toc-error-logging] -========================================= +Καταγραφή σφαλμάτων +=================== -Στη λειτουργία παραγωγής, το Tracy καταγράφει αυτόματα όλα τα σφάλματα και τις εξαιρέσεις σε ένα αρχείο καταγραφής κειμένου. Για να πραγματοποιηθεί η καταγραφή, πρέπει να ορίσετε την απόλυτη διαδρομή προς τον κατάλογο καταγραφής στη μεταβλητή `$logDirectory` ή να την περάσετε ως δεύτερη παράμετρο στη μέθοδο `enable()`: +Σε λειτουργία παραγωγής, η Tracy καταγράφει αυτόματα όλα τα σφάλματα και τις παγιδευμένες εξαιρέσεις σε ένα αρχείο καταγραφής κειμένου. Για να μπορεί να πραγματοποιηθεί η καταγραφή, πρέπει να ορίσουμε την απόλυτη διαδρομή προς τον κατάλογο καταγραφής στη μεταβλητή `$logDirectory` ή να την περάσουμε ως δεύτερη παράμετρο της μεθόδου `enable()`: ```php Debugger::$logDirectory = __DIR__ . '/log'; ``` -Η καταγραφή σφαλμάτων είναι εξαιρετικά χρήσιμη. Φανταστείτε ότι όλοι οι χρήστες της εφαρμογής σας είναι στην πραγματικότητα δοκιμαστές beta που κάνουν κορυφαία δουλειά στην εύρεση σφαλμάτων δωρεάν και θα ήταν ανόητο να πετάξετε τις πολύτιμες αναφορές τους απαρατήρητες στον κάδο απορριμμάτων. +Η καταγραφή σφαλμάτων είναι ταυτόχρονα εξαιρετικά χρήσιμη. Φανταστείτε ότι όλοι οι χρήστες της εφαρμογής σας είναι στην πραγματικότητα beta testers, οι οποίοι δωρεάν κάνουν εξαιρετική δουλειά στην εύρεση σφαλμάτων και θα κάνατε μια ανοησία αν πετάγατε τις πολύτιμες αναφορές τους χωρίς προσοχή στον κάδο απορριμμάτων. -Αν πρέπει να καταγράψετε τα δικά σας μηνύματα ή τις εξαιρέσεις που πιάσατε, χρησιμοποιήστε τη μέθοδο `log()`: +Αν χρειάζεται να καταγράψουμε ένα προσαρμοσμένο μήνυμα ή μια εξαίρεση που παγιδεύσατε, χρησιμοποιούμε για αυτό τη μέθοδο `log()`: ```php -Debugger::log('Unexpected error'); // μήνυμα κειμένου +Debugger::log('Παρουσιάστηκε ένα απροσδόκητο σφάλμα'); // μήνυμα κειμένου try { - criticalOperation(); + kritickaOperace(); // κρίσιμη λειτουργία } catch (Exception $e) { - Debugger::log($e); // εξαίρεση καταγραφής + Debugger::log($e); // μπορείτε επίσης να καταγράψετε την εξαίρεση // ή - Debugger::log($e, Debugger::ERROR); // στέλνει επίσης ειδοποίηση μέσω email + Debugger::log($e, Debugger::ERROR); // αποστέλλει και ειδοποίηση μέσω e-mail } ``` -If you want Tracy to log PHP errors like `E_NOTICE` or `E_WARNING` with detailed information (HTML report), set `Debugger::$logSeverity`: +Αν θέλετε η Tracy να καταγράφει σφάλματα PHP όπως `E_NOTICE` ή `E_WARNING` με λεπτομερείς πληροφορίες (αναφορά HTML), ορίστε το `Debugger::$logSeverity`: ```php Debugger::$logSeverity = E_NOTICE | E_WARNING; ``` -Για έναν πραγματικό επαγγελματία το αρχείο καταγραφής σφαλμάτων είναι μια κρίσιμη πηγή πληροφοριών και θέλει να ενημερώνεται αμέσως για κάθε νέο σφάλμα. Το Tracy τον βοηθάει. Είναι σε θέση να στέλνει ένα μήνυμα ηλεκτρονικού ταχυδρομείου για κάθε νέα εγγραφή σφάλματος. Η μεταβλητή $email προσδιορίζει πού θα στέλνει αυτά τα μηνύματα ηλεκτρονικού ταχυδρομείου: +Για έναν πραγματικό επαγγελματία, το αρχείο καταγραφής σφαλμάτων είναι μια βασική πηγή πληροφοριών και θέλει να ενημερωθεί αμέσως για κάθε νέο σφάλμα. Η Tracy βοηθάει σε αυτό, καθώς μπορεί να ενημερώσει μέσω e-mail για μια νέα εγγραφή στο αρχείο καταγραφής. Καθορίζουμε πού να στέλνουμε τα e-mail με τη μεταβλητή $email: ```php Debugger::$email = 'admin@example.com'; ``` -Εάν χρησιμοποιείτε ολόκληρο το Nette Framework, μπορείτε να ορίσετε αυτήν και άλλες στο [αρχείο ρυθμίσεων |nette:configuring]. +Αν χρησιμοποιείτε ολόκληρο το Nette Framework, αυτό και άλλα μπορούν να ρυθμιστούν στο [αρχείο διαμόρφωσης |nette:configuring]. -Για να προστατέψει το ηλεκτρονικό σας ταχυδρομείο από πλημμύρες, το Tracy στέλνει **μόνο ένα μήνυμα** και δημιουργεί ένα αρχείο `email-sent`. Όταν ένας προγραμματιστής λαμβάνει την ειδοποίηση ηλεκτρονικού ταχυδρομείου, ελέγχει το αρχείο καταγραφής, διορθώνει την εφαρμογή του και διαγράφει το αρχείο παρακολούθησης `email-sent`. Αυτό ενεργοποιεί και πάλι την αποστολή ηλεκτρονικού ταχυδρομείου. +Ωστόσο, για να μην πλημμυρίσει η θυρίδα ηλεκτρονικού ταχυδρομείου σας, στέλνει πάντα **μόνο ένα μήνυμα** και δημιουργεί το αρχείο `email-sent`. Ο προγραμματιστής, μετά τη λήψη της ειδοποίησης μέσω e-mail, ελέγχει το αρχείο καταγραφής, διορθώνει την εφαρμογή και διαγράφει το αρχείο παρακολούθησης, με αυτό ενεργοποιείται ξανά η αποστολή e-mail. -Άνοιγμα αρχείων στον επεξεργαστή .[#toc-opening-files-in-the-editor] -==================================================================== +Άνοιγμα στον editor +=================== -Όταν εμφανίζεται η σελίδα σφαλμάτων, μπορείτε να κάνετε κλικ σε ονόματα αρχείων και αυτά θα ανοίξουν στον επεξεργαστή σας με τον κέρσορα στην αντίστοιχη γραμμή. Μπορείτε επίσης να δημιουργήσετε αρχεία (ενέργεια `create file`) ή να διορθώσετε σφάλματα σε αυτά (ενέργεια `fix it`). Για να το κάνετε αυτό, πρέπει να [ρυθμίσετε το πρόγραμμα περιήγησης και το σύστημα |open-files-in-ide]. +Κατά την εμφάνιση της σελίδας σφάλματος, μπορείτε να κάνετε κλικ στα ονόματα των αρχείων και αυτά θα ανοίξουν στον editor σας με τον δρομέα στην αντίστοιχη γραμμή. Μπορείτε επίσης να δημιουργήσετε αρχεία (ενέργεια `create file`) ή να διορθώσετε σφάλματα σε αυτά (ενέργεια `fix it`). Για να λειτουργήσει αυτό, αρκεί να [διαμορφώσετε τον περιηγητή και το σύστημα |open-files-in-ide]. -Υποστηριζόμενες εκδόσεις PHP .[#toc-supported-php-versions] -=========================================================== +Υποστηριζόμενες εκδόσεις PHP +============================ -| Tracy | συμβατή με PHP -|-----------|-------------------- -| Tracy 2.10 – 3.0 | PHP 8.0 - 8.2 -| Tracy 2.9 | PHP 7.2 - 8.2 -| Tracy 2.8 | PHP 7.2 - 8.1 -| Tracy 2.6 - 2.7 | PHP 7.1 - 8.0 -| Tracy 2.5 | PHP 5.4 - 7.4 -| Tracy 2.4 | PHP 5.4 - 7.2 +| Tracy | συμβατό με PHP +|-----------|------------------- +| Tracy 2.10 – 3.0 | PHP 8.0 – 8.4 +| Tracy 2.9 | PHP 7.2 – 8.2 +| Tracy 2.8 | PHP 7.2 – 8.1 +| Tracy 2.6 – 2.7 | PHP 7.1 – 8.0 +| Tracy 2.5 | PHP 5.4 – 7.4 +| Tracy 2.4 | PHP 5.4 – 7.2 -Ισχύει για τις τελευταίες εκδόσεις διορθώσεων. +Ισχύει για την τελευταία έκδοση patch. -Λιμάνια .[#toc-ports] -===================== +Ports +===== -Αυτή είναι μια λίστα με ανεπίσημες μεταφορές σε άλλα πλαίσια και CMS: +Αυτή είναι μια λίστα ανεπίσημων ports για άλλα frameworks και CMS: - [Drupal 7](https://www.drupal.org/project/traced) - Laravel framework: [recca0120/laravel-tracy](https://github.com/recca0120/laravel-tracy), [whipsterCZ/laravel-tracy](https://github.com/whipsterCZ/laravel-tracy) diff --git a/tracy/el/open-files-in-ide.texy b/tracy/el/open-files-in-ide.texy index 13621cdf1c..c69ae29eb1 100644 --- a/tracy/el/open-files-in-ide.texy +++ b/tracy/el/open-files-in-ide.texy @@ -1,20 +1,20 @@ -Πώς να ανοίξετε ένα αρχείο στον Editor από το Tracy; (Ενσωμάτωση IDE) -********************************************************************* +Πώς να ανοίξετε ένα αρχείο στον editor από την Tracy; (Ενσωμάτωση με IDE) +************************************************************************* .[perex] -Όταν εμφανίζεται η σελίδα σφάλματος, μπορείτε να κάνετε κλικ στα ονόματα των αρχείων και αυτά θα ανοίξουν στον επεξεργαστή σας με τον κέρσορα στην αντίστοιχη γραμμή. Μπορείτε επίσης να δημιουργήσετε αρχεία (ενέργεια `create file`) ή να διορθώσετε σφάλματα σε αυτά (ενέργεια `fix it`). Για να το κάνετε αυτό, πρέπει να ρυθμίσετε το πρόγραμμα περιήγησης και το σύστημα. +Κατά την εμφάνιση της σελίδας σφάλματος, μπορείτε να κάνετε κλικ στα ονόματα των αρχείων και αυτά θα ανοίξουν στον editor σας με τον δρομέα στην αντίστοιχη γραμμή. Μπορείτε επίσης να δημιουργήσετε αρχεία (ενέργεια `create file`) ή να διορθώσετε σφάλματα σε αυτά (ενέργεια `fix it`). Για να γίνει αυτό, είναι απαραίτητο να διαμορφώσετε τον περιηγητή και το σύστημα. -Το Tracy ανοίγει αρχεία μέσω διευθύνσεων URL της μορφής `editor://open/?file=%file&line=%line`, δηλαδή με το πρωτόκολλο `editor://`. Θα καταχωρήσουμε τον δικό μας χειριστή για αυτό. Αυτός μπορεί να είναι οποιοδήποτε εκτελέσιμο αρχείο που επεξεργάζεται τις παραμέτρους και ξεκινά τον αγαπημένο μας επεξεργαστή. +Η Tracy ανοίγει αρχεία μέσω URL με τη μορφή `editor://open/?file=%file&line=%line`, δηλαδή με το πρωτόκολλο `editor://`. Για αυτό, θα εγγράψουμε έναν προσαρμοσμένο χειριστή. Αυτός μπορεί να είναι οποιοδήποτε εκτελέσιμο αρχείο που θα "επεξεργαστεί" τις παραμέτρους και θα εκκινήσει τον αγαπημένο μας editor. -Μπορείτε να αλλάξετε τη διεύθυνση URL στη μεταβλητή `Tracy\Debugger::$editor` ή να απενεργοποιήσετε το click-through θέτοντας το `Tracy\Debugger::$editor = null`. +Μπορείτε να αλλάξετε το URL στη μεταβλητή `Tracy\Debugger::$editor`, ή να απενεργοποιήσετε το κλικ ορίζοντας `Tracy\Debugger::$editor = null`. -Windows .[#toc-windows] -======================= +Windows +======= -1. Κατεβάστε τα κατάλληλα αρχεία "από το αποθετήριο Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/windows στο δίσκο. +1. Κατεβάστε τα σχετικά αρχεία "από το αποθετήριο της Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/windows στο δίσκο. -2. Επεξεργαστείτε το `open-editor.js` και ξεσχολιάστε ή επεξεργαστείτε τη διαδρομή προς τον επεξεργαστή σας στο `settings`: +2. Επεξεργαστείτε το αρχείο `open-editor.js` και στον πίνακα `settings` αποσχολιάστε και, ενδεχομένως, τροποποιήστε τη διαδρομή προς τον editor σας: ```js var settings = { @@ -35,19 +35,28 @@ var settings = { ... ``` -Να είστε προσεκτικοί και να διατηρήσετε τις διπλές κάθετους στις διαδρομές. +Προσοχή, διατηρήστε τις διπλές κάθετους στις διαδρομές. -3. Καταχωρήστε τον χειριστή για το πρωτόκολλο `editor://` στο σύστημα. +3. Εγγράψτε τον χειριστή του πρωτοκόλλου `editor://` στο σύστημα. -Αυτό γίνεται εκτελώντας το `install.cmd`. **Πρέπει να το εκτελέσετε ως διαχειριστής.** Το σενάριο `open-editor.js` θα εξυπηρετεί τώρα το πρωτόκολλο `editor://`. +Αυτό γίνεται εκτελώντας το αρχείο `install.cmd`. **Πρέπει να εκτελεστεί ως Διαχειριστής.** Το σενάριο `open-editor.js` θα χειρίζεται πλέον το πρωτόκολλο `editor://`. +Για να είναι δυνατή η έναρξη συνδέσμων που δημιουργήθηκαν σε άλλους διακομιστές, όπως σε έναν ζωντανό διακομιστή ή στο Docker, προσθέστε στο `open-editor.js` και την αντιστοίχιση της απομακρυσμένης URL στην τοπική: + +```js + mappings: { + // απομακρυσμένη διαδρομή: τοπική διαδρομή + '/var/www/nette.app': 'W:\\Nette.web\\_web', + } +``` -Linux .[#toc-linux] -=================== -1. Κατεβάστε τα κατάλληλα αρχεία "από το αποθετήριο Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux στον κατάλογο `~/bin`. +Linux +===== -2. Επεξεργαστείτε το `open-editor.sh` και ξεσχολιάστε ή επεξεργαστείτε τη διαδρομή προς τον επεξεργαστή σας στη μεταβλητή `editor`: +1. Κατεβάστε τα σχετικά αρχεία "από το αποθετήριο της Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux στον κατάλογο `~/bin`. + +2. Επεξεργαστείτε το αρχείο `open-editor.sh` και αποσχολιάστε και, ενδεχομένως, τροποποιήστε τη διαδρομή προς τον editor σας στη μεταβλητή `editor`. ```shell #!/bin/bash @@ -67,24 +76,25 @@ Linux .[#toc-linux] ... ``` -Κάντε το εκτελέσιμο: +Κάντε το αρχείο εκτελέσιμο: ```shell chmod +x ~/bin/open-editor.sh ``` -Αν ο επεξεργαστής που χρησιμοποιείτε δεν έχει εγκατασταθεί από το πακέτο, το δυαδικό αρχείο πιθανόν να μην έχει διαδρομή στο `$PATH`. Αυτό μπορεί να διορθωθεί εύκολα. Στον κατάλογο `~/bin`, δημιουργήστε έναν συμβολικό σύνδεσμο στο δυαδικό αρχείο του επεξεργαστή. .[note] +.[note] +Αν ο χρησιμοποιούμενος editor δεν είναι εγκατεστημένος από πακέτο, πιθανώς το εκτελέσιμο δεν θα έχει διαδρομή στο $PATH. Αυτό μπορεί εύκολα να διορθωθεί. Στον κατάλογο `~/bin` δημιουργήστε έναν συμβολικό σύνδεσμο προς το εκτελέσιμο του editor. -3. Καταχωρήστε τον χειριστή για το πρωτόκολλο `editor://` στο σύστημα. +3. Εγγράψτε τον χειριστή του πρωτοκόλλου `editor://` στο σύστημα. -Αυτό γίνεται με την εκτέλεση του `install.sh`. Το σενάριο `open-editor.js` θα εξυπηρετεί τώρα το πρωτόκολλο `editor://`. +Αυτό γίνεται εκτελώντας το αρχείο `install.sh`. Το σενάριο `open-editor.sh` θα χειρίζεται πλέον το πρωτόκολλο `editor://`. -macOS .[#toc-macos] -=================== +macOS +===== -Επεξεργαστές όπως το PhpStorm, το TextMate κ.λπ. σας επιτρέπουν να ανοίγετε αρχεία μέσω μιας ειδικής διεύθυνσης URL, την οποία απλώς πρέπει να ορίσετε: +Editors όπως το PhpStorm, TextMate κ.λπ. επιτρέπουν το άνοιγμα αρχείων μέσω ενός ειδικού URL, το οποίο αρκεί να ρυθμίσετε: ```php // PhpStorm @@ -92,44 +102,43 @@ Tracy\Debugger::$editor = 'phpstorm://open?file=%file&line=%line'; // TextMate Tracy\Debugger::$editor = 'txmt://open/?url=file://%file&line=%line'; // MacVim -Tracy\Debugger::$editor = 'mvim://open/?url=file://%file&line=%line'; +Tracy\Debugger::$editor = 'mvim://open?url=file:///%file&line=%line'; // Visual Studio Code Tracy\Debugger::$editor = 'vscode://file/%file:%line'; ``` -Αν χρησιμοποιείτε το standalone Tracy, βάλτε τη γραμμή πριν από το `Tracy\Debugger::enable()`, αν το Nette, πριν από το `$configurator->enableTracy()` στο `Bootstrap.php`. +Αν χρησιμοποιείτε την αυτόνομη Tracy, εισαγάγετε τη γραμμή πριν από το `Tracy\Debugger::enable()`, αν χρησιμοποιείτε το Nette, τότε πριν από το `$configurator->enableTracy()` στο `Bootstrap.php`. -Δυστυχώς, οι ενέργειες `create file` ή `fix it` δεν λειτουργούν στο macOS. +Οι ενέργειες `create file` ή `fix it` δυστυχώς δεν λειτουργούν στο macOS. -Επιδείξεις .[#toc-demos] -======================== +Παραδείγματα +============ Διόρθωση σφάλματος: -<iframe width="560" height="315" src="<m id=49/> frameborder="0" allow="encrypted-media" allowfullscreen></iframe> +<iframe width="560" height="315" src="https://www.youtube.com/embed/3ITT4mC0Eq4?rel=0&showinfo=0" frameborder="0" allow="encrypted-media" allowfullscreen></iframe> -Δημιουργία νέου αρχείου: +Δημιουργία αρχείου: -<iframe width="560" height="315" src="https://www.youtube.com/embed/AJ_FUivAGZQ?rel=0&showinfo=0" frameborder="0" allow="encrypted-media" allowfullscreen></iframe> +<iframe width="560" height="315" src="https://www.youtube.com/embed/AJ_FUivAGZQ?rel=0&showinfo=0" frameborder="0" allow="encrypted-media" allowfullscreen></iframe> -Αντιμετώπιση προβλημάτων .[#toc-troubleshooting] -================================================ +Επίλυση προβλημάτων +=================== -- Στον Firefox μπορεί να χρειαστεί να [επιτρέψετε την |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] εκτέλεση προσαρμοσμένου πρωτοκόλλου στο about:config, ορίζοντας το `network.protocol-handler.expose.editor` σε `false` και το `network.protocol-handler.expose-all` σε `true`. Θα πρέπει να επιτρέπεται από προεπιλογή, ωστόσο. -- Αν δεν λειτουργούν όλα αμέσως, μην πανικοβάλλεστε. Προσπαθήστε να ανανεώσετε τη σελίδα, να κάνετε επανεκκίνηση του προγράμματος περιήγησης ή του υπολογιστή. Αυτό θα πρέπει να βοηθήσει. -- Δείτε [εδώ |https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/] για να το διορθώσετε: - Σφάλμα εισόδου: Ίσως συσχετίσατε το αρχείο ".js" με μια άλλη εφαρμογή, όχι με τη μηχανή JScript. +- Στον Firefox, μπορεί να χρειαστεί να ενεργοποιήσετε το πρωτόκολλο [ρυθμίζοντας |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] το `network.protocol-handler.expose.editor` σε `false` και το `network.protocol-handler.expose-all` σε `true` στο about:config. +- Αν δεν λειτουργήσει αμέσως, μην πανικοβάλλεστε και δοκιμάστε να ανανεώσετε τη σελίδα μερικές φορές πριν κάνετε κλικ στον σύνδεσμο. Θα ξεκινήσει! +- Εδώ είναι ένας [σύνδεσμος|https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/] για τη διόρθωση πιθανού σφάλματος: `Σφάλμα εισόδου: Δεν υπάρχει μηχανή σεναρίων για την επέκταση αρχείου ".js"`, `Ίσως συσχετίσατε το αρχείο ".js" με άλλη εφαρμογή, όχι με τη μηχανή JScript.` αντίστοιχα `για την επέκταση .js δεν είναι διαθέσιμη καμία μηχανή σεναρίων`. -Ξεκινώντας από την έκδοση 77 του Google Chrome, δεν θα βλέπετε πλέον το πλαίσιο ελέγχου "Να ανοίγετε πάντα αυτούς τους τύπους συνδέσμων στη συνδεδεμένη εφαρμογή" όταν ανοίγει ο επεξεργαστής μέσω ενός συνδέσμου. Αντιμετώπιση για τα Windows: δημιουργήστε το αρχείο `fix.reg`: +Στο Google Chrome από την έκδοση 77, δεν θα βλέπετε πλέον το πλαίσιο ελέγχου „Πάντα να ανοίγετε αυτόν τον τύπο συνδέσμων στη συσχετισμένη εφαρμογή“, όταν ο editor εκτελείται μέσω συνδέσμου. Λύση για Windows: δημιουργήστε ένα αρχείο `fix.reg`: ``` Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\URLWhitelist] "123"="editor://*" ``` -Εισάγετε το με διπλό κλικ και επανεκκινήστε το Chrome. +Εισαγάγετέ το με διπλό κλικ και επανεκκινήστε τον Chrome. -Σε περίπτωση περισσότερων προβλημάτων ή ερωτήσεων, ρωτήστε στο [φόρουμ |https://forum.nette.org]. +Για τυχόν ερωτήσεις ή σχόλια, παρακαλούμε απευθυνθείτε στο [φόρουμ |https://forum.nette.org]. diff --git a/tracy/el/recipes.texy b/tracy/el/recipes.texy index d5381ba77d..6009bcd080 100644 --- a/tracy/el/recipes.texy +++ b/tracy/el/recipes.texy @@ -1,12 +1,11 @@ -Συνταγές -******** +Οδηγοί +****** -Πολιτική ασφάλειας περιεχομένου .[#toc-content-security-policy] -=============================================================== +Content Security Policy +======================= -Εάν ο ιστότοπός σας χρησιμοποιεί Πολιτική ασφάλειας περιεχομένου, θα πρέπει να προσθέσετε `'nonce-<value>'` και το `'strict-dynamic'` στο `script-src` για να λειτουργήσει σωστά το Tracy. Ορισμένα πρόσθετα 3ου τύπου μπορεί να απαιτούν πρόσθετες οδηγίες. -Το Nonce δεν υποστηρίζεται στην οδηγία `style-src`, αν χρησιμοποιείτε αυτή την οδηγία θα πρέπει να προσθέσετε το `'unsafe-inline'`, αλλά αυτό θα πρέπει να αποφεύγεται σε λειτουργία παραγωγής. +Αν ο ιστότοπός σας χρησιμοποιεί Content Security Policy, θα πρέπει να προσθέσετε τα ίδια `'nonce-<value>'` και `'strict-dynamic'` στο `script-src`, ώστε η Tracy να λειτουργεί σωστά. Ορισμένα πρόσθετα τρίτων μπορεί να απαιτούν πρόσθετες ρυθμίσεις. Το Nonce δεν υποστηρίζεται στην οδηγία `style-src`, αν χρησιμοποιείτε αυτήν την οδηγία, πρέπει να προσθέσετε `'unsafe-inline'`, αλλά σε λειτουργία παραγωγής θα πρέπει να το αποφύγετε. Παράδειγμα διαμόρφωσης για το [Nette Framework |nette:configuring]: @@ -16,7 +15,7 @@ http: script-src: [nonce, strict-dynamic] ``` -PHP: +Παράδειγμα σε καθαρό PHP: ```php $nonce = base64_encode(random_bytes(20)); @@ -24,11 +23,10 @@ header("Content-Security-Policy: script-src 'nonce-$nonce' 'strict-dynamic';"); ``` -Πιο γρήγορη φόρτωση .[#toc-faster-loading] -========================================== +Ταχύτερη φόρτωση +================ -Η βασική ενσωμάτωση είναι απλή, ωστόσο αν έχετε αργά σενάρια που μπλοκάρουν την ιστοσελίδα, μπορεί να επιβραδύνουν τη φόρτωση του Tracy. -Η λύση είναι να τοποθετήσετε `<?php Tracy\Debugger::renderLoader() ?>` στο πρότυπό σας πριν από οποιαδήποτε σενάρια: +Η εκκίνηση είναι απλή, ωστόσο, αν έχετε στην ιστοσελίδα αργά φορτωνόμενα σενάρια που μπλοκάρουν, μπορούν να επιβραδύνουν τη φόρτωση της Tracy. Η λύση είναι να τοποθετήσετε το `<?php Tracy\Debugger::renderLoader() ?>` στο πρότυπό σας πριν από όλα τα σενάρια: ```latte <!DOCTYPE html> @@ -42,12 +40,37 @@ header("Content-Security-Policy: script-src 'nonce-$nonce' 'strict-dynamic';"); ``` -AJAX και ανακατευθυνόμενες αιτήσεις .[#toc-ajax-and-redirected-requests] -======================================================================== +Εντοπισμός σφαλμάτων αιτημάτων AJAX +=================================== -Το Tracy μπορεί να εμφανίσει τη γραμμή εντοπισμού σφαλμάτων και τα Bluescreens για αιτήσεις AJAX και ανακατευθύνσεις. Το Tracy δημιουργεί τις δικές του συνεδρίες, αποθηκεύει δεδομένα στα δικά του προσωρινά αρχεία και χρησιμοποιεί ένα cookie `tracy-session`. +Η Tracy παγιδεύει αυτόματα τα αιτήματα AJAX που δημιουργήθηκαν με το jQuery ή το εγγενές API `fetch`. Τα αιτήματα εμφανίζονται στη γραμμή Tracy ως πρόσθετες γραμμές, το οποίο επιτρέπει εύκολο και άνετο εντοπισμό σφαλμάτων AJAX. -Το Tracy μπορεί επίσης να ρυθμιστεί ώστε να χρησιμοποιεί μια εγγενή συνεδρία PHP, η οποία ξεκινά πριν από την ενεργοποίηση του Tracy: +Αν δεν θέλετε να παγιδεύετε αυτόματα τα αιτήματα AJAX, μπορείτε να απενεργοποιήσετε αυτήν τη λειτουργία ρυθμίζοντας τη μεταβλητή JavaScript: + +```js +window.TracyAutoRefresh = false; +``` + +Για χειροκίνητη παρακολούθηση συγκεκριμένων αιτημάτων AJAX, προσθέστε την κεφαλίδα HTTP `X-Tracy-Ajax` με την τιμή που επιστρέφει το `Tracy.getAjaxHeader()`. Εδώ είναι ένα παράδειγμα χρήσης με τη συνάρτηση `fetch`: + +```js +fetch(url, { + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Tracy-Ajax': Tracy.getAjaxHeader(), + } +}) +``` + +Αυτή η προσέγγιση επιτρέπει επιλεκτικό εντοπισμό σφαλμάτων αιτημάτων AJAX. + + +Αποθήκευση δεδομένων +==================== + +Η Tracy μπορεί να εμφανίσει πάνελ στο Tracy bar και Bluescreens για αιτήματα AJAX και ανακατευθύνσεις. Η Tracy δημιουργεί τη δική της συνεδρία, αποθηκεύει δεδομένα στα δικά της προσωρινά αρχεία και χρησιμοποιεί το cookie `tracy-session`. + +Η Tracy μπορεί επίσης να διαμορφωθεί έτσι ώστε να χρησιμοποιεί την εγγενή συνεδρία PHP, την οποία ξεκινάμε πριν ενεργοποιήσουμε την Tracy: ```php session_start(); @@ -55,44 +78,44 @@ Debugger::setSessionStorage(new Tracy\NativeSession); Debugger::enable(); ``` -Σε περίπτωση που η εκκίνηση μιας συνόδου απαιτεί πιο σύνθετη αρχικοποίηση, μπορείτε να εκκινήσετε το Tracy αμέσως (ώστε να μπορεί να χειριστεί τυχόν σφάλματα που θα προκύψουν) και στη συνέχεια να αρχικοποιήσετε τον χειριστή συνόδου και τέλος να ενημερώσετε το Tracy ότι η σύνοδος είναι έτοιμη να χρησιμοποιηθεί χρησιμοποιώντας τη συνάρτηση `dispatch()`: +Σε περίπτωση που η έναρξη της συνεδρίας απαιτεί πιο σύνθετη αρχικοποίηση, μπορείτε να εκκινήσετε την Tracy αμέσως (για να μπορεί να επεξεργαστεί τυχόν σφάλματα που προέκυψαν), στη συνέχεια να αρχικοποιήσετε τον χειριστή της συνεδρίας και τέλος να ενημερώσετε την Tracy ότι η συνεδρία είναι έτοιμη για χρήση χρησιμοποιώντας τη συνάρτηση `dispatch()`: ```php Debugger::setSessionStorage(new Tracy\NativeSession); Debugger::enable(); -// ακολουθούμενη από αρχικοποίηση συνόδου -// και έναρξη της συνεδρίας +// ακολουθεί η αρχικοποίηση της συνεδρίας +// και η έναρξη της συνεδρίας session_start(); Debugger::dispatch(); ``` -Η συνάρτηση `setSessionStorage()` υπάρχει από την έκδοση 2.9, ενώ πριν από αυτήν το Tracy χρησιμοποιούσε πάντα την εγγενή συνεδρία της PHP. +Η συνάρτηση `setSessionStorage()` υπάρχει από την έκδοση 2.9, πριν, η Tracy χρησιμοποιούσε πάντα την εγγενή συνεδρία PHP. -Προσαρμοσμένος καθαριστής .[#toc-custom-scrubber] -================================================= +Προσαρμοσμένος scrubber +======================= -Το Scrubber είναι ένα φίλτρο που αποτρέπει τη διαρροή ευαίσθητων δεδομένων από απορρίψεις, όπως κωδικούς πρόσβασης ή διαπιστευτήρια. Το φίλτρο καλείται για κάθε στοιχείο του πίνακα ή του αντικειμένου που απορρίπτεται και επιστρέφει `true` εάν η τιμή είναι ευαίσθητη. Σε αυτή την περίπτωση, αντί της τιμής εκτυπώνεται το `*****`. +Το Scrubber είναι ένα φίλτρο που αποτρέπει τη διαρροή ευαίσθητων δεδομένων κατά το dumping, όπως κωδικοί πρόσβασης ή διαπιστευτήρια. Το φίλτρο καλείται για κάθε στοιχείο του dump-αρισμένου πίνακα ή αντικειμένου και επιστρέφει `true` αν η τιμή είναι ευαίσθητη. Σε αυτήν την περίπτωση, αντί της τιμής, εκτυπώνεται `*****`. ```php -// αποφεύγει την απόρριψη τιμών κλειδιών και ιδιοτήτων όπως ο κωδικός πρόσβασης, +// αποτρέπει την εκτύπωση τιμών κλειδιών και ιδιοτήτων όπως `password`, // `password_repeat`, `check_password`, `DATABASE_PASSWORD`, κ.λπ. $scrubber = function(string $key, $value, ?string $class): bool { return preg_match('#password#i', $key) && $value !== null; }; -// το χρησιμοποιούμε για όλες τις απορρίψεις μέσα στο BlueScreen +// θα το χρησιμοποιήσουμε για όλα τα dumps μέσα στο BlueScreen Tracy\Debugger::getBlueScreen()->scrubber = $scrubber; ``` -Προσαρμοσμένος καταγραφέας .[#toc-custom-logger] -================================================ +Προσαρμοσμένος logger +===================== -Μπορούμε να δημιουργήσουμε έναν προσαρμοσμένο καταγραφέα για να καταγράφει σφάλματα, μη ληφθείσες εξαιρέσεις και επίσης να καλείται από το `Tracy\Debugger::log()`. Ο καταγραφέας υλοποιεί τη διεπαφή [api:Tracy\ILogger]. +Μπορούμε να δημιουργήσουμε τον δικό μας logger, ο οποίος θα καταγράφει σφάλματα, μη παγιδευμένες εξαιρέσεις και επίσης θα κληθεί από τη μέθοδο `Tracy\Debugger::log()`. Ο Logger υλοποιεί το interface [api:Tracy\ILogger]. ```php use Tracy\ILogger; @@ -101,18 +124,18 @@ class SlackLogger implements ILogger { public function log($value, $priority = ILogger::INFO) { - // στέλνει ένα αίτημα στο Slack + // στέλνει αίτημα στο Slack } } ``` -Και στη συνέχεια τον ενεργοποιούμε: +Και στη συνέχεια το ενεργοποιούμε: ```php Tracy\Debugger::setLogger(new SlackLogger); ``` -Εάν χρησιμοποιούμε το πλήρες Nette Framework, μπορούμε να το ορίσουμε στο αρχείο ρυθμίσεων NEON: +Αν χρησιμοποιούμε το πλήρες Nette Framework, μπορείτε να το ρυθμίσετε στο αρχείο διαμόρφωσης NEON: ```neon services: @@ -120,10 +143,10 @@ services: ``` -Monolog Integration .[#toc-monolog-integration] ------------------------------------------------ +Ενσωμάτωση Monolog +------------------ -Το πακέτο Tracy παρέχει έναν προσαρμογέα PSR-3, που επιτρέπει την ενσωμάτωση του [monolog/monolog](https://github.com/Seldaek/monolog). +Το πακέτο Tracy παρέχει έναν προσαρμογέα PSR-3 που επιτρέπει την ενσωμάτωση του [monolog/monolog](https://github.com/Seldaek/monolog). ```php $monolog = new Monolog\Logger('main-channel'); @@ -134,20 +157,20 @@ Debugger::setLogger($tracyLogger); Debugger::enable(); Debugger::log('info'); // γράφει: [<TIMESTAMP>] main-channel.INFO: info [] [] -Debugger::log('warning', Debugger::WARNING); // writes: [<TIMESTAMP>] main-channel.WARNING: warning [] [] +Debugger::log('warning', Debugger::WARNING); // γράφει: [<TIMESTAMP>] main-channel.WARNING: warning [] [] ``` -nginx .[#toc-nginx] -=================== +nginx +===== -Αν το Tracy δεν λειτουργεί στο nginx, είναι πιθανόν να μην έχει ρυθμιστεί σωστά. Αν υπάρχει κάτι σαν +Αν η Tracy δεν λειτουργεί στον διακομιστή nginx, πιθανότατα είναι λάθος διαμορφωμένος. Αν υπάρχει κάτι στη διαμόρφωση όπως: ```nginx try_files $uri $uri/ /index.php; ``` -αλλάξτε το σε +αλλάξτε το σε: ```nginx try_files $uri $uri/ /index.php$is_args$args; diff --git a/tracy/el/stopwatch.texy b/tracy/el/stopwatch.texy index c5e3f51e7d..7b89042191 100644 --- a/tracy/el/stopwatch.texy +++ b/tracy/el/stopwatch.texy @@ -1,19 +1,19 @@ -Χρονόμετρο -********** +Μέτρηση χρόνου +************** -Ένα άλλο χρήσιμο εργαλείο είναι το χρονόμετρο του αποσφαλματωτή με ακρίβεια μικροδευτερολέπτων: +Ένα άλλο χρήσιμο εργαλείο του debugger είναι το χρονόμετρο με ακρίβεια μικροδευτερολέπτου: ```php Debugger::timer(); -// γλυκά όνειρα γλυκιά μου +// κάποια χρονοβόρα λειτουργία... sleep(2); $elapsed = Debugger::timer(); // $elapsed = 2 ``` -Πολλαπλές μετρήσεις ταυτόχρονα μπορούν να επιτευχθούν μέσω μιας προαιρετικής παραμέτρου. +Με μια προαιρετική παράμετρο, είναι δυνατή η επίτευξη πολλαπλών μετρήσεων. ```php Debugger::timer('page-generating'); @@ -27,9 +27,9 @@ $pageElapsed = Debugger::timer('page-generating'); ``` ```php -Debugger::timer(); // τρέχει το χρονόμετρο +Debugger::timer(); // ενεργοποιεί το χρονόμετρο -... // κάποια χρονοβόρα λειτουργία +... // χρονοβόρα λειτουργία -echo Debugger::timer(); // χρόνος που παρήλθε σε δευτερόλεπτα +echo Debugger::timer(); // εκτυπώνει τον прошедшее χρόνο σε δευτερόλεπτα ``` diff --git a/tracy/en/@home.texy b/tracy/en/@home.texy index 14b9d41787..caba9e107d 100644 --- a/tracy/en/@home.texy +++ b/tracy/en/@home.texy @@ -1,2 +1,2 @@ {{maintitle: Tracy – A Must-Have Debugging Tool for All PHP Developers}} -{{description: Tracy is a tool designed to facilitate debugging PHP code. It is a useful assistant to all PHP programmers, which helps with clear visualizing and logging errors, dumping variables and a lot more. Warning: Tracy is addictive!}} +{{description: Tracy is a tool designed to facilitate debugging PHP code. It is a useful assistant for all PHP programmers, helping with clear visualization and logging of errors, dumping variables, and much more. Warning: Tracy is addictive!}} diff --git a/tracy/en/@meta.texy b/tracy/en/@meta.texy new file mode 100644 index 0000000000..c0491c0f50 --- /dev/null +++ b/tracy/en/@meta.texy @@ -0,0 +1 @@ +{{sitename: Tracy Documentation}} diff --git a/tracy/en/configuring.texy b/tracy/en/configuring.texy index a97e48b3de..7226c25fba 100644 --- a/tracy/en/configuring.texy +++ b/tracy/en/configuring.texy @@ -1,7 +1,7 @@ Tracy Configuration ******************* -Following examples assume the following class alias is defined: +The following examples assume the following class alias is defined: ```php use Tracy\Debugger; @@ -14,16 +14,16 @@ Error Logging ```php $logger = Debugger::getLogger(); -// if error has occurred the notification is sent to this email +// email(s) to send error notifications to $logger->email = 'dev@example.com'; // (string|string[]) defaults to unset // email sender $logger->fromEmail = 'me@example.com'; // (string) defaults to unset // routine for sending email -$logger->mailer = /* ... */; // (callable) default it sending by mail() +$logger->mailer = /* ... */; // (callable) default is sending by mail() -// after what shortest time to send another email? +// minimum interval for sending further emails? $logger->emailSnooze = /* ... */; // (string) default is '2 days' // for which error levels is BlueScreen also logged? @@ -36,10 +36,10 @@ Debugger::$logSeverity = E_WARNING | E_NOTICE; // defaults to 0 (no error level ```php // maximum string length -Debugger::$maxLength = 150; // (int) default according to Tracy +Debugger::$maxLength = 150; // (int) default according to Tracy version -// how deep will list -Debugger::$maxDepth = 10; // (int) default according to Tracy +// maximum nesting depth +Debugger::$maxDepth = 10; // (int) default according to Tracy version // hide values of these keys (since Tracy 2.8) Debugger::$keysToHide = ['password', /* ... */]; // (string[]) defaults to [] @@ -47,8 +47,8 @@ Debugger::$keysToHide = ['password', /* ... */]; // (string[]) defaults to [] // visual theme (since Tracy 2.8) Debugger::$dumpTheme = 'dark'; // (light|dark) defaults to 'light' -// displays the location where dump() was called? -Debugger::$showLocation = /* ... */; // (bool) default according to Tracy +// display location where dump() was called? +Debugger::$showLocation = /* ... */; // (bool) default according to Tracy version ``` @@ -56,19 +56,19 @@ Others ------ ```php -// in Development mode, you will see notice or error warnings as BlueScreen -Debugger::$strictMode = /* ... */; // (bool|int) defaults to false, you can select only specific error levels (e.g. E_USER_DEPRECATED | E_DEPRECATED) +// in development mode, display errors of type notice or warning as BlueScreen +Debugger::$strictMode = /* ... */; // (bool|int) defaults to false, specific error levels can be selected (e.g. E_USER_DEPRECATED | E_DEPRECATED) -// displays silent (@) error messages -Debugger::$scream = /* ... */; // (bool|int) defaults to false, since version 2.9 it is possible to select only specific error levels (e.g. E_USER_DEPRECATED | E_DEPRECATED) +// display silenced (@) error messages? +Debugger::$scream = /* ... */; // (bool|int) defaults to false, since version 2.9 specific error levels can be selected (e.g. E_USER_DEPRECATED | E_DEPRECATED) -// link format to open in the editor +// link format for opening in the editor Debugger::$editor = /* ... */; // (string|null) defaults to 'editor://open/?file=%file&line=%line' // path to template with custom page for error 500 Debugger::$errorTemplate = /* ... */; // (string) defaults to unset -// shows Tracy Bar? +// show Tracy Bar? Debugger::$showBar = /* ... */; // (bool) defaults to true Debugger::$editorMapping = [ @@ -82,38 +82,37 @@ Debugger::$editorMapping = [ Nette Framework --------------- -If you are using the Nette Framework, you can also configure Tracy and add new panels to the Tracy Bar using the configuration file. -You can set the Tracy parameters in the configuration and also add new panels to the Tracy Bar. These settings are applied only after the DI container has been created, so errors that occurred earlier cannot reflect them. +If you are using Nette Framework, you can configure Tracy and add new panels to the Tracy Bar using the configuration file. In the configuration, you can set parameters and add new panels to the Tracy Bar. These settings are applied only after the DI container is created, so errors occurring before that cannot reflect them. Error logging configuration: ```neon tracy: - # if error has occurred the notification is sent to this email + # email(s) to send error notifications to email: dev@example.com # (string|string[]) defaults to unset # email sender fromEmail: robot@example.com # (string) defaults to unset - # period of postponement of emails sending (since Tracy 2.8.8) + # email sending snooze time (since Tracy 2.8.8) emailSnooze: ... # (string) defaults to '2 days' - # to use a mailer defined in the configuration? (since Tracy 2.5) + # use Nette mailer for sending emails? (since Tracy 2.5) netteMailer: ... # (bool) defaults to true # for which error levels is BlueScreen also logged? logSeverity: [E_WARNING, E_NOTICE] # defaults to [] ``` -Configuration for function `dump()`: +Configuration for the `dump()` function: ```neon tracy: # maximum string length - maxLength: 150 # (int) default according to Tracy + maxLength: 150 # (int) default according to Tracy version - # how deep will list - maxDepth: 10 # (int) default according to Tracy + # maximum nesting depth + maxDepth: 10 # (int) default according to Tracy version # hide values of these keys (since Tracy 2.8) keysToHide: [password, pass] # (string[]) defaults to [] @@ -121,22 +120,22 @@ tracy: # visual theme (since Tracy 2.8) dumpTheme: dark # (light|dark) defaults to 'light' - # displays the location where dump() was called? - showLocation: ... # (bool) default according to Tracy + # display location where dump() was called? + showLocation: ... # (bool) default according to Tracy version ``` -To install the Tracy extension: +Tracy extension installation: ```neon tracy: - # appends bars to Tracy Bar + # adds panels to Tracy Bar bar: - Nette\Bridges\DITracy\ContainerPanel - IncludePanel - XDebugHelper('myIdeKey') - MyPanel(@MyService) - # append panels to BlueScreen + # adds panels to BlueScreen blueScreen: - DoctrinePanel::renderException ``` @@ -145,19 +144,19 @@ Other options: ```neon tracy: - # in Development mode, you will see notice or error warnings as BlueScreen + # in development mode, display errors of type notice or warning as BlueScreen strictMode: ... # defaults to true - # displays silent (@) error messages + # display silenced (@) error messages? scream: ... # defaults to false - # link format to open in the editor + # link format for opening in the editor editor: ... # (string) defaults to 'editor://open/?file=%file&line=%line' # path to template with custom page for error 500 errorTemplate: ... # (string) defaults to unset - # shows Tracy Bar? + # show Tracy Bar? showBar: ... # (bool) defaults to true editorMapping: @@ -167,3 +166,15 @@ tracy: ``` The values of the `logSeverity`, `strictMode` and `scream` options can be written as an array of error levels (e.g. `[E_WARNING, E_NOTICE]`) or as an expression used in PHP (e.g. `E_ALL & ~E_NOTICE`). + + +DI Services +----------- + +These services are added to the DI container: + +| Name | Type | Description +|---------------------------------------------------------- +| `tracy.logger` | [api:Tracy\ILogger] | logger +| `tracy.blueScreen` | [api:Tracy\BlueScreen] | BlueScreen +| `tracy.bar` | [api:Tracy\Bar] | Tracy Bar diff --git a/tracy/en/dumper.texy b/tracy/en/dumper.texy index 1711d2af77..c7290cab95 100644 --- a/tracy/en/dumper.texy +++ b/tracy/en/dumper.texy @@ -1,7 +1,7 @@ -Dumper -****** +Dumping Variables +***************** -Every debugging developer is a good friend with the function `var_dump`, which lists all contents of any variable in detail. Unfortunately, its output is without HTML formatting and outputs the dump into a single line of HTML code, not to mention context escaping. It is necessary to replace the `var_dump` with a more handy function. That is just what `dump()` is. +Every debugger is familiar with the [php:var_dump] function, which prints detailed information about a variable. Unfortunately, its output lacks HTML formatting and merges into a single line, not to mention HTML escaping issues. In practice, it's necessary to replace `var_dump` with a more convenient function. That function is `dump()`. ```php $arr = [10, 20.2, true, null, 'hello']; @@ -22,23 +22,23 @@ Debugger::$dumpTheme = 'dark'; [* dump-dark.webp *] -You can also change the nesting depth by `Debugger::$maxDepth` and displayed strings length by `Debugger::$maxLength`. Naturally, lower values accelerate Tracy rendering. +You can also change the nesting depth using [Debugger::$maxDepth |api:Tracy\Debugger::$maxDepth] and the length of displayed strings using [Debugger::$maxLength |api:Tracy\Debugger::$maxLength]. Naturally, lower values speed up rendering. ```php Debugger::$maxDepth = 2; // default: 3 Debugger::$maxLength = 50; // default: 150 ``` -The `dump()` function can display other useful information. `Tracy\Dumper::LOCATION_SOURCE` adds a tooltip with path to the file, where the function was called. `Tracy\Dumper::LOCATION_LINK` adds a link to the file. `Tracy\Dumper::LOCATION_CLASS` adds a tooltip to every dumped object containing path to the file, in which the object's class is defined. All these constants can be set in `Debugger::$showLocation` variable before calling the `dump()`. You can set multiple values at once using the `|` operator. +The `dump()` function can display additional useful information. The constant `Tracy\Dumper::LOCATION_SOURCE` adds a tooltip with the path to the file where the function was called. `Tracy\Dumper::LOCATION_LINK` provides a link to that location. `Tracy\Dumper::LOCATION_CLASS` adds a tooltip to each dumped object showing the path to the file where its class is defined. These constants are set in the `Debugger::$showLocation` variable before calling `dump()`. To set multiple values simultaneously, combine them using the `|` operator. ```php -Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // Shows path to where the dump() was called -Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Shows both paths to the classes and link to where the dump() was called -Debugger::$showLocation = false; // Hides additional location information -Debugger::$showLocation = true; // Shows all additional location information +Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // Sets only the display of the call location +Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Sets both the link display and the path to the class +Debugger::$showLocation = false; // Disables the display of additional information +Debugger::$showLocation = true; // Enables the display of all additional information ``` -Very handy alternative to `dump()` is `dumpe()` (ie. dump and exit) and `bdump()`. This allows us to dump variables in Tracy Bar. This is useful, because dumps don't mess up the output and we can also add a title to the dump. +Practical alternatives to `dump()` are `dumpe()` (dump & exit) and `bdump()`. The latter allows us to dump variable values in the Tracy Bar panel. This is very convenient, as the dumps are separate from the page layout, and we can also add a title to them. ```php bdump([2, 4, 6, 8], 'even numbers up to ten'); diff --git a/tracy/en/extensions.texy b/tracy/en/extensions.texy index 62cd6e17bc..6a90c18526 100644 --- a/tracy/en/extensions.texy +++ b/tracy/en/extensions.texy @@ -3,21 +3,21 @@ Creating Tracy Extensions <div class=perex> -Tracy is a great tool for debugging your application. However, you sometimes need more information than Tracy offers. You'll learn about: +Tracy is a great tool for debugging your application. However, you sometimes might want additional information readily available. We'll show you how to write your own extensions for the Tracy Bar to make development even more pleasant. -- Creating your own Tracy Bar panels -- Creating your own Bluescreen extensions +- Creating your own Tracy Bar panel +- Creating your own Bluescreen extension </div> .[tip] -You can find useful extensions for Tracy on "Componette":https://componette.org/search/tracy. +You can find a repository of ready-made extensions for Tracy on "Componette":https://componette.org/search/tracy. Tracy Bar Extensions ==================== -Creating a new extension for Tracy Bar is simple. You need to implement `Tracy\IBarPanel` interface with methods `getTab()` and `getPanel()`. The methods must return the HTML code of a tab (small label on Tracy Bar) and a panel (pop-up displayed after clicking on the tab). If `getPanel()` returns nothing, only the tab will be displayed. If `getTab()` returns nothing, nothing is displayed and `getPanel()` will not be called. +Creating a new extension for the Tracy Bar is straightforward. Create an object that implements the `Tracy\IBarPanel` interface, which has two methods: `getTab()` and `getPanel()`. These methods must return the HTML code for the tab (a small label displayed directly on the Bar) and the panel (a popup displayed after clicking the tab). If `getPanel()` returns nothing, only the tab itself is displayed. If `getTab()` returns nothing, nothing is displayed at all, and `getPanel()` is not called. ```php class ExamplePanel implements Tracy\IBarPanel @@ -38,13 +38,13 @@ class ExamplePanel implements Tracy\IBarPanel Registration ------------ -Registration is done by calling `Tracy\Bar::addPanel()`: +Registration is done by calling `Tracy\Debugger::getBar()->addPanel()`: ```php Tracy\Debugger::getBar()->addPanel(new ExamplePanel); ``` -or you can simply register your panel in application configuration: +Alternatively, you can register the panel directly in the application configuration: ```neon tracy: @@ -59,13 +59,13 @@ Tab HTML Code Should look something like this: ```latte -<span title="Explaining tooltip"> +<span title="Explanatory tooltip"> <svg>...</svg> <span class="tracy-label">Title</span> </span> ``` -Image should be in format SVG. If you don't need tooltip, you can leave `<span>` out. +The image should be in SVG format. If an explanatory tooltip is not needed, the outer `<span>` can be omitted. Panel HTML Code @@ -83,23 +83,23 @@ Should look something like this: </div> ``` -Title should either be the same as in tab or contain additional information. +The title should either be the same as the tab title or contain additional information. -One extension can be registered multiple times, so it's recommended not to use `id` attribute for styling. You can use classes, preferably in `tracy-addons-<class-name>[-<optional>]` format. When creating CSS, it's better to use `#tracy-debug .class`, because such rule has a higher priority than reset. +Keep in mind that a single extension might be registered multiple times, perhaps with different settings. Therefore, for styling, you cannot use CSS IDs, only classes, preferably in the format `tracy-addons-<ClassName>[-<optional>]`. Add this class to the div along with the `tracy-inner` class. When writing CSS, it's useful to prefix selectors with `#tracy-debug .your-class`, as this gives the rule higher specificity than the reset styles. Default Styles -------------- -In the panel, elements `<a>`, `<table>`, `<pre>`, `<code>` have default styles. For creating a link for hiding or displaying other element, connect them with `href` and `id` attributes and class `tracy-toggle`. +In the panel, `<a>`, `<table>`, `<pre>`, and `<code>` elements have predefined styles. If you want to create a link that hides and shows another element, connect them using `href` and `id` attributes and the `tracy-toggle` class: ```latte -<a href="#tracy-addons-className-{$counter}" class="tracy-toggle">Detail</a> +<a href="#tracy-addons-ClassName-{$counter}" class="tracy-toggle">Details</a> -<div id="tracy-addons-className-{$counter}">...</div> +<div id="tracy-addons-ClassName-{$counter}">...</div> ``` -If the default state is collapsed, add class `tracy-collapsed` to both elements. +If the default state is collapsed, add the class `tracy-collapsed` to both elements. Use a static counter to prevent duplicate IDs on one page. @@ -107,16 +107,16 @@ Use a static counter to prevent duplicate IDs on one page. Bluescreen Extensions ===================== -You can add your own exception visualizations or panels, which will appear on the bluescreen. +This way, you can add custom exception visualizations or panels that will appear on the bluescreen. -Extension is made like this: +An extension is created like this: ```php -Tracy\Debugger::getBlueScreen()->addPanel(function (?Throwable $e) { // catched exception +Tracy\Debugger::getBlueScreen()->addPanel(function (?Throwable $e) { // caught exception return [ 'tab' => '...Title...', - 'panel' => '...content...', + 'panel' => '...HTML panel content...', ]; }); ``` -The function is called twice, first the exception itself is passed in the `$e` parameter and the returned panel is rendered at the beginning of the page. If nothing is returned, the panel is not rendered. Then it is called with the `null` parameter and the returned panel is rendered below the callstack. If the function returns `'bottom' => true` in the array, the panel is rendered at the very bottom. +The function is called twice. First, the exception itself is passed in the `$e` parameter (if an exception occurred), and the returned panel is rendered at the beginning of the page. If it returns `null` or an empty array, the panel is not rendered. Then, it is called with `$e = null`, and the returned panel is rendered below the call stack. If the function returns `'bottom' => true` in the array, the panel is rendered at the very bottom. diff --git a/tracy/en/guide.texy b/tracy/en/guide.texy index 94b755fbcf..9ab2120f19 100644 --- a/tracy/en/guide.texy +++ b/tracy/en/guide.texy @@ -3,7 +3,7 @@ Getting Started with Tracy <div class=perex> -Tracy library is a useful helper for everyday PHP programmers. It helps you to: +The Tracy library is a useful everyday helper for PHP programmers. It helps you to: - quickly detect and correct errors - log errors @@ -14,27 +14,27 @@ Tracy library is a useful helper for everyday PHP programmers. It helps you to: </div> -PHP is a perfect language for making hardly detectable errors because it gives great flexibility to programmers. Tracy\Debugger is more valuable because of that. It is an ultimate tool among the diagnostic ones. +PHP is a language perfectly suited for creating hard-to-detect errors, as it gives developers considerable freedom. This makes a debugging tool like Tracy all the more valuable. It represents the absolute pinnacle among diagnostic tools for PHP. -If you are meeting Tracy for the first time, believe me, your life starts to be divided into one before the Tracy and the one with her. Welcome to the good part! +If you're encountering Tracy for the first time today, believe that your life will start to be divided into the time before Tracy and the time with her. Welcome to the better part! -Installation and Requirements -============================= +Installation +============ -The best way how to install Tracy is to [download the latest package](https://github.com/nette/tracy/releases) or use Composer: +The best way to install Tracy is to [download the latest package](https://github.com/nette/tracy/releases) or use Composer: ```shell composer require tracy/tracy ``` -Alternatively, you can download the whole package or [tracy.phar |https://github.com/nette/tracy/releases] file. +Alternatively, you can download the whole package or the [tracy.phar |https://github.com/nette/tracy/releases] file. Usage ===== -Tracy is activated by calling the `Tracy\Debugger::enable()' method as soon as possible at the beginning of the program, before any output is sent: +Tracy is activated by calling the `Tracy\Debugger::enable()` method as soon as possible at the beginning of the program, before any output is sent: ```php use Tracy\Debugger; @@ -44,20 +44,19 @@ require 'vendor/autoload.php'; // alternatively tracy.phar Debugger::enable(); ``` -The first thing you'll notice on the page is the Tracy Bar in the bottom right corner. If you don't see it, it may mean that Tracy is running in production mode. -This is because Tracy is only visible on localhost for security reasons. To test if it works, you can temporarily put it into development mode using the `Debugger::enable(Debugger::Development)` parameter. +The first thing you'll notice on the page is the Tracy Bar in the bottom right corner. If you don't see it, it may mean that Tracy is running in production mode. This is because Tracy is only visible on localhost for security reasons. To test if it works, you can temporarily put it into development mode using the `Debugger::enable(Debugger::Development)` parameter. Tracy Bar ========= -The Tracy Bar is a floating panel. It is displayed in the bottom right corner of a page. You can move it using the mouse. It will remember its position after the page reloading. +The Tracy Bar is a floating panel displayed in the bottom right corner of the page. You can move it with the mouse, and it will remember its position after the page reloads. [* tracy-bar.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html You can add other useful panels to the Tracy Bar. You can find interesting ones in [addons |https://componette.org] or you can [create your own |extensions]. -If you do not want to show Tracy Bar, set: +If you do not want to show the Tracy Bar, set: ```php Debugger::$showBar = false; @@ -67,40 +66,40 @@ Debugger::$showBar = false; Visualization of Errors and Exceptions ====================================== -Surely, you know how PHP reports errors: there is something like this in the page source code: +You surely know how PHP reports errors: it prints something like this into the page's source code: /--pre .{font-size: 90%} <b>Parse error</b>: syntax error, unexpected '}' in <b>HomePresenter.php</b> on line <b>15</b> \-- -or uncaught exception: +or an uncaught exception: /--pre .{font-size: 90%} <b>Fatal error</b>: Uncaught Nette\MemberAccessException: Call to undefined method Nette\Application\UI\Form::addTest()? in /sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php:100 Stack trace: #0 /sandbox/vendor/nette/utils/src/Utils/Object.php(75): Nette\Utils\ObjectMixin::call(Object(Nette\Application\UI\Form), 'addTest', Array) -#1 /sandbox/app/forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) -#2 /sandbox/app/presenters/SignPresenter.php(21): App\Forms\SignFormFactory->create() -#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presenters\SignPresenter->createComponentSignInForm('signInForm') +#1 /sandbox/app/Forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) +#2 /sandbox/app/Presentation/Sign/SignPresenter.php(21): App\Forms\SignFormFactory->create() +#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presentation\Sign\SignPresenter->createComponentSignInForm('signInForm') #4 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(139): Nette\ComponentModel\Container->createComponent('signInForm') #5 /sandbox/temp/cache/latte/15206b353f351f6bfca2c36cc.php(17): Nette\ComponentModel\Co in <b>/sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php</b> on line <b>100</b><br /> \-- -It is not so easy to navigate through this output. If you enable Tracy, both errors and exceptions are displayed in a completely different form: +Navigating such output isn't exactly easy. If you enable Tracy, errors and exceptions are displayed in a completely different form: [* tracy-exception.webp .{url:-} *] -The error message literally screams. You can see a part of the source code with the highlighted line where the error occurred. A message clearly explains an error. The entire site is [interactive, try it](https://nette.github.io/tracy/tracy-exception.html). +The error message literally screams. You can see the part of the source code with the highlighted line where the error occurred. The message *Call to undefined method Nette\Http\User::isLogedIn()* clearly explains the error. The entire page is interactive; you can click through for more details. [Try it |https://nette.github.io/tracy/tracy-exception.html]. -And you know what? Fatal errors are captured and displayed in the same way. No need to install any extension (click for live example): +And guess what? Fatal errors are captured and displayed in the same way. Without needing to install any extensions. [* tracy-error.webp .{url:-} *] -Errors like a typo in a variable name or an attempt to open a nonexistent file generate reports of E_NOTICE or E_WARNING level. These can be easily overlooked and/or can be completely hidden in a web page graphic layout. Let Tracy manage them: +Errors like a typo in a variable name or an attempt to open a non-existent file generate reports at the E_NOTICE or E_WARNING level. These can be easily overlooked within the page's graphical layout, or even be completely invisible (unless you look at the source code). Let Tracy manage them: [* tracy-notice2.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html -Or they may be displayed like errors: +Or they can be displayed like errors: ```php Debugger::$strictMode = true; // display all errors @@ -109,19 +108,19 @@ Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // all error [* tracy-notice.webp .{url:-} *] -Note: Tracy when activated changes the error reporting level to E_ALL. If you want to change this, do so after calling `enable()`. +Note: Tracy, when activated, changes the error reporting level to E_ALL. If you want to change this, do so after calling `enable()`. Development vs Production Mode ============================== -As you can see, Tracy is quite talkative, which can be appreciated in the development environment, while on the production server it would cause a disaster. That's because no debugging information should be displayed there. Tracy therefore has **environment auto-detection** and if the example is run on a live server, the error will be logged instead of displayed, and the visitor will only see a user-friendly message: +As you can see, Tracy is quite talkative, which can be appreciated in the development environment, while on the production server it would cause a disaster. That's because no debugging information should be displayed there. Tracy therefore has **environment auto-detection**. If the example is run on a live server, the error will be logged instead of displayed, and the visitor will only see a user-friendly message: [* tracy-error2.webp .{url:-} *] Production mode suppresses the display of all debugging information sent out using [dump() |dumper], and of course also all error messages generated by PHP. So if you have forgotten some `dump($obj)` in the code, you don't have to worry, nothing will be displayed on the production server. -How does mode auto-detection work? The mode is development if the application is running on localhost (i.e., IP address `127.0.0.1` or `::1`) and there is no proxy (i.e., its HTTP header). Otherwise, it runs in production mode. +How does mode auto-detection work? The mode is development if the application is running on localhost (i.e., IP address `127.0.0.1` or `::1`) and there is no proxy (i.e., its HTTP header is not present). Otherwise, it runs in production mode. If you want to enable development mode in other cases, for example for developers accessing from a specific IP address, you can specify it as a parameter of the `enable()` method: @@ -138,13 +137,13 @@ Debugger::enable('secret1234@23.75.345.200'); You can also directly set the development/production mode using the `Debugger::Development` or `Debugger::Production` constants as a parameter of the `enable()` method. .[note] -If you use the Nette Framework, take a look at how to [set the mode for it |application:bootstrap#Development vs Production Mode], and it will then also be used for Tracy. +If you use the Nette Framework, take a look at how to [set the mode for it |application:bootstrapping#Development vs Production Mode], and it will then also be used for Tracy. Error Logging ============= -In production mode, Tracy automatically logs all errors and exceptions to a text log. In order for logging to take place, you need to set the absolute path to the log directory to the `$logDirectory` variable or pass it as the second parameter to `enable()` method: +In production mode, Tracy automatically logs all errors and caught exceptions to a text log. For logging to work, you need to set the absolute path to the log directory in the `$logDirectory` variable or pass it as the second parameter to the `enable()` method: ```php Debugger::$logDirectory = __DIR__ . '/log'; @@ -152,7 +151,7 @@ Debugger::$logDirectory = __DIR__ . '/log'; Error logging is extremely useful. Imagine that all users of your application are actually beta testers who do top-notch work in finding errors for free, and you would be foolish to throw their valuable reports away unnoticed into the trash bin. -If you need to log your own messages or caught exceptions, use the method `log()`: +If you need to log your own messages or caught exceptions, use the `log()` method: ```php Debugger::log('Unexpected error'); // text message @@ -172,7 +171,7 @@ If you want Tracy to log PHP errors like `E_NOTICE` or `E_WARNING` with detailed Debugger::$logSeverity = E_NOTICE | E_WARNING; ``` -For a real professional the error log is a crucial source of information and he or she wants to be notified about any new error immediately. Tracy helps him. She is capable of sending an email for every new error record. The variable $email identifies where to send these e-mails: +For a true professional, the error log is a key source of information, and they want to be informed immediately about every new error. Tracy accommodates this by being able to send email notifications for new log entries. The `$email` variable determines where to send these emails: ```php Debugger::$email = 'admin@example.com'; @@ -180,21 +179,21 @@ Debugger::$email = 'admin@example.com'; If you use the entire Nette Framework, you can set this and others in the [configuration file |nette:configuring]. -To protect your e-mail box from flood, Tracy sends **only one message** and creates a file `email-sent`. When a developer receives the e-mail notification, he checks the log, corrects his application and deletes the `email-sent` monitoring file. This activates the e-mail sending again. +To protect your e-mail box from being flooded, Tracy sends **only one message** and creates a file `email-sent`. When a developer receives the e-mail notification, they check the log, correct the application, and delete the `email-sent` monitoring file. This reactivates the e-mail sending. Opening Files in the Editor =========================== -When the error page is displayed, you can click on file names and they will open in your editor with the cursor on the corresponding line. Files can also be created (action `create file`) or bug fixed in them (action `fix it`). In order to do this, you need to [configure the browser and the system |open-files-in-ide]. +When the error page is displayed, you can click on file names and they will open in your editor with the cursor on the corresponding line. Files can also be created (action `create file`) or bugs fixed in them (action `fix it`). In order to do this, you need to [configure the browser and the system |open-files-in-ide]. Supported PHP Versions ====================== -| Tracy | compatible with PHP +| Tracy | Compatible with PHP |-----------|-------------------- -| Tracy 2.10 – 3.0 | PHP 8.0 – 8.2 +| Tracy 2.10 – 3.0 | PHP 8.0 – 8.4 | Tracy 2.9 | PHP 7.2 – 8.2 | Tracy 2.8 | PHP 7.2 – 8.1 | Tracy 2.6 – 2.7 | PHP 7.1 – 8.0 @@ -215,4 +214,4 @@ This is a list of unofficial ports to other frameworks and CMS: - [ProcessWire CMS/CMF](https://github.com/adrianbj/TracyDebugger) - [Slim Framework](https://github.com/runcmf/runtracy) - Symfony framework: [kutny/tracy-bundle](https://github.com/kutny/tracy-bundle), [VasekPurchart/Tracy-Blue-Screen-Bundle](https://github.com/VasekPurchart/Tracy-Blue-Screen-Bundle) -- [Wordpress](https://github.com/ktstudio/WP-Tracy) +- [WordPress](https://github.com/ktstudio/WP-Tracy) diff --git a/tracy/en/open-files-in-ide.texy b/tracy/en/open-files-in-ide.texy index 0c55974726..2c8d9112ba 100644 --- a/tracy/en/open-files-in-ide.texy +++ b/tracy/en/open-files-in-ide.texy @@ -2,9 +2,9 @@ How to Open a File in Editor from Tracy? (IDE Integration) ********************************************************** .[perex] -When the error page is displayed, you can click on file names and they will open in your editor with the cursor on the corresponding line. Files can also be created (action `create file`) or bug fixed in them (action `fix it`). In order to do this, you need to configure the browser and the system. +When the error page is displayed, you can click on file names and they will open in your editor with the cursor on the corresponding line. Files can also be created (action `create file`) or bugs fixed in them (action `fix it`). In order to do this, you need to configure the browser and the system. -Tracy opens files via URLs of the form `editor://open/?file=%file&line=%line`, i.e. with the `editor://` protocol. We will register our own handler for this one. This can be any executable file that process the parameters and starts our favorite editor. +Tracy opens files via URLs of the form `editor://open/?file=%file&line=%line`, i.e., using the `editor://` protocol. We need to register a custom handler for this protocol. This handler can be any executable file that processes the parameters and launches your preferred editor. You can change the URL in the `Tracy\Debugger::$editor` variable, or disable click-through by setting `Tracy\Debugger::$editor = null`. @@ -14,7 +14,7 @@ Windows 1. Download the appropriate files "from the Tracy repository":https://github.com/nette/tracy/tree/master/tools/open-in-editor/windows to disk. -2. Edit `open-editor.js` and uncomment or edit the path to your editor in `settings`: +2. Edit the `open-editor.js` file and in the `settings` object, uncomment and, if necessary, modify the path to your editor: ```js var settings = { @@ -35,19 +35,28 @@ var settings = { ... ``` -Be careful and keep the double slashes in the paths. +Be careful and keep the double backslashes in the paths. -3. Register the handler for `editor://` protocol in the system. +3. Register the handler for the `editor://` protocol in the system. This is done by running `install.cmd`. **You need to run it as an Administrator.** The `open-editor.js` script will now serve the `editor://` protocol. +In order to open links generated on other servers, such as a production server or Docker, add a remote to local URL mapping to `open-editor.js`: + +```js + mappings: { + // remote path: local path + '/var/www/nette.app': 'W:\\Nette.web\\_web', + } +``` + Linux ===== -1. Download the appropriate files "from the Tracy repository":https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux to directory `~/bin`. +1. Download the appropriate files "from the Tracy repository":https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux to the `~/bin` directory. -2. Edit `open-editor.sh` and uncomment or edit the path to your editor in the variable `editor`: +2. Edit the `open-editor.sh` file and uncomment and, if necessary, modify the path to your editor in the `editor` variable. ```shell #!/bin/bash @@ -73,12 +82,13 @@ Make it executable: chmod +x ~/bin/open-editor.sh ``` -If the editor you are using is not installed from the package, the binary will probably not have a path in `$PATH`. This can be easily corrected. In the `~/bin` directory, create a symlink on the editor binary. .[note] +.[note] +If the editor you use is not installed from a package, its binary might not be in the system's `$PATH`. This can be easily fixed. In the `~/bin` directory, create a symbolic link to the editor's binary. -3. Register the handler for `editor://` protocol in the system. +3. Register the handler for the `editor://` protocol in the system. -This is done by running `install.sh`. The `open-editor.js` script will now serve the `editor://` protocol. +This is done by running the `install.sh` file. The `open-editor.sh` script will now handle the `editor://` protocol. macOS @@ -92,12 +102,12 @@ Tracy\Debugger::$editor = 'phpstorm://open?file=%file&line=%line'; // TextMate Tracy\Debugger::$editor = 'txmt://open/?url=file://%file&line=%line'; // MacVim -Tracy\Debugger::$editor = 'mvim://open/?url=file://%file&line=%line'; +Tracy\Debugger::$editor = 'mvim://open?url=file:///%file&line=%line'; // Visual Studio Code Tracy\Debugger::$editor = 'vscode://file/%file:%line'; ``` -If you are using standalone Tracy, put the line before `Tracy\Debugger::enable()`, if Nette, before the `$configurator->enableTracy()` in `Bootstrap.php`. +If you are using standalone Tracy, put the line before `Tracy\Debugger::enable()`. If using Nette, place it before `$configurator->enableTracy()` in `Bootstrap.php`. Unfortunately, actions `create file` or `fix it` do not work on macOS. @@ -105,7 +115,7 @@ Unfortunately, actions `create file` or `fix it` do not work on macOS. Demos ===== -Fixing bug: +Fixing a bug: <iframe width="560" height="315" src="https://www.youtube.com/embed/3ITT4mC0Eq4?rel=0&showinfo=0" frameborder="0" allow="encrypted-media" allowfullscreen></iframe> @@ -117,12 +127,11 @@ Creating a new file: Troubleshooting =============== -- In Firefox you may need to [allow |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] custom protocol execution in about:config by setting `network.protocol-handler.expose.editor` to `false` and `network.protocol-handler.expose-all` to `true`. It should be allowed by default, however. -- If it's not all working immediately, don't panic. Try to refresh the page, restart browser or computer. That should help. -- See [here|https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/] to fix: - Input Error: There is no script engine for file extension ".js" Maybe you associated ".js" file to another app, not JScript engine. +- In Firefox you may need to [allow |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] custom protocol execution in `about:config` by setting `network.protocol-handler.expose.editor` to `false` and `network.protocol-handler.expose-all` to `true`. It should be allowed by default, however. +- If it doesn't work immediately, don't panic. Try refreshing the page a few times before clicking the link, or restart your browser or computer. That should help. +- Here is a [link|https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/] to fix potential errors like: `Input Error: There is no script engine for file extension ".js"` or `Maybe you associated ".js" file to another app, not JScript engine.` -Starting from Google Chrome version 77 you will no longer see the checkbox “Always open these types of links in the associated app” when editor is opened through a link. Workaround for Windows: create file `fix.reg`: +Starting from Google Chrome version 77 you will no longer see the checkbox “Always open these types of links in the associated app” when the editor is launched through a link. Workaround for Windows: create file `fix.reg`: ``` Windows Registry Editor Version 5.00 @@ -132,4 +141,4 @@ Windows Registry Editor Version 5.00 Import it by double clicking and restart Chrome. -In case of more troubles or questions, ask on [forum |https://forum.nette.org]. +For further questions or suggestions, please visit the [forum |https://forum.nette.org]. diff --git a/tracy/en/recipes.texy b/tracy/en/recipes.texy index 158fd0a39b..9d021b9ea7 100644 --- a/tracy/en/recipes.texy +++ b/tracy/en/recipes.texy @@ -5,8 +5,7 @@ Recipes Content Security Policy ======================= -If your site uses Content Security Policy, you'll need to add `'nonce-<value>'` and `'strict-dynamic'` to `script-src` for Tracy to work properly. Some 3rd plugins may require additional directives. -Nonce is not supported in the `style-src` directive, if you use this directive you need to add `'unsafe-inline'`, but this should be avoided in production mode. +If your site uses Content Security Policy (CSP), you'll need to add `'nonce-<value>'` and `'strict-dynamic'` to the `script-src` directive for Tracy to function correctly. Some third-party plugins might require additional directives. Nonce is not supported in the `style-src` directive; if you use this directive, you must add `'unsafe-inline'`, but this should be avoided in production mode. Configuration example for [Nette Framework |nette:configuring]: @@ -27,8 +26,7 @@ header("Content-Security-Policy: script-src 'nonce-$nonce' 'strict-dynamic';"); Faster Loading ============== -The basic integration is straightforward, however if you have slow blocking scripts in web page, they can slow the Tracy loading. -The solution is to place `<?php Tracy\Debugger::renderLoader() ?>` into your template before any scripts: +Basic integration is straightforward. However, if you have slow-loading blocking scripts on your webpage, they can slow down Tracy's loading. The solution is to place `<?php Tracy\Debugger::renderLoader() ?>` in your template before any scripts: ```latte <!DOCTYPE html> @@ -42,12 +40,37 @@ The solution is to place `<?php Tracy\Debugger::renderLoader() ?>` into your tem ``` -AJAX and Redirected Requests -============================ +Debugging AJAX Requests +======================= + +Tracy automatically captures AJAX requests made using jQuery or the native `fetch` API. These requests are displayed as additional rows in the Tracy bar, enabling easy and convenient AJAX debugging. + +If you do not want to capture AJAX requests automatically, you can disable this feature by setting the JavaScript variable: + +```js +window.TracyAutoRefresh = false; +``` + +For manual monitoring of specific AJAX requests, add the HTTP header `X-Tracy-Ajax` with the value returned by `Tracy.getAjaxHeader()`. Here is an example of using it with the `fetch` function: + +```js +fetch(url, { + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Tracy-Ajax': Tracy.getAjaxHeader(), + } +}) +``` + +This approach allows for selective debugging of AJAX requests. + + +Data Storage +============ -Tracy can display Debug bar and Bluescreens for AJAX requests and redirects. Tracy creates its own sessions, stores data in its own temporary files, and uses a `tracy-session` cookie. +Tracy can display Tracy bar panels and Bluescreens for AJAX requests and redirects. Tracy creates its own sessions, stores data in its own temporary files, and uses a `tracy-session` cookie. -Tracy can also be configured to use a native PHP session, which is started before Tracy is turned on: +Tracy can also be configured to use a native PHP session, which must be started before enabling Tracy: ```php session_start(); @@ -55,7 +78,7 @@ Debugger::setSessionStorage(new Tracy\NativeSession); Debugger::enable(); ``` -In case starting a session requires more complex initialization, you can start Tracy immediately (so that it can handle any errors that occur) and then initialize the session handler and finally inform Tracy that the session is ready to be used using the `dispatch()` function: +In case starting a session requires more complex initialization, you can start Tracy immediately (so that it can handle any errors that occur) and then initialize the session handler. Finally, inform Tracy that the session is ready to be used using the `dispatch()` function: ```php Debugger::setSessionStorage(new Tracy\NativeSession); @@ -68,23 +91,23 @@ session_start(); Debugger::dispatch(); ``` -The `setSessionStorage()` function has existed since version 2.9, before that Tracy always used the native PHP session. +The `setSessionStorage()` function has existed since version 2.9; before that, Tracy always used the native PHP session. Custom Scrubber =============== -Scrubber is a filter that prevents sensitive data from leaking from dumps, such as passwords or credentials. The filter is called for each item of the dumped array or object and returns `true` if the value is sensitive. In this case, `*****` is printed instead of the value. +A Scrubber is a filter that prevents sensitive data from leaking from dumps, such as passwords or credentials. The filter is called for each item of the dumped array or object and returns `true` if the value is sensitive. In this case, `*****` is printed instead of the value. ```php -// avoids dumping key values and properties such as `password`, +// prevents dumping values of keys and properties like `password`, // `password_repeat`, `check_password`, `DATABASE_PASSWORD`, etc. $scrubber = function(string $key, $value, ?string $class): bool { return preg_match('#password#i', $key) && $value !== null; }; -// we use it for all dumps inside BlueScreen +// use it for all dumps inside BlueScreen Tracy\Debugger::getBlueScreen()->scrubber = $scrubber; ``` @@ -92,7 +115,7 @@ Tracy\Debugger::getBlueScreen()->scrubber = $scrubber; Custom Logger ============= -We can create a custom logger to log errors, uncatched exceptions, and also be called by `Tracy\Debugger::log()`. Logger implements the interface [api:Tracy\ILogger]. +We can create a custom logger that will log errors, uncaught exceptions, and will also be invoked by the `Tracy\Debugger::log()` method. The logger must implement the [api:Tracy\ILogger] interface. ```php use Tracy\ILogger; @@ -112,7 +135,7 @@ And then we activate it: Tracy\Debugger::setLogger(new SlackLogger); ``` -If we use the full Nette Framework, we can set it in the NEON configuration file: +If you are using the full Nette Framework, you can set it in the NEON configuration file: ```neon services: @@ -123,7 +146,7 @@ services: Monolog Integration ------------------- -Tracy package provides a PSR-3 adapter, allowing for integration of [monolog/monolog](https://github.com/Seldaek/monolog). +The Tracy package provides a PSR-3 adapter, allowing for integration of [monolog/monolog](https://github.com/Seldaek/monolog). ```php $monolog = new Monolog\Logger('main-channel'); @@ -141,13 +164,13 @@ Debugger::log('warning', Debugger::WARNING); // writes: [<TIMESTAMP>] main-chann nginx ===== -If Tracy does not work on nginx, it is probably misconfigured. If there is something like +If Tracy does not work on nginx, it is probably misconfigured. If there is something like: ```nginx try_files $uri $uri/ /index.php; ``` -change it to +change it to: ```nginx try_files $uri $uri/ /index.php$is_args$args; diff --git a/tracy/en/stopwatch.texy b/tracy/en/stopwatch.texy index 6d28d56bd8..6546ac6e71 100644 --- a/tracy/en/stopwatch.texy +++ b/tracy/en/stopwatch.texy @@ -1,7 +1,7 @@ Stopwatch ********* -Another useful tool is the debugger stopwatch with a precision of microseconds: +Another useful tool is the debugger stopwatch with microsecond precision: ```php Debugger::timer(); @@ -13,7 +13,7 @@ $elapsed = Debugger::timer(); // $elapsed = 2 ``` -Multiple measurements at once can be achieved by an optional parameter. +Multiple measurements can be achieved using an optional parameter as an identifier. ```php Debugger::timer('page-generating'); diff --git a/tracy/es/@home.texy b/tracy/es/@home.texy index a196ab2bfa..ab9a11fb1a 100644 --- a/tracy/es/@home.texy +++ b/tracy/es/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Tracy - Una herramienta de depuración imprescindible para todos los desarrolladores PHP}} -{{description: Tracy es una herramienta diseñada para facilitar la depuración de código PHP. Es un asistente útil para todos los programadores PHP, que ayuda a visualizar claramente y registrar errores, volcar variables y mucho más. Advertencia: ¡Tracy es adictivo!}} +{{maintitle: Tracy – la herramienta de depuración con la que da gusto equivocarse}} +{{description: Tracy es una herramienta diseñada para facilitar la depuración de código PHP. Es un asistente útil para todos los programadores de PHP, que les ayuda a visualizar y registrar errores, volcar variables y mucho más. Advertencia: ¡Tracy es adictiva!}} diff --git a/tracy/es/@left-menu.texy b/tracy/es/@left-menu.texy index 6bda0be0fa..24c9e989dd 100644 --- a/tracy/es/@left-menu.texy +++ b/tracy/es/@left-menu.texy @@ -1,7 +1,7 @@ -- [Primeros pasos |Guide] -- [Volquete |Dumper] -- [Cronómetro |Stopwatch] -- [Configurar |Configuring] -- [Recetas |Recipes] -- [Integración IDE |open-files-in-ide] +- [Empezando con Tracy |guide] +- [Volcado |dumper] +- [Medición de tiempo |stopwatch] +- [Configuración |configuring] +- [Recetas |recipes] +- [Integración con IDE |open-files-in-ide] - [Creación de extensiones |extensions] diff --git a/tracy/es/@menu.texy b/tracy/es/@menu.texy index b106784577..8203d1063d 100644 --- a/tracy/es/@menu.texy +++ b/tracy/es/@menu.texy @@ -1,3 +1,3 @@ -- [Inicio |@home] -- [Documentación |Guide] +- [Introducción |@home] +- [Documentación |guide] - "GitHub .[link-external]":https://github.com/nette/tracy diff --git a/tracy/es/@meta.texy b/tracy/es/@meta.texy new file mode 100644 index 0000000000..891977ea09 --- /dev/null +++ b/tracy/es/@meta.texy @@ -0,0 +1 @@ +{{sitename: Tracy Documentación}} diff --git a/tracy/es/configuring.texy b/tracy/es/configuring.texy index 6f6194d7a5..3f6cfac74f 100644 --- a/tracy/es/configuring.texy +++ b/tracy/es/configuring.texy @@ -1,77 +1,77 @@ Configuración de Tracy ********************** -Los siguientes ejemplos asumen que el siguiente alias de clase está definido: +Todos los ejemplos asumen que se ha creado un alias: ```php use Tracy\Debugger; ``` -Registro de errores .[#toc-error-logging] ------------------------------------------ +Registro de errores +------------------- ```php $logger = Debugger::getLogger(); -// si se ha producido un error la notificación se envía a este email -$logger->email = 'dev@example.com'; // (string|string[]) por defecto es unset +// correo electrónico al que se envían las notificaciones de que ha ocurrido un error +$logger->email = 'dev@example.com'; // (string|string[]) por defecto no está configurado // remitente del correo electrónico -$logger->fromEmail = 'me@example.com'; // (string) por defecto unset +$logger->fromEmail = 'me@example.com'; // (string) por defecto no está configurado -// rutina para enviar correo electrónico -$logger->mailer = /* ... */; // (callable) por defecto se envía por mail() +// rutina que asegura el envío del correo electrónico +$logger->mailer = /* ... */; // (callable) por defecto es el envío mediante la función mail() -// ¿después de qué tiempo enviar otro email? -$logger->emailSnooze = /* ... */; // (string) por defecto es '2 días' +// ¿después de cuánto tiempo mínimo enviar el siguiente correo electrónico? +$logger->emailSnooze = /* ... */; // (string) por defecto es '2 days' -// ¿para qué niveles de error se registra también BlueScreen? -Debugger::$logSeverity = E_WARNING | E_NOTICE; // por defecto 0 (sin nivel de error) +// ¿para qué niveles de error se registra también el BlueScreen? +Debugger::$logSeverity = E_WARNING | E_NOTICE; // por defecto es 0 (ningún nivel de error) ``` -`dump()` Comportamiento .[#toc-dump-behavior] ---------------------------------------------- +Comportamiento de `dump()` +-------------------------- ```php // longitud máxima de la cadena -Debugger::$maxLength = 150; // (int) por defecto según Tracy +Debugger::$maxLength = 150; // (int) por defecto según la versión de Tracy -// profundidad de la lista -Debugger::$maxDepth = 10; // (int) por defecto según Tracy +// profundidad máxima de anidamiento +Debugger::$maxDepth = 10; // (int) por defecto según la versión de Tracy // ocultar los valores de estas claves (desde Tracy 2.8) -Debugger::$keysToHide = ['password', /* ... */]; // (string[]) por defecto [] +Debugger::$keysToHide = ['password', /* ... */]; // (string[]) por defecto es [] // tema visual (desde Tracy 2.8) -Debugger::$dumpTheme = 'dark'; // (light|dark) por defecto 'light' +Debugger::$dumpTheme = 'dark'; // (light|dark) por defecto es 'light' -// ¿muestra la ubicación donde se llamó a dump()? -Debugger::$showLocation = /* ... */; // (bool) por defecto según Tracy +// ¿mostrar el lugar donde se llamó a la función dump()? +Debugger::$showLocation = /* ... */; // (bool) por defecto según la versión de Tracy ``` -Otros .[#toc-others] --------------------- +Otros +----- ```php -// en modo Desarrollo, verá avisos o advertencias de error como BlueScreen -Debugger::$strictMode = /* ... */; // (bool|int) por defecto false, puede seleccionar sólo niveles de error específicos (p.e. E_USER_DEPRECATED | E_DEPRECATED) +// en modo de desarrollo, muestra errores de tipo notice o warning como BlueScreen +Debugger::$strictMode = /* ... */; // (bool|int) por defecto es false, es posible seleccionar solo algunos niveles de error (p.ej. E_USER_DEPRECATED | E_DEPRECATED) -// muestra mensajes de error silenciosos (@) -Debugger::$scream = /* ... */; // (bool|int) por defecto false, desde la versión 2.9 es posible seleccionar sólo niveles de error específicos (e.g. E_USER_DEPRECATED | E_DEPRECATED) +// ¿mostrar mensajes de error silenciados (@)? +Debugger::$scream = /* ... */; // (bool|int) por defecto es false, desde la versión 2.9 es posible seleccionar solo algunos niveles de error (p.ej. E_USER_DEPRECATED | E_DEPRECATED) -// formato del enlace a abrir en el editor -Debugger::$editor = /* ... */; // (string|null) por defecto 'editor://open/?file=%file&line=%line' +// formato de enlace para abrir en el editor +Debugger::$editor = /* ... */; // (string|null) por defecto es 'editor://open/?file=%file&line=%line' -// ruta a la plantilla con la página personalizada para el error 500 -Debugger::$errorTemplate = /* ... */; // (string) por defecto unset +// ruta a la plantilla con una página personalizada para el error 500 +Debugger::$errorTemplate = /* ... */; // (string) por defecto no está configurado -// ¿muestra Tracy Bar? -Debugger::$showBar = /* ... */; // (bool) por defecto true +// ¿mostrar la Tracy Bar? +Debugger::$showBar = /* ... */; // (bool) por defecto es true -Depurador::$editorMapping = [ +Debugger::$editorMapping = [ // original => nuevo '/var/www/html' => '/data/web', '/home/web' => '/srv/html', @@ -79,64 +79,63 @@ Depurador::$editorMapping = [ ``` -Marco Nette .[#toc-nette-framework] ------------------------------------ +Nette Framework +--------------- -Si utiliza Nette Framework, también puede configurar Tracy y añadir nuevos paneles a Tracy Bar utilizando el archivo de configuración. -Puede establecer los parámetros de Tracy en la configuración y también añadir nuevos paneles a la Tracy Bar. Estos ajustes se aplican sólo después de que se haya creado el contenedor DI, por lo que los errores ocurridos con anterioridad no pueden reflejarlos. +Si utilizas Nette Framework, puedes configurar Tracy y añadir nuevos paneles a la Tracy Bar también mediante el archivo de configuración. En la configuración se pueden establecer parámetros y también añadir nuevos paneles a la Tracy Bar. Estos ajustes se aplican solo después de la creación del contenedor DI, por lo que los errores que ocurran antes no pueden reflejarlos. Configuración del registro de errores: ```neon tracy: - # si se ha producido un error la notificación se envía a este email - email: dev@example.com # (string|string[]) por defecto unset + # correo electrónico al que se envían las notificaciones de que ha ocurrido un error + email: dev@example.com # (string|string[]) por defecto no está configurado # remitente del correo electrónico - fromEmail: robot@example.com # (string) por defecto unset + fromEmail: robot@example.com # (string) por defecto no está configurado - # periodo de aplazamiento de envío de emails (desde Tracy 2.8.8) - emailSnooze: ... # (string) por defecto '2 días + # tiempo de aplazamiento del envío de correos electrónicos (desde Tracy 2.8.8) + emailSnooze: ... # (string) por defecto es '2 days' - # ¿utilizar un mailer definido en la configuración? (desde Tracy 2.5) - netteMailer: ... # (bool) por defecto true + # ¿usar el Nette mailer para enviar correos electrónicos? (desde Tracy 2.5) + netteMailer: ... # (bool) por defecto es true - # ¿para qué niveles de error se registra también BlueScreen? - logSeverity: [E_WARNING, E_NOTICE] # por defecto [] + # ¿para qué niveles de error se registra también el BlueScreen? + logSeverity: [E_WARNING, E_NOTICE] # por defecto es [] ``` -Configuración de la función `dump()`: +Configuración del comportamiento de la función `dump()`: ```neon tracy: # longitud máxima de la cadena - maxLength: 150 # (int) por defecto según Tracy + maxLength: 150 # (int) por defecto según la versión de Tracy - # profundidad de la lista - maxDepth: 10 # (int) por defecto según Tracy + # profundidad máxima de anidamiento + maxDepth: 10 # (int) por defecto según la versión de Tracy - # ocultar valores de estas claves (desde Tracy 2.8) - keysToHide: [password, pass] # (string[]) por defecto [] + # ocultar los valores de estas claves (desde Tracy 2.8) + keysToHide: [password, pass] # (string[]) por defecto es [] # tema visual (desde Tracy 2.8) - dumpTheme: dark # (light|dark) por defecto a 'light' + dumpTheme: dark # (light|dark) por defecto es 'light' - # ¿muestra la ubicación donde se llamó a dump()? - showLocation: ... # (bool) por defecto según Tracy + # ¿mostrar el lugar donde se llamó a la función dump()? + showLocation: ... # (bool) por defecto según la versión de Tracy ``` -Para instalar la extensión Tracy: +Instalación de extensiones de Tracy: ```neon tracy: - # appends bars to Tracy Bar + # añade paneles a la Tracy Bar bar: - Nette\Bridges\DITracy\ContainerPanel - IncludePanel - XDebugHelper('myIdeKey') - MyPanel(@MyService) - # añadir paneles a BlueScreen + # añade paneles al BlueScreen blueScreen: - DoctrinePanel::renderException ``` @@ -145,25 +144,37 @@ Otras opciones: ```neon tracy: - # en modo Desarrollo, verá avisos o advertencias de error como BlueScreen - strictMode: ... # por defecto a true + # en modo de desarrollo, muestra errores de tipo notice o warning como BlueScreen + strictMode: ... # por defecto es true - # muestra mensajes de error silenciosos (@) - scream: ... # por defecto falso + # ¿mostrar mensajes de error silenciados (@)? + scream: ... # por defecto es false # formato de enlace para abrir en el editor - editor: ... # (string) por defecto 'editor://open/?file=%file&line=%line' + editor: ... # (string) por defecto es 'editor://open/?file=%file&line=%line' - # ruta a la plantilla con la página personalizada para el error 500 - errorTemplate: ... # (string) por defecto unset + # ruta a la plantilla con una página personalizada para el error 500 + errorTemplate: ... # (string) por defecto no está configurado - # ¿muestra Tracy Bar? - showBar: ... # (bool) por defecto true + # ¿mostrar la Tracy Bar? + showBar: ... # (bool) por defecto es true editorMapping: - # original: new + # original: nuevo /var/www/html: /data/web /home/web: /srv/html ``` -Los valores de las opciones `logSeverity`, `strictMode` y `scream` pueden escribirse como una matriz de niveles de error (p. ej. `[E_WARNING, E_NOTICE]`) o como una expresión utilizada en PHP (por ejemplo, `E_ALL & ~E_NOTICE`). +Los valores de las opciones `logSeverity`, `strictMode` y `scream` se pueden escribir como un array de niveles de error (p.ej. `[E_WARNING, E_NOTICE]`), o como una expresión utilizada en el lenguaje PHP (p.ej. `E_ALL & ~E_NOTICE`). + + +Servicios DI +------------ + +Estos servicios se añaden al contenedor DI: + +| Nombre | Tipo | Descripción +|---------------------------------------------------------- +| `tracy.logger` | [api:Tracy\ILogger] | logger +| `tracy.blueScreen` | [api:Tracy\BlueScreen] | BlueScreen +| `tracy.bar` | [api:Tracy\Bar] | Tracy Bar diff --git a/tracy/es/dumper.texy b/tracy/es/dumper.texy index ababc68883..6d35ef7026 100644 --- a/tracy/es/dumper.texy +++ b/tracy/es/dumper.texy @@ -1,20 +1,20 @@ -Dumper -****** +Volcado (Dumping) +***************** -Todo desarrollador de depuración es un buen amigo de la función `var_dump`, que lista todos los contenidos de cualquier variable en detalle. Desafortunadamente, su salida no tiene formato HTML y emite el volcado en una sola línea de código HTML, sin mencionar el escapado de contexto. Es necesario sustituir `var_dump` por una función más práctica. Eso es precisamente lo que es `dump()`. +Todo depurador es un buen amigo de la función [php:var_dump], que imprime detalladamente el contenido de una variable. Desafortunadamente, en el entorno HTML, la salida pierde su formato y se fusiona en una sola línea, sin mencionar la sanitización del código HTML. En la práctica, es necesario reemplazar `var_dump` con una función más inteligente. Esa es precisamente `dump()`. ```php $arr = [10, 20.2, true, null, 'hello']; dump($arr); -// or Debugger::dump($arr); +// o Debugger::dump($arr); ``` genera la salida: [* dump-basic.webp *] -Puedes cambiar el tema claro por defecto a oscuro: +Puedes cambiar el tema claro predeterminado a oscuro: ```php Debugger::$dumpTheme = 'dark'; @@ -22,27 +22,27 @@ Debugger::$dumpTheme = 'dark'; [* dump-dark.webp *] -También puede cambiar la profundidad de anidamiento en `Debugger::$maxDepth` y la longitud de las cadenas mostradas en `Debugger::$maxLength`. Naturalmente, los valores más bajos aceleran el renderizado de Tracy. +Además, podemos cambiar la profundidad de anidamiento usando [Debugger::$maxDepth |api:Tracy\Debugger::$maxDepth] y la longitud de las descripciones mostradas usando [Debugger::$maxLength |api:Tracy\Debugger::$maxLength]. Valores más bajos naturalmente acelerarán Tracy. ```php -Debugger::$maxDepth = 2; // default: 3 -Debugger::$maxLength = 50; // default: 150 +Debugger::$maxDepth = 2; // por defecto: 3 +Debugger::$maxLength = 50; // por defecto: 150 ``` -La función `dump()` puede mostrar otra información útil. `Tracy\Dumper::LOCATION_SOURCE` añade un tooltip con la ruta al archivo en el que se llamó a la función. `Tracy\Dumper::LOCATION_LINK` añade un enlace al archivo. `Tracy\Dumper::LOCATION_CLASS` añade un tooltip a cada objeto volcado que contiene la ruta al archivo en el que se define la clase del objeto. Todas estas constantes pueden establecerse en la variable `Debugger::$showLocation` antes de llamar a la función `dump()`. Puede establecer varios valores a la vez utilizando el operador `|`. +La función `dump()` también puede imprimir otra información útil. La constante `Tracy\Dumper::LOCATION_SOURCE` añade un tooltip con la ruta al lugar donde se llamó la función. `Tracy\Dumper::LOCATION_LINK` nos proporciona un enlace a ese lugar. `Tracy\Dumper::LOCATION_CLASS` muestra, para cada objeto volcado, un tooltip con la ruta al archivo donde está definida su clase. Las constantes se establecen en la variable `Debugger::$showLocation` antes de llamar a `dump()`. Si queremos establecer varios valores a la vez, los combinamos usando el operador `|`. ```php -Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // Muestra la ruta donde se llamó a dump() -Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Muestra tanto las rutas a las clases como el enlace a donde se llamó al dump() -Debugger::$showLocation = false; // Oculta la información adicional de localización -Debugger::$showLocation = true; // Muestra toda la información adicional de localización +Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // Establece solo la impresión sobre el lugar de la llamada a la función +Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Establece simultáneamente la impresión del enlace y la ruta a la clase +Debugger::$showLocation = false; // Desactiva la impresión de información adicional +Debugger::$showLocation = true; // Activa la impresión de toda la información adicional ``` -Una alternativa muy práctica a `dump()` es `dumpe()` (es decir, volcar y salir) y `bdump()`. Esto nos permite volcar variables en Tracy Bar. Esto es útil, porque los volcados no ensucian la salida y también podemos añadir un título al volcado. +Una alternativa práctica a `dump()` es `dumpe()` (dump & exit) y `bdump()`. Este último nos permite imprimir el valor de una variable en el panel de la Tracy Bar. Esto es muy útil, ya que los volcados están separados del diseño de la página y también podemos añadirles un comentario. ```php -bdump([2, 4, 6, 8], 'even numbers up to ten'); -bdump([1, 3, 5, 7, 9], 'odd numbers up to ten'); +bdump([2, 4, 6, 8], 'números pares hasta diez'); +bdump([1, 3, 5, 7, 9], 'números impares hasta diez'); ``` -[* bardump-en.webp *] +[* bardump-cs.webp *] diff --git a/tracy/es/extensions.texy b/tracy/es/extensions.texy index 2cf00edbb2..9df9157c1b 100644 --- a/tracy/es/extensions.texy +++ b/tracy/es/extensions.texy @@ -1,23 +1,23 @@ -Creación de extensiones de Tracy -******************************** +Creación de extensiones para Tracy +********************************** <div class=perex> -Tracy es una gran herramienta para depurar tu aplicación. Sin embargo, a veces necesitas más información de la que Tracy ofrece. Aprenderás sobre: +Tracy proporciona una excelente herramienta para depurar tu aplicación. Sin embargo, a veces te gustaría tener a mano alguna información adicional. Te mostraremos cómo escribir tu propia extensión para la Tracy Bar, para que el desarrollo sea aún más agradable. -- Crear tus propios paneles Tracy Bar -- Crear tus propias extensiones Bluescreen +- Creación de un panel personalizado para la Tracy Bar +- Creación de una extensión personalizada para Bluescreen </div> .[tip] -Puedes encontrar extensiones útiles para Tracy en "Componette":https://componette.org/search/tracy. +Puedes encontrar un repositorio de extensiones listas para Tracy en "Componette":https://componette.org/search/tracy. -Extensiones de la barra Tracy .[#toc-tracy-bar-extensions] -========================================================== +Extensiones para la Tracy Bar +============================= -Crear una nueva extensión para Tracy Bar es sencillo. Necesita implementar la interfaz `Tracy\IBarPanel` con los métodos `getTab()` y `getPanel()`. Los métodos deben devolver el código HTML de una pestaña (pequeña etiqueta en Tracy Bar) y un panel (ventana emergente que se muestra tras hacer clic en la pestaña). Si `getPanel()` no devuelve nada, sólo se mostrará la pestaña. Si `getTab()` no devuelve nada, no se mostrará nada y no se llamará a `getPanel()`. +Crear una nueva extensión para la Tracy Bar no es nada complicado. Creas un objeto que implemente la interfaz `Tracy\IBarPanel`, que tiene dos métodos `getTab()` y `getPanel()`. Los métodos deben devolver el código HTML de la pestaña (una pequeña etiqueta mostrada directamente en la Bar) y del panel. Si `getPanel()` no devuelve nada, solo se mostrará la etiqueta. Si `getTab()` no devuelve nada, no se mostrará nada en absoluto y `getPanel()` ya no se llamará. ```php class ExamplePanel implements Tracy\IBarPanel @@ -35,16 +35,16 @@ class ExamplePanel implements Tracy\IBarPanel ``` -Registro .[#toc-registration] ------------------------------ +Registro +-------- -La inscripción se realiza llamando a `Tracy\Bar::addPanel()`: +El registro se realiza mediante `Tracy\Bar::addPanel()`: ```php Tracy\Debugger::getBar()->addPanel(new ExamplePanel); ``` -o simplemente puede registrar su panel en la configuración de la aplicación: +O puedes registrar el panel directamente en la configuración de la aplicación: ```neon tracy: @@ -53,70 +53,70 @@ tracy: ``` -Pestaña Código HTML .[#toc-tab-html-code] ------------------------------------------ +Código HTML de la pestaña +------------------------- -Debería tener este aspecto: +Debería verse aproximadamente así: ```latte -<span title="Explaining tooltip"> +<span title="Descripción explicativa"> <svg>...</svg> - <span class="tracy-label">Title</span> + <span class="tracy-label">Título</span> </span> ``` -La imagen debe estar en formato SVG. Si no necesitas tooltip, puedes dejar `<span>` . +La imagen debe estar en formato SVG. Si no se necesita una descripción explicativa, se puede omitir el `<span>`. -Código HTML del panel .[#toc-panel-html-code] ---------------------------------------------- +Código HTML del panel +--------------------- -Debería tener este aspecto: +Debería verse aproximadamente así: ```latte -<h1>Title</h1> +<h1>Título</h1> <div class="tracy-inner"> <div class="tracy-inner-container"> - ... content ... + ... contenido ... </div> </div> ``` -El título debe ser el mismo que en la pestaña o contener información adicional. +El título debería ser el mismo que el título de la pestaña, o puede contener datos adicionales. -Una extensión puede registrarse varias veces, por lo que se recomienda no utilizar el atributo `id` para el estilo. Puede utilizar clases, preferiblemente en `tracy-addons-<class-name>[-<optional>]` formato. Al crear CSS, es mejor utilizar `#tracy-debug .class`, porque dicha regla tiene mayor prioridad que reset. +Hay que tener en cuenta que una extensión puede registrarse varias veces, por ejemplo, con diferentes configuraciones, por lo que para la estilización no se pueden usar IDs CSS, sino solo clases, y en la forma `tracy-addons-<NombreClase>[-<opcional>]`. Luego, escribe la clase en el div junto con la clase `tracy-inner`. Al escribir CSS, es útil escribir `#tracy-debug .clase`, porque la regla tendrá una prioridad más alta que el reset. -Estilos por defecto .[#toc-default-styles] ------------------------------------------- +Estilos predeterminados +----------------------- -En el panel, elementos `<a>`, `<table>`, `<pre>`, `<code>` tienen estilos predeterminados. Para crear un enlace para ocultar o mostrar otro elemento, conéctelos con los atributos `href` y `id` y la clase `tracy-toggle`. +En el panel, `<a>`, `<table>`, `<pre>`, `<code>` están pre-estilizados. Si quieres crear un enlace que oculte y muestre otro elemento, conéctalos con los atributos `href` e `id` y la clase `tracy-toggle`: ```latte -<a href="#tracy-addons-className-{$counter}" class="tracy-toggle">Detail</a> +<a href="#tracy-addons-NombreClase-{$counter}" class="tracy-toggle">Detalles</a> -<div id="tracy-addons-className-{$counter}">...</div> +<div id="tracy-addons-NombreClase-{$counter}">...</div> ``` -Si el estado por defecto es colapsado, añada la clase `tracy-collapsed` a ambos elementos. +Si el estado predeterminado debe ser colapsado, añade la clase `tracy-collapsed` a ambos elementos. -Utilice un contador estático para evitar la duplicación de ID en una página. +Usa un contador estático para evitar crear IDs duplicados en una página. -Extensiones Bluescreen .[#toc-bluescreen-extensions] -==================================================== +Extensiones para Bluescreen +=========================== -Puede añadir sus propias visualizaciones de excepciones o paneles, que aparecerán en el bluescreen. +De esta manera, se pueden añadir visualizaciones personalizadas de excepciones o paneles que se mostrarán en el bluescreen. -La extensión se hace así: +La extensión se crea con este comando: ```php -Tracy\Debugger::getBlueScreen()->addPanel(function (?Throwable $e) { // catched exception +Tracy\Debugger::getBlueScreen()->addPanel(function (?Throwable $e) { // excepción capturada return [ - 'tab' => '...Title...', - 'panel' => '...content...', + 'tab' => '...Etiqueta...', + 'panel' => '...Código HTML del panel...', ]; }); ``` -La función se llama dos veces, primero se pasa la propia excepción en el parámetro `$e` y el panel devuelto se renderiza al principio de la página. Si no se devuelve nada, el panel no se renderiza. Luego se llama con el parámetro `null` y el panel devuelto se renderiza debajo de la pila de llamadas. Si la función devuelve `'bottom' => true` en el array, el panel se renderiza en la parte inferior. +La función se llama dos veces, primero se pasa la excepción misma en el parámetro `$e` y el panel devuelto se renderiza al principio de la página. Si no devuelve nada, el panel no se renderiza. Luego se llama con el parámetro `null` y el panel devuelto se renderiza debajo del callstack. Si la función devuelve en el array la clave `'bottom' => true`, el panel se renderiza completamente abajo. diff --git a/tracy/es/guide.texy b/tracy/es/guide.texy index 3538ea6275..7ea8f86346 100644 --- a/tracy/es/guide.texy +++ b/tracy/es/guide.texy @@ -1,213 +1,212 @@ -Primeros pasos con Tracy -************************ +Empezando con Tracy +******************* <div class=perex> -La librería Tracy es una ayuda útil para los programadores PHP. Le ayuda a: +La librería Tracy es una útil ayuda diaria para el programador PHP. Te ayudará a: - detectar y corregir errores rápidamente - registrar errores -- volcar variables -- medir el tiempo de ejecución de scripts/consultas -- ver el consumo de memoria +- imprimir variables +- medir el tiempo de ejecución de scripts y consultas de base de datos +- monitorizar los requisitos de memoria </div> -PHP es un lenguaje perfecto para cometer errores difícilmente detectables porque da una gran flexibilidad a los programadores. Tracy\Debugger es más valioso por eso. Es una herramienta definitiva entre las de diagnóstico. +PHP es un lenguaje perfecto para cometer errores difíciles de detectar, ya que da a los desarrolladores una considerable libertad. Por eso, la herramienta de depuración Tracy es aún más valiosa. Representa la cima absoluta entre las herramientas de diagnóstico para PHP. -Si conoces a Tracy por primera vez, créeme, tu vida empieza a dividirse en una antes de Tracy y otra con ella. ¡Bienvenido a la parte buena! +Si hoy te encuentras con Tracy por primera vez, créeme, tu vida comenzará a dividirse en la de antes de Tracy y la de después. ¡Bienvenido a la mejor parte! -Instalación y requisitos .[#toc-installation-and-requirements] -============================================================== +Instalación +=========== -La mejor manera de instalar Tracy es [descargar el último paquete |https://github.com/nette/tracy/releases] o utilizar Composer: +La mejor manera de instalar Tracy es [descargar el último paquete |https://github.com/nette/tracy/releases], o usar Composer: ```shell composer require tracy/tracy ``` -También puede descargar el paquete completo o el archivo [tracy.phar |https://github.com/nette/tracy/releases]. +También puedes descargar el paquete completo como un archivo [tracy.phar |https://github.com/nette/tracy/releases]. -Utilización .[#toc-usage] -========================= +Uso +=== -Tracy se activa llamando al método `Tracy\Debugger::enable()' tan pronto como sea posible al principio del programa, antes de que se envíe ninguna salida: +Activamos Tracy llamando al método `Tracy\Debugger::enable()` lo antes posible al principio del programa, antes de enviar cualquier salida: ```php use Tracy\Debugger; -require 'vendor/autoload.php'; // alternativamente tracy.phar +require 'vendor/autoload.php'; // o tracy.phar Debugger::enable(); ``` -Lo primero que verá en la página es la barra de Tracy en la esquina inferior derecha. Si no la ves, puede significar que Tracy se está ejecutando en modo de producción. -Esto se debe a que Tracy sólo es visible en localhost por razones de seguridad. Para probar si funciona, puede ponerlo temporalmente en modo de desarrollo utilizando el parámetro `Debugger::enable(Debugger::Development)`. +Lo primero que notarás en la página es la Tracy Bar en la esquina inferior derecha. Si no la ves, puede significar que Tracy se está ejecutando en modo de producción. Tracy, por razones de seguridad, solo es visible en localhost. Para probar si funciona, puedes cambiarla temporalmente al modo de desarrollo usando el parámetro `Debugger::enable(Debugger::Development)`. -Barra Tracy .[#toc-tracy-bar] -============================= +Tracy Bar +========= -La Tracy Bar es un panel flotante. Aparece en la esquina inferior derecha de una página. Puede moverla con el ratón. Recordará su posición tras la recarga de la página. +La Tracy Bar es un panel flotante que aparece en la esquina inferior derecha de la página. Podemos moverla con el ratón y recordará su posición después de recargar la página. [* tracy-bar.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html -Puedes añadir otros paneles útiles a la Tracy Bar. Puedes encontrar algunos interesantes en [addons |https://componette.org] o puedes [crear los tuyos |extensions] propios. +Se pueden añadir otros paneles útiles a la Tracy Bar. Encontrarás muchos en los [complementos |https://componette.org], o incluso [puedes escribir los tuyos propios |extensions]. -Si no desea mostrar Tracy Bar, configure: +Si no quieres mostrar la Tracy Bar, configura: ```php Debugger::$showBar = false; ``` -Visualización de Errores y Excepciones .[#toc-visualization-of-errors-and-exceptions] -===================================================================================== +Visualización de errores y excepciones +====================================== -Seguramente, usted sabe cómo PHP informa de los errores: hay algo como esto en el código fuente de la página: +Seguro que sabes bien cómo PHP notifica los errores: imprime algo como esto en el código fuente de la página: /--pre .{font-size: 90%} <b>Parse error</b>: syntax error, unexpected '}' in <b>HomePresenter.php</b> on line <b>15</b> \-- -o excepción no capturada: +o en caso de una excepción no capturada: /--pre .{font-size: 90%} <b>Fatal error</b>: Uncaught Nette\MemberAccessException: Call to undefined method Nette\Application\UI\Form::addTest()? in /sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php:100 Stack trace: #0 /sandbox/vendor/nette/utils/src/Utils/Object.php(75): Nette\Utils\ObjectMixin::call(Object(Nette\Application\UI\Form), 'addTest', Array) -#1 /sandbox/app/forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) -#2 /sandbox/app/presenters/SignPresenter.php(21): App\Forms\SignFormFactory->create() -#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presenters\SignPresenter->createComponentSignInForm('signInForm') +#1 /sandbox/app/Forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) +#2 /sandbox/app/Presentation/Sign/SignPresenter.php(21): App\Forms\SignFormFactory->create() +#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presentation\Sign\SignPresenter->createComponentSignInForm('signInForm') #4 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(139): Nette\ComponentModel\Container->createComponent('signInForm') #5 /sandbox/temp/cache/latte/15206b353f351f6bfca2c36cc.php(17): Nette\ComponentModel\Co in <b>/sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php</b> on line <b>100</b><br /> \-- -No es tan fácil navegar por esta salida. Si habilitas Tracy, tanto los errores como las excepciones se muestran de una forma completamente diferente: +Orientarse en tal salida no es precisamente fácil. Si activamos Tracy, el error o la excepción se mostrarán de una forma completamente diferente: [* tracy-exception.webp .{url:-} *] -El mensaje de error grita literalmente. Puede ver una parte del código fuente con la línea resaltada donde se ha producido el error. El mensaje explica claramente el error. Todo el sitio es [interactivo, pruébelo |https://nette.github.io/tracy/tracy-exception.html]. +El mensaje de error literalmente grita. Vemos parte del código fuente con la línea resaltada donde ocurrió el error y la información *Call to undefined method Nette\Http\User::isLogedIn()* explica claramente de qué error se trata. Toda la página es además interactiva, podemos hacer clic para ver más detalles. [Pruébalo |https://nette.github.io/tracy/tracy-exception.html]. -¿Y sabe qué? Los errores fatales se capturan y muestran de la misma manera. No es necesario instalar ninguna extensión (haga clic para ver un ejemplo en vivo): +¿Y sabes qué? De esta manera captura y muestra incluso errores fatales. Sin necesidad de instalar ninguna extensión. [* tracy-error.webp .{url:-} *] -Errores como una errata en el nombre de una variable o un intento de abrir un archivo inexistente generan informes de nivel E_NOTICE o E_WARNING. Éstos pueden pasarse por alto fácilmente y/o quedar completamente ocultos en el diseño gráfico de una página web. Deje que Tracy los gestione: +Errores como un error tipográfico en el nombre de una variable o el intento de abrir un archivo inexistente generan mensajes de nivel E_NOTICE o E_WARNING. Estos pueden pasarse por alto fácilmente en los gráficos de la página, incluso pueden no ser visibles en absoluto (a menos que mires el código fuente de la página). [* tracy-notice2.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html -O pueden mostrarse como errores: +O pueden mostrarse igual que los errores: ```php -Debugger::$strictMode = true; // display all errors -Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // all errors except deprecated notices +Debugger::$strictMode = true; // mostrar todos los errores +Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // todos los errores excepto los avisos de deprecación ``` [* tracy-notice.webp .{url:-} *] -Nota: Tracy, cuando se activa, cambia el nivel de notificación de errores a E_ALL. Si desea cambiarlo, hágalo después de llamar a `enable()`. +Nota: Tracy, después de la activación, cambia el nivel de reporte de errores a E_ALL. Si quieres cambiar este valor, hazlo después de llamar a `enable()`. -Modo de desarrollo frente a modo de producción .[#toc-development-vs-production-mode] -===================================================================================== +Modo de desarrollo vs producción +================================ -Como puedes ver, Tracy es bastante hablador, lo que puede apreciarse en el entorno de desarrollo, mientras que en el servidor de producción causaría un desastre. Eso es porque allí no debería mostrarse información de depuración. Por lo tanto, Tracy tiene **autodetección de entorno** y si el ejemplo se ejecuta en un servidor en vivo, el error se registrará en lugar de mostrarse, y el visitante sólo verá un mensaje amigable: +Como puedes ver, Tracy es bastante locuaz, lo cual se puede apreciar en el entorno de desarrollo, mientras que en un servidor de producción causaría un completo desastre. Allí no se debe imprimir ninguna información de depuración. Tracy, por lo tanto, dispone de **autodetección de entorno** y si ejecutamos el ejemplo en un servidor en vivo, el error se registrará en lugar de mostrarse y el visitante solo verá un mensaje comprensible para el usuario: [* tracy-error2.webp .{url:-} *] -El modo de producción suprime la visualización de toda la información de depuración enviada mediante [dump() |dumper], y por supuesto también todos los mensajes de error generados por PHP. Así que si has olvidado algún `dump($obj)` en el código, no tienes que preocuparte, no se mostrará nada en el servidor de producción. +El modo de producción suprime la visualización de toda la información de depuración que enviamos mediante [dump() |dumper], y por supuesto también de todos los mensajes de error que genera PHP. Por lo tanto, si has olvidado algún `dump($obj)` en el código, no tienes que preocuparte, en el servidor de producción no se imprimirá nada. -¿Cómo funciona la autodetección de modo? El modo es desarrollo si la aplicación se ejecuta en localhost (es decir, la dirección IP `127.0.0.1` o `::1`) y no hay proxy (es decir, su cabecera HTTP). En caso contrario, se ejecuta en modo producción. +¿Cómo funciona la autodetección de modo? El modo es de desarrollo si la aplicación se ejecuta en localhost (es decir, dirección IP `127.0.0.1` o `::1`) y no hay presente un proxy (es decir, su cabecera HTTP). De lo contrario, se ejecuta en modo de producción. -Si desea habilitar el modo de desarrollo en otros casos, por ejemplo para desarrolladores que acceden desde una dirección IP específica, puede especificarlo como parámetro del método `enable()`: +Si queremos habilitar el modo de desarrollo también en otros casos, por ejemplo, para programadores que acceden desde una dirección IP específica, la indicamos como parámetro del método `enable()`: ```php -Debugger::enable('23.75.345.200'); // también puede proporcionar una serie de direcciones IP +Debugger::enable('23.75.345.200'); // también se puede indicar un array de direcciones IP ``` -Recomendamos encarecidamente combinar la dirección IP con una cookie. Almacene un token secreto, por ejemplo, `secret1234`, en la cookie `tracy-debug`, y de esta manera, active el modo de desarrollo sólo para los desarrolladores que accedan desde una dirección IP específica y que tengan el mencionado token en la cookie: +Definitivamente recomendamos combinar la dirección IP con una cookie. En la cookie `tracy-debug` guardamos un token secreto, p.ej. `secret1234`, y de esta manera activamos el modo de desarrollo solo para programadores que acceden desde una dirección IP específica y que tienen el token mencionado en la cookie: ```php Debugger::enable('secret1234@23.75.345.200'); ``` -También puedes establecer directamente el modo desarrollo/producción utilizando las constantes `Debugger::Development` o `Debugger::Production` como parámetro del método `enable()`. +También podemos establecer directamente el modo de desarrollo/producción usando la constante `Debugger::Development` o `Debugger::Production` como parámetro del método `enable()`. .[note] -Si utilizas el Nette Framework, echa un vistazo a cómo establecer [el modo para él |application:bootstrap#Development vs Production Mode], y entonces también se utilizará para Tracy. +Si usas Nette Framework, consulta cómo [configurar el modo para él |application:bootstrapping#Modo de desarrollo vs producción] y ese se usará posteriormente también para Tracy. -Registro de errores .[#toc-error-logging] -========================================= +Registro de errores +=================== -En modo de producción, Tracy registra automáticamente todos los errores y excepciones en un registro de texto. Para que el registro tenga lugar, es necesario establecer la ruta absoluta al directorio de registro en la variable `$logDirectory` o pasarla como segundo parámetro al método `enable()`: +En modo de producción, Tracy registra automáticamente todos los errores y excepciones capturadas en un log de texto. Para que el registro pueda llevarse a cabo, debemos establecer la ruta absoluta al directorio de logs en la variable `$logDirectory` o pasarla como segundo parámetro del método `enable()`: ```php Debugger::$logDirectory = __DIR__ . '/log'; ``` -El registro de errores es extremadamente útil. Imagina que todos los usuarios de tu aplicación son en realidad beta testers que hacen un trabajo de primera para encontrar errores de forma gratuita, y serías tonto si tiraras sus valiosos informes sin darte cuenta a la papelera. +El registro de errores es, al mismo tiempo, extremadamente útil. Imagina que todos los usuarios de tu aplicación son en realidad betatesters que realizan gratuitamente un trabajo excelente en la búsqueda de errores y cometerías una tontería si desecharas sus valiosos reportes sin prestar atención en la papelera. -Si necesita registrar sus propios mensajes o excepciones capturadas, utilice el método `log()`: +Si necesitamos registrar nuestro propio mensaje o una excepción que hemos capturado, usamos para ello el método `log()`: ```php -Debugger::log('Unexpected error'); // text message +Debugger::log('Ocurrió un error inesperado'); // mensaje de texto try { - criticalOperation(); + operacionCritica(); } catch (Exception $e) { - Debugger::log($e); // log exception - // or - Debugger::log($e, Debugger::ERROR); // also sends an email notification + Debugger::log($e); // también se puede registrar la excepción + // o + Debugger::log($e, Debugger::ERROR); // también envía una notificación por correo electrónico } ``` -If you want Tracy to log PHP errors like `E_NOTICE` or `E_WARNING` with detailed information (HTML report), set `Debugger::$logSeverity`: +Si quieres que Tracy registre errores de PHP como `E_NOTICE` o `E_WARNING` con información detallada (informe HTML), establece `Debugger::$logSeverity`: ```php Debugger::$logSeverity = E_NOTICE | E_WARNING; ``` -Para un verdadero profesional el registro de errores es una fuente crucial de información y quiere ser notificado sobre cualquier nuevo error inmediatamente. Tracy le ayuda. Ella es capaz de enviar un correo electrónico por cada nuevo registro de error. La variable $email identifica dónde enviar estos correos electrónicos: +Para un verdadero profesional, el log de errores es una fuente clave de información y quiere ser informado inmediatamente sobre cada nuevo error. Tracy le ayuda en esto, ya que puede informar sobre un nuevo registro en el log por correo electrónico. Determinamos a dónde enviar los correos electrónicos con la variable $email: ```php Debugger::$email = 'admin@example.com'; ``` -Si utiliza todo el Nette Framework, puede establecer ésta y otras en el [fichero de configuración |nette:configuring]. +Si usas todo el Nette Framework, puedes configurar esto y otros ajustes en el [archivo de configuración |nette:configuring]. -Para proteger su buzón de correo electrónico de inundaciones, Tracy envía **sólo un mensaje** y crea un archivo `email-sent`. Cuando un desarrollador recibe la notificación por correo electrónico, comprueba el registro, corrige su aplicación y borra el archivo de seguimiento `email-sent`. Esto activa de nuevo el envío de e-mails. +Sin embargo, para que no inunde tu buzón de correo electrónico, siempre envía **solo un mensaje** y crea el archivo `email-sent`. El desarrollador, después de recibir la notificación por correo electrónico, comprueba el log, corrige la aplicación y elimina el archivo de monitorización, lo que reactiva el envío de correos electrónicos. -Abrir archivos en el editor .[#toc-opening-files-in-the-editor] -=============================================================== +Apertura en el editor +===================== -Cuando se muestra la página de errores, puede hacer clic en los nombres de los archivos y se abrirán en su editor con el cursor en la línea correspondiente. También se pueden crear archivos (acción `create file`) o corregir errores en ellos (acción `fix it`). Para ello, es necesario [configurar el navegador y el sistema |open-files-in-ide]. +Al mostrar la página de error, se puede hacer clic en los nombres de los archivos y estos se abrirán en tu editor con el cursor en la línea correspondiente. También se pueden crear archivos (acción `create file`) o corregir errores en ellos (acción `fix it`). Para que esto funcione, es suficiente [configurar el navegador y el sistema |open-files-in-ide]. -Versiones de PHP soportadas .[#toc-supported-php-versions] -========================================================== +Versiones de PHP soportadas +=========================== -| Tracy | compatible con PHP -|-----------|-------------------- -| Tracy 2.10 – 3.0 | PHP 8.0 – 8.2 +| Tracy | compatible con PHP +|-----------|------------------- +| Tracy 2.10 – 3.0 | PHP 8.0 – 8.4 | Tracy 2.9 | PHP 7.2 – 8.2 | Tracy 2.8 | PHP 7.2 – 8.1 | Tracy 2.6 – 2.7 | PHP 7.1 – 8.0 | Tracy 2.5 | PHP 5.4 – 7.4 | Tracy 2.4 | PHP 5.4 – 7.2 -Se aplica a las últimas versiones de parches. +Aplica a la última versión de parche. -Puertos .[#toc-ports] -===================== +Ports +===== -Esta es una lista de ports no oficiales a otros frameworks y CMS: +Esta es una lista de ports no oficiales para otros frameworks y CMS: - [Drupal 7](https://www.drupal.org/project/traced) - Laravel framework: [recca0120/laravel-tracy](https://github.com/recca0120/laravel-tracy), [whipsterCZ/laravel-tracy](https://github.com/whipsterCZ/laravel-tracy) diff --git a/tracy/es/open-files-in-ide.texy b/tracy/es/open-files-in-ide.texy index 787ff8a5db..057410966d 100644 --- a/tracy/es/open-files-in-ide.texy +++ b/tracy/es/open-files-in-ide.texy @@ -1,20 +1,20 @@ -¿Cómo abrir un archivo en el editor desde Tracy? (Integración IDE) -****************************************************************** +¿Cómo abrir un archivo en el editor desde Tracy? (Integración con IDE) +********************************************************************** .[perex] -Cuando se muestra la página de errores, puede hacer clic en los nombres de los archivos y se abrirán en su editor con el cursor en la línea correspondiente. También se pueden crear archivos (acción `create file`) o corregir errores en ellos (acción `fix it`). Para ello, es necesario configurar el navegador y el sistema. +Al mostrar la página de error, se puede hacer clic en los nombres de los archivos y estos se abrirán en tu editor con el cursor en la línea correspondiente. También se pueden crear archivos (acción `create file`) o corregir errores en ellos (acción `fix it`). Para que esto suceda, es necesario configurar el navegador y el sistema. -Tracy abre ficheros a través de URLs de la forma `editor://open/?file=%file&line=%line`, es decir, con el protocolo `editor://`. Registraremos nuestro propio manejador para éste. Este puede ser cualquier archivo ejecutable que procese los parámetros e inicie nuestro editor favorito. +Tracy abre archivos a través de una URL con la forma `editor://open/?file=%file&line=%line`, es decir, con el protocolo `editor://`. Registraremos un manejador personalizado para este protocolo. Este puede ser cualquier archivo ejecutable que procese los parámetros y ejecute nuestro editor favorito. -Podemos cambiar la URL en la variable `Tracy\Debugger::$editor`, o desactivar el click-through configurando `Tracy\Debugger::$editor = null`. +Puedes cambiar la URL en la variable `Tracy\Debugger::$editor`, o desactivar el clic a través de la configuración `Tracy\Debugger::$editor = null`. -Windows .[#toc-windows] -======================= +Windows +======= -1. Descargue los archivos apropiados "del repositorio Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/windows al disco. +1. Descarga los archivos correspondientes del "repositorio de Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/windows al disco. -2. Edite `open-editor.js` y descomente o edite la ruta a su editor en `settings`: +2. Edita el archivo `open-editor.js` y en el array `settings` descomenta y, si es necesario, modifica la ruta a tu editor: ```js var settings = { @@ -35,19 +35,28 @@ var settings = { ... ``` -Ten cuidado y mantén las barras dobles en las rutas. +Atención, mantén las dobles barras en las rutas. -3. Registre el manejador para el protocolo `editor://` en el sistema. +3. Registra el handler del protocolo `editor://` en el sistema. -Esto se hace ejecutando `install.cmd`. **Necesitas ejecutarlo como Administrador. El script `open-editor.js` ahora servirá al protocolo `editor://`. +Esto se hace ejecutando el archivo `install.cmd`. **Es necesario ejecutarlo como Administrador.** El script `open-editor.js` manejará ahora el protocolo `editor://`. +Para poder abrir enlaces generados en otros servidores, como en un servidor en vivo o en Docker, añade también el mapeo de la URL remota a la local en `open-editor.js`: -Linux .[#toc-linux] -=================== +```js + mappings: { + // ruta remota: ruta local + '/var/www/nette.app': 'W:\\Nette.web\\_web', + } +``` + + +Linux +===== -1. Descargue los archivos apropiados "del repositorio Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux al directorio `~/bin`. +1. Descarga los archivos correspondientes del "repositorio de Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux al directorio `~/bin`. -2. 2. Edite `open-editor.sh` y descomente o edite la ruta a su editor en la variable `editor`: +2. Edita el archivo `open-editor.sh` y descomenta y, si es necesario, modifica la ruta a tu editor en la variable `editor`. ```shell #!/bin/bash @@ -67,24 +76,25 @@ Linux .[#toc-linux] ... ``` -Hazlo ejecutable: +Haz el archivo ejecutable: ```shell chmod +x ~/bin/open-editor.sh ``` -Si el editor que está utilizando no está instalado desde el paquete, el binario probablemente no tendrá una ruta en `$PATH`. Esto puede corregirse fácilmente. En el directorio `~/bin`, crea un enlace simbólico en el binario del editor. .[note] +.[note] +Si el editor utilizado no está instalado desde un paquete, probablemente el binario no tendrá la ruta en $PATH. Esto se puede corregir fácilmente. En el directorio `~/bin`, crea un enlace simbólico al binario del editor. -3. Registre el manejador para el protocolo `editor://` en el sistema. +3. Registra el handler del protocolo `editor://` en el sistema. -Esto se hace ejecutando `install.sh`. El script `open-editor.js` servirá ahora el protocolo `editor://`. +Esto se hace ejecutando el archivo `install.sh`. El script `open-editor.sh` manejará ahora el protocolo `editor://`. -macOS .[#toc-macos] -=================== +macOS +===== -Editores como PhpStorm, TextMate, etc. te permiten abrir archivos a través de una URL especial, que sólo tienes que configurar: +Editores como PhpStorm, TextMate, etc., permiten abrir archivos a través de una URL especial, que solo necesitas configurar: ```php // PhpStorm @@ -92,44 +102,43 @@ Tracy\Debugger::$editor = 'phpstorm://open?file=%file&line=%line'; // TextMate Tracy\Debugger::$editor = 'txmt://open/?url=file://%file&line=%line'; // MacVim -Tracy\Debugger::$editor = 'mvim://open/?url=file://%file&line=%line'; +Tracy\Debugger::$editor = 'mvim://open?url=file:///%file&line=%line'; // Visual Studio Code Tracy\Debugger::$editor = 'vscode://file/%file:%line'; ``` -Si está usando Tracy independiente, ponga la línea antes de `Tracy\Debugger::enable()`, si es Nette, antes de `$configurator->enableTracy()` en `Bootstrap.php`. +Si usas Tracy de forma independiente, inserta la línea antes de `Tracy\Debugger::enable()`, si usas Nette, entonces antes de `$configurator->enableTracy()` en `Bootstrap.php`. -Lamentablemente, las acciones `create file` o `fix it` no funcionan en macOS. +Las acciones `create file` o `fix it` desafortunadamente no funcionan en macOS. -Demostraciones .[#toc-demos] -============================ +Demostraciones +============== Corrección de errores: <iframe width="560" height="315" src="https://www.youtube.com/embed/3ITT4mC0Eq4?rel=0&showinfo=0" frameborder="0" allow="encrypted-media" allowfullscreen></iframe> -Creación de un nuevo archivo: +Creación de archivo: <iframe width="560" height="315" src="https://www.youtube.com/embed/AJ_FUivAGZQ?rel=0&showinfo=0" frameborder="0" allow="encrypted-media" allowfullscreen></iframe> -Solución de problemas .[#toc-troubleshooting] -============================================= +Solución de problemas +===================== -- En Firefox puede que necesite [permitir |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] la ejecución de protocolos personalizados en about:config estableciendo `network.protocol-handler.expose.editor` como `false` y `network.protocol-handler.expose-all` como `true`. Sin embargo, debería estar permitido por defecto. -- Si no funciona todo inmediatamente, no te asustes. Intenta actualizar la página, reinicia el navegador o el ordenador. Eso debería ayudar. -- Consulte [aquí |https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/] para solucionarlo: - Error de entrada: No hay motor de script para la extensión de archivo ".js" Tal vez usted asoció el archivo ".js" a otra aplicación, no al motor JScript. +- En Firefox, puede ser necesario permitir el protocolo [configurando |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] `network.protocol-handler.expose.editor` a `false` y `network.protocol-handler.expose-all` a `true` en about:config. +- Si no funciona de inmediato, no entres en pánico e intenta refrescar la página unas cuantas veces antes de hacer clic en el enlace. ¡Empezará a funcionar! +- Aquí hay un [enlace |https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/] para corregir un posible error: `Input Error: There is no script engine for file extension ".js"`, `Maybe you associated ".js" file to another app, not JScript engine.` respectivamente `no hay disponible ningún motor de scripting para la extensión .js`. -A partir de la versión 77 de Google Chrome ya no aparecerá la casilla "Abrir siempre este tipo de enlaces en la aplicación asociada" cuando se abra el editor a través de un enlace. Solución para Windows: cree el archivo `fix.reg`: +En Google Chrome desde la versión 77 ya no verás la casilla de verificación „Abrir siempre este tipo de enlaces en la aplicación asociada“ cuando el editor se ejecuta a través de un enlace. Solución para Windows: crea el archivo `fix.reg`: ``` Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\URLWhitelist] "123"="editor://*" ``` -Imórtelo haciendo doble clic y reinicie Chrome. +Impórtalo haciendo doble clic y reinicia el navegador Chrome. -En caso de más problemas o preguntas, pregunte en [el foro |https://forum.nette.org]. +Para cualquier pregunta o comentario, por favor contacta con el [foro |https://forum.nette.org]. diff --git a/tracy/es/recipes.texy b/tracy/es/recipes.texy index c78bdad3a7..b796fb4e60 100644 --- a/tracy/es/recipes.texy +++ b/tracy/es/recipes.texy @@ -1,12 +1,11 @@ -Recetas -******* +Tutoriales +********** -Política de seguridad de contenidos .[#toc-content-security-policy] -=================================================================== +Content Security Policy +======================= -Si su sitio utiliza la Política de Seguridad de Contenidos, tendrá que añadir `'nonce-<value>'` y `'strict-dynamic'` a `script-src` para que Tracy funcione correctamente. Algunos plugins de terceros pueden requerir directivas adicionales. -Nonce no está soportado en la directiva `style-src`, si usa esta directiva necesitará añadir `'unsafe-inline'`, pero esto debería evitarse en modo de producción. +Si tu sitio web utiliza Content Security Policy, necesitarás añadir el mismo `'nonce-<value>'` y `'strict-dynamic'` a `script-src` para que Tracy funcione correctamente. Algunos complementos de terceros pueden requerir configuraciones adicionales. Nonce no es soportado en la directiva `style-src`, si utilizas esta directiva, debes añadir `'unsafe-inline'`, pero deberías evitarlo en modo de producción. Ejemplo de configuración para [Nette Framework |nette:configuring]: @@ -24,11 +23,10 @@ header("Content-Security-Policy: script-src 'nonce-$nonce' 'strict-dynamic';"); ``` -Carga más rápida .[#toc-faster-loading] -======================================= +Carga más rápida +================ -La integración básica es sencilla, sin embargo si tienes scripts lentos de bloqueo en la página web, pueden ralentizar la carga de Tracy. -La solución es colocar `<?php Tracy\Debugger::renderLoader() ?>` en tu plantilla antes de cualquier script: +La ejecución es directa, sin embargo, si tienes scripts bloqueantes de carga lenta en tu página web, pueden ralentizar la carga de Tracy. La solución es colocar `<?php Tracy\Debugger::renderLoader() ?>` en tu plantilla antes de todos los scripts: ```latte <!DOCTYPE html> @@ -42,12 +40,37 @@ La solución es colocar `<?php Tracy\Debugger::renderLoader() ?>` en tu plantill ``` -AJAX y peticiones redirigidas .[#toc-ajax-and-redirected-requests] -================================================================== +Depuración de peticiones AJAX +============================= -Tracy puede mostrar Debug bar y Bluescreens para peticiones AJAX y redirecciones. Tracy crea sus propias sesiones, almacena datos en sus propios archivos temporales y utiliza una cookie `tracy-session`. +Tracy captura automáticamente las peticiones AJAX creadas mediante jQuery o la API nativa `fetch`. Las peticiones se muestran en la barra de Tracy como líneas adicionales, lo que permite una depuración fácil y cómoda de AJAX. -Tracy también puede configurarse para usar una sesión nativa PHP, que se inicia antes de que Tracy se active: +Si no quieres capturar las peticiones AJAX automáticamente, puedes deshabilitar esta función configurando la variable de JavaScript: + +```js +window.TracyAutoRefresh = false; +``` + +Para monitorizar manualmente peticiones AJAX específicas, añade la cabecera HTTP `X-Tracy-Ajax` con el valor que devuelve `Tracy.getAjaxHeader()`. Aquí hay un ejemplo de uso con la función `fetch`: + +```js +fetch(url, { + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Tracy-Ajax': Tracy.getAjaxHeader(), + } +}) +``` + +Este enfoque permite la depuración selectiva de peticiones AJAX. + + +Almacenamiento de datos +======================= + +Tracy puede mostrar paneles en la Tracy bar y Bluescreens para peticiones AJAX y redirecciones. Tracy crea su propia sesión, almacena los datos en sus propios archivos temporales y utiliza la cookie `tracy-session`. + +Tracy también se puede configurar para usar la sesión nativa de PHP, que iniciamos antes de activar Tracy: ```php session_start(); @@ -55,44 +78,44 @@ Debugger::setSessionStorage(new Tracy\NativeSession); Debugger::enable(); ``` -En caso de que iniciar una sesión requiera una inicialización más compleja, se puede iniciar Tracy inmediatamente (para que pueda manejar cualquier error que se produzca) y después inicializar el manejador de sesión y finalmente informar a Tracy de que la sesión está lista para ser usada usando la función `dispatch()`: +En caso de que el inicio de la sesión requiera una inicialización más compleja, puedes ejecutar Tracy inmediatamente (para que pueda procesar cualquier error que surja) y luego inicializar el manejador de la sesión y finalmente informar a Tracy que la sesión está lista para usar mediante la función `dispatch()`: ```php Debugger::setSessionStorage(new Tracy\NativeSession); Debugger::enable(); -// seguido de la inicialización de la sesión -// e iniciar la sesión +// sigue la inicialización de la sesión +// y el inicio de la sesión session_start(); Debugger::dispatch(); ``` -La función `setSessionStorage()` existe desde la versión 2.9, antes de eso Tracy siempre usaba la sesión nativa de PHP. +La función `setSessionStorage()` existe desde la versión 2.9, antes Tracy usaba siempre la sesión nativa de PHP. -Depurador personalizado .[#toc-custom-scrubber] -=============================================== +Scrubber personalizado +====================== -Scrubber es un filtro que evita que se filtren datos sensibles de los volcados, como contraseñas o credenciales. El filtro es llamado para cada elemento del array u objeto volcado y devuelve `true` si el valor es sensible. En este caso, se imprime `*****` en lugar del valor. +Scrubber es un filtro que previene la fuga de datos sensibles durante el volcado, como contraseñas o credenciales de acceso. El filtro se llama para cada elemento del array u objeto volcado y devuelve `true` si el valor es sensible. En tal caso, se imprime `*****` en lugar del valor. ```php -// evita el volcado de valores clave y propiedades como `password`, +// impide la impresión de valores de claves y propiedades como `password`, // `password_repeat`, `check_password`, `DATABASE_PASSWORD`, etc. $scrubber = function(string $key, $value, ?string $class): bool { return preg_match('#password#i', $key) && $value !== null; }; -// lo utilizamos para todos los volcados dentro de BlueScreen +// lo usamos para todos los dumps dentro de BlueScreen Tracy\Debugger::getBlueScreen()->scrubber = $scrubber; ``` -Registrador personalizado .[#toc-custom-logger] -=============================================== +Logger personalizado +==================== -Podemos crear un logger personalizado para registrar errores, excepciones no capturadas, y también ser llamado por `Tracy\Debugger::log()`. Logger implementa la interfaz [api:Tracy\ILogger]. +Podemos crear nuestro propio logger que registrará errores, excepciones no capturadas y también será invocado por el método `Tracy\Debugger::log()`. El logger implementa la interfaz [api:Tracy\ILogger]. ```php use Tracy\ILogger; @@ -101,18 +124,18 @@ class SlackLogger implements ILogger { public function log($value, $priority = ILogger::INFO) { - // envía una solicitud a Slack + // envía una petición a Slack } } ``` -Y luego lo activamos: +Y posteriormente lo activamos: ```php Tracy\Debugger::setLogger(new SlackLogger); ``` -Si usamos el Nette Framework completo, podemos configurarlo en el archivo de configuración de NEON: +Si usamos el Nette Framework completo, puedes configurarlo en el archivo de configuración NEON: ```neon services: @@ -120,8 +143,8 @@ services: ``` -Integración Monolog .[#toc-monolog-integration] ------------------------------------------------ +Integración de monolog +---------------------- El paquete Tracy proporciona un adaptador PSR-3 que permite la integración de [monolog/monolog](https://github.com/Seldaek/monolog). @@ -134,20 +157,20 @@ Debugger::setLogger($tracyLogger); Debugger::enable(); Debugger::log('info'); // escribe: [<TIMESTAMP>] main-channel.INFO: info [] [] -Debugger::log('warning', Debugger::WARNING); // escribe: [<TIMESTAMP>] main-channel.WARNING: advertencia [] [] +Debugger::log('warning', Debugger::WARNING); // escribe: [<TIMESTAMP>] main-channel.WARNING: warning [] [] ``` -nginx .[#toc-nginx] -=================== +nginx +===== -Si Tracy no funciona en nginx, probablemente esté mal configurado. Si hay algo como +Si Tracy no funciona en el servidor nginx, probablemente esté mal configurado. Si hay algo como esto en la configuración: ```nginx try_files $uri $uri/ /index.php; ``` -cámbielo por +cámbialo a: ```nginx try_files $uri $uri/ /index.php$is_args$args; diff --git a/tracy/es/stopwatch.texy b/tracy/es/stopwatch.texy index f20c201ed1..d91c4f5076 100644 --- a/tracy/es/stopwatch.texy +++ b/tracy/es/stopwatch.texy @@ -1,35 +1,35 @@ -Cronómetro -********** +Medición de tiempo +****************** -Otra herramienta útil es el cronómetro del depurador con una precisión de microsegundos: +Otra herramienta útil del depurador es el cronómetro con precisión de microsegundos: ```php Debugger::timer(); -// sweet dreams my cherrie +// mi principito duerme, los pajaritos ya duermen dulcemente... sleep(2); $elapsed = Debugger::timer(); // $elapsed = 2 ``` -Se pueden realizar varias mediciones a la vez mediante un parámetro opcional. +Con un parámetro opcional, se pueden lograr múltiples mediciones. ```php Debugger::timer('page-generating'); -// some code +// algún código Debugger::timer('rss-generating'); -// some code +// algún código $rssElapsed = Debugger::timer('rss-generating'); $pageElapsed = Debugger::timer('page-generating'); ``` ```php -Debugger::timer(); // runs the timer +Debugger::timer(); // inicia el cronómetro -... // some time-consuming operation +... // operación que consume tiempo -echo Debugger::timer(); // elapsed time in seconds +echo Debugger::timer(); // imprime el tiempo transcurrido en segundos ``` diff --git a/tracy/fr/@home.texy b/tracy/fr/@home.texy index c565fb027b..a29158f84a 100644 --- a/tracy/fr/@home.texy +++ b/tracy/fr/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Tracy - Un outil de débogage indispensable pour tous les développeurs PHP}} -{{description: Tracy est un outil conçu pour faciliter le débogage du code PHP. C'est un assistant utile pour tous les programmeurs PHP, qui aide à visualiser clairement et à enregistrer les erreurs, à vidanger les variables et bien plus encore. Avertissement : Tracy est addictif !}} +{{maintitle: Tracy – l'outil de débogage avec lequel c'est un plaisir de faire des erreurs}} +{{description: Tracy est un outil conçu pour faciliter le débogage du code PHP. C'est une aide utile pour tous les programmeurs PHP, qui les aide à visualiser et à journaliser les erreurs, à vider les variables et bien plus encore. Attention : Tracy crée une dépendance !}} diff --git a/tracy/fr/@left-menu.texy b/tracy/fr/@left-menu.texy index e2696e97ae..611b530a6d 100644 --- a/tracy/fr/@left-menu.texy +++ b/tracy/fr/@left-menu.texy @@ -1,7 +1,7 @@ -- [Commencer à travailler |Guide] -- [Dumper] -- [Chronomètre |Stopwatch] -- [Configuration |Configuring] -- [Recettes |Recipes] -- [Intégration à l'IDE |open-files-in-ide] +- [Commencer avec Tracy |guide] +- [Dumpage |dumper] +- [Mesure du temps |stopwatch] +- [Configuration |configuring] +- [Recettes |recipes] +- [Intégration avec l'IDE |open-files-in-ide] - [Création d'extensions |extensions] diff --git a/tracy/fr/@menu.texy b/tracy/fr/@menu.texy index 4af965532f..3b2105acd8 100644 --- a/tracy/fr/@menu.texy +++ b/tracy/fr/@menu.texy @@ -1,3 +1,3 @@ -- [Accueil |@home] -- [Documentation |Guide] +- [Introduction |@home] +- [Documentation |guide] - "GitHub .[link-external]":https://github.com/nette/tracy diff --git a/tracy/fr/@meta.texy b/tracy/fr/@meta.texy new file mode 100644 index 0000000000..ccb3776c80 --- /dev/null +++ b/tracy/fr/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentation Tracy}} diff --git a/tracy/fr/configuring.texy b/tracy/fr/configuring.texy index 83b4afb513..e59ca17685 100644 --- a/tracy/fr/configuring.texy +++ b/tracy/fr/configuring.texy @@ -1,142 +1,141 @@ Configuration de Tracy ********************** -Les exemples suivants supposent que l'alias de classe suivant est défini : +Tous les exemples supposent qu'un alias a été créé : ```php use Tracy\Debugger; ``` -Enregistrement des erreurs .[#toc-error-logging] ------------------------------------------------- +Logging des erreurs +------------------- ```php $logger = Debugger::getLogger(); -// if error has occurred the notification is sent to this email -$logger->email = 'dev@example.com'; // (string|string[]) defaults to unset +// e-mail auquel les notifications d'erreur sont envoyées +$logger->email = 'dev@example.com'; // (string|string[]) par défaut non défini -// email sender -$logger->fromEmail = 'me@example.com'; // (string) defaults to unset +// expéditeur de l'e-mail +$logger->fromEmail = 'me@example.com'; // (string) par défaut non défini -// routine for sending email -$logger->mailer = /* ... */; // (callable) default it sending by mail() +// routine assurant l'envoi de l'e-mail +$logger->mailer = /* ... */; // (callable) par défaut l'envoi via la fonction mail() -// after what shortest time to send another email? -$logger->emailSnooze = /* ... */; // (string) default is '2 days' +// après quel délai minimum envoyer le prochain e-mail ? +$logger->emailSnooze = /* ... */; // (string) par défaut '2 days' -// for which error levels is BlueScreen also logged? -Debugger::$logSeverity = E_WARNING | E_NOTICE; // defaults to 0 (no error level) +// pour quels niveaux d'erreur le BlueScreen est-il également journalisé ? +Debugger::$logSeverity = E_WARNING | E_NOTICE; // par défaut 0 (aucun niveau d'erreur) ``` -`dump()` Comportement .[#toc-dump-behavior] -------------------------------------------- +Comportement de `dump()` +------------------------ ```php -// maximum string length -Debugger::$maxLength = 150; // (int) default according to Tracy +// longueur maximale de la chaîne de caractères +Debugger::$maxLength = 150; // (int) par défaut selon la version de Tracy -// how deep will list -Debugger::$maxDepth = 10; // (int) default according to Tracy +// profondeur maximale d'imbrication +Debugger::$maxDepth = 10; // (int) par défaut selon la version de Tracy -// hide values of these keys (since Tracy 2.8) -Debugger::$keysToHide = ['password', /* ... */]; // (string[]) defaults to [] +// masquer les valeurs de ces clés (depuis Tracy 2.8) +Debugger::$keysToHide = ['password', /* ... */]; // (string[]) par défaut [] -// visual theme (since Tracy 2.8) -Debugger::$dumpTheme = 'dark'; // (light|dark) defaults to 'light' +// thème visuel (depuis Tracy 2.8) +Debugger::$dumpTheme = 'dark'; // (light|dark) par défaut 'light' -// displays the location where dump() was called? -Debugger::$showLocation = /* ... */; // (bool) default according to Tracy +// afficher l'endroit où la fonction dump() a été appelée ? +Debugger::$showLocation = /* ... */; // (bool) par défaut selon la version de Tracy ``` -Autres .[#toc-others] ---------------------- +Autres +------ ```php -// in Development mode, you will see notice or error warnings as BlueScreen -Debugger::$strictMode = /* ... */; // (bool|int) defaults to false, you can select only specific error levels (e.g. E_USER_DEPRECATED | E_DEPRECATED) +// en mode développement, affiche les erreurs de type notice ou warning comme BlueScreen +Debugger::$strictMode = /* ... */; // (bool|int) par défaut false, il est possible de sélectionner seulement certains niveaux d'erreur (par ex. E_USER_DEPRECATED | E_DEPRECATED) -// displays silent (@) error messages -Debugger::$scream = /* ... */; // (bool|int) defaults to false, since version 2.9 it is possible to select only specific error levels (e.g. E_USER_DEPRECATED | E_DEPRECATED) +// afficher les messages d'erreur masqués (@) ? +Debugger::$scream = /* ... */; // (bool|int) par défaut false, depuis la version 2.9 il est possible de sélectionner seulement certains niveaux d'erreur (par ex. E_USER_DEPRECATED | E_DEPRECATED) -// link format to open in the editor -Debugger::$editor = /* ... */; // (string|null) defaults to 'editor://open/?file=%file&line=%line' +// format du lien pour l'ouverture dans l'éditeur +Debugger::$editor = /* ... */; // (string|null) par défaut 'editor://open/?file=%file&line=%line' -// path to template with custom page for error 500 -Debugger::$errorTemplate = /* ... */; // (string) defaults to unset +// chemin vers le template avec une page personnalisée pour l'erreur 500 +Debugger::$errorTemplate = /* ... */; // (string) par défaut non défini -// shows Tracy Bar? -Debugger::$showBar = /* ... */; // (bool) defaults to true +// afficher la barre Tracy ? +Debugger::$showBar = /* ... */; // (bool) par défaut true Debugger::$editorMapping = [ - // original => new + // original => nouveau '/var/www/html' => '/data/web', '/home/web' => '/srv/html', ]; ``` -Cadre de Nette .[#toc-nette-framework] --------------------------------------- +Nette Framework +--------------- -Si vous utilisez le Nette Framework, vous pouvez également configurer Tracy et ajouter de nouveaux panneaux à la barre Tracy à l'aide du fichier de configuration. -Vous pouvez définir les paramètres de Tracy dans la configuration et également ajouter de nouveaux panneaux à la barre Tracy. Ces paramètres ne sont appliqués qu'après la création du conteneur DI, de sorte que les erreurs survenues plus tôt ne peuvent pas les refléter. +Si vous utilisez Nette Framework, vous pouvez configurer Tracy et ajouter de nouveaux panneaux à la barre Tracy également via le fichier de configuration. Dans la configuration, vous pouvez définir des paramètres et ajouter de nouveaux panneaux à la barre Tracy. Ces paramètres ne sont appliqués qu'après la création du conteneur DI, les erreurs survenues avant ne peuvent donc pas les refléter. -Configuration de l'enregistrement des erreurs : +Configuration du logging des erreurs : ```neon tracy: - # if error has occurred the notification is sent to this email - email: dev@example.com # (string|string[]) defaults to unset + # e-mail auquel les notifications d'erreur sont envoyées + email: dev@example.com # (string|string[]) par défaut non défini - # email sender - fromEmail: robot@example.com # (string) defaults to unset + # expéditeur de l'e-mail + fromEmail: robot@example.com # (string) par défaut non défini - # period of postponement of emails sending (since Tracy 2.8.8) - emailSnooze: ... # (string) defaults to '2 days' + # durée de report de l'envoi des e-mails (depuis Tracy 2.8.8) + emailSnooze: ... # (string) par défaut '2 days' - # to use a mailer defined in the configuration? (since Tracy 2.5) - netteMailer: ... # (bool) defaults to true + # utiliser Nette mailer pour l'envoi des e-mails ? (depuis Tracy 2.5) + netteMailer: ... # (bool) par défaut true - # for which error levels is BlueScreen also logged? - logSeverity: [E_WARNING, E_NOTICE] # defaults to [] + # pour quels niveaux d'erreur le BlueScreen est-il également journalisé ? + logSeverity: [E_WARNING, E_NOTICE] # par défaut [] ``` -Configuration pour la fonction `dump()`: +Configuration du comportement de la fonction `dump()` : ```neon tracy: - # maximum string length - maxLength: 150 # (int) default according to Tracy + # longueur maximale de la chaîne de caractères + maxLength: 150 # (int) par défaut selon la version de Tracy - # how deep will list - maxDepth: 10 # (int) default according to Tracy + # profondeur maximale d'imbrication + maxDepth: 10 # (int) par défaut selon la version de Tracy - # hide values of these keys (since Tracy 2.8) - keysToHide: [password, pass] # (string[]) defaults to [] + # masquer les valeurs de ces clés (depuis Tracy 2.8) + keysToHide: [password, pass] # (string[]) par défaut [] - # visual theme (since Tracy 2.8) - dumpTheme: dark # (light|dark) defaults to 'light' + # thème visuel (depuis Tracy 2.8) + dumpTheme: dark # (light|dark) par défaut 'light' - # displays the location where dump() was called? - showLocation: ... # (bool) default according to Tracy + # afficher l'endroit où la fonction dump() a été appelée ? + showLocation: ... # (bool) par défaut selon la version de Tracy ``` -Pour installer l'extension Tracy : +Installation des extensions Tracy : ```neon tracy: - # appends bars to Tracy Bar + # ajoute des panneaux à la barre Tracy bar: - Nette\Bridges\DITracy\ContainerPanel - IncludePanel - XDebugHelper('myIdeKey') - MyPanel(@MyService) - # append panels to BlueScreen + # ajoute des panneaux au BlueScreen blueScreen: - DoctrinePanel::renderException ``` @@ -145,25 +144,37 @@ Autres options : ```neon tracy: - # in Development mode, you will see notice or error warnings as BlueScreen - strictMode: ... # defaults to true + # en mode développement, affiche les erreurs de type notice ou warning comme BlueScreen + strictMode: ... # par défaut true - # displays silent (@) error messages - scream: ... # defaults to false + # afficher les messages d'erreur masqués (@) ? + scream: ... # par défaut false - # link format to open in the editor - editor: ... # (string) defaults to 'editor://open/?file=%file&line=%line' + # format du lien pour l'ouverture dans l'éditeur + editor: ... # (string) par défaut 'editor://open/?file=%file&line=%line' - # path to template with custom page for error 500 - errorTemplate: ... # (string) defaults to unset + # chemin vers le template avec une page personnalisée pour l'erreur 500 + errorTemplate: ... # (string) par défaut non défini - # shows Tracy Bar? - showBar: ... # (bool) defaults to true + # afficher la barre Tracy ? + showBar: ... # (bool) par défaut true editorMapping: - # original: new + # original: nouveau /var/www/html: /data/web /home/web: /srv/html ``` -Les valeurs des options `logSeverity`, `strictMode` et `scream` peuvent être écrites comme un tableau de niveaux d'erreur (par ex. `[E_WARNING, E_NOTICE]`) ou comme une expression utilisée en PHP (par exemple, `E_ALL & ~E_NOTICE`). +Les valeurs des options `logSeverity`, `strictMode` et `scream` peuvent être écrites comme un tableau de niveaux d'erreur (par ex. `[E_WARNING, E_NOTICE]`), ou comme une expression utilisée dans le langage PHP (par ex. `E_ALL & ~E_NOTICE`). + + +Services DI +----------- + +Ces services sont ajoutés au conteneur DI : + +| Nom | Type | Description +|---------------------------------------------------------- +| `tracy.logger` | [api:Tracy\ILogger] | logger +| `tracy.blueScreen` | [api:Tracy\BlueScreen] | BlueScreen +| `tracy.bar` | [api:Tracy\Bar] | Barre Tracy diff --git a/tracy/fr/dumper.texy b/tracy/fr/dumper.texy index 42e90a52ec..b459fc3684 100644 --- a/tracy/fr/dumper.texy +++ b/tracy/fr/dumper.texy @@ -1,20 +1,20 @@ -Dumper -****** +Dump +**** -Tout développeur de débogage connaît bien la fonction `var_dump`, qui liste en détail le contenu de n'importe quelle variable. Malheureusement, sa sortie est dépourvue de formatage HTML et produit le dump en une seule ligne de code HTML, sans parler de l'échappement du contexte. Il est nécessaire de remplacer le `var_dump` par une fonction plus pratique. C'est exactement ce qu'est `dump()`. +Chaque débogueur est un bon ami de la fonction [php:var_dump], qui affiche en détail le contenu d'une variable. Malheureusement, dans un environnement HTML, l'affichage perd son formatage et se fond en une seule ligne, sans parler de l'assainissement du code HTML. En pratique, il est nécessaire de remplacer `var_dump` par une fonction plus pratique. C'est précisément `dump()`. ```php $arr = [10, 20.2, true, null, 'hello']; dump($arr); -// or Debugger::dump($arr); +// ou Debugger::dump($arr); ``` génère la sortie : [* dump-basic.webp *] -Vous pouvez changer le thème clair par défaut en thème foncé : +Vous pouvez changer le thème clair par défaut en thème sombre : ```php Debugger::$dumpTheme = 'dark'; @@ -22,27 +22,27 @@ Debugger::$dumpTheme = 'dark'; [* dump-dark.webp *] -Vous pouvez également modifier la profondeur d'imbrication par `Debugger::$maxDepth` et la longueur des chaînes affichées par `Debugger::$maxLength`. Naturellement, des valeurs plus faibles accélèrent le rendu de Tracy. +Nous pouvons également modifier la profondeur d'imbrication à l'aide de [Debugger::$maxDepth |api:Tracy\Debugger::$maxDepth] et la longueur des descriptions affichées à l'aide de [Debugger::$maxLength |api:Tracy\Debugger::$maxLength]. Des valeurs plus basses accéléreront naturellement Tracy. ```php Debugger::$maxDepth = 2; // default: 3 Debugger::$maxLength = 50; // default: 150 ``` -La fonction `dump()` peut afficher d'autres informations utiles. `Tracy\Dumper::LOCATION_SOURCE` ajoute une infobulle avec le chemin d'accès au fichier, dans lequel la fonction a été appelée. `Tracy\Dumper::LOCATION_LINK` ajoute un lien vers le fichier. `Tracy\Dumper::LOCATION_CLASS` ajoute une infobulle à chaque objet vidé contenant le chemin d'accès au fichier, dans lequel la classe de l'objet est définie. Toutes ces constantes peuvent être définies dans la variable `Debugger::$showLocation` avant d'appeler la fonction `dump()`. Vous pouvez définir plusieurs valeurs à la fois en utilisant l'opérateur `|`. +La fonction `dump()` peut également afficher d'autres informations utiles. La constante `Tracy\Dumper::LOCATION_SOURCE` ajoute une infobulle avec le chemin vers l'endroit où la fonction a été appelée. `Tracy\Dumper::LOCATION_LINK` nous fournit un lien vers cet endroit. `Tracy\Dumper::LOCATION_CLASS` affiche pour chaque objet dumpé une infobulle avec le chemin vers le fichier où sa classe est définie. Les constantes sont définies dans la variable `Debugger::$showLocation` avant l'appel de `dump()`. Si nous voulons définir plusieurs valeurs à la fois, nous les combinons à l'aide de l'opérateur `|`. ```php -Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // Shows path to where the dump() was called -Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Shows both paths to the classes and link to where the dump() was called -Debugger::$showLocation = false; // Hides additional location information -Debugger::$showLocation = true; // Shows all additional location information +Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // Définit uniquement l'affichage de l'endroit de l'appel de la fonction +Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Définit à la fois l'affichage du lien et le chemin vers la classe +Debugger::$showLocation = false; // Désactive l'affichage des informations supplémentaires +Debugger::$showLocation = true; // Active l'affichage de toutes les informations supplémentaires ``` -Une alternative très pratique à `dump()` est `dumpe()` (c'est-à-dire dump and exit) et `bdump()`. Cela nous permet de vider les variables dans Tracy Bar. C'est utile, car les dumps ne brouillent pas la sortie et nous pouvons également ajouter un titre au dump. +Une alternative pratique à `dump()` est `dumpe()` (dump & exit) et `bdump()`. Ce dernier nous permet d'afficher la valeur d'une variable dans le panneau de la barre Tracy. C'est très pratique, car les dumps sont séparés de la mise en page de la page et nous pouvons également y ajouter un commentaire. ```php -bdump([2, 4, 6, 8], 'even numbers up to ten'); -bdump([1, 3, 5, 7, 9], 'odd numbers up to ten'); +bdump([2, 4, 6, 8], 'nombres pairs jusqu\'à dix'); +bdump([1, 3, 5, 7, 9], 'nombres impairs jusqu\'à dix'); ``` -[* bardump-en.webp *] +[* bardump-cs.webp *] diff --git a/tracy/fr/extensions.texy b/tracy/fr/extensions.texy index d50f5af63f..68221f80bc 100644 --- a/tracy/fr/extensions.texy +++ b/tracy/fr/extensions.texy @@ -1,23 +1,23 @@ -Création d'extensions de Tracy -****************************** +Création d'extensions pour Tracy +******************************** <div class=perex> -Tracy est un excellent outil pour déboguer votre application. Cependant, vous avez parfois besoin de plus d'informations que celles offertes par Tracy. Vous apprendrez à connaître : +Tracy fournit un excellent outil pour déboguer votre application. Parfois, cependant, vous aimeriez avoir quelques informations supplémentaires à portée de main. Nous allons vous montrer comment écrire votre propre extension pour la barre Tracy afin de rendre le développement encore plus agréable. -- Créer vos propres panneaux Tracy Bar -- Créer vos propres extensions Bluescreen +- Création de votre propre panneau pour la barre Tracy +- Création de votre propre extension pour Bluescreen </div> .[tip] -Vous pouvez trouver des extensions utiles pour Tracy sur "Componette":https://componette.org/search/tracy. +Vous trouverez le dépôt des extensions Tracy prêtes à l'emploi sur "Componette":https://componette.org/search/tracy. -Extensions de la barre Tracy .[#toc-tracy-bar-extensions] -========================================================= +Extensions pour la barre Tracy +============================== -La création d'une nouvelle extension pour Tracy Bar est simple. Vous devez implémenter l'interface `Tracy\IBarPanel` avec les méthodes `getTab()` et `getPanel()`. Ces méthodes doivent renvoyer le code HTML d'un onglet (petite étiquette sur Tracy Bar) et d'un panneau (fenêtre contextuelle affichée après avoir cliqué sur l'onglet). Si `getPanel()` ne renvoie rien, seul l'onglet sera affiché. Si `getTab()` ne renvoie rien, rien n'est affiché et `getPanel()` ne sera pas appelé. +Créer une nouvelle extension pour la barre Tracy n'est pas compliqué. Vous créez un objet implémentant l'interface `Tracy\IBarPanel`, qui a deux méthodes `getTab()` et `getPanel()`. Les méthodes doivent retourner le code HTML de l'onglet (une petite étiquette affichée directement sur la barre) et du panneau. Si `getPanel()` ne retourne rien, seule l'étiquette elle-même sera affichée. Si `getTab()` ne retourne rien, rien ne sera affiché du tout et getPanel() ne sera même pas appelé. ```php class ExamplePanel implements Tracy\IBarPanel @@ -35,16 +35,16 @@ class ExamplePanel implements Tracy\IBarPanel ``` -Enregistrement .[#toc-registration] ------------------------------------ +Enregistrement +-------------- -L'inscription se fait en appelant `Tracy\Bar::addPanel()`: +L'enregistrement se fait via `Tracy\Bar::addPanel()` : ```php Tracy\Debugger::getBar()->addPanel(new ExamplePanel); ``` -ou vous pouvez simplement enregistrer votre panneau dans la configuration de l'application : +Ou vous pouvez enregistrer le panneau directement dans la configuration de l'application : ```neon tracy: @@ -53,70 +53,70 @@ tracy: ``` -Code HTML de l'onglet .[#toc-tab-html-code] -------------------------------------------- +Code HTML de l'onglet +--------------------- -devrait ressembler à quelque chose comme ceci : +Il devrait ressembler approximativement à ceci : ```latte -<span title="Explaining tooltip"> +<span title="Description explicative"> <svg>...</svg> - <span class="tracy-label">Title</span> + <span class="tracy-label">Titre</span> </span> ``` -L'image doit être au format SVG. Si vous n'avez pas besoin d'infobulle, vous pouvez omettre `<span>` l'info-bulle. +L'image doit être au format SVG. Si la description explicative n'est pas nécessaire, le `<span>` peut être omis. -Code HTML du panneau .[#toc-panel-html-code] --------------------------------------------- +Code HTML du panneau +-------------------- -devrait ressembler à quelque chose comme ceci : +Il devrait ressembler approximativement à ceci : ```latte -<h1>Title</h1> +<h1>Titre</h1> <div class="tracy-inner"> <div class="tracy-inner-container"> - ... content ... + ... contenu ... </div> </div> ``` -Le titre doit être identique à celui de l'onglet ou contenir des informations supplémentaires. +Le titre doit être soit le même que le titre de l'onglet, soit il peut contenir des informations supplémentaires. -Une extension peut être enregistrée plusieurs fois, il est donc recommandé de ne pas utiliser l'attribut `id` pour le style. Vous pouvez utiliser des classes, de préférence au format `tracy-addons-<class-name>[-<optional>]` de préférence. Lors de la création d'un CSS, il est préférable d'utiliser `#tracy-debug .class`, car cette règle a une priorité supérieure à celle de la réinitialisation. +Il faut tenir compte du fait qu'une même extension peut être enregistrée plusieurs fois, par exemple avec des paramètres différents, il n'est donc pas possible d'utiliser des ID CSS pour le style, mais seulement des classes, et ce sous la forme `tracy-addons-<NomDeLaClasse>[-<optionnel>]`. Écrivez ensuite la classe dans le div avec la classe `tracy-inner`. Lors de l'écriture du CSS, il est utile d'écrire `#tracy-debug .classe`, car la règle a alors une priorité plus élevée que le reset. -Styles par défaut .[#toc-default-styles] ----------------------------------------- +Styles par défaut +----------------- -Dans le panneau, les éléments `<a>`, `<table>`, `<pre>`, `<code>` ont des styles par défaut. Pour créer un lien permettant de masquer ou d'afficher un autre élément, connectez-les avec les attributs `href` et `id` et la classe `tracy-toggle`. +Dans le panneau, `<a>`, `<table>`, `<pre>`, `<code>` sont pré-stylés. Si vous souhaitez créer un lien qui masque et affiche un autre élément, liez-les avec les attributs `href` et `id` et la classe `tracy-toggle` : ```latte -<a href="#tracy-addons-className-{$counter}" class="tracy-toggle">Detail</a> +<a href="#tracy-addons-NomDeLaClasse-{$counter}" class="tracy-toggle">Détails</a> -<div id="tracy-addons-className-{$counter}">...</div> +<div id="tracy-addons-NomDeLaClasse-{$counter}">...</div> ``` -Si l'état par défaut est effondré, ajoutez la classe `tracy-collapsed` aux deux éléments. +Si l'état par défaut doit être réduit, ajoutez la classe `tracy-collapsed` aux deux éléments. -Utilisez un compteur statique pour éviter les doublons d'identifiants sur une page. +Utilisez un compteur statique pour éviter de créer des ID en double sur une même page. -Extensions de Bluescreen .[#toc-bluescreen-extensions] -====================================================== +Extensions pour Bluescreen +========================== -Vous pouvez ajouter vos propres visualisations d'exception ou panneaux, qui apparaîtront sur le bluescreen. +De cette manière, il est possible d'ajouter des visualisations personnalisées d'exceptions ou des panneaux qui s'afficheront sur le bluescreen. -L'extension est faite comme ceci : +L'extension est créée avec cette instruction : ```php -Tracy\Debugger::getBlueScreen()->addPanel(function (?Throwable $e) { // catched exception +Tracy\Debugger::getBlueScreen()->addPanel(function (?Throwable $e) { // exception interceptée return [ - 'tab' => '...Title...', - 'panel' => '...content...', + 'tab' => '...Étiquette...', + 'panel' => '...Code HTML du panneau...', ]; }); ``` -La fonction est appelée deux fois, d'abord l'exception elle-même est passée dans le paramètre `$e` et le panneau retourné est rendu au début de la page. Si rien n'est renvoyé, le panneau n'est pas rendu. Ensuite, elle est appelée avec le paramètre `null` et le panneau renvoyé est rendu en dessous de la pile d'appels. Si la fonction renvoie `'bottom' => true` dans le tableau, le panneau est rendu tout en bas. +La fonction est appelée deux fois, d'abord l'exception elle-même est passée dans le paramètre `$e` et le panneau retourné est affiché au début de la page. S'il ne retourne rien, le panneau n'est pas affiché. Ensuite, elle est appelée avec le paramètre `null` et le panneau retourné est affiché sous la pile d'appels (callstack). Si la fonction retourne la clé `'bottom' => true` dans le tableau, le panneau est affiché tout en bas. diff --git a/tracy/fr/guide.texy b/tracy/fr/guide.texy index de2abc975f..53f9f4db10 100644 --- a/tracy/fr/guide.texy +++ b/tracy/fr/guide.texy @@ -1,218 +1,217 @@ -Démarrer avec Tracy -******************* +Démarrage avec Tracy +******************** <div class=perex> -La bibliothèque Tracy est une aide utile pour les programmeurs PHP de tous les jours. Elle vous aide à : +La bibliothèque Tracy est une aide quotidienne utile pour le programmeur PHP. Elle vous aidera à : - détecter et corriger rapidement les erreurs -- enregistrer les erreurs -- vider les variables -- mesurer le temps d'exécution des scripts/requêtes -- voir la consommation de mémoire +- journaliser les erreurs +- afficher les variables +- mesurer le temps d'exécution des scripts et des requêtes de base de données +- surveiller l'utilisation de la mémoire </div> -PHP est un langage parfait pour faire des erreurs difficilement détectables car il offre une grande flexibilité aux programmeurs. Tracy\Debugger est plus précieux pour cette raison. C'est un outil ultime parmi les outils de diagnostic. +PHP est un langage propice à la création d'erreurs difficiles à détecter, car il donne aux développeurs une grande liberté. L'outil de débogage Tracy est d'autant plus précieux. Parmi les outils de diagnostic pour PHP, il représente le summum absolu. -Si vous rencontrez Tracy pour la première fois, croyez-moi, votre vie commence à être divisée en une avant Tracy et une avec elle. Bienvenue dans la bonne partie ! +Si vous rencontrez Tracy pour la première fois aujourd'hui, croyez bien que votre vie va se diviser en celle avant Tracy et celle avec elle. Bienvenue dans la meilleure partie ! -Installation et conditions requises .[#toc-installation-and-requirements] -========================================================================= +Installation +============ -La meilleure façon d'installer Tracy est de [télécharger le dernier paquet](https://github.com/nette/tracy/releases) ou d'utiliser Composer : +La meilleure façon d'installer Tracy est de [télécharger le dernier paquet](https://github.com/nette/tracy/releases), ou d'utiliser Composer : ```shell composer require tracy/tracy ``` -Vous pouvez également télécharger le paquet complet ou le fichier [tracy.phar |https://github.com/nette/tracy/releases]. +Vous pouvez également télécharger le paquet complet sous forme de fichier [tracy.phar |https://github.com/nette/tracy/releases]. -Utilisation .[#toc-usage] -========================= +Utilisation +=========== -Tracy est activé en appelant la méthode `Tracy\Debugger::enable()' dès que possible au début du programme, avant que toute sortie ne soit envoyée : +Nous activons Tracy en appelant la méthode `Tracy\Debugger::enable()` le plus tôt possible au début du programme, avant d'envoyer toute sortie : ```php use Tracy\Debugger; -require 'vendor/autoload.php' ; // alternativement tracy.phar +require 'vendor/autoload.php'; // ou tracy.phar Debugger::enable(); ``` -La première chose que vous remarquerez sur la page est la barre Tracy dans le coin inférieur droit. Si vous ne la voyez pas, cela peut signifier que Tracy fonctionne en mode production. -En effet, Tracy n'est visible que sur localhost pour des raisons de sécurité. Pour tester son fonctionnement, vous pouvez le mettre temporairement en mode développement en utilisant le paramètre `Debugger::enable(Debugger::Development)`. +La première chose que vous remarquerez sur la page est la barre Tracy dans le coin inférieur droit. Si vous ne la voyez pas, cela peut signifier que Tracy fonctionne en mode production. En effet, pour des raisons de sécurité, Tracy n'est visible que sur localhost. Pour tester si elle fonctionne, vous pouvez la basculer temporairement en mode développement à l'aide du paramètre `Debugger::enable(Debugger::Development)`. -Barre Tracy .[#toc-tracy-bar] -============================= +Barre Tracy +=========== -La barre Tracy est un panneau flottant. Elle s'affiche dans le coin inférieur droit d'une page. Vous pouvez la déplacer à l'aide de la souris. Elle se souviendra de sa position après le rechargement de la page. +La barre Tracy est un panneau flottant qui s'affiche dans le coin inférieur droit de la page. Nous pouvons la déplacer avec la souris et elle se souviendra de sa position après le rechargement de la page. [* tracy-bar.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html -Vous pouvez ajouter d'autres panneaux utiles à la barre Tracy. Vous pouvez en trouver des intéressants dans les [addons |https://componette.org] ou vous pouvez [créer les vôtres|extensions]. +Il est possible d'ajouter d'autres panneaux utiles à la barre Tracy. Vous en trouverez beaucoup dans les [add-ons |https://componette.org], ou vous pouvez même [écrire les vôtres |extensions]. -Si vous ne voulez pas afficher Tracy Bar, réglez : +Si vous ne souhaitez pas afficher la barre Tracy, définissez : ```php Debugger::$showBar = false; ``` -Visualisation des erreurs et des exceptions .[#toc-visualization-of-errors-and-exceptions] -========================================================================================== +Visualisation des erreurs et exceptions +======================================= -Vous savez certainement comment PHP signale les erreurs : il y a quelque chose comme ceci dans le code source de la page : +Vous savez sûrement bien comment PHP signale les erreurs : il affiche quelque chose comme ceci dans le code source de la page : /--pre .{font-size: 90%} <b>Parse error</b>: syntax error, unexpected '}' in <b>HomePresenter.php</b> on line <b>15</b> \-- -ou exception non attrapée : +ou lors d'une exception non interceptée : /--pre .{font-size: 90%} <b>Fatal error</b>: Uncaught Nette\MemberAccessException: Call to undefined method Nette\Application\UI\Form::addTest()? in /sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php:100 Stack trace: #0 /sandbox/vendor/nette/utils/src/Utils/Object.php(75): Nette\Utils\ObjectMixin::call(Object(Nette\Application\UI\Form), 'addTest', Array) -#1 /sandbox/app/forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) -#2 /sandbox/app/presenters/SignPresenter.php(21): App\Forms\SignFormFactory->create() -#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presenters\SignPresenter->createComponentSignInForm('signInForm') +#1 /sandbox/app/Forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) +#2 /sandbox/app/Presentation/Sign/SignPresenter.php(21): App\Forms\SignFormFactory->create() +#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presentation\Sign\SignPresenter->createComponentSignInForm('signInForm') #4 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(139): Nette\ComponentModel\Container->createComponent('signInForm') #5 /sandbox/temp/cache/latte/15206b353f351f6bfca2c36cc.php(17): Nette\ComponentModel\Co in <b>/sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php</b> on line <b>100</b><br /> \-- -Il n'est pas si facile de naviguer dans cette sortie. Si vous activez Tracy, les erreurs et les exceptions sont affichées sous une forme complètement différente : +Il n'est pas vraiment facile de s'orienter dans un tel affichage. Si nous activons Tracy, l'erreur ou l'exception s'affiche sous une forme complètement différente : [* tracy-exception.webp .{url:-} *] -Le message d'erreur est littéralement criant. Vous pouvez voir une partie du code source avec la ligne surlignée où l'erreur s'est produite. Un message explique clairement une erreur. L'ensemble du site est [interactif, essayez-le](https://nette.github.io/tracy/tracy-exception.html). +Le message d'erreur crie littéralement. Nous voyons une partie du code source avec la ligne surlignée où l'erreur s'est produite et l'information *Call to undefined method Nette\Http\User::isLogedIn()* explique clairement de quelle erreur il s'agit. De plus, toute la page est interactive, nous pouvons cliquer pour obtenir plus de détails. [Essayez |https://nette.github.io/tracy/tracy-exception.html]. -Et vous savez quoi ? Les erreurs fatales sont capturées et affichées de la même manière. Il n'est pas nécessaire d'installer une quelconque extension (cliquez pour voir un exemple en direct) : +Et vous savez quoi ? De cette manière, elle intercepte et affiche même les erreurs fatales. Sans avoir besoin d'installer la moindre extension. [* tracy-error.webp .{url:-} *] -Les erreurs telles qu'une faute de frappe dans un nom de variable ou une tentative d'ouverture d'un fichier inexistant génèrent des rapports de niveau E_NOTICE ou E_WARNING. Ces erreurs peuvent être facilement négligées et/ou peuvent être complètement cachées dans la mise en page graphique d'une page Web. Laissez Tracy les gérer : +Les erreurs comme une faute de frappe dans le nom d'une variable ou une tentative d'ouvrir un fichier inexistant génèrent des messages de niveau E_NOTICE ou E_WARNING. Celles-ci peuvent être facilement négligées dans l'interface graphique de la page, voire ne pas être visibles du tout (sauf en regardant le code source de la page). [* tracy-notice2.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html -Ou ils peuvent être affichés comme des erreurs : +Ou elles peuvent être affichées de la même manière que les erreurs : ```php -Debugger::$strictMode = true; // display all errors -Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // all errors except deprecated notices +Debugger::$strictMode = true; // affiche toutes les erreurs +Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // toutes les erreurs sauf les notifications de dépréciation ``` [* tracy-notice.webp .{url:-} *] -Note : Tracy, lorsqu'il est activé, fait passer le niveau de signalement des erreurs à E_ALL. Si vous souhaitez modifier ce niveau, faites-le après avoir appelé `enable()`. +Note : Tracy, après activation, change le niveau de rapport d'erreurs à E_ALL. Si vous souhaitez modifier cette valeur, faites-le après l'appel de `enable()`. -Mode développement ou mode production .[#toc-development-vs-production-mode] -============================================================================ +Mode développeur vs mode production +=================================== -Comme vous pouvez le constater, Tracy est assez bavard, ce qui peut être apprécié dans l'environnement de développement, alors que sur le serveur de production, cela provoquerait un désastre. En effet, aucune information de débogage ne doit y être affichée. Tracy dispose donc d'une **détection automatique de l'environnement** et si l'exemple est exécuté sur un serveur en production, l'erreur sera enregistrée au lieu d'être affichée, et le visiteur ne verra qu'un message convivial : +Comme vous pouvez le voir, Tracy est assez bavarde, ce qui peut être apprécié dans un environnement de développement, alors que sur un serveur de production, cela provoquerait une véritable catastrophe. En effet, aucune information de débogage ne doit y être affichée. Tracy dispose donc d'une **autodétection de l'environnement** et si l'exemple est exécuté sur un serveur de production, l'erreur sera journalisée au lieu d'être affichée et le visiteur ne verra qu'un message compréhensible par l'utilisateur : [* tracy-error2.webp .{url:-} *] -Le mode production supprime l'affichage de toutes les informations de débogage envoyées à l'aide de [dump() |dumper], et bien sûr de tous les messages d'erreur générés par PHP. Ainsi, si vous avez oublié quelques `dump($obj)` dans le code, vous n'avez pas à vous inquiéter, rien ne sera affiché sur le serveur de production. +Le mode production supprime l'affichage de toutes les informations de débogage que nous envoyons via [dump() |dumper], et bien sûr aussi de tous les messages d'erreur générés par PHP. Donc, si vous avez oublié quelques `dump($obj)` dans le code, ne vous inquiétez pas, rien ne sera affiché sur le serveur de production. -Comment fonctionne l'autodétection du mode ? Le mode est développement si l'application tourne sur localhost (c'est-à-dire l'adresse IP `127.0.0.1` ou `::1`) et qu'il n'y a pas de proxy (c'est-à-dire son en-tête HTTP). Sinon, elle s'exécute en mode production. +Comment fonctionne l'autodétection du mode ? Le mode est développeur si l'application est exécutée sur localhost (c'est-à-dire l'adresse IP `127.0.0.1` ou `::1`) et qu'il n'y a pas de proxy présent (c'est-à-dire son en-tête HTTP). Sinon, elle s'exécute en mode production. -Si vous souhaitez activer le mode développement dans d'autres cas, par exemple pour les développeurs accédant à partir d'une adresse IP spécifique, vous pouvez le spécifier en tant que paramètre de la méthode `enable()`: +Si nous voulons activer le mode développeur dans d'autres cas, par exemple pour les programmeurs accédant depuis une adresse IP spécifique, nous l'indiquons comme paramètre de la méthode `enable()` : ```php -Debugger::enable('23.75.345.200'); // vous pouvez également fournir un tableau d'adresses IP +Debugger::enable('23.75.345.200'); // il est possible d'indiquer aussi un tableau d'adresses IP ``` -Nous recommandons vivement de combiner l'adresse IP avec un cookie. Stockez un jeton secret, par exemple `secret1234`, dans le cookie `tracy-debug`, et activez ainsi le mode développement uniquement pour les développeurs accédant à partir d'une adresse IP spécifique qui ont le jeton mentionné dans le cookie : +Nous recommandons vivement de combiner l'adresse IP avec un cookie. Dans le cookie `tracy-debug`, nous stockons un jeton secret, par ex. `secret1234`, et de cette manière, nous activons le mode développeur uniquement pour les programmeurs accédant depuis une adresse IP spécifique et qui ont le jeton mentionné dans le cookie : ```php Debugger::enable('secret1234@23.75.345.200'); ``` -Vous pouvez également définir directement le mode développement/production en utilisant les constantes `Debugger::Development` ou `Debugger::Production` comme paramètre de la méthode `enable()`. +Nous pouvons également définir directement le mode développeur/production en utilisant la constante `Debugger::Development` ou `Debugger::Production` comme paramètre de la méthode `enable()`. .[note] -Si vous utilisez le Nette Framework, jetez un coup d'œil à la manière de [définir le mode pour celui-ci |application:bootstrap#Development vs Production Mode], et il sera alors également utilisé pour Tracy. +Si vous utilisez Nette Framework, regardez comment [définir le mode pour celui-ci |application:bootstrapping#Mode Développement vs Production] et celui-ci sera ensuite utilisé également pour Tracy. -Enregistrement des erreurs .[#toc-error-logging] -================================================ +Logging des erreurs +=================== -En mode production, Tracy enregistre automatiquement toutes les erreurs et exceptions dans un journal texte. Pour que la journalisation ait lieu, vous devez définir le chemin absolu vers le répertoire de journalisation dans la variable `$logDirectory` ou le transmettre en tant que deuxième paramètre de la méthode `enable()`: +En mode production, Tracy enregistre automatiquement toutes les erreurs et exceptions interceptées dans un journal texte. Pour que le logging puisse avoir lieu, nous devons définir le chemin absolu vers le répertoire de logs dans la variable `$logDirectory` ou le passer comme second paramètre de la méthode `enable()` : ```php Debugger::$logDirectory = __DIR__ . '/log'; ``` -La journalisation des erreurs est extrêmement utile. Imaginez que tous les utilisateurs de votre application sont en fait des bêta-testeurs qui font un excellent travail en trouvant des erreurs gratuitement, et vous seriez stupide de jeter leurs précieux rapports à la poubelle sans vous en rendre compte. +Le logging des erreurs est extrêmement utile. Imaginez que tous les utilisateurs de votre application sont en fait des bêta-testeurs qui effectuent gratuitement un travail de pointe pour trouver des erreurs, et vous feriez une bêtise si vous jetiez leurs précieux rapports sans y prêter attention à la poubelle. -Si vous avez besoin de consigner vos propres messages ou les exceptions capturées, utilisez la méthode `log()`: +Si nous avons besoin de journaliser notre propre message ou une exception que nous avons interceptée, nous utilisons pour cela la méthode `log()` : ```php -Debugger::log('Unexpected error'); // text message +Debugger::log('Une erreur inattendue s\'est produite'); // message texte try { - criticalOperation(); + operationCritique(); } catch (Exception $e) { - Debugger::log($e); // log exception - // or - Debugger::log($e, Debugger::ERROR); // also sends an email notification + Debugger::log($e); // il est possible de journaliser aussi une exception + // ou + Debugger::log($e, Debugger::ERROR); // envoie également une notification par e-mail } ``` -If you want Tracy to log PHP errors like `E_NOTICE` or `E_WARNING` with detailed information (HTML report), set `Debugger::$logSeverity`: +Si vous voulez que Tracy journalise les erreurs PHP comme `E_NOTICE` ou `E_WARNING` avec des informations détaillées (rapport HTML), définissez `Debugger::$logSeverity` : ```php Debugger::$logSeverity = E_NOTICE | E_WARNING; ``` -Pour un vrai professionnel, le journal des erreurs est une source d'information cruciale et il veut être informé immédiatement de toute nouvelle erreur. Tracy l'aide. Elle est capable d'envoyer un e-mail pour chaque nouvel enregistrement d'erreur. La variable $email identifie l'endroit où envoyer ces e-mails : +Pour un vrai professionnel, le journal des erreurs est une source d'informations clé et il veut être informé immédiatement de chaque nouvelle erreur. Tracy lui vient en aide, car elle peut informer d'une nouvelle entrée dans le journal par e-mail. Où envoyer les e-mails est déterminé par la variable $email : ```php Debugger::$email = 'admin@example.com'; ``` -Si vous utilisez l'ensemble du Nette Framework, vous pouvez définir cette variable et d'autres dans le [fichier de configuration |nette:configuring]. +Si vous utilisez l'ensemble du Nette Framework, ceci et d'autres paramètres peuvent être définis dans le [fichier de configuration |nette:configuring]. -Pour protéger votre boîte aux lettres électronique d'une inondation, Tracy envoie **un seul message** et crée un fichier `email-sent`. Lorsqu'un développeur reçoit la notification par e-mail, il vérifie le journal, corrige son application et supprime le fichier de surveillance `email-sent`. Cela active à nouveau l'envoi d'e-mails. +Cependant, pour ne pas inonder votre boîte e-mail, elle n'envoie toujours **qu'un seul message** et crée un fichier `email-sent`. Le développeur, après avoir reçu la notification par e-mail, vérifie le journal, corrige l'application et supprime le fichier de surveillance, ce qui réactive l'envoi d'e-mails. -Ouverture de fichiers dans l'éditeur .[#toc-opening-files-in-the-editor] -======================================================================== +Ouverture dans l'éditeur +======================== -Lorsque la page d'erreur est affichée, vous pouvez cliquer sur les noms de fichiers et ils s'ouvriront dans votre éditeur avec le curseur sur la ligne correspondante. Il est également possible de créer des fichiers (action `create file`) ou d'y corriger des erreurs (action `fix it`). Pour ce faire, vous devez [configurer le navigateur et le système |open-files-in-ide]. +Lors de l'affichage de la page d'erreur, il est possible de cliquer sur les noms de fichiers et ceux-ci s'ouvriront dans votre éditeur avec le curseur sur la ligne correspondante. Il est également possible de créer des fichiers (action `create file`) ou d'y corriger des erreurs (action `fix it`). Pour que cela fonctionne, il suffit de [configurer le navigateur et le système |open-files-in-ide]. -Versions de PHP prises en charge .[#toc-supported-php-versions] -=============================================================== +Versions PHP supportées +======================= -| Tracy | compatible avec PHP -|-----------|-------------------- -| Tracy 2.10 – 3.0 | PHP 8.0 - 8.2 -| Tracy 2.9 | PHP 7.2 - 8.2 -| Tracy 2.8 | PHP 7.2 - 8.1 -| Tracy 2.6 - 2.7 | PHP 7.1 - 8.0 -| Tracy 2.5 | PHP 5.4 - 7.4 -| Tracy 2.4 | PHP 5.4 - 7.2 +| Tracy | compatible avec PHP +|-----------|------------------- +| Tracy 2.10 – 3.0 | PHP 8.0 – 8.4 +| Tracy 2.9 | PHP 7.2 – 8.2 +| Tracy 2.8 | PHP 7.2 – 8.1 +| Tracy 2.6 – 2.7 | PHP 7.1 – 8.0 +| Tracy 2.5 | PHP 5.4 – 7.4 +| Tracy 2.4 | PHP 5.4 – 7.2 -S'applique aux dernières versions des correctifs. +Valable pour la dernière version patch. -Ports .[#toc-ports] -=================== +Ports +===== -C'est une liste de ports non officiels vers d'autres frameworks et CMS : +Ceci est une liste de ports non officiels pour d'autres frameworks et CMS : - [Drupal 7](https://www.drupal.org/project/traced) -- Laravel framework: [recca0120/laravel-tracy](https://github.com/recca0120/laravel-tracy), [whipsterCZ/laravel-tracy](https://github.com/whipsterCZ/laravel-tracy) +- Framework Laravel : [recca0120/laravel-tracy](https://github.com/recca0120/laravel-tracy), [whipsterCZ/laravel-tracy](https://github.com/whipsterCZ/laravel-tracy) - [OpenCart](https://github.com/BurdaPraha/oc_tracy) - [ProcessWire CMS/CMF](https://github.com/adrianbj/TracyDebugger) - [Slim Framework](https://github.com/runcmf/runtracy) -- Symfony framework: [kutny/tracy-bundle](https://github.com/kutny/tracy-bundle), [VasekPurchart/Tracy-Blue-Screen-Bundle](https://github.com/VasekPurchart/Tracy-Blue-Screen-Bundle) +- Framework Symfony : [kutny/tracy-bundle](https://github.com/kutny/tracy-bundle), [VasekPurchart/Tracy-Blue-Screen-Bundle](https://github.com/VasekPurchart/Tracy-Blue-Screen-Bundle) - [Wordpress](https://github.com/ktstudio/WP-Tracy) diff --git a/tracy/fr/open-files-in-ide.texy b/tracy/fr/open-files-in-ide.texy index 4adeae2223..1a58a6d23c 100644 --- a/tracy/fr/open-files-in-ide.texy +++ b/tracy/fr/open-files-in-ide.texy @@ -1,20 +1,20 @@ -Comment ouvrir un fichier dans l'éditeur à partir de Tracy ? (Intégration IDE) -****************************************************************************** +Comment ouvrir un fichier dans l'éditeur depuis Tracy ? (Intégration avec l'IDE) +******************************************************************************** .[perex] -Lorsque la page d'erreur est affichée, vous pouvez cliquer sur les noms de fichiers et ils s'ouvriront dans votre éditeur avec le curseur sur la ligne correspondante. Il est également possible de créer des fichiers (action `create file`) ou d'y corriger des erreurs (action `fix it`). Pour ce faire, vous devez configurer le navigateur et le système. +Lors de l'affichage de la page d'erreur, il est possible de cliquer sur les noms de fichiers et ceux-ci s'ouvriront dans votre éditeur avec le curseur sur la ligne correspondante. Il est également possible de créer des fichiers (action `create file`) ou d'y corriger des erreurs (action `fix it`). Pour que cela se produise, il est nécessaire de configurer le navigateur et le système. -Tracy ouvre les fichiers via des URL de la forme `editor://open/?file=%file&line=%line`, c'est-à-dire avec le protocole `editor://`. Nous allons enregistrer notre propre gestionnaire pour celui-ci. Il peut s'agir de n'importe quel fichier exécutable qui traite les paramètres et lance notre éditeur préféré. +Tracy ouvre les fichiers via une URL de la forme `editor://open/?file=%file&line=%line`, c'est-à-dire avec le protocole `editor://`. Nous enregistrerons notre propre gestionnaire pour celui-ci. Celui-ci peut être n'importe quel fichier exécutable qui "mâchera" les paramètres et lancera notre éditeur préféré. Vous pouvez modifier l'URL dans la variable `Tracy\Debugger::$editor`, ou désactiver le clic en définissant `Tracy\Debugger::$editor = null`. -Windows .[#toc-windows] -======================= +Windows +======= -1. Téléchargez les fichiers appropriés "du dépôt Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/windows sur le disque. +1. Téléchargez les fichiers appropriés depuis le "depuis le dépôt Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/windows sur votre disque. -2. Editez `open-editor.js` et décommentez ou modifiez le chemin d'accès à votre éditeur dans `settings`: +2. Modifiez le fichier `open-editor.js` et dans le tableau `settings`, décommentez et éventuellement modifiez le chemin vers votre éditeur : ```js var settings = { @@ -35,19 +35,28 @@ var settings = { ... ``` -Faites attention et gardez les doubles slashs dans les chemins. +Attention, conservez les doubles barres obliques inversées dans les chemins. + +3. Enregistrez le gestionnaire du protocole `editor://` dans le système. + +Vous le faites en exécutant le fichier `install.cmd`. **Il faut l'exécuter en tant qu'Administrateur.** Le script `open-editor.js` gérera désormais le protocole `editor://`. -3. Enregistrez le gestionnaire pour le protocole `editor://` dans le système. +Pour pouvoir ouvrir les liens générés sur d'autres serveurs, comme sur le serveur de production ou dans Docker, ajoutez également le mappage de l'URL distante vers le local dans `open-editor.js` : -Ceci est fait en exécutant `install.cmd`. **Vous devez l'exécuter en tant qu'administrateur. Le script `open-editor.js` servira désormais le protocole `editor://`. +```js + mappings: { + // chemin distant : chemin local + '/var/www/nette.app': 'W:\\Nette.web\\_web', + } +``` -Linux .[#toc-linux] -=================== +Linux +===== -1. Téléchargez les fichiers appropriés "à partir du référentiel Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux dans le répertoire `~/bin`. +1. Téléchargez les fichiers appropriés depuis le "depuis le dépôt Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux dans le répertoire `~/bin`. -2. Editez `open-editor.sh` et décommentez ou modifiez le chemin d'accès à votre éditeur dans la variable `editor`: +2. Modifiez le fichier `open-editor.sh` et décommentez et éventuellement modifiez le chemin vers votre éditeur dans la variable `editor`. ```shell #!/bin/bash @@ -67,24 +76,25 @@ Linux .[#toc-linux] ... ``` -Rendez-le exécutable : +Rendez le fichier exécutable : ```shell chmod +x ~/bin/open-editor.sh ``` -Si l'éditeur que vous utilisez n'est pas installé à partir du paquet, le binaire n'aura probablement pas de chemin dans `$PATH`. Ceci peut être facilement corrigé. Dans le répertoire `~/bin`, créez un lien symbolique sur le binaire de l'éditeur. .[note] +.[note] +Si l'éditeur utilisé n'est pas installé à partir d'un paquet, le binaire n'aura probablement pas de chemin dans $PATH. Cela peut être facilement corrigé. Dans le répertoire `~/bin`, créez un lien symbolique vers le binaire de l'éditeur. -3. Enregistrez le gestionnaire pour le protocole `editor://` dans le système. +3. Enregistrez le gestionnaire du protocole `editor://` dans le système. -Pour ce faire, exécutez `install.sh`. Le script `open-editor.js` servira désormais le protocole `editor://`. +Vous le faites en exécutant le fichier `install.sh`. Le script `open-editor.sh` gérera désormais le protocole `editor://`. -macOS .[#toc-macos] -=================== +macOS +===== -Les éditeurs comme PhpStorm, TextMate, etc. vous permettent d'ouvrir des fichiers via une URL spéciale, qu'il vous suffit de définir : +Les éditeurs comme PhpStorm, TextMate, etc. permettent d'ouvrir des fichiers via une URL spéciale qu'il suffit de définir : ```php // PhpStorm @@ -92,37 +102,36 @@ Tracy\Debugger::$editor = 'phpstorm://open?file=%file&line=%line'; // TextMate Tracy\Debugger::$editor = 'txmt://open/?url=file://%file&line=%line'; // MacVim -Tracy\Debugger::$editor = 'mvim://open/?url=file://%file&line=%line'; +Tracy\Debugger::$editor = 'mvim://open?url=file:///%file&line=%line'; // Visual Studio Code Tracy\Debugger::$editor = 'vscode://file/%file:%line'; ``` -Si vous utilisez Tracy en mode autonome, mettez la ligne avant `Tracy\Debugger::enable()`, si vous utilisez Nette, avant le `$configurator->enableTracy()` dans `Bootstrap.php`. +Si vous utilisez Tracy de manière autonome, insérez la ligne avant `Tracy\Debugger::enable()`, si vous utilisez Nette, alors avant `$configurator->enableTracy()` dans `Bootstrap.php`. -Malheureusement, les actions `create file` ou `fix it` ne fonctionnent pas sur macOS. +Les actions `create file` ou `fix it` ne fonctionnent malheureusement pas sur macOS. -Démonstrations .[#toc-demos] -============================ +Démonstrations +============== -Correction d'un bogue : +Correction d'une erreur : <iframe width="560" height="315" src="https://www.youtube.com/embed/3ITT4mC0Eq4?rel=0&showinfo=0" frameborder="0" allow="encrypted-media" allowfullscreen></iframe> -Création d'un nouveau fichier : +Création d'un fichier : <iframe width="560" height="315" src="https://www.youtube.com/embed/AJ_FUivAGZQ?rel=0&showinfo=0" frameborder="0" allow="encrypted-media" allowfullscreen></iframe> -Dépannage .[#toc-troubleshooting] -================================= +Résolution de problèmes +======================= -- Dans Firefox, il se peut que vous deviez [autoriser l' |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] exécution de protocoles personnalisés dans about:config en définissant `network.protocol-handler.expose.editor` comme `false` et `network.protocol-handler.expose-all` comme `true`. Cela devrait toutefois être autorisé par défaut. -- Si tout ne fonctionne pas immédiatement, ne paniquez pas. Essayez d'actualiser la page, de redémarrer le navigateur ou l'ordinateur. Cela devrait vous aider. -- Voir [ici |https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/] pour corriger : - Erreur d'entrée : There is no script engine for file extension ".js" Peut-être avez-vous associé le fichier ".js" à une autre application, et non au moteur JScript. +- Dans Firefox, il peut être nécessaire d'autoriser le protocole en [définissant |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] `network.protocol-handler.expose.editor` sur `false` et `network.protocol-handler.expose-all` sur `true` dans about:config. +- Si cela ne fonctionne pas tout de suite, ne paniquez pas et essayez de rafraîchir la page plusieurs fois avant de cliquer sur ce lien. Ça va démarrer ! +- Voici un [lien|https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/] pour corriger une éventuelle erreur : `Input Error: There is no script engine for file extension ".js"`, `Maybe you associated ".js" file to another app, not JScript engine.` respectivement `aucun moteur de script n'est disponible pour l'extension .js`. -À partir de la version 77 de Google Chrome, vous ne verrez plus la case à cocher "Toujours ouvrir ces types de liens dans l'application associée" lorsque l'éditeur est ouvert par un lien. Solution pour Windows : créer un fichier `fix.reg`: +Dans Google Chrome depuis la version 77, vous ne verrez plus la case à cocher „Toujours ouvrir ce type de liens dans l'application associée“, quand l'éditeur est lancé via un lien. Solution pour Windows : créez un fichier `fix.reg` : ``` Windows Registry Editor Version 5.00 @@ -132,4 +141,4 @@ Windows Registry Editor Version 5.00 Importez-le en double-cliquant et redémarrez Chrome. -En cas d'autres problèmes ou questions, posez-les sur le [forum |https://forum.nette.org]. +Pour toute question ou suggestion, veuillez vous adresser au [forum |https://forum.nette.org]. diff --git a/tracy/fr/recipes.texy b/tracy/fr/recipes.texy index 8bef651219..dbe20e7f9b 100644 --- a/tracy/fr/recipes.texy +++ b/tracy/fr/recipes.texy @@ -1,14 +1,13 @@ -Recettes -******** +Tutoriels +********* -Politique de sécurité du contenu .[#toc-content-security-policy] -================================================================ +Content Security Policy +======================= -Si votre site utilise la politique de sécurité du contenu, vous devrez ajouter `'nonce-<value>'` et `'strict-dynamic'` à `script-src` pour que Tracy fonctionne correctement. Certains plugins tiers peuvent nécessiter des directives supplémentaires. -Le nonce n'est pas pris en charge par la directive `style-src`. Si vous utilisez cette directive, vous devez ajouter `'unsafe-inline'`, mais cela doit être évité en mode production. +Si votre site web utilise Content Security Policy, vous devrez ajouter les mêmes `'nonce-<value>'` et `'strict-dynamic'` à `script-src` pour que Tracy fonctionne correctement. Certains add-ons tiers peuvent nécessiter des configurations supplémentaires. Nonce n'est pas supporté dans la directive `style-src`, si vous utilisez cette directive, vous devez ajouter `'unsafe-inline'`, mais vous devriez éviter cela en mode production. -Exemple de configuration pour [Nette Framework |nette:configuring]: +Exemple de configuration pour [Nette Framework |nette:configuring] : ```neon http: @@ -16,7 +15,7 @@ http: script-src: [nonce, strict-dynamic] ``` -Exemple en pur PHP : +Exemple en PHP pur : ```php $nonce = base64_encode(random_bytes(20)); @@ -24,11 +23,10 @@ header("Content-Security-Policy: script-src 'nonce-$nonce' 'strict-dynamic';"); ``` -Chargement plus rapide .[#toc-faster-loading] -============================================= +Chargement plus rapide +====================== -L'intégration de base est simple, mais si vous avez des scripts qui bloquent lentement dans la page Web, ils peuvent ralentir le chargement de Tracy. -La solution consiste à placer `<?php Tracy\Debugger::renderLoader() ?>` dans votre modèle avant tout script : +Le démarrage est simple, cependant, si vous avez des scripts bloquants qui se chargent lentement sur votre page web, ils peuvent ralentir le chargement de Tracy. La solution est de placer `<?php Tracy\Debugger::renderLoader() ?>` dans votre template avant tous les scripts : ```latte <!DOCTYPE html> @@ -42,12 +40,37 @@ La solution consiste à placer `<?php Tracy\Debugger::renderLoader() ?>` dans vo ``` -AJAX et requêtes redirigées .[#toc-ajax-and-redirected-requests] -================================================================ +Débogage des requêtes AJAX +========================== -Tracy peut afficher la barre de débogage et les écrans bleus pour les demandes AJAX et les redirections. Tracy crée ses propres sessions, stocke les données dans ses propres fichiers temporaires et utilise un cookie `tracy-session`. +Tracy intercepte automatiquement les requêtes AJAX créées à l'aide de jQuery ou de l'API native `fetch`. Les requêtes sont affichées dans la barre Tracy comme des lignes supplémentaires, ce qui permet un débogage AJAX facile et pratique. -Tracy peut également être configuré pour utiliser une session PHP native, qui est lancée avant que Tracy ne soit activé : +Si vous ne souhaitez pas intercepter automatiquement les requêtes AJAX, vous pouvez désactiver cette fonction en définissant la variable JavaScript : + +```js +window.TracyAutoRefresh = false; +``` + +Pour une surveillance manuelle des requêtes AJAX spécifiques, ajoutez l'en-tête HTTP `X-Tracy-Ajax` avec la valeur retournée par `Tracy.getAjaxHeader()`. Voici un exemple d'utilisation avec la fonction `fetch` : + +```js +fetch(url, { + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Tracy-Ajax': Tracy.getAjaxHeader(), + } +}) +``` + +Cette approche permet un débogage sélectif des requêtes AJAX. + + +Stockage de données +=================== + +Tracy peut afficher des panneaux dans la barre Tracy et des Bluescreens pour les requêtes AJAX et les redirections. Tracy crée sa propre session, stocke les données dans ses propres fichiers temporaires et utilise le cookie `tracy-session`. + +Tracy peut également être configuré pour utiliser la session PHP native, que nous démarrons avant d'activer Tracy : ```php session_start(); @@ -55,14 +78,14 @@ Debugger::setSessionStorage(new Tracy\NativeSession); Debugger::enable(); ``` -Dans le cas où le démarrage d'une session nécessite une initialisation plus complexe, vous pouvez démarrer Tracy immédiatement (afin qu'il puisse gérer les éventuelles erreurs qui se produisent), puis initialiser le gestionnaire de session et enfin informer Tracy que la session est prête à être utilisée en utilisant la fonction `dispatch()`: +Dans le cas où le démarrage de la session nécessite une initialisation plus complexe, vous pouvez démarrer Tracy immédiatement (afin qu'elle puisse traiter les erreurs éventuelles) puis initialiser le gestionnaire de session et enfin informer Tracy que la session est prête à être utilisée à l'aide de la fonction `dispatch()` : ```php Debugger::setSessionStorage(new Tracy\NativeSession); Debugger::enable(); -// suivi de l'initialisation de la session -// et démarrer la session +// suit l'initialisation de la session +// et le démarrage de la session session_start(); Debugger::dispatch(); @@ -71,28 +94,28 @@ Debugger::dispatch(); La fonction `setSessionStorage()` existe depuis la version 2.9, avant cela Tracy utilisait toujours la session PHP native. -Épurateur personnalisé .[#toc-custom-scrubber] -============================================== +Scrubber personnalisé +===================== -Scrubber est un filtre qui empêche les données sensibles de s'échapper des vidages, comme les mots de passe ou les informations d'identification. Le filtre est appelé pour chaque élément du tableau ou de l'objet vidés et renvoie `true` si la valeur est sensible. Dans ce cas, `*****` est imprimé à la place de la valeur. +Le Scrubber est un filtre qui empêche la fuite de données sensibles lors du dump, comme les mots de passe ou les identifiants. Le filtre est appelé pour chaque élément du tableau ou de l'objet dumpé et retourne `true` si la valeur est sensible. Dans ce cas, `*****` est affiché à la place de la valeur. ```php -// évite de vider les valeurs des clés et les propriétés telles que `password`, +// empêche l'affichage des valeurs des clés et propriétés comme `password`, // `password_repeat`, `check_password`, `DATABASE_PASSWORD`, etc. $scrubber = function(string $key, $value, ?string $class): bool { return preg_match('#password#i', $key) && $value !== null; }; -// Nous l'utilisons pour tous les dumps dans BlueScreen. +// nous l'utilisons pour tous les dumps à l'intérieur de BlueScreen Tracy\Debugger::getBlueScreen()->scrubber = $scrubber; ``` -Logger personnalisé .[#toc-custom-logger] -========================================= +Logger personnalisé +=================== -Nous pouvons créer un enregistreur personnalisé pour consigner les erreurs, les exceptions non corrigées et être également appelé par `Tracy\Debugger::log()`. Logger implémente l'interface [api:Tracy\ILogger]. +Nous pouvons créer notre propre logger qui journalisera les erreurs, les exceptions non interceptées et sera également appelé par la méthode `Tracy\Debugger::log()`. Le logger implémente l'interface [api:Tracy\ILogger]. ```php use Tracy\ILogger; @@ -101,7 +124,7 @@ class SlackLogger implements ILogger { public function log($value, $priority = ILogger::INFO) { - // envoie une demande à Slack + // envoie une requête à Slack } } ``` @@ -112,7 +135,7 @@ Et ensuite nous l'activons : Tracy\Debugger::setLogger(new SlackLogger); ``` -Si nous utilisons le Nette Framework complet, nous pouvons le définir dans le fichier de configuration de NEON : +Si nous utilisons le framework Nette complet, vous pouvez le configurer dans le fichier de configuration NEON : ```neon services: @@ -120,10 +143,10 @@ services: ``` -Intégration Monolog .[#toc-monolog-integration] ------------------------------------------------ +Intégration de Monolog +---------------------- -Le paquet Tracy fournit un adaptateur PSR-3, permettant l'intégration de [monolog/monolog](https://github.com/Seldaek/monolog). +Le paquet Tracy fournit un adaptateur PSR-3 qui permet l'intégration de [monolog/monolog](https://github.com/Seldaek/monolog). ```php $monolog = new Monolog\Logger('main-channel'); @@ -133,21 +156,21 @@ $tracyLogger = new Tracy\Bridges\Psr\PsrToTracyLoggerAdapter($monolog); Debugger::setLogger($tracyLogger); Debugger::enable(); -Debugger::log('info'); // écrit: [<TIMESTAMP>] canal principal.INFO: info [] [] -Debugger::log('warning', Debugger::WARNING); // écrit: [<TIMESTAMP>] canal principal.WARNING: avertissement [] [] +Debugger::log('info'); // écrit : [<TIMESTAMP>] main-channel.INFO: info [] [] +Debugger::log('warning', Debugger::WARNING); // écrit : [<TIMESTAMP>] main-channel.WARNING: warning [] [] ``` -nginx .[#toc-nginx] -=================== +nginx +===== -Si Tracy ne fonctionne pas sur nginx, il est probablement mal configuré. S'il y a quelque chose comme +Si Tracy ne fonctionne pas sur votre serveur nginx, il est probablement mal configuré. S'il y a quelque chose comme ceci dans la configuration : ```nginx try_files $uri $uri/ /index.php; ``` -changez-le en +modifiez-le en : ```nginx try_files $uri $uri/ /index.php$is_args$args; diff --git a/tracy/fr/stopwatch.texy b/tracy/fr/stopwatch.texy index ae634ba315..00da62af9d 100644 --- a/tracy/fr/stopwatch.texy +++ b/tracy/fr/stopwatch.texy @@ -1,35 +1,35 @@ -Chronomètre -*********** +Mesure du temps +*************** -Un autre outil utile est le chronomètre du débogueur avec une précision de l'ordre de la microseconde : +Un autre outil utile du débogueur est le chronomètre avec une précision à la microseconde : ```php Debugger::timer(); -// sweet dreams my cherrie +// mon petit prince dors, les oiseaux chantent déjà doucement... sleep(2); $elapsed = Debugger::timer(); // $elapsed = 2 ``` -Un paramètre optionnel permet d'effectuer plusieurs mesures à la fois. +Avec un paramètre optionnel, il est possible d'obtenir des mesures multiples. ```php Debugger::timer('page-generating'); -// some code +// un peu de code Debugger::timer('rss-generating'); -// some code +// un peu de code $rssElapsed = Debugger::timer('rss-generating'); $pageElapsed = Debugger::timer('page-generating'); ``` ```php -Debugger::timer(); // runs the timer +Debugger::timer(); // démarre le chronomètre -... // some time-consuming operation +... // opération coûteuse en temps -echo Debugger::timer(); // elapsed time in seconds +echo Debugger::timer(); // affiche le temps écoulé en secondes ``` diff --git a/tracy/hu/@home.texy b/tracy/hu/@home.texy index 1800fb7297..41d41deb51 100644 --- a/tracy/hu/@home.texy +++ b/tracy/hu/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Tracy - Egy kötelező hibakereső eszköz minden PHP fejlesztő számára}} -{{description: A Tracy egy olyan eszköz, amelyet a PHP kód hibakeresésének megkönnyítésére terveztek. Hasznos segítője minden PHP programozónak, amely segít a hibák egyértelmű megjelenítésében és naplózásában, a változók dömpingjében és még sok másban. Figyelmeztetés: A Tracy függőséget okoz!}} +{{maintitle: Tracy – hibakereső eszköz, amellyel öröm hibázni}} +{description: A Tracy egy eszköz, amely a PHP kód hibakeresésének megkönnyítésére szolgál. Hasznos segítőtárs minden PHP programozó számára, segít a hibák vizualizálásában és naplózásában, a változók dumpolásában és még sok másban. Figyelmeztetés: A Tracy függőséget okoz!}} diff --git a/tracy/hu/@left-menu.texy b/tracy/hu/@left-menu.texy index 89fc0dcd47..5e49c260d9 100644 --- a/tracy/hu/@left-menu.texy +++ b/tracy/hu/@left-menu.texy @@ -1,7 +1,7 @@ -- [Kezdő lépések |Guide] -- [Dömper |Dumper] -- [Stopperóra |Stopwatch] -- [Konfigurálás |Configuring] -- [Receptek |Recipes] -- [IDE integráció |open-files-in-ide] -- [Bővítmények létrehozása |extensions] +- [Első lépések a Tracy-vel |guide] +- [Dumpolás |dumper] +- [Időmérés |stopwatch] +- [Konfiguráció |configuring] +- [Receptek |recipes] +- [Integráció IDE-vel |open-files-in-ide] +- [Bővítmények készítése |extensions] diff --git a/tracy/hu/@menu.texy b/tracy/hu/@menu.texy index fca48f0d51..4dde520bc3 100644 --- a/tracy/hu/@menu.texy +++ b/tracy/hu/@menu.texy @@ -1,3 +1,3 @@ -- [Otthon |@home] -- [Dokumentáció |Guide] +- [Bevezetés |@home] +- [Dokumentáció |guide] - "GitHub .[link-external]":https://github.com/nette/tracy diff --git a/tracy/hu/@meta.texy b/tracy/hu/@meta.texy new file mode 100644 index 0000000000..59d1cb7017 --- /dev/null +++ b/tracy/hu/@meta.texy @@ -0,0 +1 @@ +{{sitename: Tracy dokumentáció}} diff --git a/tracy/hu/configuring.texy b/tracy/hu/configuring.texy index 92127ceec1..81787c3023 100644 --- a/tracy/hu/configuring.texy +++ b/tracy/hu/configuring.texy @@ -1,75 +1,75 @@ -Tracy konfiguráció -****************** +Tracy konfigurációja +******************** -A következő példák feltételezik, hogy a következő osztály alias van definiálva: +Minden példa feltételezi a következő alias létrehozását: ```php use Tracy\Debugger; ``` -Hibanaplózás .[#toc-error-logging] ----------------------------------- +Hibanaplózás +------------ ```php $logger = Debugger::getLogger(); -// ha hiba történt, az értesítést erre az e-mail címre küldjük. -$logger->email = 'dev@example.com'; // (string|string[]) alapértelmezés szerint nem beállított. +// e-mail cím(ek), amely(ek)re értesítést küldünk hiba esetén +$logger->email = 'dev@example.com'; // (string|string[]) alapértelmezett: nincs beállítva -// email feladója -$logger->fromEmail = 'me@example.com'; // (string) alapértelmezés szerint nem állítható be. +// az e-mail feladója +$logger->fromEmail = 'me@example.com'; // (string) alapértelmezett: nincs beállítva -// rutin az e-mail küldéséhez -$logger->mailer = /* ... */; // (hívható) alapértelmezés szerint a mail() segítségével küldi el. +// az e-mail küldését biztosító rutin +$logger->mailer = /* ... */; // (callable) alapértelmezett: küldés a mail() függvénnyel -// mi után a legrövidebb idő után küldjünk újabb emailt? -$logger->emailSnooze = /* ... */; // (string) alapértelmezés szerint '2 nap'. +// mennyi a legrövidebb idő a következő e-mail küldése előtt? +$logger->emailSnooze = /* ... */; // (string) alapértelmezett: '2 days' -// milyen hibaszintek esetén is naplóz a BlueScreen? -Debugger::$logSeverity = E_WARNING | E_NOTICE; // alapértelmezés szerint 0 (nincs hibaszint). +// mely hibaszintek esetén naplózódik a BlueScreen is? +Debugger::$logSeverity = E_WARNING | E_NOTICE; // alapértelmezett: 0 (nincs hibaszint) ``` -`dump()` Viselkedés -------------------- +A `dump()` viselkedése +---------------------- ```php -// maximális karakterlánc hossza -Debugger::$maxLength = 150; // (int) alapértelmezett a Tracy szerint +// a string maximális hossza +Debugger::$maxLength = 150; // (int) alapértelmezett a Tracy verziója szerint -// milyen mélyen fog listázni -Debugger::$maxDepth = 10; // (int) alapértelmezett Tracy szerint +// maximális beágyazási mélység +Debugger::$maxDepth = 10; // (int) alapértelmezett a Tracy verziója szerint -// a kulcsok értékeinek elrejtése (Tracy 2.8 óta) -Debugger::$keysToHide = ['password', /* ... */]; // (string[]) alapértelmezett [] +// ezen kulcsok értékeinek elrejtése (Tracy 2.8-tól) +Debugger::$keysToHide = ['password', /* ... */]; // (string[]) alapértelmezett: [] -// vizuális téma (Tracy 2.8 óta) -Debugger::$dumpTheme = 'dark'; // (light|dark) alapértelmezés szerint 'light'. +// vizuális téma (Tracy 2.8-tól) +Debugger::$dumpTheme = 'dark'; // (light|dark) alapértelmezett: 'light' -// megjeleníti a dump() hívás helyét? -Debugger::$showLocation = /* ... */; // (bool) alapértelmezett a Tracy szerint +// megjelenítse a helyet, ahol a dump() függvényt hívták? +Debugger::$showLocation = /* ... */; // (bool) alapértelmezett a Tracy verziója szerint ``` -Egyéb .[#toc-others] --------------------- +Egyéb +----- ```php -// Fejlesztés üzemmódban a BlueScreen figyelmeztetéseket vagy hibajelzéseket fog látni. -Debugger::$strictMode = /* ... */; // (bool|int) alapértelmezett értéke false, csak bizonyos hibaszinteket lehet kiválasztani (pl. E_USER_DEPRECATED | E_DEPRECATED). +// fejlesztői módban a notice vagy warning típusú hibákat BlueScreenként jeleníti meg +Debugger::$strictMode = /* ... */; // (bool|int) alapértelmezett: false, lehetőség van csak néhány hibaszint kiválasztására (pl. E_USER_DEPRECATED | E_DEPRECATED) -// néma (@) hibaüzenetek megjelenítése -Debugger::$scream = /* ... */; // (bool|int) alapértelmezett értéke false, a 2.9-es verzió óta csak bizonyos hibaszintek (pl. E_USER_DEPRECATED | E_DEPRECATED) választhatók ki. +// megjelenítse az elnémított (@) hibaüzeneteket? +Debugger::$scream = /* ... */; // (bool|int) alapértelmezett: false, a 2.9-es verziótól lehetőség van csak néhány hibaszint kiválasztására (pl. E_USER_DEPRECATED | E_DEPRECATED) -// a szerkesztőben megnyitandó link formátum -Debugger::$editor = /* ... */; // (string|null) alapértelmezés szerint 'editor://open/?file=%file&line=%line' +// a szerkesztőben való megnyitáshoz használt link formátuma +Debugger::$editor = /* ... */; // (string|null) alapértelmezett: 'editor://open/?file=%file&line=%line' -// az 500-as hiba esetén az egyéni oldalt tartalmazó sablon elérési útja -Debugger::$errorTemplate = /* ... */; // (string) alapértelmezés szerint nem beállított +// az egyéni 500-as hibaoldal sablonjának elérési útja +Debugger::$errorTemplate = /* ... */; // (string) alapértelmezett: nincs beállítva -// megjeleníti a Tracy Bar? -Debugger::$showBar = /* ... */; // (bool) alapértelmezett értéke true +// megjelenítse a Tracy Bart? +Debugger::$showBar = /* ... */; // (bool) alapértelmezett: true Debugger::$editorMapping = [ // eredeti => új @@ -79,91 +79,102 @@ Debugger::$editorMapping = [ ``` -Nette keretrendszer .[#toc-nette-framework] -------------------------------------------- +Nette Framework +--------------- -Ha a Nette Frameworket használja, akkor a Tracy-t is konfigurálhatja, és a konfigurációs fájl segítségével új paneleket adhat hozzá a Tracy Barhoz. -A konfigurációban beállíthatja a Tracy paramétereit, és új paneleket is hozzáadhat a Tracy Barhoz. Ezek a beállítások csak a DI konténer létrehozása után kerülnek alkalmazásra, így a korábban bekövetkezett hibák nem tükröződhetnek. +Ha a Nette Frameworköt használja, a Tracy-t konfigurálhatja és új paneleket adhat hozzá a Tracy Barhoz a konfigurációs fájl segítségével is. A konfigurációban beállíthatók a paraméterek, és új panelek is hozzáadhatók a Tracy Barhoz. Ezek a beállítások csak a DI konténer létrehozása után lépnek érvénybe, így a korábban keletkezett hibák nem tükrözhetik őket. -Hibanaplózási konfiguráció: +Hibanaplózás konfigurációja: ```neon tracy: - # ha hiba történt, az értesítést erre az e-mail címre küldjük. - email: dev@example.com # (string|string[]) alapértelmezett értéke unset + # e-mail cím(ek), amely(ek)re értesítést küldünk hiba esetén + email: dev@example.com # (string|string[]) alapértelmezett: nincs beállítva - # email feladó - fromEmail: robot@example.com # (string) alapértelmezés szerint unset + # az e-mail feladója + fromEmail: robot@example.com # (string) alapértelmezett: nincs beállítva - # az e-mailek küldésének elhalasztási ideje (Tracy 2.8.8.8 óta) - emailSnooze: ... # (string) alapértelmezett értéke '2 nap'. + # az e-mailek küldésének halasztási ideje (Tracy 2.8.8-tól) + emailSnooze: ... # (string) alapértelmezett: '2 days' - # a konfigurációban definiált mailer használata? (Tracy 2.5 óta) - netteMailer: ... # (bool) alapértelmezés szerint true + # használja a Nette mailert az e-mailek küldéséhez? (Tracy 2.5-től) + netteMailer: ... # (bool) alapértelmezett: true # mely hibaszintek esetén naplózódik a BlueScreen is? - logSeverity: [E_WARNING, E_NOTICE] # alapértelmezett értéke [] + logSeverity: [E_WARNING, E_NOTICE] # alapértelmezett: [] ``` -A `dump()` funkció konfigurációja: +A `dump()` függvény viselkedésének konfigurációja: ```neon tracy: - # maximum string length - maxLength: 150 # (int) alapértelmezett Tracy szerint + # a string maximális hossza + maxLength: 150 # (int) alapértelmezett a Tracy verziója szerint - # milyen mély lesz a lista - maxDepth: 10 # (int) alapértelmezett Tracy szerint + # maximális beágyazási mélység + maxDepth: 10 # (int) alapértelmezett a Tracy verziója szerint - # a kulcsok értékeinek elrejtése (Tracy 2.8 óta) - keysToHide: [password, pass] # (string[]) alapértelmezés szerint [] + # ezen kulcsok értékeinek elrejtése (Tracy 2.8-tól) + keysToHide: [password, pass] # (string[]) alapértelmezett: [] - # vizuális téma (Tracy 2.8 óta) - dumpTheme: dark # (light|dark) alapértelmezett értéke 'light'. + # vizuális téma (Tracy 2.8-tól) + dumpTheme: dark # (light|dark) alapértelmezett: 'light' - # megjeleníti a dump() hívásának helyét? - showLocation: ... # (bool) alapértelmezett a Tracy szerint + # megjelenítse a helyet, ahol a dump() függvényt hívták? + showLocation: ... # (bool) alapértelmezett a Tracy verziója szerint ``` -A Tracy kiterjesztés telepítése: +Tracy kiterjesztések telepítése: ```neon tracy: - # appends bars to Tracy Bar + # paneleket ad hozzá a Tracy Barhoz bar: - Nette\Bridges\DITracy\ContainerPanel - IncludePanel - XDebugHelper('myIdeKey') - MyPanel(@MyService) - # panelek hozzáadása a BlueScreen-hez + # paneleket ad hozzá a BlueScreenhez blueScreen: - DoctrinePanel::renderException ``` -Egyéb lehetőségek: +Egyéb opciók: ```neon tracy: - # fejlesztési módban, akkor látni fogod az értesítést vagy a hiba figyelmeztetéseket, mint BlueScreen - strictMode: ... # alapértelmezés szerint true + # fejlesztői módban a notice vagy warning típusú hibákat BlueScreenként jeleníti meg + strictMode: ... # alapértelmezett: true - # csendes (@) hibaüzeneteket jelenít meg - scream: ... # alapértelmezés szerint false + # megjelenítse az elnémított (@) hibaüzeneteket? + scream: ... # alapértelmezett: false - # link formátum a szerkesztőben való megnyitáshoz - editor: ... # (string) alapértelmezés szerint 'editor://open/?file=%file&line=%line' + # a szerkesztőben való megnyitáshoz használt link formátuma + editor: ... # (string) alapértelmezett: 'editor://open/?file=%file&line=%line' - # az 500-as hiba esetén az egyéni oldalt tartalmazó sablon elérési útja - errorTemplate: ... # (string) alapértelmezés szerint unset + # az egyéni 500-as hibaoldal sablonjának elérési útja + errorTemplate: ... # (string) alapértelmezett: nincs beállítva - # shows Tracy Bar? - showBar: ... # (bool) alapértelmezés szerint true + # megjelenítse a Tracy Bart? + showBar: ... # (bool) alapértelmezett: true editorMapping: - # original: new + # eredeti: új /var/www/html: /data/web /home/web: /srv/html ``` -A `logSeverity`, `strictMode` és `scream` opciók értékei hibaszintek tömbjeként is megadhatók (pl. `[E_WARNING, E_NOTICE]`) vagy PHP-ban használt kifejezésként (pl. `E_ALL & ~E_NOTICE`). +A `logSeverity`, `strictMode` és `scream` opciók értékei hibaszintek tömbjeként (pl. `[E_WARNING, E_NOTICE]`) vagy a PHP nyelvben használt kifejezésként (pl. `E_ALL & ~E_NOTICE`) is megadhatók. + + +DI Szolgáltatások +----------------- + +Ezek a szolgáltatások kerülnek hozzáadásra a DI konténerhez: + +| Név | Típus | Leírás +|---------------------------------------------------------- +| `tracy.logger` | [api:Tracy\ILogger] | logger +| `tracy.blueScreen` | [api:Tracy\BlueScreen] | BlueScreen +| `tracy.bar` | [api:Tracy\Bar] | Tracy Bar diff --git a/tracy/hu/dumper.texy b/tracy/hu/dumper.texy index a639ac90e2..2b8fe4e1d6 100644 --- a/tracy/hu/dumper.texy +++ b/tracy/hu/dumper.texy @@ -1,7 +1,7 @@ -Dömper -****** +Dumpolás +******** -Minden hibakereső fejlesztő jó barátja a `var_dump` függvény, amely részletesen felsorolja bármely változó összes tartalmát. Sajnos a kimenete HTML-formázás nélküli, és a dumpot egyetlen sor HTML-kódba adja ki, nem is beszélve a kontextus eszkópolásáról. Szükséges a `var_dump` helyett egy sokkal praktikusabb függvényt használni. Éppen ez a `dump()`. +Minden debugger jó barátja a [php:var_dump] függvénynek, amely részletesen kiírja egy változó tartalmát. Sajnos HTML környezetben a kimenet elveszíti a formázását és egyetlen sorba olvad össze, a HTML kód tisztításáról nem is beszélve. A gyakorlatban elengedhetetlen a `var_dump` helyettesítése egy ügyesebb függvénnyel. Ez pontosan a `dump()`. ```php $arr = [10, 20.2, true, null, 'hello']; @@ -10,7 +10,7 @@ dump($arr); // vagy Debugger::dump($arr); ``` -generálja a kimenetet: +a következő kimenetet generálja: [* dump-basic.webp *] @@ -22,27 +22,27 @@ Debugger::$dumpTheme = 'dark'; [* dump-dark.webp *] -Megváltoztathatja a beágyazás mélységét a `Debugger::$maxDepth` és a megjelenített karakterláncok hosszát a `Debugger::$maxLength`. Természetesen az alacsonyabb értékek gyorsítják a Tracy renderelését. +Továbbá módosíthatjuk a beágyazási mélységet a [Debugger::$maxDepth |api:Tracy\Debugger::$maxDepth] segítségével és a megjelenített stringek hosszát a [Debugger::$maxLength |api:Tracy\Debugger::$maxLength] segítségével. Az alacsonyabb értékek természetesen gyorsítják a Tracy-t. ```php Debugger::$maxDepth = 2; // alapértelmezett: 3 Debugger::$maxLength = 50; // alapértelmezett: 150 ``` -A `dump()` függvény más hasznos információkat is megjeleníthet. `Tracy\Dumper::LOCATION_SOURCE` egy tooltipet ad hozzá a fájl elérési útvonalával, ahol a függvényt meghívták. `Tracy\Dumper::LOCATION_LINK` egy linket ad hozzá a fájlhoz. `Tracy\Dumper::LOCATION_CLASS` egy tooltipet ad hozzá minden dömpingelt objektumhoz, amely tartalmazza a fájl elérési útvonalát, amelyben az objektum osztálya definiálva van. Mindezek a konstansok a `Debugger::$showLocation` változóban állíthatók be a `dump()` meghívása előtt. A `|` operátorral egyszerre több értéket is beállíthat. +A `dump()` függvény további hasznos információkat is ki tud írni. A `Tracy\Dumper::LOCATION_SOURCE` konstans hozzáad egy tooltipet azzal az elérési úttal, ahol a függvényt hívták. A `Tracy\Dumper::LOCATION_LINK` egy linket ad nekünk erre a helyre. A `Tracy\Dumper::LOCATION_CLASS` minden dumpolt objektumnál kiír egy tooltipet azzal a fájl elérési úttal, ahol az osztálya definiálva van. A konstansokat a `Debugger::$showLocation` változóba állítjuk be még a `dump()` hívása előtt. Ha több értéket szeretnénk egyszerre beállítani, az `|` operátorral kapcsoljuk össze őket. ```php -Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // Megmutatja az elérési utat, ahol a dump() meghívásra került. -Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Megjeleníti az osztályok elérési útvonalát és a dump() meghívásának helyére mutató linket is. -Debugger::$showLocation = false; // Elrejti a további helyinformációkat. -Debugger::$showLocation = true; // Megjeleníti az összes további helyinformációt. +Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // Csak a függvényhívás helyének kiírását állítja be +Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Egyszerre állítja be a link kiírását és az osztály elérési útját +Debugger::$showLocation = false; // Kikapcsolja a további információk kiírását +Debugger::$showLocation = true; // Bekapcsolja az összes további információ kiírását ``` -A `dump()` nagyon praktikus alternatívája a `dumpe()` (azaz dump és exit) és a `bdump()`. Ez lehetővé teszi számunkra a változók dumpolását a Tracy Barban. Ez azért hasznos, mert a dumpok nem rontják el a kimenetet, és címet is adhatunk a dumphoz. +A `dump()` praktikus alternatívája a `dumpe()` (dump & exit) és a `bdump()`. Ez lehetővé teszi számunkra, hogy egy változó értékét a Tracy Bar paneljén írjuk ki. Ez nagyon praktikus, mivel a dumpok elkülönülnek az oldalelrendezéstől, és megjegyzést is fűzhetünk hozzájuk. ```php -bdump([2, 4, 6, 8], 'even numbers up to ten'); -bdump([1, 3, 5, 7, 9], 'odd numbers up to ten'); +bdump([2, 4, 6, 8], 'páros számok tízig'); +bdump([1, 3, 5, 7, 9], 'páratlan számok tízig'); ``` -[* bardump-en.webp *] +[* bardump-cs.webp *] diff --git a/tracy/hu/extensions.texy b/tracy/hu/extensions.texy index e45da6a1ea..03473ffd57 100644 --- a/tracy/hu/extensions.texy +++ b/tracy/hu/extensions.texy @@ -1,23 +1,23 @@ -Tracy kiterjesztések létrehozása -******************************** +Tracy kiterjesztések készítése +****************************** <div class=perex> -A Tracy egy nagyszerű eszköz az alkalmazás hibakereséséhez. Néha azonban több információra van szükséged, mint amennyit a Tracy nyújt. Megtanulhatsz a következőket: +A Tracy kiváló eszközt biztosít az alkalmazás debuggolásához. Néha azonban szeretne néhány további információt is kéznél tartani. Megmutatjuk, hogyan írhat saját kiterjesztést a Tracy Barhoz, hogy a fejlesztés még kellemesebb legyen. -- Saját Tracy Bar panelek létrehozása -- Saját Bluescreen-bővítmények létrehozása +- Saját panel létrehozása a Tracy Barhoz +- Saját kiterjesztés létrehozása a Bluescreenhez </div> .[tip] -A Tracy számára hasznos bővítményeket találhat a "Componette":https://componette.org/search/tracy oldalon. +A kész Tracy kiterjesztések repositoryját a "Componette"-en találja:https://componette.org/search/tracy. -Tracy bárbővítmények .[#toc-tracy-bar-extensions] -================================================= +Kiterjesztések a Tracy Barhoz +============================= -Egy új kiterjesztés létrehozása a Tracy Bar számára egyszerű. Meg kell valósítania a `Tracy\IBarPanel` interfészt a `getTab()` és a `getPanel()` metódusokkal. A metódusoknak vissza kell adniuk egy fül (kis címke a Tracy Baron) és egy panel (a fülre kattintás után megjelenő felugró ablak) HTML-kódját. Ha a `getPanel()` nem ad vissza semmit, akkor csak a fül jelenik meg. Ha a `getTab()` nem ad vissza semmit, akkor semmi sem jelenik meg, és a `getPanel()` nem hívódik meg. +Új kiterjesztés létrehozása a Tracy Barhoz nem bonyolult. Létrehoz egy objektumot, amely implementálja a `Tracy\IBarPanel` interfészt, amelynek két metódusa van: `getTab()` és `getPanel()`. A metódusoknak vissza kell adniuk a fül (a Bar-on közvetlenül megjelenő kis címke) és a panel HTML kódját. Ha a `getPanel()` semmit sem ad vissza, csak maga a címke jelenik meg. Ha a `getTab()` semmit sem ad vissza, egyáltalán semmi sem jelenik meg, és a getPanel() sem hívódik meg többé. ```php class ExamplePanel implements Tracy\IBarPanel @@ -35,16 +35,16 @@ class ExamplePanel implements Tracy\IBarPanel ``` -Regisztráció .[#toc-registration] ---------------------------------- +Regisztráció +------------ -A regisztráció a `Tracy\Bar::addPanel()` címen történik: +A regisztráció a `Tracy\Bar::addPanel()` segítségével történik: ```php Tracy\Debugger::getBar()->addPanel(new ExamplePanel); ``` -vagy egyszerűen regisztrálhatja a panelt az alkalmazás konfigurációjában: +Vagy regisztrálhatja a panelt közvetlenül az alkalmazás konfigurációjában: ```neon tracy: @@ -53,70 +53,70 @@ tracy: ``` -HTML kód .[#toc-tab-html-code] ------------------------------- +A fül HTML kódja +---------------- -Valahogy így kell kinéznie: +Körülbelül így kell kinéznie: ```latte -<span title="Explaining tooltip"> +<span title="Magyarázó címke"> <svg>...</svg> - <span class="tracy-label">Title</span> + <span class="tracy-label">Cím</span> </span> ``` -A képnek SVG formátumúnak kell lennie. Ha nincs szükséged tooltipre, akkor hagyhatod a `<span>` ki. +A képnek SVG formátumúnak kell lennie. Ha nincs szükség magyarázó címkére, a `<span>` elhagyható. -Panel HTML kód .[#toc-panel-html-code] --------------------------------------- +A panel HTML kódja +------------------ -Valahogy így kell kinéznie: +Körülbelül így kell kinéznie: ```latte -<h1>Title</h1> +<h1>Cím</h1> <div class="tracy-inner"> <div class="tracy-inner-container"> - ... content ... + ... tartalom ... </div> </div> ``` -A címnek vagy ugyanannak kell lennie, mint a lapon, vagy további információkat kell tartalmaznia. +A címnek vagy meg kell egyeznie a fül címével, vagy tartalmazhat további adatokat. -Egy kiterjesztés többször is regisztrálható, ezért ajánlott nem használni a `id` attribútumot a stílus kialakításához. Használhat osztályokat, lehetőleg a `tracy-addons-<class-name>[-<optional>]` formátumban. CSS létrehozásakor jobb a `#tracy-debug .class`, mert az ilyen szabály magasabb prioritású, mint a reset. +Számolni kell azzal, hogy egy kiterjesztés többször is regisztrálható, például eltérő beállításokkal, így a stílusozáshoz nem használhatók CSS id-k, csak class-ok, mégpedig `tracy-addons-<OsztályNév>[-<opcionális>]` formában. Az osztályt ezután írja be a div-be a `tracy-inner` osztállyal együtt. CSS írásakor hasznos a `#tracy-debug .osztály` írásmód, mert a szabálynak így magasabb prioritása lesz, mint a resetnek. -Alapértelmezett stílusok .[#toc-default-styles] ------------------------------------------------ +Alapértelmezett stílusok +------------------------ -A panelben az elemek `<a>`, `<table>`, `<pre>`, `<code>` elemek alapértelmezett stílusokkal rendelkeznek. Más elemek elrejtésére vagy megjelenítésére szolgáló link létrehozásához kapcsolja össze őket a `href` és a `id` attribútumokkal és a `tracy-toggle` osztállyal. +A panelben előre stílusozottak az `<a>`, `<table>`, `<pre>`, `<code>` elemek. Ha olyan linket szeretne létrehozni, amely elrejt és megjelenít egy másik elemet, kösse össze őket a `href` és `id` attribútumokkal és a `tracy-toggle` osztállyal: ```latte -<a href="#tracy-addons-className-{$counter}" class="tracy-toggle">Detail</a> +<a href="#tracy-addons-OsztályNév-{$counter}" class="tracy-toggle">Részletek</a> -<div id="tracy-addons-className-{$counter}">...</div> +<div id="tracy-addons-OsztályNév-{$counter}">...</div> ``` -Ha az alapértelmezett állapot az összecsukott, akkor mindkét elemhez adjuk hozzá a `tracy-collapsed` osztályt. +Ha az alapértelmezett állapotnak összecsukottnak kell lennie, adjon hozzá mindkét elemhez a `tracy-collapsed` osztályt. -Használjon statikus számlálót, hogy megakadályozza a duplikált azonosítókat egy oldalon. +A számlálót használja statikusként, hogy ne jöjjenek létre duplikált ID-k egy oldalon. -Bluescreen kiterjesztések .[#toc-bluescreen-extensions] -======================================================= +Kiterjesztések a Bluescreenhez +============================== -Hozzáadhat saját kivételes vizualizációkat vagy paneleket, amelyek megjelennek a bluescreen-en. +Ezzel a módszerrel hozzáadhat saját kivétel-vizualizációkat vagy paneleket, amelyek a bluescreenen jelennek meg. -A bővítmény így készül: +A kiterjesztés ezzel a paranccsal hozható létre: ```php Tracy\Debugger::getBlueScreen()->addPanel(function (?Throwable $e) { // elkapott kivétel return [ - 'tab' => '...Cím...', - 'panel' => '...tartalom...', + 'tab' => '...Címke...', + 'panel' => '...Panel HTML kódja...', ]; }); ``` -A függvényt kétszer hívjuk meg, először magát a kivételt adjuk át a `$e` paraméterben, majd a visszaküldött panelt az oldal elején megjelenítjük. Ha semmi sem érkezik vissza, a panel nem kerül renderelésre. Ezután a `null` paraméterrel hívjuk meg, és a visszaadott panel a callstack alatt kerül renderelésre. Ha a függvény a tömbben a `'bottom' => true` címet adja vissza, a panel a legalsó részen kerül megjelenítésre. +A függvény kétszer hívódik meg, először a `$e` paraméterben maga a kivétel kerül átadásra, és a visszaadott panel az oldal elején rajzolódik ki. Ha semmit sem ad vissza, a panel nem rajzolódik ki. Ezután `null` paraméterrel hívódik meg, és a visszaadott panel a callstack alatt rajzolódik ki. Ha a függvény a tömbben a `'bottom' => true` kulcsot adja vissza, a panel teljesen alulra kerül. diff --git a/tracy/hu/guide.texy b/tracy/hu/guide.texy index 31dec6942e..d6eba633ea 100644 --- a/tracy/hu/guide.texy +++ b/tracy/hu/guide.texy @@ -1,218 +1,217 @@ -Kezdő lépések Tracyvel +Ismerkedés a Tracy-vel ********************** <div class=perex> -A Tracy könyvtár egy hasznos segédprogram a mindennapi PHP programozók számára. Segít a következőkben: +A Tracy könyvtár hasznos mindennapi segédje a PHP programozónak. Segít Önnek: -- a hibák gyors felismerésében és kijavításában -- a hibák naplózása -- változók dumpolása -- a szkriptek/lekérdezések végrehajtási idejének mérése -- a memóriafogyasztás megtekintése +- gyorsan felfedezni és javítani a hibákat +- hibákat naplózni +- változókat kiírni +- szkriptek és adatbázis-lekérdezések idejét mérni +- memóriaigényeket figyelni </div> -A PHP tökéletes nyelv a nehezen észrevehető hibák készítésére, mert nagy rugalmasságot biztosít a programozóknak. A Tracy\Debugger emiatt még értékesebb. Ez egy végső eszköz a diagnosztikai eszközök között. +A PHP egy olyan nyelv, amely tökéletesen alkalmas a nehezen felfedezhető hibák elkövetésére, mivel jelentős szabadságot ad a fejlesztőknek. Annál értékesebb a Tracy debuggoló eszköz. A PHP diagnosztikai eszközei között abszolút csúcsot képvisel. -Ha először találkozol Tracyvel, hidd el, az életed kezd kettéválni egy Tracy előtti és egy vele való életre. Üdvözöljük a jó résznél! +Ha ma találkozik először a Tracy-vel, higgye el, hogy az élete ketté fog válni: a Tracy előtti és a vele való életre. Üdvözöljük a jobbik részben! -Telepítés és követelmények .[#toc-installation-and-requirements] -================================================================ +Telepítés +========= -A Tracy telepítésének legjobb módja a [legújabb csomag letöltés](https://github.com/nette/tracy/releases) vagy a Composer használata: +A Tracy telepítésének legjobb módja a [legújabb csomag letöltése](https://github.com/nette/tracy/releases), vagy a Composer használata: ```shell composer require tracy/tracy ``` -Alternatívaként letöltheti a teljes csomagot vagy a [tracy.phar |https://github.com/nette/tracy/releases] fájlt. +Letöltheti az egész csomagot [tracy.phar |https://github.com/nette/tracy/releases] fájlként is. -Használat .[#toc-usage] -======================= +Használat +========= -A Tracy aktiválása a `Tracy\Debugger::enable()' metódus meghívásával történik a program elején a lehető leghamarabb, mielőtt bármilyen kimenet elküldésre kerülne: +A Tracy-t a `Tracy\Debugger::enable()` metódus hívásával aktiváljuk a program legelején, még bármilyen kimenet küldése előtt: ```php use Tracy\Debugger; -require 'vendor/autoload.php'; // alternatívaként tracy.phar +require 'vendor/autoload.php'; // vagy tracy.phar Debugger::enable(); ``` -Az első dolog, amit az oldalon észreveszel, a Tracy Bar a jobb alsó sarokban. Ha nem látja, az azt jelentheti, hogy a Tracy termelési üzemmódban fut. -Ez azért van, mert a Tracy biztonsági okokból csak a localhost-on látható. Ha tesztelni szeretné, hogy működik-e, a `Debugger::enable(Debugger::Development)` paraméter segítségével ideiglenesen fejlesztési üzemmódba helyezheti. +Az első dolog, amit észrevesz az oldalon, a Tracy Bar a jobb alsó sarokban. Ha nem látja, az jelentheti, hogy a Tracy éles/produkciós módban fut. A Tracy ugyanis biztonsági okokból csak localhoston látható. Annak tesztelésére, hogy működik-e, ideiglenesen átkapcsolhatja fejlesztői módba a `Debugger::enable(Debugger::Development)` paraméterrel. -Tracy Bar .[#toc-tracy-bar] -=========================== +Tracy Bar +========= -A Tracy Bar egy lebegő panel. Az oldal jobb alsó sarkában jelenik meg. Az egérrel mozgatható. Az oldal újratöltése után megjegyzi a pozícióját. +A Tracy Bar egy lebegő panel, amely az oldal jobb alsó sarkában jelenik meg. Egérrel mozgathatjuk, és az oldal újratöltése után emlékezni fog a pozíciójára. [* tracy-bar.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html -Más hasznos paneleket is hozzáadhat a Tracy Barhoz. Érdekeseket találhatsz az [addonokban |https://componette.org], vagy [létrehozhatod a sajátodat |extensions]. +A Tracy Barhoz további hasznos paneleket lehet hozzáadni. Sokat találhat a [kiegészítőkben |https://componette.org], vagy akár [írhatsz sajátot |extensions] is. -Ha nem szeretné megjeleníteni a Tracy Bar-t, állítsa be: +Ha nem szeretné megjeleníteni a Tracy Bart, állítsa be: ```php Debugger::$showBar = false; ``` -Hibák és kivételek megjelenítése .[#toc-visualization-of-errors-and-exceptions] -=============================================================================== +Hibák és kivételek vizualizációja +================================= -Bizonyára tudod, hogyan jelzi a PHP a hibákat: az oldal forráskódjában van valami ilyesmi: +Bizonyára jól tudja, hogyan jelzi a PHP a hibákat: az oldal forráskódjába valami ilyesmit ír ki: /--pre .{font-size: 90%} <b>Parse error</b>: syntax error, unexpected '}' in <b>HomePresenter.php</b> on line <b>15</b> \-- -vagy el nem fogott kivétel: +vagy el nem kapott kivétel esetén: /--pre .{font-size: 90%} <b>Fatal error</b>: Uncaught Nette\MemberAccessException: Call to undefined method Nette\Application\UI\Form::addTest()? in /sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php:100 Stack trace: #0 /sandbox/vendor/nette/utils/src/Utils/Object.php(75): Nette\Utils\ObjectMixin::call(Object(Nette\Application\UI\Form), 'addTest', Array) -#1 /sandbox/app/forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) -#2 /sandbox/app/presenters/SignPresenter.php(21): App\Forms\SignFormFactory->create() -#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presenters\SignPresenter->createComponentSignInForm('signInForm') +#1 /sandbox/app/Forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) +#2 /sandbox/app/Presentation/Sign/SignPresenter.php(21): App\Forms\SignFormFactory->create() +#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presentation\Sign\SignPresenter->createComponentSignInForm('signInForm') #4 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(139): Nette\ComponentModel\Container->createComponent('signInForm') #5 /sandbox/temp/cache/latte/15206b353f351f6bfca2c36cc.php(17): Nette\ComponentModel\Co in <b>/sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php</b> on line <b>100</b><br /> \-- -Nem olyan könnyű eligazodni ebben a kimenetben. Ha engedélyezi a Tracy funkciót, mind a hibák, mind a kivételek teljesen más formában jelennek meg: +Egy ilyen kimenetben eligazodni nem éppen könnyű. Ha bekapcsoljuk a Tracy-t, a hiba vagy kivétel teljesen más formában jelenik meg: [* tracy-exception.webp .{url:-} *] -A hibaüzenet szó szerint ordít. Látható a forráskód egy része a kiemelt sorral, ahol a hiba előfordult. Az üzenet világosan megmagyarázza a hibát. Az egész oldal [interaktív, próbálja ki](https://nette.github.io/tracy/tracy-exception.html). +A hibaüzenet szó szerint kiabál. Látjuk a forráskód egy részletét a kiemelt sorral, ahol a hiba történt és a *Call to undefined method Nette\Http\User::isLogedIn()* információ érthetően magyarázza, milyen hibáról van szó. Az egész oldal ráadásul élő, átkattinthatunk további részletekre. [Próbáld ki |https://nette.github.io/tracy/tracy-exception.html]. -És tudod mit? A végzetes hibákat ugyanígy rögzíti és megjeleníti. Nem kell semmilyen bővítményt telepíteni (kattintson az élő példa megtekintéséhez): +És tudja mit? Így kapja el és jeleníti meg a fatális hibákat is. Anélkül, hogy bármilyen kiterjesztést telepíteni kellene. [* tracy-error.webp .{url:-} *] -Az olyan hibák, mint például egy változó nevének elírása vagy egy nem létező fájl megnyitásának kísérlete E_NOTICE vagy E_WARNING szintű jelentéseket generál. Ezek könnyen figyelmen kívül hagyhatók és/vagy teljesen elrejthetők egy weboldal grafikai elrendezésében. Hagyja, hogy a Tracy kezelje ezeket: +Az olyan hibák, mint egy elgépelés a változó nevében vagy egy nem létező fájl megnyitásának kísérlete, E_NOTICE vagy E_WARNING szintű jelentéseket generálnak. Ezeket az oldal grafikájában könnyű figyelmen kívül hagyni, sőt, lehet, hogy egyáltalán nem láthatók (legfeljebb az oldal kódjának megtekintésével). [* tracy-notice2.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html -Vagy hibaként jelenhetnek meg: +Vagy megjeleníthetők ugyanúgy, mint a hibák: ```php -Debugger::$strictMode = true; // minden hiba megjelenítése -Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // minden hiba, kivéve az elévült hibaüzeneteket. +Debugger::$strictMode = true; // jelenítsen meg minden hibát +Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // minden hiba, kivéve a deprecate értesítéseket ``` [* tracy-notice.webp .{url:-} *] -Megjegyzés: A Tracy aktiválásakor a hibajelentési szint E_ALL-ra változik. Ha ezt meg akarja változtatni, tegye meg a `enable()` meghívása után. +Megjegyzés: A Tracy aktiválás után az E_ALL szintre változtatja a hibajelentési szintet. Ha ezt az értéket módosítani szeretné, tegye meg az `enable()` hívása után. -Fejlesztési vs. termelési üzemmód .[#toc-development-vs-production-mode] -======================================================================== +Fejlesztői vs éles/produkciós mód +================================= -Mint látható, Tracy elég beszédes, ami a fejlesztői környezetben értékelhető, míg a produktív szerveren katasztrófát okozna. Ott ugyanis nem jelenhet meg hibakeresési információ. A Tracy ezért rendelkezik **környezet automatikus felismeréssel**, és ha a példát éles szerveren futtatjuk, a hiba megjelenítés helyett naplózásra kerül, és a látogató csak egy felhasználóbarát üzenetet lát: +Ahogy látja, a Tracy meglehetősen bőbeszédű, ami fejlesztői környezetben értékelhető, míg egy éles/produkciós szerveren kész katasztrófát okozna. Ott ugyanis semmilyen debuggolási információ nem íródhat ki. A Tracy ezért **környezet automatikus észlelésével** rendelkezik, és ha a példát éles szerveren futtatjuk, a hiba megjelenítés helyett naplózásra kerül, és a látogató csak egy felhasználóbarát üzenetet lát: [* tracy-error2.webp .{url:-} *] -A termelési mód elnyomja a [dump() |dumper] segítségével küldött összes hibakeresési információ megjelenítését, és természetesen a PHP által generált összes hibaüzenetet is. Tehát ha elfelejtettél néhány `dump($obj)` címet a kódban, nem kell aggódnod, a termelő szerveren semmi sem fog megjelenni. +Az éles/produkciós mód elnyomja az összes debuggolási információ megjelenítését, amelyet a [dump() |dumper] segítségével küldünk ki, és természetesen az összes hibaüzenetet is, amelyet a PHP generál. Tehát ha a kódban elfelejtett néhány `dump($obj)`-ot, nem kell aggódnia, az éles/produkciós szerveren semmi sem fog kiíródni. -Hogyan működik a mód automatikus felismerése? Az üzemmód fejlesztési, ha az alkalmazás localhoston fut (azaz a `127.0.0.1` vagy `::1` IP-címen ) és nincs proxy (azaz a HTTP fejléce). Ellenkező esetben termelési üzemmódban fut. +Hogyan működik a mód automatikus észlelése? A mód akkor fejlesztői, ha az alkalmazás localhoston fut (azaz `127.0.0.1` vagy `::1` IP-címen), és nincs jelen proxy (azaz annak HTTP fejléce). Egyébként éles/produkciós módban fut. -Ha más esetekben is engedélyezni szeretné a fejlesztési üzemmódot, például egy adott IP-címről hozzáférő fejlesztők számára, akkor azt a `enable()` módszer paramétereként adhatja meg: +Ha engedélyezni szeretnénk a fejlesztői módot más esetekben is, például egy konkrét IP-címről hozzáférő programozóknak, adjuk meg azt az `enable()` metódus paramétereként: ```php -Debugger::enable('23.75.345.200'); // IP-címek tömbjét is megadhatja +Debugger::enable('23.75.345.200'); // IP-címek tömbje is megadható ``` -Mindenképpen javasoljuk az IP-cím és egy cookie kombinálását. Tároljon egy titkos tokent, pl. `secret1234`, a `tracy-debug` cookie-ban, és így csak azon fejlesztők számára aktiválja a fejlesztési módot, akik egy adott IP-címről lépnek be, és akiknek a cookie-ban szerepel az említett token: +Határozottan javasoljuk az IP-cím és egy süti kombinálását. A `tracy-debug` sütibe mentsünk el egy titkos tokent, pl. `secret1234`, és így aktiváljuk a fejlesztői módot csak azoknak a programozóknak, akik egy konkrét IP-címről férnek hozzá, és a sütijükben szerepel az említett token: ```php Debugger::enable('secret1234@23.75.345.200'); ``` -A fejlesztési/gyártási módot közvetlenül is beállíthatja a `Debugger::Development` vagy a `Debugger::Production` konstansok segítségével, a `enable()` módszer paramétereként. +A fejlesztői/éles/produkciós módot közvetlenül is beállíthatjuk a `Debugger::Development` vagy `Debugger::Production` konstans használatával az `enable()` metódus paramétereként. .[note] -Ha a Nette keretrendszert használja, nézze meg, hogyan kell [beállítani a módot |application:bootstrap#Development vs Production Mode], és ezután a Tracy esetében is használni fogja. +Ha a Nette Frameworköt használja, nézze meg, hogyan [állítsa be a módot |application:bootstrapping#Fejlesztői vs éles mód] számára, és ez utána a Tracy számára is használva lesz. -Hibanaplózás .[#toc-error-logging] -================================== +Hibanaplózás +============ -Termelési üzemmódban a Tracy automatikusan naplózza az összes hibát és kivételt egy szöveges naplóba. Ahhoz, hogy a naplózás megtörténjen, a naplókönyvtár abszolút elérési útját be kell állítani a `$logDirectory` változóba, vagy a `enable()` metódus második paramétereként kell átadni: +Éles/produkciós módban a Tracy automatikusan minden hibát és elkapott kivételt egy szöveges naplóba rögzít. Ahhoz, hogy a naplózás működjön, be kell állítanunk a naplózási könyvtár abszolút elérési útját a `$logDirectory` változóba, vagy át kell adnunk az `enable()` metódus második paramétereként: ```php Debugger::$logDirectory = __DIR__ . '/log'; ``` -A hibanaplózás rendkívül hasznos. Képzeld el, hogy az alkalmazásod összes felhasználója valójában bétatesztelő, akik ingyenesen végeznek elsőrangú munkát a hibák felkutatásában, és bolond lennél, ha az értékes jelentéseiket észrevétlenül a kukába dobnád. +A hibanaplózás rendkívül hasznos. Képzelje el, hogy az alkalmazásának minden felhasználója valójában bétatesztelő, akik ingyen végeznek csúcsmunkát a hibakeresésben, és butaságot követne el, ha értékes jelentéseiket figyelmen kívül hagyva a szemeteskosárba dobná. -Ha saját üzeneteket vagy elkapott kivételeket kell naplóznod, használd a `log()` módszert: +Ha saját üzenetet vagy egy Ön által elkapott kivételt kell naplóznunk, használjuk erre a `log()` metódust: ```php -Debugger::log('Váratlan hiba'); // szöveges üzenet +Debugger::log('Váratlan hiba történt'); // szöveges üzenet try { - criticalOperation(); + kritikusMuvelet(); } catch (Exception $e) { - Debugger::log($e); // log kivétel + Debugger::log($e); // kivételt is lehet naplózni // vagy - Debugger::log($e, Debugger::ERROR); // e-mail értesítést is küld. + Debugger::log($e, Debugger::ERROR); // e-mail értesítést is küld } ``` -If you want Tracy to log PHP errors like `E_NOTICE` or `E_WARNING` with detailed information (HTML report), set `Debugger::$logSeverity`: +Ha azt szeretné, hogy a Tracy a PHP hibákat, mint az `E_NOTICE` vagy `E_WARNING`, részletes információkkal (HTML jelentés) naplózza, állítsa be a `Debugger::$logSeverity`-t: ```php Debugger::$logSeverity = E_NOTICE | E_WARNING; ``` -Egy igazi szakember számára a hibanapló fontos információforrás, és minden új hibáról azonnal értesülni akar. A Tracy segít neki ebben. Képes arra, hogy minden új hibajegyzékről e-mailt küldjön. A $email változó azonosítja, hogy hova küldje ezeket az e-maileket: +Egy igazi profi számára a hibanapló kulcsfontosságú információforrás, és azonnal tájékoztatást szeretne kapni minden új hibáról. A Tracy ebben segítséget nyújt, ugyanis képes e-mailben tájékoztatni a napló új bejegyzéséről. Azt, hogy hova küldje az e-maileket, a `$email` változóval határozzuk meg: ```php Debugger::$email = 'admin@example.com'; ``` -Ha a teljes Nette keretrendszert használja, akkor ezt és másokat is beállíthat a [konfigurációs fájlban |nette:configuring]. +Ha a teljes Nette Frameworköt használja, ezt és más beállításokat a [konfigurációs fájlban |nette:configuring] állíthatja be. -Hogy megvédje az e-mail postafiókját az elárasztástól, a Tracy **csak egy üzenetet** küld, és létrehoz egy fájlt: `email-sent`. Amikor egy fejlesztő megkapja az e-mail értesítést, ellenőrzi a naplót, kijavítja az alkalmazását, és törli a `email-sent` felügyeleti fájlt. Ez újra aktiválja az e-mail küldést. +Azonban, hogy ne árassza el az e-mail postafiókját, mindig **csak egy üzenetet** küld, és létrehoz egy `email-sent` fájlt. A fejlesztő az e-mail értesítés fogadása után ellenőrzi a naplót, javítja az alkalmazást, és törli a figyelő fájlt, amivel újra aktiválódik az e-mail küldés. -Fájlok megnyitása a szerkesztőben .[#toc-opening-files-in-the-editor] -===================================================================== +Megnyitás a szerkesztőben +========================= -Amikor a hibaoldal megjelenik, a fájlnevekre kattintva megnyílnak a szerkesztőprogramban, a kurzor a megfelelő soron lesz. A fájlokat létre is lehet hozni (művelet `create file`) vagy hibát javítani bennük (művelet `fix it`). Ehhez a [böngészőt és a rendszert kell beállítani |open-files-in-ide]. +A hibaoldal megjelenítésekor rá lehet kattintani a fájlnevekre, és azok megnyílnak a szerkesztőben a kurzorral a megfelelő soron. Lehetőség van fájlok létrehozására (`create file` akció) vagy hibák javítására (`fix it` akció) is. Ahhoz, hogy ez működjön, [konfigurálni kell a böngészőt és a rendszert |open-files-in-ide]. -Támogatott PHP-verziók .[#toc-supported-php-versions] -===================================================== +Támogatott PHP verziók +====================== -| Tracy | kompatibilis a PHP-vel -|-----------|-------------------- -| Tracy 2.10 – 3.0 | PHP 8.0 - 8.2 -| Tracy 2.9 | PHP 7.2 - 8.2 -| Tracy 2.8 | PHP 7.2 - 8.1 -| Tracy 2.6 - 2.7 | PHP 7.1 - 8.0 -| Tracy 2.5 | PHP 5.4 - 7.4 -| Tracy 2.4 | PHP 5.4 - 7.2 +| Tracy | kompatibilis PHP verziókkal +|-----------|------------------- +| Tracy 2.10 – 3.0 | PHP 8.0 – 8.4 +| Tracy 2.9 | PHP 7.2 – 8.2 +| Tracy 2.8 | PHP 7.2 – 8.1 +| Tracy 2.6 – 2.7 | PHP 7.1 – 8.0 +| Tracy 2.5 | PHP 5.4 – 7.4 +| Tracy 2.4 | PHP 5.4 – 7.2 -A legújabb javítási verziókra vonatkozik. +Az utolsó patch verzióra érvényes. -Portok .[#toc-ports] -==================== +Portok +====== -Ez egy lista a más keretrendszerek és CMS-ek nem hivatalos portjairól: +Ez a nem hivatalos portok listája más keretrendszerekhez és CMS-ekhez: -- [Drupal 7](https://www.drupal.org/project/traced) -- Laravel framework: [recca0120/laravel-tracy](https://github.com/recca0120/laravel-tracy), [whipsterCZ/laravel-tracy](https://github.com/whipsterCZ/laravel-tracy) -- [OpenCart](https://github.com/BurdaPraha/oc_tracy) -- [ProcessWire CMS/CMF](https://github.com/adrianbj/TracyDebugger) -- [Slim Framework](https://github.com/runcmf/runtracy) -- Symfony framework: [kutny/tracy-bundle](https://github.com/kutny/tracy-bundle), [VasekPurchart/Tracy-Blue-Screen-Bundle](https://github.com/VasekPurchart/Tracy-Blue-Screen-Bundle) -- [Wordpress](https://github.com/ktstudio/WP-Tracy) +- [Drupal 7 |https://www.drupal.org/project/traced] +- Laravel framework: [recca0120/laravel-tracy |https://github.com/recca0120/laravel-tracy], [whipsterCZ/laravel-tracy |https://github.com/whipsterCZ/laravel-tracy] +- [OpenCart |https://github.com/BurdaPraha/oc_tracy] +- [ProcessWire CMS/CMF |https://github.com/adrianbj/TracyDebugger] +- [Slim Framework |https://github.com/runcmf/runtracy] +- Symfony framework: [kutny/tracy-bundle |https://github.com/kutny/tracy-bundle], [VasekPurchart/Tracy-Blue-Screen-Bundle |https://github.com/VasekPurchart/Tracy-Blue-Screen-Bundle] +- [Wordpress |https://github.com/ktstudio/WP-Tracy] diff --git a/tracy/hu/open-files-in-ide.texy b/tracy/hu/open-files-in-ide.texy index 5500772969..08cd0fe41f 100644 --- a/tracy/hu/open-files-in-ide.texy +++ b/tracy/hu/open-files-in-ide.texy @@ -1,20 +1,20 @@ -Hogyan nyissunk meg egy fájlt a Tracy szerkesztőben? (IDE integráció) -********************************************************************* +Hogyan nyissunk meg fájlt a szerkesztőben a Tracy-ből? (Integráció az IDE-vel) +****************************************************************************** .[perex] -Amikor a hibaoldal megjelenik, a fájlnevekre kattintva megnyílnak a szerkesztőprogramban, a kurzor a megfelelő soron lesz. A fájlokat létre is lehet hozni (művelet `create file`) vagy hibát javítani bennük (művelet `fix it`). Ehhez a böngészőt és a rendszert kell beállítani. +A hibaoldal megjelenítésekor rá lehet kattintani a fájlnevekre, és azok megnyílnak a szerkesztőben a kurzorral a megfelelő soron. Lehetőség van fájlok létrehozására (`create file` akció) vagy hibák javítására (`fix it` akció) is. Ahhoz, hogy ez megtörténjen, konfigurálni kell a böngészőt és a rendszert. -A Tracy a `editor://open/?file=%file&line=%line` formájú URL-eken keresztül, azaz a `editor://` protokollal nyitja meg a fájlokat. Ehhez saját kezelőt fogunk regisztrálni. Ez lehet bármilyen futtatható fájl, amely feldolgozza a paramétereket és elindítja a kedvenc szerkesztőnket. +A Tracy az `editor://open/?file=%file&line=%line` formátumú URL-en keresztül nyitja meg a fájlokat, azaz az `editor://` protokollal. Ehhez regisztrálunk egy saját kezelőt. Ez lehet bármilyen futtatható fájl, amely "feldolgozza" a paramétereket és elindítja a kedvenc szerkesztőnket. -A `Tracy\Debugger::$editor` változóban megváltoztathatjuk az URL-t, vagy a `Tracy\Debugger::$editor = null` beállításával letilthatjuk az átkattintást. +Az URL-t megváltoztathatja a `Tracy\Debugger::$editor` változóban, vagy kikapcsolhatja a kattintást a `Tracy\Debugger::$editor = null` beállítással. -Windows .[#toc-windows] -======================= +Windows +======= -1. Töltse le a megfelelő fájlokat "a Tracy tárolóból":https://github.com/nette/tracy/tree/master/tools/open-in-editor/windows a lemezre. +1. Töltse le a megfelelő fájlokat [a Tracy repositoryból |https://github.com/nette/tracy/tree/master/tools/open-in-editor/windows] a lemezre. -2. Szerkessze meg a `open-editor.js` címet, és vegye ki a `settings` címen a szerkesztőhöz vezető elérési utat, vagy szerkessze azt: +2. Szerkessze az `open-editor.js` fájlt, és a `settings` tömbben távolítsa el a kommentet, és szükség esetén módosítsa a szerkesztő elérési útját: ```js var settings = { @@ -35,19 +35,28 @@ var settings = { ... ``` -Legyen óvatos, és tartsa meg a kettős kötőjeleket az elérési utakban. +Figyelem, hagyja meg a dupla perjeleket az elérési utakban. -3. Regisztrálja a `editor://` protokoll kezelőjét a rendszerben. +3. Regisztrálja az `editor://` protokoll kezelőjét a rendszerben. -Ez a `install.cmd` futtatásával történik. **Adminisztrátorként kell futtatnia.** A `open-editor.js` szkript mostantól a `editor://` protokollt fogja kiszolgálni. +Ezt az `install.cmd` fájl futtatásával teheti meg. **Rendszergazdaként kell futtatni.** Az `open-editor.js` szkript mostantól az `editor://` protokollt fogja kiszolgálni. +Annak érdekében, hogy más szervereken, például éles szerveren vagy Dockerben generált linkeket is meg lehessen nyitni, egészítse ki az `open-editor.js`-t a távoli URL helyi URL-re történő leképezésével: -Linux .[#toc-linux] -=================== +```js + mappings: { + // távoli elérési út: helyi elérési út + '/var/www/nette.app': 'W:\\Nette.web\\_web', + } +``` + + +Linux +===== -1. Töltse le a megfelelő fájlokat "a Tracy repositoryból":https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux a `~/bin` könyvtárba. +1. Töltse le a megfelelő fájlokat [a Tracy repositoryból |https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux] a `~/bin` könyvtárba. -2. Szerkessze a `open-editor.sh` címet, és vegye ki a `editor` változóból a szerkesztőhöz vezető elérési utat, vagy szerkessze azt: +2. Szerkessze az `open-editor.sh` fájlt, és távolítsa el a kommentet, és szükség esetén módosítsa a szerkesztő elérési útját az `editor` változóban. ```shell #!/bin/bash @@ -67,24 +76,25 @@ Linux .[#toc-linux] ... ``` -Tegye futtathatóvá: +Tegye a fájlt futtathatóvá: ```shell chmod +x ~/bin/open-editor.sh ``` -Ha az Ön által használt szerkesztő nem a csomagból van telepítve, a bináris verzióban valószínűleg nem lesz elérési útvonal a `$PATH` címen. Ez könnyen korrigálható. A `~/bin` könyvtárban hozzon létre egy szimlinket a szerkesztő binárisára. .[note] +.[note] +Ha a használt szerkesztő nincs csomagból telepítve, valószínűleg a binárisnak nem lesz elérési útja a $PATH-ban. Ezt egyszerűen javíthatja. A `~/bin` könyvtárban hozzon létre egy szimbolikus linket a szerkesztő binárisára. -3. Regisztrálja a `editor://` protokoll kezelőjét a rendszerben. +3. Regisztrálja az `editor://` protokoll kezelőjét a rendszerben. -Ez a `install.sh` futtatásával történik. A `open-editor.js` szkript mostantól a `editor://` protokollt fogja kiszolgálni. +Ezt az `install.sh` fájl futtatásával teheti meg. Az `open-editor.sh` szkript mostantól az `editor://` protokollt fogja kiszolgálni. -macOS .[#toc-macos] -=================== +macOS +===== -Az olyan szerkesztők, mint a PhpStorm, TextMate stb. lehetővé teszik a fájlok megnyitását egy speciális URL-en keresztül, amelyet csak be kell állítanod: +Az olyan szerkesztők, mint a PhpStorm, TextMate stb., lehetővé teszik a fájlok megnyitását egy speciális URL-en keresztül, amelyet csak be kell állítani: ```php // PhpStorm @@ -92,44 +102,43 @@ Tracy\Debugger::$editor = 'phpstorm://open?file=%file&line=%line'; // TextMate Tracy\Debugger::$editor = 'txmt://open/?url=file://%file&line=%line'; // MacVim -Tracy\Debugger::$editor = 'mvim://open/?url=file://%file&line=%line'; +Tracy\Debugger::$editor = 'mvim://open?url=file:///%file&line=%line'; // Visual Studio Code Tracy\Debugger::$editor = 'vscode://file/%file:%line'; ``` -Ha önálló Tracy-t használ, akkor a sort a `Tracy\Debugger::enable()`, ha Nette-t, akkor a `$configurator->enableTracy()` elé kell tenni a `Bootstrap.php`. +Ha önálló Tracy-t használ, illessze be a sort a `Tracy\Debugger::enable()` elé, ha Nette-t, akkor a `$configurator->enableTracy()` elé a `Bootstrap.php`-ban. -Sajnos a `create file` vagy a `fix it` műveletek nem működnek macOS alatt. +A `create file` vagy `fix it` akciók sajnos nem működnek macOS-en. -Demók .[#toc-demos] -=================== +Bemutatók +========= -Hiba javítása: +Hibajavítás: <iframe width="560" height="315" src="https://www.youtube.com/embed/3ITT4mC0Eq4?rel=0&showinfo=0" frameborder="0" allow="encrypted-media" allowfullscreen></iframe> -Új fájl létrehozása: +Fájl létrehozása: <iframe width="560" height="315" src="https://www.youtube.com/embed/AJ_FUivAGZQ?rel=0&showinfo=0" frameborder="0" allow="encrypted-media" allowfullscreen></iframe> -Hibaelhárítás .[#toc-troubleshooting] -===================================== +Hibaelhárítás +============= -- A Firefoxban előfordulhat, hogy [engedélyeznie |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] kell az egyéni protokollok végrehajtását az about:configban a `network.protocol-handler.expose.editor` beállításával a `false` és a `network.protocol-handler.expose-all` beállításával a `true` címre. Alapértelmezés szerint azonban engedélyezni kell. -- Ha nem működik minden azonnal, ne ess pánikba. Próbálja meg frissíteni az oldalt, indítsa újra a böngészőt vagy a számítógépet. Ennek segítenie kell. -- A javításhoz lásd [itt |https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/]: - Input Error: js" fájlt egy másik alkalmazáshoz társította, nem pedig a JScript motorhoz. +- Firefoxban szükség lehet a protokoll engedélyezésére a `network.protocol-handler.expose.editor` `false`-ra és a `network.protocol-handler.expose-all` `true`-ra [állítással |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] az about:config-ban. +- Ha nem megy azonnal, ne essen pánikba, és próbálja meg néhányszor frissíteni az oldalt, mielőtt a linkre kattintana. El fog indulni! +- Itt van egy [link |https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/] az esetleges hiba javítására: `Input Error: There is no script engine for file extension ".js"`, `Maybe you associated ".js" file to another app, not JScript engine.` illetve `a .js kiterjesztéshez nincs elérhető szkript motor`. -A Google Chrome 77-es verziójától kezdve már nem jelenik meg a "Az ilyen típusú linkeket mindig a társított alkalmazásban nyissa meg" jelölőnégyzet, ha a szerkesztő egy hivatkozáson keresztül nyílik meg. Megoldás Windows esetén: hozzon létre fájlt `fix.reg`: +A Google Chrome 77-es verziójától kezdve már nem látható a „Mindig nyissa meg az ilyen típusú linkeket a társított alkalmazásban” jelölőnégyzet, ha a szerkesztő egy linken keresztül van indítva. Megoldás Windowsra: hozzon létre egy `fix.reg` fájlt: ``` Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\URLWhitelist] "123"="editor://*" ``` -Importálja azt dupla kattintással, és indítsa újra a Chrome-ot. +Importálja dupla kattintással, és indítsa újra a Chrome böngészőt. -További problémák vagy kérdések esetén kérdezzen a [fórumon |https://forum.nette.org]. +Esetleges kérdésekkel vagy megjegyzésekkel kérjük, forduljon a [fórumhoz |https://forum.nette.org]. diff --git a/tracy/hu/recipes.texy b/tracy/hu/recipes.texy index da394e86cb..e5b0011950 100644 --- a/tracy/hu/recipes.texy +++ b/tracy/hu/recipes.texy @@ -1,14 +1,13 @@ -Receptek -******** +Útmutatók +********* -Tartalombiztonsági politika .[#toc-content-security-policy] -=========================================================== +Content Security Policy +======================= -Ha webhelye a Tartalombiztonsági házirendet használja, hozzá kell adnia a következőket `'nonce-<value>'` és a `'strict-dynamic'` címet a `script-src` címhez, hogy a Tracy megfelelően működjön. Néhány 3. bővítmény további direktívákat igényelhet. -A nonce nem támogatott a `style-src` direktívában, ha ezt a direktívát használja, akkor hozzá kell adnia a `'unsafe-inline'`, de ezt termelési üzemmódban kerülni kell. +Ha a webhelye Content Security Policy-t használ, hozzá kell adnia ugyanazt a `'nonce-<value>'`-t és `'strict-dynamic'`-ot a `script-src`-hez, hogy a Tracy megfelelően működjön. Néhány harmadik féltől származó kiegészítő további beállításokat igényelhet. A Nonce nem támogatott a `style-src` direktívában, ha ezt a direktívát használja, hozzá kell adnia az `'unsafe-inline'`-t, de éles/produkciós módban ezt kerülnie kell. -Konfigurációs példa a [Nette keretrendszerhez |nette:configuring]: +Konfigurációs példa a [Nette Frameworkhöz |nette:configuring]: ```neon http: @@ -16,7 +15,7 @@ http: script-src: [nonce, strict-dynamic] ``` -Példa tiszta PHP nyelven: +Példa tiszta PHP-ban: ```php $nonce = base64_encode(random_bytes(20)); @@ -24,11 +23,10 @@ header("Content-Security-Policy: script-src 'nonce-$nonce' 'strict-dynamic';"); ``` -Gyorsabb betöltés .[#toc-faster-loading] -======================================== +Gyorsabb betöltés +================= -Az alapvető integráció egyszerű, azonban ha lassú blokkoló szkriptek vannak a weboldalon, akkor ezek lelassíthatják a Tracy betöltését. -A megoldás az, hogy a `<?php Tracy\Debugger::renderLoader() ?>` a sablonba a szkriptek előtt: +Az indítás egyenes, de ha a weboldalán lassan betöltődő blokkoló szkriptek vannak, azok lassíthatják a Tracy betöltését. A megoldás az, hogy helyezze el a `<?php Tracy\Debugger::renderLoader() ?>` kódot a sablonjában minden szkript elé: ```latte <!DOCTYPE html> @@ -42,12 +40,37 @@ A megoldás az, hogy a `<?php Tracy\Debugger::renderLoader() ?>` a sablonba a sz ``` -AJAX és átirányított kérések .[#toc-ajax-and-redirected-requests] -================================================================= +AJAX kérések debuggolása +======================== -A Tracy megjelenítheti a hibakereső sávot és a Bluescreent az AJAX-kérések és átirányítások esetében. A Tracy saját munkameneteket hoz létre, az adatokat saját ideiglenes fájlokban tárolja, és a `tracy-session` cookie-t használja. +A Tracy automatikusan elkapja a jQuery vagy a natív `fetch` API segítségével létrehozott AJAX kéréseket. A kérések a Tracy sávban további sorokként jelennek meg, ami lehetővé teszi az AJAX könnyű és kényelmes debuggolását. -A Tracy úgy is beállítható, hogy natív PHP munkamenetet használjon, amely a Tracy bekapcsolása előtt indul el: +Ha nem szeretné automatikusan elkapni az AJAX kéréseket, letilthatja ezt a funkciót egy JavaScript változó beállításával: + +```js +window.TracyAutoRefresh = false; +``` + +Specifikus AJAX kérések kézi monitorozásához adjon hozzá egy `X-Tracy-Ajax` HTTP fejlécet a `Tracy.getAjaxHeader()` által visszaadott értékkel. Itt egy példa a `fetch` függvénnyel való használatra: + +```js +fetch(url, { + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Tracy-Ajax': Tracy.getAjaxHeader(), + } +}) +``` + +Ez a megközelítés lehetővé teszi az AJAX kérések szelektív debuggolását. + + +Adattároló +========== + +A Tracy képes paneleket megjeleníteni a Tracy bárban és Bluescreeneket AJAX kérésekhez és átirányításokhoz. A Tracy saját sessiont hoz létre, az adatokat saját ideiglenes fájlokban tárolja, és a `tracy-session` sütit használja. + +A Tracy úgy is konfigurálható, hogy a natív PHP sessiont használja, amelyet még a Tracy bekapcsolása előtt elindítunk: ```php session_start(); @@ -55,44 +78,44 @@ Debugger::setSessionStorage(new Tracy\NativeSession); Debugger::enable(); ``` -Abban az esetben, ha a munkamenet elindítása bonyolultabb inicializálást igényel, a Tracy-t azonnal elindíthatja (így az esetlegesen felmerülő hibákat kezelni tudja), majd inicializálja a munkamenetkezelőt, végül pedig a `dispatch()` függvény segítségével tájékoztatja a Tracy-t, hogy a munkamenet készen áll a használatra: +Abban az esetben, ha a session indítása bonyolultabb inicializálást igényel, a Tracy-t azonnal elindíthatja (hogy feldolgozhassa az esetlegesen keletkezett hibákat), majd inicializálhatja a munkamenet kezelőt, és végül tájékoztathatja a Tracy-t, hogy a munkamenet használatra kész a `dispatch()` függvény segítségével: ```php Debugger::setSessionStorage(new Tracy\NativeSession); Debugger::enable(); -// amelyet a munkamenet inicializálása követ -// és a munkamenet elindítása +// következik a session inicializálása +// és a session indítása session_start(); Debugger::dispatch(); ``` -A `setSessionStorage()` függvény a 2.9-es verzió óta létezik, előtte a Tracy mindig a natív PHP munkamenetet használta. +A `setSessionStorage()` függvény a 2.9-es verzió óta létezik, korábban a Tracy mindig a natív PHP sessiont használta. -Egyéni Scrubber .[#toc-custom-scrubber] -======================================= +Egyéni scrubber +=============== -A Scrubber egy olyan szűrő, amely megakadályozza, hogy érzékeny adatok szivárogjanak ki a dumps-okból, például jelszavak vagy hitelesítő adatok. A szűrő a dumpolt tömb vagy objektum minden egyes elemére meghívódik, és a `true` értéket adja vissza, ha az érték érzékeny. Ebben az esetben az érték helyett a `*****` kerül kiírásra. +A Scrubber egy szűrő, amely megakadályozza az érzékeny adatok, például jelszavak vagy hozzáférési adatok kiszivárgását a dumpolás során. A szűrő minden dumpolt tömb vagy objektum elemre meghívódik, és `true`-t ad vissza, ha az érték érzékeny. Ebben az esetben az érték helyett `*****` íródik ki. ```php -// elkerüli a kulcsértékek és az olyan tulajdonságok, mint a `password` dömpingjét, -// `password_repeat`, `check_password`, `DATABASE_PASSWORD`, stb. +// megakadályozza a `password`, `password_repeat`, `check_password`, +// `DATABASE_PASSWORD`, stb. kulcsok és property-k értékeinek kiírását $scrubber = function(string $key, $value, ?string $class): bool { return preg_match('#password#i', $key) && $value !== null; }; -// ezt használjuk a BlueScreen-en belüli összes dömperhez. +// használjuk minden dumpra a BlueScreenen belül Tracy\Debugger::getBlueScreen()->scrubber = $scrubber; ``` -Egyéni naplózó .[#toc-custom-logger] -==================================== +Egyéni logger +============= -Létrehozhatunk egy egyéni naplózót a hibák és a nem fogadott kivételek naplózására, valamint a `Tracy\Debugger::log()` által történő meghívásra. A logger a [api:Tracy\ILogger] interfészt valósítja meg. +Létrehozhatunk saját loggert, amely naplózza a hibákat, az el nem kapott kivételeket, és amelyet a `Tracy\Debugger::log()` metódus is meghív. A logger implementálja az [api:Tracy\ILogger] interfészt. ```php use Tracy\ILogger; @@ -101,18 +124,18 @@ class SlackLogger implements ILogger { public function log($value, $priority = ILogger::INFO) { - // kérést küld a Slacknek + // kérést küld a Slack-re } } ``` -Ezután aktiváljuk: +Majd aktiváljuk: ```php Tracy\Debugger::setLogger(new SlackLogger); ``` -Ha a teljes Nette keretrendszert használjuk, akkor a NEON konfigurációs fájlban állíthatjuk be: +Ha a teljes Nette Frameworköt használjuk, beállíthatjuk a NEON konfigurációs fájlban: ```neon services: @@ -120,10 +143,10 @@ services: ``` -Monolog integráció .[#toc-monolog-integration] ----------------------------------------------- +Monolog integráció +------------------ -A Tracy csomag egy PSR-3 adaptert biztosít, amely lehetővé teszi a [monolog/monolog](https://github.com/Seldaek/monolog) integrálását. +A Tracy csomag egy PSR-3 adaptert biztosít, amely lehetővé teszi a [monolog/monolog |https://github.com/Seldaek/monolog] integrációját. ```php $monolog = new Monolog\Logger('main-channel'); @@ -133,21 +156,21 @@ $tracyLogger = new Tracy\Bridges\Psr\PsrToTracyLoggerAdapter($monolog); Debugger::setLogger($tracyLogger); Debugger::enable(); -Debugger::log('info'); // írja: [<TIMESTAMP>] main-channel.INFO: info [] [] [] -Debugger::log('warning', Debugger::WARNING); // writes: [<TIMESTAMP>] main-channel.WARNING: warning [] [] [] +Debugger::log('info'); // writes: [<TIMESTAMP>] main-channel.INFO: info [] [] +Debugger::log('warning', Debugger::WARNING); // writes: [<TIMESTAMP>] main-channel.WARNING: warning [] [] ``` -nginx .[#toc-nginx] -=================== +nginx +===== -Ha a Tracy nem működik az nginx-en, akkor valószínűleg rosszul van beállítva. Ha van valami ilyesmi, mint +Ha nem működik a Tracy az nginx szerveren, valószínűleg rosszul van konfigurálva. Ha a konfigurációban valami ilyesmi van: ```nginx try_files $uri $uri/ /index.php; ``` -változtassa meg +változtassa erre: ```nginx try_files $uri $uri/ /index.php$is_args$args; diff --git a/tracy/hu/stopwatch.texy b/tracy/hu/stopwatch.texy index dda55a49d4..1c8524647e 100644 --- a/tracy/hu/stopwatch.texy +++ b/tracy/hu/stopwatch.texy @@ -1,35 +1,35 @@ -Stopperóra -********** +Időmérés +******** -Egy másik hasznos eszköz a debugger stopperórája, amely mikroszekundumos pontossággal működik: +A debugger másik hasznos eszköze a mikroszekundumos pontosságú stopperóra: ```php Debugger::timer(); -// sweet dreams my cherrie +// aludj kicsi hercegem, a madárkák már édesen alszanak... sleep(2); $elapsed = Debugger::timer(); // $elapsed = 2 ``` -Egyszerre több mérés is elvégezhető egy opcionális paraméterrel. +Egy opcionális paraméterrel többszörös mérést lehet elérni. ```php Debugger::timer('page-generating'); -// némi kód +// valamilyen kód Debugger::timer('rss-generating'); -// néhány kód +// valamilyen kód $rssElapsed = Debugger::timer('rss-generating'); $pageElapsed = Debugger::timer('page-generating'); ``` ```php -Debugger::timer(); // az időzítő futása +Debugger::timer(); // bekapcsolja a stoppert -... // valamilyen időigényes művelet +... // időigényes művelet -echo Debugger::timer(); // az eltelt idő másodpercben kifejezve +echo Debugger::timer(); // kiírja az eltelt időt másodpercben ``` diff --git a/tracy/it/@home.texy b/tracy/it/@home.texy index 681e993582..3bfc1f5186 100644 --- a/tracy/it/@home.texy +++ b/tracy/it/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Tracy - Uno strumento di debug indispensabile per tutti gli sviluppatori PHP}} -{{description: Tracy è uno strumento progettato per facilitare il debug del codice PHP. È un utile assistente per tutti i programmatori PHP, che aiuta a visualizzare e registrare chiaramente gli errori, a scaricare le variabili e molto altro ancora. Attenzione: Tracy crea dipendenza!}} +{{maintitle: Tracy – lo strumento di debug con cui è un piacere sbagliare}} +{{description: Tracy è uno strumento progettato per facilitare il debug del codice PHP. È un utile assistente per tutti i programmatori PHP, che aiuta con la visualizzazione e il logging degli errori, con il dump delle variabili e molto altro. Attenzione: Tracy crea dipendenza!}} diff --git a/tracy/it/@left-menu.texy b/tracy/it/@left-menu.texy index ba6b263082..a0b89ff043 100644 --- a/tracy/it/@left-menu.texy +++ b/tracy/it/@left-menu.texy @@ -1,7 +1,7 @@ -- [Per iniziare |Guide] -- [Dumper |Dumper] -- [Cronometro |Stopwatch] -- [Configurazione |Configuring] -- [Ricette |Recipes] -- [Integrazione dell'IDE |open-files-in-ide] -- [Creare estensioni |extensions] +- [Iniziare con Tracy |guide] +- [Dump |dumper] +- [Misurazione del tempo |stopwatch] +- [Configurazione |configuring] +- [Tutorial |recipes] +- [Integrazione con IDE |open-files-in-ide] +- [Creazione di estensioni |extensions] diff --git a/tracy/it/@menu.texy b/tracy/it/@menu.texy index 357474e581..7d3b8edbc5 100644 --- a/tracy/it/@menu.texy +++ b/tracy/it/@menu.texy @@ -1,3 +1,3 @@ -- [Casa |@home] -- [Documentazione |Guide] +- [Introduzione |@home] +- [Documentazione |guide] - "GitHub .[link-external]":https://github.com/nette/tracy diff --git a/tracy/it/@meta.texy b/tracy/it/@meta.texy new file mode 100644 index 0000000000..80acea0506 --- /dev/null +++ b/tracy/it/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentazione Tracy}} diff --git a/tracy/it/configuring.texy b/tracy/it/configuring.texy index b5ff5a2b98..3a86acd347 100644 --- a/tracy/it/configuring.texy +++ b/tracy/it/configuring.texy @@ -1,75 +1,75 @@ -Configurazione Tracy -******************** +Configurazione di Tracy +*********************** -Gli esempi seguenti presuppongono che sia definito il seguente alias di classe: +Tutti gli esempi presuppongono la creazione di un alias: ```php use Tracy\Debugger; ``` -Registrazione degli errori .[#toc-error-logging] ------------------------------------------------- +Logging degli errori +-------------------- ```php $logger = Debugger::getLogger(); -// se si è verificato un errore, la notifica viene inviata a questa email -$logger->email = 'dev@example.com'; // (string|string[]) predefinito come non impostato +// e-mail a cui vengono inviate le notifiche in caso di errore +$logger->email = 'dev@example.com'; // (string|string[]) il default è non impostato // mittente dell'e-mail -$logger->fromEmail = 'me@example.com'; // (string) predefinito come non impostato +$logger->fromEmail = 'me@example.com'; // (string) il default è non impostato -// routine per l'invio di e-mail -$logger->mailer = /* ... */; // (richiamabile) predefinita per l'invio tramite mail() +// routine che garantisce l'invio dell'email +$logger->mailer = /* ... */; // (callable) il default è l'invio tramite la funzione mail() -// dopo quanto tempo inviare un'altra email? -$logger->emailSnooze = /* ... */; // (string) l'impostazione predefinita è "2 giorni". +// dopo quanto tempo minimo inviare la prossima email? +$logger->emailSnooze = /* ... */; // (string) il default è '2 days' -// per quali livelli di errore viene registrato anche BlueScreen? -Debugger::$logSeverity = E_WARNING | E_NOTICE; // predefinito a 0 (nessun livello di errore) +// per quali livelli di errore viene registrato anche il BlueScreen? +Debugger::$logSeverity = E_WARNING | E_NOTICE; // il default è 0 (nessun livello di errore) ``` -`dump()` Comportamento ----------------------- +Comportamento di `dump()` +------------------------- ```php // lunghezza massima della stringa -Debugger::$maxLength = 150; // (int) predefinito secondo Tracy +Debugger::$maxLength = 150; // (int) default secondo la versione di Tracy -// profondità dell'elenco -Debugger::$maxDepth = 10; // (int) predefinito secondo Tracy +// profondità massima di annidamento +Debugger::$maxDepth = 10; // (int) default secondo la versione di Tracy // nascondere i valori di queste chiavi (da Tracy 2.8) -Debugger::$keysToHide = ['password', /* ... */]; // (string[]) predefinito a [] +Debugger::$keysToHide = ['password', /* ... */]; // (string[]) il default è [] // tema visivo (da Tracy 2.8) -Debugger::$dumpTheme = 'dark'; // (light|dark) predefinito a 'light' +Debugger::$dumpTheme = 'dark'; // (light|dark) il default è 'light' -// mostra la posizione in cui è stato chiamato dump()? -Debugger::$showLocation = /* ... */; // (bool) predefinito secondo Tracy +// mostrare il punto in cui è stata chiamata la funzione dump()? +Debugger::$showLocation = /* ... */; // (bool) default secondo la versione di Tracy ``` -Altri .[#toc-others] --------------------- +Altro +----- ```php -// in modalità Sviluppo, si vedranno gli avvisi di avviso o di errore come BlueScreen -Debugger::$strictMode = /* ... */; // (bool|int) predefinito a false, si possono selezionare solo livelli di errore specifici (ad esempio E_USER_DEPRECATED | E_DEPRECATED) +// in modalità sviluppo, mostra gli errori di tipo notice o warning come BlueScreen +Debugger::$strictMode = /* ... */; // (bool|int) il default è false, è possibile selezionare solo alcuni livelli di errore (es. E_USER_DEPRECATED | E_DEPRECATED) -// visualizza i messaggi di errore silenziosi (@) -Debugger::$scream = /* ... */; // (bool|int) predefinito a false, dalla versione 2.9 è possibile selezionare solo livelli di errore specifici (ad es. E_USER_DEPRECATED | E_DEPRECATED) +// mostrare i messaggi di errore soppressi (@)? +Debugger::$scream = /* ... */; // (bool|int) il default è false, dalla versione 2.9 è possibile selezionare solo alcuni livelli di errore (es. E_USER_DEPRECATED | E_DEPRECATED) -// formato del collegamento da aprire nell'editor -Debugger::$editor = /* ... */; // (string|null) predefinito a "editor://open/?file=%file&line=%line". +// formato del link per l'apertura nell'editor +Debugger::$editor = /* ... */; // (string|null) il default è 'editor://open/?file=%file&line=%line' // percorso del template con la pagina personalizzata per l'errore 500 -Debugger::$errorTemplate = /* ... */; // (string) predefinito a non impostato +Debugger::$errorTemplate = /* ... */; // (string) il default è non impostato -// mostra la barra Tracy? -Debugger::$showBar = /* ... */; // (bool) predefinito a true +// mostrare la Tracy Bar? +Debugger::$showBar = /* ... */; // (bool) il default è true Debugger::$editorMapping = [ // originale => nuovo @@ -79,64 +79,63 @@ Debugger::$editorMapping = [ ``` -Struttura Nette .[#toc-nette-framework] ---------------------------------------- +Nette Framework +--------------- -Se si utilizza Nette Framework, è possibile configurare Tracy e aggiungere nuovi pannelli alla barra Tracy utilizzando il file di configurazione. -È possibile impostare i parametri di Tracy nella configurazione e aggiungere nuovi pannelli alla barra Tracy. Queste impostazioni vengono applicate solo dopo la creazione del contenitore DI, quindi gli errori verificatisi in precedenza non possono riflettersi. +Se si utilizza Nette Framework, è possibile configurare Tracy e aggiungere nuovi pannelli alla Tracy Bar anche tramite il file di configurazione. Nella configurazione è possibile impostare parametri e aggiungere nuovi pannelli alla Tracy Bar. Queste impostazioni vengono applicate solo dopo la creazione del container DI, quindi gli errori verificatisi prima non possono rifletterle. -Configurazione della registrazione degli errori: +Configurazione del logging degli errori: ```neon tracy: - # se si è verificato un errore, la notifica viene inviata a questa email - email: dev@example.com # (string|string[]) predefinito a non impostato + # e-mail a cui vengono inviate le notifiche in caso di errore + email: dev@example.com # (string|string[]) il default è non impostato - # mittente dell'email - fromEmail: robot@example.com # (string) predefinito a unset + # mittente dell'e-mail + fromEmail: robot@example.com # (string) il default è non impostato - # periodo di rinvio dell'invio delle email (da Tracy 2.8.8) - emailSnooze: ... # (string) predefinito a '2 giorni'. + # periodo di rinvio dell'invio delle e-mail (da Tracy 2.8.8) + emailSnooze: ... # (string) il default è '2 days' - # utilizzare un mailer definito nella configurazione? (da Tracy 2.5) - netteMailer: ... # (bool) predefinito a true + # utilizzare Nette mailer per l'invio delle e-mail? (da Tracy 2.5) + netteMailer: ... # (bool) il default è true - # per quali livelli di errore viene registrato anche BlueScreen? - logSeverity: [E_WARNING, E_NOTICE] # predefinito a [] + # per quali livelli di errore viene registrato anche il BlueScreen? + logSeverity: [E_WARNING, E_NOTICE] # il default è [] ``` -Configurazione per la funzione `dump()`: +Configurazione del comportamento della funzione `dump()`: ```neon tracy: # lunghezza massima della stringa - maxLength: 150 # (int) predefinito secondo Tracy + maxLength: 150 # (int) default secondo la versione di Tracy - # quanta profondità avrà l'elenco - maxDepth: 10 # (int) predefinito secondo Tracy + # profondità massima di annidamento + maxDepth: 10 # (int) default secondo la versione di Tracy # nascondere i valori di queste chiavi (da Tracy 2.8) - keysToHide: [password, pass] # (string[]) predefinito a [] + keysToHide: [password, pass] # (string[]) il default è [] # tema visivo (da Tracy 2.8) - dumpTheme: dark # (light|dark) predefinito a "light". + dumpTheme: dark # (light|dark) il default è 'light' - # mostra la posizione in cui è stato chiamato dump()? - showLocation: ... # (bool) predefinito secondo Tracy + # mostrare il punto in cui è stata chiamata la funzione dump()? + showLocation: ... # (bool) default secondo la versione di Tracy ``` -Per installare l'estensione Tracy: +Installazione delle estensioni di Tracy: ```neon tracy: - # aggiunge le barre alla barra Tracy + # aggiunge pannelli alla Tracy Bar bar: - Nette\Bridges\DITracy\ContainerPanel - IncludePanel - XDebugHelper('myIdeKey') - MyPanel(@MyService) - # aggiunge pannelli a BlueScreen + # aggiunge pannelli al BlueScreen blueScreen: - DoctrinePanel::renderException ``` @@ -145,20 +144,20 @@ Altre opzioni: ```neon tracy: - # in modalità Sviluppo, si vedranno gli avvisi di avviso o di errore come BlueScreen - strictMode: ... # predefinito a true + # in modalità sviluppo, mostra gli errori di tipo notice o warning come BlueScreen + strictMode: ... # il default è true - # visualizza i messaggi di errore silenziosi (@) - scream: ... # predefinito a false + # mostrare i messaggi di errore soppressi (@)? + scream: ... # il default è false - # formato del collegamento da aprire nell'editor - editor: ... # (string) predefinito a "editor://open/?file=%file&line=%line". + # formato del link per l'apertura nell'editor + editor: ... # (string) il default è 'editor://open/?file=%file&line=%line' # percorso del template con la pagina personalizzata per l'errore 500 - errorTemplate: ... # (string) predefinito a unset + errorTemplate: ... # (string) il default è non impostato - # mostra la barra Tracy? - showBar: ... # (bool) predefinito a true + # mostrare la Tracy Bar? + showBar: ... # (bool) il default è true editorMapping: # originale: nuovo @@ -166,4 +165,16 @@ tracy: /home/web: /srv/html ``` -I valori delle opzioni `logSeverity`, `strictMode` e `scream` possono essere scritti come un array di livelli di errore (ad es. `[E_WARNING, E_NOTICE]`) o come espressione utilizzata in PHP (ad esempio `E_ALL & ~E_NOTICE`). +I valori delle opzioni `logSeverity`, `strictMode` e `scream` possono essere scritti come un array di livelli di errore (es. `[E_WARNING, E_NOTICE]`), o come un'espressione utilizzata nel linguaggio PHP (es. `E_ALL & ~E_NOTICE`). + + +Servizi DI +---------- + +Questi servizi vengono aggiunti al container DI: + +| Nome | Tipo | Descrizione +|---------------------------------------------------------- +| `tracy.logger` | [api:Tracy\ILogger] | logger +| `tracy.blueScreen` | [api:Tracy\BlueScreen] | BlueScreen +| `tracy.bar` | [api:Tracy\Bar] | Tracy Bar diff --git a/tracy/it/dumper.texy b/tracy/it/dumper.texy index 3cf231de82..d7d8d1cc1e 100644 --- a/tracy/it/dumper.texy +++ b/tracy/it/dumper.texy @@ -1,10 +1,10 @@ -Dumper -****** +Dump +**** -Ogni sviluppatore di debug è un buon amico della funzione `var_dump`, che elenca in dettaglio tutti i contenuti di qualsiasi variabile. Purtroppo, il suo output è privo di formattazione HTML e produce il dump in una singola riga di codice HTML, senza contare l'escape del contesto. È necessario sostituire `var_dump` con una funzione più pratica. È proprio questo il caso di `dump()`. +Ogni debugger è un buon amico della funzione [php:var_dump], che stampa dettagliatamente il contenuto di una variabile. Sfortunatamente, in un ambiente HTML, l'output perde la formattazione e si fonde in un'unica riga, per non parlare della sanificazione del codice HTML. In pratica, è necessario sostituire `var_dump` con una funzione più pratica. Questa è `dump()`. ```php -$arr = [10, 20.2, true, null, 'ciao']; +$arr = [10, 20.2, true, null, 'hello']; dump($arr); // o Debugger::dump($arr); @@ -14,7 +14,7 @@ genera l'output: [* dump-basic.webp *] -È possibile modificare il tema chiaro predefinito in scuro: +È possibile cambiare il tema chiaro predefinito in uno scuro: ```php Debugger::$dumpTheme = 'dark'; @@ -22,27 +22,27 @@ Debugger::$dumpTheme = 'dark'; [* dump-dark.webp *] -È inoltre possibile modificare la profondità di annidamento con `Debugger::$maxDepth` e la lunghezza delle stringhe visualizzate con `Debugger::$maxLength`. Naturalmente, valori più bassi accelerano il rendering di Tracy. +Inoltre, possiamo modificare la profondità di annidamento utilizzando [Debugger::$maxDepth |api:Tracy\Debugger::$maxDepth] e la lunghezza delle etichette visualizzate utilizzando [Debugger::$maxLength |api:Tracy\Debugger::$maxLength]. Valori più bassi accelereranno naturalmente Tracy. ```php -Debugger::$maxDepth = 2; // valore predefinito: 3 -Debugger::$maxLength = 50; // valore predefinito: 150 +Debugger::$maxDepth = 2; // default: 3 +Debugger::$maxLength = 50; // default: 150 ``` -La funzione `dump()` può visualizzare altre informazioni utili. `Tracy\Dumper::LOCATION_SOURCE` aggiunge una descrizione del percorso del file in cui è stata chiamata la funzione. `Tracy\Dumper::LOCATION_LINK` aggiunge un collegamento al file. `Tracy\Dumper::LOCATION_CLASS` aggiunge una descrizione di ogni oggetto scaricato contenente il percorso del file in cui è definita la classe dell'oggetto. Tutte queste costanti possono essere impostate nella variabile `Debugger::$showLocation` prima di chiamare la funzione `dump()`. È possibile impostare più valori contemporaneamente usando l'operatore `|`. +La funzione `dump()` può anche stampare altre informazioni utili. La costante `Tracy\Dumper::LOCATION_SOURCE` aggiunge un tooltip con il percorso del punto in cui la funzione è stata chiamata. `Tracy\Dumper::LOCATION_LINK` ci fornisce un link a quella posizione. `Tracy\Dumper::LOCATION_CLASS` stampa un tooltip per ogni oggetto dumpato con il percorso del file in cui è definita la sua classe. Le costanti vengono impostate nella variabile `Debugger::$showLocation` prima di chiamare `dump()`. Se vogliamo impostare più valori contemporaneamente, li combiniamo usando l'operatore `|`. ```php -Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // Mostra il percorso in cui è stato chiamato il dump() -Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Mostra sia il percorso delle classi che il link al punto in cui è stato chiamato il dump() -Debugger::$showLocation = false; // Nasconde le informazioni aggiuntive sul percorso -Debugger::$showLocation = true; // Mostra tutte le informazioni supplementari sulla localizzazione +Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // Imposta solo la stampa sulla posizione della chiamata della funzione +Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Imposta contemporaneamente la stampa del link e il percorso della classe +Debugger::$showLocation = false; // Disattiva la stampa delle informazioni aggiuntive +Debugger::$showLocation = true; // Attiva la stampa di tutte le informazioni aggiuntive ``` -Un'alternativa molto comoda a `dump()` è rappresentata da `dumpe()` (cioè dump and exit) e `bdump()`. Questi permettono di eseguire il dump delle variabili in Tracy Bar. È utile perché i dump non rovinano l'output e si può anche aggiungere un titolo al dump. +Un'alternativa pratica a `dump()` è `dumpe()` (dump & exit) e `bdump()`. Questo ci permette di stampare il valore di una variabile nel pannello della Tracy Bar. Questo è molto utile, poiché i dump sono separati dal layout della pagina e possiamo anche aggiungere un commento ad essi. ```php -bdump([2, 4, 6, 8], 'even numbers up to ten'); -bdump([1, 3, 5, 7, 9], 'odd numbers up to ten'); +bdump([2, 4, 6, 8], 'numeri pari fino a dieci'); +bdump([1, 3, 5, 7, 9], 'numeri dispari fino a dieci'); ``` -[* bardump-en.webp *] +[* bardump-cs.webp *] diff --git a/tracy/it/extensions.texy b/tracy/it/extensions.texy index 2af65aaf40..e0f87a8799 100644 --- a/tracy/it/extensions.texy +++ b/tracy/it/extensions.texy @@ -1,23 +1,23 @@ -Creare le estensioni di Tracy -***************************** +Creazione di estensioni per Tracy +********************************* <div class=perex> -Tracy è un ottimo strumento per il debug dell'applicazione. Tuttavia, a volte sono necessarie più informazioni di quelle offerte da Tracy. Imparerete a conoscere: +Tracy fornisce un ottimo strumento per il debug della tua applicazione. A volte, però, vorresti avere a portata di mano anche altre informazioni. Vediamo come scrivere la tua estensione personalizzata per la Tracy Bar per rendere lo sviluppo ancora più piacevole. -- Creare i propri pannelli della barra Tracy -- Creare estensioni Bluescreen personalizzate +- Creazione di un pannello personalizzato per la Tracy Bar +- Creazione di un'estensione personalizzata per il Bluescreen </div> .[tip] -Potete trovare estensioni utili per Tracy su "Componette":https://componette.org/search/tracy. +Il repository delle estensioni Tracy già pronte si trova su "Componette":https://componette.org/search/tracy. -Estensioni della barra Tracy .[#toc-tracy-bar-extensions] -========================================================= +Estensioni per la Tracy Bar +=========================== -Creare una nuova estensione per Tracy Bar è semplice. È necessario implementare l'interfaccia `Tracy\IBarPanel` con i metodi `getTab()` e `getPanel()`. I metodi devono restituire il codice HTML di una scheda (piccola etichetta sulla barra Tracy) e di un pannello (pop-up visualizzato dopo aver fatto clic sulla scheda). Se `getPanel()` non restituisce nulla, verrà visualizzata solo la scheda. Se `getTab()` non restituisce nulla, non viene visualizzato nulla e `getPanel()` non verrà richiamato. +Creare una nuova estensione per la Tracy Bar non è complicato. Si crea un oggetto che implementa l'interfaccia `Tracy\IBarPanel`, che ha due metodi `getTab()` e `getPanel()`. I metodi devono restituire il codice HTML della scheda (una piccola etichetta visualizzata direttamente sulla Bar) e del pannello. Se `getPanel()` non restituisce nulla, viene visualizzata solo l'etichetta stessa. Se `getTab()` non restituisce nulla, non viene visualizzato nulla e `getPanel()` non viene più chiamato. ```php class ExamplePanel implements Tracy\IBarPanel @@ -35,16 +35,16 @@ class ExamplePanel implements Tracy\IBarPanel ``` -Registrazione .[#toc-registration] ----------------------------------- +Registrazione +------------- -La registrazione si effettua chiamando `Tracy\Bar::addPanel()`: +La registrazione viene effettuata tramite `Tracy\Bar::addPanel()`: ```php Tracy\Debugger::getBar()->addPanel(new ExamplePanel); ``` -oppure si può semplicemente registrare il pannello nella configurazione dell'applicazione: +Oppure puoi registrare il pannello direttamente nella configurazione dell'applicazione: ```neon tracy: @@ -53,70 +53,70 @@ tracy: ``` -Codice HTML della scheda .[#toc-tab-html-code] ----------------------------------------------- +Codice HTML della scheda +------------------------ -Dovrebbe avere un aspetto simile a questo: +Dovrebbe assomigliare approssimativamente a questo: ```latte -<span title="Explaining tooltip"> +<span title="Etichetta esplicativa"> <svg>...</svg> - <span class="tracy-label">Title</span> + <span class="tracy-label">Titolo</span> </span> ``` -L'immagine deve essere in formato SVG. Se non si ha bisogno del tooltip, si può tralasciare `<span>` fuori. +L'immagine dovrebbe essere in formato SVG. Se l'etichetta esplicativa non è necessaria, `<span>` può essere omesso. -Codice HTML del pannello .[#toc-panel-html-code] ------------------------------------------------- +Codice HTML del pannello +------------------------ -Dovrebbe avere un aspetto simile a questo: +Dovrebbe assomigliare approssimativamente a questo: ```latte -<h1>Title</h1> +<h1>Titolo</h1> <div class="tracy-inner"> <div class="tracy-inner-container"> - ... content ... + ... contenuto ... </div> </div> ``` -Il titolo deve essere lo stesso della scheda o contenere informazioni aggiuntive. +Il titolo dovrebbe essere lo stesso del titolo della scheda, oppure può contenere informazioni aggiuntive. -Un'estensione può essere registrata più volte, quindi si raccomanda di non usare l'attributo `id` per lo stile. Si possono usare le classi, preferibilmente nel formato `tracy-addons-<class-name>[-<optional>]` nel formato Quando si creano i CSS, è meglio usare `#tracy-debug .class`, perché tale regola ha una priorità maggiore rispetto a reset. +È necessario tenere conto del fatto che un'estensione può essere registrata più volte, ad esempio con impostazioni diverse, quindi per lo stile non è possibile utilizzare ID CSS, ma solo classi, nella forma `tracy-addons-<NomeClasse>[-<opzionale>]`. Quindi scrivi la classe nel div insieme alla classe `tracy-inner`. Quando si scrive CSS, è utile scrivere `#tracy-debug .classe`, perché la regola avrà quindi una priorità maggiore rispetto al reset. -Stili predefiniti .[#toc-default-styles] ----------------------------------------- +Stili predefiniti +----------------- -Nel pannello, gli elementi `<a>`, `<table>`, `<pre>`, `<code>` hanno stili predefiniti. Per creare un collegamento per nascondere o visualizzare altri elementi, collegarli con gli attributi `href` e `id` e la classe `tracy-toggle`. +Nel pannello, `<a>`, `<table>`, `<pre>`, `<code>` sono pre-stilizzati. Se vuoi creare un link che nasconde e mostra un altro elemento, collegali con gli attributi `href` e `id` e la classe `tracy-toggle`: ```latte -<a href="#tracy-addons-className-{$counter}" class="tracy-toggle">Detail</a> +<a href="#tracy-addons-NazevTridy-{$counter}" class="tracy-toggle">Dettagli</a> -<div id="tracy-addons-className-{$counter}">...</div> +<div id="tracy-addons-NazevTridy-{$counter}">...</div> ``` -Se lo stato predefinito è collassato, aggiungere la classe `tracy-collapsed` a entrambi gli elementi. +Se lo stato predefinito deve essere compresso, aggiungi la classe `tracy-collapsed` a entrambi gli elementi. -Utilizzare un contatore statico per evitare la duplicazione degli ID in una pagina. +Utilizza un contatore statico per evitare la creazione di ID duplicati sulla stessa pagina. -Estensioni Bluescreen .[#toc-bluescreen-extensions] -=================================================== +Estensioni per il Bluescreen +============================ -È possibile aggiungere visualizzazioni o pannelli di eccezioni personalizzati, che appariranno sul bluescreen. +In questo modo è possibile aggiungere visualizzazioni personalizzate delle eccezioni o pannelli che verranno visualizzati sul bluescreen. -L'estensione è fatta in questo modo: +L'estensione viene creata con questo comando: ```php -Tracy\Debugger::getBlueScreen()->addPanel(function (?Throwable $e) { // catched exception +Tracy\Debugger::getBlueScreen()->addPanel(function (?Throwable $e) { // eccezione catturata return [ - 'tab' => '...Title...', - 'panel' => '...content...', + 'tab' => '...Etichetta...', + 'panel' => '...Codice HTML del pannello...', ]; }); ``` -La funzione viene chiamata due volte, prima viene passata l'eccezione stessa nel parametro `$e` e il pannello restituito viene reso all'inizio della pagina. Se non viene restituito nulla, il pannello non viene reso. Quindi viene richiamata con il parametro `null` e il pannello restituito viene reso sotto il callstack. Se la funzione restituisce `'bottom' => true` nell'array, il pannello viene reso in fondo. +La funzione viene chiamata due volte, prima viene passata l'eccezione stessa nel parametro `$e` e il pannello restituito viene renderizzato all'inizio della pagina. Se non restituisce nulla, il pannello non viene renderizzato. Successivamente viene chiamata con il parametro `null` e il pannello restituito viene renderizzato sotto il callstack. Se la funzione restituisce la chiave `'bottom' => true` nell'array, il pannello viene renderizzato in fondo. diff --git a/tracy/it/guide.texy b/tracy/it/guide.texy index 4657d2e8d7..fe56e9b84a 100644 --- a/tracy/it/guide.texy +++ b/tracy/it/guide.texy @@ -1,213 +1,212 @@ -Come iniziare con Tracy -*********************** +Iniziare con Tracy +****************** <div class=perex> -La libreria Tracy è un utile strumento per i programmatori PHP di tutti i giorni. Aiuta a: +La libreria Tracy è un utile aiuto quotidiano per il programmatore PHP. Ti aiuterà a: - individuare e correggere rapidamente gli errori -- registrare gli errori -- scaricare le variabili -- misurare il tempo di esecuzione di script/query -- vedere il consumo di memoria +- loggare gli errori +- stampare le variabili +- misurare il tempo degli script e delle query del database +- monitorare l'utilizzo della memoria </div> -PHP è un linguaggio perfetto per la creazione di errori difficilmente rilevabili, perché offre una grande flessibilità ai programmatori. Per questo motivo, TracyDebugger è ancora più prezioso. È uno strumento di ultima generazione tra quelli diagnostici. +PHP è un linguaggio fatto apposta per creare errori difficili da individuare, poiché offre agli sviluppatori una notevole libertà. Questo rende lo strumento di debug Tracy ancora più prezioso. Tra gli strumenti diagnostici per PHP, rappresenta l'apice assoluto. -Se incontrate Tracy per la prima volta, credetemi, la vostra vita inizierà a dividersi in una prima di Tracy e una con lei. Benvenuti nella parte migliore! +Se oggi incontri Tracy per la prima volta, credimi, la tua vita inizierà a dividersi tra quella prima di Tracy e quella con lei. Benvenuto nella parte migliore! -Installazione e requisiti .[#toc-installation-and-requirements] -=============================================================== +Installazione +============= -Il modo migliore per installare Tracy è [scaricare l'ultimo pacchetto](https://github.com/nette/tracy/releases) o usare Composer: +Il modo migliore per installare Tracy è [scaricare l'ultimo pacchetto |https://github.com/nette/tracy/releases], o usare Composer: ```shell composer require tracy/tracy ``` -In alternativa, è possibile scaricare l'intero pacchetto o il file [tracy.phar |https://github.com/nette/tracy/releases]. +Puoi anche scaricare l'intero pacchetto come file [tracy.phar |https://github.com/nette/tracy/releases]. -Utilizzo .[#toc-usage] -====================== +Utilizzo +======== -Tracy viene attivato chiamando il metodo `Tracy\Debugger::enable()' il prima possibile all'inizio del programma, prima che venga inviato qualsiasi output: +Attiviamo Tracy chiamando il metodo `Tracy\Debugger::enable()` il prima possibile all'inizio del programma, prima di inviare qualsiasi output: ```php use Tracy\Debugger; -require 'vendor/autoload.php'; // in alternativa tracy.phar +require 'vendor/autoload.php'; // o tracy.phar Debugger::enable(); ``` -La prima cosa che noterete nella pagina è la barra Tracy nell'angolo in basso a destra. Se non la si vede, significa che Tracy è in modalità di produzione. -Questo perché Tracy è visibile solo su localhost per motivi di sicurezza. Per verificare se funziona, è possibile metterlo temporaneamente in modalità di sviluppo utilizzando il parametro `Debugger::enable(Debugger::Development)`. +La prima cosa che noterai sulla pagina è la Tracy Bar nell'angolo in basso a destra. Se non la vedi, potrebbe significare che Tracy è in esecuzione in modalità produzione. Infatti, per motivi di sicurezza, Tracy è visibile solo su localhost. Per testare se funziona, puoi temporaneamente passare alla modalità sviluppo usando il parametro `Debugger::enable(Debugger::Development)`. -Barra Tracy .[#toc-tracy-bar] -============================= +Tracy Bar +========= -La barra Tracy è un pannello fluttuante. Viene visualizzata nell'angolo inferiore destro della pagina. È possibile spostarla con il mouse. Ricorda la sua posizione dopo il ricaricamento della pagina. +La Tracy Bar è un pannello flottante che appare nell'angolo in basso a destra della pagina. Possiamo spostarlo con il mouse e ricorderà la sua posizione dopo il ricaricamento della pagina. [* tracy-bar.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html -È possibile aggiungere altri pannelli utili alla barra Tracy. È possibile trovarne di interessanti nei [componenti aggiuntivi |https://componette.org] o [crearne |extensions] di propri. +È possibile aggiungere altri pannelli utili alla Tracy Bar. Ne troverai molti nei [componenti aggiuntivi |https://componette.org], o puoi persino [scriverne di tuoi |extensions]. -Se non si desidera visualizzare la barra Tracy, impostare: +Se non vuoi visualizzare la Tracy Bar, imposta: ```php Debugger::$showBar = false; ``` -Visualizzazione di errori ed eccezioni .[#toc-visualization-of-errors-and-exceptions] -===================================================================================== +Visualizzazione di errori ed eccezioni +====================================== -Sicuramente sapete come PHP segnala gli errori: c'è qualcosa del genere nel codice sorgente della pagina: +Sicuramente sai bene come PHP segnala gli errori: stampa qualcosa del genere nel codice sorgente della pagina: /--pre .{font-size: 90%} <b>Parse error</b>: syntax error, unexpected '}' in <b>HomePresenter.php</b> on line <b>15</b> \-- -o eccezione non catturata: +o in caso di eccezione non catturata: /--pre .{font-size: 90%} <b>Fatal error</b>: Uncaught Nette\MemberAccessException: Call to undefined method Nette\Application\UI\Form::addTest()? in /sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php:100 Stack trace: #0 /sandbox/vendor/nette/utils/src/Utils/Object.php(75): Nette\Utils\ObjectMixin::call(Object(Nette\Application\UI\Form), 'addTest', Array) -#1 /sandbox/app/forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) -#2 /sandbox/app/presenters/SignPresenter.php(21): App\Forms\SignFormFactory->create() -#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presenters\SignPresenter->createComponentSignInForm('signInForm') +#1 /sandbox/app/Forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) +#2 /sandbox/app/Presentation/Sign/SignPresenter.php(21): App\Forms\SignFormFactory->create() +#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presentation\Sign\SignPresenter->createComponentSignInForm('signInForm') #4 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(139): Nette\ComponentModel\Container->createComponent('signInForm') #5 /sandbox/temp/cache/latte/15206b353f351f6bfca2c36cc.php(17): Nette\ComponentModel\Co in <b>/sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php</b> on line <b>100</b><br /> \-- -Non è facile navigare in questo output. Se si abilita Tracy, sia gli errori che le eccezioni vengono visualizzati in una forma completamente diversa: +Non è esattamente facile orientarsi in un output del genere. Se attiviamo Tracy, l'errore o l'eccezione vengono visualizzati in una forma completamente diversa: [* tracy-exception.webp .{url:-} *] -Il messaggio di errore urla letteralmente. È possibile vedere una parte del codice sorgente con la riga evidenziata in cui si è verificato l'errore. Un messaggio spiega chiaramente un errore. L'intero sito è [interattivo, provatelo](https://nette.github.io/tracy/tracy-exception.html). +Il messaggio di errore urla letteralmente. Vediamo una parte del codice sorgente con la riga evidenziata dove si è verificato l'errore e l'informazione *Call to undefined method Nette\Http\User::isLogedIn()* spiega chiaramente di che errore si tratta. Inoltre, l'intera pagina è interattiva, possiamo cliccare per ottenere maggiori dettagli. [Provalo |https://nette.github.io/tracy/tracy-exception.html]. -E sapete cosa? Gli errori fatali vengono catturati e visualizzati allo stesso modo. Non è necessario installare alcuna estensione (fare clic per un esempio dal vivo): +E sai una cosa? In questo modo cattura e visualizza anche gli errori fatali. Senza la necessità di installare alcuna estensione. [* tracy-error.webp .{url:-} *] -Errori come un errore di battitura nel nome di una variabile o il tentativo di aprire un file inesistente generano segnalazioni di livello E_NOTICE o E_WARNING. Questi errori possono essere facilmente trascurati e/o possono essere completamente nascosti nel layout grafico di una pagina web. Lasciate che sia Tracy a gestirli: +Errori come un refuso nel nome di una variabile o il tentativo di aprire un file inesistente generano messaggi di livello E_NOTICE o E_WARNING. Questi possono essere facilmente trascurati nella grafica della pagina, potrebbero addirittura non essere visibili affatto (a meno che non si guardi il codice sorgente). [* tracy-notice2.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html -Oppure possono essere visualizzati come errori: +Oppure possono essere visualizzati allo stesso modo degli errori: ```php -Debugger::$strictMode = true; // visualizza tutti gli errori +Debugger::$strictMode = true; // mostra tutti gli errori Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // tutti gli errori tranne gli avvisi di deprecazione ``` [* tracy-notice.webp .{url:-} *] -Nota: Tracy, una volta attivato, cambia il livello di segnalazione degli errori in E_ALL. Se si desidera cambiare questo livello, farlo dopo aver chiamato `enable()`. +Nota: Dopo l'attivazione, Tracy cambia il livello di segnalazione degli errori in E_ALL. Se vuoi cambiare questo valore, fallo dopo aver chiamato `enable()`. -Modalità di sviluppo e modalità di produzione .[#toc-development-vs-production-mode] -==================================================================================== +Modalità Sviluppo vs Produzione +=============================== -Come si può vedere, Tracy è piuttosto loquace, il che può essere apprezzato nell'ambiente di sviluppo, mentre sul server di produzione sarebbe un disastro. Questo perché lì non dovrebbero essere visualizzate informazioni di debug. Tracy ha quindi un **rilevamento automatico dell'ambiente** e se l'esempio viene eseguito su un server live, l'errore verrà registrato anziché visualizzato e il visitatore vedrà solo un messaggio di facile comprensione: +Come puoi vedere, Tracy è piuttosto loquace, il che può essere apprezzato in un ambiente di sviluppo, mentre su un server di produzione causerebbe un vero disastro. Infatti, nessuna informazione di debug deve essere stampata lì. Tracy dispone quindi di **rilevamento automatico dell'ambiente** e se eseguiamo l'esempio su un server live, l'errore verrà loggato invece di essere visualizzato e il visitatore vedrà solo un messaggio comprensibile per l'utente: [* tracy-error2.webp .{url:-} *] -La modalità di produzione sopprime la visualizzazione di tutte le informazioni di debug inviate tramite [dump() |dumper] e, naturalmente, anche tutti i messaggi di errore generati da PHP. Quindi, se si è dimenticato qualche `dump($obj)` nel codice, non ci si deve preoccupare: sul server di produzione non verrà visualizzato nulla. +La modalità produzione sopprime la visualizzazione di tutte le informazioni di debug che inviamo tramite [dump() |dumper], e ovviamente anche di tutti i messaggi di errore generati da PHP. Quindi, se hai dimenticato qualche `dump($obj)` nel codice, non devi preoccuparti, nulla verrà stampato sul server di produzione. -Come funziona il rilevamento automatico della modalità? La modalità è di sviluppo se l'applicazione è in esecuzione su localhost (cioè, l'indirizzo IP `127.0.0.1` o `::1`) e non c'è alcun proxy (cioè, la sua intestazione HTTP). Altrimenti, viene eseguita in modalità produzione. +Come funziona il rilevamento automatico della modalità? La modalità è di sviluppo quando l'applicazione viene eseguita su localhost (cioè indirizzo IP `127.0.0.1` o `::1`) e non è presente un proxy (cioè la sua intestazione HTTP). Altrimenti, viene eseguita in modalità produzione. -Se si desidera abilitare la modalità di sviluppo in altri casi, ad esempio per gli sviluppatori che accedono da un indirizzo IP specifico, è possibile specificarlo come parametro del metodo `enable()`: +Se vogliamo abilitare la modalità sviluppo anche in altri casi, ad esempio per i programmatori che accedono da un indirizzo IP specifico, lo specifichiamo come parametro del metodo `enable()`: ```php -Debugger::enable('23.75.345.200'); // è possibile fornire anche un array di indirizzi IP +Debugger::enable('23.75.345.200'); // è possibile specificare anche un array di indirizzi IP ``` -Si consiglia di combinare l'indirizzo IP con un cookie. Memorizzare un token segreto, ad esempio `secret1234`, nel cookie `tracy-debug` e, in questo modo, attivare la modalità di sviluppo solo per gli sviluppatori che accedono da un indirizzo IP specifico e che hanno il token citato nel cookie: +Consigliamo vivamente di combinare l'indirizzo IP con un cookie. Salviamo un token segreto nel cookie `tracy-debug`, ad esempio `secret1234`, e in questo modo attiviamo la modalità sviluppo solo per i programmatori che accedono da un indirizzo IP specifico e che hanno il token menzionato nel cookie: ```php Debugger::enable('secret1234@23.75.345.200'); ``` -È anche possibile impostare direttamente la modalità di sviluppo/produzione utilizzando le costanti `Debugger::Development` o `Debugger::Production` come parametro del metodo `enable()`. +Possiamo anche impostare direttamente la modalità sviluppo/produzione utilizzando la costante `Debugger::Development` o `Debugger::Production` come parametro del metodo `enable()`. .[note] -Se si usa Nette Framework, si può vedere come [impostare la modalità per esso |application:bootstrap#Development vs Production Mode], che verrà poi usata anche per Tracy. +Se utilizzi Nette Framework, guarda come [impostare la modalità per esso |application:bootstrapping#Modalità Sviluppo vs Produzione] e questa verrà quindi utilizzata anche per Tracy. -Registrazione degli errori .[#toc-error-logging] -================================================ +Logging degli errori +==================== -In modalità di produzione, Tracy registra automaticamente tutti gli errori e le eccezioni in un registro di testo. Affinché la registrazione avvenga, è necessario impostare il percorso assoluto della cartella di log nella variabile `$logDirectory` o passarla come secondo parametro al metodo `enable()`: +In modalità produzione, Tracy logga automaticamente tutti gli errori e le eccezioni catturate in un log di testo. Affinché il logging funzioni, dobbiamo impostare il percorso assoluto della directory di log nella variabile `$logDirectory` o passarlo come secondo parametro del metodo `enable()`: ```php Debugger::$logDirectory = __DIR__ . '/log'; ``` -La registrazione degli errori è estremamente utile. Immaginate che tutti gli utenti della vostra applicazione siano in realtà dei beta tester che svolgono un lavoro di prim'ordine nel trovare gli errori gratuitamente, e sareste sciocchi a gettare le loro preziose segnalazioni nel cestino. +Il logging degli errori è estremamente utile. Immagina che tutti gli utenti della tua applicazione siano in realtà beta tester che svolgono gratuitamente un lavoro eccellente nella ricerca di errori, e sarebbe sciocco da parte tua gettare i loro preziosi report nel cestino senza prenderne nota. -Se avete bisogno di registrare i vostri messaggi o le eccezioni catturate, usate il metodo `log()`: +Se abbiamo bisogno di loggare un messaggio personalizzato o un'eccezione che abbiamo catturato, usiamo il metodo `log()`: ```php -Debugger::log('Errore inatteso'); // messaggio di testo +Debugger::log('Si è verificato un errore imprevisto'); // messaggio di testo try { criticalOperation(); } catch (Exception $e) { - Debugger::log($e); // registra l'eccezione - // oppure - Debugger::log($e, Debugger::ERROR); // invia anche una notifica via email + Debugger::log($e); // è possibile loggare anche l'eccezione + // o + Debugger::log($e, Debugger::ERROR); // invia anche una notifica via e-mail } ``` -If you want Tracy to log PHP errors like `E_NOTICE` or `E_WARNING` with detailed information (HTML report), set `Debugger::$logSeverity`: +Se vuoi che Tracy logghi gli errori PHP come `E_NOTICE` o `E_WARNING` con informazioni dettagliate (report HTML), imposta `Debugger::$logSeverity`: ```php Debugger::$logSeverity = E_NOTICE | E_WARNING; ``` -Per un vero professionista il registro degli errori è una fonte cruciale di informazioni e vuole essere avvisato immediatamente di ogni nuovo errore. Tracy lo aiuta. È in grado di inviare un'e-mail per ogni nuovo record di errore. La variabile $email identifica dove inviare queste e-mail: +Per un vero professionista, l'error log è una fonte chiave di informazioni e vuole essere informato immediatamente di ogni nuovo errore. Tracy viene incontro a questo, poiché può informare via e-mail di una nuova voce nel log. Specifichiamo dove inviare le e-mail con la variabile $email: ```php Debugger::$email = 'admin@example.com'; ``` -Se si utilizza l'intero Nette Framework, è possibile impostare questa variabile e altre nel [file di configurazione |nette:configuring]. +Se utilizzi l'intero Nette Framework, questo e altro possono essere impostati nel [file di configurazione |nette:configuring]. -Per proteggere la casella di posta elettronica dall'inondazione, Tracy invia **un solo messaggio** e crea un file `email-sent`. Quando uno sviluppatore riceve la notifica via e-mail, controlla il registro, corregge la sua applicazione e cancella il file di monitoraggio `email-sent`. Questo attiva nuovamente l'invio di e-mail. +Tuttavia, per non inondare la tua casella di posta elettronica, invia sempre **solo un messaggio** e crea il file `email-sent`. Dopo aver ricevuto la notifica via e-mail, lo sviluppatore controlla il log, corregge l'applicazione ed elimina il file di monitoraggio, riattivando così l'invio delle e-mail. -Apertura dei file nell'editor .[#toc-opening-files-in-the-editor] -================================================================= +Apertura nell'editor +==================== -Quando viene visualizzata la pagina degli errori, è possibile fare clic sui nomi dei file e questi si apriranno nell'editor con il cursore sulla riga corrispondente. È anche possibile creare file (azione `create file`) o correggere bug in essi (azione `fix it`). Per fare ciò, è necessario [configurare il browser e il sistema |open-files-in-ide]. +Quando viene visualizzata la pagina di errore, è possibile fare clic sui nomi dei file e questi si apriranno nel tuo editor con il cursore sulla riga corrispondente. È anche possibile creare file (azione `create file`) o correggere errori in essi (azione `fix it`). Affinché funzioni, è sufficiente [configurare il browser e il sistema |open-files-in-ide]. -Versioni PHP supportate .[#toc-supported-php-versions] -====================================================== +Versioni PHP supportate +======================= -| Tracy | compatibile con PHP -|-----------|-------------------- -| Tracy 2.10 – 3.0 | PHP 8.0 - 8.2 -| Tracy 2.9 | PHP 7.2 - 8.2 -| Tracy 2.8 | PHP 7.2 - 8.1 -| Tracy 2.6 - 2.7 | PHP 7.1 - 8.0 -| Tracy 2.5 | PHP 5.4 - 7.4 -| Tracy 2.4 | PHP 5.4 - 7.2 +| Tracy | compatibile con PHP +|-----------|------------------- +| Tracy 2.10 – 3.0 | PHP 8.0 – 8.4 +| Tracy 2.9 | PHP 7.2 – 8.2 +| Tracy 2.8 | PHP 7.2 – 8.1 +| Tracy 2.6 – 2.7 | PHP 7.1 – 8.0 +| Tracy 2.5 | PHP 5.4 – 7.4 +| Tracy 2.4 | PHP 5.4 – 7.2 -Si applica alle ultime versioni della patch. +Valido per l'ultima versione patch. -Porte .[#toc-ports] -=================== +Port +==== -Questo è un elenco di porte non ufficiali per altri framework e CMS: +Questo è un elenco di port non ufficiali per altri framework e CMS: - [Drupal 7](https://www.drupal.org/project/traced) - Laravel framework: [recca0120/laravel-tracy](https://github.com/recca0120/laravel-tracy), [whipsterCZ/laravel-tracy](https://github.com/whipsterCZ/laravel-tracy) diff --git a/tracy/it/open-files-in-ide.texy b/tracy/it/open-files-in-ide.texy index 4250065585..15c98ad54f 100644 --- a/tracy/it/open-files-in-ide.texy +++ b/tracy/it/open-files-in-ide.texy @@ -1,20 +1,20 @@ -Come aprire un file nell'editor da Tracy? (Integrazione IDE) -************************************************************ +Come aprire un file nell'editor da Tracy? (Integrazione con IDE) +**************************************************************** .[perex] -Quando viene visualizzata la pagina degli errori, è possibile fare clic sui nomi dei file e questi si apriranno nell'editor con il cursore sulla riga corrispondente. È anche possibile creare file (azione `create file`) o correggere bug in essi (azione `fix it`). Per fare ciò, è necessario configurare il browser e il sistema. +Quando viene visualizzata la pagina di errore, è possibile fare clic sui nomi dei file e questi si apriranno nel tuo editor con il cursore sulla riga corrispondente. È anche possibile creare file (azione `create file`) o correggere errori in essi (azione `fix it`). Affinché ciò accada, è necessario configurare il browser e il sistema. -Tracy apre i file tramite URL della forma `editor://open/?file=%file&line=%line`, cioè con il protocollo `editor://`. Per questo registreremo il nostro gestore. Questo può essere un qualsiasi file eseguibile che elabora i parametri e avvia il nostro editor preferito. +Tracy apre i file tramite URL nella forma `editor://open/?file=%file&line=%line`, cioè con il protocollo `editor://`. Per questo, registreremo un gestore personalizzato. Questo può essere qualsiasi file eseguibile che "mastichi" i parametri e avvii il nostro editor preferito. -È possibile modificare l'URL nella variabile `Tracy\Debugger::$editor` o disabilitare il click-through impostando `Tracy\Debugger::$editor = null`. +Puoi cambiare l'URL nella variabile `Tracy\Debugger::$editor`, o disattivare il clic impostando `Tracy\Debugger::$editor = null`. -Windows .[#toc-windows] -======================= +Windows +======= -1. Scaricate su disco i file appropriati "dal repository Tracy:https://github.com/nette/tracy/tree/master/tools/open-in-editor/windows ". +1. Scarica i file appropriati dal "repository Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/windows sul tuo disco. -2. Modificare `open-editor.js` e decommentare o modificare il percorso del proprio editor in `settings`: +2. Modifica il file `open-editor.js` e nell'array `settings` decommenta ed eventualmente modifica il percorso del tuo editor: ```js var settings = { @@ -35,19 +35,28 @@ var settings = { ... ``` -Fate attenzione a mantenere le doppie barre nei percorsi. +Attenzione, mantieni le doppie barre rovesciate nei percorsi. -3. Registrate il gestore del protocollo `editor://` nel sistema. +3. Registra il gestore del protocollo `editor://` nel sistema. -Questo si fa eseguendo `install.cmd`. **Lo script `open-editor.js` servirà ora il protocollo `editor://`. +Puoi farlo eseguendo il file `install.cmd`. **È necessario eseguirlo come Amministratore.** Lo script `open-editor.js` gestirà ora il protocollo `editor://`. +Per poter aprire i link generati su altri server, come un server live o in Docker, aggiungi anche la mappatura dell'URL remoto a quello locale in `open-editor.js`: -Linux .[#toc-linux] -=================== +```js + mappings: { + // percorso remoto: percorso locale + '/var/www/nette.app': 'W:\\Nette.web\\_web', + } +``` + + +Linux +===== -1. Scaricare i file appropriati "dal repository Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux nella directory `~/bin`. +1. Scarica i file appropriati dal "repository Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux nella directory `~/bin`. -2. Modificare `open-editor.sh` e decommentare o modificare il percorso del proprio editor nella variabile `editor`: +2. Modifica il file `open-editor.sh` e decommenta ed eventualmente modifica il percorso del tuo editor nella variabile `editor`. ```shell #!/bin/bash @@ -67,24 +76,25 @@ Linux .[#toc-linux] ... ``` -Rendetelo eseguibile: +Rendi il file eseguibile: ```shell chmod +x ~/bin/open-editor.sh ``` -Se l'editor che si sta usando non è installato nel pacchetto, il binario probabilmente non avrà un percorso in `$PATH`. Questo può essere facilmente corretto. Nella cartella `~/bin`, creare un link simbolico al binario dell'editor. .[note] +.[note] +Se l'editor utilizzato non è installato da un pacchetto, probabilmente il binario non avrà un percorso in $PATH. Questo può essere facilmente risolto. Nella directory `~/bin`, crea un link simbolico al binario dell'editor. -3. Registrare il gestore del protocollo `editor://` nel sistema. +3. Registra il gestore del protocollo `editor://` nel sistema. -Ciò avviene eseguendo `install.sh`. Lo script `open-editor.js` servirà ora il protocollo `editor://`. +Puoi farlo eseguendo il file `install.sh`. Lo script `open-editor.sh` gestirà ora il protocollo `editor://`. -macOS .[#toc-macos] -=================== +macOS +===== -Gli editor come PhpStorm, TextMate, ecc. consentono di aprire i file tramite un URL speciale, che è sufficiente impostare: +Editor come PhpStorm, TextMate, ecc. consentono l'apertura di file tramite un URL speciale, che è sufficiente impostare: ```php // PhpStorm @@ -92,44 +102,43 @@ Tracy\Debugger::$editor = 'phpstorm://open?file=%file&line=%line'; // TextMate Tracy\Debugger::$editor = 'txmt://open/?url=file://%file&line=%line'; // MacVim -Tracy\Debugger::$editor = 'mvim://open/?url=file://%file&line=%line'; +Tracy\Debugger::$editor = 'mvim://open?url=file:///%file&line=%line'; // Visual Studio Code Tracy\Debugger::$editor = 'vscode://file/%file:%line'; ``` -Se si utilizza Tracy standalone, inserire la riga prima di `Tracy\Debugger::enable()`, se Nette, prima di `$configurator->enableTracy()` in `Bootstrap.php`. +Se usi Tracy standalone, inserisci la riga prima di `Tracy\Debugger::enable()`, se usi Nette, allora prima di `$configurator->enableTracy()` in `Bootstrap.php`. Purtroppo, le azioni `create file` o `fix it` non funzionano su macOS. -Dimostrazioni .[#toc-demos] -=========================== +Esempi +====== -Correzione di un bug: +Correzione di un errore: <iframe width="560" height="315" src="https://www.youtube.com/embed/3ITT4mC0Eq4?rel=0&showinfo=0" frameborder="0" allow="encrypted-media" allowfullscreen></iframe> -Creazione di un nuovo file: +Creazione di un file: <iframe width="560" height="315" src="https://www.youtube.com/embed/AJ_FUivAGZQ?rel=0&showinfo=0" frameborder="0" allow="encrypted-media" allowfullscreen></iframe> -Risoluzione dei problemi .[#toc-troubleshooting] -================================================ +Risoluzione dei problemi +======================== -- In Firefox potrebbe essere necessario [consentire l' |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] esecuzione del protocollo personalizzato in about:config impostando `network.protocol-handler.expose.editor` su `false` e `network.protocol-handler.expose-all` su `true`. Tuttavia, dovrebbe essere consentito per impostazione predefinita. -- Se non funziona tutto immediatamente, niente panico. Provate ad aggiornare la pagina, a riavviare il browser o il computer. Questo dovrebbe aiutare. -- Vedere [qui |https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/] per risolvere il problema: - Errore di input: Non esiste un motore di script per l'estensione del file ".js" Forse avete associato il file ".js" a un'altra applicazione, non al motore JScript. +- In Firefox, potrebbe essere necessario abilitare il protocollo [impostando |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] `network.protocol-handler.expose.editor` su `false` e `network.protocol-handler.expose-all` su `true` in about:config. +- Se non funziona subito, non farti prendere dal panico e prova a ricaricare la pagina alcune volte prima di fare clic sul link. Si avvierà! +- Ecco un [link|https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/] per correggere un possibile errore: `Input Error: There is no script engine for file extension ".js"`, `Maybe you associated ".js" file to another app, not JScript engine.` o `non è disponibile alcun motore di scripting per l'estensione .js`. -A partire dalla versione 77 di Google Chrome non verrà più visualizzata la casella di controllo "Apri sempre questi tipi di collegamenti nell'applicazione associata" quando l'editor viene aperto tramite un collegamento. Soluzione per Windows: creare il file `fix.reg`: +In Google Chrome dalla versione 77, non vedrai più la casella di controllo "Apri sempre questo tipo di link nell'applicazione associata" quando l'editor viene avviato tramite un link. Soluzione per Windows: crea un file `fix.reg`: ``` Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\URLWhitelist] "123"="editor://*" ``` -Importarlo con un doppio clic e riavviare Chrome. +Importalo facendo doppio clic e riavvia Chrome. -In caso di ulteriori problemi o domande, chiedere al [forum |https://forum.nette.org]. +Per eventuali domande o commenti, si prega di contattare il [forum |https://forum.nette.org]. diff --git a/tracy/it/recipes.texy b/tracy/it/recipes.texy index a0c3ddee66..e9a9b06800 100644 --- a/tracy/it/recipes.texy +++ b/tracy/it/recipes.texy @@ -1,12 +1,11 @@ -Ricette -******* +Tutorial +******** -Politica di sicurezza dei contenuti .[#toc-content-security-policy] -=================================================================== +Content Security Policy +======================= -Se il proprio sito utilizza i Criteri di sicurezza dei contenuti, è necessario aggiungere i tag `'nonce-<value>'` e `'strict-dynamic'` a `script-src` perché Tracy funzioni correttamente. Alcuni plugin di terze parti potrebbero richiedere direttive aggiuntive. -Nonce non è supportato dalla direttiva `style-src`; se si usa questa direttiva, è necessario aggiungere `'unsafe-inline'`, ma questo dovrebbe essere evitato in modalità di produzione. +Se il tuo sito web utilizza Content Security Policy, dovrai aggiungere gli stessi `'nonce-<value>'` e `'strict-dynamic'` a `script-src` affinché Tracy funzioni correttamente. Alcuni componenti aggiuntivi di terze parti potrebbero richiedere impostazioni aggiuntive. Nonce non è supportato nella direttiva `style-src`; se utilizzi questa direttiva, devi aggiungere `'unsafe-inline'`, ma dovresti evitarlo in modalità produzione. Esempio di configurazione per [Nette Framework |nette:configuring]: @@ -24,11 +23,10 @@ header("Content-Security-Policy: script-src 'nonce-$nonce' 'strict-dynamic';"); ``` -Caricamento più veloce .[#toc-faster-loading] -============================================= +Caricamento più veloce +====================== -L'integrazione di base è semplice, ma se nella pagina web sono presenti script di blocco lenti, questi possono rallentare il caricamento di Tracy. -La soluzione è inserire `<?php Tracy\Debugger::renderLoader() ?>` nel template prima di qualsiasi script: +L'avvio è semplice, ma se hai script bloccanti a caricamento lento sulla tua pagina web, possono rallentare il caricamento di Tracy. La soluzione è posizionare `<?php Tracy\Debugger::renderLoader() ?>` nel tuo template prima di tutti gli script: ```latte <!DOCTYPE html> @@ -42,12 +40,37 @@ La soluzione è inserire `<?php Tracy\Debugger::renderLoader() ?>` nel template ``` -AJAX e richieste reindirizzate .[#toc-ajax-and-redirected-requests] -=================================================================== +Debugging delle richieste AJAX +============================== -Tracy può visualizzare la barra di Debug e le Bluescreen per le richieste AJAX e di reindirizzamento. Tracy crea le proprie sessioni, memorizza i dati nei propri file temporanei e utilizza un cookie `tracy-session`. +Tracy cattura automaticamente le richieste AJAX create utilizzando jQuery o l'API nativa `fetch`. Le richieste vengono visualizzate nella barra di Tracy come righe aggiuntive, consentendo un debug AJAX facile e comodo. -Tracy può anche essere configurato per utilizzare una sessione PHP nativa, che viene avviata prima dell'attivazione di Tracy: +Se non desideri catturare automaticamente le richieste AJAX, puoi disabilitare questa funzione impostando una variabile JavaScript: + +```js +window.TracyAutoRefresh = false; +``` + +Per monitorare manualmente specifiche richieste AJAX, aggiungi l'header HTTP `X-Tracy-Ajax` con il valore restituito da `Tracy.getAjaxHeader()`. Ecco un esempio di utilizzo con la funzione `fetch`: + +```js +fetch(url, { + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Tracy-Ajax': Tracy.getAjaxHeader(), + } +}) +``` + +Questo approccio consente il debug selettivo delle richieste AJAX. + + +Archiviazione dati +================== + +Tracy può visualizzare pannelli nella barra di Tracy e Bluescreen per richieste AJAX e redirect. Tracy crea la propria sessione, memorizza i dati nei propri file temporanei e utilizza il cookie `tracy-session`. + +Tracy può anche essere configurata per utilizzare la sessione PHP nativa, che avviamo prima di abilitare Tracy: ```php session_start(); @@ -55,44 +78,44 @@ Debugger::setSessionStorage(new Tracy\NativeSession); Debugger::enable(); ``` -Nel caso in cui l'avvio di una sessione richieda un'inizializzazione più complessa, è possibile avviare Tracy immediatamente (in modo che possa gestire eventuali errori), quindi inizializzare il gestore della sessione e infine informare Tracy che la sessione è pronta per essere utilizzata utilizzando la funzione `dispatch()`: +Nel caso in cui l'avvio della sessione richieda un'inizializzazione più complessa, puoi avviare Tracy immediatamente (in modo che possa gestire eventuali errori che si verificano), quindi inizializzare il gestore della sessione e infine informare Tracy che la sessione è pronta per l'uso utilizzando la funzione `dispatch()`: ```php Debugger::setSessionStorage(new Tracy\NativeSession); Debugger::enable(); -// seguito dall'inizializzazione della sessione -// e avviare la sessione +// segue l'inizializzazione della sessione +// e l'avvio della sessione session_start(); Debugger::dispatch(); ``` -La funzione `setSessionStorage()` esiste dalla versione 2.9, prima di allora Tracy utilizzava sempre la sessione nativa di PHP. +La funzione `setSessionStorage()` esiste dalla versione 2.9; prima di allora, Tracy utilizzava sempre la sessione PHP nativa. -Scrubber personalizzato .[#toc-custom-scrubber] -=============================================== +Scrubber personalizzato +======================= -Scrubber è un filtro che impedisce la fuoriuscita di dati sensibili dai dump, come password o credenziali. Il filtro viene richiamato per ogni elemento dell'array o dell'oggetto scaricato e restituisce `true` se il valore è sensibile. In questo caso, al posto del valore viene stampato `*****`. +Lo Scrubber è un filtro che impedisce la fuga di dati sensibili durante il dump, come password o credenziali di accesso. Il filtro viene chiamato per ogni elemento dell'array o dell'oggetto dumpato e restituisce `true` se il valore è sensibile. In tal caso, viene stampato `*****` al posto del valore. ```php -// evita di scaricare i valori delle chiavi e le proprietà come `password`, +// impedisce la stampa dei valori delle chiavi e delle proprietà come `password`, // `password_repeat`, `check_password`, `DATABASE_PASSWORD`, ecc. $scrubber = function(string $key, $value, ?string $class): bool { return preg_match('#password#i', $key) && $value !== null; }; -// lo usiamo per tutti i dump all'interno di BlueScreen. +// lo useremo per tutti i dump all'interno del BlueScreen Tracy\Debugger::getBlueScreen()->scrubber = $scrubber; ``` -Logger personalizzato .[#toc-custom-logger] -=========================================== +Logger personalizzato +===================== -Possiamo creare un logger personalizzato per registrare gli errori, le eccezioni non catturate e anche per essere chiamato da `Tracy\Debugger::log()`. Il logger implementa l'interfaccia [api:Tracy\ILogger]. +Possiamo creare il nostro logger personalizzato che registrerà errori, eccezioni non catturate e sarà anche invocato dal metodo `Tracy\Debugger::log()`. Il logger implementa l'interfaccia [api:Tracy\ILogger]. ```php use Tracy\ILogger; @@ -106,13 +129,13 @@ class SlackLogger implements ILogger } ``` -E poi lo attiviamo: +E successivamente lo attiviamo: ```php Tracy\Debugger::setLogger(new SlackLogger); ``` -Se utilizziamo il Nette Framework completo, possiamo impostarlo nel file di configurazione di NEON: +Se utilizziamo il Nette Framework completo, puoi impostarlo nel file di configurazione NEON: ```neon services: @@ -120,8 +143,8 @@ services: ``` -Integrazione Monolog .[#toc-monolog-integration] ------------------------------------------------- +Integrazione di Monolog +----------------------- Il pacchetto Tracy fornisce un adattatore PSR-3 che consente l'integrazione di [monolog/monolog](https://github.com/Seldaek/monolog). @@ -133,21 +156,21 @@ $tracyLogger = new Tracy\Bridges\Psr\PsrToTracyLoggerAdapter($monolog); Debugger::setLogger($tracyLogger); Debugger::enable(); -Debugger::log('info'); // scrive: [<TIMESTAMP>] main-channel.INFO: info [] [] [] +Debugger::log('info'); // scrive: [<TIMESTAMP>] main-channel.INFO: info [] [] Debugger::log('warning', Debugger::WARNING); // scrive: [<TIMESTAMP>] main-channel.WARNING: warning [] [] ``` -nginx .[#toc-nginx] -=================== +nginx +===== -Se Tracy non funziona su nginx, probabilmente è mal configurato. Se c'è qualcosa come +Se Tracy non funziona sul tuo server nginx, probabilmente è configurato male. Se c'è qualcosa come questo nella configurazione: ```nginx try_files $uri $uri/ /index.php; ``` -cambiatelo in +cambialo in: ```nginx try_files $uri $uri/ /index.php$is_args$args; diff --git a/tracy/it/stopwatch.texy b/tracy/it/stopwatch.texy index 2bd963cb44..8658f24eff 100644 --- a/tracy/it/stopwatch.texy +++ b/tracy/it/stopwatch.texy @@ -1,35 +1,35 @@ -Cronometro -********** +Misurazione del tempo +********************* -Un altro strumento utile è il cronometro del debugger con una precisione di microsecondi: +Un altro strumento utile del debugger è il cronometro con precisione al microsecondo: ```php Debugger::timer(); -// sogni d'oro, tesoro mio +// mio piccolo principe dormi, gli uccellini già sognano dolcemente... sleep(2); $elapsed = Debugger::timer(); // $elapsed = 2 ``` -Un parametro opzionale consente di ottenere più misure contemporaneamente. +Con un parametro opzionale è possibile effettuare misurazioni multiple. ```php Debugger::timer('page-generating'); -// un po' di codice +// qualche codice Debugger::timer('rss-generating'); -// un po' di codice +// qualche codice $rssElapsed = Debugger::timer('rss-generating'); $pageElapsed = Debugger::timer('page-generating'); ``` ```php -Debugger::timer(); // esegue il timer +Debugger::timer(); // avvia il cronometro -... // qualche operazione che richiede tempo +... // operazione che richiede tempo -echo Debugger::timer(); // tempo trascorso in secondi +echo Debugger::timer(); // stampa il tempo trascorso in secondi ``` diff --git a/tracy/ja/@home.texy b/tracy/ja/@home.texy new file mode 100644 index 0000000000..4056a7cc53 --- /dev/null +++ b/tracy/ja/@home.texy @@ -0,0 +1,2 @@ +{{maintitle: Tracy – エラーが楽しくなるデバッグツール}} +{description: TracyはPHPコードのデバッグを容易にするためのツールです。エラーの視覚化とログ記録、変数のダンプなどを支援する、すべてのPHPプログラマーにとって便利なヘルパーです。注意:Tracyは中毒性があります!}} diff --git a/tracy/ja/@left-menu.texy b/tracy/ja/@left-menu.texy new file mode 100644 index 0000000000..59840b87fc --- /dev/null +++ b/tracy/ja/@left-menu.texy @@ -0,0 +1,7 @@ +- [Tracy 入門 |guide] +- [ダンプ |dumper] +- [時間計測 |stopwatch] +- [設定 |configuring] +- [レシピ |recipes] +- [IDE との統合 |open-files-in-ide] +- [拡張機能の作成 |extensions] diff --git a/tracy/ja/@menu.texy b/tracy/ja/@menu.texy new file mode 100644 index 0000000000..6e526df584 --- /dev/null +++ b/tracy/ja/@menu.texy @@ -0,0 +1,3 @@ +- [はじめに |@home] +- [ドキュメント |guide] +- "GitHub .[link-external]":https://github.com/nette/tracy diff --git a/tracy/ja/@meta.texy b/tracy/ja/@meta.texy new file mode 100644 index 0000000000..8666d0e5f6 --- /dev/null +++ b/tracy/ja/@meta.texy @@ -0,0 +1 @@ +{{sitename: Tracy ドキュメンテーション}} diff --git a/tracy/ja/configuring.texy b/tracy/ja/configuring.texy new file mode 100644 index 0000000000..7268a97dda --- /dev/null +++ b/tracy/ja/configuring.texy @@ -0,0 +1,180 @@ +Tracyの設定 +******** + +すべての例は、エイリアスが作成されていることを前提としています: + +```php +use Tracy\Debugger; +``` + + +エラーロギング +------- + +```php +$logger = Debugger::getLogger(); + +// エラー発生時に通知が送信されるEメールアドレス +$logger->email = 'dev@example.com'; // (string|string[]) デフォルトは未設定 + +// Eメールの送信者 +$logger->fromEmail = 'me@example.com'; // (string) デフォルトは未設定 + +// Eメール送信を保証するルーチン +$logger->mailer = /* ... */; // (callable) デフォルトは mail() 関数による送信 + +// 次のEメールを送信するまでの最短時間は? +$logger->emailSnooze = /* ... */; // (string) デフォルトは '2 days' + +// どのエラーレベルでBlueScreenもログに記録されますか? +Debugger::$logSeverity = E_WARNING | E_NOTICE; // デフォルトは 0 (エラーレベルなし) +``` + + +`dump()` の振る舞い +-------------- + +```php +// 文字列の最大長 +Debugger::$maxLength = 150; // (int) デフォルトはTracyのバージョンによる + +// 最大ネスト深度 +Debugger::$maxDepth = 10; // (int) デフォルトはTracyのバージョンによる + +// これらのキーの値を隠す (Tracy 2.8以降) +Debugger::$keysToHide = ['password', /* ... */]; // (string[]) デフォルトは [] + +// ビジュアルテーマ (Tracy 2.8以降) +Debugger::$dumpTheme = 'dark'; // (light|dark) デフォルトは 'light' + +// dump() 関数が呼び出された場所を表示しますか? +Debugger::$showLocation = /* ... */; // (bool) デフォルトはTracyのバージョンによる +``` + + +その他 +--- + +```php +// 開発モードでは、noticeまたはwarningタイプのエラーをBlueScreenとして表示します +Debugger::$strictMode = /* ... */; // (bool|int) デフォルトは false、特定のエラーレベルのみを選択可能 (例: E_USER_DEPRECATED | E_DEPRECATED) + +// サイレンスされた (@) エラーメッセージを表示しますか? +Debugger::$scream = /* ... */; // (bool|int) デフォルトは false、バージョン2.9以降、特定のエラーレベルのみを選択可能 (例: E_USER_DEPRECATED | E_DEPRECATED) + +// エディタで開くためのリンク形式 +Debugger::$editor = /* ... */; // (string|null) デフォルトは 'editor://open/?file=%file&line=%line' + +// エラー500用のカスタムページテンプレートへのパス +Debugger::$errorTemplate = /* ... */; // (string) デフォルトは未設定 + +// Tracy Barを表示しますか? +Debugger::$showBar = /* ... */; // (bool) デフォルトは true + +Debugger::$editorMapping = [ + // オリジナル => 新しい + '/var/www/html' => '/data/web', + '/home/web' => '/srv/html', +]; +``` + + +Nette Framework +--------------- + +Nette Frameworkを使用している場合、設定ファイルを使用してTracyを設定し、Tracy Barに新しいパネルを追加することもできます。 設定では、パラメータを設定したり、Tracy Barに新しいパネルを追加したりできます。これらの設定はDIコンテナが作成された後に適用されるため、それ以前に発生したエラーは反映されません。 + +エラーロギングの設定: + +```neon +tracy: + # エラー発生時に通知が送信されるEメールアドレス + email: dev@example.com # (string|string[]) デフォルトは未設定 + + # Eメールの送信者 + fromEmail: robot@example.com # (string) デフォルトは未設定 + + # Eメール送信の遅延時間 (Tracy 2.8.8以降) + emailSnooze: ... # (string) デフォルトは '2 days' + + # Eメール送信にNetteメーラーを使用しますか? (Tracy 2.5以降) + netteMailer: ... # (bool) デフォルトは true + + # どのエラーレベルでBlueScreenもログに記録されますか? + logSeverity: [E_WARNING, E_NOTICE] # デフォルトは [] +``` + +`dump()` 関数の振る舞いの設定: + +```neon +tracy: + # 文字列の最大長 + maxLength: 150 # (int) デフォルトはTracyのバージョンによる + + # 最大ネスト深度 + maxDepth: 10 # (int) デフォルトはTracyのバージョンによる + + # これらのキーの値を隠す (Tracy 2.8以降) + keysToHide: [password, pass] # (string[]) デフォルトは [] + + # ビジュアルテーマ (Tracy 2.8以降) + dumpTheme: dark # (light|dark) デフォルトは 'light' + + # dump() 関数が呼び出された場所を表示しますか? + showLocation: ... # (bool) デフォルトはTracyのバージョンによる +``` + +Tracy拡張機能のインストール: + +```neon +tracy: + # Tracy Barにパネルを追加します + bar: + - Nette\Bridges\DITracy\ContainerPanel + - IncludePanel + - XDebugHelper('myIdeKey') + - MyPanel(@MyService) + + # BlueScreenにパネルを追加します + blueScreen: + - DoctrinePanel::renderException +``` + +その他のオプション: + +```neon +tracy: + # 開発モードでは、noticeまたはwarningタイプのエラーをBlueScreenとして表示します + strictMode: ... # デフォルトは true + + # サイレンスされた (@) エラーメッセージを表示しますか? + scream: ... # デフォルトは false + + # エディタで開くためのリンク形式 + editor: ... # (string) デフォルトは 'editor://open/?file=%file&line=%line' + + # エラー500用のカスタムページテンプレートへのパス + errorTemplate: ... # (string) デフォルトは未設定 + + # Tracy Barを表示しますか? + showBar: ... # (bool) デフォルトは true + + editorMapping: + # オリジナル: 新しい + /var/www/html: /data/web + /home/web: /srv/html +``` + +オプション `logSeverity`、`strictMode`、`scream` の値は、エラーレベルの配列(例:`[E_WARNING, E_NOTICE]`)またはPHP言語で使用される式(例:`E_ALL & ~E_NOTICE`)として記述できます。 + + +DIサービス +------ + +これらのサービスはDIコンテナに追加されます: + +| 名前 | 型 | 説明 +|---------------------------------------------------------- +| `tracy.logger` | [api:Tracy\ILogger] | ロガー +| `tracy.blueScreen` | [api:Tracy\BlueScreen] | BlueScreen +| `tracy.bar` | [api:Tracy\Bar] | Tracy Bar diff --git a/tracy/ja/dumper.texy b/tracy/ja/dumper.texy new file mode 100644 index 0000000000..82a08ce9e1 --- /dev/null +++ b/tracy/ja/dumper.texy @@ -0,0 +1,48 @@ +ダンプ +*** + +すべてのデバッガは、変数の内容を詳細に出力する [php:var_dump |php:var_dump] 関数の良き友達です。残念ながら、HTML環境では出力のフォーマットが失われ、一行に結合されてしまい、HTMLコードのサニタイズは言うまでもありません。実用的には、`var_dump` をより便利な関数に置き換えることが不可欠です。それが `dump()` です。 + +```php +$arr = [10, 20.2, true, null, 'hello']; + +dump($arr); +// または Debugger::dump($arr); +``` + +は次の出力を生成します: + +[* dump-basic.webp *] + +デフォルトのライトテーマをダークテーマに変更できます: + +```php +Debugger::$dumpTheme = 'dark'; +``` + +[* dump-dark.webp *] + +さらに、[Debugger::$maxDepth |api:Tracy\Debugger::$maxDepth] を使用してネストの深さを、[Debugger::$maxLength |api:Tracy\Debugger::$maxLength] を使用して表示される文字列の長さを変更できます。より低い値は当然Tracyを高速化します。 + +```php +Debugger::$maxDepth = 2; // デフォルト: 3 +Debugger::$maxLength = 50; // デフォルト: 150 +``` + +`dump()` 関数は他の有用な情報も出力できます。定数 `Tracy\Dumper::LOCATION_SOURCE` は、関数が呼び出された場所へのパスを含むツールチップを追加します。`Tracy\Dumper::LOCATION_LINK` はその場所へのリンクを提供します。`Tracy\Dumper::LOCATION_CLASS` は、ダンプされた各オブジェクトに対して、そのクラスが定義されているファイルへのパスを含むツールチップを表示します。定数は、`dump()` を呼び出す前に変数 `Debugger::$showLocation` に設定されます。複数の値を一度に設定したい場合は、`|` 演算子を使用して結合します。 + +```php +Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // 関数呼び出し場所の出力のみを設定 +Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // リンクの出力とクラスへのパスを同時に設定 +Debugger::$showLocation = false; // 追加情報の出力を無効にする +Debugger::$showLocation = true; // すべての追加情報の出力を有効にする +``` + +`dump()` の実用的な代替は `dumpe()` (dump & exit) と `bdump()` です。これにより、変数の値をTracy Barのパネルに出力できます。これは非常に便利です。なぜなら、ダンプはページのレイアウトから分離されており、コメントを追加することもできるからです。 + +```php +bdump([2, 4, 6, 8], '10までの偶数'); +bdump([1, 3, 5, 7, 9], '10までの奇数'); +``` + +[* bardump-cs.webp *] diff --git a/tracy/ja/extensions.texy b/tracy/ja/extensions.texy new file mode 100644 index 0000000000..a3ae1f3f45 --- /dev/null +++ b/tracy/ja/extensions.texy @@ -0,0 +1,122 @@ +Tracyの拡張機能の作成 +************* + +<div class=perex> + +Tracyはアプリケーションのデバッグに優れたツールを提供します。しかし、時には他の情報も手元に欲しいと思うことがあります。ここでは、開発をさらに快適にするために、Tracy Bar用のカスタム拡張機能を作成する方法を示します。 + +- Tracy Bar用のカスタムパネルの作成 +- Bluescreen用のカスタム拡張機能の作成 + +</div> + +.[tip] +Tracy用の完成した拡張機能のリポジトリは "Componette":https://componette.org/search/tracy で見つけることができます。 + + +Tracy Barの拡張機能 +============== + +Tracy Bar用の新しい拡張機能を作成することは複雑ではありません。`Tracy\IBarPanel` インターフェースを実装するオブジェクトを作成します。これには `getTab()` と `getPanel()` の2つのメソッドがあります。これらのメソッドは、タブ(Barに直接表示される小さなラベル)とパネルのHTMLコードを返す必要があります。`getPanel()` が何も返さない場合、ラベル自体のみが表示されます。`getTab()` が何も返さない場合、何も表示されず、`getPanel()` も呼び出されません。 + +```php +class ExamplePanel implements Tracy\IBarPanel +{ + public function getTab() + { + return /* ... */; + } + + public function getPanel() + { + return /* ... */; + } +} +``` + + +登録 +---------- + +登録は `Tracy\Bar::addPanel()` を使用して行われます: + +```php +Tracy\Debugger::getBar()->addPanel(new ExamplePanel); +``` + +または、アプリケーションの設定で直接パネルを登録することもできます: + +```neon +tracy: + bar: + - ExamplePanel +``` + + +タブのHTMLコード +---------- + +おおよそ次のようになります: + +```latte +<span title="説明的なラベル"> + <svg>...</svg> + <span class="tracy-label">タイトル</span> +</span> +``` + +画像はSVG形式であるべきです。説明的なラベルが必要ない場合は、`<span>` を省略できます。 + + +パネルのHTMLコード +----------- + +おおよそ次のようになります: + +```latte +<h1>タイトル</h1> + +<div class="tracy-inner"> +<div class="tracy-inner-container"> + ... 内容 ... +</div> +</div> +``` + +タイトルはタブのタイトルと同じであるべきか、追加情報を含むことができます。 + +1つの拡張機能が、例えば異なる設定で複数回登録される可能性があることを考慮に入れる必要があります。そのため、スタイリングにはCSS idを使用できず、`tracy-addons-<ClassName>[-<optional>]` の形式のクラスのみを使用する必要があります。次に、クラスを `tracy-inner` クラスと一緒にdivに記述します。CSSを記述する際には、`#tracy-debug .class` と書くと便利です。なぜなら、ルールはリセットよりも高い優先度を持つからです。 + + +デフォルトスタイル +--------- + +パネル内では、`<a>`、`<table>`、`<pre>`、`<code>` が事前スタイル設定されています。別の要素を隠したり表示したりするリンクを作成したい場合は、`href` と `id` 属性および `tracy-toggle` クラスでそれらをリンクさせます: + +```latte +<a href="#tracy-addons-ClassName-{$counter}" class="tracy-toggle">詳細</a> + +<div id="tracy-addons-ClassName-{$counter}">...</div> +``` + +デフォルト状態が折りたたまれている場合は、両方の要素に `tracy-collapsed` クラスを追加します。 + +カウンターは静的に使用して、1つのページに重複したIDが作成されないようにします。 + + +Bluescreenの拡張機能 +=============== + +この方法で、例外のカスタム視覚化や、bluescreenに表示されるパネルを追加できます。 + +拡張機能はこのコマンドで作成されます: +```php +Tracy\Debugger::getBlueScreen()->addPanel(function (?Throwable $e) { // キャッチされた例外 + return [ + 'tab' => '...ラベル...', + 'panel' => '...パネルのHTMLコード...', + ]; +}); +``` + +関数は2回呼び出されます。まず、パラメータ `$e` に例外自体が渡され、返されたパネルがページの先頭にレンダリングされます。何も返さない場合、パネルはレンダリングされません。その後、`null` パラメータで呼び出され、返されたパネルがコールスタックの下にレンダリングされます。関数が配列のキー `'bottom' => true` を返す場合、パネルは一番下にレンダリングされます。 diff --git a/tracy/ja/guide.texy b/tracy/ja/guide.texy new file mode 100644 index 0000000000..e7dacd570c --- /dev/null +++ b/tracy/ja/guide.texy @@ -0,0 +1,217 @@ +はじめに +**** + +<div class=perex> + +Tracyライブラリは、PHPプログラマーにとって日常的に役立つツールです。以下の点で役立ちます: + +- エラーを素早く発見し修正する +- エラーをログに記録する +- 変数を出力する +- スクリプトやデータベースクエリの時間を計測する +- メモリ使用量を監視する + +</div> + + +PHPは、開発者にかなりの自由度を与えるため、発見しにくいエラーを作成するのに適した言語です。それだけに、デバッグツールTracyは価値があります。PHPの診断ツールの中で、絶対的な最高峰を表しています。 + +もし今日Tracyに初めて出会うなら、あなたの人生はTracy以前とTracyと共にに分かれ始めるでしょう。より良い部分へようこそ! + + +インストール +====== + +Tracyをインストールする最良の方法は、[最新のパッケージをダウンロードする](https://github.com/nette/tracy/releases)か、Composerを使用することです: + +```shell +composer require tracy/tracy +``` + +[tracy.phar |https://github.com/nette/tracy/releases] ファイルとして完全なパッケージをダウンロードすることもできます。 + + +使用法 +=== + +Tracyは、プログラムの開始時にできるだけ早く、いかなる出力も送信する前に `Tracy\Debugger::enable()` メソッドを呼び出すことで有効化します: + +```php +use Tracy\Debugger; + +require 'vendor/autoload.php'; // または tracy.phar + +Debugger::enable(); +``` + +ページ上で最初に気づくことは、右下隅にあるTracy Barです。もしそれが見えない場合、Tracyが本番モードで実行されていることを意味するかもしれません。 Tracyはセキュリティ上の理由から、localhost上でのみ表示されます。機能するかどうかをテストするために、`Debugger::enable(Debugger::Development)` パラメータを使用して一時的に開発モードに切り替えることができます。 + + +Tracy Bar +========= + +Tracy Barは、ページの右下隅に表示されるフローティングパネルです。マウスで移動でき、ページを再読み込みした後もその位置を記憶します。 + +[* tracy-bar.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html + +Tracy Barには、その他の便利なパネルを追加できます。多くは [アドオン |https://componette.org] で見つけることができ、[自分で作成することもできます |extensions]。 + +Tracy Barを表示したくない場合は、次のように設定します: + +```php +Debugger::$showBar = false; +``` + + +エラーと例外の視覚化 +========== + +PHPがどのようにエラーを通知するか、きっとよくご存知でしょう:ページのソースコードにこのようなものを出力します: + +/--pre .{font-size: 90%} +<b>Parse error</b>: syntax error, unexpected '}' in <b>HomePresenter.php</b> on line <b>15</b> +\-- + +またはキャッチされなかった例外の場合: + +/--pre .{font-size: 90%} +<b>Fatal error</b>: Uncaught Nette\MemberAccessException: Call to undefined method Nette\Application\UI\Form::addTest()? in /sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php:100 +Stack trace: +#0 /sandbox/vendor/nette/utils/src/Utils/Object.php(75): Nette\Utils\ObjectMixin::call(Object(Nette\Application\UI\Form), 'addTest', Array) +#1 /sandbox/app/Forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) +#2 /sandbox/app/Presentation/Sign/SignPresenter.php(21): App\Forms\SignFormFactory->create() +#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presentation\Sign\SignPresenter->createComponentSignInForm('signInForm') +#4 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(139): Nette\ComponentModel\Container->createComponent('signInForm') +#5 /sandbox/temp/cache/latte/15206b353f351f6bfca2c36cc.php(17): Nette\ComponentModel\Co in <b>/sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php</b> on line <b>100</b><br /> +\-- + +このような出力では、理解するのは必ずしも簡単ではありません。Tracyを有効にすると、エラーまたは例外はまったく異なる形式で表示されます: + +[* tracy-exception.webp .{url:-} *] + +エラーメッセージは文字通り叫んでいます。エラーが発生した場所で強調表示された行を含むソースコードの一部と、*Call to undefined method Nette\Http\User::isLogedIn()* という情報が、どのようなエラーであるかを分かりやすく説明しています。ページ全体がさらにインタラクティブで、クリックしてより詳細な情報へ進むことができます。[試してみてください |https://nette.github.io/tracy/tracy-exception.html]。 + +そして、ご存知ですか?この方法で、致命的なエラーもキャッチして表示します。いかなる拡張機能もインストールする必要なく。 + +[* tracy-error.webp .{url:-} *] + +変数名のタイプミスや存在しないファイルを開こうとする試みのようなエラーは、E_NOTICEまたはE_WARNINGレベルのメッセージを生成します。これらはページのグラフィック内で簡単に見落とすことができ、まったく見えないことさえあります(ページのコードを見ない限り)。 + +[* tracy-notice2.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html + +または、エラーと同様に表示されることもあります: + +```php +Debugger::$strictMode = true; // すべてのエラーを表示 +Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // 非推奨の通知を除くすべてのエラー +``` + +[* tracy-notice.webp .{url:-} *] + +注釈:Tracyは有効化後、エラー報告レベルをE_ALLに変更します。この値を変更したい場合は、`enable()` の呼び出し後に行ってください。 + + +開発モード vs 本番モード +============== + +ご覧のとおり、Tracyはかなりおしゃべりです。これは開発環境では評価できますが、本番サーバーでは完全な災害を引き起こすでしょう。そこではデバッグ情報は出力してはなりません。そのためTracyは**環境の自動検出機能**を備えており、もし例を本番サーバー上で実行すると、エラーは表示される代わりにログに記録され、訪問者はユーザーフレンドリーなメッセージのみを見ることになります: + +[* tracy-error2.webp .{url:-} *] + +本番モードは、[dump() |dumper] を使用して私たちが外部に送信するすべてのデバッグ情報の表示を抑制し、そしてもちろん、PHPが生成するすべてのエラーメッセージも抑制します。もしコード内に何らかの `dump($obj)` を忘れてしまった場合でも、心配する必要はありません。本番サーバーでは何も出力されません。 + +モードの自動検出はどのように機能しますか?モードが開発モードになるのは、アプリケーションがlocalhost上で(つまりIPアドレス `127.0.0.1` または `::1`)実行されており、プロキシが存在しない場合(つまりそのHTTPヘッダーがない場合)です。それ以外の場合は本番モードで実行されます。 + +開発モードを他のケースでも有効にしたい場合、例えば特定のIPアドレスからアクセスするプログラマーに、それを `enable()` メソッドのパラメータとして指定します: + +```php +Debugger::enable('23.75.345.200'); // IPアドレスの配列を指定することもできます +``` + +IPアドレスとクッキーを組み合わせることを強く推奨します。`tracy-debug` クッキーに秘密のトークン、例えば `secret1234` を保存し、この方法で特定のIPアドレスからアクセスし、クッキーに言及されたトークンを持っているプログラマー専用の開発モードを有効化します: + +```php +Debugger::enable('secret1234@23.75.345.200'); +``` + +開発/本番モードは、定数 `Debugger::Development` または `Debugger::Production` を `enable()` メソッドのパラメータとして使用して直接設定することもできます。 + +.[note] +Nette Frameworkを使用している場合、[それに対するモードの設定方法 |application:bootstrapping#開発環境 vs 本番環境] を確認してください。そしてそれはその後Tracyにも使用されます。 + + +エラーロギング +======= + +本番モードでは、Tracyは自動的にすべてのエラーとキャッチされた例外をテキストログに記録します。ロギングが行われるためには、ログディレクトリへの絶対パスを変数 `$logDirectory` に設定するか、`enable()` メソッドの2番目のパラメータとして渡す必要があります: + +```php +Debugger::$logDirectory = __DIR__ . '/log'; +``` + +エラーロギングは非常に便利です。想像してみてください。あなたのアプリケーションのすべてのユーザーが実際にはベータテスターであり、無料でエラー発見の優れた仕事を実行していること、そして、もし彼らの貴重なレポートを気づかずにゴミ箱に捨ててしまったら、あなたは愚かなことをするでしょう。 + +カスタムメッセージやあなたがキャッチした例外をログに記録する必要がある場合、そのためには `log()` メソッドを使用します: + +```php +Debugger::log('予期せぬエラーが発生しました'); // テキストメッセージ + +try { + kritickaOperace(); // クリティカルな操作 +} catch (Exception $e) { + Debugger::log($e); // 例外もログに記録できます + // または + Debugger::log($e, Debugger::ERROR); // Eメール通知も送信します +} +``` + +Tracyに `E_NOTICE` や `E_WARNING` のようなPHPエラーを詳細情報(HTMLレポート)付きでログ記録させたい場合は、`Debugger::$logSeverity` を設定してください: + +```php +Debugger::$logSeverity = E_NOTICE | E_WARNING; +``` + +真のプロフェッショナルにとって、エラーログは重要な情報源であり、すべての新しいエラーについてすぐに通知を受け取りたいと考えています。Tracyはその要望に応えます。なぜなら、ログの新しいエントリについてEメールで通知することができるからです。Eメールの送信先は変数 `$email` で指定します: + +```php +Debugger::$email = 'admin@example.com'; +``` + +完全なNette Frameworkを使用している場合、これや他の設定を [設定ファイル |nette:configuring] で行うことができます。 + +しかし、あなたのEメール受信箱を溢れさせないために、常に**1通のメッセージのみ**を送信し、`email-sent` ファイルを作成します。開発者はEメール通知を受け取った後、ログを確認し、アプリケーションを修正し、そして監視ファイルを削除します。これによりEメール送信が再び有効になります。 + + +エディタで開く +======= + +エラーページが表示されたとき、ファイル名をクリックでき、そしてそれらはあなたのエディタでカーソルが該当する行にある状態で開かれます。ファイルを作成する(アクション `create file`)ことや、それらの中でエラーを修正する(アクション `fix it`)こともできます。これが機能するためには、[ブラウザとシステムを設定する |open-files-in-ide] だけで十分です。 + + +サポートされているPHPバージョン +================= + +| Tracy | PHPと互換性あり +|-----------|------------------- +| Tracy 2.10 – 3.0 | PHP 8.0 – 8.4 +| Tracy 2.9 | PHP 7.2 – 8.2 +| Tracy 2.8 | PHP 7.2 – 8.1 +| Tracy 2.6 – 2.7 | PHP 7.1 – 8.0 +| Tracy 2.5 | PHP 5.4 – 7.4 +| Tracy 2.4 | PHP 5.4 – 7.2 + +最新のパッチバージョンに適用されます。 + + +ポート +=== + +これは他のフレームワークやCMS用の非公式ポートのリストです: + +- [Drupal 7](https://www.drupal.org/project/traced) +- Laravel framework: [recca0120/laravel-tracy](https://github.com/recca0120/laravel-tracy), [whipsterCZ/laravel-tracy](https://github.com/whipsterCZ/laravel-tracy) +- [OpenCart](https://github.com/BurdaPraha/oc_tracy) +- [ProcessWire CMS/CMF](https://github.com/adrianbj/TracyDebugger) +- [Slim Framework](https://github.com/runcmf/runtracy) +- Symfony framework: [kutny/tracy-bundle](https://github.com/kutny/tracy-bundle), [VasekPurchart/Tracy-Blue-Screen-Bundle](https://github.com/VasekPurchart/Tracy-Blue-Screen-Bundle) +- [Wordpress](https://github.com/ktstudio/WP-Tracy) diff --git a/tracy/ja/open-files-in-ide.texy b/tracy/ja/open-files-in-ide.texy new file mode 100644 index 0000000000..1b70daca07 --- /dev/null +++ b/tracy/ja/open-files-in-ide.texy @@ -0,0 +1,144 @@ +Tracyからエディタでファイルを開く方法は? (IDEとの統合) +********************************* + +.[perex] +エラーページが表示されたとき、ファイル名をクリックでき、そしてそれらはあなたのエディタでカーソルが該当する行にある状態で開かれます。ファイルを作成する(アクション `create file`)ことや、それらの中でエラーを修正する(アクション `fix it`)こともできます。これを実現するためには、ブラウザとシステムを設定する必要があります。 + +Tracyは `editor://open/?file=%file&line=%line` の形式のURL経由でファイルを開きます。つまり `editor://` プロトコルで。そのためにカスタムハンドラを登録します。それは任意の実行可能ファイルにすることができ、パラメータを「処理」し、そして私たちのお気に入りのエディタを起動します。 + +URLは変数 `Tracy\Debugger::$editor` で変更できます。または、`Tracy\Debugger::$editor = null` の設定によってクリックを無効にすることもできます。 + + +Windows +======= + +1. [Tracyリポジトリ](https://github.com/nette/tracy/tree/master/tools/open-in-editor/windows)から関連ファイルをディスクにダウンロードしてください。 + +2. `open-editor.js` ファイルを編集し、`settings` 配列内でコメント解除し、そして必要であれば、エディタへのパスを編集してください: + +```js +var settings = { + + // PhpStorm + editor: '"C:\\Program Files\\JetBrains\\PhpStorm 2018.1.2\\bin\\phpstorm64.exe" --line %line% "%file%"', + title: 'PhpStorm', + + // NetBeans + // editor: '"C:\\Program Files\\NetBeans 8.1\\bin\\netbeans.exe" "%file%:%line%" --console suppress', + + // Sublime Text 2 + // editor: '"C:\\Program Files\\Sublime Text 2\\sublime_text.exe" "%file%:%line%"', + + ... +} + +... +``` + +注意:パス内の二重スラッシュは保持してください。 + +3. システムに `editor://` プロトコルのハンドラを登録してください。 + +これは `install.cmd` ファイルを実行することで行います。**管理者として実行する必要があります。** スクリプト `open-editor.js` は今後 `editor://` プロトコルを処理します。 + +他のサーバーで生成されたリンク、例えば本番サーバーやDocker内などで開けるようにするため、`open-editor.js` にリモートURLからローカルURLへのマッピングを追加してください: + +```js + mappings: { + // リモートパス: ローカルパス + '/var/www/nette.app': 'W:\\Nette.web\\_web', + } +``` + + +Linux +===== + +1. [Tracyリポジトリ](https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux)から関連ファイルを `~/bin` ディレクトリにダウンロードしてください。 + +2. `open-editor.sh` ファイルを編集し、コメント解除し、そして必要であれば変数 `editor` でエディタへのパスを編集してください。 + +```shell +#!/bin/bash + +# Emacs +#editor='emacs +$LINE "$FILE"' + +# gVim +#editor='gvim +$LINE "$FILE"' + +# gEdit +#editor='gedit +$LINE "$FILE"' + +# Pluma +#editor='pluma +$LINE "$FILE"' + +... +``` + +ファイルを実行可能にしてください: + +```shell +chmod +x ~/bin/open-editor.sh +``` + +.[note] +使用しているエディタがパッケージからインストールされていない場合、バイナリのパスが $PATH に含まれていない可能性があります。これは簡単に修正できます。`~/bin` ディレクトリにエディタのバイナリへのシンボリックリンクを作成してください。 + + +3. システムに `editor://` プロトコルのハンドラを登録してください。 + +これは `install.sh` ファイルを実行することで行います。スクリプト `open-editor.sh` は今後 `editor://` プロトコルを処理します。 + + +macOS +===== + +PhpStormやTextMateなどのエディタは、特別なURL経由でのファイルオープンを可能にします。それを設定するだけで十分です: + +```php +// PhpStorm +Tracy\Debugger::$editor = 'phpstorm://open?file=%file&line=%line'; +// TextMate +Tracy\Debugger::$editor = 'txmt://open/?url=file://%file&line=%line'; +// MacVim +Tracy\Debugger::$editor = 'mvim://open?url=file:///%file&line=%line'; +// Visual Studio Code +Tracy\Debugger::$editor = 'vscode://file/%file:%line'; +``` + +スタンドアロンのTracyを使用している場合、行を `Tracy\Debugger::enable()` の前に挿入してください。Netteの場合は、`Bootstrap.php` 内の `$configurator->enableTracy()` の前に挿入してください。 + +アクション `create file` または `fix it` は、残念ながらmacOSでは機能しません。 + + +デモ +====== + +エラー修正: + +<iframe width="560" height="315" src="https://www.youtube.com/embed/3ITT4mC0Eq4?rel=0&showinfo=0" frameborder="0" allow="encrypted-media" allowfullscreen></iframe> + +ファイル作成: + +<iframe width="560" height="315" src="https://www.youtube.com/embed/AJ_FUivAGZQ?rel=0&showinfo=0" frameborder="0" allow="encrypted-media" allowfullscreen></iframe> + + +トラブルシューティング +=========== + +- Firefoxでは、about:config で `network.protocol-handler.expose.editor` を `false` に、そして `network.protocol-handler.expose-all` を `true` に [設定 |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] することでプロトコルを有効にする必要があるかもしれません。 +- すぐに機能しない場合、パニックにならないでください。そしてそのリンクをクリックする前にページを数回リフレッシュしてみてください。動き出します! +- ここに、考えられるエラーの修正への [リンク |https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/] があります: `Input Error: There is no script engine for file extension ".js"`, `Maybe you associated ".js" file to another app, not JScript engine.` または `拡張子 .js にはスクリプトエンジンが利用できません`。 + +Google Chrome バージョン77以降では、エディタがリンク経由で起動されたとき、「このタイプのリンクは常に関連付けられたアプリケーションで開く」チェックボックスは表示されなくなります。Windows向けの解決策:`fix.reg` ファイルを作成してください: + +``` +Windows Registry Editor Version 5.00 +[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\URLWhitelist] +"123"="editor://*" +``` +ダブルクリックしてインポートし、そしてChromeブラウザを再起動してください。 + + +質問やコメントがある場合、[フォーラム |https://forum.nette.org] にお問い合わせください。 diff --git a/tracy/ja/recipes.texy b/tracy/ja/recipes.texy new file mode 100644 index 0000000000..2632d11414 --- /dev/null +++ b/tracy/ja/recipes.texy @@ -0,0 +1,176 @@ +ガイド +*** + + +Content Security Policy +======================= + +あなたのウェブサイトがContent Security Policyを使用している場合、Tracyが正しく機能するように、同じ `'nonce-<value>'` と `'strict-dynamic'` を `script-src` に追加する必要があります。一部のサードパーティ製アドオンは追加の設定が必要になる場合があります。 Nonceは `style-src` ディレクティブではサポートされていません。このディレクティブを使用している場合、`'unsafe-inline'` を追加する必要がありますが、本番モードではこれを避けるべきです。 + +[Nette Framework |nette:configuring] の設定例: + +```neon +http: + csp: + script-src: [nonce, strict-dynamic] +``` + +純粋なPHPでの例: + +```php +$nonce = base64_encode(random_bytes(20)); +header("Content-Security-Policy: script-src 'nonce-$nonce' 'strict-dynamic';"); +``` + + +より高速な読み込み +========= + +起動は簡単ですが、ウェブページ上に読み込みが遅いブロッキングスクリプトがある場合、Tracyの読み込みを遅くする可能性があります。 解決策は、`<?php Tracy\Debugger::renderLoader() ?>` をあなたのテンプレートのすべてのスクリプトの前に配置することです: + +```latte +<!DOCTYPE html> +<html> +<head> + <title>... + + + + +``` + + +AJAXリクエストのデバッグ +============== + +Tracyは、jQueryまたはネイティブAPI `fetch` を使用して作成されたAJAXリクエストを自動的にキャッチします。リクエストはTracyバーに追加の行として表示され、これによりAJAXのデバッグが簡単かつ便利になります。 + +AJAXリクエストを自動的にキャッチしたくない場合、JavaScript変数を設定することでこの機能を無効にできます: + +```js +window.TracyAutoRefresh = false; +``` + +特定のAJAXリクエストを手動で監視するには、HTTPヘッダー `X-Tracy-Ajax` を `Tracy.getAjaxHeader()` が返す値で追加してください。ここに `fetch` 関数を使用した例を示します: + +```js +fetch(url, { + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Tracy-Ajax': Tracy.getAjaxHeader(), + } +}) +``` + +このアプローチにより、AJAXリクエストの選択的なデバッグが可能になります。 + + +データストレージ +======== + +TracyはTracyバーにパネルを表示でき、そしてAJAXリクエストやリダイレクト用のBluescreenも表示できます。Tracyは独自のセッションを作成し、データは独自の一時ファイルに保存し、そして `tracy-session` クッキーを使用します。 + +Tracyは、Tracyを有効にする前に開始するネイティブPHPセッションを使用するように設定することもできます: + +```php +session_start(); +Debugger::setSessionStorage(new Tracy\NativeSession); +Debugger::enable(); +``` + +セッションの開始がより複雑な初期化を要求する場合、Tracyをすぐに起動できます(発生する可能性のあるエラーを処理できるように)。そしてその後、セッションハンドラを初期化し、そして最後にTracyにセッションが使用準備完了であることを `dispatch()` 関数を使用して通知します: + +```php +Debugger::setSessionStorage(new Tracy\NativeSession); +Debugger::enable(); + +// セッションの初期化が続く +// そしてセッションの開始 +session_start(); + +Debugger::dispatch(); +``` + +`setSessionStorage()` 関数はバージョン2.9から存在します。それ以前は、Tracyは常にネイティブPHPセッションを使用していました。 + + +カスタムスクラバー +========= + +スクラバーは、ダンプ時にパスワードやアクセス認証情報などの機密データの漏洩を防ぐフィルタです。フィルタはダンプされた配列またはオブジェクトの各要素に対して呼び出され、値が機密である場合に `true` を返します。その場合、値の代わりに `*****` が出力されます。 + +```php +// `password`、`password_repeat`、`check_password`、`DATABASE_PASSWORD` などのキーやプロパティの値の出力を防ぎます +$scrubber = function(string $key, $value, ?string $class): bool +{ + return preg_match('#password#i', $key) && $value !== null; +}; + +// BlueScreen内のすべてのダンプに使用します +Tracy\Debugger::getBlueScreen()->scrubber = $scrubber; +``` + + +カスタムロガー +======= + +エラー、キャッチされなかった例外をログに記録し、そして `Tracy\Debugger::log()` メソッドによっても呼び出される独自のロガーを作成できます。ロガーは [api:Tracy\ILogger |api:Tracy\ILogger] インターフェースを実装します。 + +```php +use Tracy\ILogger; + +class SlackLogger implements ILogger +{ + public function log($value, $priority = ILogger::INFO) + { + // Slackにリクエストを送信します + } +} +``` + +そしてその後、それを有効化します: + +```php +Tracy\Debugger::setLogger(new SlackLogger); +``` + +完全なNette Frameworkを使用している場合、NEON設定ファイルで設定できます: + +```neon +services: + tracy.logger: SlackLogger +``` + + +Monologの統合 +---------- + +Tracyパッケージは、[monolog/monolog](https://github.com/Seldaek/monolog) の統合を可能にするPSR-3アダプタを提供します。 + +```php +$monolog = new Monolog\Logger('main-channel'); +$monolog->pushHandler(new Monolog\Handler\StreamHandler($logFilePath, Monolog\Logger::DEBUG)); + +$tracyLogger = new Tracy\Bridges\Psr\PsrToTracyLoggerAdapter($monolog); +Debugger::setLogger($tracyLogger); +Debugger::enable(); + +Debugger::log('info'); // 書き込み: [] main-channel.INFO: info [] [] +Debugger::log('warning', Debugger::WARNING); // 書き込み: [] main-channel.WARNING: warning [] [] +``` + + +nginx +===== + +nginxサーバーでTracyが機能しない場合、おそらく設定が間違っています。設定に次のようなものがある場合: + +```nginx +try_files $uri $uri/ /index.php; +``` + +それを次のように変更してください: + +```nginx +try_files $uri $uri/ /index.php$is_args$args; +``` diff --git a/tracy/ja/stopwatch.texy b/tracy/ja/stopwatch.texy new file mode 100644 index 0000000000..d31c908eda --- /dev/null +++ b/tracy/ja/stopwatch.texy @@ -0,0 +1,35 @@ +時間計測 +**** + +デバッガのもう一つの便利なツールは、マイクロ秒精度のストップウォッチです: + +```php +Debugger::timer(); + +// 時間のかかる操作... +sleep(2); + +$elapsed = Debugger::timer(); +// $elapsed = 2 +``` + +オプションのパラメータを使用することで、複数回の計測を達成できます。 + +```php +Debugger::timer('page-generating'); +// 何らかのコード + +Debugger::timer('rss-generating'); +// 何らかのコード + +$rssElapsed = Debugger::timer('rss-generating'); +$pageElapsed = Debugger::timer('page-generating'); +``` + +```php +Debugger::timer(); // ストップウォッチを開始します + +... // 時間のかかる操作 + +echo Debugger::timer(); // 経過時間を秒単位で出力します +``` diff --git a/tracy/pl/@home.texy b/tracy/pl/@home.texy index 7dffcf12b3..b9d5710dfa 100644 --- a/tracy/pl/@home.texy +++ b/tracy/pl/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Tracy - narzędzie do tuningu, z którym z radością można popełniać błędy}} -{description:Tracy to narzędzie zaprojektowane w celu ułatwienia debugowania kodu PHP. Jest to przydatne narzędzie dla wszystkich programistów PHP, które pomaga w wizualizacji i logowaniu błędów, zrzucaniu zmiennych i wielu innych. Tracy jest uzależniająca!}} +{{maintitle: Tracy – narzędzie do debugowania, z którym błędy stają się przyjemnością}} +{description: Tracy to narzędzie przeznaczone do ułatwienia debugowania kodu PHP. Jest użytecznym pomocnikiem dla wszystkich programistów PHP, którym pomaga w wizualizacji i logowaniu błędów, dumpowaniu zmiennych i wielu innych. Uwaga: Tracy uzależnia!}} diff --git a/tracy/pl/@left-menu.texy b/tracy/pl/@left-menu.texy index eeff0171a6..602efb5f03 100644 --- a/tracy/pl/@left-menu.texy +++ b/tracy/pl/@left-menu.texy @@ -1,7 +1,7 @@ -- [Zaczynając od Tracy |guide] -- [dumping |dumper] -- [Czas pomiaru |stopwatch] +- [Zaczynamy z Tracy |guide] +- [Dumpowanie |dumper] +- [Pomiar czasu |stopwatch] - [Konfiguracja |configuring] -- [Tutoriale |recipes] +- [Poradniki |recipes] - [Integracja z IDE |open-files-in-ide] - [Tworzenie rozszerzeń |extensions] diff --git a/tracy/pl/@meta.texy b/tracy/pl/@meta.texy new file mode 100644 index 0000000000..74de69cd94 --- /dev/null +++ b/tracy/pl/@meta.texy @@ -0,0 +1 @@ +{{sitename: Dokumentacja Tracy}} diff --git a/tracy/pl/configuring.texy b/tracy/pl/configuring.texy index aad25014bd..e2bac61310 100644 --- a/tracy/pl/configuring.texy +++ b/tracy/pl/configuring.texy @@ -1,142 +1,141 @@ Konfiguracja Tracy ****************** -Wszystkie przykłady zakładają, że alias został utworzony: +Wszystkie przykłady zakładają utworzony alias: ```php use Tracy\Debugger; ``` -Rejestrowanie błędów .[#toc-error-logging] ------------------------------------------- +Logowanie błędów +---------------- ```php $logger = Debugger::getLogger(); -// email, na który wysyłane są powiadomienia o wystąpieniu błędu -$logger->email = 'dev@example.com'; // (string|string[]) domyślnie nie jest ustawiony +// e-mail, na który wysyłane są powiadomienia o błędach +$logger->email = 'dev@example.com'; // (string|string[]) domyślnie nieustawione -// nadawca wiadomości e-mail -$logger->fromEmail = 'me@example.com'; // (string) domyślnie nie jest ustawiony +// nadawca e-maila +$logger->fromEmail = 'me@example.com'; // (string) domyślnie nieustawione -// procedura zapewniająca wysłanie wiadomości e-mail -$logger->mailer = /* ... */; // (callable) domyślnie wysyłamy za pomocą mail() +// procedura zapewniająca wysłanie e-maila +$logger->mailer = /* ... */; // (callable) domyślnie wysyłanie za pomocą funkcji mail() -// Jaki jest najkrótszy czas na wysłanie kolejnego maila? -$logger->emailSnooze = /* ... */; // (string) domyślnie "2 dni +// po jakim najkrótszym czasie wysłać następny e-mail? +$logger->emailSnooze = /* ... */; // (string) domyślnie '2 days' -// dla jakich poziomów błędów jest BlueScreen logging too? -Debugger::$logSeverity = E_WARNING | E_NOTICE; // domyślnie 0 (brak poziomów błędów) +// dla jakich poziomów błędów logowany jest również BlueScreen? +Debugger::$logSeverity = E_WARNING | E_NOTICE; // domyślnie 0 (brak poziomów błędów) ``` -Zachowanie `dump()` .[#toc-dump-behavior] ------------------------------------------ +Zachowanie `dump()` +------------------- ```php -// maksymalna długość łańcucha -Debugger::$maxLength = 150; // (int) domyślnie w wersji Tracy +// maksymalna długość ciągu +Debugger::$maxLength = 150; // (int) domyślnie zgodnie z wersją Tracy -// maksymalna głębokość zanurzenia -Debugger::$maxDepth = 10; // (int) domyślnie przez wersję Tracy +// maksymalna głębokość zagnieżdżenia +Debugger::$maxDepth = 10; // (int) domyślnie zgodnie z wersją Tracy -// ukryć wartości tych kluczy (od Tracy 2.8) -Debugger::$keysToHide = ['password', /* ... */]; // (string[]) defaults to [] +// ukryj wartości tych kluczy (od Tracy 2.8) +Debugger::$keysToHide = ['password', /* ... */]; // (string[]) domyślnie [] // motyw wizualny (od Tracy 2.8) -Debugger::$dumpTheme = 'dark'; // (light|dark) domyślnie 'light' +Debugger::$dumpTheme = 'dark'; // (light|dark) domyślnie 'light' -// pokaż, gdzie została wywołana funkcja dump()? -Debugger::$showLocation = /* ... */; // (bool) domyślnie Tracy +// wyświetlić miejsce, w którym została wywołana funkcja dump()? +Debugger::$showLocation = /* ... */; // (bool) domyślnie zgodnie z wersją Tracy ``` -Inne .[#toc-others] -------------------- +Inne +---- ```php -// w trybie deweloperskim pokaż błędy typu powiadomienie lub ostrzeżenie jako BlueScreen -Debugger::$strictMode = /* ... */; // (bool|int) domyślnie false, można wybrać tylko określone poziomy błędów (np. E_USER_DEPRECATED | E_DEPRECATED) +// w trybie deweloperskim wyświetla błędy typu notice lub warning jako BlueScreen +Debugger::$strictMode = /* ... */; // (bool|int) domyślnie false, można wybrać tylko niektóre poziomy błędów (np. E_USER_DEPRECATED | E_DEPRECATED) -// pokazać wyciszone (@) komunikaty o błędach? -Debugger::$scream = /* ... */; // (bool|int) domyślnie false, od wersji 2.9 można wybrać tylko niektóre poziomy błędów (np. E_USER_DEPRECATED | E_DEPRECATED) +// wyświetlać wyciszone (@) komunikaty błędów? +Debugger::$scream = /* ... */; // (bool|int) domyślnie false, od wersji 2.9 można wybrać tylko niektóre poziomy błędów (np. E_USER_DEPRECATED | E_DEPRECATED) // format linku do otwarcia w edytorze -Debugger::$editor = /* ... */; // (string|null) domyślnie 'editor://open/?file=%file&line=%line' +Debugger::$editor = /* ... */; // (string|null) domyślnie 'editor://open/?file=%file&line=%line' // ścieżka do szablonu z własną stroną dla błędu 500 -Debugger::$errorTemplate = /* ... */; // (string) domyślnie nie jest ustawiony +Debugger::$errorTemplate = /* ... */; // (string) domyślnie nieustawione -//pokazać Tracy Bar? -Debugger::$showBar = /* ... */; // (bool) domyślnie jest true +// wyświetlać Pasek Tracy? +Debugger::$showBar = /* ... */; // (bool) domyślnie true Debugger::$editorMapping = [ - // oryginał => nowy + // oryginał => nowa '/var/www/html' => '/data/web', '/home/web' => '/srv/html', ]; ``` -Ramy sieciowe .[#toc-nette-framework] -------------------------------------- +Nette Framework +--------------- -Jeśli używasz Nette Framework, możesz również skonfigurować Tracy i dodać nowe panele do Tracy Bar za pomocą pliku konfiguracyjnego. -Możesz ustawić parametry w konfiguracji, a także dodać nowe panele do Tracy Bar. Te ustawienia są stosowane tylko po utworzeniu kontenera DI, więc błędy wykonane wcześniej nie mogą ich odzwierciedlać. +Jeśli używasz Nette Framework, możesz konfigurować Tracy i dodawać nowe panele do Paska Tracy również za pomocą pliku konfiguracyjnego. W konfiguracji można ustawiać parametry oraz dodawać nowe panele do Paska Tracy. Te ustawienia są stosowane dopiero po utworzeniu kontenera DI, więc błędy powstałe wcześniej nie mogą ich odzwierciedlać. -Konfiguracja rejestrowania błędów: +Konfiguracja logowania błędów: ```neon tracy: - # email, na który wysyłane są powiadomienia o wystąpieniu błędu - email: dev@example.com # (string|string[]) domyślnie nie jest ustawiony + # e-mail, na który wysyłane są powiadomienia o błędach + email: dev@example.com # (string|string[]) domyślnie nieustawione - # nadawca emaila - fromEmail: robot@example.com # (string) domyślnie nie jest ustawiony + # nadawca e-maila + fromEmail: robot@example.com # (string) domyślnie nieustawione - # czas opóźnienia wysyłania e-maili (od Tracy 2.8.8) - emailSnooze: ... # (string) default is '2 days' + # czas odroczenia wysyłania e-maili (od Tracy 2.8.8) + emailSnooze: ... # (string) domyślnie '2 days' # używać Nette mailer do wysyłania e-maili? (od Tracy 2.5) - netteMailer: ... # (bool) domyślnie jest true + netteMailer: ... # (bool) domyślnie true - # dla których poziomów błędów BlueScreen jest również rejestrowany? - logSeverity: [E_WARNING, E_NOTICE] # domyślnie [] + # dla jakich poziomów błędów logowany jest również BlueScreen? + logSeverity: [E_WARNING, E_NOTICE] # domyślnie [] ``` -Skonfiguruj zachowanie strony `dump()`: +Konfiguracja zachowania funkcji `dump()`: ```neon tracy: - # maksymalna długość łańcucha - maxLength: 150 # (int) domyślnie według wersji Tracy + # maksymalna długość ciągu + maxLength: 150 # (int) domyślnie zgodnie z wersją Tracy - # maksymalna głębokość zanurzenia - maxDepth: 10 # (int) domyślnie w wersji Tracy + # maksymalna głębokość zagnieżdżenia + maxDepth: 10 # (int) domyślnie zgodnie z wersją Tracy - # ukryć wartości tych kluczy (od Tracy 2.8) - keysToHide: [password, pass] # (string[]) domyślnie [] + # ukryj wartości tych kluczy (od Tracy 2.8) + keysToHide: [password, pass] # (string[]) domyślnie [] - # visual theme (od Tracy 2.8) - dumpTheme: dark # (light|dark) domyślnie 'light' + # motyw wizualny (od Tracy 2.8) + dumpTheme: dark # (light|dark) domyślnie 'light' - # pokaż, gdzie została wywołana funkcja dump()? - showLocation: ... # (bool) domyślnie według wersji Tracy + # wyświetlić miejsce, w którym została wywołana funkcja dump()? + showLocation: ... # (bool) domyślnie zgodnie z wersją Tracy ``` -Instalacja rozszerzenia Tracy: +Instalacja rozszerzeń Tracy: ```neon tracy: - # přidá panely do Tracy Bar + # dodaje panele do Paska Tracy bar: - Nette\Bridges\DITracy\ContainerPanel - IncludePanel - XDebugHelper('myIdeKey') - MyPanel(@MyService) - # přidá panely do BlueScreen + # dodaje panele do BlueScreen blueScreen: - DoctrinePanel::renderException ``` @@ -145,25 +144,37 @@ Inne opcje: ```neon tracy: - # w trybie deweloperskim pokazuj powiadomienia lub błędy ostrzegawcze jako BlueScreen - strictMode: ... # domyślnie jest to prawda + # w trybie deweloperskim wyświetla błędy typu notice lub warning jako BlueScreen + strictMode: ... # domyślnie true - # wyświetlić wyciszone (@) komunikaty o błędach? - scream: ... # domyślnie jest to fałsz + # wyświetlać wyciszone (@) komunikaty błędów? + scream: ... # domyślnie false # format linku do otwarcia w edytorze editor: ... # (string) domyślnie 'editor://open/?file=%file&line=%line' # ścieżka do szablonu z własną stroną dla błędu 500 - errorTemplate: ... # (string) domyślnie nie jest ustawiony + errorTemplate: ... # (string) domyślnie nieustawione - # show tracy bar? - showBar: ... # (bool) domyślnie jest true + # wyświetlać Pasek Tracy? + showBar: ... # (bool) domyślnie true editorMapping: - # original: new + # oryginał: nowa /var/www/html: /data/web /home/web: /srv/html ``` -Wartości opcji `logSeverity`, `strictMode` i `scream` można zapisać jako pola poziomu błędu (np. `[E_WARNING, E_NOTICE]`), lub jako wyrażenie używane w PHP (np. `E_ALL & ~E_NOTICE`). +Wartości opcji `logSeverity`, `strictMode` i `scream` można zapisywać jako tablicę poziomów błędów (np. `[E_WARNING, E_NOTICE]`) lub jako wyrażenie używane w języku PHP (np. `E_ALL & ~E_NOTICE`). + + +Usługi DI +--------- + +Te usługi są dodawane do kontenera DI: + +| Nazwa | Typ | Opis +|---------------------------------------------------------- +| `tracy.logger` | [api:Tracy\ILogger] | logger +| `tracy.blueScreen` | [api:Tracy\BlueScreen] | BlueScreen +| `tracy.bar` | [api:Tracy\Bar] | Pasek Tracy diff --git a/tracy/pl/dumper.texy b/tracy/pl/dumper.texy index 7a8ee9bdbc..9c37f930ff 100644 --- a/tracy/pl/dumper.texy +++ b/tracy/pl/dumper.texy @@ -1,20 +1,20 @@ -Zrzucanie -********* +Dumpowanie +********** -Każdy debugger jest dobrym przyjacielem funkcji [php:var_dump], która szczegółowo wyrzuca zawartość zmiennej. Niestety, w środowisku HTML, zrzut traci formatowanie i zwala się na jedną linię, nie wspominając o sanityzacji kodu HTML. W praktyce konieczne jest zastąpienie `var_dump` funkcją poręczniejszą. To jest `dump()`. +Każdy programista debugujący jest dobrym przyjacielem funkcji [php:var_dump], która szczegółowo wypisuje zawartość zmiennej. Niestety, w środowisku HTML wynik traci formatowanie i zlewa się w jedną linię, nie wspominając o sanitizacji kodu HTML. W praktyce konieczne jest zastąpienie `var_dump` bardziej poręczną funkcją. Tą funkcją jest właśnie `dump()`. ```php $arr = [10, 20.2, true, null, 'hello']; dump($arr); -// nebo Debugger::dump($arr); +// lub Debugger::dump($arr); ``` -generuje wyjście: +generuje wynik: [* dump-basic.webp *] -Możesz zmienić domyślny jasny motyw na ciemny: +Domyślny jasny motyw można zmienić na ciemny: ```php Debugger::$dumpTheme = 'dark'; @@ -22,27 +22,27 @@ Debugger::$dumpTheme = 'dark'; [* dump-dark.webp *] -Możemy również zmienić głębię pola za pomocą [Debugger::$maxDepth |api:Tracy\Debugger::$maxDepth] oraz długość wyświetlanych etykiet za pomocą [Debugger::$maxLength |api:Tracy\Debugger::$maxLength]. Niższe wartości naturalnie przyspieszą działanie debugera. +Możemy również zmienić głębokość zagnieżdżenia za pomocą [Debugger::$maxDepth |api:Tracy\Debugger::$maxDepth] oraz długość wyświetlanych etykiet za pomocą [Debugger::$maxLength |api:Tracy\Debugger::$maxLength]. Niższe wartości naturalnie przyspieszą Tracy. ```php Debugger::$maxDepth = 2; // domyślnie: 3 Debugger::$maxLength = 50; // domyślnie: 150 ``` -Funkcja `dump()` może również wyprowadzać inne przydatne informacje. Stała `Tracy\Dumper::LOCATION_SOURCE` dodaje tooltip ze ścieżką do miejsca, w którym wywołano funkcję. `Tracy\Dumper::LOCATION_LINK` daje nam link do tego miejsca. `Tracy\Dumper::LOCATION_CLASS` Dla każdego zrzuconego obiektu wypisuje tooltip ze ścieżką do pliku definiującego jego klasę. Stałe ustawiamy w zmiennej `Debugger::$showLocation` przed wywołaniem `dump()`. Jeśli chcemy ustawić wiele wartości naraz, konkatenujemy je za pomocą operatora `|`. +Funkcja `dump()` potrafi wypisać również inne przydatne informacje. Stała `Tracy\Dumper::LOCATION_SOURCE` dodaje podpowiedź (tooltip) ze ścieżką do miejsca, w którym funkcja została wywołana. `Tracy\Dumper::LOCATION_LINK` dostarcza nam link do tego miejsca. `Tracy\Dumper::LOCATION_CLASS` przy każdym dumpowanym obiekcie wypisuje podpowiedź ze ścieżką do pliku, w którym zdefiniowana jest jego klasa. Stałe ustawia się w zmiennej `Debugger::$showLocation` jeszcze przed wywołaniem `dump()`. Jeśli chcemy ustawić wiele wartości naraz, łączymy je za pomocą operatora `|`. ```php -Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // Ustawia tylko deklarację lokalizacji wywołania funkcji -Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Ustawia zarówno listing linków jak i ścieżkę klasy +Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // Ustawia tylko wyświetlanie informacji o miejscu wywołania funkcji +Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Ustawia jednocześnie wyświetlanie linku i ścieżki do klasy Debugger::$showLocation = false; // Wyłącza wyświetlanie dodatkowych informacji Debugger::$showLocation = true; // Włącza wyświetlanie wszystkich dodatkowych informacji ``` -Praktycznymi alternatywami dla `dump()` są `dumpe()` (dump & exit) oraz `bdump()`. Ten ostatni pozwala nam zrzucić wartość zmiennej w Tracy Bar. Jest to bardzo przydatne, ponieważ zrzuty są oddzielone od układu strony, a dodatkowo możemy umieścić na nich komentarz. +Praktyczną alternatywą dla `dump()` jest `dumpe()` (dump & exit) oraz `bdump()`. Pozwala on nam wypisać wartość zmiennej w panelu Paska Tracy. Jest to bardzo przydatne, ponieważ zrzuty są oddzielone od układu strony, a także możemy do nich dodać komentarz. ```php -bdump([2, 4, 6, 8], 'sudá čísla do deseti'); -bdump([1, 3, 5, 7, 9], 'lichá čísla do deseti'); +bdump([2, 4, 6, 8], 'liczby parzyste do dziesięciu'); +bdump([1, 3, 5, 7, 9], 'liczby nieparzyste do dziesięciu'); ``` [* bardump-cs.webp *] diff --git a/tracy/pl/extensions.texy b/tracy/pl/extensions.texy index 29801a5900..a0c0f2e64b 100644 --- a/tracy/pl/extensions.texy +++ b/tracy/pl/extensions.texy @@ -1,23 +1,23 @@ -Tworzenie rozszerzenia Tracy -**************************** +Tworzenie rozszerzeń dla Tracy +******************************
                                                                                                                            -Tracy zapewnia doskonałe narzędzie do debugowania aplikacji. Czasem jednak chciałoby się mieć pod ręką jakieś dodatkowe informacje. Pokażemy Ci, jak napisać niestandardowe rozszerzenie dla Tracy Bar, aby rozwój był jeszcze przyjemniejszy. +Tracy dostarcza świetne narzędzie do debugowania Twojej aplikacji. Czasami jednak chciałbyś mieć pod ręką również inne informacje. Pokażemy, jak napisać własne rozszerzenie dla Paska Tracy, aby rozwój był jeszcze przyjemniejszy. -- Tworzenie niestandardowego paska dla Tracy Bar +- Tworzenie własnego panelu dla Paska Tracy - Tworzenie własnego rozszerzenia dla Bluescreen
                                                                                                                            .[tip] -Repozytorium gotowych rozszerzeń dla Tracy można znaleźć pod adresem "Componette":https://componette.org/search/tracy. +Repozytorium gotowych rozszerzeń dla Tracy znajdziesz na "Componette":https://componette.org/search/tracy. -Rozszerzenia dla Tracy Bar .[#toc-tracy-bar-extensions] -======================================================= +Rozszerzenia dla Paska Tracy +============================ -Stworzenie nowego rozszerzenia dla Tracy Bar nie jest trudne. Tworzysz obiekt implementujący interfejs `Tracy\IBarPanel`, który ma dwie metody `getTab()` i `getPanel()`. Metody muszą zwracać kod HTML dla zakładki (małej etykiety wyświetlanej bezpośrednio na pasku) i paska. Jeśli `getPanel()` nic nie zwraca, wyświetlana jest tylko sama etykieta. Jeśli `getTab()` nie zwraca nic, to w ogóle nic nie jest wyświetlane i getPanel() nie jest już wywoływany. +Stworzenie nowego rozszerzenia dla Paska Tracy nie jest niczym skomplikowanym. Tworzysz obiekt implementujący interfejs `Tracy\IBarPanel`, który ma dwie metody `getTab()` i `getPanel()`. Metody muszą zwrócić kod HTML zakładki (mały opis wyświetlany bezpośrednio na Pasku) oraz panelu. Jeśli `getPanel()` nic nie zwróci, wyświetli się tylko sam opis. Jeśli `getTab()` nic nie zwróci, nie wyświetli się nic, a `getPanel()` nie zostanie już wywołane. ```php class ExamplePanel implements Tracy\IBarPanel @@ -35,16 +35,16 @@ class ExamplePanel implements Tracy\IBarPanel ``` -Zarejestruj się .[#toc-registration] ------------------------------------- +Rejestracja +----------- -Rejestracja odbywa się poprzez stronę `Tracy\Bar::addPanel()`: +Rejestrację przeprowadza się za pomocą `Tracy\Bar::addPanel()`: ```php Tracy\Debugger::getBar()->addPanel(new ExamplePanel); ``` -Możesz też zarejestrować panel bezpośrednio w konfiguracji aplikacji: +Lub możesz zarejestrować panel bezpośrednio w konfiguracji aplikacji: ```neon tracy: @@ -53,70 +53,70 @@ tracy: ``` -Kod zakładki HTML .[#toc-tab-html-code] ---------------------------------------- +Kod HTML zakładki +----------------- -Powinno to wyglądać coś takiego: +Powinien wyglądać mniej więcej tak: ```latte - + ... - Titulek + Tytuł ``` -Obrazek powinien być w formacie SVG. Jeżeli napis wyjaśniający nie jest potrzebny, to `` pominięty. +Obrazek powinien być w formacie SVG. Jeśli opis wyjaśniający nie jest potrzebny, można pominąć ``. -Kod HTML panelu .[#toc-panel-html-code] ---------------------------------------- +Kod HTML panelu +--------------- -Powinno to wyglądać coś takiego: +Powinien wyglądać mniej więcej tak: ```latte -

                                                                                                                            Titulek

                                                                                                                            +

                                                                                                                            Tytuł

                                                                                                                            - ... obsah ... + ... zawartość ...
                                                                                                                            ``` -Nagłówek powinien być taki sam jak nagłówek zakładki, albo może zawierać dodatkowe informacje. +Tytuł powinien być albo taki sam jak tytuł zakładki, albo może zawierać dodatkowe dane. -Należy wziąć pod uwagę, że jedno rozszerzenie może być zarejestrowane więcej niż raz, na przykład z różnymi ustawieniami, więc nie można używać id CSS do stylizacji, a jedynie klasy, w postaci `tracy-addons-[-]`. Następnie napisz klasę w div razem z klasą `tracy-inner` Podczas pisania CSS warto napisać `#tracy-debug .trida`, ponieważ reguła ma wtedy wyższy priorytet niż reset. +Należy wziąć pod uwagę, że jedno rozszerzenie może zostać zarejestrowane wielokrotnie, na przykład z innymi ustawieniami, więc do stylizacji nie można używać ID CSS, a jedynie klas, w formacie `tracy-addons-[-]`. Klasę należy następnie zapisać w elemencie div razem z klasą `tracy-inner`. Podczas pisania CSS przydatne jest pisanie `#tracy-debug .klasa`, ponieważ reguła ma wtedy wyższy priorytet niż reset. -Domyślne style .[#toc-default-styles] -------------------------------------- +Domyślne style +-------------- -W panelu są prestylizowane `
                                                                                                                            `, ``, `
                                                                                                                            `, ``. Aby utworzyć łącze, które ukrywa i wyświetla inny element, połącz je za pomocą atrybutów `href` i `id` oraz klasy `tracy-toggle`:
                                                                                                                            +W panelu predefiniowane są style dla ``, `
                                                                                                                            `, `
                                                                                                                            `, ``. Jeśli chcesz utworzyć link, który ukrywa i pokazuje inny element, połącz je atrybutami `href` i `id` oraz klasą `tracy-toggle`:
                                                                                                                             
                                                                                                                             ```latte
                                                                                                                            -Detaily
                                                                                                                            +Szczegóły
                                                                                                                             
                                                                                                                            -
                                                                                                                            ...
                                                                                                                            +
                                                                                                                            ...
                                                                                                                            ``` -Jeśli stan domyślny ma być zwinięty, dodaj klasę `tracy-collapsed` do obu elementów. +Jeśli domyślny stan ma być zwinięty, dodaj obu elementom klasę `tracy-collapsed`. -Użyj statycznego licznika, aby uniknąć tworzenia duplikatów identyfikatorów na tej samej stronie. +Użyj statycznego licznika, aby uniknąć tworzenia zduplikowanych ID na jednej stronie. -Rozszerzenie Bluescreen .[#toc-bluescreen-extensions] -===================================================== +Rozszerzenia dla Bluescreen +=========================== -Pozwala to na dodanie niestandardowych wizualizacji wyjątków lub paneli wyświetlanych na bluescreen. +W ten sposób można dodawać własne wizualizacje wyjątków lub panele, które pojawią się na bluescreen. -Rozszerzenie jest tworzone za pomocą tego polecenia: +Rozszerzenie tworzy się za pomocą tego polecenia: ```php -Tracy\Debugger::getBlueScreen()->addPanel(function (?Throwable $e) { // zachycená výjimka +Tracy\Debugger::getBlueScreen()->addPanel(function (?Throwable $e) { // przechwycony wyjątek return [ - 'tab' => '...Label...' - 'panel' => "...Kod HTML dla panelu...", + 'tab' => '...Etykieta...', + 'panel' => '...Kod HTML panelu...', ]; }); ``` -Funkcja wywoływana jest dwukrotnie, najpierw w parametrze `$e` przekazywany jest sam wyjątek, a zwrócony panel renderowany jest na górze strony. Jeśli nic nie jest zwracane, panel nie jest renderowany. Następnie jest wywoływany z parametrem `null`, a zwrócony panel jest renderowany poniżej callstack. Jeśli funkcja zwraca klucz `'bottom' => true` w tablicy , to panel jest renderowany na samym dole. +Funkcja jest wywoływana dwukrotnie, najpierw w parametrze `$e` przekazywany jest sam wyjątek, a zwrócony panel jest renderowany na początku strony. Jeśli nic nie zwróci, panel nie zostanie wyrenderowany. Następnie jest wywoływana z parametrem `null`, a zwrócony panel jest renderowany pod stosem wywołań (callstack). Jeśli funkcja zwraca w tablicy klucz `'bottom' => true`, panel zostanie wyrenderowany na samym dole. diff --git a/tracy/pl/guide.texy b/tracy/pl/guide.texy index b59b619280..3e2b83032c 100644 --- a/tracy/pl/guide.texy +++ b/tracy/pl/guide.texy @@ -1,28 +1,28 @@ -Zaczynając od Tracy -******************* +Zaczynamy z Tracy +*****************
                                                                                                                            -Biblioteka Tracy, która została udomowiona pod nazwą *Laddy*, to przydatny na co dzień pomocnik programisty PHP. To ci pomoże: +Biblioteka Tracy jest użytecznym codziennym pomocnikiem programisty PHP. Pomoże Ci: -- szybkie wykrywanie i naprawianie błędów -- błędy w dzienniku -- wyciągnąć zmienne -- mierzyć czas wykonywania skryptów i zapytań do bazy danych -- monitorować wymagania dotyczące pamięci +- szybko wykrywać i naprawiać błędy +- logować błędy +- wypisywać zmienne +- mierzyć czas skryptów i zapytań do bazy danych +- monitorować zużycie pamięci
                                                                                                                            -PHP jest językiem z wyboru do siekania trudnych do wykrycia błędów, ponieważ daje programistom dużą swobodę. Jeszcze cenniejsze jest narzędzie do debugowania Tracy. Wśród narzędzi diagnostycznych PHP to absolutna czołówka. +PHP jest językiem stworzonym do popełniania trudnych do wykrycia błędów, ponieważ daje programistom znaczną swobodę. Tym cenniejsze jest narzędzie do debugowania Tracy. Wśród narzędzi diagnostycznych dla PHP stanowi absolutną czołówkę. -Jeśli dziś po raz pierwszy spotykasz się z Biedronką, to lepiej uwierz, że Twoje życie zacznie się dzielić na to przed Biedronką i to z Biedronką. Witamy w dobrej części! +Jeśli dziś spotykasz się z Tracy po raz pierwszy, uwierz, że Twoje życie zacznie dzielić się na to przed Tracy i to z nią. Witaj w lepszej części! -Instalacja .[#toc-installation-and-requirements] -================================================ +Instalacja +========== -Najlepszym sposobem na zainstalowanie Tracy jest [pobranie najnowszego pakietu](https://github.com/nette/tracy/releases), lub użyty Composer: +Najlepszym sposobem na zainstalowanie Tracy jest [pobranie najnowszego pakietu | https://github.com/nette/tracy/releases] lub użycie Composera: ```shell composer require tracy/tracy @@ -31,183 +31,182 @@ composer require tracy/tracy Możesz również pobrać cały pakiet jako plik [tracy.phar |https://github.com/nette/tracy/releases]. -Zastosowanie .[#toc-usage] -========================== +Użycie +====== -Tracy aktywuje się poprzez wywołanie metody `TracyDebugger::enable()' jak najszybciej na początku programu, przed wysłaniem jakiegokolwiek wyjścia: +Aktywujemy Tracy, wywołując metodę `Tracy\Debugger::enable()` jak najwcześniej na początku programu, przed wysłaniem jakiegokolwiek wyjścia: ```php use Tracy\Debugger; -require 'vendor/autoload.php'; // alternatywnie tracy.phar +require 'vendor/autoload.php'; // ewentualnie tracy.phar Debugger::enable(); ``` -Pierwszą rzeczą, którą zauważysz na stronie, jest pasek Tracy w prawym dolnym rogu. Jeśli go nie widzisz, może to oznaczać, że Tracy działa w trybie produkcyjnym. -Dzieje się tak, ponieważ Tracy jest widoczny tylko na localhost ze względów bezpieczeństwa. Aby przetestować, czy działa, możesz tymczasowo przełączyć go w tryb deweloperski za pomocą parametru `Debugger::enable(Debugger::Development)`. +Pierwszą rzeczą, którą zauważysz na stronie, jest Pasek Tracy w prawym dolnym rogu. Jeśli go nie widzisz, może to oznaczać, że Tracy działa w trybie produkcyjnym. Tracy ze względów bezpieczeństwa jest widoczna tylko na localhost. Aby przetestować, czy działa, możesz tymczasowo przełączyć ją w tryb deweloperski za pomocą parametru `Debugger::enable(Debugger::Development)`. -Tracy Bar .[#toc-tracy-bar] -=========================== +Pasek Tracy +=========== -Tracy Bar to pływający pasek, który pojawia się w prawym dolnym rogu strony. Można go przesuwać za pomocą myszy i będzie pamiętał swoją pozycję po ponownym załadowaniu strony. +Pasek Tracy to pływający panel, który pojawia się w prawym dolnym rogu strony. Możemy go przesuwać myszą, a po ponownym załadowaniu strony zapamięta swoją pozycję. [* tracy-bar.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html -Do Tracy Bar można dodać inne przydatne panele. Wiele z nich można znaleźć w [dodatkach |https://componette.org], a nawet [napisać własne |extensions]. +Do Paska Tracy można dodawać kolejne przydatne panele. Wiele z nich znajdziesz w [dodatkach |https://componette.org], a nawet [możesz napisać własne |extensions]. -Jeśli nie chcesz wyświetlać paska Tracy Bar, ustaw: +Jeśli nie chcesz wyświetlać Paska Tracy, ustaw: ```php Debugger::$showBar = false; ``` -Wizualizacja błędów i wyjątków .[#toc-visualization-of-errors-and-exceptions] -============================================================================= +Wizualizacja błędów i wyjątków +============================== -Wiesz, jak PHP zgłasza błędy: drukuje coś takiego w kodzie źródłowym strony: +Z pewnością dobrze wiesz, jak PHP zgłasza błędy: do kodu źródłowego strony wypisuje coś takiego: /--pre .{font-size: 90%} Parse error: syntax error, unexpected '}' in HomePresenter.php on line 15 \-- -lub gdy wyjątek nie zostanie złapany: +lub w przypadku nieprzechwyconego wyjątku: /--pre .{font-size: 90%} Fatal error: Uncaught Nette\MemberAccessException: Call to undefined method Nette\Application\UI\Form::addTest()? in /sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php:100 Stack trace: #0 /sandbox/vendor/nette/utils/src/Utils/Object.php(75): Nette\Utils\ObjectMixin::call(Object(Nette\Application\UI\Form), 'addTest', Array) -#1 /sandbox/app/forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) -#2 /sandbox/app/presenters/SignPresenter.php(21): App\Forms\SignFormFactory->create() -#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presenters\SignPresenter->createComponentSignInForm('signInForm') +#1 /sandbox/app/Forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) +#2 /sandbox/app/Presentation/Sign/SignPresenter.php(21): App\Forms\SignFormFactory->create() +#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presentation\Sign\SignPresenter->createComponentSignInForm('signInForm') #4 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(139): Nette\ComponentModel\Container->createComponent('signInForm') #5 /sandbox/temp/cache/latte/15206b353f351f6bfca2c36cc.php(17): Nette\ComponentModel\Co in /sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php on line 100
                                                                                                                            \-- -Poruszanie się po takim zestawieniu nie jest łatwe. Jeśli włączysz *Label*, błąd lub wyjątek zostanie wyświetlony w innej formie: +Zorientowanie się w takim wyniku nie jest łatwe. Jeśli włączymy Tracy, błąd lub wyjątek zostanie wyświetlony w zupełnie innej formie: [* tracy-exception.webp .{url:-} *] -Komunikat o błędzie dosłownie krzyczy. Widzimy fragment kodu źródłowego z podświetloną linią, w której wystąpił błąd, a informacja *Call to undefined method `Nette\Security\User::isLoggedIn()` jasno wyjaśnia, na czym polega błąd. Co więcej, cała strona jest na żywo, możemy przeklikać się do większej ilości szczegółów. [Spróbuj |https://nette.github.io/tracy/tracy-exception.html]. +Komunikat o błędzie dosłownie krzyczy. Widzimy fragment kodu źródłowego z podświetloną linią, w której wystąpił błąd, a informacja *Call to undefined method Nette\Http\User::isLogedIn()* jasno wyjaśnia, o jaki błąd chodzi. Cała strona jest ponadto interaktywna, możemy klikać, aby uzyskać więcej szczegółów. [Spróbuj |https://nette.github.io/tracy/tracy-exception.html]. -I wiesz co? Pozwoli to na wychwycenie i wyświetlenie nawet fatalnych błędów. Bez konieczności instalowania jakichkolwiek rozszerzeń. +A wiesz co? W ten sposób przechwytuje i wyświetla nawet błędy krytyczne (fatal errors). Bez konieczności instalowania jakichkolwiek rozszerzeń. [* tracy-error.webp .{url:-} *] -Błędy takie jak literówka w nazwie zmiennej lub próba otwarcia nieistniejącego pliku generują komunikat poziomu E_NOTICE lub E_WARNING. Można je łatwo przeoczyć w grafice strony, a może nawet w ogóle nie być widoczne (chyba, że patrząc na kod strony). +Błędy takie jak literówka w nazwie zmiennej lub próba otwarcia nieistniejącego pliku generują komunikaty poziomu E_NOTICE lub E_WARNING. Można je łatwo przeoczyć w grafice strony, a nawet mogą być w ogóle niewidoczne (chyba że spojrzysz w kod strony). [* tracy-notice2.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html -Mogą też być wyświetlane tak jak błędy: +Lub mogą być wyświetlane tak samo jak błędy: ```php -Debugger::$strictMode = true; // pokaż wszystkie błędy -Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // wszystkie błędy z wyjątkiem powiadomień deprecate. +Debugger::$strictMode = true; // wyświetl wszystkie błędy +Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // wszystkie błędy oprócz powiadomień o przestarzałości (deprecate) ``` [* tracy-notice.webp .{url:-} *] -Uwaga: Tracy po aktywacji zmienia poziom raportowania błędów na E_ALL. Jeśli chcesz to zmienić, zrób to po wywołaniu `enable()`. +Uwaga: Tracy po aktywacji zmienia poziom raportowania błędów na E_ALL. Jeśli chcesz zmienić tę wartość, zrób to po wywołaniu `enable()`. -Tryb deweloperski a produkcyjny .[#toc-development-vs-production-mode] -====================================================================== +Tryb deweloperski vs produkcyjny +================================ -Jak widać, Tracy jest dość gadatliwy, co można docenić w środowisku deweloperskim, natomiast na serwerze produkcyjnym spowodowałoby to katastrofę. Dzieje się tak dlatego, że nie powinny być tam wyświetlane żadne informacje o debugowaniu. Tracy posiada zatem **automatyczne wykrywanie środowiska** i jeśli przykład zostanie uruchomiony na serwerze produkcyjnym, błąd zostanie zapisany w dzienniku zamiast wyświetlony, a odwiedzający zobaczy jedynie przyjazny dla użytkownika komunikat: +Jak widzisz, Tracy jest dość gadatliwa, co można docenić w środowisku deweloperskim, podczas gdy na serwerze produkcyjnym spowodowałoby to katastrofę. Tam bowiem żadne informacje debugowania nie mogą być wyświetlane. Dlatego Tracy dysponuje **autodetekcją środowiska** i jeśli uruchomimy przykład na serwerze produkcyjnym, błąd zamiast wyświetlenia zostanie zalogowany, a odwiedzający zobaczy tylko zrozumiały dla użytkownika komunikat: [* tracy-error2.webp .{url:-} *] -Tryb produkcyjny wyłącza wyświetlanie wszystkich informacji o debugowaniu wysyłanych za pomocą [dump() |dumper], a także oczywiście wszystkich komunikatów o błędach generowanych przez PHP. Jeśli więc zapomniałeś o jakimś `dump($obj)` w kodzie, nie musisz się martwić, na serwerze produkcyjnym nic nie zostanie wyświetlone. +Tryb produkcyjny tłumi wyświetlanie wszystkich informacji debugowania, które wysyłamy za pomocą [dump() |dumper], oraz oczywiście wszystkich komunikatów błędów generowanych przez PHP. Jeśli więc zapomniałeś w kodzie jakiegoś `dump($obj)`, nie musisz się martwić, na serwerze produkcyjnym nic się nie wyświetli. -Jak działa autodetekcja trybu? Tryb jest rozwojowy, jeśli aplikacja działa na localhost (czyli adres IP `127.0.0.1` lub `::1`) i nie ma proxy (czyli jego nagłówka HTTP). W przeciwnym razie działa w trybie produkcyjnym. +Jak działa autodetekcja trybu? Tryb jest deweloperski, jeśli aplikacja jest uruchomiona na localhost (tj. adres IP `127.0.0.1` lub `::1`) i nie ma obecnego proxy (tj. jego nagłówka HTTP). W przeciwnym razie działa w trybie produkcyjnym. -Jeżeli chcesz włączyć tryb development w innych przypadkach, np. dla deweloperów uzyskujących dostęp z określonego adresu IP, możesz go określić jako parametr metody `enable()`: +Jeśli chcemy włączyć tryb deweloperski również w innych przypadkach, na przykład dla programistów uzyskujących dostęp z określonego adresu IP, podajemy go jako parametr metody `enable()`: ```php -Debugger::enable('23.75.345.200'); // można również podać tablicę adresów IP +Debugger::enable('23.75.345.200'); // można podać również tablicę adresów IP ``` -Zdecydowanie zalecamy połączenie adresu IP z plikiem cookie. Przechowuj tajny token, np. `secret1234`, w pliku cookie `tracy-debug` i w ten sposób włączaj tryb deweloperski tylko dla deweloperów uzyskujących dostęp z określonego adresu IP, którzy mają wspomniany token w pliku cookie: +Zdecydowanie zalecamy łączenie adresu IP z ciasteczkiem (cookie). W ciasteczku `tracy-debug` zapisujemy tajny token, np. `secret1234`, i w ten sposób aktywujemy tryb deweloperski tylko dla programistów uzyskujących dostęp z określonego adresu IP, którzy mają w ciasteczku wspomniany token: ```php Debugger::enable('secret1234@23.75.345.200'); ``` -Możesz również bezpośrednio ustawić tryb deweloperski/produkcyjny za pomocą stałych `Debugger::Development` lub `Debugger::Production` jako parametru metody `enable()`. +Tryb deweloperski/produkcyjny możemy również ustawić bezpośrednio, używając stałej `Debugger::Development` lub `Debugger::Production` jako parametru metody `enable()`. .[note] -Jeśli używasz Nette Framework, spójrz na [to |application:bootstrap#Development vs Production Mode], jak [ustawić tryb dla niego |application:bootstrap#Development vs Production Mode], a następnie będzie on również używany dla Tracy. +Jeśli używasz Nette Framework, zobacz, jak [ustawić tryb dla niego |application:bootstrapping#Tryb deweloperski vs produkcyjny] a ten zostanie następnie użyty również dla Tracy. -Rejestrowanie błędów .[#toc-error-logging] -========================================== +Logowanie błędów +================ -W trybie produkcyjnym Tracy automatycznie loguje wszystkie błędy i wyjątki do dziennika tekstowego. Aby logowanie miało miejsce, należy ustawić bezwzględną ścieżkę do katalogu logów w zmiennej `$logDirectory` lub przekazać ją jako drugi parametr metody `enable()`: +W trybie produkcyjnym Tracy automatycznie rejestruje wszystkie błędy i przechwycone wyjątki w logu tekstowym. Aby logowanie mogło się odbywać, musimy ustawić bezwzględną ścieżkę do katalogu logów w zmiennej `$logDirectory` lub przekazać ją jako drugi parametr metody `enable()`: ```php Debugger::$logDirectory = __DIR__ . '/log'; ``` -Rejestrowanie błędów jest niezwykle przydatne. Wyobraź sobie, że wszyscy użytkownicy Twojej aplikacji to tak naprawdę beta testerzy, którzy za darmo wykonują najwyższej klasy pracę polegającą na wyszukiwaniu błędów, a Ty byłbyś głupi, gdybyś wyrzucił ich cenne raporty niepostrzeżenie do kosza na śmieci. +Logowanie błędów jest przy tym niezwykle przydatne. Wyobraź sobie, że wszyscy użytkownicy Twojej aplikacji są w rzeczywistości beta testerami, którzy za darmo wykonują doskonałą pracę w znajdowaniu błędów, a Ty popełniłbyś głupstwo, gdybyś ich cenne raporty wyrzucił bez uwagi do kosza. -Jeśli musimy zarejestrować niestandardowy raport lub wyjątek złapany przez użytkownika, używamy do tego metody `log()`: +Jeśli potrzebujemy zalogować własną wiadomość lub przechwycony przez nas wyjątek, użyjemy do tego metody `log()`: ```php -Debugger::log('Wystąpił nieoczekiwany błąd'); // komunikat tekstowy +Debugger::log('Wystąpił nieoczekiwany błąd'); // wiadomość tekstowa try { kritickaOperace(); } catch (Exception $e) { - Debugger::log($e); // można również rejestrować wyjątek + Debugger::log($e); // można logować również wyjątek // lub - Debugger::log($e, Debugger::ERROR); wysyła również powiadomienie e-mail + Debugger::log($e, Debugger::ERROR); // wyśle również powiadomienie e-mail } ``` -Jeśli chcesz, aby Tracy rejestrował błędy PHP jako `E_NOTICE` lub `E_WARNING` ze szczegółowymi informacjami (raport HTML), ustaw `Debugger::$logSeverity`: +Jeśli chcesz, aby Tracy logowała błędy PHP takie jak `E_NOTICE` lub `E_WARNING` ze szczegółowymi informacjami (raport HTML), ustaw `Debugger::$logSeverity`: ```php Debugger::$logSeverity = E_NOTICE | E_WARNING; ``` -Dla prawdziwego zawodowca dziennik błędów jest kluczowym źródłem informacji i chce on być natychmiast informowany o każdym nowym błędzie. Ladenka jest w tym względzie bardzo pomocna, gdyż może go informować o nowych wpisach w dzienniku za pomocą poczty elektronicznej. Określamy gdzie mają być wysyłane maile za pomocą zmiennej $email: +Dla prawdziwego profesjonalisty log błędów jest kluczowym źródłem informacji i chce on być natychmiast informowany o każdym nowym błędzie. Tracy wychodzi mu naprzeciw, potrafi bowiem informować o nowym wpisie w logu za pomocą e-maila. Dokąd wysyłać e-maile, określamy zmienną $email: ```php Debugger::$email = 'admin@example.com'; ``` -Jeśli używasz całego Nette Framework, to to i więcej można ustawić w [pliku konfiguracyjnym |nette:configuring]. +Jeśli używasz całego Nette Framework, można to i inne ustawienia skonfigurować w [pliku konfiguracyjnym |nette:configuring]. -Jednak aby nie zalać skrzynki mailowej, zawsze wysyła **tylko jedną wiadomość** i tworzy plik `email-sent`. Po otrzymaniu powiadomienia mailowego, deweloper sprawdza log, naprawia aplikację i usuwa plik monitorujący, tym samym ponownie aktywując wysyłanie maili. +Aby jednak nie zalała Ci skrzynki e-mailowej, zawsze wysyła **tylko jedną wiadomość** i tworzy plik `email-sent`. Programista po otrzymaniu powiadomienia e-mail sprawdza log, naprawia aplikację i usuwa plik monitorujący, co ponownie aktywuje wysyłanie e-maili. -Otwieranie w edytorze .[#toc-opening-files-in-the-editor] -========================================================= +Otwieranie w edytorze +===================== -Po wyświetleniu strony błędu możesz kliknąć na nazwy plików i otworzą się one w Twoim edytorze z kursorem w odpowiedniej linii. Możesz również tworzyć pliki (akcja `create file`) lub poprawiać w nich błędy (akcja `fix it`). Aby to zadziałało, wystarczy [skonfigurować przeglądarkę i system |open-files-in-ide]. +Podczas wyświetlania strony błędu można kliknąć na nazwy plików, a otworzą się one w Twoim edytorze z kursorem na odpowiedniej linii. Można również tworzyć pliki (akcja `create file`) lub naprawiać w nich błędy (akcja `fix it`). Aby to działało, wystarczy [skonfigurować przeglądarkę i system |open-files-in-ide]. -Obsługiwane wersje PHP .[#toc-supported-php-versions] -===================================================== +Obsługiwane wersje PHP +====================== -| Tracy | kompatybilny z PHP +| Tracy | kompatybilne z PHP |-----------|------------------- -| Tracy 2.10 – 3.0 | PHP 8.0 - 8.2 -| Tracy 2.9 | PHP 7.2 - 8.2 -| Tracy 2.8 | PHP 7.2 - 8.1 -| Tracy 2.6 - 2.7 | PHP 7.1 - 8.0 -| Tracy 2.5 | PHP 5.4 - 7.4 -| Tracy 2.4 | PHP 5.4 - 7.2 +| Tracy 2.10 – 3.0 | PHP 8.0 – 8.4 +| Tracy 2.9 | PHP 7.2 – 8.2 +| Tracy 2.8 | PHP 7.2 – 8.1 +| Tracy 2.6 – 2.7 | PHP 7.1 – 8.0 +| Tracy 2.5 | PHP 5.4 – 7.4 +| Tracy 2.4 | PHP 5.4 – 7.2 -Ważne dla najnowszych wersji poprawek. +Dotyczy ostatniej wersji poprawkowej (patch). -Porty .[#toc-ports] -=================== +Porty +===== -Jest to lista nieoficjalnych portów dla innych frameworków i CMS-ów: +To jest lista nieoficjalnych portów dla innych frameworków i CMS: - [Drupal 7](https://www.drupal.org/project/traced) - Laravel framework: [recca0120/laravel-tracy](https://github.com/recca0120/laravel-tracy), [whipsterCZ/laravel-tracy](https://github.com/whipsterCZ/laravel-tracy) diff --git a/tracy/pl/open-files-in-ide.texy b/tracy/pl/open-files-in-ide.texy index c53061cae1..53680ca6a5 100644 --- a/tracy/pl/open-files-in-ide.texy +++ b/tracy/pl/open-files-in-ide.texy @@ -1,20 +1,20 @@ -Jak otworzyć plik w edytorze Tracy (integracja z IDE)? -****************************************************** +Jak otworzyć plik w edytorze z Tracy? (Integracja z IDE) +******************************************************** .[perex] -Gdy pojawi się strona z błędami, możesz kliknąć na nazwy plików i otworzą się one w Twoim edytorze z kursorem na odpowiedniej linii. Możesz także tworzyć pliki (akcja `create file`) lub poprawiać w nich błędy (akcja `fix it`). Aby to zrobić, musisz skonfigurować przeglądarkę i system. +Podczas wyświetlania strony błędu można kliknąć na nazwy plików, a otworzą się one w Twoim edytorze z kursorem na odpowiedniej linii. Można również tworzyć pliki (akcja `create file`) lub naprawiać w nich błędy (akcja `fix it`). Aby to się stało, należy skonfigurować przeglądarkę i system. -Tracy otwiera pliki za pośrednictwem adresów URL o postaci `editor://open/?file=%file&line=%line`, czyli za pomocą protokołu `editor://`. Rejestrujemy niestandardowy handler dla tego jednego. Może to być dowolny plik wykonywalny, który "przeżuwa" parametry i uruchamia nasz ulubiony edytor. +Tracy otwiera pliki za pomocą URL w formacie `editor://open/?file=%file&line=%line`, tj. z protokołem `editor://`. Dla niego zarejestrujemy własną obsługę. Może to być dowolny plik wykonywalny, który "przetrawi" parametry i uruchomi nasz ulubiony edytor. -Możesz zmienić adres URL w zmiennej `Tracy\Debugger::$editor`, lub wyłączyć kliknięcie ustawiając `Tracy\Debugger::$editor = null`. +Możesz zmienić URL w zmiennej `Tracy\Debugger::$editor` lub wyłączyć klikanie, ustawiając `Tracy\Debugger::$editor = null`. -Windows .[#toc-windows] -======================= +Windows +======= -1) Pobranie na dysk odpowiednich plików "z repozytorium Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/windows. +1. Pobierz odpowiednie pliki "z repozytorium Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/windows na dysk. -2. Edytuj plik `open-editor.js` i odkomentuj pole `settings` i w razie potrzeby edytuj ścieżkę do swojego edytora: +2. Edytuj plik `open-editor.js` i w tablicy `settings` odkomentuj i ewentualnie zmień ścieżkę do swojego edytora: ```js var settings = { @@ -35,19 +35,28 @@ var settings = { ... ``` -Uważaj, aby zostawić podwójne ukośniki w ścieżkach. +Uwaga, pozostaw podwójne ukośniki w ścieżkach. + +3. Zarejestruj obsługę protokołu `editor://` w systemie. -3. Zarejestruj w systemie obsługę dziennika `editor://`. +Zrobisz to, uruchamiając plik `install.cmd`. **Należy go uruchomić jako Administrator.** Skrypt `open-editor.js` będzie teraz obsługiwał protokół `editor://`. -Robisz to uruchamiając plik `install.cmd`. **Musisz uruchomić go jako Administrator.** Skrypt `open-editor.js` będzie teraz obsługiwał protokół `editor://`. +Aby można było otwierać linki wygenerowane na innych serwerach, takich jak serwer produkcyjny lub w Dockerze, dodaj do `open-editor.js` mapowanie zdalnego URL na lokalny: +```js + mappings: { + // zdalna ścieżka: lokalna ścieżka + '/var/www/nette.app': 'W:\\Nette.web\\_web', + } +``` -Linux .[#toc-linux] -=================== -1. pobrać odpowiednie pliki "z repozytorium Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux do katalogu `~/bin`. +Linux +===== -2. Edytuj plik `open-editor.sh` i odkomentuj i ewentualnie edytuj ścieżkę do swojego edytora w zmiennej `editor`. +1. Pobierz odpowiednie pliki "z repozytorium Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux do katalogu `~/bin`. + +2. Edytuj plik `open-editor.sh` i odkomentuj oraz ewentualnie zmień ścieżkę do swojego edytora w zmiennej `editor`. ```shell #!/bin/bash @@ -67,24 +76,25 @@ Linux .[#toc-linux] ... ``` -Spraw, by plik był wykonywalny: +Nadaj plikowi uprawnienia do wykonywania: ```shell chmod +x ~/bin/open-editor.sh ``` -Jeśli edytor, którego używasz, nie jest zainstalowany z pakietu, binarny prawdopodobnie nie będzie miał ścieżki w $PATH. Można to łatwo naprawić. Tworzy symlink do binarki edytora w katalogu `~/bin`. .[note] +.[note] +Jeśli używany edytor nie jest zainstalowany z pakietu, prawdopodobnie jego plik binarny nie będzie miał ścieżki w $PATH. Można to łatwo naprawić. W katalogu `~/bin` utwórz link symboliczny do pliku binarnego edytora. -3. Zarejestruj w systemie obsługę dziennika `editor://`. +3. Zarejestruj obsługę protokołu `editor://` w systemie. -Dokonuje się tego poprzez uruchomienie pliku `install.sh` Skrypt `open-editor.sh` będzie teraz obsługiwał protokół `editor://`. +Zrobisz to, uruchamiając plik `install.sh`. Skrypt `open-editor.sh` będzie teraz obsługiwał protokół `editor://`. -macOS .[#toc-macos] -=================== +macOS +===== -Edytory takie jak PhpStorm, TextMate itp. pozwalają na otwieranie plików poprzez specjalny adres URL, który wystarczy ustawić: +Edytory takie jak PhpStorm, TextMate itp. umożliwiają otwieranie plików za pomocą specjalnego URL, który wystarczy ustawić: ```php // PhpStorm @@ -97,39 +107,38 @@ Tracy\Debugger::$editor = 'mvim://open?url=file:///%file&line=%line'; Tracy\Debugger::$editor = 'vscode://file/%file:%line'; ``` -Jeśli używasz samodzielnych Tracy, umieść linię przed `Tracy\Debugger::enable()`, jeśli Nette, przed `$configurator->enableTracy()` w `Bootstrap.php`. +Jeśli używasz samodzielnej Tracy, wstaw linię przed `Tracy\Debugger::enable()`, jeśli Nette, to przed `$configurator->enableTracy()` w `Bootstrap.php`. -Niestety, akcje `create file` lub `fix it` nie działają na macOS. +Akcje `create file` lub `fix it` niestety nie działają na macOS. -Przykłady .[#toc-demos] -======================= +Przykłady +========= -Naprawa błędów: +Naprawa błędu: -Tworzenie plików: +Tworzenie pliku: -Rozwiązywanie problemów .[#toc-troubleshooting] -=============================================== +Rozwiązywanie problemów +======================= -- W Firefoksie może być konieczne włączenie protokołu [poprzez ustawienie |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] `network.protocol-handler.expose.editor` na `false` oraz `network.protocol-handler.expose-all` na `true` w about:config. -- Jeśli to nie zadziała od razu, nie panikuj i spróbuj odświeżyć stronę kilka razy przed kliknięciem na link. To zadziała! -- Tutaj jest [link |https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/], aby naprawić wszelkie błędy: - Input Error: There is no script engine for file extension ".js" Być może skojarzyłeś plik ".js" z inną aplikacją, a nie z silnikiem JScript. +- W Firefoksie może być konieczne zezwolenie na protokół przez [ustawienie |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] `network.protocol-handler.expose.editor` na `false` i `network.protocol-handler.expose-all` na `true` w about:config. +- Jeśli od razu nie zadziała, nie panikuj i spróbuj kilka razy odświeżyć stronę przed kliknięciem linku. Ruszy! +- Tutaj jest [link|https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/] do naprawy ewentualnego błędu: `Input Error: There is no script engine for file extension ".js"`, `Maybe you associated ".js" file to another app, not JScript engine.` lub `dla rozszerzenia .js nie jest dostępny żaden silnik skryptowy`. -W Google Chrome od wersji 77 nie będzie już widoczne pole wyboru "Zawsze otwieraj ten typ linku w powiązanej aplikacji", gdy edytor jest uruchamiany przez link. Rozwiązanie dla systemu Windows: utwórz plik `fix.reg`: +W Google Chrome od wersji 77 nie zobaczysz już pola wyboru „Zawsze otwieraj tego typu linki w skojarzonej aplikacji”, gdy edytor jest uruchamiany za pomocą linku. Rozwiązanie dla Windows: utwórz plik `fix.reg`: ``` Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\URLWhitelist] "123"="editor://*" ``` -Kliknij dwukrotnie, aby go zaimportować i ponownie uruchom Chrome. +Zaimportuj go podwójnym kliknięciem i ponownie uruchom przeglądarkę Chrome. -Wszelkie pytania i uwagi proszę kierować na [forum |https://forum.nette.org]. +W przypadku pytań lub uwag prosimy o kontakt na [forum |https://forum.nette.org]. diff --git a/tracy/pl/recipes.texy b/tracy/pl/recipes.texy index 23c6f24718..df6668b1b6 100644 --- a/tracy/pl/recipes.texy +++ b/tracy/pl/recipes.texy @@ -1,12 +1,11 @@ -Przepisy -******** +Poradniki +********* -Polityka bezpieczeństwa treści .[#toc-content-security-policy] -============================================================== +Content Security Policy +======================= -Jeśli Twoja witryna korzysta z Content Security Policy, będziesz musiał dodać `'nonce-'` i `'strict-dynamic'` do `script-src`, aby Tracy działała poprawnie. Niektóre wtyczki 3rd mogą wymagać dodatkowych dyrektyw. -Nonce nie jest obsługiwany w dyrektywie `style-src`, jeśli używasz tej dyrektywy, musisz dodać `'unsafe-inline'`, ale należy tego unikać w trybie produkcyjnym. +Jeśli Twoja strona używa Content Security Policy, będziesz musiał dodać te same `'nonce-'` i `'strict-dynamic'` do `script-src`, aby Tracy działała poprawnie. Niektóre dodatki stron trzecich mogą wymagać dodatkowych ustawień. Nonce nie jest obsługiwane w dyrektywie `style-src`, jeśli używasz tej dyrektywy, musisz dodać `'unsafe-inline'`, ale w trybie produkcyjnym powinieneś tego unikać. Przykład konfiguracji dla [Nette Framework |nette:configuring]: @@ -24,11 +23,10 @@ header("Content-Security-Policy: script-src 'nonce-$nonce' 'strict-dynamic';"); ``` -Szybsze ładowanie .[#toc-faster-loading] -======================================== +Szybsze ładowanie +================= -Podstawowa integracja jest prosta, jednak jeśli posiadasz wolno blokujące się skrypty na stronie, mogą one spowolnić ładowanie Tracy. -Rozwiązaniem jest umieszczenie `` w swoim szablonie przed jakimikolwiek skryptami: +Uruchomienie jest proste, jednak jeśli masz na stronie wolno ładujące się skrypty blokujące, mogą one spowolnić ładowanie Tracy. Rozwiązaniem jest umieszczenie `` w swoim szablonie przed wszystkimi skryptami: ```latte @@ -42,12 +40,37 @@ Rozwiązaniem jest umieszczenie `` w swoi ``` -AJAX i przekierowane żądania .[#toc-ajax-and-redirected-requests] -================================================================= +Debugowanie żądań AJAX +====================== -Tracy może wyświetlać pasek Debug i Bluescreens dla żądań AJAX i przekierowań. Tracy tworzy własne sesje, przechowuje dane we własnych plikach tymczasowych i używa pliku cookie `tracy-session`. +Tracy automatycznie przechwytuje żądania AJAX utworzone za pomocą jQuery lub natywnego API `fetch`. Żądania są wyświetlane w pasku Tracy jako dodatkowe wiersze, co umożliwia łatwe i wygodne debugowanie AJAX. -Tracy może być również skonfigurowany do korzystania z natywnej sesji PHP, która jest uruchamiana przed włączeniem Tracy: +Jeśli nie chcesz automatycznie przechwytywać żądań AJAX, możesz wyłączyć tę funkcję, ustawiając zmienną JavaScript: + +```js +window.TracyAutoRefresh = false; +``` + +Aby ręcznie monitorować określone żądania AJAX, dodaj nagłówek HTTP `X-Tracy-Ajax` z wartością zwróconą przez `Tracy.getAjaxHeader()`. Oto przykład użycia z funkcją `fetch`: + +```js +fetch(url, { + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Tracy-Ajax': Tracy.getAjaxHeader(), + } +}) +``` + +To podejście umożliwia selektywne debugowanie żądań AJAX. + + +Przechowywanie danych +===================== + +Tracy potrafi wyświetlać panele w pasku Tracy i Bluescreeny dla żądań AJAX i przekierowań. Tracy tworzy własną sesję, przechowuje dane we własnych plikach tymczasowych i używa ciasteczka `tracy-session`. + +Tracy można również skonfigurować tak, aby używała natywnej sesji PHP, którą uruchamiamy jeszcze przed włączeniem Tracy: ```php session_start(); @@ -55,14 +78,14 @@ Debugger::setSessionStorage(new Tracy\NativeSession); Debugger::enable(); ``` -W przypadku, gdy uruchomienie sesji wymaga bardziej złożonej inicjalizacji, można od razu uruchomić Tracy (aby mogła obsłużyć wszelkie pojawiające się błędy), a następnie zainicjować obsługę sesji i ostatecznie poinformować Tracy, że sesja jest gotowa do użycia za pomocą funkcji `dispatch()`: +W przypadku, gdy uruchomienie sesji wymaga bardziej złożonej inicjalizacji, możesz uruchomić Tracy natychmiast (aby mogła przetworzyć ewentualne powstałe błędy), a następnie zainicjować obsługę sesji i na koniec poinformować Tracy, że sesja jest gotowa do użycia za pomocą funkcji `dispatch()`: ```php Debugger::setSessionStorage(new Tracy\NativeSession); Debugger::enable(); -// po czym następuje inicjalizacja sesji -// i rozpocząć sesję +// następuje inicjalizacja sesji +// i uruchomienie sesji session_start(); Debugger::dispatch(); @@ -71,28 +94,28 @@ Debugger::dispatch(); Funkcja `setSessionStorage()` istnieje od wersji 2.9, wcześniej Tracy zawsze używała natywnej sesji PHP. -Custom Scrubber .[#toc-custom-scrubber] -======================================= +Własny scrubber +=============== -Scrubber jest filtrem, który zapobiega wyciekowi wrażliwych danych z zrzutów, takich jak hasła lub poświadczenia. Filtr ten jest wywoływany dla każdego elementu zrzucanej tablicy lub obiektu i zwraca `true`, jeśli wartość jest wrażliwa. W takim przypadku zamiast wartości wypisywana jest `*****`. +Scrubber to filtr, który zapobiega wyciekowi wrażliwych danych podczas dumpowania, takich jak hasła czy dane dostępowe. Filtr jest wywoływany dla każdego elementu dumpowanej tablicy lub obiektu i zwraca `true`, jeśli wartość jest wrażliwa. W takim przypadku zamiast wartości wypisywane jest `*****`. ```php -// pozwala uniknąć dumpingu wartości kluczy i właściwości takich jak `password`, +// zapobiega wypisywaniu wartości kluczy i właściwości takich jak `password`, // `password_repeat`, `check_password`, `DATABASE_PASSWORD`, itp. $scrubber = function(string $key, $value, ?string $class): bool { return preg_match('#password#i', $key) && $value !== null; }; -// używamy go do wszystkich zrzutów wewnątrz BlueScreen +// użyjemy go dla wszystkich zrzutów wewnątrz BlueScreen Tracy\Debugger::getBlueScreen()->scrubber = $scrubber; ``` -Logger niestandardowy .[#toc-custom-logger] -=========================================== +Własny logger +============= -Możemy stworzyć własny logger, który będzie rejestrował błędy, niezałatwione wyjątki, a także będzie wywoływany przez `Tracy\Debugger::log()`. Logger implementuje interfejs [api:Tracy\ILogger]. +Możemy stworzyć własny logger, który będzie logował błędy, nieprzechwycone wyjątki, a także będzie wywoływany przez metodę `Tracy\Debugger::log()`. Logger implementuje interfejs [api:Tracy\ILogger]. ```php use Tracy\ILogger; @@ -101,18 +124,18 @@ class SlackLogger implements ILogger { public function log($value, $priority = ILogger::INFO) { - // wysyła zapytanie do Slacka + // wyśle żądanie do Slack } } ``` -A następnie aktywujemy go: +A następnie go aktywujemy: ```php Tracy\Debugger::setLogger(new SlackLogger); ``` -Jeśli korzystamy z pełnego Nette Framework, możemy go ustawić w pliku konfiguracyjnym NEON: +Jeśli używamy pełnego Nette Framework, możesz go ustawić w pliku konfiguracyjnym NEON: ```neon services: @@ -120,10 +143,10 @@ services: ``` -Monolog Integration .[#toc-monolog-integration] ------------------------------------------------ +Integracja Monolog +------------------ -Pakiet Tracy dostarcza adapter PSR-3, pozwalający na integrację [monologu/monologu](https://github.com/Seldaek/monolog). +Pakiet Tracy dostarcza adapter PSR-3, który umożliwia integrację z [monolog/monolog | https://github.com/Seldaek/monolog]. ```php $monolog = new Monolog\Logger('main-channel'); @@ -133,21 +156,21 @@ $tracyLogger = new Tracy\Bridges\Psr\PsrToTracyLoggerAdapter($monolog); Debugger::setLogger($tracyLogger); Debugger::enable(); -Debugger::log('info'); // pisze: [] main-channel.INFO: info [] [] -Debugger::log('warning', Debugger::WARNING); // pisze: [] main-channel.WARNING: warning [] [] +Debugger::log('info'); // zapisuje: [] main-channel.INFO: info [] [] +Debugger::log('warning', Debugger::WARNING); // zapisuje: [] main-channel.WARNING: warning [] [] ``` -nginx .[#toc-nginx] -=================== +nginx +===== -Jeśli Tracy nie działa na nginx, prawdopodobnie jest źle skonfigurowany. Jeśli jest coś takiego jak +Jeśli Tracy nie działa na serwerze nginx, prawdopodobnie jest on źle skonfigurowany. Jeśli w konfiguracji jest coś takiego jak: ```nginx try_files $uri $uri/ /index.php; ``` -zmień to na +zmień to na: ```nginx try_files $uri $uri/ /index.php$is_args$args; diff --git a/tracy/pl/stopwatch.texy b/tracy/pl/stopwatch.texy index f303376bde..a335f8d861 100644 --- a/tracy/pl/stopwatch.texy +++ b/tracy/pl/stopwatch.texy @@ -1,26 +1,26 @@ Pomiar czasu ************ -Kolejnym przydatnym narzędziem tunera jest stoper z mikrosekundową dokładnością: +Kolejnym przydatnym narzędziem debuggera jest stoper z dokładnością do mikrosekund: ```php Debugger::timer(); -// Mój mały książę śpi, ptaki słodko śnią... +// śpij mój mały książę, ptaszki już słodko śnią... sleep(2); $elapsed = Debugger::timer(); // $elapsed = 2 ``` -Dzięki opcjonalnemu parametrowi można uzyskać wiele pomiarów. +Opcjonalnym parametrem można uzyskać wielokrotne pomiary. ```php Debugger::timer('page-generating'); -// nějaký kód +// jakiś kod Debugger::timer('rss-generating'); -// nějaký kód +// jakiś kod $rssElapsed = Debugger::timer('rss-generating'); $pageElapsed = Debugger::timer('page-generating'); @@ -31,5 +31,5 @@ Debugger::timer(); // włącza stoper ... // czasochłonna operacja -echo Debugger::timer(); // wypisuje czas, który upłynął w sekundach +echo Debugger::timer(); // wypisuje upłyniony czas w sekundach ``` diff --git a/tracy/pt/@home.texy b/tracy/pt/@home.texy index fc78d25187..9f0334f3e0 100644 --- a/tracy/pt/@home.texy +++ b/tracy/pt/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Tracy - Uma ferramenta de depuração obrigatória para todos os desenvolvedores de PHP}} -{{description: Tracy é uma ferramenta projetada para facilitar a depuração do código PHP. É um assistente útil para todos os programadores de PHP, o que ajuda com erros de visualização e registro claros, variáveis de dumping e muito mais. Advertência: Tracy é viciante!}} +{{maintitle: Tracy – ferramenta de depuração com a qual é um prazer errar}} +{{description: Tracy é uma ferramenta projetada para facilitar a depuração de código PHP. É um auxiliar útil para todos os programadores PHP, ajudando-os a visualizar e registrar erros, despejar variáveis e muito mais. Aviso: Tracy é viciante!}} diff --git a/tracy/pt/@left-menu.texy b/tracy/pt/@left-menu.texy index f6ed1cb4bf..d1393bb0f9 100644 --- a/tracy/pt/@left-menu.texy +++ b/tracy/pt/@left-menu.texy @@ -1,7 +1,7 @@ -- [Como Começar |Guide] -- [Dumper |Dumper] -- [Cronômetro |Stopwatch] -- [Configurando |Configuring] -- [Receitas |Recipes] -- [Integração IDE |open-files-in-ide] -- [Criação de extensões |extensions] +- [Começando com Tracy |guide] +- [Dumpando |dumper] +- [Medindo tempo |stopwatch] +- [Configuração |configuring] +- [Receitas |recipes] +- [Integração com IDE |open-files-in-ide] +- [Criando extensões |extensions] diff --git a/tracy/pt/@menu.texy b/tracy/pt/@menu.texy index beaa536a3d..acb1764b26 100644 --- a/tracy/pt/@menu.texy +++ b/tracy/pt/@menu.texy @@ -1,3 +1,3 @@ -- [Início |@home] -- [Documentação |Guide] +- [Introdução |@home] +- [Documentação |guide] - "GitHub .[link-external]":https://github.com/nette/tracy diff --git a/tracy/pt/@meta.texy b/tracy/pt/@meta.texy new file mode 100644 index 0000000000..5df23a7829 --- /dev/null +++ b/tracy/pt/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentação Tracy}} diff --git a/tracy/pt/configuring.texy b/tracy/pt/configuring.texy index 91ebf8a237..bca893762e 100644 --- a/tracy/pt/configuring.texy +++ b/tracy/pt/configuring.texy @@ -1,142 +1,141 @@ -Configuração de Tracy +Configuração do Tracy ********************* -Os exemplos a seguir assumem que a seguinte classe está definida: +Todos os exemplos pressupõem a criação de um alias: ```php use Tracy\Debugger; ``` -Registro de erros .[#toc-error-logging] ---------------------------------------- +Registro de erros +----------------- ```php $logger = Debugger::getLogger(); -// se tiver ocorrido erro, a notificação é enviada para este e-mail -$logger->email = 'dev@example.com'; // (string|string[]) não é definido por padrão +// e-mail para o qual as notificações de erro são enviadas +$logger->email = 'dev@example.com'; // (string|string[]) padrão é não definido -// remetente de e-mail -$logger->fromEmail = 'me@example.com'; // (string) default to unset +// remetente do e-mail +$logger->fromEmail = 'me@example.com'; // (string) padrão é não definido -// rotina para envio de e-mail -$logger->mailer = /* ... */; // (chamável) padrão de envio por correio() +// rotina que garante o envio do e-mail +$logger->mailer = /* ... */; // (callable) padrão é enviar usando a função mail() -// depois de que tempo mais curto para enviar outro e-mail? -$logger->emailSnooze = /* ... */; // (string) padrão é '2 dias'. +// após qual período mínimo enviar outro e-mail? +$logger->emailSnooze = /* ... */; // (string) padrão é '2 days' -// para quais níveis de erro o BlueScreen também está logado? -Debugger::$logSeverity = E_WARNING | E_NOTICE; // o padrão é 0 (sem nível de erro) +// para quais níveis de erro o BlueScreen também é registrado? +Debugger::$logSeverity = E_WARNING | E_NOTICE; // padrão é 0 (nenhum nível de erro) ``` -`dump()` Comportamento ----------------------- +Comportamento do `dump()` +------------------------- ```php -// comprimento máximo do fio -Debugger::$maxLength = 150; // (int) padrão de acordo com Tracy +// comprimento máximo da string +Debugger::$maxLength = 150; // (int) padrão depende da versão do Tracy -// que profundidade irá listar -Debugger::$maxDepth = 10; // (int) padrão de acordo com Tracy +// profundidade máxima de aninhamento +Debugger::$maxDepth = 10; // (int) padrão depende da versão do Tracy // ocultar valores destas chaves (desde Tracy 2.8) -Debugger::$keysToHide = ['password', /* ... */]; // (string[]) padrão para [] +Debugger::$keysToHide = ['password', /* ... */]; // (string[]) padrão é [] // tema visual (desde Tracy 2.8) -Debugger::$dumpTheme = 'escuro'; // (claro|escuro) tem como padrão 'luz'. +Debugger::$dumpTheme = 'dark'; // (light|dark) padrão é 'light' -// mostra o local onde dump() foi chamado? -Debugger::$showLocation = /* ... */; // (bool) padrão de acordo com Tracy +// mostrar o local onde a função dump() foi chamada? +Debugger::$showLocation = /* ... */; // (bool) padrão depende da versão do Tracy ``` -Outros .[#toc-others] ---------------------- +Outros +------ ```php -// no modo Desenvolvimento, você verá avisos ou avisos de erro como BlueScreen -Debugger::$strictMode = /* ... */; // (bool|int) por padrão, você pode selecionar apenas níveis de erro específicos (por exemplo, E_USER_DEPRECATED | E_DEPRECATED) +// no modo de desenvolvimento, exibe erros do tipo notice ou warning como BlueScreen +Debugger::$strictMode = /* ... */; // (bool|int) padrão é false, é possível selecionar apenas alguns níveis de erro (ex: E_USER_DEPRECATED | E_DEPRECATED) -// exibe mensagens de erro silenciosas (@) -Debugger::$scream = /* ... */; // (bool|int) por padrão a falso, desde a versão 2.9 é possível selecionar apenas níveis de erro específicos (por exemplo E_USER_DEPRECATED | E_DEPRECATED) +// mostrar mensagens de erro silenciadas (@)? +Debugger::$scream = /* ... */; // (bool|int) padrão é false, desde a versão 2.9 é possível selecionar apenas alguns níveis de erro (ex: E_USER_DEPRECATED | E_DEPRECATED) // formato do link para abrir no editor -Debugger::$editor = /* ... */; // (string|null) padrão de 'editor://open/?file=%file&line=%line' +Debugger::$editor = /* ... */; // (string|null) padrão é 'editor://open/?file=%file&line=%line' -// caminho para o modelo com página personalizada para erro 500 -Debugger::$errorTemplate = /* ... */; // (string) default to unset +// caminho para o template com a página personalizada para o erro 500 +Debugger::$errorTemplate = /* ... */; // (string) padrão é não definido -// mostra Tracy Bar? -Debugger::$showBar = /* ... */; // (bool) por omissão +// mostrar a Tracy Bar? +Debugger::$showBar = /* ... */; // (bool) padrão é true Debugger::$editorMapping = [ - // original => new + // original => novo '/var/www/html' => '/data/web', '/home/web' => '/srv/html', ]; ``` -Estrutura da Nette .[#toc-nette-framework] ------------------------------------------- +Nette Framework +--------------- -Se você estiver usando o Nette Framework, você também pode configurar o Tracy e adicionar novos painéis à Barra Tracy usando o arquivo de configuração. -Você pode definir os parâmetros Tracy na configuração e também adicionar novos painéis à Tracy Bar. Estas configurações são aplicadas somente após o recipiente DI ter sido criado, de modo que erros que ocorreram anteriormente não podem refleti-los. +Se você estiver usando o Nette Framework, pode configurar o Tracy e adicionar novos painéis à Tracy Bar também através do arquivo de configuração. Na configuração, é possível definir parâmetros e também adicionar novos painéis à Tracy Bar. Essas configurações são aplicadas somente após a criação do contêiner de DI, portanto, erros ocorridos antes disso não podem refleti-las. -Configuração de registro de erros: +Configuração do registro de erros: ```neon tracy: - # Se tiver ocorrido erro, a notificação é enviada para este e-mail - email: dev@example.com # (string|string[]) não é definido por padrão + # e-mail para o qual as notificações de erro são enviadas + email: dev@example.com # (string|string[]) padrão é não definido - # remetente de e-mail - fromEmail: robot@example.com # (string) defaults to unset + # remetente do e-mail + fromEmail: robot@example.com # (string) padrão é não definido # período de adiamento do envio de e-mails (desde Tracy 2.8.8) - emailSnooze: ... # (string) padrão para '2 dias'. + emailSnooze: ... # (string) padrão é '2 days' - # para usar um mailer definido na configuração? (desde Tracy 2.5) - netteMailer: ... # (bool) padrão a verdadeiro + # usar o Nette mailer para enviar e-mails? (desde Tracy 2.5) + netteMailer: ... # (bool) padrão é true - # para quais níveis de erro o BlueScreen também está logado? - logSeverity: [E_WARNING, E_NOTICE] # padrão de [] + # para quais níveis de erro o BlueScreen também é registrado? + logSeverity: [E_WARNING, E_NOTICE] # padrão é [] ``` -Configuração para a função `dump()`: +Configuração do comportamento da função `dump()`: ```neon tracy: - # comprimento máximo do fio - maxLength: 150 # (int) padrão de acordo com Tracy + # comprimento máximo da string + maxLength: 150 # (int) padrão depende da versão do Tracy - # quão profundo será listado - maxDepth: 10 # (int) padrão de acordo com Tracy + # profundidade máxima de aninhamento + maxDepth: 10 # (int) padrão depende da versão do Tracy # ocultar valores destas chaves (desde Tracy 2.8) - keysToHide: [senha, passe] # (string[]) padrão para [] + keysToHide: [password, pass] # (string[]) padrão é [] # tema visual (desde Tracy 2.8) - dumpTheme: dark # (claro|escuro) por padrão de 'luz'. + dumpTheme: dark # (light|dark) padrão é 'light' - # mostra o local onde dump() foi chamado? - showLocation: ... # (bool) padrão de acordo com Tracy + # mostrar o local onde a função dump() foi chamada? + showLocation: ... # (bool) padrão depende da versão do Tracy ``` -Para instalar a extensão Tracy: +Instalação de extensões do Tracy: ```neon tracy: - # anexa barras ao Tracy Bar + # adiciona painéis à Tracy Bar bar: - Nette\Bridges\DITracy\ContainerPanel - IncludePanel - XDebugHelper('myIdeKey') - MyPanel(@MyService) - # anexar painéis à BlueScreen + # adiciona painéis ao BlueScreen blueScreen: - DoctrinePanel::renderException ``` @@ -145,25 +144,37 @@ Outras opções: ```neon tracy: - # no modo Desenvolvimento, você verá avisos ou avisos de erro como BlueScreen - strictMode: ... # defaults to true + # no modo de desenvolvimento, exibe erros do tipo notice ou warning como BlueScreen + strictMode: ... # padrão é true - # exibe mensagens de erro silenciosas (@) - scream: ... # ... Falso + # mostrar mensagens de erro silenciadas (@)? + scream: ... # padrão é false # formato do link para abrir no editor - editor: ... # (string) padrão para 'editor://open/?file=%file&line=%line'. + editor: ... # (string) padrão é 'editor://open/?file=%file&line=%line' - # caminho para o modelo com página personalizada para erro 500 - errorTemplate: ... # (string) defaults to unset + # caminho para o template com a página personalizada para o erro 500 + errorTemplate: ... # (string) padrão é não definido - # mostra Tracy Bar? - showBar: ... # (bool) por omissão + # mostrar a Tracy Bar? + showBar: ... # (bool) padrão é true editorMapping: - # original: new + # original: novo /var/www/html: /data/web /home/web: /srv/html ``` -Os valores das opções `logSeverity`, `strictMode` e `scream` podem ser escritos como uma matriz de níveis de erro (por exemplo `[E_WARNING, E_NOTICE]`) ou como uma expressão usada em PHP (p. ex. `E_ALL & ~E_NOTICE`). +Os valores das opções `logSeverity`, `strictMode` e `scream` podem ser escritos como um array de níveis de erro (ex: `[E_WARNING, E_NOTICE]`), ou como uma expressão usada na linguagem PHP (ex: `E_ALL & ~E_NOTICE`). + + +Serviços de DI +-------------- + +Estes serviços são adicionados ao contêiner de DI: + +| Nome | Tipo | Descrição +|---------------------------------------------------------- +| `tracy.logger` | [api:Tracy\ILogger] | logger +| `tracy.blueScreen` | [api:Tracy\BlueScreen] | BlueScreen +| `tracy.bar` | [api:Tracy\Bar] | Tracy Bar diff --git a/tracy/pt/dumper.texy b/tracy/pt/dumper.texy index 2f3f819894..20c4ca524b 100644 --- a/tracy/pt/dumper.texy +++ b/tracy/pt/dumper.texy @@ -1,7 +1,7 @@ -Dumper -****** +Dumping +******* -Todo desenvolvedor de depuração é um bom amigo com a função `var_dump`, que lista em detalhes todo o conteúdo de qualquer variável. Infelizmente, sua saída é sem formatação HTML e produz o dump em uma única linha de código HTML, para não mencionar a fuga do contexto. É necessário substituir o `var_dump` por uma função mais prática. Isso é exatamente o que é o `dump()`. +Todo depurador é um bom amigo da função [php:var_dump], que exibe detalhadamente o conteúdo de uma variável. Infelizmente, no ambiente HTML, a saída perde a formatação e se mistura em uma única linha, sem mencionar a sanitização do código HTML. Na prática, é essencial substituir `var_dump` por uma função mais inteligente. Essa função é `dump()`. ```php $arr = [10, 20.2, true, null, 'hello']; @@ -10,11 +10,11 @@ dump($arr); // ou Debugger::dump($arr); ``` -gera a produção: +gera a saída: [* dump-basic.webp *] -Você pode mudar o tema padrão da luz para a escuridão: +Você pode alterar o tema claro padrão para escuro: ```php Debugger::$dumpTheme = 'dark'; @@ -22,27 +22,27 @@ Debugger::$dumpTheme = 'dark'; [* dump-dark.webp *] -Você também pode alterar a profundidade de nidificação por `Debugger::$maxDepth` e exibir o comprimento das cordas por `Debugger::$maxLength`. Naturalmente, valores mais baixos aceleram a renderização de Tracy. +Além disso, podemos alterar a profundidade de aninhamento usando [Debugger::$maxDepth |api:Tracy\Debugger::$maxDepth] e o comprimento das descrições exibidas usando [Debugger::$maxLength |api:Tracy\Debugger::$maxLength]. Valores mais baixos naturalmente acelerarão o Tracy. ```php -Debugger::$maxDepth = 2; // default: 3 -Debugger::$maxLength = 50; // default: 150 +Debugger::$maxDepth = 2; // padrão: 3 +Debugger::$maxLength = 50; // padrão: 150 ``` -A função `dump()` pode exibir outras informações úteis. `Tracy\Dumper::LOCATION_SOURCE` adiciona uma dica de ferramenta com caminho para o arquivo, onde a função foi chamada. `Tracy\Dumper::LOCATION_LINK` adiciona um link para o arquivo. `Tracy\Dumper::LOCATION_CLASS` adiciona uma dica de ferramenta para cada objeto despejado contendo caminho para o arquivo, no qual a classe do objeto é definida. Todas estas constantes podem ser definidas na variável `Debugger::$showLocation` antes de chamar o `dump()`. Você pode definir vários valores ao mesmo tempo utilizando o operador `|`. +A função `dump()` também pode exibir outras informações úteis. A constante `Tracy\Dumper::LOCATION_SOURCE` adiciona uma dica de ferramenta (tooltip) com o caminho para o local onde a função foi chamada. `Tracy\Dumper::LOCATION_LINK` nos fornece um link para esse local. `Tracy\Dumper::LOCATION_CLASS` exibe uma dica de ferramenta para cada objeto dumpado com o caminho para o arquivo onde sua classe está definida. As constantes são definidas na variável `Debugger::$showLocation` antes de chamar `dump()`. Se quisermos definir vários valores de uma vez, nós os combinamos usando o operador `|`. ```php -Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // Mostra o caminho para onde a lixeira() foi chamada -Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Mostra ambos os caminhos para as classes e link para onde a lixeira() foi chamada -Debugger::$showLocation = false; // Esconde informações adicionais de localização -Debugger::$showLocation = true; // Mostra todas as informações adicionais de localização +Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // Define apenas a exibição sobre o local da chamada da função +Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Define simultaneamente a exibição do link e o caminho para a classe +Debugger::$showLocation = false; // Desativa a exibição de informações adicionais +Debugger::$showLocation = true; // Ativa a exibição de todas as informações adicionais ``` -Uma alternativa muito útil para `dump()` é `dumpe()` (ou seja, dump and exit) e `bdump()`. Isto nos permite despejar variáveis em Tracy Bar. Isto é útil, porque as lixeiras não atrapalham a saída e também podemos adicionar um título à lixeira. +Uma alternativa prática para `dump()` é `dumpe()` (dump & exit) e `bdump()`. Este último nos permite exibir o valor de uma variável no painel da Tracy Bar. Isso é muito útil, pois os dumps são separados do layout da página e também podemos adicionar um comentário a eles. ```php -bdump([2, 4, 6, 8], 'even numbers up to ten'); -bdump([1, 3, 5, 7, 9], 'odd numbers up to ten'); +bdump([2, 4, 6, 8], 'números pares até dez'); +bdump([1, 3, 5, 7, 9], 'números ímpares até dez'); ``` -[* bardump-en.webp *] +[* bardump-cs.webp *] diff --git a/tracy/pt/extensions.texy b/tracy/pt/extensions.texy index 90403d4573..09509b3b53 100644 --- a/tracy/pt/extensions.texy +++ b/tracy/pt/extensions.texy @@ -1,23 +1,23 @@ -Criando Extensões Tracy -*********************** +Criação de extensões para o Tracy +*********************************
                                                                                                                            -Tracy é uma ótima ferramenta para depuração de sua aplicação. Entretanto, às vezes você precisa de mais informações do que Tracy oferece. Você aprenderá sobre isso: +O Tracy fornece uma ótima ferramenta para depurar sua aplicação. No entanto, às vezes você gostaria de ter algumas informações adicionais à mão. Mostraremos como escrever sua própria extensão para a Tracy Bar para tornar o desenvolvimento ainda mais agradável. -- Criando seus próprios painéis Tracy Bar -- Criando suas próprias extensões Bluescreen +- Criação de um painel personalizado para a Tracy Bar +- Criação de uma extensão personalizada para o Bluescreen
                                                                                                                            .[tip] -Você pode encontrar extensões úteis para Tracy em "Componette":https://componette.org/search/tracy. +Você pode encontrar um repositório de extensões prontas para o Tracy em "Componette":https://componette.org/search/tracy. -Extensões de Barras Tracy .[#toc-tracy-bar-extensions] -====================================================== +Extensões para a Tracy Bar +========================== -Criar uma nova extensão para Tracy Bar é simples. Você precisa implementar `Tracy\IBarPanel` interface com os métodos `getTab()` e `getPanel()`. Os métodos devem retornar o código HTML de uma aba (pequena etiqueta na Tracy Bar) e um painel (pop-up exibido após clicar na aba). Se `getPanel()` não retorna nada, somente a aba será exibida. Se `getTab()` não retorna nada, nada será exibido e `getPanel()` não será chamado. +Criar uma nova extensão para a Tracy Bar não é complicado. Você cria um objeto que implementa a interface `Tracy\IBarPanel`, que possui dois métodos: `getTab()` e `getPanel()`. Os métodos devem retornar o código HTML da aba (uma pequena descrição exibida diretamente na Bar) e do painel. Se `getPanel()` não retornar nada, apenas a descrição será exibida. Se `getTab()` não retornar nada, nada será exibido e `getPanel()` não será chamado. ```php class ExamplePanel implements Tracy\IBarPanel @@ -35,16 +35,16 @@ class ExamplePanel implements Tracy\IBarPanel ``` -Registro .[#toc-registration] ------------------------------ +Registro +-------- -O registro é feito ligando para `Tracy\Bar::addPanel()`: +O registro é feito usando `Tracy\Bar::addPanel()`: ```php Tracy\Debugger::getBar()->addPanel(new ExamplePanel); ``` -ou você pode simplesmente registrar seu painel na configuração da aplicação: +Ou você pode registrar o painel diretamente na configuração da aplicação: ```neon tracy: @@ -53,70 +53,70 @@ tracy: ``` -Tab Código HTML .[#toc-tab-html-code] -------------------------------------- +Código HTML da aba +------------------ -Deve ser parecido com isto: +Deve se parecer aproximadamente com isto: ```latte - + ... - Title + Título ``` -A imagem deve estar no formato SVG. Se você não precisa de ponta de ferramenta, você pode deixar `` fora. +A imagem deve estar no formato SVG. Se a descrição explicativa não for necessária, o `` pode ser omitido. -Código HTML do Painel .[#toc-panel-html-code] ---------------------------------------------- +Código HTML do painel +--------------------- -Deve ser parecido com isto: +Deve se parecer aproximadamente com isto: ```latte -

                                                                                                                            Title

                                                                                                                            +

                                                                                                                            Título

                                                                                                                            - ... content ... + ... conteúdo ...
                                                                                                                            ``` -O título deve ser o mesmo que na guia ou conter informações adicionais. +O título deve ser o mesmo que o título da aba ou pode conter informações adicionais. -Uma extensão pode ser registrada várias vezes, por isso é recomendado não utilizar o atributo `id` para a criação de estilos. Você pode usar classes, de preferência em `tracy-addons-[-]` formato. Ao criar o CSS, é melhor usar `#tracy-debug .class`, pois tal regra tem maior prioridade do que o reset. +É necessário considerar que uma extensão pode ser registrada várias vezes, por exemplo, com configurações diferentes, portanto, não se pode usar IDs CSS para estilização, mas apenas classes, no formato `tracy-addons-[-]`. Em seguida, escreva a classe na div junto com a classe `tracy-inner`. Ao escrever CSS, é útil escrever `#tracy-debug .classe`, pois a regra terá uma prioridade maior que o reset. -Estilos padrão .[#toc-default-styles] -------------------------------------- +Estilos padrão +-------------- -No painel, elementos ``, `
                                                                                                                            `, `
                                                                                                                            `, `` têm estilos padrão. Para criar um link para esconder ou exibir outro elemento, conecte-os com `href` e `id` atributos e classe `tracy-toggle`.
                                                                                                                            +No painel, ``, `
                                                                                                                            `, `
                                                                                                                            `, `` são pré-estilizados. Se você quiser criar um link que oculta e exibe outro elemento, conecte-os com os atributos `href` e `id` e a classe `tracy-toggle`:
                                                                                                                             
                                                                                                                             ```latte
                                                                                                                            -Detail
                                                                                                                            +Detalhes
                                                                                                                             
                                                                                                                            -
                                                                                                                            ...
                                                                                                                            +
                                                                                                                            ...
                                                                                                                            ``` -Se o estado padrão for desmoronado, adicione a classe `tracy-collapsed` a ambos os elementos. +Se o estado padrão deve ser recolhido, adicione a classe `tracy-collapsed` a ambos os elementos. -Use um contador estático para evitar duplicatas de identificação em uma página. +Use um contador estático para evitar a criação de IDs duplicados na mesma página. -Extensões de tela Bluescreen .[#toc-bluescreen-extensions] -========================================================== +Extensões para o Bluescreen +=========================== -Você pode adicionar suas próprias visualizações de exceção ou painéis, que aparecerão na tela azul. +Desta forma, é possível adicionar visualizações personalizadas de exceções ou painéis que serão exibidos no bluescreen. -A extensão é feita desta forma: +A extensão é criada com este comando: ```php -Tracy\Debugger::getBlueScreen()->addPanel(function (?Throwable $e) { // catched exception +Tracy\Debugger::getBlueScreen()->addPanel(function (?Throwable $e) { // exceção capturada return [ - 'tab' => '...Título...', - 'panel' => '...conteúdo...', + 'tab' => '...Descrição...', + 'panel' => '...Código HTML do painel...', ]; }); ``` -A função é chamada duas vezes, primeiro a própria exceção é passada no parâmetro `$e` e o painel retornado é apresentado no início da página. Se nada for retornado, o painel não é renderizado. Em seguida é chamado com o parâmetro `null` e o painel retornado é renderizado abaixo do indicativo. Se a função retorna `'bottom' => true` no array, o painel é renderizado na parte inferior. +A função é chamada duas vezes. Primeiro, a própria exceção é passada no parâmetro `$e`, e o painel retornado é renderizado no início da página. Se nada for retornado, o painel não será renderizado. Depois, é chamada com o parâmetro `null`, e o painel retornado é renderizado abaixo do callstack. Se a função retornar a chave `'bottom' => true` no array, o painel será renderizado completamente na parte inferior. diff --git a/tracy/pt/guide.texy b/tracy/pt/guide.texy index 5350ab840e..b6970f4a6b 100644 --- a/tracy/pt/guide.texy +++ b/tracy/pt/guide.texy @@ -1,213 +1,212 @@ -Começando com Tracy -******************* +Começando com o Tracy +*********************
                                                                                                                            -A biblioteca Tracy é uma ajuda útil para os programadores PHP do dia-a-dia. Ela ajuda você a: +A biblioteca Tracy é uma ajudante diária útil para o programador PHP. Ela ajudará você a: -- detectar e corrigir rapidamente os erros -- erros de registro -- variáveis de despejo -- medir o tempo de execução dos scripts/queries -- ver consumo de memória +- detectar e corrigir erros rapidamente +- registrar erros +- exibir variáveis +- medir o tempo de execução de scripts e consultas de banco de dados +- monitorar o consumo de memória
                                                                                                                            -O PHP é uma linguagem perfeita para fazer erros dificilmente detectáveis, pois dá grande flexibilidade aos programadores. Tracy\Debugger é mais valioso por causa disso. É uma ferramenta definitiva entre as ferramentas de diagnóstico. +PHP é uma linguagem perfeita para cometer erros difíceis de detectar, pois oferece aos desenvolvedores uma liberdade considerável. Isso torna a ferramenta de depuração Tracy ainda mais valiosa. Entre as ferramentas de diagnóstico para PHP, ela representa o ápice absoluto. -Se você está encontrando Tracy pela primeira vez, acredite, sua vida começa a ser dividida em uma antes da Tracy e uma com ela. Bem-vindo à parte boa! +Se você está encontrando o Tracy pela primeira vez hoje, acredite que sua vida começará a se dividir entre antes do Tracy e com ele. Bem-vindo à parte melhor! -Instalação e requisitos .[#toc-installation-and-requirements] -============================================================= +Instalação +========== -A melhor maneira de instalar Tracy é [baixar o pacote mais recente](https://github.com/nette/tracy/releases) ou usar o Composer: +A melhor maneira de instalar o Tracy é [baixar o pacote mais recente](https://github.com/nette/tracy/releases) ou usar o Composer: ```shell composer require tracy/tracy ``` -Alternativamente, você pode baixar o pacote completo ou o arquivo [tracy.phar. |https://github.com/nette/tracy/releases] +Você também pode baixar o pacote completo como um arquivo [tracy.phar |https://github.com/nette/tracy/releases]. -Utilização .[#toc-usage] -======================== +Uso +=== -Tracy é ativado chamando o método `Tracy\Debugger::enable()' o mais rápido possível no início do programa, antes que qualquer saída seja enviada: +Ativamos o Tracy chamando o método `Tracy\Debugger::enable()` o mais cedo possível no início do programa, antes de enviar qualquer saída: ```php use Tracy\Debugger; -require 'vendor/autoload.php'; // alternativamente tracy.phar +require 'vendor/autoload.php'; // ou tracy.phar Debugger::enable(); ``` -A primeira coisa que você vai notar na página é o Tracy Bar no canto inferior direito. Se você não a vir, isso pode significar que Tracy está funcionando no modo de produção. -Isto porque Tracy só é visível no local por razões de segurança. Para testar se ela funciona, você pode colocá-la temporariamente em modo de desenvolvimento usando o parâmetro `Debugger::enable(Debugger::Development)`. +A primeira coisa que você notará na página é a Tracy Bar no canto inferior direito. Se você não a vir, pode significar que o Tracy está rodando em modo de produção. Por razões de segurança, o Tracy só é visível no localhost. Para testar se está funcionando, você pode temporariamente mudá-lo para o modo de desenvolvimento usando o parâmetro `Debugger::enable(Debugger::Development)`. -Barra Tracy .[#toc-tracy-bar] -============================= +Tracy Bar +========= -O Tracy Bar é um painel flutuante. Ela é exibida no canto inferior direito de uma página. Você pode movê-la usando o mouse. Ela lembrará sua posição após a recarga da página. +A Tracy Bar é um painel flutuante que aparece no canto inferior direito da página. Podemos movê-la com o mouse e ela lembrará sua posição após recarregar a página. [* tracy-bar.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html -Você pode adicionar outros painéis úteis à Barra Tracy. Você pode encontrar painéis interessantes em [addons |https://componette.org] ou você pode [criar seus próprios |extensions]. +É possível adicionar outros painéis úteis à Tracy Bar. Você pode encontrar muitos deles em [add-ons |https://componette.org], ou pode até [escrever o seu próprio |extensions]. -Se você não quiser mostrar o Tracy Bar, preparar: +Se você não quiser exibir a Tracy Bar, defina: ```php Debugger::$showBar = false; ``` -Visualização de erros e exceções .[#toc-visualization-of-errors-and-exceptions] -=============================================================================== +Visualização de erros e exceções +================================ -Certamente, você sabe como PHP relata erros: há algo assim no código fonte da página: +Você certamente sabe como o PHP relata erros: ele imprime algo assim no código-fonte da página: /--pre .{font-size: 90%} Parse error: syntax error, unexpected '}' in HomePresenter.php on line 15 \-- -ou exceção não cautelosa: +ou com uma exceção não capturada: /--pre .{font-size: 90%} Fatal error: Uncaught Nette\MemberAccessException: Call to undefined method Nette\Application\UI\Form::addTest()? in /sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php:100 Stack trace: #0 /sandbox/vendor/nette/utils/src/Utils/Object.php(75): Nette\Utils\ObjectMixin::call(Object(Nette\Application\UI\Form), 'addTest', Array) -#1 /sandbox/app/forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) -#2 /sandbox/app/presenters/SignPresenter.php(21): App\Forms\SignFormFactory->create() -#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presenters\SignPresenter->createComponentSignInForm('signInForm') +#1 /sandbox/app/Forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) +#2 /sandbox/app/Presentation/Sign/SignPresenter.php(21): App\Forms\SignFormFactory->create() +#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presentation\Sign\SignPresenter->createComponentSignInForm('signInForm') #4 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(139): Nette\ComponentModel\Container->createComponent('signInForm') #5 /sandbox/temp/cache/latte/15206b353f351f6bfca2c36cc.php(17): Nette\ComponentModel\Co in /sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php on line 100
                                                                                                                            \-- -Não é tão fácil navegar através desta saída. Se você habilitar Tracy, tanto os erros quanto as exceções serão exibidos de uma forma completamente diferente: +Navegar por essa saída não é exatamente fácil. Se ativarmos o Tracy, o erro ou a exceção será exibido de uma forma completamente diferente: [* tracy-exception.webp .{url:-} *] -A mensagem de erro grita literalmente. Você pode ver uma parte do código fonte com a linha destacada onde o erro ocorreu. Uma mensagem explica claramente um erro. O site inteiro é [interativo, experimente-o](https://nette.github.io/tracy/tracy-exception.html). +A mensagem de erro literalmente grita. Vemos uma parte do código-fonte com a linha destacada onde o erro ocorreu, e a informação *Call to undefined method Nette\Http\User::isLogedIn()* explica claramente qual é o erro. Além disso, toda a página é interativa; podemos clicar para obter mais detalhes. [Experimente |https://nette.github.io/tracy/tracy-exception.html]. -E você sabe o que mais? Os erros fatais são capturados e exibidos da mesma maneira. Não há necessidade de instalar nenhuma extensão (clique para exemplo ao vivo): +E sabe de uma coisa? Ele captura e exibe até mesmo erros fatais desta forma. Sem a necessidade de instalar nenhuma extensão. [* tracy-error.webp .{url:-} *] -Erros como um erro de digitação em um nome de variável ou uma tentativa de abrir um arquivo inexistente geram relatórios de nível E_NOTICE ou E_WARNING. Estes podem ser facilmente ignorados e/ou podem ser completamente escondidos em um layout gráfico de uma página web. Deixe Tracy gerenciá-los: +Erros como um erro de digitação no nome de uma variável ou uma tentativa de abrir um arquivo inexistente geram relatórios de nível E_NOTICE ou E_WARNING. Estes podem ser facilmente ignorados no layout da página e podem até não ser visíveis (a menos que você olhe o código-fonte). [* tracy-notice2.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html -Ou eles podem ser exibidos como erros: +Ou podem ser exibidos da mesma forma que os erros: ```php -Debugger::$strictMode = true; // exibir todos os erros -Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // todos os erros, exceto avisos depreciados +Debugger::$strictMode = true; // exibe todos os erros +Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // todos os erros exceto avisos de depreciação ``` [* tracy-notice.webp .{url:-} *] -Nota: Tracy quando ativado muda o nível de relatório de erro para E_ALL. Se você quiser mudar isto, faça-o depois de ligar para `enable()`. +Nota: Após a ativação, o Tracy altera o nível de relatório de erros para E_ALL. Se você quiser alterar esse valor, faça-o após chamar `enable()`. -Desenvolvimento vs Modo de Produção .[#toc-development-vs-production-mode] -========================================================================== +Modo de desenvolvimento vs. produção +==================================== -Como você pode ver, Tracy é bastante faladora, o que pode ser apreciado no ambiente de desenvolvimento, enquanto no servidor de produção causaria um desastre. Isso porque nenhuma informação de depuração deve ser exibida lá. Portanto, Tracy tem **detecção automática do ambiente*** e se o exemplo for executado em um servidor ao vivo, o erro será registrado em vez de exibido, e o visitante verá apenas uma mensagem amigável: +Como você pode ver, o Tracy é bastante verboso, o que pode ser apreciado em um ambiente de desenvolvimento, enquanto em um servidor de produção causaria um desastre. Nenhuma informação de depuração deve ser exibida lá. Portanto, o Tracy possui **autodetecção de ambiente**, e se executarmos o exemplo em um servidor ativo, o erro será registrado em vez de exibido, e o visitante verá apenas uma mensagem amigável ao usuário: [* tracy-error2.webp .{url:-} *] -O modo de produção suprime a exibição de todas as informações de depuração enviadas usando [dump() |dumper], e, é claro, também todas as mensagens de erro geradas pelo PHP. Portanto, se você esqueceu algum `dump($obj)` no código, não precisa se preocupar, nada será exibido no servidor de produção. +O modo de produção suprime a exibição de todas as informações de depuração que enviamos usando [dump() |dumper], e, claro, todas as mensagens de erro geradas pelo PHP. Portanto, se você esqueceu algum `dump($obj)` no código, não precisa se preocupar; nada será exibido no servidor de produção. -Como funciona a autodetecção de modo? O modo é desenvolvimento se a aplicação estiver rodando no localhost (ou seja, endereço IP `127.0.0.1` ou `::1`) e não houver proxy (ou seja, seu cabeçalho HTTP). Caso contrário, ele é executado em modo de produção. +Como funciona a autodetecção de modo? O modo é de desenvolvimento se a aplicação for executada no localhost (ou seja, endereço IP `127.0.0.1` ou `::1`) e não houver proxy presente (ou seja, seu cabeçalho HTTP). Caso contrário, ele roda em modo de produção. -Se você quiser ativar o modo de desenvolvimento em outros casos, por exemplo, para desenvolvedores acessando de um endereço IP específico, você pode especificá-lo como um parâmetro do método `enable()`: +Se quisermos habilitar o modo de desenvolvimento em outros casos, por exemplo, para programadores acessando de um endereço IP específico, nós o fornecemos como um parâmetro para o método `enable()`: ```php -Debugger::enable('23.75.345.200'); // você também pode fornecer um conjunto de endereços IP +Debugger::enable('23.75.345.200'); // também pode ser fornecido um array de endereços IP ``` -Definitivamente, recomendamos combinar o endereço IP com um cookie. Armazene um token secreto, por exemplo, `secret1234`, no cookie `tracy-debug` e, desta forma, ative o modo de desenvolvimento somente para desenvolvedores que acessem de um endereço IP específico e que tenham o token mencionado no cookie: +Recomendamos fortemente combinar o endereço IP com um cookie. Armazenamos um token secreto, por exemplo, `secret1234`, no cookie `tracy-debug`, e desta forma ativamos o modo de desenvolvimento apenas para programadores acessando de um endereço IP específico que possuem o token mencionado no cookie: ```php Debugger::enable('secret1234@23.75.345.200'); ``` -Você também pode definir diretamente o modo de desenvolvimento/produção usando as constantes `Debugger::Development` ou `Debugger::Production` como parâmetro do método `enable()`. +Também podemos definir diretamente o modo de desenvolvimento/produção usando as constantes `Debugger::Development` ou `Debugger::Production` como parâmetro do método `enable()`. .[note] -Se você usar o Nette Framework, dê uma olhada em como [definir o modo para ele |application:bootstrap#Development vs Production Mode], e então ele também será usado para Tracy. +Se você estiver usando o Nette Framework, veja como [definir o modo para ele |application:bootstrapping#Modo de desenvolvimento vs produção], e ele será subsequentemente usado para o Tracy também. -Registro de erros .[#toc-error-logging] -======================================= +Registro de erros +================= -No modo de produção, Tracy registra automaticamente todos os erros e exceções a um registro de texto. Para que o registro ocorra, é necessário definir o caminho absoluto para o diretório de registro para a variável `$logDirectory` ou passá-lo como o segundo parâmetro para o método `enable()`: +No modo de produção, o Tracy registra automaticamente todos os erros e exceções capturadas em um log de texto. Para que o registro ocorra, devemos definir o caminho absoluto para o diretório de log na variável `$logDirectory` ou passá-lo como o segundo parâmetro do método `enable()`: ```php Debugger::$logDirectory = __DIR__ . '/log'; ``` -O registro de erros é extremamente útil. Imagine que todos os usuários de sua aplicação são na verdade testadores beta que fazem um trabalho de primeira linha para encontrar erros gratuitamente, e você seria tolo em jogar fora seus valiosos relatórios sem ser notado no caixote do lixo. +O registro de erros é extremamente útil. Imagine que todos os usuários da sua aplicação são, na verdade, testadores beta que fazem um trabalho de primeira linha encontrando erros gratuitamente, e seria tolice jogar fora seus valiosos relatórios na lixeira sem notá-los. -Se você precisar registrar suas próprias mensagens ou se tiver que pegar exceções, use o método `log()`: +Se precisarmos registrar uma mensagem personalizada ou uma exceção que capturamos, usamos o método `log()`: ```php -Depurador::log('Unexpected error'); // mensagem de texto +Debugger::log('Ocorreu um erro inesperado'); // mensagem de texto try { criticalOperation(); } catch (Exception $e) { - Debugger::log($e); // exceção de log + Debugger::log($e); // também é possível registrar uma exceção // ou Debugger::log($e, Debugger::ERROR); // também envia uma notificação por e-mail } ``` -If you want Tracy to log PHP errors like `E_NOTICE` or `E_WARNING` with detailed information (HTML report), set `Debugger::$logSeverity`: +Se você quiser que o Tracy registre erros PHP como `E_NOTICE` ou `E_WARNING` com informações detalhadas (relatório HTML), defina `Debugger::$logSeverity`: ```php Debugger::$logSeverity = E_NOTICE | E_WARNING; ``` -Para um verdadeiro profissional, o registro de erros é uma fonte crucial de informação e ele ou ela quer ser notificado sobre qualquer novo erro imediatamente. Tracy o ajuda. Ela é capaz de enviar um e-mail para cada novo registro de erro. A variável $email identifica para onde enviar esses e-mails: +Para um verdadeiro profissional, o log de erros é uma fonte chave de informação, e ele quer ser informado imediatamente sobre cada novo erro. O Tracy o ajuda nisso, pois pode informar sobre um novo registro no log por e-mail. Determinamos para onde enviar os e-mails usando a variável `$email`: ```php Debugger::$email = 'admin@example.com'; ``` -Se você usar toda a estrutura Nette, você pode definir esta e outras no [arquivo de configuração |nette:configuring]. +Se você estiver usando o Nette Framework completo, isso e outras configurações podem ser definidas no [arquivo de configuração |nette:configuring]. -Para proteger sua caixa de e-mail contra inundações, Tracy envia **somente uma mensagem** e cria um arquivo `email-sent`. Quando um desenvolvedor recebe a notificação por e-mail, ele verifica o registro, corrige sua aplicação e apaga o arquivo de monitoramento `email-sent`. Isto ativa o envio do e-mail novamente. +No entanto, para evitar inundar sua caixa de entrada de e-mail, ele sempre envia **apenas uma mensagem** e cria o arquivo `email-sent`. Após receber a notificação por e-mail, o desenvolvedor verifica o log, corrige a aplicação e exclui o arquivo de monitoramento, reativando assim o envio de e-mails. -Arquivos de abertura no editor .[#toc-opening-files-in-the-editor] -================================================================== +Abrindo no editor +================= -Quando a página de erro for exibida, você pode clicar nos nomes dos arquivos e eles serão abertos em seu editor com o cursor na linha correspondente. Os arquivos também podem ser criados (ação `create file`) ou corrigidos com bugs (ação `fix it`). Para isso, você precisa [configurar o navegador e o sistema |open-files-in-ide]. +Ao visualizar a página de erro, você pode clicar nos nomes dos arquivos, e eles serão abertos em seu editor com o cursor na linha apropriada. Também é possível criar arquivos (ação `create file`) ou corrigir erros neles (ação `fix it`). Para que isso funcione, basta [configurar o navegador e o sistema |open-files-in-ide]. -Versões PHP suportadas .[#toc-supported-php-versions] -===================================================== +Versões PHP suportadas +====================== -| Tracy | compatível com PHP -|-----------|-------------------- -| Tracy 2.10 – 3.0 | PHP 8.0 - 8.2 -| Tracy 2.9 | PHP 7.2 - 8.2 -| Tracy 2.8 | PHP 7.2 - 8.1 -| Tracy 2.6 - 2.7 | PHP 7.1 - 8.0 -| Tracy 2.5 | PHP 5.4 - 7.4 -| Tracy 2.4 | PHP 5.4 - 7.2 +| Tracy | compatível com PHP +|-----------|------------------- +| Tracy 2.10 – 3.0 | PHP 8.0 – 8.4 +| Tracy 2.9 | PHP 7.2 – 8.2 +| Tracy 2.8 | PHP 7.2 – 8.1 +| Tracy 2.6 – 2.7 | PHP 7.1 – 8.0 +| Tracy 2.5 | PHP 5.4 – 7.4 +| Tracy 2.4 | PHP 5.4 – 7.2 -Aplica-se às últimas versões de remendos. +Aplica-se à última versão de patch. -Portos .[#toc-ports] -==================== +Portas +====== -Esta é uma lista de portos não-oficiais para outras estruturas e CMS: +Esta é uma lista de portas não oficiais para outros frameworks e CMS: - [Drupal 7](https://www.drupal.org/project/traced) - Laravel framework: [recca0120/laravel-tracy](https://github.com/recca0120/laravel-tracy), [whipsterCZ/laravel-tracy](https://github.com/whipsterCZ/laravel-tracy) diff --git a/tracy/pt/open-files-in-ide.texy b/tracy/pt/open-files-in-ide.texy index ad4fa053af..919dcec49f 100644 --- a/tracy/pt/open-files-in-ide.texy +++ b/tracy/pt/open-files-in-ide.texy @@ -1,20 +1,20 @@ -Como abrir um arquivo em Editor da Tracy? (Integração IDE) -********************************************************** +Como abrir um arquivo no editor a partir do Tracy? (Integração com IDE) +*********************************************************************** .[perex] -Quando a página de erro for exibida, você pode clicar nos nomes dos arquivos e eles serão abertos em seu editor com o cursor na linha correspondente. Os arquivos também podem ser criados (ação `create file`) ou corrigidos com bugs (ação `fix it`). Para isso, você precisa configurar o navegador e o sistema. +Ao visualizar a página de erro, você pode clicar nos nomes dos arquivos, e eles serão abertos em seu editor com o cursor na linha apropriada. Também é possível criar arquivos (ação `create file`) ou corrigir erros neles (ação `fix it`). Para que isso aconteça, é necessário configurar o navegador e o sistema. -Tracy abre arquivos via URLs do formulário `editor://open/?file=%file&line=%line`, ou seja, com o protocolo `editor://`. Registraremos nosso próprio manipulador para este. Este pode ser qualquer arquivo executável que processe os parâmetros e inicie nosso editor favorito. +O Tracy abre arquivos através de uma URL no formato `editor://open/?file=%file&line=%line`, ou seja, com o protocolo `editor://`. Registraremos nosso próprio manipulador para isso. Este pode ser qualquer arquivo executável que "processe" os parâmetros e execute nosso editor favorito. -Você pode alterar a URL na variável `Tracy\Debugger::$editor`, ou desativar o click-through definindo `Tracy\Debugger::$editor = null`. +Você pode alterar a URL na variável `Tracy\Debugger::$editor`, ou desativar o clique definindo `Tracy\Debugger::$editor = null`. -Windows .[#toc-windows] -======================= +Windows +======= -1. Faça o download dos arquivos apropriados "do repositório Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/windows para o disco. +1. Baixe os arquivos relevantes do "repositório do Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/windows para o seu disco. -2. Editar `open-editor.js` e descomentar ou editar o caminho para seu editor em `settings`: +2. Edite o arquivo `open-editor.js` e, no array `settings`, descomente e, se necessário, ajuste o caminho para o seu editor: ```js var settings = { @@ -35,19 +35,28 @@ var settings = { ... ``` -Tenha cuidado e mantenha os cortes duplos nos caminhos. +Atenção, mantenha as barras duplas invertidas nos caminhos. -3. Registre o manipulador para o protocolo `editor://` no sistema. +3. Registre o manipulador do protocolo `editor://` no sistema. -Isto é feito através da `install.cmd`. ** Você precisa executá-lo como Administrador.** O script `open-editor.js` servirá agora ao protocolo `editor://`. +Você faz isso executando o arquivo `install.cmd`. **É necessário executá-lo como Administrador.** O script `open-editor.js` agora manipulará o protocolo `editor://`. +Para poder abrir links gerados em outros servidores, como um servidor ativo ou no Docker, adicione o mapeamento de URL remota para local em `open-editor.js`: -Linux .[#toc-linux] -=================== +```js + mappings: { + // caminho remoto: caminho local + '/var/www/nette.app': 'W:\\Nette.web\\_web', + } +``` + + +Linux +===== -1. Baixe os arquivos apropriados "do repositório Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux para o diretório `~/bin`. +1. Baixe os arquivos relevantes do "repositório do Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux para o diretório `~/bin`. -2. Editar `open-editor.sh` e descomentar ou editar o caminho para seu editor na variável `editor`: +2. Edite o arquivo `open-editor.sh` e descomente e, se necessário, ajuste o caminho para o seu editor na variável `editor`. ```shell #!/bin/bash @@ -67,24 +76,25 @@ Linux .[#toc-linux] ... ``` -Torná-lo executável: +Torne o arquivo executável: ```shell chmod +x ~/bin/open-editor.sh ``` -Se o editor que você está usando não estiver instalado a partir do pacote, o binário provavelmente não terá um caminho em `$PATH`. Isto pode ser facilmente corrigido. No diretório `~/bin`, crie um link simbólico no binário do editor. .[note] +.[note] +Se o editor que você usa não foi instalado a partir de um pacote, o binário provavelmente não estará no $PATH. Isso pode ser corrigido facilmente. No diretório `~/bin`, crie um link simbólico para o binário do editor. -3. Registre o manipulador para o protocolo `editor://` no sistema. +3. Registre o manipulador do protocolo `editor://` no sistema. -Isto é feito através da `install.sh`. O script `open-editor.js` servirá agora ao protocolo `editor://`. +Você faz isso executando o arquivo `install.sh`. O script `open-editor.sh` agora manipulará o protocolo `editor://`. -macOS .[#toc-macos] -=================== +macOS +===== -Editores como PhpStorm, TextMate, etc. permitem que você abra arquivos através de uma URL especial, que você só precisa definir: +Editores como PhpStorm, TextMate, etc., permitem abrir arquivos através de uma URL especial, que só precisa ser definida: ```php // PhpStorm @@ -92,44 +102,43 @@ Tracy\Debugger::$editor = 'phpstorm://open?file=%file&line=%line'; // TextMate Tracy\Debugger::$editor = 'txmt://open/?url=file://%file&line=%line'; // MacVim -Tracy\Debugger::$editor = 'mvim://open/?url=file://%file&line=%line'; +Tracy\Debugger::$editor = 'mvim://open?url=file:///%file&line=%line'; // Visual Studio Code Tracy\Debugger::$editor = 'vscode://file/%file:%line'; ``` -Se você estiver usando Tracy autônomo, coloque a linha antes de `Tracy\Debugger::enable()`, se Nette, antes do `$configurator->enableTracy()` em `Bootstrap.php`. +Se você estiver usando o Tracy autônomo, insira a linha antes de `Tracy\Debugger::enable()`; se estiver usando o Nette, insira-a antes de `$configurator->enableTracy()` em `Bootstrap.php`. -Infelizmente, as ações `create file` ou `fix it` não funcionam em macOS. +Infelizmente, as ações `create file` ou `fix it` não funcionam no macOS. -Demos .[#toc-demos] -=================== +Exemplos +======== -Consertando o bug: +Correção de erro: -Criação de um novo arquivo: +Criação de arquivo: -Solução de problemas .[#toc-troubleshooting] -============================================ +Solução de problemas +==================== -- No Firefox você pode precisar [permitir |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] a execução do protocolo personalizado em about:config config config config config configurando `network.protocol-handler.expose.editor` para `false` e `network.protocol-handler.expose-all` para `true`. Deve ser permitido por padrão, no entanto. -- Se não estiver tudo funcionando imediatamente, não entre em pânico. Tente atualizar a página, reinicie o navegador ou o computador. Isso deve ajudar. -- Veja [aqui |https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/] para consertar: - Erro de entrada: Não existe um mecanismo de script para extensão de arquivo ".js" Talvez você tenha associado o arquivo ".js" a outro aplicativo, não ao mecanismo JScript. +- No Firefox, pode ser necessário habilitar o protocolo [definindo |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] `network.protocol-handler.expose.editor` como `false` e `network.protocol-handler.expose-all` como `true` em about:config. +- Se não funcionar imediatamente, não entre em pânico e tente atualizar a página algumas vezes antes de clicar no link. Vai funcionar! +- Aqui está um [link|https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/] para corrigir um possível erro: `Input Error: There is no script engine for file extension ".js"`, `Maybe you associated ".js" file to another app, not JScript engine.` ou `nenhum mecanismo de script está disponível para a extensão .js`. -A partir da versão 77 do Google Chrome você não verá mais a caixa de seleção "Sempre abra estes tipos de links no aplicativo associado" quando o editor for aberto através de um link. Solução para o Windows: criar arquivo `fix.reg`: +No Google Chrome a partir da versão 77, você não verá mais a caixa de seleção „Sempre abrir este tipo de link no aplicativo associado“ quando o editor for iniciado através de um link. Solução para Windows: crie um arquivo `fix.reg`: ``` Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\URLWhitelist] "123"="editor://*" ``` -Importe-o clicando duas vezes e reinicie o Cromo. +Importe-o clicando duas vezes e reinicie o Chrome. -Em caso de mais problemas ou perguntas, pergunte no [fórum |https://forum.nette.org]. +Para quaisquer perguntas ou comentários, entre em contato com o [fórum |https://forum.nette.org]. diff --git a/tracy/pt/recipes.texy b/tracy/pt/recipes.texy index 80afa1234c..49b5ae3108 100644 --- a/tracy/pt/recipes.texy +++ b/tracy/pt/recipes.texy @@ -1,14 +1,13 @@ -Receitas -******** +Tutoriais +********* -Política de Segurança de Conteúdo .[#toc-content-security-policy] -================================================================= +Content Security Policy +======================= -Se seu site utiliza a Política de Segurança de Conteúdo, você precisará acrescentar `'nonce-'` e `'strict-dynamic'` a `script-src` para que Tracy funcione corretamente. Alguns 3º plugins podem exigir diretrizes adicionais. -Nonce não é suportado na diretiva `style-src`, se você usar esta diretiva você precisa adicionar `'unsafe-inline'`, mas isto deve ser evitado no modo de produção. +Se o seu site usa Content Security Policy, você precisará adicionar o mesmo `'nonce-'` e `'strict-dynamic'` ao `script-src` para que o Tracy funcione corretamente. Alguns add-ons de terceiros podem exigir configurações adicionais. Nonce não é suportado na diretiva `style-src`; se você usar esta diretiva, deverá adicionar `'unsafe-inline'`, mas deve evitar isso no modo de produção. -Exemplo de configuração para [Nette Framework |nette:configuring]: +Exemplo de configuração para o [Nette Framework |nette:configuring]: ```neon http: @@ -24,11 +23,10 @@ header("Content-Security-Policy: script-src 'nonce-$nonce' 'strict-dynamic';"); ``` -Carregamento mais rápido .[#toc-faster-loading] -=============================================== +Carregamento mais rápido +======================== -A integração básica é simples, porém se você tiver scripts de bloqueio lentos na página web, eles podem retardar o carregamento do Tracy. -A solução é colocar `` em seu modelo antes de qualquer roteiro: +A inicialização é direta, mas se você tiver scripts de bloqueio de carregamento lento em sua página da web, eles podem retardar o carregamento do Tracy. A solução é colocar `` em seu template antes de todos os scripts: ```latte @@ -42,12 +40,37 @@ A solução é colocar `` em seu modelo a ``` -AJAX e Pedidos Redirecionados .[#toc-ajax-and-redirected-requests] -================================================================== +Depuração de requisições AJAX +============================= -Tracy pode exibir a barra Debug e Bluescreens para pedidos e redirecionamentos AJAX. Tracy cria suas próprias sessões, armazena dados em seus próprios arquivos temporários, e utiliza um cookie `tracy-session`. +O Tracy captura automaticamente requisições AJAX criadas usando jQuery ou a API nativa `fetch`. As requisições são exibidas na barra do Tracy como linhas adicionais, permitindo uma depuração AJAX fácil e conveniente. -Tracy também pode ser configurado para usar uma sessão PHP nativa, que é iniciada antes de Tracy ser ligado: +Se você não quiser capturar requisições AJAX automaticamente, pode desativar este recurso definindo uma variável JavaScript: + +```js +window.TracyAutoRefresh = false; +``` + +Para monitorar manualmente requisições AJAX específicas, adicione o cabeçalho HTTP `X-Tracy-Ajax` com o valor retornado por `Tracy.getAjaxHeader()`. Aqui está um exemplo de uso com a função `fetch`: + +```js +fetch(url, { + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Tracy-Ajax': Tracy.getAjaxHeader(), + } +}) +``` + +Esta abordagem permite a depuração seletiva de requisições AJAX. + + +Armazenamento de dados +====================== + +O Tracy pode exibir painéis na barra do Tracy e Bluescreens para requisições AJAX e redirecionamentos. O Tracy cria sua própria sessão, armazena dados em seus próprios arquivos temporários e usa o cookie `tracy-session`. + +O Tracy também pode ser configurado para usar a sessão nativa do PHP, que iniciamos antes de ativar o Tracy: ```php session_start(); @@ -55,44 +78,44 @@ Debugger::setSessionStorage(new Tracy\NativeSession); Debugger::enable(); ``` -Caso o início de uma sessão requeira uma inicialização mais complexa, você pode iniciar Tracy imediatamente (para que ele possa lidar com quaisquer erros que ocorram) e então inicializar o manipulador da sessão e finalmente informar Tracy que a sessão está pronta para ser usada usando a função `dispatch()`: +Caso o início da sessão exija uma inicialização mais complexa, você pode iniciar o Tracy imediatamente (para que ele possa processar quaisquer erros que ocorram), depois inicializar o manipulador de sessão e, finalmente, informar ao Tracy que a sessão está pronta para uso usando a função `dispatch()`: ```php Debugger::setSessionStorage(new Tracy\NativeSession); Debugger::enable(); -// seguido pela inicialização da sessão -// e iniciar a sessão +// segue a inicialização da sessão +// e o início da sessão session_start(); Debugger::dispatch(); ``` -A função `setSessionStorage()` existe desde a versão 2.9, antes disso Tracy sempre usou a sessão PHP nativa. +A função `setSessionStorage()` existe desde a versão 2.9; antes disso, o Tracy sempre usava a sessão nativa do PHP. -Scrubber personalizado .[#toc-custom-scrubber] -============================================== +Scrubber personalizado +====================== -O Scrubber é um filtro que evita que dados sensíveis vazem de lixões, tais como senhas ou credenciais. O filtro é chamado para cada item da matriz ou objeto despejado e retorna `true` se o valor for sensível. Neste caso, `*****` é impresso ao invés do valor. +Scrubber é um filtro que impede o vazamento de dados sensíveis durante o dumping, como senhas ou credenciais de acesso. O filtro é chamado para cada elemento do array ou objeto dumpado e retorna `true` se o valor for sensível. Nesse caso, `*****` é exibido em vez do valor. ```php -// evita o despejo de valores e propriedades chave como "palavras-chave", -// Palavras_senhas_repetição', 'verificar_senhas', 'DATABASE_PASSWORD', etc. +// impede a exibição de valores de chaves e propriedades como `password`, +// `password_repeat`, `check_password`, `DATABASE_PASSWORD`, etc. $scrubber = function(string $key, $value, ?string $class): bool { return preg_match('#password#i', $key) && $value !== null; }; -// nós o utilizamos para todas as lixeiras dentro da BlueScreen +// nós o usamos para todos os dumps dentro do BlueScreen Tracy\Debugger::getBlueScreen()->scrubber = $scrubber; ``` -Registrador personalizado .[#toc-custom-logger] -=============================================== +Logger personalizado +==================== -Podemos criar um registrador personalizado para registrar erros, exceções inigualáveis, e também ser chamado por `Tracy\Debugger::log()`. O logger implementa a interface [api:Tracy\ILogger]. +Podemos criar nosso próprio logger que registrará erros, exceções não capturadas e também será invocado pelo método `Tracy\Debugger::log()`. O logger implementa a interface [api:Tracy\ILogger]. ```php use Tracy\ILogger; @@ -101,7 +124,7 @@ class SlackLogger implements ILogger { public function log($value, $priority = ILogger::INFO) { - // envia um pedido para Slack + // envia uma requisição para o Slack } } ``` @@ -112,7 +135,7 @@ E então o ativamos: Tracy\Debugger::setLogger(new SlackLogger); ``` -Se utilizarmos toda a estrutura Nette, podemos configurá-la no arquivo de configuração NEON: +Se estivermos usando o Nette Framework completo, você pode defini-lo no arquivo de configuração NEON: ```neon services: @@ -120,10 +143,10 @@ services: ``` -Integração do Monolog .[#toc-monolog-integration] -------------------------------------------------- +Integração do Monolog +--------------------- -O pacote Tracy fornece um adaptador PSR-3, permitindo a integração de [monolog/monolog](https://github.com/Seldaek/monolog). +O pacote Tracy fornece um adaptador PSR-3 que permite a integração com [monolog/monolog](https://github.com/Seldaek/monolog). ```php $monolog = new Monolog\Logger('main-channel'); @@ -133,21 +156,21 @@ $tracyLogger = new Tracy\Bridges\Psr\PsrToTracyLoggerAdapter($monolog); Debugger::setLogger($tracyLogger); Debugger::enable(); -Debugger::log('info'); // escreve: [] main-channel.INFO: info [] [] [] -Debugger::log('warning', Debugger::WARNING); // escreve: [] main-channel.WARNING: warning [] [] [] +Debugger::log('info'); // escreve: [] main-channel.INFO: info [] [] +Debugger::log('warning', Debugger::WARNING); // escreve: [] main-channel.WARNING: warning [] [] ``` -nginx .[#toc-nginx] -=================== +nginx +===== -Se Tracy não trabalha com nginx, provavelmente está mal configurado. Se houver algo como +Se o Tracy não estiver funcionando em um servidor nginx, provavelmente ele está mal configurado. Se houver algo como isto na configuração: ```nginx try_files $uri $uri/ /index.php; ``` -mudá-lo para +altere para: ```nginx try_files $uri $uri/ /index.php$is_args$args; diff --git a/tracy/pt/stopwatch.texy b/tracy/pt/stopwatch.texy index 408bb2b39d..3a7902f5e5 100644 --- a/tracy/pt/stopwatch.texy +++ b/tracy/pt/stopwatch.texy @@ -1,19 +1,19 @@ -Cronômetro -********** +Medição de tempo +**************** -Outra ferramenta útil é o cronômetro de depuração com uma precisão de microssegundos: +Outra ferramenta útil do depurador é um cronômetro com precisão de microssegundos: ```php Debugger::timer(); -// doces sonhos minha querida +// durma um pouco... sleep(2); $elapsed = Debugger::timer(); // $elapsed = 2 ``` -Medidas múltiplas de uma só vez podem ser obtidas através de um parâmetro opcional. +Com um parâmetro opcional, é possível realizar múltiplas medições. ```php Debugger::timer('page-generating'); @@ -27,9 +27,9 @@ $pageElapsed = Debugger::timer('page-generating'); ``` ```php -Debugger::timer(); // executa o timer +Debugger::timer(); // inicia o cronômetro -... ... // alguma operação demorada +... // operação demorada -echo Debugger::timer(); // tempo decorrido em segundos +echo Debugger::timer(); // exibe o tempo decorrido em segundos ``` diff --git a/tracy/ro/@home.texy b/tracy/ro/@home.texy index e7e0358560..715868d2f6 100644 --- a/tracy/ro/@home.texy +++ b/tracy/ro/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Tracy - O unealtă de depanare obligatorie pentru toți dezvoltatorii PHP}} -{{description: Tracy este un instrument conceput pentru a facilita depanarea codului PHP. Este un asistent util pentru toți programatorii PHP, care ajută la vizualizarea clară și înregistrarea erorilor, la descărcarea variabilelor și multe altele. Avertizare: Tracy creează dependență!}} +{{maintitle: Tracy – instrumentul de depanare cu care este o plăcere să greșești}} +{{description: Tracy este un instrument conceput pentru a facilita depanarea codului PHP. Este un ajutor util pentru toți programatorii PHP, ajutându-i să vizualizeze și să înregistreze erorile, să afișeze variabile și multe altele. Atenție: Tracy creează dependență!}} diff --git a/tracy/ro/@left-menu.texy b/tracy/ro/@left-menu.texy index c7e4d9a6fa..fb74e19cb0 100644 --- a/tracy/ro/@left-menu.texy +++ b/tracy/ro/@left-menu.texy @@ -1,7 +1,7 @@ -- [Noțiuni introductive |Guide] -- [Dumper |Dumper] -- [Cronometru |Stopwatch] -- [Configurarea |Configuring] -- [Rețete |Recipes] -- [Integrare IDE |open-files-in-ide] +- [Noțiuni introductive despre Tracy |guide] +- [Dump |dumper] +- [Măsurarea timpului |stopwatch] +- [Configurare |configuring] +- [Tutoriale |recipes] +- [Integrare cu IDE |open-files-in-ide] - [Crearea extensiilor |extensions] diff --git a/tracy/ro/@menu.texy b/tracy/ro/@menu.texy index 2536b7b4b8..b38c919d15 100644 --- a/tracy/ro/@menu.texy +++ b/tracy/ro/@menu.texy @@ -1,3 +1,3 @@ -- [Acasă |@home] -- [Documentație |Guide] +- [Introducere |@home] +- [Documentație |guide] - "GitHub .[link-external]":https://github.com/nette/tracy diff --git a/tracy/ro/@meta.texy b/tracy/ro/@meta.texy new file mode 100644 index 0000000000..d9fe86594e --- /dev/null +++ b/tracy/ro/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentație Tracy}} diff --git a/tracy/ro/configuring.texy b/tracy/ro/configuring.texy index 414d07e4c9..3551ea33e8 100644 --- a/tracy/ro/configuring.texy +++ b/tracy/ro/configuring.texy @@ -1,75 +1,75 @@ -Configurația Tracy +Configurarea Tracy ****************** -Următoarele exemple presupun că este definit următorul alias de clasă: +Toate exemplele presupun crearea unui alias: ```php use Tracy\Debugger; ``` -Jurnalizare erori .[#toc-error-logging] ---------------------------------------- +Logarea erorilor +---------------- ```php $logger = Debugger::getLogger(); -// dacă a apărut o eroare, notificarea este trimisă la acest e-mail -$logger->email = 'dev@example.com'; // (string|string[]) este implicit neimpus. +// e-mailul la care se trimit notificările în caz de eroare +$logger->email = 'dev@example.com'; // (string|string[]) implicit este nesetat -// expeditor e-mail -$logger->fromEmail = 'me@example.com'; // (șir de caractere) implicit la unset +// expeditorul e-mailului +$logger->fromEmail = 'me@example.com'; // (string) implicit este nesetat -// rutina de trimitere a e-mailului -$logger->mailer = /* ... */; // (apelabil) implicit trimiterea prin mail() +// rutina care asigură trimiterea e-mailului +$logger->mailer = /* ... */; // (callable) implicit este trimiterea prin funcția mail() -// după ce timp cel mai scurt se trimite un alt e-mail? -$logger->emailSnooze = /* ... */; // (string) implicit este "2 zile +// după ce interval minim de timp se trimite următorul e-mail? +$logger->emailSnooze = /* ... */; // (string) implicit este '2 days' -// pentru ce niveluri de eroare este înregistrat și BlueScreen? -Debugger::$logSeverity = E_WARNING | E_NOTICE; // valoarea implicită este 0 (niciun nivel de eroare) +// pentru ce niveluri de eroare se loghează și BlueScreen? +Debugger::$logSeverity = E_WARNING | E_NOTICE; // implicit este 0 (niciun nivel de eroare) ``` -`dump()` Comportament ---------------------- +Comportamentul `dump()` +----------------------- ```php -// lungimea maximă a șirului de caractere -Debugger::$maxLength = 150; // (int) implicit conform Tracy +// lungimea maximă a șirului +Debugger::$maxLength = 150; // (int) implicit depinde de versiunea Tracy -// cât de adâncă va fi lista -Debugger::$maxDepth = 10; // (int) implicită conform lui Tracy +// adâncimea maximă de imbricare +Debugger::$maxDepth = 10; // (int) implicit depinde de versiunea Tracy -// ascunde valorile acestor chei (de la Tracy 2.8) -Debugger::$keysToHide = ['password', /* ... */]; // (string[]) valoarea implicită este [] +// ascunde valorile acestor chei (începând cu Tracy 2.8) +Debugger::$keysToHide = ['password', /* ... */]; // (string[]) implicit este [] -// tema vizuală (de la Tracy 2.8) -Debugger::$dumpTheme = 'dark'; // (light|dark) implicit la "light +// tema vizuală (începând cu Tracy 2.8) +Debugger::$dumpTheme = 'dark'; // (light|dark) implicit este 'light' -// afișează locația în care a fost apelat dump()? -Debugger::$showLocation = /* ... */; // (bool) implicit în conformitate cu Tracy +// afișează locul unde a fost apelată funcția dump()? +Debugger::$showLocation = /* ... */; // (bool) implicit depinde de versiunea Tracy ``` -Altele .[#toc-others] ---------------------- +Altele +------ ```php -// în modul de dezvoltare, veți vedea avertismente de notificare sau de eroare ca BlueScreen -Debugger::$strictMode = /* ... */; // (bool|int) implicit la false, puteți selecta doar anumite niveluri de eroare (de exemplu, E_USER_DEPRECATED | E_DEPRECATED) +// în modul de dezvoltare, afișează erorile de tip notice sau warning ca BlueScreen +Debugger::$strictMode = /* ... */; // (bool|int) implicit este false, este posibil să selectați doar anumite niveluri de eroare (de ex. E_USER_DEPRECATED | E_DEPRECATED) -// afișează mesaje de eroare silențioase (@) -Debugger::$scream = /* ... */; // (bool|int) valoarea implicită este false, începând cu versiunea 2.9 este posibilă selectarea doar a unor niveluri de eroare specifice (de exemplu, E_USER_DEPRECATED | E_DEPRECATED) +// afișează mesajele de eroare suprimate (@)? +Debugger::$scream = /* ... */; // (bool|int) implicit este false, începând cu versiunea 2.9 este posibil să selectați doar anumite niveluri de eroare (de ex. E_USER_DEPRECATED | E_DEPRECATED) -// formatul linkului care trebuie deschis în editor -Debugger::$editor = /* ... */; // (string|null) valoarea implicită este "editor://open/?file=%file&line=%line +// formatul linkului pentru deschiderea în editor +Debugger::$editor = /* ... */; // (string|null) implicit este 'editor://open/?file=%file&line=%line' // calea către șablonul cu pagina personalizată pentru eroarea 500 -Debugger::$errorTemplate = /* ... */; // (șir de caractere) setează implicit la unset +Debugger::$errorTemplate = /* ... */; // (string) implicit este nesetat -// afișează bara Tracy? -Debugger::$showBar = /* ... */; // (bool) valoarea implicită este true +// afișează Tracy Bar? +Debugger::$showBar = /* ... */; // (bool) implicit este true Debugger::$editorMapping = [ // original => nou @@ -79,64 +79,63 @@ Debugger::$editorMapping = [ ``` -Cadrul Nette .[#toc-nette-framework] ------------------------------------- +Nette Framework +--------------- -Dacă utilizați Nette Framework, puteți configura Tracy și adăuga noi panouri la bara Tracy folosind fișierul de configurare. -Puteți seta parametrii Tracy în configurație și, de asemenea, puteți adăuga noi panouri la bara Tracy. Aceste setări se aplică numai după ce a fost creat containerul DI, astfel încât erorile care au avut loc anterior nu le pot reflecta. +Dacă utilizați Nette Framework, puteți configura Tracy și adăuga noi panouri în Tracy Bar și prin intermediul fișierului de configurare. În configurație se pot seta parametrii și, de asemenea, se pot adăuga noi panouri în Tracy Bar. Aceste setări se aplică numai după crearea containerului DI, astfel încât erorile apărute înainte nu le pot reflecta. -Configurarea jurnalizării erorilor: +Configurarea logării erorilor: ```neon tracy: - # dacă a apărut o eroare, notificarea este trimisă la acest e-mail - email: dev@example.com # (string|string[]) este implicit neimpus. + # e-mailul la care se trimit notificările în caz de eroare + email: dev@example.com # (string|string[]) implicit este nesetat - # expeditor e-mail - fromEmail: robot@example.com # (șir de caractere) implicit la unset + # expeditorul e-mailului + fromEmail: robot@example.com # (string) implicit este nesetat - # perioada de amânare a trimiterii e-mailurilor (de la Tracy 2.8.8) - emailSnooze: ... # (șir de caractere) implicit la "2 zile + # perioada de amânare a trimiterii e-mailurilor (începând cu Tracy 2.8.8) + emailSnooze: ... # (string) implicit este '2 days' - # pentru a utiliza un expeditor definit în configurație? (de la Tracy 2.5) - netteMailer: ... # (bool) implicit la true + # folosește Nette mailer pentru trimiterea e-mailurilor? (începând cu Tracy 2.5) + netteMailer: ... # (bool) implicit este true - # pentru care niveluri de eroare este înregistrat și BlueScreen? - logSeverity: [E_WARNING, E_NOTICE] # valoarea implicită este [] + # pentru ce niveluri de eroare se loghează și BlueScreen? + logSeverity: [E_WARNING, E_NOTICE] # implicit este [] ``` -Configurație pentru funcția `dump()`: +Configurarea comportamentului funcției `dump()`: ```neon tracy: - # lungimea maximă a șirului de caractere - maxLength: 150 # (int) implicit conform Tracy + # lungimea maximă a șirului + maxLength: 150 # (int) implicit depinde de versiunea Tracy - # cât de adâncă va fi lista - maxDepth: 10 # (int) implicită conform lui Tracy + # adâncimea maximă de imbricare + maxDepth: 10 # (int) implicit depinde de versiunea Tracy - # ascunde valorile acestor chei (de la Tracy 2.8) - keysToHide: [password, pass] # (string[]) valoarea implicită este [] + # ascunde valorile acestor chei (începând cu Tracy 2.8) + keysToHide: [password, pass] # (string[]) implicit este [] - # tema vizuală (de la Tracy 2.8) - dumpTheme: dark # (light|dark) implicit la "light + # tema vizuală (începând cu Tracy 2.8) + dumpTheme: dark # (light|dark) implicit este 'light' - # afișează locația în care a fost apelat dump()? - showLocation: ... # (bool) implicit în conformitate cu Tracy + # afișează locul unde a fost apelată funcția dump()? + showLocation: ... # (bool) implicit depinde de versiunea Tracy ``` -Pentru a instala extensia Tracy: +Instalarea extensiilor Tracy: ```neon tracy: - # adaugă bare la Tracy Bar + # adaugă panouri în Tracy Bar bar: - Nette\Bridges\DITracy\ContainerPanel - IncludePanel - XDebugHelper('myIdeKey') - MyPanel(@MyService) - # adaugă panouri la BlueScreen + # adaugă panouri în BlueScreen blueScreen: - DoctrinePanel::renderException ``` @@ -145,20 +144,20 @@ Alte opțiuni: ```neon tracy: - # în modul de dezvoltare, veți vedea avertismente de notificare sau de eroare ca BlueScreen - strictMode: ... # este implicit la true + # în modul de dezvoltare, afișează erorile de tip notice sau warning ca BlueScreen + strictMode: ... # implicit este true - # afișează mesaje de eroare silențioase (@) - scream: ... # implicit la false + # afișează mesajele de eroare suprimate (@)? + scream: ... # implicit este false - # formatul linkului pentru a se deschide în editor - editor: ... # (șir de caractere) valoarea implicită este "editor://open/?file=%file&line=%line + # formatul linkului pentru deschiderea în editor + editor: ... # (string) implicit este 'editor://open/?file=%file&line=%line' # calea către șablonul cu pagina personalizată pentru eroarea 500 - errorTemplate: ... # (șir de caractere) implicit la unset + errorTemplate: ... # (string) implicit este nesetat - # afișează bara Tracy? - showBar: ... # (bool) valoarea implicită este true + # afișează Tracy Bar? + showBar: ... # (bool) implicit este true editorMapping: # original: nou @@ -166,4 +165,16 @@ tracy: /home/web: /srv/html ``` -Valorile opțiunilor `logSeverity`, `strictMode` și `scream` pot fi scrise sub forma unui tablou de niveluri de eroare (de exemplu: , și ). `[E_WARNING, E_NOTICE]`) sau ca o expresie utilizată în PHP (de exemplu, `E_ALL & ~E_NOTICE`). +Valorile opțiunilor `logSeverity`, `strictMode` și `scream` pot fi scrise ca un array de niveluri de eroare (de ex. `[E_WARNING, E_NOTICE]`) sau ca o expresie utilizată în limbajul PHP (de ex. `E_ALL & ~E_NOTICE`). + + +Servicii DI +----------- + +Aceste servicii sunt adăugate în containerul DI: + +| Nume | Tip | Descriere +|---------------------------------------------------------- +| `tracy.logger` | [api:Tracy\ILogger] | logger +| `tracy.blueScreen` | [api:Tracy\BlueScreen] | BlueScreen +| `tracy.bar` | [api:Tracy\Bar] | Tracy Bar diff --git a/tracy/ro/dumper.texy b/tracy/ro/dumper.texy index 4e6fde6bfc..55462d91c1 100644 --- a/tracy/ro/dumper.texy +++ b/tracy/ro/dumper.texy @@ -1,7 +1,7 @@ -Dumper -****** +Dumparea +******** -Orice dezvoltator de depanare este un bun prieten cu funcția `var_dump`, care listează în detaliu tot conținutul oricărei variabile. Din păcate, ieșirea sa este lipsită de formatare HTML și scoate dump-ul într-o singură linie de cod HTML, ca să nu mai vorbim de scăparea contextului. Este necesar să se înlocuiască `var_dump` cu o funcție mai la îndemână. Este exact ceea ce este `dump()`. +Fiecare depanator este un bun prieten cu funcția [php:var_dump], care afișează detaliat conținutul unei variabile. Din păcate, în mediul HTML, afișarea își pierde formatarea și se contopește într-o singură linie, fără a mai menționa sanitizarea codului HTML. În practică, este necesar să înlocuim `var_dump` cu o funcție mai inteligentă. Aceasta este exact `dump()`. ```php $arr = [10, 20.2, true, null, 'hello']; @@ -10,11 +10,11 @@ dump($arr); // sau Debugger::dump($arr); ``` -generează rezultatul: +generează ieșirea: [* dump-basic.webp *] -Puteți schimba tema luminoasă implicită în tema întunecată: +Puteți schimba tema implicită deschisă la una închisă: ```php Debugger::$dumpTheme = 'dark'; @@ -22,27 +22,27 @@ Debugger::$dumpTheme = 'dark'; [* dump-dark.webp *] -De asemenea, puteți modifica adâncimea de anvelopare prin `Debugger::$maxDepth` și lungimea șirurilor afișate prin `Debugger::$maxLength`. Firește, valorile mai mici accelerează redarea Tracy. +În plus, putem schimba adâncimea de imbricare folosind [Debugger::$maxDepth |api:Tracy\Debugger::$maxDepth] și lungimea etichetelor afișate folosind [Debugger::$maxLength |api:Tracy\Debugger::$maxLength]. Valorile mai mici vor accelera în mod natural Tracy. ```php Debugger::$maxDepth = 2; // implicit: 3 Debugger::$maxLength = 50; // implicit: 150 ``` -Funcția `dump()` poate afișa și alte informații utile. `Tracy\Dumper::LOCATION_SOURCE` adaugă un tooltip cu calea către fișierul în care a fost apelată funcția. `Tracy\Dumper::LOCATION_LINK` adaugă un link către fișier. `Tracy\Dumper::LOCATION_CLASS` adaugă un tooltip la fiecare obiect descărcat care conține calea către fișierul în care este definită clasa obiectului. Toate aceste constante pot fi setate în variabila `Debugger::$showLocation` înainte de a apela funcția `dump()`. Puteți seta mai multe valori deodată folosind operatorul `|`. +Funcția `dump()` poate afișa și alte informații utile. Constanta `Tracy\Dumper::LOCATION_SOURCE` adaugă un tooltip cu calea către locul unde a fost apelată funcția. `Tracy\Dumper::LOCATION_LINK` ne oferă un link către acel loc. `Tracy\Dumper::LOCATION_CLASS` afișează, pentru fiecare obiect dumpat, un tooltip cu calea către fișierul în care este definită clasa sa. Constantele se setează în variabila `Debugger::$showLocation` înainte de apelarea `dump()`. Dacă dorim să setăm mai multe valori simultan, le combinăm folosind operatorul `|`. ```php -Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // Arată calea către locul unde a fost apelat dump() -Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Afișează atât căile de acces la clase, cât și link-ul către locul unde a fost apelat dump() -Debugger::$showLocation = false; // Ascunde informații suplimentare privind locația -Debugger::$showLocation = true; // Afișează toate informațiile suplimentare privind locația +Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // Setează doar afișarea locului apelării funcției +Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Setează simultan afișarea linkului și calea către clasă +Debugger::$showLocation = false; // Dezactivează afișarea informațiilor suplimentare +Debugger::$showLocation = true; // Activează afișarea tuturor informațiilor suplimentare ``` -O alternativă foarte la îndemână la `dump()` este `dumpe()` (adică dump and exit) și `bdump()`. Aceasta ne permite să aruncăm variabilele din Tracy Bar. Acest lucru este util, deoarece dump-urile nu încurcă ieșirea și putem, de asemenea, să adăugăm un titlu la dump. +O alternativă practică la `dump()` este `dumpe()` (dump & exit) și `bdump()`. Acesta din urmă ne permite să afișăm valoarea variabilei în panoul Tracy Bar. Acest lucru este foarte util, deoarece dump-urile sunt separate de layout-ul paginii și putem adăuga și comentarii la ele. ```php -bdump([2, 4, 6, 8], 'even numbers up to ten'); -bdump([1, 3, 5, 7, 9], 'odd numbers up to ten'); +bdump([2, 4, 6, 8], 'numere pare până la zece'); +bdump([1, 3, 5, 7, 9], 'numere impare până la zece'); ``` -[* bardump-en.webp *] +[* bardump-cs.webp *] diff --git a/tracy/ro/extensions.texy b/tracy/ro/extensions.texy index c7f76035c7..be88879a06 100644 --- a/tracy/ro/extensions.texy +++ b/tracy/ro/extensions.texy @@ -1,23 +1,23 @@ -Crearea extensiilor Tracy -************************* +Crearea extensiilor pentru Tracy +********************************
                                                                                                                            -Tracy este un instrument excelent pentru depanarea aplicației dumneavoastră. Cu toate acestea, uneori aveți nevoie de mai multe informații decât vă oferă Tracy. Veți învăța despre: +Tracy oferă un instrument excelent pentru depanarea aplicației dvs. Uneori, însă, ați dori să aveți la îndemână și alte informații. Vom arăta cum să scrieți propria extensie pentru Tracy Bar, pentru a face dezvoltarea și mai plăcută. -- Crearea propriilor panouri Tracy Bar -- Crearea propriilor extensii Bluescreen +- Crearea unui panou personalizat pentru Tracy Bar +- Crearea unei extensii personalizate pentru Bluescreen
                                                                                                                            .[tip] -Puteți găsi extensii utile pentru Tracy pe "Componette":https://componette.org/search/tracy. +Un depozit de extensii gata făcute pentru Tracy poate fi găsit la "Componette":https://componette.org/search/tracy. -Extensii pentru bara Tracy .[#toc-tracy-bar-extensions] -======================================================= +Extensii pentru Tracy Bar +========================= -Crearea unei noi extensii pentru Tracy Bar este simplă. Trebuie să implementați interfața `Tracy\IBarPanel` cu metodele `getTab()` și `getPanel()`. Metodele trebuie să returneze codul HTML al unei file (eticheta mică de pe Tracy Bar) și al unui panou (pop-up afișat după ce se face clic pe filă). Dacă `getPanel()` nu returnează nimic, va fi afișată doar fila. Dacă `getTab()` nu returnează nimic, nu se afișează nimic și `getPanel()` nu va fi apelată. +Crearea unei noi extensii pentru Tracy Bar nu este complicată. Creați un obiect care implementează interfața `Tracy\IBarPanel`, care are două metode `getTab()` și `getPanel()`. Metodele trebuie să returneze codul HTML al tab-ului (eticheta mică afișată direct pe Bar) și al panoului. Dacă `getPanel()` nu returnează nimic, se va afișa doar eticheta. Dacă `getTab()` nu returnează nimic, nu se va afișa nimic și nici `getPanel()` nu va mai fi apelată. ```php class ExamplePanel implements Tracy\IBarPanel @@ -35,16 +35,16 @@ class ExamplePanel implements Tracy\IBarPanel ``` -Înregistrare .[#toc-registration] ---------------------------------- +Înregistrare +------------ -Înscrierea se face prin apelarea `Tracy\Bar::addPanel()`: +Înregistrarea se face folosind `Tracy\Bar::addPanel()`: ```php Tracy\Debugger::getBar()->addPanel(new ExamplePanel); ``` -sau vă puteți înregistra pur și simplu panoul în configurația aplicației: +Sau puteți înregistra panoul direct în configurația aplicației: ```neon tracy: @@ -53,70 +53,70 @@ tracy: ``` -Tab Cod HTML .[#toc-tab-html-code] ----------------------------------- +Codul HTML al tab-ului +---------------------- -Ar trebui să arate ceva de genul acesta: +Ar trebui să arate aproximativ așa: ```latte - + ... - Title + Titlu ``` -Imaginea trebuie să fie în format SVG. Dacă nu aveți nevoie de tooltip, puteți lăsa `` afară. +Imaginea ar trebui să fie în format SVG. Dacă descrierea explicativă nu este necesară, `` cu atributul `title` poate fi omis. -Codul HTML al panoului .[#toc-panel-html-code] ----------------------------------------------- +Codul HTML al panoului +---------------------- -Ar trebui să arate ceva de genul acesta: +Ar trebui să arate aproximativ așa: ```latte -

                                                                                                                            Title

                                                                                                                            +

                                                                                                                            Titlu

                                                                                                                            - ... content ... + ... conținut ...
                                                                                                                            ``` -Titlul ar trebui să fie același cu cel din fila sau să conțină informații suplimentare. +Titlul ar trebui să fie fie același cu titlul tab-ului, fie poate conține informații suplimentare. -O extensie poate fi înregistrată de mai multe ori, astfel încât se recomandă să nu se utilizeze atributul `id` pentru stilizare. Puteți utiliza clase, de preferință în `tracy-addons-[-]` format. Atunci când creați CSS, este mai bine să utilizați `#tracy-debug .class`, deoarece o astfel de regulă are o prioritate mai mare decât resetarea. +Trebuie luat în considerare faptul că o extensie se poate înregistra de mai multe ori, de exemplu cu setări diferite, deci pentru stilizare nu se pot folosi id-uri CSS, ci doar clase, în formatul `tracy-addons-[-]`. Clasa se scrie apoi în div împreună cu clasa `tracy-inner`. La scrierea CSS, este util să scrieți `#tracy-debug .clasa`, deoarece regula va avea o prioritate mai mare decât resetarea. -Stiluri implicite .[#toc-default-styles] ----------------------------------------- +Stiluri implicite +----------------- -În panou, elementele ``, `
                                                                                                                            `, `
                                                                                                                            `, `` au stiluri implicite. Pentru a crea o legătură pentru a ascunde sau afișa un alt element, conectați-le cu atributele `href` și `id` și clasa `tracy-toggle`.
                                                                                                                            +În panou sunt prestilizate ``, `
                                                                                                                            `, `
                                                                                                                            `, ``. Dacă doriți să creați un link care ascunde și afișează un alt element, conectați-le prin atributele `href` și `id` și clasa `tracy-toggle`:
                                                                                                                             
                                                                                                                             ```latte
                                                                                                                            -Detail
                                                                                                                            +Detalii
                                                                                                                             
                                                                                                                            -
                                                                                                                            ...
                                                                                                                            +
                                                                                                                            ...
                                                                                                                            ``` -În cazul în care starea implicită este colapsată, adăugați clasa `tracy-collapsed` la ambele elemente. +Dacă starea implicită trebuie să fie restrânsă, adăugați ambelor elemente clasa `tracy-collapsed`. -Utilizați un contor static pentru a preveni duplicarea ID-urilor pe o pagină. +Folosiți un contor static pentru a evita crearea de ID-uri duplicate pe aceeași pagină. -Extensii Bluescreen .[#toc-bluescreen-extensions] -================================================= +Extensii pentru Bluescreen +========================== -Puteți adăuga propriile vizualizări de excepție sau panouri, care vor apărea pe ecranul albastru. +În acest mod se pot adăuga vizualizări personalizate ale excepțiilor sau panouri care se afișează pe bluescreen. -Extensia este realizată astfel: +Extensia se creează cu această comandă: ```php -Tracy\Debugger::getBlueScreen()->addPanel(function (?Throwable $e) { // excepție capturată +Tracy\Debugger::getBlueScreen()->addPanel(function (?Throwable $e) { // excepția capturată return [ - 'tab' => '...Title...', - 'panel' => '...content...', + 'tab' => '...Etichetă...', + 'panel' => '...Cod HTML al panoului...', ]; }); ``` -Funcția este apelată de două ori, mai întâi excepția însăși este trecută în parametrul `$e`, iar panoul returnat este redat la începutul paginii. Dacă nu se returnează nimic, panoul nu este redat. Apoi este apelată cu parametrul `null`, iar panoul returnat este redat sub callstack. Dacă funcția returnează `'bottom' => true` în matrice, panoul este redat chiar în partea de jos a paginii. +Funcția este apelată de două ori, mai întâi în parametrul `$e` este transmisă excepția însăși și panoul returnat se desenează la începutul paginii. Dacă nu returnează nimic, panoul nu se desenează. Apoi este apelată cu parametrul `null` și panoul returnat se desenează sub callstack. Dacă funcția returnează în array cheia `'bottom' => true`, panoul se desenează complet jos. diff --git a/tracy/ro/guide.texy b/tracy/ro/guide.texy index d289faf636..2311cab010 100644 --- a/tracy/ro/guide.texy +++ b/tracy/ro/guide.texy @@ -1,61 +1,60 @@ -Noțiuni de bază cu Tracy -************************ +Introducere în Tracy +********************
                                                                                                                            -Biblioteca Tracy este un ajutor util pentru programatorii PHP obișnuiți. Aceasta vă ajută să: +Biblioteca Tracy este un ajutor zilnic util pentru programatorul PHP. Vă va ajuta să: -- să detectați și să corectați rapid erorile -- înregistrați erorile -- să aruncați variabilele -- să măsurați timpul de execuție a scripturilor/interogărilor -- să vedeți consumul de memorie +- detectați și corectați rapid erorile +- logați erorile +- afișați variabilele +- măsurați timpul de execuție al scripturilor și interogărilor de baze de date +- monitorizați cerințele de memorie
                                                                                                                            -PHP este un limbaj perfect pentru a face erori greu de detectat, deoarece oferă o mare flexibilitate programatorilor. Tracy\Debugger este mai valoros din acest motiv. Este un instrument suprem printre cele de diagnosticare. +PHP este un limbaj predispus la crearea de erori greu de detectat, deoarece oferă dezvoltatorilor o libertate considerabilă. Cu atât mai valoros este instrumentul de depanare Tracy. Printre instrumentele de diagnosticare pentru PHP, reprezintă vârful absolut. -Dacă o întâlnești pe Tracy pentru prima dată, crede-mă, viața ta începe să fie împărțită în una înainte de Tracy și una cu ea. Bine ați venit în partea bună! +Dacă întâlniți Tracy pentru prima dată astăzi, credeți că viața dvs. se va împărți în cea dinainte de Tracy și cea cu ea. Bine ați venit în partea mai bună! -Instalare și cerințe .[#toc-installation-and-requirements] -========================================================== +Instalare +========= -Cel mai bun mod de a instala Tracy este să [descărcați cel mai recent pachet](https://github.com/nette/tracy/releases) sau să utilizați Composer: +Cel mai bun mod de a instala Tracy este să [descărcați cel mai recent pachet](https://github.com/nette/tracy/releases) sau să folosiți Composer: ```shell composer require tracy/tracy ``` -Alternativ, puteți descărca întregul pachet sau fișierul [tracy.phar |https://github.com/nette/tracy/releases]. +Puteți, de asemenea, să descărcați întregul pachet ca fișier [tracy.phar |https://github.com/nette/tracy/releases]. -Utilizare .[#toc-usage] -======================= +Utilizare +========= -Tracy este activat prin apelarea metodei `Tracy\Debugger::enable()' cât mai curând posibil la începutul programului, înainte de a fi trimisă orice ieșire: +Activăm Tracy apelând metoda `Tracy\Debugger::enable()` cât mai devreme posibil la începutul programului, înainte de a trimite orice ieșire: ```php use Tracy\Debugger; -require 'vendor/autoload.php'; // alternativ tracy.phar +require 'vendor/autoload.php'; // sau tracy.phar Debugger::enable(); ``` -Primul lucru pe care îl veți observa pe pagină este bara Tracy din colțul din dreapta jos. Dacă nu o vedeți, poate însemna că Tracy rulează în modul de producție. -Acest lucru se datorează faptului că Tracy este vizibil doar pe localhost din motive de securitate. Pentru a testa dacă funcționează, îl puteți pune temporar în modul de dezvoltare folosind parametrul `Debugger::enable(Debugger::Development)`. +Primul lucru pe care îl veți observa pe pagină este Tracy Bar în colțul din dreapta jos. Dacă nu îl vedeți, poate însemna că Tracy rulează în modul de producție. Tracy este, din motive de securitate, vizibilă doar pe localhost. Pentru a testa dacă funcționează, o puteți comuta temporar în modul de dezvoltare folosind parametrul `Debugger::enable(Debugger::Development)`. -Tracy Bar .[#toc-tracy-bar] -=========================== +Tracy Bar +========= -Tracy Bar este un panou plutitor. Acesta este afișat în colțul din dreapta jos al unei pagini. O puteți muta cu ajutorul mouse-ului. Își va reține poziția după reîncărcarea paginii. +Tracy Bar este un panou plutitor care apare în colțul din dreapta jos al paginii. Îl putem muta cu mouse-ul și își va aminti poziția după reîncărcarea paginii. [* tracy-bar.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html -Puteți adăuga alte panouri utile la bara de urmărire. Puteți găsi unele interesante în [addon-uri |https://componette.org] sau puteți să [vă creați propriile |extensions] panouri. +În Tracy Bar se pot adăuga alte panouri utile. Multe dintre ele le găsiți în [suplimente |https://componette.org] sau chiar [puteți scrie propriul dvs |extensions]. Dacă nu doriți să afișați Tracy Bar, setați: @@ -64,150 +63,150 @@ Debugger::$showBar = false; ``` -Vizualizarea erorilor și a excepțiilor .[#toc-visualization-of-errors-and-exceptions] -===================================================================================== +Vizualizarea erorilor și excepțiilor +==================================== -Cu siguranță, știți cum raportează PHP erorile: există ceva de genul acesta în codul sursă al paginii: +Cu siguranță știți cum PHP anunță erorile: în codul sursă al paginii afișează ceva de genul: /--pre .{font-size: 90%} Parse error: syntax error, unexpected '}' in HomePresenter.php on line 15 \-- -sau o excepție neînregistrată: +sau în cazul unei excepții necapturate: /--pre .{font-size: 90%} Fatal error: Uncaught Nette\MemberAccessException: Call to undefined method Nette\Application\UI\Form::addTest()? in /sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php:100 Stack trace: #0 /sandbox/vendor/nette/utils/src/Utils/Object.php(75): Nette\Utils\ObjectMixin::call(Object(Nette\Application\UI\Form), 'addTest', Array) -#1 /sandbox/app/forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) -#2 /sandbox/app/presenters/SignPresenter.php(21): App\Forms\SignFormFactory->create() -#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presenters\SignPresenter->createComponentSignInForm('signInForm') +#1 /sandbox/app/Forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) +#2 /sandbox/app/Presentation/Sign/SignPresenter.php(21): App\Forms\SignFormFactory->create() +#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presentation\Sign\SignPresenter->createComponentSignInForm('signInForm') #4 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(139): Nette\ComponentModel\Container->createComponent('signInForm') #5 /sandbox/temp/cache/latte/15206b353f351f6bfca2c36cc.php(17): Nette\ComponentModel\Co in /sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php on line 100
                                                                                                                            \-- -Nu este atât de ușor să navigați prin această ieșire. Dacă activați Tracy, atât erorile, cât și excepțiile sunt afișate într-o formă complet diferită: +Orientarea într-o astfel de afișare nu este tocmai ușoară. Dacă activăm Tracy, eroarea sau excepția se afișează într-o formă complet diferită: [* tracy-exception.webp .{url:-} *] -Mesajul de eroare strigă literalmente. Puteți vedea o parte din codul sursă cu linia evidențiată în care a apărut eroarea. Un mesaj explică în mod clar o eroare. Întregul site este [interactiv, încercați-l](https://nette.github.io/tracy/tracy-exception.html). +Mesajul de eroare literalmente țipă. Vedem o parte din codul sursă cu linia evidențiată unde a apărut eroarea și informația *Call to undefined method Nette\Http\User::isLogedIn()* explică clar despre ce eroare este vorba. Întreaga pagină este, de asemenea, interactivă, putem face clic pentru a vedea mai multe detalii. [Încercați |https://nette.github.io/tracy/tracy-exception.html]. -Și știți ce? Erorile fatale sunt capturate și afișate în același mod. Nu este nevoie să instalați nicio extensie (faceți clic pentru un exemplu live): +Și știți ce? În acest mod capturează și afișează chiar și erorile fatale. Fără a fi nevoie să instalați vreo extensie. [* tracy-error.webp .{url:-} *] -Erori precum o greșeală de scriere în numele unei variabile sau o încercare de a deschide un fișier inexistent generează rapoarte de nivel E_NOTICE sau E_WARNING. Acestea pot fi trecute cu ușurință cu vederea și/sau pot fi complet ascunse în aspectul grafic al unei pagini web. Lăsați-l pe Tracy să le gestioneze: +Erorile precum greșelile de tipar în numele variabilelor sau încercarea de a deschide un fișier inexistent generează rapoarte de nivel E_NOTICE sau E_WARNING. Acestea pot fi ușor trecute cu vederea în grafica paginii, ba chiar pot să nu fie vizibile deloc (decât dacă priviți codul paginii). [* tracy-notice2.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html -Sau pot fi afișate ca erori: +Sau pot fi afișate la fel ca erorile: ```php -Debugger::$strictMode = true; // afișarea tuturor erorilor -Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // toate erorile, cu excepția notificărilor depreciate +Debugger::$strictMode = true; // afișează toate erorile +Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // toate erorile, cu excepția notificărilor de depreciere ``` [* tracy-notice.webp .{url:-} *] -Notă: Tracy, atunci când este activat, modifică nivelul de raportare a erorilor la E_ALL. Dacă doriți să modificați acest lucru, faceți acest lucru după ce ați apelat `enable()`. +Notă: Tracy, după activare, schimbă nivelul de raportare a erorilor la E_ALL. Dacă doriți să schimbați această valoare, faceți acest lucru după apelarea `enable()`. -Mod de dezvoltare vs. mod de producție .[#toc-development-vs-production-mode] -============================================================================= +Mod dezvoltare vs producție +=========================== -După cum puteți vedea, Tracy este destul de vorbăreț, ceea ce poate fi apreciat în mediul de dezvoltare, în timp ce pe serverul de producție ar provoca un dezastru. Asta pentru că acolo nu ar trebui să fie afișate informații de depanare. Prin urmare, Tracy are **detecție automată a mediului** și dacă exemplul este rulat pe un server de producție, eroarea va fi înregistrată în loc să fie afișată, iar vizitatorul va vedea doar un mesaj ușor de utilizat: +După cum vedeți, Tracy este destul de vorbăreață, ceea ce poate fi apreciat în mediul de dezvoltare, în timp ce pe serverul de producție ar provoca un adevărat dezastru. Acolo nu trebuie afișate nicio informație de depanare. Tracy dispune, prin urmare, de **autodetecție a mediului** și dacă exemplul este rulat pe un server live, eroarea este logată în loc să fie afișată, iar vizitatorul vede doar un mesaj ușor de înțeles pentru utilizator: [* tracy-error2.webp .{url:-} *] -Modul de producție suprimă afișarea tuturor informațiilor de depanare trimise cu ajutorul funcției [dump() |dumper] și, bineînțeles, a tuturor mesajelor de eroare generate de PHP. Așadar, dacă ați uitat ceva `dump($obj)` în cod, nu trebuie să vă faceți griji, nimic nu va fi afișat pe serverul de producție. +Modul de producție suprimă afișarea tuturor informațiilor de depanare pe care le trimitem folosind [dump() |dumper] și, desigur, a tuturor mesajelor de eroare generate de PHP. Deci, dacă ați uitat vreun `dump($obj)` în cod, nu trebuie să vă faceți griji, pe serverul de producție nu se va afișa nimic. -Cum funcționează autodetecția modului? Modul este dezvoltare dacă aplicația rulează pe localhost (adică adresa IP `127.0.0.1` sau `::1`) și nu există un proxy (adică antetul său HTTP). În caz contrar, se execută în modul de producție. +Cum funcționează autodetecția modului? Modul este de dezvoltare atunci când aplicația este rulată pe localhost (adică adresa IP `127.0.0.1` sau `::1`) și nu există un proxy prezent (adică antetul său HTTP). Altfel, rulează în modul de producție. -Dacă doriți să activați modul de dezvoltare în alte cazuri, de exemplu, pentru dezvoltatorii care accesează de la o anumită adresă IP, îl puteți specifica ca parametru al metodei `enable()`: +Dacă dorim să permitem modul de dezvoltare și în alte cazuri, de exemplu pentru programatorii care accesează de la o anumită adresă IP, o specificăm ca parametru al metodei `enable()`: ```php -Debugger::enable('23.75.345.200'); // puteți furniza, de asemenea, o matrice de adrese IP +Debugger::enable('23.75.345.200'); // se poate specifica și un array de adrese IP ``` -Vă recomandăm cu siguranță să combinați adresa IP cu un cookie. Stocați un token secret, de exemplu, `secret1234`, în cookie-ul `tracy-debug` și, în acest fel, activați modul de dezvoltare numai pentru dezvoltatorii care accesează de la o anumită adresă IP și care au token-ul menționat în cookie: +Recomandăm cu tărie combinarea adresei IP cu un cookie. În cookie-ul `tracy-debug` stocăm un token secret, de ex. `secret1234`, și în acest mod activăm modul de dezvoltare doar pentru programatorii care accesează de la o anumită adresă IP și au tokenul menționat în cookie: ```php Debugger::enable('secret1234@23.75.345.200'); ``` -De asemenea, puteți seta direct modul de dezvoltare/producție folosind constantele `Debugger::Development` sau `Debugger::Production` ca parametru al metodei `enable()`. +Putem, de asemenea, să setăm direct modul de dezvoltare/producție folosind constantele `Debugger::Development` sau `Debugger::Production` ca parametru al metodei `enable()`. .[note] -Dacă utilizați Nette Framework, consultați [modul de setare a modului pentru acesta |application:bootstrap#Development vs Production Mode], care va fi utilizat și pentru Tracy. +Dacă utilizați Nette Framework, vedeți cum să [setați modul pentru acesta |application:bootstrapping#Modul de dezvoltare vs producție] și acesta va fi apoi utilizat și pentru Tracy. -Înregistrarea erorilor .[#toc-error-logging] -============================================ +Logarea erorilor +================ -În modul de producție, Tracy înregistrează automat toate erorile și excepțiile într-un jurnal de text. Pentru ca jurnalizarea să aibă loc, trebuie să setați calea absolută către directorul de jurnal în variabila `$logDirectory` sau să o treceți ca al doilea parametru al metodei `enable()`: +În modul de producție, Tracy înregistrează automat toate erorile și excepțiile capturate într-un log text. Pentru ca logarea să aibă loc, trebuie să setăm calea absolută către directorul de logare în variabila `$logDirectory` sau să o transmitem ca al doilea parametru al metodei `enable()`: ```php Debugger::$logDirectory = __DIR__ . '/log'; ``` -Înregistrarea erorilor este extrem de utilă. Imaginați-vă că toți utilizatorii aplicației dvs. sunt de fapt testeri beta care fac o muncă de top în găsirea erorilor în mod gratuit și că ați fi prost să aruncați rapoartele lor valoroase la coșul de gunoi fără să le observați. +Logarea erorilor este, de altfel, extrem de utilă. Imaginați-vă că toți utilizatorii aplicației dvs. sunt, de fapt, beta-testeri care fac gratuit o muncă excelentă în găsirea erorilor și ați face o prostie dacă ați arunca rapoartele lor valoroase neobservate la coșul de gunoi. -Dacă aveți nevoie să vă înregistrați propriile mesaje sau excepții capturate, utilizați metoda `log()`: +Dacă avem nevoie să logăm un mesaj personalizat sau o excepție capturată de noi, folosim metoda `log()`: ```php -Debugger::log('Unexpected error'); // mesaj text +Debugger::log('A apărut o eroare neașteptată'); // mesaj text try { criticalOperation(); } catch (Exception $e) { - Debugger::log($e); // excepție de jurnal + Debugger::log($e); // se poate loga și excepția // sau - Debugger::log($e, Debugger::ERROR); // trimite, de asemenea, o notificare prin e-mail + Debugger::log($e, Debugger::ERROR); // trimite și o notificare prin e-mail } ``` -If you want Tracy to log PHP errors like `E_NOTICE` or `E_WARNING` with detailed information (HTML report), set `Debugger::$logSeverity`: +Dacă doriți ca Tracy să logheze erorile PHP precum `E_NOTICE` sau `E_WARNING` cu informații detaliate (raport HTML), setați `Debugger::$logSeverity`: ```php Debugger::$logSeverity = E_NOTICE | E_WARNING; ``` -Pentru un adevărat profesionist, jurnalul de erori este o sursă crucială de informații și dorește să fie anunțat imediat despre orice nouă eroare. Tracy îl ajută. Ea este capabilă să trimită un e-mail pentru fiecare înregistrare de eroare nouă. Variabila $email identifică unde să trimită aceste e-mailuri: +Pentru un adevărat profesionist, logul de erori este o sursă cheie de informații și dorește să fie informat imediat despre fiecare nouă eroare. Tracy îi vine în ajutor, deoarece poate informa prin e-mail despre o nouă înregistrare în log. Unde să trimitem e-mailurile specificăm prin variabila `$email`: ```php Debugger::$email = 'admin@example.com'; ``` -Dacă folosiți întregul Nette Framework, puteți seta această variabilă și altele în [fișierul de configurare |nette:configuring]. +Dacă utilizați întregul Nette Framework, puteți seta aceasta și alte setări în [fișierul de configurare |nette:configuring]. -Pentru a vă proteja căsuța de e-mail de inundații, Tracy trimite **numai un mesaj** și creează un fișier `email-sent`. Când un dezvoltator primește notificarea prin e-mail, el verifică jurnalul, își corectează aplicația și șterge fișierul de monitorizare `email-sent`. Acest lucru activează din nou trimiterea de e-mail. +Pentru a nu vă inunda căsuța de e-mail, trimite întotdeauna **doar un singur mesaj** și creează fișierul `email-sent`. Dezvoltatorul, după primirea notificării prin e-mail, verifică logul, corectează aplicația și șterge fișierul de monitorizare `email-sent`, reactivând astfel trimiterea e-mailurilor. -Deschiderea fișierelor în editor .[#toc-opening-files-in-the-editor] -==================================================================== +Deschiderea în editor +===================== -Atunci când este afișată pagina de erori, puteți face clic pe numele fișierelor, iar acestea se vor deschide în editorul dvs. cu cursorul pe linia corespunzătoare. Fișierele pot fi, de asemenea, create (acțiunea `create file`) sau pot fi corectate erori în ele (acțiunea `fix it`). Pentru a face acest lucru, trebuie să [configurați browserul și sistemul |open-files-in-ide]. +La afișarea paginii de eroare, se poate face clic pe numele fișierelor și acestea se vor deschide în editorul dvs. cu cursorul pe linia corespunzătoare. De asemenea, se pot crea fișiere (acțiunea `create file`) sau corecta erori în ele (acțiunea `fix it`). Pentru ca acest lucru să funcționeze, este suficient să [configurați browserul și sistemul |open-files-in-ide]. -Versiuni PHP acceptate .[#toc-supported-php-versions] -===================================================== +Versiuni PHP suportate +====================== -| Tracy | compatibil cu PHP -|-----------|-------------------- -| Tracy 2.10 – 3.0 | PHP 8.0 - 8.2 -| Tracy 2.9 | PHP 7.2 - 8.2 -| Tracy 2.8 | PHP 7.2 - 8.1 -| Tracy 2.6 - 2.7 | PHP 7.1 - 8.0 -| Tracy 2.5 | PHP 5.4 - 7.4 -| Tracy 2.4 | PHP 5.4 - 7.2 +| Tracy | compatibil cu PHP +|-----------|------------------- +| Tracy 2.10 – 3.0 | PHP 8.0 – 8.4 +| Tracy 2.9 | PHP 7.2 – 8.2 +| Tracy 2.8 | PHP 7.2 – 8.1 +| Tracy 2.6 – 2.7 | PHP 7.1 – 8.0 +| Tracy 2.5 | PHP 5.4 – 7.4 +| Tracy 2.4 | PHP 5.4 – 7.2 -Se aplică la cele mai recente versiuni de patch-uri. +Valabil pentru ultima versiune patch. -Porturi .[#toc-ports] -===================== +Porturi +======= -Aceasta este o listă de portări neoficiale către alte cadre și CMS: +Aceasta este o listă de porturi neoficiale pentru alte framework-uri și CMS-uri: - [Drupal 7](https://www.drupal.org/project/traced) - Laravel framework: [recca0120/laravel-tracy](https://github.com/recca0120/laravel-tracy), [whipsterCZ/laravel-tracy](https://github.com/whipsterCZ/laravel-tracy) diff --git a/tracy/ro/open-files-in-ide.texy b/tracy/ro/open-files-in-ide.texy index b59ff680af..0ce032adab 100644 --- a/tracy/ro/open-files-in-ide.texy +++ b/tracy/ro/open-files-in-ide.texy @@ -1,20 +1,20 @@ -Cum să deschideți un fișier în editor din Tracy? (Integrare IDE) -**************************************************************** +Cum să deschideți un fișier în editor din Tracy? (Integrare cu IDE) +******************************************************************* .[perex] -Când este afișată pagina de erori, puteți face clic pe numele fișierelor și acestea se vor deschide în editorul dvs. cu cursorul pe linia corespunzătoare. De asemenea, pot fi create fișiere (acțiunea `create file`) sau pot fi corectate erori în acestea (acțiunea `fix it`). Pentru a face acest lucru, trebuie să configurați browserul și sistemul. +La afișarea paginii de eroare, se poate face clic pe numele fișierelor și acestea se vor deschide în editorul dvs. cu cursorul pe linia corespunzătoare. De asemenea, se pot crea fișiere (acțiunea `create file`) sau corecta erori în ele (acțiunea `fix it`). Pentru ca acest lucru să se întâmple, este necesar să configurați browserul și sistemul. -Tracy deschide fișiere prin intermediul URL-urilor de forma `editor://open/?file=%file&line=%line`, adică cu protocolul `editor://`. Pentru acesta vom înregistra propriul nostru handler. Acesta poate fi orice fișier executabil care procesează parametrii și pornește editorul nostru preferat. +Tracy deschide fișierele prin URL în formatul `editor://open/?file=%file&line=%line`, adică cu protocolul `editor://`. Pentru acesta vom înregistra un handler personalizat. Acesta poate fi orice fișier executabil care "mestecă" parametrii și lansează editorul nostru preferat. -Puteți modifica URL-ul în variabila `Tracy\Debugger::$editor` sau puteți dezactiva click-through prin setarea `Tracy\Debugger::$editor = null`. +Puteți schimba URL-ul în variabila `Tracy\Debugger::$editor`, sau puteți dezactiva clicarea setând `Tracy\Debugger::$editor = null`. -Windows .[#toc-windows] -======================= +Windows +======= -1. Descărcați fișierele corespunzătoare "din depozitul Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/windows pe disc. +1. Descărcați fișierele corespunzătoare "din repozitoriul Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/windows pe disc. -2. Editați `open-editor.js` și decomentați sau editați calea către editorul dvs. în `settings`: +2. Editați fișierul `open-editor.js` și în array-ul `settings` decomentați și, eventual, modificați calea către editorul dvs.: ```js var settings = { @@ -35,19 +35,28 @@ var settings = { ... ``` -Fiți atenți și păstrați barierele duble în căile de acces. +Atenție, păstrați slash-urile duble în căi. -3. Înregistrați în sistem gestionarul pentru protocolul `editor://`. +3. Înregistrați handlerul protocolului `editor://` în sistem. -Acest lucru se face prin rularea `install.cmd`. **Trebuie să îl rulați ca administrator.** Scriptul `open-editor.js` va servi acum protocolul `editor://`. +Acest lucru se face rulând fișierul `install.cmd`. **Trebuie să îl rulați ca Administrator.** Scriptul `open-editor.js` va gestiona acum protocolul `editor://`. +Pentru a putea deschide linkuri generate pe alte servere, cum ar fi pe serverul live sau în Docker, adăugați în `open-editor.js` și maparea URL-ului la distanță la cel local: -Linux .[#toc-linux] -=================== +```js + mappings: { + // cale la distanță: cale locală + '/var/www/nette.app': 'W:\\Nette.web\\_web', + } +``` + + +Linux +===== -1. Descărcați fișierele corespunzătoare "din depozitul Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux în directorul `~/bin`. +1. Descărcați fișierele corespunzătoare "din repozitoriul Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux în directorul `~/bin`. -2. Editați `open-editor.sh` și decomentați sau editați calea către editorul dvs. în variabila `editor`: +2. Editați fișierul `open-editor.sh` și decomentați și, eventual, modificați calea către editorul dvs. în variabila `editor`. ```shell #!/bin/bash @@ -67,24 +76,25 @@ Linux .[#toc-linux] ... ``` -Faceți-o executabilă: +Faceți fișierul executabil: ```shell chmod +x ~/bin/open-editor.sh ``` -Dacă editorul pe care îl folosiți nu este instalat din pachet, probabil că binarul nu va avea o cale în `$PATH`. Acest lucru poate fi corectat cu ușurință. În directorul `~/bin`, creați o legătură simbolică pe binarul editorului. .[note] +.[note] +Dacă editorul utilizat nu este instalat dintr-un pachet, probabil că binarul nu va avea calea în `$PATH`. Acest lucru poate fi remediat simplu. În directorul `~/bin` creați un symlink către binarul editorului. -3. Înregistrați în sistem gestionarul pentru protocolul `editor://`. +3. Înregistrați handlerul protocolului `editor://` în sistem. -Acest lucru se face prin rularea `install.sh`. Scriptul `open-editor.js` va servi acum protocolul `editor://`. +Acest lucru se face rulând fișierul `install.sh`. Scriptul `open-editor.sh` va gestiona acum protocolul `editor://`. -macOS .[#toc-macos] -=================== +macOS +===== -Editorii precum PhpStorm, TextMate etc. vă permit să deschideți fișiere prin intermediul unui URL special, pe care trebuie doar să îl setați: +Editoarele precum PhpStorm, TextMate etc. permit deschiderea fișierelor prin URL-uri speciale, care trebuie doar setate: ```php // PhpStorm @@ -92,44 +102,43 @@ Tracy\Debugger::$editor = 'phpstorm://open?file=%file&line=%line'; // TextMate Tracy\Debugger::$editor = 'txmt://open/?url=file://%file&line=%line'; // MacVim -Tracy\Debugger::$editor = 'mvim://open/?url=file://%file&line=%line'; +Tracy\Debugger::$editor = 'mvim://open?url=file:///%file&line=%line'; // Visual Studio Code Tracy\Debugger::$editor = 'vscode://file/%file:%line'; ``` -Dacă utilizați Tracy standalone, puneți linia înainte de `Tracy\Debugger::enable()`, dacă Nette, înainte de `$configurator->enableTracy()` în `Bootstrap.php`. +Dacă utilizați Tracy independent, inserați linia înainte de `Tracy\Debugger::enable()`, dacă utilizați Nette, atunci înainte de `$configurator->enableTracy()` în `Bootstrap.php`. -Din păcate, acțiunile `create file` sau `fix it` nu funcționează pe macOS. +Acțiunile `create file` sau `fix it` din păcate nu funcționează pe macOS. -Demonstrații .[#toc-demos] -========================== +Exemple +======= -Corectarea unui bug: +Corectarea unei erori: - + -Crearea unui nou fișier: +Crearea unui fișier: - + -Rezolvarea problemelor .[#toc-troubleshooting] -============================================== +Depanarea problemelor +===================== -- În Firefox, este posibil să fie necesar să [permiteți |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] executarea protocolului personalizat în about:config prin setarea `network.protocol-handler.expose.editor` la `false` și `network.protocol-handler.expose-all` la `true`. Cu toate acestea, ar trebui să fie permisă în mod implicit. -- Dacă nu funcționează totul imediat, nu intrați în panică. Încercați să reîmprospătați pagina, reporniți browserul sau calculatorul. Asta ar trebui să ajute. -- Vedeți [aici |https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/] pentru a remedia problema: - Eroare de intrare: There is no script engine for file extension ".js" Poate ați asociat fișierul ".js" unei alte aplicații, nu motorului JScript. +- În Firefox, poate fi necesar să permiteți protocolul [setând |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] `network.protocol-handler.expose.editor` la `false` și `network.protocol-handler.expose-all` la `true` în `about:config`. +- Dacă nu funcționează imediat, nu intrați în panică și încercați să reîmprospătați pagina de câteva ori înainte de a face clic pe link. Va porni! +- Aici este un [link |https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/] pentru a repara eventuala eroare: `Input Error: There is no script engine for file extension ".js"`, `Maybe you associated ".js" file to another app, not JScript engine.` respectiv `nu există un motor de scripting disponibil pentru extensia .js`. -Începând cu versiunea 77 a Google Chrome, nu veți mai vedea caseta de selectare "Deschideți întotdeauna aceste tipuri de linkuri în aplicația asociată" atunci când editorul este deschis printr-un link. Soluție de rezolvare pentru Windows: creați fișierul `fix.reg`: +În Google Chrome, începând cu versiunea 77, nu veți mai vedea caseta de bifat „Deschideți întotdeauna acest tip de linkuri în aplicația asociată” atunci când editorul este lansat printr-un link. Soluție pentru Windows: creați un fișier `fix.reg`: ``` Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\URLWhitelist] "123"="editor://*" ``` -Importați-l prin dublu clic și reporniți Chrome. +Importați-l făcând dublu clic și reporniți browserul Chrome. -În cazul în care aveți mai multe probleme sau întrebări, întrebați pe [forum |https://forum.nette.org]. +Pentru eventuale întrebări sau comentarii, vă rugăm să vă adresați [forumului |https://forum.nette.org]. diff --git a/tracy/ro/recipes.texy b/tracy/ro/recipes.texy index 0cd95f51c4..f0f6f9f22e 100644 --- a/tracy/ro/recipes.texy +++ b/tracy/ro/recipes.texy @@ -1,12 +1,11 @@ -Rețete -****** +Ghiduri +******* -Politica de securitate a conținutului .[#toc-content-security-policy] -===================================================================== +Content Security Policy +======================= -Dacă site-ul dvs. utilizează Content Security Policy, va trebui să adăugați `'nonce-'` și `'strict-dynamic'` la `script-src` pentru ca Tracy să funcționeze corect. Este posibil ca unele pluginuri terțe să necesite directive suplimentare. -Nonce nu este acceptat în directiva `style-src`, dacă utilizați această directivă trebuie să adăugați `'unsafe-inline'`, dar acest lucru ar trebui evitat în modul de producție. +Dacă site-ul dvs. web utilizează Content Security Policy, va trebui să adăugați același `'nonce-'` și `'strict-dynamic'` la `script-src` pentru ca Tracy să funcționeze corect. Unele suplimente de la terți pot necesita setări suplimentare. Nonce nu este suportat în directiva `style-src`, dacă utilizați această directivă, trebuie să adăugați `'unsafe-inline'`, dar ar trebui să evitați acest lucru în modul de producție. Exemplu de configurare pentru [Nette Framework |nette:configuring]: @@ -24,17 +23,16 @@ header("Content-Security-Policy: script-src 'nonce-$nonce' 'strict-dynamic';"); ``` -Încărcare mai rapidă .[#toc-faster-loading] -=========================================== +Încărcare mai rapidă +==================== -Integrarea de bază este simplă, însă dacă aveți scripturi lente de blocare în pagina web, acestea pot încetini încărcarea Tracy. -Soluția este să plasați `` în șablonul dvs. înainte de orice script: +Pornirea este directă, însă dacă aveți pe pagina web scripturi blocante care se încarcă lent, acestea pot încetini încărcarea Tracy. Soluția este să plasați `` în șablonul dvs. înainte de toate scripturile: ```latte - ...<title> + <title>... @@ -42,12 +40,37 @@ Soluția este să plasați `` în șablon ``` -AJAX și cereri redirecționate .[#toc-ajax-and-redirected-requests] -================================================================== +Depanarea cererilor AJAX +======================== -Tracy poate afișa Debug bar și Bluescreens pentru cererile AJAX și redirecționări. Tracy își creează propriile sesiuni, stochează datele în propriile fișiere temporare și utilizează un cookie `tracy-session`. +Tracy capturează automat cererile AJAX create folosind jQuery sau API-ul nativ `fetch`. Cererile sunt afișate în bara Tracy ca rânduri suplimentare, ceea ce permite depanarea ușoară și convenabilă a AJAX-ului. -Tracy poate fi configurat, de asemenea, să utilizeze o sesiune PHP nativă, care este inițiată înainte ca Tracy să fie pornit: +Dacă nu doriți să capturați automat cererile AJAX, puteți dezactiva această funcție setând variabila JavaScript: + +```js +window.TracyAutoRefresh = false; +``` + +Pentru monitorizarea manuală a cererilor AJAX specifice, adăugați antetul HTTP `X-Tracy-Ajax` cu valoarea returnată de `Tracy.getAjaxHeader()`. Iată un exemplu de utilizare cu funcția `fetch`: + +```js +fetch(url, { + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Tracy-Ajax': Tracy.getAjaxHeader(), + } +}) +``` + +Această abordare permite depanarea selectivă a cererilor AJAX. + + +Stocarea datelor +================ + +Tracy poate afișa panouri în bara Tracy și Bluescreen-uri pentru cererile AJAX și redirecționări. Tracy își creează propria sesiune, stochează datele în propriile fișiere temporare și utilizează cookie-ul `tracy-session`. + +Tracy poate fi configurat și pentru a utiliza sesiunea PHP nativă, pe care o pornim înainte de a activa Tracy: ```php session_start(); @@ -55,44 +78,44 @@ Debugger::setSessionStorage(new Tracy\NativeSession); Debugger::enable(); ``` -În cazul în care pornirea unei sesiuni necesită o inițializare mai complexă, puteți porni Tracy imediat (pentru ca acesta să poată gestiona orice erori care apar) și apoi să inițializați gestionarul de sesiune și, în final, să îl informați pe Tracy că sesiunea este pregătită pentru a fi utilizată utilizând funcția `dispatch()`: +În cazul în care pornirea sesiunii necesită o inițializare mai complexă, puteți porni Tracy imediat (pentru a putea procesa eventualele erori apărute), apoi inițializați handlerul sesiunii și, în final, informați Tracy că sesiunea este gata de utilizare folosind funcția `dispatch()`: ```php Debugger::setSessionStorage(new Tracy\NativeSession); Debugger::enable(); -// urmată de inițializarea sesiunii -// și începe sesiunea +// urmează inițializarea sesiunii +// și pornirea sesiunii session_start(); Debugger::dispatch(); ``` -Funcția `setSessionStorage()` există de la versiunea 2.9, înainte de aceasta Tracy folosea întotdeauna sesiunea nativă PHP. +Funcția `setSessionStorage()` există începând cu versiunea 2.9, înainte de aceasta Tracy utiliza întotdeauna sesiunea PHP nativă. -Scrubber personalizat .[#toc-custom-scrubber] -============================================= +Scrubber personalizat +===================== -Scrubber este un filtru care previne scurgerea datelor sensibile, cum ar fi parolele sau acreditările. Filtrul este apelat pentru fiecare element din matricea sau obiectul descărcat și returnează `true` dacă valoarea este sensibilă. În acest caz, în locul valorii se tipărește `*****`. +Scrubber este un filtru care previne scurgerea datelor sensibile la dumpare, cum ar fi parolele sau datele de acces. Filtrul este apelat pentru fiecare element al array-ului sau obiectului dumpat și returnează `true` dacă valoarea este sensibilă. În acest caz, în locul valorii se afișează `*****`. ```php -// evită descărcarea valorilor cheilor și a proprietăților precum `password`, +// previne afișarea valorilor cheilor și proprietăților precum `password`, // `password_repeat`, `check_password`, `DATABASE_PASSWORD`, etc. $scrubber = function(string $key, $value, ?string $class): bool { return preg_match('#password#i', $key) && $value !== null; }; -// îl folosim pentru toate descărcările din BlueScreen +// îl folosim pentru toate dump-urile din interiorul BlueScreen Tracy\Debugger::getBlueScreen()->scrubber = $scrubber; ``` -Logger personalizat .[#toc-custom-logger] -========================================= +Logger personalizat +=================== -Putem crea un logger personalizat pentru a înregistra erori, excepții neacoperite și, de asemenea, pentru a fi apelat de `Tracy\Debugger::log()`. Logger implementează interfața [api:Tracy\ILogger]. +Putem crea propriul nostru logger, care va loga erorile, excepțiile necapturate și va fi, de asemenea, invocat de metoda `Tracy\Debugger::log()`. Loggerul implementează interfața [api:Tracy\ILogger]. ```php use Tracy\ILogger; @@ -101,7 +124,7 @@ class SlackLogger implements ILogger { public function log($value, $priority = ILogger::INFO) { - // trimite o cerere către Slack + // trimite request către Slack } } ``` @@ -112,7 +135,7 @@ class SlackLogger implements ILogger Tracy\Debugger::setLogger(new SlackLogger); ``` -Dacă folosim Nette Framework complet, îl putem seta în fișierul de configurare NEON: +Dacă folosim întregul Nette Framework, îl puteți seta în fișierul de configurare NEON: ```neon services: @@ -120,10 +143,10 @@ services: ``` -Monolog Integration .[#toc-monolog-integration] ------------------------------------------------ +Integrarea monologului +---------------------- -Pachetul Tracy oferă un adaptor PSR-3, care permite integrarea [monolog/monolog](https://github.com/Seldaek/monolog). +Pachetul Tracy oferă un adaptor PSR-3 care permite integrarea [monolog/monolog](https://github.com/Seldaek/monolog). ```php $monolog = new Monolog\Logger('main-channel'); @@ -133,21 +156,21 @@ $tracyLogger = new Tracy\Bridges\Psr\PsrToTracyLoggerAdapter($monolog); Debugger::setLogger($tracyLogger); Debugger::enable(); -Debugger::log('info'); // scrie: [] main-channel.INFO: info [] [] [] -Debugger::log('warning', Debugger::WARNING); // scrie: [] main-channel.WARNING: warning [] [] [] +Debugger::log('info'); // writes: [] main-channel.INFO: info [] [] +Debugger::log('warning', Debugger::WARNING); // writes: [] main-channel.WARNING: warning [] [] ``` -nginx .[#toc-nginx] -=================== +nginx +===== -Dacă Tracy nu funcționează pe nginx, probabil că este configurat greșit. Dacă există ceva de genul +Dacă Tracy nu funcționează pe serverul nginx, probabil că este configurat greșit. Dacă în configurație există ceva de genul: ```nginx try_files $uri $uri/ /index.php; ``` -schimbați-l cu +schimbați-l în: ```nginx try_files $uri $uri/ /index.php$is_args$args; diff --git a/tracy/ro/stopwatch.texy b/tracy/ro/stopwatch.texy index cf3ecd338b..534d466683 100644 --- a/tracy/ro/stopwatch.texy +++ b/tracy/ro/stopwatch.texy @@ -1,19 +1,19 @@ -Cronometru -********** +Măsurarea timpului +****************** -Un alt instrument util este cronometrul de depanare cu o precizie de microsecunde: +Un alt instrument util al depanatorului este cronometrul cu precizie la microsecunde: ```php Debugger::timer(); -// vise plăcute, draga mea +// prințul meu micuț doarme, păsărelele deja cântă dulce... sleep(2); $elapsed = Debugger::timer(); // $elapsed = 2 ``` -Un parametru opțional permite realizarea mai multor măsurători simultan. +Cu un parametru opțional, se pot realiza măsurători multiple. ```php Debugger::timer('page-generating'); @@ -27,9 +27,9 @@ $pageElapsed = Debugger::timer('page-generating'); ``` ```php -Debugger::timer(); // rulează cronometrul +Debugger::timer(); // pornește cronometrul -... // o operațiune care necesită mult timp +... // operație consumatoare de timp -echo Debugger::timer(); // timpul scurs în secunde +echo Debugger::timer(); // afișează timpul scurs în secunde ``` diff --git a/tracy/ru/@home.texy b/tracy/ru/@home.texy index a703930c4d..81b2a484b1 100644 --- a/tracy/ru/@home.texy +++ b/tracy/ru/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Tracy - обязательный инструмент отладки для всех PHP-разработчиков}} -{{description: Tracy - это инструмент, предназначенный для облегчения отладки PHP-кода. Это полезный помощник для всех PHP-программистов, который помогает наглядно визуализировать и протоколировать ошибки, делать дамп переменных и многое другое. Предупреждение: Tracy вызывает привыкание!}} +{{maintitle: Tracy – инструмент отладки, с которым отладка становится в радость}} +{description: Tracy — это инструмент, предназначенный для облегчения отладки PHP-кода. Это полезный помощник для всех PHP-программистов, который помогает визуализировать и логировать ошибки, дампить переменные и многое другое. Предупреждение: Tracy вызывает привыкание!} diff --git a/tracy/ru/@left-menu.texy b/tracy/ru/@left-menu.texy index 80e10e5ad2..4911362af2 100644 --- a/tracy/ru/@left-menu.texy +++ b/tracy/ru/@left-menu.texy @@ -1,7 +1,7 @@ -- [Начало работы |Guide] -- [Дампер |Dumper] -- [Секундомер |Stopwatch] -- [Конфигурирование |Configuring] -- [Рецепты |Recipes] +- [Начинаем работать с Tracy |guide] +- [Дампинг |dumper] +- [Измерение времени |stopwatch] +- [Конфигурация |configuring] +- [Руководства |recipes] - [Интеграция с IDE |open-files-in-ide] - [Создание расширений |extensions] diff --git a/tracy/ru/@menu.texy b/tracy/ru/@menu.texy index 678c28c303..59491b0fc7 100644 --- a/tracy/ru/@menu.texy +++ b/tracy/ru/@menu.texy @@ -1,3 +1,3 @@ -- [Главная |@home] -- [Документация |Guide] +- [Введение |@home] +- [Документация |guide] - "GitHub .[link-external]":https://github.com/nette/tracy diff --git a/tracy/ru/@meta.texy b/tracy/ru/@meta.texy new file mode 100644 index 0000000000..22a9b5fa46 --- /dev/null +++ b/tracy/ru/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документация Tracy}} diff --git a/tracy/ru/configuring.texy b/tracy/ru/configuring.texy index 98a4663448..10054c7bb6 100644 --- a/tracy/ru/configuring.texy +++ b/tracy/ru/configuring.texy @@ -1,169 +1,180 @@ -Конфигурация Трейси -******************* +Конфигурация Tracy +****************** -Следующие примеры предполагают, что определен следующий псевдоним класса: +Все примеры предполагают созданный псевдоним (alias): ```php use Tracy\Debugger; ``` -Протоколирование ошибок .[#toc-error-logging] ---------------------------------------------- +Логирование ошибок +------------------ ```php $logger = Debugger::getLogger(); -// если произошла ошибка, то уведомление отправляется на этот email -$logger->email = 'dev@example.com'; // (string|string[]) defaults to unset +// e-mail, на который отправляются уведомления о возникновении ошибки +$logger->email = 'dev@example.com'; // (string|string[]) по умолчанию не установлено -// отправитель электронной почты -$logger->fromEmail = 'me@example.com'; // (string) defaults to unset +// отправитель e-mail +$logger->fromEmail = 'me@example.com'; // (string) по умолчанию не установлено -// процедура отправки электронной почты -$logger->mailer = /* ... */; // (вызываемая) по умолчанию отправка почтой mail() +// процедура, обеспечивающая отправку email +$logger->mailer = /* ... */; // (callable) по умолчанию отправка функцией mail() -// через какое наименьшее время отправить еще одно письмо? -$logger->emailSnooze = /* ... */; // (строка) по умолчанию '2 дня' +// через какое минимальное время отправить следующий email? +$logger->emailSnooze = /* ... */; // (string) по умолчанию '2 days' -// для каких уровней ошибок BlueScreen также ведется журнал? -Debugger::$logSeverity = E_WARNING | E_NOTICE; // по умолчанию 0 (уровень ошибок отсутствует) +// для каких уровней ошибок логируется и BlueScreen? +Debugger::$logSeverity = E_WARNING | E_NOTICE; // по умолчанию 0 (никакие уровни ошибок) ``` -`dump()` Поведение .[#toc-dump-behavior] ----------------------------------------- +Поведение `dump()` +------------------ ```php // максимальная длина строки -Debugger::$maxLength = 150; // (int) по умолчанию согласно Трейси +Debugger::$maxLength = 150; // (int) по умолчанию в зависимости от версии Tracy -// насколько глубоким будет список -Debugger::$maxDepth = 10; // (int) по умолчанию согласно Tracy +// максимальная глубина вложенности +Debugger::$maxDepth = 10; // (int) по умолчанию в зависимости от версии Tracy -// скрывать значения этих ключей (начиная с версии Tracy 2.8) -Debugger::$keysToHide = ['password', /* ... */]; // (string[]) по умолчанию [] +// скрыть значения этих ключей (с Tracy 2.8) +Debugger::$keysToHide = ['password', /* ... */]; // (string[]) по умолчанию [] -// визуальная тема (начиная с версии Tracy 2.8) -Debugger::$dumpTheme = 'dark'; // (light|dark) по умолчанию 'light' +// визуальная тема (с Tracy 2.8) +Debugger::$dumpTheme = 'dark'; // (light|dark) по умолчанию 'light' -// отображает место, где был вызван dump()? -Debugger::$showLocation = /* ... */; // (bool) по умолчанию в соответствии с Tracy +// отобразить место, где была вызвана функция dump()? +Debugger::$showLocation = /* ... */; // (bool) по умолчанию в зависимости от версии Tracy ``` -Другие .[#toc-others] ---------------------- +Прочее +------ ```php -// в режиме разработки вы будете видеть уведомления или предупреждения об ошибках как BlueScreen -Debugger::$strictMode = /* ... */; // (bool|int) по умолчанию false, вы можете выбрать только определенные уровни ошибок (например, E_USER_DEPRECATED | E_DEPRECATED) +// в режиме разработки отображает ошибки типа notice или warning как BlueScreen +Debugger::$strictMode = /* ... */; // (bool|int) по умолчанию false, можно выбрать только некоторые уровни ошибок (напр. E_USER_DEPRECATED | E_DEPRECATED) -// отображает беззвучные (@) сообщения об ошибках -Debugger::$scream = /* ... */; // (bool|int) по умолчанию false, с версии 2.9 можно выбрать только определенные уровни ошибок (например, E_USER_DEPRECATED | E_DEPRECATED) +// отображать подавленные (@) сообщения об ошибках? +Debugger::$scream = /* ... */; // (bool|int) по умолчанию false, с версии 2.9 можно выбрать только некоторые уровни ошибок (напр. E_USER_DEPRECATED | E_DEPRECATED) // формат ссылки для открытия в редакторе -Debugger::$editor = /* ... */; // (string|null) по умолчанию 'editor://open/?file=%file&line=%line' +Debugger::$editor = /* ... */; // (string|null) по умолчанию 'editor://open/?file=%file&line=%line' // путь к шаблону с пользовательской страницей для ошибки 500 -Debugger::$errorTemplate = /* ... */; // (строка) по умолчанию не задана +Debugger::$errorTemplate = /* ... */; // (string) по умолчанию не установлено -// показывать панель трейсов? -Debugger::$showBar = /* ... */; // (bool) по умолчанию true +// отображать Tracy Bar? +Debugger::$showBar = /* ... */; // (bool) по умолчанию true Debugger::$editorMapping = [ - // original => new + // оригинал => новый '/var/www/html' => '/data/web', '/home/web' => '/srv/html', ]; ``` -Nette Framework .[#toc-nette-framework] ---------------------------------------- +Nette Framework +--------------- -Если вы используете Nette Framework, вы также можете настроить Tracy и добавить новые панели на панель Tracy Bar с помощью файла конфигурации. -В конфигурации можно задать параметры Tracy, а также добавить новые панели на панель Tracy. Эти параметры применяются только после создания контейнера DI, поэтому ошибки, возникшие ранее, не могут их отразить. +Если вы используете Nette Framework, вы можете конфигурировать Tracy и добавлять новые панели в Tracy Bar также с помощью конфигурационного файла. В конфигурации можно устанавливать параметры и добавлять новые панели в Tracy Bar. Эти настройки применяются только после создания DI-контейнера, поэтому ошибки, возникшие до этого, не могут их отражать. -Конфигурация регистрации ошибок: +Конфигурация логирования ошибок: ```neon tracy: - # если произошла ошибка, уведомление отправляется на этот email - email: dev@example.com # (string|string[]) по умолчанию unset + # e-mail, на который отправляются уведомления о возникновении ошибки + email: dev@example.com # (string|string[]) по умолчанию не установлено - # отправитель электронной почты - fromEmail: robot@example.com # (string) по умолчанию unset + # отправитель e-mail + fromEmail: robot@example.com # (string) по умолчанию не установлено - # период отсрочки отправки писем (начиная с версии Tracy 2.8.8) - emailSnooze: ... # (строка) по умолчанию '2 дня' + # время отсрочки отправки e-mail (с Tracy 2.8.8) + emailSnooze: ... # (string) по умолчанию '2 days' - # использовать почтовик, определенный в конфигурации? (начиная с Tracy 2.5) + # использовать для отправки e-mail Nette mailer? (с Tracy 2.5) netteMailer: ... # (bool) по умолчанию true - # для каких уровней ошибок BlueScreen также записывается в журнал? - logSeverity: [E_WARNING, E_NOTICE] # по умолчанию [] + # для каких уровней ошибок логируется и BlueScreen? + logSeverity: [E_WARNING, E_NOTICE] # по умолчанию [] ``` -Конфигурация для функции `dump()`: +Конфигурация поведения функции `dump()`: ```neon tracy: # максимальная длина строки - maxLength: 150 # (int) по умолчанию в соответствии с Tracy + maxLength: 150 # (int) по умолчанию в зависимости от версии Tracy - # насколько глубоким будет список - maxDepth: 10 # (int) по умолчанию согласно Tracy + # максимальная глубина вложенности + maxDepth: 10 # (int) по умолчанию в зависимости от версии Tracy - # скрывать значения этих ключей (начиная с Tracy 2.8) + # скрыть значения этих ключей (с Tracy 2.8) keysToHide: [password, pass] # (string[]) по умолчанию [] - # визуальная тема (начиная с Tracy 2.8) - dumpTheme: dark # (light|dark) по умолчанию 'light' + # визуальная тема (с Tracy 2.8) + dumpTheme: dark # (light|dark) по умолчанию 'light' - # отображает место, где была вызвана функция dump()? - showLocation: ... # (bool) по умолчанию согласно Tracy + # отобразить место, где была вызвана функция dump()? + showLocation: ... # (bool) по умолчанию в зависимости от версии Tracy ``` -Чтобы установить расширение Tracy: +Установка расширений Tracy: ```neon tracy: - # appends bars to Tracy Bar + # добавляет панели в Tracy Bar bar: - Nette\Bridges\DITracy\ContainerPanel - IncludePanel - XDebugHelper('myIdeKey') - MyPanel(@MyService) - # append panels to BlueScreen + # добавляет панели в BlueScreen blueScreen: - DoctrinePanel::renderException ``` -Другие варианты: +Прочие опции: ```neon tracy: - # в режиме разработки вы увидите уведомления или предупреждения об ошибках как BlueScreen + # в режиме разработки отображает ошибки типа notice или warning как BlueScreen strictMode: ... # по умолчанию true - # отображает беззвучные (@) сообщения об ошибках + # отображать подавленные (@) сообщения об ошибках? scream: ... # по умолчанию false # формат ссылки для открытия в редакторе - editor: ... # (строка) по умолчанию 'editor://open/?file=%file&line=%line' + editor: ... # (string) по умолчанию 'editor://open/?file=%file&line=%line' # путь к шаблону с пользовательской страницей для ошибки 500 - errorTemplate: ... # (строка) по умолчанию unset + errorTemplate: ... # (string) по умолчанию не установлено - # показывает панель трейсинга? + # отображать Tracy Bar? showBar: ... # (bool) по умолчанию true editorMapping: - # original: new + # оригинал: новый /var/www/html: /data/web /home/web: /srv/html ``` -Значения опций `logSeverity`, `strictMode` и `scream` могут быть записаны в виде массива уровней ошибок (например. `[E_WARNING, E_NOTICE]`) или как выражение, используемое в PHP (например, `E_ALL & ~E_NOTICE`). +Значения опций `logSeverity`, `strictMode` и `scream` можно записывать как массив уровней ошибок (напр. `[E_WARNING, E_NOTICE]`) или как выражение, используемое в языке PHP (напр. `E_ALL & ~E_NOTICE`). + + +Сервисы DI +---------- + +Эти сервисы добавляются в DI-контейнер: + +| Имя | Тип | Описание +|---------------------------------------------------------- +| `tracy.logger` | [api:Tracy\ILogger] | логгер +| `tracy.blueScreen` | [api:Tracy\BlueScreen] | BlueScreen +| `tracy.bar` | [api:Tracy\Bar] | Tracy Bar diff --git a/tracy/ru/dumper.texy b/tracy/ru/dumper.texy index 0d96e4e4b3..e4175bbfa2 100644 --- a/tracy/ru/dumper.texy +++ b/tracy/ru/dumper.texy @@ -1,16 +1,16 @@ -Самосвал -******** +Дампинг +******* -Каждый разработчик отладочных программ хорошо знаком с функцией `var_dump`, которая подробно перечисляет все содержимое любой переменной. К сожалению, ее вывод не имеет HTML-форматирования и выводит дамп в одну строку HTML-кода, не говоря уже о контекстном экранировании. Необходимо заменить `var_dump` на более удобную функцию. Именно такой функцией и является `dump()`. +Каждый отладчик — хороший друг функции [php:var_dump], которая подробно выводит содержимое переменной. К сожалению, в среде HTML вывод теряет форматирование и сливается в одну строку, не говоря уже о санитизации HTML-кода. На практике необходимо заменить `var_dump` более удобной функцией. Этой функцией является `dump()`. ```php $arr = [10, 20.2, true, null, 'hello']; dump($arr); -// or Debugger::dump($arr); +// или Debugger::dump($arr); ``` -генерирует вывод: +сгенерирует вывод: [* dump-basic.webp *] @@ -22,27 +22,27 @@ Debugger::$dumpTheme = 'dark'; [* dump-dark.webp *] -Вы также можете изменить глубину вложенности по адресу `Debugger::$maxDepth` и длину отображаемых строк по адресу `Debugger::$maxLength`. Естественно, меньшие значения ускоряют рендеринг Tracy. +Далее мы можем изменить глубину вложенности с помощью [Debugger::$maxDepth |api:Tracy\Debugger::$maxDepth] и длину отображаемых меток с помощью [Debugger::$maxLength |api:Tracy\Debugger::$maxLength]. Меньшие значения, естественно, ускорят Tracy. ```php Debugger::$maxDepth = 2; // default: 3 Debugger::$maxLength = 50; // default: 150 ``` -Функция `dump()` может отображать и другую полезную информацию. `Tracy\Dumper::LOCATION_SOURCE` добавляет всплывающую подсказку с путем к файлу, в котором была вызвана функция. `Tracy\Dumper::LOCATION_LINK` добавляет ссылку на файл. `Tracy\Dumper::LOCATION_CLASS` добавляет всплывающую подсказку к каждому объекту дампа, содержащую путь к файлу, в котором определен класс объекта. Все эти константы могут быть установлены в переменной `Debugger::$showLocation` перед вызовом функции `dump()`. Вы можете установить несколько значений одновременно, используя оператор `|`. +Функция `dump()` умеет выводить и другую полезную информацию. Константа `Tracy\Dumper::LOCATION_SOURCE` добавляет подсказку (tooltip) с путем к месту, где была вызвана функция. `Tracy\Dumper::LOCATION_LINK` предоставляет нам ссылку на это место. `Tracy\Dumper::LOCATION_CLASS` для каждого дампа объекта выводит подсказку (tooltip) с путем к файлу, в котором определен его класс. Константы устанавливаются в переменную `Debugger::$showLocation` еще до вызова `dump()`. Если мы хотим установить несколько значений одновременно, мы соединяем их с помощью оператора `|`. ```php -Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // Shows path to where the dump() was called -Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Shows both paths to the classes and link to where the dump() was called -Debugger::$showLocation = false; // Hides additional location information -Debugger::$showLocation = true; // Shows all additional location information +Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // Устанавливает только вывод о месте вызова функции +Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Устанавливает одновременно вывод ссылки и путь к классу +Debugger::$showLocation = false; // Отключает вывод дополнительной информации +Debugger::$showLocation = true; // Включает вывод всей дополнительной информации ``` -Очень удобной альтернативой `dump()` является `dumpe()` (т.е. dump and exit) и `bdump()`. Это позволяет нам сбрасывать переменные в Tracy Bar. Это полезно, потому что дампы не портят вывод, и мы также можем добавить заголовок к дампу. +Практической альтернативой `dump()` являются `dumpe()` (dump & exit) и `bdump()`. Последний позволяет нам вывести значение переменной в панели Tracy Baru. Это очень удобно, так как дампы отделены от макета страницы, и мы также можем разместить к ним комментарий. ```php -bdump([2, 4, 6, 8], 'even numbers up to ten'); -bdump([1, 3, 5, 7, 9], 'odd numbers up to ten'); +bdump([2, 4, 6, 8], 'четные числа до десяти'); +bdump([1, 3, 5, 7, 9], 'нечетные числа до десяти'); ``` -[* bardump-en.webp *] +[* bardump-cs.webp *] diff --git a/tracy/ru/extensions.texy b/tracy/ru/extensions.texy index 65d7147d78..44029cfc71 100644 --- a/tracy/ru/extensions.texy +++ b/tracy/ru/extensions.texy @@ -1,23 +1,23 @@ -Создание расширений Трейси -************************** +Создание расширений для Tracy +*****************************
                                                                                                                            -Tracy - отличный инструмент для отладки вашего приложения. Однако иногда вам требуется больше информации, чем предлагает Tracy. Вы узнаете о: +Tracy предоставляет отличный инструмент для отладки вашего приложения. Однако иногда вам хотелось бы иметь под рукой и некоторую другую информацию. Мы покажем, как написать собственное расширение для Tracy Bar, чтобы сделать разработку еще приятнее. -- Создание собственных панелей Tracy Bar -- Создание собственных расширений Bluescreen +- Создание собственной панели для Tracy Bar +- Создание собственного расширения для Bluescreen
                                                                                                                            .[tip] -Вы можете найти полезные расширения для Tracy на "Componette":https://componette.org/search/tracy. +Репозиторий готовых расширений для Tracy вы найдете на "Componette":https://componette.org/search/tracy. -Расширения для бара Tracy .[#toc-tracy-bar-extensions] -====================================================== +Расширения для Tracy Bar +======================== -Создать новое расширение для Tracy Bar очень просто. Вам необходимо реализовать интерфейс `Tracy\IBarPanel` с методами `getTab()` и `getPanel()`. Эти методы должны возвращать HTML-код вкладки (небольшой ярлык на панели Tracy Bar) и панели (всплывающее окно, отображаемое после щелчка на вкладке). Если `getPanel()` не возвращает ничего, будет отображаться только вкладка. Если `getTab()` ничего не возвращает, то ничего не отображается и `getPanel()` не будет вызван. +Создать новое расширение для Tracy Bar несложно. Вы создаете объект, реализующий интерфейс `Tracy\IBarPanel`, который имеет два метода: `getTab()` и `getPanel()`. Методы должны вернуть HTML-код вкладки (tab) (маленькая метка, отображаемая прямо на Baru) и панели. Если `getPanel()` ничего не возвращает, отобразится только сама метка. Если `getTab()` ничего не возвращает, ничего не отображается, и getPanel() больше не вызывается. ```php class ExamplePanel implements Tracy\IBarPanel @@ -35,16 +35,16 @@ class ExamplePanel implements Tracy\IBarPanel ``` -Регистрация .[#toc-registration] --------------------------------- +Регистрация +----------- -Регистрация осуществляется по телефону `Tracy\Bar::addPanel()`: +Регистрация выполняется с помощью `Tracy\Bar::addPanel()`: ```php Tracy\Debugger::getBar()->addPanel(new ExamplePanel); ``` -или вы можете просто зарегистрировать свою панель в конфигурации приложения: +Или вы можете зарегистрировать панель прямо в конфигурации приложения: ```neon tracy: @@ -53,70 +53,70 @@ tracy: ``` -HTML-код вкладки .[#toc-tab-html-code] --------------------------------------- +HTML-код вкладки (tab) +---------------------- -Должно выглядеть примерно так: +Должен выглядеть примерно так: ```latte - + ... - Title + Заголовок ``` -Изображение должно быть в формате SVG. Если вам не нужна всплывающая подсказка, вы можете оставить `` исключить. +Изображение должно быть в формате SVG. Если поясняющая метка не нужна, `` можно опустить. -HTML код панели .[#toc-panel-html-code] ---------------------------------------- +HTML-код панели +--------------- -Должно выглядеть примерно так: +Должен выглядеть примерно так: ```latte -

                                                                                                                            Title

                                                                                                                            +

                                                                                                                            Заголовок

                                                                                                                            - ... content ... + ... содержимое ...
                                                                                                                            ``` -Заголовок должен быть либо таким же, как на вкладке, либо содержать дополнительную информацию. +Заголовок должен быть либо таким же, как заголовок вкладки (tab), либо может содержать дополнительные данные. -Одно расширение может быть зарегистрировано несколько раз, поэтому рекомендуется не использовать атрибут `id` для стилизации. Вы можете использовать классы, предпочтительно в `tracy-addons-[-]` формате. При создании CSS лучше использовать `#tracy-debug .class`, так как такое правило имеет более высокий приоритет, чем reset. +Нужно учитывать, что одно расширение может зарегистрироваться несколько раз, например, с другими настройками, поэтому для стилизации нельзя использовать CSS id, а только class, и это в виде `tracy-addons-<ИмяКласса>[-<необязательно>]`. Затем запишите класс в div вместе с классом `tracy-inner`. При написании CSS полезно писать `#tracy-debug .trida`, потому что правило тогда имеет более высокий приоритет, чем reset. -Стили по умолчанию .[#toc-default-styles] ------------------------------------------ +Стили по умолчанию +------------------ -На панели элементы ``, `
                                                                                                                            `, `
                                                                                                                            `, `` имеют стили по умолчанию. Для создания ссылки для скрытия или отображения другого элемента соедините их атрибутами `href` и `id` и классом `tracy-toggle`.
                                                                                                                            +В панели предварительно стилизованы ``, `
                                                                                                                            `, `
                                                                                                                            `, ``. Если вы хотите создать ссылку, которая скрывает и показывает другой элемент, свяжите их атрибутами `href` и `id` и классом `tracy-toggle`:
                                                                                                                             
                                                                                                                             ```latte
                                                                                                                            -Detail
                                                                                                                            +Детали
                                                                                                                             
                                                                                                                            -
                                                                                                                            ...
                                                                                                                            +
                                                                                                                            ...
                                                                                                                            ``` -Если состояние по умолчанию - свернутое, добавьте класс `tracy-collapsed` к обоим элементам. +Если начальное состояние должно быть свернутым, добавьте обоим элементам класс `tracy-collapsed`. -Используйте статический счетчик для предотвращения дублирования идентификаторов на одной странице. +Используйте статический счетчик (Counter), чтобы не создавались дублирующиеся ID на одной странице. -Расширения для синего экрана .[#toc-bluescreen-extensions] -========================================================== +Расширение для Bluescreen +========================= -Вы можете добавить свои собственные визуализации исключений или панели, которые будут отображаться на блюскрине. +Таким образом можно добавлять собственные визуализации исключений или панели, которые отобразятся на bluescreen. -Расширение делается следующим образом: +Расширение создается этой командой: ```php -Tracy\Debugger::getBlueScreen()->addPanel(function (?Throwable $e) { // catched exception +Tracy\Debugger::getBlueScreen()->addPanel(function (?Throwable $e) { // перехваченное исключение return [ - 'tab' => '...Title...', - 'panel' => '...content...', + 'tab' => '...Метка...', + 'panel' => '...HTML-код панели...', ]; }); ``` -Функция вызывается дважды, сначала само исключение передается в параметре `$e`, а возвращаемая панель выводится в начале страницы. Если ничего не возвращается, то панель не отрисовывается. Затем она вызывается с параметром `null`, и возвращаемая панель рендерится ниже стека вызовов. Если функция возвращает `'bottom' => true` в массиве, то панель отрисовывается в самом низу. +Функция вызывается дважды: сначала в параметре `$e` передается само исключение, и возвращенная панель отображается в начале страницы. Если ничего не возвращает, панель не отображается. Затем она вызывается с параметром `null` (в случае фатальной ошибки, которую нельзя перехватить как исключение), и возвращенная панель отображается под стеком вызовов (callstack). Если функция возвращает в массиве ключ `'bottom' => true`, панель отображается в самом низу. diff --git a/tracy/ru/guide.texy b/tracy/ru/guide.texy index 93f029345a..80e1cc88fe 100644 --- a/tracy/ru/guide.texy +++ b/tracy/ru/guide.texy @@ -1,213 +1,212 @@ -Начало работы с Трейси -********************** +Начинаем работать с Tracy +*************************
                                                                                                                            -Библиотека Tracy - это полезный помощник для обычных PHP-программистов. Она поможет вам: +Библиотека Tracy — полезный ежедневный помощник PHP-программиста. Она поможет вам: - быстро обнаруживать и исправлять ошибки -- регистрировать ошибки -- дамп переменных -- измерять время выполнения скриптов/запросов -- просматривать потребление памяти +- логировать ошибки +- выводить переменные +- измерять время выполнения скриптов и запросов к базе данных +- отслеживать потребление памяти
                                                                                                                            -PHP - идеальный язык для создания едва различимых ошибок, потому что он предоставляет программистам большую гибкость. Tracy\Debugger более ценен из-за этого. Это лучший инструмент среди диагностических. +PHP — это язык, будто созданный для совершения трудно обнаруживаемых ошибок, поскольку дает разработчикам значительную свободу. Тем ценнее инструмент отладки Tracy. Среди диагностических инструментов для PHP он представляет собой абсолютную вершину. -Если вы встречаетесь с Трейси впервые, поверьте, ваша жизнь начнет делиться на ту, что была до Трейси, и ту, что с ней. Добро пожаловать в хорошую часть! +Если вы сегодня впервые сталкиваетесь с Tracy, то поверьте, что ваша жизнь начнет делиться на ту, что была до Tracy, и ту, что с ней. Добро пожаловать в лучшую часть! -Установка и требования .[#toc-installation-and-requirements] -============================================================ +Установка +========= -Лучший способ установки Tracy - [скачать последний пакет |https://github.com/nette/tracy/releases] или использовать Composer: +Лучший способ установить Tracy — это [скачать последний пакет](https://github.com/nette/tracy/releases) или использовать Composer: ```shell composer require tracy/tracy ``` -Также вы можете скачать весь пакет или файл [tracy.phar |https://github.com/nette/tracy/releases]. +Вы также можете скачать весь пакет как файл [tracy.phar |https://github.com/nette/tracy/releases]. -Использование .[#toc-usage] -=========================== +Использование +============= -Трейси активируется вызовом метода `Tracy\Debugger::enable()' как можно раньше в начале программы, до того, как будет отправлен какой-либо вывод: +Tracy активируем вызовом метода `Tracy\Debugger::enable()' как можно раньше в начале программы, перед отправкой любого вывода: ```php use Tracy\Debugger; -require 'vendor/autoload.php'; // альтернатива tracy.phar +require 'vendor/autoload.php'; // или tracy.phar Debugger::enable(); ``` -Первое, что вы заметите на странице, - это панель Tracy Bar в правом нижнем углу. Если вы его не видите, это может означать, что Tracy работает в производственном режиме. -Это происходит потому, что в целях безопасности Tracy виден только на localhost. Чтобы проверить, работает ли он, вы можете временно перевести его в режим разработки с помощью параметра `Debugger::enable(Debugger::Development)`. +Первое, что вы заметите на странице, — это Tracy Bar в правом нижнем углу. Если вы его не видите, это может означать, что Tracy работает в production-режиме. Tracy по соображениям безопасности видна только на localhost. Для тестирования, работает ли она, вы можете временно переключить ее в режим разработки с помощью параметра `Debugger::enable(Debugger::Development)`. -Бар Трейси .[#toc-tracy-bar] -============================ +Tracy Bar +========= -Панель Трейси - это плавающая панель. Она отображается в правом нижнем углу страницы. Вы можете перемещать ее с помощью мыши. Она запоминает свое положение после перезагрузки страницы. +Tracy Bar — это плавающая панель, которая отображается в правом нижнем углу страницы. Мы можем перемещать ее мышью, и после перезагрузки страницы она запомнит свою позицию. [* tracy-bar.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html -Вы можете добавить другие полезные панели на панель Tracy Bar. Вы можете найти интересные из них в [аддонах |https://componette.org] или [создать свои собственные |extensions]. +В Tracy Bar можно добавлять другие полезные панели. Многие из них вы найдете в [дополнениях |https://componette.org], или вы даже [можете написать свои собственные |extensions]. -Если вы не хотите показывать панель Tracy Bar, установите: +Если вы не хотите отображать Tracy Bar, установите: ```php Debugger::$showBar = false; ``` -Визуализация ошибок и исключений .[#toc-visualization-of-errors-and-exceptions] -=============================================================================== +Визуализация ошибок и исключений +================================ -Наверняка вы знаете, как PHP сообщает об ошибках: в исходном коде страницы есть что-то вроде этого: +Вы наверняка хорошо знаете, как PHP сообщает об ошибках: в исходный код страницы выводит что-то вроде этого: /--pre .{font-size: 90%} Parse error: syntax error, unexpected '}' in HomePresenter.php on line 15 \-- -or uncaught exception: +или при неперехваченном исключении: /--pre .{font-size: 90%} Fatal error: Uncaught Nette\MemberAccessException: Call to undefined method Nette\Application\UI\Form::addTest()? in /sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php:100 Stack trace: #0 /sandbox/vendor/nette/utils/src/Utils/Object.php(75): Nette\Utils\ObjectMixin::call(Object(Nette\Application\UI\Form), 'addTest', Array) -#1 /sandbox/app/forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) -#2 /sandbox/app/presenters/SignPresenter.php(21): App\Forms\SignFormFactory->create() -#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presenters\SignPresenter->createComponentSignInForm('signInForm') +#1 /sandbox/app/Forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) +#2 /sandbox/app/Presentation/Sign/SignPresenter.php(21): App\Forms\SignFormFactory->create() +#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presentation\Sign\SignPresenter->createComponentSignInForm('signInForm') #4 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(139): Nette\ComponentModel\Container->createComponent('signInForm') #5 /sandbox/temp/cache/latte/15206b353f351f6bfca2c36cc.php(17): Nette\ComponentModel\Co in /sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php on line 100
                                                                                                                            \-- -Ориентироваться в этом выводе не так просто. Если вы включите функцию Tracy, то ошибки и исключения будут отображаться в совершенно другом виде: +Разобраться в таком выводе не так-то просто. Если мы включим Tracy, ошибка или исключение отображаются совершенно в другом виде: [* tracy-exception.webp .{url:-} *] -Сообщение об ошибке буквально кричит. Вы можете увидеть часть исходного кода с выделенной строкой, в которой произошла ошибка. Сообщение четко объясняет ошибку. Весь сайт [интерактивный, попробуйте его |https://nette.github.io/tracy/tracy-exception.html]. +Сообщение об ошибке буквально кричит. Мы видим часть исходного кода с выделенной строкой, где произошла ошибка, и информация *Call to undefined method Nette\Http\User::isLogedIn()* понятно объясняет, о какой ошибке идет речь. Вся страница к тому же живая, мы можем переходить по ссылкам к более подробной информации. [Попробуйте сами |https://nette.github.io/tracy/tracy-exception.html]. -И знаете что? Фатальные ошибки отлавливаются и отображаются точно так же. Не нужно устанавливать никаких расширений (нажмите для живого примера): +И знаете что? Таким образом она перехватывает и отображает даже фатальные ошибки. Без необходимости устанавливать какие-либо расширения. [* tracy-error.webp .{url:-} *] -Такие ошибки, как опечатка в имени переменной или попытка открыть несуществующий файл, генерируют отчеты уровня E_NOTICE или E_WARNING. Их можно легко пропустить и/или они могут быть полностью скрыты в графическом макете веб-страницы. Позвольте Tracy управлять ими: +Ошибки, такие как опечатка в имени переменной или попытка открыть несуществующий файл, генерируют сообщения уровня E_NOTICE или E_WARNING. Их легко пропустить в графике страницы, они даже могут быть вообще не видны (разве что при просмотре кода страницы). [* tracy-notice2.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html -Или они могут отображаться как ошибки: +Или они могут быть отображены так же, как ошибки: ```php -Debugger::$strictMode = true; // display all errors -Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // all errors except deprecated notices +Debugger::$strictMode = true; // отображать все ошибки +Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // все ошибки, кроме уведомлений о deprecation ``` [* tracy-notice.webp .{url:-} *] -Примечание: Tracy при активации изменяет уровень сообщения об ошибках на E_ALL. Если вы хотите изменить это значение, сделайте это после вызова `enable()`. +Примечание: Tracy после активации изменяет уровень сообщений об ошибках на E_ALL. Если вы хотите изменить это значение, сделайте это после вызова `enable()`. -Режим разработки и режим производства .[#toc-development-vs-production-mode] -============================================================================ +Режим разработки vs production-режим +==================================== -Как видите, Трейси довольно разговорчив, что можно оценить в среде разработки, в то время как на рабочем сервере это привело бы к катастрофе. Это потому, что там не должна отображаться отладочная информация. Поэтому Tracy имеет **автоопределение среды**, и если пример запущен на рабочем сервере, ошибка будет записана в журнал, а не отображена, и посетитель увидит только удобное сообщение: +Как видите, Tracy довольно многословна, что можно оценить в среде разработки, в то время как на production-сервере это вызвало бы настоящую катастрофу. Там ведь никакая отладочная информация выводиться не должна. Поэтому Tracy обладает **автоопределением среды**, и если мы запустим пример на реальном сервере, ошибка вместо отображения будет залогирована, а посетитель увидит только понятное пользователю сообщение: [* tracy-error2.webp .{url:-} *] -Режим Production подавляет отображение всей отладочной информации, отправляемой с помощью функции [dump() |dumper], и, конечно же, всех сообщений об ошибках, генерируемых PHP. Так что если вы забыли в коде какой-нибудь `dump($obj)`, можете не беспокоиться, на рабочем сервере ничего не будет отображено. +Production-режим подавляет отображение всей отладочной информации, которую мы отправляем наружу с помощью [dump() |dumper], и, конечно же, всех сообщений об ошибках, которые генерирует PHP. Так что если вы забыли в коде какой-нибудь `dump($obj)`, можете не беспокоиться, на production-сервере ничего не выведется. -Как работает автоматическое определение режима? Режим является режимом разработки, если приложение работает на localhost (т.е. IP-адрес `127.0.0.1` или `::1`) и отсутствует прокси (т.е. его HTTP-заголовок). В противном случае оно работает в режиме production. +Как работает автоопределение режима? Режим является режимом разработки, если приложение запущено на localhost (т.е. IP-адрес `127.0.0.1` или `::1`) и при этом отсутствует прокси (т.е. ее HTTP-заголовок). В противном случае она работает в production-режиме. -Если вы хотите включить режим разработки в других случаях, например, для разработчиков, получающих доступ с определенного IP-адреса, вы можете указать его в качестве параметра метода `enable()`: +Если мы хотим разрешить режим разработки и в других случаях, например, программистам, обращающимся с определенного IP-адреса, мы укажем его как параметр метода `enable()`: ```php -Debugger::enable('23.75.345.200'); // вы также можете предоставить массив IP-адресов +Debugger::enable('23.75.345.200'); // можно также указать массив IP-адресов ``` -Мы определенно рекомендуем комбинировать IP-адрес с cookie. Храните секретный токен, например, `secret1234`, в cookie `tracy-debug`, и таким образом активируйте режим разработки только для разработчиков, обращающихся с определенного IP-адреса, у которых в cookie есть указанный токен: +Настоятельно рекомендуем комбинировать IP-адрес с cookie. В cookie `tracy-debug` сохраним секретный токен, напр. `secret1234`, и таким образом активируем режим разработки только для программистов, обращающихся с определенного IP-адреса, у которых в cookie есть упомянутый токен: ```php Debugger::enable('secret1234@23.75.345.200'); ``` -Вы также можете напрямую установить режим разработки/производства, используя константы `Debugger::Development` или `Debugger::Production` в качестве параметра метода `enable()`. +Режим разработки/production-режим мы также можем установить напрямую использованием константы `Debugger::Development` или `Debugger::Production` в качестве параметра метода `enable()`. .[note] -Если вы используете Nette Framework, посмотрите, как [установить режим для него |application:bootstrap#Development vs Production Mode], и тогда он также будет использоваться для Tracy. +Если вы используете Nette Framework, посмотрите, как [установить режим для него |application:bootstrapping#Режим разработки vs режим production], и он затем будет использован и для Tracy. -Протоколирование ошибок .[#toc-error-logging] -============================================= +Логирование ошибок +================== -В производственном режиме Tracy автоматически записывает все ошибки и исключения в текстовый журнал. Для того чтобы ведение журнала происходило, необходимо задать абсолютный путь к директории журнала в переменной `$logDirectory` или передать его в качестве второго параметра методу `enable()`: +В production-режиме Tracy автоматически все ошибки и перехваченные исключения записывает в текстовый лог. Чтобы логирование могло происходить, мы должны установить абсолютный путь к каталогу логов в переменную `$logDirectory` или передать как второй параметр метода `enable()`: ```php Debugger::$logDirectory = __DIR__ . '/log'; ``` -Ведение журнала ошибок чрезвычайно полезно. Представьте, что все пользователи вашего приложения на самом деле являются бета-тестерами, которые бесплатно выполняют первоклассную работу по поиску ошибок, и было бы глупо выбрасывать их ценные отчеты незамеченными в мусорную корзину. +Логирование ошибок при этом чрезвычайно полезно. Представьте себе, что все пользователи вашего приложения на самом деле являются бета-тестерами, которые бесплатно выполняют первоклассную работу по поиску ошибок, и вы бы совершили глупость, если бы выбросили их ценные отчеты без внимания в мусорную корзину. -Если вам нужно записать в журнал собственные сообщения или пойманные исключения, используйте метод `log()`: +Если нам нужно залогировать собственное сообщение или перехваченное вами исключение, мы используем для этого метод `log()`: ```php -Debugger::log('Unexpected error'); // text message +Debugger::log('Произошла неожиданная ошибка'); // текстовое сообщение try { - criticalOperation(); + kritickaOperace(); } catch (Exception $e) { - Debugger::log($e); // log exception - // or - Debugger::log($e, Debugger::ERROR); // also sends an email notification + Debugger::log($e); // логировать можно и исключение + // или + Debugger::log($e, Debugger::ERROR); // также отправит уведомление по e-mail } ``` -If you want Tracy to log PHP errors like `E_NOTICE` or `E_WARNING` with detailed information (HTML report), set `Debugger::$logSeverity`: +Если вы хотите, чтобы Tracy логировала ошибки PHP, такие как `E_NOTICE` или `E_WARNING`, с подробной информацией (HTML-отчет), установите `Debugger::$logSeverity`: ```php Debugger::$logSeverity = E_NOTICE | E_WARNING; ``` -Для настоящего профессионала журнал ошибок является важнейшим источником информации, и он или она хочет получать уведомления о любой новой ошибке немедленно. Трейси помогает ему. Она способна отправлять электронное письмо для каждой новой записи об ошибке. Переменная $email определяет, куда отправлять эти письма: +Для настоящего профессионала лог ошибок является ключевым источником информации, и он хочет быть немедленно проинформирован о каждой новой ошибке. Tracy идет ему в этом навстречу, она ведь умеет информировать по e-mail о новой записи в логе. Куда отправлять e-mail, мы определяем переменной $email: ```php Debugger::$email = 'admin@example.com'; ``` -Если вы используете весь Nette Framework, вы можете установить эту и другие переменные в [конфигурационном файле |nette:configuring]. +Если вы используете весь Nette Framework, это и другое можно настроить в [конфигурационном файле |nette:configuring]. -Чтобы защитить свой почтовый ящик от переполнения, Tracy отправляет **только одно сообщение** и создает файл `email-sent`. Когда разработчик получает уведомление по электронной почте, он проверяет журнал, исправляет свое приложение и удаляет файл мониторинга `email-sent`. Это снова активирует отправку электронной почты. +Однако, чтобы она не завалила ваш почтовый ящик, она всегда отправляет **только одно сообщение** и создает файл `email-sent`. Разработчик после получения уведомления по e-mail проверяет лог, исправляет приложение и удаляет файл мониторинга, тем самым снова активируется отправка e-mail. -Открытие файлов в редакторе .[#toc-opening-files-in-the-editor] -=============================================================== +Открытие в редакторе +==================== -Когда отображается страница ошибки, вы можете щелкнуть по именам файлов, и они откроются в вашем редакторе с курсором на соответствующей строке. Файлы также можно создавать (действие `create file`) или исправлять в них ошибки (действие `fix it`). Для этого необходимо [настроить браузер и систему |open-files-in-ide]. +При отображении страницы ошибки можно кликнуть на имена файлов, и они откроются в вашем редакторе с курсором на соответствующей строке. Также можно создавать файлы (действие `create file`) или исправлять в них ошибки (действие `fix it`). Чтобы это работало, достаточно [настроить браузер и систему |open-files-in-ide]. -Поддерживаемые версии PHP .[#toc-supported-php-versions] -======================================================== +Поддерживаемые версии PHP +========================= -| Tracy | совместимые с PHP -|-----------|-------------------- -| Tracy 2.10 – 3.0 | PHP 8.0 - 8.2 -| Tracy 2.9 | PHP 7.2 - 8.2 -| Tracy 2.8 | PHP 7.2 - 8.1 -| Трейси 2.6 - 2.7 | PHP 7.1 - 8.0 -| Трейси 2.5 | PHP 5.4 - 7.4 -| Трейси 2.4 | PHP 5.4 - 7.2 +| Tracy | Совместим с PHP +|-----------|------------------- +| Tracy 2.10 – 3.0 | PHP 8.0 – 8.4 +| Tracy 2.9 | PHP 7.2 – 8.2 +| Tracy 2.8 | PHP 7.2 – 8.1 +| Tracy 2.6 – 2.7 | PHP 7.1 – 8.0 +| Tracy 2.5 | PHP 5.4 – 7.4 +| Tracy 2.4 | PHP 5.4 – 7.2 -Применимо к последним версиям патчей. +Действительно для последней патч-версии. -Порты .[#toc-ports] -=================== +Порты +===== -Это список неофициальных портов на другие фреймворки и CMS: +Это список неофициальных портов для других фреймворков и CMS: - [Drupal 7](https://www.drupal.org/project/traced) - Laravel framework: [recca0120/laravel-tracy](https://github.com/recca0120/laravel-tracy), [whipsterCZ/laravel-tracy](https://github.com/whipsterCZ/laravel-tracy) diff --git a/tracy/ru/open-files-in-ide.texy b/tracy/ru/open-files-in-ide.texy index deb1328d5a..a0c7cd06fa 100644 --- a/tracy/ru/open-files-in-ide.texy +++ b/tracy/ru/open-files-in-ide.texy @@ -2,19 +2,19 @@ ********************************************************* .[perex] -Когда отображается страница ошибки, вы можете нажать на имена файлов, и они откроются в вашем редакторе с курсором на соответствующей строке. Файлы также можно создавать (действие `create file`) или исправлять в них ошибки (действие `fix it`). Для этого необходимо настроить браузер и систему. +При отображении страницы ошибки можно кликнуть на имена файлов, и они откроются в вашем редакторе с курсором на соответствующей строке. Также можно создавать файлы (действие `create file`) или исправлять в них ошибки (действие `fix it`). Чтобы это произошло, необходимо настроить браузер и систему. -Tracy открывает файлы через URL вида `editor://open/?file=%file&line=%line`, т.е. по протоколу `editor://`. Для этого мы зарегистрируем собственный обработчик. Это может быть любой исполняемый файл, который обработает параметры и запустит наш любимый редактор. +Tracy открывает файлы по URL вида `editor://open/?file=%file&line=%line`, т.е. с протоколом `editor://`. Для него мы зарегистрируем собственный обработчик. Им может быть любой исполняемый файл, который "переварит" параметры и запустит наш любимый редактор. -Вы можете изменить URL в переменной `Tracy\Debugger::$editor` или отключить клик-критерий, установив `Tracy\Debugger::$editor = null`. +URL можно изменить в переменной `Tracy\Debugger::$editor`, или отключить переход по ссылкам, установив `Tracy\Debugger::$editor = null`. -Windows .[#toc-windows] -======================= +Windows +======= -1. Загрузите соответствующие файлы "из хранилища Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/windows на диск. +1. Скачайте соответствующие файлы "из репозитория Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/windows на диск. -2. Отредактируйте `open-editor.js` и откомментируйте или отредактируйте путь к вашему редактору в `settings`: +2. Отредактируйте файл `open-editor.js` и в массиве `settings` раскомментируйте и при необходимости измените путь к вашему редактору: ```js var settings = { @@ -35,19 +35,28 @@ var settings = { ... ``` -Будьте внимательны и сохраняйте двойные косые черты в путях. +Внимание, оставляйте двойные слеши в путях. -3. Зарегистрируйте обработчик для протокола `editor://` в системе. +3. Зарегистрируйте обработчик протокола `editor://` в системе. -Это делается путем запуска скрипта `install.cmd`. **Вы должны запустить его от имени администратора.** Скрипт `open-editor.js` теперь будет обслуживать протокол `editor://`. +Это вы сделаете, запустив файл `install.cmd`. **Его необходимо запустить от имени Администратора.** Скрипт `open-editor.js` теперь будет обрабатывать протокол `editor://`. +Чтобы можно было открывать ссылки, сгенерированные на других серверах, например, на реальном сервере или в Docker, добавьте в `open-editor.js` еще и сопоставление удаленного URL с локальным: -Linux .[#toc-linux] -=================== +```js + mappings: { + // удаленный путь: локальный путь + '/var/www/nette.app': 'W:\\Nette.web\\_web', + } +``` + + +Linux +===== -1. Загрузите соответствующие файлы "из репозитория Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux в каталог `~/bin`. +1. Скачайте соответствующие файлы "из репозитория Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux в каталог `~/bin`. -2. Отредактируйте `open-editor.sh` и откомментируйте или отредактируйте путь к вашему редактору в переменной `editor`: +2. Отредактируйте файл `open-editor.sh` и раскомментируйте и при необходимости измените путь к вашему редактору в переменной `editor`. ```shell #!/bin/bash @@ -67,24 +76,25 @@ Linux .[#toc-linux] ... ``` -Сделайте его исполняемым: +Сделайте файл исполняемым: ```shell chmod +x ~/bin/open-editor.sh ``` -Если используемый вами редактор не установлен из пакета, в бинарном файле, скорее всего, не будет пути в `$PATH`. Это можно легко исправить. В каталоге `~/bin` создайте симлинк на двоичный файл редактора. .[note] +.[note] +Если используемый редактор не установлен из пакета, вероятно, исполняемый файл не будет иметь пути в $PATH. Это можно легко исправить. В каталоге `~/bin` создайте символическую ссылку на исполняемый файл редактора. -3. Зарегистрируйте в системе обработчик для протокола `editor://`. +3. Зарегистрируйте обработчик протокола `editor://` в системе. -Это делается путем запуска скрипта `install.sh`. Теперь скрипт `open-editor.js` будет обслуживать протокол `editor://`. +Это вы сделаете, запустив файл `install.sh`. Скрипт `open-editor.sh` теперь будет обрабатывать протокол `editor://`. -macOS .[#toc-macos] -=================== +macOS +===== -Такие редакторы, как PhpStorm, TextMate и т.д., позволяют открывать файлы через специальный URL, который нужно просто задать: +Редакторы, такие как PhpStorm, TextMate и т.д., позволяют открывать файлы по специальным URL, который достаточно установить: ```php // PhpStorm @@ -92,44 +102,43 @@ Tracy\Debugger::$editor = 'phpstorm://open?file=%file&line=%line'; // TextMate Tracy\Debugger::$editor = 'txmt://open/?url=file://%file&line=%line'; // MacVim -Tracy\Debugger::$editor = 'mvim://open/?url=file://%file&line=%line'; +Tracy\Debugger::$editor = 'mvim://open?url=file:///%file&line=%line'; // Visual Studio Code Tracy\Debugger::$editor = 'vscode://file/%file:%line'; ``` -Если вы используете автономный Tracy, поставьте строку перед `Tracy\Debugger::enable()`, если Nette, то перед `$configurator->enableTracy()` в `Bootstrap.php`. +Если вы используете отдельную Tracy, вставьте строку перед `Tracy\Debugger::enable()`, если Nette, то перед `$configurator->enableTracy()` в `Bootstrap.php`. -К сожалению, действия `create file` или `fix it` не работают на macOS. +Действия `create file` или `fix it`, к сожалению, не работают на macOS. -Демонстрации .[#toc-demos] -========================== +Примеры +======= Исправление ошибки: -Создание нового файла: +Создание файла: -Устранение неполадок .[#toc-troubleshooting] -============================================ +Решение проблем +=============== -- В Firefox вам может потребоваться [разрешить |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] выполнение пользовательского протокола в about:config, установив `network.protocol-handler.expose.editor` на `false` и `network.protocol-handler.expose-all` на `true`. Однако это должно быть разрешено по умолчанию. -- Если не все работает сразу, не паникуйте. Попробуйте обновить страницу, перезагрузить браузер или компьютер. Это должно помочь. -- Смотрите [здесь |https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/], чтобы исправить: - Ошибка ввода: Нет механизма сценариев для расширения файла ".js" Возможно, вы связали файл ".js" с другим приложением, а не с механизмом JScript. +- В Firefox может потребоваться разрешить протокол, [установив |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] `network.protocol-handler.expose.editor` в `false` и `network.protocol-handler.expose-all` в `true` в about:config. +- Если у вас сразу не получится, не паникуйте и попробуйте несколько раз обновить страницу перед кликом по этой ссылке. Заработает! +- Вот [ссылка|https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/] на исправление возможной ошибки: `Input Error: There is no script engine for file extension ".js"`, `Maybe you associated ".js" file to another app, not JScript engine.` соответственно `для расширения .js нет доступного скриптового движка`. -Начиная с версии Google Chrome 77, вы больше не увидите флажок "Всегда открывать эти типы ссылок в связанном приложении", когда редактор открывается по ссылке. Обходной путь для Windows: создайте файл `fix.reg`: +В Google Chrome начиная с версии 77 вы больше не увидите флажок „Всегда открывать ссылки этого типа в связанном приложении“, когда редактор запущен по ссылке. Решение для Windows: создайте файл `fix.reg`: ``` Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\URLWhitelist] "123"="editor://*" ``` -Импортируйте его двойным щелчком и перезапустите Chrome. +Импортируйте его двойным кликом и перезапустите браузер Chrome. -Если у вас возникнут дополнительные проблемы или вопросы, задавайте их на [форуме |https://forum.nette.org]. +С возможными вопросами или замечаниями, пожалуйста, обращайтесь на [форум |https://forum.nette.org]. diff --git a/tracy/ru/recipes.texy b/tracy/ru/recipes.texy index 4131f8fb8a..246bf8ecda 100644 --- a/tracy/ru/recipes.texy +++ b/tracy/ru/recipes.texy @@ -1,12 +1,11 @@ -Рецепты -******* +Руководства +*********** -Политика безопасности контента .[#toc-content-security-policy] -============================================================== +Content Security Policy +======================= -Если на вашем сайте используется политика безопасности содержимого, вам нужно добавить `'nonce-'` и `'strict-dynamic'` к `script-src`, чтобы Tracy работал правильно. Некоторые третьи плагины могут потребовать дополнительные директивы. -Nonce не поддерживается в директиве `style-src`, если вы используете эту директиву, вам нужно добавить `'unsafe-inline'`, но этого следует избегать в производственном режиме. +Если ваш сайт использует Content Security Policy, вам нужно будет добавить те же `'nonce-'` и `'strict-dynamic'` в `script-src`, чтобы Tracy работала правильно. Некоторые сторонние дополнения могут требовать дополнительных настроек. Nonce не поддерживается в директиве `style-src`, если вы используете эту директиву, вы должны добавить `'unsafe-inline'`, но в production-режиме этого следует избегать. Пример конфигурации для [Nette Framework |nette:configuring]: @@ -24,11 +23,10 @@ header("Content-Security-Policy: script-src 'nonce-$nonce' 'strict-dynamic';"); ``` -Ускоренная загрузка .[#toc-faster-loading] -========================================== +Более быстрая загрузка +====================== -Базовая интеграция проста, однако если на веб-странице есть медленные блокирующие скрипты, они могут замедлить загрузку Tracy. -Решение заключается в том, чтобы поместить `` в ваш шаблон перед любыми скриптами: +Запуск прост, однако, если у вас на веб-странице есть медленно загружающиеся блокирующие скрипты, они могут замедлить загрузку Tracy. Решение - поместить `` в ваш шаблон перед всеми скриптами: ```latte @@ -42,12 +40,37 @@ header("Content-Security-Policy: script-src 'nonce-$nonce' 'strict-dynamic';"); ``` -AJAX и перенаправленные запросы .[#toc-ajax-and-redirected-requests] -==================================================================== +Отладка AJAX-запросов +===================== -Tracy может отображать панель отладки и блюскрины для AJAX-запросов и перенаправлений. Tracy создает собственные сессии, хранит данные в собственных временных файлах и использует cookie `tracy-session`. +Tracy автоматически перехватывает AJAX-запросы, созданные с помощью jQuery или нативного API `fetch`. Запросы отображаются в панели Tracy как дополнительные строки, что позволяет легко и удобно отлаживать AJAX. -Tracy также может быть настроен на использование собственной сессии PHP, которая запускается до включения Tracy: +Если вы не хотите автоматически перехватывать AJAX-запросы, вы можете отключить эту функцию, установив переменную JavaScript: + +```js +window.TracyAutoRefresh = false; +``` + +Для ручного мониторинга конкретных AJAX-запросов добавьте HTTP-заголовок `X-Tracy-Ajax` со значением, которое вернет `Tracy.getAjaxHeader()`. Вот пример использования с функцией `fetch`: + +```js +fetch(url, { + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Tracy-Ajax': Tracy.getAjaxHeader(), + } +}) +``` + +Этот подход позволяет выборочно отлаживать AJAX-запросы. + + +Хранилище данных +================ + +Tracy умеет отобразить панели в Tracy Bar и Bluescreen для AJAX-запросов и перенаправлений. Tracy создает собственную сессию, данные хранит в собственных временных файлах и использует cookie `tracy-session`. + +Tracy также можно настроить так, чтобы она использовала нативную PHP-сессию, которую мы запускаем еще до включения Tracy: ```php session_start(); @@ -55,30 +78,30 @@ Debugger::setSessionStorage(new Tracy\NativeSession); Debugger::enable(); ``` -В случае, если запуск сессии требует более сложной инициализации, вы можете запустить Tracy сразу (чтобы он мог обработать любые возникающие ошибки), затем инициализировать обработчик сессии и, наконец, сообщить Tracy, что сессия готова к использованию, используя функцию `dispatch()`: +В случае, если запуск сессии требует более сложной инициализации, вы можете запустить Tracy немедленно (чтобы она могла обработать возможные возникшие ошибки), а затем инициализировать обработчик сессии и, наконец, сообщить Tracy, что сессия готова к использованию, с помощью функции `dispatch()`: ```php Debugger::setSessionStorage(new Tracy\NativeSession); Debugger::enable(); -// затем инициализация сеанса -// и начать сеанс +// следует инициализация сессии +// и запуск сессии session_start(); Debugger::dispatch(); ``` -Функция `setSessionStorage()` существует с версии 2.9, до этого Tracy всегда использовал родную сессию PHP. +Функция `setSessionStorage()` существует с версии 2.9, до этого Tracy всегда использовала нативную PHP-сессию. -Пользовательский скраббер .[#toc-custom-scrubber] -================================================= +Собственный scrubber +==================== -Scrubber - это фильтр, который предотвращает утечку конфиденциальных данных из дампов, таких как пароли или учетные данные. Фильтр вызывается для каждого элемента массива или объекта дампа и возвращает `true`, если значение является чувствительным. В этом случае вместо значения выводится `*****`. +Scrubber - это фильтр, который предотвращает утечку конфиденциальных данных при дампинге, таких как пароли или учетные данные. Фильтр вызывается для каждого элемента дампируемого массива или объекта и возвращает `true`, если значение является конфиденциальным. В таком случае вместо значения выводится `*****`. ```php -// позволяет избежать дампа значений ключей и свойств, таких как `password`, -// `password_repeat`, `check_password`, `DATABASE_PASSWORD` и т.д. +// предотвратит вывод значений ключей и свойств, таких как `password`, +// `password_repeat`, `check_password`, `DATABASE_PASSWORD` и т.п. $scrubber = function(string $key, $value, ?string $class): bool { return preg_match('#password#i', $key) && $value !== null; @@ -89,10 +112,10 @@ Tracy\Debugger::getBlueScreen()->scrubber = $scrubber; ``` -Пользовательский регистратор .[#toc-custom-logger] -================================================== +Собственный логгер +================== -Мы можем создать пользовательский логгер, который будет регистрировать ошибки, невыявленные исключения, а также вызываться `Tracy\Debugger::log()`. Logger реализует интерфейс [api:Tracy\ILogger]. +Мы можем создать собственный логгер, который будет логировать ошибки, неперехваченные исключения, а также будет вызван методом `Tracy\Debugger::log()`. Логгер реализует интерфейс [api:Tracy\ILogger]. ```php use Tracy\ILogger; @@ -101,18 +124,18 @@ class SlackLogger implements ILogger { public function log($value, $priority = ILogger::INFO) { - // отправляет запрос в Slack + // отправит запрос в Slack } } ``` -Затем мы активируем его: +И затем активируем его: ```php Tracy\Debugger::setLogger(new SlackLogger); ``` -Если мы используем полный Nette Framework, мы можем установить его в конфигурационном файле NEON: +Если мы используем полный Nette Framework, вы можете установить его в конфигурационном файле NEON: ```neon services: @@ -120,10 +143,10 @@ services: ``` -Monolog Integration .[#toc-monolog-integration] ------------------------------------------------ +Интеграция Monolog +------------------ -Пакет Tracy предоставляет адаптер PSR-3, позволяющий интегрировать [monolog/monolog](https://github.com/Seldaek/monolog). +Пакет Tracy предоставляет адаптер PSR-3, который позволяет интегрировать [monolog/monolog](https://github.com/Seldaek/monolog). ```php $monolog = new Monolog\Logger('main-channel'); @@ -133,21 +156,21 @@ $tracyLogger = new Tracy\Bridges\Psr\PsrToTracyLoggerAdapter($monolog); Debugger::setLogger($tracyLogger); Debugger::enable(); -Debugger::log('info'); // пишет: [] main-channel.INFO: info [] [] -Debugger::log('warning', Debugger::WARNING); // пишет: [] main-channel.WARNING: warning [] [] +Debugger::log('info'); // writes: [] main-channel.INFO: info [] [] +Debugger::log('warning', Debugger::WARNING); // writes: [] main-channel.WARNING: warning [] [] ``` -nginx .[#toc-nginx] -=================== +nginx +===== -Если Tracy не работает на nginx, скорее всего, он неправильно настроен. Если есть что-то вроде +Если у вас не работает Tracy на сервере nginx, скорее всего, он неправильно настроен. Если в конфигурации есть что-то вроде: ```nginx try_files $uri $uri/ /index.php; ``` -измените его на +измените это на: ```nginx try_files $uri $uri/ /index.php$is_args$args; diff --git a/tracy/ru/stopwatch.texy b/tracy/ru/stopwatch.texy index 558a9daf9e..e03b8fa57a 100644 --- a/tracy/ru/stopwatch.texy +++ b/tracy/ru/stopwatch.texy @@ -1,35 +1,35 @@ -Секундомер -********** +Измерение времени +***************** -Еще один полезный инструмент - секундомер отладчика с точностью до микросекунд: +Еще одним полезным инструментом отладчика являются секундомеры с точностью до микросекунд: ```php Debugger::timer(); -// sweet dreams my cherrie +// princi můj malinký spi, ptáčkové sladce již sní... sleep(2); $elapsed = Debugger::timer(); // $elapsed = 2 ``` -С помощью дополнительного параметра можно выполнять несколько измерений одновременно. +С помощью необязательного параметра можно выполнить несколько измерений. ```php Debugger::timer('page-generating'); -// some code +// какой-то код Debugger::timer('rss-generating'); -// some code +// какой-то код $rssElapsed = Debugger::timer('rss-generating'); $pageElapsed = Debugger::timer('page-generating'); ``` ```php -Debugger::timer(); // runs the timer +Debugger::timer(); // включает секундомер -... // some time-consuming operation +... // времязатратная операция -echo Debugger::timer(); // elapsed time in seconds +echo Debugger::timer(); // выводит прошедшее время в секундах ``` diff --git a/tracy/sl/@home.texy b/tracy/sl/@home.texy index 030fb2b845..85eeacc61e 100644 --- a/tracy/sl/@home.texy +++ b/tracy/sl/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Tracy - obvezno orodje za razhroščevanje za vse razvijalce PHP}} -{{description: Tracy je orodje, zasnovano za lažje razhroščevanje kode PHP. Je uporaben pomočnik vseh programerjev PHP, ki pomaga pri jasni vizualizaciji in beleženju napak, izpisovanju spremenljivk in še veliko več. Opozorilo: Tracy je zasvojljiv!}} +{{maintitle: Tracy – razhroščevalno orodje, s katerim je veselje delati napake}} +{description: Tracy je orodje, namenjeno olajšanju razhroščevanja PHP kode. Je koristen pomočnik za vse programerje PHP, ki jim pomaga pri vizualizaciji in beleženju napak, izpisovanju spremenljivk in še veliko več. Opozorilo: Tracy povzroča odvisnost!}} diff --git a/tracy/sl/@left-menu.texy b/tracy/sl/@left-menu.texy index b593af38ca..369c3606e4 100644 --- a/tracy/sl/@left-menu.texy +++ b/tracy/sl/@left-menu.texy @@ -1,7 +1,7 @@ -- [Začetek |Guide] -- [Odlagalnik |Dumper] -- [Stojni ura |Stopwatch] -- [Konfiguracija |Configuring] -- [Recepti |Recipes] -- [Vključevanje IDE |open-files-in-ide] +- [Začenjamo s Tracy |guide] +- [Izpisovanje |dumper] +- [Merjenje časa |stopwatch] +- [Konfiguracija |configuring] +- [Navodila |recipes] +- [Integracija z IDE |open-files-in-ide] - [Ustvarjanje razširitev |extensions] diff --git a/tracy/sl/@menu.texy b/tracy/sl/@menu.texy index 934ee6a393..3ca552ba86 100644 --- a/tracy/sl/@menu.texy +++ b/tracy/sl/@menu.texy @@ -1,3 +1,3 @@ -- [Domov |@home] -- [Dokumentacija |Guide] +- [Uvod |@home] +- [Dokumentacija |guide] - "GitHub .[link-external]":https://github.com/nette/tracy diff --git a/tracy/sl/@meta.texy b/tracy/sl/@meta.texy new file mode 100644 index 0000000000..c2ee8bbc08 --- /dev/null +++ b/tracy/sl/@meta.texy @@ -0,0 +1 @@ +{{sitename: Tracy Dokumentacija}} diff --git a/tracy/sl/configuring.texy b/tracy/sl/configuring.texy index f64a408d57..094d226ed2 100644 --- a/tracy/sl/configuring.texy +++ b/tracy/sl/configuring.texy @@ -1,142 +1,141 @@ Konfiguracija Tracy ******************* -Naslednji primeri predpostavljajo, da je definiran naslednji vzdevek razreda: +Vsi primeri predpostavljajo ustvarjen alias: ```php use Tracy\Debugger; ``` -beleženje napak .[#toc-error-logging] -------------------------------------- +Dnevniško beleženje napak +------------------------- ```php $logger = Debugger::getLogger(); -// če je prišlo do napake, se obvestilo pošlje na ta e-poštni naslov. -$logger->email = 'dev@example.com'; // (string|string[]) privzeto je nenastavljeno +// e-pošta, na katero se pošiljajo obvestila o napakah +$logger->email = 'dev@example.com'; // (string|string[]) privzeto ni nastavljeno // pošiljatelj e-pošte -$logger->fromEmail = 'me@example.com'; // (niz) privzeto je nenastavljeno +$logger->fromEmail = 'me@example.com'; // (string) privzeto ni nastavljeno -// postopek za pošiljanje e-pošte -$logger->mailer = /* ... */; // (klicljiva) privzeto pošiljanje z mail() +// rutina, ki zagotavlja pošiljanje e-pošte +$logger->mailer = /* ... */; // (callable) privzeto je pošiljanje s funkcijo mail() -// po katerem najkrajšem času se pošlje drugo elektronsko sporočilo? -$logger->emailSnooze = /* ... */; // (niz) privzeto je '2 dni' +// po kakšnem najkrajšem času poslati naslednjo e-pošto? +$logger->emailSnooze = /* ... */; // (string) privzeto je '2 days' -// za katere ravni napak se BlueScreen tudi beleži? -Debugger::$logSeverity = E_WARNING | E_NOTICE; // privzeto 0 (brez stopnje napake) +// za katere ravni napak se beleži tudi BlueScreen? +Debugger::$logSeverity = E_WARNING | E_NOTICE; // privzeto je 0 (nobena raven napak) ``` -`dump()` Vedenje ----------------- +Obnašanje `dump()` +------------------ ```php // največja dolžina niza -Debugger::$maxLength = 150; // (int) privzeto glede na Tracy +Debugger::$maxLength = 150; // (int) privzeto glede na različico Tracy -// kako globok bo seznam -Debugger::$maxDepth = 10; // (int) privzeto po Tracy +// največja globina gnezdenja +Debugger::$maxDepth = 10; // (int) privzeto glede na različico Tracy -// skrivanje vrednosti teh ključev (od različice Tracy 2.8) +// skrij vrednosti teh ključev (od Tracy 2.8) Debugger::$keysToHide = ['password', /* ... */]; // (string[]) privzeto je [] -// vizualna tema (od različice Tracy 2.8) +// vizualna tema (od Tracy 2.8) Debugger::$dumpTheme = 'dark'; // (light|dark) privzeto je 'light' -// prikaže lokacijo, kjer je bila klicana funkcija dump()? -Debugger::$showLocation = /* ... */; // (bool) privzeto glede na Tracy +// prikaži mesto, kjer je bila klicana funkcija `dump()`? +Debugger::$showLocation = /* ... */; // (bool) privzeto glede na različico Tracy ``` -Drugo .[#toc-others] --------------------- +Drugo +----- ```php -// v razvojnem načinu se vam bodo prikazala opozorila o napaki kot BlueScreen -Debugger::$strictMode = /* ... */; // (bool|int) privzeta vrednost je false, izberete lahko samo določene stopnje napak (npr. E_USER_DEPRECATED | E_DEPRECATED) +// v razvojnem načinu prikaže napake tipa notice ali warning kot BlueScreen +Debugger::$strictMode = /* ... */; // (bool|int) privzeto je false, možno je izbrati le nekatere ravni napak (npr. E_USER_DEPRECATED | E_DEPRECATED) -// prikaže tiha (@) sporočila o napakah -Debugger::$scream = /* ... */; // (bool|int) privzeto je false, od različice 2.9 je mogoče izbrati samo določene ravni napak (npr. E_USER_DEPRECATED | E_DEPRECATED) +// prikaži utišana (@) sporočila o napakah? +Debugger::$scream = /* ... */; // (bool|int) privzeto je false, od različice 2.9 je možno izbrati le nekatere ravni napak (npr. E_USER_DEPRECATED | E_DEPRECATED) -// oblika povezave, ki se odpre v urejevalniku -Debugger::$editor = /* ... */; // (niz|null) privzeto je 'editor://open/?file=%file&line=%line' +// format povezave za odpiranje v urejevalniku +Debugger::$editor = /* ... */; // (string|null) privzeto je 'editor://open/?file=%file&line=%line' -// pot do predloge s stranjo po meri za napako 500 -Debugger::$errorTemplate = /* ... */; // (niz) privzeto je nenastavljeno +// pot do predloge z lastno stranjo za napako 500 +Debugger::$errorTemplate = /* ... */; // (string) privzeto ni nastavljeno -// prikaže Tracy Bar? +// prikaži Tracy Bar? Debugger::$showBar = /* ... */; // (bool) privzeto je true Debugger::$editorMapping = [ - // original => new + // original => nova '/var/www/html' => '/data/web', '/home/web' => '/srv/html', ]; ``` -Okvir Nette .[#toc-nette-framework] ------------------------------------ +Nette Framework +--------------- -Če uporabljate ogrodje Nette, lahko Tracy konfigurirate in v vrstico Tracy Bar dodajate nove plošče s pomočjo konfiguracijske datoteke. -V konfiguracijski datoteki lahko nastavite parametre Tracyja in tudi dodate nove plošče v vrstico Tracy. Te nastavitve se uporabijo šele po ustvarjanju vsebnika DI, zato se napake, ki so se pojavile prej, ne morejo odražati. +Če uporabljate Nette Framework, lahko konfigurirate Tracy in dodajate nove panele v Tracy Bar tudi s pomočjo konfiguracijske datoteke. V konfiguraciji lahko nastavljate parametre in dodajate nove panele v Tracy Bar. Te nastavitve se uporabijo šele po ustvarjanju DI vsebnika, zato napake, ki nastanejo pred tem, jih ne morejo odražati. -Konfiguracija beleženja napak: +Konfiguracija dnevniškega beleženja napak: ```neon tracy: - # če je prišlo do napake, se obvestilo pošlje na ta e-poštni naslov. - email: dev@example.com # (string|string[]) privzeto je nenastavljeno + # e-pošta, na katero se pošiljajo obvestila o napakah + email: dev@example.com # (string|string[]) privzeto ni nastavljeno # pošiljatelj e-pošte - fromEmail: robot@example.com # (niz) privzeto je nenastavljeno + fromEmail: robot@example.com # (string) privzeto ni nastavljeno - # obdobje odloga pošiljanja e-pošte (od Tracy 2.8.8) - emailSnooze: ... # (niz) privzeto je '2 dni' + # čas zakasnitve pošiljanja e-pošte (od Tracy 2.8.8) + emailSnooze: ... # (string) privzeto je '2 days' - # za uporabo poštnega pošiljatelja, določenega v konfiguraciji? (od Tracy 2.5) + # uporabiti Nette mailer za pošiljanje e-pošte? (od Tracy 2.5) netteMailer: ... # (bool) privzeto je true - # za katere ravni napak se BlueScreen tudi beleži? + # za katere ravni napak se beleži tudi BlueScreen? logSeverity: [E_WARNING, E_NOTICE] # privzeto je [] ``` -Konfiguracija za funkcijo `dump()`: +Konfiguracija obnašanja funkcije `dump()`: ```neon tracy: # največja dolžina niza - maxLength: 150 # (int) privzeto glede na Tracy + maxLength: 150 # (int) privzeto glede na različico Tracy - # kako globok bo seznam - maxDepth: 10 # (int) privzeto po Tracy + # največja globina gnezdenja + maxDepth: 10 # (int) privzeto glede na različico Tracy - # skrivanje vrednosti teh ključev (od različice Tracy 2.8) + # skrij vrednosti teh ključev (od Tracy 2.8) keysToHide: [password, pass] # (string[]) privzeto je [] - # vizualna tema (od različice Tracy 2.8) + # vizualna tema (od Tracy 2.8) dumpTheme: dark # (light|dark) privzeto je 'light' - # prikaže lokacijo, kjer je bila klicana funkcija dump()? - showLocation: ... # (bool) privzeto glede na Tracy + # prikaži mesto, kjer je bila klicana funkcija dump()? + showLocation: ... # (bool) privzeto glede na različico Tracy ``` -Namestitev razširitve Tracy: +Namestitev razširitev Tracy: ```neon tracy: - # doda palice Tracy Bar + # doda panele v Tracy Bar bar: - Nette\Bridges\DITracy\ContainerPanel - IncludePanel - XDebugHelper('myIdeKey') - MyPanel(@MyService) - # doda plošče v BlueScreen + # doda panele v BlueScreen blueScreen: - DoctrinePanel::renderException ``` @@ -145,25 +144,37 @@ Druge možnosti: ```neon tracy: - # v razvojnem načinu se vam bodo prikazala opozorila o napaki kot BlueScreen - strictMode: ... # privzeta vrednost true + # v razvojnem načinu prikaže napake tipa notice ali warning kot BlueScreen + strictMode: ... # privzeto je true - # prikaže tiha (@) sporočila o napakah - scream: ... # privzeta vrednost je false + # prikaži utišana (@) sporočila o napakah? + scream: ... # privzeto je false - # oblika povezave, ki se odpre v urejevalniku - editor: ... # (niz) privzeto je 'editor://open/?file=%file&line=%line' + # format povezave za odpiranje v urejevalniku + editor: ... # (string) privzeto je 'editor://open/?file=%file&line=%line' - # pot do predloge s stranjo po meri za napako 500 - errorTemplate: ... # (niz) privzeto je nenastavljeno + # pot do predloge z lastno stranjo za napako 500 + errorTemplate: ... # (string) privzeto ni nastavljeno - # prikaže Tracy Bar? + # prikaži Tracy Bar? showBar: ... # (bool) privzeto je true editorMapping: - # original: new + # original: nova /var/www/html: /data/web /home/web: /srv/html ``` -Vrednosti možnosti `logSeverity`, `strictMode` in `scream` se lahko zapišejo kot polje ravni napak (npr. `[E_WARNING, E_NOTICE]`) ali kot izraz, ki se uporablja v PHP (npr. `E_ALL & ~E_NOTICE`). +Vrednosti možnosti `logSeverity`, `strictMode` in `scream` lahko zapišete kot polje ravni napak (npr. `[E_WARNING, E_NOTICE]`) ali kot izraz, uporabljen v jeziku PHP (npr. `E_ALL & ~E_NOTICE`). + + +Storitve DI +----------- + +Te storitve se dodajo v DI vsebnik: + +| Ime | Tip | Opis +|-----------------|----------------------------|----------- +| `tracy.logger` | [api:Tracy\ILogger] | logger +| `tracy.blueScreen` | [api:Tracy\BlueScreen] | BlueScreen +| `tracy.bar` | [api:Tracy\Bar] | Tracy Bar diff --git a/tracy/sl/dumper.texy b/tracy/sl/dumper.texy index ebd8df627b..2db72dc7b6 100644 --- a/tracy/sl/dumper.texy +++ b/tracy/sl/dumper.texy @@ -1,7 +1,7 @@ -Samohodnik -********** +Izpisovanje +*********** -Vsak razvijalec razhroščevanja je dober prijatelj s funkcijo `var_dump`, ki podrobno izpiše vso vsebino poljubne spremenljivke. Na žalost je njen izpis brez oblikovanja HTML in izpiše izpis v eno samo vrstico kode HTML, da ne omenjamo eskapiranja konteksta. Funkcijo `var_dump` je treba nadomestiti z bolj priročno funkcijo. Prav to je `dump()`. +Vsak razhroščevalec je dober prijatelj s funkcijo [php:var_dump], ki podrobno izpiše vsebino spremenljivke. Na žalost v okolju HTML izpis izgubi oblikovanje in se zlije v eno vrstico, da o čiščenju kode HTML niti ne govorimo. V praksi je nujno `var_dump` nadomestiti s priročnejšo funkcijo. To je prav `dump()`. ```php $arr = [10, 20.2, true, null, 'hello']; @@ -10,7 +10,7 @@ dump($arr); // ali Debugger::dump($arr); ``` -generira izhodni rezultat: +generira izpis: [* dump-basic.webp *] @@ -22,27 +22,27 @@ Debugger::$dumpTheme = 'dark'; [* dump-dark.webp *] -Prav tako lahko spremenite globino gnezdenja s `Debugger::$maxDepth` in dolžino prikazanih nizov s `Debugger::$maxLength`. Seveda nižje vrednosti pospešijo upodabljanje programa Tracy. +Nadalje lahko spremenite globino gnezdenja z [Debugger::$maxDepth |api:Tracy\Debugger::$maxDepth] in dolžino prikazanih nizov z [Debugger::$maxLength |api:Tracy\Debugger::$maxLength]. Nižje vrednosti bodo Tracy seveda pospešile. ```php Debugger::$maxDepth = 2; // privzeto: 3 Debugger::$maxLength = 50; // privzeto: 150 ``` -Funkcija `dump()` lahko prikaže še druge koristne informacije. `Tracy\Dumper::LOCATION_SOURCE` doda namig s potjo do datoteke, v kateri je bila funkcija klicana. `Tracy\Dumper::LOCATION_LINK` doda povezavo do datoteke. `Tracy\Dumper::LOCATION_CLASS` vsakemu izpisanemu objektu doda namig s potjo do datoteke, v kateri je definiran razred objekta. Vse te konstante lahko nastavite v spremenljivki `Debugger::$showLocation`, preden pokličete funkcijo `dump()`. Z operatorjem `|` lahko nastavite več vrednosti hkrati. +Funkcija `dump()` zna izpisati tudi druge koristne informacije. Konstanta `Tracy\Dumper::LOCATION_SOURCE` doda namig s potjo do mesta, kjer je bila funkcija klicana. `Tracy\Dumper::LOCATION_LINK` nam ponudi povezavo do tega mesta. `Tracy\Dumper::LOCATION_CLASS` pri vsakem izpisanem objektu izpiše namig s potjo do datoteke, v kateri je definiran njegov razred. Konstante se nastavijo v spremenljivko `Debugger::$showLocation` še pred klicem `dump()`. Če želimo nastaviti več vrednosti hkrati, jih združimo z operatorjem `|`. ```php -Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // Pokaže pot do mesta, kjer je bil klican ukaz dump() -Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Prikaže poti do razredov in povezavo do mesta, kjer je bil klican ukaz dump(). -Debugger::$showLocation = false; // Skrije dodatne informacije o lokaciji -Debugger::$showLocation = true; // Prikaže vse dodatne informacije o lokaciji +Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // Nastavi samo izpis o mestu klica funkcije +Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Hkrati nastavi izpis povezave in pot do razreda +Debugger::$showLocation = false; // Izklopi izpis dodatnih informacij +Debugger::$showLocation = true; // Vklopi izpis vseh dodatnih informacij ``` -Zelo priročna alternativa za `dump()` sta `dumpe()` (tj. dump and exit) in `bdump()`. To nam omogoča, da v programu Tracy Bar izpraznimo spremenljivke. To je koristno, ker se pri izpisu ne zmoti izpis, poleg tega pa lahko izpisu dodamo tudi naslov. +Praktična alternativa `dump()` sta `dumpe()` (dump & exit) in `bdump()`. Ta nam omogoča izpis vrednosti spremenljivke v panelu Tracy Bar. To je zelo priročno, saj so izpisi ločeni od postavitve strani in jim lahko dodate tudi komentar. ```php -bdump([2, 4, 6, 8], 'even numbers up to ten'); -bdump([1, 3, 5, 7, 9], 'odd numbers up to ten'); +bdump([2, 4, 6, 8], 'soda števila do deset'); +bdump([1, 3, 5, 7, 9], 'liha števila do deset'); ``` -[* bardump-en.webp *] +[* bardump-cs.webp *] diff --git a/tracy/sl/extensions.texy b/tracy/sl/extensions.texy index a2502bd270..3a40671a41 100644 --- a/tracy/sl/extensions.texy +++ b/tracy/sl/extensions.texy @@ -1,23 +1,23 @@ -Ustvarjanje razširitev Tracy -**************************** +Ustvarjanje razširitev za Tracy +*******************************
                                                                                                                            -Tracy je odlično orodje za odpravljanje napak v aplikaciji. Vendar včasih potrebujete več informacij, kot jih ponuja Tracy. Spoznali boste: +Tracy ponuja odlično orodje za razhroščevanje vaše aplikacije. Včasih pa bi radi imeli pri roki tudi nekatere druge informacije. Pokazali bomo, kako napisati lastno razširitev za Tracy Bar, da bo razvoj še prijetnejši. -- ustvarjanju lastnih plošč Tracy Bar -- ustvarjanju lastnih razširitev Bluescreen +- Ustvarjanje lastnega panela za Tracy Bar +- Ustvarjanje lastne razširitve za Bluescreen
                                                                                                                            .[tip] -Uporabne razširitve za Tracy lahko najdete na "Componette":https://componette.org/search/tracy. +Repozitorij končnih razširitev za Tracy najdete na "Componette":https://componette.org/search/tracy. -Tracy Bar Extensions .[#toc-tracy-bar-extensions] -================================================= +Razširitve za Tracy Bar +======================= -Ustvarjanje nove razširitve za Tracy Bar je preprosto. Implementirati morate vmesnik `Tracy\IBarPanel` z metodama `getTab()` in `getPanel()`. Metodi morata vrniti kodo HTML zavihka (majhna nalepka v Tracy Baru) in plošče (pojavno okno, ki se prikaže po kliku na zavihek). Če `getPanel()` ne vrne ničesar, bo prikazan samo zavihek. Če `getTab()` ne vrne ničesar, se ne prikaže nič in `getPanel()` se ne pokliče. +Ustvariti novo razširitev za Tracy Bar ni nič zapletenega. Ustvarite objekt, ki implementira vmesnik `Tracy\IBarPanel`, ki ima dve metodi `getTab()` in `getPanel()`. Metodi morata vrniti kodo HTML zavihka (majhen opis, prikazan neposredno na Baru) in panela. Če `getPanel()` ne vrne ničesar, se prikaže samo zavihek. Če `getTab()` ne vrne ničesar, se ne prikaže nič in `getPanel()` se sploh ne kliče. ```php class ExamplePanel implements Tracy\IBarPanel @@ -35,16 +35,16 @@ class ExamplePanel implements Tracy\IBarPanel ``` -Registracija .[#toc-registration] ---------------------------------- +Registracija +------------ -Registracija se opravi s klicem na `Tracy\Bar::addPanel()`: +Registracija se izvede s pomočjo `Tracy\Bar::addPanel()`: ```php Tracy\Debugger::getBar()->addPanel(new ExamplePanel); ``` -ali pa lahko ploščo preprosto registrirate v konfiguraciji aplikacije: +Ali pa lahko panel registrirate neposredno v konfiguraciji aplikacije: ```neon tracy: @@ -53,70 +53,70 @@ tracy: ``` -Koda HTML zavihka .[#toc-tab-html-code] ---------------------------------------- +Koda HTML zavihka +----------------- -Izgledati mora nekako takole: +Izgledati mora približno takole: ```latte - + ... - Title + Naslov ``` -Slika mora biti v formatu SVG. Če ne potrebujete namigov, lahko pustite `` izpustite. +Slika mora biti v formatu SVG. Če pojasnjevalni opis ni potreben, lahko `` izpustite. -Koda HTML plošče .[#toc-panel-html-code] ----------------------------------------- +Koda HTML panela +---------------- -Izgledati mora nekako takole: +Izgledati mora približno takole: ```latte -

                                                                                                                            Title

                                                                                                                            +

                                                                                                                            Naslov

                                                                                                                            - ... content ... + ... vsebina ...
                                                                                                                            ``` -Naslov mora biti enak kot v zavihku ali pa mora vsebovati dodatne informacije. +Naslov mora biti bodisi enak naslovu zavihka ali pa lahko vsebuje dodatne podatke. -Ena razširitev je lahko registrirana večkrat, zato je priporočljivo, da za oblikovanje ne uporabljate atributa `id`. Uporabite lahko razrede, po možnosti v `tracy-addons-[-]` obliki. Pri ustvarjanju CSS je bolje uporabiti `#tracy-debug .class`, saj ima takšno pravilo večjo prednost kot reset. +Upoštevati je treba, da se ena razširitev lahko registrira večkrat, na primer z drugačnimi nastavitvami, zato za stiliziranje ni mogoče uporabljati ID-jev CSS, ampak samo `class`, in sicer v obliki `tracy-addons-[-]`. Razred nato zapišite v `div` skupaj z razredom `tracy-inner`. Pri pisanju CSS je koristno pisati `#tracy-debug .razred`, ker ima pravilo potem višjo prioriteto kot ponastavitev (reset). -Privzeti slogi .[#toc-default-styles] -------------------------------------- +Privzeti stili +-------------- -Na plošči so elementi ``, `
                                                                                                                            `, `
                                                                                                                            `, `` imajo privzete sloge. Če želite ustvariti povezavo za skrivanje ali prikaz drugega elementa, jih povežite z atributoma `href` in `id` ter razredom `tracy-toggle`.
                                                                                                                            +V panelu so predhodno stilizirani ``, `
                                                                                                                            `, `
                                                                                                                            `, ``. Če želite ustvariti povezavo, ki skriva in prikazuje drug element, jih povežite z atributi `href` in `id` ter razredom `tracy-toggle`:
                                                                                                                             
                                                                                                                             ```latte
                                                                                                                            -Detail
                                                                                                                            +Podrobnosti
                                                                                                                             
                                                                                                                            -
                                                                                                                            ...
                                                                                                                            +
                                                                                                                            ...
                                                                                                                            ``` -Če je privzeto stanje zloženo, obema elementoma dodajte razred `tracy-collapsed`. +Če mora biti privzeto stanje skrčeno, obema elementoma dodajte razred `tracy-collapsed`. -Uporabite statični števec, da preprečite podvajanje ID-jev na eni strani. +Uporabite statični števec, da se na eni strani ne ustvarjajo podvojeni ID-ji. -Razširitve Bluescreen .[#toc-bluescreen-extensions] -=================================================== +Razširitve za Bluescreen +======================== -Dodate lahko lastne vizualizacije izjem ali plošče, ki bodo prikazane na zaslonu Bluescreen. +Na ta način lahko dodajate lastne vizualizacije izjem ali panele, ki se prikažejo na modrem zaslonu (Bluescreen). -Razširitev je narejena na naslednji način: +Razširitev se ustvari s tem ukazom: ```php Tracy\Debugger::getBlueScreen()->addPanel(function (?Throwable $e) { // ujeta izjema return [ - 'tab' => '...Title...', - 'panel' => '...content...', + 'tab' => '...Oznaka...', + 'panel' => '...HTML koda panela...', ]; }); ``` -Funkcija se pokliče dvakrat, najprej se v parametru `$e` posreduje sama izjema, vrnjena plošča pa se prikaže na začetku strani. Če se ne vrne nič, se plošča ne prikaže. Nato se pokliče s parametrom `null` in vrnjena plošča se izriše pod skladovnico klicev. Če funkcija vrne `'bottom' => true` v polju, se plošča prikaže na samem dnu. +Funkcija se kliče dvakrat, najprej se v parametru `$e` preda sama izjema in vrnjeni panel se izriše na začetku strani. Če ne vrne ničesar, se panel ne izriše. Nato se kliče s parametrom `null` in vrnjeni panel se izriše pod skladom klicev (call stack). Če funkcija v polju vrne ključ `'bottom' => true`, se panel izriše čisto na dnu. diff --git a/tracy/sl/guide.texy b/tracy/sl/guide.texy index 3a411e57c7..517beb28ab 100644 --- a/tracy/sl/guide.texy +++ b/tracy/sl/guide.texy @@ -1,213 +1,212 @@ -Začetek dela s Tracyjem -*********************** +Začenjamo s Tracy +*****************
                                                                                                                            -Knjižnica Tracy je uporaben pomočnik za vsakdanje programerje PHP. Pomaga vam pri: +Knjižnica Tracy je koristen vsakodnevni pomočnik programerja PHP. Pomagala vam bo: -- hitro odkrijete in popravite napake -- beleženje napak -- izpisati spremenljivke -- merjenje časa izvajanja skript/zahtev -- pregledati porabo pomnilnika +- hitro odkriti in odpraviti napake +- dnevniško beležiti napake +- izpisovati spremenljivke +- meriti čas izvajanja skript in poizvedb v podatkovni bazi +- spremljati porabo pomnilnika
                                                                                                                            -PHP je odličen jezik za izdelavo težko zaznavnih napak, saj programerjem omogoča veliko prilagodljivost. Tracy\Debugger je zaradi tega še bolj dragocen. Je vrhunsko orodje med diagnostičnimi orodji. +PHP je jezik, kot nalašč za ustvarjanje težko odkritih napak, saj daje razvijalcem precejšnjo svobodo. Zato je orodje za razhroščevanje Tracy še toliko bolj dragoceno. Med diagnostičnimi orodji za PHP predstavlja absolutni vrh. -Če se s Tracy srečate prvič, verjemite, da se vaše življenje začne deliti na tisto pred Tracy in tisto z njo. Dobrodošli v dobrem delu! +Če se danes prvič srečujete s Tracy, verjemite, da se bo vaše življenje začelo deliti na tisto pred Tracy in tisto z njo. Dobrodošli v boljšem delu! -Namestitev in zahteve .[#toc-installation-and-requirements] -=========================================================== +Namestitev +========== -Tracy najbolje namestite tako, da [prenesete najnovejši paket](https://github.com/nette/tracy/releases) ali uporabite Composer: +Najboljši način za namestitev Tracy je [prenesti najnovejši paket |https://github.com/nette/tracy/releases] ali uporabiti Composer: ```shell composer require tracy/tracy ``` -lahko prenesete tudi celoten paket ali datoteko [tracy.phar |https://github.com/nette/tracy/releases]. +Lahko si prenesete tudi celoten paket kot datoteko [tracy.phar |https://github.com/nette/tracy/releases]. -Uporaba .[#toc-usage] -===================== +Uporaba +======= -Tracy se aktivira s klicem metode `Tracy\Debugger::enable()' čim prej na začetku programa, preden se pošlje kakršen koli izhod: +Tracy aktivirate s klicem metode `Tracy\Debugger::enable()` čim prej na začetku programa, pred pošiljanjem kakršnega koli izpisa: ```php use Tracy\Debugger; -require 'vendor/autoload.php'; // alternativno tracy.phar +require 'vendor/autoload.php'; // ali `tracy.phar` Debugger::enable(); ``` -Prva stvar, ki jo boste opazili na strani, je vrstica Tracy v spodnjem desnem kotu. Če je ne vidite, to lahko pomeni, da Tracy deluje v produkcijskem načinu. -Tracy je namreč iz varnostnih razlogov viden samo na lokalnem gostitelju. Če želite preizkusiti, ali deluje, ga lahko začasno preklopite v razvojni način z uporabo parametra `Debugger::enable(Debugger::Development)`. +Prva stvar, ki jo boste opazili na strani, je Tracy Bar v spodnjem desnem kotu. Če ga ne vidite, lahko pomeni, da Tracy teče v produkcijskem načinu. Tracy je namreč iz varnostnih razlogov vidna samo na localhostu. Za testiranje, ali deluje, jo lahko začasno preklopite v razvojni način s parametrom `Debugger::enable(Debugger::Development)`. -Tracy Bar .[#toc-tracy-bar] -=========================== +Tracy Bar +========= -Tracy Bar je plavajoča plošča. Prikazana je v spodnjem desnem kotu strani. Premikate jo lahko z miško. Po ponovnem nalaganju strani si bo zapomnila svoj položaj. +Tracy Bar je lebdeči panel, ki se prikaže v spodnjem desnem kotu strani. Lahko ga premikate z miško in po ponovnem nalaganju strani si bo zapomnil svoj položaj. [* tracy-bar.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html -V vrstico Tracy lahko dodate druge uporabne plošče. Zanimive lahko najdete v [dodatkih |https://componette.org] ali pa [ustvarite svoje |extensions]. +V Tracy Bar lahko dodate druge koristne panele. Veliko jih najdete v [dodatkih |https://componette.org] ali pa si lahko celo [napišete lastne |extensions]. -Če ne želite prikazati Tracy Bar, nastavite: +Če ne želite prikazovati Tracy Bar, nastavite: ```php Debugger::$showBar = false; ``` -Vizualizacija napak in izjem .[#toc-visualization-of-errors-and-exceptions] -=========================================================================== +Vizualizacija napak in izjem +============================ -Zagotovo veste, kako PHP sporoča napake: v izvorni kodi strani je nekaj takega: +Zagotovo dobro veste, kako PHP sporoča napake: v izvorno kodo strani izpiše nekaj takega: /--pre .{font-size: 90%} Parse error: syntax error, unexpected '}' in HomePresenter.php on line 15 \-- -ali nezajeta izjema: +ali pri neujeti izjemi: /--pre .{font-size: 90%} Fatal error: Uncaught Nette\MemberAccessException: Call to undefined method Nette\Application\UI\Form::addTest()? in /sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php:100 Stack trace: #0 /sandbox/vendor/nette/utils/src/Utils/Object.php(75): Nette\Utils\ObjectMixin::call(Object(Nette\Application\UI\Form), 'addTest', Array) -#1 /sandbox/app/forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) -#2 /sandbox/app/presenters/SignPresenter.php(21): App\Forms\SignFormFactory->create() -#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presenters\SignPresenter->createComponentSignInForm('signInForm') +#1 /sandbox/app/Forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) +#2 /sandbox/app/Presentation/Sign/SignPresenter.php(21): App\Forms\SignFormFactory->create() +#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presentation\Sign\SignPresenter->createComponentSignInForm('signInForm') #4 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(139): Nette\ComponentModel\Container->createComponent('signInForm') #5 /sandbox/temp/cache/latte/15206b353f351f6bfca2c36cc.php(17): Nette\ComponentModel\Co in /sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php on line 100
                                                                                                                            \-- -Prehajanje po tem izpisu ni tako enostavno. Če omogočite funkcijo Tracy, so napake in izjeme prikazane v povsem drugačni obliki: +V takem izpisu se ni ravno lahko znajti. Če vklopite Tracy, se napaka ali izjema prikaže v povsem drugačni obliki: [* tracy-exception.webp .{url:-} *] -Sporočilo o napaki dobesedno kriči. Vidite lahko del izvorne kode s poudarjeno vrstico, v kateri se je pojavila napaka. Sporočilo jasno pojasnjuje napako. Celotno spletno mesto je [interaktivno, poskusite ga](https://nette.github.io/tracy/tracy-exception.html). +Sporočilo o napaki dobesedno kriči. Vidimo del izvorne kode z označeno vrstico, kjer je prišlo do napake, in informacija *Call to undefined method Nette\\Http\\User::isLogedIn()* razumljivo pojasnjuje, za kakšno napako gre. Celotna stran je poleg tega interaktivna, lahko se preklikate do večjih podrobnosti. [Poskusite |https://nette.github.io/tracy/tracy-exception.html]. -In veste kaj? Usodne napake so zajete in prikazane na enak način. Ni treba namestiti nobene razširitve (kliknite za primer v živo): +In veste kaj? Na ta način ujame in prikaže tudi fatalne napake. Brez potrebe po namestitvi kakršnih koli razširitev. [* tracy-error.webp .{url:-} *] -Napake, kot sta tiskarska napaka v imenu spremenljivke ali poskus odpiranja neobstoječe datoteke, ustvarijo poročila ravni E_NOTICE ali E_WARNING. Te napake je mogoče zlahka spregledati in/ali so lahko popolnoma skrite v grafični postavitvi spletne strani. Naj jih upravlja podjetje Tracy: +Napake, kot so tipkarske napake v imenu spremenljivke ali poskus odpiranja neobstoječe datoteke, generirajo sporočila ravni `E_NOTICE` ali `E_WARNING`. Te je v grafiki strani lahko spregledati, morda celo sploh niso vidne (razen s pogledom v kodo strani). [* tracy-notice2.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html -Lahko pa se prikažejo kot napake: +Ali pa so lahko prikazane enako kot napake: ```php -Debugger::$strictMode = true; // prikaz vseh napak -Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // vse napake, razen zastarelih obvestil +Debugger::$strictMode = true; // prikaži vse napake +Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // vse napake razen obvestil o zastarelosti ``` [* tracy-notice.webp .{url:-} *] -Opomba: Ko je funkcija Tracy aktivirana, se raven poročanja o napakah spremeni na E_ALL. Če želite to spremeniti, to storite po klicu `enable()`. +Opomba: Tracy po aktivaciji spremeni raven poročanja napak na `E_ALL`. Če želite to vrednost spremeniti, to storite po klicu `enable()`. -Razvojni in produkcijski način .[#toc-development-vs-production-mode] -===================================================================== +Razvojni vs. produkcijski način +=============================== -Kot lahko vidite, je Tracy precej zgovoren, kar je mogoče ceniti v razvojnem okolju, medtem ko bi na produkcijskem strežniku to povzročilo katastrofo. To pa zato, ker se tam ne bi smele prikazati nobene informacije za odpravljanje napak. Tracy ima zato **avtomatsko zaznavanje okolja** in če se primer izvaja na strežniku v živo, se napaka namesto prikaza zabeleži, obiskovalec pa vidi le uporabniku prijazno sporočilo: +Kot vidite, je Tracy precej zgovorna, kar je cenjeno v razvojnem okolju, medtem ko bi na produkcijskem strežniku povzročilo pravo katastrofo. Tam se namreč ne smejo izpisati nobene informacije za razhroščevanje. Tracy zato razpolaga s **samodejnim zaznavanjem okolja** in če primer zaženete na produkcijskem strežniku, se napaka namesto prikaza zabeleži v dnevnik, obiskovalec pa vidi le uporabniku prijazno sporočilo: [* tracy-error2.webp .{url:-} *] -Proizvodni način onemogoči prikaz vseh informacij za odpravljanje napak, poslanih s funkcijo [dump() |dumper], in seveda tudi vseh sporočil o napakah, ki jih ustvari PHP. Če ste v kodi pozabili na kakšen `dump($obj)`, vam ni treba skrbeti, saj se v produkcijskem strežniku ne bo nič prikazalo. +Produkcijski način zatre prikaz vseh informacij za razhroščevanje, ki jih pošiljate ven s pomočjo [dump() |dumper], in seveda tudi vseh sporočil o napakah, ki jih generira PHP. Če ste torej v kodi pozabili kakšen `dump($obj)`, vam ni treba skrbeti, na produkcijskem strežniku se ne bo nič izpisalo. -Kako deluje samodejno zaznavanje načina? Način je razvojni, če aplikacija teče na lokalnem gostitelju (tj. naslovu IP `127.0.0.1` ali `::1`) in ni posrednika (tj. njegove glave HTTP). V nasprotnem primeru deluje v produkcijskem načinu. +Kako deluje samodejno zaznavanje načina? Način je razvojni, če se aplikacija izvaja na localhostu (tj. IP naslov `127.0.0.1` ali `::1`) in ni prisoten proxy (tj. njegova glava HTTP). Sicer teče v produkcijskem načinu. -Če želite omogočiti razvojni način v drugih primerih, na primer za razvijalce, ki dostopajo z določenega naslova IP, ga lahko določite kot parameter metode `enable()`: +Če želite razvojni način omogočiti tudi v drugih primerih, na primer programerjem, ki dostopajo z določenega IP naslova, ga navedite kot parameter metode `enable()`: ```php -Debugger::enable('23.75.345.200'); // zagotovite lahko tudi niz naslovov IP. +Debugger::enable('23.75.345.200'); // lahko navedete tudi polje IP naslovov ``` -Vsekakor priporočamo kombinacijo naslova IP s piškotkom. V piškotek `tracy-debug` shranite tajni žeton, na primer `secret1234`, in na ta način aktivirajte razvojni način samo za razvijalce, ki dostopajo z določenega naslova IP in imajo omenjeni žeton v piškotku: +Vsekakor priporočamo kombiniranje IP naslova s piškotkom. V piškotek `tracy-debug` shranite skrivni žeton, npr. `secret1234`, in na ta način aktivirate razvojni način samo za programerje, ki dostopajo z določenega IP naslova in imajo v piškotku omenjeni žeton: ```php Debugger::enable('secret1234@23.75.345.200'); ``` -Razvojni/produkcijski način lahko nastavite tudi neposredno z uporabo konstant `Debugger::Development` ali `Debugger::Production` kot parametra metode `enable()`. +Razvojni/produkcijski način lahko neposredno nastavite tudi z uporabo konstant `Debugger::Development` ali `Debugger::Production` kot parametra metode `enable()`. .[note] -Če uporabljate ogrodje Nette Framework, si oglejte, kako [zanj nastaviti način |application:bootstrap#Development vs Production Mode], ki se bo nato uporabljal tudi za Tracy. +Če uporabljate Nette Framework, poglejte, kako [nastaviti način zanj |application:bootstrapping#Razvojni vs produkcijski način], ki se bo nato uporabil tudi za Tracy. -Beleženje napak .[#toc-error-logging] -===================================== +Dnevniško beleženje napak +========================= -V produkcijskem načinu Tracy samodejno beleži vse napake in izjeme v besedilni dnevnik. Da bi se beleženje izvajalo, morate v spremenljivko `$logDirectory` nastaviti absolutno pot do imenika dnevnika ali jo posredovati kot drugi parameter metodi `enable()`: +V produkcijskem načinu Tracy samodejno vse napake in ujete izjeme beleži v besedilni dnevnik. Da lahko dnevniško beleženje poteka, morate nastaviti absolutno pot do imenika za dnevnike v spremenljivko `$logDirectory` ali jo predati kot drugi parameter metode `enable()`: ```php Debugger::$logDirectory = __DIR__ . '/log'; ``` -Beleženje napak je zelo uporabno. Predstavljajte si, da so vsi uporabniki vaše aplikacije pravzaprav beta testerji, ki brezplačno opravljajo vrhunsko delo pri iskanju napak, in neumno bi bilo, če bi njihova dragocena poročila neopaženo vrgli v koš za smeti. +Dnevniško beleženje napak je pri tem izjemno koristno. Predstavljajte si, da so vsi uporabniki vaše aplikacije pravzaprav beta testerji, ki brezplačno opravljajo vrhunsko delo pri iskanju napak, in bi naredili neumnost, če bi njihova dragocena poročila brez pozornosti vrgli v koš za smeti. -Če morate zabeležiti svoja sporočila ali ujete izjeme, uporabite metodo `log()`: +Če morate zabeležiti lastno sporočilo ali izjemo, ki ste jo ujeli, za to uporabite metodo `log()`: ```php -Debugger::log('Unexpected error'); // besedilno sporočilo +Debugger::log('Prišlo je do nepričakovane napake'); // besedilno sporočilo try { - criticalOperation(); + kritickaOperace(); } catch (Exception $e) { - Debugger::log($e); // izjema v dnevniku + Debugger::log($e); // beležiti je mogoče tudi izjemo // ali Debugger::log($e, Debugger::ERROR); // pošlje tudi e-poštno obvestilo } ``` -If you want Tracy to log PHP errors like `E_NOTICE` or `E_WARNING` with detailed information (HTML report), set `Debugger::$logSeverity`: +Če želite, da Tracy beleži napake PHP, kot sta `E_NOTICE` ali `E_WARNING`, s podrobnimi informacijami (poročilo HTML), nastavite `Debugger::$logSeverity`: ```php Debugger::$logSeverity = E_NOTICE | E_WARNING; ``` -Za pravega strokovnjaka je dnevnik napak ključni vir informacij in želi biti takoj obveščen o vsaki novi napaki. Tracy mu pri tem pomaga. Sposobna je poslati e-poštno sporočilo za vsak nov zapis o napaki. Spremenljivka $email določa, kam naj se ta e-poštna sporočila pošljejo: +Za pravega profesionalca je dnevnik napak ključni vir informacij in želi biti takoj obveščen o vsaki novi napaki. Tracy mu pri tem pomaga, saj zna o novem zapisu v dnevniku obvestiti po e-pošti. Kam pošiljati e-pošto, določite s spremenljivko `$email`: ```php Debugger::$email = 'admin@example.com'; ``` -Če uporabljate celotno ogrodje Nette, lahko to in druge nastavite v [konfiguracijski datoteki |nette:configuring]. +Če uporabljate celoten Nette Framework, lahko to in drugo nastavite v [konfiguracijski datoteki |nette:configuring]. -Za zaščito e-poštnega predala pred poplavo Tracy pošlje **pougo eno sporočilo** in ustvari datoteko `email-sent`. Ko razvijalec prejme e-poštno obvestilo, preveri dnevnik, popravi svojo aplikacijo in izbriše datoteko za spremljanje `email-sent`. S tem se ponovno aktivira pošiljanje e-pošte. +Da pa vam ne bi preplavila e-poštnega predala, vedno pošlje **samo eno sporočilo** in ustvari datoteko `email-sent`. Razvijalec po prejemu e-poštnega obvestila preveri dnevnik, popravi aplikacijo in izbriše nadzorno datoteko, s čimer se ponovno aktivira pošiljanje e-pošte. -Odpiranje datotek v urejevalniku .[#toc-opening-files-in-the-editor] -==================================================================== +Odpiranje v urejevalniku +======================== -Ko je prikazana stran z napako, lahko kliknete na imena datotek in te se bodo odprle v urejevalniku s kazalcem na ustrezni vrstici. Datoteke lahko tudi ustvarite (dejanje `create file`) ali v njih odpravite napake (dejanje `fix it`). Da bi to lahko storili, morate [konfigurirati brskalnik in sistem |open-files-in-ide]. +Pri prikazu strani z napako lahko kliknete na imena datotek in te se bodo odprle v vašem urejevalniku s kazalcem na ustrezni vrstici. Prav tako lahko datoteke ustvarjate (dejanje `create file`) ali v njih popravljate napake (dejanje `fix it`). Da bi to delovalo, je treba [konfigurirati brskalnik in sistem |open-files-in-ide]. -Podprte različice PHP .[#toc-supported-php-versions] -==================================================== +Podprte različice PHP +===================== -| Tracy | združljivo s PHP -|-----------|-------------------- -| Tracy 2.10 – 3.0 | PHP 8.0 - 8.2 -| Tracy 2.9 | PHP 7.2 - 8.2 -| Tracy 2.8 | PHP 7.2 - 8.1 -| Tracy 2.6 - 2.7 | PHP 7.1 - 8.0 -| Tracy 2.5 | PHP 5.4 - 7.4 -| Tracy 2.4 | PHP 5.4 - 7.2 +| Tracy | združljiv s PHP +|-----------|------------------- +| Tracy 2.10 – 3.0 | PHP 8.0 – 8.4 +| Tracy 2.9 | PHP 7.2 – 8.2 +| Tracy 2.8 | PHP 7.2 – 8.1 +| Tracy 2.6 – 2.7 | PHP 7.1 – 8.0 +| Tracy 2.5 | PHP 5.4 – 7.4 +| Tracy 2.4 | PHP 5.4 – 7.2 -Velja za najnovejše različice popravkov. +Velja za zadnjo različico popravka (patch). -Pristanišča .[#toc-ports] -========================= +Porti +===== -To je seznam neuradnih portov za druga ogrodja in CMS: +To je seznam neuradnih prenosov za druga ogrodja in CMS: - [Drupal 7](https://www.drupal.org/project/traced) - Laravel framework: [recca0120/laravel-tracy](https://github.com/recca0120/laravel-tracy), [whipsterCZ/laravel-tracy](https://github.com/whipsterCZ/laravel-tracy) diff --git a/tracy/sl/open-files-in-ide.texy b/tracy/sl/open-files-in-ide.texy index f616ef9d31..9830558416 100644 --- a/tracy/sl/open-files-in-ide.texy +++ b/tracy/sl/open-files-in-ide.texy @@ -1,20 +1,20 @@ -Kako odpreti datoteko v urejevalniku iz programa Tracy? (Integracija IDE) -************************************************************************* +Kako odpreti datoteko v urejevalniku iz Tracy? (Integracija z IDE) +****************************************************************** .[perex] -Ko je prikazana stran z napako, lahko kliknete na imena datotek in te se bodo odprle v urejevalniku s kazalcem na ustrezni vrstici. Datoteke lahko tudi ustvarite (akcija `create file`) ali v njih odpravite napake (akcija `fix it`). Da bi to lahko storili, morate konfigurirati brskalnik in sistem. +Pri prikazu strani z napako lahko kliknete na imena datotek in te se bodo odprle v vašem urejevalniku s kazalcem na ustrezni vrstici. Prav tako lahko datoteke ustvarjate (dejanje `create file`) ali v njih popravljate napake (dejanje `fix it`). Da bi se to zgodilo, je treba konfigurirati brskalnik in sistem. -Tracy odpira datoteke prek naslovov URL v obliki `editor://open/?file=%file&line=%line`, tj. s protokolom `editor://`. Za to bomo registrirali svoj lasten izvajalec. To je lahko katerakoli izvedljiva datoteka, ki obdela parametre in zažene naš najljubši urejevalnik. +Tracy odpira datoteke prek URL-jev v obliki `editor://open/?file=%file&line=%line`, tj. s protokolom `editor://`. Zanj registrirate lastnega upravljalca. To je lahko katera koli izvedljiva datoteka, ki »prežveči« parametre in zažene vaš priljubljeni urejevalnik. -Naslov URL lahko spremenimo v spremenljivki `Tracy\Debugger::$editor` ali pa onemogočimo klik z nastavitvijo `Tracy\Debugger::$editor = null`. +URL lahko spremenite v spremenljivki `Tracy\Debugger::$editor` ali pa klikanje onemogočite z nastavitvijo `Tracy\Debugger::$editor = null`. -Windows .[#toc-windows] -======================= +Windows +======= -1. Prenesite ustrezne datoteke "iz skladišča Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/windows na disk. +1. Prenesite ustrezne datoteke iz "repostorija Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/windows na disk. -2. Uredite spletno stran `open-editor.js` in odkomentirajte ali uredite pot do svojega urejevalnika v `settings`: +2. Uredite datoteko `open-editor.js` in v polju `settings` odkomentirajte ter po potrebi prilagodite pot do vašega urejevalnika: ```js var settings = { @@ -35,19 +35,28 @@ var settings = { ... ``` -Bodite previdni in ohranite dvojne poševnice v poteh. +Pozor, ohranite dvojne poševnice v poteh. -3. V sistemu registrirajte upravljalnik za protokol `editor://`. +3. Registrirajte upravljalca protokola `editor://` v sistemu. -To storite tako, da zaženete `install.cmd`. **Zažeti ga morate kot skrbnik.** Skripta `open-editor.js` bo zdaj služila protokolu `editor://`. +To storite z zagonom datoteke `install.cmd`. **Treba jo je zagnati kot skrbnik.** Skript `open-editor.js` bo zdaj upravljal protokol `editor://`. +Da bi bilo mogoče odpirati povezave, ustvarjene na drugih strežnikih, kot na primer na produkcijskem strežniku ali v Dockerju, v `open-editor.js` dodajte še preslikavo oddaljene poti na lokalno: -Linux .[#toc-linux] -=================== +```js + mappings: { + // oddaljena pot: lokalna pot + '/var/www/nette.app': 'W:\\Nette.web\\_web', + } +``` + + +Linux +===== -1. Prenesite ustrezne datoteke "iz skladišča Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux v imenik `~/bin`. +1. Prenesite ustrezne datoteke iz "repostorija Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux v imenik `~/bin`. -2. Uredite naslov `open-editor.sh` in v spremenljivki `editor` odkomentirajte ali uredite pot do svojega urejevalnika: +2. Uredite datoteko `open-editor.sh` in odkomentirajte ter po potrebi prilagodite pot do vašega urejevalnika v spremenljivki `editor`. ```shell #!/bin/bash @@ -67,24 +76,25 @@ Linux .[#toc-linux] ... ``` -Naredite jo izvršljivo: +Naredite datoteko izvedljivo: ```shell chmod +x ~/bin/open-editor.sh ``` -Če urejevalnik, ki ga uporabljate, ni nameščen iz paketa, binarni program verjetno ne bo imel poti v `$PATH`. To lahko preprosto popravite. V imeniku `~/bin` ustvarite simelno povezavo na binarno datoteko urejevalnika. .[note] +.[note] +Če uporabljenega urejevalnika nimate nameščenega iz paketa, verjetno izvedljiva datoteka ne bo imela poti v `$PATH`. To lahko enostavno popravite. V imeniku `~/bin` ustvarite simbolično povezavo do izvedljive datoteke urejevalnika. -3. V sistemu registrirajte upravljalnik za protokol `editor://`. +3. Registrirajte upravljalca protokola `editor://` v sistemu. -To storite tako, da zaženete `install.sh`. Skripta `open-editor.js` bo zdaj servisirala protokol `editor://`. +To storite z zagonom datoteke `install.sh`. Skript `open-editor.sh` bo zdaj upravljal protokol `editor://`. -macOS .[#toc-macos] -=================== +macOS +===== -Urejevalniki, kot so PhpStorm, TextMate itd., omogočajo odpiranje datotek prek posebnega naslova URL, ki ga morate nastaviti: +Urejevalniki, kot so PhpStorm, TextMate itd., omogočajo odpiranje datotek prek posebnega URL-ja, ki ga je treba samo nastaviti: ```php // PhpStorm @@ -92,44 +102,43 @@ Tracy\Debugger::$editor = 'phpstorm://open?file=%file&line=%line'; // TextMate Tracy\Debugger::$editor = 'txmt://open/?url=file://%file&line=%line'; // MacVim -Tracy\Debugger::$editor = 'mvim://open/?url=file://%file&line=%line'; +Tracy\Debugger::$editor = 'mvim://open?url=file:///%file&line=%line'; // Visual Studio Code Tracy\Debugger::$editor = 'vscode://file/%file:%line'; ``` -Če uporabljate samostojni Tracy, postavite vrstico pred `Tracy\Debugger::enable()`, če uporabljate Nette, pred `$configurator->enableTracy()` v `Bootstrap.php`. +Če uporabljate samostojno Tracy, vstavite vrstico pred `Tracy\Debugger::enable()`, če uporabljate Nette, pa pred `$configurator->enableTracy()` v `Bootstrap.php`. -Na žalost ukrepa `create file` ali `fix it` ne delujeta v operacijskem sistemu MacOS. +Dejanji `create file` ali `fix it` na žalost v sistemu macOS ne delujeta. -Predstavitveni program .[#toc-demos] -==================================== +Primeri +======= -Odpravljanje napake: +Popravek napake: - + -Ustvarjanje nove datoteke: +Ustvarjanje datoteke: - + -Odpravljanje težav .[#toc-troubleshooting] -========================================== +Reševanje težav +=============== -- V brskalniku Firefox boste morda morali [dovoliti |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] izvajanje protokola po meri v nastavitvah about:config tako, da nastavite `network.protocol-handler.expose.editor` na `false` in `network.protocol-handler.expose-all` na `true`. Vendar bi moralo biti to privzeto dovoljeno. -- Če vse skupaj ne deluje takoj, ne zganjajte panike. Poskusite osvežiti stran, ponovno zaženite brskalnik ali računalnik. To bi moralo pomagati. -- Če želite popraviti, glejte [tukaj |https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/]: - Vhodna napaka: Morda ste datoteko ".js" povezali z drugo aplikacijo in ne z gonilnikom JScript. +- V Firefoxu bo morda treba protokol omogočiti z [nastavitvijo |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] `network.protocol-handler.expose.editor` na `false` in `network.protocol-handler.expose-all` na `true` v `about:config`. +- Če vam takoj ne uspe, ne paničarite in poskusite nekajkrat osvežiti stran, preden kliknete na povezavo. Začelo bo delovati! +- Tukaj je [povezava |https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/] za popravek morebitne napake: `Input Error: There is no script engine for file extension ".js"`, `Maybe you associated ".js" file to another app, not JScript engine.` oziroma `za končnico .js ni na voljo nobenega skriptnega pogona`. -Od različice 77 brskalnika Google Chrome se ob odprtju urejevalnika prek povezave ne bo več prikazovalo potrditveno polje "Vedno odpiraj te vrste povezav v povezani aplikaciji". Ovinek za Windows: ustvarite datoteko `fix.reg`: +V brskalniku Google Chrome od različice 77 ne boste več videli potrditvenega polja »Vedno odpri te vrste povezav v povezani aplikaciji«, ko se urejevalnik zažene prek povezave. Rešitev za Windows: ustvarite datoteko `fix.reg`: ``` Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\URLWhitelist] "123"="editor://*" ``` -Uvozite jo z dvojnim klikom in znova zaženite Chrome. +Uvozite jo z dvojnim klikom in ponovno zaženite brskalnik Chrome. -Če imate več težav ali vprašanj, vprašajte na [forumu |https://forum.nette.org]. +Z morebitnimi vprašanji ali pripombami se obrnite na [forum |https://forum.nette.org]. diff --git a/tracy/sl/recipes.texy b/tracy/sl/recipes.texy index 7288e81350..546ae2dbd3 100644 --- a/tracy/sl/recipes.texy +++ b/tracy/sl/recipes.texy @@ -1,14 +1,13 @@ -Recepti -******* +Navodila +******** -Politika varnosti vsebine .[#toc-content-security-policy] -========================================================= +Content Security Policy +======================= -Če vaše spletno mesto uporablja politiko za varnost vsebine, morate dodati `'nonce-'` in `'strict-dynamic'` na `script-src`, da bo Tracy pravilno deloval. Nekateri tretji vtičniki lahko zahtevajo dodatne direktive. -Nonce ni podprt v direktivi `style-src`, če uporabljate to direktivo, morate dodati `'unsafe-inline'`, vendar se temu v produkcijskem načinu izognite. +Če vaše spletno mesto uporablja Content Security Policy, boste morali dodati enaka `'nonce-'` in `'strict-dynamic'` v `script-src`, da bo Tracy pravilno delovala. Nekateri dodatki tretjih oseb lahko zahtevajo dodatne nastavitve. Nonce ni podprt v direktivi `style-src`, če uporabljate to direktivo, morate dodati `'unsafe-inline'`, vendar se temu v produkcijskem načinu izogibajte. -Primer konfiguracije za [ogrodje Nette |nette:configuring]: +Primer konfiguracije za [Nette Framework |nette:configuring]: ```neon http: @@ -16,7 +15,7 @@ http: script-src: [nonce, strict-dynamic] ``` -Primer v čistem jeziku PHP: +Primer v čistem PHP: ```php $nonce = base64_encode(random_bytes(20)); @@ -24,11 +23,10 @@ header("Content-Security-Policy: script-src 'nonce-$nonce' 'strict-dynamic';"); ``` -Hitrejše nalaganje .[#toc-faster-loading] -========================================= +Hitrejše nalaganje +================== -Osnovna integracija je preprosta, vendar če imate na spletni strani skripte za počasno blokiranje, lahko upočasnijo nalaganje Tracyja. -Rešitev je, da postavite `` v predlogo pred vsemi skriptami: +Zagon je preprost, vendar če imate na spletni strani počasi nalagajoče se blokirajoče skripte, lahko upočasnijo nalaganje Tracy. Rešitev je, da postavite `` v vašo predlogo pred vse skripte: ```latte @@ -42,12 +40,37 @@ Rešitev je, da postavite `` v predlogo p ``` -AJAX in preusmerjeni zahtevki .[#toc-ajax-and-redirected-requests] -================================================================== +Razhroščevanje zahtev AJAX +========================== -Tracy lahko prikaže Debug bar in Bluescreens za zahteve AJAX in preusmeritve. Tracy ustvarja lastne seje, shranjuje podatke v lastne začasne datoteke in uporablja piškotek `tracy-session`. +Tracy samodejno zajema zahteve AJAX, ustvarjene s pomočjo jQuery ali izvornega API-ja `fetch`. Zahteve so v vrstici Tracy prikazane kot dodatne vrstice, kar omogoča enostavno in udobno razhroščevanje AJAX. -Tracy je mogoče konfigurirati tudi za uporabo izvorne seje PHP, ki se zažene, preden je Tracy vklopljen: +Če ne želite samodejno zajemati zahtev AJAX, lahko to funkcijo onemogočite z nastavitvijo spremenljivke JavaScript: + +```js +window.TracyAutoRefresh = false; +``` + +Za ročno spremljanje specifičnih zahtev AJAX dodajte glavo HTTP `X-Tracy-Ajax` z vrednostjo, ki jo vrne `Tracy.getAjaxHeader()`. Tukaj je primer uporabe s funkcijo `fetch`: + +```js +fetch(url, { + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Tracy-Ajax': Tracy.getAjaxHeader(), + } +}) +``` + +Ta pristop omogoča selektivno razhroščevanje zahtev AJAX. + + +Shranjevanje podatkov +===================== + +Tracy zna prikazati panele v Tracy Baru in modre zaslone (Bluescreens) za zahteve AJAX in preusmeritve. Tracy ustvarja lastno sejo, podatke shranjuje v lastne začasne datoteke in uporablja piškotek `tracy-session`. + +Tracy lahko konfigurirate tudi tako, da uporablja izvorno sejo PHP, ki jo zaženete še pred vklopom Tracy: ```php session_start(); @@ -55,44 +78,44 @@ Debugger::setSessionStorage(new Tracy\NativeSession); Debugger::enable(); ``` -Če je za zagon seje potrebna bolj zapletena inicializacija, lahko Tracy zaženete takoj (tako da lahko obravnava morebitne napake), nato pa inicializirate izvajalca seje in nazadnje obvestite Tracy, da je seja pripravljena za uporabo, s funkcijo `dispatch()`: +V primeru, da zagon seje zahteva bolj zapleteno inicializacijo, lahko Tracy zaženete takoj (da lahko obdela morebitne nastale napake), nato inicializirate upravljalca seje in na koncu obvestite Tracy, da je seja pripravljena za uporabo s funkcijo `dispatch()`: ```php Debugger::setSessionStorage(new Tracy\NativeSession); Debugger::enable(); -// ki mu sledi inicializacija seje -// in zaženite sejo. +// sledi inicializacija seje +// in zagon seje session_start(); Debugger::dispatch(); ``` -Funkcija `setSessionStorage()` obstaja od različice 2.9, pred tem je Tracy vedno uporabljal izvorno sejo PHP. +Funkcija `setSessionStorage()` obstaja od različice 2.9, pred tem je Tracy vedno uporabljala izvorno sejo PHP. -Čistilec po meri .[#toc-custom-scrubber] -======================================== +Lastni Scrubber +=============== -Scrubber je filter, ki preprečuje uhajanje občutljivih podatkov, kot so gesla ali poverilnice, iz odlagališč. Filter se pokliče za vsak element izpisanega polja ali predmeta in vrne `true`, če je vrednost občutljiva. V tem primeru se namesto vrednosti izpiše `*****`. +Scrubber je filter, ki preprečuje uhajanje občutljivih podatkov pri izpisovanju (dumping), kot so gesla ali poverilnice. Filter se kliče za vsak element izpisanega polja ali objekta in vrne `true`, če je vrednost občutljiva. V tem primeru se namesto vrednosti izpiše `*****`. ```php -// se izogne odmetavanju vrednosti ključev in lastnosti, kot je `password`, +// prepreči izpis vrednosti ključev in lastnosti, kot so `password`, // `password_repeat`, `check_password`, `DATABASE_PASSWORD` itd. $scrubber = function(string $key, $value, ?string $class): bool { return preg_match('#password#i', $key) && $value !== null; }; -// uporabljamo ga za vsa odlagališča znotraj sistema BlueScreen +// uporabimo ga za vse izpise znotraj BlueScreen Tracy\Debugger::getBlueScreen()->scrubber = $scrubber; ``` -Dnevnik po meri .[#toc-custom-logger] -===================================== +Lastni Logger +============= -Ustvarimo lahko dnevnik po meri, ki beleži napake, nezajete izjeme in ga lahko pokliče tudi `Tracy\Debugger::log()`. Logger implementira vmesnik [api:Tracy\ILogger]. +Lahko ustvarite lasten logger, ki bo beležil napake, neujete izjeme in bo prav tako klican z metodo `Tracy\Debugger::log()`. Logger implementira vmesnik [api:Tracy\ILogger]. ```php use Tracy\ILogger; @@ -101,18 +124,18 @@ class SlackLogger implements ILogger { public function log($value, $priority = ILogger::INFO) { - // pošlje zahtevo v storitev Slack + // pošlje zahtevo na Slack } } ``` -Nato ga aktiviramo: +In nato ga aktivirate: ```php Tracy\Debugger::setLogger(new SlackLogger); ``` -Če uporabljamo celotno ogrodje Nette, ga lahko nastavimo v konfiguracijski datoteki NEON: +Če uporabljate celoten Nette Framework, ga lahko nastavite v konfiguracijski datoteki NEON: ```neon services: @@ -120,10 +143,10 @@ services: ``` -Monolog Integracija .[#toc-monolog-integration] ------------------------------------------------ +Integracija Monologa +-------------------- -Paket Tracy zagotavlja adapter PSR-3, ki omogoča integracijo [monolog/monolog](https://github.com/Seldaek/monolog). +Paket Tracy ponuja adapter PSR-3, ki omogoča integracijo [monolog/monolog |https://github.com/Seldaek/monolog]. ```php $monolog = new Monolog\Logger('main-channel'); @@ -134,20 +157,20 @@ Debugger::setLogger($tracyLogger); Debugger::enable(); Debugger::log('info'); // zapiše: [] main-channel.INFO: info [] [] -Debugger::log('warning', Debugger::WARNING); // zapiše: [] main-channel.WARNING: opozorilo [] [] +Debugger::log('warning', Debugger::WARNING); // zapiše: [] main-channel.WARNING: warning [] [] ``` -nginx .[#toc-nginx] -=================== +Nginx +===== -Če Tracy ne deluje v nginxu, je verjetno napačno konfiguriran. Če je na voljo nekaj takega kot +Če vam Tracy ne deluje na strežniku Nginx, je verjetno napačno konfiguriran. Če je v konfiguraciji nekaj takega: ```nginx try_files $uri $uri/ /index.php; ``` -spremenite v +spremenite to v: ```nginx try_files $uri $uri/ /index.php$is_args$args; diff --git a/tracy/sl/stopwatch.texy b/tracy/sl/stopwatch.texy index b46e9545d3..2038ff5d32 100644 --- a/tracy/sl/stopwatch.texy +++ b/tracy/sl/stopwatch.texy @@ -1,35 +1,35 @@ -Štoparica -********* +Merjenje časa +************* -Še eno uporabno orodje je debuggerjeva štoparica z natančnostjo mikrosekund: +Drugo koristno orodje razhroščevalca so štoparice z natančnostjo mikrosekund: ```php Debugger::timer(); -// sladke sanje, moja draga +// (primer časovno zahtevne operacije) sleep(2); $elapsed = Debugger::timer(); // $elapsed = 2 ``` -Z izbirnim parametrom je mogoče opraviti več meritev hkrati. +Z neobveznim parametrom je mogoče doseči večkratna merjenja. ```php Debugger::timer('page-generating'); -// nekaj kode +// neka koda Debugger::timer('rss-generating'); -// nekaj kode +// neka koda $rssElapsed = Debugger::timer('rss-generating'); $pageElapsed = Debugger::timer('page-generating'); ``` ```php -Debugger::timer(); // zažene časovnik. +Debugger::timer(); // zažene štoparico -... // neko dolgotrajno operacijo. +... // časovno zahtevna operacija -echo Debugger::timer(); // pretečeni čas v sekundah +echo Debugger::timer(); // izpiše pretečeni čas v sekundah ``` diff --git a/tracy/tr/@home.texy b/tracy/tr/@home.texy index 9687315168..5076db70e7 100644 --- a/tracy/tr/@home.texy +++ b/tracy/tr/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Tracy - Tüm PHP Geliştiricileri İçin Olmazsa Olmaz Bir Hata Ayıklama Aracı}} -{{description: Tracy, PHP kodunda hata ayıklamayı kolaylaştırmak için tasarlanmış bir araçtır. Hataları net bir şekilde görselleştirmeye ve günlüğe kaydetmeye, değişkenleri dökmeye ve çok daha fazlasına yardımcı olan tüm PHP programcıları için yararlı bir yardımcıdır. Dikkat! Tracy bağımlılık yapar!}} +{{maintitle: Tracy – Hata Yapmaktan Keyif Aldıran Hata Ayıklama Aracı}} +{description: Tracy, PHP kodunun hata ayıklamasını kolaylaştırmak için tasarlanmış bir araçtır. Tüm PHP programcıları için yararlı bir yardımcıdır, hataların görselleştirilmesine ve günlüğe kaydedilmesine, değişkenlerin dökümüne ve çok daha fazlasına yardımcı olur. Uyarı: Tracy bağımlılık yapar!}} diff --git a/tracy/tr/@left-menu.texy b/tracy/tr/@left-menu.texy index 0faf3a2aeb..9e3473aaeb 100644 --- a/tracy/tr/@left-menu.texy +++ b/tracy/tr/@left-menu.texy @@ -1,7 +1,7 @@ -- [Başlarken |Guide] -- [Damper |Dumper] -- [Kronometre |Stopwatch] -- [Yapılandırma |Configuring] -- Yemek [Tarifleri |Recipes] +- [Tracy ile Başlarken |guide] +- [Değişken Dökümü |dumper] +- [Zaman Ölçümü |stopwatch] +- [Yapılandırma |configuring] +- [Öğreticiler |recipes] - [IDE Entegrasyonu |open-files-in-ide] -- [Uzantılar Oluşturma |extensions] +- [Uzantı Oluşturma |extensions] diff --git a/tracy/tr/@menu.texy b/tracy/tr/@menu.texy index f6ced5f867..b88ed24036 100644 --- a/tracy/tr/@menu.texy +++ b/tracy/tr/@menu.texy @@ -1,3 +1,3 @@ -- [Ev |@home] -- [Dokümantasyon |Guide] +- [Giriş |@home] +- [Dokümantasyon |guide] - "GitHub .[link-external]":https://github.com/nette/tracy diff --git a/tracy/tr/@meta.texy b/tracy/tr/@meta.texy new file mode 100644 index 0000000000..272c7f354c --- /dev/null +++ b/tracy/tr/@meta.texy @@ -0,0 +1 @@ +{{sitename: Tracy Dokümantasyonu}} diff --git a/tracy/tr/configuring.texy b/tracy/tr/configuring.texy index ac55138a98..bf430e5739 100644 --- a/tracy/tr/configuring.texy +++ b/tracy/tr/configuring.texy @@ -1,75 +1,75 @@ -Tracy Konfigürasyonu +Tracy Yapılandırması ******************** -Aşağıdaki örneklerde, aşağıdaki sınıf takma adının tanımlandığı varsayılmaktadır: +Tüm örnekler, oluşturulmuş bir takma ad varsayar: ```php use Tracy\Debugger; ``` -Hata Günlüğü .[#toc-error-logging] ----------------------------------- +Hata Günlüklemesi +----------------- ```php $logger = Debugger::getLogger(); -// hata oluştuysa bildirim bu e-postaya gönderilir -$logger->email = 'dev@example.com'; // (string|string[]) varsayılan olarak unset +// Hata oluştuğunda bildirimlerin gönderildiği e-posta adresi +$logger->email = 'dev@example.com'; // (string|string[]) varsayılan olarak ayarlanmamıştır -// e-posta gönderen -$logger->fromEmail = 'me@example.com'; // (string) varsayılan olarak unset +// E-postanın göndericisi +$logger->fromEmail = 'me@example.com'; // (string) varsayılan olarak ayarlanmamıştır -// e-posta göndermek için rutin -$logger->mailer = /* ... */; // (çağrılabilir) varsayılan olarak mail() ile gönderilir +// E-posta gönderimini sağlayan rutin +$logger->mailer = /* ... */; // (callable) varsayılan, mail() fonksiyonu ile göndermektir -// başka bir e-posta göndermek için en kısa ne kadar süre sonra? -$logger->emailSnooze = /* ... */; // (string) varsayılan değer '2 gün' +// Bir sonraki e-postanın gönderilmesi için en kısa süre nedir? +$logger->emailSnooze = /* ... */; // (string) varsayılan '2 days' -// BlueScreen hangi hata seviyeleri için de günlüğe kaydedilir? -Debugger::$logSeverity = E_WARNING | E_NOTICE; // varsayılan değer 0 (hata seviyesi yok) +// Hangi hata seviyeleri için BlueScreen de günlüğe kaydedilir? +Debugger::$logSeverity = E_WARNING | E_NOTICE; // varsayılan 0'dır (hata seviyesi yok) ``` -`dump()` Davranış ------------------ +`dump()` Davranışı +------------------ ```php -// maksimum dize uzunluğu -Debugger::$maxLength = 150; // (int) Tracy'ye göre varsayılan +// Maksimum karakter dizisi uzunluğu +Debugger::$maxLength = 150; // (int) varsayılan Tracy sürümüne göre değişir -// ne kadar derin listeleyecek -Debugger::$maxDepth = 10; // (int) Tracy'ye göre varsayılan +// Maksimum iç içe geçme derinliği +Debugger::$maxDepth = 10; // (int) varsayılan Tracy sürümüne göre değişir -// bu anahtarların değerlerini gizleyin (Tracy 2.8'den beri) -Debugger::$keysToHide = ['password', /* ... */]; // (string[]) varsayılan olarak [] +// Bu anahtarların değerlerini gizle (Tracy 2.8'den itibaren) +Debugger::$keysToHide = ['password', /* ... */]; // (string[]) varsayılan [] -// görsel tema (Tracy 2.8'den beri) -Debugger::$dumpTheme = 'dark'; // (light|dark) varsayılan olarak 'light' +// Görsel tema (Tracy 2.8'den itibaren) +Debugger::$dumpTheme = 'dark'; // (light|dark) varsayılan 'light' -// dump() işlevinin çağrıldığı konumu gösterir mi? -Debugger::$showLocation = /* ... */; // (bool) Tracy'ye göre varsayılan +// dump() fonksiyonunun çağrıldığı yeri göster? +Debugger::$showLocation = /* ... */; // (bool) varsayılan Tracy sürümüne göre değişir ``` -Diğerleri .[#toc-others] ------------------------- +Diğer +----- ```php -// Geliştirme modunda, Mavi Ekran olarak bildirim veya hata uyarıları göreceksiniz -Debugger::$strictMode = /* ... */; // (bool|int) varsayılan olarak false, sadece belirli hata seviyelerini seçebilirsiniz (örn. E_USER_DEPRECATED | E_DEPRECATED) +// Geliştirme modunda, notice veya warning türündeki hataları BlueScreen olarak gösterir +Debugger::$strictMode = /* ... */; // (bool|int) varsayılan false'tur, sadece belirli hata seviyeleri seçilebilir (örneğin, E_USER_DEPRECATED | E_DEPRECATED) -// sessiz (@) hata mesajlarını görüntüler -Debugger::$scream = /* ... */; // (bool|int) varsayılan olarak false, 2.9 sürümünden beri sadece belirli hata seviyelerini seçmek mümkündür (örn. E_USER_DEPRECATED | E_DEPRECATED) +// Susturulmuş (@) hata mesajlarını göster? +Debugger::$scream = /* ... */; // (bool|int) varsayılan false'tur, sürüm 2.9'dan itibaren sadece belirli hata seviyeleri seçilebilir (örneğin, E_USER_DEPRECATED | E_DEPRECATED) -// düzenleyicide açmak için bağlantı biçimi -Debugger::$editor = /* ... */; // (string|null) varsayılan değer 'editor://open/?file=%file&line=%line' +// Düzenleyicide açmak için bağlantı formatı +Debugger::$editor = /* ... */; // (string|null) varsayılan 'editor://open/?file=%file&line=%line' -// 500 hatası için özel sayfa içeren şablonun yolu -Debugger::$errorTemplate = /* ... */; // (string) varsayılan olarak unset +// Hata 500 için özel sayfa şablonunun yolu +Debugger::$errorTemplate = /* ... */; // (string) varsayılan olarak ayarlanmamıştır -// Tracy Bar'ı gösterir mi? -Debugger::$showBar = /* ... */; // (bool) varsayılan olarak true +// Tracy Bar'ı göster? +Debugger::$showBar = /* ... */; // (bool) varsayılan true Debugger::$editorMapping = [ // orijinal => yeni @@ -79,64 +79,63 @@ Debugger::$editorMapping = [ ``` -Nette Çerçevesi .[#toc-nette-framework] ---------------------------------------- +Nette Framework +--------------- -Nette Framework kullanıyorsanız, yapılandırma dosyasını kullanarak Tracy'yi yapılandırabilir ve Tracy Çubuğuna yeni paneller ekleyebilirsiniz. -Yapılandırmada Tracy parametrelerini ayarlayabilir ve ayrıca Tracy Çubuğuna yeni paneller ekleyebilirsiniz. Bu ayarlar yalnızca DI konteyneri oluşturulduktan sonra uygulanır, bu nedenle daha önce meydana gelen hatalar bunları yansıtamaz. +Nette Framework kullanıyorsanız, Tracy'yi yapılandırabilir ve yapılandırma dosyası aracılığıyla Tracy Bar'a yeni paneller ekleyebilirsiniz. Yapılandırmada parametreleri ayarlayabilir ve ayrıca Tracy Bar'a yeni paneller ekleyebilirsiniz. Bu ayarlar yalnızca DI konteyneri oluşturulduktan sonra uygulanır, bu nedenle daha önce oluşan hatalar bunları yansıtamaz. -Hata günlüğü yapılandırması: +Hata günlükleme yapılandırması: ```neon tracy: - # hata oluştuysa bildirim bu e-postaya gönderilir - email: dev@example.com # (string|string[]) varsayılan olarak unset + # Hata oluştuğunda bildirimlerin gönderildiği e-posta adresi + email: dev@example.com # (string|string[]) varsayılan olarak ayarlanmamıştır - # e-posta gönderen - fromEmail: robot@example.com # (string) varsayılan olarak unset + # E-postanın göndericisi + fromEmail: robot@example.com # (string) varsayılan olarak ayarlanmamıştır - # e-posta gönderimini erteleme süresi (Tracy 2.8.8'den beri) - emailSnooze: ... # (string) varsayılan olarak '2 gün' + # E-posta gönderiminin ertelenme süresi (Tracy 2.8.8'den itibaren) + emailSnooze: ... # (string) varsayılan '2 days' - # yapılandırmada tanımlanmış bir mailer kullanmak için? (Tracy 2.5'ten beri) - netteMailer: ... # (bool) varsayılan olarak true + # E-posta göndermek için Nette mailer kullanılsın mı? (Tracy 2.5'ten itibaren) + netteMailer: ... # (bool) varsayılan true - # BlueScreen hangi hata seviyeleri için de günlüğe kaydedilir? - logSeverity: [E_WARNING, E_NOTICE] # varsayılan değer [] + # Hangi hata seviyeleri için BlueScreen de günlüğe kaydedilir? + logSeverity: [E_WARNING, E_NOTICE] # varsayılan [] ``` -`dump()` işlevi için yapılandırma: +`dump()` fonksiyonunun davranış yapılandırması: ```neon tracy: - # maksimum dize uzunluğu - maxLength: 150 # (int) Tracy'ye göre varsayılan + # Maksimum karakter dizisi uzunluğu + maxLength: 150 # (int) varsayılan Tracy sürümüne göre değişir - # ne kadar derin listeleyecek - maxDepth: 10 # (int) Tracy'ye göre varsayılan + # Maksimum iç içe geçme derinliği + maxDepth: 10 # (int) varsayılan Tracy sürümüne göre değişir - # bu anahtarların değerlerini gizle (Tracy 2.8'den beri) - keysToHide: [password, pass] # (string[]) varsayılan olarak [] + # Bu anahtarların değerlerini gizle (Tracy 2.8'den itibaren) + keysToHide: [password, pass] # (string[]) varsayılan [] - # görsel tema (Tracy 2.8'den beri) - dumpTheme: dark # (light|dark) varsayılan olarak 'light' + # Görsel tema (Tracy 2.8'den itibaren) + dumpTheme: dark # (light|dark) varsayılan 'light' - # dump() işlevinin çağrıldığı konumu gösterir mi? - showLocation: ... # (bool) Tracy'ye göre varsayılan + # dump() fonksiyonunun çağrıldığı yeri göster? + showLocation: ... # (bool) varsayılan Tracy sürümüne göre değişir ``` -Tracy uzantısını yüklemek için: +Tracy eklentilerinin kurulumu: ```neon tracy: - # çubukları Tracy Bar'a ekler + # Tracy Bar'a paneller ekler bar: - Nette\Bridges\DITracy\ContainerPanel - IncludePanel - XDebugHelper('myIdeKey') - MyPanel(@MyService) - # panelleri BlueScreen'e ekle + # BlueScreen'e paneller ekler blueScreen: - DoctrinePanel::renderException ``` @@ -145,20 +144,20 @@ Diğer seçenekler: ```neon tracy: - # Geliştirme modunda, bildirim veya hata uyarılarını Mavi Ekran olarak göreceksiniz - strictMode: ... # varsayılan değer true + # Geliştirme modunda, notice veya warning türündeki hataları BlueScreen olarak gösterir + strictMode: ... # varsayılan true - # sessiz (@) hata mesajlarını görüntüler - scream: ... # varsayılan değer false + # Susturulmuş (@) hata mesajlarını göster? + scream: ... # varsayılan false - # düzenleyicide açılacak bağlantı biçimi - editor: ... # (string) varsayılan olarak 'editor://open/?file=%file&line=%line' + # Düzenleyicide açmak için bağlantı formatı + editor: ... # (string) varsayılan 'editor://open/?file=%file&line=%line' - # 500 hatası için özel sayfa içeren şablonun yolu - errorTemplate: ... # (string) varsayılan olarak unset + # Hata 500 için özel sayfa şablonunun yolu + errorTemplate: ... # (string) varsayılan olarak ayarlanmamıştır - # Tracy Bar'ı gösterir mi? - showBar: ... # (bool) varsayılan değer true + # Tracy Bar'ı göster? + showBar: ... # (bool) varsayılan true editorMapping: # orijinal: yeni @@ -166,4 +165,16 @@ tracy: /home/web: /srv/html ``` -`logSeverity`, `strictMode` ve `scream` seçeneklerinin değerleri hata seviyelerinin bir dizisi olarak (örn. `[E_WARNING, E_NOTICE]`) veya PHP'de kullanılan bir ifade olarak (örn. `E_ALL & ~E_NOTICE`) yazılabilir. +`logSeverity`, `strictMode` ve `scream` seçeneklerinin değerleri, hata seviyeleri dizisi olarak (örneğin `[E_WARNING, E_NOTICE]`) veya PHP dilinde kullanılan bir ifade olarak (örneğin `E_ALL & ~E_NOTICE`) yazılabilir. + + +DI Servisleri +------------- + +Bu servisler DI konteynerine eklenir: + +| İsim | Tip | Açıklama +|---------------------------------------------------------- +| `tracy.logger` | [api:Tracy\ILogger] | logger +| `tracy.blueScreen` | [api:Tracy\BlueScreen] | BlueScreen +| `tracy.bar` | [api:Tracy\Bar] | Tracy Bar diff --git a/tracy/tr/dumper.texy b/tracy/tr/dumper.texy index d3684b84d6..bfb3d0e85b 100644 --- a/tracy/tr/dumper.texy +++ b/tracy/tr/dumper.texy @@ -1,7 +1,7 @@ -Damper -****** +Döküm (Dumping) +*************** -Her hata ayıklama geliştiricisi, herhangi bir değişkenin tüm içeriğini ayrıntılı olarak listeleyen `var_dump` işlevi ile iyi bir arkadaştır. Ne yazık ki, çıktısı HTML biçimlendirmesi içermez ve dökümü tek bir HTML kodu satırına çıkarır, bağlam kaçışından bahsetmeye bile gerek yoktur. `var_dump` adresini daha kullanışlı bir işlevle değiştirmek gerekir. İşte `dump()` tam olarak budur. +Her hata ayıklayıcı, değişkenin içeriğini ayrıntılı olarak yazdıran [php:var_dump] fonksiyonunun iyi bir arkadaşıdır. Maalesef, HTML ortamında çıktı biçimlendirmeyi kaybeder ve tek bir satıra karışır, HTML kodunun temizlenmesinden bahsetmiyorum bile. Pratikte, `var_dump` yerine daha kullanışlı bir fonksiyon kullanmak gerekir. İşte bu fonksiyon `dump()`. ```php $arr = [10, 20.2, true, null, 'hello']; @@ -10,11 +10,11 @@ dump($arr); // veya Debugger::dump($arr); ``` -çıktıyı üretir: +şu çıktıyı üretir: [* dump-basic.webp *] -Varsayılan açık temayı koyu olarak değiştirebilirsiniz: +Varsayılan açık temayı koyu temaya değiştirebilirsiniz: ```php Debugger::$dumpTheme = 'dark'; @@ -22,27 +22,27 @@ Debugger::$dumpTheme = 'dark'; [* dump-dark.webp *] -Ayrıca yuvalama derinliğini `Debugger::$maxDepth` ve görüntülenen dizelerin uzunluğunu `Debugger::$maxLength` ile değiştirebilirsiniz. Doğal olarak, daha düşük değerler Tracy görüntülemeyi hızlandırır. +Ayrıca, [Debugger::$maxDepth |api:Tracy\Debugger::$maxDepth] kullanarak iç içe geçme derinliğini ve [Debugger::$maxLength |api:Tracy\Debugger::$maxLength] kullanarak görüntülenen etiketlerin uzunluğunu değiştirebiliriz. Daha düşük değerler doğal olarak Tracy'yi hızlandırır. ```php Debugger::$maxDepth = 2; // varsayılan: 3 Debugger::$maxLength = 50; // varsayılan: 150 ``` -`dump()` işlevi başka yararlı bilgiler de görüntüleyebilir. `Tracy\Dumper::LOCATION_SOURCE` işlevin çağrıldığı dosyanın yolunu içeren bir araç ipucu ekler. `Tracy\Dumper::LOCATION_LINK` dosyaya bir bağlantı ekler. `Tracy\Dumper::LOCATION_CLASS` dökümü alınan her nesneye, nesnenin sınıfının tanımlandığı dosyanın yolunu içeren bir araç ipucu ekler. Tüm bu sabitler `dump()` çağrılmadan önce `Debugger::$showLocation` değişkeninde ayarlanabilir. `|` operatörünü kullanarak birden fazla değeri aynı anda ayarlayabilirsiniz. +`dump()` fonksiyonu diğer yararlı bilgileri de yazdırabilir. `Tracy\Dumper::LOCATION_SOURCE` sabiti, fonksiyonun çağrıldığı yere giden yolu içeren bir araç ipucu ekler. `Tracy\Dumper::LOCATION_LINK` bize o yere bir bağlantı sağlar. `Tracy\Dumper::LOCATION_CLASS`, dökümü yapılan her nesne için, sınıfının tanımlandığı dosyanın yolunu içeren bir araç ipucu yazdırır. Sabitler, `dump()` çağrısından önce `Debugger::$showLocation` değişkenine ayarlanır. Aynı anda birden fazla değer ayarlamak istiyorsak, bunları `|` operatörü ile birleştiririz. ```php -Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // dump() işlevinin çağrıldığı yerin yolunu gösterir -Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Hem sınıflara giden yolları hem de dump() işlevinin çağrıldığı yere giden bağlantıyı gösterir -Debugger::$showLocation = false; // Ek konum bilgilerini gizler -Debugger::$showLocation = true; // Tüm ek konum bilgilerini gösterir +Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // Yalnızca fonksiyon çağrı yerinin çıktısını ayarlar +Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Aynı anda bağlantı çıktısını ve sınıf yolunu ayarlar +Debugger::$showLocation = false; // Ek bilgilerin çıktısını kapatır +Debugger::$showLocation = true; // Tüm ek bilgilerin çıktısını açar ``` -`dump()` için çok kullanışlı bir alternatif `dumpe()` (yani dump and exit) ve `bdump()`. Bu, Tracy Bar'daki değişkenleri dökmemizi sağlar. Bu kullanışlıdır, çünkü dökümler çıktıyı karıştırmaz ve ayrıca döküme bir başlık ekleyebiliriz. +`dump()` fonksiyonuna pratik bir alternatif `dumpe()` (dump & exit) ve `bdump()` fonksiyonudur. Bu, değişkenin değerini Tracy Bar panelinde yazdırmamızı sağlar. Bu çok kullanışlıdır, çünkü dökümler sayfa düzeninden ayrıdır ve yanlarına yorum da ekleyebiliriz. ```php -bdump([2, 4, 6, 8], 'even numbers up to ten'); -bdump([1, 3, 5, 7, 9], 'odd numbers up to ten'); +bdump([2, 4, 6, 8], 'ona kadar çift sayılar'); +bdump([1, 3, 5, 7, 9], 'ona kadar tek sayılar'); ``` -[* bardump-en.webp *] +[* bardump-cs.webp *] diff --git a/tracy/tr/extensions.texy b/tracy/tr/extensions.texy index 149409eee0..56c833e7a1 100644 --- a/tracy/tr/extensions.texy +++ b/tracy/tr/extensions.texy @@ -1,23 +1,23 @@ -Tracy Uzantıları Oluşturma -************************** +Tracy için Eklenti Oluşturma +****************************
                                                                                                                            -Tracy, uygulamanızda hata ayıklamak için harika bir araçtır. Ancak bazen Tracy'nin sunduğundan daha fazla bilgiye ihtiyacınız olabilir. Şunlar hakkında bilgi edineceksiniz: +Tracy, uygulamanızın hatalarını ayıklamak için harika bir araç sağlar. Ancak bazen elinizin altında başka bilgilere de sahip olmak istersiniz. Geliştirmeyi daha da keyifli hale getirmek için Tracy Bar için kendi eklentilerinizi nasıl yazacağınızı göstereceğiz. -- Kendi Tracy Bar panellerinizi oluşturma -- Kendi Bluescreen uzantılarınızı oluşturma +- Tracy Bar için kendi panelinizi oluşturma +- Bluescreen için kendi eklentinizi oluşturma
                                                                                                                            .[tip] -Tracy için faydalı uzantıları "Componette":https://componette.org/search/tracy üzerinde bulabilirsiniz. +Tracy için hazır eklentilerin deposunu "Componette":https://componette.org/search/tracy adresinde bulabilirsiniz. -Tracy Bar Uzatmaları .[#toc-tracy-bar-extensions] -================================================= +Tracy Bar Eklentileri +===================== -Tracy Bar için yeni bir uzantı oluşturmak basittir. `Tracy\IBarPanel` arayüzünü `getTab()` ve `getPanel()` yöntemleriyle uygulamanız gerekir. Yöntemler bir sekmenin (Tracy Bar üzerindeki küçük etiket) ve bir panelin (sekmeye tıklandıktan sonra görüntülenen açılır pencere) HTML kodunu döndürmelidir. `getPanel()` hiçbir şey döndürmezse, yalnızca sekme görüntülenir. `getTab()` hiçbir şey döndürmezse, hiçbir şey görüntülenmez ve `getPanel()` çağrılmaz. +Tracy Bar için yeni bir eklenti oluşturmak karmaşık değildir. `Tracy\IBarPanel` arayüzünü uygulayan bir nesne oluşturursunuz. Bu arayüzün iki metodu vardır: `getTab()` ve `getPanel()`. Metotlar, sekmenin (doğrudan Bar üzerinde görüntülenen küçük bir etiket) ve panelin HTML kodunu döndürmelidir. `getPanel()` hiçbir şey döndürmezse, yalnızca etiketin kendisi görüntülenir. `getTab()` hiçbir şey döndürmezse, hiçbir şey görüntülenmez ve `getPanel()` artık çağrılmaz. ```php class ExamplePanel implements Tracy\IBarPanel @@ -35,16 +35,16 @@ class ExamplePanel implements Tracy\IBarPanel ``` -Kayıt .[#toc-registration] --------------------------- +Kayıt +----- -Kayıtlar `Tracy\Bar::addPanel()` adresinden yapılmaktadır: +Kayıt işlemi `Tracy\Bar::addPanel()` kullanılarak yapılır: ```php Tracy\Debugger::getBar()->addPanel(new ExamplePanel); ``` -ya da panelinizi uygulama yapılandırmasına kaydedebilirsiniz: +Veya paneli doğrudan uygulama yapılandırmasında kaydedebilirsiniz: ```neon tracy: @@ -53,70 +53,70 @@ tracy: ``` -Sekme HTML Kodu .[#toc-tab-html-code] -------------------------------------- +Sekme HTML Kodu +--------------- -Şuna benzer bir şey olmalı: +Yaklaşık olarak şöyle görünmelidir: ```latte - + ... - Title + Başlık ``` -Görüntü SVG biçiminde olmalıdır. Araç ipucuna ihtiyacınız yoksa, bırakabilirsiniz `` Dışarı. +Resim SVG formatında olmalıdır. Açıklayıcı bir etiket gerekmiyorsa, `` atlanabilir. -Panel HTML Kodu .[#toc-panel-html-code] ---------------------------------------- +Panel HTML Kodu +--------------- -Şuna benzer bir şey olmalı: +Yaklaşık olarak şöyle görünmelidir: ```latte -

                                                                                                                            Title

                                                                                                                            +

                                                                                                                            Başlık

                                                                                                                            - ... content ... + ... içerik ...
                                                                                                                            ``` -Başlık ya sekmedeki ile aynı olmalı ya da ek bilgi içermelidir. +Başlık, sekme başlığıyla aynı olmalı veya ek bilgiler içerebilir. -Bir uzantı birden fazla kez kaydedilebilir, bu nedenle stil için `id` özniteliğini kullanmamanız önerilir. Sınıfları kullanabilirsiniz, tercihen `tracy-addons-[-]` biçimi. CSS oluştururken, `#tracy-debug .class` adresini kullanmak daha iyidir, çünkü bu kural sıfırlamadan daha yüksek önceliğe sahiptir. +Bir eklentinin, örneğin farklı ayarlarla birden çok kez kaydedilebileceğini unutmayın, bu nedenle stil için CSS id'leri kullanılamaz, yalnızca class kullanılabilir ve bu da `tracy-addons-[-]` şeklinde olmalıdır. Sınıfı daha sonra `tracy-inner` sınıfıyla birlikte div'e yazın. CSS yazarken, `#tracy-debug .trida` yazmak yararlıdır, çünkü kural daha sonra sıfırlamadan daha yüksek önceliğe sahip olur. -Varsayılan Stiller .[#toc-default-styles] ------------------------------------------ +Varsayılan Stiller +------------------ -Panelde, öğeler ``, `
                                                                                                                            `, `
                                                                                                                            `, `` varsayılan stillere sahiptir. Başka bir öğeyi gizlemek veya görüntülemek için bir bağlantı oluşturmak için, bunları `href` ve `id` nitelikleri ve `tracy-toggle` sınıfı ile bağlayın.
                                                                                                                            +Panelde ``, `
                                                                                                                            `, `
                                                                                                                            `, `` önceden stillendirilmiştir. Başka bir öğeyi gizleyen ve gösteren bir bağlantı oluşturmak istiyorsanız, bunları `href` ve `id` nitelikleri ve `tracy-toggle` sınıfıyla bağlayın:
                                                                                                                             
                                                                                                                             ```latte
                                                                                                                            -Detail
                                                                                                                            +Detaylar
                                                                                                                             
                                                                                                                            -
                                                                                                                            ...
                                                                                                                            +
                                                                                                                            ...
                                                                                                                            ``` Varsayılan durum daraltılmışsa, her iki öğeye de `tracy-collapsed` sınıfını ekleyin. -Bir sayfada yinelenen kimlikleri önlemek için statik bir sayaç kullanın. +Aynı sayfada yinelenen ID'ler oluşturmamak için sayacı statik olarak kullanın. -Bluescreen Uzantıları .[#toc-bluescreen-extensions] -=================================================== +Bluescreen Eklentileri +====================== -Mavi ekranda görünecek olan kendi istisna görselleştirmelerinizi veya panellerinizi ekleyebilirsiniz. +Bu şekilde, istisnaların özel görselleştirmelerini veya bluescreen'de görüntülenecek panelleri ekleyebilirsiniz. -Uzatma bu şekilde yapılır: +Eklenti şu komutla oluşturulur: ```php Tracy\Debugger::getBlueScreen()->addPanel(function (?Throwable $e) { // yakalanan istisna return [ - 'tab' => '...Başlık...', - 'panel' => '...içerik...', + 'tab' => '...Etiket...', + 'panel' => '...Panel HTML kodu...', ]; }); ``` -Fonksiyon iki kez çağrılır, önce istisnanın kendisi `$e` parametresinde geçirilir ve döndürülen panel sayfanın başında oluşturulur. Hiçbir şey döndürülmezse panel oluşturulmaz. Ardından `null` parametresiyle çağrılır ve döndürülen panel callstack'in altında oluşturulur. Fonksiyon dizide `'bottom' => true` döndürürse, panel en altta oluşturulur. +Fonksiyon iki kez çağrılır: ilk olarak `$e` parametresinde istisnanın kendisi iletilir ve döndürülen panel sayfanın başında oluşturulur. Hiçbir şey döndürmezse, panel oluşturulmaz. Ardından `null` parametresiyle çağrılır ve döndürülen panel çağrı yığınının altında oluşturulur. Eğer fonksiyon dizide `'bottom' => true` anahtarını döndürürse, panel en altta oluşturulur. diff --git a/tracy/tr/guide.texy b/tracy/tr/guide.texy index 30b5ded8b0..989ba32ac6 100644 --- a/tracy/tr/guide.texy +++ b/tracy/tr/guide.texy @@ -1,213 +1,212 @@ -Tracy ile Başlarken -******************* +Tracy'ye Başlarken +******************
                                                                                                                            -Tracy kütüphanesi günlük PHP programcıları için yararlı bir yardımcıdır. Size yardımcı olur: +Tracy kütüphanesi, PHP programcısının günlük olarak kullandığı yararlı bir yardımcıdır. Size şu konularda yardımcı olur: -- hataları hızla tespit edin ve düzeltin -- günlük hataları -- değişkenlerin dökümü -- komut dosyalarının/sorguların yürütme süresini ölçme -- bellek tüketimine bakın +- hataları hızla tespit etme ve düzeltme +- hataları günlüğe kaydetme +- değişkenleri yazdırma +- betiklerin ve veritabanı sorgularının süresini ölçme +- bellek gereksinimlerini izleme
                                                                                                                            -PHP, programcılara büyük esneklik sağladığı için zor tespit edilebilen hatalar yapmak için mükemmel bir dildir. Tracy\Debugger bu nedenle daha değerlidir. Teşhis araçları arasında nihai bir araçtır. +PHP, geliştiricilere önemli ölçüde özgürlük verdiği için tespit edilmesi zor hatalar oluşturmak için mükemmel bir dildir. Bu nedenle hata ayıklama aracı Tracy daha da değerlidir. PHP için teşhis araçları arasında mutlak zirveyi temsil eder. -Eğer Tracy ile ilk kez tanışıyorsanız, inanın bana, hayatınız Tracy'den önceki ve onunla olan olarak ikiye ayrılmaya başlar. İyi kısma hoş geldiniz! +Bugün Tracy ile ilk kez karşılaşıyorsanız, hayatınızın Tracy'den öncesi ve sonrası olarak ikiye ayrılacağına inanın. Daha iyi kısma hoş geldiniz! -Kurulum ve Gereksinimler .[#toc-installation-and-requirements] -============================================================== +Kurulum +======= -Tracy'yi kurmanın en iyi yolu en [son paketi indirmektir](https://github.com/nette/tracy/releases) veya Composer'ı kullanmaktır: +Tracy'yi kurmanın en iyi yolu [en son paketi indirmek](https://github.com/nette/tracy/releases) veya Composer kullanmaktır: ```shell composer require tracy/tracy ``` -Alternatif olarak, tüm paketi veya [tracy.phar |https://github.com/nette/tracy/releases] dosyasını indirebilirsiniz. +Ayrıca tüm paketi [tracy.phar |https://github.com/nette/tracy/releases] dosyası olarak da indirebilirsiniz. -Kullanım .[#toc-usage] -====================== +Kullanım +======== -Tracy, `Tracy\Debugger::enable()' yönteminin programın başında, herhangi bir çıktı gönderilmeden önce mümkün olan en kısa sürede çağrılmasıyla etkinleştirilir: +Tracy'yi programın başlangıcında, herhangi bir çıktı gönderilmeden önce mümkün olan en kısa sürede `Tracy\Debugger::enable()` metodunu çağırarak etkinleştiririz: ```php use Tracy\Debugger; -require 'vendor/autoload.php'; // alternatif olarak tracy.phar +require 'vendor/autoload.php'; // veya tracy.phar Debugger::enable(); ``` -Sayfada ilk fark edeceğiniz şey sağ alt köşedeki Tracy Çubuğudur. Eğer bunu göremiyorsanız, bu Tracy'nin üretim modunda çalıştığı anlamına gelebilir. -Bunun nedeni Tracy'nin güvenlik nedeniyle yalnızca localhost'ta görünür olmasıdır. Çalışıp çalışmadığını test etmek için `Debugger::enable(Debugger::Development)` parametresini kullanarak geçici olarak geliştirme moduna geçirebilirsiniz. +Sayfada fark edeceğiniz ilk şey, sağ alt köşedeki Tracy Bar'dır. Görmüyorsanız, Tracy'nin üretim modunda çalıştığı anlamına gelebilir. Tracy, güvenlik nedenleriyle yalnızca localhost'ta görünür. Çalışıp çalışmadığını test etmek için, `Debugger::enable(Debugger::Development)` parametresini kullanarak geçici olarak geliştirme moduna geçirebilirsiniz. -Tracy Bar .[#toc-tracy-bar] -=========================== +Tracy Bar +========= -Tracy Çubuğu yüzen bir paneldir. Bir sayfanın sağ alt köşesinde görüntülenir. Fareyi kullanarak hareket ettirebilirsiniz. Sayfa yeniden yüklendikten sonra konumunu hatırlayacaktır. +Tracy Bar, sayfanın sağ alt köşesinde görüntülenen kayan bir paneldir. Fare ile hareket ettirebiliriz ve sayfa yeniden yüklendikten sonra konumunu hatırlar. [* tracy-bar.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html -Tracy Bar'a başka kullanışlı paneller ekleyebilirsiniz. İlginç olanları [eklentilerde |https://componette.org] bulabilir veya [kendiniz oluşturabilirsiniz |extensions]. +Tracy Bar'a başka yararlı paneller eklenebilir. Birçoğunu [eklentilerde |https://componette.org] bulabilir veya hatta [kendiniz yazabilirsiniz |extensions]. -Tracy Bar'ı göstermek istemiyorsanız, ayarlayın: +Tracy Bar'ı görüntülemek istemiyorsanız, şunu ayarlayın: ```php Debugger::$showBar = false; ``` -Hata ve İstisnaların Görselleştirilmesi .[#toc-visualization-of-errors-and-exceptions] -====================================================================================== +Hataların ve İstisnaların Görselleştirilmesi +============================================ -PHP'nin hataları nasıl raporladığını biliyorsunuzdur: sayfanın kaynak kodunda buna benzer bir şey vardır: +PHP'nin hataları nasıl bildirdiğini kesinlikle iyi biliyorsunuz: sayfanın kaynak koduna şöyle bir şey yazar: /--pre .{font-size: 90%} Parse error: syntax error, unexpected '}' in HomePresenter.php on line 15 \-- -veya yakalanmamış istisna: +veya yakalanmamış bir istisna durumunda: /--pre .{font-size: 90%} Fatal error: Uncaught Nette\MemberAccessException: Call to undefined method Nette\Application\UI\Form::addTest()? in /sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php:100 Stack trace: #0 /sandbox/vendor/nette/utils/src/Utils/Object.php(75): Nette\Utils\ObjectMixin::call(Object(Nette\Application\UI\Form), 'addTest', Array) -#1 /sandbox/app/forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) -#2 /sandbox/app/presenters/SignPresenter.php(21): App\Forms\SignFormFactory->create() -#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presenters\SignPresenter->createComponentSignInForm('signInForm') +#1 /sandbox/app/Forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) +#2 /sandbox/app/Presentation/Sign/SignPresenter.php(21): App\Forms\SignFormFactory->create() +#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presentation\Sign\SignPresenter->createComponentSignInForm('signInForm') #4 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(139): Nette\ComponentModel\Container->createComponent('signInForm') #5 /sandbox/temp/cache/latte/15206b353f351f6bfca2c36cc.php(17): Nette\ComponentModel\Co in /sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php on line 100
                                                                                                                            \-- -Bu çıktıda gezinmek o kadar kolay değildir. Tracy'yi etkinleştirirseniz, hem hatalar hem de istisnalar tamamen farklı bir biçimde görüntülenir: +Böyle bir çıktıda gezinmek pek kolay değildir. Tracy'yi açarsak, hata veya istisna tamamen farklı bir biçimde görüntülenir: [* tracy-exception.webp .{url:-} *] -Hata mesajı tam anlamıyla çığlık atıyor. Kaynak kodun bir bölümünü, hatanın oluştuğu vurgulanmış satırla birlikte görebilirsiniz. Bir mesaj hatayı net bir şekilde açıklıyor. Sitenin tamamı [etkileşimlidir, deneyin](https://nette.github.io/tracy/tracy-exception.html). +Hata mesajı kelimenin tam anlamıyla bağırıyor. Hatanın meydana geldiği vurgulanmış satırla birlikte kaynak kodunun bir bölümünü ve *Call to undefined method Nette\Http\User::isLogedIn()* bilgisini görüyoruz, bu da hatanın ne olduğunu açıkça açıklıyor. Ayrıca tüm sayfa canlıdır, daha fazla ayrıntıya tıklayabiliriz. [Deneyin |https://nette.github.io/tracy/tracy-exception.html]. -Ve ne var biliyor musunuz? Ölümcül hatalar da aynı şekilde yakalanır ve görüntülenir. Herhangi bir uzantı yüklemenize gerek yok (canlı örnek için tıklayın): +Ve biliyor musunuz? Bu şekilde ölümcül hataları bile yakalar ve görüntüler. Herhangi bir eklenti yüklemeye gerek kalmadan. [* tracy-error.webp .{url:-} *] -Bir değişken adındaki yazım hatası veya var olmayan bir dosyanın açılmaya çalışılması gibi hatalar E_NOTICE veya E_WARNING düzeyinde raporlar oluşturur. Bunlar kolayca gözden kaçabilir ve/veya bir web sayfası grafik düzeninde tamamen gizlenebilir. Tracy'nin bunları yönetmesine izin verin: +Değişken adındaki bir yazım hatası veya var olmayan bir dosyayı açma girişimi gibi hatalar, E_NOTICE veya E_WARNING seviyesinde mesajlar üretir. Bunlar sayfa grafiğinde kolayca gözden kaçabilir, hatta hiç görünmeyebilir (sayfa koduna bakmadıkça). [* tracy-notice2.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html -Ya da hatalar gibi görüntülenebilirler: +Veya hatalarla aynı şekilde görüntülenebilirler: ```php -Debugger::$strictMode = true; // tüm hataları görüntüle -Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // deprecated bildirimleri hariç tüm hatalar +Debugger::$strictMode = true; // tüm hataları göster +Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // kullanımdan kaldırma bildirimleri dışındaki tüm hatalar ``` [* tracy-notice.webp .{url:-} *] -Not: Tracy etkinleştirildiğinde hata raporlama seviyesini E_ALL olarak değiştirir. Bunu değiştirmek istiyorsanız, `enable()` adresini çağırdıktan sonra bunu yapın. +Not: Tracy etkinleştirildikten sonra hata raporlama seviyesini E_ALL olarak değiştirir. Bu değeri değiştirmek isterseniz, `enable()` çağrısından sonra yapın. -Geliştirme ve Üretim Modu .[#toc-development-vs-production-mode] -================================================================ +Geliştirme vs Üretim Modu +========================= -Gördüğünüz gibi, Tracy oldukça konuşkan, bu da geliştirme ortamında takdir edilebilirken, üretim sunucusunda bir felakete neden olacaktır. Bunun nedeni, orada hiçbir hata ayıklama bilgisinin görüntülenmemesi gerektiğidir. Bu nedenle Tracy **ortam otomatik algılama** özelliğine sahiptir ve örnek canlı bir sunucuda çalıştırılırsa, hata görüntülenmek yerine günlüğe kaydedilir ve ziyaretçi yalnızca kullanıcı dostu bir mesaj görür: +Gördüğünüz gibi, Tracy oldukça konuşkan, bu geliştirme ortamında takdir edilebilir, ancak üretim sunucusunda tam bir felakete neden olur. Orada hiçbir hata ayıklama bilgisi yazdırılmamalıdır. Bu nedenle Tracy **ortamın otomatik tespiti** özelliğine sahiptir ve örnek canlı bir sunucuda çalıştırılırsa, hata görüntülenmek yerine günlüğe kaydedilir ve ziyaretçi yalnızca kullanıcı dostu bir mesaj görür: [* tracy-error2.webp .{url:-} *] -Üretim kipi, [dump() |dumper] kullanılarak gönderilen tüm hata ayıklama bilgilerinin ve elbette PHP tarafından üretilen tüm hata iletilerinin görüntülenmesini engeller. Yani kodda bazı `dump($obj)` adreslerini unuttuysanız, endişelenmenize gerek yok, üretim sunucusunda hiçbir şey görüntülenmeyecektir. +Üretim modu, [dump() |dumper] kullanarak gönderdiğimiz tüm hata ayıklama bilgilerinin ve tabii ki PHP tarafından üretilen tüm hata mesajlarının görüntülenmesini engeller. Bu nedenle, kodda bazı `dump($obj)` unutmuşsanız endişelenmenize gerek yok, üretim sunucusunda hiçbir şey yazdırılmaz. -Mod otomatik algılama nasıl çalışır? Uygulama localhost üzerinde çalışıyorsa (yani, IP adresi `127.0.0.1` veya `::1`) ve proxy yoksa (yani, HTTP başlığı) mod geliştirmedir. Aksi takdirde, üretim modunda çalışır. +Modun otomatik tespiti nasıl çalışır? Uygulama localhost'ta (yani IP adresi `127.0.0.1` veya `::1`) çalıştırıldığında ve bir proxy mevcut olmadığında (yani HTTP başlığı olmadığında) mod geliştirme modudur. Aksi takdirde üretim modunda çalışır. -Geliştirme modunu diğer durumlarda, örneğin belirli bir IP adresinden erişen geliştiriciler için etkinleştirmek istiyorsanız, bunu `enable()` yönteminin bir parametresi olarak belirtebilirsiniz: +Geliştirme modunu diğer durumlarda da etkinleştirmek istersek, örneğin belirli bir IP adresinden erişen programcılar için, bunu `enable()` metodunun parametresi olarak belirtiriz: ```php -Debugger::enable('23.75.345.200'); // bir dizi IP adresi de sağlayabilirsiniz +Debugger::enable('23.75.345.200'); // IP adresleri dizisi de belirtilebilir ``` -IP adresini bir çerez ile birleştirmenizi kesinlikle öneririz. `tracy-debug` çerezinde `secret1234` gibi gizli bir belirteç saklayın ve bu şekilde, geliştirme modunu yalnızca çerezde belirtilen belirtece sahip olan belirli bir IP adresinden erişen geliştiriciler için etkinleştirin: +IP adresini bir çerezle birleştirmenizi kesinlikle öneririz. `tracy-debug` çerezine gizli bir belirteç, örneğin `secret1234` kaydederiz ve bu şekilde geliştirme modunu yalnızca belirli bir IP adresinden erişen ve çerezde belirtilen belirtece sahip olan programcılar için etkinleştiririz: ```php Debugger::enable('secret1234@23.75.345.200'); ``` -Ayrıca `enable()` yönteminin bir parametresi olarak `Debugger::Development` veya `Debugger::Production` sabitlerini kullanarak geliştirme/üretim modunu doğrudan ayarlayabilirsiniz. +Geliştirme/üretim modunu ayrıca `Debugger::Development` veya `Debugger::Production` sabitini `enable()` metodunun parametresi olarak kullanarak doğrudan ayarlayabiliriz. .[note] -Nette Framework kullanıyorsanız, [bunun için modu |application:bootstrap#Development vs Production Mode] nasıl [ayarlayacağınıza |application:bootstrap#Development vs Production Mode] bir göz atın ve daha sonra Tracy için de kullanılacaktır. +Nette Framework kullanıyorsanız, [onun için modu nasıl ayarlayacağınıza |application:bootstrapping#Geliştirme vs Üretim Modu] bakın ve bu daha sonra Tracy için de kullanılacaktır. -Hata Günlüğü .[#toc-error-logging] -================================== +Hata Günlüklemesi +================= -Üretim modunda, Tracy tüm hataları ve istisnaları otomatik olarak bir metin günlüğüne kaydeder. Günlüğe kaydetme işleminin gerçekleşmesi için, günlük dizininin mutlak yolunu `$logDirectory` değişkenine ayarlamanız veya `enable()` yöntemine ikinci parametre olarak aktarmanız gerekir: +Üretim modunda, Tracy tüm hataları ve yakalanan istisnaları otomatik olarak bir metin günlüğüne kaydeder. Günlüklemenin gerçekleşebilmesi için, günlükleme dizinine mutlak yolu `$logDirectory` değişkenine ayarlamamız veya `enable()` metodunun ikinci parametresi olarak iletmemiz gerekir: ```php Debugger::$logDirectory = __DIR__ . '/log'; ``` -Hata kaydı son derece kullanışlıdır. Uygulamanızın tüm kullanıcılarının aslında ücretsiz olarak hata bulma konusunda birinci sınıf iş yapan beta testçileri olduğunu ve onların değerli raporlarını fark etmeden çöp kutusuna atmanızın aptallık olacağını düşünün. +Hata günlüklemesi son derece yararlıdır. Uygulamanızın tüm kullanıcılarının aslında ücretsiz olarak hata bulmada mükemmel bir iş çıkaran beta testçileri olduğunu hayal edin ve onların değerli raporlarını fark edilmeden çöp kutusuna atarak aptallık yapmış olursunuz. -Kendi mesajlarınızı veya yakalanan istisnaları günlüğe kaydetmeniz gerekiyorsa, `log()` yöntemini kullanın: +Kendi mesajımızı veya yakaladığınız bir istisnayı günlüğe kaydetmemiz gerekirse, bunun için `log()` metodunu kullanırız: ```php -Debugger::log('Beklenmeyen hata'); // metin mesajı +Debugger::log('Beklenmedik bir hata oluştu'); // metin mesajı try { - criticalOperation(); + kritikOperasyon(); } catch (Exception $e) { - Debugger::log($e); // günlük istisnası + Debugger::log($e); // istisna da günlüğe kaydedilebilir // veya - Debugger::log($e, Debugger::ERROR); // ayrıca bir e-posta bildirimi gönderir + Debugger::log($e, Debugger::ERROR); // e-posta bildirimi de gönderir } ``` -If you want Tracy to log PHP errors like `E_NOTICE` or `E_WARNING` with detailed information (HTML report), set `Debugger::$logSeverity`: +Tracy'nin `E_NOTICE` veya `E_WARNING` gibi PHP hatalarını ayrıntılı bilgilerle (HTML raporu) günlüğe kaydetmesini istiyorsanız, `Debugger::$logSeverity` ayarlayın: ```php Debugger::$logSeverity = E_NOTICE | E_WARNING; ``` -Gerçek bir profesyonel için hata günlüğü çok önemli bir bilgi kaynağıdır ve herhangi bir yeni hatadan hemen haberdar olmak ister. Tracy ona yardımcı olur. Her yeni hata kaydı için bir e-posta gönderebilir. Bu e-postaların nereye gönderileceği $email değişkeni ile belirlenir: +Gerçek bir profesyonel için hata günlüğü önemli bir bilgi kaynağıdır ve her yeni hatadan hemen haberdar olmak ister. Tracy bu konuda ona yardımcı olur, çünkü günlükteki yeni bir kayıt hakkında e-posta ile bilgi verebilir. E-postaların nereye gönderileceğini `$email` değişkeni ile belirleriz: ```php Debugger::$email = 'admin@example.com'; ``` -Nette Framework'ün tamamını kullanıyorsanız, bunu ve diğerlerini [yapılandırma dosyasında |nette:configuring] ayarlayabilirsiniz. +Tüm Nette Framework'ü kullanıyorsanız, bunu ve diğer ayarları [yapılandırma dosyasında |nette:configuring] ayarlayabilirsiniz. -E-posta kutunuzu selden korumak için Tracy **sadece bir mesaj** gönderir ve bir dosya oluşturur `email-sent`. Bir geliştirici e-posta bildirimini aldığında, günlüğü kontrol eder, uygulamasını düzeltir ve `email-sent` izleme dosyasını siler. Bu, e-posta gönderimini tekrar etkinleştirir. +Ancak e-posta gelen kutunuzu doldurmamak için her zaman **yalnızca bir mesaj** gönderir ve `email-sent` adlı bir dosya oluşturur. Geliştirici, e-posta bildirimini aldıktan sonra günlüğü kontrol eder, uygulamayı düzeltir ve izleme dosyasını siler, böylece e-posta gönderimi yeniden etkinleştirilir. -Dosyaları Düzenleyicide Açma .[#toc-opening-files-in-the-editor] -================================================================ +Düzenleyicide Açma +================== -Hata sayfası görüntülendiğinde, dosya adlarına tıklayabilirsiniz ve imleç ilgili satırda olacak şekilde editörünüzde açılırlar. Dosyalar da oluşturulabilir (eylem `create file`) veya içlerindeki hatalar düzeltilebilir (eylem `fix it`). Bunu yapmak için [tarayıcıyı ve sistemi yapılandırmanız |open-files-in-ide] gerekir. +Hata sayfası görüntülendiğinde, dosya adlarına tıklayabilirsiniz ve bunlar düzenleyicinizde ilgili satırda imleçle açılır. Ayrıca dosyalar oluşturabilir (eylem `create file`) veya içlerindeki hataları düzeltebilirsiniz (eylem `fix it`). Bunun çalışması için [tarayıcıyı ve sistemi yapılandırmanız |open-files-in-ide] yeterlidir. -Desteklenen PHP Sürümleri .[#toc-supported-php-versions] -======================================================== +Desteklenen PHP Sürümleri +========================= -| Tracy | PHP ile uyumlu -|-----------|-------------------- -| Tracy 2.10 – 3.0 | PHP 8.0 - 8.2 -| Tracy 2.9 | PHP 7.2 - 8.2 -| Tracy 2.8 | PHP 7.2 - 8.1 -| Tracy 2.6 - 2.7 | PHP 7.1 - 8.0 -| Tracy 2.5 | PHP 5.4 - 7.4 -| Tracy 2.4 | PHP 5.4 - 7.2 +| Tracy | PHP ile uyumlu +|-----------|------------------- +| Tracy 2.10 – 3.0 | PHP 8.0 – 8.4 +| Tracy 2.9 | PHP 7.2 – 8.2 +| Tracy 2.8 | PHP 7.2 – 8.1 +| Tracy 2.6 – 2.7 | PHP 7.1 – 8.0 +| Tracy 2.5 | PHP 5.4 – 7.4 +| Tracy 2.4 | PHP 5.4 – 7.2 -En son yama sürümleri için geçerlidir. +Son yama sürümü için geçerlidir. -Limanlar .[#toc-ports] -====================== +Portlar +======= -Bu, diğer çerçevelere ve CMS'ye resmi olmayan bağlantı noktalarının bir listesidir: +Bu, diğer frameworkler ve CMS'ler için resmi olmayan portların bir listesidir: - [Drupal 7](https://www.drupal.org/project/traced) - Laravel framework: [recca0120/laravel-tracy](https://github.com/recca0120/laravel-tracy), [whipsterCZ/laravel-tracy](https://github.com/whipsterCZ/laravel-tracy) diff --git a/tracy/tr/open-files-in-ide.texy b/tracy/tr/open-files-in-ide.texy index b9b4900fb2..eb169b6091 100644 --- a/tracy/tr/open-files-in-ide.texy +++ b/tracy/tr/open-files-in-ide.texy @@ -1,20 +1,20 @@ -Tracy'den Editörde Dosya Nasıl Açılır? (IDE Entegrasyonu) -********************************************************* +Tracy'den dosyayı düzenleyicide nasıl açarım? (IDE ile Entegrasyon) +******************************************************************* .[perex] -Hata sayfası görüntülendiğinde, dosya adlarına tıklayabilirsiniz ve imleç ilgili satırda olacak şekilde editörünüzde açılırlar. Dosyalar da oluşturulabilir (eylem `create file`) veya içlerindeki hatalar düzeltilebilir (eylem `fix it`). Bunu yapmak için tarayıcıyı ve sistemi yapılandırmanız gerekir. +Hata sayfası görüntülendiğinde, dosya adlarına tıklayabilirsiniz ve bunlar düzenleyicinizde ilgili satırda imleçle açılır. Ayrıca dosyalar oluşturabilir (eylem `create file`) veya içlerindeki hataları düzeltebilirsiniz (eylem `fix it`). Bunun gerçekleşmesi için tarayıcıyı ve sistemi yapılandırmanız gerekir. -Tracy, dosyaları `editor://open/?file=%file&line=%line` biçimindeki URL'ler aracılığıyla, yani `editor://` protokolü ile açar. Bunun için kendi işleyicimizi kaydedeceğiz. Bu, parametreleri işleyen ve favori editörümüzü başlatan herhangi bir çalıştırılabilir dosya olabilir. +Tracy, dosyaları `editor://open/?file=%file&line=%line` biçimindeki URL'ler aracılığıyla açar, yani `editor://` protokolü ile. Bunun için kendi işleyicimizi kaydedeceğiz. Bu, parametreleri işleyen ve favori düzenleyicimizi başlatan herhangi bir yürütülebilir dosya olabilir. -URL'yi `Tracy\Debugger::$editor` değişkeninde değiştirebilir veya `Tracy\Debugger::$editor = null` adresini ayarlayarak tıklamayı devre dışı bırakabilirsiniz. +URL'yi `Tracy\Debugger::$editor` değişkeninde değiştirebilir veya `Tracy\Debugger::$editor = null` ayarlayarak tıklamayı devre dışı bırakabilirsiniz. -Pencereler .[#toc-windows] -========================== +Windows +======= -1. Uygun dosyaları "Tracy deposundan":https://github.com/nette/tracy/tree/master/tools/open-in-editor/windows diske indirin. +1. İlgili dosyaları "Tracy deposundan":https://github.com/nette/tracy/tree/master/tools/open-in-editor/windows diske indirin. -2. `open-editor.js` adresini düzenleyin ve `settings` adresindeki düzenleyicinizin yolunu kaldırın veya düzenleyin: +2. `open-editor.js` dosyasını düzenleyin ve `settings` dizisinde düzenleyicinizin yolunu yorumdan çıkarın ve gerekirse değiştirin: ```js var settings = { @@ -35,19 +35,28 @@ var settings = { ... ``` -Dikkatli olun ve çift eğik çizgileri yollarda tutun. +Dikkat, yollarda çift ters eğik çizgi bırakın. -3. `editor://` protokolü için işleyiciyi sisteme kaydedin. +3. Sistemde `editor://` protokolü için işleyiciyi kaydedin. -Bu işlem `install.cmd` çalıştırılarak yapılır. **Yönetici olarak çalıştırmanız gerekir.** `open-editor.js` betiği artık `editor://` protokolüne hizmet edecektir. +Bunu `install.cmd` dosyasını çalıştırarak yaparsınız. **Yönetici olarak çalıştırılması gerekir.** `open-editor.js` betiği artık `editor://` protokolünü işleyecektir. +Canlı sunucu veya Docker gibi başka sunucularda oluşturulan bağlantıları açabilmek için, `open-editor.js` dosyasına uzak URL'nin yerel URL'ye eşlemesini de ekleyin: -Linux .[#toc-linux] -=================== +```js + mappings: { + // uzak yol: yerel yol + '/var/www/nette.app': 'W:\\Nette.web\\_web', + } +``` + + +Linux +===== -1. Uygun dosyaları "Tracy deposundan":https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux `~/bin` dizinine indirin. +1. İlgili dosyaları "Tracy deposundan":https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux `~/bin` dizinine indirin. -2. `open-editor.sh` adresini düzenleyin ve `editor` değişkenindeki düzenleyicinizin yolunu kaldırın veya düzenleyin: +2. `open-editor.sh` dosyasını düzenleyin ve `editor` değişkeninde düzenleyicinizin yolunu yorumdan çıkarın ve gerekirse değiştirin. ```shell #!/bin/bash @@ -67,24 +76,25 @@ Linux .[#toc-linux] ... ``` -Çalıştırılabilir hale getirin: +Dosyayı yürütülebilir yapın: ```shell chmod +x ~/bin/open-editor.sh ``` -Kullandığınız editör paketten yüklenmemişse, ikili dosyada muhtemelen `$PATH` adresinde bir yol bulunmayacaktır. Bu kolayca düzeltilebilir. `~/bin` dizininde, düzenleyici ikili dosyasına bir sembolik bağlantı oluşturun. .[note] +.[note] +Kullanılan düzenleyici bir paketten yüklenmemişse, ikili dosyanın muhtemelen $PATH içinde bir yolu olmayacaktır. Bu kolayca düzeltilebilir. `~/bin` dizininde, düzenleyicinin ikili dosyasına bir sembolik bağlantı oluşturun. -3. `editor://` protokolü için işleyiciyi sisteme kaydedin. +3. Sistemde `editor://` protokolü için işleyiciyi kaydedin. -Bu işlem `install.sh` çalıştırılarak yapılır. `open-editor.js` betiği artık `editor://` protokolüne hizmet edecektir. +Bunu `install.sh` dosyasını çalıştırarak yaparsınız. `open-editor.sh` betiği artık `editor://` protokolünü işleyecektir. -macOS .[#toc-macos] -=================== +macOS +===== -PhpStorm, TextMate vb. editörler, dosyaları sadece ayarlamanız gereken özel bir URL aracılığıyla açmanıza izin verir: +PhpStorm, TextMate vb. düzenleyiciler, dosyaları özel URL'ler aracılığıyla açmaya izin verir, bu URL'yi ayarlamak yeterlidir: ```php // PhpStorm @@ -92,44 +102,43 @@ Tracy\Debugger::$editor = 'phpstorm://open?file=%file&line=%line'; // TextMate Tracy\Debugger::$editor = 'txmt://open/?url=file://%file&line=%line'; // MacVim -Tracy\Debugger::$editor = 'mvim://open/?url=file://%file&line=%line'; +Tracy\Debugger::$editor = 'mvim://open?url=file:///%file&line=%line'; // Visual Studio Code Tracy\Debugger::$editor = 'vscode://file/%file:%line'; ``` -Bağımsız Tracy kullanıyorsanız, satırı `Tracy\Debugger::enable()` adresinden önce, Nette kullanıyorsanız `Bootstrap.php` adresindeki `$configurator->enableTracy()` adresinden önce koyun. +Bağımsız Tracy kullanıyorsanız, satırı `Tracy\Debugger::enable()`'dan önce, Nette kullanıyorsanız `Bootstrap.php`'deki `$configurator->enableTracy()`'den önce ekleyin. -Ne yazık ki `create file` veya `fix it` eylemleri macOS üzerinde çalışmaz. +`create file` veya `fix it` eylemleri maalesef macOS'ta çalışmaz. -Demolar .[#toc-demos] -===================== +Örnekler +======== -Hata düzeltiliyor: +Hata düzeltme: -Yeni bir dosya oluşturuluyor: +Dosya oluşturma: -Sorun Giderme .[#toc-troubleshooting] -===================================== +Sorun Giderme +============= -- Firefox'ta about:config'te `network.protocol-handler.expose.editor` 'u `false` ve `network.protocol-handler.expose-all` 'yi `true` olarak ayarlayarak özel protokol yürütülmesine [izin |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] vermeniz gerekebilir. Ancak varsayılan olarak buna izin verilmelidir. -- Her şey hemen çalışmıyorsa panik yapmayın. Sayfayı yenilemeyi, tarayıcıyı veya bilgisayarı yeniden başlatmayı deneyin. Bu yardımcı olacaktır. -- Düzeltmek için [buraya |https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/] bakın: - Giriş Hatası: Dosya uzantısı ".js" için komut dosyası motoru yok Belki de ".js" dosyasını JScript motoruyla değil başka bir uygulamayla ilişkilendirdiniz. +- Firefox'ta, about:config içinde `network.protocol-handler.expose.editor` öğesini `false` ve `network.protocol-handler.expose-all` öğesini `true` olarak [ayarlayarak |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] protokolü etkinleştirmeniz gerekebilir. +- Hemen çalışmazsa panik yapmayın ve bağlantıya tıklamadan önce sayfayı birkaç kez yenilemeyi deneyin. Çalışacaktır! +- İşte olası bir hatayı düzeltmek için bir [bağlantı |https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/]: `Input Error: There is no script engine for file extension ".js"`, `Maybe you associated ".js" file to another app, not Jscript engine.` veya `".js" uzantısı için komut dosyası motoru yok`. -Google Chrome sürüm 77'den itibaren, düzenleyici bir bağlantı aracılığıyla açıldığında artık "Bu tür bağlantıları her zaman ilişkili uygulamada aç" onay kutusunu görmeyeceksiniz. Windows için geçici çözüm: `fix.reg` dosyasını oluşturun: +Google Chrome sürüm 77'den itibaren, düzenleyici bir bağlantı aracılığıyla başlatıldığında "Bu tür bağlantıları her zaman ilişkili uygulamada aç" onay kutusunu görmezsiniz. Windows için çözüm: `fix.reg` adlı bir dosya oluşturun: ``` Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\URLWhitelist] "123"="editor://*" ``` -Çift tıklayarak içe aktarın ve Chrome'u yeniden başlatın. +Çift tıklayarak içe aktarın ve Chrome tarayıcısını yeniden başlatın. -Daha fazla sorun veya soru olması durumunda, [forumda |https://forum.nette.org] sorun. +Herhangi bir sorunuz veya yorumunuz varsa, lütfen [foruma |https://forum.nette.org] başvurun. diff --git a/tracy/tr/recipes.texy b/tracy/tr/recipes.texy index 31495801b3..9f17d2fe9c 100644 --- a/tracy/tr/recipes.texy +++ b/tracy/tr/recipes.texy @@ -1,14 +1,13 @@ -Yemek Tarifleri -*************** +Nasıl Yapılır Kılavuzları +************************* -İçerik Güvenliği Politikası .[#toc-content-security-policy] -=========================================================== +İçerik Güvenlik Politikası (Content Security Policy) +==================================================== -Siteniz İçerik Güvenliği İlkesi kullanıyorsa, şunları eklemeniz gerekir `'nonce-'` ve Tracy'nin düzgün çalışması için `'strict-dynamic'` adresinden `script-src` adresine gidin. Bazı 3. eklentiler ek yönergeler gerektirebilir. -Nonce, `style-src` yönergesinde desteklenmez, bu yönergeyi kullanırsanız `'unsafe-inline'` eklemeniz gerekir, ancak üretim modunda bundan kaçınılmalıdır. +Web siteniz İçerik Güvenlik Politikası kullanıyorsa, Tracy'nin düzgün çalışması için `script-src`'ye aynı `'nonce-'` ve `'strict-dynamic'` eklemeniz gerekecektir. Bazı üçüncü taraf eklentiler ek ayarlar gerektirebilir. Nonce, `style-src` yönergesinde desteklenmez; bu yönergeyi kullanıyorsanız `'unsafe-inline'` eklemeniz gerekir, ancak üretim modunda bundan kaçınmalısınız. -[Nette Framework |nette:configuring] için yapılandırma örneği: +[Nette Framework |nette:configuring] için örnek yapılandırma: ```neon http: @@ -24,17 +23,16 @@ header("Content-Security-Policy: script-src 'nonce-$nonce' 'strict-dynamic';"); ``` -Daha Hızlı Yükleme .[#toc-faster-loading] -========================================= +Daha Hızlı Yükleme +================== -Temel entegrasyon basittir, ancak web sayfasında yavaş engelleyen komut dosyalarınız varsa, Tracy'nin yüklenmesini yavaşlatabilirler. -Çözüm yerleştirmektir `` herhangi bir komut dosyasından önce şablonunuza yerleştirin: +Başlatma basittir, ancak web sayfanızda yavaş yüklenen engelleyici betikler varsa, Tracy'nin yüklenmesini yavaşlatabilirler. Çözüm, `` öğesini şablonunuza tüm betiklerden önce yerleştirmektir: ```latte - ...<title> + <title>... @@ -42,12 +40,37 @@ Temel entegrasyon basittir, ancak web sayfasında yavaş engelleyen komut dosyal ``` -AJAX ve Yönlendirilmiş İstekler .[#toc-ajax-and-redirected-requests] -==================================================================== +AJAX İsteklerinde Hata Ayıklama +=============================== -Tracy, AJAX istekleri ve yönlendirmeleri için Hata Ayıklama çubuğu ve Mavi Ekranlar görüntüleyebilir. Tracy kendi oturumlarını oluşturur, verileri kendi geçici dosyalarında saklar ve bir `tracy-session` çerezi kullanır. +Tracy, jQuery veya yerel `fetch` API kullanılarak oluşturulan AJAX isteklerini otomatik olarak yakalar. İstekler, Tracy çubuğunda ek satırlar olarak görüntülenir, bu da AJAX'ın kolay ve rahat bir şekilde hata ayıklanmasını sağlar. -Tracy, Tracy açılmadan önce başlatılan yerel bir PHP oturumu kullanacak şekilde de yapılandırılabilir: +AJAX isteklerini otomatik olarak yakalamak istemiyorsanız, JavaScript değişkenini ayarlayarak bu özelliği devre dışı bırakabilirsiniz: + +```js +window.TracyAutoRefresh = false; +``` + +Belirli AJAX isteklerini manuel olarak izlemek için, `Tracy.getAjaxHeader()` tarafından döndürülen değerle `X-Tracy-Ajax` HTTP başlığını ekleyin. İşte `fetch` fonksiyonuyla bir kullanım örneği: + +```js +fetch(url, { + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Tracy-Ajax': Tracy.getAjaxHeader(), + } +}) +``` + +Bu yaklaşım, AJAX isteklerinin seçici olarak hata ayıklanmasını sağlar. + + +Veri Depolama +============= + +Tracy, AJAX istekleri ve yönlendirmeler için Tracy çubuğunda ve Bluescreen'lerde panelleri görüntüleyebilir. Tracy kendi oturumunu oluşturur, verileri kendi geçici dosyalarında saklar ve `tracy-session` çerezini kullanır. + +Tracy, Tracy'yi açmadan önce başlattığımız yerel PHP oturumunu kullanacak şekilde de yapılandırılabilir: ```php session_start(); @@ -55,44 +78,44 @@ Debugger::setSessionStorage(new Tracy\NativeSession); Debugger::enable(); ``` -Bir oturumun başlatılması daha karmaşık bir başlatma gerektiriyorsa, Tracy'yi hemen başlatabilir (böylece meydana gelen hataları ele alabilir) ve ardından oturum işleyicisini başlatabilir ve son olarak Tracy'ye `dispatch()` işlevini kullanarak oturumun kullanıma hazır olduğunu bildirebilirsiniz: +Oturumun başlatılması daha karmaşık bir başlatma gerektiriyorsa, Tracy'yi hemen başlatabilir (oluşabilecek hataları işleyebilmesi için), ardından oturum işleyicisini başlatabilir ve son olarak `dispatch()` fonksiyonunu kullanarak oturumun kullanıma hazır olduğunu Tracy'ye bildirebilirsiniz: ```php Debugger::setSessionStorage(new Tracy\NativeSession); Debugger::enable(); -// ardından oturum başlatma -// ve oturumu başlatın +// oturum başlatma işlemi takip eder +// ve oturumun başlatılması session_start(); Debugger::dispatch(); ``` -`setSessionStorage()` işlevi 2.9 sürümünden beri mevcuttur, bundan önce Tracy her zaman yerel PHP oturumunu kullanmıştır. +`setSessionStorage()` fonksiyonu sürüm 2.9'dan beri mevcuttur, daha önce Tracy her zaman yerel PHP oturumunu kullanıyordu. -Özel Yıkayıcı .[#toc-custom-scrubber] -===================================== +Özel Temizleyici (Scrubber) +=========================== -Scrubber, parolalar veya kimlik bilgileri gibi hassas verilerin dökümlerden sızmasını önleyen bir filtredir. Filtre, dökülen dizi veya nesnenin her öğesi için çağrılır ve değer hassas ise `true` döndürür. Bu durumda, değer yerine `*****` yazdırılır. +Scrubber, döküm sırasında parolalar veya erişim kimlik bilgileri gibi hassas verilerin sızmasını önleyen bir filtredir. Filtre, dökümü yapılan dizinin veya nesnenin her öğesi için çağrılır ve değer hassas ise `true` döndürür. Bu durumda, değer yerine `*****` yazdırılır. ```php -// anahtar değerlerinin ve `password` gibi özelliklerin dökülmesini önler, -// `password_repeat`, `check_password`, `DATABASE_PASSWORD`, vb. +// `password`, `password_repeat`, `check_password`, `DATABASE_PASSWORD` gibi anahtarların +// ve özelliklerin değerlerinin yazdırılmasını engeller. $scrubber = function(string $key, $value, ?string $class): bool { return preg_match('#password#i', $key) && $value !== null; }; -// BlueScreen içindeki tüm dökümler için kullanırız +// BlueScreen içindeki tüm dökümler için kullanacağız Tracy\Debugger::getBlueScreen()->scrubber = $scrubber; ``` -Özel Kaydedici .[#toc-custom-logger] -==================================== +Özel Logger +=========== -Hataları, yakalanmamış istisnaları günlüğe kaydetmek ve `Tracy\Debugger::log()` tarafından çağrılmak üzere özel bir logger oluşturabiliriz. Logger, [api:Tracy\ILogger] arayüzünü uygular. +Hataları, yakalanmamış istisnaları günlüğe kaydedecek ve ayrıca `Tracy\Debugger::log()` metodu tarafından çağrılacak kendi logger'ımızı oluşturabiliriz. Logger, [api:Tracy\ILogger] arayüzünü uygular. ```php use Tracy\ILogger; @@ -101,18 +124,18 @@ class SlackLogger implements ILogger { public function log($value, $priority = ILogger::INFO) { - // Slack'e bir istek gönderir + // Slack'e istek gönderir } } ``` -Ve sonra onu etkinleştiririz: +Ve ardından etkinleştiririz: ```php Tracy\Debugger::setLogger(new SlackLogger); ``` -Eğer Nette Framework'ün tamamını kullanırsak, bunu NEON yapılandırma dosyasında ayarlayabiliriz: +Tam Nette Framework kullanıyorsak, bunu yapılandırma NEON dosyasında ayarlayabilirsiniz: ```neon services: @@ -120,10 +143,10 @@ services: ``` -Monolog Entegrasyonu .[#toc-monolog-integration] ------------------------------------------------- +Monolog Entegrasyonu +-------------------- -Tracy paketi, [monolog/monolog](https://github.com/Seldaek/monolog) entegrasyonuna izin veren bir PSR-3 adaptörü sağlar. +Tracy paketi, [monolog/monolog](https://github.com/Seldaek/monolog) entegrasyonunu sağlayan bir PSR-3 adaptörü sunar. ```php $monolog = new Monolog\Logger('main-channel'); @@ -133,21 +156,21 @@ $tracyLogger = new Tracy\Bridges\Psr\PsrToTracyLoggerAdapter($monolog); Debugger::setLogger($tracyLogger); Debugger::enable(); -Debugger::log('info'); // yazıyor: [] main-channel.INFO: info [] [] -Debugger::log('warning', Debugger::WARNING); // yazıyor: [] main-channel.WARNING: uyarı [] [] +Debugger::log('info'); // yazar: [] main-channel.INFO: info [] [] +Debugger::log('warning', Debugger::WARNING); // yazar: [] main-channel.WARNING: warning [] [] ``` -nginx .[#toc-nginx] -=================== +nginx +===== -Eğer Tracy nginx üzerinde çalışmıyorsa, muhtemelen yanlış yapılandırılmıştır. Eğer şöyle bir şey varsa +Tracy nginx sunucusunda çalışmıyorsa, muhtemelen yanlış yapılandırılmıştır. Yapılandırmada şöyle bir şey varsa: ```nginx try_files $uri $uri/ /index.php; ``` -olarak değiştirin +şuna değiştirin: ```nginx try_files $uri $uri/ /index.php$is_args$args; diff --git a/tracy/tr/stopwatch.texy b/tracy/tr/stopwatch.texy index 1c4a846b0d..bdebc51055 100644 --- a/tracy/tr/stopwatch.texy +++ b/tracy/tr/stopwatch.texy @@ -1,35 +1,35 @@ -Kronometre -********** +Zaman Ölçümü +************ -Bir başka yararlı araç da mikrosaniye hassasiyetli hata ayıklayıcı kronometresidir: +Hata ayıklayıcının bir diğer yararlı aracı, mikrosaniye hassasiyetinde bir kronometredir: ```php Debugger::timer(); -// tatlı rüyalar tatlım +// küçük prensim uyu, kuşlar tatlı tatlı uyuyor... sleep(2); $elapsed = Debugger::timer(); // $elapsed = 2 ``` -İsteğe bağlı bir parametre ile aynı anda birden fazla ölçüm gerçekleştirilebilir. +İsteğe bağlı bir parametre ile birden fazla ölçüm yapılabilir. ```php Debugger::timer('page-generating'); -// bazı kodlar +// biraz kod Debugger::timer('rss-generating'); -// bazı kodlar +// biraz kod $rssElapsed = Debugger::timer('rss-generating'); $pageElapsed = Debugger::timer('page-generating'); ``` ```php -Debugger::timer(); // zamanlayıcıyı çalıştırır +Debugger::timer(); // kronometreyi başlatır -... // bazı zaman alıcı işlemler +... // zaman alan işlem -echo Debugger::timer(); // saniye cinsinden geçen süre +echo Debugger::timer(); // geçen süreyi saniye cinsinden yazdırır ``` diff --git a/tracy/uk/@home.texy b/tracy/uk/@home.texy index 0f6a634882..c020c51023 100644 --- a/tracy/uk/@home.texy +++ b/tracy/uk/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Tracy - обов'язковий інструмент налагодження для всіх PHP-розробників}} -{{description: Tracy - це інструмент, призначений для полегшення налагодження PHP-коду. Це корисний помічник для всіх PHP-програмістів, який допомагає наочно візуалізувати і протоколювати помилки, дамп змінних і багато іншого. Попередження: Tracy викликає залежність!}} +{{maintitle: Tracy – інструмент налагодження, з яким помилятися — це радість}} +{{description: Tracy — це інструмент, призначений для полегшення налагодження PHP-коду. Це корисний помічник для всіх програмістів PHP, який допомагає візуалізувати та логувати помилки, дампити змінні та багато іншого. Увага: Tracy викликає залежність!}} diff --git a/tracy/uk/@left-menu.texy b/tracy/uk/@left-menu.texy index c975fb7b33..fbc11fbf40 100644 --- a/tracy/uk/@left-menu.texy +++ b/tracy/uk/@left-menu.texy @@ -1,7 +1,7 @@ -- [Початок роботи |Guide] -- [Дампер |Dumper] -- [Секундомір |Stopwatch] -- [Конфігурування |Configuring] -- [Рецепти |Recipes] +- [Починаємо з Tracy |guide] +- [Дампінг |dumper] +- [Вимірювання часу |stopwatch] +- [Конфігурація |configuring] +- [Посібники |recipes] - [Інтеграція з IDE |open-files-in-ide] - [Створення розширень |extensions] diff --git a/tracy/uk/@menu.texy b/tracy/uk/@menu.texy index e58610d4b1..73b26e6753 100644 --- a/tracy/uk/@menu.texy +++ b/tracy/uk/@menu.texy @@ -1,3 +1,3 @@ -- [Головна сторінка|@home] -- [Документація |Guide] +- [Вступ |@home] +- [Документація |guide] - "GitHub .[link-external]":https://github.com/nette/tracy diff --git a/tracy/uk/@meta.texy b/tracy/uk/@meta.texy new file mode 100644 index 0000000000..f50e3b03b0 --- /dev/null +++ b/tracy/uk/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документація Tracy}} diff --git a/tracy/uk/configuring.texy b/tracy/uk/configuring.texy index 9c38ca4f4a..e35712b4ce 100644 --- a/tracy/uk/configuring.texy +++ b/tracy/uk/configuring.texy @@ -1,169 +1,180 @@ -Конфігурація Трейсі -******************* +Конфігурація Tracy +****************** -Наступні приклади припускають, що визначено такий псевдонім класу: +Усі приклади передбачають створений псевдонім: ```php use Tracy\Debugger; ``` -Протоколювання помилок .[#toc-error-logging] --------------------------------------------- +Логування помилок +----------------- ```php $logger = Debugger::getLogger(); -// якщо сталася помилка, то повідомлення надсилається на цей email -$logger->email = 'dev@example.com'; // (string|string[]) за замовчуванням unset +// електронна пошта, на яку надсилаються сповіщення про помилку +$logger->email = 'dev@example.com'; // (string|string[]) за замовчуванням не встановлено // відправник електронної пошти -$logger->fromEmail = 'me@example.com'; // (string) defaults to unset +$logger->fromEmail = 'me@example.com'; // (string) за замовчуванням не встановлено -// процедура надсилання електронної пошти -$logger->mailer = /* ... */; // (викликається) за замовчуванням відправлення поштою mail() +// процедура, що забезпечує надсилання електронної пошти +$logger->mailer = /* ... */; // (callable) за замовчуванням надсилання функцією mail() -// через який найменший час відправити ще один лист? -$logger->emailSnooze = /* ... */; // (рядок) за замовчуванням '2 дні' +// через який найкоротший час надіслати наступний лист? +$logger->emailSnooze = /* ... */; // (string) за замовчуванням '2 days' -// для яких рівнів помилок BlueScreen також ведеться журнал? -Debugger::$logSeverity = E_WARNING | E_NOTICE; // за замовчуванням 0 (рівень помилок відсутній) +// для яких рівнів помилок також логується BlueScreen? +Debugger::$logSeverity = E_WARNING | E_NOTICE; // за замовчуванням 0 (немає рівнів помилок) ``` -`dump()` Поведінка .[#toc-dump-behavior] ----------------------------------------- +Поведінка `dump()` +------------------ ```php // максимальна довжина рядка -Debugger::$maxLength = 150; // (int) за замовчуванням згідно з Трейсі +Debugger::$maxLength = 150; // (int) за замовчуванням відповідно до версії Tracy -// наскільки глибоким буде список -Debugger::$maxDepth = 10; // (int) за замовчуванням згідно з Tracy +// максимальна глибина вкладеності +Debugger::$maxDepth = 10; // (int) за замовчуванням відповідно до версії Tracy -// приховувати значення цих ключів (починаючи з версії Tracy 2.8) -Debugger::$keysToHide = ['password', /* ... */]; // (string[]) за замовчуванням [] +// приховати значення цих ключів (з Tracy 2.8) +Debugger::$keysToHide = ['password', /* ... */]; // (string[]) за замовчуванням [] -// візуальна тема (починаючи з версії Tracy 2.8) -Debugger::$dumpTheme = 'dark'; // (light|dark) за замовчуванням 'light' +// візуальна тема (з Tracy 2.8) +Debugger::$dumpTheme = 'dark'; // (light|dark) за замовчуванням 'light' -// відображає місце, де було викликано dump()? -Debugger::$showLocation = /* ... */; // (bool) за замовчуванням відповідно до Tracy +// показати місце, де була викликана функція dump()? +Debugger::$showLocation = /* ... */; // (bool) за замовчуванням відповідно до версії Tracy ``` -Інші .[#toc-others] -------------------- +Інше +---- ```php -// у режимі розробки ви будете бачити сповіщення або попередження про помилки як BlueScreen -Debugger::$strictMode = /* ... ... */; // (bool|int) за замовчуванням false, ви можете вибрати тільки певні рівні помилок (наприклад, E_USER_DEPRECATED | E_DEPRECATED) +// у режимі розробки відображає помилки типу notice або warning як BlueScreen +Debugger::$strictMode = /* ... */; // (bool|int) за замовчуванням false, можна вибрати лише деякі рівні помилок (наприклад, E_USER_DEPRECATED | E_DEPRECATED) -// відображає беззвучні (@) повідомлення про помилки -Debugger::$scream = /* ... */; // (bool|int) за замовчуванням false, з версії 2.9 можна вибрати тільки певні рівні помилок (наприклад, E_USER_DEPRECATED | E_DEPRECATED) +// показувати приглушені (@) повідомлення про помилки? +Debugger::$scream = /* ... */; // (bool|int) за замовчуванням false, з версії 2.9 можна вибрати лише деякі рівні помилок (наприклад, E_USER_DEPRECATED | E_DEPRECATED) // формат посилання для відкриття в редакторі -Debugger::$editor = /* ... */; // (string|null) за замовчуванням 'editor://open/?file=%file&line=%line' +Debugger::$editor = /* ... */; // (string|null) за замовчуванням 'editor://open/?file=%file&line=%line' -// шлях до шаблону з користувацькою сторінкою для помилки 500 -Debugger::$errorTemplate = /* ... ... */; // (рядок) за замовчуванням не задано +// шлях до шаблону з власною сторінкою для помилки 500 +Debugger::$errorTemplate = /* ... */; // (string) за замовчуванням не встановлено -// показувати панель трейсів? -Debugger::$showBar = /* ... */; // (bool) за замовчуванням true +// показувати Tracy Bar? +Debugger::$showBar = /* ... */; // (bool) за замовчуванням true Debugger::$editorMapping = [ - // original => new + // оригінал => новий '/var/www/html' => '/data/web', '/home/web' => '/srv/html', ]; ``` -Nette Framework .[#toc-nette-framework] ---------------------------------------- +Nette Framework +--------------- -Якщо ви використовуєте Nette Framework, ви також можете налаштувати Tracy і додати нові панелі на панель Tracy Bar за допомогою файлу конфігурації. -У конфігурації можна задати параметри Tracy, а також додати нові панелі на панель Tracy. Ці параметри застосовуються тільки після створення контейнера DI, тому помилки, що виникли раніше, не можуть їх відобразити. +Якщо ви використовуєте Nette Framework, ви можете налаштувати Tracy та додати нові панелі до Tracy Bar також за допомогою конфігураційного файлу. У конфігурації можна встановлювати параметри, а також додавати нові панелі до Tracy Bar. Ці налаштування застосовуються лише після створення DI-контейнера, тому помилки, що виникли до цього, не можуть їх відображати. -Конфігурація реєстрації помилок: +Конфігурація логування помилок: ```neon tracy: - # якщо сталася помилка, повідомлення надсилається на цей email - email: dev@example.com # (string|string[]) за замовчуванням unset + # електронна пошта, на яку надсилаються сповіщення про помилку + email: dev@example.com # (string|string[]) за замовчуванням не встановлено # відправник електронної пошти - fromEmail: robot@example.com # (string) за замовчуванням unset + fromEmail: robot@example.com # (string) за замовчуванням не встановлено - # період відстрочки надсилання листів (починаючи з версії Tracy 2.8.8) - emailSnooze: ... # (рядок) за замовчуванням '2 дні' + # час відкладення надсилання електронних листів (з Tracy 2.8.8) + emailSnooze: ... # (string) за замовчуванням '2 days' - # використовувати поштовик, визначений у конфігурації? (починаючи з Tracy 2.5) + # використовувати для надсилання електронних листів Nette mailer? (з Tracy 2.5) netteMailer: ... # (bool) за замовчуванням true - # для яких рівнів помилок BlueScreen також записується в журнал? - logSeverity: [E_WARNING, E_NOTICE] # за замовчуванням []. + # для яких рівнів помилок також логується BlueScreen? + logSeverity: [E_WARNING, E_NOTICE] # за замовчуванням [] ``` -Конфігурація для функції `dump()`: +Конфігурація поведінки функції `dump()`: ```neon tracy: # максимальна довжина рядка - maxLength: 150 # (int) за замовчуванням відповідно до Tracy + maxLength: 150 # (int) за замовчуванням відповідно до версії Tracy - # наскільки глибоким буде список - maxDepth: 10 # (int) за замовчуванням відповідно до Tracy + # максимальна глибина вкладеності + maxDepth: 10 # (int) за замовчуванням відповідно до версії Tracy - # приховувати значення цих ключів (починаючи з Tracy 2.8) + # приховати значення цих ключів (з Tracy 2.8) keysToHide: [password, pass] # (string[]) за замовчуванням [] - # візуальна тема (починаючи з Tracy 2.8) + # візуальна тема (з Tracy 2.8) dumpTheme: dark # (light|dark) за замовчуванням 'light' - # відображає місце, де було викликано функцію dump()? - showLocation: ... # (bool) за замовчуванням згідно з Tracy + # показати місце, де була викликана функція dump()? + showLocation: ... # (bool) за замовчуванням відповідно до версії Tracy ``` -Щоб встановити розширення Tracy: +Встановлення розширень Tracy: ```neon tracy: - # додає бари до бару Tracy + # додає панелі до Tracy Bar bar: - Nette\Bridges\DITracy\ContainerPanel - IncludePanel - XDebugHelper('myIdeKey') - MyPanel(@MyService) - # додаємо панелі до BlueScreen + # додає панелі до BlueScreen blueScreen: - DoctrinePanel::renderException ``` -Інші варіанти: +Інші опції: ```neon tracy: - # у режимі розробки ви побачите сповіщення або попередження про помилки як BlueScreen + # у режимі розробки відображає помилки типу notice або warning як BlueScreen strictMode: ... # за замовчуванням true - # відображає беззвучні (@) повідомлення про помилки + # показувати приглушені (@) повідомлення про помилки? scream: ... # за замовчуванням false # формат посилання для відкриття в редакторі - editor: ... # (рядок) за замовчуванням 'editor://open/?file=%file&line=%line' + editor: ... # (string) за замовчуванням 'editor://open/?file=%file&line=%line' - # шлях до шаблону з користувацькою сторінкою для помилки 500 - errorTemplate: ... # (рядок) за замовчуванням unset + # шлях до шаблону з власною сторінкою для помилки 500 + errorTemplate: ... # (string) за замовчуванням не встановлено - # показує панель трейсингу? + # показувати Tracy Bar? showBar: ... # (bool) за замовчуванням true editorMapping: - # original: new + # оригінал: новий /var/www/html: /data/web /home/web: /srv/html ``` -Значення опцій `logSeverity`, `strictMode` і `scream` можуть бути записані у вигляді масиву рівнів помилок (наприклад. `[E_WARNING, E_NOTICE]`) або як вираз, що використовується в PHP (наприклад, `E_ALL & ~E_NOTICE`). +Значення опцій `logSeverity`, `strictMode` та `scream` можна записувати як масив рівнів помилок (наприклад, `[E_WARNING, E_NOTICE]`), або як вираз, що використовується в мові PHP (наприклад, `E_ALL & ~E_NOTICE`). + + +Сервіси DI +---------- + +Ці сервіси додаються до DI-контейнера: + +| Назва | Тип | Опис +|---------------------------------------------------------- +| `tracy.logger` | [api:Tracy\ILogger] | logger +| `tracy.blueScreen` | [api:Tracy\BlueScreen] | BlueScreen +| `tracy.bar` | [api:Tracy\Bar] | Tracy Bar diff --git a/tracy/uk/dumper.texy b/tracy/uk/dumper.texy index fb9d06500b..8af511a6d8 100644 --- a/tracy/uk/dumper.texy +++ b/tracy/uk/dumper.texy @@ -1,7 +1,7 @@ -Самоскид -******** +Дампінг +******* -Кожен розробник налагоджувальних програм добре знайомий з функцією `var_dump`, яка детально перераховує весь вміст будь-якої змінної. На жаль, її виведення не має HTML-форматування і виводить дамп в один рядок HTML-коду, не кажучи вже про контекстне екранування. Необхідно замінити `var_dump` на більш зручну функцію. Саме такою функцією і є `dump()`. +Кожен налагоджувач є добрим другом функції [php:var_dump], яка детально виводить вміст змінної. На жаль, у середовищі HTML вивід втрачає форматування і зливається в один рядок, не кажучи вже про санітизацію HTML-коду. На практиці необхідно замінити `var_dump` на зручнішу функцію. Саме такою є `dump()`. ```php $arr = [10, 20.2, true, null, 'hello']; @@ -10,11 +10,11 @@ dump($arr); // або Debugger::dump($arr); ``` -генерує виведення: +генерує вивід: [* dump-basic.webp *] -Ви можете змінити світлу тему за замовчуванням на темну: +Стандартну світлу тему можна змінити на темну: ```php Debugger::$dumpTheme = 'dark'; @@ -22,27 +22,27 @@ Debugger::$dumpTheme = 'dark'; [* dump-dark.webp *] -Ви також можете змінити глибину вкладеності за адресою `Debugger::$maxDepth` і довжину відображуваних рядків за адресою `Debugger::$maxLength`. Природно, менші значення прискорюють рендеринг Tracy. +Далі ми можемо змінити глибину вкладеності за допомогою [Debugger::$maxDepth |api:Tracy\Debugger::$maxDepth] та довжину відображуваних міток за допомогою [Debugger::$maxLength |api:Tracy\Debugger::$maxLength]. Нижчі значення природно прискорять налагодження. ```php Debugger::$maxDepth = 2; // за замовчуванням: 3 Debugger::$maxLength = 50; // за замовчуванням: 150 ``` -Функція `dump()` може відображати й іншу корисну інформацію. `Tracy\Dumper::LOCATION_SOURCE` додає підказку, що спливає, з шляхом до файлу, у якому було викликано функцію. `Tracy\Dumper::LOCATION_LINK` додає посилання на файл. `Tracy\Dumper::LOCATION_CLASS` додає підказку, що спливає, до кожного об'єкта дампа, що містить шлях до файлу, у якому визначено клас об'єкта. Усі ці константи можуть бути встановлені у змінній `Debugger::$showLocation` перед викликом функції `dump()`. Ви можете встановити кілька значень одночасно, використовуючи оператор `|`. +Функція `dump()` може виводити й іншу корисну інформацію. Константа `Tracy\Dumper::LOCATION_SOURCE` додає підказку зі шляхом до місця, де була викликана функція. `Tracy\Dumper::LOCATION_LINK` надає нам посилання на це місце. `Tracy\Dumper::LOCATION_CLASS` для кожного дампованого об'єкта виводить підказку зі шляхом до файлу, в якому визначено його клас. Константи встановлюються у змінну `Debugger::$showLocation` ще до виклику `dump()`. Якщо ми хочемо встановити кілька значень одночасно, ми об'єднуємо їх за допомогою оператора `|`. ```php -Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // Показує шлях до місця виклику dump() -Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Показує як шлях до класів, так і посилання на місце виклику dump() -Debugger::$showLocation = false; // Приховує додаткову інформацію про розташування -Debugger::$showLocation = true; // Показує всю додаткову інформацію про місцезнаходження +Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // Встановлює лише вивід про місце виклику функції +Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Встановлює одночасно вивід посилання та шлях до класу +Debugger::$showLocation = false; // Вимикає вивід додаткової інформації +Debugger::$showLocation = true; // Вмикає вивід усієї додаткової інформації ``` -Дуже зручною альтернативою `dump()` є `dumpe()` (тобто dump and exit) та `bdump()`. Це дозволяє нам скидати змінні в Tracy Bar. Це корисно, тому що дампи не псують висновок, і ми також можемо додати заголовок до дампу. +Практичною альтернативою `dump()` є `dumpe()` (dump & exit) та `bdump()`. Це дозволяє нам виводити значення змінної на панелі Tracy Bar. Це дуже зручно, оскільки дампи відокремлені від макета сторінки, і ми також можемо додати до них коментар. ```php -bdump([2, 4, 6, 8], 'even numbers up to ten'); -bdump([1, 3, 5, 7, 9], 'odd numbers up to ten'); +bdump([2, 4, 6, 8], 'парні числа до десяти'); +bdump([1, 3, 5, 7, 9], 'непарні числа до десяти'); ``` -[* bardump-en.webp *] +[* bardump-cs.webp *] diff --git a/tracy/uk/extensions.texy b/tracy/uk/extensions.texy index 6b47c76241..759d385a51 100644 --- a/tracy/uk/extensions.texy +++ b/tracy/uk/extensions.texy @@ -1,23 +1,23 @@ -Створення розширень Трейсі -************************** +Створення розширень для Tracy +*****************************
                                                                                                                            -Tracy - чудовий інструмент для налагодження вашої програми. Однак іноді вам потрібно більше інформації, ніж пропонує Tracy. Ви дізнаєтеся про: +Tracy надає чудовий інструмент для налагодження вашого застосунку. Однак іноді вам може знадобитися додаткова інформація під рукою. Ми покажемо вам, як написати власне розширення для Tracy Bar, щоб зробити розробку ще приємнішою. -- Створення власних панелей Tracy Bar -- Створення власних розширень Bluescreen +- Створення власної панелі для Tracy Bar +- Створення власного розширення для Bluescreen
                                                                                                                            .[tip] -Ви можете знайти корисні розширення для Tracy на "Componette":https://componette.org/search/tracy. +Репозиторій готових розширень для Tracy можна знайти на "Componette":https://componette.org/search/tracy. -Розширення для бару Tracy .[#toc-tracy-bar-extensions] -====================================================== +Розширення для Tracy Bar +======================== -Створити нове розширення для Tracy Bar дуже просто. Вам необхідно реалізувати інтерфейс `Tracy\IBarPanel` з методами `getTab()` і `getPanel()`. Ці методи повинні повертати HTML-код вкладки (невеликий ярлик на панелі Tracy Bar) і панелі (спливаюче вікно, що відображається після клацання на вкладці). Якщо `getPanel()` не повертає нічого, буде відображатися тільки вкладка. Якщо `getTab()` нічого не повертає, то нічого не відображається і `getPanel()` не буде викликано. +Створити нове розширення для Tracy Bar нескладно. Ви створюєте об'єкт, що реалізує інтерфейс `Tracy\IBarPanel`, який має два методи: `getTab()` та `getPanel()`. Методи повинні повертати HTML-код вкладки (невеликий опис, що відображається безпосередньо на панелі) та панелі. Якщо `getPanel()` нічого не повертає, відображається лише сам опис. Якщо `getTab()` нічого не повертає, нічого не відображається, і `getPanel()` більше не викликається. ```php class ExamplePanel implements Tracy\IBarPanel @@ -35,16 +35,16 @@ class ExamplePanel implements Tracy\IBarPanel ``` -Реєстрація .[#toc-registration] -------------------------------- +Реєстрація +---------- -Реєстрація здійснюється за телефоном `Tracy\Bar::addPanel()`: +Реєстрація здійснюється за допомогою `Tracy\Bar::addPanel()`: ```php Tracy\Debugger::getBar()->addPanel(new ExamplePanel); ``` -або ви можете просто зареєструвати свою панель у конфігурації програми: +Або ви можете зареєструвати панель безпосередньо в конфігурації застосунку: ```neon tracy: @@ -53,70 +53,70 @@ tracy: ``` -HTML-код вкладки .[#toc-tab-html-code] --------------------------------------- +HTML-код вкладки +---------------- -Має виглядати приблизно так: +Він повинен виглядати приблизно так: ```latte - + ... - Title + Заголовок ``` -Зображення має бути у форматі SVG. Якщо вам не потрібна спливаюча підказка, ви можете залишити `` виключити. +Зображення має бути у форматі SVG. Якщо пояснювальний опис не потрібен, `` можна пропустити. -HTML код панелі .[#toc-panel-html-code] ---------------------------------------- +HTML-код панелі +--------------- -Має виглядати приблизно так: +Він повинен виглядати приблизно так: ```latte -

                                                                                                                            Title

                                                                                                                            +

                                                                                                                            Заголовок

                                                                                                                            - ... content ... + ... вміст ...
                                                                                                                            ``` -Заголовок має бути або таким самим, як на вкладці, або містити додаткову інформацію. +Заголовок має бути таким самим, як заголовок вкладки, або може містити додаткові дані. -Одне розширення може бути зареєстровано кілька разів, тому рекомендується не використовувати атрибут `id` для стилізації. Ви можете використовувати класи, переважно в `tracy-addons-[-]` форматі. При створенні CSS краще використовувати `#tracy-debug .class`, оскільки таке правило має вищий пріоритет, ніж reset. +Слід враховувати, що одне розширення може бути зареєстроване кілька разів, наприклад, з різними налаштуваннями, тому для стилізації не можна використовувати CSS id, а лише class, у форматі `tracy-addons-<НазваКласу>[-<необов'язково>]`. Потім запишіть клас у div разом із класом `tracy-inner`. При написанні CSS корисно писати `#tracy-debug .trida`, оскільки правило тоді матиме вищий пріоритет, ніж reset. -Стилі за замовчуванням .[#toc-default-styles] ---------------------------------------------- +Стандартні стилі +---------------- -На панелі елементи ``, `
                                                                                                                            `, `
                                                                                                                            `, `` мають стилі за замовчуванням. Для створення посилання для приховування або відображення іншого елемента з'єднайте їх атрибутами `href` і `id` і класом `tracy-toggle`.
                                                                                                                            +На панелі попередньо стилізовані ``, `
                                                                                                                            `, `
                                                                                                                            `, ``. Якщо ви хочете створити посилання, яке приховує та показує інший елемент, зв'яжіть їх атрибутами `href` та `id` та класом `tracy-toggle`:
                                                                                                                             
                                                                                                                             ```latte
                                                                                                                            -Detail
                                                                                                                            +Деталі
                                                                                                                             
                                                                                                                            -
                                                                                                                            ...
                                                                                                                            +
                                                                                                                            ...
                                                                                                                            ``` -Якщо стан за замовчуванням - згорнутий, додайте клас `tracy-collapsed` до обох елементів. +Якщо стандартний стан має бути згорнутим, додайте обом елементам клас `tracy-collapsed`. -Використовуйте статичний лічильник для запобігання дублювання ідентифікаторів на одній сторінці. +Використовуйте статичний лічильник, щоб уникнути створення дублікатів ID на одній сторінці. -Розширення для синього екрана .[#toc-bluescreen-extensions] -=========================================================== +Розширення для Bluescreen +========================= -Ви можете додати свої власні візуалізації винятків або панелі, які відображатимуться на блюскрині. +Таким чином можна додавати власні візуалізації винятків або панелі, які відображатимуться на bluescreen. -Розширення робиться таким чином: +Розширення створюється цією командою: ```php -Tracy\Debugger::getBlueScreen()->addPanel(function (?Throwable $e) { // перехоплене виключення +Tracy\Debugger::getBlueScreen()->addPanel(function (?Throwable $e) { // перехоплений виняток return [ - 'tab' => '...Title...', - 'panel' => '...content...', + 'tab' => '...Мітка...', + 'panel' => '...HTML-код панелі...', ]; }); ``` -Функція викликається двічі, спочатку сам виняток передається в параметрі `$e`, а панель, що повертається, виводиться на початку сторінки. Якщо нічого не повертається, то панель не відтворюється. Потім вона викликається з параметром `null`, і панель, що повертається, рендериться нижче стека викликів. Якщо функція повертає `'bottom' => true` у масиві, то панель відтворюється в самому низу. +Функція викликається двічі: спочатку в параметрі `$e` передається сам виняток, і повернута панель відображається на початку сторінки. Якщо нічого не повертається, панель не відображається. Потім вона викликається з параметром `null`, і повернута панель відображається під callstack. Якщо функція повертає в масиві ключ `'bottom' => true`, панель відображається в самому низу. diff --git a/tracy/uk/guide.texy b/tracy/uk/guide.texy index 6761dcd4bb..d2c3f192b4 100644 --- a/tracy/uk/guide.texy +++ b/tracy/uk/guide.texy @@ -1,40 +1,40 @@ -Початок роботи з Трейсі -*********************** +Починаємо з Tracy +*****************
                                                                                                                            -Бібліотека Tracy - це корисний помічник для звичайних PHP-програмістів. Вона допоможе вам: +Бібліотека Tracy є корисним щоденним помічником для PHP-програміста. Вона допоможе вам: - швидко виявляти та виправляти помилки -- реєструвати помилки -- дамп змінних -- вимірювати час виконання скриптів/запитів -- переглядати споживання пам'яті +- логувати помилки +- виводити змінні +- вимірювати час виконання скриптів та запитів до бази даних +- відстежувати використання пам'яті
                                                                                                                            -PHP - ідеальна мова для створення ледь помітних помилок, тому що вона надає програмістам велику гнучкість. Tracy\Debugger більш цінний через це. Це найкращий інструмент серед діагностичних. +PHP — це мова, яка ніби створена для створення важко виявлюваних помилок, оскільки надає розробникам значну свободу. Тим ціннішим є інструмент налагодження Tracy. Серед діагностичних інструментів для PHP він є абсолютним лідером. -Якщо ви зустрічаєтеся з Трейсі вперше, повірте, ваше життя почне ділитися на те, що було до Трейсі, і те, що з нею. Ласкаво просимо в хорошу частину! +Якщо ви сьогодні вперше знайомитеся з Tracy, повірте, ваше життя почне ділитися на те, що було до Tracy, і те, що з нею. Ласкаво просимо до кращої частини! -Встановлення та вимоги .[#toc-installation-and-requirements] -============================================================ +Встановлення +============ -Найкращий спосіб встановлення Tracy - [завантажити останній пакет |https://github.com/nette/tracy/releases] або використовувати Composer: +Найкращий спосіб встановити Tracy — це [завантажити останній пакет |https://github.com/nette/tracy/releases] або використати Composer: ```shell composer require tracy/tracy ``` -Також ви можете завантажити весь пакет або файл [tracy.phar |https://github.com/nette/tracy/releases]. +Ви також можете завантажити весь пакет як файл [tracy.phar |https://github.com/nette/tracy/releases]. -Використання .[#toc-usage] -========================== +Використання +============ -Tracy активується викликом методу `Tracy\Debugger::enable()' якнайшвидше на початку програми, до того, як буде надіслано будь-який вивід: +Tracy активується викликом методу `Tracy\Debugger::enable()' якомога раніше на початку програми, перед надсиланням будь-якого виводу: ```php use Tracy\Debugger; @@ -44,170 +44,169 @@ require 'vendor/autoload.php'; // або tracy.phar Debugger::enable(); ``` -Перше, що ви помітите на сторінці, це панель Tracy у правому нижньому куті. Якщо ви її не бачите, це може означати, що Tracy працює у виробничому режимі. -Це пов'язано з тим, що з міркувань безпеки Tracy видно лише на localhost. Щоб перевірити, чи працює він, ви можете тимчасово перевести його у режим розробки за допомогою параметра `Debugger::enable(Debugger::Development)`. +Перше, що ви помітите на сторінці, — це Tracy Bar у правому нижньому куті. Якщо ви його не бачите, це може означати, що Tracy працює в робочому режимі. З міркувань безпеки Tracy видима лише на localhost. Щоб перевірити, чи вона працює, ви можете тимчасово перемкнути її в режим розробки за допомогою параметра `Debugger::enable(Debugger::Development)`. -Бар Трейсі .[#toc-tracy-bar] -============================ +Tracy Bar +========= -Панель Трейсі - це плаваюча панель. Вона відображається в правому нижньому кутку сторінки. Ви можете переміщати її за допомогою миші. Вона запам'ятовує своє положення після перезавантаження сторінки. +Tracy Bar — це плаваюча панель, яка з'являється в правому нижньому куті сторінки. Її можна переміщати мишею, і після перезавантаження сторінки вона запам'ятає свою позицію. [* tracy-bar.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html -Ви можете додати інші корисні панелі на панель Tracy Bar. Ви можете знайти цікаві з них в [аддонах |https://componette.org] або [створити свої власні |extensions]. +До Tracy Bar можна додавати інші корисні панелі. Багато з них можна знайти в [доповненнях |https://componette.org], або ви навіть [можете написати власні |extensions]. -Якщо ви не хочете показувати панель Tracy Bar, встановіть: +Якщо ви не хочете відображати Tracy Bar, встановіть: ```php Debugger::$showBar = false; ``` -Візуалізація помилок і винятків .[#toc-visualization-of-errors-and-exceptions] -============================================================================== +Візуалізація помилок та винятків +================================ -Напевно ви знаєте, як PHP повідомляє про помилки: у вихідному коді сторінки є щось на зразок цього: +Ви, напевно, добре знаєте, як PHP повідомляє про помилки: у вихідний код сторінки виводиться щось подібне: /--pre .{font-size: 90%} Parse error: syntax error, unexpected '}' in HomePresenter.php on line 15 \-- -or uncaught exception: +або при неперехопленому винятку: /--pre .{font-size: 90%} Fatal error: Uncaught Nette\MemberAccessException: Call to undefined method Nette\Application\UI\Form::addTest()? in /sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php:100 Stack trace: #0 /sandbox/vendor/nette/utils/src/Utils/Object.php(75): Nette\Utils\ObjectMixin::call(Object(Nette\Application\UI\Form), 'addTest', Array) -#1 /sandbox/app/forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) -#2 /sandbox/app/presenters/SignPresenter.php(21): App\Forms\SignFormFactory->create() -#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presenters\SignPresenter->createComponentSignInForm('signInForm') +#1 /sandbox/app/Forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array) +#2 /sandbox/app/Presentation/Sign/SignPresenter.php(21): App\Forms\SignFormFactory->create() +#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presentation\Sign\SignPresenter->createComponentSignInForm('signInForm') #4 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(139): Nette\ComponentModel\Container->createComponent('signInForm') #5 /sandbox/temp/cache/latte/15206b353f351f6bfca2c36cc.php(17): Nette\ComponentModel\Co in /sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php on line 100
                                                                                                                            \-- -Орієнтуватися в цьому висновку не так просто. Якщо ви ввімкнете функцію Tracy, то помилки та винятки відображатимуться в зовсім іншому вигляді: +Зорієнтуватися в такому виводі не дуже легко. Якщо увімкнути Tracy, помилка або виняток відображатимуться зовсім в іншому вигляді: [* tracy-exception.webp .{url:-} *] -Повідомлення про помилку буквально кричить. Ви можете побачити частину вихідного коду з виділеним рядком, у якому сталася помилка. Повідомлення чітко пояснює помилку. Весь сайт [інтерактивний, спробуйте його |https://nette.github.io/tracy/tracy-exception.html]. +Повідомлення про помилку буквально кричить. Ми бачимо частину вихідного коду з виділеним рядком, де сталася помилка, а інформація *Call to undefined method Nette\Http\User::isLogedIn()* зрозуміло пояснює, про яку помилку йдеться. Крім того, вся сторінка інтерактивна, ми можемо переходити до більш детальної інформації. [Спробуйте |https://nette.github.io/tracy/tracy-exception.html]. -І знаєте що? Фатальні помилки відловлюються і відображаються точно так само. Не потрібно встановлювати жодних розширень (натисніть для живого прикладу): +І знаєте що? Таким чином вона перехоплює і відображає навіть фатальні помилки. Без необхідності встановлювати будь-які розширення. [* tracy-error.webp .{url:-} *] -Такі помилки, як помилка в імені змінної або спроба відкрити неіснуючий файл, генерують звіти рівня E_NOTICE або E_WARNING. Їх можна легко пропустити і/або вони можуть бути повністю приховані в графічному макеті веб-сторінки. Дозвольте Tracy керувати ними: +Помилки, такі як одруківка в назві змінної або спроба відкрити неіснуючий файл, генерують повідомлення рівня E_NOTICE або E_WARNING. Їх легко пропустити в графіці сторінки, вони навіть можуть бути зовсім невидимими (хіба що при перегляді коду сторінки). [* tracy-notice2.webp *]:https://nette.github.io/tracy/tracy-debug-bar.html -Або вони можуть відображатися як помилки: +Або вони можуть відображатися так само, як помилки: ```php -Debugger::$strictMode = true; // виводити усі помилки -Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // всі помилки, крім застарілих повідомлень +Debugger::$strictMode = true; // показати всі помилки +Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // всі помилки, крім повідомлень про застарілість ``` [* tracy-notice.webp .{url:-} *] -Зауваження: Tracy при активації змінює рівень повідомлення про помилки на E_ALL. Якщо ви хочете змінити це, зробіть це, звернувшись за адресою `enable()`. +Примітка: Tracy після активації змінює рівень повідомлень про помилки на E_ALL. Якщо ви хочете змінити це значення, зробіть це після виклику `enable()`. -Розвиток vs виробничий режим .[#toc-development-vs-production-mode] -=================================================================== +Режим розробки vs робочий режим +=============================== -Як бачите, Трейсі досить балакучий, що можна оцінити в середовищі розробки, в той час як на продакшн-сервері це призвело б до катастрофи. Це тому, що там не повинна відображатися ніяка налагоджувальна інформація. Тому Tracy має **автовизначення оточення**, і якщо приклад виконується на реальному сервері, помилка буде записана в журнал, а не показана, і відвідувач побачить лише зручне для користувача повідомлення: +Як бачите, Tracy досить багатослівна, що цінується в середовищі розробки, тоді як на робочому сервері це спричинило б справжню катастрофу. Там жодна інформація для налагодження не повинна виводитися. Тому Tracy має **автоматичне визначення середовища**, і якщо приклад запустити на реальному сервері, помилка замість відображення буде залогована, а відвідувач побачить лише зрозуміле для користувача повідомлення: [* tracy-error2.webp .{url:-} *] -Виробничий режим пригнічує відображення всієї налагоджувальної інформації, що надсилається за допомогою [dump( |dumper]), і, звичайно ж, всіх повідомлень про помилки, що генеруються PHP. Тож якщо ви забули якийсь `dump($obj)` в коді, не хвилюйтеся, на продакшн сервері нічого не буде відображено. +Робочий режим пригнічує відображення всієї інформації для налагодження, яку ми надсилаємо за допомогою [dump() |dumper], і, звичайно, всіх повідомлень про помилки, які генерує PHP. Отже, якщо ви забули в коді якийсь `dump($obj)`, не хвилюйтеся, на робочому сервері нічого не виведеться. -Як працює автоматичне визначення режиму? Режим є режимом розробки, якщо додаток працює на локальному хості (тобто IP-адреса `127.0.0.1` або `::1`) і немає проксі (тобто його HTTP-заголовка). В іншому випадку він працює в режимі production. +Як працює автоматичне визначення режиму? Режим є режимом розробки, якщо застосунок запущено на localhost (тобто IP-адреса `127.0.0.1` або `::1`) і немає проксі (тобто його HTTP-заголовка). В іншому випадку він працює в робочому режимі. -Якщо ви хочете увімкнути режим розробки в інших випадках, наприклад, для доступу розробників з певної IP-адреси, ви можете вказати його як параметр методу `enable()`: +Якщо ми хочемо увімкнути режим розробки і в інших випадках, наприклад, для програмістів, які підключаються з певної IP-адреси, ми вказуємо її як параметр методу `enable()`: ```php -Debugger::enable('23.75.345.200'); // ви також можете вказати масив IP-адрес +Debugger::enable('23.75.345.200'); // можна також вказати масив IP-адрес ``` -Ми настійно рекомендуємо поєднувати IP-адресу з файлом cookie. Збережіть секретний маркер, наприклад, `secret1234`, у файлі cookie `tracy-debug` і таким чином активуйте режим розробки лише для розробників, які заходять з певної IP-адреси і мають згаданий маркер у файлі cookie: +Настійно рекомендуємо поєднувати IP-адресу з cookie. У cookie `tracy-debug` ми зберігаємо секретний токен, наприклад, `secret1234`, і таким чином активуємо режим розробки лише для програмістів, які підключаються з певної IP-адреси та мають зазначений токен у cookie: ```php Debugger::enable('secret1234@23.75.345.200'); ``` -Ви також можете безпосередньо встановити режим розробки/виробництва, використовуючи константи `Debugger::Development` або `Debugger::Production` як параметр методу `enable()`. +Режим розробки/робочий режим можна також встановити безпосередньо, використовуючи константу `Debugger::Development` або `Debugger::Production` як параметр методу `enable()`. .[note] -Якщо ви використовуєте Nette Framework, подивіться, як [встановити режим для нього |application:bootstrap#Development vs Production Mode], і він також буде використаний для Tracy. +Якщо ви використовуєте Nette Framework, подивіться, як [налаштувати режим для нього |application:bootstrapping#Режим розробки проти робочого режиму], і він згодом буде використаний також для Tracy. -Журналювання помилок .[#toc-error-logging] -========================================== +Логування помилок +================= -У виробничому режимі Tracy автоматично записує всі помилки та винятки до текстового журналу. Для того, щоб лог відбувався, вам потрібно задати абсолютний шлях до каталогу журналу у змінній `$logDirectory` або передати його як другий параметр методу `enable()`: +У робочому режимі Tracy автоматично записує всі помилки та перехоплені винятки до текстового логу. Щоб логування могло відбуватися, ми повинні встановити абсолютний шлях до директорії логів у змінну `$logDirectory` або передати його як другий параметр методу `enable()`: ```php Debugger::$logDirectory = __DIR__ . '/log'; ``` -Логування помилок надзвичайно корисне. Уявіть, що всі користувачі вашого додатку насправді є бета-тестерами, які безкоштовно виконують першокласну роботу з пошуку помилок, і було б нерозумно викидати їхні цінні звіти непоміченими у смітник. +Логування помилок при цьому надзвичайно корисне. Уявіть, що всі користувачі вашого застосунку насправді є бета-тестерами, які безкоштовно виконують чудову роботу з пошуку помилок, і ви зробили б дурість, якби викинули їхні цінні звіти непоміченими у смітник. -Якщо вам потрібно записати до журналу власні повідомлення або спіймані винятки, використовуйте метод `log()`: +Якщо нам потрібно залогувати власне повідомлення або перехоплений вами виняток, ми використовуємо для цього метод `log()`: ```php -Debugger::log('Unexpected error'); // текстове повідомлення +Debugger::log('Сталася неочікувана помилка'); // текстове повідомлення try { - criticalOperation(); + kritickaOperace(); } catch (Exception $e) { - Debugger::log($e); // занести виключення в лог + Debugger::log($e); // можна логувати і виняток // або - Debugger::log($e, Debugger::ERROR); // також відправляє повідомлення на email + Debugger::log($e, Debugger::ERROR); // також надішле сповіщення електронною поштою } ``` -If you want Tracy to log PHP errors like `E_NOTICE` or `E_WARNING` with detailed information (HTML report), set `Debugger::$logSeverity`: +Якщо ви хочете, щоб Tracy логувала помилки PHP, такі як `E_NOTICE` або `E_WARNING`, з детальною інформацією (HTML-звіт), встановіть `Debugger::$logSeverity`: ```php Debugger::$logSeverity = E_NOTICE | E_WARNING; ``` -Для справжнього професіонала журнал помилок є найважливішим джерелом інформації, і він або вона хоче отримувати повідомлення про будь-яку нову помилку негайно. Трейсі допомагає йому. Вона здатна надсилати електронний лист для кожного нового запису про помилку. Змінна $email визначає, куди надсилати ці листи: +Для справжнього професіонала лог помилок є ключовим джерелом інформації, і він хоче бути негайно поінформованим про кожну нову помилку. Tracy йде йому назустріч, оскільки вміє інформувати про новий запис у лозі електронною поштою. Куди надсилати листи, визначаємо змінною $email: ```php Debugger::$email = 'admin@example.com'; ``` -Якщо ви використовуєте весь Nette Framework, ви можете встановити цю та інші змінні в [конфігураційному файлі |nette:configuring]. +Якщо ви використовуєте весь Nette Framework, це та інші налаштування можна встановити в [конфігураційному файлі |nette:configuring]. -Щоб захистити свою поштову скриньку від переповнення, Tracy відправляє **тільки одне повідомлення** і створює файл `email-sent`. Коли розробник отримує сповіщення електронною поштою, він перевіряє журнал, виправляє свій додаток і видаляє файл моніторингу `email-sent`. Це знову активує надсилання електронної пошти. +Однак, щоб не заповнювати вашу поштову скриньку, вона завжди надсилає **лише одне повідомлення** і створює файл `email-sent`. Розробник після отримання сповіщення електронною поштою перевіряє лог, виправляє застосунок і видаляє файл моніторингу, чим знову активує надсилання електронних листів. -Відкриття файлів у редакторі .[#toc-opening-files-in-the-editor] -================================================================ +Відкриття в редакторі +===================== -Коли відображається сторінка помилки, ви можете клацнути по іменах файлів, і вони відкриються у вашому редакторі з курсором на відповідному рядку. Файли також можна створювати (дія `create file`) або виправляти в них помилки (дія `fix it`). Для цього необхідно налаштувати [браузер і систему |open-files-in-ide]. +При відображенні сторінки помилки можна натиснути на імена файлів, і вони відкриються у вашому редакторі з курсором на відповідному рядку. Також можна створювати файли (дія `create file`) або виправляти в них помилки (дія `fix it`). Щоб це працювало, достатньо [налаштувати браузер та систему |open-files-in-ide]. -Підтримувані версії PHP .[#toc-supported-php-versions] -====================================================== +Підтримувані версії PHP +======================= -| Tracy | сумісні з PHP -|-----------|-------------------- -| Tracy 2.10 – 3.0 | PHP 8.0 - 8.2 -| Tracy 2.9 | PHP 7.2 - 8.2 -| Tracy 2.8 | PHP 7.2 - 8.1 -| Трейсі 2.6 - 2.7 | PHP 7.1 - 8.0 -| Трейсі 2.5 | PHP 5.4 - 7.4 -| Трейсі 2.4 | PHP 5.4 - 7.2 +| Tracy | сумісний з PHP +|-----------|------------------- +| Tracy 2.10 – 3.0 | PHP 8.0 – 8.4 +| Tracy 2.9 | PHP 7.2 – 8.2 +| Tracy 2.8 | PHP 7.2 – 8.1 +| Tracy 2.6 – 2.7 | PHP 7.1 – 8.0 +| Tracy 2.5 | PHP 5.4 – 7.4 +| Tracy 2.4 | PHP 5.4 – 7.2 -Застосовується до останніх версій патчів. +Застосовується до останньої версії патча. -Порти .[#toc-ports] -=================== +Порти +===== -Це список неофіційних портів на інші фреймворки та CMS: +Це список неофіційних портів для інших фреймворків та CMS: - [Drupal 7](https://www.drupal.org/project/traced) - Laravel framework: [recca0120/laravel-tracy](https://github.com/recca0120/laravel-tracy), [whipsterCZ/laravel-tracy](https://github.com/whipsterCZ/laravel-tracy) diff --git a/tracy/uk/open-files-in-ide.texy b/tracy/uk/open-files-in-ide.texy index b1272d7225..ff3737af1b 100644 --- a/tracy/uk/open-files-in-ide.texy +++ b/tracy/uk/open-files-in-ide.texy @@ -2,19 +2,19 @@ ******************************************************** .[perex] -Коли відображається сторінка помилки, ви можете натиснути на імена файлів, і вони відкриються у вашому редакторі з курсором на відповідному рядку. Файли також можна створювати (дія `create file`) або виправляти в них помилки (дія `fix it`). Для цього необхідно налаштувати браузер і систему. +При відображенні сторінки помилки можна натиснути на імена файлів, і вони відкриються у вашому редакторі з курсором на відповідному рядку. Також можна створювати файли (дія `create file`) або виправляти в них помилки (дія `fix it`). Щоб це сталося, необхідно налаштувати браузер та систему. -Tracy відкриває файли через URL виду `editor://open/?file=%file&line=%line`, тобто за протоколом `editor://`. Для цього ми зареєструємо власний обробник. Це може бути будь-який виконуваний файл, який обробить параметри і запустить наш улюблений редактор. +Tracy відкриває файли через URL у форматі `editor://open/?file=%file&line=%line`, тобто з протоколом `editor://`. Для нього ми зареєструємо власний обробник. Ним може бути будь-який виконуваний файл, який "перетравить" параметри та запустить наш улюблений редактор. -Ви можете змінити URL у змінній `Tracy\Debugger::$editor` або вимкнути клік-критерій, встановивши `Tracy\Debugger::$editor = null`. +URL можна змінити у змінній `Tracy\Debugger::$editor`, або вимкнути перехід за посиланням, встановивши `Tracy\Debugger::$editor = null`. -Windows .[#toc-windows] -======================= +Windows +======= -1. Завантажте відповідні файли "зі сховища Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/windows на диск. +1. Завантажте відповідні файли з "репозиторію Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/windows на диск. -2. відредагуйте `open-editor.js` і відкоментуйте або відредагуйте шлях до вашого редактора в `settings`: +2. Відредагуйте файл `open-editor.js` і в масиві `settings` розкоментуйте та, за потреби, змініть шлях до вашого редактора: ```js var settings = { @@ -35,19 +35,28 @@ var settings = { ... ``` -Будьте уважні та зберігайте подвійні косі риски в шляхах. +Увага, залишайте подвійні зворотні скісні риски у шляхах. -3. Зареєструйте обробник для протоколу `editor://` у системі. +3. Зареєструйте обробник протоколу `editor://` в системі. -Це робиться шляхом запуску скрипта `install.cmd`. **Ви повинні запустити його від імені адміністратора.** Скрипт `open-editor.js` тепер буде обслуговувати протокол `editor://`. +Це робиться запуском файлу `install.cmd`. **Його потрібно запустити від імені адміністратора.** Скрипт `open-editor.js` тепер оброблятиме протокол `editor://`. +Щоб можна було відкривати посилання, згенеровані на інших серверах, наприклад, на робочому сервері або в Docker, додайте до `open-editor.js` ще й зіставлення віддаленої URL з локальною: -Linux .[#toc-linux] -=================== +```js + mappings: { + // віддалений шлях: локальний шлях + '/var/www/nette.app': 'W:\\Nette.web\\_web', + } +``` + + +Linux +===== -1. Завантажте відповідні файли "з репозиторію Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux у каталог `~/bin`. +1. Завантажте відповідні файли з "репозиторію Tracy":https://github.com/nette/tracy/tree/master/tools/open-in-editor/linux до каталогу `~/bin`. -2. відредагуйте `open-editor.sh` і відкоментуйте або відредагуйте шлях до вашого редактора у змінній `editor`: +2. Відредагуйте файл `open-editor.sh` і розкоментуйте та, за потреби, змініть шлях до вашого редактора у змінній `editor`. ```shell #!/bin/bash @@ -67,24 +76,25 @@ Linux .[#toc-linux] ... ``` -Зробіть його виконуваним: +Зробіть файл виконуваним: ```shell chmod +x ~/bin/open-editor.sh ``` -Якщо редактор, який ви використовуєте, не встановлений з пакета, у бінарному файлі, швидше за все, не буде шляху до `$PATH`. Це можна легко виправити. У каталозі `~/bin` створіть сімлінк на двійковий файл редактора. .[note] +.[note] +Якщо використовуваний редактор не встановлено з пакета, ймовірно, його бінарний файл не матиме шляху в $PATH. Це легко виправити. У каталозі `~/bin` створіть символічне посилання на бінарний файл редактора. -3. Зареєструйте в системі обробник для протоколу `editor://`. +3. Зареєструйте обробник протоколу `editor://` в системі. -Це робиться шляхом запуску скрипта `install.sh`. Тепер скрипт `open-editor.js` обслуговуватиме протокол `editor://`. +Це робиться запуском файлу `install.sh`. Скрипт `open-editor.sh` тепер оброблятиме протокол `editor://`. -macOS .[#toc-macos] -=================== +macOS +===== -Такі редактори, як PhpStorm, TextMate тощо, дають змогу відкривати файли через спеціальний URL, який потрібно просто задати: +Редактори, такі як PhpStorm, TextMate тощо, дозволяють відкривати файли через спеціальну URL-адресу, яку достатньо встановити: ```php // PhpStorm @@ -92,44 +102,43 @@ Tracy\Debugger::$editor = 'phpstorm://open?file=%file&line=%line'; // TextMate Tracy\Debugger::$editor = 'txmt://open/?url=file://%file&line=%line'; // MacVim -Tracy\Debugger::$editor = 'mvim://open/?url=file://%file&line=%line'; +Tracy\Debugger::$editor = 'mvim://open?url=file:///%file&line=%line'; // Visual Studio Code Tracy\Debugger::$editor = 'vscode://file/%file:%line'; ``` -Якщо ви використовуєте автономний Tracy, поставте рядок перед `Tracy\Debugger::enable()`, якщо Nette, то перед `$configurator->enableTracy()` в `Bootstrap.php`. +Якщо ви використовуєте окрему Tracy, вставте рядок перед `Tracy\Debugger::enable()`, якщо Nette, то перед `$configurator->enableTracy()` у `Bootstrap.php`. -На жаль, дії `create file` або `fix it` не працюють на macOS. +Дії `create file` або `fix it`, на жаль, не працюють на macOS. -Демонстрації .[#toc-demos] -========================== +Приклади +======== Виправлення помилки: -Створення нового файлу: +Створення файлу: -Усунення неполадок .[#toc-troubleshooting] -========================================== +Вирішення проблем +================= -- У Firefox вам може знадобитися [дозволити |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] виконання користувацького протоколу в about:config, встановивши `network.protocol-handler.expose.editor` на `false` і `network.protocol-handler.expose-all` на `true`. Однак це має бути дозволено за замовчуванням. -- Якщо не все працює відразу, не панікуйте. Спробуйте оновити сторінку, перезавантажити браузер або комп'ютер. Це має допомогти. -- Дивіться [тут |https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/], щоб виправити: - Помилка введення: Не існує скриптового движка для файлів з розширенням ".js" Можливо, ви пов'язали файл з розширенням ".js" з іншим додатком, а не скриптовим движком JScript. +- У Firefox може знадобитися дозволити протокол, [встановивши |http://kb.mozillazine.org/Register_protocol#Firefox_3.5_and_above] `network.protocol-handler.expose.editor` на `false` та `network.protocol-handler.expose-all` на `true` в about:config. +- Якщо це не спрацює одразу, не панікуйте і спробуйте кілька разів оновити сторінку перед тим, як натиснути на це посилання. Воно запрацює! +- Ось [посилання |https://www.winhelponline.com/blog/error-there-is-no-script-engine-for-file-extension-when-running-js-files/] для виправлення можливої помилки: `Input Error: There is no script engine for file extension ".js"`, `Maybe you associated ".js" file to another app, not JScript engine.` відповідно `для розширення .js немає доступного скриптового рушія`. -Починаючи з версії Google Chrome 77, ви більше не побачите прапорець "Завжди відкривати ці типи посилань у пов'язаному додатку", коли редактор відкривається за посиланням. Обхідний шлях для Windows: створіть файл `fix.reg`: +У Google Chrome, починаючи з версії 77, ви більше не побачите прапорець «Завжди відкривати посилання цього типу у пов'язаному застосунку», коли редактор запускається через посилання. Рішення для Windows: створіть файл `fix.reg`: ``` Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\URLWhitelist] "123"="editor://*" ``` -Імпортуйте його подвійним клацанням і перезапустіть Chrome. +Імпортуйте його подвійним клацанням миші та перезапустіть браузер Chrome. -Якщо у вас виникнуть додаткові проблеми або запитання, задавайте їх на [форумі |https://forum.nette.org]. +З будь-якими питаннями чи зауваженнями, будь ласка, звертайтеся на [форум |https://forum.nette.org]. diff --git a/tracy/uk/recipes.texy b/tracy/uk/recipes.texy index ca817ea99c..8abc4933c6 100644 --- a/tracy/uk/recipes.texy +++ b/tracy/uk/recipes.texy @@ -1,12 +1,11 @@ -Рецепти -******* +Інструкції +********** -Політика безпеки контенту .[#toc-content-security-policy] -========================================================= +Content Security Policy +======================= -Якщо ваш сайт використовує політику безпеки вмісту, вам потрібно додати `'nonce-'` і `'strict-dynamic'` до `script-src`, щоб Tracy працював належним чином. Деякі сторонні плагіни можуть потребувати додаткових директив. -Nonce не підтримується в директиві `style-src`, якщо ви використовуєте цю директиву, вам потрібно додати `'unsafe-inline'`, але цього слід уникати у виробничому режимі. +Якщо ваш веб-сайт використовує Content Security Policy, вам потрібно буде додати ті самі `'nonce-'` та `'strict-dynamic'` до `script-src`, щоб Tracy працювала належним чином. Деякі доповнення сторонніх розробників можуть вимагати додаткових налаштувань. Nonce не підтримується в директиві `style-src`, якщо ви використовуєте цю директиву, вам потрібно додати `'unsafe-inline'`, але в робочому режимі цього слід уникати. Приклад конфігурації для [Nette Framework |nette:configuring]: @@ -24,11 +23,10 @@ header("Content-Security-Policy: script-src 'nonce-$nonce' 'strict-dynamic';"); ``` -Швидше завантаження .[#toc-faster-loading] -========================================== +Швидше завантаження +=================== -Базова інтеграція проста, однак, якщо на вашій веб-сторінці є повільні блокувальні скрипти, вони можуть сповільнити завантаження Tracy. -Рішення полягає в тому, щоб розмістити `` у ваш шаблон перед будь-якими скриптами: +Запуск простий, однак, якщо на вашій веб-сторінці є повільні блокуючі скрипти, вони можуть уповільнити завантаження Tracy. Рішенням є розміщення `` у вашому шаблоні перед усіма скриптами: ```latte @@ -42,12 +40,37 @@ header("Content-Security-Policy: script-src 'nonce-$nonce' 'strict-dynamic';"); ``` -AJAX та перенаправлення запитів .[#toc-ajax-and-redirected-requests] -==================================================================== +Налагодження AJAX-запитів +========================= + +Tracy автоматично перехоплює AJAX-запити, створені за допомогою jQuery або нативного API `fetch`. Запити відображаються на панелі Tracy як додаткові рядки, що забезпечує легке та зручне налагодження AJAX. + +Якщо ви не хочете автоматично перехоплювати AJAX-запити, ви можете вимкнути цю функцію, встановивши змінну JavaScript: + +```js +window.TracyAutoRefresh = false; +``` + +Для ручного моніторингу конкретних AJAX-запитів додайте HTTP-заголовок `X-Tracy-Ajax` зі значенням, яке повертає `Tracy.getAjaxHeader()`. Ось приклад використання з функцією `fetch`: + +```js +fetch(url, { + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Tracy-Ajax': Tracy.getAjaxHeader(), + } +}) +``` + +Цей підхід дозволяє вибірково налагоджувати AJAX-запити. + + +Сховище даних +============= -Tracy може відображати панель налагодження та сині екрани для AJAX-запитів і перенаправлень. Tracy створює власні сесії, зберігає дані у власних тимчасових файлах і використовує файли cookie `tracy-session`. +Tracy може відображати панелі в Tracy Bar та Bluescreen для AJAX-запитів та перенаправлень. Tracy створює власну сесію, зберігає дані у власних тимчасових файлах і використовує cookie `tracy-session`. -Tracy також можна налаштувати на використання власного сеансу PHP, який запускається перед ввімкненням Tracy: +Tracy також можна налаштувати для використання нативної сесії PHP, яку ми запускаємо ще до ввімкнення Tracy: ```php session_start(); @@ -55,44 +78,44 @@ Debugger::setSessionStorage(new Tracy\NativeSession); Debugger::enable(); ``` -Якщо запуск сеансу вимагає складнішої ініціалізації, ви можете запустити Tracy негайно (щоб він міг обробити будь-які помилки, що виникають), а потім ініціалізувати обробник сеансу і, нарешті, повідомити Tracy, що сеанс готовий до використання, за допомогою функції `dispatch()`: +У випадку, якщо запуск сесії вимагає складнішої ініціалізації, ви можете запустити Tracy негайно (щоб вона могла обробити будь-які помилки, що виникли), а потім ініціалізувати обробник сесії та, нарешті, повідомити Tracy, що сесія готова до використання за допомогою функції `dispatch()`: ```php Debugger::setSessionStorage(new Tracy\NativeSession); Debugger::enable(); -// з подальшою ініціалізацією сеансу -// і запустіть сесію +// далі йде ініціалізація сесії +// і запуск сесії session_start(); Debugger::dispatch(); ``` -Функція `setSessionStorage()` існує з версії 2.9, до цього Tracy завжди використовував власний сеанс PHP. +Функція `setSessionStorage()` існує з версії 2.9, до цього Tracy завжди використовувала нативну сесію PHP. -Користувацький скрубер .[#toc-custom-scrubber] -============================================== +Власний скрабер +=============== -Скрубер - це фільтр, який запобігає витоку конфіденційних даних з дампа, таких як паролі або облікові дані. Фільтр викликається для кожного елемента дампа масиву або об'єкта і повертає `true`, якщо значення є конфіденційним. У цьому випадку замість значення виводиться `*****`. +Скрабер — це фільтр, який запобігає витоку конфіденційних даних під час дампінгу, таких як паролі або облікові дані. Фільтр викликається для кожного елемента дампованого масиву або об'єкта і повертає `true`, якщо значення є конфіденційним. У такому випадку замість значення виводиться `*****`. ```php -// уникає вивантаження значень і властивостей ключів, таких як `password`, +// запобігає виведенню значень ключів та властивостей, таких як `password`, // `password_repeat`, `check_password`, `DATABASE_PASSWORD` тощо. $scrubber = function(string $key, $value, ?string $class): bool { return preg_match('#password#i', $key) && $value !== null; }; -// ми використовуємо його для всіх дампів всередині BlueScreen +// використовуємо його для всіх дампів усередині BlueScreen Tracy\Debugger::getBlueScreen()->scrubber = $scrubber; ``` -Користувацький логгер .[#toc-custom-logger] -=========================================== +Власний логер +============= -Ми можемо створити власний логгер для реєстрації помилок, неперехоплених виключень, а також для виклику за адресою `Tracy\Debugger::log()`. Логгер реалізує інтерфейс [api:Tracy\ILogger]. +Ми можемо створити власний логер, який буде логувати помилки, неперехоплені винятки, а також буде викликаний методом `Tracy\Debugger::log()`. Логер реалізує інтерфейс [api:Tracy\ILogger]. ```php use Tracy\ILogger; @@ -106,13 +129,13 @@ class SlackLogger implements ILogger } ``` -А потім ми його активуємо: +А потім активуємо його: ```php Tracy\Debugger::setLogger(new SlackLogger); ``` -Якщо ми використовуємо повний Nette Framework, ми можемо встановити його у файлі конфігурації NEON: +Якщо ми використовуємо повний Nette Framework, ви можете налаштувати його в конфігураційному файлі NEON: ```neon services: @@ -120,10 +143,10 @@ services: ``` -Інтеграція монологу .[#toc-monolog-integration] ------------------------------------------------ +Інтеграція Monolog +------------------ -До складу пакета Tracy входить адаптер PSR-3, що дозволяє інтегрувати [monolog/monolog](https://github.com/Seldaek/monolog). +Пакет Tracy надає адаптер PSR-3, який дозволяє інтеграцію [monolog/monolog](https://github.com/Seldaek/monolog). ```php $monolog = new Monolog\Logger('main-channel'); @@ -133,21 +156,21 @@ $tracyLogger = new Tracy\Bridges\Psr\PsrToTracyLoggerAdapter($monolog); Debugger::setLogger($tracyLogger); Debugger::enable(); -Debugger::log('info'); // пише: [] main-channel.INFO: інформація [] [] [] -Debugger::log('warning', Debugger::WARNING); // пише: [] main-channel.WARNING: warning [] [] +Debugger::log('info'); // записує: [] main-channel.INFO: info [] [] +Debugger::log('warning', Debugger::WARNING); // записує: [] main-channel.WARNING: warning [] [] ``` -nginx .[#toc-nginx] -=================== +nginx +===== -Якщо Tracy не працює на nginx, ймовірно, він неправильно налаштований. Якщо є щось на кшталт +Якщо Tracy не працює на сервері nginx, ймовірно, він неправильно налаштований. Якщо в конфігурації є щось подібне: ```nginx try_files $uri $uri/ /index.php; ``` -змініть його на +змініть це на: ```nginx try_files $uri $uri/ /index.php$is_args$args; diff --git a/tracy/uk/stopwatch.texy b/tracy/uk/stopwatch.texy index 50fa1a86b8..4bffa965bf 100644 --- a/tracy/uk/stopwatch.texy +++ b/tracy/uk/stopwatch.texy @@ -1,35 +1,35 @@ -Секундомір -********** +Вимірювання часу +**************** -Ще один корисний інструмент - секундомір відладчика з точністю до мікросекунд: +Ще одним корисним інструментом налагоджувача є секундомір з точністю до мікросекунд: ```php Debugger::timer(); -// Солодких снів, моя вишенько +// мій маленький принце, спи, пташки вже солодко сплять... sleep(2); $elapsed = Debugger::timer(); // $elapsed = 2 ``` -За допомогою додаткового параметра можна виконувати кілька вимірювань одночасно. +За допомогою необов'язкового параметра можна проводити багаторазові вимірювання. ```php Debugger::timer('page-generating'); -// деякий код +// якийсь код Debugger::timer('rss-generating'); -// деякий код +// якийсь код $rssElapsed = Debugger::timer('rss-generating'); $pageElapsed = Debugger::timer('page-generating'); ``` ```php -Debugger::timer(); // запускає таймер +Debugger::timer(); // вмикає секундомір -... // деяка операція, що займає багато часу +... // операція, що потребує багато часу -echo Debugger::timer(); // час, що пройшов в секундах +echo Debugger::timer(); // виводить минулий час у секундах ``` diff --git a/utils/bg/@home.texy b/utils/bg/@home.texy index 25520611d9..5e8de71cdd 100644 --- a/utils/bg/@home.texy +++ b/utils/bg/@home.texy @@ -1,42 +1,46 @@ -Инструменти +Nette Utils *********** .[perex] -В пакета `nette/utils` ще намерите набор от полезни класове за ежедневна употреба: +В пакета `nette/utils` ще намерите набор от полезни класове за ежедневно използване: -| [Откриване |validators] | Nette\Utils\Откриватели -| [Обратна връзка |Callback] | Nette\Utils\Callback -| [Finder |Finder] | Nette\Utils\Finder -| [Floats |Floats] | Nette\Utils\Floats -| [Генериране на случайни низове |random] | Nette\Utils\Random +| [Callback |Callback] | Nette\Utils\Callback | [Дата и час |datetime] | Nette\Utils\DateTime -| [Nette\Utils\Image |images] -| [Модел на обекта |smartobject] | Nette\SmartObject & Nette\StaticClass -| [Пагинация |paginator] | Nette\Utils\Paginator -| [Парсинг и генериране на JSON |json] | Nette\Utils\Json -| [Полета |arrays] | Nette\Utils\Arrays +| [Finder |Finder] | Nette\Utils\Finder +| [HTML елементи |html-elements] | Nette\Utils\Html +| [Итератори |iterables] | Nette\Utils\Iterables +| [JSON |json] | Nette\Utils\Json +| [Случайни низове |random] | Nette\Utils\Random +| [Изображения |images] | Nette\Utils\Image +| [PHP рефлексия |reflection] | Nette\Utils\Reflection +| [PHP типове |type] | Nette\Utils\Type +| [Масиви |arrays] | Nette\Utils\Arrays +| [Помощни функции |helpers] | Nette\Utils\Helpers +| [Сравняване на float |floats] | Nette\Utils\Floats | [Низове |strings] | Nette\Utils\Strings -| [Тип Nette\Utils\Type |type] | [Файлова система |filesystem] | Nette\Utils\FileSystem -| [Полезни функции | Nette\Utils\Helpers |helpers] -| [HTML елементи |html-elements] | Nette\Utils\Html -| [PHP Reflection |reflection] | Nette\Utils\Reflection +| [Странициране |paginator] | Nette\Utils\Paginator +| [SmartObject |SmartObject] & [StaticClass |StaticClass] | Nette\SmartObject & Nette\StaticClass +| [Валидатор |validators] | Nette\Utils\Validators -Настройка ---------- +Инсталация +---------- -Изтеглете и инсталирайте библиотеката с помощта на [Composer |best-practices:composer]: +Можете да изтеглите и инсталирате библиотеката с помощта на инструмента [Composer |best-practices:composer]: ```shell composer require nette/utils ``` -| PHP съвместима версия +| версия | съвместим с PHP |-----------|------------------- -| Nette Utils 4.0 | PHP 8.0 - 8.2 -| Nette Utils 3.2 | PHP 7.2 - 8.2 -| Nette Utils 3.0 - 3.1 | PHP 7.1 - 8.0 -| Nette Utils 2.5 | PHP 5.6 - 8.0 +| Nette Utils 4.0 | PHP 8.0 – 8.4 +| Nette Utils 3.2 | PHP 7.2 – 8.3 +| Nette Utils 3.0 – 3.1 | PHP 7.1 – 8.0 +| Nette Utils 2.5 | PHP 5.6 – 8.0 + +Важи за последната patch версия. + -Валидно за последната версия на кръпката. +Ако актуализирате пакета до по-нова версия, погледнете страницата [надграждане |en:upgrading]. diff --git a/utils/bg/@left-menu.texy b/utils/bg/@left-menu.texy index 9d14fb22d2..2d9375b6da 100644 --- a/utils/bg/@left-menu.texy +++ b/utils/bg/@left-menu.texy @@ -1,26 +1,28 @@ -Пакет Nette/utils -***************** -- [Валидатор |validators] -- [Търсачка |Finder] -- [Поплавъци |Floats] -- [JSON |JSON] +Nette Utils +*********** +- [Callbacks |callback] - [Дата и час |datetime] -- [Снимки |images] -- [Обратни извиквания |callback] -- [Пагинатор |paginator] -- [Поле |arrays] +- [Finder |Finder] +- [Floats |Floats] +- [HTML елементи |html-elements] +- [Итератори |iterables] +- [JSON |JSON] - [Случайни низове |random] -- [Струни |strings] -- [Типове PHP |type] -- [Файлова система |filesystem] +- [Изображения |images] +- [Paginator |paginator] +- [PHP Рефлексия |reflection] +- [PHP типове |type] +- [Масиви |arrays] - [Помощни функции |helpers] -- [Елементи на HTML |html-elements] -- [Размисли за PHP |reflection] -- [SmartObject |smartobject] +- [Низове |strings] +- [SmartObject |SmartObject] +- [StaticClass |StaticClass] +- [Файлова система |filesystem] +- [Валидатор |validators] Други инструменти ***************** -- [NEON |neon:] -- [Събиране на пароли |security:passwords] -- [Анализиране и сбиване на URL адреси |http:urls] +- [NEON|neon:] +- [Хеширане на пароли |security:passwords] +- [Разбор и съставяне на URL |http:urls] diff --git a/utils/bg/@meta.texy b/utils/bg/@meta.texy new file mode 100644 index 0000000000..57804a1127 --- /dev/null +++ b/utils/bg/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документация на Nette}} diff --git a/utils/bg/arrays.texy b/utils/bg/arrays.texy index 820513e2e8..c2f5884283 100644 --- a/utils/bg/arrays.texy +++ b/utils/bg/arrays.texy @@ -1,33 +1,74 @@ -Работа с полето +Работа с масиви *************** .[perex] -Тази страница е посветена на класовете [Nette\Utils\Arrays |#Arrays], [ArrayHash |#ArrayHash] и [ArrayList |#ArrayList], които са свързани с масиви. +Тази страница е посветена на класовете [Nette\Utils\Arrays |#Arrays], [#ArrayHash] и [#ArrayList], които се отнасят до масиви. -Монтаж: +Инсталация: ```shell composer require nette/utils ``` -Масиви .[#toc-arrays] -===================== +Arrays +====== -[api:Nette\Utils\Arrays] е статичен клас, който съдържа полезни функции за работа с масиви. +[api:Nette\Utils\Arrays] е статичен клас, съдържащ полезни функции за работа с масиви. Негов аналог за итератори е [Nette\Utils\Iterables|iterables]. -Следващите примери предполагат, че псевдонимът вече е създаден: +Следващите примери предполагат създаден псевдоним (alias): ```php use Nette\Utils\Arrays; ``` +associate(array $array, mixed $path): array|\stdClass .[method] +--------------------------------------------------------------- + +Функцията гъвкаво трансформира масива `$array` в асоциативен масив или обекти според зададения път `$path`. Пътят може да бъде низ или масив. Той се състои от имената на ключовете на входния масив и оператори като '[]', '->', '=' и '|'. Хвърля `Nette\InvalidArgumentException`, ако пътят е невалиден. + +```php +// преобразуване в асоциативен масив по прост ключ +$arr = [ + ['name' => 'John', 'age' => 11], + ['name' => 'Mary', 'age' => null], + // ... +]; +$result = Arrays::associate($arr, 'name'); +// $result = ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// присвояване на стойности от един ключ на друг с използване на оператора = +$result = Arrays::associate($arr, 'name=age'); // или ['name', '=', 'age'] +// $result = ['John' => 11, 'Mary' => null, ...] +``` + +```php +// създаване на обект с използване на оператора -> +$result = Arrays::associate($arr, '->name'); // или ['->', 'name'] +// $result = (object) ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// комбинация от ключове с помощта на оператора | +$result = Arrays::associate($arr, 'name|age'); // или ['name', '|', 'age'] +// $result: ['John' => ['name' => 'John', 'age' => 11], 'Paul' => ['name' => 'Paul', 'age' => 44]] +``` + +```php +// добавяне към масив с използване на [] +$result = Arrays::associate($arr, 'name[]'); // или ['name', '[]'] +// $result: ['John' => [['name' => 'John', 'age' => 22], ['name' => 'John', 'age' => 11]]] +``` + + contains(array $array, $value): bool .[method] ---------------------------------------------- -Проверява масива за стойност. Използва стриктно сравнение (`===`). +Тества масива за наличие на стойност. Използва стриктно сравнение (`===`). ```php Arrays::contains([1, 2, 3], 1); // true @@ -35,10 +76,10 @@ Arrays::contains(['1', false], 1); // false ``` -every(iterable $array, callable $callback): bool .[method] ----------------------------------------------------------- +every(array $array, callable $predicate): bool .[method] +-------------------------------------------------------- -Проверява дали всички елементи в масива са преминали теста, реализиран в `$callback` с подпис `function ($value, $key, array $array): bool`. +Тества дали всички елементи в масива преминават теста, имплементиран в `$predicate` със сигнатура `function ($value, $key, array $array): bool`. ```php $array = [1, 30, 39, 29, 10, 13]; @@ -46,24 +87,59 @@ $isBelowThreshold = fn($value) => $value < 40; $res = Arrays::every($array, $isBelowThreshold); // true ``` -Вижте [some( |#some]). +Вижте [#some()]. -first(array $array): mixed .[method] ------------------------------------- +filter(array $array, callable $predicate): array .[method]{data-version:4.0.4} +------------------------------------------------------------------------------ + +Връща нов масив, съдържащ всички двойки ключ-стойност, отговарящи на зададения предикат. Callback-ът има сигнатура `function ($value, int|string $key, array $array): bool`. + +```php +Arrays::filter( + ['a' => 1, 'b' => 2, 'c' => 3], + fn($v) => $v < 3, +); +// ['a' => 1, 'b' => 2] +``` + + +first(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------- -Връща първия запис в масива или null, ако масивът е празен. Не променя вътрешния указател, за разлика от `reset()`. +Връща първия елемент (отговарящ на предиката, ако е зададен). Ако такъв елемент не съществува, връща резултата от извикването на `$else` или null. Параметърът `$predicate` има сигнатура `function ($value, int|string $key, array $array): bool`. + +Не променя вътрешния указател, за разлика от `reset()`. Параметрите `$predicate` и `$else` съществуват от версия 4.0.4. + +```php +Arrays::first([1, 2, 3]); // 1 +Arrays::first([1, 2, 3], fn($v) => $v > 2); // 3 +Arrays::first([]); // null +Arrays::first([], else: fn() => false); // false +``` + +Вижте [#last()]. + + +firstKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +------------------------------------------------------------------------------------------------ + +Връща ключа на първия елемент (отговарящ на предиката, ако е посочен) или null, ако такъв елемент не съществува. Предикатът `$predicate` има сигнатура `function ($value, int|string $key, array $array): bool`. ```php -Arrays::first([1, 2, 3]); // 1 -Масиви::first([]); // null +Arrays::firstKey([1, 2, 3]); // 0 +Arrays::firstKey([1, 2, 3], fn($v) => $v > 2); // 2 +Arrays::firstKey(['a' => 1, 'b' => 2]); // 'a' +Arrays::firstKey([]); // null ``` +Вижте [#lastKey()]. + flatten(array $array, bool $preserveKeys=false): array .[method] ---------------------------------------------------------------- -Консолидира масив от няколко нива в плосък масив. +Обединява многоизмерен масив в плосък. ```php $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); @@ -71,16 +147,16 @@ $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); ``` -get(array $array, string|int|array $key, mixed $default=null): mixed .[method] ------------------------------------------------------------------------------- +get(array $array, string|int|array $key, ?mixed $default=null): mixed .[method] +------------------------------------------------------------------------------- -Връща елемент `$array[$key]`. Ако той не съществува, се изписва изключение `Nette\InvalidArgumentException` или, ако е зададен трети параметър `$default`, се връща този параметър. +Връща елемента `$array[$key]`. Ако не съществува, хвърля изключение `Nette\InvalidArgumentException` или, ако е посочен трети параметър `$default`, връща него. ```php -// ако $array['foo'] не съществува, се хвърля изключение +// ако $array['foo'] не съществува, хвърля изключение $value = Arrays::get($array, 'foo'); -// ако $array['foo'] не съществува, върнете 'bar' +// ако $array['foo'] не съществува, връща 'bar' $value = Arrays::get($array, 'foo', 'bar'); ``` @@ -90,32 +166,32 @@ $value = Arrays::get($array, 'foo', 'bar'); $array = ['color' => ['favorite' => 'red'], 5]; $value = Arrays::get($array, ['color', 'favorite']); -// vrátí 'red' +// връща 'red' ``` getRef(array &$array, string|int|array $key): mixed .[method] ------------------------------------------------------------- -Получава референция към посочения елемент на масива. Ако елементът не съществува, той ще бъде създаден със стойност null. +Получава референция към зададения елемент на масива. Ако елементът не съществува, ще бъде създаден със стойност null. ```php $valueRef = & Arrays::getRef($array, 'foo'); -// връща препратка към $array['foo'] +// връща референция към $array['foo'] ``` -Подобно на функцията [get() |#get], тя може да обработва многомерни масиви. +Подобно на функцията [#get()], може да работи с многоизмерни масиви. ```php $value = & Arrays::getRef($array, ['color', 'favorite']); -// vrátí referenci na $array['color']['favorite'] +// връща референция към $array['color']['favorite'] ``` grep(array $array, string $pattern, bool $invert=false): array .[method] ------------------------------------------------------------------------ -Връща само елементите на масива, чиято стойност отговаря на регулярния израз `$pattern`. Ако `$invert` е равно на `true`, се връщат елементи, които не съвпадат. Грешка при компилиране или изразяване хвърля изключение `Nette\RegexpException`. +Връща само тези елементи на масива, чиято стойност съответства на регулярния израз `$pattern`. Ако `$invert` е `true`, връща елементите, които не съответстват. Грешка при компилация или обработка на израза хвърля изключение `Nette\RegexpException`. ```php $filteredArray = Arrays::grep($array, '~^\d+$~'); @@ -126,7 +202,7 @@ $filteredArray = Arrays::grep($array, '~^\d+$~'); insertAfter(array &$array, string|int|null $key, array $inserted): void .[method] --------------------------------------------------------------------------------- -Вмъква съдържанието на полето `$inserted` в полето `$array` веднага след елемента с ключ `$key`. Ако `$key` е `null` (или не е в полето), той се вмъква в края. +Вмъква съдържанието на масива `$inserted` в масива `$array` веднага след елемента с ключ `$key`. Ако `$key` е `null` (или не е в масива), се вмъква в края. ```php $array = ['first' => 10, 'second' => 20]; @@ -138,7 +214,7 @@ Arrays::insertAfter($array, 'first', ['hello' => 'world']); insertBefore(array &$array, string|int|null $key, array $inserted): void .[method] ---------------------------------------------------------------------------------- -Вмъква съдържанието на полето `$inserted` в полето `$array` преди елемента с ключ `$key`. Ако `$key` е `null` (или не е в полето), той се вмъква в началото. +Вмъква съдържанието на масива `$inserted` в масива `$array` преди елемента с ключ `$key`. Ако `$key` е `null` (или не е в масива), се вмъква в началото. ```php $array = ['first' => 10, 'second' => 20]; @@ -150,7 +226,7 @@ Arrays::insertBefore($array, 'first', ['hello' => 'world']); invoke(iterable $callbacks, ...$args): array .[method] ------------------------------------------------------ -Извиква всички обратни повиквания и връща масив от резултати. +Извиква всички callback-ове и връща масив с резултатите. ```php $callbacks = [ @@ -166,7 +242,7 @@ $array = Arrays::invoke($callbacks, 5, 11); invokeMethod(iterable $objects, string $method, ...$args): array .[method] -------------------------------------------------------------------------- -Извиква метод за всеки обект в масива и връща масив от резултати. +Извиква метод върху всеки обект в масива и връща масив с резултатите. ```php $objects = ['a' => $obj1, 'b' => $obj2]; @@ -179,7 +255,7 @@ $array = Arrays::invokeMethod($objects, 'foo', 1, 2); isList(array $array): bool .[method] ------------------------------------ -Проверява се дали масивът е индексиран с нарастващ брой числови ключове, започвайки от нула, т.е. списък. +Проверява дали масивът е индексиран по възходяща поредица от числови ключове от нула, т.е. списък (list). ```php Arrays::isList(['a', 'b', 'c']); // true @@ -188,21 +264,42 @@ Arrays::isList(['a' => 1, 'b' => 2]); // false ``` -last(array $array): mixed .[method] ------------------------------------ +last(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------ + +Връща последния елемент (отговарящ на предиката, ако е зададен). Ако такъв елемент не съществува, връща резултата от извикването на `$else` или null. Параметърът `$predicate` има сигнатура `function ($value, int|string $key, array $array): bool`. + +Не променя вътрешния указател, за разлика от `end()`. Параметрите `$predicate` и `$else` съществуват от версия 4.0.4. + +```php +Arrays::last([1, 2, 3]); // 3 +Arrays::last([1, 2, 3], fn($v) => $v < 3); // 2 +Arrays::last([]); // null +Arrays::last([], else: fn() => false); // false +``` + +Вижте [#first()]. + + +lastKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +----------------------------------------------------------------------------------------------- -Връща последния запис на масива или null, ако масивът е празен. Не променя вътрешния указател, за разлика от `end()`. +Връща ключа на последния елемент (отговарящ на предиката, ако е посочен) или null, ако такъв елемент не съществува. Предикатът `$predicate` има сигнатура `function ($value, int|string $key, array $array): bool`. ```php -Arrays::last([1, 2, 3]); // 3 -Arrays::last([]); // null +Arrays::lastKey([1, 2, 3]); // 2 +Arrays::lastKey([1, 2, 3], fn($v) => $v < 3); // 1 +Arrays::lastKey(['a' => 1, 'b' => 2]); // 'b' +Arrays::lastKey([]); // null ``` +Вижте [#firstKey()]. -map(iterable $array, callable $callback): array .[method] + +map(array $array, callable $transformer): array .[method] --------------------------------------------------------- -Извиква `$callback` върху всички елементи на масив и връща масив от върнати стойности. Обратното извикване има сигнатура `function ($value, $key, array $array): bool`. +Извиква `$transformer` върху всички елементи в масива и връща масив с върнатите стойности. Callback-ът има сигнатура `function ($value, $key, array $array): mixed`. ```php $array = ['foo', 'bar', 'baz']; @@ -211,10 +308,24 @@ $res = Arrays::map($array, fn($value) => $value . $value); ``` +mapWithKeys(array $array, callable $transformer): array .[method] +----------------------------------------------------------------- + +Създава нов масив чрез трансформиране на стойностите и ключовете на оригиналния масив. Функцията `$transformer` има сигнатура `function ($value, $key, array $array): ?array{$newKey, $newValue}`. Ако `$transformer` върне `null`, елементът се пропуска. За запазените елементи първият елемент на върнатия масив се използва като нов ключ, а вторият елемент като нова стойност. + +```php +$array = ['a' => 1, 'b' => 2]; +$result = Arrays::mapWithKeys($array, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +Този метод е полезен в ситуации, когато трябва да промените структурата на масива (ключове и стойности едновременно) или да филтрирате елементи по време на трансформация (чрез връщане на null за нежелани елементи). + + mergeTree(array $array1, array $array2): array .[method] -------------------------------------------------------- -Рекурсивно комбинира две полета. Това е полезно например за обединяване на дървовидни структури. При сливането той следва същите правила като оператора `+`, прилаган за масиви, т.е. добавя двойката ключ/стойност от втория масив към първия масив и оставя стойността от първия масив в случай на сблъсък на ключове. +Рекурсивно обединява два масива. Подходящ е например за обединяване на дървовидни структури. При обединяването се ръководи от същите правила като оператора `+`, приложен към масиви, т.е. към първия масив добавя двойки ключ/стойност от втория масив и в случай на колизия на ключове запазва стойността от първия масив. ```php $array1 = ['color' => ['favorite' => 'red'], 5]; @@ -224,13 +335,13 @@ $array = Arrays::mergeTree($array1, $array2); // $array = ['color' => ['favorite' => 'red', 'blue'], 5]; ``` -Стойностите от втория масив винаги се добавят към края на първия масив. Изчезването на стойността `10` от второто поле може да изглежда малко объркващо. Обърнете внимание, че тази стойност е същата като стойността `5` v poli prvním mají přiřazený stejný numerický klíč `0`, така че само елементът от първото поле влиза в получения масив. +Стойностите от втория масив винаги се добавят в края на първия. Малко объркващо може да изглежда изчезването на стойността `10` от втория масив. Трябва да се има предвид, че тази стойност, както и стойността `5` в първия масив, имат присвоен един и същ числов ключ `0`, затова в резултантния масив има само елемент от първия масив. -normalize(array $array, string $filling=null): array .[method] --------------------------------------------------------------- +normalize(array $array, ?string $filling=null): array .[method] +--------------------------------------------------------------- -Нормализира масива до асоциативен масив. Заменя ключовете с числа с техните стойности, като новата стойност е `$filling`. +Нормализира масива до асоциативен масив. Числовите ключове се заменят с техните стойности, новата стойност ще бъде `$filling`. ```php $array = Arrays::normalize([1 => 'first', 'a' => 'second']); @@ -243,14 +354,14 @@ $array = Arrays::normalize([1 => 'first', 'a' => 'second'], 'foobar'); ``` -pick(array &$array, string|int $key, mixed $default=null): mixed .[method] --------------------------------------------------------------------------- +pick(array &$array, string|int $key, ?mixed $default=null): mixed .[method] +--------------------------------------------------------------------------- -Връща и премахва стойността на елемент от масива. Ако не съществува, се хвърля изключение или се връща стойността `$default`, ако съществува. +Връща и премахва стойността на елемент от масива. Ако не съществува, хвърля изключение или връща стойността `$default`, ако е посочена. ```php -$array = [1 => 'foo', null => 'bar']; -$a = Arrays::pick($array, null); +$array = [1 => 'foo', 'x' => 'bar']; +$a = Arrays::pick($array, 'x'); // $a = 'bar' $b = Arrays::pick($array, 'not-exists', 'foobar'); // $b = 'foobar' @@ -262,7 +373,7 @@ $c = Arrays::pick($array, 'not-exists'); renameKey(array &$array, string|int $oldKey, string|int $newKey): bool .[method] -------------------------------------------------------------------------------- -Преименуване на ключ в масив. Връща `true`, ако ключът е намерен в масива. +Преименува ключ в масива. Връща `true`, ако ключът е намерен в масива. ```php $array = ['first' => 10, 'second' => 20]; @@ -274,20 +385,20 @@ Arrays::renameKey($array, 'first', 'renamed'); getKeyOffset(array $array, string|int $key): ?int .[method] ----------------------------------------------------------- -Връща позицията на посочения ключ в масива. Позицията се номерира от 0. Ако не е намерен ключ, функцията връща `null`. +Връща позицията на даден ключ в масива. Позицията се номерира от 0. В случай, че ключът не бъде намерен, функцията връща `null`. ```php $array = ['first' => 10, 'second' => 20]; -$position = Arrays::getKeyOffset($array, 'first'); // vrátí 0 -$position = Arrays::getKeyOffset($array, 'second'); // vrátí 1 -$position = Arrays::getKeyOffset($array, 'not-exists'); // vrátí null +$position = Arrays::getKeyOffset($array, 'first'); // връща 0 +$position = Arrays::getKeyOffset($array, 'second'); // връща 1 +$position = Arrays::getKeyOffset($array, 'not-exists'); // връща null ``` -some(iterable $array, callable $callback): bool .[method] ---------------------------------------------------------- +some(array $array, callable $predicate): bool .[method] +------------------------------------------------------- -Проверява се дали поне един елемент от масива е преминал теста, реализиран в `$callback`, с подпис `function ($value, $key, array $array): bool`. +Тества дали поне един елемент в масива преминава теста, имплементиран в `$predicate` със сигнатура `function ($value, $key, array $array): bool`. ```php $array = [1, 2, 3, 4]; @@ -295,13 +406,13 @@ $isEven = fn($value) => $value % 2 === 0; $res = Arrays::some($array, $isEven); // true ``` -Вижте [Every() |#every]. +Вижте [#every()]. toKey(mixed $key): string|int .[method] --------------------------------------- -Конвертира стойността в ключ на масив, който е цяло число или низ. +Преобразува стойност в ключ на масив, който е или цяло число (integer), или низ. ```php Arrays::toKey('1'); // 1 @@ -312,19 +423,19 @@ Arrays::toKey('01'); // '01' toObject(iterable $array, object $object): object .[method] ----------------------------------------------------------- -Копира елементите на масива `$array` в обект `$object`, който след това връща. +Копира елементите на масива `$array` в обекта `$object`, който след това връща. ```php $obj = new stdClass; $array = ['foo' => 1, 'bar' => 2]; -Arrays::toObject($array, $obj); // nastaví $obj->foo = 1; $obj->bar = 2; +Arrays::toObject($array, $obj); // задава $obj->foo = 1; $obj->bar = 2; ``` -wrap(iterable $array, string $prefix='', string $suffix=''): array .[method] ----------------------------------------------------------------------------- +wrap(array $array, string $prefix='', string $suffix=''): array .[method] +------------------------------------------------------------------------- -Извежда всеки елемент от масива в низ и го обвива с префикс `$prefix` и суфикс `$suffix`. +Всеки елемент в масива се преобразува в низ и се обвива с префикс `$prefix` и суфикс `$suffix`. ```php $array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); @@ -332,21 +443,21 @@ $array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); ``` -ArrayHash .[#toc-arrayhash] -=========================== +ArrayHash +========= -Обектът [api:Nette\Utils\ArrayHash] е наследник на общия клас stdClass и го разширява с възможността да се третира като масив, т.е. да се отнася към членовете му например чрез квадратни скоби: +Обектът [api:Nette\Utils\ArrayHash] е наследник на генеричния клас stdClass и го разширява с възможността да се работи с него като с масив, т.е. например да се достъпват членовете чрез квадратни скоби: ```php $hash = new Nette\Utils\ArrayHash; $hash['foo'] = 123; -$hash->bar = 456; // записът на обекти работи едновременно +$hash->bar = 456; // едновременно работи и обектният запис $hash->foo; // 123 ``` -Можете да използвате функцията `count($hash)`, за да получите броя на членовете. +Може да се използва функцията `count($hash)` за установяване на броя на елементите. -Можете да итерирате над обект, както при масив, дори при референция: +Над обекта може да се итерира по същия начин, както при масив, включително и с референция: ```php foreach ($hash as $key => $value) { @@ -354,11 +465,11 @@ foreach ($hash as $key => $value) { } foreach ($hash as $key => &$value) { - $value = 'нова стойност'; + $value = 'new value'; } ``` -Можем да преобразуваме съществуващ масив в `ArrayHash`, като използваме метода `from()`: +Съществуващ масив можем да трансформираме в `ArrayHash` с метода `from()`: ```php $array = ['foo' => 123, 'bar' => 456]; @@ -379,24 +490,24 @@ $hash->inner->a; // 'b' $hash['inner']['a']; // 'b' ``` -Това може да се предотврати чрез използване на втори параметър: +Това може да се предотврати с втория параметър: ```php $hash = Nette\Utils\ArrayHash::from($array, false); -$hash->inner; // полюс +$hash->inner; // масив ``` -Преобразуване обратно в масив: +Трансформация обратно в масив: ```php $array = (array) $hash; ``` -ArrayList .[#toc-arraylist] -=========================== +ArrayList +========= -[api:Nette\Utils\ArrayList] е линеен масив, в който като индекси се използват само цели числа, нарастващи от 0. +[api:Nette\Utils\ArrayList] представлява линеен масив, където индексите са само цели числа, нарастващи от 0. ```php $list = new Nette\Utils\ArrayList; @@ -407,16 +518,16 @@ $list[] = 'c'; count($list); // 3 ``` -Съществуващите масиви могат да бъдат преобразувани в `ArrayList` с помощта на метода `from()`: +Съществуващ масив можем да трансформираме в `ArrayList` с метода `from()`: ```php $array = ['foo', 'bar']; $list = Nette\Utils\ArrayList::from($array); ``` -Можете да използвате функцията `count($list)`, за да получите броя на елементите. +Може да се използва функцията `count($list)` за установяване на броя на елементите. -Можете да итерирате над обект, както в случая с масив, дори с референция: +Над обекта може да се итерира по същия начин, както при масив, включително и с референция: ```php foreach ($list as $key => $value) { @@ -424,25 +535,25 @@ foreach ($list as $key => $value) { } foreach ($list as $key => &$value) { - $value = 'нова стойност'; + $value = 'new value'; } ``` -Достъпът до ключове извън разрешените стойности води до изключение `Nette\OutOfRangeException`: +Достъпът до ключове извън разрешените стойности хвърля изключение `Nette\OutOfRangeException`: ```php -echo $list[-1]; // предизвиква Nette\OutOfRangeException -unset($list[30]); // предизвиква Nette\OutOfRangeException +echo $list[-1]; // хвърля Nette\OutOfRangeException +unset($list[30]); // хвърля Nette\OutOfRangeException ``` -Премахването на клавиш ще доведе до промяна на номерацията на елементите: +Премахването на ключ води до преномериране на елементите: ```php unset($list[1]); // ArrayList(0 => 'a', 1 => 'c') ``` -Нов елемент може да бъде добавен в началото чрез метода `prepend()`: +Нов елемент може да се добави в началото с метода `prepend()`: ```php $list->prepend('d'); diff --git a/utils/bg/callback.texy b/utils/bg/callback.texy index 13da51ad14..e1c5626871 100644 --- a/utils/bg/callback.texy +++ b/utils/bg/callback.texy @@ -1,17 +1,17 @@ -Работа с обратни извиквания -*************************** +Работа с callback-ове +********************* .[perex] -[api:Nette\Utils\Callback] е статичен клас с функции за работа с [обратни повиквания на PHP |https://www.php.net/manual/en/language.types.callable.php]. +[api:Nette\Utils\Callback] е статичен клас с функции за работа с [PHP callback-ове |https://www.php.net/manual/en/language.types.callable.php]. -Монтаж: +Инсталация: ```shell composer require nette/utils ``` -Всички примери предполагат, че псевдонимът вече е създаден: +Всички примери предполагат създаден псевдоним (alias): ```php use Nette\Utils\Callback; @@ -21,7 +21,7 @@ use Nette\Utils\Callback; check($callable, bool $syntax=false): callable .[method] -------------------------------------------------------- -Проверява дали променливата `$callable` е валидна обратна връзка. Ако това не е така, `Nette\InvalidArgumentException` се изхвърля . Ако `$syntax` е true, функцията проверява само дали `$callable` има структура за обратна връзка, но не проверява дали класът или методът действително съществуват. Той връща `$callable`. +Проверява дали променливата `$callable` е валиден callback. В противен случай хвърля `Nette\InvalidArgumentException`. Ако `$syntax` е true, функцията само проверява дали `$callable` има структурата на callback, но не проверява дали даденият клас или метод действително съществува. Връща `$callable`. ```php Callback::check('trim'); // не хвърля изключение @@ -35,7 +35,7 @@ Callback::check(null); // хвърля Nette\InvalidArgumentException toString($callable): string .[method] ------------------------------------- -Преобразува обратната връзка на PHP в текстова форма. Не е задължително класът или методът да съществува. +Преобразува PHP callback в текстова форма. Класът или методът не е необходимо да съществуват. ```php Callback::toString('trim'); // 'trim' @@ -46,21 +46,21 @@ Callback::toString(['MyClass', 'method']); // 'MyClass::method' toReflection($callable): ReflectionMethod|ReflectionFunction .[method] ---------------------------------------------------------------------- -Връща отражение за метода или функцията в обратната връзка на PHP. +Връща рефлексия за метода или функцията в PHP callback. ```php $ref = Callback::toReflection('trim'); -// $ref je ReflectionFunction('trim') +// $ref е ReflectionFunction('trim') $ref = Callback::toReflection(['MyClass', 'method']); -// $ref je ReflectionMethod('MyClass', 'method') +// $ref е ReflectionMethod('MyClass', 'method') ``` isStatic($callable): bool .[method] ----------------------------------- -Определя дали обратното извикване на PHP е функция или статичен метод. +Установява дали PHP callback е функция или статичен метод. ```php Callback::isStatic('trim'); // true @@ -73,7 +73,7 @@ Callback::isStatic(function () {}); // false unwrap(Closure $closure): callable|array .[method] -------------------------------------------------- -Разопаковайте затвора, създаден с `Closure::fromCallable`:https://www.php.net/manual/en/closure.fromcallable.php. +Обратно разгръща Closure, създадена с помощта на `Closure::fromCallable`:https://www.php.net/manual/en/closure.fromcallable.php. ```php $closure = Closure::fromCallable(['MyClass', 'method']); diff --git a/utils/bg/datetime.texy b/utils/bg/datetime.texy index 7c98e521c0..dec2cc20a0 100644 --- a/utils/bg/datetime.texy +++ b/utils/bg/datetime.texy @@ -2,16 +2,16 @@ ********** .[perex] -[api:Nette\Utils\DateTime] - е клас, който разширява родния DateTime с допълнителни функции. +[api:Nette\Utils\DateTime] е клас, който разширява нативния [php:DateTime] с допълнителни функции. -Монтаж: +Инсталация: ```shell composer require nette/utils ``` -Всички примери предполагат създаден псевдоним: +Всички примери предполагат създаден псевдоним (alias): ```php use Nette\Utils\DateTime; @@ -20,27 +20,27 @@ use Nette\Utils\DateTime; static from(string|int|\DateTimeInterface $time): DateTime .[method] -------------------------------------------------------------------- -Можете да изберете обект DateTime z řetězce, UNIX timestamp или друг обект [php:DateTimeInterface]. Vyhodí výjimku `Exception`, pokud datum a čas není platný. +Създава обект DateTime от низ, UNIX timestamp или друг обект [php:DateTimeInterface]. Хвърля изключение `Exception`, ако датата и часът не са валидни. ```php -DateTime::from(1138013640); // создает DateTime из временной метки UNIX с часовым поясом по умолчанию -DateTime::from(42); // создает DateTime из текущего времени плюс 42 секунды -DateTime::from('1994-02-26 04:15:32'); // создает DateTime из строки -DateTime::from('1994-02-26'); // создает DateTime по дате, время будет 00:00:00 +DateTime::from(1138013640); // създава DateTime от UNIX timestamp с часова зона по подразбиране +DateTime::from(42); // създава DateTime от текущото време плюс 42 секунди +DateTime::from('1994-02-26 04:15:32'); // създава DateTime според низ +DateTime::from('1994-02-26'); // създава DateTime според дата, часът ще бъде 00:00:00 ``` static fromParts(int $year, int $month, int $day, int $hour=0, int $minute=0, float $second=0.0): DateTime .[method] -------------------------------------------------------------------------------------------------------------------- -Създайте обект DateTime и го изведете на адрес `Nette\InvalidArgumentException`, където ще бъдат показани датата и нейната стойност. +Създава обект DateTime или хвърля изключение `Nette\InvalidArgumentException`, ако датата и часът не са валидни. ```php DateTime::fromParts(1994, 2, 26, 4, 15, 32); ``` -static createFromFormat(string $format, string $time, string|\DateTimeZone $timezone=null): DateTime|false .[method] --------------------------------------------------------------------------------------------------------------------- -Разширява функцията [DateTime::createFromFormat( |https://www.php.net/manual/en/datetime.createfromformat.php] ) с възможност за въвеждане на часовата зона като низ. +static createFromFormat(string $format, string $time, ?string|\DateTimeZone $timezone=null): DateTime|false .[method] +--------------------------------------------------------------------------------------------------------------------- +Разширява [DateTime::createFromFormat()|https://www.php.net/manual/en/datetime.createfromformat.php] с възможността да се зададе часова зона като низ. ```php DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); ``` @@ -48,7 +48,7 @@ DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); modifyClone(string $modify=''): static .[method] ------------------------------------------------ -Създайте копие с повишена часова зона. +Създава копие с променено време. ```php $original = DateTime::from('2017-02-03'); $clone = $original->modifyClone('+1 day'); @@ -59,15 +59,15 @@ $clone->format('Y-m-d'); // '2017-02-04' __toString(): string .[method] ------------------------------ -Връща датата и часа във формат `Y-m-d H:i:s`. +Връща дата и час във формат `Y-m-d H:i:s`. ```php echo $dateTime; // '2017-02-03 04:15:32' ``` -Реализиране на JsonSerializable .[#toc-implements-jsonserializable] -------------------------------------------------------------------- -Връща датата и часа във формат ISO 8601, който се използва например в JavaScript. +имплементира JsonSerializable +----------------------------- +Връща дата и час във формат ISO 8601, който се използва например в JavaScript. ```php $date = DateTime::from('2017-02-03'); echo json_encode($date); diff --git a/utils/bg/filesystem.texy b/utils/bg/filesystem.texy index c17d8ffb79..435ae4b88c 100644 --- a/utils/bg/filesystem.texy +++ b/utils/bg/filesystem.texy @@ -1,41 +1,43 @@ -Функции на файловата система -**************************** +Файлова система +*************** .[perex] -[api:Nette\Utils\FileSystem] е статичен клас, който съдържа полезни функции за работа с файлова система. Едно от предимствата им пред нативните функции на PHP е, че те хвърлят изключения в случай на грешки. +[api:Nette\Utils\FileSystem] е клас с полезни функции за работа с файловата система. Едно от предимствата пред нативните PHP функции е, че в случай на грешка хвърлят изключения. +Ако трябва да търсите файлове на диска, използвайте [Finder |finder]. + Инсталация: ```shell composer require nette/utils ``` -Следващите примери предполагат, че е дефиниран следният псевдоним на клас: +Следващите примери предполагат създаден псевдоним: ```php use Nette\Utils\FileSystem; ``` -Манипулация .[#toc-manipulation] -================================ +Манипулация +=========== copy(string $origin, string $target, bool $overwrite=true): void .[method] -------------------------------------------------------------------------- -Копира файл или цяла директория. По подразбиране презаписва съществуващи файлове и директории. Ако `$overwrite` е зададен на `false` и вече съществува `$target`, се хвърля изключение `Nette\InvalidStateException`. Хвърля изключение `Nette\IOException` при възникнала грешка. +Копира файл или цяла директория. По подразбиране презаписва съществуващи файлове и директории. С параметър `$overwrite`, зададен на `false`, хвърля изключение `Nette\InvalidStateException`, ако целевият файл или директория `$target` съществува. При грешка хвърля изключение `Nette\IOException`. ```php FileSystem::copy('/path/to/source', '/path/to/dest', overwrite: true); ``` -createDir(string $directory, int $mode=0777): void .[method] ------------------------------------------------------------- +createDir(string $dir, int $mode=0777): void .[method] +------------------------------------------------------ -Създава директория, ако тя не съществува, включително родителските директории. Хвърля изключение `Nette\IOException` при възникнала грешка. +Създава директория, ако не съществува, включително родителските директории. При грешка хвърля изключение `Nette\IOException`. ```php FileSystem::createDir('/path/to/dir'); @@ -45,7 +47,7 @@ FileSystem::createDir('/path/to/dir'); delete(string $path): void .[method] ------------------------------------ -Изтрива файл или цяла директория, ако съществува. Ако директорията не е празна, първо се изтрива нейното съдържание. Хвърля изключение `Nette\IOException` при възникнала грешка. +Изтрива файл или цяла директория, ако съществува. Ако директорията не е празна, първо изтрива нейното съдържание. При грешка хвърля изключение `Nette\IOException`. ```php FileSystem::delete('/path/to/fileOrDir'); @@ -55,7 +57,7 @@ FileSystem::delete('/path/to/fileOrDir'); makeWritable(string $path, int $dirMode=0777, int $fileMode=0666): void .[method] --------------------------------------------------------------------------------- -Задава разрешения за файлове на `$fileMode` или разрешения за директории на `$dirMode`. Рекурсивно обхожда и задава разрешения и за цялото съдържание на директорията. +Задава правата на файла на `$fileMode` или на директорията на `$dirMode`. Рекурсивно преминава и задава права и на цялото съдържание на директорията. ```php FileSystem::makeWritable('/path/to/fileOrDir'); @@ -65,7 +67,7 @@ FileSystem::makeWritable('/path/to/fileOrDir'); open(string $path, string $mode): resource .[method] ---------------------------------------------------- -Отваря файл и връща ресурс. Параметърът `$mode` работи по същия начин, както родната функция `fopen()`:https://www.php.net/manual/en/function.fopen.php. Ако възникне грешка, тя предизвиква изключение `Nette\IOException`. +Отваря файл и връща ресурс. Параметърът `$mode` работи по същия начин като при нативната функция `fopen()`:https://www.php.net/manual/en/function.fopen.php. В случай на грешка хвърля изключение `Nette\IOException`. ```php $res = FileSystem::open('/path/to/file', 'r'); @@ -75,7 +77,7 @@ $res = FileSystem::open('/path/to/file', 'r'); read(string $file): string .[method] ------------------------------------ -Чете съдържанието на `$file`. Изхвърля изключение `Nette\IOException` при възникване на грешка. +Връща съдържанието на файла `$file`. При грешка хвърля изключение `Nette\IOException`. ```php $content = FileSystem::read('/path/to/file'); @@ -85,14 +87,13 @@ $content = FileSystem::read('/path/to/file'); readLines(string $file, bool $stripNewLines=true): \Generator .[method] ----------------------------------------------------------------------- -Прочита съдържанието на файла ред по ред. За разлика от родната функция `file()`, тя не чете целия файл в паметта, а го чете непрекъснато, така че да могат да се четат файлове, по-големи от наличната памет. Функцията `$stripNewLines` указва дали да се премахнат символите за прекъсване на реда `\r` и `\n`. -В случай на грешка, тя предизвиква изключение `Nette\IOException`. +Прочита съдържанието на файла ред по ред. За разлика от нативната функция [file() |php:file], не зарежда целия файл в паметта, а го чете непрекъснато, така че е възможно да се четат и файлове, по-големи от наличната памет. `$stripNewLines` указва дали да се премахнат знаците за край на ред `\r` и `\n`. В случай на грешка хвърля изключение `Nette\IOException`. ```php $lines = FileSystem::readLines('/path/to/file'); foreach ($lines as $lineNum => $line) { - echo "Line $lineNum: $line\n"; + echo "Ред $lineNum: $line\n"; } ``` @@ -100,7 +101,7 @@ foreach ($lines as $lineNum => $line) { rename(string $origin, string $target, bool $overwrite=true): void .[method] ---------------------------------------------------------------------------- -Преименува или премества файл или директория, посочени от `$origin`, в `$target`. По подразбиране презаписва съществуващите файлове и директории. Ако `$overwrite` е зададен на `false` и `$target` вече съществува, се хвърля изключение `Nette\InvalidStateException`. Изхвърля изключение `Nette\IOException` при възникнала грешка. +Преименува или премества файл или директория `$origin`. По подразбиране презаписва съществуващи файлове и директории. С параметър `$overwrite`, зададен на `false`, хвърля изключение `Nette\InvalidStateException`, ако целевият файл или директория `$target` съществува. При грешка хвърля изключение `Nette\IOException`. ```php FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); @@ -110,21 +111,21 @@ FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); write(string $file, string $content, int $mode=0666): void .[method] -------------------------------------------------------------------- -Записва `$content` в `$file`. Хвърля изключение `Nette\IOException` при възникнала грешка. +Записва низа `$content` във файла `$file`. При грешка хвърля изключение `Nette\IOException`. ```php FileSystem::write('/path/to/file', $content); ``` -Пътища .[#toc-paths] -==================== +Пътища +====== isAbsolute(string $path): bool .[method] ---------------------------------------- -Определя дали `$path` е абсолютен. +Проверява дали пътят `$path` е абсолютен. ```php FileSystem::isAbsolute('../backup'); // false @@ -146,7 +147,7 @@ FileSystem::joinPaths('/a/', '/../b'); // '/b' normalizePath(string $path): string .[method] --------------------------------------------- -Нормализира `..` и `.` и разделителите на директории в пътя. +Нормализира `..` и `.` и разделителите на директории в пътя до системните. ```php FileSystem::normalizePath('/file/.'); // '/file/' @@ -159,7 +160,7 @@ FileSystem::normalizePath('file/../../bar'); // '/../bar' unixSlashes(string $path): string .[method] ------------------------------------------- -Преобразува наклонените черти в `/`, използвани в Unix системите. +Преобразува наклонените черти в `/`, използвани в Unix системи. ```php $path = FileSystem::unixSlashes($path); @@ -169,8 +170,45 @@ $path = FileSystem::unixSlashes($path); platformSlashes(string $path): string .[method] ----------------------------------------------- -Конвертира наклонените черти в символи, характерни за текущата платформа, т.е. `\` в Windows и `/` другаде. +Преобразува наклонените черти в знаци, специфични за текущата платформа, т.е. `\` в Windows и `/` другаде. ```php $path = FileSystem::platformSlashes($path); ``` + + +resolvePath(string $basePath, string $path): string .[method]{data-version:4.0.6} +--------------------------------------------------------------------------------- + +Извежда крайния път от пътя `$path` спрямо базовата директория `$basePath`. Абсолютните пътища (`/foo`, `C:/foo`) остават непроменени (само нормализира наклонените черти), относителните пътища се добавят към базовия път. + +```php +// В Windows наклонените черти в изхода биха били обратни (\) +FileSystem::resolvePath('/base/dir', '/abs/path'); // '/abs/path' +FileSystem::resolvePath('/base/dir', 'rel'); // '/base/dir/rel' +FileSystem::resolvePath('base/dir', '../file.txt'); // 'base/file.txt' +FileSystem::resolvePath('base', ''); // 'base' +``` + + +Статичен срещу нестатичен достъп +================================ + +За да можете например за целите на тестването лесно да замените класа с друг (mock), използвайте го нестатично: + +```php +class AnyClassUsingFileSystem +{ + public function __construct( + private FileSystem $fileSystem, + ) { + } + + public function readConfig(): string + { + return $this->fileSystem->read(/* ... */); + } + + ... +} +``` diff --git a/utils/bg/finder.texy b/utils/bg/finder.texy index b7d2b8f193..941eebffdd 100644 --- a/utils/bg/finder.texy +++ b/utils/bg/finder.texy @@ -1,27 +1,27 @@ -Търсачка: Търсене на файлове -**************************** +Finder: търсене на файлове +************************** .[perex] -Трябва да намерите файлове, отговарящи на определена маска? Търсачката може да ви помогне. Това е универсален и бърз инструмент за преглед на структурата на директориите. +Трябва да намерите файлове, отговарящи на определена маска? Finder ще ви помогне с това. Това е универсален и бърз инструмент за обхождане на структурата на директориите. -Монтаж: +Инсталация: ```shell composer require nette/utils ``` -Примерите предполагат, че псевдонимът вече е създаден: +Примерите предполагат създаден псевдоним: ```php use Nette\Utils\Finder; ``` -Използване на .[#toc-using] ---------------------------- +Използване +---------- -Първо нека видим как [api:Nette\Utils\Finder] може да се използва за изписване на имена на файлове с разширения `.txt` и `.md` в текущата директория: +Първо ще покажем как можете с помощта на [api:Nette\Utils\Finder] да изведете имената на файловете с разширения `.txt` и `.md` в текущата директория: ```php foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { @@ -29,96 +29,97 @@ foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { } ``` -По подразбиране за търсене се използва текущата директория, но можете да я промените чрез методите [in() или from() |#Where to search?]. -Променливата `$file` е инстанция на класа [FileInfo |#FileInfo] с много полезни методи. Ключът `$name` съдържа пътя до файла като низ. +Директорията за търсене по подразбиране е текущата директория, но можете да я промените с методите [in() или from() |#Къде да се търси]. Променливата `$file` е инстанция на класа [#FileInfo] с много полезни методи. В ключа `$name` е пътят до файла като низ. -Какво да търсим? .[#toc-what-to-search-for] -------------------------------------------- +Какво да се търси? +------------------ -В допълнение към метода `findFiles()` има и `findDirectories()`, който претърсва само директории, и `find()`, който претърсва и двете директории. Тези методи са статични, така че могат да бъдат извикани, без да се създава инстанция. Параметърът маска не е задължителен, ако не го посочите, ще се търси във всички директории. +Освен метода `findFiles()` съществува и `findDirectories()`, който търси само директории, и `find()`, който търси и двете. Тези методи са статични, така че могат да бъдат извикани без създаване на инстанция. Параметърът с маската е незадължителен; ако не го посочите, ще се търси всичко. ```php foreach (Finder::find() as $file) { - echo $file; // всички файлове и директории вече са изброени + echo $file; // сега ще се изведат всички файлове и директории } ``` -Използвайте методите `files()` и `directories()`, за да добавите какво още да търсите. Методите могат да се извикват многократно, а като параметър може да се предостави масив от маски: +С помощта на методите `files()` и `directories()` можете да допълвате какво още да се търси. Методите могат да се извикват многократно и като параметър може да се посочи и масив от маски: ```php Finder::findDirectories('vendor') // всички директории ->files(['*.php', '*.phpt']); // плюс всички PHP файлове ``` -Алтернативата на статичните методи е да създадете инстанция с помощта на `new Finder` (свеж обект, създаден по този начин, не търси нищо) и да посочите какво да се търси с помощта на `files()` и `directories()`: +Алтернатива на статичните методи е създаването на инстанция с помощта на `new Finder` (така създаденият нов обект не търси нищо) и посочване какво да се търси с помощта на `files()` и `directories()`: ```php (new Finder) - ->directories() //всички директории - ->files('*.php'); // плюс всички PHP файлове + ->directories() // всички директории + ->files('*.php'); // плюс всички PHP файлове ``` -Можете да използвате [заместващи символи |#wildcards] `*`, `**`, `?` and `[...]` в маската. Можете дори да посочите директории, например `src/*.php` ще търси всички PHP файлове в директорията `src`. +В маската можете да използвате [#плейсхолдери] `*`, `**`, `?` и `[...]`. Можете дори да посочите директории, например `src/*.php` ще намери всички PHP файлове в директорията `src`. +Символните връзки (symlinks) също се считат за директории или файлове. -Къде да търсим? .[#toc-where-to-search] ---------------------------------------- -Директорията за търсене по подразбиране е текущата директория. Можете да промените това чрез методите `in()` и `from()`. Както можете да видите от имената на методите, `in()` търси само в текущата директория, докато `from()` търси и в нейните поддиректории (рекурсивно). Ако искате да търсите в текущата директория рекурсивно, можете да използвате `from('.')`. +Къде да се търси? +----------------- -Можете да извикате тези методи многократно или да им предадете няколко пътя като масиви, след което ще бъдат претърсени всички директории. Ако някоя от директориите не съществува, ще се появи грешка `Nette\UnexpectedValueException`. +Директорията за търсене по подразбиране е текущата директория. Можете да я промените с методите `in()` и `from()`. Както се вижда от имената на методите, `in()` търси само в дадената директория, докато `from()` търси и в нейните поддиректории (рекурсивно). Ако искате да търсите рекурсивно в текущата директория, можете да използвате `from('.')`. + +Тези методи могат да се извикват многократно или да им се предадат няколко пътя като масив; тогава файловете ще се търсят във всички директории. Ако някоя от директориите не съществува, ще бъде хвърлено изключение `Nette\UnexpectedValueException`. ```php Finder::findFiles('*.php') ->in(['src', 'tests']) // търси директно в src/ и tests/ - ->from('vendor'); // търси и в поддиректориите vendor/ + ->from('vendor'); // търси и в поддиректориите на vendor/ ``` -Относителните пътища са относителни спрямо текущата директория. Разбира се, могат да се задават и абсолютни пътища: +Относителните пътища са относителни спрямо текущата директория. Разбира се, могат да се посочат и абсолютни пътища: ```php Finder::findFiles('*.php') ->in('/var/www/html'); ``` -[Заместващите символи |#wildcards] `*`, `**`, `?` can be used in the path. For example, you can use the path `src/*/*.php` за търсене на всички PHP файлове в директориите от второ ниво в директорията `src`. Заместващият символ `**`, наречен globstar, е мощен коз, тъй като ви позволява да търсите и в поддиректории: използвайте `src/**/tests/*.php`, за да търсите всички PHP файлове в директорията `tests`, намираща се в `src` или в някоя от нейните поддиректории. +В пътя е възможно да се използват [#плейсхолдери] `*`, `**`, `?`. Така например с пътя `src/*/*.php` можете да търсите всички PHP файлове в директории от второ ниво в директорията `src`. Знакът `**`, наречен globstar, е мощен коз, защото позволява търсене и в поддиректории: с `src/**/tests/*.php` търсите всички PHP файлове в директорията `tests`, намираща се в `src` или в някоя от нейните поддиректории. -От друга страна, заместващите знаци `[...]` не се поддържат в пътя, т.е. нямат специално значение, за да се избегне нежелано поведение, в случай че търсите например `in(__DIR__)` и в пътя се появят символите `[]`. +Обратно, плейсхолдерите `[...]` не се поддържат в пътя, т.е. нямат специално значение, за да не се стига до нежелано поведение в случай, че търсите например `in(__DIR__)` и случайно в пътя се срещат знаците `[]`. -Дълбоките търсения на файлове и директории първо връщат родителската директория, а след това файловете, които тя съдържа, което може да се обърне с `childFirst()`. +При търсене на файлове и директории в дълбочина първо се връща родителската директория и едва след това файловете, съдържащи се в нея, което може да се обърне с помощта на `childFirst()`. -Wildcards .[#toc-wildcards] ---------------------------- +Плейсхолдери +------------ -В маската могат да се използват няколко специални символа: +В маската можете да използвате няколко специални знака: -- `*` - replaces any number of arbitrary characters (except `/`) -- `**` - замества произволен брой символи, включително `/` (т.е. може да се извърши многостепенно търсене) -- `?` - replaces one arbitrary character (except `/`) -- `[a-z]` - замества един символ от списък със символи в квадратни скоби -- `[!a-z]` - замества един символ извън списъка със символи в квадратни скоби +- `*` - замества произволен брой произволни знаци (с изключение на `/`) +- `**` - замества произволен брой произволни знаци, включително `/` (т.е. може да се търси на няколко нива) +- `?` - замества един произволен знак (с изключение на `/`) +- `[a-z]` - замества един знак от списъка със знаци в квадратните скоби +- `[!a-z]` - замества един знак извън списъка със знаци в квадратните скоби -Примери за употреба: +Примери за използване: - `img/?.png` - файлове с еднобуквено име `0.png`, `1.png`, `x.png` и т.н. -- `logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log` - лог файлове във формат `YYYY-MM-DD` +- `logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log` - логове във формат `YYYY-MM-DD` - `src/**/tests/*` - файлове в директорията `src/tests`, `src/foo/tests`, `src/foo/bar/tests` и т.н. - `docs/**.md` - всички файлове с разширение `.md` във всички поддиректории на директорията `docs` -С изключение на .[#toc-excluding] ---------------------------------- +Изключване +---------- -Използвайте метода `exclude()`, за да изключите файлове и директории от търсенето. Посочвате маска, с която файлът не трябва да съвпада. Пример за търсене на `*.txt`, с изключение на тези, които съдържат буквата `X` в името: +С помощта на метода `exclude()` могат да се изключат файлове и директории от търсенето. Посочвате маска, на която файлът не трябва да отговаря. Пример за търсене на файлове `*.txt` с изключение на тези, които съдържат буквата `X` в името си: ```php Finder::findFiles('*.txt') ->exclude('*X*'); ``` -Използвайте `exclude()`, за да прескочите преглежданите поддиректории: +За пропускане на обхождани поддиректории използвайте `exclude()`: ```php Finder::findFiles('*.php') @@ -127,12 +128,12 @@ Finder::findFiles('*.php') ``` -Филтриране .[#toc-filtering] ----------------------------- +Филтриране +---------- -Търсачката предлага няколко метода за филтриране на резултатите (т.е. за тяхното намаляване). Те могат да се комбинират и да се извикват многократно. +Finder предлага няколко метода за филтриране на резултатите (т.е. тяхното намаляване). Можете да ги комбинирате и да ги извиквате многократно. -Използвайте `size()`, за да филтрирате по размер на файла. По този начин намираме файлове с размер между 100 и 200 байта: +С помощта на `size()` филтрираме по размер на файла. Така намираме файлове с размер в диапазона от 100 до 200 байта: ```php Finder::findFiles('*.php') @@ -140,7 +141,7 @@ Finder::findFiles('*.php') ->size('<=', 200); ``` -Методът `date()` филтрира по датата на последната промяна на файла. Стойностите могат да бъдат абсолютни или относителни спрямо текущата дата и час, например така се намират файлове, променени през последните две седмици: +Методът `date()` филтрира по дата на последната промяна на файла. Стойностите могат да бъдат абсолютни или относителни спрямо текущата дата и час, например така намираме файлове, променени през последните две седмици: ```php Finder::findFiles('*.php') @@ -150,9 +151,9 @@ Finder::findFiles('*.php') И двете функции разбират операторите `>`, `>=`, `<`, `<=`, `=`, `!=`, `<>`. -Търсачката ви позволява също така да филтрирате резултатите с помощта на персонализирани функции. Функцията приема `Nette\Utils\FileInfo` като параметър и трябва да върне `true`, за да включи файла в резултатите. +Finder позволява също така да се филтрират резултатите с помощта на собствени функции. Функцията получава като параметър обект `Nette\Utils\FileInfo` и трябва да върне `true`, за да бъде файлът включен в резултатите. -Пример: търсене на PHP файлове, съдържащи символа `Nette` (без значение на големи и малки букви): +Пример: търсене на PHP файлове, които съдържат низа `Nette` (без значение от регистъра): ```php Finder::findFiles('*.php') @@ -160,51 +161,51 @@ Finder::findFiles('*.php') ``` -Филтриране на дълбочината .[#toc-depth-filtering] -------------------------------------------------- +Филтриране в дълбочина +---------------------- -При рекурсивното търсене можете да зададете максимална дълбочина на обхождане, като използвате метода `limitDepth()`. Ако зададете `limitDepth(1)`, ще бъдат обхождани само първите поддиректории, `limitDepth(0)` деактивира филтрирането в дълбочина, а стойността -1 отменя ограничението. +При рекурсивно търсене можете да зададете максимална дълбочина на обхождане с помощта на метода `limitDepth()`. Ако зададете `limitDepth(1)`, се обхождат само първите поддиректории, `limitDepth(0)` изключва обхождането в дълбочина, а стойност -1 отменя ограничението. -Търсачката ви позволява да използвате собствените си функции, за да решите в коя директория да влезете при сърфиране. Функцията получава обекта `Nette\Utils\FileInfo` като параметър и трябва да върне `true`, за да влезе в директорията: +Finder позволява с помощта на собствени функции да се решава в коя директория да се влезе при обхождане. Функцията получава като параметър обект `Nette\Utils\FileInfo` и трябва да върне `true`, за да се влезе в директорията: ```php Finder::findFiles('*.php') - ->descentFilter($file->getBasename() !== 'temp'); + ->descentFilter(fn($file) => $file->getBasename() !== 'temp'); ``` -Сортиране .[#toc-sorting] -------------------------- +Сортиране +--------- -Търсачката предлага и няколко функции за сортиране на резултатите. +Finder предлага също няколко функции за сортиране на резултатите. -Методът `sortByName()` подрежда резултатите по името на файла. Сортирането е естествено, т.е. то обработва правилно числата в имената и връща например `foo1.txt` преди `foo10.txt`. +Методът `sortByName()` сортира резултатите по имената на файловете. Сортирането е натурално, т.е. правилно се справя с числата в имената и връща например `foo1.txt` преди `foo10.txt`. -Търсачката позволява и сортиране с потребителска функция. Той приема два обекта `Nette\Utils\FileInfo` като параметри и трябва да върне резултата от сравнението с оператора `<=>`т.е. `-1`, `0` nebo `1`. Например, така сортираме файловете по размер: +Finder позволява също така да се сортира с помощта на собствена функция. Тя получава като параметър два обекта `Nette\Utils\FileInfo` и трябва да върне резултата от сравнението с оператора `<=>`, т.е. `-1`, `0` или `1`. Например, така ще сортираме файловете по размер: ```php $finder->sortBy(fn($a, $b) => $a->getSize() <=> $b->getSize()); ``` -Няколко различни търсения .[#toc-multiple-different-searches] -------------------------------------------------------------- +Няколко различни търсения +------------------------- -Ако трябва да намерите няколко различни файла на различни места или отговарящи на различни критерии, използвайте метода `append()`. Той връща нов обект `Finder`, така че можете да използвате верига от извиквания на методи: +Ако трябва да намерите няколко различни файла на различни места или отговарящи на други критерии, използвайте метода `append()`. Той връща нов обект `Finder`, така че е възможно да се верижат извикванията на методи: ```php -($finder = new Finder) // съхранявайте първия Finder в променливата $finder! - ->files('*.php') // търсене на *.php файлове в src/ +($finder = new Finder) // в променливата $finder запазваме първия Finder! + ->files('*.php') // в src/ търсим файлове *.php ->from('src') ->append() - ->files('*.md') // в документите/ търсене на *.md файлове + ->files('*.md') // в docs/ търсим файлове *.md ->from('docs') ->append() - ->files('*.json'); // в текущата папка търсете *.json файлове + ->files('*.json'); // в текущата папка търсим файлове *.json ``` -Можете също така да използвате метода `append()`, за да добавите конкретен файл (или масив от файлове). След това връща същия обект `Finder`: +Алтернативно може да се използва методът `append()` за добавяне на конкретен файл (или масив от файлове). Тогава той връща същия обект `Finder`: ```php $finder = Finder::findFiles('*.txt') @@ -212,12 +213,12 @@ $finder = Finder::findFiles('*.txt') ``` -FileInfo .[#toc-fileinfo] -------------------------- +FileInfo +-------- -[Nette\Utils\FileInfo |api:] е клас, който представя файл или директория в резултатите от търсенето. Това е разширение на класа [SplFileInfo |php:SplFileInfo], което предоставя информация като размер на файла, дата на последна промяна, име, път и др. +[Nette\Utils\FileInfo |api:] е клас, представляващ файл или директория в резултатите от търсенето. Той е разширение на класа [SplFileInfo |php:SplFileInfo], който предоставя информация като размер на файла, дата на последна промяна, име, път и т.н. -Той предоставя и методи за връщане на относителни пътища, което е полезно за задълбочени търсения: +Освен това предоставя методи за връщане на относителен път, което е полезно при обхождане в дълбочина: ```php foreach (Finder::findFiles('*.jpg')->from('.') as $file) { @@ -226,7 +227,7 @@ foreach (Finder::findFiles('*.jpg')->from('.') as $file) { } ``` -Имате и методи за четене и записване на съдържанието на файла: +Освен това имате на разположение методи за четене и запис на съдържанието на файла: ```php foreach ($finder as $file) { @@ -237,12 +238,12 @@ foreach ($finder as $file) { ``` -Връщане на резултатите като масив .[#toc-returning-results-as-an-array] ------------------------------------------------------------------------ +Връщане на резултатите като масив +--------------------------------- -Както можете да видите от примерите, Finder имплементира интерфейса `IteratorAggregate`, така че можете да използвате `foreach`, за да преглеждате резултатите. Той е програмиран така, че резултатите се зареждат само докато преглеждате, така че ако имате голям брой файлове, няма да чака да бъдат прочетени всички. +Както се видя в примерите, Finder имплементира интерфейса `IteratorAggregate`, така че можете да използвате `foreach` за обхождане на резултатите. Той е програмиран така, че резултатите се зареждат само по време на обхождането, така че ако имате голям брой файлове, не се чака, докато всички се прочетат. -Можете също така да върнете резултатите като масив от обекти `Nette\Utils\FileInfo`, като използвате метода `collect()`. Масивът не е асоциативен, а числов. +Можете също така да получите резултатите като масив от обекти `Nette\Utils\FileInfo`, и то с метода `collect()`. Масивът не е асоциативен, а числов. ```php $array = $finder->findFiles('*.php')->collect(); diff --git a/utils/bg/floats.texy b/utils/bg/floats.texy index bb50d2f0ba..003f11b3cd 100644 --- a/utils/bg/floats.texy +++ b/utils/bg/floats.texy @@ -1,28 +1,27 @@ -Работа с поплавъци -****************** +Работа с числа с плаваща запетая +******************************** .[perex] [api:Nette\Utils\Floats] е статичен клас с полезни функции за сравняване на десетични числа. -Настройка: +Инсталация: ```shell composer require nette/utils ``` -Всички примери предполагат, че псевдонимът вече е създаден: +Всички примери предполагат създаден псевдоним: ```php use Nette\Utils\Floats; ``` -Мотивация .[#toc-motivation] -============================ +Мотивация +========= -Питате се защо е необходим клас за сравняване на плувки? Имам предвид, че мога да използвам операторите `<`, `>`, `===` и всичко е готово. -Това не е точно така. Какво мислите, че ще даде този код? +Може би се чудите защо изобщо има клас за сравняване на числа с плаваща запетая? В крайна сметка мога да използвам операторите `<`, `>`, `===` и съм готов. Това не е съвсем вярно. Какво мислите, че ще изведе този код? ```php $a = 0.1 + 0.2; @@ -30,21 +29,24 @@ $b = 0.3; echo $a === $b ? 'same' : 'not same'; ``` -Ако стартирате този код, някои от вас ще се изненадат да видят, че програмата отпечатва `not same`. +Ако стартирате кода, някои от вас със сигурност ще бъдат изненадани, че програмата изведе `not same`. -При извършване на математически операции с десетични числа възникват грешки, тъй като десетичните числа се преобразуват в двоични. Например, `0.1 + 0.2` отпечатва `0.300000000000000044…`. Ето защо, когато правим сравнения, трябва да допуснем малко разминаване с определен десетичен знак. +При математически операции с десетични числа възникват грешки поради преобразуването между десетична и двоична система. Например `0.1 + 0.2` дава `0.300000000000000044…`. Затова при сравняване трябва да толерираме малка разлика след определен десетичен знак. -Това прави класът `Floats`. Следното сравнение ще работи както трябва: +И точно това прави класът `Floats`. Следващото сравнение вече ще работи според очакванията: ```php echo Floats::areEqual($a, $b) ? 'same' : 'not same'; // same ``` -При опит за сравняване на `NAN` се получава изключение `\LogicException`. +При опит за сравняване на `NAN` се хвърля изключение `\LogicException`. +.[tip] +Класът `Floats` толерира разлики, по-малки от `1e-10`. Ако трябва да работите с по-голяма точност, използвайте библиотеката BCMath. -Сравнение на плаващи стойности .[#toc-float-comparison] -======================================================= + +Сравняване на числа с плаваща запетая +===================================== areEqual(float $a, float $b): bool .[method] @@ -60,7 +62,7 @@ Floats::areEqual(10, 10.0); // true isLessThan(float $a, float $b): bool .[method] ---------------------------------------------- -Връща `true`, ако `$a` < `$b`. +Връща `true`, ако е изпълнено `$a` < `$b`. ```php Floats::isLessThan(9.5, 10.2); // true @@ -71,7 +73,7 @@ Floats::isLessThan(INF, 10.2); // false isLessThanOrEqualTo(float $a, float $b): bool .[method] ------------------------------------------------------- -Връща `true`, ако `$a` <= `$b`. +Връща `true`, ако е изпълнено `$a` <= `$b`. ```php Floats::isLessThanOrEqualTo(9.5, 10.2); // true @@ -82,7 +84,7 @@ Floats::isLessThanOrEqualTo(10.25, 10.25); // true isGreaterThan(float $a, float $b): bool .[method] ------------------------------------------------- -Връща `true`, ако се прилага `$a` > `$b`. +Връща `true`, ако е изпълнено `$a` > `$b`. ```php Floats::isGreaterThan(9.5, -10.2); // true @@ -93,7 +95,7 @@ Floats::isGreaterThan(9.5, 10.2); // false isGreaterThanOrEqualTo(float $a, float $b): bool .[method] ---------------------------------------------------------- -Връща `true`, ако `$a` >= `$b`. +Връща `true`, ако е изпълнено `$a` >= `$b`. ```php Floats::isGreaterThanOrEqualTo(9.5, 10.2); // false @@ -104,25 +106,25 @@ Floats::isGreaterThanOrEqualTo(10.2, 10.2); // true compare(float $a, float $b): int .[method] ------------------------------------------ -Ако `$a` < `$b`, се връща `-1`, ако е равно на `0` a pokud je `$a` > `$b`, се връща `1`. +Ако `$a` < `$b`, връща `-1`, ако са равни, връща `0`, а ако `$a` > `$b`, връща `1`. -Може да се използва напр. с функцията `usort`. +Може да се използва например с функцията `usort`. ```php $arr = [1, 5, 2, -3.5]; -usort($arr, [Float::class, 'compare']); -// $arr je nyní [-3.5, 1, 2, 5] +usort($arr, [Floats::class, 'compare']); +// $arr е сега [-3.5, 1, 2, 5] ``` -Спомагателни функции .[#toc-helpers-functions] -============================================== +Помощни функции +=============== isZero(float $value): bool .[method] ------------------------------------ -Връща `true`, ако стойността е нула. +Връща `true`, ако стойността е равна на нула. ```php Floats::isZero(0.0); // true diff --git a/utils/bg/helpers.texy b/utils/bg/helpers.texy index aae9d43943..b0c3e4eddc 100644 --- a/utils/bg/helpers.texy +++ b/utils/bg/helpers.texy @@ -5,13 +5,13 @@ [api:Nette\Utils\Helpers] е статичен клас с полезни функции. -Монтаж: +Инсталация: ```shell composer require nette/utils ``` -Всички примери предполагат, че псевдонимът вече е създаден: +Всички примери предполагат създаден псевдоним: ```php use Nette\Utils\Helpers; @@ -21,7 +21,7 @@ use Nette\Utils\Helpers; capture(callable $cb): string .[method] --------------------------------------- -Изпълнява обратна връзка и връща уловения изход като низ. +Изпълнява callback и връща уловения изход като низ. ```php $res = Helpers::capture(function () use ($template) { @@ -33,7 +33,7 @@ $res = Helpers::capture(function () use ($template) { clamp(int|float $value, int|float $min, int|float $max): int|float .[method] ---------------------------------------------------------------------------- -Ограничава стойността до посочения диапазон на включване от min и max. +Ограничава стойността в дадения включителен диапазон min и max. ```php Helpers::clamp($level, 0, 255); @@ -43,8 +43,7 @@ Helpers::clamp($level, 0, 255); compare(mixed $left, string $operator, mixed $right): bool .[method] -------------------------------------------------------------------- -Сравнява две стойности по същия начин, както PHP. Прави разлика между операторите `>`, `>=`, `<`, `<=`, `=`, `==`, `===`, `!=`, `!==`, `<>`. -Тази функция е полезна в ситуации, в които операторът може да бъде променян. +Сравнява две стойности по същия начин, както го прави PHP. Разграничава операторите `>`, `>=`, `<`, `<=`, `=`, `==`, `===`, `!=`, `!==`, `<>`. Функцията е полезна в ситуации, когато операторът е променлива. ```php Helpers::compare(10, '<', 20); // true @@ -54,7 +53,7 @@ Helpers::compare(10, '<', 20); // true falseToNull(mixed $value): mixed .[method] ------------------------------------------ -Преобразува `false` в `null`, не променя други стойности. +Преобразува `false` в `null`, другите стойности не се променят. ```php Helpers::falseToNull(false); // null @@ -65,7 +64,7 @@ Helpers::falseToNull(123); // 123 getLastError(): string .[method] -------------------------------- -Връща последната грешка на PHP или празен низ, ако не е възникнала грешка. За разлика от `error_get_last()`, тя не зависи от PHP директивата `html_errors` и винаги връща текст, а не HTML. +Връща последната грешка в PHP или празен низ, ако не е възникнала грешка. За разлика от [error_get_last() |php:error_get_last], не се влияе от PHP директивата `html_errors` и винаги връща текст, а не HTML. ```php Helpers::getLastError(); @@ -75,13 +74,13 @@ Helpers::getLastError(); getSuggestion(string[] $possibilities, string $value): ?string .[method] ------------------------------------------------------------------------ -От дадените опции `$possibilities` търси низ, който най-много прилича на `$value`, но не съвпада с него. Той поддържа само 8-битово кодиране. +От предложените възможности `$possibilities` търси низ, който е най-подобен на `$value`, но не е същият. Поддържа само 8-битово кодиране. -Това е полезно, ако дадена опция е невалидна и искаме да предложим на потребителя подобна (но различна, така че същият низ да бъде игнориран). Ето как Nette създава съобщения `did you mean ...?`. +Подходящо е в случай, че определена опция не е валидна и искаме да посъветваме потребителя за подобна (но различна, затова се игнорира същият низ). По този начин Nette създава съобщенията `did you mean ...?`. ```php $items = ['foo', 'bar', 'baz']; Helpers::getSuggestion($items, 'fo'); // 'foo' Helpers::getSuggestion($items, 'barr'); // 'bar' -Helpers::getSuggestion($items, 'baz'); // 'bar', ne 'baz' +Helpers::getSuggestion($items, 'baz'); // 'bar', не 'baz' ``` diff --git a/utils/bg/html-elements.texy b/utils/bg/html-elements.texy index bb2856bba9..4a6ddc2cc8 100644 --- a/utils/bg/html-elements.texy +++ b/utils/bg/html-elements.texy @@ -1,47 +1,47 @@ -Елементи на HTML -**************** +HTML елементи +************* .[perex] -Класът [api:Nette\Utils\Html] е помощно средство за генериране на HTML код, който не позволява уязвимости от типа Cross Site Scripting (XSS). +Класът [api:Nette\Utils\Html] е помощник за генериране на HTML код, който предотвратява уязвимостта Cross Site Scripting (XSS). -Начинът, по който тя работи, е, че нейните обекти са HTML елементи, на които задаваме параметри и позволяваме да бъдат визуализирани: +Работи така, че неговите обекти представляват HTML елементи, на които задаваме параметри и ги рендираме: ```php -$el = Html::el('img'); // създава елемент -$el->src = 'image.jpg'; // задава атрибута src -echo $el; // отпечатва '' +$el = Html::el('img'); // създава елемент +$el->src = 'image.jpg'; // задава атрибут src +echo $el; // извежда '' ``` -Настройка: +Инсталация: ```shell composer require nette/utils ``` -Всички примери предполагат, че псевдонимът вече е създаден: +Всички примери предполагат създаден псевдоним: ```php use Nette\Utils\Html; ``` -Създаване на HTML елемент .[#toc-creating-an-html-element] -========================================================== +Създаване на HTML елемент +========================= -Създаване на елемент чрез метода `Html::el()`: +Елементът се създава с метода `Html::el()`: ```php $el = Html::el('img'); // създава елемент ``` -В допълнение към името можете да зададете и други атрибути в синтаксиса на HTML: +Освен името, можете да зададете и други атрибути в HTML синтаксис: ```php $el = Html::el('input type=text class="red important"'); ``` -Или ги предайте като асоциативно поле с втори параметър: +Или да ги предадете като асоциативен масив във втория параметър: ```php $el = Html::el('input', [ @@ -59,30 +59,30 @@ $el->isEmpty(); // true, тъй като е празен елемент ``` -Атрибути на HTML .[#toc-html-attributes] -======================================== +HTML атрибути +============= -Съществуват три начина за промяна и четене на отделни атрибути на HTML и вие трябва да решите кой от тях предпочитате. Първата е чрез собственост: +Можем да променяме и четем отделните HTML атрибути по три начина, зависи от вас кой ще ви хареса повече. Първият е чрез свойства: ```php -$el->src = 'image.jpg'; // задава атрибута src +$el->src = 'image.jpg'; // задава атрибут src echo $el->src; // 'image.jpg' -unset($el->src); // премахване на атрибута +unset($el->src); // премахва атрибута // или $el->src = null; ``` -Вторият начин е чрез извикване на методи, които, за разлика от задаването на свойства, могат да бъдат верижно свързани: +Вторият начин е извикването на методи, които за разлика от задаването на свойства, можем да свързваме верижно: ```php $el = Html::el('img')->src('image.jpg')->alt('photo'); // photo -$el->alt(null); // отмяна на атрибут +$el->alt(null); // премахване на атрибута ``` -Третият метод е най-многословен: +А третият начин е най-многословен: ```php $el = Html::el('img') @@ -94,9 +94,9 @@ echo $el->getAttribute('src'); // 'image.jpg' $el->removeAttribute('alt'); ``` -Атрибутите могат да се задават масово с `addAttributes(array $attrs)` и да се премахват с `removeAttributes(array $attrNames)`. +Атрибутите могат да се задават групово с `addAttributes(array $attrs)` и да се премахват с `removeAttributes(array $attrNames)`. -Стойността на атрибута не е задължително да бъде низ, можете също така да използвате булеви стойности за логически атрибути: +Стойността на атрибута не трябва да бъде само низ, могат да се използват и булеви стойности за булеви атрибути: ```php $checkbox = Html::el('input')->type('checkbox'); @@ -104,17 +104,17 @@ $checkbox->checked = true; // $checkbox->checked = false; // ``` -Атрибутът може да бъде и масив от стойности, които се изобразяват след интервали, което е полезно например за CSS класове: +Атрибутът може да бъде и масив от стойности, които се извеждат разделени с интервали, което е полезно например за CSS класове: ```php $el = Html::el('input'); $el->class[] = 'active'; -$el->class[] = null; // null se ignoruje +$el->class[] = null; // null се игнорира $el->class[] = 'top'; echo $el; // '' ``` -Алтернативата е асоциативен масив, в който стойностите определят дали ключът трябва да бъде визуализиран: +Алтернатива е асоциативен масив, където стойностите показват дали ключът трябва да бъде изведен: ```php $el = Html::el('input'); @@ -123,7 +123,7 @@ $el->class['top'] = false; echo $el; // '' ``` -CSS стиловете могат да бъдат записани като асоциативни полета: +CSS стиловете могат да се записват под формата на асоциативни масиви: ```php $el = Html::el('input'); @@ -132,7 +132,7 @@ $el->style['display'] = 'block'; echo $el; // '' ``` -Сега използвахме свойство, но същото може да се напише и с помощта на методи: +Сега използвахме свойства, но същото може да се запише и с помощта на методи: ```php $el = Html::el('input'); @@ -141,7 +141,7 @@ $el->style('display', 'block'); echo $el; // '' ``` -Или дори в най-кратката форма: +Или дори по най-многословния начин: ```php $el = Html::el('input'); @@ -150,7 +150,7 @@ $el->appendAttribute('style', 'display', 'block'); echo $el; // '' ``` -Последна подробност: методът `href()` може да улесни съставянето на параметри на заявката в URL адреса: +Още една дреболия накрая: методът `href()` може да улесни съставянето на query параметри в URL: ```php echo Html::el('a')->href('index.php', [ @@ -161,10 +161,10 @@ echo Html::el('a')->href('index.php', [ ``` -Атрибути на данните .[#toc-data-attributes] -------------------------------------------- +Data атрибути +------------- -Атрибутите на данни имат специална поддръжка. Тъй като имената им съдържат тирета, достъпът чрез свойства и методи не е толкова елегантен, затова има метод `data()`: +Data атрибутите имат специална поддръжка. Тъй като имената им съдържат тирета, достъпът чрез свойства и методи не е толкова елегантен, затова съществува методът `data()`: ```php $el = Html::el('input'); @@ -173,7 +173,7 @@ $el->data('max-size', '500x300'); // е елегантно echo $el; // '' ``` -Ако стойността на атрибут за данни е масив, тя автоматично се сериализира в JSON: +Ако стойността на data атрибута е масив, той автоматично се сериализира в JSON: ```php $el = Html::el('input'); @@ -182,10 +182,10 @@ echo $el; // '' ``` -Съдържание на елемента .[#toc-element-content] -============================================== +Съдържание на елемента +====================== -Задайте вътрешното съдържание на елемента, като използвате методите `setHtml()` или `setText()`. Използвайте първата опция само ако знаете, че в параметъра се предава защитен HTML низ. +Вътрешното съдържание на елемента се задава с методите `setHtml()` или `setText()`. Използвайте първия само ако знаете, че предавате надеждно безопасен HTML низ в параметъра. ```php echo Html::el('span')->setHtml('hello
                                                                                                                            '); @@ -195,7 +195,7 @@ echo Html::el('span')->setText('10 < 20'); // '10 < 20' ``` -Обратно, използвайте методите `getHtml()` или `getText()`, за да получите вътрешното съдържание. Последният метод премахва HTML таговете от изхода и преобразува HTML същностите в символи. +И обратно, вътрешното съдържание се получава с методите `getHtml()` или `getText()`. Вторият премахва HTML таговете от изхода и преобразува HTML същностите в символи. ```php echo $el->getHtml(); // '10 < 20' @@ -203,10 +203,10 @@ echo $el->getText(); // '10 < 20' ``` -Дъщерни възли .[#toc-child-nodes] ---------------------------------- +Дъщерни възли +------------- -В рамките на един елемент може да има и масив от дъщерни възли. Всеки от тях може да бъде низ или друг елемент `Html`. Те се поставят с помощта на `addHtml()` или `addText()`: +Вътрешността на елемента може да бъде и масив от дъщерни възли. Всеки от тях може да бъде или низ, или друг `Html` елемент. Вмъкваме ги с помощта на `addHtml()` или `addText()`: ```php $el = Html::el('span') @@ -216,16 +216,16 @@ $el = Html::el('span') // hello
                                                                                                                            10 < 20
                                                                                                                            ``` -Друг начин за създаване и вмъкване на нов възел `Html`: +Друг начин за създаване и вмъкване на нов `Html` възел: ```php -$el = Html::el('ul') - ->create('li', ['class' => 'first']) - ->setText('první'); -//
                                                                                                                            • první
                                                                                                                            +$ul = Html::el('ul'); +$ul->create('li', ['class' => 'first']) + ->setText('първи'); +//
                                                                                                                            • първи
                                                                                                                            ``` -Можете да работите с възли, сякаш са масиви. Тоест адресирайте всеки от тях с квадратни скоби, пребройте ги с `count()` и извършете итерация: +С възлите може да се работи по същия начин, както с масив. Тоест, достъп до отделните възли с квадратни скоби, преброяване с `count()` и итериране върху тях: ```php $el = Html::el('div'); @@ -238,20 +238,20 @@ foreach ($el as $child) { /* ... */ } echo count($el); // 2 ``` -Нов възел може да бъде вмъкнат на определено място с помощта на `insert(?int $index, $child, bool $replace = false)`. Ако тя е `$replace = false`, елементът ще бъде вмъкнат в позицията `$index`, а останалите ще бъдат преместени. Ако тя е `$index = null`, елементът се добавя последен. +Нов възел може да бъде вмъкнат на конкретно място с помощта на `insert(?int $index, $child, bool $replace = false)`. Ако `$replace = false`, вмъква елемента на позиция `$index` и измества останалите. Ако `$index = null`, добавя елемента в края. ```php // вмъква елемента на първа позиция и измества останалите $el->insert(0, Html::el('span')); ``` -Всички възли се извличат по метода `getChildren()` и се премахват по метода `removeChildren()`. +Всички възли се получават с метода `getChildren()` и се премахват с метода `removeChildren()`. -Създаване на фрагмент от документ .[#toc-creating-a-document-fragment] ----------------------------------------------------------------------- +Създаване на document fragment +------------------------------ -Ако искаме да работим с масив от възли и не се интересуваме от обгръщащ елемент, можем да създадем *фрагмент на документ*, като подадем `null` вместо името на елемента: +Ако искаме да работим с масив от възли и не ни интересува обвиващият елемент, можем да създадем т.нар. *document fragment*, като предадем `null` вместо име на елемент: ```php $el = Html::el(null) @@ -261,7 +261,7 @@ $el = Html::el(null) // hello
                                                                                                                            10 < 20
                                                                                                                            ``` -Методите `fromHtml()` и `fromText()` предлагат по-бърз начин за създаване на фрагмент: +По-бърз начин за създаване на фрагмент предлагат методите `fromHtml()` и `fromText()`: ```php $el = Html::fromHtml('hello
                                                                                                                            '); @@ -272,10 +272,10 @@ echo $el; // '10 < 20' ``` -Генериране на HTML изход .[#toc-generating-html-output] -======================================================= +Генериране на HTML изход +======================== -Най-лесният начин за извеждане на HTML елемент е да използвате `echo` или да пренапишете обекта на `(string)`. Можете също така да извеждате отделно отварящи или затварящи тагове и атрибути: +Най-лесният начин да изведете HTML елемент е да използвате `echo` или да преобразувате обекта към `(string)`. Можете също така да изведете отделно отварящия или затварящия таг и атрибутите: ```php $el = Html::el('div class=header')->setText('hello'); @@ -289,7 +289,7 @@ echo $el->endTag(); // '' echo $el->attributes(); // 'class="header"' ``` -Важна функция е автоматичната защита срещу [скриптиране на кръстосани сайтове (XSS |nette:glossary#Cross-Site-Scripting-XSS]). Всички стойности на атрибути или съдържание, вмъкнати чрез `setText()` или `addText()`, се ескапират по сигурен начин: +Важна характеристика е автоматичната защита срещу [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS]. Всички стойности на атрибути или съдържание, вмъкнати чрез `setText()` или `addText()`, се екранират надеждно: ```php echo Html::el('div') @@ -300,17 +300,17 @@ echo Html::el('div') ``` -Преобразуване на HTML ↔ текст .[#toc-conversion-html-text] -========================================================== +Конвертиране на HTML ↔ текст +============================ -За да преобразувате HTML в текст, можете да използвате статичния метод `htmlToText()`: +За преобразуване на HTML в текст можете да използвате статичния метод `htmlToText()`: ```php echo Html::htmlToText('One & Two'); // 'One & Two' ``` -HtmlStringable .[#toc-htmlstringable] -===================================== +HtmlStringable +============== -Обектът `Nette\Utils\Html` имплементира интерфейса `Nette\HtmlStringable`, който например Latte или Forms използват, за да разграничат обектите, които имат метод `__toString()`, който връща HTML код. Така че няма да има двойна проверка, ако например изброим обект в шаблон, използвайки `{$el}`. +Обектът `Nette\Utils\Html` имплементира интерфейса [Nette\HtmlStringable |api:Nette\HtmlStringable], чрез който например Latte или формите разграничават обекти, които имат метод `__toString()`, връщащ HTML код. Така че няма да се получи двойно екраниране, ако например изведем обекта в шаблон с помощта на `{$el}`. diff --git a/utils/bg/images.texy b/utils/bg/images.texy index ea5a079c9e..0bc3d5a59c 100644 --- a/utils/bg/images.texy +++ b/utils/bg/images.texy @@ -2,130 +2,141 @@ ******************** .[perex] -Класът [api:Nette\Utils\Image] улеснява манипулирането на изображения, например промяна на размера, изрязване, изостряне, рисуване или обединяване на няколко изображения. +Класът [api:Nette\Utils\Image] опростява манипулирането на изображения, като например преоразмеряване, изрязване, изостряне, рисуване или комбиниране на няколко изображения. -PHP разполага с богат набор от функции за манипулиране на изображения. Но техният API не е много удобен за потребителите. Това нямаше да е Nette Framework, ако не бяха предложили секси API. +PHP разполага с богат набор от функции за манипулиране на изображения. Но API-то им не е много удобно. Това не би бил Nette Framework, ако не предлагаше секси API. -Монтаж: +Инсталация: ```shell composer require nette/utils ``` -Всички примери предполагат, че псевдонимът вече е създаден: +Всички примери предполагат създаден псевдоним: ```php use Nette\Utils\Image; +use Nette\Utils\ImageColor; +use Nette\Utils\ImageType; ``` -Създаване на изображение .[#toc-creating-an-image] -================================================== +Създаване на изображение +======================== -Създайте ново истинско цветно изображение, напр. 100×200: +Създаваме ново true color изображение, например с размери 100×200: ```php $image = Image::fromBlank(100, 200); ``` -По желание можете да зададете цвят на фона (по подразбиране е черен): +По желание може да се определи цвят на фона (по подразбиране е черен): ```php -$image = Image::fromBlank(100, 200, Image::rgb(125, 0, 0)); +$image = Image::fromBlank(100, 200, ImageColor::rgb(125, 0, 0)); ``` -Или да качите изображение от файл: +Или зареждаме изображение от файл: ```php $image = Image::fromFile('nette.jpg'); ``` -Поддържаните формати са JPEG, PNG, GIF, WebP, AVIF и BMP, но вашата версия на PHP също трябва да ги поддържа (проверете `phpinfo()`, раздел GD). Анимациите не се поддържат. -Трябва ли да определям формата на изображението при качване? Методът го връща във втория параметър: +Запазване на изображение +======================== + +Изображението може да бъде запазено във файл: ```php -$image = Image::fromFile('nette.jpg', $type); -// $type е Image::JPEG, Image::PNG, Image::GIF, Image::WEBP, Image::AVIF или Image::BMP +$image->save('resampled.jpg'); ``` -Само откриването без зареждане на изображението се извършва от `Image::detectTypeFromFile()`. +Можем да определим качеството на компресия в диапазона 0..100 за JPEG (по подразбиране 85), WEBP (по подразбиране 80) и AVIF (по подразбиране 30) и 0..9 за PNG (по подразбиране 9): +```php +$image->save('resampled.jpg', 80); // JPEG, качество 80% +``` -Запазване на изображения .[#toc-save-the-image] -=============================================== - -Изображението може да бъде записано във файл: +Ако форматът не е ясен от разширението на файла, може да се определи с [константа |#Формати]: ```php -$image->save('resampled.jpg'); +$image->save('resampled.tmp', null, ImageType::JPEG); ``` -Можем да зададем качество на компресията в диапазона 0..100 за JPEG (по подразбиране 85), WEBP (по подразбиране 80) и AVIF (по подразбиране 30) и 0..9 за PNG (по подразбиране 9): +Изображението може да бъде записано в променлива вместо на диск: ```php -$image->save('resampled.jpg', 80); // JPEG, 80% качество +$data = $image->toString(ImageType::JPEG, 80); // JPEG, качество 80% ``` -Ако форматът не е очевиден от разширението на файла, той може да бъде посочен с една от константите `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` и `Image::BMP`: +или да се изпрати директно в браузъра със съответния HTTP хедър `Content-Type`: ```php -$image->save('resampled.tmp', null, Image::JPEG); +// изпраща хедър Content-Type: image/png +$image->send(ImageType::PNG); ``` -Изображението може да бъде записано на променлива вместо на диск: + +Формати +======= + +Поддържаните формати са JPEG, PNG, GIF, WebP, AVIF и BMP, но те трябва да бъдат поддържани и от вашата версия на PHP, което можете да проверите с функцията [#isTypeSupported()]. Анимациите не се поддържат. + +Форматът се представя от константите `ImageType::JPEG`, `ImageType::PNG`, `ImageType::GIF`, `ImageType::WEBP`, `ImageType::AVIF` и `ImageType::BMP`. ```php -$data = $image->toString(Image::JPEG, 80); // JPEG, 80% качество +$supported = Image::isTypeSupported(ImageType::JPEG); ``` -или се изпраща директно на браузъра със съответния HTTP хедър `Content-Type`: +Трябва ли да откриете формата на изображението при зареждане? Методът го връща във втория параметър: ```php -// изпраща заглавие Content-Type: image/png -$image->send(Image::PNG); +$image = Image::fromFile('nette.jpg', $type); ``` +Самото откриване без зареждане на изображението се извършва от `Image::detectTypeFromFile()`. + -Промяна на размера .[#toc-image-resize] -======================================= +Преоразмеряване +=============== -Често срещана операция е промяна на размера на изображението. Действителните размери се връщат чрез методите `getWidth()` и `getHeight()`. +Честа операция е промяната на размерите на изображението. Текущите размери се връщат от методите `getWidth()` и `getHeight()`. -Методът `resize()` се използва за промяна на размера на изображението, така че то да не надвишава 500x300 пиксела (или ширината ще бъде точно 500 px, или височината точно 300 px, като едно от измеренията се изчислява, за да се запази съотношението на страните): +За промяна се използва методът `resize()`. Пример за пропорционално преоразмеряване, така че да не надвишава размери 500x300 пиксела (или ширината ще бъде точно 500 px, или височината ще бъде точно 300 px, единият от размерите се изчислява така, че да се запази съотношението на страните): ```php $image->resize(500, 300); ``` -Може да се зададе само едно измерение, а другото ще бъде изчислено: +Възможно е да се посочи само един размер, а другият ще бъде изчислен: ```php -$image->resize(500, null); // ширина 500px, височина се изчислява +$image->resize(500, null); // ширина 500px, височината се изчислява -$image->resize(null, 300); // изчислена ширина, височина 300px +$image->resize(null, 300); // ширината се изчислява, височина 300px ``` -Всяко измерение може да бъде зададено като процент: +Всеки размер може да бъде посочен и в проценти: ```php $image->resize('75%', 300); // 75 % × 300px ``` -Поведението `resize` може да бъде повлияно от следните симптоми. Всички, с изключение на `Image::Stretch`, запазват съотношението на страните. +Поведението на `resize` може да се повлияе от следните флагове. Всички, освен `Image::Stretch`, запазват съотношението на страните. |--------------------------------------------------------------------------------------- -| Флаг | Описание +| Флаг | Описание |--------------------------------------------------------------------------------------- -| `Image::OrSmaller` (по подразбиране)| получените размери ще бъдат по-малки или равни на заявените размери. -| `Image::OrBigger` | запълва (и евентуално надхвърля в едно измерение) целевата област -| `Image::Cover` | запълва целевата област и подрязва това, което е извън нея. -| `Image::ShrinkOnly` | свиване (избягва разтягане на малко изображение) само -| `Image::Stretch` | не запазвайте съотношението на страните +| `Image::OrSmaller` (по подразбиране) | крайните размери ще бъдат по-малки или равни на исканите размери +| `Image::OrBigger` | запълва (и евентуално надхвърля в един размер) целевата площ +| `Image::Cover` | запълва целевата площ и изрязва това, което надхвърля +| `Image::ShrinkOnly` | само намаляване (предотвратява разтягането на малко изображение) +| `Image::Stretch` | не запазва съотношението на страните -Флаговете се подават като трети аргумент на функцията: +Флаговете се посочват като трети аргумент на функцията: ```php $image->resize(500, 300, Image::OrBigger); @@ -137,33 +148,33 @@ $image->resize(500, 300, Image::OrBigger); $image->resize(500, 300, Image::ShrinkOnly | Image::Stretch); ``` -Изображенията могат да бъдат обърнати вертикално или хоризонтално, като се зададе едно от измеренията (или и двете) като отрицателно число: +Изображенията могат да бъдат обърнати вертикално или хоризонтално, като един от размерите (или и двата) се посочи като отрицателно число: ```php -$flipped = $image->resize(null, '-100%'); // обръщане по вертикала +$flipped = $image->resize(null, '-100%'); // обръщане вертикално $flipped = $image->resize('-100%', '-100%'); // завъртане на 180° -$flipped = $image->resize(-125, 500); // промяна на размера и обръщане в хоризонтално положение +$flipped = $image->resize(-125, 500); // преоразмеряване и обръщане хоризонтално ``` -След като изображението е намалено, можете да подобрите външния му вид, като настроите остротата му: +След намаляване на размера на изображението е възможно да се подобри външният му вид чрез леко изостряне: ```php $image->sharpen(); ``` -Засаждане .[#toc-cropping] -========================== +Изрязване +========= -Методът, използван за култивиране, е `crop()`: +За изрязване се използва методът `crop()`: ```php $image->crop($left, $top, $width, $height); ``` -Както и при `resize()`, всички стойности могат да бъдат представени като проценти. Процентите за `$left` и `$top` се изчисляват от оставащото пространство, подобно на CSS свойството `background-position`: +Подобно на `resize()`, всички стойности могат да бъдат посочени в проценти. Процентите за `$left` и `$top` се изчисляват от оставащото място, подобно на CSS свойството `background-position`: ```php $image->crop('100%', '50%', '80%', '80%'); @@ -172,329 +183,359 @@ $image->crop('100%', '50%', '80%', '80%'); [* crop.svg *] -Изображението може също така да бъде автоматично изрязано, напр. черните граници могат да бъдат изрязани: +Изображението може също да бъде изрязано автоматично, например изрязване на черни рамки: ```php $image->cropAuto(IMG_CROP_BLACK); ``` -Методът `cropAuto()` е заместител на обект на функцията `imagecropauto()`, за повече подробности вижте [нейната документация |https://www.php.net/manual/en/function.imagecropauto]. +Методът `cropAuto()` е обектен заместител на функцията `imagecropauto()`, в [нейната документация |https://www.php.net/manual/en/function.imagecropauto] ще намерите повече информация. + +Цветове .{data-version:4.0.2} +============================= -Рисуване и редактиране .[#toc-drawing-and-editing] -================================================== +Методът `ImageColor::rgb()` ви позволява да дефинирате цвят чрез стойностите на червено, зелено и синьо (RGB). По желание можете да посочите и стойност на прозрачност в диапазона от 0 (напълно прозрачно) до 1 (напълно непрозрачно), т.е. същото като в CSS. + +```php +$color = ImageColor::rgb(255, 0, 0); // Червено +$transparentBlue = ImageColor::rgb(0, 0, 255, 0.5); // Полупрозрачно синьо +``` -Можете да рисувате, можете да пишете, но не разкъсвайте страниците. Всички функции за изображения на PHP, като например [imagefilledellipse |https://www.php.net/manual/en/function.imagefilledellipse.php], са достъпни за вас, но в обектно-ориентиран вид: +Методът `ImageColor::hex()` позволява да дефинирате цвят чрез шестнадесетичен формат, подобно на CSS. Поддържа форматите `#rgb`, `#rrggbb`, `#rgba` и `#rrggbbaa`: ```php -$image->filledEllipse($cx, $cy, $width, $height, Image::rgb(255, 0, 0, 63)); +$color = ImageColor::hex("#F00"); // Червено +$transparentGreen = ImageColor::hex("#00FF0080"); // Полупрозрачно зелено ``` -Вижте [Преглед на методите |#Overview-of-Methods]. +Цветовете могат да се използват в други методи, като `ellipse()`, `fill()` и т.н. -Комбиниране на няколко изображения .[#toc-merge-multiple-images] -================================================================ +Рисуване и редакции +=================== -Можете лесно да вмъкнете друго изображение в снимка: +Можеш да рисуваш, можеш да пишеш, но не късай листата. На ваше разположение са всички функции на PHP за работа с изображения, вижте [#Преглед на методите], но в обектна обвивка: + +```php +$image->filledEllipse($centerX, $centerY, $width, $height, ImageColor::rgb(255, 0, 0)); +``` + +Тъй като PHP функциите за рисуване на правоъгълници са непрактични поради определянето на координати, класът `Image` предлага техни заместители под формата на функциите [#rectangleWH()] и [#filledRectangleWH()]. + + +Комбиниране на няколко изображения +================================== + +В изображението лесно може да се вмъкне друго изображение: ```php $logo = Image::fromFile('logo.png'); -$blank = Image::fromBlank(320, 240, Image::rgb(52, 132, 210)); +$blank = Image::fromBlank(320, 240, ImageColor::rgb(52, 132, 210)); -// координатите отново могат да бъдат зададени като процент -$blank->place($logo, '80%', '80%'); // вмъкване в долния десен ъгъл +// координатите могат да бъдат посочени отново в проценти +$blank->place($logo, '80%', '80%'); // вмъкваме близо до долния десен ъгъл ``` -Алфа каналът се спазва по време на вмъкването и можем да влияем върху прозрачността на вмъкнатото изображение (създаваме воден знак): +При вмъкването се спазва алфа каналът, освен това можем да повлияем на прозрачността на вмъкваното изображение (създаваме т.нар. воден знак): ```php -$blank->place($image, '80%', '80%', 25); // прозрачността е 25% +$blank->place($image, '80%', '80%', 25); // прозрачността е 25 % ``` -Използването на този API е истинско удоволствие! +Такова API е наистина удоволствие да се използва! -Преглед на методите .[#toc-overview-of-methods] -=============================================== +Преглед на методите +=================== -static fromBlank(int $width, int $height, array $color=null): Image .[method] ------------------------------------------------------------------------------ -Създава ново истинско цветно изображение със зададени размери. Цветът по подразбиране е черен. +static fromBlank(int $width, int $height, ?ImageColor $color=null): Image .[method] +----------------------------------------------------------------------------------- +Създава ново true color изображение с дадените размери. Цветът по подразбиране е черен. static fromFile(string $file, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------- -Чете изображение от файл и връща неговия тип в `$detectedFormat`. Поддържаните типове са `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` и `Image::BMP`. +Зарежда изображение от файл и връща неговия [тип |#Формати] в `$detectedFormat`. static fromString(string $s, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------ -Чете изображение от низ и връща неговия тип в `$detectedFormat`. Поддържаните типове са `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` и `Image::BMP`. +Зарежда изображение от низ и връща неговия [тип |#Формати] в `$detectedFormat`. -static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method] ---------------------------------------------------------------------------------- -Създава цвят, който може да се използва в други методи, като например `ellipse()`, `fill()` и т.н. +static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method][deprecated] +--------------------------------------------------------------------------------------------- +Тази функция е заменена от класа `ImageColor`, вижте [#цветове]. static typeToExtension(int $type): string .[method] --------------------------------------------------- -Връща разширението на файла за дадената константа `Image::XXX`. +Връща разширението на файла за дадения [тип |#Формати]. static typeToMimeType(int $type): string .[method] -------------------------------------------------- -Връща типа mime за дадената константа `Image::XXX`. +Връща mime типа за дадения [тип |#Формати]. static extensionToType(string $extension): int .[method] -------------------------------------------------------- -Връща типа mime като константа `Image::XXX` според разширението на файла. +Връща [тип |#Формати] на изображението според разширението на файла. static detectTypeFromFile(string $file, int &$width=null, int &$height=null): ?int .[method] -------------------------------------------------------------------------------------------- -Връща типа на изображението като константа `Image::XXX`, а размерите му - като `$width` и `$height`. +Връща [тип |#Формати] на изображението и в параметрите `$width` и `$height` също неговите размери. static detectTypeFromString(string $s, int &$width=null, int &$height=null): ?int .[method] ------------------------------------------------------------------------------------------- -Връща типа на изображението от символния низ като константа `Image::XXX` и размерите му в параметрите `$width` и `$height`. +Връща [тип |#Формати] на изображението от низ и в параметрите `$width` и `$height` също неговите размери. -affine(array $affine, array $clip=null): Image .[method] --------------------------------------------------------- -Връща изображение, съдържащо афинно-трансформирано src изображение, като се използва опционална област на изрязване. ([повече информация |https://www.php.net/manual/en/function.imageaffine]). +static isTypeSupported(int $type): bool .[method] +------------------------------------------------- +Проверява дали даденият [тип |#Формати] на изображението е поддържан. + + +static getSupportedTypes(): array .[method]{data-version:4.0.4} +--------------------------------------------------------------- +Връща масив от поддържаните [типове |#Формати] на изображения. + + +static calculateTextBox(string $text, string $fontFile, float $size, float $angle=0, array $options=[]): array .[method] +------------------------------------------------------------------------------------------------------------------------ +Изчислява размерите на правоъгълника, който обхваща текста с определен шрифт и размер. Връща асоциативен масив, съдържащ ключовете `left`, `top`, `width`, `height`. Левият ръб може да бъде и отрицателен, ако текстът започва с ляво подрязване. + + +affine(array $affine, ?array $clip=null): Image .[method] +--------------------------------------------------------- +Върнете изображение, съдържащо афинно трансформирано изображение src, като използвате незадължителна област на изрязване. ([повече |https://www.php.net/manual/en/function.imageaffine]). affineMatrixConcat(array $m1, array $m2): array .[method] --------------------------------------------------------- -Връща конкатенацията на две матрици за афинна трансформация, което е полезно, ако трябва да се приложат няколко трансформации към едно и също изображение. ([повече подробности |https://www.php.net/manual/en/function.imageaffinematrixconcat]) +Връща конкатенацията на две афинни трансформационни матрици, което е полезно, ако няколко трансформации трябва да се приложат едновременно към едно и също изображение. ([повече |https://www.php.net/manual/en/function.imageaffinematrixconcat]) -affineMatrixGet(int $type, mixed $options=null): array .[method] ----------------------------------------------------------------- -Връща матричната трансформационна матрица. ([повече подробности |https://www.php.net/manual/en/function.imageaffinematrixget]) +affineMatrixGet(int $type, ?mixed $options=null): array .[method] +----------------------------------------------------------------- +Връща матрична трансформационна матрица. ([повече |https://www.php.net/manual/en/function.imageaffinematrixget]) alphaBlending(bool $on): void .[method] --------------------------------------- -Позволява ви да използвате два различни режима на рисуване в трицветни изображения. В режим на наслагване цветовият компонент на алфа-канала, използван във всички функции за рисуване, като например `setPixel()`, определя каква част от основния цвят трябва да прозира. В резултат на това в този момент съществуващият цвят автоматично се смесва с цвета на чертежа и резултатът се записва в изображението. В резултат на това пикселът става непрозрачен. В режим без смесване цветът на карикатурата се копира дословно с информацията за алфа канала и се заменя с целевия пиксел. Режимът на смесване не е наличен, когато рисувате върху изображения от палитра. ([повече) |https://www.php.net/manual/en/function.imagealphablending] +Позволява два различни режима на рисуване в truecolor изображения. В режим на смесване, алфа каналният компонент на цвета, използван във всички функции за рисуване, като `setPixel()`, определя до каква степен трябва да се позволи на основния цвят да прозира. В резултат на това съществуващият цвят в този момент автоматично се смесва с рисувания цвят и резултатът се запазва в изображението. Крайният пиксел е непрозрачен. В режим без смесване, рисуваният цвят се копира буквално с информацията за алфа канала и замества целевия пиксел. Режимът на смесване не е наличен при рисуване върху палетни изображения. ([повече |https://www.php.net/manual/en/function.imagealphablending]) antialias(bool $on): void .[method] ----------------------------------- -Активиране на изчертаването на изгладени линии и многоъгълници. Не поддържа алфа канали. Работи само с трицветни изображения. +Активирайте рисуването на изгладени линии и полигони. Не поддържа алфа канали. Работи само с truecolor изображения. -Използването на изгладения примитив с прозрачен цвят на фона може да доведе до неочаквани резултати. Методът на смесване използва цвета на фона като всеки друг цвят. ([повече подробности |https://www.php.net/manual/en/function.imageantialias]) +Използването на антиалиасни примитиви с прозрачен цвят на фона може да доведе до неочаквани резултати. Методът на смесване използва цвета на фона като всички останали цветове. ([повече |https://www.php.net/manual/en/function.imageantialias]) -arc(int $x, int $y, int $w, int $h, int $start, int $end, int $color): void .[method] -------------------------------------------------------------------------------------- -Изчертава дъга от окръжност с център в зададените координати. ([повече подробности |https://www.php.net/manual/en/function.imagearc]) - - -char(int $font, int $x, int $y, string $char, int $color): void .[method] -------------------------------------------------------------------------- -Изчертава първия символ `$char` в изображението с горен ляв ъгъл `$x`, `$y` (горният ляв ъгъл е равен на 0, 0), цвят `$color`. ([повече подробности |https://www.php.net/manual/en/function.imagechar]) - - -charUp(int $font, int $x, int $y, string $char, int $color): void .[method] ---------------------------------------------------------------------------- -Изчертава символа `$char` вертикално в посочената координата в посоченото изображение. ([повече подробности |https://www.php.net/manual/en/function.imagecharup]) +arc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color): void .[method] +--------------------------------------------------------------------------------------------------------------------------- +Рисува дъга от кръг с център в дадените координати. ([повече |https://www.php.net/manual/en/function.imagearc]) colorAllocate(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------- -Връща идентификатор на цвят, представляващ цвят, съставен от дадените компоненти RGB. Трябва да се извика, за да се създаде всеки цвят, който ще се използва в изображението. ([повече подробности |https://www.php.net/manual/en/function.imagecolorallocate]) +Връща идентификатор на цвят, представляващ цвета, съставен от дадените RGB компоненти. Трябва да се извика за създаване на всеки цвят, който ще се използва в изображението. ([повече |https://www.php.net/manual/en/function.imagecolorallocate]) colorAllocateAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ------------------------------------------------------------------------------ -Работи по същия начин като `colorAllocate()`, като се добавя параметърът за прозрачност `$alpha`. ([повече подробности |https://www.php.net/manual/en/function.imagecolorallocatealpha]) +Действа по същия начин като `colorAllocate()` с добавяне на параметър за прозрачност `$alpha`. ([повече |https://www.php.net/manual/en/function.imagecolorallocatealpha]) colorAt(int $x, int $y): int .[method] -------------------------------------- -Връща цветовия индекс на пиксела на зададеното място в изображението. Ако изображението е truecolor, тази функция връща стойността RGB за този пиксел като цяло число. Използвайте битово изместване и битова маска, за да получите достъп до отделните стойности за червения, зеления и синия компонент. ([повече) |https://www.php.net/manual/en/function.imagecolorat] +Връща индекса на цвета на пиксела на определеното място в изображението. Ако изображението е truecolor, тази функция връща RGB стойността на този пиксел като цяло число. Използвайте битово изместване и битово маскиране за достъп до отделните стойности на червения, зеления и синия компонент: ([повече |https://www.php.net/manual/en/function.imagecolorat]) colorClosest(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------ -Връща индекса на цвета в палитрата на изображението, който е "най-близък" до зададената стойност RGB. "Разстоянието" между желания цвят и всеки цвят в палитрата се изчислява така, сякаш стойностите RGB са точки в триизмерното пространство. ([повече подробности |https://www.php.net/manual/en/function.imagecolorclosest]) +Връща индекса на цвета в палитрата на изображението, който е „най-близък“ до зададената RGB стойност. "Разстоянието" между искания цвят и всеки цвят в палитрата се изчислява, сякаш RGB стойностите представляват точки в триизмерно пространство. ([повече |https://www.php.net/manual/en/function.imagecolorclosest]) colorClosestAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ----------------------------------------------------------------------------- -Връща цветовия индекс в палитрата на изображението, който е "най-близък" до зададената RGB стойност и ниво `$alpha`. ([повече) |https://www.php.net/manual/en/function.imagecolorclosestalpha] +Връща индекса на цвета в палитрата на изображението, който е „най-близък“ до зададената RGB стойност и ниво `$alpha`. ([повече |https://www.php.net/manual/en/function.imagecolorclosestalpha]) colorClosestHWB(int $red, int $green, int $blue): int .[method] --------------------------------------------------------------- -Получаване на цветовия индекс, който има цветовете нюанс, бяло и черно, най-близки до дадения цвят. ([повече подробности |https://www.php.net/manual/en/function.imagecolorclosesthwb]) +Получете индекса на цвета, който има оттенък, бяло и черно най-близо до дадения цвят. ([повече |https://www.php.net/manual/en/function.imagecolorclosesthwb]) colorDeallocate(int $color): void .[method] ------------------------------------------- -Премахва цвета, който е бил зададен преди това с помощта на `colorAllocate()` или `colorAllocateAlpha()`. ([повече подробности |https://www.php.net/manual/en/function.imagecolordeallocate]) +Де-алокира цвят, предварително разпределен с `colorAllocate()` или `colorAllocateAlpha()`. ([повече |https://www.php.net/manual/en/function.imagecolordeallocate]) colorExact(int $red, int $green, int $blue): int .[method] ---------------------------------------------------------- -Връща индекса на посочения цвят в палитрата с изображения. ([повече подробности |https://www.php.net/manual/en/function.imagecolorexact]) +Връща индекса на зададения цвят в палитрата на изображението. ([повече |https://www.php.net/manual/en/function.imagecolorexact]) colorExactAlpha(int $red, int $green, int $blue, int $alpha): int .[method] --------------------------------------------------------------------------- -Връща индекса на посочения цвят + алфа в палитрата с изображения. ([повече подробности |https://www.php.net/manual/en/function.imagecolorexactalpha]) +Връща индекса на зададения цвят + алфа в палитрата на изображението. ([повече |https://www.php.net/manual/en/function.imagecolorexactalpha]) colorMatch(Image $image2): void .[method] ----------------------------------------- -Комбинира цветовете от палитрата с тези от друг панел. ([повече подробности |https://www.php.net/manual/en/function.imagecolormatch]) +Приспособява цветовете на палитрата към второто изображение. ([повече |https://www.php.net/manual/en/function.imagecolormatch]) colorResolve(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------ -Връща цветовия индекс за желания цвят - точния цвят или най-близкия възможен алтернативен цвят. ([повече подробности |https://www.php.net/manual/en/function.imagecolorresolve]) +Връща индекс на цвят за искания цвят, или точния цвят, или най-близката възможна алтернатива. ([повече |https://www.php.net/manual/en/function.imagecolorresolve]) colorResolveAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ----------------------------------------------------------------------------- -Връща цветовия индекс за желания цвят - точния цвят или най-близкия възможен алтернативен цвят. ([повече подробности |https://www.php.net/manual/en/function.imagecolorresolvealpha]) +Връща индекс на цвят за искания цвят, или точния цвят, или най-близката възможна алтернатива. ([повече |https://www.php.net/manual/en/function.imagecolorresolvealpha]) colorSet(int $index, int $red, int $green, int $blue): void .[method] --------------------------------------------------------------------- -Задава посочения индекс в палитрата на посочения цвят. ([повече подробности |https://www.php.net/manual/en/function.imagecolorset]) +Задава указания индекс в палитрата на зададения цвят. ([повече |https://www.php.net/manual/en/function.imagecolorset]) colorsForIndex(int $index): array .[method] ------------------------------------------- -Получава цвета на посочения индекс. ([повече подробности |https://www.php.net/manual/en/function.imagecolorsforindex]) +Получава цвета на указания индекс. ([повече |https://www.php.net/manual/en/function.imagecolorsforindex]) colorsTotal(): int .[method] ---------------------------- -Връща броя на цветовете в палитрата на изображението. ([повече подробности |https://www.php.net/manual/en/function.imagecolorstotal]) +Връща броя на цветовете в палитрата на изображението. ([повече |https://www.php.net/manual/en/function.imagecolorstotal]) -colorTransparent(int $color=null): int .[method] ------------------------------------------------- -Получава или задава прозрачен цвят за изображението. ([повече подробности |https://www.php.net/manual/en/function.imagecolortransparent]) +colorTransparent(?int $color=null): int .[method] +------------------------------------------------- +Получава или задава прозрачния цвят в изображението. ([повече |https://www.php.net/manual/en/function.imagecolortransparent]) convolution(array $matrix, float $div, float $offset): void .[method] --------------------------------------------------------------------- -Прилага конволюционна матрица към изображението, като използва посочените коефициент и отместване. ([повече подробности |https://www.php.net/manual/en/function.imageconvolution]) +Прилага конволюционна матрица към изображението, като използва дадения коефициент и отместване. ([повече |https://www.php.net/manual/en/function.imageconvolution]) .[note] -Изисква *Пълен GD разширение*, така че може да не работи навсякъде. +Изисква наличието на *Bundled GD extension*, така че може да не работи навсякъде. copy(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH): void .[method] -------------------------------------------------------------------------------------------------- -Копира част от `$src` в изображение, започващо от `$srcX`, `$srcY` с ширина `$srcW` и височина `$srcH`. Определената част ще бъде копирана в координатите `$dstX` и `$dstY`. ([повече подробности |https://www.php.net/manual/en/function.imagecopy]) +Копира част от `$src` върху изображението, започвайки от координати `$srcX`, `$srcY` с ширина `$srcW` и височина `$srcH`. Дефинираната част ще бъде копирана на координати `$dstX` и `$dstY`. ([повече |https://www.php.net/manual/en/function.imagecopy]) copyMerge(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] --------------------------------------------------------------------------------------------------------------------- -Копира част от `$src` в изображение, започващо от `$srcX`, `$srcY` с ширина `$srcW` и височина `$srcH`. Определената част ще бъде копирана в координатите `$dstX` и `$dstY`. ([повече подробности |https://www.php.net/manual/en/function.imagecopymerge]) +Копира част от `$src` върху изображението, започвайки от координати `$srcX`, `$srcY` с ширина `$srcW` и височина `$srcH`. Дефинираната част ще бъде копирана на координати `$dstX` и `$dstY`. ([повече |https://www.php.net/manual/en/function.imagecopymerge]) copyMergeGray(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] ------------------------------------------------------------------------------------------------------------------------- -Копира част от `$src` в изображение, започващо от `$srcX`, `$srcY` с ширина `$srcW` и височина `$srcH`. Определената част ще бъде копирана в координатите `$dstX` и `$dstY`. +Копира част от `$src` върху изображението, започвайки от координати `$srcX`, `$srcY` с ширина `$srcW` и височина `$srcH`. Дефинираната част ще бъде копирана на координати `$dstX` и `$dstY`. -Тази функция е идентична с `copyMerge()`, с изключение на това, че при сливането запазва оригиналния оттенък, като преобразува целевите пиксели в сива скала преди операцията по копиране. ([повече подробности |https://www.php.net/manual/en/function.imagecopymergegray]) +Тази функция е идентична с `copyMerge()` с изключение на това, че при сливането запазва оттенъка на източника, като преобразува целевите пиксели в сива скала преди операцията по копиране. ([повече |https://www.php.net/manual/en/function.imagecopymergegray]) copyResampled(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] --------------------------------------------------------------------------------------------------------------------------------- -Копира правоъгълна част от едно изображение в друго изображение, като плавно интерполира стойностите на пикселите, така че изображението да запази висока яснота при намаляване на размера. +Копира правоъгълна част от едно изображение върху друго изображение, гладко интерполира стойностите на пикселите, така че особено намаляването на размера на изображението все още запазва голяма яснота. -С други думи, `copyResampled()` взема правоъгълна област от `$src` с ширина `$srcW` и височина `$srcH` на позиция (`$srcX`, `$srcY`) и я поставя в правоъгълна област на изображението с ширина `$dstW` и височина `$dstH` на позиция (`$dstX`, `$dstY`). +С други думи, `copyResampled()` взема правоъгълна област от `$src` с ширина `$srcW` и височина `$srcH` в позиция (`$srcX`, `$srcY`) и я поставя в правоъгълна област на изображението с ширина `$dstW` и височина `$dstH` в позиция (`$dstX`, `$dstY`). -Ако координатите на източника и местоназначението, ширината и височината са различни, фрагментът от изображението се разтяга или компресира съответно. Координатите се отнасят за горния ляв ъгъл. Тази функция може да се използва за копиране на области от едно и също изображение, но ако областите се припокриват, резултатите не са предвидими. ([повече подробности |https://www.php.net/manual/en/function.imagecopyresampled]) +Ако изходните и целевите координати, ширина и височина се различават, се извършва съответното разтягане или свиване на фрагмента на изображението. Координатите се отнасят до горния ляв ъгъл. Тази функция може да се използва за копиране на области в рамките на едно и също изображение, но ако областите се припокриват, резултатите няма да бъдат предвидими. ([повече |https://www.php.net/manual/en/function.imagecopyresampled]) copyResized(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] ------------------------------------------------------------------------------------------------------------------------------- -Копира правоъгълната част на едно изображение върху друго изображение. С други думи, `copyResized()` взема правоъгълната област от `$src` с ширина `$srcW` и височина `$srcH` на позиция (`$srcX`, `$srcY`) и я поставя в правоъгълната област на изображението с ширина `$dstW` ] и височина `$dstH` на позиция (`$dstX`, `$dstY`). +Копира правоъгълна част от едно изображение върху друго изображение. С други думи, `copyResized()` получава правоъгълна област от `$src` с ширина `$srcW` и височина `$srcH` в позиция (`$srcX`, `$srcY`) и я поставя в правоъгълна област на изображението с ширина `$dstW` и височина `$dstH` в позиция (`$dstX`, `$dstY`). -Ако координатите на източника и местоназначението, ширината и височината са различни, фрагментът от изображението се разтяга или компресира съответно. Координатите се отнасят за горния ляв ъгъл. Тази функция може да се използва за копиране на области от едно и също изображение, но ако областите се припокриват, резултатите не са предвидими. ([повече подробности |https://www.php.net/manual/en/function.imagecopyresized]) +Ако изходните и целевите координати, ширина и височина се различават, се извършва съответното разтягане или свиване на фрагмента на изображението. Координатите се отнасят до горния ляв ъгъл. Тази функция може да се използва за копиране на области в рамките на едно и също изображение, но ако областите се припокриват, резултатите няма да бъдат предвидими. ([повече |https://www.php.net/manual/en/function.imagecopyresized]) crop(int|string $left, int|string $top, int|string $width, int|string $height): Image .[method] ----------------------------------------------------------------------------------------------- -Изрязване на изображението до определена правоъгълна област. Размерът може да бъде зададен като цяло число в пиксели или като низ в проценти (напр. `'50%'`). +Изрязва изображението до дадената правоъгълна област. Размерите могат да се задават като цели числа в пиксели или низове в проценти (например `'50%'`). -cropAuto(int $mode=-1, float $threshold=.5, int $color=-1): Image .[method] ---------------------------------------------------------------------------- -Автоматично изрязване на изображението в съответствие със зададеното `$mode`. ([прочети повече) |https://www.php.net/manual/en/function.imagecropauto] +cropAuto(int $mode=-1, float $threshold=.5, ?ImageColor $color=null): Image .[method] +------------------------------------------------------------------------------------- +Автоматично изрязва изображението според дадения `$mode`. ([повече |https://www.php.net/manual/en/function.imagecropauto]) -ellipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------- -Изчертава елипса с център в зададените координати. ([повече подробности |https://www.php.net/manual/en/function.imageellipse]) +ellipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------- +Рисува елипса с център на зададените координати. ([повече |https://www.php.net/manual/en/function.imageellipse]) -fill(int $x, int $y, int $color): void .[method] ------------------------------------------------- -Запълва областта, започваща от зададената координата (горе вляво 0, 0), със зададеното `$color`. ([повече подробности |https://www.php.net/manual/en/function.imagefill]) +fill(int $x, int $y, ImageColor $color): void .[method] +------------------------------------------------------- +Запълва област, започвайки от дадената координата (горе вляво е 0, 0) с дадения `$color`. ([повече |https://www.php.net/manual/en/function.imagefill]) -filledArc(int $cx, int $cy, int $w, int $h, int $s, int $e, int $color, int $style): void .[method] ---------------------------------------------------------------------------------------------------- -Изчертава непълна дъга с център в зададените координати. ([повече подробности |https://www.php.net/manual/en/function.imagefilledarc]) +filledArc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color, int $style): void .[method] +--------------------------------------------------------------------------------------------------------------------------------------------- +Рисува частична дъга с център на зададените координати. ([повече |https://www.php.net/manual/en/function.imagefilledarc]) -filledEllipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------------- -Изчертава елипса с център в зададените координати. ([повече подробности |https://www.php.net/manual/en/function.imagefilledellipse]) +filledEllipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------------- +Рисува елипса с център на зададените координати. ([повече |https://www.php.net/manual/en/function.imagefilledellipse]) -filledPolygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------------- -Създава запълнен многоъгълник върху изображението. ([повече подробности |https://www.php.net/manual/en/function.imagefilledpolygon]) +filledPolygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------------- +Създава в изображението запълнен многоъгълник. ([повече |https://www.php.net/manual/en/function.imagefilledpolygon]) -filledRectangle(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] -------------------------------------------------------------------------------- -Създава правоъгълник, запълнен с `$color` в изображението, като започва от точка 1 и завършва в точка 2. Точка 0, 0 е горният ляв ъгъл на изображението. ([повече подробности |https://www.php.net/manual/en/function.imagefilledrectangle]) +filledRectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------- +Създава правоъгълник, запълнен с `$color` в изображението, започвайки от точка `$x1` & `$y1` и завършвайки в точка `$x2` & `$y2`. Точка 0, 0 е горният ляв ъгъл на изображението. ([повече |https://www.php.net/manual/en/function.imagefilledrectangle]) -fillToBorder(int $x, int $y, int $border, int $color): void .[method] ---------------------------------------------------------------------- -Създава запълване, чийто цвят на границата се определя от `$border`. Началната точка на запълването е `$x`, `$y` (горният ляв ъгъл - 0, 0), а областта се запълва с цвета `$color`. ([повече подробности |https://www.php.net/manual/en/function.imagefilltoborder]) +filledRectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------------- +Създава правоъгълник, запълнен с `$color` в изображението, започвайки от точка `$left` & `$top` с ширина `$width` и височина `$height`. Точка 0, 0 е горният ляв ъгъл на изображението. + + +fillToBorder(int $x, int $y, int $border, ImageColor $color): void .[method] +---------------------------------------------------------------------------- +Извършва запълване, чийто цвят на границата е дефиниран с `$border`. Началната точка на запълването е `$x`, `$y` (горе вляво е 0, 0) и областта се запълва с цвят `$color`. ([повече |https://www.php.net/manual/en/function.imagefilltoborder]) filter(int $filtertype, int ...$args): void .[method] ----------------------------------------------------- -Прилага зададения филтър `$filtertype` към изображението. ([повече подробности |https://www.php.net/manual/en/function.imagefilter]) +Прилага дадения филтър `$filtertype` към изображението. ([повече |https://www.php.net/manual/en/function.imagefilter]) flip(int $mode): void .[method] ------------------------------- -Инвертира изображението на посочения адрес `$mode`. ([прочети повече) |https://www.php.net/manual/en/function.imageflip] +Обръща изображението, използвайки дадения `$mode`. ([повече |https://www.php.net/manual/en/function.imageflip]) -ftText(int $size, int $angle, int $x, int $y, int $col, string $fontFile, string $text, array $extrainfo=null): array .[method] -------------------------------------------------------------------------------------------------------------------------------- -Напишете текст върху изображението, като използвате шрифтове FreeType 2. ([още) |https://www.php.net/manual/en/function.imagefttext] +ftText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +---------------------------------------------------------------------------------------------------------------------------------------- +Напишете текст в изображението. ([повече |https://www.php.net/manual/en/function.imagefttext]) gammaCorrect(float $inputgamma, float $outputgamma): void .[method] ------------------------------------------------------------------- -Прилагане на гама корекция на изображението спрямо входната и изходната гама. ([повече подробности |https://www.php.net/manual/en/function.imagegammacorrect]) +Прилага гама корекция към изображението спрямо входната и изходната гама. ([повече |https://www.php.net/manual/en/function.imagegammacorrect]) getClip(): array .[method] -------------------------- -Връща текущото изрязване, т.е. областта, извън която няма да бъдат изчертавани пиксели. ([повече подробности |https://www.php.net/manual/en/function.imagegetclip]) +Връща текущото изрязване, т.е. областта, извън която няма да бъдат нарисувани пиксели. ([повече |https://www.php.net/manual/en/function.imagegetclip]) getHeight(): int .[method] @@ -504,7 +545,7 @@ getHeight(): int .[method] getImageResource(): resource|GdImage .[method] ---------------------------------------------- -Връща ресурса източник. +Връща оригиналния ресурс. getWidth(): int .[method] @@ -512,169 +553,164 @@ getWidth(): int .[method] Връща ширината на изображението. -interlace(int $interlace=null): int .[method] ---------------------------------------------- -Включва или изключва режима с преливане. Ако е зададен режим с преплитане и изображението е записано във формат JPEG, изображението ще бъде записано като прогресивен JPEG. ([повече подробности |https://www.php.net/manual/en/function.imageinterlace]) +interlace(?int $interlace=null): int .[method] +---------------------------------------------- +Включва или изключва режима на преплитане. Ако режимът на преплитане е зададен и изображението се запазва като JPEG, то ще бъде запазено като прогресивен JPEG. ([повече |https://www.php.net/manual/en/function.imageinterlace]) isTrueColor(): bool .[method] ----------------------------- -Определете дали изображението е истинскоцветно. ([прочети повече) |https://www.php.net/manual/en/function.imageistruecolor] +Установява дали изображението е truecolor. ([повече |https://www.php.net/manual/en/function.imageistruecolor]) layerEffect(int $effect): void .[method] ---------------------------------------- -Задайте флага за алфа смесване, за да използвате ефекти на наслояване. ([повече подробности |https://www.php.net/manual/en/function.imagelayereffect]) +Задайте флага за алфа смесване, за да използвате ефекти на слоеве. ([повече |https://www.php.net/manual/en/function.imagelayereffect]) -line(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] --------------------------------------------------------------------- -Начертава линия между две дадени точки. ([повече подробности |https://www.php.net/manual/en/function.imageline]) +line(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +--------------------------------------------------------------------------- +Рисува линия между две дадени точки. ([повече |https://www.php.net/manual/en/function.imageline]) -openPolygon(array $points, int $numPoints, int $color): void .[method] ----------------------------------------------------------------------- -Начертава отворен многоъгълник в изображението. За разлика от `polygon()`, между последната и първата точка не се прокарва линия. ([повече подробности |https://www.php.net/manual/en/function.imageopenpolygon]) +openPolygon(array $points, ImageColor $color): void .[method] +------------------------------------------------------------- +Рисува отворен многоъгълник върху изображението. За разлика от `polygon()`, между последната и първата точка не се рисува линия. ([повече |https://www.php.net/manual/en/function.imageopenpolygon]) paletteCopy(Image $source): void .[method] ------------------------------------------ -Копира палитрата от `$source` в изображението. ([повече подробности |https://www.php.net/manual/en/function.imagepalettecopy]) +Копира палитрата от `$source` в изображението. ([повече |https://www.php.net/manual/en/function.imagepalettecopy]) paletteToTrueColor(): void .[method] ------------------------------------ -Преобразува изображение, базирано на палитра, в пълноцветно изображение. ([повече подробности |https://www.php.net/manual/en/function.imagepalettetotruecolor]) +Преобразува изображение, базирано на палитра, в пълноцветно изображение. ([повече |https://www.php.net/manual/en/function.imagepalettetotruecolor]) place(Image $image, int|string $left=0, int|string $top=0, int $opacity=100): Image .[method] --------------------------------------------------------------------------------------------- -Копира `$image` в изображението с координати `$left` и `$top`. Координатите могат да бъдат зададени като цели числа в пиксели или като низове в проценти (напр. `'50%'`). +Копира `$image` в изображението на координати `$left` и `$top`. Координатите могат да се задават като цели числа в пиксели или низове в проценти (например `'50%'`). -polygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------- -Създава многоъгълник в изображението. ([повече подробности |https://www.php.net/manual/en/function.imagepolygon]) +polygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------- +Създава многоъгълник в изображението. ([повече |https://www.php.net/manual/en/function.imagepolygon]) -rectangle(int $x1, int $y1, int $x2, int $y2, int $col): void .[method] ------------------------------------------------------------------------ -Създава правоъгълник със зададени координати. ([повече подробности |https://www.php.net/manual/en/function.imagerectangle]) +rectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------- +Създава правоъгълник на зададените координати. ([повече |https://www.php.net/manual/en/function.imagerectangle]) + + +rectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------- +Създава правоъгълник на зададените координати. resize(int|string $width, int|string $height, int $flags=Image::OrSmaller): Image .[method] ------------------------------------------------------------------------------------------- -Промяна на размера на изображението, [допълнителна информация |#Image-Resize]. Размерите могат да бъдат зададени като цели числа в пиксели или като низове в проценти (напр. `'50%'`). +Преоразмерява изображението, [повече информация |#Преоразмеряване]. Размерите могат да се задават като цели числа в пиксели или низове в проценти (например `'50%'`). -resolution(int $resX=null, int $resY=null): mixed .[method] ------------------------------------------------------------ -Задава или връща разделителната способност на изображението в DPI (точки на инч). Ако не е посочен нито един от незадължителните параметри, текущата резолюция се връща като индексирано поле. Ако е посочен само `$resX`, хоризонталната и вертикалната разделителна способност се задава на тази стойност. Ако са зададени и двата незадължителни параметъра, хоризонталната и вертикалната разделителна способност се настройват на тези стойности. +resolution(?int $resX=null, ?int $resY=null): mixed .[method] +------------------------------------------------------------- +Задава или връща резолюцията на изображението в DPI (точки на инч). Ако не е зададен нито един от незадължителните параметри, текущата резолюция се връща като индексиран масив. Ако е зададен само `$resX`, хоризонталната и вертикалната резолюция се задават на тази стойност. Ако са зададени и двата незадължителни параметъра, хоризонталната и вертикалната резолюция се задават на тези стойности. -Разделителната способност се използва като метаинформация само при четене и запис на изображения във формати, които поддържат такава информация (понастоящем PNG и JPEG). Тя не оказва влияние върху операциите по боядисване. Разделителната способност по подразбиране за нови изображения е 96 DPI. ([прочети повече) |https://www.php.net/manual/en/function.imageresolution] +Резолюцията се използва само като мета информация, когато изображенията се четат и записват във формати, поддържащи този вид информация (в момента PNG и JPEG). Тя не влияе на никакви операции по рисуване. Резолюцията по подразбиране на новите изображения е 96 DPI. ([повече |https://www.php.net/manual/en/function.imageresolution]) rotate(float $angle, int $backgroundColor): Image .[method] ----------------------------------------------------------- -Завърта изображението със зададената стойност `$angle` в градуси. Центърът на завъртане е центърът на изображението и завъртяното изображение може да има различен размер от оригиналното изображение. ([повече подробности |https://www.php.net/manual/en/function.imagerotate]) +Завърта изображението с дадения `$angle` в градуси. Центърът на въртене е центърът на изображението и завъртяното изображение може да има различни размери от оригиналното изображение. ([повече |https://www.php.net/manual/en/function.imagerotate]) .[note] -Изисква *Пълен GD разширение*, така че може да не работи навсякъде. +Изисква наличието на *Bundled GD extension*, така че може да не работи навсякъде. -save(string $file, int $quality=null, int $type=null): void .[method] ---------------------------------------------------------------------- -Записва изображението във файл. +save(string $file, ?int $quality=null, ?int $type=null): void .[method] +----------------------------------------------------------------------- +Запазва изображението във файл. -Качеството на компресията е в диапазона 0..100 за JPEG (по подразбиране 85), WEBP (по подразбиране 80) и AVIF (по подразбиране 30) и 0..9 за PNG (по подразбиране 9). Ако типът не е очевиден от разширението на файла, можете да го посочите, като използвате една от константите `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` и `Image::BMP`. +Качеството на компресия е в диапазона 0..100 за JPEG (по подразбиране 85), WEBP (по подразбиране 80) и AVIF (по подразбиране 30) и 0..9 за PNG (по подразбиране 9). Ако типът не е ясен от разширението на файла, можете да го зададете с помощта на една от константите `ImageType`. saveAlpha(bool $saveflag): void .[method] ----------------------------------------- -Задава флага за запазване на пълната информация за алфа канала (за разлика от монохромната прозрачност) при запазване на PNG изображения. +Задава флага дали при запазване на PNG изображения да се запази пълната информация за алфа канала (за разлика от едноцветната прозрачност). -За да запазите алфа канала, алфа квантуването трябва да е деактивирано (`alphaBlending(false)`). ([още) |https://www.php.net/manual/en/function.imagesavealpha] +Алфа смесването трябва да бъде деактивирано (`alphaBlending(false)`), за да се запази алфа каналът на първо място. ([повече |https://www.php.net/manual/en/function.imagesavealpha]) scale(int $newWidth, int $newHeight=-1, int $mode=IMG_BILINEAR_FIXED): Image .[method] -------------------------------------------------------------------------------------- -Мащабирайте изображението, като използвате зададения алгоритъм за интерполация. ([повече подробности |https://www.php.net/manual/en/function.imagescale]) +Мащабира изображението, използвайки дадения интерполационен алгоритъм. ([повече |https://www.php.net/manual/en/function.imagescale]) -send(int $type=Image::JPEG, int $quality=null): void .[method] --------------------------------------------------------------- +send(int $type=ImageType::JPEG, ?int $quality=null): void .[method] +------------------------------------------------------------------- Извежда изображението в браузъра. -Качеството на компресията е в диапазона 0..100 за JPEG (по подразбиране 85), WEBP (по подразбиране 80) и AVIF (по подразбиране 30) и 0..9 за PNG (по подразбиране 9). Типът е една от константите `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` и `Image::BMP`. +Качеството на компресия е в диапазона 0..100 за JPEG (по подразбиране 85), WEBP (по подразбиране 80) и AVIF (по подразбиране 30) и 0..9 за PNG (по подразбиране 9). setBrush(Image $brush): void .[method] -------------------------------------- -Задава изображението на четката, което да се използва във всички функции за рисуване на линии (напр. `line()` и `polygon()`) при рисуване със специални цветове IMG_COLOR_BRUSHED или IMG_COLOR_STYLEDBRUSHED. ([още) |https://www.php.net/manual/en/function.imagesetbrush] +Задава изображението на четката, което ще се използва във всички функции за рисуване на линии (например `line()` и `polygon()`) при рисуване със специалните цветове IMG_COLOR_BRUSHED или IMG_COLOR_STYLEDBRUSHED. ([повече |https://www.php.net/manual/en/function.imagesetbrush]) setClip(int $x1, int $y1, int $x2, int $y2): void .[method] ----------------------------------------------------------- -Задава текущото изрязване, т.е. областта, извън която няма да бъдат изчертавани пиксели. ([повече подробности |https://www.php.net/manual/en/function.imagesetclip]) +Задава текущото изрязване, т.е. областта, извън която няма да бъдат нарисувани пиксели. ([повече |https://www.php.net/manual/en/function.imagesetclip]) setInterpolation(int $method=IMG_BILINEAR_FIXED): void .[method] ---------------------------------------------------------------- -Задава метода на интерполация, който влияе на методите `rotate()` и `affine()`. ([повече подробности |https://www.php.net/manual/en/function.imagesetinterpolation]) +Задава метода на интерполация, който влияе на методите `rotate()` и `affine()`. ([повече |https://www.php.net/manual/en/function.imagesetinterpolation]) -setPixel(int $x, int $y, int $color): void .[method] ----------------------------------------------------- -Изчертава пиксел в зададена координата. ([повече подробности |https://www.php.net/manual/en/function.imagesetpixel]) +setPixel(int $x, int $y, ImageColor $color): void .[method] +----------------------------------------------------------- +Рисува пиксел на зададената координата. ([повече |https://www.php.net/manual/en/function.imagesetpixel]) setStyle(array $style): void .[method] -------------------------------------- -Задава стила, който да се използва от всички функции за рисуване на линии (например `line()` и `polygon()`) при рисуване на линии със специален цвят IMG_COLOR_STYLED или линии на изображения с цвят IMG_COLOR_STYLEDBRUSHED. ([още |https://www.php.net/manual/en/function.imagesetstyle]) +Задава стила, който трябва да се използва от всички функции за рисуване на линии (например `line()` и `polygon()`) при рисуване със специалния цвят IMG_COLOR_STYLED или линии от изображения с цвят IMG_COLOR_STYLEDBRUSHED. ([повече |https://www.php.net/manual/en/function.imagesetstyle]) setThickness(int $thickness): void .[method] -------------------------------------------- -Задава дебелината на линията при чертане на правоъгълници, многоъгълници, дъги и др. В `$thickness` пиксела. ([повече подробности |https://www.php.net/manual/en/function.imagesetthickness]) +Задава дебелината на линиите при рисуване на правоъгълници, многоъгълници, дъги и т.н. на `$thickness` пиксела. ([повече |https://www.php.net/manual/en/function.imagesetthickness]) setTile(Image $tile): void .[method] ------------------------------------ -Задава изображението на плочките, което да се използва във всички функции за запълване на региони (напр. `fill()` и `filledPolygon()`), когато е запълнено със специалния цвят IMG_COLOR_TILED. +Задава изображението на плочката, което ще се използва във всички функции за запълване на региони (например `fill()` и `filledPolygon()`), когато се запълва със специалния цвят IMG_COLOR_TILED. -Плочка е изображение, което се използва за запълване на област с повтарящ се модел. Всяко изображение може да бъде използвано като плочка и чрез посочване на индекса на прозрачния цвят на изображението на плочката с `colorTransparent()`, можете да създадете плочка, в която определени части от основния регион ще прозират. ([повече подробности |https://www.php.net/manual/en/function.imagesettile]) +Плочката е изображение, използвано за запълване на област с повтарящ се модел. Всяко изображение може да се използва като плочка и чрез задаване на прозрачен цветен индекс на изображението на плочката с `colorTransparent()` може да се създаде плочка, където определени части от подлежащата област ще прозират. ([повече |https://www.php.net/manual/en/function.imagesettile]) sharpen(): Image .[method] -------------------------- -Увеличава рязкостта на изображението. +Изостря изображението. .[note] -Изисква *Пълен GD разширение*, така че може да не работи навсякъде. - - -string(int $font, int $x, int $y, string $str, int $col): void .[method] ------------------------------------------------------------------------- -Извежда низ в зададените координати. ([повече подробности |https://www.php.net/manual/en/function.imagestring]) +Изисква наличието на *Bundled GD extension*, така че може да не работи навсякъде. -stringUp(int $font, int $x, int $y, string $s, int $col): void .[method] ------------------------------------------------------------------------- -Извежда низ по вертикала в зададените координати. ([повече подробности |https://www.php.net/manual/en/function.imagestringup]) - - -toString(int $type=Image::JPEG, int $quality=null): string .[method] --------------------------------------------------------------------- -Записва изображението в низ. +toString(int $type=ImageType::JPEG, ?int $quality=null): string .[method] +------------------------------------------------------------------------- +Запазва изображението в низ. -Качеството на компресията е в диапазона 0..100 за JPEG (по подразбиране 85), WEBP (по подразбиране 80) и AVIF (по подразбиране 30) и 0..9 за PNG (по подразбиране 9). Типът е една от константите `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` и `Image::BMP`. +Качеството на компресия е в диапазона 0..100 за JPEG (по подразбиране 85), WEBP (по подразбиране 80) и AVIF (по подразбиране 30) и 0..9 за PNG (по подразбиране 9). trueColorToPalette(bool $dither, int $ncolors): void .[method] -------------------------------------------------------------- -Преобразува истинско цветно изображение в палитра. ([прочети повече) |https://www.php.net/manual/en/function.imagetruecolortopalette] +Преобразува truecolor изображение в палетно. ([повече |https://www.php.net/manual/en/function.imagetruecolortopalette]) -ttfText(int $size, int $angle, int $x, int $y, int $color, string $fontfile, string $text): array .[method] ------------------------------------------------------------------------------------------------------------ -Отпечатва зададен текст в изображение, като използва шрифтове TrueType. ([повече подробности |https://www.php.net/manual/en/function.imagettftext]) +ttfText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +----------------------------------------------------------------------------------------------------------------------------------------- +Извежда дадения текст в изображението. ([повече |https://www.php.net/manual/en/function.imagettftext]) diff --git a/utils/bg/iterables.texy b/utils/bg/iterables.texy new file mode 100644 index 0000000000..c466d66e7d --- /dev/null +++ b/utils/bg/iterables.texy @@ -0,0 +1,170 @@ +Работа с итератори +****************** + +.[perex]{data-version:4.0.4} +[api:Nette\Utils\Iterables] е статичен клас с функции за работа с итератори. Неговият аналог за масиви е [Nette\Utils\Arrays |arrays]. + + +Инсталация: + +```shell +composer require nette/utils +``` + +Всички примери предполагат създаден псевдоним: + +```php +use Nette\Utils\Iterables; +``` + + +contains(iterable $iterable, $value): bool .[method] +---------------------------------------------------- + +Търси зададена стойност в итератора. Използва стриктно сравнение (`===`) за проверка на съвпадението. Връща `true`, ако стойността е намерена, в противен случай `false`. + +```php +Iterables::contains(new ArrayIterator([1, 2, 3]), 1); // true +Iterables::contains(new ArrayIterator([1, 2, 3]), '1'); // false +``` + +Този метод е полезен, когато трябва бързо да разберете дали конкретна стойност се намира в итератора, без да се налага ръчно да преминавате през всички елементи. + + +containsKey(iterable $iterable, $key): bool .[method] +----------------------------------------------------- + +Търси зададен ключ в итератора. Използва стриктно сравнение (`===`) за проверка на съвпадението. Връща `true`, ако ключът е намерен, в противен случай `false`. + +```php +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 0); // true +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 4); // false +``` + + +every(iterable $iterable, callable $predicate): bool .[method] +-------------------------------------------------------------- + +Проверява дали всички елементи на итератора отговарят на условието, дефинирано в `$predicate`. Функцията `$predicate` има сигнатура `function ($value, $key, iterable $iterable): bool` и трябва да връща `true` за всеки елемент, за да може методът `every()` да върне `true`. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isBelowThreshold = fn($value) => $value < 40; +$res = Iterables::every($iterator, $isBelowThreshold); // true +``` + +Този метод е полезен за проверка дали всички елементи в колекция отговарят на определено условие, например дали всички числа са по-малки от определена стойност. + + +filter(iterable $iterable, callable $predicate): Generator .[method] +-------------------------------------------------------------------- + +Създава нов итератор, който съдържа само тези елементи от оригиналния итератор, които отговарят на условието, дефинирано в `$predicate`. Функцията `$predicate` има сигнатура `function ($value, $key, iterable $iterable): bool` и трябва да връща `true` за елементите, които трябва да бъдат запазени. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::filter($iterator, fn($v) => $v < 3); +// 1, 2 +``` + +Методът използва генератор, което означава, че филтрирането се извършва постепенно при преминаване през резултата. Това е ефективно от гледна точка на паметта и позволява обработката дори на много големи колекции. Ако не преминете през всички елементи на резултантния итератор, ще спестите изчислителна мощ, тъй като не всички елементи от оригиналния итератор ще бъдат обработени. + + +first(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------------- + +Връща първия елемент на итератора. Ако е зададен `$predicate`, връща първия елемент, който отговаря на даденото условие. Функцията `$predicate` има сигнатура `function ($value, $key, iterable $iterable): bool`. Ако не е намерен отговарящ елемент, се извиква функцията `$else` (ако е зададена) и се връща нейният резултат. Ако `$else` не е зададено, се връща `null`. + +```php +Iterables::first(new ArrayIterator([1, 2, 3])); // 1 +Iterables::first(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 3 +Iterables::first(new ArrayIterator([])); // null +Iterables::first(new ArrayIterator([]), else: fn() => false); // false +``` + +Този метод е полезен, когато трябва бързо да получите първия елемент на колекция или първия елемент, отговарящ на определено условие, без да се налага ръчно да преминавате през цялата колекция. + + +firstKey(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +---------------------------------------------------------------------------------------------- + +Връща ключа на първия елемент на итератора. Ако е зададен `$predicate`, връща ключа на първия елемент, който отговаря на даденото условие. Функцията `$predicate` има сигнатура `function ($value, $key, iterable $iterable): bool`. Ако не е намерен отговарящ елемент, се извиква функцията `$else` (ако е зададена) и се връща нейният резултат. Ако `$else` не е зададено, се връща `null`. + +```php +Iterables::firstKey(new ArrayIterator([1, 2, 3])); // 0 +Iterables::firstKey(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 2 +Iterables::firstKey(new ArrayIterator(['a' => 1, 'b' => 2])); // 'a' +Iterables::firstKey(new ArrayIterator([])); // null +``` + + +map(iterable $iterable, callable $transformer): Generator .[method] +------------------------------------------------------------------- + +Създава нов итератор чрез прилагане на функцията `$transformer` към всеки елемент на оригиналния итератор. Функцията `$transformer` има сигнатура `function ($value, $key, iterable $iterable): mixed` и нейната върната стойност се използва като нова стойност на елемента. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::map($iterator, fn($v) => $v * 2); +// 2, 4, 6 +``` + +Методът използва генератор, което означава, че трансформацията се извършва постепенно при преминаване през резултата. Това е ефективно от гледна точка на паметта и позволява обработката дори на много големи колекции. Ако не преминете през всички елементи на резултантния итератор, ще спестите изчислителна мощ, тъй като не всички елементи от оригиналния итератор ще бъдат обработени. + + +mapWithKeys(iterable $iterable, callable $transformer): Generator .[method] +--------------------------------------------------------------------------- + +Създава нов итератор чрез трансформация на стойностите и ключовете на оригиналния итератор. Функцията `$transformer` има сигнатура `function ($value, $key, iterable $iterable): ?array{$newKey, $newValue}`. Ако `$transformer` върне `null`, елементът се пропуска. За запазените елементи първият елемент на върнатия масив се използва като нов ключ, а вторият елемент като нова стойност. + +```php +$iterator = new ArrayIterator(['a' => 1, 'b' => 2]); +$iterator = Iterables::mapWithKeys($iterator, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +Подобно на `map()`, този метод използва генератор за постепенно обработване и ефективна работа с паметта. Това позволява работа с големи колекции и спестяване на изчислителна мощ при частично преминаване през резултата. + + +memoize(iterable $iterable): IteratorAggregate .[method] +-------------------------------------------------------- + +Създава обвивка около итератора, която по време на итерация кешира неговите ключове и стойности. Това позволява повторна итерация на данните без необходимост от повторно преминаване през оригиналния източник на данни. + +```php +$iterator = /* данни, които не могат да бъдат итерирани повече от веднъж */ +$memoized = Iterables::memoize($iterator); +// Сега можете да итерирате $memoized многократно без загуба на данни +``` + +Този метод е полезен в ситуации, когато трябва да преминете многократно през един и същ набор от данни, но оригиналният итератор не позволява повторна итерация или повторното преминаване би било скъпо (напр. при четене на данни от база данни или файл). + + +some(iterable $iterable, callable $predicate): bool .[method] +------------------------------------------------------------- + +Проверява дали поне един елемент на итератора отговаря на условието, дефинирано в `$predicate`. Функцията `$predicate` има сигнатура `function ($value, $key, iterable $iterable): bool` и трябва да връща `true` за поне един елемент, за да може методът `some()` да върне `true`. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isEven = fn($value) => $value % 2 === 0; +$res = Iterables::some($iterator, $isEven); // true +``` + +Този метод е полезен за бърза проверка дали в колекцията съществува поне един елемент, отговарящ на определено условие, например дали колекцията съдържа поне едно четно число. + +Вижте [#every()]. + + +toIterator(iterable $iterable): Iterator .[method] +-------------------------------------------------- + +Преобразува всеки итерируем обект (array, Traversable) в Iterator. Ако входът вече е Iterator, го връща без промяна. + +```php +$array = [1, 2, 3]; +$iterator = Iterables::toIterator($array); +// Сега имате Iterator вместо масив +``` + +Този метод е полезен, когато трябва да се уверите, че разполагате с Iterator, независимо от типа на входните данни. Това може да бъде полезно при създаване на функции, които работят с различни типове итерируеми данни. diff --git a/utils/bg/json.texy b/utils/bg/json.texy index ae3f5f104c..cc1fd32100 100644 --- a/utils/bg/json.texy +++ b/utils/bg/json.texy @@ -2,53 +2,53 @@ ************* .[perex] -[api:Nette\Utils\Json] е статичен клас с функции за кодиране и декодиране на JSON. Тя се справя с уязвимостите в различните версии на PHP и хвърля изключения при възникване на грешки. +[api:Nette\Utils\Json] е статичен клас с функции за кодиране и декодиране на JSON формат. Той обработва уязвимостите на различни версии на PHP и хвърля изключения при грешки. -Монтаж: +Инсталация: ```shell composer require nette/utils ``` -Всички примери предполагат, че псевдонимът вече е създаден: +Всички примери предполагат създаден псевдоним: ```php use Nette\Utils\Json; ``` -Използване на .[#toc-usage] -=========================== +Използване +========== encode(mixed $value, bool $pretty=false, bool $asciiSafe=false, bool $htmlSafe=false, bool $forceObjects=false): string .[method] --------------------------------------------------------------------------------------------------------------------------------- -Конвертира `$value` във формат JSON. +Преобразува `$value` в JSON формат. -Ако е зададено на `$pretty`, това форматира JSON за по-лесно четене и яснота: +При настройка `$pretty` форматира JSON за по-лесно четене и прегледност: ```php Json::encode($value); // връща JSON -Json::encode($value, pretty: true); // връща по-чист JSON +Json::encode($value, pretty: true); // връща по-прегледен JSON ``` -На адрес `$asciiSafe` той генерира изход в ASCII, т.е. замества символите Unicode с последователността `\uxxxx`: +При `$asciiSafe` генерира изход в ASCII, т.е. unicode знаците се заменят със секвенции `\uxxxx`: ```php -Json::encode('žluťoučký', asciiSafe: true); -// '"\u017elu\u0165ou\u010dk\u00fd"' +Json::encode('жълт', asciiSafe: true); // 'жълт' е преведено от 'žluťoučký' +// '"\u0436\u044a\u043b\u0442"' ``` -Параметърът `$htmlSafe` гарантира, че изходът няма да съдържа символи, които имат специално значение в HTML: +Параметърът `$htmlSafe` гарантира, че изходът няма да съдържа знаци със специално значение в HTML: ```php Json::encode('onesendJson($data)`, който може да бъде извикан в метода `action*()`, например вижте [Изпращане на отговор |application:presenters#Sending a Response]. +За това може да се използва методът `$this->sendJson($data)`, който можем да извикаме например в метод `action*()`, вижте [Изпращане на отговор |application:presenters#Изпращане на отговор]. diff --git a/utils/bg/paginator.texy b/utils/bg/paginator.texy index 3d9a6ce637..1971b53d54 100644 --- a/utils/bg/paginator.texy +++ b/utils/bg/paginator.texy @@ -2,47 +2,47 @@ Paginator ********* .[perex] -Необходимо е да подредите страниците на изхвърляне на данни? Тъй като математиката на странирането може да бъде сложна, [api:Nette\Utils\Paginator] може да ви помогне с това. +Трябва да страницирате извеждането на данни? Тъй като математиката на страницирането може да бъде коварна, [api:Nette\Utils\Paginator] ще ви помогне с нея. -Монтаж: +Инсталация: ```shell composer require nette/utils ``` -Създаване на обект за странициране и задаване на основната му информация: +Създаваме обект на странициращия елемент и му задаваме основната информация: ```php $paginator = new Nette\Utils\Paginator; $paginator->setPage(1); // номер на текущата страница -$paginator->setItemsPerPage(30); // брой елементи на страницата +$paginator->setItemsPerPage(30); // брой елементи на страница $paginator->setItemCount(356); // общ брой елементи, ако е известен ``` -Страниците са номерирани от 1. Можем да променим това с помощта на `setBase()`: +Страниците се номерират от 1. Можем да променим това с помощта на `setBase()`: ```php -$paginator->setBase(0); // номериране от 0 +$paginator->setBase(0); // номерираме от 0 ``` -Обектът вече ще предоставя цялата основна информация, полезна за създаването на страница за страниране. Например можете да го предадете на шаблон и да го използвате там. +Обектът сега предоставя цялата основна информация, полезна при създаването на странициращ елемент. Можете например да го предадете на шаблон и да го използвате там. ```php -$paginator->isFirst(); // дали сме на първата страница? -$paginator->isLast(); // дали сме на последната страница? +$paginator->isFirst(); // на първа страница ли сме? +$paginator->isLast(); // на последна страница ли сме? $paginator->getPage(); // номер на текущата страница $paginator->getFirstPage(); // номер на първата страница $paginator->getLastPage(); // номер на последната страница $paginator->getFirstItemOnPage(); // пореден номер на първия елемент на страницата $paginator->getLastItemOnPage(); // пореден номер на последния елемент на страницата -$paginator->getPageIndex(); // номер на текущата страница, номериране от 0 +$paginator->getPageIndex(); // номер на текущата страница, номериран от 0 $paginator->getPageCount(); // общ брой страници -$paginator->getItemsPerPage(); // брой елементи на страницата +$paginator->getItemsPerPage(); // брой елементи на страница $paginator->getItemCount(); // общ брой елементи, ако е известен ``` -Конструкторът на страници ще ви помогне да правите SQL заявки. Методите `getLength()` и `getOffset()` връщат стойности, които да се използват в клаузите LIMIT и OFFSET: +Странициращият елемент помага при формулирането на SQL заявка. Методите `getLength()` и `getOffset()` връщат стойности, които използваме в клаузите LIMIT и OFFSET: ```php $result = $database->query( @@ -52,7 +52,7 @@ $result = $database->query( ); ``` -Ако трябва да подредим страниците в обратен ред, т.е. страница 1 да отговаря на най-високото отместване, използваме `getCountdownOffset()`: +Ако трябва да страницираме в обратен ред, т.е. страница № 1 съответства на най-високото отместване, използваме `getCountdownOffset()`: ```php $result = $database->query( @@ -62,4 +62,4 @@ $result = $database->query( ); ``` -Пример за използване на тази функция в приложение е даден в готварската книга [Database Results Pagination |best-practices:pagination]. +Пример за използване в приложение можете да намерите в готварската книга [Странициране на резултати от база данни |best-practices:pagination]. diff --git a/utils/bg/random.texy b/utils/bg/random.texy index b960ff1fac..d0817dc81a 100644 --- a/utils/bg/random.texy +++ b/utils/bg/random.texy @@ -5,7 +5,7 @@ [api:Nette\Utils\Random] е статичен клас за генериране на криптографски сигурни псевдослучайни низове. -Монтаж: +Инсталация: ```shell composer require nette/utils @@ -15,7 +15,7 @@ composer require nette/utils generate(int $length=10, string $charlist=`'0-9a-z'`): string .[method] ----------------------------------------------------------------------- -Генерира случаен низ със зададена дължина от символите, посочени в `$charlist`. Можете да използвате и интервали, записани например като `0-9`. +Генерира случаен низ с дадена дължина от знаците, посочени в параметъра `$charlist`. Могат да се използват и интервали, записани като например `0-9`. ```php use Nette\Utils\Random; diff --git a/utils/bg/reflection.texy b/utils/bg/reflection.texy index 144f8a5863..21f51a0cd8 100644 --- a/utils/bg/reflection.texy +++ b/utils/bg/reflection.texy @@ -1,17 +1,17 @@ -Отражение на PHP -**************** +PHP Рефлексия +************* .[perex] -[api:Nette\Utils\Reflection] е статичен клас с полезни функции за отразяване на PHP. Целта му е да отстрани недостатъците на оригиналните класове и да уеднакви поведението на различните версии на PHP. +[api:Nette\Utils\Reflection] е статичен клас с полезни функции за PHP рефлексия. Неговата задача е да коригира недостатъците на нативните класове и да уеднакви поведението в различните версии на PHP. -Монтаж: +Инсталация: ```shell composer require nette/utils ``` -Всички примери предполагат, че псевдонимът вече е създаден: +Всички примери предполагат създаден псевдоним: ```php use Nette\Utils\Reflection; @@ -21,13 +21,13 @@ use Nette\Utils\Reflection; areCommentsAvailable(): bool .[method] -------------------------------------- -Определяне дали отражението има достъп до коментарите на PHPdoc. Коментарите може да не са налични поради кеша на опкодовете, вижте например директивата [opcache.save-comments |https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments]. +Проверява дали рефлексията има достъп до PHPdoc коментари. Коментарите може да са недостъпни поради opcode кеш, вижте например директивата [opcache.save-comments|https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments]. expandClassName(string $name, ReflectionClass $context): string .[method] ------------------------------------------------------------------------- -Разширява името на класа `$name` до пълното му име в контекста на класа `$context`, т.е. в контекста на неговото пространство от имена и дефинираните псевдоними. Следователно това, което всъщност се казва тук, е начинът, по който PHP анализаторът `$name` би го разбрал, ако беше написано в тялото на класа `$context`. +Разширява името на класа `$name` до пълното му име в контекста на класа `$context`, т.е. в контекста на неговото пространство от имена и дефинирани псевдоними. Тоест, всъщност казва как PHP парсерът би разбрал `$name`, ако беше записан в тялото на класа `$context`. ```php namespace Foo; @@ -47,9 +47,9 @@ Reflection::expandClassName('Baz', $context); // 'Foo\Baz' getMethodDeclaringMethod(ReflectionMethod $method): ReflectionMethod .[method] ------------------------------------------------------------------------------ -Връща отражение на метод, съдържащо декларацията на метода `$method`. Обикновено всеки метод е със собствена декларация, но тялото на метода може да бъде в тава и под различно име. +Връща рефлексия на метода, който съдържа декларацията на метода `$method`. Обикновено всеки метод е своя собствена декларация, но тялото на метода може да се намира и в trait и под друго име. -Тъй като PHP не предоставя достатъчно информация, за да определи действителната декларация, Nette използва собствена евристика, която **трябва** да е надеждна. +Тъй като PHP не предоставя достатъчно информация, с която може да се установи истинската декларация, Nette използва собствена евристика, която **би трябвало да бъде** надеждна. ```php trait DemoTrait @@ -76,9 +76,9 @@ Reflection::getMethodDeclaringMethod($method); // ReflectionMethod('DemoTrait::f getPropertyDeclaringClass(ReflectionProperty $prop): ReflectionClass .[method] ------------------------------------------------------------------------------ -Връща отражение на клас или черта, което съдържа декларация за свойство `$prop`. Свойството може да бъде декларирано в черта. +Връща рефлексия на класа или trait-а, който съдържа декларацията на свойството `$prop`. Свойството може да бъде декларирано и в trait. -Тъй като PHP не предоставя достатъчно информация, за да определи действителната декларация, Nette използва собствени евристични методи, които не са **надеждни**. +Тъй като PHP не предоставя достатъчно информация, с която може да се установи истинската декларация, Nette използва собствена евристика, която **не е** надеждна. ```php trait DemoTrait @@ -96,25 +96,25 @@ $prop = new ReflectionProperty(DemoClass::class, 'foo'); Reflection::getPropertyDeclaringClass($prop); // ReflectionClass('DemoTrait') ``` -/--comment - - - - - - - +isBuiltinType(string $type): bool .[method deprecated] +------------------------------------------------------ +Проверява дали `$type` е вграден тип на PHP. В противен случай това е име на клас. +```php +Reflection::isBuiltinType('string'); // true +Reflection::isBuiltinType('Foo'); // false +``` -\-- +.[note] +Използвайте [Nette\Utils\Validator::isBuiltinType() |validators#isBuiltinType]. toString($reflection): string .[method] --------------------------------------- -Преобразува отражението в разбираем за човека низ. +Преобразува рефлексията в разбираем за човека низ. ```php $func = new ReflectionFunction('func'); diff --git a/utils/bg/smartobject.texy b/utils/bg/smartobject.texy index 471bf25ce9..bae8b32f3d 100644 --- a/utils/bg/smartobject.texy +++ b/utils/bg/smartobject.texy @@ -1,29 +1,74 @@ -SmartObject и StaticClass -************************* +SmartObject +*********** .[perex] -SmartObject добавя поддръжка на *свойства* към класовете на PHP. StaticClass се използва за обозначаване на статични класове. +SmartObject години наред подобряваше поведението на обектите в PHP. От версия PHP 8.4 всички негови функции вече са част от самия PHP, с което завърши своята историческа мисия да бъде пионер на модерния обектен подход в PHP. -Настройка: +Инсталация: ```shell composer require nette/utils ``` +SmartObject възниква през 2007 г. като революционно решение на недостатъците на тогавашния обектен модел на PHP. Във време, когато PHP страдаше от редица проблеми с обектния дизайн, той донесе значително подобрение и опростяване на работата за разработчиците. Стана легендарна част от Nette Framework. Предлагаше функционалност, която PHP придоби едва много години по-късно - от контрол на достъпа до свойствата на обектите до сложни синтактични захари. С появата на PHP 8.4 завърши своята историческа мисия, тъй като всички негови функции станаха нативна част от езика. Изпревари развитието на PHP със забележителните 17 години. -Свойства, getteri и setteri .[#toc-properties-getters-and-setters] -================================================================== +Технически SmartObject премина през интересно развитие. Първоначално беше имплементиран като клас `Nette\Object`, от който другите класове наследяваха необходимата функционалност. Основна промяна настъпи с PHP 5.4, което донесе поддръжка на трейтове (traits). Това позволи трансформацията във формата на трейт `Nette\SmartObject`, което донесе по-голяма гъвкавост - разработчиците можеха да използват функционалността и в класове, които вече наследяваха от друг клас. Докато оригиналният клас `Nette\Object` изчезна с появата на PHP 7.2 (което забрани именуването на класове с думата `Object`), трейтът `Nette\SmartObject` продължава да живее. -В съвременните обектно-ориентирани езици (напр. C#, Python, Ruby, JavaScript) терминът *собственост* се отнася до [специални членове на класа |https://en.wikipedia.org/wiki/Property_(programming)], които изглеждат като променливи, но всъщност са представени като методи. Когато се присвоява или чете стойността на такава "променлива", се извиква съответният метод (наречен getter или setter). Това е много удобно, тъй като ни дава пълен контрол върху достъпа до променливите. Можем да проверяваме входни данни или да генерираме резултати само когато свойството е прочетено. +Нека разгледаме свойствата, които някога `Nette\Object`, а по-късно `Nette\SmartObject` предлагаха. Всяка от тези функции по своето време представляваше значителна стъпка напред в областта на обектно-ориентираното програмиране в PHP. -PHP свойствата не се поддържат, но чертите `Nette\SmartObject` могат да ги симулират. Как да го използвате? -- Добавяне на анотация към класа под формата на `@property $xyz` -- Създайте getter с име `getXyz()` или `isXyz()`, setter с име `setXyz()` -- Getter и setter трябва да са *публични* или *защитени* и незадължителни, така че може да има свойство *само за четене* или *само за писане*. +Консистентни състояния на грешки +-------------------------------- +Един от най-наболелите проблеми на ранния PHP беше неконсистентното поведение при работа с обекти. `Nette\Object` внесе ред и предвидимост в този хаос. Нека видим как изглеждаше първоначалното поведение на PHP: -Ще използваме свойството за класа Circle, за да гарантираме, че в променливата `$radius` се поставят само неотрицателни числа. Заменете `public $radius` с имота: +```php +echo $obj->undeclared; // E_NOTICE, по-късно E_WARNING +$obj->undeclared = 1; // преминава тихо без съобщение +$obj->unknownMethod(); // Fatal error (неуловим с try/catch) +``` + +Фаталната грешка прекратяваше приложението без възможност за реакция. Тихото записване в несъществуващи членове без предупреждение можеше да доведе до сериозни грешки, които бяха трудни за откриване. `Nette\Object` улавяше всички тези случаи и хвърляше изключение `MemberAccessException`, което позволяваше на програмистите да реагират на грешките и да ги решават. + +```php +echo $obj->undeclared; // хвърля Nette\MemberAccessException +$obj->undeclared = 1; // хвърля Nette\MemberAccessException +$obj->unknownMethod(); // хвърля Nette\MemberAccessException +``` + +От PHP 7.0 езикът вече не причинява неуловими фатални грешки, а от PHP 8.2 достъпът до недекларирани членове се счита за грешка. + + +Подсказка "Did you mean?" +------------------------- +`Nette\Object` дойде с много приятна функция: интелигентна подсказка при печатни грешки. Когато разработчикът направеше грешка в името на метод или променлива, не само съобщаваше грешката, но и предлагаше помощна ръка под формата на предложение за правилното име. Това иконично съобщение, известно като "did you mean?", спести на програмистите часове търсене на печатни грешки: + +```php +class Foo extends Nette\Object +{ + public static function from($var) + { + } +} + +$foo = Foo::form($var); +// хвърля Nette\MemberAccessException +// "Call to undefined static method Foo::form(), did you mean from()?" +``` + +Днешният PHP няма никаква форма на „did you mean?“, но този допълнителен текст може да бъде добавен към грешките от [Tracy|tracy:]. И дори такива грешки могат да бъдат [самостоятелно коригирани |tracy:open-files-in-ide#Примери]. + + +Свойства с контролиран достъп +----------------------------- +Значителна иновация, която SmartObject внесе в PHP, бяха свойствата с контролиран достъп. Тази концепция, често срещана в езици като C# или Python, позволи на разработчиците елегантно да контролират достъпа до данните на обекта и да гарантират тяхната консистенция. Свойствата са мощен инструмент на обектно-ориентираното програмиране. Те функционират като променливи, но всъщност се представят от методи (getters и setters). Това позволява валидиране на входовете или генериране на стойности в момента на четене. + +За да използвате свойства, трябва: +- Да добавите анотация към класа във формата `@property $xyz` +- Да създадете getter с име `getXyz()` или `isXyz()`, setter с име `setXyz()` +- Да се уверите, че getter-ът и setter-ът са *public* или *protected*. Те са незадължителни - могат да съществуват като свойство *само за четене* (read-only) или *само за запис* (write-only) + +Нека покажем практически пример с клас Circle, където ще използваме свойствата, за да гарантираме, че радиусът винаги ще бъде неотрицателно число. Заменяме оригиналното `public $radius` със свойство: ```php /** @@ -34,22 +79,22 @@ class Circle { use Nette\SmartObject; - private float $radius = 0.0; // не е публичен + private float $radius = 0.0; // не е public! - // getter за свойството $radius + // getter за свойство $radius protected function getRadius(): float { return $this->radius; } - // setter за свойството $radius + // setter за свойство $radius protected function setRadius(float $radius): void { - // обработване на стойността, преди да я запишем + // санираме стойността преди запис $this->radius = max(0.0, $radius); } - // getter за свойството $visible + // getter за свойство $visible protected function isVisible(): bool { return $this->radius > 0; @@ -57,84 +102,30 @@ class Circle } $circle = new Circle; -$circle->radius = 10; // всъщност се извиква setRadius(10) -echo $circle->radius; // извиква getRadius() +$circle->radius = 10; // всъщност извиква setRadius(10) +echo $circle->radius; // извиква getRadius() echo $circle->visible; // извиква isVisible() ``` -Свойствата са преди всичко "синтактична захар" и са предназначени да направят живота на програмиста по-сладък, като опростят кода. Ако нямате нужда от тях, не е нужно да ги използвате. - - -Статични класове .[#toc-static-classes] -======================================= - -Статичните класове, т.е. класовете, които не са предназначени да бъдат инстанцирани, могат да бъдат маркирани с `Nette\StaticClass`: +От PHP 8.4 може да се постигне същата функционалност с помощта на property hooks, които предлагат много по-елегантен и кратък синтаксис: ```php -class Strings +class Circle { - use Nette\StaticClass; -} -``` - -Когато се опитате да създадете инстанция, се появява изключение `Error`, което показва, че класът е статичен. - - -Поглед към историята .[#toc-a-look-into-the-history] -==================================================== - -SmartObject е използван за подобряване и коригиране на поведението на класовете по много начини, но развитието на PHP направи повечето от първоначалните функции излишни. Затова по-долу ще разгледаме историята на развитието на тези функции. - -От самото начало обектният модел на PHP страдаше от някои сериозни недостатъци и неефективност. Това доведе до създаването на класа `Nette\Object` (през 2007 г.), който се опита да коригира тези проблеми и да подобри работата на потребителите на PHP. Това беше достатъчно, за да могат други класове да го наследят и да се възползват от предимствата му. Когато в PHP 5.4 беше въведена поддръжка на черти, класът `Nette\Object` беше заменен с `Nette\SmartObject`. По този начин вече не е било необходимо да се наследява от общ прародител. Освен това чертата може да се използва в класове, които вече са наследени от друг клас. Окончателният край на `Nette\Object` настъпи с излизането на PHP 7.2, който забрани именуването на класове `Object`. - -С развитието на PHP обектният модел и функциите на езика се усъвършенстваха. Някои характеристики на класа `SmartObject` станаха излишни. След излизането на PHP 8.2 единствената функция, която все още не се поддържа директно в PHP, е възможността за използване на т.нар. [свойства |#Properties, getters and setters]. - -Какви функции предлагат `Nette\Object` и `Nette\Object`? Ето един преглед. (В примерите е използван класът `Nette\Object`, но повечето от свойствата се отнасят и за `Nette\SmartObject` ). - - -Непоследователни грешки .[#toc-inconsistent-errors] ---------------------------------------------------- -PHP имаше непоследователно поведение при достъп до недекларирани членове. Към момента на публикуване на `Nette\Object` статусът е следният: - -```php -echo $obj->undeclared; // E_NOTICE, по-късно E_WARNING -$obj->undeclared = 1; // преминава безшумно, без да съобщава -$obj->unknownMethod(); // Фатална грешка (която не може да бъде уловена чрез try/catch) -``` - -Фатална грешка ще прекрати приложението без възможност за реакция. Тихото записване на несъществуващи членове без предупреждение можеше да доведе до сериозни грешки, които трудно се откриваха. `Nette\Object` Всички тези случаи бяха уловени и беше хвърлено изключение на адрес `MemberAccessException`. - -```php -echo $obj->undeclared; // throw Nette\MemberAccessException -$obj->undeclared = 1; // throw Nette\MemberAccessException -$obj->unknownMethod(); // throw Nette\MemberAccessException -``` -От PHP 7.0 PHP вече не причинява фатални грешки, които не могат да бъдат прихванати, а достъпът до необявени членове е грешка от PHP 8.2 насам. - - -Какво имаш предвид? .[#toc-did-you-mean] ----------------------------------------- -Ако се появи грешка в `Nette\MemberAccessException`, вероятно поради печатна грешка при достъп до променлива на обект или извикване на метод, `Nette\Object` се опитва да подскаже в съобщението за грешка как да се поправи грешката под формата на емблематичното допълнение "Искахте ли да кажете?". + public float $radius = 0.0 { + set => max(0.0, $value); + } -```php -class Foo extends Nette\Object -{ - public static function from($var) - { + public bool $visible { + get => $this->radius > 0; } } - -$foo = Foo::form($var); -// throw Nette\MemberAccessException -// "Call to undefined static method Foo::form(), did you mean from()?" ``` -Съвременният PHP може и да не разполага с формата "Искахте ли да кажете?", но [Трейси |tracy:] добавя тази добавка към грешките. Той дори може сам да [коригира |tracy:open-files-in-ide#toc-demos] такива грешки. - -Методи за разширение .[#toc-extension-methods] ----------------------------------------------- -Вдъхновено от методите за разширение от C#. Те ви позволяват да добавяте нови методи към съществуващи класове. Например можете да добавите метод `addDateTime()` към формуляр, за да добавите свой собствен DateTimePicker. +Extension methods +----------------- +`Nette\Object` внесе в PHP още една интересна концепция, вдъхновена от съвременните програмни езици - extension methods (разширителни методи). Тази функция, заета от C#, позволи на разработчиците елегантно да разширяват съществуващи класове с нови методи, без да е необходимо да ги променят или да наследяват от тях. Например, можехте да добавите към формуляр метод `addDateTime()`, който добавя собствен DateTimePicker: ```php Form::extensionMethod( @@ -146,22 +137,22 @@ $form = new Form; $form->addDateTime('date'); ``` -Методите за разширение се оказаха непрактични, тъй като имената им не се попълваха автоматично от редакторите, а вместо това те съобщаваха, че методът не съществува. Поради това тяхната подкрепа беше преустановена. +Разширителните методи се оказаха непрактични, тъй като техните имена не се подсказваха от редакторите, а напротив, съобщаваха, че методът не съществува. Затова поддръжката им беше прекратена. Днес е по-често срещано използването на композиция или наследяване за разширяване на функционалността на класовете. -Получаване на името на класа .[#toc-getting-the-class-name] ------------------------------------------------------------ +Установяване на името на класа +------------------------------ +За установяване на името на класа SmartObject предлагаше прост метод: ```php -$class = $obj->getClass(); // използване на Nette\Object -$class = $obj::class; // от PHP 8.0 +$class = $obj->getClass(); // с Nette\Object +$class = $obj::class; // от PHP 8.0 ``` -Достъп до рефлексии и анотации .[#toc-access-to-reflection-and-annotations] ---------------------------------------------------------------------------- - -`Nette\Object` Достъп до размишленията и анотациите чрез методите `getReflection()` и `getAnnotation()`: +Достъп до рефлексия и анотации +------------------------------ +`Nette\Object` предлагаше достъп до рефлексия и анотации чрез методите `getReflection()` и `getAnnotation()`. Този подход значително опрости работата с метаинформацията на класовете: ```php /** @@ -173,10 +164,10 @@ class Foo extends Nette\Object $obj = new Foo; $reflection = $obj->getReflection(); -$reflection->getAnnotation('author'); // връща 'John Doe +$reflection->getAnnotation('author'); // връща 'John Doe' ``` -От версия 8.0 на PHP вече е възможен достъп до метаинформация под формата на атрибути: +От PHP 8.0 е възможно да се достъпва метаинформация под формата на атрибути, които предлагат още по-големи възможности и по-добър контрол на типовете: ```php #[Author('John Doe')] @@ -190,10 +181,9 @@ $reflection->getAttributes(Author::class)[0]; ``` -Получатели на методи .[#toc-method-getters] -------------------------------------------- - -`Nette\Object` предлагат елегантен начин за работа с методите, сякаш са променливи: +Method getters +-------------- +`Nette\Object` предлагаше елегантен начин за предаване на методи, сякаш са променливи: ```php class Foo extends Nette\Object @@ -209,7 +199,7 @@ $method = $obj->adder; echo $method(2, 3); // 5 ``` -От версия 8.1 на PHP можете да използвате така наречения "синтаксис на извикване на метод от първи клас":https://www.php.net/manual/en/functions.first_class_callable_syntax: +От PHP 8.1 е възможно да се използва т.нар. "first-class callable syntax":https://www.php.net/manual/en/functions.first_class_callable_syntax, който издига тази концепция още по-далеч: ```php $obj = new Foo; @@ -218,10 +208,9 @@ echo $method(2, 3); // 5 ``` -Събития .[#toc-events] ----------------------- - -`Nette\Object` предлага синтактична захар за задействане на [събитие |nette:glossary#events]: +Събития +------- +SmartObject предлага опростен синтаксис за работа със [събития |nette:glossary#Събития events]. Събитията позволяват на обектите да информират други части на приложението за промени в своето състояние: ```php class Circle extends Nette\Object @@ -231,12 +220,12 @@ class Circle extends Nette\Object public function setRadius(float $radius): void { $this->onChange($this, $radius); - $this->radius = $radius + $this->radius = $radius; } } ``` -Кодът `$this->onChange($this, $radius)` е еквивалентен на следното: +Кодът `$this->onChange($this, $radius)` е еквивалентен на следния цикъл: ```php foreach ($this->onChange as $callback) { @@ -244,7 +233,7 @@ foreach ($this->onChange as $callback) { } ``` -За по-голяма яснота препоръчваме да избягвате магическия метод `$this->onChange()`. Добър заместител би бил [Nette\Utils\Arrays::invoke: |arrays#invoke] +Поради разбираемостта препоръчваме да се избягва магическият метод `$this->onChange()`. Практичен заместител е например функцията [Nette\Utils\Arrays::invoke |arrays#invoke]: ```php Nette\Utils\Arrays::invoke($this->onChange, $this, $radius); diff --git a/utils/bg/staticclass.texy b/utils/bg/staticclass.texy new file mode 100644 index 0000000000..a663a54fe6 --- /dev/null +++ b/utils/bg/staticclass.texy @@ -0,0 +1,21 @@ +Статични класове +**************** + +.[perex] +StaticClass служи за обозначаване на статични класове. + + +Инсталация: + +```shell +composer require nette/utils +``` + +Статичните класове, т.е. класовете, които не са предназначени за създаване на инстанции, можете да обозначите с трейта (trait) [api:Nette\StaticClass]: + +```php +class Strings +{ + use Nette\StaticClass; +} +``` diff --git a/utils/bg/strings.texy b/utils/bg/strings.texy index dde4e47a1e..a728d5aea1 100644 --- a/utils/bg/strings.texy +++ b/utils/bg/strings.texy @@ -2,92 +2,92 @@ *************** .[perex] -[api:Nette\Utils\Strings] Статичен клас, съдържащ полезни функции за работа с низове, предимно кодирани в UTF-8. +[api:Nette\Utils\Strings] е статичен клас с полезни функции за работа с низове, предимно в кодировка UTF-8. -Монтаж: +Инсталация: ```shell composer require nette/utils ``` -Всички примери предполагат, че псевдонимът вече е създаден: +Всички примери предполагат създаден псевдоним: ```php use Nette\Utils\Strings; ``` -Чувствителност на буквите .[#toc-letter-case] -============================================= +Промяна на регистъра на буквите +=============================== -Тези функции изискват разширението на PHP `mbstring`. +Тези функции изискват PHP разширението `mbstring`. lower(string $s): string .[method] ---------------------------------- -Преобразува низ от UTF-8 в малки букви. +Преобразува UTF-8 низ в малки букви. ```php -Strings::lower('Dobrý den'); // 'dobrý den' +Strings::lower('Добър ден'); // 'добър ден' ``` upper(string $s): string .[method] ---------------------------------- -Конвертира низ от UTF-8 в главни букви. +Преобразува UTF-8 низ в главни букви. ```php -Strings::upper('Dobrý den'); // 'DOBRÝ DEN' +Strings::upper('Добър ден'); // 'ДОБЪР ДЕН' ``` firstUpper(string $s): string .[method] --------------------------------------- -Преобразува първата буква от низ в UTF-8 в главна буква, а останалите букви остават непроменени. +Преобразува първата буква на UTF-8 низ в главна, останалите не променя. ```php -Strings::firstUpper('dobrý den'); // 'Dobrý den' +Strings::firstUpper('добър ден'); // 'Добър ден' ``` firstLower(string $s): string .[method] --------------------------------------- -Преобразува първата буква от низ UTF-8 в малка, без да променя останалите. +Преобразува първата буква на UTF-8 низ в малка, останалите не променя. ```php -Strings::firstLower('Dobrý den'); // 'dobrý den' +Strings::firstLower('Добър ден'); // 'добър ден' ``` capitalize(string $s): string .[method] --------------------------------------- -Преобразува първата буква на всяка дума в низ UTF-8 в главни букви, а останалите - в малки. +Преобразува първата буква на всяка дума в UTF-8 низ в главна, останалите в малки. ```php -Strings::capitalize('Dobrý den'); // 'Dobrý Den' +Strings::capitalize('Добър ден'); // 'Добър Ден' ``` -Редактиране на низ .[#toc-editing-a-string] -=========================================== +Редактиране на низ +================== normalize(string $s): string .[method] -------------------------------------- -Премахва контролните символи, нормализира прекъсванията на редовете до `\n`, подрязва водещите и завършващите празни редове, подрязва прекъсванията на редовете отдясно, нормализира UTF-8 до нормалната форма на NFC. +Премахва контролните знаци, нормализира краищата на редовете до `\n`, изрязва водещите и крайните празни редове, изрязва десните интервали на редовете, нормализира UTF-8 до нормална форма NFC. unixNewLines(string $s): string .[method] ----------------------------------------- -Преобразува прекъсванията на редовете в `\n`, както се използва в системите на Unix. Прекъсвания на редовете: `\n`, `\r`, `\r\n`, U+2028 разделител на редове, U+2029 разделител на абзаци. +Преобразува краищата на редовете в `\n`, използвани в unix системи. Краищата на редовете са: `\n`, `\r`, `\r\n`, U+2028 line separator, U+2029 paragraph separator. ```php $unixLikeLines = Strings::unixNewLines($string); @@ -97,42 +97,42 @@ $unixLikeLines = Strings::unixNewLines($string); platformNewLines(string $s): string .[method] --------------------------------------------- -Преобразува подаването на реда в символи, характерни за текущата платформа, т.е. `\r\n` в Windows и `\n` другаде. Прекъсвания на редовете: `\n`, `\r`, `\r\n`, U+2028 разделител на редове, U+2029 разделител на абзаци. +Преобразува краищата на редовете в знаци, специфични за текущата платформа, т.е. `\r\n` на Windows и `\n` другаде. Краищата на редовете са: `\n`, `\r`, `\r\n`, U+2028 line separator, U+2029 paragraph separator. ```php $platformLines = Strings::platformNewLines($string); ``` -webalize(string $s, string $charlist=null, bool $lower=true): string .[method] ------------------------------------------------------------------------------- +webalize(string $s, ?string $charlist=null, bool $lower=true): string .[method] +------------------------------------------------------------------------------- -Променя низът UTF-8 до формата, използван в URL адресите, т.е. премахва диакритичните знаци и заменя всички символи с изключение на английските букви и цифри с дефис. +Редактира UTF-8 низ във формата, използван в URL, т.е. премахва диакритиката и всички знаци, освен буквите от английската азбука и цифрите, заменя с тире. ```php -Strings::webalize('náš produkt'); // 'nas-produkt' +Strings::webalize('нашият продукт'); // 'nashiyat-produkt' ``` -Ако трябва да се запазят и други символи, те могат да бъдат посочени във втория параметър на функцията. +Ако трябва да се запазят и други знаци, те могат да бъдат посочени във втория параметър на функцията. ```php -Strings::webalize('10. obrázek_id', '._'); // '10.-obrazek_id' +Strings::webalize('10. изображение_id', '._'); // '10.-izobrazhenie_id' ``` -Третият параметър може да се използва, за да се потисне преобразуването в малки букви. +С третия параметър може да се потисне преобразуването в малки букви. ```php -Strings::webalize('Dobrý den', null, false); // 'Dobry-den' +Strings::webalize('Добър ден', null, false); // 'Dobyr-den' ``` .[caution] -Изисква разширението на PHP `intl`. +Изисква PHP разширението `intl`. -trim(string $s, string $charlist=null): string .[method] --------------------------------------------------------- +trim(string $s, ?string $charlist=null): string .[method] +--------------------------------------------------------- -Орязва белите символи (или други символи, посочени от втория параметър) от началото и края на низ в UTF-8. +Изрязва интервалите (или други знаци, определени от втория параметър) от началото и края на UTF-8 низ. ```php Strings::trim(' Hello '); // 'Hello' @@ -142,21 +142,21 @@ Strings::trim(' Hello '); // 'Hello' truncate(string $s, int $maxLen, string $append=`'…'`): string .[method] ------------------------------------------------------------------------ -Съкращава низ UTF-8 до зададената максимална дължина, като се опитва да запази цели думи. Ако низът е съкратен, в края му се добавя тройка (може да се промени с третия параметър). +Изрязва UTF-8 низ до посочената максимална дължина, като се опитва да запази цели думи. Ако низът бъде скъсен, в края се добавя многоточие (може да се промени с третия параметър). ```php -$text = 'Řekněte, jak se máte?'; -Strings::truncate($text, 5); // 'Řekn…' -Strings::truncate($text, 20); // 'Řekněte, jak se…' -Strings::truncate($text, 30); // 'Řekněte, jak se máte?' -Strings::truncate($text, 20, '~'); // 'Řekněte, jak se~' +$text = 'Кажете как сте?'; +Strings::truncate($text, 5); // 'Каже…' +Strings::truncate($text, 15); // 'Кажете как…' +Strings::truncate($text, 30); // 'Кажете как сте?' +Strings::truncate($text, 15, '~'); // 'Кажете как~' ``` indent(string $s, int $level=1, string $indentationChar=`"\t"`): string .[method] --------------------------------------------------------------------------------- -Отстъпване на многоредовия текст наляво. Броят на отстъпите се определя от втория параметър, който се използва за отстъпите на третия параметър (стойността по подразбиране е tab). +Отстъпва многоредов текст отляво. Броят на отстъпите се определя от втория параметър, с какво да се отстъпва - от третия параметър (стойността по подразбиране е табулатор). ```php Strings::indent('Nette'); // "\tNette" @@ -167,7 +167,7 @@ Strings::indent('Nette', 2, '+'); // '++Nette' padLeft(string $s, int $length, string $pad=`' '`): string .[method] -------------------------------------------------------------------- -Завършва низ UTF-8 до зададената дължина, като повтаря низа `$pad` отляво. +Допълва UTF-8 низ до зададената дължина чрез повтаряне на низа `$pad` отляво. ```php Strings::padLeft('Nette', 6); // ' Nette' @@ -178,7 +178,7 @@ Strings::padLeft('Nette', 8, '+*'); // '+*+Nette' padRight(string $s, int $length, string $pad=`' '`): string .[method] --------------------------------------------------------------------- -Завършва низ UTF-8 до зададената дължина, като повтаря низа `$pad` отдясно. +Допълва UTF-8 низ до зададената дължина чрез повтаряне на низа `$pad` отдясно. ```php Strings::padRight('Nette', 6); // 'Nette ' @@ -186,10 +186,10 @@ Strings::padRight('Nette', 8, '+*'); // 'Nette+*+' ``` -substring(string $s, int $start, int $length=null): string .[method] --------------------------------------------------------------------- +substring(string $s, int $start, ?int $length=null): string .[method] +--------------------------------------------------------------------- -Връща UTF-8 частта на низа `$s`, зададена от началната позиция `$start` и дължината `$length`. Ако `$start` е отрицателна стойност, върнатият низ ще започне със символа -`$start` символа от края. +Връща част от UTF-8 низ `$s`, зададена от началната позиция `$start` и дължината `$length`. Ако `$start` е отрицателен, върнатият низ ще започва със знака -`$start` от края. ```php Strings::substring('Nette Framework', 0, 5); // 'Nette' @@ -201,7 +201,7 @@ Strings::substring('Nette Framework', -4); // 'work' reverse(string $s): string .[method] ------------------------------------ -Модифицира низ UTF-8. +Обръща UTF-8 низ. ```php Strings::reverse('Nette'); // 'etteN' @@ -211,77 +211,77 @@ Strings::reverse('Nette'); // 'etteN' length(string $s): int .[method] -------------------------------- -Връща броя на символите (не байтовете) в низ UTF-8. +Връща броя на знаците (не байтовете) в UTF-8 низ. -Това е броят на кодовите точки на Unicode, който може да се различава от броя на графемите. +Това е броят на Unicode кодовите точки, които могат да се различават от броя на графемите. ```php Strings::length('Nette'); // 5 -Strings::length('červená'); // 7 +Strings::length('червена'); // 7 ``` -/--comment - - - - - - - - - - - - - - - - - - - - - - - - - - - - +startsWith(string $haystack, string $needle): bool .[method deprecated] +----------------------------------------------------------------------- +Проверява дали низът `$haystack` започва с низ `$needle`. +```php +$haystack = 'Започва'; +$needle = 'За'; +Strings::startsWith($haystack, $needle); // true +``` +.[note] +Използвайте нативната `str_starts_with()`:https://www.php.net/manual/en/function.str-starts-with.php. +endsWith(string $haystack, string $needle): bool .[method deprecated] +--------------------------------------------------------------------- +Проверява дали низът `$haystack` завършва с низ `$needle`. +```php +$haystack = 'Завършва'; +$needle = 'шва'; +Strings::endsWith($haystack, $needle); // true +``` +.[note] +Използвайте нативната `str_ends_with()`:https://www.php.net/manual/en/function.str-ends-with.php. +contains(string $haystack, string $needle): bool .[method deprecated] +--------------------------------------------------------------------- +Проверява дали низът `$haystack` съдържа `$needle`. +```php +$haystack = 'Аудитория'; +$needle = 'дитор'; +Strings::contains($haystack, $needle); // true +``` -\-- +.[note] +Използвайте нативната `str_contains()`:https://www.php.net/manual/en/function.str-contains.php. -compare(string $left, string $right, int $length=null): bool .[method] ----------------------------------------------------------------------- +compare(string $left, string $right, ?int $length=null): bool .[method] +----------------------------------------------------------------------- -Сравняване на два низа в UTF-8 или части от низове, които не се различават по размер. Ако `$length` съдържа null, се сравняват цели низове, ако е отрицателен, се сравняват съответния брой символи от края на низовете, в противен случай се сравняват съответния брой символи от началото. +Сравнение на два UTF-8 низа или техни части без оглед на регистъра на буквите. Ако `$length` съдържа null, се сравняват целите низове, ако е отрицателен, се сравнява съответният брой знаци от края на низовете, в противен случай се сравнява съответният брой знаци от началото. ```php Strings::compare('Nette', 'nette'); // true -Strings::compare('Nette', 'next', 2); // true - shoda prvních 2 znaků -Strings::compare('Nette', 'Latte', -2); // true - shoda posledních 2 znaků +Strings::compare('Nette', 'next', 2); // true - съвпадение на първите 2 знака +Strings::compare('Nette', 'Latte', -2); // true - съвпадение на последните 2 знака ``` findPrefix(...$strings): string .[method] ----------------------------------------- -Намира общото начало на низовете. Или връща празен низ, ако не е намерен общ префикс. +Намира общото начало на низовете. Или връща празен низ, ако общ префикс не е намерен. ```php Strings::findPrefix('prefix-a', 'prefix-bb', 'prefix-c'); // 'prefix-' @@ -293,7 +293,7 @@ Strings::findPrefix('Nette', 'is', 'great'); // '' before(string $haystack, string $needle, int $nth=1): ?string .[method] ----------------------------------------------------------------------- -Връща част от низа `$haystack` преди n-тата `$nth` поява на низа `$needle`. Или `null`, ако `$needle` не е намерен. Ако `$nth` е отрицателен, се търси от края на низа. +Връща част от низа `$haystack` преди n-тото `$nth` срещане на низа `$needle`. Или `null`, ако `$needle` не е намерен. При отрицателна стойност на `$nth` се търси от края на низа. ```php Strings::before('Nette_is_great', '_', 1); // 'Nette' @@ -306,7 +306,7 @@ Strings::before('Nette_is_great', '_', 3); // null after(string $haystack, string $needle, int $nth=1): ?string .[method] ---------------------------------------------------------------------- -Връща част от низ `$haystack` след n-тото `$nth` появяване на низ `$needle`. Или `null`, ако `$needle` не е намерен. Ако `$nth` е отрицателен, се търси от края на низа. +Връща част от низа `$haystack` след n-тото `$nth` срещане на низа `$needle`. Или `null`, ако `$needle` не е намерен. При отрицателна стойност на `$nth` се търси от края на низа. ```php Strings::after('Nette_is_great', '_', 2); // 'great' @@ -319,7 +319,7 @@ Strings::after('Nette_is_great', '_', 3); // null indexOf(string $haystack, string $needle, int $nth=1): ?int .[method] --------------------------------------------------------------------- -Връща позицията в символи на n-тата `$nth` поява на низа `$needle` към низа `$haystack`. Или `null`, ако `$needle` не е намерен. Ако `$nth` е отрицателен, се търси от края на низа. +Връща позицията в знаци на n-тото `$nth` срещане на низа `$needle` в низа `$haystack`. Или `null`, ако `$needle` не е намерен. При отрицателна стойност на `$nth` се търси от края на низа. ```php Strings::indexOf('abc abc abc', 'abc', 2); // 4 @@ -328,76 +328,76 @@ Strings::indexOf('abc abc abc', 'd'); // null ``` -Кодиране .[#toc-kodovani] -========================= +Кодиране +======== fixEncoding(string $s): string .[method] ---------------------------------------- -Премахва невалидните UTF-8 символи от символния низ. +Премахва от низа невалидни UTF-8 знаци. ```php $correctStrings = Strings::fixEncoding($string); ``` -/--comment - - - - - - +checkEncoding(string $s): bool .[method deprecated] +--------------------------------------------------- +Проверява дали това е валиден UTF-8 низ. +```php +$isUtf8 = Strings::checkEncoding($string); +``` -\-- +.[note] +Използвайте [Nette\Utils\Validator::isUnicode() |validators#isUnicode]. toAscii(string $s): string .[method] ------------------------------------ -Конвертира низ от UTF-8 в ASCII, т.е. премахва диакритичните знаци и т.н. +Преобразува UTF-8 низ в ASCII, т.е. премахва диакритиката и т.н. ```php -Strings::toAscii('žluťoučký kůň'); // 'zlutoucky kun' +Strings::toAscii('жълт кон'); // 'zhalt kon' ``` .[caution] -Изисква разширението на PHP `intl`. +Изисква PHP разширението `intl`. chr(int $code): string .[method] -------------------------------- -Връща конкретен UTF-8 символ от кодовата точка (число в диапазона 0x0000...D7FF и 0xE000...10FFFFFF). +Връща специфичен знак в UTF-8 от кодова точка (число в диапазона 0x0000..D7FF и 0xE000..10FFFF). ```php -Strings::chr(0xA9); // '©' v kódování UTF-8 +Strings::chr(0xA9); // '©' в кодировка UTF-8 ``` ord(string $char): int .[method] -------------------------------- -Връща кодовата точка UTF-8 на определен символ (число в диапазона 0x0000...D7FF или 0xE000...10FFFFFF). +Връща кодовата точка на конкретен знак в UTF-8 (число в диапазона 0x0000..D7FF или 0xE000..10FFFF). ```php Strings::ord('©'); // 0xA9 ``` -Редовни изрази .[#toc-fixencoding] -================================== +Регулярни изрази +================ -Класът Strings предоставя функции за работа с регулярни изрази. За разлика от собствените функции на PHP те имат по-ясен API, по-добра поддръжка на Unicode и, което е най-важно, откриване на грешки. Всички грешки на компилатора или при обработката на изрази ще предизвикат изключение `Nette\RegexpException`. +Класът Strings предлага функции за работа с регулярни изрази. За разлика от нативните PHP функции, те разполагат с по-разбираемо API, по-добра поддръжка на Unicode и преди всичко откриване на грешки. Всяка грешка при компилация или обработка на израза хвърля изключение `Nette\RegexpException`. split(string $subject, string $pattern, bool $captureOffset=false, bool $skipEmpty=false, int $limit=-1, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------- -Разделя низ в масив в съответствие с регулярен израз. Изразите в скоби също ще бъдат прихванати и върнати. +Разделя низ в масив според регулярен израз. Изразите в скоби ще бъдат уловени и върнати също. ```php Strings::split('hello, world', '~,\s*~'); @@ -407,7 +407,7 @@ Strings::split('hello, world', '~(,)\s*~'); // ['hello', ',', 'world']`` ``` -Ако `$skipEmpty` е `true`, ще бъдат върнати само непразни записи: +Ако `$skipEmpty` е `true`, ще бъдат върнати само непразните елементи: ```php Strings::split('hello, world, ', '~,\s*~'); @@ -417,30 +417,30 @@ Strings::split('hello, world, ', '~,\s*~', skipEmpty: true); // ['hello', 'world'] ``` -Ако `$limit`, ще бъдат върнати само поднизове до границата, а останалата част от низа ще бъде поставена в последния елемент. Ограничение от -1 или 0 означава, че няма ограничение. +Ако е зададено `$limit`, ще бъдат върнати само поднизове до лимита, а останалата част от низа ще бъде поставена в последния елемент. Лимит -1 или 0 означава без ограничение. ```php Strings::split('hello, world, third', '~,\s*~', limit: 2); // ['hello', 'world, third'] ``` -Ако `$utf8` е равно на `true`, оценката ще премине в режим Unicode. Това е същото като посочването на модификатора `u`. +Ако `$utf8` е `true`, оценяването се превключва в Unicode режим. Подобно на това, когато посочите модификатор `u`. -Ако `$captureOffset` е `true`, тогава за всяка поява на съвпадение ще бъде върната позицията му в низа (в байтове; в символи, ако е зададено `$utf8` ). По този начин връщаната стойност се превръща в масив, всеки елемент от който представлява двойка, състояща се от съответстващ низ и неговата позиция. +Ако `$captureOffset` е `true`, за всяко срещано съвпадение ще бъде върната и неговата позиция в низа (в байтове; ако е зададено `$utf8`, тогава в знаци). Това променя върнатата стойност на масив, където всеки елемент е двойка, съставена от съвпадащия низ и неговата позиция. ```php -Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true); -// [['žlutý', 0], ['kůň', 9]] +Strings::split('жълт, кон', '~,\s*~', captureOffset: true); +// [['жълт', 0], ['кон', 7]] -Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true, utf8: true); -// [['žlutý', 0], ['kůň', 7]] +Strings::split('жълт, кон', '~,\s*~', captureOffset: true, utf8: true); +// [['жълт', 0], ['кон', 6]] ``` match(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $utf8=false): ?array .[method] -------------------------------------------------------------------------------------------------------------------------------------------------- -Търси частта в низа, която съответства на регулярен израз, и връща масив с намерения израз и всеки подизраз, или `null`. +Търси в низ част, съответстваща на регулярен израз, и връща масив с намерения израз и отделните подизрази, или `null`. ```php Strings::match('hello!', '~\w+(!+)~'); @@ -450,7 +450,7 @@ Strings::match('hello!', '~X~'); // null ``` -Ако `$unmatchedAsNull` е равно на `true`, тогава незаловените подизрази се връщат като нула; в противен случай се връщат като празен низ или не се връщат: +Ако `$unmatchedAsNull` е `true`, неуловените подшаблони се връщат като null; в противен случай се връщат като празен низ или не се връщат: ```php Strings::match('hello', '~\w+(!+)?~'); @@ -460,33 +460,33 @@ Strings::match('hello', '~\w+(!+)?~', unmatchedAsNull: true); // ['hello', null] ``` -Ако `$utf8` е равно на `true`, оценката се превключва в режим Unicode. Подобно на посочването на модификатора `u`: +Ако `$utf8` е `true`, оценяването се превключва в Unicode режим. Подобно на това, когато посочите модификатор `u`: ```php -Strings::match('žlutý kůň', '~\w+~'); -// ['lut'] +Strings::match('жълт кон', '~\w+~'); +// ['лт'] -Strings::match('žlutý kůň', '~\w+~', utf8: true); -// ['žlutý'] +Strings::match('жълт кон', '~\w+~', utf8: true); +// ['жълт'] ``` -Параметърът `$offset` може да се използва за определяне на позицията, от която трябва да започне търсенето (в байтове; в символи, ако е зададено `$utf8` ). +Параметърът `$offset` може да се използва за определяне на позицията, от която да започне търсенето (в байтове; ако е зададено `$utf8`, тогава в знаци). -Ако `$captureOffset` е `true`, то за всяко срещнато съвпадение ще бъде върната и неговата позиция в низа (в байтове; ако е зададено `$utf8`, в символи). Това превръща върнатата стойност в масив, всеки елемент от който представлява двойка, състояща се от съответстващ низ и неговото отместване: +Ако `$captureOffset` е `true`, за всяко срещано съвпадение ще бъде върната и неговата позиция в низа (в байтове; ако е зададено `$utf8`, тогава в знаци). Това променя върнатата стойност на масив, където всеки елемент е двойка, съставена от съвпадащия низ и неговото отместване: ```php -Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true); -// [['lut', 2]] +Strings::match('жълт!', '~\w+(!+)?~', captureOffset: true); +// [['лт', 2]] -Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true, utf8: true); -// [['žlutý!', 0], ['!', 5]] +Strings::match('жълт!', '~\w+(!+)?~', captureOffset: true, utf8: true); +// [['жълт!', 0], ['!', 5]] ``` -matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false, bool $lazy=false): array|Generator .[method] +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Претърсва низ за всички срещания, съответстващи на регулярен израз, и връща масив, съдържащ намерения израз и всеки подизраз. +Търси в низ всички срещания, съответстващи на регулярен израз, и връща масив от масиви с намерения израз и отделните подизрази. ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~'); @@ -496,7 +496,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~'); ] */ ``` -Ако `$patternOrder` е `true`, структурата на резултатите се променя така, че първият запис е масив от пълни съвпадения на шаблона, вторият е масив от низове, съответстващи на първия подизраз в скоби, и т.н: +Ако `$patternOrder` е `true`, структурата на резултатите се променя така, че в първия елемент е масив от пълни съвпадения на шаблона, във втория е масив от низове, на които съответства първият подшаблон в скоби, и така нататък: ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~', patternOrder: true); @@ -506,7 +506,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~', patternOrder: true); ] */ ``` -Ако `$unmatchedAsNull` е `true`, тогава несъответстващите подшаблони се връщат като нула; в противен случай те се връщат като празни низове или не се връщат: +Ако `$unmatchedAsNull` е `true`, неуловените подшаблони се връщат като null; в противен случай се връщат като празен низ или не се връщат: ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~', unmatchedAsNull: true); @@ -516,45 +516,55 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~', unmatchedAsNull: true); ] */ ``` -Ако `$utf8` е равно на `true`, оценката се превключва в режим Unicode. Подобно на посочването на модификатора `u`: +Ако `$utf8` е `true`, оценяването се превключва в Unicode режим. Подобно на това, когато посочите модификатор `u`: ```php -Strings::matchAll('žlutý kůň', '~\w+~'); +Strings::matchAll('жълт кон', '~\w+~'); /* [ - 0 => ['lut'], - 1 => ['k'], + 0 => ['лт'], + 1 => ['к'], ] */ -Strings::matchAll('žlutý kůň', '~\w+~', utf8: true); +Strings::matchAll('жълт кон', '~\w+~', utf8: true); /* [ - 0 => ['žlutý'], - 1 => ['kůň'], + 0 => ['жълт'], + 1 => ['кон'], ] */ ``` -Параметърът `$offset` може да се използва за определяне на позицията, от която трябва да започне търсенето (в байтове; в символи, ако е зададено `$utf8` ). +Параметърът `$offset` може да се използва за определяне на позицията, от която да започне търсенето (в байтове; ако е зададено `$utf8`, тогава в знаци). -Ако `$captureOffset` е `true`, то за всяко срещнато съвпадение ще бъде върната и неговата позиция в низа (в байтове; ако е зададено `$utf8`, в символи). По този начин връщаната стойност се превръща в масив, всеки елемент от който представлява двойка, състояща се от съответстващ низ и неговата позиция: +Ако `$captureOffset` е `true`, за всяко срещано съвпадение ще бъде върната и неговата позиция в низа (в байтове; ако е зададено `$utf8`, тогава в знаци). Това променя върнатата стойност на масив, където всеки елемент е двойка, съставена от съвпадащия низ и неговата позиция: ```php -Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true); +Strings::matchAll('жълт кон', '~\w+~', captureOffset: true); /* [ - 0 => [['lut', 2]], - 1 => [['k', 8]], + 0 => [['лт', 2]], + 1 => [['к', 6]], ] */ -Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true, utf8: true); +Strings::matchAll('жълт кон', '~\w+~', captureOffset: true, utf8: true); /* [ - 0 => [['žlutý', 0]], - 1 => [['kůň', 6]], + 0 => [['жълт', 0]], + 1 => [['кон', 5]], ] */ ``` +Ако `$lazy` е `true`, функцията връща `Generator` вместо масив, което носи значителни предимства в производителността при работа с големи низове. Генераторът позволява търсене на съвпадения постепенно, вместо на целия низ наведнъж. Това позволява ефективна работа дори с изключително големи входни текстове. Освен това можете по всяко време да прекъснете обработката, ако намерите търсеното съвпадение, което спестява изчислително време. + +```php +$matches = Strings::matchAll($largeText, '~\w+~', lazy: true); +foreach ($matches as $match) { + echo "Намерено: $match[0]\n"; + // Обработката може да бъде прекъсната по всяко време +} +``` + replace(string $subject, string|array $pattern, string|callable $replacement='', int $limit=-1, bool $captureOffset=false, bool $unmatchedAsNull=false, bool $utf8=false): string .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Заменя всички срещания, отговарящи на регулярен израз. `$replacement` - е или маска на заместващ низ, или обратна връзка. +Заменя всички срещания, съответстващи на регулярен израз. `$replacement` е или маска на заместващия низ, или callback. ```php Strings::replace('hello, world!', '~\w+~', '--'); @@ -564,7 +574,7 @@ Strings::replace('hello, world!', '~\w+~', fn($m) => strrev($m[0])); // 'olleh, dlrow!' ``` -Функцията позволява също така многократни замествания, като във втория параметър се подава масив с формата `pattern => replacement`: +Функцията също така позволява извършването на множество замени, като във втория параметър предадем масив във формата `pattern => replacement`: ```php Strings::replace('hello, world!', [ @@ -574,40 +584,40 @@ Strings::replace('hello, world!', [ // '-- --!' ``` -Параметърът `$limit` ограничава броя на заместванията, които могат да бъдат направени. Ограничение от -1 означава, че няма ограничение. +Параметърът `$limit` ограничава броя на извършените замени. Лимит -1 означава без ограничение. -Ако `$utf8` е равно на `true`, оценката се превключва в режим Unicode. Това е същото като посочването на модификатора `u`. +Ако `$utf8` е `true`, оценяването се превключва в Unicode режим. Подобно на това, когато посочите модификатор `u`. ```php -Strings::replace('žlutý kůň', '~\w+~', '--'); -// 'ž--ý --ůň' +Strings::replace('жълт кон', '~\w+~', '--'); +// 'ж--т --он' -Strings::replace('žlutý kůň', '~\w+~', '--', utf8: true); +Strings::replace('жълт кон', '~\w+~', '--', utf8: true); // '-- --' ``` -Ако `$captureOffset` е `true`, то за всяко срещнато съвпадение позицията му в низ (в байтове; в символи, ако е зададено `$utf8` ) също ще бъде предадена на обратната връзка. Това променя формата на предадения масив, в който всеки елемент е двойка, състояща се от съответстващия низ и неговата позиция. +Ако `$captureOffset` е `true`, за всяко срещано съвпадение ще бъде предадена на callback-а и неговата позиция в низа (в байтове; ако е зададено `$utf8`, тогава в знаци). Това променя формата на предавания масив, където всеки елемент е двойка, съставена от съвпадащия низ и неговата позиция. ```php Strings::replace( - 'žlutý kůň', + 'жълт кон', '~\w+~', function (array $m) { dump($m); return ''; }, captureOffset: true, ); -// dumps [['lut', 2]] a [['k', 8]] +// dumps [['лт', 2]] и [['к', 6]] Strings::replace( - 'žlutý kůň', + 'жълт кон', '~\w+~', function (array $m) { dump($m); return ''; }, captureOffset: true, utf8: true, ); -// dumps [['žlutý', 0]] a [['kůň', 6]] +// dumps [['жълт', 0]] и [['кон', 5]] ``` -Ако `$unmatchedAsNull` е `true`, тогава несъответстващите подшаблони се предават на обратната връзка като null; в противен случай те се предават като празен низ или не се предават: +Ако `$unmatchedAsNull` е `true`, неуловените подшаблони се предават на callback-а като null; в противен случай се предават като празен низ или не се предават: ```php Strings::replace( diff --git a/utils/bg/type.texy b/utils/bg/type.texy index e4317e3eb9..1ab556dd36 100644 --- a/utils/bg/type.texy +++ b/utils/bg/type.texy @@ -1,17 +1,17 @@ -Типове PHP -********** +PHP Тип +******* .[perex] -[api:Nette\Utils\Type] е клас за работа с типове данни в PHP. +[api:Nette\Utils\Type] е клас за работа с типове данни на PHP. -Монтаж: +Инсталация: ```shell composer require nette/utils ``` -Всички примери предполагат, че псевдонимът вече е създаден: +Всички примери предполагат създаден псевдоним: ```php use Nette\Utils\Type; @@ -21,7 +21,7 @@ use Nette\Utils\Type; fromReflection($reflection): ?Type .[method] -------------------------------------------- -Статичният метод създава обект Type въз основа на отражение. Параметърът може да бъде обект `ReflectionMethod` или `ReflectionFunction` (той връща типа на връщаната стойност) или `ReflectionParameter` или `ReflectionProperty`. Той превежда `self`, `static` и `parent` към действителното име на класа. Ако обектът няма тип, се връща `null`. +Статичният метод създава обект Type въз основа на рефлексия. Параметърът може да бъде обект `ReflectionMethod` или `ReflectionFunction` (връща типа на върнатата стойност) или `ReflectionParameter` или `ReflectionProperty`. Превежда `self`, `static` и `parent` на действителното име на класа. Ако субектът няма тип, връща `null`. ```php class DemoClass @@ -37,7 +37,7 @@ echo Type::fromReflection($prop); // 'DemoClass' fromString(string $type): Type .[method] ---------------------------------------- -Статичният метод създава обект Type в съответствие с текстовата нотация. +Статичният метод създава обект Type според текстовия запис. ```php $type = Type::fromString('Foo|Bar'); @@ -48,10 +48,10 @@ echo $type; // 'Foo|Bar' getNames(): (string|array)[] .[method] -------------------------------------- -Връща масив от подтипове, съставляващи съставния тип, като низове. +Връща масив от подтипове, от които се състои съставният тип, като низове. ```php -$type = Type::fromString('string|null'); // nebo '?string' +$type = Type::fromString('string|null'); // или '?string' $type->getNames(); // ['string', 'null'] $type = Type::fromString('(Foo&Bar)|string'); @@ -62,10 +62,10 @@ $type->getNames(); // [['Foo', 'Bar'], 'string'] getTypes(): Type[] .[method] ---------------------------- -Връща масив от подтипове, съставящи съставен тип, като обекти `ReflectionType`: +Връща масив от подтипове, от които се състои съставният тип, като обекти `Type`: ```php -$type = Type::fromString('string|null'); // or '?string' +$type = Type::fromString('string|null'); // или '?string' $type->getTypes(); // [Type::fromString('string'), Type::fromString('null')] $type = Type::fromString('(Foo&Bar)|string'); @@ -79,7 +79,7 @@ $type->getTypes(); // [Type::fromString('Foo'), Type::fromString('Bar')] getSingleName(): ?string .[method] ---------------------------------- -Връща името на типа за прости типове, в противен случай е null. +При прости типове връща името на типа, в противен случай null. ```php $type = Type::fromString('string|null'); @@ -99,14 +99,14 @@ echo $type->getSingleName(); // null isSimple(): bool .[method] -------------------------- -Връща дали типът е прост тип. Обикновените типове също се считат за прости нулируеми типове: +Връща дали е прост тип. За прости типове се считат и простите nullable типове: ```php $type = Type::fromString('string'); $type->isSimple(); // true $type->isUnion(); // false -$type = Type::fromString('?Foo'); // nebo 'Foo|null' +$type = Type::fromString('?Foo'); // или 'Foo|null' $type->isSimple(); // true $type->isUnion(); // true ``` @@ -115,7 +115,7 @@ $type->isUnion(); // true isUnion(): bool .[method] ------------------------- -Връща информация дали съществува тип съюз. +Връща дали е union тип. ```php $type = Type::fromString('string|int'); @@ -126,7 +126,7 @@ $type->isUnion(); // true isIntersection(): bool .[method] -------------------------------- -Връща дали o е тип пресичане. +Връща дали е intersection тип. ```php @@ -138,7 +138,7 @@ $type->isIntersection(); // true isBuiltin(): bool .[method] --------------------------- -Връща информация дали типът е едновременно прост и вграден тип на PHP. +Връща дали типът е прост и същевременно вграден тип на PHP. ```php $type = Type::fromString('string'); @@ -155,7 +155,7 @@ $type->isBuiltin(); // false isClass(): bool .[method] ------------------------- -Връща информация дали типът е едновременно прост тип и име на клас. +Връща дали типът е прост и същевременно име на клас. ```php $type = Type::fromString('string'); @@ -186,7 +186,7 @@ $type->isClassKeyword(); // false allows(string $type): bool .[method] ------------------------------------ -Методът `allows()` проверява съвместимостта на типовете. Например тя ви позволява да проверите дали стойност от определен тип може да бъде предадена като параметър. +Методът `allows()` проверява съвместимостта на типовете. Например, позволява да се установи дали стойност от определен тип може да бъде предадена като параметър. ```php $type = Type::fromString('string|null'); diff --git a/utils/bg/validators.texy b/utils/bg/validators.texy index 15acdcebd1..73544c19c4 100644 --- a/utils/bg/validators.texy +++ b/utils/bg/validators.texy @@ -2,26 +2,26 @@ *********************** .[perex] -Трябва бързо и лесно да проверите дали дадена променлива съдържа валиден имейл адрес, например? Тук на помощ идва [api:Nette\Utils\Validators], статичен клас с полезни функции за валидиране на стойности. +Трябва бързо и лесно да проверите дали в променлива има например валиден имейл адрес? За това ще ви бъде полезен [api:Nette\Utils\Validators], статичен клас с полезни функции за валидация на стойности. -Настройка: +Инсталация: ```shell composer require nette/utils ``` -Всички примери предполагат, че псевдонимът вече е създаден: +Всички примери предполагат създаден псевдоним: ```php use Nette\Utils\Validators; ``` -Основно използване .[#toc-basic-usage] -====================================== +Основна употреба +================ -Класът има редица методи за проверка на стойности, като [isList( |#isList]), [isUnicode( |#isUnicode]), [isEmail() |#isEmail], [isUrl() |#isUrl] и др., които можете да използвате в кода си: +Класът разполага с редица методи за проверка на стойности, като например [#isUnicode()], [#isEmail()], [#isUrl()] и т.н., за използване във вашия код: ```php if (!Validators::isEmail($email)) { @@ -29,7 +29,7 @@ if (!Validators::isEmail($email)) { } ``` -Той може също така да провери дали стойността е от [очаквания тип |#Expected-Types], който представлява низ, в който опциите са разделени с наклонена черта `|`. По този начин можем лесно да проверяваме няколко типа с помощта на [if() |#if()]: +Освен това може да провери дали стойността е т.нар. [#очаквани типове], което е низ, където отделните възможности се разделят с вертикална черта `|`. Така можем лесно да проверим повече типове с помощта на [#is()]: ```php if (!Validators::is($val, 'int|string|bool')) { @@ -37,81 +37,81 @@ if (!Validators::is($val, 'int|string|bool')) { } ``` -Но също така ни дава система, в която трябва да записваме очаквания като низове (например в анотации или конфигурации) и след това да проверяваме стойностите спрямо тях. +Но това ни дава и възможност да създадем система, където е необходимо очакванията да се записват като низове (например в анотации или конфигурация) и след това според тях да се проверяват стойностите. -Можем също така да поставим заявка [assert() |#assert] за очакваните типове, която хвърля изключение, ако не успее. +За очакваните типове може да се постави и изискване [#assert()], което ако не е изпълнено, се хвърля изключение. -Очаквани видове .[#toc-expected-types] -====================================== +Очаквани типове +=============== -Очакваните типове образуват низ, състоящ се от един или повече варианти, разделени с вертикална черта `|`, podobně jako se zapisují typy v PHP (např. `'int|string|bool')`. Приема се и нулевият запис `?int`. +Очакваните типове представляват низ, състоящ се от една или повече варианти, разделени с вертикална черта `|`, подобно на начина, по който се записват типовете в PHP (напр. `'int|string|bool')`. Приема се и nullable запис `?int`. -Масив, в който всички елементи имат определен тип, се записва като `int[]`. +Масив, където всички елементи са от определен тип, се записва във формата `int[]`. -Някои типове могат да бъдат последвани от двоеточие и дължина `:length` или диапазон. `:[min]..[max]`Например `string:10` (низ от 10 байта), `float:10..` (число от 10 или повече), `array:..10` (масив от до десет елемента) или `list:10..20` (списък от 10 до 20 елемента), или регулярният израз u `pattern:[0-9]+`. +След някои типове може да последва двоеточие и дължина `:length` или диапазон `:[min]..[max]`, напр. `string:10` (низ с дължина 10 байта), `float:10..` (число 10 и по-голямо), `array:..10` (масив до десет елемента) или `list:10..20` (списък с 10 до 20 елемента), евентуално регулярен израз при `pattern:[0-9]+`. -Преглед на видовете и правилата: +Преглед на типовете и правилата: .[wide] -| PHP типове || +| PHP типове || |-------------------------- -| `array` .{width: 140px} || За броя на елементите може да се посочи диапазон. -|| `bool` | -|| `float` || За дадена стойност може да бъде зададен диапазон. -| `int` | може да се зададе диапазон от стойности. -| `null` | -| `object` | +| `array` .{width: 140px} | може да се посочи диапазон за броя на елементите +| `bool` | +| `float` | може да се посочи диапазон за стойността +| `int` | може да се посочи диапазон за стойността +| `null` | +| `object` | | `resource` | -| `scalar` | int\|float\|bool\|string -| `string` | За дължината в байтове може да се зададе диапазон. +| `scalar` | int\|float\|bool\|string +| `string` | може да се посочи диапазон за дължината в байтове | `callable` | | `iterable` | -| `mixed` | +| `mixed` | |-------------------------- -| псевдотипове || +| псевдо-типове || |------------------------------------------------ -| `list` | [индексиран масив |#isList], може да се зададе диапазон за броя на елементите -| `none` | празна стойност: `''`, `null`, `false` -| `number` | int\|float -| `numeric` | [число, включително текстово представяне |#isNumeric] -| `numericint`| [цяло число |#isNumericInt], включително текстово представяне -| `unicode` | [UTF-8 низ |#isUnicode], може да се зададе диапазон на дължината на символите. +| `list` | индексиран масив, може да се посочи диапазон за броя на елементите +| `none` | празна стойност: `''`, `null`, `false` +| `number` | int\|float +| `numeric` | [число, включително текстово представяне |#isNumeric] +| `numericint`| [цяло число, включително текстово представяне |#isNumericInt] +| `unicode` | [UTF-8 низ |#isUnicode], може да се посочи диапазон за дължината в знаци |-------------------------- -| клас символи (не трябва да е празен низ)|| +| знаков клас (не трябва да бъде празен низ) || |------------------------------------------------ -| `alnum` | всички буквено-цифрови символи -| `alpha` | всички символи са букви `[A-Za-z]` -| `digit` | всички символи са числа -| `lower` | всички символи са малки букви `[a-z]` -| `space` | всички символи са интервали -| `upper` | всички символи са главни букви `[A-Z]` -| `xdigit` | всички символи са шестнадесетични цифри `[0-9A-Fa-f]` +| `alnum` | всички знаци са буквено-цифрови +| `alpha` | всички знаци са букви `[A-Za-z]` +| `digit` | всички знаци са цифри +| `lower` | всички знаци са малки букви `[a-z]` +| `space` | всички знаци са интервали +| `upper` | всички знаци са главни букви `[A-Z]` +| `xdigit` | всички знаци са шестнадесетични цифри `[0-9A-Fa-f]` |-------------------------- -| проверка на синтаксиса || +| проверка на синтаксиса || |------------------------------------------------ -| `pattern` | регулярен израз, който трябва да съответства на **всички** редове -| `email` | [E-mail |#isEmail] -| `identifier`| [Идентификатор на PHP |#isPhpIdentifier] -| `url` | [URL |#isUrl] -| `uri` | [URI |#isUri] +| `pattern` | регулярен израз, на който трябва да отговаря **целият** низ +| `email` | [Имейл |#isEmail] +| `identifier`| [PHP идентификатор |#isPhpIdentifier] +| `url` | [URL |#isUrl] +| `uri` | [URI |#isUri] |-------------------------- -| удостоверяване на средата || +| проверка на средата || |------------------------------------------------ -| `class` | това е съществуващ клас -| `interface` | това е съществуващ интерфейс -| `directory` | това е съществуваща директория -| `file` | това е съществуващ файл +| `class` | е съществуващ клас +| `interface` | е съществуващ интерфейс +| `directory` | е съществуваща директория +| `file` | е съществуващ файл -Твърдение .[#toc-assertion] -=========================== +Assertions +========== assert($value, string $expected, string $label='variable'): void .[method] -------------------------------------------------------------------------- -Проверява дали стойността е един от [очакваните типове |#Expected-Types], разделени със звездичка. В противен случай се изхвърля изключението [api:Nette\Utils\AssertionException]. Думата `variable` в текста на изключението може да бъде заменена с друг параметър `$label`. +Проверява дали стойността е един от [очакваните типове |#Очаквани типове], разделени с вертикална черта. Ако не е, хвърля изключение [api:Nette\Utils\AssertionException]. Думата `variable` в текста на изключението може да бъде заменена с друга чрез параметъра `$label`. ```php Validators::assert('Nette', 'string:5'); // OK @@ -120,10 +120,10 @@ Validators::assert('Lorem ipsum dolor sit', 'string:78'); ``` -assertField(array $array, string|int $key, string $expected=null, string $label=null): void .[method] ------------------------------------------------------------------------------------------------------ +assertField(array $array, string|int $key, ?string $expected=null, ?string $label=null): void .[method] +------------------------------------------------------------------------------------------------------- -Проверява се дали елементът под ключа `$key` в полето `$array` е един от [очакваните типове |#Expected-Types], разделени със звездичка. В противен случай се изхвърля изключение [api:Nette\Utils\AssertionException]. Реда `item '%' in array` в текста на изключението може да бъде заменен с друг параметър `$label`. +Проверява дали елементът под ключ `$key` в масива `$array` е един от [очакваните типове |#Очаквани типове], разделени с вертикална черта. Ако не е, хвърля изключение [api:Nette\Utils\AssertionException]. Низът `item '%' in array` в текста на изключението може да бъде заменен с друг чрез параметъра `$label`. ```php $arr = ['foo' => 'Nette']; @@ -136,19 +136,19 @@ Validators::assertField($arr, 'foo', 'int'); ``` -Удостоверители .[#toc-validators] -================================= +Валидатори +========== is($value, string $expected): bool .[method] -------------------------------------------- -Проверява дали стойността е един от [очакваните типове |#Expected-Types], разделени със звездичка. +Проверява дали стойността е един от [очакваните типове |#Очаквани типове], разделени с вертикална черта. ```php Validators::is(1, 'int|float'); // true Validators::is(23, 'int:0..10'); // false -Validators::is('Nette Framework', 'string:15'); // true, délka je 15 bytů +Validators::is('Nette Framework', 'string:15'); // true, дължината е 15 байта Validators::is('Nette Framework', 'string:8..'); // true Validators::is('Nette Framework', 'string:30..40'); // false ``` @@ -157,7 +157,7 @@ Validators::is('Nette Framework', 'string:30..40'); // false isEmail(mixed $value): bool .[method] ------------------------------------- -Проверява дали стойността е валиден имейл адрес. Не се проверява дали домейнът действително съществува, проверява се само синтаксисът. Функцията взема предвид и бъдещите [TLD, които |https://cs.wikipedia.org/wiki/Doména_nejvyššího_řádu] може да са в Unicode. +Проверява дали стойността е валиден имейл адрес. Не се проверява дали домейнът действително съществува, проверява се само синтаксисът. Функцията отчита и бъдещите [TLD|https://en.wikipedia.org/wiki/Top-level_domain], които могат да бъдат и в unicode. ```php Validators::isEmail('example@nette.org'); // true @@ -169,9 +169,9 @@ Validators::isEmail('nette'); // false isInRange(mixed $value, array $range): bool .[method] ----------------------------------------------------- -Проверява дали стойността е в рамките на зададен диапазон `[min, max]`където може да се пропусне горна или долна граница (`null`). Могат да се сравняват числа, низове и обекти от тип DateTime. +Проверява дали стойността е в дадения диапазон `[min, max]`, където горната или долната граница можем да пропуснем (`null`). Могат да се сравняват числа, низове и обекти DateTime. -Ако липсват и двете граници (`[null, null]`) или се връща стойността `null`, `false`. +Ако липсват и двете граници (`[null, null]`) или стойността е `null`, връща `false`. ```php Validators::isInRange(5, [0, 5]); // true @@ -184,7 +184,7 @@ Validators::isInRange(1, [5]); // false isNone(mixed $value): bool .[method] ------------------------------------ -Проверява се дали стойността е `0`, `''`, `false` или `null`. +Проверява дали стойността е `0`, `''`, `false` или `null`. ```php Validators::isNone(0); // true @@ -227,7 +227,7 @@ Validators::isNumericInt('nette'); // false isPhpIdentifier(string $value): bool .[method] ---------------------------------------------- -Проверява дали стойността е синтактично валиден идентификатор в PHP, например за имена на класове, методи, функции и т.н. +Проверява дали стойността е синтактично валиден идентификатор в PHP, например за имена на класове, методи, функции и др. ```php Validators::isPhpIdentifier(''); // false @@ -240,7 +240,7 @@ Validators::isPhpIdentifier('one two'); // false isBuiltinType(string $type): bool .[method] ------------------------------------------- -Проверява дали `$type` е вграден тип в PHP. Ако не, това е име на клас. +Проверява дали `$type` е вграден тип на PHP. В противен случай това е име на клас. ```php Validators::isBuiltinType('string'); // true @@ -251,7 +251,7 @@ Validators::isBuiltinType('Foo'); // false isTypeDeclaration(string $type): bool .[method] ----------------------------------------------- -Проверява дали дадената декларация на типа е синтактично валидна. +Проверява дали дадената декларация на тип е синтактично валидна. ```php Validators::isTypeDeclaration('?string'); // true @@ -268,7 +268,7 @@ Validators::isTypeDeclaration('(A|B)'); // false isClassKeyword(string $type): bool .[method] -------------------------------------------- -Проверява се дали `$type` е един от вътрешните типове `self`, `parent`, `static`. +Проверява дали `$type` е един от вътрешните типове `self`, `parent`, `static`. ```php Validators::isClassKeyword('self'); // true @@ -279,7 +279,7 @@ Validators::isClassKeyword('Foo'); // false isUnicode(mixed $value): bool .[method] --------------------------------------- -Проверява дали стойността е валиден низ в UTF-8. +Проверява дали стойността е валиден UTF-8 низ. ```php Validators::isUnicode('nette'); // true @@ -306,7 +306,7 @@ Validators::isUrl('nette.org'); // false isUri(string $value): bool .[method] ------------------------------------ -Проверява дали стойността е валиден URI адрес, който всъщност е низ, започващ със синтактично валиден шаблон. +Проверява дали стойността е валиден URI адрес, т.е. всъщност низ, започващ със синтактично валидна схема. ```php Validators::isUri('https://nette.org'); // true diff --git a/utils/cs/@home.texy b/utils/cs/@home.texy index bf462b878e..ac310407d8 100644 --- a/utils/cs/@home.texy +++ b/utils/cs/@home.texy @@ -1,5 +1,5 @@ -Nástroje -******** +Nette Utils +*********** .[perex] V balíčku `nette/utils` najdete sadu užitečných tříd pro každodenní použití: @@ -8,9 +8,9 @@ V balíčku `nette/utils` najdete sadu užitečných tříd pro každodenní pou | [Datum a čas |datetime] | Nette\Utils\DateTime | [Finder] | Nette\Utils\Finder | [HTML elementy |html-elements] | Nette\Utils\Html +| [Iterátory |iterables] | Nette\Utils\Iterables | [JSON |json] | Nette\Utils\Json | [Náhodné řetězce |random] | Nette\Utils\Random -| [Objektový model |smartobject] | Nette\SmartObject & Nette\StaticClass | [Obrázky |images] | Nette\Utils\Image | [PHP reflexe |reflection] | Nette\Utils\Reflection | [PHP typy |type] | Nette\Utils\Type @@ -20,6 +20,7 @@ V balíčku `nette/utils` najdete sadu užitečných tříd pro každodenní pou | [Řetězce |strings] | Nette\Utils\Strings | [Souborový systém |filesystem] | Nette\Utils\FileSystem | [Stránkování |paginator] | Nette\Utils\Paginator +| [SmartObject] & [StaticClass] | Nette\SmartObject & Nette\StaticClass | [Validátor |validators] | Nette\Utils\Validators @@ -34,9 +35,12 @@ composer require nette/utils | verze | kompatibilní s PHP |-----------|------------------- -| Nette Utils 4.0 | PHP 8.0 – 8.2 -| Nette Utils 3.2 | PHP 7.2 – 8.2 +| Nette Utils 4.0 | PHP 8.0 – 8.4 +| Nette Utils 3.2 | PHP 7.2 – 8.3 | Nette Utils 3.0 – 3.1 | PHP 7.1 – 8.0 | Nette Utils 2.5 | PHP 5.6 – 8.0 Platí pro poslední patch verze. + + +Pokud aktualizujte balíček na novější verzi, podívejte se na stránku [upgrade|upgrading]. diff --git a/utils/cs/@left-menu.texy b/utils/cs/@left-menu.texy index 94c90f1dd2..dd371e9e34 100644 --- a/utils/cs/@left-menu.texy +++ b/utils/cs/@left-menu.texy @@ -1,10 +1,11 @@ -Balíček nette/utils -******************* +Nette Utils +*********** - [Callbacky |callback] - [Datum a čas |datetime] - [Finder] - [Floats] - [HTML elementy |html-elements] +- [Iterátory |iterables] - [JSON] - [Náhodné řetězce |random] - [Obrázky |images] @@ -14,7 +15,8 @@ Balíček nette/utils - [Pole |arrays] - [Pomocné funkce |helpers] - [Řetězce |strings] -- [SmartObject |smartobject] +- [SmartObject] +- [StaticClass] - [Souborový systém |filesystem] - [Validátor |validators] diff --git a/utils/cs/@meta.texy b/utils/cs/@meta.texy new file mode 100644 index 0000000000..462d9add80 --- /dev/null +++ b/utils/cs/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokumentace}} diff --git a/utils/cs/arrays.texy b/utils/cs/arrays.texy index de80e0c0cf..baa1c82d10 100644 --- a/utils/cs/arrays.texy +++ b/utils/cs/arrays.texy @@ -2,7 +2,7 @@ Práce s poli ************ .[perex] -Tato stránka se věnuje třídám [Nette\Utils\Arrays|#Arrays], [#ArrayHash] a [#ArrayList], které se týkají polí. +Tato stránka se věnuje třídám [Nette\Utils\Arrays |#Arrays], [#ArrayHash] a [#ArrayList], které se týkají polí. Instalace: @@ -15,7 +15,7 @@ composer require nette/utils Arrays ====== -[api:Nette\Utils\Arrays] je statická třída obsahující užitečné funkce pro práci s poli. +[api:Nette\Utils\Arrays] je statická třída obsahující užitečné funkce pro práci s poli. Její obdobou pro iterátory je [Nette\Utils\Iterables|iterables]. Následující příklady předpokládají vytvořený alias: @@ -24,6 +24,47 @@ use Nette\Utils\Arrays; ``` +associate(array $array, mixed $path): array|\stdClass .[method] +--------------------------------------------------------------- + +Funkce flexibilně transformuje pole `$array` na asociativní pole nebo objekty podle zadané cesty `$path`. Cesta může být řetězec nebo pole. Tvoří ji názvy klíčů vstupního pole a operátory jako '[]', '->', '=', a '|'. Vyhazuje `Nette\InvalidArgumentException` v případě, že cesta je neplatná. + +```php +// převod na asociativní pole podle jednoduchého klíče +$arr = [ + ['name' => 'John', 'age' => 11], + ['name' => 'Mary', 'age' => null], + // ... +]; +$result = Arrays::associate($arr, 'name'); +// $result = ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// přiřazení hodnot z jednoho klíče k jinému s použitím operátoru = +$result = Arrays::associate($arr, 'name=age'); // nebo ['name', '=', 'age'] +// $result = ['John' => 11, 'Mary' => null, ...] +``` + +```php +// vytvoření objektu s použitím operátoru -> +$result = Arrays::associate($arr, '->name'); // nebo ['->', 'name'] +// $result = (object) ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// kombinace klíčů pomocí operátoru | +$result = Arrays::associate($arr, 'name|age'); // nebo ['name', '|', 'age'] +// $result: ['John' => ['name' => 'John', 'age' => 11], 'Paul' => ['name' => 'Paul', 'age' => 44]] +``` + +```php +// přidání do pole s použitím [] +$result = Arrays::associate($arr, 'name[]'); // nebo ['name', '[]'] +// $result: ['John' => [['name' => 'John', 'age' => 22], ['name' => 'John', 'age' => 11]]] +``` + + contains(array $array, $value): bool .[method] ---------------------------------------------- @@ -35,10 +76,10 @@ Arrays::contains(['1', false], 1); // false ``` -every(iterable $array, callable $callback): bool .[method] ----------------------------------------------------------- +every(array $array, callable $predicate): bool .[method] +-------------------------------------------------------- -Testuje, zda všechny prvky v poli projdou testem implementovaným v `$callback` se signaturou `function ($value, $key, array $array): bool`. +Testuje, zda všechny prvky v poli projdou testem implementovaným v `$predicate` se signaturou `function ($value, $key, array $array): bool`. ```php $array = [1, 30, 39, 29, 10, 13]; @@ -49,16 +90,51 @@ $res = Arrays::every($array, $isBelowThreshold); // true Viz [#some()]. -first(array $array): mixed .[method] ------------------------------------- +filter(array $array, callable $predicate): array .[method]{data-version:4.0.4} +------------------------------------------------------------------------------ + +Vrací nové pole obsahující všechny dvojice klíč-hodnota odpovídající zadanému predikátu. Callback má signaturu `function ($value, int|string $key, array $array): bool`. + +```php +Arrays::filter( + ['a' => 1, 'b' => 2, 'c' => 3], + fn($v) => $v < 3, +); +// ['a' => 1, 'b' => 2] +``` + + +first(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------- -Vrátí první položku z pole nebo null, pokud je pole prázdné. Nezmění vnitřní ukazatel narozdíl od `reset()`. +Vrátí první položku (odpovídající predikátu, je-li zadán). Pokud taková položka neexistuje, vrátí výsledek volání `$else` nebo null. Parametr `$predicate` má signaturu `function ($value, int|string $key, array $array): bool`. + +Nezmění vnitřní ukazatel narozdíl od `reset()`. Parametry `$predicate` a `$else` existují od verze 4.0.4. + +```php +Arrays::first([1, 2, 3]); // 1 +Arrays::first([1, 2, 3], fn($v) => $v > 2); // 3 +Arrays::first([]); // null +Arrays::first([], else: fn() => false); // false +``` + +Viz [#last()]. + + +firstKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +------------------------------------------------------------------------------------------------ + +Vrací klíč první položky (odpovídající predikátu, pokud je uveden) nebo null, pokud taková položka neexistuje. Predikát `$predicate` má signaturu `function ($value, int|string $key, array $array): bool`. ```php -Arrays::first([1, 2, 3]); // 1 -Arrays::first([]); // null +Arrays::firstKey([1, 2, 3]); // 0 +Arrays::firstKey([1, 2, 3], fn($v) => $v > 2); // 2 +Arrays::firstKey(['a' => 1, 'b' => 2]); // 'a' +Arrays::firstKey([]); // null ``` +Viz [#lastKey()]. + flatten(array $array, bool $preserveKeys=false): array .[method] ---------------------------------------------------------------- @@ -71,8 +147,8 @@ $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); ``` -get(array $array, string|int|array $key, mixed $default=null): mixed .[method] ------------------------------------------------------------------------------- +get(array $array, string|int|array $key, ?mixed $default=null): mixed .[method] +------------------------------------------------------------------------------- Vrací prvek `$array[$key]`. Pokud neexistuje, vyhodí buď výjimku `Nette\InvalidArgumentException`, nebo je-li uveden třetí parametr `$default`, vrátí ten. @@ -104,7 +180,7 @@ $valueRef = & Arrays::getRef($array, 'foo'); // vrátí referenci na $array['foo'] ``` -Stejně jako funkce [get() |#get()] umí pracovat s vícerozměrnými poli. +Stejně jako funkce [#get()] umí pracovat s vícerozměrnými poli. ```php $value = & Arrays::getRef($array, ['color', 'favorite']); @@ -188,21 +264,42 @@ Arrays::isList(['a' => 1, 'b' => 2]); // false ``` -last(array $array): mixed .[method] ------------------------------------ +last(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------ + +Vrátí poslední položku (odpovídající predikátu, je-li zadán). Pokud taková položka neexistuje, vrátí výsledek volání `$else` nebo null. Parametr `$predicate` má signaturu `function ($value, int|string $key, array $array): bool`. + +Nezmění vnitřní ukazatel narozdíl od `end()`. Parametry `$predicate` a `$else` existují od verze 4.0.4. + +```php +Arrays::last([1, 2, 3]); // 3 +Arrays::last([1, 2, 3], fn($v) => $v < 3); // 2 +Arrays::last([]); // null +Arrays::last([], else: fn() => false); // false +``` + +Viz [#first()]. + + +lastKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +----------------------------------------------------------------------------------------------- -Vrátí poslední položku z pole nebo null, pokud je pole prázdné. Nezmění vnitřní ukazatel narozdíl od `end()`. +Vrací klíč poslední položky (odpovídající predikátu, pokud je uveden) nebo null, pokud taková položka neexistuje. Predikát `$predicate` má signaturu `function ($value, int|string $key, array $array): bool`. ```php -Arrays::last([1, 2, 3]); // 3 -Arrays::last([]); // null +Arrays::lastKey([1, 2, 3]); // 2 +Arrays::lastKey([1, 2, 3], fn($v) => $v < 3); // 1 +Arrays::lastKey(['a' => 1, 'b' => 2]); // 'b' +Arrays::lastKey([]); // null ``` +Viz [#firstKey()]. -map(iterable $array, callable $callback): array .[method] + +map(array $array, callable $transformer): array .[method] --------------------------------------------------------- -Zavolá `$callback` na všechny prvky v poli a vrátí pole vrácených hodnot. Callback má signaturu `function ($value, $key, array $array): bool`. +Zavolá `$transformer` na všechny prvky v poli a vrátí pole vrácených hodnot. Callback má signaturu `function ($value, $key, array $array): mixed`. ```php $array = ['foo', 'bar', 'baz']; @@ -211,10 +308,24 @@ $res = Arrays::map($array, fn($value) => $value . $value); ``` +mapWithKeys(array $array, callable $transformer): array .[method] +----------------------------------------------------------------- + +Vytváří nové pole transformací hodnot a klíčů původního pole. Funkce `$transformer` má signaturu `function ($value, $key, array $array): ?array{$newKey, $newValue}`. Pokud `$transformer` vrátí `null`, prvek je přeskočen. Pro zachované prvky se první prvek vráceného pole použije jako nový klíč a druhý prvek jako nová hodnota. + +```php +$array = ['a' => 1, 'b' => 2]; +$result = Arrays::mapWithKeys($array, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +Tato metoda je užitečná v situacích, kdy potřebujete změnit strukturu pole (klíče i hodnoty současně) nebo filtrovat prvky při transformaci (vracením null pro nežádoucí prvky). + + mergeTree(array $array1, array $array2): array .[method] -------------------------------------------------------- -Rekurzivně sloučí dvě pole. Hodí se například ke slučování stromových struktur. Při slučování se řídí stejnými pravidly jako operátor `+` aplikovaný na pole, tj. k prvnímu poli přidává dvojice klíč/hodnota z druhého pole a v případě kolize klíčů ponechá hodnotu z prvního pole. +Rekurzivně sloučí dvě pole. Hodí se například ke slučování stromových struktur. Při slučování se řídí stejnými pravidly jako operátor `+` aplikovaný na pole, tj. k prvnímu poli přidává dvojice klíč/hodnota z druhého pole a v případě kolize klíčů ponechá hodnotu z prvního pole. ```php $array1 = ['color' => ['favorite' => 'red'], 5]; @@ -227,8 +338,8 @@ $array = Arrays::mergeTree($array1, $array2); Hodnoty z druhého pole jsou vždy přidány na konec prvního. Jako trošku matoucí se může zdát zmizení hodnoty `10` z druhého pole. Je třeba si uvědomit, že tato hodnota a stejně tak hodnota `5` v poli prvním mají přiřazený stejný numerický klíč `0`, proto ve výsledném poli je jen prvek z prvního pole. -normalize(array $array, string $filling=null): array .[method] --------------------------------------------------------------- +normalize(array $array, ?string $filling=null): array .[method] +--------------------------------------------------------------- Normalizuje pole na asociativní pole. Numerické klíče nahradí jejich hodnotami, novou hodnotou bude `$filling`. @@ -243,14 +354,14 @@ $array = Arrays::normalize([1 => 'first', 'a' => 'second'], 'foobar'); ``` -pick(array &$array, string|int $key, mixed $default=null): mixed .[method] --------------------------------------------------------------------------- +pick(array &$array, string|int $key, ?mixed $default=null): mixed .[method] +--------------------------------------------------------------------------- Vrátí a odstraní hodnotu prvku z pole. Pokud neexistuje, vyhodí výjimku, nebo vrátí hodnotu `$default`, pokud je uvedena. ```php -$array = [1 => 'foo', null => 'bar']; -$a = Arrays::pick($array, null); +$array = [1 => 'foo', 'x' => 'bar']; +$a = Arrays::pick($array, 'x'); // $a = 'bar' $b = Arrays::pick($array, 'not-exists', 'foobar'); // $b = 'foobar' @@ -284,10 +395,10 @@ $position = Arrays::getKeyOffset($array, 'not-exists'); // vrátí null ``` -some(iterable $array, callable $callback): bool .[method] ---------------------------------------------------------- +some(array $array, callable $predicate): bool .[method] +------------------------------------------------------- -Testuje, zda alespoň jeden prvek v poli projde testem implementovaným v `$callback` se signaturou `function ($value, $key, array $array): bool`. +Testuje, zda alespoň jeden prvek v poli projde testem implementovaným v `$predicate` se signaturou `function ($value, $key, array $array): bool`. ```php $array = [1, 2, 3, 4]; @@ -321,8 +432,8 @@ Arrays::toObject($array, $obj); // nastaví $obj->foo = 1; $obj->bar = 2; ``` -wrap(iterable $array, string $prefix='', string $suffix=''): array .[method] ----------------------------------------------------------------------------- +wrap(array $array, string $prefix='', string $suffix=''): array .[method] +------------------------------------------------------------------------- Každou položku v poli přetypuje na řetězec a obalí předponou `$prefix` a příponou `$suffix`. diff --git a/utils/cs/datetime.texy b/utils/cs/datetime.texy index ea1c626e56..f685af7909 100644 --- a/utils/cs/datetime.texy +++ b/utils/cs/datetime.texy @@ -4,6 +4,9 @@ Datum a čas .[perex] [api:Nette\Utils\DateTime] je třída, která rozšiřuje nativní [php:DateTime] o další funkce. +Oproti svému předchůdci je striktní. Zatímco PHP **tiše akceptuje** nesmyslná data jako `0000-00-00` (převede na `-0001-11-30`) nebo `2024-02-31` (převede na `2024-03-02`), `Nette\Utils\DateTime` v takových případech vyhodí výjimku. + +Zároveň **opravuje chování** při přechodu na letní/zimní čas, kdy v nativním PHP může přičtení relativního času (např. `+100 minutes`) "vést k dřívějšímu výslednému času":https://phpfashion.com/cs/100-minut-je-mene-nez-50-paradoxy-php-pri-zmene-casu než přičtení kratšího úseku (např. `+50 minutes`). Nette zajišťuje, že aritmetika funguje intuitivně a `+100 minutes` je vždy více než `+50 minutes`. Instalace: @@ -38,14 +41,25 @@ DateTime::fromParts(1994, 2, 26, 4, 15, 32); ``` -static createFromFormat(string $format, string $time, string|\DateTimeZone $timezone=null): DateTime|false .[method] --------------------------------------------------------------------------------------------------------------------- +static createFromFormat(string $format, string $time, ?string|\DateTimeZone $timezone=null): DateTime|false .[method] +--------------------------------------------------------------------------------------------------------------------- Rozšiřuje [DateTime::createFromFormat()|https://www.php.net/manual/en/datetime.createfromformat.php] o možnost zadat timezone jako řetězec. ```php DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); ``` +static relativeToSeconds(string $str): int .[method]{data-version:4.0.7} +------------------------------------------------------------------------ +Převede relativní časový údaj na sekundy. Hodí se pro převod časů jako `5 minutes` nebo `2 hours` na číselnou hodnotu. + +```php +DateTime::relativeToSeconds('1 minute'); // 60 +DateTime::relativeToSeconds('10 minutes'); // 600 +DateTime::relativeToSeconds('-1 hour'); // -3600 +``` + + modifyClone(string $modify=''): static .[method] ------------------------------------------------ Vytvoří kopii s upraveným časem. diff --git a/utils/cs/filesystem.texy b/utils/cs/filesystem.texy index 03a950b5e2..1f68e7313b 100644 --- a/utils/cs/filesystem.texy +++ b/utils/cs/filesystem.texy @@ -2,9 +2,11 @@ Souborový systém **************** .[perex] -[api:Nette\Utils\FileSystem] je statická třída s užitečnými funkcemi pro práci se souborovým systémem. Jednou z výhod oproti nativním PHP funkcím je, že v případě chyb vyhazují výjimky. +[api:Nette\Utils\FileSystem] je třída s užitečnými funkcemi pro práci se souborovým systémem. Jednou z výhod oproti nativním PHP funkcím je, že v případě chyby vyhazují výjimky. +Pokud potřebujete hledat soubory na disku, použijte [Finder|finder]. + Instalace: ```shell @@ -85,8 +87,7 @@ $content = FileSystem::read('/path/to/file'); readLines(string $file, bool $stripNewLines=true): \Generator .[method] ----------------------------------------------------------------------- -Přečte obsah souboru řádek po řádku. Na rozdíl od nativní funkce `file()` nenačítá celý soubor do paměti, ale čte jej průběžně, takže je možné číst i soubory větší než dostupná paměť. `$stripNewLines` říká, zda se mají ostranit znaky konce řádku `\r` a `\n`. -V případě chyby vyvolá výjimku `Nette\IOException`. +Přečte obsah souboru řádek po řádku. Na rozdíl od nativní funkce `file()` nenačítá celý soubor do paměti, ale čte jej průběžně, takže je možné číst i soubory větší než dostupná paměť. `$stripNewLines` říká, zda se mají ostranit znaky konce řádku `\r` a `\n`. V případě chyby vyvolá výjimku `Nette\IOException`. ```php $lines = FileSystem::readLines('/path/to/file'); @@ -174,3 +175,40 @@ Převede lomítka na znaky specifické pro aktuální platformu, tj. `\` ve Wind ```php $path = FileSystem::platformSlashes($path); ``` + + +resolvePath(string $basePath, string $path): string .[method]{data-version:4.0.6} +--------------------------------------------------------------------------------- + +Odvozuje finální cestu z cesty `$path` vzhledem k základnímu adresáři `$basePath`. Absolutní cesty (`/foo`, `C:/foo`) ponechá beze změny (pouze normalizuje lomítka), relativní cesty připojí k základní cestě. + +```php +// Na Windows by lomítka ve výstupu byla opačná (\) +FileSystem::resolvePath('/base/dir', '/abs/path'); // '/abs/path' +FileSystem::resolvePath('/base/dir', 'rel'); // '/base/dir/rel' +FileSystem::resolvePath('base/dir', '../file.txt'); // 'base/file.txt' +FileSystem::resolvePath('base', ''); // 'base' +``` + + +Statický vs nestatický přístup +============================== + +Abyste kupříkladu pro účely testování mohli třídu snadno nahradit jinou (mockem), používejte ji nestaticky: + +```php +class AnyClassUsingFileSystem +{ + public function __construct( + private FileSystem $fileSystem, + ) { + } + + public function readConfig(): string + { + return $this->fileSystem->read(/* ... */); + } + + ... +} +``` diff --git a/utils/cs/finder.texy b/utils/cs/finder.texy index b7748df578..3c929c7e5d 100644 --- a/utils/cs/finder.texy +++ b/utils/cs/finder.texy @@ -29,8 +29,7 @@ foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { } ``` -Výchozí adresář pro hledání je aktuální adresář, ale můžete ho změnit pomocí metod [in() nebo from()|#Kde se má hledat?]. -Proměnná `$file` je instancí třídy [FileInfo |#FileInfo] se spoustou užitečných metod. V klíči `$name` je cesta k souboru jako řetězec. +Výchozí adresář pro hledání je aktuální adresář, ale můžete ho změnit pomocí metod [in() nebo from() |#Kde se má hledat]. Proměnná `$file` je instancí třídy [#FileInfo] se spoustou užitečných metod. V klíči `$name` je cesta k souboru jako řetězec. Co se má hledat? @@ -59,7 +58,9 @@ Alternativou statických metod je vytvoření instance pomocí `new Finder` (tak ->files('*.php'); // plus všechny PHP soubory ``` -V masce můžete používat [zástupné znaky|#Zástupné znaky] `*`, `**`, `?` a `[...]`. Dokonce můžete specifikovat i v adresáře, například `src/*.php` vyhledá všechny PHP soubory v adresáři `src`. +V masce můžete používat [#zástupné znaky] `*`, `**`, `?` a `[...]`. Dokonce můžete specifikovat i v adresáře, například `src/*.php` vyhledá všechny PHP soubory v adresáři `src`. + +Symlinky jsou také považovány za adresáře nebo soubory. Kde se má hledat? @@ -82,7 +83,7 @@ Finder::findFiles('*.php') ->in('/var/www/html'); ``` -V cestě je možné použít zástupné znaky [zástupné znaky|#Zástupné znaky] `*`, `**`, `?`. Můžete tak třeba pomocí cesty `src/*/*.php` hledat všechny PHP soubory v adresářích druhé úrovně v adresáři `src`. Znak `**` nazývaný globstar je mocným trumfem, protože umožňuje hledat i v podadresářích: pomocí `src/**/tests/*.php` hledáte všechny PHP soubory v adresáři `tests` nacházejícím se v `src` nebo jakémkoliv jeho podadresáři. +V cestě je možné použít zástupné znaky [#zástupné znaky] `*`, `**`, `?`. Můžete tak třeba pomocí cesty `src/*/*.php` hledat všechny PHP soubory v adresářích druhé úrovně v adresáři `src`. Znak `**` nazývaný globstar je mocným trumfem, protože umožňuje hledat i v podadresářích: pomocí `src/**/tests/*.php` hledáte všechny PHP soubory v adresáři `tests` nacházejícím se v `src` nebo jakémkoliv jeho podadresáři. Naopak zástupné znaky `[...]` v cestě podporovány nejsou, tj. nemají speciální význam, aby nedocházelo k nežádoucímu chování v případě, že budete hledat třeba `in(__DIR__)` a náhodou v cestě se budou vyskytovat znaky `[]`. @@ -169,7 +170,7 @@ Finder umožňuje pomocí vlastních funkcí rozhodovat, do kterého adresáře ```php Finder::findFiles('*.php') - ->descentFilter($file->getBasename() !== 'temp'); + ->descentFilter(fn($file) => $file->getBasename() !== 'temp'); ``` diff --git a/utils/cs/floats.texy b/utils/cs/floats.texy index b8d71ef7e2..bd01e4f548 100644 --- a/utils/cs/floats.texy +++ b/utils/cs/floats.texy @@ -21,8 +21,7 @@ use Nette\Utils\Floats; Motivace ======== -Říkáte si, proč vlastně třída na porovnávání floatů? Vždyť můžu použít operátory `<`, `>`, `===` a mám vystaráno. -Není to tak úplně pravda. Co myslíte, že vypíše tento kód? +Říkáte si, proč vlastně třída na porovnávání floatů? Vždyť můžu použít operátory `<`, `>`, `===` a mám vystaráno. Není to tak úplně pravda. Co myslíte, že vypíše tento kód? ```php $a = 0.1 + 0.2; @@ -42,6 +41,9 @@ echo Floats::areEqual($a, $b) ? 'same' : 'not same'; // same Při pokusu porovnávat `NAN` vyhodí výjimku `\LogicException`. +.[tip] +Třída `Floats` toleruje rozdíly menší než `1e-10`. Pokud potřebujete pracovat s větší přesností, použijte raději knihovnu BCMath. + Porovnávání floatů ================== @@ -110,7 +112,7 @@ Lze použít například s funkcí `usort`. ```php $arr = [1, 5, 2, -3.5]; -usort($arr, [Float::class, 'compare']); +usort($arr, [Floats::class, 'compare']); // $arr je nyní [-3.5, 1, 2, 5] ``` diff --git a/utils/cs/helpers.texy b/utils/cs/helpers.texy index 198009371c..49e37ebaed 100644 --- a/utils/cs/helpers.texy +++ b/utils/cs/helpers.texy @@ -43,8 +43,7 @@ Helpers::clamp($level, 0, 255); compare(mixed $left, string $operator, mixed $right): bool .[method] -------------------------------------------------------------------- -Porovná dvě hodnoty stejným způsobem, jako to dělá PHP. Rozlišuje operátory `>`, `>=`, `<`, `<=`, `=`, `==`, `===`, `!=`, `!==`, `<>`. -Funkce je užitečná v situacích, kdy operátor je proměnlivý. +Porovná dvě hodnoty stejným způsobem, jako to dělá PHP. Rozlišuje operátory `>`, `>=`, `<`, `<=`, `=`, `==`, `===`, `!=`, `!==`, `<>`. Funkce je užitečná v situacích, kdy operátor je proměnlivý. ```php Helpers::compare(10, '<', 20); // true @@ -85,3 +84,14 @@ Helpers::getSuggestion($items, 'fo'); // 'foo' Helpers::getSuggestion($items, 'barr'); // 'bar' Helpers::getSuggestion($items, 'baz'); // 'bar', ne 'baz' ``` + + +splitClassName(string $name): array .[method]{data-version:4.0.10} +------------------------------------------------------------------ + +Rozdělí celý název třídy v PHP na jmenný prostor a zkrácený název třídy. Vrací pole dvou řetězců, kde první prvek je namespace a druhý název třídy. + +```php +Helpers::splitClassName('Nette\Utils\Helpers'); // ['Nette\Utils', 'Helpers'] +Helpers::splitClassName('Foo'); // ['', 'Foo'] +``` diff --git a/utils/cs/html-elements.texy b/utils/cs/html-elements.texy index 4a4153962c..1647c2664e 100644 --- a/utils/cs/html-elements.texy +++ b/utils/cs/html-elements.texy @@ -219,9 +219,9 @@ $el = Html::el('span') Další způsob pro vytvoření a vložení nového `Html` uzlu: ```php -$el = Html::el('ul') - ->create('li', ['class' => 'first']) - ->setText('první'); +$ul = Html::el('ul'); +$ul->create('li', ['class' => 'first']) + ->setText('první'); //
                                                                                                                            • první
                                                                                                                            ``` @@ -289,7 +289,7 @@ echo $el->endTag(); // '' echo $el->attributes(); // 'class="header"' ``` -Důležitým rysem je automatická ochrana proti [Cross Site Scriptingu (XSS) |nette:glossary#cross-site-scripting-xss]. Všechny hodnoty atributů nebo obsah vložený přes `setText()` či `addText()` se spolehlivě escapuje: +Důležitým rysem je automatická ochrana proti [Cross Site Scriptingu (XSS) |nette:glossary#Cross-Site Scripting XSS]. Všechny hodnoty atributů nebo obsah vložený přes `setText()` či `addText()` se spolehlivě escapuje: ```php echo Html::el('div') diff --git a/utils/cs/images.texy b/utils/cs/images.texy index 0e16ebff54..9f2317fde3 100644 --- a/utils/cs/images.texy +++ b/utils/cs/images.texy @@ -17,6 +17,8 @@ Všechny příklady předpokládají vytvořený alias: ```php use Nette\Utils\Image; +use Nette\Utils\ImageColor; +use Nette\Utils\ImageType; ``` @@ -32,7 +34,7 @@ $image = Image::fromBlank(100, 200); Volitelně lze určit barvu pozadí (výchozí je černá): ```php -$image = Image::fromBlank(100, 200, Image::rgb(125, 0, 0)); +$image = Image::fromBlank(100, 200, ImageColor::rgb(125, 0, 0)); ``` Nebo obrázek načteme ze souboru: @@ -41,17 +43,6 @@ Nebo obrázek načteme ze souboru: $image = Image::fromFile('nette.jpg'); ``` -Podporované formáty jsou JPEG, PNG, GIF, WebP, AVIF a BMP, nicméně musí je podporovat i vaše verze PHP (ověříte ve výpisu `phpinfo()` v sekci GD). Animace podporované nejsou. - -Potřebujete při načtení detekovat formát obrázku? Metoda ho vrátí v druhém parametru: - -```php -$image = Image::fromFile('nette.jpg', $type); -// $type bude Image::JPEG, Image::PNG, Image::GIF, Image::WEBP, Image::AVIF nebo Image::BMP -``` - -Samotnou detekci bez načtení obrázku provádí `Image::detectTypeFromFile()`. - Uložení obrázku =============== @@ -68,26 +59,46 @@ Můžeme určit kvalitu komprese v rozsahu 0..100 pro JPEG (výchozí 85), WEBP $image->save('resampled.jpg', 80); // JPEG, kvalita 80% ``` -Pokud z přípony souboru není zřejmý formát, lze ho určit jednou z konstant `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` a `Image::BMP`: +Pokud z přípony souboru není zřejmý formát, lze ho určit [konstantou |#Formáty]: ```php -$image->save('resampled.tmp', null, Image::JPEG); +$image->save('resampled.tmp', null, ImageType::JPEG); ``` Obrázek lze místo na disk zapsat do proměnné: ```php -$data = $image->toString(Image::JPEG, 80); // JPEG, kvalita 80% +$data = $image->toString(ImageType::JPEG, 80); // JPEG, kvalita 80% ``` nebo poslat přímo do prohlížeče s příslušnou HTTP hlavičkou `Content-Type`: ```php // odešle hlavičku Content-Type: image/png -$image->send(Image::PNG); +$image->send(ImageType::PNG); ``` +Formáty +======= + +Podporované formáty jsou JPEG, PNG, GIF, WebP, AVIF a BMP, nicméně musí je podporovat i vaše verze PHP, což ověříte funkcí [#isTypeSupported()]. Animace podporované nejsou. + +Formát představují konstanty `ImageType::JPEG`, `ImageType::PNG`, `ImageType::GIF`, `ImageType::WEBP`, `ImageType::AVIF` a `ImageType::BMP`. + +```php +$supported = Image::isTypeSupported(ImageType::JPEG); +``` + +Potřebujete při načtení detekovat formát obrázku? Metoda ho vrátí v druhém parametru: + +```php +$image = Image::fromFile('nette.jpg', $type); +``` + +Samotnou detekci bez načtení obrázku provádí `Image::detectTypeFromFile()`. + + Změna velikosti =============== @@ -181,16 +192,36 @@ $image->cropAuto(IMG_CROP_BLACK); Metoda `cropAuto()` je objektovou náhradou funkce `imagecropauto()`, v [její dokumentaci|https://www.php.net/manual/en/function.imagecropauto] najdete další informace. +Barvy .{data-version:4.0.2} +=========================== + +Metoda `ImageColor::rgb()` vám umožňuje definovat barvu pomocí hodnot červené, zelené a modré (RGB). Volitelně můžete také specifikovat hodnotu průhlednosti v rozmezí 0 (naprosto průhledné) až 1 (zcela neprůhledné), tedy stejně jako v CSS. + +```php +$color = ImageColor::rgb(255, 0, 0); // Červená +$transparentBlue = ImageColor::rgb(0, 0, 255, 0.5); // Poloprůhledná modrá +``` + +Metoda `ImageColor::hex()` umožňuje definovat barvu pomocí hexadecimálního formátu, podobně jako v CSS. Podporuje formáty `#rgb`, `#rrggbb`, `#rgba` a `#rrggbbaa`: + +```php +$color = ImageColor::hex("#F00"); // Červená +$transparentGreen = ImageColor::hex("#00FF0080"); // Poloprůhledná zelená +``` + +Barvy lze použít v dalších metodách, jako třeba `ellipse()`, `fill()` atd. + + Kreslení a úpravy ================= -Můžeš kreslit, můžeš psát, ale listy netrhat. Jsou vám k dispozici všechny funkce PHP pro práci s obrázky, jako například [imagefilledellipse|https://www.php.net/manual/en/function.imagefilledellipse.php], ale v objektovém hávu: +Můžeš kreslit, můžeš psát, ale listy netrhat. Jsou vám k dispozici všechny funkce PHP pro práci s obrázky, viz [#Přehled metod], ale v objektovém hávu: ```php -$image->filledEllipse($cx, $cy, $width, $height, Image::rgb(255, 0, 0, 63)); +$image->filledEllipse($centerX, $centerY, $width, $height, ImageColor::rgb(255, 0, 0)); ``` -Viz [#Přehled metod]. +Protože PHP funkce pro kreslení obdelníků jsou nepraktické kvůli určení souřadnic, nabízí třída `Image` jejich náhrady v podobě funkcí [#rectangleWH()] a [#filledRectangleWH()]. Spojení více obrázků @@ -200,7 +231,7 @@ Do obrázku lze snadno vložit jiný obrázek: ```php $logo = Image::fromFile('logo.png'); -$blank = Image::fromBlank(320, 240, Image::rgb(52, 132, 210)); +$blank = Image::fromBlank(320, 240, ImageColor::rgb(52, 132, 210)); // souřadnice lze uvést opět v procentech $blank->place($logo, '80%', '80%'); // vložíme poblíž pravého dolního rohu @@ -219,53 +250,68 @@ Přehled metod ============= -static fromBlank(int $width, int $height, array $color=null): Image .[method] ------------------------------------------------------------------------------ +static fromBlank(int $width, int $height, ?ImageColor $color=null): Image .[method] +----------------------------------------------------------------------------------- Vytvoří nový true color obrázek daných rozměrů. Výchozí barva je černá. static fromFile(string $file, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------- -Načte obrázek ze souboru a vrací jeho typ v `$detectedFormat`. Podporované typy jsou `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` a `Image::BMP`. +Načte obrázek ze souboru a vrací jeho [typ |#Formáty] v `$detectedFormat`. static fromString(string $s, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------ -Načte obrázek z řetezce a vrací jeho typ v `$detectedFormat`. Podporované typy jsou `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` a `Image::BMP`. +Načte obrázek z řetezce a vrací jeho [typ |#Formáty] v `$detectedFormat`. -static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method] ---------------------------------------------------------------------------------- -Vytvoří barvu, kterou lze použít v dalších metodách, jako třeba `ellipse()`, `fill()` atd. +static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method][deprecated] +--------------------------------------------------------------------------------------------- +Tuto funkci nahradila třída `ImageColor`, viz [#barvy]. static typeToExtension(int $type): string .[method] --------------------------------------------------- -Vrátí příponu souboru pro danou konstantu `Image::XXX`. +Vrátí příponu souboru pro daný [typ |#Formáty]. static typeToMimeType(int $type): string .[method] -------------------------------------------------- -Vrátí mime type pro danou konstantu `Image::XXX`. +Vrátí mime type pro daný [typ |#Formáty]. static extensionToType(string $extension): int .[method] -------------------------------------------------------- -Vrátí typ obrázku jako konstantu `Image::XXX` podle přípony souboru. +Vrátí [typ |#Formáty] obrázku podle přípony souboru. static detectTypeFromFile(string $file, int &$width=null, int &$height=null): ?int .[method] -------------------------------------------------------------------------------------------- -Vrátí typ obrázku jako konstantu `Image::XXX` a v parametrech `$width` a `$height` také jeho rozměry. +Vrátí [typ |#Formáty] obrázku a v parametrech `$width` a `$height` také jeho rozměry. static detectTypeFromString(string $s, int &$width=null, int &$height=null): ?int .[method] ------------------------------------------------------------------------------------------- -Vrátí typ obrázku z řetězce jako konstantu `Image::XXX` a v parametrech `$width` a `$height` také jeho rozměry. +Vrátí [typ |#Formáty] obrázku z řetězce a v parametrech `$width` a `$height` také jeho rozměry. -affine(array $affine, array $clip=null): Image .[method] --------------------------------------------------------- +static isTypeSupported(int $type): bool .[method] +------------------------------------------------- +Zjišťuje, zda je podporovaný daný [typ |#Formáty] obrázku. + + +static getSupportedTypes(): array .[method]{data-version:4.0.4} +--------------------------------------------------------------- +Vrací pole podporovaných [typů |#Formáty] obrázku. + + +static calculateTextBox(string $text, string $fontFile, float $size, float $angle=0, array $options=[]): array .[method] +------------------------------------------------------------------------------------------------------------------------ +Spočítá rozměry obdélníku, který obepne text v určitém písmu a velikosti. Vrací asociativní pole obsahující klíče `left`, `top`, `width`, `height`. Levý okraj může být i záporný, pokud text začíná levým podřezáváním. + + +affine(array $affine, ?array $clip=null): Image .[method] +--------------------------------------------------------- Vraťte obrázek obsahující afinně transformovaný obraz src pomocí volitelné oblasti oříznutí. ([více |https://www.php.net/manual/en/function.imageaffine]). @@ -274,8 +320,8 @@ affineMatrixConcat(array $m1, array $m2): array .[method] Vrací zřetězení dvou afinních transformačních matic, což je užitečné, pokud by se na stejný obrázek mělo najednou použít více transformací. ([více |https://www.php.net/manual/en/function.imageaffinematrixconcat]) -affineMatrixGet(int $type, mixed $options=null): array .[method] ----------------------------------------------------------------- +affineMatrixGet(int $type, ?mixed $options=null): array .[method] +----------------------------------------------------------------- Vrátí maticovou transformační matici. ([více |https://www.php.net/manual/en/function.imageaffinematrixget]) @@ -291,21 +337,11 @@ Aktivujte kreslení vyhlazených čar a polygonů. Nepodporuje alfa kanály. Fun Použití antialiased primitiv s průhlednou barvou pozadí může skončit s některými neočekávanými výsledky. Metoda prolnutí používá barvu pozadí jako všechny ostatní barvy. ([více |https://www.php.net/manual/en/function.imageantialias]) -arc(int $x, int $y, int $w, int $h, int $start, int $end, int $color): void .[method] -------------------------------------------------------------------------------------- +arc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color): void .[method] +--------------------------------------------------------------------------------------------------------------------------- Nakreslí oblouk kruhu se středem v daných souřadnicích. ([více |https://www.php.net/manual/en/function.imagearc]) -char(int $font, int $x, int $y, string $char, int $color): void .[method] -------------------------------------------------------------------------- -Nakreslí první znak `$char` do obrázku s jeho levým horním rohem na `$x`, `$y` (vlevo nahoře je 0, 0) s barvou `$color`. ([více |https://www.php.net/manual/en/function.imagechar]) - - -charUp(int $font, int $x, int $y, string $char, int $color): void .[method] ---------------------------------------------------------------------------- -Nakreslí znak `$char` svisle na určenou souřadnici na daném obrázku. ([více |https://www.php.net/manual/en/function.imagecharup]) - - colorAllocate(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------- Vrací identifikátor barvy představující barvu složenou z daných komponent RGB. Musí být volána pro vytvoření každé barvy, která má být použita v obrázku. ([více |https://www.php.net/manual/en/function.imagecolorallocate]) @@ -381,8 +417,8 @@ colorsTotal(): int .[method] Vrátí počet barev v obrazové paletě. ([více |https://www.php.net/manual/en/function.imagecolorstotal]) -colorTransparent(int $color=null): int .[method] ------------------------------------------------- +colorTransparent(?int $color=null): int .[method] +------------------------------------------------- Získá nebo nastaví průhlednou barvu v obrázku. ([více |https://www.php.net/manual/en/function.imagecolortransparent]) @@ -432,43 +468,48 @@ crop(int|string $left, int|string $top, int|string $width, int|string $height): Ořízne obrázek do dané obdélníkové oblasti. Rozměry je možné zadávat jako integery v pixelech nebo řetězce v procentech (například `'50%'`). -cropAuto(int $mode=-1, float $threshold=.5, int $color=-1): Image .[method] ---------------------------------------------------------------------------- +cropAuto(int $mode=-1, float $threshold=.5, ?ImageColor $color=null): Image .[method] +------------------------------------------------------------------------------------- Automaticky ořízne obrázek podle daného `$mode`. ([více |https://www.php.net/manual/en/function.imagecropauto]) -ellipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------- +ellipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------- Nakreslí elipsu se středem na zadaných souřadnicích. ([více |https://www.php.net/manual/en/function.imageellipse]) -fill(int $x, int $y, int $color): void .[method] ------------------------------------------------- +fill(int $x, int $y, ImageColor $color): void .[method] +------------------------------------------------------- Výplní oblast počínaje danou souřadnicí (vlevo nahoře je 0, 0) s daným `$color`. ([více |https://www.php.net/manual/en/function.imagefill]) -filledArc(int $cx, int $cy, int $w, int $h, int $s, int $e, int $color, int $style): void .[method] ---------------------------------------------------------------------------------------------------- +filledArc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color, int $style): void .[method] +--------------------------------------------------------------------------------------------------------------------------------------------- Nakreslí částečný oblouk se středem na zadaných souřadnicích. ([více |https://www.php.net/manual/en/function.imagefilledarc]) -filledEllipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------------- +filledEllipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------------- Nakreslí elipsu se středem na zadaných souřadnicích. ([více |https://www.php.net/manual/en/function.imagefilledellipse]) -filledPolygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------------- +filledPolygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------------- Vytvoří v obraze vyplněný mnohoúhelník. ([více |https://www.php.net/manual/en/function.imagefilledpolygon]) -filledRectangle(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] -------------------------------------------------------------------------------- -Vytvoří obdélník vyplněný `$color` v obrázku počínaje bodem 1 a končící bodem 2. Bod 0, 0 je levý horní roh obrázku. ([více |https://www.php.net/manual/en/function.imagefilledrectangle]) +filledRectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------- +Vytvoří obdélník vyplněný `$color` v obrázku počínaje bodem `$x1` & `$y1` a končící bodem `$x2` & `$y2`. Bod 0, 0 je levý horní roh obrázku. ([více |https://www.php.net/manual/en/function.imagefilledrectangle]) -fillToBorder(int $x, int $y, int $border, int $color): void .[method] ---------------------------------------------------------------------- +filledRectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------------- +Vytvoří obdélník vyplněný `$color` v obrázku počínaje bodem `$left` & `$top` o šířce `$width` a výšce `$height`. Bod 0, 0 je levý horní roh obrázku. + + +fillToBorder(int $x, int $y, int $border, ImageColor $color): void .[method] +---------------------------------------------------------------------------- Vykreslí výplň, jejíž barva okraje je definována pomocí `$border`. Výchozím bodem výplně je `$x`, `$y` (vlevo nahoře je 0, 0) a oblast je vyplněna barvou `$color`. ([více |https://www.php.net/manual/en/function.imagefilltoborder]) @@ -482,9 +523,9 @@ flip(int $mode): void .[method] Převrátí obrázek pomocí daného `$mode`. ([více |https://www.php.net/manual/en/function.imageflip]) -ftText(int $size, int $angle, int $x, int $y, int $col, string $fontFile, string $text, array $extrainfo=null): array .[method] -------------------------------------------------------------------------------------------------------------------------------- -Napište text do obrázku pomocí písem pomocí FreeType 2. ([více |https://www.php.net/manual/en/function.imagefttext]) +ftText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +---------------------------------------------------------------------------------------------------------------------------------------- +Napište text do obrázku. ([více |https://www.php.net/manual/en/function.imagefttext]) gammaCorrect(float $inputgamma, float $outputgamma): void .[method] @@ -512,8 +553,8 @@ getWidth(): int .[method] Vrátí šířku obrázku. -interlace(int $interlace=null): int .[method] ---------------------------------------------- +interlace(?int $interlace=null): int .[method] +---------------------------------------------- Zapíná nebo vypíná prokládaný režim. Pokud je prokládaný režim nastaven a obrázek je uložen jako JPEG, bude uložen jako progresivní JPEG. ([více |https://www.php.net/manual/en/function.imageinterlace]) @@ -527,13 +568,13 @@ layerEffect(int $effect): void .[method] Nastavte příznak prolnutí alfa tak, aby používal efekty vrstvení. ([více |https://www.php.net/manual/en/function.imagelayereffect]) -line(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] --------------------------------------------------------------------- +line(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +--------------------------------------------------------------------------- Nakreslí čáru mezi dvěma danými body. ([více |https://www.php.net/manual/en/function.imageline]) -openPolygon(array $points, int $numPoints, int $color): void .[method] ----------------------------------------------------------------------- +openPolygon(array $points, ImageColor $color): void .[method] +------------------------------------------------------------- Nakreslí na obrázek otevřený mnohoúhelník. Na rozdíl od `polygon()` není mezi posledním a prvním bodem nakreslena žádná čára. ([více |https://www.php.net/manual/en/function.imageopenpolygon]) @@ -552,23 +593,28 @@ place(Image $image, int|string $left=0, int|string $top=0, int $opacity=100): Im Zkopíruje `$image` do obrázku na souřadnice `$left` a `$top`. Souřadnice je možné zadávat jako integery v pixelech nebo řetězce v procentech (například `'50%'`). -polygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------- +polygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------- Vytvoří v obrazu mnohoúhelník. ([více |https://www.php.net/manual/en/function.imagepolygon]) -rectangle(int $x1, int $y1, int $x2, int $y2, int $col): void .[method] ------------------------------------------------------------------------ +rectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------- Vytvoří obdélník na zadaných souřadnicích. ([více |https://www.php.net/manual/en/function.imagerectangle]) +rectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------- +Vytvoří obdélník na zadaných souřadnicích. + + resize(int|string $width, int|string $height, int $flags=Image::OrSmaller): Image .[method] ------------------------------------------------------------------------------------------- -Změní velikost obrázku, [více informací|#Změna velikosti]. Rozměry je možné zadávat jako integery v pixelech nebo řetězce v procentech (například `'50%'`). +Změní velikost obrázku, [více informací |#Změna velikosti]. Rozměry je možné zadávat jako integery v pixelech nebo řetězce v procentech (například `'50%'`). -resolution(int $resX=null, int $resY=null): mixed .[method] ------------------------------------------------------------ +resolution(?int $resX=null, ?int $resY=null): mixed .[method] +------------------------------------------------------------- Nastaví nebo vrátí rozlišení obrázku v DPI (tečky na palec). Pokud není zadán žádný z volitelných parametrů, je aktuální rozlišení vráceno jako indexované pole. Pokud je zadáno pouze `$resX`, horizontální a vertikální rozlišení se nastaví na tuto hodnotu. Pokud jsou zadány oba volitelné parametry, horizontální a vertikální rozlišení se nastaví na tyto hodnoty. Rozlišení se používá pouze jako meta informace, když jsou obrázky čteny a zapisovány do formátů podporujících tento druh informací (aktuálně PNG a JPEG). Nemá to vliv na žádné operace kreslení. Výchozí rozlišení nových snímků je 96 DPI. ([více |https://www.php.net/manual/en/function.imageresolution]) @@ -582,11 +628,11 @@ Otočí obrázek pomocí zadaného `$angle` ve stupních. Střed otáčení je s Vyžaduje přítomnost *Bundled GD extension*, nemusí tedy fungovat všude. -save(string $file, int $quality=null, int $type=null): void .[method] ---------------------------------------------------------------------- +save(string $file, ?int $quality=null, ?int $type=null): void .[method] +----------------------------------------------------------------------- Uloží obrázek do souboru. -Kvalita komprese je v rozsahu 0..100 pro JPEG (výchozí 85), WEBP (výchozí 80) a AVIF (výchozí 30) a 0..9 pro PNG (výchozí 9). Pokud typ není zřejmý z přípony souboru, můžete jej zadat pomocí jedné z konstant `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` a `Image::BMP`. +Kvalita komprese je v rozsahu 0..100 pro JPEG (výchozí 85), WEBP (výchozí 80) a AVIF (výchozí 30) a 0..9 pro PNG (výchozí 9). Pokud typ není zřejmý z přípony souboru, můžete jej zadat pomocí jedné z konstant `ImageType`. saveAlpha(bool $saveflag): void .[method] @@ -601,11 +647,11 @@ scale(int $newWidth, int $newHeight=-1, int $mode=IMG_BILINEAR_FIXED): Image .[m Měřítko obrázku pomocí daného interpolačního algoritmu. ([více |https://www.php.net/manual/en/function.imagescale]) -send(int $type=Image::JPEG, int $quality=null): void .[method] --------------------------------------------------------------- +send(int $type=ImageType::JPEG, ?int $quality=null): void .[method] +------------------------------------------------------------------- Vypíše obrázek do prohlížeče. -Kvalita komprese je v rozsahu 0..100 pro JPEG (výchozí 85), WEBP (výchozí 80) a AVIF (výchozí 30) a 0..9 pro PNG (výchozí 9). Typ je jednou z konstant `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` a `Image::BMP`. +Kvalita komprese je v rozsahu 0..100 pro JPEG (výchozí 85), WEBP (výchozí 80) a AVIF (výchozí 30) a 0..9 pro PNG (výchozí 9). setBrush(Image $brush): void .[method] @@ -623,8 +669,8 @@ setInterpolation(int $method=IMG_BILINEAR_FIXED): void .[method] Nastaví metodu interpolace, která ovlivní metody `rotate()` a `affine()`. ([více |https://www.php.net/manual/en/function.imagesetinterpolation]) -setPixel(int $x, int $y, int $color): void .[method] ----------------------------------------------------- +setPixel(int $x, int $y, ImageColor $color): void .[method] +----------------------------------------------------------- Nakreslí pixel na zadané souřadnici. ([více |https://www.php.net/manual/en/function.imagesetpixel]) @@ -653,21 +699,11 @@ Doostří obrázek. Vyžaduje přítomnost *Bundled GD extension*, nemusí tedy fungovat všude. -string(int $font, int $x, int $y, string $str, int $col): void .[method] ------------------------------------------------------------------------- -Vypíše řetězec na zadané souřadnice. ([více |https://www.php.net/manual/en/function.imagestring]) - - -stringUp(int $font, int $x, int $y, string $s, int $col): void .[method] ------------------------------------------------------------------------- -Vypíše řetězec svisle na dané souřadnice. ([více |https://www.php.net/manual/en/function.imagestringup]) - - -toString(int $type=Image::JPEG, int $quality=null): string .[method] --------------------------------------------------------------------- +toString(int $type=ImageType::JPEG, ?int $quality=null): string .[method] +------------------------------------------------------------------------- Uloží obrázek do řetezce. -Kvalita komprese je v rozsahu 0..100 pro JPEG (výchozí 85), WEBP (výchozí 80) a AVIF (výchozí 30) a 0..9 pro PNG (výchozí 9). Typ je jednou z konstant `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` a `Image::BMP`. +Kvalita komprese je v rozsahu 0..100 pro JPEG (výchozí 85), WEBP (výchozí 80) a AVIF (výchozí 30) a 0..9 pro PNG (výchozí 9). trueColorToPalette(bool $dither, int $ncolors): void .[method] @@ -675,6 +711,6 @@ trueColorToPalette(bool $dither, int $ncolors): void .[method] Převede obraz truecolor na paletový. ([více |https://www.php.net/manual/en/function.imagetruecolortopalette]) -ttfText(int $size, int $angle, int $x, int $y, int $color, string $fontfile, string $text): array .[method] ------------------------------------------------------------------------------------------------------------ -Vypíše daný text do obrázku pomocí písem TrueType. ([více |https://www.php.net/manual/en/function.imagettftext]) +ttfText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +----------------------------------------------------------------------------------------------------------------------------------------- +Vypíše daný text do obrázku. ([více |https://www.php.net/manual/en/function.imagettftext]) diff --git a/utils/cs/iterables.texy b/utils/cs/iterables.texy new file mode 100644 index 0000000000..ebf73344b7 --- /dev/null +++ b/utils/cs/iterables.texy @@ -0,0 +1,191 @@ +Práce s iterátory +***************** + +.[perex]{data-version:4.0.4} +[api:Nette\Utils\Iterables] je statická třída s funkcemi pro práci s iterátory. Její obdobou pro pole je [Nette\Utils\Arrays|arrays]. + + +Instalace: + +```shell +composer require nette/utils +``` + +Všechny příklady předpokládají vytvořený alias: + +```php +use Nette\Utils\Iterables; +``` + + +contains(iterable $iterable, $value): bool .[method] +---------------------------------------------------- + +Hledá zadanou hodnotu v iterátoru. Používá striktní porovnání (`===`) pro ověření shody. Vrací `true`, pokud je hodnota nalezena, jinak `false`. + +```php +Iterables::contains(new ArrayIterator([1, 2, 3]), 1); // true +Iterables::contains(new ArrayIterator([1, 2, 3]), '1'); // false +``` + +Tato metoda je užitečná, když potřebujete rychle zjistit, zda se konkrétní hodnota v iterátoru nachází, aniž byste museli procházet všechny prvky ručně. + + +containsKey(iterable $iterable, $key): bool .[method] +----------------------------------------------------- + +Hledá zadaný klíč v iterátoru. Používá striktní porovnání (`===`) pro ověření shody. Vrací `true`, pokud je klíč nalezen, jinak `false`. + +```php +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 0); // true +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 4); // false +``` + + +every(iterable $iterable, callable $predicate): bool .[method] +-------------------------------------------------------------- + +Ověřuje, zda všechny prvky iterátoru splňují podmínku definovanou v `$predicate`. Funkce `$predicate` má signaturu `function ($value, $key, iterable $iterable): bool` a musí vracet `true` pro každý prvek, aby metoda `every()` vrátila `true`. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isBelowThreshold = fn($value) => $value < 40; +$res = Iterables::every($iterator, $isBelowThreshold); // true +``` + +Tato metoda je užitečná pro ověření, zda všechny prvky v kolekci splňují určitou podmínku, například zda jsou všechna čísla menší než určitá hodnota. + + +filter(iterable $iterable, callable $predicate): Generator .[method] +-------------------------------------------------------------------- + +Vytváří nový iterátor, který obsahuje pouze ty prvky z původního iterátoru, které splňují podmínku definovanou v `$predicate`. Funkce `$predicate` má signaturu `function ($value, $key, iterable $iterable): bool` a musí vracet `true` pro prvky, které mají být zachovány. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::filter($iterator, fn($v) => $v < 3); +// 1, 2 +``` + +Metoda využívá generátor, což znamená, že filtrování probíhá postupně při procházení výsledku. To je efektivní z hlediska paměti a umožňuje zpracovávat i velmi velké kolekce. Pokud neprojdete všechny prvky výsledného iterátoru, ušetříte výpočetní výkon, protože se nezpracují všechny prvky původního iterátoru. + + +first(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------------- + +Vrací první prvek iterátoru. Pokud je zadán `$predicate`, vrací první prvek, který splňuje danou podmínku. Funkce `$predicate` má signaturu `function ($value, $key, iterable $iterable): bool`. Pokud není nalezen žádný vyhovující prvek, volá se funkce `$else` (pokud je zadána) a vrací se její výsledek. Pokud `$else` není zadáno, vrací se `null`. + +```php +Iterables::first(new ArrayIterator([1, 2, 3])); // 1 +Iterables::first(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 3 +Iterables::first(new ArrayIterator([])); // null +Iterables::first(new ArrayIterator([]), else: fn() => false); // false +``` + +Tato metoda je užitečná, když potřebujete rychle získat první prvek kolekce nebo první prvek splňující určitou podmínku, aniž byste museli procházet celou kolekci ručně. + + +firstKey(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +---------------------------------------------------------------------------------------------- + +Vrací klíč prvního prvku iterátoru. Pokud je zadán `$predicate`, vrací klíč prvního prvku, který splňuje danou podmínku. Funkce `$predicate` má signaturu `function ($value, $key, iterable $iterable): bool`. Pokud není nalezen žádný vyhovující prvek, volá se funkce `$else` (pokud je zadána) a vrací se její výsledek. Pokud `$else` není zadáno, vrací se `null`. + +```php +Iterables::firstKey(new ArrayIterator([1, 2, 3])); // 0 +Iterables::firstKey(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 2 +Iterables::firstKey(new ArrayIterator(['a' => 1, 'b' => 2])); // 'a' +Iterables::firstKey(new ArrayIterator([])); // null +``` + + +map(iterable $iterable, callable $transformer): Generator .[method] +------------------------------------------------------------------- + +Vytváří nový iterátor aplikováním funkce `$transformer` na každý prvek původního iterátoru. Funkce `$transformer` má signaturu `function ($value, $key, iterable $iterable): mixed` a její návratová hodnota se použije jako nová hodnota prvku. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::map($iterator, fn($v) => $v * 2); +// 2, 4, 6 +``` + +Metoda využívá generátor, což znamená, že transformace probíhá postupně při procházení výsledku. To je efektivní z hlediska paměti a umožňuje zpracovávat i velmi velké kolekce. Pokud neprojdete všechny prvky výsledného iterátoru, ušetříte výpočetní výkon, protože se nezpracují všechny prvky původního iterátoru. + + +mapWithKeys(iterable $iterable, callable $transformer): Generator .[method] +--------------------------------------------------------------------------- + +Vytváří nový iterátor transformací hodnot a klíčů původního iterátoru. Funkce `$transformer` má signaturu `function ($value, $key, iterable $iterable): ?array{$newKey, $newValue}`. Pokud `$transformer` vrátí `null`, prvek je přeskočen. Pro zachované prvky se první prvek vráceného pole použije jako nový klíč a druhý prvek jako nová hodnota. + +```php +$iterator = new ArrayIterator(['a' => 1, 'b' => 2]); +$iterator = Iterables::mapWithKeys($iterator, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +Stejně jako `map()`, tato metoda využívá generátor pro postupné zpracování a efektivní práci s pamětí. To umožňuje pracovat s velkými kolekcemi a šetřit výpočetní výkon při částečném průchodu výsledkem. + + +memoize(iterable $iterable): IteratorAggregate .[method] +-------------------------------------------------------- + +Vytváří obal kolem iterátoru, který během iterace ukládá do mezipaměti jeho klíče a hodnoty. To umožňuje opakovanou iteraci dat bez nutnosti znovu procházet původní zdroj dat. + +```php +$iterator = /* data, která nelze iterovat vícekrát */ +$memoized = Iterables::memoize($iterator); +// Nyní můžete iterovat $memoized vícekrát bez ztráty dat +``` + +Tato metoda je užitečná v situacích, kdy potřebujete vícekrát projít stejnou sadu dat, ale původní iterátor neumožňuje opakovanou iteraci nebo by opakované procházení bylo nákladné (např. při čtení dat z databáze nebo souboru). + + +repeatable(callable $factory): IteratorAggregate .[method]{data-version:4.0.10} +------------------------------------------------------------------------------- + +Umožňuje opakovanou iteraci objektů, které to běžně nedovolují, typicky [PHP generátorů |https://www.php.net/manual/en/language.generators.overview.php]. Metoda `repeatable()` tento problém elegantně řeší: místo samotného iterátoru jí předáte funkci, která iterátor vytváří. Tato továrna je pak zavolána automaticky při každém novém průchodu cyklem. + +```php +// Běžný generátor, který nelze projít dvakrát +$generator = function () { + yield 'A'; + yield 'B'; +}; + +$iterator = Iterables::repeatable($generator); + +foreach ($iterator as $v) echo $v; // Vypíše: AB +foreach ($iterator as $v) echo $v; // Vypíše: AB (generátor se spustil znovu) +``` + +Tato metoda je alternativou k [#memoize()] v situacích, kdy pracujete s **velkým objemem dat**, jelikož `repeatable()` si data neukládá, ale při každém průchodu je generuje znovu. + + +some(iterable $iterable, callable $predicate): bool .[method] +------------------------------------------------------------- + +Ověřuje, zda alespoň jeden prvek iterátoru splňuje podmínku definovanou v `$predicate`. Funkce `$predicate` má signaturu `function ($value, $key, iterable $iterable): bool` a musí vracet `true` pro alespoň jeden prvek, aby metoda `some()` vrátila `true`. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isEven = fn($value) => $value % 2 === 0; +$res = Iterables::some($iterator, $isEven); // true +``` + +Tato metoda je užitečná pro rychlé ověření, zda v kolekci existuje alespoň jeden prvek splňující určitou podmínku, například zda kolekce obsahuje alespoň jedno sudé číslo. + +Viz [#every()]. + + +toIterator(iterable $iterable): Iterator .[method] +-------------------------------------------------- + +Převádí jakýkoliv iterovatelný objekt (array, Traversable) na Iterator. Pokud je vstup již Iterator, vrátí ho beze změny. + +```php +$array = [1, 2, 3]; +$iterator = Iterables::toIterator($array); +// Nyní máte Iterator místo pole +``` + +Tato metoda je užitečná, když potřebujete zajistit, že máte k dispozici Iterator, bez ohledu na typ vstupních dat. To může být užitečné při vytváření funkcí, které pracují s různými typy iterovatelných dat. diff --git a/utils/cs/json.texy b/utils/cs/json.texy index 4d8cf928a5..f9490ed421 100644 --- a/utils/cs/json.texy +++ b/utils/cs/json.texy @@ -77,7 +77,7 @@ Nastavení `$forceArray` vynutí vrácení polí místo objektů: ```php Json::decode('{"variable": true}'); // vrací objekt typu stdClass -Json::decode('{"variable": true}', forceArray: true); // vrací pole +Json::decode('{"variable": true}', forceArrays: true); // vrací pole ``` Při chybě vyhazuje výjimku `Nette\Utils\JsonException`. @@ -94,4 +94,4 @@ try { Jak odeslat JSON z presenteru? ============================== -Lze k tomu použít metodu `$this->sendJson($data)`, kterou můžeme zavolat třeba v metodě `action*()`, viz [Odeslání odpovědi|application:presenters#Odeslání odpovědi]. +Lze k tomu použít metodu `$this->sendJson($data)`, kterou můžeme zavolat třeba v metodě `action*()`, viz [Odeslání odpovědi |application:presenters#Odeslání odpovědi]. diff --git a/utils/cs/smartobject.texy b/utils/cs/smartobject.texy index 63cc12a241..d90aed3883 100644 --- a/utils/cs/smartobject.texy +++ b/utils/cs/smartobject.texy @@ -1,8 +1,8 @@ -SmartObject a StaticClass -************************* +SmartObject +*********** .[perex] -SmartObject přidává PHP třídám podporu pro tzv. *property*. StaticClass slouží pro označení statických tříd. +SmartObject po léta vylepšoval chování objektů v PHP. Od verze PHP 8.4 jsou již všechny jeho funkce součástí samotného PHP, čímž završil svou historickou misi být průkopníkem moderního objektového přístupu v PHP. Instalace: @@ -11,19 +11,64 @@ Instalace: composer require nette/utils ``` +SmartObject vznikl v roce 2007 jako revoluční řešení nedostatků tehdejšího objektového modelu PHP. V době, kdy PHP trpělo řadou problémů s objektovým návrhem, přinesl výrazné vylepšení a zjednodušení práce pro vývojáře. Stal se legendární součástí frameworku Nette. Nabízel funkcionalitu, kterou PHP získalo až o mnoho let později - od kontrolu přístupu k vlastnostem objektů až po sofistikované syntaktické cukrátka. S příchodem PHP 8.4 završil svou historickou misi, protože všechny jeho funkce se staly nativní součástí jazyka. Předběhl vývoj PHP o pozoruhodných 17 let. -Properties, gettery a settery -============================= +Technicky prošel SmartObject zajímavým vývojem. Původně byl implementován jako třída `Nette\Object`, od které ostatní třídy dědily potřebnou funkcionalitu. Zásadní změna přišla s PHP 5.4, které přineslo podporu trait. To umožnilo transformaci do podoby traity `Nette\SmartObject`, což přineslo větší flexibilitu - vývojáři mohli funkcionalitu využít i ve třídách, které již dědily od jiné třídy. Zatímco původní třída `Nette\Object` zanikla s příchodem PHP 7.2 (které zakázalo pojmenování tříd slovem `Object`), traita `Nette\SmartObject` žije dál. -Termínem *property* (česky vlastnost) se v moderních objektově orientovaných jazycích (např. C#, Python, Ruby, JavaScript) označují [speciální členy tříd|https://en.wikipedia.org/wiki/Property_(programming)], které se tváří jako proměnné, ale ve skutečnosti jsou reprezentovány metodami. Při přiřazení nebo čtení hodnoty této „proměnné“ se zavolá příslušná metoda (tzv. getter nebo setter). Jde o velice šikovnou věc, díky ní máme přístup k proměnným plně pod kontrolou. Můžeme tak validovat vstupy nebo generovat výsledky až ve chvíli, kdy se property čte. +Pojďme si projít vlastnosti, které kdysi `Nette\Object` a později `Nette\SmartObject` nabízeli. Každá z těchto funkcí ve své době představovala významný krok vpřed v oblasti objektově orientovaného programování v PHP. -PHP property nepodporují, ale traita `Nette\SmartObject` je umí imitovat. Jak na to? -- Přidejte třídě anotaci ve tvaru `@property $xyz` -- Vytvořte getter s názvem `getXyz()` nebo `isXyz()`, setter s názvem `setXyz()` -- Getter a setter musí být *public* nebo *protected* a jsou volitelné, mohou tedy existovat *read-only* nebo *write-only* property +Konzistentní chybové stavy +-------------------------- +Jedním z nejpalčivějších problémů raného PHP bylo nekonzistentní chování při práci s objekty. `Nette\Object` přinesl do tohoto chaosu řád a předvídatelnost. Podívejme se, jak vypadalo původní chování PHP: -Property využijeme u třídy Circle, abychom zajistili, že do proměnné `$radius` se dostanou jen nezáporná čísla. Nahradíme `public $radius` za property: +```php +echo $obj->undeclared; // E_NOTICE, později E_WARNING +$obj->undeclared = 1; // projde tiše bez hlášení +$obj->unknownMethod(); // Fatal error (nezachytitelný pomocí try/catch) +``` + +Fatal error ukončil aplikaci bez možnosti jakkoliv reagovat. Tichý zápis do neexistujících členů bez upozornění mohl vést k závažným chybám, které šly obtížné odhalit. `Nette\Object` všechny tyto případy zachytával a vyhazoval výjimku `MemberAccessException`, což umožnilo programátorům na chyby reagovat a řešit je. + +```php +echo $obj->undeclared; // vyhodí Nette\MemberAccessException +$obj->undeclared = 1; // vyhodí Nette\MemberAccessException +$obj->unknownMethod(); // vyhodí Nette\MemberAccessException +``` + +Od PHP 7.0 již jazyk nezpůsobuje nezachytitelné fatal error a od PHP 8.2 je přístup k nedeklarovaným členům považován za chybu. + + +Nápověda "Did you mean?" +------------------------ +`Nette\Object` přišel s velmi příjemnou funkcí: inteligentní nápovědou při překlepech. Když vývojář udělal chybu v názvu metody nebo proměnné, nejen oznámil chybu, ale také nabídl pomocnou ruku v podobě návrhu správného názvu. Tato ikonická hláška, známá jako "did you mean?", ušetřila programátorům hodiny hledání překlepů: + +```php +class Foo extends Nette\Object +{ + public static function from($var) + { + } +} + +$foo = Foo::form($var); +// vyhodí Nette\MemberAccessException +// "Call to undefined static method Foo::form(), did you mean from()?" +``` + +Dnešní PHP sice nemá žádnou podobu „did you mean?“, ale tento dovětek umí do chyb doplňovat [Tracy|tracy:]. A dokonce takové chyby i [samo opravovat |tracy:open-files-in-ide#Ukázky]. + + +Properties s kontrolovaným přístupem +------------------------------------ +Významnou inovací, kterou SmartObject přinesl do PHP, byly properties s kontrolovaným přístupem. Tento koncept, běžný v jazycích jako C# nebo Python, umožnil vývojářům elegantně kontrolovat přístup k datům objektu a zajistit jejich konzistenci. Properties jsou mocným nástrojem objektově orientovaného programování. Fungují jako proměnné, ale ve skutečnosti jsou reprezentovány metodami (gettery a settery). To umožňuje validovat vstupy nebo generovat hodnoty až v momentě čtení. + +Pro používání properties musíte: +- Přidat třídě anotaci ve tvaru `@property $xyz` +- Vytvořit getter s názvem `getXyz()` nebo `isXyz()`, setter s názvem `setXyz()` +- Zajistit, aby getter a setter byly *public* nebo *protected*. Jsou volitelné - mohou tedy existovat jako *read-only* nebo *write-only* property + +Ukažme si praktický příklad na třídě Circle, kde properties využijeme k zajištění, že poloměr bude vždy nezáporné číslo. Nahradíme původní `public $radius` za property: ```php /** @@ -62,79 +107,25 @@ echo $circle->radius; // volá getRadius() echo $circle->visible; // volá isVisible() ``` -Properties jsou především "syntaktickým cukříkem"((syntactic sugar)), jehož smyslem je zpřehlednit kód a osladit tak programátorovi život. Pokud nechcete, nemusíte je používat. - - -Statické třídy -============== - -Statické třídy, tedy třídy, které nejsou určené k vytváření instancí, můžete označit traitou `Nette\StaticClass`: +Od PHP 8.4 lze dosáhnout stejné funkcionality pomocí property hooks, které nabízí mnohem elegantnější a stručnější syntaxi: ```php -class Strings +class Circle { - use Nette\StaticClass; -} -``` - -Při pokusu o vytvoření instance se vyhodí výjimka `Error` s informací, že uvedená třída je statická. - - -Pohled do historie -================== - -SmartObject dříve vylepšovala a opravovala chování tříd v mnoha směrech, ale díky vývoji PHP se většina původních funkcí stala zbytečnou. Následující text je tak pohledem do historie, jak se věci vyvíjely. - -Objektový model PHP trpěl od počátku celou řadou vážných nedostatků a necnostní. To byl důvod vzniku třídy `Nette\Object` (v roce 2007), která se je pokoušela napravovat a zlepšit komfort při používání PHP. Stačilo, aby ostatní třídy od ní dědily, a získaly výhody, které přinášela. Když PHP 5.4 přišlo s podporou trait, byla třída `Nette\Object` nahrazena traitou `Nette\SmartObject`. Nebylo tak nutné už dědit od společného předka. Navíc traita se dala použít i ve třídách, které již dědily od jiné třídy. Definitivní konec `Nette\Object` přišel s vydáním PHP 7.2, které zakázalo třídám jmenovat se `Object`. - -Jak šel vývoj PHP dál, objektový model a schopnosti jazyka se vylepšovaly. Jednotlivé funkce třídy `SmartObject` se stávaly zbytečnými. Od vydání PHP 8.2 zůstala jediná feature, která ještě není v PHP přímo podporována, a to možnost používat tzv. [property|#Properties, gettery a settery]. - -Jaké vlastnosti kdysi `Nette\Object` a potažmo `Nette\Object` nabízely? Nabízíme přehled. (V ukázkách se používá třída `Nette\Object`, ale většina vlastnosti se týká i traity `Nette\SmartObject`). - - -Nekonzistentní chyby --------------------- -PHP mělo nekonzistentní chování při přístupu k nedeklarovaným členům. Stav v době vzniku `Nette\Object` byl následující: - -```php -echo $obj->undeclared; // E_NOTICE, později E_WARNING -$obj->undeclared = 1; // projde tiše bez hlášení -$obj->unknownMethod(); // Fatal error (nezachytitelný pomocí try/catch) -``` - -Fatal error ukončil aplikaci bez možnosti jakkoliv reagovat. Tichý zápis do neexistujících členů bez upozornění mohl vést k závažným chybám, které šly obtížné odhalit. `Nette\Object` všechny tyto případy zachytával a vyhazoval výjimku `MemberAccessException`. - -```php -echo $obj->undeclared; // vyhodí Nette\MemberAccessException -$obj->undeclared = 1; // vyhodí Nette\MemberAccessException -$obj->unknownMethod(); // vyhodí Nette\MemberAccessException -``` -PHP od verze PHP 7.0 už nezachytitelné fatal error nezpůsobuje a přístup k nedeklarovaným členům se stává chybou od PHP 8.2. - - -Did you mean? -------------- -Pokud došlo k vyhození chyby `Nette\MemberAccessException`, třeba z důvodu překlepu při přístupu k proměnné objektu nebo volání metody, pokusilo se `Nette\Object` v chybové hlášce napovědět, jak chybu opravit, a to v podobě ikonického dovětku „did you mean?“. + public float $radius = 0.0 { + set => max(0.0, $value); + } -```php -class Foo extends Nette\Object -{ - public static function from($var) - { + public bool $visible { + get => $this->radius > 0; } } - -$foo = Foo::form($var); -// vyhodí Nette\MemberAccessException -// "Call to undefined static method Foo::form(), did you mean from()?" ``` -Dnešní PHP sice nemá žádnou podobu „did you mean?“, ale tento dovětek umí do chyb doplňovat [Tracy|tracy:]. A dokonce takové chyby i [samo opravovat |tracy:open-files-in-ide#Ukázky]. - Extension methods ----------------- -Inspirací byly extension methods z jazyka C#. Dávaly možnost do existujících tříd přidávat nové metody. Třeba jste si mohli do formuláře přidat metodu `addDateTime()`, která přidá vlastní DateTimePicker. +`Nette\Object` přinesl do PHP další zajímavý koncept inspirovaný moderními programovacími jazyky - extension methods. Tato funkce, převzatá z C#, umožnila vývojářům elegantně rozšiřovat existující třídy o nové metody bez nutnosti je upravovat nebo od nich dědit. Třeba jste si mohli do formuláře přidat metodu `addDateTime()`, která přidá vlastní DateTimePicker: ```php Form::extensionMethod( @@ -146,11 +137,12 @@ $form = new Form; $form->addDateTime('date'); ``` -Extension metody se ukázaly jako nepraktické, protože jejich názvy nenapovídaly editory, naopak hlásily, že metoda neexistuje. Proto byla jejich podpora ukončena. +Extension metody se ukázaly jako nepraktické, protože jejich názvy nenapovídaly editory, naopak hlásily, že metoda neexistuje. Proto byla jejich podpora ukončena. Dnes je běžnější využívat kompozici nebo dědičnost pro rozšíření funkcionality tříd. -Zjištění názvu třídy: ---------------------- +Zjištění názvu třídy +-------------------- +Pro zjištění názvu třídy nabízel SmartObject jednoduchou metodu: ```php $class = $obj->getClass(); // pomocí Nette\Object @@ -158,10 +150,9 @@ $class = $obj::class; // od PHP 8.0 ``` -Přístup k reflexi a anotacem +Přístup k reflexi a anotacím ---------------------------- - -`Nette\Object` nabízel přístup k reflexi a anotacím pomocí metod `getReflection()` a `getAnnotation()`: +`Nette\Object` nabízel přístup k reflexi a anotacím pomocí metod `getReflection()` a `getAnnotation()`. Tento přístup významně zjednodušil práci s metainformacemi tříd: ```php /** @@ -173,10 +164,10 @@ class Foo extends Nette\Object $obj = new Foo; $reflection = $obj->getReflection(); -$reflection->getAnnotation('author'); // vrátí 'John Doe +$reflection->getAnnotation('author'); // vrátí 'John Doe' ``` -Od PHP 8.0 je možné přistupovat k metainformacím v podobě atributů: +Od PHP 8.0 je možné přistupovat k metainformacím v podobě atributů, které nabízí ještě větší možnosti a lepší typovou kontrolu: ```php #[Author('John Doe')] @@ -192,7 +183,6 @@ $reflection->getAttributes(Author::class)[0]; Method gettery -------------- - `Nette\Object` nabízel elegantní způsob, jak předávat metody jako kdyby šlo o proměnné: ```php @@ -209,7 +199,7 @@ $method = $obj->adder; echo $method(2, 3); // 5 ``` -Od PHP 8.1 je možné využít tzv. "first-class callable syntax":https://www.php.net/manual/en/functions.first_class_callable_syntax: +Od PHP 8.1 je možné využít tzv. "first-class callable syntax":https://www.php.net/manual/en/functions.first_class_callable_syntax, která tento koncept posouvá ještě dál: ```php $obj = new Foo; @@ -220,8 +210,7 @@ echo $method(2, 3); // 5 Události -------- - -`Nette\Object` nabízel syntaktický cukr pro vyvolání [události|nette:glossary#události]: +SmartObject nabízí zjednodušenou syntax pro práci s [událostmi |nette:glossary#události]. Události umožňují objektům informovat ostatní části aplikace o změnách svého stavu: ```php class Circle extends Nette\Object @@ -231,12 +220,12 @@ class Circle extends Nette\Object public function setRadius(float $radius): void { $this->onChange($this, $radius); - $this->radius = $radius + $this->radius = $radius; } } ``` -Kód `$this->onChange($this, $radius)` je ekvivalentní následujícímu: +Kód `$this->onChange($this, $radius)` je ekvivalentní následujícímu cyklu: ```php foreach ($this->onChange as $callback) { diff --git a/utils/cs/staticclass.texy b/utils/cs/staticclass.texy new file mode 100644 index 0000000000..ea757f58f9 --- /dev/null +++ b/utils/cs/staticclass.texy @@ -0,0 +1,21 @@ +Statické třídy +************** + +.[perex] +StaticClass slouží pro označení statických tříd. + + +Instalace: + +```shell +composer require nette/utils +``` + +Statické třídy, tedy třídy, které nejsou určené k vytváření instancí, můžete označit traitou [api:Nette\StaticClass]: + +```php +class Strings +{ + use Nette\StaticClass; +} +``` diff --git a/utils/cs/strings.texy b/utils/cs/strings.texy index 18eb2fcd7b..83d63a73d2 100644 --- a/utils/cs/strings.texy +++ b/utils/cs/strings.texy @@ -104,8 +104,8 @@ $platformLines = Strings::platformNewLines($string); ``` -webalize(string $s, string $charlist=null, bool $lower=true): string .[method] ------------------------------------------------------------------------------- +webalize(string $s, ?string $charlist=null, bool $lower=true): string .[method] +------------------------------------------------------------------------------- Upraví UTF-8 řetězec do tvaru používaného v URL, tj. odstraní diakritiku a všechny znaky, kromě písmen anglické abecedy a číslic, nahradí spojovníkem. @@ -129,8 +129,8 @@ Strings::webalize('Dobrý den', null, false); // 'Dobry-den' Vyžaduje PHP rozšíření `intl`. -trim(string $s, string $charlist=null): string .[method] --------------------------------------------------------- +trim(string $s, ?string $charlist=null): string .[method] +--------------------------------------------------------- Ořízne mezery (nebo jiné znaky určené druhým parametrem) ze začátku a konce UTF-8 řetězce. @@ -186,8 +186,8 @@ Strings::padRight('Nette', 8, '+*'); // 'Nette+*+' ``` -substring(string $s, int $start, int $length=null): string .[method] --------------------------------------------------------------------- +substring(string $s, int $start, ?int $length=null): string .[method] +--------------------------------------------------------------------- Vrátí část UTF-8 řetězce `$s` zadanou počáteční pozicí `$start` a délkou `$length`. Pokud je `$start` záporný, bude vrácený řetězec začínat znakem -`$start` znakem od konce. @@ -266,8 +266,8 @@ Strings::contains($haystack, $needle); // true Používejte nativní `str_contains()`:https://www.php.net/manual/en/function.str-contains.php. -compare(string $left, string $right, int $length=null): bool .[method] ----------------------------------------------------------------------- +compare(string $left, string $right, ?int $length=null): bool .[method] +----------------------------------------------------------------------- Porovnání dvou UTF-8 řetězců nebo jejich částí bez ohledu na velikost písmen. Pokud `$length` obsahuje null, porovnávají se celé řetězce, pokud je záporný, porovnává se příslušný počet znaků od konce řetězců, jinak se porovnává příslušný počet znaků od začátku. @@ -483,8 +483,8 @@ Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true, utf8: true); ``` -matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false, bool $lazy=false): array|Generator .[method] +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Hledá v řetězci všechny výskyty odpovídající regulárnímu výrazu a vrátí pole polí s nalezeným výrazem a jednotlivými podvýrazy. @@ -550,6 +550,16 @@ Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true, utf8: true); ] */ ``` +Pokud `$lazy` je `true`, funkce vrací `Generator` místo pole, což přináší významné výkonnostní výhody při práci s velkými řetězci. Generátor umožňuje vyhledávat shody postupně, místo celého řetězce najednou. To umožňuje efektivně pracovat i s extrémně velkými vstupními texty. Navíc můžete kdykoliv přerušit zpracování, pokud najdete hledanou shodu, což šetří výpočetní čas. + +```php +$matches = Strings::matchAll($largeText, '~\w+~', lazy: true); +foreach ($matches as $match) { + echo "Nalezeno: $match[0]\n"; + // Zpracování může být kdykoli přerušeno +} +``` + replace(string $subject, string|array $pattern, string|callable $replacement='', int $limit=-1, bool $captureOffset=false, bool $unmatchedAsNull=false, bool $utf8=false): string .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/utils/cs/type.texy b/utils/cs/type.texy index d4c539ee67..f2af19e5f8 100644 --- a/utils/cs/type.texy +++ b/utils/cs/type.texy @@ -2,8 +2,13 @@ PHP Typ ******* .[perex] -[api:Nette\Utils\Type] je třída pro práci s datovými typy PHP. +[api:Nette\Utils\Type] reprezentuje datový typ PHP. Slouží k analýze, porovnávání a manipulaci s typy, ať už pocházejí z řetězce nebo z reflexe. +PHP má dnes velmi bohatý typový systém: od skalárních typů (`int`, `string`) přes objekty a rozhraní až po složené typy (union `A|B`, intersection `A&B` nebo disjunktivní normální formy `(A&B)|D`). Navíc existují speciální typy jako `void`, `never`, `mixed` nebo relativní `self` či `static`. + +Práce s těmito typy nativně, zejména přes `ReflectionType`, je často zdlouhavá, protože musíte rekurzivně rozlišovat mezi `ReflectionNamedType`, `ReflectionUnionType` a dalšími objekty. Třída `Nette\Utils\Type` toto vše zapouzdřuje a poskytuje **jednotné a srozumitelné API** pro práci s jakýmkoliv typem, který PHP podporuje. + +Umožňuje například snadno zjistit, zda jeden typ [akceptuje|#allows] druhý (kompatibilita), [rozšiřovat typy|#with] nebo převádět reflexe na čitelný zápis. Instalace: @@ -45,6 +50,25 @@ echo $type; // 'Foo|Bar' ``` +fromValue(mixed $value): Type .[method]{data-version:4.0.10} +------------------------------------------------------------ + +Statická metoda, která vytvoří objekt Type podle typu předané hodnoty. + +```php +$type = Type::fromValue('hello'); // 'string' +$type = Type::fromValue(123); // 'int' +$type = Type::fromValue(new stdClass); // 'stdClass' +``` + +Pro resources vrací `mixed`, protože PHP typ `resource` nezná. U anonymních tříd vrací nejbližšího předka nebo typ `object`. + +```php +$obj = new class extends Foo { }; +$type = Type::fromValue($obj); // 'Foo' +``` + + getNames(): (string|array)[] .[method] -------------------------------------- @@ -183,8 +207,8 @@ $type->isClassKeyword(); // false ``` -allows(string $type): bool .[method] ------------------------------------- +allows(string|Type $type): bool .[method] +----------------------------------------- Metoda `allows()` ověřuje kompatibilitu typů. Například umožní zjistit, jestli hodnota určitého typu by mohla být předaná jako parametr. @@ -197,3 +221,24 @@ $type->allows('Foo'); // false $type = Type::fromString('mixed'); $type->allows('null'); // true ``` + + +with(string|Type $type): Type .[method]{data-version:4.0.10} +------------------------------------------------------------ + +Vrací objekt Type, který akceptuje jak původní typ, tak i nově přidaný. Vytváří tzv. union type. + +Metoda je chytrá a typy zbytečně nezdvojuje. Pokud přidáte typ, který je již obsažen, nebo je nadmnožinou stávajícího typu (např. přidání `mixed` k `string`), vrátí se zjednodušený výsledek. + +```php +$type = Type::fromString('string'); + +// Rozšíření na nullable string +echo $type->with('null'); // '?string' + +// Vytvoření union typu +echo $type->with('int'); // 'string|int' + +// Přidání typu, který "přebije" vše ostatní +echo $type->with('mixed'); // 'mixed' +``` diff --git a/utils/cs/upgrading.texy b/utils/cs/upgrading.texy new file mode 100644 index 0000000000..e69260c8f7 --- /dev/null +++ b/utils/cs/upgrading.texy @@ -0,0 +1,34 @@ +Upgrade +******* + + +Přechod z verze 3.x na 4.0 +========================== + +Minimální požadovaná verze PHP je 8.0. + +Třída `Nette\Utils\Reflection` poskytovala metody pro práci s typy `getParameterType()`, `getPropertyType()` a `getReturnType()`. Metody vznikly v době, kdy PHP nemělo union, intersection nebo nejnovější disjunctive normal form typy, se kterými už nefungují a nahradila je [třída Type |utils:type]. Od verze 4.0 jsou tyto metody odstraněné. + +Metoda `Nette\Utils\Reflection::getParameterDefaultValue()` je deprecated, protože nativní `ReflectionParameter::getDefaultValue()` už funguje správně. + +Zrušená je proměnná `Nette\Utils\Html::$xhtml`. + + +Finder +------ + +Finder se přestěhoval do balíčku `nette/utils`, původní odstraňte: + +```shell +composer remove nette/finder +``` + +Na Linuxu se nově chová v režimu case-sensitive. + +V předchozí verzi platilo, že metody `exclude()` a `filter()` fungovaly jinak, když byly zavolány **před** `from()` resp. `in()` a **po** ní. Tohle už neplatí, `exclude()` a `filter()` fungují vždy stejně. Dřívější `filter()` volaný *až po* nahradila nová metoda `descentFilter()`. + +Finder již neimplementuje rozhraní Countable. + +Řetězec začínající lomítkem ve `Finder::findFiles('/f*')` se nově považuje za absolutní cestu, je potřeba ho nahradit např. za `Finder::findFiles('./f*')`. + +Pokud adresář, ve kterém hledáte, neexistuje, vyhodí se `Nette\InvalidStateException` (místo `UnexpectedValueException`). diff --git a/utils/cs/validators.texy b/utils/cs/validators.texy index f29e003c3f..4b9b1da4f4 100644 --- a/utils/cs/validators.texy +++ b/utils/cs/validators.texy @@ -21,7 +21,7 @@ use Nette\Utils\Validators; Základní použití ================ -Třída disponuje řadou metod pro kontrolu hodnot, jako třeba [#isList()], [#isUnicode()], [#isEmail()], [#isUrl()] atd. pro využití ve vašem kódu: +Třída disponuje řadou metod pro kontrolu hodnot, jako třeba [#isUnicode()], [#isEmail()], [#isUrl()] atd. pro využití ve vašem kódu: ```php if (!Validators::isEmail($email)) { @@ -29,7 +29,7 @@ if (!Validators::isEmail($email)) { } ``` -Dále umí oveřit, zda hodnota je tzv. [očekávaný typy|#Očekávané typy], což je řetězec, kde se jednotlivé možnosti oddělují svislítkem `|`. Můžeme tak snadno oveřit více typů pomocí [#if()]: +Dále umí oveřit, zda hodnota je tzv. [očekávaný typy |#Očekávané typy], což je řetězec, kde se jednotlivé možnosti oddělují svislítkem `|`. Můžeme tak snadno oveřit více typů pomocí [#is()]: ```php if (!Validators::is($val, 'int|string|bool')) { @@ -71,7 +71,7 @@ Přehled typů a pravidel: |-------------------------- | pseudo-typy || |------------------------------------------------ -| `list` | [indexované pole |#isList], lze uvést rozsah pro počet prvků +| `list` | indexované pole, lze uvést rozsah pro počet prvků | `none` | prázdná hodnota: `''`, `null`, `false` | `number` | int\|float | `numeric` | [číslo včetně textové reprezentace |#isNumeric] @@ -111,7 +111,7 @@ Asserce assert($value, string $expected, string $label='variable'): void .[method] -------------------------------------------------------------------------- -Ověřuje, že hodnota je jedním z [očekávaných typů|#Očekávané typy] oddělených svislítkem. Pokud ne, vyhodí výjimku [api:Nette\Utils\AssertionException]. Slovo `variable` v textu výjimky lze nahradit za jiné parametrem `$label`. +Ověřuje, že hodnota je jedním z [očekávaných typů |#Očekávané typy] oddělených svislítkem. Pokud ne, vyhodí výjimku [api:Nette\Utils\AssertionException]. Slovo `variable` v textu výjimky lze nahradit za jiné parametrem `$label`. ```php Validators::assert('Nette', 'string:5'); // OK @@ -120,10 +120,10 @@ Validators::assert('Lorem ipsum dolor sit', 'string:78'); ``` -assertField(array $array, string|int $key, string $expected=null, string $label=null): void .[method] ------------------------------------------------------------------------------------------------------ +assertField(array $array, string|int $key, ?string $expected=null, ?string $label=null): void .[method] +------------------------------------------------------------------------------------------------------- -Ověřuje, zda prvek pod klíčem `$key` v poli `$array` je jedním z [očekávaných typů|#Očekávané typy] oddělených svislítkem. Pokud ne, vyhodí výjimku [api:Nette\Utils\AssertionException]. Řetězec `item '%' in array` v textu výjimky lze nahradit za jiný parametrem `$label`. +Ověřuje, zda prvek pod klíčem `$key` v poli `$array` je jedním z [očekávaných typů |#Očekávané typy] oddělených svislítkem. Pokud ne, vyhodí výjimku [api:Nette\Utils\AssertionException]. Řetězec `item '%' in array` v textu výjimky lze nahradit za jiný parametrem `$label`. ```php $arr = ['foo' => 'Nette']; @@ -143,7 +143,7 @@ Validátory is($value, string $expected): bool .[method] -------------------------------------------- -Ověří, zda hodnota je jedním z [očekávaných typů|#Očekávané typy] oddělených svislítkem. +Ověří, zda hodnota je jedním z [očekávaných typů |#Očekávané typy] oddělených svislítkem. ```php Validators::is(1, 'int|float'); // true diff --git a/utils/de/@home.texy b/utils/de/@home.texy index a0eb54a00f..8597b252ef 100644 --- a/utils/de/@home.texy +++ b/utils/de/@home.texy @@ -1,42 +1,46 @@ -Dienstprogramme -*************** +Nette Utils +*********** .[perex] -Im Paket `nette/utils` finden Sie eine Reihe von nützlichen Klassen für den täglichen Gebrauch: - -| [Arrays] | Nette\Utils\Arrays -| [Bilder |Images] | Nette\Utils\Image +Im Paket `nette/utils` finden Sie eine Reihe nützlicher Klassen für den täglichen Gebrauch: + +| [Callback |Callback] | Nette\Utils\Callback +| [Datum und Zeit |datetime] | Nette\Utils\DateTime +| [Finder |Finder] | Nette\Utils\Finder +| [HTML-Elemente |html-elements] | Nette\Utils\Html +| [Iteratoren |iterables] | Nette\Utils\Iterables +| [JSON |json] | Nette\Utils\Json +| [Zufällige Zeichenketten |random] | Nette\Utils\Random +| [Bilder |images] | Nette\Utils\Image +| [PHP-Reflexion |reflection] | Nette\Utils\Reflection +| [PHP-Typen |type] | Nette\Utils\Type +| [Arrays |arrays] | Nette\Utils\Arrays +| [Hilfsfunktionen |helpers] | Nette\Utils\Helpers +| [Vergleich von Floats |floats] | Nette\Utils\Floats +| [Zeichenketten |strings] | Nette\Utils\Strings | [Dateisystem |filesystem] | Nette\Utils\FileSystem -| [Datum und Uhrzeit |datetime] | Nette\Utils\DateTime -| [Finder] | Nette\Utils\Finder -| [Floats vergleichen |floats] | Nette\Utils\Floats -| [Generieren von HTML-Code |html-elements] | Nette\Utils\Html -| [Helferfunktionen |helpers] | Nette\Utils\Helfer -| [JSON] | Nette\Utils\Json -| [Objektmodell |smartobject] | Nette\SmartObject & Nette\StaticClass -| [Paginator] | Nette\Utils\Paginator -| [PHP-Reflexion |reflection] | Nette\Utils\Reflexion -| [Rückruf |Callback] | Nette\Utils\Callback -| [Typen |type] | Nette\Utils\Type -| [Validatoren |Validators] | Nette\Utils\Validatoren -| [Zeichenketten |Strings] | Nette\Utils\Strings -| [Zufällige Zeichenketten generieren |random] | Nette\Utils\Random +| [Paginierung |paginator] | Nette\Utils\Paginator +| [SmartObject |SmartObject] & [StaticClass |StaticClass] | Nette\SmartObject & Nette\StaticClass +| [Validator |validators] | Nette\Utils\Validators Installation ------------ -Laden Sie das Paket herunter und installieren Sie es mit [Composer |best-practices:composer]: +Laden Sie die Bibliothek herunter und installieren Sie sie mit [Composer|best-practices:composer]: ```shell composer require nette/utils ``` -| Version | kompatibel mit PHP -|-----------|------------------- -| Nette Utils 4.0 | PHP 8.0 - 8.2 -| Nette Utils 3.2 | PHP 7.2 - 8.2 -| Nette Utils 3.0 - 3.1 | PHP 7.1 - 8.0 -| Nette Utils 2.5 | PHP 5.6 - 8.0 +| Version | Kompatibel mit PHP +|--------------|-------------------- +| Nette Utils 4.0 | PHP 8.0 – 8.4 +| Nette Utils 3.2 | PHP 7.2 – 8.3 +| Nette Utils 3.0 – 3.1 | PHP 7.1 – 8.0 +| Nette Utils 2.5 | PHP 5.6 – 8.0 + +Gilt für die letzte Patch-Version. + -Gilt für die neuesten Patch-Versionen. +Wenn Sie das Paket auf eine neuere Version aktualisieren, lesen Sie die Seite [Upgrade|en:upgrading]. diff --git a/utils/de/@left-menu.texy b/utils/de/@left-menu.texy index f820e0f6ce..122f792574 100644 --- a/utils/de/@left-menu.texy +++ b/utils/de/@left-menu.texy @@ -1,26 +1,28 @@ -Paket nette/utils -***************** -- [Arrays] -- [Bilder|Images] -- [Callback] -- [Dateisystem |filesystem] -- [Datum und Uhrzeit |datetime] -- [Finder] -- [Floats] -- [Hilfsfunktionen |helpers] -- [HTML-Elemente |HTML Elements] -- [JSON] +Nette Utils +*********** +- [Callbacks |callback] +- [Datum und Zeit |datetime] +- [Finder |Finder] +- [Floats |Floats] +- [HTML-Elemente |html-elements] +- [Iteratoren |iterables] +- [JSON |JSON] +- [Zufällige Zeichenketten |random] +- [Bilder |images] - [Paginator |paginator] - [PHP-Reflexion |reflection] -- [PHP Typen |type] -- [SmartObject] -- [Strings] -- [Validatoren |validators] -- [Zufällige Zeichenketten |random] +- [PHP-Typen |type] +- [Arrays |arrays] +- [Hilfsfunktionen |helpers] +- [Zeichenketten |strings] +- [SmartObject |SmartObject] +- [StaticClass |StaticClass] +- [Dateisystem |filesystem] +- [Validator |validators] -Andere Hilfsprogramme -********************* -- [NEON |neon:] +Weitere Werkzeuge +***************** +- [NEON|neon:] - [Passwort-Hashing |security:passwords] -- [URL Parser und Builder |http:urls] +- [Parsen und Zusammensetzen von URLs |http:urls] diff --git a/utils/de/@meta.texy b/utils/de/@meta.texy new file mode 100644 index 0000000000..b3b806b2ca --- /dev/null +++ b/utils/de/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokumentation}} diff --git a/utils/de/arrays.texy b/utils/de/arrays.texy index 7aea115b2e..472f4b1c8e 100644 --- a/utils/de/arrays.texy +++ b/utils/de/arrays.texy @@ -1,8 +1,8 @@ -Array-Funktionen -**************** +Arbeiten mit Arrays +******************* .[perex] -Diese Seite befasst sich mit den Klassen [Nette\Utils\Arrays |#Arrays], [ArrayHash |#ArrayHash] und [ArrayList |#ArrayList], die mit Arrays zu tun haben. +Diese Seite widmet sich den Klassen [Nette\Utils\Arrays |#Arrays], [#ArrayHash] und [#ArrayList], die sich auf Arrays beziehen. Installation: @@ -12,22 +12,63 @@ composer require nette/utils ``` -Arrays .[#toc-arrays] -===================== +Arrays +====== -[api:Nette\Utils\Arrays] ist eine statische Klasse, die eine Handvoll praktischer Array-Funktionen enthält. +[api:Nette\Utils\Arrays] ist eine statische Klasse, die nützliche Funktionen für die Arbeit mit Arrays enthält. Ihr Analogon für Iteratoren ist [Nette\Utils\Iterables |iterables]. -Die folgenden Beispiele setzen voraus, dass der folgende Klassenalias definiert ist: +Die folgenden Beispiele setzen voraus, dass ein Alias erstellt wurde: ```php use Nette\Utils\Arrays; ``` +associate(array $array, mixed $path): array|\stdClass .[method] +--------------------------------------------------------------- + +Die Funktion transformiert das Array `$array` flexibel in ein assoziatives Array oder Objekte gemäß dem angegebenen Pfad `$path`. Der Pfad kann ein String oder ein Array sein. Er besteht aus den Schlüsselnamen des Eingabearrays und Operatoren wie '[]', '->', '=', und '|'. Wirft `Nette\InvalidArgumentException`, wenn der Pfad ungültig ist. + +```php +// Umwandlung in ein assoziatives Array nach einem einfachen Schlüssel +$arr = [ + ['name' => 'John', 'age' => 11], + ['name' => 'Mary', 'age' => null], + // ... +]; +$result = Arrays::associate($arr, 'name'); +// $result = ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// Zuweisung von Werten von einem Schlüssel zu einem anderen unter Verwendung des Operators = +$result = Arrays::associate($arr, 'name=age'); // oder ['name', '=', 'age'] +// $result = ['John' => 11, 'Mary' => null, ...] +``` + +```php +// Erstellung eines Objekts unter Verwendung des Operators -> +$result = Arrays::associate($arr, '->name'); // oder ['->', 'name'] +// $result = (object) ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// Kombination von Schlüsseln unter Verwendung des Operators | +$result = Arrays::associate($arr, 'name|age'); // oder ['name', '|', 'age'] +// $result: ['John' => ['name' => 'John', 'age' => 11], 'Paul' => ['name' => 'Paul', 'age' => 44]] +``` + +```php +// Hinzufügen zum Array unter Verwendung von [] +$result = Arrays::associate($arr, 'name[]'); // oder ['name', '[]'] +// $result: ['John' => [['name' => 'John', 'age' => 22], ['name' => 'John', 'age' => 11]]] +``` + + contains(array $array, $value): bool .[method] ---------------------------------------------- -Prüft ein Array auf das Vorhandensein eines Wertes. Verwendet einen strengen Vergleich (`===`) +Testet ein Array auf die Anwesenheit eines Wertes. Verwendet einen strikten Vergleich (`===`). ```php Arrays::contains([1, 2, 3], 1); // true @@ -35,10 +76,10 @@ Arrays::contains(['1', false], 1); // false ``` -every(iterable $array, callable $callback): bool .[method] ----------------------------------------------------------- +every(array $array, callable $predicate): bool .[method] +-------------------------------------------------------- -Prüft, ob alle Elemente im Array den Test bestehen, der von der angegebenen Funktion mit der Signatur `function ($value, $key, array $array): bool` implementiert wird. +Testet, ob alle Elemente im Array den Test bestehen, der in `$predicate` mit der Signatur `function ($value, $key, array $array): bool` implementiert ist. ```php $array = [1, 30, 39, 29, 10, 13]; @@ -46,24 +87,59 @@ $isBelowThreshold = fn($value) => $value < 40; $res = Arrays::every($array, $isBelowThreshold); // true ``` -Siehe [some() |#some()]. +Siehe [#some()]. -first(array $array): mixed .[method] ------------------------------------- +filter(array $array, callable $predicate): array .[method]{data-version:4.0.4} +------------------------------------------------------------------------------ -Gibt das erste Element aus dem Array zurück oder null, wenn Array leer ist. Im Gegensatz zu `reset()` wird der interne Zeiger nicht verändert. +Gibt ein neues Array zurück, das alle Schlüssel-Wert-Paare enthält, die dem angegebenen Prädikat entsprechen. Der Callback hat die Signatur `function ($value, int|string $key, array $array): bool`. ```php -Arrays::first([1, 2, 3]); // 1 -Arrays::first([]); // null +Arrays::filter( + ['a' => 1, 'b' => 2, 'c' => 3], + fn($v) => $v < 3, +); +// ['a' => 1, 'b' => 2] ``` +first(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------- + +Gibt das erste Element zurück (das dem Prädikat entspricht, falls angegeben). Wenn kein solches Element existiert, gibt es das Ergebnis des Aufrufs von `$else` oder null zurück. Der Parameter `$predicate` hat die Signatur `function ($value, int|string $key, array $array): bool`. + +Ändert den internen Zeiger im Gegensatz zu `reset()` nicht. Die Parameter `$predicate` und `$else` existieren seit Version 4.0.4. + +```php +Arrays::first([1, 2, 3]); // 1 +Arrays::first([1, 2, 3], fn($v) => $v > 2); // 3 +Arrays::first([]); // null +Arrays::first([], else: fn() => false); // false +``` + +Siehe [#last()]. + + +firstKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +------------------------------------------------------------------------------------------------ + +Gibt den Schlüssel des ersten Elements zurück (das dem Prädikat entspricht, falls angegeben) oder null, wenn kein solches Element existiert. Das Prädikat `$predicate` hat die Signatur `function ($value, int|string $key, array $array): bool`. + +```php +Arrays::firstKey([1, 2, 3]); // 0 +Arrays::firstKey([1, 2, 3], fn($v) => $v > 2); // 2 +Arrays::firstKey(['a' => 1, 'b' => 2]); // 'a' +Arrays::firstKey([]); // null +``` + +Siehe [#lastKey()]. + + flatten(array $array, bool $preserveKeys=false): array .[method] ---------------------------------------------------------------- -Wandelt ein mehrdimensionales Array in ein flaches Array um. +Vereinigt ein mehrdimensionales Array zu einem flachen Array. ```php $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); @@ -71,62 +147,62 @@ $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); ``` -get(array $array, string|int|array $key, mixed $default=null): mixed .[method] ------------------------------------------------------------------------------- +get(array $array, string|int|array $key, ?mixed $default=null): mixed .[method] +------------------------------------------------------------------------------- -Gibt `$array[$key]` Element. Wenn es nicht existiert, wird `Nette\InvalidArgumentException` ausgelöst, es sei denn, ein Standardwert wird als drittes Argument angegeben. +Gibt das Element `$array[$key]` zurück. Wenn es nicht existiert, wird entweder eine Ausnahme `Nette\InvalidArgumentException` geworfen oder, wenn der dritte Parameter `$default` angegeben ist, dieser zurückgegeben. ```php -// wenn $array['foo'] nicht existiert, wird eine Ausnahme ausgelöst +// wenn $array['foo'] nicht existiert, wird eine Ausnahme geworfen $value = Arrays::get($array, 'foo'); -// wenn $array['foo'] nicht existiert, gibt 'bar' zurück +// wenn $array['foo'] nicht existiert, wird 'bar' zurückgegeben $value = Arrays::get($array, 'foo', 'bar'); ``` -Das Argument `$key` kann ebenso gut ein Array sein. +Der Schlüssel `$key` kann auch ein Array sein. ```php $array = ['color' => ['favorite' => 'red'], 5]; $value = Arrays::get($array, ['color', 'favorite']); -// returns 'red' +// gibt 'red' zurück ``` getRef(array &$array, string|int|array $key): mixed .[method] ------------------------------------------------------------- -Ruft eine Referenz auf die angegebene `$array[$key]`. Wenn der Index nicht existiert, wird ein neuer Index mit dem Wert `null` erstellt. +Gibt eine Referenz auf das angegebene Array-Element zurück. Wenn das Element nicht existiert, wird es mit dem Wert null erstellt. ```php $valueRef = & Arrays::getRef($array, 'foo'); -// returns $array['foo'] reference +// gibt eine Referenz auf $array['foo'] zurück ``` -Funktioniert sowohl mit mehrdimensionalen Arrays als auch mit [get() |#get()]. +Wie die Funktion [#get()] kann sie auch mit mehrdimensionalen Arrays arbeiten. ```php -$value = & Arrays::get($array, ['color', 'favorite']); -// returns $array['color']['favorite'] reference +$value = & Arrays::getRef($array, ['color', 'favorite']); +// gibt eine Referenz auf $array['color']['favorite'] zurück ``` grep(array $array, string $pattern, bool $invert=false): array .[method] ------------------------------------------------------------------------ -Gibt nur die Array-Elemente zurück, die einem regulären Ausdruck `$pattern` entsprechen. Wenn `$invert` gleich `true` ist, werden Elemente zurückgegeben, die nicht übereinstimmen. Ein Regex-Kompilierungs- oder Laufzeitfehler führt zu `Nette\RegexpException`. +Gibt nur die Array-Elemente zurück, deren Wert dem regulären Ausdruck `$pattern` entspricht. Wenn `$invert` `true` ist, werden umgekehrt die Elemente zurückgegeben, die nicht entsprechen. Ein Fehler bei der Kompilierung oder Verarbeitung des Ausdrucks wirft eine Ausnahme `Nette\RegexpException`. ```php $filteredArray = Arrays::grep($array, '~^\d+$~'); -// gibt nur numerische Elemente zurück +// gibt nur Array-Elemente zurück, die aus Ziffern bestehen ``` insertAfter(array &$array, string|int|null $key, array $inserted): void .[method] --------------------------------------------------------------------------------- -Fügt den Inhalt des Arrays `$inserted` in `$array` unmittelbar nach `$key` ein. Wenn `$key` `null` ist (oder nicht existiert), wird es am Ende eingefügt. +Fügt den Inhalt des Arrays `$inserted` in das Array `$array` direkt nach dem Element mit dem Schlüssel `$key` ein. Wenn `$key` `null` ist (oder nicht im Array vorhanden ist), wird es am Ende eingefügt. ```php $array = ['first' => 10, 'second' => 20]; @@ -138,7 +214,7 @@ Arrays::insertAfter($array, 'first', ['hello' => 'world']); insertBefore(array &$array, string|int|null $key, array $inserted): void .[method] ---------------------------------------------------------------------------------- -Fügt den Inhalt des Arrays `$inserted` in `$array` vor `$key` ein. Wenn `$key` gleich `null` ist (oder nicht existiert), wird er am Anfang eingefügt. +Fügt den Inhalt des Arrays `$inserted` in das Array `$array` vor dem Element mit dem Schlüssel `$key` ein. Wenn `$key` `null` ist (oder nicht im Array vorhanden ist), wird es am Anfang eingefügt. ```php $array = ['first' => 10, 'second' => 20]; @@ -150,7 +226,7 @@ Arrays::insertBefore($array, 'first', ['hello' => 'world']); invoke(iterable $callbacks, ...$args): array .[method] ------------------------------------------------------ -Ruft alle Callbacks auf und gibt ein Array mit den Ergebnissen zurück. +Ruft alle Callbacks auf und gibt ein Array von Ergebnissen zurück. ```php $callbacks = [ @@ -166,7 +242,7 @@ $array = Arrays::invoke($callbacks, 5, 11); invokeMethod(iterable $objects, string $method, ...$args): array .[method] -------------------------------------------------------------------------- -Ruft die Methode für jedes Objekt in einem Array auf und gibt ein Array mit Ergebnissen zurück. +Ruft eine Methode auf jedem Objekt im Array auf und gibt ein Array von Ergebnissen zurück. ```php $objects = ['a' => $obj1, 'b' => $obj2]; @@ -179,7 +255,7 @@ $array = Arrays::invokeMethod($objects, 'foo', 1, 2); isList(array $array): bool .[method] ------------------------------------ -Prüft, ob das Array in aufsteigender Reihenfolge der numerischen Schlüssel von Null an indiziert ist, d.h. eine Liste. +Überprüft, ob das Array nach einer aufsteigenden Reihe numerischer Schlüssel von Null an indiziert ist, a.k.a. eine Liste. ```php Arrays::isList(['a', 'b', 'c']); // true @@ -188,21 +264,42 @@ Arrays::isList(['a' => 1, 'b' => 2]); // false ``` -last(array $array): mixed .[method] ------------------------------------ +last(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------ -Gibt das letzte Element des Arrays zurück oder null, wenn das Array leer ist. Im Gegensatz zu `end()` wird der interne Zeiger nicht verändert. +Gibt das letzte Element zurück (das dem Prädikat entspricht, falls angegeben). Wenn kein solches Element existiert, gibt es das Ergebnis des Aufrufs von `$else` oder null zurück. Der Parameter `$predicate` hat die Signatur `function ($value, int|string $key, array $array): bool`. + +Ändert den internen Zeiger im Gegensatz zu `end()` nicht. Die Parameter `$predicate` und `$else` existieren seit Version 4.0.4. ```php -Arrays::last([1, 2, 3]); // 3 -Arrays::last([]); // null +Arrays::last([1, 2, 3]); // 3 +Arrays::last([1, 2, 3], fn($v) => $v < 3); // 2 +Arrays::last([]); // null +Arrays::last([], else: fn() => false); // false ``` +Siehe [#first()]. + -map(iterable $array, callable $callback): array .[method] +lastKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +----------------------------------------------------------------------------------------------- + +Gibt den Schlüssel des letzten Elements zurück (das dem Prädikat entspricht, falls angegeben) oder null, wenn kein solches Element existiert. Das Prädikat `$predicate` hat die Signatur `function ($value, int|string $key, array $array): bool`. + +```php +Arrays::lastKey([1, 2, 3]); // 2 +Arrays::lastKey([1, 2, 3], fn($v) => $v < 3); // 1 +Arrays::lastKey(['a' => 1, 'b' => 2]); // 'b' +Arrays::lastKey([]); // null +``` + +Siehe [#firstKey()]. + + +map(array $array, callable $transformer): array .[method] --------------------------------------------------------- -Ruft `$callback` für alle Elemente im Array auf und gibt das Array der Rückgabewerte zurück. Der Callback hat die Signatur `function ($value, $key, array $array): bool`. +Ruft `$transformer` auf alle Elemente im Array auf und gibt ein Array der zurückgegebenen Werte zurück. Der Callback hat die Signatur `function ($value, $key, array $array): mixed`. ```php $array = ['foo', 'bar', 'baz']; @@ -211,10 +308,24 @@ $res = Arrays::map($array, fn($value) => $value . $value); ``` +mapWithKeys(array $array, callable $transformer): array .[method] +----------------------------------------------------------------- + +Erzeugt ein neues Array durch Transformation der Werte und Schlüssel des ursprünglichen Arrays. Die Funktion `$transformer` hat die Signatur `function ($value, $key, array $array): ?array{$newKey, $newValue}`. Wenn `$transformer` `null` zurückgibt, wird das Element übersprungen. Für beibehaltene Elemente wird das erste Element des zurückgegebenen Arrays als neuer Schlüssel und das zweite Element als neuer Wert verwendet. + +```php +$array = ['a' => 1, 'b' => 2]; +$result = Arrays::mapWithKeys($array, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +Diese Methode ist nützlich in Situationen, in denen Sie die Struktur eines Arrays ändern müssen (Schlüssel und Werte gleichzeitig) oder Elemente während der Transformation filtern müssen (indem Sie null für unerwünschte Elemente zurückgeben). + + mergeTree(array $array1, array $array2): array .[method] -------------------------------------------------------- -Führt zwei Felder rekursiv zusammen. Er ist z.B. nützlich, um Baumstrukturen zusammenzuführen. Er verhält sich wie der `+`-Operator für Array, d.h. er fügt ein Schlüssel/Wert-Paar aus dem zweiten Array zum ersten hinzu und behält den Wert aus dem ersten Array im Falle einer Schlüsselkollision bei. +Führt zwei Arrays rekursiv zusammen. Dies ist beispielsweise nützlich zum Zusammenführen von Baumstrukturen. Beim Zusammenführen gelten die gleichen Regeln wie für den Operator `+`, der auf Arrays angewendet wird, d.h. dem ersten Array werden Schlüssel/Wert-Paare aus dem zweiten Array hinzugefügt, und im Falle von Schlüsselkollisionen wird der Wert aus dem ersten Array beibehalten. ```php $array1 = ['color' => ['favorite' => 'red'], 5]; @@ -224,13 +335,13 @@ $array = Arrays::mergeTree($array1, $array2); // $array = ['color' => ['favorite' => 'red', 'blue'], 5]; ``` -Werte aus dem zweiten Array werden immer an das erste angehängt. Das Verschwinden des Wertes `10` aus dem zweiten Array mag ein wenig verwirrend erscheinen. Es sollte beachtet werden, dass dieser Wert sowie der Wert `5` in the first array have the same numeric key `0`, also im resultierenden Feld nur ein Element aus dem ersten Array vorhanden ist. +Werte aus dem zweiten Array werden immer am Ende des ersten hinzugefügt. Etwas verwirrend mag das Verschwinden des Wertes `10` aus dem zweiten Array erscheinen. Es ist zu beachten, dass dieser Wert, ebenso wie der Wert `5` im ersten Array, denselben numerischen Schlüssel `0` zugewiesen bekommen hat, weshalb im resultierenden Array nur das Element aus dem ersten Array vorhanden ist. -normalize(array $array, string $filling=null): array .[method] --------------------------------------------------------------- +normalize(array $array, ?string $filling=null): array .[method] +--------------------------------------------------------------- -Normalisiert ein Array zu einem assoziativen Array. Ersetzen Sie numerische Schlüssel durch ihre Werte, der neue Wert wird `$filling` sein. +Normalisiert ein Array zu einem assoziativen Array. Numerische Schlüssel werden durch ihre Werte ersetzt, der neue Wert wird `$filling`. ```php $array = Arrays::normalize([1 => 'first', 'a' => 'second']); @@ -243,14 +354,14 @@ $array = Arrays::normalize([1 => 'first', 'a' => 'second'], 'foobar'); ``` -pick(array &$array, string|int $key, mixed $default=null): mixed .[method] --------------------------------------------------------------------------- +pick(array &$array, string|int $key, ?mixed $default=null): mixed .[method] +--------------------------------------------------------------------------- -Liefert und entfernt den Wert eines Elements aus einem Array. Wenn es nicht existiert, wird eine Exception geworfen oder `$default` zurückgegeben, falls angegeben. +Gibt den Wert eines Elements aus dem Array zurück und entfernt ihn. Wenn er nicht existiert, wird eine Ausnahme geworfen, oder der Wert `$default` zurückgegeben, falls angegeben. ```php -$array = [1 => 'foo', null => 'bar']; -$a = Arrays::pick($array, null); +$array = [1 => 'foo', 'x' => 'bar']; +$a = Arrays::pick($array, 'x'); // $a = 'bar' $b = Arrays::pick($array, 'not-exists', 'foobar'); // $b = 'foobar' @@ -262,7 +373,7 @@ $c = Arrays::pick($array, 'not-exists'); renameKey(array &$array, string|int $oldKey, string|int $newKey): bool .[method] -------------------------------------------------------------------------------- -Benennt einen Schlüssel um. Gibt `true` zurück, wenn der Schlüssel im Array gefunden wurde. +Benennt einen Schlüssel im Array um. Gibt `true` zurück, wenn der Schlüssel im Array gefunden wurde. ```php $array = ['first' => 10, 'second' => 20]; @@ -274,20 +385,20 @@ Arrays::renameKey($array, 'first', 'renamed'); getKeyOffset(array $array, string|int $key): ?int .[method] ----------------------------------------------------------- -Gibt die null-indizierte Position des angegebenen Array-Schlüssels zurück. Gibt `null` zurück, wenn der Schlüssel nicht gefunden wird. +Gibt die Position des angegebenen Schlüssels im Array zurück. Die Position ist ab 0 nummeriert. Falls der Schlüssel nicht gefunden wird, gibt die Funktion `null` zurück. ```php $array = ['first' => 10, 'second' => 20]; -$position = Arrays::getKeyOffset($array, 'first'); // returns 0 -$position = Arrays::getKeyOffset($array, 'second'); // returns 1 -$position = Arrays::getKeyOffset($array, 'not-exists'); // returns null +$position = Arrays::getKeyOffset($array, 'first'); // gibt 0 zurück +$position = Arrays::getKeyOffset($array, 'second'); // gibt 1 zurück +$position = Arrays::getKeyOffset($array, 'not-exists'); // gibt null zurück ``` -some(iterable $array, callable $callback): bool .[method] ---------------------------------------------------------- +some(array $array, callable $predicate): bool .[method] +------------------------------------------------------- -Prüft, ob mindestens ein Element im Array den Test besteht, der durch den angegebenen Callback mit der Signatur `function ($value, $key, array $array): bool` implementiert wird. +Testet, ob mindestens ein Element im Array den Test besteht, der in `$predicate` mit der Signatur `function ($value, $key, array $array): bool` implementiert ist. ```php $array = [1, 2, 3, 4]; @@ -295,13 +406,13 @@ $isEven = fn($value) => $value % 2 === 0; $res = Arrays::some($array, $isEven); // true ``` -Siehe [every() |#every()]. +Siehe [#every()]. toKey(mixed $key): string|int .[method] --------------------------------------- -Konvertiert einen Wert in einen Array-Schlüssel, der entweder eine Ganzzahl oder eine Zeichenkette ist. +Konvertiert einen Wert in einen Array-Schlüssel, der entweder ein Integer oder ein String ist. ```php Arrays::toKey('1'); // 1 @@ -312,19 +423,19 @@ Arrays::toKey('01'); // '01' toObject(iterable $array, object $object): object .[method] ----------------------------------------------------------- -Kopiert die Elemente des Arrays `$array` in das Objekt `$object` und gibt es dann zurück. +Kopiert die Elemente des Arrays `$array` in das Objekt `$object`, das dann zurückgegeben wird. ```php $obj = new stdClass; $array = ['foo' => 1, 'bar' => 2]; -Arrays::toObject($array, $obj); // it sets $obj->foo = 1; $obj->bar = 2; +Arrays::toObject($array, $obj); // setzt $obj->foo = 1; $obj->bar = 2; ``` -wrap(iterable $array, string $prefix='', string $suffix=''): array .[method] ----------------------------------------------------------------------------- +wrap(array $array, string $prefix='', string $suffix=''): array .[method] +------------------------------------------------------------------------- -Jedes Element des Arrays wird in einen String umgewandelt und mit `$prefix` und `$suffix` umschlossen. +Konvertiert jedes Element im Array in einen String und umgibt es mit dem Präfix `$prefix` und dem Suffix `$suffix`. ```php $array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); @@ -332,21 +443,21 @@ $array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); ``` -ArrayHash .[#toc-arrayhash] -=========================== +ArrayHash +========= -Das Objekt [api:Nette\Utils\ArrayHash] ist ein Nachkomme der generischen Klasse stdClass und erweitert diese um die Möglichkeit, sie als Array zu behandeln, z. B. den Zugriff auf Mitglieder mit eckigen Klammern: +Das Objekt [api:Nette\Utils\ArrayHash] ist ein Nachkomme der generischen Klasse stdClass und erweitert diese um die Fähigkeit, sie wie ein Array zu behandeln, d.h. zum Beispiel auf Mitglieder über eckige Klammern zuzugreifen: ```php $hash = new Nette\Utils\ArrayHash; $hash['foo'] = 123; -$hash->bar = 456; // funktioniert auch in Objektnotation +$hash->bar = 456; // gleichzeitig funktioniert auch die Objektnotation $hash->foo; // 123 ``` -Sie können die Funktion `count($hash)` verwenden, um die Anzahl der Elemente zu ermitteln. +Die Funktion `count($hash)` kann verwendet werden, um die Anzahl der Elemente zu ermitteln. -Sie können über ein Objekt wie über ein Array iterieren, sogar mit einer Referenz: +Über das Objekt kann wie über ein Array iteriert werden, auch mit Referenz: ```php foreach ($hash as $key => $value) { @@ -358,7 +469,7 @@ foreach ($hash as $key => &$value) { } ``` -Vorhandene Arrays können mit `from()` in `ArrayHash` umgewandelt werden: +Ein vorhandenes Array kann mit der Methode `from()` in einen `ArrayHash` transformiert werden: ```php $array = ['foo' => 123, 'bar' => 456]; @@ -368,7 +479,7 @@ $hash->foo; // 123 $hash->bar; // 456 ``` -Die Umwandlung ist rekursiv: +Die Konvertierung ist rekursiv: ```php $array = ['foo' => 123, 'inner' => ['a' => 'b']]; @@ -379,24 +490,24 @@ $hash->inner->a; // 'b' $hash['inner']['a']; // 'b' ``` -Sie kann durch den zweiten Parameter vermieden werden: +Dies kann durch den zweiten Parameter verhindert werden: ```php $hash = Nette\Utils\ArrayHash::from($array, false); -$hash->inner; // array +$hash->inner; // Array ``` -Zurück in das Array transformieren: +Transformation zurück in ein Array: ```php $array = (array) $hash; ``` -ArrayList .[#toc-arraylist] -=========================== +ArrayList +========= -[api:Nette\Utils\ArrayList] stellt ein lineares Array dar, bei dem die Indizes nur ganze Zahlen sind, die von 0 an aufsteigen. +[api:Nette\Utils\ArrayList] stellt ein lineares Array dar, bei dem die Indizes nur ganze Zahlen aufsteigend ab 0 sind. ```php $list = new Nette\Utils\ArrayList; @@ -407,9 +518,16 @@ $list[] = 'c'; count($list); // 3 ``` -Sie können die Funktion `count($list)` verwenden, um die Anzahl der Elemente zu ermitteln. +Ein vorhandenes Array kann mit der Methode `from()` in einen `ArrayList` transformiert werden: + +```php +$array = ['foo', 'bar']; +$list = Nette\Utils\ArrayList::from($array); +``` -Sie können über ein Objekt wie über ein Array iterieren, sogar mit einem Verweis: +Die Funktion `count($list)` kann verwendet werden, um die Anzahl der Elemente zu ermitteln. + +Über das Objekt kann wie über ein Array iteriert werden, auch mit Referenz: ```php foreach ($list as $key => $value) { @@ -417,32 +535,25 @@ foreach ($list as $key => $value) { } foreach ($list as $key => &$value) { - $value = 'neuer Wert'; + $value = 'new value'; } ``` -Vorhandene Arrays können mit `from()` in `ArrayList` umgewandelt werden: - -```php -$array = ['foo', 'bar']; -$list = Nette\Utils\ArrayList::from($array); -``` - -Der Zugriff auf Schlüssel, die über die zulässigen Werte hinausgehen, führt zu einer Ausnahme `Nette\OutOfRangeException`: +Der Zugriff auf Schlüssel außerhalb der erlaubten Werte wirft eine Ausnahme `Nette\OutOfRangeException`: ```php -echo $list[-1]; // throws Nette\OutOfRangeException -unset($list[30]); // throws Nette\OutOfRangeException +echo $list[-1]; // wirft Nette\OutOfRangeException +unset($list[30]); // wirft Nette\OutOfRangeException ``` -Das Entfernen des Schlüssels führt zu einer Neunummerierung der Elemente: +Das Entfernen eines Schlüssels führt zur Neuindizierung der Elemente: ```php unset($list[1]); // ArrayList(0 => 'a', 1 => 'c') ``` -Mit `prepend()` können Sie ein neues Element an den Anfang setzen: +Ein neues Element kann mit der Methode `prepend()` am Anfang hinzugefügt werden: ```php $list->prepend('d'); diff --git a/utils/de/callback.texy b/utils/de/callback.texy index 417bcdf71d..5d1a8cfbc0 100644 --- a/utils/de/callback.texy +++ b/utils/de/callback.texy @@ -1,8 +1,8 @@ -Rückruf-Funktionen -****************** +Arbeiten mit Callbacks +********************** .[perex] -[api:Nette\Utils\Callback] ist eine statische Klasse, die Funktionen für die Arbeit mit [PHP-Callbacks |https://www.php.net/manual/en/language.types.callable.php] enthält. +[api:Nette\Utils\Callback] ist eine statische Klasse mit Funktionen für die Arbeit mit [PHP Callbacks |https://www.php.net/manual/en/language.types.callable.php]. Installation: @@ -11,7 +11,7 @@ Installation: composer require nette/utils ``` -Alle Beispiele setzen voraus, dass der folgende Klassenalias definiert ist: +Alle Beispiele setzen voraus, dass ein Alias erstellt wurde: ```php use Nette\Utils\Callback; @@ -21,13 +21,13 @@ use Nette\Utils\Callback; check($callable, bool $syntax=false): callable .[method] -------------------------------------------------------- -Überprüft, ob `$callable` ein gültiger PHP-Callback ist. Andernfalls wird `Nette\InvalidArgumentException` geworfen. Wenn `$syntax` auf true gesetzt ist, prüft die Funktion nur, ob `$callable` eine gültige Struktur hat, die als Callback verwendet werden kann, aber sie prüft nicht, ob die Klasse oder Methode tatsächlich existiert. Gibt `$callable` zurück. +Überprüft, ob die Variable `$callable` ein gültiger Callback ist. Andernfalls wird `Nette\InvalidArgumentException` geworfen. Wenn `$syntax` true ist, überprüft die Funktion nur, ob `$callable` die Struktur eines Callbacks hat, überprüft aber nicht, ob die angegebene Klasse oder Methode tatsächlich existiert. Gibt `$callable` zurück. ```php -Callback::check('trim'); // keine Ausnahme +Callback::check('trim'); // wirft keine Ausnahme Callback::check(['NonExistentClass', 'method']); // wirft Nette\InvalidArgumentException -Callback::check(['NichtExistierendeKlasse', 'Methode'], true); // keine Ausnahme -Callback::check(function () {}); // keine Ausnahme +Callback::check(['NonExistentClass', 'method'], true); // wirft keine Ausnahme +Callback::check(function () {}); // wirft keine Ausnahme Callback::check(null); // wirft Nette\InvalidArgumentException ``` @@ -35,7 +35,7 @@ Callback::check(null); // wirft Nette\InvalidArgumentException toString($callable): string .[method] ------------------------------------- -Konvertiert PHP-Callback in Textform. Klasse oder Methode darf nicht existieren. +Konvertiert einen PHP-Callback in eine Textform. Die Klasse oder Methode muss nicht existieren. ```php Callback::toString('trim'); // 'trim' @@ -46,7 +46,7 @@ Callback::toString(['MyClass', 'method']); // 'MyClass::method' toReflection($callable): ReflectionMethod|ReflectionFunction .[method] ---------------------------------------------------------------------- -Gibt die Reflektion für die in PHP Callback verwendete Methode oder Funktion zurück. +Gibt die Reflexion für eine Methode oder Funktion in einem PHP-Callback zurück. ```php $ref = Callback::toReflection('trim'); @@ -60,7 +60,7 @@ $ref = Callback::toReflection(['MyClass', 'method']); isStatic($callable): bool .[method] ----------------------------------- -Prüft, ob PHP-Callback eine Funktion oder eine statische Methode ist. +Stellt fest, ob ein PHP-Callback eine Funktion oder eine statische Methode ist. ```php Callback::isStatic('trim'); // true @@ -73,7 +73,7 @@ Callback::isStatic(function () {}); // false unwrap(Closure $closure): callable|array .[method] -------------------------------------------------- -Hebt die von `Closure::fromCallable` erzeugte Schließung auf:https://www.php.net/manual/en/closure.fromcallable.php. +Packt eine Closure, die mit `Closure::fromCallable`:https://www.php.net/manual/en/closure.fromcallable.php erstellt wurde, wieder aus. ```php $closure = Closure::fromCallable(['MyClass', 'method']); diff --git a/utils/de/datetime.texy b/utils/de/datetime.texy index 18d573bc6b..41ea5f0a38 100644 --- a/utils/de/datetime.texy +++ b/utils/de/datetime.texy @@ -1,8 +1,8 @@ -Date and Time -************* +Datum und Zeit +************** .[perex] -[api:Nette\Utils\DateTime] is a class extends native [php:DateTime]. +[api:Nette\Utils\DateTime] ist eine Klasse, die die native [php:DateTime] um zusätzliche Funktionen erweitert. Installation: @@ -11,7 +11,7 @@ Installation: composer require nette/utils ``` -All examples assume the following class alias is defined: +Alle Beispiele setzen voraus, dass ein Alias erstellt wurde: ```php use Nette\Utils\DateTime; @@ -20,35 +20,35 @@ use Nette\Utils\DateTime; static from(string|int|\DateTimeInterface $time): DateTime .[method] -------------------------------------------------------------------- -Creates a DateTime object from a string, UNIX timestamp, or other [php:DateTimeInterface] object. Throws an `Exception` if the date and time are not valid. +Erstellt ein DateTime-Objekt aus einem String, UNIX-Zeitstempel oder einem anderen [php:DateTimeInterface]-Objekt. Wirft eine Ausnahme `Exception`, wenn Datum und Uhrzeit ungültig sind. ```php -DateTime::from(1138013640); // erstellt eine DateTime aus dem UNIX-Zeitstempel mit einem Standard-Zeitstempel -DateTime::from(42); // erstellt einen DateTime aus der aktuellen Zeit plus 42 Sekunden -DateTime::from('1994-02-26 04:15:32'); // erstellt eine DateTime auf der Grundlage einer Zeichenkette -DateTime::from('1994-02-26'); // erstellt DateTime anhand des Datums, die Zeit wird 00:00:00 sein +DateTime::from(1138013640); // erstellt DateTime aus UNIX-Zeitstempel mit Standard-Zeitzone +DateTime::from(42); // erstellt DateTime aus der aktuellen Zeit plus 42 Sekunden +DateTime::from('1994-02-26 04:15:32'); // erstellt DateTime gemäß dem String +DateTime::from('1994-02-26'); // erstellt DateTime gemäß dem Datum, Zeit wird 00:00:00 sein ``` static fromParts(int $year, int $month, int $day, int $hour=0, int $minute=0, float $second=0.0): DateTime .[method] -------------------------------------------------------------------------------------------------------------------- -Creates DateTime object or throws an `Nette\InvalidArgumentException` exception if the date and time are not valid. +Erstellt ein DateTime-Objekt oder wirft eine Ausnahme `Nette\InvalidArgumentException`, wenn Datum und Uhrzeit ungültig sind. ```php DateTime::fromParts(1994, 2, 26, 4, 15, 32); ``` -static createFromFormat(string $format, string $time, string|\DateTimeZone $timezone=null): DateTime|false .[method] --------------------------------------------------------------------------------------------------------------------- -Extends [DateTime::createFromFormat() |https://www.php.net/manual/en/datetime.createfromformat.php] with the ability to specify a timezone as a string. +static createFromFormat(string $format, string $time, ?string|\DateTimeZone $timezone=null): DateTime|false .[method] +--------------------------------------------------------------------------------------------------------------------- +Erweitert [DateTime::createFromFormat() |https://www.php.net/manual/en/datetime.createfromformat.php] um die Möglichkeit, die Zeitzone als String anzugeben. ```php -DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); // Erstellung mit benutzerdefinierter Zeitzone +DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); ``` modifyClone(string $modify=''): static .[method] ------------------------------------------------ -Creates a copy with a modified time. +Erstellt eine Kopie mit modifizierter Zeit. ```php $original = DateTime::from('2017-02-03'); $clone = $original->modifyClone('+1 day'); @@ -59,15 +59,15 @@ $clone->format('Y-m-d'); // '2017-02-04' __toString(): string .[method] ------------------------------ -Returns the date and time in the format `Y-m-d H:i:s`. +Gibt Datum und Uhrzeit im Format `Y-m-d H:i:s` zurück. ```php echo $dateTime; // '2017-02-03 04:15:32' ``` -Implements JsonSerializable .[#toc-implements-jsonserializable] ---------------------------------------------------------------- -Returns the date and time in ISO 8601 format, which is used in JavaScript, for example. +implementiert JsonSerializable +------------------------------ +Gibt Datum und Uhrzeit im Format ISO 8601 zurück, das z.B. in JavaScript verwendet wird. ```php $date = DateTime::from('2017-02-03'); echo json_encode($date); diff --git a/utils/de/filesystem.texy b/utils/de/filesystem.texy index be0bd14ff5..f4002c447d 100644 --- a/utils/de/filesystem.texy +++ b/utils/de/filesystem.texy @@ -1,41 +1,43 @@ -Dateisystem-Funktionen -********************** +Dateisystem +*********** .[perex] -[api:Nette\Utils\FileSystem] ist eine statische Klasse, die nützliche Funktionen für die Arbeit mit einem Dateisystem enthält. Ein Vorteil gegenüber nativen PHP-Funktionen ist, dass sie im Fehlerfall Ausnahmen auslösen. +[api:Nette\Utils\FileSystem] ist eine Klasse, die nützliche Funktionen für die Arbeit mit dem Dateisystem bereitstellt. Ein Vorteil gegenüber nativen PHP-Funktionen ist, dass sie im Fehlerfall Ausnahmen auslösen. +Wenn Sie nach Dateien auf der Festplatte suchen müssen, verwenden Sie den [Finder |finder]. + Installation: ```shell composer require nette/utils ``` -Die folgenden Beispiele gehen davon aus, dass der folgende Klassenalias definiert ist: +Die folgenden Beispiele setzen voraus, dass der folgende Alias definiert wurde: ```php use Nette\Utils\FileSystem; ``` -Manipulation .[#toc-manipulation] -================================= +Manipulation +============ copy(string $origin, string $target, bool $overwrite=true): void .[method] -------------------------------------------------------------------------- -Kopiert eine Datei oder ein ganzes Verzeichnis. Überschreibt standardmäßig vorhandene Dateien und Verzeichnisse. Wenn `$overwrite` auf `false` gesetzt ist und ein `$target` bereits existiert, wird eine Exception `Nette\InvalidStateException` geworfen. Löst eine Exception `Nette\IOException` aus, wenn ein Fehler auftritt. +Kopiert eine Datei oder ein ganzes Verzeichnis. Standardmäßig werden vorhandene Dateien und Verzeichnisse überschrieben. Wenn der Parameter `$overwrite` auf `false` gesetzt ist und die Zieldatei oder das Zielverzeichnis `$target` bereits existiert, wird eine `Nette\InvalidStateException` ausgelöst. Bei anderen Fehlern wird eine `Nette\IOException` ausgelöst. ```php FileSystem::copy('/path/to/source', '/path/to/dest', overwrite: true); ``` -createDir(string $directory, int $mode=0777): void .[method] ------------------------------------------------------------- +createDir(string $dir, int $mode=0777): void .[method] +------------------------------------------------------ -Erstellt ein Verzeichnis, wenn es nicht existiert, einschließlich übergeordneter Verzeichnisse. Wirft eine Exception `Nette\IOException` wenn ein Fehler auftritt. +Erstellt ein Verzeichnis `$dir`, falls es nicht existiert, einschließlich aller übergeordneten Verzeichnisse. Bei einem Fehler wird eine `Nette\IOException` ausgelöst. ```php FileSystem::createDir('/path/to/dir'); @@ -45,7 +47,7 @@ FileSystem::createDir('/path/to/dir'); delete(string $path): void .[method] ------------------------------------ -Löscht eine Datei oder ein ganzes Verzeichnis, falls vorhanden. Wenn das Verzeichnis nicht leer ist, wird zuerst sein Inhalt gelöscht. Wirft eine Exception `Nette\IOException` wenn ein Fehler auftritt. +Löscht eine Datei oder ein ganzes Verzeichnis `$path`, falls vorhanden. Wenn das Verzeichnis nicht leer ist, wird zuerst sein Inhalt gelöscht. Bei einem Fehler wird eine `Nette\IOException` ausgelöst. ```php FileSystem::delete('/path/to/fileOrDir'); @@ -55,7 +57,7 @@ FileSystem::delete('/path/to/fileOrDir'); makeWritable(string $path, int $dirMode=0777, int $fileMode=0666): void .[method] --------------------------------------------------------------------------------- -Setzt die Dateiberechtigungen auf `$fileMode` oder die Verzeichnisberechtigungen auf `$dirMode`. Durchläuft rekursiv den gesamten Inhalt des Verzeichnisses und setzt die Berechtigungen auch für diesen. +Setzt die Berechtigungen für eine Datei auf `$fileMode` oder für ein Verzeichnis auf `$dirMode`. Durchläuft rekursiv den Pfad `$path` und setzt die Berechtigungen auch für den gesamten Inhalt des Verzeichnisses. ```php FileSystem::makeWritable('/path/to/fileOrDir'); @@ -65,7 +67,7 @@ FileSystem::makeWritable('/path/to/fileOrDir'); open(string $path, string $mode): resource .[method] ---------------------------------------------------- -Öffnet die Datei und gibt die Ressource zurück. Der Parameter `$mode` funktioniert genauso wie die native Funktion `fopen()`:https://www.php.net/manual/en/function.fopen.php. Wenn ein Fehler auftritt, wird die Ausnahme `Nette\IOException` ausgelöst. +Öffnet eine Datei `$path` und gibt eine Ressource zurück. Der Parameter `$mode` funktioniert genauso wie bei der nativen Funktion [`fopen()` |https://www.php.net/manual/en/function.fopen.php]. Bei einem Fehler wird eine `Nette\IOException` ausgelöst. ```php $res = FileSystem::open('/path/to/file', 'r'); @@ -75,7 +77,7 @@ $res = FileSystem::open('/path/to/file', 'r'); read(string $file): string .[method] ------------------------------------ -Liest den Inhalt einer `$file`. Bei Auftreten eines Fehlers wird die Ausnahme `Nette\IOException` ausgelöst. +Gibt den Inhalt der Datei `$file` zurück. Bei einem Fehler wird eine `Nette\IOException` ausgelöst. ```php $content = FileSystem::read('/path/to/file'); @@ -85,8 +87,7 @@ $content = FileSystem::read('/path/to/file'); readLines(string $file, bool $stripNewLines=true): \Generator .[method] ----------------------------------------------------------------------- -Liest den Inhalt der Datei Zeile für Zeile. Im Gegensatz zur nativen Funktion `file()` wird nicht die gesamte Datei in den Speicher eingelesen, sondern kontinuierlich, so dass auch Dateien gelesen werden können, die größer als der verfügbare Speicher sind. Der Parameter `$stripNewLines` gibt an, ob die Zeilenumbruchzeichen `\r` und `\n` entfernt werden sollen. -Im Falle eines Fehlers löst sie eine `Nette\IOException` Ausnahme aus. +Liest den Inhalt der Datei `$file` Zeile für Zeile. Im Gegensatz zur nativen Funktion `file()` wird nicht die gesamte Datei in den Speicher geladen, sondern sie wird fortlaufend gelesen, sodass auch Dateien gelesen werden können, die größer als der verfügbare Speicher sind. `$stripNewLines` gibt an, ob die Zeilenendezeichen `\r` und `\n` entfernt werden sollen (`true` standardmäßig). Bei einem Fehler wird eine `Nette\IOException` ausgelöst. ```php $lines = FileSystem::readLines('/path/to/file'); @@ -100,7 +101,7 @@ foreach ($lines as $lineNum => $line) { rename(string $origin, string $target, bool $overwrite=true): void .[method] ---------------------------------------------------------------------------- -Benennt eine durch `$origin` angegebene Datei oder ein Verzeichnis um oder verschiebt sie/es nach `$target`. Überschreibt standardmäßig vorhandene Dateien und Verzeichnisse. Wenn `$overwrite` auf `false` gesetzt ist und `$target` bereits existiert, wird eine Ausnahme `Nette\InvalidStateException` geworfen. Wirft eine Exception `Nette\IOException`, wenn ein Fehler auftritt. +Benennt eine Datei oder ein Verzeichnis `$origin` in `$target` um oder verschiebt es. Standardmäßig werden vorhandene Dateien und Verzeichnisse überschrieben. Wenn der Parameter `$overwrite` auf `false` gesetzt ist und die Zieldatei oder das Zielverzeichnis `$target` bereits existiert, wird eine `Nette\InvalidStateException` ausgelöst. Bei anderen Fehlern wird eine `Nette\IOException` ausgelöst. ```php FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); @@ -110,21 +111,21 @@ FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); write(string $file, string $content, int $mode=0666): void .[method] -------------------------------------------------------------------- -Schreibt die `$content` in eine `$file`. Bei einem Fehler wird die Ausnahme `Nette\IOException` ausgelöst. +Schreibt den String `$content` in die Datei `$file`. Setzt optional die Berechtigungen `$mode`. Bei einem Fehler wird eine `Nette\IOException` ausgelöst. ```php FileSystem::write('/path/to/file', $content); ``` -Pfade .[#toc-paths] -=================== +Pfade +===== isAbsolute(string $path): bool .[method] ---------------------------------------- -Bestimmt, ob die `$path` absolut ist. +Prüft, ob der Pfad `$path` absolut ist. ```php FileSystem::isAbsolute('../backup'); // false @@ -135,7 +136,7 @@ FileSystem::isAbsolute('C:/backup'); // true joinPaths(string ...$segments): string .[method] ------------------------------------------------ -Verbindet alle Segmente des Pfades und normalisiert das Ergebnis. +Verbindet alle Pfadsegmente und normalisiert das Ergebnis. ```php FileSystem::joinPaths('a', 'b', 'file.txt'); // 'a/b/file.txt' @@ -146,7 +147,7 @@ FileSystem::joinPaths('/a/', '/../b'); // '/b' normalizePath(string $path): string .[method] --------------------------------------------- -Normalisiert `..` und `.` sowie die Verzeichnistrennzeichen im Pfad. +Normalisiert `..` und `.` sowie Verzeichnistrennzeichen im Pfad auf die systemüblichen. ```php FileSystem::normalizePath('/file/.'); // '/file/' @@ -159,7 +160,7 @@ FileSystem::normalizePath('file/../../bar'); // '/../bar' unixSlashes(string $path): string .[method] ------------------------------------------- -Wandelt Schrägstriche in `/` um, die auf Unix-Systemen verwendet werden. +Konvertiert Schrägstriche in `/`, die in Unix-Systemen verwendet werden. ```php $path = FileSystem::unixSlashes($path); @@ -169,8 +170,45 @@ $path = FileSystem::unixSlashes($path); platformSlashes(string $path): string .[method] ----------------------------------------------- -Konvertiert Schrägstriche in Zeichen, die für die aktuelle Plattform spezifisch sind, d. h. `\` unter Windows und `/` anderswo. +Konvertiert Schrägstriche in die für die aktuelle Plattform spezifischen Zeichen, d.h. `\` unter Windows und `/` anderswo. ```php $path = FileSystem::platformSlashes($path); ``` + + +resolvePath(string $basePath, string $path): string .[method]{data-version:4.0.6} +--------------------------------------------------------------------------------- + +Löst den Pfad `$path` relativ zum Basisverzeichnis `$basePath` auf und gibt den endgültigen Pfad zurück. Absolute Pfade (`/foo`, `C:/foo`) bleiben unverändert (nur Schrägstriche werden normalisiert), relative Pfade werden an den Basispfad angehängt. + +```php +// Unter Windows wären die Schrägstriche in der Ausgabe umgekehrt (\) +FileSystem::resolvePath('/base/dir', '/abs/path'); // '/abs/path' +FileSystem::resolvePath('/base/dir', 'rel'); // '/base/dir/rel' +FileSystem::resolvePath('base/dir', '../file.txt'); // 'base/file.txt' +FileSystem::resolvePath('base', ''); // 'base' +``` + + +Statischer vs. nicht-statischer Zugriff +======================================= + +Um die Klasse beispielsweise zu Testzwecken einfach durch eine andere (einen Mock) ersetzen zu können, verwenden Sie sie nicht-statisch: + +```php +class AnyClassUsingFileSystem +{ + public function __construct( + private FileSystem $fileSystem, + ) { + } + + public function readConfig(): string + { + return $this->fileSystem->read(/* ... */); + } + + ... +} +``` diff --git a/utils/de/finder.texy b/utils/de/finder.texy index 0964f17a52..869f06b49c 100644 --- a/utils/de/finder.texy +++ b/utils/de/finder.texy @@ -2,7 +2,7 @@ Finder: Dateisuche ****************** .[perex] -Müssen Sie Dateien finden, die einer bestimmten Maske entsprechen? Der Finder kann Ihnen helfen. Er ist ein vielseitiges und schnelles Werkzeug zum Durchsuchen der Verzeichnisstruktur. +Müssen Sie Dateien finden, die einer bestimmten Maske entsprechen? Der Finder hilft Ihnen dabei. Es ist ein vielseitiges und schnelles Werkzeug zum Durchsuchen der Verzeichnisstruktur. Installation: @@ -11,17 +11,17 @@ Installation: composer require nette/utils ``` -Die Beispiele gehen davon aus, dass ein Alias erstellt wurde: +Die Beispiele setzen voraus, dass der folgende Alias definiert wurde: ```php use Nette\Utils\Finder; ``` -Verwendung von .[#toc-using] ----------------------------- +Verwendung +---------- -Sehen wir uns zunächst an, wie Sie [api:Nette\Utils\Finder] verwenden können, um die Dateinamen mit den Erweiterungen `.txt` und `.md` im aktuellen Verzeichnis aufzulisten: +Zuerst zeigen wir, wie Sie mit [api:Nette\Utils\Finder] Dateinamen mit den Erweiterungen `.txt` und `.md` im aktuellen Verzeichnis auflisten können: ```php foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { @@ -29,50 +29,51 @@ foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { } ``` -Das Standardverzeichnis für die Suche ist das aktuelle Verzeichnis, aber Sie können es mit den Methoden [in() oder from() |#Where to search?] ändern. -Die Variable `$file` ist eine Instanz der Klasse [FileInfo |#FileInfo] mit vielen nützlichen Methoden. Der Schlüssel `$name` enthält den Pfad zur Datei als String. +Das Standardverzeichnis für die Suche ist das aktuelle Verzeichnis, aber Sie können es mit den Methoden [in() oder from() |#Wo soll gesucht werden] ändern. Die Variable `$file` ist eine Instanz der Klasse [#FileInfo] mit vielen nützlichen Methoden. Der Schlüssel `$name` enthält den Pfad zur Datei als String. -Wonach soll gesucht werden? .[#toc-what-to-search-for] ------------------------------------------------------- +Was soll gesucht werden? +------------------------ -Neben der Methode `findFiles()` gibt es auch `findDirectories()`, die nur Verzeichnisse durchsucht, und `find()`, die beide durchsucht. Diese Methoden sind statisch, d.h. sie können aufgerufen werden, ohne eine Instanz zu erzeugen. Der Parameter mask ist optional, wenn Sie ihn nicht angeben, wird alles durchsucht. +Neben der Methode `findFiles()` gibt es auch `findDirectories()`, die nur Verzeichnisse sucht, und `find()`, die beides sucht. Diese Methoden sind statisch und können daher ohne Erstellung einer Instanz aufgerufen werden. Der Parameter mit der Maske ist optional; wenn Sie ihn nicht angeben, wird alles gesucht. ```php foreach (Finder::find() as $file) { - echo $file; // jetzt sind alle Dateien und Verzeichnisse aufgelistet + echo $file; // jetzt werden alle Dateien und Verzeichnisse ausgegeben } ``` -Verwenden Sie die Methoden `files()` und `directories()`, um hinzuzufügen, wonach sonst noch gesucht werden soll. Die Methoden können wiederholt aufgerufen werden und ein Array von Masken kann als Parameter übergeben werden: +Mit den Methoden `files()` und `directories()` können Sie ergänzen, was zusätzlich gesucht werden soll. Die Methoden können wiederholt aufgerufen werden, und als Parameter kann auch ein Array von Masken angegeben werden: ```php Finder::findDirectories('vendor') // alle Verzeichnisse - ->files(['*.php', '*.phpt']); // sowie alle PHP-Dateien + ->files(['*.php', '*.phpt']); // plus alle PHP-Dateien ``` -Eine Alternative zu statischen Methoden besteht darin, mit `new Finder` eine Instanz zu erstellen (das auf diese Weise neu erstellte Objekt sucht nach nichts) und mit `files()` und `directories()` anzugeben, wonach gesucht werden soll: +Eine Alternative zu statischen Methoden ist die Erstellung einer Instanz mit `new Finder` (ein so erstelltes leeres Objekt sucht nichts) und die Angabe, was gesucht werden soll, mit `files()` und `directories()`: ```php (new Finder) - ->directories() // alle Verzeichnisse - ->files('*.php'); // sowie alle PHP-Dateien + ->directories() // alle Verzeichnisse + ->files('*.php'); // plus alle PHP-Dateien ``` -Sie können [Wildcards |#wildcards] `*`, `**` verwenden, `?` and `[...]` in der Maske verwenden. Sie können sogar Verzeichnisse angeben, z. B. `src/*.php` sucht nach allen PHP-Dateien im Verzeichnis `src`. +In der Maske können Sie die [#Platzhalter] `*`, `**`, `?` und `[...]` verwenden. Sie können sogar Verzeichnisse angeben, zum Beispiel sucht `src/*.php` alle PHP-Dateien im Verzeichnis `src`. +Symlinks werden ebenfalls als Verzeichnisse oder Dateien behandelt. -Wo soll gesucht werden? .[#toc-where-to-search] ------------------------------------------------ -Das Standard-Suchverzeichnis ist das aktuelle Verzeichnis. Sie können dies ändern, indem Sie die Methoden `in()` und `from()` verwenden. Wie Sie aus den Namen der Methoden ersehen können, durchsucht `in()` nur das aktuelle Verzeichnis, während `from()` auch die Unterverzeichnisse durchsucht (rekursiv). Wenn Sie rekursiv im aktuellen Verzeichnis suchen wollen, können Sie `from('.')` verwenden. +Wo soll gesucht werden? +----------------------- -Diese Methoden können mehrfach aufgerufen werden oder Sie können ihnen mehrere Pfade als Arrays übergeben, dann werden Dateien in allen Verzeichnissen gesucht. Wenn eines der Verzeichnisse nicht existiert, wird ein `Nette\UnexpectedValueException` geworfen. +Das Standardverzeichnis für die Suche ist das aktuelle Verzeichnis. Sie ändern es mit den Methoden `in()` und `from()`. Wie aus den Methodennamen ersichtlich ist, sucht `in()` nur im angegebenen Verzeichnis, während `from()` auch in dessen Unterverzeichnissen (rekursiv) sucht. Wenn Sie rekursiv im aktuellen Verzeichnis suchen möchten, können Sie `from('.')` verwenden. + +Diese Methoden können mehrmals aufgerufen werden oder ihnen mehrere Pfade als Array übergeben werden; die Dateien werden dann in allen angegebenen Verzeichnissen gesucht. Wenn eines der Verzeichnisse nicht existiert, wird eine `Nette\UnexpectedValueException` ausgelöst. ```php Finder::findFiles('*.php') ->in(['src', 'tests']) // sucht direkt in src/ und tests/ - ->from('vendor'); // sucht auch in den Unterverzeichnissen von vendor/ + ->from('vendor'); // sucht auch in Unterverzeichnissen von vendor/ ``` Relative Pfade sind relativ zum aktuellen Verzeichnis. Es können natürlich auch absolute Pfade angegeben werden: @@ -82,43 +83,43 @@ Finder::findFiles('*.php') ->in('/var/www/html'); ``` -Mit den [Platzhaltern |#wildcards] `*`, `**`, `?` can be used in the path. For example, you can use the path `src/*/*.php` können Sie nach allen PHP-Dateien in den Verzeichnissen der zweiten Ebene im Verzeichnis `src` suchen. Das Zeichen `**`, globstar genannt, ist ein mächtiger Trumpf, weil es Ihnen erlaubt, auch Unterverzeichnisse zu durchsuchen: Verwenden Sie `src/**/tests/*.php`, um nach allen PHP-Dateien im Verzeichnis `tests` zu suchen, das sich in `src` oder einem seiner Unterverzeichnisse befindet. +Im Pfad können die [#Platzhalter] `*`, `**`, `?` verwendet werden. Sie können beispielsweise mit dem Pfad `src/*/*.php` alle PHP-Dateien in Verzeichnissen der zweiten Ebene im Verzeichnis `src` suchen. Das Zeichen `**`, genannt Globstar, ist ein mächtiger Trumpf, da es die Suche auch in Unterverzeichnissen ermöglicht: Mit `src/**/tests/*.php` suchen Sie alle PHP-Dateien im Verzeichnis `tests`, das sich in `src` oder einem seiner Unterverzeichnisse befindet. -Andererseits werden Wildcards `[...]` Zeichen werden im Pfad nicht unterstützt, d.h. sie haben keine besondere Bedeutung, um unerwünschtes Verhalten zu vermeiden, falls Sie z.B. nach `in(__DIR__)` suchen und zufällig `[]` Zeichen im Pfad erscheinen. +Im Gegensatz dazu werden die Platzhalter `[...]` im Pfad nicht unterstützt, d.h. sie haben keine besondere Bedeutung, um unerwünschtes Verhalten zu vermeiden, falls Sie beispielsweise `in(__DIR__)` suchen und zufällig im Pfad die Zeichen `[]` vorkommen. -Bei der Suche nach Dateien und Verzeichnissen in der Tiefe wird zuerst das übergeordnete Verzeichnis zurückgegeben und dann die darin enthaltenen Dateien, was sich mit `childFirst()` umkehren lässt. +Bei der Tiefensuche von Dateien und Verzeichnissen wird standardmäßig zuerst das übergeordnete Verzeichnis und dann dessen Inhalt zurückgegeben. Dieses Verhalten kann mit `childFirst()` umgekehrt werden. -Wildcards .[#toc-wildcards] ---------------------------- +Platzhalter +----------- -Sie können mehrere Sonderzeichen in der Maske verwenden: +In der Maske können Sie folgende Sonderzeichen verwenden: -- `*` - replaces any number of arbitrary characters (except `/`) -- `**` - ersetzt eine beliebige Anzahl von Zeichen einschließlich `/` (d.h. es kann mehrstufig gesucht werden) -- `?` - replaces one arbitrary character (except `/`) +- `*` - ersetzt eine beliebige Anzahl beliebiger Zeichen (außer `/`) +- `**` - ersetzt eine beliebige Anzahl beliebiger Zeichen einschließlich `/` (d.h. es kann mehrstufig gesucht werden) +- `?` - ersetzt ein beliebiges Zeichen (außer `/`) - `[a-z]` - ersetzt ein Zeichen aus der Liste der Zeichen in eckigen Klammern - `[!a-z]` - ersetzt ein Zeichen außerhalb der Liste der Zeichen in eckigen Klammern -Beispiele für die Verwendung: +Anwendungsbeispiele: -- `img/?.png` - Dateien mit dem Einbuchstabennamen `0.png`, `1.png`, `x.png`, usw. -- `logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log` - Protokolldateien im Format `YYYY-MM-DD` -- `src/**/tests/*` - Dateien im Verzeichnis `src/tests`, `src/foo/tests`, `src/foo/bar/tests` und so weiter. +- `img/?.png` - Dateien mit einbuchstabigem Namen `0.png`, `1.png`, `x.png`, usw. +- `logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log` - Logs im Format `YYYY-MM-DD` +- `src/**/tests/*` - Dateien in den Verzeichnissen `src/tests`, `src/foo/tests`, `src/foo/bar/tests` und so weiter. - `docs/**.md` - alle Dateien mit der Erweiterung `.md` in allen Unterverzeichnissen des Verzeichnisses `docs` -Ausgenommen .[#toc-excluding] ------------------------------ +Ausschluss +---------- -Verwenden Sie die Methode `exclude()`, um Dateien und Verzeichnisse von der Suche auszuschließen. Sie geben eine Maske an, der die Datei nicht entsprechen darf. Beispiel für die Suche nach Dateien `*.txt` mit Ausnahme derjenigen, die den Buchstaben "X" im Namen enthalten: +Mit der Methode `exclude()` können Dateien und Verzeichnisse von der Suche ausgeschlossen werden. Sie geben eine Maske an, der die Datei nicht entsprechen darf. Beispiel für die Suche nach `*.txt`-Dateien außer denen, die den Buchstaben `X` im Namen enthalten: ```php Finder::findFiles('*.txt') ->exclude('*X*'); ``` -Verwenden Sie `exclude()`, um durchsuchte Unterverzeichnisse zu überspringen: +Verwenden Sie `exclude()`, um das Durchsuchen bestimmter Unterverzeichnisse zu überspringen: ```php Finder::findFiles('*.php') @@ -127,12 +128,12 @@ Finder::findFiles('*.php') ``` -Filtern von .[#toc-filtering] ------------------------------ +Filterung +--------- -Der Finder bietet mehrere Methoden, um die Ergebnisse zu filtern (d.h. zu reduzieren). Sie können sie kombinieren und wiederholt aufrufen. +Der Finder bietet mehrere Methoden zum Filtern (d.h. Reduzieren) der Ergebnisse. Sie können sie kombinieren und wiederholt aufrufen. -Verwenden Sie `size()`, um nach der Dateigröße zu filtern. Auf diese Weise finden wir Dateien mit Größen zwischen 100 und 200 Byte: +Mit `size()` filtern wir nach Dateigröße. So finden wir Dateien mit einer Größe zwischen 100 und 200 Bytes: ```php Finder::findFiles('*.php') @@ -140,7 +141,7 @@ Finder::findFiles('*.php') ->size('<=', 200); ``` -Die Methode `date()` filtert nach dem Datum, an dem die Datei zuletzt geändert wurde. Die Werte können absolut oder relativ zum aktuellen Datum und zur aktuellen Uhrzeit angegeben werden, um z. B. Dateien zu finden, die in den letzten zwei Wochen geändert wurden: +Die Methode `date()` filtert nach dem Datum der letzten Änderung der Datei. Die Werte können absolut oder relativ zum aktuellen Datum und zur aktuellen Uhrzeit sein; zum Beispiel finden wir so Dateien, die in den letzten zwei Wochen geändert wurden: ```php Finder::findFiles('*.php') @@ -148,11 +149,11 @@ Finder::findFiles('*.php') ->from($dir) ``` -Beide Funktionen verstehen die Operatoren `>`, `>=`, `<`, `<=`, `=`, `!=`, `<>`. +Beide Methoden unterstützen die Operatoren `>`, `>=`, `<`, `<=`, `=`, `!=`, `<>`. -Der Finder erlaubt Ihnen auch, die Ergebnisse mit Hilfe von benutzerdefinierten Funktionen zu filtern. Die Funktion erhält ein `Nette\Utils\FileInfo` Objekt als Parameter und muss `true` zurückgeben, um die Datei in die Ergebnisse aufzunehmen. +Der Finder ermöglicht auch das Filtern der Ergebnisse mithilfe benutzerdefinierter Funktionen. Die Funktion erhält als Parameter ein `Nette\Utils\FileInfo`-Objekt und muss `true` zurückgeben, damit die Datei in die Ergebnisse aufgenommen wird. -Beispiel: Suche nach PHP-Dateien, die die Zeichenkette `Nette` enthalten (Groß- und Kleinschreibung wird nicht berücksichtigt): +Beispiel: Suche nach PHP-Dateien, die den String `Nette` enthalten (unabhängig von der Groß-/Kleinschreibung): ```php Finder::findFiles('*.php') @@ -160,51 +161,51 @@ Finder::findFiles('*.php') ``` -Tiefenfilterung .[#toc-depth-filtering] ---------------------------------------- +Tiefenfilterung +--------------- -Bei der rekursiven Suche können Sie mit der Methode `limitDepth()` die maximale Crawl-Tiefe festlegen. Wenn Sie `limitDepth(1)` einstellen, werden nur die ersten Unterverzeichnisse gecrawlt, `limitDepth(0)` deaktiviert das Crawlen in der Tiefe und ein Wert von -1 hebt die Begrenzung auf. +Bei der rekursiven Suche können Sie die maximale Suchtiefe mit der Methode `limitDepth()` festlegen. Wenn Sie `limitDepth(1)` setzen, werden nur die ersten Unterverzeichnisse durchlaufen, `limitDepth(0)` deaktiviert den Tiefendurchlauf und der Wert `-1` (Standard) hebt das Limit auf. -Der Finder erlaubt Ihnen, seine eigenen Funktionen zu verwenden, um zu entscheiden, welches Verzeichnis beim Durchsuchen eingegeben werden soll. Die Funktion erhält ein `Nette\Utils\FileInfo` Objekt als Parameter und muss `true` zurückgeben, um das Verzeichnis zu betreten: +Der Finder ermöglicht es, mithilfe benutzerdefinierter Funktionen zu entscheiden, welche Verzeichnisse während der Suche betreten werden sollen. Die Funktion erhält als Parameter ein `Nette\Utils\FileInfo`-Objekt, das das Verzeichnis repräsentiert, und muss `true` zurückgeben, damit in das Verzeichnis eingetreten wird: ```php Finder::findFiles('*.php') - ->descentFilter($file->getBasename() !== 'temp'); + ->descentFilter(fn($file) => $file->getBasename() !== 'temp'); ``` -Sortieren .[#toc-sorting] -------------------------- +Sortierung +---------- -Der Finder bietet auch mehrere Funktionen zum Sortieren von Ergebnissen. +Der Finder bietet auch mehrere Funktionen zum Sortieren der Ergebnisse. -Die Methode `sortByName()` sortiert die Ergebnisse nach Dateinamen. Die Sortierung ist natürlich, d.h. sie behandelt die Zahlen in den Namen korrekt und gibt z.B. `foo1.txt` vor `foo10.txt` zurück. +Die Methode `sortByName()` sortiert die Ergebnisse nach Dateinamen. Die Sortierung erfolgt natürlich, d.h. sie behandelt Zahlen in Namen korrekt und gibt z.B. `foo1.txt` vor `foo10.txt` zurück. -Der Finder erlaubt Ihnen auch, mit einer eigenen Funktion zu sortieren. Sie nimmt zwei `Nette\Utils\FileInfo` Objekte als Parameter und muss das Ergebnis des Vergleichs mit dem Operator `<=>`zurückgeben, d.h. `-1`, `0` nebo `1`. So sortieren wir zum Beispiel Dateien nach Größe: +Der Finder ermöglicht auch die Sortierung mithilfe einer benutzerdefinierten Funktion. Diese erhält als Parameter zwei `Nette\Utils\FileInfo`-Objekte und muss das Ergebnis des Vergleichs mit dem `<=>`-Operator zurückgeben (also `-1`, `0` oder `1`). Zum Beispiel sortieren wir so Dateien nach Größe: ```php $finder->sortBy(fn($a, $b) => $a->getSize() <=> $b->getSize()); ``` -Mehrere verschiedene Suchvorgänge .[#toc-multiple-different-searches] ---------------------------------------------------------------------- +Mehrere verschiedene Suchen +--------------------------- -Wenn Sie mehrere verschiedene Dateien an unterschiedlichen Orten oder nach unterschiedlichen Kriterien suchen müssen, verwenden Sie die Methode `append()`. Sie gibt ein neues `Finder` Objekt zurück, so dass Sie Methodenaufrufe verketten können: +Wenn Sie mehrere unterschiedliche Dateien an verschiedenen Orten oder mit unterschiedlichen Kriterien finden müssen, verwenden Sie die Methode `append()`. Sie gibt ein neues `Finder`-Objekt zurück, sodass Methodenaufrufe verkettet werden können: ```php -($finder = new Finder) // speichert den ersten Finder in der Variable $finder! - ->files('*.php') // Suche nach *.php Dateien in src/ +($finder = new Finder) // wir speichern den ersten Finder in der Variable $finder! + ->files('*.php') // in src/ suchen wir nach *.php Dateien ->from('src') ->append() - ->files('*.md') // in docs/ suchen Sie nach *.md-Dateien + ->files('*.md') // in docs/ suchen wir nach *.md Dateien ->from('docs') ->append() - ->files('*.json'); // im aktuellen Ordner wird nach *.json-Dateien gesucht + ->files('*.json'); // im aktuellen Ordner suchen wir nach *.json Dateien ``` -Alternativ können Sie auch die Methode `append()` verwenden, um eine bestimmte Datei (oder ein Array von Dateien) hinzuzufügen. Sie gibt dann das gleiche Objekt `Finder` zurück: +Alternativ kann die Methode `append()` verwendet werden, um eine bestimmte Datei (oder ein Array von Dateien) hinzuzufügen. In diesem Fall gibt sie dasselbe `Finder`-Objekt zurück: ```php $finder = Finder::findFiles('*.txt') @@ -212,12 +213,12 @@ $finder = Finder::findFiles('*.txt') ``` -FileInfo .[#toc-fileinfo] -------------------------- +FileInfo +-------- -[Nette\Utils\FileInfo |api:] ist eine Klasse, die eine Datei oder ein Verzeichnis in den Suchergebnissen darstellt. Sie ist eine Erweiterung der Klasse [SplFileInfo |php:SplFileInfo], die Informationen wie Dateigröße, Datum der letzten Änderung, Name, Pfad usw. liefert. +[api:Nette\Utils\FileInfo] ist eine Klasse, die eine Datei oder ein Verzeichnis in den Suchergebnissen repräsentiert. Sie erweitert die Klasse [php:SplFileInfo] und stellt Informationen wie Dateigröße, letztes Änderungsdatum, Name, Pfad usw. bereit. -Darüber hinaus bietet sie Methoden zur Rückgabe relativer Pfade, was bei der Suche in der Tiefe nützlich ist: +Zusätzlich bietet sie Methoden zur Rückgabe des relativen Pfads, was bei der Tiefensuche nützlich ist: ```php foreach (Finder::findFiles('*.jpg')->from('.') as $file) { @@ -226,7 +227,7 @@ foreach (Finder::findFiles('*.jpg')->from('.') as $file) { } ``` -Sie haben auch Methoden zum Lesen und Schreiben des Inhalts einer Datei: +Außerdem stehen Methoden zum Lesen und Schreiben des Dateiinhalts zur Verfügung: ```php foreach ($finder as $file) { @@ -237,12 +238,12 @@ foreach ($finder as $file) { ``` -Ergebnisse als Array zurückgeben .[#toc-returning-results-as-an-array] ----------------------------------------------------------------------- +Rückgabe der Ergebnisse als Array +--------------------------------- -Wie in den Beispielen gesehen, implementiert der Finder die Schnittstelle `IteratorAggregate`, so dass Sie `foreach` verwenden können, um die Ergebnisse zu durchsuchen. Er ist so programmiert, dass die Ergebnisse erst beim Durchsuchen geladen werden. Wenn Sie also eine große Anzahl von Dateien haben, wartet er nicht darauf, dass alle gelesen werden. +Wie in den Beispielen gezeigt, implementiert der Finder das `IteratorAggregate`-Interface, sodass Sie `foreach` verwenden können, um die Ergebnisse zu durchlaufen. Er ist so implementiert, dass die Ergebnisse erst während des Durchlaufens geladen werden (Lazy Loading). Wenn Sie also eine große Anzahl von Dateien haben, müssen Sie nicht warten, bis alle eingelesen sind. -Sie können sich die Ergebnisse auch als Array von `Nette\Utils\FileInfo` -Objekten zurückgeben lassen, indem Sie die Methode `collect()` verwenden. Das Array ist nicht assoziativ, sondern numerisch. +Mit der Methode `collect()` können Sie die Ergebnisse auch als Array von `Nette\Utils\FileInfo`-Objekten erhalten. Das Array ist numerisch indiziert (nicht assoziativ). ```php $array = $finder->findFiles('*.php')->collect(); diff --git a/utils/de/floats.texy b/utils/de/floats.texy index 89fe22cfa3..119dfbe3fb 100644 --- a/utils/de/floats.texy +++ b/utils/de/floats.texy @@ -1,8 +1,8 @@ -Schwebekörper-Funktionen -************************ +Arbeiten mit Fließkommazahlen +***************************** .[perex] -[api:Nette\Utils\Floats] ist eine statische Klasse mit nützlichen Funktionen zum Vergleich von Fließkommazahlen. +[api:Nette\Utils\Floats] ist eine statische Klasse mit nützlichen Funktionen zum Vergleichen von Fließkommazahlen (Floats). Installation: @@ -11,18 +11,17 @@ Installation: composer require nette/utils ``` -Alle Beispiele gehen davon aus, dass der folgende Klassenalias definiert ist: +Alle Beispiele setzen voraus, dass der folgende Alias definiert wurde: ```php use Nette\Utils\Floats; ``` -Motivation .[#toc-motivation] -============================= +Motivation +========== -Sie fragen sich, wozu eine Float-Vergleichsklasse gut ist? Sie können die Operatoren `<`, `>`, `===`, denken Sie. -Das ist nicht ganz richtig. Was, glauben Sie, wird dieser Code ausgeben? +Sie fragen sich vielleicht, warum es überhaupt eine Klasse zum Vergleichen von Fließkommazahlen gibt? Man kann doch die Operatoren `<`, `>`, `===` verwenden und ist fertig. Das stimmt nicht ganz. Was denken Sie, gibt dieser Code aus? ```php $a = 0.1 + 0.2; @@ -30,27 +29,30 @@ $b = 0.3; echo $a === $b ? 'same' : 'not same'; ``` -Wenn Sie den Code ausführen, werden einige von Ihnen überrascht sein, dass das Programm `not same` ausgibt. +Wenn Sie den Code ausführen, werden einige von Ihnen sicherlich überrascht sein, dass das Programm `not same` ausgibt. -Bei mathematischen Operationen mit Fließkommazahlen treten aufgrund der Umrechnung zwischen Dezimal- und Binärsystem Fehler auf. Zum Beispiel entspricht `0.1 + 0.2` `0.300000000000000044…` . Daher müssen wir beim Vergleich von Fließkommazahlen eine kleine Differenz ab einer bestimmten Dezimalstelle tolerieren. +Bei mathematischen Operationen mit Fließkommazahlen kommt es aufgrund der Konvertierung zwischen dem Dezimal- und Binärsystem zu Genauigkeitsfehlern. Beispielsweise ergibt `0.1 + 0.2` das Ergebnis `0.300000000000000044…`. Deshalb müssen wir beim Vergleichen eine kleine Toleranz für Unterschiede ab einer bestimmten Dezimalstelle zulassen. -Und genau das tut die Klasse `Floats`. Der folgende Vergleich wird wie erwartet funktionieren: +Und genau das erledigt die Klasse `Floats`. Der folgende Vergleich funktioniert nun wie erwartet: ```php -echo Floats::areEqual($a, $b) ? 'gleich' : 'nicht gleich'; // gleich +echo Floats::areEqual($a, $b) ? 'same' : 'not same'; // same ``` -Beim Versuch, `NAN` zu vergleichen, kommt es zu einer `\LogicException` Ausnahme. +Beim Versuch, `NAN` zu vergleichen, wird eine `\LogicException` ausgelöst. +.[tip] +Die Klasse `Floats` toleriert Unterschiede, die kleiner als `1e-10` sind. Wenn Sie mit höherer Präzision arbeiten müssen, verwenden Sie stattdessen die [BCMath-Bibliothek |php:book.bc]. -Float-Vergleich .[#toc-float-comparison] -======================================== + +Vergleich von Fließkommazahlen +============================== areEqual(float $a, float $b): bool .[method] -------------------------------------------- -Gibt `true` zurück, wenn `$a` = `$b`. +Gibt `true` zurück, wenn `$a` = `$b` ist. ```php Floats::areEqual(10, 10.0); // true @@ -60,7 +62,7 @@ Floats::areEqual(10, 10.0); // true isLessThan(float $a, float $b): bool .[method] ---------------------------------------------- -Gibt `true` zurück, wenn `$a` < `$b`. +Gibt `true` zurück, wenn `$a < $b` ist. ```php Floats::isLessThan(9.5, 10.2); // true @@ -71,7 +73,7 @@ Floats::isLessThan(INF, 10.2); // false isLessThanOrEqualTo(float $a, float $b): bool .[method] ------------------------------------------------------- -Gibt `true` zurück, wenn `$a` <= `$b`. +Gibt `true` zurück, wenn `$a <= $b` ist. ```php Floats::isLessThanOrEqualTo(9.5, 10.2); // true @@ -82,7 +84,7 @@ Floats::isLessThanOrEqualTo(10.25, 10.25); // true isGreaterThan(float $a, float $b): bool .[method] ------------------------------------------------- -Gibt `true` zurück, wenn `$a` > `$b`. +Gibt `true` zurück, wenn `$a > $b` ist. ```php Floats::isGreaterThan(9.5, -10.2); // true @@ -93,7 +95,7 @@ Floats::isGreaterThan(9.5, 10.2); // false isGreaterThanOrEqualTo(float $a, float $b): bool .[method] ---------------------------------------------------------- -Gibt `true` zurück, wenn `$a` >= `$b`. +Gibt `true` zurück, wenn `$a >= $b` ist. ```php Floats::isGreaterThanOrEqualTo(9.5, 10.2); // false @@ -104,19 +106,19 @@ Floats::isGreaterThanOrEqualTo(10.2, 10.2); // true compare(float $a, float $b): int .[method] ------------------------------------------ -Wenn `$a` < `$b` ist, wird `-1` zurückgegeben, wenn sie gleich sind, wird `0` and if `$a` > `$b` zurückgegeben und `1`. +Gibt `-1` zurück, wenn `$a < $b` ist, `0`, wenn sie gleich sind, und `1`, wenn `$a > $b` ist. -Sie kann z. B. mit der Funktion `usort` verwendet werden. +Kann beispielsweise mit der Funktion `usort()` verwendet werden. ```php $arr = [1, 5, 2, -3.5]; -usort($arr, [Float::class, 'compare']); -// $arr ist [-3.5, 1, 2, 5] +usort($arr, [Floats::class, 'compare']); +// $arr ist jetzt [-3.5, 1, 2, 5] ``` -Helfer-Funktionen .[#toc-helpers-functions] -=========================================== +Hilfsfunktionen +=============== isZero(float $value): bool .[method] diff --git a/utils/de/helpers.texy b/utils/de/helpers.texy index 5dbf363fe2..051fa22a93 100644 --- a/utils/de/helpers.texy +++ b/utils/de/helpers.texy @@ -2,7 +2,7 @@ Hilfsfunktionen *************** .[perex] -[api:Nette\Utils\Helpers] ist eine statische Klasse mit nützlichen Funktionen. +[api:Nette\Utils\Helpers] ist eine statische Klasse mit nützlichen Hilfsfunktionen. Installation: @@ -11,7 +11,7 @@ Installation: composer require nette/utils ``` -Alle Beispiele gehen davon aus, dass der folgende Klassenalias definiert ist: +Alle Beispiele setzen voraus, dass der folgende Alias definiert wurde: ```php use Nette\Utils\Helpers; @@ -21,7 +21,7 @@ use Nette\Utils\Helpers; capture(callable $cb): string .[method] --------------------------------------- -Führt einen Callback aus und gibt die erfasste Ausgabe als String zurück. +Führt einen Callback `$cb` aus und gibt dessen erfasste Ausgabe als String zurück. ```php $res = Helpers::capture(function () use ($template) { @@ -33,7 +33,7 @@ $res = Helpers::capture(function () use ($template) { clamp(int|float $value, int|float $min, int|float $max): int|float .[method] ---------------------------------------------------------------------------- -Gibt einen Wert zurück, der auf den einschließenden Bereich von min und max geklemmt ist. +Schränkt einen Wert `$value` auf den angegebenen inklusiven Bereich zwischen `$min` und `$max` ein. ```php Helpers::clamp($level, 0, 255); @@ -43,8 +43,7 @@ Helpers::clamp($level, 0, 255); compare(mixed $left, string $operator, mixed $right): bool .[method] -------------------------------------------------------------------- -Vergleicht zwei Werte auf die gleiche Weise wie PHP. Es wird zwischen den Operatoren `>`, `>=`, `<`, `<=`, `=`, `==`, `===`, `!=`, `!==`, `<>` unterschieden. -Die Funktion ist nützlich in Situationen, in denen der Operator variabel ist. +Vergleicht zwei Werte (`$left`, `$right`) auf die gleiche Weise wie PHP, unter Verwendung des angegebenen `$operator`. Unterstützt die Operatoren `>`, `>=`, `<`, `<=`, `=`, `==`, `===`, `!=`, `!==`, `<>`. Die Methode ist nützlich in Situationen, in denen der Operator variabel ist. ```php Helpers::compare(10, '<', 20); // true @@ -54,7 +53,7 @@ Helpers::compare(10, '<', 20); // true falseToNull(mixed $value): mixed .[method] ------------------------------------------ -Wandelt `false` in `null` um, ändert aber keine anderen Werte. +Konvertiert `false` zu `null`, andere Werte bleiben unverändert. ```php Helpers::falseToNull(false); // null @@ -65,7 +64,7 @@ Helpers::falseToNull(123); // 123 getLastError(): string .[method] -------------------------------- -Gibt den letzten aufgetretenen PHP-Fehler zurück oder einen leeren String, wenn kein Fehler aufgetreten ist. Im Gegensatz zu `error_get_last()` wird sie nicht von der PHP-Direktive `html_errors` beeinflusst und gibt immer Text, nicht HTML, zurück. +Gibt den letzten PHP-Fehler als String zurück oder einen leeren String, wenn kein Fehler aufgetreten ist. Im Gegensatz zu `error_get_last()` wird das Ergebnis nicht von der PHP-Direktive `html_errors` beeinflusst und ist immer Text (kein HTML). ```php Helpers::getLastError(); @@ -75,13 +74,13 @@ Helpers::getLastError(); getSuggestion(string[] $possibilities, string $value): ?string .[method] ------------------------------------------------------------------------ -Sucht nach einer Zeichenkette von `$possibilities`, die `$value` am ähnlichsten ist, aber nicht dieselbe. Unterstützt nur 8-Bit-Kodierungen. +Sucht in den `$possibilities` nach dem String, der `$value` am ähnlichsten, aber nicht identisch ist. Unterstützt nur 8-Bit-Kodierungen. -Sie ist nützlich, wenn eine bestimmte Option nicht gültig ist und wir dem Benutzer eine ähnliche Option vorschlagen wollen (die sich aber unterscheidet, so dass die gleiche Zeichenkette ignoriert wird). Auf diese Weise erstellt Nette die Nachrichten `did you mean ...?`. +Dies ist nützlich, wenn eine angegebene Option ungültig ist und man dem Benutzer eine ähnliche vorschlagen möchte (aber eine *andere*, weshalb der identische String ignoriert wird). Auf diese Weise erstellt Nette die `Did you mean ...?`-Meldungen. ```php $items = ['foo', 'bar', 'baz']; Helpers::getSuggestion($items, 'fo'); // 'foo' Helpers::getSuggestion($items, 'barr'); // 'bar' -Helpers::getSuggestion($items, 'baz'); // 'bar', ne 'baz' +Helpers::getSuggestion($items, 'baz'); // 'bar', nicht 'baz' ``` diff --git a/utils/de/html-elements.texy b/utils/de/html-elements.texy index 695a3f416e..1e70aaec46 100644 --- a/utils/de/html-elements.texy +++ b/utils/de/html-elements.texy @@ -2,46 +2,46 @@ HTML-Elemente ************* .[perex] -Die Klasse [api:Nette\Utils\Html] ist ein Hilfsmittel zur Generierung von HTML-Code, der Cross Site Scripting (XSS)-Schwachstellen verhindert. +Die Klasse [api:Nette\Utils\Html] ist ein Helfer zur Generierung von HTML-Code, der hilft, Cross-Site-Scripting (XSS)-Schwachstellen zu verhindern. -Sie funktioniert so, dass ihre Objekte HTML-Elemente darstellen, wir setzen ihre Parameter und lassen sie rendern: +Sie funktioniert, indem ihre Objekte HTML-Elemente repräsentieren, deren Attribute gesetzt und die dann gerendert werden können: ```php -$el = Html::el('img'); // erzeugt -Element -$el->src = 'image.jpg'; // setzt src-Attribut -echo $el; // gibt '' aus +$el = Html::el('img'); // erstellt das Element +$el->src = 'image.jpg'; // setzt das Attribut src +echo $el; // gibt '' aus ``` -Die Installation: +Installation: ```shell composer require nette/utils ``` -Alle Beispiele gehen davon aus, dass der folgende Klassenalias definiert ist: +Alle Beispiele setzen voraus, dass der folgende Alias definiert wurde: ```php use Nette\Utils\Html; ``` -Ein HTML-Element erstellen .[#toc-creating-an-html-element] -=========================================================== +Erstellung von HTML-Elementen +============================= -Das Element wird mit der Methode `Html::el()` erstellt: +Ein Element wird mit der Methode `Html::el()` erstellt: ```php -$el = Html::el('img'); // erzeugt -Element +$el = Html::el('img'); // erstellt das Element ``` -Neben dem Namen können Sie weitere Attribute in der HTML-Syntax angeben: +Neben dem Elementnamen können Sie weitere Attribute direkt in HTML-Syntax angeben: ```php $el = Html::el('input type=text class="red important"'); ``` -Oder Sie übergeben sie als assoziatives Array an den zweiten Parameter: +Oder übergeben Sie sie als assoziatives Array im zweiten Parameter: ```php $el = Html::el('input', [ @@ -50,39 +50,39 @@ $el = Html::el('input', [ ]); ``` -Um einen Elementnamen zu ändern und zurückzugeben: +Ändern und Abrufen des Elementnamens: ```php $el->setName('img'); $el->getName(); // 'img' -$el->isEmpty(); // wahr, da ein leeres Element ist +$el->isEmpty(); // true, da ein leeres Element ist (z.B.
                                                                                                                            ,
                                                                                                                            ) ``` -HTML-Attribute .[#toc-html-attributes] -====================================== +HTML-Attribute +============== -Sie können einzelne HTML-Attribute auf drei Arten setzen und abrufen, es bleibt Ihnen überlassen, welche Ihnen besser gefällt. Die erste ist über die Eigenschaften: +Einzelne HTML-Attribute können auf drei Arten geändert und gelesen werden, es hängt von Ihnen ab, welche Ihnen am besten gefällt. Die erste ist über Properties: ```php -$el->src = 'image.jpg'; // setzt src-Attribut +$el->src = 'image.jpg'; // setzt das Attribut src echo $el->src; // 'image.jpg' -unset($el->src); // entfernt das Attribut +unset($el->src); // entfernt das Attribut // oder $el->src = null; ``` -Der zweite Weg ist der Aufruf von Methoden, die wir im Gegensatz zum Setzen von Eigenschaften aneinanderreihen können: +Die zweite Möglichkeit ist der Aufruf von Methoden, die – im Gegensatz zum Setzen von Eigenschaften – verkettet werden können: ```php $el = Html::el('img')->src('image.jpg')->alt('photo'); // photo -$el->alt(null); // entfernt das Attribut +$el->alt(null); // Attribut entfernen ``` -Und der dritte Weg ist der gesprächigste: +Und die dritte Möglichkeit ist die ausführlichste: ```php $el = Html::el('img') @@ -94,9 +94,9 @@ echo $el->getAttribute('src'); // 'image.jpg' $el->removeAttribute('alt'); ``` -In der Masse können Attribute mit `addAttributes(array $attrs)` gesetzt und mit `removeAttributes(array $attrNames)` gelöscht werden. +Attribute können gesammelt mit `addAttributes(array $attrs)` gesetzt und mit `removeAttributes(array $attrNames)` entfernt werden. -Der Wert eines Attributs muss nicht nur ein String sein, es können auch logische Werte für logische Attribute verwendet werden: +Der Wert eines Attributs muss kein String sein; für boolesche Attribute können auch boolesche Werte verwendet werden: ```php $checkbox = Html::el('input')->type('checkbox'); @@ -104,7 +104,7 @@ $checkbox->checked = true; // $checkbox->checked = false; // ``` -Ein Attribut kann auch ein Array von Token sein, die durch Leerzeichen getrennt aufgelistet werden, was sich z.B. für CSS-Klassen eignet: +Ein Attribut kann auch ein Array von Werten sein, die durch Leerzeichen getrennt ausgegeben werden. Dies ist beispielsweise für CSS-Klassen nützlich: ```php $el = Html::el('input'); @@ -114,16 +114,16 @@ $el->class[] = 'top'; echo $el; // '' ``` -Eine Alternative ist ein assoziatives Array, bei dem die Werte angeben, ob der Schlüssel aufgeführt werden soll: +Eine Alternative ist ein assoziatives Array, bei dem die booleschen Werte angeben, ob der jeweilige Schlüssel als Wert ausgegeben werden soll: ```php $el = Html::el('input'); $el->class['active'] = true; -$el->class['top'] = false; +$el->class['top'] = false; // wird nicht ausgegeben echo $el; // '' ``` -CSS-Stile können in Form von assoziativen Arrays geschrieben werden: +CSS-Stile können als assoziatives Array geschrieben werden: ```php $el = Html::el('input'); @@ -132,7 +132,7 @@ $el->style['display'] = 'block'; echo $el; // '' ``` -Wir haben jetzt Eigenschaften verwendet, aber das Gleiche lässt sich auch mit den Methoden machen: +Wir haben eben Eigenschaften verwendet, aber dasselbe kann auch mit Methoden erreicht werden: ```php $el = Html::el('input'); @@ -141,7 +141,7 @@ $el->style('display', 'block'); echo $el; // '' ``` -Oder sogar auf die gesprächigste Art und Weise: +Oder auf die ausführlichste Weise: ```php $el = Html::el('input'); @@ -150,7 +150,7 @@ $el->appendAttribute('style', 'display', 'block'); echo $el; // '' ``` -Eine letzte Sache: Die Methode `href()` kann die Zusammenstellung von Abfrageparametern in einer URL erleichtern: +Noch eine Anmerkung: Die Methode `href()` kann das Zusammenstellen von Query-Parametern in URLs vereinfachen: ```php echo Html::el('a')->href('index.php', [ @@ -161,31 +161,31 @@ echo Html::el('a')->href('index.php', [ ``` -Daten-Attribute .[#toc-data-attributes] ---------------------------------------- +Datenattribute +-------------- -Datenattribute haben eine besondere Unterstützung. Da ihre Namen Bindestriche enthalten, ist der Zugriff über Eigenschaften und Methoden nicht so elegant, daher gibt es eine Methode `data()`: +Datenattribute (`data-*`) werden besonders unterstützt. Da ihre Namen Bindestriche enthalten, ist der Zugriff über Eigenschaften und Methoden weniger elegant. Daher gibt es die Methode `data()`: ```php $el = Html::el('input'); -$el->{'data-max-size'} = '500x300'; // nicht so elegant -$el->data('max-size', '500x300'); // ist elegant +$el->{'data-max-size'} = '500x300'; // weniger elegant +$el->data('max-size', '500x300'); // elegant echo $el; // '' ``` -Wenn der Wert des Datenattributs ein Array ist, wird es automatisch in JSON serialisiert: +Wenn der Wert eines Datenattributs ein Array ist, wird er automatisch nach JSON serialisiert: ```php $el = Html::el('input'); -$el->data('items', [1,2,3]); +$el->data('items', [1, 2, 3]); echo $el; // '' ``` -Element Inhalt .[#toc-element-content] -====================================== +Inhalt des Elements +=================== -Der innere Inhalt des Elements wird mit den Methoden `setHtml()` oder `setText()` festgelegt. Verwenden Sie die erste Methode nur, wenn Sie wissen, dass Sie zuverlässig einen sicheren HTML-String im Parameter übergeben. +Der innere Inhalt eines Elements wird mit den Methoden `setHtml()` oder `setText()` festgelegt. Verwenden Sie `setHtml()` nur, wenn Sie sicher sind, dass der übergebene String vertrauenswürdiges HTML enthält. ```php echo Html::el('span')->setHtml('hello
                                                                                                                            '); @@ -195,7 +195,7 @@ echo Html::el('span')->setText('10 < 20'); // '10 < 20' ``` -Umgekehrt wird der innere Inhalt mit den Methoden `getHtml()` oder `getText()` ermittelt. Die zweite Methode entfernt Tags aus der HTML-Ausgabe und wandelt die HTML-Entities in Zeichen um. +Umgekehrt wird der innere Inhalt mit den Methoden `getHtml()` oder `getText()` abgerufen. Letztere entfernt HTML-Tags aus dem Inhalt und konvertiert HTML-Entitäten in die entsprechenden Zeichen. ```php echo $el->getHtml(); // '10 < 20' @@ -203,10 +203,10 @@ echo $el->getText(); // '10 < 20' ``` -Kind-Knoten .[#toc-child-nodes] -------------------------------- +Kindknoten +---------- -Der innere Inhalt eines Elements kann auch ein Array von Kindern sein. Jedes dieser Elemente kann entweder eine Zeichenkette oder ein anderes `Html` Element sein. Sie werden mit `addHtml()` oder `addText()` eingefügt: +Der Inhalt eines Elements kann auch ein Array von Kindknoten (Children) sein. Jeder Kindknoten kann entweder ein String oder ein weiteres `Html`-Element sein. Sie werden mit `addHtml()` oder `addText()` hinzugefügt: ```php $el = Html::el('span') @@ -216,16 +216,16 @@ $el = Html::el('span') // hello
                                                                                                                            10 < 20
                                                                                                                            ``` -Eine andere Möglichkeit, einen neuen `Html` Knoten zu erstellen und einzufügen: +Eine weitere Möglichkeit, einen neuen `Html`-Knoten zu erstellen und hinzuzufügen: ```php -$el = Html::el('ul') - ->create('li', ['class' => 'first']) - ->setText('hello'); -//
                                                                                                                            • hello
                                                                                                                            +$ul = Html::el('ul'); +$ul->create('li', ['class' => 'first']) + ->setText('první'); +//
                                                                                                                            • první
                                                                                                                            ``` -Sie können mit Knoten arbeiten, als wären sie Array-Elemente. Greifen Sie also mit eckigen Klammern auf die einzelnen Knoten zu, zählen Sie sie mit `count()` und iterieren Sie über sie: +Mit den Kindknoten kann wie mit einem Array gearbeitet werden. Das heißt, man kann auf einzelne Knoten über eckige Klammern zugreifen, ihre Anzahl mit `count()` ermitteln und über sie iterieren: ```php $el = Html::el('div'); @@ -238,20 +238,20 @@ foreach ($el as $child) { /* ... */ } echo count($el); // 2 ``` -Mit `insert(?int $index, $child, bool $replace = false)` kann ein neuer Knoten an einer bestimmten Position eingefügt werden. Wenn `$replace = false`, wird das Element an der Position `$index` eingefügt und die anderen verschoben. Wenn `$index = null`, wird ein Element am Ende angehängt. +Ein neuer Knoten kann an einer bestimmten Position mit `insert(?int $index, $child, bool $replace = false)` eingefügt werden. Wenn `$replace = false` ist, wird das Element an der Position `$index` eingefügt und die nachfolgenden Elemente werden verschoben. Wenn `$index = null` ist, wird das Element am Ende angefügt. ```php -// fügt das Element an der ersten Position ein und schiebt die anderen vor +// Fügt das Element an die erste Position ein und verschiebt die anderen $el->insert(0, Html::el('span')); ``` -Alle Knoten werden von der Methode `getChildren()` zurückgegeben und von der Methode `removeChildren()` entfernt. +Alle Kindknoten können mit der Methode `getChildren()` abgerufen und mit `removeChildren()` entfernt werden. -Erstellen eines Dokumentfragments .[#toc-creating-a-document-fragment] ----------------------------------------------------------------------- +Erstellung eines Dokumentfragments +---------------------------------- -Wenn Sie mit einem Array von Knoten arbeiten wollen und nicht an dem umhüllenden Element interessiert sind, können Sie ein sogenanntes *Dokumentenfragment* erstellen, indem Sie `null` anstelle des Elementnamens übergeben: +Wenn man mit einem Array von Knoten arbeiten möchte, ohne ein umschließendes Element zu benötigen, kann man ein sogenanntes *Dokumentfragment* erstellen, indem man `null` anstelle eines Elementnamens übergibt: ```php $el = Html::el(null) @@ -261,7 +261,7 @@ $el = Html::el(null) // hello
                                                                                                                            10 < 20
                                                                                                                            ``` -Die Methoden `fromHtml()` und `fromText()` bieten eine schnellere Möglichkeit, ein Fragment zu erstellen: +Einen schnelleren Weg zur Erstellung eines Fragments bieten die Methoden `fromHtml()` und `fromText()`: ```php $el = Html::fromHtml('hello
                                                                                                                            '); @@ -272,15 +272,15 @@ echo $el; // '10 < 20' ``` -HTML-Ausgabe generieren .[#toc-generating-html-output] -====================================================== +Generierung der HTML-Ausgabe +============================ -Der einfachste Weg, ein HTML-Element zu erzeugen, ist die Verwendung von `echo` oder die Umwandlung eines Objekts in `(string)`. Sie können auch öffnende oder schließende Tags und Attribute separat drucken: +Der einfachste Weg, ein HTML-Element auszugeben, ist die Verwendung von `echo` oder die Typumwandlung des Objekts zu `(string)`. Es ist auch möglich, das öffnende Tag, das schließende Tag und die Attribute separat auszugeben: ```php $el = Html::el('div class=header')->setText('hello'); -echo $el; // '
                                                                                                                            ' +echo $el; // '
                                                                                                                            hello
                                                                                                                            ' $s = (string) $el; // '
                                                                                                                            hello
                                                                                                                            ' $s = $el->toHtml(); // '
                                                                                                                            hello
                                                                                                                            ' $s = $el->toText(); // 'hello' @@ -289,7 +289,7 @@ echo $el->endTag(); // '' echo $el->attributes(); // 'class="header"' ``` -Eine wichtige Funktion ist der automatische Schutz gegen [Cross Site Scripting (XSS) |nette:glossary#cross-site-scripting-xss]. Alle Attributwerte oder Inhalte, die mit `setText()` oder `addText()` eingefügt werden, werden zuverlässig escaped: +Ein wichtiges Merkmal ist der automatische Schutz vor [Cross-Site-Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS]. Alle Attributwerte oder Inhalte, die mit `setText()` oder `addText()` eingefügt werden, werden zuverlässig maskiert (escaped): ```php echo Html::el('div') @@ -300,17 +300,17 @@ echo Html::el('div') ``` -Konvertierung HTML ↔ Text .[#toc-conversion-html-text] -====================================================== +Konvertierung HTML ↔ Text +========================= -Sie können die statische Methode `htmlToText()` verwenden, um HTML in Text umzuwandeln: +Zur Konvertierung von HTML in reinen Text können Sie die statische Methode `htmlToText()` verwenden: ```php -echo Html::htmlToText('Ein & Zwei'); // 'Eins & Zwei' +echo Html::htmlToText('One & Two'); // 'One & Two' ``` -HtmlStringable .[#toc-htmlstringable] -===================================== +HtmlStringable +============== -Das Objekt `Nette\Utils\Html` implementiert die Schnittstelle `Nette\HtmlStringable`, die z.B. von Latte oder Formularen verwendet wird, um Objekte zu unterscheiden, die eine Methode `__toString()` haben, die HTML-Code zurückgibt. Das doppelte Escaping tritt also nicht auf, wenn wir z.B. das Objekt in der Vorlage mit `{$el}` ausgeben. +Das Objekt `Nette\Utils\Html` implementiert das `Nette\HtmlStringable`-Interface. Dieses Interface wird beispielsweise von Latte oder Formularen verwendet, um Objekte zu erkennen, deren `__toString()`-Methode HTML-Code zurückgibt. Dadurch wird verhindert, dass der Inhalt doppelt maskiert wird, wenn ein solches Objekt beispielsweise in einer Vorlage mit `{$el}` ausgegeben wird. diff --git a/utils/de/images.texy b/utils/de/images.texy index 30354d6095..a51f2c5afa 100644 --- a/utils/de/images.texy +++ b/utils/de/images.texy @@ -1,38 +1,40 @@ -Bildfunktionen -************** +Arbeiten mit Bildern +******************** .[perex] -Die Klasse [api:Nette\Utils\Image] vereinfacht die Bildbearbeitung, z. B. das Ändern der Größe, Beschneiden, Schärfen, Zeichnen oder Zusammenführen mehrerer Bilder. +Die Klasse [api:Nette\Utils\Image] vereinfacht Bildmanipulationen wie Größenänderung, Zuschneiden, Schärfen, Zeichnen oder das Zusammenfügen mehrerer Bilder. -PHP verfügt über einen umfangreichen Satz von Funktionen zur Bearbeitung von Bildern. Aber die API ist nicht sehr schön. Es wäre kein Neat Framework, wenn es eine sexy API gäbe. +PHP verfügt über eine umfangreiche Sammlung von Funktionen zur Bildmanipulation. Ihre API ist jedoch nicht sehr komfortabel. Es wäre nicht das Nette Framework, wenn es nicht eine elegante API dafür anbieten würde. -Einbau: +Installation: ```shell composer require nette/utils ``` -Die folgenden Beispiele gehen davon aus, dass der folgende Klassenalias definiert ist: +Alle Beispiele setzen voraus, dass die folgenden Aliase definiert wurden: ```php use Nette\Utils\Image; +use Nette\Utils\ImageColor; +use Nette\Utils\ImageType; ``` -Ein Bild erstellen .[#toc-creating-an-image] -============================================ +Erstellung eines Bildes +======================= -Wir erstellen ein neues Echtfarbbild, zum Beispiel mit den Maßen 100×200: +Wir erstellen ein neues True-Color-Bild, beispielsweise mit den Abmessungen 100×200: ```php $image = Image::fromBlank(100, 200); ``` -Optional können Sie eine Hintergrundfarbe angeben (Standard ist schwarz): +Optional kann eine Hintergrundfarbe angegeben werden (Standard ist Schwarz): ```php -$image = Image::fromBlank(100, 200, Image::rgb(125, 0, 0)); +$image = Image::fromBlank(100, 200, ImageColor::rgb(125, 0, 0)); ``` Oder wir laden das Bild aus einer Datei: @@ -41,129 +43,138 @@ Oder wir laden das Bild aus einer Datei: $image = Image::fromFile('nette.jpg'); ``` -Unterstützte Formate sind JPEG, PNG, GIF, WebP, AVIF und BMP, aber Ihre PHP-Version muss diese Formate ebenfalls unterstützen (siehe `phpinfo()`, Abschnitt GD). Animationen werden nicht unterstützt. -Müssen Sie das Bildformat beim Laden erkennen? Die Methode gibt das Format im zweiten Parameter zurück: +Speichern des Bildes +==================== + +Das Bild kann in eine Datei gespeichert werden: ```php -$image = Image::fromFile('nette.jpg', $type); -// $type ist Image::JPEG, Image::PNG, Image::GIF, Image::WEBP, Image::AVIF oder Image::BMP +$image->save('resampled.jpg'); ``` -Nur die Erkennung ohne Laden des Bildes wird von `Image::detectTypeFromFile()` durchgeführt. +Wir können die Kompressionsqualität angeben: im Bereich 0..100 für JPEG (Standard 85), WEBP (Standard 80) und AVIF (Standard 30) sowie im Bereich 0..9 für PNG (Standard 9): +```php +$image->save('resampled.jpg', 80); // JPEG, Qualität 80% +``` -Speichern Sie das Bild .[#toc-save-the-image] -============================================= - -Das Bild kann in einer Datei gespeichert werden: +Wenn das Format aus der Dateiendung nicht ersichtlich ist, kann es explizit mit einer [Konstante |#Formate] angegeben werden: ```php -$image->save('resampled.jpg'); +$image->save('resampled.tmp', null, ImageType::JPEG); ``` -Wir können die Komprimierungsqualität im Bereich 0..100 für JPEG (Standard 85), WEBP (Standard 80) und AVIF (Standard 30) und 0..9 für PNG (Standard 9) festlegen: +Das Bild kann statt auf die Festplatte auch in eine Variable geschrieben werden: ```php -$image->save('resampled.jpg', 80); // JPEG, Qualität 80% +$data = $image->toString(ImageType::JPEG, 80); // JPEG, Qualität 80% ``` -Wenn das Format nicht aus der Dateierweiterung ersichtlich ist, kann es durch eine der Konstanten `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` und `Image::BMP` angegeben werden: +oder direkt an den Browser gesendet werden, wobei der entsprechende HTTP-Header `Content-Type` gesetzt wird: ```php -$image->save('resampled.tmp', null, Image::JPEG); +// sendet den Header Content-Type: image/png +$image->send(ImageType::PNG); ``` -Das Bild kann in eine Variable statt auf die Festplatte geschrieben werden: + +Formate +======= + +Unterstützte Formate sind JPEG, PNG, GIF, WebP, AVIF und BMP. Sie müssen jedoch auch von Ihrer PHP-Installation unterstützt werden, was Sie mit der Methode [#isTypeSupported()] überprüfen können. Animierte Bilder werden nicht unterstützt. + +Die Formate werden durch die Konstanten `ImageType::JPEG`, `ImageType::PNG`, `ImageType::GIF`, `ImageType::WEBP`, `ImageType::AVIF` und `ImageType::BMP` repräsentiert. ```php -$data = $image->toString(Image::JPEG, 80); // JPEG, Qualität 80%. +$supported = Image::isTypeSupported(ImageType::JPEG); ``` -oder direkt an den Browser mit dem entsprechenden HTTP-Header `Content-Type` senden: +Müssen Sie das Bildformat beim Laden erkennen? Die Methode gibt es im zweiten Parameter zurück: ```php -// sendet Kopfzeile Content-Type: image/png -$image->send(Image::PNG); +$image = Image::fromFile('nette.jpg', $type); ``` +Die reine Formaterkennung ohne Laden des Bildes erfolgt durch `Image::detectTypeFromFile()`. + -Bildgröße ändern .[#toc-image-resize] -===================================== +Größenänderung +============== -Ein gängiger Vorgang ist die Größenänderung eines Bildes. Die aktuellen Abmessungen werden von den Methoden `getWidth()` und `getHeight()` zurückgegeben. +Eine häufige Operation ist die Änderung der Bildgröße. Die aktuellen Abmessungen werden von den Methoden `getWidth()` und `getHeight()` zurückgegeben. -Die Methode `resize()` wird für die Größenänderung verwendet. Dies ist ein Beispiel für eine proportionale Größenänderung, so dass sie 500×300 Pixel nicht überschreitet (entweder ist die Breite genau 500px oder die Höhe genau 300px, eine der Dimensionen wird berechnet, um das Seitenverhältnis beizubehalten): +Zur Größenänderung dient die Methode `resize()`. Beispiel für eine proportionale Größenänderung, sodass die Dimensionen 500×300 Pixel nicht überschritten werden (entweder wird die Breite genau 500 px oder die Höhe genau 300 px; die jeweils andere Dimension wird so berechnet, dass das Seitenverhältnis erhalten bleibt): ```php $image->resize(500, 300); ``` -Es ist möglich, nur eine Dimension festzulegen, und die zweite Dimension wird berechnet: +Es ist möglich, nur eine Dimension anzugeben; die andere wird dann berechnet: ```php -$image->resize(500, null); // Breite 500px, Höhe auto +$image->resize(500, null); // Breite 500px, Höhe wird berechnet -$image->resize(null, 300); // Breite auto, Höhe 300px +$image->resize(null, 300); // Breite wird berechnet, Höhe 300px ``` -Jedes Maß kann in Prozenten angegeben werden: +Jede Dimension kann auch in Prozent angegeben werden: ```php $image->resize('75%', 300); // 75 % × 300px ``` -Das Verhalten von `resize` kann durch die folgenden Flags beeinflusst werden. Alle außer `Image::Stretch` behalten das Seitenverhältnis bei. +Das Verhalten von `resize()` kann durch die folgenden Flags beeinflusst werden. Alle Flags außer `Image::Stretch` erhalten das Seitenverhältnis. |--------------------------------------------------------------------------------------- -| Flagge | Beschreibung +| Flag | Beschreibung |--------------------------------------------------------------------------------------- -| `Image::OrSmaller` (Standard) | Die resultierenden Abmessungen sind kleiner oder gleich den angegebenen -| `Image::OrBigger` | füllt den Zielbereich aus und erweitert ihn möglicherweise in eine Richtung -| `Image::Cover` | füllt den gesamten Bereich aus und schneidet ab, was darüber hinausgeht -| `Image::ShrinkOnly` | skaliert nur nach unten (erweitert ein kleines Bild nicht) -| `Image::Stretch` | behält das Seitenverhältnis nicht bei +| `Image::OrSmaller` (Standard) | Die resultierenden Abmessungen sind kleiner oder gleich den angeforderten Abmessungen +| `Image::OrBigger` | Füllt (und überschreitet möglicherweise in einer Dimension) die Zielfläche +| `Image::Cover` | Füllt die Zielfläche und schneidet ab, was übersteht +| `Image::ShrinkOnly` | Nur Verkleinerung (verhindert das Strecken eines kleinen Bildes) +| `Image::Stretch` | Seitenverhältnis nicht beibehalten -Die Flaggen werden als drittes Argument der Funktion übergeben: +Die Flags werden als drittes Argument der Methode übergeben: ```php $image->resize(500, 300, Image::OrBigger); ``` -Flaggen können kombiniert werden: +Die Flags können kombiniert werden: ```php $image->resize(500, 300, Image::ShrinkOnly | Image::Stretch); ``` -Bilder können vertikal oder horizontal gespiegelt werden, indem eine der Dimensionen (oder beide) als negative Zahl angegeben wird: +Bilder können vertikal oder horizontal gespiegelt werden, indem eine oder beide Dimensionen als negative Zahl angegeben werden: ```php $flipped = $image->resize(null, '-100%'); // vertikal spiegeln -$flipped = $image->resize('-100%', '-100%'); // Drehen um 180° +$flipped = $image->resize('-100%', '-100%'); // um 180° drehen -$flipped = $image->resize(-125, 500); // Größe ändern und horizontal spiegeln +$flipped = $image->resize(-125, 500); // Größe ändern & horizontal spiegeln ``` -Nach der Verkleinerung des Bildes können wir es durch Schärfen verbessern: +Nach dem Verkleinern kann das Aussehen des Bildes durch leichtes Nachschärfen verbessert werden: ```php $image->sharpen(); ``` -Beschneiden .[#toc-cropping] -============================ +Zuschneiden +=========== -Die Methode `crop()` wird für den Anbau verwendet: +Zum Zuschneiden dient die Methode `crop()`: ```php $image->crop($left, $top, $width, $height); ``` -Wie bei `resize()` können alle Werte in Prozenten angegeben werden. Die Prozentsätze für `$left` und `$top` werden aus dem verbleibenden Platz berechnet, ähnlich wie bei der CSS-Eigenschaft `background-position`: +Ähnlich wie bei `resize()` können alle Werte in Prozent angegeben werden. Prozentangaben für `$left` und `$top` beziehen sich auf den verbleibenden Platz, ähnlich der CSS-Eigenschaft `background-position`: ```php $image->crop('100%', '50%', '80%', '80%'); @@ -172,188 +183,213 @@ $image->crop('100%', '50%', '80%', '80%'); [* crop.svg *] -Das Bild kann auch automatisch beschnitten werden, z. B. um schwarze Ränder abzuschneiden: +Das Bild kann auch automatisch zugeschnitten werden, beispielsweise um schwarze Ränder zu entfernen: ```php $image->cropAuto(IMG_CROP_BLACK); ``` -Die Methode `cropAuto()` ist eine Objektkapselung der Funktion `imagecropauto()`, weitere Informationen finden Sie in der [zugehörigen Dokumentation |https://www.php.net/manual/en/function.imagecropauto]. +Die Methode `cropAuto()` ist der objektorientierte Ersatz für die Funktion `imagecropauto()`. Weitere Informationen finden Sie in [deren Dokumentation |https://www.php.net/manual/en/function.imagecropauto]. + +Farben .{data-version:4.0.2} +============================ -Zeichnen und Editieren .[#toc-drawing-and-editing] -================================================== +Die Methode `ImageColor::rgb()` ermöglicht die Definition einer Farbe mittels Rot-, Grün- und Blauwerten (RGB). Optional kann auch ein Transparenzwert im Bereich von 0 (vollständig transparent) bis 1 (vollständig undurchsichtig) angegeben werden, genau wie in CSS. -Sie können zeichnen, Sie können schreiben, Sie können alle PHP-Funktionen für die Arbeit mit Bildern verwenden, wie z.B. [imagefilledellipse() |https://www.php.net/manual/en/function.imagefilledellipse.php], aber im Objektstil: +```php +$color = ImageColor::rgb(255, 0, 0); // Rot +$transparentBlue = ImageColor::rgb(0, 0, 255, 0.5); // Halbdurchsichtiges Blau +``` + +Die Methode `ImageColor::hex()` ermöglicht die Definition einer Farbe im Hexadezimalformat, ähnlich wie in CSS. Sie unterstützt die Formate `#rgb`, `#rrggbb`, `#rgba` und `#rrggbbaa`: ```php -$image->filledEllipse($cx, $cy, $width, $height, Image::rgb(255, 0, 0, 63)); +$color = ImageColor::hex("#F00"); // Rot +$transparentGreen = ImageColor::hex("#00FF0080"); // Halbdurchsichtiges Grün ``` -Siehe [Überblick über die Methoden |#Overview of Methods]. +Die Farben können in anderen Methoden wie `ellipse()`, `fill()` usw. verwendet werden. -Mehrere Bilder zusammenführen .[#toc-merge-multiple-images] -=========================================================== +Zeichnen und Bearbeiten +======================= -Sie können problemlos ein anderes Bild in das Bild einfügen: +Ihnen stehen alle PHP-Funktionen zur Bildbearbeitung zur Verfügung (siehe [#Methodenübersicht]), jedoch in einer objektorientierten Kapselung: + +```php +$image->filledEllipse($centerX, $centerY, $width, $height, ImageColor::rgb(255, 0, 0)); +``` + +Da die nativen PHP-Funktionen zum Zeichnen von Rechtecken aufgrund der Parameter für die Koordinaten unpraktisch sind, bietet die `Image`-Klasse Alternativen in Form der Methoden [#rectangleWH()] und [#filledRectangleWH()]. + + +Zusammenfügen mehrerer Bilder +============================= + +Ein anderes Bild kann leicht in das aktuelle Bild eingefügt werden: ```php $logo = Image::fromFile('logo.png'); -$blank = Image::fromBlank(320, 240, Image::rgb(52, 132, 210)); +$blank = Image::fromBlank(320, 240, ImageColor::rgb(52, 132, 210)); // Blaues Bild -// Koordinaten können auch in Prozent angegeben werden -$blank->place($logo, '80%', '80%'); // in der Nähe der rechten unteren Ecke +// Koordinaten können wieder in Prozent angegeben werden +$blank->place($logo, '80%', '80%'); // fügt das Logo nahe der rechten unteren Ecke ein ``` -Beim Einfügen wird der Alphakanal beachtet, außerdem können wir die Transparenz des eingefügten Bildes beeinflussen (wir werden ein sogenanntes Wasserzeichen erstellen): +Beim Einfügen wird der Alphakanal berücksichtigt. Zusätzlich kann die Transparenz des eingefügten Bildes beeinflusst werden (um z.B. ein Wasserzeichen zu erstellen): ```php $blank->place($image, '80%', '80%', 25); // Transparenz beträgt 25 % ``` -Eine solche API ist wirklich ein Vergnügen, nicht wahr? +Eine solche API macht wirklich Freude! -Überblick über die Methoden .[#toc-overview-of-methods] -======================================================= +Methodenübersicht +================= -static fromBlank(int $width, int $height, array $color=null): Image .[method] ------------------------------------------------------------------------------ -Erzeugt ein neues Echtfarbbild mit den angegebenen Abmessungen. Die Standardfarbe ist Schwarz. +static fromBlank(int $width, int $height, ?ImageColor $color=null): Image .[method] +----------------------------------------------------------------------------------- +Erstellt ein neues True-Color-Bild mit den angegebenen Abmessungen (`$width`, `$height`) und optionaler Hintergrundfarbe `$color`. Die Standardfarbe ist Schwarz. static fromFile(string $file, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------- -Liest ein Bild aus einer Datei und gibt seinen Typ in `$detectedFormat` zurück. Die unterstützten Typen sind `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` und `Image::BMP`. +Lädt ein Bild aus einer Datei `$file` und gibt optional dessen [Typ |#Formate] in `$detectedFormat` zurück. static fromString(string $s, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------ -Liest ein Bild aus einer Zeichenkette und gibt seinen Typ in `$detectedFormat` zurück. Die unterstützten Typen sind `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF`, und `Image::BMP`. +Lädt ein Bild aus einem String `$s` und gibt optional dessen [Typ |#Formate] in `$detectedFormat` zurück. -static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method] ---------------------------------------------------------------------------------- -Erzeugt eine Farbe, die in anderen Methoden verwendet werden kann, z. B. `ellipse()`, `fill()`, usw. +static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method][deprecated] +--------------------------------------------------------------------------------------------- +Diese Methode wurde durch die Klasse `ImageColor` ersetzt, siehe [#Farben]. static typeToExtension(int $type): string .[method] --------------------------------------------------- -Gibt die Dateierweiterung für die angegebene Konstante `Image::XXX` zurück. +Gibt die Dateiendung für den angegebenen [Typ |#Formate] zurück (z.B. `.jpg`). static typeToMimeType(int $type): string .[method] -------------------------------------------------- -Gibt den Mime-Typ für die angegebene Konstante `Image::XXX` zurück. +Gibt den MIME-Typ für den angegebenen [Typ |#Formate] zurück (z.B. `image/jpeg`). static extensionToType(string $extension): int .[method] -------------------------------------------------------- -Gibt den Bildtyp als Konstante `Image::XXX` entsprechend der Dateierweiterung zurück. +Gibt den [Typ |#Formate] des Bildes anhand der Dateiendung `$extension` zurück. static detectTypeFromFile(string $file, int &$width=null, int &$height=null): ?int .[method] -------------------------------------------------------------------------------------------- -Gibt den Typ der Bilddatei als Konstante `Image::XXX` zurück und in den Parametern `$width` und `$height` auch ihre Abmessungen. +Gibt den [Typ |#Formate] des Bildes aus der Datei `$file` zurück und optional dessen Abmessungen in den Parametern `$width` und `$height`. static detectTypeFromString(string $s, int &$width=null, int &$height=null): ?int .[method] ------------------------------------------------------------------------------------------- -Gibt den Typ des Bildes von string als `Image::XXX` Konstante zurück und in den Parametern `$width` und `$height` auch seine Abmessungen. +Gibt den [Typ |#Formate] des Bildes aus einem String `$s` zurück und optional dessen Abmessungen in den Parametern `$width` und `$height`. -affine(array $affine, array $clip=null): Image .[method] --------------------------------------------------------- -Gibt ein Bild zurück, das das affin transformierte Ausgangsbild enthält, wobei ein optionaler Beschneidungsbereich verwendet wird. ([mehr |https://www.php.net/manual/en/function.imageaffine]). +static isTypeSupported(int $type): bool .[method] +------------------------------------------------- +Prüft, ob der angegebene [Typ |#Formate] des Bildes von der aktuellen PHP-Installation unterstützt wird. + + +static getSupportedTypes(): array .[method]{data-version:4.0.4} +--------------------------------------------------------------- +Gibt ein Array der unterstützten Bild[typen |#Formate] zurück. + + +static calculateTextBox(string $text, string $fontFile, float $size, float $angle=0, array $options=[]): array .[method] +------------------------------------------------------------------------------------------------------------------------ +Berechnet die Abmessungen des Rechtecks, das den Text `$text` in einer bestimmten Schriftart (`$fontFile`), Größe (`$size`) und Winkel (`$angle`) umschließt. Gibt ein assoziatives Array zurück, das die Schlüssel `left`, `top`, `width` und `height` enthält. Der linke Rand (`left`) kann negativ sein, wenn der Text links überhängt (Kerning). + + +affine(array $affine, ?array $clip=null): Image .[method] +--------------------------------------------------------- +Gibt ein Bild zurück, das das affin transformierte Quellbild enthält, optional mit einem Zuschneidebereich. ([mehr |https://www.php.net/manual/en/function.imageaffine]). affineMatrixConcat(array $m1, array $m2): array .[method] --------------------------------------------------------- -Gibt die Verkettung von zwei affinen Transformationsmatrizen zurück, was nützlich ist, wenn mehrere Transformationen in einem Durchgang auf dasselbe Bild angewendet werden sollen. ([mehr |https://www.php.net/manual/en/function.imageaffinematrixconcat]) +Gibt die Verkettung zweier affiner Transformationsmatrizen zurück, was nützlich ist, wenn mehrere Transformationen gleichzeitig auf dasselbe Bild angewendet werden sollen. ([mehr |https://www.php.net/manual/en/function.imageaffinematrixconcat]) -affineMatrixGet(int $type, mixed $options=null): array .[method] ----------------------------------------------------------------- +affineMatrixGet(int $type, ?mixed $options=null): array .[method] +----------------------------------------------------------------- Gibt eine affine Transformationsmatrix zurück. ([mehr |https://www.php.net/manual/en/function.imageaffinematrixget]) alphaBlending(bool $on): void .[method] --------------------------------------- -Ermöglicht zwei verschiedene Arten des Zeichnens auf Truecolor-Bildern. Im Überblendungsmodus bestimmt die Alphakanalkomponente der Farbe, die allen Zeichenfunktionen wie `setPixel()` zugeführt wird, wie viel von der Grundfarbe durchscheinen darf. Dadurch wird die vorhandene Farbe an dieser Stelle automatisch mit der Zeichenfarbe gemischt und das Ergebnis im Bild gespeichert. Das resultierende Pixel ist undurchsichtig. Im Nichtüberblendungsmodus wird die Zeichenfarbe buchstäblich mit ihren Alphakanalinformationen kopiert und das Zielpixel ersetzt. Der Überblendungsmodus ist beim Zeichnen auf Palettenbildern nicht verfügbar. ([mehr |https://www.php.net/manual/en/function.imagealphablending]) +Ermöglicht zwei verschiedene Zeichenmodi in Truecolor-Bildern. Im Blending-Modus bestimmt die Alpha-Komponente der Farbe, die in allen Zeichenfunktionen wie `setPixel()` verwendet wird, inwieweit die darunterliegende Farbe durchscheinen darf. Als Ergebnis wird die vorhandene Farbe an diesem Punkt automatisch mit der gezeichneten Farbe gemischt und das Ergebnis im Bild gespeichert. Das resultierende Pixel ist undurchsichtig. Im Modus ohne Blending wird die gezeichnete Farbe buchstäblich mit ihren Alphakanal-Informationen kopiert und ersetzt das Zielpixel. Der Blending-Modus ist beim Zeichnen auf Palettenbildern nicht verfügbar. ([mehr |https://www.php.net/manual/en/function.imagealphablending]) antialias(bool $on): void .[method] ----------------------------------- -Aktivieren Sie die Methoden zum schnellen Zeichnen von Linien und verdrahteten Polygonen mit Antialiasing. Es unterstützt keine Alpha-Komponenten. Sie arbeitet mit einem direkten Mischvorgang. Es funktioniert nur mit Truecolor-Bildern. - -Die Verwendung von Primitiven mit Antialiasing und transparenter Hintergrundfarbe kann zu unerwarteten Ergebnissen führen. Bei der Mischmethode wird die Hintergrundfarbe wie jede andere Farbe verwendet. Die fehlende Unterstützung von Alphakomponenten erlaubt keine alphabasierte Antialiasing-Methode. ([mehr |https://www.php.net/manual/en/function.imageantialias]) - +Aktiviert das Zeichnen von geglätteten Linien und Polygonen. Unterstützt keine Alphakanäle. Funktioniert nur bei Truecolor-Bildern. -arc(int $x, int $y, int $w, int $h, int $start, int $end, int $color): void .[method] -------------------------------------------------------------------------------------- -Zeichnet einen Kreisbogen, dessen Mittelpunkt die angegebenen Koordinaten sind. ([mehr |https://www.php.net/manual/en/function.imagearc]) - - -char(int $font, int $x, int $y, string $char, int $color): void .[method] -------------------------------------------------------------------------- -Zeichnet das erste Zeichen von `$char` im Bild mit seiner oberen linken Seite auf `$x`,`$y` (oben links ist 0, 0) mit der Farbe `$color`. ([mehr |https://www.php.net/manual/en/function.imagechar]) +Die Verwendung von antialiased Primitiven mit einer transparenten Hintergrundfarbe kann zu unerwarteten Ergebnissen führen. Die Blending-Methode verwendet die Hintergrundfarbe wie alle anderen Farben. ([mehr |https://www.php.net/manual/en/function.imageantialias]) -charUp(int $font, int $x, int $y, string $char, int $color): void .[method] ---------------------------------------------------------------------------- -Zeichnet das Zeichen `$char` vertikal an der angegebenen Koordinate auf dem angegebenen Bild. ([mehr |https://www.php.net/manual/en/function.imagecharup]) +arc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color): void .[method] +--------------------------------------------------------------------------------------------------------------------------- +Zeichnet einen Kreisbogen mit dem Mittelpunkt an den angegebenen Koordinaten. ([mehr |https://www.php.net/manual/en/function.imagearc]) colorAllocate(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------- -Gibt einen Farbbezeichner zurück, der die aus den angegebenen RGB-Komponenten zusammengesetzte Farbe darstellt. Sie muss aufgerufen werden, um jede Farbe, die im Bild verwendet werden soll, zu erzeugen. ([mehr |https://www.php.net/manual/en/function.imagecolorallocate]) +Gibt einen Farbidentifikator zurück, der die aus den angegebenen RGB-Komponenten zusammengesetzte Farbe darstellt. Muss für jede Farbe aufgerufen werden, die im Bild verwendet werden soll. ([mehr |https://www.php.net/manual/en/function.imagecolorallocate]) colorAllocateAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ------------------------------------------------------------------------------ -Verhält sich identisch zu `colorAllocate()` mit dem Zusatz des Transparenzparameters `$alpha`. ([mehr |https://www.php.net/manual/en/function.imagecolorallocatealpha]) +Verhält sich genauso wie `colorAllocate()` mit dem zusätzlichen Transparenzparameter `$alpha`. ([mehr |https://www.php.net/manual/en/function.imagecolorallocatealpha]) colorAt(int $x, int $y): int .[method] -------------------------------------- -Gibt den Index der Farbe des Pixels an der angegebenen Stelle im Bild zurück. Handelt es sich bei dem Bild um ein Truecolour-Bild, gibt diese Funktion den RGB-Wert dieses Pixels als Ganzzahl zurück. Verwenden Sie Bit-Shifting und Maskierung, um auf die unterschiedlichen Werte der roten, grünen und blauen Komponenten zuzugreifen: ([mehr |https://www.php.net/manual/en/function.imagecolorat]) +Gibt den Farbindex des Pixels an der angegebenen Stelle im Bild zurück. Wenn das Bild Truecolor ist, gibt diese Funktion den RGB-Wert dieses Pixels als Ganzzahl zurück. Verwenden Sie Bitshifting und Bitmaskierung, um auf die separaten Werte der Rot-, Grün- und Blau-Komponenten zuzugreifen: ([mehr |https://www.php.net/manual/en/function.imagecolorat]) colorClosest(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------ -Gibt den Index der Farbe in der Palette des Bildes zurück, die dem angegebenen RGB-Wert "am nächsten" ist. Der "Abstand" zwischen der gewünschten Farbe und jeder Farbe in der Palette wird berechnet, als ob die RGB-Werte Punkte im dreidimensionalen Raum darstellen würden. ([mehr |https://www.php.net/manual/en/function.imagecolorclosest]) +Gibt den Index der Farbe in der Bildpalette zurück, die dem angegebenen RGB-Wert „am nächsten“ ist. Der "Abstand" zwischen der gewünschten Farbe und jeder Farbe in der Palette wird berechnet, als ob die RGB-Werte Punkte in einem dreidimensionalen Raum darstellen würden. ([mehr |https://www.php.net/manual/en/function.imagecolorclosest]) colorClosestAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ----------------------------------------------------------------------------- -Gibt den Index der Farbe in der Palette des Bildes zurück, die dem angegebenen RGB-Wert und der Stufe `$alpha` am nächsten kommt. ([mehr |https://www.php.net/manual/en/function.imagecolorclosestalpha]) +Gibt den Index der Farbe in der Bildpalette zurück, die dem angegebenen RGB-Wert und der `$alpha`-Ebene „am nächsten“ ist. ([mehr |https://www.php.net/manual/en/function.imagecolorclosestalpha]) colorClosestHWB(int $red, int $green, int $blue): int .[method] --------------------------------------------------------------- -Ermittelt den Index der Farbe, deren Farbton, Weiß und Schwärze der angegebenen Farbe am nächsten kommt. ([mehr |https://www.php.net/manual/en/function.imagecolorclosesthwb]) +Ruft den Index der Farbe ab, die den Farbton, Weiß und Schwarz der angegebenen Farbe am nächsten kommt. ([mehr |https://www.php.net/manual/en/function.imagecolorclosesthwb]) colorDeallocate(int $color): void .[method] ------------------------------------------- -Hebt die Zuweisung einer Farbe auf, die zuvor mit `colorAllocate()` oder `colorAllocateAlpha()` zugewiesen wurde. ([mehr |https://www.php.net/manual/en/function.imagecolordeallocate]) +Gibt eine Farbe frei, die zuvor mit `colorAllocate()` oder `colorAllocateAlpha()` zugewiesen wurde. ([mehr |https://www.php.net/manual/en/function.imagecolordeallocate]) colorExact(int $red, int $green, int $blue): int .[method] ---------------------------------------------------------- -Gibt den Index der angegebenen Farbe in der Palette des Bildes zurück. ([mehr |https://www.php.net/manual/en/function.imagecolorexact]) +Gibt den Index der angegebenen Farbe in der Bildpalette zurück. ([mehr |https://www.php.net/manual/en/function.imagecolorexact]) colorExactAlpha(int $red, int $green, int $blue, int $alpha): int .[method] --------------------------------------------------------------------------- -Gibt den Index der angegebenen Farbe+Alpha in der Palette des Bildes zurück. ([mehr |https://www.php.net/manual/en/function.imagecolorexactalpha]) +Gibt den Index der angegebenen Farbe + Alpha in der Bildpalette zurück. ([mehr |https://www.php.net/manual/en/function.imagecolorexactalpha]) colorMatch(Image $image2): void .[method] ----------------------------------------- -Bringt die Farben der Palettenversion eines Bildes näher an die Echtfarbenversion heran. ([mehr |https://www.php.net/manual/en/function.imagecolormatch]) +Passt die Farben der Palette an das zweite Bild an. ([mehr |https://www.php.net/manual/en/function.imagecolormatch]) colorResolve(int $red, int $green, int $blue): int .[method] @@ -368,108 +404,113 @@ Gibt einen Farbindex für eine angeforderte Farbe zurück, entweder die genaue F colorSet(int $index, int $red, int $green, int $blue): void .[method] --------------------------------------------------------------------- -Damit wird der angegebene Index in der Palette auf die angegebene Farbe gesetzt. ([mehr |https://www.php.net/manual/en/function.imagecolorset]) +Setzt den angegebenen Index in der Palette auf die angegebene Farbe. ([mehr |https://www.php.net/manual/en/function.imagecolorset]) colorsForIndex(int $index): array .[method] ------------------------------------------- -Ermittelt die Farbe für einen bestimmten Index. ([mehr |https://www.php.net/manual/en/function.imagecolorsforindex]) +Ruft die Farbe des angegebenen Index ab. ([mehr |https://www.php.net/manual/en/function.imagecolorsforindex]) colorsTotal(): int .[method] ---------------------------- -Gibt die Anzahl der Farben in einer Bildpalette zurück ([mehr |https://www.php.net/manual/en/function.imagecolorstotal]). +Gibt die Anzahl der Farben in der Bildpalette zurück. ([mehr |https://www.php.net/manual/en/function.imagecolorstotal]) -colorTransparent(int $color=null): int .[method] ------------------------------------------------- -Liest oder setzt die transparente Farbe des Bildes. ([mehr |https://www.php.net/manual/en/function.imagecolortransparent]) +colorTransparent(?int $color=null): int .[method] +------------------------------------------------- +Ruft die transparente Farbe im Bild ab oder legt sie fest. ([mehr |https://www.php.net/manual/en/function.imagecolortransparent]) convolution(array $matrix, float $div, float $offset): void .[method] --------------------------------------------------------------------- -Wendet eine Faltungsmatrix auf das Bild an, wobei der angegebene Koeffizient und Versatz verwendet wird. ([mehr |https://www.php.net/manual/en/function.imageconvolution]) +Wendet eine Faltungsmatrix auf das Bild an, unter Verwendung des angegebenen Koeffizienten und Offsets. ([mehr |https://www.php.net/manual/en/function.imageconvolution]) .[note] -Erfordert *GD-Erweiterung*, daher ist nicht sicher, dass sie überall funktioniert. +Erfordert das Vorhandensein der *Bundled GD extension*, funktioniert also möglicherweise nicht überall. copy(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH): void .[method] -------------------------------------------------------------------------------------------------- -Kopiert einen Teil von `$src` auf das Bild, beginnend bei den Koordinaten `$srcX`, `$srcY` mit einer Breite von `$srcW` und einer Höhe von `$srcH`. Der definierte Teil wird auf die Koordinaten `$dstX` und `$dstY` kopiert. ([mehr |https://www.php.net/manual/en/function.imagecopy]) +Kopiert einen Teil von `$src` auf das Bild, beginnend bei den Koordinaten `$srcX`, `$srcY` mit der Breite `$srcW` und Höhe `$srcH`. Der definierte Teil wird an die Koordinaten `$dstX` und `$dstY` kopiert. ([mehr |https://www.php.net/manual/en/function.imagecopy]) copyMerge(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] --------------------------------------------------------------------------------------------------------------------- -Kopiert einen Teil von `$src` auf das Bild, beginnend bei den Koordinaten `$srcX`, `$srcY` mit einer Breite von `$srcW` und einer Höhe von `$srcH`. Der definierte Teil wird auf die Koordinaten `$dstX` und `$dstY` kopiert. ([mehr |https://www.php.net/manual/en/function.imagecopymerge]) +Kopiert einen Teil von `$src` auf das Bild, beginnend bei den Koordinaten `$srcX`, `$srcY` mit der Breite `$srcW` und Höhe `$srcH`. Der definierte Teil wird an die Koordinaten `$dstX` und `$dstY` kopiert. ([mehr |https://www.php.net/manual/en/function.imagecopymerge]) copyMergeGray(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] ------------------------------------------------------------------------------------------------------------------------- -Kopiert einen Teil von `$src` auf das Bild, beginnend bei den Koordinaten `$srcX`, `$srcY` mit einer Breite von `$srcW` und einer Höhe von `$srcH`. Der definierte Teil wird auf die Koordinaten `$dstX` und `$dstY` kopiert. +Kopiert einen Teil von `$src` auf das Bild, beginnend bei den Koordinaten `$srcX`, `$srcY` mit der Breite `$srcW` und Höhe `$srcH`. Der definierte Teil wird an die Koordinaten `$dstX` und `$dstY` kopiert. -Diese Funktion ist identisch mit `copyMerge()`, mit dem Unterschied, dass sie beim Zusammenführen den Farbton der Quelle beibehält, indem sie die Zielpixel vor dem Kopiervorgang in Graustufen umwandelt. ([mehr |https://www.php.net/manual/en/function.imagecopymergegray]) +Diese Funktion ist identisch mit `copyMerge()` mit der Ausnahme, dass beim Zusammenführen der Farbton der Quelle erhalten bleibt, indem die Zielpixel vor dem Kopiervorgang in Graustufen konvertiert werden. ([mehr |https://www.php.net/manual/en/function.imagecopymergegray]) copyResampled(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] --------------------------------------------------------------------------------------------------------------------------------- -Kopiert einen rechteckigen Ausschnitt eines Bildes in ein anderes Bild, wobei die Pixelwerte gleichmäßig interpoliert werden, so dass insbesondere bei der Verkleinerung eines Bildes ein hohes Maß an Klarheit erhalten bleibt. +Kopiert einen rechteckigen Bereich von `$src` in das aktuelle Bild, wobei die Pixelwerte geglättet interpoliert werden. Dadurch bleibt insbesondere beim Verkleinern eine hohe Bildschärfe erhalten. -Mit anderen Worten: `copyResampled()` nimmt einen rechteckigen Bereich von `$src` mit der Breite `$srcW` und der Höhe `$srcH` an der Position (`$srcX`,`$srcY`) und platziert ihn in einem rechteckigen Bereich des Bildes mit der Breite `$dstW` und der Höhe `$dstH` an der Position (`$dstX`,`$dstY`). +Anders ausgedrückt: `copyResampled()` nimmt einen rechteckigen Bereich aus `$src` mit Breite `$srcW` und Höhe `$srcH` an der Position (`$srcX`, `$srcY`) und platziert ihn in einen rechteckigen Bereich des Zielbildes mit Breite `$dstW` und Höhe `$dstH` an der Position (`$dstX`, `$dstY`). -Wenn Quell- und Zielkoordinaten sowie Breite und Höhe voneinander abweichen, wird das Bildfragment entsprechend gestreckt oder verkleinert. Die Koordinaten beziehen sich auf die linke obere Ecke. Diese Funktion kann zum Kopieren von Regionen innerhalb desselben Bildes verwendet werden, aber wenn sich die Regionen überschneiden, sind die Ergebnisse unvorhersehbar. ([mehr |https://www.php.net/manual/en/function.imagecopyresampled]) +Wenn sich Quell- und Zielkoordinaten sowie Breiten und Höhen unterscheiden, wird das Bildfragment entsprechend gestreckt oder gestaucht. Die Koordinaten beziehen sich auf die obere linke Ecke. Diese Methode kann zum Kopieren von Bereichen innerhalb desselben Bildes verwendet werden; wenn sich die Bereiche jedoch überlappen, sind die Ergebnisse nicht vorhersagbar. ([mehr |https://www.php.net/manual/en/function.imagecopyresampled]) copyResized(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] ------------------------------------------------------------------------------------------------------------------------------- -Kopiert einen rechteckigen Teil eines Bildes in ein anderes Bild. Mit anderen Worten: `copyResized()` nimmt einen rechteckigen Bereich von `$src` mit der Breite `$srcW` und der Höhe `$srcH` an der Position (`$srcX`,`$srcY`) und platziert ihn in einem rechteckigen Bereich des Bildes mit der Breite `$dstW` und der Höhe `$dstH` an der Position (`$dstX`,`$dstY`). +Kopiert einen rechteckigen Bereich von `$src` in das aktuelle Bild. Anders ausgedrückt: `copyResized()` nimmt einen rechteckigen Bereich aus `$src` mit Breite `$srcW` und Höhe `$srcH` an der Position (`$srcX`, `$srcY`) und platziert ihn in einen rechteckigen Bereich des Zielbildes mit Breite `$dstW` und Höhe `$dstH` an der Position (`$dstX`, `$dstY`). -Wenn Quell- und Zielkoordinaten sowie Breite und Höhe voneinander abweichen, wird das Bildfragment entsprechend gestreckt oder verkleinert. Die Koordinaten beziehen sich auf die linke obere Ecke. Diese Funktion kann zum Kopieren von Regionen innerhalb desselben Bildes verwendet werden, aber wenn sich die Regionen überschneiden, sind die Ergebnisse unvorhersehbar. ([mehr |https://www.php.net/manual/en/function.imagecopyresized]) +Wenn sich Quell- und Zielkoordinaten sowie Breiten und Höhen unterscheiden, wird das Bildfragment entsprechend gestreckt oder gestaucht. Die Koordinaten beziehen sich auf die obere linke Ecke. Diese Methode kann zum Kopieren von Bereichen innerhalb desselben Bildes verwendet werden; wenn sich die Bereiche jedoch überlappen, sind die Ergebnisse nicht vorhersagbar. ([mehr |https://www.php.net/manual/en/function.imagecopyresized]) crop(int|string $left, int|string $top, int|string $width, int|string $height): Image .[method] ----------------------------------------------------------------------------------------------- -Beschneidet ein Bild auf den angegebenen rechteckigen Bereich. Die Abmessungen können als ganze Zahlen in Pixeln oder als Strings in Prozent (z. B. `'50%'`) übergeben werden. +Schneidet das Bild auf den angegebenen rechteckigen Bereich zu. Die Abmessungen können als Integer in Pixeln oder als Strings in Prozent (z.B. `'50%'`) angegeben werden. -cropAuto(int $mode=-1, float $threshold=.5, int $color=-1): Image .[method] ---------------------------------------------------------------------------- -Beschneidet ein Bild automatisch entsprechend der angegebenen `$mode`. ([mehr |https://www.php.net/manual/en/function.imagecropauto]) +cropAuto(int $mode=-1, float $threshold=.5, ?ImageColor $color=null): Image .[method] +------------------------------------------------------------------------------------- +Schneidet das Bild automatisch zu, basierend auf dem angegebenen `$mode`, `$threshold` und optional `$color`. ([mehr |https://www.php.net/manual/en/function.imagecropauto]) -ellipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------- -Zeichnet eine Ellipse, deren Mittelpunkt die angegebenen Koordinaten sind. ([mehr |https://www.php.net/manual/en/function.imageellipse]) +ellipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------- +Zeichnet eine Ellipse mit dem Mittelpunkt an den angegebenen Koordinaten. ([mehr |https://www.php.net/manual/en/function.imageellipse]) -fill(int $x, int $y, int $color): void .[method] ------------------------------------------------- -Führt eine Flutfüllung durch, beginnend an der angegebenen Koordinate (oben links ist 0, 0) mit der angegebenen `$color` im Bild. ([mehr |https://www.php.net/manual/en/function.imagefill]) +fill(int $x, int $y, ImageColor $color): void .[method] +------------------------------------------------------- +Füllt einen Bereich beginnend bei den angegebenen Koordinaten (oben links ist 0, 0) mit der angegebenen `$color`. ([mehr |https://www.php.net/manual/en/function.imagefill]) -filledArc(int $cx, int $cy, int $w, int $h, int $s, int $e, int $color, int $style): void .[method] ---------------------------------------------------------------------------------------------------- -Zeichnet einen Teilbogen, der auf die angegebene Koordinate im Bild zentriert ist. ([mehr |https://www.php.net/manual/en/function.imagefilledarc]) +filledArc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color, int $style): void .[method] +--------------------------------------------------------------------------------------------------------------------------------------------- +Zeichnet einen Teilbogen mit dem Mittelpunkt an den angegebenen Koordinaten. ([mehr |https://www.php.net/manual/en/function.imagefilledarc]) -filledEllipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------------- -Zeichnet eine Ellipse, deren Mittelpunkt die angegebene Koordinate im Bild ist. ([mehr |https://www.php.net/manual/en/function.imagefilledellipse]) +filledEllipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------------- +Zeichnet eine Ellipse mit dem Mittelpunkt an den angegebenen Koordinaten. ([mehr |https://www.php.net/manual/en/function.imagefilledellipse]) -filledPolygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------------- -Erzeugt ein gefülltes Polygon im $image. ([mehr |https://www.php.net/manual/en/function.imagefilledpolygon]) +filledPolygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------------- +Erstellt ein gefülltes Polygon im Bild. ([mehr |https://www.php.net/manual/en/function.imagefilledpolygon]) -filledRectangle(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] -------------------------------------------------------------------------------- -Erzeugt ein Rechteck, das im Bild mit `$color` gefüllt ist und bei Punkt 1 beginnt und bei Punkt 2 endet. 0, 0 ist die linke obere Ecke des Bildes. ([mehr |https://www.php.net/manual/en/function.imagefilledrectangle]) +filledRectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------- +Erstellt ein mit `$color` gefülltes Rechteck im Bild, beginnend am Punkt `$x1` & `$y1` und endend am Punkt `$x2` & `$y2`. Punkt 0, 0 ist die obere linke Ecke des Bildes. ([mehr |https://www.php.net/manual/en/function.imagefilledrectangle]) -fillToBorder(int $x, int $y, int $border, int $color): void .[method] ---------------------------------------------------------------------- -Führt eine Flutfüllung aus, deren Randfarbe durch `$border` definiert ist. Der Startpunkt für die Füllung ist `$x`, `$y` (oben links ist 0, 0) und der Bereich wird mit der Farbe `$color` gefüllt. ([mehr |https://www.php.net/manual/en/function.imagefilltoborder]) +filledRectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------------- +Erstellt ein mit `$color` gefülltes Rechteck im Bild, beginnend am Punkt `$left` & `$top` mit der Breite `$width` und Höhe `$height`. Punkt 0, 0 ist die obere linke Ecke des Bildes. + + +fillToBorder(int $x, int $y, int $border, ImageColor $color): void .[method] +---------------------------------------------------------------------------- +Führt eine Flutfüllung durch, deren Randfarbe mit `$border` definiert ist. Der Startpunkt der Füllung ist `$x`, `$y` (oben links ist 0, 0) und der Bereich wird mit der Farbe `$color` gefüllt. ([mehr |https://www.php.net/manual/en/function.imagefilltoborder]) filter(int $filtertype, int ...$args): void .[method] @@ -479,195 +520,190 @@ Wendet den angegebenen Filter `$filtertype` auf das Bild an. ([mehr |https://www flip(int $mode): void .[method] ------------------------------- -Spiegelt das Bild unter Verwendung der angegebenen `$mode`. ([mehr |https://www.php.net/manual/en/function.imageflip]) +Spiegelt das Bild entsprechend dem angegebenen `$mode`. ([mehr |https://www.php.net/manual/en/function.imageflip]) -ftText(int $size, int $angle, int $x, int $y, int $col, string $fontFile, string $text, array $extrainfo=null): array .[method] -------------------------------------------------------------------------------------------------------------------------------- -Schreiben Sie Text in das Bild mit Hilfe von FreeType 2-Schriftarten. ([mehr |https://www.php.net/manual/en/function.imagefttext]) +ftText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +---------------------------------------------------------------------------------------------------------------------------------------- +Schreibt Text in das Bild. ([mehr |https://www.php.net/manual/en/function.imagefttext]) gammaCorrect(float $inputgamma, float $outputgamma): void .[method] ------------------------------------------------------------------- -Wendet eine Gammakorrektur auf das Bild an, das einen Eingabe- und einen Ausgabegammawert hat. ([mehr |https://www.php.net/manual/en/function.imagegammacorrect]) +Wendet eine Gammakorrektur auf das Bild an, relativ zum Eingangs- und Ausgangsgamma. ([mehr |https://www.php.net/manual/en/function.imagegammacorrect]) getClip(): array .[method] -------------------------- -Ruft das aktuelle Beschneidungsrechteck ab, d.h. den Bereich, über den hinaus keine Pixel gezeichnet werden. ([mehr |https://www.php.net/manual/en/function.imagegetclip]) +Gibt den aktuellen Clipping-Bereich als Array mit den Koordinaten x1, y1, x2, y2 zurück. ([mehr |https://www.php.net/manual/en/function.imagegetclip]) getHeight(): int .[method] -------------------------- -Gibt die Höhe des Bildes zurück. +Gibt die Höhe des Bildes in Pixeln zurück. -getImageResource(): resource|GdImage .[method] ----------------------------------------------- -Gibt die ursprüngliche Ressource zurück. +getImageResource(): \GdImage|resource .[method] +----------------------------------------------- +Gibt die zugrundeliegende GD-Bildressource (`resource` oder `GdImage`-Objekt) zurück. getWidth(): int .[method] ------------------------- -Gibt die Breite des Bildes zurück. +Gibt die Breite des Bildes in Pixeln zurück. -interlace(int $interlace=null): int .[method] ---------------------------------------------- -Schaltet das Zeilensprungbit ein oder aus. Wenn das Interlace-Bit gesetzt ist und das Bild als JPEG-Bild verwendet wird, wird das Bild als progressives JPEG erstellt. ([mehr |https://www.php.net/manual/en/function.imageinterlace]) +interlace(?int $interlace=null): int .[method] +---------------------------------------------- +Aktiviert oder deaktiviert den Interlace-Modus. Wenn der Interlace-Modus aktiviert ist und das Bild als JPEG gespeichert wird, wird es als progressives JPEG gespeichert. ([mehr |https://www.php.net/manual/en/function.imageinterlace]) isTrueColor(): bool .[method] ----------------------------- -Findet heraus, ob das Bild eine Truecolor-Farbe ist. ([mehr |https://www.php.net/manual/en/function.imageistruecolor]) +Prüft, ob das Bild ein Truecolor-Bild ist. ([mehr |https://www.php.net/manual/en/function.imageistruecolor]) layerEffect(int $effect): void .[method] ---------------------------------------- -Setzen Sie das Alpha-Blending-Flag, um Überlagerungseffekte zu verwenden. ([mehr |https://www.php.net/manual/en/function.imagelayereffect]) +Setzt das Alpha-Blending-Flag, um Layer-Effekte zu verwenden. ([mehr |https://www.php.net/manual/en/function.imagelayereffect]) -line(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] --------------------------------------------------------------------- -Zeichnet eine Linie zwischen den beiden angegebenen Punkten. ([mehr |https://www.php.net/manual/en/function.imageline]) +line(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +--------------------------------------------------------------------------- +Zeichnet eine Linie zwischen zwei gegebenen Punkten. ([mehr |https://www.php.net/manual/en/function.imageline]) -openPolygon(array $points, int $numPoints, int $color): void .[method] ----------------------------------------------------------------------- -Zeichnet ein offenes Polygon auf das Bild. Im Gegensatz zu `polygon()` wird keine Linie zwischen dem letzten und dem ersten Punkt gezogen. ([mehr |https://www.php.net/manual/en/function.imageopenpolygon]) +openPolygon(array $points, ImageColor $color): void .[method] +------------------------------------------------------------- +Zeichnet ein offenes Polygon auf das Bild. Im Gegensatz zu `polygon()` wird keine Linie zwischen dem letzten und dem ersten Punkt gezeichnet. ([mehr |https://www.php.net/manual/en/function.imageopenpolygon]) paletteCopy(Image $source): void .[method] ------------------------------------------ -Kopiert die Palette von `$source` in das Bild. ([mehr |https://www.php.net/manual/en/function.imagepalettecopy]) +Kopiert die Palette vom Quellbild (`$source`) in das aktuelle Bild. ([mehr |https://www.php.net/manual/en/function.imagepalettecopy]) paletteToTrueColor(): void .[method] ------------------------------------ -Konvertiert ein palettenbasiertes Bild, das mit Funktionen wie `create()` erstellt wurde, in ein Echtfarbbild, wie `createtruecolor()`. ([mehr |https://www.php.net/manual/en/function.imagepalettetotruecolor]) +Konvertiert ein palettenbasiertes Bild in ein Truecolor-Bild. ([mehr |https://www.php.net/manual/en/function.imagepalettetotruecolor]) place(Image $image, int|string $left=0, int|string $top=0, int $opacity=100): Image .[method] --------------------------------------------------------------------------------------------- -Kopiert `$image` in das Bild an den Koordinaten `$left` und `$top`. Die Koordinaten können als ganze Zahlen in Pixeln oder als Zeichenketten in Prozent übergeben werden (z. B. `'50%'`). +Kopiert `$image` in das Bild an die Koordinaten `$left` und `$top`. Die Koordinaten können als Integer in Pixeln oder als Strings in Prozent (z.B. `'50%'`) angegeben werden. -polygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------- -Erzeugt ein Polygon im Bild. ([mehr |https://www.php.net/manual/en/function.imagepolygon]) +polygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------- +Erstellt ein Polygon im Bild. ([mehr |https://www.php.net/manual/en/function.imagepolygon]) -rectangle(int $x1, int $y1, int $x2, int $y2, int $col): void .[method] ------------------------------------------------------------------------ -Erzeugt ein Rechteck, das an den angegebenen Koordinaten beginnt. ([mehr |https://www.php.net/manual/en/function.imagerectangle]) +rectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------- +Erstellt ein Rechteck an den angegebenen Koordinaten. ([mehr |https://www.php.net/manual/en/function.imagerectangle]) + + +rectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------- +Erstellt ein Rechteck an den angegebenen Koordinaten. resize(int|string $width, int|string $height, int $flags=Image::OrSmaller): Image .[method] ------------------------------------------------------------------------------------------- -Skaliert ein Bild, siehe [weitere Informationen |#Image Resize]. Die Abmessungen können als ganze Zahlen in Pixeln oder als Strings in Prozent (z. B. `'50%'`) übergeben werden. +Ändert die Größe des Bildes, [mehr Informationen |#Größenänderung]. Die Abmessungen können als Integer in Pixeln oder als Strings in Prozent (z.B. `'50%'`) angegeben werden. -resolution(int $resX=null, int $resY=null): mixed .[method] ------------------------------------------------------------ -Ermöglicht das Einstellen und Abrufen der Auflösung eines Bildes in DPI (dots per inch). Wenn keiner der optionalen Parameter angegeben wird, wird die aktuelle Auflösung als indiziertes Array zurückgegeben. Wenn nur `$resX` angegeben wird, werden die horizontale und vertikale Auflösung auf diesen Wert gesetzt. Wenn beide optionalen Parameter angegeben werden, werden die horizontale und vertikale Auflösung auf diese Werte gesetzt. +resolution(?int $resX=null, ?int $resY=null): mixed .[method] +------------------------------------------------------------- +Setzt oder gibt die Auflösung des Bildes in DPI (dots per inch) zurück. Wenn keiner der optionalen Parameter angegeben wird, wird die aktuelle Auflösung als indiziertes Array zurückgegeben. Wenn nur `$resX` angegeben wird, werden die horizontale und vertikale Auflösung auf diesen Wert gesetzt. Wenn beide optionalen Parameter angegeben werden, werden die horizontale und vertikale Auflösung auf diese Werte gesetzt. -Die Auflösung wird nur als Metainformation verwendet, wenn Bilder aus Formaten gelesen und in Formate geschrieben werden, die diese Art von Information unterstützen (derzeit PNG und JPEG). Sie hat keinen Einfluss auf die Zeichenoperationen. Die Standardauflösung für neue Bilder beträgt 96 DPI. ([mehr |https://www.php.net/manual/en/function.imageresolution]) +Die Auflösung wird nur als Metainformation verwendet, wenn Bilder in Formaten gelesen und geschrieben werden, die diese Art von Informationen unterstützen (derzeit PNG und JPEG). Sie hat keinen Einfluss auf Zeichenoperationen. Die Standardauflösung neuer Bilder beträgt 96 DPI. ([mehr |https://www.php.net/manual/en/function.imageresolution]) rotate(float $angle, int $backgroundColor): Image .[method] ----------------------------------------------------------- -Dreht das Bild unter Verwendung der angegebenen `$angle` in Grad. Das Zentrum der Drehung ist die Mitte des Bildes, und das gedrehte Bild kann andere Abmessungen als das Originalbild haben. ([mehr |https://www.php.net/manual/en/function.imagerotate]) +Dreht das Bild um den angegebenen `$angle` in Grad. Der Drehpunkt ist der Mittelpunkt des Bildes, und das gedrehte Bild kann andere Abmessungen haben als das Originalbild. ([mehr |https://www.php.net/manual/en/function.imagerotate]) .[note] -Erfordert *GD-Erweiterung*, daher ist nicht sicher, dass sie überall funktioniert. +Erfordert das Vorhandensein der *Bundled GD extension*, funktioniert also möglicherweise nicht überall. -save(string $file, int $quality=null, int $type=null): void .[method] ---------------------------------------------------------------------- -Speichert ein Bild in einer Datei. +save(string $file, ?int $quality=null, ?int $type=null): void .[method] +----------------------------------------------------------------------- +Speichert das Bild in einer Datei. -Die Kompressionsqualität liegt im Bereich 0..100 für JPEG (Standard 85), WEBP (Standard 80) und AVIF (Standard 30) und 0..9 für PNG (Standard 9). Wenn der Typ nicht aus der Dateierweiterung ersichtlich ist, können Sie ihn mit einer der Konstanten `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` und `Image::BMP` angeben. +Die Kompressionsqualität liegt im Bereich 0..100 für JPEG (Standard 85), WEBP (Standard 80) und AVIF (Standard 30) und 0..9 für PNG (Standard 9). Wenn der Typ aus der Dateiendung nicht ersichtlich ist, können Sie ihn mit einer der `ImageType`-Konstanten angeben. saveAlpha(bool $saveflag): void .[method] ----------------------------------------- -Legt fest, ob beim Speichern von PNG-Bildern die vollständige Alphakanal-Information (im Gegensatz zur einfarbigen Transparenz) erhalten bleiben soll. +Setzt das Flag, ob beim Speichern von PNG-Bildern die vollständigen Alphakanal-Informationen beibehalten werden sollen (im Gegensatz zu einfarbiger Transparenz). -Alphablending muss deaktiviert werden (`alphaBlending(false)`), damit der Alphakanal überhaupt erhalten bleibt. ([mehr |https://www.php.net/manual/en/function.imagesavealpha]) +Alphablending muss deaktiviert sein (`alphaBlending(false)`), damit der Alphakanal überhaupt erhalten bleibt. ([mehr |https://www.php.net/manual/en/function.imagesavealpha]) scale(int $newWidth, int $newHeight=-1, int $mode=IMG_BILINEAR_FIXED): Image .[method] -------------------------------------------------------------------------------------- -Skaliert ein Bild unter Verwendung des angegebenen Interpolationsalgorithmus. ([mehr |https://www.php.net/manual/en/function.imagescale]) +Skaliert das Bild unter Verwendung des angegebenen Interpolationsalgorithmus. ([mehr |https://www.php.net/manual/en/function.imagescale]) -send(int $type=Image::JPEG, int $quality=null): void .[method] --------------------------------------------------------------- -Gibt ein Bild an den Browser aus. +send(int $type=ImageType::JPEG, ?int $quality=null): void .[method] +------------------------------------------------------------------- +Gibt das Bild an den Browser aus. -Die Kompressionsqualität liegt im Bereich 0..100 für JPEG (Standard 85), WEBP (Standard 80) und AVIF (Standard 30) und 0..9 für PNG (Standard 9). Typ ist eine der Konstanten `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` und `Image::BMP`. +Die Kompressionsqualität liegt im Bereich 0..100 für JPEG (Standard 85), WEBP (Standard 80) und AVIF (Standard 30) und 0..9 für PNG (Standard 9). setBrush(Image $brush): void .[method] -------------------------------------- -Legt das Pinselbild fest, das von allen Funktionen zum Zeichnen von Linien (z. B. `line()` und `polygon()`) verwendet wird, wenn mit den Sonderfarben IMG_COLOR_BRUSHED oder IMG_COLOR_STYLEDBRUSHED gezeichnet wird. ([mehr |https://www.php.net/manual/en/function.imagesetbrush]) +Legt das Pinselbild fest, das in allen Linienzeichnungsfunktionen (z. B. `line()` und `polygon()`) verwendet wird, wenn mit den speziellen Farben IMG_COLOR_BRUSHED oder IMG_COLOR_STYLEDBRUSHED gezeichnet wird. ([mehr |https://www.php.net/manual/en/function.imagesetbrush]) setClip(int $x1, int $y1, int $x2, int $y2): void .[method] ----------------------------------------------------------- -Legt das aktuelle Beschneidungsrechteck fest, d. h. den Bereich, über den hinaus keine Pixel gezeichnet werden. ([mehr |https://www.php.net/manual/en/function.imagesetclip]) +Legt den aktuellen Clipping-Bereich fest, d.h. den Bereich, außerhalb dessen keine Pixel gezeichnet werden. ([mehr |https://www.php.net/manual/en/function.imagesetclip]) setInterpolation(int $method=IMG_BILINEAR_FIXED): void .[method] ---------------------------------------------------------------- -Legt die Interpolationsmethode fest, die sich auf die Methoden `rotate()` und `affine()` auswirkt. ([mehr |https://www.php.net/manual/en/function.imagesetinterpolation]) +Legt die Interpolationsmethode fest, die die Methoden `rotate()` und `affine()` beeinflusst. ([mehr |https://www.php.net/manual/en/function.imagesetinterpolation]) -setPixel(int $x, int $y, int $color): void .[method] ----------------------------------------------------- -Zeichnet ein Pixel an der angegebenen Koordinate. (mehr |https://www.php.net/manual/en/function.imagesetpixel]) +setPixel(int $x, int $y, ImageColor $color): void .[method] +----------------------------------------------------------- +Zeichnet ein Pixel an den angegebenen Koordinaten. ([mehr |https://www.php.net/manual/en/function.imagesetpixel]) setStyle(array $style): void .[method] -------------------------------------- -Legt den Stil fest, der von allen Funktionen zum Zeichnen von Linien (z. B. `line()` und `polygon()`) verwendet werden soll, wenn mit der speziellen Farbe IMG_COLOR_STYLED oder Linien von Bildern mit der Farbe IMG_COLOR_STYLEDBRUSHED gezeichnet wird. ([mehr |https://www.php.net/manual/en/function.imagesetstyle]) +Legt den Stil fest, der von allen Linienzeichnungsfunktionen (z. B. `line()` und `polygon()`) verwendet werden soll, wenn mit der speziellen Farbe IMG_COLOR_STYLED oder Linien von Bildern mit der Farbe IMG_COLOR_STYLEDBRUSHED gezeichnet wird. ([mehr |https://www.php.net/manual/en/function.imagesetstyle]) setThickness(int $thickness): void .[method] -------------------------------------------- -Setzt die Dicke der Linien, die beim Zeichnen von Rechtecken, Polygonen, Bögen usw. gezeichnet werden, auf `$thickness` Pixel. ([mehr |https://www.php.net/manual/en/function.imagesetthickness]) +Legt die Liniendicke beim Zeichnen von Rechtecken, Polygonen, Bögen usw. auf `$thickness` Pixel fest. ([mehr |https://www.php.net/manual/en/function.imagesetthickness]) setTile(Image $tile): void .[method] ------------------------------------ -Legt das Kachelbild fest, das von allen Funktionen zum Füllen von Regionen (z. B. `fill()` und `filledPolygon()`) beim Füllen mit der Sonderfarbe IMG_COLOR_TILED verwendet wird. +Legt das Kachelbild fest, das in allen Bereichsfüllfunktionen (z. B. `fill()` und `filledPolygon()`) verwendet wird, wenn mit der speziellen Farbe IMG_COLOR_TILED gefüllt wird. -Eine Kachel ist ein Bild, das dazu dient, einen Bereich mit einem sich wiederholenden Muster zu füllen. Jedes Bild kann als Kachel verwendet werden, und durch Einstellen des transparenten Farbindex des Kachelbildes mit `colorTransparent()` kann eine Kachel erstellt werden, die bestimmte Teile des darunter liegenden Bereichs durchscheinen lässt. ([mehr |https://www.php.net/manual/en/function.imagesettile]) +Eine Kachel ist ein Bild, das zum Füllen eines Bereichs mit einem sich wiederholenden Muster verwendet wird. Jedes Bild kann als Kachel verwendet werden, und durch Festlegen des transparenten Farbindex des Kachelbildes mit `colorTransparent()` kann eine Kachel erstellt werden, bei der bestimmte Teile des darunterliegenden Bereichs durchscheinen. ([mehr |https://www.php.net/manual/en/function.imagesettile]) sharpen(): Image .[method] -------------------------- -Schärft das Bild ein wenig. +Schärft das Bild. .[note] -Erfordert *GD-Erweiterung*, daher ist nicht sicher, dass sie überall funktioniert. - - -string(int $font, int $x, int $y, string $str, int $col): void .[method] ------------------------------------------------------------------------- -Zeichnet eine Zeichenkette an den angegebenen Koordinaten. ([mehr |https://www.php.net/manual/en/function.imagestring]) - +Erfordert das Vorhandensein der *Bundled GD extension*, funktioniert also möglicherweise nicht überall. -stringUp(int $font, int $x, int $y, string $s, int $col): void .[method] ------------------------------------------------------------------------- -Zeichnet eine Zeichenkette vertikal an den angegebenen Koordinaten. ([mehr |https://www.php.net/manual/en/function.imagestringup]) +toString(int $type=ImageType::JPEG, ?int $quality=null): string .[method] +------------------------------------------------------------------------- +Speichert das Bild in einem String. -toString(int $type=Image::JPEG, int $quality=null): string .[method] --------------------------------------------------------------------- -Gibt ein Bild als String aus. - -Die Kompressionsqualität liegt im Bereich 0..100 für JPEG (Standard 85), WEBP (Standard 80) und AVIF (Standard 30) und 0..9 für PNG (Standard 9). Typ ist eine der Konstanten `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` und `Image::BMP`. +Die Kompressionsqualität liegt im Bereich 0..100 für JPEG (Standard 85), WEBP (Standard 80) und AVIF (Standard 30) und 0..9 für PNG (Standard 9). trueColorToPalette(bool $dither, int $ncolors): void .[method] @@ -675,6 +711,6 @@ trueColorToPalette(bool $dither, int $ncolors): void .[method] Konvertiert ein Truecolor-Bild in ein Palettenbild. ([mehr |https://www.php.net/manual/en/function.imagetruecolortopalette]) -ttfText(int $size, int $angle, int $x, int $y, int $color, string $fontfile, string $text): array .[method] ------------------------------------------------------------------------------------------------------------ -Schreibt den angegebenen Text mit TrueType-Schriften in das Bild. ([mehr |https://www.php.net/manual/en/function.imagettftext]) +ttfText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +----------------------------------------------------------------------------------------------------------------------------------------- +Gibt den angegebenen Text in das Bild aus. ([mehr |https://www.php.net/manual/en/function.imagettftext]) diff --git a/utils/de/iterables.texy b/utils/de/iterables.texy new file mode 100644 index 0000000000..cbd5116bc3 --- /dev/null +++ b/utils/de/iterables.texy @@ -0,0 +1,170 @@ +Arbeiten mit Iteratoren +*********************** + +.[perex]{data-version:4.0.4} +[api:Nette\Utils\Iterables] ist eine statische Klasse mit Hilfsfunktionen für die Arbeit mit Iteratoren. Ihr Gegenstück für Arrays ist [Nette\Utils\Arrays |arrays]. + + +Installation: + +```shell +composer require nette/utils +``` + +Alle Beispiele setzen voraus, dass der folgende Alias definiert wurde: + +```php +use Nette\Utils\Iterables; +``` + + +contains(iterable $iterable, $value): bool .[method] +---------------------------------------------------- + +Prüft, ob der Iterator `$iterable` den angegebenen Wert (`$value`) enthält. Verwendet einen strikten Vergleich (`===`). Gibt `true` zurück, wenn der Wert gefunden wird, andernfalls `false`. + +```php +Iterables::contains(new ArrayIterator([1, 2, 3]), 1); // true +Iterables::contains(new ArrayIterator([1, 2, 3]), '1'); // false +``` + +Diese Methode ist nützlich, um schnell zu prüfen, ob ein bestimmter Wert in einem Iterator vorhanden ist, ohne alle Elemente manuell durchlaufen zu müssen. + + +containsKey(iterable $iterable, $key): bool .[method] +----------------------------------------------------- + +Sucht nach dem angegebenen Schlüssel im Iterator. Verwendet einen strikten Vergleich (`===`) zur Überprüfung der Übereinstimmung. Gibt `true` zurück, wenn der Schlüssel gefunden wird, andernfalls `false`. + +```php +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 0); // true +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 4); // false +``` + + +every(iterable $iterable, callable $predicate): bool .[method] +-------------------------------------------------------------- + +Prüft, ob alle Elemente des Iterators `$iterable` die im `$predicate` definierte Bedingung erfüllen. Das `$predicate` (Signatur: `function ($value, $key, iterable $iterable): bool`) muss für jedes Element `true` zurückgeben, damit `every()` `true` zurückgibt. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isBelowThreshold = fn($value) => $value < 40; +$res = Iterables::every($iterator, $isBelowThreshold); // true +``` + +Diese Methode ist nützlich, um zu prüfen, ob alle Elemente einer Sammlung eine bestimmte Bedingung erfüllen (z.B. ob alle Zahlen kleiner als ein Grenzwert sind). + + +filter(iterable $iterable, callable $predicate): Generator .[method] +-------------------------------------------------------------------- + +Erstellt einen neuen Iterator, der nur die Elemente aus dem ursprünglichen Iterator enthält, die die in `$predicate` definierte Bedingung erfüllen. Die Funktion `$predicate` hat die Signatur `function ($value, $key, iterable $iterable): bool` und muss für die Elemente, die beibehalten werden sollen, `true` zurückgeben. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::filter($iterator, fn($v) => $v < 3); +// 1, 2 +``` + +Die Methode verwendet einen Generator, d.h. die Filterung erfolgt 'lazy' beim Durchlaufen des Ergebnisses. Dies ist speichereffizient und ermöglicht die Verarbeitung sehr großer Sammlungen. Wenn nicht alle Elemente des resultierenden Generators durchlaufen werden, wird Rechenleistung gespart, da nicht alle Elemente des ursprünglichen Iterators verarbeitet werden müssen. + + +first(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------------- + +Gibt das erste Element des Iterators zurück. Wenn `$predicate` angegeben ist, gibt es das erste Element zurück, das die angegebene Bedingung erfüllt. Die Funktion `$predicate` hat die Signatur `function ($value, $key, iterable $iterable): bool`. Wenn kein passendes Element gefunden wird, wird die Funktion `$else` aufgerufen (falls angegeben) und ihr Ergebnis zurückgegeben. Wenn `$else` nicht angegeben ist, wird `null` zurückgegeben. + +```php +Iterables::first(new ArrayIterator([1, 2, 3])); // 1 +Iterables::first(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 3 +Iterables::first(new ArrayIterator([])); // null +Iterables::first(new ArrayIterator([]), else: fn() => false); // false +``` + +Diese Methode ist nützlich, um schnell das erste Element einer Sammlung (oder das erste, das eine Bedingung erfüllt) zu erhalten, ohne die gesamte Sammlung manuell durchlaufen zu müssen. + + +firstKey(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +---------------------------------------------------------------------------------------------- + +Gibt den Schlüssel des ersten Elements des Iterators zurück. Wenn `$predicate` angegeben ist, gibt es den Schlüssel des ersten Elements zurück, das die angegebene Bedingung erfüllt. Die Funktion `$predicate` hat die Signatur `function ($value, $key, iterable $iterable): bool`. Wenn kein passendes Element gefunden wird, wird die Funktion `$else` aufgerufen (falls angegeben) und ihr Ergebnis zurückgegeben. Wenn `$else` nicht angegeben ist, wird `null` zurückgegeben. + +```php +Iterables::firstKey(new ArrayIterator([1, 2, 3])); // 0 +Iterables::firstKey(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 2 +Iterables::firstKey(new ArrayIterator(['a' => 1, 'b' => 2])); // 'a' +Iterables::firstKey(new ArrayIterator([])); // null +``` + + +map(iterable $iterable, callable $transformer): Generator .[method] +------------------------------------------------------------------- + +Erstellt einen neuen Generator durch Anwendung der Funktion `$transformer` auf jedes Element des ursprünglichen Iterators `$iterable`. Das `$transformer` (Signatur: `function ($value, $key, iterable $iterable): mixed`) gibt den neuen Wert für das Element zurück. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::map($iterator, fn($v) => $v * 2); +// 2, 4, 6 +``` + +Die Methode verwendet einen Generator, was bedeutet, dass die Transformation schrittweise beim Durchlaufen des Ergebnisses erfolgt. Dies ist speichereffizient und ermöglicht die Verarbeitung auch sehr großer Sammlungen. Wenn Sie nicht alle Elemente des resultierenden Iterators durchlaufen, sparen Sie Rechenleistung, da nicht alle Elemente des ursprünglichen Iterators verarbeitet werden. + + +mapWithKeys(iterable $iterable, callable $transformer): Generator .[method] +--------------------------------------------------------------------------- + +Erstellt einen neuen Iterator durch Transformation der Werte und Schlüssel des ursprünglichen Iterators. Die Funktion `$transformer` hat die Signatur `function ($value, $key, iterable $iterable): ?array{$newKey, $newValue}`. Wenn `$transformer` `null` zurückgibt, wird das Element übersprungen. Für beibehaltene Elemente wird das erste Element des zurückgegebenen Arrays als neuer Schlüssel und das zweite Element als neuer Wert verwendet. + +```php +$iterator = new ArrayIterator(['a' => 1, 'b' => 2]); +$iterator = Iterables::mapWithKeys($iterator, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +Wie `map()` verwendet diese Methode einen Generator für die schrittweise Verarbeitung und effiziente Speicherverwaltung. Dies ermöglicht die Arbeit mit großen Sammlungen und spart Rechenleistung bei teilweisem Durchlauf des Ergebnisses. + + +memoize(iterable $iterable): IteratorAggregate .[method] +-------------------------------------------------------- + +Erstellt einen Wrapper um einen Iterator, der während der Iteration dessen Schlüssel und Werte zwischenspeichert. Dies ermöglicht eine wiederholte Iteration der Daten, ohne die ursprüngliche Datenquelle erneut durchlaufen zu müssen. + +```php +$iterator = /* Datenquelle, die nicht mehrmals iteriert werden kann */ +$memoized = Iterables::memoize($iterator); +// Jetzt können Sie $memoized mehrmals iterieren, ohne Daten zu verlieren +``` + +Diese Methode ist nützlich, wenn derselbe Datensatz mehrmals durchlaufen werden muss, der ursprüngliche Iterator dies jedoch nicht zulässt oder der erneute Durchlauf kostspielig wäre (z.B. beim Lesen aus einer Datenbank oder Datei). + + +some(iterable $iterable, callable $predicate): bool .[method] +------------------------------------------------------------- + +Überprüft, ob mindestens ein Element des Iterators die in `$predicate` definierte Bedingung erfüllt. Die Funktion `$predicate` hat die Signatur `function ($value, $key, iterable $iterable): bool` und muss für mindestens ein Element `true` zurückgeben, damit die Methode `some()` `true` zurückgibt. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isEven = fn($value) => $value % 2 === 0; +$res = Iterables::some($iterator, $isEven); // true +``` + +Diese Methode ist nützlich zur schnellen Überprüfung, ob in einer Sammlung mindestens ein Element existiert, das eine bestimmte Bedingung erfüllt, beispielsweise ob die Sammlung mindestens eine gerade Zahl enthält. + +Gegenstück zu [#every()]. + + +toIterator(iterable $iterable): Iterator .[method] +-------------------------------------------------- + +Konvertiert jedes iterierbare Objekt (Array, Traversable) in einen Iterator. Wenn die Eingabe bereits ein Iterator ist, wird sie unverändert zurückgegeben. + +```php +$array = [1, 2, 3]; +$iterator = Iterables::toIterator($array); +// Jetzt haben Sie einen Iterator anstelle eines Arrays +``` + +Diese Methode ist nützlich, wenn Sie sicherstellen müssen, dass Sie einen Iterator zur Verfügung haben, unabhängig vom Typ der Eingabedaten. Dies kann beim Erstellen von Funktionen nützlich sein, die mit verschiedenen Typen iterierbarer Daten arbeiten. diff --git a/utils/de/json.texy b/utils/de/json.texy index 75a70b407e..7e5c657a77 100644 --- a/utils/de/json.texy +++ b/utils/de/json.texy @@ -1,8 +1,8 @@ -JSON-Funktionen -*************** +Arbeiten mit JSON +***************** .[perex] -[api:Nette\Utils\Json] ist eine statische Klasse mit JSON-Kodierungs- und Dekodierungsfunktionen. Sie behandelt Sicherheitslücken in verschiedenen PHP-Versionen und löst bei Fehlern Ausnahmen aus. +[api:Nette\Utils\Json] ist eine statische Klasse mit Funktionen zum Kodieren und Dekodieren des JSON-Formats. Sie behandelt Schwachstellen verschiedener PHP-Versionen und löst bei Fehlern Ausnahmen aus. Installation: @@ -11,15 +11,15 @@ Installation: composer require nette/utils ``` -Alle Beispiele gehen davon aus, dass der folgende Klassenalias definiert ist: +Alle Beispiele setzen voraus, dass der folgende Alias definiert wurde: ```php use Nette\Utils\Json; ``` -Verwendung .[#toc-usage] -======================== +Verwendung +========== encode(mixed $value, bool $pretty=false, bool $asciiSafe=false, bool $htmlSafe=false, bool $forceObjects=false): string .[method] @@ -27,25 +27,25 @@ encode(mixed $value, bool $pretty=false, bool $asciiSafe=false, bool $htmlSafe=f Konvertiert `$value` in das JSON-Format. -Wenn `$pretty` gesetzt ist, wird JSON zur besseren Lesbarkeit und Übersichtlichkeit formatiert: +Bei Einstellung `$pretty` formatiert es JSON für leichtere Lesbarkeit und Übersichtlichkeit: ```php -Json::encode($value); // liefert JSON -Json::encode($value, pretty: true); // gibt eindeutigeres JSON zurück +Json::encode($value); // gibt JSON zurück +Json::encode($value, pretty: true); // gibt übersichtlicheres JSON zurück ``` -Wenn `$asciiSafe` eingestellt ist, wird eine ASCII-Ausgabe erzeugt, d. h. die Unicode-Zeichen werden durch `\uxxxx` -Sequenzen ersetzt: +Mit `$asciiSafe = true` wird eine reine ASCII-Ausgabe erzeugt, d.h. Unicode-Zeichen werden als `\uxxxx`-Sequenzen kodiert: ```php Json::encode('žluťoučký', asciiSafe: true); // '"\u017elu\u0165ou\u010dk\u00fd"' ``` -Der Parameter `$htmlSafe` stellt sicher, dass die Ausgabe keine Zeichen mit besonderer Bedeutung in HTML enthält: +Der Parameter `$htmlSafe` stellt sicher, dass die Ausgabe keine Zeichen enthält, die in HTML eine besondere Bedeutung haben: ```php -Json::encode('einssendJson($data)` verwenden, die z.B. in der Methode `action*()` aufgerufen werden kann, siehe [Senden einer Antwort |application:presenters#Sending a Response]. +Dazu kann die Methode `$this->sendJson($data)` verwendet werden, die beispielsweise in einer `action*()`-Methode aufgerufen werden kann, siehe [Antwort senden |application:presenters#Senden der Antwort]. diff --git a/utils/de/paginator.texy b/utils/de/paginator.texy index 8411d974ca..c82b8a7525 100644 --- a/utils/de/paginator.texy +++ b/utils/de/paginator.texy @@ -2,7 +2,7 @@ Paginator ********* .[perex] -Müssen Sie eine Datenliste paginieren? Da die Mathematik hinter der Paginierung kompliziert sein kann, hilft Ihnen [api:Nette\Utils\Paginator]. +Müssen Sie die Ausgabe von Daten paginieren? Da die Paginierungsmathematik knifflig sein kann, hilft Ihnen [api:Nette\Utils\Paginator] dabei. Installation: @@ -11,38 +11,38 @@ Installation: composer require nette/utils ``` -Erstellen wir ein Paging-Objekt und legen wir die grundlegenden Informationen dafür fest: +Wir erstellen ein Paginator-Objekt und legen die grundlegenden Informationen fest: ```php $paginator = new Nette\Utils\Paginator; -$paginator->setPage(1); // die Nummer der aktuellen Seite (nummeriert ab 1) -$paginator->setItemsPerPage(30); // die Anzahl der Datensätze pro Seite -$paginator->setItemCount(356); // die Gesamtzahl der Datensätze (falls verfügbar) +$paginator->setPage(1); // Nummer der aktuellen Seite +$paginator->setItemsPerPage(30); // Anzahl der Elemente pro Seite +$paginator->setItemCount(356); // Gesamtzahl der Elemente, falls bekannt ``` -Die Seiten sind von 1 an nummeriert. Wir können sie mit `setBase()` ändern: +Die Seiten werden standardmäßig ab 1 nummeriert. Dies kann mit `setBase()` geändert werden: ```php -$paginator->setBase(0); // nummeriert von 0 +$paginator->setBase(0); // wir nummerieren ab 0 ``` -Das Objekt liefert nun alle grundlegenden Informationen, die für die Erstellung eines Paginators nützlich sind. Sie können es z. B. an eine Vorlage übergeben und dort verwenden. +Das Objekt liefert nun alle grundlegenden Informationen, die für die Erstellung der Paginierung nützlich sind. Sie können es beispielsweise an eine Vorlage übergeben und dort verwenden. ```php -$paginator->isFirst(); // ist dies die erste Seite? -$paginator->isLast(); // ist dies die letzte Seite? -$paginator->getPage(); // aktuelle Seitenzahl -$paginator->getFirstPage(); // die erste Seitennummer -$paginator->getLastPage(); // die letzte Seitenzahl -$paginator->getFirstItemOnPage(); // laufende Nummer des ersten Eintrags auf der Seite -$paginator->getLastItemOnPage(); // Laufende Nummer des letzten Eintrags auf der Seite -$paginator->getPageIndex(); // aktuelle Seitennummer, wenn von 0 an nummeriert -$paginator->getPageCount(); // die Gesamtzahl der Seiten -$paginator->getItemsPerPage(); // die Anzahl der Datensätze pro Seite -$paginator->getItemCount(); // Gesamtzahl der Datensätze (falls vorhanden) +$paginator->isFirst(); // Sind wir auf der ersten Seite? +$paginator->isLast(); // Sind wir auf der letzten Seite? +$paginator->getPage(); // Nummer der aktuellen Seite +$paginator->getFirstPage(); // Nummer der ersten Seite +$paginator->getLastPage(); // Nummer der letzten Seite +$paginator->getFirstItemOnPage(); // Nummer des ersten Elements auf der Seite (1-basiert) +$paginator->getLastItemOnPage(); // Nummer des letzten Elements auf der Seite (1-basiert) +$paginator->getPageIndex(); // Index der aktuellen Seite (0-basiert) +$paginator->getPageCount(); // Gesamtzahl der Seiten +$paginator->getItemsPerPage(); // Anzahl der Elemente pro Seite +$paginator->getItemCount(); // Gesamtzahl der Elemente (falls bekannt) ``` -Der Paginator wird Ihnen bei der Formulierung der SQL-Abfrage helfen. Die Methoden `getLength()` und `getOffset()` geben die Werte zurück, die Sie in den LIMIT- und OFFSET-Klauseln verwenden können: +Der Paginator hilft bei der Formulierung von SQL-Abfragen. Die Methoden `getLength()` und `getOffset()` geben die Werte zurück, die für die `LIMIT`- und `OFFSET`-Klauseln benötigt werden: ```php $result = $database->query( @@ -52,7 +52,7 @@ $result = $database->query( ); ``` -Wenn Sie in umgekehrter Reihenfolge paginieren müssen, d.h. Seite Nr. 1 entspricht dem höchsten Offset, können Sie `getCountdownOffset()` verwenden: +Wenn die Paginierung in umgekehrter Reihenfolge erfolgen soll (d.h. Seite 1 entspricht dem höchsten Offset), verwenden Sie `getCountdownOffset()`: ```php $result = $database->query( @@ -62,4 +62,4 @@ $result = $database->query( ); ``` -Ein Beispiel für die Verwendung in der Anwendung finden Sie im Kochbuch [Paginieren von Datenbankergebnissen |best-practices:pagination]. +Ein Anwendungsbeispiel finden Sie im Rezept [Datenbankergebnisse paginieren |best-practices:pagination]. diff --git a/utils/de/random.texy b/utils/de/random.texy index ac2c894b2f..3533f87eea 100644 --- a/utils/de/random.texy +++ b/utils/de/random.texy @@ -1,11 +1,11 @@ -Generator für zufällige Zeichenketten -************************************* +Generierung von Zufallszeichenketten +************************************ .[perex] -[api:Nette\Utils\Random] ist eine statische Klasse zur Erzeugung kryptographisch sicherer Pseudo-Zufallszeichenfolgen. +[api:Nette\Utils\Random] ist eine statische Klasse zur Generierung kryptographisch sicherer Pseudozufallszeichenketten. -Einbau: +Installation: ```shell composer require nette/utils @@ -15,7 +15,7 @@ composer require nette/utils generate(int $length=10, string $charlist=`'0-9a-z'`): string .[method] ----------------------------------------------------------------------- -Erzeugt eine zufällige Zeichenkette der angegebenen Länge aus den im zweiten Argument angegebenen Zeichen. Unterstützt Intervalle, wie `0-9` oder `A-Z`. +Generiert eine zufällige Zeichenkette der Länge `$length` aus den im Parameter `$charlist` angegebenen Zeichen. Es können auch Bereiche wie `0-9` oder `a-z` verwendet werden. ```php use Nette\Utils\Random; diff --git a/utils/de/reflection.texy b/utils/de/reflection.texy index 433484c394..e621bbef80 100644 --- a/utils/de/reflection.texy +++ b/utils/de/reflection.texy @@ -1,8 +1,8 @@ -PHP-Reflexion +PHP Reflexion ************* .[perex] -[api:Nette\Utils\Reflection] ist eine statische Klasse mit nützlichen Funktionen für PHP Reflection. Ihr Zweck ist es, Fehler in nativen Klassen zu beheben und das Verhalten zwischen verschiedenen PHP-Versionen zu vereinheitlichen. +[api:Nette\Utils\Reflection] ist eine statische Klasse mit Hilfsfunktionen für die PHP-Reflexion. Ihre Aufgabe ist es, Unzulänglichkeiten der nativen Reflexionsklassen zu beheben und das Verhalten über verschiedene PHP-Versionen hinweg zu vereinheitlichen. Installation: @@ -11,7 +11,7 @@ Installation: composer require nette/utils ``` -Alle Beispiele setzen voraus, dass der folgende Klassenalias definiert ist: +Alle Beispiele setzen voraus, dass der folgende Alias definiert wurde: ```php use Nette\Utils\Reflection; @@ -21,13 +21,13 @@ use Nette\Utils\Reflection; areCommentsAvailable(): bool .[method] -------------------------------------- -Findet heraus, ob Reflection Zugriff auf PHPdoc-Kommentare hat. Kommentare können aufgrund des Opcode-Caches nicht verfügbar sein, siehe z.B. die Direktive [opcache.save-comments |https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments]. +Prüft, ob die Reflexion Zugriff auf PHPdoc-Kommentare hat. Kommentare sind möglicherweise aufgrund des Opcode-Caches nicht verfügbar (siehe z.B. die Direktive [opcache.save-comments |https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments]). expandClassName(string $name, ReflectionClass $context): string .[method] ------------------------------------------------------------------------- -Erweitert die `$name` der Klasse auf den vollen Namen im Kontext der `$context`, d.h. im Kontext ihres Namespace und der definierten Aliase. Er gibt also zurück, wie der PHP-Parser `$name` verstehen würde, wenn er im Body der `$context` geschrieben wäre. +Erweitert den Klassennamen `$name` zu seinem vollständigen Namen im Kontext der Klasse `$context`, d.h. im Kontext ihres Namensraums und der definierten Aliase. Es sagt also eigentlich, wie der PHP-Parser `$name` verstehen würde, wenn er im Körper der Klasse `$context` geschrieben wäre. ```php namespace Foo; @@ -47,9 +47,9 @@ Reflection::expandClassName('Baz', $context); // 'Foo\Baz' getMethodDeclaringMethod(ReflectionMethod $method): ReflectionMethod .[method] ------------------------------------------------------------------------------ -Gibt eine Reflexion einer Methode zurück, die eine Deklaration von `$method` enthält. Normalerweise ist jede Methode eine eigene Deklaration, aber der Körper der Methode kann auch im Trait und unter einem anderen Namen stehen. +Gibt die `ReflectionMethod` der Methode zurück, die die ursprüngliche Deklaration von `$method` enthält. Normalerweise ist jede Methode ihre eigene Deklaration, aber der Methodenrumpf kann sich auch in einem Trait befinden und die Methode unter einem anderen Namen (Alias) verwendet werden. -Da PHP nicht genügend Informationen liefert, um die tatsächliche Deklaration zu bestimmen, verwendet Nette seine eigene Heuristik, die **zuverlässig** sein sollte. +Da PHP keine ausreichenden Informationen zur Ermittlung der tatsächlichen Deklaration bereitstellt, verwendet Nette eine eigene Heuristik, die **zuverlässig sein sollte**. ```php trait DemoTrait @@ -76,9 +76,9 @@ Reflection::getMethodDeclaringMethod($method); // ReflectionMethod('DemoTrait::f getPropertyDeclaringClass(ReflectionProperty $prop): ReflectionClass .[method] ------------------------------------------------------------------------------ -Gibt eine Reflexion einer Klasse oder eines Traits zurück, die eine Deklaration der Eigenschaft `$prop` enthält. Die Eigenschaft kann auch im Trait deklariert werden. +Gibt die Reflexion der Klasse oder des Traits zurück, die die Deklaration der Eigenschaft `$prop` enthält. Eine Eigenschaft kann nämlich auch in einem Trait deklariert sein. -Da PHP nicht genügend Informationen liefert, um die tatsächliche Deklaration zu bestimmen, verwendet Nette seine eigene Heuristik, die **nicht** zuverlässig ist. +Da PHP keine ausreichenden Informationen zur Ermittlung der tatsächlichen Deklaration bereitstellt, verwendet Nette eine eigene Heuristik, die jedoch **nicht** immer zuverlässig ist. ```php trait DemoTrait @@ -100,7 +100,7 @@ Reflection::getPropertyDeclaringClass($prop); // ReflectionClass('DemoTrait') isBuiltinType(string $type): bool .[method deprecated] ------------------------------------------------------ -Ermittelt, ob `$type` ein in PHP eingebauter Typ ist. Andernfalls ist es der Klassenname. +Stellt fest, ob `$type` ein eingebauter PHP-Typ ist. Andernfalls handelt es sich um einen Klassennamen. ```php Reflection::isBuiltinType('string'); // true @@ -114,7 +114,7 @@ Verwenden Sie [Nette\Utils\Validator::isBuiltinType() |validators#isBuiltinType] toString($reflection): string .[method] --------------------------------------- -Konvertiert eine Reflexion in eine für Menschen lesbare Zeichenkette. +Konvertiert eine Reflexion in einen für Menschen lesbaren String. ```php $func = new ReflectionFunction('func'); diff --git a/utils/de/smartobject.texy b/utils/de/smartobject.texy index 783015895d..226af704b3 100644 --- a/utils/de/smartobject.texy +++ b/utils/de/smartobject.texy @@ -1,8 +1,8 @@ -SmartObject und StaticClass -*************************** +SmartObject +*********** .[perex] -SmartObject fügt PHP-Klassen Unterstützung für *Eigenschaften* hinzu. StaticClass wird verwendet, um statische Klassen zu bezeichnen. +SmartObject hat über Jahre hinweg das Verhalten von Objekten in PHP verbessert. Seit PHP 8.4 sind alle seine Funktionen bereits Teil von PHP selbst, womit es seine historische Mission als Pionier des modernen objektorientierten Ansatzes in PHP vollendet hat. Installation: @@ -11,19 +11,64 @@ Installation: composer require nette/utils ``` +SmartObject entstand 2007 als revolutionäre Lösung für die Mängel des damaligen PHP-Objektmodells. In einer Zeit, als PHP unter zahlreichen Problemen im Objektdesign litt, brachte es erhebliche Verbesserungen und vereinfachte die Arbeit für Entwickler. Es wurde zu einem legendären Bestandteil des Nette Frameworks. Es bot Funktionalität, die PHP erst viele Jahre später erhielt – von der Zugriffskontrolle auf Objekteigenschaften bis hin zu ausgefeiltem syntaktischen Zucker. Mit der Einführung von PHP 8.4 vollendete es seine historische Mission, da alle seine Funktionen native Bestandteile der Sprache wurden. Es war der PHP-Entwicklung um bemerkenswerte 17 Jahre voraus. -Eigenschaften, Getters und Setters .[#toc-properties-getters-and-setters] -========================================================================= +Technisch durchlief SmartObject eine interessante Entwicklung. Ursprünglich wurde es als Klasse `Nette\Object` implementiert, von der andere Klassen die benötigte Funktionalität erbten. Eine grundlegende Änderung kam mit PHP 5.4, das Unterstützung für Traits einführte. Dies ermöglichte die Transformation in die Form des Traits `Nette\SmartObject`, was größere Flexibilität brachte – Entwickler konnten die Funktionalität auch in Klassen nutzen, die bereits von einer anderen Klasse erbten. Während die ursprüngliche Klasse `Nette\Object` mit der Einführung von PHP 7.2 (das die Benennung von Klassen mit dem Wort `Object` verbot) verschwand, lebt das Trait `Nette\SmartObject` weiter. -In modernen objektorientierten Sprachen (z. B. C#, Python, Ruby, JavaScript) bezieht sich der Begriff *Eigenschaft* auf [spezielle Mitglieder von Klassen |https://en.wikipedia.org/wiki/Property_(programming)], die wie Variablen aussehen, aber eigentlich durch Methoden repräsentiert werden. Wenn der Wert dieser "Variablen" zugewiesen oder gelesen wird, wird die entsprechende Methode (Getter oder Setter genannt) aufgerufen. Das ist sehr praktisch, denn es gibt uns die volle Kontrolle über den Zugriff auf Variablen. Wir können die Eingabe validieren oder Ergebnisse nur dann erzeugen, wenn die Eigenschaft gelesen wird. +Gehen wir die Eigenschaften durch, die einst `Nette\Object` und später `Nette\SmartObject` boten. Jede dieser Funktionen stellte zu ihrer Zeit einen bedeutenden Fortschritt im Bereich der objektorientierten Programmierung in PHP dar. -PHP-Eigenschaften werden nicht unterstützt, aber Trait `Nette\SmartObject` kann sie imitieren. Wie verwendet man es? -- Fügen Sie der Klasse eine Annotation in der Form `@property $xyz` -- Erstellen Sie einen Getter namens `getXyz()` oder `isXyz()`, einen Setter namens `setXyz()` -- Die Getter und Setter müssen *public* oder *protected* sein und sind optional, d.h. es kann eine *read-only* oder *write-only* Eigenschaft geben +Konsistente Fehlerzustände +-------------------------- +Eines der dringendsten Probleme des frühen PHP war das inkonsistente Verhalten bei der Arbeit mit Objekten. `Nette\Object` brachte Ordnung und Vorhersehbarkeit in dieses Chaos. Schauen wir uns an, wie das ursprüngliche Verhalten von PHP aussah: -Wir werden die Eigenschaft für die Klasse Circle verwenden, um sicherzustellen, dass nur nicht-negative Zahlen in die Variable `$radius` eingegeben werden. Ersetzen Sie `public $radius` durch property: +```php +echo $obj->undeclared; // E_NOTICE, später E_WARNING +$obj->undeclared = 1; // läuft leise ohne Meldung durch +$obj->unknownMethod(); // Fatal error (nicht abfangbar mit try/catch) +``` + +Ein Fatal Error beendete die Anwendung ohne die Möglichkeit, darauf zu reagieren. Das stille Schreiben in nicht existierende Eigenschaften ohne Warnung konnte zu schwerwiegenden Fehlern führen, die schwer zu entdecken waren. `Nette\Object` fing all diese Fälle ab und warf eine `MemberAccessException`, was Programmierern ermöglichte, auf Fehler zu reagieren und sie zu beheben. + +```php +echo $obj->undeclared; // wirft Nette\MemberAccessException +$obj->undeclared = 1; // wirft Nette\MemberAccessException +$obj->unknownMethod(); // wirft Nette\MemberAccessException +``` + +Seit PHP 7.0 verursacht die Sprache keine nicht abfangbaren Fatal Errors mehr, und seit PHP 8.2 wird der Zugriff auf nicht deklarierte Eigenschaften als Fehler betrachtet. + + +Hilfe "Did you mean?" +--------------------- +`Nette\Object` kam mit einer sehr angenehmen Funktion: intelligenter Hilfe bei Tippfehlern. Wenn ein Entwickler einen Fehler im Namen einer Methode oder Variablen machte, meldete es nicht nur den Fehler, sondern bot auch eine helfende Hand in Form eines Vorschlags für den richtigen Namen. Diese ikonische Meldung, bekannt als "did you mean?", ersparte Programmierern Stunden der Suche nach Tippfehlern: + +```php +class Foo extends Nette\Object +{ + public static function from($var) + { + } +} + +$foo = Foo::form($var); +// wirft Nette\MemberAccessException +// "Call to undefined static method Foo::form(), did you mean from()?" +``` + +Das heutige PHP hat zwar keine Form von „did you mean?“, aber dieser Zusatz kann von [Tracy|tracy:] zu Fehlern hinzugefügt werden. Und solche Fehler sogar [automatisch korrigieren |tracy:open-files-in-ide#Beispiele]. + + +Properties mit kontrolliertem Zugriff +------------------------------------- +Eine bedeutende Innovation, die SmartObject in PHP einführte, waren Properties (Eigenschaften) mit kontrolliertem Zugriff. Dieses Konzept, das in Sprachen wie C# oder Python üblich ist, ermöglichte es Entwicklern, den Zugriff auf Objektdaten elegant zu kontrollieren und deren Konsistenz sicherzustellen. Properties sind ein mächtiges Werkzeug der objektorientierten Programmierung. Sie funktionieren wie Variablen, werden aber tatsächlich durch Methoden (Getter und Setter) repräsentiert. Dies ermöglicht die Validierung von Eingaben oder die Generierung von Werten erst im Moment des Lesens. + +Um Properties zu verwenden, müssen Sie: +- Der Klasse eine Annotation im Format `@property $xyz` hinzufügen +- Einen Getter mit dem Namen `getXyz()` oder `isXyz()`, einen Setter mit dem Namen `setXyz()` erstellen +- Sicherstellen, dass Getter und Setter *public* oder *protected* sind. Sie sind optional – sie können also als *read-only* oder *write-only* Property existieren + +Zeigen wir ein praktisches Beispiel an der Klasse Circle, wo wir Properties verwenden, um sicherzustellen, dass der Radius immer eine nicht negative Zahl ist. Wir ersetzen das ursprüngliche `public $radius` durch eine Property: ```php /** @@ -34,22 +79,22 @@ class Circle { use Nette\SmartObject; - private float $radius = 0.0; // not public + private float $radius = 0.0; // ist nicht public! - // getter for property $radius + // Getter für die Eigenschaft $radius protected function getRadius(): float { return $this->radius; } - // setter for property $radius + // Setter für die Eigenschaft $radius protected function setRadius(float $radius): void { - // sanitizing value before saving it + // wir bereinigen den Wert vor dem Speichern $this->radius = max(0.0, $radius); } - // getter for property $visible + // Getter für die Eigenschaft $visible protected function isVisible(): bool { return $this->radius > 0; @@ -57,84 +102,30 @@ class Circle } $circle = new Circle; -$circle->radius = 10; // actually calls setRadius(10) -echo $circle->radius; // calls getRadius() -echo $circle->visible; // calls isVisible() +$circle->radius = 10; // ruft tatsächlich setRadius(10) auf +echo $circle->radius; // ruft getRadius() auf +echo $circle->visible; // ruft isVisible() auf ``` -Eigenschaften sind in erster Linie "syntaktischer Zucker" ((syntactic sugar)), der das Leben des Programmierers durch Vereinfachung des Codes versüßen soll. Wenn Sie sie nicht wollen, müssen Sie sie nicht verwenden. - - -Statische Klassen .[#toc-static-classes] -======================================== - -Statische Klassen, d.h. Klassen, die nicht instanziiert werden sollen, können mit dem Trait `Nette\StaticClass` gekennzeichnet werden: +Seit PHP 8.4 kann dieselbe Funktionalität mit Property Hooks erreicht werden, die eine viel elegantere und prägnantere Syntax bieten: ```php -class Strings +class Circle { - use Nette\StaticClass; -} -``` - -Wenn Sie versuchen, eine Instanz zu erstellen, wird die Ausnahme `Error` ausgelöst, die angibt, dass die Klasse statisch ist. - - -Ein Blick in die Geschichte .[#toc-a-look-into-the-history] -=========================================================== - -SmartObject hat das Verhalten von Klassen in vielerlei Hinsicht verbessert und korrigiert, aber die Entwicklung von PHP hat die meisten der ursprünglichen Funktionen überflüssig gemacht. Im Folgenden werfen wir einen Blick in die Geschichte, wie sich die Dinge entwickelt haben. - -Von Anfang an litt das PHP-Objektmodell unter einer Reihe von schwerwiegenden Mängeln und Ineffizienzen. Dies war der Grund für die Schaffung der Klasse `Nette\Object` (im Jahr 2007), mit der versucht wurde, diese zu beheben und die Benutzung von PHP zu verbessern. Es reichte aus, dass andere Klassen von ihr erbten und von den Vorteilen profitierten, die sie mit sich brachte. Als PHP 5.4 mit Trait-Unterstützung kam, wurde die Klasse `Nette\Object` durch `Nette\SmartObject` ersetzt. Somit war es nicht mehr notwendig, von einem gemeinsamen Vorfahren zu erben. Darüber hinaus konnte Trait in Klassen verwendet werden, die bereits von einer anderen Klasse geerbt hatten. Das endgültige Ende von `Nette\Object` kam mit der Veröffentlichung von PHP 7.2, die es verbot, Klassen den Namen `Object` zu geben. - -Im weiteren Verlauf der PHP-Entwicklung wurden das Objektmodell und die Sprachfähigkeiten verbessert. Die einzelnen Funktionen der Klasse `SmartObject` wurden überflüssig. Seit der Veröffentlichung von PHP 8.2 ist die einzige Funktion, die noch nicht direkt in PHP unterstützt wird, die Möglichkeit, sogenannte [Properties |#Properties, getters and setters] zu verwenden. - -Welche Funktionen boten `Nette\Object` und `Nette\Object` früher? Hier ist ein Überblick. (Die Beispiele verwenden die Klasse `Nette\Object`, aber die meisten der Eigenschaften gelten auch für die Eigenschaft `Nette\SmartObject` ). - - -Inkonsistente Fehler .[#toc-inconsistent-errors] ------------------------------------------------- -PHP hatte ein inkonsistentes Verhalten beim Zugriff auf nicht deklarierte Mitglieder. Der Zustand zum Zeitpunkt von `Nette\Object` war wie folgt: - -```php -echo $obj->undeclared; // E_NOTICE, later E_WARNING -$obj->undeclared = 1; // passes silently without reporting -$obj->unknownMethod(); // Fatal error (not catchable by try/catch) -``` - -Ein schwerwiegender Fehler beendete die Anwendung, ohne dass eine Möglichkeit zur Reaktion bestand. Das stille Schreiben auf nicht existierende Mitglieder ohne Warnung konnte zu schwerwiegenden Fehlern führen, die schwer zu erkennen waren. `Nette\Object` Alle diese Fälle wurden abgefangen und eine Ausnahme `MemberAccessException` wurde ausgelöst. - -```php -echo $obj->undeclared; // throw Nette\MemberAccessException -$obj->undeclared = 1; // throw Nette\MemberAccessException -$obj->unknownMethod(); // throw Nette\MemberAccessException -``` -Seit PHP 7.0 verursacht PHP keine nicht abfangbaren fatalen Fehler mehr, und der Zugriff auf nicht deklarierte Member ist seit PHP 8.2 ein Fehler. - - -Haben Sie gemeint? .[#toc-did-you-mean] ---------------------------------------- -Wenn ein `Nette\MemberAccessException` -Fehler ausgelöst wurde, etwa aufgrund eines Tippfehlers beim Zugriff auf eine Objektvariable oder beim Aufruf einer Methode, versuchte `Nette\Object`, in der Fehlermeldung einen Hinweis darauf zu geben, wie der Fehler zu beheben ist, und zwar in Form des ikonischen Zusatzes "Meinten Sie?". + public float $radius = 0.0 { + set => max(0.0, $value); + } -```php -class Foo extends Nette\Object -{ - public static function from($var) - { + public bool $visible { + get => $this->radius > 0; } } - -$foo = Foo::form($var); -// throw Nette\MemberAccessException -// "Call to undefined static method Foo::form(), did you mean from()?" ``` -Das heutige PHP hat zwar keine Form von "did you mean?", aber [Tracy |tracy:] fügt diesen Zusatz zu Fehlern hinzu. Und es kann solche Fehler sogar selbst [beheben |tracy:open-files-in-ide#toc-demos]. - -Erweiterungsmethoden .[#toc-extension-methods] ----------------------------------------------- -Inspiriert von Erweiterungsmethoden aus C#. Sie bieten die Möglichkeit, neue Methoden zu bestehenden Klassen hinzuzufügen. Zum Beispiel könnten Sie die Methode `addDateTime()` zu einem Formular hinzufügen, um Ihren eigenen DateTimePicker hinzuzufügen. +Extension methods +----------------- +`Nette\Object` brachte ein weiteres interessantes Konzept in PHP ein, das von modernen Programmiersprachen inspiriert war – Extension Methods. Diese Funktion, übernommen aus C#, ermöglichte es Entwicklern, bestehende Klassen elegant um neue Methoden zu erweitern, ohne sie ändern oder von ihnen erben zu müssen. Zum Beispiel konnten Sie einem Formular eine Methode `addDateTime()` hinzufügen, die einen eigenen DateTimePicker hinzufügt: ```php Form::extensionMethod( @@ -146,22 +137,22 @@ $form = new Form; $form->addDateTime('date'); ``` -Erweiterungsmethoden erwiesen sich als unpraktisch, da ihre Namen von Editoren nicht automatisch vervollständigt wurden, sondern sie meldeten, dass die Methode nicht existierte. Daher wurde ihre Unterstützung eingestellt. +Extension Methods erwiesen sich als unpraktisch, da ihre Namen von Editoren nicht vorgeschlagen wurden, sondern diese meldeten, dass die Methode nicht existiert. Daher wurde ihre Unterstützung eingestellt. Heutzutage ist es üblicher, Komposition oder Vererbung zur Erweiterung der Funktionalität von Klassen zu verwenden. -Ermitteln des Klassennamens .[#toc-getting-the-class-name] ----------------------------------------------------------- +Ermitteln des Klassennamens +--------------------------- +Zur Ermittlung des Klassennamens bot SmartObject eine einfache Methode: ```php -$class = $obj->getClass(); // using Nette\Object -$class = $obj::class; // since PHP 8.0 +$class = $obj->getClass(); // mit Nette\Object +$class = $obj::class; // seit PHP 8.0 ``` -Zugang zu Reflexion und Anmerkungen .[#toc-access-to-reflection-and-annotations] --------------------------------------------------------------------------------- - -`Nette\Object` bietet den Zugang zu Reflexion und Kommentaren mit den Methoden `getReflection()` und `getAnnotation()`: +Zugriff auf Reflexion und Annotationen +-------------------------------------- +`Nette\Object` bot Zugriff auf Reflexion und Annotationen über die Methoden `getReflection()` und `getAnnotation()`. Dieser Ansatz vereinfachte die Arbeit mit Metainformationen von Klassen erheblich: ```php /** @@ -173,10 +164,10 @@ class Foo extends Nette\Object $obj = new Foo; $reflection = $obj->getReflection(); -$reflection->getAnnotation('author'); // returns 'John Doe +$reflection->getAnnotation('author'); // gibt 'John Doe' zurück ``` -Seit PHP 8.0 ist es möglich, auf Metainformationen in Form von Attributen zuzugreifen: +Seit PHP 8.0 ist es möglich, auf Metainformationen in Form von Attributen zuzugreifen, die noch mehr Möglichkeiten und eine bessere Typkontrolle bieten: ```php #[Author('John Doe')] @@ -190,10 +181,9 @@ $reflection->getAttributes(Author::class)[0]; ``` -Methoden-Getter .[#toc-method-getters] --------------------------------------- - -`Nette\Object` boten eine elegante Möglichkeit, mit Methoden so umzugehen, als wären sie Variablen: +Methoden-Getter +--------------- +`Nette\Object` bot eine elegante Möglichkeit, Methoden so zu übergeben, als wären sie Variablen: ```php class Foo extends Nette\Object @@ -209,7 +199,7 @@ $method = $obj->adder; echo $method(2, 3); // 5 ``` -Seit PHP 8.1 können Sie die sogenannte "First-Class-Callable-Syntax":https://www.php.net/manual/en/functions.first_class_callable_syntax verwenden: +Seit PHP 8.1 kann die sogenannte "first-class callable syntax":https://www.php.net/manual/en/functions.first_class_callable_syntax verwendet werden, die dieses Konzept noch weiterführt: ```php $obj = new Foo; @@ -218,10 +208,9 @@ echo $method(2, 3); // 5 ``` -Ereignisse .[#toc-events] -------------------------- - -`Nette\Object` bietet syntaktischen Zucker zum Auslösen des [Ereignisses |nette:glossary#events]: +Ereignisse +---------- +SmartObject bietet eine vereinfachte Syntax für die Arbeit mit [Ereignissen |nette:glossary#Events Ereignisse]. Ereignisse ermöglichen es Objekten, andere Teile der Anwendung über Änderungen ihres Zustands zu informieren: ```php class Circle extends Nette\Object @@ -231,12 +220,12 @@ class Circle extends Nette\Object public function setRadius(float $radius): void { $this->onChange($this, $radius); - $this->radius = $radius + $this->radius = $radius; } } ``` -Der Code `$this->onChange($this, $radius)` entspricht folgendem: +Der Code `$this->onChange($this, $radius)` ist äquivalent zur folgenden Schleife: ```php foreach ($this->onChange as $callback) { @@ -244,7 +233,7 @@ foreach ($this->onChange as $callback) { } ``` -Aus Gründen der Übersichtlichkeit wird empfohlen, die magische Methode `$this->onChange()` zu vermeiden. Ein guter Ersatz ist [Nette\Utils\Arrays::invoke |arrays#invoke]: +Aus Gründen der Verständlichkeit empfehlen wir, die magische Methode `$this->onChange()` zu vermeiden. Eine praktische Alternative ist beispielsweise die Funktion [Nette\Utils\Arrays::invoke |arrays#invoke]: ```php Nette\Utils\Arrays::invoke($this->onChange, $this, $radius); diff --git a/utils/de/staticclass.texy b/utils/de/staticclass.texy new file mode 100644 index 0000000000..96ac4bcac0 --- /dev/null +++ b/utils/de/staticclass.texy @@ -0,0 +1,21 @@ +Statische Klassen +***************** + +.[perex] +StaticClass dient zur Kennzeichnung statischer Klassen. + + +Installation: + +```shell +composer require nette/utils +``` + +Statische Klassen, also Klassen, die nicht zur Instanziierung vorgesehen sind, können Sie mit dem Trait [api:Nette\StaticClass] kennzeichnen: + +```php +class Strings +{ + use Nette\StaticClass; +} +``` diff --git a/utils/de/strings.texy b/utils/de/strings.texy index f5d0d29c12..af0eb69264 100644 --- a/utils/de/strings.texy +++ b/utils/de/strings.texy @@ -1,8 +1,8 @@ -String-Funktionen -***************** +Arbeiten mit Zeichenketten +************************** .[perex] -[api:Nette\Utils\Strings] ist eine statische Klasse, die viele nützliche Funktionen für die Arbeit mit UTF-8-kodierten Zeichenketten enthält. +[api:Nette\Utils\Strings] ist eine statische Klasse mit nützlichen Funktionen für die Arbeit mit Zeichenketten, hauptsächlich in UTF-8-Kodierung. Installation: @@ -11,14 +11,14 @@ Installation: composer require nette/utils ``` -Alle Beispiele gehen davon aus, dass der folgende Klassenalias definiert ist: +Alle Beispiele setzen voraus, dass ein Alias erstellt wurde: ```php use Nette\Utils\Strings; ``` -Buchstabe Case .[#toc-letter-case] +Änderung der Groß-/Kleinschreibung ================================== Diese Funktionen erfordern die PHP-Erweiterung `mbstring`. @@ -27,67 +27,67 @@ Diese Funktionen erfordern die PHP-Erweiterung `mbstring`. lower(string $s): string .[method] ---------------------------------- -Konvertiert alle Zeichen einer UTF-8-Zeichenfolge in Kleinbuchstaben. +Konvertiert einen UTF-8-String in Kleinbuchstaben. ```php -Strings::lower('Hello world'); // 'hello world' +Strings::lower('Guten Tag'); // 'guten tag' ``` upper(string $s): string .[method] ---------------------------------- -Konvertiert alle Zeichen einer UTF-8-Zeichenfolge in Großbuchstaben. +Konvertiert einen UTF-8-String in Großbuchstaben. ```php -Strings::upper('Hello world'); // 'HELLO WORLD' +Strings::upper('Guten Tag'); // 'GUTEN TAG' ``` firstUpper(string $s): string .[method] --------------------------------------- -Konvertiert das erste Zeichen einer UTF-8-Zeichenkette in Großbuchstaben und lässt die anderen Zeichen unverändert. +Konvertiert den ersten Buchstaben eines UTF-8-Strings in einen Großbuchstaben, die anderen bleiben unverändert. ```php -Strings::firstUpper('hello world'); // 'Hello world' +Strings::firstUpper('guten tag'); // 'Guten tag' ``` firstLower(string $s): string .[method] --------------------------------------- -Konvertiert das erste Zeichen einer UTF-8-Zeichenfolge in Kleinbuchstaben und lässt die anderen Zeichen unverändert. +Konvertiert den ersten Buchstaben eines UTF-8-Strings in einen Kleinbuchstaben, die anderen bleiben unverändert. ```php -Strings::firstLower('Hello world'); // 'hello world' +Strings::firstLower('Guten Tag'); // 'guten Tag' ``` capitalize(string $s): string .[method] --------------------------------------- -Konvertiert das erste Zeichen eines jeden Wortes einer UTF-8-Zeichenkette in Großbuchstaben und die anderen in Kleinbuchstaben. +Konvertiert den ersten Buchstaben jedes Wortes in einem UTF-8-String in einen Großbuchstaben, die anderen in Kleinbuchstaben. ```php -Strings::capitalize('Hello world'); // 'Hello World' +Strings::capitalize('Guten Tag'); // 'Guten Tag' ``` -Bearbeiten einer Zeichenkette .[#toc-editing-a-string] -====================================================== +Bearbeitung von Zeichenketten +============================= normalize(string $s): string .[method] -------------------------------------- -Entfernt Steuerzeichen, normalisiert Zeilenumbrüche auf `\n`, entfernt führende und nachfolgende Leerzeilen, schneidet Leerzeichen am Zeilenende ab, normalisiert UTF-8 auf die normale Form von NFC. +Entfernt Steuerzeichen, normalisiert Zeilenenden auf `\n`, entfernt führende und nachfolgende leere Zeilen, entfernt nachfolgende Leerzeichen in Zeilen, normalisiert UTF-8 auf die Normalform NFC. unixNewLines(string $s): string .[method] ----------------------------------------- -Konvertiert Zeilenumbrüche in `\n`, die auf Unix-Systemen verwendet werden. Die Zeilenumbrüche sind: `\n`, `\r`, `\r\n`, U+2028 Zeilentrenner, U+2029 Absatztrenner. +Konvertiert Zeilenenden in `\n`, die in Unix-Systemen verwendet werden. Zeilenenden sind: `\n`, `\r`, `\r\n`, U+2028 line separator, U+2029 paragraph separator. ```php $unixLikeLines = Strings::unixNewLines($string); @@ -97,42 +97,42 @@ $unixLikeLines = Strings::unixNewLines($string); platformNewLines(string $s): string .[method] --------------------------------------------- -Konvertiert Zeilenumbrüche in plattformspezifische Zeichen, d. h. `\r\n` unter Windows und `\n` in anderen Betriebssystemen. Die Zeilenumbrüche sind `\n`, `\r`, `\r\n`, U+2028 Zeilentrenner, U+2029 Absatztrenner. +Konvertiert Zeilenenden in die für die aktuelle Plattform spezifischen Zeichen, d.h. `\r\n` unter Windows und `\n` anderswo. Zeilenenden sind: `\n`, `\r`, `\r\n`, U+2028 line separator, U+2029 paragraph separator. ```php $platformLines = Strings::platformNewLines($string); ``` -webalize(string $s, string $charlist=null, bool $lower=true): string .[method] ------------------------------------------------------------------------------- +webalize(string $s, ?string $charlist=null, bool $lower=true): string .[method] +------------------------------------------------------------------------------- -Ändert die UTF-8-Zeichenkette in die Form, die in der URL verwendet wird, d. h. entfernt diakritische Zeichen und ersetzt alle Zeichen außer Buchstaben des englischen Alphabets und Zahlen durch einen Bindestrich. +Modifiziert einen UTF-8-String in eine Form, die in URLs verwendet wird, d.h. entfernt Diakritika und ersetzt alle Zeichen außer Buchstaben des englischen Alphabets und Ziffern durch Bindestriche. ```php -Strings::webalize('žluťoučký kůň'); // 'zlutoucky-kun' +Strings::webalize('unser produkt'); // 'unser-produkt' ``` -Andere Zeichen können ebenfalls beibehalten werden, müssen aber als zweites Argument übergeben werden. +Sollen auch andere Zeichen beibehalten werden, können diese im zweiten Parameter der Funktion angegeben werden. ```php -Strings::webalize('10. image_id', '._'); // '10.-image_id' +Strings::webalize('10. bild_id', '._'); // '10.-bild_id' ``` -Das dritte Argument kann die Umwandlung der Zeichenkette in Kleinbuchstaben unterdrücken. +Mit dem dritten Parameter kann die Konvertierung in Kleinbuchstaben unterdrückt werden. ```php -Strings::webalize('Hello world', null, false); // 'Hello-world' +Strings::webalize('Guten Tag', null, false); // 'Guten-Tag' ``` .[caution] Erfordert die PHP-Erweiterung `intl`. -trim(string $s, string $charlist=null): string .[method] --------------------------------------------------------- +trim(string $s, ?string $charlist=null): string .[method] +--------------------------------------------------------- -Entfernt alle links- und rechtsseitigen Leerzeichen (oder die als zweites Argument übergebenen Zeichen) aus einer UTF-8-kodierten Zeichenkette. +Entfernt Leerzeichen (oder andere im zweiten Parameter angegebene Zeichen) vom Anfang und Ende eines UTF-8-Strings. ```php Strings::trim(' Hello '); // 'Hello' @@ -142,21 +142,21 @@ Strings::trim(' Hello '); // 'Hello' truncate(string $s, int $maxLen, string $append=`'…'`): string .[method] ------------------------------------------------------------------------ -Schneidet eine UTF-8-Zeichenkette auf die angegebene Maximallänge ab, wobei versucht wird, keine ganzen Wörter zu trennen. Nur wenn die Zeichenkette abgeschnitten wird, wird ein Ellipsis (oder etwas anderes, das mit dem dritten Argument festgelegt wird) an die Zeichenkette angehängt. +Kürzt einen UTF-8-String auf die angegebene maximale Länge, wobei versucht wird, ganze Wörter beizubehalten. Wenn der String gekürzt wird, wird am Ende ein Dreipunkt hinzugefügt (kann durch den dritten Parameter geändert werden). ```php -$text = 'Hello, how are you today?'; -Strings::truncate($text, 5); // 'Hell…' -Strings::truncate($text, 20); // 'Hello, how are you…' -Strings::truncate($text, 30); // 'Hello, how are you today?' -Strings::truncate($text, 20, '~'); // 'Hello, how are you~' +$text = 'Sagen Sie, wie geht es Ihnen?'; +Strings::truncate($text, 5); // 'Sagen…' +Strings::truncate($text, 20); // 'Sagen Sie, wie geht…' +Strings::truncate($text, 30); // 'Sagen Sie, wie geht es Ihnen?' +Strings::truncate($text, 20, '~'); // 'Sagen Sie, wie geht~' ``` indent(string $s, int $level=1, string $indentationChar=`"\t"`): string .[method] --------------------------------------------------------------------------------- -Rückt einen mehrzeiligen Text von links ein. Das zweite Argument legt fest, wie viele Einrückungszeichen verwendet werden sollen, während die Einrückung selbst das dritte Argument ist (standardmäßig *tab*). +Rückt einen mehrzeiligen Text von links ein. Die Anzahl der Einrückungen wird durch den zweiten Parameter bestimmt, womit eingerückt wird, durch den dritten Parameter (Standardwert ist Tabulator). ```php Strings::indent('Nette'); // "\tNette" @@ -167,7 +167,7 @@ Strings::indent('Nette', 2, '+'); // '++Nette' padLeft(string $s, int $length, string $pad=`' '`): string .[method] -------------------------------------------------------------------- -Füllt eine UTF-8-Zeichenkette auf die angegebene Länge auf, indem die Zeichenkette `$pad` dem Anfang vorangestellt wird. +Füllt einen UTF-8-String auf die angegebene Länge auf, indem der String `$pad` von links wiederholt wird. ```php Strings::padLeft('Nette', 6); // ' Nette' @@ -178,7 +178,7 @@ Strings::padLeft('Nette', 8, '+*'); // '+*+Nette' padRight(string $s, int $length, string $pad=`' '`): string .[method] --------------------------------------------------------------------- -Bringt eine UTF-8-Zeichenfolge auf die angegebene Länge, indem die Zeichenfolge `$pad` am Ende angefügt wird. +Füllt einen UTF-8-String auf die angegebene Länge auf, indem der String `$pad` von rechts wiederholt wird. ```php Strings::padRight('Nette', 6); // 'Nette ' @@ -186,10 +186,10 @@ Strings::padRight('Nette', 8, '+*'); // 'Nette+*+' ``` -substring(string $s, int $start, int $length=null): string .[method] --------------------------------------------------------------------- +substring(string $s, int $start, ?int $length=null): string .[method] +--------------------------------------------------------------------- -Gibt einen Teil der UTF-8-Zeichenkette zurück, der durch die Startposition `$start` und die Länge `$length` angegeben wird. Wenn `$start` negativ ist, beginnt die zurückgegebene Zeichenkette am `$start`'ten Zeichen vom Ende der Zeichenkette. +Gibt einen Teil des UTF-8-Strings `$s` zurück, der durch die Startposition `$start` und die Länge `$length` angegeben wird. Wenn `$start` negativ ist, beginnt der zurückgegebene String beim -$start`-ten Zeichen vom Ende. ```php Strings::substring('Nette Framework', 0, 5); // 'Nette' @@ -201,7 +201,7 @@ Strings::substring('Nette Framework', -4); // 'work' reverse(string $s): string .[method] ------------------------------------ -Kehrt die UTF-8-Zeichenkette um. +Kehrt einen UTF-8-String um. ```php Strings::reverse('Nette'); // 'etteN' @@ -211,77 +211,77 @@ Strings::reverse('Nette'); // 'etteN' length(string $s): int .[method] -------------------------------- -Gibt die Anzahl der Zeichen (nicht Bytes) in der UTF-8-Zeichenfolge zurück. +Gibt die Anzahl der Zeichen (nicht Bytes) in einem UTF-8-String zurück. -Das ist die Anzahl der Unicode-Codepunkte, die sich von der Anzahl der Grapheme unterscheiden kann. +Dies ist die Anzahl der Unicode-Codepunkte, die sich von der Anzahl der Grapheme unterscheiden kann. ```php -Strings::length('Nette'); // 5 -Strings::length('red'); // 3 +Strings::length('Nette'); // 5 +Strings::length('červená'); // 7 ``` startsWith(string $haystack, string $needle): bool .[method deprecated] ----------------------------------------------------------------------- -Prüft, ob die Zeichenfolge `$haystack` mit `$needle` beginnt. +Stellt fest, ob der String `$haystack` mit dem String `$needle` beginnt. ```php -$haystack = 'Begins'; +$haystack = 'Beginnt'; $needle = 'Be'; Strings::startsWith($haystack, $needle); // true ``` .[note] -Verwenden Sie die native `str_starts_with()`:https://www.php.net/manual/en/function.str-starts-with.php. +Verwenden Sie die native Funktion `str_starts_with()`:https://www.php.net/manual/en/function.str-starts-with.php. endsWith(string $haystack, string $needle): bool .[method deprecated] --------------------------------------------------------------------- -Prüft, ob `$haystack` string mit `$needle` endet. +Stellt fest, ob der String `$haystack` mit dem String `$needle` endet. ```php -$haystack = 'Ends'; -$needle = 'ds'; +$haystack = 'Endet'; +$needle = 'det'; Strings::endsWith($haystack, $needle); // true ``` .[note] -Verwenden Sie `str_ends_with()`:https://www.php.net/manual/en/function.str-ends-with.php. +Verwenden Sie die native Funktion `str_ends_with()`:https://www.php.net/manual/en/function.str-ends-with.php. contains(string $haystack, string $needle): bool .[method deprecated] --------------------------------------------------------------------- -Überprüft, ob `$haystack` string `$needle` enthält. +Stellt fest, ob der String `$haystack` `$needle` enthält. ```php -$haystack = 'Contains'; -$needle = 'tai'; +$haystack = 'Hörsaal'; +$needle = 'saal'; Strings::contains($haystack, $needle); // true ``` .[note] -Verwenden Sie die native `str_contains()`:https://www.php.net/manual/en/function.str-contains.php. +Verwenden Sie die native Funktion `str_contains()`:https://www.php.net/manual/en/function.str-contains.php. -compare(string $left, string $right, int $length=null): bool .[method] ----------------------------------------------------------------------- +compare(string $left, string $right, ?int $length=null): bool .[method] +----------------------------------------------------------------------- -Vergleicht zwei UTF-8-Zeichenketten oder deren Teile, ohne Berücksichtigung der Groß- und Kleinschreibung. Ist `$length` gleich null, werden ganze Zeichenketten verglichen, ist er negativ, wird die entsprechende Anzahl von Zeichen vom Ende der Zeichenketten verglichen, andernfalls die entsprechende Anzahl von Zeichen vom Anfang. +Vergleicht zwei UTF-8-Strings oder Teile davon ohne Berücksichtigung der Groß-/Kleinschreibung. Wenn `$length` null enthält, werden die gesamten Strings verglichen, wenn es negativ ist, wird die entsprechende Anzahl von Zeichen vom Ende der Strings verglichen, andernfalls wird die entsprechende Anzahl von Zeichen vom Anfang verglichen. ```php Strings::compare('Nette', 'nette'); // true -Strings::compare('Nette', 'next', 2); // true - two first characters match -Strings::compare('Nette', 'Latte', -2); // true - two last characters match +Strings::compare('Nette', 'next', 2); // true - Übereinstimmung der ersten 2 Zeichen +Strings::compare('Nette', 'Latte', -2); // true - Übereinstimmung der letzten 2 Zeichen ``` findPrefix(...$strings): string .[method] ----------------------------------------- -Findet das gemeinsame Präfix von Zeichenketten oder gibt eine leere Zeichenkette zurück, wenn das Präfix nicht gefunden wurde. +Findet den gemeinsamen Anfang von Strings. Oder gibt einen leeren String zurück, wenn kein gemeinsames Präfix gefunden wurde. ```php Strings::findPrefix('prefix-a', 'prefix-bb', 'prefix-c'); // 'prefix-' @@ -293,33 +293,33 @@ Strings::findPrefix('Nette', 'is', 'great'); // '' before(string $haystack, string $needle, int $nth=1): ?string .[method] ----------------------------------------------------------------------- -Gibt einen Teil von `$haystack` zurück, bevor `$nth` in `$needle` vorkommt, oder gibt `null` zurück, wenn die Nadel nicht gefunden wurde. Ein negativer Wert bedeutet, dass vom Ende her gesucht wird. +Gibt den Teil des Strings `$haystack` vor dem n-ten `$nth` Vorkommen des Strings `$needle` zurück. Oder `null`, wenn `$needle` nicht gefunden wurde. Bei einem negativen Wert von `$nth` wird vom Ende des Strings gesucht. ```php -Strings::before('Nette_is_great', '_', 1); // 'Nette' -Strings::before('Nette_is_great', '_', -2); // 'Nette' -Strings::before('Nette_is_great', ' '); // null -Strings::before('Nette_is_great', '_', 3); // null +Strings::before('Nette_ist_großartig', '_', 1); // 'Nette' +Strings::before('Nette_ist_großartig', '_', -2); // 'Nette' +Strings::before('Nette_ist_großartig', ' '); // null +Strings::before('Nette_ist_großartig', '_', 3); // null ``` after(string $haystack, string $needle, int $nth=1): ?string .[method] ---------------------------------------------------------------------- -Gibt einen Teil von `$haystack` zurück, nachdem `$nth` in `$needle` vorkommt, oder gibt `null` zurück, wenn `$needle` nicht gefunden wurde. Ein negativer Wert von `$nth` bedeutet, dass vom Ende her gesucht wird. +Gibt den Teil des Strings `$haystack` nach dem n-ten `$nth` Vorkommen des Strings `$needle` zurück. Oder `null`, wenn `$needle` nicht gefunden wurde. Bei einem negativen Wert von `$nth` wird vom Ende des Strings gesucht. ```php -Strings::after('Nette_is_great', '_', 2); // 'great' -Strings::after('Nette_is_great', '_', -1); // 'great' -Strings::after('Nette_is_great', ' '); // null -Strings::after('Nette_is_great', '_', 3); // null +Strings::after('Nette_ist_großartig', '_', 2); // 'großartig' +Strings::after('Nette_ist_großartig', '_', -1); // 'großartig' +Strings::after('Nette_ist_großartig', ' '); // null +Strings::after('Nette_ist_großartig', '_', 3); // null ``` indexOf(string $haystack, string $needle, int $nth=1): ?int .[method] --------------------------------------------------------------------- -Gibt die Position in Zeichen des `$nth` Vorkommens von `$needle` in `$haystack` oder `null` zurück, wenn `$needle` nicht gefunden wurde. Ein negativer Wert von `$nth` bedeutet, dass vom Ende her gesucht wird. +Gibt die Position in Zeichen des n-ten `$nth` Vorkommens des Strings `$needle` im String `$haystack` zurück. Oder `null`, wenn `$needle` nicht gefunden wurde. Bei einem negativen Wert von `$nth` wird vom Ende des Strings gesucht. ```php Strings::indexOf('abc abc abc', 'abc', 2); // 4 @@ -328,14 +328,14 @@ Strings::indexOf('abc abc abc', 'd'); // null ``` -Kodierung .[#toc-encoding] -========================== +Kodierung +========= fixEncoding(string $s): string .[method] ---------------------------------------- -Entfernt alle ungültigen UTF-8-Zeichen aus einer Zeichenkette. +Entfernt ungültige UTF-8-Zeichen aus dem String. ```php $correctStrings = Strings::fixEncoding($string); @@ -345,7 +345,7 @@ $correctStrings = Strings::fixEncoding($string); checkEncoding(string $s): bool .[method deprecated] --------------------------------------------------- -Prüft, ob die Zeichenkette in UTF-8-Kodierung gültig ist. +Stellt fest, ob es sich um einen gültigen UTF-8-String handelt. ```php $isUtf8 = Strings::checkEncoding($string); @@ -358,7 +358,7 @@ Verwenden Sie [Nette\Utils\Validator::isUnicode() |validators#isUnicode]. toAscii(string $s): string .[method] ------------------------------------ -Konvertiert UTF-8-String in ASCII, d.h. entfernt diakritische Zeichen usw. +Konvertiert einen UTF-8-String in ASCII, d.h. entfernt Diakritika usw. ```php Strings::toAscii('žluťoučký kůň'); // 'zlutoucky kun' @@ -371,33 +371,33 @@ Erfordert die PHP-Erweiterung `intl`. chr(int $code): string .[method] -------------------------------- -Gibt ein bestimmtes Zeichen in UTF-8 ab Codepoint (Zahl im Bereich 0x0000..D7FF oder 0xE000..10FFFF) zurück. +Gibt ein spezifisches Zeichen in UTF-8 aus einem Codepunkt zurück (Zahl im Bereich 0x0000..D7FF und 0xE000..10FFFF). ```php -Strings::chr(0xA9); // '©' +Strings::chr(0xA9); // '©' in UTF-8-Kodierung ``` ord(string $char): int .[method] -------------------------------- -Gibt einen Codepunkt eines bestimmten Zeichens in UTF-8 zurück (Zahl im Bereich 0x0000..D7FF oder 0xE000..10FFFF). +Gibt den Codepunkt eines spezifischen Zeichens in UTF-8 zurück (Zahl im Bereich 0x0000..D7FF oder 0xE000..10FFFF). ```php Strings::ord('©'); // 0xA9 ``` -Reguläre Ausdrücke .[#toc-regular-expressions] -============================================== +Reguläre Ausdrücke +================== -Die Klasse Strings bietet Funktionen für die Arbeit mit regulären Ausdrücken. Im Gegensatz zu den nativen PHP-Funktionen haben sie eine verständlichere API, bessere Unicode-Unterstützung und vor allem eine Fehlererkennung. Jeder Kompilierungs- oder Ausdrucksverarbeitungsfehler löst eine `Nette\RegexpException` Exception aus. +Die Klasse Strings bietet Funktionen für die Arbeit mit regulären Ausdrücken. Im Gegensatz zu nativen PHP-Funktionen verfügen sie über eine verständlichere API, bessere Unterstützung für Unicode und vor allem Fehlererkennung. Jeder Fehler bei der Kompilierung oder Verarbeitung eines Ausdrucks wirft eine Ausnahme `Nette\RegexpException`. split(string $subject, string $pattern, bool $captureOffset=false, bool $skipEmpty=false, int $limit=-1, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------- -Teilt die Zeichenkette entsprechend dem regulären Ausdruck in Arrays auf. Ausdrücke in Klammern werden ebenfalls erfasst und zurückgegeben. +Teilt einen String anhand eines regulären Ausdrucks in ein Array auf. Ausdrücke in Klammern werden ebenfalls erfasst und zurückgegeben. ```php Strings::split('hello, world', '~,\s*~'); @@ -417,16 +417,16 @@ Strings::split('hello, world, ', '~,\s*~', skipEmpty: true); // ['hello', 'world'] ``` -Wenn `$limit` angegeben ist, werden nur Teilstrings bis zum Limit zurückgegeben und der Rest der Zeichenkette wird in das letzte Element eingefügt. Eine Grenze von -1 oder 0 bedeutet keine Grenze. +Wenn `$limit` angegeben ist, werden nur Teilstrings bis zum Limit zurückgegeben, und der Rest des Strings wird im letzten Element platziert. Ein Limit von -1 oder 0 bedeutet keine Beschränkung. ```php Strings::split('hello, world, third', '~,\s*~', limit: 2); // ['hello', 'world, third'] ``` -Wenn `$utf8` für `true` steht, schaltet die Auswertung in den Unicode-Modus. Dies ist ähnlich wie die Angabe des Modifikators "u". +Wenn `$utf8` `true` ist, wird die Auswertung in den Unicode-Modus umgeschaltet. Ähnlich wie bei der Angabe des Modifikators `u`. -Wenn `$captureOffset` gleich `true` ist, wird für jede auftretende Übereinstimmung auch deren Position in der Zeichenkette zurückgegeben (in Bytes; in Zeichen, wenn `$utf8` gesetzt ist). Dies ändert den Rückgabewert in ein Array, in dem jedes Element ein Paar ist, das aus der übereinstimmenden Zeichenkette und ihrer Position besteht. +Wenn `$captureOffset` `true` ist, wird für jede gefundene Übereinstimmung auch ihre Position im String zurückgegeben (in Bytes; wenn `$utf8` gesetzt ist, in Zeichen). Dies ändert den Rückgabewert in ein Array, bei dem jedes Element ein Paar aus dem übereinstimmenden String und seiner Position ist. ```php Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true); @@ -440,7 +440,7 @@ Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true, utf8: true); match(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $utf8=false): ?array .[method] -------------------------------------------------------------------------------------------------------------------------------------------------- -Durchsucht die Zeichenkette nach dem Teil, der mit dem regulären Ausdruck übereinstimmt, und gibt ein Array mit dem gefundenen Ausdruck und einzelnen Unterausdrücken zurück, oder `null`. +Sucht im String nach einem Teil, der dem regulären Ausdruck entspricht, und gibt ein Array mit dem gefundenen Ausdruck und einzelnen Teilausdrücken zurück, oder `null`. ```php Strings::match('hello!', '~\w+(!+)~'); @@ -450,7 +450,7 @@ Strings::match('hello!', '~X~'); // null ``` -Wenn `$unmatchedAsNull` gleich `true` ist, werden nicht übereinstimmende Teilmuster als Null zurückgegeben; andernfalls werden sie als leere Zeichenfolge oder nicht zurückgegeben: +Wenn `$unmatchedAsNull` `true` ist, werden nicht erfasste Teilausdrücke als null zurückgegeben; andernfalls werden sie als leerer String zurückgegeben oder nicht zurückgegeben: ```php Strings::match('hello', '~\w+(!+)?~'); @@ -460,7 +460,7 @@ Strings::match('hello', '~\w+(!+)?~', unmatchedAsNull: true); // ['hello', null] ``` -Wenn `$utf8` gleich `true` ist, schaltet die Auswertung in den Unicode-Modus. Dies ist vergleichbar mit der Angabe des Modifikators "u": +Wenn `$utf8` `true` ist, wird die Auswertung in den Unicode-Modus umgeschaltet. Ähnlich wie bei der Angabe des Modifikators `u`: ```php Strings::match('žlutý kůň', '~\w+~'); @@ -470,9 +470,9 @@ Strings::match('žlutý kůň', '~\w+~', utf8: true); // ['žlutý'] ``` -Der Parameter `$offset` kann verwendet werden, um die Position anzugeben, an der die Suche beginnen soll (in Bytes; in Zeichen, wenn `$utf8` gesetzt ist). +Der Parameter `$offset` kann verwendet werden, um die Position anzugeben, ab der die Suche beginnen soll (in Bytes; wenn `$utf8` gesetzt ist, in Zeichen). -Wenn `$captureOffset` gleich `true` ist, wird für jede auftretende Übereinstimmung auch deren Position in der Zeichenkette zurückgegeben (in Bytes; in Zeichen, wenn `$utf8` eingestellt ist). Dadurch wird der Rückgabewert zu einem Array, in dem jedes Element ein Paar ist, das aus der übereinstimmenden Zeichenfolge und ihrem Versatz besteht: +Wenn `$captureOffset` `true` ist, wird für jede gefundene Übereinstimmung auch ihre Position im String zurückgegeben (in Bytes; wenn `$utf8` gesetzt ist, in Zeichen). Dies ändert den Rückgabewert in ein Array, bei dem jedes Element ein Paar aus dem übereinstimmenden String und seinem Offset ist: ```php Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true); @@ -483,10 +483,10 @@ Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true, utf8: true); ``` -matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false, bool $lazy=false): array|Generator .[method] +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Durchsucht die Zeichenkette nach allen Vorkommen, die mit dem regulären Ausdruck übereinstimmen, und gibt ein Array von Arrays zurück, das den gefundenen Ausdruck und jeden Unterausdruck enthält. +Sucht im String nach allen Vorkommen, die dem regulären Ausdruck entsprechen, und gibt ein Array von Arrays mit dem gefundenen Ausdruck und einzelnen Teilausdrücken zurück. ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~'); @@ -496,7 +496,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~'); ] */ ``` -Wenn `$patternOrder` `true` ist, ändert sich die Struktur der Ergebnisse so, dass das erste Element ein Array mit vollständigen Mustertreffern ist, das zweite ein Array mit Zeichenketten, die dem ersten Untermuster in Klammern entsprechen, und so weiter: +Wenn `$patternOrder` `true` ist, ändert sich die Struktur der Ergebnisse so, dass im ersten Eintrag ein Array der vollständigen Musterübereinstimmungen steht, im zweiten ein Array der Strings, die dem ersten geklammerten Teilausdruck entsprechen, und so weiter: ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~', patternOrder: true); @@ -506,7 +506,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~', patternOrder: true); ] */ ``` -Wenn `$unmatchedAsNull` gleich `true` ist, werden nicht übereinstimmende Teilmuster als Null zurückgegeben; andernfalls werden sie als leere Zeichenfolge oder nicht zurückgegeben: +Wenn `$unmatchedAsNull` `true` ist, werden nicht erfasste Teilausdrücke als null zurückgegeben; andernfalls werden sie als leerer String zurückgegeben oder nicht zurückgegeben: ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~', unmatchedAsNull: true); @@ -516,7 +516,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~', unmatchedAsNull: true); ] */ ``` -Wenn `$utf8` gleich `true` ist, schaltet die Auswertung in den Unicode-Modus. Dies ist vergleichbar mit der Angabe des Modifikators "u": +Wenn `$utf8` `true` ist, wird die Auswertung in den Unicode-Modus umgeschaltet. Ähnlich wie bei der Angabe des Modifikators `u`: ```php Strings::matchAll('žlutý kůň', '~\w+~'); @@ -532,9 +532,9 @@ Strings::matchAll('žlutý kůň', '~\w+~', utf8: true); ] */ ``` -Der Parameter `$offset` kann verwendet werden, um die Position anzugeben, an der die Suche beginnen soll (in Bytes; in Zeichen, wenn `$utf8` gesetzt ist). +Der Parameter `$offset` kann verwendet werden, um die Position anzugeben, ab der die Suche beginnen soll (in Bytes; wenn `$utf8` gesetzt ist, in Zeichen). -Wenn `$captureOffset` gleich `true` ist, wird für jede auftretende Übereinstimmung auch deren Position in der Zeichenkette zurückgegeben (in Bytes; in Zeichen, wenn `$utf8` eingestellt ist). Dadurch wird der Rückgabewert zu einem Array, in dem jedes Element ein Paar ist, das aus der übereinstimmenden Zeichenfolge und ihrer Position besteht: +Wenn `$captureOffset` `true` ist, wird für jede gefundene Übereinstimmung auch ihre Position im String zurückgegeben (in Bytes; wenn `$utf8` gesetzt ist, in Zeichen). Dies ändert den Rückgabewert in ein Array, bei dem jedes Element ein Paar aus dem übereinstimmenden String und seiner Position ist: ```php Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true); @@ -550,11 +550,21 @@ Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true, utf8: true); ] */ ``` +Wenn `$lazy` `true` ist, gibt die Funktion einen `Generator` anstelle eines Arrays zurück, was erhebliche Leistungsvorteile bei der Arbeit mit großen Strings bringt. Der Generator ermöglicht die schrittweise Suche nach Übereinstimmungen, anstatt den gesamten String auf einmal zu verarbeiten. Dies ermöglicht die effiziente Verarbeitung auch extrem großer Eingabetexte. Darüber hinaus können Sie die Verarbeitung jederzeit unterbrechen, wenn Sie die gesuchte Übereinstimmung finden, was Rechenzeit spart. + +```php +$matches = Strings::matchAll($largeText, '~\w+~', lazy: true); +foreach ($matches as $match) { + echo "Gefunden: $match[0]\n"; + // Die Verarbeitung kann jederzeit unterbrochen werden +} +``` + replace(string $subject, string|array $pattern, string|callable $replacement='', int $limit=-1, bool $captureOffset=false, bool $unmatchedAsNull=false, bool $utf8=false): string .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Ersetzt alle Vorkommen, die mit dem regulären Ausdruck übereinstimmen. `$replacement` ist entweder eine Ersetzungszeichenfolgenmaske oder ein Rückruf. +Ersetzt alle Vorkommen, die dem regulären Ausdruck entsprechen. `$replacement` ist entweder eine Maske für den Ersatzstring oder ein Callback. ```php Strings::replace('hello, world!', '~\w+~', '--'); @@ -564,7 +574,7 @@ Strings::replace('hello, world!', '~\w+~', fn($m) => strrev($m[0])); // 'olleh, dlrow!' ``` -Die Funktion erlaubt auch Mehrfachersetzungen, indem sie im zweiten Parameter ein Array der Form `pattern => replacement` übergibt: +Die Funktion ermöglicht auch die Durchführung mehrerer Ersetzungen, indem im zweiten Parameter ein Array im Format `pattern => replacement` übergeben wird: ```php Strings::replace('hello, world!', [ @@ -574,9 +584,9 @@ Strings::replace('hello, world!', [ // '-- --!' ``` -Der Parameter `$limit` begrenzt die Anzahl der Ersetzungen. Limit -1 bedeutet kein Limit. +Der Parameter `$limit` begrenzt die Anzahl der durchgeführten Ersetzungen. Ein Limit von -1 bedeutet keine Beschränkung. -Wenn `$utf8` gleich `true` ist, schaltet die Auswertung in den Unicode-Modus. Dies ist vergleichbar mit der Angabe des Modifikators "u". +Wenn `$utf8` `true` ist, wird die Auswertung in den Unicode-Modus umgeschaltet. Ähnlich wie bei der Angabe des Modifikators `u`. ```php Strings::replace('žlutý kůň', '~\w+~', '--'); @@ -586,7 +596,7 @@ Strings::replace('žlutý kůň', '~\w+~', '--', utf8: true); // '-- --' ``` -Wenn `$captureOffset` `true` ist, wird für jede auftretende Übereinstimmung auch die Position in der Zeichenkette (in Bytes; in Zeichen, wenn `$utf8` gesetzt ist) an den Callback übergeben. Dies ändert die Form des übergebenen Arrays, wobei jedes Element ein Paar ist, das aus der übereinstimmenden Zeichenkette und ihrer Position besteht. +Wenn `$captureOffset` `true` ist, wird für jede gefundene Übereinstimmung auch ihre Position im String an den Callback übergeben (in Bytes; wenn `$utf8` gesetzt ist, in Zeichen). Dies ändert die Form des übergebenen Arrays, bei dem jedes Element ein Paar aus dem übereinstimmenden String und seiner Position ist. ```php Strings::replace( @@ -595,7 +605,7 @@ Strings::replace( function (array $m) { dump($m); return ''; }, captureOffset: true, ); -// dumps [['lut', 2]] a [['k', 8]] +// dumps [['lut', 2]] und [['k', 8]] Strings::replace( 'žlutý kůň', @@ -604,10 +614,10 @@ Strings::replace( captureOffset: true, utf8: true, ); -// dumps [['žlutý', 0]] a [['kůň', 6]] +// dumps [['žlutý', 0]] und [['kůň', 6]] ``` -Wenn `$unmatchedAsNull` gleich `true` ist, werden nicht übereinstimmende Teilmuster als Null an den Callback übergeben; andernfalls werden sie als leere Zeichenkette oder nicht übergeben: +Wenn `$unmatchedAsNull` `true` ist, werden nicht erfasste Teilausdrücke als null an den Callback übergeben; andernfalls werden sie als leerer String übergeben oder nicht übergeben: ```php Strings::replace( diff --git a/utils/de/type.texy b/utils/de/type.texy index 9e9fa546a2..4e1ba2032e 100644 --- a/utils/de/type.texy +++ b/utils/de/type.texy @@ -1,8 +1,8 @@ -PHP-Typ +PHP Typ ******* .[perex] -[api:Nette\Utils\Type] ist eine PHP-Datentypklasse. +[api:Nette\Utils\Type] ist eine Klasse für die Arbeit mit PHP-Datentypen. Installation: @@ -11,7 +11,7 @@ Installation: composer require nette/utils ``` -Alle Beispiele gehen davon aus, dass der folgende Klassenalias definiert ist: +Alle Beispiele setzen voraus, dass ein Alias erstellt wurde: ```php use Nette\Utils\Type; @@ -21,7 +21,7 @@ use Nette\Utils\Type; fromReflection($reflection): ?Type .[method] -------------------------------------------- -Die statische Methode erzeugt ein Type-Objekt auf der Grundlage von Reflexion. Der Parameter kann ein `ReflectionMethod` oder `ReflectionFunction` Objekt sein (gibt den Typ des Rückgabewerts zurück) oder ein `ReflectionParameter` oder `ReflectionProperty` Objekt. Löst `self`, `static` und `parent` in den tatsächlichen Klassennamen auf. Wenn das Subjekt keinen Typ hat, wird `null` zurückgegeben. +Die statische Methode erstellt ein Type-Objekt basierend auf der Reflexion. Der Parameter kann ein `ReflectionMethod`- oder `ReflectionFunction`-Objekt sein (gibt den Rückgabetyp zurück) oder `ReflectionParameter` oder `ReflectionProperty`. Übersetzt `self`, `static` und `parent` in den tatsächlichen Klassennamen. Wenn das Subjekt keinen Typ hat, gibt es `null` zurück. ```php class DemoClass @@ -37,7 +37,7 @@ echo Type::fromReflection($prop); // 'DemoClass' fromString(string $type): Type .[method] ---------------------------------------- -Die statische Methode erstellt das Type-Objekt gemäß der Textnotation. +Die statische Methode erstellt ein Type-Objekt gemäß der Textnotation. ```php $type = Type::fromString('Foo|Bar'); @@ -48,10 +48,10 @@ echo $type; // 'Foo|Bar' getNames(): (string|array)[] .[method] -------------------------------------- -Gibt das Array der Subtypen, aus denen der zusammengesetzte Typ besteht, als Strings zurück. +Gibt ein Array der Subtypen zurück, aus denen sich der zusammengesetzte Typ zusammensetzt, als Strings. ```php -$type = Type::fromString('string|null'); // nebo '?string' +$type = Type::fromString('string|null'); // oder '?string' $type->getNames(); // ['string', 'null'] $type = Type::fromString('(Foo&Bar)|string'); @@ -62,7 +62,7 @@ $type->getNames(); // [['Foo', 'Bar'], 'string'] getTypes(): Type[] .[method] ---------------------------- -Gibt das Array der Subtypen, aus denen sich der zusammengesetzte Typ zusammensetzt, als `Type` Objekte zurück: +Gibt ein Array der Subtypen zurück, aus denen sich der zusammengesetzte Typ zusammensetzt, als `Type`-Objekte: ```php $type = Type::fromString('string|null'); // or '?string' @@ -79,7 +79,7 @@ $type->getTypes(); // [Type::fromString('Foo'), Type::fromString('Bar')] getSingleName(): ?string .[method] ---------------------------------- -Gibt den Typnamen für einfache Typen zurück, ansonsten null. +Bei einfachen Typen gibt es den Typnamen zurück, andernfalls null. ```php $type = Type::fromString('string|null'); @@ -99,14 +99,14 @@ echo $type->getSingleName(); // null isSimple(): bool .[method] -------------------------- -Gibt zurück, ob es ein einfacher Typ ist. Einfache nullbare Typen werden ebenfalls als einfache Typen betrachtet: +Gibt zurück, ob es sich um einen einfachen Typ handelt. Als einfache Typen gelten auch einfache nullable Typen: ```php $type = Type::fromString('string'); $type->isSimple(); // true $type->isUnion(); // false -$type = Type::fromString('?Foo'); // nebo 'Foo|null' +$type = Type::fromString('?Foo'); // oder 'Foo|null' $type->isSimple(); // true $type->isUnion(); // true ``` @@ -115,10 +115,10 @@ $type->isUnion(); // true isUnion(): bool .[method] ------------------------- -Gibt zurück, ob es ein Union-Typ ist. +Gibt zurück, ob es sich um einen Union-Typ handelt. ```php -$type = Type::fromString('Foo&Bar'); +$type = Type::fromString('string|int'); $type->isUnion(); // true ``` @@ -126,11 +126,11 @@ $type->isUnion(); // true isIntersection(): bool .[method] -------------------------------- -Gibt zurück, ob es sich um einen Schnittpunkt-Typ handelt. +Gibt zurück, ob es sich um einen Intersection-Typ handelt. ```php -$type = Type::fromString('string&int'); +$type = Type::fromString('Foo&Bar'); $type->isIntersection(); // true ``` @@ -138,7 +138,7 @@ $type->isIntersection(); // true isBuiltin(): bool .[method] --------------------------- -Gibt zurück, ob der Typ sowohl ein einfacher als auch ein eingebauter PHP-Typ ist. +Gibt zurück, ob der Typ einfach und gleichzeitig ein eingebauter PHP-Typ ist. ```php $type = Type::fromString('string'); @@ -155,7 +155,7 @@ $type->isBuiltin(); // false isClass(): bool .[method] ------------------------- -Gibt zurück, ob der Typ sowohl ein einfacher als auch ein Klassenname ist. +Gibt zurück, ob der Typ einfach und gleichzeitig ein Klassenname ist. ```php $type = Type::fromString('string'); @@ -172,7 +172,7 @@ $type->isClass(); // false isClassKeyword(): bool .[method] -------------------------------- -Ermittelt, ob der Typ einer der internen Typen `self`, `parent`, `static` ist. +Gibt zurück, ob der Typ einer der internen Typen `self`, `parent`, `static` ist. ```php $type = Type::fromString('self'); @@ -186,7 +186,7 @@ $type->isClassKeyword(); // false allows(string $type): bool .[method] ------------------------------------ -Die Methode `allows()` prüft die Typkompatibilität. So kann beispielsweise geprüft werden, ob ein Wert eines bestimmten Typs als Parameter übergeben werden kann. +Die Methode `allows()` überprüft die Typkompatibilität. Zum Beispiel ermöglicht sie festzustellen, ob ein Wert eines bestimmten Typs als Parameter übergeben werden könnte. ```php $type = Type::fromString('string|null'); diff --git a/utils/de/validators.texy b/utils/de/validators.texy index 7128ff2c8e..431782739b 100644 --- a/utils/de/validators.texy +++ b/utils/de/validators.texy @@ -1,8 +1,8 @@ -Wert-Validatoren -**************** +Wertvalidatoren +*************** .[perex] -Müssen Sie schnell und einfach überprüfen, ob eine Variable z.B. eine gültige Email-Adresse enthält? Dann ist [api:Nette\Utils\Validators] genau das Richtige, eine statische Klasse mit nützlichen Funktionen zur Überprüfung von Werten. +Müssen Sie schnell und einfach überprüfen, ob eine Variable beispielsweise eine gültige E-Mail-Adresse enthält? Dann wird Ihnen [api:Nette\Utils\Validators], eine statische Klasse mit nützlichen Funktionen zur Wertvalidierung, nützlich sein. Installation: @@ -11,17 +11,17 @@ Installation: composer require nette/utils ``` -Alle Beispiele gehen davon aus, dass der folgende Klassenalias definiert ist: +Alle Beispiele setzen voraus, dass ein Alias erstellt wurde: ```php use Nette\Utils\Validators; ``` -Grundlegende Verwendung .[#toc-basic-usage] -=========================================== +Grundlegende Verwendung +======================= -Die Klasse `Validators` verfügt über eine Reihe von Methoden zur Validierung von Werten, wie z.B. [isList() |#isList()], [isUnicode() |#isUnicode()], [isEmail() |#isEmail()], [isUrl() |#isUrl()], usw. zur Verwendung in Ihrem Code: +Die Klasse verfügt über eine Reihe von Methoden zur Überprüfung von Werten, wie z.B. [#isUnicode()], [#isEmail()], [#isUrl()] usw. zur Verwendung in Ihrem Code: ```php if (!Validators::isEmail($email)) { @@ -29,7 +29,7 @@ if (!Validators::isEmail($email)) { } ``` -Darüber hinaus kann überprüft werden, ob der Wert die so genannten [erwarteten Typen |#expected types] erfüllt, d. h. eine Zeichenkette, bei der die einzelnen Optionen durch einen vertikalen Balken `|` getrennt sind. Dies macht es einfach, Union-Typen mit [if() |#if()] zu überprüfen: +Weiterhin kann sie überprüfen, ob der Wert ein sogenannter [erwarteter Typ |#Erwartete Typen] ist, was ein String ist, bei dem die einzelnen Möglichkeiten durch einen senkrechten Strich `|` getrennt sind. Wir können so einfach mehrere Typen mit [#is()] überprüfen: ```php if (!Validators::is($val, 'int|string|bool')) { @@ -37,81 +37,81 @@ if (!Validators::is($val, 'int|string|bool')) { } ``` -Es gibt Ihnen aber auch die Möglichkeit, ein System zu erstellen, bei dem es notwendig ist, Erwartungen als Zeichenketten zu schreiben (z. B. in Anmerkungen oder in der Konfiguration) und dann anhand dieser zu überprüfen. +Aber es gibt uns auch die Möglichkeit, ein System zu erstellen, bei dem Erwartungen als Strings geschrieben werden müssen (zum Beispiel in Annotationen oder Konfigurationen) und dann die Werte entsprechend überprüft werden. -Sie können auch eine [Behauptung |#assert] deklarieren, die eine Ausnahme auslöst, wenn sie nicht erfüllt ist. +Auf erwartete Typen kann auch eine Anforderung [#assert()] gestellt werden, die, wenn sie nicht erfüllt ist, eine Ausnahme auslöst. -Erwartete Typen .[#toc-expected-types] -====================================== +Erwartete Typen +=============== -Ein erwarteter Typ ist eine Zeichenkette, die aus einer oder mehreren Varianten besteht, die durch einen senkrechten Strich getrennt sind `|`, similar to writing types in PHP (ie. `'int|string|bool')`. Die Notation mit Nullen ist ebenfalls zulässig `?int`. +Erwartete Typen bilden einen String, der aus einer oder mehreren Varianten besteht, die durch einen senkrechten Strich `|` getrennt sind, ähnlich wie Typen in PHP geschrieben werden (z.B. `'int|string|bool'`). Die nullable Notation `?int` wird ebenfalls akzeptiert. -Ein Array, bei dem alle Elemente von einem bestimmten Typ sind, wird in der Form `int[]` geschrieben. +Arrays, bei denen alle Elemente eines bestimmten Typs sind, werden im Format `int[]` geschrieben. -Einige Typen können von einem Doppelpunkt gefolgt werden und die Länge `:length` oder der Bereich `:[min]..[max]`folgen, z.B. `string:10` (ein String mit einer Länge von 10 Bytes), `float:10..` (Zahl 10 und größer), `array:..10` (Array mit bis zu zehn Elementen) oder `list:10..20` (Liste mit 10 bis 20 Elementen), oder ein regulärer Ausdruck für `pattern:[0-9]+`. +Hinter einigen Typen kann ein Doppelpunkt und eine Länge `:length` oder ein Bereich `:[min]..[max]` folgen, z.B. `string:10` (String der Länge 10 Bytes), `float:10..` (Zahl 10 und größer), `array:..10` (Array mit bis zu zehn Elementen) oder `list:10..20` (Liste mit 10 bis 20 Elementen), oder ein regulärer Ausdruck bei `pattern:[0-9]+`. -Übersicht über die Typen und Regeln: +Übersicht der Typen und Regeln: .[wide] -| PHP-Typen || +| PHP Typen || |-------------------------- -| `array` .{width: 140px} | Bereich für die Anzahl der Elemente kann angegeben werden -| `bool` | -| `float` | Bereich für den Wert kann angegeben werden -| `int` | Bereich für den Wert kann angegeben werden -| `null` | -| `object` | +| `array` .{width: 140px} | es kann ein Bereich für die Anzahl der Elemente angegeben werden +| `bool` | +| `float` | es kann ein Bereich für den Wert angegeben werden +| `int` | es kann ein Bereich für den Wert angegeben werden +| `null` | +| `object` | | `resource` | -| `scalar` | int\|float\|bool\|string -| `string` | Bereich für die Länge in Bytes kann angegeben werden +| `scalar` | int\|float\|bool\|string +| `string` | es kann ein Bereich für die Länge in Bytes angegeben werden | `callable` | | `iterable` | -| `mixed` | -|------------------------------------------------ -| pseudo-types || -|------------------------------------------------ -| `list` | [indiziertes Array |#isList], Bereich für die Anzahl der Elemente kann angegeben werden -| `none` | leerer Wert: `''`, `null`, `false` -| `number` | int\|Float -| `numeric` | [Zahl mit Textdarstellung |#isNumeric] -| `numericint`| [Ganzzahl mit Textdarstellung|#isNumericInt] -| `unicode` | [UTF-8 string |#isUnicode], Bereich für die Länge in Zeichen kann angegeben werden -|------------------------------------------------ -| Zeichenklasse (kann keine leere Zeichenkette sein) || +| `mixed` | +|-------------------------- +| Pseudo-Typen || |------------------------------------------------ -| `alnum` | alle Zeichen sind alphanumerisch -| `alpha` | alle Zeichen sind Buchstaben `[A-Za-z]` -| `digit` | alle Zeichen sind Ziffern -| `lower` | alle Zeichen sind Kleinbuchstaben `[a-z]` -| `space` | alle Zeichen sind Leerzeichen -| `upper` | alle Zeichen sind Großbuchstaben `[A-Z]` -| `xdigit` | alle Zeichen sind Hexadezimalziffern `[0-9A-Fa-f]` +| `list` | indexiertes Array, es kann ein Bereich für die Anzahl der Elemente angegeben werden +| `none` | leerer Wert: `''`, `null`, `false` +| `number` | int\|float +| `numeric` | [Zahl einschließlich Textdarstellung |#isNumeric] +| `numericint`| [Ganzzahl einschließlich Textdarstellung |#isNumericInt] +| `unicode` | [UTF-8-String |#isUnicode], es kann ein Bereich für die Länge in Zeichen angegeben werden +|-------------------------- +| Zeichenklasse (darf kein leerer String sein) || |------------------------------------------------ -| Syntaxprüfung || +| `alnum` | alle Zeichen sind alphanumerisch +| `alpha` | alle Zeichen sind Buchstaben `[A-Za-z]` +| `digit` | alle Zeichen sind Ziffern +| `lower` | alle Zeichen sind Kleinbuchstaben `[a-z]` +| `space` | alle Zeichen sind Leerzeichen +| `upper` | alle Zeichen sind Großbuchstaben `[A-Z]` +| `xdigit` | alle Zeichen sind hexadezimale Ziffern `[0-9A-Fa-f]` +|-------------------------- +| Syntaxüberprüfung || |------------------------------------------------ -| `pattern` | ein regulärer Ausdruck, dem die **gesamte** Zeichenkette entsprechen muss -| `email` | [E-Mail |#isEmail] +| `pattern` | regulärer Ausdruck, dem der **gesamte** String entsprechen muss +| `email` | [E-Mail |#isEmail] | `identifier`| [PHP-Bezeichner |#isPhpIdentifier] -| `url` | [URL |#isUrl] -| `uri` | [URI |#isUri] -|------------------------------------------------ -| Umgebungsvalidierung || +| `url` | [URL |#isUrl] +| `uri` | [URI |#isUri] +|-------------------------- +| Umgebungsüberprüfung || |------------------------------------------------ -| `class` | ist vorhandene Klasse -| `interface` | ist existierende Schnittstelle -| `directory` | ist ein vorhandenes Verzeichnis -| `file` | ist existierende Datei +| `class` | ist eine existierende Klasse +| `interface` | ist ein existierendes Interface +| `directory` | ist ein existierendes Verzeichnis +| `file` | ist eine existierende Datei -Behauptung .[#toc-assertion] -============================ +Assertion +========= assert($value, string $expected, string $label='variable'): void .[method] -------------------------------------------------------------------------- -Überprüft, ob der Wert den [erwarteten |#expected types], durch Pipe getrennten [Typen |#expected types] entspricht. Wenn nicht, wird die Ausnahme [api:Nette\Utils\AssertionException] ausgelöst. Das Wort `variable` in der Ausnahmemeldung kann durch den Parameter `$label` ersetzt werden. +Überprüft, ob der Wert einer der [erwarteten Typen |#Erwartete Typen] ist, die durch einen senkrechten Strich getrennt sind. Wenn nicht, wird eine Ausnahme [api:Nette\Utils\AssertionException] geworfen. Das Wort `variable` im Ausnahmetext kann durch den Parameter `$label` durch ein anderes ersetzt werden. ```php Validators::assert('Nette', 'string:5'); // OK @@ -120,10 +120,10 @@ Validators::assert('Lorem ipsum dolor sit', 'string:78'); ``` -assertField(array $array, string|int $key, string $expected=null, string $label=null): void .[method] ------------------------------------------------------------------------------------------------------ +assertField(array $array, string|int $key, ?string $expected=null, ?string $label=null): void .[method] +------------------------------------------------------------------------------------------------------- -Überprüft, ob das Element `$key` im Array `$array` die [erwarteten |#expected types], durch Pipe getrennten [Typen |#expected types] enthält. Wenn nicht, wird die Ausnahme [api:Nette\Utils\AssertionException] ausgelöst. Die Zeichenkette `item '%' in array` in der Ausnahmemeldung kann durch den Parameter `$label` ersetzt werden. +Überprüft, ob das Element unter dem Schlüssel `$key` im Array `$array` einer der [erwarteten Typen |#Erwartete Typen] ist, die durch einen senkrechten Strich getrennt sind. Wenn nicht, wird eine Ausnahme [api:Nette\Utils\AssertionException] geworfen. Der String `item '%' in array` im Ausnahmetext kann durch den Parameter `$label` durch einen anderen ersetzt werden. ```php $arr = ['foo' => 'Nette']; @@ -136,19 +136,19 @@ Validators::assertField($arr, 'foo', 'int'); ``` -Prüfer .[#toc-validators] -========================= +Validatoren +=========== is($value, string $expected): bool .[method] -------------------------------------------- -Prüft, ob der Wert die [erwarteten |#expected types], durch Pipe getrennten [Typen |#expected types] enthält. +Überprüft, ob der Wert einer der [erwarteten Typen |#Erwartete Typen] ist, die durch einen senkrechten Strich getrennt sind. ```php Validators::is(1, 'int|float'); // true Validators::is(23, 'int:0..10'); // false -Validators::is('Nette Framework', 'string:15'); // true, length is 15 bytes +Validators::is('Nette Framework', 'string:15'); // true, Länge ist 15 Bytes Validators::is('Nette Framework', 'string:8..'); // true Validators::is('Nette Framework', 'string:30..40'); // false ``` @@ -157,7 +157,7 @@ Validators::is('Nette Framework', 'string:30..40'); // false isEmail(mixed $value): bool .[method] ------------------------------------- -Überprüft, ob der Wert eine gültige E-Mail-Adresse ist. Es wird nicht überprüft, ob die Domäne tatsächlich existiert, nur die Syntax wird überprüft. Die Funktion rechnet auch mit zukünftigen [TLDs |https://en.wikipedia.org/wiki/Top-level_domain], die auch in Unicode sein können. +Überprüft, ob der Wert eine gültige E-Mail-Adresse ist. Es wird nicht überprüft, ob die Domain tatsächlich existiert, nur die Syntax wird überprüft. Die Funktion berücksichtigt auch zukünftige [TLDs|https://de.wikipedia.org/wiki/Top-Level-Domain], die auch in Unicode sein können. ```php Validators::isEmail('example@nette.org'); // true @@ -169,7 +169,7 @@ Validators::isEmail('nette'); // false isInRange(mixed $value, array $range): bool .[method] ----------------------------------------------------- -Prüft, ob der Wert innerhalb des angegebenen Bereichs liegt `[min, max]`liegt, wobei die obere oder untere Grenze weggelassen werden kann (`null`). Es können Zahlen, Strings und DateTime-Objekte verglichen werden. +Überprüft, ob der Wert im angegebenen Bereich `[min, max]` liegt, wobei die obere oder untere Grenze weggelassen werden kann (`null`). Es können Zahlen, Strings und DateTime-Objekte verglichen werden. Wenn beide Grenzen fehlen (`[null, null]`) oder der Wert `null` ist, wird `false` zurückgegeben. @@ -198,7 +198,7 @@ Validators::isNone('nette'); // false isNumeric(mixed $value): bool .[method] --------------------------------------- -Prüft, ob der Wert eine Zahl oder eine in eine Zeichenkette geschriebene Zahl ist. +Überprüft, ob der Wert eine Zahl oder eine in einem String geschriebene Zahl ist. ```php Validators::isNumeric(23); // true @@ -213,7 +213,7 @@ Validators::isNumeric('1e6'); // false isNumericInt(mixed $value): bool .[method] ------------------------------------------ -Prüft, ob der Wert eine Ganzzahl oder eine in eine Zeichenkette geschriebene Ganzzahl ist. +Überprüft, ob der Wert eine ganze Zahl oder eine in einem String geschriebene Zahl ist. ```php Validators::isNumericInt(23); // true @@ -227,7 +227,7 @@ Validators::isNumericInt('nette'); // false isPhpIdentifier(string $value): bool .[method] ---------------------------------------------- -Prüft, ob der Wert ein syntaktisch gültiger Bezeichner in PHP ist, zum Beispiel für Namen von Klassen, Methoden, Funktionen usw. +Überprüft, ob der Wert ein syntaktisch gültiger Bezeichner in PHP ist, zum Beispiel für Klassen-, Methoden-, Funktionsnamen usw. ```php Validators::isPhpIdentifier(''); // false @@ -240,7 +240,7 @@ Validators::isPhpIdentifier('one two'); // false isBuiltinType(string $type): bool .[method] ------------------------------------------- -Ermittelt, ob `$type` ein in PHP eingebauter Typ ist. Andernfalls ist es der Klassenname. +Stellt fest, ob `$type` ein eingebauter PHP-Typ ist. Andernfalls handelt es sich um einen Klassennamen. ```php Validators::isBuiltinType('string'); // true @@ -251,7 +251,7 @@ Validators::isBuiltinType('Foo'); // false isTypeDeclaration(string $type): bool .[method] ----------------------------------------------- -Prüft, ob die Typdeklaration syntaktisch korrekt ist. +Überprüft, ob die angegebene Typdeklaration syntaktisch gültig ist. ```php Validators::isTypeDeclaration('?string'); // true @@ -268,7 +268,7 @@ Validators::isTypeDeclaration('(A|B)'); // false isClassKeyword(string $type): bool .[method] -------------------------------------------- -Ermittelt, ob `$type` einer der internen Typen `self`, `parent`, `static` ist. +Stellt fest, ob `$type` einer der internen Typen `self`, `parent`, `static` ist. ```php Validators::isClassKeyword('self'); // true @@ -279,7 +279,7 @@ Validators::isClassKeyword('Foo'); // false isUnicode(mixed $value): bool .[method] --------------------------------------- -Überprüft, ob der Wert eine gültige UTF-8-Zeichenkette ist. +Überprüft, ob der Wert ein gültiger UTF-8-String ist. ```php Validators::isUnicode('nette'); // true @@ -291,7 +291,7 @@ Validators::isUnicode("\xA0"); // false isUrl(mixed $value): bool .[method] ----------------------------------- -Prüft, ob der Wert eine gültige URL-Adresse ist. +Überprüft, ob der Wert eine gültige URL-Adresse ist. ```php Validators::isUrl('https://nette.org:8080/path?query#fragment'); // true @@ -306,7 +306,7 @@ Validators::isUrl('nette.org'); // false isUri(string $value): bool .[method] ------------------------------------ -Überprüft, ob der Wert eine gültige URI-Adresse ist, d. h. tatsächlich eine Zeichenfolge, die mit einem syntaktisch gültigen Schema beginnt. +Überprüft, ob der Wert eine gültige URI-Adresse ist, also eigentlich ein String, der mit einem syntaktisch gültigen Schema beginnt. ```php Validators::isUri('https://nette.org'); // true diff --git a/utils/el/@home.texy b/utils/el/@home.texy index 049f6689d3..2625edc078 100644 --- a/utils/el/@home.texy +++ b/utils/el/@home.texy @@ -1,42 +1,46 @@ -Υπηρεσίες κοινής ωφέλειας -************************* +Nette Utils +*********** .[perex] Στο πακέτο `nette/utils` θα βρείτε ένα σύνολο χρήσιμων κλάσεων για καθημερινή χρήση: -| [Arrays |Arrays] | Nette\Utils\Arrays | [Callback |Callback] | Nette\Utils\Callback | [Ημερομηνία και ώρα |datetime] | Nette\Utils\DateTime -| [Σύστημα αρχείων |filesystem] | Nette\Utils\FileSystem | [Finder |Finder] | Nette\Utils\Finder -| [Floats |Floats] | Nette\Utils\Floats -| [Helpers |helpers] | Nette\Utils\Helpers -| [Στοιχεία HTML |HTML Elements] | Nette\Utils\Html -| [Images |Images] | Nette\Utils\Image -| [JSON |JSON] | Nette\Utils\Json -| [Μοντέλο αντικειμένων |smartobject] | Nette\SmartObject & Nette\StaticClass -| [Paginator |paginator] | Nette\Utils\Paginator -| [PHP Reflection |reflection] | Nette\Utils\Reflection -| [PHP Types |type] | Nette\Utils\Type -| [Τυχαίες συμβολοσειρές |random] | Nette\Utils\Random -| [Strings |Strings] | Nette\Utils\Strings -| [Validators |validators] | Nette\Utils\Validators +| [Στοιχεία HTML |html-elements] | Nette\Utils\Html +| [Iterators |iterables] | Nette\Utils\Iterables +| [JSON |json] | Nette\Utils\Json +| [Τυχαία strings |random] | Nette\Utils\Random +| [Εικόνες |images] | Nette\Utils\Image +| [PHP reflection |reflection] | Nette\Utils\Reflection +| [Τύποι PHP |type] | Nette\Utils\Type +| [Arrays |arrays] | Nette\Utils\Arrays +| [Βοηθητικές συναρτήσεις |helpers] | Nette\Utils\Helpers +| [Σύγκριση floats |floats] | Nette\Utils\Floats +| [Strings |strings] | Nette\Utils\Strings +| [Σύστημα αρχείων |filesystem] | Nette\Utils\FileSystem +| [Σελιδοποίηση |paginator] | Nette\Utils\Paginator +| [SmartObject |SmartObject] & [StaticClass |StaticClass] | Nette\SmartObject & Nette\StaticClass +| [Validator |validators] | Nette\Utils\Validators Εγκατάσταση ----------- -Κατεβάστε και εγκαταστήστε το πακέτο χρησιμοποιώντας το [Composer |best-practices:composer]: +Μπορείτε να κατεβάσετε και να εγκαταστήσετε τη βιβλιοθήκη χρησιμοποιώντας το [Composer|best-practices:composer]: ```shell composer require nette/utils ``` -| έκδοση | συμβατό με PHP +| έκδοση | συμβατή με PHP |-----------|------------------- -| Nette Utils 4.0 | PHP 8.0 - 8.2 -| Nette Utils 3.2 | PHP 7.2 - 8.2 -| Nette Utils 3.0 - 3.1 | PHP 7.1 - 8.0 -| Nette Utils 2.5 | PHP 5.6 - 8.0 +| Nette Utils 4.0 | PHP 8.0 – 8.4 +| Nette Utils 3.2 | PHP 7.2 – 8.3 +| Nette Utils 3.0 – 3.1 | PHP 7.1 – 8.0 +| Nette Utils 2.5 | PHP 5.6 – 8.0 + +Ισχύει για την τελευταία έκδοση patch. + -Ισχύει για τις τελευταίες εκδόσεις διορθώσεων. +Αν ενημερώνετε το πακέτο σε νεότερη έκδοση, δείτε τη σελίδα [upgrade|en:upgrading]. diff --git a/utils/el/@left-menu.texy b/utils/el/@left-menu.texy index c262d9deb2..5a8f9c90ae 100644 --- a/utils/el/@left-menu.texy +++ b/utils/el/@left-menu.texy @@ -1,26 +1,28 @@ -Πακέτο nette/utils -****************** -- [Συστοιχίες |Arrays] -- [Επανάκληση |Callback] +Nette Utils +*********** +- [Callbacks |callback] - [Ημερομηνία και ώρα |datetime] -- [Σύστημα αρχείων |filesystem] - [Finder |Finder] -- [Βοηθοί |helpers] -- [Στοιχεία HTML |HTML Elements] -- [Εικόνες |Images] +- [Floats |Floats] +- [Στοιχεία HTML |html-elements] +- [Iterators |iterables] - [JSON |JSON] -- [Σελιδοποιητής |paginator] -- [Τυχαίες συμβολοσειρές |random] -- [SmartObject |SmartObject] +- [Τυχαία strings |random] +- [Εικόνες |images] +- [Paginator |paginator] - [PHP Reflection |reflection] -- [Συμβολοσειρές |Strings] -- [Floats |Floats] - [Τύποι PHP |type] -- [Επικυρωτές |validators] +- [Arrays |arrays] +- [Βοηθητικές συναρτήσεις |helpers] +- [Strings |strings] +- [SmartObject |SmartObject] +- [StaticClass |StaticClass] +- [Σύστημα αρχείων |filesystem] +- [Validator |validators] -Άλλα βοηθητικά προγράμματα -************************** -- [NEON |neon:] -- [Κρυπτογράφηση κωδικού πρόσβασης |security:passwords] -- [Αναλυτής και κατασκευαστής URL |http:urls] +Άλλα εργαλεία +************* +- [NEON|neon:] +- [Κατακερματισμός κωδικών πρόσβασης |security:passwords] +- [Ανάλυση και σύνθεση URL |http:urls] diff --git a/utils/el/@meta.texy b/utils/el/@meta.texy new file mode 100644 index 0000000000..88e29852c7 --- /dev/null +++ b/utils/el/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Τεκμηρίωση}} diff --git a/utils/el/arrays.texy b/utils/el/arrays.texy index 29b06248e0..8fdc7e0860 100644 --- a/utils/el/arrays.texy +++ b/utils/el/arrays.texy @@ -1,8 +1,8 @@ -Λειτουργίες συστοιχίας -********************** +Εργασία με πίνακες +****************** .[perex] -Αυτή η σελίδα αφορά τις κλάσεις [Nette\Utils\Arrays |#Arrays], [ArrayHash |#ArrayHash] και [ArrayList |#ArrayList], οι οποίες σχετίζονται με τους πίνακες. +Αυτή η σελίδα είναι αφιερωμένη στις κλάσεις [Nette\Utils\Arrays |#Arrays], [#ArrayHash] και [#ArrayList], οι οποίες αφορούν πίνακες. Εγκατάσταση: @@ -12,22 +12,63 @@ composer require nette/utils ``` -Arrays .[#toc-arrays] -===================== +Arrays +====== -[api:Nette\Utils\Arrays] είναι μια στατική κλάση, η οποία περιέχει μια χούφτα από εύχρηστες συναρτήσεις συστοιχιών. +Η [api:Nette\Utils\Arrays] είναι μια στατική κλάση που περιέχει χρήσιμες συναρτήσεις για εργασία με πίνακες. Το αντίστοιχό της για επαναλήπτες είναι η [Nette\Utils\Iterables|iterables]. -Τα παραδείγματα που ακολουθούν υποθέτουν ότι έχει οριστεί το ακόλουθο ψευδώνυμο της κλάσης: +Τα ακόλουθα παραδείγματα προϋποθέτουν τη δημιουργία ενός ψευδώνυμου: ```php use Nette\Utils\Arrays; ``` +associate(array $array, mixed $path): array|\stdClass .[method] +--------------------------------------------------------------- + +Η συνάρτηση μετασχηματίζει ευέλικτα τον πίνακα `$array` σε συσχετιστικούς πίνακες ή αντικείμενα σύμφωνα με την καθορισμένη διαδρομή `$path`. Η διαδρομή μπορεί να είναι string ή πίνακας. Αποτελείται από τα ονόματα των κλειδιών του πίνακα εισόδου και τελεστές όπως '[]', '->', '=', και '|'. Ρίχνει μια `Nette\InvalidArgumentException` σε περίπτωση που η διαδρομή είναι άκυρη. + +```php +// μετατροπή σε συσχετιστικό πίνακα βάσει απλού κλειδιού +$arr = [ + ['name' => 'John', 'age' => 11], + ['name' => 'Mary', 'age' => null], + // ... +]; +$result = Arrays::associate($arr, 'name'); +// $result = ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// αντιστοίχιση τιμών από ένα κλειδί σε άλλο με τη χρήση του τελεστή = +$result = Arrays::associate($arr, 'name=age'); // ή ['name', '=', 'age'] +// $result = ['John' => 11, 'Mary' => null, ...] +``` + +```php +// δημιουργία αντικειμένου με τη χρήση του τελεστή -> +$result = Arrays::associate($arr, '->name'); // ή ['->', 'name'] +// $result = (object) ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// συνδυασμός κλειδιών με τη χρήση του τελεστή | +$result = Arrays::associate($arr, 'name|age'); // ή ['name', '|', 'age'] +// $result: ['John' => ['name' => 'John', 'age' => 11], 'Paul' => ['name' => 'Paul', 'age' => 44]] +``` + +```php +// προσθήκη στον πίνακα με τη χρήση [] +$result = Arrays::associate($arr, 'name[]'); // ή ['name', '[]'] +// $result: ['John' => [['name' => 'John', 'age' => 22], ['name' => 'John', 'age' => 11]]] +``` + + contains(array $array, $value): bool .[method] ---------------------------------------------- -Ελέγχει έναν πίνακα για την παρουσία τιμής. Χρησιμοποιεί αυστηρή σύγκριση (`===`) +Ελέγχει τον πίνακα για την παρουσία μιας τιμής. Χρησιμοποιεί αυστηρή σύγκριση (`===`). ```php Arrays::contains([1, 2, 3], 1); // true @@ -35,10 +76,10 @@ Arrays::contains(['1', false], 1); // false ``` -every(iterable $array, callable $callback): bool .[method] ----------------------------------------------------------- +every(array $array, callable $predicate): bool .[method] +-------------------------------------------------------- -Ελέγχει αν όλα τα στοιχεία του πίνακα περνούν τον έλεγχο που υλοποιείται από την παρεχόμενη συνάρτηση, η οποία έχει την υπογραφή `function ($value, $key, array $array): bool`. +Ελέγχει αν όλα τα στοιχεία στον πίνακα περνούν τη δοκιμή που υλοποιείται στο `$predicate` με την υπογραφή `function ($value, $key, array $array): bool`. ```php $array = [1, 30, 39, 29, 10, 13]; @@ -46,24 +87,59 @@ $isBelowThreshold = fn($value) => $value < 40; $res = Arrays::every($array, $isBelowThreshold); // true ``` -Βλέπε [some() |#some()]. +Βλέπε [#some()]. -first(array $array): mixed .[method] ------------------------------------- +filter(array $array, callable $predicate): array .[method]{data-version:4.0.4} +------------------------------------------------------------------------------ + +Επιστρέφει έναν νέο πίνακα που περιέχει όλα τα ζεύγη κλειδιού-τιμής που αντιστοιχούν στο καθορισμένο κατηγόρημα. Το callback έχει την υπογραφή `function ($value, int|string $key, array $array): bool`. + +```php +Arrays::filter( + ['a' => 1, 'b' => 2, 'c' => 3], + fn($v) => $v < 3, +); +// ['a' => 1, 'b' => 2] +``` + + +first(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------- + +Επιστρέφει το πρώτο στοιχείο (που αντιστοιχεί στο κατηγόρημα, αν έχει καθοριστεί). Αν δεν υπάρχει τέτοιο στοιχείο, επιστρέφει το αποτέλεσμα της κλήσης `$else` ή null. Η παράμετρος `$predicate` έχει την υπογραφή `function ($value, int|string $key, array $array): bool`. + +Δεν αλλάζει τον εσωτερικό δείκτη σε αντίθεση με το `reset()`. Οι παράμετροι `$predicate` και `$else` υπάρχουν από την έκδοση 4.0.4. + +```php +Arrays::first([1, 2, 3]); // 1 +Arrays::first([1, 2, 3], fn($v) => $v > 2); // 3 +Arrays::first([]); // null +Arrays::first([], else: fn() => false); // false +``` + +Βλέπε [#last()]. -Επιστρέφει το πρώτο στοιχείο από τον πίνακα ή null αν ο πίνακας είναι άδειος. Δεν αλλάζει τον εσωτερικό δείκτη σε αντίθεση με το `reset()`. + +firstKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +------------------------------------------------------------------------------------------------ + +Επιστρέφει το κλειδί του πρώτου στοιχείου (που αντιστοιχεί στο κατηγόρημα, αν αναφέρεται) ή null, αν δεν υπάρχει τέτοιο στοιχείο. Το κατηγόρημα `$predicate` έχει την υπογραφή `function ($value, int|string $key, array $array): bool`. ```php -Arrays::first([1, 2, 3]); // 1 -Arrays::first([]); // null +Arrays::firstKey([1, 2, 3]); // 0 +Arrays::firstKey([1, 2, 3], fn($v) => $v > 2); // 2 +Arrays::firstKey(['a' => 1, 'b' => 2]); // 'a' +Arrays::firstKey([]); // null ``` +Βλέπε [#lastKey()]. + flatten(array $array, bool $preserveKeys=false): array .[method] ---------------------------------------------------------------- -Μετατρέπει πολυδιάστατο πίνακα σε επίπεδο πίνακα. +Ενοποιεί έναν πολυεπίπεδο πίνακα σε έναν επίπεδο. ```php $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); @@ -71,20 +147,20 @@ $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); ``` -get(array $array, string|int|array $key, mixed $default=null): mixed .[method] ------------------------------------------------------------------------------- +get(array $array, string|int|array $key, ?mixed $default=null): mixed .[method] +------------------------------------------------------------------------------- -Επιστρέφει `$array[$key]` item. Εάν δεν υπάρχει, απορρίπτεται το `Nette\InvalidArgumentException`, εκτός εάν έχει οριστεί μια προεπιλεγμένη τιμή ως τρίτο όρισμα. +Επιστρέφει το στοιχείο `$array[$key]`. Αν δεν υπάρχει, ρίχνει είτε μια εξαίρεση `Nette\InvalidArgumentException`, ή αν καθορίζεται η τρίτη παράμετρος `$default`, επιστρέφει αυτήν. ```php -// αν $array['foo'] δεν υπάρχει, πετάει μια εξαίρεση +// αν το $array['foo'] δεν υπάρχει, ρίχνει μια εξαίρεση $value = Arrays::get($array, 'foo'); -// αν $array['foo'] δεν υπάρχει, επιστρέφει 'bar' +// αν το $array['foo'] δεν υπάρχει, επιστρέφει 'bar' $value = Arrays::get($array, 'foo', 'bar'); ``` -Το επιχείρημα `$key` μπορεί επίσης να είναι ένας πίνακας. +Το κλειδί `$key` μπορεί επίσης να είναι πίνακας. ```php $array = ['color' => ['favorite' => 'red'], 5]; @@ -97,36 +173,36 @@ $value = Arrays::get($array, ['color', 'favorite']); getRef(array &$array, string|int|array $key): mixed .[method] ------------------------------------------------------------- -Λαμβάνει αναφορά στο δεδομένο `$array[$key]`. Αν το ευρετήριο δεν υπάρχει, δημιουργείται νέο με τιμή `null`. +Λαμβάνει μια αναφορά στο καθορισμένο στοιχείο του πίνακα. Αν το στοιχείο δεν υπάρχει, θα δημιουργηθεί με τιμή null. ```php $valueRef = & Arrays::getRef($array, 'foo'); -// επιστρέφει την αναφορά $array['foo'] +// επιστρέφει μια αναφορά στο $array['foo'] ``` -Λειτουργεί με πολυδιάστατους πίνακες καθώς και με την [get() |#get()]. +Όπως και η συνάρτηση [#get()], μπορεί να δουλέψει με πολυδιάστατους πίνακες. ```php -$value = & Arrays::get($array, ['color', 'favorite']); -// επιστρέφει την αναφορά $array['color']['favorite'] +$value = & Arrays::getRef($array, ['color', 'favorite']); +// επιστρέφει μια αναφορά στο $array['color']['favorite'] ``` grep(array $array, string $pattern, bool $invert=false): array .[method] ------------------------------------------------------------------------ -Επιστρέφει μόνο εκείνα τα στοιχεία του πίνακα, τα οποία ταιριάζουν με μια κανονική έκφραση `$pattern`. Εάν `$invert` είναι `true`, επιστρέφει τα στοιχεία που δεν ταιριάζουν. Σφάλμα μεταγλώττισης Regex ή σφάλμα χρόνου εκτέλεσης εκπέμπει `Nette\RegexpException`. +Επιστρέφει μόνο αυτά τα στοιχεία του πίνακα των οποίων η τιμή ταιριάζει με την κανονική έκφραση `$pattern`. Αν το `$invert` είναι `true`, επιστρέφει αντίθετα τα στοιχεία που δεν ταιριάζουν. Σφάλμα κατά τη μεταγλώττιση ή την επεξεργασία της έκφρασης ρίχνει μια εξαίρεση `Nette\RegexpException`. ```php $filteredArray = Arrays::grep($array, '~^\d+$~'); -// επιστρέφει μόνο αριθμητικά στοιχεία +// επιστρέφει μόνο τα στοιχεία του πίνακα που αποτελούνται από ψηφία ``` insertAfter(array &$array, string|int|null $key, array $inserted): void .[method] --------------------------------------------------------------------------------- -Εισάγει τα περιεχόμενα του πίνακα `$inserted` στο `$array` αμέσως μετά το `$key`. Εάν το `$key` είναι `null` (ή δεν υπάρχει), εισάγεται στο τέλος. +Εισάγει το περιεχόμενο του πίνακα `$inserted` στον πίνακα `$array` αμέσως μετά το στοιχείο με το κλειδί `$key`. Αν το `$key` είναι `null` (ή δεν είναι στον πίνακα), εισάγεται στο τέλος. ```php $array = ['first' => 10, 'second' => 20]; @@ -138,7 +214,7 @@ Arrays::insertAfter($array, 'first', ['hello' => 'world']); insertBefore(array &$array, string|int|null $key, array $inserted): void .[method] ---------------------------------------------------------------------------------- -Εισάγει τα περιεχόμενα του πίνακα `$inserted` στο `$array` πριν από το `$key`. Εάν το `$key` είναι `null` (ή δεν υπάρχει), εισάγεται στην αρχή. +Εισάγει το περιεχόμενο του πίνακα `$inserted` στον πίνακα `$array` πριν από το στοιχείο με το κλειδί `$key`. Αν το `$key` είναι `null` (ή δεν είναι στον πίνακα), εισάγεται στην αρχή. ```php $array = ['first' => 10, 'second' => 20]; @@ -150,7 +226,7 @@ Arrays::insertBefore($array, 'first', ['hello' => 'world']); invoke(iterable $callbacks, ...$args): array .[method] ------------------------------------------------------ -Προκαλεί όλα τα callbacks και επιστρέφει πίνακα αποτελεσμάτων. +Καλεί όλα τα callbacks και επιστρέφει έναν πίνακα αποτελεσμάτων. ```php $callbacks = [ @@ -166,7 +242,7 @@ $array = Arrays::invoke($callbacks, 5, 11); invokeMethod(iterable $objects, string $method, ...$args): array .[method] -------------------------------------------------------------------------- -Καλεί τη μέθοδο σε κάθε αντικείμενο ενός πίνακα και επιστρέφει πίνακα αποτελεσμάτων. +Καλεί τη μέθοδο σε κάθε αντικείμενο στον πίνακα και επιστρέφει έναν πίνακα αποτελεσμάτων. ```php $objects = ['a' => $obj1, 'b' => $obj2]; @@ -179,7 +255,7 @@ $array = Arrays::invokeMethod($objects, 'foo', 1, 2); isList(array $array): bool .[method] ------------------------------------ -Ελέγχει αν ο πίνακας είναι δεικτοδοτημένος με αύξουσα σειρά αριθμητικών κλειδιών από το μηδέν, δηλαδή λίστα. +Επαληθεύει αν ο πίνακας είναι ευρετηριασμένος σύμφωνα με μια αύξουσα σειρά αριθμητικών κλειδιών από το μηδέν, γνωστός και ως λίστα. ```php Arrays::isList(['a', 'b', 'c']); // true @@ -188,21 +264,42 @@ Arrays::isList(['a' => 1, 'b' => 2]); // false ``` -last(array $array): mixed .[method] ------------------------------------ +last(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------ + +Επιστρέφει το τελευταίο στοιχείο (που αντιστοιχεί στο κατηγόρημα, αν έχει καθοριστεί). Αν δεν υπάρχει τέτοιο στοιχείο, επιστρέφει το αποτέλεσμα της κλήσης `$else` ή null. Η παράμετρος `$predicate` έχει την υπογραφή `function ($value, int|string $key, array $array): bool`. -Επιστρέφει το τελευταίο στοιχείο από τον πίνακα ή null αν ο πίνακας είναι άδειος. Δεν αλλάζει τον εσωτερικό δείκτη σε αντίθεση με το `end()`. +Δεν αλλάζει τον εσωτερικό δείκτη σε αντίθεση με το `end()`. Οι παράμετροι `$predicate` και `$else` υπάρχουν από την έκδοση 4.0.4. ```php -Arrays::last([1, 2, 3]); // 3 -Arrays::last([]); // null +Arrays::last([1, 2, 3]); // 3 +Arrays::last([1, 2, 3], fn($v) => $v < 3); // 2 +Arrays::last([]); // null +Arrays::last([], else: fn() => false); // false ``` +Βλέπε [#first()]. -map(iterable $array, callable $callback): array .[method] + +lastKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +----------------------------------------------------------------------------------------------- + +Επιστρέφει το κλειδί του τελευταίου στοιχείου (που αντιστοιχεί στο κατηγόρημα, αν αναφέρεται) ή null, αν δεν υπάρχει τέτοιο στοιχείο. Το κατηγόρημα `$predicate` έχει την υπογραφή `function ($value, int|string $key, array $array): bool`. + +```php +Arrays::lastKey([1, 2, 3]); // 2 +Arrays::lastKey([1, 2, 3], fn($v) => $v < 3); // 1 +Arrays::lastKey(['a' => 1, 'b' => 2]); // 'b' +Arrays::lastKey([]); // null +``` + +Βλέπε [#firstKey()]. + + +map(array $array, callable $transformer): array .[method] --------------------------------------------------------- -Καλεί το `$callback` σε όλα τα στοιχεία του πίνακα και επιστρέφει τον πίνακα των τιμών επιστροφής. Η ανάκληση έχει την υπογραφή `function ($value, $key, array $array): bool`. +Καλεί το `$transformer` σε όλα τα στοιχεία του πίνακα και επιστρέφει έναν πίνακα επιστρεφόμενων τιμών. Το callback έχει την υπογραφή `function ($value, $key, array $array): mixed`. ```php $array = ['foo', 'bar', 'baz']; @@ -211,10 +308,24 @@ $res = Arrays::map($array, fn($value) => $value . $value); ``` +mapWithKeys(array $array, callable $transformer): array .[method] +----------------------------------------------------------------- + +Δημιουργεί έναν νέο πίνακα μετασχηματίζοντας τις τιμές και τα κλειδιά του αρχικού πίνακα. Η συνάρτηση `$transformer` έχει την υπογραφή `function ($value, $key, array $array): ?array{$newKey, $newValue}`. Αν το `$transformer` επιστρέψει `null`, το στοιχείο παραλείπεται. Για τα διατηρημένα στοιχεία, το πρώτο στοιχείο του επιστρεφόμενου πίνακα χρησιμοποιείται ως νέο κλειδί και το δεύτερο στοιχείο ως νέα τιμή. + +```php +$array = ['a' => 1, 'b' => 2]; +$result = Arrays::mapWithKeys($array, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +Αυτή η μέθοδος είναι χρήσιμη σε καταστάσεις όπου χρειάζεται να αλλάξετε τη δομή του πίνακα (κλειδιά και τιμές ταυτόχρονα) ή να φιλτράρετε στοιχεία κατά τον μετασχηματισμό (επιστρέφοντας null για ανεπιθύμητα στοιχεία). + + mergeTree(array $array1, array $array2): array .[method] -------------------------------------------------------- -Συγχωνεύει αναδρομικά δύο πεδία. Είναι χρήσιμη, για παράδειγμα, για τη συγχώνευση δενδρικών δομών. Συμπεριφέρεται όπως ο τελεστής `+` για τον πίνακα, δηλαδή προσθέτει ένα ζεύγος κλειδιού/τιμής από τον δεύτερο πίνακα στον πρώτο και διατηρεί την τιμή από τον πρώτο πίνακα σε περίπτωση σύγκρουσης κλειδιών. +Συγχωνεύει αναδρομικά δύο πίνακες. Είναι χρήσιμο, για παράδειγμα, για τη συγχώνευση δενδρικών δομών. Κατά τη συγχώνευση, ακολουθεί τους ίδιους κανόνες με τον τελεστή `+` που εφαρμόζεται σε πίνακες, δηλαδή, προσθέτει ζεύγη κλειδιού/τιμής από τον δεύτερο πίνακα στον πρώτο πίνακα και σε περίπτωση σύγκρουσης κλειδιών, διατηρεί την τιμή από τον πρώτο πίνακα. ```php $array1 = ['color' => ['favorite' => 'red'], 5]; @@ -224,11 +335,11 @@ $array = Arrays::mergeTree($array1, $array2); // $array = ['color' => ['favorite' => 'red', 'blue'], 5]; ``` -Οι τιμές από τον δεύτερο πίνακα προστίθενται πάντα στον πρώτο. Η εξαφάνιση της τιμής `10` από τον δεύτερο πίνακα μπορεί να φαίνεται λίγο συγκεχυμένη. Θα πρέπει να σημειωθεί ότι αυτή η τιμή καθώς και η τιμή `5` in the first array have the same numeric key `0`, οπότε στο πεδίο που προκύπτει υπάρχει μόνο ένα στοιχείο από τον πρώτο πίνακα. +Οι τιμές από τον δεύτερο πίνακα προστίθενται πάντα στο τέλος του πρώτου. Η εξαφάνιση της τιμής `10` από τον δεύτερο πίνακα μπορεί να φαίνεται λίγο συγκεχυμένη. Πρέπει να συνειδητοποιήσετε ότι αυτή η τιμή, καθώς και η τιμή `5` στον πρώτο πίνακα, έχουν αντιστοιχισμένο το ίδιο αριθμητικό κλειδί `0`, επομένως, στον τελικό πίνακα υπάρχει μόνο το στοιχείο από τον πρώτο πίνακα. -normalize(array $array, string $filling=null): array .[method] --------------------------------------------------------------- +normalize(array $array, ?string $filling=null): array .[method] +--------------------------------------------------------------- Κανονικοποιεί τον πίνακα σε συσχετιστικό πίνακα. Αντικαθιστά τα αριθμητικά κλειδιά με τις τιμές τους, η νέα τιμή θα είναι `$filling`. @@ -243,14 +354,14 @@ $array = Arrays::normalize([1 => 'first', 'a' => 'second'], 'foobar'); ``` -pick(array &$array, string|int $key, mixed $default=null): mixed .[method] --------------------------------------------------------------------------- +pick(array &$array, string|int $key, ?mixed $default=null): mixed .[method] +--------------------------------------------------------------------------- -Επιστρέφει και αφαιρεί την τιμή ενός στοιχείου από έναν πίνακα. Εάν δεν υπάρχει, πετάει μια εξαίρεση ή επιστρέφει `$default`, εάν παρέχεται. +Επιστρέφει και αφαιρεί την τιμή του στοιχείου από τον πίνακα. Αν δεν υπάρχει, ρίχνει μια εξαίρεση, ή επιστρέφει την τιμή `$default`, αν έχει καθοριστεί. ```php -$array = [1 => 'foo', null => 'bar']; -$a = Arrays::pick($array, null); +$array = [1 => 'foo', 'x' => 'bar']; +$a = Arrays::pick($array, 'x'); // $a = 'bar' $b = Arrays::pick($array, 'not-exists', 'foobar'); // $b = 'foobar' @@ -262,7 +373,7 @@ $c = Arrays::pick($array, 'not-exists'); renameKey(array &$array, string|int $oldKey, string|int $newKey): bool .[method] -------------------------------------------------------------------------------- -Μετονομάζει ένα κλειδί. Επιστρέφει `true` εάν το κλειδί βρέθηκε στον πίνακα. +Μετονομάζει το κλειδί στον πίνακα. Επιστρέφει `true` αν το κλειδί βρέθηκε στον πίνακα. ```php $array = ['first' => 10, 'second' => 20]; @@ -274,7 +385,7 @@ Arrays::renameKey($array, 'first', 'renamed'); getKeyOffset(array $array, string|int $key): ?int .[method] ----------------------------------------------------------- -Επιστρέφει τη μηδενική θέση του κλειδιού του συγκεκριμένου πίνακα. Επιστρέφει `null` εάν το κλειδί δεν βρεθεί. +Επιστρέφει τη θέση του δεδομένου κλειδιού στον πίνακα. Η θέση αριθμείται από το 0. Σε περίπτωση που το κλειδί δεν βρεθεί, η συνάρτηση επιστρέφει `null`. ```php $array = ['first' => 10, 'second' => 20]; @@ -284,10 +395,10 @@ $position = Arrays::getKeyOffset($array, 'not-exists'); // επιστρέφει ``` -some(iterable $array, callable $callback): bool .[method] ---------------------------------------------------------- +some(array $array, callable $predicate): bool .[method] +------------------------------------------------------- -Ελέγχει αν τουλάχιστον ένα στοιχείο του πίνακα περνάει το τεστ που υλοποιείται από την παρεχόμενη κλήση με υπογραφή `function ($value, $key, array $array): bool`. +Ελέγχει αν τουλάχιστον ένα στοιχείο στον πίνακα περνάει τη δοκιμή που υλοποιείται στο `$predicate` με την υπογραφή `function ($value, $key, array $array): bool`. ```php $array = [1, 2, 3, 4]; @@ -295,13 +406,13 @@ $isEven = fn($value) => $value % 2 === 0; $res = Arrays::some($array, $isEven); // true ``` -Βλέπε [every() |#every()]. +Βλέπε [#every()]. toKey(mixed $key): string|int .[method] --------------------------------------- -Μετατρέπει μια τιμή σε κλειδί πίνακα, το οποίο είναι είτε ακέραιος αριθμός είτε συμβολοσειρά. +Μετατρέπει την τιμή σε κλειδί πίνακα, που είναι είτε ακέραιος είτε string. ```php Arrays::toKey('1'); // 1 @@ -312,19 +423,19 @@ Arrays::toKey('01'); // '01' toObject(iterable $array, object $object): object .[method] ----------------------------------------------------------- -Αντιγράφει τα στοιχεία του πίνακα `$array` στο αντικείμενο `$object` και στη συνέχεια το επιστρέφει. +Αντιγράφει τα στοιχεία του πίνακα `$array` στο αντικείμενο `$object`, το οποίο στη συνέχεια επιστρέφει. ```php $obj = new stdClass; $array = ['foo' => 1, 'bar' => 2]; -Arrays::toObject($array, $obj); // θέτει $obj->foo = 1; $obj->bar = 2; +Arrays::toObject($array, $obj); // ορίζει $obj->foo = 1; $obj->bar = 2; ``` -wrap(iterable $array, string $prefix='', string $suffix=''): array .[method] ----------------------------------------------------------------------------- +wrap(array $array, string $prefix='', string $suffix=''): array .[method] +------------------------------------------------------------------------- -Μετατρέπει κάθε στοιχείο του πίνακα σε συμβολοσειρά και το περικλείει με τα `$prefix` και `$suffix`. +Μετατρέπει κάθε στοιχείο στον πίνακα σε string και το περιβάλλει με πρόθεμα `$prefix` και επίθεμα `$suffix`. ```php $array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); @@ -332,21 +443,21 @@ $array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); ``` -ArrayHash .[#toc-arrayhash] -=========================== +ArrayHash +========= -Το αντικείμενο [api:Nette\Utils\ArrayHash] είναι ο απόγονος της γενικής κλάσης stdClass και την επεκτείνει με τη δυνατότητα να τη μεταχειρίζεται ως πίνακα, για παράδειγμα, προσπελαύνεται τα μέλη της με τη χρήση τετραγωνικών αγκυλών: +Το αντικείμενο [api:Nette\Utils\ArrayHash] είναι απόγονος της γενικής κλάσης stdClass και το επεκτείνει με τη δυνατότητα να το χειρίζεται σαν πίνακα, δηλαδή, για παράδειγμα, να έχει πρόσβαση στα μέλη μέσω αγκυλών: ```php $hash = new Nette\Utils\ArrayHash; $hash['foo'] = 123; -$hash->bar = 456; // λειτουργεί επίσης σημειογραφία αντικειμένου +$hash->bar = 456; // ταυτόχρονα, λειτουργεί και η αντικειμενοστρεφής γραφή $hash->foo; // 123 ``` -Μπορείτε να χρησιμοποιήσετε τη συνάρτηση `count($hash)` για να λάβετε τον αριθμό των στοιχείων. +Μπορείτε να χρησιμοποιήσετε τη συνάρτηση `count($hash)` για να βρείτε τον αριθμό των στοιχείων. -Μπορείτε να κάνετε επανάληψη σε ένα αντικείμενο όπως θα κάνατε σε έναν πίνακα, ακόμη και με μια αναφορά: +Μπορείτε να επαναλάβετε πάνω στο αντικείμενο όπως και στην περίπτωση ενός πίνακα, και μάλιστα με αναφορά: ```php foreach ($hash as $key => $value) { @@ -358,7 +469,7 @@ foreach ($hash as $key => &$value) { } ``` -Οι υπάρχοντες πίνακες μπορούν να μετατραπούν σε `ArrayHash` χρησιμοποιώντας το `from()`: +Μπορούμε να μετασχηματίσουμε έναν υπάρχοντα πίνακα σε `ArrayHash` με τη μέθοδο `from()`: ```php $array = ['foo' => 123, 'bar' => 456]; @@ -368,7 +479,7 @@ $hash->foo; // 123 $hash->bar; // 456 ``` -Ο μετασχηματισμός είναι αναδρομικός: +Η μετατροπή είναι αναδρομική: ```php $array = ['foo' => 123, 'inner' => ['a' => 'b']]; @@ -379,24 +490,24 @@ $hash->inner->a; // 'b' $hash['inner']['a']; // 'b' ``` -Μπορεί να αποφευχθεί με τη δεύτερη παράμετρο: +Αυτό μπορεί να αποφευχθεί με τη δεύτερη παράμετρο: ```php $hash = Nette\Utils\ArrayHash::from($array, false); -$hash->inner; // array +$hash->inner; // πίνακας ``` -Μετασχηματισμός πίσω στον πίνακα: +Μετασχηματισμός πίσω σε πίνακα: ```php $array = (array) $hash; ``` -ArrayList .[#toc-arraylist] -=========================== +ArrayList +========= -[api:Nette\Utils\ArrayList] αναπαριστά έναν γραμμικό πίνακα όπου οι δείκτες είναι μόνο ακέραιοι αριθμοί που αυξάνονται από το 0. +Η [api:Nette\Utils\ArrayList] αντιπροσωπεύει έναν γραμμικό πίνακα, όπου οι δείκτες είναι μόνο ακέραιοι αριθμοί αύξοντες από το 0. ```php $list = new Nette\Utils\ArrayList; @@ -407,9 +518,16 @@ $list[] = 'c'; count($list); // 3 ``` -Μπορείτε να χρησιμοποιήσετε τη συνάρτηση `count($list)` για να λάβετε τον αριθμό των στοιχείων. +Μπορούμε να μετασχηματίσουμε έναν υπάρχοντα πίνακα σε `ArrayList` με τη μέθοδο `from()`: -Μπορείτε να κάνετε επανάληψη σε ένα αντικείμενο όπως θα κάνατε σε έναν πίνακα, ακόμη και με μια αναφορά: +```php +$array = ['foo', 'bar']; +$list = Nette\Utils\ArrayList::from($array); +``` + +Μπορείτε να χρησιμοποιήσετε τη συνάρτηση `count($list)` για να βρείτε τον αριθμό των στοιχείων. + +Μπορείτε να επαναλάβετε πάνω στο αντικείμενο όπως και στην περίπτωση ενός πίνακα, και μάλιστα με αναφορά: ```php foreach ($list as $key => $value) { @@ -421,28 +539,21 @@ foreach ($list as $key => &$value) { } ``` -Οι υπάρχοντες πίνακες μπορούν να μετατραπούν σε `ArrayList` χρησιμοποιώντας το `from()`: - -```php -$array = ['foo', 'bar']; -$list = Nette\Utils\ArrayList::from($array); -``` - -Η πρόσβαση σε κλειδιά πέραν των επιτρεπόμενων τιμών προκαλεί εξαίρεση `Nette\OutOfRangeException`: +Η πρόσβαση σε κλειδιά εκτός των επιτρεπόμενων τιμών ρίχνει μια εξαίρεση `Nette\OutOfRangeException`: ```php -echo $list[-1]; // throws Nette\OutOfRangeException -unset($list[30]); // throws Nette\OutOfRangeException +echo $list[-1]; // ρίχνει Nette\OutOfRangeException +unset($list[30]); // ρίχνει Nette\OutOfRangeException ``` -Η αφαίρεση του κλειδιού θα έχει ως αποτέλεσμα την αναρίθμηση των στοιχείων: +Η αφαίρεση ενός κλειδιού προκαλεί επαναρίθμηση των στοιχείων: ```php unset($list[1]); // ArrayList(0 => 'a', 1 => 'c') ``` -Μπορείτε να προσθέσετε ένα νέο στοιχείο στην αρχή χρησιμοποιώντας το `prepend()`: +Ένα νέο στοιχείο μπορεί να προστεθεί στην αρχή με τη μέθοδο `prepend()`: ```php $list->prepend('d'); diff --git a/utils/el/callback.texy b/utils/el/callback.texy index 8f33d6da4a..65216aeb07 100644 --- a/utils/el/callback.texy +++ b/utils/el/callback.texy @@ -1,8 +1,8 @@ -Λειτουργίες επανάκλησης -*********************** +Εργασία με callbacks +******************** .[perex] -[api:Nette\Utils\Callback] είναι μια στατική κλάση, η οποία περιέχει συναρτήσεις για την εργασία με [callbacks της PHP |https://www.php.net/manual/en/language.types.callable.php]. +Η [api:Nette\Utils\Callback] είναι μια στατική κλάση με συναρτήσεις για εργασία με [PHP callbacks |https://www.php.net/manual/en/language.types.callable.php]. Εγκατάσταση: @@ -11,7 +11,7 @@ composer require nette/utils ``` -Όλα τα παραδείγματα προϋποθέτουν ότι έχει οριστεί το ακόλουθο ψευδώνυμο κλάσης: +Όλα τα παραδείγματα προϋποθέτουν τη δημιουργία ενός ψευδώνυμου: ```php use Nette\Utils\Callback; @@ -21,21 +21,21 @@ use Nette\Utils\Callback; check($callable, bool $syntax=false): callable .[method] -------------------------------------------------------- -Ελέγχει ότι το `$callable` είναι έγκυρη κλήση PHP. Διαφορετικά πετάει το `Nette\InvalidArgumentException`. Εάν το `$syntax` έχει οριστεί σε true, η συνάρτηση ελέγχει μόνο ότι το `$callable` έχει έγκυρη δομή που μπορεί να χρησιμοποιηθεί ως callback, αλλά δεν ελέγχει εάν η κλάση ή η μέθοδος υπάρχει πραγματικά. Επιστρέφει `$callable`. +Ελέγχει αν η μεταβλητή `$callable` είναι ένα έγκυρο callback. Αλλιώς ρίχνει `Nette\InvalidArgumentException`. Αν το `$syntax` είναι true, η συνάρτηση απλώς επαληθεύει ότι το `$callable` έχει τη δομή ενός callback, αλλά δεν επαληθεύει αν η δεδομένη κλάση ή μέθοδος υπάρχει πραγματικά. Επιστρέφει το `$callable`. ```php -Callback::check('trim'); // καμία εξαίρεση -Callback::check(['NonExistentClass', 'method']); // throws Nette\InvalidArgumentException -Callback::check(['NonExistentClass', 'method'], true); // καμία εξαίρεση -Callback::check(function () {}); // no exception -Callback::check(null); // throws Nette\InvalidArgumentException +Callback::check('trim'); // δεν ρίχνει εξαίρεση +Callback::check(['NonExistentClass', 'method']); // ρίχνει Nette\InvalidArgumentException +Callback::check(['NonExistentClass', 'method'], true); // δεν ρίχνει εξαίρεση +Callback::check(function () {}); // δεν ρίχνει εξαίρεση +Callback::check(null); // ρίχνει Nette\InvalidArgumentException ``` toString($callable): string .[method] ------------------------------------- -Μετατρέπει την κλήση PHP σε μορφή κειμένου. Κλάση ή μέθοδος μπορεί να μην υπάρχει. +Μετατρέπει το PHP callback σε μορφή κειμένου. Η κλάση ή η μέθοδος δεν χρειάζεται να υπάρχει. ```php Callback::toString('trim'); // 'trim' @@ -46,21 +46,21 @@ Callback::toString(['MyClass', 'method']); // 'MyClass::method' toReflection($callable): ReflectionMethod|ReflectionFunction .[method] ---------------------------------------------------------------------- -Επιστρέφει την αντανάκλαση για τη μέθοδο ή τη συνάρτηση που χρησιμοποιείται στο callback της PHP. +Επιστρέφει την αντανάκλαση για τη μέθοδο ή τη συνάρτηση στο PHP callback. ```php $ref = Callback::toReflection('trim'); -// $ref is ReflectionFunction('trim') +// $ref είναι ReflectionFunction('trim') $ref = Callback::toReflection(['MyClass', 'method']); -// $ref is ReflectionMethod('MyClass', 'method') +// $ref είναι ReflectionMethod('MyClass', 'method') ``` isStatic($callable): bool .[method] ----------------------------------- -Ελέγχει αν η ανάκληση PHP είναι συνάρτηση ή στατική μέθοδος. +Ελέγχει αν το PHP callback είναι συνάρτηση ή στατική μέθοδος. ```php Callback::isStatic('trim'); // true @@ -73,7 +73,7 @@ Callback::isStatic(function () {}); // false unwrap(Closure $closure): callable|array .[method] -------------------------------------------------- -Ξετυλίγει το κλείσιμο που δημιουργήθηκε από το `Closure::fromCallable`:https://www.php.net/manual/en/closure.fromcallable.php. +Αποσυμπιέζει αντίστροφα ένα Closure που δημιουργήθηκε με το `Closure::fromCallable`:https://www.php.net/manual/en/closure.fromcallable.php. ```php $closure = Closure::fromCallable(['MyClass', 'method']); diff --git a/utils/el/datetime.texy b/utils/el/datetime.texy index b024149658..e2681fd052 100644 --- a/utils/el/datetime.texy +++ b/utils/el/datetime.texy @@ -2,7 +2,7 @@ ****************** .[perex] -[api:Nette\Utils\DateTime] είναι μια κλάση που επεκτείνει το εγγενές [php:DateTime]. +Η [api:Nette\Utils\DateTime] είναι μια κλάση που επεκτείνει την εγγενή [php:DateTime] με πρόσθετες συναρτήσεις. Εγκατάσταση: @@ -11,7 +11,7 @@ composer require nette/utils ``` -Όλα τα παραδείγματα υποθέτουν ότι έχει οριστεί το ακόλουθο ψευδώνυμο κλάσης: +Όλα τα παραδείγματα προϋποθέτουν τη δημιουργία ενός ψευδώνυμου: ```php use Nette\Utils\DateTime; @@ -20,29 +20,29 @@ use Nette\Utils\DateTime; static from(string|int|\DateTimeInterface $time): DateTime .[method] -------------------------------------------------------------------- -Δημιουργεί ένα αντικείμενο DateTime από μια συμβολοσειρά, ένα timestamp UNIX ή ένα άλλο αντικείμενο [php:DateTimeInterface]. Πετάει ένα μήνυμα `Exception` εάν η ημερομηνία και η ώρα δεν είναι έγκυρες. +Δημιουργεί ένα αντικείμενο DateTime από ένα string, UNIX timestamp ή άλλο αντικείμενο [php:DateTimeInterface]. Ρίχνει μια εξαίρεση `Exception`, αν η ημερομηνία και η ώρα δεν είναι έγκυρες. ```php -DateTime::from(1138013640); // δημιουργεί ένα DateTime από το timestamp του UNIX με ένα προεπιλεγμένο timezamp -DateTime::from(42); // δημιουργεί ένα DateTime από την τρέχουσα ώρα συν 42 δευτερόλεπτα -DateTime::from('1994-02-26 04:15:32'); // δημιουργεί ένα DateTime με βάση μια συμβολοσειρά -DateTime::from('1994-02-26'); // create DateTime by date, time will be 00:00:00 +DateTime::from(1138013640); // δημιουργεί DateTime από UNIX timestamp με την προεπιλεγμένη ζώνη ώρας +DateTime::from(42); // δημιουργεί DateTime από την τρέχουσα ώρα συν 42 δευτερόλεπτα +DateTime::from('1994-02-26 04:15:32'); // δημιουργεί DateTime σύμφωνα με το string +DateTime::from('1994-02-26'); // δημιουργεί DateTime σύμφωνα με την ημερομηνία, η ώρα θα είναι 00:00:00 ``` static fromParts(int $year, int $month, int $day, int $hour=0, int $minute=0, float $second=0.0): DateTime .[method] -------------------------------------------------------------------------------------------------------------------- -Δημιουργεί το αντικείμενο DateTime ή πετάει μια εξαίρεση `Nette\InvalidArgumentException` εάν η ημερομηνία και η ώρα δεν είναι έγκυρες. +Δημιουργεί ένα αντικείμενο DateTime ή ρίχνει μια εξαίρεση `Nette\InvalidArgumentException`, αν η ημερομηνία και η ώρα δεν είναι έγκυρες. ```php DateTime::fromParts(1994, 2, 26, 4, 15, 32); ``` -static createFromFormat(string $format, string $time, string|\DateTimeZone $timezone=null): DateTime|false .[method] --------------------------------------------------------------------------------------------------------------------- -Επεκτείνει την [DateTime::createFromFormat() |https://www.php.net/manual/en/datetime.createfromformat.php] με τη δυνατότητα καθορισμού μιας ζώνης ώρας ως συμβολοσειρά. +static createFromFormat(string $format, string $time, ?string|\DateTimeZone $timezone=null): DateTime|false .[method] +--------------------------------------------------------------------------------------------------------------------- +Επεκτείνει το [DateTime::createFromFormat()|https://www.php.net/manual/en/datetime.createfromformat.php] με τη δυνατότητα καθορισμού της ζώνης ώρας ως string. ```php -DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); // δημιουργία με προσαρμοσμένη ζώνη ώρας +DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); ``` @@ -59,16 +59,16 @@ $clone->format('Y-m-d'); // '2017-02-04' __toString(): string .[method] ------------------------------ -Επιστρέφει την ημερομηνία και την ώρα σε μορφή `Y-m-d H:i:s`. +Επιστρέφει την ημερομηνία και την ώρα στη μορφή `Y-m-d H:i:s`. ```php echo $dateTime; // '2017-02-03 04:15:32' ``` -Υλοποιεί JsonSerializable .[#toc-implements-jsonserializable] -------------------------------------------------------------- -Επιστρέφει την ημερομηνία και την ώρα σε μορφή ISO 8601, η οποία χρησιμοποιείται, για παράδειγμα, στη JavaScript. +implementuje JsonSerializable +----------------------------- +Επιστρέφει την ημερομηνία και την ώρα σε μορφή ISO 8601, που χρησιμοποιείται, για παράδειγμα, στην JavaScript. ```php $date = DateTime::from('2017-02-03'); -echo json_encode($date); +echo json_encode($date); // '"2017-02-03T00:00:00+00:00"' ``` diff --git a/utils/el/filesystem.texy b/utils/el/filesystem.texy index 2c7b28f09c..77c9d82f7a 100644 --- a/utils/el/filesystem.texy +++ b/utils/el/filesystem.texy @@ -1,41 +1,43 @@ -Λειτουργίες συστήματος αρχείων -****************************** +Σύστημα αρχείων +*************** .[perex] -[api:Nette\Utils\FileSystem] είναι μια στατική κλάση, η οποία περιέχει χρήσιμες συναρτήσεις για την εργασία με ένα σύστημα αρχείων. Ένα πλεονέκτημα σε σχέση με τις εγγενείς συναρτήσεις της PHP είναι ότι πετάνε εξαιρέσεις σε περίπτωση σφαλμάτων. +Η κλάση [api:Nette\Utils\FileSystem] περιέχει χρήσιμες συναρτήσεις για την εργασία με το σύστημα αρχείων. Ένα από τα πλεονεκτήματα σε σχέση με τις εγγενείς συναρτήσεις της PHP είναι ότι προκαλούν εξαιρέσεις σε περίπτωση σφάλματος. +Αν χρειάζεστε να αναζητήσετε αρχεία στο δίσκο, χρησιμοποιήστε το [Finder | finder]. + Εγκατάσταση: ```shell composer require nette/utils ``` -Τα ακόλουθα παραδείγματα υποθέτουν ότι έχει οριστεί το ακόλουθο ψευδώνυμο κλάσης: +Τα παρακάτω παραδείγματα προϋποθέτουν τη δημιουργία ενός ψευδωνύμου: ```php use Nette\Utils\FileSystem; ``` -Manipulation .[#toc-manipulation] -================================= +Χειρισμός +========= copy(string $origin, string $target, bool $overwrite=true): void .[method] -------------------------------------------------------------------------- -Αντιγράφει ένα αρχείο ή έναν ολόκληρο κατάλογο. Αντικαθιστά τα υπάρχοντα αρχεία και καταλόγους από προεπιλογή. Εάν το `$overwrite` έχει οριστεί σε `false` και ένα `$target` υπάρχει ήδη, δημιουργεί μια εξαίρεση `Nette\InvalidStateException`. Πετάει μια εξαίρεση `Nette\IOException` σε περίπτωση σφάλματος. +Αντιγράφει ένα αρχείο ή έναν ολόκληρο κατάλογο. Από προεπιλογή, αντικαθιστά τα υπάρχοντα αρχεία και καταλόγους. Αν η παράμετρος `$overwrite` οριστεί σε `false`, προκαλεί μια εξαίρεση `Nette\InvalidStateException` εάν το αρχείο ή ο κατάλογος προορισμού `$target` υπάρχει. Σε περίπτωση σφάλματος, προκαλεί μια εξαίρεση `Nette\IOException`. ```php FileSystem::copy('/path/to/source', '/path/to/dest', overwrite: true); ``` -createDir(string $directory, int $mode=0777): void .[method] ------------------------------------------------------------- +createDir(string $dir, int $mode=0777): void .[method] +------------------------------------------------------ -Δημιουργεί έναν κατάλογο αν δεν υπάρχει, συμπεριλαμβανομένων των γονικών καταλόγων. Πετάει μια εξαίρεση `Nette\IOException` σε περίπτωση σφάλματος. +Δημιουργεί έναν κατάλογο εάν δεν υπάρχει, συμπεριλαμβανομένων των γονικών καταλόγων. Σε περίπτωση σφάλματος, προκαλεί μια εξαίρεση `Nette\IOException`. ```php FileSystem::createDir('/path/to/dir'); @@ -45,7 +47,7 @@ FileSystem::createDir('/path/to/dir'); delete(string $path): void .[method] ------------------------------------ -Διαγράφει ένα αρχείο ή έναν ολόκληρο κατάλογο αν υπάρχει. Εάν ο κατάλογος δεν είναι άδειος, διαγράφει πρώτα τα περιεχόμενά του. Πετάει μια εξαίρεση `Nette\IOException` σε περίπτωση σφάλματος. +Διαγράφει ένα αρχείο ή έναν ολόκληρο κατάλογο εάν υπάρχει. Εάν ο κατάλογος δεν είναι κενός, διαγράφει πρώτα το περιεχόμενό του. Σε περίπτωση σφάλματος, προκαλεί μια εξαίρεση `Nette\IOException`. ```php FileSystem::delete('/path/to/fileOrDir'); @@ -55,7 +57,7 @@ FileSystem::delete('/path/to/fileOrDir'); makeWritable(string $path, int $dirMode=0777, int $fileMode=0666): void .[method] --------------------------------------------------------------------------------- -Ορίζει δικαιώματα αρχείων στο `$fileMode` ή δικαιώματα καταλόγων στο `$dirMode`. Διατρέχει αναδρομικά και θέτει δικαιώματα και σε όλα τα περιεχόμενα του καταλόγου. +Ορίζει τα δικαιώματα του αρχείου σε `$fileMode` ή του καταλόγου σε `$dirMode`. Διασχίζει αναδρομικά και ορίζει δικαιώματα και για ολόκληρο το περιεχόμενο του καταλόγου. ```php FileSystem::makeWritable('/path/to/fileOrDir'); @@ -65,7 +67,7 @@ FileSystem::makeWritable('/path/to/fileOrDir'); open(string $path, string $mode): resource .[method] ---------------------------------------------------- -Ανοίγει το αρχείο και επιστρέφει τον πόρο. Η παράμετρος `$mode` λειτουργεί όπως η εγγενής συνάρτηση `fopen()`:https://www.php.net/manual/en/function.fopen.php. Εάν προκύψει σφάλμα, δημιουργεί την εξαίρεση `Nette\IOException`. +Ανοίγει ένα αρχείο και επιστρέφει έναν πόρο. Η παράμετρος `$mode` λειτουργεί όπως και η εγγενής συνάρτηση `fopen()`:https://www.php.net/manual/en/function.fopen.php. Σε περίπτωση σφάλματος, προκαλεί μια εξαίρεση `Nette\IOException`. ```php $res = FileSystem::open('/path/to/file', 'r'); @@ -75,7 +77,7 @@ $res = FileSystem::open('/path/to/file', 'r'); read(string $file): string .[method] ------------------------------------ -Διαβάζει το περιεχόμενο ενός `$file`. Εκπέμπει εξαίρεση `Nette\IOException` σε περίπτωση σφάλματος. +Επιστρέφει το περιεχόμενο του αρχείου `$file`. Σε περίπτωση σφάλματος, προκαλεί μια εξαίρεση `Nette\IOException`. ```php $content = FileSystem::read('/path/to/file'); @@ -85,14 +87,13 @@ $content = FileSystem::read('/path/to/file'); readLines(string $file, bool $stripNewLines=true): \Generator .[method] ----------------------------------------------------------------------- -Διαβάζει το περιεχόμενο του αρχείου γραμμή προς γραμμή. Σε αντίθεση με την εγγενή συνάρτηση `file()`, δεν διαβάζει ολόκληρο το αρχείο στη μνήμη, αλλά το διαβάζει συνεχώς, έτσι ώστε να μπορούν να διαβαστούν αρχεία μεγαλύτερα από τη διαθέσιμη μνήμη. Το `$stripNewLines` καθορίζει αν θα αφαιρεθούν οι χαρακτήρες αλλαγής γραμμής `\r` και `\n`. -Σε περίπτωση σφάλματος, δημιουργεί μια εξαίρεση `Nette\IOException`. +Διαβάζει το περιεχόμενο του αρχείου γραμμή προς γραμμή. Σε αντίθεση με την εγγενή συνάρτηση `file()`, δεν φορτώνει ολόκληρο το αρχείο στη μνήμη, αλλά το διαβάζει συνεχώς, οπότε είναι δυνατό να διαβαστούν και αρχεία μεγαλύτερα από τη διαθέσιμη μνήμη. Το `$stripNewLines` καθορίζει αν θα αφαιρεθούν οι χαρακτήρες τέλους γραμμής `\r` και `\n`. Σε περίπτωση σφάλματος, προκαλεί μια εξαίρεση `Nette\IOException`. ```php $lines = FileSystem::readLines('/path/to/file'); foreach ($lines as $lineNum => $line) { - echo "Line $lineNum: $line\n"; + echo "Line $lineNum: $line\n"; // Γραμμή $lineNum: $line\n } ``` @@ -100,7 +101,7 @@ foreach ($lines as $lineNum => $line) { rename(string $origin, string $target, bool $overwrite=true): void .[method] ---------------------------------------------------------------------------- -Μετονομάζει ή μετακινεί ένα αρχείο ή έναν κατάλογο που καθορίζεται από το `$origin` στο `$target`. Αντικαθιστά τα υπάρχοντα αρχεία και καταλόγους από προεπιλογή. Εάν το `$overwrite` έχει οριστεί σε `false` και το `$target` υπάρχει ήδη, προκαλεί εξαίρεση `Nette\InvalidStateException`. Πετάει εξαίρεση `Nette\IOException` σε περίπτωση σφάλματος. +Μετονομάζει ή μετακινεί το αρχείο ή τον κατάλογο `$origin`. Από προεπιλογή, αντικαθιστά τα υπάρχοντα αρχεία και καταλόγους. Αν η παράμετρος `$overwrite` οριστεί σε `false`, προκαλεί μια εξαίρεση `Nette\InvalidStateException` εάν το αρχείο ή ο κατάλογος προορισμού `$target` υπάρχει. Σε περίπτωση σφάλματος, προκαλεί μια εξαίρεση `Nette\IOException`. ```php FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); @@ -110,21 +111,21 @@ FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); write(string $file, string $content, int $mode=0666): void .[method] -------------------------------------------------------------------- -Γράφει το `$content` σε ένα `$file`. Πετάει μια εξαίρεση `Nette\IOException` σε περίπτωση σφάλματος. +Γράφει τη συμβολοσειρά `$content` στο αρχείο `$file`. Σε περίπτωση σφάλματος, προκαλεί μια εξαίρεση `Nette\IOException`. ```php FileSystem::write('/path/to/file', $content); ``` -Διαδρομές .[#toc-paths] -======================= +Διαδρομές +========= isAbsolute(string $path): bool .[method] ---------------------------------------- -Καθορίζει αν το `$path` είναι απόλυτο. +Ελέγχει αν η διαδρομή `$path` είναι απόλυτη. ```php FileSystem::isAbsolute('../backup'); // false @@ -146,7 +147,7 @@ FileSystem::joinPaths('/a/', '/../b'); // '/b' normalizePath(string $path): string .[method] --------------------------------------------- -Κανονικοποιεί τα `..` και `.` και τα διαχωριστικά καταλόγων στη διαδρομή. +Κανονικοποιεί τα `..` και `.` και τους διαχωριστές καταλόγων στη διαδρομή σύμφωνα με το σύστημα. ```php FileSystem::normalizePath('/file/.'); // '/file/' @@ -159,7 +160,7 @@ FileSystem::normalizePath('file/../../bar'); // '/../bar' unixSlashes(string $path): string .[method] ------------------------------------------- -Μετατρέπει τις κάθετες γραμμές σε `/` που χρησιμοποιούνται στα συστήματα Unix. +Μετατρέπει τις κάθετους σε `/` που χρησιμοποιούνται στα συστήματα Unix. ```php $path = FileSystem::unixSlashes($path); @@ -169,8 +170,45 @@ $path = FileSystem::unixSlashes($path); platformSlashes(string $path): string .[method] ----------------------------------------------- -Μετατρέπει τις κάθετες γραμμές σε χαρακτήρες συγκεκριμένους για την τρέχουσα πλατφόρμα, δηλ. `\` στα Windows και `/` αλλού. +Μετατρέπει τις κάθετους στους χαρακτήρες που είναι ειδικοί για την τρέχουσα πλατφόρμα, δηλαδή `\` στα Windows και `/` αλλού. ```php $path = FileSystem::platformSlashes($path); ``` + + +resolvePath(string $basePath, string $path): string .[method]{data-version:4.0.6} +--------------------------------------------------------------------------------- + +Συνάγει την τελική διαδρομή από τη διαδρομή `$path` σε σχέση με τον βασικό κατάλογο `$basePath`. Αφήνει τις απόλυτες διαδρομές (`/foo`, `C:/foo`) αμετάβλητες (απλώς κανονικοποιεί τις κάθετους) και συνδέει τις σχετικές διαδρομές στη βασική διαδρομή. + +```php +// Στα Windows, οι κάθετοι στην έξοδο θα ήταν αντίστροφες (\) +FileSystem::resolvePath('/base/dir', '/abs/path'); // '/abs/path' +FileSystem::resolvePath('/base/dir', 'rel'); // '/base/dir/rel' +FileSystem::resolvePath('base/dir', '../file.txt'); // 'base/file.txt' +FileSystem::resolvePath('base', ''); // 'base' +``` + + +Στατική έναντι μη στατικής πρόσβασης +==================================== + +Για να μπορείτε, για παράδειγμα για σκοπούς δοκιμών, να αντικαταστήσετε εύκολα την κλάση με μια άλλη (mock), χρησιμοποιήστε την μη στατικά: + +```php +class AnyClassUsingFileSystem +{ + public function __construct( + private FileSystem $fileSystem, + ) { + } + + public function readConfig(): string + { + return $this->fileSystem->read(/* ... */); + } + + ... +} +``` diff --git a/utils/el/finder.texy b/utils/el/finder.texy index fc541178a9..52d0052ebd 100644 --- a/utils/el/finder.texy +++ b/utils/el/finder.texy @@ -1,8 +1,8 @@ -Finder: Αναζήτηση αρχείων +Finder: αναζήτηση αρχείων ************************* .[perex] -Πρέπει να βρείτε αρχεία που ταιριάζουν με μια συγκεκριμένη μάσκα; Το Finder μπορεί να σας βοηθήσει. Είναι ένα ευέλικτο και γρήγορο εργαλείο για την περιήγηση στη δομή των καταλόγων. +Χρειάζεστε να βρείτε αρχεία που ταιριάζουν σε ένα συγκεκριμένο μοτίβο; Ο Finder θα σας βοηθήσει με αυτό. Είναι ένα ευέλικτο και γρήγορο εργαλείο για την περιήγηση στη δομή καταλόγων. Εγκατάσταση: @@ -11,17 +11,17 @@ Finder: Αναζήτηση αρχείων composer require nette/utils ``` -Τα παραδείγματα υποθέτουν ότι έχει δημιουργηθεί ένα ψευδώνυμο: +Τα παρακάτω παραδείγματα προϋποθέτουν τη δημιουργία ενός ψευδωνύμου: ```php use Nette\Utils\Finder; ``` -Χρήση του .[#toc-using] ------------------------ +Χρήση +----- -Αρχικά, ας δούμε πώς μπορείτε να χρησιμοποιήσετε το [api:Nette\Utils\Finder] για να εμφανίσετε τα ονόματα αρχείων με τις επεκτάσεις `.txt` και `.md` στον τρέχοντα κατάλογο: +Αρχικά, θα δείξουμε πώς μπορείτε να χρησιμοποιήσετε την [api:Nette\Utils\Finder] για να εμφανίσετε τα ονόματα των αρχείων με τις επεκτάσεις `.txt` και `.md` στον τρέχοντα κατάλογο: ```php foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { @@ -29,96 +29,97 @@ foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { } ``` -Ο προεπιλεγμένος κατάλογος για την αναζήτηση είναι ο τρέχων κατάλογος, αλλά μπορείτε να τον αλλάξετε χρησιμοποιώντας τις μεθόδους [in() ή from() |#Where to search?]. -Η μεταβλητή `$file` είναι μια περίπτωση της κλάσης [FileInfo |#FileInfo] με πολλές χρήσιμες μεθόδους. Το κλειδί `$name` περιέχει τη διαδρομή προς το αρχείο ως συμβολοσειρά. +Ο προεπιλεγμένος κατάλογος αναζήτησης είναι ο τρέχων κατάλογος, αλλά μπορείτε να τον αλλάξετε χρησιμοποιώντας τις μεθόδους [in() ή from() |#Πού να ψάξετε]. Η μεταβλητή `$file` είναι ένα στιγμιότυπο της κλάσης [#FileInfo] με πολλές χρήσιμες μεθόδους. Το κλειδί `$name` περιέχει τη διαδρομή προς το αρχείο ως συμβολοσειρά. -Τι να αναζητήσετε; .[#toc-what-to-search-for] ---------------------------------------------- +Τι να αναζητήσετε; +------------------ -Εκτός από τη μέθοδο `findFiles()`, υπάρχει επίσης η μέθοδος `findDirectories()`, η οποία αναζητά μόνο καταλόγους, και η μέθοδος `find()`, η οποία αναζητά και τα δύο. Αυτές οι μέθοδοι είναι στατικές, οπότε μπορούν να κληθούν χωρίς να δημιουργηθεί μια περίπτωση. Η παράμετρος mask είναι προαιρετική, αν δεν την καθορίσετε, αναζητούνται τα πάντα. +Εκτός από τη μέθοδο `findFiles()`, υπάρχει επίσης η `findDirectories()`, η οποία αναζητά μόνο καταλόγους, και η `find()`, η οποία αναζητά και τα δύο. Αυτές οι μέθοδοι είναι στατικές, οπότε μπορούν να κληθούν χωρίς τη δημιουργία στιγμιότυπου. Η παράμετρος με το μοτίβο είναι προαιρετική· αν δεν την καθορίσετε, θα αναζητηθούν τα πάντα. ```php foreach (Finder::find() as $file) { - echo $file; // τώρα εμφανίζονται όλα τα αρχεία και οι κατάλογοι + echo $file; // τώρα θα εκτυπωθούν όλα τα αρχεία και οι κατάλογοι } ``` -Χρησιμοποιήστε τις μεθόδους `files()` και `directories()` για να προσθέσετε τι άλλο θα αναζητήσετε. Οι μέθοδοι μπορούν να κληθούν επανειλημμένα και ένας πίνακας από μάσκες μπορεί να δοθεί ως παράμετρος: +Με τις μεθόδους `files()` και `directories()` μπορείτε να προσθέσετε τι άλλο να αναζητήσετε. Οι μέθοδοι μπορούν να κληθούν επανειλημμένα, και ως παράμετρος μπορεί να δοθεί και ένας πίνακας μοτίβων: ```php Finder::findDirectories('vendor') // όλοι οι κατάλογοι - ->files(['*.php', '*.phpt']); // καθώς και όλα τα αρχεία PHP + ->files(['*.php', '*.phpt']); // συν όλα τα αρχεία PHP ``` -Μια εναλλακτική λύση αντί των στατικών μεθόδων είναι να δημιουργήσετε μια περίπτωση χρησιμοποιώντας το `new Finder` (το φρέσκο αντικείμενο που δημιουργείται με αυτόν τον τρόπο δεν αναζητά τίποτα) και να καθορίσετε τι θα αναζητήσετε χρησιμοποιώντας τις `files()` και `directories()`: +Μια εναλλακτική στις στατικές μεθόδους είναι η δημιουργία ενός στιγμιότυπου με `new Finder` (ένα τέτοιο νεοδημιουργημένο αντικείμενο δεν αναζητά τίποτα) και ο καθορισμός του τι να αναζητήσετε χρησιμοποιώντας τις `files()` και `directories()`: ```php (new Finder) ->directories() // όλοι οι κατάλογοι - ->files('*.php'); // καθώς και όλα τα αρχεία PHP + ->files('*.php'); // συν όλα τα αρχεία PHP ``` -Μπορείτε να χρησιμοποιήσετε [μπαλαντέρ |#wildcards] `*`, `**`, `?` and `[...]` στη μάσκα. Μπορείτε ακόμη και να καθορίσετε σε καταλόγους, για παράδειγμα, το `src/*.php` θα αναζητήσει όλα τα αρχεία PHP στον κατάλογο `src`. +Στο μοτίβο μπορείτε να χρησιμοποιήσετε [#χαρακτήρες μπαλαντέρ] `*`, `**`, `?` και `[...]`. Μπορείτε ακόμη να καθορίσετε και καταλόγους, για παράδειγμα, το `src/*.php` αναζητά όλα τα αρχεία PHP στον κατάλογο `src`. +Οι συμβολικοί σύνδεσμοι θεωρούνται επίσης κατάλογοι ή αρχεία. -Πού να ψάξετε; .[#toc-where-to-search] --------------------------------------- -Ο προεπιλεγμένος κατάλογος αναζήτησης είναι ο τρέχων κατάλογος. Μπορείτε να το αλλάξετε αυτό χρησιμοποιώντας τις μεθόδους `in()` και `from()`. Όπως μπορείτε να δείτε από τα ονόματα των μεθόδων, η `in()` αναζητά μόνο τον τρέχοντα κατάλογο, ενώ η `from()` αναζητά και τους υποκαταλόγους του (αναδρομικά). Αν θέλετε να κάνετε αναδρομική αναζήτηση στον τρέχοντα κατάλογο, μπορείτε να χρησιμοποιήσετε τη μέθοδο `from('.')`. +Πού να ψάξετε; +-------------- -Αυτές οι μέθοδοι μπορούν να κληθούν πολλές φορές ή μπορείτε να τους περάσετε πολλαπλές διαδρομές ως πίνακες, τότε τα αρχεία θα αναζητηθούν σε όλους τους καταλόγους. Εάν ένας από τους καταλόγους δεν υπάρχει, θα εκπέμπεται ένα `Nette\UnexpectedValueException`. +Ο προεπιλεγμένος κατάλογος αναζήτησης είναι ο τρέχων κατάλογος. Μπορείτε να τον αλλάξετε χρησιμοποιώντας τις μεθόδους `in()` και `from()`. Όπως υποδηλώνουν τα ονόματα των μεθόδων, η `in()` αναζητά μόνο στον συγκεκριμένο κατάλογο, ενώ η `from()` αναζητά και στους υποκαταλόγους του (αναδρομικά). Αν θέλετε να αναζητήσετε αναδρομικά στον τρέχοντα κατάλογο, μπορείτε να χρησιμοποιήσετε το `from('.')`. + +Αυτές οι μέθοδοι μπορούν να κληθούν πολλές φορές ή να τους περάσετε πολλαπλές διαδρομές ως πίνακα· τα αρχεία θα αναζητηθούν τότε σε όλους τους καταλόγους. Αν κάποιος από τους καταλόγους δεν υπάρχει, θα προκληθεί μια εξαίρεση `Nette\UnexpectedValueException`. ```php Finder::findFiles('*.php') - ->in(['src', 'tests']) // αναζητά απευθείας στα src/ και tests/ - ->from('vendor'); // αναζητά επίσης στους υποκαταλόγους vendor/ + ->in(['src', 'tests']) // αναζητά απευθείας στο src/ και tests/ + ->from('vendor'); // αναζητά και στους υποκαταλόγους vendor/ ``` -Οι σχετικές διαδρομές είναι σχετικές με τον τρέχοντα κατάλογο. Φυσικά, μπορούν να καθοριστούν και απόλυτες διαδρομές: +Οι σχετικές διαδρομές είναι σχετικές με τον τρέχοντα κατάλογο. Φυσικά, μπορούν να δοθούν και απόλυτες διαδρομές: ```php Finder::findFiles('*.php') ->in('/var/www/html'); ``` -Wildcards [wildcards |#wildcards] `*`, `**`, `?` can be used in the path. For example, you can use the path `src/*/*.php` για την αναζήτηση όλων των αρχείων PHP στους καταλόγους δεύτερου επιπέδου στον κατάλογο `src`. Ο χαρακτήρας `**`, που ονομάζεται globstar, είναι ένα ισχυρό ατού γιατί σας επιτρέπει να αναζητήσετε και υποκαταλόγους: χρησιμοποιήστε το `src/**/tests/*.php` για να αναζητήσετε όλα τα αρχεία PHP στον κατάλογο `tests` που βρίσκονται στον κατάλογο `src` ή σε οποιονδήποτε από τους υποκαταλόγους του. +Στη διαδρομή είναι δυνατό να χρησιμοποιηθούν [#χαρακτήρες μπαλαντέρ] `*`, `**`, `?`. Για παράδειγμα, με τη διαδρομή `src/*/*.php` μπορείτε να αναζητήσετε όλα τα αρχεία PHP σε καταλόγους δεύτερου επιπέδου στον κατάλογο `src`. Ο χαρακτήρας `**`, γνωστός ως globstar, είναι ένα ισχυρό εργαλείο, καθώς επιτρέπει την αναζήτηση και σε υποκαταλόγους: με το `src/**/tests/*.php` αναζητάτε όλα τα αρχεία PHP στον κατάλογο `tests` που βρίσκεται στο `src` ή σε οποιονδήποτε υποκατάλογό του. -Από την άλλη πλευρά, οι μπαλαντέρ `[...]` χαρακτήρες δεν υποστηρίζονται στη διαδρομή, δηλαδή δεν έχουν ιδιαίτερη σημασία για να αποφύγετε ανεπιθύμητη συμπεριφορά σε περίπτωση που αναζητήσετε για παράδειγμα `in(__DIR__)` και τυχαία εμφανιστούν στη διαδρομή χαρακτήρες `[]`. +Αντίθετα, οι χαρακτήρες μπαλαντέρ `[...]` στη διαδρομή δεν υποστηρίζονται, δηλαδή δεν έχουν ειδική σημασία, για να αποφευχθεί ανεπιθύμητη συμπεριφορά σε περίπτωση που ψάχνετε, για παράδειγμα, `in(__DIR__)` και τυχαία στη διαδρομή υπάρχουν οι χαρακτήρες `[]`. -Κατά την αναζήτηση αρχείων και καταλόγων σε βάθος, επιστρέφεται πρώτα ο γονικός κατάλογος και στη συνέχεια τα αρχεία που περιέχονται σε αυτόν, κάτι που μπορεί να αντιστραφεί με το `childFirst()`. +Κατά την αναζήτηση αρχείων και καταλόγων σε βάθος, επιστρέφεται πρώτα ο γονικός κατάλογος και μετά τα αρχεία που περιέχονται σε αυτόν, κάτι που μπορεί να αντιστραφεί χρησιμοποιώντας το `childFirst()`. -Άγριοι χαρακτήρες .[#toc-wildcards] ------------------------------------ +Χαρακτήρες Μπαλαντέρ +-------------------- -Μπορείτε να χρησιμοποιήσετε διάφορους ειδικούς χαρακτήρες στη μάσκα: +Στο μοτίβο μπορείτε να χρησιμοποιήσετε αρκετούς ειδικούς χαρακτήρες: -- `*` - replaces any number of arbitrary characters (except `/`) -- `**` - αντικαθιστά οποιονδήποτε αριθμό αυθαίρετων χαρακτήρων, συμπεριλαμβανομένου του `/` (δηλ. μπορεί να γίνει αναζήτηση σε πολλαπλά επίπεδα) -- `?` - replaces one arbitrary character (except `/`) -- `[a-z]` - αντικαθιστά έναν χαρακτήρα από τη λίστα χαρακτήρων σε αγκύλες -- `[!a-z]` - αντικαθιστά έναν χαρακτήρα εκτός της λίστας χαρακτήρων σε αγκύλες +- `*` - αντικαθιστά οποιονδήποτε αριθμό οποιωνδήποτε χαρακτήρων (εκτός από `/`) +- `**` - αντικαθιστά οποιονδήποτε αριθμό οποιωνδήποτε χαρακτήρων συμπεριλαμβανομένου του `/` (δηλαδή, μπορεί να γίνει αναζήτηση σε πολλαπλά επίπεδα) +- `?` - αντικαθιστά έναν οποιονδήποτε χαρακτήρα (εκτός από `/`) +- `[a-z]` - αντικαθιστά έναν χαρακτήρα από τη λίστα χαρακτήρων στις αγκύλες +- `[!a-z]` - αντικαθιστά έναν χαρακτήρα εκτός της λίστας χαρακτήρων στις αγκύλες Παραδείγματα χρήσης: -- `img/?.png` - αρχεία με το όνομα ενός γράμματος `0.png`, `1.png`, `x.png`, κ.λπ. -- `logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log` - αρχεία καταγραφής με τη μορφή `YYYY-MM-DD` -- `src/**/tests/*` - αρχεία στον κατάλογο `src/tests`, `src/foo/tests`, `src/foo/bar/tests` κ.ο.κ. +- `img/?.png` - αρχεία με μονογράμματο όνομα `0.png`, `1.png`, `x.png`, κ.λπ. +- `logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log` - αρχεία καταγραφής σε μορφή `YYYY-MM-DD` +- `src/**/tests/*` - αρχεία στους καταλόγους `src/tests`, `src/foo/tests`, `src/foo/bar/tests` και ούτω καθεξής. - `docs/**.md` - όλα τα αρχεία με την επέκταση `.md` σε όλους τους υποκαταλόγους του καταλόγου `docs` -Εξαιρώντας το .[#toc-excluding] -------------------------------- +Εξαίρεση +-------- -Χρησιμοποιήστε τη μέθοδο `exclude()` για να αποκλείσετε αρχεία και καταλόγους από τις αναζητήσεις. Καθορίζετε μια μάσκα με την οποία το αρχείο δεν πρέπει να ταιριάζει. Παράδειγμα αναζήτησης αρχείων `*.txt` εκτός από εκείνα που περιέχουν το γράμμα `X` στο όνομα: +Με τη μέθοδο `exclude()` μπορείτε να εξαιρέσετε αρχεία και καταλόγους από την αναζήτηση. Καθορίζετε το μοτίβο με το οποίο το αρχείο δεν πρέπει να ταιριάζει. Παράδειγμα αναζήτησης αρχείων `*.txt` εκτός από αυτά που περιέχουν το γράμμα `X` στο όνομα: ```php Finder::findFiles('*.txt') ->exclude('*X*'); ``` -Χρησιμοποιήστε το `exclude()` για να παραλείψετε τους αναζητούμενους υποκαταλόγους: +Για να παραλείψετε τους διασχιζόμενους υποκαταλόγους, χρησιμοποιήστε το `exclude()`: ```php Finder::findFiles('*.php') @@ -127,12 +128,12 @@ Finder::findFiles('*.php') ``` -Φιλτράρισμα .[#toc-filtering] ------------------------------ +Φιλτράρισμα +----------- -Το Finder προσφέρει διάφορες μεθόδους για το φιλτράρισμα των αποτελεσμάτων (δηλαδή τη μείωσή τους). Μπορείτε να τις συνδυάσετε και να τις καλέσετε επανειλημμένα. +Ο Finder προσφέρει διάφορες μεθόδους για το φιλτράρισμα των αποτελεσμάτων (δηλαδή, τη μείωσή τους). Μπορείτε να τις συνδυάσετε και να τις καλέσετε επανειλημμένα. -Χρησιμοποιήστε το `size()` για να φιλτράρετε με βάση το μέγεθος του αρχείου. Με αυτόν τον τρόπο, βρίσκουμε αρχεία με μέγεθος μεταξύ 100 και 200 bytes: +Χρησιμοποιώντας το `size()`, φιλτράρουμε με βάση το μέγεθος του αρχείου. Έτσι βρίσκουμε αρχεία με μέγεθος μεταξύ 100 και 200 byte: ```php Finder::findFiles('*.php') @@ -140,7 +141,7 @@ Finder::findFiles('*.php') ->size('<=', 200); ``` -Η μέθοδος `date()` φιλτράρει με βάση την ημερομηνία τελευταίας τροποποίησης του αρχείου. Οι τιμές μπορούν να είναι απόλυτες ή σχετικές με την τρέχουσα ημερομηνία και ώρα, για παράδειγμα, με αυτόν τον τρόπο μπορείτε να βρείτε αρχεία που άλλαξαν τις τελευταίες δύο εβδομάδες: +Η μέθοδος `date()` φιλτράρει με βάση την ημερομηνία τελευταίας τροποποίησης του αρχείου. Οι τιμές μπορεί να είναι απόλυτες ή σχετικές με την τρέχουσα ημερομηνία και ώρα. Για παράδειγμα, έτσι βρίσκουμε αρχεία που τροποποιήθηκαν τις τελευταίες δύο εβδομάδες: ```php Finder::findFiles('*.php') @@ -150,9 +151,9 @@ Finder::findFiles('*.php') Και οι δύο συναρτήσεις κατανοούν τους τελεστές `>`, `>=`, `<`, `<=`, `=`, `!=`, `<>`. -Το Finder σας επιτρέπει επίσης να φιλτράρετε τα αποτελέσματα χρησιμοποιώντας προσαρμοσμένες συναρτήσεις. Η συνάρτηση λαμβάνει ένα αντικείμενο `Nette\Utils\FileInfo` ως παράμετρο και πρέπει να επιστρέψει το `true` για να συμπεριλάβει το αρχείο στα αποτελέσματα. +Ο Finder επιτρέπει επίσης το φιλτράρισμα των αποτελεσμάτων με προσαρμοσμένες συναρτήσεις. Η συνάρτηση λαμβάνει ως παράμετρο ένα αντικείμενο `Nette\Utils\FileInfo` και πρέπει να επιστρέψει `true` για να συμπεριληφθεί το αρχείο στα αποτελέσματα. -Παράδειγμα: Αναζήτηση για αρχεία PHP που περιέχουν τη συμβολοσειρά `Nette` (χωρίς να λαμβάνεται υπόψη η πεζότητα): +Παράδειγμα: αναζήτηση αρχείων PHP που περιέχουν τη συμβολοσειρά `Nette` (ανεξαρτήτως πεζών-κεφαλαίων): ```php Finder::findFiles('*.php') @@ -160,48 +161,48 @@ Finder::findFiles('*.php') ``` -Φιλτράρισμα βάθους .[#toc-depth-filtering] ------------------------------------------- +Φιλτράρισμα σε βάθος +-------------------- -Κατά την αναδρομική αναζήτηση, μπορείτε να ορίσετε το μέγιστο βάθος ανίχνευσης χρησιμοποιώντας τη μέθοδο `limitDepth()`. Αν ορίσετε `limitDepth(1)`, ανιχνεύονται μόνο οι πρώτοι υποκατάλογοι, `limitDepth(0)` απενεργοποιεί την ανίχνευση βάθους και η τιμή -1 ακυρώνει το όριο. +Κατά την αναδρομική αναζήτηση, μπορείτε να ορίσετε το μέγιστο βάθος περιήγησης χρησιμοποιώντας τη μέθοδο `limitDepth()`. Αν ορίσετε `limitDepth(1)`, περιηγούνται μόνο οι πρώτοι υποκατάλογοι, το `limitDepth(0)` απενεργοποιεί την περιήγηση σε βάθος, και η τιμή -1 ακυρώνει το όριο. -Το Finder σας επιτρέπει να χρησιμοποιήσετε τις δικές του λειτουργίες για να αποφασίσετε σε ποιον κατάλογο θα εισέλθετε κατά την περιήγηση. Η συνάρτηση λαμβάνει ένα αντικείμενο `Nette\Utils\FileInfo` ως παράμετρο και πρέπει να επιστρέψει το `true` για να εισέλθει στον κατάλογο: +Ο Finder επιτρέπει, χρησιμοποιώντας προσαρμοσμένες συναρτήσεις, να αποφασίσετε σε ποιον κατάλογο θα εισέλθετε κατά την περιήγηση. Η συνάρτηση λαμβάνει ως παράμετρο ένα αντικείμενο `Nette\Utils\FileInfo` και πρέπει να επιστρέψει `true` για να εισέλθετε στον κατάλογο: ```php Finder::findFiles('*.php') - ->descentFilter($file->getBasename() !== 'temp'); + ->descentFilter(fn($file) => $file->getBasename() !== 'temp'); ``` -Ταξινόμηση .[#toc-sorting] --------------------------- +Ταξινόμηση +---------- -Το Finder προσφέρει επίσης διάφορες λειτουργίες για την ταξινόμηση των αποτελεσμάτων. +Ο Finder προσφέρει επίσης αρκετές συναρτήσεις για την ταξινόμηση των αποτελεσμάτων. -Η μέθοδος `sortByName()` ταξινομεί τα αποτελέσματα με βάση το όνομα του αρχείου. Η ταξινόμηση είναι φυσική, δηλαδή χειρίζεται σωστά τους αριθμούς στα ονόματα και επιστρέφει π.χ. `foo1.txt` πριν από το `foo10.txt`. +Η μέθοδος `sortByName()` ταξινομεί τα αποτελέσματα με βάση τα ονόματα των αρχείων. Η ταξινόμηση είναι φυσική, δηλαδή χειρίζεται σωστά τους αριθμούς στα ονόματα και επιστρέφει, για παράδειγμα, το `foo1.txt` πριν από το `foo10.txt`. -Το Finder σας επιτρέπει επίσης να ταξινομήσετε χρησιμοποιώντας μια προσαρμοσμένη συνάρτηση. Λαμβάνει δύο αντικείμενα `Nette\Utils\FileInfo` ως παραμέτρους και πρέπει να επιστρέφει το αποτέλεσμα της σύγκρισης με τον τελεστή `<=>`, π.χ. `-1`, `0` nebo `1`. Για παράδειγμα, με αυτόν τον τρόπο ταξινομούμε τα αρχεία με βάση το μέγεθος: +Ο Finder επιτρέπει επίσης την ταξινόμηση με προσαρμοσμένη συνάρτηση. Αυτή λαμβάνει ως παράμετρο δύο αντικείμενα `Nette\Utils\FileInfo` και πρέπει να επιστρέψει το αποτέλεσμα της σύγκρισης με τον τελεστή `<=>`, δηλαδή `-1`, `0` ή `1`. Για παράδειγμα, έτσι ταξινομούμε τα αρχεία κατά μέγεθος: ```php $finder->sortBy(fn($a, $b) => $a->getSize() <=> $b->getSize()); ``` -Πολλαπλές διαφορετικές αναζητήσεις .[#toc-multiple-different-searches] ----------------------------------------------------------------------- +Πολλαπλές διαφορετικές αναζητήσεις +---------------------------------- -Εάν πρέπει να βρείτε πολλαπλά διαφορετικά αρχεία σε διαφορετικές τοποθεσίες ή που πληρούν διαφορετικά κριτήρια, χρησιμοποιήστε τη μέθοδο `append()`. Επιστρέφει ένα νέο αντικείμενο `Finder`, ώστε να μπορείτε να κάνετε αλυσιδωτές κλήσεις μεθόδων: +Αν χρειάζεστε να βρείτε περισσότερα διαφορετικά αρχεία σε διαφορετικές τοποθεσίες ή που πληρούν άλλα κριτήρια, χρησιμοποιήστε τη μέθοδο `append()`. Επιστρέφει ένα νέο αντικείμενο `Finder`, οπότε είναι δυνατή η αλυσιδωτή κλήση μεθόδων: ```php -($finder = new Finder) // αποθηκεύστε το πρώτο Finder στη μεταβλητή $finder! - ->files('*.php') // αναζήτηση για αρχεία *.php στο src/ +($finder = new Finder) // στη μεταβλητή $finder αποθηκεύουμε τον πρώτο Finder! + ->files('*.php') // στο src/ αναζητούμε αρχεία *.php ->from('src') ->append() - ->files('*.md') // στο docs/ αναζητήστε αρχεία *.md + ->files('*.md') // στο docs/ αναζητούμε αρχεία *.md ->from('docs') ->append() - ->files('*.json'); // στον τρέχοντα φάκελο αναζητήστε αρχεία *.json + ->files('*.json'); // στον τρέχοντα φάκελο αναζητούμε αρχεία *.json ``` Εναλλακτικά, μπορείτε να χρησιμοποιήσετε τη μέθοδο `append()` για να προσθέσετε ένα συγκεκριμένο αρχείο (ή έναν πίνακα αρχείων). Τότε επιστρέφει το ίδιο αντικείμενο `Finder`: @@ -212,12 +213,12 @@ $finder = Finder::findFiles('*.txt') ``` -FileInfo .[#toc-fileinfo] -------------------------- +FileInfo +-------- -Η [Nette\Utils\FileInfo |api:] είναι μια κλάση που αντιπροσωπεύει ένα αρχείο ή έναν κατάλογο στα αποτελέσματα αναζήτησης. Είναι μια επέκταση της κλάσης [SplFileInfo |php:SplFileInfo] που παρέχει πληροφορίες όπως το μέγεθος του αρχείου, την ημερομηνία τελευταίας τροποποίησης, το όνομα, τη διαδρομή κ.λπ. +Η [Nette\Utils\FileInfo |api:] είναι μια κλάση που αντιπροσωπεύει ένα αρχείο ή κατάλογο στα αποτελέσματα αναζήτησης. Είναι μια επέκταση της κλάσης [SplFileInfo |php:SplFileInfo], η οποία παρέχει πληροφορίες όπως το μέγεθος του αρχείου, η ημερομηνία τελευταίας τροποποίησης, το όνομα, η διαδρομή, κ.λπ. -Επιπλέον, παρέχει μεθόδους για την επιστροφή σχετικών διαδρομών, πράγμα που είναι χρήσιμο κατά την αναζήτηση σε βάθος: +Επιπλέον, παρέχει μεθόδους για την επιστροφή της σχετικής διαδρομής, που είναι χρήσιμο κατά την περιήγηση σε βάθος: ```php foreach (Finder::findFiles('*.jpg')->from('.') as $file) { @@ -226,7 +227,7 @@ foreach (Finder::findFiles('*.jpg')->from('.') as $file) { } ``` -Έχετε επίσης μεθόδους για την ανάγνωση και την εγγραφή των περιεχομένων ενός αρχείου: +Επιπλέον, έχετε διαθέσιμες μεθόδους για την ανάγνωση και την εγγραφή του περιεχομένου του αρχείου: ```php foreach ($finder as $file) { @@ -237,12 +238,12 @@ foreach ($finder as $file) { ``` -Επιστροφή αποτελεσμάτων ως συστοιχία .[#toc-returning-results-as-an-array] --------------------------------------------------------------------------- +Επιστροφή αποτελεσμάτων ως πίνακας +---------------------------------- -Όπως είδαμε στα παραδείγματα, το Finder υλοποιεί τη διεπαφή `IteratorAggregate`, οπότε μπορείτε να χρησιμοποιήσετε το `foreach` για να περιηγηθείτε στα αποτελέσματα. Είναι προγραμματισμένο έτσι ώστε τα αποτελέσματα να φορτώνονται μόνο κατά την περιήγησή σας, οπότε αν έχετε μεγάλο αριθμό αρχείων, δεν περιμένει να διαβαστούν όλα. +Όπως φάνηκε στα παραδείγματα, ο Finder υλοποιεί το interface `IteratorAggregate`, οπότε μπορείτε να χρησιμοποιήσετε το `foreach` για να διασχίσετε τα αποτελέσματα. Είναι προγραμματισμένος έτσι ώστε τα αποτελέσματα να φορτώνονται μόνο κατά τη διάρκεια της διάσχισης, οπότε αν έχετε μεγάλο αριθμό αρχείων, δεν περιμένει μέχρι να διαβαστούν όλα. -Μπορείτε επίσης να επιστρέψετε τα αποτελέσματα ως πίνακα αντικειμένων `Nette\Utils\FileInfo`, χρησιμοποιώντας τη μέθοδο `collect()`. Ο πίνακας δεν είναι συσχετιστικός, αλλά αριθμητικός. +Μπορείτε επίσης να λάβετε τα αποτελέσματα ως πίνακα αντικειμένων `Nette\Utils\FileInfo`, χρησιμοποιώντας τη μέθοδο `collect()`. Ο πίνακας δεν είναι συσχετιστικός, αλλά αριθμητικός. ```php $array = $finder->findFiles('*.php')->collect(); diff --git a/utils/el/floats.texy b/utils/el/floats.texy index 3125001c82..b0902508d4 100644 --- a/utils/el/floats.texy +++ b/utils/el/floats.texy @@ -1,8 +1,8 @@ -Λειτουργίες Floats -****************** +Εργασία με δεκαδικούς αριθμούς (floats) +*************************************** .[perex] -[api:Nette\Utils\Floats] είναι μια στατική κλάση με χρήσιμες συναρτήσεις για τη σύγκριση αριθμών float. +Η [api:Nette\Utils\Floats] είναι μια στατική κλάση με χρήσιμες συναρτήσεις για τη σύγκριση δεκαδικών αριθμών. Εγκατάσταση: @@ -11,18 +11,17 @@ composer require nette/utils ``` -Όλα τα παραδείγματα προϋποθέτουν ότι έχει οριστεί το ακόλουθο ψευδώνυμο κλάσης: +Όλα τα παρακάτω παραδείγματα προϋποθέτουν τη δημιουργία ενός ψευδωνύμου: ```php use Nette\Utils\Floats; ``` -Κίνητρο .[#toc-motivation] -========================== +Κίνητρο +======= -Αναρωτιέστε για ποιο λόγο υπάρχει μια κλάση σύγκρισης float; Μπορείτε να χρησιμοποιήσετε τους τελεστές `<`, `>`, `===`, νομίζετε. -Αυτό δεν είναι απολύτως αληθές. Τι νομίζετε ότι θα εκτυπώσει αυτός ο κώδικας; +Αναρωτιέστε, γιατί μια κλάση για τη σύγκριση δεκαδικών αριθμών; Αφού μπορώ να χρησιμοποιήσω τους τελεστές `<`, `>`, `===` και είμαι καλυμμένος. Δεν είναι απολύτως αλήθεια. Τι νομίζετε ότι θα εκτυπώσει ο παρακάτω κώδικας; ```php $a = 0.1 + 0.2; @@ -30,27 +29,30 @@ $b = 0.3; echo $a === $b ? 'same' : 'not same'; ``` -Αν εκτελέσετε τον κώδικα, κάποιοι από εσάς θα εκπλαγείτε από το γεγονός ότι το πρόγραμμα εκτύπωσε το `not same`. +Αν εκτελέσετε τον κώδικα, κάποιοι από εσάς σίγουρα θα εκπλαγείτε που το πρόγραμμα εκτύπωσε `not same`. -Οι μαθηματικές πράξεις με αριθμούς float προκαλούν σφάλματα λόγω της μετατροπής μεταξύ δεκαδικού και δυαδικού συστήματος. Για παράδειγμα, το `0.1 + 0.2` ισούται με το `0.300000000000000044…`. Επομένως, όταν συγκρίνουμε κινητές μονάδες, πρέπει να ανεχόμαστε μια μικρή διαφορά από ένα ορισμένο δεκαδικό ψηφίο. +Κατά τις μαθηματικές πράξεις με δεκαδικούς αριθμούς, προκύπτουν σφάλματα λόγω της μετατροπής μεταξύ του δεκαδικού και του δυαδικού συστήματος. Για παράδειγμα, το `0.1 + 0.2` δίνει `0.300000000000000044…`. Γι' αυτό, κατά τη σύγκριση, πρέπει να ανεχόμαστε μια μικρή διαφορά από ένα συγκεκριμένο δεκαδικό ψηφίο. -Και αυτό ακριβώς κάνει η κλάση `Floats`. Η ακόλουθη σύγκριση θα λειτουργήσει όπως αναμένεται: +Και αυτό ακριβώς κάνει η κλάση `Floats`. Η παρακάτω σύγκριση θα λειτουργήσει όπως αναμένεται: ```php -echo Floats::areEqual($a, $b) ? 'same': 'not same'; // ίδιο +echo Floats::areEqual($a, $b) ? 'same' : 'not same'; // same ``` -Όταν προσπαθεί να συγκρίνει το `NAN`, πετάει μια εξαίρεση `\LogicException`. +Μια προσπάθεια σύγκρισης του `NAN` προκαλεί μια εξαίρεση `\LogicException`. + +.[tip] +Η κλάση `Floats` ανέχεται διαφορές μικρότερες από `1e-10`. Αν χρειάζεστε να εργαστείτε με μεγαλύτερη ακρίβεια, χρησιμοποιήστε καλύτερα τη βιβλιοθήκη BCMath. -Σύγκριση float .[#toc-float-comparison] -======================================= +Σύγκριση δεκαδικών αριθμών +========================== areEqual(float $a, float $b): bool .[method] -------------------------------------------- -Επιστρέφει `true` εάν `$a` = `$b`. +Επιστρέφει `true` αν `$a` = `$b`. ```php Floats::areEqual(10, 10.0); // true @@ -60,7 +62,7 @@ Floats::areEqual(10, 10.0); // true isLessThan(float $a, float $b): bool .[method] ---------------------------------------------- -Επιστρέφει `true` εάν `$a` < `$b`. +Επιστρέφει `true` αν ισχύει `$a` < `$b`. ```php Floats::isLessThan(9.5, 10.2); // true @@ -71,7 +73,7 @@ Floats::isLessThan(INF, 10.2); // false isLessThanOrEqualTo(float $a, float $b): bool .[method] ------------------------------------------------------- -Επιστρέφει `true` εάν `$a` <= `$b`. +Επιστρέφει `true` αν ισχύει `$a` <= `$b`. ```php Floats::isLessThanOrEqualTo(9.5, 10.2); // true @@ -82,7 +84,7 @@ Floats::isLessThanOrEqualTo(10.25, 10.25); // true isGreaterThan(float $a, float $b): bool .[method] ------------------------------------------------- -Επιστρέφει `true` εάν `$a` > `$b`. +Επιστρέφει `true` αν ισχύει `$a` > `$b`. ```php Floats::isGreaterThan(9.5, -10.2); // true @@ -93,7 +95,7 @@ Floats::isGreaterThan(9.5, 10.2); // false isGreaterThanOrEqualTo(float $a, float $b): bool .[method] ---------------------------------------------------------- -Επιστρέφει `true` εάν `$a` >= `$b`. +Επιστρέφει `true` αν ισχύει `$a` >= `$b`. ```php Floats::isGreaterThanOrEqualTo(9.5, 10.2); // false @@ -104,25 +106,25 @@ Floats::isGreaterThanOrEqualTo(10.2, 10.2); // true compare(float $a, float $b): int .[method] ------------------------------------------ -Αν `$a` < `$b`, επιστρέφει `-1`, αν είναι ίσα επιστρέφει `0` and if `$a` > `$b` επιστρέφει `1`. +Αν `$a` < `$b`, επιστρέφει `-1`, αν είναι ίσα επιστρέφει `0`, και αν `$a` > `$b` επιστρέφει `1`. Μπορεί να χρησιμοποιηθεί, για παράδειγμα, με τη συνάρτηση `usort`. ```php $arr = [1, 5, 2, -3.5]; -usort($arr, [Float::class, 'compare']); -// $arr είναι [-3.5, 1, 2, 5] +usort($arr, [Floats::class, 'compare']); +// $arr είναι τώρα [-3.5, 1, 2, 5] ``` -Συναρτήσεις βοήθειας .[#toc-helpers-functions] -============================================== +Βοηθητικές συναρτήσεις +====================== isZero(float $value): bool .[method] ------------------------------------ -Επιστρέφει `true` αν η τιμή είναι μηδέν. +Επιστρέφει `true` αν η τιμή είναι ίση με μηδέν. ```php Floats::isZero(0.0); // true @@ -133,7 +135,7 @@ Floats::isZero(0); // true isInteger(float $value): bool .[method] --------------------------------------- -Επιστρέφει `true` εάν η τιμή είναι ακέραιος αριθμός. +Επιστρέφει `true` αν η τιμή είναι ακέραιος αριθμός. ```php Floats::isInteger(0); // true diff --git a/utils/el/helpers.texy b/utils/el/helpers.texy index 8270ff9bfd..627912c259 100644 --- a/utils/el/helpers.texy +++ b/utils/el/helpers.texy @@ -1,8 +1,8 @@ -Βοηθητικές λειτουργίες +Βοηθητικές συναρτήσεις ********************** .[perex] -[api:Nette\Utils\Helpers] είναι μια στατική κλάση με χρήσιμες λειτουργίες. +Η [api:Nette\Utils\Helpers] είναι μια στατική κλάση με χρήσιμες συναρτήσεις. Εγκατάσταση: @@ -11,7 +11,7 @@ composer require nette/utils ``` -Όλα τα παραδείγματα προϋποθέτουν ότι έχει οριστεί το ακόλουθο ψευδώνυμο κλάσης: +Όλα τα παρακάτω παραδείγματα προϋποθέτουν τη δημιουργία ενός ψευδωνύμου: ```php use Nette\Utils\Helpers; @@ -21,7 +21,7 @@ use Nette\Utils\Helpers; capture(callable $cb): string .[method] --------------------------------------- -Εκτελεί ένα callback και επιστρέφει την καταγεγραμμένη έξοδο ως συμβολοσειρά. +Εκτελεί την επανάκληση (callback) και επιστρέφει την συλλεχθείσα έξοδο ως συμβολοσειρά. ```php $res = Helpers::capture(function () use ($template) { @@ -33,7 +33,7 @@ $res = Helpers::capture(function () use ($template) { clamp(int|float $value, int|float $min, int|float $max): int|float .[method] ---------------------------------------------------------------------------- -Επιστρέφει την τιμή που έχει περιοριστεί στο εύρος των min και max. +Περιορίζει την τιμή εντός του δεδομένου εύρους min και max (συμπεριλαμβανομένων). ```php Helpers::clamp($level, 0, 255); @@ -43,8 +43,7 @@ Helpers::clamp($level, 0, 255); compare(mixed $left, string $operator, mixed $right): bool .[method] -------------------------------------------------------------------- -Συγκρίνει δύο τιμές με τον ίδιο τρόπο που το κάνει η PHP. Διακρίνει μεταξύ των τελεστών `>`, `>=`, `<`, `<=`, `=`, `==`, `===`, `!=`, `!==`, `<>`. -Η συνάρτηση είναι χρήσιμη σε περιπτώσεις όπου ο τελεστής είναι μεταβλητός. +Συγκρίνει δύο τιμές με τον ίδιο τρόπο που το κάνει η PHP. Διακρίνει τους τελεστές `>`, `>=`, `<`, `<=`, `=`, `==`, `===`, `!=`, `!==`, `<>`. Η συνάρτηση είναι χρήσιμη σε καταστάσεις όπου ο τελεστής είναι μεταβλητός. ```php Helpers::compare(10, '<', 20); // true @@ -65,7 +64,7 @@ Helpers::falseToNull(123); // 123 getLastError(): string .[method] -------------------------------- -Επιστρέφει το τελευταίο σφάλμα PHP ή μια κενή συμβολοσειρά αν δεν έχει συμβεί κανένα σφάλμα. Σε αντίθεση με το `error_get_last()`, δεν επηρεάζεται από την οδηγία PHP `html_errors` και επιστρέφει πάντα κείμενο, όχι HTML. +Επιστρέφει το τελευταίο σφάλμα στην PHP ή μια κενή συμβολοσειρά αν δεν προέκυψε κανένα σφάλμα. Σε αντίθεση με το `error_get_last()`, δεν επηρεάζεται από την οδηγία PHP `html_errors` και επιστρέφει πάντα κείμενο, όχι HTML. ```php Helpers::getLastError(); @@ -75,13 +74,13 @@ Helpers::getLastError(); getSuggestion(string[] $possibilities, string $value): ?string .[method] ------------------------------------------------------------------------ -Ψάχνει για ένα αλφαριθμητικό από το `$possibilities` που είναι πιο παρόμοιο με το `$value`, αλλά όχι το ίδιο. Υποστηρίζει μόνο κωδικοποιήσεις 8-bit. +Από τις προσφερόμενες επιλογές `$possibilities`, αναζητά τη συμβολοσειρά που είναι η πιο παρόμοια με την `$value`, αλλά όχι η ίδια. Υποστηρίζει μόνο κωδικοποίηση 8-bit. -Είναι χρήσιμο αν μια συγκεκριμένη επιλογή δεν είναι έγκυρη και θέλουμε να προτείνουμε στο χρήστη μια παρόμοια (αλλά διαφορετική, οπότε το ίδιο αλφαριθμητικό αγνοείται). Με αυτόν τον τρόπο, η Nette δημιουργεί τα μηνύματα `did you mean ...?`. +Είναι χρήσιμο σε περίπτωση που μια συγκεκριμένη επιλογή δεν είναι έγκυρη και θέλουμε να προτείνουμε στον χρήστη μια παρόμοια (αλλά διαφορετική, γι' αυτό αγνοείται η ίδια συμβολοσειρά). Με αυτόν τον τρόπο η Nette δημιουργεί τα μηνύματα "μήπως εννοούσατε ...?". ```php $items = ['foo', 'bar', 'baz']; Helpers::getSuggestion($items, 'fo'); // 'foo' Helpers::getSuggestion($items, 'barr'); // 'bar' -Helpers::getSuggestion($items, 'baz'); // 'bar', ne 'baz' +Helpers::getSuggestion($items, 'baz'); // 'bar', όχι 'baz' ``` diff --git a/utils/el/html-elements.texy b/utils/el/html-elements.texy index 289132e889..acddeb3a98 100644 --- a/utils/el/html-elements.texy +++ b/utils/el/html-elements.texy @@ -2,14 +2,14 @@ ************* .[perex] -Η κλάση [api:Nette\Utils\Html] είναι ένας βοηθός για τη δημιουργία κώδικα HTML που αποτρέπει την ευπάθεια Cross Site Scripting (XSS). +Η κλάση [api:Nette\Utils\Html] είναι ένας βοηθός για τη δημιουργία κώδικα HTML, ο οποίος αποτρέπει τη δημιουργία της ευπάθειας Cross Site Scripting (XSS). -Λειτουργεί με τέτοιο τρόπο ώστε τα αντικείμενά της αναπαριστούν στοιχεία HTML, ορίζουμε τις παραμέτρους τους και τα αφήνουμε να αποδώσουν: +Λειτουργεί έτσι ώστε τα αντικείμενά του να αντιπροσωπεύουν στοιχεία HTML, στα οποία ορίζουμε παραμέτρους και τα αφήνουμε να απεικονιστούν: ```php $el = Html::el('img'); // δημιουργεί το στοιχείο -$el->src = 'image.jpg'; // θέτει το χαρακτηριστικό src +$el->src = 'image.jpg'; // ορίζει το χαρακτηριστικό src echo $el; // εκτυπώνει '' ``` @@ -19,23 +19,23 @@ echo $el; // εκτυπώνει '' composer require nette/utils ``` -Όλα τα παραδείγματα προϋποθέτουν ότι έχει οριστεί το ακόλουθο ψευδώνυμο κλάσης: +Όλα τα παρακάτω παραδείγματα προϋποθέτουν τη δημιουργία ενός ψευδωνύμου: ```php use Nette\Utils\Html; ``` -Δημιουργία ενός στοιχείου HTML .[#toc-creating-an-html-element] -=============================================================== +Δημιουργία στοιχείου HTML +========================= -Το στοιχείο δημιουργείται με τη μέθοδο `Html::el()`: +Δημιουργούμε το στοιχείο με τη μέθοδο `Html::el()`: ```php $el = Html::el('img'); // δημιουργεί το στοιχείο ``` -Εκτός από το όνομα, μπορείτε να εισαγάγετε και άλλα χαρακτηριστικά στη σύνταξη HTML: +Εκτός από το όνομα, μπορείτε να καθορίσετε και άλλα χαρακτηριστικά στη σύνταξη HTML: ```php $el = Html::el('input type=text class="red important"'); @@ -50,39 +50,39 @@ $el = Html::el('input', [ ]); ``` -Για να αλλάξετε και να επιστρέψετε ένα όνομα στοιχείου: +Αλλαγή και επιστροφή του ονόματος του στοιχείου: ```php $el->setName('img'); $el->getName(); // 'img' -$el->isEmpty(); // true, καθώς το είναι άκυρο στοιχείο +$el->isEmpty(); // true, επειδή το είναι ένα κενό στοιχείο ``` -HTML Attributes .[#toc-html-attributes] -======================================= +Χαρακτηριστικά HTML +=================== -Μπορείτε να ορίσετε και να λάβετε μεμονωμένα χαρακτηριστικά HTML με τρεις τρόπους, εξαρτάται από εσάς ποιος σας αρέσει περισσότερο. Ο πρώτος είναι μέσω των ιδιοτήτων: +Μπορούμε να αλλάξουμε και να διαβάσουμε μεμονωμένα χαρακτηριστικά HTML με τρεις τρόπους, εξαρτάται από εσάς ποιος σας αρέσει περισσότερο. Ο πρώτος είναι μέσω ιδιοτήτων: ```php $el->src = 'image.jpg'; // ορίζει το χαρακτηριστικό src echo $el->src; // 'image.jpg' -unset($el->src); // αφαιρεί το χαρακτηριστικό -// ή $el->src = null, +unset($el->src); // ακυρώνει το χαρακτηριστικό +// ή $el->src = null; ``` -Ο δεύτερος τρόπος είναι η κλήση μεθόδων που, σε αντίθεση με τον ορισμό ιδιοτήτων, μπορούμε να συνδέσουμε αλυσιδωτά μεταξύ τους: +Ο δεύτερος τρόπος είναι η κλήση μεθόδων, τις οποίες, σε αντίθεση με τη ρύθμιση ιδιοτήτων, μπορούμε να αλυσιδώσουμε: ```php $el = Html::el('img')->src('image.jpg')->alt('photo'); // photo -$el->alt(null); // αφαιρεί το χαρακτηριστικό +$el->alt(null); // ακύρωση χαρακτηριστικού ``` -Και ο τρίτος τρόπος είναι ο πιο ομιλητικός: +Και ο τρίτος τρόπος είναι ο πιο αναλυτικός: ```php $el = Html::el('img') @@ -94,9 +94,9 @@ echo $el->getAttribute('src'); // 'image.jpg' $el->removeAttribute('alt'); ``` -Τα χαρακτηριστικά μπορούν να οριστούν μαζικά με το `addAttributes(array $attrs)` και να διαγραφούν με το `removeAttributes(array $attrNames)`. +Μαζικά, τα χαρακτηριστικά μπορούν να ρυθμιστούν χρησιμοποιώντας το `addAttributes(array $attrs)` και να αφαιρεθούν χρησιμοποιώντας το `removeAttributes(array $attrNames)`. -Η τιμή ενός χαρακτηριστικού δεν χρειάζεται να είναι μόνο μια συμβολοσειρά, μπορούν επίσης να χρησιμοποιηθούν λογικές τιμές για λογικά χαρακτηριστικά: +Η τιμή του χαρακτηριστικού δεν χρειάζεται να είναι μόνο συμβολοσειρά, μπορούν να χρησιμοποιηθούν και λογικές τιμές για λογικά χαρακτηριστικά: ```php $checkbox = Html::el('input')->type('checkbox'); @@ -104,7 +104,7 @@ $checkbox->checked = true; // $checkbox->checked = false; // ``` -Ένα attribute μπορεί επίσης να είναι ένας πίνακας από tokens, τα οποία παρατίθενται χωρισμένα με κενά διαστήματα, κάτι που είναι κατάλληλο για παράδειγμα για κλάσεις CSS: +Το χαρακτηριστικό μπορεί επίσης να είναι ένας πίνακας τιμών, οι οποίες εκτυπώνονται χωρισμένες με κενά, το οποίο είναι χρήσιμο, για παράδειγμα, για κλάσεις CSS: ```php $el = Html::el('input'); @@ -114,7 +114,7 @@ $el->class[] = 'top'; echo $el; // '' ``` -Μια εναλλακτική λύση είναι ένας συσχετιστικός πίνακας, όπου οι τιμές λένε αν το κλειδί πρέπει να αναγράφεται: +Μια εναλλακτική λύση είναι ένας συσχετιστικός πίνακας, όπου οι τιμές καθορίζουν αν το κλειδί πρέπει να εκτυπωθεί: ```php $el = Html::el('input'); @@ -132,7 +132,7 @@ $el->style['display'] = 'block'; echo $el; // '' ``` -αλλά το ίδιο μπορεί να γίνει και με τη χρήση των μεθόδων: +Τώρα χρησιμοποιήσαμε ιδιότητες, αλλά το ίδιο μπορεί να γραφτεί με μεθόδους: ```php $el = Html::el('input'); @@ -141,7 +141,7 @@ $el->style('display', 'block'); echo $el; // '' ``` -Ή ακόμη και με τον πιο φλύαρο τρόπο: +Ή ακόμα και με τον πιο αναλυτικό τρόπο: ```php $el = Html::el('input'); @@ -150,7 +150,7 @@ $el->appendAttribute('style', 'display', 'block'); echo $el; // '' ``` -Η μέθοδος `href()` μπορεί να διευκολύνει τη σύνθεση παραμέτρων ερωτήματος σε ένα URL: +Μια μικρή λεπτομέρεια στο τέλος: η μέθοδος `href()` μπορεί να διευκολύνει τη σύνθεση παραμέτρων query στο URL: ```php echo Html::el('a')->href('index.php', [ @@ -161,19 +161,19 @@ echo Html::el('a')->href('index.php', [ ``` -Χαρακτηριστικά δεδομένων .[#toc-data-attributes] ------------------------------------------------- +Χαρακτηριστικά δεδομένων (Data attributes) +------------------------------------------ Τα χαρακτηριστικά δεδομένων έχουν ειδική υποστήριξη. Επειδή τα ονόματά τους περιέχουν παύλες, η πρόσβαση μέσω ιδιοτήτων και μεθόδων δεν είναι τόσο κομψή, γι' αυτό υπάρχει η μέθοδος `data()`: ```php $el = Html::el('input'); -$el->{'data-max-size'} = '500x300'; // όχι τόσο κομψό +$el->{'data-max-size'} = '500x300'; // δεν είναι τόσο κομψό $el->data('max-size', '500x300'); // είναι κομψό echo $el; // '' ``` -Εάν η τιμή του χαρακτηριστικού δεδομένων είναι ένας πίνακας, αυτός σειριοποιείται αυτόματα σε JSON: +Αν η τιμή ενός χαρακτηριστικού δεδομένων είναι πίνακας, σειριοποιείται αυτόματα σε JSON: ```php $el = Html::el('input'); @@ -182,10 +182,10 @@ echo $el; // '' ``` -Περιεχόμενο στοιχείου .[#toc-element-content] -============================================= +Περιεχόμενο στοιχείου +===================== -Το εσωτερικό περιεχόμενο του στοιχείου ορίζεται από τις μεθόδους `setHtml()` ή `setText()`. Χρησιμοποιήστε την πρώτη μόνο αν γνωρίζετε ότι περνάτε αξιόπιστα ένα ασφαλές αλφαριθμητικό HTML στην παράμετρο. +Ορίζουμε το εσωτερικό περιεχόμενο του στοιχείου με τις μεθόδους `setHtml()` ή `setText()`. Χρησιμοποιήστε την πρώτη μόνο εάν γνωρίζετε ότι στην παράμετρο περνάτε μια αξιόπιστα ασφαλή συμβολοσειρά HTML. ```php echo Html::el('span')->setHtml('hello
                                                                                                                            '); @@ -195,7 +195,7 @@ echo Html::el('span')->setText('10 < 20'); // '10 < 20' ``` -Αντίθετα, το εσωτερικό περιεχόμενο λαμβάνεται από τις μεθόδους `getHtml()` ή `getText()`. Η δεύτερη αφαιρεί τις ετικέτες από την έξοδο HTML και μετατρέπει τις οντότητες HTML σε χαρακτήρες. +Και αντίστροφα, λαμβάνουμε το εσωτερικό περιεχόμενο με τις μεθόδους `getHtml()` ή `getText()`. Η δεύτερη αφαιρεί τις ετικέτες HTML από την έξοδο και μετατρέπει τις οντότητες HTML σε χαρακτήρες. ```php echo $el->getHtml(); // '10 < 20' @@ -203,10 +203,10 @@ echo $el->getText(); // '10 < 20' ``` -Παιδικοί κόμβοι .[#toc-child-nodes] ------------------------------------ +Θυγατρικοί κόμβοι +----------------- -Το εσωτερικό περιεχόμενο ενός στοιχείου μπορεί επίσης να είναι ένας πίνακας από παιδιά. Κάθε ένα από αυτά μπορεί να είναι είτε ένα αλφαριθμητικό είτε ένα άλλο στοιχείο `Html`. Εισάγονται χρησιμοποιώντας το `addHtml()` ή το `addText()`: +Το εσωτερικό του στοιχείου μπορεί επίσης να είναι ένας πίνακας θυγατρικών κόμβων (children). Κάθε ένας από αυτούς μπορεί να είναι είτε μια συμβολοσειρά είτε ένα άλλο στοιχείο `Html`. Τα εισάγουμε χρησιμοποιώντας `addHtml()` ή `addText()`: ```php $el = Html::el('span') @@ -216,16 +216,16 @@ $el = Html::el('span') // hello
                                                                                                                            10 < 20
                                                                                                                            ``` -Ένας άλλος τρόπος δημιουργίας και εισαγωγής ενός νέου κόμβου `Html`: +Ένας άλλος τρόπος για τη δημιουργία και την εισαγωγή ενός νέου κόμβου `Html`: ```php -$el = Html::el('ul') - ->create('li', ['class' => 'first']) - ->setText('hello'); -//
                                                                                                                            • hello
                                                                                                                            +$ul = Html::el('ul'); +$ul->create('li', ['class' => 'first']) + ->setText('πρώτο'); +//
                                                                                                                            • πρώτο
                                                                                                                            ``` -Μπορείτε να εργαστείτε με τους κόμβους σαν να ήταν στοιχεία συστοιχίας. Έτσι, έχετε πρόσβαση στα επιμέρους με τη χρήση αγκυλών, τα μετράτε με το `count()` και τα επαναλαμβάνετε: +Μπορείτε να εργαστείτε με τους κόμβους σαν να ήταν πίνακας. Δηλαδή, να έχετε πρόσβαση σε καθέναν από αυτούς χρησιμοποιώντας αγκύλες, να τους μετρήσετε χρησιμοποιώντας `count()` και να επαναλάβετε πάνω τους: ```php $el = Html::el('div'); @@ -238,20 +238,20 @@ foreach ($el as $child) { /* ... */ } echo count($el); // 2 ``` -Ένας νέος κόμβος μπορεί να εισαχθεί σε μια συγκεκριμένη θέση χρησιμοποιώντας το `insert(?int $index, $child, bool $replace = false)`. Αν `$replace = false`, εισάγει το στοιχείο στη θέση `$index` και μετακινεί τα υπόλοιπα. Εάν `$index = null`, θα προσαρτήσει ένα στοιχείο στο τέλος. +Ένας νέος κόμβος μπορεί να εισαχθεί σε μια συγκεκριμένη θέση χρησιμοποιώντας το `insert(?int $index, $child, bool $replace = false)`. Αν το `$replace = false`, εισάγει το στοιχείο στη θέση `$index` και μετακινεί τα υπόλοιπα. Αν το `$index = null`, προσθέτει το στοιχείο στο τέλος. ```php -// εισάγει το στοιχείο στην πρώτη θέση και προωθεί τα υπόλοιπα +// εισάγει το στοιχείο στην πρώτη θέση και μετακινεί τα υπόλοιπα $el->insert(0, Html::el('span')); ``` -Όλοι οι κόμβοι επιστρέφονται από τη μέθοδο `getChildren()` και αφαιρούνται από τη μέθοδο `removeChildren()`. +Λαμβάνουμε όλους τους κόμβους με τη μέθοδο `getChildren()` και τους αφαιρούμε με τη μέθοδο `removeChildren()`. -Δημιουργία ενός τμήματος εγγράφου .[#toc-creating-a-document-fragment] ----------------------------------------------------------------------- +Δημιουργία τμήματος εγγράφου (document fragment) +------------------------------------------------ -Αν θέλετε να εργαστείτε με έναν πίνακα κόμβων και δεν σας ενδιαφέρει το στοιχείο περιτύλιξης, μπορείτε να δημιουργήσετε ένα λεγόμενο *τεμάχιο εγγράφου* περνώντας το `null` αντί για το όνομα του στοιχείου: +Αν θέλουμε να εργαστούμε με έναν πίνακα κόμβων και δεν μας ενδιαφέρει το περιβάλλον στοιχείο, μπορούμε να δημιουργήσουμε ένα λεγόμενο *τμήμα εγγράφου* περνώντας `null` αντί για το όνομα του στοιχείου: ```php $el = Html::el(null) @@ -261,7 +261,7 @@ $el = Html::el(null) // hello
                                                                                                                            10 < 20
                                                                                                                            ``` -Οι μέθοδοι `fromHtml()` και `fromText()` προσφέρουν έναν ταχύτερο τρόπο δημιουργίας ενός θραύσματος: +Ένας γρηγορότερος τρόπος δημιουργίας ενός τμήματος προσφέρεται από τις μεθόδους `fromHtml()` και `fromText()`: ```php $el = Html::fromHtml('hello
                                                                                                                            '); @@ -272,15 +272,15 @@ echo $el; // '10 < 20' ``` -Δημιουργία εξόδου HTML .[#toc-generating-html-output] -===================================================== +Δημιουργία εξόδου HTML +====================== -Ο ευκολότερος τρόπος για να δημιουργήσετε ένα στοιχείο HTML είναι να χρησιμοποιήσετε το `echo` ή να εκτυπώσετε ένα αντικείμενο στο `(string)`. Μπορείτε επίσης να εκτυπώσετε ξεχωριστά τις ετικέτες ανοίγματος ή κλεισίματος και τα χαρακτηριστικά: +Ο απλούστερος τρόπος για να εκτυπώσετε ένα στοιχείο HTML είναι να χρησιμοποιήσετε το `echo` ή να μετατρέψετε τον τύπο του αντικειμένου σε `(string)`. Μπορείτε επίσης να εκτυπώσετε ξεχωριστά τις ετικέτες ανοίγματος ή κλεισίματος και τα χαρακτηριστικά: ```php $el = Html::el('div class=header')->setText('hello'); -echo $el; // '
                                                                                                                            ' +echo $el; // '
                                                                                                                            hello
                                                                                                                            ' $s = (string) $el; // '
                                                                                                                            hello
                                                                                                                            ' $s = $el->toHtml(); // '
                                                                                                                            hello
                                                                                                                            ' $s = $el->toText(); // 'hello' @@ -289,7 +289,7 @@ echo $el->endTag(); // '' echo $el->attributes(); // 'class="header"' ``` -Ένα σημαντικό χαρακτηριστικό είναι η αυτόματη προστασία από [Cross Site Scripting (XSS |nette:glossary#cross-site-scripting-xss]). Όλες οι τιμές των χαρακτηριστικών ή το περιεχόμενο που εισάγονται με τη χρήση των `setText()` ή `addText()` αποφεύγονται αξιόπιστα: +Ένα σημαντικό χαρακτηριστικό είναι η αυτόματη προστασία από [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS]. Όλες οι τιμές των χαρακτηριστικών ή το περιεχόμενο που εισάγεται μέσω `setText()` ή `addText()` γίνονται αξιόπιστα escape: ```php echo Html::el('div') @@ -300,17 +300,17 @@ echo Html::el('div') ``` -Μετατροπή HTML ↔ Κείμενο .[#toc-conversion-html-text] -===================================================== +Μετατροπή HTML ↔ κείμενο +======================== -Μπορείτε να χρησιμοποιήσετε τη στατική μέθοδο `htmlToText()` για να μετατρέψετε την HTML σε κείμενο: +Για τη μετατροπή HTML σε κείμενο, μπορείτε να χρησιμοποιήσετε τη στατική μέθοδο `htmlToText()`: ```php echo Html::htmlToText('One & Two'); // 'One & Two' ``` -HtmlStringable .[#toc-htmlstringable] -===================================== +HtmlStringable +============== -Το αντικείμενο `Nette\Utils\Html` υλοποιεί τη διεπαφή `Nette\HtmlStringable`, την οποία, για παράδειγμα, χρησιμοποιούν οι Latte ή οι φόρμες για να διακρίνουν τα αντικείμενα που έχουν μια μέθοδο `__toString()` που επιστρέφει κώδικα HTML. Έτσι, η διπλή διαφυγή δεν εμφανίζεται αν, για παράδειγμα, εκτυπώσουμε το αντικείμενο στο πρότυπο χρησιμοποιώντας το `{$el}`. +Το αντικείμενο `Nette\Utils\Html` υλοποιεί το interface `Nette\HtmlStringable`, με το οποίο, για παράδειγμα, το Latte ή οι φόρμες διακρίνουν αντικείμενα που έχουν μια μέθοδο `__toString()` η οποία επιστρέφει κώδικα HTML. Έτσι δεν θα υπάρξει διπλό escaping, αν, για παράδειγμα, εκτυπώσουμε το αντικείμενο σε ένα πρότυπο χρησιμοποιώντας `{$el}`. diff --git a/utils/el/images.texy b/utils/el/images.texy index 704d9f723c..8880b243e8 100644 --- a/utils/el/images.texy +++ b/utils/el/images.texy @@ -1,11 +1,11 @@ -Λειτουργίες εικόνας -******************* +Εργασία με εικόνες +****************** .[perex] -Η κλάση [api:Nette\Utils\Image] απλοποιεί την επεξεργασία εικόνων, όπως η αλλαγή μεγέθους, η περικοπή, η όξυνση, η σχεδίαση ή η συγχώνευση πολλαπλών εικόνων. +Η κλάση [api:Nette\Utils\Image] απλοποιεί τον χειρισμό εικόνων, όπως την αλλαγή μεγέθους, την περικοπή, την όξυνση, τη σχεδίαση ή τη συγχώνευση πολλαπλών εικόνων. -Η PHP διαθέτει ένα εκτεταμένο σύνολο λειτουργιών για την επεξεργασία εικόνων. Αλλά το API δεν είναι πολύ ωραίο. Δεν θα ήταν ένα Neat Framework να δημιουργήσει ένα σέξι API. +Η PHP διαθέτει ένα εκτεταμένο σύνολο συναρτήσεων για τον χειρισμό εικόνων. Ωστόσο, το API τους δεν είναι πολύ βολικό. Δεν θα ήταν το Nette Framework αν δεν παρουσίαζε ένα ελκυστικό API. Εγκατάσταση: @@ -13,26 +13,28 @@ composer require nette/utils ``` -Τα ακόλουθα παραδείγματα υποθέτουν ότι έχει οριστεί το ακόλουθο ψευδώνυμο κλάσης: +Όλα τα παρακάτω παραδείγματα προϋποθέτουν τη δημιουργία ενός ψευδωνύμου: ```php use Nette\Utils\Image; +use Nette\Utils\ImageColor; +use Nette\Utils\ImageType; ``` -Δημιουργία εικόνας .[#toc-creating-an-image] -============================================ +Δημιουργία εικόνας +================== -Θα δημιουργήσουμε μια νέα έγχρωμη εικόνα, για παράδειγμα με διαστάσεις 100×200: +Δημιουργούμε μια νέα εικόνα true color, για παράδειγμα με διαστάσεις 100×200: ```php $image = Image::fromBlank(100, 200); ``` -Προαιρετικά, μπορείτε να καθορίσετε ένα χρώμα φόντου (το προεπιλεγμένο είναι μαύρο): +Προαιρετικά, μπορείτε να καθορίσετε το χρώμα φόντου (το προεπιλεγμένο είναι μαύρο): ```php -$image = Image::fromBlank(100, 200, Image::rgb(125, 0, 0)); +$image = Image::fromBlank(100, 200, ImageColor::rgb(125, 0, 0)); ``` Ή φορτώνουμε την εικόνα από ένα αρχείο: @@ -41,91 +43,100 @@ $image = Image::fromBlank(100, 200, Image::rgb(125, 0, 0)); $image = Image::fromFile('nette.jpg'); ``` -Οι υποστηριζόμενες μορφές είναι JPEG, PNG, GIF, WebP, AVIF και BMP, αλλά η έκδοση της PHP σας πρέπει επίσης να τις υποστηρίζει (ελέγξτε το `phpinfo()`, ενότητα GD). Τα κινούμενα σχέδια δεν υποστηρίζονται. -Πρέπει να ανιχνεύσετε τη μορφή εικόνας κατά τη φόρτωση; Η μέθοδος επιστρέφει τη μορφή στη δεύτερη παράμετρο: +Αποθήκευση εικόνας +================== + +Η εικόνα μπορεί να αποθηκευτεί σε ένα αρχείο: ```php -$image = Image::fromFile('nette.jpg', $type); -// $type είναι Image::JPEG, Image::PNG, Image::GIF, Image::WEBP, Image::AVIF ή Image::BMP +$image->save('resampled.jpg'); ``` -Μόνο η ανίχνευση χωρίς φόρτωση της εικόνας γίνεται από το `Image::detectTypeFromFile()`. +Μπορούμε να καθορίσουμε την ποιότητα συμπίεσης στην περιοχή 0..100 για JPEG (προεπιλογή 85), WEBP (προεπιλογή 80) και AVIF (προεπιλογή 30) και 0..9 για PNG (προεπιλογή 9): +```php +$image->save('resampled.jpg', 80); // JPEG, ποιότητα 80% +``` -Αποθήκευση της εικόνας .[#toc-save-the-image] -============================================= - -Η εικόνα μπορεί να αποθηκευτεί σε αρχείο: +Εάν η μορφή δεν είναι προφανής από την επέκταση του αρχείου, μπορεί να καθοριστεί με μια [σταθερά |#Μορφές]: ```php -$image->save('resampled.jpg'); +$image->save('resampled.tmp', null, ImageType::JPEG); ``` -Μπορούμε να καθορίσουμε την ποιότητα συμπίεσης στο εύρος 0..100 για JPEG (προεπιλογή 85), WEBP (προεπιλογή 80) και AVIF (προεπιλογή 30) και 0..9 για PNG (προεπιλογή 9): +Η εικόνα μπορεί να γραφτεί σε μια μεταβλητή αντί για τον δίσκο: ```php -$image->save('resampled.jpg', 80); // JPEG, ποιότητα 80% +$data = $image->toString(ImageType::JPEG, 80); // JPEG, ποιότητα 80% ``` -Εάν η μορφή δεν είναι προφανής από την επέκταση του αρχείου, μπορεί να καθοριστεί με μία από τις σταθερές `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` και `Image::BMP`: +ή να σταλεί απευθείας στον περιηγητή με την κατάλληλη κεφαλίδα HTTP `Content-Type`: ```php -$image->save('resampled.tmp', null, Image::JPEG); +// στέλνει την κεφαλίδα Content-Type: image/png +$image->send(ImageType::PNG); ``` -Η εικόνα μπορεί να εγγραφεί σε μια μεταβλητή αντί στο δίσκο: + +Μορφές +====== + +Οι υποστηριζόμενες μορφές είναι JPEG, PNG, GIF, WebP, AVIF και BMP, ωστόσο, πρέπει επίσης να υποστηρίζονται από την έκδοση PHP σας, την οποία μπορείτε να ελέγξετε με τη συνάρτηση [#isTypeSupported()]. Οι κινούμενες εικόνες δεν υποστηρίζονται. + +Η μορφή αντιπροσωπεύεται από τις σταθερές `ImageType::JPEG`, `ImageType::PNG`, `ImageType::GIF`, `ImageType::WEBP`, `ImageType::AVIF` και `ImageType::BMP`. ```php -$data = $image->toString(Image::JPEG, 80); // JPEG, ποιότητα 80% +$supported = Image::isTypeSupported(ImageType::JPEG); ``` -ή να στείλετε απευθείας στο πρόγραμμα περιήγησης με την κατάλληλη επικεφαλίδα HTTP `Content-Type`: +Χρειάζεται να ανιχνεύσετε τη μορφή της εικόνας κατά τη φόρτωση; Η μέθοδος την επιστρέφει στη δεύτερη παράμετρο: ```php -// στέλνει επικεφαλίδα Content-Type: image/png -$image->send(Image::PNG); +$image = Image::fromFile('nette.jpg', $type); ``` +Η ανίχνευση από μόνη της χωρίς φόρτωση της εικόνας γίνεται από την `Image::detectTypeFromFile()`. -Αλλαγή μεγέθους εικόνας .[#toc-image-resize] -============================================ -Μια συνηθισμένη λειτουργία είναι η αλλαγή μεγέθους μιας εικόνας. Οι τρέχουσες διαστάσεις επιστρέφονται από τις μεθόδους `getWidth()` και `getHeight()`. +Αλλαγή μεγέθους +=============== -Η μέθοδος `resize()` χρησιμοποιείται για την αλλαγή μεγέθους. Αυτό είναι παράδειγμα αναλογικής αλλαγής του μεγέθους ώστε να μην υπερβαίνει τα 500×300 pixels (είτε το πλάτος θα είναι ακριβώς 500px είτε το ύψος θα είναι ακριβώς 300px, μία από τις διαστάσεις υπολογίζεται για να διατηρηθεί η αναλογία διαστάσεων): +Μια συχνή λειτουργία είναι η αλλαγή των διαστάσεων της εικόνας. Οι τρέχουσες διαστάσεις επιστρέφονται από τις μεθόδους `getWidth()` και `getHeight()`. + +Η μέθοδος `resize()` χρησιμοποιείται για την αλλαγή μεγέθους. Παράδειγμα αναλογικής αλλαγής μεγέθους έτσι ώστε να μην υπερβαίνει τις διαστάσεις 500x300 pixels (είτε το πλάτος θα είναι ακριβώς 500 px είτε το ύψος θα είναι ακριβώς 300 px· μία από τις διαστάσεις υπολογίζεται για να διατηρηθεί η αναλογία διαστάσεων): ```php $image->resize(500, 300); ``` -Είναι δυνατόν να ορίσετε μόνο μία διάσταση και η δεύτερη θα υπολογιστεί: +Είναι δυνατό να καθοριστεί μόνο μία διάσταση και η άλλη θα υπολογιστεί: ```php -$image->resize(500, null); // πλάτος 500px, ύψος auto +$image->resize(500, null); // πλάτος 500px, το ύψος θα υπολογιστεί -$image->resize(null, 300); // πλάτος auto, ύψος 300px +$image->resize(null, 300); // το πλάτος θα υπολογιστεί, ύψος 300px ``` -Οποιαδήποτε διάσταση μπορεί να καθοριστεί σε ποσοστά: +Οποιαδήποτε διάσταση μπορεί επίσης να καθοριστεί ως ποσοστό: ```php -$image->resize('75%', 300); // 75% × 300px +$image->resize('75%', 300); // 75 % × 300px ``` -Η συμπεριφορά του `resize` μπορεί να επηρεαστεί από τις ακόλουθες σημαίες. Όλα εκτός από το `Image::Stretch` διατηρούν την αναλογία διαστάσεων. +Η συμπεριφορά της `resize` μπορεί να επηρεαστεί από τις ακόλουθες σημαίες. Όλες εκτός από την `Image::Stretch` διατηρούν την αναλογία διαστάσεων. |--------------------------------------------------------------------------------------- -| Σημαία | Περιγραφή +| Σημαία | Περιγραφή |--------------------------------------------------------------------------------------- -| `Image::OrSmaller` (προεπιλογή) | οι διαστάσεις που θα προκύψουν θα είναι μικρότερες ή ίσες με τις καθορισμένες -| `Image::OrBigger` | γεμίζει την περιοχή στόχου και ενδεχομένως την επεκτείνει προς μία κατεύθυνση -| `Image::Cover` | γεμίζει όλη την περιοχή και κόβει ό,τι την υπερβαίνει -| `Image::ShrinkOnly` | Απλά μικραίνει (δεν επεκτείνει μια μικρή εικόνα) -| `Image::Stretch` | δεν διατηρεί την αναλογία διαστάσεων +| `Image::OrSmaller` (προεπιλογή) | οι τελικές διαστάσεις θα είναι μικρότερες ή ίσες με τις ζητούμενες διαστάσεις +| `Image::OrBigger` | γεμίζει (και πιθανώς υπερβαίνει σε μία διάσταση) την περιοχή στόχο +| `Image::Cover` | γεμίζει την περιοχή στόχο και περικόπτει ό,τι υπερβαίνει +| `Image::ShrinkOnly` | μόνο σμίκρυνση (αποτρέπει το τέντωμα μιας μικρής εικόνας) +| `Image::Stretch` | δεν διατηρεί την αναλογία διαστάσεων -Οι σημαίες περνούν ως τρίτο όρισμα της συνάρτησης: +Οι σημαίες καθορίζονται ως το τρίτο όρισμα της συνάρτησης: ```php $image->resize(500, 300, Image::OrBigger); @@ -140,30 +151,30 @@ $image->resize(500, 300, Image::ShrinkOnly | Image::Stretch); Οι εικόνες μπορούν να αναστραφούν κάθετα ή οριζόντια καθορίζοντας μία από τις διαστάσεις (ή και τις δύο) ως αρνητικό αριθμό: ```php -$flipped = $image->resize(null, '-100%'); // αναστροφή κάθετη +$flipped = $image->resize(null, '-100%'); // κάθετη αναστροφή -$flipped = $image->resize('-100%', '-100%'); // περιστροφή κατά 180° +$flipped = $image->resize('-100%', '-100%'); // περιστροφή 180° -$flipped = $image->resize(-125, 500); // αλλαγή μεγέθους & αναστροφή οριζόντια +$flipped = $image->resize(-125, 500); // αλλαγή μεγέθους & οριζόντια αναστροφή ``` -Μετά τη μείωση της εικόνας μπορούμε να τη βελτιώσουμε με την όξυνση: +Μετά τη σμίκρυνση της εικόνας, η εμφάνισή της μπορεί να βελτιωθεί με μια ελαφριά όξυνση: ```php $image->sharpen(); ``` -Κοπή .[#toc-cropping] -===================== +Περικοπή +======== -Η μέθοδος `crop()` χρησιμοποιείται για την καλλιέργεια: +Η μέθοδος `crop()` χρησιμοποιείται για την περικοπή: ```php $image->crop($left, $top, $width, $height); ``` -Όπως και με το `resize()`, όλες οι τιμές μπορούν να καθοριστούν σε ποσοστά. Τα ποσοστά για τα `$left` και `$top` υπολογίζονται από τον υπόλοιπο χώρο, παρόμοια με την ιδιότητα της CSS `background-position`: +Παρόμοια με την `resize()`, όλες οι τιμές μπορούν να καθοριστούν ως ποσοστά. Τα ποσοστά για `$left` και `$top` υπολογίζονται από τον εναπομείναντα χώρο, παρόμοια με την ιδιότητα CSS `background-position`: ```php $image->crop('100%', '50%', '80%', '80%'); @@ -172,329 +183,359 @@ $image->crop('100%', '50%', '80%', '80%'); [* crop.svg *] -Η εικόνα μπορεί επίσης να περικοπεί αυτόματα, π.χ. περικοπή μαύρων άκρων: +Η εικόνα μπορεί επίσης να περικοπεί αυτόματα, για παράδειγμα, για την περικοπή μαύρων περιθωρίων: ```php $image->cropAuto(IMG_CROP_BLACK); ``` -Η μέθοδος `cropAuto()` είναι μια ενθυλάκωση αντικειμένου της συνάρτησης `imagecropauto()`, δείτε [την τεκμηρίωσή της |https://www.php.net/manual/en/function.imagecropauto] για περισσότερες πληροφορίες. +Η μέθοδος `cropAuto()` είναι μια αντικειμενοστρεφής αντικατάσταση της συνάρτησης `imagecropauto()`. Στην [τεκμηρίωσή της |https://www.php.net/manual/en/function.imagecropauto] θα βρείτε περισσότερες πληροφορίες. + + +Χρώματα .{data-version:4.0.2} +============================= + +Η μέθοδος `ImageColor::rgb()` σας επιτρέπει να ορίσετε ένα χρώμα χρησιμοποιώντας τις τιμές κόκκινου, πράσινου και μπλε (RGB). Προαιρετικά, μπορείτε επίσης να καθορίσετε μια τιμή διαφάνειας στην περιοχή από 0 (εντελώς διαφανές) έως 1 (εντελώς αδιαφανές), δηλαδή όπως στο CSS. + +```php +$color = ImageColor::rgb(255, 0, 0); // Κόκκινο +$transparentBlue = ImageColor::rgb(0, 0, 255, 0.5); // Ημιδιαφανές μπλε +``` + +Η μέθοδος `ImageColor::hex()` σας επιτρέπει να ορίσετε ένα χρώμα χρησιμοποιώντας τη δεκαεξαδική μορφή, παρόμοια με το CSS. Υποστηρίζει τις μορφές `#rgb`, `#rrggbb`, `#rgba` και `#rrggbbaa`: + +```php +$color = ImageColor::hex("#F00"); // Κόκκινο +$transparentGreen = ImageColor::hex("#00FF0080"); // Ημιδιαφανές πράσινο +``` + +Τα χρώματα μπορούν να χρησιμοποιηθούν σε άλλες μεθόδους, όπως `ellipse()`, `fill()` κ.λπ. -Σχεδίαση και επεξεργασία .[#toc-drawing-and-editing] -==================================================== +Σχεδίαση και επεξεργασία +======================== -Μπορείτε να σχεδιάσετε, να γράψετε, να χρησιμοποιήσετε όλες τις συναρτήσεις της PHP για την εργασία με εικόνες, όπως η [imagefilledellipse() |https://www.php.net/manual/en/function.imagefilledellipse.php], αλλά χρησιμοποιώντας στυλ αντικειμένου: +Μπορείτε να σχεδιάσετε, μπορείτε να γράψετε, αλλά μην σκίζετε τα φύλλα. Όλες οι συναρτήσεις PHP για την εργασία με εικόνες είναι διαθέσιμες σε εσάς, δείτε [#Επισκόπηση μεθόδων], αλλά σε αντικειμενοστρεφή μορφή: ```php -$image->filledEllipse($cx, $cy, $width, $height, Image::rgb(255, 0, 0, 63)); +$image->filledEllipse($centerX, $centerY, $width, $height, ImageColor::rgb(255, 0, 0)); ``` -Βλέπε [Επισκόπηση των μεθόδων |#Overview of Methods]. +Επειδή οι συναρτήσεις PHP για τη σχεδίαση ορθογωνίων είναι μη πρακτικές λόγω του καθορισμού συντεταγμένων, η κλάση `Image` προσφέρει τις αντικαταστάσεις τους με τη μορφή των συναρτήσεων [#rectangleWH()] και [#filledRectangleWH()]. -Συγχώνευση πολλαπλών εικόνων .[#toc-merge-multiple-images] -========================================================== +Σύνδεση πολλαπλών εικόνων +========================= -Μπορείτε εύκολα να τοποθετήσετε μια άλλη εικόνα μέσα στην εικόνα: +Μπορείτε εύκολα να εισαγάγετε μια άλλη εικόνα σε μια εικόνα: ```php $logo = Image::fromFile('logo.png'); -$blank = Image::fromBlank(320, 240, Image::rgb(52, 132, 210)); +$blank = Image::fromBlank(320, 240, ImageColor::rgb(52, 132, 210)); -// οι συντεταγμένες μπορούν να οριστούν επίσης σε ποσοστό -$blank->place($logo, '80%', '80%'); // κοντά στην κάτω δεξιά γωνία +// οι συντεταγμένες μπορούν να καθοριστούν ξανά ως ποσοστά +$blank->place($logo, '80%', '80%'); // εισάγουμε κοντά στην κάτω δεξιά γωνία ``` -Κατά την επικόλληση, το κανάλι άλφα τηρείται, επιπλέον, μπορούμε να επηρεάσουμε τη διαφάνεια της εισαγόμενης εικόνας (θα δημιουργήσουμε ένα λεγόμενο υδατογράφημα): +Κατά την εισαγωγή, το κανάλι άλφα (alpha channel) γίνεται σεβαστό. Επιπλέον, μπορούμε να επηρεάσουμε τη διαφάνεια της εισαγόμενης εικόνας (δημιουργούμε ένα λεγόμενο υδατογράφημα): ```php $blank->place($image, '80%', '80%', 25); // η διαφάνεια είναι 25 % ``` -Ένα τέτοιο API είναι πραγματικά ευχάριστο στη χρήση, έτσι δεν είναι; +Ένα τέτοιο API είναι πραγματικά απολαυστικό στη χρήση! -Επισκόπηση των μεθόδων .[#toc-overview-of-methods] -================================================== +Επισκόπηση μεθόδων +================== -static fromBlank(int $width, int $height, array $color=null): Image .[method] ------------------------------------------------------------------------------ -Δημιουργεί μια νέα έγχρωμη εικόνα με τις δεδομένες διαστάσεις. Το προεπιλεγμένο χρώμα είναι το μαύρο. +static fromBlank(int $width, int $height, ?ImageColor $color=null): Image .[method] +----------------------------------------------------------------------------------- +Δημιουργεί μια νέα εικόνα true color με τις δεδομένες διαστάσεις. Το προεπιλεγμένο χρώμα είναι μαύρο. static fromFile(string $file, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------- -Διαβάζει μια εικόνα από ένα αρχείο και επιστρέφει τον τύπο της σε `$detectedFormat`. Οι υποστηριζόμενοι τύποι είναι `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` και `Image::BMP`. +Φορτώνει μια εικόνα από ένα αρχείο και επιστρέφει τον [τύπο |#Μορφές] της στο `$detectedFormat`. static fromString(string $s, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------ -Διαβάζει μια εικόνα από μια συμβολοσειρά και επιστρέφει τον τύπο της σε `$detectedFormat`. Οι υποστηριζόμενοι τύποι είναι `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` και `Image::BMP`. +Φορτώνει μια εικόνα από μια συμβολοσειρά και επιστρέφει τον [τύπο |#Μορφές] της στο `$detectedFormat`. -static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method] ---------------------------------------------------------------------------------- -Δημιουργεί ένα χρώμα που μπορεί να χρησιμοποιηθεί σε άλλες μεθόδους, όπως οι `ellipse()`, `fill()` κ.ο.κ. +static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method][deprecated] +--------------------------------------------------------------------------------------------- +Αυτή η συνάρτηση έχει αντικατασταθεί από την κλάση `ImageColor`, δείτε [#χρώματα]. static typeToExtension(int $type): string .[method] --------------------------------------------------- -Επιστρέφει την επέκταση αρχείου για τη δεδομένη σταθερά `Image::XXX`. +Επιστρέφει την επέκταση αρχείου για τον δεδομένο [τύπο |#Μορφές]. static typeToMimeType(int $type): string .[method] -------------------------------------------------- -Επιστρέφει τον τύπο mime για τη δεδομένη σταθερά `Image::XXX`. +Επιστρέφει τον τύπο MIME για τον δεδομένο [τύπο |#Μορφές]. static extensionToType(string $extension): int .[method] -------------------------------------------------------- -Επιστρέφει τον τύπο της εικόνας ως σταθερά `Image::XXX` σύμφωνα με την επέκταση του αρχείου. +Επιστρέφει τον [τύπο |#Μορφές] της εικόνας με βάση την επέκταση του αρχείου. static detectTypeFromFile(string $file, int &$width=null, int &$height=null): ?int .[method] -------------------------------------------------------------------------------------------- -Επιστρέφει τον τύπο του αρχείου εικόνας ως σταθερά `Image::XXX` και στις παραμέτρους `$width` και `$height` επίσης τις διαστάσεις του. +Επιστρέφει τον [τύπο |#Μορφές] της εικόνας και, στις παραμέτρους `$width` και `$height`, επίσης τις διαστάσεις της. static detectTypeFromString(string $s, int &$width=null, int &$height=null): ?int .[method] ------------------------------------------------------------------------------------------- -Επιστρέφει τον τύπο της εικόνας από τη συμβολοσειρά ως σταθερά `Image::XXX` και στις παραμέτρους `$width` και `$height` επίσης τις διαστάσεις της. +Επιστρέφει τον [τύπο |#Μορφές] της εικόνας από μια συμβολοσειρά και, στις παραμέτρους `$width` και `$height`, επίσης τις διαστάσεις της. -affine(array $affine, array $clip=null): Image .[method] --------------------------------------------------------- -Επιστρέφει μια εικόνα που περιέχει την affine μετασχηματισμένη εικόνα src, χρησιμοποιώντας μια προαιρετική περιοχή αποκοπής. ([περισσότερα |https://www.php.net/manual/en/function.imageaffine]). +static isTypeSupported(int $type): bool .[method] +------------------------------------------------- +Ελέγχει αν υποστηρίζεται ο δεδομένος [τύπος |#Μορφές] εικόνας. + + +static getSupportedTypes(): array .[method]{data-version:4.0.4} +--------------------------------------------------------------- +Επιστρέφει έναν πίνακα με τους υποστηριζόμενους [τύπους |#Μορφές] εικόνας. + + +static calculateTextBox(string $text, string $fontFile, float $size, float $angle=0, array $options=[]): array .[method] +------------------------------------------------------------------------------------------------------------------------ +Υπολογίζει τις διαστάσεις του ορθογωνίου που περιβάλλει το κείμενο σε μια συγκεκριμένη γραμματοσειρά και μέγεθος. Επιστρέφει έναν συσχετιστικό πίνακα που περιέχει τα κλειδιά `left`, `top`, `width`, `height`. Το αριστερό περιθώριο μπορεί επίσης να είναι αρνητικό εάν το κείμενο ξεκινά με αριστερό kerning. + + +affine(array $affine, ?array $clip=null): Image .[method] +--------------------------------------------------------- +Επιστρέφει μια εικόνα που περιέχει την αφινικά μετασχηματισμένη εικόνα `src` χρησιμοποιώντας μια προαιρετική περιοχή περικοπής. ([περισσότερα |https://www.php.net/manual/en/function.imageaffine]). affineMatrixConcat(array $m1, array $m2): array .[method] --------------------------------------------------------- -Επιστρέφει τη συνένωση δύο πινάκων μετασχηματισμού affine, η οποία είναι χρήσιμη εάν πρέπει να εφαρμοστούν πολλαπλοί μετασχηματισμοί στην ίδια εικόνα με μία κίνηση. ([περισσότερα |https://www.php.net/manual/en/function.imageaffinematrixconcat]) +Επιστρέφει τη συνένωση δύο πινάκων αφινικού μετασχηματισμού, η οποία είναι χρήσιμη εάν πρέπει να εφαρμοστούν ταυτόχρονα πολλαπλοί μετασχηματισμοί στην ίδια εικόνα. ([περισσότερα |https://www.php.net/manual/en/function.imageaffinematrixconcat]) -affineMatrixGet(int $type, mixed $options=null): array .[method] ----------------------------------------------------------------- -Επιστρέφει έναν πίνακα μετασχηματισμού affine. ([περισσότερα |https://www.php.net/manual/en/function.imageaffinematrixget]) +affineMatrixGet(int $type, ?mixed $options=null): array .[method] +----------------------------------------------------------------- +Επιστρέφει έναν πίνακα αφινικού μετασχηματισμού. ([περισσότερα |https://www.php.net/manual/en/function.imageaffinematrixget]) alphaBlending(bool $on): void .[method] --------------------------------------- -Επιτρέπει δύο διαφορετικούς τρόπους σχεδίασης σε εικόνες truecolor. Στη λειτουργία ανάμειξης, η συνιστώσα του καναλιού άλφα του χρώματος που παρέχεται σε όλες τις λειτουργίες σχεδίασης, όπως το `setPixel()`, καθορίζει πόσο από το υποκείμενο χρώμα θα πρέπει να επιτρέπεται να διαφαίνεται. Ως αποτέλεσμα, αναμειγνύει αυτόματα το υπάρχον χρώμα στο συγκεκριμένο σημείο με το χρώμα του σχεδίου και αποθηκεύει το αποτέλεσμα στην εικόνα. Το εικονοστοιχείο που προκύπτει είναι αδιαφανές. Στη λειτουργία μη ανάμειξης, το χρώμα σχεδίασης αντιγράφεται κυριολεκτικά με τις πληροφορίες του καναλιού άλφα, αντικαθιστώντας το εικονοστοιχείο προορισμού. Η λειτουργία ανάμειξης δεν είναι διαθέσιμη όταν σχεδιάζετε σε εικόνες παλέτας. ([περισσότερα |https://www.php.net/manual/en/function.imagealphablending]) +Επιτρέπει δύο διαφορετικούς τρόπους σχεδίασης σε εικόνες truecolor. Στη λειτουργία ανάμειξης, το στοιχείο του καναλιού άλφα του χρώματος που χρησιμοποιείται σε όλες τις συναρτήσεις σχεδίασης, όπως η `setPixel()`, καθορίζει σε ποιο βαθμό θα πρέπει να επιτρέπεται η διέλευση του βασικού χρώματος. Ως αποτέλεσμα, το υπάρχον χρώμα σε αυτό το σημείο αναμειγνύεται αυτόματα με το σχεδιασμένο χρώμα και το αποτέλεσμα αποθηκεύεται στην εικόνα. Το τελικό pixel είναι αδιαφανές. Στη λειτουργία χωρίς ανάμειξη, το σχεδιασμένο χρώμα αντιγράφεται κυριολεκτικά με τις πληροφορίες του καναλιού άλφα και αντικαθιστά το pixel στόχο. Η λειτουργία ανάμειξης δεν είναι διαθέσιμη κατά τη σχεδίαση σε εικόνες παλέτας. ([περισσότερα |https://www.php.net/manual/en/function.imagealphablending]) antialias(bool $on): void .[method] ----------------------------------- -Ενεργοποιήστε τις γρήγορες μεθόδους αντιδιαμετρικής σχεδίασης για γραμμές και καλωδιωμένα πολύγωνα. Δεν υποστηρίζει στοιχεία άλφα. Λειτουργεί με απευθείας ανάμειξη. Λειτουργεί μόνο με truecolor εικόνες. +Ενεργοποιεί τη σχεδίαση εξομαλυσμένων γραμμών και πολυγώνων. Δεν υποστηρίζει κανάλια άλφα. Λειτουργεί μόνο σε εικόνες truecolor. -Η χρήση primitives με αντιδιαμετρική απεικόνιση και διαφανές χρώμα φόντου μπορεί να οδηγήσει σε απροσδόκητα αποτελέσματα. Η μέθοδος ανάμειξης χρησιμοποιεί το χρώμα φόντου όπως όλα τα άλλα χρώματα. Η έλλειψη υποστήριξης του συστατικού άλφα δεν επιτρέπει μια μέθοδο αντιδιαστολής με βάση το άλφα. ([περισσότερα |https://www.php.net/manual/en/function.imageantialias]) +Η χρήση εξομαλυσμένων πρωτογενών με διαφανές χρώμα φόντου μπορεί να καταλήξει σε κάποια απροσδόκητα αποτελέσματα. Η μέθοδος ανάμειξης χρησιμοποιεί το χρώμα φόντου όπως όλα τα άλλα χρώματα. ([περισσότερα |https://www.php.net/manual/en/function.imageantialias]) -arc(int $x, int $y, int $w, int $h, int $start, int $end, int $color): void .[method] -------------------------------------------------------------------------------------- -Σχεδιάζει ένα τόξο κύκλου με κέντρο τις δεδομένες συντεταγμένες. ([περισσότερα |https://www.php.net/manual/en/function.imagearc]) - - -char(int $font, int $x, int $y, string $char, int $color): void .[method] -------------------------------------------------------------------------- -Σχεδιάζει τον πρώτο χαρακτήρα του `$char` στην εικόνα με το πάνω αριστερό μέρος του στο `$x`,`$y` (το πάνω αριστερό μέρος είναι 0, 0) με το χρώμα `$color`. ([περισσότερα |https://www.php.net/manual/en/function.imagechar]) - - -charUp(int $font, int $x, int $y, string $char, int $color): void .[method] ---------------------------------------------------------------------------- -Σχεδιάζει τον χαρακτήρα `$char` κάθετα στην καθορισμένη συντεταγμένη της δεδομένης εικόνας. ([περισσότερα |https://www.php.net/manual/en/function.imagecharup]) +arc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color): void .[method] +--------------------------------------------------------------------------------------------------------------------------- +Σχεδιάζει ένα τόξο κύκλου με κέντρο στις δεδομένες συντεταγμένες. ([περισσότερα |https://www.php.net/manual/en/function.imagearc]) colorAllocate(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------- -Επιστρέφει ένα αναγνωριστικό χρώματος που αντιπροσωπεύει το χρώμα που αποτελείται από τις δεδομένες συνιστώσες RGB. Πρέπει να κληθεί για να δημιουργήσει κάθε χρώμα που πρόκειται να χρησιμοποιηθεί στην εικόνα. ([περισσότερα |https://www.php.net/manual/en/function.imagecolorallocate]) +Επιστρέφει ένα αναγνωριστικό χρώματος που αντιπροσωπεύει το χρώμα που αποτελείται από τα δεδομένα συστατικά RGB. Πρέπει να καλείται για τη δημιουργία κάθε χρώματος που πρόκειται να χρησιμοποιηθεί στην εικόνα. ([περισσότερα |https://www.php.net/manual/en/function.imagecolorallocate]) colorAllocateAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ------------------------------------------------------------------------------ -Συμπεριφέρεται πανομοιότυπα με το `colorAllocate()` με την προσθήκη της παραμέτρου διαφάνειας `$alpha`. ([περισσότερα |https://www.php.net/manual/en/function.imagecolorallocatealpha]) +Συμπεριφέρεται το ίδιο με την `colorAllocate()` με την προσθήκη της παραμέτρου διαφάνειας `$alpha`. ([περισσότερα |https://www.php.net/manual/en/function.imagecolorallocatealpha]) colorAt(int $x, int $y): int .[method] -------------------------------------- -Επιστρέφει τον δείκτη του χρώματος του εικονοστοιχείου στην καθορισμένη θέση στην εικόνα. Εάν η εικόνα είναι εικόνα πραγματικών χρωμάτων, η συνάρτηση αυτή επιστρέφει την τιμή RGB του συγκεκριμένου εικονοστοιχείου ως ακέραιο αριθμό. Χρησιμοποιήστε bitshifting και masking για να αποκτήσετε πρόσβαση στις ξεχωριστές τιμές των κόκκινων, πράσινων και μπλε συνιστωσών: ([περισσότερα |https://www.php.net/manual/en/function.imagecolorat]) +Επιστρέφει τον δείκτη του χρώματος του pixel στην καθορισμένη θέση στην εικόνα. Εάν η εικόνα είναι truecolor, αυτή η συνάρτηση επιστρέφει την τιμή RGB αυτού του pixel ως ακέραιο. Χρησιμοποιήστε bit shifting και bit masking για πρόσβαση στις ξεχωριστές τιμές των συστατικών κόκκινου, πράσινου και μπλε. ([περισσότερα |https://www.php.net/manual/en/function.imagecolorat]) colorClosest(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------ -Επιστρέφει τον δείκτη του χρώματος στην παλέτα της εικόνας που είναι πιο κοντά στην καθορισμένη τιμή RGB. Η "απόσταση" μεταξύ του επιθυμητού χρώματος και κάθε χρώματος στην παλέτα υπολογίζεται σαν οι τιμές RGB να αντιπροσωπεύουν σημεία στον τρισδιάστατο χώρο. ([περισσότερα |https://www.php.net/manual/en/function.imagecolorclosest]) +Επιστρέφει τον δείκτη του χρώματος στην παλέτα της εικόνας που είναι «πλησιέστερος» στην καθορισμένη τιμή RGB. Η "απόσταση" μεταξύ του επιθυμητού χρώματος και κάθε χρώματος στην παλέτα υπολογίζεται σαν οι τιμές RGB να αντιπροσώπευαν σημεία σε έναν τρισδιάστατο χώρο. ([περισσότερα |https://www.php.net/manual/en/function.imagecolorclosest]) colorClosestAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ----------------------------------------------------------------------------- -Επιστρέφει τον δείκτη του χρώματος στην παλέτα της εικόνας που είναι "πιο κοντά" στην καθορισμένη τιμή RGB και το επίπεδο `$alpha`. ([περισσότερα |https://www.php.net/manual/en/function.imagecolorclosestalpha]) +Επιστρέφει τον δείκτη του χρώματος στην παλέτα της εικόνας που είναι «πλησιέστερος» στην καθορισμένη τιμή RGB και στο επίπεδο `$alpha`. ([περισσότερα |https://www.php.net/manual/en/function.imagecolorclosestalpha]) colorClosestHWB(int $red, int $green, int $blue): int .[method] --------------------------------------------------------------- -Λήψη του δείκτη του χρώματος που έχει την απόχρωση, το λευκό και το μαύρο που είναι πλησιέστερα στο δεδομένο χρώμα. ([περισσότερα |https://www.php.net/manual/en/function.imagecolorclosesthwb]) +Λαμβάνει τον δείκτη του χρώματος που έχει την απόχρωση, το λευκό και το μαύρο χρώμα πλησιέστερα στο δεδομένο χρώμα. ([περισσότερα |https://www.php.net/manual/en/function.imagecolorclosesthwb]) colorDeallocate(int $color): void .[method] ------------------------------------------- -Αποδεσμεύει ένα χρώμα που είχε προηγουμένως εκχωρηθεί με το `colorAllocate()` ή το `colorAllocateAlpha()`. ([περισσότερα |https://www.php.net/manual/en/function.imagecolordeallocate]) +Αποδεσμεύει ένα χρώμα που είχε προηγουμένως εκχωρηθεί χρησιμοποιώντας `colorAllocate()` ή `colorAllocateAlpha()`. ([περισσότερα |https://www.php.net/manual/en/function.imagecolordeallocate]) colorExact(int $red, int $green, int $blue): int .[method] ---------------------------------------------------------- -Επιστρέφει το δείκτη του καθορισμένου χρώματος στην παλέτα της εικόνας. ([περισσότερα |https://www.php.net/manual/en/function.imagecolorexact]) +Επιστρέφει τον δείκτη του καθορισμένου χρώματος στην παλέτα της εικόνας. ([περισσότερα |https://www.php.net/manual/en/function.imagecolorexact]) colorExactAlpha(int $red, int $green, int $blue, int $alpha): int .[method] --------------------------------------------------------------------------- -Επιστρέφει το δείκτη του καθορισμένου χρώματος+άλφα στην παλέτα της εικόνας. ([περισσότερα |https://www.php.net/manual/en/function.imagecolorexactalpha]) +Επιστρέφει τον δείκτη του καθορισμένου χρώματος + άλφα στην παλέτα της εικόνας. ([περισσότερα |https://www.php.net/manual/en/function.imagecolorexactalpha]) colorMatch(Image $image2): void .[method] ----------------------------------------- -Κάνει τα χρώματα της έκδοσης παλέτας μιας εικόνας να ταιριάζουν περισσότερο με την έκδοση πραγματικών χρωμάτων. ([περισσότερα |https://www.php.net/manual/en/function.imagecolormatch]) +Προσαρμόζει τα χρώματα της παλέτας στη δεύτερη εικόνα. ([περισσότερα |https://www.php.net/manual/en/function.imagecolormatch]) colorResolve(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------ -Επιστρέφει έναν δείκτη χρώματος για ένα ζητούμενο χρώμα, είτε το ακριβές χρώμα είτε την πλησιέστερη δυνατή εναλλακτική λύση. ([περισσότερα |https://www.php.net/manual/en/function.imagecolorresolve]) +Επιστρέφει τον δείκτη χρώματος για το επιθυμητό χρώμα, είτε το ακριβές χρώμα είτε την πλησιέστερη δυνατή εναλλακτική λύση. ([περισσότερα |https://www.php.net/manual/en/function.imagecolorresolve]) colorResolveAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ----------------------------------------------------------------------------- -Επιστρέφει έναν δείκτη χρώματος για ένα ζητούμενο χρώμα, είτε το ακριβές χρώμα είτε την πλησιέστερη δυνατή εναλλακτική λύση. ([περισσότερα |https://www.php.net/manual/en/function.imagecolorresolvealpha]) +Επιστρέφει τον δείκτη χρώματος για το επιθυμητό χρώμα, είτε το ακριβές χρώμα είτε την πλησιέστερη δυνατή εναλλακτική λύση. ([περισσότερα |https://www.php.net/manual/en/function.imagecolorresolvealpha]) colorSet(int $index, int $red, int $green, int $blue): void .[method] --------------------------------------------------------------------- -Αυτό θέτει τον καθορισμένο δείκτη στην παλέτα στο καθορισμένο χρώμα. ([περισσότερα |https://www.php.net/manual/en/function.imagecolorset]) +Ορίζει τον καθορισμένο δείκτη στην παλέτα στο καθορισμένο χρώμα. ([περισσότερα |https://www.php.net/manual/en/function.imagecolorset]) colorsForIndex(int $index): array .[method] ------------------------------------------- -Λαμβάνει το χρώμα για έναν καθορισμένο δείκτη. ([περισσότερα |https://www.php.net/manual/en/function.imagecolorsforindex]) +Λαμβάνει το χρώμα του καθορισμένου δείκτη. ([περισσότερα |https://www.php.net/manual/en/function.imagecolorsforindex]) colorsTotal(): int .[method] ---------------------------- -Επιστρέφει τον αριθμό των χρωμάτων σε μια παλέτα εικόνας ([more |https://www.php.net/manual/en/function.imagecolorstotal]). +Επιστρέφει τον αριθμό των χρωμάτων στην παλέτα της εικόνας. ([περισσότερα |https://www.php.net/manual/en/function.imagecolorstotal]) -colorTransparent(int $color=null): int .[method] ------------------------------------------------- -Λαμβάνει ή ορίζει το διαφανές χρώμα της εικόνας. ([περισσότερα |https://www.php.net/manual/en/function.imagecolortransparent]) +colorTransparent(?int $color=null): int .[method] +------------------------------------------------- +Λαμβάνει ή ορίζει το διαφανές χρώμα στην εικόνα. ([περισσότερα |https://www.php.net/manual/en/function.imagecolortransparent]) convolution(array $matrix, float $div, float $offset): void .[method] --------------------------------------------------------------------- -Εφαρμόζει έναν πίνακα συνέλιξης στην εικόνα, χρησιμοποιώντας τον δεδομένο συντελεστή και τη μετατόπιση. ([περισσότερα |https://www.php.net/manual/en/function.imageconvolution]) +Εφαρμόζει έναν πίνακα συνέλιξης στην εικόνα, χρησιμοποιώντας τον δεδομένο συντελεστή και μετατόπιση. ([περισσότερα |https://www.php.net/manual/en/function.imageconvolution]) .[note] -Απαιτεί *Επέκταση GD*, οπότε δεν είναι σίγουρο ότι θα λειτουργήσει παντού. +Απαιτεί την παρουσία της *Bundled GD extension*, επομένως μπορεί να μην λειτουργεί παντού. copy(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH): void .[method] -------------------------------------------------------------------------------------------------- -Αντιγράφει ένα τμήμα του `$src` στην εικόνα ξεκινώντας από τις συντεταγμένες `$srcX`, `$srcY` με πλάτος `$srcW` και ύψος `$srcH`. Το καθορισμένο τμήμα θα αντιγραφεί στις συντεταγμένες, `$dstX` και `$dstY`. ([περισσότερα |https://www.php.net/manual/en/function.imagecopy]) +Αντιγράφει ένα τμήμα του `$src` στην εικόνα ξεκινώντας από τις συντεταγμένες `$srcX`, `$srcY` με πλάτος `$srcW` και ύψος `$srcH`. Το καθορισμένο τμήμα θα αντιγραφεί στις συντεταγμένες `$dstX` και `$dstY`. ([περισσότερα |https://www.php.net/manual/en/function.imagecopy]) copyMerge(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] --------------------------------------------------------------------------------------------------------------------- -Αντιγράφει ένα τμήμα του `$src` στην εικόνα ξεκινώντας από τις συντεταγμένες `$srcX`, `$srcY` με πλάτος `$srcW` και ύψος `$srcH`. Το καθορισμένο τμήμα θα αντιγραφεί στις συντεταγμένες, `$dstX` και `$dstY`. ([περισσότερα |https://www.php.net/manual/en/function.imagecopymerge]) +Αντιγράφει ένα τμήμα του `$src` στην εικόνα ξεκινώντας από τις συντεταγμένες `$srcX`, `$srcY` με πλάτος `$srcW` και ύψος `$srcH`. Το καθορισμένο τμήμα θα αντιγραφεί στις συντεταγμένες `$dstX` και `$dstY`. ([περισσότερα |https://www.php.net/manual/en/function.imagecopymerge]) copyMergeGray(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] ------------------------------------------------------------------------------------------------------------------------- -Αντιγράφει ένα τμήμα του `$src` στην εικόνα ξεκινώντας από τις συντεταγμένες `$srcX`, `$srcY` με πλάτος `$srcW` και ύψος `$srcH`. Το καθορισμένο τμήμα θα αντιγραφεί στις συντεταγμένες, `$dstX` και `$dstY`. +Αντιγράφει ένα τμήμα του `$src` στην εικόνα ξεκινώντας από τις συντεταγμένες `$srcX`, `$srcY` με πλάτος `$srcW` και ύψος `$srcH`. Το καθορισμένο τμήμα θα αντιγραφεί στις συντεταγμένες `$dstX` και `$dstY`. -Αυτή η λειτουργία είναι πανομοιότυπη με τη λειτουργία `copyMerge()`, με τη διαφορά ότι κατά τη συγχώνευση διατηρεί την απόχρωση της πηγής μετατρέποντας τα εικονοστοιχεία προορισμού σε κλίμακα του γκρι πριν από τη λειτουργία αντιγραφής. ([περισσότερα |https://www.php.net/manual/en/function.imagecopymergegray]) +Αυτή η συνάρτηση είναι πανομοιότυπη με την `copyMerge()` με τη διαφορά ότι κατά τη συγχώνευση διατηρεί την απόχρωση της πηγής μετατρέποντας τα pixel στόχου σε κλίμακα του γκρι πριν από τη λειτουργία αντιγραφής. ([περισσότερα |https://www.php.net/manual/en/function.imagecopymergegray]) copyResampled(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] --------------------------------------------------------------------------------------------------------------------------------- -Αντιγράφει ένα ορθογώνιο τμήμα μιας εικόνας σε μια άλλη εικόνα, παρεμβάλλοντας ομαλά τις τιμές των εικονοστοιχείων, έτσι ώστε, ειδικότερα, κατά τη μείωση του μεγέθους μιας εικόνας να διατηρείται μεγάλη ευκρίνεια. +Αντιγράφει μια ορθογώνια περιοχή μιας εικόνας σε μια άλλη εικόνα, παρεμβάλλοντας ομαλά τις τιμές των pixel, έτσι ώστε ιδίως η μείωση του μεγέθους της εικόνας να διατηρεί ακόμα μεγάλη ευκρίνεια. -Με άλλα λόγια, το `copyResampled()` θα πάρει μια ορθογώνια περιοχή από το `$src` πλάτους `$srcW` και ύψους `$srcH` στη θέση (`$srcX`,`$srcY`) και θα την τοποθετήσει σε μια ορθογώνια περιοχή της εικόνας πλάτους `$dstW` και ύψους `$dstH` στη θέση (`$dstX`,`$dstY`). +Με άλλα λόγια, η `copyResampled()` παίρνει μια ορθογώνια περιοχή από το `$src` πλάτους `$srcW` και ύψους `$srcH` στη θέση (`$srcX`, `$srcY`) και την τοποθετεί σε μια ορθογώνια περιοχή της εικόνας πλάτους `$dstW` και ύψους `$dstH` στη θέση (`$dstX`, `$dstY`). -Εάν οι συντεταγμένες πηγής και προορισμού και τα πλάτη και ύψη διαφέρουν, θα πραγματοποιηθεί η κατάλληλη επιμήκυνση ή συρρίκνωση του τμήματος εικόνας. Οι συντεταγμένες αναφέρονται στην επάνω αριστερή γωνία. Αυτή η λειτουργία μπορεί να χρησιμοποιηθεί για την αντιγραφή περιοχών εντός της ίδιας εικόνας, αλλά αν οι περιοχές επικαλύπτονται, τα αποτελέσματα θα είναι απρόβλεπτα. ([περισσότερα |https://www.php.net/manual/en/function.imagecopyresampled]) +Εάν οι συντεταγμένες πηγής και στόχου, το πλάτος και το ύψος διαφέρουν, πραγματοποιείται αντίστοιχο τέντωμα ή σμίκρυνση του τμήματος της εικόνας. Οι συντεταγμένες αναφέρονται στην επάνω αριστερή γωνία. Αυτή η συνάρτηση μπορεί να χρησιμοποιηθεί για την αντιγραφή περιοχών στην ίδια εικόνα, αλλά εάν οι περιοχές επικαλύπτονται, τα αποτελέσματα δεν θα είναι προβλέψιμα. ([περισσότερα |https://www.php.net/manual/en/function.imagecopyresampled]) copyResized(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] ------------------------------------------------------------------------------------------------------------------------------- -Αντιγράφει ένα ορθογώνιο τμήμα μιας εικόνας σε μια άλλη εικόνα. Με άλλα λόγια, το `copyResized()` θα πάρει μια ορθογώνια περιοχή από το `$src` πλάτους `$srcW` και ύψους `$srcH` στη θέση (`$srcX`,`$srcY`) και θα την τοποθετήσει σε μια ορθογώνια περιοχή της εικόνας πλάτους `$dstW` και ύψους `$dstH` στη θέση (`$dstX`,`$dstY`). +Αντιγράφει μια ορθογώνια περιοχή μιας εικόνας σε μια άλλη εικόνα. Με άλλα λόγια, η `copyResized()` λαμβάνει μια ορθογώνια περιοχή από το `$src` πλάτους `$srcW` και ύψους `$srcH` στη θέση (`$srcX`, `$srcY`) και την τοποθετεί σε μια ορθογώνια περιοχή της εικόνας πλάτους `$dstW` και ύψους `$dstH` στη θέση (`$dstX`, `$dstY`). -Εάν οι συντεταγμένες πηγής και προορισμού και τα πλάτη και ύψη διαφέρουν, θα πραγματοποιηθεί η κατάλληλη επιμήκυνση ή συρρίκνωση του τμήματος εικόνας. Οι συντεταγμένες αναφέρονται στην επάνω αριστερή γωνία. Αυτή η λειτουργία μπορεί να χρησιμοποιηθεί για την αντιγραφή περιοχών εντός της ίδιας εικόνας, αλλά αν οι περιοχές επικαλύπτονται, τα αποτελέσματα θα είναι απρόβλεπτα. ([περισσότερα |https://www.php.net/manual/en/function.imagecopyresized]) +Εάν οι συντεταγμένες πηγής και στόχου, το πλάτος και το ύψος διαφέρουν, πραγματοποιείται αντίστοιχο τέντωμα ή σμίκρυνση του τμήματος της εικόνας. Οι συντεταγμένες αναφέρονται στην επάνω αριστερή γωνία. Αυτή η συνάρτηση μπορεί να χρησιμοποιηθεί για την αντιγραφή περιοχών στην ίδια εικόνα, αλλά εάν οι περιοχές επικαλύπτονται, τα αποτελέσματα δεν θα είναι προβλέψιμα. ([περισσότερα |https://www.php.net/manual/en/function.imagecopyresized]) crop(int|string $left, int|string $top, int|string $width, int|string $height): Image .[method] ----------------------------------------------------------------------------------------------- -Περικόπτει μια εικόνα στη δεδομένη ορθογώνια περιοχή. Οι διαστάσεις μπορούν να μεταβιβαστούν ως ακέραιοι αριθμοί σε pixels ή συμβολοσειρές σε ποσοστό (π.χ. `'50%'`). +Περικόπτει την εικόνα στην δεδομένη ορθογώνια περιοχή. Οι διαστάσεις μπορούν να καθοριστούν ως ακέραιοι σε pixel ή συμβολοσειρές σε ποσοστά (για παράδειγμα `'50%'`). -cropAuto(int $mode=-1, float $threshold=.5, int $color=-1): Image .[method] ---------------------------------------------------------------------------- -Αυτόματη περικοπή μιας εικόνας σύμφωνα με το δεδομένο `$mode`. ([περισσότερα |https://www.php.net/manual/en/function.imagecropauto]) +cropAuto(int $mode=-1, float $threshold=.5, ?ImageColor $color=null): Image .[method] +------------------------------------------------------------------------------------- +Περικόπτει αυτόματα την εικόνα σύμφωνα με τον δεδομένο `$mode`. ([περισσότερα |https://www.php.net/manual/en/function.imagecropauto]) -ellipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------- -Σχεδιάζει μια έλλειψη με κέντρο τις καθορισμένες συντεταγμένες. ([περισσότερα |https://www.php.net/manual/en/function.imageellipse]) +ellipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------- +Σχεδιάζει μια έλλειψη με κέντρο στις καθορισμένες συντεταγμένες. ([περισσότερα |https://www.php.net/manual/en/function.imageellipse]) -fill(int $x, int $y, int $color): void .[method] ------------------------------------------------- -Εκτελεί ένα γέμισμα πλημμύρας με αφετηρία τη δεδομένη συντεταγμένη (πάνω αριστερά είναι 0, 0) με το δεδομένο `$color` στην εικόνα. ([περισσότερα |https://www.php.net/manual/en/function.imagefill]) +fill(int $x, int $y, ImageColor $color): void .[method] +------------------------------------------------------- +Γεμίζει μια περιοχή ξεκινώντας από τη δεδομένη συντεταγμένη (επάνω αριστερά είναι 0, 0) με το δεδομένο `$color`. ([περισσότερα |https://www.php.net/manual/en/function.imagefill]) -filledArc(int $cx, int $cy, int $w, int $h, int $s, int $e, int $color, int $style): void .[method] ---------------------------------------------------------------------------------------------------- -Σχεδιάζει ένα μερικό τόξο με κέντρο την καθορισμένη συντεταγμένη στην εικόνα. ([περισσότερα |https://www.php.net/manual/en/function.imagefilledarc]) +filledArc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color, int $style): void .[method] +--------------------------------------------------------------------------------------------------------------------------------------------- +Σχεδιάζει ένα μερικό τόξο με κέντρο στις καθορισμένες συντεταγμένες. ([περισσότερα |https://www.php.net/manual/en/function.imagefilledarc]) -filledEllipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------------- -Σχεδιάζει μια έλλειψη με κέντρο την καθορισμένη συντεταγμένη στην εικόνα. ([περισσότερα |https://www.php.net/manual/en/function.imagefilledellipse]) +filledEllipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------------- +Σχεδιάζει μια έλλειψη με κέντρο στις καθορισμένες συντεταγμένες. ([περισσότερα |https://www.php.net/manual/en/function.imagefilledellipse]) -filledPolygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------------- -Δημιουργεί ένα γεμάτο πολύγωνο στην εικόνα $image. ([περισσότερα |https://www.php.net/manual/en/function.imagefilledpolygon]) +filledPolygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------------- +Δημιουργεί ένα γεμάτο πολύγωνο στην εικόνα. ([περισσότερα |https://www.php.net/manual/en/function.imagefilledpolygon]) -filledRectangle(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] -------------------------------------------------------------------------------- -Δημιουργεί ένα ορθογώνιο γεμάτο με το `$color` στην εικόνα που ξεκινά από το σημείο 1 και καταλήγει στο σημείο 2. 0, 0 είναι η επάνω αριστερή γωνία της εικόνας. ([περισσότερα |https://www.php.net/manual/en/function.imagefilledrectangle]) +filledRectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------- +Δημιουργεί ένα ορθογώνιο γεμάτο με `$color` στην εικόνα ξεκινώντας από το σημείο `$x1` & `$y1` και τελειώνοντας στο σημείο `$x2` & `$y2`. Το σημείο 0, 0 είναι η επάνω αριστερή γωνία της εικόνας. ([περισσότερα |https://www.php.net/manual/en/function.imagefilledrectangle]) -fillToBorder(int $x, int $y, int $border, int $color): void .[method] ---------------------------------------------------------------------- -Εκτελεί ένα γέμισμα πλημμύρας του οποίου το χρώμα των ορίων ορίζεται από το `$border`. Το σημείο εκκίνησης για το γέμισμα είναι `$x`, `$y` (πάνω αριστερά είναι 0, 0) και η περιοχή γεμίζεται με το χρώμα `$color`. ([περισσότερα |https://www.php.net/manual/en/function.imagefilltoborder]) +filledRectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------------- +Δημιουργεί ένα ορθογώνιο γεμάτο με `$color` στην εικόνα ξεκινώντας από το σημείο `$left` & `$top` με πλάτος `$width` και ύψος `$height`. Το σημείο 0, 0 είναι η επάνω αριστερή γωνία της εικόνας. + + +fillToBorder(int $x, int $y, int $border, ImageColor $color): void .[method] +---------------------------------------------------------------------------- +Σχεδιάζει ένα γέμισμα του οποίου το χρώμα περιγράμματος ορίζεται από το `$border`. Το σημείο εκκίνησης του γεμίσματος είναι `$x`, `$y` (επάνω αριστερά είναι 0, 0) και η περιοχή γεμίζεται με το χρώμα `$color`. ([περισσότερα |https://www.php.net/manual/en/function.imagefilltoborder]) filter(int $filtertype, int ...$args): void .[method] ----------------------------------------------------- -Εφαρμόζει το συγκεκριμένο φίλτρο `$filtertype` στην εικόνα. ([περισσότερα |https://www.php.net/manual/en/function.imagefilter]) +Εφαρμόζει το δεδομένο φίλτρο `$filtertype` στην εικόνα. ([περισσότερα |https://www.php.net/manual/en/function.imagefilter]) flip(int $mode): void .[method] ------------------------------- -Αναποδογυρίζει την εικόνα χρησιμοποιώντας το δεδομένο `$mode`. ([περισσότερα |https://www.php.net/manual/en/function.imageflip]) +Αναστρέφει την εικόνα χρησιμοποιώντας τον δεδομένο `$mode`. ([περισσότερα |https://www.php.net/manual/en/function.imageflip]) -ftText(int $size, int $angle, int $x, int $y, int $col, string $fontFile, string $text, array $extrainfo=null): array .[method] -------------------------------------------------------------------------------------------------------------------------------- -Γράψτε κείμενο στην εικόνα χρησιμοποιώντας γραμματοσειρές με το FreeType 2. ([περισσότερα |https://www.php.net/manual/en/function.imagefttext]) +ftText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +---------------------------------------------------------------------------------------------------------------------------------------- +Γράφει κείμενο στην εικόνα. ([περισσότερα |https://www.php.net/manual/en/function.imagefttext]) gammaCorrect(float $inputgamma, float $outputgamma): void .[method] ------------------------------------------------------------------- -Εφαρμόζει διόρθωση γάμμα στην εικόνα με δεδομένο ένα γάμμα εισόδου και ένα γάμμα εξόδου. ([περισσότερα |https://www.php.net/manual/en/function.imagegammacorrect]) +Εφαρμόζει διόρθωση γάμμα στην εικόνα σε σχέση με το γάμμα εισόδου και εξόδου. ([περισσότερα |https://www.php.net/manual/en/function.imagegammacorrect]) getClip(): array .[method] -------------------------- -Ανακτά το τρέχον ορθογώνιο αποκοπής, δηλαδή την περιοχή πέρα από την οποία δεν θα σχεδιαστεί κανένα εικονοστοιχείο. ([περισσότερα |https://www.php.net/manual/en/function.imagegetclip]) +Επιστρέφει την τρέχουσα περιοχή περικοπής, δηλαδή την περιοχή πέρα από την οποία δεν θα σχεδιαστούν pixel. ([περισσότερα |https://www.php.net/manual/en/function.imagegetclip]) getHeight(): int .[method] @@ -504,7 +545,7 @@ getHeight(): int .[method] getImageResource(): resource|GdImage .[method] ---------------------------------------------- -Επιστρέφει τον αρχικό πόρο. +Επιστρέφει τον αρχικό πόρο (resource). getWidth(): int .[method] @@ -512,29 +553,29 @@ getWidth(): int .[method] Επιστρέφει το πλάτος της εικόνας. -interlace(int $interlace=null): int .[method] ---------------------------------------------- -Ενεργοποιεί ή απενεργοποιεί το bit interlace. Εάν το bit interlace είναι ρυθμισμένο και η εικόνα χρησιμοποιείται ως εικόνα JPEG, η εικόνα δημιουργείται ως προοδευτικό JPEG. ([περισσότερα |https://www.php.net/manual/en/function.imageinterlace]) +interlace(?int $interlace=null): int .[method] +---------------------------------------------- +Ενεργοποιεί ή απενεργοποιεί τη λειτουργία πλεξίματος (interlacing). Εάν η λειτουργία πλεξίματος είναι ενεργοποιημένη και η εικόνα αποθηκεύεται ως JPEG, θα αποθηκευτεί ως προοδευτικό JPEG. ([περισσότερα |https://www.php.net/manual/en/function.imageinterlace]) isTrueColor(): bool .[method] ----------------------------- -Βρίσκει αν η εικόνα είναι truecolor. ([περισσότερα |https://www.php.net/manual/en/function.imageistruecolor]) +Ελέγχει αν η εικόνα είναι truecolor. ([περισσότερα |https://www.php.net/manual/en/function.imageistruecolor]) layerEffect(int $effect): void .[method] ---------------------------------------- -Ορίστε τη σημαία ανάμειξης άλφα για να χρησιμοποιήσετε εφέ διαστρωμάτωσης. ([περισσότερα |https://www.php.net/manual/en/function.imagelayereffect]) +Ορίζει τη σημαία ανάμειξης άλφα για χρήση εφέ επιπέδων. ([περισσότερα |https://www.php.net/manual/en/function.imagelayereffect]) -line(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] --------------------------------------------------------------------- -Σχεδιάζει μια γραμμή μεταξύ των δύο δοσμένων σημείων. ([περισσότερα |https://www.php.net/manual/en/function.imageline]) +line(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +--------------------------------------------------------------------------- +Σχεδιάζει μια γραμμή μεταξύ δύο δεδομένων σημείων. ([περισσότερα |https://www.php.net/manual/en/function.imageline]) -openPolygon(array $points, int $numPoints, int $color): void .[method] ----------------------------------------------------------------------- -Σχεδιάζει ένα ανοιχτό πολύγωνο στην εικόνα. Σε αντίθεση με το `polygon()`, δεν χαράσσεται γραμμή μεταξύ του τελευταίου και του πρώτου σημείου. ([περισσότερα |https://www.php.net/manual/en/function.imageopenpolygon]) +openPolygon(array $points, ImageColor $color): void .[method] +------------------------------------------------------------- +Σχεδιάζει ένα ανοιχτό πολύγωνο στην εικόνα. Σε αντίθεση με το `polygon()`, δεν σχεδιάζεται γραμμή μεταξύ του τελευταίου και του πρώτου σημείου. ([περισσότερα |https://www.php.net/manual/en/function.imageopenpolygon]) paletteCopy(Image $source): void .[method] @@ -544,78 +585,83 @@ paletteCopy(Image $source): void .[method] paletteToTrueColor(): void .[method] ------------------------------------ -Μετατρέπει μια εικόνα βασισμένη σε παλέτα, που δημιουργείται από συναρτήσεις όπως η `create()` σε μια εικόνα πραγματικού χρώματος, όπως η `createtruecolor()`. ([περισσότερα |https://www.php.net/manual/en/function.imagepalettetotruecolor]) +Μετατρέπει μια εικόνα βασισμένη σε παλέτα σε μια εικόνα πλήρους χρώματος (truecolor). ([περισσότερα |https://www.php.net/manual/en/function.imagepalettetotruecolor]) place(Image $image, int|string $left=0, int|string $top=0, int $opacity=100): Image .[method] --------------------------------------------------------------------------------------------- -Αντιγράφει το `$image` στην εικόνα στις συντεταγμένες `$left` και `$top`. Οι συντεταγμένες μπορούν να μεταβιβαστούν ως ακέραιοι αριθμοί σε pixels ή συμβολοσειρές σε ποσοστό (π.χ. `'50%'`). +Αντιγράφει την `$image` στην εικόνα στις συντεταγμένες `$left` και `$top`. Οι συντεταγμένες μπορούν να καθοριστούν ως ακέραιοι σε pixel ή συμβολοσειρές σε ποσοστά (για παράδειγμα `'50%'`). -polygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------- +polygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------- Δημιουργεί ένα πολύγωνο στην εικόνα. ([περισσότερα |https://www.php.net/manual/en/function.imagepolygon]) -rectangle(int $x1, int $y1, int $x2, int $y2, int $col): void .[method] ------------------------------------------------------------------------ -Δημιουργεί ένα ορθογώνιο που ξεκινά από τις καθορισμένες συντεταγμένες. ([περισσότερα |https://www.php.net/manual/en/function.imagerectangle]) +rectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------- +Δημιουργεί ένα ορθογώνιο στις καθορισμένες συντεταγμένες. ([περισσότερα |https://www.php.net/manual/en/function.imagerectangle]) + + +rectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------- +Δημιουργεί ένα ορθογώνιο στις καθορισμένες συντεταγμένες. resize(int|string $width, int|string $height, int $flags=Image::OrSmaller): Image .[method] ------------------------------------------------------------------------------------------- -Κλιμακώνει μια εικόνα, δείτε [περισσότερες πληροφορίες |#Image Resize]. Οι διαστάσεις μπορούν να μεταβιβαστούν ως ακέραιοι αριθμοί σε pixels ή συμβολοσειρές σε ποσοστό (π.χ. `'50%'`). +Αλλάζει το μέγεθος της εικόνας, [περισσότερες πληροφορίες |#Αλλαγή μεγέθους]. Οι διαστάσεις μπορούν να καθοριστούν ως ακέραιοι σε pixel ή συμβολοσειρές σε ποσοστά (για παράδειγμα `'50%'`). -resolution(int $resX=null, int $resY=null): mixed .[method] ------------------------------------------------------------ -Επιτρέπει τον ορισμό και τη λήψη της ανάλυσης μιας εικόνας σε DPI (κουκκίδες ανά ίντσα). Εάν δεν δοθεί καμία από τις προαιρετικές παραμέτρους, η τρέχουσα ανάλυση επιστρέφεται ως πίνακας με ευρετήριο. Αν δοθεί μόνο η τιμή `$resX`, η οριζόντια και η κάθετη ανάλυση καθορίζονται σε αυτή την τιμή. Εάν δίνονται και οι δύο προαιρετικές παράμετροι, η οριζόντια και η κατακόρυφη ανάλυση τίθενται σε αυτές τις τιμές, αντίστοιχα. +resolution(?int $resX=null, ?int $resY=null): mixed .[method] +------------------------------------------------------------- +Ορίζει ή επιστρέφει την ανάλυση της εικόνας σε DPI (κουκκίδες ανά ίντσα). Εάν δεν καθοριστεί καμία από τις προαιρετικές παραμέτρους, η τρέχουσα ανάλυση επιστρέφεται ως ευρετηριασμένος πίνακας. Εάν καθοριστεί μόνο το `$resX`, η οριζόντια και η κάθετη ανάλυση ορίζονται σε αυτήν την τιμή. Εάν καθοριστούν και οι δύο προαιρετικές παράμετροι, η οριζόντια και η κάθετη ανάλυση ορίζονται σε αυτές τις τιμές. -Η ανάλυση χρησιμοποιείται μόνο ως μεταπληροφορία όταν οι εικόνες διαβάζονται και εγγράφονται σε μορφές που υποστηρίζουν τέτοιου είδους πληροφορίες (επί του παρόντος PNG και JPEG). Δεν επηρεάζει καμία λειτουργία σχεδίασης. Η προεπιλεγμένη ανάλυση για νέες εικόνες είναι 96 DPI. ([περισσότερα |https://www.php.net/manual/en/function.imageresolution]) +Η ανάλυση χρησιμοποιείται μόνο ως μετα-πληροφορία όταν οι εικόνες διαβάζονται και γράφονται σε μορφές που υποστηρίζουν αυτό το είδος πληροφορίας (επί του παρόντος PNG και JPEG). Δεν επηρεάζει καμία λειτουργία σχεδίασης. Η προεπιλεγμένη ανάλυση των νέων εικόνων είναι 96 DPI. ([περισσότερα |https://www.php.net/manual/en/function.imageresolution]) rotate(float $angle, int $backgroundColor): Image .[method] ----------------------------------------------------------- -Περιστρέφει την εικόνα χρησιμοποιώντας το δεδομένο `$angle` σε μοίρες. Το κέντρο περιστροφής είναι το κέντρο της εικόνας και η περιστρεφόμενη εικόνα μπορεί να έχει διαφορετικές διαστάσεις από την αρχική εικόνα. ([περισσότερα |https://www.php.net/manual/en/function.imagerotate]) +Περιστρέφει την εικόνα χρησιμοποιώντας την καθορισμένη γωνία `$angle` σε μοίρες. Το κέντρο περιστροφής είναι το κέντρο της εικόνας και η περιστρεφόμενη εικόνα μπορεί να έχει διαφορετικές διαστάσεις από την αρχική εικόνα. ([περισσότερα |https://www.php.net/manual/en/function.imagerotate]) .[note] -Απαιτεί *Επέκταση GD*, οπότε δεν είναι σίγουρο ότι θα λειτουργήσει παντού. +Απαιτεί την παρουσία της *Bundled GD extension*, επομένως μπορεί να μην λειτουργεί παντού. -save(string $file, int $quality=null, int $type=null): void .[method] ---------------------------------------------------------------------- -Αποθηκεύει μια εικόνα σε ένα αρχείο. +save(string $file, ?int $quality=null, ?int $type=null): void .[method] +----------------------------------------------------------------------- +Αποθηκεύει την εικόνα σε ένα αρχείο. -Η ποιότητα συμπίεσης κυμαίνεται στο εύρος 0..100 για τα JPEG (προεπιλογή 85), WEBP (προεπιλογή 80) και AVIF (προεπιλογή 30) και 0..9 για το PNG (προεπιλογή 9). Εάν ο τύπος δεν είναι προφανής από την επέκταση του αρχείου, μπορείτε να τον καθορίσετε χρησιμοποιώντας μία από τις σταθερές `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` και `Image::BMP`. +Η ποιότητα συμπίεσης κυμαίνεται από 0..100 για JPEG (προεπιλογή 85), WEBP (προεπιλογή 80) και AVIF (προεπιλογή 30) και 0..9 για PNG (προεπιλογή 9). Εάν ο τύπος δεν είναι προφανής από την επέκταση του αρχείου, μπορείτε να τον καθορίσετε χρησιμοποιώντας μία από τις σταθερές `ImageType`. saveAlpha(bool $saveflag): void .[method] ----------------------------------------- -Ορίζει τη σημαία που καθορίζει αν θα διατηρούνται οι πλήρεις πληροφορίες του καναλιού άλφα (σε αντίθεση με τη διαφάνεια ενός χρώματος) κατά την αποθήκευση εικόνων PNG. +Ορίζει τη σημαία για το αν θα διατηρηθούν πλήρεις πληροφορίες καναλιού άλφα κατά την αποθήκευση εικόνων PNG (σε αντίθεση με τη μονοχρωματική διαφάνεια). -Το Alphablending πρέπει να είναι απενεργοποιημένο (`alphaBlending(false)`) για να διατηρηθεί το κανάλι άλφα εξαρχής. ([περισσότερα |https://www.php.net/manual/en/function.imagesavealpha]) +Η ανάμειξη άλφα (alphablending) πρέπει να απενεργοποιηθεί (`alphaBlending(false)`) για να διατηρηθεί το κανάλι άλφα στην πρώτη θέση. ([περισσότερα |https://www.php.net/manual/en/function.imagesavealpha]) scale(int $newWidth, int $newHeight=-1, int $mode=IMG_BILINEAR_FIXED): Image .[method] -------------------------------------------------------------------------------------- -Κλιμακώνει μια εικόνα χρησιμοποιώντας τον δεδομένο αλγόριθμο παρεμβολής. ([περισσότερα |https://www.php.net/manual/en/function.imagescale]) +Κλιμακώνει την εικόνα χρησιμοποιώντας τον δεδομένο αλγόριθμο παρεμβολής. ([περισσότερα |https://www.php.net/manual/en/function.imagescale]) -send(int $type=Image::JPEG, int $quality=null): void .[method] --------------------------------------------------------------- -Εκδίδει μια εικόνα στο πρόγραμμα περιήγησης. +send(int $type=ImageType::JPEG, ?int $quality=null): void .[method] +------------------------------------------------------------------- +Εξάγει την εικόνα στον περιηγητή. -Η ποιότητα συμπίεσης κυμαίνεται στο εύρος 0..100 για τα JPEG (προεπιλογή 85), WEBP (προεπιλογή 80) και AVIF (προεπιλογή 30) και 0..9 για το PNG (προεπιλογή 9). Ο τύπος είναι μία από τις σταθερές `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` και `Image::BMP`. +Η ποιότητα συμπίεσης κυμαίνεται από 0..100 για JPEG (προεπιλογή 85), WEBP (προεπιλογή 80) και AVIF (προεπιλογή 30) και 0..9 για PNG (προεπιλογή 9). setBrush(Image $brush): void .[method] -------------------------------------- -Ορίζει την εικόνα πινέλου που θα χρησιμοποιείται από όλες τις λειτουργίες σχεδίασης γραμμών (όπως οι `line()` και `polygon()`) όταν σχεδιάζεται με τα ειδικά χρώματα IMG_COLOR_BRUSHED ή IMG_COLOR_STYLEDBRUSHED. ([περισσότερα |https://www.php.net/manual/en/function.imagesetbrush]) +Ορίζει την εικόνα πινέλου που θα χρησιμοποιηθεί σε όλες τις συναρτήσεις σχεδίασης γραμμών (για παράδειγμα `line()` και `polygon()`) κατά τη σχεδίαση με τα ειδικά χρώματα `IMG_COLOR_BRUSHED` ή `IMG_COLOR_STYLEDBRUSHED`. ([περισσότερα |https://www.php.net/manual/en/function.imagesetbrush]) setClip(int $x1, int $y1, int $x2, int $y2): void .[method] ----------------------------------------------------------- -Ορίζει το τρέχον ορθογώνιο αποκοπής, δηλαδή την περιοχή πέρα από την οποία δεν θα σχεδιάζονται εικονοστοιχεία. ([περισσότερα |https://www.php.net/manual/en/function.imagesetclip]) +Ορίζει την τρέχουσα περιοχή περικοπής, δηλαδή την περιοχή πέρα από την οποία δεν θα σχεδιαστούν pixel. ([περισσότερα |https://www.php.net/manual/en/function.imagesetclip]) setInterpolation(int $method=IMG_BILINEAR_FIXED): void .[method] @@ -623,58 +669,48 @@ setInterpolation(int $method=IMG_BILINEAR_FIXED): void .[method] Ορίζει τη μέθοδο παρεμβολής, η οποία επηρεάζει τις μεθόδους `rotate()` και `affine()`. ([περισσότερα |https://www.php.net/manual/en/function.imagesetinterpolation]) -setPixel(int $x, int $y, int $color): void .[method] ----------------------------------------------------- -Σχεδιάζει ένα εικονοστοιχείο στην καθορισμένη συντεταγμένη. ([περισσότερα |https://www.php.net/manual/en/function.imagesetpixel]) +setPixel(int $x, int $y, ImageColor $color): void .[method] +----------------------------------------------------------- +Σχεδιάζει ένα pixel στην καθορισμένη συντεταγμένη. ([περισσότερα |https://www.php.net/manual/en/function.imagesetpixel]) setStyle(array $style): void .[method] -------------------------------------- -Ορίζει το στυλ που θα χρησιμοποιείται από όλες τις λειτουργίες σχεδίασης γραμμών (όπως οι `line()` και `polygon()`) όταν σχεδιάζονται με το ειδικό χρώμα IMG_COLOR_STYLED ή γραμμές εικόνων με χρώμα IMG_COLOR_STYLEDBRUSHED. ([περισσότερα |https://www.php.net/manual/en/function.imagesetstyle]) +Ορίζει το στυλ που θα χρησιμοποιηθεί από όλες τις συναρτήσεις σχεδίασης γραμμών (για παράδειγμα `line()` και `polygon()`) κατά τη σχεδίαση με το ειδικό χρώμα `IMG_COLOR_STYLED` ή γραμμών εικόνων με το χρώμα `IMG_COLOR_STYLEDBRUSHED`. ([περισσότερα |https://www.php.net/manual/en/function.imagesetstyle]) setThickness(int $thickness): void .[method] -------------------------------------------- -Ορίζει το πάχος των γραμμών που σχεδιάζονται κατά τη σχεδίαση ορθογωνίων, πολυγώνων, τόξων κ.λπ. σε `$thickness` pixels. ([περισσότερα |https://www.php.net/manual/en/function.imagesetthickness]) +Ορίζει το πάχος των γραμμών κατά τη σχεδίαση ορθογωνίων, πολυγώνων, τόξων κ.λπ. σε `$thickness` pixel. ([περισσότερα |https://www.php.net/manual/en/function.imagesetthickness]) setTile(Image $tile): void .[method] ------------------------------------ -Καθορίζει την εικόνα πλακιδίων που θα χρησιμοποιείται από όλες τις λειτουργίες πλήρωσης περιοχών (όπως οι `fill()` και `filledPolygon()`) κατά τη συμπλήρωση με το ειδικό χρώμα IMG_COLOR_TILED. +Ορίζει την εικόνα πλακιδίου που θα χρησιμοποιηθεί σε όλες τις συναρτήσεις γεμίσματος περιοχών (για παράδειγμα `fill()` και `filledPolygon()`) κατά το γέμισμα με το ειδικό χρώμα `IMG_COLOR_TILED`. -Το πλακίδιο είναι μια εικόνα που χρησιμοποιείται για να γεμίσει μια περιοχή με ένα επαναλαμβανόμενο μοτίβο. Οποιαδήποτε εικόνα μπορεί να χρησιμοποιηθεί ως πλακίδιο, και με τον καθορισμό του δείκτη διαφανούς χρώματος της εικόνας του πλακιδίου με το `colorTransparent()`, μπορεί να δημιουργηθεί ένα πλακίδιο που επιτρέπει σε ορισμένα τμήματα της υποκείμενης περιοχής να διαφαίνονται. ([περισσότερα |https://www.php.net/manual/en/function.imagesettile]) +Ένα πλακίδιο είναι μια εικόνα που χρησιμοποιείται για το γέμισμα μιας περιοχής με ένα επαναλαμβανόμενο μοτίβο. Οποιαδήποτε εικόνα μπορεί να χρησιμοποιηθεί ως πλακίδιο, και ορίζοντας τον δείκτη διαφανούς χρώματος της εικόνας πλακιδίου με `colorTransparent()`, μπορεί να δημιουργηθεί ένα πλακίδιο όπου ορισμένα τμήματα της υποκείμενης περιοχής θα φαίνονται. ([περισσότερα |https://www.php.net/manual/en/function.imagesettile]) sharpen(): Image .[method] -------------------------- -Οξύνει λίγο την εικόνα. +Οξύνει την εικόνα. .[note] -Απαιτεί *Επέκταση GD Bundled*, οπότε δεν είναι σίγουρο ότι θα λειτουργήσει παντού. - - -string(int $font, int $x, int $y, string $str, int $col): void .[method] ------------------------------------------------------------------------- -Σχεδιάζει μια συμβολοσειρά στις δεδομένες συντεταγμένες. ([περισσότερα |https://www.php.net/manual/en/function.imagestring]) +Απαιτεί την παρουσία της *Bundled GD extension*, επομένως μπορεί να μην λειτουργεί παντού. -stringUp(int $font, int $x, int $y, string $s, int $col): void .[method] ------------------------------------------------------------------------- -Σχεδιάζει μια συμβολοσειρά κάθετα στις δεδομένες συντεταγμένες. ([περισσότερα |https://www.php.net/manual/en/function.imagestringup]) - - -toString(int $type=Image::JPEG, int $quality=null): string .[method] --------------------------------------------------------------------- -Εξάγει μια εικόνα σε συμβολοσειρά. +toString(int $type=ImageType::JPEG, ?int $quality=null): string .[method] +------------------------------------------------------------------------- +Αποθηκεύει την εικόνα σε μια συμβολοσειρά. -Η ποιότητα συμπίεσης κυμαίνεται στο εύρος 0..100 για τα JPEG (προεπιλογή 85), WEBP (προεπιλογή 80) και AVIF (προεπιλογή 30) και 0..9 για το PNG (προεπιλογή 9). Ο τύπος είναι μία από τις σταθερές `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` και `Image::BMP`. +Η ποιότητα συμπίεσης κυμαίνεται από 0..100 για JPEG (προεπιλογή 85), WEBP (προεπιλογή 80) και AVIF (προεπιλογή 30) και 0..9 για PNG (προεπιλογή 9). trueColorToPalette(bool $dither, int $ncolors): void .[method] -------------------------------------------------------------- -Μετατρέπει μια εικόνα πραγματικού χρώματος σε εικόνα παλέτας. ([περισσότερα |https://www.php.net/manual/en/function.imagetruecolortopalette]) +Μετατρέπει μια εικόνα truecolor σε παλέτα. ([περισσότερα |https://www.php.net/manual/en/function.imagetruecolortopalette]) -ttfText(int $size, int $angle, int $x, int $y, int $color, string $fontfile, string $text): array .[method] ------------------------------------------------------------------------------------------------------------ -Γράφει το δεδομένο κείμενο στην εικόνα χρησιμοποιώντας γραμματοσειρές TrueType. ([περισσότερα |https://www.php.net/manual/en/function.imagettftext]) +ttfText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +----------------------------------------------------------------------------------------------------------------------------------------- +Εξάγει το δεδομένο κείμενο στην εικόνα. ([περισσότερα |https://www.php.net/manual/en/function.imagettftext]) diff --git a/utils/el/iterables.texy b/utils/el/iterables.texy new file mode 100644 index 0000000000..ec7c799edb --- /dev/null +++ b/utils/el/iterables.texy @@ -0,0 +1,170 @@ +Εργασία με επαναλήπτες (Iterators) +********************************** + +.[perex]{data-version:4.0.4} +Η [api:Nette\Utils\Iterables] είναι μια στατική κλάση με συναρτήσεις για την εργασία με επαναλήπτες. Το αντίστοιχό της για πίνακες είναι η [Nette\Utils\Arrays | arrays]. + + +Εγκατάσταση: + +```shell +composer require nette/utils +``` + +Όλα τα παρακάτω παραδείγματα προϋποθέτουν τη δημιουργία ενός ψευδωνύμου: + +```php +use Nette\Utils\Iterables; +``` + + +contains(iterable $iterable, $value): bool .[method] +---------------------------------------------------- + +Αναζητά την καθορισμένη τιμή στον επαναλήπτη. Χρησιμοποιεί αυστηρή σύγκριση (`===`) για την επαλήθευση της αντιστοιχίας. Επιστρέφει `true` εάν η τιμή βρεθεί, διαφορετικά `false`. + +```php +Iterables::contains(new ArrayIterator([1, 2, 3]), 1); // true +Iterables::contains(new ArrayIterator([1, 2, 3]), '1'); // false +``` + +Αυτή η μέθοδος είναι χρήσιμη όταν χρειάζεται να προσδιορίσετε γρήγορα εάν μια συγκεκριμένη τιμή υπάρχει στον επαναλήπτη, χωρίς να χρειάζεται να διατρέξετε όλα τα στοιχεία χειροκίνητα. + + +containsKey(iterable $iterable, $key): bool .[method] +----------------------------------------------------- + +Αναζητά το καθορισμένο κλειδί στον επαναλήπτη. Χρησιμοποιεί αυστηρή σύγκριση (`===`) για την επαλήθευση της αντιστοιχίας. Επιστρέφει `true` εάν το κλειδί βρεθεί, διαφορετικά `false`. + +```php +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 0); // true +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 4); // false +``` + + +every(iterable $iterable, callable $predicate): bool .[method] +-------------------------------------------------------------- + +Επαληθεύει εάν όλα τα στοιχεία του επαναλήπτη πληρούν τη συνθήκη που ορίζεται στο `$predicate`. Η συνάρτηση `$predicate` έχει την υπογραφή `function ($value, $key, iterable $iterable): bool` και πρέπει να επιστρέφει `true` για κάθε στοιχείο, ώστε η μέθοδος `every()` να επιστρέψει `true`. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isBelowThreshold = fn($value) => $value < 40; +$res = Iterables::every($iterator, $isBelowThreshold); // true +``` + +Αυτή η μέθοδος είναι χρήσιμη για την επαλήθευση εάν όλα τα στοιχεία σε μια συλλογή πληρούν μια συγκεκριμένη συνθήκη, για παράδειγμα, εάν όλοι οι αριθμοί είναι μικρότεροι από μια συγκεκριμένη τιμή. + + +filter(iterable $iterable, callable $predicate): Generator .[method] +-------------------------------------------------------------------- + +Δημιουργεί έναν νέο επαναλήπτη που περιέχει μόνο εκείνα τα στοιχεία από τον αρχικό επαναλήπτη που πληρούν τη συνθήκη που ορίζεται στο `$predicate`. Η συνάρτηση `$predicate` έχει την υπογραφή `function ($value, $key, iterable $iterable): bool` και πρέπει να επιστρέφει `true` για τα στοιχεία που πρόκειται να διατηρηθούν. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::filter($iterator, fn($v) => $v < 3); +// 1, 2 +``` + +Η μέθοδος χρησιμοποιεί έναν γεννήτορα (Generator), πράγμα που σημαίνει ότι το φιλτράρισμα πραγματοποιείται σταδιακά κατά τη διάσχιση του αποτελέσματος. Αυτό είναι αποδοτικό από άποψη μνήμης και επιτρέπει την επεξεργασία ακόμη και πολύ μεγάλων συλλογών. Εάν δεν διατρέξετε όλα τα στοιχεία του τελικού επαναλήπτη, εξοικονομείτε υπολογιστική ισχύ, επειδή δεν επεξεργάζονται όλα τα στοιχεία του αρχικού επαναλήπτη. + + +first(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------------- + +Επιστρέφει το πρώτο στοιχείο του επαναλήπτη. Εάν καθοριστεί το `$predicate`, επιστρέφει το πρώτο στοιχείο που πληροί τη δεδομένη συνθήκη. Η συνάρτηση `$predicate` έχει την υπογραφή `function ($value, $key, iterable $iterable): bool`. Εάν δεν βρεθεί κανένα στοιχείο που να ικανοποιεί τη συνθήκη, καλείται η συνάρτηση `$else` (εάν έχει καθοριστεί) και επιστρέφεται το αποτέλεσμά της. Εάν το `$else` δεν έχει καθοριστεί, επιστρέφεται `null`. + +```php +Iterables::first(new ArrayIterator([1, 2, 3])); // 1 +Iterables::first(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 3 +Iterables::first(new ArrayIterator([])); // null +Iterables::first(new ArrayIterator([]), else: fn() => false); // false +``` + +Αυτή η μέθοδος είναι χρήσιμη όταν χρειάζεται να λάβετε γρήγορα το πρώτο στοιχείο μιας συλλογής ή το πρώτο στοιχείο που πληροί μια συγκεκριμένη συνθήκη, χωρίς να χρειάζεται να διατρέξετε ολόκληρη τη συλλογή χειροκίνητα. + + +firstKey(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +---------------------------------------------------------------------------------------------- + +Επιστρέφει το κλειδί του πρώτου στοιχείου του επαναλήπτη. Εάν καθοριστεί το `$predicate`, επιστρέφει το κλειδί του πρώτου στοιχείου που πληροί τη δεδομένη συνθήκη. Η συνάρτηση `$predicate` έχει την υπογραφή `function ($value, $key, iterable $iterable): bool`. Εάν δεν βρεθεί κανένα στοιχείο που να ικανοποιεί τη συνθήκη, καλείται η συνάρτηση `$else` (εάν έχει καθοριστεί) και επιστρέφεται το αποτέλεσμά της. Εάν το `$else` δεν έχει καθοριστεί, επιστρέφεται `null`. + +```php +Iterables::firstKey(new ArrayIterator([1, 2, 3])); // 0 +Iterables::firstKey(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 2 +Iterables::firstKey(new ArrayIterator(['a' => 1, 'b' => 2])); // 'a' +Iterables::firstKey(new ArrayIterator([])); // null +``` + + +map(iterable $iterable, callable $transformer): Generator .[method] +------------------------------------------------------------------- + +Δημιουργεί έναν νέο επαναλήπτη εφαρμόζοντας τη συνάρτηση `$transformer` σε κάθε στοιχείο του αρχικού επαναλήπτη. Η συνάρτηση `$transformer` έχει την υπογραφή `function ($value, $key, iterable $iterable): mixed` και η τιμή επιστροφής της χρησιμοποιείται ως η νέα τιμή του στοιχείου. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::map($iterator, fn($v) => $v * 2); +// 2, 4, 6 +``` + +Η μέθοδος χρησιμοποιεί έναν γεννήτορα (Generator), πράγμα που σημαίνει ότι ο μετασχηματισμός πραγματοποιείται σταδιακά κατά τη διάσχιση του αποτελέσματος. Αυτό είναι αποδοτικό από άποψη μνήμης και επιτρέπει την επεξεργασία ακόμη και πολύ μεγάλων συλλογών. Εάν δεν διατρέξετε όλα τα στοιχεία του τελικού επαναλήπτη, εξοικονομείτε υπολογιστική ισχύ, επειδή δεν επεξεργάζονται όλα τα στοιχεία του αρχικού επαναλήπτη. + + +mapWithKeys(iterable $iterable, callable $transformer): Generator .[method] +--------------------------------------------------------------------------- + +Δημιουργεί έναν νέο επαναλήπτη μετασχηματίζοντας τις τιμές και τα κλειδιά του αρχικού επαναλήπτη. Η συνάρτηση `$transformer` έχει την υπογραφή `function ($value, $key, iterable $iterable): ?array{$newKey, $newValue}`. Εάν η `$transformer` επιστρέψει `null`, το στοιχείο παραλείπεται. Για τα διατηρημένα στοιχεία, το πρώτο στοιχείο του επιστρεφόμενου πίνακα χρησιμοποιείται ως το νέο κλειδί και το δεύτερο στοιχείο ως η νέα τιμή. + +```php +$iterator = new ArrayIterator(['a' => 1, 'b' => 2]); +$iterator = Iterables::mapWithKeys($iterator, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +Όπως και η `map()`, αυτή η μέθοδος χρησιμοποιεί έναν γεννήτορα (Generator) για σταδιακή επεξεργασία και αποδοτική διαχείριση μνήμης. Αυτό επιτρέπει την εργασία με μεγάλες συλλογές και την εξοικονόμηση υπολογιστικής ισχύος κατά τη μερική διάσχιση του αποτελέσματος. + + +memoize(iterable $iterable): IteratorAggregate .[method] +-------------------------------------------------------- + +Δημιουργεί ένα περιτύλιγμα γύρω από τον επαναλήπτη, το οποίο κατά τη διάρκεια της επανάληψης αποθηκεύει προσωρινά τα κλειδιά και τις τιμές του στην κρυφή μνήμη (cache). Αυτό επιτρέπει την επαναλαμβανόμενη επανάληψη των δεδομένων χωρίς την ανάγκη εκ νέου διάσχισης της αρχικής πηγής δεδομένων. + +```php +$iterator = /* δεδομένα που δεν μπορούν να επαναληφθούν περισσότερες από μία φορές */ +$memoized = Iterables::memoize($iterator); +// Τώρα μπορείτε να επαναλάβετε το $memoized πολλές φορές χωρίς απώλεια δεδομένων +``` + +Αυτή η μέθοδος είναι χρήσιμη σε περιπτώσεις όπου χρειάζεται να διατρέξετε το ίδιο σύνολο δεδομένων πολλές φορές, αλλά ο αρχικός επαναλήπτης δεν επιτρέπει την επαναλαμβανόμενη επανάληψη ή η επαναλαμβανόμενη διάσχιση θα ήταν δαπανηρή (π.χ. κατά την ανάγνωση δεδομένων από μια βάση δεδομένων ή ένα αρχείο). + + +some(iterable $iterable, callable $predicate): bool .[method] +------------------------------------------------------------- + +Επαληθεύει εάν τουλάχιστον ένα στοιχείο του επαναλήπτη πληροί τη συνθήκη που ορίζεται στο `$predicate`. Η συνάρτηση `$predicate` έχει την υπογραφή `function ($value, $key, iterable $iterable): bool` και πρέπει να επιστρέφει `true` για τουλάχιστον ένα στοιχείο, ώστε η μέθοδος `some()` να επιστρέψει `true`. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isEven = fn($value) => $value % 2 === 0; +$res = Iterables::some($iterator, $isEven); // true +``` + +Αυτή η μέθοδος είναι χρήσιμη για τη γρήγορη επαλήθευση εάν υπάρχει τουλάχιστον ένα στοιχείο στη συλλογή που πληροί μια συγκεκριμένη συνθήκη, για παράδειγμα, εάν η συλλογή περιέχει τουλάχιστον έναν άρτιο αριθμό. + +Δείτε [#every()]. + + +toIterator(iterable $iterable): Iterator .[method] +-------------------------------------------------- + +Μετατρέπει οποιοδήποτε επαναλήψιμο αντικείμενο (array, Traversable) σε Iterator. Εάν η είσοδος είναι ήδη Iterator, την επιστρέφει χωρίς αλλαγή. + +```php +$array = [1, 2, 3]; +$iterator = Iterables::toIterator($array); +// Τώρα έχετε έναν Iterator αντί για έναν πίνακα +``` + +Αυτή η μέθοδος είναι χρήσιμη όταν χρειάζεται να διασφαλίσετε ότι έχετε διαθέσιμο έναν Iterator, ανεξάρτητα από τον τύπο των δεδομένων εισόδου. Αυτό μπορεί να είναι χρήσιμο κατά τη δημιουργία συναρτήσεων που λειτουργούν με διάφορους τύπους επαναλήψιμων δεδομένων. diff --git a/utils/el/json.texy b/utils/el/json.texy index c7679f3dc5..79eba0eca7 100644 --- a/utils/el/json.texy +++ b/utils/el/json.texy @@ -1,8 +1,8 @@ -Λειτουργίες JSON -**************** +Εργασία με JSON +*************** .[perex] -[api:Nette\Utils\Json] είναι μια στατική κλάση με συναρτήσεις κωδικοποίησης και αποκωδικοποίησης JSON. Αντιμετωπίζει ευπάθειες σε διάφορες εκδόσεις της PHP και πετάει εξαιρέσεις σε σφάλματα. +Η [api:Nette\Utils\Json] είναι μια στατική κλάση με συναρτήσεις για την κωδικοποίηση και αποκωδικοποίηση της μορφής JSON. Αντιμετωπίζει τις ευπάθειες διαφόρων εκδόσεων της PHP και προκαλεί εξαιρέσεις σε περίπτωση σφαλμάτων. Εγκατάσταση: @@ -11,44 +11,44 @@ composer require nette/utils ``` -Όλα τα παραδείγματα προϋποθέτουν ότι έχει οριστεί το ακόλουθο ψευδώνυμο κλάσης: +Όλα τα παρακάτω παραδείγματα προϋποθέτουν τη δημιουργία ενός ψευδωνύμου: ```php use Nette\Utils\Json; ``` -Χρήση .[#toc-usage] -=================== +Χρήση +===== encode(mixed $value, bool $pretty=false, bool $asciiSafe=false, bool $htmlSafe=false, bool $forceObjects=false): string .[method] --------------------------------------------------------------------------------------------------------------------------------- -Μετατρέπει το `$value` σε μορφή JSON. +Μετατρέπει την `$value` σε μορφή JSON. -Όταν έχει οριστεί το `$pretty`, μορφοποιεί το JSON για ευκολότερη ανάγνωση και σαφήνεια: +Όταν το `$pretty` οριστεί σε `true`, μορφοποιεί το JSON για ευκολότερη ανάγνωση και σαφήνεια: ```php Json::encode($value); // επιστρέφει JSON -Json::encode($value, pretty: true); // επιστρέφει πιο ξεκάθαρο JSON +Json::encode($value, pretty: true); // επιστρέφει πιο ευανάγνωστο JSON ``` -Όταν έχει οριστεί το `$asciiSafe`, παράγει έξοδο ASCII, δηλαδή αντικαθιστά τους χαρακτήρες unicode με ακολουθίες `\uxxxx`: +Με το `$asciiSafe` ορισμένο σε `true`, δημιουργεί έξοδο σε ASCII, δηλαδή αντικαθιστά τους χαρακτήρες unicode με ακολουθίες `\uxxxx`: ```php Json::encode('žluťoučký', asciiSafe: true); // '"\u017elu\u0165ou\u010dk\u00fd"' ``` -Η παράμετρος `$htmlSafe` διασφαλίζει ότι η έξοδος δεν περιέχει χαρακτήρες με ειδική σημασία στην HTML: +Η παράμετρος `$htmlSafe` ορισμένη σε `true` διασφαλίζει ότι η έξοδος δεν θα περιέχει χαρακτήρες που έχουν ειδική σημασία στην HTML: ```php Json::encode('onesendJson($data)`, η οποία μπορεί να κληθεί, για παράδειγμα, στη μέθοδο `action*()`, βλέπε [Αποστολή απάντησης |application:presenters#Sending a Response]. +Μπορείτε να χρησιμοποιήσετε τη μέθοδο `$this->sendJson($data)`, την οποία μπορείτε να καλέσετε, για παράδειγμα, στη μέθοδο `action*()`, δείτε [Αποστολή απάντησης |application:presenters#Αποστολή απάντησης]. diff --git a/utils/el/paginator.texy b/utils/el/paginator.texy index 7f6257008c..ba9ecfde27 100644 --- a/utils/el/paginator.texy +++ b/utils/el/paginator.texy @@ -1,8 +1,8 @@ -Σελιδοποιητής -************* +Paginator (Σελιδοποιητής) +************************* .[perex] -Χρειάζεται να σελιδοποιήσετε μια καταχώριση δεδομένων; Επειδή τα μαθηματικά πίσω από την σελιδοποίηση μπορεί να είναι δύσκολα, το [api:Nette\Utils\Paginator] θα σας βοηθήσει. +Χρειάζεται να σελιδοποιήσετε την εμφάνιση δεδομένων; Επειδή τα μαθηματικά της σελιδοποίησης μπορεί να είναι δύσκολα, η [api:Nette\Utils\Paginator] θα σας βοηθήσει με αυτό. Εγκατάσταση: @@ -11,38 +11,38 @@ composer require nette/utils ``` -Ας δημιουργήσουμε ένα αντικείμενο σελιδοποίησης και ας ορίσουμε τις βασικές πληροφορίες για αυτό: +Δημιουργούμε ένα αντικείμενο σελιδοποιητή και ορίζουμε τις βασικές του πληροφορίες: ```php $paginator = new Nette\Utils\Paginator; -$paginator->setPage(1); // ο αριθμός της τρέχουσας σελίδας (με αρίθμηση από το 1) -$paginator->setItemsPerPage(30); // ο αριθμός των εγγραφών ανά σελίδα -$paginator->setItemCount(356); // ο συνολικός αριθμός εγγραφών (εάν είναι διαθέσιμος) +$paginator->setPage(1); // αριθμός της τρέχουσας σελίδας +$paginator->setItemsPerPage(30); // αριθμός στοιχείων ανά σελίδα +$paginator->setItemCount(356); // συνολικός αριθμός στοιχείων, εάν είναι γνωστός ``` -Μπορούμε να το αλλάξουμε χρησιμοποιώντας το `setBase()`: +Οι σελίδες αριθμούνται από το 1. Μπορούμε να το αλλάξουμε χρησιμοποιώντας το `setBase()`: ```php -$paginator->setBase(0); // αριθμημένα από το 0 +$paginator->setBase(0); // αριθμούμε από το 0 ``` -Το αντικείμενο θα παρέχει τώρα όλες τις βασικές πληροφορίες που είναι χρήσιμες για τη δημιουργία ενός paginator. Μπορείτε, για παράδειγμα, να το περάσετε σε ένα πρότυπο και να το χρησιμοποιήσετε εκεί. +Το αντικείμενο παρέχει τώρα όλες τις βασικές πληροφορίες χρήσιμες κατά τη δημιουργία ενός σελιδοποιητή. Μπορείτε, για παράδειγμα, να το περάσετε σε ένα πρότυπο και να το χρησιμοποιήσετε εκεί. ```php -$paginator->isFirst(); // Αυτή είναι η πρώτη σελίδα; -$paginator->isLast(); // Είναι αυτή η τελευταία σελίδα; -$paginator->getPage(); // Αριθμός τρέχουσας σελίδας -$paginator->getFirstPage(); // ο αριθμός της πρώτης σελίδας -$paginator->getLastPage(); // ο αριθμός της τελευταίας σελίδας -$paginator->getFirstItemOnPage(); // αύξων αριθμός του πρώτου στοιχείου της σελίδας -$paginator->getLastItemOnPage(); // αύξων αριθμός του τελευταίου στοιχείου της σελίδας -$paginator->getPageIndex(); // αριθμός τρέχουσας σελίδας εάν αριθμείται από το 0 -$paginator->getPageCount(); // ο συνολικός αριθμός σελίδων -$paginator->getItemsPerPage(); // ο αριθμός των εγγραφών ανά σελίδα -$paginator->getItemCount(); // ο συνολικός αριθμός εγγραφών (εάν είναι διαθέσιμος) +$paginator->isFirst(); // είμαστε στην πρώτη σελίδα; +$paginator->isLast(); // είμαστε στην τελευταία σελίδα; +$paginator->getPage(); // αριθμός της τρέχουσας σελίδας +$paginator->getFirstPage(); // αριθμός της πρώτης σελίδας +$paginator->getLastPage(); // αριθμός της τελευταίας σελίδας +$paginator->getFirstItemOnPage(); // σειριακός αριθμός του πρώτου στοιχείου στη σελίδα +$paginator->getLastItemOnPage(); // σειριακός αριθμός του τελευταίου στοιχείου στη σελίδα +$paginator->getPageIndex(); // αριθμός της τρέχουσας σελίδας που αριθμείται από το 0 +$paginator->getPageCount(); // συνολικός αριθμός σελίδων +$paginator->getItemsPerPage(); // αριθμός στοιχείων ανά σελίδα +$paginator->getItemCount(); // συνολικός αριθμός στοιχείων, εάν είναι γνωστός ``` -Ο σελιδοποιητής θα βοηθήσει στη διαμόρφωση του ερωτήματος SQL. Οι μέθοδοι `getLength()` και `getOffset()` επιστρέφουν τις τιμές που μπορείτε να χρησιμοποιήσετε στις ρήτρες LIMIT και OFFSET: +Ο σελιδοποιητής βοηθά στη διατύπωση ενός ερωτήματος SQL. Οι μέθοδοι `getLength()` και `getOffset()` επιστρέφουν τιμές που χρησιμοποιούμε στις ρήτρες `LIMIT` και `OFFSET`: ```php $result = $database->query( @@ -52,7 +52,7 @@ $result = $database->query( ); ``` -Εάν πρέπει να κάνετε σελιδοποίηση με αντίστροφη σειρά, δηλ. 1 αντιστοιχεί στην υψηλότερη μετατόπιση, μπορείτε να χρησιμοποιήσετε τη χρήση `getCountdownOffset()`: +Εάν χρειάζεται να σελιδοποιήσουμε με αντίστροφη σειρά, δηλαδή η σελίδα αρ. 1 αντιστοιχεί στην υψηλότερη μετατόπιση, χρησιμοποιούμε το `getCountdownOffset()`: ```php $result = $database->query( @@ -62,4 +62,4 @@ $result = $database->query( ); ``` -Ένα παράδειγμα χρήσης στην εφαρμογή μπορείτε να βρείτε στο βιβλίο μαγειρικής [Paginating Database Results (Σελιδοποίηση αποτελεσμάτων βάσης δεδομένων |best-practices:pagination]). +Ένα παράδειγμα χρήσης στην εφαρμογή μπορείτε να βρείτε στο βιβλίο συνταγών [Σελιδοποίηση αποτελεσμάτων βάσης δεδομένων | best-practices:pagination]. diff --git a/utils/el/random.texy b/utils/el/random.texy index 144dc66d53..127bd49cde 100644 --- a/utils/el/random.texy +++ b/utils/el/random.texy @@ -1,8 +1,8 @@ -Γεννήτρια τυχαίων χορδών -************************ +Δημιουργία τυχαίων συμβολοσειρών +******************************** .[perex] -[api:Nette\Utils\Random] είναι μια στατική κλάση για τη δημιουργία κρυπτογραφικά ασφαλών ψευδοτυχαίων συμβολοσειρών. +Η [api:Nette\Utils\Random] είναι μια στατική κλάση για τη δημιουργία κρυπτογραφικά ασφαλών ψευδοτυχαίων συμβολοσειρών. Εγκατάσταση: @@ -15,7 +15,7 @@ composer require nette/utils generate(int $length=10, string $charlist=`'0-9a-z'`): string .[method] ----------------------------------------------------------------------- -Δημιουργεί μια τυχαία συμβολοσειρά δεδομένου μήκους από χαρακτήρες που καθορίζονται στο δεύτερο όρισμα. Υποστηρίζει διαστήματα, όπως `0-9` ή `A-Z`. +Δημιουργεί μια τυχαία συμβολοσειρά του δεδομένου μήκους από τους χαρακτήρες που καθορίζονται από την παράμετρο `$charlist`. Μπορείτε επίσης να χρησιμοποιήσετε διαστήματα γραμμένα όπως `0-9`. ```php use Nette\Utils\Random; diff --git a/utils/el/reflection.texy b/utils/el/reflection.texy index cc2a8817ae..a38b1b02d4 100644 --- a/utils/el/reflection.texy +++ b/utils/el/reflection.texy @@ -1,8 +1,8 @@ -Αναστοχασμός PHP -**************** +PHP Reflection (Αντανάκλαση) +**************************** .[perex] -[api:Nette\Utils\Reflection] είναι μια στατική κλάση με χρήσιμες λειτουργίες για την αντανάκλαση της PHP. Σκοπός της είναι να διορθώσει ελαττώματα σε εγγενείς κλάσεις και να ενοποιήσει τη συμπεριφορά σε διαφορετικές εκδόσεις της PHP. +Η [api:Nette\Utils\Reflection] είναι μια στατική κλάση με χρήσιμες συναρτήσεις για PHP reflection. Ο σκοπός της είναι να διορθώνει τις ελλείψεις των εγγενών κλάσεων και να ενοποιεί τη συμπεριφορά σε διάφορες εκδόσεις της PHP. Εγκατάσταση: @@ -11,7 +11,7 @@ composer require nette/utils ``` -Όλα τα παραδείγματα προϋποθέτουν ότι έχει οριστεί το ακόλουθο ψευδώνυμο κλάσης: +Όλα τα παρακάτω παραδείγματα προϋποθέτουν τη δημιουργία ενός ψευδωνύμου: ```php use Nette\Utils\Reflection; @@ -21,13 +21,13 @@ use Nette\Utils\Reflection; areCommentsAvailable(): bool .[method] -------------------------------------- -Βρίσκει αν η αντανάκλαση έχει πρόσβαση στα σχόλια του PHPdoc. Τα σχόλια μπορεί να μην είναι διαθέσιμα λόγω της κρυφής μνήμης κώδικα, δείτε για παράδειγμα την οδηγία [opcache.save-comments |https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments]. +Ελέγχει εάν η reflection έχει πρόσβαση στα σχόλια PHPdoc. Τα σχόλια ενδέχεται να μην είναι διαθέσιμα λόγω της opcode cache, δείτε για παράδειγμα την οδηγία [opcache.save-comments |https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments]. expandClassName(string $name, ReflectionClass $context): string .[method] ------------------------------------------------------------------------- -Επεκτείνει το `$name` της κλάσης σε πλήρες όνομα στο πλαίσιο του `$context`, δηλαδή στο πλαίσιο του χώρου ονομάτων της και των καθορισμένων ψευδώνυμα. Έτσι, επιστρέφει το πώς θα καταλάβαινε ο αναλυτής της PHP το `$name` αν ήταν γραμμένο στο σώμα του `$context`. +Επεκτείνει το όνομα της κλάσης `$name` στο πλήρες όνομά της στο πλαίσιο της κλάσης `$context`, δηλαδή στο πλαίσιο του χώρου ονομάτων της και των ορισμένων ψευδωνύμων. Δηλαδή, στην πραγματικότητα λέει πώς ο αναλυτής της PHP θα κατανοούσε το `$name` εάν ήταν γραμμένο στο σώμα της κλάσης `$context`. ```php namespace Foo; @@ -47,9 +47,9 @@ Reflection::expandClassName('Baz', $context); // 'Foo\Baz' getMethodDeclaringMethod(ReflectionMethod $method): ReflectionMethod .[method] ------------------------------------------------------------------------------ -Επιστρέφει μια αντανάκλαση μιας μεθόδου που περιέχει μια δήλωση του `$method`. Συνήθως, κάθε μέθοδος είναι η δική της δήλωση, αλλά το σώμα της μεθόδου μπορεί επίσης να βρίσκεται στο γνώρισμα και με διαφορετικό όνομα. +Επιστρέφει τη reflection της μεθόδου που περιέχει τη δήλωση της μεθόδου `$method`. Συνήθως, κάθε μέθοδος είναι η δική της δήλωση, αλλά το σώμα της μεθόδου μπορεί επίσης να βρίσκεται σε ένα trait και με διαφορετικό όνομα. -Επειδή η PHP δεν παρέχει αρκετές πληροφορίες για τον προσδιορισμό της πραγματικής δήλωσης, η Nette χρησιμοποιεί τις δικές της ευρετικές μεθόδους, οι οποίες **θα πρέπει να είναι** αξιόπιστες. +Επειδή η PHP δεν παρέχει επαρκείς πληροφορίες με τις οποίες μπορεί να προσδιοριστεί η πραγματική δήλωση, η Nette χρησιμοποιεί τη δική της ευρετική μέθοδο, η οποία **θα έπρεπε να είναι** αξιόπιστη. ```php trait DemoTrait @@ -76,9 +76,9 @@ Reflection::getMethodDeclaringMethod($method); // ReflectionMethod('DemoTrait::f getPropertyDeclaringClass(ReflectionProperty $prop): ReflectionClass .[method] ------------------------------------------------------------------------------ -Επιστρέφει μια αντανάκλαση μιας κλάσης ή ενός γνωρίσματος που περιέχει μια δήλωση της ιδιότητας `$prop`. Η ιδιότητα μπορεί επίσης να δηλωθεί στο trait. +Επιστρέφει τη reflection της κλάσης ή του trait που περιέχει τη δήλωση της ιδιότητας `$prop`. Η ιδιότητα μπορεί επίσης να δηλωθεί σε ένα trait. -Επειδή η PHP δεν παρέχει αρκετές πληροφορίες για τον προσδιορισμό της πραγματικής δήλωσης, η Nette χρησιμοποιεί τις δικές της ευρετικές μεθόδους, οι οποίες **δεν** είναι αξιόπιστες. +Επειδή η PHP δεν παρέχει επαρκείς πληροφορίες με τις οποίες μπορεί να προσδιοριστεί η πραγματική δήλωση, η Nette χρησιμοποιεί τη δική της ευρετική μέθοδο, η οποία **δεν είναι** αξιόπιστη. ```php trait DemoTrait @@ -100,7 +100,7 @@ Reflection::getPropertyDeclaringClass($prop); // ReflectionClass('DemoTrait') isBuiltinType(string $type): bool .[method deprecated] ------------------------------------------------------ -Καθορίζει αν το `$type` είναι ενσωματωμένος τύπος της PHP. Διαφορετικά, είναι το όνομα της κλάσης. +Ελέγχει εάν το `$type` είναι ενσωματωμένος τύπος της PHP. Διαφορετικά, είναι όνομα κλάσης. ```php Reflection::isBuiltinType('string'); // true @@ -108,13 +108,13 @@ Reflection::isBuiltinType('Foo'); // false ``` .[note] -Χρησιμοποιήστε την [Nette\Utils\Validator::isBuiltinType() |validators#isBuiltinType]. +Χρησιμοποιήστε το [Nette\Utils\Validator::isBuiltinType() |validators#isBuiltinType]. toString($reflection): string .[method] --------------------------------------- -Μετατρέπει μια αντανάκλαση σε μια συμβολοσειρά αναγνώσιμη από τον άνθρωπο. +Μετατρέπει τη reflection σε μια συμβολοσειρά κατανοητή από τον άνθρωπο. ```php $func = new ReflectionFunction('func'); diff --git a/utils/el/smartobject.texy b/utils/el/smartobject.texy index 229a82276f..02bb8a6149 100644 --- a/utils/el/smartobject.texy +++ b/utils/el/smartobject.texy @@ -1,8 +1,8 @@ -SmartObject και StaticClass -*************************** +SmartObject +*********** .[perex] -Το SmartObject προσθέτει υποστήριξη για *ιδιότητες* στις κλάσεις PHP. Το StaticClass χρησιμοποιείται για να δηλώσει στατικές κλάσεις. +Το SmartObject βελτίωνε τη συμπεριφορά των αντικειμένων στην PHP για χρόνια. Από την έκδοση PHP 8.4, όλες οι λειτουργίες του είναι πλέον μέρος της ίδιας της PHP, ολοκληρώνοντας έτσι την ιστορική του αποστολή να είναι πρωτοπόρος της σύγχρονης αντικειμενοστρεφούς προσέγγισης στην PHP. Εγκατάσταση: @@ -11,19 +11,64 @@ SmartObject και StaticClass composer require nette/utils ``` +Το SmartObject δημιουργήθηκε το 2007 ως μια επαναστατική λύση στις ελλείψεις του τότε αντικειμενοστρεφούς μοντέλου της PHP. Σε μια εποχή που η PHP υπέφερε από πολλά προβλήματα με τον αντικειμενοστρεφή σχεδιασμό, έφερε σημαντική βελτίωση και απλοποίηση της εργασίας για τους προγραμματιστές. Έγινε ένα θρυλικό μέρος του Nette framework. Προσέφερε λειτουργικότητα που η PHP απέκτησε πολλά χρόνια αργότερα - από τον έλεγχο πρόσβασης στις ιδιότητες των αντικειμένων έως την εξελιγμένη συντακτική ζάχαρη. Με την έλευση της PHP 8.4, ολοκλήρωσε την ιστορική του αποστολή, καθώς όλες οι λειτουργίες του έγιναν εγγενές μέρος της γλώσσας. Προηγήθηκε της εξέλιξης της PHP κατά αξιοσημείωτα 17 χρόνια. -Ιδιότητες, Getters και Setters .[#toc-properties-getters-and-setters] -===================================================================== +Τεχνικά, το SmartObject πέρασε από μια ενδιαφέρουσα εξέλιξη. Αρχικά υλοποιήθηκε ως κλάση `Nette\Object`, από την οποία άλλες κλάσεις κληρονομούσαν την απαραίτητη λειτουργικότητα. Μια θεμελιώδης αλλαγή ήρθε με την PHP 5.4, η οποία έφερε υποστήριξη για traits. Αυτό επέτρεψε τη μετατροπή του σε μορφή trait `Nette\SmartObject`, η οποία έφερε μεγαλύτερη ευελιξία - οι προγραμματιστές μπορούσαν να χρησιμοποιήσουν τη λειτουργικότητα ακόμη και σε κλάσεις που ήδη κληρονομούσαν από άλλη κλάση. Ενώ η αρχική κλάση `Nette\Object` εξαφανίστηκε με την έλευση της PHP 7.2 (η οποία απαγόρευσε την ονομασία κλάσεων με τη λέξη `Object`), το trait `Nette\SmartObject` ζει ακόμα. -Στις σύγχρονες αντικειμενοστραφείς γλώσσες (π.χ. C#, Python, Ruby, JavaScript), ο όρος *ιδιότητα* αναφέρεται σε [ειδικά μέλη των κλάσεων |https://en.wikipedia.org/wiki/Property_(programming)] που μοιάζουν με μεταβλητές αλλά στην πραγματικότητα αντιπροσωπεύονται από μεθόδους. Όταν η τιμή αυτής της "μεταβλητής" εκχωρείται ή διαβάζεται, καλείται η αντίστοιχη μέθοδος (που ονομάζεται getter ή setter). Αυτό είναι κάτι πολύ βολικό, μας δίνει πλήρη έλεγχο της πρόσβασης στις μεταβλητές. Μπορούμε να επικυρώσουμε την είσοδο ή να δημιουργήσουμε αποτελέσματα μόνο όταν διαβάζεται η ιδιότητα. +Ας δούμε τα χαρακτηριστικά που κάποτε προσέφεραν το `Nette\Object` και αργότερα το `Nette\SmartObject`. Κάθε μία από αυτές τις λειτουργίες στην εποχή της αντιπροσώπευε ένα σημαντικό βήμα προς τα εμπρός στον τομέα του αντικειμενοστρεφούς προγραμματισμού στην PHP. -Οι ιδιότητες της PHP δεν υποστηρίζονται, αλλά το trait `Nette\SmartObject` μπορεί να τις μιμηθεί. Πώς να το χρησιμοποιήσετε; -- Προσθέστε ένα σχόλιο στην κλάση με τη μορφή `@property $xyz` -- Δημιουργήστε έναν getter με όνομα `getXyz()` ή `isXyz()`, έναν setter με όνομα `setXyz()` -- Ο getter και ο setter πρέπει να είναι *δημόσιος* ή *προστατευμένος* και είναι προαιρετικοί, οπότε μπορεί να υπάρχει μια ιδιότητα *read-only* ή *write-only*. +Συνεπείς καταστάσεις σφάλματος +------------------------------ +Ένα από τα πιο πιεστικά προβλήματα της πρώιμης PHP ήταν η ασυνεπής συμπεριφορά κατά την εργασία με αντικείμενα. Το `Nette\Object` έφερε τάξη και προβλεψιμότητα σε αυτό το χάος. Ας δούμε πώς έμοιαζε η αρχική συμπεριφορά της PHP: + +```php +echo $obj->undeclared; // E_NOTICE, αργότερα E_WARNING +$obj->undeclared = 1; // περνάει σιωπηλά χωρίς αναφορά +$obj->unknownMethod(); // Fatal error (μη συλλαμβανόμενο με try/catch) +``` + +Το Fatal error τερμάτιζε την εφαρμογή χωρίς καμία δυνατότητα αντίδρασης. Η σιωπηλή εγγραφή σε ανύπαρκτα μέλη χωρίς προειδοποίηση μπορούσε να οδηγήσει σε σοβαρά σφάλματα που ήταν δύσκολο να εντοπιστούν. Το `Nette\Object` συνέλαβε όλες αυτές τις περιπτώσεις και δημιούργησε την εξαίρεση `MemberAccessException`, η οποία επέτρεψε στους προγραμματιστές να αντιδράσουν στα σφάλματα και να τα επιλύσουν. + +```php +echo $obj->undeclared; // δημιουργεί Nette\MemberAccessException +$obj->undeclared = 1; // δημιουργεί Nette\MemberAccessException +$obj->unknownMethod(); // δημιουργεί Nette\MemberAccessException +``` + +Από την PHP 7.0, η γλώσσα δεν προκαλεί πλέον μη συλλαμβανόμενα fatal errors και από την PHP 8.2, η πρόσβαση σε μη δηλωμένα μέλη θεωρείται σφάλμα. + + +Βοήθεια "Μήπως εννοούσατε;" +--------------------------- +Το `Nette\Object` ήρθε με μια πολύ ευχάριστη λειτουργία: έξυπνη βοήθεια για τυπογραφικά λάθη. Όταν ένας προγραμματιστής έκανε λάθος στο όνομα μιας μεθόδου ή μεταβλητής, όχι μόνο ανέφερε το σφάλμα, αλλά προσέφερε επίσης ένα χέρι βοήθειας με τη μορφή πρότασης του σωστού ονόματος. Αυτό το εικονικό μήνυμα, γνωστό ως "did you mean?", εξοικονόμησε ώρες αναζήτησης τυπογραφικών λαθών στους προγραμματιστές: + +```php +class Foo extends Nette\Object +{ + public static function from($var) + { + } +} + +$foo = Foo::form($var); +// δημιουργεί Nette\MemberAccessException +// "Call to undefined static method Foo::form(), did you mean from()?" +``` + +Η σημερινή PHP δεν έχει καμία μορφή "did you mean?", αλλά αυτό το συμπλήρωμα μπορεί να προστεθεί στα σφάλματα από το [Tracy|tracy:]. Και μπορεί ακόμη και να [διορθώσει αυτόματα |tracy:open-files-in-ide#Παραδείγματα] τέτοια σφάλματα. + + +Ιδιότητες με ελεγχόμενη πρόσβαση +-------------------------------- +Μια σημαντική καινοτομία που έφερε το SmartObject στην PHP ήταν οι ιδιότητες με ελεγχόμενη πρόσβαση. Αυτή η έννοια, κοινή σε γλώσσες όπως η C# ή η Python, επέτρεψε στους προγραμματιστές να ελέγχουν κομψά την πρόσβαση στα δεδομένα του αντικειμένου και να διασφαλίζουν τη συνέπειά τους. Οι ιδιότητες είναι ένα ισχυρό εργαλείο του αντικειμενοστρεφούς προγραμματισμού. Λειτουργούν σαν μεταβλητές, αλλά στην πραγματικότητα αντιπροσωπεύονται από μεθόδους (getters και setters). Αυτό επιτρέπει την επικύρωση των εισόδων ή τη δημιουργία τιμών μόνο τη στιγμή της ανάγνωσης. -Θα χρησιμοποιήσουμε την ιδιότητα για την κλάση Circle για να διασφαλίσουμε ότι μόνο μη αρνητικοί αριθμοί μπαίνουν στη μεταβλητή `$radius`. Αντικαταστήστε το `public $radius` με το property: +Για να χρησιμοποιήσετε ιδιότητες πρέπει: +- Να προσθέσετε στην κλάση μια annotation της μορφής `@property $xyz` +- Να δημιουργήσετε έναν getter με το όνομα `getXyz()` ή `isXyz()`, έναν setter με το όνομα `setXyz()` +- Να διασφαλίσετε ότι ο getter και ο setter είναι *public* ή *protected*. Είναι προαιρετικοί - μπορούν δηλαδή να υπάρχουν ως ιδιότητα *read-only* ή *write-only* + +Ας δούμε ένα πρακτικό παράδειγμα στην κλάση Circle, όπου θα χρησιμοποιήσουμε τις ιδιότητες για να διασφαλίσουμε ότι η ακτίνα θα είναι πάντα ένας μη αρνητικός αριθμός. Αντικαθιστούμε το αρχικό `public $radius` με μια ιδιότητα: ```php /** @@ -34,7 +79,7 @@ class Circle { use Nette\SmartObject; - private float $radius = 0.0; // όχι δημόσια + private float $radius = 0.0; // δεν είναι public! // getter για την ιδιότητα $radius protected function getRadius(): float @@ -45,7 +90,7 @@ class Circle // setter για την ιδιότητα $radius protected function setRadius(float $radius): void { - // εξυγίανση της τιμής πριν από την αποθήκευση + // απολυμαίνουμε την τιμή πριν την αποθήκευση $this->radius = max(0.0, $radius); } @@ -57,84 +102,30 @@ class Circle } $circle = new Circle; -$circle->radius = 10; // στην πραγματικότητα καλεί την setRadius(10) -echo $circle->radius; // καλεί την getRadius() -echo $circle->visible; // καλεί την isVisible() +$circle->radius = 10; // στην πραγματικότητα καλεί setRadius(10) +echo $circle->radius; // καλεί getRadius() +echo $circle->visible; // καλεί isVisible() ``` -Οι ιδιότητες είναι κατά κύριο λόγο "συντακτική ζάχαρη"((syntactic sugar)), η οποία έχει σκοπό να κάνει τη ζωή του προγραμματιστή πιο γλυκιά απλοποιώντας τον κώδικα. Αν δεν τις θέλετε, δεν χρειάζεται να τις χρησιμοποιήσετε. - - -Στατικές κλάσεις .[#toc-static-classes] -======================================= - -Οι στατικές κλάσεις, δηλαδή οι κλάσεις που δεν προορίζονται για ενσάρκωση, μπορούν να επισημανθούν με το χαρακτηριστικό `Nette\StaticClass`: +Από την PHP 8.4, μπορεί να επιτευχθεί η ίδια λειτουργικότητα χρησιμοποιώντας property hooks, τα οποία προσφέρουν πολύ πιο κομψή και συνοπτική σύνταξη: ```php -class Strings +class Circle { - use Nette\StaticClass; -} -``` - -Όταν προσπαθείτε να δημιουργήσετε ένα instance, εκπέμπεται η εξαίρεση `Error`, υποδεικνύοντας ότι η κλάση είναι στατική. - - -Μια ματιά στην ιστορία .[#toc-a-look-into-the-history] -====================================================== - -Το SmartObject βελτίωνε και διόρθωνε τη συμπεριφορά των κλάσεων με πολλούς τρόπους, αλλά η εξέλιξη της PHP έχει καταστήσει τα περισσότερα από τα αρχικά χαρακτηριστικά περιττά. Έτσι, το παρακάτω είναι μια ματιά στην ιστορία της εξέλιξης των πραγμάτων. - -Από την αρχή, το μοντέλο αντικειμένων της PHP υπέφερε από μια σειρά σοβαρών ελαττωμάτων και αναποτελεσματικότητας. Αυτός ήταν ο λόγος για τη δημιουργία της κλάσης `Nette\Object` (το 2007), η οποία προσπάθησε να τις διορθώσει και να βελτιώσει την εμπειρία χρήσης της PHP. Ήταν αρκετό για να κληρονομήσουν και άλλες κλάσεις από αυτήν και να αποκτήσουν τα οφέλη που έφερε. Όταν η PHP 5.4 ήρθε με υποστήριξη γνωρισμάτων, η κλάση `Nette\Object` αντικαταστάθηκε από την `Nette\SmartObject`. Έτσι, δεν ήταν πλέον απαραίτητη η κληρονομικότητα από έναν κοινό πρόγονο. Επιπλέον, το trait μπορούσε να χρησιμοποιηθεί σε κλάσεις που ήδη κληρονομούσαν από μια άλλη κλάση. Το οριστικό τέλος της `Nette\Object` ήρθε με την κυκλοφορία της PHP 7.2, η οποία απαγόρευσε στις κλάσεις να έχουν το όνομα `Object`. - -Καθώς η ανάπτυξη της PHP προχωρούσε, το μοντέλο αντικειμένων και οι δυνατότητες της γλώσσας βελτιώνονταν. Οι επιμέρους λειτουργίες της κλάσης `SmartObject` έγιναν περιττές. Από την έκδοση της PHP 8.2, το μόνο χαρακτηριστικό που παραμένει και δεν υποστηρίζεται ακόμη άμεσα από την PHP είναι η δυνατότητα χρήσης των λεγόμενων [ιδιοτήτων |#Properties, Getters and Setters]. - -Ποια χαρακτηριστικά προσέφεραν κάποτε οι `Nette\Object` και `Nette\Object`; Ακολουθεί μια επισκόπηση. (Τα παραδείγματα χρησιμοποιούν την κλάση `Nette\Object`, αλλά οι περισσότερες από τις ιδιότητες ισχύουν και για την ιδιότητα `Nette\SmartObject` ). - - -Ασυνεπή σφάλματα .[#toc-inconsistent-errors] --------------------------------------------- -Η PHP είχε ασυνεπή συμπεριφορά κατά την πρόσβαση σε μη δηλωμένα μέλη. Η κατάσταση κατά τη στιγμή του `Nette\Object` ήταν η εξής: - -```php -echo $obj->undeclared; // E_NOTICE, αργότερα E_WARNING -$obj->undeclared = 1; // περνάει σιωπηλά χωρίς αναφορά -$obj->unknownMethod(); // Μοιραίο σφάλμα (δεν μπορεί να πιαστεί από try/catch) -``` - -Το μοιραίο σφάλμα τερμάτισε την εφαρμογή χωρίς καμία δυνατότητα αντίδρασης. Η σιωπηλή εγγραφή σε ανύπαρκτα μέλη χωρίς προειδοποίηση θα μπορούσε να οδηγήσει σε σοβαρά σφάλματα που ήταν δύσκολο να εντοπιστούν. `Nette\Object` Όλες αυτές οι περιπτώσεις εντοπίστηκαν και απορρίφθηκε μια εξαίρεση `MemberAccessException`. - -```php -echo $obj->undeclared; // throw Nette\MemberAccessException -$obj->undeclared = 1; // throw Nette\MemberAccessException -$obj->unknownMethod(); // throw Nette\MemberAccessException -``` -Από την PHP 7.0, η PHP δεν προκαλεί πλέον μη πιασμένα μοιραία σφάλματα, ενώ η πρόσβαση σε μη δηλωμένα μέλη αποτελεί σφάλμα από την PHP 8.2. - - -Εννοείτε? .[#toc-did-you-mean] ------------------------------- -Εάν εμφανιζόταν ένα σφάλμα `Nette\MemberAccessException`, ίσως λόγω τυπογραφικού λάθους κατά την προσπέλαση μιας μεταβλητής αντικειμένου ή την κλήση μιας μεθόδου, το `Nette\Object` προσπαθούσε να δώσει μια υπόδειξη στο μήνυμα σφάλματος για το πώς να διορθωθεί το σφάλμα, με τη μορφή της εικονικής προσθήκης "εννοούσες;". + public float $radius = 0.0 { + set => max(0.0, $value); + } -```php -class Foo extends Nette\Object -{ - public static function from($var) - { + public bool $visible { + get => $this->radius > 0; } } - -$foo = Foo::form($var); -// throw Nette\MemberAccessException -// "Call to undefined static method Foo::form(), did you mean from()?" ``` -Η σημερινή PHP μπορεί να μην έχει καμία μορφή του "did you mean?", αλλά [η Tracy |tracy:] προσθέτει αυτή την προσθήκη στα σφάλματα. Και μπορεί ακόμη και να [διορθώσει |tracy:open-files-in-ide#toc-demos] η ίδια τέτοια σφάλματα. - -Μέθοδοι επέκτασης .[#toc-extension-methods] -------------------------------------------- -Εμπνευσμένο από τις μεθόδους επέκτασης της C#. Έδιναν τη δυνατότητα προσθήκης νέων μεθόδων σε υπάρχουσες κλάσεις. Για παράδειγμα, θα μπορούσατε να προσθέσετε τη μέθοδο `addDateTime()` σε μια φόρμα για να προσθέσετε το δικό σας DateTimePicker. +Μέθοδοι επέκτασης +----------------- +Το `Nette\Object` έφερε στην PHP μια άλλη ενδιαφέρουσα έννοια εμπνευσμένη από τις σύγχρονες γλώσσες προγραμματισμού - τις μεθόδους επέκτασης. Αυτή η λειτουργία, δανεισμένη από τη C#, επέτρεψε στους προγραμματιστές να επεκτείνουν κομψά τις υπάρχουσες κλάσεις με νέες μεθόδους χωρίς να χρειάζεται να τις τροποποιήσουν ή να κληρονομήσουν από αυτές. Για παράδειγμα, θα μπορούσατε να προσθέσετε στη φόρμα μια μέθοδο `addDateTime()`, η οποία προσθέτει ένα προσαρμοσμένο DateTimePicker: ```php Form::extensionMethod( @@ -146,22 +137,22 @@ $form = new Form; $form->addDateTime('date'); ``` -Οι μέθοδοι επέκτασης αποδείχθηκαν ανεφάρμοστες επειδή τα ονόματά τους δεν συμπληρώνονταν αυτόματα από τους συντάκτες, αλλά ανέφεραν ότι η μέθοδος δεν υπήρχε. Ως εκ τούτου, η υποστήριξή τους διακόπηκε. +Οι μέθοδοι επέκτασης αποδείχθηκαν μη πρακτικές, επειδή τα ονόματά τους δεν υποδεικνύονταν από τους επεξεργαστές, αντίθετα ανέφεραν ότι η μέθοδος δεν υπήρχε. Γι' αυτό η υποστήριξή τους τερματίστηκε. Σήμερα, είναι πιο συνηθισμένο να χρησιμοποιείται η σύνθεση ή η κληρονομικότητα για την επέκταση της λειτουργικότητας των κλάσεων. -Λήψη του ονόματος της κλάσης .[#toc-getting-the-class-name] ------------------------------------------------------------ +Λήψη ονόματος κλάσης +-------------------- +Για τη λήψη του ονόματος της κλάσης, το SmartObject προσέφερε μια απλή μέθοδο: ```php -$class = $obj->getClass(); // χρησιμοποιώντας το Nette\Object +$class = $obj->getClass(); // με το Nette\Object $class = $obj::class; // από την PHP 8.0 ``` -Πρόσβαση στον Αναστοχασμό και τις Σημειώσεις .[#toc-access-to-reflection-and-annotations] ------------------------------------------------------------------------------------------ - -`Nette\Object` προσφέρεται πρόσβαση στον προβληματισμό και τον σχολιασμό με τη χρήση των μεθόδων `getReflection()` και `getAnnotation()`: +Πρόσβαση σε reflection και annotations +-------------------------------------- +Το `Nette\Object` προσέφερε πρόσβαση σε reflection και annotations χρησιμοποιώντας τις μεθόδους `getReflection()` και `getAnnotation()`. Αυτή η προσέγγιση απλοποίησε σημαντικά την εργασία με τις μετα-πληροφορίες των κλάσεων: ```php /** @@ -173,10 +164,10 @@ class Foo extends Nette\Object $obj = new Foo; $reflection = $obj->getReflection(); -$reflection->getAnnotation('author'); // returns 'John Doe +$reflection->getAnnotation('author'); // επιστρέφει 'John Doe' ``` -Από την PHP 8.0, είναι δυνατή η πρόσβαση σε μετα-πληροφορίες με τη μορφή χαρακτηριστικών: +Από την PHP 8.0, είναι δυνατή η πρόσβαση στις μετα-πληροφορίες με τη μορφή attributes, τα οποία προσφέρουν ακόμη περισσότερες δυνατότητες και καλύτερο έλεγχο τύπων: ```php #[Author('John Doe')] @@ -190,10 +181,9 @@ $reflection->getAttributes(Author::class)[0]; ``` -Μέθοδοι Getters .[#toc-method-getters] --------------------------------------- - -`Nette\Object` προσέφερε έναν κομψό τρόπο για να χειρίζεστε τις μεθόδους σαν να ήταν μεταβλητές: +Getters μεθόδων +--------------- +Το `Nette\Object` προσέφερε έναν κομψό τρόπο για να περνάτε μεθόδους σαν να ήταν μεταβλητές: ```php class Foo extends Nette\Object @@ -209,7 +199,7 @@ $method = $obj->adder; echo $method(2, 3); // 5 ``` -Από την PHP 8.1, μπορείτε να χρησιμοποιήσετε τη λεγόμενη "σύνταξη κλήσης πρώτης κατηγορίας":https://www.php.net/manual/en/functions.first_class_callable_syntax: +Από την PHP 8.1, είναι δυνατή η χρήση της λεγόμενης "first-class callable syntax":https://www.php.net/manual/en/functions.first_class_callable_syntax, η οποία προχωρά αυτή την έννοια ακόμη παραπέρα: ```php $obj = new Foo; @@ -218,10 +208,9 @@ echo $method(2, 3); // 5 ``` -Γεγονότα .[#toc-events] ------------------------ - -`Nette\Object` προσφέρεται συντακτική ζάχαρη για την ενεργοποίηση του [συμβάντος |nette:glossary#events]: +Συμβάντα +-------- +Το SmartObject προσφέρει μια απλοποιημένη σύνταξη για την εργασία με [συμβάντα |nette:glossary#Events]. Τα συμβάντα επιτρέπουν στα αντικείμενα να ενημερώνουν άλλα μέρη της εφαρμογής για αλλαγές στην κατάστασή τους: ```php class Circle extends Nette\Object @@ -231,12 +220,12 @@ class Circle extends Nette\Object public function setRadius(float $radius): void { $this->onChange($this, $radius); - $this->radius = $radius + $this->radius = $radius; } } ``` -Ο κώδικας `$this->onChange($this, $radius)` είναι ισοδύναμος με τα ακόλουθα: +Ο κώδικας `$this->onChange($this, $radius)` είναι ισοδύναμος με τον ακόλουθο βρόχο: ```php foreach ($this->onChange as $callback) { @@ -244,7 +233,7 @@ foreach ($this->onChange as $callback) { } ``` -Για λόγους σαφήνειας συνιστούμε να αποφύγετε τη μαγική μέθοδο `$this->onChange()`. Ένα πρακτικό υποκατάστατο είναι η συνάρτηση [Nette\Utils\Arrays::invoke |arrays#invoke]: +Για λόγους σαφήνειας, συνιστούμε να αποφεύγετε τη μαγική μέθοδο `$this->onChange()`. Μια πρακτική αντικατάσταση είναι, για παράδειγμα, η συνάρτηση [Nette\Utils\Arrays::invoke |arrays#invoke]: ```php Nette\Utils\Arrays::invoke($this->onChange, $this, $radius); diff --git a/utils/el/staticclass.texy b/utils/el/staticclass.texy new file mode 100644 index 0000000000..d6b4274661 --- /dev/null +++ b/utils/el/staticclass.texy @@ -0,0 +1,21 @@ +Στατικές κλάσεις +**************** + +.[perex] +Η StaticClass χρησιμοποιείται για την επισήμανση στατικών κλάσεων. + + +Εγκατάσταση: + +```shell +composer require nette/utils +``` + +Οι στατικές κλάσεις, δηλαδή οι κλάσεις που δεν προορίζονται για τη δημιουργία στιγμιοτύπων, μπορούν να επισημανθούν με το trait [api:Nette\StaticClass]: + +```php +class Strings +{ + use Nette\StaticClass; +} +``` diff --git a/utils/el/strings.texy b/utils/el/strings.texy index f5afa11171..558eb066b8 100644 --- a/utils/el/strings.texy +++ b/utils/el/strings.texy @@ -1,8 +1,8 @@ -Λειτουργίες συμβολοσειράς -************************* +Εργασία με συμβολοσειρές +************************ .[perex] -[api:Nette\Utils\Strings] είναι μια στατική κλάση, η οποία περιέχει πολλές χρήσιμες συναρτήσεις για την εργασία με κωδικοποιημένα αλφαριθμητικά UTF-8. +Η [api:Nette\Utils\Strings] είναι μια στατική κλάση με χρήσιμες συναρτήσεις για την εργασία με συμβολοσειρές κυρίως στην κωδικοποίηση UTF-8. Εγκατάσταση: @@ -11,83 +11,83 @@ composer require nette/utils ``` -Όλα τα παραδείγματα προϋποθέτουν ότι έχει οριστεί το ακόλουθο ψευδώνυμο κλάσης: +Όλα τα παραδείγματα προϋποθέτουν ότι έχει δημιουργηθεί ένα ψευδώνυμο: ```php use Nette\Utils\Strings; ``` -Letter Case .[#toc-letter-case] -=============================== +Αλλαγή πεζών-κεφαλαίων +====================== -Αυτές οι λειτουργίες απαιτούν την επέκταση PHP `mbstring`. +Αυτές οι συναρτήσεις απαιτούν την επέκταση PHP `mbstring`. lower(string $s): string .[method] ---------------------------------- -Μετατρέπει όλους τους χαρακτήρες της συμβολοσειράς UTF-8 σε πεζά γράμματα. +Μετατρέπει μια συμβολοσειρά UTF-8 σε πεζά γράμματα. ```php -Strings::lower('Hello world'); // 'hello world' +Strings::lower('Dobrý den'); // 'dobrý den' ``` upper(string $s): string .[method] ---------------------------------- -Μετατρέπει όλους τους χαρακτήρες μιας συμβολοσειράς UTF-8 σε κεφαλαίους. +Μετατρέπει μια συμβολοσειρά UTF-8 σε κεφαλαία γράμματα. ```php -Strings::upper('Hello world'); // 'HELLO WORLD' +Strings::upper('Dobrý den'); // 'DOBRÝ DEN' ``` firstUpper(string $s): string .[method] --------------------------------------- -Μετατρέπει τον πρώτο χαρακτήρα μιας συμβολοσειράς UTF-8 σε κεφαλαίο και αφήνει τους υπόλοιπους χαρακτήρες αμετάβλητους. +Μετατρέπει το πρώτο γράμμα μιας συμβολοσειράς UTF-8 σε κεφαλαίο, τα υπόλοιπα παραμένουν αμετάβλητα. ```php -Strings::firstUpper('hello world'); // 'Hello world' +Strings::firstUpper('dobrý den'); // 'Dobrý den' ``` firstLower(string $s): string .[method] --------------------------------------- -Μετατρέπει τον πρώτο χαρακτήρα μιας συμβολοσειράς UTF-8 σε πεζά και αφήνει τους υπόλοιπους χαρακτήρες αμετάβλητους. +Μετατρέπει το πρώτο γράμμα μιας συμβολοσειράς UTF-8 σε πεζό, τα υπόλοιπα παραμένουν αμετάβλητα. ```php -Strings::firstLower('Hello world'); // 'hello world' +Strings::firstLower('Dobrý den'); // 'dobrý den' ``` capitalize(string $s): string .[method] --------------------------------------- -Μετατρέπει τον πρώτο χαρακτήρα κάθε λέξης μιας συμβολοσειράς UTF-8 σε κεφαλαία και τους υπόλοιπους σε πεζά. +Μετατρέπει το πρώτο γράμμα κάθε λέξης σε μια συμβολοσειρά UTF-8 σε κεφαλαίο, τα υπόλοιπα σε πεζά. ```php -Strings::capitalize('Hello world'); // 'Hello World' +Strings::capitalize('Dobrý den'); // 'Dobrý Den' ``` -Επεξεργασία μιας συμβολοσειράς .[#toc-editing-a-string] -======================================================= +Τροποποίηση συμβολοσειράς +========================= normalize(string $s): string .[method] -------------------------------------- -Αφαιρεί τους χαρακτήρες ελέγχου, κανονικοποιεί τα διαλείμματα γραμμής στο `\n`, αφαιρεί τις κενές γραμμές που προηγούνται και ακολουθούν, κόβει τα κενά στο τέλος των γραμμών, κανονικοποιεί το UTF-8 στην κανονική μορφή του NFC. +Αφαιρεί τους χαρακτήρες ελέγχου, κανονικοποιεί τις αλλαγές γραμμής σε `\n`, περικόπτει τις αρχικές και τελικές κενές γραμμές, περικόπτει τα δεξιά κενά στις γραμμές, κανονικοποιεί το UTF-8 στην κανονική μορφή NFC. unixNewLines(string $s): string .[method] ----------------------------------------- -Μετατρέπει τα διαλείμματα γραμμής σε `\n` που χρησιμοποιούνται στα συστήματα Unix. Τα διαλείμματα γραμμής είναι: `\n` `\r` `\r\n` U+2028 διαχωριστικό γραμμής, U+2029 διαχωριστικό παραγράφου. +Μετατρέπει τις αλλαγές γραμμής σε `\n` που χρησιμοποιούνται σε συστήματα unix. Οι αλλαγές γραμμής είναι: `\n`, `\r`, `\r\n`, U+2028 διαχωριστής γραμμής, U+2029 διαχωριστής παραγράφου. ```php $unixLikeLines = Strings::unixNewLines($string); @@ -97,42 +97,42 @@ $unixLikeLines = Strings::unixNewLines($string); platformNewLines(string $s): string .[method] --------------------------------------------- -Μετατρέπει τα διαχωριστικά γραμμών σε χαρακτήρες που αφορούν την τρέχουσα πλατφόρμα, δηλαδή `\r\n` στα Windows και `\n` αλλού. Τα διαλείμματα γραμμής είναι `\n`, `\r`, `\r\n`, U+2028 διαχωριστικό γραμμής, U+2029 διαχωριστικό παραγράφου. +Μετατρέπει τις αλλαγές γραμμής στους χαρακτήρες που είναι συγκεκριμένοι για την τρέχουσα πλατφόρμα, δηλαδή `\r\n` στα Windows και `\n` αλλού. Οι αλλαγές γραμμής είναι: `\n`, `\r`, `\r\n`, U+2028 διαχωριστής γραμμής, U+2029 διαχωριστής παραγράφου. ```php $platformLines = Strings::platformNewLines($string); ``` -webalize(string $s, string $charlist=null, bool $lower=true): string .[method] ------------------------------------------------------------------------------- +webalize(string $s, ?string $charlist=null, bool $lower=true): string .[method] +------------------------------------------------------------------------------- -Τροποποιεί τη συμβολοσειρά UTF-8 στη μορφή που χρησιμοποιείται στη διεύθυνση URL, δηλαδή αφαιρεί τους διακριτικούς χαρακτήρες και αντικαθιστά όλους τους χαρακτήρες εκτός από τα γράμματα του αγγλικού αλφαβήτου και τους αριθμούς με μια παύλα. +Τροποποιεί μια συμβολοσειρά UTF-8 στη μορφή που χρησιμοποιείται στα URL, δηλαδή αφαιρεί τα διακριτικά και αντικαθιστά όλους τους χαρακτήρες, εκτός από τα γράμματα του αγγλικού αλφαβήτου και τους αριθμούς, με παύλες. ```php -Strings::webalize('žluťoučký kůň'); // 'zlutoucky-kun' +Strings::webalize('náš produkt'); // 'nas-produkt' ``` -Μπορούν να διατηρηθούν και άλλοι χαρακτήρες, αλλά πρέπει να μεταβιβαστούν ως δεύτερο όρισμα. +Εάν πρέπει να διατηρηθούν και άλλοι χαρακτήρες, μπορούν να καθοριστούν στη δεύτερη παράμετρο της συνάρτησης. ```php -Strings::webalize('10. image_id', '._'); // '10.-image_id' +Strings::webalize('10. obrázek_id', '._'); // '10.-obrazek_id' ``` -Το τρίτο όρισμα μπορεί να εμποδίσει τη μετατροπή της συμβολοσειράς σε πεζά γράμματα. +Με την τρίτη παράμετρο, μπορεί να καταργηθεί η μετατροπή σε πεζά γράμματα. ```php -Strings::webalize('Hello world', null, false); // 'Hello-world' +Strings::webalize('Dobrý den', null, false); // 'Dobry-den' ``` .[caution] Απαιτεί την επέκταση PHP `intl`. -trim(string $s, string $charlist=null): string .[method] --------------------------------------------------------- +trim(string $s, ?string $charlist=null): string .[method] +--------------------------------------------------------- -Αφαιρεί όλα τα αριστερά και δεξιά κενά (ή τους χαρακτήρες που περνούν ως δεύτερο όρισμα) από μια κωδικοποιημένη συμβολοσειρά UTF-8. +Περικόπτει τα κενά (ή άλλους χαρακτήρες που καθορίζονται από τη δεύτερη παράμετρο) από την αρχή και το τέλος μιας συμβολοσειράς UTF-8. ```php Strings::trim(' Hello '); // 'Hello' @@ -142,21 +142,21 @@ Strings::trim(' Hello '); // 'Hello' truncate(string $s, int $maxLen, string $append=`'…'`): string .[method] ------------------------------------------------------------------------ -Αποκόπτει μια συμβολοσειρά UTF-8 σε δεδομένο μέγιστο μήκος, ενώ προσπαθεί να μην διασπάσει ολόκληρες λέξεις. Μόνο αν η συμβολοσειρά είναι περικομμένη, μια έλλειψη (ή κάτι άλλο που ορίζεται με το τρίτο όρισμα) προσαρτάται στη συμβολοσειρά. +Περικόπτει μια συμβολοσειρά UTF-8 στο καθορισμένο μέγιστο μήκος, προσπαθώντας να διατηρήσει ολόκληρες λέξεις. Εάν η συμβολοσειρά συντομευτεί, προσθέτει αποσιωπητικά στο τέλος (μπορεί να αλλάξει με την τρίτη παράμετρο). ```php -$text = 'Hello, how are you today?'; -Strings::truncate($text, 5); // 'Hell…' -Strings::truncate($text, 20); // 'Hello, how are you…' -Strings::truncate($text, 30); // 'Hello, how are you today?' -Strings::truncate($text, 20, '~'); // 'Hello, how are you~' +$text = 'Řekněte, jak se máte?'; +Strings::truncate($text, 5); // 'Řekn…' +Strings::truncate($text, 20); // 'Řekněte, jak se…' +Strings::truncate($text, 30); // 'Řekněte, jak se máte?' +Strings::truncate($text, 20, '~'); // 'Řekněte, jak se~' ``` indent(string $s, int $level=1, string $indentationChar=`"\t"`): string .[method] --------------------------------------------------------------------------------- -Παρεμβάλλει ένα κείμενο πολλών γραμμών από τα αριστερά. Το δεύτερο όρισμα ορίζει πόσα σύμβολα εσοχής θα χρησιμοποιηθούν, ενώ η ίδια η εσοχή είναι το τρίτο όρισμα (*tab* από προεπιλογή). +Δημιουργεί εσοχή σε ένα κείμενο πολλαπλών γραμμών από τα αριστερά. Ο αριθμός των εσοχών καθορίζεται από τη δεύτερη παράμετρο, ο χαρακτήρας εσοχής από την τρίτη παράμετρο (η προεπιλεγμένη τιμή είναι tab). ```php Strings::indent('Nette'); // "\tNette" @@ -167,7 +167,7 @@ Strings::indent('Nette', 2, '+'); // '++Nette' padLeft(string $s, int $length, string $pad=`' '`): string .[method] -------------------------------------------------------------------- -Γεμίζει μια συμβολοσειρά UTF-8 στο δεδομένο μήκος, προτάσσοντας τη συμβολοσειρά `$pad` στην αρχή. +Συμπληρώνει μια συμβολοσειρά UTF-8 στο καθορισμένο μήκος επαναλαμβάνοντας τη συμβολοσειρά `$pad` από τα αριστερά. ```php Strings::padLeft('Nette', 6); // ' Nette' @@ -178,7 +178,7 @@ Strings::padLeft('Nette', 8, '+*'); // '+*+Nette' padRight(string $s, int $length, string $pad=`' '`): string .[method] --------------------------------------------------------------------- -Στερεοποιεί τη συμβολοσειρά UTF-8 στο δεδομένο μήκος, προσθέτοντας τη συμβολοσειρά `$pad` στο τέλος. +Συμπληρώνει μια συμβολοσειρά UTF-8 στο καθορισμένο μήκος επαναλαμβάνοντας τη συμβολοσειρά `$pad` από τα δεξιά. ```php Strings::padRight('Nette', 6); // 'Nette ' @@ -186,10 +186,10 @@ Strings::padRight('Nette', 8, '+*'); // 'Nette+*+' ``` -substring(string $s, int $start, int $length=null): string .[method] --------------------------------------------------------------------- +substring(string $s, int $start, ?int $length=null): string .[method] +--------------------------------------------------------------------- -Επιστρέφει ένα τμήμα της συμβολοσειράς UTF-8 που καθορίζεται από τη θέση έναρξης `$start` και το μήκος `$length`. Εάν το `$start` είναι αρνητικό, το επιστρεφόμενο αλφαριθμητικό θα ξεκινήσει από τον `$start`'οστό χαρακτήρα από το τέλος του αλφαριθμητικού. +Επιστρέφει ένα τμήμα της συμβολοσειράς UTF-8 `$s` που καθορίζεται από τη θέση έναρξης `$start` και το μήκος `$length`. Εάν το `$start` είναι αρνητικό, η επιστρεφόμενη συμβολοσειρά θα ξεκινά από τον χαρακτήρα -`$start` από το τέλος. ```php Strings::substring('Nette Framework', 0, 5); // 'Nette' @@ -201,7 +201,7 @@ Strings::substring('Nette Framework', -4); // 'work' reverse(string $s): string .[method] ------------------------------------ -Αντιστρέφει τη συμβολοσειρά UTF-8. +Αντιστρέφει μια συμβολοσειρά UTF-8. ```php Strings::reverse('Nette'); // 'etteN' @@ -211,77 +211,77 @@ Strings::reverse('Nette'); // 'etteN' length(string $s): int .[method] -------------------------------- -Επιστρέφει τον αριθμό των χαρακτήρων (όχι bytes) στη συμβολοσειρά UTF-8. +Επιστρέφει τον αριθμό των χαρακτήρων (όχι των bytes) σε μια συμβολοσειρά UTF-8. -Δηλαδή τον αριθμό των κωδικών σημείων Unicode, ο οποίος μπορεί να διαφέρει από τον αριθμό των γραφημάτων. +Αυτός είναι ο αριθμός των κωδικών σημείων Unicode, ο οποίος μπορεί να διαφέρει από τον αριθμό των γραφημάτων. ```php -Strings::length('Nette'); // 5 -Strings::length('red'); // 3 +Strings::length('Nette'); // 5 +Strings::length('červená'); // 7 ``` startsWith(string $haystack, string $needle): bool .[method deprecated] ----------------------------------------------------------------------- -Ελέγχει αν η συμβολοσειρά `$haystack` αρχίζει με `$needle`. +Ελέγχει εάν η συμβολοσειρά `$haystack` ξεκινά με τη συμβολοσειρά `$needle`. ```php -$haystack = 'Begins'; -$needle = 'Be'; +$haystack = 'Začíná'; +$needle = 'Za'; Strings::startsWith($haystack, $needle); // true ``` .[note] -Χρησιμοποιεί το εγγενές `str_starts_with()`:https://www.php.net/manual/en/function.str-starts-with.php. +Χρησιμοποιήστε την εγγενή `str_starts_with()`:https://www.php.net/manual/en/function.str-starts-with.php. endsWith(string $haystack, string $needle): bool .[method deprecated] --------------------------------------------------------------------- -Ελέγχει αν η συμβολοσειρά `$haystack` τελειώνει με `$needle`. +Ελέγχει εάν η συμβολοσειρά `$haystack` τελειώνει με τη συμβολοσειρά `$needle`. ```php -$haystack = 'Ends'; -$needle = 'ds'; +$haystack = 'Končí'; +$needle = 'čí'; Strings::endsWith($haystack, $needle); // true ``` .[note] -Χρησιμοποιεί το εγγενές `str_ends_with()`:https://www.php.net/manual/en/function.str-ends-with.php. +Χρησιμοποιήστε την εγγενή `str_ends_with()`:https://www.php.net/manual/en/function.str-ends-with.php. contains(string $haystack, string $needle): bool .[method deprecated] --------------------------------------------------------------------- -Ελέγχει αν η συμβολοσειρά `$haystack` περιέχει το `$needle`. +Ελέγχει εάν η συμβολοσειρά `$haystack` περιέχει τη `$needle`. ```php -$haystack = 'Contains'; -$needle = 'tai'; +$haystack = 'Posluchárna'; +$needle = 'sluch'; Strings::contains($haystack, $needle); // true ``` .[note] -Χρήση εγγενούς `str_contains()`:https://www.php.net/manual/en/function.str-contains.php. +Χρησιμοποιήστε την εγγενή `str_contains()`:https://www.php.net/manual/en/function.str-contains.php. -compare(string $left, string $right, int $length=null): bool .[method] ----------------------------------------------------------------------- +compare(string $left, string $right, ?int $length=null): bool .[method] +----------------------------------------------------------------------- -Συγκρίνει δύο συμβολοσειρές UTF-8 ή τα μέρη τους, χωρίς να λαμβάνει υπόψη την περίπτωση χαρακτήρων. Αν `$length` είναι null, συγκρίνονται ολόκληρες συμβολοσειρές, αν είναι αρνητικό, συγκρίνεται ο αντίστοιχος αριθμός χαρακτήρων από το τέλος των συμβολοσειρών, διαφορετικά συγκρίνεται ο αντίστοιχος αριθμός χαρακτήρων από την αρχή. +Σύγκριση δύο συμβολοσειρών UTF-8 ή τμημάτων τους χωρίς διάκριση πεζών-κεφαλαίων. Εάν το `$length` περιέχει null, συγκρίνονται ολόκληρες οι συμβολοσειρές, εάν είναι αρνητικό, συγκρίνεται ο αντίστοιχος αριθμός χαρακτήρων από το τέλος των συμβολοσειρών, διαφορετικά συγκρίνεται ο αντίστοιχος αριθμός χαρακτήρων από την αρχή. ```php Strings::compare('Nette', 'nette'); // true -Strings::compare('Nette', 'next', 2); // true - two first characters match -Strings::compare('Nette', 'Latte', -2); // true - two last characters match +Strings::compare('Nette', 'next', 2); // true - αντιστοιχία των πρώτων 2 χαρακτήρων +Strings::compare('Nette', 'Latte', -2); // true - αντιστοιχία των τελευταίων 2 χαρακτήρων ``` findPrefix(...$strings): string .[method] ----------------------------------------- -Βρίσκει το κοινό πρόθεμα των συμβολοσειρών ή επιστρέφει κενή συμβολοσειρά εάν το πρόθεμα δεν βρέθηκε. +Βρίσκει την κοινή αρχή των συμβολοσειρών. Ή επιστρέφει μια κενή συμβολοσειρά εάν δεν βρέθηκε κοινό πρόθεμα. ```php Strings::findPrefix('prefix-a', 'prefix-bb', 'prefix-c'); // 'prefix-' @@ -293,7 +293,7 @@ Strings::findPrefix('Nette', 'is', 'great'); // '' before(string $haystack, string $needle, int $nth=1): ?string .[method] ----------------------------------------------------------------------- -Επιστρέφει μέρος του `$haystack` πριν από την εμφάνιση του `$nth` στο `$needle` ή επιστρέφει το `null` εάν δεν βρέθηκε η βελόνα. Αρνητική τιμή σημαίνει αναζήτηση από το τέλος. +Επιστρέφει το τμήμα της συμβολοσειράς `$haystack` πριν από την n-οστή `$nth` εμφάνιση της συμβολοσειράς `$needle`. Ή `null`, εάν η `$needle` δεν βρέθηκε. Με αρνητική τιμή `$nth`, η αναζήτηση γίνεται από το τέλος της συμβολοσειράς. ```php Strings::before('Nette_is_great', '_', 1); // 'Nette' @@ -306,7 +306,7 @@ Strings::before('Nette_is_great', '_', 3); // null after(string $haystack, string $needle, int $nth=1): ?string .[method] ---------------------------------------------------------------------- -Επιστρέφει μέρος του `$haystack` μετά την εμφάνιση του `$nth` στο `$needle` ή επιστρέφει το `null` εάν το `$needle` δεν βρέθηκε. Αρνητική τιμή του `$nth` σημαίνει αναζήτηση από το τέλος. +Επιστρέφει το τμήμα της συμβολοσειράς `$haystack` μετά την n-οστή `$nth` εμφάνιση της συμβολοσειράς `$needle`. Ή `null`, εάν η `$needle` δεν βρέθηκε. Με αρνητική τιμή `$nth`, η αναζήτηση γίνεται από το τέλος της συμβολοσειράς. ```php Strings::after('Nette_is_great', '_', 2); // 'great' @@ -319,7 +319,7 @@ Strings::after('Nette_is_great', '_', 3); // null indexOf(string $haystack, string $needle, int $nth=1): ?int .[method] --------------------------------------------------------------------- -Επιστρέφει τη θέση σε χαρακτήρες του `$nth` εμφάνισης του `$needle` στο `$haystack` ή στο `null` εάν το `$needle` δεν βρέθηκε. Αρνητική τιμή του `$nth` σημαίνει αναζήτηση από το τέλος. +Επιστρέφει τη θέση σε χαρακτήρες της n-οστής `$nth` εμφάνισης της συμβολοσειράς `$needle` στη συμβολοσειρά `$haystack`. Ή `null`, εάν η `$needle` δεν βρέθηκε. Με αρνητική τιμή `$nth`, η αναζήτηση γίνεται από το τέλος της συμβολοσειράς. ```php Strings::indexOf('abc abc abc', 'abc', 2); // 4 @@ -328,14 +328,14 @@ Strings::indexOf('abc abc abc', 'd'); // null ``` -Κωδικοποίηση .[#toc-encoding] -============================= +Κωδικοποίηση +============ fixEncoding(string $s): string .[method] ---------------------------------------- -Αφαιρεί όλους τους μη έγκυρους χαρακτήρες UTF-8 από μια συμβολοσειρά. +Αφαιρεί τους μη έγκυρους χαρακτήρες UTF-8 από τη συμβολοσειρά. ```php $correctStrings = Strings::fixEncoding($string); @@ -345,20 +345,20 @@ $correctStrings = Strings::fixEncoding($string); checkEncoding(string $s): bool .[method deprecated] --------------------------------------------------- -Ελέγχει αν η συμβολοσειρά είναι έγκυρη σε κωδικοποίηση UTF-8. +Ελέγχει εάν πρόκειται για έγκυρη συμβολοσειρά UTF-8. ```php $isUtf8 = Strings::checkEncoding($string); ``` .[note] -Χρησιμοποιήστε την [Nette\Utils\Validator::isUnicode() |validators#isUnicode]. +Χρησιμοποιήστε το [Nette\Utils\Validator::isUnicode() |validators#isUnicode]. toAscii(string $s): string .[method] ------------------------------------ -Μετατρέπει τη συμβολοσειρά UTF-8 σε ASCII, δηλαδή αφαιρεί τα διακριτικά κ.λπ. +Μετατρέπει μια συμβολοσειρά UTF-8 σε ASCII, δηλαδή αφαιρεί τα διακριτικά κ.λπ. ```php Strings::toAscii('žluťoučký kůň'); // 'zlutoucky kun' @@ -371,33 +371,33 @@ Strings::toAscii('žluťoučký kůň'); // 'zlutoucky kun' chr(int $code): string .[method] -------------------------------- -Επιστρέφει έναν συγκεκριμένο χαρακτήρα σε UTF-8 από το σημείο κωδικού (αριθμός στην περιοχή 0x0000..D7FF ή 0xE000..10FFFF). +Επιστρέφει έναν συγκεκριμένο χαρακτήρα σε UTF-8 από το κωδικό σημείο (αριθμός στην περιοχή 0x0000..D7FF και 0xE000..10FFFF). ```php -Strings::chr(0xA9); // '©' +Strings::chr(0xA9); // '©' στην κωδικοποίηση UTF-8 ``` ord(string $char): int .[method] -------------------------------- -Επιστρέφει ένα σημείο κωδικού από συγκεκριμένο χαρακτήρα σε UTF-8 (αριθμός στην περιοχή 0x0000..D7FF ή 0xE000..10FFFF). +Επιστρέφει το κωδικό σημείο ενός συγκεκριμένου χαρακτήρα σε UTF-8 (αριθμός στην περιοχή 0x0000..D7FF ή 0xE000..10FFFF). ```php Strings::ord('©'); // 0xA9 ``` -Κανονικές εκφράσεις .[#toc-regular-expressions] -=============================================== +Κανονικές εκφράσεις +=================== -Η κλάση Strings παρέχει συναρτήσεις για την εργασία με κανονικές εκφράσεις. Σε αντίθεση με τις εγγενείς συναρτήσεις της PHP, έχουν ένα πιο κατανοητό API, καλύτερη υποστήριξη Unicode και, το σημαντικότερο, ανίχνευση σφαλμάτων. Οποιοδήποτε σφάλμα μεταγλώττισης ή επεξεργασίας εκφράσεων θα πετάξει μια εξαίρεση `Nette\RegexpException`. +Η κλάση Strings προσφέρει συναρτήσεις για την εργασία με κανονικές εκφράσεις. Σε αντίθεση με τις εγγενείς συναρτήσεις της PHP, διαθέτουν πιο κατανοητό API, καλύτερη υποστήριξη Unicode και κυρίως ανίχνευση σφαλμάτων. Οποιοδήποτε σφάλμα κατά τη μεταγλώττιση ή την επεξεργασία της έκφρασης δημιουργεί μια εξαίρεση `Nette\RegexpException`. split(string $subject, string $pattern, bool $captureOffset=false, bool $skipEmpty=false, int $limit=-1, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------- -Χωρίζει τη συμβολοσειρά σε πίνακες σύμφωνα με την κανονική έκφραση. Οι εκφράσεις σε παρενθέσεις θα καταγραφούν και θα επιστραφούν επίσης. +Διαιρεί μια συμβολοσειρά σε έναν πίνακα σύμφωνα με μια κανονική έκφραση. Οι εκφράσεις σε παρενθέσεις θα συλληφθούν και θα επιστραφούν επίσης. ```php Strings::split('hello, world', '~,\s*~'); @@ -407,7 +407,7 @@ Strings::split('hello, world', '~(,)\s*~'); // ['hello', ',', 'world']`` ``` -Εάν `$skipEmpty` είναι `true`, θα επιστραφούν μόνο μη κενά στοιχεία: +Εάν το `$skipEmpty` είναι `true`, θα επιστραφούν μόνο τα μη κενά στοιχεία: ```php Strings::split('hello, world, ', '~,\s*~'); @@ -417,16 +417,16 @@ Strings::split('hello, world, ', '~,\s*~', skipEmpty: true); // ['hello', 'world'] ``` -Εάν καθοριστεί το `$limit`, θα επιστραφούν μόνο υποσύνολα μέχρι το όριο και το υπόλοιπο της συμβολοσειράς θα τοποθετηθεί στο τελευταίο στοιχείο. Ένα όριο -1 ή 0 σημαίνει ότι δεν υπάρχει όριο. +Εάν καθοριστεί το `$limit`, θα επιστραφούν μόνο οι υποσυμβολοσειρές μέχρι το όριο και η υπόλοιπη συμβολοσειρά θα τοποθετηθεί στο τελευταίο στοιχείο. Ένα όριο -1 ή 0 σημαίνει κανένας περιορισμός. ```php Strings::split('hello, world, third', '~,\s*~', limit: 2); // ['hello', 'world, third'] ``` -Εάν `$utf8` είναι `true`, η αξιολόγηση μεταβαίνει σε λειτουργία Unicode. Αυτό είναι παρόμοιο με τον προσδιορισμό του τροποποιητή `u`. +Εάν το `$utf8` είναι `true`, η αξιολόγηση μεταβαίνει σε λειτουργία Unicode. Παρόμοια με όταν καθορίζετε τον τροποποιητή `u`. -Εάν `$captureOffset` είναι `true`, για κάθε εμφανιζόμενη αντιστοιχία, θα επιστραφεί επίσης η θέση της στη συμβολοσειρά (σε bytes- σε χαρακτήρες εάν έχει οριστεί `$utf8` ). Αυτό αλλάζει την τιμή επιστροφής σε έναν πίνακα όπου κάθε στοιχείο είναι ένα ζεύγος αποτελούμενο από το αλφαριθμητικό που ταιριάζει και τη θέση του. +Εάν το `$captureOffset` είναι `true`, για κάθε εμφάνιση αντιστοιχίας θα επιστραφεί επίσης η θέση της στη συμβολοσειρά (σε bytes, εάν έχει οριστεί το `$utf8` τότε σε χαρακτήρες). Αυτό αλλάζει την τιμή επιστροφής σε έναν πίνακα όπου κάθε στοιχείο είναι ένα ζεύγος που αποτελείται από την αντιστοιχισμένη συμβολοσειρά και τη θέση της. ```php Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true); @@ -440,7 +440,7 @@ Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true, utf8: true); match(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $utf8=false): ?array .[method] -------------------------------------------------------------------------------------------------------------------------------------------------- -Ψάχνει τη συμβολοσειρά για το τμήμα που ταιριάζει με την κανονική έκφραση και επιστρέφει έναν πίνακα με την έκφραση που βρέθηκε και τις επιμέρους υποεκφράσεις ή `null`. +Αναζητά στη συμβολοσειρά ένα τμήμα που αντιστοιχεί στην κανονική έκφραση και επιστρέφει έναν πίνακα με την αντιστοιχισμένη έκφραση και τις επιμέρους υποεκφράσεις, ή `null`. ```php Strings::match('hello!', '~\w+(!+)~'); @@ -450,7 +450,7 @@ Strings::match('hello!', '~X~'); // null ``` -Εάν το `$unmatchedAsNull` είναι `true`, τα μη ταιριαστά υποδείγματα επιστρέφονται ως null- διαφορετικά επιστρέφονται ως κενή συμβολοσειρά ή δεν επιστρέφονται: +Εάν το `$unmatchedAsNull` είναι `true`, οι μη συλληφθείσες υπο-αντιστοιχίες επιστρέφονται ως null, διαφορετικά επιστρέφονται ως κενή συμβολοσειρά ή δεν επιστρέφονται: ```php Strings::match('hello', '~\w+(!+)?~'); @@ -460,7 +460,7 @@ Strings::match('hello', '~\w+(!+)?~', unmatchedAsNull: true); // ['hello', null] ``` -Εάν `$utf8` είναι `true`, η αξιολόγηση μεταβαίνει σε λειτουργία Unicode. Αυτό είναι παρόμοιο με τον προσδιορισμό του τροποποιητή `u`: +Εάν το `$utf8` είναι `true`, η αξιολόγηση μεταβαίνει σε λειτουργία Unicode. Παρόμοια με όταν καθορίζετε τον τροποποιητή `u`: ```php Strings::match('žlutý kůň', '~\w+~'); @@ -470,9 +470,9 @@ Strings::match('žlutý kůň', '~\w+~', utf8: true); // ['žlutý'] ``` -Η παράμετρος `$offset` μπορεί να χρησιμοποιηθεί για να καθορίσει τη θέση από την οποία θα ξεκινήσει η αναζήτηση (σε bytes- σε χαρακτήρες αν έχει οριστεί το `$utf8` ). +Η παράμετρος `$offset` μπορεί να χρησιμοποιηθεί για τον καθορισμό της θέσης από την οποία θα ξεκινήσει η αναζήτηση (σε bytes, εάν έχει οριστεί το `$utf8` τότε σε χαρακτήρες). -Εάν `$captureOffset` είναι `true`, για κάθε εμφανιζόμενη αντιστοιχία, θα επιστρέφεται επίσης η θέση της στη συμβολοσειρά (σε bytes- σε χαρακτήρες εάν έχει οριστεί `$utf8` ). Αυτό αλλάζει την τιμή επιστροφής σε έναν πίνακα όπου κάθε στοιχείο είναι ένα ζεύγος αποτελούμενο από το αλφαριθμητικό που ταιριάζει και τη μετατόπισή του: +Εάν το `$captureOffset` είναι `true`, για κάθε εμφάνιση αντιστοιχίας θα επιστραφεί επίσης η θέση της στη συμβολοσειρά (σε bytes, εάν έχει οριστεί το `$utf8` τότε σε χαρακτήρες). Αυτό αλλάζει την τιμή επιστροφής σε έναν πίνακα όπου κάθε στοιχείο είναι ένα ζεύγος που αποτελείται από την αντιστοιχισμένη συμβολοσειρά και τη μετατόπισή της: ```php Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true); @@ -483,10 +483,10 @@ Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true, utf8: true); ``` -matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false, bool $lazy=false): array|Generator .[method] +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Ψάχνει τη συμβολοσειρά για όλες τις εμφανίσεις που ταιριάζουν με την κανονική έκφραση και επιστρέφει έναν πίνακα πινάκων που περιέχει την έκφραση που βρέθηκε και κάθε υποέκφραση. +Αναζητά στη συμβολοσειρά όλες τις εμφανίσεις που αντιστοιχούν στην κανονική έκφραση και επιστρέφει έναν πίνακα πινάκων με την αντιστοιχισμένη έκφραση και τις επιμέρους υποεκφράσεις. ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~'); @@ -496,7 +496,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~'); ] */ ``` -Εάν `$patternOrder` είναι `true`, η δομή των αποτελεσμάτων αλλάζει έτσι ώστε το πρώτο στοιχείο να είναι ένας πίνακας με πλήρεις αντιστοιχίες προτύπων, ο δεύτερος είναι ένας πίνακας με συμβολοσειρές που αντιστοιχούν στο πρώτο υποπρότυπο σε παρένθεση κ.ο.κ: +Εάν το `$patternOrder` είναι `true`, η δομή των αποτελεσμάτων αλλάζει έτσι ώστε το πρώτο στοιχείο να είναι ένας πίνακας πλήρων αντιστοιχίσεων του μοτίβου, το δεύτερο να είναι ένας πίνακας συμβολοσειρών που αντιστοιχούν στην πρώτη υπο-αντιστοιχία σε παρένθεση, και ούτω καθεξής: ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~', patternOrder: true); @@ -506,7 +506,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~', patternOrder: true); ] */ ``` -Εάν `$unmatchedAsNull` είναι `true`, τα υποδείγματα που δεν ταιριάζουν επιστρέφονται ως null- διαφορετικά επιστρέφονται ως κενή συμβολοσειρά ή δεν επιστρέφονται: +Εάν το `$unmatchedAsNull` είναι `true`, οι μη συλληφθείσες υπο-αντιστοιχίες επιστρέφονται ως null, διαφορετικά επιστρέφονται ως κενή συμβολοσειρά ή δεν επιστρέφονται: ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~', unmatchedAsNull: true); @@ -516,7 +516,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~', unmatchedAsNull: true); ] */ ``` -Εάν `$utf8` είναι `true`, η αξιολόγηση μεταβαίνει σε λειτουργία Unicode. Αυτό είναι παρόμοιο με τον προσδιορισμό του τροποποιητή `u`: +Εάν το `$utf8` είναι `true`, η αξιολόγηση μεταβαίνει σε λειτουργία Unicode. Παρόμοια με όταν καθορίζετε τον τροποποιητή `u`: ```php Strings::matchAll('žlutý kůň', '~\w+~'); @@ -532,9 +532,9 @@ Strings::matchAll('žlutý kůň', '~\w+~', utf8: true); ] */ ``` -Η παράμετρος `$offset` μπορεί να χρησιμοποιηθεί για να καθορίσει τη θέση από την οποία θα ξεκινήσει η αναζήτηση (σε bytes- σε χαρακτήρες αν έχει οριστεί το `$utf8` ). +Η παράμετρος `$offset` μπορεί να χρησιμοποιηθεί για τον καθορισμό της θέσης από την οποία θα ξεκινήσει η αναζήτηση (σε bytes, εάν έχει οριστεί το `$utf8` τότε σε χαρακτήρες). -Εάν `$captureOffset` είναι `true`, για κάθε εμφανιζόμενη αντιστοιχία, θα επιστρέφεται επίσης η θέση της στη συμβολοσειρά (σε bytes- σε χαρακτήρες εάν έχει οριστεί `$utf8` ). Αυτό αλλάζει την τιμή επιστροφής σε έναν πίνακα όπου κάθε στοιχείο είναι ένα ζεύγος που αποτελείται από το αλφαριθμητικό που ταιριάζει και τη θέση του: +Εάν το `$captureOffset` είναι `true`, για κάθε εμφάνιση αντιστοιχίας θα επιστραφεί επίσης η θέση της στη συμβολοσειρά (σε bytes, εάν έχει οριστεί το `$utf8` τότε σε χαρακτήρες). Αυτό αλλάζει την τιμή επιστροφής σε έναν πίνακα όπου κάθε στοιχείο είναι ένα ζεύγος που αποτελείται από την αντιστοιχισμένη συμβολοσειρά και τη θέση της: ```php Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true); @@ -550,11 +550,21 @@ Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true, utf8: true); ] */ ``` +Εάν το `$lazy` είναι `true`, η συνάρτηση επιστρέφει έναν `Generator` αντί για έναν πίνακα, πράγμα που προσφέρει σημαντικά πλεονεκτήματα απόδοσης κατά την εργασία με μεγάλες συμβολοσειρές. Ο γεννήτορας επιτρέπει την αναζήτηση αντιστοιχίσεων σταδιακά, αντί για ολόκληρη τη συμβολοσειρά ταυτόχρονα. Αυτό επιτρέπει την αποδοτική εργασία ακόμη και με εξαιρετικά μεγάλα κείμενα εισόδου. Επιπλέον, μπορείτε να διακόψετε την επεξεργασία ανά πάσα στιγμή εάν βρείτε την αντιστοιχία που αναζητάτε, εξοικονομώντας έτσι υπολογιστικό χρόνο. + +```php +$matches = Strings::matchAll($largeText, '~\w+~', lazy: true); +foreach ($matches as $match) { + echo "Βρέθηκε: $match[0]\n"; + // Η επεξεργασία μπορεί να διακοπεί ανά πάσα στιγμή +} +``` + replace(string $subject, string|array $pattern, string|callable $replacement='', int $limit=-1, bool $captureOffset=false, bool $unmatchedAsNull=false, bool $utf8=false): string .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Αντικαθιστά όλες τις εμφανίσεις που ταιριάζουν με την κανονική έκφραση. Το `$replacement` είναι είτε μια μάσκα συμβολοσειράς αντικατάστασης είτε μια επανάκληση. +Αντικαθιστά όλες τις εμφανίσεις που αντιστοιχούν στην κανονική έκφραση. Το `$replacement` είναι είτε μια μάσκα συμβολοσειράς αντικατάστασης είτε ένα callback. ```php Strings::replace('hello, world!', '~\w+~', '--'); @@ -564,7 +574,7 @@ Strings::replace('hello, world!', '~\w+~', fn($m) => strrev($m[0])); // 'olleh, dlrow!' ``` -Η συνάρτηση επιτρέπει επίσης πολλαπλές αντικαταστάσεις περνώντας στη δεύτερη παράμετρο έναν πίνακα της μορφής `pattern => replacement`: +Η συνάρτηση επιτρέπει επίσης την εκτέλεση πολλαπλών αντικαταστάσεων περνώντας έναν πίνακα στη δεύτερη παράμετρο με τη μορφή `pattern => replacement`: ```php Strings::replace('hello, world!', [ @@ -574,9 +584,9 @@ Strings::replace('hello, world!', [ // '-- --!' ``` -Η παράμετρος `$limit` περιορίζει τον αριθμό των αντικαταστάσεων. Το όριο -1 σημαίνει ότι δεν υπάρχει όριο. +Η παράμετρος `$limit` περιορίζει τον αριθμό των αντικαταστάσεων που εκτελούνται. Ένα όριο -1 σημαίνει κανένας περιορισμός. -Εάν `$utf8` είναι `true`, η αξιολόγηση μεταβαίνει σε λειτουργία Unicode. Αυτό είναι παρόμοιο με τον προσδιορισμό του τροποποιητή `u`. +Εάν το `$utf8` είναι `true`, η αξιολόγηση μεταβαίνει σε λειτουργία Unicode. Παρόμοια με όταν καθορίζετε τον τροποποιητή `u`. ```php Strings::replace('žlutý kůň', '~\w+~', '--'); @@ -586,7 +596,7 @@ Strings::replace('žlutý kůň', '~\w+~', '--', utf8: true); // '-- --' ``` -Εάν `$captureOffset` είναι `true`, για κάθε εμφανιζόμενη αντιστοιχία, η θέση της στη συμβολοσειρά (σε bytes- σε χαρακτήρες εάν έχει οριστεί το `$utf8` ) μεταβιβάζεται επίσης στο callback. Αυτό αλλάζει τη μορφή του περασμένου πίνακα, όπου κάθε στοιχείο είναι ένα ζεύγος που αποτελείται από το αλφαριθμητικό που ταιριάζει και τη θέση του. +Εάν το `$captureOffset` είναι `true`, για κάθε εμφάνιση αντιστοιχίας θα περάσει στο callback επίσης η θέση της στη συμβολοσειρά (σε bytes, εάν έχει οριστεί το `$utf8` τότε σε χαρακτήρες). Αυτό αλλάζει τη μορφή του περασμένου πίνακα, όπου κάθε στοιχείο είναι ένα ζεύγος που αποτελείται από την αντιστοιχισμένη συμβολοσειρά και τη θέση της. ```php Strings::replace( @@ -595,7 +605,7 @@ Strings::replace( function (array $m) { dump($m); return ''; }, captureOffset: true, ); -// dumps [['lut', 2]] a [['k', 8]] +// dumps [['lut', 2]] και [['k', 8]] Strings::replace( 'žlutý kůň', @@ -604,10 +614,10 @@ Strings::replace( captureOffset: true, utf8: true, ); -// dumps [['žlutý', 0]] a [['kůň', 6]] +// dumps [['žlutý', 0]] και [['kůň', 6]] ``` -Εάν το `$unmatchedAsNull` είναι `true`, τα μη ταιριασμένα υποδείγματα μεταβιβάζονται στο callback ως null- διαφορετικά μεταβιβάζονται ως κενή συμβολοσειρά ή δεν μεταβιβάζονται: +Εάν το `$unmatchedAsNull` είναι `true`, οι μη συλληφθείσες υπο-αντιστοιχίες περνούν στο callback ως null, διαφορετικά περνούν ως κενή συμβολοσειρά ή δεν περνούν: ```php Strings::replace( diff --git a/utils/el/type.texy b/utils/el/type.texy index a8c72e0704..7e6e1990fb 100644 --- a/utils/el/type.texy +++ b/utils/el/type.texy @@ -2,7 +2,7 @@ ********* .[perex] -[api:Nette\Utils\Type] είναι μια κλάση τύπου δεδομένων PHP. +Η [api:Nette\Utils\Type] είναι μια κλάση για την εργασία με τύπους δεδομένων PHP. Εγκατάσταση: @@ -11,7 +11,7 @@ composer require nette/utils ``` -Όλα τα παραδείγματα προϋποθέτουν ότι έχει οριστεί το ακόλουθο ψευδώνυμο κλάσης: +Όλα τα παραδείγματα προϋποθέτουν ότι έχει δημιουργηθεί ένα ψευδώνυμο: ```php use Nette\Utils\Type; @@ -21,7 +21,7 @@ use Nette\Utils\Type; fromReflection($reflection): ?Type .[method] -------------------------------------------- -Η στατική μέθοδος δημιουργεί ένα αντικείμενο Type με βάση την αντανάκλαση. Η παράμετρος μπορεί να είναι ένα αντικείμενο `ReflectionMethod` ή `ReflectionFunction` (επιστρέφει τον τύπο της τιμής επιστροφής) ή ένα αντικείμενο `ReflectionParameter` ή `ReflectionProperty`. Επιλύει τα `self`, `static` και `parent` στο πραγματικό όνομα της κλάσης. Εάν το αντικείμενο δεν έχει τύπο, επιστρέφει `null`. +Η στατική μέθοδος δημιουργεί ένα αντικείμενο Type με βάση τη reflection. Η παράμετρος μπορεί να είναι ένα αντικείμενο `ReflectionMethod` ή `ReflectionFunction` (επιστρέφει τον τύπο της τιμής επιστροφής) ή `ReflectionParameter` ή `ReflectionProperty`. Μεταφράζει τα `self`, `static` και `parent` στο πραγματικό όνομα της κλάσης. Εάν το υποκείμενο δεν έχει τύπο, επιστρέφει `null`. ```php class DemoClass @@ -37,7 +37,7 @@ echo Type::fromReflection($prop); // 'DemoClass' fromString(string $type): Type .[method] ---------------------------------------- -Η στατική μέθοδος δημιουργεί το αντικείμενο Type σύμφωνα με τον συμβολισμό κειμένου. +Η στατική μέθοδος δημιουργεί ένα αντικείμενο Type σύμφωνα με την κειμενική αναπαράσταση. ```php $type = Type::fromString('Foo|Bar'); @@ -48,10 +48,10 @@ echo $type; // 'Foo|Bar' getNames(): (string|array)[] .[method] -------------------------------------- -Επιστρέφει τον πίνακα των υποτύπων που συνθέτουν τον σύνθετο τύπο ως συμβολοσειρές. +Επιστρέφει έναν πίνακα υποτύπων από τους οποίους αποτελείται ο σύνθετος τύπος, ως συμβολοσειρές. ```php -$type = Type::fromString('string|null'); // nebo '?string' +$type = Type::fromString('string|null'); // ή '?string' $type->getNames(); // ['string', 'null'] $type = Type::fromString('(Foo&Bar)|string'); @@ -62,10 +62,10 @@ $type->getNames(); // [['Foo', 'Bar'], 'string'] getTypes(): Type[] .[method] ---------------------------- -Επιστρέφει τον πίνακα των υποτύπων που συνθέτουν τον σύνθετο τύπο ως αντικείμενα `Type`: +Επιστρέφει έναν πίνακα υποτύπων από τους οποίους αποτελείται ο σύνθετος τύπος, ως αντικείμενα `Type`: ```php -$type = Type::fromString('string|null'); // ή '?string' +$type = Type::fromString('string|null'); // or '?string' $type->getTypes(); // [Type::fromString('string'), Type::fromString('null')] $type = Type::fromString('(Foo&Bar)|string'); @@ -79,7 +79,7 @@ $type->getTypes(); // [Type::fromString('Foo'), Type::fromString('Bar')] getSingleName(): ?string .[method] ---------------------------------- -Επιστρέφει το όνομα του τύπου για απλούς τύπους, διαφορετικά null. +Για απλούς τύπους, επιστρέφει το όνομα του τύπου, διαφορετικά null. ```php $type = Type::fromString('string|null'); @@ -99,14 +99,14 @@ echo $type->getSingleName(); // null isSimple(): bool .[method] -------------------------- -Επιστρέφει αν πρόκειται για απλό τύπο. Οι απλοί μηδενιζόμενοι τύποι θεωρούνται επίσης απλοί τύποι: +Επιστρέφει εάν πρόκειται για απλό τύπο. Ως απλοί τύποι θεωρούνται και οι απλοί nullable τύποι: ```php $type = Type::fromString('string'); $type->isSimple(); // true $type->isUnion(); // false -$type = Type::fromString('?Foo'); // nebo 'Foo|null' +$type = Type::fromString('?Foo'); // ή 'Foo|null' $type->isSimple(); // true $type->isUnion(); // true ``` @@ -115,10 +115,10 @@ $type->isUnion(); // true isUnion(): bool .[method] ------------------------- -Επιστρέφει αν πρόκειται για τύπο ένωσης. +Επιστρέφει εάν πρόκειται για τύπο union. ```php -$type = Type::fromString('Foo&Bar'); +$type = Type::fromString('string|int'); $type->isUnion(); // true ``` @@ -126,11 +126,11 @@ $type->isUnion(); // true isIntersection(): bool .[method] -------------------------------- -Επιστρέφει αν είναι τύπος διασταύρωσης. +Επιστρέφει εάν πρόκειται για τύπο intersection. ```php -$type = Type::fromString('string&int'); +$type = Type::fromString('Foo&Bar'); $type->isIntersection(); // true ``` @@ -138,7 +138,7 @@ $type->isIntersection(); // true isBuiltin(): bool .[method] --------------------------- -Επιστρέφει αν ο τύπος είναι τόσο ένας απλός όσο και ένας ενσωματωμένος τύπος της PHP. +Επιστρέφει εάν ο τύπος είναι απλός και ταυτόχρονα ενσωματωμένος τύπος της PHP. ```php $type = Type::fromString('string'); @@ -155,7 +155,7 @@ $type->isBuiltin(); // false isClass(): bool .[method] ------------------------- -Επιστρέφει αν ο τύπος είναι τόσο ένας απλός τύπος όσο και ένα όνομα κλάσης. +Επιστρέφει εάν ο τύπος είναι απλός και ταυτόχρονα όνομα κλάσης. ```php $type = Type::fromString('string'); @@ -172,7 +172,7 @@ $type->isClass(); // false isClassKeyword(): bool .[method] -------------------------------- -Προσδιορίζει αν ο τύπος είναι ένας από τους εσωτερικούς τύπους `self`, `parent`, `static`. +Επιστρέφει εάν ο τύπος είναι ένας από τους εσωτερικούς τύπους `self`, `parent`, `static`. ```php $type = Type::fromString('self'); @@ -186,7 +186,7 @@ $type->isClassKeyword(); // false allows(string $type): bool .[method] ------------------------------------ -Η μέθοδος `allows()` επαληθεύει τη συμβατότητα των τύπων. Για παράδειγμα, επιτρέπει να ελέγξετε αν μια τιμή ενός συγκεκριμένου τύπου μπορεί να περάσει ως παράμετρος. +Η μέθοδος `allows()` επαληθεύει τη συμβατότητα των τύπων. Για παράδειγμα, επιτρέπει να διαπιστωθεί εάν μια τιμή συγκεκριμένου τύπου θα μπορούσε να περάσει ως παράμετρος. ```php $type = Type::fromString('string|null'); diff --git a/utils/el/validators.texy b/utils/el/validators.texy index 33bbfc2c76..f8b7a31726 100644 --- a/utils/el/validators.texy +++ b/utils/el/validators.texy @@ -2,7 +2,7 @@ **************** .[perex] -Χρειάζεται να επαληθεύσετε γρήγορα και εύκολα ότι μια μεταβλητή περιέχει για παράδειγμα μια έγκυρη διεύθυνση ηλεκτρονικού ταχυδρομείου; Τότε θα σας φανεί χρήσιμη η [api:Nette\Utils\Validators], μια στατική κλάση με χρήσιμες συναρτήσεις για την επικύρωση τιμών. +Χρειάζεται να επαληθεύσετε γρήγορα και εύκολα ότι μια μεταβλητή περιέχει, για παράδειγμα, μια έγκυρη διεύθυνση e-mail; Τότε θα σας φανεί χρήσιμη η [api:Nette\Utils\Validators], μια στατική κλάση με χρήσιμες συναρτήσεις για την επικύρωση τιμών. Εγκατάσταση: @@ -11,17 +11,17 @@ composer require nette/utils ``` -Όλα τα παραδείγματα προϋποθέτουν ότι έχει οριστεί το ακόλουθο ψευδώνυμο κλάσης: +Όλα τα παραδείγματα προϋποθέτουν ότι έχει δημιουργηθεί ένα ψευδώνυμο: ```php use Nette\Utils\Validators; ``` -Βασική χρήση .[#toc-basic-usage] -================================ +Βασική χρήση +============ -Η κλάση `Validators` διαθέτει έναν αριθμό μεθόδων για την επικύρωση τιμών, όπως [isList() |#isList()], [isUnicode() |#isUnicode()], [isEmail() |#isEmail()], [isUrl() |#isUrl()], κλπ. για χρήση στον κώδικά σας: +Η κλάση διαθέτει πολλές μεθόδους για τον έλεγχο τιμών, όπως [#isUnicode()], [#isEmail()], [#isUrl()] κ.λπ. για χρήση στον κώδικά σας: ```php if (!Validators::isEmail($email)) { @@ -29,7 +29,7 @@ if (!Validators::isEmail($email)) { } ``` -Επιπλέον, μπορεί να ελέγξει αν η τιμή ικανοποιεί τους λεγόμενους [αναμενόμενους τύπους |#expected types], οι οποίοι είναι μια συμβολοσειρά όπου οι επιμέρους επιλογές χωρίζονται με κάθετη γραμμή `|`. Αυτό καθιστά εύκολη την επαλήθευση των τύπων ένωσης με τη χρήση της [if() |#if()]: +Επιπλέον, μπορεί να επαληθεύσει εάν μια τιμή είναι ένας από τους λεγόμενους [αναμενόμενους τύπους |#Αναμενόμενοι τύποι], που είναι μια συμβολοσειρά όπου οι μεμονωμένες επιλογές διαχωρίζονται με κάθετο `|`. Μπορούμε έτσι εύκολα να επαληθεύσουμε πολλούς τύπους χρησιμοποιώντας το [#is()]: ```php if (!Validators::is($val, 'int|string|bool')) { @@ -37,81 +37,81 @@ if (!Validators::is($val, 'int|string|bool')) { } ``` -Αλλά σας δίνει επίσης τη δυνατότητα να δημιουργήσετε ένα σύστημα όπου είναι απαραίτητο να γράψετε τις προσδοκίες ως συμβολοσειρές (για παράδειγμα σε σημειώσεις ή ρυθμίσεις) και στη συνέχεια να επαληθεύσετε σύμφωνα με αυτές. +Αλλά μας δίνει επίσης τη δυνατότητα να δημιουργήσουμε ένα σύστημα όπου οι προσδοκίες πρέπει να γράφονται ως συμβολοσειρές (για παράδειγμα, σε annotations ή διαμόρφωση) και στη συνέχεια να επαληθεύουμε τις τιμές σύμφωνα με αυτές. -Μπορείτε επίσης να δηλώσετε [ισχυρισμό |#assert], ο οποίος πετάει μια εξαίρεση αν δεν ικανοποιείται. +Για τους αναμενόμενους τύπους, μπορεί επίσης να τεθεί μια απαίτηση [#assert()], η οποία, εάν δεν πληρούται, δημιουργεί μια εξαίρεση. -Αναμενόμενοι τύποι .[#toc-expected-types] -========================================= +Αναμενόμενοι τύποι +================== -Ένας αναμενόμενος τύπος είναι μια συμβολοσειρά που αποτελείται από μία ή περισσότερες παραλλαγές που χωρίζονται με κάθετη γραμμή `|`, similar to writing types in PHP (ie. `'int|string|bool')`. Επιτρέπεται επίσης η μηδενική σημειογραφία `?int`. +Οι αναμενόμενοι τύποι αποτελούν μια συμβολοσειρά που αποτελείται από μία ή περισσότερες παραλλαγές διαχωρισμένες με κάθετο `|`, παρόμοια με τον τρόπο που γράφονται οι τύποι στην PHP (π.χ. `'int|string|bool')`. Η nullable σημειογραφία `?int` είναι επίσης αποδεκτή. -Ένας πίνακας όπου όλα τα στοιχεία είναι ενός συγκεκριμένου τύπου γράφεται με τη μορφή `int[]`. +Ένας πίνακας όπου όλα τα στοιχεία είναι συγκεκριμένου τύπου γράφεται με τη μορφή `int[]`. -Ορισμένοι τύποι μπορούν να ακολουθούνται από άνω και κάτω τελεία και το μήκος `:length` ή το εύρος `:[min]..[max]`, π.χ. `string:10` (ένα αλφαριθμητικό με μήκος 10 bytes), `float:10..` (αριθμός 10 και μεγαλύτερος), `array:..10` (πίνακας με έως και δέκα στοιχεία) ή `list:10..20` (λίστα με 10 έως 20 στοιχεία), ή μια κανονική έκφραση για το `pattern:[0-9]+`. +Μετά από ορισμένους τύπους μπορεί να ακολουθεί διπλή τελεία και μήκος `:length` ή εύρος `:[min]..[max]`, π.χ. `string:10` (συμβολοσειρά μήκους 10 bytes), `float:10..` (αριθμός 10 και μεγαλύτερος), `array:..10` (πίνακας έως δέκα στοιχείων) ή `list:10..20` (λίστα με 10 έως 20 στοιχεία), ή μια κανονική έκφραση για `pattern:[0-9]+`. -Επισκόπηση των τύπων και των κανόνων: +Επισκόπηση τύπων και κανόνων: .[wide] -| PHP types || +| Τύποι PHP || |-------------------------- -| `array` .{width: 140px} | μπορεί να δοθεί εύρος για τον αριθμό των στοιχείων -| `bool` | -| `float` | Το εύρος για την τιμή μπορεί να δοθεί -| `int` | μπορεί να δοθεί εύρος τιμών -| `null` | -| `object` | +| `array` .{width: 140px} | μπορεί να καθοριστεί εύρος για τον αριθμό των στοιχείων +| `bool` | +| `float` | μπορεί να καθοριστεί εύρος για την τιμή +| `int` | μπορεί να καθοριστεί εύρος για την τιμή +| `null` | +| `object` | | `resource` | -| `scalar` | int\|float\|bool\|string -| `string` | μπορεί να δοθεί εύρος για το μήκος σε bytes +| `scalar` | int\|float\|bool\|string +| `string` | μπορεί να καθοριστεί εύρος για το μήκος σε bytes | `callable` | | `iterable` | -| `mixed` | -|------------------------------------------------ -| pseudo-types || -|------------------------------------------------ -| `list` | [ευρετηριασμένος πίνακας |#isList], μπορεί να δοθεί εύρος για τον αριθμό των στοιχείων -| `none` | κενή τιμή: `''`, `null`, `false` -| `number` | int\|float -| `numeric` | [αριθμός με αναπαράσταση κειμένου |#isNumeric] -| `numericint`| [ακέραιος αριθμός με αναπαράσταση κειμένου |#isNumericInt] -| `unicode` | [Συμβολοσειρά UTF-8 |#isUnicode], μπορεί να δοθεί εύρος για το μήκος σε χαρακτήρες -|------------------------------------------------ -| κλάση χαρακτήρων (δεν μπορεί να είναι κενή συμβολοσειρά) || -|------------------------------------------------ -| `alnum` | όλοι οι χαρακτήρες είναι αλφαριθμητικοί -| `alpha` | όλοι οι χαρακτήρες είναι γράμματα `[A-Za-z]` -| `digit` | όλοι οι χαρακτήρες είναι ψηφία -| `lower` | όλοι οι χαρακτήρες είναι πεζά γράμματα `[a-z]` -| `space` | όλοι οι χαρακτήρες είναι κενά -| `upper` | όλοι οι χαρακτήρες είναι κεφαλαία γράμματα `[A-Z]` -| `xdigit` | όλοι οι χαρακτήρες είναι δεκαεξαδικά ψηφία `[0-9A-Fa-f]` +| `mixed` | +|-------------------------- +| ψευδο-τύποι || |------------------------------------------------ -| επικύρωση σύνταξης || +| `list` | ευρετηριασμένος πίνακας, μπορεί να καθοριστεί εύρος για τον αριθμό των στοιχείων +| `none` | κενή τιμή: `''`, `null`, `false` +| `number` | int\|float +| `numeric` | [αριθμός συμπεριλαμβανομένης της κειμενικής αναπαράστασης |#isNumeric] +| `numericint`| [ακέραιος αριθμός συμπεριλαμβανομένης της κειμενικής αναπαράστασης |#isNumericInt] +| `unicode` | [συμβολοσειρά UTF-8 |#isUnicode], μπορεί να καθοριστεί εύρος για το μήκος σε χαρακτήρες +|-------------------------- +| κλάση χαρακτήρων (δεν πρέπει να είναι κενή συμβολοσειρά) || |------------------------------------------------ -| `pattern` | μια κανονική έκφραση με την οποία πρέπει να ταιριάζει η **ολόκληρη** συμβολοσειρά -| `email` | [Email |#isEmail] -| `identifier`| [Αναγνωριστικό PHP |#isPhpIdentifier] -| `url` | [URL |#isUrl] -| `uri` | [URI |#isUri] +| `alnum` | όλοι οι χαρακτήρες είναι αλφαριθμητικοί +| `alpha` | όλοι οι χαρακτήρες είναι γράμματα `[A-Za-z]` +| `digit` | όλοι οι χαρακτήρες είναι ψηφία +| `lower` | όλοι οι χαρακτήρες είναι πεζά γράμματα `[a-z]` +| `space` | όλοι οι χαρακτήρες είναι κενά +| `upper` | όλοι οι χαρακτήρες είναι κεφαλαία γράμματα `[A-Z]` +| `xdigit` | όλοι οι χαρακτήρες είναι δεκαεξαδικά ψηφία `[0-9A-Fa-f]` +|-------------------------- +| έλεγχος σύνταξης || |------------------------------------------------ -| επικύρωση περιβάλλοντος || +| `pattern` | κανονική έκφραση στην οποία πρέπει να αντιστοιχεί **ολόκληρη** η συμβολοσειρά +| `email` | [E-mail |#isEmail] +| `identifier`| [αναγνωριστικό PHP |#isPhpIdentifier] +| `url` | [URL |#isUrl] +| `uri` | [URI |#isUri] +|-------------------------- +| έλεγχος περιβάλλοντος || |------------------------------------------------ -| `class` | is existing class +| `class` | είναι υπάρχουσα κλάση | `interface` | είναι υπάρχουσα διεπαφή -| `directory` | is existing directory -| `file` | είναι υπάρχον αρχείο +| `directory` | είναι υπάρχων κατάλογος +| `file` | είναι υπάρχον αρχείο -Ισχυρισμός .[#toc-assertion] -============================ +Assertion +========= assert($value, string $expected, string $label='variable'): void .[method] -------------------------------------------------------------------------- -Επαληθεύει ότι η τιμή είναι των [αναμενόμενων τύπων |#expected types] που διαχωρίζονται με pipe. Εάν όχι, πετάει εξαίρεση [api:Nette\Utils\AssertionException]. Η λέξη `variable` στο μήνυμα εξαίρεσης μπορεί να αντικατασταθεί από την παράμετρο `$label`. +Επαληθεύει ότι η τιμή είναι ένας από τους [αναμενόμενους τύπους |#Αναμενόμενοι τύποι] που διαχωρίζονται με κάθετο. Εάν όχι, δημιουργεί μια εξαίρεση [api:Nette\Utils\AssertionException]. Η λέξη `variable` στο κείμενο της εξαίρεσης μπορεί να αντικατασταθεί με άλλη χρησιμοποιώντας την παράμετρο `$label`. ```php Validators::assert('Nette', 'string:5'); // OK @@ -120,10 +120,10 @@ Validators::assert('Lorem ipsum dolor sit', 'string:78'); ``` -assertField(array $array, string|int $key, string $expected=null, string $label=null): void .[method] ------------------------------------------------------------------------------------------------------ +assertField(array $array, string|int $key, ?string $expected=null, ?string $label=null): void .[method] +------------------------------------------------------------------------------------------------------- -Επαληθεύει ότι το στοιχείο `$key` στον πίνακα `$array` αποτελείται από τους [αναμενόμενους τύπους |#expected types] που χωρίζονται με pipe. Αν όχι, πετάει την εξαίρεση [api:Nette\Utils\AssertionException]. Η συμβολοσειρά `item '%' in array` στο μήνυμα εξαίρεσης μπορεί να αντικατασταθεί από την παράμετρο `$label`. +Επαληθεύει εάν το στοιχείο με το κλειδί `$key` στον πίνακα `$array` είναι ένας από τους [αναμενόμενους τύπους |#Αναμενόμενοι τύποι] που διαχωρίζονται με κάθετο. Εάν όχι, δημιουργεί μια εξαίρεση [api:Nette\Utils\AssertionException]. Η συμβολοσειρά `item '%' in array` στο κείμενο της εξαίρεσης μπορεί να αντικατασταθεί με άλλη χρησιμοποιώντας την παράμετρο `$label`. ```php $arr = ['foo' => 'Nette']; @@ -136,19 +136,19 @@ Validators::assertField($arr, 'foo', 'int'); ``` -Επικυρωτές .[#toc-validators] -============================= +Επικυρωτές +========== is($value, string $expected): bool .[method] -------------------------------------------- -Ελέγχει αν η τιμή είναι από τους [αναμενόμενους τύπους |#expected types] που διαχωρίζονται με pipe. +Επαληθεύει εάν η τιμή είναι ένας από τους [αναμενόμενους τύπους |#Αναμενόμενοι τύποι] που διαχωρίζονται με κάθετο. ```php Validators::is(1, 'int|float'); // true Validators::is(23, 'int:0..10'); // false -Validators::is('Nette Framework', 'string:15'); // true, length is 15 bytes +Validators::is('Nette Framework', 'string:15'); // true, το μήκος είναι 15 bytes Validators::is('Nette Framework', 'string:8..'); // true Validators::is('Nette Framework', 'string:30..40'); // false ``` @@ -157,7 +157,7 @@ Validators::is('Nette Framework', 'string:30..40'); // false isEmail(mixed $value): bool .[method] ------------------------------------- -Επαληθεύει ότι η τιμή είναι έγκυρη διεύθυνση ηλεκτρονικού ταχυδρομείου. Δεν επαληθεύει την πραγματική ύπαρξη του τομέα, αλλά μόνο τη σύνταξη. Η συνάρτηση υπολογίζει επίσης τα μελλοντικά [TLD |https://en.wikipedia.org/wiki/Top-level_domain], τα οποία μπορεί επίσης να είναι σε unicode. +Επαληθεύει εάν η τιμή είναι μια έγκυρη διεύθυνση e-mail. Δεν επαληθεύεται εάν ο τομέας υπάρχει πραγματικά, επαληθεύεται μόνο η σύνταξη. Η συνάρτηση λαμβάνει υπόψη και μελλοντικά [TLD|https://cs.wikipedia.org/wiki/Doména_nejvyššího_řádu], τα οποία μπορεί να είναι και σε unicode. ```php Validators::isEmail('example@nette.org'); // true @@ -169,7 +169,7 @@ Validators::isEmail('nette'); // false isInRange(mixed $value, array $range): bool .[method] ----------------------------------------------------- -Ελέγχει αν η τιμή βρίσκεται στο συγκεκριμένο εύρος. `[min, max]`, όπου το ανώτερο ή το κατώτερο όριο μπορεί να παραλειφθεί (`null`). Μπορούν να συγκριθούν αριθμοί, συμβολοσειρές και αντικείμενα DateTime. +Επαληθεύει εάν η τιμή βρίσκεται στο δεδομένο εύρος `[min, max]`, όπου το άνω ή το κάτω όριο μπορεί να παραλειφθεί (`null`). Μπορούν να συγκριθούν αριθμοί, συμβολοσειρές και αντικείμενα DateTime. Εάν λείπουν και τα δύο όρια (`[null, null]`) ή η τιμή είναι `null`, επιστρέφει `false`. @@ -184,7 +184,7 @@ Validators::isInRange(1, [5]); // false isNone(mixed $value): bool .[method] ------------------------------------ -Ελέγχει αν η τιμή είναι `0`, `''`, `false` ή `null`. +Επαληθεύει εάν η τιμή είναι `0`, `''`, `false` ή `null`. ```php Validators::isNone(0); // true @@ -198,7 +198,7 @@ Validators::isNone('nette'); // false isNumeric(mixed $value): bool .[method] --------------------------------------- -Ελέγχει αν η τιμή είναι αριθμός ή αριθμός γραμμένος σε συμβολοσειρά. +Επαληθεύει εάν η τιμή είναι αριθμός ή αριθμός γραμμένος σε συμβολοσειρά. ```php Validators::isNumeric(23); // true @@ -213,7 +213,7 @@ Validators::isNumeric('1e6'); // false isNumericInt(mixed $value): bool .[method] ------------------------------------------ -Ελέγχει αν η τιμή είναι ακέραιος αριθμός ή ακέραιος αριθμός γραμμένος σε συμβολοσειρά. +Επαληθεύει εάν η τιμή είναι ακέραιος αριθμός ή αριθμός γραμμένος σε συμβολοσειρά. ```php Validators::isNumericInt(23); // true @@ -227,7 +227,7 @@ Validators::isNumericInt('nette'); // false isPhpIdentifier(string $value): bool .[method] ---------------------------------------------- -Ελέγχει αν η τιμή είναι ένα συντακτικά έγκυρο αναγνωριστικό στην PHP, για παράδειγμα για ονόματα κλάσεων, μεθόδων, συναρτήσεων κ.λπ. +Επαληθεύει εάν η τιμή είναι ένα συντακτικά έγκυρο αναγνωριστικό στην PHP, για παράδειγμα για ονόματα κλάσεων, μεθόδων, συναρτήσεων κ.λπ. ```php Validators::isPhpIdentifier(''); // false @@ -240,7 +240,7 @@ Validators::isPhpIdentifier('one two'); // false isBuiltinType(string $type): bool .[method] ------------------------------------------- -Προσδιορίζει αν το `$type` είναι ενσωματωμένος τύπος της PHP. Διαφορετικά, είναι το όνομα της κλάσης. +Ελέγχει εάν το `$type` είναι ενσωματωμένος τύπος της PHP. Διαφορετικά, είναι όνομα κλάσης. ```php Validators::isBuiltinType('string'); // true @@ -251,7 +251,7 @@ Validators::isBuiltinType('Foo'); // false isTypeDeclaration(string $type): bool .[method] ----------------------------------------------- -Ελέγχει αν η δήλωση τύπου είναι συντακτικά ορθή. +Ελέγχει εάν η δεδομένη δήλωση τύπου είναι συντακτικά έγκυρη. ```php Validators::isTypeDeclaration('?string'); // true @@ -268,7 +268,7 @@ Validators::isTypeDeclaration('(A|B)'); // false isClassKeyword(string $type): bool .[method] -------------------------------------------- -Προσδιορίζει αν το `$type` είναι ένας από τους εσωτερικούς τύπους `self`, `parent`, `static`. +Ελέγχει εάν το `$type` είναι ένας από τους εσωτερικούς τύπους `self`, `parent`, `static`. ```php Validators::isClassKeyword('self'); // true @@ -279,7 +279,7 @@ Validators::isClassKeyword('Foo'); // false isUnicode(mixed $value): bool .[method] --------------------------------------- -Ελέγχει αν η τιμή είναι έγκυρη συμβολοσειρά UTF-8. +Επαληθεύει εάν η τιμή είναι μια έγκυρη συμβολοσειρά UTF-8. ```php Validators::isUnicode('nette'); // true @@ -291,7 +291,7 @@ Validators::isUnicode("\xA0"); // false isUrl(mixed $value): bool .[method] ----------------------------------- -Ελέγχει αν η τιμή είναι έγκυρη διεύθυνση URL. +Επαληθεύει εάν η τιμή είναι μια έγκυρη διεύθυνση URL. ```php Validators::isUrl('https://nette.org:8080/path?query#fragment'); // true @@ -306,7 +306,7 @@ Validators::isUrl('nette.org'); // false isUri(string $value): bool .[method] ------------------------------------ -Ελέγχει αν η τιμή είναι έγκυρη διεύθυνση URI, δηλαδή στην πραγματικότητα μια συμβολοσειρά που αρχίζει με ένα συντακτικά έγκυρο σχήμα. +Επαληθεύει εάν η τιμή είναι μια έγκυρη διεύθυνση URI, δηλαδή ουσιαστικά μια συμβολοσειρά που ξεκινά με ένα συντακτικά έγκυρο σχήμα. ```php Validators::isUri('https://nette.org'); // true diff --git a/utils/en/@home.texy b/utils/en/@home.texy index e2879920a3..e55b0ef0b5 100644 --- a/utils/en/@home.texy +++ b/utils/en/@home.texy @@ -1,11 +1,11 @@ -Utilities -********* +Nette Utils +*********** .[perex] -In package `nette/utils` you will find a set of useful classes for everyday use: +In the `nette/utils` package, you will find a set of useful classes for everyday use: | [Arrays] | Nette\Utils\Arrays -| [Callback] | Nette\Utils\Callback +| [Callbacks |Callback] | Nette\Utils\Callback | [Date and Time |datetime] | Nette\Utils\DateTime | [Filesystem |filesystem] | Nette\Utils\FileSystem | [Finder] | Nette\Utils\Finder @@ -13,13 +13,14 @@ In package `nette/utils` you will find a set of useful classes for everyday use: | [Helpers |helpers] | Nette\Utils\Helpers | [HTML Elements] | Nette\Utils\Html | [Images] | Nette\Utils\Image +| [Iterables] | Nette\Utils\Iterables | [JSON] | Nette\Utils\Json -| [Object model |smartobject] | Nette\SmartObject & Nette\StaticClass | [Paginator |paginator] | Nette\Utils\Paginator | [PHP Reflection |reflection] | Nette\Utils\Reflection | [PHP Types |type] | Nette\Utils\Type | [Random Strings |random] | Nette\Utils\Random | [Strings] | Nette\Utils\Strings +| [SmartObject] & [StaticClass] | Nette\SmartObject & Nette\StaticClass | [Validators |validators] | Nette\Utils\Validators @@ -32,11 +33,14 @@ Download and install the package using [Composer|best-practices:composer]: composer require nette/utils ``` -| version | compatible with PHP -|-----------|------------------- -| Nette Utils 4.0 | PHP 8.0 – 8.2 -| Nette Utils 3.2 | PHP 7.2 – 8.2 +| version | compatible with PHP +|-------------------|------------------- +| Nette Utils 4.0 | PHP 8.0 – 8.4 +| Nette Utils 3.2 | PHP 7.2 – 8.3 | Nette Utils 3.0 – 3.1 | PHP 7.1 – 8.0 -| Nette Utils 2.5 | PHP 5.6 – 8.0 +| Nette Utils 2.5 | PHP 5.6 – 8.0 Applies to the latest patch versions. + + +If you are upgrading to a newer version, see the [upgrading] page. diff --git a/utils/en/@left-menu.texy b/utils/en/@left-menu.texy index 249f02c4f6..c614d7a438 100644 --- a/utils/en/@left-menu.texy +++ b/utils/en/@left-menu.texy @@ -1,21 +1,23 @@ -Package nette/utils -******************* +Nette Utils +*********** - [Arrays] -- [Callback] +- [Callbacks |callback] - [Date and Time |datetime] - [Filesystem |filesystem] - [Finder] +- [Floats] - [Helpers |helpers] - [HTML Elements] - [Images] +- [Iterables] - [JSON] -- [Paginator |paginator] -- [Random Strings |random] -- [SmartObject] +- [Paginator] - [PHP Reflection |reflection] -- [Strings] -- [Floats] - [PHP Types |type] +- [Random Strings |random] +- [Strings] +- [SmartObject] +- [StaticClass] - [Validators |validators] diff --git a/utils/en/@meta.texy b/utils/en/@meta.texy new file mode 100644 index 0000000000..42471908b0 --- /dev/null +++ b/utils/en/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Documentation}} diff --git a/utils/en/arrays.texy b/utils/en/arrays.texy index 5e4e5f9718..c6abf68903 100644 --- a/utils/en/arrays.texy +++ b/utils/en/arrays.texy @@ -2,7 +2,7 @@ Array Functions *************** .[perex] -This page is about the [Nette\Utils\Arrays|#Arrays], [#ArrayHash] and [#ArrayList] classes, which are related to arrays. +This page covers the [Nette\Utils\Arrays |#Arrays], [#ArrayHash], and [#ArrayList] classes, which relate to arrays. Installation: @@ -15,19 +15,60 @@ composer require nette/utils Arrays ====== -[api:Nette\Utils\Arrays] is a static class, which contains a handful of handy array functions. +[api:Nette\Utils\Arrays] is a static class containing useful functions for working with arrays. Its equivalent for iterators is [Nette\Utils\Iterables |iterables]. -Following examples assume the following class alias is defined: +The following examples assume the following class alias is defined: ```php use Nette\Utils\Arrays; ``` +associate(array $array, mixed $path): array|\stdClass .[method] +--------------------------------------------------------------- + +This function flexibly transforms the `$array` into an associative array or objects according to the specified path `$path`. The path can be a string or an array. It consists of the names of keys in the input array and operators like `[]`, `->`, `=`, and `|`. Throws `Nette\InvalidArgumentException` if the path is invalid. + +```php +// convert to an associative array using a simple key +$arr = [ + ['name' => 'John', 'age' => 11], + ['name' => 'Mary', 'age' => null], + // ... +]; +$result = Arrays::associate($arr, 'name'); +// $result = ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// assigning values from one key to another using the = operator +$result = Arrays::associate($arr, 'name=age'); // or ['name', '=', 'age'] +// $result = ['John' => 11, 'Mary' => null, ...] +``` + +```php +// creating an object using the -> operator +$result = Arrays::associate($arr, '->name'); // or ['->', 'name'] +// $result = (object) ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// combining keys using the | operator +$result = Arrays::associate($arr, 'name|age'); // or ['name', '|', 'age'] +// $result: ['John' => ['name' => 'John', 'age' => 11], 'Paul' => ['name' => 'Paul', 'age' => 44]] +``` + +```php +// adding to an array using [] +$result = Arrays::associate($arr, 'name[]'); // or ['name', '[]'] +// $result: ['John' => [['name' => 'John', 'age' => 22], ['name' => 'John', 'age' => 11]]] +``` + + contains(array $array, $value): bool .[method] ---------------------------------------------- -Tests an array for the presence of value. Uses a strict comparison (`===`) +Tests an array for the presence of a value. Uses strict comparison (`===`). ```php Arrays::contains([1, 2, 3], 1); // true @@ -35,10 +76,10 @@ Arrays::contains(['1', false], 1); // false ``` -every(iterable $array, callable $callback): bool .[method] ----------------------------------------------------------- +every(array $array, callable $predicate): bool .[method] +-------------------------------------------------------- -Tests whether all elements in the array pass the test implemented by the provided function, which has the signature `function ($value, $key, array $array): bool`. +Tests whether all elements in the array pass the test implemented by the provided `$predicate` function with the signature `function ($value, $key, array $array): bool`. ```php $array = [1, 30, 39, 29, 10, 13]; @@ -49,21 +90,56 @@ $res = Arrays::every($array, $isBelowThreshold); // true See [#some()]. -first(array $array): mixed .[method] ------------------------------------- +filter(array $array, callable $predicate): array .[method]{data-version:4.0.4} +------------------------------------------------------------------------------ + +Returns a new array containing all key-value pairs matching the given `$predicate`. The callback has the signature `function ($value, int|string $key, array $array): bool`. + +```php +Arrays::filter( + ['a' => 1, 'b' => 2, 'c' => 3], + fn($v) => $v < 3, +); +// returns ['a' => 1, 'b' => 2] +``` + + +first(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------- + +Returns the first item (matching the specified predicate if given). If there is no such item, it returns the result of invoking `$else` or null. The `$predicate` has the signature `function ($value, int|string $key, array $array): bool`. + +It does not change the internal pointer, unlike `reset()`. The `$predicate` and `$else` parameters exist since version 4.0.4. + +```php +Arrays::first([1, 2, 3]); // 1 +Arrays::first([1, 2, 3], fn($v) => $v > 2); // 3 +Arrays::first([]); // null +Arrays::first([], else: fn() => false); // false +``` -Returns the first item from the array or null if array is empty. It does not change the internal pointer unlike `reset()`. +See [#last()]. + + +firstKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +------------------------------------------------------------------------------------------------ + +Returns the key of the first item (matching the specified predicate if given) or null if there is no such item. The `$predicate` has the signature `function ($value, int|string $key, array $array): bool`. ```php -Arrays::first([1, 2, 3]); // 1 -Arrays::first([]); // null +Arrays::firstKey([1, 2, 3]); // 0 +Arrays::firstKey([1, 2, 3], fn($v) => $v > 2); // 2 +Arrays::firstKey(['a' => 1, 'b' => 2]); // 'a' +Arrays::firstKey([]); // null ``` +See [#lastKey()]. + flatten(array $array, bool $preserveKeys=false): array .[method] ---------------------------------------------------------------- -Transforms multidimensional array to flat array. +Flattens a multi-level array into a single level. Keys can be preserved if `$preserveKeys` is set to true. ```php $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); @@ -74,7 +150,7 @@ $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); get(array $array, string|int|array $key, mixed $default=null): mixed .[method] ------------------------------------------------------------------------------ -Returns `$array[$key]` item. If it does not exist, `Nette\InvalidArgumentException` is thrown, unless a default value is set as third argument. +Returns the item `$array[$key]`. If it does not exist, it throws `Nette\InvalidArgumentException`, unless a default value is provided as the third argument, which is then returned. ```php // if $array['foo'] does not exist, throws an exception @@ -84,7 +160,7 @@ $value = Arrays::get($array, 'foo'); $value = Arrays::get($array, 'foo', 'bar'); ``` -Argument `$key` may as well be an array. +The `$key` can also be an array representing a path into a nested array. ```php $array = ['color' => ['favorite' => 'red'], 5]; @@ -97,36 +173,36 @@ $value = Arrays::get($array, ['color', 'favorite']); getRef(array &$array, string|int|array $key): mixed .[method] ------------------------------------------------------------- -Gets reference to given `$array[$key]`. If the index does not exist, new one is created with value `null`. +Gets a reference to the specified array item. If the item does not exist, it will be created with a value of null. ```php $valueRef = & Arrays::getRef($array, 'foo'); -// returns $array['foo'] reference +// returns a reference to $array['foo'] ``` -Works with multidimensional arrays as well as [get() |#get()]. +Works with multidimensional arrays as well as [#get()]. ```php -$value = & Arrays::get($array, ['color', 'favorite']); -// returns $array['color']['favorite'] reference +$value = & Arrays::getRef($array, ['color', 'favorite']); +// returns a reference to $array['color']['favorite'] ``` grep(array $array, string $pattern, bool $invert=false): array .[method] ------------------------------------------------------------------------ -Returns only those array items, which matches a regular expression `$pattern`. If `$invert` is `true`, it returns elements that do not match. Regex compilation or runtime error throws `Nette\RegexpException`. +Returns only those array items whose value matches the regular expression `$pattern`. If `$invert` is `true`, it returns items that do *not* match. A compilation or runtime error in the expression throws `Nette\RegexpException`. ```php $filteredArray = Arrays::grep($array, '~^\d+$~'); -// returns only numerical items +// returns only array elements consisting of digits ``` insertAfter(array &$array, string|int|null $key, array $inserted): void .[method] --------------------------------------------------------------------------------- -Inserts the contents of the `$inserted` array into the `$array` immediately after the `$key`. If `$key` is `null` (or does not exist), it is inserted at the end. +Inserts the contents of the `$inserted` array into the `$array` immediately after the item with key `$key`. If `$key` is `null` (or does not exist in the array), it is inserted at the end. ```php $array = ['first' => 10, 'second' => 20]; @@ -138,7 +214,7 @@ Arrays::insertAfter($array, 'first', ['hello' => 'world']); insertBefore(array &$array, string|int|null $key, array $inserted): void .[method] ---------------------------------------------------------------------------------- -Inserts the contents of the `$inserted` array into the `$array` before the `$key`. If `$key` is `null` (or does not exist), it is inserted at the beginning. +Inserts the contents of the `$inserted` array into the `$array` before the item with key `$key`. If `$key` is `null` (or does not exist in the array), it is inserted at the beginning. ```php $array = ['first' => 10, 'second' => 20]; @@ -150,7 +226,7 @@ Arrays::insertBefore($array, 'first', ['hello' => 'world']); invoke(iterable $callbacks, ...$args): array .[method] ------------------------------------------------------ -Invokes all callbacks and returns array of results. +Invokes all callbacks in the iterable and returns an array of the results. ```php $callbacks = [ @@ -166,7 +242,7 @@ $array = Arrays::invoke($callbacks, 5, 11); invokeMethod(iterable $objects, string $method, ...$args): array .[method] -------------------------------------------------------------------------- -Invokes method on every object in an array and returns array of results. +Invokes a method on each object in an iterable and returns an array of the results. ```php $objects = ['a' => $obj1, 'b' => $obj2]; @@ -179,7 +255,7 @@ $array = Arrays::invokeMethod($objects, 'foo', 1, 2); isList(array $array): bool .[method] ------------------------------------ -Checks if the array is indexed in ascending order of numeric keys from zero, a.k.a list. +Checks if the array is indexed in ascending order of numeric keys from zero, a.k.a., a list. ```php Arrays::isList(['a', 'b', 'c']); // true @@ -188,21 +264,42 @@ Arrays::isList(['a' => 1, 'b' => 2]); // false ``` -last(array $array): mixed .[method] ------------------------------------ +last(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------ + +Returns the last item (matching the specified predicate if given). If there is no such item, it returns the result of invoking `$else` or null. The `$predicate` has the signature `function ($value, int|string $key, array $array): bool`. -Returns the last item from the array or null if array is empty. It does not change the internal pointer unlike `end()`. +It does not change the internal pointer, unlike `end()`. The `$predicate` and `$else` parameters exist since version 4.0.4. ```php -Arrays::last([1, 2, 3]); // 3 -Arrays::last([]); // null +Arrays::last([1, 2, 3]); // 3 +Arrays::last([1, 2, 3], fn($v) => $v < 3); // 2 +Arrays::last([]); // null +Arrays::last([], else: fn() => false); // false ``` +See [#first()]. -map(iterable $array, callable $callback): array .[method] + +lastKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +----------------------------------------------------------------------------------------------- + +Returns the key of the last item (matching the specified predicate if given) or null if there is no such item. The `$predicate` has the signature `function ($value, int|string $key, array $array): bool`. + +```php +Arrays::lastKey([1, 2, 3]); // 2 +Arrays::lastKey([1, 2, 3], fn($v) => $v < 3); // 1 +Arrays::lastKey(['a' => 1, 'b' => 2]); // 'b' +Arrays::lastKey([]); // null +``` + +See [#firstKey()]. + + +map(array $array, callable $transformer): array .[method] --------------------------------------------------------- -Calls `$callback` on all elements in the array and returns the array of return values. The callback has the signature `function ($value, $key, array $array): bool`. +Calls `$transformer` on all elements in the array and returns an array of the return values. The callback has the signature `function ($value, $key, array $array): mixed`. ```php $array = ['foo', 'bar', 'baz']; @@ -211,10 +308,24 @@ $res = Arrays::map($array, fn($value) => $value . $value); ``` +mapWithKeys(array $array, callable $transformer): array .[method] +----------------------------------------------------------------- + +Creates a new array by transforming the values and keys of the original array. The `$transformer` function has the signature `function ($value, $key, array $array): ?array{$newKey, $newValue}`. If `$transformer` returns `null`, the element is skipped. For retained elements, the first element of the returned array is used as the new key, and the second element as the new value. + +```php +$array = ['a' => 1, 'b' => 2]; +$result = Arrays::mapWithKeys($array, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +This method is useful in situations where you need to change the structure of an array (both keys and values simultaneously) or filter elements during transformation (by returning null for unwanted elements). + + mergeTree(array $array1, array $array2): array .[method] -------------------------------------------------------- -Recursively merges two fields. It is useful, for example, for merging tree structures. It behaves as the `+` operator for array, ie. it adds a key/value pair from the second array to the first one and retains the value from the first array in the case of a key collision. +Recursively merges two arrays. It is useful, for example, for merging tree structures. It follows the same rules as the `+` operator applied to arrays, i.e., it adds key/value pairs from the second array to the first, and in case of key collisions, it keeps the value from the first array. ```php $array1 = ['color' => ['favorite' => 'red'], 5]; @@ -224,13 +335,13 @@ $array = Arrays::mergeTree($array1, $array2); // $array = ['color' => ['favorite' => 'red', 'blue'], 5]; ``` -Values from the second array are always appended to the first. The disappearance of the value `10` from the second array may seem a bit confusing. It should be noted that this value as well as the value `5` in the first array have the same numeric key `0`, so in the resulting field there is only an element from the first array. +Values from the second array are always appended to the end of the first. The disappearance of the value `10` from the second array might seem a bit confusing. It's important to realize that this value, as well as the value `5` in the first array, are assigned the same numeric key `0`. Therefore, the resulting array only contains the element from the first array for this key. -normalize(array $array, string $filling=null): array .[method] --------------------------------------------------------------- +normalize(array $array, ?string $filling=null): array .[method] +--------------------------------------------------------------- -Normalizes array to associative array. Replace numeric keys with their values, the new value will be `$filling`. +Normalizes an array into an associative array. Numeric keys are replaced by their values, and the new value becomes `$filling`. ```php $array = Arrays::normalize([1 => 'first', 'a' => 'second']); @@ -246,11 +357,11 @@ $array = Arrays::normalize([1 => 'first', 'a' => 'second'], 'foobar'); pick(array &$array, string|int $key, mixed $default=null): mixed .[method] -------------------------------------------------------------------------- -Returns and removes the value of an item from an array. If it does not exist, it throws an exception, or returns `$default`, if provided. +Returns and removes the value of an item with key `$key` from an array. If the item does not exist, it throws an exception, or returns `$default` if provided. ```php -$array = [1 => 'foo', null => 'bar']; -$a = Arrays::pick($array, null); +$array = [1 => 'foo', 'x' => 'bar']; +$a = Arrays::pick($array, 'x'); // $a = 'bar' $b = Arrays::pick($array, 'not-exists', 'foobar'); // $b = 'foobar' @@ -262,7 +373,7 @@ $c = Arrays::pick($array, 'not-exists'); renameKey(array &$array, string|int $oldKey, string|int $newKey): bool .[method] -------------------------------------------------------------------------------- -Renames a key. Returns `true` if the key was found in the array. +Renames a key in an array. Returns `true` if the key was found in the array. ```php $array = ['first' => 10, 'second' => 20]; @@ -274,7 +385,7 @@ Arrays::renameKey($array, 'first', 'renamed'); getKeyOffset(array $array, string|int $key): ?int .[method] ----------------------------------------------------------- -Returns zero-indexed position of given array key. Returns `null` if key is not found. +Returns the zero-indexed position of the given array key. Returns `null` if the key is not found. ```php $array = ['first' => 10, 'second' => 20]; @@ -284,10 +395,10 @@ $position = Arrays::getKeyOffset($array, 'not-exists'); // returns null ``` -some(iterable $array, callable $callback): bool .[method] ---------------------------------------------------------- +some(array $array, callable $predicate): bool .[method] +------------------------------------------------------- -Tests whether at least one element in the array passes the test implemented by the provided callback with signature `function ($value, $key, array $array): bool`. +Tests whether at least one element in the array passes the test implemented by the provided `$predicate` callback with the signature `function ($value, $key, array $array): bool`. ```php $array = [1, 2, 3, 4]; @@ -301,7 +412,7 @@ See [#every()]. toKey(mixed $key): string|int .[method] --------------------------------------- -Converts a value to an array key, which is either an integer or a string. +Converts a value to an array key, which is either an integer or a string. Floats are truncated, booleans are converted to `0` or `1`, null becomes an empty string, and objects throw an exception. ```php Arrays::toKey('1'); // 1 @@ -312,7 +423,7 @@ Arrays::toKey('01'); // '01' toObject(iterable $array, object $object): object .[method] ----------------------------------------------------------- -Copies the elements of the `$array` array to the `$object` object and then returns it. +Copies the elements of the iterable `$array` to the `$object` object and then returns the object. ```php $obj = new stdClass; @@ -321,10 +432,10 @@ Arrays::toObject($array, $obj); // it sets $obj->foo = 1; $obj->bar = 2; ``` -wrap(iterable $array, string $prefix='', string $suffix=''): array .[method] ----------------------------------------------------------------------------- +wrap(array $array, string $prefix='', string $suffix=''): array .[method] +------------------------------------------------------------------------- -It casts each element of array to string and encloses it with `$prefix` and `$suffix`. +Casts each item in the array to a string and wraps it with the `$prefix` and `$suffix`. ```php $array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); @@ -335,18 +446,18 @@ $array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); ArrayHash ========= -Object [api:Nette\Utils\ArrayHash] is the descendant of generic class stdClass and extends it to the ability to treat it as an array, for example, accessing members using square brackets: +The [api:Nette\Utils\ArrayHash] object is a descendant of the generic `stdClass` class and extends it with the ability to be treated like an array, for example, accessing members using square brackets: ```php $hash = new Nette\Utils\ArrayHash; $hash['foo'] = 123; -$hash->bar = 456; // also works object notation +$hash->bar = 456; // object notation also works $hash->foo; // 123 ``` You can use the `count($hash)` function to get the number of elements. -You can iterate over an object as you would an array, even with a reference: +You can iterate over the object as you would an array, even with a reference: ```php foreach ($hash as $key => $value) { @@ -358,7 +469,7 @@ foreach ($hash as $key => &$value) { } ``` -Existing arrays can be transformed to `ArrayHash` using `from()`: +Existing arrays can be transformed into an `ArrayHash` using the static method `from()`: ```php $array = ['foo' => 123, 'bar' => 456]; @@ -368,7 +479,7 @@ $hash->foo; // 123 $hash->bar; // 456 ``` -The transformation is recursive: +The transformation is recursive: any sub-arrays are also converted to `ArrayHash` objects. ```php $array = ['foo' => 123, 'inner' => ['a' => 'b']]; @@ -379,14 +490,14 @@ $hash->inner->a; // 'b' $hash['inner']['a']; // 'b' ``` -It can be avoided by the second parameter: +This recursive behavior can be disabled with the second parameter: ```php $hash = Nette\Utils\ArrayHash::from($array, false); $hash->inner; // array ``` -Transform back to the array: +Transform back to an array using `(array)` cast: ```php $array = (array) $hash; @@ -407,9 +518,16 @@ $list[] = 'c'; count($list); // 3 ``` +Existing arrays can be transformed into `ArrayList` using the static method `from()`: + +```php +$array = ['foo', 'bar']; +$list = Nette\Utils\ArrayList::from($array); +``` + You can use the `count($list)` function to get the number of items. -You can iterate over an object as you would an array, even with a reference: +You can iterate over the object as you would an array, even with a reference: ```php foreach ($list as $key => $value) { @@ -421,28 +539,21 @@ foreach ($list as $key => &$value) { } ``` -Existing arrays can be transformed to `ArrayList` using `from()`: - -```php -$array = ['foo', 'bar']; -$list = Nette\Utils\ArrayList::from($array); -``` - -Accessing keys beyond the allowed values throws an exception `Nette\OutOfRangeException`: +Accessing keys outside the allowed range (0 to count-1) or attempting to set non-integer keys throws an `Nette\OutOfRangeException`: ```php echo $list[-1]; // throws Nette\OutOfRangeException unset($list[30]); // throws Nette\OutOfRangeException ``` -Removing the key will result in renumbering the elements: +Removing a key causes the elements to be renumbered: ```php unset($list[1]); // ArrayList(0 => 'a', 1 => 'c') ``` -You can add a new element to the beginning using `prepend()`: +You can add a new element to the beginning using the `prepend()` method: ```php $list->prepend('d'); diff --git a/utils/en/callback.texy b/utils/en/callback.texy index 02b91c61e4..e70b83458f 100644 --- a/utils/en/callback.texy +++ b/utils/en/callback.texy @@ -2,7 +2,7 @@ Callback Functions ****************** .[perex] -[api:Nette\Utils\Callback] is a static class, which contains functions for working with [PHP callbacks |https://www.php.net/manual/en/language.types.callable.php]. +[api:Nette\Utils\Callback] is a static class containing functions for working with [PHP callbacks |https://www.php.net/manual/en/language.types.callable.php]. Installation: @@ -21,7 +21,7 @@ use Nette\Utils\Callback; check($callable, bool $syntax=false): callable .[method] -------------------------------------------------------- -Checks that `$callable` is valid PHP callback. Otherwise throws `Nette\InvalidArgumentException`. If the `$syntax` is set to true, the function only verifies that `$callable` has a valid structure to be used as a callback, but does not verify if the class or method actually exists. Returns `$callable`. +Checks if `$callable` is a valid PHP callback. Otherwise, it throws `Nette\InvalidArgumentException`. If `$syntax` is set to `true`, the function only verifies that `$callable` has a valid callback structure but does not check if the class or method actually exists. Returns the original `$callable`. ```php Callback::check('trim'); // no exception @@ -32,10 +32,10 @@ Callback::check(null); // throws Nette\InvalidArgumentException ``` -toString($callable): string .[method] -------------------------------------- +toString(callable $callable): string .[method] +---------------------------------------------- -Converts PHP callback to textual form. Class or method may not exists. +Converts a PHP callback to its textual representation. The class or method does not need to exist. ```php Callback::toString('trim'); // 'trim' @@ -43,10 +43,10 @@ Callback::toString(['MyClass', 'method']); // 'MyClass::method' ``` -toReflection($callable): ReflectionMethod|ReflectionFunction .[method] ----------------------------------------------------------------------- +toReflection(callable $callable): \ReflectionMethod|\ReflectionFunction .[method] +--------------------------------------------------------------------------------- -Returns reflection for method or function used in PHP callback. +Returns a reflection for the method or function used in the PHP callback. Throws `Nette\InvalidArgumentException` if the callback is not valid or does not reference an existing method/function. ```php $ref = Callback::toReflection('trim'); @@ -57,10 +57,10 @@ $ref = Callback::toReflection(['MyClass', 'method']); ``` -isStatic($callable): bool .[method] ------------------------------------ +isStatic(callable $callable): bool .[method] +-------------------------------------------- -Checks whether PHP callback is function or static method. +Checks whether the PHP callback is a global function or a static method. Throws `Nette\InvalidArgumentException` if the callback is not valid or does not reference an existing method/function. ```php Callback::isStatic('trim'); // true @@ -70,10 +70,10 @@ Callback::isStatic(function () {}); // false ``` -unwrap(Closure $closure): callable|array .[method] --------------------------------------------------- +unwrap(\Closure $closure): callable|array .[method] +--------------------------------------------------- -Unwraps closure created by `Closure::fromCallable`:https://www.php.net/manual/en/closure.fromcallable.php. +Unwraps a closure created by `Closure::fromCallable()`. Returns the original callable array or string. ```php $closure = Closure::fromCallable(['MyClass', 'method']); diff --git a/utils/en/datetime.texy b/utils/en/datetime.texy index b2be48fba5..5b00d789d3 100644 --- a/utils/en/datetime.texy +++ b/utils/en/datetime.texy @@ -2,8 +2,11 @@ Date and Time ************* .[perex] -[api:Nette\Utils\DateTime] is a class extends native [php:DateTime]. +[api:Nette\Utils\DateTime] is a class that extends the native [php:DateTime] with additional useful features. +Compared to the native class, it is strict. While PHP **silently accepts** invalid dates like `0000-00-00` (converts to `-0001-11-30`) or `2024-02-31` (converts to `2024-03-02`), `Nette\Utils\DateTime` throws an exception in such cases. + +It also **fixes the behavior** during Daylight Saving Time (DST) transitions, where in native PHP adding a relative time (e.g., `+100 minutes`) can "result in an earlier time":https://phpfashion.com/en/100-minutes-is-less-than-50-php-paradoxes-during-time-changes than adding a shorter period (e.g., `+50 minutes`). Nette ensures that arithmetic works intuitively and `+100 minutes` is always more than `+50 minutes`. Installation: @@ -18,34 +21,45 @@ use Nette\Utils\DateTime; ``` -static from(string|int|\DateTimeInterface $time): DateTime .[method] --------------------------------------------------------------------- -Creates a DateTime object from a string, UNIX timestamp, or other [php:DateTimeInterface] object. Throws an `Exception` if the date and time are not valid. +static from(string|int|\DateTimeInterface $time): static .[method] +------------------------------------------------------------------ +Creates a `DateTime` object from a string, UNIX timestamp, or another [php:DateTimeInterface] object. Throws an `\Exception` if the date and time are not valid. ```php -DateTime::from(1138013640); // creates a DateTime from the UNIX timestamp with a default timezamp +DateTime::from(1138013640); // creates a DateTime from the UNIX timestamp with the default timezone DateTime::from(42); // creates a DateTime from the current time plus 42 seconds DateTime::from('1994-02-26 04:15:32'); // creates a DateTime based on a string -DateTime::from('1994-02-26'); // create DateTime by date, time will be 00:00:00 +DateTime::from('1994-02-26'); // creates DateTime from date, time will be 00:00:00 ``` -static fromParts(int $year, int $month, int $day, int $hour=0, int $minute=0, float $second=0.0): DateTime .[method] --------------------------------------------------------------------------------------------------------------------- -Creates DateTime object or throws an `Nette\InvalidArgumentException` exception if the date and time are not valid. +static fromParts(int $year, int $month, int $day, int $hour=0, int $minute=0, float $second=0.0): static .[method] +------------------------------------------------------------------------------------------------------------------ +Creates a `DateTime` object or throws an `Nette\InvalidArgumentException` if the date and time are not valid. ```php DateTime::fromParts(1994, 2, 26, 4, 15, 32); ``` -static createFromFormat(string $format, string $time, string|\DateTimeZone $timezone=null): DateTime|false .[method] --------------------------------------------------------------------------------------------------------------------- -Extends [DateTime::createFromFormat()|https://www.php.net/manual/en/datetime.createfromformat.php] with the ability to specify a timezone as a string. +static createFromFormat(string $format, string $time, string|\DateTimeZone|null $timezone=null): static|false .[method] +----------------------------------------------------------------------------------------------------------------------- +Extends [php:DateTime::createFromFormat] with the ability to specify a timezone as a string. ```php DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); // create with custom timezone ``` +static relativeToSeconds(string $str): int .[method]{data-version:4.0.7} +------------------------------------------------------------------------ +Converts a relative time string to seconds. It is useful for converting times like `5 minutes` or `2 hours` to a numeric value. + +```php +DateTime::relativeToSeconds('1 minute'); // 60 +DateTime::relativeToSeconds('10 minutes'); // 600 +DateTime::relativeToSeconds('-1 hour'); // -3600 +``` + + modifyClone(string $modify=''): static .[method] ------------------------------------------------ Creates a copy with a modified time. @@ -65,9 +79,9 @@ echo $dateTime; // '2017-02-03 04:15:32' ``` -Implements JsonSerializable ---------------------------- -Returns the date and time in ISO 8601 format, which is used in JavaScript, for example. +implements JsonSerializable .[method] +------------------------------------- +Returns the date and time in ISO 8601 format (e.g., `2017-02-03T04:15:32.123+01:00`), which is commonly used in JavaScript. ```php $date = DateTime::from('2017-02-03'); echo json_encode($date); diff --git a/utils/en/filesystem.texy b/utils/en/filesystem.texy index d7fc8b600b..7c66d4c3e3 100644 --- a/utils/en/filesystem.texy +++ b/utils/en/filesystem.texy @@ -2,16 +2,18 @@ Filesystem Functions ******************** .[perex] -[api:Nette\Utils\FileSystem] is a static class, which contains useful functions for working with a filesystem. One advantage over native PHP functions is that they throw exceptions in case of errors. +[api:Nette\Utils\FileSystem] is a class with useful functions for working with the file system. One advantage over native PHP functions is that they throw exceptions in case of errors. +If you need to search for files on the disk, use [Finder|finder]. + Installation: ```shell composer require nette/utils ``` -Following examples assume the following class alias is defined: +The following examples assume the following class alias is defined: ```php use Nette\Utils\FileSystem; @@ -25,7 +27,7 @@ Manipulation copy(string $origin, string $target, bool $overwrite=true): void .[method] -------------------------------------------------------------------------- -Copies a file or an entire directory. Overwrites existing files and directories by default. If `$overwrite` is set to `false` and a `$target` already exists, throws an exception `Nette\InvalidStateException`. Throws an exception `Nette\IOException` on error occurred. +Copies a file or an entire directory. Overwrites existing files and directories by default. If `$overwrite` is set to `false` and the target file or directory `$target` already exists, it throws a `Nette\InvalidStateException`. Throws a `Nette\IOException` on error. ```php FileSystem::copy('/path/to/source', '/path/to/dest', overwrite: true); @@ -35,7 +37,7 @@ FileSystem::copy('/path/to/source', '/path/to/dest', overwrite: true); createDir(string $directory, int $mode=0777): void .[method] ------------------------------------------------------------ -Creates a directory if it does not exist, including parent directories. Throws an exception `Nette\IOException` on error occurred. +Creates a directory if it does not exist, including parent directories. Throws a `Nette\IOException` on error. ```php FileSystem::createDir('/path/to/dir'); @@ -45,7 +47,7 @@ FileSystem::createDir('/path/to/dir'); delete(string $path): void .[method] ------------------------------------ -Deletes a file or an entire directory if exists. If the directory is not empty, it deletes its contents first. Throws an exception `Nette\IOException` on error occurred. +Deletes a file or an entire directory if it exists. If the directory is not empty, it deletes its contents first. Throws a `Nette\IOException` on error. ```php FileSystem::delete('/path/to/fileOrDir'); @@ -55,7 +57,7 @@ FileSystem::delete('/path/to/fileOrDir'); makeWritable(string $path, int $dirMode=0777, int $fileMode=0666): void .[method] --------------------------------------------------------------------------------- -Sets file permissions to `$fileMode` or directory permissions to `$dirMode`. Recursively traverses and sets permissions on the entire contents of the directory as well. +Sets file permissions to `$fileMode` or directory permissions to `$dirMode`. Recursively traverses and sets permissions for the entire directory content as well. ```php FileSystem::makeWritable('/path/to/fileOrDir'); @@ -65,7 +67,7 @@ FileSystem::makeWritable('/path/to/fileOrDir'); open(string $path, string $mode): resource .[method] ---------------------------------------------------- -Opens file and returns resource. The `$mode` parameter works the same as the native `fopen()`:https://www.php.net/manual/en/function.fopen.php function. If an error occurs, it raises the `Nette\IOException` exception. +Opens a file and returns a resource handle. The `$mode` parameter works the same as the native `fopen()`:https://www.php.net/manual/en/function.fopen.php function. Throws a `Nette\IOException` on error. ```php $res = FileSystem::open('/path/to/file', 'r'); @@ -75,7 +77,7 @@ $res = FileSystem::open('/path/to/file', 'r'); read(string $file): string .[method] ------------------------------------ -Reads the content of a `$file`. Throws an exception `Nette\IOException` on error occurred. +Reads the content of a file `$file`. Throws a `Nette\IOException` on error. ```php $content = FileSystem::read('/path/to/file'); @@ -85,8 +87,7 @@ $content = FileSystem::read('/path/to/file'); readLines(string $file, bool $stripNewLines=true): \Generator .[method] ----------------------------------------------------------------------- -Reads the file content line by line. Unlike the native `file()` function, it does not read the entire file into memory, but reads it continuously, so that files larger than the available memory can be read. The `$stripNewLines` specifies whether to strip the `\r` and `\n` line break characters. -In case of an error, it raises a `Nette\IOException` exception. +Reads the file content line by line. Unlike the native `file()` function, it does not load the entire file into memory but reads it continuously, allowing you to read files larger than the available memory. `$stripNewLines` specifies whether to remove the line break characters `\r` and `\n`. Throws a `Nette\IOException` on error. ```php $lines = FileSystem::readLines('/path/to/file'); @@ -100,7 +101,7 @@ foreach ($lines as $lineNum => $line) { rename(string $origin, string $target, bool $overwrite=true): void .[method] ---------------------------------------------------------------------------- -Renames or moves a file or a directory specified by `$origin` to `$target`. Overwrites existing files and directories by default. If `$overwrite` is set to `false` and `$target` already exists, throws an exception `Nette\InvalidStateException`. Throws an exception `Nette\IOException` on error occurred. +Renames or moves a file or directory specified by `$origin` to `$target`. Overwrites existing files and directories by default. If `$overwrite` is set to `false` and the target file or directory `$target` already exists, it throws a `Nette\InvalidStateException`. Throws a `Nette\IOException` on error. ```php FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); @@ -110,7 +111,7 @@ FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); write(string $file, string $content, int $mode=0666): void .[method] -------------------------------------------------------------------- -Writes the `$content` to a `$file`. Throws an exception `Nette\IOException` on error occurred. +Writes the string `$content` to the file `$file`. Throws a `Nette\IOException` on error. ```php FileSystem::write('/path/to/file', $content); @@ -124,7 +125,7 @@ Paths isAbsolute(string $path): bool .[method] ---------------------------------------- -Determines if the `$path` is absolute. +Determines if the path `$path` is absolute. ```php FileSystem::isAbsolute('../backup'); // false @@ -135,7 +136,7 @@ FileSystem::isAbsolute('C:/backup'); // true joinPaths(string ...$segments): string .[method] ------------------------------------------------ -Joins all segments of the path and normalizes the result. +Joins all path segments and normalizes the result. ```php FileSystem::joinPaths('a', 'b', 'file.txt'); // 'a/b/file.txt' @@ -146,7 +147,7 @@ FileSystem::joinPaths('/a/', '/../b'); // '/b' normalizePath(string $path): string .[method] --------------------------------------------- -Normalizes `..` and `.` and directory separators in path. +Normalizes `..`, `.`, and directory separators in the path to the system's standard. ```php FileSystem::normalizePath('/file/.'); // '/file/' @@ -169,8 +170,45 @@ $path = FileSystem::unixSlashes($path); platformSlashes(string $path): string .[method] ----------------------------------------------- -Converts slashes to characters specific to the current platform, i.e. `\` on Windows and `/` elsewhere. +Converts slashes to characters specific to the current platform, i.e., `\` on Windows and `/` elsewhere. ```php $path = FileSystem::platformSlashes($path); ``` + + +resolvePath(string $basePath, string $path): string .[method]{data-version:4.0.6} +--------------------------------------------------------------------------------- + +Resolves the final path from `$path` relative to the base directory `$basePath`. Absolute paths (`/foo`, `C:/foo`) remain unchanged (only normalizes slashes), relative paths are appended to the base path. + +```php +// On Windows, slashes in the output would be reversed (\) +FileSystem::resolvePath('/base/dir', '/abs/path'); // '/abs/path' +FileSystem::resolvePath('/base/dir', 'rel'); // '/base/dir/rel' +FileSystem::resolvePath('base/dir', '../file.txt'); // 'base/file.txt' +FileSystem::resolvePath('base', ''); // 'base' +``` + + +Static vs Non-static Approach +============================= + +To easily replace the class with another one (e.g., a mock) for testing purposes, use it non-statically: + +```php +class AnyClassUsingFileSystem +{ + public function __construct( + private FileSystem $fileSystem, + ) { + } + + public function readConfig(): string + { + return $this->fileSystem->read(/* ... */); + } + + ... +} +``` diff --git a/utils/en/finder.texy b/utils/en/finder.texy index f9117246db..d3a5c73bc8 100644 --- a/utils/en/finder.texy +++ b/utils/en/finder.texy @@ -1,8 +1,8 @@ -Finder: File Search -******************* +Finder: Searching for Files +*************************** .[perex] -Need to find files matching a certain mask? The Finder can help you. It's a versatile and fast tool for browsing the directory structure. +Need to find files matching a certain mask? Finder can help you with that. It's a versatile and fast tool for browsing directory structures. Installation: @@ -11,17 +11,17 @@ Installation: composer require nette/utils ``` -The examples assume an alias has been created: +The examples assume the following class alias has been created: ```php use Nette\Utils\Finder; ``` -Using +Usage ----- -First, let's see how you can use [api:Nette\Utils\Finder] to list the file names with the extensions `.txt` and `.md` in the current directory: +First, let's see how you can use [api:Nette\Utils\Finder] to list the names of files with the extensions `.txt` and `.md` in the current directory: ```php foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { @@ -29,14 +29,13 @@ foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { } ``` -The default directory for the search is the current directory, but you can change it using the [in() or from() |#Where to search?] methods. -The `$file` variable is an instance of the [FileInfo |#FileInfo] class with lots of useful methods. The `$name` key contains the path to the file as a string. +The default search directory is the current directory, but you can change it using the [in() or from() |#Where to Search] methods. The `$file` variable is an instance of the [#FileInfo] class, which offers many useful methods. The key `$name` holds the path to the file as a string. What to Search For? ------------------- -In addition to the `findFiles()` method, there is also `findDirectories()`, which searches only directories, and `find()`, which searches both. These methods are static, so they can be called without creating an instance. The mask parameter is optional, if you don't specify it, everything is searched. +In addition to the `findFiles()` method, there is also `findDirectories()`, which searches only for directories, and `find()`, which searches for both. These methods are static, so they can be called without creating an instance. The mask parameter is optional; if omitted, everything is searched. ```php foreach (Finder::find() as $file) { @@ -44,14 +43,14 @@ foreach (Finder::find() as $file) { } ``` -Use the `files()` and `directories()` methods to add what else to search for. The methods can be called repeatedly and an array of masks can be provided as a parameter: +Using the `files()` and `directories()` methods, you can specify additional items to search for. The methods can be called repeatedly, and an array of masks can also be provided as a parameter: ```php Finder::findDirectories('vendor') // all directories ->files(['*.php', '*.phpt']); // plus all PHP files ``` -An alternative to static methods is to create an instance using `new Finder` (the fresh object created this way does not search for anything) and specify what to search for using `files()` and `directories()`: +An alternative to static methods is creating an instance using `new Finder` (a newly created object like this doesn't search for anything initially) and specifying what to search for using `files()` and `directories()`: ```php (new Finder) @@ -59,34 +58,36 @@ An alternative to static methods is to create an instance using `new Finder` (th ->files('*.php'); // plus all PHP files ``` -You can use [#wildcards] `*`, `**`, `?` and `[...]` in the mask. You can even specify in directories, for example `src/*.php` will search for all PHP files in the `src` directory. +You can use [#wildcards] `*`, `**`, `?`, and `[...]` in the mask. You can even specify directories in the mask; for example, `src/*.php` will find all PHP files in the `src` directory. + +Symlinks are also treated as directories or files. Where to Search? ---------------- -The default search directory is the current directory. You can change this by using the `in()` and `from()` methods. As you can see from the method names, `in()` searches only the current directory, while `from()` searches its subdirectories too (recursively). If you want to search recursively in the current directory, you can use `from('.')`. +The default search directory is the current directory. You can change this using the `in()` and `from()` methods. As the method names suggest, `in()` searches only within the specified directory, while `from()` searches its subdirectories as well (recursively). If you want to search recursively in the current directory, you can use `from('.')`. -These methods can be called multiple times or you can pass multiple paths to them as arrays, then files will be searched in all directories. If one of the directories does not exist, a `Nette\UnexpectedValueException` is thrown. +These methods can be called multiple times, or you can pass multiple paths as an array; files will then be searched in all specified directories. If any of the specified directories do not exist, a `Nette\UnexpectedValueException` is thrown. ```php Finder::findFiles('*.php') ->in(['src', 'tests']) // searches directly in src/ and tests/ - ->from('vendor'); // searches also in vendor/ subdirectories + ->from('vendor'); // also searches in vendor/ subdirectories ``` -Relative paths are relative to the current directory. Of course, absolute paths can also be specified: +Relative paths are relative to the current directory. Absolute paths can also be specified, of course: ```php Finder::findFiles('*.php') ->in('/var/www/html'); ``` -Wildcards [#wildcards] `*`, `**`, `?` can be used in the path. For example, you can use the path `src/*/*.php` to search for all PHP files in the second level directories in the `src` directory. The `**` character, called globstar, is a powerful trump card because it allows you to search subdirectories as well: use `src/**/tests/*.php` to search for all PHP files in the `tests` directory located in `src` or any of its subdirectories. +You can use [#wildcards] `*`, `**`, `?` in the path. For example, using the path `src/*/*.php`, you can search for all PHP files in second-level directories within the `src` directory. The `**` character, called globstar, is a powerful feature as it allows searching within subdirectories: using `src/**/tests/*.php` searches for all PHP files in the `tests` directory located within `src` or any of its subdirectories. -On the other hand, wildcards `[...]` characters are not supported in the path, i.e. they have no special meaning to avoid unwanted behavior in case you search for example `in(__DIR__)` and by chance `[]` characters appear in the path. +Conversely, the `[...]` wildcards are not supported in the path, meaning they have no special significance, to prevent unintended behavior if you were searching, for instance, `in(__DIR__)` and the path happened to contain `[]` characters. -When searching files and directories in depth, the parent directory is returned first and then the files contained in it, which can be reversed with `childFirst()`. +When searching files and directories recursively (depth-first), the parent directory is returned first, followed by the files it contains. This order can be reversed using `childFirst()`. Wildcards @@ -95,30 +96,30 @@ Wildcards You can use several special characters in the mask: - `*` - replaces any number of arbitrary characters (except `/`) -- `**` - replaces any number of arbitrary characters including `/` (i.e. can be searched multi-level) +- `**` - replaces any number of arbitrary characters including `/` (i.e., allows multi-level searching) - `?` - replaces one arbitrary character (except `/`) - `[a-z]` - replaces one character from the list of characters in square brackets - `[!a-z]` - replaces one character outside the list of characters in square brackets -Examples of use: +Usage examples: -- `img/?.png` - files with the single-letter name `0.png`, `1.png`, `x.png`, etc. +- `img/?.png` - files with a single-letter name like `0.png`, `1.png`, `x.png`, etc. - `logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log` - log files in the format `YYYY-MM-DD` -- `src/**/tests/*` - files in the directory `src/tests`, `src/foo/tests`, `src/foo/bar/tests` and so on. -- `docs/**.md` - all files with the extension `.md` in all subdirectories of the directory `docs` +- `src/**/tests/*` - files in the directories `src/tests`, `src/foo/tests`, `src/foo/bar/tests`, and so on. +- `docs/**.md` - all files with the `.md` extension in all subdirectories of the `docs` directory Excluding --------- -Use the `exclude()` method to exclude files and directories from searches. You specify a mask that the file must not match. Example of searching for files `*.txt` except those containing the letter `X` in the name: +Using the `exclude()` method, you can exclude files and directories from the search. You specify a mask that the file or directory must not match. Example of searching for `*.txt` files, excluding those containing the letter `X` in their name: ```php Finder::findFiles('*.txt') ->exclude('*X*'); ``` -Use `exclude()` to skip browsed subdirectories: +To skip specific subdirectories during traversal, use `exclude()`: ```php Finder::findFiles('*.php') @@ -130,9 +131,9 @@ Finder::findFiles('*.php') Filtering --------- -The Finder offers several methods for filtering the results (i.e. reducing them). You can combine them and call them repeatedly. +Finder offers several methods for filtering the results (i.e., reducing them). You can combine them and call them repeatedly. -Use `size()` to filter by file size. In this way, we find files with sizes between 100 and 200 bytes: +Using `size()`, we filter by file size. This way, we find files with sizes in the range of 100 to 200 bytes: ```php Finder::findFiles('*.php') @@ -140,7 +141,7 @@ Finder::findFiles('*.php') ->size('<=', 200); ``` -The `date()` method filters by the date the file was last modified. The values can be absolute or relative to the current date and time, for example, this is how to find files changed in the last two weeks: +The `date()` method filters by the file's last modification date. Values can be absolute dates or relative to the current date and time. For example, this finds files modified within the last two weeks: ```php Finder::findFiles('*.php') @@ -148,11 +149,11 @@ Finder::findFiles('*.php') ->from($dir) ``` -Both functions understand the operators `>`, `>=`, `<`, `<=`, `=`, `!=`, `<>`. +Both methods understand the operators `>`, `>=`, `<`, `<=`, `=`, `!=`, `<>`. -The Finder also allows you to filter results using custom functions. The function receives an `Nette\Utils\FileInfo` object as a parameter and must return `true` to include the file in the results. +Finder also allows filtering results using custom callbacks. The callback receives a `Nette\Utils\FileInfo` object as a parameter and must return `true` for the file to be included in the results. -Example: search for PHP files that contain the string `Nette` (case-insensitive): +Example: searching for PHP files containing the string `'Nette'` (case-insensitive): ```php Finder::findFiles('*.php') @@ -163,24 +164,24 @@ Finder::findFiles('*.php') Depth Filtering --------------- -When searching recursively, you can set the maximum crawl depth using the `limitDepth()` method. If you set `limitDepth(1)`, only the first subdirectories are crawled, `limitDepth(0)` disables depth crawling, and a value of -1 cancels the limit. +When searching recursively, you can set the maximum traversal depth using the `limitDepth()` method. Setting `limitDepth(1)` traverses only the first level of subdirectories, `limitDepth(0)` disables depth traversal entirely, and a value of -1 removes the depth limit. -The Finder allows you to use its own functions to decide which directory to enter when browsing. The function receives an `Nette\Utils\FileInfo` object as a parameter and must return `true` to enter the directory: +Finder allows using custom callbacks to decide which directories to enter during traversal. The callback receives a `Nette\Utils\FileInfo` object representing the directory and must return `true` to enter it: ```php Finder::findFiles('*.php') - ->descentFilter($file->getBasename() !== 'temp'); + ->descentFilter(fn($file) => $file->getBasename() !== 'temp'); ``` Sorting ------- -The Finder also offers several functions for sorting results. +Finder also offers several methods for sorting the results. -The `sortByName()` method sorts results by file name. The sorting is natural, i.e. it correctly handles the numbers in the names and returns e.g. `foo1.txt` before `foo10.txt`. +The `sortByName()` method sorts results by file name. The sorting is natural, meaning it correctly handles numbers in names and returns, e.g., `foo1.txt` before `foo10.txt`. -The Finder also allows you to sort using a custom function. It takes two `Nette\Utils\FileInfo` objects as parameters and must return the result of the comparison with the operator `<=>`, i.e. `-1`, `0` nebo `1`. For example, this is how we sort files by size: +Finder also allows sorting using a custom callback. It receives two `Nette\Utils\FileInfo` objects as parameters and must return the result of the comparison using the `<=>` operator (i.e., `-1`, `0`, or `1`). For example, this is how we sort files by size: ```php $finder->sortBy(fn($a, $b) => $a->getSize() <=> $b->getSize()); @@ -190,7 +191,7 @@ $finder->sortBy(fn($a, $b) => $a->getSize() <=> $b->getSize()); Multiple Different Searches --------------------------- -If you need to find multiple different files in different locations or that meet different criteria, use the `append()` method. It returns a new `Finder` object so you can chain method calls: +If you need to find multiple sets of files in different locations or meeting different criteria, use the `append()` method. It returns a new `Finder` object, allowing you to chain method calls for the appended search: ```php @@ -204,7 +205,7 @@ If you need to find multiple different files in different locations or that meet ->files('*.json'); // in the current folder look for *.json files ``` -Alternatively, you can use the `append()` method to add a specific file (or an array of files). Then it returns the same object `Finder`: +Alternatively, the `append()` method can be used to add a specific file (or an array of files). In this case, it returns the same `Finder` object: ```php $finder = Finder::findFiles('*.txt') @@ -215,9 +216,9 @@ $finder = Finder::findFiles('*.txt') FileInfo -------- -[Nette\Utils\FileInfo |api:] is a class representing a file or directory in the search results. It is an extension of the [SplFileInfo |php:SplFileInfo] class that provides information such as file size, last modified date, name, path, etc. +[api:Nette\Utils\FileInfo] is a class representing a file or directory found in the search results. It extends the [php:SplFileInfo] class and provides information such as file size, last modification date, name, path, etc. -Additionally, it provides methods for returning relative paths, which is useful when browsing in depth: +Additionally, it provides methods for returning the relative path, which is useful during recursive traversal: ```php foreach (Finder::findFiles('*.jpg')->from('.') as $file) { @@ -226,7 +227,7 @@ foreach (Finder::findFiles('*.jpg')->from('.') as $file) { } ``` -You also have methods for reading and writing the contents of a file: +Furthermore, methods are available for reading and writing the file's content: ```php foreach ($finder as $file) { @@ -240,9 +241,9 @@ foreach ($finder as $file) { Returning Results as an Array ----------------------------- -As seen in the examples, the Finder implements the `IteratorAggregate` interface , so you can use `foreach` to browse the results. It's programmed so that results are only loaded as you browse, so if you have a large number of files, it doesn't wait for them all to be read. +As seen in the examples, Finder implements the `IteratorAggregate` interface, so you can use `foreach` to iterate through the results. It's designed so that results are loaded only during iteration, meaning if you have a large number of files, it doesn't wait for all of them to be read beforehand. -You can also have the results returned as an array of `Nette\Utils\FileInfo` objects, using the `collect()` method. The array is not associative, but numeric. +You can also retrieve the results as an array of `Nette\Utils\FileInfo` objects using the `collect()` method. The array is numerically indexed, not associative. ```php $array = $finder->findFiles('*.php')->collect(); diff --git a/utils/en/floats.texy b/utils/en/floats.texy index 96bbe394e9..8ebebc8c15 100644 --- a/utils/en/floats.texy +++ b/utils/en/floats.texy @@ -1,8 +1,8 @@ -Floats Functions -**************** +Working with Floats +******************* .[perex] -[api:Nette\Utils\Floats] is a static class with useful functions for comparing float numbers. +[api:Nette\Utils\Floats] is a static class containing useful functions for comparing floating-point numbers. Installation: @@ -21,8 +21,7 @@ use Nette\Utils\Floats; Motivation ========== -Wondering what a float comparison class is for? You can use operators `<`, `>`, `===`, you think. -This is not entirely true. What do you think will print this code? +Are you wondering why we need a class for comparing floats? After all, you can use the operators `<`, `>`, `===`, and you're done, right? Well, it's not entirely true. What do you think this code will output? ```php $a = 0.1 + 0.2; @@ -30,17 +29,20 @@ $b = 0.3; echo $a === $b ? 'same' : 'not same'; ``` -If you run the code, some of you will be surprised that the program printed `not same`. +If you run the code, some of you might be surprised that the program outputs `not same`. -Mathematical operations with float numbers cause errors due to conversion between decimal and binary systems. For example `0.1 + 0.2` equates to `0.300000000000000044…`. Therefore, when comparing floats, we must tolerate a small difference from a certain decimal place. +Mathematical operations with floating-point numbers can lead to precision errors due to the conversion between decimal and binary representations. For example, `0.1 + 0.2` results in something like `0.300000000000000044…`. Therefore, when comparing floats, we need to tolerate a small difference, an epsilon. -And that's what the `Floats` class is doing. The following comparison will work as expected: +And that's exactly what the `Floats` class does. The following comparison will now work as expected: ```php echo Floats::areEqual($a, $b) ? 'same' : 'not same'; // same ``` -When trying to compare `NAN`, it throws an `\LogicException` exception. +Trying to compare `NAN` throws a `\LogicException`. + +.[tip] +The `Floats` class tolerates differences smaller than `1e-10`. If you need to work with higher precision, consider using the BCMath library instead. Float Comparison @@ -104,25 +106,25 @@ Floats::isGreaterThanOrEqualTo(10.2, 10.2); // true compare(float $a, float $b): int .[method] ------------------------------------------ -If `$a` < `$b`, it returns `-1`, if they are equal it returns `0` and if `$a` > `$b` it returns `1`. +Returns `-1` if `$a` < `$b`, `0` if they are equal, and `1` if `$a` > `$b`. -It can be used, for example, with the `usort` function. +It can be used, for example, with the `usort()` function. ```php $arr = [1, 5, 2, -3.5]; -usort($arr, [Float::class, 'compare']); -// $arr is [-3.5, 1, 2, 5] +usort($arr, [Floats::class, 'compare']); +// $arr is now [-3.5, 1, 2, 5] ``` -Helpers Functions -================= +Helper Functions +================ isZero(float $value): bool .[method] ------------------------------------ -Returns `true` if value is zero. +Returns `true` if the value is zero. ```php Floats::isZero(0.0); // true @@ -133,7 +135,7 @@ Floats::isZero(0); // true isInteger(float $value): bool .[method] --------------------------------------- -Returns `true` if value is integer. +Returns `true` if the value is an integer. ```php Floats::isInteger(0); // true diff --git a/utils/en/helpers.texy b/utils/en/helpers.texy index 2311d7b132..4c69556b64 100644 --- a/utils/en/helpers.texy +++ b/utils/en/helpers.texy @@ -2,7 +2,7 @@ Helper Functions **************** .[perex] -[api:Nette\Utils\Helpers] is a static class with useful functions. +[api:Nette\Utils\Helpers] is a static class containing useful helper functions. Installation: @@ -33,7 +33,7 @@ $res = Helpers::capture(function () use ($template) { clamp(int|float $value, int|float $min, int|float $max): int|float .[method] ---------------------------------------------------------------------------- -Returns value clamped to the inclusive range of min and max. +Clamps a value within the inclusive range specified by min and max. ```php Helpers::clamp($level, 0, 255); @@ -43,8 +43,7 @@ Helpers::clamp($level, 0, 255); compare(mixed $left, string $operator, mixed $right): bool .[method] -------------------------------------------------------------------- -Compares two values in the same way PHP does. It distinguishes between the operators `>`, `>=`, `<`, `<=`, `=`, `==`, `===`, `!=`, `!==`, `<>`. -The function is useful in situations where the operator is variable. +Compares two values the same way PHP does. It supports the operators `>`, `>=`, `<`, `<=`, `=`, `==`, `===`, `!=`, `!==`, `<>`. The function is useful in situations where the operator is determined dynamically (is a variable). ```php Helpers::compare(10, '<', 20); // true @@ -54,7 +53,7 @@ Helpers::compare(10, '<', 20); // true falseToNull(mixed $value): mixed .[method] ------------------------------------------ -Converts `false` to `null`, does not change other values. +Converts `false` to `null`; other values remain unchanged. ```php Helpers::falseToNull(false); // null @@ -65,7 +64,7 @@ Helpers::falseToNull(123); // 123 getLastError(): string .[method] -------------------------------- -Returns the last occurred PHP error or an empty string if no error occurred. Unlike `error_get_last()`, it is not affected by the PHP directive `html_errors` and always returns text, not HTML. +Returns the last occurred PHP error or an empty string if no error occurred. Unlike `error_get_last()`, it's not affected by the PHP directive `html_errors` and always returns text, not HTML. ```php Helpers::getLastError(); @@ -75,13 +74,24 @@ Helpers::getLastError(); getSuggestion(string[] $possibilities, string $value): ?string .[method] ------------------------------------------------------------------------ -Looks for a string from `$possibilities` that is most similar to `$value`, but not the same. Supports only 8-bit encodings. +From the given `$possibilities`, it finds the string most similar to `$value`, but not identical. It only supports 8-bit encodings. -It is useful if a certain option is not valid and we want to suggest the user a similar one (but different, so the same string is ignored). In this way, Nette creates messages `did you mean ...?`. +This is useful when a certain option is invalid, and you want to suggest a similar alternative to the user (but a different one, hence why identical strings are ignored). This is how Nette generates the "did you mean ...?" messages. ```php $items = ['foo', 'bar', 'baz']; Helpers::getSuggestion($items, 'fo'); // 'foo' Helpers::getSuggestion($items, 'barr'); // 'bar' -Helpers::getSuggestion($items, 'baz'); // 'bar', ne 'baz' +Helpers::getSuggestion($items, 'baz'); // 'bar', not 'baz' +``` + + +splitClassName(string $name): array .[method]{data-version:4.0.10} +------------------------------------------------------------------ + +Splits a PHP class name into a namespace and a short class name. Returns an array of two strings where the first is the namespace and the second is the class name. + +```php +Helpers::splitClassName('Nette\Utils\Helpers'); // ['Nette\Utils', 'Helpers'] +Helpers::splitClassName('Foo'); // ['', 'Foo'] ``` diff --git a/utils/en/html-elements.texy b/utils/en/html-elements.texy index b9f7023e12..a71d6d7bc5 100644 --- a/utils/en/html-elements.texy +++ b/utils/en/html-elements.texy @@ -2,10 +2,10 @@ HTML Elements ************* .[perex] -The [api:Nette\Utils\Html] class is a helper for generating HTML code that prevents Cross Site Scripting (XSS) vulnerability. +The [api:Nette\Utils\Html] class is a helper for generating HTML code that helps prevent Cross-Site Scripting (XSS) vulnerabilities. -It works in such a way that its objects represent HTML elements, we set their parameters and let them render: +It works by having its objects represent HTML elements; you set their parameters and then render them: ```php $el = Html::el('img'); // creates element @@ -29,19 +29,19 @@ use Nette\Utils\Html; Creating an HTML Element ======================== -The element is created using the method `Html::el()`: +An element is created using the `Html::el()` method: ```php $el = Html::el('img'); // creates element ``` -In addition to the name, you can enter other attributes in the HTML syntax: +Besides the name, you can also specify other attributes using HTML syntax: ```php $el = Html::el('input type=text class="red important"'); ``` -Or pass them as an associative array to the second parameter: +Or pass them as an associative array in the second parameter: ```php $el = Html::el('input', [ @@ -50,39 +50,39 @@ $el = Html::el('input', [ ]); ``` -To change and return an element name: +To change and retrieve the element's name: ```php $el->setName('img'); $el->getName(); // 'img' -$el->isEmpty(); // true, as is void element +$el->isEmpty(); // true, as is a void element ``` HTML Attributes =============== -You can set and get individual HTML attributes in three ways, it's up to you who you like more. The first is through the properties: +Individual HTML attributes can be set and retrieved in three ways; it's up to you which one you prefer. The first is via properties: ```php $el->src = 'image.jpg'; // sets src attribute echo $el->src; // 'image.jpg' -unset($el->src); // removes attribute +unset($el->src); // removes the attribute // or $el->src = null; ``` -The second way is to call methods that, in contrast to setting properties, we can chain together: +The second way is by calling methods, which, unlike setting properties, can be chained: ```php $el = Html::el('img')->src('image.jpg')->alt('photo'); // photo -$el->alt(null); // removes attribute +$el->alt(null); // removes the attribute ``` -And the third way is most talkative: +And the third way is the most verbose: ```php $el = Html::el('img') @@ -94,9 +94,9 @@ echo $el->getAttribute('src'); // 'image.jpg' $el->removeAttribute('alt'); ``` -In bulk, attributes can be set with `addAttributes(array $attrs)` and deleted with `removeAttributes(array $attrNames)`. +Attributes can be set in bulk using `addAttributes(array $attrs)` and removed using `removeAttributes(array $attrNames)`. -The value of an attribute does not have to be only a string, logical values ​​for logical attributes can also be used: +The value of an attribute doesn't have to be just a string; boolean values can be used for boolean attributes: ```php $checkbox = Html::el('input')->type('checkbox'); @@ -104,7 +104,7 @@ $checkbox->checked = true; // $checkbox->checked = false; // ``` -An attribute can also be an array of tokens, which are listed separated by spaces, which is suitable for CSS classes, for example: +An attribute can also be an array of values, which are output separated by spaces. This is useful for CSS classes, for example: ```php $el = Html::el('input'); @@ -114,7 +114,7 @@ $el->class[] = 'top'; echo $el; // '' ``` -An alternative is an associative array, where the values ​​say whether the key should be listed: +An alternative is an associative array, where the values indicate whether the key should be included: ```php $el = Html::el('input'); @@ -123,7 +123,7 @@ $el->class['top'] = false; echo $el; // '' ``` -CSS styles can be written in the form of associative arrays: +CSS styles can be written as associative arrays: ```php $el = Html::el('input'); @@ -132,7 +132,7 @@ $el->style['display'] = 'block'; echo $el; // '' ``` -We have now used properties, but the same can be done using the methods: +We've used properties so far, but the same can be achieved using methods: ```php $el = Html::el('input'); @@ -141,7 +141,7 @@ $el->style('display', 'block'); echo $el; // '' ``` -Or even in the most talkative way: +Or even in the most verbose way: ```php $el = Html::el('input'); @@ -150,7 +150,7 @@ $el->appendAttribute('style', 'display', 'block'); echo $el; // '' ``` -One last thing: the method `href()` can make it easier to compose query parameters in a URL: +One final detail: the `href()` method can simplify the composition of URL query parameters: ```php echo Html::el('a')->href('index.php', [ @@ -164,16 +164,16 @@ echo Html::el('a')->href('index.php', [ Data Attributes --------------- -Data attributes have special support. Because their names contain hyphens, access via properties and methods is not so elegant, so there is a method `data()`: +Data attributes have special support. Since their names contain hyphens, accessing them via properties and methods isn't as elegant, so there's a dedicated `data()` method: ```php $el = Html::el('input'); -$el->{'data-max-size'} = '500x300'; // not so elegant +$el->{'data-max-size'} = '500x300'; // not as elegant $el->data('max-size', '500x300'); // is elegant echo $el; // '' ``` -If the value of the data attribute is an array, it is automatically serialized to JSON: +If the value of a data attribute is an array, it is automatically serialized to JSON: ```php $el = Html::el('input'); @@ -185,7 +185,7 @@ echo $el; // '' Element Content =============== -The inner content of the element is set by the `setHtml()` or `setText()` methods. Use the first one only if you know that you are reliably passing a secure HTML string in the parameter. +The inner content of the element is set using the `setHtml()` or `setText()` methods. Use the former only if you are sure that the parameter contains a reliably safe HTML string. ```php echo Html::el('span')->setHtml('hello
                                                                                                                            '); @@ -195,7 +195,7 @@ echo Html::el('span')->setText('10 < 20'); // '10 < 20' ``` -Conversely, the inner content is obtained by methods `getHtml()` or `getText()`. The second one removes tags from the HTML output and converts the HTML entities to characters. +Conversely, the inner content can be retrieved using the `getHtml()` or `getText()` methods. The latter removes HTML tags from the content and converts HTML entities back to characters. ```php echo $el->getHtml(); // '10 < 20' @@ -206,7 +206,7 @@ echo $el->getText(); // '10 < 20' Child Nodes ----------- -The inner content of an element can also be an array of children. Each of them can be either a string or another `Html` element. They are inserted using `addHtml()` or `addText()`: +The inner content of an element can also be an array of child nodes. Each child can be either a string or another `Html` object. They are added using `addHtml()` or `addText()`: ```php $el = Html::el('span') @@ -219,13 +219,13 @@ $el = Html::el('span') Another way to create and insert a new `Html` node: ```php -$el = Html::el('ul') - ->create('li', ['class' => 'first']) - ->setText('hello'); -//
                                                                                                                            • hello
                                                                                                                            +$ul = Html::el('ul'); +$ul->create('li', ['class' => 'first']) + ->setText('first'); +//
                                                                                                                            • first
                                                                                                                            ``` -You can work with nodes as if they were array items. So access the individual ones using square brackets, count them with `count()` and iterate over them: +You can work with nodes as if they were array elements. That is, access individual nodes using square brackets, count them using `count()`, and iterate over them: ```php $el = Html::el('div'); @@ -238,20 +238,20 @@ foreach ($el as $child) { /* ... */ } echo count($el); // 2 ``` -A new node can be inserted at a specific position using `insert(?int $index, $child, bool $replace = false)`. If `$replace = false`, it inserts the element at the `$index` position and moves the others. If `$index = null`, it will append an element at the end. +A new node can be inserted at a specific position using `insert(?int $index, $child, bool $replace = false)`. If `$replace = false`, it inserts the element at position `$index` and shifts the others. If `$index = null`, it appends the element to the end. ```php -// inserts the element in the first position and advances the others +// inserts the element at the first position and shifts the others $el->insert(0, Html::el('span')); ``` -All nodes are returned by method `getChildren()` and removed by method `removeChildren()`. +All nodes can be retrieved using the `getChildren()` method and removed using the `removeChildren()` method. Creating a Document Fragment ---------------------------- -If you want to work with an array of nodes and are not interested in the wrapping element, you can create a so-called *document fragment* by passing `null` instead of the element name: +If you want to work with an array of nodes without a wrapping element, you can create a *document fragment* by passing `null` instead of an element name: ```php $el = Html::el(null) @@ -261,7 +261,7 @@ $el = Html::el(null) // hello
                                                                                                                            10 < 20
                                                                                                                            ``` -Methods `fromHtml()` and `fromText()` offer a faster way to create a fragment: +The methods `fromHtml()` and `fromText()` offer a faster way to create a fragment: ```php $el = Html::fromHtml('hello
                                                                                                                            '); @@ -275,12 +275,12 @@ echo $el; // '10 < 20' Generating HTML Output ====================== -The easiest way to generate an HTML element is to use `echo` or cast an object to `(string)`. You can also prints opening or closing tags and attributes separately: +The simplest way to output an HTML element is to use `echo` or cast the object to `(string)`. You can also output the opening tag, closing tag, and attributes separately: ```php $el = Html::el('div class=header')->setText('hello'); -echo $el; // '
                                                                                                                            ' +echo $el; // '
                                                                                                                            hello
                                                                                                                            ' $s = (string) $el; // '
                                                                                                                            hello
                                                                                                                            ' $s = $el->toHtml(); // '
                                                                                                                            hello
                                                                                                                            ' $s = $el->toText(); // 'hello' @@ -289,7 +289,7 @@ echo $el->endTag(); // '' echo $el->attributes(); // 'class="header"' ``` -An important feature is the automatic protection against [Cross Site Scripting (XSS) |nette:glossary#cross-site-scripting-xss]. All attribute values ​​or content inserted using `setText()` or `addText()` is reliably escaped: +An important feature is automatic protection against [Cross-Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS]. All attribute values or content inserted via `setText()` or `addText()` are reliably escaped: ```php echo Html::el('div') @@ -313,4 +313,4 @@ echo Html::htmlToText('One & Two'); // 'One & Two' HtmlStringable ============== -The `Nette\Utils\Html` object implements the `Nette\HtmlStringable` interface, which, for example, Latte or forms use to distinguish objects that have a method `__toString()` that returns HTML code. So double escaping does not occur if, for example, we print the object in the template using `{$el}`. +The `Nette\Utils\Html` object implements the `Nette\HtmlStringable` interface. Latte and Forms use this interface, for example, to distinguish objects that have a `__toString()` method returning HTML code. This prevents double escaping if, for example, you print the object in a template using `{$el}`. diff --git a/utils/en/images.texy b/utils/en/images.texy index 8226cce7b6..6485f905a3 100644 --- a/utils/en/images.texy +++ b/utils/en/images.texy @@ -1,11 +1,11 @@ -Image Functions -*************** +Working with Images +******************* .[perex] The [api:Nette\Utils\Image] class simplifies image manipulation, such as resizing, cropping, sharpening, drawing, or merging multiple images. -PHP has an extensive set of functions for manipulating images. But the API is not very nice. It wouldn't be a Nette Framework to come up with a sexy API. +PHP has an extensive set of functions for image manipulation. However, their API isn't very user-friendly. It wouldn't be Nette Framework if it didn't come up with a delightful API. Installation: @@ -13,119 +13,130 @@ Installation: composer require nette/utils ``` -Following examples assume the following class alias is defined: +The following examples assume the following class alias is defined: ```php use Nette\Utils\Image; +use Nette\Utils\ImageColor; +use Nette\Utils\ImageType; ``` Creating an Image ================= -We'll create a new true color image, for example with dimensions of 100×200: +Create a new true color image, for example, with dimensions 100×200: ```php $image = Image::fromBlank(100, 200); ``` -Optionally, you can specify a background color (default is black): +Optionally, you can specify the background color (default is black): ```php -$image = Image::fromBlank(100, 200, Image::rgb(125, 0, 0)); +$image = Image::fromBlank(100, 200, ImageColor::rgb(125, 0, 0)); ``` -Or we load the image from a file: +Or load an image from a file: ```php $image = Image::fromFile('nette.jpg'); ``` -Supported formats are JPEG, PNG, GIF, WebP, AVIF and BMP, but your version of PHP must also support them (check the `phpinfo()`, section GD). Animations are not supported. -Need to detect the image format when loading? The method returns format in the second parameter: +Saving the Image +================ + +The image can be saved to a file: ```php -$image = Image::fromFile('nette.jpg', $type); -// $type is Image::JPEG, Image::PNG, Image::GIF, Image::WEBP, Image::AVIF or Image::BMP +$image->save('resampled.jpg'); ``` -Only the detection without loading the image is done by `Image::detectTypeFromFile()`. - +You can specify the compression quality in the range 0-100 for JPEG (default 85), WEBP (default 80), and AVIF (default 30), and 0-9 for PNG (default 9): -Save the Image -============== +```php +$image->save('resampled.jpg', 80); // JPEG, quality 80% +``` -The image can be saved to a file: +If the format is not clear from the file extension, it can be specified using a [constant |#Formats]: ```php -$image->save('resampled.jpg'); +$image->save('resampled.tmp', null, ImageType::JPEG); ``` -We can specify compression quality in the range 0..100 for JPEG (default 85), WEBP (default 80) and AVIF (default 30) and 0..9 for PNG (default 9): +Instead of saving to disk, the image can be written to a variable: ```php -$image->save('resampled.jpg', 80); // JPEG, quality 80% +$data = $image->toString(ImageType::JPEG, 80); // JPEG, quality 80% ``` -If the format is not obvious from the file extension, it can be specified by one of the constants `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF`, and `Image::BMP`: +or sent directly to the browser with the appropriate `Content-Type` HTTP header: ```php -$image->save('resampled.tmp', null, Image::JPEG); +// sends the Content-Type: image/png header +$image->send(ImageType::PNG); ``` -The image can be written to a variable instead of to disk: + +Formats +======= + +Supported formats are JPEG, PNG, GIF, WebP, AVIF, and BMP. However, your PHP version must also support them, which you can verify using the [#isTypeSupported()] function. Animations are not supported. + +The formats are represented by the constants `ImageType::JPEG`, `ImageType::PNG`, `ImageType::GIF`, `ImageType::WEBP`, `ImageType::AVIF`, and `ImageType::BMP`. ```php -$data = $image->toString(Image::JPEG, 80); // JPEG, quality 80% +$supported = Image::isTypeSupported(ImageType::JPEG); ``` -or send directly to the browser with the appropriate HTTP header `Content-Type`: +Need to detect the image format upon loading? The method returns it in the second parameter: ```php -// sends header Content-Type: image/png -$image->send(Image::PNG); +$image = Image::fromFile('nette.jpg', $type); ``` +Detection without loading the image itself is done by `Image::detectTypeFromFile()`. + -Image Resize -============ +Resizing +======== -A common operation is to resize an image. The current dimensions are returned by methods `getWidth()` and `getHeight()`. +A common operation is resizing an image. The current dimensions are returned by the `getWidth()` and `getHeight()` methods. -The method `resize()` is used for resizing. This is example of proportional size change so that it does not exceed 500×300 pixels (either the width will be exactly 500px or the height will be exactly 300px, one of dimensions is calculated to maintain the aspect ratio): +The `resize()` method is used for resizing. Example of proportional resizing so that the image does not exceed 500x300 pixels (either the width will be exactly 500px or the height will be exactly 300px; one dimension is calculated to maintain the aspect ratio): ```php $image->resize(500, 300); ``` -It's possible to set only one dimension and the second one will be calculated: +It's possible to specify only one dimension, and the other will be calculated automatically: ```php -$image->resize(500, null); // width 500px, height auto +$image->resize(500, null); // width 500px, height calculated automatically -$image->resize(null, 300); // width auto, height 300px +$image->resize(null, 300); // width calculated automatically, height 300px ``` -Any dimension can be specified in percentages: +Either dimension can also be specified in percentages: ```php $image->resize('75%', 300); // 75 % × 300px ``` -The behavior of `resize` can be influenced by the following flags. All but `Image::Stretch` preserve the aspect ratio. +The behavior of `resize()` can be influenced by the following flags. All flags except `Image::Stretch` preserve the aspect ratio. |--------------------------------------------------------------------------------------- -| Flag | Description +| Flag | Description |--------------------------------------------------------------------------------------- -| `Image::OrSmaller` (default) | resulting dimensions will be less or equal as specified -| `Image::OrBigger` | fills the target area and possibly extends it in one direction -| `Image::Cover` | fills the whole area and cuts what exceeds -| `Image::ShrinkOnly` | just scales down (does not extend a small image) -| `Image::Stretch` | does not keep the aspect ratio +| `Image::OrSmaller` (default) | the resulting dimensions will be less than or equal to the requested dimensions +| `Image::OrBigger` | fills (and possibly exceeds in one dimension) the target area +| `Image::Cover` | fills the target area and crops anything that exceeds it +| `Image::ShrinkOnly` | only shrinks (prevents stretching a small image) +| `Image::Stretch` | does not preserve the aspect ratio -The flags are passed as the third argument of the function: +Flags are passed as the third argument to the method: ```php $image->resize(500, 300, Image::OrBigger); @@ -140,14 +151,14 @@ $image->resize(500, 300, Image::ShrinkOnly | Image::Stretch); Images can be flipped vertically or horizontally by specifying one of the dimensions (or both) as a negative number: ```php -$flipped = $image->resize(null, '-100%'); // flip vertical +$flipped = $image->resize(null, '-100%'); // flip vertically -$flipped = $image->resize('-100%', '-100%'); // rotate by 180° +$flipped = $image->resize('-100%', '-100%'); // rotate 180° -$flipped = $image->resize(-125, 500); // resize & flip horizontal +$flipped = $image->resize(-125, 500); // resize & flip horizontally ``` -After reducing the image we can improve it by sharpening: +After resizing an image, you can enhance its appearance with subtle sharpening: ```php $image->sharpen(); @@ -157,13 +168,13 @@ $image->sharpen(); Cropping ======== -The method `crop()` is used for cropping: +The `crop()` method is used for cropping: ```php $image->crop($left, $top, $width, $height); ``` -As with `resize()`, all values can be specified in percentages. The percentages for `$left` and `$top` are calculated from the remaining space, similar to the CSS property `background-position`: +Similar to `resize()`, all values can be specified in percentages. Percentages for `$left` and `$top` are calculated from the remaining space, similar to the CSS `background-position` property: ```php $image->crop('100%', '50%', '80%', '80%'); @@ -172,243 +183,268 @@ $image->crop('100%', '50%', '80%', '80%'); [* crop.svg *] -The image can also be cropped automatically, eg cropped black edges: +The image can also be cropped automatically, for example, to remove black borders: ```php $image->cropAuto(IMG_CROP_BLACK); ``` -Method `cropAuto()` is an object encapsulation of the `imagecropauto()` function, see [its documentation|https://www.php.net/manual/en/function.imagecropauto] for more information. +The `cropAuto()` method is an object-oriented wrapper for the `imagecropauto()` function; see [its documentation|https://www.php.net/manual/en/function.imagecropauto] for more information. + + +Colors .{data-version:4.0.2} +============================ + +The `ImageColor::rgb()` method allows you to define a color using red, green, and blue (RGB) values. Optionally, you can also specify a transparency value ranging from 0 (completely transparent) to 1 (fully opaque), just like in CSS. + +```php +$color = ImageColor::rgb(255, 0, 0); // Red +$transparentBlue = ImageColor::rgb(0, 0, 255, 0.5); // Semi-transparent blue +``` + +The `ImageColor::hex()` method allows you to define a color using the hexadecimal format, similar to CSS. It supports the formats `#rgb`, `#rrggbb`, `#rgba`, and `#rrggbbaa`: + +```php +$color = ImageColor::hex("#F00"); // Red +$transparentGreen = ImageColor::hex("#00FF0080"); // Semi-transparent green +``` + +Colors can be used in other methods, such as `ellipse()`, `fill()`, etc. Drawing and Editing =================== -You can draw, you can write, you can use all PHP functions for working with images, such as [imagefilledellipse()|https://www.php.net/manual/en/function.imagefilledellipse.php], but using object style: +All PHP functions for image manipulation are available to you, see [#Overview of Methods], but wrapped in an object-oriented style: ```php -$image->filledEllipse($cx, $cy, $width, $height, Image::rgb(255, 0, 0, 63)); +$image->filledEllipse($centerX, $centerY, $width, $height, ImageColor::rgb(255, 0, 0)); ``` -See [#Overview of Methods]. +Because PHP's native functions for drawing rectangles are somewhat impractical due to coordinate specification, the `Image` class offers replacements: [#rectangleWH()] and [#filledRectangleWH()]. -Merge Multiple Images -===================== +Merging Multiple Images +======================= -You can easily place another image into the image: +You can easily place another image onto the current one: ```php $logo = Image::fromFile('logo.png'); -$blank = Image::fromBlank(320, 240, Image::rgb(52, 132, 210)); +$blank = Image::fromBlank(320, 240, ImageColor::rgb(52, 132, 210)); -// coordinates can be set also in percentage -$blank->place($logo, '80%', '80%'); // near the right bottom corner +// coordinates can also be specified in percentages +$blank->place($logo, '80%', '80%'); // place near the bottom right corner ``` -When pasting, the alpha channel is respected, in addition, we can influence the transparency of the inserted image (we will create a so-called watermark): +When placing, the alpha channel is respected. Additionally, you can influence the transparency of the placed image (creating a watermark): ```php -$blank->place($image, '80%', '80%', 25); // transparency is 25 % +$blank->place($image, '80%', '80%', 25); // transparency is 25% ``` -Such API is really a pleasure to use, isn't it? +Using such an API is truly a pleasure! Overview of Methods =================== -static fromBlank(int $width, int $height, array $color=null): Image .[method] ------------------------------------------------------------------------------ +static fromBlank(int $width, int $height, ?ImageColor $color=null): Image .[method] +----------------------------------------------------------------------------------- Creates a new true color image of the given dimensions. The default color is black. static fromFile(string $file, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------- -Reads an image from a file and returns its type in `$detectedFormat`. The supported types are `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` and `Image::BMP`. +Reads an image from a file and returns its [type |#Formats] in `$detectedFormat`. static fromString(string $s, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------ -Reads an image from a string and returns its type in `$detectedFormat`. The supported types are `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF`, and `Image::BMP`. +Reads an image from a string and returns its [type |#Formats] in `$detectedFormat`. -static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method] ---------------------------------------------------------------------------------- -Creates a color that can be used in other methods, such as `ellipse()`, `fill()`, and so on. +static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method][deprecated] +--------------------------------------------------------------------------------------------- +This function has been replaced by the `ImageColor` class, see [#Colors]. static typeToExtension(int $type): string .[method] --------------------------------------------------- -Returns the file extension for the given `Image::XXX` constant. +Returns the file extension for the given [type |#Formats]. static typeToMimeType(int $type): string .[method] -------------------------------------------------- -Returns the mime type for the given `Image::XXX` constant. +Returns the MIME type for the given [type |#Formats]. static extensionToType(string $extension): int .[method] -------------------------------------------------------- -Returns the image type as a constant `Image::XXX` according to the file extension. +Returns the image [type |#Formats] based on the file extension. static detectTypeFromFile(string $file, int &$width=null, int &$height=null): ?int .[method] -------------------------------------------------------------------------------------------- -Returns the type of image file as `Image::XXX` constant and in the `$width` and `$height` parameters also its dimensions. +Returns the [type |#Formats] of the image file and, in the `$width` and `$height` parameters, also its dimensions. static detectTypeFromString(string $s, int &$width=null, int &$height=null): ?int .[method] ------------------------------------------------------------------------------------------- -Returns the type of image from string as `Image::XXX` constant and in the `$width` and `$height` parameters also its dimensions. +Returns the [type |#Formats] of the image from a string and, in the `$width` and `$height` parameters, also its dimensions. -affine(array $affine, array $clip=null): Image .[method] --------------------------------------------------------- -Return an image containing the affine transformed src image, using an optional clipping area. ([more|https://www.php.net/manual/en/function.imageaffine]). +static isTypeSupported(int $type): bool .[method] +------------------------------------------------- +Checks if the given image [type |#Formats] is supported. + + +static getSupportedTypes(): array .[method]{data-version:4.0.4} +--------------------------------------------------------------- +Returns an array of supported image [types |#Formats]. + + +static calculateTextBox(string $text, string $fontFile, float $size, float $angle=0, array $options=[]): array .[method] +------------------------------------------------------------------------------------------------------------------------ +Calculates the dimensions of the rectangle bounding the text in a specific font and size. Returns an associative array containing the keys `left`, `top`, `width`, `height`. The left margin can be negative if the text starts with a left kerning overhang. + + +affine(array $affine, ?array $clip=null): Image .[method] +--------------------------------------------------------- +Returns an image containing the affine transformed source image, using an optional clipping area. ([more |https://www.php.net/manual/en/function.imageaffine]). affineMatrixConcat(array $m1, array $m2): array .[method] --------------------------------------------------------- -Returns the concatenation of two affine transformation matrices, what is useful if multiple transformations should be applied to the same image in one go. ([more|https://www.php.net/manual/en/function.imageaffinematrixconcat]) +Returns the concatenation of two affine transformation matrices, which is useful if multiple transformations should be applied to the same image in one go. ([more |https://www.php.net/manual/en/function.imageaffinematrixconcat]) -affineMatrixGet(int $type, mixed $options=null): array .[method] ----------------------------------------------------------------- -Returns an affine transformation matrix. ([more|https://www.php.net/manual/en/function.imageaffinematrixget]) +affineMatrixGet(int $type, ?mixed $options=null): array .[method] +----------------------------------------------------------------- +Returns an affine transformation matrix. ([more |https://www.php.net/manual/en/function.imageaffinematrixget]) alphaBlending(bool $on): void .[method] --------------------------------------- -Allows for two different modes of drawing on truecolor images. In blending mode, the alpha channel component of the color supplied to all drawing function, such as `setPixel()` determines how much of the underlying color should be allowed to shine through. As a result, it automatically blends the existing color at that point with the drawing color, and stores the result in the image. The resulting pixel is opaque. In non-blending mode, the drawing color is copied literally with its alpha channel information, replacing the destination pixel. Blending mode is not available when drawing on palette images. ([more|https://www.php.net/manual/en/function.imagealphablending]) +Allows for two different modes of drawing on truecolor images. In blending mode, the alpha channel component of the color supplied to all drawing function, such as `setPixel()`, determines how much of the underlying color should be allowed to shine through. As a result, it automatically blends the existing color at that point with the drawing color, and stores the result in the image. The resulting pixel is opaque. In non-blending mode, the drawing color is copied literally with its alpha channel information, replacing the destination pixel. Blending mode is not available when drawing on palette images. ([more |https://www.php.net/manual/en/function.imagealphablending]) antialias(bool $on): void .[method] ----------------------------------- Activate the fast drawing antialiased methods for lines and wired polygons. It does not support alpha components. It works using a direct blend operation. It works only with truecolor images. -Using antialiased primitives with transparent background color can end with some unexpected results. The blend method uses the background color as any other colors. The lack of alpha component support does not allow an alpha based antialiasing method. ([more|https://www.php.net/manual/en/function.imageantialias]) - - -arc(int $x, int $y, int $w, int $h, int $start, int $end, int $color): void .[method] -------------------------------------------------------------------------------------- -Draws an arc of circle centered at the given coordinates. ([more|https://www.php.net/manual/en/function.imagearc]) - - -char(int $font, int $x, int $y, string $char, int $color): void .[method] -------------------------------------------------------------------------- -Draws the first character of `$char` in the image with its upper-left at `$x`,`$y` (top left is 0, 0) with the color `$color`. ([more|https://www.php.net/manual/en/function.imagechar]) +Using antialiased primitives with transparent background color can end with some unexpected results. The blend method uses the background color as any other colors. The lack of alpha component support does not allow an alpha based antialiasing method. ([more |https://www.php.net/manual/en/function.imageantialias]) -charUp(int $font, int $x, int $y, string $char, int $color): void .[method] ---------------------------------------------------------------------------- -Draws the character `$char` vertically at the specified coordinate on the given image. ([more|https://www.php.net/manual/en/function.imagecharup]) +arc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color): void .[method] +--------------------------------------------------------------------------------------------------------------------------- +Draws a circular arc centered at the given coordinates. ([more |https://www.php.net/manual/en/function.imagearc]) colorAllocate(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------- -Returns a color identifier representing the color composed of the given RGB components. It must be called to create each color that is to be used in the image. ([more|https://www.php.net/manual/en/function.imagecolorallocate]) +Returns a color identifier representing the color composed of the given RGB components. It must be called to create each color that is to be used in the image. ([more |https://www.php.net/manual/en/function.imagecolorallocate]) colorAllocateAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ------------------------------------------------------------------------------ -Behaves identically to `colorAllocate()` with the addition of the transparency parameter `$alpha`. ([more|https://www.php.net/manual/en/function.imagecolorallocatealpha]) +Behaves identically to `colorAllocate()` with the addition of the transparency parameter `$alpha`. ([more |https://www.php.net/manual/en/function.imagecolorallocatealpha]) colorAt(int $x, int $y): int .[method] -------------------------------------- -Returns the index of the color of the pixel at the specified location in the image. If the image is a truecolor image, this function returns the RGB value of that pixel as integer. Use bitshifting and masking to access the distinct red, green and blue component values: ([more|https://www.php.net/manual/en/function.imagecolorat]) +Returns the color index of the pixel at the specified location in the image. If the image is a truecolor image, this function returns the RGB value of that pixel as an integer. Use bit shifting and masking to access the distinct red, green, and blue component values: ([more |https://www.php.net/manual/en/function.imagecolorat]) colorClosest(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------ -Returns the index of the color in the palette of the image which is "closest" to the specified RGB value. The "distance" between the desired color and each color in the palette is calculated as if the RGB values represented points in three-dimensional space. ([more|https://www.php.net/manual/en/function.imagecolorclosest]) +Returns the index of the color in the image's palette that is "closest" to the specified RGB value. The "distance" between the desired color and each color in the palette is calculated as if the RGB values represented points in three-dimensional space. ([more |https://www.php.net/manual/en/function.imagecolorclosest]) colorClosestAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ----------------------------------------------------------------------------- -Returns the index of the color in the palette of the image which is "closest" to the specified RGB value and `$alpha` level. ([more|https://www.php.net/manual/en/function.imagecolorclosestalpha]) +Returns the index of the color in the image's palette that is "closest" to the specified RGB value and `$alpha` level. ([more |https://www.php.net/manual/en/function.imagecolorclosestalpha]) colorClosestHWB(int $red, int $green, int $blue): int .[method] --------------------------------------------------------------- -Get the index of the color which has the hue, white and blackness nearest the given color. ([more|https://www.php.net/manual/en/function.imagecolorclosesthwb]) +Gets the index of the color that has the hue, white, and blackness nearest to the given color. ([more |https://www.php.net/manual/en/function.imagecolorclosesthwb]) colorDeallocate(int $color): void .[method] ------------------------------------------- -De-allocates a color previously allocated with `colorAllocate()` or `colorAllocateAlpha()`. ([more|https://www.php.net/manual/en/function.imagecolordeallocate]) +Deallocates a color previously allocated with `colorAllocate()` or `colorAllocateAlpha()`. ([more |https://www.php.net/manual/en/function.imagecolordeallocate]) colorExact(int $red, int $green, int $blue): int .[method] ---------------------------------------------------------- -Returns the index of the specified color in the palette of the image. ([more|https://www.php.net/manual/en/function.imagecolorexact]) +Returns the index of the specified color in the image's palette. ([more |https://www.php.net/manual/en/function.imagecolorexact]) colorExactAlpha(int $red, int $green, int $blue, int $alpha): int .[method] --------------------------------------------------------------------------- -Returns the index of the specified color+alpha in the palette of the image. ([more|https://www.php.net/manual/en/function.imagecolorexactalpha]) +Returns the index of the specified color + alpha in the image's palette. ([more |https://www.php.net/manual/en/function.imagecolorexactalpha]) colorMatch(Image $image2): void .[method] ----------------------------------------- -Makes the colors of the palette version of an image more closely match the true color version. ([more|https://www.php.net/manual/en/function.imagecolormatch]) +Makes the colors of the palette version of an image more closely match the true color version. ([more |https://www.php.net/manual/en/function.imagecolormatch]) colorResolve(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------ -Returns a color index for a requested color, either the exact color or the closest possible alternative. ([more|https://www.php.net/manual/en/function.imagecolorresolve]) +Returns a color index for a requested color, either the exact color or the closest possible alternative. ([more |https://www.php.net/manual/en/function.imagecolorresolve]) colorResolveAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ----------------------------------------------------------------------------- -Returns a color index for a requested color, either the exact color or the closest possible alternative. ([more|https://www.php.net/manual/en/function.imagecolorresolvealpha]) +Returns a color index for a requested color, either the exact color or the closest possible alternative. ([more |https://www.php.net/manual/en/function.imagecolorresolvealpha]) colorSet(int $index, int $red, int $green, int $blue): void .[method] --------------------------------------------------------------------- -This sets the specified index in the palette to the specified color. ([more|https://www.php.net/manual/en/function.imagecolorset]) +Sets the specified index in the palette to the specified color. ([more |https://www.php.net/manual/en/function.imagecolorset]) colorsForIndex(int $index): array .[method] ------------------------------------------- -Gets the color for a specified index. ([more|https://www.php.net/manual/en/function.imagecolorsforindex]) +Gets the color for a specified index. ([more |https://www.php.net/manual/en/function.imagecolorsforindex]) colorsTotal(): int .[method] ---------------------------- -Returns the number of colors in an image palette. ([more|https://www.php.net/manual/en/function.imagecolorstotal]) +Returns the number of colors in the image palette. ([more |https://www.php.net/manual/en/function.imagecolorstotal]) -colorTransparent(int $color=null): int .[method] ------------------------------------------------- -Gets or sets the transparent color in the image. ([more|https://www.php.net/manual/en/function.imagecolortransparent]) +colorTransparent(?int $color=null): int .[method] +------------------------------------------------- +Gets or sets the transparent color in the image. ([more |https://www.php.net/manual/en/function.imagecolortransparent]) convolution(array $matrix, float $div, float $offset): void .[method] --------------------------------------------------------------------- -Applies a convolution matrix on the image, using the given coefficient and offset. ([more|https://www.php.net/manual/en/function.imageconvolution]) +Applies a convolution matrix to the image, using the given coefficient and offset. ([more |https://www.php.net/manual/en/function.imageconvolution]) .[note] -Requires *Bundled GD extension*, so it is not sure it will work everywhere. +Requires the *Bundled GD extension*, so it might not work everywhere. copy(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH): void .[method] -------------------------------------------------------------------------------------------------- -Copies a part of `$src` onto image starting at the coordinates `$srcX`, `$srcY` with a width of `$srcW` and a height of `$srcH`. The portion defined will be copied onto the coordinates, `$dstX` and `$dstY`. ([more|https://www.php.net/manual/en/function.imagecopy]) +Copies a part of `$src` onto image starting at the coordinates `$srcX`, `$srcY` with a width of `$srcW` and a height of `$srcH`. The portion defined will be copied onto the coordinates, `$dstX` and `$dstY`. ([more |https://www.php.net/manual/en/function.imagecopy]) copyMerge(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] --------------------------------------------------------------------------------------------------------------------- -Copies a part of `$src` onto image starting at the coordinates `$srcX`, `$srcY` with a width of `$srcW` and a height of `$srcH`. The portion defined will be copied onto the coordinates, `$dstX` and `$dstY`. ([more|https://www.php.net/manual/en/function.imagecopymerge]) +Copies a part of `$src` onto image starting at the coordinates `$srcX`, `$srcY` with a width of `$srcW` and a height of `$srcH`. The portion defined will be copied onto the coordinates, `$dstX` and `$dstY`. ([more |https://www.php.net/manual/en/function.imagecopymerge]) copyMergeGray(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] ------------------------------------------------------------------------------------------------------------------------- Copies a part of `$src` onto image starting at the coordinates `$srcX`, `$srcY` with a width of `$srcW` and a height of `$srcH`. The portion defined will be copied onto the coordinates, `$dstX` and `$dstY`. -This function is identical to `copyMerge()` except that when merging it preserves the hue of the source by converting the destination pixels to gray scale before the copy operation. ([more|https://www.php.net/manual/en/function.imagecopymergegray]) +This function is identical to `copyMerge()` except that when merging it preserves the hue of the source by converting the destination pixels to gray scale before the copy operation. ([more |https://www.php.net/manual/en/function.imagecopymergegray]) copyResampled(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] @@ -417,84 +453,89 @@ Copies a rectangular portion of one image to another image, smoothly interpolati In other words, `copyResampled()` will take a rectangular area from `$src` of width `$srcW` and height `$srcH` at position (`$srcX`,`$srcY`) and place it in a rectangular area of image of width `$dstW` and height `$dstH` at position (`$dstX`,`$dstY`). -If the source and destination coordinates and width and heights differ, appropriate stretching or shrinking of the image fragment will be performed. The coordinates refer to the upper left corner. This function can be used to copy regions within the same image but if the regions overlap the results will be unpredictable. ([more|https://www.php.net/manual/en/function.imagecopyresampled]) +If the source and destination coordinates and width and heights differ, appropriate stretching or shrinking of the image fragment will be performed. The coordinates refer to the upper left corner. This function can be used to copy regions within the same image but if the regions overlap the results will be unpredictable. ([more |https://www.php.net/manual/en/function.imagecopyresampled]) copyResized(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] ------------------------------------------------------------------------------------------------------------------------------- Copies a rectangular portion of one image to another image. In other words, `copyResized()` will take a rectangular area from `$src` of width `$srcW` and height `$srcH` at position (`$srcX`,`$srcY`) and place it in a rectangular area of image of width `$dstW` and height `$dstH` at position (`$dstX`,`$dstY`). -If the source and destination coordinates and width and heights differ, appropriate stretching or shrinking of the image fragment will be performed. The coordinates refer to the upper left corner. This function can be used to copy regions within the same image but if the regions overlap the results will be unpredictable. ([more|https://www.php.net/manual/en/function.imagecopyresized]) +If the source and destination coordinates and width and heights differ, appropriate stretching or shrinking of the image fragment will be performed. The coordinates refer to the upper left corner. This function can be used to copy regions within the same image but if the regions overlap the results will be unpredictable. ([more |https://www.php.net/manual/en/function.imagecopyresized]) crop(int|string $left, int|string $top, int|string $width, int|string $height): Image .[method] ----------------------------------------------------------------------------------------------- -Crops an image to the given rectangular area. Dimensions can be passed as integers in pixels or strings in percent (i.e. `'50%'`). +Crops an image to the given rectangular area. Dimensions can be specified as integers in pixels or as strings in percentages (e.g., `'50%'`). -cropAuto(int $mode=-1, float $threshold=.5, int $color=-1): Image .[method] ---------------------------------------------------------------------------- -Automatically crops an image according to the given `$mode`. ([more|https://www.php.net/manual/en/function.imagecropauto]) +cropAuto(int $mode=-1, float $threshold=.5, ?ImageColor $color=null): Image .[method] +------------------------------------------------------------------------------------- +Automatically crops an image according to the given `$mode`. ([more |https://www.php.net/manual/en/function.imagecropauto]) -ellipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------- -Draws an ellipse centered at the specified coordinates. ([more|https://www.php.net/manual/en/function.imageellipse]) +ellipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------- +Draws an ellipse centered at the specified coordinates. ([more |https://www.php.net/manual/en/function.imageellipse]) -fill(int $x, int $y, int $color): void .[method] ------------------------------------------------- -Performs a flood fill starting at the given coordinate (top left is 0, 0) with the given `$color` in the image. ([more|https://www.php.net/manual/en/function.imagefill]) +fill(int $x, int $y, ImageColor $color): void .[method] +------------------------------------------------------- +Performs a flood fill starting at the given coordinate (top left is 0, 0) with the given `$color`. ([more |https://www.php.net/manual/en/function.imagefill]) -filledArc(int $cx, int $cy, int $w, int $h, int $s, int $e, int $color, int $style): void .[method] ---------------------------------------------------------------------------------------------------- -Draws a partial arc centered at the specified coordinate in the image. ([more|https://www.php.net/manual/en/function.imagefilledarc]) +filledArc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color, int $style): void .[method] +--------------------------------------------------------------------------------------------------------------------------------------------- +Draws a partial arc centered at the specified coordinates. ([more |https://www.php.net/manual/en/function.imagefilledarc]) -filledEllipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------------- -Draws an ellipse centered at the specified coordinate in the image. ([more|https://www.php.net/manual/en/function.imagefilledellipse]) +filledEllipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------------- +Draws an ellipse centered at the specified coordinates. ([more |https://www.php.net/manual/en/function.imagefilledellipse]) -filledPolygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------------- -Creates a filled polygon in the $image. ([more|https://www.php.net/manual/en/function.imagefilledpolygon]) +filledPolygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------------- +Creates a filled polygon in the image. ([more |https://www.php.net/manual/en/function.imagefilledpolygon]) -filledRectangle(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] -------------------------------------------------------------------------------- -Creates a rectangle filled with `$color` in the image starting at point 1 and ending at point 2. 0, 0 is the top left corner of the image. ([more|https://www.php.net/manual/en/function.imagefilledrectangle]) +filledRectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------- +Creates a rectangle filled with `$color` in the image, starting at point (`$x1`, `$y1`) and ending at (`$x2`, `$y2`). Point (0, 0) is the top-left corner of the image. ([more |https://www.php.net/manual/en/function.imagefilledrectangle]) -fillToBorder(int $x, int $y, int $border, int $color): void .[method] ---------------------------------------------------------------------- -Performs a flood fill whose border color is defined by `$border`. The starting point for the fill is `$x`, `$y` (top left is 0, 0) and the region is filled with color `$color`. ([more|https://www.php.net/manual/en/function.imagefilltoborder]) +filledRectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------------- +Creates a rectangle filled with `$color` in the image, starting at point (`$left`, `$top`) with width `$width` and height `$height`. Point (0, 0) is the top-left corner of the image. + + +fillToBorder(int $x, int $y, int $border, ImageColor $color): void .[method] +---------------------------------------------------------------------------- +Performs a flood fill whose border color is defined by `$border`. The starting point for the fill is (`$x`, `$y`) (top-left is 0, 0), and the region is filled with the color `$color`. ([more |https://www.php.net/manual/en/function.imagefilltoborder]) filter(int $filtertype, int ...$args): void .[method] ----------------------------------------------------- -Applies the given filter `$filtertype` on the image. ([more|https://www.php.net/manual/en/function.imagefilter]) +Applies the given filter `$filtertype` to the image. ([more |https://www.php.net/manual/en/function.imagefilter]) flip(int $mode): void .[method] ------------------------------- -Flips the image using the given `$mode`. ([more|https://www.php.net/manual/en/function.imageflip]) +Flips the image using the given `$mode`. ([more |https://www.php.net/manual/en/function.imageflip]) -ftText(int $size, int $angle, int $x, int $y, int $col, string $fontFile, string $text, array $extrainfo=null): array .[method] -------------------------------------------------------------------------------------------------------------------------------- -Write text to the image using fonts using FreeType 2. ([more|https://www.php.net/manual/en/function.imagefttext]) +ftText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +---------------------------------------------------------------------------------------------------------------------------------------- +Writes text to the image. ([more |https://www.php.net/manual/en/function.imagefttext]) gammaCorrect(float $inputgamma, float $outputgamma): void .[method] ------------------------------------------------------------------- -Applies gamma correction to the image given an input and an output gamma. ([more|https://www.php.net/manual/en/function.imagegammacorrect]) +Applies gamma correction to the image given an input and an output gamma. ([more |https://www.php.net/manual/en/function.imagegammacorrect]) getClip(): array .[method] -------------------------- -Retrieves the current clipping rectangle, i.e. the area beyond which no pixels will be drawn. ([more|https://www.php.net/manual/en/function.imagegetclip]) +Retrieves the current clipping rectangle, i.e., the area beyond which no pixels will be drawn. ([more |https://www.php.net/manual/en/function.imagegetclip]) getHeight(): int .[method] @@ -504,7 +545,7 @@ Returns the height of the image. getImageResource(): resource|GdImage .[method] ---------------------------------------------- -Returns the original resource. +Returns the underlying GD image resource. getWidth(): int .[method] @@ -512,169 +553,164 @@ getWidth(): int .[method] Returns the width of the image. -interlace(int $interlace=null): int .[method] ---------------------------------------------- -Turns the interlace bit on or off. If the interlace bit is set and the image is used as a JPEG image, the image is created as a progressive JPEG. ([more|https://www.php.net/manual/en/function.imageinterlace]) +interlace(?int $interlace=null): int .[method] +---------------------------------------------- +Turns interlacing on or off. If interlacing is enabled and the image is saved as JPEG, it will be saved as a progressive JPEG. ([more |https://www.php.net/manual/en/function.imageinterlace]) isTrueColor(): bool .[method] ----------------------------- -Finds whether the image is a truecolor. ([more|https://www.php.net/manual/en/function.imageistruecolor]) +Checks if the image is a truecolor image. ([more |https://www.php.net/manual/en/function.imageistruecolor]) layerEffect(int $effect): void .[method] ---------------------------------------- -Set the alpha blending flag to use layering effects. ([more|https://www.php.net/manual/en/function.imagelayereffect]) +Sets the alpha blending flag to use layering effects. ([more |https://www.php.net/manual/en/function.imagelayereffect]) -line(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] --------------------------------------------------------------------- -Draws a line between the two given points. ([more|https://www.php.net/manual/en/function.imageline]) +line(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +--------------------------------------------------------------------------- +Draws a line between the two given points. ([more |https://www.php.net/manual/en/function.imageline]) -openPolygon(array $points, int $numPoints, int $color): void .[method] ----------------------------------------------------------------------- -Draws an open polygon on the image. Contrary to `polygon()`, no line is drawn between the last and the first point. ([more|https://www.php.net/manual/en/function.imageopenpolygon]) +openPolygon(array $points, ImageColor $color): void .[method] +------------------------------------------------------------- +Draws an open polygon on the image. Unlike `polygon()`, no line is drawn between the last and the first point. ([more |https://www.php.net/manual/en/function.imageopenpolygon]) paletteCopy(Image $source): void .[method] ------------------------------------------ -Copies the palette from the `$source` to the image. ([more|https://www.php.net/manual/en/function.imagepalettecopy]) +Copies the palette from `$source` to the image. ([more |https://www.php.net/manual/en/function.imagepalettecopy]) paletteToTrueColor(): void .[method] ------------------------------------ -Converts a palette based image, created by functions like `create()` to a true color image, like `createtruecolor()`. ([more|https://www.php.net/manual/en/function.imagepalettetotruecolor]) +Converts a palette-based image to a truecolor image. ([more |https://www.php.net/manual/en/function.imagepalettetotruecolor]) place(Image $image, int|string $left=0, int|string $top=0, int $opacity=100): Image .[method] --------------------------------------------------------------------------------------------- -Copies `$image` to the image at the coordinates `$left` and `$top`. Coordinates can be passed as integers in pixels or strings in percent (i.e. `'50%'`). +Copies `$image` onto the current image at coordinates (`$left`, `$top`). Coordinates can be specified as integers in pixels or as strings in percentages (e.g., `'50%'`). -polygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------- -Creates a polygon in the image. ([more|https://www.php.net/manual/en/function.imagepolygon]) +polygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------- +Creates a polygon in the image. ([more |https://www.php.net/manual/en/function.imagepolygon]) -rectangle(int $x1, int $y1, int $x2, int $y2, int $col): void .[method] ------------------------------------------------------------------------ -Creates a rectangle starting at the specified coordinates. ([more|https://www.php.net/manual/en/function.imagerectangle]) +rectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------- +Creates a rectangle at the specified coordinates. ([more |https://www.php.net/manual/en/function.imagerectangle]) + + +rectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------- +Creates a rectangle at the given coordinates using width and height. resize(int|string $width, int|string $height, int $flags=Image::OrSmaller): Image .[method] ------------------------------------------------------------------------------------------- -Scales an image, see [more info|#Image Resize]. Dimensions can be passed as integers in pixels or strings in percent (i.e. `'50%'`). +Resizes an image, see [more info |#Resizing]. Dimensions can be specified as integers in pixels or as strings in percentages (e.g., `'50%'`). -resolution(int $resX=null, int $resY=null): mixed .[method] ------------------------------------------------------------ +resolution(?int $resX=null, ?int $resY=null): mixed .[method] +------------------------------------------------------------- Allows to set and get the resolution of an image in DPI (dots per inch). If none of the optional parameters is given, the current resolution is returned as indexed array. If only `$resX` is given, the horizontal and vertical resolution are set to this value. If both optional parameters are given, the horizontal and vertical resolution are set to these values, respectively. -The resolution is only used as meta information when images are read from and written to formats supporting this kind of information (curently PNG and JPEG). It does not affect any drawing operations. The default resolution for new images is 96 DPI. ([more|https://www.php.net/manual/en/function.imageresolution]) +The resolution is only used as meta information when images are read from and written to formats supporting this kind of information (currently PNG and JPEG). It does not affect any drawing operations. The default resolution for new images is 96 DPI. ([more |https://www.php.net/manual/en/function.imageresolution]) rotate(float $angle, int $backgroundColor): Image .[method] ----------------------------------------------------------- -Rotates the image using the given `$angle` in degrees. The center of rotation is the center of the image, and the rotated image may have different dimensions than the original image. ([more|https://www.php.net/manual/en/function.imagerotate]) +Rotates the image by the given `$angle` in degrees. The center of rotation is the center of the image, and the rotated image may have different dimensions than the original image. ([more |https://www.php.net/manual/en/function.imagerotate]) .[note] -Requires *Bundled GD extension*, so it is not sure it will work everywhere. +Requires the *Bundled GD extension*, so it might not work everywhere. -save(string $file, int $quality=null, int $type=null): void .[method] ---------------------------------------------------------------------- -Saves an image to a file. +save(string $file, ?int $quality=null, ?int $type=null): void .[method] +----------------------------------------------------------------------- +Saves the image to a file. -The compression quality is in the range 0..100 for JPEG (default 85), WEBP (default 80) and AVIF (default 30) and 0..9 for PNG (default 9). If the type is not obvious from the file extension, you can specify it using one of the constants `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF`, and `Image::BMP`. +Compression quality is in the range 0-100 for JPEG (default 85), WEBP (default 80), and AVIF (default 30), and 0-9 for PNG (default 9). If the type is not clear from the file extension, you can specify it using one of the `ImageType` constants. saveAlpha(bool $saveflag): void .[method] ----------------------------------------- -Sets the flag which determines whether to retain full alpha channel information (as opposed to single-color transparency) when saving PNG images. +Sets the flag determining whether to save full alpha channel information (as opposed to single-color transparency) when saving PNG images. -Alphablending has to be disabled (`alphaBlending(false)`) to retain the alpha-channel in the first place. ([more|https://www.php.net/manual/en/function.imagesavealpha]) +Alphablending must be disabled (`alphaBlending(false)`) to retain the alpha channel in the first place. ([more |https://www.php.net/manual/en/function.imagesavealpha]) scale(int $newWidth, int $newHeight=-1, int $mode=IMG_BILINEAR_FIXED): Image .[method] -------------------------------------------------------------------------------------- -Scales an image using the given interpolation algorithm. ([more|https://www.php.net/manual/en/function.imagescale]) +Scales an image using the given interpolation algorithm. ([more |https://www.php.net/manual/en/function.imagescale]) -send(int $type=Image::JPEG, int $quality=null): void .[method] --------------------------------------------------------------- -Outputs an image to the browser. +send(int $type=ImageType::JPEG, ?int $quality=null): void .[method] +------------------------------------------------------------------- +Outputs the image to the browser. -The compression quality is in the range 0..100 for JPEG (default 85), WEBP (default 80) and AVIF (default 30) and 0..9 for PNG (default 9). Type is one of the constants `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` and `Image::BMP`. +Compression quality is in the range 0-100 for JPEG (default 85), WEBP (default 80), and AVIF (default 30), and 0-9 for PNG (default 9). setBrush(Image $brush): void .[method] -------------------------------------- -Sets the brush image to be used by all line drawing functions (such as `line()` and `polygon()`) when drawing with the special colors IMG_COLOR_BRUSHED or IMG_COLOR_STYLEDBRUSHED. ([more|https://www.php.net/manual/en/function.imagesetbrush]) +Sets the brush image to be used by all line drawing functions (such as `line()` and `polygon()`) when drawing with the special colors `IMG_COLOR_BRUSHED` or `IMG_COLOR_STYLEDBRUSHED`. ([more |https://www.php.net/manual/en/function.imagesetbrush]) setClip(int $x1, int $y1, int $x2, int $y2): void .[method] ----------------------------------------------------------- -Sets the current clipping rectangle, i.e. the area beyond which no pixels will be drawn. ([more|https://www.php.net/manual/en/function.imagesetclip]) +Sets the current clipping rectangle, i.e., the area beyond which no pixels will be drawn. ([more |https://www.php.net/manual/en/function.imagesetclip]) setInterpolation(int $method=IMG_BILINEAR_FIXED): void .[method] ---------------------------------------------------------------- -Sets the interpolation method which affects methods `rotate()` and `affine()`. ([more|https://www.php.net/manual/en/function.imagesetinterpolation]) +Sets the interpolation method, which affects the `rotate()` and `affine()` methods. ([more |https://www.php.net/manual/en/function.imagesetinterpolation]) -setPixel(int $x, int $y, int $color): void .[method] ----------------------------------------------------- -Draws a pixel at the specified coordinate. ([more|https://www.php.net/manual/en/function.imagesetpixel]) +setPixel(int $x, int $y, ImageColor $color): void .[method] +----------------------------------------------------------- +Draws a pixel at the specified coordinate. ([more |https://www.php.net/manual/en/function.imagesetpixel]) setStyle(array $style): void .[method] -------------------------------------- -Sets the style to be used by all line drawing functions (such as `line()` and `polygon()`) when drawing with the special color IMG_COLOR_STYLED or lines of images with color IMG_COLOR_STYLEDBRUSHED. ([more|https://www.php.net/manual/en/function.imagesetstyle]) +Sets the style to be used by all line drawing functions (such as `line()` and `polygon()`) when drawing with the special color `IMG_COLOR_STYLED` or lines of images with the color `IMG_COLOR_STYLEDBRUSHED`. ([more |https://www.php.net/manual/en/function.imagesetstyle]) setThickness(int $thickness): void .[method] -------------------------------------------- -Sets the thickness of the lines drawn when drawing rectangles, polygons, arcs etc. to `$thickness` pixels. ([more|https://www.php.net/manual/en/function.imagesetthickness]) +Sets the thickness of lines drawn when drawing rectangles, polygons, arcs, etc., to `$thickness` pixels. ([more |https://www.php.net/manual/en/function.imagesetthickness]) setTile(Image $tile): void .[method] ------------------------------------ -Sets the tile image to be used by all region filling functions (such as `fill()` and `filledPolygon()`) when filling with the special color IMG_COLOR_TILED. +Sets the tile image to be used by all region filling functions (such as `fill()` and `filledPolygon()`) when filling with the special color `IMG_COLOR_TILED`. -A tile is an image used to fill an area with a repeated pattern. Any image can be used as a tile, and by setting the transparent color index of the tile image with `colorTransparent()`, a tile allows certain parts of the underlying area to shine through can be created. ([more|https://www.php.net/manual/en/function.imagesettile]) +A tile is an image used to fill an area with a repeated pattern. Any image can be used as a tile, and by setting the transparent color index of the tile image with `colorTransparent()`, a tile allows certain parts of the underlying area to shine through can be created. ([more |https://www.php.net/manual/en/function.imagesettile]) sharpen(): Image .[method] -------------------------- -Sharpens image a little bit. +Sharpens the image. .[note] -Requires *Bundled GD extension*, so it is not sure it will work everywhere. - - -string(int $font, int $x, int $y, string $str, int $col): void .[method] ------------------------------------------------------------------------- -Draws a string at the given coordinates. ([more|https://www.php.net/manual/en/function.imagestring]) - +Requires the *Bundled GD extension*, so it might not work everywhere. -stringUp(int $font, int $x, int $y, string $s, int $col): void .[method] ------------------------------------------------------------------------- -Draws a string vertically at the given coordinates. ([more|https://www.php.net/manual/en/function.imagestringup]) +toString(int $type=ImageType::JPEG, ?int $quality=null): string .[method] +------------------------------------------------------------------------- +Outputs the image as a string. -toString(int $type=Image::JPEG, int $quality=null): string .[method] --------------------------------------------------------------------- -Outputs an image to string. - -The compression quality is in the range 0..100 for JPEG (default 85), WEBP (default 80) and AVIF (default 30) and 0..9 for PNG (default 9). Type is one of the constants `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` and `Image::BMP`. +Compression quality is in the range 0-100 for JPEG (default 85), WEBP (default 80), and AVIF (default 30), and 0-9 for PNG (default 9). trueColorToPalette(bool $dither, int $ncolors): void .[method] -------------------------------------------------------------- -Converts a truecolor image to a palette image. ([more|https://www.php.net/manual/en/function.imagetruecolortopalette]) +Converts a truecolor image to a palette image. ([more |https://www.php.net/manual/en/function.imagetruecolortopalette]) -ttfText(int $size, int $angle, int $x, int $y, int $color, string $fontfile, string $text): array .[method] ------------------------------------------------------------------------------------------------------------ -Writes the given text into the image using TrueType fonts. ([more|https://www.php.net/manual/en/function.imagettftext]) +ttfText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +----------------------------------------------------------------------------------------------------------------------------------------- +Writes the given text into the image. ([more |https://www.php.net/manual/en/function.imagettftext]) diff --git a/utils/en/iterables.texy b/utils/en/iterables.texy new file mode 100644 index 0000000000..56c86f60d7 --- /dev/null +++ b/utils/en/iterables.texy @@ -0,0 +1,191 @@ +Working with Iterators +********************** + +.[perex]{data-version:4.0.4} +[api:Nette\Utils\Iterables] is a static class with functions for working with iterators. Its counterpart for arrays is [Nette\Utils\Arrays|arrays]. + + +Installation: + +```shell +composer require nette/utils +``` + +All examples assume the following alias is created: + +```php +use Nette\Utils\Iterables; +``` + + +contains(iterable $iterable, $value): bool .[method] +---------------------------------------------------- + +Searches for a given value within an iterator. Uses strict comparison (`===`) to check for a match. Returns `true` if the value is found, otherwise `false`. + +```php +Iterables::contains(new ArrayIterator([1, 2, 3]), 1); // true +Iterables::contains(new ArrayIterator([1, 2, 3]), '1'); // false +``` + +This method is useful when you need to quickly determine if a specific value exists in an iterator without manually iterating through all elements. + + +containsKey(iterable $iterable, $key): bool .[method] +----------------------------------------------------- + +Searches for a given key within an iterator. Uses strict comparison (`===`) to check for a match. Returns `true` if the key is found, otherwise `false`. + +```php +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 0); // true +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 4); // false +``` + + +every(iterable $iterable, callable $predicate): bool .[method] +-------------------------------------------------------------- + +Checks if all elements of the iterator satisfy the condition defined in `$predicate`. The callback `$predicate` has the signature `function ($value, $key, iterable $iterable): bool` and must return `true` for every element for the `every()` method to return `true`. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isBelowThreshold = fn($value) => $value < 40; +$res = Iterables::every($iterator, $isBelowThreshold); // true +``` + +This method is useful for verifying that all elements in a collection meet a certain condition, for example, whether all numbers are less than a specific value. + + +filter(iterable $iterable, callable $predicate): Generator .[method] +-------------------------------------------------------------------- + +Creates a new iterator containing only those elements from the original iterator that satisfy the condition defined in `$predicate`. The callback `$predicate` has the signature `function ($value, $key, iterable $iterable): bool` and must return `true` for elements that should be retained. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::filter($iterator, fn($v) => $v < 3); +// 1, 2 +``` + +The method uses a generator, meaning filtering occurs incrementally as you iterate through the result. This is memory-efficient and allows processing very large collections. If you don't iterate through all elements of the resulting iterator, you save computational power because not all elements of the original iterator are processed. + + +first(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------------- + +Returns the first element of the iterator. If `$predicate` is provided, it returns the first element that satisfies the condition. The callback `$predicate` has the signature `function ($value, $key, iterable $iterable): bool`. If no matching element is found, the `$else` callback (if provided) is called, and its result is returned. If `$else` is not provided, `null` is returned. + +```php +Iterables::first(new ArrayIterator([1, 2, 3])); // 1 +Iterables::first(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 3 +Iterables::first(new ArrayIterator([])); // null +Iterables::first(new ArrayIterator([]), else: fn() => false); // false +``` + +This method is useful when you need to quickly get the first element of a collection, or the first element satisfying a specific condition, without manually iterating through the entire collection. + + +firstKey(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +---------------------------------------------------------------------------------------------- + +Returns the key of the first element of the iterator. If `$predicate` is provided, it returns the key of the first element that satisfies the condition. The callback `$predicate` has the signature `function ($value, $key, iterable $iterable): bool`. If no matching element is found, the `$else` callback (if provided) is called, and its result is returned. If `$else` is not provided, `null` is returned. + +```php +Iterables::firstKey(new ArrayIterator([1, 2, 3])); // 0 +Iterables::firstKey(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 2 +Iterables::firstKey(new ArrayIterator(['a' => 1, 'b' => 2])); // 'a' +Iterables::firstKey(new ArrayIterator([])); // null +``` + + +map(iterable $iterable, callable $transformer): Generator .[method] +------------------------------------------------------------------- + +Creates a new iterator by applying the `$transformer` callback to each element of the original iterator. The callback `$transformer` has the signature `function ($value, $key, iterable $iterable): mixed`, and its return value is used as the new value of the element. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::map($iterator, fn($v) => $v * 2); +// 2, 4, 6 +``` + +The method uses a generator, meaning the transformation occurs incrementally as you iterate through the result. This is memory-efficient and allows processing very large collections. If you don't iterate through all elements of the resulting iterator, you save computational power because not all elements of the original iterator are processed. + + +mapWithKeys(iterable $iterable, callable $transformer): Generator .[method] +--------------------------------------------------------------------------- + +Creates a new iterator by transforming the values and keys of the original iterator. The callback `$transformer` has the signature `function ($value, $key, iterable $iterable): ?array{$newKey, $newValue}`. If `$transformer` returns `null`, the element is skipped. For retained elements, the first element of the returned array is used as the new key, and the second element as the new value. + +```php +$iterator = new ArrayIterator(['a' => 1, 'b' => 2]); +$iterator = Iterables::mapWithKeys($iterator, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +Like `map()`, this method uses a generator for incremental processing and memory efficiency. This allows working with large collections and saving computational power when only partially iterating through the result. + + +memoize(iterable $iterable): IteratorAggregate .[method] +-------------------------------------------------------- + +Creates a wrapper around an iterator that caches its keys and values during iteration. This allows repeated iteration over the data without needing to traverse the original data source again. + +```php +$iterator = /* data that cannot be iterated multiple times */; +$memoized = Iterables::memoize($iterator); +// Now you can iterate $memoized multiple times without data loss +``` + +This method is useful in situations where you need to iterate over the same dataset multiple times, but the original iterator doesn't allow repeated iteration, or re-traversing would be costly (e.g., reading data from a database or file). + + +repeatable(callable $factory): IteratorAggregate .[method]{data-version:4.0.10} +------------------------------------------------------------------------------- + +Allows repeated iteration of objects that otherwise do not support it, typically [PHP generators |https://www.php.net/manual/en/language.generators.overview.php]. The `repeatable()` method solves this problem elegantly: instead of passing the iterator itself, you pass a function that creates it. This factory is then called automatically during each iteration loop. + +```php +// A standard generator that cannot be iterated twice +$generator = function () { + yield 'A'; + yield 'B'; +}; + +$iterator = Iterables::repeatable($generator); + +foreach ($iterator as $v) echo $v; // Prints: AB +foreach ($iterator as $v) echo $v; // Prints: AB (generator ran again) +``` + +This method is an alternative to [#memoize()] in situations where you are working with **large amounts of data**, since `repeatable()` does not cache data, but generates it again during each iteration. + + +some(iterable $iterable, callable $predicate): bool .[method] +------------------------------------------------------------- + +Checks if at least one element of the iterator satisfies the condition defined in `$predicate`. The callback `$predicate` has the signature `function ($value, $key, iterable $iterable): bool` and must return `true` for at least one element for the `some()` method to return `true`. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isEven = fn($value) => $value % 2 === 0; +$res = Iterables::some($iterator, $isEven); // true +``` + +This method is useful for quickly verifying if at least one element in a collection meets a certain condition, for example, whether the collection contains at least one even number. + +See [#every()]. + + +toIterator(iterable $iterable): Iterator .[method] +-------------------------------------------------- + +Converts any iterable object (array, Traversable) into an Iterator. If the input is already an Iterator, it is returned unchanged. + +```php +$array = [1, 2, 3]; +$iterator = Iterables::toIterator($array); +// Now you have an Iterator instead of an array +``` + +This method is useful when you need to ensure you have an Iterator available, regardless of the input data type. This can be helpful when creating functions that work with various types of iterable data. diff --git a/utils/en/json.texy b/utils/en/json.texy index f29024378f..a635f49110 100644 --- a/utils/en/json.texy +++ b/utils/en/json.texy @@ -1,8 +1,8 @@ -JSON Functions -************** +Working with JSON +***************** .[perex] -[api:Nette\Utils\Json] is a static class with JSON encoding and decoding functions. It handles vulnerabilities in different PHP versions and throws exceptions on errors. +[api:Nette\Utils\Json] is a static class with functions for encoding and decoding the JSON format. It addresses vulnerabilities in various PHP versions and throws exceptions upon errors. Installation: @@ -27,14 +27,14 @@ encode(mixed $value, bool $pretty=false, bool $asciiSafe=false, bool $htmlSafe=f Converts `$value` to JSON format. -When `$pretty` is set, it formats JSON for easier reading and clarity: +Setting `$pretty` to `true` formats the JSON for better readability and clarity: ```php Json::encode($value); // returns JSON -Json::encode($value, pretty: true); // returns clearer JSON +Json::encode($value, pretty: true); // returns more readable JSON ``` -When `$asciiSafe` is set, it generates ASCII output, i.e. it replaces the unicode characters with `\uxxxx` sequences: +With `$asciiSafe` set to `true`, it generates ASCII output, meaning Unicode characters are replaced with `\uxxxx` sequences: ```php Json::encode('žluťoučký', asciiSafe: true); @@ -48,7 +48,7 @@ Json::encode('onesendJson($data)` method, which can be called, for example, in the `action*()` method, see [Sending a Response|application:presenters#Sending a Response]. +You can use the `$this->sendJson($data)` method, which can be called, for example, within an `action*()` method. See [Sending a Response |application:presenters#Sending a Response]. diff --git a/utils/en/paginator.texy b/utils/en/paginator.texy index 21e4ceb17b..a3bf9b6421 100644 --- a/utils/en/paginator.texy +++ b/utils/en/paginator.texy @@ -2,7 +2,7 @@ Paginator ********* .[perex] -Need to page a data listing? Because mathematics behind pagination can be tricky, [api:Nette\Utils\Paginator] will help you. +Need to paginate data listings? Since pagination math can be tricky, [api:Nette\Utils\Paginator] is here to help. Installation: @@ -11,38 +11,38 @@ Installation: composer require nette/utils ``` -Lets create a paging object and set basic information for it: +Let's create a paginator object and set some basic information: ```php $paginator = new Nette\Utils\Paginator; -$paginator->setPage(1); // the number of the current page (numbered from 1) -$paginator->setItemsPerPage(30); // the number of records per page -$paginator->setItemCount(356); // the total number of records (if available) +$paginator->setPage(1); // number of the current page +$paginator->setItemsPerPage(30); // number of items per page +$paginator->setItemCount(356); // total number of items (if known) ``` -Pages are numbered from 1. We can change it using `setBase()`: +Pages are numbered starting from 1. You can change this using `setBase()`: ```php -$paginator->setBase(0); // numbered from 0 +$paginator->setBase(0); // number from 0 ``` -The object will now provide all the basic information useful in creating a paginator. You can, for example, pass it to a template and use it there. +The object now provides all the essential information needed for creating pagination controls. For example, you can pass it to a template and use it there. ```php -$paginator->isFirst(); // is this the first page? -$paginator->isLast(); // is this the last page? +$paginator->isFirst(); // are we on the first page? +$paginator->isLast(); // are we on the last page? $paginator->getPage(); // current page number -$paginator->getFirstPage(); // the first page number -$paginator->getLastPage(); // the last page number +$paginator->getFirstPage(); // first page number +$paginator->getLastPage(); // last page number $paginator->getFirstItemOnPage(); // sequence number of the first item on the page $paginator->getLastItemOnPage(); // sequence number of the last item on the page -$paginator->getPageIndex(); // current page number if numbered from 0 -$paginator->getPageCount(); // the total number of pages -$paginator->getItemsPerPage(); // the number of records per page -$paginator->getItemCount(); // the total number of records (if available) +$paginator->getPageIndex(); // current page number (0-based) +$paginator->getPageCount(); // total number of pages +$paginator->getItemsPerPage(); // number of items per page +$paginator->getItemCount(); // total number of items (if known) ``` -The paginator will help in formulating the SQL query. The `getLength()` and `getOffset()` methods return the values you may use in the LIMIT and OFFSET clauses: +The paginator helps in formulating SQL queries. The `getLength()` and `getOffset()` methods return values used in `LIMIT` and `OFFSET` clauses: ```php $result = $database->query( @@ -52,7 +52,7 @@ $result = $database->query( ); ``` -If you need to paginate in reverse order, i.e. page no. 1 corresponds to the highest offset, you may use `getCountdownOffset()`: +If you need to paginate in reverse order (i.e., page 1 corresponds to the highest offset), use `getCountdownOffset()`: ```php $result = $database->query( @@ -62,4 +62,4 @@ $result = $database->query( ); ``` -An example of use in the application can be found in the cookbook [Paginating Database Results |best-practices:pagination]. +An example of usage in an application can be found in the cookbook [Paginating Database Results|best-practices:pagination]. diff --git a/utils/en/random.texy b/utils/en/random.texy index 0e9e22f174..cb17d6ba96 100644 --- a/utils/en/random.texy +++ b/utils/en/random.texy @@ -1,5 +1,5 @@ -Random Strings Generator -************************ +Generating Random Strings +************************* .[perex] [api:Nette\Utils\Random] is a static class for generating cryptographically secure pseudo-random strings. @@ -15,7 +15,7 @@ composer require nette/utils generate(int $length=10, string $charlist=`'0-9a-z'`): string .[method] ----------------------------------------------------------------------- -Generates a random string of given length from characters specified in second argument. Supports intervals, such as `0-9` or `A-Z`. +Generates a random string of a specified length using characters from the `$charlist`. Character ranges like `0-9` can also be used. ```php use Nette\Utils\Random; diff --git a/utils/en/reflection.texy b/utils/en/reflection.texy index e5512d9bb6..d487057a95 100644 --- a/utils/en/reflection.texy +++ b/utils/en/reflection.texy @@ -2,7 +2,7 @@ PHP Reflection ************** .[perex] -[api:Nette\Utils\Reflection] is a static class with useful functions for PHP reflection. Its purpose is to fix flaws in native classes and to unify behavior across different versions of PHP. +[api:Nette\Utils\Reflection] is a static class providing useful functions for PHP reflection. Its purpose is to address shortcomings in native reflection classes and unify behavior across different PHP versions. Installation: @@ -21,13 +21,13 @@ use Nette\Utils\Reflection; areCommentsAvailable(): bool .[method] -------------------------------------- -Finds out if reflection has access to PHPdoc comments. Comments may not be available due to the opcode cache, see for example the directive [opcache.save-comments |https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments]. +Checks if reflection has access to PHPdoc comments. Comments might be unavailable due to opcode caching, see for example the [opcache.save-comments|https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments] directive. expandClassName(string $name, ReflectionClass $context): string .[method] ------------------------------------------------------------------------- -Expands the `$name` of the class to full name in the context of the `$context`, ie in the context of its namespace and defined aliases. Thus, it returns how the PHP parser would understand `$name` if it were written in the body of the `$context`. +Expands the class name `$name` to its fully qualified name within the context of the class `$context`, considering its namespace and defined aliases. Essentially, it determines how the PHP parser would interpret `$name` if it were written inside the body of the `$context` class. ```php namespace Foo; @@ -47,9 +47,9 @@ Reflection::expandClassName('Baz', $context); // 'Foo\Baz' getMethodDeclaringMethod(ReflectionMethod $method): ReflectionMethod .[method] ------------------------------------------------------------------------------ -Returns a reflection of a method that contains a declaration of `$method`. Usually, each method is its own declaration, but the body of the method can also be in the trait and under a different name. +Returns the reflection of the method that contains the actual declaration of `$method`. Usually, a method is its own declaration, but the method body might originate from a trait and potentially under a different name. -Because PHP does not provide enough information to determine the actual declaration, Nette uses its own heuristics, which **should be** reliable. +Since PHP doesn't provide sufficient information to determine the true declaration reliably, Nette uses its own heuristics, which **should be** reliable. ```php trait DemoTrait @@ -76,9 +76,9 @@ Reflection::getMethodDeclaringMethod($method); // ReflectionMethod('DemoTrait::f getPropertyDeclaringClass(ReflectionProperty $prop): ReflectionClass .[method] ------------------------------------------------------------------------------ -Returns a reflection of a class or trait that contains a declaration of property `$prop`. Property can also be declared in the trait. +Returns the reflection of the class or trait that contains the declaration of the property `$prop`. The property might be declared within a trait. -Because PHP does not provide enough information to determine the actual declaration, Nette uses its own heuristics, which **are not** reliable. +Since PHP doesn't provide sufficient information to determine the true declaration reliably, Nette uses its own heuristics, which **are not** fully reliable. ```php trait DemoTrait @@ -100,7 +100,7 @@ Reflection::getPropertyDeclaringClass($prop); // ReflectionClass('DemoTrait') isBuiltinType(string $type): bool .[method deprecated] ------------------------------------------------------ -Determines if `$type` is PHP built-in type. Otherwise, it is the class name. +Checks if `$type` is a PHP built-in type. Otherwise, it's considered a class name. ```php Reflection::isBuiltinType('string'); // true @@ -114,7 +114,7 @@ Use [Nette\Utils\Validator::isBuiltinType() |validators#isBuiltinType]. toString($reflection): string .[method] --------------------------------------- -Converts a reflection to a human readable string. +Converts a reflection object to a human-readable string. ```php $func = new ReflectionFunction('func'); diff --git a/utils/en/smartobject.texy b/utils/en/smartobject.texy index f53fde130d..9ddb2935ae 100644 --- a/utils/en/smartobject.texy +++ b/utils/en/smartobject.texy @@ -1,8 +1,8 @@ -SmartObject and StaticClass -*************************** +SmartObject +*********** .[perex] -SmartObject adds support for *property* to PHP classes. StaticClass is used to denote static classes. +SmartObject enhanced PHP object behavior for many years. Since PHP 8.4, all of its features have become native parts of PHP itself, thus completing its historic mission as a pioneer of the modern object-oriented approach in PHP. Installation: @@ -11,19 +11,64 @@ Installation: composer require nette/utils ``` +SmartObject was introduced in 2007 as a revolutionary solution to the shortcomings of PHP's object model at the time. In an era when PHP faced numerous issues with object-oriented design, it brought significant improvements and simplified developers' workflows. It became a legendary component of the Nette Framework. SmartObject offered functionality that PHP only acquired many years later – from access control for object properties to sophisticated syntactic sugar. With the release of PHP 8.4, it fulfilled its historical mission, as all its features became a native part of the language. It was ahead of PHP's development by an impressive 17 years. -Properties, Getters and Setters -=============================== +Technically, SmartObject went through an interesting evolution. Initially, it was implemented as the `Nette\Object` class, from which other classes inherited the needed functionality. A significant change came with PHP 5.4, which introduced trait support. This enabled transformation into the `Nette\SmartObject` trait, bringing greater flexibility - developers could use the functionality even in classes that already inherited from another class. While the original `Nette\Object` class ceased to exist with PHP 7.2 (which prohibited naming classes with the word `Object`), the `Nette\SmartObject` trait lives on. -In modern object-oriented languages (e.g. C#, Python, Ruby, JavaScript), the term *property* refers to [special members of classes |https://en.wikipedia.org/wiki/Property_(programming)] that look like variables but are actually represented by methods. When the value of this "variable" is assigned or read, the corresponding method (called getter or setter) is called. This is a very handy thing to do, it gives us full control over access to variables. We can validate the input or generate results only when the property is read. +Let's explore the features that `Nette\Object` and later `Nette\SmartObject` offered. Each of these functions represented a significant step forward in PHP object-oriented programming at the time. -PHP properties are not supported, but trait `Nette\SmartObject` can imitate them. How to use it? -- Add an annotation to the class in the form `@property $xyz` +Consistent Error States +----------------------- +One of the most pressing issues of early PHP was inconsistent behavior when working with objects. `Nette\Object` brought order and predictability to this chaos. Let's look at how PHP originally behaved: + +```php +echo $obj->undeclared; // E_NOTICE, later E_WARNING +$obj->undeclared = 1; // passes silently without warning +$obj->unknownMethod(); // Fatal error (uncatchable by try/catch) +``` + +A fatal error would terminate the application without any possibility to react. Silently writing to non-existent members without warning could lead to serious errors that were difficult to detect. `Nette\Object` caught all these cases and threw a `MemberAccessException`, allowing programmers to react to and handle these errors: + +```php +echo $obj->undeclared; // throws Nette\MemberAccessException +$obj->undeclared = 1; // throws Nette\MemberAccessException +$obj->unknownMethod(); // throws Nette\MemberAccessException +``` + +Since PHP 7.0, the language no longer causes uncatchable fatal errors, and since PHP 8.2, access to undeclared members is considered an error. + + +"Did you mean?" Helper +---------------------- +`Nette\Object` came with a very convenient feature: intelligent suggestions for typos. When a developer made a mistake in a method or variable name, it not only reported the error but also offered help by suggesting the correct name. This iconic message, known as "did you mean?", saved programmers hours of hunting for typos: + +```php +class Foo extends Nette\Object +{ + public static function from($var) + { + } +} + +$foo = Foo::form($var); +// throws Nette\MemberAccessException +// "Call to undefined static method Foo::form(), did you mean from()?" +``` + +While current PHP doesn't have any form of "did you mean?", this suffix can be added to errors by [Tracy|tracy:]. And it can even [auto-fix |tracy:open-files-in-ide#Demos] such errors. + + +Properties with Controlled Access +--------------------------------- +A significant innovation that SmartObject brought to PHP was properties with controlled access. This concept, common in languages like C# or Python, allowed developers to elegantly control access to object data and ensure their consistency. Properties are a powerful tool of object-oriented programming. They function like variables but are actually represented by methods (getters and setters). This allows input validation or value generation at the time of reading. + +To use properties, you had to: +- Add the annotation `@property $xyz` to the class - Create a getter named `getXyz()` or `isXyz()`, a setter named `setXyz()` -- The getter and setter must be *public* or *protected* and are optional, so there can be a *read-only* or *write-only* property +- Ensure the getter and setter were *public* or *protected*. They were optional - thus could exist as *read-only* or *write-only* properties -We will use the property for the Circle class to ensure that only non-negative numbers are put into the `$radius` variable. Replace `public $radius` with property: +Let's look at a practical example using the `Circle` class, where we'll use properties to ensure that the radius is always non-negative. We'll replace `public $radius` with a property: ```php /** @@ -34,22 +79,22 @@ class Circle { use Nette\SmartObject; - private float $radius = 0.0; // not public + private float $radius = 0.0; // not public! - // getter for property $radius + // getter for $radius property protected function getRadius(): float { return $this->radius; } - // setter for property $radius + // setter for $radius property protected function setRadius(float $radius): void { - // sanitizing value before saving it + // sanitize the value before saving $this->radius = max(0.0, $radius); } - // getter for property $visible + // getter for $visible property protected function isVisible(): bool { return $this->radius > 0; @@ -62,79 +107,25 @@ echo $circle->radius; // calls getRadius() echo $circle->visible; // calls isVisible() ``` -Properties are primarily "syntactic sugar"((syntactic sugar)), which is intended to make the programmer's life sweeter by simplifying the code. If you don't want them, you don't have to use them. - - -Static Classes -============== - -Static classes, i.e. classes that are not intended to be instantiated, can be marked with the trait `Nette\StaticClass`: +Since PHP 8.4, the same functionality can be achieved using property hooks, which offer a much more elegant and concise syntax: ```php -class Strings +class Circle { - use Nette\StaticClass; -} -``` - -When you try to create an instance, the `Error` exception is thrown, indicating that the class is static. - - -A Look into the History -======================= - -SmartObject used to improve and fix class behavior in many ways, but the evolution of PHP has made most of the original features redundant. So the following is a look into the history of how things have evolved. - -From the beginning, the PHP object model suffered from a number of serious flaws and inefficiencies. This was the reason for the creation of the `Nette\Object` class (in 2007), which attempted to remedy them and improve the experience of using PHP. It was enough for other classes to inherit from it, and gain the benefits it brought. When PHP 5.4 came with trait support, the `Nette\Object` class was replaced by `Nette\SmartObject`. Thus, it was no longer necessary to inherit from a common ancestor. In addition, trait could be used in classes that already inherited from another class. The final end of `Nette\Object` came with the release of PHP 7.2, which forbade classes to be named `Object`. - -As PHP development went on, the object model and language capabilities were improved. The individual functions of the `SmartObject` class became redundant. Since the release of PHP 8.2, the only feature that remains that is not yet directly supported in PHP is the ability to use so-called [properties |#Properties, Getters and Setters]. - -What features did `Nette\Object` and `Nette\Object` once offer? Here is an overview. (The examples use the `Nette\Object` class, but most of the properties also apply to the `Nette\SmartObject` trait.) - - -Inconsistent Errors -------------------- -PHP had inconsistent behavior when accessing undeclared members. The state at the time of `Nette\Object` was as follows: - -```php -echo $obj->undeclared; // E_NOTICE, later E_WARNING -$obj->undeclared = 1; // passes silently without reporting -$obj->unknownMethod(); // Fatal error (not catchable by try/catch) -``` - -Fatal error terminated the application without any possibility to react. Silently writing to non-existent members without warning could lead to serious errors that were difficult to detect. `Nette\Object` All of these cases were caught and an exception `MemberAccessException` was thrown. - -```php -echo $obj->undeclared; // throw Nette\MemberAccessException -$obj->undeclared = 1; // throw Nette\MemberAccessException -$obj->unknownMethod(); // throw Nette\MemberAccessException -``` -Since PHP 7.0, PHP no longer causes not catchable fatal errors, and accessing undeclared members has been a bug since PHP 8.2. - - -Did you mean? -------------- -If an `Nette\MemberAccessException` error was thrown, perhaps due to a typo when accessing an object variable or calling a method, `Nette\Object` attempted to give a hint in the error message on how to fix the error, in the form of the iconic "did you mean?" addendum. + public float $radius = 0.0 { + set => max(0.0, $value); + } -```php -class Foo extends Nette\Object -{ - public static function from($var) - { + public bool $visible { + get => $this->radius > 0; } } - -$foo = Foo::form($var); -// throw Nette\MemberAccessException -// "Call to undefined static method Foo::form(), did you mean from()?" ``` -Today's PHP may not have any form of "did you mean?", but [Tracy |tracy:] adds this addendum to errors. And it can even [fix |tracy:open-files-in-ide#toc-demos] such errors itself. - -Extension methods +Extension Methods ----------------- -Inspired by extension methods from C#. They gave the possibility to add new methods to existing classes. For example, you could add the `addDateTime()` method to a form to add your own DateTimePicker. +`Nette\Object` brought another interesting concept to PHP inspired by modern programming languages - extension methods. This feature, borrowed from C#, allowed developers to elegantly extend existing classes with new methods without modifying them or inheriting from them. For instance, you could add an `addDateTime()` method to a form that adds a custom DateTimePicker: ```php Form::extensionMethod( @@ -146,11 +137,12 @@ $form = new Form; $form->addDateTime('date'); ``` -Extension methods proved to be impractical because their names was not autocompleted by editors, instead they reported that the method did not exist. Therefore, their support was discontinued. +Extension methods proved impractical because their names were not suggested by code editors, which instead reported that the method did not exist. Therefore, their support was discontinued. Today, it's more common to use composition or inheritance to extend class functionality. -Getting the Class Name ----------------------- +Getting Class Name +------------------ +SmartObject offered a simple method for getting the class name: ```php $class = $obj->getClass(); // using Nette\Object @@ -158,10 +150,9 @@ $class = $obj::class; // since PHP 8.0 ``` -Access to Reflection and Annotations ------------------------------------- - -`Nette\Object` offered access to reflection and annotation using the methods `getReflection()` and `getAnnotation()`: +Reflection and Annotation Access +-------------------------------- +`Nette\Object` provided access to reflection and annotations through the methods `getReflection()` and `getAnnotation()`. This approach significantly simplified working with class meta-information: ```php /** @@ -173,10 +164,10 @@ class Foo extends Nette\Object $obj = new Foo; $reflection = $obj->getReflection(); -$reflection->getAnnotation('author'); // returns 'John Doe +$reflection->getAnnotation('author'); // returns 'John Doe' ``` -As of PHP 8.0, it is possible to access meta-information in the form of attributes: +Since PHP 8.0, it's possible to access meta-information through attributes, which offer even more possibilities and better type checking: ```php #[Author('John Doe')] @@ -192,8 +183,7 @@ $reflection->getAttributes(Author::class)[0]; Method Getters -------------- - -`Nette\Object` offered an elegant way to deal with methods as if they were variables: +`Nette\Object` offered an elegant way to pass methods as if they were variables: ```php class Foo extends Nette\Object @@ -209,7 +199,7 @@ $method = $obj->adder; echo $method(2, 3); // 5 ``` -As of PHP 8.1, you can use the so-called "first-class callable syntax":https://www.php.net/manual/en/functions.first_class_callable_syntax: +Since PHP 8.1, you can use the "first-class callable syntax":https://www.php.net/manual/en/functions.first_class_callable_syntax.php, which takes this concept even further: ```php $obj = new Foo; @@ -220,8 +210,7 @@ echo $method(2, 3); // 5 Events ------ - -`Nette\Object` offered syntactic sugar to trigger the [event |nette:glossary#events]: +SmartObject offers simplified syntax for working with [events |nette:glossary#Events]. Events allow objects to inform other parts of the application about changes in their state: ```php class Circle extends Nette\Object @@ -231,12 +220,12 @@ class Circle extends Nette\Object public function setRadius(float $radius): void { $this->onChange($this, $radius); - $this->radius = $radius + $this->radius = $radius; } } ``` -The code `$this->onChange($this, $radius)` is equivalent to the following: +The code `$this->onChange($this, $radius)` is equivalent to the following loop: ```php foreach ($this->onChange as $callback) { @@ -244,7 +233,7 @@ foreach ($this->onChange as $callback) { } ``` -For the sake of clarity we recommend to avoid the magic method `$this->onChange()`. A practical substitute is the [Nette\Utils\Arrays::invoke |arrays#invoke] function: +For clarity, we recommend avoiding the magic `$this->onChange()` method. A practical replacement is the [Nette\Utils\Arrays::invoke |arrays#invoke] function: ```php Nette\Utils\Arrays::invoke($this->onChange, $this, $radius); diff --git a/utils/en/staticclass.texy b/utils/en/staticclass.texy new file mode 100644 index 0000000000..d68b70954c --- /dev/null +++ b/utils/en/staticclass.texy @@ -0,0 +1,21 @@ +Static Classes +************** + +.[perex] +StaticClass is used to denote static classes. + + +Installation: + +```shell +composer require nette/utils +``` + +Static classes, i.e., classes that are not intended to be instantiated, can be marked with the trait [api:Nette\StaticClass]: + +```php +class Strings +{ + use Nette\StaticClass; +} +``` diff --git a/utils/en/strings.texy b/utils/en/strings.texy index cea37f2e31..62f2a52c03 100644 --- a/utils/en/strings.texy +++ b/utils/en/strings.texy @@ -2,7 +2,7 @@ String Functions **************** .[perex] -[api:Nette\Utils\Strings] is a static class, which contains many useful functions for working with UTF-8 encoded strings. +[api:Nette\Utils\Strings] is a static class containing useful functions for working with UTF-8 encoded strings. Installation: @@ -27,27 +27,27 @@ These functions require the PHP extension `mbstring`. lower(string $s): string .[method] ---------------------------------- -Converts all characters of UTF-8 string to lower case. +Converts a UTF-8 string to lowercase. ```php -Strings::lower('Hello world'); // 'hello world' +Strings::lower('Hello World'); // 'hello world' ``` upper(string $s): string .[method] ---------------------------------- -Converts all characters of a UTF-8 string to upper case. +Converts a UTF-8 string to uppercase. ```php -Strings::upper('Hello world'); // 'HELLO WORLD' +Strings::upper('Hello World'); // 'HELLO WORLD' ``` firstUpper(string $s): string .[method] --------------------------------------- -Converts the first character of a UTF-8 string to upper case and leaves the other characters unchanged. +Converts the first character of a UTF-8 string to uppercase and leaves the other characters unchanged. ```php Strings::firstUpper('hello world'); // 'Hello world' @@ -57,20 +57,20 @@ Strings::firstUpper('hello world'); // 'Hello world' firstLower(string $s): string .[method] --------------------------------------- -Converts the first character of a UTF-8 string to lower case and leaves the other characters unchanged. +Converts the first character of a UTF-8 string to lowercase and leaves the other characters unchanged. ```php -Strings::firstLower('Hello world'); // 'hello world' +Strings::firstLower('Hello World'); // 'hello world' ``` capitalize(string $s): string .[method] --------------------------------------- -Converts the first character of every word of a UTF-8 string to upper case and the others to lower case. +Converts the first character of every word in a UTF-8 string to uppercase and the others to lowercase. ```php -Strings::capitalize('Hello world'); // 'Hello World' +Strings::capitalize('hello world'); // 'Hello World' ``` @@ -81,13 +81,13 @@ Editing a String normalize(string $s): string .[method] -------------------------------------- -Removes control characters, normalizes line breaks to `\n`, removes leading and trailing blank lines, trims end spaces on lines, normalizes UTF-8 to the normal form of NFC. +Removes control characters, normalizes line endings to `\n`, trims leading and trailing blank lines, trims trailing spaces on lines, and normalizes UTF-8 to the NFC normal form. unixNewLines(string $s): string .[method] ----------------------------------------- -Converts line breaks to `\n` used on Unix systems. The line breaks are: `\n`, `\r`, `\r\n`, U+2028 line separator, U+2029 paragraph separator. +Converts line endings to `\n` used on Unix systems. Line endings are: `\n`, `\r`, `\r\n`, U+2028 line separator, U+2029 paragraph separator. ```php $unixLikeLines = Strings::unixNewLines($string); @@ -97,42 +97,42 @@ $unixLikeLines = Strings::unixNewLines($string); platformNewLines(string $s): string .[method] --------------------------------------------- -Converts line breaks to characters specific to the current platform, i.e. `\r\n` on Windows and `\n` elsewhere. The line breaks are `\n`, `\r`, `\r\n`, U+2028 line separator, U+2029 paragraph separator. +Converts line endings to characters specific to the current platform, i.e., `\r\n` on Windows and `\n` elsewhere. Line endings are: `\n`, `\r`, `\r\n`, U+2028 line separator, U+2029 paragraph separator. ```php $platformLines = Strings::platformNewLines($string); ``` -webalize(string $s, string $charlist=null, bool $lower=true): string .[method] ------------------------------------------------------------------------------- +webalize(string $s, ?string $charlist=null, bool $lower=true): string .[method] +------------------------------------------------------------------------------- -Modifies the UTF-8 string to the form used in the URL, ie removes diacritics and replaces all characters except letters of the English alphabet and numbers with a hyphens. +Modifies a UTF-8 string to the form used in URLs, i.e., removes diacritics and replaces all characters except letters of the English alphabet and numbers with hyphens. ```php Strings::webalize('žluťoučký kůň'); // 'zlutoucky-kun' ``` -Other characters may be preserved as well, but they must be passed as second argument. +If other characters should be preserved, they can be specified in the second parameter. ```php Strings::webalize('10. image_id', '._'); // '10.-image_id' ``` -The third argument may suppress converting the string to lower case. +The third parameter can suppress conversion to lowercase. ```php -Strings::webalize('Hello world', null, false); // 'Hello-world' +Strings::webalize('Dobrý den', null, false); // 'Dobry-den' ``` .[caution] -Requires PHP extension `intl`. +Requires the PHP extension `intl`. -trim(string $s, string $charlist=null): string .[method] --------------------------------------------------------- +trim(string $s, ?string $charlist=null): string .[method] +--------------------------------------------------------- -Removes all left and right side spaces (or the characters passed as second argument) from a UTF-8 encoded string. +Strips leading and trailing whitespace (or other characters specified by the second parameter) from a UTF-8 string. ```php Strings::trim(' Hello '); // 'Hello' @@ -142,7 +142,7 @@ Strings::trim(' Hello '); // 'Hello' truncate(string $s, int $maxLen, string $append=`'…'`): string .[method] ------------------------------------------------------------------------ -Truncates a UTF-8 string to given maximal length, while trying not to split whole words. Only if the string is truncated, an ellipsis (or something else set with third argument) is appended to the string. +Truncates a UTF-8 string to the specified maximum length, trying to preserve whole words. If the string is truncated, an ellipsis (changeable via the third parameter) is added to the end. ```php $text = 'Hello, how are you today?'; @@ -156,7 +156,7 @@ Strings::truncate($text, 20, '~'); // 'Hello, how are you~' indent(string $s, int $level=1, string $indentationChar=`"\t"`): string .[method] --------------------------------------------------------------------------------- -Indents a multiline text from the left. Second argument sets how many indentation chars should be used, while the indent itself is the third argument (*tab* by default). +Indents a multiline text from the left. The second parameter specifies the number of indentation characters, and the third parameter specifies the character(s) to use for indentation (default is tab). ```php Strings::indent('Nette'); // "\tNette" @@ -167,7 +167,7 @@ Strings::indent('Nette', 2, '+'); // '++Nette' padLeft(string $s, int $length, string $pad=`' '`): string .[method] -------------------------------------------------------------------- -Pads a UTF-8 string to given length by prepending the `$pad` string to the beginning. +Pads a UTF-8 string to a specified length by prepending the `$pad` string from the left. ```php Strings::padLeft('Nette', 6); // ' Nette' @@ -178,7 +178,7 @@ Strings::padLeft('Nette', 8, '+*'); // '+*+Nette' padRight(string $s, int $length, string $pad=`' '`): string .[method] --------------------------------------------------------------------- -Pads UTF-8 string to given length by appending the `$pad` string to the end. +Pads a UTF-8 string to a specified length by appending the `$pad` string from the right. ```php Strings::padRight('Nette', 6); // 'Nette ' @@ -186,10 +186,10 @@ Strings::padRight('Nette', 8, '+*'); // 'Nette+*+' ``` -substring(string $s, int $start, int $length=null): string .[method] --------------------------------------------------------------------- +substring(string $s, int $start, ?int $length=null): string .[method] +--------------------------------------------------------------------- -Returns a part of UTF-8 string specified by starting position `$start` and length `$length`. If `$start` is negative, the returned string will start at the `$start`'th character from the end of string. +Returns a portion of a UTF-8 string `$s` specified by the starting position `$start` and length `$length`. If `$start` is negative, the returned string will start at the `$start`-th character from the end. ```php Strings::substring('Nette Framework', 0, 5); // 'Nette' @@ -201,7 +201,7 @@ Strings::substring('Nette Framework', -4); // 'work' reverse(string $s): string .[method] ------------------------------------ -Reverses UTF-8 string. +Reverses a UTF-8 string. ```php Strings::reverse('Nette'); // 'etteN' @@ -211,35 +211,35 @@ Strings::reverse('Nette'); // 'etteN' length(string $s): int .[method] -------------------------------- -Returns number of characters (not bytes) in UTF-8 string. +Returns the number of characters (not bytes) in a UTF-8 string. -That is the number of Unicode code points which may differ from the number of graphemes. +This is the number of Unicode code points, which may differ from the number of graphemes. ```php -Strings::length('Nette'); // 5 -Strings::length('red'); // 3 +Strings::length('Nette'); // 5 +Strings::length('červená'); // 7 ``` startsWith(string $haystack, string $needle): bool .[method deprecated] ----------------------------------------------------------------------- -Checks if `$haystack` string begins with `$needle`. +Checks if the string `$haystack` starts with the string `$needle`. ```php -$haystack = 'Begins'; -$needle = 'Be'; +$haystack = 'Starts'; +$needle = 'St'; Strings::startsWith($haystack, $needle); // true ``` .[note] -Use native `str_starts_with()`:https://www.php.net/manual/en/function.str-starts-with.php. +Use the native `str_starts_with()`:https://www.php.net/manual/en/function.str-starts-with.php function. endsWith(string $haystack, string $needle): bool .[method deprecated] --------------------------------------------------------------------- -Checks if `$haystack` string end with `$needle`. +Checks if the string `$haystack` ends with the string `$needle`. ```php $haystack = 'Ends'; @@ -248,40 +248,40 @@ Strings::endsWith($haystack, $needle); // true ``` .[note] -Use native `str_ends_with()`:https://www.php.net/manual/en/function.str-ends-with.php. +Use the native `str_ends_with()`:https://www.php.net/manual/en/function.str-ends-with.php function. contains(string $haystack, string $needle): bool .[method deprecated] --------------------------------------------------------------------- -Checks if `$haystack` string contains `$needle`. +Checks if the string `$haystack` contains the string `$needle`. ```php -$haystack = 'Contains'; -$needle = 'tai'; +$haystack = 'Auditorium'; +$needle = 'dit'; Strings::contains($haystack, $needle); // true ``` .[note] -Use native `str_contains()`:https://www.php.net/manual/en/function.str-contains.php. +Use the native `str_contains()`:https://www.php.net/manual/en/function.str-contains.php function. -compare(string $left, string $right, int $length=null): bool .[method] ----------------------------------------------------------------------- +compare(string $left, string $right, ?int $length=null): bool .[method] +----------------------------------------------------------------------- -Compares two UTF-8 strings or their parts, without taking character case into account. If `$length` is null, whole strings are compared, if it is negative, the corresponding number of characters from the end of the strings is compared, otherwise the appropriate number of characters from the beginning is compared. +Compares two UTF-8 strings or their parts, case-insensitively. If `$length` is null, compares the entire strings. If negative, compares the corresponding number of characters from the end of the strings. Otherwise, compares the corresponding number of characters from the beginning. ```php Strings::compare('Nette', 'nette'); // true -Strings::compare('Nette', 'next', 2); // true - two first characters match -Strings::compare('Nette', 'Latte', -2); // true - two last characters match +Strings::compare('Nette', 'next', 2); // true - first 2 characters match +Strings::compare('Nette', 'Latte', -2); // true - last 2 characters match ``` findPrefix(...$strings): string .[method] ----------------------------------------- -Finds the common prefix of strings or returns empty string if the prefix was not found. +Finds the common prefix of the strings. Returns an empty string if no common prefix is found. ```php Strings::findPrefix('prefix-a', 'prefix-bb', 'prefix-c'); // 'prefix-' @@ -293,7 +293,7 @@ Strings::findPrefix('Nette', 'is', 'great'); // '' before(string $haystack, string $needle, int $nth=1): ?string .[method] ----------------------------------------------------------------------- -Returns part of `$haystack` before `$nth` occurence of `$needle` or returns `null` if the needle was not found. Negative value means searching from the end. +Returns the portion of string `$haystack` before the `$nth` occurrence of string `$needle`. Returns `null` if `$needle` is not found. If `$nth` is negative, it searches from the end of the string. ```php Strings::before('Nette_is_great', '_', 1); // 'Nette' @@ -306,7 +306,7 @@ Strings::before('Nette_is_great', '_', 3); // null after(string $haystack, string $needle, int $nth=1): ?string .[method] ---------------------------------------------------------------------- -Returns part of `$haystack` after `$nth` occurence of `$needle` or returns `null` if the `$needle` was not found. Negative value of `$nth` means searching from the end. +Returns the portion of string `$haystack` after the `$nth` occurrence of string `$needle`. Returns `null` if `$needle` is not found. If `$nth` is negative, it searches from the end of the string. ```php Strings::after('Nette_is_great', '_', 2); // 'great' @@ -319,7 +319,7 @@ Strings::after('Nette_is_great', '_', 3); // null indexOf(string $haystack, string $needle, int $nth=1): ?int .[method] --------------------------------------------------------------------- -Returns position in characters of `$nth` occurence of `$needle` in `$haystack` or `null` if the `$needle` was not found. Negative value of `$nth` means searching from the end. +Returns the character position of the `$nth` occurrence of string `$needle` in string `$haystack`. Returns `null` if `$needle` is not found. If `$nth` is negative, it searches from the end of the string. ```php Strings::indexOf('abc abc abc', 'abc', 2); // 4 @@ -335,17 +335,17 @@ Encoding fixEncoding(string $s): string .[method] ---------------------------------------- -Removes all invalid UTF-8 characters from a string. +Removes invalid UTF-8 characters from a string. ```php -$correctStrings = Strings::fixEncoding($string); +$correctString = Strings::fixEncoding($invalidString); ``` checkEncoding(string $s): bool .[method deprecated] --------------------------------------------------- -Checks if the string is valid in UTF-8 encoding. +Checks if a string is a valid UTF-8 string. ```php $isUtf8 = Strings::checkEncoding($string); @@ -358,53 +358,53 @@ Use [Nette\Utils\Validator::isUnicode() |validators#isUnicode]. toAscii(string $s): string .[method] ------------------------------------ -Converts UTF-8 string to ASCII, ie removes diacritics etc. +Converts a UTF-8 string to ASCII, i.e., removes diacritics, etc. ```php Strings::toAscii('žluťoučký kůň'); // 'zlutoucky kun' ``` .[caution] -Requires PHP extension `intl`. +Requires the PHP extension `intl`. chr(int $code): string .[method] -------------------------------- -Returns a specific character in UTF-8 from code point (number in range 0x0000..D7FF or 0xE000..10FFFF). +Returns a specific character in UTF-8 from a code point (a number in the range 0x0000..D7FF or 0xE000..10FFFF). ```php -Strings::chr(0xA9); // '©' +Strings::chr(0xA9); // '©' in UTF-8 encoding ``` ord(string $char): int .[method] -------------------------------- -Returns a code point of specific character in UTF-8 (number in range 0x0000..D7FF or 0xE000..10FFFF). +Returns the code point of a specific character in UTF-8 (a number in the range 0x0000..D7FF or 0xE000..10FFFF). ```php -Strings::ord('©'); // 0xA9 +Strings::ord('©'); // 169 (0xA9) ``` Regular Expressions =================== -The Strings class provides functions for working with regular expressions. Unlike native PHP functions, they have a more understandable API, better Unicode support, and most importantly, error detection. Any compilation or expression processing error will throw a `Nette\RegexpException` exception. +The `Strings` class offers functions for working with regular expressions. Unlike native PHP functions, they feature a more understandable API, better Unicode support, and, crucially, error detection. Any error during compilation or expression processing throws a `Nette\RegexpException`. split(string $subject, string $pattern, bool $captureOffset=false, bool $skipEmpty=false, int $limit=-1, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------- -Divides the string into arrays according to the regular expression. Expressions in parentheses will be captured and returned as well. +Splits a string into an array using a regular expression. Expressions in parentheses will also be captured and returned. ```php Strings::split('hello, world', '~,\s*~'); // ['hello', 'world'] Strings::split('hello, world', '~(,)\s*~'); -// ['hello', ',', 'world']`` +// ['hello', ',', 'world'] ``` If `$skipEmpty` is `true`, only non-empty items will be returned: @@ -417,30 +417,30 @@ Strings::split('hello, world, ', '~,\s*~', skipEmpty: true); // ['hello', 'world'] ``` -If `$limit` is specified, only substrings up to the limit will be returned and the rest of the string will be placed in the last element. A limit of -1 or 0 means no limit. +If `$limit` is specified, only substrings up to the limit are returned, and the rest of the string is placed in the last element. A limit of -1 or 0 means no limit. ```php Strings::split('hello, world, third', '~,\s*~', limit: 2); // ['hello', 'world, third'] ``` -If `$utf8` is `true`, the evaluation switches to Unicode mode. This is similar to specifying the `u` modifier. +If `$utf8` is `true`, evaluation switches to Unicode mode, similar to using the `u` modifier. -If `$captureOffset` is `true`, for each occurring match, its position in the string will also be returned (in bytes; in characters if `$utf8` is set). This changes the return value to an array where each element is a pair consisting of the matched string and its position. +If `$captureOffset` is `true`, the position of each match in the string will also be returned (in bytes; in characters if `$utf8` is set). This changes the return value to an array where each element is a pair consisting of the matched string and its position. ```php Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true); // [['žlutý', 0], ['kůň', 9]] Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true, utf8: true); -// [['žlutý', 0], ['kůň', 7]] +// [['žlutý', 0], ['kůň', 7]] // positions are in characters ``` match(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $utf8=false): ?array .[method] -------------------------------------------------------------------------------------------------------------------------------------------------- -Searches the string for the part matching the regular expression and returns an array with the found expression and individual subexpressions, or `null`. +Searches a string for a part matching a regular expression and returns an array containing the found expression and individual subexpressions, or `null` if no match is found. ```php Strings::match('hello!', '~\w+(!+)~'); @@ -450,43 +450,43 @@ Strings::match('hello!', '~X~'); // null ``` -If `$unmatchedAsNull` is `true`, unmatched subpatterns are returned as null; otherwise they are returned as an empty string or not returned: +If `$unmatchedAsNull` is `true`, unmatched subpatterns are returned as `null`; otherwise, they are returned as an empty string or omitted entirely: ```php Strings::match('hello', '~\w+(!+)?~'); -// ['hello'] +// ['hello'] (the optional group !+ did not match) Strings::match('hello', '~\w+(!+)?~', unmatchedAsNull: true); // ['hello', null] ``` -If `$utf8` is `true`, the evaluation switches to Unicode mode. This is similar to specifying the `u` modifier: +If `$utf8` is `true`, evaluation switches to Unicode mode, similar to using the `u` modifier: ```php -Strings::match('žlutý kůň', '~\w+~'); -// ['lut'] +Strings::match('žlutý kůň', '~\w+~'); // Without UTF-8 +// ['lut'] (matches only ASCII word characters) -Strings::match('žlutý kůň', '~\w+~', utf8: true); -// ['žlutý'] +Strings::match('žlutý kůň', '~\w+~', utf8: true); // With UTF-8 +// ['žlutý'] (matches Unicode word characters) ``` -The `$offset` parameter can be used to specify the position from which to start the search (in bytes; in characters if `$utf8` is set). +The `$offset` parameter can specify the starting position for the search (in bytes; in characters if `$utf8` is set). -If `$captureOffset` is `true`, for each occurring match, its position in the string will also be returned (in bytes; in characters if `$utf8` is set). This changes the return value to an array where each element is a pair consisting of the matched string and its offset: +If `$captureOffset` is `true`, the position of each match in the string will also be returned (in bytes; in characters if `$utf8` is set). This changes the return value to an array where each element is a pair consisting of the matched string and its offset: ```php -Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true); -// [['lut', 2]] +Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true); // Without UTF-8 +// [['lut', 2]] (only ASCII match, offset in bytes) -Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true, utf8: true); -// [['žlutý!', 0], ['!', 5]] +Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true, utf8: true); // With UTF-8 +// [['žlutý!', 0], ['!', 5]] (Unicode match, offsets in characters) ``` -matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false, bool $lazy=false): array|Generator .[method] +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Searches the string for all occurrences matching the regular expression and returns an array of arrays containing the found expression and each subexpression. +Searches a string for all occurrences matching a regular expression and returns an array of arrays containing the found expression and individual subexpressions. ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~'); @@ -496,7 +496,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~'); ] */ ``` -If `$patternOrder` is `true`, the structure of the results changes so that the first item is an array of full pattern matches, the second is an array of strings corresponding to the first subpattern in parentheses, and so on: +If `$patternOrder` is `true`, the structure of the results changes: the first element is an array of full pattern matches, the second is an array of strings matching the first parenthesized subpattern, and so on: ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~', patternOrder: true); @@ -506,7 +506,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~', patternOrder: true); ] */ ``` -If `$unmatchedAsNull` is `true`, unmatched subpatterns are returned as null; otherwise they are returned as an empty string or not returned: +If `$unmatchedAsNull` is `true`, unmatched subpatterns are returned as `null`; otherwise, they are returned as an empty string or omitted: ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~', unmatchedAsNull: true); @@ -516,7 +516,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~', unmatchedAsNull: true); ] */ ``` -If `$utf8` is `true`, the evaluation switches to Unicode mode. This is similar to specifying the `u` modifier: +If `$utf8` is `true`, evaluation switches to Unicode mode, similar to using the `u` modifier: ```php Strings::matchAll('žlutý kůň', '~\w+~'); @@ -532,9 +532,9 @@ Strings::matchAll('žlutý kůň', '~\w+~', utf8: true); ] */ ``` -The `$offset` parameter can be used to specify the position from which to start the search (in bytes; in characters if `$utf8` is set). +The `$offset` parameter can specify the starting position for the search (in bytes; in characters if `$utf8` is set). -If `$captureOffset` is `true`, for each occurring match, its position in the string will also be returned (in bytes; in characters if `$utf8` is set). This changes the return value to an array where each element is a pair consisting of the matched string and its position: +If `$captureOffset` is `true`, the position of each match in the string will also be returned (in bytes; in characters if `$utf8` is set). This changes the return value structure, where each match element is a pair `[matched_string, position]`: ```php Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true); @@ -550,11 +550,21 @@ Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true, utf8: true); ] */ ``` +If `$lazy` is `true`, the function returns a `Generator` instead of an array. This offers significant performance benefits when working with large strings, as matches are found incrementally rather than processing the entire string at once. This allows efficient handling of extremely large inputs. Additionally, you can interrupt processing at any time if you find the desired match, saving computation time. + +```php +$matches = Strings::matchAll($largeText, '~\w+~', lazy: true); +foreach ($matches as $match) { + echo "Found: $match[0]\n"; + // Processing can be interrupted at any time, e.g., with break; +} +``` + replace(string $subject, string|array $pattern, string|callable $replacement='', int $limit=-1, bool $captureOffset=false, bool $unmatchedAsNull=false, bool $utf8=false): string .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Replaces all occurrences matching the regular expression. The `$replacement` is either a replacement string mask or a callback. +Replaces all occurrences matching a regular expression. `$replacement` is either a replacement string mask or a callback function. ```php Strings::replace('hello, world!', '~\w+~', '--'); @@ -564,7 +574,7 @@ Strings::replace('hello, world!', '~\w+~', fn($m) => strrev($m[0])); // 'olleh, dlrow!' ``` -The function also allows multiple replacements by passing an array of the form `pattern => replacement` in the second parameter: +The function also allows multiple replacements by passing an array in the format `pattern => replacement` as the second parameter: ```php Strings::replace('hello, world!', [ @@ -574,9 +584,9 @@ Strings::replace('hello, world!', [ // '-- --!' ``` -The `$limit` parameter limits the number of substitutions. Limit -1 means no limit. +The `$limit` parameter restricts the number of replacements performed. A limit of -1 means no limit. -If `$utf8` is `true`, the evaluation switches to Unicode mode. This is similar to specifying the `u` modifier. +If `$utf8` is `true`, evaluation switches to Unicode mode, similar to using the `u` modifier. ```php Strings::replace('žlutý kůň', '~\w+~', '--'); @@ -586,7 +596,7 @@ Strings::replace('žlutý kůň', '~\w+~', '--', utf8: true); // '-- --' ``` -If `$captureOffset` is `true`, for each occurring match, its position in the string (in bytes; in characters if `$utf8` is set) is also passed to the callback. This changes the form of the passed array, where each element is a pair consisting of the matched string and its position. +If `$captureOffset` is `true`, the position of each match in the string (in bytes; in characters if `$utf8` is set) is also passed to the callback. This changes the structure of the passed array, where each element is a pair `[matched_string, position]`. ```php Strings::replace( @@ -595,7 +605,7 @@ Strings::replace( function (array $m) { dump($m); return ''; }, captureOffset: true, ); -// dumps [['lut', 2]] a [['k', 8]] +// dumps [['lut', 2]] and [['k', 8]] Strings::replace( 'žlutý kůň', @@ -604,10 +614,10 @@ Strings::replace( captureOffset: true, utf8: true, ); -// dumps [['žlutý', 0]] a [['kůň', 6]] +// dumps [['žlutý', 0]] and [['kůň', 6]] ``` -If `$unmatchedAsNull` is `true`, unmatched subpatterns are passed to the callback as null; otherwise they are passed as an empty string or not passed: +If `$unmatchedAsNull` is `true`, unmatched subpatterns are passed to the callback as `null`; otherwise, they are passed as an empty string or omitted: ```php Strings::replace( diff --git a/utils/en/type.texy b/utils/en/type.texy index 5bbfd697a6..f462bbb31c 100644 --- a/utils/en/type.texy +++ b/utils/en/type.texy @@ -2,8 +2,13 @@ PHP Type ******** .[perex] -[api:Nette\Utils\Type] is a PHP data type class. +[api:Nette\Utils\Type] represents a PHP data type. It is used for analyzing, comparing, and manipulating types, whether obtained from a string or reflection. +PHP currently has a very rich type system: from scalar types (`int`, `string`), through objects and interfaces, to complex types (union `A|B`, intersection `A&B`, or disjunctive normal forms `(A&B)|D`). Additionally, there are special types like `void`, `never`, `mixed`, or relative types `self` and `static`. + +Working with these types natively, especially via `ReflectionType`, is often cumbersome because you must recursively distinguish between `ReflectionNamedType`, `ReflectionUnionType`, and other objects. The `Nette\Utils\Type` class encapsulates all of this and provides a **unified and intuitive API** for working with any type supported by PHP. + +For example, it allows you to easily check if one type [accepts|#allows] another (compatibility), [extend types|#with], or convert reflections into a readable notation. Installation: @@ -21,7 +26,7 @@ use Nette\Utils\Type; fromReflection($reflection): ?Type .[method] -------------------------------------------- -The static method creates a Type object based on reflection. The parameter can be a `ReflectionMethod` or `ReflectionFunction` object (returns the type of the return value) or a `ReflectionParameter` or `ReflectionProperty` object. Resolves `self`, `static` and `parent` to the actual class name. If the subject has no type, it returns `null`. +This static method creates a `Type` object based on reflection. The parameter can be a `ReflectionMethod` or `ReflectionFunction` object (returns the return value type), or a `ReflectionParameter` or `ReflectionProperty` object. It resolves `self`, `static`, and `parent` to the actual class name. If the subject has no type, it returns `null`. ```php class DemoClass @@ -37,7 +42,7 @@ echo Type::fromReflection($prop); // 'DemoClass' fromString(string $type): Type .[method] ---------------------------------------- -The static method creates the Type object according to the text notation. +This static method creates a `Type` object from its string representation. ```php $type = Type::fromString('Foo|Bar'); @@ -45,13 +50,32 @@ echo $type; // 'Foo|Bar' ``` +fromValue(mixed $value): Type .[method]{data-version:4.0.10} +------------------------------------------------------------ + +Static method that creates a Type object based on the type of the passed value. + +```php +$type = Type::fromValue('hello'); // 'string' +$type = Type::fromValue(123); // 'int' +$type = Type::fromValue(new stdClass); // 'stdClass' +``` + +For resources, it returns `mixed`, as PHP does not support the `resource` type. For anonymous classes, it returns the name of the nearest ancestor or `object`. + +```php +$obj = new class extends Foo { }; +$type = Type::fromValue($obj); // 'Foo' +``` + + getNames(): (string|array)[] .[method] -------------------------------------- -Returns the array of subtypes that make up the compound type as strings. +Returns an array of strings representing the subtypes that make up a compound type. For intersection types, it returns an array of arrays. ```php -$type = Type::fromString('string|null'); // nebo '?string' +$type = Type::fromString('string|null'); // or '?string' $type->getNames(); // ['string', 'null'] $type = Type::fromString('(Foo&Bar)|string'); @@ -62,7 +86,7 @@ $type->getNames(); // [['Foo', 'Bar'], 'string'] getTypes(): Type[] .[method] ---------------------------- -Returns the array of subtypes that make up the compound type as `Type` objects: +Returns an array of `Type` objects representing the subtypes that make up a compound type: ```php $type = Type::fromString('string|null'); // or '?string' @@ -79,7 +103,7 @@ $type->getTypes(); // [Type::fromString('Foo'), Type::fromString('Bar')] getSingleName(): ?string .[method] ---------------------------------- -Returns the type name for simple types, otherwise null. +For simple types (including simple nullable types like `?string`), returns the type name. Otherwise, returns null. ```php $type = Type::fromString('string|null'); @@ -92,30 +116,30 @@ echo $type->getSingleName(); // 'Foo' $type = Type::fromString('Foo|Bar'); echo $type; // 'Foo|Bar' -echo $type->getSingleName(); // null +echo $type->getSingleName(); // null (it's a union type) ``` isSimple(): bool .[method] -------------------------- -Returns whether it is a simple type. Simple nullable types are also considered to be simple types: +Returns whether it is a simple type. Simple types also include simple nullable types (e.g., `?string`, `?Foo`): ```php $type = Type::fromString('string'); $type->isSimple(); // true $type->isUnion(); // false -$type = Type::fromString('?Foo'); // nebo 'Foo|null' +$type = Type::fromString('?Foo'); // or 'Foo|null' $type->isSimple(); // true -$type->isUnion(); // true +$type->isUnion(); // true (because it contains null) ``` isUnion(): bool .[method] ------------------------- -Returns whether it is a union type. +Returns whether it is a union type (contains `|`). ```php $type = Type::fromString('Foo&Bar'); @@ -126,11 +150,11 @@ $type->isUnion(); // true isIntersection(): bool .[method] -------------------------------- -Returns whether it is an intersection type. +Returns whether it is an intersection type (contains `&`). ```php -$type = Type::fromString('string&int'); +$type = Type::fromString('Foo&Bar'); $type->isIntersection(); // true ``` @@ -138,7 +162,7 @@ $type->isIntersection(); // true isBuiltin(): bool .[method] --------------------------- -Returns whether the type is both a simple and a PHP built-in type. +Returns whether the type is simple and also a PHP built-in type (like `string`, `int`, `array`, `callable`, etc.). ```php $type = Type::fromString('string'); @@ -155,7 +179,7 @@ $type->isBuiltin(); // false isClass(): bool .[method] ------------------------- -Returns whether the type is both a simple and a class name. +Returns whether the type is simple and also a class name (not a built-in type like `string` or `int`). ```php $type = Type::fromString('string'); @@ -172,7 +196,7 @@ $type->isClass(); // false isClassKeyword(): bool .[method] -------------------------------- -Determine whether the type is one of the internal types `self`, `parent`, `static`. +Returns whether the type is one of the internal keywords `self`, `parent`, or `static`. ```php $type = Type::fromString('self'); @@ -183,10 +207,10 @@ $type->isClassKeyword(); // false ``` -allows(string $type): bool .[method] ------------------------------------- +allows(string|Type $type): bool .[method] +----------------------------------------- -The `allows()` method verifies type compatibility. For example, it allows to check if a value of a certain type could be passed as a parameter. +The `allows()` method checks type compatibility. For example, it can determine if a value of a certain type could be passed as a parameter to a function expecting this type. ```php $type = Type::fromString('string|null'); @@ -197,3 +221,24 @@ $type->allows('Foo'); // false $type = Type::fromString('mixed'); $type->allows('null'); // true ``` + + +with(string|Type $type): Type .[method]{data-version:4.0.10} +------------------------------------------------------------ + +Returns a Type object that accepts both the original type and the one being added. It creates a so-called union type. + +The method is clever and does not duplicate types unnecessarily. If you add a type that is already present, or is a superset of the current type (e.g., adding `mixed` to `string`), the result is simplified. + +```php +$type = Type::fromString('string'); + +// Extending to nullable string +echo $type->with('null'); // '?string' + +// Creating a union type +echo $type->with('int'); // 'string|int' + +// Adding a type that supersedes everything +echo $type->with('mixed'); // 'mixed' +``` diff --git a/utils/en/upgrading.texy b/utils/en/upgrading.texy new file mode 100644 index 0000000000..1f7302e680 --- /dev/null +++ b/utils/en/upgrading.texy @@ -0,0 +1,34 @@ +Upgrading +********* + + +Migrating from 3.x to 4.0 +========================= + +The minimum required PHP version is 8.0. + +The `Nette\Utils\Reflection` class provided methods `getParameterType()`, `getPropertyType()`, and `getReturnType()` for working with types. These methods were created when PHP didn't have union, intersection, or the newest disjunctive normal form types, with which they no longer work correctly. They have been replaced by the [Type class |utils:type]. As of version 4.0, these methods have been removed. + +The method `Nette\Utils\Reflection::getParameterDefaultValue()` is deprecated because the native `ReflectionParameter::getDefaultValue()` now works correctly. + +The variable `Nette\Utils\Html::$xhtml` has been removed. + + +Finder +------ + +Finder has moved to the `nette/utils` package. Remove the original `nette/finder` package: + +```shell +composer remove nette/finder +``` + +On Linux, Finder now operates in case-sensitive mode by default. + +In the previous version, the `exclude()` and `filter()` methods behaved differently depending on whether they were called **before** or **after** `from()` or `in()`. This is no longer the case; `exclude()` and `filter()` always function the same way. The behavior of `filter()` called *after* `from()`/`in()` is now handled by the new method `descentFilter()`. + +Finder no longer implements the `Countable` interface. + +A string starting with a slash in `Finder::findFiles('/f*')` is now considered an absolute path. If you intended a relative path from the current directory, replace it with, for example, `Finder::findFiles('./f*')`. + +If a directory you are searching in does not exist, `Nette\InvalidStateException` is thrown (instead of the previous `UnexpectedValueException`). diff --git a/utils/en/validators.texy b/utils/en/validators.texy index 937d9cec1f..3313edc503 100644 --- a/utils/en/validators.texy +++ b/utils/en/validators.texy @@ -2,7 +2,7 @@ Value Validators **************** .[perex] -Need to quickly and easily verify that a variable contains for example a valid email address? Then [api:Nette\Utils\Validators] will come in handy, a static class with useful functions for validating values. +Need to quickly and easily verify that a variable contains, for example, a valid email address? Then [api:Nette\Utils\Validators] will come in handy, a static class with useful functions for validating values. Installation: @@ -21,87 +21,87 @@ use Nette\Utils\Validators; Basic Usage =========== -The class `Validators` has a number of methods for validating values, such as [#isList()], [#isUnicode()], [#isEmail()], [#isUrl()], etc. for use in your code: +The `Validators` class provides numerous methods for checking values, such as [#isUnicode()], [#isEmail()], [#isUrl()], etc., for use in your code: ```php if (!Validators::isEmail($email)) { - throw new InvalidArgumentException; + throw new InvalidArgumentException('Invalid email address provided.'); } ``` -Furthermore, it can verify whether the value satisfies the so-called [#expected types], which is a string where the individual options are separated by a vertical bar `|`. This makes it easy to verify union types using [#if()]: +Furthermore, it can verify whether the value satisfies the so-called [#expected types], which is a string where the individual options are separated by a vertical bar `|`. This makes it easy to verify union types using [#is()]: ```php if (!Validators::is($val, 'int|string|bool')) { - // ... + // Handle invalid type... } ``` -But it also gives you the opportunity to create a system where it is necessary to write expectation as strings (for example in annotations or configuration) and then verify according to them. +This also allows you to create systems where expectations need to be written as strings (e.g., in annotations or configurations) and then validate values against them. -You can also declare [assertion|#assert], which throws an exception if is not met. +You can also declare an [assertion |#assert], which throws an exception if the expectation is not met. Expected Types ============== -An expected types is a string consisting of one or more variants separated by a vertical bar `|`, similar to writing types in PHP (ie. `'int|string|bool')`. Nullable notation is also allowed `?int`. +Expected types form a string consisting of one or more variants separated by a pipe `|`, similar to how types are written in PHP (e.g., `'int|string|bool'`). Nullable notation `?int` is also accepted. An array where all elements are of a certain type is written in the form `int[]`. -Some types can be followed by a colon and the length `:length` or the range `:[min]..[max]`, eg `string:10` (a string with a length of 10 bytes), `float:10..` (number 10 and bigger), `array:..10` (array of up to ten elements) or `list:10..20` (list with 10 to 20 elements), or a regular expression for `pattern:[0-9]+`. +Some types can be followed by a colon and a length `:length` or a range `:[min]..[max]`, e.g., `string:10` (a string of 10 bytes length), `float:10..` (a number 10 or greater), `array:..10` (an array with up to ten elements), or `list:10..20` (a list with 10 to 20 elements), or a regular expression like `pattern:[0-9]+`. Overview of types and rules: .[wide] | PHP types || |-------------------------- -| `array` .{width: 140px} | range for the number of items can be given +| `array` .{width: 140px} | range for the number of elements can be specified | `bool` | -| `float` | range for the value can be given -| `int` | range for the value can be given +| `float` | range for the value can be specified +| `int` | range for the value can be specified | `null` | | `object` | | `resource` | -| `scalar` | int\|float\|bool\|string -| `string` | range for the length in bytes can be given +| `scalar` | `int|float|bool|string` +| `string` | range for the length in bytes can be specified | `callable` | | `iterable` | | `mixed` | |------------------------------------------------ -| pseudo-types || +| Pseudo-types || |------------------------------------------------ -| `list` | [indexed array |#isList], range for the number of items can be given -| `none` | empty value: `''`, `null`, `false` -| `number` | int\|float -| `numeric` | [number including textual representation |#isNumeric] -| `numericint`| [integer including textual representation |#isNumericInt] -| `unicode` | [UTF-8 string |#isUnicode], range for the length in characters can be given +| `list` | indexed array, range for the number of elements can be specified +| `none` | empty value: `''`, `null`, `false`, `0`, `0.0`, `[]` +| `number` | `int|float` +| `numeric` | [number including string representation |#isNumeric] +| `numericint`| [integer including string representation |#isNumericInt] +| `unicode` | [UTF-8 string |#isUnicode], range for the length in characters can be specified |------------------------------------------------ -| character class (cannot be an empty string) || +| Character class (must not be an empty string) || |------------------------------------------------ | `alnum` | all characters are alphanumeric -| `alpha` ​​| all characters are letters `[A-Za-z]` +| `alpha` | all characters are letters `[A-Za-z]` | `digit` | all characters are digits | `lower` | all characters are lowercase letters `[a-z]` -| `space` | all characters are spaces +| `space` | all characters are whitespace | `upper` | all characters are uppercase letters `[A-Z]` | `xdigit` | all characters are hexadecimal digits `[0-9A-Fa-f]` |------------------------------------------------ -| syntax validation || +| Syntax validation || |------------------------------------------------ -| `pattern` | a regular expression which the **entire** string must match +| `pattern` | a regular expression that the **entire** string must match | `email` | [Email |#isEmail] | `identifier`| [PHP identifier |#isPhpIdentifier] | `url` | [URL |#isUrl] | `uri` | [URI |#isUri] |------------------------------------------------ -| environment validation || +| Environment validation || |------------------------------------------------ -| `class` | is existing class -| `interface` | is existing interface -| `directory` | is existing directory -| `file` | is existing file +| `class` | is an existing class name +| `interface` | is an existing interface name +| `directory` | is an existing directory path +| `file` | is an existing file path Assertion @@ -111,19 +111,19 @@ Assertion assert($value, string $expected, string $label='variable'): void .[method] -------------------------------------------------------------------------- -Verifies that the value is of [#expected types] separated by pipe. If not, it throws exception [api:Nette\Utils\AssertionException]. The word `variable` in the exception message can be replaced by parameter `$label`. +Verifies that the value is one of the [#expected types] separated by a pipe. If not, it throws an [api:Nette\Utils\AssertionException]. The word `variable` in the exception message can be replaced by the `$label` parameter. ```php -Validators::assert('Nette', 'string:5'); // OK +Validators::assert('Nette', 'string:5'); // OK (string 'Nette' has 5 bytes) Validators::assert('Lorem ipsum dolor sit', 'string:78'); // AssertionException: The variable expects to be string:78, string 'Lorem ipsum dolor sit' given. ``` -assertField(array $array, string|int $key, string $expected=null, string $label=null): void .[method] ------------------------------------------------------------------------------------------------------ +assertField(array $array, string|int $key, ?string $expected=null, ?string $label=null): void .[method] +------------------------------------------------------------------------------------------------------- -Verifies that element `$key` in array `$array` is of [#expected types] separated by pipe. If not, it throws exception [api:Nette\Utils\AssertionException]. The string `item '%' in array` in the exception message can be replaced by parameter `$label`. +Verifies that the element with key `$key` in array `$array` is one of the [#expected types] separated by a pipe. If not, it throws an [api:Nette\Utils\AssertionException]. The string `item '%' in array` in the exception message can be replaced by the `$label` parameter. ```php $arr = ['foo' => 'Nette']; @@ -143,11 +143,11 @@ Validators is($value, string $expected): bool .[method] -------------------------------------------- -Checks if the value is of [#expected types] separated by pipe. +Checks if the value is one of the [#expected types] separated by a pipe. ```php Validators::is(1, 'int|float'); // true -Validators::is(23, 'int:0..10'); // false +Validators::is(23, 'int:0..10'); // false (23 is outside the range 0-10) Validators::is('Nette Framework', 'string:15'); // true, length is 15 bytes Validators::is('Nette Framework', 'string:8..'); // true Validators::is('Nette Framework', 'string:30..40'); // false @@ -157,7 +157,7 @@ Validators::is('Nette Framework', 'string:30..40'); // false isEmail(mixed $value): bool .[method] ------------------------------------- -Verifies that the value is a valid email address. It does not verify that the domain actually exists, only the syntax is verified. The function also counts on future [TLDs|https://en.wikipedia.org/wiki/Top-level_domain], which may also be in unicode. +Verifies that the value is a valid email address. It does not verify that the domain actually exists, only the syntax is verified. The function also accounts for future [TLDs|https://en.wikipedia.org/wiki/Top-level_domain], which may also be in unicode. ```php Validators::isEmail('example@nette.org'); // true @@ -169,14 +169,14 @@ Validators::isEmail('nette'); // false isInRange(mixed $value, array $range): bool .[method] ----------------------------------------------------- -Checks if the value is in the given range `[min, max]`, where the upper or lower limit can be omitted (`null`). Numbers, strings and DateTime objects can be compared. +Checks if the value is within the given range `[min, max]`, where the upper or lower bound can be omitted (`null`). Numbers, strings, and DateTime objects can be compared. If both boundaries are missing (`[null, null]`) or the value is `null`, it returns `false`. ```php Validators::isInRange(5, [0, 5]); // true Validators::isInRange(23, [null, 5]); // false -Validators::isInRange(23, [5]); // true +Validators::isInRange(23, [5]); // true (equivalent to [5, null]) Validators::isInRange(1, [5]); // false ``` @@ -184,7 +184,7 @@ Validators::isInRange(1, [5]); // false isNone(mixed $value): bool .[method] ------------------------------------ -Checks if the value is `0`, `''`, `false` or `null`. +Checks if the value is `0`, `''`, `false`, `null`, `0.0` or `[]`. ```php Validators::isNone(0); // true @@ -198,7 +198,7 @@ Validators::isNone('nette'); // false isNumeric(mixed $value): bool .[method] --------------------------------------- -Checks if the value is a number or a number written in a string. +Checks if the value is a number or a number represented as a string. ```php Validators::isNumeric(23); // true @@ -206,14 +206,14 @@ Validators::isNumeric(1.78); // true Validators::isNumeric('+42'); // true Validators::isNumeric('3.14'); // true Validators::isNumeric('nette'); // false -Validators::isNumeric('1e6'); // false +Validators::isNumeric('1e6'); // false (scientific notation not accepted) ``` isNumericInt(mixed $value): bool .[method] ------------------------------------------ -Checks if the value is an integer or a integer written in a string. +Checks if the value is an integer or an integer represented as a string. ```php Validators::isNumericInt(23); // true @@ -227,7 +227,7 @@ Validators::isNumericInt('nette'); // false isPhpIdentifier(string $value): bool .[method] ---------------------------------------------- -Checks if the value is a syntactically valid identifier in PHP, for example for names of classes, methods, functions, etc. +Checks if the value is a syntactically valid identifier in PHP (e.g., for class names, method names, function names, etc.). ```php Validators::isPhpIdentifier(''); // false @@ -240,7 +240,7 @@ Validators::isPhpIdentifier('one two'); // false isBuiltinType(string $type): bool .[method] ------------------------------------------- -Determines if `$type` is PHP built-in type. Otherwise, it is the class name. +Determines if `$type` is a PHP built-in type (e.g., `string`, `int`, `array`, `bool`). Otherwise, it's assumed to be a class name. ```php Validators::isBuiltinType('string'); // true @@ -251,7 +251,7 @@ Validators::isBuiltinType('Foo'); // false isTypeDeclaration(string $type): bool .[method] ----------------------------------------------- -Checks whether the type declaration is syntactically correct. +Checks whether the given type declaration string is syntactically valid according to PHP's type declaration rules (including union, intersection, DNF types). ```php Validators::isTypeDeclaration('?string'); // true @@ -268,7 +268,7 @@ Validators::isTypeDeclaration('(A|B)'); // false isClassKeyword(string $type): bool .[method] -------------------------------------------- -Determine if `$type` is one of the internal types `self`, `parent`, `static`. +Determines if `$type` is one of the internal type keywords `self`, `parent`, or `static`. ```php Validators::isClassKeyword('self'); // true @@ -284,32 +284,32 @@ Checks if the value is a valid UTF-8 string. ```php Validators::isUnicode('nette'); // true Validators::isUnicode(''); // true -Validators::isUnicode("\xA0"); // false +Validators::isUnicode("\xA0"); // false (invalid UTF-8 sequence) ``` isUrl(mixed $value): bool .[method] ----------------------------------- -Checks if the value is a valid URL address. +Checks if the value is a valid absolute URL address according to RFC 3986. ```php Validators::isUrl('https://nette.org:8080/path?query#fragment'); // true Validators::isUrl('http://localhost'); // true Validators::isUrl('http://192.168.1.1'); // true Validators::isUrl('http://[::1]'); // true -Validators::isUrl('http://user:pass@nette.org'); // false -Validators::isUrl('nette.org'); // false +Validators::isUrl('http://user:pass@nette.org'); // false (userinfo part not validated by this function) +Validators::isUrl('nette.org'); // false (missing scheme) ``` isUri(string $value): bool .[method] ------------------------------------ -Verifies that the value is a valid URI address, that is, actually a string beginning with a syntactically valid schema. +Verifies that the value is a valid URI address, meaning it's a string starting with a syntactically valid scheme followed by a colon (e.g., `http:`, `https:`, `mailto:`, `ftp:`). ```php Validators::isUri('https://nette.org'); // true Validators::isUri('mailto:gandalf@example.org'); // true -Validators::isUri('nette.org'); // false +Validators::isUri('nette.org'); // false (missing scheme) ``` diff --git a/utils/es/@home.texy b/utils/es/@home.texy index f54df2b0d8..ce5c03e010 100644 --- a/utils/es/@home.texy +++ b/utils/es/@home.texy @@ -1,42 +1,46 @@ -Utilidades -********** +Nette Utils +*********** .[perex] -En el paquete `nette/utils` encontrará un conjunto de clases útiles para el uso cotidiano: - -| [Comparación de cadenas |Strings] | Nette\Utils\Strings +En el paquete `nette/utils` encontrará un conjunto de clases útiles para el uso diario: + +| [Callback |Callback] | Nette\Utils\Callback +| [Fecha y hora |datetime] | Nette\Utils\DateTime +| [Finder |Finder] | Nette\Utils\Finder +| [Elementos HTML |html-elements] | Nette\Utils\Html +| [Iteradores |iterables] | Nette\Utils\Iterables +| [JSON |json] | Nette\Utils\Json +| [Cadenas aleatorias |random] | Nette\Utils\Random +| [Imágenes |images] | Nette\Utils\Image +| [Reflexión PHP |reflection] | Nette\Utils\Reflection +| [Tipos PHP |type] | Nette\Utils\Type +| [Arrays |arrays] | Nette\Utils\Arrays +| [Funciones auxiliares |helpers] | Nette\Utils\Helpers | [Comparación de floats |floats] | Nette\Utils\Floats -| [Devolución de llamada |Callback] | Nette\Utils\Callback -| [Fecha y Hora|datetime] | Nette\Utils\DateTime -| [Finder] | Nette\Utils\Finder -| [Funciones de ayuda |helpers] | Nette\Utils\Helpers -| [Generación de cadenas aleatorias |random] | Nette\Utils\Random -| [Generar código HTML |html-elements] | Nette\Utils\Html -| [Generar PHP Reflection |reflection] | Nette\Utils\Reflection -| [Imágenes |Images] | Nette\Utils\Image -| [JSON] | Nette\Utils\Json -| [Matrices |Arrays] | Nette\Utils\Arrays -| [Modelo de objeto |smartobject] | Nette\SmartObject & Nette\StaticClass -| [Paginador |Paginator] | Nette\Utils\Paginator +| [Cadenas |strings] | Nette\Utils\Strings | [Sistema de archivos |filesystem] | Nette\Utils\FileSystem -| [Tipos |type] | Nette\Utils\Type -| [Validadores |Validators] | Nette\Utils\Validators +| [Paginador |paginator] | Nette\Utils\Paginator +| [SmartObject |SmartObject] & [StaticClass |StaticClass] | Nette\SmartObject & Nette\StaticClass +| [Validador |validators] | Nette\Utils\Validators Instalación ----------- -Descargue e instale el paquete utilizando [Composer |best-practices:composer]: +Descargue e instale la biblioteca usando [Composer|best-practices:composer]: ```shell composer require nette/utils ``` -| versión | compatible con PHP +| versión | compatible con PHP |-----------|------------------- -| Nette Utils 4.0 | PHP 8.0 - 8.2 -| Nette Utils 3.2 | PHP 7.2 - 8.2 -| Nette Utils 3.0 - 3.1 | PHP 7.1 - 8.0 -| Nette Utils 2.5 | PHP 5.6 - 8.0 +| Nette Utils 4.0 | PHP 8.0 – 8.4 +| Nette Utils 3.2 | PHP 7.2 – 8.3 +| Nette Utils 3.0 – 3.1 | PHP 7.1 – 8.0 +| Nette Utils 2.5 | PHP 5.6 – 8.0 + +Se aplica a la última versión de parche. + -Se aplica a las últimas versiones de parches. +Si actualiza el paquete a una versión más reciente, consulte la página [actualización|en:upgrading]. diff --git a/utils/es/@left-menu.texy b/utils/es/@left-menu.texy index 9948ca0769..1a357a40e1 100644 --- a/utils/es/@left-menu.texy +++ b/utils/es/@left-menu.texy @@ -1,26 +1,28 @@ -Paquete nette/utils -******************* -- [Buscador |Finder] -- [Cadenas aleatorias |random] -- [Cuerdas |Strings] -- [Devolución de llamada |Callback] -- [Elementos HTML |HTML Elements] +Nette Utils +*********** +- [Callbacks |callback] - [Fecha y hora |datetime] -- [Flotadores |Floats] -- [Funciones auxiliares |helpers] -- [Imágenes |Images] -- [JSON] -- [Matrices |Arrays] -- [Objeto inteligente |SmartObject] +- [Finder |Finder] +- [Floats |Floats] +- [Elementos HTML |html-elements] +- [Iteradores |iterables] +- [JSON |JSON] +- [Cadenas aleatorias |random] +- [Imágenes |images] - [Paginador |paginator] - [Reflexión PHP |reflection] +- [Tipos PHP |type] +- [Arrays |arrays] +- [Funciones auxiliares |helpers] +- [Cadenas |strings] +- [SmartObject |SmartObject] +- [StaticClass |StaticClass] - [Sistema de archivos |filesystem] -- [Tipos de PHP |type] -- [Validadores |validators] +- [Validador |validators] -Otras utilidades -**************** -- [NEON |neon:] -- [Cifrado de contraseñas |security:passwords] -- [Parser y constructor de URL |http:urls] +Otras herramientas +****************** +- [NEON|neon:] +- [Hashing de contraseñas |security:passwords] +- [Análisis y composición de URL |http:urls] diff --git a/utils/es/@meta.texy b/utils/es/@meta.texy new file mode 100644 index 0000000000..1670b124ad --- /dev/null +++ b/utils/es/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Documentación}} diff --git a/utils/es/arrays.texy b/utils/es/arrays.texy index a393e4b351..39497de06e 100644 --- a/utils/es/arrays.texy +++ b/utils/es/arrays.texy @@ -1,8 +1,8 @@ -Funciones de matriz -******************* +Trabajo con arrays +****************** .[perex] -Esta página trata sobre las clases [Nette\Utils\Arrays |#Arrays], [ArrayHash |#ArrayHash] y [ArrayList |#ArrayList], relacionadas con los arrays. +Esta página se dedica a las clases [Nette\Utils\Arrays |#Arrays], [#ArrayHash] y [#ArrayList], que se refieren a los arrays. Instalación: @@ -12,22 +12,63 @@ composer require nette/utils ``` -Arrays .[#toc-arrays] -===================== +Arrays +====== -[api:Nette\Utils\Arrays] es una clase estática que contiene un puñado de prácticas funciones de array. +[api:Nette\Utils\Arrays] es una clase estática que contiene funciones útiles para trabajar con arrays. Su equivalente para iteradores es [Nette\Utils\Iterables|iterables]. -Los siguientes ejemplos asumen que el siguiente alias de clase está definido: +Los siguientes ejemplos asumen que se ha creado un alias: ```php use Nette\Utils\Arrays; ``` +associate(array $array, mixed $path): array|\stdClass .[method] +--------------------------------------------------------------- + +La función transforma flexiblemente el array `$array` en un array asociativo u objetos según la ruta especificada `$path`. La ruta puede ser una cadena o un array. Está formada por los nombres de las claves del array de entrada y operadores como '[]', '->', '=', y '|'. Lanza `Nette\InvalidArgumentException` en caso de que la ruta sea inválida. + +```php +// conversión a array asociativo según una clave simple +$arr = [ + ['name' => 'John', 'age' => 11], + ['name' => 'Mary', 'age' => null], + // ... +]; +$result = Arrays::associate($arr, 'name'); +// $result = ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// asignación de valores de una clave a otra usando el operador = +$result = Arrays::associate($arr, 'name=age'); // o ['name', '=', 'age'] +// $result = ['John' => 11, 'Mary' => null, ...] +``` + +```php +// creación de un objeto usando el operador -> +$result = Arrays::associate($arr, '->name'); // o ['->', 'name'] +// $result = (object) ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// combinación de claves mediante el operador | +$result = Arrays::associate($arr, 'name|age'); // o ['name', '|', 'age'] +// $result: ['John' => ['name' => 'John', 'age' => 11], 'Paul' => ['name' => 'Paul', 'age' => 44]] +``` + +```php +// adición a un array usando [] +$result = Arrays::associate($arr, 'name[]'); // o ['name', '[]'] +// $result: ['John' => [['name' => 'John', 'age' => 22], ['name' => 'John', 'age' => 11]]] +``` + + contains(array $array, $value): bool .[method] ---------------------------------------------- -Comprueba la presencia de un valor en un array. Utiliza una comparación estricta (`===`) +Prueba si un array contiene un valor. Utiliza una comparación estricta (`===`). ```php Arrays::contains([1, 2, 3], 1); // true @@ -35,10 +76,10 @@ Arrays::contains(['1', false], 1); // false ``` -every(iterable $array, callable $callback): bool .[method] ----------------------------------------------------------- +every(array $array, callable $predicate): bool .[method] +-------------------------------------------------------- -Comprueba si todos los elementos de la matriz pasan la prueba implementada por la función proporcionada, que tiene la firma `function ($value, $key, array $array): bool`. +Prueba si todos los elementos del array pasan la prueba implementada en `$predicate` con la firma `function ($value, $key, array $array): bool`. ```php $array = [1, 30, 39, 29, 10, 13]; @@ -46,24 +87,59 @@ $isBelowThreshold = fn($value) => $value < 40; $res = Arrays::every($array, $isBelowThreshold); // true ``` -Véase [some() |#some()]. +Ver [#some()]. -first(array $array): mixed .[method] ------------------------------------- +filter(array $array, callable $predicate): array .[method]{data-version:4.0.4} +------------------------------------------------------------------------------ + +Devuelve un nuevo array que contiene todos los pares clave-valor que coinciden con el predicado especificado. El callback tiene la firma `function ($value, int|string $key, array $array): bool`. + +```php +Arrays::filter( + ['a' => 1, 'b' => 2, 'c' => 3], + fn($v) => $v < 3, +); +// ['a' => 1, 'b' => 2] +``` + + +first(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------- + +Devuelve el primer elemento (que coincide con el predicado, si se especifica). Si tal elemento no existe, devuelve el resultado de llamar a `$else` o null. El parámetro `$predicate` tiene la firma `function ($value, int|string $key, array $array): bool`. + +No cambia el puntero interno a diferencia de `reset()`. Los parámetros `$predicate` y `$else` existen desde la versión 4.0.4. + +```php +Arrays::first([1, 2, 3]); // 1 +Arrays::first([1, 2, 3], fn($v) => $v > 2); // 3 +Arrays::first([]); // null +Arrays::first([], else: fn() => false); // false +``` + +Ver [#last()]. -Devuelve el primer elemento del array o null si el array está vacío. No cambia el puntero interno a diferencia de `reset()`. + +firstKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +------------------------------------------------------------------------------------------------ + +Devuelve la clave del primer elemento (que coincide con el predicado, si se especifica) o null si tal elemento no existe. El predicado `$predicate` tiene la firma `function ($value, int|string $key, array $array): bool`. ```php -Arrays::first([1, 2, 3]); // 1 -Arrays::first([]); // null +Arrays::firstKey([1, 2, 3]); // 0 +Arrays::firstKey([1, 2, 3], fn($v) => $v > 2); // 2 +Arrays::firstKey(['a' => 1, 'b' => 2]); // 'a' +Arrays::firstKey([]); // null ``` +Ver [#lastKey()]. + flatten(array $array, bool $preserveKeys=false): array .[method] ---------------------------------------------------------------- -Transforma array multidimensional a array plano. +Unifica un array multinivel en uno plano. ```php $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); @@ -71,62 +147,62 @@ $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); ``` -get(array $array, string|int|array $key, mixed $default=null): mixed .[method] ------------------------------------------------------------------------------- +get(array $array, string|int|array $key, ?mixed $default=null): mixed .[method] +------------------------------------------------------------------------------- -Devuelve `$array[$key]` item. Si no existe, se lanza `Nette\InvalidArgumentException`, a menos que se establezca un valor por defecto como tercer argumento. +Devuelve el elemento `$array[$key]`. Si no existe, lanza una excepción `Nette\InvalidArgumentException`, o si se especifica el tercer parámetro `$default`, lo devuelve. ```php -// if $array['foo'] does not exist, throws an exception +// si $array['foo'] no existe, lanza una excepción $value = Arrays::get($array, 'foo'); -// if $array['foo'] does not exist, returns 'bar' +// si $array['foo'] no existe, devuelve 'bar' $value = Arrays::get($array, 'foo', 'bar'); ``` -El argumento `$key` también puede ser una matriz. +La clave `$key` también puede ser un array. ```php $array = ['color' => ['favorite' => 'red'], 5]; $value = Arrays::get($array, ['color', 'favorite']); -// returns 'red' +// devuelve 'red' ``` getRef(array &$array, string|int|array $key): mixed .[method] ------------------------------------------------------------- -Obtiene la referencia a un `$array[$key]`. Si el índice no existe, se crea uno nuevo con el valor `null`. +Obtiene una referencia al elemento especificado del array. Si el elemento no existe, se creará con el valor null. ```php $valueRef = & Arrays::getRef($array, 'foo'); -// returns $array['foo'] reference +// devuelve una referencia a $array['foo'] ``` -Funciona con matrices multidimensionales así como con [get() |#get()]. +Al igual que la función [#get()], puede trabajar con arrays multidimensionales. ```php -$value = & Arrays::get($array, ['color', 'favorite']); -// returns $array['color']['favorite'] reference +$value = & Arrays::getRef($array, ['color', 'favorite']); +// devuelve una referencia a $array['color']['favorite'] ``` grep(array $array, string $pattern, bool $invert=false): array .[method] ------------------------------------------------------------------------ -Devuelve sólo los elementos de la matriz que coinciden con una expresión regular `$pattern`. En cambio, cuando `$invert = true`, devuelve elementos que no coinciden. Se produce un error de compilación o de ejecución de la expresión regular `Nette\RegexpException`. +Devuelve solo aquellos elementos del array cuyo valor coincide con la expresión regular `$pattern`. Si `$invert` es `true`, devuelve por el contrario los elementos que no coinciden. Un error durante la compilación o procesamiento de la expresión lanza una excepción `Nette\RegexpException`. ```php $filteredArray = Arrays::grep($array, '~^\d+$~'); -// returns only numerical items +// devuelve solo los elementos del array formados por dígitos ``` insertAfter(array &$array, string|int|null $key, array $inserted): void .[method] --------------------------------------------------------------------------------- -Inserta el contenido de la matriz `$inserted` en `$array` inmediatamente después de `$key`. Si `$key` es `null` (o no existe), se inserta al final. +Inserta el contenido del array `$inserted` en el array `$array` justo después del elemento con la clave `$key`. Si `$key` es `null` (o no está en el array), se inserta al final. ```php $array = ['first' => 10, 'second' => 20]; @@ -138,7 +214,7 @@ Arrays::insertAfter($array, 'first', ['hello' => 'world']); insertBefore(array &$array, string|int|null $key, array $inserted): void .[method] ---------------------------------------------------------------------------------- -Inserta el contenido de la matriz `$inserted` en `$array` antes de `$key`. Si `$key` es `null` (o no existe), se inserta al principio. +Inserta el contenido del array `$inserted` en el array `$array` antes del elemento con la clave `$key`. Si `$key` es `null` (o no está en el array), se inserta al principio. ```php $array = ['first' => 10, 'second' => 20]; @@ -150,7 +226,7 @@ Arrays::insertBefore($array, 'first', ['hello' => 'world']); invoke(iterable $callbacks, ...$args): array .[method] ------------------------------------------------------ -Invoca todos los callbacks y devuelve el array de resultados. +Invoca todos los callbacks y devuelve un array de resultados. ```php $callbacks = [ @@ -166,7 +242,7 @@ $array = Arrays::invoke($callbacks, 5, 11); invokeMethod(iterable $objects, string $method, ...$args): array .[method] -------------------------------------------------------------------------- -Invoca el método en cada objeto de un array y devuelve un array de resultados. +Llama al método en cada objeto del array y devuelve un array de resultados. ```php $objects = ['a' => $obj1, 'b' => $obj2]; @@ -179,7 +255,7 @@ $array = Arrays::invokeMethod($objects, 'foo', 1, 2); isList(array $array): bool .[method] ------------------------------------ -Comprueba si la matriz está indexada en orden ascendente de claves numéricas a partir de cero, es decir, si es una lista. +Verifica si el array está indexado según una serie ascendente de claves numéricas desde cero, alias lista. ```php Arrays::isList(['a', 'b', 'c']); // true @@ -188,21 +264,42 @@ Arrays::isList(['a' => 1, 'b' => 2]); // false ``` -last(array $array): mixed .[method] ------------------------------------ +last(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------ + +Devuelve el último elemento (que coincide con el predicado, si se especifica). Si tal elemento no existe, devuelve el resultado de llamar a `$else` o null. El parámetro `$predicate` tiene la firma `function ($value, int|string $key, array $array): bool`. -Devuelve el último elemento del array o null si el array está vacío. No cambia el puntero interno a diferencia de `end()`. +No cambia el puntero interno a diferencia de `end()`. Los parámetros `$predicate` y `$else` existen desde la versión 4.0.4. ```php -Arrays::last([1, 2, 3]); // 3 -Arrays::last([]); // null +Arrays::last([1, 2, 3]); // 3 +Arrays::last([1, 2, 3], fn($v) => $v < 3); // 2 +Arrays::last([]); // null +Arrays::last([], else: fn() => false); // false ``` +Ver [#first()]. -map(iterable $array, callable $callback): array .[method] + +lastKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +----------------------------------------------------------------------------------------------- + +Devuelve la clave del último elemento (que coincide con el predicado, si se especifica) o null si tal elemento no existe. El predicado `$predicate` tiene la firma `function ($value, int|string $key, array $array): bool`. + +```php +Arrays::lastKey([1, 2, 3]); // 2 +Arrays::lastKey([1, 2, 3], fn($v) => $v < 3); // 1 +Arrays::lastKey(['a' => 1, 'b' => 2]); // 'b' +Arrays::lastKey([]); // null +``` + +Ver [#firstKey()]. + + +map(array $array, callable $transformer): array .[method] --------------------------------------------------------- -Llama a `$callback` en todos los elementos de la matriz y devuelve la matriz de valores de retorno. La llamada de retorno tiene la firma `function ($value, $key, array $array): bool`. +Llama a `$transformer` en todos los elementos del array y devuelve un array de los valores devueltos. El callback tiene la firma `function ($value, $key, array $array): mixed`. ```php $array = ['foo', 'bar', 'baz']; @@ -211,10 +308,24 @@ $res = Arrays::map($array, fn($value) => $value . $value); ``` +mapWithKeys(array $array, callable $transformer): array .[method] +----------------------------------------------------------------- + +Crea un nuevo array transformando los valores y claves del array original. La función `$transformer` tiene la firma `function ($value, $key, array $array): ?array{$newKey, $newValue}`. Si `$transformer` devuelve `null`, el elemento se omite. Para los elementos conservados, el primer elemento del array devuelto se usa como nueva clave y el segundo elemento como nuevo valor. + +```php +$array = ['a' => 1, 'b' => 2]; +$result = Arrays::mapWithKeys($array, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +Este método es útil en situaciones donde necesitas cambiar la estructura del array (claves y valores simultáneamente) o filtrar elementos durante la transformación (devolviendo null para elementos no deseados). + + mergeTree(array $array1, array $array2): array .[method] -------------------------------------------------------- -Fusiona recursivamente dos campos. Es útil, por ejemplo, para fusionar estructuras de árbol. Se comporta como el operador `+` para matrices, es decir, añade un par clave/valor de la segunda matriz a la primera y conserva el valor de la primera matriz en caso de colisión de claves. +Fusiona recursivamente dos arrays. Es útil, por ejemplo, para fusionar estructuras de árbol. Durante la fusión, sigue las mismas reglas que el operador `+` aplicado a arrays, es decir, añade pares clave/valor del segundo array al primer array y en caso de colisión de claves, conserva el valor del primer array. ```php $array1 = ['color' => ['favorite' => 'red'], 5]; @@ -224,13 +335,13 @@ $array = Arrays::mergeTree($array1, $array2); // $array = ['color' => ['favorite' => 'red', 'blue'], 5]; ``` -Los valores de la segunda matriz se añaden siempre a la primera. La desaparición del valor `10` de la segunda matriz puede parecer un poco confusa. Debe tenerse en cuenta que este valor, así como el valor `5` in the first array have the same numeric key `0`, por lo que en el campo resultante sólo hay un elemento de la primera matriz. +Los valores del segundo array siempre se añaden al final del primero. Puede parecer un poco confuso la desaparición del valor `10` del segundo array. Hay que darse cuenta de que este valor, así como el valor `5` en el primer array, tienen asignada la misma clave numérica `0`, por lo tanto, en el array resultante solo está el elemento del primer array. -normalize(array $array, string $filling=null): array .[method] --------------------------------------------------------------- +normalize(array $array, ?string $filling=null): array .[method] +--------------------------------------------------------------- -Normaliza array a array asociativo. Sustituye las claves numéricas por sus valores, el nuevo valor será `$filling`. +Normaliza un array a un array asociativo. Reemplaza las claves numéricas con sus valores, el nuevo valor será `$filling`. ```php $array = Arrays::normalize([1 => 'first', 'a' => 'second']); @@ -243,26 +354,26 @@ $array = Arrays::normalize([1 => 'first', 'a' => 'second'], 'foobar'); ``` -pick(array &$array, string|int $key, mixed $default=null): mixed .[method] --------------------------------------------------------------------------- +pick(array &$array, string|int $key, ?mixed $default=null): mixed .[method] +--------------------------------------------------------------------------- -Devuelve y elimina el valor de un elemento de un array. Si no existe, lanza una excepción, o devuelve `$default`, si se proporciona. +Devuelve y elimina el valor de un elemento del array. Si no existe, lanza una excepción, o devuelve el valor `$default` si se especifica. ```php -$array = [1 => 'foo', null => 'bar']; -$a = Arrays::pick($array, null); +$array = [1 => 'foo', 'x' => 'bar']; +$a = Arrays::pick($array, 'x'); // $a = 'bar' $b = Arrays::pick($array, 'not-exists', 'foobar'); // $b = 'foobar' $c = Arrays::pick($array, 'not-exists'); -// throws Nette\InvalidArgumentException +// lanza Nette\InvalidArgumentException ``` renameKey(array &$array, string|int $oldKey, string|int $newKey): bool .[method] -------------------------------------------------------------------------------- -Cambia el nombre de una clave. Devuelve `true` si la clave se encontró en el array. +Renombra una clave en el array. Devuelve `true` si la clave fue encontrada en el array. ```php $array = ['first' => 10, 'second' => 20]; @@ -274,20 +385,20 @@ Arrays::renameKey($array, 'first', 'renamed'); getKeyOffset(array $array, string|int $key): ?int .[method] ----------------------------------------------------------- -Devuelve la posición de índice cero de la clave del array dado. Devuelve `null` si no se encuentra la clave. +Devuelve la posición de la clave dada en el array. La posición se numera desde 0. En caso de que la clave no sea encontrada, la función devuelve `null`. ```php $array = ['first' => 10, 'second' => 20]; -$position = Arrays::getKeyOffset($array, 'first'); // returns 0 -$position = Arrays::getKeyOffset($array, 'second'); // returns 1 -$position = Arrays::getKeyOffset($array, 'not-exists'); // returns null +$position = Arrays::getKeyOffset($array, 'first'); // devuelve 0 +$position = Arrays::getKeyOffset($array, 'second'); // devuelve 1 +$position = Arrays::getKeyOffset($array, 'not-exists'); // devuelve null ``` -some(iterable $array, callable $callback): bool .[method] ---------------------------------------------------------- +some(array $array, callable $predicate): bool .[method] +------------------------------------------------------- -Comprueba si al menos un elemento de la matriz supera la prueba implementada por la llamada de retorno proporcionada con la firma `function ($value, $key, array $array): bool`. +Prueba si al menos un elemento del array pasa la prueba implementada en `$predicate` con la firma `function ($value, $key, array $array): bool`. ```php $array = [1, 2, 3, 4]; @@ -295,13 +406,13 @@ $isEven = fn($value) => $value % 2 === 0; $res = Arrays::some($array, $isEven); // true ``` -Véase [every() |#every()]. +Ver [#every()]. toKey(mixed $key): string|int .[method] --------------------------------------- -Convierte un valor en una clave de matriz, que puede ser un entero o una cadena. +Convierte un valor a una clave de array, que es un entero o una cadena. ```php Arrays::toKey('1'); // 1 @@ -312,19 +423,19 @@ Arrays::toKey('01'); // '01' toObject(iterable $array, object $object): object .[method] ----------------------------------------------------------- -Copia los elementos del array `$array` al objeto `$object` y luego lo devuelve. +Copia los elementos del array `$array` al objeto `$object`, que luego devuelve. ```php $obj = new stdClass; $array = ['foo' => 1, 'bar' => 2]; -Arrays::toObject($array, $obj); // it sets $obj->foo = 1; $obj->bar = 2; +Arrays::toObject($array, $obj); // establece $obj->foo = 1; $obj->bar = 2; ``` -wrap(iterable $array, string $prefix='', string $suffix=''): array .[method] ----------------------------------------------------------------------------- +wrap(array $array, string $prefix='', string $suffix=''): array .[method] +------------------------------------------------------------------------- -Convierte cada elemento del array en cadena y lo encierra con `$prefix` y `$suffix`. +Convierte cada elemento del array a una cadena y lo envuelve con el prefijo `$prefix` y el sufijo `$suffix`. ```php $array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); @@ -332,21 +443,21 @@ $array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); ``` -ArrayHash .[#toc-arrayhash] -=========================== +ArrayHash +========= -El objeto [api:Nette\Utils\ArrayHash] es el descendiente de la clase genérica stdClass y lo extiende a la capacidad de tratarlo como un array, por ejemplo, accediendo a los miembros usando corchetes: +El objeto [api:Nette\Utils\ArrayHash] es un descendiente de la clase genérica stdClass y la extiende con la capacidad de tratarlo como un array, es decir, por ejemplo, acceder a los miembros a través de corchetes: ```php $hash = new Nette\Utils\ArrayHash; $hash['foo'] = 123; -$hash->bar = 456; // also works object notation +$hash->bar = 456; // al mismo tiempo funciona la notación de objeto $hash->foo; // 123 ``` -Puedes utilizar la función `count($hash)` para obtener el número de elementos. +Se puede usar la función `count($hash)` para obtener el número de elementos. -Puedes iterar sobre un objeto como lo harías sobre un array, incluso con una referencia: +Se puede iterar sobre el objeto igual que en el caso de un array, incluso con referencia: ```php foreach ($hash as $key => $value) { @@ -354,11 +465,11 @@ foreach ($hash as $key => $value) { } foreach ($hash as $key => &$value) { - $value = 'nuevo valor'; + $value = 'new value'; } ``` -Las matrices existentes pueden transformarse a `ArrayHash` utilizando `from()`: +Un array existente podemos transformarlo a `ArrayHash` con el método `from()`: ```php $array = ['foo' => 123, 'bar' => 456]; @@ -368,35 +479,35 @@ $hash->foo; // 123 $hash->bar; // 456 ``` -La transformación es recursiva: +La conversión es recursiva: ```php $array = ['foo' => 123, 'inner' => ['a' => 'b']]; $hash = Nette\Utils\ArrayHash::from($array); -$hash->inner; // object ArrayHash +$hash->inner; // objeto ArrayHash $hash->inner->a; // 'b' $hash['inner']['a']; // 'b' ``` -Se puede evitar mediante el segundo parámetro: +Esto se puede prevenir con el segundo parámetro: ```php $hash = Nette\Utils\ArrayHash::from($array, false); $hash->inner; // array ``` -Transformar de nuevo a la matriz: +Transformación de vuelta a array: ```php $array = (array) $hash; ``` -ArrayList .[#toc-arraylist] -=========================== +ArrayList +========= -[api:Nette\Utils\ArrayList] representa un array lineal donde los índices son sólo enteros ascendentes desde 0. +[api:Nette\Utils\ArrayList] representa un array lineal, donde los índices son solo números enteros ascendentemente desde 0. ```php $list = new Nette\Utils\ArrayList; @@ -407,9 +518,16 @@ $list[] = 'c'; count($list); // 3 ``` -Puede utilizar la función `count($list)` para obtener el número de elementos. +Un array existente podemos transformarlo a `ArrayList` con el método `from()`: -Puedes iterar sobre un objeto como lo harías sobre un array, incluso con una referencia: +```php +$array = ['foo', 'bar']; +$list = Nette\Utils\ArrayList::from($array); +``` + +Se puede usar la función `count($list)` para obtener el número de elementos. + +Se puede iterar sobre el objeto igual que en el caso de un array, incluso con referencia: ```php foreach ($list as $key => $value) { @@ -417,32 +535,25 @@ foreach ($list as $key => $value) { } foreach ($list as $key => &$value) { - $value = 'nuevo valor'; + $value = 'new value'; } ``` -Las matrices existentes pueden transformarse a `ArrayList` utilizando `from()`: - -```php -$array = ['foo', 'bar']; -$list = Nette\Utils\ArrayList::from($array); -``` - -Acceder a claves más allá de los valores permitidos lanza una excepción `Nette\OutOfRangeException`: +El acceso a claves fuera de los valores permitidos lanza una excepción `Nette\OutOfRangeException`: ```php -echo $list[-1]; // throws Nette\OutOfRangeException -unset($list[30]); // throws Nette\OutOfRangeException +echo $list[-1]; // lanza Nette\OutOfRangeException +unset($list[30]); // lanza Nette\OutOfRangeException ``` -Al eliminar la clave se renumeran los elementos: +La eliminación de una clave causa la renumeración de los elementos: ```php unset($list[1]); // ArrayList(0 => 'a', 1 => 'c') ``` -Puede añadir un nuevo elemento al principio utilizando `prepend()`: +Se puede añadir un nuevo elemento al principio con el método `prepend()`: ```php $list->prepend('d'); diff --git a/utils/es/callback.texy b/utils/es/callback.texy index 8b907590cc..1200495faa 100644 --- a/utils/es/callback.texy +++ b/utils/es/callback.texy @@ -1,8 +1,8 @@ -Funciones Callback -****************** +Trabajo con callbacks +********************* .[perex] -[api:Nette\Utils\Callback] es una clase estática, que contiene funciones para trabajar con [retrollamadas PHP |https://www.php.net/manual/en/language.types.callable.php]. +[api:Nette\Utils\Callback] es una clase estática con funciones para trabajar con [callbacks de PHP |https://www.php.net/manual/en/language.types.callable.php]. Instalación: @@ -11,7 +11,7 @@ Instalación: composer require nette/utils ``` -Todos los ejemplos asumen que el siguiente alias de clase está definido: +Todos los ejemplos asumen que se ha creado un alias: ```php use Nette\Utils\Callback; @@ -21,21 +21,21 @@ use Nette\Utils\Callback; check($callable, bool $syntax=false): callable .[method] -------------------------------------------------------- -Comprueba que `$callable` es una llamada de retorno PHP válida. En caso contrario lanza `Nette\InvalidArgumentException`. Si `$syntax` es verdadero, la función sólo verifica que `$callable` tiene una estructura válida para ser usada como callback, pero no verifica si la clase o método existe realmente. Devuelve `$callable`. +Comprueba si la variable `$callable` es un callback válido. De lo contrario, lanza `Nette\InvalidArgumentException`. Si `$syntax` es true, la función solo verifica que `$callable` tenga la estructura de un callback, pero no verifica si la clase o método dado realmente existe. Devuelve `$callable`. ```php -Callback::check('trim'); // no exception -Callback::check(['NonExistentClass', 'method']); // throws Nette\InvalidArgumentException -Callback::check(['NonExistentClass', 'method'], true); // no exception -Callback::check(function () {}); // no exception -Callback::check(null); // throws Nette\InvalidArgumentException +Callback::check('trim'); // no lanza excepción +Callback::check(['NonExistentClass', 'method']); // lanza Nette\InvalidArgumentException +Callback::check(['NonExistentClass', 'method'], true); // no lanza excepción +Callback::check(function () {}); // no lanza excepción +Callback::check(null); // lanza Nette\InvalidArgumentException ``` toString($callable): string .[method] ------------------------------------- -Convierte la llamada de retorno PHP a forma textual. La clase o el método pueden no existir. +Convierte un callback de PHP a forma textual. La clase o método no tiene que existir. ```php Callback::toString('trim'); // 'trim' @@ -46,21 +46,21 @@ Callback::toString(['MyClass', 'method']); // 'MyClass::method' toReflection($callable): ReflectionMethod|ReflectionFunction .[method] ---------------------------------------------------------------------- -Devuelve la reflexión para el método o función usado en la llamada de retorno de PHP. +Devuelve la reflexión para el método o función en el callback de PHP. ```php $ref = Callback::toReflection('trim'); -// $ref is ReflectionFunction('trim') +// $ref es ReflectionFunction('trim') $ref = Callback::toReflection(['MyClass', 'method']); -// $ref is ReflectionMethod('MyClass', 'method') +// $ref es ReflectionMethod('MyClass', 'method') ``` isStatic($callable): bool .[method] ----------------------------------- -Comprueba si la llamada de retorno de PHP es una función o un método estático. +Determina si un callback de PHP es una función o un método estático. ```php Callback::isStatic('trim'); // true @@ -73,7 +73,7 @@ Callback::isStatic(function () {}); // false unwrap(Closure $closure): callable|array .[method] -------------------------------------------------- -Desenvuelve el cierre creado por `Closure::fromCallable`:https://www.php.net/manual/en/closure.fromcallable.php. +Desenvuelve inversamente una Closure creada mediante `Closure::fromCallable`:https://www.php.net/manual/en/closure.fromcallable.php. ```php $closure = Closure::fromCallable(['MyClass', 'method']); diff --git a/utils/es/datetime.texy b/utils/es/datetime.texy index d3d11dd8bf..62f3ca316b 100644 --- a/utils/es/datetime.texy +++ b/utils/es/datetime.texy @@ -2,7 +2,7 @@ Fecha y hora ************ .[perex] -[api:Nette\Utils\DateTime] es una clase que extiende la nativa [php:DateTime]. +[api:Nette\Utils\DateTime] es una clase que extiende la clase nativa [php:DateTime] con funciones adicionales. Instalación: @@ -11,7 +11,7 @@ Instalación: composer require nette/utils ``` -Todos los ejemplos asumen que el siguiente alias de clase está definido: +Todos los ejemplos asumen que se ha creado un alias: ```php use Nette\Utils\DateTime; @@ -20,35 +20,35 @@ use Nette\Utils\DateTime; static from(string|int|\DateTimeInterface $time): DateTime .[method] -------------------------------------------------------------------- -Crea un objeto DateTime a partir de una cadena, una marca de tiempo UNIX u otro objeto [php:DateTimeInterface]. Lanza un `Exception` si la fecha y la hora no son válidas. +Crea un objeto DateTime a partir de una cadena, timestamp UNIX u otro objeto [php:DateTimeInterface]. Lanza una excepción `Exception` si la fecha y hora no son válidas. ```php -DateTime::from(1138013640); // creates a DateTime from the UNIX timestamp with a default timezamp -DateTime::from(42); // creates a DateTime from the current time plus 42 seconds -DateTime::from('1994-02-26 04:15:32'); // creates a DateTime based on a string -DateTime::from('1994-02-26'); // create DateTime by date, time will be 00:00:00 +DateTime::from(1138013640); // crea DateTime desde un timestamp UNIX con la zona horaria predeterminada +DateTime::from(42); // crea DateTime desde la hora actual más 42 segundos +DateTime::from('1994-02-26 04:15:32'); // crea DateTime según la cadena +DateTime::from('1994-02-26'); // crea DateTime según la fecha, la hora será 00:00:00 ``` static fromParts(int $year, int $month, int $day, int $hour=0, int $minute=0, float $second=0.0): DateTime .[method] -------------------------------------------------------------------------------------------------------------------- -Crea un objeto DateTime o lanza una excepción `Nette\InvalidArgumentException` si la fecha y la hora no son válidas. +Crea un objeto DateTime o lanza una excepción `Nette\InvalidArgumentException` si la fecha y hora no son válidas. ```php DateTime::fromParts(1994, 2, 26, 4, 15, 32); ``` -static createFromFormat(string $format, string $time, string|\DateTimeZone $timezone=null): DateTime|false .[method] --------------------------------------------------------------------------------------------------------------------- -Amplía [DateTime::createFromFormat() |https://www.php.net/manual/en/datetime.createfromformat.php] con la posibilidad de especificar una zona horaria como cadena. +static createFromFormat(string $format, string $time, ?string|\DateTimeZone $timezone=null): DateTime|false .[method] +--------------------------------------------------------------------------------------------------------------------- +Extiende [DateTime::createFromFormat()|https://www.php.net/manual/en/datetime.createfromformat.php] con la posibilidad de especificar la zona horaria como una cadena. ```php -DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); // create with custom timezone +DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); ``` modifyClone(string $modify=''): static .[method] ------------------------------------------------ -Crea una copia con una hora modificada. +Crea una copia con la hora modificada. ```php $original = DateTime::from('2017-02-03'); $clone = $original->modifyClone('+1 day'); @@ -59,15 +59,15 @@ $clone->format('Y-m-d'); // '2017-02-04' __toString(): string .[method] ------------------------------ -Devuelve la fecha y la hora en el formato `Y-m-d H:i:s`. +Devuelve la fecha y hora en formato `Y-m-d H:i:s`. ```php echo $dateTime; // '2017-02-03 04:15:32' ``` -Implementa JsonSerializable .[#toc-implements-jsonserializable] ---------------------------------------------------------------- -Devuelve la fecha y la hora en formato ISO 8601, que se utiliza, por ejemplo, en JavaScript. +implementa JsonSerializable +--------------------------- +Devuelve la fecha y hora en formato ISO 8601, que se utiliza por ejemplo en JavaScript. ```php $date = DateTime::from('2017-02-03'); echo json_encode($date); diff --git a/utils/es/filesystem.texy b/utils/es/filesystem.texy index f7f914d42b..db1a450dd3 100644 --- a/utils/es/filesystem.texy +++ b/utils/es/filesystem.texy @@ -1,41 +1,43 @@ -Funciones del sistema de archivos -********************************* +Sistema de archivos +******************* .[perex] -[api:Nette\Utils\FileSystem] es una clase estática, que contiene funciones útiles para trabajar con un sistema de archivos. Una ventaja sobre las funciones nativas de PHP es que lanzan excepciones en caso de errores. +[api:Nette\Utils\FileSystem] es una clase con funciones útiles para trabajar con el sistema de archivos. Una de las ventajas sobre las funciones nativas de PHP es que lanzan excepciones en caso de error. +Si necesita buscar archivos en el disco, utilice [Finder|finder]. + Instalación: ```shell composer require nette/utils ``` -Los siguientes ejemplos asumen que el siguiente alias de clase está definido: +Los siguientes ejemplos asumen que se ha creado un alias: ```php use Nette\Utils\FileSystem; ``` -Manipulación .[#toc-manipulation] -================================= +Manipulación +============ copy(string $origin, string $target, bool $overwrite=true): void .[method] -------------------------------------------------------------------------- -Copia un archivo o un directorio completo. Sobrescribe los archivos y directorios existentes por defecto. Si `$overwrite` se establece en `false` y ya existe un `$target`, lanza una excepción `Nette\InvalidStateException`. Lanza una excepción `Nette\IOException` en caso de error. +Copia un archivo o un directorio completo. Por defecto, sobrescribe los archivos y directorios existentes. Con el parámetro `$overwrite` establecido en `false`, lanza una excepción `Nette\InvalidStateException` si el archivo o directorio de destino `$target` ya existe. En caso de error, lanza una excepción `Nette\IOException`. ```php FileSystem::copy('/path/to/source', '/path/to/dest', overwrite: true); ``` -createDir(string $directory, int $mode=0777): void .[method] ------------------------------------------------------------- +createDir(string $dir, int $mode=0777): void .[method] +------------------------------------------------------ -Crea un directorio si no existe, incluyendo los directorios padre. Lanza una excepción `Nette\IOException` en caso de error. +Crea un directorio si no existe, incluyendo los directorios padre. En caso de error, lanza una excepción `Nette\IOException`. ```php FileSystem::createDir('/path/to/dir'); @@ -45,7 +47,7 @@ FileSystem::createDir('/path/to/dir'); delete(string $path): void .[method] ------------------------------------ -Borra un fichero o un directorio entero si existe. Si el directorio no está vacío, borra primero su contenido. Lanza una excepción `Nette\IOException` en caso de error. +Elimina un archivo o un directorio completo si existe. Si el directorio no está vacío, elimina primero su contenido. En caso de error, lanza una excepción `Nette\IOException`. ```php FileSystem::delete('/path/to/fileOrDir'); @@ -55,7 +57,7 @@ FileSystem::delete('/path/to/fileOrDir'); makeWritable(string $path, int $dirMode=0777, int $fileMode=0666): void .[method] --------------------------------------------------------------------------------- -Establece los permisos de archivo a `$fileMode` o los permisos de directorio a `$dirMode`. Recursivamente recorre y establece permisos en todo el contenido del directorio también. +Establece los permisos del archivo a `$fileMode` o del directorio a `$dirMode`. Recorre recursivamente y establece los permisos también para todo el contenido del directorio. ```php FileSystem::makeWritable('/path/to/fileOrDir'); @@ -65,7 +67,7 @@ FileSystem::makeWritable('/path/to/fileOrDir'); open(string $path, string $mode): resource .[method] ---------------------------------------------------- -Abre el archivo y devuelve el recurso. El parámetro `$mode` funciona igual que la función nativa `fopen()`:https://www.php.net/manual/en/function.fopen.php. Si se produce un error, lanza la excepción `Nette\IOException`. +Abre un archivo y devuelve un resource. El parámetro `$mode` funciona igual que en la función nativa [`fopen()`|https://www.php.net/manual/en/function.fopen.php]. En caso de error, lanza una excepción `Nette\IOException`. ```php $res = FileSystem::open('/path/to/file', 'r'); @@ -75,7 +77,7 @@ $res = FileSystem::open('/path/to/file', 'r'); read(string $file): string .[method] ------------------------------------ -Lee el contenido de un `$file`. Lanza una excepción `Nette\IOException` si se produce un error. +Devuelve el contenido del archivo `$file`. En caso de error, lanza una excepción `Nette\IOException`. ```php $content = FileSystem::read('/path/to/file'); @@ -85,8 +87,7 @@ $content = FileSystem::read('/path/to/file'); readLines(string $file, bool $stripNewLines=true): \Generator .[method] ----------------------------------------------------------------------- -Lee el contenido del fichero línea a línea. A diferencia de la función nativa `file()`, no lee todo el fichero en memoria, sino que lo lee de forma continua, de modo que se pueden leer ficheros mayores que la memoria disponible. `$stripNewLines` especifica si se eliminan los caracteres de salto de línea de `\r` y `\n`. -En caso de error, lanza una excepción `Nette\IOException`. +Lee el contenido del archivo línea por línea. A diferencia de la función nativa `file()`, no carga todo el archivo en memoria, sino que lo lee continuamente, por lo que es posible leer archivos más grandes que la memoria disponible. `$stripNewLines` indica si se deben eliminar los caracteres de fin de línea `\r` y `\n`. En caso de error, lanza una excepción `Nette\IOException`. ```php $lines = FileSystem::readLines('/path/to/file'); @@ -100,7 +101,7 @@ foreach ($lines as $lineNum => $line) { rename(string $origin, string $target, bool $overwrite=true): void .[method] ---------------------------------------------------------------------------- -Renombra o mueve un archivo o un directorio especificado por `$origin` a `$target`. Sobrescribe los archivos y directorios existentes por defecto. Si `$overwrite` se establece en `false` y `$target` ya existe, lanza una excepción `Nette\InvalidStateException`. Lanza una excepción `Nette\IOException` en caso de error. +Renombra o mueve un archivo o directorio `$origin` a `$target`. Por defecto, sobrescribe los archivos y directorios existentes. Con el parámetro `$overwrite` establecido en `false`, lanza una excepción `Nette\InvalidStateException` si el archivo o directorio de destino `$target` ya existe. En caso de error, lanza una excepción `Nette\IOException`. ```php FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); @@ -110,21 +111,21 @@ FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); write(string $file, string $content, int $mode=0666): void .[method] -------------------------------------------------------------------- -Escribe `$content` en `$file`. Lanza una excepción `Nette\IOException` si se produce un error. +Escribe la cadena `$content` en el archivo `$file`. En caso de error, lanza una excepción `Nette\IOException`. ```php FileSystem::write('/path/to/file', $content); ``` -Rutas .[#toc-paths] -=================== +Rutas +===== isAbsolute(string $path): bool .[method] ---------------------------------------- -Determina si la dirección `$path` es absoluta. +Determina si la ruta `$path` es absoluta. ```php FileSystem::isAbsolute('../backup'); // false @@ -146,7 +147,7 @@ FileSystem::joinPaths('/a/', '/../b'); // '/b' normalizePath(string $path): string .[method] --------------------------------------------- -Normaliza `..` y `.` y los separadores de directorio de la ruta. +Normaliza `..` y `.` y los separadores de directorios en la ruta a los separadores del sistema operativo actual. ```php FileSystem::normalizePath('/file/.'); // '/file/' @@ -159,7 +160,7 @@ FileSystem::normalizePath('file/../../bar'); // '/../bar' unixSlashes(string $path): string .[method] ------------------------------------------- -Convierte las barras oblicuas en `/` utilizadas en los sistemas Unix. +Convierte las barras invertidas a barras `/` utilizadas en los sistemas Unix. ```php $path = FileSystem::unixSlashes($path); @@ -169,8 +170,45 @@ $path = FileSystem::unixSlashes($path); platformSlashes(string $path): string .[method] ----------------------------------------------- -Convierte las barras inclinadas en caracteres específicos de la plataforma actual, es decir, `\` en Windows y `/` en otros sistemas. +Convierte las barras a los caracteres específicos de la plataforma actual, es decir, `\` en Windows y `/` en otros sistemas. ```php $path = FileSystem::platformSlashes($path); ``` + + +resolvePath(string $basePath, string $path): string .[method]{data-version:4.0.6} +--------------------------------------------------------------------------------- + +Resuelve la ruta final a partir de la ruta `$path` relativa al directorio base `$basePath`. Las rutas absolutas (`/foo`, `C:/foo`) se dejan sin cambios (solo se normalizan las barras), las rutas relativas se concatenan a la ruta base. + +```php +// En Windows, las barras en la salida serían invertidas (\) +FileSystem::resolvePath('/base/dir', '/abs/path'); // '/abs/path' +FileSystem::resolvePath('/base/dir', 'rel'); // '/base/dir/rel' +FileSystem::resolvePath('base/dir', '../file.txt'); // 'base/file.txt' +FileSystem::resolvePath('base', ''); // 'base' +``` + + +Acceso estático vs no estático +============================== + +Para poder reemplazar fácilmente la clase por otra (por ejemplo, un mock para fines de prueba), úsela de forma no estática: + +```php +class AnyClassUsingFileSystem +{ + public function __construct( + private FileSystem $fileSystem, + ) { + } + + public function readConfig(): string + { + return $this->fileSystem->read(/* ... */); + } + + ... +} +``` diff --git a/utils/es/finder.texy b/utils/es/finder.texy index 04afe3c631..299be1a569 100644 --- a/utils/es/finder.texy +++ b/utils/es/finder.texy @@ -1,27 +1,27 @@ -Buscador: Búsqueda de archivos -****************************** +Finder: búsqueda de archivos +**************************** .[perex] -¿Necesitas encontrar archivos que coincidan con una determinada máscara? El Finder puede ayudarte. Es una herramienta versátil y rápida para navegar por la estructura de directorios. +¿Necesita encontrar archivos que coincidan con una máscara determinada? Finder le ayudará con eso. Es una herramienta versátil y rápida para navegar por la estructura de directorios. -Se instala: +Instalación: ```shell composer require nette/utils ``` -Los ejemplos suponen que se ha creado un alias: +Los ejemplos asumen que se ha creado un alias: ```php use Nette\Utils\Finder; ``` -Utilizando .[#toc-using] ------------------------- +Uso +--- -En primer lugar, vamos a ver cómo se puede utilizar [api:Nette\Utils\Finder] para listar los nombres de archivo con las extensiones `.txt` y `.md` en el directorio actual: +Primero, mostraremos cómo puede usar [api:Nette\Utils\Finder] para listar los nombres de los archivos con extensiones `.txt` y `.md` en el directorio actual: ```php foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { @@ -29,50 +29,51 @@ foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { } ``` -El directorio por defecto para la búsqueda es el directorio actual, pero puedes cambiarlo usando los métodos [in() o from() |#Where to search?]. -La variable `$file` es una instancia de la clase [FileInfo |#FileInfo] con muchos métodos útiles. La clave `$name` contiene la ruta al archivo como una cadena. +El directorio predeterminado para la búsqueda es el directorio actual, pero puede cambiarlo usando los métodos [in() o from() |#Dónde buscar]. La variable `$file` es una instancia de la clase [#FileInfo] con muchos métodos útiles. En la clave `$name` se encuentra la ruta al archivo como una cadena. -¿Qué buscar? .[#toc-what-to-search-for] ---------------------------------------- +¿Qué buscar? +------------ -Además del método `findFiles()`, también existe `findDirectories()`, que busca sólo en directorios, y `find()`, que busca en ambos. Estos métodos son estáticos, por lo que pueden invocarse sin crear una instancia. El parámetro máscara es opcional, si no se especifica, se busca en todo. +Además del método `findFiles()`, también existe `findDirectories()`, que busca solo directorios, y `find()`, que busca ambos. Estos métodos son estáticos, por lo que se pueden llamar sin crear una instancia. El parámetro con la máscara es opcional; si no lo especifica, se buscará todo. ```php foreach (Finder::find() as $file) { - echo $file; // ahora todos los archivos y directorios están listados + echo $file; // ahora se imprimirán todos los archivos y directorios } ``` -Utilice los métodos `files()` y `directories()` para añadir qué más buscar. Los métodos se pueden llamar repetidamente y se puede proporcionar una matriz de máscaras como parámetro: +Con los métodos `files()` y `directories()` puede especificar qué más buscar. Los métodos se pueden llamar repetidamente y como parámetro también se puede pasar un array de máscaras: ```php Finder::findDirectories('vendor') // todos los directorios ->files(['*.php', '*.phpt']); // además de todos los archivos PHP ``` -Una alternativa a los métodos estáticos es crear una instancia utilizando `new Finder` (el objeto fresco creado de esta manera no busca nada) y especificar qué buscar utilizando `files()` y `directories()`: +Una alternativa a los métodos estáticos es crear una instancia usando `new Finder` (un objeto recién creado de esta manera no busca nada) y especificar qué buscar usando `files()` y `directories()`: ```php (new Finder) - ->directories() // todos los directorios - ->files('*.php'); // además de todos los ficheros PHP + ->directories() // todos los directorios + ->files('*.php'); // además de todos los archivos PHP ``` -Puede utilizar [comodines |#wildcards] `*`, `**`, `?` and `[...]` en la máscara. Incluso puede especificar en directorios, por ejemplo `src/*.php` buscará todos los archivos PHP en el directorio `src`. +En la máscara puede usar los [#comodines] `*`, `**`, `?` y `[...]`. Incluso puede especificar directorios; por ejemplo, `src/*.php` buscará todos los archivos PHP en el directorio `src`. +Los enlaces simbólicos también se consideran directorios o archivos. -¿Dónde buscar? .[#toc-where-to-search] --------------------------------------- -El directorio de búsqueda por defecto es el directorio actual. Puede cambiarlo utilizando los métodos `in()` y `from()`. Como puedes ver por los nombres de los métodos, `in()` busca sólo en el directorio actual, mientras que `from()` busca también en sus subdirectorios (recursivamente). Si desea buscar recursivamente en el directorio actual, puede utilizar `from('.')`. +¿Dónde buscar? +-------------- -Estos métodos se pueden llamar varias veces o se les pueden pasar varias rutas como matrices, entonces se buscará en todos los directorios. Si uno de los directorios no existe, se lanza un `Nette\UnexpectedValueException`. +El directorio predeterminado para la búsqueda es el directorio actual. Puede cambiarlo usando los métodos `in()` y `from()`. Como se desprende de los nombres de los métodos, `in()` busca solo en el directorio especificado, mientras que `from()` busca también en sus subdirectorios (recursivamente). Si desea buscar recursivamente en el directorio actual, puede usar `from('.')`. + +Estos métodos se pueden llamar varias veces o pasarles varias rutas como un array; los archivos se buscarán entonces en todos los directorios. Si alguno de los directorios no existe, se lanzará una excepción `Nette\UnexpectedValueException`. ```php Finder::findFiles('*.php') ->in(['src', 'tests']) // busca directamente en src/ y tests/ - ->from('vendor'); // busca también en vendor/ subdirectorios + ->from('vendor'); // busca también en los subdirectorios de vendor/ ``` Las rutas relativas son relativas al directorio actual. Por supuesto, también se pueden especificar rutas absolutas: @@ -82,57 +83,57 @@ Finder::findFiles('*.php') ->in('/var/www/html'); ``` -Wildcards [comodines |#wildcards] `*`, `**`, `?` can be used in the path. For example, you can use the path `src/*/*.php` para buscar todos los archivos PHP en los directorios de segundo nivel en el directorio `src`. El carácter `**`, llamado globstar, es una poderosa carta de triunfo porque permite buscar también en subdirectorios: use `src/**/tests/*.php` para buscar todos los archivos PHP en el directorio `tests` ubicados en `src` o cualquiera de sus subdirectorios. +En la ruta es posible usar los [#comodines] `*`, `**`, `?`. Así, por ejemplo, con la ruta `src/*/*.php` puede buscar todos los archivos PHP en los directorios de segundo nivel dentro del directorio `src`. El carácter `**`, llamado globstar, es una baza poderosa, ya que permite buscar también en subdirectorios: con `src/**/tests/*.php` busca todos los archivos PHP en el directorio `tests` ubicado en `src` o en cualquiera de sus subdirectorios. -Por otro lado, los caracteres comodín `[...]` no están soportados en la ruta, es decir, no tienen un significado especial para evitar comportamientos no deseados en caso de que busque por ejemplo `in(__DIR__)` y por casualidad aparezcan caracteres `[]` en la ruta. +Por el contrario, los comodines `[...]` no están soportados en la ruta, es decir, no tienen un significado especial, para evitar comportamientos no deseados en caso de que busque, por ejemplo, `in(__DIR__)` y casualmente en la ruta aparezcan los caracteres `[]`. -Al buscar archivos y directorios en profundidad, se devuelve primero el directorio padre y luego los archivos que contiene, lo que puede invertirse con `childFirst()`. +Al buscar archivos y directorios en profundidad, primero se devuelve el directorio padre y luego los archivos contenidos en él, lo cual se puede invertir usando `childFirst()`. -Comodines .[#toc-wildcards] ---------------------------- +Comodines +--------- -Puede utilizar varios caracteres especiales en la máscara: +En la máscara puede usar varios caracteres especiales: -- `*` - replaces any number of arbitrary characters (except `/`) -- `**` - sustituye a cualquier número de caracteres arbitrarios, incluido `/` (es decir, puede buscarse en varios niveles) -- `?` - replaces one arbitrary character (except `/`) -- `[a-z]` - sustituye un carácter de la lista de caracteres entre corchetes -- `[!a-z]` - sustituye un carácter fuera de la lista de caracteres entre corchetes +- `*` - reemplaza cualquier número de caracteres cualesquiera (excepto `/`) +- `**` - reemplaza cualquier número de caracteres cualesquiera incluyendo `/` (es decir, se puede buscar en múltiples niveles) +- `?` - reemplaza un carácter cualquiera (excepto `/`) +- `[a-z]` - reemplaza un carácter de la lista de caracteres entre corchetes +- `[!a-z]` - reemplaza un carácter fuera de la lista de caracteres entre corchetes -Ejemplos de utilización: +Ejemplos de uso: -- `img/?.png` - archivos con el nombre de una sola letra `0.png`, `1.png`, `x.png`, etc. -- `logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log` - archivos de registro con el formato `YYYY-MM-DD` -- `src/**/tests/*` - ficheros en el directorio `src/tests`, `src/foo/tests`, `src/foo/bar/tests` etc. -- `docs/**.md` - todos los archivos con la extensión `.md` en todos los subdirectorios del directorio `docs` +- `img/?.png` - archivos con nombre de una sola letra `0.png`, `1.png`, `x.png`, etc. +- `logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log` - logs en formato `YYYY-MM-DD` +- `src/**/tests/*` - archivos en el directorio `src/tests`, `src/foo/tests`, `src/foo/bar/tests` y así sucesivamente. +- `docs/**.md` - todos los archivos con extensión `.md` en todos los subdirectorios del directorio `docs` -Excluyendo .[#toc-excluding] ----------------------------- +Exclusión +--------- -Utilice el método `exclude()` para excluir archivos y directorios de las búsquedas. Se especifica una máscara con la que el fichero no debe coincidir. Ejemplo de búsqueda de archivos `*.txt` excepto los que contienen la letra `X` en el nombre: +Con el método `exclude()` puede excluir archivos y directorios de la búsqueda. Especifica una máscara que el archivo no debe cumplir. Ejemplo de búsqueda de archivos `*.txt` excepto aquellos que contienen la letra `X` en el nombre: ```php Finder::findFiles('*.txt') ->exclude('*X*'); ``` -Utilice `exclude()` para omitir los subdirectorios buscados: +Para omitir la navegación por subdirectorios específicos, use `exclude()`: ```php Finder::findFiles('*.php') ->from($dir) - ->exclude('temp', '.git') + ->exclude('temp', '.git'); ``` -Filtrado .[#toc-filtering] --------------------------- +Filtrado +-------- -El Finder ofrece varios métodos para filtrar los resultados (es decir, reducirlos). Puede combinarlos y llamarlos repetidamente. +Finder ofrece varios métodos para filtrar los resultados (es decir, reducirlos). Puede combinarlos y llamarlos repetidamente. -Utilice `size()` para filtrar por tamaño de archivo. De este modo, encontraremos los ficheros con tamaños comprendidos entre 100 y 200 bytes: +Con `size()` filtramos por tamaño de archivo. Así encontramos archivos con un tamaño en el rango de 100 a 200 bytes: ```php Finder::findFiles('*.php') @@ -140,19 +141,19 @@ Finder::findFiles('*.php') ->size('<=', 200); ``` -El método `date()` filtra por la fecha de la última modificación del fichero. Los valores pueden ser absolutos o relativos a la fecha y hora actuales, por ejemplo, así se encuentran los ficheros modificados en las dos últimas semanas: +El método `date()` filtra por fecha de última modificación del archivo. Los valores pueden ser absolutos o relativos a la fecha y hora actuales; por ejemplo, así encontramos archivos modificados en las últimas dos semanas: ```php Finder::findFiles('*.php') ->date('>', '-2 weeks') - ->from($dir) + ->from($dir); ``` Ambas funciones entienden los operadores `>`, `>=`, `<`, `<=`, `=`, `!=`, `<>`. -El Finder también permite filtrar los resultados mediante funciones personalizadas. La función recibe un objeto `Nette\Utils\FileInfo` como parámetro y debe devolver `true` para incluir el archivo en los resultados. +Finder también permite filtrar los resultados usando funciones personalizadas. La función recibe como parámetro un objeto `Nette\Utils\FileInfo` y debe devolver `true` para que el archivo sea incluido en los resultados. -Ejemplo: buscar archivos PHP que contengan la cadena `Nette` (sin distinguir mayúsculas de minúsculas): +Ejemplo: búsqueda de archivos PHP que contienen la cadena `Nette` (independientemente de mayúsculas/minúsculas): ```php Finder::findFiles('*.php') @@ -160,51 +161,51 @@ Finder::findFiles('*.php') ``` -Filtrado en profundidad .[#toc-depth-filtering] ------------------------------------------------ +Filtrado en profundidad +----------------------- -Al realizar búsquedas recursivas, puedes establecer la profundidad máxima de rastreo utilizando el método `limitDepth()`. Si establece `limitDepth(1)`, sólo se rastrean los primeros subdirectorios, `limitDepth(0)` desactiva el rastreo en profundidad y un valor de -1 anula el límite. +Durante la búsqueda recursiva, puede establecer la profundidad máxima de navegación usando el método `limitDepth()`. Si establece `limitDepth(1)`, solo se navega por los primeros subdirectorios; `limitDepth(0)` desactiva la navegación en profundidad y el valor -1 cancela el límite. -El Finder permite utilizar sus propias funciones para decidir en qué directorio se entra al navegar. La función recibe un objeto `Nette\Utils\FileInfo` como parámetro y debe devolver `true` para entrar en el directorio: +Finder permite, usando funciones personalizadas, decidir en qué directorio entrar durante la navegación. La función recibe como parámetro un objeto `Nette\Utils\FileInfo` y debe devolver `true` para entrar en el directorio: ```php Finder::findFiles('*.php') - ->descentFilter($file->getBasename() !== 'temp'); + ->descentFilter(fn($file) => $file->getBasename() !== 'temp'); ``` -Ordenar .[#toc-sorting] ------------------------ +Ordenación +---------- -El Buscador también ofrece varias funciones para ordenar los resultados. +Finder también ofrece varias funciones para ordenar los resultados. -El método `sortByName()` ordena los resultados por nombre de archivo. La ordenación es natural, es decir, maneja correctamente los números de los nombres y devuelve, por ejemplo, `foo1.txt` antes que `foo10.txt`. +El método `sortByName()` ordena los resultados por nombres de archivo. La ordenación es natural, es decir, maneja correctamente los números en los nombres y devuelve, por ejemplo, `foo1.txt` antes de `foo10.txt`. -El Finder también permite ordenar utilizando una función personalizada. Toma dos objetos `Nette\Utils\FileInfo` como parámetros y debe devolver el resultado de la comparación con el operador `<=>`es decir, `-1`, `0` nebo `1`. Por ejemplo, así es como se ordenan los archivos por tamaño: +Finder también permite ordenar usando una función personalizada. Esta recibe como parámetro dos objetos `Nette\Utils\FileInfo` y debe devolver el resultado de la comparación con el operador `<=>`, es decir, `-1`, `0` o `1`. Por ejemplo, así ordenamos los archivos por tamaño: ```php $finder->sortBy(fn($a, $b) => $a->getSize() <=> $b->getSize()); ``` -Múltiples búsquedas diferentes .[#toc-multiple-different-searches] ------------------------------------------------------------------- +Múltiples búsquedas diferentes +------------------------------ -Si necesita encontrar varios archivos diferentes en distintas ubicaciones o que cumplan distintos criterios, utilice el método `append()`. Devuelve un nuevo objeto `Finder` para que pueda encadenar llamadas a métodos: +Si necesita encontrar varios archivos diferentes en diferentes ubicaciones o que cumplan otros criterios, use el método `append()`. Devuelve un nuevo objeto `Finder`, por lo que es posible encadenar llamadas a métodos: ```php -($finder = new Finder) // ¡almacena el primer Finder en la variable $finder! - ->files('*.php') // busca archivos *.php en src/ +($finder = new Finder) // ¡guardamos el primer Finder en la variable $finder! + ->files('*.php') // en src/ buscamos archivos *.php ->from('src') ->append() - ->files('*.md') // en docs/ busca archivos *.md + ->files('*.md') // en docs/ buscamos archivos *.md ->from('docs') ->append() - ->files('*.json'); // en la carpeta actual busca archivos *.json + ->files('*.json'); // en la carpeta actual buscamos archivos *.json ``` -Alternativamente, puede utilizar el método `append()` para añadir un archivo específico (o una matriz de archivos). Entonces devuelve el mismo objeto `Finder`: +Alternativamente, se puede usar el método `append()` para añadir un archivo específico (o un array de archivos). Entonces devuelve el mismo objeto `Finder`: ```php $finder = Finder::findFiles('*.txt') @@ -212,12 +213,12 @@ $finder = Finder::findFiles('*.txt') ``` -FileInfo .[#toc-fileinfo] -------------------------- +FileInfo +-------- -[Nette\Utils\FileInfo |api:] es una clase que representa un fichero o directorio en los resultados de búsqueda. Es una extensión de la clase [SplFileInfo |php:SplFileInfo] que proporciona información como el tamaño del fichero, fecha de última modificación, nombre, ruta, etc. +[Nette\Utils\FileInfo |api:] es una clase que representa un archivo o directorio en los resultados de la búsqueda. Es una extensión de la clase [SplFileInfo |php:SplFileInfo], que proporciona información como el tamaño del archivo, fecha de última modificación, nombre, ruta, etc. -Además, proporciona métodos para devolver rutas relativas, lo cual es útil cuando se navega en profundidad: +Además, proporciona métodos para devolver la ruta relativa, lo cual es útil durante la navegación en profundidad: ```php foreach (Finder::findFiles('*.jpg')->from('.') as $file) { @@ -226,24 +227,24 @@ foreach (Finder::findFiles('*.jpg')->from('.') as $file) { } ``` -También dispone de métodos para leer y escribir el contenido de un fichero: +Además, tiene disponibles métodos para leer y escribir el contenido del archivo: ```php -foreach ($buscador como $archivo) { - $contenidos = $file->read(); +foreach ($finder as $file) { + $contents = $file->read(); // ... $file->write($contents); } ``` -Devolución de resultados como array .[#toc-returning-results-as-an-array] -------------------------------------------------------------------------- +Devolución de resultados como array +----------------------------------- -Como se ha visto en los ejemplos, el Finder implementa la interfaz `IteratorAggregate`, así que puedes usar `foreach` para navegar por los resultados. Está programado para que los resultados sólo se carguen mientras navegas, así que si tienes un gran número de archivos, no espera a que se lean todos. +Como se ha visto en los ejemplos, Finder implementa la interfaz `IteratorAggregate`, por lo que puede usar `foreach` para recorrer los resultados. Está programado de tal manera que los resultados se cargan solo durante el recorrido, por lo que si tiene una gran cantidad de archivos, no se espera a que se lean todos. -También puede obtener los resultados como una matriz de objetos `Nette\Utils\FileInfo`, utilizando el método `collect()`. La matriz no es asociativa, sino numérica. +También puede obtener los resultados como un array de objetos `Nette\Utils\FileInfo`, usando el método `collect()`. El array no es asociativo, sino numérico. ```php -$array = $finder->findFiles('*.php')->collect(); +$array = Finder::findFiles('*.php')->collect(); ``` diff --git a/utils/es/floats.texy b/utils/es/floats.texy index 71989a6346..4290959da7 100644 --- a/utils/es/floats.texy +++ b/utils/es/floats.texy @@ -1,8 +1,8 @@ -Funciones flotantes +Trabajar con floats ******************* .[perex] -[api:Nette\Utils\Floats] es una clase estática con funciones útiles para comparar números flotantes. +[api:Nette\Utils\Floats] es una clase estática con funciones útiles para comparar números de punto flotante (floats). Instalación: @@ -11,18 +11,17 @@ Instalación: composer require nette/utils ``` -Todos los ejemplos asumen que el siguiente alias de clase está definido: +Todos los ejemplos asumen que se ha creado un alias: ```php use Nette\Utils\Floats; ``` -Motivación .[#toc-motivation] -============================= +Motivación +========== -¿Te preguntas para qué sirve una clase de comparación float? Puedes utilizar los operadores `<`, `>`, `===`, pensará usted. -Esto no es del todo cierto. ¿Qué crees que imprimirá este código? +Se preguntará, ¿por qué una clase para comparar floats? Después de todo, puedo usar los operadores `<`, `>`, `===` y ya está. No es del todo cierto. ¿Qué cree que imprimirá este código? ```php $a = 0.1 + 0.2; @@ -30,11 +29,11 @@ $b = 0.3; echo $a === $b ? 'same' : 'not same'; ``` -Si ejecuta el código, algunos de ustedes se sorprenderá de que el programa impreso `not same`. +Si ejecuta el código, algunos de ustedes seguramente se sorprenderán de que el programa imprima `not same`. -Las operaciones matemáticas con números flotantes provocan errores debidos a la conversión entre los sistemas decimal y binario. Por ejemplo `0.1 + 0.2` equivale a `0.300000000000000044…`. Por lo tanto, al comparar números flotantes, debemos tolerar una pequeña diferencia a partir de un determinado decimal. +Al realizar operaciones matemáticas con números decimales, se producen errores debido a la conversión entre los sistemas decimal y binario. Por ejemplo, `0.1 + 0.2` da como resultado `0.300000000000000044…`. Por lo tanto, al comparar, debemos tolerar una pequeña diferencia a partir de una cierta posición decimal. -Y eso es lo que hace la clase `Floats`. La siguiente comparación funcionará como se espera: +Y eso es exactamente lo que hace la clase `Floats`. La siguiente comparación funcionará como se esperaba: ```php echo Floats::areEqual($a, $b) ? 'same' : 'not same'; // same @@ -42,9 +41,12 @@ echo Floats::areEqual($a, $b) ? 'same' : 'not same'; // same Al intentar comparar `NAN`, lanza una excepción `\LogicException`. +.[tip] +La clase `Floats` tolera diferencias menores que `1e-10`. Si necesita trabajar con mayor precisión, utilice en su lugar la [librería BCMath|https://www.php.net/manual/en/book.bc.php]. -Comparación de flotadores .[#toc-float-comparison] -================================================== + +Comparación de floats +===================== areEqual(float $a, float $b): bool .[method] @@ -104,25 +106,25 @@ Floats::isGreaterThanOrEqualTo(10.2, 10.2); // true compare(float $a, float $b): int .[method] ------------------------------------------ -Si `$a` < `$b`, devuelve `-1`, si son iguales devuelve `0` and if `$a` > `$b` devuelve `1`. +Si `$a` < `$b`, devuelve `-1`; si son iguales, devuelve `0`; y si `$a` > `$b`, devuelve `1`. -Puede utilizarse, por ejemplo, con la función `usort`. +Se puede usar, por ejemplo, con la función `usort`. ```php $arr = [1, 5, 2, -3.5]; -usort($arr, [Float::class, 'compare']); -// $arr is [-3.5, 1, 2, 5] +usort($arr, [Floats::class, 'compare']); +// $arr es ahora [-3.5, 1, 2, 5] ``` -Funciones de ayuda .[#toc-helpers-functions] -============================================ +Funciones auxiliares +==================== isZero(float $value): bool .[method] ------------------------------------ -Devuelve `true` si el valor es cero. +Devuelve `true` si el valor es igual a cero. ```php Floats::isZero(0.0); // true @@ -133,7 +135,7 @@ Floats::isZero(0); // true isInteger(float $value): bool .[method] --------------------------------------- -Devuelve `true` si el valor es entero. +Devuelve `true` si el valor es un número entero. ```php Floats::isInteger(0); // true diff --git a/utils/es/helpers.texy b/utils/es/helpers.texy index 3c3480c75a..62e3c74e56 100644 --- a/utils/es/helpers.texy +++ b/utils/es/helpers.texy @@ -11,7 +11,7 @@ Instalación: composer require nette/utils ``` -Todos los ejemplos asumen que el siguiente alias de clase está definido: +Todos los ejemplos asumen que se ha creado un alias: ```php use Nette\Utils\Helpers; @@ -21,7 +21,7 @@ use Nette\Utils\Helpers; capture(callable $cb): string .[method] --------------------------------------- -Ejecuta una llamada de retorno y devuelve la salida capturada como una cadena. +Ejecuta el callback y devuelve la salida capturada como una cadena. ```php $res = Helpers::capture(function () use ($template) { @@ -33,7 +33,7 @@ $res = Helpers::capture(function () use ($template) { clamp(int|float $value, int|float $min, int|float $max): int|float .[method] ---------------------------------------------------------------------------- -Devuelve el valor ajustado al rango inclusivo de min y max. +Limita el valor al rango inclusivo dado de min y max. ```php Helpers::clamp($level, 0, 255); @@ -43,8 +43,7 @@ Helpers::clamp($level, 0, 255); compare(mixed $left, string $operator, mixed $right): bool .[method] -------------------------------------------------------------------- -Compara dos valores de la misma forma que PHP. Distingue entre los operadores `>`, `>=`, `<`, `<=`, `=`, `==`, `===`, `!=`, `!==`, `<>`. -La función es útil en situaciones en las que el operador es variable. +Compara dos valores de la misma manera que lo hace PHP. Distingue los operadores `>`, `>=`, `<`, `<=`, `=`, `==`, `===`, `!=`, `!==`, `<>`. La función es útil en situaciones donde el operador es variable. ```php Helpers::compare(10, '<', 20); // true @@ -65,7 +64,7 @@ Helpers::falseToNull(123); // 123 getLastError(): string .[method] -------------------------------- -Devuelve el último error PHP ocurrido o una cadena vacía si no ocurrió ningún error. A diferencia de `error_get_last()`, no se ve afectado por la directiva de PHP `html_errors` y siempre devuelve texto, no HTML. +Devuelve el último error en PHP o una cadena vacía si no ocurrió ningún error. A diferencia de `error_get_last()`, no se ve afectado por la directiva `html_errors` de PHP y siempre devuelve texto, no HTML. ```php Helpers::getLastError(); @@ -75,13 +74,13 @@ Helpers::getLastError(); getSuggestion(string[] $possibilities, string $value): ?string .[method] ------------------------------------------------------------------------ -Busca una cadena de `$possibilities` que sea lo más similar a `$value`, pero no la misma. Sólo admite codificaciones de 8 bits. +De las opciones ofrecidas en `$possibilities`, busca la cadena que sea más similar a `$value`, pero no idéntica. Solo admite codificación de 8 bits. -Es útil si una determinada opción no es válida y queremos sugerir al usuario una similar (pero diferente, por lo que se ignora la misma cadena). De esta forma, Nette crea los mensajes `did you mean ...?`. +Es útil cuando una opción determinada no es válida y queremos sugerir al usuario una similar (pero diferente, por eso se ignora la cadena idéntica). De esta manera, Nette crea los mensajes `¿quisiste decir ...?`. ```php $items = ['foo', 'bar', 'baz']; Helpers::getSuggestion($items, 'fo'); // 'foo' Helpers::getSuggestion($items, 'barr'); // 'bar' -Helpers::getSuggestion($items, 'baz'); // 'bar', ne 'baz' +Helpers::getSuggestion($items, 'baz'); // 'bar', no 'baz' ``` diff --git a/utils/es/html-elements.texy b/utils/es/html-elements.texy index 553f1c2cdf..fd86140f40 100644 --- a/utils/es/html-elements.texy +++ b/utils/es/html-elements.texy @@ -2,15 +2,15 @@ Elementos HTML ************** .[perex] -La clase [api:Nette\Utils\Html] es una ayuda para generar código HTML que previene la vulnerabilidad Cross Site Scripting (XSS). +La clase [api:Nette\Utils\Html] es un ayudante para generar código HTML que previene la vulnerabilidad de Cross Site Scripting (XSS). -Funciona de tal forma que sus objetos representan elementos HTML, establecemos sus parámetros y dejamos que se rendericen: +Funciona de tal manera que sus objetos representan elementos HTML, a los que establecemos parámetros y dejamos que se rendericen: ```php -$el = Html::el('img'); // crea el elemento . -$el->src = 'image.jpg'; // establece el atributo src -echo $el; // imprime '' +$el = Html::el('img'); // Crea el elemento +$el->src = 'image.jpg'; // Establece el atributo src +echo $el; // Imprime '' ``` Instalación: @@ -19,29 +19,29 @@ Instalación: composer require nette/utils ``` -Todos los ejemplos asumen que el siguiente alias de clase está definido: +Todos los ejemplos asumen que se ha creado un alias: ```php use Nette\Utils\Html; ``` -Creación de un elemento HTML .[#toc-creating-an-html-element] -============================================================= +Creación de elementos HTML +========================== -El elemento se crea utilizando el método `Html::el()`: +Creamos un elemento con el método `Html::el()`: ```php -$el = Html::el('img'); // crea el elemento +$el = Html::el('img'); // Crea el elemento ``` -Además del nombre, puede introducir otros atributos en la sintaxis HTML: +Además del nombre, también puede especificar otros atributos en sintaxis HTML: ```php $el = Html::el('input type=text class="red important"'); ``` -O pasarlos como un array asociativo al segundo parámetro: +O pasarlos como un array asociativo en el segundo parámetro: ```php $el = Html::el('input', [ @@ -50,39 +50,39 @@ $el = Html::el('input', [ ]); ``` -Para cambiar y devolver el nombre de un elemento: +Cambiar y devolver el nombre del elemento: ```php $el->setName('img'); $el->getName(); // 'img' -$el->isEmpty(); // true, as is void element +$el->isEmpty(); // true, ya que es un elemento vacío ``` -Atributos HTML .[#toc-html-attributes] -====================================== +Atributos HTML +============== -Puedes establecer y obtener atributos HTML individuales de tres maneras, depende de quién te guste más. La primera es a través de las propiedades: +Podemos cambiar y leer atributos HTML individuales de tres maneras, depende de usted cuál le guste más. La primera es a través de propiedades: ```php -$el->src = 'image.jpg'; // establece el atributo src +$el->src = 'image.jpg'; // Establece el atributo src echo $el->src; // 'image.jpg' -unset($el->src); // elimina el atributo -// or $el->src = null; +unset($el->src); // Elimina el atributo +// O $el->src = null; ``` -La segunda forma es llamar a métodos que, a diferencia de establecer propiedades, podemos encadenar: +La segunda forma es llamar a métodos, que a diferencia de establecer propiedades, podemos encadenar: ```php $el = Html::el('img')->src('image.jpg')->alt('photo'); // photo -$el->alt(null); // elimina el atributo +$el->alt(null); // Eliminación del atributo ``` -Y la tercera forma es la más locuaz: +Y la tercera forma es la más verbosa: ```php $el = Html::el('img') @@ -94,9 +94,9 @@ echo $el->getAttribute('src'); // 'image.jpg' $el->removeAttribute('alt'); ``` -En bloque, los atributos pueden fijarse con `addAttributes(array $attrs)` y borrarse con `removeAttributes(array $attrNames)`. +Los atributos se pueden establecer en masa usando `addAttributes(array $attrs)` y eliminar usando `removeAttributes(array $attrNames)`. -El valor de un atributo no tiene por qué ser sólo una cadena, también pueden utilizarse valores lógicos para atributos lógicos: +El valor de un atributo no tiene que ser solo una cadena, también se pueden usar valores booleanos para atributos booleanos: ```php $checkbox = Html::el('input')->type('checkbox'); @@ -104,17 +104,17 @@ $checkbox->checked = true; // $checkbox->checked = false; // ``` -Un atributo también puede ser una matriz de tokens, que se enumeran separados por espacios, lo que es adecuado para las clases CSS, por ejemplo: +Un atributo también puede ser un array de valores, que se imprimirán separados por espacios, lo cual es útil, por ejemplo, para clases CSS: ```php $el = Html::el('input'); $el->class[] = 'active'; -$el->class[] = null; // se ignora null +$el->class[] = null; // null se ignora $el->class[] = 'top'; echo $el; // '' ``` -Una alternativa es un array asociativo, donde los valores dicen si la clave debe ser listada: +Una alternativa es un array asociativo, donde los valores indican si la clave debe imprimirse: ```php $el = Html::el('input'); @@ -123,7 +123,7 @@ $el->class['top'] = false; echo $el; // '' ``` -Los estilos CSS pueden escribirse en forma de matrices asociativas: +Los estilos CSS se pueden escribir en forma de arrays asociativos: ```php $el = Html::el('input'); @@ -132,7 +132,7 @@ $el->style['display'] = 'block'; echo $el; // '' ``` -Ahora hemos utilizado propiedades, pero se puede hacer lo mismo utilizando los métodos: +Ahora hemos usado propiedades, pero lo mismo se puede escribir usando métodos: ```php $el = Html::el('input'); @@ -141,7 +141,7 @@ $el->style('display', 'block'); echo $el; // '' ``` -O incluso de la forma más locuaz: +O incluso de la manera más verbosa: ```php $el = Html::el('input'); @@ -150,7 +150,7 @@ $el->appendAttribute('style', 'display', 'block'); echo $el; // '' ``` -Una última cosa: el método `href()` puede facilitar la composición de los parámetros de consulta en una URL: +Un pequeño detalle para terminar: el método `href()` puede facilitar la composición de parámetros de consulta en una URL: ```php echo Html::el('a')->href('index.php', [ @@ -161,31 +161,31 @@ echo Html::el('a')->href('index.php', [ ``` -Atributos de datos .[#toc-data-attributes] ------------------------------------------- +Atributos de datos +------------------ -Los atributos de datos tienen un soporte especial. Debido a que sus nombres contienen guiones, el acceso a través de propiedades y métodos no es tan elegante, por lo que existe un método `data()`: +Los atributos de datos (`data-*`) tienen soporte especial. Debido a que sus nombres contienen guiones, el acceso a través de propiedades y métodos no es tan elegante, por eso existe el método `data()`: ```php $el = Html::el('input'); -$el->{'data-max-size'} = '500x300'; // not so elegant -$el->data('max-size', '500x300'); // is elegant +$el->{'data-max-size'} = '500x300'; // No es tan elegante +$el->data('max-size', '500x300'); // Es elegante echo $el; // '' ``` -Si el valor del atributo de datos es un array, se serializa automáticamente a JSON: +Si el valor de un atributo de datos es un array, se serializa automáticamente a JSON: ```php $el = Html::el('input'); -$el->data('items', [1,2,3]); +$el->data('items', [1, 2, 3]); echo $el; // '' ``` -Contenido del elemento .[#toc-element-content] -============================================== +Contenido del elemento +====================== -El contenido interno del elemento se establece mediante los métodos `setHtml()` o `setText()`. Utilice el primero sólo si sabe que está pasando de forma fiable una cadena HTML segura en el parámetro. +Establecemos el contenido interno del elemento con los métodos `setHtml()` o `setText()`. Use el primero solo si sabe que está pasando una cadena HTML confiablemente segura en el parámetro. ```php echo Html::el('span')->setHtml('hello
                                                                                                                            '); @@ -195,7 +195,7 @@ echo Html::el('span')->setText('10 < 20'); // '10 < 20' ``` -Por el contrario, el contenido interno se obtiene mediante los métodos `getHtml()` o `getText()`. El segundo elimina las etiquetas de la salida HTML y convierte las entidades HTML en caracteres. +Y viceversa, obtenemos el contenido interno con los métodos `getHtml()` o `getText()`. El segundo elimina las etiquetas HTML de la salida y convierte las entidades HTML en caracteres. ```php echo $el->getHtml(); // '10 < 20' @@ -203,10 +203,10 @@ echo $el->getText(); // '10 < 20' ``` -Nodos hijos .[#toc-child-nodes] -------------------------------- +Nodos hijos +----------- -El contenido interno de un elemento también puede ser un array de hijos. Cada uno de ellos puede ser una cadena u otro elemento de `Html`. Se insertan utilizando `addHtml()` o `addText()`: +El interior de un elemento también puede ser un array de nodos hijos (children). Cada uno de ellos puede ser una cadena u otro elemento `Html`. Los insertamos usando `addHtml()` o `addText()`: ```php $el = Html::el('span') @@ -219,13 +219,13 @@ $el = Html::el('span') Otra forma de crear e insertar un nuevo nodo `Html`: ```php -$el = Html::el('ul') - ->create('li', ['class' => 'first']) - ->setText('hello'); -//
                                                                                                                            • hello
                                                                                                                            +$ul = Html::el('ul'); +$ul->create('li', ['class' => 'first']) + ->setText('první'); +//
                                                                                                                            • první
                                                                                                                            ``` -Puedes trabajar con nodos como si fueran elementos de un array. Así que accede a los individuales usando corchetes, cuéntalos con `count()` e itera sobre ellos: +Se puede trabajar con los nodos como si fueran un array. Es decir, acceder a ellos individualmente usando corchetes, contarlos usando `count()` e iterar sobre ellos: ```php $el = Html::el('div'); @@ -238,20 +238,20 @@ foreach ($el as $child) { /* ... */ } echo count($el); // 2 ``` -Se puede insertar un nuevo nodo en una posición específica utilizando `insert(?int $index, $child, bool $replace = false)`. Si `$replace = false`, inserta el elemento en la posición `$index` y desplaza los demás. Si `$index = null`, añadirá un elemento al final. +Se puede insertar un nuevo nodo en una ubicación específica usando `insert(?int $index, $child, bool $replace = false)`. Si `$replace = false`, inserta el elemento en la posición `$index` y desplaza los demás. Si `$index = null`, añade el elemento al final. ```php -// inserta el elemento en la primera posición y avanza los demás +// Inserta el elemento en la primera posición y desplaza los demás $el->insert(0, Html::el('span')); ``` -Todos los nodos son devueltos por el método `getChildren()` y eliminados por el método `removeChildren()`. +Obtenemos todos los nodos con el método `getChildren()` y los eliminamos con el método `removeChildren()`. -Creación de un fragmento de documento .[#toc-creating-a-document-fragment] --------------------------------------------------------------------------- +Creación de fragmento de documento +---------------------------------- -Si desea trabajar con una matriz de nodos y no está interesado en el elemento envolvente, puede crear un llamado *fragmento de documento* pasando `null` en lugar del nombre del elemento: +Si queremos trabajar con un array de nodos y no nos interesa el elemento contenedor, podemos crear un llamado *fragmento de documento* pasando `null` en lugar del nombre del elemento: ```php $el = Html::el(null) @@ -261,7 +261,7 @@ $el = Html::el(null) // hello
                                                                                                                            10 < 20
                                                                                                                            ``` -Los métodos `fromHtml()` y `fromText()` ofrecen una forma más rápida de crear un fragmento: +Una forma más rápida de crear un fragmento la ofrecen los métodos `fromHtml()` y `fromText()`: ```php $el = Html::fromHtml('hello
                                                                                                                            '); @@ -272,15 +272,15 @@ echo $el; // '10 < 20' ``` -Generar salida HTML .[#toc-generating-html-output] -================================================== +Generación de salida HTML +========================= -La forma más sencilla de generar un elemento HTML es utilizar `echo` o lanzar un objeto a `(string)`. También puede imprimir etiquetas de apertura o cierre y atributos por separado: +La forma más sencilla de imprimir un elemento HTML es usar `echo` o convertir (cast) el objeto a `(string)`. También es posible imprimir por separado las etiquetas de apertura o cierre y los atributos: ```php $el = Html::el('div class=header')->setText('hello'); -echo $el; // '
                                                                                                                            ' +echo $el; // '
                                                                                                                            hello
                                                                                                                            ' $s = (string) $el; // '
                                                                                                                            hello
                                                                                                                            ' $s = $el->toHtml(); // '
                                                                                                                            hello
                                                                                                                            ' $s = $el->toText(); // 'hello' @@ -289,7 +289,7 @@ echo $el->endTag(); // '' echo $el->attributes(); // 'class="header"' ``` -Una característica importante es la protección automática contra [Cross Site Scripting (XSS) |nette:glossary#cross-site-scripting-xss]. Todos los valores de atributos o contenidos insertados mediante `setText()` o `addText()` se escapan de forma fiable: +Una característica importante es la protección automática contra [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS]. Todos los valores de atributos o contenido insertado a través de `setText()` o `addText()` se escapan de forma fiable: ```php echo Html::el('div') @@ -300,17 +300,17 @@ echo Html::el('div') ``` -Conversión HTML ↔ Texto .[#toc-conversion-html-text] -==================================================== +Conversión HTML ↔ texto +======================= -Puede utilizar el método estático `htmlToText()` para convertir HTML en texto: +Para convertir HTML a texto, puede utilizar el método estático `htmlToText()`: ```php echo Html::htmlToText('One & Two'); // 'One & Two' ``` -HtmlStringable .[#toc-htmlstringable] -===================================== +HtmlStringable +============== -El objeto `Nette\Utils\Html` implementa la interfaz `Nette\HtmlStringable`, que, por ejemplo, Latte o forms utilizan para distinguir los objetos que tienen un método `__toString()` que devuelve código HTML. Así, el doble escape no se produce si, por ejemplo, imprimimos el objeto en la plantilla utilizando `{$el}`. +El objeto `Nette\Utils\Html` implementa la interfaz `Nette\HtmlStringable`, que Latte o los formularios, por ejemplo, utilizan para distinguir objetos que tienen un método `__toString()` que devuelve código HTML. De esta forma, no habrá doble escape si, por ejemplo, imprimimos el objeto en una plantilla usando `{$el}`. diff --git a/utils/es/images.texy b/utils/es/images.texy index 2f9f140d87..59db0b070e 100644 --- a/utils/es/images.texy +++ b/utils/es/images.texy @@ -1,11 +1,11 @@ -Funciones de imagen -******************* +Trabajando con imágenes +*********************** .[perex] -La clase [api:Nette\Utils\Image] simplifica la manipulación de imágenes, como redimensionar, recortar, enfocar, dibujar o fusionar varias imágenes. +La clase [api:Nette\Utils\Image] simplifica la manipulación de imágenes, como el cambio de tamaño, recorte, enfoque, dibujo o la combinación de múltiples imágenes. -PHP dispone de un amplio conjunto de funciones para manipular imágenes. Pero la API no es muy agradable. No sería un Neat Framework para inventar una API sexy. +PHP tiene un amplio conjunto de funciones para la manipulación de imágenes, pero su API no es muy cómoda. Nette Framework no sería Nette Framework si no ofreciera una API atractiva. Instalación: @@ -13,26 +13,28 @@ Instalación: composer require nette/utils ``` -Los siguientes ejemplos suponen que se ha definido el siguiente alias de clase: +Todos los ejemplos asumen que se ha creado un alias: ```php use Nette\Utils\Image; +use Nette\Utils\ImageColor; +use Nette\Utils\ImageType; ``` -Crear una imagen .[#toc-creating-an-image] -========================================== +Creación de una imagen +====================== -Crearemos una nueva imagen en color verdadero, por ejemplo con unas dimensiones de 100×200: +Creamos una nueva imagen true color, por ejemplo, con dimensiones de 100×200: ```php $image = Image::fromBlank(100, 200); ``` -Opcionalmente, puede especificar un color de fondo (por defecto es negro): +Opcionalmente, se puede especificar el color de fondo (el predeterminado es negro): ```php -$image = Image::fromBlank(100, 200, Image::rgb(125, 0, 0)); +$image = Image::fromBlank(100, 200, ImageColor::rgb(125, 0, 0)); ``` O cargamos la imagen desde un archivo: @@ -41,129 +43,138 @@ O cargamos la imagen desde un archivo: $image = Image::fromFile('nette.jpg'); ``` -Los formatos soportados son JPEG, PNG, GIF, WebP, AVIF y BMP, pero su versión de PHP también debe soportarlos (compruebe la `phpinfo()`, sección GD). No se admiten animaciones. -¿Necesita detectar el formato de la imagen al cargarla? El método devuelve el formato en el segundo parámetro: +Guardado de la imagen +===================== + +La imagen se puede guardar en un archivo: ```php -$image = Image::fromFile('nette.jpg', $type); -// $type es Image::JPEG, Image::PNG, Image::GIF, Image::WEBP, Image::AVIF o Image::BMP +$image->save('resampled.jpg'); ``` -Sólo la detección sin carga de la imagen se realiza mediante `Image::detectTypeFromFile()`. +Podemos especificar la calidad de compresión en el rango de 0..100 para JPEG (por defecto 85), WEBP (por defecto 80) y AVIF (por defecto 30) y 0..9 para PNG (por defecto 9): +```php +$image->save('resampled.jpg', 80); // JPEG, calidad 80% +``` -Guardar la imagen .[#toc-save-the-image] -======================================== - -La imagen puede guardarse en un archivo: +Si el formato no es evidente por la extensión del archivo, se puede especificar mediante una [constante |#Formatos]: ```php -$image->save('resampled.jpg'); +$image->save('resampled.tmp', null, ImageType::JPEG); ``` -Podemos especificar la calidad de compresión en el rango 0..100 para JPEG (por defecto 85), WEBP (por defecto 80) y AVIF (por defecto 30) y 0..9 para PNG (por defecto 9): +La imagen se puede escribir en una variable en lugar de en el disco: ```php -$image->save('resampled.jpg', 80); // JPEG, quality 80% +$data = $image->toString(ImageType::JPEG, 80); // JPEG, calidad 80% ``` -Si el formato no resulta obvio a partir de la extensión del archivo, puede especificarse mediante una de las constantes `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` y `Image::BMP`: +o enviar directamente al navegador con la cabecera HTTP `Content-Type` apropiada: ```php -$image->save('resampled.tmp', null, Image::JPEG); +// envía la cabecera Content-Type: image/png +$image->send(ImageType::PNG); ``` -La imagen puede escribirse en una variable en lugar de en el disco: + +Formatos +======== + +Los formatos soportados son JPEG, PNG, GIF, WebP, AVIF y BMP; sin embargo, su versión de PHP también debe soportarlos, lo cual puede verificar con la función [#isTypeSupported()]. Las animaciones no son soportadas. + +El formato está representado por las constantes `ImageType::JPEG`, `ImageType::PNG`, `ImageType::GIF`, `ImageType::WEBP`, `ImageType::AVIF` y `ImageType::BMP`. ```php -$data = $image->toString(Image::JPEG, 80); // JPEG, quality 80% +$supported = Image::isTypeSupported(ImageType::JPEG); ``` -o enviar directamente al navegador con el encabezado HTTP apropiado `Content-Type`: +¿Necesita detectar el formato de la imagen al cargarla? El método lo devolverá en el segundo parámetro: ```php -// sends header Content-Type: image/png -$image->send(Image::PNG); +$image = Image::fromFile('nette.jpg', $type); ``` +La detección en sí, sin cargar la imagen, la realiza `Image::detectTypeFromFile()`. + -Redimensionar imagen .[#toc-image-resize] -========================================= +Cambio de tamaño +================ -Una operación habitual es cambiar el tamaño de una imagen. Las dimensiones actuales son devueltas por los métodos `getWidth()` y `getHeight()`. +Una operación común es el cambio de tamaño de la imagen. Las dimensiones actuales son devueltas por los métodos `getWidth()` y `getHeight()`. -El método `resize()` se utiliza para cambiar el tamaño. Este es un ejemplo de cambio de tamaño proporcional para que no supere los 500×300 píxeles (o la anchura será exactamente 500px o la altura será exactamente 300px, una de las dimensiones se calcula para mantener la relación de aspecto): +El método `resize()` se utiliza para el cambio. Ejemplo de cambio de tamaño proporcional para que no exceda las dimensiones de 500x300 píxeles (o el ancho será exactamente 500 px o la altura será exactamente 300 px; una de las dimensiones se calcula para mantener la relación de aspecto): ```php $image->resize(500, 300); ``` -Es posible fijar sólo una dimensión y se calculará la segunda: +Es posible especificar solo una dimensión y la otra se calculará: ```php -$image->resize(500, null); // width 500px, height auto +$image->resize(500, null); // ancho 500px, la altura se calculará -$image->resize(null, 300); // width auto, height 300px +$image->resize(null, 300); // el ancho se calculará, altura 300px ``` -Cualquier dimensión puede especificarse en porcentajes: +Cualquier dimensión también se puede especificar en porcentajes: ```php $image->resize('75%', 300); // 75 % × 300px ``` -El comportamiento de `resize` puede ser influenciado por las siguientes banderas. Todos excepto `Image::Stretch` conservan la relación de aspecto. +El comportamiento de `resize` puede ser influenciado por las siguientes banderas. Todas excepto `Image::Stretch` mantienen la relación de aspecto. |--------------------------------------------------------------------------------------- -| Bandera Descripción +| Bandera | Descripción |--------------------------------------------------------------------------------------- -| `Image::OrSmaller` (por defecto) | las dimensiones resultantes serán menores o iguales a las especificadas -| `Image::OrBigger` | rellena el área objetivo y posiblemente la extiende en una dirección -| `Image::Cover` | rellena toda el área y corta lo que la sobrepasa -| `Image::ShrinkOnly` | sólo reduce la escala (no amplía una imagen pequeña) -| `Image::Stretch` | no mantiene la relación de aspecto +| `Image::OrSmaller` (predeterminado) | las dimensiones resultantes serán menores o iguales a las dimensiones solicitadas +| `Image::OrBigger` | llenará (y posiblemente excederá en una dimensión) el área de destino +| `Image::Cover` | llenará el área de destino y recortará lo que exceda +| `Image::ShrinkOnly` | solo reducción (evita estirar una imagen pequeña) +| `Image::Stretch` | no mantener la relación de aspecto -Las banderas se pasan como tercer argumento de la función: +Las banderas se especifican como el tercer argumento de la función: ```php $image->resize(500, 300, Image::OrBigger); ``` -Los indicadores pueden combinarse: +Las banderas se pueden combinar: ```php $image->resize(500, 300, Image::ShrinkOnly | Image::Stretch); ``` -Las imágenes pueden voltearse vertical u horizontalmente especificando una de las dimensiones (o ambas) como número negativo: +Las imágenes se pueden voltear vertical u horizontalmente especificando una de las dimensiones (o ambas) como un número negativo: ```php -$flipped = $image->resize(null, '-100%'); // flip vertical +$flipped = $image->resize(null, '-100%'); // voltear verticalmente -$flipped = $image->resize('-100%', '-100%'); // rotate by 180° +$flipped = $image->resize('-100%', '-100%'); // rotar 180° -$flipped = $image->resize(-125, 500); // resize & flip horizontal +$flipped = $image->resize(-125, 500); // redimensionar y voltear horizontalmente ``` -Después de reducir la imagen, podemos mejorarla mediante la nitidez: +Después de reducir el tamaño de la imagen, es posible mejorar su apariencia con un ligero enfoque: ```php $image->sharpen(); ``` -Cultivo .[#toc-cropping] -======================== +Recorte +======= -Para el cultivo se utiliza el método `crop()`: +El método `crop()` se utiliza para recortar: ```php $image->crop($left, $top, $width, $height); ``` -Al igual que en `resize()`, todos los valores pueden especificarse en porcentajes. Los porcentajes de `$left` y `$top` se calculan a partir del espacio restante, de forma similar a la propiedad CSS `background-position`: +Similar a `resize()`, todos los valores pueden especificarse en porcentajes. Los porcentajes para `$left` y `$top` se calculan a partir del espacio restante, similar a la propiedad CSS `background-position`: ```php $image->crop('100%', '50%', '80%', '80%'); @@ -172,106 +183,141 @@ $image->crop('100%', '50%', '80%', '80%'); [* crop.svg *] -La imagen también puede recortarse automáticamente, por ejemplo, recortando los bordes negros: +La imagen también se puede recortar automáticamente, por ejemplo, recortando los bordes negros: ```php $image->cropAuto(IMG_CROP_BLACK); ``` -El método `cropAuto()` es una encapsulación de objeto de la función `imagecropauto()`, consulte [su documentación |https://www.php.net/manual/en/function.imagecropauto] para obtener más información. +El método `cropAuto()` es un reemplazo orientado a objetos de la función `imagecropauto()`; en [su documentación|https://www.php.net/manual/en/function.imagecropauto] encontrará más información. + + +Colores .{data-version:4.0.2} +============================= + +El método `ImageColor::rgb()` le permite definir un color utilizando los valores rojo, verde y azul (RGB). Opcionalmente, también puede especificar un valor de transparencia en el rango de 0 (completamente transparente) a 1 (completamente opaco), igual que en CSS. + +```php +$color = ImageColor::rgb(255, 0, 0); // Rojo +$transparentBlue = ImageColor::rgb(0, 0, 255, 0.5); // Azul semitransparente +``` + +El método `ImageColor::hex()` le permite definir un color utilizando el formato hexadecimal, similar a CSS. Soporta los formatos `#rgb`, `#rrggbb`, `#rgba` y `#rrggbbaa`: +```php +$color = ImageColor::hex("#F00"); // Rojo +$transparentGreen = ImageColor::hex("#00FF0080"); // Verde semitransparente +``` + +Los colores se pueden usar en otros métodos, como `ellipse()`, `fill()`, etc. -Dibujo y edición .[#toc-drawing-and-editing] -============================================ -Puedes dibujar, puedes escribir, puedes usar todas las funciones PHP para trabajar con imágenes, como [imagefilledellipse() |https://www.php.net/manual/en/function.imagefilledellipse.php], pero usando el estilo objeto: +Dibujo y edición +================ + +Puede dibujar, puede escribir, pero no arranque las hojas (expresión idiomática checa). Todas las funciones de PHP para trabajar con imágenes están disponibles para usted, consulte [#Resumen de métodos], pero en una envoltura orientada a objetos: ```php -$image->filledEllipse($cx, $cy, $width, $height, Image::rgb(255, 0, 0, 63)); +$image->filledEllipse($centerX, $centerY, $width, $height, ImageColor::rgb(255, 0, 0)); ``` -Véase el [resumen de métodos |#Overview of Methods]. +Dado que las funciones de PHP para dibujar rectángulos no son prácticas debido a la especificación de coordenadas, la clase `Image` ofrece sus reemplazos en forma de funciones [#rectangleWH()] y [#filledRectangleWH()]. -Fusionar varias imágenes .[#toc-merge-multiple-images] -====================================================== +Combinación de múltiples imágenes +================================= -Puedes colocar fácilmente otra imagen dentro de la imagen: +Se puede insertar fácilmente otra imagen en la imagen: ```php $logo = Image::fromFile('logo.png'); -$blank = Image::fromBlank(320, 240, Image::rgb(52, 132, 210)); +$blank = Image::fromBlank(320, 240, ImageColor::rgb(52, 132, 210)); -// coordinates can be set also in percentage -$blank->place($logo, '80%', '80%'); // near the right bottom corner +// las coordenadas se pueden especificar nuevamente en porcentajes +$blank->place($logo, '80%', '80%'); // insertamos cerca de la esquina inferior derecha ``` -Al pegar, se respeta el canal alfa, además, podemos influir en la transparencia de la imagen insertada (crearemos una llamada marca de agua): +Al insertar, se respeta el canal alfa; además, podemos influir en la transparencia de la imagen insertada (creamos una llamada marca de agua): ```php -$blank->place($image, '80%', '80%', 25); // transparency is 25 % +$blank->place($logo, '80%', '80%', 25); // la transparencia es del 25 % ``` -Es un verdadero placer utilizar esta API, ¿verdad? +¡Usar una API así es realmente un placer! -Métodos .[#toc-overview-of-methods] -=================================== +Resumen de métodos +================== -static fromBlank(int $width, int $height, array $color=null): Image .[method] ------------------------------------------------------------------------------ -Crea una nueva imagen en color verdadero de las dimensiones dadas. El color por defecto es el negro. +static fromBlank(int $width, int $height, ?ImageColor $color=null): Image .[method] +----------------------------------------------------------------------------------- +Crea una nueva imagen true color de las dimensiones dadas. El color por defecto es negro. static fromFile(string $file, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------- -Lee una imagen de un archivo y devuelve su tipo en `$detectedFormat`. Los tipos admitidos son `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` y `Image::BMP`. +Carga una imagen desde un archivo y devuelve su [tipo |#Formatos] en `$detectedFormat`. static fromString(string $s, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------ -Lee una imagen de una cadena y devuelve su tipo en `$detectedFormat`. Los tipos admitidos son `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` y `Image::BMP`. +Carga una imagen desde una cadena y devuelve su [tipo |#Formatos] en `$detectedFormat`. -static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method] ---------------------------------------------------------------------------------- -Crea un color que puede utilizarse en otros métodos, como `ellipse()`, `fill()`, etc. +static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method][deprecated] +--------------------------------------------------------------------------------------------- +Esta función ha sido reemplazada por la clase `ImageColor`, consulte [#colores]. static typeToExtension(int $type): string .[method] --------------------------------------------------- -Devuelve la extensión de archivo para la constante `Image::XXX` dada. +Devuelve la extensión de archivo para el [tipo |#Formatos] dado. static typeToMimeType(int $type): string .[method] -------------------------------------------------- -Devuelve el tipo mime para la constante `Image::XXX` dada. +Devuelve el tipo MIME para el [tipo |#Formatos] dado. static extensionToType(string $extension): int .[method] -------------------------------------------------------- -Devuelve el tipo de imagen como una constante `Image::XXX` según la extensión del archivo. +Devuelve el [tipo |#Formatos] de imagen según la extensión del archivo. static detectTypeFromFile(string $file, int &$width=null, int &$height=null): ?int .[method] -------------------------------------------------------------------------------------------- -Devuelve el tipo de archivo de imagen como constante `Image::XXX` y en los parámetros `$width` y `$height` también sus dimensiones. +Devuelve el [tipo |#Formatos] de la imagen y también sus dimensiones en los parámetros `$width` y `$height`. static detectTypeFromString(string $s, int &$width=null, int &$height=null): ?int .[method] ------------------------------------------------------------------------------------------- -Devuelve el tipo de imagen a partir de la cadena como constante `Image::XXX` y en los parámetros `$width` y `$height` también sus dimensiones. +Devuelve el [tipo |#Formatos] de la imagen desde una cadena y también sus dimensiones en los parámetros `$width` y `$height`. -affine(array $affine, array $clip=null): Image .[method] --------------------------------------------------------- -Devuelve una imagen que contiene la imagen src transformada afinadamente, utilizando un área de recorte opcional. ([más |https://www.php.net/manual/en/function.imageaffine]). +static isTypeSupported(int $type): bool .[method] +------------------------------------------------- +Comprueba si el [tipo |#Formatos] de imagen dado es soportado. + + +static getSupportedTypes(): array .[method]{data-version:4.0.4} +--------------------------------------------------------------- +Devuelve un array de los [tipos |#Formatos] de imagen soportados. + + +static calculateTextBox(string $text, string $fontFile, float $size, float $angle=0, array $options=[]): array .[method] +------------------------------------------------------------------------------------------------------------------------ +Calcula las dimensiones del rectángulo que encierra el texto en una fuente y tamaño específicos. Devuelve un array asociativo que contiene las claves `left`, `top`, `width`, `height`. El borde izquierdo puede ser negativo si el texto comienza con un kerning izquierdo. + + +affine(array $affine, ?array $clip=null): Image .[method] +--------------------------------------------------------- +Devuelve una imagen que contiene la imagen `src` transformada afínmente utilizando un área de recorte opcional. ([más |https://www.php.net/manual/en/function.imageaffine]). affineMatrixConcat(array $m1, array $m2): array .[method] --------------------------------------------------------- -Devuelve la concatenación de dos matrices de transformación afín, lo que resulta útil si deben aplicarse múltiples transformaciones a la misma imagen de una sola vez. ([más |https://www.php.net/manual/en/function.imageaffinematrixconcat]) +Devuelve la concatenación de dos matrices de transformación afín, lo cual es útil si se deben aplicar múltiples transformaciones a la misma imagen a la vez. ([más |https://www.php.net/manual/en/function.imageaffinematrixconcat]) affineMatrixGet(int $type, mixed $options=null): array .[method] @@ -281,54 +327,44 @@ Devuelve una matriz de transformación afín. ([más |https://www.php.net/manual alphaBlending(bool $on): void .[method] --------------------------------------- -Permite dos modos diferentes de dibujo en imágenes truecolor. En el modo de mezcla, el componente del canal alfa del color suministrado a todas las funciones de dibujo, como `setPixel()`, determina la cantidad de color subyacente que debe dejarse traslucir. Como resultado, mezcla automáticamente el color existente en ese punto con el color del dibujo y almacena el resultado en la imagen. El píxel resultante es opaco. En el modo sin mezcla, el color de dibujo se copia literalmente con la información de su canal alfa, sustituyendo al píxel de destino. El modo de fusión no está disponible cuando se dibuja sobre imágenes de paleta. ([más |https://www.php.net/manual/en/function.imagealphablending]) +Permite dos modos diferentes de dibujo en imágenes truecolor. En el modo de fusión, el componente del canal alfa del color utilizado en todas las funciones de dibujo, como `setPixel()`, determina hasta qué punto se debe permitir que el color subyacente se transparente. Como resultado, el color existente se mezcla automáticamente con el color dibujado en ese punto, y el resultado se guarda en la imagen. El píxel resultante es opaco. En el modo sin fusión, el color dibujado se copia literalmente con su información de canal alfa, reemplazando el píxel de destino. El modo de fusión no está disponible al dibujar en imágenes de paleta. ([más |https://www.php.net/manual/en/function.imagealphablending]) antialias(bool $on): void .[method] ----------------------------------- -Activa los métodos de dibujo rápido antialiasing para líneas y polígonos alámbricos. No admite componentes alfa. Funciona mediante una operación de mezcla directa. Sólo funciona con imágenes truecolor. - -El uso de primitivas con antialiasing y color de fondo transparente puede dar lugar a resultados inesperados. El método de mezcla utiliza el color de fondo como cualquier otro color. La falta de soporte de componentes alfa no permite un método de antialiasing basado en alfa. ([más |https://www.php.net/manual/en/function.imageantialias]) - - -arc(int $x, int $y, int $w, int $h, int $start, int $end, int $color): void .[method] -------------------------------------------------------------------------------------- -Dibuja un arco de círculo centrado en las coordenadas dadas. ([más |https://www.php.net/manual/en/function.imagearc]) +Activa el dibujo de líneas y polígonos suavizados. No soporta canales alfa. Solo funciona con imágenes truecolor. - -char(int $font, int $x, int $y, string $char, int $color): void .[method] -------------------------------------------------------------------------- -Dibuja el primer carácter de `$char` en la imagen con su parte superior izquierda en `$x`,`$y` (la parte superior izquierda es 0, 0) con el color `$color`. ([más |https://www.php.net/manual/en/function.imagechar]) +El uso de primitivas suavizadas con un color de fondo transparente puede terminar con algunos resultados inesperados. El método de fusión utiliza el color de fondo como cualquier otro color. ([más |https://www.php.net/manual/en/function.imageantialias]) -charUp(int $font, int $x, int $y, string $char, int $color): void .[method] ---------------------------------------------------------------------------- -Dibuja el carácter `$char` verticalmente en la coordenada especificada en la imagen dada. ([más |https://www.php.net/manual/en/function.imagecharup]) +arc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color): void .[method] +--------------------------------------------------------------------------------------------------------------------------- +Dibuja un arco de elipse centrado en las coordenadas dadas. ([más |https://www.php.net/manual/en/function.imagearc]) colorAllocate(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------- -Devuelve un identificador de color que representa el color compuesto por los componentes RGB dados. Debe llamarse para crear cada color que se vaya a utilizar en la imagen. ([más |https://www.php.net/manual/en/function.imagecolorallocate]) +Devuelve un identificador de color que representa el color compuesto por los componentes RGB dados. Debe llamarse para crear cada color que se utilizará en la imagen (solo para imágenes basadas en paleta). ([más |https://www.php.net/manual/en/function.imagecolorallocate]) colorAllocateAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ------------------------------------------------------------------------------ -Se comporta de forma idéntica a `colorAllocate()` añadiendo el parámetro de transparencia `$alpha`. ([más |https://www.php.net/manual/en/function.imagecolorallocatealpha]) +Se comporta igual que `colorAllocate()` con la adición del parámetro de transparencia `$alpha`. ([más |https://www.php.net/manual/en/function.imagecolorallocatealpha]) colorAt(int $x, int $y): int .[method] -------------------------------------- -Devuelve el índice del color del píxel en la ubicación especificada de la imagen. Si la imagen es truecolor, esta función devuelve el valor RGB de ese píxel como entero. Utilice el desplazamiento de bits y el enmascaramiento para acceder a los distintos valores de los componentes rojo, verde y azul:([más |https://www.php.net/manual/en/function.imagecolorat]) +Devuelve el índice del color del píxel en la ubicación especificada en la imagen. Si la imagen es truecolor, esta función devuelve el valor RGB de ese píxel como un entero. Use desplazamiento de bits y enmascaramiento de bits para acceder a los valores individuales de los componentes rojo, verde y azul. ([más |https://www.php.net/manual/en/function.imagecolorat]) colorClosest(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------ -Devuelve el índice del color de la paleta de la imagen más "cercano" al valor RGB especificado. La "distancia" entre el color deseado y cada color de la paleta se calcula como si los valores RGB representaran puntos en un espacio tridimensional. ([más |https://www.php.net/manual/en/function.imagecolorclosest]) +Devuelve el índice del color en la paleta de la imagen que está „más cerca“ del valor RGB especificado. La "distancia" entre el color deseado y cada color en la paleta se calcula como si los valores RGB representaran puntos en un espacio tridimensional. ([más |https://www.php.net/manual/en/function.imagecolorclosest]) colorClosestAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ----------------------------------------------------------------------------- -Devuelve el índice del color de la paleta de la imagen más "cercano" al valor RGB y al nivel `$alpha` especificados. ([más |https://www.php.net/manual/en/function.imagecolorclosestalpha]) +Devuelve el índice del color en la paleta de la imagen que está „más cerca“ del valor RGB especificado y del nivel `$alpha`. ([más |https://www.php.net/manual/en/function.imagecolorclosestalpha]) colorClosestHWB(int $red, int $green, int $blue): int .[method] @@ -338,7 +374,7 @@ Obtiene el índice del color que tiene el tono, el blanco y el negro más cercan colorDeallocate(int $color): void .[method] ------------------------------------------- -Desasigna un color previamente asignado con `colorAllocate()` o `colorAllocateAlpha()`. ([más |https://www.php.net/manual/en/function.imagecolordeallocate]) +Desasigna un color previamente asignado usando `colorAllocate()` o `colorAllocateAlpha()`. ([más |https://www.php.net/manual/en/function.imagecolordeallocate]) colorExact(int $red, int $green, int $blue): int .[method] @@ -348,12 +384,12 @@ Devuelve el índice del color especificado en la paleta de la imagen. ([más |ht colorExactAlpha(int $red, int $green, int $blue, int $alpha): int .[method] --------------------------------------------------------------------------- -Devuelve el índice del color+alfa especificado en la paleta de la imagen. ([más |https://www.php.net/manual/en/function.imagecolorexactalpha]) +Devuelve el índice del color especificado + alfa en la paleta de la imagen. ([más |https://www.php.net/manual/en/function.imagecolorexactalpha]) colorMatch(Image $image2): void .[method] ----------------------------------------- -Hace que los colores de la versión en paleta de una imagen se ajusten más a la versión en color real. ([más |https://www.php.net/manual/en/function.imagecolormatch]) +Hace coincidir los colores de la paleta con la segunda imagen. ([más |https://www.php.net/manual/en/function.imagecolormatch]) colorResolve(int $red, int $green, int $blue): int .[method] @@ -363,12 +399,12 @@ Devuelve un índice de color para un color solicitado, ya sea el color exacto o colorResolveAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ----------------------------------------------------------------------------- -Devuelve un índice de color para un color solicitado, ya sea el color exacto o la alternativa más cercana posible. ([más |https://www.php.net/manual/en/function.imagecolorresolvealpha]) +Devuelve un índice de color para un color solicitado, ya sea el color exacto o la alternativa más cercana posible, incluyendo el canal alfa. ([más |https://www.php.net/manual/en/function.imagecolorresolvealpha]) colorSet(int $index, int $red, int $green, int $blue): void .[method] --------------------------------------------------------------------- -Establece el índice especificado en la paleta con el color especificado. ([más |https://www.php.net/manual/en/function.imagecolorset]) +Establece el índice especificado en la paleta al color especificado. ([más |https://www.php.net/manual/en/function.imagecolorset]) colorsForIndex(int $index): array .[method] @@ -378,123 +414,128 @@ Obtiene el color para un índice especificado. ([más |https://www.php.net/manua colorsTotal(): int .[method] ---------------------------- -Devuelve el número de colores de una paleta de imágenes ([más |https://www.php.net/manual/en/function.imagecolorstotal]). +Devuelve el número de colores en la paleta de una imagen. ([más |https://www.php.net/manual/en/function.imagecolorstotal]) -colorTransparent(int $color=null): int .[method] ------------------------------------------------- -Obtiene o establece el color transparente de la imagen. ([más |https://www.php.net/manual/en/function.imagecolortransparent]) +colorTransparent(?int $color=null): int .[method] +------------------------------------------------- +Obtiene o establece el color transparente en la imagen. ([más |https://www.php.net/manual/en/function.imagecolortransparent]) convolution(array $matrix, float $div, float $offset): void .[method] --------------------------------------------------------------------- -Aplica una matriz de convolución a la imagen, utilizando el coeficiente y el desplazamiento dados. ([más |https://www.php.net/manual/en/function.imageconvolution]) +Aplica una matriz de convolución en la imagen, utilizando el coeficiente y el desplazamiento dados. ([más |https://www.php.net/manual/en/function.imageconvolution]) .[note] -Requiere la extensión *Bundled GD*, por lo que no es seguro que funcione en todas partes. +Requiere la presencia de la *extensión Bundled GD*, por lo que puede no funcionar en todas partes. copy(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH): void .[method] -------------------------------------------------------------------------------------------------- -Copia una parte de `$src` en la imagen comenzando en las coordenadas `$srcX`, `$srcY` con una anchura de `$srcW` y una altura de `$srcH`. La porción definida se copiará en las coordenadas, `$dstX` y `$dstY`. ([más |https://www.php.net/manual/en/function.imagecopy]) +Copia parte de `$src` en la imagen comenzando en las coordenadas `$srcX`, `$srcY` con un ancho de `$srcW` y una altura de `$srcH`. La parte definida se copiará en las coordenadas `$dstX` y `$dstY`. ([más |https://www.php.net/manual/en/function.imagecopy]) copyMerge(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] --------------------------------------------------------------------------------------------------------------------- -Copia una parte de `$src` en la imagen comenzando en las coordenadas `$srcX`, `$srcY` con una anchura de `$srcW` y una altura de `$srcH`. La porción definida se copiará en las coordenadas, `$dstX` y `$dstY`. ([más |https://www.php.net/manual/en/function.imagecopymerge]) +Copia parte de la imagen `$src` en la imagen actual con una opacidad dada. ([más |https://www.php.net/manual/en/function.imagecopymerge]) copyMergeGray(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] ------------------------------------------------------------------------------------------------------------------------- -Copia una parte de `$src` en la imagen comenzando en las coordenadas `$srcX`, `$srcY` con una anchura de `$srcW` y una altura de `$srcH`. La porción definida se copiará en las coordenadas, `$dstX` y `$dstY`. +Copia parte de la imagen `$src` en la imagen actual con una opacidad dada y convierte los píxeles de destino a escala de grises. -Esta función es idéntica a `copyMerge()` excepto que al fusionar preserva el tono de la fuente convirtiendo los píxeles de destino a escala de grises antes de la operación de copia. ([más |https://www.php.net/manual/en/function.imagecopymergegray]) +([más |https://www.php.net/manual/en/function.imagecopymergegray]) copyResampled(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] --------------------------------------------------------------------------------------------------------------------------------- -Copia una porción rectangular de una imagen en otra imagen, interpolando suavemente los valores de los píxeles para que, en particular, al reducir el tamaño de una imagen se conserve una gran nitidez. +Copia la parte rectangular de una imagen en otra imagen, interpolando suavemente los valores de los píxeles de forma que, al reducir especialmente el tamaño de la imagen, ésta siga conservando una gran nitidez. -En otras palabras, `copyResampled()` tomará un área rectangular de `$src` de anchura `$srcW` y altura `$srcH` en la posición (`$srcX`,`$srcY`) y la colocará en un área rectangular de imagen de anchura `$dstW` y altura `$dstH` en la posición (`$dstX`,`$dstY`). +En otras palabras, `copyResampled()` toma una región rectangular de `$src` de anchura `$srcW` y altura `$srcH` en la posición (`$srcX`, `$srcY`) y la coloca en una región rectangular de la imagen de anchura `$dstW` y altura `$dstH` en la posición (`$dstX`, `$dstY`). -Si las coordenadas de origen y destino y la anchura y altura difieren, se realizará el estiramiento o encogimiento apropiado del fragmento de imagen. Las coordenadas se refieren a la esquina superior izquierda. Esta función puede utilizarse para copiar regiones dentro de la misma imagen, pero si las regiones se solapan los resultados serán impredecibles. ([más |https://www.php.net/manual/en/function.imagecopyresampled]) +Si las coordenadas de origen y destino, anchura y altura, difieren, el fragmento de imagen se estira o encoge en consecuencia. Las coordenadas se refieren a la esquina superior izquierda. Esta función puede utilizarse para copiar áreas en la misma imagen, pero si las áreas se solapan, los resultados no serán predecibles. ([más |https://www.php.net/manual/en/function.imagecopyresampled]) copyResized(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] ------------------------------------------------------------------------------------------------------------------------------- -Copia una porción rectangular de una imagen en otra imagen. En otras palabras, `copyResized()` tomará un área rectangular de `$src` de anchura `$srcW` y altura `$srcH` en la posición (`$srcX`,`$srcY`) y la colocará en un área rectangular de imagen de anchura `$dstW` y altura `$dstH` en la posición (`$dstX`,`$dstY`). +Copia una parte rectangular de una imagen en otra imagen. En otras palabras, `copyResized()` obtiene una región rectangular de `$src` de anchura `$srcW` y altura `$srcH` en la posición (`$srcX`, `$srcY`) y la coloca en una región rectangular de la imagen de anchura `$dstW` ] y altura `$dstH` en la posición (`$dstX`, `$dstY`). -Si las coordenadas de origen y destino y la anchura y altura difieren, se realizará el estiramiento o encogimiento apropiado del fragmento de imagen. Las coordenadas se refieren a la esquina superior izquierda. Esta función puede utilizarse para copiar regiones dentro de la misma imagen, pero si las regiones se solapan los resultados serán impredecibles. ([más |https://www.php.net/manual/en/function.imagecopyresized]) +Si las coordenadas de origen y destino, anchura y altura difieren, el fragmento de imagen se estira o encoge en consecuencia. Las coordenadas se refieren a la esquina superior izquierda. Esta función puede utilizarse para copiar áreas en la misma imagen, pero si las áreas se solapan, los resultados no serán predecibles. ([más |https://www.php.net/manual/en/function.imagecopyresized]) crop(int|string $left, int|string $top, int|string $width, int|string $height): Image .[method] ----------------------------------------------------------------------------------------------- -Recorta una imagen en el área rectangular dada. Las dimensiones pueden pasarse como números enteros en píxeles o cadenas en porcentaje (es decir, `'50%'`). +Recorta una imagen al área rectangular dada. Las dimensiones se pueden especificar como enteros en píxeles o cadenas en porcentajes (por ejemplo, `'50%'`). Devuelve `$this` para permitir encadenamiento. -cropAuto(int $mode=-1, float $threshold=.5, int $color=-1): Image .[method] ---------------------------------------------------------------------------- -Recorta automáticamente una imagen según la dirección `$mode`. ([más |https://www.php.net/manual/en/function.imagecropauto]) +cropAuto(int $mode=-1, float $threshold=.5, ?ImageColor $color=null): Image .[method] +------------------------------------------------------------------------------------- +Recorta automáticamente una imagen según el `$mode` dado. ([más |https://www.php.net/manual/en/function.imagecropauto]). Devuelve `$this` para permitir encadenamiento. -ellipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------- +ellipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------- Dibuja una elipse centrada en las coordenadas especificadas. ([más |https://www.php.net/manual/en/function.imageellipse]) -fill(int $x, int $y, int $color): void .[method] ------------------------------------------------- -Realiza un relleno de inundación comenzando en la coordenada dada (arriba a la izquierda es 0, 0) con el `$color` dado en la imagen. ([más |https://www.php.net/manual/en/function.imagefill]) +fill(int $x, int $y, ImageColor $color): void .[method] +------------------------------------------------------- +Realiza un relleno de área comenzando en la coordenada dada (la esquina superior izquierda es 0, 0) con el `$color` dado. ([más |https://www.php.net/manual/en/function.imagefill]) -filledArc(int $cx, int $cy, int $w, int $h, int $s, int $e, int $color, int $style): void .[method] ---------------------------------------------------------------------------------------------------- -Dibuja un arco parcial centrado en la coordenada especificada en la imagen. ([más |https://www.php.net/manual/en/function.imagefilledarc]) +filledArc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color, int $style): void .[method] +--------------------------------------------------------------------------------------------------------------------------------------------- +Dibuja un arco parcial relleno centrado en las coordenadas especificadas. ([más |https://www.php.net/manual/en/function.imagefilledarc]) -filledEllipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------------- -Dibuja una elipse centrada en la coordenada especificada en la imagen. ([más |https://www.php.net/manual/en/function.imagefilledellipse]) +filledEllipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------------- +Dibuja una elipse rellena centrada en las coordenadas especificadas. ([más |https://www.php.net/manual/en/function.imagefilledellipse]) -filledPolygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------------- -Crea un polígono relleno en la $imagen. ([más |https://www.php.net/manual/en/function.imagefilledpolygon]) +filledPolygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------------- +Crea un polígono relleno en la imagen. ([más |https://www.php.net/manual/en/function.imagefilledpolygon]) -filledRectangle(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] -------------------------------------------------------------------------------- -Crea un rectángulo relleno con `$color` en la imagen comenzando en el punto 1 y terminando en el punto 2. 0, 0 es la esquina superior izquierda de la imagen. ([más |https://www.php.net/manual/en/function.imagefilledrectangle]) +filledRectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------- +Crea un rectángulo relleno con `$color` en la imagen desde la esquina superior izquierda (`$x1`, `$y1`) hasta la esquina inferior derecha (`$x2`, `$y2`). El punto 0, 0 es la esquina superior izquierda de la imagen. ([más |https://www.php.net/manual/en/function.imagefilledrectangle]) -fillToBorder(int $x, int $y, int $border, int $color): void .[method] ---------------------------------------------------------------------- -Realiza un relleno de inundación cuyo color de borde está definido por `$border`. El punto de partida del relleno es `$x`, `$y` (arriba a la izquierda es 0, 0) y la región se rellena con el color `$color`. ([más |https://www.php.net/manual/en/function.imagefilltoborder]) +filledRectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------------- +Crea un rectángulo relleno con `$color` en la imagen comenzando en el punto (`$left`, `$top`) con ancho `$width` y alto `$height`. El punto 0, 0 es la esquina superior izquierda de la imagen. + + +fillToBorder(int $x, int $y, int $border, ImageColor $color): void .[method] +---------------------------------------------------------------------------- +Realiza un relleno de área (flood fill) cuyo color de borde está definido con `$border`. El punto de partida para el relleno es `$x`, `$y` (la esquina superior izquierda es 0, 0) y la región se rellena con `$color`. ([más |https://www.php.net/manual/en/function.imagefilltoborder]) filter(int $filtertype, int ...$args): void .[method] ----------------------------------------------------- -Aplica el filtro `$filtertype` a la imagen. ([más |https://www.php.net/manual/en/function.imagefilter]) +Aplica el filtro `$filtertype` dado a la imagen. ([más |https://www.php.net/manual/en/function.imagefilter]) flip(int $mode): void .[method] ------------------------------- -Voltea la imagen utilizando la dirección `$mode`. ([más |https://www.php.net/manual/en/function.imageflip]) +Voltea la imagen usando el `$mode` dado (`IMG_FLIP_HORIZONTAL`, `IMG_FLIP_VERTICAL`, `IMG_FLIP_BOTH`). ([más |https://www.php.net/manual/en/function.imageflip]) -ftText(int $size, int $angle, int $x, int $y, int $col, string $fontFile, string $text, array $extrainfo=null): array .[method] -------------------------------------------------------------------------------------------------------------------------------- -Escribe texto en la imagen utilizando fuentes con FreeType 2. ([más |https://www.php.net/manual/en/function.imagefttext]) +ftText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +---------------------------------------------------------------------------------------------------------------------------------------- +Escribe texto en la imagen usando fuentes FreeType. ([más |https://www.php.net/manual/en/function.imagefttext]) gammaCorrect(float $inputgamma, float $outputgamma): void .[method] ------------------------------------------------------------------- -Aplica la corrección gamma a la imagen dada una gamma de entrada y otra de salida. ([más |https://www.php.net/manual/en/function.imagegammacorrect]) +Aplica la corrección gamma a la imagen dada la gamma de entrada y salida. ([más |https://www.php.net/manual/en/function.imagegammacorrect]) getClip(): array .[method] -------------------------- -Recupera el rectángulo de recorte actual, es decir, el área más allá de la cual no se dibujarán píxeles. ([más |https://www.php.net/manual/en/function.imagegetclip]) +Devuelve el recorte actual (clipping rectangle) como `[x1, y1, x2, y2]`. ([más |https://www.php.net/manual/en/function.imagegetclip]) getHeight(): int .[method] @@ -509,91 +550,96 @@ Devuelve el recurso original. getWidth(): int .[method] ------------------------- -Devuelve la anchura de la imagen. +Devuelve el ancho de la imagen. -interlace(int $interlace=null): int .[method] ---------------------------------------------- -Activa o desactiva el bit de entrelazado. Si el bit de entrelazado está activado y la imagen se utiliza como imagen JPEG, la imagen se crea como JPEG progresivo. ([más |https://www.php.net/manual/en/function.imageinterlace]) +interlace(?int $interlace=null): int .[method] +---------------------------------------------- +Activa o desactiva el modo entrelazado. Si el modo entrelazado está activado y la imagen se guarda como JPEG, se guardará como JPEG progresivo. ([más |https://www.php.net/manual/en/function.imageinterlace]) isTrueColor(): bool .[method] ----------------------------- -Averigua si la imagen es truecolor. ([más |https://www.php.net/manual/en/function.imageistruecolor]) +Comprueba si la imagen es truecolor. ([más |https://www.php.net/manual/en/function.imageistruecolor]) layerEffect(int $effect): void .[method] ---------------------------------------- -Establezca la bandera de mezcla alfa para utilizar efectos de estratificación. ([más |https://www.php.net/manual/en/function.imagelayereffect]) +Establece la bandera de fusión alfa para usar efectos de capas (por ejemplo, `IMG_EFFECT_REPLACE`). ([más |https://www.php.net/manual/en/function.imagelayereffect]) -line(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] --------------------------------------------------------------------- +line(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +--------------------------------------------------------------------------- Dibuja una línea entre los dos puntos dados. ([más |https://www.php.net/manual/en/function.imageline]) -openPolygon(array $points, int $numPoints, int $color): void .[method] ----------------------------------------------------------------------- -Dibuja un polígono abierto en la imagen. Contrariamente a `polygon()`, no se traza ninguna línea entre el último y el primer punto. ([más |https://www.php.net/manual/en/function.imageopenpolygon]) +openPolygon(array $points, ImageColor $color): void .[method] +------------------------------------------------------------- +Dibuja un polígono abierto en la imagen. A diferencia de `polygon()`, no se dibuja ninguna línea entre el último y el primer punto. ([más |https://www.php.net/manual/en/function.imageopenpolygon]) paletteCopy(Image $source): void .[method] ------------------------------------------ -Copia la paleta de `$source` a la imagen. ([más |https://www.php.net/manual/en/function.imagepalettecopy]) +Copia la paleta de `$source` a la imagen actual. ([más |https://www.php.net/manual/en/function.imagepalettecopy]) paletteToTrueColor(): void .[method] ------------------------------------ -Convierte una imagen basada en paleta, creada por funciones como `create()` a una imagen de color verdadero, como `createtruecolor()`. ([más |https://www.php.net/manual/en/function.imagepalettetotruecolor]) +Convierte una imagen basada en paleta a una imagen truecolor. ([más |https://www.php.net/manual/en/function.imagepalettetotruecolor]) place(Image $image, int|string $left=0, int|string $top=0, int $opacity=100): Image .[method] --------------------------------------------------------------------------------------------- -Copia `$image` en la imagen en las coordenadas `$left` y `$top`. Las coordenadas pueden pasarse como números enteros en píxeles o cadenas en porcentaje (es decir, `'50%'`). +Copia `$image` en la imagen en las coordenadas `$left` y `$top`. Las coordenadas se pueden especificar como enteros en píxeles o cadenas en porcentajes (por ejemplo, `'50%'`). -polygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------- +polygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------- Crea un polígono en la imagen. ([más |https://www.php.net/manual/en/function.imagepolygon]) -rectangle(int $x1, int $y1, int $x2, int $y2, int $col): void .[method] ------------------------------------------------------------------------ -Crea un rectángulo que comienza en las coordenadas especificadas. ([más |https://www.php.net/manual/en/function.imagerectangle]) +rectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------- +Crea un rectángulo (solo el borde) en las coordenadas especificadas. ([más |https://www.php.net/manual/en/function.imagerectangle]) + + +rectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------- +Crea un rectángulo (solo el borde) en las coordenadas especificadas, definidas por la esquina superior izquierda, ancho y alto. resize(int|string $width, int|string $height, int $flags=Image::OrSmaller): Image .[method] ------------------------------------------------------------------------------------------- -Escala una imagen, ver [más info |#Image Resize]. Las dimensiones pueden pasarse como números enteros en píxeles o cadenas en porcentaje (es decir, `'50%'`). +Cambia el tamaño de una imagen, [más información |#Cambio de tamaño]. Las dimensiones se pueden especificar como enteros en píxeles o cadenas en porcentajes (por ejemplo, `'50%'`). Devuelve `$this` para permitir encadenamiento. -resolution(int $resX=null, int $resY=null): mixed .[method] ------------------------------------------------------------ -Permite establecer y obtener la resolución de una imagen en PPP (puntos por pulgada). Si no se proporciona ninguno de los parámetros opcionales, se devuelve la resolución actual como matriz indexada. Si sólo se indica `$resX`, la resolución horizontal y vertical se ajustan a este valor. Si se dan ambos parámetros opcionales, la resolución horizontal y vertical se fijan en estos valores, respectivamente. +resolution(?int $resX=null, ?int $resY=null): mixed .[method] +------------------------------------------------------------- +Establece o devuelve la resolución de la imagen en DPI (puntos por pulgada). Si no se especifica ningún parámetro, devuelve la resolución actual como `[resX, resY]`. Si solo se especifica `$resX`, la resolución horizontal y vertical se establecen en este valor. Si se especifican ambos, se establecen respectivamente. -La resolución sólo se utiliza como metainformación cuando las imágenes se leen y escriben en formatos que admiten este tipo de información (actualmente PNG y JPEG). No afecta a ninguna operación de dibujo. La resolución por defecto para las nuevas imágenes es de 96 PPP. ([más |https://www.php.net/manual/en/function.imageresolution]) +La resolución solo se utiliza como metainformación cuando las imágenes se leen y escriben en formatos que admiten este tipo de información (actualmente PNG y JPEG). No afecta a ninguna operación de dibujo. La resolución predeterminada para nuevas imágenes es de 96 DPI. ([más |https://www.php.net/manual/en/function.imageresolution]) rotate(float $angle, int $backgroundColor): Image .[method] ----------------------------------------------------------- -Rota la imagen utilizando la dirección `$angle` en grados. El centro de rotación es el centro de la imagen, y la imagen rotada puede tener dimensiones diferentes a las de la imagen original. ([más |https://www.php.net/manual/en/function.imagerotate]) +Gira la imagen con el `$angle` dado en grados. `$backgroundColor` especifica el color de las áreas descubiertas después de la rotación. El centro de rotación es el centro de la imagen. Devuelve `$this` para permitir encadenamiento. ([más |https://www.php.net/manual/en/function.imagerotate]) .[note] -Requiere la extensión *Bundled GD*, por lo que no es seguro que funcione en todas partes. +Requiere la presencia de la *extensión Bundled GD*, por lo que puede no funcionar en todas partes. -save(string $file, int $quality=null, int $type=null): void .[method] ---------------------------------------------------------------------- +save(string $file, ?int $quality=null, ?int $type=null): void .[method] +----------------------------------------------------------------------- Guarda una imagen en un archivo. -La calidad de compresión está en el rango 0..100 para JPEG (por defecto 85), WEBP (por defecto 80) y AVIF (por defecto 30) y 0..9 para PNG (por defecto 9). Si el tipo no es obvio a partir de la extensión del archivo, puede especificarlo utilizando una de las constantes `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF`, y `Image::BMP`. +La calidad de compresión está en el rango de 0..100 para JPEG (predeterminado 85), WEBP (predeterminado 80) y AVIF (predeterminado 30) y 0..9 para PNG (predeterminado 9). Si el tipo no es evidente por la extensión del archivo, puede especificarlo usando una de las constantes `ImageType`. saveAlpha(bool $saveflag): void .[method] ----------------------------------------- -Establece el indicador que determina si se conserva toda la información del canal alfa (en contraposición a la transparencia de un solo color) al guardar imágenes PNG. +Establece la bandera que determina si se guarda la información completa del canal alfa (en lugar de la transparencia de un solo color) al guardar imágenes PNG. -Alphablending tiene que estar desactivado (`alphaBlending(false)`) para conservar el canal alfa en primer lugar. ([más |https://www.php.net/manual/en/function.imagesavealpha]) +La fusión alfa debe estar desactivada (`alphaBlending(false)`) para conservar el canal alfa en primer lugar. ([más |https://www.php.net/manual/en/function.imagesavealpha]) scale(int $newWidth, int $newHeight=-1, int $mode=IMG_BILINEAR_FIXED): Image .[method] @@ -601,80 +647,70 @@ scale(int $newWidth, int $newHeight=-1, int $mode=IMG_BILINEAR_FIXED): Image .[m Escala una imagen utilizando el algoritmo de interpolación dado. ([más |https://www.php.net/manual/en/function.imagescale]) -send(int $type=Image::JPEG, int $quality=null): void .[method] --------------------------------------------------------------- -Envía una imagen al navegador. +send(int $type=ImageType::JPEG, ?int $quality=null): void .[method] +------------------------------------------------------------------- +Envía una imagen al navegador (salida estándar). -La calidad de compresión está en el rango 0..100 para JPEG (por defecto 85), WEBP (por defecto 80) y AVIF (por defecto 30) y 0..9 para PNG (por defecto 9). El tipo es una de las constantes `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` y `Image::BMP`. +La calidad de compresión está en el rango de 0..100 para JPEG (predeterminado 85), WEBP (predeterminado 80) y AVIF (predeterminado 30) y 0..9 para PNG (predeterminado 9). setBrush(Image $brush): void .[method] -------------------------------------- -Establece la imagen del pincel que utilizarán todas las funciones de dibujo lineal (como `line()` y `polygon()`) cuando se dibuje con los colores especiales IMG_COLOR_BRUSHED o IMG_COLOR_STYLEDBRUSHED. ([más |https://www.php.net/manual/en/function.imagesetbrush]) +Establece la imagen del pincel que se utilizará en todas las funciones de dibujo de líneas (como `line()` y `polygon()`) al dibujar con los colores especiales IMG_COLOR_BRUSHED o IMG_COLOR_STYLEDBRUSHED. ([más |https://www.php.net/manual/en/function.imagesetbrush]) setClip(int $x1, int $y1, int $x2, int $y2): void .[method] ----------------------------------------------------------- -Establece el rectángulo de recorte actual, es decir, el área más allá de la cual no se dibujarán píxeles. ([más |https://www.php.net/manual/en/function.imagesetclip]) +Establece el área de recorte actual (clipping rectangle). ([más |https://www.php.net/manual/en/function.imagesetclip]) setInterpolation(int $method=IMG_BILINEAR_FIXED): void .[method] ---------------------------------------------------------------- -Establece el método de interpolación que afecta a los métodos `rotate()` y `affine()`. ([más |https://www.php.net/manual/en/function.imagesetinterpolation]) +Establece el método de interpolación, que afecta a los métodos `rotate()` y `affine()`. ([más |https://www.php.net/manual/en/function.imagesetinterpolation]) -setPixel(int $x, int $y, int $color): void .[method] ----------------------------------------------------- +setPixel(int $x, int $y, ImageColor $color): void .[method] +----------------------------------------------------------- Dibuja un píxel en la coordenada especificada. ([más |https://www.php.net/manual/en/function.imagesetpixel]) setStyle(array $style): void .[method] -------------------------------------- -Establece el estilo a utilizar por todas las funciones de dibujo de líneas (como `line()` y `polygon()`) cuando se dibuja con el color especial IMG_COLOR_STYLED o líneas de imágenes con color IMG_COLOR_STYLEDBRUSHED. ([más |https://www.php.net/manual/en/function.imagesetstyle]) +Establece el estilo que utilizarán todas las funciones de dibujo de líneas (como `line()` y `polygon()`) al dibujar con el color especial IMG_COLOR_STYLED o líneas de imágenes con el color IMG_COLOR_STYLEDBRUSHED. ([más |https://www.php.net/manual/en/function.imagesetstyle]) setThickness(int $thickness): void .[method] -------------------------------------------- -Establece el grosor de las líneas dibujadas al dibujar rectángulos, polígonos, arcos, etc. en `$thickness` píxeles. ([más |https://www.php.net/manual/en/function.imagesetthickness]) +Establece el grosor de las líneas al dibujar rectángulos, polígonos, arcos, etc. a `$thickness` píxeles. ([más |https://www.php.net/manual/en/function.imagesetthickness]) setTile(Image $tile): void .[method] ------------------------------------ -Establece la imagen de mosaico que utilizarán todas las funciones de relleno de regiones (como `fill()` y `filledPolygon()`) al rellenar con el color especial IMG_COLOR_TILED. +Establece la imagen de mosaico que se utilizará en todas las funciones de relleno de regiones (como `fill()` y `filledPolygon()`) al rellenar con el color especial IMG_COLOR_TILED. -Un azulejo es una imagen utilizada para rellenar un área con un patrón repetido. Se puede utilizar cualquier imagen como mosaico, y estableciendo el índice de color transparente de la imagen del mosaico con `colorTransparent()`, se puede crear un mosaico que permita que ciertas partes del área subyacente brillen a través de él. ([más |https://www.php.net/manual/en/function.imagesettile]) +Un mosaico es una imagen utilizada para rellenar un área con un patrón repetido. Cualquier imagen se puede usar como mosaico, y al establecer el índice de color transparente de la imagen de mosaico con `colorTransparent()`, se puede crear un mosaico que permita que ciertas partes del área subyacente se vean a través. ([más |https://www.php.net/manual/en/function.imagesettile]) sharpen(): Image .[method] -------------------------- -Enfoca un poco la imagen. +Aplica un filtro de enfoque (sharpen) a la imagen. Devuelve `$this` para permitir encadenamiento. .[note] -Requiere la extensión *Bundled GD*, por lo que no es seguro que funcione en todas partes. - - -string(int $font, int $x, int $y, string $str, int $col): void .[method] ------------------------------------------------------------------------- -Dibuja una cadena en las coordenadas dadas. ([más |https://www.php.net/manual/en/function.imagestring]) +Requiere la presencia de la *extensión Bundled GD*, por lo que puede no funcionar en todas partes. -stringUp(int $font, int $x, int $y, string $s, int $col): void .[method] ------------------------------------------------------------------------- -Dibuja una cadena verticalmente en las coordenadas dadas. ([más |https://www.php.net/manual/en/function.imagestringup]) - - -toString(int $type=Image::JPEG, int $quality=null): string .[method] --------------------------------------------------------------------- -Muestra una imagen en una cadena. +toString(int $type=ImageType::JPEG, ?int $quality=null): string .[method] +------------------------------------------------------------------------- +Devuelve la imagen como una cadena en el formato especificado. -La calidad de compresión está en el rango 0..100 para JPEG (por defecto 85), WEBP (por defecto 80) y AVIF (por defecto 30) y 0..9 para PNG (por defecto 9). El tipo es una de las constantes `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` y `Image::BMP`. +La calidad de compresión está en el rango de 0..100 para JPEG (predeterminado 85), WEBP (predeterminado 80) y AVIF (predeterminado 30) y 0..9 para PNG (predeterminado 9). trueColorToPalette(bool $dither, int $ncolors): void .[method] -------------------------------------------------------------- -Convierte una imagen truecolor en una imagen de paleta. ([más |https://www.php.net/manual/en/function.imagetruecolortopalette]) +Convierte una imagen truecolor a una imagen de paleta. ([más |https://www.php.net/manual/en/function.imagetruecolortopalette]) -ttfText(int $size, int $angle, int $x, int $y, int $color, string $fontfile, string $text): array .[method] ------------------------------------------------------------------------------------------------------------ -Escribe el texto dado en la imagen utilizando fuentes TrueType. ([más |https://www.php.net/manual/en/function.imagettftext]) +ttfText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +----------------------------------------------------------------------------------------------------------------------------------------- +Escribe el texto dado en la imagen usando fuentes TrueType. ([más |https://www.php.net/manual/en/function.imagettftext]) diff --git a/utils/es/iterables.texy b/utils/es/iterables.texy new file mode 100644 index 0000000000..b67cf4abdc --- /dev/null +++ b/utils/es/iterables.texy @@ -0,0 +1,170 @@ +Trabajando con iteradores +************************* + +.[perex]{data-version:4.0.4} +[api:Nette\Utils\Iterables] es una clase estática con funciones para trabajar con iteradores. Su contraparte para arrays es [Nette\Utils\Arrays |arrays]. + + +Instalación: + +```shell +composer require nette/utils +``` + +Todos los ejemplos asumen que se ha creado un alias: + +```php +use Nette\Utils\Iterables; +``` + + +contains(iterable $iterable, $value): bool .[method] +---------------------------------------------------- + +Busca un valor específico en el iterador. Utiliza una comparación estricta (`===`) para verificar la coincidencia. Devuelve `true` si se encuentra el valor, de lo contrario `false`. + +```php +Iterables::contains(new ArrayIterator([1, 2, 3]), 1); // true +Iterables::contains(new ArrayIterator([1, 2, 3]), '1'); // false +``` + +Este método es útil cuando necesita determinar rápidamente si un valor particular está presente en el iterador sin tener que recorrer manualmente todos los elementos. + + +containsKey(iterable $iterable, $key): bool .[method] +----------------------------------------------------- + +Busca una clave específica en el iterador. Utiliza una comparación estricta (`===`) para verificar la coincidencia. Devuelve `true` si se encuentra la clave, de lo contrario `false`. + +```php +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 0); // true +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 4); // false +``` + + +every(iterable $iterable, callable $predicate): bool .[method] +-------------------------------------------------------------- + +Verifica si todos los elementos del iterador cumplen la condición definida en `$predicate`. La función `$predicate` tiene la firma `function ($value, $key, iterable $iterable): bool` y debe devolver `true` para cada elemento para que el método `every()` devuelva `true`. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isBelowThreshold = fn($value) => $value < 40; +$res = Iterables::every($iterator, $isBelowThreshold); // true +``` + +Este método es útil para verificar si todos los elementos de una colección cumplen una determinada condición, por ejemplo, si todos los números son menores que un valor determinado. + + +filter(iterable $iterable, callable $predicate): Generator .[method] +-------------------------------------------------------------------- + +Crea un nuevo iterador que contiene solo aquellos elementos del iterador original que cumplen la condición definida en `$predicate`. La función `$predicate` tiene la firma `function ($value, $key, iterable $iterable): bool` y debe devolver `true` para los elementos que se deben conservar. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::filter($iterator, fn($v) => $v < 3); +// 1, 2 +``` + +El método utiliza un generador, lo que significa que el filtrado se realiza gradualmente al recorrer el resultado. Esto es eficiente en términos de memoria y permite procesar colecciones muy grandes. Si no recorre todos los elementos del iterador resultante, ahorrará potencia de cálculo, ya que no se procesarán todos los elementos del iterador original. + + +first(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------------- + +Devuelve el primer elemento del iterador. Si se especifica `$predicate`, devuelve el primer elemento que cumple la condición dada. La función `$predicate` tiene la firma `function ($value, $key, iterable $iterable): bool`. Si no se encuentra ningún elemento que coincida, se llama a la función `$else` (si se especifica) y se devuelve su resultado. Si no se especifica `$else`, se devuelve `null`. + +```php +Iterables::first(new ArrayIterator([1, 2, 3])); // 1 +Iterables::first(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 3 +Iterables::first(new ArrayIterator([])); // null +Iterables::first(new ArrayIterator([]), else: fn() => false); // false +``` + +Este método es útil cuando necesita obtener rápidamente el primer elemento de una colección o el primer elemento que cumple una determinada condición, sin tener que recorrer manualmente toda la colección. + + +firstKey(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +---------------------------------------------------------------------------------------------- + +Devuelve la clave del primer elemento del iterador. Si se especifica `$predicate`, devuelve la clave del primer elemento que cumple la condición dada. La función `$predicate` tiene la firma `function ($value, $key, iterable $iterable): bool`. Si no se encuentra ningún elemento que coincida, se llama a la función `$else` (si se especifica) y se devuelve su resultado. Si no se especifica `$else`, se devuelve `null`. + +```php +Iterables::firstKey(new ArrayIterator([1, 2, 3])); // 0 +Iterables::firstKey(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 2 +Iterables::firstKey(new ArrayIterator(['a' => 1, 'b' => 2])); // 'a' +Iterables::firstKey(new ArrayIterator([])); // null +``` + + +map(iterable $iterable, callable $transformer): Generator .[method] +------------------------------------------------------------------- + +Crea un nuevo iterador aplicando la función `$transformer` a cada elemento del iterador original. La función `$transformer` tiene la firma `function ($value, $key, iterable $iterable): mixed` y su valor de retorno se utiliza como el nuevo valor del elemento. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::map($iterator, fn($v) => $v * 2); +// 2, 4, 6 +``` + +El método utiliza un generador, lo que significa que la transformación se realiza gradualmente al recorrer el resultado. Esto es eficiente en términos de memoria y permite procesar colecciones muy grandes. Si no recorre todos los elementos del iterador resultante, ahorrará potencia de cálculo, ya que no se procesarán todos los elementos del iterador original. + + +mapWithKeys(iterable $iterable, callable $transformer): Generator .[method] +--------------------------------------------------------------------------- + +Crea un nuevo iterador transformando los valores y claves del iterador original. La función `$transformer` tiene la firma `function ($value, $key, iterable $iterable): ?array{$newKey, $newValue}`. Si `$transformer` devuelve `null`, el elemento se omite. Para los elementos conservados, el primer elemento del array devuelto se utiliza como la nueva clave y el segundo elemento como el nuevo valor. + +```php +$iterator = new ArrayIterator(['a' => 1, 'b' => 2]); +$iterator = Iterables::mapWithKeys($iterator, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +Al igual que `map()`, este método utiliza un generador para el procesamiento gradual y el trabajo eficiente con la memoria. Esto permite trabajar con colecciones grandes y ahorrar potencia de cálculo en el recorrido parcial del resultado. + + +memoize(iterable $iterable): IteratorAggregate .[method] +-------------------------------------------------------- + +Crea un envoltorio alrededor de un iterador que almacena en caché sus claves y valores durante la iteración. Esto permite la iteración repetida de datos sin necesidad de volver a recorrer la fuente de datos original. + +```php +$iterator = /* datos que no se pueden iterar más de una vez */ +$memoized = Iterables::memoize($iterator); +// Ahora puedes iterar $memoized varias veces sin perder datos +``` + +Este método es útil en situaciones en las que necesita recorrer el mismo conjunto de datos varias veces, pero el iterador original no permite la iteración repetida o el recorrido repetido sería costoso (por ejemplo, al leer datos de una base de datos o un archivo). + + +some(iterable $iterable, callable $predicate): bool .[method] +------------------------------------------------------------- + +Verifica si al menos un elemento del iterador cumple la condición definida en `$predicate`. La función `$predicate` tiene la firma `function ($value, $key, iterable $iterable): bool` y debe devolver `true` para al menos un elemento para que el método `some()` devuelva `true`. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isEven = fn($value) => $value % 2 === 0; +$res = Iterables::some($iterator, $isEven); // true +``` + +Este método es útil para verificar rápidamente si existe al menos un elemento en la colección que cumpla una determinada condición, por ejemplo, si la colección contiene al menos un número par. + +Ver [#every()]. + + +toIterator(iterable $iterable): Iterator .[method] +-------------------------------------------------- + +Convierte cualquier objeto iterable (array, Traversable) en un Iterator. Si la entrada ya es un Iterator, lo devuelve sin cambios. + +```php +$array = [1, 2, 3]; +$iterator = Iterables::toIterator($array); +// Ahora tienes un Iterator en lugar de un array +``` + +Este método es útil cuando necesita asegurarse de que tiene un `Iterator` disponible, independientemente del tipo de datos de entrada. Esto puede ser útil al crear funciones que trabajan con diferentes tipos de datos iterables. diff --git a/utils/es/json.texy b/utils/es/json.texy index 1b4cd3ee45..fcef307824 100644 --- a/utils/es/json.texy +++ b/utils/es/json.texy @@ -1,8 +1,8 @@ -Funciones JSON -************** +Trabajando con JSON +******************* .[perex] -[api:Nette\Utils\Json] es una clase estática con funciones de codificación y decodificación JSON. Maneja vulnerabilidades en diferentes versiones de PHP y lanza excepciones en caso de error. +[api:Nette\Utils\Json] es una clase estática con funciones para codificar y decodificar el formato JSON. Maneja vulnerabilidades de diferentes versiones de PHP y lanza excepciones en caso de errores. Instalación: @@ -11,44 +11,44 @@ Instalación: composer require nette/utils ``` -Todos los ejemplos asumen que el siguiente alias de clase está definido: +Todos los ejemplos asumen que se ha creado un alias: ```php use Nette\Utils\Json; ``` -Uso .[#toc-usage] -================= +Uso +=== encode(mixed $value, bool $pretty=false, bool $asciiSafe=false, bool $htmlSafe=false, bool $forceObjects=false): string .[method] --------------------------------------------------------------------------------------------------------------------------------- -Convierte `$valor` a formato JSON. +Convierte `$value` al formato JSON. -Cuando `$pretty` está activado, formatea JSON para facilitar la lectura y la claridad: +Cuando se establece `$pretty`, formatea el JSON para facilitar la lectura y la claridad: ```php -Json::encode($valor); // devuelve JSON -Json::encode($valor, pretty: true); // devuelve un JSON más claro +Json::encode($value); // devuelve JSON +Json::encode($value, pretty: true); // devuelve JSON más legible ``` -Cuando `$asciiSafe` está activado, genera una salida ASCII, es decir, sustituye los caracteres unicode por secuencias `\uxxxx`: +Con `$asciiSafe`, genera una salida en ASCII, es decir, los caracteres unicode se reemplazan con secuencias `\uxxxx`: ```php Json::encode('žluťoučký', asciiSafe: true); // '"\u017elu\u0165ou\u010dk\u00fd"' ``` -El parámetro `$htmlSafe` garantiza que la salida no contenga caracteres con significado especial en HTML: +El parámetro `$htmlSafe` asegura que la salida no contenga caracteres que tengan un significado especial en HTML: ```php Json::encode('onesendJson($data)`, que puede llamarse, por ejemplo, en el método `action*()`, véase [Envío de una respuesta |application:presenters#Sending a Response]. +Puede usar el método `$this->sendJson($data)` para esto, que podemos llamar, por ejemplo, en el método `action*()`, ver [Enviar una respuesta |application:presenters#Envío de la respuesta]. diff --git a/utils/es/paginator.texy b/utils/es/paginator.texy index c4f7f03fd0..e2f4375ddb 100644 --- a/utils/es/paginator.texy +++ b/utils/es/paginator.texy @@ -1,8 +1,8 @@ -Paginador +Paginator ********* .[perex] -¿Necesita paginar un listado de datos? Porque las matemáticas detrás de la paginación pueden ser complicadas, [api:Nette\Utils\Paginator] le ayudará. +¿Necesita paginar la salida de datos? Dado que las matemáticas de paginación pueden ser complicadas, [api:Nette\Utils\Paginator] le ayudará con eso. Instalación: @@ -11,38 +11,38 @@ Instalación: composer require nette/utils ``` -Vamos a crear un objeto de paginación y establecer la información básica para él: +Creamos un objeto paginador y le establecemos la información básica: ```php $paginator = new Nette\Utils\Paginator; -$paginator->setPage(1); // el número de la página actual (numerada desde 1) -$paginator->setItemsPerPage(30); // el número de registros por página -$paginator->setItemCount(356); // el número total de registros (si está disponible) +$paginator->setPage(1); // número de la página actual +$paginator->setItemsPerPage(30); // número de elementos por página +$paginator->setItemCount(356); // número total de elementos, si se conoce ``` -Las páginas se numeran a partir de 1. Podemos cambiarla usando `setBase()`: +Las páginas se numeran desde 1 por defecto. Podemos cambiar esto usando `setBase()`: ```php -$paginator->setBase(0); // numbered from 0 +$paginator->setBase(0); // numeramos desde 0 ``` -El objeto proporcionará ahora toda la información básica útil para crear un paginador. Puedes, por ejemplo, pasarlo a una plantilla y usarlo allí. +El objeto ahora proporciona toda la información básica útil para crear un paginador. Puede pasarlo a una plantilla, por ejemplo, y usarlo allí. ```php -$paginator->isFirst(); // ¿es esta la primera página? -$paginator->isLast(); // ¿es ésta la última página? -$paginator->getPage(); // número de página actual -$paginator->getFirstPage(); // el número de la primera página -$paginator->getLastPage(); // el número de la última página -$paginator->getFirstItemOnPage(); // número de secuencia del primer elemento de la página -$paginator->getLastItemOnPage(); // número de secuencia del último elemento de la página -$paginator->getPageIndex(); // número de página actual si se numera desde 0 -$paginator->getPageCount(); // el número total de páginas -$paginator->getItemsPerPage(); // el número de registros por página -$paginator->getItemCount(); // el número total de registros (si está disponible) +$paginator->isFirst(); // ¿estamos en la primera página? +$paginator->isLast(); // ¿estamos en la última página? +$paginator->getPage(); // número de la página actual +$paginator->getFirstPage(); // número de la primera página +$paginator->getLastPage(); // número de la última página +$paginator->getFirstItemOnPage(); // número de secuencia del primer elemento en la página +$paginator->getLastItemOnPage(); // número de secuencia del último elemento en la página +$paginator->getPageIndex(); // número de la página actual numerado desde 0 +$paginator->getPageCount(); // número total de páginas +$paginator->getItemsPerPage(); // número de elementos por página +$paginator->getItemCount(); // número total de elementos, si se conoce ``` -El paginador ayudará a formular la consulta SQL. Los métodos `getLength()` y `getOffset()` devuelven los valores que puede utilizar en las cláusulas LIMIT y OFFSET: +El paginador ayuda a formular una consulta SQL. Los métodos `getLength()` y `getOffset()` devuelven valores que usamos en las cláusulas `LIMIT` y `OFFSET`: ```php $result = $database->query( @@ -52,7 +52,7 @@ $result = $database->query( ); ``` -Si necesita paginar en orden inverso, es decir, la página no. 1 corresponde al desplazamiento más alto, puede utilizar `getCountdownOffset()`: +Si necesitamos paginar en orden inverso, es decir, la página n.º 1 corresponde al offset más alto, usamos `getCountdownOffset()`: ```php $result = $database->query( @@ -62,4 +62,4 @@ $result = $database->query( ); ``` -Encontrará un ejemplo de utilización en la aplicación en el libro de recetas [Paginación de los resultados de la base de datos |best-practices:pagination]. +Puede encontrar un ejemplo de uso en la aplicación en el libro de cocina [Paginación de resultados de base de datos |best-practices:pagination]. diff --git a/utils/es/random.texy b/utils/es/random.texy index aa0633897f..ae2f1c8883 100644 --- a/utils/es/random.texy +++ b/utils/es/random.texy @@ -1,5 +1,5 @@ -Generador de cadenas aleatorias -******************************* +Generación de cadenas aleatorias +******************************** .[perex] [api:Nette\Utils\Random] es una clase estática para generar cadenas pseudoaleatorias criptográficamente seguras. @@ -12,10 +12,10 @@ composer require nette/utils ``` -generate(int $length=10, string $charlist='0-9a-z'): string .[method] ---------------------------------------------------------------------- +generate(int $length=10, string $charlist=`'0-9a-z'`): string .[method] +----------------------------------------------------------------------- -Genera una cadena aleatoria de longitud dada a partir de los caracteres especificados en el segundo argumento. Admite intervalos, como `0-9` o `A-Z`. +Genera una cadena aleatoria de la longitud dada a partir de los caracteres especificados por el parámetro `$charlist`. También se pueden usar intervalos escritos como `0-9`. ```php use Nette\Utils\Random; diff --git a/utils/es/reflection.texy b/utils/es/reflection.texy index ad930fdff5..514c27b8b5 100644 --- a/utils/es/reflection.texy +++ b/utils/es/reflection.texy @@ -1,8 +1,8 @@ -Reflexión PHP -************* +Reflexión de PHP +**************** .[perex] -[api:Nette\Utils\Reflection] es una clase estática con funciones útiles para PHP reflection. Su propósito es arreglar fallas en clases nativas y unificar el comportamiento a través de diferentes versiones de PHP. +[api:Nette\Utils\Reflection] es una clase estática con funciones útiles para la reflexión de PHP. Su tarea es corregir las deficiencias de las clases nativas y unificar el comportamiento en diferentes versiones de PHP. Instalación: @@ -11,7 +11,7 @@ Instalación: composer require nette/utils ``` -Todos los ejemplos asumen que el siguiente alias de clase está definido: +Todos los ejemplos asumen que se ha creado un alias: ```php use Nette\Utils\Reflection; @@ -21,13 +21,13 @@ use Nette\Utils\Reflection; areCommentsAvailable(): bool .[method] -------------------------------------- -Averigua si reflection tiene acceso a los comentarios de PHPdoc. Los comentarios pueden no estar disponibles debido a la caché de opcode, véase por ejemplo la directiva [opcache.save-comments |https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments]. +Comprueba si la reflexión tiene acceso a los comentarios de PHPdoc. Los comentarios pueden no estar disponibles debido a la caché de opcode, ver, por ejemplo, la directiva [opcache.save-comments|https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments]. expandClassName(string $name, ReflectionClass $context): string .[method] ------------------------------------------------------------------------- -Expande el `$name` de la clase a nombre completo en el contexto del `$context`, es decir, en el contexto de su espacio de nombres y alias definidos. Así, devuelve cómo el analizador sintáctico de PHP entendería `$name` si estuviera escrito en el cuerpo de `$context`. +Expande el nombre de la clase `$name` a su nombre completo en el contexto de la clase `$context`, es decir, en el contexto de su espacio de nombres y alias definidos. Básicamente, dice cómo el analizador de PHP entendería `$name` si estuviera escrito en el cuerpo de la clase `$context`. ```php namespace Foo; @@ -47,9 +47,9 @@ Reflection::expandClassName('Baz', $context); // 'Foo\Baz' getMethodDeclaringMethod(ReflectionMethod $method): ReflectionMethod .[method] ------------------------------------------------------------------------------ -Devuelve una reflexión de un método que contiene una declaración de `$method`. Normalmente, cada método es su propia declaración, pero el cuerpo del método también puede estar en el trait y bajo un nombre diferente. +Devuelve la reflexión del método que contiene la declaración del método `$method`. Normalmente, cada método es su propia declaración, pero el cuerpo del método también puede estar en un trait y bajo un nombre diferente. -Debido a que PHP no proporciona suficiente información para determinar la declaración real, Nette usa su propia heurística, la cual **debería ser** confiable. +Dado que PHP no proporciona suficiente información para determinar la declaración real, Nette utiliza su propia heurística, que **debería ser** fiable. ```php trait DemoTrait @@ -76,9 +76,9 @@ Reflection::getMethodDeclaringMethod($method); // ReflectionMethod('DemoTrait::f getPropertyDeclaringClass(ReflectionProperty $prop): ReflectionClass .[method] ------------------------------------------------------------------------------ -Devuelve un reflejo de una clase o trait que contiene una declaración de propiedad `$prop`. La propiedad también puede ser declarada en el trait. +Devuelve la reflexión de la clase o trait que contiene la declaración de la propiedad `$prop`. La propiedad también puede declararse en un trait. -Debido a que PHP no proporciona suficiente información para determinar la declaración real, Nette usa su propia heurística, la cual **no es** confiable. +Dado que PHP no proporciona suficiente información para determinar la declaración real, Nette utiliza su propia heurística, que **no es** fiable. ```php trait DemoTrait @@ -96,19 +96,19 @@ $prop = new ReflectionProperty(DemoClass::class, 'foo'); Reflection::getPropertyDeclaringClass($prop); // ReflectionClass('DemoTrait') ``` -/--comment - - - - - - - +isBuiltinType(string $type): bool .[method deprecated] +------------------------------------------------------ +Comprueba si `$type` es un tipo incorporado de PHP. De lo contrario, es un nombre de clase. +```php +Reflection::isBuiltinType('string'); // true +Reflection::isBuiltinType('Foo'); // false +``` -\-- +.[note] +Use [Nette\Utils\Validator::isBuiltinType() |validators#isBuiltinType]. toString($reflection): string .[method] diff --git a/utils/es/smartobject.texy b/utils/es/smartobject.texy index b8824ec24d..adf9a9c357 100644 --- a/utils/es/smartobject.texy +++ b/utils/es/smartobject.texy @@ -1,8 +1,8 @@ -SmartObject y StaticClass -************************* +SmartObject +*********** .[perex] -SmartObject añade soporte para *propiedades* a las clases PHP. StaticClass se utiliza para denotar clases estáticas. +SmartObject mejoró el comportamiento de los objetos en PHP durante años. Desde la versión PHP 8.4, todas sus funciones ya forman parte del propio PHP, completando así su misión histórica como pionero en el enfoque moderno de objetos en PHP. Instalación: @@ -11,19 +11,64 @@ Instalación: composer require nette/utils ``` +SmartObject nació en 2007 como una solución revolucionaria a las deficiencias del modelo de objetos de PHP de entonces. En un momento en que PHP sufría numerosos problemas de diseño de objetos, aportó mejoras significativas y simplificó el trabajo para los desarrolladores. Se convirtió en una parte legendaria del framework Nette. Ofrecía funcionalidades que PHP no adquirió hasta muchos años después, desde el control de acceso a las propiedades de los objetos hasta sofisticado *syntax sugar*. Con la llegada de PHP 8.4, completó su misión histórica al convertirse todas sus funciones en parte nativa del lenguaje. Se adelantó al desarrollo de PHP la notable cifra de 17 años. -Propiedades, getters y setters .[#toc-properties-getters-and-setters] -===================================================================== +Técnicamente, SmartObject ha experimentado una interesante evolución. Originalmente se implementó como la clase `Nette\Object`, de la que otras clases heredaban la funcionalidad necesaria. Un cambio fundamental llegó con PHP 5.4, que introdujo el soporte para traits. Esto permitió la transformación en el trait `Nette\SmartObject`, lo que aportó una mayor flexibilidad: los desarrolladores podían utilizar la funcionalidad incluso en clases que ya heredaban de otra clase. Mientras que la clase original `Nette\Object` desapareció con la llegada de PHP 7.2 (que prohibió nombrar clases con la palabra `Object`), el trait `Nette\SmartObject` perdura. -En los lenguajes modernos orientados a objetos (por ejemplo, C#, Python, Ruby, JavaScript), el término *propiedad* se refiere a [miembros especiales de las clases |https://en.wikipedia.org/wiki/Property_(programming)] que parecen variables pero que en realidad están representados por métodos. Cuando se asigna o se lee el valor de esta "variable", se llama al método correspondiente (llamado getter o setter). Esto es muy práctico, nos da un control total sobre el acceso a las variables. Podemos validar la entrada o generar resultados sólo cuando la propiedad es leída. +Repasemos las características que ofrecieron en su día `Nette\Object` y más tarde `Nette\SmartObject`. Cada una de estas funciones representó en su momento un importante avance en el campo de la programación orientada a objetos en PHP. -Las propiedades PHP no están soportadas, pero trait `Nette\SmartObject` puede imitarlas. ¿Cómo usarlo? -- Añade una anotación a la clase de la forma `@property $xyz` -- Crea un getter llamado `getXyz()` o `isXyz()`, un setter llamado `setXyz()` -- El getter y el setter deben ser *public* o *protected* y son opcionales, por lo que puede haber una propiedad *read-only* o *write-only +Estados de error consistentes +----------------------------- +Uno de los problemas más apremiantes de los primeros PHP era el comportamiento inconsistente al trabajar con objetos. `Nette\Object` trajo orden y previsibilidad a este caos. Veamos cómo era el comportamiento original de PHP: -Utilizaremos la propiedad de la clase Circle para asegurarnos de que sólo se introducen números no negativos en la variable `$radius`. Sustituye `public $radius` por propiedad: +```php +echo $obj->undeclared; // E_NOTICE, más tarde E_WARNING +$obj->undeclared = 1; // pasa silenciosamente sin notificación +$obj->unknownMethod(); // Error fatal (no capturable mediante try/catch) +``` + +Un error fatal terminaba la aplicación sin posibilidad alguna de reaccionar. La escritura silenciosa en miembros inexistentes sin notificación podía provocar errores graves difíciles de detectar. `Nette\Object` capturaba todos estos casos y lanzaba la excepción `MemberAccessException`, lo que permitía a los programadores reaccionar a los errores y solucionarlos. + +```php +echo $obj->undeclared; // lanza Nette\MemberAccessException +$obj->undeclared = 1; // lanza Nette\MemberAccessException +$obj->unknownMethod(); // lanza Nette\MemberAccessException +``` + +Desde PHP 7.0, el lenguaje ya no causa errores fatales no capturables, y desde PHP 8.2, el acceso a miembros no declarados se considera un error. + + +Ayuda "¿Quisiste decir?" +------------------------ +`Nette\Object` introdujo una característica muy útil: ayuda inteligente para errores tipográficos. Cuando un desarrollador cometía un error en el nombre de un método o variable, no solo informaba del error, sino que también ofrecía una mano amiga sugiriendo el nombre correcto. Este icónico mensaje, conocido como "¿quisiste decir?", ahorró a los programadores horas de búsqueda de errores tipográficos: + +```php +class Foo extends Nette\Object +{ + public static function from($var) + { + } +} + +$foo = Foo::form($var); +// lanza Nette\MemberAccessException +// "Call to undefined static method Foo::form(), did you mean from()?" +``` + +Aunque el PHP actual no tiene una función similar a "¿quisiste decir?", [Tracy|tracy:] puede añadir este texto a los errores. E incluso [corregir automáticamente |tracy:open-files-in-ide#Demostraciones] tales errores. + + +Propiedades con acceso controlado +--------------------------------- +Una innovación significativa que SmartObject trajo a PHP fueron las propiedades con acceso controlado. Este concepto, común en lenguajes como C# o Python, permitió a los desarrolladores controlar de forma elegante el acceso a los datos del objeto y asegurar su consistencia. Las propiedades son una herramienta potente en la programación orientada a objetos. Funcionan como variables, pero en realidad están representadas por métodos (getters y setters). Esto permite validar entradas o generar valores justo en el momento de la lectura. + +Para usar propiedades, debes: +- Añadir a la clase una anotación con el formato `@property $xyz` +- Crear un getter con el nombre `getXyz()` o `isXyz()`, un setter con el nombre `setXyz()` +- Asegurarte de que el getter y el setter sean `public` o `protected`. Son opcionales, por lo que pueden existir como propiedades de *solo lectura* (*read-only*) o *solo escritura* (*write-only*) + +Veamos un ejemplo práctico con la clase `Circle`, donde usaremos propiedades para asegurar que el radio sea siempre un número no negativo. Reemplazaremos la propiedad pública `$radius` original por una propiedad: ```php /** @@ -34,22 +79,22 @@ class Circle { use Nette\SmartObject; - private float $radius = 0.0; // not public + private float $radius = 0.0; // ¡no es pública! - // getter for property $radius + // getter para la propiedad $radius protected function getRadius(): float { return $this->radius; } - // setter for property $radius + // setter para la propiedad $radius protected function setRadius(float $radius): void { - // sanitizing value before saving it + // saneamos el valor antes de guardarlo $this->radius = max(0.0, $radius); } - // getter for property $visible + // getter para la propiedad $visible protected function isVisible(): bool { return $this->radius > 0; @@ -57,84 +102,30 @@ class Circle } $circle = new Circle; -$circle->radius = 10; // actually calls setRadius(10) -echo $circle->radius; // calls getRadius() -echo $circle->visible; // calls isVisible() +$circle->radius = 10; // en realidad llama a `setRadius(10)` +echo $circle->radius; // llama a `getRadius()` +echo $circle->visible; // llama a `isVisible()` ``` -Las propiedades son principalmente "azúcar sintáctico"((syntactic sugar)), que pretende hacer la vida del programador más dulce simplificando el código. Si no las quieres, no tienes por qué usarlas. - - -Clases estáticas .[#toc-static-classes] -======================================= - -Las clases estáticas, es decir, las clases que no están destinadas a ser instanciadas, pueden marcarse con el rasgo `Nette\StaticClass`: +Desde PHP 8.4, se puede lograr la misma funcionalidad usando *property hooks*, que ofrecen una sintaxis mucho más elegante y concisa: ```php -class Strings +class Circle { - use Nette\StaticClass; -} -``` - -Cuando se intenta crear una instancia, se lanza la excepción `Error`, indicando que la clase es estática. - - -Un vistazo a la historia .[#toc-a-look-into-the-history] -======================================================== - -SmartObject solía mejorar y arreglar el comportamiento de las clases de muchas maneras, pero la evolución de PHP ha hecho que la mayoría de las características originales sean redundantes. Así que lo siguiente es una mirada a la historia de cómo han evolucionado las cosas. - -Desde el principio, el modelo de objetos de PHP sufrió de un número de serios defectos e ineficiencias. Este fue el motivo de la creación de la clase `Nette\Object` (en 2007), que intentaba remediarlos y mejorar la experiencia de uso de PHP. Fue suficiente para que otras clases heredaran de ella y obtuvieran los beneficios que aportaba. Cuando PHP 5.4 llegó con soporte para traits, la clase `Nette\Object` fue reemplazada por `Nette\SmartObject`. Así, ya no era necesario heredar de un ancestro común. Además, trait podía usarse en clases que ya heredaban de otra clase. El fin definitivo de `Nette\Object` llegó con el lanzamiento de PHP 7.2, que prohibió que las clases se llamaran `Object`. - -A medida que avanzaba el desarrollo de PHP, el modelo de objetos y las capacidades del lenguaje fueron mejorando. Las funciones individuales de la clase `SmartObject` se volvieron redundantes. Desde el lanzamiento de PHP 8.2, la única característica que aún no está soportada directamente en PHP es la capacidad de usar las llamadas [propiedades |#Properties, getters and setters]. - -¿Qué características ofrecían `Nette\Object` y `Nette\Object`? He aquí un resumen. (Los ejemplos usan la clase `Nette\Object`, pero la mayoría de las propiedades también se aplican al rasgo `Nette\SmartObject` ). - - -Errores incoherentes .[#toc-inconsistent-errors] ------------------------------------------------- -PHP tenía un comportamiento inconsistente al acceder a miembros no declarados. El estado en el momento de `Nette\Object` era el siguiente: - -```php -echo $obj->undeclared; // E_NOTICE, later E_WARNING -$obj->undeclared = 1; // passes silently without reporting -$obj->unknownMethod(); // Fatal error (not catchable by try/catch) -``` - -Un error fatal terminaba la aplicación sin posibilidad de reaccionar. La escritura silenciosa en miembros inexistentes sin previo aviso podía dar lugar a errores graves difíciles de detectar. `Nette\Object` Todos estos casos eran detectados y se lanzaba una excepción `MemberAccessException`. - -```php -echo $obj->undeclared; // throw Nette\MemberAccessException -$obj->undeclared = 1; // throw Nette\MemberAccessException -$obj->unknownMethod(); // throw Nette\MemberAccessException -``` -Desde PHP 7.0, PHP ya no causa errores fatales no capturables, y acceder a miembros no declarados ha sido un error desde PHP 8.2. - - -¿Te refieres a? .[#toc-did-you-mean] ------------------------------------- -Si se lanzaba un error `Nette\MemberAccessException`, quizás debido a un error tipográfico al acceder a una variable de objeto o al llamar a un método, `Nette\Object` intentaba dar una pista en el mensaje de error sobre cómo solucionar el error, en la forma del icónico apéndice "¿querías decir?". + public float $radius = 0.0 { + set => max(0.0, $value); + } -```php -class Foo extends Nette\Object -{ - public static function from($var) - { + public bool $visible { + get => $this->radius > 0; } } - -$foo = Foo::form($var); -// throw Nette\MemberAccessException -// "Call to undefined static method Foo::form(), did you mean from()?" ``` -Puede que el PHP actual no tenga ninguna forma de "¿querías decir?", pero [Tracy |tracy:] añade este apéndice a los errores. E incluso puede [corregir |tracy:open-files-in-ide#toc-demos] esos errores por sí mismo. - -Métodos de extensión .[#toc-extension-methods] ----------------------------------------------- -Inspirado en los métodos de extensión de C#. Ofrecen la posibilidad de añadir nuevos métodos a clases existentes. Por ejemplo, podrías añadir el método `addDateTime()` a un formulario para añadir tu propio DateTimePicker. +Métodos de extensión +-------------------- +`Nette\Object` introdujo otro concepto interesante en PHP inspirado en lenguajes de programación modernos: los métodos de extensión (*extension methods*). Esta característica, tomada de C#, permitió a los desarrolladores extender de forma elegante clases existentes con nuevos métodos sin necesidad de modificarlas ni heredar de ellas. Por ejemplo, podías añadir un método `addDateTime()` a un formulario que añadiera un `DateTimePicker` personalizado: ```php Form::extensionMethod( @@ -146,22 +137,22 @@ $form = new Form; $form->addDateTime('date'); ``` -Los métodos de extensión resultaron ser poco prácticos porque sus nombres no eran autocompletados por los editores, sino que informaban de que el método no existía. Por lo tanto, su soporte fue descontinuado. +Los métodos de extensión resultaron ser poco prácticos porque sus nombres no eran sugeridos por los editores de código; al contrario, informaban que el método no existía. Por lo tanto, se dejó de darles soporte. Hoy en día, es más común usar composición o herencia para ampliar la funcionalidad de las clases. -Obtención del nombre de la clase .[#toc-getting-the-class-name] ---------------------------------------------------------------- +Obtención del nombre de la clase +-------------------------------- +Para obtener el nombre de la clase, SmartObject ofrecía un método sencillo: ```php -$class = $obj->getClass(); // using Nette\Object -$class = $obj::class; // since PHP 8.0 +$class = $obj->getClass(); // usando Nette\Object +$class = $obj::class; // desde PHP 8.0 ``` -Acceso a la reflexión y a las anotaciones .[#toc-access-to-reflection-and-annotations] --------------------------------------------------------------------------------------- - -`Nette\Object` ofrece acceso a la reflexión y a las anotaciones mediante los métodos `getReflection()` y `getAnnotation()`: +Acceso a la reflexión y anotaciones +----------------------------------- +`Nette\Object` ofrecía acceso a la reflexión y a las anotaciones usando los métodos `getReflection()` y `getAnnotation()`. Este enfoque simplificó significativamente el trabajo con la metainformación de las clases: ```php /** @@ -173,10 +164,10 @@ class Foo extends Nette\Object $obj = new Foo; $reflection = $obj->getReflection(); -$reflection->getAnnotation('author'); // returns 'John Doe +$reflection->getAnnotation('author'); // devuelve `'John Doe'` ``` -A partir de PHP 8.0, es posible acceder a meta-información en forma de atributos: +Desde PHP 8.0, es posible acceder a la metainformación en forma de atributos, que ofrecen aún más posibilidades y un mejor control de tipos: ```php #[Author('John Doe')] @@ -190,10 +181,9 @@ $reflection->getAttributes(Author::class)[0]; ``` -Método getters .[#toc-method-getters] -------------------------------------- - -`Nette\Object` ofrecían una forma elegante de tratar los métodos como si fueran variables: +Getters de método +----------------- +`Nette\Object` ofrecía una forma elegante de pasar métodos como si fueran variables: ```php class Foo extends Nette\Object @@ -209,7 +199,7 @@ $method = $obj->adder; echo $method(2, 3); // 5 ``` -A partir de PHP 8.1, se puede utilizar la llamada "sintaxis callable de primera clase":https://www.php.net/manual/en/functions.first_class_callable_syntax: +Desde PHP 8.1, es posible usar la llamada "sintaxis callable de primera clase":https://www.php.net/manual/en/functions.first_class_callable_syntax, que lleva este concepto aún más lejos: ```php $obj = new Foo; @@ -218,10 +208,9 @@ echo $method(2, 3); // 5 ``` -Eventos .[#toc-events] ----------------------- - -`Nette\Object` ofrece azúcar sintáctico para activar el [evento |nette:glossary#events]: +Eventos +------- +SmartObject ofrece una sintaxis simplificada para trabajar con [eventos |nette:glossary#Eventos]. Los eventos permiten a los objetos informar a otras partes de la aplicación sobre cambios en su estado: ```php class Circle extends Nette\Object @@ -231,12 +220,12 @@ class Circle extends Nette\Object public function setRadius(float $radius): void { $this->onChange($this, $radius); - $this->radius = $radius + $this->radius = $radius; } } ``` -El código `$this->onChange($this, $radius)` es equivalente al siguiente: +El código `$this->onChange($this, $radius);` es equivalente al siguiente bucle: ```php foreach ($this->onChange as $callback) { @@ -244,7 +233,7 @@ foreach ($this->onChange as $callback) { } ``` -Por claridad, recomendamos evitar el método mágico `$this->onChange()`. Un buen sustituto es [Nette\Utils\Arrays::invoke |arrays#invoke]: +Por razones de claridad, recomendamos evitar el método mágico `$this->onChange()`. Una alternativa práctica es, por ejemplo, la función [Nette\Utils\Arrays::invoke |arrays#invoke]: ```php Nette\Utils\Arrays::invoke($this->onChange, $this, $radius); diff --git a/utils/es/staticclass.texy b/utils/es/staticclass.texy new file mode 100644 index 0000000000..b9906f1958 --- /dev/null +++ b/utils/es/staticclass.texy @@ -0,0 +1,21 @@ +Clases estáticas +**************** + +.[perex] +`StaticClass` se utiliza para marcar clases estáticas. + + +Instalación: + +```shell +composer require nette/utils +``` + +Las clases estáticas, es decir, clases que no están destinadas a ser instanciadas, se pueden marcar con el trait [api:Nette\StaticClass]: + +```php +class Strings +{ + use Nette\StaticClass; +} +``` diff --git a/utils/es/strings.texy b/utils/es/strings.texy index 97a3af980b..3e68b23bc4 100644 --- a/utils/es/strings.texy +++ b/utils/es/strings.texy @@ -1,8 +1,8 @@ -Funciones de cadena -******************* +Trabajando con cadenas +********************** .[perex] -[api:Nette\Utils\Strings] es una clase estática que contiene muchas funciones útiles para trabajar con cadenas codificadas en UTF-8. +[api:Nette\Utils\Strings] es una clase estática con funciones útiles para trabajar con cadenas, principalmente en codificación UTF-8. Instalación: @@ -11,15 +11,15 @@ Instalación: composer require nette/utils ``` -Todos los ejemplos asumen que el siguiente alias de clase está definido: +Todos los ejemplos asumen que se ha creado un alias: ```php use Nette\Utils\Strings; ``` -Caja de letras .[#toc-letter-case] -================================== +Cambiar mayúsculas y minúsculas +=============================== Estas funciones requieren la extensión PHP `mbstring`. @@ -27,67 +27,67 @@ Estas funciones requieren la extensión PHP `mbstring`. lower(string $s): string .[method] ---------------------------------- -Convierte todos los caracteres de la cadena UTF-8 a minúsculas. +Convierte una cadena UTF-8 a minúsculas. ```php -Strings::lower('Hello world'); // 'hello world' +Strings::lower('Dobrý den'); // 'dobrý den' ``` upper(string $s): string .[method] ---------------------------------- -Convierte todos los caracteres de una cadena UTF-8 a mayúsculas. +Convierte una cadena UTF-8 a mayúsculas. ```php -Strings::upper('Hello world'); // 'HELLO WORLD' +Strings::upper('Dobrý den'); // 'DOBRÝ DEN' ``` firstUpper(string $s): string .[method] --------------------------------------- -Convierte el primer carácter de una cadena UTF-8 a mayúsculas y deja el resto de caracteres sin cambios. +Convierte la primera letra de una cadena UTF-8 a mayúscula, dejando el resto sin cambios. ```php -Strings::firstUpper('hello world'); // 'Hello world' +Strings::firstUpper('dobrý den'); // 'Dobrý den' ``` firstLower(string $s): string .[method] --------------------------------------- -Convierte el primer carácter de una cadena UTF-8 a minúsculas y deja el resto de caracteres sin cambios. +Convierte la primera letra de una cadena UTF-8 a minúscula, dejando el resto sin cambios. ```php -Strings::firstLower('Hello world'); // 'hello world' +Strings::firstLower('Dobrý den'); // 'dobrý den' ``` capitalize(string $s): string .[method] --------------------------------------- -Convierte el primer carácter de cada palabra de una cadena UTF-8 a mayúsculas y los demás a minúsculas. +Convierte la primera letra de cada palabra en una cadena UTF-8 a mayúscula y el resto a minúsculas. ```php -Strings::capitalize('Hello world'); // 'Hello World' +Strings::capitalize('Dobrý den'); // 'Dobrý Den' ``` -Editar una cadena .[#toc-editing-a-string] -========================================== +Modificar la cadena +=================== normalize(string $s): string .[method] -------------------------------------- -Elimina los caracteres de control, normaliza los saltos de línea a `\n`, elimina las líneas en blanco iniciales y finales, recorta los espacios al final de las líneas, normaliza UTF-8 a la forma normal de NFC. +Elimina caracteres de control, normaliza los saltos de línea a `\n`, elimina las líneas en blanco iniciales y finales, elimina los espacios finales en las líneas y normaliza UTF-8 a la forma normal NFC. unixNewLines(string $s): string .[method] ----------------------------------------- -Convierte los saltos de línea en `\n` utilizados en los sistemas Unix. Los saltos de línea son: `\n`, `\r`, `\r\n`, U+2028 separador de línea, U+2029 separador de párrafo. +Convierte los saltos de línea a `\n` utilizados en sistemas Unix. Los saltos de línea son: `\n`, `\r`, `\r\n`, separador de línea U+2028, separador de párrafo U+2029. ```php $unixLikeLines = Strings::unixNewLines($string); @@ -97,42 +97,42 @@ $unixLikeLines = Strings::unixNewLines($string); platformNewLines(string $s): string .[method] --------------------------------------------- -Convierte los saltos de línea en caracteres específicos de la plataforma actual, es decir, `\r\n` en Windows y `\n` en otros sitios. Los saltos de línea son `\n`, `\r`, `\r\n`, U+2028 separador de línea, U+2029 separador de párrafo. +Convierte los saltos de línea a los caracteres específicos de la plataforma actual, es decir, `\r\n` en Windows y `\n` en otros sistemas. Los saltos de línea son: `\n`, `\r`, `\r\n`, separador de línea U+2028, separador de párrafo U+2029. ```php $platformLines = Strings::platformNewLines($string); ``` -webalize(string $s, string $charlist=null, bool $lower=true): string .[method] ------------------------------------------------------------------------------- +webalize(string $s, ?string $charlist=null, bool $lower=true): string .[method] +------------------------------------------------------------------------------- -Modifica la cadena UTF-8 a la forma utilizada en la URL, es decir, elimina los diacríticos y sustituye todos los caracteres excepto las letras del alfabeto inglés y los números por un guión. +Modifica una cadena UTF-8 al formato utilizado en las URL, es decir, elimina los diacríticos y reemplaza todos los caracteres, excepto las letras del alfabeto inglés y los números, por guiones. ```php -Strings::webalize('žluťoučký kůň'); // 'zlutoucky-kun' +Strings::webalize('náš produkt'); // 'nas-produkt' ``` -También pueden conservarse otros caracteres, pero deben pasarse como segundo argumento. +Si se deben conservar otros caracteres, se pueden especificar en el segundo parámetro. ```php -Strings::webalize('10. image_id', '._'); // '10.-image_id' +Strings::webalize('10. obrázek_id', '._'); // '10.-obrazek_id' ``` -El tercer argumento puede suprimir la conversión de la cadena a minúsculas. +El tercer parámetro puede desactivar la conversión a minúsculas. ```php -Strings::webalize('Hello world', null, false); // 'Hello-world' +Strings::webalize('Dobrý den', null, false); // 'Dobry-den' ``` .[caution] Requiere la extensión PHP `intl`. -trim(string $s, string $charlist=null): string .[method] --------------------------------------------------------- +trim(string $s, ?string $charlist=null): string .[method] +--------------------------------------------------------- -Elimina todos los espacios a la izquierda y a la derecha (o los caracteres pasados como segundo argumento) de una cadena codificada en UTF-8. +Elimina espacios (u otros caracteres especificados por el segundo parámetro) del principio y final de una cadena UTF-8. ```php Strings::trim(' Hello '); // 'Hello' @@ -142,21 +142,21 @@ Strings::trim(' Hello '); // 'Hello' truncate(string $s, int $maxLen, string $append=`'…'`): string .[method] ------------------------------------------------------------------------ -Trunca una cadena UTF-8 a la longitud máxima dada, intentando no dividir palabras enteras. Sólo si la cadena está truncada, se añade una elipsis (u otra cosa establecida con el tercer argumento) a la cadena. +Trunca una cadena UTF-8 a la longitud máxima especificada, intentando conservar palabras completas. Si la cadena se acorta, añade puntos suspensivos al final (se puede cambiar mediante el tercer parámetro). ```php -$text = 'Hello, how are you today?'; -Strings::truncate($text, 5); // 'Hell…' -Strings::truncate($text, 20); // 'Hello, how are you…' -Strings::truncate($text, 30); // 'Hello, how are you today?' -Strings::truncate($text, 20, '~'); // 'Hello, how are you~' +$text = 'Řekněte, jak se máte?'; +Strings::truncate($text, 5); // 'Řekn…' +Strings::truncate($text, 20); // 'Řekněte, jak se…' +Strings::truncate($text, 30); // 'Řekněte, jak se máte?' +Strings::truncate($text, 20, '~'); // 'Řekněte, jak se~' ``` indent(string $s, int $level=1, string $indentationChar=`"\t"`): string .[method] --------------------------------------------------------------------------------- -Indenta un texto multilínea desde la izquierda. El segundo argumento establece cuántos caracteres de sangría deben utilizarse, mientras que la propia sangría es el tercer argumento (*tab* por defecto). +Indenta un texto multilínea desde la izquierda. El número de indentaciones lo determina el segundo parámetro, y el carácter de indentación el tercero (el valor predeterminado es un tabulador). ```php Strings::indent('Nette'); // "\tNette" @@ -167,7 +167,7 @@ Strings::indent('Nette', 2, '+'); // '++Nette' padLeft(string $s, int $length, string $pad=`' '`): string .[method] -------------------------------------------------------------------- -Rellena una cadena UTF-8 con la longitud dada añadiendo la cadena `$pad` al principio. +Rellena una cadena UTF-8 hasta la longitud especificada repitiendo la cadena `$pad` por la izquierda. ```php Strings::padLeft('Nette', 6); // ' Nette' @@ -178,7 +178,7 @@ Strings::padLeft('Nette', 8, '+*'); // '+*+Nette' padRight(string $s, int $length, string $pad=`' '`): string .[method] --------------------------------------------------------------------- -Rellena la cadena UTF-8 a la longitud dada añadiendo la cadena `$pad` al final. +Rellena una cadena UTF-8 hasta la longitud especificada repitiendo la cadena `$pad` por la derecha. ```php Strings::padRight('Nette', 6); // 'Nette ' @@ -186,10 +186,10 @@ Strings::padRight('Nette', 8, '+*'); // 'Nette+*+' ``` -substring(string $s, int $start, int $length=null): string .[method] --------------------------------------------------------------------- +substring(string $s, int $start, ?int $length=null): string .[method] +--------------------------------------------------------------------- -Devuelve una parte de la cadena UTF-8 especificada por la posición inicial `$start` y la longitud `$length`. Si `$start` es negativo, la cadena devuelta comenzará en el carácter `$start`'th desde el final de la cadena. +Devuelve una parte de la cadena UTF-8 `$s` especificada por la posición inicial `$start` y la longitud `$length`. Si `$start` es negativo, la cadena devuelta comenzará en la posición `-`$start` desde el final. ```php Strings::substring('Nette Framework', 0, 5); // 'Nette' @@ -201,7 +201,7 @@ Strings::substring('Nette Framework', -4); // 'work' reverse(string $s): string .[method] ------------------------------------ -Invierte la cadena UTF-8. +Invierte una cadena UTF-8. ```php Strings::reverse('Nette'); // 'etteN' @@ -211,77 +211,77 @@ Strings::reverse('Nette'); // 'etteN' length(string $s): int .[method] -------------------------------- -Devuelve el número de caracteres (no bytes) de una cadena UTF-8. +Devuelve el número de caracteres (no de bytes) en una cadena UTF-8. -Es decir, el número de puntos de código Unicode, que puede diferir del número de grafemas. +Este es el número de puntos de código Unicode, que puede diferir del número de grafemas. ```php -Strings::length('Nette'); // 5 -Strings::length('red'); // 3 +Strings::length('Nette'); // 5 +Strings::length('červená'); // 7 ``` -/--comment - - - - - - - - - - - - - - - - - - - - - - - - - - - - +startsWith(string $haystack, string $needle): bool .[method deprecated] +----------------------------------------------------------------------- +Comprueba si la cadena `$haystack` empieza por la cadena `$needle`. +```php +$haystack = 'Začíná'; +$needle = 'Za'; +Strings::startsWith($haystack, $needle); // true +``` +.[note] +Utiliza la función nativa `str_starts_with()`:https://www.php.net/manual/en/function.str-starts-with.php. +endsWith(string $haystack, string $needle): bool .[method deprecated] +--------------------------------------------------------------------- +Comprueba si la cadena `$haystack` termina con la cadena `$needle`. +```php +$haystack = 'Končí'; +$needle = 'čí'; +Strings::endsWith($haystack, $needle); // true +``` +.[note] +Utiliza la función nativa `str_ends_with()`:https://www.php.net/manual/en/function.str-ends-with.php. +contains(string $haystack, string $needle): bool .[method deprecated] +--------------------------------------------------------------------- +Comprueba si la cadena `$haystack` contiene la cadena `$needle`. +```php +$haystack = 'Posluchárna'; +$needle = 'sluch'; +Strings::contains($haystack, $needle); // true +``` -\-- +.[note] +Utiliza la función nativa `str_contains()`:https://www.php.net/manual/en/function.str-contains.php. -compare(string $left, string $right, int $length=null): bool .[method] ----------------------------------------------------------------------- +compare(string $left, string $right, ?int $length=null): bool .[method] +----------------------------------------------------------------------- -Compara dos cadenas UTF-8 o sus partes, sin tener en cuenta las mayúsculas y minúsculas. Si `$length` es nulo, se comparan cadenas enteras; si es negativo, se compara el número correspondiente de caracteres desde el final de las cadenas; en caso contrario, se compara el número correspondiente de caracteres desde el principio. +Compara dos cadenas UTF-8 o partes de ellas sin distinguir entre mayúsculas y minúsculas. Si `$length` es `null`, se comparan las cadenas completas; si es negativo, se compara el número correspondiente de caracteres desde el final de las cadenas; en caso contrario, se compara el número correspondiente de caracteres desde el principio. ```php Strings::compare('Nette', 'nette'); // true -Strings::compare('Nette', 'next', 2); // true - two first characters match -Strings::compare('Nette', 'Latte', -2); // true - two last characters match +Strings::compare('Nette', 'next', 2); // true - coincidencia de los primeros 2 caracteres +Strings::compare('Nette', 'Latte', -2); // true - coincidencia de los últimos 2 caracteres ``` findPrefix(...$strings): string .[method] ----------------------------------------- -Busca el prefijo común de las cadenas o devuelve una cadena vacía si no se ha encontrado el prefijo. +Encuentra el prefijo común de las cadenas. Devuelve una cadena vacía si no se encuentra un prefijo común. ```php Strings::findPrefix('prefix-a', 'prefix-bb', 'prefix-c'); // 'prefix-' @@ -293,7 +293,7 @@ Strings::findPrefix('Nette', 'is', 'great'); // '' before(string $haystack, string $needle, int $nth=1): ?string .[method] ----------------------------------------------------------------------- -Devuelve parte de `$haystack` antes de `$nth` ocurrencia de `$needle` o devuelve `null` si no se encontró la aguja. Un valor negativo significa buscar desde el final. +Devuelve la parte de la cadena `$haystack` que precede a la n-ésima (`$nth`) aparición de la cadena `$needle`. Devuelve `null` si no se encuentra `$needle`. Si `$nth` es negativo, la búsqueda se realiza desde el final de la cadena. ```php Strings::before('Nette_is_great', '_', 1); // 'Nette' @@ -306,7 +306,7 @@ Strings::before('Nette_is_great', '_', 3); // null after(string $haystack, string $needle, int $nth=1): ?string .[method] ---------------------------------------------------------------------- -Devuelve parte de `$haystack` tras la aparición de `$nth` en `$needle` o devuelve `null` si no se ha encontrado `$needle`. El valor negativo de `$nth` significa buscar desde el final. +Devuelve la parte de la cadena `$haystack` que sigue a la n-ésima (`$nth`) aparición de la cadena `$needle`. Devuelve `null` si no se encuentra `$needle`. Si `$nth` es negativo, la búsqueda se realiza desde el final de la cadena. ```php Strings::after('Nette_is_great', '_', 2); // 'great' @@ -319,7 +319,7 @@ Strings::after('Nette_is_great', '_', 3); // null indexOf(string $haystack, string $needle, int $nth=1): ?int .[method] --------------------------------------------------------------------- -Devuelve la posición en caracteres de `$nth` ocurrencia de `$needle` en `$haystack` o `null` si no se encontró `$needle`. El valor negativo de `$nth` significa buscar desde el final. +Devuelve la posición (en caracteres) de la n-ésima (`$nth`) aparición de la cadena `$needle` dentro de la cadena `$haystack`. Devuelve `null` si no se encuentra `$needle`. Si `$nth` es negativo, la búsqueda se realiza desde el final de la cadena. ```php Strings::indexOf('abc abc abc', 'abc', 2); // 4 @@ -328,37 +328,37 @@ Strings::indexOf('abc abc abc', 'd'); // null ``` -Codificación .[#toc-encoding] -============================= +Codificación +============ fixEncoding(string $s): string .[method] ---------------------------------------- -Elimina todos los caracteres UTF-8 no válidos de una cadena. +Elimina los caracteres UTF-8 no válidos de la cadena. ```php $correctStrings = Strings::fixEncoding($string); ``` -/--comment - - - - - - +checkEncoding(string $s): bool .[method deprecated] +--------------------------------------------------- +Comprueba si la cadena es UTF-8 válida. +```php +$isUtf8 = Strings::checkEncoding($string); +``` -\-- +.[note] +Utiliza [Nette\Utils\Validator::isUnicode() |validators#isUnicode]. toAscii(string $s): string .[method] ------------------------------------ -Convierte una cadena UTF-8 a ASCII, es decir, elimina los diacríticos, etc. +Convierte una cadena UTF-8 a ASCII, es decir, elimina diacríticos, etc. ```php Strings::toAscii('žluťoučký kůň'); // 'zlutoucky kun' @@ -371,33 +371,33 @@ Requiere la extensión PHP `intl`. chr(int $code): string .[method] -------------------------------- -Devuelve un carácter específico en UTF-8 desde el punto de código (número en el rango 0x0000..D7FF o 0xE000..10FFFF). +Devuelve un carácter específico en UTF-8 a partir de su punto de código (número en el rango 0x0000..D7FF y 0xE000..10FFFF). ```php -Strings::chr(0xA9); // '©' +Strings::chr(0xA9); // '©' en codificación UTF-8 ``` ord(string $char): int .[method] -------------------------------- -Devuelve un punto de código de carácter específico en UTF-8 (número en el rango 0x0000..D7FF o 0xE000..10FFFF). +Devuelve el punto de código de un carácter específico en UTF-8 (número en el rango 0x0000..D7FF o 0xE000..10FFFF). ```php Strings::ord('©'); // 0xA9 ``` -Expresiones regulares .[#toc-encoding] -====================================== +Expresiones regulares +===================== -La clase Strings proporciona funciones para trabajar con expresiones regulares. A diferencia de las funciones nativas de PHP, tiene una API más comprensible, mejor soporte Unicode y, lo más importante, detección de errores. Cualquier error de compilación o de procesamiento de expresiones lanzará una excepción `Nette\RegexpException`. +La clase `Strings` ofrece funciones para trabajar con expresiones regulares. A diferencia de las funciones nativas de PHP, tienen una API más clara, mejor soporte para Unicode y, sobre todo, detección de errores. Cualquier error durante la compilación o el procesamiento de la expresión lanza una excepción `Nette\RegexpException`. split(string $subject, string $pattern, bool $captureOffset=false, bool $skipEmpty=false, int $limit=-1, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------- -Divide la cadena en matrices según la expresión regular. Las expresiones entre paréntesis también se capturarán y devolverán. +Divide una cadena en un array utilizando una expresión regular. Las expresiones entre paréntesis también se capturan y devuelven. ```php Strings::split('hello, world', '~,\s*~'); @@ -407,7 +407,7 @@ Strings::split('hello, world', '~(,)\s*~'); // ['hello', ',', 'world']`` ``` -Si `$skipEmpty` es `true`, sólo se devolverán los elementos no vacíos: +Si `$skipEmpty` es `true`, solo se devuelven los elementos no vacíos: ```php Strings::split('hello, world, ', '~,\s*~'); @@ -417,16 +417,16 @@ Strings::split('hello, world, ', '~,\s*~', skipEmpty: true); // ['hello', 'world'] ``` -Si se especifica `$limit`, sólo se devolverán las subcadenas hasta el límite y el resto de la cadena se colocará en el último elemento. Un límite de -1 o 0 significa que no hay límite. +Si se especifica `$limit`, solo se devuelven subcadenas hasta ese límite, y el resto de la cadena se coloca en el último elemento. Un límite de -1 o 0 significa que no hay límite. ```php Strings::split('hello, world, third', '~,\s*~', limit: 2); // ['hello', 'world, third'] ``` -Si `$utf8` es `true`, la evaluación cambia a modo Unicode. Esto es similar a especificar el modificador `u`. +Si `$utf8` es `true`, la evaluación cambia al modo Unicode. Similar a especificar el modificador `u`. -Si `$captureOffset` es `true`, su posición en la cadena (en bytes; en caracteres si `$utf8` está definido) también se devolverá para cada coincidencia que se produzca. Esto cambia el valor de retorno a un array donde cada elemento es un par formado por la cadena coincidente y su posición. +Si `$captureOffset` es `true`, para cada coincidencia encontrada, también se devolverá su posición en la cadena (en bytes; o en caracteres si se establece `$utf8`). Esto cambia el valor devuelto a un array donde cada elemento es un par formado por la cadena coincidente y su posición. ```php Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true); @@ -440,7 +440,7 @@ Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true, utf8: true); match(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $utf8=false): ?array .[method] -------------------------------------------------------------------------------------------------------------------------------------------------- -Busca en la cadena la parte que coincide con la expresión regular y devuelve una matriz con la expresión encontrada y las subexpresiones individuales, o `null`. +Busca en una cadena una parte que coincida con una expresión regular y devuelve un array con la expresión encontrada y sus subexpresiones individuales, o `null` si no hay coincidencia. ```php Strings::match('hello!', '~\w+(!+)~'); @@ -450,7 +450,7 @@ Strings::match('hello!', '~X~'); // null ``` -Si `$unmatchedAsNull` es `true`, los subpatrones no coincidentes se devuelven como nulos; en caso contrario, se devuelven como una cadena vacía o no se devuelven: +Si `$unmatchedAsNull` es `true`, las subexpresiones no capturadas se devuelven como `null`; de lo contrario, se devuelven como una cadena vacía o no se incluyen en el resultado: ```php Strings::match('hello', '~\w+(!+)?~'); @@ -460,7 +460,7 @@ Strings::match('hello', '~\w+(!+)?~', unmatchedAsNull: true); // ['hello', null] ``` -Si `$utf8` es `true`, la evaluación cambia a modo Unicode. Esto es similar a especificar el modificador `u`: +Si `$utf8` es `true`, la evaluación cambia al modo Unicode. Similar a especificar el modificador `u`: ```php Strings::match('žlutý kůň', '~\w+~'); @@ -470,9 +470,9 @@ Strings::match('žlutý kůň', '~\w+~', utf8: true); // ['žlutý'] ``` -El parámetro `$offset` puede utilizarse para especificar la posición a partir de la cual se inicia la búsqueda (en bytes; en caracteres si se establece `$utf8`). +El parámetro `$offset` se puede usar para especificar la posición desde la cual comenzar la búsqueda (en bytes; o en caracteres si se establece `$utf8`). -Si `$captureOffset` es `true`, para cada coincidencia que se produzca, también se devolverá su posición en la cadena (en bytes; en caracteres si se establece `$utf8`). Esto cambia el valor de retorno a una matriz en la que cada elemento es un par formado por la cadena coincidente y su desplazamiento: +Si `$captureOffset` es `true`, para cada coincidencia encontrada, también se devolverá su posición en la cadena (en bytes; o en caracteres si se establece `$utf8`). Esto cambia el valor devuelto a un array donde cada elemento es un par formado por la cadena coincidente y su desplazamiento (*offset*): ```php Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true); @@ -483,10 +483,10 @@ Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true, utf8: true); ``` -matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false, bool $lazy=false): array|Generator .[method] +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Busca en la cadena todas las ocurrencias que coincidan con la expresión regular y devuelve una matriz de matrices que contiene la expresión encontrada y cada subexpresión. +Busca en una cadena todas las ocurrencias que coincidan con una expresión regular y devuelve un array de arrays, cada uno conteniendo la expresión encontrada y sus subexpresiones individuales. ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~'); @@ -496,7 +496,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~'); ] */ ``` -Si `$patternOrder` es `true`, la estructura de los resultados cambia de modo que el primer elemento es una matriz de coincidencias de patrones completos, el segundo es una matriz de cadenas que coinciden con el primer sub-patrón entre paréntesis, y así sucesivamente: +Si `$patternOrder` es `true`, la estructura de los resultados cambia de modo que el primer elemento es un array de todas las coincidencias completas del patrón, el segundo es un array de las cadenas que coinciden con la primera subexpresión entre paréntesis, y así sucesivamente: ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~', patternOrder: true); @@ -506,7 +506,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~', patternOrder: true); ] */ ``` -Si `$unmatchedAsNull` es `true`, los subpatrones no coincidentes se devuelven como nulos; en caso contrario, se devuelven como una cadena vacía o no se devuelven: +Si `$unmatchedAsNull` es `true`, las subexpresiones no capturadas se devuelven como `null`; de lo contrario, se devuelven como una cadena vacía o no se incluyen en el resultado: ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~', unmatchedAsNull: true); @@ -516,7 +516,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~', unmatchedAsNull: true); ] */ ``` -Si `$utf8` es `true`, la evaluación cambia a modo Unicode. Esto es similar a especificar el modificador `u`: +Si `$utf8` es `true`, la evaluación cambia al modo Unicode. Similar a especificar el modificador `u`: ```php Strings::matchAll('žlutý kůň', '~\w+~'); @@ -532,9 +532,9 @@ Strings::matchAll('žlutý kůň', '~\w+~', utf8: true); ] */ ``` -El parámetro `$offset` puede utilizarse para especificar la posición a partir de la cual se inicia la búsqueda (en bytes; en caracteres si se establece `$utf8`). +El parámetro `$offset` se puede usar para especificar la posición desde la cual comenzar la búsqueda (en bytes; o en caracteres si se establece `$utf8`). -Si `$captureOffset` es `true`, para cada coincidencia que se produzca, también se devolverá su posición en la cadena (en bytes; en caracteres si se establece `$utf8`). Esto cambia el valor de retorno a un array donde cada elemento es un par formado por la cadena coincidente y su posición: +Si `$captureOffset` es `true`, para cada coincidencia encontrada, también se devolverá su posición en la cadena (en bytes; o en caracteres si se establece `$utf8`). Esto cambia el valor devuelto a un array donde cada elemento es un par formado por la cadena coincidente y su posición: ```php Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true); @@ -550,11 +550,21 @@ Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true, utf8: true); ] */ ``` +Si `$lazy` es `true`, la función devuelve un `Generator` en lugar de un array, lo que ofrece importantes ventajas de rendimiento al trabajar con cadenas grandes. El generador permite buscar coincidencias de forma progresiva, en lugar de procesar toda la cadena a la vez. Esto permite trabajar eficientemente incluso con textos de entrada extremadamente grandes. Además, puedes interrumpir el procesamiento en cualquier momento si encuentras la coincidencia deseada, lo que ahorra tiempo de cómputo. + +```php +$matches = Strings::matchAll($largeText, '~\w+~', lazy: true); +foreach ($matches as $match) { + echo "Encontrado: $match[0]\n"; + // El procesamiento puede interrumpirse en cualquier momento +} +``` + replace(string $subject, string|array $pattern, string|callable $replacement='', int $limit=-1, bool $captureOffset=false, bool $unmatchedAsNull=false, bool $utf8=false): string .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Reemplaza todas las ocurrencias que coincidan con la expresión regular. El `$replacement` es una máscara de cadena de reemplazo o una llamada de retorno. +Reemplaza todas las ocurrencias que coinciden con una expresión regular. `$replacement` es una máscara para la cadena de reemplazo o una función *callback*. ```php Strings::replace('hello, world!', '~\w+~', '--'); @@ -564,7 +574,7 @@ Strings::replace('hello, world!', '~\w+~', fn($m) => strrev($m[0])); // 'olleh, dlrow!' ``` -La función también permite reemplazos múltiples pasando una matriz de la forma `patrón => reemplazo` en el segundo parámetro: +La función también permite realizar múltiples reemplazos pasando un array en el segundo parámetro con el formato `patrón => reemplazo`: ```php Strings::replace('hello, world!', [ @@ -574,9 +584,9 @@ Strings::replace('hello, world!', [ // '-- --!' ``` -El parámetro `$limit` limita el número de sustituciones. Límite -1 significa sin límite. +El parámetro `$limit` limita el número de reemplazos realizados. Un límite de -1 significa que no hay límite. -Si `$utf8` es `true`, la evaluación cambia a modo Unicode. Esto es similar a especificar el modificador `u`. +Si `$utf8` es `true`, la evaluación cambia al modo Unicode. Similar a especificar el modificador `u`. ```php Strings::replace('žlutý kůň', '~\w+~', '--'); @@ -586,7 +596,7 @@ Strings::replace('žlutý kůň', '~\w+~', '--', utf8: true); // '-- --' ``` -Si `$captureOffset` es `true`, por cada coincidencia que se produzca, su posición en la cadena (en bytes; en caracteres si se establece `$utf8`) también se pasa a la llamada de retorno. Esto cambia la forma del array pasado, donde cada elemento es un par formado por la cadena coincidente y su posición. +Si `$captureOffset` es `true`, para cada coincidencia encontrada, también se pasará a la función *callback* su posición en la cadena (en bytes; o en caracteres si se establece `$utf8`). Esto cambia la forma del array pasado, donde cada elemento es un par formado por la cadena coincidente y su posición. ```php Strings::replace( @@ -595,7 +605,7 @@ Strings::replace( function (array $m) { dump($m); return ''; }, captureOffset: true, ); -// dumps [['lut', 2]] a [['k', 8]] +// dumps [['lut', 2]] y [['k', 8]] Strings::replace( 'žlutý kůň', @@ -604,10 +614,10 @@ Strings::replace( captureOffset: true, utf8: true, ); -// dumps [['žlutý', 0]] a [['kůň', 6]] +// dumps [['žlutý', 0]] y [['kůň', 6]] ``` -Si `$unmatchedAsNull` es `true`, los subpatrones no coincidentes se pasan a la llamada de retorno como nulos; en caso contrario, se pasan como una cadena vacía o no se pasan: +Si `$unmatchedAsNull` es `true`, las subexpresiones no capturadas se pasan a la función *callback* como `null`; de lo contrario, se pasan como una cadena vacía o no se incluyen: ```php Strings::replace( diff --git a/utils/es/type.texy b/utils/es/type.texy index 50409877a2..5428d9ef90 100644 --- a/utils/es/type.texy +++ b/utils/es/type.texy @@ -1,8 +1,8 @@ -Tipo de PHP -*********** +Tipo PHP +******** .[perex] -[api:Nette\Utils\Type] es una clase de tipo de datos PHP. +[api:Nette\Utils\Type] es una clase para trabajar con tipos de datos de PHP. Instalación: @@ -11,7 +11,7 @@ Instalación: composer require nette/utils ``` -Todos los ejemplos asumen que el siguiente alias de clase está definido: +Todos los ejemplos asumen que se ha creado un alias: ```php use Nette\Utils\Type; @@ -21,7 +21,7 @@ use Nette\Utils\Type; fromReflection($reflection): ?Type .[method] -------------------------------------------- -El método static crea un objeto Type basado en la reflexión. El parámetro puede ser un objeto `ReflectionMethod` o `ReflectionFunction` (devuelve el tipo del valor de retorno) o un objeto `ReflectionParameter` o `ReflectionProperty`. Resuelve `self`, `static` y `parent` al nombre real de la clase. Si el asunto no tiene tipo, devuelve `null`. +Método estático que crea un objeto `Type` basado en la reflexión. El parámetro puede ser un objeto `ReflectionMethod` o `ReflectionFunction` (devuelve el tipo de valor de retorno), o `ReflectionParameter` o `ReflectionProperty`. Traduce `self`, `static` y `parent` al nombre real de la clase. Si el sujeto no tiene tipo, devuelve `null`. ```php class DemoClass @@ -37,7 +37,7 @@ echo Type::fromReflection($prop); // 'DemoClass' fromString(string $type): Type .[method] ---------------------------------------- -El método estático crea el objeto Type según la notación de texto. +Método estático que crea un objeto `Type` a partir de su representación textual. ```php $type = Type::fromString('Foo|Bar'); @@ -48,10 +48,10 @@ echo $type; // 'Foo|Bar' getNames(): (string|array)[] .[method] -------------------------------------- -Devuelve la matriz de subtipos que forman el tipo compuesto como cadenas. +Devuelve un array con los nombres de los subtipos que componen el tipo compuesto. ```php -$type = Type::fromString('string|null'); // nebo '?string' +$type = Type::fromString('string|null'); // o '?string' $type->getNames(); // ['string', 'null'] $type = Type::fromString('(Foo&Bar)|string'); @@ -62,7 +62,7 @@ $type->getNames(); // [['Foo', 'Bar'], 'string'] getTypes(): Type[] .[method] ---------------------------- -Devuelve la matriz de subtipos que forman el tipo compuesto como objetos `Type`: +Devuelve un array de objetos `Type` que representan los subtipos que componen el tipo compuesto: ```php $type = Type::fromString('string|null'); // or '?string' @@ -79,7 +79,7 @@ $type->getTypes(); // [Type::fromString('Foo'), Type::fromString('Bar')] getSingleName(): ?string .[method] ---------------------------------- -Devuelve el nombre del tipo para tipos simples, en caso contrario null. +Si el tipo es simple, devuelve su nombre; de lo contrario, devuelve `null`. ```php $type = Type::fromString('string|null'); @@ -99,14 +99,14 @@ echo $type->getSingleName(); // null isSimple(): bool .[method] -------------------------- -Devuelve si se trata de un tipo simple. Los tipos simples anulables también se consideran tipos simples: +Devuelve si es un tipo simple. Los tipos simples también incluyen tipos nullable simples: ```php $type = Type::fromString('string'); $type->isSimple(); // true $type->isUnion(); // false -$type = Type::fromString('?Foo'); // nebo 'Foo|null' +$type = Type::fromString('?Foo'); // o 'Foo|null' $type->isSimple(); // true $type->isUnion(); // true ``` @@ -115,10 +115,10 @@ $type->isUnion(); // true isUnion(): bool .[method] ------------------------- -Devuelve si se trata de un tipo de unión. +Devuelve si es un tipo de unión. ```php -$type = Type::fromString('Foo&Bar'); +$type = Type::fromString('string|int'); $type->isUnion(); // true ``` @@ -126,11 +126,11 @@ $type->isUnion(); // true isIntersection(): bool .[method] -------------------------------- -Devuelve si es un tipo intersección. +Devuelve si es un tipo de intersección. ```php -$type = Type::fromString('string&int'); +$type = Type::fromString('Foo&Bar'); $type->isIntersection(); // true ``` @@ -138,7 +138,7 @@ $type->isIntersection(); // true isBuiltin(): bool .[method] --------------------------- -Devuelve si el tipo es tanto un tipo simple como un tipo incorporado de PHP. +Devuelve si el tipo es simple y también un tipo incorporado de PHP. ```php $type = Type::fromString('string'); @@ -155,7 +155,7 @@ $type->isBuiltin(); // false isClass(): bool .[method] ------------------------- -Devuelve si el tipo es tanto simple como un nombre de clase. +Devuelve si el tipo es simple y también un nombre de clase. ```php $type = Type::fromString('string'); @@ -172,7 +172,7 @@ $type->isClass(); // false isClassKeyword(): bool .[method] -------------------------------- -Determina si el tipo es uno de los tipos internos `self`, `parent`, `static`. +Devuelve si el tipo es uno de los tipos internos `self`, `parent`, `static`. ```php $type = Type::fromString('self'); @@ -186,7 +186,7 @@ $type->isClassKeyword(); // false allows(string $type): bool .[method] ------------------------------------ -El método `allows()` verifica la compatibilidad de tipos. Por ejemplo, permite comprobar si un valor de un determinado tipo puede pasarse como parámetro. +El método `allows()` verifica la compatibilidad de tipos. Por ejemplo, permite determinar si un valor de un tipo determinado podría pasarse como parámetro. ```php $type = Type::fromString('string|null'); diff --git a/utils/es/validators.texy b/utils/es/validators.texy index a01a9ce39c..fc99c47c1e 100644 --- a/utils/es/validators.texy +++ b/utils/es/validators.texy @@ -2,7 +2,7 @@ Validadores de valores ********************** .[perex] -¿Necesitas verificar rápida y fácilmente que una variable contiene, por ejemplo, una dirección de correo electrónico válida? Entonces le resultará útil [api:Nette\Utils\Validators], una clase estática con funciones útiles para validar valores. +¿Necesitas verificar rápida y fácilmente que una variable contiene, por ejemplo, una dirección de correo electrónico válida? Para eso te será útil [api:Nette\Utils\Validators], una clase estática con funciones útiles para la validación de valores. Instalación: @@ -11,17 +11,17 @@ Instalación: composer require nette/utils ``` -Todos los ejemplos asumen que el siguiente alias de clase está definido: +Todos los ejemplos asumen que se ha creado un alias: ```php use Nette\Utils\Validators; ``` -Uso básico .[#toc-basic-usage] -============================== +Uso básico +========== -La clase `Validators` tiene un número de métodos para validar valores, como [isList() |#isList()], [isUnicode() |#isUnicode()], [isEmail() |#isEmail()], [isUrl() |#isUrl()], etc. para usar en tu código: +La clase dispone de una serie de métodos para comprobar valores, como [#isUnicode()], [#isEmail()], [#isUrl()], etc., que puedes usar en tu código: ```php if (!Validators::isEmail($email)) { @@ -29,7 +29,7 @@ if (!Validators::isEmail($email)) { } ``` -Además, puede verificar si el valor satisface los llamados tipos [esperados |#expected types], que es una cadena donde las opciones individuales están separadas por una barra vertical `|`. Esto facilita la verificación de tipos de unión utilizando [if() |#if()]: +Además, puede verificar si un valor pertenece a los llamados [#tipos esperados], que es una cadena donde las diferentes opciones se separan con una barra vertical `|`. Así podemos verificar fácilmente múltiples tipos usando [#is()]: ```php if (!Validators::is($val, 'int|string|bool')) { @@ -37,81 +37,81 @@ if (!Validators::is($val, 'int|string|bool')) { } ``` -Pero también te da la oportunidad de crear un sistema donde es necesario escribir las expectativas como cadenas (por ejemplo en anotaciones o configuración) y luego verificar de acuerdo a ellas. +Esto también nos da la opción de crear un sistema donde las expectativas se escriben como cadenas (por ejemplo, en anotaciones o configuración) y luego se usan para validar los valores. -También puede declarar [aserción |#assert], que lanza una excepción si no se cumple. +También se puede aplicar una aserción a los tipos esperados usando [#assert()], que lanza una excepción si no se cumple. -Tipos esperados .[#toc-expected-types] -====================================== +Tipos esperados +=============== -Un tipo esperado es una cadena formada por una o más variantes separadas por una barra vertical `|`, similar to writing types in PHP (ie. `'int|string|bool')`. También se permite la notación nulable `?int`. +Los tipos esperados forman una cadena que consta de una o más variantes separadas por una barra vertical `|`, de forma similar a como se escriben los tipos en PHP (p. ej., `'int|string|bool')`. También se acepta la notación *nullable* `?int`. -Una matriz en la que todos los elementos son de un tipo determinado se escribe de la forma `int[]`. +Un array donde todos los elementos son de un tipo determinado se escribe en la forma `int[]`. -Algunos tipos pueden ir seguidos de dos puntos y la longitud `:length` o el rango `:[min]..[max]` por ejemplo, `string:10` (cadena con una longitud de 10 bytes), `float:10..` (número 10 y mayores), `array:..10` (matriz de hasta diez elementos) o `list:10..20` (lista de 10 a 20 elementos), o una expresión regular para `pattern:[0-9]+`. +Algunos tipos pueden ir seguidos de dos puntos y una longitud `:length` o un rango `:[min]..[max]`. Por ejemplo: `string:10` (cadena de 10 bytes de longitud), `float:10..` (número 10 o mayor), `array:..10` (array con hasta diez elementos), `list:10..20` (lista con 10 a 20 elementos), o una expresión regular con `pattern:'[0-9]+'`. Resumen de tipos y reglas: .[wide] -| Tipos PHP || +| Tipos PHP || |-------------------------- -| `array` .{width: 140px} | range for the number of items can be given -| `bool` | -| `float` | rango para el valor puede ser dado -| `int` | rango para el valor puede ser dado -| `null` | -| `object` | +| `array` .{width: 140px} | se puede especificar un rango para el número de elementos +| `bool` | +| `float` | se puede especificar un rango para el valor +| `int` | se puede especificar un rango para el valor +| `null` | +| `object` | | `resource` | -| `scalar` | int\|float\|bool\|string -| `string` | se puede indicar la longitud en bytes +| `scalar` | int\|float\|bool\|string +| `string` | se puede especificar un rango para la longitud en bytes | `callable` | | `iterable` | -| `mixed` | -|------------------------------------------------ -| Pseudotipos || -|------------------------------------------------ -| `list` | [array indexado |#isList], se puede dar un rango para el número de elementos -| `none` | valor vacío: `''`, `null`, `false` -| `number` | int\|float -| `numeric` | [número con representación textual |#isNumeric] -| `numericint`| [número entero con representación textual |#isNumericInt] -| `unicode` | [Cadena UTF-8 |#isUnicode], se puede dar un rango para la longitud en caracteres -|------------------------------------------------ -| Clase de caracteres (no puede ser una cadena vacía) || -|------------------------------------------------ -| `alnum` | todos los caracteres son alfanuméricos -| `alpha` | todos los caracteres son letras `[A-Za-z]` -| `digit` | todos los caracteres son dígitos -| `lower` | todos los caracteres son minúsculas. `[a-z]` -| `space` | todos los caracteres son espacios. -| `upper` | todos los caracteres son mayúsculas. `[A-Z]` -| `xdigit` | todos los caracteres son dígitos hexadecimales. `[0-9A-Fa-f]` +| `mixed` | +|-------------------------- +| pseudo-tipos || |------------------------------------------------ -| Validación de sintaxis || +| `list` | array indexado, se puede especificar un rango para el número de elementos +| `none` | valor vacío: `''`, `null`, `false` +| `number` | int\|float +| `numeric` | [número incluyendo representación textual |#isNumeric] +| `numericint`| [entero incluyendo representación textual |#isNumericInt] +| `unicode` | [cadena UTF-8 |#isUnicode], se puede especificar un rango para la longitud en caracteres +|-------------------------- +| clase de caracteres (no debe ser una cadena vacía) || |------------------------------------------------ -| `pattern` | una expresión regular con la que debe coincidir **toda** la cadena -| `email` | [Correo electrónico |#isEmail] -| `identifier` | [Identificador PHP |#isPhpIdentifier] -| `url` | [URL |#isUrl] -| `uri` | [URI |#isUri] +| `alnum` | todos los caracteres son alfanuméricos +| `alpha` | todos los caracteres son letras `[A-Za-z]` +| `digit` | todos los caracteres son dígitos +| `lower` | todos los caracteres son letras minúsculas `[a-z]` +| `space` | todos los caracteres son espacios +| `upper` | todos los caracteres son letras mayúsculas `[A-Z]` +| `xdigit` | todos los caracteres son dígitos hexadecimales `[0-9A-Fa-f]` +|-------------------------- +| verificación de sintaxis || |------------------------------------------------ -| Validación del entorno || +| `pattern` | expresión regular que debe coincidir con la cadena **completa** +| `email` | [Correo electrónico |#isEmail] +| `identifier`| [Identificador PHP |#isPhpIdentifier] +| `url` | [URL |#isUrl] +| `uri` | [URI |#isUri] +|-------------------------- +| verificación del entorno || |------------------------------------------------ -| `class` | es una clase existente +| `class` | es una clase existente | `interface` | es una interfaz existente | `directory` | es un directorio existente -| `file` | es un archivo existente +| `file` | es un archivo existente -Afirmación .[#toc-assertion] -============================ +Aserciones +========== assert($value, string $expected, string $label='variable'): void .[method] -------------------------------------------------------------------------- -Verifica que el valor es de los [tipos esperados |#expected types] separados por una tubería. Si no es así, lanza la excepción [api:Nette\Utils\AssertionException]. La palabra `variable` del mensaje de excepción puede sustituirse por el parámetro `$label`. +Verifica que el valor pertenezca a uno de los [#tipos esperados] separados por barra vertical. Si no es así, lanza una excepción [api:Nette\Utils\AssertionException]. La palabra `variable` en el texto de la excepción se puede reemplazar por otra mediante el parámetro `$label`. ```php Validators::assert('Nette', 'string:5'); // OK @@ -120,10 +120,10 @@ Validators::assert('Lorem ipsum dolor sit', 'string:78'); ``` -assertField(array $array, string|int $key, string $expected=null, string $label=null): void .[method] ------------------------------------------------------------------------------------------------------ +assertField(array $array, string|int $key, ?string $expected=null, ?string $label=null): void .[method] +------------------------------------------------------------------------------------------------------- -Verifica que el elemento `$key` en el array `$array` es de los [tipos esperados |#expected types] separados por una tubería. Si no es así, lanza la excepción [api:Nette\Utils\AssertionException]. La cadena `item '%' in array` del mensaje de excepción puede sustituirse por el parámetro `$label`. +Verifica si el elemento con la clave `$key` en el array `$array` pertenece a uno de los [#tipos esperados] separados por barra vertical. Si no es así, lanza una excepción [api:Nette\Utils\AssertionException]. La cadena `item '%' in array` en el texto de la excepción se puede reemplazar por otra mediante el parámetro `$label`. ```php $arr = ['foo' => 'Nette']; @@ -136,19 +136,19 @@ Validators::assertField($arr, 'foo', 'int'); ``` -Validadores .[#toc-validators] -============================== +Validadores +=========== is($value, string $expected): bool .[method] -------------------------------------------- -Comprueba si el valor es de los [tipos esperados |#expected types] separados por una tubería. +Verifica si el valor pertenece a uno de los [#tipos esperados] separados por barra vertical. ```php Validators::is(1, 'int|float'); // true Validators::is(23, 'int:0..10'); // false -Validators::is('Nette Framework', 'string:15'); // true, length is 15 bytes +Validators::is('Nette Framework', 'string:15'); // true, la longitud es de 15 bytes Validators::is('Nette Framework', 'string:8..'); // true Validators::is('Nette Framework', 'string:30..40'); // false ``` @@ -157,7 +157,7 @@ Validators::is('Nette Framework', 'string:30..40'); // false isEmail(mixed $value): bool .[method] ------------------------------------- -Verifica que el valor es una dirección de correo electrónico válida. No verifica que el dominio exista realmente, sólo se verifica la sintaxis. La función también cuenta con futuros [TLD |https://en.wikipedia.org/wiki/Top-level_domain], que también pueden estar en unicode. +Verifica si el valor es una dirección de correo electrónico válida. No comprueba si el dominio existe realmente, solo verifica la sintaxis. La función también tiene en cuenta futuros [TLD|https://es.wikipedia.org/wiki/Dominio_de_nivel_superior], que también pueden estar en Unicode. ```php Validators::isEmail('example@nette.org'); // true @@ -169,9 +169,9 @@ Validators::isEmail('nette'); // false isInRange(mixed $value, array $range): bool .[method] ----------------------------------------------------- -Comprueba si el valor está dentro del rango dado `[min, max]`donde se puede omitir el límite superior o inferior (`null`). Se pueden comparar números, cadenas y objetos DateTime. +Verifica si el valor está dentro del rango dado `[min, max]`, donde el límite superior o inferior puede omitirse (estableciéndolo a `null`). Se pueden comparar números, cadenas y objetos `DateTime`. -Si faltan ambos límites (`[null, null]`) o el valor es `null`, devuelve `false`. +Si faltan ambos límites (`[null, null]`) o si el valor es `null`, devuelve `false`. ```php Validators::isInRange(5, [0, 5]); // true @@ -184,7 +184,7 @@ Validators::isInRange(1, [5]); // false isNone(mixed $value): bool .[method] ------------------------------------ -Comprueba si el valor es `0`, `''`, `false` o `null`. +Verifica si el valor es `0`, `''`, `false` o `null`. ```php Validators::isNone(0); // true @@ -198,7 +198,7 @@ Validators::isNone('nette'); // false isNumeric(mixed $value): bool .[method] --------------------------------------- -Comprueba si el valor es un número o un número escrito en una cadena. +Verifica si el valor es un número o un número representado como una cadena. ```php Validators::isNumeric(23); // true @@ -213,7 +213,7 @@ Validators::isNumeric('1e6'); // false isNumericInt(mixed $value): bool .[method] ------------------------------------------ -Comprueba si el valor es un número entero o un número entero escrito en una cadena. +Verifica si el valor es un número entero o un entero representado como una cadena. ```php Validators::isNumericInt(23); // true @@ -227,7 +227,7 @@ Validators::isNumericInt('nette'); // false isPhpIdentifier(string $value): bool .[method] ---------------------------------------------- -Comprueba si el valor es un identificador sintácticamente válido en PHP, por ejemplo para nombres de clases, métodos, funciones, etc. +Verifica si el valor es un identificador sintácticamente válido en PHP (por ejemplo, para nombres de clases, métodos, funciones, etc.). ```php Validators::isPhpIdentifier(''); // false @@ -240,7 +240,7 @@ Validators::isPhpIdentifier('one two'); // false isBuiltinType(string $type): bool .[method] ------------------------------------------- -Determina si `$type` es un tipo incorporado en PHP. De lo contrario, es el nombre de la clase. +Comprueba si `$type` es un tipo incorporado (*builtin*) de PHP. En caso contrario, se asume que es un nombre de clase. ```php Validators::isBuiltinType('string'); // true @@ -251,7 +251,7 @@ Validators::isBuiltinType('Foo'); // false isTypeDeclaration(string $type): bool .[method] ----------------------------------------------- -Comprueba si la declaración de tipo es sintácticamente correcta. +Comprueba si la declaración de tipo dada es sintácticamente válida. ```php Validators::isTypeDeclaration('?string'); // true @@ -268,7 +268,7 @@ Validators::isTypeDeclaration('(A|B)'); // false isClassKeyword(string $type): bool .[method] -------------------------------------------- -Determine si `$type` es uno de los tipos internos `self`, `parent`, `static`. +Comprueba si `$type` es una de las palabras clave internas `self`, `parent`, `static`. ```php Validators::isClassKeyword('self'); // true @@ -279,7 +279,7 @@ Validators::isClassKeyword('Foo'); // false isUnicode(mixed $value): bool .[method] --------------------------------------- -Comprueba si el valor es una cadena UTF-8 válida. +Verifica si el valor es una cadena UTF-8 válida. ```php Validators::isUnicode('nette'); // true @@ -291,7 +291,7 @@ Validators::isUnicode("\xA0"); // false isUrl(mixed $value): bool .[method] ----------------------------------- -Comprueba si el valor es una dirección URL válida. +Verifica si el valor es una dirección URL válida. ```php Validators::isUrl('https://nette.org:8080/path?query#fragment'); // true @@ -306,7 +306,7 @@ Validators::isUrl('nette.org'); // false isUri(string $value): bool .[method] ------------------------------------ -Comprueba que el valor es una dirección URI válida, es decir, realmente una cadena que comienza con un esquema sintácticamente válido. +Verifica si el valor es una URI válida, es decir, una cadena que comienza con un esquema sintácticamente válido. ```php Validators::isUri('https://nette.org'); // true diff --git a/utils/fr/@home.texy b/utils/fr/@home.texy index f96f963d94..8f85db224c 100644 --- a/utils/fr/@home.texy +++ b/utils/fr/@home.texy @@ -1,42 +1,46 @@ -Utilitaires +Nette Utils *********** .[perex] -Dans le paquetage `nette/utils`, vous trouverez un ensemble de classes utiles pour un usage quotidien : +Dans le paquet `nette/utils`, vous trouverez un ensemble de classes utiles pour une utilisation quotidienne : -| [Callback] | Nette\Utils\Callback -| [Comparaison de flottants |floats] | Nette\Utils\Floats +| [Callback |Callback] | Nette\Utils\Callback | [Date et heure |datetime] | Nette\Utils\DateTime +| [Finder |Finder] | Nette\Utils\Finder +| [Éléments HTML |html-elements] | Nette\Utils\Html +| [Itérateurs |iterables] | Nette\Utils\Iterables +| [JSON |json] | Nette\Utils\Json +| [Chaînes aléatoires |random] | Nette\Utils\Random +| [Images |images] | Nette\Utils\Image +| [Réflexion PHP |reflection] | Nette\Utils\Reflection +| [Types PHP |type] | Nette\Utils\Type +| [Tableaux |arrays] | Nette\Utils\Arrays | [Fonctions d'aide |helpers] | Nette\Utils\Helpers -| [Générer des chaînes de caractères aléatoires |random] | Nette\Utils\Random -| [Générer un code HTML |html-elements] | Nette\Utils\Html -| [Chaînes de caractères |Strings] | Nette\Utils\Strings -| [Images] | Nette\Utils\Image -| [JSON] | Nette\Utils\Json -| [Modèle d'objet |smartobject] | Nette\SmartObject & Nette\StaticClass -| [Finder] | Nette\Utils\Finder -| [Paginateur |Paginator] | Nette\Utils\Paginator -| [PHP Reflection |reflection] | Nette\Utils\Reflection +| [Comparaison de flottants |floats] | Nette\Utils\Floats +| [Chaînes de caractères |strings] | Nette\Utils\Strings | [Système de fichiers |filesystem] | Nette\Utils\FileSystem -| [Tableaux |Arrays] | Nette\Utils\Arrays -| [Types |type] | Nette\Utils\Type -| [Validateurs |Validators] | Nette\Utils\Validators +| [Pagination |paginator] | Nette\Utils\Paginator +| [SmartObject |SmartObject] & [StaticClass |StaticClass] | Nette\SmartObject & Nette\StaticClass +| [Validateur |validators] | Nette\Utils\Validators Installation ------------ -Téléchargez et installez le paquet en utilisant [Composer |best-practices:composer]: +Vous pouvez télécharger et installer la bibliothèque à l'aide de [Composer|best-practices:composer] : ```shell composer require nette/utils ``` -| version | compatible avec PHP -|-----------|------------------- -| Nette Utils 4.0 | PHP 8.0 - 8.2 -| Nette Utils 3.2 | PHP 7.2 - 8.2 -| Nette Utils 3.0 - 3.1 | PHP 7.1 - 8.0 -| Nette Utils 2.5 | PHP 5.6 - 8.0 +| version | compatible avec PHP +|-----------------|------------------- +| Nette Utils 4.0 | PHP 8.0 – 8.4 +| Nette Utils 3.2 | PHP 7.2 – 8.3 +| Nette Utils 3.0 – 3.1 | PHP 7.1 – 8.0 +| Nette Utils 2.5 | PHP 5.6 – 8.0 + +Valable pour la dernière version patch. + -S'applique aux dernières versions des correctifs. +Si vous mettez à jour le paquet vers une version plus récente, consultez la page [mise à niveau|en:upgrading]. diff --git a/utils/fr/@left-menu.texy b/utils/fr/@left-menu.texy index 8b9a577cae..4e64ba19be 100644 --- a/utils/fr/@left-menu.texy +++ b/utils/fr/@left-menu.texy @@ -1,26 +1,28 @@ -Paquet net/utils -**************** +Nette Utils +*********** +- [Callbacks |callback] - [Date et heure |datetime] -- [Éléments HTML |HTML Elements] -- [Finder] -- [Floats] -- [Fonctions d'aide |helpers] +- [Finder |Finder] +- [Floats |Floats] +- [Éléments HTML |html-elements] +- [Itérateurs |iterables] +- [JSON |JSON] - [Chaînes aléatoires |random] -- [Chaînes de caractères |Strings] -- [Images] -- [JSON] -- [Paginateur |paginator] -- [PHP Reflection |reflection] -- [Retour d'appel |Callback] -- [SmartObject] -- [Système de fichiers |filesystem] -- [Tableaux |Arrays] +- [Images |images] +- [Paginator |paginator] +- [Réflexion PHP |reflection] - [Types PHP |type] -- [Validateurs |validators] +- [Tableaux |arrays] +- [Fonctions d'aide |helpers] +- [Chaînes de caractères |strings] +- [SmartObject |SmartObject] +- [StaticClass |StaticClass] +- [Système de fichiers |filesystem] +- [Validateur |validators] -Autres utilitaires -****************** -- [NEON |neon:] +Autres outils +************* +- [NEON|neon:] - [Hachage de mots de passe |security:passwords] -- [Analyseur et constructeur d'URL |http:urls] +- [Analyse et composition d'URL |http:urls] diff --git a/utils/fr/@meta.texy b/utils/fr/@meta.texy new file mode 100644 index 0000000000..72ae4b8db8 --- /dev/null +++ b/utils/fr/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentation Nette}} diff --git a/utils/fr/arrays.texy b/utils/fr/arrays.texy index 7d94514953..140b2e2cc8 100644 --- a/utils/fr/arrays.texy +++ b/utils/fr/arrays.texy @@ -1,8 +1,8 @@ -Fonctions des tableaux -********************** +Manipulation de tableaux +************************ .[perex] -Cette page concerne les classes [Nette\Utils\Arrays |#Arrays], [ArrayHash |#ArrayHash] et [ArrayList |#ArrayList], qui sont liées aux tableaux. +Cette page est dédiée aux classes [Nette\Utils\Arrays |#Arrays], [#ArrayHash] et [#ArrayList], qui concernent les tableaux. Installation : @@ -12,22 +12,63 @@ composer require nette/utils ``` -Matrices .[#toc-arrays] -======================= +Arrays +====== -[api:Nette\Utils\Arrays] est une classe statique, qui contient une poignée de fonctions pratiques pour les tableaux. +[api:Nette\Utils\Arrays] est une classe statique contenant des fonctions utiles pour travailler avec les tableaux. Son équivalent pour les itérateurs est [Nette\Utils\Iterables|iterables]. -Les exemples suivants supposent que l'alias de classe suivant est défini : +Les exemples suivants supposent qu'un alias a été créé : ```php use Nette\Utils\Arrays; ``` +associate(array $array, mixed $path): array|\stdClass .[method] +--------------------------------------------------------------- + +La fonction transforme de manière flexible le tableau `$array` en un tableau associatif ou des objets selon le chemin spécifié `$path`. Le chemin peut être une chaîne ou un tableau. Il est constitué des noms des clés du tableau d'entrée et d'opérateurs tels que '[]', '->', '=', et '|'. Elle lève `Nette\InvalidArgumentException` si le chemin est invalide. + +```php +// conversion en tableau associatif selon une clé simple +$arr = [ + ['name' => 'John', 'age' => 11], + ['name' => 'Mary', 'age' => null], + // ... +]; +$result = Arrays::associate($arr, 'name'); +// $result = ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// assignation des valeurs d'une clé à une autre en utilisant l'opérateur = +$result = Arrays::associate($arr, 'name=age'); // ou ['name', '=', 'age'] +// $result = ['John' => 11, 'Mary' => null, ...] +``` + +```php +// création d'un objet en utilisant l'opérateur -> +$result = Arrays::associate($arr, '->name'); // ou ['->', 'name'] +// $result = (object) ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// combinaison de clés à l'aide de l'opérateur | +$result = Arrays::associate($arr, 'name|age'); // ou ['name', '|', 'age'] +// $result: ['John' => ['name' => 'John', 'age' => 11], 'Paul' => ['name' => 'Paul', 'age' => 44]] +``` + +```php +// ajout à un tableau en utilisant [] +$result = Arrays::associate($arr, 'name[]'); // ou ['name', '[]'] +// $result: ['John' => [['name' => 'John', 'age' => 22], ['name' => 'John', 'age' => 11]]] +``` + + contains(array $array, $value): bool .[method] ---------------------------------------------- -Teste un tableau pour vérifier la présence d'une valeur. Utilise une comparaison stricte (`===`) +Teste la présence d'une valeur dans le tableau. Utilise une comparaison stricte (`===`). ```php Arrays::contains([1, 2, 3], 1); // true @@ -35,10 +76,10 @@ Arrays::contains(['1', false], 1); // false ``` -every(iterable $array, callable $callback): bool .[method] ----------------------------------------------------------- +every(array $array, callable $predicate): bool .[method] +-------------------------------------------------------- -Teste si tous les éléments du tableau passent le test implémenté par la fonction fournie, qui a la signature `function ($value, $key, array $array): bool`. +Teste si tous les éléments du tableau passent le test implémenté dans `$predicate` avec la signature `function ($value, $key, array $array): bool`. ```php $array = [1, 30, 39, 29, 10, 13]; @@ -46,24 +87,59 @@ $isBelowThreshold = fn($value) => $value < 40; $res = Arrays::every($array, $isBelowThreshold); // true ``` -Voir [some() |#some()]. +Voir [#some()]. -first(array $array): mixed .[method] ------------------------------------- +filter(array $array, callable $predicate): array .[method]{data-version:4.0.4} +------------------------------------------------------------------------------ + +Retourne un nouveau tableau contenant toutes les paires clé-valeur correspondant au prédicat spécifié. Le callback a la signature `function ($value, int|string $key, array $array): bool`. + +```php +Arrays::filter( + ['a' => 1, 'b' => 2, 'c' => 3], + fn($v) => $v < 3, +); +// ['a' => 1, 'b' => 2] +``` + + +first(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------- + +Retourne le premier élément (correspondant au prédicat, s'il est spécifié). Si un tel élément n'existe pas, retourne le résultat de l'appel de `$else` ou null. Le paramètre `$predicate` a la signature `function ($value, int|string $key, array $array): bool`. + +Ne modifie pas le pointeur interne contrairement à `reset()`. Les paramètres `$predicate` et `$else` existent depuis la version 4.0.4. + +```php +Arrays::first([1, 2, 3]); // 1 +Arrays::first([1, 2, 3], fn($v) => $v > 2); // 3 +Arrays::first([]); // null +Arrays::first([], else: fn() => false); // false +``` + +Voir [#last()]. -Retourne le premier élément du tableau ou null si le tableau est vide. Elle ne modifie pas le pointeur interne contrairement à `reset()`. + +firstKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +------------------------------------------------------------------------------------------------ + +Retourne la clé du premier élément (correspondant au prédicat, s'il est spécifié) ou null si un tel élément n'existe pas. Le prédicat `$predicate` a la signature `function ($value, int|string $key, array $array): bool`. ```php -Arrays::first([1, 2, 3]); // 1 -Arrays::first([]); // null +Arrays::firstKey([1, 2, 3]); // 0 +Arrays::firstKey([1, 2, 3], fn($v) => $v > 2); // 2 +Arrays::firstKey(['a' => 1, 'b' => 2]); // 'a' +Arrays::firstKey([]); // null ``` +Voir [#lastKey()]. + flatten(array $array, bool $preserveKeys=false): array .[method] ---------------------------------------------------------------- -Transforme un tableau multidimensionnel en tableau plat. +Aplatit un tableau multidimensionnel en un tableau plat. ```php $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); @@ -71,62 +147,62 @@ $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); ``` -get(array $array, string|int|array $key, mixed $default=null): mixed .[method] ------------------------------------------------------------------------------- +get(array $array, string|int|array $key, ?mixed $default=null): mixed .[method] +------------------------------------------------------------------------------- -Retourne `$array[$key]` item. S'il n'existe pas, `Nette\InvalidArgumentException` est lancé, sauf si une valeur par défaut est définie comme troisième argument. +Retourne l'élément `$array[$key]`. S'il n'existe pas, lève soit une exception `Nette\InvalidArgumentException`, soit, si le troisième paramètre `$default` est spécifié, retourne celui-ci. ```php -// si $array['foo'] n'existe pas, lance une exception +// si $array['foo'] n'existe pas, lève une exception $value = Arrays::get($array, 'foo'); -// si $array['foo'] n'existe pas, retourne 'bar'. +// si $array['foo'] n'existe pas, retourne 'bar' $value = Arrays::get($array, 'foo', 'bar'); ``` -L'argument `$key` peut aussi bien être un tableau. +La clé `$key` peut aussi être un tableau. ```php $array = ['color' => ['favorite' => 'red'], 5]; $value = Arrays::get($array, ['color', 'favorite']); -// returns 'red' +// retourne 'red' ``` getRef(array &$array, string|int|array $key): mixed .[method] ------------------------------------------------------------- -Obtient une référence à l'objet `$array[$key]`. Si l'index n'existe pas, un nouvel index est créé avec la valeur `null`. +Obtient une référence à l'élément spécifié du tableau. Si l'élément n'existe pas, il sera créé avec la valeur null. ```php $valueRef = & Arrays::getRef($array, 'foo'); -// renvoie une référence à $array['foo']. +// retourne une référence à $array['foo'] ``` -Fonctionne avec les tableaux multidimensionnels ainsi qu'avec [get() |#get()]. +Comme la fonction [#get()], elle peut travailler avec des tableaux multidimensionnels. ```php -$value = & Arrays::get($array, ['color', 'favorite']); -// renvoie la référence $array['color']['favorite']. +$value = & Arrays::getRef($array, ['color', 'favorite']); +// retourne une référence à $array['color']['favorite'] ``` grep(array $array, string $pattern, bool $invert=false): array .[method] ------------------------------------------------------------------------ -Renvoie uniquement les éléments du tableau qui correspondent à l'expression régulière `$pattern`. Si `$invert` est `true`, il renvoie les éléments qui ne correspondent pas. Une erreur de compilation ou d'exécution de l'expression régulière entraîne le lancement de `Nette\RegexpException`. +Retourne uniquement les éléments du tableau dont la valeur correspond à l'expression régulière `$pattern`. Si `$invert` est `true`, retourne au contraire les éléments qui ne correspondent pas. Une erreur lors de la compilation ou du traitement de l'expression lève une exception `Nette\RegexpException`. ```php $filteredArray = Arrays::grep($array, '~^\d+$~'); -// renvoie uniquement les éléments numériques +// retourne uniquement les éléments du tableau constitués de chiffres ``` insertAfter(array &$array, string|int|null $key, array $inserted): void .[method] --------------------------------------------------------------------------------- -Insère le contenu du tableau `$inserted` dans le tableau `$array` immédiatement après le tableau `$key`. Si `$key` est `null` (ou n'existe pas), il est inséré à la fin. +Insère le contenu du tableau `$inserted` dans le tableau `$array` juste après l'élément avec la clé `$key`. Si `$key` est `null` (ou n'est pas dans le tableau), il est inséré à la fin. ```php $array = ['first' => 10, 'second' => 20]; @@ -138,7 +214,7 @@ Arrays::insertAfter($array, 'first', ['hello' => 'world']); insertBefore(array &$array, string|int|null $key, array $inserted): void .[method] ---------------------------------------------------------------------------------- -Insère le contenu du tableau `$inserted` dans le fichier `$array` avant le fichier `$key`. Si `$key` est `null` (ou n'existe pas), il est inséré au début. +Insère le contenu du tableau `$inserted` dans le tableau `$array` avant l'élément avec la clé `$key`. Si `$key` est `null` (ou n'est pas dans le tableau), il est inséré au début. ```php $array = ['first' => 10, 'second' => 20]; @@ -150,7 +226,7 @@ Arrays::insertBefore($array, 'first', ['hello' => 'world']); invoke(iterable $callbacks, ...$args): array .[method] ------------------------------------------------------ -Appelle tous les callbacks et renvoie un tableau de résultats. +Appelle tous les callbacks et retourne un tableau des résultats. ```php $callbacks = [ @@ -166,7 +242,7 @@ $array = Arrays::invoke($callbacks, 5, 11); invokeMethod(iterable $objects, string $method, ...$args): array .[method] -------------------------------------------------------------------------- -Appelle la méthode sur chaque objet d'un tableau et renvoie un tableau de résultats. +Appelle la méthode sur chaque objet du tableau et retourne un tableau des résultats. ```php $objects = ['a' => $obj1, 'b' => $obj2]; @@ -179,7 +255,7 @@ $array = Arrays::invokeMethod($objects, 'foo', 1, 2); isList(array $array): bool .[method] ------------------------------------ -Vérifie si le tableau est indexé dans l'ordre croissant des clés numériques à partir de zéro, c'est-à-dire une liste. +Vérifie si le tableau est indexé selon une séquence ascendante de clés numériques à partir de zéro, alias une liste. ```php Arrays::isList(['a', 'b', 'c']); // true @@ -188,21 +264,42 @@ Arrays::isList(['a' => 1, 'b' => 2]); // false ``` -last(array $array): mixed .[method] ------------------------------------ +last(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------ + +Retourne le dernier élément (correspondant au prédicat, s'il est spécifié). Si un tel élément n'existe pas, retourne le résultat de l'appel de `$else` ou null. Le paramètre `$predicate` a la signature `function ($value, int|string $key, array $array): bool`. -Retourne le dernier élément du tableau ou null si le tableau est vide. Elle ne modifie pas le pointeur interne contrairement à `end()`. +Ne modifie pas le pointeur interne contrairement à `end()`. Les paramètres `$predicate` et `$else` existent depuis la version 4.0.4. ```php -Arrays::last([1, 2, 3]); // 3 -Arrays::last([]); // null +Arrays::last([1, 2, 3]); // 3 +Arrays::last([1, 2, 3], fn($v) => $v < 3); // 2 +Arrays::last([]); // null +Arrays::last([], else: fn() => false); // false ``` +Voir [#first()]. -map(iterable $array, callable $callback): array .[method] + +lastKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +----------------------------------------------------------------------------------------------- + +Retourne la clé du dernier élément (correspondant au prédicat, s'il est spécifié) ou null si un tel élément n'existe pas. Le prédicat `$predicate` a la signature `function ($value, int|string $key, array $array): bool`. + +```php +Arrays::lastKey([1, 2, 3]); // 2 +Arrays::lastKey([1, 2, 3], fn($v) => $v < 3); // 1 +Arrays::lastKey(['a' => 1, 'b' => 2]); // 'b' +Arrays::lastKey([]); // null +``` + +Voir [#firstKey()]. + + +map(array $array, callable $transformer): array .[method] --------------------------------------------------------- -Appelle `$callback` sur tous les éléments du tableau et renvoie le tableau des valeurs de retour. Le callback a la signature `function ($value, $key, array $array): bool`. +Appelle `$transformer` sur tous les éléments du tableau et retourne un tableau des valeurs retournées. Le callback a la signature `function ($value, $key, array $array): mixed`. ```php $array = ['foo', 'bar', 'baz']; @@ -211,10 +308,24 @@ $res = Arrays::map($array, fn($value) => $value . $value); ``` +mapWithKeys(array $array, callable $transformer): array .[method] +----------------------------------------------------------------- + +Crée un nouveau tableau en transformant les valeurs et les clés du tableau original. La fonction `$transformer` a la signature `function ($value, $key, array $array): ?array{$newKey, $newValue}`. Si `$transformer` retourne `null`, l'élément est ignoré. Pour les éléments conservés, le premier élément du tableau retourné est utilisé comme nouvelle clé et le second élément comme nouvelle valeur. + +```php +$array = ['a' => 1, 'b' => 2]; +$result = Arrays::mapWithKeys($array, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +Cette méthode est utile dans les situations où vous devez modifier la structure du tableau (clés et valeurs simultanément) ou filtrer des éléments lors de la transformation (en retournant null pour les éléments indésirables). + + mergeTree(array $array1, array $array2): array .[method] -------------------------------------------------------- -Fusionne récursivement deux champs. Il est utile, par exemple, pour fusionner des structures arborescentes. Il se comporte comme l'opérateur `+` pour les tableaux, c'est-à-dire qu'il ajoute une paire clé/valeur du second tableau au premier et conserve la valeur du premier tableau en cas de collision de clés. +Fusionne récursivement deux tableaux. Utile par exemple pour fusionner des structures arborescentes. Lors de la fusion, elle suit les mêmes règles que l'opérateur `+` appliqué aux tableaux, c'est-à-dire qu'elle ajoute les paires clé/valeur du second tableau au premier et, en cas de collision de clés, conserve la valeur du premier tableau. ```php $array1 = ['color' => ['favorite' => 'red'], 5]; @@ -224,13 +335,13 @@ $array = Arrays::mergeTree($array1, $array2); // $array = ['color' => ['favorite' => 'red', 'blue'], 5]; ``` -Les valeurs du deuxième tableau sont toujours ajoutées au premier. La disparition de la valeur `10` du deuxième tableau peut sembler un peu déroutante. Il convient de noter que cette valeur, ainsi que la valeur `5` in the first array have the same numeric key `0`, font que dans le champ résultant, il n'y a qu'un élément du premier tableau. +Les valeurs du second tableau sont toujours ajoutées à la fin du premier. La disparition de la valeur `10` du second tableau peut sembler un peu déroutante. Il faut réaliser que cette valeur, ainsi que la valeur `5` dans le premier tableau, ont la même clé numérique `0` assignée, c'est pourquoi dans le tableau résultant, il n'y a que l'élément du premier tableau. -normalize(array $array, string $filling=null): array .[method] --------------------------------------------------------------- +normalize(array $array, ?string $filling=null): array .[method] +--------------------------------------------------------------- -Normalise le tableau en tableau associatif. Remplacez les clés numériques par leurs valeurs, la nouvelle valeur sera `$filling`. +Normalise le tableau en un tableau associatif. Remplace les clés numériques par leurs valeurs, la nouvelle valeur sera `$filling`. ```php $array = Arrays::normalize([1 => 'first', 'a' => 'second']); @@ -243,14 +354,14 @@ $array = Arrays::normalize([1 => 'first', 'a' => 'second'], 'foobar'); ``` -pick(array &$array, string|int $key, mixed $default=null): mixed .[method] --------------------------------------------------------------------------- +pick(array &$array, string|int $key, ?mixed $default=null): mixed .[method] +--------------------------------------------------------------------------- -Renvoie et supprime la valeur d'un élément d'un tableau. S'il n'existe pas, il lève une exception, ou renvoie `$default`, s'il est fourni. +Retourne et supprime la valeur de l'élément du tableau. S'il n'existe pas, lève une exception, ou retourne la valeur `$default` si elle est spécifiée. ```php -$array = [1 => 'foo', null => 'bar']; -$a = Arrays::pick($array, null); +$array = [1 => 'foo', 'x' => 'bar']; +$a = Arrays::pick($array, 'x'); // $a = 'bar' $b = Arrays::pick($array, 'not-exists', 'foobar'); // $b = 'foobar' @@ -262,7 +373,7 @@ $c = Arrays::pick($array, 'not-exists'); renameKey(array &$array, string|int $oldKey, string|int $newKey): bool .[method] -------------------------------------------------------------------------------- -Renomme une clé. Retourne `true` si la clé a été trouvée dans le tableau. +Renomme une clé dans le tableau. Retourne `true` si la clé a été trouvée dans le tableau. ```php $array = ['first' => 10, 'second' => 20]; @@ -274,20 +385,20 @@ Arrays::renameKey($array, 'first', 'renamed'); getKeyOffset(array $array, string|int $key): ?int .[method] ----------------------------------------------------------- -Renvoie la position indexée zéro de la clé du tableau donné. Retourne `null` si la clé n'est pas trouvée. +Retourne la position de la clé donnée dans le tableau. La position est numérotée à partir de 0. Si la clé n'est pas trouvée, la fonction retourne `null`. ```php $array = ['first' => 10, 'second' => 20]; -$position = Arrays::getKeyOffset($array, 'first'); // returns 0 -$position = Arrays::getKeyOffset($array, 'second'); // returns 1 -$position = Arrays::getKeyOffset($array, 'not-exists'); // returns null +$position = Arrays::getKeyOffset($array, 'first'); // retourne 0 +$position = Arrays::getKeyOffset($array, 'second'); // retourne 1 +$position = Arrays::getKeyOffset($array, 'not-exists'); // retourne null ``` -some(iterable $array, callable $callback): bool .[method] ---------------------------------------------------------- +some(array $array, callable $predicate): bool .[method] +------------------------------------------------------- -Teste si au moins un élément du tableau passe le test implémenté par la callback fournie avec la signature `function ($value, $key, array $array): bool`. +Teste si au moins un élément du tableau passe le test implémenté dans `$predicate` avec la signature `function ($value, $key, array $array): bool`. ```php $array = [1, 2, 3, 4]; @@ -295,13 +406,13 @@ $isEven = fn($value) => $value % 2 === 0; $res = Arrays::some($array, $isEven); // true ``` -Voir [every() |#every()]. +Voir [#every()]. toKey(mixed $key): string|int .[method] --------------------------------------- -Convertit une valeur en une clé de tableau, qui est soit un entier, soit une chaîne de caractères. +Convertit une valeur en clé de tableau, qui est soit un entier, soit une chaîne de caractères. ```php Arrays::toKey('1'); // 1 @@ -312,19 +423,19 @@ Arrays::toKey('01'); // '01' toObject(iterable $array, object $object): object .[method] ----------------------------------------------------------- -Copie les éléments du tableau `$array` dans l'objet `$object`, puis le renvoie. +Copie les éléments du tableau `$array` dans l'objet `$object`, qu'il retourne ensuite. ```php $obj = new stdClass; $array = ['foo' => 1, 'bar' => 2]; -Arrays::toObject($array, $obj); // it sets $obj->foo = 1; $obj->bar = 2; +Arrays::toObject($array, $obj); // définit $obj->foo = 1; $obj->bar = 2; ``` -wrap(iterable $array, string $prefix='', string $suffix=''): array .[method] ----------------------------------------------------------------------------- +wrap(array $array, string $prefix='', string $suffix=''): array .[method] +------------------------------------------------------------------------- -Il transforme chaque élément du tableau en chaîne de caractères et l'entoure de `$prefix` et `$suffix`. +Convertit chaque élément du tableau en chaîne de caractères et l'entoure du préfixe `$prefix` et du suffixe `$suffix`. ```php $array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); @@ -332,21 +443,21 @@ $array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); ``` -ArrayHash .[#toc-arrayhash] -=========================== +ArrayHash +========= -L'objet [api:Nette\Utils\ArrayHash] est le descendant de la classe générique stdClass et l'étend à la possibilité de le traiter comme un tableau, par exemple en accédant aux membres à l'aide de crochets : +L'objet [api:Nette\Utils\ArrayHash] est un descendant de la classe générique stdClass et l'étend avec la capacité de le traiter comme un tableau, c'est-à-dire par exemple d'accéder aux membres via des crochets : ```php $hash = new Nette\Utils\ArrayHash; $hash['foo'] = 123; -$hash->bar = 456; // also works object notation +$hash->bar = 456; // la notation objet fonctionne également $hash->foo; // 123 ``` -Vous pouvez utiliser la fonction `count($hash)` pour obtenir le nombre d'éléments. +Il est possible d'utiliser la fonction `count($hash)` pour obtenir le nombre d'éléments. -Vous pouvez itérer sur un objet comme vous le feriez sur un tableau, même avec une référence : +On peut itérer sur l'objet de la même manière que sur un tableau, y compris avec une référence : ```php foreach ($hash as $key => $value) { @@ -354,11 +465,11 @@ foreach ($hash as $key => $value) { } foreach ($hash as $key => &$value) { - $value = 'nouvelle valeur' ; + $value = 'new value'; } ``` -Les tableaux existants peuvent être transformés en `ArrayHash` en utilisant `from()`: +Un tableau existant peut être transformé en `ArrayHash` avec la méthode `from()` : ```php $array = ['foo' => 123, 'bar' => 456]; @@ -368,35 +479,35 @@ $hash->foo; // 123 $hash->bar; // 456 ``` -La transformation est récursive : +La conversion est récursive : ```php $array = ['foo' => 123, 'inner' => ['a' => 'b']]; $hash = Nette\Utils\ArrayHash::from($array); -$hash->inner; // object ArrayHash +$hash->inner; // objet ArrayHash $hash->inner->a; // 'b' $hash['inner']['a']; // 'b' ``` -Elle peut être évitée par le deuxième paramètre : +Cela peut être évité avec le second paramètre : ```php $hash = Nette\Utils\ArrayHash::from($array, false); -$hash->inner; // array +$hash->inner; // tableau ``` -Transformer en retour au tableau : +Transformation inverse en tableau : ```php $array = (array) $hash; ``` -ArrayList .[#toc-arraylist] -=========================== +ArrayList +========= -[api:Nette\Utils\ArrayList] représente un tableau linéaire où les index sont uniquement des entiers croissants à partir de 0. +[api:Nette\Utils\ArrayList] représente un tableau linéaire, où les indices sont uniquement des entiers croissants à partir de 0. ```php $list = new Nette\Utils\ArrayList; @@ -407,9 +518,16 @@ $list[] = 'c'; count($list); // 3 ``` -Vous pouvez utiliser la fonction `count($list)` pour obtenir le nombre d'éléments. +Un tableau existant peut être transformé en `ArrayList` avec la méthode `from()` : -Vous pouvez itérer sur un objet comme vous le feriez sur un tableau, même avec une référence : +```php +$array = ['foo', 'bar']; +$list = Nette\Utils\ArrayList::from($array); +``` + +Il est possible d'utiliser la fonction `count($list)` pour obtenir le nombre d'éléments. + +On peut itérer sur l'objet de la même manière que sur un tableau, y compris avec une référence : ```php foreach ($list as $key => $value) { @@ -417,32 +535,25 @@ foreach ($list as $key => $value) { } foreach ($list as $key => &$value) { - $value = 'nouvelle valeur' ; + $value = 'new value'; } ``` -Les tableaux existants peuvent être transformés en `ArrayList` en utilisant `from()`: - -```php -$array = ['foo', 'bar']; -$list = Nette\Utils\ArrayList::from($array); -``` - -L'accès aux clés au-delà des valeurs autorisées déclenche une exception `Nette\OutOfRangeException`: +L'accès à des clés en dehors des valeurs autorisées lève une exception `Nette\OutOfRangeException` : ```php -echo $list[-1]; // throws Nette\OutOfRangeException -unset($list[30]); // throws Nette\OutOfRangeException +echo $list[-1]; // lève Nette\OutOfRangeException +unset($list[30]); // lève Nette\OutOfRangeException ``` -La suppression de la clé entraîne la renumérotation des éléments : +La suppression d'une clé provoque la renumérotation des éléments : ```php unset($list[1]); // ArrayList(0 => 'a', 1 => 'c') ``` -Vous pouvez ajouter un nouvel élément au début en utilisant `prepend()`: +Un nouvel élément peut être ajouté au début avec la méthode `prepend()` : ```php $list->prepend('d'); diff --git a/utils/fr/callback.texy b/utils/fr/callback.texy index de2d36257b..9adc598ab2 100644 --- a/utils/fr/callback.texy +++ b/utils/fr/callback.texy @@ -1,8 +1,8 @@ -Fonctions de rappel -******************* +Utilisation des callbacks +************************* .[perex] -[api:Nette\Utils\Callback] est une classe statique, qui contient des fonctions permettant de travailler avec les [callbacks PHP |https://www.php.net/manual/en/language.types.callable.php]. +[api:Nette\Utils\Callback] est une classe statique avec des fonctions pour travailler avec les [callbacks PHP |https://www.php.net/manual/en/language.types.callable.php]. Installation : @@ -11,7 +11,7 @@ Installation : composer require nette/utils ``` -Tous les exemples supposent que l'alias de classe suivant est défini : +Tous les exemples supposent qu'un alias a été créé : ```php use Nette\Utils\Callback; @@ -21,21 +21,21 @@ use Nette\Utils\Callback; check($callable, bool $syntax=false): callable .[method] -------------------------------------------------------- -Vérifie que `$callable` est un callback PHP valide. Sinon, elle lance `Nette\InvalidArgumentException`. Si le paramètre `$syntax` est défini à true, la fonction vérifie seulement que `$callable` a une structure valide pour être utilisée comme callback, mais ne vérifie pas si la classe ou la méthode existe réellement. Retourne `$callable`. +Vérifie si la variable `$callable` est un callback valide. Sinon, lève `Nette\InvalidArgumentException`. Si `$syntax` est true, la fonction vérifie seulement que `$callable` a la structure d'un callback, mais ne vérifie pas si la classe ou la méthode donnée existe réellement. Retourne `$callable`. ```php -Callback::check('trim'); // pas d'exception -Callback::check(['NonExistentClass', 'method']); // rejette la Nette\InvalidArgumentException -Callback::check(['NonExistentClass', 'method'], true); // pas d'exception -Callback::check(function () {}); // pas d'exception -Callback::check(null); // pas d'exception. +Callback::check('trim'); // ne lève pas d'exception +Callback::check(['NonExistentClass', 'method']); // lève Nette\InvalidArgumentException +Callback::check(['NonExistentClass', 'method'], true); // ne lève pas d'exception +Callback::check(function () {}); // ne lève pas d'exception +Callback::check(null); // lève Nette\InvalidArgumentException ``` toString($callable): string .[method] ------------------------------------- -Convertit le callback PHP en forme textuelle. La classe ou la méthode peut ne pas exister. +Convertit un callback PHP en forme textuelle. La classe ou la méthode n'a pas besoin d'exister. ```php Callback::toString('trim'); // 'trim' @@ -46,7 +46,7 @@ Callback::toString(['MyClass', 'method']); // 'MyClass::method' toReflection($callable): ReflectionMethod|ReflectionFunction .[method] ---------------------------------------------------------------------- -Renvoie la réflexion pour la méthode ou la fonction utilisée dans le callback PHP. +Retourne la réflexion pour la méthode ou la fonction dans le callback PHP. ```php $ref = Callback::toReflection('trim'); @@ -60,7 +60,7 @@ $ref = Callback::toReflection(['MyClass', 'method']); isStatic($callable): bool .[method] ----------------------------------- -Vérifie si le callback PHP est une fonction ou une méthode statique. +Détermine si le callback PHP est une fonction ou une méthode statique. ```php Callback::isStatic('trim'); // true @@ -73,7 +73,7 @@ Callback::isStatic(function () {}); // false unwrap(Closure $closure): callable|array .[method] -------------------------------------------------- -Défait la fermeture créée par `Closure::fromCallable`:https://www.php.net/manual/en/closure.fromcallable.php. +Déballe une Closure créée à l'aide de "Closure::fromCallable":https://www.php.net/manual/en/closure.fromcallable.php. ```php $closure = Closure::fromCallable(['MyClass', 'method']); diff --git a/utils/fr/datetime.texy b/utils/fr/datetime.texy index 912c12aa0a..f22332778c 100644 --- a/utils/fr/datetime.texy +++ b/utils/fr/datetime.texy @@ -2,7 +2,7 @@ Date et heure ************* .[perex] -[api:Nette\Utils\DateTime] is a class extends native [php:DateTime]. +[api:Nette\Utils\DateTime] est une classe qui étend la classe native [php:DateTime] avec des fonctionnalités supplémentaires. Installation : @@ -11,7 +11,7 @@ Installation : composer require nette/utils ``` -Tous les exemples supposent que l'alias de classe suivant est défini : +Tous les exemples supposent qu'un alias a été créé : ```php use Nette\Utils\DateTime; @@ -20,35 +20,35 @@ use Nette\Utils\DateTime; static from(string|int|\DateTimeInterface $time): DateTime .[method] -------------------------------------------------------------------- -Crée un objet DateTime à partir d'une chaîne de caractères, d'un timestamp UNIX, ou d'un autre objet [php:DateTimeInterface]. Throws an `Exception` si la date et l'heure ne sont pas valides. +Crée un objet DateTime à partir d'une chaîne, d'un timestamp UNIX ou d'un autre objet [php:DateTimeInterface]. Lève une exception `Exception` si la date et l'heure ne sont pas valides. ```php -DateTime::from(1138013640); // crée un DateTime à partir du timestamp UNIX avec un timzamp par défaut +DateTime::from(1138013640); // crée un DateTime à partir d'un timestamp UNIX avec le fuseau horaire par défaut DateTime::from(42); // crée un DateTime à partir de l'heure actuelle plus 42 secondes -DateTime::from('1994-02-26 04:15:32'); // crée une DateTime à partir d'une chaîne de caractères -DateTime::from('1994-02-26'); // crée une DateTime à partir de la date, l'heure sera 00:00:00 +DateTime::from('1994-02-26 04:15:32'); // crée un DateTime selon la chaîne +DateTime::from('1994-02-26'); // crée un DateTime selon la date, l'heure sera 00:00:00 ``` static fromParts(int $year, int $month, int $day, int $hour=0, int $minute=0, float $second=0.0): DateTime .[method] -------------------------------------------------------------------------------------------------------------------- -Creates DateTime object or throws an `Nette\InvalidArgumentException` exception if the date and time are not valid. +Crée un objet DateTime ou lève une exception `Nette\InvalidArgumentException` si la date et l'heure ne sont pas valides. ```php DateTime::fromParts(1994, 2, 26, 4, 15, 32); ``` -static createFromFormat(string $format, string $time, string|\DateTimeZone $timezone=null): DateTime|false .[method] --------------------------------------------------------------------------------------------------------------------- -Extends [DateTime::createFromFormat() |https://www.php.net/manual/en/datetime.createfromformat.php] avec la possibilité de spécifier un fuseau horaire comme une chaîne de caractères. +static createFromFormat(string $format, string $time, ?string|\DateTimeZone $timezone=null): DateTime|false .[method] +--------------------------------------------------------------------------------------------------------------------- +Étend [DateTime::createFromFormat()|https://www.php.net/manual/en/datetime.createfromformat.php] avec la possibilité de spécifier le fuseau horaire sous forme de chaîne. ```php -DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); // créer avec un fuseau horaire personnalisé +DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); ``` modifyClone(string $modify=''): static .[method] ------------------------------------------------ -Crée une copie avec une heure modifiée. +Crée une copie avec l'heure modifiée. ```php $original = DateTime::from('2017-02-03'); $clone = $original->modifyClone('+1 day'); @@ -59,15 +59,15 @@ $clone->format('Y-m-d'); // '2017-02-04' __toString(): string .[method] ------------------------------ -Renvoie la date et l'heure au format `Y-m-d H:i:s`. +Retourne la date et l'heure au format `Y-m-d H:i:s`. ```php echo $dateTime; // '2017-02-03 04:15:32' ``` -Implements JsonSerializable .[#toc-implements-jsonserializable] ---------------------------------------------------------------- -Renvoie la date et l'heure au format ISO 8601, utilisé en JavaScript, par exemple. +implémente JsonSerializable +--------------------------- +Retourne la date et l'heure au format ISO 8601, qui est utilisé par exemple en JavaScript. ```php $date = DateTime::from('2017-02-03'); echo json_encode($date); diff --git a/utils/fr/filesystem.texy b/utils/fr/filesystem.texy index a8aceb487f..8af08454d5 100644 --- a/utils/fr/filesystem.texy +++ b/utils/fr/filesystem.texy @@ -1,41 +1,43 @@ -Fonctions du système de fichiers -******************************** +Système de fichiers +******************* .[perex] -[api:Nette\Utils\FileSystem] est une classe statique, qui contient des fonctions utiles pour travailler avec un système de fichiers. Un avantage par rapport aux fonctions natives de PHP est qu'elles lèvent des exceptions en cas d'erreurs. +[api:Nette\Utils\FileSystem] est une classe contenant des fonctions utiles pour travailler avec le système de fichiers. L'un des avantages par rapport aux fonctions natives de PHP est qu'elles lèvent des exceptions en cas d'erreur. +Si vous avez besoin de rechercher des fichiers sur le disque, utilisez [Finder |finder]. + Installation : ```shell composer require nette/utils ``` -Les exemples suivants supposent que l'alias de classe suivant est défini : +Les exemples suivants supposent qu'un alias a été créé : ```php use Nette\Utils\FileSystem; ``` -Manipulation .[#toc-manipulation] -================================= +Manipulation +============ copy(string $origin, string $target, bool $overwrite=true): void .[method] -------------------------------------------------------------------------- -Copie un fichier ou un répertoire entier. Ecrase les fichiers et répertoires existants par défaut. Si `$overwrite` a pour valeur `false` et qu'un `$target` existe déjà, lance une exception `Nette\InvalidStateException`. Lance une exception `Nette\IOException` en cas d'erreur. +Copie un fichier ou un répertoire entier. Par défaut, écrase les fichiers et répertoires existants. Si le paramètre `$overwrite` est défini sur `false`, une exception `Nette\InvalidStateException` est levée si le fichier ou le répertoire cible `$target` existe. En cas d'erreur, une exception `Nette\IOException` est levée. ```php FileSystem::copy('/path/to/source', '/path/to/dest', overwrite: true); ``` -createDir(string $directory, int $mode=0777): void .[method] ------------------------------------------------------------- +createDir(string $dir, int $mode=0777): void .[method] +------------------------------------------------------ -Crée un répertoire s'il n'existe pas, y compris les répertoires parents. Lance une exception `Nette\IOException` en cas d'erreur. +Crée un répertoire s'il n'existe pas, y compris les répertoires parents. En cas d'erreur, une exception `Nette\IOException` est levée. ```php FileSystem::createDir('/path/to/dir'); @@ -45,7 +47,7 @@ FileSystem::createDir('/path/to/dir'); delete(string $path): void .[method] ------------------------------------ -Supprime un fichier ou un répertoire entier s'il existe. Si le répertoire n'est pas vide, il supprime son contenu en premier. Lance une exception `Nette\IOException` en cas d'erreur. +Supprime un fichier ou un répertoire entier s'il existe. Si le répertoire n'est pas vide, son contenu est d'abord supprimé. En cas d'erreur, une exception `Nette\IOException` est levée. ```php FileSystem::delete('/path/to/fileOrDir'); @@ -55,7 +57,7 @@ FileSystem::delete('/path/to/fileOrDir'); makeWritable(string $path, int $dirMode=0777, int $fileMode=0666): void .[method] --------------------------------------------------------------------------------- -Définit les permissions des fichiers à `$fileMode` ou les permissions des répertoires à `$dirMode`. Parcourt récursivement et définit également les autorisations sur l'ensemble du contenu du répertoire. +Définit les permissions du fichier sur `$fileMode` ou du répertoire sur `$dirMode`. Parcourt récursivement et définit également les permissions pour tout le contenu du répertoire. ```php FileSystem::makeWritable('/path/to/fileOrDir'); @@ -65,7 +67,7 @@ FileSystem::makeWritable('/path/to/fileOrDir'); open(string $path, string $mode): resource .[method] ---------------------------------------------------- -Ouvre le fichier et retourne la ressource. Le paramètre `$mode` fonctionne de la même manière que la fonction native `fopen()`:https://www.php.net/manual/en/function.fopen.php. Si une erreur se produit, elle soulève l'exception `Nette\IOException`. +Ouvre un fichier et retourne une ressource. Le paramètre `$mode` fonctionne de la même manière que la fonction native `fopen()`:https://www.php.net/manual/en/function.fopen.php. En cas d'erreur, une exception `Nette\IOException` est levée. ```php $res = FileSystem::open('/path/to/file', 'r'); @@ -75,7 +77,7 @@ $res = FileSystem::open('/path/to/file', 'r'); read(string $file): string .[method] ------------------------------------ -Lit le contenu d'un fichier `$file`. Lance une exception `Nette\IOException` si une erreur se produit. +Retourne le contenu du fichier `$file`. En cas d'erreur, une exception `Nette\IOException` est levée. ```php $content = FileSystem::read('/path/to/file'); @@ -85,8 +87,7 @@ $content = FileSystem::read('/path/to/file'); readLines(string $file, bool $stripNewLines=true): \Generator .[method] ----------------------------------------------------------------------- -Lit le contenu du fichier ligne par ligne. Contrairement à la fonction native `file()`, elle ne lit pas le fichier entier en mémoire, mais le lit de manière continue, de sorte que les fichiers plus grands que la mémoire disponible peuvent être lus. Le paramètre `$stripNewLines` indique s'il faut supprimer les caractères de saut de ligne `\r` et `\n`. -En cas d'erreur, elle lève une exception `Nette\IOException`. +Lit le contenu du fichier ligne par ligne. Contrairement à la fonction native `file()`, elle ne charge pas tout le fichier en mémoire, mais le lit progressivement, ce qui permet de lire des fichiers plus volumineux que la mémoire disponible. `$stripNewLines` indique si les caractères de fin de ligne `\r` et `\n` doivent être supprimés. En cas d'erreur, une exception `Nette\IOException` est levée. ```php $lines = FileSystem::readLines('/path/to/file'); @@ -100,7 +101,7 @@ foreach ($lines as $lineNum => $line) { rename(string $origin, string $target, bool $overwrite=true): void .[method] ---------------------------------------------------------------------------- -Renomme ou déplace un fichier ou un répertoire spécifié par `$origin` vers `$target`. Ecrase les fichiers et répertoires existants par défaut. Si `$overwrite` a pour valeur `false` et que `$target` existe déjà, lance une exception `Nette\InvalidStateException`. Lance une exception `Nette\IOException` en cas d'erreur. +Renomme ou déplace un fichier ou un répertoire `$origin`. Par défaut, écrase les fichiers et répertoires existants. Si le paramètre `$overwrite` est défini sur `false`, une exception `Nette\InvalidStateException` est levée si le fichier ou le répertoire cible `$target` existe. En cas d'erreur, une exception `Nette\IOException` est levée. ```php FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); @@ -110,21 +111,21 @@ FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); write(string $file, string $content, int $mode=0666): void .[method] -------------------------------------------------------------------- -Écrit l'adresse `$content` dans un fichier `$file`. Lance une exception `Nette\IOException` si une erreur se produit. +Écrit la chaîne `$content` dans le fichier `$file`. En cas d'erreur, une exception `Nette\IOException` est levée. ```php FileSystem::write('/path/to/file', $content); ``` -Chemins d'accès .[#toc-paths] -============================= +Chemins +======= isAbsolute(string $path): bool .[method] ---------------------------------------- -Détermine si le site `$path` est absolu. +Vérifie si le chemin `$path` est absolu. ```php FileSystem::isAbsolute('../backup'); // false @@ -146,7 +147,7 @@ FileSystem::joinPaths('/a/', '/../b'); // '/b' normalizePath(string $path): string .[method] --------------------------------------------- -Normalise `..` et `.` et les séparateurs de répertoire dans le chemin. +Normalise `..` et `.` ainsi que les séparateurs de répertoires dans le chemin vers les séparateurs système. ```php FileSystem::normalizePath('/file/.'); // '/file/' @@ -159,7 +160,7 @@ FileSystem::normalizePath('file/../../bar'); // '/../bar' unixSlashes(string $path): string .[method] ------------------------------------------- -Convertit les barres obliques en `/` utilisées sur les systèmes Unix. +Convertit les barres obliques en `/` utilisés dans les systèmes Unix. ```php $path = FileSystem::unixSlashes($path); @@ -169,8 +170,45 @@ $path = FileSystem::unixSlashes($path); platformSlashes(string $path): string .[method] ----------------------------------------------- -Convertit les barres obliques en caractères spécifiques à la plate-forme actuelle, c'est-à-dire `\` sous Windows et `/` ailleurs. +Convertit les barres obliques en caractères spécifiques à la plateforme actuelle, c'est-à-dire `\` sous Windows et `/` ailleurs. ```php $path = FileSystem::platformSlashes($path); ``` + + +resolvePath(string $basePath, string $path): string .[method]{data-version:4.0.6} +--------------------------------------------------------------------------------- + +Déduit le chemin final à partir du chemin `$path` par rapport au répertoire de base `$basePath`. Les chemins absolus (`/foo`, `C:/foo`) sont laissés inchangés (seuls les slashes sont normalisés), les chemins relatifs sont ajoutés au chemin de base. + +```php +// Sous Windows, les slashes dans la sortie seraient inversés (\) +FileSystem::resolvePath('/base/dir', '/abs/path'); // '/abs/path' +FileSystem::resolvePath('/base/dir', 'rel'); // '/base/dir/rel' +FileSystem::resolvePath('base/dir', '../file.txt'); // 'base/file.txt' +FileSystem::resolvePath('base', ''); // 'base' +``` + + +Accès statique vs non statique +============================== + +Afin de pouvoir facilement remplacer la classe par une autre (un mock) à des fins de test, par exemple, utilisez-la de manière non statique : + +```php +class AnyClassUsingFileSystem +{ + public function __construct( + private FileSystem $fileSystem, + ) { + } + + public function readConfig(): string + { + return $this->fileSystem->read(/* ... */); + } + + ... +} +``` diff --git a/utils/fr/finder.texy b/utils/fr/finder.texy index c846adf89c..ab270d98d6 100644 --- a/utils/fr/finder.texy +++ b/utils/fr/finder.texy @@ -1,8 +1,8 @@ -Finder : Recherche de fichiers +Finder : recherche de fichiers ****************************** .[perex] -Vous avez besoin de trouver des fichiers correspondant à un certain masque ? Le Finder peut vous aider. Il s'agit d'un outil polyvalent et rapide pour parcourir la structure des répertoires. +Besoin de trouver des fichiers correspondant à un certain masque ? Finder vous y aidera. C'est un outil polyvalent et rapide pour parcourir la structure des répertoires. Installation : @@ -18,10 +18,10 @@ use Nette\Utils\Finder; ``` -Utilisation de .[#toc-using] ----------------------------- +Utilisation +----------- -Tout d'abord, voyons comment vous pouvez utiliser [api:Nette\Utils\Finder] pour lister les noms de fichiers avec les extensions `.txt` et `.md` dans le répertoire actuel : +Montrons d'abord comment vous pouvez utiliser [api:Nette\Utils\Finder] pour lister les noms de fichiers avec les extensions `.txt` et `.md` dans le répertoire courant : ```php foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { @@ -29,96 +29,97 @@ foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { } ``` -Le répertoire par défaut pour la recherche est le répertoire courant, mais vous pouvez le changer en utilisant les méthodes [in() ou from() |#Where to search?]. -La variable `$file` est une instance de la classe [FileInfo |#FileInfo] avec de nombreuses méthodes utiles. La clé `$name` contient le chemin d'accès au fichier sous forme de chaîne. +Le répertoire par défaut pour la recherche est le répertoire courant, mais vous pouvez le modifier à l'aide des méthodes [in() ou from() |#Où rechercher]. La variable `$file` est une instance de la classe [#FileInfo] avec de nombreuses méthodes utiles. La clé `$name` contient le chemin du fichier sous forme de chaîne. -Que rechercher ? .[#toc-what-to-search-for] -------------------------------------------- +Que rechercher ? +---------------- -En plus de la méthode `findFiles()`, il existe également `findDirectories()`, qui ne recherche que les répertoires, et `find()`, qui recherche les deux. Ces méthodes sont statiques, elles peuvent donc être appelées sans créer d'instance. Le paramètre mask est facultatif, si vous ne le spécifiez pas, tout est recherché. +En plus de la méthode `findFiles()`, il existe aussi `findDirectories()`, qui recherche uniquement les répertoires, et `find()`, qui recherche les deux. Ces méthodes sont statiques, elles peuvent donc être appelées sans créer d'instance. Le paramètre avec le masque est optionnel, si vous ne le spécifiez pas, tout sera recherché. ```php foreach (Finder::find() as $file) { - echo $file; // maintenant tous les fichiers et répertoires sont listés + echo $file; // maintenant tous les fichiers et répertoires seront affichés } ``` -Utilisez les méthodes `files()` et `directories()` pour ajouter ce qui doit être recherché. Ces méthodes peuvent être appelées à plusieurs reprises et un tableau de masques peut être fourni en tant que paramètre : +À l'aide des méthodes `files()` et `directories()`, vous pouvez spécifier ce qui doit être recherché en plus. Les méthodes peuvent être appelées de manière répétée et un tableau de masques peut également être spécifié comme paramètre : ```php Finder::findDirectories('vendor') // tous les répertoires ->files(['*.php', '*.phpt']); // plus tous les fichiers PHP ``` -Une alternative aux méthodes statiques est de créer une instance à l'aide de `new Finder` (l'objet frais créé de cette façon ne cherche rien) et de spécifier ce qu'il faut rechercher à l'aide de `files()` et `directories()`: +Une alternative aux méthodes statiques est de créer une instance à l'aide de `new Finder` (un objet fraîchement créé de cette manière ne recherche rien) et de spécifier ce qu'il faut rechercher à l'aide de `files()` et `directories()` : ```php (new Finder) - ->directories() // tous les répertoires - ->files('*.php'); // plus tous les fichiers PHP + ->directories() // tous les répertoires + ->files('*.php'); // plus tous les fichiers PHP ``` -Vous pouvez utiliser des [caractères génériques |#wildcards] `*`, `**`, `?` and `[...]` dans le masque. Vous pouvez même spécifier des répertoires, par exemple `src/*.php` recherchera tous les fichiers PHP dans le répertoire `src`. +Dans le masque, vous pouvez utiliser les [#caractères génériques] `*`, `**`, `?` et `[...]`. Vous pouvez même spécifier des répertoires, par exemple `src/*.php` recherchera tous les fichiers PHP dans le répertoire `src`. +Les liens symboliques sont également considérés comme des répertoires ou des fichiers. -Où chercher ? .[#toc-where-to-search] -------------------------------------- -Le répertoire de recherche par défaut est le répertoire courant. Vous pouvez le modifier en utilisant les méthodes `in()` et `from()`. Comme vous pouvez le constater d'après les noms des méthodes, `in()` ne recherche que dans le répertoire courant, tandis que `from()` recherche également dans ses sous-répertoires (de manière récursive). Si vous souhaitez effectuer une recherche récursive dans le répertoire courant, vous pouvez utiliser `from('.')`. +Où rechercher ? +--------------- -Ces méthodes peuvent être appelées plusieurs fois ou vous pouvez leur passer plusieurs chemins sous forme de tableaux, les fichiers seront alors recherchés dans tous les répertoires. Si l'un des répertoires n'existe pas, un message d'erreur ( `Nette\UnexpectedValueException` ) est lancé. +Le répertoire par défaut pour la recherche est le répertoire courant. Vous pouvez le modifier à l'aide des méthodes `in()` et `from()`. Comme les noms des méthodes l'indiquent, `in()` recherche uniquement dans le répertoire donné, tandis que `from()` recherche également dans ses sous-répertoires (récursivement). Si vous souhaitez rechercher récursivement dans le répertoire courant, vous pouvez utiliser `from('.')`. + +Ces méthodes peuvent être appelées plusieurs fois ou recevoir plusieurs chemins sous forme de tableau, les fichiers seront alors recherchés dans tous les répertoires. Si l'un des répertoires n'existe pas, une exception `Nette\UnexpectedValueException` sera levée. ```php Finder::findFiles('*.php') ->in(['src', 'tests']) // recherche directement dans src/ et tests/ - ->from('vendor'); // recherche également dans les sous-répertoires vendor/ + ->from('vendor'); // recherche également dans les sous-répertoires de vendor/ ``` -Les chemins relatifs sont relatifs au répertoire actuel. Bien sûr, des chemins absolus peuvent également être spécifiés : +Les chemins relatifs sont relatifs au répertoire courant. Il est bien sûr possible de spécifier également des chemins absolus : ```php Finder::findFiles('*.php') ->in('/var/www/html'); ``` -Les [caractères |#wildcards] génériques `*`, `**`, `?` can be used in the path. For example, you can use the path `src/*/*.php` pour rechercher tous les fichiers PHP dans les répertoires de deuxième niveau du répertoire `src`. Le caractère `**`, appelé globstar, est un atout puissant car il vous permet de rechercher également dans les sous-répertoires : utilisez `src/**/tests/*.php` pour rechercher tous les fichiers PHP du répertoire `tests` situés dans `src` ou n'importe lequel de ses sous-répertoires. +Il est possible d'utiliser des [#caractères génériques] `*`, `**`, `?` dans le chemin. Vous pouvez ainsi, par exemple, à l'aide du chemin `src/*/*.php`, rechercher tous les fichiers PHP dans les répertoires de deuxième niveau du répertoire `src`. Le caractère `**`, appelé globstar, est un atout puissant, car il permet de rechercher également dans les sous-répertoires : à l'aide de `src/**/tests/*.php`, vous recherchez tous les fichiers PHP dans le répertoire `tests` situé dans `src` ou dans n'importe lequel de ses sous-répertoires. -D'autre part, les caractères de remplacement `[...]` ne sont pas pris en charge dans le chemin d'accès, c'est-à-dire qu'ils n'ont pas de signification particulière pour éviter tout comportement indésirable dans le cas où vous recherchez par exemple `in(__DIR__)` et que par hasard les caractères `[]` apparaissent dans le chemin d'accès. +Inversement, les caractères génériques `[...]` ne sont pas supportés dans le chemin, c'est-à-dire qu'ils n'ont pas de signification spéciale, afin d'éviter un comportement indésirable dans le cas où vous rechercheriez par exemple `in(__DIR__)` et que, par hasard, les caractères `[]` apparaîtraient dans le chemin. -Lors d'une recherche approfondie de fichiers et de répertoires, le répertoire parent est renvoyé en premier et ensuite les fichiers qu'il contient, ce qui peut être inversé avec `childFirst()`. +Lors de la recherche de fichiers et de répertoires en profondeur, le répertoire parent est retourné en premier, puis les fichiers qu'il contient, ce qui peut être inversé à l'aide de `childFirst()`. -Caractères génériques .[#toc-wildcards] ---------------------------------------- +Caractères génériques +--------------------- -Vous pouvez utiliser plusieurs caractères spéciaux dans le masque : +Dans le masque, vous pouvez utiliser plusieurs caractères spéciaux : -- `*` - replaces any number of arbitrary characters (except `/`) -- `**` - remplace un nombre quelconque de caractères arbitraires, y compris `/` (c'est-à-dire qu'il peut être recherché sur plusieurs niveaux) -- `?` - replaces one arbitrary character (except `/`) -- `[a-z]` - remplace un caractère de la liste de caractères entre crochets. +- `*` - remplace n'importe quel nombre de caractères quelconques (sauf `/`) +- `**` - remplace n'importe quel nombre de caractères quelconques y compris `/` (c'est-à-dire qu'il est possible de rechercher sur plusieurs niveaux) +- `?` - remplace un caractère quelconque (sauf `/`) +- `[a-z]` - remplace un caractère de la liste de caractères entre crochets - `[!a-z]` - remplace un caractère en dehors de la liste de caractères entre crochets Exemples d'utilisation : -- `img/?.png` - fichiers avec le nom à une lettre `0.png`, `1.png`, `x.png`, etc. -- `logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log` - fichiers journaux au format `YYYY-MM-DD` -- `src/**/tests/*` - fichiers dans le répertoire `src/tests`, `src/foo/tests`, `src/foo/bar/tests` et ainsi de suite. +- `img/?.png` - fichiers avec un nom d'une seule lettre `0.png`, `1.png`, `x.png`, etc. +- `logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log` - logs au format `YYYY-MM-DD` +- `src/**/tests/*` - fichiers dans les répertoires `src/tests`, `src/foo/tests`, `src/foo/bar/tests` et ainsi de suite. - `docs/**.md` - tous les fichiers avec l'extension `.md` dans tous les sous-répertoires du répertoire `docs` -A l'exception de .[#toc-excluding] ----------------------------------- +Exclusion +--------- -Utilisez la méthode `exclude()` pour exclure des fichiers et des répertoires des recherches. Vous spécifiez un masque auquel le fichier ne doit pas correspondre. Exemple de recherche de fichiers `*.txt` à l'exception de ceux contenant la lettre `X` dans le nom : +À l'aide de la méthode `exclude()`, il est possible d'exclure des fichiers et des répertoires de la recherche. Vous spécifiez un masque auquel le fichier ne doit pas correspondre. Exemple de recherche de fichiers `*.txt` sauf ceux qui contiennent la lettre `X` dans leur nom : ```php Finder::findFiles('*.txt') ->exclude('*X*'); ``` -Utilisez `exclude()` pour ignorer les sous-répertoires parcourus : +Pour omettre les sous-répertoires parcourus, utilisez `exclude()` : ```php Finder::findFiles('*.php') @@ -127,12 +128,12 @@ Finder::findFiles('*.php') ``` -Filtrage de .[#toc-filtering] ------------------------------ +Filtrage +-------- -Le Finder propose plusieurs méthodes pour filtrer les résultats (c'est-à-dire les réduire). Vous pouvez les combiner et les appeler à plusieurs reprises. +Finder offre plusieurs méthodes pour filtrer les résultats (c'est-à-dire les réduire). Vous pouvez les combiner et les appeler de manière répétée. -Utilisez `size()` pour filtrer par taille de fichier. De cette façon, nous trouvons les fichiers dont la taille est comprise entre 100 et 200 octets : +À l'aide de `size()`, nous filtrons par taille de fichier. De cette manière, nous trouvons les fichiers dont la taille est comprise entre 100 et 200 octets : ```php Finder::findFiles('*.php') @@ -140,7 +141,7 @@ Finder::findFiles('*.php') ->size('<=', 200); ``` -La méthode `date()` permet de filtrer par la date de la dernière modification du fichier. Les valeurs peuvent être absolues ou relatives à la date et à l'heure actuelles. Par exemple, voici comment trouver les fichiers modifiés au cours des deux dernières semaines : +La méthode `date()` filtre par date de dernière modification du fichier. Les valeurs peuvent être absolues ou relatives à la date et à l'heure actuelles, par exemple, de cette manière, nous trouvons les fichiers modifiés au cours des deux dernières semaines : ```php Finder::findFiles('*.php') @@ -150,9 +151,9 @@ Finder::findFiles('*.php') Les deux fonctions comprennent les opérateurs `>`, `>=`, `<`, `<=`, `=`, `!=`, `<>`. -Le Finder vous permet également de filtrer les résultats à l'aide de fonctions personnalisées. La fonction reçoit un objet `Nette\Utils\FileInfo` comme paramètre et doit retourner `true` pour inclure le fichier dans les résultats. +Finder permet également de filtrer les résultats à l'aide de fonctions personnalisées. La fonction reçoit comme paramètre l'objet `Nette\Utils\FileInfo` et doit retourner `true` pour que le fichier soit inclus dans les résultats. -Exemple : rechercher les fichiers PHP qui contiennent la chaîne de caractères `Nette` (insensible à la casse) : +Exemple : recherche de fichiers PHP qui contiennent la chaîne `Nette` (indépendamment de la casse) : ```php Finder::findFiles('*.php') @@ -160,51 +161,51 @@ Finder::findFiles('*.php') ``` -Filtrage en profondeur .[#toc-depth-filtering] ----------------------------------------------- +Filtrage en profondeur +---------------------- -Lors d'une recherche récursive, vous pouvez définir la profondeur d'exploration maximale à l'aide de la méthode `limitDepth()`. Si vous définissez `limitDepth(1)`, seuls les premiers sous-répertoires sont explorés, `limitDepth(0)` désactive l'exploration en profondeur, et une valeur de -1 annule la limite. +Lors de la recherche récursive, vous pouvez définir la profondeur maximale de parcours à l'aide de la méthode `limitDepth()`. Si vous définissez `limitDepth(1)`, seuls les premiers sous-répertoires sont parcourus, `limitDepth(0)` désactive le parcours en profondeur et la valeur -1 annule la limite. -Le Finder vous permet d'utiliser ses propres fonctions pour décider du répertoire à pénétrer lors de l'exploration. La fonction reçoit un objet `Nette\Utils\FileInfo` comme paramètre et doit retourner `true` pour entrer dans le répertoire : +Finder permet, à l'aide de fonctions personnalisées, de décider dans quel répertoire entrer lors du parcours. La fonction reçoit comme paramètre l'objet `Nette\Utils\FileInfo` et doit retourner `true` pour entrer dans le répertoire : ```php Finder::findFiles('*.php') - ->descentFilter($file->getBasename() !== 'temp'); + ->descentFilter(fn($file) => $file->getBasename() !== 'temp'); ``` -Triage .[#toc-sorting] ----------------------- +Tri +--- -Le Finder offre également plusieurs fonctions permettant de trier les résultats. +Finder offre également plusieurs fonctions pour trier les résultats. -La méthode `sortByName()` trie les résultats par nom de fichier. Le tri est naturel, c'est-à-dire qu'il traite correctement les chiffres dans les noms et renvoie par exemple `foo1.txt` avant `foo10.txt`. +La méthode `sortByName()` trie les résultats par noms de fichiers. Le tri est naturel, c'est-à-dire qu'il gère correctement les nombres dans les noms et retourne par exemple `foo1.txt` avant `foo10.txt`. -Le Finder vous permet également de trier en utilisant une fonction personnalisée. Elle prend deux objets `Nette\Utils\FileInfo` comme paramètres et doit retourner le résultat de la comparaison avec l'opérateur `<=>`c'est-à-dire `-1`, `0` nebo `1`. Par exemple, voici comment trier les fichiers par taille : +Finder permet également de trier à l'aide d'une fonction personnalisée. Celle-ci reçoit comme paramètre deux objets `Nette\Utils\FileInfo` et doit retourner le résultat de la comparaison avec l'opérateur `<=>`, c'est-à-dire `-1`, `0` ou `1`. Par exemple, de cette manière, nous trions les fichiers par taille : ```php $finder->sortBy(fn($a, $b) => $a->getSize() <=> $b->getSize()); ``` -Plusieurs recherches différentes .[#toc-multiple-different-searches] --------------------------------------------------------------------- +Plusieurs recherches différentes +-------------------------------- -Si vous devez trouver plusieurs fichiers différents dans des endroits différents ou répondant à des critères différents, utilisez la méthode `append()`. Elle renvoie un nouvel objet `Finder` afin que vous puissiez enchaîner les appels de méthode : +Si vous avez besoin de trouver plusieurs fichiers différents à différents emplacements ou répondant à d'autres critères, utilisez la méthode `append()`. Elle retourne un nouvel objet `Finder`, il est donc possible d'enchaîner les appels de méthodes : ```php -($finder = new Finder) // enregistre le premier Finder dans la variable $finder ! - ->files('*.php') // recherche les fichiers *.php dans src/ +($finder = new Finder) // nous stockons le premier Finder dans la variable $finder ! + ->files('*.php') // dans src/, nous recherchons les fichiers *.php ->from('src') ->append() - ->files('*.md') // dans docs/ recherche les fichiers *.md + ->files('*.md') // dans docs/, nous recherchons les fichiers *.md ->from('docs') ->append() - ->files('*.json'); // dans le dossier actuel, recherche des fichiers *.json + ->files('*.json'); // dans le dossier actuel, nous recherchons les fichiers *.json ``` -Alternativement, vous pouvez utiliser la méthode `append()` pour ajouter un fichier spécifique (ou un tableau de fichiers). Elle renvoie alors le même objet `Finder`: +Alternativement, il est possible d'utiliser la méthode `append()` pour ajouter un fichier spécifique (ou un tableau de fichiers). Elle retourne alors le même objet `Finder` : ```php $finder = Finder::findFiles('*.txt') @@ -212,12 +213,12 @@ $finder = Finder::findFiles('*.txt') ``` -FileInfo .[#toc-fileinfo] -------------------------- +FileInfo +-------- -[Nette\Utils\FileInfo |api:] est une classe représentant un fichier ou un répertoire dans les résultats de recherche. Il s'agit d'une extension de la classe [SplFileInfo |php:SplFileInfo] qui fournit des informations telles que la taille du fichier, la date de dernière modification, le nom, le chemin, etc. +[Nette\Utils\FileInfo |api:] est une classe représentant un fichier ou un répertoire dans les résultats de la recherche. Il s'agit d'une extension de la classe [SplFileInfo |php:SplFileInfo], qui fournit des informations telles que la taille du fichier, la date de dernière modification, le nom, le chemin, etc. -De plus, elle fournit des méthodes pour renvoyer les chemins relatifs, ce qui est utile lors d'une navigation en profondeur : +De plus, elle fournit des méthodes pour retourner le chemin relatif, ce qui est utile lors du parcours en profondeur : ```php foreach (Finder::findFiles('*.jpg')->from('.') as $file) { @@ -226,7 +227,7 @@ foreach (Finder::findFiles('*.jpg')->from('.') as $file) { } ``` -Vous disposez également de méthodes pour lire et écrire le contenu d'un fichier : +De plus, vous avez à disposition des méthodes pour lire et écrire le contenu du fichier : ```php foreach ($finder as $file) { @@ -237,12 +238,12 @@ foreach ($finder as $file) { ``` -Renvoyer les résultats sous forme de tableau .[#toc-returning-results-as-an-array] ----------------------------------------------------------------------------------- +Retour des résultats sous forme de tableau +------------------------------------------ -Comme nous l'avons vu dans les exemples, le Finder implémente l'interface `IteratorAggregate`, ce qui vous permet d'utiliser `foreach` pour parcourir les résultats. Il est programmé de manière à ce que les résultats ne soient chargés qu'au fur et à mesure de la navigation, donc si vous avez un grand nombre de fichiers, il n'attend pas qu'ils soient tous lus. +Comme on l'a vu dans les exemples, Finder implémente l'interface `IteratorAggregate`, vous pouvez donc utiliser `foreach` pour parcourir les résultats. Il est programmé de telle sorte que les résultats sont chargés uniquement pendant le parcours, donc si vous avez une grande quantité de fichiers, on n'attend pas que tous soient lus. -Vous pouvez également obtenir les résultats sous la forme d'un tableau d'objets `Nette\Utils\FileInfo`, en utilisant la méthode `collect()`. Le tableau n'est pas associatif, mais numérique. +Vous pouvez également faire retourner les résultats sous forme de tableau d'objets `Nette\Utils\FileInfo`, et ce, par la méthode `collect()`. Le tableau n'est pas associatif, mais numérique. ```php $array = $finder->findFiles('*.php')->collect(); diff --git a/utils/fr/floats.texy b/utils/fr/floats.texy index 0ae4e0fd11..168e215589 100644 --- a/utils/fr/floats.texy +++ b/utils/fr/floats.texy @@ -1,8 +1,8 @@ -Fonctions flottantes -******************** +Travailler avec les flottants +***************************** .[perex] -[api:Nette\Utils\Floats] est une classe statique contenant des fonctions utiles pour comparer des nombres flottants. +[api:Nette\Utils\Floats] est une classe statique avec des fonctions utiles pour comparer les nombres décimaux. Installation : @@ -11,18 +11,17 @@ Installation : composer require nette/utils ``` -Tous les exemples supposent que l'alias de classe suivant est défini : +Tous les exemples supposent qu'un alias a été créé : ```php use Nette\Utils\Floats; ``` -Motivation .[#toc-motivation] -============================= +Motivation +========== -Vous vous demandez à quoi sert une classe de comparaison de flottants ? Vous pouvez utiliser les opérateurs `<`, `>`, `===`, pensez-vous. -Ce n'est pas tout à fait vrai. A votre avis, qu'est-ce qui va imprimer ce code ? +Vous vous demandez pourquoi une classe pour comparer les flottants ? Après tout, je peux utiliser les opérateurs `<`, `>`, `===` et c'est réglé. Ce n'est pas tout à fait vrai. Que pensez-vous que ce code affichera ? ```php $a = 0.1 + 0.2; @@ -30,27 +29,30 @@ $b = 0.3; echo $a === $b ? 'same' : 'not same'; ``` -Si vous exécutez ce code, certains d'entre vous seront surpris que le programme ait imprimé `not same`. +Si vous exécutez le code, certains d'entre vous seront sûrement surpris que le programme ait affiché `not same`. -Les opérations mathématiques avec des nombres flottants provoquent des erreurs dues à la conversion entre les systèmes décimal et binaire. Par exemple, `0.1 + 0.2` équivaut à `0.300000000000000044…`. Par conséquent, lorsque nous comparons des nombres flottants, nous devons tolérer une petite différence à partir d'une certaine décimale. +Lors des opérations mathématiques avec des nombres décimaux, des erreurs se produisent en raison de la conversion entre les systèmes décimal et binaire. Par exemple, `0.1 + 0.2` donne `0.300000000000000044…`. C'est pourquoi, lors de la comparaison, nous devons tolérer une petite différence à partir d'une certaine décimale. -Et c'est ce que fait la classe `Floats`. La comparaison suivante fonctionnera comme prévu : +Et c'est exactement ce que fait la classe `Floats`. La comparaison suivante fonctionnera déjà comme prévu : ```php echo Floats::areEqual($a, $b) ? 'same' : 'not same'; // same ``` -Lorsque l'on essaie de comparer `NAN`, une exception `\LogicException` est levée. +En essayant de comparer `NAN`, elle lève une exception `\LogicException`. +.[tip] +La classe `Floats` tolère des différences inférieures à `1e-10`. Si vous avez besoin de travailler avec une plus grande précision, utilisez plutôt la bibliothèque BCMath. -Comparaison de flottants .[#toc-float-comparison] -================================================= + +Comparaison de flottants +======================== areEqual(float $a, float $b): bool .[method] -------------------------------------------- -Renvoie `true` si `$a` = `$b`. +Retourne `true` si `$a` = `$b`. ```php Floats::areEqual(10, 10.0); // true @@ -60,7 +62,7 @@ Floats::areEqual(10, 10.0); // true isLessThan(float $a, float $b): bool .[method] ---------------------------------------------- -Renvoie `true` si `$a` < `$b`. +Retourne `true` si `$a` < `$b`. ```php Floats::isLessThan(9.5, 10.2); // true @@ -71,7 +73,7 @@ Floats::isLessThan(INF, 10.2); // false isLessThanOrEqualTo(float $a, float $b): bool .[method] ------------------------------------------------------- -Renvoie `true` si `$a` <= `$b`. +Retourne `true` si `$a` <= `$b`. ```php Floats::isLessThanOrEqualTo(9.5, 10.2); // true @@ -82,7 +84,7 @@ Floats::isLessThanOrEqualTo(10.25, 10.25); // true isGreaterThan(float $a, float $b): bool .[method] ------------------------------------------------- -Renvoie `true` si `$a` > `$b`. +Retourne `true` si `$a` > `$b`. ```php Floats::isGreaterThan(9.5, -10.2); // true @@ -93,7 +95,7 @@ Floats::isGreaterThan(9.5, 10.2); // false isGreaterThanOrEqualTo(float $a, float $b): bool .[method] ---------------------------------------------------------- -Renvoie `true` si `$a` >= `$b`. +Retourne `true` si `$a` >= `$b`. ```php Floats::isGreaterThanOrEqualTo(9.5, 10.2); // false @@ -104,25 +106,25 @@ Floats::isGreaterThanOrEqualTo(10.2, 10.2); // true compare(float $a, float $b): int .[method] ------------------------------------------ -Si `$a` < `$b`, il retourne `-1`, s'ils sont égaux il retourne `0` and if `$a` > `$b` il retourne `1`. +Si `$a` < `$b`, retourne `-1`, s'ils sont égaux, retourne `0` et si `$a` > `$b`, retourne `1`. -Elle peut être utilisée, par exemple, avec la fonction `usort`. +Peut être utilisé par exemple avec la fonction `usort`. ```php $arr = [1, 5, 2, -3.5]; -usort($arr, [Float::class, 'compare']); -// $arr est [-3.5, 1, 2, 5] +usort($arr, [Floats::class, 'compare']); +// $arr est maintenant [-3.5, 1, 2, 5] ``` -Fonctions d'aide .[#toc-helpers-functions] -========================================== +Fonctions utilitaires +===================== isZero(float $value): bool .[method] ------------------------------------ -Renvoie `true` si la valeur est nulle. +Retourne `true` si la valeur est égale à zéro. ```php Floats::isZero(0.0); // true @@ -133,7 +135,7 @@ Floats::isZero(0); // true isInteger(float $value): bool .[method] --------------------------------------- -Renvoie `true` si la valeur est un nombre entier. +Retourne `true` si la valeur est un entier. ```php Floats::isInteger(0); // true diff --git a/utils/fr/helpers.texy b/utils/fr/helpers.texy index b3e0e0630f..a214e41f2f 100644 --- a/utils/fr/helpers.texy +++ b/utils/fr/helpers.texy @@ -1,8 +1,8 @@ -Fonctions d'aide -**************** +Fonctions utilitaires +********************* .[perex] -[api:Nette\Utils\Helpers] est une classe statique contenant des fonctions utiles. +[api:Nette\Utils\Helpers] est une classe statique avec des fonctions utiles. Installation : @@ -11,7 +11,7 @@ Installation : composer require nette/utils ``` -Tous les exemples supposent que l'alias de classe suivant est défini : +Tous les exemples supposent qu'un alias a été créé : ```php use Nette\Utils\Helpers; @@ -21,7 +21,7 @@ use Nette\Utils\Helpers; capture(callable $cb): string .[method] --------------------------------------- -Exécute un callback et renvoie la sortie capturée sous forme de chaîne. +Exécute le callback et retourne la sortie capturée sous forme de chaîne. ```php $res = Helpers::capture(function () use ($template) { @@ -33,7 +33,7 @@ $res = Helpers::capture(function () use ($template) { clamp(int|float $value, int|float $min, int|float $max): int|float .[method] ---------------------------------------------------------------------------- -Renvoie une valeur limitée à la plage inclusive de min et max. +Borne la valeur dans l'intervalle inclusif donné min et max. ```php Helpers::clamp($level, 0, 255); @@ -43,8 +43,7 @@ Helpers::clamp($level, 0, 255); compare(mixed $left, string $operator, mixed $right): bool .[method] -------------------------------------------------------------------- -Compare deux valeurs de la même manière que le fait PHP. Elle fait la distinction entre les opérateurs `>`, `>=`, `<`, `<=`, `=`, `==`, `===`, `!=`, `!==`, `<>`. -Cette fonction est utile dans les situations où l'opérateur est variable. +Compare deux valeurs de la même manière que PHP le fait. Distingue les opérateurs `>`, `>=`, `<`, `<=`, `=`, `==`, `===`, `!=`, `!==`, `<>`. La fonction est utile dans les situations où l'opérateur est variable. ```php Helpers::compare(10, '<', 20); // true @@ -54,7 +53,7 @@ Helpers::compare(10, '<', 20); // true falseToNull(mixed $value): mixed .[method] ------------------------------------------ -Convertit `false` en `null`, ne change pas les autres valeurs. +Convertit `false` en `null`, ne modifie pas les autres valeurs. ```php Helpers::falseToNull(false); // null @@ -65,7 +64,7 @@ Helpers::falseToNull(123); // 123 getLastError(): string .[method] -------------------------------- -Renvoie la dernière erreur PHP survenue ou une chaîne vide si aucune erreur n'est survenue. Contrairement à `error_get_last()`, elle n'est pas affectée par la directive PHP `html_errors` et renvoie toujours du texte, pas du HTML. +Retourne la dernière erreur en PHP ou une chaîne vide si aucune erreur ne s'est produite. Contrairement à `error_get_last()`, n'est pas soumis à l'influence de la directive PHP `html_errors` et retourne toujours du texte, pas du HTML. ```php Helpers::getLastError(); @@ -75,13 +74,13 @@ Helpers::getLastError(); getSuggestion(string[] $possibilities, string $value): ?string .[method] ------------------------------------------------------------------------ -Cherche une chaîne de `$possibilities` qui est la plus similaire à `$value`, mais pas la même. Ne prend en charge que les codages 8 bits. +Parmi les options proposées `$possibilities`, recherche la chaîne qui est la plus similaire à `$value`, mais pas identique. Supporte uniquement l'encodage 8 bits. -C'est utile si une certaine option n'est pas valide et que nous voulons suggérer à l'utilisateur une option similaire (mais différente, donc la même chaîne est ignorée). De cette façon, Nette crée les messages `did you mean ...?`. +Est utile dans le cas où une certaine option n'est pas valide et nous voulons conseiller à l'utilisateur une option similaire (mais différente, c'est pourquoi la chaîne identique est ignorée). De cette manière, Nette crée les messages `did you mean ...?`. ```php $items = ['foo', 'bar', 'baz']; Helpers::getSuggestion($items, 'fo'); // 'foo' Helpers::getSuggestion($items, 'barr'); // 'bar' -Helpers::getSuggestion($items, 'baz'); // 'bar', ne 'baz' +Helpers::getSuggestion($items, 'baz'); // 'bar', pas 'baz' ``` diff --git a/utils/fr/html-elements.texy b/utils/fr/html-elements.texy index fa92fa6979..67f07b7d3b 100644 --- a/utils/fr/html-elements.texy +++ b/utils/fr/html-elements.texy @@ -2,15 +2,15 @@ ************* .[perex] -La classe [api:Nette\Utils\Html] est une aide pour générer du code HTML qui empêche la vulnérabilité Cross Site Scripting (XSS). +La classe [api:Nette\Utils\Html] est un assistant pour la génération de code HTML qui empêche l'apparition de vulnérabilités Cross Site Scripting (XSS). -Elle fonctionne de telle manière que ses objets représentent des éléments HTML, nous définissons leurs paramètres et les laissons effectuer le rendu : +Elle fonctionne de telle sorte que ses objets représentent des éléments HTML, auxquels nous définissons des paramètres et les laissons s'afficher : ```php -$el = Html::el('img'); // crée l'élément . +$el = Html::el('img'); // crée l'élément $el->src = 'image.jpg'; // définit l'attribut src -echo $el; // imprime '' +echo $el; // affiche '' ``` Installation : @@ -19,29 +19,29 @@ Installation : composer require nette/utils ``` -Tous les exemples supposent que l'alias de classe suivant est défini : +Tous les exemples supposent qu'un alias a été créé : ```php use Nette\Utils\Html; ``` -Création d'un élément HTML .[#toc-creating-an-html-element] -=========================================================== +Création d'un élément HTML +========================== -L'élément est créé à l'aide de la méthode `Html::el()`: +Nous créons un élément avec la méthode `Html::el()` : ```php $el = Html::el('img'); // crée l'élément ``` -En plus du nom, vous pouvez saisir d'autres attributs dans la syntaxe HTML : +En plus du nom, vous pouvez spécifier d'autres attributs en syntaxe HTML : ```php $el = Html::el('input type=text class="red important"'); ``` -Ou les passer comme un tableau associatif au deuxième paramètre : +Ou les passer sous forme de tableau associatif comme second paramètre : ```php $el = Html::el('input', [ @@ -50,39 +50,39 @@ $el = Html::el('input', [ ]); ``` -Pour modifier et renvoyer le nom d'un élément : +Modification et retour du nom de l'élément : ```php $el->setName('img'); -$el->getName(); // 'img'. -$el->isEmpty(); // true, car est un élément vide. +$el->getName(); // 'img' +$el->isEmpty(); // true, car est un élément vide ``` -Attributs HTML .[#toc-html-attributes] -====================================== +Attributs HTML +============== -Vous pouvez définir et obtenir des attributs HTML individuels de trois manières, à vous de voir laquelle vous préférez. La première consiste à utiliser les propriétés : +Nous pouvons modifier et lire les attributs HTML individuels de trois manières, cela dépend de vous laquelle vous préférez. La première est via les propriétés : ```php $el->src = 'image.jpg'; // définit l'attribut src -echo $el->src; // 'image.jpg'. +echo $el->src; // 'image.jpg' -unset($el->src); // supprime l'attribut +unset($el->src); // supprime l'attribut // ou $el->src = null; ``` -La deuxième façon est d'appeler des méthodes qui, contrairement à la définition des propriétés, peuvent être enchaînées : +La deuxième voie est l'appel de méthodes, que nous pouvons enchaîner contrairement à la définition via propriétés : ```php $el = Html::el('img')->src('image.jpg')->alt('photo'); // photo -$el->alt(null); // supprime l'attribut +$el->alt(null); // suppression de l'attribut ``` -Et la troisième façon est la plus bavarde : +Et la troisième manière est la plus verbeuse : ```php $el = Html::el('img') @@ -94,9 +94,9 @@ echo $el->getAttribute('src'); // 'image.jpg' $el->removeAttribute('alt'); ``` -En vrac, les attributs peuvent être définis avec `addAttributes(array $attrs)` et supprimés avec `removeAttributes(array $attrNames)`. +Les attributs peuvent être définis en masse à l'aide de `addAttributes(array $attrs)` et supprimés à l'aide de `removeAttributes(array $attrNames)`. -La valeur d'un attribut ne doit pas être uniquement une chaîne de caractères, des valeurs logiques pour les attributs logiques peuvent également être utilisées : +La valeur de l'attribut ne doit pas être seulement une chaîne, il est possible d'utiliser aussi des valeurs logiques pour les attributs logiques : ```php $checkbox = Html::el('input')->type('checkbox'); @@ -104,17 +104,17 @@ $checkbox->checked = true; // $checkbox->checked = false; // ``` -Un attribut peut également être un tableau de tokens, qui sont énumérés séparés par des espaces, ce qui convient aux classes CSS, par exemple : +L'attribut peut aussi être un tableau de valeurs, qui s'affichent séparées par des espaces, ce qui est utile par exemple pour les classes CSS : ```php $el = Html::el('input'); $el->class[] = 'active'; -$el->class[] = null; // null is ignored +$el->class[] = null; // null est ignoré $el->class[] = 'top'; echo $el; // '' ``` -Une alternative est un tableau associatif, où les valeurs indiquent si la clé doit être listée : +L'alternative est un tableau associatif, où les valeurs indiquent si la clé doit être affichée : ```php $el = Html::el('input'); @@ -123,7 +123,7 @@ $el->class['top'] = false; echo $el; // '' ``` -Les styles CSS peuvent être écrits sous la forme de tableaux associatifs : +Les styles CSS peuvent être écrits sous forme de tableaux associatifs : ```php $el = Html::el('input'); @@ -132,7 +132,7 @@ $el->style['display'] = 'block'; echo $el; // '' ``` -Nous avons maintenant utilisé les propriétés, mais la même chose peut être faite en utilisant les méthodes : +Nous avons utilisé les propriétés maintenant, mais la même chose peut être écrite à l'aide de méthodes : ```php $el = Html::el('input'); @@ -141,7 +141,7 @@ $el->style('display', 'block'); echo $el; // '' ``` -Ou même de la manière la plus bavarde : +Ou même de la manière la plus verbeuse : ```php $el = Html::el('input'); @@ -150,7 +150,7 @@ $el->appendAttribute('style', 'display', 'block'); echo $el; // '' ``` -Une dernière chose : la méthode `href()` peut faciliter la composition des paramètres de requête dans une URL : +Encore un petit détail pour finir : la méthode `href()` peut faciliter l'assemblage des paramètres de requête dans l'URL : ```php echo Html::el('a')->href('index.php', [ @@ -161,19 +161,19 @@ echo Html::el('a')->href('index.php', [ ``` -Attributs de données .[#toc-data-attributes] --------------------------------------------- +Attributs de données +-------------------- -Les attributs de données bénéficient d'un support spécial. Comme leurs noms contiennent des traits d'union, l'accès via les propriétés et les méthodes n'est pas aussi élégant, il existe donc une méthode `data()`: +Les attributs de données bénéficient d'un support spécial. Parce que leurs noms contiennent des tirets, l'accès via propriétés et méthodes n'est pas si élégant, c'est pourquoi la méthode `data()` existe : ```php $el = Html::el('input'); $el->{'data-max-size'} = '500x300'; // pas très élégant -$el->data('max-size', '500x300'); // c'est élégant -echo $el; // ''. +$el->data('max-size', '500x300'); // est élégant +echo $el; // '' ``` -Si la valeur de l'attribut de données est un tableau, il est automatiquement sérialisé en JSON : +Si la valeur de l'attribut de données est un tableau, elle est automatiquement sérialisée en JSON : ```php $el = Html::el('input'); @@ -182,10 +182,10 @@ echo $el; // '' ``` -Contenu de l'élément .[#toc-element-content] -============================================ +Contenu de l'élément +==================== -Le contenu interne de l'élément est défini par les méthodes `setHtml()` ou `setText()`. N'utilisez la première méthode que si vous savez que vous transmettez de manière fiable une chaîne HTML sécurisée dans le paramètre. +Nous définissons le contenu interne de l'élément avec les méthodes `setHtml()` ou `setText()`. Utilisez la première seulement si vous savez que vous passez une chaîne HTML fiable et sûre dans le paramètre. ```php echo Html::el('span')->setHtml('hello
                                                                                                                            '); @@ -195,7 +195,7 @@ echo Html::el('span')->setText('10 < 20'); // '10 < 20' ``` -Inversement, le contenu interne est obtenu par les méthodes `getHtml()` ou `getText()`. La seconde supprime les balises de la sortie HTML et convertit les entités HTML en caractères. +Et inversement, nous obtenons le contenu interne avec les méthodes `getHtml()` ou `getText()`. La seconde supprime les balises HTML de la sortie et convertit les entités HTML en caractères. ```php echo $el->getHtml(); // '10 < 20' @@ -203,10 +203,10 @@ echo $el->getText(); // '10 < 20' ``` -Nœuds enfants .[#toc-child-nodes] ---------------------------------- +Nœuds enfants +------------- -Le contenu interne d'un élément peut également être un tableau d'enfants. Chacun d'entre eux peut être soit une chaîne de caractères, soit un autre élément `Html`. Ils sont insérés à l'aide de `addHtml()` ou `addText()`: +L'intérieur de l'élément peut aussi être un tableau de nœuds enfants (children). Chacun d'eux peut être soit une chaîne, soit un autre élément `Html`. Nous les insérons à l'aide de `addHtml()` ou `addText()` : ```php $el = Html::el('span') @@ -216,16 +216,16 @@ $el = Html::el('span') // hello
                                                                                                                            10 < 20
                                                                                                                            ``` -Une autre façon de créer et d'insérer un nouveau nœud `Html`: +Une autre manière de créer et d'insérer un nouveau nœud `Html` : ```php -$el = Html::el('ul') - ->create('li', ['class' => 'first']) - ->setText('hello'); -//
                                                                                                                            • hello
                                                                                                                            +$ul = Html::el('ul'); +$ul->create('li', ['class' => 'first']) + ->setText('premier'); +//
                                                                                                                            • premier
                                                                                                                            ``` -Vous pouvez travailler avec les nœuds comme s'ils étaient des éléments de tableau. Accédez donc à chacun d'entre eux à l'aide de crochets, comptez-les avec `count()` et itérez sur eux : +On peut travailler avec les nœuds de la même manière que s'il s'agissait d'un tableau. C'est-à-dire accéder à chacun d'eux à l'aide de crochets, les compter à l'aide de `count()` et itérer sur eux : ```php $el = Html::el('div'); @@ -238,20 +238,20 @@ foreach ($el as $child) { /* ... */ } echo count($el); // 2 ``` -Un nouveau nœud peut être inséré à une position spécifique en utilisant `insert(?int $index, $child, bool $replace = false)`. Si `$replace = false`, il insère l'élément à la position `$index` et déplace les autres. Si vous utilisez `$index = null`, vous ajouterez un élément à la fin. +Un nouveau nœud peut être inséré à un endroit spécifique à l'aide de `insert(?int $index, $child, bool $replace = false)`. Si `$replace = false`, insère l'élément à la position `$index` et décale les autres. Si `$index = null`, ajoute l'élément à la fin. ```php -// insère l'élément en première position et avance les autres +// insère l'élément à la première position et décale les autres $el->insert(0, Html::el('span')); ``` -Tous les nœuds sont renvoyés par la méthode `getChildren()` et supprimés par la méthode `removeChildren()`. +Nous obtenons tous les nœuds avec la méthode `getChildren()` et les supprimons avec la méthode `removeChildren()`. -Création d'un fragment de document .[#toc-creating-a-document-fragment] ------------------------------------------------------------------------ +Création d'un fragment de document +---------------------------------- -Si vous souhaitez travailler avec un tableau de nœuds et que vous n'êtes pas intéressé par l'élément d'habillage, vous pouvez créer ce que l'on appelle un *fragments de document* en passant `null` au lieu du nom de l'élément : +Si nous voulons travailler avec un tableau de nœuds et que l'élément enveloppant ne nous intéresse pas, nous pouvons créer un soi-disant *fragment de document* en passant `null` au lieu du nom de l'élément : ```php $el = Html::el(null) @@ -261,7 +261,7 @@ $el = Html::el(null) // hello
                                                                                                                            10 < 20
                                                                                                                            ``` -Les méthodes `fromHtml()` et `fromText()` offrent un moyen plus rapide de créer un fragment : +Une manière plus rapide de créer un fragment est offerte par les méthodes `fromHtml()` et `fromText()` : ```php $el = Html::fromHtml('hello
                                                                                                                            '); @@ -272,15 +272,15 @@ echo $el; // '10 < 20' ``` -Générer une sortie HTML .[#toc-generating-html-output] -====================================================== +Génération de la sortie HTML +============================ -La façon la plus simple de générer un élément HTML est d'utiliser `echo` ou d'envoyer un objet à `(string)`. Vous pouvez également imprimer séparément les balises ouvrantes ou fermantes et les attributs : +La manière la plus simple d'afficher un élément HTML est d'utiliser `echo` ou de convertir l'objet en `(string)`. Il est aussi possible d'afficher séparément les balises ouvrantes ou fermantes et les attributs : ```php $el = Html::el('div class=header')->setText('hello'); -echo $el; // '
                                                                                                                            ' +echo $el; // '
                                                                                                                            hello
                                                                                                                            ' $s = (string) $el; // '
                                                                                                                            hello
                                                                                                                            ' $s = $el->toHtml(); // '
                                                                                                                            hello
                                                                                                                            ' $s = $el->toText(); // 'hello' @@ -289,7 +289,7 @@ echo $el->endTag(); // '' echo $el->attributes(); // 'class="header"' ``` -Une caractéristique importante est la protection automatique contre le [Cross Site Scripting (XSS) |nette:glossary#cross-site-scripting-xss]. Toutes les valeurs d'attributs ou le contenu inséré à l'aide de `setText()` ou `addText()` sont échappés de manière fiable : +Une caractéristique importante est la protection automatique contre le [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS]. Toutes les valeurs d'attributs ou le contenu inséré via `setText()` ou `addText()` sont échappés de manière fiable : ```php echo Html::el('div') @@ -300,17 +300,17 @@ echo Html::el('div') ``` -Conversion HTML ↔ Texte .[#toc-conversion-html-text] -==================================================== +Conversion HTML ↔ texte +======================= -Vous pouvez utiliser la méthode statique `htmlToText()` pour convertir du HTML en texte : +Pour la conversion de HTML en texte, vous pouvez utiliser la méthode statique `htmlToText()` : ```php echo Html::htmlToText('One & Two'); // 'One & Two' ``` -HtmlStringable .[#toc-htmlstringable] -===================================== +HtmlStringable +============== -L'objet `Nette\Utils\Html` implémente l'interface `Nette\HtmlStringable`, que, par exemple, Latte ou les formulaires utilisent pour distinguer les objets qui ont une méthode `__toString()` qui renvoie du code HTML. Ainsi, le double échappement ne se produit pas si, par exemple, nous imprimons l'objet dans le modèle en utilisant `{$el}`. +L'objet `Nette\Utils\Html` implémente l'interface `Nette\HtmlStringable`, par laquelle Latte ou les formulaires, par exemple, distinguent les objets qui ont une méthode `__toString()` retournant du code HTML. Ainsi, il n'y aura pas de double échappement si par exemple nous affichons l'objet dans un template avec `{$el}`. diff --git a/utils/fr/images.texy b/utils/fr/images.texy index 94e3677f74..ae8be18c91 100644 --- a/utils/fr/images.texy +++ b/utils/fr/images.texy @@ -1,11 +1,11 @@ -Fonctions d'image -***************** +Travailler avec les images +************************** .[perex] -La classe [api:Nette\Utils\Image] simplifie la manipulation des images, comme le redimensionnement, le recadrage, la netteté, le dessin ou la fusion de plusieurs images. +La classe [api:Nette\Utils\Image] simplifie la manipulation des images, comme le redimensionnement, le recadrage, l'accentuation, le dessin ou la fusion de plusieurs images. -PHP dispose d'un ensemble complet de fonctions pour manipuler les images. Mais l'API n'est pas très agréable. Il ne s'agirait pas d'un Neat Framework pour proposer une API sexy. +PHP dispose d'un ensemble complet de fonctions pour la manipulation des images. Mais leur API n'est pas très pratique. Ce ne serait pas Nette Framework s'il n'apportait pas une API attrayante. Installation : @@ -13,26 +13,28 @@ Installation : composer require nette/utils ``` -Les exemples suivants supposent que l'alias de classe suivant est défini : +Tous les exemples supposent qu'un alias a été créé : ```php use Nette\Utils\Image; +use Nette\Utils\ImageColor; +use Nette\Utils\ImageType; ``` -Création d'une image .[#toc-creating-an-image] -============================================== +Créer une image +=============== -Nous allons créer une nouvelle image en couleur, par exemple avec des dimensions de 100×200 : +Nous créons une nouvelle image true color, par exemple avec les dimensions 100×200 : ```php $image = Image::fromBlank(100, 200); ``` -En option, vous pouvez spécifier une couleur de fond (noir par défaut) : +Optionnellement, vous pouvez spécifier la couleur de fond (la valeur par défaut est le noir) : ```php -$image = Image::fromBlank(100, 200, Image::rgb(125, 0, 0)); +$image = Image::fromBlank(100, 200, ImageColor::rgb(125, 0, 0)); ``` Ou nous chargeons l'image depuis un fichier : @@ -41,91 +43,100 @@ Ou nous chargeons l'image depuis un fichier : $image = Image::fromFile('nette.jpg'); ``` -Les formats pris en charge sont JPEG, PNG, GIF, WebP, AVIF et BMP, mais votre version de PHP doit également les prendre en charge (consultez le site `phpinfo()`, section GD). Les animations ne sont pas prises en charge. -Vous avez besoin de détecter le format de l'image lors du chargement ? La méthode renvoie le format dans le deuxième paramètre : +Enregistrer une image +===================== + +L'image peut être enregistrée dans un fichier : ```php -$image = Image::fromFile('nette.jpg', $type); -// $type est Image::JPEG, Image::PNG, Image::GIF, Image::WEBP, Image::AVIF ou Image::BMP +$image->save('resampled.jpg'); ``` -Seule la détection sans chargement de l'image est effectuée par `Image::detectTypeFromFile()`. - +Nous pouvons spécifier la qualité de compression dans la plage 0..100 pour JPEG (par défaut 85), WEBP (par défaut 80) et AVIF (par défaut 30) et 0..9 pour PNG (par défaut 9) : -Sauvegarder l'image .[#toc-save-the-image] -========================================== +```php +$image->save('resampled.jpg', 80); // JPEG, qualité 80% +``` -L'image peut être enregistrée dans un fichier : +Si le format n'est pas évident d'après l'extension du fichier, il peut être spécifié avec une [constante |#Formats] : ```php -$image->save('resampled.jpg'); +$image->save('resampled.tmp', null, ImageType::JPEG); ``` -Nous pouvons spécifier une qualité de compression comprise entre 0 et 100 pour JPEG (85 par défaut), WEBP (80 par défaut) et AVIF (30 par défaut) et entre 0 et 9 pour PNG (9 par défaut) : +L'image peut être écrite dans une variable au lieu d'un disque : ```php -$image->save('resampled.jpg', 80); // JPEG, qualité 80 +$data = $image->toString(ImageType::JPEG, 80); // JPEG, qualité 80% ``` -Si le format ne ressort pas clairement de l'extension du fichier, il peut être spécifié par l'une des constantes suivantes : `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF`, et `Image::BMP`: +ou envoyée directement au navigateur avec l'en-tête HTTP `Content-Type` approprié : ```php -$image->save('resampled.tmp', null, Image::JPEG); +// envoie l'en-tête Content-Type: image/png +$image->send(ImageType::PNG); ``` -L'image peut être écrite dans une variable plutôt que sur le disque : + +Formats +======= + +Les formats supportés sont JPEG, PNG, GIF, WebP, AVIF et BMP, cependant, ils doivent également être supportés par votre version de PHP, ce que vous pouvez vérifier avec la fonction [#isTypeSupported()]. Les animations ne sont pas supportées. + +Le format est représenté par les constantes `ImageType::JPEG`, `ImageType::PNG`, `ImageType::GIF`, `ImageType::WEBP`, `ImageType::AVIF` et `ImageType::BMP`. ```php -$data = $image->toString(Image::JPEG, 80); // JPEG, qualité 80%. +$supported = Image::isTypeSupported(ImageType::JPEG); ``` -ou envoyer directement au navigateur avec l'en-tête HTTP approprié `Content-Type`: +Avez-vous besoin de détecter le format de l'image lors du chargement ? La méthode le retourne dans le deuxième paramètre : ```php -// envoie l'en-tête Content-Type: image/png -$image->send(Image::PNG); +$image = Image::fromFile('nette.jpg', $type); ``` +La détection seule sans charger l'image est effectuée par `Image::detectTypeFromFile()`. + -Redimensionnement de l'image .[#toc-image-resize] -================================================= +Redimensionnement +================= -Une opération courante consiste à redimensionner une image. Les dimensions actuelles sont renvoyées par les méthodes `getWidth()` et `getHeight()`. +Une opération fréquente est le redimensionnement de l'image. Les dimensions actuelles sont retournées par les méthodes `getWidth()` et `getHeight()`. -La méthode `resize()` est utilisée pour le redimensionnement. Il s'agit d'un exemple de changement de taille proportionnel afin qu'il ne dépasse pas 500×300 pixels (soit la largeur sera exactement de 500px, soit la hauteur sera exactement de 300px, l'une des dimensions est calculée pour maintenir le rapport d'aspect) : +La méthode `resize()` est utilisée pour le changement. Exemple de redimensionnement proportionnel pour ne pas dépasser les dimensions de 500x300 pixels (soit la largeur sera exactement de 500 px, soit la hauteur sera exactement de 300 px, l'une des dimensions sera calculée pour préserver le rapport d'aspect) : ```php $image->resize(500, 300); ``` -Il est possible de définir une seule dimension et la seconde sera calculée : +Il est possible de spécifier une seule dimension et l'autre sera calculée : ```php -$image->resize(500, null); // largeur 500px, hauteur auto +$image->resize(500, null); // largeur 500px, la hauteur sera calculée -$image->resize(null, 300); // largeur auto, hauteur 300px +$image->resize(null, 300); // la largeur sera calculée, hauteur 300px ``` -Toute dimension peut être spécifiée en pourcentages : +N'importe quelle dimension peut également être indiquée en pourcentage : ```php $image->resize('75%', 300); // 75 % × 300px ``` -Le comportement de `resize` peut être influencé par les drapeaux suivants. Tous, à l'exception de `Image::Stretch`, préservent le rapport hauteur/largeur. +Le comportement de `resize` peut être influencé par les drapeaux suivants. Tous, sauf `Image::Stretch`, préservent le rapport d'aspect. |--------------------------------------------------------------------------------------- -| Drapeau | Description +| Drapeau | Description |--------------------------------------------------------------------------------------- -| `Image::OrSmaller` (par défaut) | les dimensions résultantes seront inférieures ou égales à celles spécifiées -| `Image::OrBigger` | remplit la zone cible et l'étend éventuellement dans une direction -| `Image::Cover` | remplit toute la zone et coupe ce qui la dépasse -| `Image::ShrinkOnly` | ne fait que réduire l'échelle (n'étend pas une petite image) -| `Image::Stretch` | ne conserve pas le rapport d'aspect +| `Image::OrSmaller` (par défaut) | les dimensions résultantes seront inférieures ou égales aux dimensions requises +| `Image::OrBigger` | remplit (et dépasse éventuellement dans une dimension) la zone cible +| `Image::Cover` | remplit la zone cible et recadre ce qui dépasse +| `Image::ShrinkOnly` | réduction uniquement (empêche l'étirement d'une petite image) +| `Image::Stretch` | ne pas préserver le rapport d'aspect -Les drapeaux sont passés comme troisième argument de la fonction : +Les drapeaux sont indiqués comme troisième argument de la fonction : ```php $image->resize(500, 300, Image::OrBigger); @@ -137,33 +148,33 @@ Les drapeaux peuvent être combinés : $image->resize(500, 300, Image::ShrinkOnly | Image::Stretch); ``` -Les images peuvent être retournées verticalement ou horizontalement en spécifiant l'une des dimensions (ou les deux) comme un nombre négatif : +Les images peuvent être retournées verticalement ou horizontalement en indiquant l'une des dimensions (ou les deux) comme un nombre négatif : ```php -$flipped = $image->resize(null, '-100%'); // retournement à la verticale +$flipped = $image->resize(null, '-100%'); // retournement vertical -$flipped = $image->resize('-100%', '-100%'); // rotation à 180 +$flipped = $image->resize('-100%', '-100%'); // rotation de 180° -$flipped = $image->resize(-125, 500); // redimensionnement et retournement horizontal +$flipped = $image->resize(-125, 500); // redimensionnement & retournement horizontal ``` -Après avoir réduit l'image, nous pouvons l'améliorer en la rendant plus nette : +Après avoir réduit l'image, il est possible d'améliorer son apparence par une légère accentuation : ```php $image->sharpen(); ``` -Culture .[#toc-cropping] -======================== +Recadrage +========= -La méthode `crop()` est utilisée pour les cultures : +La méthode `crop()` est utilisée pour le recadrage : ```php $image->crop($left, $top, $width, $height); ``` -Comme pour `resize()`, toutes les valeurs peuvent être spécifiées en pourcentages. Les pourcentages pour `$left` et `$top` sont calculés à partir de l'espace restant, comme pour la propriété CSS `background-position`: +Similairement à `resize()`, toutes les valeurs peuvent être indiquées en pourcentages. Les pourcentages pour `$left` et `$top` sont calculés à partir de l'espace restant, similaire à la propriété CSS `background-position` : ```php $image->crop('100%', '50%', '80%', '80%'); @@ -172,334 +183,364 @@ $image->crop('100%', '50%', '80%', '80%'); [* crop.svg *] -L'image peut également être recadrée automatiquement, par exemple en rognant les bords noirs : +L'image peut également être recadrée automatiquement, par exemple pour recadrer les bordures noires : ```php $image->cropAuto(IMG_CROP_BLACK); ``` -La méthode `cropAuto()` est une encapsulation objet de la fonction `imagecropauto()`, voir [sa documentation |https://www.php.net/manual/en/function.imagecropauto] pour plus d'informations. +La méthode `cropAuto()` est un remplacement orienté objet de la fonction `imagecropauto()`, vous trouverez plus d'informations dans [sa documentation |https://www.php.net/manual/en/function.imagecropauto]. -Dessin et édition .[#toc-drawing-and-editing] -============================================= +Couleurs .{data-version:4.0.2} +============================== -Vous pouvez dessiner, vous pouvez écrire, vous pouvez utiliser toutes les fonctions PHP pour travailler avec des images, comme [imagefilledellipse() |https://www.php.net/manual/en/function.imagefilledellipse.php], mais en utilisant le style objet : +La méthode `ImageColor::rgb()` vous permet de définir une couleur en utilisant les valeurs rouge, vert et bleu (RVB). Optionnellement, vous pouvez également spécifier une valeur de transparence dans la plage de 0 (complètement transparent) à 1 (complètement opaque), c'est-à-dire comme en CSS. ```php -$image->filledEllipse($cx, $cy, $width, $height, Image::rgb(255, 0, 0, 63)); +$color = ImageColor::rgb(255, 0, 0); // Rouge +$transparentBlue = ImageColor::rgb(0, 0, 255, 0.5); // Bleu semi-transparent ``` -Voir l'[aperçu des méthodes |#Overview of Methods]. +La méthode `ImageColor::hex()` permet de définir une couleur en utilisant le format hexadécimal, similaire à CSS. Elle supporte les formats `#rgb`, `#rrggbb`, `#rgba` et `#rrggbbaa` : +```php +$color = ImageColor::hex("#F00"); // Rouge +$transparentGreen = ImageColor::hex("#00FF0080"); // Vert semi-transparent +``` + +Les couleurs peuvent être utilisées dans d'autres méthodes, comme `ellipse()`, `fill()`, etc. -Fusionner plusieurs images .[#toc-merge-multiple-images] -======================================================== -Vous pouvez facilement placer une autre image dans l'image : +Dessin et modifications +======================= + +Vous pouvez dessiner, vous pouvez écrire, mais ne pas déchirer les feuilles. Toutes les fonctions PHP pour la manipulation d'images sont à votre disposition, voir [#Aperçu des méthodes], mais dans une enveloppe objet : + +```php +$image->filledEllipse($centerX, $centerY, $width, $height, ImageColor::rgb(255, 0, 0)); +``` + +Parce que les fonctions PHP pour dessiner des rectangles sont peu pratiques à cause de la spécification des coordonnées, la classe `Image` offre leurs remplacements sous forme de fonctions [#rectangleWH()] et [#filledRectangleWH()]. + + +Fusion de plusieurs images +========================== + +Il est facile d'insérer une autre image dans une image : ```php $logo = Image::fromFile('logo.png'); -$blank = Image::fromBlank(320, 240, Image::rgb(52, 132, 210)); +$blank = Image::fromBlank(320, 240, ImageColor::rgb(52, 132, 210)); -// les coordonnées peuvent également être définies en pourcentage -$blank->place($logo, '80%', '80%'); // près du coin inférieur droit de l'image +// les coordonnées peuvent à nouveau être indiquées en pourcentages +$blank->place($logo, '80%', '80%'); // nous insérons près du coin inférieur droit ``` -Lors du collage, le canal alpha est respecté, de plus, nous pouvons influencer la transparence de l'image insérée (nous allons créer un filigrane) : +Lors de l'insertion, le canal alpha est respecté, de plus, nous pouvons influencer la transparence de l'image insérée (nous créons un soi-disant filigrane) : ```php -$blank->place($image, '80%', '80%', 25); // la transparence est de 25 %. +$blank->place($image, '80%', '80%', 25); // la transparence est de 25 % ``` -Une telle API est vraiment un plaisir à utiliser, n'est-ce pas ? +Une telle API est vraiment un plaisir à utiliser ! -Aperçu des méthodes .[#toc-overview-of-methods] -=============================================== +Aperçu des méthodes +=================== -static fromBlank(int $width, int $height, array $color=null): Image .[method] ------------------------------------------------------------------------------ -Crée une nouvelle image en couleurs réelles avec les dimensions données. La couleur par défaut est le noir. +static fromBlank(int $width, int $height, ?ImageColor $color=null): Image .[method] +----------------------------------------------------------------------------------- +Crée une nouvelle image true color des dimensions données. La couleur par défaut est noire. static fromFile(string $file, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------- -Lit une image à partir d'un fichier et renvoie son type à `$detectedFormat`. Les types supportés sont `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` et `Image::BMP`. +Charge une image depuis un fichier et retourne son [type |#Formats] dans `$detectedFormat`. static fromString(string $s, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------ -Lit une image à partir d'une chaîne de caractères et renvoie son type à `$detectedFormat`. Les types supportés sont `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF`, et `Image::BMP`. +Charge une image depuis une chaîne de caractères et retourne son [type |#Formats] dans `$detectedFormat`. -static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method] ---------------------------------------------------------------------------------- -Crée une couleur qui peut être utilisée dans d'autres méthodes, telles que `ellipse()`, `fill()`, etc. +static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method][deprecated] +--------------------------------------------------------------------------------------------- +Cette fonction a été remplacée par la classe `ImageColor`, voir [#couleurs]. static typeToExtension(int $type): string .[method] --------------------------------------------------- -Renvoie l'extension de fichier pour la constante `Image::XXX` donnée. +Retourne l'extension de fichier pour le [type |#Formats] donné. static typeToMimeType(int $type): string .[method] -------------------------------------------------- -Renvoie le type de mime pour la constante `Image::XXX` donnée. +Retourne le type MIME pour le [type |#Formats] donné. static extensionToType(string $extension): int .[method] -------------------------------------------------------- -Renvoie le type d'image sous la forme d'une constante `Image::XXX` en fonction de l'extension du fichier. +Retourne le [type |#Formats] de l'image selon l'extension du fichier. static detectTypeFromFile(string $file, int &$width=null, int &$height=null): ?int .[method] -------------------------------------------------------------------------------------------- -Renvoie le type de fichier image comme constante `Image::XXX` et dans les paramètres `$width` et `$height` également ses dimensions. +Retourne le [type |#Formats] de l'image et, dans les paramètres `$width` et `$height`, également ses dimensions. static detectTypeFromString(string $s, int &$width=null, int &$height=null): ?int .[method] ------------------------------------------------------------------------------------------- -Renvoie le type d'image de la chaîne de caractères comme constante `Image::XXX` et dans les paramètres `$width` et `$height` également ses dimensions. +Retourne le [type |#Formats] de l'image depuis une chaîne de caractères et, dans les paramètres `$width` et `$height`, également ses dimensions. -affine(array $affine, array $clip=null): Image .[method] --------------------------------------------------------- -Renvoie une image contenant l'image src transformée affine, en utilisant une zone de découpage optionnelle. ([plus |https://www.php.net/manual/en/function.imageaffine]). +static isTypeSupported(int $type): bool .[method] +------------------------------------------------- +Détermine si le [type |#Formats] d'image donné est supporté. + + +static getSupportedTypes(): array .[method]{data-version:4.0.4} +--------------------------------------------------------------- +Retourne un tableau des [types |#Formats] d'image supportés. + + +static calculateTextBox(string $text, string $fontFile, float $size, float $angle=0, array $options=[]): array .[method] +------------------------------------------------------------------------------------------------------------------------ +Calcule les dimensions du rectangle qui entoure le texte dans une certaine police et taille. Retourne un tableau associatif contenant les clés `left`, `top`, `width`, `height`. La marge gauche peut être négative si le texte commence par un crénage gauche négatif. + + +affine(array $affine, ?array $clip=null): Image .[method] +--------------------------------------------------------- +Retourne une image contenant l'image source transformée affinement en utilisant une zone de découpe optionnelle. ([plus |https://www.php.net/manual/en/function.imageaffine]). affineMatrixConcat(array $m1, array $m2): array .[method] --------------------------------------------------------- -Renvoie la concaténation de deux matrices de transformation affine, ce qui est utile si plusieurs transformations doivent être appliquées à la même image en une seule fois. ([plus |https://www.php.net/manual/en/function.imageaffinematrixconcat]) +Retourne la concaténation de deux matrices de transformation affine, ce qui est utile si plusieurs transformations doivent être appliquées simultanément à la même image. ([plus |https://www.php.net/manual/en/function.imageaffinematrixconcat]) -affineMatrixGet(int $type, mixed $options=null): array .[method] ----------------------------------------------------------------- +affineMatrixGet(int $type, ?mixed $options=null): array .[method] +----------------------------------------------------------------- Retourne une matrice de transformation affine. ([plus |https://www.php.net/manual/en/function.imageaffinematrixget]) alphaBlending(bool $on): void .[method] --------------------------------------- -Permet deux modes différents de dessin sur des images en couleurs vraies. En mode de mélange, la composante du canal alpha de la couleur fournie à toutes les fonctions de dessin, telles que `setPixel()`, détermine la part de la couleur sous-jacente qui doit être laissée transparaître. Par conséquent, il mélange automatiquement la couleur existante à cet endroit avec la couleur du dessin, et enregistre le résultat dans l'image. Le pixel résultant est opaque. En mode sans mélange, la couleur du dessin est copiée littéralement avec les informations de son canal alpha, en remplaçant le pixel de destination. Le mode de mélange n'est pas disponible lorsque vous dessinez sur des images de palette. ([plus |https://www.php.net/manual/en/function.imagealphablending]) +Permet deux modes de dessin différents dans les images truecolor. En mode de fusion, la composante du canal alpha de la couleur utilisée dans toutes les fonctions de dessin, comme par exemple `setPixel()`, détermine dans quelle mesure la couleur de base devrait être autorisée à transparaître. Le résultat est que la couleur existante est automatiquement mélangée à ce point avec la couleur dessinée et le résultat est enregistré dans l'image. Le pixel résultant est opaque. En mode sans fusion, la couleur dessinée est copiée littéralement avec les informations du canal alpha et remplace le pixel cible. Le mode de fusion n'est pas disponible lors du dessin sur des images à palette. ([plus |https://www.php.net/manual/en/function.imagealphablending]) antialias(bool $on): void .[method] ----------------------------------- -Activez les méthodes de dessin rapide avec anticrénelage pour les lignes et les polygones câblés. Il ne prend pas en charge les composants alpha. Il fonctionne en utilisant une opération de mélange direct. Il ne fonctionne qu'avec des images en couleurs vraies. +Active le dessin de lignes et de polygones lissés. Ne supporte pas les canaux alpha. Fonctionne uniquement sur les images truecolor. -L'utilisation de primitives anticrénelées avec une couleur de fond transparente peut donner des résultats inattendus. La méthode de fusion utilise la couleur de fond comme n'importe quelle autre couleur. L'absence de prise en charge de la composante alpha ne permet pas d'utiliser une méthode d'anticrénelage basée sur l'alpha. ([plus |https://www.php.net/manual/en/function.imageantialias]) +L'utilisation de primitives anticrénelées avec une couleur de fond transparente peut aboutir à certains résultats inattendus. La méthode de fusion utilise la couleur de fond comme toutes les autres couleurs. ([plus |https://www.php.net/manual/en/function.imageantialias]) -arc(int $x, int $y, int $w, int $h, int $start, int $end, int $color): void .[method] -------------------------------------------------------------------------------------- -Dessine un arc de cercle centré sur les coordonnées données. ([plus |https://www.php.net/manual/en/function.imagearc]) - - -char(int $font, int $x, int $y, string $char, int $color): void .[method] -------------------------------------------------------------------------- -Dessine le premier caractère de `$char` dans l'image avec son haut gauche à `$x`,`$y` (haut gauche est 0, 0) avec la couleur `$color`. ([plus |https://www.php.net/manual/en/function.imagechar]) - - -charUp(int $font, int $x, int $y, string $char, int $color): void .[method] ---------------------------------------------------------------------------- -Dessine le caractère `$char` verticalement à la coordonnée spécifiée sur l'image donnée. ([plus |https://www.php.net/manual/en/function.imagecharup]) +arc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color): void .[method] +--------------------------------------------------------------------------------------------------------------------------- +Dessine un arc de cercle centré aux coordonnées données. ([plus |https://www.php.net/manual/en/function.imagearc]) colorAllocate(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------- -Renvoie un identifiant de couleur représentant la couleur composée des composantes RVB données. Elle doit être appelée pour créer chaque couleur qui doit être utilisée dans l'image. ([plus |https://www.php.net/manual/en/function.imagecolorallocate]) +Retourne un identifiant de couleur représentant la couleur composée des composantes RVB données. Doit être appelée pour créer chaque couleur qui doit être utilisée dans l'image. ([plus |https://www.php.net/manual/en/function.imagecolorallocate]) colorAllocateAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ------------------------------------------------------------------------------ -Se comporte de manière identique à `colorAllocate()` avec l'ajout du paramètre de transparence `$alpha`. ([plus |https://www.php.net/manual/en/function.imagecolorallocatealpha]) +Se comporte de la même manière que `colorAllocate()` avec l'ajout du paramètre de transparence `$alpha`. ([plus |https://www.php.net/manual/en/function.imagecolorallocatealpha]) colorAt(int $x, int $y): int .[method] -------------------------------------- -Renvoie l'indice de la couleur du pixel à l'emplacement spécifié dans l'image. Si l'image est une image en couleur, cette fonction renvoie la valeur RVB de ce pixel sous forme d'un nombre entier. Utilisez le décalage de bits et le masquage pour accéder aux valeurs distinctes des composantes rouge, verte et bleue : ([plus |https://www.php.net/manual/en/function.imagecolorat]) +Retourne l'index de couleur du pixel à l'emplacement spécifié dans l'image. Si l'image est truecolor, cette fonction retourne la valeur RVB de ce pixel comme un entier. Utilisez le décalage de bits et le masquage de bits pour accéder aux valeurs séparées des composantes rouge, verte et bleue : ([plus |https://www.php.net/manual/en/function.imagecolorat]) colorClosest(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------ -Renvoie l'indice de la couleur dans la palette de l'image qui est "la plus proche" de la valeur RVB spécifiée. La "distance" entre la couleur souhaitée et chaque couleur de la palette est calculée comme si les valeurs RVB représentaient des points dans un espace tridimensionnel. ([plus |https://www.php.net/manual/en/function.imagecolorclosest]) +Retourne l'index de couleur dans la palette de l'image qui est « le plus proche » de la valeur RVB spécifiée. La « distance » entre la couleur requise et chaque couleur dans la palette est calculée comme si les valeurs RVB représentaient des points dans un espace tridimensionnel. ([plus |https://www.php.net/manual/en/function.imagecolorclosest]) colorClosestAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ----------------------------------------------------------------------------- -Renvoie l'indice de la couleur dans la palette de l'image qui est "la plus proche" de la valeur RVB et du niveau `$alpha` spécifiés. ([plus |https://www.php.net/manual/en/function.imagecolorclosestalpha]) +Retourne l'index de couleur dans la palette de l'image qui est « le plus proche » de la valeur RVB spécifiée et du niveau `$alpha`. ([plus |https://www.php.net/manual/en/function.imagecolorclosestalpha]) colorClosestHWB(int $red, int $green, int $blue): int .[method] --------------------------------------------------------------- -Obtenez l'indice de la couleur qui a la teinte, le blanc et la noirceur les plus proches de la couleur donnée. ([plus |https://www.php.net/manual/en/function.imagecolorclosesthwb]) +Obtient l'index de la couleur qui a la teinte, le blanc et le noir les plus proches de la couleur donnée. ([plus |https://www.php.net/manual/en/function.imagecolorclosesthwb]) colorDeallocate(int $color): void .[method] ------------------------------------------- -Désallocation d'une couleur précédemment allouée avec `colorAllocate()` ou `colorAllocateAlpha()`. ([plus |https://www.php.net/manual/en/function.imagecolordeallocate]) +Désalloue une couleur précédemment allouée avec `colorAllocate()` ou `colorAllocateAlpha()`. ([plus |https://www.php.net/manual/en/function.imagecolordeallocate]) colorExact(int $red, int $green, int $blue): int .[method] ---------------------------------------------------------- -Renvoie l'index de la couleur spécifiée dans la palette de l'image. ([plus |https://www.php.net/manual/en/function.imagecolorexact]) +Retourne l'index de la couleur spécifiée dans la palette de l'image. ([plus |https://www.php.net/manual/en/function.imagecolorexact]) colorExactAlpha(int $red, int $green, int $blue, int $alpha): int .[method] --------------------------------------------------------------------------- -Renvoie l'index de la couleur+alpha spécifiée dans la palette de l'image. ([plus |https://www.php.net/manual/en/function.imagecolorexactalpha]) +Retourne l'index de la couleur + alpha spécifiée dans la palette de l'image. ([plus |https://www.php.net/manual/en/function.imagecolorexactalpha]) colorMatch(Image $image2): void .[method] ----------------------------------------- -Les couleurs de la version palette d'une image se rapprochent davantage de la version en couleurs réelles. ([plus |https://www.php.net/manual/en/function.imagecolormatch]) +Adapte les couleurs de la palette à la seconde image. ([plus |https://www.php.net/manual/en/function.imagecolormatch]) colorResolve(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------ -Renvoie un indice de couleur pour une couleur demandée, soit la couleur exacte, soit l'alternative la plus proche possible. ([plus |https://www.php.net/manual/en/function.imagecolorresolve]) +Retourne l'index de couleur pour la couleur requise, soit la couleur exacte, soit l'alternative la plus proche possible. ([plus |https://www.php.net/manual/en/function.imagecolorresolve]) colorResolveAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ----------------------------------------------------------------------------- -Renvoie un indice de couleur pour une couleur demandée, soit la couleur exacte, soit l'alternative la plus proche possible. ([plus |https://www.php.net/manual/en/function.imagecolorresolvealpha]) +Retourne l'index de couleur pour la couleur requise, soit la couleur exacte, soit l'alternative la plus proche possible. ([plus |https://www.php.net/manual/en/function.imagecolorresolvealpha]) colorSet(int $index, int $red, int $green, int $blue): void .[method] --------------------------------------------------------------------- -Ceci définit l'index spécifié dans la palette à la couleur spécifiée. ([plus |https://www.php.net/manual/en/function.imagecolorset]) +Définit l'index spécifié dans la palette à la couleur spécifiée. ([plus |https://www.php.net/manual/en/function.imagecolorset]) colorsForIndex(int $index): array .[method] ------------------------------------------- -Obtient la couleur pour un indice spécifié. ([plus |https://www.php.net/manual/en/function.imagecolorsforindex]) +Obtient la couleur de l'index spécifié. ([plus |https://www.php.net/manual/en/function.imagecolorsforindex]) colorsTotal(): int .[method] ---------------------------- -Renvoie le nombre de couleurs dans une palette d'images. ([plus |https://www.php.net/manual/en/function.imagecolorstotal]) +Retourne le nombre de couleurs dans la palette de l'image. ([plus |https://www.php.net/manual/en/function.imagecolorstotal]) -colorTransparent(int $color=null): int .[method] ------------------------------------------------- -Obtient ou définit la couleur transparente de l'image. ([plus |https://www.php.net/manual/en/function.imagecolortransparent]) +colorTransparent(?int $color=null): int .[method] +------------------------------------------------- +Obtient ou définit la couleur transparente dans l'image. ([plus |https://www.php.net/manual/en/function.imagecolortransparent]) convolution(array $matrix, float $div, float $offset): void .[method] --------------------------------------------------------------------- -Applique une matrice de convolution sur l'image, en utilisant le coefficient et le décalage donnés. ([plus |https://www.php.net/manual/en/function.imageconvolution]) +Applique une matrice de convolution à l'image, en utilisant le coefficient et le décalage donnés. ([plus |https://www.php.net/manual/en/function.imageconvolution]) .[note] -Nécessite *l'extension GD groupée*, il n'est donc pas sûr qu'elle fonctionne partout. +Nécessite la présence de l'*extension GD intégrée*, peut donc ne pas fonctionner partout. copy(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH): void .[method] -------------------------------------------------------------------------------------------------- -Copie une partie de `$src` sur l'image à partir des coordonnées `$srcX`, `$srcY` avec une largeur de `$srcW` et une hauteur de `$srcH`. La portion définie sera copiée sur les coordonnées, `$dstX` et `$dstY`. ([plus |https://www.php.net/manual/en/function.imagecopy]) +Copie une partie de `$src` sur l'image commençant aux coordonnées `$srcX`, `$srcY` avec une largeur `$srcW` et une hauteur `$srcH`. La partie définie sera copiée aux coordonnées `$dstX` et `$dstY`. ([plus |https://www.php.net/manual/en/function.imagecopy]) copyMerge(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] --------------------------------------------------------------------------------------------------------------------- -Copie une partie de `$src` sur l'image à partir des coordonnées `$srcX`, `$srcY` avec une largeur de `$srcW` et une hauteur de `$srcH`. La portion définie sera copiée sur les coordonnées, `$dstX` et `$dstY`. ([plus |https://www.php.net/manual/en/function.imagecopymerge]) +Copie une partie de `$src` sur l'image commençant aux coordonnées `$srcX`, `$srcY` avec une largeur `$srcW` et une hauteur `$srcH`. La partie définie sera copiée aux coordonnées `$dstX` et `$dstY`. ([plus |https://www.php.net/manual/en/function.imagecopymerge]) copyMergeGray(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] ------------------------------------------------------------------------------------------------------------------------- -Copie une partie de `$src` sur l'image à partir des coordonnées `$srcX`, `$srcY` avec une largeur de `$srcW` et une hauteur de `$srcH`. La portion définie sera copiée sur les coordonnées, `$dstX` et `$dstY`. +Copie une partie de `$src` sur l'image commençant aux coordonnées `$srcX`, `$srcY` avec une largeur `$srcW` et une hauteur `$srcH`. La partie définie sera copiée aux coordonnées `$dstX` et `$dstY`. -Cette fonction est identique à `copyMerge()`, sauf que lors de la fusion, elle préserve la teinte de la source en convertissant les pixels de destination en échelle de gris avant l'opération de copie. ([plus |https://www.php.net/manual/en/function.imagecopymergegray]) +Cette fonction est identique à `copyMerge()` à l'exception que lors de la fusion, elle préserve la teinte de la source en convertissant les pixels cibles en échelle de gris avant l'opération de copie. ([plus |https://www.php.net/manual/en/function.imagecopymergegray]) copyResampled(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] --------------------------------------------------------------------------------------------------------------------------------- -Copie une partie rectangulaire d'une image sur une autre image, en interpolant de manière fluide les valeurs des pixels, de sorte que, notamment, la réduction de la taille d'une image conserve une grande clarté. +Copie une partie rectangulaire d'une image sur une autre image, en interpolant en douceur les valeurs des pixels, de sorte que notamment la réduction de la taille de l'image conserve toujours une grande clarté. -En d'autres termes, `copyResampled()` prendra une zone rectangulaire de `$src` de largeur `$srcW` et de hauteur `$srcH` à la position (`$srcX`,`$srcY`) et la placera dans une zone rectangulaire de l'image de largeur `$dstW` et de hauteur `$dstH` à la position (`$dstX`,`$dstY`). +En d'autres termes, `copyResampled()` prend une zone rectangulaire de `$src` de largeur `$srcW` et de hauteur `$srcH` à la position (`$srcX`, `$srcY`) et la place dans une zone rectangulaire de l'image de largeur `$dstW` et de hauteur `$dstH` à la position (`$dstX`, `$dstY`). -Si les coordonnées, la largeur et la hauteur de la source et de la destination diffèrent, le fragment d'image sera étiré ou rétréci de manière appropriée. Les coordonnées se rapportent au coin supérieur gauche. Cette fonction peut être utilisée pour copier des régions dans la même image, mais si les régions se chevauchent, les résultats seront imprévisibles. ([plus |https://www.php.net/manual/en/function.imagecopyresampled]) +Si les coordonnées source et cible, la largeur et la hauteur diffèrent, un étirement ou une réduction correspondant du fragment d'image est effectué. Les coordonnées se réfèrent au coin supérieur gauche. Cette fonction peut être utilisée pour copier des zones dans la même image, mais si les zones se chevauchent, les résultats ne seront pas prévisibles. ([plus |https://www.php.net/manual/en/function.imagecopyresampled]) copyResized(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] ------------------------------------------------------------------------------------------------------------------------------- -Copie une partie rectangulaire d'une image sur une autre image. En d'autres termes, `copyResized()` prendra une zone rectangulaire de `$src` de largeur `$srcW` et de hauteur `$srcH` à la position (`$srcX`,`$srcY`) et la placera dans une zone rectangulaire de l'image de largeur `$dstW` et de hauteur `$dstH` à la position (`$dstX`,`$dstY`). +Copie une partie rectangulaire d'une image sur une autre image. En d'autres termes, `copyResized()` obtient une zone rectangulaire de `$src` de largeur `$srcW` et de hauteur `$srcH` à la position (`$srcX`, `$srcY`) et la place dans une zone rectangulaire de l'image de largeur `$dstW` et de hauteur `$dstH` à la position (`$dstX`, `$dstY`). -Si les coordonnées, la largeur et la hauteur de la source et de la destination diffèrent, le fragment d'image sera étiré ou rétréci de manière appropriée. Les coordonnées se rapportent au coin supérieur gauche. Cette fonction peut être utilisée pour copier des régions dans la même image, mais si les régions se chevauchent, les résultats seront imprévisibles. ([plus |https://www.php.net/manual/en/function.imagecopyresized]) +Si les coordonnées source et cible, la largeur et la hauteur diffèrent, un étirement ou une réduction correspondant du fragment d'image est effectué. Les coordonnées se réfèrent au coin supérieur gauche. TCette fonction peut être utilisée pour copier des zones dans la même image, mais si les zones se chevauchent, les résultats ne seront pas prévisibles. ([plus |https://www.php.net/manual/en/function.imagecopyresized]) crop(int|string $left, int|string $top, int|string $width, int|string $height): Image .[method] ----------------------------------------------------------------------------------------------- -Recadrer une image dans une zone rectangulaire donnée. Les dimensions peuvent être transmises sous forme d'entiers en pixels ou de chaînes de caractères en pourcentage (par exemple, `'50%'`). +Recadre l'image dans la zone rectangulaire donnée. Les dimensions peuvent être spécifiées comme des entiers en pixels ou des chaînes en pourcentages (par exemple `'50%'`). -cropAuto(int $mode=-1, float $threshold=.5, int $color=-1): Image .[method] ---------------------------------------------------------------------------- -Recadrage automatique d'une image en fonction de l'adresse `$mode`. ([plus |https://www.php.net/manual/en/function.imagecropauto]) +cropAuto(int $mode=-1, float $threshold=.5, ?ImageColor $color=null): Image .[method] +------------------------------------------------------------------------------------- +Recadre automatiquement l'image selon le `$mode` donné. ([plus |https://www.php.net/manual/en/function.imagecropauto]) -ellipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------- -Dessine une ellipse centrée sur les coordonnées spécifiées. ([plus |https://www.php.net/manual/en/function.imageellipse]) +ellipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------- +Dessine une ellipse centrée aux coordonnées spécifiées. ([plus |https://www.php.net/manual/en/function.imageellipse]) -fill(int $x, int $y, int $color): void .[method] ------------------------------------------------- -Effectue un remplissage par inondation à partir de la coordonnée donnée (en haut à gauche, c'est 0, 0) avec le `$color` donné dans l'image. ([plus |https://www.php.net/manual/en/function.imagefill]) +fill(int $x, int $y, ImageColor $color): void .[method] +------------------------------------------------------- +Remplit une zone en commençant par la coordonnée donnée (en haut à gauche est 0, 0) avec la `$color` donnée. ([plus |https://www.php.net/manual/en/function.imagefill]) -filledArc(int $cx, int $cy, int $w, int $h, int $s, int $e, int $color, int $style): void .[method] ---------------------------------------------------------------------------------------------------- -Dessine un arc partiel centré sur la coordonnée spécifiée dans l'image. ([plus |https://www.php.net/manual/en/function.imagefilledarc]) +filledArc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color, int $style): void .[method] +--------------------------------------------------------------------------------------------------------------------------------------------- +Dessine un arc partiel centré aux coordonnées spécifiées. ([plus |https://www.php.net/manual/en/function.imagefilledarc]) -filledEllipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------------- -Dessine une ellipse centrée sur la coordonnée spécifiée dans l'image. ([plus |https://www.php.net/manual/en/function.imagefilledellipse]) +filledEllipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------------- +Dessine une ellipse centrée aux coordonnées spécifiées. ([plus |https://www.php.net/manual/en/function.imagefilledellipse]) -filledPolygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------------- -Crée un polygone rempli dans l'image $. ([plus |https://www.php.net/manual/en/function.imagefilledpolygon]) +filledPolygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------------- +Crée un polygone rempli dans l'image. ([plus |https://www.php.net/manual/en/function.imagefilledpolygon]) -filledRectangle(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] -------------------------------------------------------------------------------- -Crée un rectangle rempli de `$color` dans l'image en commençant au point 1 et en terminant au point 2. 0, 0 est le coin supérieur gauche de l'image. ([plus |https://www.php.net/manual/en/function.imagefilledrectangle]) +filledRectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------- +Crée un rectangle rempli avec `$color` dans l'image commençant au point `$x1` & `$y1` et se terminant au point `$x2` & `$y2`. Le point 0, 0 est le coin supérieur gauche de l'image. ([plus |https://www.php.net/manual/en/function.imagefilledrectangle]) -fillToBorder(int $x, int $y, int $border, int $color): void .[method] ---------------------------------------------------------------------- -Effectue un remplissage par inondation dont la couleur de la bordure est définie par `$border`. Le point de départ du remplissage est `$x`, `$y` (en haut à gauche, 0, 0) et la région est remplie de la couleur `$color`. ([plus |https://www.php.net/manual/en/function.imagefilltoborder]) +filledRectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------------- +Crée un rectangle rempli avec `$color` dans l'image commençant au point `$left` & `$top` avec une largeur `$width` et une hauteur `$height`. Le point 0, 0 est le coin supérieur gauche de l'image. + + +fillToBorder(int $x, int $y, int $border, ImageColor $color): void .[method] +---------------------------------------------------------------------------- +Dessine un remplissage dont la couleur de bordure est définie par `$border`. Le point de départ du remplissage est `$x`, `$y` (en haut à gauche est 0, 0) et la zone est remplie avec la couleur `$color`. ([plus |https://www.php.net/manual/en/function.imagefilltoborder]) filter(int $filtertype, int ...$args): void .[method] ----------------------------------------------------- -Applique le filtre donné `$filtertype` sur l'image. ([plus |https://www.php.net/manual/en/function.imagefilter]) +Applique le filtre `$filtertype` donné à l'image. ([plus |https://www.php.net/manual/en/function.imagefilter]) flip(int $mode): void .[method] ------------------------------- -Retourne l'image en utilisant l'adresse `$mode`. ([plus |https://www.php.net/manual/en/function.imageflip]) +Retourne l'image en utilisant le `$mode` donné. ([plus |https://www.php.net/manual/en/function.imageflip]) -ftText(int $size, int $angle, int $x, int $y, int $col, string $fontFile, string $text, array $extrainfo=null): array .[method] -------------------------------------------------------------------------------------------------------------------------------- -Écrivez du texte sur l'image à l'aide de polices de caractères utilisant FreeType 2. ([plus |https://www.php.net/manual/en/function.imagefttext]) +ftText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +---------------------------------------------------------------------------------------------------------------------------------------- +Écrit le texte dans l'image. ([plus |https://www.php.net/manual/en/function.imagefttext]) gammaCorrect(float $inputgamma, float $outputgamma): void .[method] ------------------------------------------------------------------- -Applique une correction gamma à l'image en fonction d'un gamma d'entrée et d'un gamma de sortie. ([plus |https://www.php.net/manual/en/function.imagegammacorrect]) +Applique une correction gamma à l'image par rapport au gamma d'entrée et de sortie. ([plus |https://www.php.net/manual/en/function.imagegammacorrect]) getClip(): array .[method] -------------------------- -Récupère le rectangle d'écrêtage actuel, c'est-à-dire la zone au-delà de laquelle aucun pixel ne sera dessiné. ([plus |https://www.php.net/manual/en/function.imagegetclip]) +Retourne le découpage actuel, c'est-à-dire la zone au-delà de laquelle aucun pixel ne sera dessiné. ([plus |https://www.php.net/manual/en/function.imagegetclip]) getHeight(): int .[method] -------------------------- -Renvoie la hauteur de l'image. +Retourne la hauteur de l'image. getImageResource(): resource|GdImage .[method] @@ -509,172 +550,167 @@ Retourne la ressource originale. getWidth(): int .[method] ------------------------- -Renvoie la largeur de l'image. +Retourne la largeur de l'image. -interlace(int $interlace=null): int .[method] ---------------------------------------------- -Active ou désactive le bit d'entrelacement. Si le bit d'entrelacement est activé et que l'image est utilisée comme une image JPEG, l'image est créée comme un JPEG progressif. ([plus |https://www.php.net/manual/en/function.imageinterlace]) +interlace(?int $interlace=null): int .[method] +---------------------------------------------- +Active ou désactive le mode entrelacé. Si le mode entrelacé est activé et que l'image est enregistrée en JPEG, elle sera enregistrée en JPEG progressif. ([plus |https://www.php.net/manual/en/function.imageinterlace]) isTrueColor(): bool .[method] ----------------------------- -Détermine si l'image est une vraie couleur. ([plus |https://www.php.net/manual/en/function.imageistruecolor]) +Détermine si l'image est truecolor. ([plus |https://www.php.net/manual/en/function.imageistruecolor]) layerEffect(int $effect): void .[method] ---------------------------------------- -Définissez le drapeau de mélange alpha pour utiliser les effets de superposition. ([plus |https://www.php.net/manual/en/function.imagelayereffect]) +Définit le drapeau de fusion alpha pour utiliser les effets de calque. ([plus |https://www.php.net/manual/en/function.imagelayereffect]) -line(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] --------------------------------------------------------------------- -Trace une ligne entre les deux points donnés. ([plus |https://www.php.net/manual/en/function.imageline]) +line(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +--------------------------------------------------------------------------- +Dessine une ligne entre deux points donnés. ([plus |https://www.php.net/manual/en/function.imageline]) -openPolygon(array $points, int $numPoints, int $color): void .[method] ----------------------------------------------------------------------- -Dessine un polygone ouvert sur l'image. Contrairement à `polygon()`, aucune ligne n'est tracée entre le dernier et le premier point. ([plus |https://www.php.net/manual/en/function.imageopenpolygon]) +openPolygon(array $points, ImageColor $color): void .[method] +------------------------------------------------------------- +Dessine un polygone ouvert sur l'image. Contrairement à `polygon()`, aucune ligne n'est dessinée entre le dernier et le premier point. ([plus |https://www.php.net/manual/en/function.imageopenpolygon]) paletteCopy(Image $source): void .[method] ------------------------------------------ -Copie la palette du site `$source` sur l'image. ([plus |https://www.php.net/manual/en/function.imagepalettecopy]) +Copie la palette de `$source` dans l'image. ([plus |https://www.php.net/manual/en/function.imagepalettecopy]) paletteToTrueColor(): void .[method] ------------------------------------ -Convertit une image basée sur une palette, créée par des fonctions comme `create()`, en une image en couleurs réelles, comme `createtruecolor()`. ([plus |https://www.php.net/manual/en/function.imagepalettetotruecolor]) +Convertit une image basée sur une palette en image en couleurs réelles. ([plus |https://www.php.net/manual/en/function.imagepalettetotruecolor]) place(Image $image, int|string $left=0, int|string $top=0, int $opacity=100): Image .[method] --------------------------------------------------------------------------------------------- -Copie `$image` sur l'image aux coordonnées `$left` et `$top`. Les coordonnées peuvent être transmises sous forme d'entiers en pixels ou de chaînes de caractères en pourcentage (par exemple `'50%'`). +Copie `$image` dans l'image aux coordonnées `$left` et `$top`. Les coordonnées peuvent être spécifiées comme des entiers en pixels ou des chaînes en pourcentages (par exemple `'50%'`). -polygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------- +polygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------- Crée un polygone dans l'image. ([plus |https://www.php.net/manual/en/function.imagepolygon]) -rectangle(int $x1, int $y1, int $x2, int $y2, int $col): void .[method] ------------------------------------------------------------------------ -Crée un rectangle à partir des coordonnées spécifiées. ([plus |https://www.php.net/manual/en/function.imagerectangle]) +rectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------- +Crée un rectangle aux coordonnées spécifiées. ([plus |https://www.php.net/manual/en/function.imagerectangle]) + + +rectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------- +Crée un rectangle aux coordonnées spécifiées. resize(int|string $width, int|string $height, int $flags=Image::OrSmaller): Image .[method] ------------------------------------------------------------------------------------------- -Mise à l'échelle d'une image, voir [plus d'infos |#Image Resize]. Les dimensions peuvent être transmises sous forme d'entiers en pixels ou de chaînes de caractères en pourcentage (par exemple, `'50%'`). +Redimensionne l'image, [plus d'informations |#Redimensionnement]. Les dimensions peuvent être spécifiées comme des entiers en pixels ou des chaînes en pourcentages (par exemple `'50%'`). -resolution(int $resX=null, int $resY=null): mixed .[method] ------------------------------------------------------------ -Permet de définir et d'obtenir la résolution d'une image en DPI (points par pouce). Si aucun des paramètres optionnels n'est donné, la résolution courante est retournée sous forme de tableau indexé. Si seule la valeur `$resX` est donnée, la résolution horizontale et verticale est fixée à cette valeur. Si les deux paramètres facultatifs sont donnés, les résolutions horizontale et verticale sont respectivement définies à ces valeurs. +resolution(?int $resX=null, ?int $resY=null): mixed .[method] +------------------------------------------------------------- +Définit ou retourne la résolution de l'image en DPI (points par pouce). Si aucun des paramètres optionnels n'est spécifié, la résolution actuelle est retournée comme un tableau indexé. Si seulement `$resX` est spécifié, la résolution horizontale et verticale est définie à cette valeur. Si les deux paramètres optionnels sont spécifiés, la résolution horizontale et verticale est définie à ces valeurs. -La résolution est uniquement utilisée comme méta-information lorsque les images sont lues et écrites dans des formats supportant ce type d'information (actuellement PNG et JPEG). Elle n'affecte pas les opérations de dessin. La résolution par défaut des nouvelles images est de 96 DPI. ([plus |https://www.php.net/manual/en/function.imageresolution]) +La résolution est utilisée uniquement comme méta-information lorsque les images sont lues et écrites dans des formats supportant ce type d'information (actuellement PNG et JPEG). Cela n'affecte aucune opération de dessin. La résolution par défaut des nouvelles images est de 96 DPI. ([plus |https://www.php.net/manual/en/function.imageresolution]) rotate(float $angle, int $backgroundColor): Image .[method] ----------------------------------------------------------- -Fait pivoter l'image en utilisant l'adresse `$angle` donnée en degrés. Le centre de rotation est le centre de l'image, et l'image tournée peut avoir des dimensions différentes de celles de l'image originale. ([plus |https://www.php.net/manual/en/function.imagerotate]) +Pivote l'image en utilisant l'angle `$angle` spécifié en degrés. Le centre de rotation est le centre de l'image et l'image pivotée peut avoir des dimensions différentes de l'image originale. ([plus |https://www.php.net/manual/en/function.imagerotate]) .[note] -Nécessite *l'extension GD groupée*, il n'est donc pas sûr qu'elle fonctionne partout. +Nécessite la présence de l'*extension GD intégrée*, peut donc ne pas fonctionner partout. -save(string $file, int $quality=null, int $type=null): void .[method] ---------------------------------------------------------------------- -Enregistre une image dans un fichier. +save(string $file, ?int $quality=null, ?int $type=null): void .[method] +----------------------------------------------------------------------- +Enregistre l'image dans un fichier. -La qualité de la compression est comprise entre 0 et 100 pour JPEG (85 par défaut), WEBP (80 par défaut) et AVIF (30 par défaut) et entre 0 et 9 pour PNG (9 par défaut). Si le type n'est pas évident à partir de l'extension du fichier, vous pouvez le spécifier en utilisant l'une des constantes `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF`, et `Image::BMP`. +La qualité de compression est dans la plage 0..100 pour JPEG (par défaut 85), WEBP (par défaut 80) et AVIF (par défaut 30) et 0..9 pour PNG (par défaut 9). Si le type n'est pas évident d'après l'extension du fichier, vous pouvez le spécifier en utilisant l'une des constantes `ImageType`. saveAlpha(bool $saveflag): void .[method] ----------------------------------------- -Définit l'indicateur qui détermine s'il faut conserver les informations complètes du canal alpha (par opposition à la transparence d'une seule couleur) lors de l'enregistrement des images PNG. +Définit le drapeau indiquant s'il faut conserver les informations complètes du canal alpha lors de l'enregistrement des images PNG (contrairement à la transparence monochrome). -L'alphablending doit être désactivé (`alphaBlending(false)`) pour conserver le canal alpha. ([plus |https://www.php.net/manual/en/function.imagesavealpha]) +La fusion alpha doit être désactivée (`alphaBlending(false)`) pour que le canal alpha soit conservé en premier lieu. ([plus |https://www.php.net/manual/en/function.imagesavealpha]) scale(int $newWidth, int $newHeight=-1, int $mode=IMG_BILINEAR_FIXED): Image .[method] -------------------------------------------------------------------------------------- -Met à l'échelle une image en utilisant l'algorithme d'interpolation donné. ([plus |https://www.php.net/manual/en/function.imagescale]) +Met à l'échelle l'image en utilisant l'algorithme d'interpolation donné. ([plus |https://www.php.net/manual/en/function.imagescale]) -send(int $type=Image::JPEG, int $quality=null): void .[method] --------------------------------------------------------------- -Affiche une image dans le navigateur. +send(int $type=ImageType::JPEG, ?int $quality=null): void .[method] +------------------------------------------------------------------- +Affiche l'image dans le navigateur. -La qualité de la compression est comprise entre 0 et 100 pour JPEG (85 par défaut), WEBP (80 par défaut) et AVIF (30 par défaut) et entre 0 et 9 pour PNG (9 par défaut). Type est l'une des constantes suivantes : `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` et `Image::BMP`. +La qualité de compression est dans la plage 0..100 pour JPEG (par défaut 85), WEBP (par défaut 80) et AVIF (par défaut 30) et 0..9 pour PNG (par défaut 9). setBrush(Image $brush): void .[method] -------------------------------------- -Définit l'image de la brosse à utiliser par toutes les fonctions de traçage de lignes (telles que `line()` et `polygon()`) lorsque vous dessinez avec les couleurs spéciales IMG_COLOR_BRUSHED ou IMG_COLOR_STYLEDBRUSHED. ([plus |https://www.php.net/manual/en/function.imagesetbrush]) +Définit l'image de pinceau qui sera utilisée dans toutes les fonctions de dessin de lignes (par exemple `line()` et `polygon()`) lors du dessin avec les couleurs spéciales IMG_COLOR_BRUSHED ou IMG_COLOR_STYLEDBRUSHED. ([plus |https://www.php.net/manual/en/function.imagesetbrush]) setClip(int $x1, int $y1, int $x2, int $y2): void .[method] ----------------------------------------------------------- -Définit le rectangle d'écrêtage actuel, c'est-à-dire la zone au-delà de laquelle aucun pixel ne sera dessiné. ([plus |https://www.php.net/manual/en/function.imagesetclip]) +Définit le découpage actuel, c'est-à-dire la zone au-delà de laquelle aucun pixel ne sera dessiné. ([plus |https://www.php.net/manual/en/function.imagesetclip]) setInterpolation(int $method=IMG_BILINEAR_FIXED): void .[method] ---------------------------------------------------------------- -Définit la méthode d'interpolation qui affecte les méthodes `rotate()` et `affine()`. ([plus |https://www.php.net/manual/en/function.imagesetinterpolation]) +Définit la méthode d'interpolation, qui affectera les méthodes `rotate()` et `affine()`. ([plus |https://www.php.net/manual/en/function.imagesetinterpolation]) -setPixel(int $x, int $y, int $color): void .[method] ----------------------------------------------------- +setPixel(int $x, int $y, ImageColor $color): void .[method] +----------------------------------------------------------- Dessine un pixel à la coordonnée spécifiée. ([plus |https://www.php.net/manual/en/function.imagesetpixel]) setStyle(array $style): void .[method] -------------------------------------- -Définit le style à utiliser par toutes les fonctions de dessin de lignes (telles que `line()` et `polygon()`) lorsque vous dessinez avec la couleur spéciale IMG_COLOR_STYLED ou des lignes d'images avec la couleur IMG_COLOR_STYLEDBRUSHED. ([plus |https://www.php.net/manual/en/function.imagesetstyle]) +Définit le style qui doit être utilisé par toutes les fonctions de dessin de lignes (par exemple `line()` et `polygon()`) lors du dessin avec la couleur spéciale IMG_COLOR_STYLED ou des lignes d'images avec la couleur IMG_COLOR_STYLEDBRUSHED. ([plus |https://www.php.net/manual/en/function.imagesetstyle]) setThickness(int $thickness): void .[method] -------------------------------------------- -Définit l'épaisseur des lignes tracées lors du dessin de rectangles, polygones, arcs, etc. à `$thickness` pixels. ([plus |https://www.php.net/manual/en/function.imagesetthickness]) +Définit l'épaisseur des lignes lors du dessin de rectangles, polygones, arcs, etc. à `$thickness` pixels. ([plus |https://www.php.net/manual/en/function.imagesetthickness]) setTile(Image $tile): void .[method] ------------------------------------ -Définit l'image de tuiles à utiliser par toutes les fonctions de remplissage de régions (telles que `fill()` et `filledPolygon()`) lors du remplissage avec la couleur spéciale IMG_COLOR_TILED. +Définit l'image de tuile qui sera utilisée dans toutes les fonctions de remplissage de région (par exemple `fill()` et `filledPolygon()`) lors du remplissage avec la couleur spéciale IMG_COLOR_TILED. -Une tuile est une image utilisée pour remplir une zone avec un motif répété. N'importe quelle image peut être utilisée comme tuile, et en définissant l'indice de couleur transparente de l'image de la tuile avec `colorTransparent()`, il est possible de créer une tuile permettant de faire ressortir certaines parties de la zone sous-jacente. ([plus |https://www.php.net/manual/en/function.imagesettile]) +Une tuile est une image utilisée pour remplir une zone avec un motif répétitif. N'importe quelle image peut être utilisée comme tuile et en définissant l'index de couleur transparente de l'image de tuile avec `colorTransparent()`, une tuile peut être créée où certaines parties de la zone sous-jacente transparaîtront. ([plus |https://www.php.net/manual/en/function.imagesettile]) sharpen(): Image .[method] -------------------------- -Affine un peu l'image. +Accentue l'image. .[note] -Nécessite l'extension *Bundled GD*, il n'est donc pas sûr que cela fonctionne partout. - - -string(int $font, int $x, int $y, string $str, int $col): void .[method] ------------------------------------------------------------------------- -Dessine une chaîne aux coordonnées données. ([plus |https://www.php.net/manual/en/function.imagestring]) - +Nécessite la présence de l'*extension GD intégrée*, peut donc ne pas fonctionner partout. -stringUp(int $font, int $x, int $y, string $s, int $col): void .[method] ------------------------------------------------------------------------- -Dessine une chaîne verticalement aux coordonnées données. ([plus |https://www.php.net/manual/en/function.imagestringup]) +toString(int $type=ImageType::JPEG, ?int $quality=null): string .[method] +------------------------------------------------------------------------- +Enregistre l'image dans une chaîne de caractères. -toString(int $type=Image::JPEG, int $quality=null): string .[method] --------------------------------------------------------------------- -Produit une image sous forme de chaîne. - -La qualité de la compression est comprise entre 0 et 100 pour JPEG (85 par défaut), WEBP (80 par défaut) et AVIF (30 par défaut) et entre 0 et 9 pour PNG (9 par défaut). Type est l'une des constantes suivantes : `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` et `Image::BMP`. +La qualité de compression est dans la plage 0..100 pour JPEG (par défaut 85), WEBP (par défaut 80) et AVIF (par défaut 30) et 0..9 pour PNG (par défaut 9). trueColorToPalette(bool $dither, int $ncolors): void .[method] -------------------------------------------------------------- -Convertit une image truecolor en une image palette. ([plus |https://www.php.net/manual/en/function.imagetruecolortopalette]) +Convertit une image truecolor en image à palette. ([plus |https://www.php.net/manual/en/function.imagetruecolortopalette]) -ttfText(int $size, int $angle, int $x, int $y, int $color, string $fontfile, string $text): array .[method] ------------------------------------------------------------------------------------------------------------ -Écrit le texte donné dans l'image en utilisant les polices TrueType. ([plus |https://www.php.net/manual/en/function.imagettftext]) +ttfText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +----------------------------------------------------------------------------------------------------------------------------------------- +Affiche le texte donné dans l'image. ([plus |https://www.php.net/manual/en/function.imagettftext]) diff --git a/utils/fr/iterables.texy b/utils/fr/iterables.texy new file mode 100644 index 0000000000..5d3cab56dd --- /dev/null +++ b/utils/fr/iterables.texy @@ -0,0 +1,170 @@ +Travailler avec les itérateurs +****************************** + +.[perex]{data-version:4.0.4} +[api:Nette\Utils\Iterables] est une classe statique avec des fonctions pour travailler avec les itérateurs. Son équivalent pour les tableaux est [Nette\Utils\Arrays |arrays]. + + +Installation : + +```shell +composer require nette/utils +``` + +Tous les exemples supposent qu'un alias a été créé : + +```php +use Nette\Utils\Iterables; +``` + + +contains(iterable $iterable, $value): bool .[method] +---------------------------------------------------- + +Recherche la valeur spécifiée dans l'itérateur. Utilise une comparaison stricte (`===`) pour vérifier la correspondance. Retourne `true` si la valeur est trouvée, sinon `false`. + +```php +Iterables::contains(new ArrayIterator([1, 2, 3]), 1); // true +Iterables::contains(new ArrayIterator([1, 2, 3]), '1'); // false +``` + +Cette méthode est utile lorsque vous avez besoin de déterminer rapidement si une valeur spécifique se trouve dans l'itérateur, sans avoir à parcourir tous les éléments manuellement. + + +containsKey(iterable $iterable, $key): bool .[method] +----------------------------------------------------- + +Recherche la clé spécifiée dans l'itérateur. Utilise une comparaison stricte (`===`) pour vérifier la correspondance. Retourne `true` si la clé est trouvée, sinon `false`. + +```php +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 0); // true +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 4); // false +``` + + +every(iterable $iterable, callable $predicate): bool .[method] +-------------------------------------------------------------- + +Vérifie si tous les éléments de l'itérateur satisfont la condition définie dans `$predicate`. La fonction `$predicate` a la signature `function ($value, $key, iterable $iterable): bool` et doit retourner `true` pour chaque élément pour que la méthode `every()` retourne `true`. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isBelowThreshold = fn($value) => $value < 40; +$res = Iterables::every($iterator, $isBelowThreshold); // true +``` + +Cette méthode est utile pour vérifier si tous les éléments d'une collection satisfont une certaine condition, par exemple si tous les nombres sont inférieurs à une certaine valeur. + + +filter(iterable $iterable, callable $predicate): Generator .[method] +-------------------------------------------------------------------- + +Crée un nouvel itérateur qui contient seulement les éléments de l'itérateur original qui satisfont la condition définie dans `$predicate`. La fonction `$predicate` a la signature `function ($value, $key, iterable $iterable): bool` et doit retourner `true` pour les éléments qui doivent être conservés. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::filter($iterator, fn($v) => $v < 3); +// 1, 2 +``` + +La méthode utilise un générateur, ce qui signifie que le filtrage se déroule progressivement lors du parcours du résultat. C'est efficace en termes de mémoire et permet de traiter même de très grandes collections. Si vous ne parcourez pas tous les éléments de l'itérateur résultant, vous économisez de la puissance de calcul, car tous les éléments de l'itérateur original ne sont pas traités. + + +first(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------------- + +Retourne le premier élément de l'itérateur. Si `$predicate` est spécifié, retourne le premier élément qui satisfait la condition donnée. La fonction `$predicate` a la signature `function ($value, $key, iterable $iterable): bool`. Si aucun élément correspondant n'est trouvé, la fonction `$else` est appelée (si elle est spécifiée) et son résultat est retourné. Si `$else` n'est pas spécifié, `null` est retourné. + +```php +Iterables::first(new ArrayIterator([1, 2, 3])); // 1 +Iterables::first(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 3 +Iterables::first(new ArrayIterator([])); // null +Iterables::first(new ArrayIterator([]), else: fn() => false); // false +``` + +Cette méthode est utile lorsque vous avez besoin d'obtenir rapidement le premier élément d'une collection ou le premier élément satisfaisant une certaine condition, sans avoir à parcourir toute la collection manuellement. + + +firstKey(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +---------------------------------------------------------------------------------------------- + +Retourne la clé du premier élément de l'itérateur. Si `$predicate` est spécifié, retourne la clé du premier élément qui satisfait la condition donnée. La fonction `$predicate` a la signature `function ($value, $key, iterable $iterable): bool`. Si aucun élément correspondant n'est trouvé, la fonction `$else` est appelée (si elle est spécifiée) et son résultat est retourné. Si `$else` n'est pas spécifié, `null` est retourné. + +```php +Iterables::firstKey(new ArrayIterator([1, 2, 3])); // 0 +Iterables::firstKey(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 2 +Iterables::firstKey(new ArrayIterator(['a' => 1, 'b' => 2])); // 'a' +Iterables::firstKey(new ArrayIterator([])); // null +``` + + +map(iterable $iterable, callable $transformer): Generator .[method] +------------------------------------------------------------------- + +Crée un nouvel itérateur en appliquant la fonction `$transformer` à chaque élément de l'itérateur original. La fonction `$transformer` a la signature `function ($value, $key, iterable $iterable): mixed` et sa valeur de retour est utilisée comme nouvelle valeur de l'élément. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::map($iterator, fn($v) => $v * 2); +// 2, 4, 6 +``` + +La méthode utilise un générateur, ce qui signifie que la transformation se déroule progressivement lors du parcours du résultat. C'est efficace en termes de mémoire et permet de traiter même de très grandes collections. Si vous ne parcourez pas tous les éléments de l'itérateur résultant, vous économisez de la puissance de calcul, car tous les éléments de l'itérateur original ne sont pas traités. + + +mapWithKeys(iterable $iterable, callable $transformer): Generator .[method] +--------------------------------------------------------------------------- + +Crée un nouvel itérateur par transformation des valeurs et des clés de l'itérateur original. La fonction `$transformer` a la signature `function ($value, $key, iterable $iterable): ?array{$newKey, $newValue}`. Si `$transformer` retourne `null`, l'élément est sauté. Pour les éléments conservés, le premier élément du tableau retourné est utilisé comme nouvelle clé et le deuxième élément comme nouvelle valeur. + +```php +$iterator = new ArrayIterator(['a' => 1, 'b' => 2]); +$iterator = Iterables::mapWithKeys($iterator, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +Tout comme `map()`, cette méthode utilise un générateur pour un traitement progressif et un travail efficace avec la mémoire. Cela permet de travailler avec de grandes collections et d'économiser de la puissance de calcul lors d'un parcours partiel du résultat. + + +memoize(iterable $iterable): IteratorAggregate .[method] +-------------------------------------------------------- + +Crée un wrapper autour de l'itérateur qui, pendant l'itération, met en cache ses clés et valeurs. Cela permet une itération répétée des données sans nécessité de reparcourir la source de données originale. + +```php +$iterator = /* données qui ne peuvent pas être itérées plusieurs fois */; +$memoized = Iterables::memoize($iterator); +// Vous pouvez maintenant itérer $memoized plusieurs fois sans perdre de données +``` + +Cette méthode est utile dans des situations où vous avez besoin de parcourir plusieurs fois le même ensemble de données, mais l'itérateur original ne permet pas l'itération répétée ou un parcours répété serait coûteux (par ex. lors de la lecture de données depuis une base de données ou un fichier). + + +some(iterable $iterable, callable $predicate): bool .[method] +------------------------------------------------------------- + +Vérifie si au moins un élément de l'itérateur satisfait la condition définie dans `$predicate`. La fonction `$predicate` a la signature `function ($value, $key, iterable $iterable): bool` et doit retourner `true` pour au moins un élément pour que la méthode `some()` retourne `true`. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isEven = fn($value) => $value % 2 === 0; +$res = Iterables::some($iterator, $isEven); // true +``` + +Cette méthode est utile pour une vérification rapide si dans la collection existe au moins un élément satisfaisant une certaine condition, par exemple si la collection contient au moins un nombre pair. + +Voir [#every()]. + + +toIterator(iterable $iterable): Iterator .[method] +-------------------------------------------------- + +Convertit n'importe quel objet itérable (array, Traversable) en Iterator. Si l'entrée est déjà un Iterator, le retourne sans modification. + +```php +$array = [1, 2, 3]; +$iterator = Iterables::toIterator($array); +// Vous avez maintenant un Iterator au lieu d'un tableau +``` + +Cette méthode est utile lorsque vous avez besoin d'assurer que vous disposez d'un Iterator, quel que soit le type de données d'entrée. Cela peut être utile lors de la création de fonctions qui travaillent avec différents types de données itérables. diff --git a/utils/fr/json.texy b/utils/fr/json.texy index a01a7e23d9..246dfc7d14 100644 --- a/utils/fr/json.texy +++ b/utils/fr/json.texy @@ -1,8 +1,8 @@ -Fonctions JSON -************** +Travailler avec JSON +******************** .[perex] -[api:Nette\Utils\Json] est une classe statique avec des fonctions d'encodage et de décodage JSON. Elle gère les vulnérabilités des différentes versions de PHP et lève les exceptions en cas d'erreur. +[api:Nette\Utils\Json] est une classe statique avec des fonctions pour l'encodage et le décodage du format JSON. Elle gère les vulnérabilités des différentes versions de PHP et lance des exceptions en cas d'erreurs. Installation : @@ -11,15 +11,15 @@ Installation : composer require nette/utils ``` -Tous les exemples supposent que l'alias de classe suivant est défini : +Tous les exemples supposent qu'un alias a été créé : ```php use Nette\Utils\Json; ``` -Utilisation .[#toc-usage] -========================= +Utilisation +=========== encode(mixed $value, bool $pretty=false, bool $asciiSafe=false, bool $htmlSafe=false, bool $forceObjects=false): string .[method] @@ -27,21 +27,21 @@ encode(mixed $value, bool $pretty=false, bool $asciiSafe=false, bool $htmlSafe=f Convertit `$value` au format JSON. -Lorsque `$pretty` est défini, il formate JSON pour une lecture plus facile et une plus grande clarté : +Lorsque `$pretty` est défini, formate le JSON pour une lecture plus facile et une clarté accrue : ```php -Json::encode($value); // renvoie du JSON -Json::encode($value, pretty: true); // renvoie un JSON plus clair +Json::encode($value); // retourne JSON +Json::encode($value, pretty: true); // retourne un JSON plus lisible ``` -Lorsque `$asciiSafe` est défini, il génère une sortie ASCII, c'est-à-dire qu'il remplace les caractères unicode par des séquences `\uxxxx`: +Lorsque `$asciiSafe` est défini, génère une sortie en ASCII, c'est-à-dire remplace les caractères unicode par des séquences `\uxxxx` : ```php Json::encode('žluťoučký', asciiSafe: true); // '"\u017elu\u0165ou\u010dk\u00fd"' ``` -Le paramètre `$htmlSafe` garantit que la sortie ne contient pas de caractères ayant une signification spéciale en HTML : +Le paramètre `$htmlSafe` assure que la sortie ne contiendra pas de caractères ayant une signification spéciale en HTML : ```php Json::encode('onesendJson($data)`, qui peut être appelée, par exemple, dans la méthode `action*()`, voir [Envoi d'une réponse |application:presenters#Sending a Response]. +La méthode `$this->sendJson($data)` peut être utilisée pour cela, que nous pouvons appeler par exemple dans la méthode `action*()`, voir [Envoi de la réponse |application:presenters#Envoi de la réponse]. diff --git a/utils/fr/paginator.texy b/utils/fr/paginator.texy index bffd9ddcb3..1610e5b6c1 100644 --- a/utils/fr/paginator.texy +++ b/utils/fr/paginator.texy @@ -1,8 +1,8 @@ -Paginateur -********** +Paginator +********* .[perex] -Vous avez besoin de paginer une liste de données ? Parce que les mathématiques derrière la pagination peuvent être délicates, [api:Nette\Utils\Paginator] vous aidera. +Avez-vous besoin de paginer l'affichage des données ? Parce que les mathématiques de pagination peuvent être délicates, [api:Nette\Utils\Paginator] vous aidera avec cela. Installation : @@ -11,38 +11,38 @@ Installation : composer require nette/utils ``` -Nous allons créer un objet de pagination et définir les informations de base pour celui-ci : +Nous créons un objet paginator et lui définissons les informations de base : ```php $paginator = new Nette\Utils\Paginator; -$paginator->setPage(1); // le numéro de la page actuelle (numéroté à partir de 1) -$paginator->setItemsPerPage(30); // le nombre d'enregistrements par page -$paginator->setItemCount(356); // le nombre total d'enregistrements (si disponible) +$paginator->setPage(1); // numéro de la page actuelle +$paginator->setItemsPerPage(30); // nombre d'éléments par page +$paginator->setItemCount(356); // nombre total d'éléments, si connu ``` -Les pages sont numérotées à partir de 1. Nous pouvons les modifier en utilisant `setBase()`: +Les pages sont numérotées à partir de 1. Nous pouvons changer cela en utilisant `setBase()` : ```php -$paginator->setBase(0); // numéroté à partir de 0 +$paginator->setBase(0); // nous numérotons à partir de 0 ``` -L'objet va maintenant fournir toutes les informations de base utiles à la création d'un paginateur. Vous pouvez, par exemple, le passer à un modèle et l'y utiliser. +L'objet fournit maintenant toutes les informations de base utiles lors de la création d'un paginateur. Vous pouvez par exemple le passer au template et l'utiliser là-bas. ```php -$paginator->isFirst(); // s'agit-il de la première page ? -$paginator->isLast(); // s'agit-il de la dernière page ? +$paginator->isFirst(); // sommes-nous sur la première page ? +$paginator->isLast(); // sommes-nous sur la dernière page ? $paginator->getPage(); // numéro de la page actuelle -$paginator->getFirstPage(); // le numéro de la première page -$paginator->getLastPage(); // le numéro de la dernière page -$paginator->getFirstItemOnPage(); // numéro d'ordre du premier élément de la page -$paginator->getLastItemOnPage(); // numéro de séquence du dernier élément de la page -$paginator->getPageIndex(); // numéro de la page actuelle si elle est numérotée à partir de 0 -$paginator->getPageCount(); // le nombre total de pages -$paginator->getItemsPerPage(); // le nombre d'enregistrements par page -$paginator->getItemCount(); // le nombre total d'enregistrements (si disponible) +$paginator->getFirstPage(); // numéro de la première page +$paginator->getLastPage(); // numéro de la dernière page +$paginator->getFirstItemOnPage(); // numéro d'ordre du premier élément sur la page +$paginator->getLastItemOnPage(); // numéro d'ordre du dernier élément sur la page +$paginator->getPageIndex(); // numéro de la page actuelle numérotée à partir de 0 +$paginator->getPageCount(); // nombre total de pages +$paginator->getItemsPerPage(); // nombre d'éléments par page +$paginator->getItemCount(); // nombre total d'éléments, si connu ``` -Le paginateur vous aidera à formuler la requête SQL. Les méthodes `getLength()` et `getOffset()` renvoient les valeurs que vous pouvez utiliser dans les clauses LIMIT et OFFSET : +Le paginateur aide lors de la formulation d'une requête SQL. Les méthodes `getLength()` et `getOffset()` retournent des valeurs que nous utiliserons dans les clauses LIMIT et OFFSET : ```php $result = $database->query( @@ -52,7 +52,7 @@ $result = $database->query( ); ``` -Si vous avez besoin de paginer dans l'ordre inverse, c'est-à-dire que la page no. 1 correspond au décalage le plus élevé, vous pouvez utiliser `getCountdownOffset()`: +Si nous avons besoin de paginer dans l'ordre inverse, c'est-à-dire que la page n° 1 correspond à l'offset le plus élevé, nous utiliserons `getCountdownOffset()` : ```php $result = $database->query( @@ -62,4 +62,4 @@ $result = $database->query( ); ``` -Un exemple d'utilisation dans l'application se trouve dans le livre de recettes [Pagination des résultats de la base de données |best-practices:pagination]. +Un exemple d'utilisation dans l'application se trouve dans le cookbook [Pagination des résultats de la base de données |best-practices:pagination]. diff --git a/utils/fr/random.texy b/utils/fr/random.texy index baa7312674..a67aac620b 100644 --- a/utils/fr/random.texy +++ b/utils/fr/random.texy @@ -1,8 +1,8 @@ -Générateur de chaînes aléatoires +Génération de chaînes aléatoires ******************************** .[perex] -[api:Nette\Utils\Random] est une classe statique permettant de générer des chaînes pseudo-aléatoires à sécurité cryptographique. +[api:Nette\Utils\Random] est une classe statique pour la génération de chaînes pseudo-aléatoires cryptographiquement sûres. Installation : @@ -15,7 +15,7 @@ composer require nette/utils generate(int $length=10, string $charlist=`'0-9a-z'`): string .[method] ----------------------------------------------------------------------- -Génère une chaîne aléatoire de longueur donnée à partir des caractères spécifiés dans le second argument. Prend en charge les intervalles, tels que `0-9` ou `A-Z`. +Génère une chaîne aléatoire de la longueur donnée à partir des caractères spécifiés par le paramètre `$charlist`. Il est également possible d'utiliser des intervalles écrits comme par exemple `0-9`. ```php use Nette\Utils\Random; diff --git a/utils/fr/reflection.texy b/utils/fr/reflection.texy index c71350ed7b..a40d3e605a 100644 --- a/utils/fr/reflection.texy +++ b/utils/fr/reflection.texy @@ -1,8 +1,8 @@ -PHP Reflection -************** +Réflexion PHP +************* .[perex] -[api:Nette\Utils\Reflection] est une classe statique contenant des fonctions utiles pour la réflexion PHP. Son but est de corriger les défauts des classes natives et d'unifier le comportement entre les différentes versions de PHP. +[api:Nette\Utils\Reflection] est une classe statique avec des fonctions utiles pour la réflexion PHP. Sa tâche est de corriger les lacunes des classes natives et d'unifier le comportement à travers différentes versions de PHP. Installation : @@ -11,7 +11,7 @@ Installation : composer require nette/utils ``` -Tous les exemples supposent que l'alias de classe suivant est défini : +Tous les exemples supposent qu'un alias a été créé : ```php use Nette\Utils\Reflection; @@ -21,13 +21,13 @@ use Nette\Utils\Reflection; areCommentsAvailable(): bool .[method] -------------------------------------- -Détermine si la réflexion a accès aux commentaires de PHPdoc. Les commentaires peuvent ne pas être disponibles à cause du cache opcode, voir par exemple la directive [opcache.save-comments |https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments]. +Détermine si la réflexion a accès aux commentaires PHPdoc. Les commentaires peuvent être indisponibles à cause du cache d'opcode, voir par exemple la directive [opcache.save-comments |https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments]. expandClassName(string $name, ReflectionClass $context): string .[method] ------------------------------------------------------------------------- -Étend le `$name` de la classe au nom complet dans le contexte du `$context`, c'est-à-dire dans le contexte de son espace de noms et des alias définis. Ainsi, il renvoie la manière dont l'analyseur PHP comprendrait `$name` s'il était écrit dans le corps de `$context`. +Développe le nom de classe `$name` en son nom complet dans le contexte de la classe `$context`, c'est-à-dire dans le contexte de son espace de noms et des alias définis. Donc, en fait, il indique comment l'analyseur PHP comprendrait `$name` s'il était écrit dans le corps de la classe `$context`. ```php namespace Foo; @@ -47,9 +47,9 @@ Reflection::expandClassName('Baz', $context); // 'Foo\Baz' getMethodDeclaringMethod(ReflectionMethod $method): ReflectionMethod .[method] ------------------------------------------------------------------------------ -Renvoie une réflexion d'une méthode qui contient une déclaration de `$method`. Habituellement, chaque méthode a sa propre déclaration, mais le corps de la méthode peut aussi se trouver dans le trait et sous un nom différent. +Retourne la réflexion de la méthode qui contient la déclaration de la méthode `$method`. Habituellement, chaque méthode est sa propre déclaration, mais le corps de la méthode peut se trouver aussi dans un trait et sous un autre nom. -Comme PHP ne fournit pas assez d'informations pour déterminer la déclaration réelle, Nette utilise sa propre heuristique, qui **devrait** être fiable. +Parce que PHP ne fournit pas d'informations suffisantes avec lesquelles on peut déterminer la déclaration réelle, Nette utilise sa propre heuristique qui **devrait être** fiable. ```php trait DemoTrait @@ -76,9 +76,9 @@ Reflection::getMethodDeclaringMethod($method); // ReflectionMethod('DemoTrait::f getPropertyDeclaringClass(ReflectionProperty $prop): ReflectionClass .[method] ------------------------------------------------------------------------------ -Renvoie une réflexion d'une classe ou d'un trait qui contient une déclaration de la propriété `$prop`. La propriété peut également être déclarée dans le trait. +Retourne la réflexion de la classe ou du trait qui contient la déclaration de la propriété `$prop`. La propriété peut en effet être déclarée aussi dans un trait. -Comme PHP ne fournit pas assez d'informations pour déterminer la déclaration réelle, Nette utilise sa propre heuristique, qui **n'est pas** fiable. +Parce que PHP ne fournit pas d'informations suffisantes avec lesquelles on peut déterminer la déclaration réelle, Nette utilise sa propre heuristique qui **n'est pas** fiable. ```php trait DemoTrait @@ -100,7 +100,7 @@ Reflection::getPropertyDeclaringClass($prop); // ReflectionClass('DemoTrait') isBuiltinType(string $type): bool .[method deprecated] ------------------------------------------------------ -Détermine si `$type` est un type intégré à PHP. Sinon, c'est le nom de la classe. +Détermine si `$type` est un type intégré de PHP. Sinon, c'est un nom de classe. ```php Reflection::isBuiltinType('string'); // true @@ -114,7 +114,7 @@ Utilisez [Nette\Utils\Validator::isBuiltinType() |validators#isBuiltinType]. toString($reflection): string .[method] --------------------------------------- -Convertit une réflexion en une chaîne de caractères lisible par l'homme. +Convertit la réflexion en une chaîne compréhensible par l'homme. ```php $func = new ReflectionFunction('func'); diff --git a/utils/fr/smartobject.texy b/utils/fr/smartobject.texy index 70d1024e3c..53e189475b 100644 --- a/utils/fr/smartobject.texy +++ b/utils/fr/smartobject.texy @@ -1,8 +1,8 @@ -SmartObject et StaticClass -************************** +SmartObject +*********** .[perex] -SmartObject ajoute le support des *propriétés* aux classes PHP. StaticClass est utilisé pour désigner les classes statiques. +SmartObject a amélioré pendant des années le comportement des objets en PHP. Depuis la version PHP 8.4, toutes ses fonctions font déjà partie de PHP lui-même, achevant ainsi sa mission historique d'être un pionnier de l'approche objet moderne en PHP. Installation : @@ -11,19 +11,64 @@ Installation : composer require nette/utils ``` +SmartObject a été créé en 2007 comme une solution révolutionnaire aux lacunes du modèle objet PHP de l'époque. À une époque où PHP souffrait de nombreux problèmes de conception objet, il a apporté une amélioration significative et une simplification du travail pour les développeurs. Il est devenu une partie légendaire du framework Nette. Il offrait des fonctionnalités que PHP n'a acquises que de nombreuses années plus tard - du contrôle d'accès aux propriétés des objets jusqu'aux sucres syntaxiques sophistiqués. Avec l'arrivée de PHP 8.4, il a achevé sa mission historique, car toutes ses fonctions sont devenues une partie native du langage. Il a devancé le développement de PHP de 17 années remarquables. -Propriétés, getters et setters .[#toc-properties-getters-and-setters] -===================================================================== +Techniquement, SmartObject a subi une évolution intéressante. Initialement implémenté comme une classe `Nette\Object`, dont les autres classes héritaient les fonctionnalités nécessaires. Un changement fondamental est survenu avec PHP 5.4, qui a apporté le support des traits. Cela a permis la transformation sous forme de trait `Nette\SmartObject`, ce qui a apporté une plus grande flexibilité - les développeurs pouvaient utiliser la fonctionnalité même dans les classes qui héritaient déjà d'une autre classe. Alors que la classe originale `Nette\Object` a disparu avec l'arrivée de PHP 7.2 (qui a interdit la dénomination des classes avec le mot `Object`), le trait `Nette\SmartObject` continue de vivre. -Dans les langages modernes orientés objet (par exemple C#, Python, Ruby, JavaScript), le terme *propriété* fait référence aux [membres spéciaux des classes |https://en.wikipedia.org/wiki/Property_(programming)] qui ressemblent à des variables mais sont en fait représentés par des méthodes. Lorsque la valeur de cette "variable" est assignée ou lue, la méthode correspondante (appelée getter ou setter) est appelée. C'est très pratique, cela nous donne un contrôle total sur l'accès aux variables. Nous pouvons valider l'entrée ou générer des résultats uniquement lorsque la propriété est lue. +Passons en revue les caractéristiques qu'offraient autrefois `Nette\Object` et plus tard `Nette\SmartObject`. Chacune de ces fonctions, à son époque, représentait une avancée significative dans le domaine de la programmation orientée objet en PHP. -Les propriétés PHP ne sont pas prises en charge, mais le trait `Nette\SmartObject` peut les imiter. Comment l'utiliser ? -- Ajoutez une annotation à la classe sous la forme `@property $xyz` -- Créez un getter nommé `getXyz()` ou `isXyz()`, un setter nommé `setXyz()` -- Le getter et le setter doivent être *public* ou *protégé* et sont optionnels, donc il peut y avoir une propriété *lire seulement* ou *écrire seulement*. +États d'erreur cohérents +------------------------ +L'un des problèmes les plus brûlants du PHP précoce était le comportement incohérent lors du travail avec les objets. `Nette\Object` a apporté ordre et prévisibilité à ce chaos. Regardons à quoi ressemblait le comportement original de PHP : -Nous utiliserons la propriété de la classe Circle pour nous assurer que seuls des nombres non négatifs sont placés dans la variable `$radius`. Remplacez `public $radius` par la propriété : +```php +echo $obj->undeclared; // E_NOTICE, plus tard E_WARNING +$obj->undeclared = 1; // passe silencieusement sans rapport +$obj->unknownMethod(); // Erreur fatale (non interceptable avec try/catch) +``` + +L'erreur fatale terminait l'application sans possibilité de réagir de quelque manière que ce soit. L'écriture silencieuse dans des membres inexistants sans avertissement pouvait conduire à des erreurs graves difficiles à détecter. `Nette\Object` interceptait tous ces cas et lançait une exception `MemberAccessException`, ce qui permettait aux programmeurs de réagir aux erreurs et de les résoudre. + +```php +echo $obj->undeclared; // lance Nette\MemberAccessException +$obj->undeclared = 1; // lance Nette\MemberAccessException +$obj->unknownMethod(); // lance Nette\MemberAccessException +``` + +Depuis PHP 7.0, le langage ne cause plus d'erreurs fatales non interceptables et depuis PHP 8.2, l'accès aux membres non déclarés est considéré comme une erreur. + + +Aide "Did you mean?" +-------------------- +`Nette\Object` est venu avec une fonctionnalité très agréable : une aide intelligente en cas de fautes de frappe. Quand un développeur faisait une erreur dans le nom d'une méthode ou d'une variable, non seulement il signalait l'erreur, mais il offrait aussi un coup de main sous forme de suggestion du nom correct. Ce message emblématique, connu sous le nom de "did you mean?", a épargné aux programmeurs des heures de recherche de fautes de frappe : + +```php +class Foo extends Nette\Object +{ + public static function from($var) + { + } +} + +$foo = Foo::form($var); +// lance Nette\MemberAccessException +// "Appel à la méthode statique non définie Foo::form(), vouliez-vous dire from() ?" +``` + +Le PHP d'aujourd'hui, bien qu'il n'ait pas de forme de « did you mean? », mais [Tracy |tracy:] sait compléter cet ajout aux erreurs. Et même [les corriger automatiquement |tracy:open-files-in-ide#Démonstrations]. + + +Propriétés avec accès contrôlé +------------------------------ +Une innovation significative que SmartObject a apportée à PHP étaient les propriétés avec accès contrôlé. Ce concept, courant dans des langages comme C# ou Python, a permis aux développeurs de contrôler élégamment l'accès aux données de l'objet et d'assurer leur cohérence. Les propriétés sont un outil puissant de la programmation orientée objet. Elles fonctionnent comme des variables, mais en réalité sont représentées par des méthodes (getters et setters). Cela permet de valider les entrées ou de générer des valeurs seulement au moment de la lecture. + +Pour utiliser les propriétés, vous devez : +- Ajouter une annotation à la classe sous la forme `@property $xyz` +- Créer un getter avec le nom `getXyz()` ou `isXyz()`, un setter avec le nom `setXyz()` +- Assurer que le getter et le setter soient *public* ou *protected*. Ils sont optionnels - peuvent donc exister comme propriété en *lecture seule* ou en *écriture seule* + +Montrons un exemple pratique sur la classe Circle, où nous utiliserons les propriétés pour assurer que le rayon sera toujours un nombre non négatif. Remplaçons l'original `public $radius` par une propriété : ```php /** @@ -34,22 +79,22 @@ class Circle { use Nette\SmartObject; - private float $radius = 0.0; // not public + private float $radius = 0.0; // n'est pas public ! - // getter for property $radius + // getter pour la propriété $radius protected function getRadius(): float { return $this->radius; } - // setter for property $radius + // setter pour la propriété $radius protected function setRadius(float $radius): void { - // sanitizing value before saving it + // nous nettoyons la valeur avant de l'enregistrer $this->radius = max(0.0, $radius); } - // getter for property $visible + // getter pour la propriété $visible protected function isVisible(): bool { return $this->radius > 0; @@ -57,84 +102,30 @@ class Circle } $circle = new Circle; -$circle->radius = 10; // actually calls setRadius(10) -echo $circle->radius; // calls getRadius() -echo $circle->visible; // calls isVisible() +$circle->radius = 10; // appelle en réalité setRadius(10) +echo $circle->radius; // appelle getRadius() +echo $circle->visible; // appelle isVisible() ``` -Les propriétés sont principalement du "sucre syntaxique" ((sucre syntaxique)), destiné à rendre la vie du programmeur plus douce en simplifiant le code. Si vous n'en voulez pas, vous n'êtes pas obligé de les utiliser. - - -Classes statiques .[#toc-static-classes] -======================================== - -Les classes statiques, c'est-à-dire les classes qui ne sont pas destinées à être instanciées, peuvent être marquées par le trait `Nette\StaticClass`: +Depuis PHP 8.4, on peut atteindre la même fonctionnalité en utilisant les property hooks, qui offrent une syntaxe beaucoup plus élégante et concise : ```php -class Strings +class Circle { - use Nette\StaticClass; -} -``` - -Lorsque vous essayez de créer une instance, l'exception `Error` est levée, indiquant que la classe est statique. - - -Un regard sur l'histoire .[#toc-a-look-into-the-history] -======================================================== - -SmartObject améliorait et corrigeait le comportement des classes de plusieurs façons, mais l'évolution de PHP a rendu la plupart des fonctionnalités originales superflues. Ce qui suit est donc un regard sur l'histoire de l'évolution des choses. - -Dès le début, le modèle objet de PHP a souffert d'un certain nombre de défauts sérieux et d'inefficacités. C'est la raison de la création de la classe `Nette\Object` (en 2007), qui a tenté d'y remédier et d'améliorer l'expérience d'utilisation de PHP. Il a suffi que d'autres classes héritent d'elle pour bénéficier de ses avantages. Lorsque PHP 5.4 a intégré le support des traits, la classe `Nette\Object` a été remplacée par `Nette\SmartObject`. Ainsi, il n'était plus nécessaire d'hériter d'un ancêtre commun. De plus, les traits pouvaient être utilisés dans des classes qui héritaient déjà d'une autre classe. La fin définitive de `Nette\Object` est intervenue avec la sortie de PHP 7.2, qui interdisait aux classes d'être nommées `Object`. - -Au fur et à mesure du développement de PHP, le modèle objet et les capacités du langage ont été améliorés. Les fonctions individuelles de la classe `SmartObject` sont devenues redondantes. Depuis la version 8.2 de PHP, la seule fonctionnalité qui n'est pas encore directement supportée par PHP est la possibilité d'utiliser des [propriétés |#Properties, getters and setters]. - -Quelles fonctionnalités offraient autrefois `Nette\Object` et `Nette\Object`? En voici un aperçu. (Les exemples utilisent la classe `Nette\Object`, mais la plupart des propriétés s'appliquent également au trait `Nette\SmartObject` ). - - -Erreurs de cohérence .[#toc-inconsistent-errors] ------------------------------------------------- -PHP avait un comportement incohérent lors de l'accès aux membres non déclarés. L'état au moment de `Nette\Object` était le suivant : - -```php -echo $obj->undeclared; // E_NOTICE, later E_WARNING -$obj->undeclared = 1; // passes silently without reporting -$obj->unknownMethod(); // Fatal error (not catchable by try/catch) -``` - -Une erreur fatale terminait l'application sans possibilité de réagir. L'écriture silencieuse dans des membres inexistants sans avertissement pouvait entraîner des erreurs graves difficiles à détecter. `Nette\Object` Tous ces cas ont été détectés et une exception `MemberAccessException` a été levée. - -```php -echo $obj->undeclared; // throw Nette\MemberAccessException -$obj->undeclared = 1; // throw Nette\MemberAccessException -$obj->unknownMethod(); // throw Nette\MemberAccessException -``` -Depuis PHP 7.0, PHP ne provoque plus d'erreurs fatales non capturables, et l'accès à des membres non déclarés est un bug depuis PHP 8.2. - - -Voulez-vous dire ? .[#toc-did-you-mean] ---------------------------------------- -Si une erreur `Nette\MemberAccessException` était déclenchée, peut-être à cause d'une faute de frappe lors de l'accès à une variable d'objet ou de l'appel d'une méthode, `Nette\Object` tentait de donner un indice dans le message d'erreur sur la façon de corriger l'erreur, sous la forme de l'addendum emblématique "Did you mean ?". + public float $radius = 0.0 { + set => max(0.0, $value); + } -```php -class Foo extends Nette\Object -{ - public static function from($var) - { + public bool $visible { + get => $this->radius > 0; } } - -$foo = Foo::form($var); -// throw Nette\MemberAccessException -// "Call to undefined static method Foo::form(), did you mean from()?" ``` -Le PHP d'aujourd'hui n'a peut-être pas de forme de "did you mean ?", mais [Tracy |tracy:] ajoute cet addendum aux erreurs. Et il peut même [corriger |tracy:open-files-in-ide#toc-demos] ces erreurs lui-même. - -Méthodes d'extension .[#toc-extension-methods] ----------------------------------------------- -Inspiré par les méthodes d'extension de C#. Elles donnaient la possibilité d'ajouter de nouvelles méthodes à des classes existantes. Par exemple, vous pouvez ajouter la méthode `addDateTime()` à un formulaire pour ajouter votre propre DateTimePicker. +Méthodes d'extension +-------------------- +`Nette\Object` a apporté à PHP un autre concept intéressant inspiré des langages de programmation modernes - les méthodes d'extension. Cette fonction, reprise de C#, a permis aux développeurs d'étendre élégamment les classes existantes avec de nouvelles méthodes sans nécessité de les modifier ou d'en hériter. Par exemple, vous pouviez ajouter au formulaire une méthode `addDateTime()` qui ajoute un DateTimePicker personnalisé : ```php Form::extensionMethod( @@ -146,22 +137,22 @@ $form = new Form; $form->addDateTime('date'); ``` -Les méthodes d'extension se sont avérées peu pratiques car leurs noms n'étaient pas autocomplétés par les éditeurs, qui signalaient au contraire que la méthode n'existait pas. Par conséquent, leur prise en charge a été abandonnée. +Les méthodes d'extension se sont avérées peu pratiques, car leurs noms n'étaient pas suggérés par les éditeurs, au contraire, ils signalaient que la méthode n'existait pas. C'est pourquoi leur support a été arrêté. Aujourd'hui, il est plus courant d'utiliser la composition ou l'héritage pour étendre la fonctionnalité des classes. -Obtention du nom de la classe .[#toc-getting-the-class-name] ------------------------------------------------------------- +Obtention du nom de la classe +----------------------------- +Pour obtenir le nom de la classe, SmartObject offrait une méthode simple : ```php -$class = $obj->getClass(); // using Nette\Object -$class = $obj::class; // since PHP 8.0 +$class = $obj->getClass(); // en utilisant Nette\Object +$class = $obj::class; // depuis PHP 8.0 ``` -Accès à la réflexion et aux annotations .[#toc-access-to-reflection-and-annotations] ------------------------------------------------------------------------------------- - -`Nette\Object` a proposé un accès à la réflexion et aux annotations en utilisant les méthodes `getReflection()` et `getAnnotation()`: +Accès à la réflexion et aux annotations +--------------------------------------- +`Nette\Object` offrait l'accès à la réflexion et aux annotations en utilisant les méthodes `getReflection()` et `getAnnotation()`. Cette approche a simplifié significativement le travail avec les méta-informations des classes : ```php /** @@ -173,10 +164,10 @@ class Foo extends Nette\Object $obj = new Foo; $reflection = $obj->getReflection(); -$reflection->getAnnotation('author'); // returns 'John Doe +$reflection->getAnnotation('author'); // retourne 'John Doe' ``` -Depuis PHP 8.0, il est possible d'accéder aux méta-informations sous forme d'attributs : +Depuis PHP 8.0, il est possible d'accéder aux méta-informations sous forme d'attributs, qui offrent encore plus de possibilités et un meilleur contrôle de type : ```php #[Author('John Doe')] @@ -190,10 +181,9 @@ $reflection->getAttributes(Author::class)[0]; ``` -Les récupérateurs de méthodes .[#toc-method-getters] ----------------------------------------------------- - -`Nette\Object` offraient un moyen élégant de traiter les méthodes comme s'il s'agissait de variables : +Getters de méthode +------------------ +`Nette\Object` offrait une manière élégante de passer des méthodes comme s'il s'agissait de variables : ```php class Foo extends Nette\Object @@ -209,7 +199,7 @@ $method = $obj->adder; echo $method(2, 3); // 5 ``` -Depuis PHP 8.1, vous pouvez utiliser la syntaxe:https://www.php.net/manual/en/functions.first_class_callable_syntax dite "first-class callable":https://www.php.net/manual/en/functions.first_class_callable_syntax: +Depuis PHP 8.1, il est possible d'utiliser la dite "syntaxe callable de première classe":https://www.php.net/manual/en/functions.first_class_callable_syntax, qui pousse ce concept encore plus loin : ```php $obj = new Foo; @@ -218,10 +208,9 @@ echo $method(2, 3); // 5 ``` -Événements .[#toc-events] -------------------------- - -`Nette\Object` a offert un sucre syntaxique pour déclencher l'[événement |nette:glossary#events]: +Événements +---------- +SmartObject offre une syntaxe simplifiée pour travailler avec les [événements |nette:glossary#Événements events]. Les événements permettent aux objets d'informer les autres parties de l'application des changements de leur état : ```php class Circle extends Nette\Object @@ -231,12 +220,12 @@ class Circle extends Nette\Object public function setRadius(float $radius): void { $this->onChange($this, $radius); - $this->radius = $radius + $this->radius = $radius; } } ``` -Le code `$this->onChange($this, $radius)` est équivalent à ce qui suit : +Le code `$this->onChange($this, $radius)` est équivalent à la boucle suivante : ```php foreach ($this->onChange as $callback) { @@ -244,7 +233,7 @@ foreach ($this->onChange as $callback) { } ``` -Pour des raisons de clarté, nous vous recommandons d'éviter la méthode magique `$this->onChange()`. Un bon substitut est [Nette\Utils\Arrays::invoke |arrays#invoke]: +Pour des raisons de clarté, nous recommandons d'éviter la méthode magique `$this->onChange()`. Un remplacement pratique est par exemple la fonction [Nette\Utils\Arrays::invoke |arrays#invoke] : ```php Nette\Utils\Arrays::invoke($this->onChange, $this, $radius); diff --git a/utils/fr/staticclass.texy b/utils/fr/staticclass.texy new file mode 100644 index 0000000000..481a8aea97 --- /dev/null +++ b/utils/fr/staticclass.texy @@ -0,0 +1,21 @@ +Classes statiques +***************** + +.[perex] +StaticClass sert à marquer les classes statiques. + + +Installation : + +```shell +composer require nette/utils +``` + +Les classes statiques, c'est-à-dire les classes qui ne sont pas destinées à la création d'instances, peuvent être marquées avec le trait [api:Nette\StaticClass] : + +```php +class Strings +{ + use Nette\StaticClass; +} +``` diff --git a/utils/fr/strings.texy b/utils/fr/strings.texy index 57fb7eaf41..e08c299a7a 100644 --- a/utils/fr/strings.texy +++ b/utils/fr/strings.texy @@ -1,8 +1,8 @@ -Fonctions des chaînes de caractères -*********************************** +Travailler avec les chaînes de caractères +***************************************** .[perex] -[api:Nette\Utils\Strings] est une classe statique qui contient de nombreuses fonctions utiles pour travailler avec des chaînes de caractères codées UTF-8. +[api:Nette\Utils\Strings] est une classe statique avec des fonctions utiles pour travailler avec les chaînes de caractères principalement en encodage UTF-8. Installation : @@ -11,15 +11,15 @@ Installation : composer require nette/utils ``` -Tous les exemples supposent que l'alias de classe suivant est défini : +Tous les exemples supposent qu'un alias a été créé : ```php use Nette\Utils\Strings; ``` -Letter Case .[#toc-letter-case] -=============================== +Changement de casse +=================== Ces fonctions nécessitent l'extension PHP `mbstring`. @@ -27,67 +27,67 @@ Ces fonctions nécessitent l'extension PHP `mbstring`. lower(string $s): string .[method] ---------------------------------- -Convertit tous les caractères de la chaîne UTF-8 en minuscules. +Convertit une chaîne UTF-8 en minuscules. ```php -Strings::lower('Hello world'); // 'hello world' +Strings::lower('Dobrý den'); // 'dobrý den' ``` upper(string $s): string .[method] ---------------------------------- -Convertit tous les caractères d'une chaîne UTF-8 en majuscules. +Convertit une chaîne UTF-8 en majuscules. ```php -Strings::upper('Hello world'); // 'HELLO WORLD' +Strings::upper('Dobrý den'); // 'DOBRÝ DEN' ``` firstUpper(string $s): string .[method] --------------------------------------- -Convertit le premier caractère d'une chaîne UTF-8 en majuscule et laisse les autres caractères inchangés. +Convertit la première lettre d'une chaîne UTF-8 en majuscule, ne change pas les autres. ```php -Strings::firstUpper('hello world'); // 'Hello world' +Strings::firstUpper('dobrý den'); // 'Dobrý den' ``` firstLower(string $s): string .[method] --------------------------------------- -Convertit le premier caractère d'une chaîne UTF-8 en minuscule et laisse les autres caractères inchangés. +Convertit la première lettre d'une chaîne UTF-8 en minuscule, ne change pas les autres. ```php -Strings::firstLower('Hello world'); // 'hello world' +Strings::firstLower('Dobrý den'); // 'dobrý den' ``` capitalize(string $s): string .[method] --------------------------------------- -Convertit le premier caractère de chaque mot d'une chaîne UTF-8 en majuscule et les autres en minuscule. +Convertit la première lettre de chaque mot dans une chaîne UTF-8 en majuscule, les autres en minuscules. ```php -Strings::capitalize('Hello world'); // 'Hello World' +Strings::capitalize('Dobrý den'); // 'Dobrý Den' ``` -Modification d'une chaîne de caractères .[#toc-editing-a-string] -================================================================ +Modification de chaîne +====================== normalize(string $s): string .[method] -------------------------------------- -Supprime les caractères de contrôle, normalise les sauts de ligne à `\n`, supprime les lignes vides de début et de fin, coupe les espaces de fin de ligne, normalise UTF-8 à la forme normale de NFC. +Supprime les caractères de contrôle, normalise les fins de ligne en `\n`, supprime les lignes vides de début et de fin, supprime les espaces de fin sur les lignes, normalise l'UTF-8 en forme normale NFC. unixNewLines(string $s): string .[method] ----------------------------------------- -Convertit les sauts de ligne en `\n` utilisés sur les systèmes Unix. Les sauts de ligne sont : `\n`, `\r`, `\r\n`, U+2028 séparateur de ligne, U+2029 séparateur de paragraphe. +Convertit les fins de ligne en `\n` utilisé dans les systèmes Unix. Les fins de ligne sont : `\n`, `\r`, `\r\n`, U+2028 séparateur de ligne, U+2029 séparateur de paragraphe. ```php $unixLikeLines = Strings::unixNewLines($string); @@ -97,42 +97,42 @@ $unixLikeLines = Strings::unixNewLines($string); platformNewLines(string $s): string .[method] --------------------------------------------- -Convertit les sauts de ligne en caractères spécifiques à la plate-forme actuelle, c'est-à-dire `\r\n` sous Windows et `\n` ailleurs. Les sauts de ligne sont `\n`, `\r`, `\r\n`, U+2028 séparateur de ligne, U+2029 séparateur de paragraphe. +Convertit les fins de ligne en caractères spécifiques à la plateforme actuelle, c'est-à-dire `\r\n` sous Windows et `\n` ailleurs. Les fins de ligne sont : `\n`, `\r`, `\r\n`, U+2028 séparateur de ligne, U+2029 séparateur de paragraphe. ```php $platformLines = Strings::platformNewLines($string); ``` -webalize(string $s, string $charlist=null, bool $lower=true): string .[method] ------------------------------------------------------------------------------- +webalize(string $s, ?string $charlist=null, bool $lower=true): string .[method] +------------------------------------------------------------------------------- -Modifie la chaîne UTF-8 à la forme utilisée dans l'URL, c'est-à-dire supprime les diacritiques et remplace tous les caractères sauf les lettres de l'alphabet anglais et les chiffres par un trait d'union. +Modifie une chaîne UTF-8 à la forme utilisée dans les URL, c'est-à-dire supprime les diacritiques et remplace tous les caractères, sauf les lettres de l'alphabet anglais et les chiffres, par un trait d'union. ```php -Strings::webalize('žluťoučký kůň'); // 'zlutoucky-kun' +Strings::webalize('náš produkt'); // 'nas-produkt' ``` -D'autres caractères peuvent également être préservés, mais ils doivent être passés en second argument. +Si d'autres caractères doivent être conservés, ils peuvent être indiqués dans le deuxième paramètre de la fonction. ```php -Strings::webalize('10. image_id', '._'); // '10.-image_id' +Strings::webalize('10. obrázek_id', '._'); // '10.-obrazek_id' ``` -Le troisième argument peut supprimer la conversion de la chaîne en minuscules. +Avec le troisième paramètre, on peut supprimer la conversion en minuscules. ```php -Strings::webalize('Hello world', null, false); // 'Hello-world' +Strings::webalize('Dobrý den', null, false); // 'Dobry-den' ``` .[caution] Nécessite l'extension PHP `intl`. -trim(string $s, string $charlist=null): string .[method] --------------------------------------------------------- +trim(string $s, ?string $charlist=null): string .[method] +--------------------------------------------------------- -Supprime tous les espaces à gauche et à droite (ou les caractères passés en second argument) d'une chaîne de caractères encodée en UTF-8. +Supprime les espaces (ou autres caractères spécifiés par le deuxième paramètre) du début et de la fin d'une chaîne UTF-8. ```php Strings::trim(' Hello '); // 'Hello' @@ -142,21 +142,21 @@ Strings::trim(' Hello '); // 'Hello' truncate(string $s, int $maxLen, string $append=`'…'`): string .[method] ------------------------------------------------------------------------ -Tronque une chaîne UTF-8 à une longueur maximale donnée, tout en essayant de ne pas couper des mots entiers. Seulement si la chaîne est tronquée, une ellipse (ou autre chose définie avec le troisième argument) est ajoutée à la chaîne. +Tronque une chaîne UTF-8 à la longueur maximale spécifiée, en essayant de préserver les mots entiers. Si la chaîne est raccourcie, ajoute des points de suspension à la fin (peut être changé avec le troisième paramètre). ```php -$text = 'Hello, how are you today?'; -Strings::truncate($text, 5); // 'Hell…' -Strings::truncate($text, 20); // 'Hello, how are you…' -Strings::truncate($text, 30); // 'Hello, how are you today?' -Strings::truncate($text, 20, '~'); // 'Hello, how are you~' +$text = 'Řekněte, jak se máte?'; +Strings::truncate($text, 5); // 'Řekn…' +Strings::truncate($text, 20); // 'Řekněte, jak se…' +Strings::truncate($text, 30); // 'Řekněte, jak se máte?' +Strings::truncate($text, 20, '~'); // 'Řekněte, jak se~' ``` indent(string $s, int $level=1, string $indentationChar=`"\t"`): string .[method] --------------------------------------------------------------------------------- -Indente un texte multiligne à partir de la gauche. Le deuxième argument définit le nombre de caractères d'indentation à utiliser, tandis que l'indentation elle-même est le troisième argument (*tab* par défaut). +Indente un texte multiligne depuis la gauche. Le nombre d'indentations est déterminé par le deuxième paramètre, avec quoi indenter par le troisième paramètre (la valeur par défaut est une tabulation). ```php Strings::indent('Nette'); // "\tNette" @@ -167,7 +167,7 @@ Strings::indent('Nette', 2, '+'); // '++Nette' padLeft(string $s, int $length, string $pad=`' '`): string .[method] -------------------------------------------------------------------- -Remplit une chaîne UTF-8 à une longueur donnée en ajoutant la chaîne `$pad` au début. +Remplit une chaîne UTF-8 à la longueur spécifiée en répétant la chaîne `$pad` depuis la gauche. ```php Strings::padLeft('Nette', 6); // ' Nette' @@ -178,7 +178,7 @@ Strings::padLeft('Nette', 8, '+*'); // '+*+Nette' padRight(string $s, int $length, string $pad=`' '`): string .[method] --------------------------------------------------------------------- -Ajoute une chaîne UTF-8 à une longueur donnée en ajoutant la chaîne `$pad` à la fin. +Remplit une chaîne UTF-8 à la longueur spécifiée en répétant la chaîne `$pad` depuis la droite. ```php Strings::padRight('Nette', 6); // 'Nette ' @@ -186,10 +186,10 @@ Strings::padRight('Nette', 8, '+*'); // 'Nette+*+' ``` -substring(string $s, int $start, int $length=null): string .[method] --------------------------------------------------------------------- +substring(string $s, int $start, ?int $length=null): string .[method] +--------------------------------------------------------------------- -Renvoie une partie de la chaîne UTF-8 spécifiée par la position de départ `$start` et la longueur `$length`. Si `$start` est négatif, la chaîne retournée commencera au `$start`'ème caractère à partir de la fin de la chaîne. +Retourne une partie de la chaîne UTF-8 `$s` spécifiée par la position de départ `$start` et la longueur `$length`. Si `$start` est négatif, la chaîne retournée commencera par le caractère -`$start` depuis la fin. ```php Strings::substring('Nette Framework', 0, 5); // 'Nette' @@ -201,7 +201,7 @@ Strings::substring('Nette Framework', -4); // 'work' reverse(string $s): string .[method] ------------------------------------ -Inverse la chaîne UTF-8. +Inverse une chaîne UTF-8. ```php Strings::reverse('Nette'); // 'etteN' @@ -211,77 +211,77 @@ Strings::reverse('Nette'); // 'etteN' length(string $s): int .[method] -------------------------------- -Renvoie le nombre de caractères (et non d'octets) dans une chaîne UTF-8. +Retourne le nombre de caractères (pas d'octets) dans la chaîne UTF-8. -Il s'agit du nombre de points de code Unicode, qui peut différer du nombre de graphèmes. +C'est le nombre de points de code Unicode, qui peuvent différer du nombre de graphèmes. ```php -Strings::length('Nette'); // 5 -Strings::length('red'); // 3 +Strings::length('Nette'); // 5 +Strings::length('červená'); // 7 ``` startsWith(string $haystack, string $needle): bool .[method deprecated] ----------------------------------------------------------------------- -Vérifie si la chaîne `$haystack` commence par `$needle`. +Détermine si la chaîne `$haystack` commence par la chaîne `$needle`. ```php -$haystack = 'Begins'; -$needle = 'Be'; +$haystack = 'Commence'; +$needle = 'Co'; Strings::startsWith($haystack, $needle); // true ``` .[note] -Utilise les natifs `str_starts_with()`:https://www.php.net/manual/en/function.str-starts-with.php. +Utilisez la fonction native `str_starts_with()`:https://www.php.net/manual/en/function.str-starts-with.php. endsWith(string $haystack, string $needle): bool .[method deprecated] --------------------------------------------------------------------- -Vérifie si la chaîne `$haystack` se termine par `$needle`. +Détermine si la chaîne `$haystack` se termine par la chaîne `$needle`. ```php -$haystack = 'Ends'; -$needle = 'ds'; +$haystack = 'Finit'; +$needle = 'it'; Strings::endsWith($haystack, $needle); // true ``` .[note] -Utilisation de `str_ends_with()`:https://www.php.net/manual/en/function.str-ends-with.php. +Utilisez la fonction native `str_ends_with()`:https://www.php.net/manual/en/function.str-ends-with.php. contains(string $haystack, string $needle): bool .[method deprecated] --------------------------------------------------------------------- -Vérifie si la chaîne `$haystack` contient `$needle`. +Détermine si la chaîne `$haystack` contient `$needle`. ```php -$haystack = 'Contains'; -$needle = 'tai'; +$haystack = 'Auditorium'; +$needle = 'dito'; Strings::contains($haystack, $needle); // true ``` .[note] -Utilise le natif `str_contains()`:https://www.php.net/manual/en/function.str-contains.php. +Utilisez la fonction native `str_contains()`:https://www.php.net/manual/en/function.str-contains.php. -compare(string $left, string $right, int $length=null): bool .[method] ----------------------------------------------------------------------- +compare(string $left, string $right, ?int $length=null): bool .[method] +----------------------------------------------------------------------- -Compare deux chaînes de caractères UTF-8 ou leurs parties, sans tenir compte de la casse des caractères. Si `$length` est nul, les chaînes entières sont comparées, s'il est négatif, le nombre correspondant de caractères depuis la fin des chaînes est comparé, sinon le nombre approprié de caractères depuis le début est comparé. +Comparaison de deux chaînes UTF-8 ou de leurs parties sans tenir compte de la casse. Si `$length` contient null, les chaînes entières sont comparées, s'il est négatif, le nombre correspondant de caractères est comparé depuis la fin des chaînes, sinon le nombre correspondant de caractères est comparé depuis le début. ```php Strings::compare('Nette', 'nette'); // true -Strings::compare('Nette', 'next', 2); // true - two first characters match -Strings::compare('Nette', 'Latte', -2); // true - two last characters match +Strings::compare('Nette', 'next', 2); // true - correspondance des 2 premiers caractères +Strings::compare('Nette', 'Latte', -2); // true - correspondance des 2 derniers caractères ``` findPrefix(...$strings): string .[method] ----------------------------------------- -Trouve le préfixe commun des chaînes de caractères ou renvoie une chaîne vide si le préfixe n'a pas été trouvé. +Trouve le début commun des chaînes. Ou retourne une chaîne vide si le préfixe commun n'a pas été trouvé. ```php Strings::findPrefix('prefix-a', 'prefix-bb', 'prefix-c'); // 'prefix-' @@ -293,7 +293,7 @@ Strings::findPrefix('Nette', 'is', 'great'); // '' before(string $haystack, string $needle, int $nth=1): ?string .[method] ----------------------------------------------------------------------- -Renvoie la partie de `$haystack` avant l'occurrence de `$nth` de `$needle` ou renvoie `null` si l'aiguille n'a pas été trouvée. Une valeur négative signifie que la recherche se fait à partir de la fin. +Retourne la partie de la chaîne `$haystack` avant la n-ième occurrence `$nth` de la chaîne `$needle`. Ou `null` si `$needle` n'a pas été trouvé. Avec une valeur négative `$nth`, la recherche se fait depuis la fin de la chaîne. ```php Strings::before('Nette_is_great', '_', 1); // 'Nette' @@ -306,7 +306,7 @@ Strings::before('Nette_is_great', '_', 3); // null after(string $haystack, string $needle, int $nth=1): ?string .[method] ---------------------------------------------------------------------- -Renvoie une partie de `$haystack` après l'occurrence de `$nth` sur `$needle` ou renvoie `null` si `$needle` n'a pas été trouvé. Une valeur négative de `$nth` signifie que la recherche se fait à partir de la fin. +Retourne la partie de la chaîne `$haystack` après la n-ième occurrence `$nth` de la chaîne `$needle`. Ou `null` si `$needle` n'a pas été trouvé. Avec une valeur négative `$nth`, la recherche se fait depuis la fin de la chaîne. ```php Strings::after('Nette_is_great', '_', 2); // 'great' @@ -319,7 +319,7 @@ Strings::after('Nette_is_great', '_', 3); // null indexOf(string $haystack, string $needle, int $nth=1): ?int .[method] --------------------------------------------------------------------- -Renvoie la position en caractères de `$nth`, l'occurrence de `$needle` dans `$haystack` ou `null` si `$needle` n'a pas été trouvé. Une valeur négative de `$nth` signifie que la recherche se fait à partir de la fin. +Retourne la position en caractères de la n-ième occurrence `$nth` de la chaîne `$needle` dans la chaîne `$haystack`. Ou `null` s'il n'a pas été trouvé `$needle`. Avec une valeur négative `$nth`, la recherche se fait depuis la fin de la chaîne. ```php Strings::indexOf('abc abc abc', 'abc', 2); // 4 @@ -328,14 +328,14 @@ Strings::indexOf('abc abc abc', 'd'); // null ``` -Encodage .[#toc-encoding] -========================= +Encodage +======== fixEncoding(string $s): string .[method] ---------------------------------------- -Supprime tous les caractères UTF-8 invalides d'une chaîne de caractères. +Supprime de la chaîne les caractères UTF-8 invalides. ```php $correctStrings = Strings::fixEncoding($string); @@ -345,7 +345,7 @@ $correctStrings = Strings::fixEncoding($string); checkEncoding(string $s): bool .[method deprecated] --------------------------------------------------- -Vérifie si la chaîne est valide dans l'encodage UTF-8. +Détermine s'il s'agit d'une chaîne UTF-8 valide. ```php $isUtf8 = Strings::checkEncoding($string); @@ -358,7 +358,7 @@ Utilisez [Nette\Utils\Validator::isUnicode() |validators#isUnicode]. toAscii(string $s): string .[method] ------------------------------------ -Convertit une chaîne UTF-8 en ASCII, c'est-à-dire qu'elle supprime les diacritiques, etc. +Convertit une chaîne UTF-8 en ASCII, c'est-à-dire supprime les diacritiques, etc. ```php Strings::toAscii('žluťoučký kůň'); // 'zlutoucky kun' @@ -371,43 +371,43 @@ Nécessite l'extension PHP `intl`. chr(int $code): string .[method] -------------------------------- -Renvoie un caractère spécifique en UTF-8 à partir du point de code (numéro dans la plage 0x0000..D7FF ou 0xE000..10FFFF). +Retourne un caractère spécifique en UTF-8 à partir du point de code (nombre dans la plage 0x0000..D7FF et 0xE000..10FFFF). ```php -Strings::chr(0xA9); // '©' +Strings::chr(0xA9); // '©' en encodage UTF-8 ``` ord(string $char): int .[method] -------------------------------- -Renvoie le point de code d'un caractère spécifique en UTF-8 (nombre compris entre 0x0000..D7FF ou 0xE000..10FFFF). +Retourne le point de code d'un caractère spécifique en UTF-8 (nombre dans la plage 0x0000..D7FF ou 0xE000..10FFFF). ```php Strings::ord('©'); // 0xA9 ``` -Expressions régulières .[#toc-regular-expressions] -================================================== +Expressions régulières +====================== -La classe Strings fournit des fonctions pour travailler avec des expressions régulières. Contrairement aux fonctions natives de PHP, elles disposent d'une API plus compréhensible, d'un meilleur support de l'Unicode et, surtout, d'une détection des erreurs. Toute erreur de compilation ou de traitement d'expression entraînera une exception `Nette\RegexpException`. +La classe Strings offre des fonctions pour travailler avec les expressions régulières. Contrairement aux fonctions natives de PHP, elles disposent d'une API plus compréhensible, d'un meilleur support Unicode et surtout d'une détection d'erreurs. Toute erreur lors de la compilation ou du traitement de l'expression lance une exception `Nette\RegexpException`. split(string $subject, string $pattern, bool $captureOffset=false, bool $skipEmpty=false, int $limit=-1, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------- -Divise la chaîne de caractères en tableaux selon l'expression régulière. Les expressions entre parenthèses seront également capturées et renvoyées. +Divise une chaîne en tableau selon une expression régulière. Les expressions entre parenthèses seront capturées et retournées également. ```php Strings::split('hello, world', '~,\s*~'); // ['hello', 'world'] Strings::split('hello, world', '~(,)\s*~'); -// ['hello', ',', 'world']`` +// ['hello', ',', 'world'] ``` -Si `$skipEmpty` est `true`, seuls les éléments non vides seront renvoyés : +Si `$skipEmpty` est `true`, seuls les éléments non vides seront retournés : ```php Strings::split('hello, world, ', '~,\s*~'); @@ -417,16 +417,16 @@ Strings::split('hello, world, ', '~,\s*~', skipEmpty: true); // ['hello', 'world'] ``` -Si `$limit` est spécifié, seules les sous-chaînes jusqu'à la limite seront retournées et le reste de la chaîne sera placé dans le dernier élément. Une limite de -1 ou 0 signifie qu'il n'y a pas de limite. +Si `$limit` est spécifié, seules les sous-chaînes jusqu'à la limite seront retournées et le reste de la chaîne sera placé dans le dernier élément. Une limite de -1 ou 0 signifie aucune restriction. ```php Strings::split('hello, world, third', '~,\s*~', limit: 2); // ['hello', 'world, third'] ``` -Si `$utf8` est `true`, l'évaluation passe en mode Unicode. Ceci est similaire à la spécification du modificateur `u`. +Si `$utf8` est `true`, l'évaluation passe en mode Unicode. Similaire à quand vous spécifiez le modificateur `u`. -Si `$captureOffset` est `true`, pour chaque correspondance, sa position dans la chaîne sera également retournée (en octets ; en caractères si `$utf8` est défini). Cela change la valeur de retour en un tableau où chaque élément est une paire composée de la chaîne de caractères correspondante et de sa position. +Si `$captureOffset` est `true`, pour chaque correspondance trouvée, sa position dans la chaîne sera également retournée (en octets ; si `$utf8` est défini, alors en caractères). Cela change la valeur de retour en tableau, où chaque élément est une paire composée de la chaîne correspondante et de sa position. ```php Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true); @@ -440,7 +440,7 @@ Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true, utf8: true); match(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $utf8=false): ?array .[method] -------------------------------------------------------------------------------------------------------------------------------------------------- -Recherche dans la chaîne la partie correspondant à l'expression régulière et renvoie un tableau contenant l'expression trouvée et les sous-expressions individuelles, ou `null`. +Recherche dans la chaîne une partie correspondant à l'expression régulière et retourne un tableau avec l'expression trouvée et les sous-expressions individuelles, ou `null`. ```php Strings::match('hello!', '~\w+(!+)~'); @@ -450,7 +450,7 @@ Strings::match('hello!', '~X~'); // null ``` -Si `$unmatchedAsNull` est `true`, les sous-motifs non appariés sont renvoyés comme nuls ; sinon, ils sont renvoyés comme une chaîne vide ou ne sont pas renvoyés : +Si `$unmatchedAsNull` est `true`, les sous-modèles non capturés sont retournés comme null ; sinon, ils sont retournés comme une chaîne vide ou non retournés : ```php Strings::match('hello', '~\w+(!+)?~'); @@ -460,7 +460,7 @@ Strings::match('hello', '~\w+(!+)?~', unmatchedAsNull: true); // ['hello', null] ``` -Si `$utf8` est `true`, l'évaluation passe en mode Unicode. Ceci est similaire à la spécification du modificateur `u` : +Si `$utf8` est `true`, l'évaluation passe en mode Unicode. Similaire à quand vous spécifiez le modificateur `u` : ```php Strings::match('žlutý kůň', '~\w+~'); @@ -470,9 +470,9 @@ Strings::match('žlutý kůň', '~\w+~', utf8: true); // ['žlutý'] ``` -Le paramètre `$offset` peut être utilisé pour spécifier la position à partir de laquelle commencer la recherche (en octets ; en caractères si `$utf8` est défini). +Le paramètre `$offset` peut être utilisé pour spécifier la position à partir de laquelle commencer la recherche (en octets ; si `$utf8` est défini, alors en caractères). -Si `$captureOffset` est `true`, pour chaque correspondance, sa position dans la chaîne sera également renvoyée (en octets ; en caractères si `$utf8` est défini). Cela transforme la valeur de retour en un tableau où chaque élément est une paire constituée de la chaîne de caractères correspondante et de son décalage : +Si `$captureOffset` est `true`, pour chaque correspondance trouvée, sa position dans la chaîne sera également retournée (en octets ; si `$utf8` est défini, alors en caractères). Cela change la valeur de retour en tableau, où chaque élément est une paire composée de la chaîne correspondante et de son offset : ```php Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true); @@ -483,10 +483,10 @@ Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true, utf8: true); ``` -matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false, bool $lazy=false): array|Generator .[method] +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Recherche dans la chaîne toutes les occurrences correspondant à l'expression régulière et renvoie un tableau de tableaux contenant l'expression trouvée et chaque sous-expression. +Recherche dans la chaîne toutes les occurrences correspondant à l'expression régulière et retourne un tableau de tableaux avec l'expression trouvée et les sous-expressions individuelles. ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~'); @@ -496,7 +496,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~'); ] */ ``` -Si `$patternOrder` est `true`, la structure des résultats change de sorte que le premier élément est un tableau de correspondances de motifs complets, le second est un tableau de chaînes correspondant au premier sous-modèle entre parenthèses, et ainsi de suite : +Si `$patternOrder` est `true`, la structure des résultats change de sorte que dans le premier élément se trouve un tableau des correspondances complètes du modèle, dans le deuxième se trouve un tableau des chaînes auxquelles correspond le premier sous-modèle entre parenthèses, et ainsi de suite : ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~', patternOrder: true); @@ -506,7 +506,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~', patternOrder: true); ] */ ``` -Si `$unmatchedAsNull` est `true`, les sous-motifs non appariés sont renvoyés sous la forme de null ; sinon, ils sont renvoyés sous la forme d'une chaîne vide ou ne sont pas renvoyés : +Si `$unmatchedAsNull` est `true`, les sous-modèles non capturés sont retournés comme null ; sinon, ils sont retournés comme une chaîne vide ou non retournés : ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~', unmatchedAsNull: true); @@ -516,7 +516,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~', unmatchedAsNull: true); ] */ ``` -Si `$utf8` est `true`, l'évaluation passe en mode Unicode. Ceci est similaire à la spécification du modificateur `u` : +Si `$utf8` est `true`, l'évaluation passe en mode Unicode. Similaire à quand vous spécifiez le modificateur `u` : ```php Strings::matchAll('žlutý kůň', '~\w+~'); @@ -532,9 +532,9 @@ Strings::matchAll('žlutý kůň', '~\w+~', utf8: true); ] */ ``` -Le paramètre `$offset` peut être utilisé pour spécifier la position à partir de laquelle commencer la recherche (en octets ; en caractères si `$utf8` est défini). +Le paramètre `$offset` peut être utilisé pour spécifier la position à partir de laquelle commencer la recherche (en octets ; si `$utf8` est défini, alors en caractères). -Si `$captureOffset` est `true`, pour chaque correspondance, sa position dans la chaîne sera également renvoyée (en octets ; en caractères si `$utf8` est défini). Cela transforme la valeur de retour en un tableau où chaque élément est une paire constituée de la chaîne de caractères correspondante et de sa position : +Si `$captureOffset` est `true`, pour chaque correspondance trouvée, sa position dans la chaîne sera également retournée (en octets ; si `$utf8` est défini, alors en caractères). Cela change la valeur de retour en tableau, où chaque élément est une paire composée de la chaîne correspondante et de sa position : ```php Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true); @@ -550,11 +550,21 @@ Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true, utf8: true); ] */ ``` +Si `$lazy` est `true`, la fonction retourne un `Generator` au lieu d'un tableau, ce qui apporte des avantages significatifs en termes de performance lors du travail avec de grandes chaînes. Le générateur permet de rechercher les correspondances progressivement, au lieu de toute la chaîne en une fois. Cela permet de travailler efficacement même avec des textes d'entrée extrêmement grands. De plus, vous pouvez interrompre le traitement à tout moment si vous trouvez la correspondance recherchée, ce qui économise du temps de calcul. + +```php +$matches = Strings::matchAll($largeText, '~\w+~', lazy: true); +foreach ($matches as $match) { + echo "Trouvé : $match[0]\n"; + // Le traitement peut être interrompu à tout moment +} +``` + replace(string $subject, string|array $pattern, string|callable $replacement='', int $limit=-1, bool $captureOffset=false, bool $unmatchedAsNull=false, bool $utf8=false): string .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Remplace toutes les occurrences correspondant à l'expression régulière. L'adresse `$replacement` est soit un masque de chaîne de remplacement, soit une fonction de rappel. +Remplace toutes les occurrences correspondant à l'expression régulière. `$replacement` est soit un masque de chaîne de remplacement, soit un callback. ```php Strings::replace('hello, world!', '~\w+~', '--'); @@ -564,7 +574,7 @@ Strings::replace('hello, world!', '~\w+~', fn($m) => strrev($m[0])); // 'olleh, dlrow!' ``` -La fonction permet également des remplacements multiples en passant un tableau de la forme `pattern => replacement` dans le second paramètre : +La fonction permet également d'effectuer plusieurs remplacements en passant dans le deuxième paramètre un tableau sous la forme `pattern => replacement` : ```php Strings::replace('hello, world!', [ @@ -574,9 +584,9 @@ Strings::replace('hello, world!', [ // '-- --!' ``` -Le paramètre `$limit` limite le nombre de substitutions. Limite -1 signifie aucune limite. +Le paramètre `$limit` limite le nombre de remplacements effectués. Une limite de -1 signifie aucune restriction. -Si `$utf8` est `true`, l'évaluation passe en mode Unicode. Ceci est similaire à la spécification du modificateur `u`. +Si `$utf8` est `true`, l'évaluation passe en mode Unicode. Similaire à quand vous spécifiez le modificateur `u`. ```php Strings::replace('žlutý kůň', '~\w+~', '--'); @@ -586,7 +596,7 @@ Strings::replace('žlutý kůň', '~\w+~', '--', utf8: true); // '-- --' ``` -Si `$captureOffset` est `true`, pour chaque correspondance, sa position dans la chaîne (en octets ; en caractères si `$utf8` est défini) est également transmise à la fonction de rappel. Cela change la forme du tableau passé, où chaque élément est une paire composée de la chaîne de caractères correspondante et de sa position. +Si `$captureOffset` est `true`, pour chaque correspondance trouvée, sa position dans la chaîne sera également passée au callback (en octets ; si `$utf8` est défini, alors en caractères). Cela change la forme du tableau passé, où chaque élément est une paire composée de la chaîne correspondante et de sa position. ```php Strings::replace( @@ -595,7 +605,7 @@ Strings::replace( function (array $m) { dump($m); return ''; }, captureOffset: true, ); -// dumps [['lut', 2]] a [['k', 8]] +// dumps [['lut', 2]] et [['k', 8]] Strings::replace( 'žlutý kůň', @@ -604,10 +614,10 @@ Strings::replace( captureOffset: true, utf8: true, ); -// dumps [['žlutý', 0]] a [['kůň', 6]] +// dumps [['žlutý', 0]] et [['kůň', 6]] ``` -Si `$unmatchedAsNull` est `true`, les sous-motifs non appariés sont transmis à la fonction d'appel comme nuls ; sinon, ils sont transmis comme une chaîne vide ou ne sont pas transmis : +Si `$unmatchedAsNull` est `true`, les sous-modèles non capturés sont passés au callback comme null ; sinon, ils sont passés comme une chaîne vide ou non passés : ```php Strings::replace( diff --git a/utils/fr/type.texy b/utils/fr/type.texy index c885fe08cd..40d152975a 100644 --- a/utils/fr/type.texy +++ b/utils/fr/type.texy @@ -1,8 +1,8 @@ -Type de PHP -*********** +Type PHP +******** .[perex] -[api:Nette\Utils\Type] est une classe de type de données PHP. +[api:Nette\Utils\Type] est une classe pour travailler avec les types de données PHP. Installation : @@ -11,7 +11,7 @@ Installation : composer require nette/utils ``` -Tous les exemples supposent que l'alias de classe suivant est défini : +Tous les exemples supposent qu'un alias a été créé : ```php use Nette\Utils\Type; @@ -21,7 +21,7 @@ use Nette\Utils\Type; fromReflection($reflection): ?Type .[method] -------------------------------------------- -La méthode statique crée un objet Type basé sur la réflexion. Le paramètre peut être un objet `ReflectionMethod` ou `ReflectionFunction` (renvoie le type de la valeur de retour) ou un objet `ReflectionParameter` ou `ReflectionProperty`. Résout `self`, `static` et `parent` au nom de la classe réelle. Si l'objet n'a pas de type, elle renvoie `null`. +La méthode statique crée un objet Type basé sur la réflexion. Le paramètre peut être un objet `ReflectionMethod` ou `ReflectionFunction` (retourne le type de retour) ou `ReflectionParameter` ou `ReflectionProperty`. Traduit `self`, `static` et `parent` en nom de classe réel. Si le sujet n'a pas de type, retourne `null`. ```php class DemoClass @@ -37,7 +37,7 @@ echo Type::fromReflection($prop); // 'DemoClass' fromString(string $type): Type .[method] ---------------------------------------- -La méthode statique crée l'objet Type selon la notation du texte. +La méthode statique crée un objet Type selon la notation textuelle. ```php $type = Type::fromString('Foo|Bar'); @@ -48,10 +48,10 @@ echo $type; // 'Foo|Bar' getNames(): (string|array)[] .[method] -------------------------------------- -Renvoie le tableau des sous-types qui composent le type composé sous forme de chaînes de caractères. +Retourne un tableau de sous-types dont est composé le type composé, comme des chaînes. ```php -$type = Type::fromString('string|null'); // nebo '?string' +$type = Type::fromString('string|null'); // ou '?string' $type->getNames(); // ['string', 'null'] $type = Type::fromString('(Foo&Bar)|string'); @@ -62,10 +62,10 @@ $type->getNames(); // [['Foo', 'Bar'], 'string'] getTypes(): Type[] .[method] ---------------------------- -Renvoie le tableau des sous-types qui composent le type composé sous forme d'objets `Type`: +Retourne un tableau de sous-types dont est composé le type composé, comme des objets `ReflectionType` : ```php -$type = Type::fromString('string|null'); // or '?string' +$type = Type::fromString('string|null'); // ou '?string' $type->getTypes(); // [Type::fromString('string'), Type::fromString('null')] $type = Type::fromString('(Foo&Bar)|string'); @@ -79,7 +79,7 @@ $type->getTypes(); // [Type::fromString('Foo'), Type::fromString('Bar')] getSingleName(): ?string .[method] ---------------------------------- -Renvoie le nom du type pour les types simples, sinon null. +Pour les types simples, retourne le nom du type, sinon null. ```php $type = Type::fromString('string|null'); @@ -99,14 +99,14 @@ echo $type->getSingleName(); // null isSimple(): bool .[method] -------------------------- -Retourne s'il s'agit d'un type simple. Les types simples annulables sont également considérés comme des types simples : +Retourne s'il s'agit d'un type simple. Sont considérés comme des types simples aussi les types simples nullable : ```php $type = Type::fromString('string'); $type->isSimple(); // true $type->isUnion(); // false -$type = Type::fromString('?Foo'); // nebo 'Foo|null' +$type = Type::fromString('?Foo'); // ou 'Foo|null' $type->isSimple(); // true $type->isUnion(); // true ``` @@ -115,10 +115,10 @@ $type->isUnion(); // true isUnion(): bool .[method] ------------------------- -Indique s'il s'agit d'un type d'union. +Retourne s'il s'agit d'un type union. ```php -$type = Type::fromString('Foo&Bar'); +$type = Type::fromString('string|int'); $type->isUnion(); // true ``` @@ -126,11 +126,11 @@ $type->isUnion(); // true isIntersection(): bool .[method] -------------------------------- -Indique s'il s'agit d'un type d'intersection. +Retourne s'il s'agit d'un type intersection. ```php -$type = Type::fromString('string&int'); +$type = Type::fromString('Foo&Bar'); $type->isIntersection(); // true ``` @@ -138,7 +138,7 @@ $type->isIntersection(); // true isBuiltin(): bool .[method] --------------------------- -Retourne si le type est à la fois un type simple et un type intégré PHP. +Retourne si le type est simple et en même temps un type intégré de PHP. ```php $type = Type::fromString('string'); @@ -155,7 +155,7 @@ $type->isBuiltin(); // false isClass(): bool .[method] ------------------------- -Indique si le type est à la fois simple et un nom de classe. +Retourne si le type est simple et en même temps un nom de classe. ```php $type = Type::fromString('string'); @@ -172,7 +172,7 @@ $type->isClass(); // false isClassKeyword(): bool .[method] -------------------------------- -Détermine si le type est l'un des types internes `self`, `parent`, `static`. +Retourne si le type est l'un des types internes `self`, `parent`, `static`. ```php $type = Type::fromString('self'); @@ -186,7 +186,7 @@ $type->isClassKeyword(); // false allows(string $type): bool .[method] ------------------------------------ -La méthode `allows()` vérifie la compatibilité des types. Par exemple, elle permet de vérifier si une valeur d'un certain type peut être passée en paramètre. +La méthode `allows()` vérifie la compatibilité des types. Par exemple, elle permet de déterminer si une valeur d'un certain type pourrait être passée comme paramètre. ```php $type = Type::fromString('string|null'); diff --git a/utils/fr/validators.texy b/utils/fr/validators.texy index 54073869c0..de3df7e623 100644 --- a/utils/fr/validators.texy +++ b/utils/fr/validators.texy @@ -2,7 +2,7 @@ Validateurs de valeurs ********************** .[perex] -Vous avez besoin de vérifier rapidement et facilement qu'une variable contient par exemple une adresse électronique valide ? Alors [api:Nette\Utils\Validators] vous sera utile. Il s'agit d'une classe statique contenant des fonctions utiles pour la validation des valeurs. +Avez-vous besoin de vérifier rapidement et simplement qu'une variable contient, par exemple, une adresse e-mail valide ? La classe statique [api:Nette\Utils\Validators], avec ses fonctions utiles pour la validation de valeurs, vous sera utile. Installation : @@ -11,17 +11,17 @@ Installation : composer require nette/utils ``` -Tous les exemples supposent que l'alias de classe suivant est défini : +Tous les exemples supposent qu'un alias a été créé : ```php use Nette\Utils\Validators; ``` -Utilisation de base .[#toc-basic-usage] -======================================= +Utilisation de base +=================== -La classe `Validators` possède un certain nombre de méthodes pour valider des valeurs, telles que [isList() |#isList()], [isUnicode() |#isUnicode()], [isEmail() |#isEmail()], [isUrl() |#isUrl()], etc. à utiliser dans votre code : +La classe dispose de nombreuses méthodes pour le contrôle des valeurs, comme par exemple [#isUnicode()], [#isEmail()], [#isUrl()] etc. à utiliser dans votre code : ```php if (!Validators::isEmail($email)) { @@ -29,7 +29,7 @@ if (!Validators::isEmail($email)) { } ``` -En outre, il peut vérifier si la valeur satisfait à ce qu'on appelle les [types attendus |#expected types], qui est une chaîne de caractères où les différentes options sont séparées par une barre verticale `|`. Cela permet de vérifier facilement les types d'union en utilisant [if() |#if()]: +De plus, elle sait vérifier si la valeur est l'un des [#types attendus], qui est une chaîne où les différentes options sont séparées par une barre verticale `|`. Nous pouvons ainsi facilement vérifier plusieurs types en utilisant [#is()] : ```php if (!Validators::is($val, 'int|string|bool')) { @@ -37,118 +37,118 @@ if (!Validators::is($val, 'int|string|bool')) { } ``` -Mais cela vous donne également la possibilité de créer un système où il est nécessaire d'écrire les attentes sous forme de chaînes (par exemple dans les annotations ou la configuration) et de vérifier ensuite en fonction de celles-ci. +Mais cela nous donne aussi la possibilité de créer un système où il est nécessaire d'écrire les attentes comme des chaînes (par exemple dans les annotations ou la configuration) et ensuite vérifier les valeurs en fonction de celles-ci. -Vous pouvez également déclarer une [assertion |#assert], qui lève une exception si elle n'est pas satisfaite. +Aux types attendus, on peut aussi ajouter une exigence [#assert()], qui, si elle n'est pas satisfaite, lance une exception. -Types d'attentes .[#toc-expected-types] -======================================= +Types attendus +============== -Un type attendu est une chaîne constituée d'une ou plusieurs variantes séparées par une barre verticale `|`, similar to writing types in PHP (ie. `'int|string|bool')`. La notation nullable est également autorisée `?int`. +Les types attendus forment une chaîne composée d'une ou plusieurs variantes séparées par une barre verticale `|`, similaire à la façon dont les types sont écrits en PHP (par ex. `'int|string|bool'`). La notation nullable `?int` est également acceptée. -Un tableau dont tous les éléments sont d'un certain type s'écrit sous la forme `int[]`. +Un tableau où tous les éléments sont d'un certain type s'écrit sous la forme `int[]`. -Certains types peuvent être suivis d'un deux-points et de la longueur `:length` ou de l'intervalle `:[min]..[max]`par exemple `string:10` (une chaîne de 10 octets), `float:10..` (un nombre égal ou supérieur à 10), `array:..10` (un tableau de 10 éléments maximum) ou `list:10..20` (une liste de 10 à 20 éléments), ou encore une expression régulière comme `pattern:[0-9]+`. +Après certains types peut suivre un deux-points et la longueur `:length` ou une plage `:[min]..[max]`, par ex. `string:10` (chaîne de 10 octets de long), `float:10..` (nombre 10 et plus), `array:..10` (tableau jusqu'à dix éléments) ou `list:10..20` (liste avec 10 à 20 éléments), ou éventuellement une expression régulière pour `pattern:[0-9]+`. Aperçu des types et des règles : .[wide] -| Types PHP || +| Types PHP || |-------------------------- -| `array` .{width: 140px} | On peut donner une fourchette pour le nombre d'éléments -| `bool` | -| `float` - La valeur peut être indiquée dans une plage donnée. -| `int` - L'étendue de la valeur peut être donnée. -| `null` | -| `object` | +| `array` .{width: 140px} | peut spécifier une plage pour le nombre d'éléments +| `bool` | +| `float` | peut spécifier une plage pour la valeur +| `int` | peut spécifier une plage pour la valeur +| `null` | +| `object` | | `resource` | -| `scalar` | int\|float\|bool\|string -| `string` | La longueur en octets peut être indiquée dans une plage donnée. +| `scalar` | int\|float\|bool\|string +| `string` | peut spécifier une plage pour la longueur en octets | `callable` | | `iterable` | -| `mixed` | -|------------------------------------------------ -| pseudo-types || -|------------------------------------------------ -| `list` | [tableau indexé |#isList], le nombre d'éléments peut être donné dans une fourchette. -| `none` | valeur vide : `''`, `null`, `false` -| `number` | int\||float -| `numeric` | [nombre incluant une représentation textuelle |#isNumeric] -| `numericint`| [nombre entier incluant une représentation textuelle|#isNumericInt] -| `unicode` | [Chaîne de caractères UTF-8 |#isUnicode], la longueur en caractères peut être indiquée dans une fourchette -|------------------------------------------------ -| classe de caractères (ne peut être une chaîne vide) | +| `mixed` | +|-------------------------- +| Pseudo-types || |------------------------------------------------ -| `alnum` | tous les caractères sont alphanumériques -| `alpha` | tous les caractères sont des lettres `[A-Za-z]` -| `digit` | tous les caractères sont des chiffres -| `lower` | tous les caractères sont des lettres minuscules `[a-z]` -| `space` | tous les caractères sont des espaces -| `upper` | tous les caractères sont des lettres majuscules `[A-Z]` -| `xdigit` | tous les caractères sont des chiffres hexadécimaux `[0-9A-Fa-f]` +| `list` | tableau indexé, peut spécifier une plage pour le nombre d'éléments +| `none` | valeur vide : `''`, `null`, `false` +| `number` | int\|float +| `numeric` | [nombre y compris représentation textuelle |#isNumeric] +| `numericint`| [entier y compris représentation textuelle |#isNumericInt] +| `unicode` | [chaîne UTF-8 |#isUnicode], peut spécifier une plage pour la longueur en caractères +|-------------------------- +| Classe de caractères (ne doit pas être une chaîne vide) || |------------------------------------------------ -| validation de la syntaxe || +| `alnum` | tous les caractères sont alphanumériques +| `alpha` | tous les caractères sont des lettres `[A-Za-z]` +| `digit` | tous les caractères sont des chiffres +| `lower` | tous les caractères sont des lettres minuscules `[a-z]` +| `space` | tous les caractères sont des espaces +| `upper` | tous les caractères sont des lettres majuscules `[A-Z]` +| `xdigit` | tous les caractères sont des chiffres hexadécimaux `[0-9A-Fa-f]` +|-------------------------- +| Vérification de syntaxe || |------------------------------------------------ -| `pattern` | une expression régulière à laquelle la **entière** chaîne doit correspondre -| `email` | [Courriel |#isEmail] +| `pattern` | expression régulière à laquelle doit correspondre la chaîne **entière** +| `email` | [E-mail |#isEmail] | `identifier`| [Identifiant PHP |#isPhpIdentifier] -| `url` | [URL |#isUrl] -| `uri` | [URI |#isUri] -|------------------------------------------------ -| Validation de l'environnement +| `url` | [URL |#isUrl] +| `uri` | [URI |#isUri] +|-------------------------- +| Vérification d'environnement || |------------------------------------------------ -| `class` | est une classe existante +| `class` | est une classe existante | `interface` | est une interface existante | `directory` | est un répertoire existant -| `file` | est un fichier existant +| `file` | est un fichier existant -Assertion .[#toc-assertion] -=========================== +Assertions +========== assert($value, string $expected, string $label='variable'): void .[method] -------------------------------------------------------------------------- -Vérifie que la valeur est constituée des [types attendus |#expected types] séparés par un pipe. Si ce n'est pas le cas, il lève l'exception [api:Nette\Utils\AssertionException]. Le mot `variable` dans le message d'exception peut être remplacé par le paramètre `$label`. +Vérifie que la valeur est l'un des [#types attendus] séparés par une barre verticale. Sinon, lance une exception [api:Nette\Utils\AssertionException]. Le mot `variable` dans le texte de l'exception peut être remplacé par un autre avec le paramètre `$label`. ```php Validators::assert('Nette', 'string:5'); // OK Validators::assert('Lorem ipsum dolor sit', 'string:78'); -// AssertionException: The variable expects to be string:78, string 'Lorem ipsum dolor sit' given. +// AssertionException: La variable attend d'être string:78, la chaîne 'Lorem ipsum dolor sit' est donnée. ``` -assertField(array $array, string|int $key, string $expected=null, string $label=null): void .[method] ------------------------------------------------------------------------------------------------------ +assertField(array $array, string|int $key, ?string $expected=null, ?string $label=null): void .[method] +------------------------------------------------------------------------------------------------------- -Vérifie que l'élément `$key` dans le tableau `$array` est constitué des [types attendus |#expected types] séparés par un pipe. Si ce n'est pas le cas, il lève l'exception [api:Nette\Utils\AssertionException]. La chaîne `item '%' in array` dans le message d'exception peut être remplacée par le paramètre `$label`. +Vérifie si l'élément sous la clé `$key` dans le tableau `$array` est l'un des [#types attendus] séparés par une barre verticale. Sinon, lance une exception [api:Nette\Utils\AssertionException]. La chaîne `item '%' in array` dans le texte de l'exception peut être remplacée par une autre avec le paramètre `$label`. ```php $arr = ['foo' => 'Nette']; Validators::assertField($arr, 'foo', 'string:5'); // OK Validators::assertField($arr, 'bar', 'string:15'); -// AssertionException: Missing item 'bar' in array. +// AssertionException: Élément manquant 'bar' dans le tableau. Validators::assertField($arr, 'foo', 'int'); -// AssertionException: The item 'foo' in array expects to be int, string 'Nette' given. +// AssertionException: L'élément 'foo' dans le tableau attend d'être int, la chaîne 'Nette' est donnée. ``` -Validateurs .[#toc-validators] -============================== +Validateurs +=========== is($value, string $expected): bool .[method] -------------------------------------------- -Vérifie si la valeur est constituée de [types attendus |#expected types] séparés par un pipe. +Vérifie si la valeur est l'un des [#types attendus] séparés par une barre verticale. ```php Validators::is(1, 'int|float'); // true Validators::is(23, 'int:0..10'); // false -Validators::is('Nette Framework', 'string:15'); // true, length is 15 bytes +Validators::is('Nette Framework', 'string:15'); // true, la longueur est de 15 octets Validators::is('Nette Framework', 'string:8..'); // true Validators::is('Nette Framework', 'string:30..40'); // false ``` @@ -157,7 +157,7 @@ Validators::is('Nette Framework', 'string:30..40'); // false isEmail(mixed $value): bool .[method] ------------------------------------- -Vérifie que la valeur est une adresse électronique valide. Elle ne vérifie pas que le domaine existe réellement, seule la syntaxe est vérifiée. La fonction tient également compte des futurs [TLD |https://en.wikipedia.org/wiki/Top-level_domain], qui peuvent également être en unicode. +Vérifie si la valeur est une adresse e-mail valide. Ne vérifie pas si le domaine existe réellement, vérifie seulement la syntaxe. La fonction prend également en compte les futurs [TLD |https://fr.wikipedia.org/wiki/Domaine_de_premier_niveau], qui peuvent être aussi en unicode. ```php Validators::isEmail('example@nette.org'); // true @@ -169,9 +169,9 @@ Validators::isEmail('nette'); // false isInRange(mixed $value, array $range): bool .[method] ----------------------------------------------------- -Vérifie si la valeur se situe dans l'intervalle donné `[min, max]`où la limite supérieure ou inférieure peut être omise (`null`). Les nombres, les chaînes de caractères et les objets DateTime peuvent être comparés. +Vérifie si la valeur est dans la plage donnée `[min, max]`, où la limite supérieure ou inférieure peut être omise (`null`). Peut comparer des nombres, des chaînes et des objets DateTime. -Si les deux limites sont manquantes (`[null, null]`) ou si la valeur est `null`, il renvoie `false`. +Si les deux limites manquent (`[null, null]`) ou si la valeur est `null`, retourne `false`. ```php Validators::isInRange(5, [0, 5]); // true @@ -213,7 +213,7 @@ Validators::isNumeric('1e6'); // false isNumericInt(mixed $value): bool .[method] ------------------------------------------ -Vérifie si la valeur est un nombre entier ou un nombre entier écrit dans une chaîne de caractères. +Vérifie si la valeur est un entier ou un nombre écrit dans une chaîne. ```php Validators::isNumericInt(23); // true @@ -227,7 +227,7 @@ Validators::isNumericInt('nette'); // false isPhpIdentifier(string $value): bool .[method] ---------------------------------------------- -Vérifie si la valeur est un identifiant syntaxiquement valide en PHP, par exemple pour les noms de classes, de méthodes, de fonctions, etc. +Vérifie si la valeur est un identifiant syntaxiquement valide en PHP, par exemple pour les noms de classes, méthodes, fonctions, etc. ```php Validators::isPhpIdentifier(''); // false @@ -240,7 +240,7 @@ Validators::isPhpIdentifier('one two'); // false isBuiltinType(string $type): bool .[method] ------------------------------------------- -Détermine si `$type` est un type intégré à PHP. Sinon, c'est le nom de la classe. +Détermine si `$type` est un type intégré de PHP. Sinon, c'est un nom de classe. ```php Validators::isBuiltinType('string'); // true @@ -251,7 +251,7 @@ Validators::isBuiltinType('Foo'); // false isTypeDeclaration(string $type): bool .[method] ----------------------------------------------- -Vérifie si la déclaration de type est syntaxiquement correcte. +Vérifie si la déclaration de type donnée est syntaxiquement valide. ```php Validators::isTypeDeclaration('?string'); // true @@ -306,7 +306,7 @@ Validators::isUrl('nette.org'); // false isUri(string $value): bool .[method] ------------------------------------ -Vérifie que la valeur est une adresse URI valide, c'est-à-dire qu'il s'agit bien d'une chaîne commençant par un schéma syntaxiquement valide. +Vérifie si la valeur est une adresse URI valide, c'est-à-dire en fait une chaîne commençant par un schéma syntaxiquement valide. ```php Validators::isUri('https://nette.org'); // true diff --git a/utils/hu/@home.texy b/utils/hu/@home.texy index 9a33f01d4c..505eef03ab 100644 --- a/utils/hu/@home.texy +++ b/utils/hu/@home.texy @@ -1,42 +1,46 @@ -Közművek -******** +Nette Utils +*********** .[perex] -A `nette/utils` csomagban a mindennapi használatra hasznos osztályok találhatók: +A `nette/utils` csomagban egy sor hasznos osztályt talál a mindennapi használatra: -| [Arrays |Arrays] | Nette\Utils\Arrays -| [Visszahívás |Callback] | Nette\Utils\Callback +| [Callback |Callback] | Nette\Utils\Callback | [Dátum és idő |datetime] | Nette\Utils\DateTime -| [Fájlrendszer |filesystem] | Nette\Utils\FileSystem | [Finder |Finder] | Nette\Utils\Finder -| [Floats |Floats] | Nette\Utils\Floats -| [Segédprogramok |helpers] | Nette\Utils\Helpers -| [HTML elemek |HTML Elements] | Nette\Utils\Html -| [Képek |Images] | Nette\Utils\Image -| [JSON |JSON] | Nette\Utils\Json -| [Objektum modell |smartobject] | Nette\SmartObject & Nette\StaticClass -| [Paginátor |paginator] | Nette\Utils\Paginátor -| [PHP Reflection |reflection] | Nette\Utils\Reflection +| [HTML elemek |html-elements] | Nette\Utils\Html +| [Iterátorok |iterables] | Nette\Utils\Iterables +| [JSON |json] | Nette\Utils\Json +| [Véletlen stringek |random] | Nette\Utils\Random +| [Képek |images] | Nette\Utils\Image +| [PHP reflexió |reflection] | Nette\Utils\Reflection | [PHP típusok |type] | Nette\Utils\Type -| [Véletlenszerű karakterláncok |random] | Nette\Utils\Random -| [Sztringek |Strings] | Nette\Utils\Strings -| [Érvényesítők |validators] | Nette\Utils\Validators +| [Tömbök |arrays] | Nette\Utils\Arrays +| [Segédfüggvények |helpers] | Nette\Utils\Helpers +| [Lebegőpontos számok összehasonlítása |floats] | Nette\Utils\Floats +| [Stringek |strings] | Nette\Utils\Strings +| [Fájlrendszer |filesystem] | Nette\Utils\FileSystem +| [Lapozás |paginator] | Nette\Utils\Paginator +| [SmartObject |SmartObject] & [StaticClass |StaticClass] | Nette\SmartObject & Nette\StaticClass +| [Validátor |validators] | Nette\Utils\Validators Telepítés --------- -Töltse le és telepítse a csomagot a [Composer |best-practices:composer] segítségével: +A könyvtárat a [Composer|best-practices:composer] segítségével töltheti le és telepítheti: ```shell composer require nette/utils ``` -| verzió | kompatibilis a PHP-vel +| verzió | kompatibilis PHP-val |-----------|------------------- -| Nette Utils 4.0 | PHP 8.0 - 8.2 -| Nette Utils 3.2 | PHP 7.2 - 8.2 -| Nette Utils 3.0 - 3.1 | PHP 7.1 - 8.0 -| Nette Utils 2.5 | PHP 5.6 - 8.0 +| Nette Utils 4.0 | PHP 8.0 – 8.4 +| Nette Utils 3.2 | PHP 7.2 – 8.3 +| Nette Utils 3.0 – 3.1 | PHP 7.1 – 8.0 +| Nette Utils 2.5 | PHP 5.6 – 8.0 + +Az utolsó patch verzióra vonatkozik. + -A legújabb javítási verziókra vonatkozik. +Ha a csomagot újabb verzióra frissíti, nézze meg a [frissítési |en:upgrading] oldalt. diff --git a/utils/hu/@left-menu.texy b/utils/hu/@left-menu.texy index 7c674c589e..fb9eeba46e 100644 --- a/utils/hu/@left-menu.texy +++ b/utils/hu/@left-menu.texy @@ -1,26 +1,28 @@ -Csomag nette/utils -****************** -- [Tömbök |Arrays] -- [Visszahívás |Callback] +Nette Utils +*********** +- [Callbackek |callback] - [Dátum és idő |datetime] -- [Fájlrendszer |filesystem] -- [Kereső |Finder] -- [Segédprogramok |helpers] -- [HTML elemek |HTML Elements] -- [Képek |Images] +- [Finder |Finder] +- [Floats |Floats] +- [HTML elemek |html-elements] +- [Iterátorok |iterables] - [JSON |JSON] -- [Paginátor |paginator] -- [Véletlenszerű karakterláncok |random] -- [SmartObject |SmartObject] -- [PHP reflexió |reflection] -- [Strings |Strings] -- [Lebegők |Floats] +- [Véletlen stringek |random] +- [Képek |images] +- [Paginator |paginator] +- [PHP Reflexió |reflection] - [PHP típusok |type] -- [Validátorok |validators] +- [Tömbök |arrays] +- [Segédfüggvények |helpers] +- [Stringek |strings] +- [SmartObject |SmartObject] +- [StaticClass |StaticClass] +- [Fájlrendszer |filesystem] +- [Validátor |validators] -Egyéb segédprogramok -******************** -- [NEON |neon:] -- [Jelszóhamisítás |security:passwords] -- [URL elemző és építő |http:urls] +További eszközök +**************** +- [NEON|neon:] +- [Jelszó hashelés |security:passwords] +- [URL elemzés és összeállítás |http:urls] diff --git a/utils/hu/@meta.texy b/utils/hu/@meta.texy new file mode 100644 index 0000000000..c172d1cda5 --- /dev/null +++ b/utils/hu/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette dokumentáció}} diff --git a/utils/hu/arrays.texy b/utils/hu/arrays.texy index b2efecd091..a25bf3e74f 100644 --- a/utils/hu/arrays.texy +++ b/utils/hu/arrays.texy @@ -1,8 +1,8 @@ -Tömbfüggvények -************** +Munka tömbökkel +*************** .[perex] -Ez az oldal a [Nette\Utils\Arrays |#Arrays], [ArrayHash |#ArrayHash] és [ArrayList |#ArrayList] osztályokról szól, amelyek a tömbökkel kapcsolatosak. +Ez az oldal a [Nette\Utils\Arrays |#Arrays], [#ArrayHash] és [#ArrayList] osztályokkal foglalkozik, amelyek tömbökre vonatkoznak. Telepítés: @@ -12,22 +12,63 @@ composer require nette/utils ``` -Arrays .[#toc-arrays] -===================== +Arrays +====== -[api:Nette\Utils\Arrays] egy statikus osztály, amely egy maroknyi praktikus tömbfüggvényt tartalmaz. +Az [api:Nette\Utils\Arrays] egy statikus osztály, amely hasznos függvényeket tartalmaz tömbökkel való munkához. Ennek megfelelője iterátorokhoz a [Nette\Utils\Iterables |iterables]. -A következő példák feltételezik, hogy a következő osztály alias definiálva van: +A következő példák feltételezik a következő alias létrehozását: ```php use Nette\Utils\Arrays; ``` +associate(array $array, mixed $path): array|\stdClass .[method] +--------------------------------------------------------------- + +A függvény rugalmasan átalakítja az `$array` tömböt asszociatív tömbbé vagy objektumokká a megadott `$path` elérési út szerint. Az elérési út lehet string vagy tömb. A bemeneti tömb kulcsneveiből és olyan operátorokból áll, mint '[]', '->', '=', és '|'. `Nette\InvalidArgumentException` kivételt dob, ha az elérési út érvénytelen. + +```php +// átalakítás asszociatív tömbbé egyszerű kulcs szerint +$arr = [ + ['name' => 'John', 'age' => 11], + ['name' => 'Mary', 'age' => null], + // ... +]; +$result = Arrays::associate($arr, 'name'); +// $result = ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// értékek hozzárendelése egyik kulcsból a másikhoz a = operátor használatával +$result = Arrays::associate($arr, 'name=age'); // vagy ['name', '=', 'age'] +// $result = ['John' => 11, 'Mary' => null, ...] +``` + +```php +// objektum létrehozása a -> operátor használatával +$result = Arrays::associate($arr, '->name'); // vagy ['->', 'name'] +// $result = (object) ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// kulcsok kombinálása a | operátor segítségével +$result = Arrays::associate($arr, 'name|age'); // vagy ['name', '|', 'age'] +// $result: ['John' => ['name' => 'John', 'age' => 11], 'Paul' => ['name' => 'Paul', 'age' => 44]] +``` + +```php +// hozzáadás a tömbhöz a [] használatával +$result = Arrays::associate($arr, 'name[]'); // vagy ['name', '[]'] +// $result: ['John' => [['name' => 'John', 'age' => 22], ['name' => 'John', 'age' => 11]]] +``` + + contains(array $array, $value): bool .[method] ---------------------------------------------- -Teszteli egy tömböt érték jelenlétére. Szigorú összehasonlítást használ (`===`) +Teszteli a tömböt egy érték jelenlétére. Szigorú összehasonlítást (`===`) használ. ```php Arrays::contains([1, 2, 3], 1); // true @@ -35,10 +76,10 @@ Arrays::contains(['1', false], 1); // false ``` -every(iterable $array, callable $callback): bool .[method] ----------------------------------------------------------- +every(array $array, callable $predicate): bool .[method] +-------------------------------------------------------- -Megvizsgálja, hogy a tömb minden eleme megfelel-e a megadott függvény által végrehajtott tesztnek, amelynek aláírása `function ($value, $key, array $array): bool`. +Teszteli, hogy a tömb minden eleme átmegy-e a `$predicate`-ben implementált teszten, amelynek szignatúrája `function ($value, $key, array $array): bool`. ```php $array = [1, 30, 39, 29, 10, 13]; @@ -46,24 +87,59 @@ $isBelowThreshold = fn($value) => $value < 40; $res = Arrays::every($array, $isBelowThreshold); // true ``` -Lásd [some() |#some()]. +Lásd [#some()]. -first(array $array): mixed .[method] ------------------------------------- +filter(array $array, callable $predicate): array .[method]{data-version:4.0.4} +------------------------------------------------------------------------------ + +Új tömböt ad vissza, amely minden olyan kulcs-érték párt tartalmaz, amely megfelel a megadott predikátumnak. A callback szignatúrája `function ($value, int|string $key, array $array): bool`. + +```php +Arrays::filter( + ['a' => 1, 'b' => 2, 'c' => 3], + fn($v) => $v < 3, +); +// ['a' => 1, 'b' => 2] +``` + + +first(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------- + +Visszaadja az első elemet (amely megfelel a predikátumnak, ha meg van adva). Ha ilyen elem nem létezik, visszaadja az `$else` hívás eredményét vagy null-t. A `$predicate` paraméter szignatúrája `function ($value, int|string $key, array $array): bool`. + +Nem változtatja meg a belső mutatót, ellentétben a `reset()`-tel. A `$predicate` és `$else` paraméterek a 4.0.4-es verzió óta léteznek. + +```php +Arrays::first([1, 2, 3]); // 1 +Arrays::first([1, 2, 3], fn($v) => $v > 2); // 3 +Arrays::first([]); // null +Arrays::first([], else: fn() => false); // false +``` + +Lásd [#last()]. -Visszaadja a tömb első elemét, vagy null, ha a tömb üres. Nem változtatja meg a belső mutatót, ellentétben a `reset()`. + +firstKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +------------------------------------------------------------------------------------------------ + +Visszaadja az első elem kulcsát (amely megfelel a predikátumnak, ha meg van adva) vagy null-t, ha ilyen elem nem létezik. A `$predicate` predikátum szignatúrája `function ($value, int|string $key, array $array): bool`. ```php -Arrays::first([1, 2, 3]); // 1 -Arrays::first([]); // null +Arrays::firstKey([1, 2, 3]); // 0 +Arrays::firstKey([1, 2, 3], fn($v) => $v > 2); // 2 +Arrays::firstKey(['a' => 1, 'b' => 2]); // 'a' +Arrays::firstKey([]); // null ``` +Lásd [#lastKey()]. + flatten(array $array, bool $preserveKeys=false): array .[method] ---------------------------------------------------------------- -Többdimenziós tömböt alakít át lapos tömbre. +Egyesíti a többszintű tömböt laposra. ```php $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); @@ -71,62 +147,62 @@ $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); ``` -get(array $array, string|int|array $key, mixed $default=null): mixed .[method] ------------------------------------------------------------------------------- +get(array $array, string|int|array $key, ?mixed $default=null): mixed .[method] +------------------------------------------------------------------------------- -Visszaadja `$array[$key]` item. Ha nem létezik, akkor a `Nette\InvalidArgumentException` dob, kivéve, ha harmadik argumentumként egy alapértelmezett érték van megadva. +Visszaadja az `$array[$key]` elemet. Ha nem létezik, vagy `Nette\InvalidArgumentException` kivételt dob, vagy ha a harmadik `$default` paraméter meg van adva, azt adja vissza. ```php // ha $array['foo'] nem létezik, kivételt dob $value = Arrays::get($array, 'foo'); -// ha $array['foo'] nem létezik, 'bar' értéket ad vissza. +// ha $array['foo'] nem létezik, 'bar'-t ad vissza $value = Arrays::get($array, 'foo', 'bar'); ``` -A `$key` argumentum lehet egy tömb is. +A `$key` kulcs lehet tömb is. ```php $array = ['color' => ['favorite' => 'red'], 5]; $value = Arrays::get($array, ['color', 'favorite']); -// returns 'red' +// 'red'-et ad vissza ``` getRef(array &$array, string|int|array $key): mixed .[method] ------------------------------------------------------------- -Hivatkozást kap az adott `$array[$key]`. Ha az index nem létezik, új indexet hoz létre a `null` értékkel. +Referenciát szerez a megadott tömbelemre. Ha az elem nem létezik, null értékkel lesz létrehozva. ```php $valueRef = & Arrays::getRef($array, 'foo'); -// visszaadja a $array['foo'] referenciát +// referenciát ad vissza $array['foo']-ra ``` -Működik többdimenziós tömbökkel és a [get() |#get()] funkcióval is. +Ugyanúgy, mint a [#get()] függvény, tud dolgozni többdimenziós tömbökkel. ```php -$value = & Arrays::get($array, ['color', 'favorite']); -// visszaadja a $array['color']['favorite'] hivatkozást +$value = & Arrays::getRef($array, ['color', 'favorite']); +// referenciát ad vissza $array['color']['favorite']-re ``` grep(array $array, string $pattern, bool $invert=false): array .[method] ------------------------------------------------------------------------ -Csak azokat a tömbelemeket adja vissza, amelyek megfelelnek egy reguláris kifejezésnek `$pattern`. Ha a `$invert` a `true`, visszaadja azokat az elemeket, amelyek nem egyeznek. Regex fordítási vagy futásidejű hiba esetén a `Nette\RegexpException` dob. +Csak azokat a tömbelemeket adja vissza, amelyek értéke megfelel a `$pattern` reguláris kifejezésnek. Ha az `$invert` `true`, ellenkezőleg, azokat az elemeket adja vissza, amelyek nem felelnek meg. A kifejezés fordítása vagy feldolgozása során fellépő hiba `Nette\RegexpException` kivételt dob. ```php $filteredArray = Arrays::grep($array, '~^\d+$~'); -// csak numerikus elemeket ad vissza +// csak a számjegyekből álló tömbelemeket adja vissza ``` insertAfter(array &$array, string|int|null $key, array $inserted): void .[method] --------------------------------------------------------------------------------- -Beilleszti a `$inserted` tömb tartalmát a `$array` tömbbe közvetlenül a `$key` után. Ha a `$key` a `null` (vagy nem létezik), akkor a tömb végére kerül. +Beilleszti az `$inserted` tömb tartalmát az `$array` tömbbe közvetlenül a `$key` kulccsal rendelkező elem után. Ha a `$key` `null` (vagy nincs a tömbben), a végére illeszti be. ```php $array = ['first' => 10, 'second' => 20]; @@ -138,7 +214,7 @@ Arrays::insertAfter($array, 'first', ['hello' => 'world']); insertBefore(array &$array, string|int|null $key, array $inserted): void .[method] ---------------------------------------------------------------------------------- -A `$inserted` tömb tartalmát a `$key` előtt a `$array` tömbbe illeszti be. Ha a `$key` a `null` (vagy nem létezik), akkor az elejére illeszti be. +Beilleszti az `$inserted` tömb tartalmát az `$array` tömbbe a `$key` kulccsal rendelkező elem elé. Ha a `$key` `null` (vagy nincs a tömbben), az elejére illeszti be. ```php $array = ['first' => 10, 'second' => 20]; @@ -150,7 +226,7 @@ Arrays::insertBefore($array, 'first', ['hello' => 'world']); invoke(iterable $callbacks, ...$args): array .[method] ------------------------------------------------------ -Meghívja az összes visszahívást, és visszaadja az eredmények tömbjét. +Meghívja az összes callbacket és visszaadja az eredmények tömbjét. ```php $callbacks = [ @@ -166,7 +242,7 @@ $array = Arrays::invoke($callbacks, 5, 11); invokeMethod(iterable $objects, string $method, ...$args): array .[method] -------------------------------------------------------------------------- -Meghívja a metódust egy tömb minden objektumán, és visszaadja az eredmények tömbjét. +Meghívja a metódust a tömb minden objektumán és visszaadja az eredmények tömbjét. ```php $objects = ['a' => $obj1, 'b' => $obj2]; @@ -179,7 +255,7 @@ $array = Arrays::invokeMethod($objects, 'foo', 1, 2); isList(array $array): bool .[method] ------------------------------------ -Ellenőrzi, hogy a tömb a numerikus kulcsok nullától növekvő sorrendben indexelt-e, azaz lista. +Ellenőrzi, hogy a tömb indexelt-e nullától kezdődő numerikus kulcsok növekvő sorrendje szerint, más néven lista. ```php Arrays::isList(['a', 'b', 'c']); // true @@ -188,21 +264,42 @@ Arrays::isList(['a' => 1, 'b' => 2]); // false ``` -last(array $array): mixed .[method] ------------------------------------ +last(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------ + +Visszaadja az utolsó elemet (amely megfelel a predikátumnak, ha meg van adva). Ha ilyen elem nem létezik, visszaadja az `$else` hívás eredményét vagy null-t. A `$predicate` paraméter szignatúrája `function ($value, int|string $key, array $array): bool`. -Visszaadja a tömb utolsó elemét vagy nullát, ha a tömb üres. Nem változtatja meg a belső mutatót, ellentétben a `end()`. +Nem változtatja meg a belső mutatót, ellentétben az `end()`-del. A `$predicate` és `$else` paraméterek a 4.0.4-es verzió óta léteznek. ```php -Arrays::last([1, 2, 3]); // 3 -Arrays::last([]); // null +Arrays::last([1, 2, 3]); // 3 +Arrays::last([1, 2, 3], fn($v) => $v < 3); // 2 +Arrays::last([]); // null +Arrays::last([], else: fn() => false); // false ``` +Lásd [#first()]. -map(iterable $array, callable $callback): array .[method] + +lastKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +----------------------------------------------------------------------------------------------- + +Visszaadja az utolsó elem kulcsát (amely megfelel a predikátumnak, ha meg van adva) vagy null-t, ha ilyen elem nem létezik. A `$predicate` predikátum szignatúrája `function ($value, int|string $key, array $array): bool`. + +```php +Arrays::lastKey([1, 2, 3]); // 2 +Arrays::lastKey([1, 2, 3], fn($v) => $v < 3); // 1 +Arrays::lastKey(['a' => 1, 'b' => 2]); // 'b' +Arrays::lastKey([]); // null +``` + +Lásd [#firstKey()]. + + +map(array $array, callable $transformer): array .[method] --------------------------------------------------------- -A `$callback` meghívja a tömb összes elemét, és visszaadja a visszatérési értékek tömbjét. A visszahívás aláírása `function ($value, $key, array $array): bool`. +Meghívja a `$transformer`-t a tömb minden elemén és visszaadja a visszaadott értékek tömbjét. A callback szignatúrája `function ($value, $key, array $array): mixed`. ```php $array = ['foo', 'bar', 'baz']; @@ -211,10 +308,24 @@ $res = Arrays::map($array, fn($value) => $value . $value); ``` +mapWithKeys(array $array, callable $transformer): array .[method] +----------------------------------------------------------------- + +Új tömböt hoz létre az eredeti tömb értékeinek és kulcsainak átalakításával. A `$transformer` függvény szignatúrája `function ($value, $key, array $array): ?array{$newKey, $newValue}`. Ha a `$transformer` `null`-t ad vissza, az elem átugrásra kerül. A megőrzött elemekhez a visszaadott tömb első eleme új kulcsként, a második elem pedig új értékként lesz használva. + +```php +$array = ['a' => 1, 'b' => 2]; +$result = Arrays::mapWithKeys($array, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +Ez a metódus hasznos olyan helyzetekben, amikor meg kell változtatni a tömb struktúráját (kulcsokat és értékeket egyszerre), vagy szűrni az elemeket átalakítás közben (null visszaadásával a nem kívánt elemekhez). + + mergeTree(array $array1, array $array2): array .[method] -------------------------------------------------------- -Rekurzívan egyesít két mezőt. Hasznos például fa struktúrák összevonására. Úgy viselkedik, mint a `+` operátor a tömbök esetében, azaz a második tömbből hozzáad egy kulcs/érték párt az elsőhöz, és kulcsütközés esetén megtartja az első tömb értékét. +Rekurzívan egyesít két tömböt. Például alkalmas fa struktúrák egyesítésére. Az egyesítéskor ugyanazokat a szabályokat követi, mint a tömbre alkalmazott `+` operátor, azaz az első tömbhöz hozzáadja a második tömbből származó kulcs/érték párokat, és kulcsütközés esetén meghagyja az értéket az első tömbből. ```php $array1 = ['color' => ['favorite' => 'red'], 5]; @@ -224,13 +335,13 @@ $array = Arrays::mergeTree($array1, $array2); // $array = ['color' => ['favorite' => 'red', 'blue'], 5]; ``` -A második tömb értékei mindig hozzá lesznek csatolva az elsőhöz. A `10` érték eltűnése a második tömbből kissé zavarónak tűnhet. Meg kell jegyezni, hogy ez az érték, valamint a `5` in the first array have the same numeric key `0`, tehát a kapott mezőben csak egy elem van az első tömbből. +A második tömb értékei mindig az első végére kerülnek. Kissé zavarónak tűnhet a `10`-es érték eltűnése a második tömbből. Tudatosítani kell, hogy ehhez az értékhez és ugyanúgy az `5`-ös értékhez az első tömbben ugyanaz a `0` numerikus kulcs van hozzárendelve, ezért az eredménytömbben csak az első tömb eleme van. -normalize(array $array, string $filling=null): array .[method] --------------------------------------------------------------- +normalize(array $array, ?string $filling=null): array .[method] +--------------------------------------------------------------- -Normalizálja a tömböt asszociatív tömbre. A numerikus kulcsokat kicseréli az értékeikkel, az új érték `$filling` lesz. +Normalizálja a tömböt asszociatív tömbre. A numerikus kulcsokat az értékeikkel helyettesíti, az új érték `$filling` lesz. ```php $array = Arrays::normalize([1 => 'first', 'a' => 'second']); @@ -243,26 +354,26 @@ $array = Arrays::normalize([1 => 'first', 'a' => 'second'], 'foobar'); ``` -pick(array &$array, string|int $key, mixed $default=null): mixed .[method] --------------------------------------------------------------------------- +pick(array &$array, string|int $key, ?mixed $default=null): mixed .[method] +--------------------------------------------------------------------------- -Visszaadja és eltávolítja egy elem értékét egy tömbből. Ha nem létezik, akkor kivételt dob, vagy visszaadja a `$default`, ha megadva van. +Visszaadja és eltávolítja az elem értékét a tömbből. Ha nem létezik, kivételt dob, vagy visszaadja a `$default` értéket, ha meg van adva. ```php -$array = [1 => 'foo', null => 'bar']; -$a = Arrays::pick($array, null); +$array = [1 => 'foo', 'x' => 'bar']; +$a = Arrays::pick($array, 'x'); // $a = 'bar' $b = Arrays::pick($array, 'not-exists', 'foobar'); // $b = 'foobar' $c = Arrays::pick($array, 'not-exists'); -// throws Nette\InvalidArgumentException +// Nette\InvalidArgumentException kivételt dob ``` renameKey(array &$array, string|int $oldKey, string|int $newKey): bool .[method] -------------------------------------------------------------------------------- -Átnevez egy kulcsot. Visszaadja a `true` értéket, ha a kulcs megtalálható a tömbben. +Átnevezi a kulcsot a tömbben. `true`-t ad vissza, ha a kulcsot megtalálta a tömbben. ```php $array = ['first' => 10, 'second' => 20]; @@ -274,20 +385,20 @@ Arrays::renameKey($array, 'first', 'renamed'); getKeyOffset(array $array, string|int $key): ?int .[method] ----------------------------------------------------------- -Visszaadja a megadott tömbkulcs nulla indexált pozícióját. Visszaadja a `null` értéket, ha a kulcs nem található. +Visszaadja a megadott kulcs pozícióját a tömbben. A pozíció 0-tól számozódik. Abban az esetben, ha a kulcs nem található, a függvény `null`-t ad vissza. ```php $array = ['first' => 10, 'second' => 20]; -$position = Arrays::getKeyOffset($array, 'first'); // 0-t ad vissza. +$position = Arrays::getKeyOffset($array, 'first'); // 0-t ad vissza $position = Arrays::getKeyOffset($array, 'second'); // 1-et ad vissza -$position = Arrays::getKeyOffset($array, 'not-exists'); // nullát ad vissza +$position = Arrays::getKeyOffset($array, 'not-exists'); // null-t ad vissza ``` -some(iterable $array, callable $callback): bool .[method] ---------------------------------------------------------- +some(array $array, callable $predicate): bool .[method] +------------------------------------------------------- -Megvizsgálja, hogy a tömb legalább egy eleme átmegy-e a `function ($value, $key, array $array): bool` aláírással ellátott callback által végrehajtott teszten. +Teszteli, hogy a tömb legalább egy eleme átmegy-e a `$predicate`-ben implementált teszten, amelynek szignatúrája `function ($value, $key, array $array): bool`. ```php $array = [1, 2, 3, 4]; @@ -295,13 +406,13 @@ $isEven = fn($value) => $value % 2 === 0; $res = Arrays::some($array, $isEven); // true ``` -Lásd [every() |#every()]. +Lásd [#every()]. toKey(mixed $key): string|int .[method] --------------------------------------- -Egy értéket egy tömbkulccsá konvertál, amely vagy egész szám vagy karakterlánc. +Átalakítja az értéket tömbkulccsá, ami vagy egész szám, vagy string. ```php Arrays::toKey('1'); // 1 @@ -312,7 +423,7 @@ Arrays::toKey('01'); // '01' toObject(iterable $array, object $object): object .[method] ----------------------------------------------------------- -A `$array` tömb elemeit átmásolja a `$object` objektumba, majd visszaadja azt. +Átmásolja az `$array` tömb elemeit az `$object` objektumba, amit azután visszaad. ```php $obj = new stdClass; @@ -321,10 +432,10 @@ Arrays::toObject($array, $obj); // beállítja $obj->foo = 1; $obj->bar = 2; ``` -wrap(iterable $array, string $prefix='', string $suffix=''): array .[method] ----------------------------------------------------------------------------- +wrap(array $array, string $prefix='', string $suffix=''): array .[method] +------------------------------------------------------------------------- -A tömb minden egyes elemét karakterlánccá alakítja, és a `$prefix` és a `$suffix` objektummal körülveszi. +A tömb minden elemét stringgé típuskonvertálja, és `$prefix` előtaggal és `$suffix` utótaggal burkolja. ```php $array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); @@ -332,21 +443,21 @@ $array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); ``` -ArrayHash .[#toc-arrayhash] -=========================== +ArrayHash +========= -Az objektum [api:Nette\Utils\ArrayHash] az általános osztály stdClass leszármazottja, és kiterjeszti azt arra a képességre, hogy tömbként kezelje, például a tagokhoz szögletes zárójelek segítségével férjen hozzá: +Az [api:Nette\Utils\ArrayHash] objektum a generikus stdClass osztály leszármazottja, és kibővíti azzal a képességgel, hogy tömbként kezeljük, azaz például szögletes zárójeleken keresztül férjünk hozzá a tagokhoz: ```php $hash = new Nette\Utils\ArrayHash; $hash['foo'] = 123; -$hash->bar = 456; // az objektum jelölés is működik. +$hash->bar = 456; // egyidejűleg működik az objektum írásmód is $hash->foo; // 123 ``` -A `count($hash)` függvényt használhatja az elemek számának megadására. +Használható a `count($hash)` függvény az elemszám megállapítására. -Egy objektumon ugyanúgy iterálhatsz, mint egy tömbön, akár hivatkozással is: +Az objektumon lehet iterálni ugyanúgy, mint tömb esetén, akár referenciával is: ```php foreach ($hash as $key => $value) { @@ -354,11 +465,11 @@ foreach ($hash as $key => $value) { } foreach ($hash as $key => &$value) { - $value = 'új érték'; + $value = 'new value'; } ``` -A meglévő tömbök a `from()` segítségével átalakíthatók `ArrayHash` címmé: +Létező tömböt átalakíthatunk `ArrayHash`-sá a `from()` metódussal: ```php $array = ['foo' => 123, 'bar' => 456]; @@ -374,29 +485,29 @@ Az átalakítás rekurzív: $array = ['foo' => 123, 'inner' => ['a' => 'b']]; $hash = Nette\Utils\ArrayHash::from($array); -$hash->inner; // objektum ArrayHash +$hash->inner; // ArrayHash objektum $hash->inner->a; // 'b' $hash['inner']['a']; // 'b' ``` -A második paraméterrel elkerülhető: +Ezt meg lehet akadályozni a második paraméterrel: ```php $hash = Nette\Utils\ArrayHash::from($array, false); $hash->inner; // tömb ``` -Visszaalakítás a tömbre: +Visszaalakítás tömbbé: ```php $array = (array) $hash; ``` -ArrayList .[#toc-arraylist] -=========================== +ArrayList +========= -[api:Nette\Utils\ArrayList] egy olyan lineáris tömböt reprezentál, ahol az indexek csak 0-tól felfelé növekvő egész számok. +Az [api:Nette\Utils\ArrayList] lineáris tömböt képvisel, ahol az indexek csak 0-tól növekvő sorrendben lévő egész számok. ```php $list = new Nette\Utils\ArrayList; @@ -407,9 +518,16 @@ $list[] = 'c'; count($list); // 3 ``` -A `count($list)` függvényt használhatja az elemek számának megadására. +Létező tömböt átalakíthatunk `ArrayList`-té a `from()` metódussal: -Egy objektumon ugyanúgy iterálhatsz, mint egy tömbön, akár hivatkozással is: +```php +$array = ['foo', 'bar']; +$list = Nette\Utils\ArrayList::from($array); +``` + +Használható a `count($list)` függvény az elemszám megállapítására. + +Az objektumon lehet iterálni ugyanúgy, mint tömb esetén, akár referenciával is: ```php foreach ($list as $key => $value) { @@ -417,32 +535,25 @@ foreach ($list as $key => $value) { } foreach ($list as $key => &$value) { - $value = 'új érték'; + $value = 'new value'; } ``` -A meglévő tömbök a `from()` segítségével átalakíthatók `ArrayList` címmé: - -```php -$array = ['foo', 'bar']; -$list = Nette\Utils\ArrayList::from($array); -``` - -A megengedett értékeken túli kulcsokhoz való hozzáférés kivételt dob `Nette\OutOfRangeException`: +Az engedélyezett értékeken kívüli kulcsokhoz való hozzáférés `Nette\OutOfRangeException` kivételt dob: ```php -echo $list[-1]; // throws Nette\OutOfOfRangeException -unset($list[30]); // throws Nette\OutOfRangeException +echo $list[-1]; // Nette\OutOfRangeException kivételt dob +unset($list[30]); // Nette\OutOfRangeException kivételt dob ``` -A kulcs eltávolítása az elemek átszámozását eredményezi: +A kulcs eltávolítása az elemek újraszámozását okozza: ```php unset($list[1]); // ArrayList(0 => 'a', 1 => 'c') ``` -A `prepend()` segítségével új elemet adhat hozzá az elejére: +Új elemet lehet hozzáadni az elejére a `prepend()` metódussal: ```php $list->prepend('d'); diff --git a/utils/hu/callback.texy b/utils/hu/callback.texy index ffb5306c13..1a47c7dfee 100644 --- a/utils/hu/callback.texy +++ b/utils/hu/callback.texy @@ -1,8 +1,8 @@ -Visszahívási funkciók -********************* +Munka callbackekkel +******************* .[perex] -[api:Nette\Utils\Callback] egy statikus osztály, amely a [PHP visszahívásokkal |https://www.php.net/manual/en/language.types.callable.php] való munkához szükséges függvényeket tartalmazza. +Az [api:Nette\Utils\Callback] egy statikus osztály függvényekkel a [PHP callbackekkel |https://www.php.net/manual/en/language.types.callable.php] való munkához. Telepítés: @@ -11,7 +11,7 @@ Telepítés: composer require nette/utils ``` -Minden példa feltételezi, hogy a következő osztály alias van definiálva: +Minden példa feltételezi a következő alias létrehozását: ```php use Nette\Utils\Callback; @@ -21,21 +21,21 @@ use Nette\Utils\Callback; check($callable, bool $syntax=false): callable .[method] -------------------------------------------------------- -Ellenőrzi, hogy a `$callable` érvényes PHP callback-e. Ellenkező esetben dobja a `Nette\InvalidArgumentException`. Ha a `$syntax` értéke true, a függvény csak azt ellenőrzi, hogy a `$callable` érvényes struktúrával rendelkezik-e, amely callbackként használható, de azt nem ellenőrzi, hogy az osztály vagy metódus valóban létezik-e. Visszaadja a `$callable`. +Ellenőrzi, hogy a `$callable` változó érvényes callback-e. Egyébként `Nette\InvalidArgumentException` kivételt dob. Ha a `$syntax` true, a függvény csak ellenőrzi, hogy a `$callable`-nek callback struktúrája van-e, de nem ellenőrzi, hogy az adott osztály vagy metódus valóban létezik-e. Visszaadja a `$callable`-t. ```php -Callback::check('trim'); // nincs kivétel -Callback::check(['NonExistentClass', 'method']); // Nette\InvalidArgumentException-t dob. -Callback::check(['NonExistentClass', 'method'], true); // nincs kivétel -Callback::check(function () {}); // nincs kivétel -Callback::check(null); // Nette\InvalidArgumentException-t dob. +Callback::check('trim'); // nem dob kivételt +Callback::check(['NonExistentClass', 'method']); // Nette\InvalidArgumentException kivételt dob +Callback::check(['NonExistentClass', 'method'], true); // nem dob kivételt +Callback::check(function () {}); // nem dob kivételt +Callback::check(null); // Nette\InvalidArgumentException kivételt dob ``` toString($callable): string .[method] ------------------------------------- -A PHP visszahívást szöveges formába konvertálja. Osztály vagy metódus nem létezhet. +Átalakítja a PHP callbacket szöveges formába. Az osztálynak vagy metódusnak nem kell léteznie. ```php Callback::toString('trim'); // 'trim' @@ -46,21 +46,21 @@ Callback::toString(['MyClass', 'method']); // 'MyClass::method' toReflection($callable): ReflectionMethod|ReflectionFunction .[method] ---------------------------------------------------------------------- -Visszaadja a PHP visszahívásban használt metódus vagy függvény reflexióját. +Reflexiót ad vissza a metódushoz vagy függvényhez a PHP callbackben. ```php $ref = Callback::toReflection('trim'); -// $ref is ReflectionFunction('trim') +// $ref ReflectionFunction('trim') $ref = Callback::toReflection(['MyClass', 'method']); -// $ref is ReflectionMethod('MyClass', 'method') +// $ref ReflectionMethod('MyClass', 'method') ``` isStatic($callable): bool .[method] ----------------------------------- -Ellenőrzi, hogy a PHP callback függvény vagy statikus metódus. +Megállapítja, hogy a PHP callback függvény vagy statikus metódus-e. ```php Callback::isStatic('trim'); // true @@ -73,7 +73,7 @@ Callback::isStatic(function () {}); // false unwrap(Closure $closure): callable|array .[method] -------------------------------------------------- -Feloldja a `Closure::fromCallable` által létrehozott lezárást :https://www.php.net/manual/en/closure.fromcallable.php. +Visszacsomagolja a `Closure::fromCallable`:https://www.php.net/manual/en/closure.fromcallable.php segítségével létrehozott Closure-t. ```php $closure = Closure::fromCallable(['MyClass', 'method']); diff --git a/utils/hu/datetime.texy b/utils/hu/datetime.texy index 3dea476b42..55fc12c583 100644 --- a/utils/hu/datetime.texy +++ b/utils/hu/datetime.texy @@ -2,7 +2,7 @@ Dátum és idő ************ .[perex] -[api:Nette\Utils\DateTime] egy osztály a natív [php:DateTime] kiterjesztése. +Az [api:Nette\Utils\DateTime] egy osztály, amely kibővíti a natív [php:DateTime] osztályt további funkciókkal. Telepítés: @@ -11,7 +11,7 @@ Telepítés: composer require nette/utils ``` -Minden példa feltételezi, hogy a következő osztály alias definiálva van: +Minden példa feltételezi a következő alias létrehozását: ```php use Nette\Utils\DateTime; @@ -20,35 +20,35 @@ use Nette\Utils\DateTime; static from(string|int|\DateTimeInterface $time): DateTime .[method] -------------------------------------------------------------------- -DateTime objektumot hoz létre egy karakterláncból, UNIX időbélyegből vagy más [php:DateTimeInterface] objektumból. Ha a dátum és az idő érvénytelen, a `Exception` üzenetet dobja. +DateTime objektumot hoz létre stringből, UNIX időbélyegből vagy egy másik [php:DateTimeInterface] objektumból. `Exception` kivételt dob, ha a dátum és idő érvénytelen. ```php -DateTime::from(1138013640); // creates a DateTime from the UNIX timestamp with a default timezamp -DateTime::from(42); // creates a DateTime from the current time plus 42 seconds -DateTime::from('1994-02-26 04:15:32'); // creates a DateTime based on a string -DateTime::from('1994-02-26'); // create DateTime by date, time will be 00:00:00 +DateTime::from(1138013640); // DateTime-ot hoz létre UNIX időbélyegből az alapértelmezett időzónával +DateTime::from(42); // DateTime-ot hoz létre az aktuális időből plusz 42 másodperccel +DateTime::from('1994-02-26 04:15:32'); // DateTime-ot hoz létre string alapján +DateTime::from('1994-02-26'); // DateTime-ot hoz létre dátum alapján, az idő 00:00:00 lesz ``` static fromParts(int $year, int $month, int $day, int $hour=0, int $minute=0, float $second=0.0): DateTime .[method] -------------------------------------------------------------------------------------------------------------------- -DateTime objektumot hoz létre, vagy `Nette\InvalidArgumentException` kivételt dob, ha a dátum és az idő érvénytelen. +DateTime objektumot hoz létre, vagy `Nette\InvalidArgumentException` kivételt dob, ha a dátum és idő érvénytelen. ```php DateTime::fromParts(1994, 2, 26, 4, 15, 32); ``` -static createFromFormat(string $format, string $time, string|\DateTimeZone $timezone=null): DateTime|false .[method] --------------------------------------------------------------------------------------------------------------------- -Kiterjeszti a [DateTime::createFromFormat() |https://www.php.net/manual/en/datetime.createfromformat.php] funkciót az időzóna stringként történő megadásának lehetőségével. +static createFromFormat(string $format, string $time, ?string|\DateTimeZone $timezone=null): DateTime|false .[method] +--------------------------------------------------------------------------------------------------------------------- +Kibővíti a [DateTime::createFromFormat() |https://www.php.net/manual/en/datetime.createfromformat.php] funkciót azzal a lehetőséggel, hogy az időzónát stringként adjuk meg. ```php -DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); // create with custom timezone +DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); ``` modifyClone(string $modify=''): static .[method] ------------------------------------------------ -Másolatot készít egy módosított időponttal. +Másolatot hoz létre módosított idővel. ```php $original = DateTime::from('2017-02-03'); $clone = $original->modifyClone('+1 day'); @@ -59,15 +59,15 @@ $clone->format('Y-m-d'); // '2017-02-04' __toString(): string .[method] ------------------------------ -Visszaadja a dátumot és az időt a `Y-m-d H:i:s` formátumban. +Visszaadja a dátumot és időt `Y-m-d H:i:s` formátumban. ```php echo $dateTime; // '2017-02-03 04:15:32' ``` -JsonSerializable implementálása .[#toc-implements-jsonserializable] -------------------------------------------------------------------- -Visszaadja a dátumot és az időt ISO 8601 formátumban, amelyet például JavaScriptben használnak. +implementálja a JsonSerializable-t +---------------------------------- +Visszaadja a dátumot és időt ISO 8601 formátumban, amit például JavaScriptben használnak. ```php $date = DateTime::from('2017-02-03'); echo json_encode($date); diff --git a/utils/hu/filesystem.texy b/utils/hu/filesystem.texy index 83e5836ceb..9356b132c5 100644 --- a/utils/hu/filesystem.texy +++ b/utils/hu/filesystem.texy @@ -1,41 +1,43 @@ -Fájlrendszer funkciók -********************* +Fájlrendszer +************ .[perex] -[api:Nette\Utils\FileSystem] egy statikus osztály, amely hasznos függvényeket tartalmaz a fájlrendszerrel való munkához. Egyik előnye a natív PHP függvényekkel szemben, hogy hiba esetén kivételeket dobnak. +A [api:Nette\Utils\FileSystem] egy osztály hasznos funkciókkal a fájlrendszerrel való munkához. A natív PHP funkciókkal szembeni egyik előnye, hogy hiba esetén kivételeket dobnak. +Ha fájlokat kell keresnie a lemezen, használja a [Findert |finder]. + Telepítés: ```shell composer require nette/utils ``` -A következő példák feltételezik, hogy a következő osztály alias van definiálva: +A következő példák feltételezik a következő alias létrehozását: ```php use Nette\Utils\FileSystem; ``` -Manipuláció .[#toc-manipulation] -================================ +Manipuláció +=========== copy(string $origin, string $target, bool $overwrite=true): void .[method] -------------------------------------------------------------------------- -Egy fájl vagy egy teljes könyvtár másolása. Alapértelmezés szerint felülírja a meglévő fájlokat és könyvtárakat. Ha a `$overwrite` értéke `false`, és a `$target` már létezik, a `Nette\InvalidStateException` kivételt dob. Hiba esetén kivételt dob `Nette\IOException`. +Lemásolja a fájlt vagy a teljes könyvtárat. Alapértelmezés szerint felülírja a meglévő fájlokat és könyvtárakat. Ha a `$overwrite` paraméter `false` értékre van állítva, `Nette\InvalidStateException` kivételt vált ki, ha a `$target` célfájl vagy könyvtár már létezik. Hiba esetén `Nette\IOException` kivételt vált ki. ```php FileSystem::copy('/path/to/source', '/path/to/dest', overwrite: true); ``` -createDir(string $directory, int $mode=0777): void .[method] ------------------------------------------------------------- +createDir(string $dir, int $mode=0777): void .[method] +------------------------------------------------------ -Létrehoz egy könyvtárat, ha az nem létezik, beleértve a szülői könyvtárakat is. Hiba esetén kivételt dob `Nette\IOException`. +Létrehozza a könyvtárat, ha az nem létezik, beleértve a szülőkönyvtárakat is. Hiba esetén `Nette\IOException` kivételt vált ki. ```php FileSystem::createDir('/path/to/dir'); @@ -45,7 +47,7 @@ FileSystem::createDir('/path/to/dir'); delete(string $path): void .[method] ------------------------------------ -Töröl egy fájlt vagy egy teljes könyvtárat, ha létezik. Ha a könyvtár nem üres, akkor először annak tartalmát törli. Hiba esetén kivételt dob `Nette\IOException`. +Törli a fájlt vagy a teljes könyvtárat, ha létezik. Ha a könyvtár nem üres, először annak tartalmát törli. Hiba esetén `Nette\IOException` kivételt vált ki. ```php FileSystem::delete('/path/to/fileOrDir'); @@ -55,7 +57,7 @@ FileSystem::delete('/path/to/fileOrDir'); makeWritable(string $path, int $dirMode=0777, int $fileMode=0666): void .[method] --------------------------------------------------------------------------------- -A fájljogosultságokat a `$fileMode` vagy a könyvtárjogosultságokat a `$dirMode` értékre állítja. Rekurzívan végigjárja és beállítja a könyvtár teljes tartalmának engedélyeit is. +Beállítja a fájl jogosultságait `$fileMode`-ra vagy a könyvtárét `$dirMode`-ra. Rekurzívan bejárja és beállítja a jogosultságokat a könyvtár teljes tartalmára is. ```php FileSystem::makeWritable('/path/to/fileOrDir'); @@ -65,7 +67,7 @@ FileSystem::makeWritable('/path/to/fileOrDir'); open(string $path, string $mode): resource .[method] ---------------------------------------------------- -Megnyitja a fájlt és visszaadja az erőforrást. A `$mode` paraméter ugyanúgy működik, mint a natív `fopen()`:https://www.php.net/manual/en/function.fopen.php függvény. Hiba esetén a `Nette\IOException` kivételt vet fel. +Megnyitja a fájlt és egy resource-t ad vissza. A `$mode` paraméter ugyanúgy működik, mint a natív `fopen()`:https://www.php.net/manual/en/function.fopen.php függvénynél. Hiba esetén `Nette\IOException` kivételt vált ki. ```php $res = FileSystem::open('/path/to/file', 'r'); @@ -75,7 +77,7 @@ $res = FileSystem::open('/path/to/file', 'r'); read(string $file): string .[method] ------------------------------------ -Beolvassa a `$file` tartalmát. Hiba esetén a `Nette\IOException` kivételt dobja. +Visszaadja a `$file` fájl tartalmát. Hiba esetén `Nette\IOException` kivételt vált ki. ```php $content = FileSystem::read('/path/to/file'); @@ -85,8 +87,7 @@ $content = FileSystem::read('/path/to/file'); readLines(string $file, bool $stripNewLines=true): \Generator .[method] ----------------------------------------------------------------------- -Soronként olvassa be a fájl tartalmát. A natív `file()` függvénytől eltérően nem a teljes fájlt olvassa be a memóriába, hanem folyamatosan olvassa, így a rendelkezésre álló memóriánál nagyobb fájlok is beolvashatók. A `$stripNewLines` megadja, hogy a `\r` és a `\n` sortörő karakterek eltávolításra kerüljenek-e. -Hiba esetén `Nette\IOException` kivételt vet fel. +Soronként olvassa be a fájl tartalmát. Ellentétben a natív `file()` függvénnyel, nem tölti be az egész fájlt a memóriába, hanem folyamatosan olvassa, így a rendelkezésre álló memóriánál nagyobb fájlokat is lehet olvasni. A `$stripNewLines` megadja, hogy el kell-e távolítani a `\r` és `\n` sorvégi karaktereket. Hiba esetén `Nette\IOException` kivételt vált ki. ```php $lines = FileSystem::readLines('/path/to/file'); @@ -100,7 +101,7 @@ foreach ($lines as $lineNum => $line) { rename(string $origin, string $target, bool $overwrite=true): void .[method] ---------------------------------------------------------------------------- -A `$origin` által megadott fájl vagy könyvtár átnevezése vagy áthelyezése a `$target` címre. Alapértelmezés szerint felülírja a meglévő fájlokat és könyvtárakat. Ha a `$overwrite` értéke `false` és a `$target` már létezik, akkor a `Nette\InvalidStateException` kivételt dob. Hiba esetén kivételt dob `Nette\IOException`. +Átnevezi vagy áthelyezi a `$origin` fájlt vagy könyvtárat. Alapértelmezés szerint felülírja a meglévő fájlokat és könyvtárakat. Ha a `$overwrite` paraméter `false` értékre van állítva, `Nette\InvalidStateException` kivételt vált ki, ha a `$target` célfájl vagy könyvtár már létezik. Hiba esetén `Nette\IOException` kivételt vált ki. ```php FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); @@ -110,21 +111,21 @@ FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); write(string $file, string $content, int $mode=0666): void .[method] -------------------------------------------------------------------- -A `$content` értéket a `$file` címre írja. Hiba esetén a `Nette\IOException` kivételt dob. +Beírja a `$content` stringet a `$file` fájlba. Hiba esetén `Nette\IOException` kivételt vált ki. ```php FileSystem::write('/path/to/file', $content); ``` -Útvonalak .[#toc-paths] -======================= +Elérési utak +============ isAbsolute(string $path): bool .[method] ---------------------------------------- -Meghatározza, hogy a `$path` abszolút-e. +Megállapítja, hogy a `$path` elérési út abszolút-e. ```php FileSystem::isAbsolute('../backup'); // false @@ -135,7 +136,7 @@ FileSystem::isAbsolute('C:/backup'); // true joinPaths(string ...$segments): string .[method] ------------------------------------------------ -Összekapcsolja az útvonal összes szegmensét, és normalizálja az eredményt. +Összefűzi az elérési út összes szegmensét és normalizálja az eredményt. ```php FileSystem::joinPaths('a', 'b', 'file.txt'); // 'a/b/file.txt' @@ -146,7 +147,7 @@ FileSystem::joinPaths('/a/', '/../b'); // '/b' normalizePath(string $path): string .[method] --------------------------------------------- -Normalizálja a `..` és a `.` oldalakat, valamint a könyvtárak elválasztóit az elérési útvonalban. +Normalizálja a `..` és `.` karaktereket, valamint a könyvtárelválasztókat az elérési úton a rendszernek megfelelőre. ```php FileSystem::normalizePath('/file/.'); // '/file/' @@ -159,7 +160,7 @@ FileSystem::normalizePath('file/../../bar'); // '/../bar' unixSlashes(string $path): string .[method] ------------------------------------------- -A Unix rendszereken használt `/` -ra konvertálja a slasheket. +A perjeleket `/`-re konvertálja, amelyeket Unix rendszerekben használnak. ```php $path = FileSystem::unixSlashes($path); @@ -169,8 +170,45 @@ $path = FileSystem::unixSlashes($path); platformSlashes(string $path): string .[method] ----------------------------------------------- -A slashes karaktereket az aktuális platformra jellemző karakterekre konvertálja, azaz Windowson `\`, máshol `/`. +A perjeleket az aktuális platformra jellemző karakterekre konvertálja, azaz `\` Windows alatt és `/` máshol. ```php $path = FileSystem::platformSlashes($path); ``` + + +resolvePath(string $basePath, string $path): string .[method]{data-version:4.0.6} +--------------------------------------------------------------------------------- + +Levezeti a végső elérési utat a `$path` elérési útból az `$basePath` alapkönyvtárhoz képest. Az abszolút elérési utakat (`/foo`, `C:/foo`) változatlanul hagyja (csak a perjeleket normalizálja), a relatív elérési utakat hozzáfűzi az alap elérési úthoz. + +```php +// Windows alatt a kimenetben lévő perjelek fordítottak lennének (\) +FileSystem::resolvePath('/base/dir', '/abs/path'); // '/abs/path' +FileSystem::resolvePath('/base/dir', 'rel'); // '/base/dir/rel' +FileSystem::resolvePath('base/dir', '../file.txt'); // 'base/file.txt' +FileSystem::resolvePath('base', ''); // 'base' +``` + + +Statikus vs. nem statikus hozzáférés +==================================== + +Annak érdekében, hogy például tesztelési célokra könnyen helyettesíthessük az osztályt egy másikkal (mockkal), használja nem statikusan: + +```php +class AnyClassUsingFileSystem +{ + public function __construct( + private FileSystem $fileSystem, + ) { + } + + public function readConfig(): string + { + return $this->fileSystem->read(/* ... */); + } + + ... +} +``` diff --git a/utils/hu/finder.texy b/utils/hu/finder.texy index 81131ce5a4..734fa207fe 100644 --- a/utils/hu/finder.texy +++ b/utils/hu/finder.texy @@ -1,8 +1,8 @@ -Kereső: Fájlkeresés -******************* +Finder: fájlok keresése +*********************** .[perex] -Meg kell találnia egy bizonyos maszknak megfelelő fájlokat? A Finder segíthet. Ez egy sokoldalú és gyors eszköz a könyvtárstruktúra böngészésére. +Olyan fájlokat kell találnia, amelyek megfelelnek egy adott maszknak? A Finder segít ebben. Ez egy sokoldalú és gyors eszköz a könyvtárstruktúra bejárására. Telepítés: @@ -11,17 +11,17 @@ Telepítés: composer require nette/utils ``` -A példák feltételezik egy alias létrehozását: +A példák feltételezik a következő alias létrehozását: ```php use Nette\Utils\Finder; ``` -A használata .[#toc-using] ---------------------------- +Használat +--------- -Először is nézzük meg, hogyan használhatjuk a [api:Nette\Utils\Finder] programot a `.txt` és `.md` kiterjesztésű fájlnevek listázására az aktuális könyvtárban: +Először megmutatjuk, hogyan írhatja ki a `.txt` és `.md` kiterjesztésű fájlneveket az aktuális könyvtárban a [api:Nette\Utils\Finder] segítségével: ```php foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { @@ -29,96 +29,97 @@ foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { } ``` -A keresés alapértelmezett könyvtára az aktuális könyvtár, de ezt az [in() vagy from() |#Where to search?] metódusokkal megváltoztathatjuk. -A `$file` változó a [FileInfo |#FileInfo] osztály egy példánya, amely sok hasznos metódussal rendelkezik. A `$name` kulcs a fájl elérési útvonalát tartalmazza sztringként. +A keresés alapértelmezett könyvtára az aktuális könyvtár, de ezt megváltoztathatja az [in() vagy from() |#Hol kell keresni] metódusokkal. A `$file` változó a [#FileInfo] osztály példánya, rengeteg hasznos metódussal. A `$name` kulcsban a fájl elérési útja található stringként. -Mit keressünk? .[#toc-what-to-search-for] ------------------------------------------ +Mit kell keresni? +----------------- -A `findFiles()` módszer mellett létezik a `findDirectories()`, amely csak a könyvtárakban keres, és a `find()`, amely mindkettőben keres. Ezek a metódusok statikusak, tehát példány létrehozása nélkül hívhatók. A maszk paraméter opcionális, ha nem adjuk meg, mindent átkutat. +A `findFiles()` metódus mellett létezik a `findDirectories()`, amely csak könyvtárakat keres, és a `find()`, amely mindkettőt keresi. Ezek a metódusok statikusak, így példány létrehozása nélkül hívhatók. A maszk paraméter opcionális, ha nem adja meg, mindent megkeres. ```php foreach (Finder::find() as $file) { - echo $file; // most már minden fájl és könyvtár fel van sorolva. + echo $file; // most minden fájl és könyvtár kiíródik } ``` -A `files()` és a `directories()` metódusokkal adhatjuk meg, hogy mit keressünk még. A metódusok ismételten meghívhatók, és paraméterként megadható a maszkok tömbje: +A `files()` és `directories()` metódusokkal kiegészítheti, hogy mi mást kell keresni. A metódusok ismételten hívhatók, és paraméterként maszkok tömbje is megadható: ```php -Finder::findDirectories('vendor') // az összes könyvtárat - ->files(['*.php', '*.phpt']); // valamint az összes PHP fájl +Finder::findDirectories('vendor') // minden könyvtár + ->files(['*.php', '*.phpt']); // plusz minden PHP fájl ``` -A statikus módszerek alternatívája, hogy létrehozunk egy példányt a `new Finder` segítségével (az így létrehozott friss objektum nem keres semmit), és megadjuk, hogy mit keressünk a `files()` és a `directories()` segítségével: +A statikus metódusok alternatívája a példány létrehozása a `new Finder` segítségével (az így létrehozott friss objektum semmit sem keres), és a `files()` és `directories()` segítségével megadni, mit kell keresni: ```php (new Finder) - ->directories() // az összes könyvtárat - ->files('*.php'); // valamint az összes PHP fájl + ->directories() // minden könyvtár + ->files('*.php'); // plusz minden PHP fájl ``` -Használhat [vadkártyákat |#wildcards] `*`, `**`, `?` and `[...]` a maszkban. Megadhat könyvtárakat is, például a `src/*.php` a `src` könyvtárban található összes PHP-fájlt megkeresi. +A maszkban használhat [helyettesítő karaktereket |#Helyettesítő karakterek] `*`, `**`, `?` és `[...]`. Akár könyvtárakat is megadhat, például a `src/*.php` megkeresi az összes PHP fájlt a `src` könyvtárban. +A szimbolikus linkek szintén könyvtárnak vagy fájlnak minősülnek. -Hol keressünk? .[#toc-where-to-search] --------------------------------------- -Az alapértelmezett keresési könyvtár az aktuális könyvtár. Ezt a `in()` és a `from()` módszerekkel módosíthatja. Amint a metódusnevekből látható, a `in()` csak az aktuális könyvtárban keres, míg a `from()` annak alkönyvtáraiban is (rekurzívan). Ha rekurzívan akarsz keresni az aktuális könyvtárban, használhatod a `from('.')`. +Hol kell keresni? +----------------- -Ezeket a metódusokat többször is meg lehet hívni, vagy több elérési utat is átadhatunk nekik tömbként, akkor a fájlok minden könyvtárban keresésre kerülnek. Ha az egyik könyvtár nem létezik, akkor a `Nette\UnexpectedValueException` eredményt kapjuk. +A keresés alapértelmezett könyvtára az aktuális könyvtár. Ezt az `in()` és `from()` metódusokkal változtathatja meg. Ahogy a metódusnevekből is látható, az `in()` csak az adott könyvtárban keres, míg a `from()` annak alkönyvtáraiban is keres (rekurzívan). Ha rekurzívan szeretne keresni az aktuális könyvtárban, használhatja a `from('.')`-ot. + +Ezek a metódusok többször is hívhatók, vagy több elérési utat átadhat nekik tömbként, a fájlok ezután minden könyvtárban keresve lesznek. Ha valamelyik könyvtár nem létezik, `Nette\UnexpectedValueException` kivétel dobódik. ```php Finder::findFiles('*.php') - ->in(['src', 'tests']) // közvetlenül a src/ és tests/ fájlokban keres. - ->from('vendor'); // keres a vendor/ alkönyvtárakban is + ->in(['src', 'tests']) // közvetlenül keres a src/ és tests/ könyvtárakban + ->from('vendor'); // a vendor/ alkönyvtáraiban is keres ``` -A relatív elérési utak az aktuális könyvtárhoz képest relatívak. Természetesen abszolút elérési utak is megadhatók: +A relatív elérési utak az aktuális könyvtárhoz képest relatívak. Természetesen abszolút elérési utakat is meg lehet adni: ```php Finder::findFiles('*.php') ->in('/var/www/html'); ``` -Wildcards [wildcards |#wildcards] `*`, `**`, `?` can be used in the path. For example, you can use the path `src/*/*.php` a `src` könyvtár második szintű könyvtáraiban található összes PHP fájl kereséséhez. A `**` karakter, az úgynevezett globstar egy erős aduász, mert lehetővé teszi az alkönyvtárakban való keresést is: a `src/**/tests/*.php` használatával a `tests` könyvtárban található összes PHP-fájlt kereshetjük, amelyek a `src` könyvtárban vagy annak bármelyik alkönyvtárában találhatók. +Az elérési úton lehet használni [helyettesítő karaktereket |#Helyettesítő karakterek] `*`, `**`, `?`. Így például a `src/*/*.php` elérési út segítségével keresheti az összes PHP fájlt a `src` könyvtár második szintű könyvtáraiban. A `**` karakter, amit globstarnak neveznek, egy erős aduász, mert lehetővé teszi az alkönyvtárakban való keresést is: a `src/**/tests/*.php` segítségével keresi az összes PHP fájlt a `tests` könyvtárban, amely a `src`-ben vagy annak bármely alkönyvtárában található. -Másrészt a jokerek `[...]` karakterek nem támogatottak az elérési útvonalban, azaz nincs speciális jelentésük, hogy elkerüljük a nem kívánt viselkedést abban az esetben, ha például a `in(__DIR__)` címre keresünk, és véletlenül `[]` karakterek jelennek meg az elérési útvonalban. +Ellenben a `[...]` helyettesítő karakterek az elérési úton nem támogatottak, azaz nincs különleges jelentésük, hogy elkerüljük a nem kívánt viselkedést abban az esetben, ha például az `in(__DIR__)`-t keresi, és véletlenül az elérési úton `[]` karakterek lesznek. -A fájlok és könyvtárak mélységi keresésekor a rendszer először a szülő könyvtárat adja vissza, majd az abban található fájlokat, ami a `childFirst()` segítségével megfordítható. +Fájlok és könyvtárak mélységi keresésekor először a szülőkönyvtárat adja vissza, csak azután a benne lévő fájlokat, amit a `childFirst()` segítségével meg lehet fordítani. -Jokerjelek .[#toc-wildcards] ----------------------------- +Helyettesítő karakterek +----------------------- -A maszkban több speciális karaktert is használhat: +A maszkban több speciális karaktert használhat: -- `*` - replaces any number of arbitrary characters (except `/`) -- `**` - tetszőleges számú tetszőleges karaktert helyettesít, beleértve a `/`-t is (azaz többszintű keresés is lehetséges). -- `?` - replaces one arbitrary character (except `/`) -- `[a-z]` - egy karaktert helyettesít a szögletes zárójelben lévő karakterek listájából. -- `[!a-z]` - egy karaktert helyettesít a szögletes zárójelben lévő karakterek listáján kívül. +- `*` - tetszőleges számú tetszőleges karaktert helyettesít (kivéve `/`) +- `**` - tetszőleges számú tetszőleges karaktert helyettesít, beleértve a `/`-t is (azaz többszintű keresés lehetséges) +- `?` - egy tetszőleges karaktert helyettesít (kivéve `/`) +- `[a-z]` - egy karaktert helyettesít a szögletes zárójelben lévő karakterlistából +- `[!a-z]` - egy karaktert helyettesít a szögletes zárójelben lévő karakterlistán kívül Használati példák: -- `img/?.png` - `0.png`, `1.png`, `x.png`, stb. -- `logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log` - naplófájlok a következő formátumban `YYYY-MM-DD` -- `src/**/tests/*` - a `src/tests`, `src/foo/tests`, `src/foo/bar/tests` stb. könyvtárban található fájlok. -- `docs/**.md` - a `.md` kiterjesztésű összes fájl a könyvtár összes alkönyvtárában. `docs` +- `img/?.png` - egybetűs nevű fájlok `0.png`, `1.png`, `x.png`, stb. +- `logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log` - `YYYY-MM-DD` formátumú naplók +- `src/**/tests/*` - fájlok a `src/tests`, `src/foo/tests`, `src/foo/bar/tests` könyvtárakban és így tovább. +- `docs/**.md` - minden `.md` kiterjesztésű fájl a `docs` könyvtár összes alkönyvtárában -A kivételével. .[#toc-excluding] ---------------------------------- +Kizárás +------- -A `exclude()` módszerrel kizárhat fájlokat és könyvtárakat a keresésből. Megad egy maszkot, amelynek a fájl nem felelhet meg. Példa a `*.txt` fájlok keresésére, kivéve azokat, amelyek nevében szerepel az `X` betű: +Az `exclude()` metódussal kizárhat fájlokat és könyvtárakat a keresésből. Megadja a maszkot, amelynek a fájl nem felelhet meg. Példa `*.txt` fájlok keresésére, kivéve azokat, amelyek nevében `X` betű szerepel: ```php Finder::findFiles('*.txt') ->exclude('*X*'); ``` -A `exclude()` használatával kihagyhatja a böngészett alkönyvtárakat: +A bejárt alkönyvtárak kihagyásához használja az `exclude()`-ot: ```php Finder::findFiles('*.php') @@ -127,12 +128,12 @@ Finder::findFiles('*.php') ``` -Szűrés: .[#toc-filtering] -------------------------- +Szűrés +------ -A Finder többféle módszert kínál a találatok szűrésére (azaz csökkentésére). Ezeket kombinálhatja és többször is meghívhatja. +A Finder több metódust kínál az eredmények szűrésére (azaz azok csökkentésére). Kombinálhatja őket és ismételten hívhatja. -A `size()` segítségével szűrhet a fájlméret szerint. Így a 100 és 200 bájt közötti méretű fájlokat találjuk meg: +A `size()` segítségével fájlméret szerint szűrünk. Így találunk 100 és 200 bájt közötti méretű fájlokat: ```php Finder::findFiles('*.php') @@ -140,7 +141,7 @@ Finder::findFiles('*.php') ->size('<=', 200); ``` -A `date()` módszer a fájl utolsó módosításának dátuma alapján szűr. Az értékek lehetnek abszolút vagy az aktuális dátumhoz és időhöz viszonyítottak, például így találjuk meg az elmúlt két hétben módosított fájlokat: +A `date()` metódus a fájl utolsó módosításának dátuma szerint szűr. Az értékek lehetnek abszolútak vagy relatívak az aktuális dátumhoz és időhöz képest, például így találjuk meg az elmúlt két hétben módosított fájlokat: ```php Finder::findFiles('*.php') @@ -148,11 +149,11 @@ Finder::findFiles('*.php') ->from($dir) ``` -Mindkét függvény érti a `>`, `>=`, `<`, `<=`, `=`, `!=`, `<>` operátorokat. +Mindkét funkció érti a `>`, `>=`, `<`, `<=`, `=`, `!=`, `<>` operátorokat. -A Finder lehetővé teszi az eredmények szűrését egyéni függvények segítségével is. A függvény paraméterként egy `Nette\Utils\FileInfo` objektumot kap, és vissza kell adnia a `true` címet ahhoz, hogy a fájl bekerüljön az eredmények közé. +A Finder lehetővé teszi az eredmények szűrését saját funkciók segítségével is. A funkció paraméterként kap egy `Nette\Utils\FileInfo` objektumot, és `true`-t kell visszaadnia, hogy a fájl bekerüljön az eredmények közé. -Példa: A `Nette` karakterláncot tartalmazó PHP-fájlok keresése (nagy- és kisbetűket nem érzékeny): +Példa: olyan PHP fájlok keresése, amelyek tartalmazzák a `Nette` stringet (kis- és nagybetűk figyelmen kívül hagyásával): ```php Finder::findFiles('*.php') @@ -160,51 +161,51 @@ Finder::findFiles('*.php') ``` -Mélységi szűrés .[#toc-depth-filtering] ---------------------------------------- +Mélységi szűrés +--------------- -Rekurzív keresés esetén a `limitDepth()` módszerrel beállíthatja a maximális feltérképezési mélységet. A `limitDepth(1)` beállítása esetén csak az első alkönyvtárak kerülnek feltérképezésre, a `limitDepth(0)` letiltja a mélységi feltérképezést, a -1 érték pedig törli a korlátozást. +Rekurzív kereséskor beállíthatja a maximális bejárási mélységet a `limitDepth()` metódussal. Ha beállítja a `limitDepth(1)`-et, csak az első alkönyvtárakat járja be, a `limitDepth(0)` kikapcsolja a mélységi bejárást, és a -1 érték törli a korlátot. -A Finder lehetővé teszi, hogy saját függvényei segítségével döntse el, melyik könyvtárba lépjen be a böngészés során. A függvény paraméterként egy `Nette\Utils\FileInfo` objektumot kap, és a könyvtárba való belépéshez vissza kell adnia a `true` értéket: +A Finder lehetővé teszi saját funkciókkal eldönteni, melyik könyvtárba lépjen be a bejárás során. A funkció paraméterként kap egy `Nette\Utils\FileInfo` objektumot, és `true`-t kell visszaadnia, hogy belépjen a könyvtárba: ```php Finder::findFiles('*.php') - ->descentFilter($file->getBasename() !== 'temp'); + ->descentFilter(fn($file) => $file->getBasename() !== 'temp'); ``` -Rendezés .[#toc-sorting] ------------------------- +Rendezés +-------- A Finder több funkciót is kínál az eredmények rendezésére. -A `sortByName()` módszer az eredményeket fájlnév szerint rendezi. A rendezés természetes, azaz helyesen kezeli a nevekben szereplő számokat, és például a `foo1.txt` -t a `foo10.txt` előtt adja vissza. +A `sortByName()` metódus az eredményeket fájlnevek szerint rendezi. A rendezés naturális, azaz helyesen kezeli a számokat a nevekben, és például a `foo1.txt`-t adja vissza a `foo10.txt` előtt. -A Finder lehetővé teszi az egyéni funkcióval történő rendezést is. Ez két `Nette\Utils\FileInfo` objektumot vesz fel paraméterként, és az összehasonlítás eredményét kell visszaadnia az operátorral `<=>`, azaz `-1`, `0` nebo `1`. Például így rendezzük a fájlokat méret szerint: +A Finder lehetővé teszi a rendezést saját funkcióval is. Paraméterként két `Nette\Utils\FileInfo` objektumot kap, és vissza kell adnia az `<=>` operátorral végzett összehasonlítás eredményét, azaz `-1`, `0` vagy `1`. Például így rendezzük a fájlokat méret szerint: ```php $finder->sortBy(fn($a, $b) => $a->getSize() <=> $b->getSize()); ``` -Több különböző keresés .[#toc-multiple-different-searches] ----------------------------------------------------------- +Több különböző keresés +---------------------- -Ha több különböző helyen lévő vagy különböző feltételeknek megfelelő fájlt kell keresnie, használja a `append()` módszert. Ez egy új `Finder` objektumot ad vissza, így a metódushívások láncolhatók: +Ha több különböző fájlt kell találnia különböző helyeken, vagy más kritériumoknak megfelelőket, használja az `append()` metódust. Új `Finder` objektumot ad vissza, így lehetséges a metódushívásokat láncolni: ```php -($finder = new Finder) // tároljuk az első Finder-t a $finder változóban! - ->files('*.php') // *.php fájlokat keresünk a src/ könyvtárban. +($finder = new Finder) // a $finder változóba elmentjük az első Findert! + ->files('*.php') // a src/-ben *.php fájlokat keresünk ->from('src') ->append() - ->files('*.md') // a docs/-ban keres *.md fájlokat + ->files('*.md') // a docs/-ban *.md fájlokat keresünk ->from('docs') ->append() - ->files('*.json'); // az aktuális mappában keressük a *.json fájlokat. + ->files('*.json'); // az aktuális mappában *.json fájlokat keresünk ``` -Alternatívaként használhatja a `append()` metódust egy adott fájl (vagy fájlok tömbjének) hozzáadásához. Ekkor ugyanazt az objektumot adja vissza `Finder`: +Alternatívaként használható az `append()` metódus egy konkrét fájl (vagy fájlok tömbjének) hozzáadására. Akkor ugyanazt a `Finder` objektumot adja vissza: ```php $finder = Finder::findFiles('*.txt') @@ -212,12 +213,12 @@ $finder = Finder::findFiles('*.txt') ``` -FileInfo .[#toc-fileinfo] -------------------------- +FileInfo +-------- -A[Nette\Utils\FileInfo |api:] egy osztály, amely egy fájlt vagy könyvtárat reprezentál a keresési eredményekben. Ez a [SplFileInfo |php:SplFileInfo] osztály kiterjesztése, amely olyan információkat szolgáltat, mint a fájl mérete, utolsó módosítás dátuma, neve, elérési útja stb. +A [Nette\Utils\FileInfo |api:] egy fájlt vagy könyvtárat reprezentáló osztály a keresési eredményekben. Ez a [SplFileInfo |php:SplFileInfo] osztály kiterjesztése, amely információkat nyújt, mint például a fájlméret, utolsó módosítás dátuma, név, elérési út, stb. -Ezen kívül metódusokat biztosít a relatív elérési utak visszaadására, ami hasznos a mélységi böngészés során: +Ezenkívül metódusokat biztosít a relatív elérési út visszaadására, ami hasznos a mélységi bejárás során: ```php foreach (Finder::findFiles('*.jpg')->from('.') as $file) { @@ -226,7 +227,7 @@ foreach (Finder::findFiles('*.jpg')->from('.') as $file) { } ``` -Vannak módszerei a fájl tartalmának olvasására és írására is: +Továbbá rendelkezésre állnak metódusok a fájl tartalmának olvasására és írására: ```php foreach ($finder as $file) { @@ -237,12 +238,12 @@ foreach ($finder as $file) { ``` -Eredmények visszaadása tömbként .[#toc-returning-results-as-an-array] ---------------------------------------------------------------------- +Eredmények visszaadása tömbként +------------------------------- -Amint a példákban láttuk, a Finder megvalósítja a `IteratorAggregate` interfészt , így a `foreach` segítségével böngészhetünk az eredmények között. Úgy van programozva, hogy az eredmények csak a böngészés közben töltődnek be, így ha nagyszámú fájlod van, nem várja meg, hogy mindet beolvassa. +Ahogy a példákban látható volt, a Finder implementálja az `IteratorAggregate` interfészt, így használhatja a `foreach`-et az eredmények bejárására. Úgy van programozva, hogy az eredmények csak a bejárás során töltődnek be, így ha nagy mennyiségű fájlja van, nem várja meg, amíg mind beolvasódik. -A `collect()` metódus segítségével az eredményeket `Nette\Utils\FileInfo` objektumok tömbjeként is visszakaphatja. A tömb nem asszociatív, hanem numerikus. +Az eredményeket `Nette\Utils\FileInfo` objektumok tömbjeként is visszaadhatja, méghozzá a `collect()` metódussal. A tömb nem asszociatív, hanem numerikus. ```php $array = $finder->findFiles('*.php')->collect(); diff --git a/utils/hu/floats.texy b/utils/hu/floats.texy index 64f72c88b3..391506e246 100644 --- a/utils/hu/floats.texy +++ b/utils/hu/floats.texy @@ -1,8 +1,8 @@ -Lebegők Funkciók +Munka floatokkal **************** .[perex] -[api:Nette\Utils\Floats] egy statikus osztály, amely hasznos függvényeket tartalmaz a lebegőszámok összehasonlítására. +Az [api:Nette\Utils\Floats] egy statikus osztály hasznos funkciókkal tizedes számok összehasonlítására. Telepítés: @@ -11,18 +11,17 @@ Telepítés: composer require nette/utils ``` -Minden példa feltételezi, hogy a következő osztály alias van definiálva: +Minden példa feltételezi a következő alias létrehozását: ```php use Nette\Utils\Floats; ``` -Motiváció .[#toc-motivation] -============================ +Motiváció +========= -Kíváncsi vagy, hogy mire való egy float összehasonlító osztály? Használhatod az operátorokat `<`, `>`, `===`, gondolja. -Ez nem teljesen igaz. Mit gondolsz, mit fog kiírni ez a kód? +Azt gondolhatja, miért is van szükség egy osztályra a lebegőpontos számok összehasonlítására? Hiszen használhatom a `<`, `>`, `===` operátorokat, és kész. Ez nem teljesen igaz. Mit gondol, mit ír ki ez a kód? ```php $a = 0.1 + 0.2; @@ -30,27 +29,30 @@ $b = 0.3; echo $a === $b ? 'same' : 'not same'; ``` -Ha lefuttatjátok a kódot, néhányan meg fognak lepődni, hogy a program a `not same` címet nyomtatta ki. +Ha futtatja a kódot, néhányan biztosan meglepődnek, hogy a program `not same`-et írt ki. -A lebegőszámokkal végzett matematikai műveletek hibákat okoznak a decimális és a bináris rendszer közötti konverzió miatt. Például `0.1 + 0.2` egyenlő `0.300000000000000044…`. Ezért a lebegőszámok összehasonlításakor el kell tűrnünk egy kis eltérést egy bizonyos tizedesjegyen belül. +Tizedes számokkal végzett matematikai műveletek során hibák lépnek fel a tízes és kettes számrendszer közötti átváltás miatt. Például a `0.1 + 0.2` eredménye `0.300000000000000044…`. Ezért összehasonlításkor tolerálnunk kell egy kis különbséget egy bizonyos tizedesjegytől kezdve. -És ezt teszi a `Floats` osztály. A következő összehasonlítás az elvárásoknak megfelelően fog működni: +És pontosan ezt teszi a `Floats` osztály. A következő összehasonlítás már az elvárásoknak megfelelően fog működni: ```php echo Floats::areEqual($a, $b) ? 'same' : 'not same'; // same ``` -Amikor megpróbálja összehasonlítani a `NAN`, akkor a `\LogicException` kivételt dob. +`NAN` összehasonlítási kísérletekor `\LogicException` kivételt dob. +.[tip] +A `Floats` osztály az `1e-10`-nél kisebb különbségeket tolerálja. Ha nagyobb pontosságra van szüksége, használja inkább a BCMath könyvtárat. -Lebegő összehasonlítás .[#toc-float-comparison] -=============================================== + +Lebegőpontos számok összehasonlítása +==================================== areEqual(float $a, float $b): bool .[method] -------------------------------------------- -Visszaadja a `true` értéket, ha `$a` = `$b`. +`true`-t ad vissza, ha `$a` = `$b`. ```php Floats::areEqual(10, 10.0); // true @@ -60,7 +62,7 @@ Floats::areEqual(10, 10.0); // true isLessThan(float $a, float $b): bool .[method] ---------------------------------------------- -Visszaadja a `true` értéket, ha `$a` < `$b`. +`true`-t ad vissza, ha `$a` < `$b`. ```php Floats::isLessThan(9.5, 10.2); // true @@ -71,7 +73,7 @@ Floats::isLessThan(INF, 10.2); // false isLessThanOrEqualTo(float $a, float $b): bool .[method] ------------------------------------------------------- -Visszaadja a `true` értéket, ha `$a` <= `$b`. +`true`-t ad vissza, ha `$a` <= `$b`. ```php Floats::isLessThanOrEqualTo(9.5, 10.2); // true @@ -82,7 +84,7 @@ Floats::isLessThanOrEqualTo(10.25, 10.25); // true isGreaterThan(float $a, float $b): bool .[method] ------------------------------------------------- -Visszaadja a `true` értéket, ha `$a` > `$b`. +`true`-t ad vissza, ha `$a` > `$b`. ```php Floats::isGreaterThan(9.5, -10.2); // true @@ -93,7 +95,7 @@ Floats::isGreaterThan(9.5, 10.2); // false isGreaterThanOrEqualTo(float $a, float $b): bool .[method] ---------------------------------------------------------- -Visszaadja a `true` értéket, ha `$a` >= `$b`. +`true`-t ad vissza, ha `$a` >= `$b`. ```php Floats::isGreaterThanOrEqualTo(9.5, 10.2); // false @@ -104,25 +106,25 @@ Floats::isGreaterThanOrEqualTo(10.2, 10.2); // true compare(float $a, float $b): int .[method] ------------------------------------------ -Ha `$a` < `$b`, akkor `-1`, ha egyenlőek, akkor `0` and if `$a` > `$b`, akkor `1`. +Ha `$a` < `$b`, `-1`-et ad vissza, ha egyenlőek, `0`-t ad vissza, és ha `$a` > `$b`, `1`-et ad vissza. -Használható például a `usort` függvénnyel. +Például használható az `usort` funkcióval. ```php $arr = [1, 5, 2, -3.5]; -usort($arr, [Float::class, 'compare']); -// $arr is [-3.5, 1, 2, 5] +usort($arr, [Floats::class, 'compare']); +// $arr most [-3.5, 1, 2, 5] ``` -Segédfunkciók .[#toc-helpers-functions] -======================================= +Segédfunkciók +============= isZero(float $value): bool .[method] ------------------------------------ -Visszaadja a `true` értéket, ha az érték nulla. +`true`-t ad vissza, ha az érték nulla. ```php Floats::isZero(0.0); // true @@ -133,7 +135,7 @@ Floats::isZero(0); // true isInteger(float $value): bool .[method] --------------------------------------- -Visszaadja a `true` értéket, ha az érték egész szám. +`true`-t ad vissza, ha az érték egész szám. ```php Floats::isInteger(0); // true diff --git a/utils/hu/helpers.texy b/utils/hu/helpers.texy index ae8ef88246..04c7fa7ce4 100644 --- a/utils/hu/helpers.texy +++ b/utils/hu/helpers.texy @@ -2,7 +2,7 @@ Segédfunkciók ************* .[perex] -[api:Nette\Utils\Helpers] egy statikus osztály hasznos funkciókkal. +Az [api:Nette\Utils\Helpers] egy statikus osztály hasznos funkciókkal. Telepítés: @@ -11,7 +11,7 @@ Telepítés: composer require nette/utils ``` -Minden példa feltételezi, hogy a következő osztály alias van definiálva: +Minden példa feltételezi a következő alias létrehozását: ```php use Nette\Utils\Helpers; @@ -21,7 +21,7 @@ use Nette\Utils\Helpers; capture(callable $cb): string .[method] --------------------------------------- -Végrehajt egy visszahívást, és a rögzített kimenetet stringként adja vissza. +Végrehajtja a callbacket és visszaadja az elfogott kimenetet stringként. ```php $res = Helpers::capture(function () use ($template) { @@ -33,7 +33,7 @@ $res = Helpers::capture(function () use ($template) { clamp(int|float $value, int|float $min, int|float $max): int|float .[method] ---------------------------------------------------------------------------- -Visszaadja a min és max tartományba szorított értéket. +Korlátozza az értéket a megadott inkluzív `min` és `max` tartományba. ```php Helpers::clamp($level, 0, 255); @@ -43,8 +43,7 @@ Helpers::clamp($level, 0, 255); compare(mixed $left, string $operator, mixed $right): bool .[method] -------------------------------------------------------------------- -Összehasonlít két értéket a PHP-hoz hasonlóan. Megkülönbözteti a `>`, `>=`, `<`, `<=`, `=`, `==`, `===`, `!=`, `!==`, `<>` operátorokat. -A függvény olyan helyzetekben hasznos, amikor az operátor változó. +Összehasonlít két értéket ugyanúgy, ahogy a PHP teszi. Megkülönbözteti a `>`, `>=`, `<`, `<=`, `=`, `==`, `===`, `!=`, `!==`, `<>` operátorokat. A funkció hasznos olyan helyzetekben, amikor az operátor változó. ```php Helpers::compare(10, '<', 20); // true @@ -54,7 +53,7 @@ Helpers::compare(10, '<', 20); // true falseToNull(mixed $value): mixed .[method] ------------------------------------------ -Átváltja a `false` -t `null`-re, más értékeket nem változtat. +A `false`-t `null`-ra konvertálja, más értékeket nem változtat. ```php Helpers::falseToNull(false); // null @@ -65,7 +64,7 @@ Helpers::falseToNull(123); // 123 getLastError(): string .[method] -------------------------------- -Visszaadja az utoljára előfordult PHP-hibát, vagy egy üres karakterláncot, ha nem történt hiba. A `error_get_last()`-től eltérően nem befolyásolja a `html_errors` PHP-direktíva, és mindig szöveget ad vissza, nem HTML-t. +Visszaadja az utolsó PHP hibát vagy üres stringet, ha nem történt hiba. Az `error_get_last()`-tal szemben nem befolyásolja a `html_errors` PHP direktíva, és mindig szöveget ad vissza, nem HTML-t. ```php Helpers::getLastError(); @@ -75,13 +74,13 @@ Helpers::getLastError(); getSuggestion(string[] $possibilities, string $value): ?string .[method] ------------------------------------------------------------------------ -Olyan karakterláncot keres a `$possibilities` címből, amely leginkább hasonlít a `$value` címhez, de nem azonos vele. Csak 8 bites kódolásokat támogat. +A felkínált `$possibilities` lehetőségek közül keres egy stringet, amely a leginkább hasonlít a `$value`-ra, de nem azonos vele. Csak 8 bites kódolást támogat. -Hasznos, ha egy bizonyos opció nem érvényes, és egy hasonlót akarunk javasolni a felhasználónak (de másikat, tehát ugyanazt a karakterláncot figyelmen kívül hagyjuk). Ily módon a Nette a `did you mean ...?` üzeneteket hozza létre. +Hasznos abban az esetben, ha egy bizonyos opció érvénytelen, és szeretnénk a felhasználónak egy hasonlót javasolni (de mást, ezért az azonos stringet figyelmen kívül hagyja). Így hozza létre a Nette a `did you mean ...?` üzeneteket. ```php $items = ['foo', 'bar', 'baz']; Helpers::getSuggestion($items, 'fo'); // 'foo' Helpers::getSuggestion($items, 'barr'); // 'bar' -Helpers::getSuggestion($items, 'baz'); // 'bar', ne 'baz' +Helpers::getSuggestion($items, 'baz'); // 'bar', nem 'baz' ``` diff --git a/utils/hu/html-elements.texy b/utils/hu/html-elements.texy index 4b8eeb1770..c76dc1863c 100644 --- a/utils/hu/html-elements.texy +++ b/utils/hu/html-elements.texy @@ -2,15 +2,15 @@ HTML elemek *********** .[perex] -A [api:Nette\Utils\Html] osztály egy segédprogram a HTML kód generálásához, amely megakadályozza a Cross Site Scripting (XSS) sebezhetőséget. +Az [api:Nette\Utils\Html] osztály egy segéd a HTML kód generálásához, amely megakadályozza a Cross Site Scripting (XSS) sebezhetőség kialakulását. -Úgy működik, hogy az objektumai HTML elemeket reprezentálnak, paramétereiket beállítjuk, és hagyjuk őket renderelni: +Úgy működik, hogy objektumai HTML elemeket képviselnek, amelyeknek paramétereket állítunk be, és kirajzoltatjuk őket: ```php -$el = Html::el('img'); // elemet hoz létre +$el = Html::el('img'); // létrehozza az elemet $el->src = 'image.jpg'; // beállítja az src attribútumot -echo $el; // kiírja '' +echo $el; // kiírja: '' ``` Telepítés: @@ -19,29 +19,29 @@ Telepítés: composer require nette/utils ``` -Minden példa feltételezi, hogy a következő osztály alias van definiálva: +Minden példa feltételezi a következő alias létrehozását: ```php use Nette\Utils\Html; ``` -HTML-elem létrehozása .[#toc-creating-an-html-element] -====================================================== +HTML elem létrehozása +===================== -Az elem létrehozása a `Html::el()` módszerrel történik: +Elemet a `Html::el()` metódussal hozunk létre: ```php -$el = Html::el('img'); // elemet hoz létre +$el = Html::el('img'); // létrehozza az elemet ``` -A HTML-szintaxisban a név mellett más attribútumokat is megadhat: +A néven kívül megadhat más attribútumokat is HTML szintaxisban: ```php $el = Html::el('input type=text class="red important"'); ``` -Vagy asszociatív tömbként adja át őket a második paraméterhez: +Vagy átadhatja őket asszociatív tömbként a második paraméterben: ```php $el = Html::el('input', [ @@ -50,39 +50,39 @@ $el = Html::el('input', [ ]); ``` -Egy elem nevének megváltoztatása és visszaadása: +Az elem nevének megváltoztatása és lekérdezése: ```php $el->setName('img'); $el->getName(); // 'img' -$el->isEmpty(); // true, mivel üres elem +$el->isEmpty(); // true, mivel az egy üres elem ``` -HTML-attribútumok .[#toc-html-attributes] -========================================= +HTML attribútumok +================= -Az egyes HTML-attribútumokat háromféleképpen állíthatod be és kaphatod meg, rajtad áll, hogy melyik tetszik jobban. Az első a tulajdonságokon keresztül: +Az egyes HTML attribútumokat három módon módosíthatjuk és olvashatjuk, Öntől függ, melyik tetszik jobban. Az első a property-ken keresztül történik: ```php $el->src = 'image.jpg'; // beállítja az src attribútumot echo $el->src; // 'image.jpg' -unset($el->src); // az attribútum eltávolítása +unset($el->src); // törli az attribútumot // vagy $el->src = null; ``` -A második mód a metódusok hívása, amelyeket a tulajdonságok beállításával ellentétben láncszerűen egymáshoz kapcsolhatunk: +A második út a metódushívás, amelyek a property-k beállításával ellentétben láncolhatók: ```php $el = Html::el('img')->src('image.jpg')->alt('photo'); // photo -$el->alt(null); // eltávolítja az attribútumot. +$el->alt(null); // attribútum törlése ``` -A harmadik mód pedig a legbeszédesebb: +A harmadik mód a legbőbeszédűbb: ```php $el = Html::el('img') @@ -94,27 +94,27 @@ echo $el->getAttribute('src'); // 'image.jpg' $el->removeAttribute('alt'); ``` -Tömegesen, az attribútumokat a `addAttributes(array $attrs)` segítségével lehet beállítani, és a `removeAttributes(array $attrNames)` segítségével törölni. +Az attribútumokat tömegesen lehet beállítani az `addAttributes(array $attrs)` segítségével, és eltávolítani az `removeAttributes(array $attrNames)` segítségével. -Egy attribútum értékének nem kell csak egy karakterláncnak lennie, logikai attribútumokhoz logikai értékek is használhatók: +Az attribútum értéke nem csak string lehet, logikai értékeket is lehet használni logikai attribútumokhoz: ```php $checkbox = Html::el('input')->type('checkbox'); -$checkbox->checked = true; // +$checkbox->checked = true; // $checkbox->checked = false; // ``` -Egy attribútum lehet szóközökkel elválasztva felsorolt tokenek tömbje is, ami például CSS-osztályok esetében megfelelő: +Az attribútum lehet értékek tömbje is, amelyek szóközökkel elválasztva íródnak ki, ami például CSS osztályokhoz hasznos: ```php $el = Html::el('input'); $el->class[] = 'active'; -$el->class[] = null; // a null értéket figyelmen kívül hagyjuk. +$el->class[] = null; // a null figyelmen kívül van hagyva $el->class[] = 'top'; echo $el; // '' ``` -Alternatív megoldás az asszociatív tömb, ahol az értékek megmondják, hogy a kulcsot fel kell-e sorolni: +Alternatíva az asszociatív tömb, ahol az értékek megmondják, hogy a kulcsot ki kell-e írni: ```php $el = Html::el('input'); @@ -123,7 +123,7 @@ $el->class['top'] = false; echo $el; // '' ``` -A CSS-stílusok asszociatív tömbök formájában írhatók: +A CSS stílusokat asszociatív tömbök formájában lehet írni: ```php $el = Html::el('input'); @@ -132,7 +132,7 @@ $el->style['display'] = 'block'; echo $el; // '' ``` -Most a tulajdonságokat használtuk, de ugyanezt megtehetjük a metódusok használatával is: +Most property-ket használtunk, de ugyanez leírható metódusokkal is: ```php $el = Html::el('input'); @@ -141,7 +141,7 @@ $el->style('display', 'block'); echo $el; // '' ``` -Vagy akár a legbeszédesebb módon: +Vagy akár a legbőbeszédűbb módon is: ```php $el = Html::el('input'); @@ -150,7 +150,7 @@ $el->appendAttribute('style', 'display', 'block'); echo $el; // '' ``` -Még egy utolsó dolog: a `href()` módszer megkönnyítheti a lekérdezési paraméterek összeállítását egy URL-ben: +Még egy apróság a végére: a `href()` metódus megkönnyítheti a query paraméterek összeállítását az URL-ben: ```php echo Html::el('a')->href('index.php', [ @@ -161,19 +161,19 @@ echo Html::el('a')->href('index.php', [ ``` -Adatattribútumok .[#toc-data-attributes] ----------------------------------------- +Data attribútumok +----------------- -Az adatattribútumok speciális támogatással rendelkeznek. Mivel a nevük kötőjeleket tartalmaz, a tulajdonságokon és metódusokon keresztüli hozzáférés nem olyan elegáns, ezért van egy módszer: `data()`: +A data attribútumok speciális támogatást élveznek. Mivel nevük kötőjeleket tartalmaz, a property-ken és metódusokon keresztüli hozzáférés nem olyan elegáns, ezért létezik a `data()` metódus: ```php $el = Html::el('input'); -$el->{'data-max-size'} = '500x300'; // nem olyan elegáns -$el->data('max-size', '500x300'); // nagyon elegáns +$el->{'data-max-size'} = '500x300'; // nem annyira elegáns +$el->data('max-size', '500x300'); // elegáns echo $el; // '' ``` -Ha az adatattribútum értéke egy tömb, akkor az automatikusan JSON-ba szerializálódik: +Ha a data attribútum értéke tömb, automatikusan JSON-ba szerializálódik: ```php $el = Html::el('input'); @@ -182,10 +182,10 @@ echo $el; // '' ``` -Elem tartalma .[#toc-element-content] -===================================== +Elem tartalma +============= -Az elem belső tartalmát a `setHtml()` vagy a `setText()` metódusok határozzák meg. Az elsőt csak akkor használja, ha tudja, hogy megbízhatóan biztonságos HTML karakterláncot ad át a paraméterben. +Az elem belső tartalmát a `setHtml()` vagy `setText()` metódusokkal állítjuk be. Az elsőt csak abban az esetben használja, ha biztosan tudja, hogy a paraméterben megbízhatóan biztonságos HTML stringet ad át. ```php echo Html::el('span')->setHtml('hello
                                                                                                                            '); @@ -195,7 +195,7 @@ echo Html::el('span')->setText('10 < 20'); // '10 < 20' ``` -Ezzel szemben a belső tartalmat a `getHtml()` vagy a `getText()` metódusokkal kapjuk meg. A második eltávolítja a címkéket a HTML-kimenetből, és a HTML-elemeket karakterekké alakítja. +És fordítva, a belső tartalmat a `getHtml()` vagy `getText()` metódusokkal kapjuk meg. A második eltávolítja a HTML tageket a kimenetből, és a HTML entitásokat karakterekké konvertálja. ```php echo $el->getHtml(); // '10 < 20' @@ -203,10 +203,10 @@ echo $el->getText(); // '10 < 20' ``` -Gyermek csomópontok .[#toc-child-nodes] ---------------------------------------- +Gyermek csomópontok +------------------- -Egy elem belső tartalma lehet a gyermekelemek tömbje is. Ezek mindegyike lehet egy-egy karakterlánc vagy egy másik `Html` elem. Ezek beillesztése a `addHtml()` vagy a `addText()` segítségével történik: +Az elem belseje lehet gyermek (children) csomópontok tömbje is. Mindegyikük lehet string vagy egy másik `Html` elem. Az `addHtml()` vagy `addText()` segítségével illesztjük be őket: ```php $el = Html::el('span') @@ -216,16 +216,16 @@ $el = Html::el('span') // hello
                                                                                                                            10 < 20
                                                                                                                            ``` -Egy másik módja egy új `Html` csomópont létrehozásának és beszúrásának: +Egy másik mód új `Html` csomópont létrehozására és beillesztésére: ```php -$el = Html::el('ul') - ->create('li', ['class' => 'first']) - ->setText('hello'); -//
                                                                                                                            • hello
                                                                                                                            +$ul = Html::el('ul'); +$ul->create('li', ['class' => 'first']) + ->setText('első'); +//
                                                                                                                            • első
                                                                                                                            ``` -A csomópontokkal úgy dolgozhat, mintha tömbelemek lennének. Tehát az egyeseket szögletes zárójelek segítségével érhetjük el, a `count()` segítségével megszámolhatjuk őket, és iterálhatunk rajtuk: +A csomópontokkal ugyanúgy lehet dolgozni, mintha tömbről lenne szó. Azaz hozzáférni az egyes elemekhez szögletes zárójelekkel, megszámolni őket a `count()` segítségével, és iterálni rajtuk: ```php $el = Html::el('div'); @@ -238,20 +238,20 @@ foreach ($el as $child) { /* ... */ } echo count($el); // 2 ``` -Egy új csomópontot a `insert(?int $index, $child, bool $replace = false)` segítségével lehet egy adott pozícióba beilleszteni. Ha `$replace = false`, akkor a `$index` pozícióba illeszti be az elemet, a többit pedig áthelyezi. Ha `$index = null`, akkor egy elemet illeszt a végére. +Új csomópontot lehet egy konkrét helyre beilleszteni az `insert(?int $index, $child, bool $replace = false)` segítségével. Ha a `$replace = false`, beilleszti az elemet az `$index` pozícióra, és a többit eltolja. Ha az `$index = null`, hozzáadja az elemet a végére. ```php -// beilleszti az elemet az első pozícióba és előrébb lépteti a többit. +// beilleszti az elemet az első pozícióra és a többit eltolja $el->insert(0, Html::el('span')); ``` -Az összes csomópontot a `getChildren()` metódus adja vissza, és a `removeChildren()` metódus távolítja el. +Az összes csomópontot a `getChildren()` metódussal kapjuk meg, és eltávolítjuk őket a `removeChildren()` metódussal. -Dokumentumtöredék létrehozása .[#toc-creating-a-document-fragment] ------------------------------------------------------------------- +Dokumentum fragmentum létrehozása +--------------------------------- -Ha csomópontok tömbjével akarsz dolgozni, és nem érdekel a csomagoló elem, létrehozhatsz egy úgynevezett *dokumentumtöredéket*, ha az elem neve helyett a `null` címet adod meg: +Ha csomópontok tömbjével akarunk dolgozni, és nem érdekel minket a burkoló elem, létrehozhatunk egy ún. *dokumentum fragmentumot* `null` átadásával az elem neve helyett: ```php $el = Html::el(null) @@ -261,7 +261,7 @@ $el = Html::el(null) // hello
                                                                                                                            10 < 20
                                                                                                                            ``` -A `fromHtml()` és a `fromText()` metódusok gyorsabb módját kínálják a töredék létrehozásának: +Gyorsabb módot kínálnak a fragmentum létrehozására a `fromHtml()` és `fromText()` metódusok: ```php $el = Html::fromHtml('hello
                                                                                                                            '); @@ -272,15 +272,15 @@ echo $el; // '10 < 20' ``` -HTML kimenet generálása .[#toc-generating-html-output] -====================================================== +HTML kimenet generálása +======================= -A HTML-elemek generálásának legegyszerűbb módja a `echo` használata, vagy egy objektumnak a `(string)` címre történő átvitele. A nyitó vagy záró címkéket és attribútumokat külön is ki lehet nyomtatni: +A legegyszerűbb módja egy HTML elem kiírásának az `echo` használata vagy az objektum `(string)`-gé típuskonvertálása. Külön is kiírhatók a nyitó vagy záró tagek és attribútumok: ```php $el = Html::el('div class=header')->setText('hello'); -echo $el; // '
                                                                                                                            ' +echo $el; // '
                                                                                                                            hello
                                                                                                                            ' $s = (string) $el; // '
                                                                                                                            hello
                                                                                                                            ' $s = $el->toHtml(); // '
                                                                                                                            hello
                                                                                                                            ' $s = $el->toText(); // 'hello' @@ -289,7 +289,7 @@ echo $el->endTag(); // '' echo $el->attributes(); // 'class="header"' ``` -Fontos funkció a [Cross Site Scripting (XSS |nette:glossary#cross-site-scripting-xss]) elleni automatikus védelem. Minden attribútumérték vagy a `setText()` vagy a `addText()` használatával beillesztett tartalom megbízhatóan elkerüli az escapet: +Fontos jellemző az automatikus védelem a [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS] ellen. Minden attribútumérték vagy a `setText()` vagy `addText()` keresztül beillesztett tartalom megbízhatóan escapelve van: ```php echo Html::el('div') @@ -300,17 +300,17 @@ echo Html::el('div') ``` -Átalakítás HTML ↔ Szöveg .[#toc-conversion-html-text] -===================================================== +HTML ↔ Szöveg konverzió +======================= -A `htmlToText()` statikus módszerrel a HTML-t szöveggé alakíthatja: +HTML szöveggé alakításához használhatja a `htmlToText()` statikus metódust: ```php echo Html::htmlToText('One & Two'); // 'One & Two' ``` -HtmlStringable .[#toc-htmlstringable] -===================================== +HtmlStringable +============== -A `Nette\Utils\Html` objektum megvalósítja a `Nette\HtmlStringable` interfészt, amelyet például a Latte vagy az űrlapok használnak azon objektumok megkülönböztetésére, amelyek rendelkeznek egy olyan `__toString()` metódussal, amely HTML-kódot ad vissza. Így nem történik kettős eszkábálás, ha például a sablonban az objektumot a `{$el}` segítségével nyomtatjuk ki. +A `Nette\Utils\Html` objektum implementálja a `Nette\HtmlStringable` interfészt, amellyel például a Latte vagy az űrlapok megkülönböztetik azokat az objektumokat, amelyek rendelkeznek a `__toString()` metódussal, amely HTML kódot ad vissza. Így nem történik dupla escapelés, ha például az objektumot kiírjuk a sablonban a `{$el}` segítségével. diff --git a/utils/hu/images.texy b/utils/hu/images.texy index f267d98de0..b4d7f5b17f 100644 --- a/utils/hu/images.texy +++ b/utils/hu/images.texy @@ -1,11 +1,11 @@ -Képfunkciók -*********** +Képekkel való munka +******************* .[perex] -A [api:Nette\Utils\Image] osztály leegyszerűsíti a képmanipulációt, például a méretváltoztatást, a képkivágást, az élesítést, a rajzolást vagy több kép összevonását. +A [api:Nette\Utils\Image] osztály leegyszerűsíti a képekkel való manipulációt, mint például a méretváltoztatás, vágás, élesítés, rajzolás vagy több kép összefűzése. -A PHP széleskörű függvénykészlettel rendelkezik a képek manipulálásához. De az API nem túl szép. Nem lenne egy Neat Framework, ha egy szexi API-val állna elő. +A PHP kiterjedt funkciókészlettel rendelkezik a képek manipulálására. De az API-juk nem túl kényelmes. Nem lenne a Nette Framework, ha nem jönne elő egy szexi API-val. Telepítés: @@ -13,26 +13,28 @@ Telepítés: composer require nette/utils ``` -A következő példák feltételezik, hogy a következő osztály alias van definiálva: +Minden példa feltételezi a létrehozott aliast: ```php use Nette\Utils\Image; +use Nette\Utils\ImageColor; +use Nette\Utils\ImageType; ``` -Kép létrehozása .[#toc-creating-an-image] -========================================= +Kép létrehozása +=============== -Létrehozunk egy új színes képet, például 100×200-as méretben: +Létrehozunk egy új true color képet, például 100×200 méretekkel: ```php $image = Image::fromBlank(100, 200); ``` -Opcionálisan megadhat egy háttérszínt (alapértelmezett a fekete): +Opcionálisan megadhatjuk a háttérszínt (az alapértelmezett a fekete): ```php -$image = Image::fromBlank(100, 200, Image::rgb(125, 0, 0)); +$image = Image::fromBlank(100, 200, ImageColor::rgb(125, 0, 0)); ``` Vagy betöltjük a képet egy fájlból: @@ -41,129 +43,138 @@ Vagy betöltjük a képet egy fájlból: $image = Image::fromFile('nette.jpg'); ``` -A támogatott formátumok a JPEG, PNG, GIF, WebP, AVIF és BMP, de a PHP verziójának támogatnia kell ezeket (ellenőrizze a `phpinfo()`, GD szakaszban). Az animációk nem támogatottak. -Fel kell ismernie a képformátumot betöltéskor? A módszer a formátumot adja vissza a második paraméterben: +Kép mentése +=========== + +A képet el lehet menteni egy fájlba: ```php -$image = Image::fromFile('nette.jpg', $type); -// $type az Image::JPEG, Image::PNG, Image::GIF, Image::WEBP, Image::AVIF vagy Image::BMP. +$image->save('resampled.jpg'); ``` -Csak a kép betöltése nélküli felismerést végzi a `Image::detectTypeFromFile()`. +Megadhatjuk a tömörítés minőségét 0..100 tartományban JPEG (alapértelmezett 85), WEBP (alapértelmezett 80) és AVIF (alapértelmezett 30) esetén, és 0..9 tartományban PNG (alapértelmezett 9) esetén: +```php +$image->save('resampled.jpg', 80); // JPEG, 80% minőség +``` -A kép mentése .[#toc-save-the-image] -==================================== - -A kép elmenthető egy fájlba: +Ha a fájlkiterjesztésből nem egyértelmű a formátum, megadhatjuk [konstanssal |#Formátumok]: ```php -$image->save('resampled.jpg'); +$image->save('resampled.tmp', null, ImageType::JPEG); ``` -A tömörítési minőséget a JPEG (alapértelmezett 85), WEBP (alapértelmezett 80) és AVIF (alapértelmezett 30) esetében 0..100, PNG (alapértelmezett 9) esetében pedig 0..9 tartományban adhatjuk meg: +A képet lemez helyett egy változóba is írhatjuk: ```php -$image->save('resampled.jpg', 80); // JPEG, minőség 80% +$data = $image->toString(ImageType::JPEG, 80); // JPEG, 80% minőség ``` -Ha a formátum nem egyértelmű a fájlkiterjesztésből, akkor a `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` és `Image::BMP` konstansok valamelyikével adható meg: +vagy közvetlenül elküldhetjük a böngészőbe a megfelelő `Content-Type` HTTP fejléccel: ```php -$image->save('resampled.tmp', null, Image::JPEG); +// elküldi a Content-Type: image/png fejlécet +$image->send(ImageType::PNG); ``` -A képet lemez helyett egy változóba is ki lehet írni: + +Formátumok +========== + +A támogatott formátumok a JPEG, PNG, GIF, WebP, AVIF és BMP, azonban a PHP verziójának is támogatnia kell őket, amit a [#isTypeSupported()] funkcióval ellenőrizhetünk. Az animációk nem támogatottak. + +A formátumot az `ImageType::JPEG`, `ImageType::PNG`, `ImageType::GIF`, `ImageType::WEBP`, `ImageType::AVIF` és `ImageType::BMP` konstansok képviselik. ```php -$data = $image->toString(Image::JPEG, 80); // JPEG, minőség 80%. +$supported = Image::isTypeSupported(ImageType::JPEG); ``` -vagy közvetlenül a böngészőnek a megfelelő HTTP fejléccel `Content-Type`: +Szüksége van a kép formátumának észlelésére betöltéskor? A metódus a második paraméterben adja vissza: ```php -// fejlécet küld Content-Type: image/png -$image->send(Image::PNG); +$image = Image::fromFile('nette.jpg', $type); ``` +Az észlelést magát a kép betöltése nélkül az `Image::detectTypeFromFile()` végzi. + -Kép átméretezése .[#toc-image-resize] -===================================== +Méretváltoztatás +================ -Gyakori művelet a kép átméretezése. Az aktuális méreteket a `getWidth()` és a `getHeight()` metódusok adják vissza. +Gyakori művelet a kép méreteinek megváltoztatása. Az aktuális méreteket a `getWidth()` és `getHeight()` metódusok adják vissza. -Az átméretezéshez a `resize()` módszer használható. Ez egy példa az arányos méretváltoztatásra úgy, hogy az nem haladja meg az 500×300 pixelt (vagy a szélesség lesz pontosan 500px, vagy a magasság lesz pontosan 300px, az egyik dimenzió a képarány megtartása érdekében kerül kiszámításra): +A változtatáshoz a `resize()` metódus szolgál. Példa az arányos méretváltoztatásra úgy, hogy ne haladja meg az 500x300 pixeles méreteket (vagy a szélesség pontosan 500 px lesz, vagy a magasság pontosan 300 px, az egyik méret úgy kerül kiszámításra, hogy megmaradjon az oldalarány): ```php $image->resize(500, 300); ``` -Lehetőség van arra, hogy csak egy dimenziót állítson be, és a második dimenzió kiszámításra kerül: +Lehetséges csak egy méretet megadni, a másik pedig kiszámításra kerül: ```php -$image->resize(500, null); // szélesség 500px, magasság auto +$image->resize(500, null); // szélesség 500px, a magasság kiszámításra kerül -$image->resize(null, 300); // szélesség auto, magasság 300px +$image->resize(null, 300); // a szélesség kiszámításra kerül, magasság 300px ``` -Bármelyik dimenzió megadható százalékban: +Bármelyik méretet százalékban is meg lehet adni: ```php $image->resize('75%', 300); // 75 % × 300px ``` -A `resize` viselkedését a következő jelzőkkel lehet befolyásolni. A `Image::Stretch` kivételével mindegyik megőrzi a képarányt. +A `resize` viselkedését a következő jelzőkkel lehet befolyásolni. Az `Image::Stretch` kivételével mindegyik megőrzi az oldalarányt. |--------------------------------------------------------------------------------------- -| Zászló | Leírás +| Jelző | Leírás |--------------------------------------------------------------------------------------- -| `Image::OrSmaller` (alapértelmezett) | a kapott méretek kisebbek vagy egyenlőek lesznek a megadottakkal. -| `Image::OrBigger` | kitölti a célterületet, és esetleg egy irányba kiterjeszti azt. -| `Image::Cover` | kitölti az egész területet, és kivágja a túlnyúló részt. -| `Image::ShrinkOnly` | csak lefelé skálázódik (nem bővíti ki a kis képet) -| `Image::Stretch` | nem tartja meg a képarányt. +| `Image::OrSmaller` (alapértelmezett) | az eredményül kapott méretek kisebbek vagy egyenlőek lesznek a kívánt méretekkel +| `Image::OrBigger` | kitölti (és esetleg túllépi az egyik méretben) a célterületet +| `Image::Cover` | kitölti a célterületet és levágja azt, ami túllóg +| `Image::ShrinkOnly` | csak kicsinyítés (megakadályozza a kis kép nyújtását) +| `Image::Stretch` | ne tartsa meg az oldalarányt -A zászlók a függvény harmadik argumentumaként kerülnek átadásra: +A jelzőket a funkció harmadik argumentumaként adjuk meg: ```php $image->resize(500, 300, Image::OrBigger); ``` -A zászlók kombinálhatók: +A jelzők kombinálhatók: ```php $image->resize(500, 300, Image::ShrinkOnly | Image::Stretch); ``` -A képek függőlegesen vagy vízszintesen megfordíthatók az egyik dimenzió (vagy mindkettő) negatív számként történő megadásával: +A képeket függőlegesen vagy vízszintesen lehet tükrözni úgy, hogy az egyik méretet (esetleg mindkettőt) negatív számként adjuk meg: ```php -$flipped = $image->resize(null, '-100%'); // Függőlegesen átfordítva +$flipped = $image->resize(null, '-100%'); // függőleges tükrözés -$flipped = $image->resize('-100%', '-100%'); // 180°-kal elforgatni +$flipped = $image->resize('-100%', '-100%'); // 180°-os forgatás -$flipped = $image->resize(-125, 500); // átméretezés és átfordítás vízszintesen +$flipped = $image->resize(-125, 500); // átméretezés és vízszintes tükrözés ``` -A kép kicsinyítése után élesítéssel javíthatunk rajta: +A kép kicsinyítése után finom élesítéssel javítható a megjelenése: ```php $image->sharpen(); ``` -Vágás .[#toc-cropping] -====================== +Vágás +===== -A `crop()` módszer a termesztésre szolgál: +A vágáshoz a `crop()` metódus szolgál: ```php $image->crop($left, $top, $width, $height); ``` -A `resize()` oldalhoz hasonlóan minden érték százalékban is megadható. A `$left` és a `$top` százalékos értékeit a fennmaradó helyből számítják ki, hasonlóan a `background-position` CSS-tulajdonsághoz: +Hasonlóan a `resize()`-hoz, minden érték megadható százalékban. A `$left` és `$top` százalékai a fennmaradó helyből számítódnak, hasonlóan a CSS `background-position` tulajdonságához: ```php $image->crop('100%', '50%', '80%', '80%'); @@ -172,329 +183,359 @@ $image->crop('100%', '50%', '80%', '80%'); [* crop.svg *] -A képet automatikusan is lehet vágni, pl. fekete élek vágása: +A képet automatikusan is le lehet vágni, például a fekete szegélyek levágása: ```php $image->cropAuto(IMG_CROP_BLACK); ``` -A `cropAuto()` metódus a `imagecropauto()` függvény objektumkapszulázása, további információkért lásd [a dokumentációját |https://www.php.net/manual/en/function.imagecropauto]. +A `cropAuto()` metódus az `imagecropauto()` funkció objektumorientált helyettesítője, [dokumentációjában |https://www.php.net/manual/en/function.imagecropauto] további információkat talál. + +Színek .{data-version:4.0.2} +============================ -Rajzolás és szerkesztés .[#toc-drawing-and-editing] -=================================================== +Az `ImageColor::rgb()` metódus lehetővé teszi a szín meghatározását a piros, zöld és kék (RGB) értékek segítségével. Opcionálisan megadhatja az átlátszósági értéket is 0 (teljesen átlátszó) és 1 (teljesen átlátszatlan) között, tehát ugyanúgy, mint a CSS-ben. + +```php +$color = ImageColor::rgb(255, 0, 0); // Piros +$transparentBlue = ImageColor::rgb(0, 0, 255, 0.5); // Félig átlátszó kék +``` -Rajzolhatsz, írhatsz, használhatod az összes PHP függvényt a képekkel való munkához, mint például az [imagefilledellipse() |https://www.php.net/manual/en/function.imagefilledellipse.php], de objektum stílusban: +Az `ImageColor::hex()` metódus lehetővé teszi a szín meghatározását hexadecimális formátumban, hasonlóan a CSS-hez. Támogatja a `#rgb`, `#rrggbb`, `#rgba` és `#rrggbbaa` formátumokat: ```php -$image->filledEllipse($cx, $cy, $width, $height, Image::rgb(255, 0, 0, 63)); +$color = ImageColor::hex("#F00"); // Piros +$transparentGreen = ImageColor::hex("#00FF0080"); // Félig átlátszó zöld ``` -Lásd [a módszerek áttekintését |#Overview of Methods]. +A színeket más metódusokban is lehet használni, mint például az `ellipse()`, `fill()` stb. -Több kép egyesítése .[#toc-merge-multiple-images] -================================================= +Rajzolás és szerkesztés +======================= -Könnyedén elhelyezhet egy másik képet a képbe: +Rajzolhatsz, írhatsz, de a leveleket ne tépd le. Rendelkezésedre állnak a PHP összes képkezelő funkciói, lásd [#Metódusok áttekintése], de objektumorientált köntösben: + +```php +$image->filledEllipse($centerX, $centerY, $width, $height, ImageColor::rgb(255, 0, 0)); +``` + +Mivel a PHP téglalaprajzoló funkciói a koordináták megadása miatt nem praktikusak, az `Image` osztály helyettesítőket kínál a [#rectangleWH()] és [#filledRectangleWH()] funkciók formájában. + + +Több kép összefűzése +==================== + +Egy képbe könnyen beilleszthető egy másik kép: ```php $logo = Image::fromFile('logo.png'); -$blank = Image::fromBlank(320, 240, Image::rgb(52, 132, 210)); +$blank = Image::fromBlank(320, 240, ImageColor::rgb(52, 132, 210)); -// a koordináták százalékban is megadhatók -$blank->place($logo, '80%', '80%'); // közel a jobb alsó sarokhoz +// a koordinátákat ismét százalékban lehet megadni +$blank->place($logo, '80%', '80%'); // beillesztjük a jobb alsó sarok közelébe ``` -Beillesztéskor az alfa-csatornát tiszteletben tartjuk, emellett befolyásolhatjuk a beillesztett kép átlátszóságát (ún. vízjelet hozunk létre): +A beillesztéskor figyelembe veszi az alfakanált, ráadásul befolyásolhatjuk a beillesztett kép átlátszóságát (létrehozunk egy ún. vízjelet): ```php -$blank->place($image, '80%', '80%', 25); // az átlátszóság 25 %-os +$blank->place($image, '80%', '80%', 25); // az átlátszóság 25 % ``` -Egy ilyen API-t tényleg öröm használni, nem igaz? +Egy ilyen API-t valóban öröm használni! -A módszerek áttekintése .[#toc-overview-of-methods] -=================================================== +Metódusok áttekintése +===================== -static fromBlank(int $width, int $height, array $color=null): Image .[method] ------------------------------------------------------------------------------ -Új, valós színű képet hoz létre a megadott méretekkel. Az alapértelmezett szín a fekete. +static fromBlank(int $width, int $height, ?ImageColor $color=null): Image .[method] +----------------------------------------------------------------------------------- +Létrehoz egy új true color képet a megadott méretekkel. Az alapértelmezett szín a fekete. static fromFile(string $file, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------- -Beolvas egy képet egy fájlból, és visszaadja a típusát a `$detectedFormat` címen. A támogatott típusok: `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` és `Image::BMP`. +Betölt egy képet egy fájlból és visszaadja a [típusát |#Formátumok] a `$detectedFormat`-ban. static fromString(string $s, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------ -Beolvas egy képet egy karakterláncból, és visszaadja a típusát a `$detectedFormat` címen. A támogatott típusok: `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` és `Image::BMP`. +Betölt egy képet egy stringből és visszaadja a [típusát |#Formátumok] a `$detectedFormat`-ban. -static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method] ---------------------------------------------------------------------------------- -Létrehoz egy színt, amely más módszerekben, például a `ellipse()`, `fill()`, stb. használható. +static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method][deprecated] +--------------------------------------------------------------------------------------------- +Ezt a funkciót az `ImageColor` osztály váltotta fel, lásd [#színek]. static typeToExtension(int $type): string .[method] --------------------------------------------------- -Visszaadja az adott `Image::XXX` konstans fájlkiterjesztését. +Visszaadja a fájlkiterjesztést a megadott [típushoz |#Formátumok]. static typeToMimeType(int $type): string .[method] -------------------------------------------------- -Visszaadja a megadott `Image::XXX` konstans mime típusát. +Visszaadja a MIME típust a megadott [típushoz |#Formátumok]. static extensionToType(string $extension): int .[method] -------------------------------------------------------- -Visszaadja a kép típusát a `Image::XXX` konstansként a fájlkiterjesztésnek megfelelően. +Visszaadja a kép [típusát |#Formátumok] a fájlkiterjesztés alapján. static detectTypeFromFile(string $file, int &$width=null, int &$height=null): ?int .[method] -------------------------------------------------------------------------------------------- -Visszaadja a képfájl típusát a `Image::XXX` konstansként, valamint a `$width` és `$height` paraméterekben a méreteit is. +Visszaadja a kép [típusát |#Formátumok] és a `$width` és `$height` paraméterekben a méreteit is. static detectTypeFromString(string $s, int &$width=null, int &$height=null): ?int .[method] ------------------------------------------------------------------------------------------- -Visszaadja a kép típusát a stringből a `Image::XXX` konstansként, és a `$width` és `$height` paraméterekben a méreteit is. +Visszaadja a kép [típusát |#Formátumok] egy stringből és a `$width` és `$height` paraméterekben a méreteit is. -affine(array $affine, array $clip=null): Image .[method] --------------------------------------------------------- -Visszaad egy képet, amely az affin transzformált src képet tartalmazza, egy opcionális vágási terület használatával. ([tovább |https://www.php.net/manual/en/function.imageaffine]). +static isTypeSupported(int $type): bool .[method] +------------------------------------------------- +Ellenőrzi, hogy a megadott kép[típus |#Formátumok] támogatott-e. + + +static getSupportedTypes(): array .[method]{data-version:4.0.4} +--------------------------------------------------------------- +Visszaadja a támogatott kép[típusok |#Formátumok] tömbjét. + + +static calculateTextBox(string $text, string $fontFile, float $size, float $angle=0, array $options=[]): array .[method] +------------------------------------------------------------------------------------------------------------------------ +Kiszámítja egy téglalap méreteit, amely egy adott betűtípusban és méretben lévő szöveget foglal magába. Asszociatív tömböt ad vissza, amely tartalmazza a `left`, `top`, `width`, `height` kulcsokat. A bal margó negatív is lehet, ha a szöveg bal oldali alávágással kezdődik. + + +affine(array $affine, ?array $clip=null): Image .[method] +--------------------------------------------------------- +Visszaad egy képet, amely a forráskép affin transzformált képét tartalmazza egy opcionális vágási területtel. ([több |https://www.php.net/manual/en/function.imageaffine]). affineMatrixConcat(array $m1, array $m2): array .[method] --------------------------------------------------------- -Két affin transzformációs mátrix összekapcsolását adja vissza, ami akkor hasznos, ha több transzformációt kell alkalmazni ugyanarra a képre egy menetben. ([tovább |https://www.php.net/manual/en/function.imageaffinematrixconcat]) +Visszaadja két affin transzformációs mátrix összefűzését, ami hasznos, ha több transzformációt kell egyszerre alkalmazni ugyanarra a képre. ([több |https://www.php.net/manual/en/function.imageaffinematrixconcat]) -affineMatrixGet(int $type, mixed $options=null): array .[method] ----------------------------------------------------------------- -Visszaad egy affin transzformációs mátrixot. ([tovább |https://www.php.net/manual/en/function.imageaffinematrixget]) +affineMatrixGet(int $type, ?mixed $options=null): array .[method] +----------------------------------------------------------------- +Visszaad egy affin transzformációs mátrixot. ([több |https://www.php.net/manual/en/function.imageaffinematrixget]) alphaBlending(bool $on): void .[method] --------------------------------------- -Két különböző rajzolási módot tesz lehetővé a truecolor képeken. Keverési módban az összes rajzolási funkcióhoz, például a `setPixel()` oldalhoz megadott szín alfa-csatorna komponense határozza meg, hogy az alapszínből mennyi látszódjon át. Ennek eredményeképpen a program automatikusan összekeveri az adott ponton meglévő színt a rajz színével, és az eredményt a képen tárolja. Az eredményül kapott képpont átlátszatlan. Nem keverési módban a rajz színe szó szerint az alfacsatorna információival együtt másolódik, és a célpixel helyébe lép. A palettás képekre való rajzoláskor a blending mód nem érhető el. ([tovább |https://www.php.net/manual/en/function.imagealphablending]) +Két különböző rajzolási módot tesz lehetővé truecolor képeken. Keverési módban a szín alfa csatornájának komponense, amelyet minden rajzolási funkcióban, például a `setPixel()`-ben használnak, meghatározza, hogy az alapul szolgáló szín milyen mértékben látszódjon át. Ennek eredményeként az adott ponton a meglévő szín automatikusan keveredik a rajzolt színnel, és az eredmény a képbe kerül mentésre. Az eredményül kapott pixel átlátszatlan. Nem keverési módban a rajzolt szín szó szerint másolódik az alfa csatorna információival együtt, és felülírja a cél pixelt. A keverési mód nem érhető el palettás képekre történő rajzoláskor. ([több |https://www.php.net/manual/en/function.imagealphablending]) antialias(bool $on): void .[method] ----------------------------------- -Aktiválja a gyors rajzolás antializált módszereit a vonalak és a vezetékes sokszögek esetében. Nem támogatja az alfa komponenseket. Közvetlen keverési művelettel működik. Csak színhű képekkel működik. +Aktiválja a simított vonalak és sokszögek rajzolását. Nem támogatja az alfa csatornákat. Csak truecolor képekkel működik. -Az antialiased primitívek átlátszó háttérszínnel történő használata nem várt eredményekkel járhat. A keverési módszer a háttérszínt ugyanúgy használja, mint bármely más színt. Az alfa komponens támogatás hiánya nem teszi lehetővé az alfa alapú antialiasing módszert. ([tovább |https://www.php.net/manual/en/function.imageantialias]) +Az élsimított primitívek használata átlátszó háttérszínnel váratlan eredményekhez vezethet. A keverési módszer a háttérszínt ugyanúgy használja, mint az összes többi színt. ([több |https://www.php.net/manual/en/function.imageantialias]) -arc(int $x, int $y, int $w, int $h, int $start, int $end, int $color): void .[method] -------------------------------------------------------------------------------------- -A megadott koordináták középpontjában lévő körív rajzolása. ([tovább |https://www.php.net/manual/en/function.imagearc]) - - -char(int $font, int $x, int $y, string $char, int $color): void .[method] -------------------------------------------------------------------------- -A `$char` első karakterét a képen a `$x`,`$y` (a bal felső rész 0, 0) színnel rajzolja meg a `$color` színnel a első karakterét. ([tovább |https://www.php.net/manual/en/function.imagechar]) - - -charUp(int $font, int $x, int $y, string $char, int $color): void .[method] ---------------------------------------------------------------------------- -A `$char` karaktert függőlegesen rajzolja a megadott koordinátán a megadott képen. ([tovább |https://www.php.net/manual/en/function.imagecharup]) +arc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color): void .[method] +--------------------------------------------------------------------------------------------------------------------------- +Rajzol egy körívet a megadott koordinátákon középponttal. ([több |https://www.php.net/manual/en/function.imagearc]) colorAllocate(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------- -Visszaad egy színazonosítót, amely a megadott RGB-komponensekből álló színt reprezentálja. Minden egyes, a képen használni kívánt szín létrehozásához meg kell hívni. ([tovább |https://www.php.net/manual/en/function.imagecolorallocate]) +Visszaad egy színazonosítót, amely a megadott RGB komponensekből álló színt képviseli. Minden olyan szín létrehozásához meg kell hívni, amelyet a képben használni kívánnak. ([több |https://www.php.net/manual/en/function.imagecolorallocate]) colorAllocateAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ------------------------------------------------------------------------------ -Ugyanúgy viselkedik, mint a `colorAllocate()`, kiegészítve a `$alpha` átlátszósági paraméterrel. ([tovább |https://www.php.net/manual/en/function.imagecolorallocatealpha]) +Ugyanúgy viselkedik, mint a `colorAllocate()`, az átlátszósági paraméter `$alpha` hozzáadásával. ([több |https://www.php.net/manual/en/function.imagecolorallocatealpha]) colorAt(int $x, int $y): int .[method] -------------------------------------- -Visszaadja a kép megadott helyén lévő pixel színének indexét. Ha a kép valódi színű kép, ez a függvény egész számként adja vissza az adott pixel RGB-értékét. A biteltolás és a maszkolás segítségével hozzáférhet a különböző piros, zöld és kék komponens értékekhez: ([tovább |https://www.php.net/manual/en/function.imagecolorat]) +Visszaadja a kép megadott helyén lévő pixel színindexét. Ha a kép truecolor, ez a funkció a pixel RGB értékét adja vissza egész számként. Használjon biteltolást és bitmaszkolást a piros, zöld és kék komponensek különálló értékeinek eléréséhez: ([több |https://www.php.net/manual/en/function.imagecolorat]) colorClosest(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------ -Visszaadja a kép palettáján annak a színnek az indexét, amelyik a legközelebb áll a megadott RGB-értékhez. A kívánt szín és a paletta egyes színei közötti "távolság" úgy kerül kiszámításra, mintha az RGB-értékek a háromdimenziós tér pontjai lennének. ([tovább |https://www.php.net/manual/en/function.imagecolorclosest]) +Visszaadja a kép palettájában lévő szín indexét, amely a „legközelebb” van a megadott RGB értékhez. A kívánt szín és a paletta minden színe közötti „távolság” úgy kerül kiszámításra, mintha az RGB értékek pontokat jelölnének egy háromdimenziós térben. ([több |https://www.php.net/manual/en/function.imagecolorclosest]) colorClosestAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ----------------------------------------------------------------------------- -Visszaadja a kép palettáján annak a színnek az indexét, amelyik a legközelebb áll a megadott RGB-értékhez és a `$alpha` szinthez. ([tovább |https://www.php.net/manual/en/function.imagecolorclosestalpha]) +Visszaadja a kép palettájában lévő szín indexét, amely a „legközelebb” van a megadott RGB értékhez és `$alpha` szinthez. ([több |https://www.php.net/manual/en/function.imagecolorclosestalpha]) colorClosestHWB(int $red, int $green, int $blue): int .[method] --------------------------------------------------------------- -Annak a színnek az indexét kapja meg, amelynek árnyalata, fehérsége és feketesége a legközelebb áll az adott színhez. ([tovább |https://www.php.net/manual/en/function.imagecolorclosesthwb]) +Megszerzi annak a színnek az indexét, amelynek árnyalata, fehérsége és feketesége a legközelebb áll a megadott színhez. ([több |https://www.php.net/manual/en/function.imagecolorclosesthwb]) colorDeallocate(int $color): void .[method] ------------------------------------------- -Feloldja a korábban a `colorAllocate()` vagy a `colorAllocateAlpha()` segítségével kiosztott színt. ([tovább |https://www.php.net/manual/en/function.imagecolordeallocate]) +Deallokál egy korábban a `colorAllocate()` vagy `colorAllocateAlpha()` segítségével lefoglalt színt. ([több |https://www.php.net/manual/en/function.imagecolordeallocate]) colorExact(int $red, int $green, int $blue): int .[method] ---------------------------------------------------------- -Visszaadja a megadott szín indexét a kép palettáján. ([tovább |https://www.php.net/manual/en/function.imagecolorexact]) +Visszaadja a megadott szín indexét a kép palettájában. ([több |https://www.php.net/manual/en/function.imagecolorexact]) colorExactAlpha(int $red, int $green, int $blue, int $alpha): int .[method] --------------------------------------------------------------------------- -Visszaadja a megadott szín+alfa indexét a kép palettáján. ([tovább |https://www.php.net/manual/en/function.imagecolorexactalpha]) +Visszaadja a megadott szín + alfa indexét a kép palettájában. ([több |https://www.php.net/manual/en/function.imagecolorexactalpha]) colorMatch(Image $image2): void .[method] ----------------------------------------- -A kép palettás változatának színei jobban megfelelnek a valódi színváltozatnak. ([tovább |https://www.php.net/manual/en/function.imagecolormatch]) +Hozzáigazítja a paletta színeit a második képhez. ([több |https://www.php.net/manual/en/function.imagecolormatch]) colorResolve(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------ -Visszaadja a kért szín szín indexét, vagy a pontos színt, vagy a legközelebbi lehetséges alternatívát. ([tovább |https://www.php.net/manual/en/function.imagecolorresolve]) +Visszaadja a kívánt szín színindexét, vagy a pontos színt, vagy a legközelebbi lehetséges alternatívát. ([több |https://www.php.net/manual/en/function.imagecolorresolve]) colorResolveAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ----------------------------------------------------------------------------- -Visszaadja a kért szín szín indexét, vagy a pontos színt, vagy a legközelebbi lehetséges alternatívát. ([tovább |https://www.php.net/manual/en/function.imagecolorresolvealpha]) +Visszaadja a kívánt szín színindexét, vagy a pontos színt, vagy a legközelebbi lehetséges alternatívát. ([több |https://www.php.net/manual/en/function.imagecolorresolvealpha]) colorSet(int $index, int $red, int $green, int $blue): void .[method] --------------------------------------------------------------------- -Ez a palettán a megadott indexet a megadott színre állítja. ([tovább |https://www.php.net/manual/en/function.imagecolorset]) +Beállítja a paletta megadott indexét a megadott színre. ([több |https://www.php.net/manual/en/function.imagecolorset]) colorsForIndex(int $index): array .[method] ------------------------------------------- -Megkapja a színt egy megadott indexhez. ([tovább |https://www.php.net/manual/en/function.imagecolorsforindex]) +Megszerzi a megadott index színét. ([több |https://www.php.net/manual/en/function.imagecolorsforindex]) colorsTotal(): int .[method] ---------------------------- -Visszaadja a színek számát egy képpalettán ([more |https://www.php.net/manual/en/function.imagecolorstotal]). +Visszaadja a képpaletta színeinek számát. ([több |https://www.php.net/manual/en/function.imagecolorstotal]) -colorTransparent(int $color=null): int .[method] ------------------------------------------------- -A kép átlátszó színének lekérdezése vagy beállítása. ([tovább |https://www.php.net/manual/en/function.imagecolortransparent]) +colorTransparent(?int $color=null): int .[method] +------------------------------------------------- +Megszerzi vagy beállítja az átlátszó színt a képben. ([több |https://www.php.net/manual/en/function.imagecolortransparent]) convolution(array $matrix, float $div, float $offset): void .[method] --------------------------------------------------------------------- -Egy konvolúciós mátrixot alkalmaz a képre a megadott együttható és eltolás segítségével. ([tovább |https://www.php.net/manual/en/function.imageconvolution]) +Alkalmaz egy konvolúciós mátrixot a képre, a megadott együtthatót és eltolást használva. ([több |https://www.php.net/manual/en/function.imageconvolution]) .[note] -Igényli a *Bundled GD kiterjesztést*, így nem biztos, hogy mindenhol működik. +A *Bundled GD extension* jelenlétét igényli, tehát nem biztos, hogy mindenhol működik. copy(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH): void .[method] -------------------------------------------------------------------------------------------------- -A `$src` egy részét a `$srcX`, `$srcY` koordinátáknál kezdődő képre másolja a `$srcW` szélességű és `$srcH` magasságú képre. A meghatározott rész a koordinátákra, a `$dstX` és a `$dstY` koordinátákra lesz másolva. ([tovább |https://www.php.net/manual/en/function.imagecopy]) +Másolja a `$src` egy részét a képre a `$srcX`, `$srcY` koordinátáktól kezdve `$srcW` szélességgel és `$srcH` magassággal. A definiált rész a `$dstX` és `$dstY` koordinátákra lesz másolva. ([több |https://www.php.net/manual/en/function.imagecopy]) copyMerge(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] --------------------------------------------------------------------------------------------------------------------- -A `$src` egy részét a `$srcX`, `$srcY` koordinátáknál kezdődő képre másolja a `$srcW` szélességű és `$srcH` magasságú képre. A meghatározott rész a koordinátákra, a `$dstX` és a `$dstY` koordinátákra lesz másolva. ([tovább |https://www.php.net/manual/en/function.imagecopymerge]) +Másolja a `$src` egy részét a képre a `$srcX`, `$srcY` koordinátáktól kezdve `$srcW` szélességgel és `$srcH` magassággal. A definiált rész a `$dstX` és `$dstY` koordinátákra lesz másolva. ([több |https://www.php.net/manual/en/function.imagecopymerge]) copyMergeGray(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] ------------------------------------------------------------------------------------------------------------------------- -A `$src` egy részét a `$srcX`, `$srcY` koordinátáknál kezdődő képre másolja a `$srcW` szélességű és `$srcH` magasságú képre. A meghatározott rész a koordinátákra, a `$dstX` és a `$dstY` koordinátákra lesz másolva. +Másolja a `$src` egy részét a képre a `$srcX`, `$srcY` koordinátáktól kezdve `$srcW` szélességgel és `$srcH` magassággal. A definiált rész a `$dstX` és `$dstY` koordinátákra lesz másolva. -Ez a funkció megegyezik a `copyMerge()` funkcióval, azzal a különbséggel, hogy az összevonás során megőrzi a forrás színárnyalatát azáltal, hogy a célpixeleket a másolási művelet előtt szürkeárnyalatúvá alakítja. ([tovább |https://www.php.net/manual/en/function.imagecopymergegray]) +Ez a funkció azonos a `copyMerge()` funkcióval, azzal a kivétellel, hogy az egyesítéskor megőrzi a forrás árnyalatát azáltal, hogy a cél pixeleket szürkeárnyalatosra konvertálja a másolási művelet előtt. ([több |https://www.php.net/manual/en/function.imagecopymergegray]) copyResampled(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] --------------------------------------------------------------------------------------------------------------------------------- -Egy kép téglalap alakú részét átmásolja egy másik képre, simán interpolálva a pixelértékeket, így különösen a kép méretének csökkentése esetén továbbra is nagyfokú tisztaságot biztosít. +Másol egy téglalap alakú részt az egyik képről a másikra, simán interpolálva a képpontok értékeit, így különösen a kép méretének csökkentésekor is megmarad a nagy tisztaság. -Más szóval, a `copyResampled()` a `$src` egy téglalap alakú területet vesz a `$srcW` szélességű és a `$srcH` magasságú -ből a (`$srcX`,`$srcY`) pozícióban, és elhelyezi a kép egy téglalap alakú, `$dstW` szélességű és a `$dstH` magasságú`$dstX`,`$dstY`) pozícióban lévő téglalap alakú területébe. +Más szavakkal, a `copyResampled()` vesz egy téglalap alakú területet a `$src`-ből `$srcW` szélességgel és `$srcH` magassággal a (`$srcX`, `$srcY`) pozícióban, és elhelyezi azt a kép egy téglalap alakú területére `$dstW` szélességgel és `$dstH` magassággal a (`$dstX`, `$dstY`) pozícióban. -Ha a forrás- és a célkoordináták, valamint a szélesség és a magasság eltér, a képfragmentum megfelelő nyújtása vagy zsugorítása történik. A koordináták a bal felső sarokra vonatkoznak. Ez a funkció használható ugyanazon a képen belüli régiók másolására, de ha a régiók átfedik egymást, az eredmény kiszámíthatatlan lesz. ([tovább |https://www.php.net/manual/en/function.imagecopyresampled]) +Ha a forrás- és célkoordináták, a szélesség és a magasság eltérőek, a képfragmentum megfelelő nyújtása vagy zsugorítása történik. A koordináták a bal felső sarokra vonatkoznak. Ezt a funkciót ugyanazon a képen belüli területek másolására is lehet használni, de ha a területek átfedik egymást, az eredmények nem lesznek kiszámíthatóak. ([több |https://www.php.net/manual/en/function.imagecopyresampled]) copyResized(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] ------------------------------------------------------------------------------------------------------------------------------- -Egy kép téglalap alakú részét másolja át egy másik képre. Más szóval, a `copyResized()` a `$src` egy téglalap alakú területet vesz a `$srcW` szélességű és a `$srcH` magasságú -ből a (`$srcX`,`$srcY`) pozícióban, és elhelyezi a kép egy téglalap alakú, `$dstW` szélességű és a `$dstH` magasságú`$dstX`,`$dstY`) pozícióban lévő téglalap alakú területébe. +Másol egy téglalap alakú részt az egyik képről a másikra. Más szavakkal, a `copyResized()` vesz egy téglalap alakú területet a `$src`-ből `$srcW` szélességgel és `$srcH` magassággal a (`$srcX`, `$srcY`) pozícióban, és elhelyezi azt a kép egy téglalap alakú területére `$dstW` szélességgel és `$dstH` magassággal a (`$dstX`, `$dstY`) pozícióban. -Ha a forrás- és a célkoordináták, valamint a szélesség és a magasság eltér, a képfragmentum megfelelő nyújtása vagy zsugorítása történik. A koordináták a bal felső sarokra vonatkoznak. Ez a funkció használható ugyanazon a képen belüli régiók másolására, de ha a régiók átfedik egymást, az eredmény kiszámíthatatlan lesz. ([tovább |https://www.php.net/manual/en/function.imagecopyresized]) +Ha a forrás- és célkoordináták, a szélesség és a magasság eltérőek, a képfragmentum megfelelő nyújtása vagy zsugorítása történik. A koordináták a bal felső sarokra vonatkoznak. Ezt a funkciót ugyanazon a képen belüli területek másolására is lehet használni, de ha a területek átfedik egymást, az eredmények nem lesznek kiszámíthatóak. ([több |https://www.php.net/manual/en/function.imagecopyresized]) crop(int|string $left, int|string $top, int|string $width, int|string $height): Image .[method] ----------------------------------------------------------------------------------------------- -A képet a megadott téglalap alakú területre vágja. A méretek átadhatók egész számokként pixelben vagy karakterláncokként százalékban (pl. `'50%'`). +Levágja a képet a megadott téglalap alakú területre. A méreteket meg lehet adni egész számként pixelekben vagy stringként százalékban (például `'50%'`). -cropAuto(int $mode=-1, float $threshold=.5, int $color=-1): Image .[method] ---------------------------------------------------------------------------- -Automatikusan levágja a képet a megadott `$mode` címnek megfelelően. ([tovább |https://www.php.net/manual/en/function.imagecropauto]) +cropAuto(int $mode=-1, float $threshold=.5, ?ImageColor $color=null): Image .[method] +------------------------------------------------------------------------------------- +Automatikusan levágja a képet a megadott `$mode` szerint. ([több |https://www.php.net/manual/en/function.imagecropauto]) -ellipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------- -Egy ellipszist rajzol a megadott koordináták középpontjába. ([tovább |https://www.php.net/manual/en/function.imageellipse]) +ellipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------- +Rajzol egy ellipszist a megadott koordinátákon középponttal. ([több |https://www.php.net/manual/en/function.imageellipse]) -fill(int $x, int $y, int $color): void .[method] ------------------------------------------------- -A megadott koordinátánál (balra fent 0, 0) kezdődő áradásos kitöltést hajt végre a megadott `$color` címen a képen. ([tovább |https://www.php.net/manual/en/function.imagefill]) +fill(int $x, int $y, ImageColor $color): void .[method] +------------------------------------------------------- +Kitölt egy területet a megadott koordinátától kezdve (bal felső sarok 0, 0) a megadott `$color` színnel. ([több |https://www.php.net/manual/en/function.imagefill]) -filledArc(int $cx, int $cy, int $w, int $h, int $s, int $e, int $color, int $style): void .[method] ---------------------------------------------------------------------------------------------------- -Egy részleges ívet rajzol, amelynek középpontja a kép megadott koordinátája. ([tovább |https://www.php.net/manual/en/function.imagefilledarc]) +filledArc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color, int $style): void .[method] +--------------------------------------------------------------------------------------------------------------------------------------------- +Rajzol egy részleges ívet a megadott koordinátákon középponttal. ([több |https://www.php.net/manual/en/function.imagefilledarc]) -filledEllipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------------- -Egy ellipszist rajzol, amelynek középpontja a kép megadott koordinátája. ([tovább |https://www.php.net/manual/en/function.imagefilledellipse]) +filledEllipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------------- +Rajzol egy ellipszist a megadott koordinátákon középponttal. ([több |https://www.php.net/manual/en/function.imagefilledellipse]) -filledPolygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------------- -Egy kitöltött sokszöget hoz létre a $image-ben. ([tovább |https://www.php.net/manual/en/function.imagefilledpolygon]) +filledPolygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------------- +Létrehoz egy kitöltött sokszöget a képen. ([több |https://www.php.net/manual/en/function.imagefilledpolygon]) -filledRectangle(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] -------------------------------------------------------------------------------- -Egy téglalapot hoz létre a képen a `$color` címmel, amely az 1. pontban kezdődik és a 2. pontban végződik. 0, 0 a kép bal felső sarka. ([tovább |https://www.php.net/manual/en/function.imagefilledrectangle]) +filledRectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------- +Létrehoz egy `$color` színnel kitöltött téglalapot a képen, a `$x1` & `$y1` ponttól kezdve és a `$x2` & `$y2` ponttal végződve. A 0, 0 pont a kép bal felső sarka. ([több |https://www.php.net/manual/en/function.imagefilledrectangle]) -fillToBorder(int $x, int $y, int $border, int $color): void .[method] ---------------------------------------------------------------------- -Olyan áradásos kitöltést végez, amelynek határszínét a `$border` határozza meg. A kitöltés kezdőpontja a `$x`, `$y` (balra fent 0, 0), és a régiót a `$color` színnel töltjük ki. ([tovább |https://www.php.net/manual/en/function.imagefilltoborder]) +filledRectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------------- +Létrehoz egy `$color` színnel kitöltött téglalapot a képen, a `$left` & `$top` ponttól kezdve `$width` szélességgel és `$height` magassággal. A 0, 0 pont a kép bal felső sarka. + + +fillToBorder(int $x, int $y, int $border, ImageColor $color): void .[method] +---------------------------------------------------------------------------- +Kitöltést rajzol, amelynek szegélyszínét a `$border` határozza meg. A kitöltés kezdőpontja `$x`, `$y` (bal felső sarok 0, 0), és a terület a `$color` színnel van kitöltve. ([több |https://www.php.net/manual/en/function.imagefilltoborder]) filter(int $filtertype, int ...$args): void .[method] ----------------------------------------------------- -A megadott `$filtertype` szűrőt alkalmazza a képre. ([tovább |https://www.php.net/manual/en/function.imagefilter]) +Alkalmazza a megadott `$filtertype` szűrőt a képre. ([több |https://www.php.net/manual/en/function.imagefilter]) flip(int $mode): void .[method] ------------------------------- -Megfordítja a képet a megadott `$mode` segítségével. ([tovább |https://www.php.net/manual/en/function.imageflip]) +Tükrözi a képet a megadott `$mode` segítségével. ([több |https://www.php.net/manual/en/function.imageflip]) -ftText(int $size, int $angle, int $x, int $y, int $col, string $fontFile, string $text, array $extrainfo=null): array .[method] -------------------------------------------------------------------------------------------------------------------------------- -Írjon szöveget a képre a FreeType 2 betűtípusok segítségével. ([tovább |https://www.php.net/manual/en/function.imagefttext]) +ftText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +---------------------------------------------------------------------------------------------------------------------------------------- +Szöveget ír a képbe. ([több |https://www.php.net/manual/en/function.imagefttext]) gammaCorrect(float $inputgamma, float $outputgamma): void .[method] ------------------------------------------------------------------- -Gammakorrekciót alkalmaz a képre egy bemeneti és egy kimeneti gamma megadásával. ([tovább |https://www.php.net/manual/en/function.imagegammacorrect]) +Gamma korrekciót alkalmaz a képre a bemeneti és kimeneti gamma értékekhez viszonyítva. ([több |https://www.php.net/manual/en/function.imagegammacorrect]) getClip(): array .[method] -------------------------- -Az aktuális vágási téglalap, azaz az a terület, amelyen túl nem rajzolunk pixeleket. ([tovább |https://www.php.net/manual/en/function.imagegetclip]) +Visszaadja az aktuális vágási területet, azaz azt a területet, amelyen kívül nem rajzolódnak pixelek. ([több |https://www.php.net/manual/en/function.imagegetclip]) getHeight(): int .[method] @@ -504,7 +545,7 @@ Visszaadja a kép magasságát. getImageResource(): resource|GdImage .[method] ---------------------------------------------- -Visszaadja az eredeti erőforrást. +Visszaadja az eredeti resource-t. getWidth(): int .[method] @@ -512,169 +553,164 @@ getWidth(): int .[method] Visszaadja a kép szélességét. -interlace(int $interlace=null): int .[method] ---------------------------------------------- -Be- vagy kikapcsolja az interlace bitet. Ha az interlace bit be van állítva, és a képet JPEG-ként használják, a kép progresszív JPEG-ként jön létre. ([tovább |https://www.php.net/manual/en/function.imageinterlace]) +interlace(?int $interlace=null): int .[method] +---------------------------------------------- +Be- vagy kikapcsolja az átlapolt módot. Ha az átlapolt mód be van állítva, és a kép JPEG formátumban van mentve, akkor progresszív JPEG-ként lesz mentve. ([több |https://www.php.net/manual/en/function.imageinterlace]) isTrueColor(): bool .[method] ----------------------------- -Megállapítja, hogy a kép színhű-e. ([tovább |https://www.php.net/manual/en/function.imageistruecolor]) +Megállapítja, hogy a kép truecolor-e. ([több |https://www.php.net/manual/en/function.imageistruecolor]) layerEffect(int $effect): void .[method] ---------------------------------------- -Állítsa be az alpha blending flaget a réteghatások használatához. ([tovább |https://www.php.net/manual/en/function.imagelayereffect]) +Beállítja az alfa keverési jelzőt a réteghatások használatához. ([több |https://www.php.net/manual/en/function.imagelayereffect]) -line(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] --------------------------------------------------------------------- -Vonalat húz a két megadott pont közé. ([tovább |https://www.php.net/manual/en/function.imageline]) +line(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +--------------------------------------------------------------------------- +Vonalat rajzol két megadott pont között. ([több |https://www.php.net/manual/en/function.imageline]) -openPolygon(array $points, int $numPoints, int $color): void .[method] ----------------------------------------------------------------------- -Egy nyitott sokszöget rajzol a képre. A `polygon()` címmel ellentétben az utolsó és az első pont között nem húzódik vonal. ([tovább |https://www.php.net/manual/en/function.imageopenpolygon]) +openPolygon(array $points, ImageColor $color): void .[method] +------------------------------------------------------------- +Nyitott sokszöget rajzol a képre. Ellentétben a `polygon()` funkcióval, az utolsó és az első pont között nem rajzolódik vonal. ([több |https://www.php.net/manual/en/function.imageopenpolygon]) paletteCopy(Image $source): void .[method] ------------------------------------------ -A palettát a `$source` oldalról átmásolja a képre. ([tovább |https://www.php.net/manual/en/function.imagepalettecopy]) +Másolja a palettát a `$source`-ból a képbe. ([több |https://www.php.net/manual/en/function.imagepalettecopy]) paletteToTrueColor(): void .[method] ------------------------------------ -Az olyan funkciókkal létrehozott paletta alapú képet, mint a `create()`, valódi színű képpé alakítja, mint a `createtruecolor()`. ([tovább |https://www.php.net/manual/en/function.imagepalettetotruecolor]) +Átalakít egy paletta alapú képet teljes színű képpé. ([több |https://www.php.net/manual/en/function.imagepalettetotruecolor]) place(Image $image, int|string $left=0, int|string $top=0, int $opacity=100): Image .[method] --------------------------------------------------------------------------------------------- -Másolja a `$image` címet a képre a `$left` és a `$top` koordinátáknál. A koordináták átadhatók egész számokként pixelben vagy karakterláncokként százalékban (pl. `'50%'`). +Másolja az `$image`-et a képbe a `$left` és `$top` koordinátákra. A koordinátákat meg lehet adni egész számként pixelekben vagy stringként százalékban (például `'50%'`). -polygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------- -Egy sokszöget hoz létre a képen. ([tovább |https://www.php.net/manual/en/function.imagepolygon]) +polygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------- +Sokszöget hoz létre a képen. ([több |https://www.php.net/manual/en/function.imagepolygon]) -rectangle(int $x1, int $y1, int $x2, int $y2, int $col): void .[method] ------------------------------------------------------------------------ -A megadott koordinátáknál kezdődő téglalapot hoz létre. ([tovább |https://www.php.net/manual/en/function.imagerectangle]) +rectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------- +Téglalapot hoz létre a megadott koordinátákon. ([több |https://www.php.net/manual/en/function.imagerectangle]) + + +rectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------- +Téglalapot hoz létre a megadott koordinátákon. resize(int|string $width, int|string $height, int $flags=Image::OrSmaller): Image .[method] ------------------------------------------------------------------------------------------- -Méretezi a képet, [további információk |#Image Resize]. A méretek átadhatók egész számokként pixelben vagy karakterláncokként százalékban (pl. `'50%'`). +Átméretezi a képet, [további információk |#Méretváltoztatás]. A méreteket meg lehet adni egész számként pixelekben vagy stringként százalékban (például `'50%'`). -resolution(int $resX=null, int $resY=null): mixed .[method] ------------------------------------------------------------ -Lehetővé teszi a kép felbontásának beállítását és lekérdezését DPI-ben (dots per inch). Ha az opcionális paraméterek egyike sincs megadva, az aktuális felbontás indexelt tömbként kerül visszaadásra. Ha csak a `$resX` értéket adja meg, a vízszintes és függőleges felbontás erre az értékre kerül beállításra. Ha mindkét opcionális paramétert megadjuk, a vízszintes és függőleges felbontás ezekre az értékekre kerül beállításra. +resolution(?int $resX=null, ?int $resY=null): mixed .[method] +------------------------------------------------------------- +Beállítja vagy visszaadja a kép felbontását DPI-ben (pont per hüvelyk). Ha egyik opcionális paraméter sincs megadva, az aktuális felbontás indexelt tömbként kerül visszaadásra. Ha csak a `$resX` van megadva, a vízszintes és függőleges felbontás erre az értékre lesz beállítva. Ha mindkét opcionális paraméter meg van adva, a vízszintes és függőleges felbontás ezekre az értékekre lesz beállítva. -A felbontást csak akkor használjuk meta információként, amikor a képeket az ilyen információt támogató formátumokból olvassuk be és írjuk ki (jelenleg PNG és JPEG). Ez nem befolyásolja a rajzolási műveleteket. Az új képek alapértelmezett felbontása 96 DPI. ([tovább |https://www.php.net/manual/en/function.imageresolution]) +A felbontás csak metaadatként használatos, amikor a képeket olyan formátumokban olvassák és írják, amelyek támogatják ezt a fajta információt (jelenleg PNG és JPEG). Nincs hatással semmilyen rajzolási műveletre. Az új képek alapértelmezett felbontása 96 DPI. ([több |https://www.php.net/manual/en/function.imageresolution]) rotate(float $angle, int $backgroundColor): Image .[method] ----------------------------------------------------------- -Elforgatja a képet a megadott `$angle` értékkel fokban. Az elforgatás középpontja a kép középpontja, és az elforgatott kép mérete eltérhet az eredeti képtől. ([tovább |https://www.php.net/manual/en/function.imagerotate]) +Elforgatja a képet a megadott `$angle` szöggel fokban. A forgatás középpontja a kép közepe, és a forgatott kép méretei eltérhetnek az eredeti kép méreteitől. ([több |https://www.php.net/manual/en/function.imagerotate]) .[note] -Igényli a *Bundled GD kiterjesztést*, így nem biztos, hogy mindenhol működik. +A *Bundled GD extension* jelenlétét igényli, tehát nem biztos, hogy mindenhol működik. -save(string $file, int $quality=null, int $type=null): void .[method] ---------------------------------------------------------------------- -Kép mentése egy fájlba. +save(string $file, ?int $quality=null, ?int $type=null): void .[method] +----------------------------------------------------------------------- +Elmenti a képet egy fájlba. -A tömörítési minőség a JPEG (alapértelmezett 85), WEBP (alapértelmezett 80) és AVIF (alapértelmezett 30) esetében 0..100, PNG (alapértelmezett 9) esetében pedig 0..9 tartományban van. Ha a típus nem egyértelmű a fájlkiterjesztésből, akkor a `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` és `Image::BMP` konstansok valamelyikével adhatja meg. +A tömörítés minősége 0..100 tartományban van JPEG (alapértelmezett 85), WEBP (alapértelmezett 80) és AVIF (alapértelmezett 30) esetén, és 0..9 tartományban PNG (alapértelmezett 9) esetén. Ha a típus nem egyértelmű a fájlkiterjesztésből, megadhatja az `ImageType` konstansok egyikével. saveAlpha(bool $saveflag): void .[method] ----------------------------------------- -Beállítja azt a jelzőt, amely meghatározza, hogy a PNG-képek mentésekor megmaradjon-e a teljes alfa-csatorna információ (szemben az egyszínű átlátszósággal). +Beállítja a jelzőt, hogy a PNG képek mentésekor megőrizze-e a teljes alfa csatorna információt (ellentétben az egyszínű átlátszósággal). -Az alfacsatorna megtartásához az alfacsatornát ki kell kapcsolni (`alphaBlending(false)`). ([tovább |https://www.php.net/manual/en/function.imagesavealpha]) +Az alfablendinget ki kell kapcsolni (`alphaBlending(false)`), hogy az alfa csatorna először megmaradjon. ([több |https://www.php.net/manual/en/function.imagesavealpha]) scale(int $newWidth, int $newHeight=-1, int $mode=IMG_BILINEAR_FIXED): Image .[method] -------------------------------------------------------------------------------------- -A megadott interpolációs algoritmus segítségével méretezi a képet. ([tovább |https://www.php.net/manual/en/function.imagescale]) +Méretezi a képet a megadott interpolációs algoritmus segítségével. ([több |https://www.php.net/manual/en/function.imagescale]) -send(int $type=Image::JPEG, int $quality=null): void .[method] --------------------------------------------------------------- -Kimeneti egy képet a böngészőbe. +send(int $type=ImageType::JPEG, ?int $quality=null): void .[method] +------------------------------------------------------------------- +Kimenti a képet a böngészőbe. -A tömörítési minőség a JPEG (alapértelmezett 85), WEBP (alapértelmezett 80) és AVIF (alapértelmezett 30) esetében 0..100, PNG (alapértelmezett 9) esetében 0..9 tartományban van. A típus a `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` és `Image::BMP` konstansok egyike. +A tömörítés minősége 0..100 tartományban van JPEG (alapértelmezett 85), WEBP (alapértelmezett 80) és AVIF (alapértelmezett 30) esetén, és 0..9 tartományban PNG (alapértelmezett 9) esetén. setBrush(Image $brush): void .[method] -------------------------------------- -Beállítja az ecsetképet, amelyet az összes vonalrajzoló funkció (például a `line()` és a `polygon()`) használ, amikor az IMG_COLOR_BRUSHED vagy IMG_COLOR_STYLEDBRUSHED speciális színekkel rajzol. ([tovább |https://www.php.net/manual/en/function.imagesetbrush]) +Beállítja az ecset képét, amelyet minden vonalrajzoló funkció (például `line()` és `polygon()`) használni fog, amikor speciális IMG_COLOR_BRUSHED vagy IMG_COLOR_STYLEDBRUSHED színekkel rajzolnak. ([több |https://www.php.net/manual/en/function.imagesetbrush]) setClip(int $x1, int $y1, int $x2, int $y2): void .[method] ----------------------------------------------------------- -Beállítja az aktuális vágási téglalapot, azaz azt a területet, amelyen túl nem rajzolunk pixeleket. ([tovább |https://www.php.net/manual/en/function.imagesetclip]) +Beállítja az aktuális vágási területet, azaz azt a területet, amelyen kívül nem rajzolódnak pixelek. ([több |https://www.php.net/manual/en/function.imagesetclip]) setInterpolation(int $method=IMG_BILINEAR_FIXED): void .[method] ---------------------------------------------------------------- -Beállítja az interpolációs módszert, amely hatással van a `rotate()` és a `affine()` módszerekre. ([tovább |https://www.php.net/manual/en/function.imagesetinterpolation]) +Beállítja az interpolációs módszert, amely befolyásolja a `rotate()` és `affine()` metódusokat. ([több |https://www.php.net/manual/en/function.imagesetinterpolation]) -setPixel(int $x, int $y, int $color): void .[method] ----------------------------------------------------- -Egy pixelt rajzol a megadott koordinátára. ([tovább |https://www.php.net/manual/en/function.imagesetpixel]) +setPixel(int $x, int $y, ImageColor $color): void .[method] +----------------------------------------------------------- +Pixelt rajzol a megadott koordinátára. ([több |https://www.php.net/manual/en/function.imagesetpixel]) setStyle(array $style): void .[method] -------------------------------------- -Beállítja az IMG_COLOR_STYLED speciális színnel vagy IMG_COLOR_STYLEDBRUSHED színű képek vonalainak rajzolásakor az összes vonalrajzoló funkció (például `line()` és `polygon()`) által használt stílust. ([tovább |https://www.php.net/manual/en/function.imagesetstyle]) +Beállítja a stílust, amelyet minden vonalrajzoló funkció (például `line()` és `polygon()`) használni fog, amikor speciális IMG_COLOR_STYLED színnel vagy IMG_COLOR_STYLEDBRUSHED színű képekkel rajzolnak vonalakat. ([több |https://www.php.net/manual/en/function.imagesetstyle]) setThickness(int $thickness): void .[method] -------------------------------------------- -A téglalapok, sokszögek, ívek stb. rajzolásakor rajzolt vonalak vastagságát a `$thickness` pixelre állítja be. ([tovább |https://www.php.net/manual/en/function.imagesetthickness]) +Beállítja a vonalak vastagságát téglalapok, sokszögek, ívek stb. rajzolásakor `$thickness` pixelre. ([több |https://www.php.net/manual/en/function.imagesetthickness]) setTile(Image $tile): void .[method] ------------------------------------ -Beállítja a csempeképet, amelyet az összes régiókitöltő funkció (például a `fill()` és a `filledPolygon()`) az IMG_COLOR_TILED speciális színnel való kitöltéskor használ. +Beállítja a csempe képét, amelyet minden régiókitöltő funkció (például `fill()` és `filledPolygon()`) használni fog, amikor speciális IMG_COLOR_TILED színnel töltenek ki. -A csempe egy olyan kép, amelyet egy terület ismétlődő mintával való kitöltésére használnak. Bármilyen kép használható csempeként, és a csempekép átlátszó színindexének a `colorTransparent()` segítségével történő beállításával olyan csempe hozható létre, amely lehetővé teszi az alapterület bizonyos részeinek átvilágítását. ([tovább |https://www.php.net/manual/en/function.imagesettile]) +A csempe egy kép, amelyet egy terület ismétlődő mintával való kitöltésére használnak. Bármilyen képet lehet csempeként használni, és a csempe kép átlátszó színindexének a `colorTransparent()` segítségével történő beállításával olyan csempét lehet létrehozni, ahol az alapul szolgáló terület bizonyos részei átlátszanak. ([több |https://www.php.net/manual/en/function.imagesettile]) sharpen(): Image .[method] -------------------------- -Egy kicsit élesíti a képet. +Élesíti a képet. .[note] -Igényli a *Bundled GD kiterjesztést*, így nem biztos, hogy mindenhol működik. - - -string(int $font, int $x, int $y, string $str, int $col): void .[method] ------------------------------------------------------------------------- -Egy karakterláncot rajzol a megadott koordinátákhoz. ([tovább |https://www.php.net/manual/en/function.imagestring]) +A *Bundled GD extension* jelenlétét igényli, tehát nem biztos, hogy mindenhol működik. -stringUp(int $font, int $x, int $y, string $s, int $col): void .[method] ------------------------------------------------------------------------- -Egy sztringet rajzol függőlegesen a megadott koordinátákhoz. ([tovább |https://www.php.net/manual/en/function.imagestringup]) - - -toString(int $type=Image::JPEG, int $quality=null): string .[method] --------------------------------------------------------------------- -Kimeneti képet ad ki stringként. +toString(int $type=ImageType::JPEG, ?int $quality=null): string .[method] +------------------------------------------------------------------------- +Elmenti a képet egy stringbe. -A tömörítési minőség a JPEG (alapértelmezett 85), WEBP (alapértelmezett 80) és AVIF (alapértelmezett 30) esetében 0..100, PNG (alapértelmezett 9) esetében 0..9 tartományban van. A típus a `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` és `Image::BMP` konstansok egyike. +A tömörítés minősége 0..100 tartományban van JPEG (alapértelmezett 85), WEBP (alapértelmezett 80) és AVIF (alapértelmezett 30) esetén, és 0..9 tartományban PNG (alapértelmezett 9) esetén. trueColorToPalette(bool $dither, int $ncolors): void .[method] -------------------------------------------------------------- -Egy valódi színű képet palettaképpé alakít át. ([tovább |https://www.php.net/manual/en/function.imagetruecolortopalette]) +Átalakít egy truecolor képet palettás képpé. ([több |https://www.php.net/manual/en/function.imagetruecolortopalette]) -ttfText(int $size, int $angle, int $x, int $y, int $color, string $fontfile, string $text): array .[method] ------------------------------------------------------------------------------------------------------------ -A megadott szöveget TrueType betűtípusok használatával írja a képbe. ([tovább |https://www.php.net/manual/en/function.imagettftext]) +ttfText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +----------------------------------------------------------------------------------------------------------------------------------------- +Kimenti a megadott szöveget a képbe. ([több |https://www.php.net/manual/en/function.imagettftext]) diff --git a/utils/hu/iterables.texy b/utils/hu/iterables.texy new file mode 100644 index 0000000000..c802e056f5 --- /dev/null +++ b/utils/hu/iterables.texy @@ -0,0 +1,170 @@ +Iterátorokkal való munka +************************ + +.[perex]{data-version:4.0.4} +A [api:Nette\Utils\Iterables] egy statikus osztály iterátorokkal való munkához szükséges funkciókkal. A tömbökhöz hasonló megfelelője a [Nette\Utils\Arrays |arrays]. + + +Telepítés: + +```shell +composer require nette/utils +``` + +Minden példa feltételezi a létrehozott aliast: + +```php +use Nette\Utils\Iterables; +``` + + +contains(iterable $iterable, $value): bool .[method] +---------------------------------------------------- + +Keresi a megadott értéket az iterátorban. Szigorú összehasonlítást (`===`) használ az egyezés ellenőrzésére. `true`-t ad vissza, ha az érték megtalálható, egyébként `false`-t. + +```php +Iterables::contains(new ArrayIterator([1, 2, 3]), 1); // true +Iterables::contains(new ArrayIterator([1, 2, 3]), '1'); // false +``` + +Ez a metódus hasznos, ha gyorsan meg kell tudni, hogy egy adott érték megtalálható-e az iterátorban anélkül, hogy manuálisan végig kellene menni az összes elemen. + + +containsKey(iterable $iterable, $key): bool .[method] +----------------------------------------------------- + +Keresi a megadott kulcsot az iterátorban. Szigorú összehasonlítást (`===`) használ az egyezés ellenőrzésére. `true`-t ad vissza, ha a kulcs megtalálható, egyébként `false`-t. + +```php +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 0); // true +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 4); // false +``` + + +every(iterable $iterable, callable $predicate): bool .[method] +-------------------------------------------------------------- + +Ellenőrzi, hogy az iterátor összes eleme megfelel-e a `$predicate`-ben definiált feltételnek. A `$predicate` funkció szignatúrája `function ($value, $key, iterable $iterable): bool`, és `true`-t kell visszaadnia minden elemre, hogy az `every()` metódus `true`-t adjon vissza. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isBelowThreshold = fn($value) => $value < 40; +$res = Iterables::every($iterator, $isBelowThreshold); // true +``` + +Ez a metódus hasznos annak ellenőrzésére, hogy egy gyűjtemény összes eleme megfelel-e egy bizonyos feltételnek, például hogy minden szám kisebb-e egy adott értéknél. + + +filter(iterable $iterable, callable $predicate): Generator .[method] +-------------------------------------------------------------------- + +Létrehoz egy új iterátort, amely csak azokat az elemeket tartalmazza az eredeti iterátorból, amelyek megfelelnek a `$predicate`-ben definiált feltételnek. A `$predicate` funkció szignatúrája `function ($value, $key, iterable $iterable): bool`, és `true`-t kell visszaadnia a megtartandó elemekre. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::filter($iterator, fn($v) => $v < 3); +// 1, 2 +``` + +A metódus generátort használ, ami azt jelenti, hogy a szűrés fokozatosan történik az eredmény bejárása során. Ez memóriahatékony és lehetővé teszi nagyon nagy gyűjtemények feldolgozását is. Ha nem járja be az eredményül kapott iterátor összes elemét, számítási teljesítményt takarít meg, mivel nem kerül feldolgozásra az eredeti iterátor összes eleme. + + +first(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------------- + +Visszaadja az iterátor első elemét. Ha meg van adva a `$predicate`, akkor az első olyan elemet adja vissza, amely megfelel a megadott feltételnek. A `$predicate` funkció szignatúrája `function ($value, $key, iterable $iterable): bool`. Ha nem található megfelelő elem, meghívódik az `$else` funkció (ha meg van adva), és annak eredménye kerül visszaadásra. Ha az `$else` nincs megadva, `null` kerül visszaadásra. + +```php +Iterables::first(new ArrayIterator([1, 2, 3])); // 1 +Iterables::first(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 3 +Iterables::first(new ArrayIterator([])); // null +Iterables::first(new ArrayIterator([]), else: fn() => false); // false +``` + +Ez a metódus hasznos, ha gyorsan meg kell szerezni egy gyűjtemény első elemét vagy az első olyan elemet, amely megfelel egy bizonyos feltételnek, anélkül, hogy manuálisan végig kellene menni az egész gyűjteményen. + + +firstKey(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +---------------------------------------------------------------------------------------------- + +Visszaadja az iterátor első elemének kulcsát. Ha meg van adva a `$predicate`, akkor az első olyan elem kulcsát adja vissza, amely megfelel a megadott feltételnek. A `$predicate` funkció szignatúrája `function ($value, $key, iterable $iterable): bool`. Ha nem található megfelelő elem, meghívódik az `$else` funkció (ha meg van adva), és annak eredménye kerül visszaadásra. Ha az `$else` nincs megadva, `null` kerül visszaadásra. + +```php +Iterables::firstKey(new ArrayIterator([1, 2, 3])); // 0 +Iterables::firstKey(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 2 +Iterables::firstKey(new ArrayIterator(['a' => 1, 'b' => 2])); // 'a' +Iterables::firstKey(new ArrayIterator([])); // null +``` + + +map(iterable $iterable, callable $transformer): Generator .[method] +------------------------------------------------------------------- + +Létrehoz egy új iterátort a `$transformer` funkció alkalmazásával az eredeti iterátor minden elemére. A `$transformer` funkció szignatúrája `function ($value, $key, iterable $iterable): mixed`, és a visszatérési értéke lesz az elem új értéke. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::map($iterator, fn($v) => $v * 2); +// 2, 4, 6 +``` + +A metódus generátort használ, ami azt jelenti, hogy a transzformáció fokozatosan történik az eredmény bejárása során. Ez memóriahatékony és lehetővé teszi nagyon nagy gyűjtemények feldolgozását is. Ha nem járja be az eredményül kapott iterátor összes elemét, számítási teljesítményt takarít meg, mivel nem kerül feldolgozásra az eredeti iterátor összes eleme. + + +mapWithKeys(iterable $iterable, callable $transformer): Generator .[method] +--------------------------------------------------------------------------- + +Létrehoz egy új iterátort az eredeti iterátor értékeinek és kulcsainak transzformálásával. A `$transformer` funkció szignatúrája `function ($value, $key, iterable $iterable): ?array{$newKey, $newValue}`. Ha a `$transformer` `null`-t ad vissza, az elem kihagyásra kerül. A megtartott elemek esetében a visszaadott tömb első eleme lesz az új kulcs, a második eleme pedig az új érték. + +```php +$iterator = new ArrayIterator(['a' => 1, 'b' => 2]); +$iterator = Iterables::mapWithKeys($iterator, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +Mint a `map()`, ez a metódus is generátort használ a fokozatos feldolgozáshoz és a hatékony memóriakezeléshez. Ez lehetővé teszi nagy gyűjteményekkel való munkát és számítási teljesítmény megtakarítását az eredmény részleges bejárása esetén. + + +memoize(iterable $iterable): IteratorAggregate .[method] +-------------------------------------------------------- + +Létrehoz egy burkolót az iterátor köré, amely az iteráció során gyorsítótárba helyezi annak kulcsait és értékeit. Ez lehetővé teszi az adatok ismételt iterálását anélkül, hogy újra végig kellene menni az eredeti adatforráson. + +```php +$iterator = /* adatok, amelyeket nem lehet többször iterálni */ +$memoized = Iterables::memoize($iterator); +// Most már többször is iterálhatja a $memoized-et adatvesztés nélkül +``` + +Ez a metódus hasznos olyan helyzetekben, amikor ugyanazt az adathalmazt többször kell bejárni, de az eredeti iterátor nem teszi lehetővé az ismételt iterációt, vagy az ismételt bejárás költséges lenne (pl. adatbázisból vagy fájlból történő adatolvasáskor). + + +some(iterable $iterable, callable $predicate): bool .[method] +------------------------------------------------------------- + +Ellenőrzi, hogy az iterátor legalább egy eleme megfelel-e a `$predicate`-ben definiált feltételnek. A `$predicate` funkció szignatúrája `function ($value, $key, iterable $iterable): bool`, és `true`-t kell visszaadnia legalább egy elemre, hogy a `some()` metódus `true`-t adjon vissza. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isEven = fn($value) => $value % 2 === 0; +$res = Iterables::some($iterator, $isEven); // true +``` + +Ez a metódus hasznos annak gyors ellenőrzésére, hogy a gyűjteményben létezik-e legalább egy elem, amely megfelel egy bizonyos feltételnek, például hogy a gyűjtemény tartalmaz-e legalább egy páros számot. + +Lásd [#every()]. + + +toIterator(iterable $iterable): Iterator .[method] +-------------------------------------------------- + +Átalakít bármilyen iterálható objektumot (array, Traversable) Iteratorrá. Ha a bemenet már Iterator, változatlanul visszaadja. + +```php +$array = [1, 2, 3]; +$iterator = Iterables::toIterator($array); +// Most már Iterator van a tömb helyett +``` + +Ez a metódus hasznos, ha biztosítani kell, hogy Iterator álljon rendelkezésre, függetlenül a bemeneti adatok típusától. Ez hasznos lehet olyan funkciók létrehozásakor, amelyek különböző típusú iterálható adatokkal dolgoznak. diff --git a/utils/hu/json.texy b/utils/hu/json.texy index dd74607580..c69a6a1c9f 100644 --- a/utils/hu/json.texy +++ b/utils/hu/json.texy @@ -1,8 +1,8 @@ -JSON funkciók -************* +JSON-nal való munka +******************* .[perex] -[api:Nette\Utils\Json] egy statikus osztály JSON kódolási és dekódolási funkciókkal. Kezeli a különböző PHP verziók sebezhetőségeit és hiba esetén kivételeket dob. +A [api:Nette\Utils\Json] egy statikus osztály a JSON formátum kódolására és dekódolására szolgáló funkciókkal. Kezeli a különböző PHP verziók sebezhetőségeit és kivételeket dob hibák esetén. Telepítés: @@ -11,44 +11,44 @@ Telepítés: composer require nette/utils ``` -Minden példa feltételezi, hogy a következő osztály alias van definiálva: +Minden példa feltételezi a létrehozott aliast: ```php use Nette\Utils\Json; ``` -Használat .[#toc-usage] -======================= +Használat +========= encode(mixed $value, bool $pretty=false, bool $asciiSafe=false, bool $htmlSafe=false, bool $forceObjects=false): string .[method] --------------------------------------------------------------------------------------------------------------------------------- -Átalakítja a `$value` oldalt JSON formátumra. +Átalakítja a `$value`-t JSON formátumba. -A `$pretty` beállítása esetén a JSON formátumot formázza a könnyebb olvashatóság és áttekinthetőség érdekében: +Ha a `$pretty` be van állítva, a JSON-t formázza a könnyebb olvashatóság és áttekinthetőség érdekében: ```php Json::encode($value); // JSON-t ad vissza -Json::encode($value, pretty: true); // tisztább JSON-t ad vissza +Json::encode($value, pretty: true); // áttekinthetőbb JSON-t ad vissza ``` -A `$asciiSafe` beállítása esetén ASCII kimenetet generál, azaz az unicode karaktereket `\uxxxx` szekvenciákkal helyettesíti: +Az `$asciiSafe` esetén a kimenetet ASCII-ben generálja, azaz a unicode karaktereket `\uxxxx` szekvenciákra cseréli: ```php Json::encode('žluťoučký', asciiSafe: true); // '"\u017elu\u0165ou\u010dk\u00fd"' ``` -A `$htmlSafe` paraméter biztosítja, hogy a kimenet ne tartalmazzon a HTML-ben speciális jelentéssel bíró karaktereket: +A `$htmlSafe` paraméter biztosítja, hogy a kimenet ne tartalmazzon olyan karaktereket, amelyeknek speciális jelentésük van a HTML-ben: ```php Json::encode('onesendJson($data)` metódust, amelyet például a `action*()` metódusban hívhat meg, lásd [Válasz küldése |application:presenters#Sending a Response]. +Erre használható a `$this->sendJson($data)` metódus, amelyet például az `action*()` metódusban hívhatunk meg, lásd [Válasz küldése |application:presenters#Válasz küldése]. diff --git a/utils/hu/paginator.texy b/utils/hu/paginator.texy index 14e9b621a1..49eb114106 100644 --- a/utils/hu/paginator.texy +++ b/utils/hu/paginator.texy @@ -1,8 +1,8 @@ -Paginátor +Paginator ********* .[perex] -Egy adatlistát kell lapoznia? Mivel a lapozás mögött álló matematika trükkös lehet, a [api:Nette\Utils\Paginator] segít Önnek. +Szüksége van az adatok listázásának lapozására? Mivel a lapozási matematika trükkös lehet, a [api:Nette\Utils\Paginator] segít ebben. Telepítés: @@ -11,38 +11,38 @@ Telepítés: composer require nette/utils ``` -Létrehozunk egy lapozó objektumot, és megadjuk az alapvető információkat: +Létrehozunk egy lapozó objektumot és beállítjuk az alapvető információkat: ```php $paginator = new Nette\Utils\Paginator; -$paginator->setPage(1); // az aktuális oldal száma (1-től számozva) -$paginator->setItemsPerPage(30); // az oldalankénti rekordok száma -$paginator->setItemCount(356); // a rekordok teljes száma (ha van) +$paginator->setPage(1); // az aktuális oldal száma +$paginator->setItemsPerPage(30); // elemek száma oldalanként +$paginator->setItemCount(356); // az elemek teljes száma, ha ismert ``` -Ezt a `setBase()` segítségével tudjuk megváltoztatni: +Az oldalak 1-től vannak számozva. Ezt megváltoztathatjuk a `setBase()` segítségével: ```php -$paginator->setBase(0); // 0-tól számozva +$paginator->setBase(0); // 0-tól számozunk ``` -Az objektum most már minden alapvető információt megad, ami egy paginátor létrehozásához hasznos. Átadhatjuk például egy sablonhoz, és ott használhatjuk. +Az objektum most minden alapvető információt megad, ami hasznos a lapozó létrehozásakor. Például átadhatja egy sablonnak és ott felhasználhatja. ```php -$paginator->isFirst(); // ez az első oldal? -$paginator->isLast(); // ez az utolsó oldal? +$paginator->isFirst(); // az első oldalon vagyunk? +$paginator->isLast(); // az utolsó oldalon vagyunk? $paginator->getPage(); // az aktuális oldal száma $paginator->getFirstPage(); // az első oldal száma $paginator->getLastPage(); // az utolsó oldal száma $paginator->getFirstItemOnPage(); // az oldalon lévő első elem sorszáma $paginator->getLastItemOnPage(); // az oldalon lévő utolsó elem sorszáma -$paginator->getPageIndex(); // az aktuális oldal száma, ha 0-tól kezdődően számozódik. -$paginator->getPageCount(); // az oldalak száma összesen -$paginator->getItemsPerPage(); // az oldalankénti rekordok száma -$paginator->getItemCount(); // a rekordok teljes száma (ha van) +$paginator->getPageIndex(); // az aktuális oldal száma 0-tól számozva +$paginator->getPageCount(); // az oldalak teljes száma +$paginator->getItemsPerPage(); // elemek száma oldalanként +$paginator->getItemCount(); // az elemek teljes száma, ha ismert ``` -A paginátor segít az SQL-lekérdezés megfogalmazásában. A `getLength()` és a `getOffset()` metódusok visszaadják azokat az értékeket, amelyeket a LIMIT és OFFSET záradékokban használhat: +A lapozó segít az SQL lekérdezés megfogalmazásában. A `getLength()` és `getOffset()` metódusok olyan értékeket adnak vissza, amelyeket a LIMIT és OFFSET klauzulákban használhatunk: ```php $result = $database->query( @@ -52,7 +52,7 @@ $result = $database->query( ); ``` -Ha fordított sorrendben kell paginálni, azaz az oldalszámot. Az 1. oldal a legnagyobb eltolásnak felel meg, akkor a `getCountdownOffset()` parancsot használhatja: +Ha fordított sorrendben kell lapoznunk, azaz az 1. oldal a legmagasabb offsetnek felel meg, használjuk a `getCountdownOffset()` metódust: ```php $result = $database->query( @@ -62,4 +62,4 @@ $result = $database->query( ); ``` -Az alkalmazásban való használatra példát az [Adatbázis-eredmények paginálása |best-practices:pagination] című szakácskönyvben talál. +Alkalmazásbeli használati példát a [Adatbázis eredmények lapozása |best-practices:pagination] szakácskönyvben talál. diff --git a/utils/hu/random.texy b/utils/hu/random.texy index 77b03e2b11..89ba1e2338 100644 --- a/utils/hu/random.texy +++ b/utils/hu/random.texy @@ -1,8 +1,8 @@ -Véletlenszerű karakterláncok generátora -*************************************** +Véletlen stringek generálása +**************************** .[perex] -[api:Nette\Utils\Random] egy statikus osztály kriptográfiailag biztonságos pszeudo-véletlen karakterláncok generálására. +A [api:Nette\Utils\Random] egy statikus osztály kriptográfiailag biztonságos pszeudovéletlen stringek generálására. Telepítés: @@ -15,7 +15,7 @@ composer require nette/utils generate(int $length=10, string $charlist=`'0-9a-z'`): string .[method] ----------------------------------------------------------------------- -A második argumentumban megadott karakterekből egy adott hosszúságú véletlen karakterláncot generál. Támogatja az intervallumokat, például `0-9` vagy `A-Z`. +Véletlen stringet generál adott hosszúságban a `$charlist` paraméterben megadott karakterekből. Intervallumokat is lehet használni, mint például `0-9`. ```php use Nette\Utils\Random; diff --git a/utils/hu/reflection.texy b/utils/hu/reflection.texy index 89f16dea2d..0047018955 100644 --- a/utils/hu/reflection.texy +++ b/utils/hu/reflection.texy @@ -1,8 +1,8 @@ -PHP reflexió +PHP Reflexió ************ .[perex] -[api:Nette\Utils\Reflection] egy statikus osztály, amely hasznos funkciókat tartalmaz a PHP reflexióhoz. Célja a natív osztályok hibáinak kijavítása és a viselkedés egységesítése a PHP különböző verzióiban. +A [api:Nette\Utils\Reflection] egy statikus osztály hasznos funkciókkal a PHP reflexióhoz. Feladata a natív osztályok hiányosságainak javítása és a viselkedés egységesítése a különböző PHP verziók között. Telepítés: @@ -11,7 +11,7 @@ Telepítés: composer require nette/utils ``` -Minden példa feltételezi, hogy a következő osztály alias van definiálva: +Minden példa feltételezi a létrehozott aliast: ```php use Nette\Utils\Reflection; @@ -21,13 +21,13 @@ use Nette\Utils\Reflection; areCommentsAvailable(): bool .[method] -------------------------------------- -Kideríti, hogy a reflection hozzáfér-e a PHPdoc megjegyzésekhez. Előfordulhat, hogy a megjegyzések nem állnak rendelkezésre az opcode cache miatt, lásd például az [opcache.save-comments |https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments] direktívát. +Megállapítja, hogy a reflexió hozzáfér-e a PHPdoc kommentekhez. A kommentek elérhetetlenek lehetnek az opcode cache miatt, lásd például a [opcache.save-comments |https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments] direktívát. expandClassName(string $name, ReflectionClass $context): string .[method] ------------------------------------------------------------------------- -Kibővíti a `$name` az osztály teljes nevére a `$context`, azaz a névtér és a definiált aliasok kontextusában. Így azt adja vissza, ahogyan a PHP-elemző értelmezné a `$name` -t, ha az a `$context` testében lenne leírva. +Kibővíti a `$name` osztálynevet a teljes nevére a `$context` osztály kontextusában, azaz a névterének és a definiált aliasoknak a kontextusában. Tehát valójában azt mondja meg, hogyan értelmezné a PHP parser a `$name`-et, ha az a `$context` osztály törzsében lenne leírva. ```php namespace Foo; @@ -47,9 +47,9 @@ Reflection::expandClassName('Baz', $context); // 'Foo\Baz' getMethodDeclaringMethod(ReflectionMethod $method): ReflectionMethod .[method] ------------------------------------------------------------------------------ -Visszaadja egy olyan metódus tükörképét, amely tartalmazza a `$method` deklarációját. Általában minden metódus saját deklaráció, de a metódus teste is lehet a traitben és más néven. +Visszaadja annak a metódusnak a reflexióját, amely a `$method` metódus deklarációját tartalmazza. Általában minden metódus a saját deklarációja, de a metódus törzse trait-ben is lehet, és más néven. -Mivel a PHP nem ad elég információt a tényleges deklaráció meghatározásához, a Nette a saját heurisztikáját használja, amelynek **megbízhatónak** kell lennie. +Mivel a PHP nem szolgáltat elegendő információt a valódi deklaráció megállapításához, a Nette saját heurisztikát használ, amelynek **megbízhatónak kellene** lennie. ```php trait DemoTrait @@ -76,9 +76,9 @@ Reflection::getMethodDeclaringMethod($method); // ReflectionMethod('DemoTrait::f getPropertyDeclaringClass(ReflectionProperty $prop): ReflectionClass .[method] ------------------------------------------------------------------------------ -Visszaadja egy olyan osztály vagy tulajdonság tükörképét, amely tartalmazza a `$prop` tulajdonság deklarációját. A tulajdonság a tulajdonságban is deklarálható. +Visszaadja annak az osztálynak vagy trait-nek a reflexióját, amely a `$prop` property deklarációját tartalmazza. A property ugyanis trait-ben is deklarálható. -Mivel a PHP nem ad elég információt a tényleges deklaráció meghatározásához, a Nette a saját heurisztikáját használja, amely **nem** megbízható. +Mivel a PHP nem szolgáltat elegendő információt a valódi deklaráció megállapításához, a Nette saját heurisztikát használ, amely **nem** megbízható. ```php trait DemoTrait @@ -100,7 +100,7 @@ Reflection::getPropertyDeclaringClass($prop); // ReflectionClass('DemoTrait') isBuiltinType(string $type): bool .[method deprecated] ------------------------------------------------------ -Megállapítja, hogy a `$type` PHP beépített típus-e. Ellenkező esetben az osztály neve. +Megállapítja, hogy a `$type` beépített PHP típus-e. Ellenkező esetben osztálynév. ```php Reflection::isBuiltinType('string'); // true @@ -108,13 +108,13 @@ Reflection::isBuiltinType('Foo'); // false ``` .[note] -Használja a [Nette\Utils\Validator::isBuiltinType() |validators#isBuiltinType] függvényt. +Használja a [Nette\Utils\Validator::isBuiltinType() |validators#isBuiltinType] metódust. toString($reflection): string .[method] --------------------------------------- -A tükrözést emberi olvasásra alkalmas karakterlánccá alakítja. +Átalakítja a reflexiót ember által érthető stringgé. ```php $func = new ReflectionFunction('func'); diff --git a/utils/hu/smartobject.texy b/utils/hu/smartobject.texy index a158b2858b..22579b2760 100644 --- a/utils/hu/smartobject.texy +++ b/utils/hu/smartobject.texy @@ -1,8 +1,8 @@ -SmartObject és StaticClass -************************** +SmartObject +*********** .[perex] -A SmartObject a PHP osztályok *tulajdonságok* támogatását adja hozzá. A StaticClass a statikus osztályok jelölésére szolgál. +A SmartObject évekig javította az objektumok viselkedését PHP-ban. A PHP 8.4 verziótól kezdve minden funkciója a PHP részévé vált, ezzel beteljesítve történelmi küldetését, hogy úttörője legyen a modern objektumorientált megközelítésnek PHP-ban. Telepítés: @@ -11,19 +11,64 @@ Telepítés: composer require nette/utils ``` +A SmartObject 2007-ben jött létre forradalmi megoldásként a PHP akkori objektummodelljének hiányosságaira. Abban az időben, amikor a PHP számos objektumtervezési problémával küzdött, jelentős javulást és egyszerűsítést hozott a fejlesztők munkájába. A Nette keretrendszer legendás részévé vált. Olyan funkcionalitást kínált, amelyet a PHP csak sok évvel később kapott meg - az objektumtulajdonságokhoz való hozzáférés ellenőrzésétől a kifinomult szintaktikai cukrokig. A PHP 8.4 megjelenésével beteljesítette történelmi küldetését, mivel minden funkciója a nyelv natív részévé vált. Figyelemre méltó 17 évvel előzte meg a PHP fejlődését. -Tulajdonságok, Getterek és Setterek .[#toc-properties-getters-and-setters] -========================================================================== +Technikailag a SmartObject érdekes fejlődésen ment keresztül. Eredetileg `Nette\Object` osztályként valósult meg, amelyből más osztályok örökölték a szükséges funkcionalitást. Alapvető változás következett be a PHP 5.4-gyel, amely bevezette a trait-ek támogatását. Ez lehetővé tette az átalakulást `Nette\SmartObject` trait-té, ami nagyobb rugalmasságot hozott - a fejlesztők olyan osztályokban is használhatták a funkcionalitást, amelyek már egy másik osztályból örököltek. Míg az eredeti `Nette\Object` osztály a PHP 7.2 megjelenésével megszűnt (amely megtiltotta az osztályok `Object` szóval való elnevezését), a `Nette\SmartObject` trait tovább él. -A modern objektumorientált nyelvekben (pl. C#, Python, Ruby, JavaScript) a *tulajdonság* kifejezés az [osztályok speciális tagjaira |https://en.wikipedia.org/wiki/Property_(programming)] utal, amelyek változóknak tűnnek, de valójában metódusok képviselik őket. Amikor ennek a "változónak" az értékét hozzárendeljük vagy kiolvassuk, a megfelelő metódust (az úgynevezett gettert vagy settert) hívjuk meg. Ez egy nagyon praktikus dolog, teljes kontrollt biztosít számunkra a változókhoz való hozzáférés felett. Csak akkor tudjuk érvényesíteni a bemenetet, vagy csak akkor generálhatunk eredményt, ha a tulajdonságot kiolvassuk. +Nézzük át azokat a tulajdonságokat, amelyeket egykor a `Nette\Object`, később pedig a `Nette\SmartObject` kínált. Ezen funkciók mindegyike a maga idejében jelentős előrelépést jelentett az objektumorientált programozás területén PHP-ban. -A PHP tulajdonságok nem támogatottak, de a `Nette\SmartObject` trait képes utánozni őket. Hogyan használjuk? -- Adjunk hozzá egy megjegyzést az osztályhoz a következő formában `@property $xyz` -- Hozzon létre egy `getXyz()` vagy `isXyz()` nevű gettert, egy settert, amelynek a neve `setXyz()` -- A getter és a setter *nyilvános* vagy *védett* kell, hogy legyen, és opcionális, tehát lehet *read-only* vagy *write-only* tulajdonság. +Konzisztens hibaállapotok +------------------------- +A korai PHP egyik legégetőbb problémája az objektumokkal való munka inkonzisztens viselkedése volt. A `Nette\Object` rendet és kiszámíthatóságot hozott ebbe a káoszba. Nézzük meg, hogyan nézett ki a PHP eredeti viselkedése: -A Circle osztály tulajdonságát fogjuk használni annak biztosítására, hogy a `$radius` változóba csak nem negatív számok kerüljenek. Helyettesítsük a `public $radius` címet a tulajdonsággal: +```php +echo $obj->undeclared; // E_NOTICE, később E_WARNING +$obj->undeclared = 1; // csendben lefut figyelmeztetés nélkül +$obj->unknownMethod(); // Fatal error (nem fogható el try/catch segítségével) +``` + +A Fatal error leállította az alkalmazást anélkül, hogy bármilyen módon reagálni lehetett volna rá. A nem létező tagokba történő csendes írás figyelmeztetés nélkül súlyos, nehezen felderíthető hibákhoz vezethetett. A `Nette\Object` mindezeket az eseteket elfogta és `MemberAccessException` kivételt dobott, ami lehetővé tette a programozóknak, hogy reagáljanak a hibákra és kezeljék azokat. + +```php +echo $obj->undeclared; // Nette\MemberAccessException kivételt dob +$obj->undeclared = 1; // Nette\MemberAccessException kivételt dob +$obj->unknownMethod(); // Nette\MemberAccessException kivételt dob +``` + +A PHP 7.0 óta a nyelv már nem okoz elfoghatatlan fatal error-t, és a PHP 8.2 óta a nem deklarált tagokhoz való hozzáférés hibának minősül. + + +"Did you mean?" súgó +-------------------- +A `Nette\Object` egy nagyon kellemes funkcióval érkezett: intelligens súgóval elgépelések esetén. Amikor a fejlesztő hibát vétett egy metódus vagy változó nevében, nemcsak a hibát jelezte, hanem segítő kezet is nyújtott a helyes név javaslatának formájában. Ez az ikonikus üzenet, amelyet "did you mean?" néven ismerünk, órákat spórolt meg a programozóknak az elgépelések keresésével: + +```php +class Foo extends Nette\Object +{ + public static function from($var) + { + } +} + +$foo = Foo::form($var); +// Nette\MemberAccessException kivételt dob +// "Call to undefined static method Foo::form(), did you mean from()?" +``` + +A mai PHP ugyan nem rendelkezik „did you mean?” funkcióval, de ezt a kiegészítést a [Tracy|tracy:] hozzá tudja adni a hibákhoz. Sőt, az ilyen hibákat [automatikusan ki is tudja javítani |tracy:open-files-in-ide#Bemutatók]. + + +Property-k ellenőrzött hozzáféréssel +------------------------------------ +Jelentős innováció, amelyet a SmartObject hozott a PHP-ba, az ellenőrzött hozzáféréssel rendelkező property-k voltak. Ez a koncepció, amely a C# vagy Python nyelvekben elterjedt, lehetővé tette a fejlesztők számára, hogy elegánsan ellenőrizzék az objektum adataihoz való hozzáférést és biztosítsák azok konzisztenciáját. A property-k az objektumorientált programozás erőteljes eszközei. Változókként működnek, de valójában metódusok (getterek és setterek) képviselik őket. Ez lehetővé teszi a bemenetek validálását vagy az értékek generálását csak az olvasás pillanatában. + +A property-k használatához: +- Adjon hozzá egy `@property $xyz` alakú annotációt az osztályhoz +- Hozzon létre egy `getXyz()` vagy `isXyz()` nevű gettert, és egy `setXyz()` nevű settert +- Biztosítsa, hogy a getter és a setter *public* vagy *protected* legyen. Opcionálisak - tehát létezhetnek *read-only* vagy *write-only* property-ként is + +Nézzünk egy gyakorlati példát a Circle osztályra, ahol a property-ket arra használjuk, hogy biztosítsuk, hogy a sugár mindig nemnegatív szám legyen. Cseréljük le az eredeti `public $radius`-t property-re: ```php /** @@ -34,22 +79,22 @@ class Circle { use Nette\SmartObject; - private float $radius = 0.0; // nem nyilvános + private float $radius = 0.0; // nem public! - // getter a $radius tulajdonsághoz + // getter a $radius property-hez protected function getRadius(): float { return $this->radius; } - // setter a $radius tulajdonsághoz + // setter a $radius property-hez protected function setRadius(float $radius): void { - // az érték mentés előtti szanálása + // az értéket mentés előtt tisztítjuk $this->radius = max(0.0, $radius); } - // getter a $visible tulajdonsághoz + // getter a $visible property-hez protected function isVisible(): bool { return $this->radius > 0; @@ -57,84 +102,30 @@ class Circle } $circle = new Circle; -$circle->radius = 10; // valójában a setRadius(10) hívása. -echo $circle->radius; // meghívja a getRadius() függvényt. -echo $circle->visible; // hívja az isVisible() függvényt +$circle->radius = 10; // valójában a setRadius(10)-et hívja +echo $circle->radius; // a getRadius()-t hívja +echo $circle->visible; // az isVisible()-t hívja ``` -A tulajdonságok elsősorban "szintaktikai cukor"((syntactic sugar)), amelynek célja, hogy a kód egyszerűsítésével megédesítse a programozó életét. Ha nem akarod őket, nem kell használni őket. - - -Statikus osztályok .[#toc-static-classes] -========================================= - -A statikus osztályokat, azaz azokat az osztályokat, amelyeket nem szándékoznak példányosítani, a `Nette\StaticClass` tulajdonsággal lehet jelölni: +A PHP 8.4 óta ugyanezt a funkcionalitást property hook-okkal lehet elérni, amelyek sokkal elegánsabb és tömörebb szintaxist kínálnak: ```php -class Strings +class Circle { - use Nette\StaticClass; -} -``` - -Amikor megpróbál egy példányt létrehozni, a `Error` kivétel dobódik, jelezve, hogy az osztály statikus. - - -Egy pillantás a történelembe .[#toc-a-look-into-the-history] -============================================================ - -A SmartObject korábban sokféleképpen javította és javította az osztályok viselkedését, de a PHP fejlődése az eredeti funkciók nagy részét feleslegessé tette. A következőkben tehát a dolgok fejlődésének történetébe pillantunk bele. - -A PHP objektummodellje a kezdetektől fogva számos komoly hibától és hiányosságtól szenvedett. Ez volt az oka a `Nette\Object` osztály létrehozásának (2007-ben), amely megpróbálta ezeket orvosolni és javítani a PHP használatának élményét. Ez elég volt ahhoz, hogy más osztályok is örököljenek belőle, és elnyerjék az általa nyújtott előnyöket. Amikor a PHP 5.4-ben megjelent a trait-támogatás, a `Nette\Object` osztályt a `Nette\SmartObject` osztály váltotta fel. Így már nem volt szükség arra, hogy egy közös őstől örököljenek. Ráadásul a trait olyan osztályokban is használható volt, amelyek már egy másik osztálytól örököltek. A `Nette\Object` végleges végét a PHP 7.2 kiadása jelentette, amely megtiltotta az osztályok `Object` nevének használatát. - -A PHP fejlesztésének előrehaladtával az objektummodell és a nyelvi képességek tovább fejlődtek. A `SmartObject` osztály egyes függvényei feleslegessé váltak. A PHP 8.2 kiadása óta az egyetlen olyan funkció, amely megmaradt, és amelyet a PHP még nem támogat közvetlenül, az úgynevezett [tulajdonságok |#Properties, Getters and Setters] használatának lehetősége. - -Milyen funkciókat kínált egykor a `Nette\Object` és a `Nette\Object`? Íme egy áttekintés. (A példák a `Nette\Object` osztályt használják, de a tulajdonságok többsége a `Nette\SmartObject` tulajdonságra is vonatkozik). - - -Inkonzisztens hibák .[#toc-inconsistent-errors] ------------------------------------------------ -A PHP következetlenül viselkedett a nem deklarált tagok elérésekor. Az állapot a `Nette\Object` idején a következő volt: - -```php -echo $obj->undeclared; // E_NOTICE, később E_WARNING -$obj->undeclared = 1; // csendben átmegy jelentés nélkül. -$obj->unknownMethod(); // Végzetes hiba (try/catch-el nem fogható) -``` - -A végzetes hiba mindenféle reagálási lehetőség nélkül megszakította az alkalmazást. A nem létező tagokba való csendes írás figyelmeztetés nélkül súlyos, nehezen észlelhető hibákhoz vezethetett. `Nette\Object` Minden ilyen esetet elkaptunk, és a `MemberAccessException` címen egy kivételt dobtunk. - -```php -echo $obj->undeclared; // throw Nette\MemberAccessException -$obj->undeclared = 1; // throw Nette\MemberAccessException -$obj->unknownMethod(); // throw Nette\MemberAccessException -``` -A PHP 7.0 óta a PHP már nem okoz nem fogható halálos hibákat, a nem deklarált tagokhoz való hozzáférés pedig a PHP 8.2 óta hiba. - - -Úgy értetted? .[#toc-did-you-mean] ----------------------------------- -Ha egy `Nette\MemberAccessException` hiba lépett fel, például egy objektumváltozó elérésekor vagy egy metódus hívásakor elírás miatt, a `Nette\Object` megpróbált a hibaüzenetben egy tippet adni a hiba kijavítására, az ikonikus "did you mean?" kiegészítés formájában. + public float $radius = 0.0 { + set => max(0.0, $value); + } -```php -class Foo extends Nette\Object -{ - public static function from($var) - { + public bool $visible { + get => $this->radius > 0; } } - -$foo = Foo::form($var); -// throw Nette\MemberAccessException -// "Call to undefined static method Foo::form(), did you mean from()?" ``` -A mai PHP-ben talán nincs a "did you mean?" semmilyen formája, de [Tracy |tracy:] ezt a kiegészítést hozzáadja a hibákhoz. És még maga is képes [kijavítani |tracy:open-files-in-ide#toc-demos] az ilyen hibákat. - -Bővítési módszerek .[#toc-extension-methods] --------------------------------------------- -A C# bővítési metódusok által inspirálva. Lehetőséget adtak új metódusok hozzáadására a meglévő osztályokhoz. Például a `addDateTime()` metódust hozzáadhatta egy űrlaphoz, hogy saját DateTimePickert adjon hozzá. +Extension metódusok +------------------- +A `Nette\Object` egy másik érdekes koncepciót hozott a PHP-ba, amelyet a modern programozási nyelvek ihlettek - az extension metódusokat. Ez a funkció, amelyet a C#-ból vettek át, lehetővé tette a fejlesztők számára, hogy elegánsan bővítsék a meglévő osztályokat új metódusokkal anélkül, hogy módosítaniuk vagy örökölniük kellene tőlük. Például hozzáadhatott egy `addDateTime()` metódust az űrlaphoz, amely egy saját DateTimePickert ad hozzá: ```php Form::extensionMethod( @@ -146,22 +137,22 @@ $form = new Form; $form->addDateTime('date'); ``` -A kiterjesztési metódusok nem bizonyultak praktikusnak, mert a nevüket a szerkesztők nem töltötték ki automatikusan, hanem azt jelentették, hogy a metódus nem létezik. Ezért a támogatásuk megszűnt. +Az extension metódusok nem bizonyultak praktikusnak, mert a nevüket nem ismerték fel az editorok, sőt, azt jelezték, hogy a metódus nem létezik. Ezért a támogatásuk megszűnt. Ma már gyakoribb a kompozíció vagy az öröklődés használata az osztályok funkcionalitásának bővítésére. -Az osztály nevének megadása .[#toc-getting-the-class-name] ----------------------------------------------------------- +Osztálynév lekérdezése +---------------------- +Az osztálynév lekérdezéséhez a SmartObject egyszerű metódust kínált: ```php -$class = $obj->getClass(); // using Nette\Object -$class = $obj::class; // PHP 8.0 óta +$class = $obj->getClass(); // Nette\Object segítségével +$class = $obj::class; // PHP 8.0 óta ``` -Hozzáférés a reflexióhoz és a megjegyzésekhez .[#toc-access-to-reflection-and-annotations] ------------------------------------------------------------------------------------------- - -`Nette\Object` a `getReflection()` és a `getAnnotation()` metódusok segítségével kínált hozzáférést a reflexióhoz és a megjegyzésekhez: +Hozzáférés a reflexióhoz és annotációkhoz +----------------------------------------- +A `Nette\Object` hozzáférést kínált a reflexióhoz és annotációkhoz a `getReflection()` és `getAnnotation()` metódusok segítségével. Ez a megközelítés jelentősen leegyszerűsítette az osztályok metaadataival való munkát: ```php /** @@ -173,10 +164,10 @@ class Foo extends Nette\Object $obj = new Foo; $reflection = $obj->getReflection(); -$reflection->getAnnotation('author'); // visszaadja 'John Doe'. +$reflection->getAnnotation('author'); // 'John Doe'-t ad vissza ``` -A PHP 8.0-tól kezdve a metainformációkat attribútumok formájában is elérhetjük: +A PHP 8.0 óta lehetséges a metaadatokhoz attribútumok formájában hozzáférni, amelyek még több lehetőséget és jobb típusellenőrzést kínálnak: ```php #[Author('John Doe')] @@ -190,10 +181,9 @@ $reflection->getAttributes(Author::class)[0]; ``` -Metódus Getterek .[#toc-method-getters] ---------------------------------------- - -`Nette\Object` elegáns módot kínált arra, hogy a módszereket úgy kezeljük, mintha változók lennének: +Metódus getterek +---------------- +A `Nette\Object` elegáns módot kínált arra, hogy a metódusokat úgy adjuk át, mintha változók lennének: ```php class Foo extends Nette\Object @@ -209,7 +199,7 @@ $method = $obj->adder; echo $method(2, 3); // 5 ``` -A PHP 8.1-től kezdve használhatod az úgynevezett "első osztályú hívható szintaxist":https://www.php.net/manual/en/functions.first_class_callable_syntax: +A PHP 8.1 óta lehetséges az ún. "first-class callable syntax":https://www.php.net/manual/en/functions.first_class_callable_syntax használata, amely ezt a koncepciót még tovább viszi: ```php $obj = new Foo; @@ -218,10 +208,9 @@ echo $method(2, 3); // 5 ``` -Események .[#toc-events] ------------------------- - -`Nette\Object` szintaktikai cukrot kínált az [esemény |nette:glossary#events] kiváltásához: +Események +--------- +A SmartObject egyszerűsített szintaxist kínál az [eseményekkel |nette:glossary#Eventek események] való munkához. Az események lehetővé teszik az objektumok számára, hogy tájékoztassák az alkalmazás többi részét állapotuk változásairól: ```php class Circle extends Nette\Object @@ -231,12 +220,12 @@ class Circle extends Nette\Object public function setRadius(float $radius): void { $this->onChange($this, $radius); - $this->radius = $radius + $this->radius = $radius; } } ``` -A `$this->onChange($this, $radius)` kód a következővel egyenértékű: +A `$this->onChange($this, $radius)` kód a következő ciklussal ekvivalens: ```php foreach ($this->onChange as $callback) { @@ -244,7 +233,7 @@ foreach ($this->onChange as $callback) { } ``` -Az áttekinthetőség kedvéért javasoljuk, hogy kerüljük a `$this->onChange()` varázsmódszert. Ennek praktikus helyettesítője a [Nette\Utils\Arrays::invoke |arrays#invoke] függvény: +Az érthetőség kedvéért javasoljuk a mágikus `$this->onChange()` metódus elkerülését. Praktikus helyettesítője például a [Nette\Utils\Arrays::invoke |arrays#invoke] függvény: ```php Nette\Utils\Arrays::invoke($this->onChange, $this, $radius); diff --git a/utils/hu/staticclass.texy b/utils/hu/staticclass.texy new file mode 100644 index 0000000000..24fc8b3523 --- /dev/null +++ b/utils/hu/staticclass.texy @@ -0,0 +1,21 @@ +Statikus osztályok +****************** + +.[perex] +A StaticClass statikus osztályok megjelölésére szolgál. + + +Telepítés: + +```shell +composer require nette/utils +``` + +A statikus osztályokat, azaz azokat az osztályokat, amelyek nem példányosításra szolgálnak, megjelölheti a [api:Nette\StaticClass] trait-tel: + +```php +class Strings +{ + use Nette\StaticClass; +} +``` diff --git a/utils/hu/strings.texy b/utils/hu/strings.texy index e81a276a8b..d7eed1e469 100644 --- a/utils/hu/strings.texy +++ b/utils/hu/strings.texy @@ -1,8 +1,8 @@ -String funkciók -*************** +Stringekkel való munka +********************** .[perex] -[api:Nette\Utils\Strings] egy statikus osztály, amely számos hasznos függvényt tartalmaz az UTF-8 kódolt karakterláncokkal való munkához. +A [api:Nette\Utils\Strings] egy statikus osztály hasznos függvényekkel a stringekkel való munkához, főként UTF-8 kódolásban. Telepítés: @@ -11,83 +11,83 @@ Telepítés: composer require nette/utils ``` -Minden példa feltételezi, hogy a következő osztály alias van definiálva: +Minden példa feltételezi a létrehozott aliast: ```php use Nette\Utils\Strings; ``` -Letter Case .[#toc-letter-case] -=============================== +Kis- és nagybetűk váltása +========================= -Ezek a funkciók a `mbstring` PHP kiterjesztést igénylik. +Ezek a függvények a `mbstring` PHP kiterjesztést igénylik. lower(string $s): string .[method] ---------------------------------- -Az UTF-8 karakterlánc minden karakterét kisbetűvé alakítja. +Átalakít egy UTF-8 stringet kisbetűssé. ```php -Strings::lower('Hello world'); // 'hello world' +Strings::lower('Dobrý den'); // 'dobrý den' ``` upper(string $s): string .[method] ---------------------------------- -Az UTF-8 karakterlánc összes karakterét nagybetűvé alakítja. +Átalakít egy UTF-8 stringet nagybetűssé. ```php -Strings::upper('Hello world'); // 'HELLO WORLD' +Strings::upper('Dobrý den'); // 'DOBRÝ DEN' ``` firstUpper(string $s): string .[method] --------------------------------------- -Az UTF-8 karakterlánc első karakterét nagybetűvé alakítja, a többi karaktert változatlanul hagyja. +Átalakítja egy UTF-8 string első betűjét naggyá, a többit változatlanul hagyja. ```php -Strings::firstUpper('hello world'); // 'Hello world' +Strings::firstUpper('dobrý den'); // 'Dobrý den' ``` firstLower(string $s): string .[method] --------------------------------------- -Az UTF-8 karakterlánc első karakterét kisbetűssé alakítja, a többi karaktert változatlanul hagyja. +Átalakítja egy UTF-8 string első betűjét kissé, a többit változatlanul hagyja. ```php -Strings::firstLower('Hello world'); // 'hello world' +Strings::firstLower('Dobrý den'); // 'dobrý den' ``` capitalize(string $s): string .[method] --------------------------------------- -Egy UTF-8 karakterlánc minden szavának első karakterét nagybetűsre, a többit pedig kisbetűsre alakítja. +Átalakítja egy UTF-8 string minden szavának első betűjét naggyá, a többit kissé. ```php -Strings::capitalize('Hello world'); // 'Hello World' +Strings::capitalize('Dobrý den'); // 'Dobrý Den' ``` -Egy karakterlánc szerkesztése .[#toc-editing-a-string] -====================================================== +String módosítása +================= normalize(string $s): string .[method] -------------------------------------- -Eltávolítja a vezérlő karaktereket, normalizálja a sortöréseket a `\n` címre, eltávolítja a vezető és az utolsó üres sorokat, levágja a sorvégeket, normalizálja az UTF-8 formátumot az NFC normál formájára. +Eltávolítja a vezérlőkaraktereket, normalizálja a sorvégeket `\n`-re, levágja a kezdő és záró üres sorokat, levágja a sorok jobb oldali szóközét, normalizálja az UTF-8-at NFC normál formára. unixNewLines(string $s): string .[method] ----------------------------------------- -Átalakítja a sortöréseket a Unix rendszerekben használt `\n` címre. A sortörések a következők: `\n`, `\r`, `\r\n`, U+2028 sorválasztó, U+2029 bekezdésválasztó. +Átalakítja a sorvégeket `\n`-re, amelyet Unix rendszereken használnak. A sorvégek: `\n`, `\r`, `\r\n`, U+2028 line separator, U+2029 paragraph separator. ```php $unixLikeLines = Strings::unixNewLines($string); @@ -97,42 +97,42 @@ $unixLikeLines = Strings::unixNewLines($string); platformNewLines(string $s): string .[method] --------------------------------------------- -A sortörést az aktuális platformra jellemző karakterekre konvertálja, azaz Windowson a `\r\n`, máshol a `\n` karakterekre. A sortörések a következők: `\n`, `\r`, `\r\n`, U+2028 sorválasztó, U+2029 bekezdésválasztó. +Átalakítja a sorvégeket az aktuális platformra jellemző karakterekre, azaz `\r\n`-re Windows-on és `\n`-re máshol. A sorvégek: `\n`, `\r`, `\r\n`, U+2028 line separator, U+2029 paragraph separator. ```php $platformLines = Strings::platformNewLines($string); ``` -webalize(string $s, string $charlist=null, bool $lower=true): string .[method] ------------------------------------------------------------------------------- +webalize(string $s, ?string $charlist=null, bool $lower=true): string .[method] +------------------------------------------------------------------------------- -Az UTF-8 karakterláncot az URL-ben használt formára módosítja, azaz eltávolítja a diakritikus jeleket, és az angol ábécé betűinek és a számoknak a kivételével minden karaktert kötőjellel helyettesít. +Módosít egy UTF-8 stringet URL-ben használatos formára, azaz eltávolítja az ékezeteket, és az angol ábécé betűin és számjegyein kívül minden karaktert kötőjelre cserél. ```php -Strings::webalize('žluťoučký kůň'); // 'zlutoucky-kun' +Strings::webalize('náš produkt'); // 'nas-produkt' ``` -Más karakterek is megmaradhatnak, de azokat második argumentumként kell átadni. +Ha más karaktereket is meg kell őrizni, azokat a függvény második paraméterében lehet megadni. ```php -Strings::webalize('10. image_id', '._'); // '10.-image_id' +Strings::webalize('10. obrázek_id', '._'); // '10.-obrazek_id' ``` -A harmadik argumentum kiküszöbölheti a karakterlánc kisbetűvé alakítását. +A harmadik paraméterrel letiltható a kisbetűsre alakítás. ```php -Strings::webalize('Hello world', null, false); // 'Hello-world' +Strings::webalize('Dobrý den', null, false); // 'Dobry-den' ``` .[caution] -Szükséges a `intl` PHP kiterjesztés. +PHP `intl` kiterjesztést igényel. -trim(string $s, string $charlist=null): string .[method] --------------------------------------------------------- +trim(string $s, ?string $charlist=null): string .[method] +--------------------------------------------------------- -Eltávolítja a bal és jobb oldali szóközöket (vagy a második argumentumként átadott karaktereket) egy UTF-8 kódolt karakterláncból. +Levágja a szóközöket (vagy a második paraméterben megadott egyéb karaktereket) egy UTF-8 string elejéről és végéről. ```php Strings::trim(' Hello '); // 'Hello' @@ -142,21 +142,21 @@ Strings::trim(' Hello '); // 'Hello' truncate(string $s, int $maxLen, string $append=`'…'`): string .[method] ------------------------------------------------------------------------ -Az UTF-8 karakterláncot a megadott maximális hosszúságra vágja le, miközben megpróbálja elkerülni az egész szavak szétválasztását. Csak akkor, ha a karakterlánc csonkolva van, egy ellipszist (vagy valami mást, amit a harmadik argumentummal állítunk be) csatol a karakterlánchoz. +Levág egy UTF-8 stringet a megadott maximális hosszúságra, miközben igyekszik megőrizni az egész szavakat. Ha a string lerövidül, a végére három pontot tesz (ez a harmadik paraméterrel módosítható). ```php -$text = 'Hello, how are you today?'; -Strings::truncate($text, 5); // 'Hell…' -Strings::truncate($text, 20); // 'Hello, how are you…' -Strings::truncate($text, 30); // 'Hello, how are you today?' -Strings::truncate($text, 20, '~'); // 'Hello, how are you~' +$text = 'Řekněte, jak se máte?'; +Strings::truncate($text, 5); // 'Řekn…' +Strings::truncate($text, 20); // 'Řekněte, jak se…' +Strings::truncate($text, 30); // 'Řekněte, jak se máte?' +Strings::truncate($text, 20, '~'); // 'Řekněte, jak se~' ``` indent(string $s, int $level=1, string $indentationChar=`"\t"`): string .[method] --------------------------------------------------------------------------------- -Többsoros szöveg balról történő behúzása. A második argumentum határozza meg, hogy hány behúzásjelet használjon, míg maga a behúzás a harmadik argumentum (alapértelmezés szerint *tab*). +Behúz egy többsoros szöveget balról. A behúzások számát a második paraméter, a behúzás karakterét a harmadik paraméter határozza meg (alapértelmezett érték a tabulátor). ```php Strings::indent('Nette'); // "\tNette" @@ -167,7 +167,7 @@ Strings::indent('Nette', 2, '+'); // '++Nette' padLeft(string $s, int $length, string $pad=`' '`): string .[method] -------------------------------------------------------------------- -Kitölti az UTF-8 karakterláncot a megadott hosszúságra a `$pad` karakterlánc elejére történő előtagolással. +Kiegészít egy UTF-8 stringet a megadott hosszúságra a `$pad` string ismétlésével balról. ```php Strings::padLeft('Nette', 6); // ' Nette' @@ -178,7 +178,7 @@ Strings::padLeft('Nette', 8, '+*'); // '+*+Nette' padRight(string $s, int $length, string $pad=`' '`): string .[method] --------------------------------------------------------------------- -UTF-8 karakterlánc kitöltése megadott hosszúságúra a `$pad` karakterlánc végéhez való hozzáadásával. +Kiegészít egy UTF-8 stringet a megadott hosszúságra a `$pad` string ismétlésével jobbról. ```php Strings::padRight('Nette', 6); // 'Nette ' @@ -186,10 +186,10 @@ Strings::padRight('Nette', 8, '+*'); // 'Nette+*+' ``` -substring(string $s, int $start, int $length=null): string .[method] --------------------------------------------------------------------- +substring(string $s, int $start, ?int $length=null): string .[method] +--------------------------------------------------------------------- -Visszaadja az UTF-8 karakterlánc egy részét, amelyet a `$start` kezdőpozíció és a `$length` hossza határoz meg. Ha a `$start` negatív, akkor a visszaadott karakterlánc a karakterlánc végétől számított `$start`'th karaktertől kezdődik. +Visszaadja a `$s` UTF-8 string egy részét, amelyet a `$start` kezdőpozíció és a `$length` hosszúság határoz meg. Ha a `$start` negatív, a visszaadott string a végétől számított -`$start` karakterrel kezdődik. ```php Strings::substring('Nette Framework', 0, 5); // 'Nette' @@ -201,7 +201,7 @@ Strings::substring('Nette Framework', -4); // 'work' reverse(string $s): string .[method] ------------------------------------ -Megfordítja az UTF-8 karakterláncot. +Megfordít egy UTF-8 stringet. ```php Strings::reverse('Nette'); // 'etteN' @@ -211,77 +211,77 @@ Strings::reverse('Nette'); // 'etteN' length(string $s): int .[method] -------------------------------- -Visszaadja az UTF-8 karakterláncban lévő karakterek (nem bájtok) számát. +Visszaadja a karakterek (nem bájtok) számát egy UTF-8 stringben. -Ez a Unicode kódpontok száma, ami eltérhet a graphemák számától. +Ez az Unicode kódpontok száma, amely eltérhet a grafémák számától. ```php -Strings::length('Nette'); // 5 -Strings::length('red'); // 3 +Strings::length('Nette'); // 5 +Strings::length('červená'); // 7 ``` startsWith(string $haystack, string $needle): bool .[method deprecated] ----------------------------------------------------------------------- -Ellenőrzi, hogy a `$haystack` karakterlánc kezdőbetűje `$needle`. +Megállapítja, hogy a `$haystack` string a `$needle` stringgel kezdődik-e. ```php -$haystack = 'Begins'; -$needle = 'Be'; +$haystack = 'Začíná'; +$needle = 'Za'; Strings::startsWith($haystack, $needle); // true ``` .[note] -Használja a natív `str_starts_with()`:https://www.php.net/manual/en/function.str-starts-with.php. +Használja a natív `str_starts_with()`:https://www.php.net/manual/en/function.str-starts-with.php függvényt. endsWith(string $haystack, string $needle): bool .[method deprecated] --------------------------------------------------------------------- -Ellenőrzi, hogy a `$haystack` karakterlánc a `$needle` végződéssel végződik-e. +Megállapítja, hogy a `$haystack` string a `$needle` stringgel végződik-e. ```php -$haystack = 'Ends'; -$needle = 'ds'; +$haystack = 'Končí'; +$needle = 'čí'; Strings::endsWith($haystack, $needle); // true ``` .[note] -Használja a natív `str_ends_with()`:https://www.php.net/manual/en/function.str-ends-with.php. +Használja a natív `str_ends_with()`:https://www.php.net/manual/en/function.str-ends-with.php függvényt. contains(string $haystack, string $needle): bool .[method deprecated] --------------------------------------------------------------------- -Ellenőrzi, hogy a `$haystack` karakterlánc tartalmazza-e a `$needle` címet. +Megállapítja, hogy a `$haystack` string tartalmazza-e a `$needle`-t. ```php -$haystack = 'Contains'; -$needle = 'tai'; +$haystack = 'Posluchárna'; +$needle = 'sluch'; Strings::contains($haystack, $needle); // true ``` .[note] -Használja a natív `str_contains()`:https://www.php.net/manual/en/function.str-contains.php. +Használja a natív `str_contains()`:https://www.php.net/manual/en/function.str-contains.php függvényt. -compare(string $left, string $right, int $length=null): bool .[method] ----------------------------------------------------------------------- +compare(string $left, string $right, ?int $length=null): bool .[method] +----------------------------------------------------------------------- -Összehasonlít két UTF-8 karakterláncot vagy azok részeit, a karakterek esetének figyelembevétele nélkül. Ha a `$length` értéke nulla, akkor egész karakterláncokat hasonlít össze, ha negatív, akkor a karakterláncok végétől számított megfelelő számú karaktert, ellenkező esetben az elejétől számított megfelelő számú karaktert hasonlít össze. +Két UTF-8 string vagy azok részeinek összehasonlítása kis- és nagybetűk figyelmen kívül hagyásával. Ha a `$length` null, a teljes stringeket hasonlítja össze, ha negatív, a stringek végétől számított megfelelő számú karaktert hasonlítja össze, egyébként a stringek elejétől számított megfelelő számú karaktert hasonlítja össze. ```php Strings::compare('Nette', 'nette'); // true -Strings::compare('Nette', 'next', 2); // true - two first characters match -Strings::compare('Nette', 'Latte', -2); // true - two last characters match +Strings::compare('Nette', 'next', 2); // true - az első 2 karakter egyezik +Strings::compare('Nette', 'Latte', -2); // true - az utolsó 2 karakter egyezik ``` findPrefix(...$strings): string .[method] ----------------------------------------- -Megkeresi a karakterláncok közös előtagját, vagy üres karakterláncot ad vissza, ha az előtagot nem találta meg. +Megkeresi a stringek közös elejét. Vagy üres stringet ad vissza, ha nem található közös prefix. ```php Strings::findPrefix('prefix-a', 'prefix-bb', 'prefix-c'); // 'prefix-' @@ -293,7 +293,7 @@ Strings::findPrefix('Nette', 'is', 'great'); // '' before(string $haystack, string $needle, int $nth=1): ?string .[method] ----------------------------------------------------------------------- -Visszaadja a `$haystack` egy részét a `$needle` `$nth` előfordulása előtt, vagy visszaadja a `null` -t, ha a tűt nem találták. A negatív érték azt jelenti, hogy a végétől kezdve keres. +Visszaadja a `$haystack` stringnek a `$needle` string n-edik `$nth` előfordulása előtti részét. Vagy `null`-t, ha a `$needle` nem található. Negatív `$nth` érték esetén a string végétől keres. ```php Strings::before('Nette_is_great', '_', 1); // 'Nette' @@ -306,7 +306,7 @@ Strings::before('Nette_is_great', '_', 3); // null after(string $haystack, string $needle, int $nth=1): ?string .[method] ---------------------------------------------------------------------- -Visszaadja a `$haystack` egy részét a `$needle` `$nth` előfordulása után, vagy visszaadja a `null` -t, ha a `$needle` nem található. A `$nth` negatív értéke azt jelenti, hogy a végétől kezdve keressük. +Visszaadja a `$haystack` stringnek a `$needle` string n-edik `$nth` előfordulása utáni részét. Vagy `null`-t, ha a `$needle` nem található. Negatív `$nth` érték esetén a string végétől keres. ```php Strings::after('Nette_is_great', '_', 2); // 'great' @@ -319,7 +319,7 @@ Strings::after('Nette_is_great', '_', 3); // null indexOf(string $haystack, string $needle, int $nth=1): ?int .[method] --------------------------------------------------------------------- -Visszaadja a `$nth` karakterekben kifejezett pozícióját a `$needle` előfordulásának a `$haystack` vagy a `null` oldalon, ha a `$needle` nem található. A `$nth` negatív értéke azt jelenti, hogy a végétől kezdve keressük. +Visszaadja a `$needle` string n-edik `$nth` előfordulásának pozícióját karakterekben a `$haystack` stringben. Vagy `null`-t, ha a `$needle` nem található. Negatív `$nth` érték esetén a string végétől keres. ```php Strings::indexOf('abc abc abc', 'abc', 2); // 4 @@ -328,14 +328,14 @@ Strings::indexOf('abc abc abc', 'd'); // null ``` -Kódolás .[#toc-encoding] -======================== +Kódolás +======= fixEncoding(string $s): string .[method] ---------------------------------------- -Eltávolítja az összes érvénytelen UTF-8 karaktert egy karakterláncból. +Eltávolítja az érvénytelen UTF-8 karaktereket a stringből. ```php $correctStrings = Strings::fixEncoding($string); @@ -345,59 +345,59 @@ $correctStrings = Strings::fixEncoding($string); checkEncoding(string $s): bool .[method deprecated] --------------------------------------------------- -Ellenőrzi, hogy a karakterlánc érvényes-e UTF-8 kódolásban. +Megállapítja, hogy érvényes UTF-8 stringről van-e szó. ```php $isUtf8 = Strings::checkEncoding($string); ``` .[note] -Használja a [Nette\Utils\Validator::isUnicode() |validators#isUnicode] függvényt. +Használja a [Nette\Utils\Validator::isUnicode() |validators#isUnicode] metódust. toAscii(string $s): string .[method] ------------------------------------ -Az UTF-8 karakterláncot ASCII-re konvertálja, azaz eltávolítja a diakritikus jeleket stb. +Átalakít egy UTF-8 stringet ASCII-ra, azaz eltávolítja az ékezeteket stb. ```php Strings::toAscii('žluťoučký kůň'); // 'zlutoucky kun' ``` .[caution] -PHP kiterjesztést igényel: `intl`. +PHP `intl` kiterjesztést igényel. chr(int $code): string .[method] -------------------------------- -Egy adott UTF-8 karaktert ad vissza kódpontból (szám a 0x0000..D7FF vagy 0xE000..10FFFF tartományban). +Visszaad egy specifikus UTF-8 karaktert a kódpontból (szám a 0x0000..D7FF és 0xE000..10FFFF tartományban). ```php -Strings::chr(0xA9); // '©' +Strings::chr(0xA9); // '©' UTF-8 kódolásban ``` ord(string $char): int .[method] -------------------------------- -Egy adott UTF-8 karakter kódpontját adja vissza (szám a 0x0000..D7FF vagy 0xE000..10FFFF tartományban). +Visszaadja egy konkrét UTF-8 karakter kódpontját (szám a 0x0000..D7FF vagy 0xE000..10FFFF tartományban). ```php Strings::ord('©'); // 0xA9 ``` -Szabályos kifejezések .[#toc-regular-expressions] -================================================= +Reguláris kifejezések +===================== -A Strings osztály függvényeket biztosít a reguláris kifejezésekkel való munkához. A natív PHP függvényektől eltérően érthetőbb API-val, jobb Unicode támogatással és ami a legfontosabb, hibaérzékeléssel rendelkeznek. Bármilyen fordítási vagy kifejezésfeldolgozási hiba a `Nette\RegexpException` kivételt dob. +A Strings osztály függvényeket kínál a reguláris kifejezésekkel való munkához. A natív PHP függvényekkel ellentétben érthetőbb API-val, jobb Unicode támogatással és mindenekelőtt hibadetektálással rendelkeznek. Bármilyen hiba a kifejezés fordítása vagy feldolgozása során `Nette\RegexpException` kivételt dob. split(string $subject, string $pattern, bool $captureOffset=false, bool $skipEmpty=false, int $limit=-1, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------- -A karakterláncot a reguláris kifejezésnek megfelelően tömbökre osztja. A zárójelben lévő kifejezéseket is rögzíti és visszaadja. +Feloszt egy stringet egy tömbre reguláris kifejezés alapján. A zárójelben lévő kifejezések is rögzítésre és visszaadásra kerülnek. ```php Strings::split('hello, world', '~,\s*~'); @@ -407,7 +407,7 @@ Strings::split('hello, world', '~(,)\s*~'); // ['hello', ',', 'world']`` ``` -Ha a `$skipEmpty` értéke `true`, csak a nem üres elemek kerülnek visszaadásra: +Ha a `$skipEmpty` `true`, csak a nem üres elemek kerülnek visszaadásra: ```php Strings::split('hello, world, ', '~,\s*~'); @@ -417,16 +417,16 @@ Strings::split('hello, world, ', '~,\s*~', skipEmpty: true); // ['hello', 'world'] ``` -Ha `$limit` van megadva, akkor csak a határértékig terjedő részláncok kerülnek vissza, a string többi része pedig az utolsó elembe kerül. A -1 vagy 0 értékű határérték azt jelenti, hogy nincs határérték. +Ha a `$limit` meg van adva, csak a limitig terjedő részstringek kerülnek visszaadásra, és a string többi része az utolsó elembe kerül. A -1 vagy 0 limit nem jelent korlátozást. ```php Strings::split('hello, world, third', '~,\s*~', limit: 2); // ['hello', 'world, third'] ``` -Ha a `$utf8` a `true`, a kiértékelés Unicode módra vált. Ez hasonló az `u` módosító megadásához. +Ha az `$utf8` `true`, az értékelés Unicode módba vált. Hasonlóan ahhoz, mintha az `u` módosítót adná meg. -Ha a `$captureOffset` a `true`, akkor minden egyes előforduló egyezés esetén annak a karakterláncban elfoglalt helye is visszaküldésre kerül (bájtban; karakterekben, ha a `$utf8` be van állítva). Ez a visszatérési értéket egy tömbre változtatja, amelynek minden eleme egy pár, amely a megfelelő karakterláncból és annak pozíciójából áll. +Ha a `$captureOffset` `true`, minden előforduló egyezéshez visszaadásra kerül a pozíciója is a stringben (bájtokban; ha az `$utf8` be van állítva, akkor karakterekben). Ez megváltoztatja a visszatérési értéket egy tömbre, ahol minden elem egy pár, amely az egyező stringből és annak pozíciójából áll. ```php Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true); @@ -440,7 +440,7 @@ Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true, utf8: true); match(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $utf8=false): ?array .[method] -------------------------------------------------------------------------------------------------------------------------------------------------- -Megkeresi a karakterláncban a reguláris kifejezésnek megfelelő részt, és egy tömböt ad vissza a talált kifejezéssel és az egyes részkifejezésekkel, vagy `null`. +Keres a stringben egy részt, amely megfelel a reguláris kifejezésnek, és visszaad egy tömböt a talált kifejezéssel és az egyes részkifejezésekkel, vagy `null`-t. ```php Strings::match('hello!', '~\w+(!+)~'); @@ -450,7 +450,7 @@ Strings::match('hello!', '~X~'); // null ``` -Ha a `$unmatchedAsNull` a `true`, a nem illeszkedő részminták nullaként kerülnek visszaadásra; egyébként üres karakterláncként vagy nem kerülnek visszaadásra: +Ha az `$unmatchedAsNull` `true`, a nem rögzített részminták null-ként kerülnek visszaadásra; egyébként üres stringként vagy nem kerülnek visszaadásra: ```php Strings::match('hello', '~\w+(!+)?~'); @@ -460,7 +460,7 @@ Strings::match('hello', '~\w+(!+)?~', unmatchedAsNull: true); // ['hello', null] ``` -Ha a `$utf8` a `true`, az értékelés Unicode módra vált. Ez hasonló az `u` módosító megadásához: +Ha az `$utf8` `true`, az értékelés Unicode módba vált. Hasonlóan ahhoz, mintha az `u` módosítót adná meg: ```php Strings::match('žlutý kůň', '~\w+~'); @@ -470,9 +470,9 @@ Strings::match('žlutý kůň', '~\w+~', utf8: true); // ['žlutý'] ``` -A `$offset` paraméterrel megadható a keresés kezdőpozíciója (bájtokban; karakterekben, ha a `$utf8` be van állítva). +A `$offset` paraméterrel megadható a keresés kezdőpozíciója (bájtokban; ha az `$utf8` be van állítva, akkor karakterekben). -Ha a `$captureOffset` a `true`, akkor minden egyes előforduló találat esetén annak a karakterláncban elfoglalt pozíciója is visszaküldésre kerül (bájtokban; karakterekben, ha a `$utf8` be van állítva). Ezáltal a visszatérési érték egy olyan tömbre változik, amelynek minden eleme egy pár, amely a megfelelő karakterláncból és annak eltolásából áll: +Ha a `$captureOffset` `true`, minden előforduló egyezéshez visszaadásra kerül a pozíciója is a stringben (bájtokban; ha az `$utf8` be van állítva, akkor karakterekben). Ez megváltoztatja a visszatérési értéket egy tömbre, ahol minden elem egy pár, amely az egyező stringből és annak eltolásából áll: ```php Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true); @@ -483,10 +483,10 @@ Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true, utf8: true); ``` -matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false, bool $lazy=false): array|Generator .[method] +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Megkeresi a karakterlánc minden olyan előfordulását, amely megfelel a reguláris kifejezésnek, és egy olyan tömböt ad vissza, amely tartalmazza a talált kifejezést és az egyes részkifejezéseket. +Keres a stringben minden olyan előfordulást, amely megfelel a reguláris kifejezésnek, és visszaad egy tömbökből álló tömböt a talált kifejezéssel és az egyes részkifejezésekkel. ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~'); @@ -496,7 +496,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~'); ] */ ``` -Ha a `$patternOrder` értéke `true`, akkor az eredmények szerkezete úgy változik, hogy az első elem a teljes minta egyezéseinek tömbje, a második az első almintának megfelelő karakterláncok tömbje zárójelben, és így tovább: +Ha a `$patternOrder` `true`, az eredmények szerkezete megváltozik úgy, hogy az első elem a teljes mintaegyezések tömbje, a második a zárójelben lévő első részmintának megfelelő stringek tömbje, és így tovább: ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~', patternOrder: true); @@ -506,7 +506,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~', patternOrder: true); ] */ ``` -Ha a `$unmatchedAsNull` a `true`, a nem illeszkedő részminták nullaként kerülnek visszaadásra; egyébként üres karakterláncként kerülnek visszaadásra, vagy nem kerülnek visszaadásra: +Ha az `$unmatchedAsNull` `true`, a nem rögzített részminták null-ként kerülnek visszaadásra; egyébként üres stringként vagy nem kerülnek visszaadásra: ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~', unmatchedAsNull: true); @@ -516,7 +516,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~', unmatchedAsNull: true); ] */ ``` -Ha a `$utf8` a `true`, az értékelés Unicode módra vált. Ez hasonló az `u` módosító megadásához: +Ha az `$utf8` `true`, az értékelés Unicode módba vált. Hasonlóan ahhoz, mintha az `u` módosítót adná meg: ```php Strings::matchAll('žlutý kůň', '~\w+~'); @@ -532,9 +532,9 @@ Strings::matchAll('žlutý kůň', '~\w+~', utf8: true); ] */ ``` -A `$offset` paraméterrel megadható a keresés kezdőpozíciója (bájtokban; karakterekben, ha a `$utf8` be van állítva). +A `$offset` paraméterrel megadható a keresés kezdőpozíciója (bájtokban; ha az `$utf8` be van állítva, akkor karakterekben). -Ha a `$captureOffset` a `true`, akkor minden egyes előforduló találat esetén annak a karakterláncban elfoglalt pozíciója is visszaküldésre kerül (bájtokban; karakterekben, ha a `$utf8` be van állítva). Ezáltal a visszatérési érték egy olyan tömbre változik, amelynek minden eleme egy pár, amely a megfelelő karakterláncból és annak pozíciójából áll: +Ha a `$captureOffset` `true`, minden előforduló egyezéshez visszaadásra kerül a pozíciója is a stringben (bájtokban; ha az `$utf8` be van állítva, akkor karakterekben). Ez megváltoztatja a visszatérési értéket egy tömbre, ahol minden elem egy pár, amely az egyező stringből és annak pozíciójából áll: ```php Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true); @@ -550,11 +550,21 @@ Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true, utf8: true); ] */ ``` +Ha a `$lazy` `true`, a függvény `Generator`-t ad vissza tömb helyett, ami jelentős teljesítményelőnyökkel jár nagy stringekkel való munka során. A generátor lehetővé teszi az egyezések fokozatos keresését, ahelyett, hogy az egész stringet egyszerre keresné. Ez lehetővé teszi a rendkívül nagy bemeneti szövegek hatékony feldolgozását is. Ráadásul bármikor megszakíthatja a feldolgozást, ha megtalálja a keresett egyezést, ami számítási időt takarít meg. + +```php +$matches = Strings::matchAll($largeText, '~\w+~', lazy: true); +foreach ($matches as $match) { + echo "Találat: $match[0]\n"; + // A feldolgozás bármikor megszakítható +} +``` + replace(string $subject, string|array $pattern, string|callable $replacement='', int $limit=-1, bool $captureOffset=false, bool $unmatchedAsNull=false, bool $utf8=false): string .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -A reguláris kifejezésnek megfelelő összes előfordulást helyettesíti. A `$replacement` vagy egy helyettesítő karakterlánc maszkja vagy egy visszahívás. +Lecseréli az összes olyan előfordulást, amely megfelel a reguláris kifejezésnek. A `$replacement` vagy egy helyettesítő string maszkja, vagy egy callback. ```php Strings::replace('hello, world!', '~\w+~', '--'); @@ -564,7 +574,7 @@ Strings::replace('hello, world!', '~\w+~', fn($m) => strrev($m[0])); // 'olleh, dlrow!' ``` -A függvény többszörös helyettesítést is lehetővé tesz, ha a második paraméterben egy `pattern => replacement` formájú tömböt ad át: +A funkció lehetővé teszi több csere végrehajtását is azáltal, hogy a második paraméterben egy `pattern => replacement` alakú tömböt adunk át: ```php Strings::replace('hello, world!', [ @@ -574,9 +584,9 @@ Strings::replace('hello, world!', [ // '-- --!' ``` -A `$limit` paraméter korlátozza a helyettesítések számát. A -1-es korlát azt jelenti, hogy nincs korlát. +A `$limit` paraméter korlátozza a végrehajtott cserék számát. A -1 limit nem jelent korlátozást. -Ha a `$utf8` a `true`, a kiértékelés Unicode módra vált. Ez hasonló a `u` módosító megadásához. +Ha az `$utf8` `true`, az értékelés Unicode módba vált. Hasonlóan ahhoz, mintha az `u` módosítót adná meg. ```php Strings::replace('žlutý kůň', '~\w+~', '--'); @@ -586,7 +596,7 @@ Strings::replace('žlutý kůň', '~\w+~', '--', utf8: true); // '-- --' ``` -Ha a `$captureOffset` a `true`, akkor minden egyes előforduló egyezés esetén a karakterláncban elfoglalt pozícióját (bájtokban; karakterekben, ha a `$utf8` be van állítva) is átadjuk a visszahívásnak. Ez megváltoztatja az átadott tömb formáját, ahol minden elem egy pár, amely az illesztett karakterláncból és annak pozíciójából áll. +Ha a `$captureOffset` `true`, minden előforduló egyezéshez a callbacknek átadásra kerül a pozíciója is a stringben (bájtokban; ha az `$utf8` be van állítva, akkor karakterekben). Ez megváltoztatja az átadott tömb formáját, ahol minden elem egy pár, amely az egyező stringből és annak pozíciójából áll. ```php Strings::replace( @@ -595,7 +605,7 @@ Strings::replace( function (array $m) { dump($m); return ''; }, captureOffset: true, ); -// dumps [['lut', 2]] a [['k', 8]] +// dumps [['lut', 2]] és [['k', 8]] Strings::replace( 'žlutý kůň', @@ -604,10 +614,10 @@ Strings::replace( captureOffset: true, utf8: true, ); -// dumps [['žlutý', 0]] a [['kůň', 6]] +// dumps [['žlutý', 0]] és [['kůň', 6]] ``` -Ha a `$unmatchedAsNull` a `true`, a nem illeszkedő részminták nullaként kerülnek átadásra a callbacknek; egyébként üres karakterláncként kerülnek átadásra vagy nem kerülnek átadásra: +Ha az `$unmatchedAsNull` `true`, a nem rögzített részminták null-ként kerülnek átadásra a callbacknek; egyébként üres stringként vagy nem kerülnek átadásra: ```php Strings::replace( diff --git a/utils/hu/type.texy b/utils/hu/type.texy index 41590b3fae..9a60145cc8 100644 --- a/utils/hu/type.texy +++ b/utils/hu/type.texy @@ -1,8 +1,8 @@ -PHP típus +PHP Típus ********* .[perex] -[api:Nette\Utils\Type] egy PHP adattípus osztály. +A [api:Nette\Utils\Type] egy osztály a PHP adattípusokkal való munkához. Telepítés: @@ -11,7 +11,7 @@ Telepítés: composer require nette/utils ``` -Minden példa feltételezi, hogy a következő osztály alias van definiálva: +Minden példa feltételezi a létrehozott aliast: ```php use Nette\Utils\Type; @@ -21,7 +21,7 @@ use Nette\Utils\Type; fromReflection($reflection): ?Type .[method] -------------------------------------------- -A statikus metódus létrehoz egy Type objektumot a tükrözés alapján. A paraméter lehet egy `ReflectionMethod` vagy `ReflectionFunction` objektum (visszaadja a visszatérési érték típusát) vagy egy `ReflectionParameter` vagy `ReflectionProperty` objektum. Feloldja a `self`, `static` és `parent` a tényleges osztálynévre. Ha a tárgynak nincs típusa, akkor a `null` értéket adja vissza. +Statikus metódus, amely Type objektumot hoz létre reflexió alapján. A paraméter lehet `ReflectionMethod` vagy `ReflectionFunction` objektum (a visszatérési érték típusát adja vissza) vagy `ReflectionParameter` vagy `ReflectionProperty`. Lefordítja a `self`, `static` és `parent` szavakat a tényleges osztálynévre. Ha az alanynak nincs típusa, `null`-t ad vissza. ```php class DemoClass @@ -37,7 +37,7 @@ echo Type::fromReflection($prop); // 'DemoClass' fromString(string $type): Type .[method] ---------------------------------------- -A statikus metódus létrehozza a Type objektumot a szöveges jelölésnek megfelelően. +Statikus metódus, amely Type objektumot hoz létre szöveges leírás alapján. ```php $type = Type::fromString('Foo|Bar'); @@ -51,7 +51,7 @@ getNames(): (string|array)[] .[method] Visszaadja az összetett típust alkotó altípusok tömbjét stringként. ```php -$type = Type::fromString('string|null'); // nebo '?string' +$type = Type::fromString('string|null'); // vagy '?string' $type->getNames(); // ['string', 'null'] $type = Type::fromString('(Foo&Bar)|string'); @@ -62,7 +62,7 @@ $type->getNames(); // [['Foo', 'Bar'], 'string'] getTypes(): Type[] .[method] ---------------------------- -Visszaadja az összetett típust alkotó altípusok tömbjét `Type` objektumként: +Visszaadja az összetett típust alkotó altípusok tömbjét `ReflectionType` objektumként: ```php $type = Type::fromString('string|null'); // or '?string' @@ -79,7 +79,7 @@ $type->getTypes(); // [Type::fromString('Foo'), Type::fromString('Bar')] getSingleName(): ?string .[method] ---------------------------------- -Egyszerű típusok esetén visszaadja a típus nevét, egyébként null. +Egyszerű típusok esetén visszaadja a típus nevét, egyébként null-t. ```php $type = Type::fromString('string|null'); @@ -99,14 +99,14 @@ echo $type->getSingleName(); // null isSimple(): bool .[method] -------------------------- -Visszaadja, hogy egyszerű típusról van-e szó. Az egyszerű nullázható típusok is egyszerű típusoknak tekintendők: +Visszaadja, hogy egyszerű típusról van-e szó. Az egyszerű nullable típusok is egyszerűnek számítanak: ```php $type = Type::fromString('string'); $type->isSimple(); // true $type->isUnion(); // false -$type = Type::fromString('?Foo'); // nebo 'Foo|null' +$type = Type::fromString('?Foo'); // vagy 'Foo|null' $type->isSimple(); // true $type->isUnion(); // true ``` @@ -118,7 +118,7 @@ isUnion(): bool .[method] Visszaadja, hogy union típusról van-e szó. ```php -$type = Type::fromString('Foo&Bar'); +$type = Type::fromString('string|int'); $type->isUnion(); // true ``` @@ -126,11 +126,11 @@ $type->isUnion(); // true isIntersection(): bool .[method] -------------------------------- -Visszaadja, hogy metszet típus-e. +Visszaadja, hogy intersection típusról van-e szó. ```php -$type = Type::fromString('string&int'); +$type = Type::fromString('Foo&Bar'); $type->isIntersection(); // true ``` @@ -138,7 +138,7 @@ $type->isIntersection(); // true isBuiltin(): bool .[method] --------------------------- -Visszaadja, hogy a típus egyszerre egyszerű és PHP beépített típus-e. +Visszaadja, hogy a típus egyszerű és egyben beépített PHP típus-e. ```php $type = Type::fromString('string'); @@ -155,7 +155,7 @@ $type->isBuiltin(); // false isClass(): bool .[method] ------------------------- -Visszaadja, hogy a típus egyszerre egyszerű és osztálynév. +Visszaadja, hogy a típus egyszerű és egyben osztálynév-e. ```php $type = Type::fromString('string'); @@ -172,7 +172,7 @@ $type->isClass(); // false isClassKeyword(): bool .[method] -------------------------------- -Meghatározza, hogy a típus a `self`, `parent`, `static` belső típusok egyike-e. . +Visszaadja, hogy a típus a `self`, `parent`, `static` belső típusok egyike-e. ```php $type = Type::fromString('self'); @@ -186,7 +186,7 @@ $type->isClassKeyword(); // false allows(string $type): bool .[method] ------------------------------------ -A `allows()` módszer ellenőrzi a típus kompatibilitást. Például lehetővé teszi annak ellenőrzését, hogy egy bizonyos típusú érték átadható-e paraméterként. +Az `allows()` metódus ellenőrzi a típuskompatibilitást. Például lehetővé teszi annak megállapítását, hogy egy adott típusú érték átadható-e paraméterként. ```php $type = Type::fromString('string|null'); diff --git a/utils/hu/validators.texy b/utils/hu/validators.texy index 29d5510af8..91dc73414f 100644 --- a/utils/hu/validators.texy +++ b/utils/hu/validators.texy @@ -1,8 +1,8 @@ -Értékellenőrzők -*************** +Érték validátorok +***************** .[perex] -Gyorsan és egyszerűen szeretné ellenőrizni, hogy egy változó tartalmaz-e például érvényes e-mail címet? Akkor jól fog jönni a [api:Nette\Utils\Validators], egy statikus osztály, amely hasznos függvényeket tartalmaz az értékek érvényesítéséhez. +Gyorsan és egyszerűen kell ellenőriznie, hogy egy változóban például érvényes e-mail cím van-e? Ehhez hasznos lesz a [api:Nette\Utils\Validators], egy statikus osztály hasznos függvényekkel az értékek validálásához. Telepítés: @@ -11,17 +11,17 @@ Telepítés: composer require nette/utils ``` -Minden példa feltételezi, hogy a következő osztály alias van definiálva: +Minden példa feltételezi a létrehozott aliast: ```php use Nette\Utils\Validators; ``` -Alapvető használat .[#toc-basic-usage] -====================================== +Alapvető használat +================== -A `Validators` osztály számos módszerrel rendelkezik az értékek érvényesítésére, mint például az [isList() |#isList()], [isUnicode() |#isUnicode()], [isEmail() |#isEmail()], [isUrl() |#isUrl()], stb., amelyeket a kódodban használhatsz: +Az osztály számos metódussal rendelkezik az értékek ellenőrzésére, mint például a [#isUnicode()], [#isEmail()], [#isUrl()] stb., amelyeket a kódjában használhat: ```php if (!Validators::isEmail($email)) { @@ -29,7 +29,7 @@ if (!Validators::isEmail($email)) { } ``` -Továbbá képes ellenőrizni, hogy az érték megfelel-e az úgynevezett [elvárt típusoknak |#expected types], ami egy olyan karakterlánc, amelyben az egyes opciókat függőleges vonallal `|` választják el. Ez megkönnyíti az unió típusok ellenőrzését az [if() |#if()] segítségével: +Továbbá képes ellenőrizni, hogy az érték ún. [#várt típusok]-e, ami egy string, ahol az egyes lehetőségeket függőleges vonal `|` választja el. Így könnyen ellenőrizhetünk több típust a [#is()] segítségével: ```php if (!Validators::is($val, 'int|string|bool')) { @@ -37,81 +37,81 @@ if (!Validators::is($val, 'int|string|bool')) { } ``` -De lehetőséget ad arra is, hogy olyan rendszert hozzunk létre, ahol az elvárásokat stringként kell leírni (például annotációkban vagy konfigurációban), majd ezek szerint ellenőrizni. +De ez lehetőséget ad arra is, hogy olyan rendszert hozzunk létre, ahol az elvárásokat stringként kell megadni (például annotációkban vagy konfigurációban), majd ezek alapján ellenőrizni az értékeket. -Olyan [állítást |#assert] is deklarálhatsz, ami kivételt dob, ha nem teljesül. +A várt típusokra követelményt is állíthatunk a [#assert()] segítségével, amely ha nem teljesül, kivételt dob. -Várható típusok .[#toc-expected-types] -====================================== +Várt típusok +============ -Az elvárt típusok egy vagy több, függőleges vonallal elválasztott változatból álló karakterlánc `|`, similar to writing types in PHP (ie. `'int|string|bool')`. A nullázható jelölés is megengedett `?int`. +A várt típusok egy stringet alkotnak, amely egy vagy több, függőleges vonallal `|` elválasztott változatból áll, hasonlóan ahhoz, ahogy a típusokat PHP-ban írják (pl. `'int|string|bool'`). Elfogadott a nullable jelölés `?int` is. -Egy olyan tömböt, amelynek minden eleme egy bizonyos típusba tartozik, a `int[]` formában írunk le. +Egy tömb, ahol minden elem egy bizonyos típusú, `int[]` formában íródik. -Egyes típusokat kettőspont és a hossz `:length` vagy a tartomány követheti. `:[min]..[max]`, pl. `string:10` (10 bájt hosszúságú karakterlánc), `float:10..` (10-es és annál nagyobb szám), `array:..10` (legfeljebb tíz elemű tömb) vagy `list:10..20` (10-20 elemű lista), vagy egy reguláris kifejezés a következőre `pattern:[0-9]+`. +Néhány típus után kettőspont és hossz `:length` vagy tartomány `:[min]..[max]` következhet, pl. `string:10` (10 bájt hosszúságú string), `float:10..` (10-es vagy nagyobb szám), `array:..10` (legfeljebb tíz elemű tömb) vagy `list:10..20` (10-20 elemű lista), illetve reguláris kifejezés a `pattern:[0-9]+` esetén. -A típusok és szabályok áttekintése: +Típusok és szabályok áttekintése: .[wide] -| PHP típusok || +| PHP típusok || |-------------------------- -| `array` .{width: 140px} | az elemek számának tartománya megadható. -| `bool` | -| `float` | értéktartományt lehet megadni. -| `int` | az érték tartománya megadható. -| `null` | -| `object` | +| `array` .{width: 140px} | megadható tartomány az elemek számára +| `bool` | +| `float` | megadható tartomány az értékre +| `int` | megadható tartomány az értékre +| `null` | +| `object` | | `resource` | -| `scalar` | int\|float\|bool\|string -| `string` | meg lehet adni a bájtban megadott hossz tartományt. +| `scalar` | int\|float\|bool\|string +| `string` | megadható tartomány a hosszra bájtokban | `callable` | | `iterable` | -| `mixed` | -|------------------------------------------------ -| pszeudotípusok || -|------------------------------------------------ -| `list` | [indexelt tömb |#isList], az elemek számának tartománya megadható. -| `none` | üres érték: `''`, `null`, `false` -| `number` | int\ |float -| `numeric` | [szám, beleértve a szöveges megjelenítést is |#isNumeric] -| `numericint`| [egész szám, beleértve a szöveges ábrázolást is |#isNumericInt]. -| `unicode` | [UTF-8 karakterlánc |#isUnicode], megadható a karakterekben kifejezett hosszúság tartománya. -|------------------------------------------------ -| karakterosztály (nem lehet üres karakterlánc) || +| `mixed` | +|-------------------------- +| pszeudo-típusok || |------------------------------------------------ -| `alnum` | minden karakter alfanumerikus -| `alpha` | minden karakter betű `[A-Za-z]` -| `digit` | minden karakter számjegy -| `lower` | minden karakter kisbetűs betű `[a-z]` -| `space` | minden karakter szóköz -| `upper` | minden karakter nagybetűs betű `[A-Z]` -| `xdigit` | minden karakter hexadecimális számjegyek `[0-9A-Fa-f]` +| `list` | indexelt tömb, megadható tartomány az elemek számára +| `none` | üres érték: `''`, `null`, `false` +| `number` | int\|float +| `numeric` | [szám, beleértve a szöveges reprezentációt |#isNumeric] +| `numericint`| [egész szám, beleértve a szöveges reprezentációt |#isNumericInt] +| `unicode` | [UTF-8 string |#isUnicode], megadható tartomány a hosszra karakterekben +|-------------------------- +| karakterosztály (nem lehet üres string) || |------------------------------------------------ -| syntax validation || +| `alnum` | minden karakter alfanumerikus +| `alpha` | minden karakter betű `[A-Za-z]` +| `digit` | minden karakter számjegy +| `lower` | minden karakter kisbetű `[a-z]` +| `space` | minden karakter szóköz +| `upper` | minden karakter nagybetű `[A-Z]` +| `xdigit` | minden karakter hexadecimális számjegy `[0-9A-Fa-f]` +|-------------------------- +| szintaxis ellenőrzése || |------------------------------------------------ -| `pattern` | egy reguláris kifejezés, amelynek a teljes sztringnek meg kell felelnie. -| `email` | [Email |#isEmail] +| `pattern` | reguláris kifejezés, amelynek a **teljes** stringnek meg kell felelnie +| `email` | [E-mail |#isEmail] | `identifier`| [PHP azonosító |#isPhpIdentifier] -| `url` | [URL |#isUrl] -| `uri` | [URI |#isUri] -|------------------------------------------------ -| környezeti érvényesítés || +| `url` | [URL |#isUrl] +| `uri` | [URI |#isUri] +|-------------------------- +| környezet ellenőrzése || |------------------------------------------------ -| `class` | létezik osztály +| `class` | létező osztály | `interface` | létező interfész | `directory` | létező könyvtár -| `file` | létező fájl +| `file` | létező fájl -Állítás .[#toc-assertion] -========================= +Assertok +======== assert($value, string $expected, string $label='variable'): void .[method] -------------------------------------------------------------------------- -Ellenőrzi, hogy az érték a pipával elválasztott [elvárt típusokból |#expected types] áll-e. Ha nem, akkor kivételt dob [api:Nette\Utils\AssertionException]. A kivételüzenetben a `variable` szó helyettesíthető a `$label` paraméterrel. +Ellenőrzi, hogy az érték a függőleges vonallal elválasztott [#várt típusok] egyike-e. Ha nem, [api:Nette\Utils\AssertionException] kivételt dob. A `variable` szó a kivétel szövegében helyettesíthető mással a `$label` paraméterrel. ```php Validators::assert('Nette', 'string:5'); // OK @@ -120,10 +120,10 @@ Validators::assert('Lorem ipsum dolor sit', 'string:78'); ``` -assertField(array $array, string|int $key, string $expected=null, string $label=null): void .[method] ------------------------------------------------------------------------------------------------------ +assertField(array $array, string|int $key, ?string $expected=null, ?string $label=null): void .[method] +------------------------------------------------------------------------------------------------------- -Ellenőrzi, hogy a `$array` tömb `$key` eleme a tömbben csővel elválasztott, [elvárt típusokból |#expected types] áll. Ha nem, akkor kivételt dob [api:Nette\Utils\AssertionException]. A kivételüzenetben szereplő `item '%' in array` karakterláncot a `$label` paraméterrel lehet helyettesíteni. +Ellenőrzi, hogy a `$key` kulcs alatti elem a `$array` tömbben a függőleges vonallal elválasztott [#várt típusok] egyike-e. Ha nem, [api:Nette\Utils\AssertionException] kivételt dob. Az `item '%' in array` string a kivétel szövegében helyettesíthető mással a `$label` paraméterrel. ```php $arr = ['foo' => 'Nette']; @@ -136,19 +136,19 @@ Validators::assertField($arr, 'foo', 'int'); ``` -Validátorok .[#toc-validators] -============================== +Validátorok +=========== is($value, string $expected): bool .[method] -------------------------------------------- -Ellenőrzi, hogy az érték a pipával [elválasztott elvárt típusokból |#expected types] áll-e. +Ellenőrzi, hogy az érték a függőleges vonallal elválasztott [#várt típusok] egyike-e. ```php Validators::is(1, 'int|float'); // true Validators::is(23, 'int:0..10'); // false -Validators::is('Nette Framework', 'string:15'); // true, length is 15 bytes +Validators::is('Nette Framework', 'string:15'); // true, a hossz 15 bájt Validators::is('Nette Framework', 'string:8..'); // true Validators::is('Nette Framework', 'string:30..40'); // false ``` @@ -157,7 +157,7 @@ Validators::is('Nette Framework', 'string:30..40'); // false isEmail(mixed $value): bool .[method] ------------------------------------- -Ellenőrzi, hogy az érték érvényes e-mail cím-e. Nem ellenőrzi, hogy a domain valóban létezik-e, csak a szintaxisát ellenőrzi. A funkció számol a jövőbeli [TLD-kkel |https://en.wikipedia.org/wiki/Top-level_domain] is, amelyek szintén lehetnek unicode-ban. +Ellenőrzi, hogy az érték érvényes e-mail cím-e. Nem ellenőrzi, hogy a domain valóban létezik-e, csak a szintaxist ellenőrzi. A funkció számol a jövőbeli [TLD|https://hu.wikipedia.org/wiki/Legfelső_szintű_tartomány]-kkel is, amelyek unicode-ban is lehetnek. ```php Validators::isEmail('example@nette.org'); // true @@ -169,9 +169,9 @@ Validators::isEmail('nette'); // false isInRange(mixed $value, array $range): bool .[method] ----------------------------------------------------- -Ellenőrzi, hogy az érték a megadott tartományban van-e. `[min, max]`, ahol a felső vagy alsó határérték elhagyható (`null`). Számok, karakterláncok és DateTime objektumok összehasonlíthatók. +Ellenőrzi, hogy az érték a megadott `[min, max]` tartományban van-e, ahol a felső vagy alsó határt elhagyhatjuk (`null`). Összehasonlíthatók számok, stringek és DateTime objektumok. -Ha mindkét határ hiányzik (`[null, null]`), vagy az érték `null`, akkor a `false` értéket adja vissza. +Ha mindkét határ hiányzik (`[null, null]`) vagy az érték `null`, `false`-t ad vissza. ```php Validators::isInRange(5, [0, 5]); // true @@ -184,7 +184,7 @@ Validators::isInRange(1, [5]); // false isNone(mixed $value): bool .[method] ------------------------------------ -Ellenőrzi, hogy az érték `0`, `''`, `false` vagy `null`. +Ellenőrzi, hogy az érték `0`, `''`, `false` vagy `null`-e. ```php Validators::isNone(0); // true @@ -198,7 +198,7 @@ Validators::isNone('nette'); // false isNumeric(mixed $value): bool .[method] --------------------------------------- -Ellenőrzi, hogy az érték egy szám vagy egy karakterláncba írt szám. +Ellenőrzi, hogy az érték szám-e vagy stringben megadott szám. ```php Validators::isNumeric(23); // true @@ -213,7 +213,7 @@ Validators::isNumeric('1e6'); // false isNumericInt(mixed $value): bool .[method] ------------------------------------------ -Ellenőrzi, hogy az érték egész szám vagy egy karakterláncba írt egész szám. +Ellenőrzi, hogy az érték egész szám-e vagy stringben megadott egész szám. ```php Validators::isNumericInt(23); // true @@ -227,7 +227,7 @@ Validators::isNumericInt('nette'); // false isPhpIdentifier(string $value): bool .[method] ---------------------------------------------- -Ellenőrzi, hogy az érték egy szintaktikailag érvényes azonosító-e a PHP-ben, például osztályok, metódusok, függvények stb. nevei esetében. +Ellenőrzi, hogy az érték szintaktikailag érvényes PHP azonosító-e, például osztály-, metódus-, függvénynevekhez stb. ```php Validators::isPhpIdentifier(''); // false @@ -240,7 +240,7 @@ Validators::isPhpIdentifier('one two'); // false isBuiltinType(string $type): bool .[method] ------------------------------------------- -Megállapítja, hogy a `$type` PHP beépített típus-e. Ellenkező esetben az osztály neve. +Megállapítja, hogy a `$type` beépített PHP típus-e. Ellenkező esetben osztálynév. ```php Validators::isBuiltinType('string'); // true @@ -251,7 +251,7 @@ Validators::isBuiltinType('Foo'); // false isTypeDeclaration(string $type): bool .[method] ----------------------------------------------- -Ellenőrzi, hogy a típusdeklaráció szintaktikailag helyes-e. +Ellenőrzi, hogy a megadott típusdeklaráció szintaktikailag érvényes-e. ```php Validators::isTypeDeclaration('?string'); // true @@ -268,7 +268,7 @@ Validators::isTypeDeclaration('(A|B)'); // false isClassKeyword(string $type): bool .[method] -------------------------------------------- -Megállapítja, hogy a `$type` egyike-e a `self`, `parent`, `static` belső típusoknak. +Megállapítja, hogy a `$type` a `self`, `parent`, `static` belső típusok egyike-e. ```php Validators::isClassKeyword('self'); // true @@ -279,7 +279,7 @@ Validators::isClassKeyword('Foo'); // false isUnicode(mixed $value): bool .[method] --------------------------------------- -Ellenőrzi, hogy az érték érvényes UTF-8 karakterlánc-e. +Ellenőrzi, hogy az érték érvényes UTF-8 string-e. ```php Validators::isUnicode('nette'); // true @@ -291,7 +291,7 @@ Validators::isUnicode("\xA0"); // false isUrl(mixed $value): bool .[method] ----------------------------------- -Ellenőrzi, hogy az érték érvényes URL-cím-e. +Ellenőrzi, hogy az érték érvényes URL cím-e. ```php Validators::isUrl('https://nette.org:8080/path?query#fragment'); // true @@ -306,7 +306,7 @@ Validators::isUrl('nette.org'); // false isUri(string $value): bool .[method] ------------------------------------ -Ellenőrzi, hogy az érték érvényes URI-cím-e, azaz valóban egy szintaktikailag érvényes sémával kezdődő karakterlánc. +Ellenőrzi, hogy az érték érvényes URI cím-e, azaz egy szintaktikailag érvényes sémával kezdődő string. ```php Validators::isUri('https://nette.org'); // true diff --git a/utils/it/@home.texy b/utils/it/@home.texy index d7eac70856..e7f6265d56 100644 --- a/utils/it/@home.texy +++ b/utils/it/@home.texy @@ -1,42 +1,46 @@ -Utilità -******* +Nette Utils +*********** .[perex] -Il pacchetto `nette/utils` contiene un insieme di classi utili per l'uso quotidiano: +Nel pacchetto `nette/utils` troverai un set di classi utili per l'uso quotidiano: -| [Arrays |Arrays] | Nette\Utils\Arrays -| [Callback |Callback] | Nette\Utils\Callback +| [Callback |callback] | Nette\Utils\Callback | [Data e ora |datetime] | Nette\Utils\DateTime -| [Filesystem |filesystem] | Nette\Utils\FileSystem | [Finder |Finder] | Nette\Utils\Finder -| [Galleggianti |Floats] | Nette\Utils\Floats -| [Helpers |helpers] | Nette\Utils\Helpers -| [Elementi HTML |HTML Elements] | Nette\Utils\Html -| [Immagini |Images] | Nette\Utils\Image -| [JSON |JSON] | Nette\Utils\Json -| [Modello a oggetti |smartobject] | Nette\SmartObject & Nette\StaticClass -| [Paginatore |paginator] | Nette\Utils\Paginatore +| [Elementi HTML |html-elements] | Nette\Utils\Html +| [Iteratori |iterables] | Nette\Utils\Iterables +| [JSON |json] | Nette\Utils\Json +| [Stringhe casuali |random] | Nette\Utils\Random +| [Immagini |images] | Nette\Utils\Image | [Riflessione PHP |reflection] | Nette\Utils\Reflection | [Tipi PHP |type] | Nette\Utils\Type -| [Stringhe casuali |random] | Nette\Utils\Random -| [Stringhe |Strings] | Nette\Utils\Strings -| [Validatori |validators] | Nette\Utils\Validators +| [Array |arrays] | Nette\Utils\Arrays +| [Funzioni ausiliarie |helpers] | Nette\Utils\Helpers +| [Confronto di float |floats] | Nette\Utils\Floats +| [Stringhe |strings] | Nette\Utils\Strings +| [File system |filesystem] | Nette\Utils\FileSystem +| [Paginazione |paginator] | Nette\Utils\Paginator +| [SmartObject |SmartObject] & [StaticClass |StaticClass] | Nette\SmartObject & Nette\StaticClass +| [Validatore |validators] | Nette\Utils\Validators Installazione ------------- -Scaricare e installare il pacchetto utilizzando [Composer |best-practices:composer]: +Scarica e installa la libreria utilizzando [Composer|best-practices:composer]: ```shell composer require nette/utils ``` -| versione | compatibile con PHP -|-----------|------------------- -| Nette Utils 4.0 | PHP 8.0 - 8.2 -| Nette Utils 3.2 | PHP 7.2 - 8.2 -| Nette Utils 3.0 - 3.1 | PHP 7.1 - 8.0 -| Nette Utils 2.5 | PHP 5.6 - 8.0 +| versione | compatibile con PHP +|-------------------|------------------- +| Nette Utils 4.0 | PHP 8.0 – 8.4 +| Nette Utils 3.2 | PHP 7.2 – 8.3 +| Nette Utils 3.0 – 3.1 | PHP 7.1 – 8.0 +| Nette Utils 2.5 | PHP 5.6 – 8.0 + +Vale per l'ultima versione patch. + -Si applica alle ultime versioni della patch. +Se aggiorni il pacchetto a una versione più recente, consulta la pagina [upgrade|en:upgrading]. diff --git a/utils/it/@left-menu.texy b/utils/it/@left-menu.texy index c856685e85..bc888b4579 100644 --- a/utils/it/@left-menu.texy +++ b/utils/it/@left-menu.texy @@ -1,26 +1,28 @@ -Pacchetto nette/utils -********************* -- [Array |Arrays] -- [Callback |Callback] +Nette Utils +*********** +- [Callback |callback] - [Data e ora |datetime] -- [Sistema di file |filesystem] -- [Cercatore |Finder] -- [Aiutanti |helpers] -- [Elementi HTML |HTML Elements] -- [Immagini |Images] +- [Finder |Finder] +- [Floats |Floats] +- [Elementi HTML |html-elements] +- [Iteratori |iterables] - [JSON |JSON] -- [Paginatore |paginator] - [Stringhe casuali |random] -- [Oggetto intelligente |SmartObject] +- [Immagini |images] +- [Paginatore |paginator] - [Riflessione PHP |reflection] -- [Stringhe |Strings] -- [Galleggianti |Floats] - [Tipi PHP |type] -- [Validatori |validators] +- [Array |arrays] +- [Funzioni ausiliarie |helpers] +- [Stringhe |strings] +- [SmartObject |SmartObject] +- [StaticClass |StaticClass] +- [File system |filesystem] +- [Validatore |validators] -Altre utilità -************* -- [NEON |neon:] -- [Hashing di password |security:passwords] -- [Parser e costruttore di URL |http:urls] +Altri strumenti +*************** +- [NEON|neon:] +- [Hashing delle password |security:passwords] +- [Parsing e composizione di URL |http:urls] diff --git a/utils/it/@meta.texy b/utils/it/@meta.texy new file mode 100644 index 0000000000..4647d0c8a2 --- /dev/null +++ b/utils/it/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentazione Nette}} diff --git a/utils/it/arrays.texy b/utils/it/arrays.texy index a4394bde90..c2b6c5ae5c 100644 --- a/utils/it/arrays.texy +++ b/utils/it/arrays.texy @@ -1,8 +1,8 @@ -Funzioni della matrice +Lavorare con gli array ********************** .[perex] -Questa pagina tratta delle classi [Nette\Utils\Arrays |#Arrays], [ArrayHash |#ArrayHash] e [ArrayList |#ArrayList], relative agli array. +Questa pagina è dedicata alle classi [Nette\Utils\Arrays |#Arrays], [#ArrayHash] e [#ArrayList], che riguardano gli array. Installazione: @@ -12,22 +12,63 @@ composer require nette/utils ``` -Array .[#toc-arrays] -==================== +Arrays +====== -[api:Nette\Utils\Arrays] è una classe statica che contiene una serie di pratiche funzioni per gli array. +[api:Nette\Utils\Arrays] è una classe statica che contiene funzioni utili per lavorare con gli array. La sua controparte per gli iteratori è [Nette\Utils\Iterables|iterables]. -Gli esempi seguenti presuppongono che sia definito il seguente alias di classe: +Gli esempi seguenti presuppongono la creazione di un alias: ```php use Nette\Utils\Arrays; ``` +associate(array $array, mixed $path): array|\stdClass .[method] +--------------------------------------------------------------- + +La funzione trasforma in modo flessibile l'array `$array` in un array associativo o oggetti secondo il percorso specificato `$path`. Il percorso può essere una stringa o un array. È composto dai nomi delle chiavi dell'array di input e da operatori come '[]', '->', '=', e '|'. Lancia `Nette\InvalidArgumentException` nel caso in cui il percorso non sia valido. + +```php +// conversione in array associativo secondo una chiave semplice +$arr = [ + ['name' => 'John', 'age' => 11], + ['name' => 'Mary', 'age' => null], + // ... +]; +$result = Arrays::associate($arr, 'name'); +// $result = ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// assegnazione di valori da una chiave a un'altra utilizzando l'operatore = +$result = Arrays::associate($arr, 'name=age'); // o ['name', '=', 'age'] +// $result = ['John' => 11, 'Mary' => null, ...] +``` + +```php +// creazione di un oggetto utilizzando l'operatore -> +$result = Arrays::associate($arr, '->name'); // o ['->', 'name'] +// $result = (object) ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// combinazione di chiavi utilizzando l'operatore | +$result = Arrays::associate($arr, 'name|age'); // o ['name', '|', 'age'] +// $result: ['John' => ['name' => 'John', 'age' => 11], 'Paul' => ['name' => 'Paul', 'age' => 44]] +``` + +```php +// aggiunta all'array utilizzando [] +$result = Arrays::associate($arr, 'name[]'); // o ['name', '[]'] +// $result: ['John' => [['name' => 'John', 'age' => 22], ['name' => 'John', 'age' => 11]]] +``` + + contains(array $array, $value): bool .[method] ---------------------------------------------- -Verifica la presenza di un valore in un array. Utilizza un confronto rigoroso (`===`) +Verifica la presenza di un valore nell'array. Utilizza un confronto rigoroso (`===`). ```php Arrays::contains([1, 2, 3], 1); // true @@ -35,10 +76,10 @@ Arrays::contains(['1', false], 1); // false ``` -every(iterable $array, callable $callback): bool .[method] ----------------------------------------------------------- +every(array $array, callable $predicate): bool .[method] +-------------------------------------------------------- -Verifica se tutti gli elementi dell'array superano il test implementato dalla funzione fornita, che ha la firma `function ($value, $key, array $array): bool`. +Verifica se tutti gli elementi nell'array superano il test implementato in `$predicate` con la firma `function ($value, $key, array $array): bool`. ```php $array = [1, 30, 39, 29, 10, 13]; @@ -46,24 +87,59 @@ $isBelowThreshold = fn($value) => $value < 40; $res = Arrays::every($array, $isBelowThreshold); // true ``` -Vedere [some() |#some()]. +Vedi [#some()]. -first(array $array): mixed .[method] ------------------------------------- +filter(array $array, callable $predicate): array .[method]{data-version:4.0.4} +------------------------------------------------------------------------------ -Restituisce il primo elemento dell'array o null se l'array è vuoto. Non modifica il puntatore interno, a differenza di `reset()`. +Restituisce un nuovo array contenente tutte le coppie chiave-valore che corrispondono al predicato specificato. Il callback ha la firma `function ($value, int|string $key, array $array): bool`. ```php -Arrays::first([1, 2, 3]); // 1 -Arrays::first([]); // null +Arrays::filter( + ['a' => 1, 'b' => 2, 'c' => 3], + fn($v) => $v < 3, +); +// ['a' => 1, 'b' => 2] ``` +first(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------- + +Restituisce il primo elemento (corrispondente al predicato, se specificato). Se tale elemento non esiste, restituisce il risultato della chiamata `$else` o null. Il parametro `$predicate` ha la firma `function ($value, int|string $key, array $array): bool`. + +Non modifica il puntatore interno a differenza di `reset()`. I parametri `$predicate` e `$else` esistono dalla versione 4.0.4. + +```php +Arrays::first([1, 2, 3]); // 1 +Arrays::first([1, 2, 3], fn($v) => $v > 2); // 3 +Arrays::first([]); // null +Arrays::first([], else: fn() => false); // false +``` + +Vedi [#last()]. + + +firstKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +------------------------------------------------------------------------------------------------ + +Restituisce la chiave del primo elemento (corrispondente al predicato, se specificato) o null se tale elemento non esiste. Il predicato `$predicate` ha la firma `function ($value, int|string $key, array $array): bool`. + +```php +Arrays::firstKey([1, 2, 3]); // 0 +Arrays::firstKey([1, 2, 3], fn($v) => $v > 2); // 2 +Arrays::firstKey(['a' => 1, 'b' => 2]); // 'a' +Arrays::firstKey([]); // null +``` + +Vedi [#lastKey()]. + + flatten(array $array, bool $preserveKeys=false): array .[method] ---------------------------------------------------------------- -Trasforma un array multidimensionale in un array piatto. +Appiattisce un array multidimensionale in uno piatto. ```php $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); @@ -71,10 +147,10 @@ $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); ``` -get(array $array, string|int|array $key, mixed $default=null): mixed .[method] ------------------------------------------------------------------------------- +get(array $array, string|int|array $key, ?mixed $default=null): mixed .[method] +------------------------------------------------------------------------------- -Restituisce `$array[$key]` item. Se non esiste, viene lanciato `Nette\InvalidArgumentException`, a meno che non venga impostato un valore predefinito come terzo parametro. +Restituisce l'elemento `$array[$key]`. Se non esiste, lancia un'eccezione `Nette\InvalidArgumentException`, oppure, se è specificato il terzo parametro `$default`, restituisce quello. ```php // se $array['foo'] non esiste, lancia un'eccezione @@ -84,49 +160,49 @@ $value = Arrays::get($array, 'foo'); $value = Arrays::get($array, 'foo', 'bar'); ``` -L'argomento `$key` può anche essere un array. +La chiave `$key` può anche essere un array. ```php $array = ['color' => ['favorite' => 'red'], 5]; $value = Arrays::get($array, ['color', 'favorite']); -// returns 'red' +// restituisce 'red' ``` getRef(array &$array, string|int|array $key): mixed .[method] ------------------------------------------------------------- -Ottiene il riferimento al dato `$array[$key]`. Se l'indice non esiste, ne viene creato uno nuovo con il valore `null`. +Ottiene un riferimento all'elemento specificato dell'array. Se l'elemento non esiste, verrà creato con il valore null. ```php $valueRef = & Arrays::getRef($array, 'foo'); -// restituisce il riferimento a $array['foo']. +// restituisce un riferimento a $array['foo'] ``` -Funziona con gli array multidimensionali e con [get() |#get()]. +Come la funzione [#get()], può lavorare con array multidimensionali. ```php -$value = & Arrays::get($array, ['colore', 'preferito']); -// restituisce il riferimento $array['colore']['preferito'] +$value = & Arrays::getRef($array, ['color', 'favorite']); +// restituisce un riferimento a $array['color']['favorite'] ``` grep(array $array, string $pattern, bool $invert=false): array .[method] ------------------------------------------------------------------------ -Restituisce solo gli elementi dell'array che corrispondono a un'espressione regolare `$pattern`. Se `$invert` è `true`, restituisce gli elementi che non corrispondono. Un errore di compilazione o di esecuzione della Regex lancia `Nette\RegexpException`. +Restituisce solo gli elementi dell'array il cui valore corrisponde all'espressione regolare `$pattern`. Se `$invert` è `true`, restituisce invece gli elementi che non corrispondono. Un errore durante la compilazione o l'elaborazione dell'espressione lancia un'eccezione `Nette\RegexpException`. ```php $filteredArray = Arrays::grep($array, '~^\d+$~'); -// restituisce solo elementi numerici +// restituisce solo gli elementi dell'array composti da cifre ``` insertAfter(array &$array, string|int|null $key, array $inserted): void .[method] --------------------------------------------------------------------------------- -Inserisce il contenuto dell'array `$inserted` nell'array `$array` immediatamente dopo `$key`. Se `$key` è `null` (o non esiste), viene inserito alla fine. +Inserisce il contenuto dell'array `$inserted` nell'array `$array` subito dopo l'elemento con la chiave `$key`. Se `$key` è `null` (o non è nell'array), viene inserito alla fine. ```php $array = ['first' => 10, 'second' => 20]; @@ -138,7 +214,7 @@ Arrays::insertAfter($array, 'first', ['hello' => 'world']); insertBefore(array &$array, string|int|null $key, array $inserted): void .[method] ---------------------------------------------------------------------------------- -Inserisce il contenuto della matrice `$inserted` in `$array` prima di `$key`. Se `$key` è `null` (o non esiste), viene inserito all'inizio. +Inserisce il contenuto dell'array `$inserted` nell'array `$array` prima dell'elemento con la chiave `$key`. Se `$key` è `null` (o non è nell'array), viene inserito all'inizio. ```php $array = ['first' => 10, 'second' => 20]; @@ -166,7 +242,7 @@ $array = Arrays::invoke($callbacks, 5, 11); invokeMethod(iterable $objects, string $method, ...$args): array .[method] -------------------------------------------------------------------------- -Invoca il metodo su ogni oggetto di un array e restituisce un array di risultati. +Chiama un metodo su ogni oggetto nell'array e restituisce un array di risultati. ```php $objects = ['a' => $obj1, 'b' => $obj2]; @@ -179,7 +255,7 @@ $array = Arrays::invokeMethod($objects, 'foo', 1, 2); isList(array $array): bool .[method] ------------------------------------ -Verifica se l'array è indicizzato in ordine crescente di chiavi numeriche a partire da zero, ovvero un elenco. +Verifica se l'array è indicizzato secondo una serie ascendente di chiavi numeriche a partire da zero, alias lista. ```php Arrays::isList(['a', 'b', 'c']); // true @@ -188,21 +264,42 @@ Arrays::isList(['a' => 1, 'b' => 2]); // false ``` -last(array $array): mixed .[method] ------------------------------------ +last(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------ -Restituisce l'ultimo elemento dell'array o null se l'array è vuoto. Non modifica il puntatore interno, a differenza di `end()`. +Restituisce l'ultimo elemento (corrispondente al predicato, se specificato). Se tale elemento non esiste, restituisce il risultato della chiamata `$else` o null. Il parametro `$predicate` ha la firma `function ($value, int|string $key, array $array): bool`. + +Non modifica il puntatore interno a differenza di `end()`. I parametri `$predicate` e `$else` esistono dalla versione 4.0.4. ```php -Arrays::last([1, 2, 3]); // 3 -Arrays::last([]); // null +Arrays::last([1, 2, 3]); // 3 +Arrays::last([1, 2, 3], fn($v) => $v < 3); // 2 +Arrays::last([]); // null +Arrays::last([], else: fn() => false); // false ``` +Vedi [#first()]. + -map(iterable $array, callable $callback): array .[method] +lastKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +----------------------------------------------------------------------------------------------- + +Restituisce la chiave dell'ultimo elemento (corrispondente al predicato, se specificato) o null se tale elemento non esiste. Il predicato `$predicate` ha la firma `function ($value, int|string $key, array $array): bool`. + +```php +Arrays::lastKey([1, 2, 3]); // 2 +Arrays::lastKey([1, 2, 3], fn($v) => $v < 3); // 1 +Arrays::lastKey(['a' => 1, 'b' => 2]); // 'b' +Arrays::lastKey([]); // null +``` + +Vedi [#firstKey()]. + + +map(array $array, callable $transformer): array .[method] --------------------------------------------------------- -Richiama `$callback` su tutti gli elementi dell'array e restituisce l'array dei valori di ritorno. Il callback ha la firma `function ($value, $key, array $array): bool`. +Chiama `$transformer` su tutti gli elementi dell'array e restituisce un array dei valori restituiti. Il callback ha la firma `function ($value, $key, array $array): mixed`. ```php $array = ['foo', 'bar', 'baz']; @@ -211,26 +308,40 @@ $res = Arrays::map($array, fn($value) => $value . $value); ``` +mapWithKeys(array $array, callable $transformer): array .[method] +----------------------------------------------------------------- + +Crea un nuovo array trasformando i valori e le chiavi dell'array originale. La funzione `$transformer` ha la firma `function ($value, $key, array $array): ?array{$newKey, $newValue}`. Se `$transformer` restituisce `null`, l'elemento viene saltato. Per gli elementi conservati, il primo elemento dell'array restituito viene utilizzato come nuova chiave e il secondo elemento come nuovo valore. + +```php +$array = ['a' => 1, 'b' => 2]; +$result = Arrays::mapWithKeys($array, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +Questo metodo è utile in situazioni in cui è necessario modificare la struttura dell'array (chiavi e valori contemporaneamente) o filtrare elementi durante la trasformazione (restituendo null per gli elementi indesiderati). + + mergeTree(array $array1, array $array2): array .[method] -------------------------------------------------------- -Unisce ricorsivamente due campi. È utile, ad esempio, per unire strutture ad albero. Si comporta come l'operatore `+' per gli array, cioè aggiunge una coppia chiave/valore dal secondo array al primo e mantiene il valore dal primo array nel caso di una collisione di chiavi. +Unisce ricorsivamente due array. È utile, ad esempio, per unire strutture ad albero. Durante l'unione, segue le stesse regole dell'operatore `+` applicato agli array, cioè aggiunge coppie chiave/valore dal secondo array al primo e, in caso di collisione di chiavi, mantiene il valore del primo array. ```php -$array1 = ['colore' => ['preferito' => 'rosso'], 5]; -$array2 = [10, 'colore' => ['preferito' => 'verde', 'blu']]; +$array1 = ['color' => ['favorite' => 'red'], 5]; +$array2 = [10, 'color' => ['favorite' => 'green', 'blue']]; $array = Arrays::mergeTree($array1, $array2); -// $array = ['colore' => ['preferito' => 'rosso', 'blu'], 5]; +// $array = ['color' => ['favorite' => 'red', 'blue'], 5]; ``` -I valori del secondo array vengono sempre aggiunti al primo. La scomparsa del valore `10` dal secondo array può sembrare un po' confusa. Va notato che questo valore, così come il valore `5` in the first array have the same numeric key `0`, quindi nel campo risultante c'è solo un elemento del primo array. +I valori del secondo array vengono sempre aggiunti alla fine del primo. La scomparsa del valore `10` dal secondo array può sembrare un po' confusionaria. È necessario rendersi conto che questo valore, così come il valore `5` nel primo array, hanno assegnata la stessa chiave numerica `0`, quindi nell'array risultante c'è solo l'elemento del primo array. -normalize(array $array, string $filling=null): array .[method] --------------------------------------------------------------- +normalize(array $array, ?string $filling=null): array .[method] +--------------------------------------------------------------- -Normalizza l'array in un array associativo. Sostituisce le chiavi numeriche con i loro valori, il nuovo valore sarà `$filling`. +Normalizza un array in un array associativo. Sostituisce le chiavi numeriche con i loro valori; il nuovo valore sarà `$filling`. ```php $array = Arrays::normalize([1 => 'first', 'a' => 'second']); @@ -243,26 +354,26 @@ $array = Arrays::normalize([1 => 'first', 'a' => 'second'], 'foobar'); ``` -pick(array &$array, string|int $key, mixed $default=null): mixed .[method] --------------------------------------------------------------------------- +pick(array &$array, string|int $key, ?mixed $default=null): mixed .[method] +--------------------------------------------------------------------------- -Restituisce e rimuove il valore di un elemento da un array. Se non esiste, lancia un'eccezione o restituisce `$default`, se fornito. +Restituisce e rimuove il valore di un elemento dall'array. Se non esiste, lancia un'eccezione o restituisce il valore `$default`, se specificato. ```php -$array = [1 => 'foo', null => 'bar']; -$a = Arrays::pick($array, null); +$array = [1 => 'foo', 'x' => 'bar']; +$a = Arrays::pick($array, 'x'); // $a = 'bar' $b = Arrays::pick($array, 'not-exists', 'foobar'); // $b = 'foobar' $c = Arrays::pick($array, 'not-exists'); -// throws Nette\InvalidArgumentException +// lancia Nette\InvalidArgumentException ``` renameKey(array &$array, string|int $oldKey, string|int $newKey): bool .[method] -------------------------------------------------------------------------------- -Rinomina una chiave. Restituisce `true` se la chiave è stata trovata nell'array. +Rinomina una chiave nell'array. Restituisce `true` se la chiave è stata trovata nell'array. ```php $array = ['first' => 10, 'second' => 20]; @@ -274,7 +385,7 @@ Arrays::renameKey($array, 'first', 'renamed'); getKeyOffset(array $array, string|int $key): ?int .[method] ----------------------------------------------------------- -Restituisce la posizione a indice zero della chiave dell'array dato. Restituisce `null` se la chiave non viene trovata. +Restituisce la posizione della chiave data nell'array. La posizione è numerata da 0. Nel caso in cui la chiave non venga trovata, la funzione restituisce `null`. ```php $array = ['first' => 10, 'second' => 20]; @@ -284,10 +395,10 @@ $position = Arrays::getKeyOffset($array, 'not-exists'); // restituisce null ``` -some(iterable $array, callable $callback): bool .[method] ---------------------------------------------------------- +some(array $array, callable $predicate): bool .[method] +------------------------------------------------------- -Verifica se almeno un elemento dell'array supera il test implementato dal callback fornito con la firma `function ($value, $key, array $array): bool`. +Verifica se almeno un elemento nell'array supera il test implementato in `$predicate` con la firma `function ($value, $key, array $array): bool`. ```php $array = [1, 2, 3, 4]; @@ -295,13 +406,13 @@ $isEven = fn($value) => $value % 2 === 0; $res = Arrays::some($array, $isEven); // true ``` -Vedere [every() |#every()]. +Vedi [#every()]. toKey(mixed $key): string|int .[method] --------------------------------------- -Converte un valore in una chiave di array, che può essere un intero o una stringa. +Converte un valore in una chiave di array, che è un intero o una stringa. ```php Arrays::toKey('1'); // 1 @@ -312,7 +423,7 @@ Arrays::toKey('01'); // '01' toObject(iterable $array, object $object): object .[method] ----------------------------------------------------------- -Copia gli elementi dell'array `$array` nell'oggetto `$object` e lo restituisce. +Copia gli elementi dell'array `$array` nell'oggetto `$object`, che viene poi restituito. ```php $obj = new stdClass; @@ -321,10 +432,10 @@ Arrays::toObject($array, $obj); // imposta $obj->foo = 1; $obj->bar = 2; ``` -wrap(iterable $array, string $prefix='', string $suffix=''): array .[method] ----------------------------------------------------------------------------- +wrap(array $array, string $prefix='', string $suffix=''): array .[method] +------------------------------------------------------------------------- -Lancia ogni elemento dell'array come stringa e lo racchiude con `$prefix` e `$suffix`. +Converte ogni elemento dell'array in una stringa e lo racchiude con il prefisso `$prefix` e il suffisso `$suffix`. ```php $array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); @@ -332,21 +443,21 @@ $array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); ``` -ArrayHash .[#toc-arrayhash] -=========================== +ArrayHash +========= -L'oggetto [api:Nette\Utils\ArrayHash] è il discendente della classe generica stdClass e la estende alla possibilità di trattarla come un array, ad esempio accedendo ai membri usando le parentesi quadre: +L'oggetto [api:Nette\Utils\ArrayHash] è un discendente della classe generica stdClass e la estende con la capacità di trattarlo come un array, ad esempio accedendo ai membri tramite parentesi quadre: ```php $hash = new Nette\Utils\ArrayHash; $hash['foo'] = 123; -$hash->bar = 456; // funziona anche in notazione oggetto +$hash->bar = 456; // funziona anche la notazione a oggetto $hash->foo; // 123 ``` -È possibile utilizzare la funzione `count($hash)` per ottenere il numero di elementi. +È possibile utilizzare la funzione `count($hash)` per determinare il numero di elementi. -È possibile iterare su un oggetto come su un array, anche con un riferimento: +È possibile iterare sull'oggetto come su un array, anche con un riferimento: ```php foreach ($hash as $key => $value) { @@ -354,11 +465,11 @@ foreach ($hash as $key => $value) { } foreach ($hash as $key => &$value) { - $value = 'nuovo valore'; + $value = 'new value'; } ``` -Gli array esistenti possono essere trasformati in `ArrayHash` utilizzando `from()`: +Possiamo trasformare un array esistente in `ArrayHash` con il metodo `from()`: ```php $array = ['foo' => 123, 'bar' => 456]; @@ -368,7 +479,7 @@ $hash->foo; // 123 $hash->bar; // 456 ``` -La trasformazione è ricorsiva: +La conversione è ricorsiva: ```php $array = ['foo' => 123, 'inner' => ['a' => 'b']]; @@ -379,24 +490,24 @@ $hash->inner->a; // 'b' $hash['inner']['a']; // 'b' ``` -Può essere evitata con il secondo parametro: +Questo può essere evitato con il secondo parametro: ```php $hash = Nette\Utils\ArrayHash::from($array, false); $hash->inner; // array ``` -Trasformazione in array: +Trasformazione di nuovo in array: ```php $array = (array) $hash; ``` -ArrayList .[#toc-arraylist] -=========================== +ArrayList +========= -[api:Nette\Utils\ArrayList] rappresenta un array lineare in cui gli indici sono solo numeri interi ascendenti a partire da 0. +[api:Nette\Utils\ArrayList] rappresenta un array lineare in cui gli indici sono solo numeri interi crescenti a partire da 0. ```php $list = new Nette\Utils\ArrayList; @@ -407,9 +518,16 @@ $list[] = 'c'; count($list); // 3 ``` -È possibile utilizzare la funzione `count($list)` per ottenere il numero di elementi. +Possiamo trasformare un array esistente in `ArrayList` con il metodo `from()`: -È possibile iterare su un oggetto come su un array, anche con un riferimento: +```php +$array = ['foo', 'bar']; +$list = Nette\Utils\ArrayList::from($array); +``` + +È possibile utilizzare la funzione `count($list)` per determinare il numero di elementi. + +È possibile iterare sull'oggetto come su un array, anche con un riferimento: ```php foreach ($list as $key => $value) { @@ -421,28 +539,21 @@ foreach ($list as $key => &$value) { } ``` -Gli array esistenti possono essere trasformati in `ArrayList` utilizzando `from()`: - -```php -$array = ['foo', 'bar']; -$list = Nette\Utils\ArrayList::from($array); -``` - -L'accesso alle chiavi oltre i valori consentiti lancia un'eccezione `Nette\OutOfRangeException`: +L'accesso a chiavi al di fuori dei valori consentiti lancia un'eccezione `Nette\OutOfRangeException`: ```php -echo $list[-1]; // throws Nette\OutOfRangeException -unset($list[30]); // throws Nette\OutOfRangeException +echo $list[-1]; // lancia Nette\OutOfRangeException +unset($list[30]); // lancia Nette\OutOfRangeException ``` -La rimozione della chiave comporta la rinumerazione degli elementi: +La rimozione di una chiave causa la rinumerazione degli elementi: ```php unset($list[1]); // ArrayList(0 => 'a', 1 => 'c') ``` -È possibile aggiungere un nuovo elemento all'inizio utilizzando `prepend()`: +È possibile aggiungere un nuovo elemento all'inizio con il metodo `prepend()`: ```php $list->prepend('d'); diff --git a/utils/it/callback.texy b/utils/it/callback.texy index 0c38fc399b..0e59eba16d 100644 --- a/utils/it/callback.texy +++ b/utils/it/callback.texy @@ -1,8 +1,8 @@ -Funzioni di callback -******************** +Lavorare con i callback +*********************** .[perex] -[api:Nette\Utils\Callback] è una classe statica che contiene funzioni per lavorare con i [callback di PHP |https://www.php.net/manual/en/language.types.callable.php]. +[api:Nette\Utils\Callback] è una classe statica con funzioni per lavorare con i [callback PHP |https://www.php.net/manual/en/language.types.callable.php]. Installazione: @@ -11,7 +11,7 @@ Installazione: composer require nette/utils ``` -Tutti gli esempi presuppongono che sia definito il seguente alias di classe: +Tutti gli esempi presuppongono la creazione di un alias: ```php use Nette\Utils\Callback; @@ -21,21 +21,21 @@ use Nette\Utils\Callback; check($callable, bool $syntax=false): callable .[method] -------------------------------------------------------- -Verifica che `$callable` sia un callback PHP valido. Altrimenti lancia `Nette\InvalidArgumentException`. Se `$syntax` è impostato a true, la funzione verifica solo che `$callable` abbia una struttura valida da usare come callback, ma non verifica se la classe o il metodo esistono effettivamente. Restituisce `$callable`. +Verifica se la variabile `$callable` è un callback valido. Altrimenti, lancia `Nette\InvalidArgumentException`. Se `$syntax` è true, la funzione verifica solo che `$callable` abbia la struttura di un callback, ma non verifica se la classe o il metodo specificato esista effettivamente. Restituisce `$callable`. ```php -Callback::check('trim'); // nessuna eccezione +Callback::check('trim'); // non lancia eccezione Callback::check(['NonExistentClass', 'method']); // lancia Nette\InvalidArgumentException -Callback::check(['NonExistentClass', 'method'], true); // nessuna eccezione -Callback::check(function () {}); // nessuna eccezione -Callback::check(null); // lancia l'eccezione Nette\InvalidArgumentException +Callback::check(['NonExistentClass', 'method'], true); // non lancia eccezione +Callback::check(function () {}); // non lancia eccezione +Callback::check(null); // lancia Nette\InvalidArgumentException ``` toString($callable): string .[method] ------------------------------------- -Converte il callback PHP in forma testuale. La classe o il metodo potrebbero non esistere. +Converte un callback PHP in forma testuale. La classe o il metodo non devono necessariamente esistere. ```php Callback::toString('trim'); // 'trim' @@ -46,7 +46,7 @@ Callback::toString(['MyClass', 'method']); // 'MyClass::method' toReflection($callable): ReflectionMethod|ReflectionFunction .[method] ---------------------------------------------------------------------- -Restituisce la riflessione per il metodo o la funzione utilizzati nel callback PHP. +Restituisce la reflection per il metodo o la funzione nel callback PHP. ```php $ref = Callback::toReflection('trim'); @@ -60,7 +60,7 @@ $ref = Callback::toReflection(['MyClass', 'method']); isStatic($callable): bool .[method] ----------------------------------- -Controlla se il callback PHP è una funzione o un metodo statico. +Determina se il callback PHP è una funzione o un metodo statico. ```php Callback::isStatic('trim'); // true @@ -73,7 +73,7 @@ Callback::isStatic(function () {}); // false unwrap(Closure $closure): callable|array .[method] -------------------------------------------------- -Dischiude la chiusura creata da `Closure::fromCallable`:https://www.php.net/manual/en/closure.fromcallable.php. +Scompatta all'indietro una Closure creata utilizzando `Closure::fromCallable`:https://www.php.net/manual/en/closure.fromcallable.php. ```php $closure = Closure::fromCallable(['MyClass', 'method']); diff --git a/utils/it/datetime.texy b/utils/it/datetime.texy index 02fd389dc4..6bb7f22eee 100644 --- a/utils/it/datetime.texy +++ b/utils/it/datetime.texy @@ -2,7 +2,7 @@ Data e ora ********** .[perex] -[api:Nette\Utils\DateTime] è una classe che estende il nativo [php:DateTime]. +[api:Nette\Utils\DateTime] è una classe che estende la classe nativa [php:DateTime] con funzioni aggiuntive. Installazione: @@ -11,7 +11,7 @@ Installazione: composer require nette/utils ``` -Tutti gli esempi presuppongono che sia definito il seguente alias di classe: +Tutti gli esempi presuppongono la creazione di un alias: ```php use Nette\Utils\DateTime; @@ -20,13 +20,13 @@ use Nette\Utils\DateTime; static from(string|int|\DateTimeInterface $time): DateTime .[method] -------------------------------------------------------------------- -Crea un oggetto DateTime da una stringa, un timestamp UNIX o un altro oggetto [php:DateTimeInterface]. Lancia un `Exception` se la data e l'ora non sono valide. +Crea un oggetto DateTime da una stringa, un timestamp UNIX o un altro oggetto [php:DateTimeInterface]. Lancia un'eccezione `Exception` se la data e l'ora non sono valide. ```php -DateTime::from(1138013640); // creates a DateTime from the UNIX timestamp with a default timezamp -DateTime::from(42); // creates a DateTime from the current time plus 42 seconds -DateTime::from('1994-02-26 04:15:32'); // creates a DateTime based on a string -DateTime::from('1994-02-26'); // create DateTime by date, time will be 00:00:00 +DateTime::from(1138013640); // crea un DateTime da un timestamp UNIX con il fuso orario predefinito +DateTime::from(42); // crea un DateTime dall'ora corrente più 42 secondi +DateTime::from('1994-02-26 04:15:32'); // crea un DateTime da una stringa +DateTime::from('1994-02-26'); // crea un DateTime da una data, l'ora sarà 00:00:00 ``` @@ -38,17 +38,17 @@ DateTime::fromParts(1994, 2, 26, 4, 15, 32); ``` -static createFromFormat(string $format, string $time, string|\DateTimeZone $timezone=null): DateTime|false .[method] --------------------------------------------------------------------------------------------------------------------- -Estende [DateTime::createFromFormat() |https://www.php.net/manual/en/datetime.createfromformat.php] con la possibilità di specificare un fuso orario come stringa. +static createFromFormat(string $format, string $time, ?string|\DateTimeZone $timezone=null): DateTime|false .[method] +--------------------------------------------------------------------------------------------------------------------- +Estende [DateTime::createFromFormat()|https://www.php.net/manual/en/datetime.createfromformat.php] con la possibilità di specificare il fuso orario come stringa. ```php -DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); // create with custom timezone +DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); ``` modifyClone(string $modify=''): static .[method] ------------------------------------------------ -Crea una copia con un'ora modificata. +Crea una copia con l'ora modificata. ```php $original = DateTime::from('2017-02-03'); $clone = $original->modifyClone('+1 day'); @@ -65,8 +65,8 @@ echo $dateTime; // '2017-02-03 04:15:32' ``` -Implementa JsonSerializable .[#toc-implements-jsonserializable] ---------------------------------------------------------------- +implementa JsonSerializable +--------------------------- Restituisce la data e l'ora nel formato ISO 8601, utilizzato ad esempio in JavaScript. ```php $date = DateTime::from('2017-02-03'); diff --git a/utils/it/filesystem.texy b/utils/it/filesystem.texy index 695312636c..0fb9af6f2d 100644 --- a/utils/it/filesystem.texy +++ b/utils/it/filesystem.texy @@ -1,41 +1,43 @@ -Funzioni del filesystem -*********************** +Sistema di file +*************** .[perex] -[api:Nette\Utils\FileSystem] è una classe statica che contiene funzioni utili per lavorare con il filesystem. Un vantaggio rispetto alle funzioni native di PHP è che lanciano eccezioni in caso di errori. +[api:Nette\Utils\FileSystem] è una classe con funzioni utili per lavorare con il file system. Uno dei vantaggi rispetto alle funzioni PHP native è che lanciano eccezioni in caso di errore. +Se hai bisogno di cercare file sul disco, usa [Finder |finder]. + Installazione: ```shell composer require nette/utils ``` -Gli esempi seguenti presuppongono che sia stato definito il seguente alias di classe: +Gli esempi seguenti presuppongono la creazione di un alias: ```php use Nette\Utils\FileSystem; ``` -Manipolazione .[#toc-manipulation] -================================== +Manipolazione +============= copy(string $origin, string $target, bool $overwrite=true): void .[method] -------------------------------------------------------------------------- -Copia un file o un'intera directory. Sovrascrive i file e le directory esistenti per impostazione predefinita. Se `$overwrite` è impostato su `false` e un `$target` esiste già, lancia un'eccezione `Nette\InvalidStateException`. Lancia un'eccezione `Nette\IOException` se si verifica un errore. +Copia un file o un'intera directory. Per impostazione predefinita, sovrascrive i file e le directory esistenti. Con il parametro `$overwrite` impostato su `false`, lancia un'eccezione `Nette\InvalidStateException` se il file o la directory di destinazione `$target` esiste. In caso di errore, lancia un'eccezione `Nette\IOException`. ```php FileSystem::copy('/path/to/source', '/path/to/dest', overwrite: true); ``` -createDir(string $directory, int $mode=0777): void .[method] ------------------------------------------------------------- +createDir(string $dir, int $mode=0777): void .[method] +------------------------------------------------------ -Crea una directory se non esiste, comprese le directory parentali. Lancia un'eccezione `Nette\IOException` se si verifica un errore. +Crea una directory se non esiste, incluse le directory padre. In caso di errore, lancia un'eccezione `Nette\IOException`. ```php FileSystem::createDir('/path/to/dir'); @@ -45,7 +47,7 @@ FileSystem::createDir('/path/to/dir'); delete(string $path): void .[method] ------------------------------------ -Elimina un file o un'intera directory, se esistente. Se la directory non è vuota, ne cancella prima il contenuto. Lancia un'eccezione `Nette\IOException` se si verifica un errore. +Elimina un file o un'intera directory se esiste. Se la directory non è vuota, elimina prima il suo contenuto. In caso di errore, lancia un'eccezione `Nette\IOException`. ```php FileSystem::delete('/path/to/fileOrDir'); @@ -55,7 +57,7 @@ FileSystem::delete('/path/to/fileOrDir'); makeWritable(string $path, int $dirMode=0777, int $fileMode=0666): void .[method] --------------------------------------------------------------------------------- -Imposta i permessi dei file a `$fileMode` o i permessi delle directory a `$dirMode`. Esegue una ricerca ricorsiva e imposta i permessi anche sull'intero contenuto della directory. +Imposta i permessi del file su `$fileMode` o della directory su `$dirMode`. Attraversa ricorsivamente e imposta i permessi anche per l'intero contenuto della directory. ```php FileSystem::makeWritable('/path/to/fileOrDir'); @@ -65,7 +67,7 @@ FileSystem::makeWritable('/path/to/fileOrDir'); open(string $path, string $mode): resource .[method] ---------------------------------------------------- -Apre il file e restituisce la risorsa. Il parametro `$mode` funziona come la funzione nativa `fopen()`:https://www.php.net/manual/en/function.fopen.php. Se si verifica un errore, viene sollevata l'eccezione `Nette\IOException`. +Apre un file e restituisce una risorsa. Il parametro `$mode` funziona allo stesso modo della funzione nativa `fopen()`:https://www.php.net/manual/en/function.fopen.php. In caso di errore, lancia un'eccezione `Nette\IOException`. ```php $res = FileSystem::open('/path/to/file', 'r'); @@ -75,7 +77,7 @@ $res = FileSystem::open('/path/to/file', 'r'); read(string $file): string .[method] ------------------------------------ -Legge il contenuto di un file `$file`. Lancia un'eccezione `Nette\IOException` se si verifica un errore. +Restituisce il contenuto del file `$file`. In caso di errore, lancia un'eccezione `Nette\IOException`. ```php $content = FileSystem::read('/path/to/file'); @@ -85,14 +87,13 @@ $content = FileSystem::read('/path/to/file'); readLines(string $file, bool $stripNewLines=true): \Generator .[method] ----------------------------------------------------------------------- -Legge il contenuto del file riga per riga. A differenza della funzione nativa `file()`, non legge l'intero file in memoria, ma lo legge continuamente, in modo da poter leggere file più grandi della memoria disponibile. Il parametro `$stripNewLines` specifica se eliminare i caratteri di interruzione di riga `\r` e `\n`. -In caso di errore, solleva un'eccezione `Nette\IOException`. +Legge il contenuto del file riga per riga. A differenza della funzione nativa `file()`, non carica l'intero file in memoria, ma lo legge progressivamente, rendendo possibile leggere anche file più grandi della memoria disponibile. `$stripNewLines` indica se rimuovere i caratteri di fine riga `\r` e `\n`. In caso di errore, lancia un'eccezione `Nette\IOException`. ```php $lines = FileSystem::readLines('/path/to/file'); foreach ($lines as $lineNum => $line) { - echo "Line $lineNum: $line\n"; + echo "Riga $lineNum: $line\n"; } ``` @@ -100,7 +101,7 @@ foreach ($lines as $lineNum => $line) { rename(string $origin, string $target, bool $overwrite=true): void .[method] ---------------------------------------------------------------------------- -Rinomina o sposta un file o una directory specificata da `$origin` a `$target`. Sovrascrive i file e le directory esistenti per impostazione predefinita. Se `$overwrite` è impostato su `false` e `$target` esiste già, lancia un'eccezione `Nette\InvalidStateException`. Lancia un'eccezione `Nette\IOException` se si verifica un errore. +Rinomina o sposta il file o la directory `$origin`. Per impostazione predefinita, sovrascrive i file e le directory esistenti. Con il parametro `$overwrite` impostato su `false`, lancia un'eccezione `Nette\InvalidStateException` se il file o la directory di destinazione `$target` esiste. In caso di errore, lancia un'eccezione `Nette\IOException`. ```php FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); @@ -110,21 +111,21 @@ FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); write(string $file, string $content, int $mode=0666): void .[method] -------------------------------------------------------------------- -Scrive il file `$content` in un file `$file`. Lancia un'eccezione `Nette\IOException` se si verifica un errore. +Scrive la stringa `$content` nel file `$file`. In caso di errore, lancia un'eccezione `Nette\IOException`. ```php FileSystem::write('/path/to/file', $content); ``` -Percorsi .[#toc-paths] -====================== +Percorsi +======== isAbsolute(string $path): bool .[method] ---------------------------------------- -Determina se il sito `$path` è assoluto. +Determina se il percorso `$path` è assoluto. ```php FileSystem::isAbsolute('../backup'); // false @@ -135,7 +136,7 @@ FileSystem::isAbsolute('C:/backup'); // true joinPaths(string ...$segments): string .[method] ------------------------------------------------ -Unisce tutti i segmenti del percorso e normalizza il risultato. +Congiunge tutti i segmenti del percorso e normalizza il risultato. ```php FileSystem::joinPaths('a', 'b', 'file.txt'); // 'a/b/file.txt' @@ -146,7 +147,7 @@ FileSystem::joinPaths('/a/', '/../b'); // '/b' normalizePath(string $path): string .[method] --------------------------------------------- -Normalizza `..` e `.` e i separatori di directory nel percorso. +Normalizza `..` e `.` e i separatori di directory nel percorso a quelli di sistema (`/`). ```php FileSystem::normalizePath('/file/.'); // '/file/' @@ -159,7 +160,7 @@ FileSystem::normalizePath('file/../../bar'); // '/../bar' unixSlashes(string $path): string .[method] ------------------------------------------- -Converte gli slash in `/` utilizzati nei sistemi Unix. +Converte le barre rovesciate in `/` utilizzate nei sistemi Unix. ```php $path = FileSystem::unixSlashes($path); @@ -169,8 +170,45 @@ $path = FileSystem::unixSlashes($path); platformSlashes(string $path): string .[method] ----------------------------------------------- -Converte gli slash in caratteri specifici della piattaforma corrente, ad esempio `\` su Windows e `/` altrove. +Converte le barre nei caratteri specifici della piattaforma corrente, cioè `\` su Windows e `/` altrove. ```php $path = FileSystem::platformSlashes($path); ``` + + +resolvePath(string $basePath, string $path): string .[method]{data-version:4.0.6} +--------------------------------------------------------------------------------- + +Deriva il percorso finale dal percorso `$path` rispetto alla directory di base `$basePath`. Lascia invariati i percorsi assoluti (`/foo`, `C:/foo`) (normalizza solo le barre), i percorsi relativi vengono aggiunti al percorso di base. + +```php +// Su Windows le barre nell'output sarebbero invertite (\) +FileSystem::resolvePath('/base/dir', '/abs/path'); // '/abs/path' +FileSystem::resolvePath('/base/dir', 'rel'); // '/base/dir/rel' +FileSystem::resolvePath('base/dir', '../file.txt'); // 'base/file.txt' +FileSystem::resolvePath('base', ''); // 'base' +``` + + +Approccio statico vs non statico +================================ + +Ad esempio, per poter sostituire facilmente la classe con un'altra (un mock) a scopo di test, utilizzala in modo non statico: + +```php +class AnyClassUsingFileSystem +{ + public function __construct( + private FileSystem $fileSystem, + ) { + } + + public function readConfig(): string + { + return $this->fileSystem->read(/* ... */); + } + + ... +} +``` diff --git a/utils/it/finder.texy b/utils/it/finder.texy index fd44122a2a..a722607377 100644 --- a/utils/it/finder.texy +++ b/utils/it/finder.texy @@ -1,8 +1,8 @@ -Finder: Ricerca di file +Finder: ricerca di file *********************** .[perex] -Avete bisogno di trovare i file che corrispondono a una determinata maschera? Il Finder può aiutarvi. È uno strumento versatile e veloce per navigare nella struttura delle directory. +Hai bisogno di trovare file che corrispondano a una certa maschera? Finder ti aiuterà in questo. È uno strumento versatile e veloce per attraversare la struttura delle directory. Installazione: @@ -18,10 +18,10 @@ use Nette\Utils\Finder; ``` -Utilizzando .[#toc-using] -------------------------- +Utilizzo +-------- -Per prima cosa, vediamo come utilizzare [api:Nette\Utils\Finder] per elencare i nomi dei file con estensione `.txt` e `.md` nella directory corrente: +Innanzitutto, vediamo come puoi usare [api:Nette\Utils\Finder] per elencare i nomi dei file con estensioni `.txt` e `.md` nella directory corrente: ```php foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { @@ -29,96 +29,97 @@ foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { } ``` -La directory predefinita per la ricerca è quella corrente, ma è possibile cambiarla utilizzando i metodi [in() o from() |#Where to search?]. -La variabile `$file` è un'istanza della classe [FileInfo |#FileInfo] con molti metodi utili. La chiave `$name` contiene il percorso del file come stringa. +La directory predefinita per la ricerca è la directory corrente, ma puoi cambiarla usando i metodi [in() o from() |#Dove cercare]. La variabile `$file` è un'istanza della classe [#FileInfo] con molti metodi utili. Nella chiave `$name` c'è il percorso del file come stringa. -Cosa cercare? .[#toc-what-to-search-for] ----------------------------------------- +Cosa cercare? +------------- -Oltre al metodo `findFiles()`, esistono anche `findDirectories()`, che cerca solo nelle directory, e `find()`, che cerca in entrambe. Questi metodi sono statici, quindi possono essere richiamati senza creare un'istanza. Il parametro mask è opzionale; se non viene specificato, viene cercato tutto. +Oltre al metodo `findFiles()`, c'è anche `findDirectories()`, che cerca solo directory, e `find()`, che cerca entrambi. Questi metodi sono statici, quindi possono essere chiamati senza creare un'istanza. Il parametro con la maschera è opzionale; se non lo specifichi, verrà cercato tutto. ```php foreach (Finder::find() as $file) { - echo $file; // ora tutti i file e le directory sono elencati + echo $file; // ora verranno stampati tutti i file e le directory } ``` -Si possono usare i metodi `files()` e `directories()` per aggiungere altri elementi da ricercare. I metodi possono essere richiamati ripetutamente e si può fornire un array di maschere come parametro: +Usando i metodi `files()` e `directories()`, puoi specificare cos'altro cercare. I metodi possono essere chiamati ripetutamente e puoi anche specificare un array di maschere come parametro: ```php Finder::findDirectories('vendor') // tutte le directory ->files(['*.php', '*.phpt']); // più tutti i file PHP ``` -Un'alternativa ai metodi statici è creare un'istanza con `new Finder` (l'oggetto fresco creato in questo modo non cerca nulla) e specificare cosa cercare con `files()` e `directories()`: +Un'alternativa ai metodi statici è creare un'istanza usando `new Finder` (un oggetto appena creato in questo modo non cerca nulla) e specificare cosa cercare usando `files()` e `directories()`: ```php (new Finder) - ->directories() // tutte le directory - ->files('*.php'); // più tutti i file PHP + ->directories() // tutte le directory + ->files('*.php'); // più tutti i file PHP ``` -Si possono usare [i caratteri jolly |#wildcards] `*`, `**`, `?` and `[...]` nella maschera. Si possono anche specificare le directory, ad esempio `src/*.php` cercherà tutti i file PHP nella directory `src`. +Nella maschera puoi usare i [#caratteri jolly] `*`, `**`, `?` e `[...]`. Puoi persino specificare directory, ad esempio `src/*.php` cercherà tutti i file PHP nella directory `src`. +Anche i link simbolici sono considerati directory o file. -Dove cercare? .[#toc-where-to-search] -------------------------------------- -La directory di ricerca predefinita è la directory corrente. È possibile modificarla utilizzando i metodi `in()` e `from()`. Come si può vedere dai nomi dei metodi, `in()` cerca solo nella directory corrente, mentre `from()` cerca anche nelle sue sottodirectory (in modo ricorsivo). Se si desidera effettuare una ricerca ricorsiva nella directory corrente, si può usare `from('.')`. +Dove cercare? +------------- -Questi metodi possono essere richiamati più volte o si possono passare percorsi multipli come array; i file verranno cercati in tutte le directory. Se una delle directory non esiste, viene lanciato `Nette\UnexpectedValueException`. +La directory predefinita per la ricerca è la directory corrente. Puoi cambiarla usando i metodi `in()` e `from()`. Come suggeriscono i nomi dei metodi, `in()` cerca solo nella directory specificata, mentre `from()` cerca anche nelle sue sottodirectory (ricorsivamente). Se vuoi cercare ricorsivamente nella directory corrente, puoi usare `from('.')`. + +Questi metodi possono essere chiamati più volte o puoi passare loro più percorsi come array; i file verranno quindi cercati in tutte le directory. Se una delle directory non esiste, verrà lanciata un'eccezione `Nette\UnexpectedValueException`. ```php Finder::findFiles('*.php') ->in(['src', 'tests']) // cerca direttamente in src/ e tests/ - ->from('vendor'); // cerca anche nelle sottodirectory di vendor/ + ->from('vendor'); // cerca anche nelle sottodirectory di vendor/ ``` -I percorsi relativi sono relativi alla directory corrente. Naturalmente, possono essere specificati anche percorsi assoluti: +I percorsi relativi sono relativi alla directory corrente. Naturalmente, è possibile specificare anche percorsi assoluti: ```php Finder::findFiles('*.php') ->in('/var/www/html'); ``` -Caratteri [jolly |#wildcards] `*`, `**`, `?` can be used in the path. For example, you can use the path `src/*/*.php` per cercare tutti i file PHP nelle directory di secondo livello della directory `src`. Il carattere `**`, chiamato globstar, è un potente asso nella manica perché permette di cercare anche nelle sottodirectory: usare `src/**/tests/*.php` per cercare tutti i file PHP nella directory `tests` che si trovano in `src` o in una qualsiasi delle sue sottodirectory. +È possibile utilizzare i [#caratteri jolly] `*`, `**`, `?` nel percorso. Ad esempio, puoi usare il percorso `src/*/*.php` per cercare tutti i file PHP nelle directory di secondo livello nella directory `src`. Il carattere `**`, chiamato globstar, è un potente asso nella manica perché permette di cercare anche nelle sottodirectory: usando `src/**/tests/*.php` cerchi tutti i file PHP nella directory `tests` situata in `src` o in una qualsiasi delle sue sottodirectory. -D'altra parte, i caratteri jolly `[...]` non sono supportati nel percorso, cioè non hanno un significato speciale per evitare comportamenti indesiderati nel caso in cui si cerchi, ad esempio, `in(__DIR__)` e per caso appaia il carattere `[]` nel percorso. +Al contrario, i caratteri jolly `[...]` non sono supportati nel percorso, cioè non hanno un significato speciale, per evitare comportamenti indesiderati nel caso in cui si cerchi, ad esempio, `in(__DIR__)` e casualmente nel percorso compaiano i caratteri `[]`. -Quando si effettua una ricerca approfondita di file e directory, viene restituita prima la directory madre e poi i file in essa contenuti, cosa che può essere invertita con `childFirst()`. +Durante la ricerca approfondita di file e directory, viene restituita prima la directory padre e solo dopo i file in essa contenuti, il che può essere invertito usando `childFirst()`. -I caratteri jolly .[#toc-wildcards] ------------------------------------ +Caratteri jolly +--------------- -È possibile utilizzare diversi caratteri speciali nella maschera: +Nella maschera puoi usare diversi caratteri speciali: -- `*` - replaces any number of arbitrary characters (except `/`) -- `**` - sostituisce un numero qualsiasi di caratteri arbitrari, incluso `/` (cioè può essere ricercato a più livelli) -- `?` - replaces one arbitrary character (except `/`) +- `*` - sostituisce un numero qualsiasi di caratteri qualsiasi (eccetto `/`) +- `**` - sostituisce un numero qualsiasi di caratteri qualsiasi incluso `/` (cioè è possibile cercare a più livelli) +- `?` - sostituisce un carattere qualsiasi (eccetto `/`) - `[a-z]` - sostituisce un carattere dall'elenco di caratteri tra parentesi quadre -- `[!a-z]` - sostituisce un carattere al di fuori dell'elenco di caratteri tra parentesi quadre +- `[!a-z]` - sostituisce un carattere non presente nell'elenco di caratteri tra parentesi quadre Esempi di utilizzo: -- `img/?.png` - file con il nome di una sola lettera `0.png`, `1.png`, `x.png`, ecc. -- `logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log` - file di log nel formato `YYYY-MM-DD` +- `img/?.png` - file con un nome di una sola lettera `0.png`, `1.png`, `x.png`, ecc. +- `logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log` - log nel formato `YYYY-MM-DD` - `src/**/tests/*` - file nella directory `src/tests`, `src/foo/tests`, `src/foo/bar/tests` e così via. -- `docs/**.md` - tutti i file con l'estensione `.md` in tutte le sottodirectory della directory `docs` +- `docs/**.md` - tutti i file con estensione `.md` in tutte le sottodirectory della directory `docs` -Escluso .[#toc-excluding] -------------------------- +Esclusione +---------- -Utilizzate il metodo `exclude()` per escludere file e directory dalle ricerche. Si specifica una maschera a cui il file non deve corrispondere. Esempio di ricerca dei file `*.txt` tranne quelli contenenti la lettera `X` nel nome: +Usando il metodo `exclude()`, è possibile escludere file e directory dalla ricerca. Specifichi una maschera a cui il file non deve corrispondere. Esempio di ricerca di file `*.txt` eccetto quelli che contengono la lettera `X` nel nome: ```php Finder::findFiles('*.txt') ->exclude('*X*'); ``` -Utilizzare `exclude()` per saltare le sottodirectory sfogliate: +Per saltare le sottodirectory attraversate, usa `exclude()`: ```php Finder::findFiles('*.php') @@ -127,12 +128,12 @@ Finder::findFiles('*.php') ``` -Filtraggio .[#toc-filtering] ----------------------------- +Filtraggio +---------- -Il Finder offre diversi metodi per filtrare i risultati (cioè per ridurli). È possibile combinarli e richiamarli ripetutamente. +Finder offre diversi metodi per filtrare i risultati (cioè ridurli). Puoi combinarli e chiamarli ripetutamente. -Utilizzate `size()` per filtrare in base alla dimensione del file. In questo modo si trovano i file con dimensioni comprese tra 100 e 200 byte: +Usando `size()`, filtriamo per dimensione del file. In questo modo troviamo file con dimensioni comprese tra 100 e 200 byte: ```php Finder::findFiles('*.php') @@ -140,7 +141,7 @@ Finder::findFiles('*.php') ->size('<=', 200); ``` -Il metodo `date()` filtra in base alla data dell'ultima modifica del file. I valori possono essere assoluti o relativi alla data e all'ora corrente, ad esempio per trovare i file modificati nelle ultime due settimane: +Il metodo `date()` filtra per data dell'ultima modifica del file. I valori possono essere assoluti o relativi alla data e all'ora correnti; ad esempio, in questo modo troviamo i file modificati nelle ultime due settimane: ```php Finder::findFiles('*.php') @@ -150,9 +151,9 @@ Finder::findFiles('*.php') Entrambe le funzioni comprendono gli operatori `>`, `>=`, `<`, `<=`, `=`, `!=`, `<>`. -Il Finder consente anche di filtrare i risultati utilizzando funzioni personalizzate. La funzione riceve un oggetto `Nette\Utils\FileInfo` come parametro e deve restituire `true` per includere il file nei risultati. +Finder permette anche di filtrare i risultati usando funzioni personalizzate. La funzione riceve come parametro un oggetto `Nette\Utils\FileInfo` e deve restituire `true` affinché il file sia incluso nei risultati. -Esempio: ricerca dei file PHP che contengono la stringa `Nette` (senza distinzione tra maiuscole e minuscole): +Esempio: ricerca di file PHP che contengono la stringa `Nette` (indipendentemente dalle maiuscole/minuscole): ```php Finder::findFiles('*.php') @@ -160,51 +161,51 @@ Finder::findFiles('*.php') ``` -Filtro di profondità .[#toc-depth-filtering] --------------------------------------------- +Filtraggio in profondità +------------------------ -Quando si effettua una ricerca ricorsiva, è possibile impostare la profondità massima di crawling utilizzando il metodo `limitDepth()`. Se si imposta `limitDepth(1)`, vengono strisciate solo le prime sottodirectory, `limitDepth(0)` disabilita il crawling di profondità e un valore di -1 annulla il limite. +Durante la ricerca ricorsiva, puoi impostare la profondità massima di attraversamento usando il metodo `limitDepth()`. Se imposti `limitDepth(1)`, vengono attraversate solo le prime sottodirectory, `limitDepth(0)` disattiva l'attraversamento in profondità e il valore -1 annulla il limite. -Il Finder consente di utilizzare le proprie funzioni per decidere quale directory inserire durante la navigazione. La funzione riceve un oggetto `Nette\Utils\FileInfo` come parametro e deve restituire `true` per entrare nella directory: +Finder permette di decidere in quale directory entrare durante l'attraversamento usando funzioni personalizzate. La funzione riceve come parametro un oggetto `Nette\Utils\FileInfo` e deve restituire `true` per entrare nella directory: ```php Finder::findFiles('*.php') - ->descentFilter($file->getBasename() !== 'temp'); + ->descentFilter(fn($file) => $file->getBasename() !== 'temp'); ``` -Ordinamento .[#toc-sorting] ---------------------------- +Ordinamento +----------- -Il Finder offre anche diverse funzioni per ordinare i risultati. +Finder offre anche diverse funzioni per ordinare i risultati. -Il metodo `sortByName()` ordina i risultati in base al nome del file. L'ordinamento è naturale, cioè gestisce correttamente i numeri nei nomi e restituisce, ad esempio, `foo1.txt` prima di `foo10.txt`. +Il metodo `sortByName()` ordina i risultati per nome del file. L'ordinamento è naturale, quindi gestisce correttamente i numeri nei nomi e restituisce, ad esempio, `foo1.txt` prima di `foo10.txt`. -Il Finder consente anche di ordinare utilizzando una funzione personalizzata. Essa prende come parametri due oggetti `Nette\Utils\FileInfo` e deve restituire il risultato del confronto con l'operatore `<=>`cioè `-1`, `0` nebo `1`. Ad esempio, ecco come ordinare i file in base alle dimensioni: +Finder permette anche di ordinare usando una funzione personalizzata. Questa riceve come parametri due oggetti `Nette\Utils\FileInfo` e deve restituire il risultato del confronto con l'operatore `<=>`, cioè `-1`, `0` o `1`. Ad esempio, in questo modo ordiniamo i file per dimensione: ```php $finder->sortBy(fn($a, $b) => $a->getSize() <=> $b->getSize()); ``` -Ricerche multiple diverse .[#toc-multiple-different-searches] -------------------------------------------------------------- +Ricerche multiple diverse +------------------------- -Se è necessario trovare più file diversi in posizioni diverse o che soddisfano criteri diversi, utilizzare il metodo `append()`. Esso restituisce un nuovo oggetto `Finder`, in modo da poter concatenare le chiamate ai metodi: +Se hai bisogno di trovare più file diversi in posizioni diverse o che soddisfano criteri diversi, usa il metodo `append()`. Restituisce un nuovo oggetto `Finder`, quindi è possibile concatenare le chiamate ai metodi: ```php -($finder = new Finder) // memorizza il primo Finder nella variabile $finder! - ->files('*.php') // cerca i file *.php in src/ +($finder = new Finder) // salviamo il primo Finder nella variabile $finder! + ->files('*.php') // in src/ cerchiamo file *.php ->from('src') ->append() - ->files('*.md') // in docs/ cerca i file *.md + ->files('*.md') // in docs/ cerchiamo file *.md ->from('docs') ->append() - ->files('*.json'); // nella cartella corrente cerca i file *.json + ->files('*.json'); // nella cartella corrente cerchiamo file *.json ``` -In alternativa, si può usare il metodo `append()` per aggiungere un file specifico (o un array di file). In tal caso, viene restituito lo stesso oggetto `Finder`: +In alternativa, è possibile utilizzare il metodo `append()` per aggiungere un file specifico (o un array di file). Quindi restituisce lo stesso oggetto `Finder`: ```php $finder = Finder::findFiles('*.txt') @@ -212,12 +213,12 @@ $finder = Finder::findFiles('*.txt') ``` -FileInfo .[#toc-fileinfo] -------------------------- +FileInfo +-------- -[Nette\Utils\FileInfo |api:] è una classe che rappresenta un file o una directory nei risultati della ricerca. È un'estensione della classe [SplFileInfo |php:SplFileInfo] che fornisce informazioni quali la dimensione del file, la data dell'ultima modifica, il nome, il percorso, ecc. +[api:Nette\Utils\FileInfo] è una classe che rappresenta un file o una directory nei risultati della ricerca. È un'estensione della classe [SplFileInfo |php:SplFileInfo], che fornisce informazioni come la dimensione del file, la data dell'ultima modifica, il nome, il percorso, ecc. -Inoltre, fornisce metodi per la restituzione di percorsi relativi, utili quando si naviga in profondità: +Inoltre, fornisce metodi per restituire il percorso relativo, il che è utile durante l'attraversamento in profondità: ```php foreach (Finder::findFiles('*.jpg')->from('.') as $file) { @@ -226,7 +227,7 @@ foreach (Finder::findFiles('*.jpg')->from('.') as $file) { } ``` -Sono disponibili anche metodi per leggere e scrivere il contenuto di un file: +Inoltre, hai a disposizione metodi per leggere e scrivere il contenuto del file: ```php foreach ($finder as $file) { @@ -237,12 +238,12 @@ foreach ($finder as $file) { ``` -Restituzione dei risultati come array .[#toc-returning-results-as-an-array] ---------------------------------------------------------------------------- +Restituzione dei risultati come array +------------------------------------- -Come si è visto negli esempi, il Finder implementa l'interfaccia `IteratorAggregate`, quindi si può usare `foreach` per sfogliare i risultati. È programmato in modo che i risultati vengano caricati solo durante la navigazione, quindi se si ha un gran numero di file, non si aspetta che vengano letti tutti. +Come visto negli esempi, Finder implementa l'interfaccia `IteratorAggregate`, quindi puoi usare `foreach` per scorrere i risultati. È programmato in modo che i risultati vengano caricati solo durante l'iterazione, quindi se hai un gran numero di file, non si aspetta che vengano letti tutti. -I risultati possono anche essere restituiti come un array di oggetti `Nette\Utils\FileInfo`, usando il metodo `collect()`. L'array non è associativo, ma numerico. +Puoi anche farti restituire i risultati come un array di oggetti `Nette\Utils\FileInfo`, usando il metodo `collect()`. L'array non è associativo, ma numerico. ```php $array = $finder->findFiles('*.php')->collect(); diff --git a/utils/it/floats.texy b/utils/it/floats.texy index 87d310788f..cc6454d385 100644 --- a/utils/it/floats.texy +++ b/utils/it/floats.texy @@ -1,8 +1,8 @@ -Funzioni a galleggiante -*********************** +Lavorare con i float +******************** .[perex] -[api:Nette\Utils\Floats] è una classe statica con funzioni utili per confrontare i numeri float. +[api:Nette\Utils\Floats] è una classe statica con funzioni utili per confrontare numeri decimali. Installazione: @@ -11,40 +11,42 @@ Installazione: composer require nette/utils ``` -Tutti gli esempi presuppongono che sia definito il seguente alias di classe: +Tutti gli esempi presuppongono la creazione di un alias: ```php use Nette\Utils\Floats; ``` -Motivazione .[#toc-motivation] -============================== +Motivazione +=========== -Vi state chiedendo a cosa serva una classe di confronto per i float? Si possono usare gli operatori `<`, `>`, `===`, si pensa. -Questo non è del tutto vero. Cosa pensate che stampi questo codice? +Ti starai chiedendo, perché una classe per confrontare i float? Dopotutto, posso usare gli operatori `<`, `>`, `===` e sono a posto. Non è del tutto vero. Cosa pensi che stamperà questo codice? ```php $a = 0.1 + 0.2; $b = 0.3; -echo $a === $b ? 'same' : 'not same'; +echo $a === $b ? 'uguale' : 'non uguale'; // Tradotto "same" e "not same" ``` -Se eseguite il codice, alcuni di voi saranno sorpresi che il programma abbia stampato `not same`. +Se esegui il codice, alcuni di voi saranno sicuramente sorpresi che il programma abbia stampato `non uguale`. -Le operazioni matematiche con i numeri float causano errori dovuti alla conversione tra il sistema decimale e quello binario. Ad esempio, `0.1 + 0.2` equivale a `0.300000000000000044…`. Pertanto, quando si confrontano i numeri float, è necessario tollerare una piccola differenza da una certa posizione decimale. +Durante le operazioni matematiche con numeri decimali, si verificano errori a causa della conversione tra il sistema decimale e quello binario. Ad esempio, `0.1 + 0.2` risulta `0.300000000000000044…`. Pertanto, durante il confronto, dobbiamo tollerare una piccola differenza a partire da una certa posizione decimale. -Questo è ciò che fa la classe `Floats`. Il seguente confronto funzionerà come previsto: +Ed è proprio quello che fa la classe `Floats`. Il seguente confronto funzionerà come previsto: ```php -echo Floats::areEqual($a, $b) ? 'same' : 'not same'; // same +echo Floats::areEqual($a, $b) ? 'uguale' : 'non uguale'; // uguale ``` -Quando si tenta di confrontare `NAN`, viene lanciata un'eccezione `\LogicException`. +Quando si tenta di confrontare `NAN`, lancia un'eccezione `\LogicException`. +.[tip] +La classe `Floats` tollera differenze inferiori a `1e-10`. Se hai bisogno di lavorare con una precisione maggiore, usa piuttosto la libreria BCMath. -Confronto tra i galleggianti .[#toc-float-comparison] -===================================================== + +Confronto di float +================== areEqual(float $a, float $b): bool .[method] @@ -104,25 +106,25 @@ Floats::isGreaterThanOrEqualTo(10.2, 10.2); // true compare(float $a, float $b): int .[method] ------------------------------------------ -Se `$a` < `$b`, restituisce `-1`, se sono uguali restituisce `0` and if `$a` > `$b` restituisce `1`. +Se `$a` < `$b`, restituisce `-1`, se sono uguali restituisce `0`, e se `$a` > `$b` restituisce `1`. -Può essere utilizzata, ad esempio, con la funzione `usort`. +Può essere utilizzato, ad esempio, con la funzione `usort`. ```php $arr = [1, 5, 2, -3.5]; -usort($arr, [Float::class, 'compare']); -// $arr is [-3.5, 1, 2, 5] +usort($arr, [Floats::class, 'compare']); +// $arr è ora [-3.5, 1, 2, 5] ``` -Funzioni Helpers .[#toc-helpers-functions] -========================================== +Funzioni ausiliarie +=================== isZero(float $value): bool .[method] ------------------------------------ -Restituisce `true` se il valore è zero. +Restituisce `true` se il valore è uguale a zero. ```php Floats::isZero(0.0); // true diff --git a/utils/it/helpers.texy b/utils/it/helpers.texy index e41bd5dfcc..fe2bf287a7 100644 --- a/utils/it/helpers.texy +++ b/utils/it/helpers.texy @@ -1,5 +1,5 @@ -Funzioni di aiuto -***************** +Funzioni ausiliarie +******************* .[perex] [api:Nette\Utils\Helpers] è una classe statica con funzioni utili. @@ -11,7 +11,7 @@ Installazione: composer require nette/utils ``` -Tutti gli esempi presuppongono che sia definito il seguente alias di classe: +Tutti gli esempi presuppongono la creazione di un alias: ```php use Nette\Utils\Helpers; @@ -21,7 +21,7 @@ use Nette\Utils\Helpers; capture(callable $cb): string .[method] --------------------------------------- -Esegue un callback e restituisce l'output catturato come stringa. +Esegue il callback e restituisce l'output catturato come stringa. ```php $res = Helpers::capture(function () use ($template) { @@ -33,7 +33,7 @@ $res = Helpers::capture(function () use ($template) { clamp(int|float $value, int|float $min, int|float $max): int|float .[method] ---------------------------------------------------------------------------- -Restituisce un valore limitato all'intervallo compreso tra min e max. +Limita il valore all'interno dell'intervallo inclusivo specificato min e max. ```php Helpers::clamp($level, 0, 255); @@ -43,8 +43,7 @@ Helpers::clamp($level, 0, 255); compare(mixed $left, string $operator, mixed $right): bool .[method] -------------------------------------------------------------------- -Confronta due valori nello stesso modo in cui lo fa PHP. Distingue tra gli operatori `>`, `>=`, `<`, `<=`, `=`, `==`, `===`, `!=`, `!==`, `<>`. -La funzione è utile in situazioni in cui l'operatore è variabile. +Confronta due valori nello stesso modo in cui lo fa PHP. Distingue gli operatori `>`, `>=`, `<`, `<=`, `=`, `==`, `===`, `!=`, `!==`, `<>`. La funzione è utile in situazioni in cui l'operatore è variabile. ```php Helpers::compare(10, '<', 20); // true @@ -54,7 +53,7 @@ Helpers::compare(10, '<', 20); // true falseToNull(mixed $value): mixed .[method] ------------------------------------------ -Converte `false` in `null`, non modifica gli altri valori. +Converte `false` in `null`, non modifica altri valori. ```php Helpers::falseToNull(false); // null @@ -65,23 +64,23 @@ Helpers::falseToNull(123); // 123 getLastError(): string .[method] -------------------------------- -Restituisce l'ultimo errore PHP verificatosi o una stringa vuota se non si è verificato alcun errore. A differenza di `error_get_last()`, non è influenzato dalla direttiva PHP `html_errors` e restituisce sempre testo, non HTML. +Restituisce l'ultimo errore in PHP o una stringa vuota se non si è verificato alcun errore. A differenza di `error_get_last()`, non è influenzato dalla direttiva PHP `html_errors` e restituisce sempre testo, non HTML. ```php Helpers::getLastError(); ``` -getSuggestion(string[] $possibilities, string $value): ?string .[method] ------------------------------------------------------------------------- +getSuggestion(array $possibilities, string $value): ?string .[method] +--------------------------------------------------------------------- -Cerca una stringa da `$possibilities` che sia la più simile a `$value`, ma non la stessa. Supporta solo codifiche a 8 bit. +Tra le opzioni offerte `$possibilities`, cerca la stringa più simile a `$value`, ma non identica. Supporta solo la codifica a 8 bit. -È utile se una certa opzione non è valida e si vuole suggerire all'utente un'opzione simile (ma diversa, quindi la stessa stringa viene ignorata). In questo modo, Nette crea i messaggi `did you mean ...?`. +È utile nel caso in cui una certa opzione non sia valida e vogliamo suggerire all'utente una simile (ma diversa, motivo per cui la stringa identica viene ignorata). In questo modo Nette crea i messaggi `did you mean ...?`. ```php $items = ['foo', 'bar', 'baz']; Helpers::getSuggestion($items, 'fo'); // 'foo' Helpers::getSuggestion($items, 'barr'); // 'bar' -Helpers::getSuggestion($items, 'baz'); // 'bar', ne 'baz' +Helpers::getSuggestion($items, 'baz'); // 'bar', non 'baz' ``` diff --git a/utils/it/html-elements.texy b/utils/it/html-elements.texy index b58fae2b14..60023592cc 100644 --- a/utils/it/html-elements.texy +++ b/utils/it/html-elements.texy @@ -2,15 +2,15 @@ Elementi HTML ************* .[perex] -La classe [api:Nette\Utils\Html] è un aiuto per la generazione di codice HTML che previene le vulnerabilità Cross Site Scripting (XSS). +La classe [api:Nette\Utils\Html] è un helper per la generazione di codice HTML che impedisce la vulnerabilità Cross Site Scripting (XSS). -Funziona in modo tale che i suoi oggetti rappresentano elementi HTML, si impostano i loro parametri e si lascia che vengano resi: +Funziona in modo tale che i suoi oggetti rappresentano elementi HTML, ai quali impostiamo i parametri e li facciamo renderizzare: ```php -$el = Html::el('img'); // crea l'elemento . +$el = Html::el('img'); // crea l'elemento $el->src = 'image.jpg'; // imposta l'attributo src -echo $el; // stampa '' +echo $el; // stampa '' ``` Installazione: @@ -19,29 +19,29 @@ Installazione: composer require nette/utils ``` -Tutti gli esempi presuppongono che sia definito il seguente alias di classe: +Tutti gli esempi presuppongono la creazione di un alias: ```php use Nette\Utils\Html; ``` -Creazione di un elemento HTML .[#toc-creating-an-html-element] -============================================================== +Creazione di un elemento HTML +============================= -L'elemento viene creato con il metodo `Html::el()`: +Creiamo un elemento con il metodo `Html::el()`: ```php -$el = Html::el('img'); // crea l'elemento ``` -Oltre al nome, è possibile inserire altri attributi nella sintassi HTML: +Oltre al nome, puoi specificare anche altri attributi nella sintassi HTML: ```php $el = Html::el('input type=text class="red important"'); ``` -Oppure passarli come array associativo al secondo parametro: +Oppure passarli come array associativo nel secondo parametro: ```php $el = Html::el('input', [ @@ -50,39 +50,39 @@ $el = Html::el('input', [ ]); ``` -Per modificare e restituire il nome di un elemento: +Modifica e restituzione del nome dell'elemento: ```php $el->setName('img'); $el->getName(); // 'img' -$el->isEmpty(); // vero, poiché è un elemento nullo +$el->isEmpty(); // true, poiché è un elemento vuoto ``` -Attributi HTML .[#toc-html-attributes] -====================================== +Attributi HTML +============== -È possibile impostare e ottenere i singoli attributi HTML in tre modi, a seconda di quello che si preferisce. Il primo è attraverso le proprietà: +Possiamo modificare e leggere i singoli attributi HTML in tre modi, dipende da te quale ti piacerà di più. Il primo è tramite le proprietà: ```php $el->src = 'image.jpg'; // imposta l'attributo src echo $el->src; // 'image.jpg' -unset($el->src); // rimuove l'attributo -// oppure $el->src = null; +unset($el->src); // rimuove l'attributo +// o $el->src = null; ``` -Il secondo modo è quello di chiamare metodi che, a differenza dell'impostazione delle proprietà, possono essere concatenati tra loro: +Il secondo modo è chiamare metodi, che, a differenza dell'impostazione delle proprietà, possiamo concatenare: ```php $el = Html::el('img')->src('image.jpg')->alt('photo'); // photo -$el->alt(null); // rimuove l'attributo +$el->alt(null); // rimozione dell'attributo ``` -E il terzo modo è il più loquace: +E il terzo modo è il più prolisso: ```php $el = Html::el('img') @@ -94,9 +94,9 @@ echo $el->getAttribute('src'); // 'image.jpg' $el->removeAttribute('alt'); ``` -In blocco, gli attributi possono essere impostati con `addAttributes(array $attrs)` e cancellati con `removeAttributes(array $attrNames)`. +È possibile impostare attributi in blocco usando `addAttributes(array $attrs)` e rimuoverli usando `removeAttributes(array $attrNames)`. -Il valore di un attributo non deve essere necessariamente una stringa, ma si possono usare anche valori logici per gli attributi logici: +Il valore di un attributo non deve essere solo una stringa, è possibile utilizzare anche valori booleani per attributi booleani: ```php $checkbox = Html::el('input')->type('checkbox'); @@ -104,17 +104,17 @@ $checkbox->checked = true; // $checkbox->checked = false; // ``` -Un attributo può anche essere un array di token, separati da spazi, adatto per esempio alle classi CSS: +Un attributo può anche essere un array di valori, che verranno stampati separati da spazi, il che è utile ad esempio per le classi CSS: ```php $el = Html::el('input'); $el->class[] = 'active'; -$el->class[] = null; // null is ignored +$el->class[] = null; // null viene ignorato $el->class[] = 'top'; echo $el; // '' ``` -Un'alternativa è un array associativo, in cui i valori dicono se la chiave deve essere elencata: +Un'alternativa è un array associativo, dove i valori indicano se la chiave deve essere stampata: ```php $el = Html::el('input'); @@ -132,7 +132,7 @@ $el->style['display'] = 'block'; echo $el; // '' ``` -Abbiamo utilizzato le proprietà, ma la stessa cosa può essere fatta utilizzando i metodi: +Ora abbiamo usato le proprietà, ma la stessa cosa può essere scritta usando i metodi: ```php $el = Html::el('input'); @@ -141,7 +141,7 @@ $el->style('display', 'block'); echo $el; // '' ``` -O anche nel modo più loquace: +O anche nel modo più prolisso: ```php $el = Html::el('input'); @@ -150,7 +150,7 @@ $el->appendAttribute('style', 'display', 'block'); echo $el; // '' ``` -Un'ultima cosa: il metodo `href()` può facilitare la composizione dei parametri di query in un URL: +Un ultimo piccolo dettaglio: il metodo `href()` può facilitare la composizione dei parametri di query nell'URL: ```php echo Html::el('a')->href('index.php', [ @@ -161,10 +161,10 @@ echo Html::el('a')->href('index.php', [ ``` -Attributi dei dati .[#toc-data-attributes] ------------------------------------------- +Attributi data +-------------- -Gli attributi dei dati hanno un supporto speciale. Poiché i loro nomi contengono dei trattini, l'accesso tramite proprietà e metodi non è molto elegante, quindi esiste un metodo `data()`: +Gli attributi data hanno un supporto speciale. Poiché i loro nomi contengono trattini, l'accesso tramite proprietà e metodi non è così elegante, quindi esiste il metodo `data()`: ```php $el = Html::el('input'); @@ -173,7 +173,7 @@ $el->data('max-size', '500x300'); // è elegante echo $el; // '' ``` -Se il valore dell'attributo dati è un array, viene automaticamente serializzato in JSON: +Se il valore di un attributo data è un array, viene automaticamente serializzato in JSON: ```php $el = Html::el('input'); @@ -182,10 +182,10 @@ echo $el; // '' ``` -Contenuto dell'elemento .[#toc-element-content] -=============================================== +Contenuto dell'elemento +======================= -Il contenuto interno dell'elemento viene impostato dai metodi `setHtml()` o `setText()`. Usare il primo solo se si sa che si sta passando in modo affidabile una stringa HTML sicura nel parametro. +Impostiamo il contenuto interno dell'elemento con i metodi `setHtml()` o `setText()`. Usa il primo solo se sai che stai passando una stringa HTML sicuramente sicura nel parametro. ```php echo Html::el('span')->setHtml('hello
                                                                                                                            '); @@ -195,7 +195,7 @@ echo Html::el('span')->setText('10 < 20'); // '10 < 20' ``` -Al contrario, il contenuto interno si ottiene con i metodi `getHtml()` o `getText()`. Il secondo rimuove i tag dall'output HTML e converte le entità HTML in caratteri. +E viceversa, otteniamo il contenuto interno con i metodi `getHtml()` o `getText()`. Il secondo rimuove i tag HTML dall'output e converte le entità HTML in caratteri. ```php echo $el->getHtml(); // '10 < 20' @@ -203,10 +203,10 @@ echo $el->getText(); // '10 < 20' ``` -Nodi figli .[#toc-child-nodes] ------------------------------- +Nodi figli +---------- -Il contenuto interno di un elemento può anche essere un array di figli. Ognuno di essi può essere una stringa o un altro elemento `Html`. Vengono inseriti usando `addHtml()` o `addText()`: +L'interno dell'elemento può anche essere un array di nodi figli (children). Ognuno di essi può essere una stringa o un altro elemento `Html`. Li inseriamo usando `addHtml()` o `addText()`: ```php $el = Html::el('span') @@ -219,13 +219,13 @@ $el = Html::el('span') Un altro modo per creare e inserire un nuovo nodo `Html`: ```php -$el = Html::el('ul') - ->create('li', ['class' => 'first']) - ->setText('hello'); -//
                                                                                                                            • hello
                                                                                                                            +$ul = Html::el('ul'); +$ul->create('li', ['class' => 'first']) + ->setText('primo'); +//
                                                                                                                            • primo
                                                                                                                            ``` -È possibile lavorare con i nodi come se fossero elementi di un array. Si può quindi accedere ai singoli nodi usando le parentesi quadre, contarli con `count()` e iterare su di essi: +È possibile lavorare con i nodi come se fossero un array. Cioè, accedere a ciascuno di essi usando parentesi quadre, contarli usando `count()` e iterare su di essi: ```php $el = Html::el('div'); @@ -238,20 +238,20 @@ foreach ($el as $child) { /* ... */ } echo count($el); // 2 ``` -Un nuovo nodo può essere inserito in una posizione specifica usando `insert(?int $index, $child, bool $replace = false)`. Se `$replace = false`, inserisce l'elemento nella posizione `$index` e sposta gli altri. Se `$index = null`, aggiunge un elemento alla fine. +È possibile inserire un nuovo nodo in una posizione specifica usando `insert(?int $index, $child, bool $replace = false)`. Se `$replace = false`, inserisce l'elemento nella posizione `$index` e sposta gli altri. Se `$index = null`, aggiunge l'elemento alla fine. ```php -// inserisce l'elemento nella prima posizione e fa avanzare gli altri +// inserisce l'elemento nella prima posizione e sposta gli altri $el->insert(0, Html::el('span')); ``` -Tutti i nodi vengono restituiti dal metodo `getChildren()` e rimossi dal metodo `removeChildren()`. +Otteniamo tutti i nodi con il metodo `getChildren()` e li rimuoviamo con il metodo `removeChildren()`. -Creare un frammento di documento .[#toc-creating-a-document-fragment] ---------------------------------------------------------------------- +Creazione di un frammento di documento +-------------------------------------- -Se si vuole lavorare con una matrice di nodi e non si è interessati all'elemento che li avvolge, si può creare un cosiddetto *frammento di documento* passando `null` al posto del nome dell'elemento: +Se vogliamo lavorare con un array di nodi e non ci interessa l'elemento contenitore, possiamo creare un cosiddetto *document fragment* passando `null` invece del nome dell'elemento: ```php $el = Html::el(null) @@ -261,7 +261,7 @@ $el = Html::el(null) // hello
                                                                                                                            10 < 20
                                                                                                                            ``` -I metodi `fromHtml()` e `fromText()` offrono un modo più rapido per creare un frammento: +Un modo più veloce per creare un frammento è offerto dai metodi `fromHtml()` e `fromText()`: ```php $el = Html::fromHtml('hello
                                                                                                                            '); @@ -272,15 +272,15 @@ echo $el; // '10 < 20' ``` -Generare l'output HTML .[#toc-generating-html-output] -===================================================== +Generazione dell'output HTML +============================ -Il modo più semplice per generare un elemento HTML è usare `echo` o lanciare un oggetto in `(string)`. È anche possibile stampare separatamente i tag e gli attributi di apertura o chiusura: +Il modo più semplice per stampare un elemento HTML è usare `echo` o convertire l'oggetto in `(string)`. È anche possibile stampare separatamente i tag di apertura o chiusura e gli attributi: ```php $el = Html::el('div class=header')->setText('hello'); -echo $el; // '
                                                                                                                            ' +echo $el; // '
                                                                                                                            hello
                                                                                                                            ' $s = (string) $el; // '
                                                                                                                            hello
                                                                                                                            ' $s = $el->toHtml(); // '
                                                                                                                            hello
                                                                                                                            ' $s = $el->toText(); // 'hello' @@ -289,7 +289,7 @@ echo $el->endTag(); // '' echo $el->attributes(); // 'class="header"' ``` -Una caratteristica importante è la protezione automatica contro il [Cross Site Scripting (XSS) |nette:glossary#cross-site-scripting-xss]. Tutti i valori degli attributi o i contenuti inseriti con `setText()` o `addText()` vengono evasi in modo affidabile: +Una caratteristica importante è la protezione automatica contro il [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS]. Tutti i valori degli attributi o il contenuto inserito tramite `setText()` o `addText()` vengono escapati in modo affidabile: ```php echo Html::el('div') @@ -300,17 +300,17 @@ echo Html::el('div') ``` -Conversione HTML ↔ Testo .[#toc-conversion-html-text] -===================================================== +Conversione HTML ↔ testo +======================== -È possibile utilizzare il metodo statico `htmlToText()` per convertire l'HTML in testo: +Per convertire HTML in testo, puoi utilizzare il metodo statico `htmlToText()`: ```php echo Html::htmlToText('One & Two'); // 'One & Two' ``` -HtmlStringable .[#toc-htmlstringable] -===================================== +HtmlStringable +============== -L'oggetto `Nette\Utils\Html` implementa l'interfaccia `Nette\HtmlStringable`, che, ad esempio, Latte o i form utilizzano per distinguere gli oggetti che hanno un metodo `__toString()` che restituisce codice HTML. Quindi il doppio escape non si verifica se, ad esempio, si stampa l'oggetto nel modello usando `{$el}`. +L'oggetto `Nette\Utils\Html` implementa l'interfaccia `Nette\HtmlStringable`, con cui, ad esempio, Latte o i form distinguono gli oggetti che hanno un metodo `__toString()` che restituisce codice HTML. Quindi non si verifica un doppio escaping se, ad esempio, stampiamo l'oggetto in un template usando `{$el}`. diff --git a/utils/it/images.texy b/utils/it/images.texy index f8888325f6..723d690a69 100644 --- a/utils/it/images.texy +++ b/utils/it/images.texy @@ -1,11 +1,11 @@ -Funzioni di immagine -******************** +Lavorare con le immagini +************************ .[perex] -La classe [api:Nette\Utils\Image] semplifica la manipolazione delle immagini, come il ridimensionamento, il ritaglio, la nitidezza, il disegno o la fusione di più immagini. +La classe [api:Nette\Utils\Image] semplifica la manipolazione delle immagini, come il ridimensionamento, il ritaglio, l'applicazione di nitidezza, il disegno o l'unione di più immagini. -PHP dispone di un'ampia serie di funzioni per la manipolazione delle immagini. Ma l'API non è molto bella. Non sarebbe un Neat Framework a proporre un'API sexy. +PHP dispone di un'ampia gamma di funzioni per la manipolazione delle immagini. Ma la loro API non è molto comoda. Non sarebbe Nette Framework se non avesse proposto un'API sexy. Installazione: @@ -13,119 +13,130 @@ Installazione: composer require nette/utils ``` -Gli esempi seguenti presuppongono che sia stato definito il seguente alias di classe: +Tutti gli esempi presuppongono la creazione di un alias: ```php use Nette\Utils\Image; +use Nette\Utils\ImageColor; +use Nette\Utils\ImageType; ``` -Creare un'immagine .[#toc-creating-an-image] -============================================ +Creazione di un'immagine +======================== -Creeremo una nuova immagine a colori reali, ad esempio con dimensioni 100×200: +Creiamo una nuova immagine true color, ad esempio con dimensioni 100×200: ```php $image = Image::fromBlank(100, 200); ``` -Opzionalmente, è possibile specificare un colore di sfondo (l'impostazione predefinita è il nero): +Opzionalmente, è possibile specificare il colore di sfondo (il predefinito è nero): ```php -$image = Image::fromBlank(100, 200, Image::rgb(125, 0, 0)); +$image = Image::fromBlank(100, 200, ImageColor::rgb(125, 0, 0)); ``` -Oppure si carica l'immagine da un file: +Oppure carichiamo l'immagine da un file: ```php $image = Image::fromFile('nette.jpg'); ``` -I formati supportati sono JPEG, PNG, GIF, WebP, AVIF e BMP, ma è necessario che anche la vostra versione di PHP li supporti (controllate `phpinfo()`, sezione GD). Le animazioni non sono supportate. -È necessario rilevare il formato dell'immagine durante il caricamento? Il metodo restituisce il formato nel secondo parametro: +Salvataggio dell'immagine +========================= + +L'immagine può essere salvata in un file: ```php -$image = Image::fromFile('nette.jpg', $type); -// $tipo è Immagine::JPEG, Immagine::PNG, Immagine::GIF, Immagine::WEBP, Immagine::AVIF o Immagine::BMP +$image->save('resampled.jpg'); ``` -Solo il rilevamento senza caricamento dell'immagine viene effettuato da `Image::detectTypeFromFile()`. - +Possiamo specificare la qualità della compressione nell'intervallo 0..100 per JPEG (predefinito 85), WEBP (predefinito 80) e AVIF (predefinito 30) e 0..9 per PNG (predefinito 9): -Salvare l'immagine .[#toc-save-the-image] -========================================= +```php +$image->save('resampled.jpg', 80); // JPEG, qualità 80% +``` -L'immagine può essere salvata in un file: +Se il formato non è chiaro dall'estensione del file, può essere specificato con una [costante |#Formati]: ```php -$image->save('resampled.jpg'); +$image->save('resampled.tmp', null, ImageType::JPEG); ``` -È possibile specificare la qualità di compressione nell'intervallo 0..100 per JPEG (valore predefinito 85), WEBP (valore predefinito 80) e AVIF (valore predefinito 30) e 0..9 per PNG (valore predefinito 9): +L'immagine può essere scritta in una variabile invece che su disco: ```php -$image->save('resampled.jpg', 80); // JPEG, qualità 80% +$data = $image->toString(ImageType::JPEG, 80); // JPEG, qualità 80% ``` -Se il formato non è evidente dall'estensione del file, può essere specificato da una delle costanti `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF`, e `Image::BMP`: +o inviata direttamente al browser con l'header HTTP `Content-Type` appropriato: ```php -$image->save('resampled.tmp', null, Image::JPEG); +// invia l'header Content-Type: image/png +$image->send(ImageType::PNG); ``` -L'immagine può essere scritta in una variabile invece che su disco: + +Formati +======= + +I formati supportati sono JPEG, PNG, GIF, WebP, AVIF e BMP, tuttavia devono essere supportati anche dalla vostra versione di PHP, cosa che potete verificare con la funzione [#isTypeSupported()]. Le animazioni non sono supportate. + +Il formato è rappresentato dalle costanti `ImageType::JPEG`, `ImageType::PNG`, `ImageType::GIF`, `ImageType::WEBP`, `ImageType::AVIF` e `ImageType::BMP`. ```php -$data = $image->toString(Image::JPEG, 80); // JPEG, quality 80% +$supported = Image::isTypeSupported(ImageType::JPEG); ``` -o inviare direttamente al browser con l'intestazione HTTP appropriata `Content-Type`: +Avete bisogno di rilevare il formato dell'immagine durante il caricamento? Il metodo lo restituisce nel secondo parametro: ```php -// invia l'intestazione Content-Type: image/png -$image->send(Image::PNG); +$image = Image::fromFile('nette.jpg', $type); ``` +Il rilevamento stesso senza caricare l'immagine viene eseguito da `Image::detectTypeFromFile()`. + -Ridimensionamento dell'immagine .[#toc-image-resize] -==================================================== +Ridimensionamento +================= -Un'operazione comune è il ridimensionamento di un'immagine. Le dimensioni correnti sono restituite dai metodi `getWidth()` e `getHeight()`. +Un'operazione comune è il ridimensionamento delle dimensioni dell'immagine. Le dimensioni attuali vengono restituite dai metodi `getWidth()` e `getHeight()`. -Per il ridimensionamento viene utilizzato il metodo `resize()`. Questo è un esempio di modifica proporzionale delle dimensioni, in modo che non superi i 500×300 pixel (o la larghezza sarà esattamente 500px o l'altezza sarà esattamente 300px, una delle dimensioni è calcolata per mantenere il rapporto di aspetto): +Per il ridimensionamento si utilizza il metodo `resize()`. Esempio di ridimensionamento proporzionale in modo che non superi le dimensioni di 500x300 pixel (o la larghezza sarà esattamente 500 px o l'altezza sarà esattamente 300 px, una delle dimensioni viene calcolata in modo da mantenere il rapporto d'aspetto): ```php $image->resize(500, 300); ``` -È possibile impostare solo una dimensione e la seconda verrà calcolata: +È possibile specificare solo una dimensione e l'altra verrà calcolata: ```php -$image->resize(500, null); // larghezza 500px, altezza auto +$image->resize(500, null); // larghezza 500px, l'altezza viene calcolata -$image->resize(null, 300); // larghezza auto, altezza 300px +$image->resize(null, 300); // la larghezza viene calcolata, altezza 300px ``` -Qualsiasi dimensione può essere specificata in percentuale: +Qualsiasi dimensione può essere specificata anche in percentuale: ```php -$image->resize('75%', 300); // 75% × 300px +$image->resize('75%', 300); // 75 % × 300px ``` -Il comportamento di `resize` può essere influenzato dai seguenti flag. Tutti, tranne `Image::Stretch`, conservano il rapporto d'aspetto. +Il comportamento di `resize` può essere influenzato dai seguenti flag. Tutti tranne `Image::Stretch` mantengono il rapporto d'aspetto. |--------------------------------------------------------------------------------------- -| Flag | Descrizione +| Flag | Descrizione |--------------------------------------------------------------------------------------- -| `Image::OrSmaller` (default) | le dimensioni risultanti saranno minori o uguali a quelle specificate -| `Image::OrBigger` | riempie l'area di destinazione ed eventualmente la estende in una direzione -| `Image::Cover` | riempie l'intera area e taglia ciò che la supera -| `Image::ShrinkOnly` | si limita a ridimensionare (non estende un'immagine piccola) -| `Image::Stretch` | non mantiene il rapporto d'aspetto +| `Image::OrSmaller` (predefinito) | le dimensioni risultanti saranno minori o uguali alle dimensioni richieste +| `Image::OrBigger` | riempie (e eventualmente supera in una dimensione) l'area di destinazione +| `Image::Cover` | riempie l'area di destinazione e ritaglia ciò che supera +| `Image::ShrinkOnly` | solo ridimensionamento (impedisce l'ingrandimento di un'immagine piccola) +| `Image::Stretch` | non mantenere il rapporto d'aspetto -I flag vengono passati come terzo argomento della funzione: +I flag vengono specificati come terzo argomento della funzione: ```php $image->resize(500, 300, Image::OrBigger); @@ -140,30 +151,30 @@ $image->resize(500, 300, Image::ShrinkOnly | Image::Stretch); Le immagini possono essere capovolte verticalmente o orizzontalmente specificando una delle dimensioni (o entrambe) come numero negativo: ```php -$flipped = $image->resize(null, '-100%'); // flip verticale +$flipped = $image->resize(null, '-100%'); // capovolgimento verticale -$flipped = $image->resize('-100%', '-100%'); // ruota di 180° +$flipped = $image->resize('-100%', '-100%'); // rotazione di 180° -$flipped = $image->resize(-125, 500); // ridimensionamento e capovolgimento orizzontale +$flipped = $image->resize(-125, 500); // ridimensiona e capovolge orizzontalmente ``` -Dopo aver ridotto l'immagine, possiamo migliorarla con la nitidezza: +Dopo aver ridimensionato l'immagine, è possibile migliorarne l'aspetto con una leggera applicazione di nitidezza: ```php $image->sharpen(); ``` -Ritaglio .[#toc-cropping] -========================= +Ritaglio +======== -Il metodo `crop()` è utilizzato per le coltivazioni: +Per il ritaglio si utilizza il metodo `crop()`: ```php $image->crop($left, $top, $width, $height); ``` -Come per `resize()`, tutti i valori possono essere specificati in percentuale. Le percentuali per `$left` e `$top` sono calcolate dallo spazio rimanente, in modo simile alla proprietà CSS `background-position`: +Similmente a `resize()`, tutti i valori possono essere specificati in percentuale. Le percentuali per `$left` e `$top` vengono calcolate dallo spazio rimanente, in modo simile alla proprietà CSS `background-position`: ```php $image->crop('100%', '50%', '80%', '80%'); @@ -172,329 +183,359 @@ $image->crop('100%', '50%', '80%', '80%'); [* crop.svg *] -L'immagine può anche essere ritagliata automaticamente, ad esempio ritagliando i bordi neri: +L'immagine può anche essere ritagliata automaticamente, ad esempio per ritagliare i bordi neri: ```php $image->cropAuto(IMG_CROP_BLACK); ``` -Il metodo `cropAuto()` è un oggetto che incapsula la funzione `imagecropauto()`; per ulteriori informazioni, consultare la [relativa documentazione |https://www.php.net/manual/en/function.imagecropauto]. +Il metodo `cropAuto()` è un sostituto orientato agli oggetti della funzione `imagecropauto()`, nella [sua documentazione |https://www.php.net/manual/en/function.imagecropauto] troverete ulteriori informazioni. -Disegno e modifica .[#toc-drawing-and-editing] -============================================== +Colori .{data-version:4.0.2} +============================ -Si può disegnare, si può scrivere, si possono usare tutte le funzioni PHP per lavorare con le immagini, come [imagefilledellipse() |https://www.php.net/manual/en/function.imagefilledellipse.php], ma utilizzando lo stile a oggetti: +Il metodo `ImageColor::rgb()` consente di definire un colore utilizzando i valori di rosso, verde e blu (RGB). Opzionalmente, è possibile specificare anche un valore di trasparenza nell'intervallo da 0 (completamente trasparente) a 1 (completamente opaco), proprio come in CSS. ```php -$image->filledEllipse($cx, $cy, $width, $height, Image::rgb(255, 0, 0, 63)); +$color = ImageColor::rgb(255, 0, 0); // Rosso +$transparentBlue = ImageColor::rgb(0, 0, 255, 0.5); // Blu semitrasparente ``` -Vedere [Panoramica dei metodi |#Overview of Methods]. +Il metodo `ImageColor::hex()` consente di definire un colore utilizzando il formato esadecimale, simile a CSS. Supporta i formati `#rgb`, `#rrggbb`, `#rgba` e `#rrggbbaa`: +```php +$color = ImageColor::hex("#F00"); // Rosso +$transparentGreen = ImageColor::hex("#00FF0080"); // Verde semitrasparente +``` -Unire più immagini .[#toc-merge-multiple-images] -================================================ +I colori possono essere utilizzati in altri metodi, come `ellipse()`, `fill()`, ecc. -È possibile inserire facilmente un'altra immagine nell'immagine: + +Disegno e modifiche +=================== + +Puoi disegnare, puoi scrivere, ma non strappare le foglie. Sono a vostra disposizione tutte le funzioni PHP per lavorare con le immagini, vedi [#Panoramica dei metodi], ma in un involucro orientato agli oggetti: + +```php +$image->filledEllipse($centerX, $centerY, $width, $height, ImageColor::rgb(255, 0, 0)); +``` + +Poiché le funzioni PHP per disegnare rettangoli non sono pratiche a causa della specificazione delle coordinate, la classe `Image` offre le loro sostituzioni sotto forma delle funzioni [#rectangleWH()] e [#filledRectangleWH()]. + + +Unione di più immagini +====================== + +È facile inserire un'altra immagine in un'immagine: ```php $logo = Image::fromFile('logo.png'); -$blank = Image::fromBlank(320, 240, Image::rgb(52, 132, 210)); +$blank = Image::fromBlank(320, 240, ImageColor::rgb(52, 132, 210)); -// le coordinate possono essere impostate anche in percentuale -$blank->place($logo, '80%', '80%'); // vicino all'angolo destro in basso +// le coordinate possono essere specificate nuovamente in percentuale +$blank->place($logo, '80%', '80%'); // inseriamo vicino all'angolo inferiore destro ``` -Quando si incolla, il canale alfa viene rispettato; inoltre, possiamo influenzare la trasparenza dell'immagine inserita (creeremo una cosiddetta filigrana): +Durante l'inserimento, il canale alfa viene rispettato, inoltre possiamo influenzare la trasparenza dell'immagine inserita (creiamo la cosiddetta filigrana): ```php -$blank->place($image, '80%', '80%', 25); // la trasparenza è del 25%. +$blank->place($image, '80%', '80%', 25); // la trasparenza è del 25 % ``` -Questa API è davvero un piacere da usare, non è vero? +Un'API del genere è davvero un piacere da usare! -Panoramica dei metodi .[#toc-overview-of-methods] -================================================= +Panoramica dei metodi +===================== -static fromBlank(int $width, int $height, array $color=null): Image .[method] ------------------------------------------------------------------------------ -Crea una nuova immagine a colori reali delle dimensioni indicate. Il colore predefinito è il nero. +static fromBlank(int $width, int $height, ?ImageColor $color=null): Image .[method] +----------------------------------------------------------------------------------- +Crea una nuova immagine true color delle dimensioni specificate. Il colore predefinito è nero. static fromFile(string $file, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------- -Legge un'immagine da un file e ne restituisce il tipo in `$detectedFormat`. I tipi supportati sono `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` e `Image::BMP`. +Carica un'immagine da un file e restituisce il suo [tipo |#Formati] in `$detectedFormat`. static fromString(string $s, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------ -Legge un'immagine da una stringa e ne restituisce il tipo in `$detectedFormat`. I tipi supportati sono `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` e `Image::BMP`. +Carica un'immagine da una stringa e restituisce il suo [tipo |#Formati] in `$detectedFormat`. -static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method] ---------------------------------------------------------------------------------- -Crea un colore che può essere utilizzato in altri metodi, come `ellipse()`, `fill()`, e così via. +static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method][deprecated] +--------------------------------------------------------------------------------------------- +Questa funzione è stata sostituita dalla classe `ImageColor`, vedi [#colori]. static typeToExtension(int $type): string .[method] --------------------------------------------------- -Restituisce l'estensione del file per la costante `Image::XXX` data. +Restituisce l'estensione del file per il [tipo |#Formati] specificato. static typeToMimeType(int $type): string .[method] -------------------------------------------------- -Restituisce il tipo di mime per la costante `Image::XXX`. +Restituisce il tipo mime per il [tipo |#Formati] specificato. static extensionToType(string $extension): int .[method] -------------------------------------------------------- -Restituisce il tipo di immagine come costante `Image::XXX` in base all'estensione del file. +Restituisce il [tipo |#Formati] dell'immagine in base all'estensione del file. static detectTypeFromFile(string $file, int &$width=null, int &$height=null): ?int .[method] -------------------------------------------------------------------------------------------- -Restituisce il tipo di file immagine come costante `Image::XXX` e nei parametri `$width` e `$height` anche le sue dimensioni. +Restituisce il [tipo |#Formati] dell'immagine e nei parametri `$width` e `$height` anche le sue dimensioni. static detectTypeFromString(string $s, int &$width=null, int &$height=null): ?int .[method] ------------------------------------------------------------------------------------------- -Restituisce il tipo di immagine dalla stringa come costante `Image::XXX` e nei parametri `$width` e `$height` anche le sue dimensioni. +Restituisce il [tipo |#Formati] dell'immagine da una stringa e nei parametri `$width` e `$height` anche le sue dimensioni. -affine(array $affine, array $clip=null): Image .[method] --------------------------------------------------------- -Restituisce un'immagine contenente l'immagine src trasformata affine, utilizzando un'area di ritaglio opzionale. ([altro |https://www.php.net/manual/en/function.imageaffine]). +static isTypeSupported(int $type): bool .[method] +------------------------------------------------- +Verifica se il [tipo |#Formati] di immagine specificato è supportato. + + +static getSupportedTypes(): array .[method]{data-version:4.0.4} +--------------------------------------------------------------- +Restituisce un array dei [tipi |#Formati] di immagine supportati. + + +static calculateTextBox(string $text, string $fontFile, float $size, float $angle=0, array $options=[]): array .[method] +------------------------------------------------------------------------------------------------------------------------ +Calcola le dimensioni del rettangolo che circonda il testo in un determinato carattere e dimensione. Restituisce un array associativo contenente le chiavi `left`, `top`, `width`, `height`. Il margine sinistro può essere anche negativo se il testo inizia con un kerning sinistro. + + +affine(array $affine, ?array $clip=null): Image .[method] +--------------------------------------------------------- +Restituisce un'immagine contenente l'immagine src trasformata affinemente utilizzando un'area di ritaglio opzionale. ([maggiori informazioni |https://www.php.net/manual/en/function.imageaffine]). affineMatrixConcat(array $m1, array $m2): array .[method] --------------------------------------------------------- -Restituisce la concatenazione di due matrici di trasformazione affine, utile se si vogliono applicare più trasformazioni alla stessa immagine in una sola volta. ([altro |https://www.php.net/manual/en/function.imageaffinematrixconcat]) +Restituisce la concatenazione di due matrici di trasformazione affine, utile se più trasformazioni devono essere applicate alla stessa immagine contemporaneamente. ([maggiori informazioni |https://www.php.net/manual/en/function.imageaffinematrixconcat]) -affineMatrixGet(int $type, mixed $options=null): array .[method] ----------------------------------------------------------------- -Restituisce una matrice di trasformazione affine. ([altro |https://www.php.net/manual/en/function.imageaffinematrixget]) +affineMatrixGet(int $type, ?mixed $options=null): array .[method] +----------------------------------------------------------------- +Restituisce una matrice di trasformazione affine. ([maggiori informazioni |https://www.php.net/manual/en/function.imageaffinematrixget]) alphaBlending(bool $on): void .[method] --------------------------------------- -Consente di disegnare in due modi diversi sulle immagini truecolor. In modalità di fusione, la componente del canale alfa del colore fornito a tutte le funzioni di disegno, come ad esempio `setPixel()`, determina la quantità di colore sottostante da lasciar trasparire. Di conseguenza, fonde automaticamente il colore esistente in quel punto con il colore del disegno e memorizza il risultato nell'immagine. Il pixel risultante è opaco. In modalità non sfumata, il colore del disegno viene copiato letteralmente con le informazioni del suo canale alfa, sostituendo il pixel di destinazione. La modalità di fusione non è disponibile quando si disegna sulle immagini della tavolozza. ([altro |https://www.php.net/manual/en/function.imagealphablending]) +Consente due diverse modalità di disegno nelle immagini truecolor. In modalità di fusione, il componente del canale alfa del colore utilizzato in tutte le funzioni di disegno, come `setPixel()`, determina in che misura il colore sottostante dovrebbe trasparire. Di conseguenza, il colore esistente in quel punto viene automaticamente miscelato con il colore disegnato e il risultato viene salvato nell'immagine. Il pixel risultante è opaco. In modalità senza fusione, il colore disegnato viene copiato letteralmente con le informazioni del canale alfa e sostituisce il pixel di destinazione. La modalità di fusione non è disponibile quando si disegna su immagini a tavolozza. ([maggiori informazioni |https://www.php.net/manual/en/function.imagealphablending]) antialias(bool $on): void .[method] ----------------------------------- -Attivare i metodi di disegno rapido antialias per linee e poligoni cablati. Non supporta i componenti alfa. Funziona con un'operazione di miscelazione diretta. Funziona solo con immagini in vero colore. +Attiva il disegno di linee e poligoni con antialiasing. Non supporta i canali alfa. Funziona solo con immagini truecolor. -L'uso di primitive con antialias e colore di sfondo trasparente può portare a risultati inaspettati. Il metodo blend utilizza il colore di sfondo come qualsiasi altro colore. La mancanza del supporto per i componenti alfa non consente un metodo di antialiasing basato su alfa. ([altro |https://www.php.net/manual/en/function.imageantialias]) +L'uso di primitive con antialiasing con un colore di sfondo trasparente può portare a risultati imprevisti. Il metodo di fusione utilizza il colore di sfondo come tutti gli altri colori. ([maggiori informazioni |https://www.php.net/manual/en/function.imageantialias]) -arc(int $x, int $y, int $w, int $h, int $start, int $end, int $color): void .[method] -------------------------------------------------------------------------------------- -Disegna un arco di cerchio centrato sulle coordinate date. ([altro |https://www.php.net/manual/en/function.imagearc]) - - -char(int $font, int $x, int $y, string $char, int $color): void .[method] -------------------------------------------------------------------------- -Disegna il primo carattere di `$char` nell'immagine con la parte superiore sinistra a `$x`,`$y` (in alto a sinistra è 0, 0) con il colore `$color`. ([altro |https://www.php.net/manual/en/function.imagechar]) - - -charUp(int $font, int $x, int $y, string $char, int $color): void .[method] ---------------------------------------------------------------------------- -Disegna il carattere `$char` verticalmente alla coordinata specificata sull'immagine data. ([altro |https://www.php.net/manual/en/function.imagecharup]) +arc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color): void .[method] +--------------------------------------------------------------------------------------------------------------------------- +Disegna un arco di cerchio centrato nelle coordinate specificate. ([maggiori informazioni |https://www.php.net/manual/en/function.imagearc]) colorAllocate(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------- -Restituisce un identificatore di colore che rappresenta il colore composto dalle componenti RGB indicate. Deve essere richiamato per creare ogni colore da utilizzare nell'immagine. ([altro |https://www.php.net/manual/en/function.imagecolorallocate]) +Restituisce un identificatore di colore che rappresenta il colore composto dai componenti RGB specificati. Deve essere chiamata per creare ogni colore da utilizzare nell'immagine. ([maggiori informazioni |https://www.php.net/manual/en/function.imagecolorallocate]) colorAllocateAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ------------------------------------------------------------------------------ -Si comporta in modo identico a `colorAllocate()` con l'aggiunta del parametro di trasparenza `$alpha`. ([altro |https://www.php.net/manual/en/function.imagecolorallocatealpha]) +Si comporta come `colorAllocate()` con l'aggiunta del parametro di trasparenza `$alpha`. ([maggiori informazioni |https://www.php.net/manual/en/function.imagecolorallocatealpha]) colorAt(int $x, int $y): int .[method] -------------------------------------- -Restituisce l'indice del colore del pixel nella posizione specificata dell'immagine. Se l'immagine è a colori, questa funzione restituisce il valore RGB del pixel come numero intero. Utilizzare il bitshifting e il mascheramento per accedere ai valori distinti dei componenti rosso, verde e blu: ([continua |https://www.php.net/manual/en/function.imagecolorat]) +Restituisce l'indice del colore del pixel nella posizione specificata nell'immagine. Se l'immagine è truecolor, questa funzione restituisce il valore RGB di quel pixel come intero. Utilizzare lo spostamento di bit e il mascheramento di bit per accedere ai valori separati dei componenti rosso, verde e blu: ([maggiori informazioni |https://www.php.net/manual/en/function.imagecolorat]) colorClosest(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------ -Restituisce l'indice del colore nella tavolozza dell'immagine più "vicino" al valore RGB specificato. La "distanza" tra il colore desiderato e ciascun colore della tavolozza viene calcolata come se i valori RGB rappresentassero dei punti nello spazio tridimensionale. ([altro |https://www.php.net/manual/en/function.imagecolorclosest]) +Restituisce l'indice del colore nella tavolozza dell'immagine che è "più vicino" al valore RGB specificato. La "distanza" tra il colore desiderato e ogni colore nella tavolozza viene calcolata come se i valori RGB rappresentassero punti in uno spazio tridimensionale. ([maggiori informazioni |https://www.php.net/manual/en/function.imagecolorclosest]) colorClosestAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ----------------------------------------------------------------------------- -Restituisce l'indice del colore nella tavolozza dell'immagine più "vicino" al valore RGB e al livello `$alpha` specificati. ([altro |https://www.php.net/manual/en/function.imagecolorclosestalpha]) +Restituisce l'indice del colore nella tavolozza dell'immagine che è "più vicino" al valore RGB specificato e al livello `$alpha`. ([maggiori informazioni |https://www.php.net/manual/en/function.imagecolorclosestalpha]) colorClosestHWB(int $red, int $green, int $blue): int .[method] --------------------------------------------------------------- -Ottiene l'indice del colore che ha la tinta, il bianco e il nero più vicini al colore dato. ([altro |https://www.php.net/manual/en/function.imagecolorclosesthwb]) +Ottiene l'indice del colore che ha la tonalità, il bianco e il nero più vicini al colore specificato. ([maggiori informazioni |https://www.php.net/manual/en/function.imagecolorclosesthwb]) colorDeallocate(int $color): void .[method] ------------------------------------------- -Disalloca un colore precedentemente assegnato con `colorAllocate()` o `colorAllocateAlpha()`. ([altro |https://www.php.net/manual/en/function.imagecolordeallocate]) +Dealloca un colore precedentemente allocato con `colorAllocate()` o `colorAllocateAlpha()`. ([maggiori informazioni |https://www.php.net/manual/en/function.imagecolordeallocate]) colorExact(int $red, int $green, int $blue): int .[method] ---------------------------------------------------------- -Restituisce l'indice del colore specificato nella tavolozza dell'immagine. ([altro |https://www.php.net/manual/en/function.imagecolorexact]) +Restituisce l'indice del colore specificato nella tavolozza dell'immagine. ([maggiori informazioni |https://www.php.net/manual/en/function.imagecolorexact]) colorExactAlpha(int $red, int $green, int $blue, int $alpha): int .[method] --------------------------------------------------------------------------- -Restituisce l'indice del colore+alfa specificato nella tavolozza dell'immagine. ([altro |https://www.php.net/manual/en/function.imagecolorexactalpha]) +Restituisce l'indice del colore specificato + alfa nella tavolozza dell'immagine. ([maggiori informazioni |https://www.php.net/manual/en/function.imagecolorexactalpha]) colorMatch(Image $image2): void .[method] ----------------------------------------- -Rende i colori della versione palette di un'immagine più simili alla versione a colori reali. ([altro |https://www.php.net/manual/en/function.imagecolormatch]) +Adatta i colori della tavolozza alla seconda immagine. ([maggiori informazioni |https://www.php.net/manual/en/function.imagecolormatch]) colorResolve(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------ -Restituisce un indice di colore per un colore richiesto, il colore esatto o l'alternativa più vicina possibile. ([altro |https://www.php.net/manual/en/function.imagecolorresolve]) +Restituisce l'indice del colore per il colore richiesto, sia il colore esatto che l'alternativa più vicina possibile. ([maggiori informazioni |https://www.php.net/manual/en/function.imagecolorresolve]) colorResolveAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ----------------------------------------------------------------------------- -Restituisce un indice di colore per un colore richiesto, il colore esatto o l'alternativa più vicina possibile. ([altro |https://www.php.net/manual/en/function.imagecolorresolvealpha]) +Restituisce l'indice del colore per il colore richiesto, sia il colore esatto che l'alternativa più vicina possibile. ([maggiori informazioni |https://www.php.net/manual/en/function.imagecolorresolvealpha]) colorSet(int $index, int $red, int $green, int $blue): void .[method] --------------------------------------------------------------------- -Imposta l'indice specificato nella tavolozza al colore specificato. ([altro |https://www.php.net/manual/en/function.imagecolorset]) +Imposta l'indice specificato nella tavolozza sul colore specificato. ([maggiori informazioni |https://www.php.net/manual/en/function.imagecolorset]) colorsForIndex(int $index): array .[method] ------------------------------------------- -Ottiene il colore per un indice specificato. ([altro |https://www.php.net/manual/en/function.imagecolorsforindex]) +Ottiene il colore dell'indice specificato. ([maggiori informazioni |https://www.php.net/manual/en/function.imagecolorsforindex]) colorsTotal(): int .[method] ---------------------------- -Restituisce il numero di colori in una tavolozza di immagini ([more |https://www.php.net/manual/en/function.imagecolorstotal]). +Restituisce il numero di colori nella tavolozza dell'immagine. ([maggiori informazioni |https://www.php.net/manual/en/function.imagecolorstotal]) -colorTransparent(int $color=null): int .[method] ------------------------------------------------- -Ottiene o imposta il colore trasparente dell'immagine. ([altro |https://www.php.net/manual/en/function.imagecolortransparent]) +colorTransparent(?int $color=null): int .[method] +------------------------------------------------- +Ottiene o imposta il colore trasparente nell'immagine. ([maggiori informazioni |https://www.php.net/manual/en/function.imagecolortransparent]) convolution(array $matrix, float $div, float $offset): void .[method] --------------------------------------------------------------------- -Applica una matrice di convoluzione all'immagine, utilizzando il coefficiente e l'offset indicati. ([altro |https://www.php.net/manual/en/function.imageconvolution]) +Applica una matrice di convoluzione all'immagine, utilizzando il coefficiente e l'offset specificati. ([maggiori informazioni |https://www.php.net/manual/en/function.imageconvolution]) .[note] -Richiede l'estensione *Bundled GD*, quindi non è sicuro che funzioni ovunque. +Richiede la presenza dell'estensione *Bundled GD*, quindi potrebbe non funzionare ovunque. copy(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH): void .[method] -------------------------------------------------------------------------------------------------- -Copia una parte di `$src` sull'immagine a partire dalle coordinate `$srcX`, `$srcY` con una larghezza di `$srcW` e un'altezza di `$srcH`. La porzione definita verrà copiata sulle coordinate `$dstX` e `$dstY`. ([altro |https://www.php.net/manual/en/function.imagecopy]) +Copia una parte di `$src` sull'immagine iniziando dalle coordinate `$srcX`, `$srcY` con larghezza `$srcW` e altezza `$srcH`. La parte definita verrà copiata nelle coordinate `$dstX` e `$dstY`. ([maggiori informazioni |https://www.php.net/manual/en/function.imagecopy]) copyMerge(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] --------------------------------------------------------------------------------------------------------------------- -Copia una parte di `$src` sull'immagine a partire dalle coordinate `$srcX`, `$srcY` con una larghezza di `$srcW` e un'altezza di `$srcH`. La porzione definita verrà copiata sulle coordinate `$dstX` e `$dstY`. ([altro |https://www.php.net/manual/en/function.imagecopymerge]) +Copia una parte di `$src` sull'immagine iniziando dalle coordinate `$srcX`, `$srcY` con larghezza `$srcW` e altezza `$srcH`. La parte definita verrà copiata nelle coordinate `$dstX` e `$dstY`. ([maggiori informazioni |https://www.php.net/manual/en/function.imagecopymerge]) copyMergeGray(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] ------------------------------------------------------------------------------------------------------------------------- -Copia una parte di `$src` sull'immagine a partire dalle coordinate `$srcX`, `$srcY` con una larghezza di `$srcW` e un'altezza di `$srcH`. La porzione definita verrà copiata sulle coordinate `$dstX` e `$dstY`. +Copia una parte di `$src` sull'immagine iniziando dalle coordinate `$srcX`, `$srcY` con larghezza `$srcW` e altezza `$srcH`. La parte definita verrà copiata nelle coordinate `$dstX` e `$dstY`. -Questa funzione è identica a `copyMerge()`, tranne che per il fatto che durante la fusione preserva la tonalità dell'origine convertendo i pixel di destinazione in scala di grigi prima dell'operazione di copia. ([altro |https://www.php.net/manual/en/function.imagecopymergegray]) +Questa funzione è identica a `copyMerge()` con l'eccezione che durante la fusione conserva la tonalità della sorgente convertendo i pixel di destinazione in scala di grigi prima dell'operazione di copia. ([maggiori informazioni |https://www.php.net/manual/en/function.imagecopymergegray]) copyResampled(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] --------------------------------------------------------------------------------------------------------------------------------- -Copia una porzione rettangolare di un'immagine in un'altra immagine, interpolando in modo fluido i valori dei pixel in modo che, in particolare, la riduzione delle dimensioni di un'immagine conservi una grande chiarezza. +Copia una porzione rettangolare di un'immagine su un'altra immagine, interpolando uniformemente i valori dei pixel, in modo che in particolare la riduzione delle dimensioni dell'immagine mantenga comunque una grande chiarezza. -In altre parole, `copyResampled()` prenderà un'area rettangolare da `$src` di larghezza `$srcW` e altezza `$srcH` in posizione (`$srcX`,`$srcY`) e la collocherà in un'area rettangolare dell'immagine di larghezza `$dstW` e altezza `$dstH` in posizione (`$dstX`,`$dstY`). +In altre parole, `copyResampled()` prende un'area rettangolare da `$src` di larghezza `$srcW` e altezza `$srcH` nella posizione (`$srcX`, `$srcY`) e la posiziona in un'area rettangolare dell'immagine di larghezza `$dstW` e altezza `$dstH` nella posizione (`$dstX`, `$dstY`). -Se le coordinate di origine e di destinazione, la larghezza e l'altezza differiscono, verrà eseguito un allungamento o un restringimento appropriato del frammento di immagine. Le coordinate si riferiscono all'angolo superiore sinistro. Questa funzione può essere utilizzata per copiare regioni all'interno della stessa immagine, ma se le regioni si sovrappongono i risultati saranno imprevedibili. ([altro |https://www.php.net/manual/en/function.imagecopyresampled]) +Se le coordinate di origine e destinazione, la larghezza e l'altezza differiscono, viene eseguito un corrispondente allungamento o restringimento del frammento dell'immagine. Le coordinate si riferiscono all'angolo superiore sinistro. Questa funzione può essere utilizzata per copiare aree all'interno della stessa immagine, ma se le aree si sovrappongono, i risultati non saranno prevedibili. ([maggiori informazioni |https://www.php.net/manual/en/function.imagecopyresampled]) copyResized(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] ------------------------------------------------------------------------------------------------------------------------------- -Copia una porzione rettangolare di un'immagine in un'altra immagine. In altre parole, `copyResized()` prenderà un'area rettangolare da `$src` di larghezza `$srcW` e altezza `$srcH` in posizione (`$srcX`,`$srcY`) e la collocherà in un'area rettangolare dell'immagine di larghezza `$dstW` e altezza `$dstH` in posizione (`$dstX`,`$dstY`). +Copia una porzione rettangolare di un'immagine su un'altra immagine. In altre parole, `copyResized()` ottiene un'area rettangolare da `$src` di larghezza `$srcW` e altezza `$srcH` nella posizione (`$srcX`, `$srcY`) e la posiziona in un'area rettangolare dell'immagine di larghezza `$dstW` e altezza `$dstH` nella posizione (`$dstX`, `$dstY`). -Se le coordinate di origine e di destinazione, la larghezza e l'altezza differiscono, verrà eseguito un allungamento o un restringimento appropriato del frammento di immagine. Le coordinate si riferiscono all'angolo superiore sinistro. Questa funzione può essere utilizzata per copiare regioni all'interno della stessa immagine, ma se le regioni si sovrappongono i risultati saranno imprevedibili. ([altro |https://www.php.net/manual/en/function.imagecopyresized]) +Se le coordinate di origine e destinazione, la larghezza e l'altezza differiscono, viene eseguito un corrispondente allungamento o restringimento del frammento dell'immagine. Le coordinate si riferiscono all'angolo superiore sinistro. Questa funzione può essere utilizzata per copiare aree all'interno della stessa immagine, ma se le aree si sovrappongono, i risultati non saranno prevedibili. ([maggiori informazioni |https://www.php.net/manual/en/function.imagecopyresized]) crop(int|string $left, int|string $top, int|string $width, int|string $height): Image .[method] ----------------------------------------------------------------------------------------------- -Ritaglia un'immagine nell'area rettangolare indicata. Le dimensioni possono essere passate come numeri interi in pixel o come stringhe in percentuale (ad esempio `'50%'`). +Ritaglia l'immagine nell'area rettangolare specificata. Le dimensioni possono essere specificate come interi in pixel o stringhe in percentuale (ad esempio `'50%'`). -cropAuto(int $mode=-1, float $threshold=.5, int $color=-1): Image .[method] ---------------------------------------------------------------------------- -Ritaglia automaticamente un'immagine in base all'indirizzo `$mode`. ([altro |https://www.php.net/manual/en/function.imagecropauto]) +cropAuto(int $mode=-1, float $threshold=.5, ?ImageColor $color=null): Image .[method] +------------------------------------------------------------------------------------- +Ritaglia automaticamente l'immagine secondo la `$mode` specificata. ([maggiori informazioni |https://www.php.net/manual/en/function.imagecropauto]) -ellipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------- -Disegna un'ellisse centrata sulle coordinate specificate. ([altro |https://www.php.net/manual/en/function.imageellipse]) +ellipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------- +Disegna un'ellisse centrata nelle coordinate specificate. ([maggiori informazioni |https://www.php.net/manual/en/function.imageellipse]) -fill(int $x, int $y, int $color): void .[method] ------------------------------------------------- -Esegue un riempimento a tappeto a partire dalla coordinata data (in alto a sinistra è 0, 0) con il dato `$color` nell'immagine. ([altro |https://www.php.net/manual/en/function.imagefill]) +fill(int $x, int $y, ImageColor $color): void .[method] +------------------------------------------------------- +Riempie un'area iniziando dalle coordinate specificate (in alto a sinistra è 0, 0) con il `$color` specificato. ([maggiori informazioni |https://www.php.net/manual/en/function.imagefill]) -filledArc(int $cx, int $cy, int $w, int $h, int $s, int $e, int $color, int $style): void .[method] ---------------------------------------------------------------------------------------------------- -Disegna un arco parziale centrato nella coordinata specificata dell'immagine. ([altro |https://www.php.net/manual/en/function.imagefilledarc]) +filledArc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color, int $style): void .[method] +--------------------------------------------------------------------------------------------------------------------------------------------- +Disegna un arco parziale centrato nelle coordinate specificate. ([maggiori informazioni |https://www.php.net/manual/en/function.imagefilledarc]) -filledEllipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------------- -Disegna un'ellisse centrata sulla coordinata specificata nell'immagine. ([altro |https://www.php.net/manual/en/function.imagefilledellipse]) +filledEllipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------------- +Disegna un'ellisse centrata nelle coordinate specificate. ([maggiori informazioni |https://www.php.net/manual/en/function.imagefilledellipse]) -filledPolygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------------- -Crea un poligono riempito nell'immagine $. ([altro |https://www.php.net/manual/en/function.imagefilledpolygon]) +filledPolygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------------- +Crea un poligono riempito nell'immagine. ([maggiori informazioni |https://www.php.net/manual/en/function.imagefilledpolygon]) -filledRectangle(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] -------------------------------------------------------------------------------- -Crea un rettangolo riempito con `$color` nell'immagine a partire dal punto 1 e fino al punto 2. 0, 0 è l'angolo superiore sinistro dell'immagine. ([altro |https://www.php.net/manual/en/function.imagefilledrectangle]) +filledRectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------- +Crea un rettangolo riempito con `$color` nell'immagine iniziando dal punto `$x1` & `$y1` e terminando al punto `$x2` & `$y2`. Il punto 0, 0 è l'angolo superiore sinistro dell'immagine. ([maggiori informazioni |https://www.php.net/manual/en/function.imagefilledrectangle]) -fillToBorder(int $x, int $y, int $border, int $color): void .[method] ---------------------------------------------------------------------- -Esegue un riempimento a tappeto il cui colore del bordo è definito da `$border`. Il punto di partenza per il riempimento è `$x`, `$y` (in alto a sinistra 0, 0) e la regione viene riempita con il colore `$color`. ([altro |https://www.php.net/manual/en/function.imagefilltoborder]) +filledRectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------------- +Crea un rettangolo riempito con `$color` nell'immagine iniziando dal punto `$left` & `$top` con larghezza `$width` e altezza `$height`. Il punto 0, 0 è l'angolo superiore sinistro dell'immagine. + + +fillToBorder(int $x, int $y, int $border, ImageColor $color): void .[method] +---------------------------------------------------------------------------- +Esegue un riempimento il cui colore del bordo è definito da `$border`. Il punto di partenza del riempimento è `$x`, `$y` (in alto a sinistra è 0, 0) e l'area viene riempita con il colore `$color`. ([maggiori informazioni |https://www.php.net/manual/en/function.imagefilltoborder]) filter(int $filtertype, int ...$args): void .[method] ----------------------------------------------------- -Applica il filtro indicato `$filtertype` all'immagine. ([altro |https://www.php.net/manual/en/function.imagefilter]) +Applica il filtro `$filtertype` specificato all'immagine. ([maggiori informazioni |https://www.php.net/manual/en/function.imagefilter]) flip(int $mode): void .[method] ------------------------------- -Capovolge l'immagine utilizzando l'indirizzo `$mode`. ([altro |https://www.php.net/manual/en/function.imageflip]) +Capovolge l'immagine utilizzando la `$mode` specificata. ([maggiori informazioni |https://www.php.net/manual/en/function.imageflip]) -ftText(int $size, int $angle, int $x, int $y, int $col, string $fontFile, string $text, array $extrainfo=null): array .[method] -------------------------------------------------------------------------------------------------------------------------------- -Scrivere il testo sull'immagine usando i font di FreeType 2. ([altro |https://www.php.net/manual/en/function.imagefttext]) +ftText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +---------------------------------------------------------------------------------------------------------------------------------------- +Scrive il testo nell'immagine. ([maggiori informazioni |https://www.php.net/manual/en/function.imagefttext]) gammaCorrect(float $inputgamma, float $outputgamma): void .[method] ------------------------------------------------------------------- -Applica la correzione gamma all'immagine data una gamma di ingresso e una di uscita. ([altro |https://www.php.net/manual/en/function.imagegammacorrect]) +Applica la correzione gamma all'immagine rispetto alla gamma di input e output. ([maggiori informazioni |https://www.php.net/manual/en/function.imagegammacorrect]) getClip(): array .[method] -------------------------- -Recupera il rettangolo di ritaglio corrente, cioè l'area oltre la quale non verranno disegnati pixel. ([altro |https://www.php.net/manual/en/function.imagegetclip]) +Restituisce il ritaglio corrente, ovvero l'area oltre la quale non verranno disegnati pixel. ([maggiori informazioni |https://www.php.net/manual/en/function.imagegetclip]) getHeight(): int .[method] @@ -512,169 +553,164 @@ getWidth(): int .[method] Restituisce la larghezza dell'immagine. -interlace(int $interlace=null): int .[method] ---------------------------------------------- -Attiva o disattiva il bit di interlacciamento. Se il bit di interlacciamento è impostato e l'immagine viene utilizzata come immagine JPEG, l'immagine viene creata come JPEG progressivo. ([altro |https://www.php.net/manual/en/function.imageinterlace]) +interlace(?int $interlace=null): int .[method] +---------------------------------------------- +Attiva o disattiva la modalità interlacciata. Se la modalità interlacciata è impostata e l'immagine viene salvata come JPEG, verrà salvata come JPEG progressivo. ([maggiori informazioni |https://www.php.net/manual/en/function.imageinterlace]) isTrueColor(): bool .[method] ----------------------------- -Trova se l'immagine è un colore reale. ([altro |https://www.php.net/manual/en/function.imageistruecolor]) +Verifica se l'immagine è truecolor. ([maggiori informazioni |https://www.php.net/manual/en/function.imageistruecolor]) layerEffect(int $effect): void .[method] ---------------------------------------- -Impostare il flag di fusione alfa per utilizzare gli effetti di stratificazione. ([altro |https://www.php.net/manual/en/function.imagelayereffect]) +Imposta il flag di fusione alfa per utilizzare gli effetti di layering. ([maggiori informazioni |https://www.php.net/manual/en/function.imagelayereffect]) -line(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] --------------------------------------------------------------------- -Disegna una linea tra i due punti dati. ([altro |https://www.php.net/manual/en/function.imageline]) +line(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +--------------------------------------------------------------------------- +Disegna una linea tra i due punti specificati. ([maggiori informazioni |https://www.php.net/manual/en/function.imageline]) -openPolygon(array $points, int $numPoints, int $color): void .[method] ----------------------------------------------------------------------- -Disegna un poligono aperto sull'immagine. Contrariamente a `polygon()`, non viene tracciata una linea tra l'ultimo e il primo punto. ([altro |https://www.php.net/manual/en/function.imageopenpolygon]) +openPolygon(array $points, ImageColor $color): void .[method] +------------------------------------------------------------- +Disegna un poligono aperto sull'immagine. A differenza di `polygon()`, non viene disegnata alcuna linea tra l'ultimo e il primo punto. ([maggiori informazioni |https://www.php.net/manual/en/function.imageopenpolygon]) paletteCopy(Image $source): void .[method] ------------------------------------------ -Copia la tavolozza da `$source` all'immagine. ([altro |https://www.php.net/manual/en/function.imagepalettecopy]) +Copia la tavolozza da `$source` all'immagine. ([maggiori informazioni |https://www.php.net/manual/en/function.imagepalettecopy]) paletteToTrueColor(): void .[method] ------------------------------------ -Converte un'immagine basata su una tavolozza, creata da funzioni come `create()`, in un'immagine a colori reali, come `createtruecolor()`. ([altro |https://www.php.net/manual/en/function.imagepalettetotruecolor]) +Converte un'immagine basata su tavolozza in un'immagine a colori pieni. ([maggiori informazioni |https://www.php.net/manual/en/function.imagepalettetotruecolor]) place(Image $image, int|string $left=0, int|string $top=0, int $opacity=100): Image .[method] --------------------------------------------------------------------------------------------- -Copia `$image` nell'immagine alle coordinate `$left` e `$top`. Le coordinate possono essere passate come numeri interi in pixel o come stringhe in percentuale (ad esempio `'50%'`). +Copia `$image` nell'immagine alle coordinate `$left` e `$top`. Le coordinate possono essere specificate come interi in pixel o stringhe in percentuale (ad esempio `'50%'`). + +polygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------- +Crea un poligono nell'immagine. ([maggiori informazioni |https://www.php.net/manual/en/function.imagepolygon]) -polygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------- -Crea un poligono nell'immagine. ([altro |https://www.php.net/manual/en/function.imagepolygon]) +rectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------- +Crea un rettangolo alle coordinate specificate. ([maggiori informazioni |https://www.php.net/manual/en/function.imagerectangle]) -rectangle(int $x1, int $y1, int $x2, int $y2, int $col): void .[method] ------------------------------------------------------------------------ -Crea un rettangolo a partire dalle coordinate specificate. ([altro |https://www.php.net/manual/en/function.imagerectangle]) + +rectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------- +Crea un rettangolo alle coordinate specificate. resize(int|string $width, int|string $height, int $flags=Image::OrSmaller): Image .[method] ------------------------------------------------------------------------------------------- -Scala un'immagine, vedi [maggiori informazioni |#Image Resize]. Le dimensioni possono essere passate come numeri interi in pixel o come stringhe in percentuale (ad esempio `'50%'`). +Ridimensiona l'immagine, [maggiori informazioni |#Ridimensionamento]. Le dimensioni possono essere specificate come interi in pixel o stringhe in percentuale (ad esempio `'50%'`). -resolution(int $resX=null, int $resY=null): mixed .[method] ------------------------------------------------------------ -Permette di impostare e ottenere la risoluzione di un'immagine in DPI (punti per pollice). Se non viene fornito nessuno dei parametri opzionali, la risoluzione corrente viene restituita come array indicizzato. Se viene indicato solo `$resX`, la risoluzione orizzontale e verticale viene impostata su questo valore. Se vengono forniti entrambi i parametri opzionali, la risoluzione orizzontale e verticale viene impostata rispettivamente su questi valori. +resolution(?int $resX=null, ?int $resY=null): mixed .[method] +------------------------------------------------------------- +Imposta o restituisce la risoluzione dell'immagine in DPI (punti per pollice). Se non viene specificato nessuno dei parametri opzionali, la risoluzione corrente viene restituita come array indicizzato. Se viene specificato solo `$resX`, la risoluzione orizzontale e verticale viene impostata su questo valore. Se vengono specificati entrambi i parametri opzionali, la risoluzione orizzontale e verticale viene impostata su questi valori. -La risoluzione viene utilizzata come meta-informazione solo quando le immagini vengono lette e scritte in formati che supportano questo tipo di informazioni (attualmente PNG e JPEG). Non influisce su nessuna operazione di disegno. La risoluzione predefinita per le nuove immagini è 96 DPI. ([altro |https://www.php.net/manual/en/function.imageresolution]) +La risoluzione viene utilizzata solo come meta informazione quando le immagini vengono lette e scritte in formati che supportano questo tipo di informazioni (attualmente PNG e JPEG). Non influisce su alcuna operazione di disegno. La risoluzione predefinita delle nuove immagini è 96 DPI. ([maggiori informazioni |https://www.php.net/manual/en/function.imageresolution]) rotate(float $angle, int $backgroundColor): Image .[method] ----------------------------------------------------------- -Ruota l'immagine utilizzando l'indirizzo `$angle` in gradi. Il centro di rotazione è il centro dell'immagine e l'immagine ruotata può avere dimensioni diverse dall'immagine originale. ([altro |https://www.php.net/manual/en/function.imagerotate]) +Ruota l'immagine utilizzando l'`$angle` specificato in gradi. Il centro di rotazione è il centro dell'immagine e l'immagine ruotata può avere dimensioni diverse dall'immagine originale. ([maggiori informazioni |https://www.php.net/manual/en/function.imagerotate]) .[note] -Richiede l'estensione *Bundled GD*, quindi non è sicuro che funzioni ovunque. +Richiede la presenza dell'estensione *Bundled GD*, quindi potrebbe non funzionare ovunque. -save(string $file, int $quality=null, int $type=null): void .[method] ---------------------------------------------------------------------- -Salva un'immagine in un file. +save(string $file, ?int $quality=null, ?int $type=null): void .[method] +----------------------------------------------------------------------- +Salva l'immagine in un file. -La qualità di compressione è compresa nell'intervallo 0..100 per JPEG (valore predefinito 85), WEBP (valore predefinito 80) e AVIF (valore predefinito 30) e 0..9 per PNG (valore predefinito 9). Se il tipo non è evidente dall'estensione del file, è possibile specificarlo utilizzando una delle costanti `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF`, e `Image::BMP`. +La qualità della compressione è nell'intervallo 0..100 per JPEG (predefinito 85), WEBP (predefinito 80) e AVIF (predefinito 30) e 0..9 per PNG (predefinito 9). Se il tipo non è chiaro dall'estensione del file, è possibile specificarlo utilizzando una delle costanti `ImageType`. saveAlpha(bool $saveflag): void .[method] ----------------------------------------- -Imposta il flag che determina se mantenere le informazioni del canale alfa completo (rispetto alla trasparenza di un solo colore) quando si salvano le immagini PNG. +Imposta il flag che indica se conservare le informazioni complete del canale alfa durante il salvataggio delle immagini PNG (a differenza della trasparenza a colore singolo). -L'alphablending deve essere disabilitato (`alphaBlending(false)`) per mantenere il canale alfa. ([altro |https://www.php.net/manual/en/function.imagesavealpha]) +L'alfablending deve essere disattivato (`alphaBlending(false)`) affinché il canale alfa venga mantenuto in primo luogo. ([maggiori informazioni |https://www.php.net/manual/en/function.imagesavealpha]) scale(int $newWidth, int $newHeight=-1, int $mode=IMG_BILINEAR_FIXED): Image .[method] -------------------------------------------------------------------------------------- -Ridimensiona un'immagine utilizzando l'algoritmo di interpolazione indicato. ([altro |https://www.php.net/manual/en/function.imagescale]) +Scala l'immagine utilizzando l'algoritmo di interpolazione specificato. ([maggiori informazioni |https://www.php.net/manual/en/function.imagescale]) -send(int $type=Image::JPEG, int $quality=null): void .[method] --------------------------------------------------------------- -Invia un'immagine al browser. +send(int $type=ImageType::JPEG, ?int $quality=null): void .[method] +------------------------------------------------------------------- +Invia l'immagine al browser. -La qualità di compressione è compresa nell'intervallo 0..100 per JPEG (valore predefinito 85), WEBP (valore predefinito 80) e AVIF (valore predefinito 30) e 0..9 per PNG (valore predefinito 9). Il tipo è una delle costanti `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` e `Image::BMP`. +La qualità della compressione è nell'intervallo 0..100 per JPEG (predefinito 85), WEBP (predefinito 80) e AVIF (predefinito 30) e 0..9 per PNG (predefinito 9). setBrush(Image $brush): void .[method] -------------------------------------- -Imposta l'immagine del pennello da utilizzare da tutte le funzioni di disegno di linee (come `line()` e `polygon()`) quando si disegna con i colori speciali IMG_COLOR_BRUSHED o IMG_COLOR_STYLEDBRUSHED. ([altro |https://www.php.net/manual/en/function.imagesetbrush]) +Imposta l'immagine del pennello da utilizzare in tutte le funzioni di disegno di linee (ad esempio `line()` e `polygon()`) quando si disegna con i colori speciali IMG_COLOR_BRUSHED o IMG_COLOR_STYLEDBRUSHED. ([maggiori informazioni |https://www.php.net/manual/en/function.imagesetbrush]) setClip(int $x1, int $y1, int $x2, int $y2): void .[method] ----------------------------------------------------------- -Imposta il rettangolo di ritaglio corrente, cioè l'area oltre la quale non verranno disegnati pixel. ([altro |https://www.php.net/manual/en/function.imagesetclip]) +Imposta il ritaglio corrente, ovvero l'area oltre la quale non verranno disegnati pixel. ([maggiori informazioni |https://www.php.net/manual/en/function.imagesetclip]) setInterpolation(int $method=IMG_BILINEAR_FIXED): void .[method] ---------------------------------------------------------------- -Imposta il metodo di interpolazione che influisce sui metodi `rotate()` e `affine()`. ([altro |https://www.php.net/manual/en/function.imagesetinterpolation]) +Imposta il metodo di interpolazione, che influisce sui metodi `rotate()` e `affine()`. ([maggiori informazioni |https://www.php.net/manual/en/function.imagesetinterpolation]) -setPixel(int $x, int $y, int $color): void .[method] ----------------------------------------------------- -Disegna un pixel alla coordinata specificata. ([altro |https://www.php.net/manual/en/function.imagesetpixel]) +setPixel(int $x, int $y, ImageColor $color): void .[method] +----------------------------------------------------------- +Disegna un pixel alle coordinate specificate. ([maggiori informazioni |https://www.php.net/manual/en/function.imagesetpixel]) setStyle(array $style): void .[method] -------------------------------------- -Imposta lo stile da utilizzare da tutte le funzioni di disegno delle linee (come `line()` e `polygon()`) quando si disegna con il colore speciale IMG_COLOR_STYLED o linee di immagini con colore IMG_COLOR_STYLEDBRUSHED. ([altro |https://www.php.net/manual/en/function.imagesetstyle]) +Imposta lo stile da utilizzare per tutte le funzioni di disegno di linee (ad esempio `line()` e `polygon()`) quando si disegna con il colore speciale IMG_COLOR_STYLED o linee di immagini con il colore IMG_COLOR_STYLEDBRUSHED. ([maggiori informazioni |https://www.php.net/manual/en/function.imagesetstyle]) setThickness(int $thickness): void .[method] -------------------------------------------- -Imposta lo spessore delle linee tracciate quando si disegnano rettangoli, poligoni, archi ecc. a `$thickness` pixel. ([altro |https://www.php.net/manual/en/function.imagesetthickness]) +Imposta lo spessore delle linee durante il disegno di rettangoli, poligoni, archi, ecc. a `$thickness` pixel. ([maggiori informazioni |https://www.php.net/manual/en/function.imagesetthickness]) setTile(Image $tile): void .[method] ------------------------------------ -Imposta l'immagine della piastrella da usare da tutte le funzioni di riempimento delle regioni (come `fill()` e `filledPolygon()`) quando si riempie con il colore speciale IMG_COLOR_TILED. +Imposta l'immagine della piastrella da utilizzare in tutte le funzioni di riempimento delle regioni (ad esempio `fill()` e `filledPolygon()`) quando si riempie con il colore speciale IMG_COLOR_TILED. -Una piastrella è un'immagine utilizzata per riempire un'area con un motivo ripetuto. È possibile utilizzare qualsiasi immagine come piastrella e, impostando l'indice del colore trasparente dell'immagine della piastrella con `colorTransparent()`, è possibile creare una piastrella che lascia trasparire alcune parti dell'area sottostante. ([altro |https://www.php.net/manual/en/function.imagesettile]) +Una piastrella è un'immagine utilizzata per riempire un'area con un motivo ripetuto. Qualsiasi immagine può essere utilizzata come piastrella e impostando l'indice del colore trasparente dell'immagine della piastrella con `colorTransparent()` è possibile creare una piastrella in cui alcune parti dell'area sottostante traspaiono. ([maggiori informazioni |https://www.php.net/manual/en/function.imagesettile]) sharpen(): Image .[method] -------------------------- -Rende l'immagine un po' più nitida. +Applica nitidezza all'immagine. .[note] -Richiede l'estensione *Bundled GD*, quindi non è sicuro che funzioni ovunque. - - -string(int $font, int $x, int $y, string $str, int $col): void .[method] ------------------------------------------------------------------------- -Disegna una stringa alle coordinate date. ([altro |https://www.php.net/manual/en/function.imagestring]) - +Richiede la presenza dell'estensione *Bundled GD*, quindi potrebbe non funzionare ovunque. -stringUp(int $font, int $x, int $y, string $s, int $col): void .[method] ------------------------------------------------------------------------- -Disegna una stringa in verticale alle coordinate date. ([altro |https://www.php.net/manual/en/function.imagestringup]) +toString(int $type=ImageType::JPEG, ?int $quality=null): string .[method] +------------------------------------------------------------------------- +Salva l'immagine in una stringa. -toString(int $type=Image::JPEG, int $quality=null): string .[method] --------------------------------------------------------------------- -Emette un'immagine in stringa. - -La qualità di compressione è compresa nell'intervallo 0..100 per JPEG (valore predefinito 85), WEBP (valore predefinito 80) e AVIF (valore predefinito 30) e 0..9 per PNG (valore predefinito 9). Il tipo è una delle costanti `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` e `Image::BMP`. +La qualità della compressione è nell'intervallo 0..100 per JPEG (predefinito 85), WEBP (predefinito 80) e AVIF (predefinito 30) e 0..9 per PNG (predefinito 9). trueColorToPalette(bool $dither, int $ncolors): void .[method] -------------------------------------------------------------- -Converte un'immagine truecolor in un'immagine palette. ([altro |https://www.php.net/manual/en/function.imagetruecolortopalette]) +Converte un'immagine truecolor in una a tavolozza. ([maggiori informazioni |https://www.php.net/manual/en/function.imagetruecolortopalette]) -ttfText(int $size, int $angle, int $x, int $y, int $color, string $fontfile, string $text): array .[method] ------------------------------------------------------------------------------------------------------------ -Scrive il testo dato nell'immagine usando i font TrueType. ([altro |https://www.php.net/manual/en/function.imagettftext]) +ttfText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +----------------------------------------------------------------------------------------------------------------------------------------- +Scrive il testo specificato nell'immagine. ([maggiori informazioni |https://www.php.net/manual/en/function.imagettftext]) diff --git a/utils/it/iterables.texy b/utils/it/iterables.texy new file mode 100644 index 0000000000..d56e04c3ef --- /dev/null +++ b/utils/it/iterables.texy @@ -0,0 +1,170 @@ +Lavorare con gli iteratori +************************** + +.[perex]{data-version:4.0.4} +[api:Nette\Utils\Iterables] è una classe statica con funzioni per lavorare con gli iteratori. La sua controparte per gli array è [Nette\Utils\Arrays |arrays]. + + +Installazione: + +```shell +composer require nette/utils +``` + +Tutti gli esempi presuppongono la creazione di un alias: + +```php +use Nette\Utils\Iterables; +``` + + +contains(iterable $iterable, $value): bool .[method] +---------------------------------------------------- + +Cerca il valore specificato nell'iteratore. Utilizza un confronto rigoroso (`===`) per verificare la corrispondenza. Restituisce `true` se il valore viene trovato, altrimenti `false`. + +```php +Iterables::contains(new ArrayIterator([1, 2, 3]), 1); // true +Iterables::contains(new ArrayIterator([1, 2, 3]), '1'); // false +``` + +Questo metodo è utile quando è necessario determinare rapidamente se un valore specifico si trova nell'iteratore senza dover scorrere manualmente tutti gli elementi. + + +containsKey(iterable $iterable, $key): bool .[method] +----------------------------------------------------- + +Cerca la chiave specificata nell'iteratore. Utilizza un confronto rigoroso (`===`) per verificare la corrispondenza. Restituisce `true` se la chiave viene trovata, altrimenti `false`. + +```php +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 0); // true +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 4); // false +``` + + +every(iterable $iterable, callable $predicate): bool .[method] +-------------------------------------------------------------- + +Verifica se tutti gli elementi dell'iteratore soddisfano la condizione definita in `$predicate`. La funzione `$predicate` ha la firma `function ($value, $key, iterable $iterable): bool` e deve restituire `true` per ogni elemento affinché il metodo `every()` restituisca `true`. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isBelowThreshold = fn($value) => $value < 40; +$res = Iterables::every($iterator, $isBelowThreshold); // true +``` + +Questo metodo è utile per verificare se tutti gli elementi di una collezione soddisfano una determinata condizione, ad esempio se tutti i numeri sono inferiori a un certo valore. + + +filter(iterable $iterable, callable $predicate): Generator .[method] +-------------------------------------------------------------------- + +Crea un nuovo iteratore che contiene solo gli elementi dell'iteratore originale che soddisfano la condizione definita in `$predicate`. La funzione `$predicate` ha la firma `function ($value, $key, iterable $iterable): bool` e deve restituire `true` per gli elementi che devono essere conservati. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::filter($iterator, fn($v) => $v < 3); +// 1, 2 +``` + +Il metodo utilizza un generatore, il che significa che il filtraggio avviene gradualmente durante l'attraversamento del risultato. Questo è efficiente dal punto di vista della memoria e consente di elaborare anche collezioni molto grandi. Se non si attraversano tutti gli elementi dell'iteratore risultante, si risparmia potenza di calcolo, poiché non vengono elaborati tutti gli elementi dell'iteratore originale. + + +first(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------------- + +Restituisce il primo elemento dell'iteratore. Se viene specificato `$predicate`, restituisce il primo elemento che soddisfa la condizione data. La funzione `$predicate` ha la firma `function ($value, $key, iterable $iterable): bool`. Se non viene trovato alcun elemento corrispondente, viene chiamata la funzione `$else` (se specificata) e viene restituito il suo risultato. Se `$else` non è specificato, viene restituito `null`. + +```php +Iterables::first(new ArrayIterator([1, 2, 3])); // 1 +Iterables::first(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 3 +Iterables::first(new ArrayIterator([])); // null +Iterables::first(new ArrayIterator([]), else: fn() => false); // false +``` + +Questo metodo è utile quando è necessario ottenere rapidamente il primo elemento di una collezione o il primo elemento che soddisfa una determinata condizione, senza dover attraversare manualmente l'intera collezione. + + +firstKey(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +---------------------------------------------------------------------------------------------- + +Restituisce la chiave del primo elemento dell'iteratore. Se viene specificato `$predicate`, restituisce la chiave del primo elemento che soddisfa la condizione data. La funzione `$predicate` ha la firma `function ($value, $key, iterable $iterable): bool`. Se non viene trovato alcun elemento corrispondente, viene chiamata la funzione `$else` (se specificata) e viene restituito il suo risultato. Se `$else` non è specificato, viene restituito `null`. + +```php +Iterables::firstKey(new ArrayIterator([1, 2, 3])); // 0 +Iterables::firstKey(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 2 +Iterables::firstKey(new ArrayIterator(['a' => 1, 'b' => 2])); // 'a' +Iterables::firstKey(new ArrayIterator([])); // null +``` + + +map(iterable $iterable, callable $transformer): Generator .[method] +------------------------------------------------------------------- + +Crea un nuovo iteratore applicando la funzione `$transformer` a ogni elemento dell'iteratore originale. La funzione `$transformer` ha la firma `function ($value, $key, iterable $iterable): mixed` e il suo valore di ritorno viene utilizzato come nuovo valore dell'elemento. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::map($iterator, fn($v) => $v * 2); +// 2, 4, 6 +``` + +Il metodo utilizza un generatore, il che significa che la trasformazione avviene gradualmente durante l'attraversamento del risultato. Questo è efficiente dal punto di vista della memoria e consente di elaborare anche collezioni molto grandi. Se non si attraversano tutti gli elementi dell'iteratore risultante, si risparmia potenza di calcolo, poiché non vengono elaborati tutti gli elementi dell'iteratore originale. + + +mapWithKeys(iterable $iterable, callable $transformer): Generator .[method] +--------------------------------------------------------------------------- + +Crea un nuovo iteratore trasformando i valori e le chiavi dell'iteratore originale. La funzione `$transformer` ha la firma `function ($value, $key, iterable $iterable): ?array{$newKey, $newValue}`. Se `$transformer` restituisce `null`, l'elemento viene saltato. Per gli elementi conservati, il primo elemento dell'array restituito viene utilizzato come nuova chiave e il secondo elemento come nuovo valore. + +```php +$iterator = new ArrayIterator(['a' => 1, 'b' => 2]); +$iterator = Iterables::mapWithKeys($iterator, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +Come `map()`, questo metodo utilizza un generatore per l'elaborazione graduale e un lavoro efficiente con la memoria. Ciò consente di lavorare con grandi collezioni e risparmiare potenza di calcolo durante l'attraversamento parziale del risultato. + + +memoize(iterable $iterable): IteratorAggregate .[method] +-------------------------------------------------------- + +Crea un wrapper attorno all'iteratore che memorizza nella cache le sue chiavi e i suoi valori durante l'iterazione. Ciò consente l'iterazione ripetuta dei dati senza la necessità di attraversare nuovamente l'origine dati originale. + +```php +$iterator = /* dati che non possono essere iterati più volte */ +$memoized = Iterables::memoize($iterator); +// Ora puoi iterare $memoized più volte senza perdita di dati +``` + +Questo metodo è utile in situazioni in cui è necessario attraversare più volte lo stesso set di dati, ma l'iteratore originale non consente l'iterazione ripetuta o l'attraversamento ripetuto sarebbe costoso (ad esempio, durante la lettura di dati da un database o da un file). + + +some(iterable $iterable, callable $predicate): bool .[method] +------------------------------------------------------------- + +Verifica se almeno un elemento dell'iteratore soddisfa la condizione definita in `$predicate`. La funzione `$predicate` ha la firma `function ($value, $key, iterable $iterable): bool` e deve restituire `true` per almeno un elemento affinché il metodo `some()` restituisca `true`. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isEven = fn($value) => $value % 2 === 0; +$res = Iterables::some($iterator, $isEven); // true +``` + +Questo metodo è utile per verificare rapidamente se esiste almeno un elemento nella collezione che soddisfa una determinata condizione, ad esempio se la collezione contiene almeno un numero pari. + +Vedi [#every()]. + + +toIterator(iterable $iterable): Iterator .[method] +-------------------------------------------------- + +Converte qualsiasi oggetto iterabile (array, Traversable) in un Iterator. Se l'input è già un Iterator, lo restituisce senza modifiche. + +```php +$array = [1, 2, 3]; +$iterator = Iterables::toIterator($array); +// Ora hai un Iterator invece di un array +``` + +Questo metodo è utile quando è necessario assicurarsi di avere a disposizione un `Iterator`, indipendentemente dal tipo di dati di input. Questo può essere utile durante la creazione di funzioni che lavorano con diversi tipi di dati iterabili. diff --git a/utils/it/json.texy b/utils/it/json.texy index 6a4771124b..9474e4a0da 100644 --- a/utils/it/json.texy +++ b/utils/it/json.texy @@ -1,8 +1,8 @@ -Funzioni JSON -************* +Lavorare con JSON +***************** .[perex] -[api:Nette\Utils\Json] è una classe statica con funzioni di codifica e decodifica JSON. Gestisce le vulnerabilità delle diverse versioni di PHP e lancia eccezioni in caso di errore. +[api:Nette\Utils\Json] è una classe statica con funzioni per la codifica e la decodifica del formato JSON. Gestisce le vulnerabilità delle diverse versioni di PHP e lancia eccezioni in caso di errori. Installazione: @@ -11,44 +11,44 @@ Installazione: composer require nette/utils ``` -Tutti gli esempi presuppongono che sia definito il seguente alias di classe: +Tutti gli esempi presuppongono la creazione di un alias: ```php use Nette\Utils\Json; ``` -Uso .[#toc-usage] -================= +Utilizzo +======== encode(mixed $value, bool $pretty=false, bool $asciiSafe=false, bool $htmlSafe=false, bool $forceObjects=false): string .[method] --------------------------------------------------------------------------------------------------------------------------------- -Converte `$value` in formato JSON. +Converte `$value` nel formato JSON. -Quando `$pretty` è impostato, formatta JSON per facilitare la lettura e la chiarezza: +Impostando `$pretty` su `true`, formatta il JSON per una lettura e una chiarezza più semplici: ```php Json::encode($value); // restituisce JSON -Json::encode($value, pretty: true); // restituisce un JSON più chiaro +Json::encode($value, pretty: true); // restituisce un JSON più leggibile ``` -Quando `$asciiSafe` è impostato, genera un output ASCII, cioè sostituisce i caratteri unicode con le sequenze `\uxxxx`: +Con `$asciiSafe` genera un output in ASCII, ovvero i caratteri unicode vengono sostituiti con sequenze `\uxxxx`: ```php Json::encode('žluťoučký', asciiSafe: true); // '"\u017elu\u0165ou\u010dk\u00fd"' ``` -Il parametro `$htmlSafe` assicura che l'output non contenga caratteri con un significato speciale in HTML: +Il parametro `$htmlSafe` garantisce che l'output non contenga caratteri che hanno un significato speciale in HTML: ```php Json::encode('onesendJson($data)`, che può essere richiamato, ad esempio, nel metodo `action*()`, vedere [Invio di una risposta |application:presenters#Sending a Response]. +A tale scopo è possibile utilizzare il metodo `$this->sendJson($data)`, che possiamo chiamare ad esempio nel metodo `action*()`, vedi [Invio della risposta |application:presenters#Invio della risposta]. diff --git a/utils/it/paginator.texy b/utils/it/paginator.texy index 850c377802..f4a65f284c 100644 --- a/utils/it/paginator.texy +++ b/utils/it/paginator.texy @@ -1,8 +1,8 @@ -Paginatore -********** +Paginator +********* .[perex] -Avete bisogno di impaginare un elenco di dati? Poiché la matematica che sta dietro alla paginazione può essere complicata, [api:Nette\Utils\Paginator] vi aiuterà. +Avete bisogno di impaginare un elenco di dati? Poiché la matematica della paginazione può essere insidiosa, [api:Nette\Utils\Paginator] vi aiuterà. Installazione: @@ -11,38 +11,38 @@ Installazione: composer require nette/utils ``` -Creiamo un oggetto di paginazione e impostiamo le informazioni di base: +Creiamo un oggetto paginator e impostiamo le informazioni di base: ```php $paginator = new Nette\Utils\Paginator; -$paginator->setPage(1); // il numero della pagina corrente (numerata a partire da 1) -$paginator->setItemsPerPage(30); // il numero di record per pagina -$paginator->setItemCount(356); // il numero totale di record (se disponibile) +$paginator->setPage(1); // numero della pagina corrente +$paginator->setItemsPerPage(30); // numero di elementi per pagina +$paginator->setItemCount(356); // numero totale di elementi, se noto ``` -Le pagine sono numerate a partire da 1. Possiamo cambiarle usando `setBase()`: +Le pagine sono numerate a partire da 1. Possiamo cambiarlo usando `setBase()`: ```php -$paginator->setBase(0); // numerato da 0 +$paginator->setBase(0); // numeriamo da 0 ``` -L'oggetto fornirà ora tutte le informazioni di base utili per creare un paginatore. È possibile, ad esempio, passarlo a un template e utilizzarlo lì. +L'oggetto ora fornirà tutte le informazioni di base utili per la creazione di un paginator. Potete, ad esempio, passarlo a un template e utilizzarlo lì. ```php -$paginator->isFirst(); // è la prima pagina? -$paginator->isLast(); // è l'ultima pagina? -$paginator->getPage(); // numero di pagina attuale -$paginator->getFirstPage(); // il numero della prima pagina -$paginator->getLastPage(); // il numero dell'ultima pagina -$paginator->getFirstItemOnPage(); // numero di sequenza del primo elemento della pagina -$paginator->getLastItemOnPage(); // numero di sequenza dell'ultimo elemento della pagina -$paginator->getPageIndex(); // numero della pagina corrente, se numerata a partire da 0 -$paginator->getPageCount(); // il numero totale di pagine -$paginator->getItemsPerPage(); // il numero di record per pagina -$paginator->getItemCount(); // il numero totale di record (se disponibile) +$paginator->isFirst(); // siamo sulla prima pagina? +$paginator->isLast(); // siamo sull'ultima pagina? +$paginator->getPage(); // numero della pagina corrente +$paginator->getFirstPage(); // numero della prima pagina +$paginator->getLastPage(); // numero dell'ultima pagina +$paginator->getFirstItemOnPage(); // numero ordinale del primo elemento sulla pagina +$paginator->getLastItemOnPage(); // numero ordinale dell'ultimo elemento sulla pagina +$paginator->getPageIndex(); // numero della pagina corrente numerata da 0 (indice) +$paginator->getPageCount(); // numero totale di pagine +$paginator->getItemsPerPage(); // numero di elementi per pagina +$paginator->getItemCount(); // numero totale di elementi, se noto ``` -Il paginatore aiuterà a formulare la query SQL. I metodi `getLength()` e `getOffset()` restituiscono i valori che possono essere utilizzati nelle clausole LIMIT e OFFSET: +Il paginator aiuta nella formulazione di una query SQL. I metodi `getLength()` e `getOffset()` restituiscono valori che utilizziamo nelle clausole LIMIT e OFFSET: ```php $result = $database->query( @@ -52,7 +52,7 @@ $result = $database->query( ); ``` -Se si desidera effettuare la paginazione in ordine inverso, ossia che la pagina n. 1 corrisponda all'offset più alto, si può utilizzare il metodo . 1 corrisponde all'offset più alto, si può utilizzare il metodo `getCountdownOffset()`: +Se abbiamo bisogno di impaginare in ordine inverso, ovvero la pagina n. 1 corrisponde all'offset più alto, utilizziamo `getCountdownOffset()`: ```php $result = $database->query( @@ -62,4 +62,4 @@ $result = $database->query( ); ``` -Un esempio di utilizzo nell'applicazione si trova nel ricettario [Paginazione dei risultati del database |best-practices:pagination]. +Un esempio di utilizzo nell'applicazione si trova nel cookbook [Paginazione dei risultati del database |best-practices:pagination]. diff --git a/utils/it/random.texy b/utils/it/random.texy index ab63fc2c6e..6bf05b1df3 100644 --- a/utils/it/random.texy +++ b/utils/it/random.texy @@ -1,8 +1,8 @@ -Generatore di stringhe casuali -****************************** +Generazione di stringhe casuali +******************************* .[perex] -[api:Nette\Utils\Random] è una classe statica per generare stringhe pseudocasuali crittograficamente sicure. +[api:Nette\Utils\Random] è una classe statica per la generazione di stringhe pseudocasuali crittograficamente sicure. Installazione: @@ -15,7 +15,7 @@ composer require nette/utils generate(int $length=10, string $charlist=`'0-9a-z'`): string .[method] ----------------------------------------------------------------------- -Genera una stringa casuale di lunghezza determinata a partire dai caratteri specificati nel secondo argomento. Supporta intervalli, come `0-9` o `A-Z`. +Genera una stringa casuale della lunghezza specificata dai caratteri specificati dal parametro `$charlist`. È possibile utilizzare anche intervalli scritti come `0-9`. ```php use Nette\Utils\Random; diff --git a/utils/it/reflection.texy b/utils/it/reflection.texy index 2e55c11f71..3a06bed144 100644 --- a/utils/it/reflection.texy +++ b/utils/it/reflection.texy @@ -2,7 +2,7 @@ Riflessione PHP *************** .[perex] -[api:Nette\Utils\Reflection] è una classe statica con funzioni utili per la riflessione in PHP. Il suo scopo è quello di correggere i difetti delle classi native e di unificare il comportamento delle diverse versioni di PHP. +[api:Nette\Utils\Reflection] è una classe statica con funzioni utili per la riflessione PHP. Il suo compito è correggere le mancanze delle classi native e unificare il comportamento tra le diverse versioni di PHP. Installazione: @@ -11,7 +11,7 @@ Installazione: composer require nette/utils ``` -Tutti gli esempi presuppongono che sia definito il seguente alias di classe: +Tutti gli esempi presuppongono la creazione di un alias: ```php use Nette\Utils\Reflection; @@ -21,13 +21,13 @@ use Nette\Utils\Reflection; areCommentsAvailable(): bool .[method] -------------------------------------- -Scopre se reflection ha accesso ai commenti di PHPdoc. I commenti potrebbero non essere disponibili a causa della cache degli opcode, si veda per esempio la direttiva [opcache.save-comments |https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments]. +Verifica se la riflessione ha accesso ai commenti PHPdoc. I commenti potrebbero non essere disponibili a causa della cache opcode, vedi ad esempio la direttiva [opcache.save-comments |https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments]. expandClassName(string $name, ReflectionClass $context): string .[method] ------------------------------------------------------------------------- -Espande il nome `$name` della classe al nome completo nel contesto di `$context`, cioè nel contesto del suo spazio dei nomi e degli alias definiti. Quindi, restituisce come il parser PHP capirebbe `$name` se fosse scritto nel corpo di `$context`. +Espande il nome della classe `$name` al suo nome completo nel contesto della classe `$context`, ovvero nel contesto del suo namespace e degli alias definiti. Quindi, in pratica, dice come il parser PHP capirebbe `$name` se fosse scritto nel corpo della classe `$context`. ```php namespace Foo; @@ -47,9 +47,9 @@ Reflection::expandClassName('Baz', $context); // 'Foo\Baz' getMethodDeclaringMethod(ReflectionMethod $method): ReflectionMethod .[method] ------------------------------------------------------------------------------ -Restituisce una riflessione di un metodo che contiene una dichiarazione di `$method`. Di solito, ogni metodo è una dichiarazione propria, ma il corpo del metodo può anche essere nel trait e con un nome diverso. +Restituisce la reflection del metodo che contiene la dichiarazione del metodo `$method`. Di solito, ogni metodo è la propria dichiarazione, ma il corpo del metodo può trovarsi anche in un trait e con un nome diverso. -Poiché PHP non fornisce informazioni sufficienti per determinare la dichiarazione effettiva, Nette utilizza una propria euristica, che dovrebbe essere affidabile. +Poiché PHP non fornisce informazioni sufficienti per determinare la dichiarazione effettiva, Nette utilizza una propria euristica che **dovrebbe essere** affidabile. ```php trait DemoTrait @@ -76,9 +76,9 @@ Reflection::getMethodDeclaringMethod($method); // ReflectionMethod('DemoTrait::f getPropertyDeclaringClass(ReflectionProperty $prop): ReflectionClass .[method] ------------------------------------------------------------------------------ -Restituisce una riflessione di una classe o di un tratto che contiene una dichiarazione di proprietà `$prop`. Le proprietà possono essere dichiarate anche nel tratto. +Restituisce la reflection della classe o del trait che contiene la dichiarazione della proprietà `$prop`. La proprietà, infatti, può essere dichiarata anche in un trait. -Poiché PHP non fornisce informazioni sufficienti per determinare la dichiarazione effettiva, Nette utilizza una propria euristica, che non è affidabile. +Poiché PHP non fornisce informazioni sufficienti per determinare la dichiarazione effettiva, Nette utilizza una propria euristica che **non è** affidabile. ```php trait DemoTrait @@ -100,7 +100,7 @@ Reflection::getPropertyDeclaringClass($prop); // ReflectionClass('DemoTrait') isBuiltinType(string $type): bool .[method deprecated] ------------------------------------------------------ -Determina se `$type` è un tipo incorporato in PHP. Altrimenti, è il nome della classe. +Verifica se `$type` è un tipo built-in di PHP. Altrimenti, è un nome di classe. ```php Reflection::isBuiltinType('string'); // true @@ -114,7 +114,7 @@ Utilizzare [Nette\Utils\Validator::isBuiltinType() |validators#isBuiltinType]. toString($reflection): string .[method] --------------------------------------- -Converte una riflessione in una stringa leggibile dall'uomo. +Converte una reflection in una stringa comprensibile all'uomo. ```php $func = new ReflectionFunction('func'); diff --git a/utils/it/smartobject.texy b/utils/it/smartobject.texy index aefed8cef1..04784c622b 100644 --- a/utils/it/smartobject.texy +++ b/utils/it/smartobject.texy @@ -1,8 +1,8 @@ -SmartObject e StaticClass -************************* +SmartObject +*********** .[perex] -SmartObject aggiunge il supporto per le *proprietà* alle classi PHP. StaticClass è usato per indicare le classi statiche. +SmartObject ha migliorato per anni il comportamento degli oggetti in PHP. Dalla versione PHP 8.4, tutte le sue funzioni sono già parte integrante di PHP stesso, completando così la sua missione storica di essere un pioniere dell'approccio moderno agli oggetti in PHP. Installazione: @@ -11,19 +11,64 @@ Installazione: composer require nette/utils ``` +SmartObject è nato nel 2007 come soluzione rivoluzionaria alle mancanze del modello a oggetti di PHP dell'epoca. In un momento in cui PHP soffriva di numerosi problemi di design degli oggetti, ha portato un significativo miglioramento e semplificazione del lavoro per gli sviluppatori. È diventato una parte leggendaria del framework Nette. Offriva funzionalità che PHP ha acquisito solo molti anni dopo - dal controllo dell'accesso alle proprietà degli oggetti a sofisticati "syntactic sugar". Con l'arrivo di PHP 8.4 ha completato la sua missione storica, poiché tutte le sue funzioni sono diventate parte nativa del linguaggio. Ha anticipato lo sviluppo di PHP di ben 17 anni. -Proprietà, Getter e Setter .[#toc-properties-getters-and-setters] -================================================================= +Tecnicamente, SmartObject ha subito un'interessante evoluzione. Originariamente era implementato come classe `Nette\Object`, da cui altre classi ereditavano la funzionalità necessaria. Un cambiamento fondamentale è avvenuto con PHP 5.4, che ha introdotto il supporto per i trait. Ciò ha permesso la trasformazione nella forma del trait `Nette\SmartObject`, che ha portato maggiore flessibilità - gli sviluppatori potevano utilizzare la funzionalità anche in classi che già ereditavano da un'altra classe. Mentre la classe originale `Nette\Object` è scomparsa con l'arrivo di PHP 7.2 (che ha vietato la denominazione delle classi con la parola `Object`), il trait `Nette\SmartObject` vive ancora. -Nei moderni linguaggi orientati agli oggetti (ad esempio, C#, Python, Ruby, JavaScript), il termine *property* si riferisce a [membri speciali delle classi |https://en.wikipedia.org/wiki/Property_(programming)] che sembrano variabili, ma in realtà sono rappresentati da metodi. Quando il valore di questa "variabile" viene assegnato o letto, viene richiamato il metodo corrispondente (chiamato getter o setter). Si tratta di una cosa molto comoda, che ci dà il pieno controllo sull'accesso alle variabili. Possiamo convalidare l'input o generare risultati solo quando la proprietà viene letta. +Esaminiamo le proprietà che un tempo `Nette\Object` e successivamente `Nette\SmartObject` offrivano. Ognuna di queste funzioni, a suo tempo, rappresentava un significativo passo avanti nel campo della programmazione orientata agli oggetti in PHP. -Le proprietà PHP non sono supportate, ma il trait `Nette\SmartObject` può imitarle. Come si usa? -- Aggiungere un'annotazione alla classe nella forma `@property $xyz` -- Creare un getter chiamato `getXyz()` o `isXyz()`, un setter chiamato `setXyz()` -- Il getter e il setter devono essere *pubblici* o *protetti* e sono opzionali, quindi può esserci una proprietà *di sola lettura* o *di sola scrittura*. +Stati di errore coerenti +------------------------ +Uno dei problemi più spinosi del primo PHP era il comportamento incoerente nel lavorare con gli oggetti. `Nette\Object` ha portato ordine e prevedibilità in questo caos. Vediamo come appariva il comportamento originale di PHP: -Utilizzeremo la proprietà per la classe Circle per garantire che nella variabile `$radius` vengano inseriti solo numeri non negativi. Sostituire `public $radius` con la proprietà: +```php +echo $obj->undeclared; // E_NOTICE, successivamente E_WARNING +$obj->undeclared = 1; // passa silenziosamente senza segnalazione +$obj->unknownMethod(); // Fatal error (non catturabile con try/catch) +``` + +Un fatal error terminava l'applicazione senza alcuna possibilità di reagire. La scrittura silenziosa su membri inesistenti senza preavviso poteva portare a gravi errori difficili da individuare. `Nette\Object` catturava tutti questi casi e lanciava un'eccezione `MemberAccessException`, consentendo ai programmatori di reagire agli errori e risolverli. + +```php +echo $obj->undeclared; // lancia Nette\MemberAccessException +$obj->undeclared = 1; // lancia Nette\MemberAccessException +$obj->unknownMethod(); // lancia Nette\MemberAccessException +``` + +Da PHP 7.0, il linguaggio non causa più fatal error non catturabili e da PHP 8.2 l'accesso a membri non dichiarati è considerato un errore. + + +Aiuto "Did you mean?" +--------------------- +`Nette\Object` ha introdotto una funzione molto piacevole: un aiuto intelligente per gli errori di battitura. Quando uno sviluppatore commetteva un errore nel nome di un metodo o di una variabile, non solo segnalava l'errore, ma offriva anche una mano sotto forma di suggerimento del nome corretto. Questo messaggio iconico, noto come "did you mean?", ha risparmiato ai programmatori ore di ricerca di errori di battitura: + +```php +class Foo extends Nette\Object +{ + public static function from($var) + { + } +} + +$foo = Foo::form($var); +// lancia Nette\MemberAccessException +// "Call to undefined static method Foo::form(), did you mean from()?" +``` + +Il PHP di oggi non ha alcuna forma di "did you mean?", ma questa aggiunta può essere inserita negli errori da [Tracy|tracy:]. E può persino [correggerli automaticamente |tracy:open-files-in-ide#Esempi]. + + +Proprietà con accesso controllato +--------------------------------- +Un'innovazione significativa che SmartObject ha portato in PHP sono state le proprietà con accesso controllato. Questo concetto, comune in linguaggi come C# o Python, ha permesso agli sviluppatori di controllare elegantemente l'accesso ai dati dell'oggetto e garantirne la coerenza. Le proprietà sono uno strumento potente della programmazione orientata agli oggetti. Funzionano come variabili, ma in realtà sono rappresentate da metodi (getter e setter). Ciò consente di validare gli input o generare valori solo al momento della lettura. + +Per utilizzare le proprietà è necessario: +- Aggiungere alla classe un'annotazione nella forma `@property $xyz` +- Creare un getter con il nome `getXyz()` o `isXyz()`, un setter con il nome `setXyz()` +- Assicurarsi che getter e setter siano *public* o *protected*. Sono opzionali - possono quindi esistere come proprietà *read-only* o *write-only* + +Vediamo un esempio pratico sulla classe Circle, dove utilizziamo le proprietà per garantire che il raggio sia sempre un numero non negativo. Sostituiamo l'originale `public $radius` con una proprietà: ```php /** @@ -34,7 +79,7 @@ class Circle { use Nette\SmartObject; - private float $radius = 0.0; // non pubblico + private float $radius = 0.0; // non è public! // getter per la proprietà $radius protected function getRadius(): float @@ -45,7 +90,7 @@ class Circle // setter per la proprietà $radius protected function setRadius(float $radius): void { - // sanitizza il valore prima di salvarlo + // sanitizziamo il valore prima di salvarlo $this->radius = max(0.0, $radius); } @@ -57,84 +102,30 @@ class Circle } $circle = new Circle; -$circle->radius = 10; // in realtà chiama setRadius(10) -echo $circle->radius; // chiama getRadius() +$circle->radius = 10; // in realtà chiama setRadius(10) +echo $circle->radius; // chiama getRadius() echo $circle->visible; // chiama isVisible() ``` -Le proprietà sono principalmente "zucchero sintattico" ((syntactic sugar)), che ha lo scopo di rendere più dolce la vita del programmatore semplificando il codice. Se non le si vuole, non è necessario usarle. - - -Classi statiche .[#toc-static-classes] -====================================== - -Le classi statiche, cioè quelle che non sono destinate ad essere istanziate, possono essere contrassegnate con il tratto `Nette\StaticClass`: +Da PHP 8.4 è possibile ottenere la stessa funzionalità utilizzando gli [property hooks |https://wiki.php.net/rfc/property-hooks], che offrono una sintassi molto più elegante e concisa: ```php -class Strings +class Circle { - use Nette\StaticClass; -} -``` - -Quando si tenta di creare un'istanza, viene lanciata l'eccezione `Error`, che indica che la classe è statica. - - -Uno sguardo alla storia .[#toc-a-look-into-the-history] -======================================================= - -SmartObject migliorava e correggeva il comportamento delle classi in molti modi, ma l'evoluzione di PHP ha reso superflua la maggior parte delle caratteristiche originali. Di seguito viene presentata la storia di come si sono evolute le cose. - -Fin dall'inizio, il modello a oggetti di PHP soffriva di una serie di gravi difetti e inefficienze. Questo è stato il motivo della creazione della classe `Nette\Object` (nel 2007), che ha cercato di porvi rimedio e di migliorare l'esperienza d'uso di PHP. È bastato che altre classi ereditassero da essa per trarne i benefici. Quando PHP 5.4 ha introdotto il supporto ai trait, la classe `Nette\Object` è stata sostituita da `Nette\SmartObject`. Pertanto, non era più necessario ereditare da un antenato comune. Inoltre, i trait potevano essere usati in classi che già ereditavano da un'altra classe. La fine definitiva di `Nette\Object` avvenne con il rilascio di PHP 7.2, che proibiva alle classi di chiamarsi `Object`. - -Con il proseguire dello sviluppo di PHP, il modello a oggetti e le funzionalità del linguaggio sono stati migliorati. Le singole funzioni della classe `SmartObject` sono diventate superflue. Dal rilascio di PHP 8.2, l'unica caratteristica rimasta che non è ancora direttamente supportata in PHP è la possibilità di usare le cosiddette [proprietà |#Properties, Getters and Setters]. - -Quali funzioni offrivano un tempo `Nette\Object` e `Nette\Object`? Ecco una panoramica. (Gli esempi utilizzano la classe `Nette\Object`, ma la maggior parte delle proprietà si applicano anche al tratto `Nette\SmartObject` ). - - -Errori inconsistenti .[#toc-inconsistent-errors] ------------------------------------------------- -PHP aveva un comportamento incoerente quando si accedeva a membri non dichiarati. Lo stato al momento di `Nette\Object` era il seguente: - -```php -echo $obj->undeclared; // E_NOTICE, successivamente E_WARNING -$obj->undeclared = 1; // passa in modo silenzioso, senza segnalare nulla -$obj->unknownMethod(); // Errore fatale (non catturabile con try/catch) -``` - -L'errore fatale terminava l'applicazione senza alcuna possibilità di reagire. Scrivere silenziosamente su membri inesistenti senza preavviso poteva portare a errori gravi, difficili da individuare. `Nette\Object` Tutti questi casi sono stati catturati ed è stata lanciata l'eccezione `MemberAccessException`. - -```php -echo $obj->undeclared; // lancia l'eccezione Nette\MemberAccessException -$obj->undeclared = 1; // lanciare l'eccezione Nette\MemberAccessException -$obj->unknownMethod(); // lanciare l'eccezione Nette\MemberAccessException -``` -A partire da PHP 7.0, PHP non causa più errori fatali non catturabili e l'accesso a membri non dichiarati è un bug da PHP 8.2. - - -Intendevi dire? .[#toc-did-you-mean] ------------------------------------- -Se veniva lanciato un errore `Nette\MemberAccessException`, magari a causa di un errore di battitura nell'accesso a una variabile oggetto o nella chiamata di un metodo, `Nette\Object` cercava di dare un suggerimento nel messaggio di errore su come correggere l'errore, sotto forma dell'iconico addendum "intendevi? + public float $radius = 0.0 { + set => max(0.0, $value); + } -```php -class Foo extends Nette\Object -{ - public static function from($var) - { + public bool $visible { + get => $this->radius > 0; } } - -$foo = Foo::form($var); -// throw Nette\MemberAccessException -// "Call to undefined static method Foo::form(), did you mean from()?" ``` -Il PHP di oggi potrebbe non avere alcuna forma di "did you mean?", ma [Tracy |tracy:] aggiunge questa appendice agli errori. E può persino [correggere |tracy:open-files-in-ide#toc-demos] tali errori da solo. - -Metodi di estensione .[#toc-extension-methods] ----------------------------------------------- -Ispirato ai metodi di estensione di C#. Offrono la possibilità di aggiungere nuovi metodi a classi esistenti. Ad esempio, si può aggiungere il metodo `addDateTime()` a un modulo per aggiungere il proprio DateTimePicker. +Metodi di estensione +-------------------- +`Nette\Object` ha introdotto in PHP un altro concetto interessante ispirato ai moderni linguaggi di programmazione - i metodi di estensione. Questa funzione, presa in prestito da C#, ha permesso agli sviluppatori di estendere elegantemente le classi esistenti con nuovi metodi senza la necessità di modificarle o ereditarle. Ad esempio, si poteva aggiungere al form un metodo `addDateTime()` che aggiungeva un DateTimePicker personalizzato: ```php Form::extensionMethod( @@ -146,22 +137,22 @@ $form = new Form; $form->addDateTime('date'); ``` -I metodi di estensione si sono rivelati poco pratici, perché i loro nomi non venivano autocompilati dagli editor, che invece segnalavano che il metodo non esisteva. Pertanto, il loro supporto è stato interrotto. +I metodi di estensione si sono rivelati poco pratici, poiché i loro nomi non venivano suggeriti dagli editor, anzi segnalavano che il metodo non esisteva. Pertanto, il loro supporto è stato interrotto. Oggi è più comune utilizzare la composizione o l'ereditarietà per estendere la funzionalità delle classi. -Ottenere il nome della classe .[#toc-getting-the-class-name] ------------------------------------------------------------- +Ottenere il nome della classe +----------------------------- +Per ottenere il nome della classe, SmartObject offriva un metodo semplice: ```php $class = $obj->getClass(); // usando Nette\Object -$class = $obj::class; // da PHP 8.0 +$class = $obj::class; // da PHP 8.0 ``` -Accesso alla riflessione e alle annotazioni .[#toc-access-to-reflection-and-annotations] ----------------------------------------------------------------------------------------- - -`Nette\Object` ha offerto l'accesso alla riflessione e alle annotazioni utilizzando i metodi `getReflection()` e `getAnnotation()`: +Accesso a reflection e annotazioni +---------------------------------- +`Nette\Object` offriva l'accesso a reflection e annotazioni tramite i metodi `getReflection()` e `getAnnotation()`. Questo approccio ha semplificato notevolmente il lavoro con le metainformazioni delle classi: ```php /** @@ -173,10 +164,10 @@ class Foo extends Nette\Object $obj = new Foo; $reflection = $obj->getReflection(); -$reflection->getAnnotation('author'); // restituisce 'John Doe'. +$reflection->getAnnotation('author'); // restituisce 'John Doe' ``` -A partire da PHP 8.0, è possibile accedere alle meta-informazioni sotto forma di attributi: +Da PHP 8.0 è possibile accedere alle metainformazioni sotto forma di attributi, che offrono ancora maggiori possibilità e un migliore controllo dei tipi: ```php #[Author('John Doe')] @@ -190,10 +181,9 @@ $reflection->getAttributes(Author::class)[0]; ``` -Metodi Getter .[#toc-method-getters] ------------------------------------- - -`Nette\Object` offre un modo elegante per trattare i metodi come se fossero variabili: +Getter di metodi +---------------- +`Nette\Object` offriva un modo elegante per passare metodi come se fossero variabili: ```php class Foo extends Nette\Object @@ -209,7 +199,7 @@ $method = $obj->adder; echo $method(2, 3); // 5 ``` -A partire da PHP 8.1, è possibile utilizzare la cosiddetta "sintassi callable di prima classe":https://www.php.net/manual/en/functions.first_class_callable_syntax: +Da PHP 8.1 è possibile utilizzare la cosiddetta "sintassi callable di prima classe":https://www.php.net/manual/en/functions.first_class_callable_syntax, che porta questo concetto ancora oltre: ```php $obj = new Foo; @@ -218,10 +208,9 @@ echo $method(2, 3); // 5 ``` -Eventi .[#toc-events] ---------------------- - -`Nette\Object` ha offerto uno zucchero sintattico per innescare l'[evento |nette:glossary#events]: +Eventi +------ +SmartObject offre una sintassi semplificata per lavorare con gli [eventi |nette:glossary#Eventi]. Gli eventi consentono agli oggetti di informare altre parti dell'applicazione sui cambiamenti del loro stato: ```php class Circle extends Nette\Object @@ -231,12 +220,12 @@ class Circle extends Nette\Object public function setRadius(float $radius): void { $this->onChange($this, $radius); - $this->radius = $radius + $this->radius = $radius; } } ``` -Il codice `$this->onChange($this, $radius)` è equivalente al seguente: +Il codice `$this->onChange($this, $radius)` è equivalente al seguente ciclo: ```php foreach ($this->onChange as $callback) { @@ -244,7 +233,7 @@ foreach ($this->onChange as $callback) { } ``` -Per motivi di chiarezza si consiglia di evitare il metodo magico `$this->onChange()`. Un sostituto pratico è la funzione [Nette\Utils\Arrays::invoke |arrays#invoke]: +Per motivi di chiarezza, si consiglia di evitare il metodo magico `$this->onChange()`. Un sostituto pratico è, ad esempio, la funzione [Nette\Utils\Arrays::invoke |arrays#invoke]: ```php Nette\Utils\Arrays::invoke($this->onChange, $this, $radius); diff --git a/utils/it/staticclass.texy b/utils/it/staticclass.texy new file mode 100644 index 0000000000..28d503681e --- /dev/null +++ b/utils/it/staticclass.texy @@ -0,0 +1,21 @@ +Classi statiche +*************** + +.[perex] +StaticClass serve per contrassegnare le classi statiche. + + +Installazione: + +```shell +composer require nette/utils +``` + +Le classi statiche, ovvero le classi che non sono destinate alla creazione di istanze, possono essere contrassegnate con il trait [api:Nette\StaticClass]: + +```php +class Strings +{ + use Nette\StaticClass; +} +``` diff --git a/utils/it/strings.texy b/utils/it/strings.texy index f3c27865d7..a156c2f3f6 100644 --- a/utils/it/strings.texy +++ b/utils/it/strings.texy @@ -1,8 +1,8 @@ -Funzioni di stringa -******************* +Lavorare con le stringhe +************************ .[perex] -[api:Nette\Utils\Strings] è una classe statica che contiene molte funzioni utili per lavorare con le stringhe codificate in UTF-8. +[api:Nette\Utils\Strings] è una classe statica con funzioni utili per lavorare con stringhe, principalmente in codifica UTF-8. Installazione: @@ -11,15 +11,15 @@ Installazione: composer require nette/utils ``` -Tutti gli esempi presuppongono che sia definito il seguente alias di classe: +Tutti gli esempi presuppongono la creazione di un alias: ```php use Nette\Utils\Strings; ``` -Lettera Caso .[#toc-letter-case] -================================ +Cambio di maiuscole/minuscole +============================= Queste funzioni richiedono l'estensione PHP `mbstring`. @@ -27,67 +27,67 @@ Queste funzioni richiedono l'estensione PHP `mbstring`. lower(string $s): string .[method] ---------------------------------- -Converte tutti i caratteri della stringa UTF-8 in minuscolo. +Converte una stringa UTF-8 in minuscolo. ```php -Strings::lower('Hello world'); // 'hello world' +Strings::lower('Dobrý den'); // 'dobrý den' ``` upper(string $s): string .[method] ---------------------------------- -Converte tutti i caratteri di una stringa UTF-8 in maiuscole. +Converte una stringa UTF-8 in maiuscolo. ```php -Strings::upper('Hello world'); // 'HELLO WORLD' +Strings::upper('Dobrý den'); // 'DOBRÝ DEN' ``` firstUpper(string $s): string .[method] --------------------------------------- -Converte il primo carattere di una stringa UTF-8 in maiuscolo e lascia invariati gli altri caratteri. +Converte la prima lettera di una stringa UTF-8 in maiuscolo, le altre rimangono invariate. ```php -Strings::firstUpper('hello world'); // 'Hello world' +Strings::firstUpper('dobrý den'); // 'Dobrý den' ``` firstLower(string $s): string .[method] --------------------------------------- -Converte il primo carattere di una stringa UTF-8 in minuscolo e lascia invariati gli altri caratteri. +Converte la prima lettera di una stringa UTF-8 in minuscolo, le altre rimangono invariate. ```php -Strings::firstLower('Hello world'); // 'hello world' +Strings::firstLower('Dobrý den'); // 'dobrý den' ``` capitalize(string $s): string .[method] --------------------------------------- -Converte il primo carattere di ogni parola di una stringa UTF-8 in maiuscolo e gli altri in minuscolo. +Converte la prima lettera di ogni parola in una stringa UTF-8 in maiuscolo, le altre in minuscolo. ```php -Strings::capitalize('Hello world'); // 'Hello World' +Strings::capitalize('Dobrý den'); // 'Dobrý Den' ``` -Modifica di una stringa .[#toc-editing-a-string] -================================================ +Modifica della stringa +====================== normalize(string $s): string .[method] -------------------------------------- -Rimuove i caratteri di controllo, normalizza le interruzioni di riga a `\n`, rimuove le righe vuote iniziali e finali, taglia gli spazi finali delle righe, normalizza UTF-8 alla forma normale di NFC. +Rimuove i caratteri di controllo, normalizza le terminazioni di riga a `\n`, rimuove le righe vuote iniziali e finali, rimuove gli spazi finali nelle righe, normalizza UTF-8 alla forma normale NFC. unixNewLines(string $s): string .[method] ----------------------------------------- -Converte le interruzioni di riga in `\n` usato nei sistemi Unix. Le interruzioni di riga sono: `\n`, `\r`, `\r\n`, U+2028 separatore di riga, U+2029 separatore di paragrafo. +Converte le terminazioni di riga in `\n` utilizzate nei sistemi Unix. Le terminazioni di riga sono: `\n`, `\r`, `\r\n`, U+2028 line separator, U+2029 paragraph separator. ```php $unixLikeLines = Strings::unixNewLines($string); @@ -97,42 +97,42 @@ $unixLikeLines = Strings::unixNewLines($string); platformNewLines(string $s): string .[method] --------------------------------------------- -Converte le interruzioni di riga in caratteri specifici della piattaforma corrente, ad esempio `\r\n` su Windows e `\n` altrove. Le interruzioni di riga sono `\n`, `\r`, `\r\n`, U+2028 separatore di riga, U+2029 separatore di paragrafo. +Converte le terminazioni di riga nei caratteri specifici della piattaforma corrente, cioè `\r\n` su Windows e `\n` altrove. Le terminazioni di riga sono: `\n`, `\r`, `\r\n`, U+2028 line separator, U+2029 paragraph separator. ```php $platformLines = Strings::platformNewLines($string); ``` -webalize(string $s, string $charlist=null, bool $lower=true): string .[method] ------------------------------------------------------------------------------- +webalize(string $s, ?string $charlist=null, bool $lower=true): string .[method] +------------------------------------------------------------------------------- -Modifica la stringa UTF-8 nella forma usata nell'URL, cioè rimuove i diacritici e sostituisce tutti i caratteri tranne le lettere dell'alfabeto inglese e i numeri con un trattino. +Modifica una stringa UTF-8 nella forma utilizzata negli URL, cioè rimuove i diacritici e sostituisce tutti i caratteri, ad eccezione delle lettere dell'alfabeto inglese e dei numeri, con un trattino. ```php -Strings::webalize('žluťoučký kůň'); // 'zlutoucky-kun' +Strings::webalize('náš produkt'); // 'nas-produkt' ``` -Anche altri caratteri possono essere conservati, ma devono essere passati come secondo argomento. +Se devono essere conservati anche altri caratteri, possono essere specificati nel secondo parametro della funzione. ```php -Strings::webalize('10. image_id', '._'); // '10.-image_id' +Strings::webalize('10. obrázek_id', '._'); // '10.-obrazek_id' ``` -Il terzo argomento può sopprimere la conversione della stringa in minuscolo. +Con il terzo parametro è possibile sopprimere la conversione in minuscolo. ```php -Strings::webalize('Hello world', null, false); // 'Hello-world' +Strings::webalize('Dobrý den', null, false); // 'Dobry-den' ``` .[caution] Richiede l'estensione PHP `intl`. -trim(string $s, string $charlist=null): string .[method] --------------------------------------------------------- +trim(string $s, ?string $charlist=null): string .[method] +--------------------------------------------------------- -Rimuove tutti gli spazi a sinistra e a destra (o i caratteri passati come secondo argomento) da una stringa codificata UTF-8. +Rimuove gli spazi (o altri caratteri specificati dal secondo parametro) dall'inizio e dalla fine di una stringa UTF-8. ```php Strings::trim(' Hello '); // 'Hello' @@ -142,21 +142,21 @@ Strings::trim(' Hello '); // 'Hello' truncate(string $s, int $maxLen, string $append=`'…'`): string .[method] ------------------------------------------------------------------------ -Tronca una stringa UTF-8 alla lunghezza massima indicata, cercando di non dividere le parole intere. Solo se la stringa viene troncata, alla stringa viene aggiunta un'ellissi (o qualcos'altro impostato con il terzo argomento). +Tronca una stringa UTF-8 alla lunghezza massima specificata, cercando di mantenere le parole intere. Se la stringa viene accorciata, aggiunge tre puntini alla fine (modificabile con il terzo parametro). ```php -$text = 'Hello, how are you today?'; -Strings::truncate($text, 5); // 'Hell…' -Strings::truncate($text, 20); // 'Hello, how are you…' -Strings::truncate($text, 30); // 'Hello, how are you today?' -Strings::truncate($text, 20, '~'); // 'Hello, how are you~' +$text = 'Řekněte, jak se máte?'; +Strings::truncate($text, 5); // 'Řekn…' +Strings::truncate($text, 20); // 'Řekněte, jak se…' +Strings::truncate($text, 30); // 'Řekněte, jak se máte?' +Strings::truncate($text, 20, '~'); // 'Řekněte, jak se~' ``` indent(string $s, int $level=1, string $indentationChar=`"\t"`): string .[method] --------------------------------------------------------------------------------- -Rientra un testo multilinea da sinistra. Il secondo argomento stabilisce il numero di caratteri di rientro da utilizzare, mentre il rientro stesso è il terzo argomento (*tab* per impostazione predefinita). +Indenta un testo multilinea da sinistra. Il numero di indentazioni è determinato dal secondo parametro, il carattere di indentazione dal terzo (il valore predefinito è il tabulatore). ```php Strings::indent('Nette'); // "\tNette" @@ -167,7 +167,7 @@ Strings::indent('Nette', 2, '+'); // '++Nette' padLeft(string $s, int $length, string $pad=`' '`): string .[method] -------------------------------------------------------------------- -Imbottisce una stringa UTF-8 alla lunghezza data, anteponendo la stringa `$pad` all'inizio. +Completa una stringa UTF-8 alla lunghezza specificata ripetendo la stringa `$pad` da sinistra. ```php Strings::padLeft('Nette', 6); // ' Nette' @@ -178,7 +178,7 @@ Strings::padLeft('Nette', 8, '+*'); // '+*+Nette' padRight(string $s, int $length, string $pad=`' '`): string .[method] --------------------------------------------------------------------- -Imbottisce una stringa UTF-8 alla lunghezza data aggiungendo la stringa `$pad` alla fine. +Completa una stringa UTF-8 alla lunghezza specificata ripetendo la stringa `$pad` da destra. ```php Strings::padRight('Nette', 6); // 'Nette ' @@ -186,10 +186,10 @@ Strings::padRight('Nette', 8, '+*'); // 'Nette+*+' ``` -substring(string $s, int $start, int $length=null): string .[method] --------------------------------------------------------------------- +substring(string $s, int $start, ?int $length=null): string .[method] +--------------------------------------------------------------------- -Restituisce una parte della stringa UTF-8 specificata dalla posizione iniziale `$start` e dalla lunghezza `$length`. Se `$start` è negativo, la stringa restituita inizierà a partire dal `$start`'esimo carattere dalla fine della stringa. +Restituisce una parte della stringa UTF-8 `$s` specificata dalla posizione iniziale `$start` e dalla lunghezza `$length`. Se `$start` è negativo, la stringa restituita inizierà dal carattere -`$start` dalla fine. ```php Strings::substring('Nette Framework', 0, 5); // 'Nette' @@ -201,7 +201,7 @@ Strings::substring('Nette Framework', -4); // 'work' reverse(string $s): string .[method] ------------------------------------ -Inverte la stringa UTF-8. +Inverte una stringa UTF-8. ```php Strings::reverse('Nette'); // 'etteN' @@ -211,77 +211,77 @@ Strings::reverse('Nette'); // 'etteN' length(string $s): int .[method] -------------------------------- -Restituisce il numero di caratteri (non di byte) nella stringa UTF-8. +Restituisce il numero di caratteri (non di byte) in una stringa UTF-8. -Si tratta del numero di punti di codice Unicode, che può differire dal numero di grafemi. +Questo è il numero di punti di codice Unicode, che può differire dal numero di grafemi. ```php -Strings::length('Nette'); // 5 -Strings::length('red'); // 3 +Strings::length('Nette'); // 5 +Strings::length('červená'); // 7 ``` startsWith(string $haystack, string $needle): bool .[method deprecated] ----------------------------------------------------------------------- -Controlla se la stringa `$haystack` inizia con `$needle`. +Verifica se la stringa `$haystack` inizia con la stringa `$needle`. ```php -$haystack = 'Begins'; -$needle = 'Be'; +$haystack = 'Začíná'; +$needle = 'Za'; Strings::startsWith($haystack, $needle); // true ``` .[note] -Utilizzare il nativo `str_starts_with()`:https://www.php.net/manual/en/function.str-starts-with.php. +Utilizzare la funzione nativa `str_starts_with()`:https://www.php.net/manual/en/function.str-starts-with.php. endsWith(string $haystack, string $needle): bool .[method deprecated] --------------------------------------------------------------------- -Controlla se la stringa `$haystack` termina con `$needle`. +Verifica se la stringa `$haystack` termina con la stringa `$needle`. ```php -$haystack = 'Ends'; -$needle = 'ds'; +$haystack = 'Končí'; +$needle = 'čí'; Strings::endsWith($haystack, $needle); // true ``` .[note] -Utilizzare `str_ends_with()` nativo:https://www.php.net/manual/en/function.str-ends-with.php. +Utilizzare la funzione nativa `str_ends_with()`:https://www.php.net/manual/en/function.str-ends-with.php. contains(string $haystack, string $needle): bool .[method deprecated] --------------------------------------------------------------------- -Controlla se la stringa `$haystack` contiene `$needle`. +Verifica se la stringa `$haystack` contiene `$needle`. ```php -$haystack = 'Contains'; -$needle = 'tai'; +$haystack = 'Posluchárna'; +$needle = 'sluch'; Strings::contains($haystack, $needle); // true ``` .[note] -Utilizzare `str_contains()` nativo :https://www.php.net/manual/en/function.str-contains.php. +Utilizzare la funzione nativa `str_contains()`:https://www.php.net/manual/en/function.str-contains.php. -compare(string $left, string $right, int $length=null): bool .[method] ----------------------------------------------------------------------- +compare(string $left, string $right, ?int $length=null): bool .[method] +----------------------------------------------------------------------- -Confronta due stringhe UTF-8 o le loro parti, senza tenere conto del caso dei caratteri. Se `$length` è nullo, vengono confrontate le stringhe intere, se è negativo, viene confrontato il numero corrispondente di caratteri dalla fine delle stringhe, altrimenti viene confrontato il numero appropriato di caratteri dall'inizio. +Confronto di due stringhe UTF-8 o delle loro parti senza distinzione tra maiuscole e minuscole. Se `$length` contiene null, vengono confrontate le stringhe intere, se è negativo, viene confrontato il numero corrispondente di caratteri dalla fine delle stringhe, altrimenti viene confrontato il numero corrispondente di caratteri dall'inizio. ```php Strings::compare('Nette', 'nette'); // true -Strings::compare('Nette', 'next', 2); // true - two first characters match -Strings::compare('Nette', 'Latte', -2); // true - two last characters match +Strings::compare('Nette', 'next', 2); // true - corrispondenza dei primi 2 caratteri +Strings::compare('Nette', 'Latte', -2); // true - corrispondenza degli ultimi 2 caratteri ``` findPrefix(...$strings): string .[method] ----------------------------------------- -Trova il prefisso comune delle stringhe o restituisce una stringa vuota se il prefisso non è stato trovato. +Trova l'inizio comune delle stringhe. Oppure restituisce una stringa vuota se non è stato trovato alcun prefisso comune. ```php Strings::findPrefix('prefix-a', 'prefix-bb', 'prefix-c'); // 'prefix-' @@ -293,7 +293,7 @@ Strings::findPrefix('Nette', 'is', 'great'); // '' before(string $haystack, string $needle, int $nth=1): ?string .[method] ----------------------------------------------------------------------- -Restituisce una parte di `$haystack` prima dell'occorrenza di `$nth` in `$needle` o restituisce `null` se l'ago non è stato trovato. Il valore negativo indica la ricerca dalla fine. +Restituisce la parte della stringa `$haystack` prima della n-esima `$nth` occorrenza della stringa `$needle`. Oppure `null` se `$needle` non è stato trovato. Con un valore negativo di `$nth`, la ricerca avviene dalla fine della stringa. ```php Strings::before('Nette_is_great', '_', 1); // 'Nette' @@ -306,7 +306,7 @@ Strings::before('Nette_is_great', '_', 3); // null after(string $haystack, string $needle, int $nth=1): ?string .[method] ---------------------------------------------------------------------- -Restituisce una parte di `$haystack` dopo l'occorrenza di `$nth` in `$needle` o restituisce `null` se `$needle` non è stato trovato. Il valore negativo di `$nth` indica la ricerca dalla fine. +Restituisce la parte della stringa `$haystack` dopo la n-esima `$nth` occorrenza della stringa `$needle`. Oppure `null` se `$needle` non è stato trovato. Con un valore negativo di `$nth`, la ricerca avviene dalla fine della stringa. ```php Strings::after('Nette_is_great', '_', 2); // 'great' @@ -319,7 +319,7 @@ Strings::after('Nette_is_great', '_', 3); // null indexOf(string $haystack, string $needle, int $nth=1): ?int .[method] --------------------------------------------------------------------- -Restituisce la posizione in caratteri di `$nth` in cui si trova `$needle` in `$haystack` o `null` se `$needle` non è stato trovato. Il valore negativo di `$nth` indica la ricerca dalla fine. +Restituisce la posizione in caratteri della n-esima `$nth` occorrenza della stringa `$needle` nella stringa `$haystack`. Oppure `null` se `$needle` non è stato trovato. Con un valore negativo di `$nth`, la ricerca avviene dalla fine della stringa. ```php Strings::indexOf('abc abc abc', 'abc', 2); // 4 @@ -328,14 +328,14 @@ Strings::indexOf('abc abc abc', 'd'); // null ``` -Codifica .[#toc-encoding] -========================= +Codifica +======== fixEncoding(string $s): string .[method] ---------------------------------------- -Rimuove tutti i caratteri UTF-8 non validi da una stringa. +Rimuove dalla stringa i caratteri UTF-8 non validi. ```php $correctStrings = Strings::fixEncoding($string); @@ -345,7 +345,7 @@ $correctStrings = Strings::fixEncoding($string); checkEncoding(string $s): bool .[method deprecated] --------------------------------------------------- -Verifica se la stringa è valida nella codifica UTF-8. +Verifica se si tratta di una stringa UTF-8 valida. ```php $isUtf8 = Strings::checkEncoding($string); @@ -358,7 +358,7 @@ Utilizzare [Nette\Utils\Validator::isUnicode() |validators#isUnicode]. toAscii(string $s): string .[method] ------------------------------------ -Converte le stringhe UTF-8 in ASCII, cioè rimuove i diacritici ecc. +Converte una stringa UTF-8 in ASCII, cioè rimuove i diacritici, ecc. ```php Strings::toAscii('žluťoučký kůň'); // 'zlutoucky kun' @@ -371,33 +371,33 @@ Richiede l'estensione PHP `intl`. chr(int $code): string .[method] -------------------------------- -Restituisce un carattere specifico in UTF-8 dal punto di codice (numero nell'intervallo 0x0000..D7FF o 0xE000..10FFFF). +Restituisce un carattere specifico in UTF-8 dal punto di codice (numero nell'intervallo 0x0000..D7FF e 0xE000..10FFFF). ```php -Strings::chr(0xA9); // '©' +Strings::chr(0xA9); // '©' in codifica UTF-8 ``` ord(string $char): int .[method] -------------------------------- -Restituisce un punto di codice di un carattere specifico in UTF-8 (numero nell'intervallo 0x0000..D7FF o 0xE000..10FFFF). +Restituisce il punto di codice di un carattere specifico in UTF-8 (numero nell'intervallo 0x0000..D7FF o 0xE000..10FFFF). ```php Strings::ord('©'); // 0xA9 ``` -Espressioni regolari .[#toc-regular-expressions] -================================================ +Espressioni regolari +==================== -La classe Strings fornisce funzioni per lavorare con le espressioni regolari. A differenza delle funzioni native di PHP, hanno un'API più comprensibile, un migliore supporto Unicode e, soprattutto, un rilevamento degli errori. Qualsiasi errore di compilazione o di elaborazione delle espressioni lancerà un'eccezione `Nette\RegexpException`. +La classe Strings offre funzioni per lavorare con le espressioni regolari. A differenza delle funzioni PHP native, dispongono di un'API più comprensibile, un migliore supporto Unicode e, soprattutto, il rilevamento degli errori. Qualsiasi errore durante la compilazione o l'elaborazione dell'espressione lancia un'eccezione `Nette\RegexpException`. split(string $subject, string $pattern, bool $captureOffset=false, bool $skipEmpty=false, int $limit=-1, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------- -Divide la stringa in array in base all'espressione regolare. Anche le espressioni tra parentesi vengono catturate e restituite. +Divide una stringa in un array secondo un'espressione regolare. Le espressioni tra parentesi verranno catturate e restituite anch'esse. ```php Strings::split('hello, world', '~,\s*~'); @@ -417,16 +417,16 @@ Strings::split('hello, world, ', '~,\s*~', skipEmpty: true); // ['hello', 'world'] ``` -Se viene specificato `$limit`, verranno restituite solo le sottostringhe fino al limite e il resto della stringa verrà inserito nell'ultimo elemento. Un limite di -1 o 0 significa nessun limite. +Se viene specificato `$limit`, verranno restituite solo le sottostringhe fino al limite e il resto della stringa verrà inserito nell'ultimo elemento. Un limite di -1 o 0 significa nessuna restrizione. ```php Strings::split('hello, world, third', '~,\s*~', limit: 2); // ['hello', 'world, third'] ``` -Se `$utf8` è `true`, la valutazione passa alla modalità Unicode. Questo è simile alla specificazione del modificatore `u`. +Se `$utf8` è `true`, la valutazione passa alla modalità Unicode. Simile a quando si specifica il modificatore `u`. -Se `$captureOffset` è `true`, per ogni corrispondenza che si verifica, viene restituita anche la sua posizione nella stringa (in byte; in caratteri se `$utf8` è impostato). Questo cambia il valore di ritorno in un array in cui ogni elemento è una coppia composta dalla stringa abbinata e dalla sua posizione. +Se `$captureOffset` è `true`, per ogni corrispondenza trovata verrà restituita anche la sua posizione nella stringa (in byte; se è impostato `$utf8`, in caratteri). Ciò modifica il valore di ritorno in un array in cui ogni elemento è una coppia composta dalla stringa corrispondente e dalla sua posizione. ```php Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true); @@ -440,7 +440,7 @@ Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true, utf8: true); match(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $utf8=false): ?array .[method] -------------------------------------------------------------------------------------------------------------------------------------------------- -Cerca nella stringa la parte corrispondente all'espressione regolare e restituisce un array con l'espressione trovata e le singole sottoespressioni, oppure `null`. +Cerca nella stringa una parte corrispondente all'espressione regolare e restituisce un array con l'espressione trovata e le singole sottoespressioni, oppure `null`. ```php Strings::match('hello!', '~\w+(!+)~'); @@ -450,7 +450,7 @@ Strings::match('hello!', '~X~'); // null ``` -Se `$unmatchedAsNull` è `true`, le sottoespressioni non corrispondenti vengono restituite come null; altrimenti vengono restituite come una stringa vuota o non vengono restituite: +Se `$unmatchedAsNull` è `true`, i sottogruppi non catturati vengono restituiti come null; altrimenti vengono restituiti come stringa vuota o non restituiti: ```php Strings::match('hello', '~\w+(!+)?~'); @@ -460,7 +460,7 @@ Strings::match('hello', '~\w+(!+)?~', unmatchedAsNull: true); // ['hello', null] ``` -Se `$utf8` è `true`, la valutazione passa alla modalità Unicode. Ciò è simile alla specificazione del modificatore `u`: +Se `$utf8` è `true`, la valutazione passa alla modalità Unicode. Simile a quando si specifica il modificatore `u`: ```php Strings::match('žlutý kůň', '~\w+~'); @@ -470,9 +470,9 @@ Strings::match('žlutý kůň', '~\w+~', utf8: true); // ['žlutý'] ``` -Il parametro `$offset` può essere usato per specificare la posizione da cui iniziare la ricerca (in byte; in caratteri se è impostato `$utf8` ). +Il parametro `$offset` può essere utilizzato per specificare la posizione da cui iniziare la ricerca (in byte; se è impostato `$utf8`, in caratteri). -Se `$captureOffset` è `true`, per ogni corrispondenza che si verifica, viene restituita anche la sua posizione nella stringa (in byte; in caratteri se `$utf8` è impostato). Questo cambia il valore di ritorno in un array in cui ogni elemento è una coppia composta dalla stringa corrispondente e dal suo offset: +Se `$captureOffset` è `true`, per ogni corrispondenza trovata verrà restituita anche la sua posizione nella stringa (in byte; se è impostato `$utf8`, in caratteri). Ciò modifica il valore di ritorno in un array in cui ogni elemento è una coppia composta dalla stringa corrispondente e dal suo offset: ```php Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true); @@ -483,10 +483,10 @@ Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true, utf8: true); ``` -matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false, bool $lazy=false): array|Generator .[method] +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Cerca nella stringa tutte le occorrenze che corrispondono all'espressione regolare e restituisce un array di array contenenti l'espressione trovata e ogni sottoespressione. +Cerca nella stringa tutte le occorrenze corrispondenti all'espressione regolare e restituisce un array di array con l'espressione trovata e le singole sottoespressioni. ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~'); @@ -496,7 +496,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~'); ] */ ``` -Se `$patternOrder` è `true`, la struttura dei risultati cambia in modo che il primo elemento è un array di corrispondenze complete del modello, il secondo è un array di stringhe corrispondenti alla prima sottoespressione tra parentesi e così via: +Se `$patternOrder` è `true`, la struttura dei risultati cambia in modo che il primo elemento sia un array di corrispondenze complete del pattern, il secondo sia un array di stringhe corrispondenti al primo sottogruppo tra parentesi, e così via: ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~', patternOrder: true); @@ -506,7 +506,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~', patternOrder: true); ] */ ``` -Se `$unmatchedAsNull` è `true`, i sottopattern senza corrispondenza vengono restituiti come null; altrimenti vengono restituiti come una stringa vuota o non vengono restituiti: +Se `$unmatchedAsNull` è `true`, i sottogruppi non catturati vengono restituiti come null; altrimenti vengono restituiti come stringa vuota o non restituiti: ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~', unmatchedAsNull: true); @@ -516,7 +516,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~', unmatchedAsNull: true); ] */ ``` -Se `$utf8` è `true`, la valutazione passa alla modalità Unicode. Ciò è simile alla specificazione del modificatore `u`: +Se `$utf8` è `true`, la valutazione passa alla modalità Unicode. Simile a quando si specifica il modificatore `u`: ```php Strings::matchAll('žlutý kůň', '~\w+~'); @@ -532,9 +532,9 @@ Strings::matchAll('žlutý kůň', '~\w+~', utf8: true); ] */ ``` -Il parametro `$offset` può essere usato per specificare la posizione da cui iniziare la ricerca (in byte; in caratteri se è impostato `$utf8` ). +Il parametro `$offset` può essere utilizzato per specificare la posizione da cui iniziare la ricerca (in byte; se è impostato `$utf8`, in caratteri). -Se `$captureOffset` è `true`, per ogni corrispondenza che si verifica, viene restituita anche la sua posizione nella stringa (in byte; in caratteri se `$utf8` è impostato). Questo cambia il valore di ritorno in un array in cui ogni elemento è una coppia composta dalla stringa corrispondente e dalla sua posizione: +Se `$captureOffset` è `true`, per ogni corrispondenza trovata verrà restituita anche la sua posizione nella stringa (in byte; se è impostato `$utf8`, in caratteri). Ciò modifica il valore di ritorno in un array in cui ogni elemento è una coppia composta dalla stringa corrispondente e dalla sua posizione: ```php Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true); @@ -550,11 +550,21 @@ Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true, utf8: true); ] */ ``` +Se `$lazy` è `true`, la funzione restituisce un `Generator` invece di un array, il che comporta significativi vantaggi in termini di prestazioni quando si lavora con stringhe di grandi dimensioni. Il generatore consente di cercare le corrispondenze gradualmente, invece dell'intera stringa contemporaneamente. Ciò consente di lavorare in modo efficiente anche con testi di input estremamente grandi. Inoltre, è possibile interrompere l'elaborazione in qualsiasi momento se si trova la corrispondenza cercata, risparmiando tempo di calcolo. + +```php +$matches = Strings::matchAll($largeText, '~\w+~', lazy: true); +foreach ($matches as $match) { + echo "Trovato: $match[0]\n"; + // L'elaborazione può essere interrotta in qualsiasi momento +} +``` + replace(string $subject, string|array $pattern, string|callable $replacement='', int $limit=-1, bool $captureOffset=false, bool $unmatchedAsNull=false, bool $utf8=false): string .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Sostituisce tutte le occorrenze che corrispondono all'espressione regolare. Il parametro `$replacement` è una maschera di stringa sostitutiva o un richiamo. +Sostituisce tutte le occorrenze corrispondenti all'espressione regolare. `$replacement` è una maschera di stringa di sostituzione o un callback. ```php Strings::replace('hello, world!', '~\w+~', '--'); @@ -564,7 +574,7 @@ Strings::replace('hello, world!', '~\w+~', fn($m) => strrev($m[0])); // 'olleh, dlrow!' ``` -La funzione consente anche sostituzioni multiple passando un array della forma `pattern => replacement` nel secondo parametro: +La funzione consente anche di eseguire più sostituzioni passando un array nel secondo parametro nella forma `pattern => replacement`: ```php Strings::replace('hello, world!', [ @@ -574,9 +584,9 @@ Strings::replace('hello, world!', [ // '-- --!' ``` -Il parametro `$limit` limita il numero di sostituzioni. Limite -1 significa nessun limite. +Il parametro `$limit` limita il numero di sostituzioni eseguite. Un limite di -1 significa nessuna restrizione. -Se `$utf8` è `true`, la valutazione passa alla modalità Unicode. Ciò è simile alla specificazione del modificatore `u`. +Se `$utf8` è `true`, la valutazione passa alla modalità Unicode. Simile a quando si specifica il modificatore `u`. ```php Strings::replace('žlutý kůň', '~\w+~', '--'); @@ -586,7 +596,7 @@ Strings::replace('žlutý kůň', '~\w+~', '--', utf8: true); // '-- --' ``` -Se `$captureOffset` è `true`, per ogni corrispondenza che si verifica, la sua posizione nella stringa (in byte; in caratteri se `$utf8` è impostato) viene passata anche al callback. Questo cambia la forma dell'array passato, dove ogni elemento è una coppia composta dalla stringa corrispondente e dalla sua posizione. +Se `$captureOffset` è `true`, per ogni corrispondenza trovata verrà passata al callback anche la sua posizione nella stringa (in byte; se è impostato `$utf8`, in caratteri). Ciò modifica la forma dell'array passato, dove ogni elemento è una coppia composta dalla stringa corrispondente e dalla sua posizione. ```php Strings::replace( @@ -595,7 +605,7 @@ Strings::replace( function (array $m) { dump($m); return ''; }, captureOffset: true, ); -// dumps [['lut', 2]] a [['k', 8]] +// dumps [['lut', 2]] e [['k', 8]] Strings::replace( 'žlutý kůň', @@ -604,10 +614,10 @@ Strings::replace( captureOffset: true, utf8: true, ); -// dumps [['žlutý', 0]] a [['kůň', 6]] +// dumps [['žlutý', 0]] e [['kůň', 6]] ``` -Se `$unmatchedAsNull` è `true`, i sottopattern non abbinati vengono passati alla callback come null; altrimenti vengono passati come una stringa vuota o non vengono passati: +Se `$unmatchedAsNull` è `true`, i sottogruppi non catturati vengono passati al callback come null; altrimenti vengono passati come stringa vuota o non passati: ```php Strings::replace( diff --git a/utils/it/type.texy b/utils/it/type.texy index c4f6a7191a..a118bf1f40 100644 --- a/utils/it/type.texy +++ b/utils/it/type.texy @@ -2,7 +2,7 @@ Tipo PHP ******** .[perex] -[api:Nette\Utils\Type] è una classe di tipi di dati PHP. +[api:Nette\Utils\Type] è una classe per lavorare con i tipi di dati PHP. Installazione: @@ -11,7 +11,7 @@ Installazione: composer require nette/utils ``` -Tutti gli esempi presuppongono che sia definito il seguente alias di classe: +Tutti gli esempi presuppongono la creazione di un alias: ```php use Nette\Utils\Type; @@ -21,7 +21,7 @@ use Nette\Utils\Type; fromReflection($reflection): ?Type .[method] -------------------------------------------- -Il metodo statico crea un oggetto Tipo basato sulla riflessione. Il parametro può essere un oggetto `ReflectionMethod` o `ReflectionFunction` (restituisce il tipo del valore di ritorno) o un oggetto `ReflectionParameter` o `ReflectionProperty`. Risolve `self`, `static` e `parent` al nome effettivo della classe. Se l'oggetto non ha un tipo, restituisce `null`. +Il metodo statico crea un oggetto Type basato sulla reflection. Il parametro può essere un oggetto `ReflectionMethod` o `ReflectionFunction` (restituisce il tipo del valore di ritorno) o `ReflectionParameter` o `ReflectionProperty`. Traduce `self`, `static` e `parent` nel nome effettivo della classe. Se il soggetto non ha alcun tipo, restituisce `null`. ```php class DemoClass @@ -37,7 +37,7 @@ echo Type::fromReflection($prop); // 'DemoClass' fromString(string $type): Type .[method] ---------------------------------------- -Il metodo statico crea l'oggetto Tipo secondo la notazione del testo. +Il metodo statico crea un oggetto Type basato sulla notazione testuale. ```php $type = Type::fromString('Foo|Bar'); @@ -48,10 +48,10 @@ echo $type; // 'Foo|Bar' getNames(): (string|array)[] .[method] -------------------------------------- -Restituisce l'array di sottotipi che compongono il tipo composto come stringhe. +Restituisce un array di sottotipi che compongono il tipo composto, come stringhe. ```php -$type = Type::fromString('string|null'); // nebo '?string' +$type = Type::fromString('string|null'); // o '?string' $type->getNames(); // ['string', 'null'] $type = Type::fromString('(Foo&Bar)|string'); @@ -62,10 +62,10 @@ $type->getNames(); // [['Foo', 'Bar'], 'string'] getTypes(): Type[] .[method] ---------------------------- -Restituisce l'array di sottotipi che compongono il tipo composto come oggetti `Type`: +Restituisce un array di sottotipi che compongono il tipo composto, come oggetti `Type`: ```php -$type = Type::fromString('string|null'); // or '?string' +$type = Type::fromString('string|null'); // o '?string' $type->getTypes(); // [Type::fromString('string'), Type::fromString('null')] $type = Type::fromString('(Foo&Bar)|string'); @@ -79,7 +79,7 @@ $type->getTypes(); // [Type::fromString('Foo'), Type::fromString('Bar')] getSingleName(): ?string .[method] ---------------------------------- -Restituisce il nome del tipo per i tipi semplici, altrimenti null. +Per i tipi semplici, restituisce il nome del tipo, altrimenti null. ```php $type = Type::fromString('string|null'); @@ -106,7 +106,7 @@ $type = Type::fromString('string'); $type->isSimple(); // true $type->isUnion(); // false -$type = Type::fromString('?Foo'); // nebo 'Foo|null' +$type = Type::fromString('?Foo'); // o 'Foo|null' $type->isSimple(); // true $type->isUnion(); // true ``` @@ -115,10 +115,10 @@ $type->isUnion(); // true isUnion(): bool .[method] ------------------------- -Restituisce se si tratta di un tipo union. +Restituisce se si tratta di un tipo unione. ```php -$type = Type::fromString('Foo&Bar'); +$type = Type::fromString('string|int'); $type->isUnion(); // true ``` @@ -126,11 +126,11 @@ $type->isUnion(); // true isIntersection(): bool .[method] -------------------------------- -Restituisce se si tratta di un tipo di intersezione. +Restituisce se si tratta di un tipo intersezione. ```php -$type = Type::fromString('string&int'); +$type = Type::fromString('Foo&Bar'); $type->isIntersection(); // true ``` @@ -138,7 +138,7 @@ $type->isIntersection(); // true isBuiltin(): bool .[method] --------------------------- -Restituisce se il tipo è sia un tipo semplice che un tipo incorporato in PHP. +Restituisce se il tipo è semplice e allo stesso tempo un tipo built-in di PHP. ```php $type = Type::fromString('string'); @@ -155,7 +155,7 @@ $type->isBuiltin(); // false isClass(): bool .[method] ------------------------- -Restituisce se il tipo è sia un tipo semplice che un nome di classe. +Restituisce se il tipo è semplice e allo stesso tempo un nome di classe. ```php $type = Type::fromString('string'); @@ -172,7 +172,7 @@ $type->isClass(); // false isClassKeyword(): bool .[method] -------------------------------- -Determina se il tipo è uno dei tipi interni `self`, `parent`, `static`. +Restituisce se il tipo è uno dei tipi interni `self`, `parent`, `static`. ```php $type = Type::fromString('self'); @@ -186,7 +186,7 @@ $type->isClassKeyword(); // false allows(string $type): bool .[method] ------------------------------------ -Il metodo `allows()` verifica la compatibilità dei tipi. Ad esempio, consente di verificare se un valore di un certo tipo può essere passato come parametro. +Il metodo `allows()` verifica la compatibilità dei tipi. Ad esempio, consente di determinare se un valore di un certo tipo potrebbe essere passato come parametro. ```php $type = Type::fromString('string|null'); diff --git a/utils/it/validators.texy b/utils/it/validators.texy index e76da8432a..4d5dcb3e9f 100644 --- a/utils/it/validators.texy +++ b/utils/it/validators.texy @@ -1,8 +1,8 @@ -Validatori di valore +Validatori di valori ******************** .[perex] -Avete bisogno di verificare rapidamente e facilmente che una variabile contenga, ad esempio, un indirizzo e-mail valido? Allora vi sarà utile [api:Nette\Utils\Validators], una classe statica con utili funzioni per la validazione dei valori. +Avete bisogno di verificare rapidamente e facilmente che una variabile contenga, ad esempio, un indirizzo email valido? Allora vi sarà utile [api:Nette\Utils\Validators], una classe statica con funzioni utili per la validazione dei valori. Installazione: @@ -11,17 +11,17 @@ Installazione: composer require nette/utils ``` -Tutti gli esempi presuppongono che sia definito il seguente alias di classe: +Tutti gli esempi presuppongono la creazione di un alias: ```php use Nette\Utils\Validators; ``` -Uso di base .[#toc-basic-usage] -=============================== +Utilizzo di base +================ -La classe `Validators` ha una serie di metodi per la validazione dei valori, come [isList() |#isList()], [isUnicode() |#isUnicode()], [isEmail() |#isEmail()], [isUrl() |#isUrl()], ecc. da utilizzare nel codice: +La classe dispone di numerosi metodi per il controllo dei valori, come [#isUnicode()], [#isEmail()], [#isUrl()] ecc. da utilizzare nel vostro codice: ```php if (!Validators::isEmail($email)) { @@ -29,7 +29,7 @@ if (!Validators::isEmail($email)) { } ``` -Inoltre, è in grado di verificare se il valore soddisfa i cosiddetti [tipi attesi |#expected types], ovvero una stringa in cui le singole opzioni sono separate da una barra verticale `|`. In questo modo è facile verificare i tipi di unione usando [if() |#if()]: +Inoltre, può verificare se un valore è uno dei cosiddetti [#tipi attesi], che è una stringa in cui le singole opzioni sono separate da una barra verticale `|`. Possiamo così verificare facilmente più tipi utilizzando [#is()]: ```php if (!Validators::is($val, 'int|string|bool')) { @@ -37,81 +37,81 @@ if (!Validators::is($val, 'int|string|bool')) { } ``` -Ma offre anche l'opportunità di creare un sistema in cui è necessario scrivere le aspettative come stringhe (ad esempio nelle annotazioni o nella configurazione) e poi verificarle in base ad esse. +Ma questo ci dà anche la possibilità di creare un sistema in cui è necessario scrivere le aspettative come stringhe (ad esempio in annotazioni o configurazioni) e poi verificare i valori in base ad esse. -Si possono anche dichiarare [asserzioni |#assert], che lanciano un'eccezione se non sono soddisfatte. +Sui tipi attesi si può anche porre un requisito [#assert()], che se non soddisfatto, lancia un'eccezione. -Tipi attesi .[#toc-expected-types] -================================== +Tipi attesi +=========== -Un tipo atteso è una stringa composta da una o più varianti separate da una barra verticale `|`, similar to writing types in PHP (ie. `'int|string|bool')`. Sono ammesse anche notazioni nulle `?int`. +I tipi attesi formano una stringa composta da una o più varianti separate da una barra verticale `|`, in modo simile a come vengono scritti i tipi in PHP (es. `'int|string|bool')`. Si accetta anche la notazione nullable `?int`. -Un array in cui tutti gli elementi sono di un certo tipo è scritto nella forma `int[]`. +Un array in cui tutti gli elementi sono di un certo tipo viene scritto nella forma `int[]`. -Alcuni tipi possono essere seguiti da due punti e dalla lunghezza `:length` o dall'intervallo `:[min]..[max]`ad esempio `string:10` (una stringa di lunghezza pari a 10 byte), `float:10..` (numero 10 o più grande), `array:..10` (array fino a dieci elementi) o `list:10..20` (elenco da 10 a 20 elementi), oppure un'espressione regolare per `pattern:[0-9]+`. +Dopo alcuni tipi può seguire un due punti e una lunghezza `:length` o un intervallo `:[min]..[max]`, ad esempio `string:10` (stringa di 10 byte), `float:10..` (numero 10 o maggiore), `array:..10` (array fino a dieci elementi) o `list:10..20` (lista con 10-20 elementi), oppure un'espressione regolare per `pattern:[0-9]+`. Panoramica dei tipi e delle regole: .[wide] -| Tipi di PHP || +| Tipi PHP || |-------------------------- -| `array` .{width: 140px} | è possibile indicare un intervallo per il numero di elementi -| `bool` | -| `float` | può essere dato un intervallo per il valore -| `int` | può essere dato un intervallo per il valore -| `null` | -| `object` | +| `array` .{width: 140px} | è possibile specificare un intervallo per il numero di elementi +| `bool` | +| `float` | è possibile specificare un intervallo per il valore +| `int` | è possibile specificare un intervallo per il valore +| `null` | +| `object` | | `resource` | -| `scalar` | int\float\bool\stringhe -| `string` | è possibile indicare un intervallo per la lunghezza in byte +| `scalar` | int\|float\|bool\|string +| `string` | è possibile specificare un intervallo per la lunghezza in byte | `callable` | | `iterable` | -| `mixed` | -|------------------------------------------------ +| `mixed` | +|-------------------------- | pseudo-tipi || |------------------------------------------------ -| `list` | [array indicizzato |#isList], può essere dato un intervallo per il numero di elementi -| `none` | valore vuoto: `''`, `null`, `false` -| `number` | int\\float -| `numeric` | [numero con rappresentazione testuale |#isNumeric] -| `numericint`| [numero intero comprensivo di rappresentazione testuale |#isNumericInt] -| `unicode` | [stringa UTF-8 |#isUnicode], è possibile indicare un intervallo per la lunghezza in caratteri -|------------------------------------------------ -| classe di caratteri (non può essere una stringa vuota) || -|------------------------------------------------ -| `alnum` | tutti i caratteri sono alfanumerici -| `alpha` | tutti i caratteri sono lettere `[A-Za-z]` -| `digit` | tutti i caratteri sono cifre -| `lower` | tutti i caratteri sono lettere minuscole `[a-z]` -| `space` | tutti i caratteri sono spazi -| `upper` | tutti i caratteri sono lettere maiuscole `[A-Z]` -| `xdigit` | tutti i caratteri sono cifre esadecimali `[0-9A-Fa-f]` -|------------------------------------------------ -| convalida della sintassi || +| `list` | array indicizzato, è possibile specificare un intervallo per il numero di elementi +| `none` | valore vuoto: `''`, `null`, `false` +| `number` | int\|float +| `numeric` | [numero inclusa la rappresentazione testuale |#isNumeric] +| `numericint`| [numero intero inclusa la rappresentazione testuale |#isNumericInt] +| `unicode` | [stringa UTF-8 |#isUnicode], è possibile specificare un intervallo per la lunghezza in caratteri +|-------------------------- +| classe di caratteri (non può essere una stringa vuota) || |------------------------------------------------ -| `pattern` | un'espressione regolare a cui l'intera stringa deve corrispondere -| `email` | [Email |#isEmail] -| `identifier`| [Identificatore PHP |#isPhpIdentifier] -| `url` | [URL |#isUrl] -| `uri` | [URI |#isUri] +| `alnum` | tutti i caratteri sono alfanumerici +| `alpha` | tutti i caratteri sono lettere `[A-Za-z]` +| `digit` | tutti i caratteri sono cifre +| `lower` | tutti i caratteri sono lettere minuscole `[a-z]` +| `space` | tutti i caratteri sono spazi +| `upper` | tutti i caratteri sono lettere maiuscole `[A-Z]` +| `xdigit` | tutti i caratteri sono cifre esadecimali `[0-9A-Fa-f]` +|-------------------------- +| validazione della sintassi || |------------------------------------------------ -| Convalida dell'ambiente +| `pattern` | espressione regolare a cui deve corrispondere l'**intera** stringa +| `email` | [E-mail |#isEmail] +| `identifier`| [identificatore PHP |#isPhpIdentifier] +| `url` | [URL |#isUrl] +| `uri` | [URI |#isUri] +|-------------------------- +| validazione dell'ambiente || |------------------------------------------------ -| `class` | è una classe esistente +| `class` | è una classe esistente | `interface` | è un'interfaccia esistente | `directory` | è una directory esistente -| `file` | è un file esistente +| `file` | è un file esistente -Asserzione .[#toc-assertion] -============================ +Asserzioni +========== assert($value, string $expected, string $label='variable'): void .[method] -------------------------------------------------------------------------- -Verifica che il valore sia dei [tipi previsti |#expected types], separati da pipe. In caso contrario, lancia l'eccezione [api:Nette\Utils\AssertionException]. La parola `variable` nel messaggio di eccezione può essere sostituita dal parametro `$label`. +Verifica che il valore sia uno dei [#tipi attesi] separati da una barra verticale. In caso contrario, lancia un'eccezione [api:Nette\Utils\AssertionException]. La parola `variable` nel testo dell'eccezione può essere sostituita con un'altra tramite il parametro `$label`. ```php Validators::assert('Nette', 'string:5'); // OK @@ -120,10 +120,10 @@ Validators::assert('Lorem ipsum dolor sit', 'string:78'); ``` -assertField(array $array, string|int $key, string $expected=null, string $label=null): void .[method] ------------------------------------------------------------------------------------------------------ +assertField(array $array, string|int $key, ?string $expected=null, ?string $label=null): void .[method] +------------------------------------------------------------------------------------------------------- -Verifica che l'elemento `$key` nell'array `$array` sia dei [tipi previsti |#expected types] separati da pipe. In caso contrario, lancia l'eccezione [api:Nette\Utils\AssertionException]. La stringa `item '%' in array` nel messaggio di eccezione può essere sostituita dal parametro `$label`. +Verifica se l'elemento con chiave `$key` nell'array `$array` è uno dei [#tipi attesi] separati da una barra verticale. In caso contrario, lancia un'eccezione [api:Nette\Utils\AssertionException]. La stringa `item '%' in array` nel testo dell'eccezione può essere sostituita con un'altra tramite il parametro `$label`. ```php $arr = ['foo' => 'Nette']; @@ -136,19 +136,19 @@ Validators::assertField($arr, 'foo', 'int'); ``` -Validatori .[#toc-validators] -============================= +Validatori +========== is($value, string $expected): bool .[method] -------------------------------------------- -Controlla se il valore è dei [tipi previsti |#expected types], separati da pipe. +Verifica se il valore è uno dei [#tipi attesi] separati da una barra verticale. ```php Validators::is(1, 'int|float'); // true Validators::is(23, 'int:0..10'); // false -Validators::is('Nette Framework', 'string:15'); // true, length is 15 bytes +Validators::is('Nette Framework', 'string:15'); // true, la lunghezza è di 15 byte Validators::is('Nette Framework', 'string:8..'); // true Validators::is('Nette Framework', 'string:30..40'); // false ``` @@ -157,7 +157,7 @@ Validators::is('Nette Framework', 'string:30..40'); // false isEmail(mixed $value): bool .[method] ------------------------------------- -Verifica che il valore sia un indirizzo e-mail valido. Non verifica l'effettiva esistenza del dominio, ma solo la sintassi. La funzione tiene conto anche dei [TLD |https://en.wikipedia.org/wiki/Top-level_domain] futuri, che potrebbero essere anche in unicode. +Verifica se il valore è un indirizzo email valido. Non viene verificato se il dominio esiste effettivamente, viene verificata solo la sintassi. La funzione tiene conto anche dei futuri [TLD|https://it.wikipedia.org/wiki/Dominio_di_primo_livello], che possono essere anche in unicode. ```php Validators::isEmail('example@nette.org'); // true @@ -169,7 +169,7 @@ Validators::isEmail('nette'); // false isInRange(mixed $value, array $range): bool .[method] ----------------------------------------------------- -Verifica se il valore è compreso nell'intervallo indicato `[min, max]`dove il limite superiore o inferiore può essere omesso (`null`). È possibile confrontare numeri, stringhe e oggetti DateTime. +Verifica se il valore è nell'intervallo specificato `[min, max]`, dove il limite superiore o inferiore può essere omesso (`null`). È possibile confrontare numeri, stringhe e oggetti DateTime. Se mancano entrambi i limiti (`[null, null]`) o il valore è `null`, restituisce `false`. @@ -184,7 +184,7 @@ Validators::isInRange(1, [5]); // false isNone(mixed $value): bool .[method] ------------------------------------ -Controlla se il valore è `0`, `''`, `false` o `null`. +Verifica se il valore è `0`, `''`, `false` o `null`. ```php Validators::isNone(0); // true @@ -198,7 +198,7 @@ Validators::isNone('nette'); // false isNumeric(mixed $value): bool .[method] --------------------------------------- -Controlla se il valore è un numero o un numero scritto in una stringa. +Verifica se il valore è un numero o un numero scritto in una stringa. ```php Validators::isNumeric(23); // true @@ -213,7 +213,7 @@ Validators::isNumeric('1e6'); // false isNumericInt(mixed $value): bool .[method] ------------------------------------------ -Controlla se il valore è un numero intero o un numero intero scritto in una stringa. +Verifica se il valore è un numero intero o un numero intero scritto in una stringa. ```php Validators::isNumericInt(23); // true @@ -227,7 +227,7 @@ Validators::isNumericInt('nette'); // false isPhpIdentifier(string $value): bool .[method] ---------------------------------------------- -Controlla se il valore è un identificatore sintatticamente valido in PHP, ad esempio per i nomi di classi, metodi, funzioni, ecc. +Verifica se il valore è un identificatore sintatticamente valido in PHP, ad esempio per nomi di classi, metodi, funzioni, ecc. ```php Validators::isPhpIdentifier(''); // false @@ -240,7 +240,7 @@ Validators::isPhpIdentifier('one two'); // false isBuiltinType(string $type): bool .[method] ------------------------------------------- -Determina se `$type` è un tipo incorporato in PHP. Altrimenti, è il nome della classe. +Verifica se `$type` è un tipo built-in di PHP. Altrimenti, è un nome di classe. ```php Validators::isBuiltinType('string'); // true @@ -251,7 +251,7 @@ Validators::isBuiltinType('Foo'); // false isTypeDeclaration(string $type): bool .[method] ----------------------------------------------- -Controlla se la dichiarazione del tipo è sintatticamente corretta. +Verifica se la dichiarazione di tipo specificata è sintatticamente valida. ```php Validators::isTypeDeclaration('?string'); // true @@ -268,7 +268,7 @@ Validators::isTypeDeclaration('(A|B)'); // false isClassKeyword(string $type): bool .[method] -------------------------------------------- -Determina se `$type` è uno dei tipi interni `self`, `parent`, `static`. +Verifica se `$type` è uno dei tipi interni `self`, `parent`, `static`. ```php Validators::isClassKeyword('self'); // true @@ -291,7 +291,7 @@ Validators::isUnicode("\xA0"); // false isUrl(mixed $value): bool .[method] ----------------------------------- -Controlla se il valore è un indirizzo URL valido. +Verifica se il valore è un indirizzo URL valido. ```php Validators::isUrl('https://nette.org:8080/path?query#fragment'); // true @@ -306,7 +306,7 @@ Validators::isUrl('nette.org'); // false isUri(string $value): bool .[method] ------------------------------------ -Verifica che il valore sia un indirizzo URI valido, cioè una stringa che inizia con uno schema sintatticamente valido. +Verifica se il valore è un indirizzo URI valido, cioè una stringa che inizia con uno schema sintatticamente valido. ```php Validators::isUri('https://nette.org'); // true diff --git a/utils/ja/@home.texy b/utils/ja/@home.texy new file mode 100644 index 0000000000..aba49c9e3d --- /dev/null +++ b/utils/ja/@home.texy @@ -0,0 +1,46 @@ +Nette Utils +*********** + +.[perex] +`nette/utils`パッケージには、日常的に使用できる便利なクラスのセットが含まれています: + +| [コールバック |Callback] | Nette\Utils\Callback +| [日付と時刻 |datetime] | Nette\Utils\DateTime +| [Finder |Finder] | Nette\Utils\Finder +| [HTML 要素 |html-elements] | Nette\Utils\Html +| [イテレータ |iterables] | Nette\Utils\Iterables +| [JSON |JSON] | Nette\Utils\Json +| [ランダム文字列 |random] | Nette\Utils\Random +| [画像 |images] | Nette\Utils\Image +| [PHP リフレクション |reflection] | Nette\Utils\Reflection +| [PHP 型 |type] | Nette\Utils\Type +| [配列 |arrays] | Nette\Utils\Arrays +| [ヘルパー関数 |helpers] | Nette\Utils\Helpers +| [浮動小数点数の比較 |floats] | Nette\Utils\Floats +| [文字列 |strings] | Nette\Utils\Strings +| [ファイルシステム |filesystem] | Nette\Utils\FileSystem +| [ページネーション |paginator] | Nette\Utils\Paginator +| [SmartObject |SmartObject] & [StaticClass |StaticClass] | Nette\SmartObject & Nette\StaticClass +| [バリデータ |validators] | Nette\Utils\Validators + + +インストール +------ + +ライブラリは[Composer|best-practices:composer]ツールを使用してダウンロードおよびインストールします: + +```shell +composer require nette/utils +``` + +| バージョン | PHPとの互換性 +|-----------|------------------- +| Nette Utils 4.0 | PHP 8.0 – 8.4 +| Nette Utils 3.2 | PHP 7.2 – 8.3 +| Nette Utils 3.0 – 3.1 | PHP 7.1 – 8.0 +| Nette Utils 2.5 | PHP 5.6 – 8.0 + +最新のパッチバージョンに適用されます。 + + +パッケージを新しいバージョンに更新する場合は、[アップグレード|en:upgrading]ページを参照してください。 diff --git a/utils/ja/@left-menu.texy b/utils/ja/@left-menu.texy new file mode 100644 index 0000000000..dfe43433b1 --- /dev/null +++ b/utils/ja/@left-menu.texy @@ -0,0 +1,28 @@ +Nette Utils +*********** +- [コールバック |callback] +- [日付と時刻 |datetime] +- [Finder |Finder] +- [Floats |Floats] +- [HTML 要素 |html-elements] +- [イテレータ |iterables] +- [JSON |JSON] +- [ランダム文字列 |random] +- [画像 |images] +- [ページネータ |paginator] +- [PHP リフレクション |reflection] +- [PHP 型 |type] +- [配列 |arrays] +- [ヘルパー関数 |helpers] +- [文字列 |strings] +- [SmartObject |SmartObject] +- [StaticClass |StaticClass] +- [ファイルシステム |filesystem] +- [バリデータ |validators] + + +その他のツール +******* +- [NEON|neon:] +- [パスワードハッシュ化 |security:passwords] +- [URL の解析と構築 |http:urls] diff --git a/utils/ja/@meta.texy b/utils/ja/@meta.texy new file mode 100644 index 0000000000..d3c41dc3d7 --- /dev/null +++ b/utils/ja/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette ドキュメンテーション}} diff --git a/utils/ja/arrays.texy b/utils/ja/arrays.texy new file mode 100644 index 0000000000..090e2f6646 --- /dev/null +++ b/utils/ja/arrays.texy @@ -0,0 +1,561 @@ +配列の操作 +***** + +.[perex] +このページは、配列に関連するクラス [Nette\Utils\Arrays |#Arrays]、[#ArrayHash]、[#ArrayList] に焦点を当てています。 + + +インストール: + +```shell +composer require nette/utils +``` + + +Arrays +====== + +[api:Nette\Utils\Arrays |api:Nette\Utils\Arrays] は、配列を操作するための便利な関数を含む静的クラスです。イテレータに対するその類似物は [Nette\Utils\Iterables |iterables] です。 + +以下の例では、エイリアスが作成されていることを前提としています: + +```php +use Nette\Utils\Arrays; +``` + + +associate(array $array, mixed $path): array|\stdClass .[method] +--------------------------------------------------------------- + +関数は、指定されたパス `$path` に従って配列 `$array` を連想配列またはオブジェクトに柔軟に変換します。パスは文字列または配列にすることができます。入力配列のキー名と '[]', '->', '=', '|' のような演算子で構成されます。パスが無効な場合、`Nette\InvalidArgumentException` をスローします。 + +```php +// 単純なキーによる連想配列への変換 +$arr = [ + ['name' => 'John', 'age' => 11], + ['name' => 'Mary', 'age' => null], + // ... +]; +$result = Arrays::associate($arr, 'name'); +// $result = ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// = 演算子を使用した、あるキーから別のキーへの値の割り当て +$result = Arrays::associate($arr, 'name=age'); // または ['name', '=', 'age'] +// $result = ['John' => 11, 'Mary' => null, ...] +``` + +```php +// -> 演算子を使用したオブジェクトの作成 +$result = Arrays::associate($arr, '->name'); // または ['->', 'name'] +// $result = (object) ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// | 演算子を使用したキーの組み合わせ +$result = Arrays::associate($arr, 'name|age'); // または ['name', '|', 'age'] +// $result: ['John' => ['name' => 'John', 'age' => 11], 'Paul' => ['name' => 'Paul', 'age' => 44]] +``` + +```php +// [] を使用した配列への追加 +$result = Arrays::associate($arr, 'name[]'); // または ['name', '[]'] +// $result: ['John' => [['name' => 'John', 'age' => 22], ['name' => 'John', 'age' => 11]]] +``` + + +contains(array $array, $value): bool .[method] +---------------------------------------------- + +配列に値が存在するかどうかをテストします。厳密な比較 (`===`) を使用します。 + +```php +Arrays::contains([1, 2, 3], 1); // true +Arrays::contains(['1', false], 1); // false +``` + + +every(array $array, callable $predicate): bool .[method] +-------------------------------------------------------- + +配列内のすべての要素が、シグネチャ `function ($value, $key, array $array): bool` を持つ `$predicate` に実装されたテストに合格するかどうかをテストします。 + +```php +$array = [1, 30, 39, 29, 10, 13]; +$isBelowThreshold = fn($value) => $value < 40; +$res = Arrays::every($array, $isBelowThreshold); // true +``` + +[#some()] を参照。 + + +filter(array $array, callable $predicate): array .[method]{data-version:4.0.4} +------------------------------------------------------------------------------ + +指定された述語に一致するすべてのキーと値のペアを含む新しい配列を返します。コールバックはシグネチャ `function ($value, int|string $key, array $array): bool` を持ちます。 + +```php +Arrays::filter( + ['a' => 1, 'b' => 2, 'c' => 3], + fn($v) => $v < 3, +); +// ['a' => 1, 'b' => 2] +``` + + +first(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------- + +最初の項目(指定されている場合、述語に一致するもの)を返します。そのような項目が存在しない場合、`$else` の呼び出し結果または null を返します。 パラメータ `$predicate` はシグネチャ `function ($value, int|string $key, array $array): bool` を持ちます。 + +`reset()` とは異なり、内部ポインタを変更しません。パラメータ `$predicate` と `$else` はバージョン4.0.4から存在します。 + +```php +Arrays::first([1, 2, 3]); // 1 +Arrays::first([1, 2, 3], fn($v) => $v > 2); // 3 +Arrays::first([]); // null +Arrays::first([], else: fn() => false); // false +``` + +[#last()] を参照。 + + +firstKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +------------------------------------------------------------------------------------------------ + +最初の項目(指定されている場合、述語に一致するもの)のキー、またはそのような項目が存在しない場合は null を返します。述語 `$predicate` はシグネチャ `function ($value, int|string $key, array $array): bool` を持ちます。 + +```php +Arrays::firstKey([1, 2, 3]); // 0 +Arrays::firstKey([1, 2, 3], fn($v) => $v > 2); // 2 +Arrays::firstKey(['a' => 1, 'b' => 2]); // 'a' +Arrays::firstKey([]); // null +``` + +[#lastKey()] を参照。 + + +flatten(array $array, bool $preserveKeys=false): array .[method] +---------------------------------------------------------------- + +多次元配列をフラットな配列に統合します。 + +```php +$array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); +// $array = [1, 2, 3, 4, 5, 6]; +``` + + +get(array $array, string|int|array $key, ?mixed $default=null): mixed .[method] +------------------------------------------------------------------------------- + +要素 `$array[$key]` を返します。存在しない場合、例外 `Nette\InvalidArgumentException` をスローするか、3番目のパラメータ `$default` が指定されている場合はそれを返します。 + +```php +// $array['foo'] が存在しない場合、例外をスローします +$value = Arrays::get($array, 'foo'); + +// $array['foo'] が存在しない場合、'bar' を返します +$value = Arrays::get($array, 'foo', 'bar'); +``` + +キー `$key` は配列にすることもできます。 + +```php +$array = ['color' => ['favorite' => 'red'], 5]; + +$value = Arrays::get($array, ['color', 'favorite']); +// 'red' を返します +``` + + +getRef(array &$array, string|int|array $key): mixed .[method] +------------------------------------------------------------- + +指定された配列要素への参照を取得します。要素が存在しない場合、null値で作成されます。 + +```php +$valueRef = & Arrays::getRef($array, 'foo'); +// $array['foo'] への参照を返します +``` + +[#get()] 関数と同様に、多次元配列を扱うことができます。 + +```php +$value = & Arrays::getRef($array, ['color', 'favorite']); +// $array['color']['favorite'] への参照を返します +``` + + +grep(array $array, string $pattern, bool $invert=false): array .[method] +------------------------------------------------------------------------ + +その値が正規表現 `$pattern` に一致する配列の要素のみを返します。`$invert` が `true` の場合、逆に一致しない要素を返します。式のコンパイルまたは処理中のエラーは、例外 `Nette\RegexpException` をスローします。 + +```php +$filteredArray = Arrays::grep($array, '~^\d+$~'); +// 数字のみで構成される配列要素のみを返します +``` + + +insertAfter(array &$array, string|int|null $key, array $inserted): void .[method] +--------------------------------------------------------------------------------- + +配列 `$inserted` の内容を、キー `$key` を持つ要素の直後に配列 `$array` に挿入します。`$key` が `null` の場合(または配列内にない場合)、末尾に挿入されます。 + +```php +$array = ['first' => 10, 'second' => 20]; +Arrays::insertAfter($array, 'first', ['hello' => 'world']); +// $array = ['first' => 10, 'hello' => 'world', 'second' => 20]; +``` + + +insertBefore(array &$array, string|int|null $key, array $inserted): void .[method] +---------------------------------------------------------------------------------- + +配列 `$inserted` の内容を、キー `$key` を持つ要素の前に配列 `$array` に挿入します。`$key` が `null` の場合(または配列内にない場合)、先頭に挿入されます。 + +```php +$array = ['first' => 10, 'second' => 20]; +Arrays::insertBefore($array, 'first', ['hello' => 'world']); +// $array = ['hello' => 'world', 'first' => 10, 'second' => 20]; +``` + + +invoke(iterable $callbacks, ...$args): array .[method] +------------------------------------------------------ + +すべてのコールバックを呼び出し、結果の配列を返します。 + +```php +$callbacks = [ + '+' => fn($a, $b) => $a + $b, + '*' => fn($a, $b) => $a * $b, +]; + +$array = Arrays::invoke($callbacks, 5, 11); +// $array = ['+' => 16, '*' => 55]; +``` + + +invokeMethod(iterable $objects, string $method, ...$args): array .[method] +-------------------------------------------------------------------------- + +配列内の各オブジェクトに対してメソッドを呼び出し、結果の配列を返します。 + +```php +$objects = ['a' => $obj1, 'b' => $obj2]; + +$array = Arrays::invokeMethod($objects, 'foo', 1, 2); +// $array = ['a' => $obj1->foo(1, 2), 'b' => $obj2->foo(1, 2)]; +``` + + +isList(array $array): bool .[method] +------------------------------------ + +配列がゼロから始まる昇順の数値キーに従ってインデックス付けされているかどうか(別名 list)を検証します。 + +```php +Arrays::isList(['a', 'b', 'c']); // true +Arrays::isList([4 => 1, 2, 3]); // false +Arrays::isList(['a' => 1, 'b' => 2]); // false +``` + + +last(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------ + +最後の項目(指定されている場合、述語に一致するもの)を返します。そのような項目が存在しない場合、`$else` の呼び出し結果または null を返します。 パラメータ `$predicate` はシグネチャ `function ($value, int|string $key, array $array): bool` を持ちます。 + +`end()` とは異なり、内部ポインタを変更しません。パラメータ `$predicate` と `$else` はバージョン4.0.4から存在します。 + +```php +Arrays::last([1, 2, 3]); // 3 +Arrays::last([1, 2, 3], fn($v) => $v < 3); // 2 +Arrays::last([]); // null +Arrays::last([], else: fn() => false); // false +``` + +[#first()] を参照。 + + +lastKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +----------------------------------------------------------------------------------------------- + +最後の項目(指定されている場合、述語に一致するもの)のキー、またはそのような項目が存在しない場合は null を返します。述語 `$predicate` はシグネチャ `function ($value, int|string $key, array $array): bool` を持ちます。 + +```php +Arrays::lastKey([1, 2, 3]); // 2 +Arrays::lastKey([1, 2, 3], fn($v) => $v < 3); // 1 +Arrays::lastKey(['a' => 1, 'b' => 2]); // 'b' +Arrays::lastKey([]); // null +``` + +[#firstKey()] を参照。 + + +map(array $array, callable $transformer): array .[method] +--------------------------------------------------------- + +配列内のすべての要素に対して `$transformer` を呼び出し、返された値の配列を返します。コールバックはシグネチャ `function ($value, $key, array $array): mixed` を持ちます。 + +```php +$array = ['foo', 'bar', 'baz']; +$res = Arrays::map($array, fn($value) => $value . $value); +// $res = ['foofoo', 'barbar', 'bazbaz'] +``` + + +mapWithKeys(array $array, callable $transformer): array .[method] +----------------------------------------------------------------- + +元の配列の値とキーを変換して新しい配列を作成します。関数 `$transformer` はシグネチャ `function ($value, $key, array $array): ?array{$newKey, $newValue}` を持ちます。`$transformer` が `null` を返した場合、要素はスキップされます。保持された要素については、返された配列の最初の要素が新しいキーとして使用され、2番目の要素が新しい値として使用されます。 + +```php +$array = ['a' => 1, 'b' => 2]; +$result = Arrays::mapWithKeys($array, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B', 6 => 'C'] +``` + +このメソッドは、配列の構造(キーと値を同時に)を変更する必要がある場合や、変換中に要素をフィルタリングする必要がある場合(不要な要素に対してnullを返すことで)に役立ちます。 + + +mergeTree(array $array1, array $array2): array .[method] +-------------------------------------------------------- + +2つの配列を再帰的にマージします。例えば、ツリー構造のマージに適しています。マージ時には、配列に適用される `+` 演算子のように同じルールに従います。つまり、最初の配列に2番目の配列のキー/値ペアを追加し、キーが衝突した場合は、最初の配列の値を保持します。 + +```php +$array1 = ['color' => ['favorite' => 'red'], 5]; +$array2 = [10, 'color' => ['favorite' => 'green', 'blue']]; + +$array = Arrays::mergeTree($array1, $array2); +// $array = ['color' => ['favorite' => 'red', 'blue'], 5]; +``` + +2番目の配列の値は常に最初の配列の末尾に追加されます。2番目の配列から値 `10` が消えることは、少し混乱を招くかもしれません。認識する必要があります。この値と、最初の配列の値 `5` は、同じ数値キー `0` が割り当てられています。そのため、結果の配列には最初の配列の要素のみが含まれます。 + + +normalize(array $array, ?string $filling=null): array .[method] +--------------------------------------------------------------- + +配列を連想配列に正規化します。数値キーはその値で置き換えられ、新しい値は `$filling` になります。 + +```php +$array = Arrays::normalize([1 => 'first', 'a' => 'second']); +// $array = ['first' => null, 'a' => 'second']; +``` + +```php +$array = Arrays::normalize([1 => 'first', 'a' => 'second'], 'foobar'); +// $array = ['first' => 'foobar', 'a' => 'second']; +``` + + +pick(array &$array, string|int $key, ?mixed $default=null): mixed .[method] +--------------------------------------------------------------------------- + +配列から要素の値を返して削除します。存在しない場合は例外をスローするか、`$default` が指定されている場合はその値を返します。 + +```php +$array = [1 => 'foo', 'x' => 'bar']; +$a = Arrays::pick($array, 'x'); +// $a = 'bar' +$b = Arrays::pick($array, 'not-exists', 'foobar'); +// $b = 'foobar' +$c = Arrays::pick($array, 'not-exists'); +// Nette\InvalidArgumentException をスローします +``` + + +renameKey(array &$array, string|int $oldKey, string|int $newKey): bool .[method] +-------------------------------------------------------------------------------- + +配列内のキーの名前を変更します。キーが配列内で見つかった場合に `true` を返します。 + +```php +$array = ['first' => 10, 'second' => 20]; +Arrays::renameKey($array, 'first', 'renamed'); +// $array = ['renamed' => 10, 'second' => 20]; +``` + + +getKeyOffset(array $array, string|int $key): ?int .[method] +----------------------------------------------------------- + +配列内の指定されたキーの位置を返します。位置は0から番号付けされます。キーが見つからない場合、関数は `null` を返します。 + +```php +$array = ['first' => 10, 'second' => 20]; +$position = Arrays::getKeyOffset($array, 'first'); // 0 を返します +$position = Arrays::getKeyOffset($array, 'second'); // 1 を返します +$position = Arrays::getKeyOffset($array, 'not-exists'); // null を返します +``` + + +some(array $array, callable $predicate): bool .[method] +------------------------------------------------------- + +配列内の少なくとも1つの要素が、シグネチャ `function ($value, $key, array $array): bool` を持つ `$predicate` に実装されたテストに合格するかどうかをテストします。 + +```php +$array = [1, 2, 3, 4]; +$isEven = fn($value) => $value % 2 === 0; +$res = Arrays::some($array, $isEven); // true +``` + +[#every()] を参照。 + + +toKey(mixed $key): string|int .[method] +--------------------------------------- + +値を配列キー(整数または文字列のいずれか)に変換します。 + +```php +Arrays::toKey('1'); // 1 +Arrays::toKey('01'); // '01' +``` + + +toObject(iterable $array, object $object): object .[method] +----------------------------------------------------------- + +配列 `$array` の要素をオブジェクト `$object` にコピーし、その後、それを返します。 + +```php +$obj = new stdClass; +$array = ['foo' => 1, 'bar' => 2]; +Arrays::toObject($array, $obj); // $obj->foo = 1; $obj->bar = 2; を設定します +``` + + +wrap(array $array, string $prefix='', string $suffix=''): array .[method] +------------------------------------------------------------------------- + +配列内の各項目を文字列にキャストし、接頭辞 `$prefix` と接尾辞 `$suffix` で囲みます。 + +```php +$array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); +// $array = ['a' => '<>', 'b' => '<>']; +``` + + +ArrayHash +========= + +オブジェクト [api:Nette\Utils\ArrayHash] はジェネリッククラス stdClass の子孫であり、それを配列のように扱う能力(つまり、例えば角括弧を通してメンバーにアクセスする)で拡張します: + +```php +$hash = new Nette\Utils\ArrayHash; +$hash['foo'] = 123; +$hash->bar = 456; // 同時にオブジェクト記法も機能します +$hash->foo; // 123 +``` + +`count($hash)` 関数を使用して要素の数を取得できます。 + +オブジェクトに対して、配列の場合と同様に、参照を使用しても反復処理が可能です: + +```php +foreach ($hash as $key => $value) { + // ... +} + +foreach ($hash as $key => &$value) { + $value = 'new value'; +} +``` + +既存の配列を `from()` メソッドを使用して `ArrayHash` に変換できます: + +```php +$array = ['foo' => 123, 'bar' => 456]; + +$hash = Nette\Utils\ArrayHash::from($array); +$hash->foo; // 123 +$hash->bar; // 456 +``` + +変換は再帰的です: + +```php +$array = ['foo' => 123, 'inner' => ['a' => 'b']]; + +$hash = Nette\Utils\ArrayHash::from($array); +$hash->inner; // ArrayHashオブジェクト +$hash->inner->a; // 'b' +$hash['inner']['a']; // 'b' +``` + +これは2番目のパラメータで防ぐことができます: + +```php +$hash = Nette\Utils\ArrayHash::from($array, false); +$hash->inner; // 配列 +``` + +配列への逆変換: + +```php +$array = (array) $hash; +``` + + +ArrayList +========= + +[api:Nette\Utils\ArrayList] は線形配列を表し、インデックスは0から始まる昇順の整数のみです。 + +```php +$list = new Nette\Utils\ArrayList; +$list[] = 'a'; +$list[] = 'b'; +$list[] = 'c'; +// ArrayList(0 => 'a', 1 => 'b', 2 => 'c') +count($list); // 3 +``` + +既存の配列を `from()` メソッドを使用して `ArrayList` に変換できます: + +```php +$array = ['foo', 'bar']; +$list = Nette\Utils\ArrayList::from($array); +``` + +`count($list)` 関数を使用して要素の数を取得できます。 + +オブジェクトに対して、配列の場合と同様に、参照を使用しても反復処理が可能です: + +```php +foreach ($list as $key => $value) { + // ... +} + +foreach ($list as $key => &$value) { + $value = 'new value'; +} +``` + +許可された値以外のキーへのアクセスは、例外 `Nette\OutOfRangeException` をスローします: + +```php +echo $list[-1]; // Nette\OutOfRangeException をスローします +unset($list[30]); // Nette\OutOfRangeException をスローします +``` + +キーの削除は要素の再番号付けを引き起こします: + +```php +unset($list[1]); +// ArrayList(0 => 'a', 1 => 'c') +``` + +新しい要素を `prepend()` メソッドを使用して先頭に追加できます: + +```php +$list->prepend('d'); +// ArrayList(0 => 'd', 1 => 'a', 2 => 'c') +``` diff --git a/utils/ja/callback.texy b/utils/ja/callback.texy new file mode 100644 index 0000000000..82f89d7ffd --- /dev/null +++ b/utils/ja/callback.texy @@ -0,0 +1,81 @@ +コールバックの操作 +********* + +.[perex] +[api:Nette\Utils\Callback] は、[PHPコールバック |https://www.php.net/manual/en/language.types.callable.php] を操作するための関数を持つ静的クラスです。 + + +インストール: + +```shell +composer require nette/utils +``` + +以下の例では、エイリアスが作成されていることを前提としています: + +```php +use Nette\Utils\Callback; +``` + + +check($callable, bool $syntax=false): callable .[method] +-------------------------------------------------------- + +変数 `$callable` が有効なコールバックであるかどうかをチェックします。そうでない場合は `Nette\InvalidArgumentException` をスローします。`$syntax` が true の場合、関数は単に `$callable` がコールバックの構造を持っていることを検証しますが、指定されたクラスやメソッドが実際に存在するかどうかは検証しません。`$callable` を返します。 + +```php +Callback::check('trim'); // 例外をスローしません +Callback::check(['NonExistentClass', 'method']); // Nette\InvalidArgumentException をスローします +Callback::check(['NonExistentClass', 'method'], true); // 例外をスローしません +Callback::check(function () {}); // 例外をスローしません +Callback::check(null); // Nette\InvalidArgumentException をスローします +``` + + +toString($callable): string .[method] +------------------------------------- + +PHPコールバックをテキスト形式に変換します。クラスやメソッドは存在する必要はありません。 + +```php +Callback::toString('trim'); // 'trim' +Callback::toString(['MyClass', 'method']); // 'MyClass::method' +``` + + +toReflection($callable): ReflectionMethod|ReflectionFunction .[method] +---------------------------------------------------------------------- + +PHPコールバック内のメソッドまたは関数のリフレクションを返します。 + +```php +$ref = Callback::toReflection('trim'); +// $ref は ReflectionFunction('trim') です + +$ref = Callback::toReflection(['MyClass', 'method']); +// $ref は ReflectionMethod('MyClass', 'method') です +``` + + +isStatic($callable): bool .[method] +----------------------------------- + +PHPコールバックが関数または静的メソッドであるかどうかを判定します。 + +```php +Callback::isStatic('trim'); // true +Callback::isStatic(['MyClass', 'method']); // true +Callback::isStatic([$obj, 'method']); // false +Callback::isStatic(function () {}); // false +``` + + +unwrap(Closure $closure): callable|array .[method] +-------------------------------------------------- + +`Closure::fromCallable`:https://www.php.net/manual/en/closure.fromcallable.php を使用して作成されたClosureを逆展開します。 + +```php +$closure = Closure::fromCallable(['MyClass', 'method']); +Callback::unwrap($closure); // ['MyClass', 'method'] +``` diff --git a/utils/ja/datetime.texy b/utils/ja/datetime.texy new file mode 100644 index 0000000000..a629e1dccd --- /dev/null +++ b/utils/ja/datetime.texy @@ -0,0 +1,74 @@ +日付と時刻 +***** + +.[perex] +[api:Nette\Utils\DateTime] は、ネイティブの [php:DateTime] を追加機能で拡張するクラスです。 + + +インストール: + +```shell +composer require nette/utils +``` + +以下の例では、エイリアスが作成されていることを前提としています: + +```php +use Nette\Utils\DateTime; +``` + + +static from(string|int|\DateTimeInterface $time): DateTime .[method] +-------------------------------------------------------------------- +文字列、UNIXタイムスタンプ、または他の [php:DateTimeInterface] オブジェクトからDateTimeオブジェクトを作成します。日付と時刻が無効な場合、`Exception` 例外をスローします。 + +```php +DateTime::from(1138013640); // デフォルトのタイムゾーンでUNIXタイムスタンプからDateTimeを作成します +DateTime::from(42); // 現在時刻に42秒を加えたDateTimeを作成します +DateTime::from('1994-02-26 04:15:32'); // 文字列に従ってDateTimeを作成します +DateTime::from('1994-02-26'); // 日付に従ってDateTimeを作成し、時刻は00:00:00になります +``` + + +static fromParts(int $year, int $month, int $day, int $hour=0, int $minute=0, float $second=0.0): DateTime .[method] +-------------------------------------------------------------------------------------------------------------------- +DateTimeオブジェクトを作成するか、日付と時刻が無効な場合は例外 `Nette\InvalidArgumentException` をスローします。 +```php +DateTime::fromParts(1994, 2, 26, 4, 15, 32); +``` + + +static createFromFormat(string $format, string $time, ?string|\DateTimeZone $timezone=null): DateTime|false .[method] +--------------------------------------------------------------------------------------------------------------------- +[DateTime::createFromFormat()|https://www.php.net/manual/en/datetime.createfromformat.php] を拡張し、タイムゾーンを文字列として指定する可能性を追加します。 +```php +DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); +``` + + +modifyClone(string $modify=''): static .[method] +------------------------------------------------ +変更された時刻を持つコピーを作成します。 +```php +$original = DateTime::from('2017-02-03'); +$clone = $original->modifyClone('+1 day'); +$original->format('Y-m-d'); // '2017-02-03' +$clone->format('Y-m-d'); // '2017-02-04' +``` + + +__toString(): string .[method] +------------------------------ +日付と時刻を `Y-m-d H:i:s` 形式で返します。 +```php +echo $dateTime; // '2017-02-03 04:15:32' +``` + + +JsonSerializableを実装します +---------------------- +日付と時刻をISO 8601形式で返します。これは例えばJavaScriptで使用されます。 +```php +$date = DateTime::from('2017-02-03'); +echo json_encode($date); +``` diff --git a/utils/ja/filesystem.texy b/utils/ja/filesystem.texy new file mode 100644 index 0000000000..fb8892da47 --- /dev/null +++ b/utils/ja/filesystem.texy @@ -0,0 +1,214 @@ +ファイルシステム +******** + +.[perex] +[api:Nette\Utils\FileSystem] は、ファイルシステムを操作するための便利な関数を持つクラスです。ネイティブPHP関数と比較した場合の利点の1つは、エラー発生時に例外をスローする点です。 + + +ディスク上のファイルを検索する必要がある場合は、[Finder|finder] を使用してください。 + +インストール: + +```shell +composer require nette/utils +``` + +以下の例では、エイリアスが作成されていることを前提としています: + +```php +use Nette\Utils\FileSystem; +``` + + +操作 +========== + + +copy(string $origin, string $target, bool $overwrite=true): void .[method] +-------------------------------------------------------------------------- + +ファイルまたはディレクトリ全体をコピーします。デフォルトでは、既存のファイルとディレクトリを上書きします。`$overwrite` パラメータが `false` に設定されている場合、ターゲットのファイルまたはディレクトリ `$target` が存在すると、例外 `Nette\InvalidStateException` をスローします。エラー時には例外 `Nette\IOException` をスローします。 + +```php +FileSystem::copy('/path/to/source', '/path/to/dest', overwrite: true); +``` + + +createDir(string $dir, int $mode=0777): void .[method] +------------------------------------------------------ + +ディレクトリが存在しない場合に、親ディレクトリを含めて作成します。エラー時には例外 `Nette\IOException` をスローします。 + +```php +FileSystem::createDir('/path/to/dir'); +``` + + +delete(string $path): void .[method] +------------------------------------ + +ファイルまたはディレクトリ全体が存在する場合に削除します。ディレクトリが空でない場合、まずその内容を削除します。エラー時には例外 `Nette\IOException` をスローします。 + +```php +FileSystem::delete('/path/to/fileOrDir'); +``` + + +makeWritable(string $path, int $dirMode=0777, int $fileMode=0666): void .[method] +--------------------------------------------------------------------------------- + +ファイルの権限を `$fileMode` に、またはディレクトリの権限を `$dirMode` に設定します。再帰的に処理し、ディレクトリの全内容にも権限を設定します。 + +```php +FileSystem::makeWritable('/path/to/fileOrDir'); +``` + + +open(string $path, string $mode): resource .[method] +---------------------------------------------------- + +ファイルを開き、リソースを返します。パラメータ `$mode` はネイティブ関数 `fopen()`:https://www.php.net/manual/en/function.fopen.php と同様に機能します。エラーの場合、例外 `Nette\IOException` をスローします。 + +```php +$res = FileSystem::open('/path/to/file', 'r'); +``` + + +read(string $file): string .[method] +------------------------------------ + +ファイル `$file` の内容を返します。エラー時には例外 `Nette\IOException` をスローします。 + +```php +$content = FileSystem::read('/path/to/file'); +``` + + +readLines(string $file, bool $stripNewLines=true): \Generator .[method] +----------------------------------------------------------------------- + +ファイルの内容を行ごとに読み取ります。ネイティブ関数 `file()` とは異なり、ファイル全体をメモリに読み込まず、継続的に読み取ります。そのため、利用可能なメモリよりも大きなファイルを読み取ることも可能です。`$stripNewLines` は、行末文字 `\r` と `\n` を削除するかどうかを示します。 エラーの場合、例外 `Nette\IOException` をスローします。 + +```php +$lines = FileSystem::readLines('/path/to/file'); + +foreach ($lines as $lineNum => $line) { + echo "Line $lineNum: $line\n"; +} +``` + + +rename(string $origin, string $target, bool $overwrite=true): void .[method] +---------------------------------------------------------------------------- + +ファイルまたはディレクトリ `$origin` の名前を変更または移動します。デフォルトでは、既存のファイルとディレクトリを上書きします。`$overwrite` パラメータが `false` に設定されている場合、ターゲットのファイルまたはディレクトリ `$target` が存在すると、例外 `Nette\InvalidStateException` をスローします。エラー時には例外 `Nette\IOException` をスローします。 + +```php +FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); +``` + + +write(string $file, string $content, int $mode=0666): void .[method] +-------------------------------------------------------------------- + +文字列 `$content` をファイル `$file` に書き込みます。エラー時には例外 `Nette\IOException` をスローします。 + +```php +FileSystem::write('/path/to/file', $content); +``` + + +パス +===== + + +isAbsolute(string $path): bool .[method] +---------------------------------------- + +パス `$path` が絶対パスかどうかを判定します。 + +```php +FileSystem::isAbsolute('../backup'); // false +FileSystem::isAbsolute('/backup'); // true +FileSystem::isAbsolute('C:/backup'); // true +``` + + +joinPaths(string ...$segments): string .[method] +------------------------------------------------ +パスのすべてのセグメントを結合し、結果を正規化します。 + +```php +FileSystem::joinPaths('a', 'b', 'file.txt'); // 'a/b/file.txt' +FileSystem::joinPaths('/a/', '/b/'); // '/a/b/' +FileSystem::joinPaths('/a/', '/../b'); // '/b' +``` + + +normalizePath(string $path): string .[method] +--------------------------------------------- +パス内の `..` と `.` およびディレクトリ区切り文字をシステムのものに正規化します。 + +```php +FileSystem::normalizePath('/file/.'); // '/file/' +FileSystem::normalizePath('\file\..'); // '/file' +FileSystem::normalizePath('/file/../..'); // '/..' +FileSystem::normalizePath('file/../../bar'); // '/../bar' +``` + + +unixSlashes(string $path): string .[method] +------------------------------------------- + +スラッシュをUnixシステムで使用される `/` に変換します。 + +```php +$path = FileSystem::unixSlashes($path); +``` + + +platformSlashes(string $path): string .[method] +----------------------------------------------- + +スラッシュを現在のプラットフォーム固有の文字に変換します。つまり、Windowsでは `\`、それ以外では `/` です。 + +```php +$path = FileSystem::platformSlashes($path); +``` + + +resolvePath(string $basePath, string $path): string .[method]{data-version:4.0.6} +--------------------------------------------------------------------------------- + +パス `$path` からベースディレクトリ `$basePath` を基準として最終的なパスを導き出します。絶対パス (`/foo`, `C:/foo`) は変更せずに保持します(スラッシュのみ正規化します)。相対パスはベースパスに結合します。 + +```php +// Windowsでは、出力のスラッシュは逆になります(\) +FileSystem::resolvePath('/base/dir', '/abs/path'); // '/abs/path' +FileSystem::resolvePath('/base/dir', 'rel'); // '/base/dir/rel' +FileSystem::resolvePath('base/dir', '../file.txt'); // 'base/file.txt' +FileSystem::resolvePath('base', ''); // 'base' +``` + + +静的アクセス vs 非静的アクセス +================= + +例えばテスト目的のために、クラスを簡単に別のもの(モック)に置き換えられるように、非静的に使用してください: + +```php +class AnyClassUsingFileSystem +{ + public function __construct( + private FileSystem $fileSystem, + ) { + } + + public function readConfig(): string + { + return $this->fileSystem->read(/* ... */); + } + + ... +} +``` diff --git a/utils/ja/finder.texy b/utils/ja/finder.texy new file mode 100644 index 0000000000..4fe171519a --- /dev/null +++ b/utils/ja/finder.texy @@ -0,0 +1,250 @@ +Finder: ファイルの検索 +*************** + +.[perex] +特定のマスクに一致するファイルを見つける必要がありますか?Finderが役立ちます。これは、ディレクトリ構造を探索するための多目的で高速なツールです。 + + +インストール: + +```shell +composer require nette/utils +``` + +例では、エイリアスが作成されていることを前提としています。 + +```php +use Nette\Utils\Finder; +``` + + +使用法 +--- + +まず、[api:Nette\Utils\Finder]を使用して、現在のディレクトリにある拡張子`.txt`と`.md`のファイル名を出力する方法を示します。 + +```php +foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { + echo $file; +} +``` + +検索のデフォルトディレクトリは現在のディレクトリですが、[in() または from() |#どこを検索するか]メソッドを使用して変更できます。 変数`$file`は、多くの便利なメソッドを持つ[#FileInfo]クラスのインスタンスです。キー`$name`には、ファイルへのパスが文字列として含まれています。 + + +何を検索するか? +-------- + +`findFiles()`メソッドに加えて、ディレクトリのみを検索する`findDirectories()`と、両方を検索する`find()`もあります。これらのメソッドは静的なので、インスタンスを作成せずに呼び出すことができます。マスク付きのパラメータはオプションであり、指定しない場合はすべてが検索されます。 + +```php +foreach (Finder::find() as $file) { + echo $file; // これですべてのファイルとディレクトリが出力されます +} +``` + +`files()`および`directories()`メソッドを使用して、さらに検索するものを追加できます。メソッドは繰り返し呼び出すことができ、パラメータとしてマスクの配列を指定することもできます。 + +```php +Finder::findDirectories('vendor') // すべてのディレクトリ + ->files(['*.php', '*.phpt']); // さらにすべてのPHPファイル +``` + +静的メソッドの代替として、`new Finder`を使用してインスタンスを作成し(このように作成された新しいオブジェクトは何も検索しません)、`files()`および`directories()`を使用して検索対象を指定します。 + +```php +(new Finder) + ->directories() // すべてのディレクトリ + ->files('*.php'); // さらにすべてのPHPファイル +``` + +マスクでは、[#ワイルドカード] `*`、`**`、`?`、`[...]`を使用できます。ディレクトリを指定することもできます。たとえば、`src/*.php`は`src`ディレクトリ内のすべてのPHPファイルを検索します。 + +シンボリックリンクもディレクトリまたはファイルと見なされます。 + + +どこを検索するか? +--------- + +検索のデフォルトディレクトリは現在のディレクトリです。`in()`および`from()`メソッドを使用して変更します。メソッド名からわかるように、`in()`はそのディレクトリ内のみを検索し、`from()`はそのサブディレクトリも(再帰的に)検索します。現在のディレクトリを再帰的に検索したい場合は、`from('.')`を使用できます。 + +これらのメソッドは複数回呼び出すか、複数のパスを配列として渡すことができ、ファイルはすべてのディレクトリで検索されます。ディレクトリのいずれかが存在しない場合、例外`Nette\UnexpectedValueException`がスローされます。 + +```php +Finder::findFiles('*.php') + ->in(['src', 'tests']) // src/ および tests/ 内を直接検索 + ->from('vendor'); // vendor/ のサブディレクトリも検索 +``` + +相対パスは現在のディレクトリからの相対パスです。もちろん、絶対パスを指定することもできます。 + +```php +Finder::findFiles('*.php') + ->in('/var/www/html'); +``` + +パスでは、[#ワイルドカード] `*`、`**`、`?`を使用できます。たとえば、パス`src/*/*.php`を使用して、`src`ディレクトリの第2レベルのディレクトリにあるすべてのPHPファイルを検索できます。`**`文字(globstarと呼ばれる)は強力な切り札であり、サブディレクトリも検索できます。`src/**/tests/*.php`を使用すると、`src`またはその任意のサブディレクトリにある`tests`ディレクトリ内のすべてのPHPファイルを検索します。 + +逆に、パス内のワイルドカード`[...]`はサポートされていません。つまり、特別な意味を持ちません。これは、たとえば`in(__DIR__)`を検索し、パスに偶然`[]`文字が含まれている場合に望ましくない動作が発生するのを防ぐためです。 + +ファイルとディレクトリの両方を深く検索する場合、最初に親ディレクトリが返され、次にその中のファイルが返されます。これは`childFirst()`を使用して逆にすることができます。 + + +ワイルドカード +------- + +マスクでは、いくつかの特殊文字を使用できます。 + +- `*` - 任意の数の任意の文字(`/`を除く)を置き換えます +- `**` - `/`を含む任意の数の任意の文字を置き換えます(つまり、複数レベルで検索できます) +- `?` - 任意の1文字(`/`を除く)を置き換えます +- `[a-z]` - 角括弧内の文字リストから1文字を置き換えます +- `[!a-z]` - 角括弧内の文字リスト以外の1文字を置き換えます + +使用例: + +- `img/?.png` - 1文字の名前を持つファイル `0.png`、`1.png`、`x.png` など。 +- `logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log` - `YYYY-MM-DD`形式のログ +- `src/**/tests/*` - `src/tests`、`src/foo/tests`、`src/foo/bar/tests`などのディレクトリ内のファイル。 +- `docs/**.md` - `docs`ディレクトリのすべてのサブディレクトリにある拡張子`.md`を持つすべてのファイル + + +除外 +--------- + +`exclude()`メソッドを使用すると、検索からファイルとディレクトリを除外できます。ファイルが一致してはならないマスクを指定します。例:名前に文字`X`を含むものを除く`*.txt`ファイルの検索: + +```php +Finder::findFiles('*.txt') + ->exclude('*X*'); +``` + +探索するサブディレクトリを除外するには、`exclude()`を使用します。 + +```php +Finder::findFiles('*.php') + ->from($dir) + ->exclude('temp', '.git') +``` + + +フィルタリング +------- + +Finderは、結果をフィルタリング(つまり削減)するためのいくつかのメソッドを提供します。それらを組み合わせたり、繰り返し呼び出したりできます。 + +`size()`を使用してファイルサイズでフィルタリングします。これにより、サイズが100〜200バイトの範囲のファイルが見つかります。 + +```php +Finder::findFiles('*.php') + ->size('>=', 100) + ->size('<=', 200); +``` + +`date()`メソッドは、ファイルの最終変更日でフィルタリングします。値は絶対値または現在の日時からの相対値にすることができます。たとえば、これにより過去2週間以内に変更されたファイルが見つかります。 + +```php +Finder::findFiles('*.php') + ->date('>', '-2 weeks') + ->from($dir) +``` + +両方の関数は、演算子`>`、`>=`、`<`、`<=`、`=`、`!=`、`<>`を理解します。 + +Finderでは、カスタム関数を使用して結果をフィルタリングすることもできます。関数はパラメータとして`Nette\Utils\FileInfo`オブジェクトを受け取り、ファイルが結果に含まれるためには`true`を返す必要があります。 + +例:文字列`Nette`(大文字小文字を区別しない)を含むPHPファイルの検索: + +```php +Finder::findFiles('*.php') + ->filter(fn($file) => strcasecmp($file->read(), 'Nette') === 0); +``` + + +深さによるフィルタリング +------------ + +再帰検索を行う場合、`limitDepth()`メソッドを使用して最大探索深度を設定できます。`limitDepth(1)`を設定すると、最初のサブディレクトリのみが探索され、`limitDepth(0)`は深さ探索を無効にし、値 -1 は制限を解除します。 + +Finderでは、カスタム関数を使用して、探索中にどのディレクトリに入るかを決定できます。関数はパラメータとして`Nette\Utils\FileInfo`オブジェクトを受け取り、ディレクトリに入るためには`true`を返す必要があります。 + +```php +Finder::findFiles('*.php') + ->descentFilter(fn($file) => $file->getBasename() !== 'temp'); +``` + + +ソート +--- + +Finderは、結果をソートするためのいくつかの関数も提供します。 + +`sortByName()`メソッドは、ファイル名で結果をソートします。ソートは自然順であり、つまり名前の数字を正しく処理し、たとえば`foo1.txt`を`foo10.txt`の前に返します。 + +Finderでは、カスタム関数を使用してソートすることもできます。これはパラメータとして2つの`Nette\Utils\FileInfo`オブジェクトを受け取り、比較演算子`<=>`の結果、つまり`-1`、`0`、または`1`を返す必要があります。たとえば、これによりファイルをサイズでソートします。 + +```php +$finder->sortBy(fn($a, $b) => $a->getSize() <=> $b->getSize()); +``` + + +複数の異なる検索 +-------- + +異なる場所にある、または他の基準を満たす複数の異なるファイルを見つける必要がある場合は、`append()`メソッドを使用します。これは新しい`Finder`オブジェクトを返すため、メソッド呼び出しを連鎖させることができます。 + + +```php +($finder = new Finder) // 変数 $finder に最初の Finder を保存します! + ->files('*.php') // src/ で *.php ファイルを検索 + ->from('src') + ->append() + ->files('*.md') // docs/ で *.md ファイルを検索 + ->from('docs') + ->append() + ->files('*.json'); // 現在のフォルダで *.json ファイルを検索 +``` + +あるいは、`append()`メソッドを使用して特定のファイル(またはファイルの配列)を追加することもできます。その場合、同じ`Finder`オブジェクトを返します。 + +```php +$finder = Finder::findFiles('*.txt') + ->append(__FILE__); +``` + + +FileInfo +-------- + +[Nette\Utils\FileInfo |api:]は、検索結果のファイルまたはディレクトリを表すクラスです。これは、ファイルサイズ、最終変更日、名前、パスなどの情報を提供する[SplFileInfo |php:SplFileInfo]クラスの拡張です。 + +さらに、相対パスを返すメソッドを提供します。これは、深さ探索を行う場合に便利です。 + +```php +foreach (Finder::findFiles('*.jpg')->from('.') as $file) { + $absoluteFilePath = $file->getRealPath(); + $relativeFilePath = $file->getRelativePathname(); +} +``` + +さらに、ファイルの内容を読み書きするためのメソッドも利用できます。 + +```php +foreach ($finder as $file) { + $contents = $file->read(); + // ... + $file->write($contents); +} +``` + + +結果を配列として返す +---------- + +例で見たように、Finderは`IteratorAggregate`インターフェースを実装しているため、`foreach`を使用して結果を反復処理できます。結果は反復処理中にのみロードされるようにプログラムされているため、大量のファイルがある場合でも、すべてが読み取られるのを待つ必要はありません。 + +`collect()`メソッドを使用して、結果を`Nette\Utils\FileInfo`オブジェクトの配列として返すこともできます。配列は連想配列ではなく、数値配列です。 + +```php +$array = $finder->findFiles('*.php')->collect(); +``` diff --git a/utils/ja/floats.texy b/utils/ja/floats.texy new file mode 100644 index 0000000000..75d1ab5ad9 --- /dev/null +++ b/utils/ja/floats.texy @@ -0,0 +1,148 @@ +浮動小数点数の操作 +********* + +.[perex] +[api:Nette\Utils\Floats]は、浮動小数点数を比較するための便利な関数を含む静的クラスです。 + + +インストール: + +```shell +composer require nette/utils +``` + +すべての例では、エイリアスが作成されていることを前提としています。 + +```php +use Nette\Utils\Floats; +``` + + +動機 +======== + +なぜ浮動小数点数を比較するためのクラスが必要なのか、疑問に思うかもしれません。演算子`<`、`>`、`===`を使用すれば十分ではないでしょうか。 それは完全には正しくありません。次のコードは何を出力すると思いますか? + +```php +$a = 0.1 + 0.2; +$b = 0.3; +echo $a === $b ? 'same' : 'not same'; +``` + +コードを実行すると、プログラムが`not same`を出力することに驚く人もいるでしょう。 + +浮動小数点数を使用した数学演算では、10進数と2進数の間の変換によりエラーが発生します。たとえば、`0.1 + 0.2`は`0.300000000000000044…`になります。したがって、比較する際には、特定の小数点以下のわずかな差を許容する必要があります。 + +そして、それがまさにクラス`Floats`が行うことです。次の比較は、期待どおりに機能します。 + +```php +echo Floats::areEqual($a, $b) ? 'same' : 'not same'; // same +``` + +`NAN`を比較しようとすると、例外`\LogicException`がスローされます。 + +.[tip] +クラス`Floats`は、`1e-10`未満の差を許容します。より高い精度で作業する必要がある場合は、代わりにBCMathライブラリを使用してください。 + + +浮動小数点数の比較 +========= + + +areEqual(float $a, float $b): bool .[method] +-------------------------------------------- + +`$a` = `$b`の場合に`true`を返します。 + +```php +Floats::areEqual(10, 10.0); // true +``` + + +isLessThan(float $a, float $b): bool .[method] +---------------------------------------------- + +`$a` < `$b`の場合に`true`を返します。 + +```php +Floats::isLessThan(9.5, 10.2); // true +Floats::isLessThan(INF, 10.2); // false +``` + + +isLessThanOrEqualTo(float $a, float $b): bool .[method] +------------------------------------------------------- + +`$a` <= `$b`の場合に`true`を返します。 + +```php +Floats::isLessThanOrEqualTo(9.5, 10.2); // true +Floats::isLessThanOrEqualTo(10.25, 10.25); // true +``` + + +isGreaterThan(float $a, float $b): bool .[method] +------------------------------------------------- + +`$a` > `$b`の場合に`true`を返します。 + +```php +Floats::isGreaterThan(9.5, -10.2); // true +Floats::isGreaterThan(9.5, 10.2); // false +``` + + +isGreaterThanOrEqualTo(float $a, float $b): bool .[method] +---------------------------------------------------------- + +`$a` >= `$b`の場合に`true`を返します。 + +```php +Floats::isGreaterThanOrEqualTo(9.5, 10.2); // false +Floats::isGreaterThanOrEqualTo(10.2, 10.2); // true +``` + + +compare(float $a, float $b): int .[method] +------------------------------------------ + +`$a` < `$b`の場合は`-1`を返し、等しい場合は`0`を返し、`$a` > `$b`の場合は`1`を返します。 + +たとえば、関数`usort`で使用できます。 + +```php +$arr = [1, 5, 2, -3.5]; +usort($arr, [Floats::class, 'compare']); +// $arr は [-3.5, 1, 2, 5] になります +``` + + +ヘルパー関数 +====== + + +isZero(float $value): bool .[method] +------------------------------------ + +値がゼロに等しい場合に`true`を返します。 + +```php +Floats::isZero(0.0); // true +Floats::isZero(0); // true +``` + + +isInteger(float $value): bool .[method] +--------------------------------------- + +値が整数の場合に`true`を返します。 + +```php +Floats::isInteger(0); // true +Floats::isInteger(0.0); // true +Floats::isInteger(-5.0); // true + +Floats::isInteger(-5.1); // false +Floats::isInteger(INF); // false +Floats::isInteger(NAN); // false +``` diff --git a/utils/ja/helpers.texy b/utils/ja/helpers.texy new file mode 100644 index 0000000000..bbee3d31b6 --- /dev/null +++ b/utils/ja/helpers.texy @@ -0,0 +1,86 @@ +ヘルパー関数 +****** + +.[perex] +[api:Nette\Utils\Helpers]は、便利な関数を含む静的クラスです。 + + +インストール: + +```shell +composer require nette/utils +``` + +すべての例では、エイリアスが作成されていることを前提としています。 + +```php +use Nette\Utils\Helpers; +``` + + +capture(callable $cb): string .[method] +--------------------------------------- + +コールバックを実行し、キャプチャされた出力を文字列として返します。 + +```php +$res = Helpers::capture(function () use ($template) { + $template->render(); +}); +``` + + +clamp(int|float $value, int|float $min, int|float $max): int|float .[method] +---------------------------------------------------------------------------- + +値を指定された包括的な範囲minとmaxに制限します。 + +```php +Helpers::clamp($level, 0, 255); +``` + + +compare(mixed $left, string $operator, mixed $right): bool .[method] +-------------------------------------------------------------------- + +PHPが行うのと同じ方法で2つの値を比較します。演算子`>`、`>=`、`<`、`<=`、`=`、`==`、`===`、`!=`、`!==`、`<>`を区別します。 この関数は、演算子が変数である場合に便利です。 + +```php +Helpers::compare(10, '<', 20); // true +``` + + +falseToNull(mixed $value): mixed .[method] +------------------------------------------ + +`false`を`null`に変換し、他の値は変更しません。 + +```php +Helpers::falseToNull(false); // null +Helpers::falseToNull(123); // 123 +``` + + +getLastError(): string .[method] +-------------------------------- + +PHPの最後のエラーを返すか、エラーが発生しなかった場合は空の文字列を返します。`error_get_last()`とは異なり、PHPディレクティブ`html_errors`の影響を受けず、常にテキストを返し、HTMLは返しません。 + +```php +Helpers::getLastError(); +``` + + +getSuggestion(string[] $possibilities, string $value): ?string .[method] +------------------------------------------------------------------------ + +提供されたオプション`$possibilities`から、`$value`に最も似ているが同じではない文字列を検索します。8ビットエンコーディングのみをサポートします。 + +特定のオプションが無効であり、ユーザーに類似した(ただし異なる、したがって同じ文字列は無視される)オプションを提案したい場合に便利です。このようにして、Netteは`did you mean ...?`メッセージを作成します。 + +```php +$items = ['foo', 'bar', 'baz']; +Helpers::getSuggestion($items, 'fo'); // 'foo' +Helpers::getSuggestion($items, 'barr'); // 'bar' +Helpers::getSuggestion($items, 'baz'); // 'bar', 'baz' ではなく +``` diff --git a/utils/ja/html-elements.texy b/utils/ja/html-elements.texy new file mode 100644 index 0000000000..36b19c7a79 --- /dev/null +++ b/utils/ja/html-elements.texy @@ -0,0 +1,316 @@ +HTML要素 +****** + +.[perex] +クラス[api:Nette\Utils\Html]は、クロスサイトスクリプティング (XSS)脆弱性の発生を防ぐHTMLコードを生成するためのヘルパーです。 + + +そのオブジェクトがHTML要素を表し、パラメータを設定してレンダリングさせることで機能します。 + +```php +$el = Html::el('img'); // 要素を作成します +$el->src = 'image.jpg'; // src 属性を設定します +echo $el; // '' を出力します +``` + +インストール: + +```shell +composer require nette/utils +``` + +すべての例では、エイリアスが作成されていることを前提としています。 + +```php +use Nette\Utils\Html; +``` + + +HTML要素の作成 +========= + +要素は`Html::el()`メソッドで作成します。 + +```php +$el = Html::el('img'); // 要素を作成します +``` + +名前以外にも、HTML構文で他の属性を指定できます。 + +```php +$el = Html::el('input type=text class="red important"'); +``` + +または、2番目のパラメータとして連想配列で渡します。 + +```php +$el = Html::el('input', [ + 'type' => 'text', + 'class' => 'important', +]); +``` + +要素名の変更と取得: + +```php +$el->setName('img'); +$el->getName(); // 'img' +$el->isEmpty(); // true、 は空要素であるため +``` + + +HTML属性 +====== + +個々のHTML属性は3つの方法で変更および読み取りできます。どちらが好きかはあなた次第です。最初の方法はプロパティを介して行われます。 + +```php +$el->src = 'image.jpg'; // src 属性を設定します + +echo $el->src; // 'image.jpg' + +unset($el->src); // 属性を削除します +// または $el->src = null; +``` + +2番目の方法はメソッド呼び出しであり、プロパティ設定とは異なり、連鎖させることができます。 + +```php +$el = Html::el('img')->src('image.jpg')->alt('photo'); +// photo + +$el->alt(null); // 属性の削除 +``` + +そして3番目の方法は最も冗長です。 + +```php +$el = Html::el('img') + ->setAttribute('src', 'image.jpg') + ->setAttribute('alt', 'photo'); + +echo $el->getAttribute('src'); // 'image.jpg' + +$el->removeAttribute('alt'); +``` + +属性は`addAttributes(array $attrs)`を使用して一括で設定でき、`removeAttributes(array $attrNames)`を使用して削除できます。 + +属性の値は文字列である必要はなく、論理属性には論理値を使用できます。 + +```php +$checkbox = Html::el('input')->type('checkbox'); +$checkbox->checked = true; // +$checkbox->checked = false; // +``` + +属性は値の配列にすることもでき、スペースで区切られて出力されます。これは、たとえばCSSクラスに便利です。 + +```php +$el = Html::el('input'); +$el->class[] = 'active'; +$el->class[] = null; // null は無視されます +$el->class[] = 'top'; +echo $el; // '' +``` + +代替案は連想配列であり、値はキーを出力するかどうかを示します。 + +```php +$el = Html::el('input'); +$el->class['active'] = true; +$el->class['top'] = false; +echo $el; // '' +``` + +CSSスタイルは連想配列の形式で記述できます。 + +```php +$el = Html::el('input'); +$el->style['color'] = 'green'; +$el->style['display'] = 'block'; +echo $el; // '' +``` + +これまでプロパティを使用してきましたが、同じことをメソッドを使用して記述できます。 + +```php +$el = Html::el('input'); +$el->style('color', 'green'); +$el->style('display', 'block'); +echo $el; // '' +``` + +または、最も冗長な方法でも可能です。 + +```php +$el = Html::el('input'); +$el->appendAttribute('style', 'color', 'green'); +$el->appendAttribute('style', 'display', 'block'); +echo $el; // '' +``` + +最後にちょっとしたこと:`href()`メソッドはURLのクエリパラメータの組み立てを容易にすることができます。 + +```php +echo Html::el('a')->href('index.php', [ + 'id' => 10, + 'lang' => 'en', +]); +// '' +``` + + +データ属性 +----- + +データ属性には特別なサポートがあります。名前にはハイフンが含まれているため、プロパティやメソッドによるアクセスはそれほどエレガントではありません。そのため、`data()`メソッドがあります。 + +```php +$el = Html::el('input'); +$el->{'data-max-size'} = '500x300'; // それほどエレガントではありません +$el->data('max-size', '500x300'); // エレガントです +echo $el; // '' +``` + +データ属性の値が配列の場合、自動的にJSONにシリアライズされます。 + +```php +$el = Html::el('input'); +$el->data('items', [1,2,3]); +echo $el; // '' +``` + + +要素の内容 +===== + +要素の内部コンテンツは`setHtml()`または`setText()`メソッドで設定します。最初のメソッドは、パラメータで確実に安全なHTML文字列を渡していることがわかっている場合にのみ使用してください。 + +```php +echo Html::el('span')->setHtml('hello
                                                                                                                            '); +// 'hello
                                                                                                                            ' + +echo Html::el('span')->setText('10 < 20'); +// '10 < 20' +``` + +逆に、内部コンテンツは`getHtml()`または`getText()`メソッドで取得します。2番目のメソッドは、出力からHTMLタグを削除し、HTMLエンティティを文字に変換します。 + +```php +echo $el->getHtml(); // '10 < 20' +echo $el->getText(); // '10 < 20' +``` + + +子ノード +---- + +要素の内部は、子(children)ノードの配列にすることもできます。それぞれは、文字列または別の`Html`要素のいずれかになります。`addHtml()`または`addText()`を使用して挿入します。 + +```php +$el = Html::el('span') + ->addHtml('hello
                                                                                                                            ') + ->addText('10 < 20') + ->addHtml( Html::el('br') ); +// hello
                                                                                                                            10 < 20
                                                                                                                            +``` + +新しい`Html`ノードを作成して挿入する別の方法: + +```php +$ul = Html::el('ul'); +$ul->create('li', ['class' => 'first']) + ->setText('最初の'); +//
                                                                                                                            • 最初の
                                                                                                                            +``` + +ノードは、配列であるかのように操作できます。つまり、角括弧を使用して個々のノードにアクセスし、`count()`を使用してカウントし、それらを反復処理します。 + +```php +$el = Html::el('div'); +$el[] = 'hello'; +$el[] = Html::el('span'); +echo $el[1]; // '' + +foreach ($el as $child) { /* ... */ } + +echo count($el); // 2 +``` + +新しいノードは、`insert(?int $index, $child, bool $replace = false)`を使用して特定の場所に挿入できます。`$replace = false`の場合、要素を`$index`の位置に挿入し、他の要素をシフトします。`$index = null`の場合、要素を末尾に追加します。 + +```php +// 要素を最初の位置に挿入し、他の要素をシフトします +$el->insert(0, Html::el('span')); +``` + +すべてのノードは`getChildren()`メソッドで取得し、`removeChildren()`メソッドで削除します。 + + +ドキュメントフラグメントの作成 +--------------- + +ノードの配列を操作し、ラッパー要素に関心がない場合は、要素名の代わりに`null`を渡すことで、いわゆる*ドキュメントフラグメント*を作成できます。 + +```php +$el = Html::el(null) + ->addHtml('hello
                                                                                                                            ') + ->addText('10 < 20') + ->addHtml( Html::el('br') ); +// hello
                                                                                                                            10 < 20
                                                                                                                            +``` + +フラグメントを作成するより高速な方法は、`fromHtml()`および`fromText()`メソッドを提供します。 + +```php +$el = Html::fromHtml('hello
                                                                                                                            '); +echo $el; // 'hello
                                                                                                                            ' + +$el = Html::fromText('10 < 20'); +echo $el; // '10 < 20' +``` + + +HTML出力の生成 +========= + +HTML要素を出力する最も簡単な方法は、`echo`を使用するか、オブジェクトを`(string)`にキャストすることです。開始タグまたは終了タグと属性を個別に出力することもできます。 + +```php +$el = Html::el('div class=header')->setText('hello'); + +echo $el; // '
                                                                                                                            hello
                                                                                                                            ' +$s = (string) $el; // '
                                                                                                                            hello
                                                                                                                            ' +$s = $el->toHtml(); // '
                                                                                                                            hello
                                                                                                                            ' +$s = $el->toText(); // 'hello' +echo $el->startTag(); // '
                                                                                                                            ' +echo $el->endTag(); // '
                                                                                                                            ' +echo $el->attributes(); // 'class="header"' +``` + +重要な特徴は、[クロスサイトスクリプティング (XSS) |nette:glossary#Cross-Site Scripting XSS]に対する自動保護です。`setText()`または`addText()`を介して挿入されたすべての属性値またはコンテンツは、確実にエスケープされます。 + +```php +echo Html::el('div') + ->title('" onmouseover="bad()') + ->setText(''); + +//
                                                                                                                            <script>bad()</script>
                                                                                                                            +``` + + +HTML ↔ テキスト変換 +============= + +HTMLをテキストに変換するには、静的メソッド`htmlToText()`を使用できます。 + +```php +echo Html::htmlToText('One & Two'); // 'One & Two' +``` + + +HtmlStringable +============== + +オブジェクト`Nette\Utils\Html`は、`Nette\HtmlStringable`インターフェースを実装しています。これにより、たとえばLatteやフォームは、HTMLコードを返す`__toString()`メソッドを持つオブジェクトを区別します。したがって、たとえばテンプレートで`{$el}`を使用してオブジェクトを出力しても、二重エスケープは発生しません。 diff --git a/utils/ja/images.texy b/utils/ja/images.texy new file mode 100644 index 0000000000..a1af055efe --- /dev/null +++ b/utils/ja/images.texy @@ -0,0 +1,716 @@ +画像の操作 +***** + +.[perex] +[api:Nette\Utils\Image] クラスは、リサイズ、切り抜き、シャープ化、描画、複数の画像の結合など、画像の操作を簡素化します。 + + +PHPには、画像を操作するための豊富な関数セットがあります。しかし、そのAPIはあまり使いやすくありません。Nette FrameworkがセクシーなAPIを提供しないわけがありません。 + +インストール: + +```shell +composer require nette/utils +``` + +すべての例は、エイリアスが作成されていることを前提としています: + +```php +use Nette\Utils\Image; +use Nette\Utils\ImageColor; +use Nette\Utils\ImageType; +``` + + +画像の作成 +===== + +新しいトゥルーカラー画像を作成します。例えば、100×200のサイズで: + +```php +$image = Image::fromBlank(100, 200); +``` + +オプションで背景色を指定できます(デフォルトは黒): + +```php +$image = Image::fromBlank(100, 200, ImageColor::rgb(125, 0, 0)); +``` + +または、ファイルから画像を読み込みます: + +```php +$image = Image::fromFile('nette.jpg'); +``` + + +画像の保存 +===== + +画像をファイルに保存できます: + +```php +$image->save('resampled.jpg'); +``` + +JPEG(デフォルト85)、WEBP(デフォルト80)、AVIF(デフォルト30)の場合は0〜100の範囲で、PNG(デフォルト9)の場合は0〜9の範囲で圧縮品質を指定できます: + +```php +$image->save('resampled.jpg', 80); // JPEG、品質80% +``` + +ファイルの拡張子からフォーマットが明らかでない場合は、[定数 |#フォーマット]で指定できます: + +```php +$image->save('resampled.tmp', null, ImageType::JPEG); +``` + +画像をディスクではなく変数に書き込むことができます: + +```php +$data = $image->toString(ImageType::JPEG, 80); // JPEG、品質80% +``` + +または、適切なHTTPヘッダー `Content-Type` と共にブラウザに直接送信します: + +```php +// Content-Type: image/png ヘッダーを送信します +$image->send(ImageType::PNG); +``` + + +フォーマット +====== + +サポートされているフォーマットはJPEG、PNG、GIF、WebP、AVIF、BMPですが、PHPのバージョンでもサポートされている必要があります。これは関数 [#isTypeSupported()] で確認できます。アニメーションはサポートされていません。 + +フォーマットは定数 `ImageType::JPEG`、`ImageType::PNG`、`ImageType::GIF`、`ImageType::WEBP`、`ImageType::AVIF`、`ImageType::BMP` で表されます。 + +```php +$supported = Image::isTypeSupported(ImageType::JPEG); +``` + +読み込み時に画像のフォーマットを検出する必要がありますか?メソッドはそれを2番目のパラメータで返します: + +```php +$image = Image::fromFile('nette.jpg', $type); +``` + +画像を読み込まずに検出のみを行うのは `Image::detectTypeFromFile()` です。 + + +リサイズ +==== + +画像の寸法を変更することは一般的な操作です。現在の寸法は `getWidth()` と `getHeight()` メソッドで返されます。 + +変更には `resize()` メソッドを使用します。500x300ピクセルを超えないように比例的にリサイズする例(幅が正確に500pxになるか、高さが正確に300pxになり、アスペクト比を維持するために一方の寸法が計算されます): + +```php +$image->resize(500, 300); +``` + +1つの寸法のみを指定し、もう一方は計算させることも可能です: + +```php +$image->resize(500, null); // 幅500px、高さは計算されます + +$image->resize(null, 300); // 幅は計算されます、高さ300px +``` + +どちらの寸法もパーセンテージで指定できます: + +```php +$image->resize('75%', 300); // 75 % × 300px +``` + +`resize` の動作は、以下のフラグで制御できます。`Image::Stretch` を除くすべてがアスペクト比を維持します。 + +|--------------------------------------------------------------------------------------- +| フラグ | 説明 +|--------------------------------------------------------------------------------------- +| `Image::OrSmaller` (デフォルト) | 結果の寸法は、要求された寸法以下になります +| `Image::OrBigger` | ターゲット領域を埋めます(そして、場合によっては一方の寸法で超過します) +| `Image::Cover` | ターゲット領域を埋め、超過した部分を切り抜きます +| `Image::ShrinkOnly` | 縮小のみ(小さな画像の拡大を防ぎます) +| `Image::Stretch` | アスペクト比を維持しません + + +フラグは関数の3番目の引数として指定します: + +```php +$image->resize(500, 300, Image::OrBigger); +``` + +フラグは組み合わせることができます: + +```php +$image->resize(500, 300, Image::ShrinkOnly | Image::Stretch); +``` + +画像を垂直または水平に反転するには、一方の寸法(または両方)を負の数として指定します: + +```php +$flipped = $image->resize(null, '-100%'); // 垂直反転 + +$flipped = $image->resize('-100%', '-100%'); // 180°回転 + +$flipped = $image->resize(-125, 500); // リサイズ & 水平反転 +``` + +画像を縮小した後、軽くシャープ化することで見た目を改善できます: + +```php +$image->sharpen(); +``` + + +切り抜き +==== + +切り抜きには `crop()` メソッドを使用します: + +```php +$image->crop($left, $top, $width, $height); +``` + +`resize()` と同様に、すべての値をパーセンテージで指定できます。`$left` と `$top` のパーセンテージは、CSSの `background-position` プロパティと同様に、残りのスペースから計算されます: + +```php +$image->crop('100%', '50%', '80%', '80%'); +``` + +[* crop.svg *] + + +画像を自動的に切り抜くこともできます。例えば、黒い縁を切り抜く場合: + +```php +$image->cropAuto(IMG_CROP_BLACK); +``` + +`cropAuto()` メソッドは `imagecropauto()` 関数のオブジェクト指向の代替であり、[そのドキュメント|https://www.php.net/manual/en/function.imagecropauto]で詳細を確認できます。 + + +色 .{data-version:4.0.2} +======================= + +`ImageColor::rgb()` メソッドを使用すると、赤、緑、青(RGB)の値を使用して色を定義できます。オプションで、CSSと同様に、0(完全に透明)から1(完全に不透明)の範囲で透明度の値を指定することもできます。 + +```php +$color = ImageColor::rgb(255, 0, 0); // 赤 +$transparentBlue = ImageColor::rgb(0, 0, 255, 0.5); // 半透明の青 +``` + +`ImageColor::hex()` メソッドを使用すると、CSSと同様に16進形式で色を定義できます。`#rgb`、`#rrggbb`、`#rgba`、`#rrggbbaa` の形式をサポートしています: + +```php +$color = ImageColor::hex("#F00"); // 赤 +$transparentGreen = ImageColor::hex("#00FF0080"); // 半透明の緑 +``` + +色は、`ellipse()`、`fill()` などの他のメソッドで使用できます。 + + +描画と編集 +===== + +描画したり、書いたりできますが、葉っぱをちぎってはいけません。画像を操作するためのすべてのPHP関数が利用可能です。[##メソッドの概要] を参照してください。ただし、オブジェクト指向の形式で: + +```php +$image->filledEllipse($centerX, $centerY, $width, $height, ImageColor::rgb(255, 0, 0)); +``` + +PHPの矩形描画関数は座標指定が扱いにくいため、`Image` クラスは [#rectangleWH()] と [#filledRectangleWH()] 関数という形で代替を提供します。 + + +複数の画像の結合 +======== + +画像に別の画像を簡単に挿入できます: + +```php +$logo = Image::fromFile('logo.png'); +$blank = Image::fromBlank(320, 240, ImageColor::rgb(52, 132, 210)); + +// 座標はパーセンテージで指定することもできます +$blank->place($logo, '80%', '80%'); // 右下の近くに挿入します +``` + +挿入時にはアルファチャンネルが尊重され、さらに挿入する画像の透明度を制御できます(いわゆるウォーターマークを作成します): + +```php +$blank->place($image, '80%', '80%', 25); // 透明度は25%です +``` + +このようなAPIは本当に使うのが楽しいです! + + +メソッドの概要 +======= + + +static fromBlank(int $width, int $height, ?ImageColor $color=null): Image .[method] +----------------------------------------------------------------------------------- +指定された寸法の新しいトゥルーカラー画像を作成します。デフォルトの色は黒です。 + + +static fromFile(string $file, int &$detectedFormat=null): Image .[method] +------------------------------------------------------------------------- +ファイルから画像を読み込み、その [タイプ |#フォーマット] を `$detectedFormat` で返します。 + + +static fromString(string $s, int &$detectedFormat=null): Image .[method] +------------------------------------------------------------------------ +文字列から画像を読み込み、その [タイプ |#フォーマット] を `$detectedFormat` で返します。 + + +static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method][deprecated] +--------------------------------------------------------------------------------------------- +この関数は `ImageColor` クラスに置き換えられました。[#色] を参照してください。 + + +static typeToExtension(int $type): string .[method] +--------------------------------------------------- +指定された [タイプ |#フォーマット] のファイル拡張子を返します。 + + +static typeToMimeType(int $type): string .[method] +-------------------------------------------------- +指定された [タイプ |#フォーマット] のMIMEタイプを返します。 + + +static extensionToType(string $extension): int .[method] +-------------------------------------------------------- +ファイル拡張子に基づいて画像の [タイプ |#フォーマット] を返します。 + + +static detectTypeFromFile(string $file, int &$width=null, int &$height=null): ?int .[method] +-------------------------------------------------------------------------------------------- +画像の [タイプ |#フォーマット] を返し、パラメータ `$width` と `$height` でその寸法も返します。 + + +static detectTypeFromString(string $s, int &$width=null, int &$height=null): ?int .[method] +------------------------------------------------------------------------------------------- +文字列から画像の [タイプ |#フォーマット] を返し、パラメータ `$width` と `$height` でその寸法も返します。 + + +static isTypeSupported(int $type): bool .[method] +------------------------------------------------- +指定された画像の [タイプ |#フォーマット] がサポートされているかどうかを調べます。 + + +static getSupportedTypes(): array .[method]{data-version:4.0.4} +--------------------------------------------------------------- +サポートされている画像の [タイプ |#フォーマット] の配列を返します。 + + +static calculateTextBox(string $text, string $fontFile, float $size, float $angle=0, array $options=[]): array .[method] +------------------------------------------------------------------------------------------------------------------------ +特定のフォントとサイズでテキストを囲む矩形の寸法を計算します。`left`、`top`、`width`、`height` キーを含む連想配列を返します。テキストが左側のアンダーカットで始まる場合、左端は負になることがあります。 + + +affine(array $affine, ?array $clip=null): Image .[method] +--------------------------------------------------------- +オプションのクリッピング領域を使用して、src画像のアフィン変換された画像を含む画像を返します。([詳細 |https://www.php.net/manual/en/function.imageaffine]) + + +affineMatrixConcat(array $m1, array $m2): array .[method] +--------------------------------------------------------- +2つのアフィン変換行列の連結を返します。これは、同じ画像に複数の変換を一度に適用する場合に便利です。([詳細 |https://www.php.net/manual/en/function.imageaffinematrixconcat]) + + +affineMatrixGet(int $type, ?mixed $options=null): array .[method] +----------------------------------------------------------------- +アフィン変換行列を返します。([詳細 |https://www.php.net/manual/en/function.imageaffinematrixget]) + + +alphaBlending(bool $on): void .[method] +--------------------------------------- +トゥルーカラー画像で2つの異なる描画モードを可能にします。ブレンドモードでは、`setPixel()` などのすべての描画関数で使用される色のアルファチャンネル成分が、下にある色がどの程度透けて見えるかを決定します。その結果、この時点で既存の色と描画される色が自動的に混合され、結果が画像に保存されます。結果のピクセルは不透明です。非ブレンドモードでは、描画される色はアルファチャンネル情報と共に文字通りコピーされ、ターゲットピクセルを置き換えます。パレット画像に描画する場合、ブレンドモードは利用できません。([詳細 |https://www.php.net/manual/en/function.imagealphablending]) + + +antialias(bool $on): void .[method] +----------------------------------- +アンチエイリアス処理された線とポリゴンの描画を有効にします。アルファチャンネルはサポートしません。トゥルーカラー画像でのみ機能します。 + +透明な背景色でアンチエイリアス処理されたプリミティブを使用すると、予期しない結果になることがあります。ブレンドメソッドは、他のすべての色と同様に背景色を使用します。([詳細 |https://www.php.net/manual/en/function.imageantialias]) + + +arc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color): void .[method] +--------------------------------------------------------------------------------------------------------------------------- +指定された座標を中心とする円弧を描画します。([詳細 |https://www.php.net/manual/en/function.imagearc]) + + +colorAllocate(int $red, int $green, int $blue): int .[method] +------------------------------------------------------------- +指定されたRGB成分で構成される色を表す色識別子を返します。画像で使用される各色を作成するために呼び出す必要があります。([詳細 |https://www.php.net/manual/en/function.imagecolorallocate]) + + +colorAllocateAlpha(int $red, int $green, int $blue, int $alpha): int .[method] +------------------------------------------------------------------------------ +透明度パラメータ `$alpha` を追加した `colorAllocate()` と同じように動作します。([詳細 |https://www.php.net/manual/en/function.imagecolorallocatealpha]) + + +colorAt(int $x, int $y): int .[method] +-------------------------------------- +画像内の指定された場所にあるピクセルの色インデックスを返します。画像がトゥルーカラーの場合、この関数はそのピクセルのRGB値を整数として返します。個々の赤、緑、青の成分値にアクセスするには、ビットシフトとビットマスキングを使用します: ([詳細 |https://www.php.net/manual/en/function.imagecolorat]) + + +colorClosest(int $red, int $green, int $blue): int .[method] +------------------------------------------------------------ +画像パレット内で、指定されたRGB値に「最も近い」色のインデックスを返します。要求された色とパレット内の各色との「距離」は、RGB値が3次元空間の点を表すかのように計算されます。([詳細 |https://www.php.net/manual/en/function.imagecolorclosest]) + + +colorClosestAlpha(int $red, int $green, int $blue, int $alpha): int .[method] +----------------------------------------------------------------------------- +画像パレット内で、指定されたRGB値と `$alpha` レベルに「最も近い」色のインデックスを返します。([詳細 |https://www.php.net/manual/en/function.imagecolorclosestalpha]) + + +colorClosestHWB(int $red, int $green, int $blue): int .[method] +--------------------------------------------------------------- +指定された色に最も近い色相、白、黒を持つ色のインデックスを取得します。([詳細 |https://www.php.net/manual/en/function.imagecolorclosesthwb]) + + +colorDeallocate(int $color): void .[method] +------------------------------------------- +`colorAllocate()` または `colorAllocateAlpha()` で以前に割り当てられた色を解放します。([詳細 |https://www.php.net/manual/en/function.imagecolordeallocate]) + + +colorExact(int $red, int $green, int $blue): int .[method] +---------------------------------------------------------- +画像パレット内の指定された色のインデックスを返します。([詳細 |https://www.php.net/manual/en/function.imagecolorexact]) + + +colorExactAlpha(int $red, int $green, int $blue, int $alpha): int .[method] +--------------------------------------------------------------------------- +画像パレット内の指定された色+アルファのインデックスを返します。([詳細 |https://www.php.net/manual/en/function.imagecolorexactalpha]) + + +colorMatch(Image $image2): void .[method] +----------------------------------------- +パレットの色を2番目の画像に合わせます。([詳細 |https://www.php.net/manual/en/function.imagecolormatch]) + + +colorResolve(int $red, int $green, int $blue): int .[method] +------------------------------------------------------------ +要求された色の色インデックスを返します。正確な色か、最も近い代替色かのいずれかです。([詳細 |https://www.php.net/manual/en/function.imagecolorresolve]) + + +colorResolveAlpha(int $red, int $green, int $blue, int $alpha): int .[method] +----------------------------------------------------------------------------- +要求された色の色インデックスを返します。正確な色か、最も近い代替色かのいずれかです。([詳細 |https://www.php.net/manual/en/function.imagecolorresolvealpha]) + + +colorSet(int $index, int $red, int $green, int $blue): void .[method] +--------------------------------------------------------------------- +パレット内の指定されたインデックスを指定された色に設定します。([詳細 |https://www.php.net/manual/en/function.imagecolorset]) + + +colorsForIndex(int $index): array .[method] +------------------------------------------- +指定されたインデックスの色を取得します。([詳細 |https://www.php.net/manual/en/function.imagecolorsforindex]) + + +colorsTotal(): int .[method] +---------------------------- +画像パレット内の色の数を返します。([詳細 |https://www.php.net/manual/en/function.imagecolorstotal]) + + +colorTransparent(?int $color=null): int .[method] +------------------------------------------------- +画像内の透明色を取得または設定します。([詳細 |https://www.php.net/manual/en/function.imagecolortransparent]) + + +convolution(array $matrix, float $div, float $offset): void .[method] +--------------------------------------------------------------------- +指定された係数とオフセットを使用して、画像に畳み込み行列を適用します。([詳細 |https://www.php.net/manual/en/function.imageconvolution]) + +.[note] +*Bundled GD extension* が必要であり、どこでも機能するとは限りません。 + + +copy(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH): void .[method] +-------------------------------------------------------------------------------------------------- +`$src` の一部を、座標 `$srcX`、`$srcY` から始まり、幅 `$srcW`、高さ `$srcH` で画像にコピーします。定義された部分は座標 `$dstX` と `$dstY` にコピーされます。([詳細 |https://www.php.net/manual/en/function.imagecopy]) + + +copyMerge(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] +--------------------------------------------------------------------------------------------------------------------- +`$src` の一部を、座標 `$srcX`、`$srcY` から始まり、幅 `$srcW`、高さ `$srcH` で画像にコピーします。定義された部分は座標 `$dstX` と `$dstY` にコピーされます。([詳細 |https://www.php.net/manual/en/function.imagecopymerge]) + + +copyMergeGray(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] +------------------------------------------------------------------------------------------------------------------------- +`$src` の一部を、座標 `$srcX`、`$srcY` から始まり、幅 `$srcW`、高さ `$srcH` で画像にコピーします。定義された部分は座標 `$dstX` と `$dstY` にコピーされます。 + +この関数は `copyMerge()` と同一ですが、マージ時にコピー操作の前にターゲットピクセルをグレースケールに変換することでソースの色相を保持する点が異なります。([詳細 |https://www.php.net/manual/en/function.imagecopymergegray]) + + +copyResampled(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] +--------------------------------------------------------------------------------------------------------------------------------- +ある画像の矩形部分を別の画像にコピーし、ピクセル値を滑らかに補間するため、特に画像のサイズを縮小しても高い鮮明さが維持されます。 + +言い換えると、`copyResampled()` は `$src` から幅 `$srcW`、高さ `$srcH` の矩形領域を位置 (`$srcX`, `$srcY`) で取得し、それを幅 `$dstW`、高さ `$dstH` の画像の矩形領域に位置 (`$dstX`, `$dstY`) で配置します。 + +ソースとターゲットの座標、幅、高さが異なる場合、画像フラグメントの対応する拡大または縮小が実行されます。座標は左上隅を基準とします。この関数は同じ画像内の領域をコピーするために使用できますが、領域が重なる場合、結果は予測できません。([詳細 |https://www.php.net/manual/en/function.imagecopyresampled]) + + +copyResized(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] +------------------------------------------------------------------------------------------------------------------------------- +ある画像の矩形部分を別の画像にコピーします。言い換えると、`copyResized()` は `$src` から幅 `$srcW`、高さ `$srcH` の矩形領域を位置 (`$srcX`, `$srcY`) で取得し、それを幅 `$dstW`、高さ `$dstH` の画像の矩形領域に位置 (`$dstX`, `$dstY`) で配置します。 + +ソースとターゲットの座標、幅、高さが異なる場合、画像フラグメントの対応する拡大または縮小が実行されます。座標は左上隅を基準とします。この関数は同じ画像内の領域をコピーするために使用できますが、領域が重なる場合、結果は予測できません。([詳細 |https://www.php.net/manual/en/function.imagecopyresized]) + + +crop(int|string $left, int|string $top, int|string $width, int|string $height): Image .[method] +----------------------------------------------------------------------------------------------- +画像を特定の矩形領域に切り抜きます。寸法はピクセル単位の整数またはパーセンテージの文字列(例: `'50%'`)で指定できます。 + + +cropAuto(int $mode=-1, float $threshold=.5, ?ImageColor $color=null): Image .[method] +------------------------------------------------------------------------------------- +指定された `$mode` に従って画像を自動的に切り抜きます。([詳細 |https://www.php.net/manual/en/function.imagecropauto]) + + +ellipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------- +指定された座標を中心とする楕円を描画します。([詳細 |https://www.php.net/manual/en/function.imageellipse]) + + +fill(int $x, int $y, ImageColor $color): void .[method] +------------------------------------------------------- +指定された座標(左上が0, 0)から始まる領域を指定された `$color` で塗りつぶします。([詳細 |https://www.php.net/manual/en/function.imagefill]) + + +filledArc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color, int $style): void .[method] +--------------------------------------------------------------------------------------------------------------------------------------------- +指定された座標を中心とする部分的な円弧を描画します。([詳細 |https://www.php.net/manual/en/function.imagefilledarc]) + + +filledEllipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------------- +指定された座標を中心とする楕円を描画します。([詳細 |https://www.php.net/manual/en/function.imagefilledellipse]) + + +filledPolygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------------- +画像内に塗りつぶされた多角形を作成します。([詳細 |https://www.php.net/manual/en/function.imagefilledpolygon]) + + +filledRectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------- +点 `$x1` & `$y1` から始まり、点 `$x2` & `$y2` で終わる画像内に `$color` で塗りつぶされた矩形を作成します。点 0, 0 は画像の左上隅です。([詳細 |https://www.php.net/manual/en/function.imagefilledrectangle]) + + +filledRectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------------- +点 `$left` & `$top` から始まり、幅 `$width`、高さ `$height` の画像内に `$color` で塗りつぶされた矩形を作成します。点 0, 0 は画像の左上隅です。 + + +fillToBorder(int $x, int $y, int $border, ImageColor $color): void .[method] +---------------------------------------------------------------------------- +境界線の色が `$border` で定義された塗りつぶしを描画します。塗りつぶしの開始点は `$x`、`$y`(左上が0, 0)で、領域は色 `$color` で塗りつぶされます。([詳細 |https://www.php.net/manual/en/function.imagefilltoborder]) + + +filter(int $filtertype, int ...$args): void .[method] +----------------------------------------------------- +指定されたフィルター `$filtertype` を画像に適用します。([詳細 |https://www.php.net/manual/en/function.imagefilter]) + + +flip(int $mode): void .[method] +------------------------------- +指定された `$mode` を使用して画像を反転します。([詳細 |https://www.php.net/manual/en/function.imageflip]) + + +ftText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +---------------------------------------------------------------------------------------------------------------------------------------- +画像にテキストを書き込みます。([詳細 |https://www.php.net/manual/en/function.imagefttext]) + + +gammaCorrect(float $inputgamma, float $outputgamma): void .[method] +------------------------------------------------------------------- +入力ガンマと出力ガンマに関して画像にガンマ補正を適用します。([詳細 |https://www.php.net/manual/en/function.imagegammacorrect]) + + +getClip(): array .[method] +-------------------------- +現在のクリッピング領域、つまりピクセルが描画されない領域を返します。([詳細 |https://www.php.net/manual/en/function.imagegetclip]) + + +getHeight(): int .[method] +-------------------------- +画像の高さを返します。 + + +getImageResource(): resource|GdImage .[method] +---------------------------------------------- +元のリソースを返します。 + + +getWidth(): int .[method] +------------------------- +画像の幅を返します。 + + +interlace(?int $interlace=null): int .[method] +---------------------------------------------- +インターレースモードをオンまたはオフにします。インターレースモードが設定され、画像がJPEGとして保存される場合、プログレッシブJPEGとして保存されます。([詳細 |https://www.php.net/manual/en/function.imageinterlace]) + + +isTrueColor(): bool .[method] +----------------------------- +画像がトゥルーカラーかどうかを調べます。([詳細 |https://www.php.net/manual/en/function.imageistruecolor]) + + +layerEffect(int $effect): void .[method] +---------------------------------------- +レイヤー効果を使用するためにアルファブレンドフラグを設定します。([詳細 |https://www.php.net/manual/en/function.imagelayereffect]) + + +line(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +--------------------------------------------------------------------------- +指定された2点間に線を描画します。([詳細 |https://www.php.net/manual/en/function.imageline]) + + +openPolygon(array $points, ImageColor $color): void .[method] +------------------------------------------------------------- +画像上に開いた多角形を描画します。`polygon()` とは異なり、最後の点と最初の点の間に線は描画されません。([詳細 |https://www.php.net/manual/en/function.imageopenpolygon]) + + +paletteCopy(Image $source): void .[method] +------------------------------------------ +`$source` から画像にパレットをコピーします。([詳細 |https://www.php.net/manual/en/function.imagepalettecopy]) + + +paletteToTrueColor(): void .[method] +------------------------------------ +パレットベースの画像をフルカラー画像に変換します。([詳細 |https://www.php.net/manual/en/function.imagepalettetotruecolor]) + + +place(Image $image, int|string $left=0, int|string $top=0, int $opacity=100): Image .[method] +--------------------------------------------------------------------------------------------- +`$image` を座標 `$left` と `$top` の画像にコピーします。座標はピクセル単位の整数またはパーセンテージの文字列(例: `'50%'`)で指定できます。 + + +polygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------- +画像内に多角形を作成します。([詳細 |https://www.php.net/manual/en/function.imagepolygon]) + + +rectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------- +指定された座標に矩形を作成します。([詳細 |https://www.php.net/manual/en/function.imagerectangle]) + + +rectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------- +指定された座標に矩形を作成します。 + + +resize(int|string $width, int|string $height, int $flags=Image::OrSmaller): Image .[method] +------------------------------------------------------------------------------------------- +画像のサイズを変更します。[詳細はこちら |#リサイズ]。寸法はピクセル単位の整数またはパーセンテージの文字列(例: `'50%'`)で指定できます。 + + +resolution(?int $resX=null, ?int $resY=null): mixed .[method] +------------------------------------------------------------- +画像の解像度をDPI(ドット/インチ)で設定または返します。オプションのパラメータが指定されていない場合、現在の解像度がインデックス付き配列として返されます。`$resX` のみが指定されている場合、水平および垂直解像度がこの値に設定されます。両方のオプションパラメータが指定されている場合、水平および垂直解像度がこれらの値に設定されます。 + +解像度は、この種の情報をサポートする形式(現在はPNGとJPEG)で画像が読み書きされる際のメタ情報としてのみ使用されます。描画操作には影響しません。新しい画像のデフォルト解像度は96 DPIです。([詳細 |https://www.php.net/manual/en/function.imageresolution]) + + +rotate(float $angle, int $backgroundColor): Image .[method] +----------------------------------------------------------- +指定された `$angle`(度単位)で画像を回転させます。回転の中心は画像の中心であり、回転された画像は元の画像とは異なる寸法を持つ場合があります。([詳細 |https://www.php.net/manual/en/function.imagerotate]) + +.[note] +*Bundled GD extension* が必要であり、どこでも機能するとは限りません。 + + +save(string $file, ?int $quality=null, ?int $type=null): void .[method] +----------------------------------------------------------------------- +画像をファイルに保存します。 + +圧縮品質は、JPEG(デフォルト85)、WEBP(デフォルト80)、AVIF(デフォルト30)の場合は0〜100の範囲、PNG(デフォルト9)の場合は0〜9の範囲です。タイプがファイル拡張子から明らかでない場合は、`ImageType` 定数のいずれかを使用して指定できます。 + + +saveAlpha(bool $saveflag): void .[method] +----------------------------------------- +PNG画像を保存するときに、完全なアルファチャンネル情報(単色透明度とは対照的に)を保持するかどうかを示すフラグを設定します。 + +アルファチャンネルを最初に保持するには、アルファブレンドを無効にする必要があります(`alphaBlending(false)`)。([詳細 |https://www.php.net/manual/en/function.imagesavealpha]) + + +scale(int $newWidth, int $newHeight=-1, int $mode=IMG_BILINEAR_FIXED): Image .[method] +-------------------------------------------------------------------------------------- +指定された補間アルゴリズムを使用して画像のスケールを変更します。([詳細 |https://www.php.net/manual/en/function.imagescale]) + + +send(int $type=ImageType::JPEG, ?int $quality=null): void .[method] +------------------------------------------------------------------- +画像をブラウザに出力します。 + +圧縮品質は、JPEG(デフォルト85)、WEBP(デフォルト80)、AVIF(デフォルト30)の場合は0〜100の範囲、PNG(デフォルト9)の場合は0〜9の範囲です。 + + +setBrush(Image $brush): void .[method] +-------------------------------------- +特殊な色 IMG_COLOR_BRUSHED または IMG_COLOR_STYLEDBRUSHED で描画する場合に、すべての線描画関数(`line()` や `polygon()` など)で使用されるブラシ画像を設定します。([詳細 |https://www.php.net/manual/en/function.imagesetbrush]) + + +setClip(int $x1, int $y1, int $x2, int $y2): void .[method] +----------------------------------------------------------- +現在のクリッピング領域、つまりピクセルが描画されない領域を設定します。([詳細 |https://www.php.net/manual/en/function.imagesetclip]) + + +setInterpolation(int $method=IMG_BILINEAR_FIXED): void .[method] +---------------------------------------------------------------- +`rotate()` および `affine()` メソッドに影響を与える補間メソッドを設定します。([詳細 |https://www.php.net/manual/en/function.imagesetinterpolation]) + + +setPixel(int $x, int $y, ImageColor $color): void .[method] +----------------------------------------------------------- +指定された座標にピクセルを描画します。([詳細 |https://www.php.net/manual/en/function.imagesetpixel]) + + +setStyle(array $style): void .[method] +-------------------------------------- +特殊な色 IMG_COLOR_STYLED または色 IMG_COLOR_STYLEDBRUSHED の画像線で描画する場合に、すべての線描画関数(`line()` や `polygon()` など)で使用されるスタイルを設定します。([詳細 |https://www.php.net/manual/en/function.imagesetstyle]) + + +setThickness(int $thickness): void .[method] +-------------------------------------------- +矩形、多角形、円弧などを描画する際の線の太さを `$thickness` ピクセルに設定します。([詳細 |https://www.php.net/manual/en/function.imagesetthickness]) + + +setTile(Image $tile): void .[method] +------------------------------------ +特殊な色 IMG_COLOR_TILED で塗りつぶす場合に、すべての領域塗りつぶし関数(`fill()` や `filledPolygon()` など)で使用されるタイル画像を設定します。 + +タイルは、繰り返しパターンで領域を埋めるために使用される画像です。任意の画像をタイルとして使用でき、`colorTransparent()` を使用してタイル画像の透明色インデックスを設定することにより、下にある領域の特定の部分が透けて見えるタイルを作成できます。([詳細 |https://www.php.net/manual/en/function.imagesettile]) + + +sharpen(): Image .[method] +-------------------------- +画像をシャープにします。 + +.[note] +*Bundled GD extension* が必要であり、どこでも機能するとは限りません。 + + +toString(int $type=ImageType::JPEG, ?int $quality=null): string .[method] +------------------------------------------------------------------------- +画像を文字列に保存します。 + +圧縮品質は、JPEG(デフォルト85)、WEBP(デフォルト80)、AVIF(デフォルト30)の場合は0〜100の範囲、PNG(デフォルト9)の場合は0〜9の範囲です。 + + +trueColorToPalette(bool $dither, int $ncolors): void .[method] +-------------------------------------------------------------- +トゥルーカラー画像をパレット画像に変換します。([詳細 |https://www.php.net/manual/en/function.imagetruecolortopalette]) + + +ttfText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +----------------------------------------------------------------------------------------------------------------------------------------- +指定されたテキストを画像に出力します。([詳細 |https://www.php.net/manual/en/function.imagettftext]) diff --git a/utils/ja/iterables.texy b/utils/ja/iterables.texy new file mode 100644 index 0000000000..3ec248ab1f --- /dev/null +++ b/utils/ja/iterables.texy @@ -0,0 +1,170 @@ +イテレータの操作 +******** + +.[perex]{data-version:4.0.4} +[api:Nette\Utils\Iterables] は、イテレータを操作するための関数を持つ静的クラスです。配列に対するその類似物は [Nette\Utils\Arrays|arrays] です。 + + +インストール: + +```shell +composer require nette/utils +``` + +すべての例は、エイリアスが作成されていることを前提としています: + +```php +use Nette\Utils\Iterables; +``` + + +contains(iterable $iterable, $value): bool .[method] +---------------------------------------------------- + +イテレータ内で指定された値を検索します。一致を確認するために厳密な比較 (`===`) を使用します。値が見つかった場合は `true` を、それ以外の場合は `false` を返します。 + +```php +Iterables::contains(new ArrayIterator([1, 2, 3]), 1); // true +Iterables::contains(new ArrayIterator([1, 2, 3]), '1'); // false +``` + +このメソッドは、すべての要素を手動で走査することなく、特定の価​​値がイテレータ内に存在するかどうかを迅速に判断する必要がある場合に便利です。 + + +containsKey(iterable $iterable, $key): bool .[method] +----------------------------------------------------- + +イテレータ内で指定されたキーを検索します。一致を確認するために厳密な比較 (`===`) を使用します。キーが見つかった場合は `true` を、それ以外の場合は `false` を返します。 + +```php +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 0); // true +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 4); // false +``` + + +every(iterable $iterable, callable $predicate): bool .[method] +-------------------------------------------------------------- + +イテレータのすべての要素が `$predicate` で定義された条件を満たすかどうかを検証します。関数 `$predicate` は `function ($value, $key, iterable $iterable): bool` のシグネチャを持ち、`every()` メソッドが `true` を返すためには、各要素に対して `true` を返す必要があります。 + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isBelowThreshold = fn($value) => $value < 40; +$res = Iterables::every($iterator, $isBelowThreshold); // true +``` + +このメソッドは、コレクション内のすべての要素が特定の条件を満たしているかどうか(例えば、すべての数値が特定の価​​値未満であるかどうか)を検証するのに便利です。 + + +filter(iterable $iterable, callable $predicate): Generator .[method] +-------------------------------------------------------------------- + +元のイテレータから `$predicate` で定義された条件を満たす要素のみを含む新しいイテレータを作成します。関数 `$predicate` は `function ($value, $key, iterable $iterable): bool` のシグネチャを持ち、保持されるべき要素に対して `true` を返す必要があります。 + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::filter($iterator, fn($v) => $v < 3); +// 1, 2 +``` + +このメソッドはジェネレータを使用します。これは、フィルタリングが結果を走査する際に段階的に行われることを意味します。これはメモリ効率が高く、非常に大きなコレクションの処理も可能です。結果のイテレータのすべての要素を走査しない場合、元のイテレータのすべての要素が処理されないため、計算能力を節約できます。 + + +first(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------------- + +イテレータの最初の要素を返します。`$predicate` が指定されている場合、指定された条件を満たす最初の要素を返します。関数 `$predicate` は `function ($value, $key, iterable $iterable): bool` のシグネチャを持ちます。適合する要素が見つからない場合、`$else` 関数が(指定されていれば)呼び出され、その結果が返されます。`$else` が指定されていない場合は `null` が返されます。 + +```php +Iterables::first(new ArrayIterator([1, 2, 3])); // 1 +Iterables::first(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 3 +Iterables::first(new ArrayIterator([])); // null +Iterables::first(new ArrayIterator([]), else: fn() => false); // false +``` + +このメソッドは、コレクション全体を手動で走査することなく、コレクションの最初の要素または特定の条件を満たす最初の要素を迅速に取得する必要がある場合に便利です。 + + +firstKey(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +---------------------------------------------------------------------------------------------- + +イテレータの最初の要素のキーを返します。`$predicate` が指定されている場合、指定された条件を満たす最初の要素のキーを返します。関数 `$predicate` は `function ($value, $key, iterable $iterable): bool` のシグネチャを持ちます。適合する要素が見つからない場合、`$else` 関数が(指定されていれば)呼び出され、その結果が返されます。`$else` が指定されていない場合は `null` が返されます。 + +```php +Iterables::firstKey(new ArrayIterator([1, 2, 3])); // 0 +Iterables::firstKey(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 2 +Iterables::firstKey(new ArrayIterator(['a' => 1, 'b' => 2])); // 'a' +Iterables::firstKey(new ArrayIterator([])); // null +``` + + +map(iterable $iterable, callable $transformer): Generator .[method] +------------------------------------------------------------------- + +元のイテレータの各要素に関数 `$transformer` を適用して新しいイテレータを作成します。関数 `$transformer` は `function ($value, $key, iterable $iterable): mixed` のシグネチャを持ち、その戻り値が要素の新しい値として使用されます。 + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::map($iterator, fn($v) => $v * 2); +// 2, 4, 6 +``` + +このメソッドはジェネレータを使用します。これは、変換が結果を走査する際に段階的に行われることを意味します。これはメモリ効率が高く、非常に大きなコレクションの処理も可能です。結果のイテレータのすべての要素を走査しない場合、元のイテレータのすべての要素が処理されないため、計算能力を節約できます。 + + +mapWithKeys(iterable $iterable, callable $transformer): Generator .[method] +--------------------------------------------------------------------------- + +元のイテレータの値とキーを変換して新しいイテレータを作成します。関数 `$transformer` は `function ($value, $key, iterable $iterable): ?array{$newKey, $newValue}` のシグネチャを持ちます。`$transformer` が `null` を返した場合、要素はスキップされます。保持される要素については、返された配列の最初の要素が新しいキーとして、2番目の要素が新しい値として使用されます。 + +```php +$iterator = new ArrayIterator(['a' => 1, 'b' => 2]); +$iterator = Iterables::mapWithKeys($iterator, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +`map()` と同様に、このメソッドは段階的な処理と効率的なメモリ使用のためにジェネレータを利用します。これにより、大きなコレクションを扱い、結果の部分的な走査時に計算能力を節約できます。 + + +memoize(iterable $iterable): IteratorAggregate .[method] +-------------------------------------------------------- + +イテレーション中にキーと値をキャッシュに保存するイテレータのラッパーを作成します。これにより、元のデータソースを再度走査することなく、データを繰り返しイテレートできます。 + +```php +$iterator = /* 複数回イテレートできないデータ */ +$memoized = Iterables::memoize($iterator); +// これで、$memoized をデータ損失なしで複数回イテレートできます +``` + +このメソッドは、同じデータセットを複数回走査する必要があるが、元のイテレータが繰り返しイテレーションを許可しない、または繰り返しの走査が高コストになる(例:データベースやファイルからのデータ読み取り時)場合に便利です。 + + +some(iterable $iterable, callable $predicate): bool .[method] +------------------------------------------------------------- + +イテレータの少なくとも1つの要素が `$predicate` で定義された条件を満たすかどうかを検証します。関数 `$predicate` は `function ($value, $key, iterable $iterable): bool` のシグネチャを持ち、`some()` メソッドが `true` を返すためには、少なくとも1つの要素に対して `true` を返す必要があります。 + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isEven = fn($value) => $value % 2 === 0; +$res = Iterables::some($iterator, $isEven); // true +``` + +このメソッドは、コレクション内に特定の条件を満たす要素が少なくとも1つ存在するかどうか(例えば、コレクションに少なくとも1つの偶数が含まれているかどうか)を迅速に検証するのに便利です。 + +[#every()] を参照してください。 + + +toIterator(iterable $iterable): Iterator .[method] +-------------------------------------------------- + +任意のイテラブルオブジェクト(array、Traversable)をIteratorに変換します。入力が既にIteratorである場合は、変更せずに返します。 + +```php +$array = [1, 2, 3]; +$iterator = Iterables::toIterator($array); +// これで、配列の代わりにIteratorが得られます +``` + +このメソッドは、入力データのタイプに関係なく、Iteratorが利用可能であることを保証する必要がある場合に便利です。これは、さまざまなタイプのイテラブルデータを扱う関数を作成する際に役立ちます。 diff --git a/utils/ja/json.texy b/utils/ja/json.texy new file mode 100644 index 0000000000..a5381246b2 --- /dev/null +++ b/utils/ja/json.texy @@ -0,0 +1,97 @@ +JSONの操作 +******* + +.[perex] +[api:Nette\Utils\Json] は、JSON形式のエンコードとデコードのための関数を持つ静的クラスです。さまざまなPHPバージョンの脆弱性を処理し、エラー時に例外をスローします。 + + +インストール: + +```shell +composer require nette/utils +``` + +すべての例は、エイリアスが作成されていることを前提としています: + +```php +use Nette\Utils\Json; +``` + + +使用法 +=== + + +encode(mixed $value, bool $pretty=false, bool $asciiSafe=false, bool $htmlSafe=false, bool $forceObjects=false): string .[method] +--------------------------------------------------------------------------------------------------------------------------------- + +`$value` をJSON形式に変換します。 + +`$pretty` を設定すると、JSONを読みやすく、見やすくフォーマットします: + +```php +Json::encode($value); // JSONを返します +Json::encode($value, pretty: true); // より見やすいJSONを返します +``` + +`$asciiSafe` を使用すると、出力をASCIIで生成します。つまり、Unicode文字を `\uxxxx` シーケンスに置き換えます: + +```php +Json::encode('žluťoučký', asciiSafe: true); +// '"\u017elu\u0165ou\u010dk\u00fd"' +``` + +パラメータ `$htmlSafe` は、出力にHTMLで特別な意味を持つ文字が含まれないようにします: + +```php +Json::encode('onesendJson($data)` メソッドを使用できます。これは例えば `action*()` メソッド内で呼び出すことができます。[レスポンスの送信 |application:presenters#応答の送信] を参照してください。 diff --git a/utils/ja/paginator.texy b/utils/ja/paginator.texy new file mode 100644 index 0000000000..9b6906b32b --- /dev/null +++ b/utils/ja/paginator.texy @@ -0,0 +1,65 @@ +Paginator +********* + +.[perex] +データリストをページ分割する必要がありますか?ページネーションの計算は厄介な場合があるため、[api:Nette\Utils\Paginator] が役立ちます。 + + +インストール: + +```shell +composer require nette/utils +``` + +ページネータオブジェクトを作成し、基本情報を設定します: + +```php +$paginator = new Nette\Utils\Paginator; +$paginator->setPage(1); // 現在のページ番号 +$paginator->setItemsPerPage(30); // ページあたりのアイテム数 +$paginator->setItemCount(356); // 合計アイテム数(わかっている場合) +``` + +ページは1から番号付けされます。これは `setBase()` を使用して変更できます: + +```php +$paginator->setBase(0); // 0から番号付けします +``` + +オブジェクトは、ページネータを作成する際に役立つすべての基本情報を提供します。例えば、テンプレートに渡してそこで使用できます。 + +```php +$paginator->isFirst(); // 最初のページですか? +$paginator->isLast(); // 最後のページですか? +$paginator->getPage(); // 現在のページ番号 +$paginator->getFirstPage(); // 最初のページ番号 +$paginator->getLastPage(); // 最後のページ番号 +$paginator->getFirstItemOnPage(); // ページ上の最初のアイテムのシーケンス番号 +$paginator->getLastItemOnPage(); // ページ上の最後のアイテムのシーケンス番号 +$paginator->getPageIndex(); // 0から始まる現在のページ番号 +$paginator->getPageCount(); // 合計ページ数 +$paginator->getItemsPerPage(); // ページあたりのアイテム数 +$paginator->getItemCount(); // 合計アイテム数(わかっている場合) +``` + +ページネータはSQLクエリの作成に役立ちます。`getLength()` と `getOffset()` メソッドは、LIMITおよびOFFSET句で使用する値を返します: + +```php +$result = $database->query( + 'SELECT * FROM items LIMIT ? OFFSET ?', + $paginator->getLength(), + $paginator->getOffset(), +); +``` + +逆順でページ分割する必要がある場合、つまりページ番号1が最大のオフセットに対応する場合、`getCountdownOffset()` を使用します: + +```php +$result = $database->query( + 'SELECT * FROM items LIMIT ? OFFSET ?', + $paginator->getLength(), + $paginator->getCountdownOffset(), +); +``` + +アプリケーションでの使用例は、クックブック [データベース結果のページ分割 |best-practices:pagination] にあります。 diff --git a/utils/ja/random.texy b/utils/ja/random.texy new file mode 100644 index 0000000000..557355eff2 --- /dev/null +++ b/utils/ja/random.texy @@ -0,0 +1,25 @@ +ランダム文字列の生成 +********** + +.[perex] +[api:Nette\Utils\Random] は、暗号学的に安全な疑似ランダム文字列を生成するための静的クラスです。 + + +インストール: + +```shell +composer require nette/utils +``` + + +generate(int $length=10, string $charlist=`'0-9a-z'`): string .[method] +----------------------------------------------------------------------- + +パラメータ `$charlist` で指定された文字から、指定された長さのランダムな文字列を生成します。`0-9` のような範囲を使用することもできます。 + +```php +use Nette\Utils\Random; + +Random::generate(10); // '6zq3a1nl8n' +Random::generate(5, 'A-Z'); // 'HLKUR' +``` diff --git a/utils/ja/reflection.texy b/utils/ja/reflection.texy new file mode 100644 index 0000000000..ae3d1d0c33 --- /dev/null +++ b/utils/ja/reflection.texy @@ -0,0 +1,134 @@ +PHPリフレクション +********** + +.[perex] +[api:Nette\Utils\Reflection] は、PHPリフレクションのための便利な関数を持つ静的クラスです。その目的は、ネイティブクラスの欠点を修正し、異なるPHPバージョン間で動作を統一することです。 + + +インストール: + +```shell +composer require nette/utils +``` + +すべての例は、エイリアスが作成されていることを前提としています: + +```php +use Nette\Utils\Reflection; +``` + + +areCommentsAvailable(): bool .[method] +-------------------------------------- + +リフレクションがPHPDocコメントにアクセスできるかどうかを調べます。コメントはオペコードキャッシュのために利用できない場合があります。例えば、[opcache.save-comments|https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments] ディレクティブを参照してください。 + + +expandClassName(string $name, ReflectionClass $context): string .[method] +------------------------------------------------------------------------- + +クラス名 `$name` をクラス `$context` のコンテキストで、つまりその名前空間と定義されたエイリアスのコンテキストで完全な名前に展開します。つまり、PHPパーサーがクラス `$context` の本体に書かれていた場合に `$name` をどのように理解するかを示します。 + +```php +namespace Foo; +use Bar; + +class DemoClass +{ + // new Bar, new Baz +} + +$context = new ReflectionClass(Foo\DemoClass::class); +Reflection::expandClassName('Bar', $context); // 'Bar' +Reflection::expandClassName('Baz', $context); // 'Foo\Baz' +``` + + +getMethodDeclaringMethod(ReflectionMethod $method): ReflectionMethod .[method] +------------------------------------------------------------------------------ + +メソッド `$method` の宣言を含むメソッドのリフレクションを返します。通常、各メソッドは自身の宣言ですが、メソッドの本体はトレイト内にあり、異なる名前で存在することもあります。 + +PHPは実際の宣言を特定するための十分な情報を提供しないため、Netteは信頼できる**はず**の独自のヒューリスティックを使用します。 + +```php +trait DemoTrait +{ + function foo() + { + } +} + + +class DemoClass +{ + use DemoTrait { + DemoTrait::foo as foo2; + } +} + + +$method = new ReflectionMethod('DemoClass::foo2'); +Reflection::getMethodDeclaringMethod($method); // ReflectionMethod('DemoTrait::foo') +``` + + +getPropertyDeclaringClass(ReflectionProperty $prop): ReflectionClass .[method] +------------------------------------------------------------------------------ + +プロパティ `$prop` の宣言を含むクラスまたはトレイトのリフレクションを返します。プロパティはトレイト内で宣言されることもあります。 + +PHPは実際の宣言を特定するための十分な情報を提供しないため、Netteは信頼できるとは**限らない**独自のヒューリスティックを使用します。 + +```php +trait DemoTrait +{ + public $foo; +} + + +class DemoClass +{ + use DemoTrait; +} + +$prop = new ReflectionProperty(DemoClass::class, 'foo'); +Reflection::getPropertyDeclaringClass($prop); // ReflectionClass('DemoTrait') +``` + + +isBuiltinType(string $type): bool .[method deprecated] +------------------------------------------------------ + +`$type` がPHPの組み込み型かどうかを調べます。そうでない場合は、クラス名です。 + +```php +Reflection::isBuiltinType('string'); // true +Reflection::isBuiltinType('Foo'); // false +``` + +.[note] +[Nette\Utils\Validator::isBuiltinType() |validators#isBuiltinType] を使用してください。 + + +toString($reflection): string .[method] +--------------------------------------- + +リフレクションを人間が読める文字列に変換します。 + +```php +$func = new ReflectionFunction('func'); +echo Reflection::toString($func); // 'func()' + +$class = new ReflectionClass('DemoClass'); +echo Reflection::toString($class); // 'DemoClass' + +$method = new ReflectionMethod('DemoClass', 'foo'); +echo Reflection::toString($method); // 'DemoClass::foo()' + +$param = new ReflectionParameter(['DemoClass', 'foo'], 'param'); +echo Reflection::toString($param); // '$param in DemoClass::foo()' + +$prop = new ReflectionProperty('DemoClass', 'foo'); +echo Reflection::toString($prop); // 'DemoClass::$foo' +``` diff --git a/utils/ja/smartobject.texy b/utils/ja/smartobject.texy new file mode 100644 index 0000000000..0f7a6a21e9 --- /dev/null +++ b/utils/ja/smartobject.texy @@ -0,0 +1,240 @@ +SmartObject +*********** + +.[perex] +SmartObjectは長年にわたりPHPのオブジェクトの動作を改善してきました。PHP 8.4以降、その機能はすべてPHP自体の一部となり、PHPにおける現代的なオブジェクト指向アプローチの先駆者としての歴史的使命を終えました。 + + +インストール: + +```shell +composer require nette/utils +``` + +SmartObjectは、当時のPHPのオブジェクトモデルの欠点を解決する革新的なソリューションとして2007年に誕生しました。PHPが多くのオブジェクト設計の問題に苦しんでいた時代に、開発者の作業を大幅に改善し、簡素化しました。Netteフレームワークの伝説的な一部となりました。オブジェクトプロパティへのアクセス制御から洗練されたシンタックスシュガーまで、PHPが何年も後になってようやく獲得した機能を提供しました。PHP 8.4の登場により、その機能がすべて言語のネイティブな一部となったため、その歴史的使命を終えました。PHPの開発を驚くべき17年も先取りしていました。 + +技術的には、SmartObjectは興味深い進化を遂げました。当初は、他のクラスが必要な機能を継承する `Nette\Object` クラスとして実装されていました。PHP 5.4でトレイトのサポートが導入されたことで、大きな変化が訪れました。これにより、`Nette\SmartObject` トレイトの形に変換され、より大きな柔軟性がもたらされました。開発者は、すでに別のクラスから継承しているクラスでも機能を利用できるようになりました。元の `Nette\Object` クラスはPHP 7.2(クラス名に `Object` という単語を使用することを禁止)の登場とともに消滅しましたが、`Nette\SmartObject` トレイトは生き続けています。 + +かつて `Nette\Object`、後に `Nette\SmartObject` が提供していた機能を見ていきましょう。これらの機能はそれぞれ、当時のPHPにおけるオブジェクト指向プログラミングの分野で重要な一歩を踏み出しました。 + + +一貫したエラー状態 +--------- +初期のPHPの最も厄介な問題の1つは、オブジェクトを扱う際の一貫性のない動作でした。`Nette\Object` は、この混乱に秩序と予測可能性をもたらしました。PHPの元の動作を見てみましょう: + +```php +echo $obj->undeclared; // E_NOTICE、後に E_WARNING +$obj->undeclared = 1; // 報告なしで静かに通過します +$obj->unknownMethod(); // Fatal error (try/catchで捕捉不可) +``` + +致命的なエラーは、反応する機会なくアプリケーションを終了させました。存在しないメンバーへの警告なしの静かな書き込みは、発見が困難な重大なエラーにつながる可能性がありました。`Nette\Object` はこれらすべてのケースを捕捉し、`MemberAccessException` 例外をスローしました。これにより、プログラマはエラーに対応し、解決することができました。 + +```php +echo $obj->undeclared; // Nette\MemberAccessException をスローします +$obj->undeclared = 1; // Nette\MemberAccessException をスローします +$obj->unknownMethod(); // Nette\MemberAccessException をスローします +``` + +PHP 7.0以降、言語は捕捉不可能な致命的エラーを引き起こさなくなり、PHP 8.2以降、未宣言メンバーへのアクセスはエラーと見なされます。 + + +ヘルプ "Did you mean?" +------------------- +`Nette\Object` は非常に便利な機能をもたらしました:タイプミス時のインテリジェントなヘルプです。開発者がメソッド名や変数名で間違いを犯した場合、エラーを報告するだけでなく、正しい名前の提案という形で助けの手を差し伸べました。この象徴的なメッセージは「did you mean?」として知られ、プログラマがタイプミスを探す時間を何時間も節約しました: + +```php +class Foo extends Nette\Object +{ + public static function from($var) + { + } +} + +$foo = Foo::form($var); +// Nette\MemberAccessException をスローします +// "Call to undefined static method Foo::form(), did you mean from()?" +``` + +今日のPHPには「did you mean?」のような形式はありませんが、この追記は [Tracy|tracy:] によってエラーに追加できます。そして、そのようなエラーを [自動的に修正 |tracy:open-files-in-ide#デモ] することもできます。 + + +アクセス制御付きプロパティ +------------- +SmartObjectがPHPにもたらした重要な革新は、アクセス制御付きプロパティでした。C#やPythonなどの言語で一般的なこの概念により、開発者はオブジェクトのデータへのアクセスをエレガントに制御し、その一貫性を確保できました。プロパティはオブジェクト指向プログラミングの強力なツールです。変数のように機能しますが、実際にはメソッド(ゲッターとセッター)によって表されます。これにより、入力を検証したり、読み取り時に値を生成したりできます。 + +プロパティを使用するには: +- クラスに `@property $xyz` の形式でアノテーションを追加します +- `getXyz()` または `isXyz()` という名前のゲッター、`setXyz()` という名前のセッターを作成します +- ゲッターとセッターが *public* または *protected* であることを確認します。これらはオプションです - したがって、*read-only* または *write-only* プロパティとして存在できます + +Circleクラスでプロパティを使用して、半径が常に非負数であることを保証する実践的な例を見てみましょう。元の `public $radius` をプロパティに置き換えます: + +```php +/** + * @property float $radius + * @property-read bool $visible + */ +class Circle +{ + use Nette\SmartObject; + + private float $radius = 0.0; // publicではありません! + + // プロパティ $radius のゲッター + protected function getRadius(): float + { + return $this->radius; + } + + // プロパティ $radius のセッター + protected function setRadius(float $radius): void + { + // 保存する前に値をサニタイズします + $this->radius = max(0.0, $radius); + } + + // プロパティ $visible のゲッター + protected function isVisible(): bool + { + return $this->radius > 0; + } +} + +$circle = new Circle; +$circle->radius = 10; // 実際には setRadius(10) を呼び出します +echo $circle->radius; // getRadius() を呼び出します +echo $circle->visible; // isVisible() を呼び出します +``` + +PHP 8.4以降、プロパティフックを使用して同じ機能を実現できます。これは、はるかにエレガントで簡潔な構文を提供します: + +```php +class Circle +{ + public float $radius = 0.0 { + set => max(0.0, $value); + } + + public bool $visible { + get => $this->radius > 0; + } +} +``` + + +拡張メソッド +------ +`Nette\Object` は、現代的なプログラミング言語に触発された別の興味深い概念、拡張メソッドをPHPにもたらしました。C#から借用されたこの機能により、開発者は既存のクラスを変更したり継承したりすることなく、新しいメソッドでエレガントに拡張できました。例えば、フォームに独自のDateTimePickerを追加する `addDateTime()` メソッドを追加できました: + +```php +Form::extensionMethod( + 'addDateTime', + fn(Form $form, string $name) => $form[$name] = new DateTimePicker, +); + +$form = new Form; +$form->addDateTime('date'); +``` + +拡張メソッドは、エディタがその名前を補完せず、逆にメソッドが存在しないと報告するため、実用的ではないことが判明しました。そのため、そのサポートは終了しました。今日では、クラスの機能を拡張するためにコンポジションや継承を使用する方が一般的です。 + + +クラス名の取得 +------- +クラス名を取得するために、SmartObjectは簡単なメソッドを提供していました: + +```php +$class = $obj->getClass(); // Nette\Object を使用 +$class = $obj::class; // PHP 8.0 以降 +``` + + +リフレクションとアノテーションへのアクセス +--------------------- +`Nette\Object` は、`getReflection()` および `getAnnotation()` メソッドを使用してリフレクションとアノテーションへのアクセスを提供しました。このアプローチは、クラスのメタ情報の操作を大幅に簡素化しました: + +```php +/** + * @author John Doe + */ +class Foo extends Nette\Object +{ +} + +$obj = new Foo; +$reflection = $obj->getReflection(); +$reflection->getAnnotation('author'); // 'John Doe' を返します +``` + +PHP 8.0以降、属性の形でメタ情報にアクセスできます。これは、さらに多くの可能性とより良い型制御を提供します: + +```php +#[Author('John Doe')] +class Foo +{ +} + +$obj = new Foo; +$reflection = new ReflectionObject($obj); +$reflection->getAttributes(Author::class)[0]; +``` + + +メソッドゲッター +-------- +`Nette\Object` は、メソッドを変数であるかのように渡すエレガントな方法を提供しました: + +```php +class Foo extends Nette\Object +{ + public function adder($a, $b) + { + return $a + $b; + } +} + +$obj = new Foo; +$method = $obj->adder; +echo $method(2, 3); // 5 +``` + +PHP 8.1以降、いわゆる [first-class callable syntax|https://www.php.net/manual/en/functions.first_class_callable_syntax.php] を利用できます。これはこの概念をさらに推し進めます: + +```php +$obj = new Foo; +$method = $obj->adder(...); +echo $method(2, 3); // 5 +``` + + +イベント +---- +SmartObjectは、[イベント |nette:glossary#イベント]を扱うための簡略化された構文を提供します。イベントにより、オブジェクトは自身の状態の変化についてアプリケーションの他の部分に通知できます: + +```php +class Circle extends Nette\Object +{ + public array $onChange = []; + + public function setRadius(float $radius): void + { + $this->onChange($this, $radius); + $this->radius = $radius; + } +} +``` + +コード `$this->onChange($this, $radius)` は、次のループと同等です: + +```php +foreach ($this->onChange as $callback) { + $callback($this, $radius); +} +``` + +わかりやすさのため、マジックメソッド `$this->onChange()` を避けることをお勧めします。実用的な代替手段は、例えば [Nette\Utils\Arrays::invoke |arrays#invoke] 関数です: + +```php +Nette\Utils\Arrays::invoke($this->onChange, $this, $radius); +``` diff --git a/utils/ja/staticclass.texy b/utils/ja/staticclass.texy new file mode 100644 index 0000000000..feb9889924 --- /dev/null +++ b/utils/ja/staticclass.texy @@ -0,0 +1,21 @@ +静的クラス +***** + +.[perex] +StaticClassは静的クラスをマークするために使用されます。 + + +インストール: + +```shell +composer require nette/utils +``` + +静的クラス、つまりインスタンスを作成することを意図していないクラスは、[api:Nette\StaticClass] トレイトでマークできます: + +```php +class Strings +{ + use Nette\StaticClass; +} +``` diff --git a/utils/ja/strings.texy b/utils/ja/strings.texy new file mode 100644 index 0000000000..a1493ef9b1 --- /dev/null +++ b/utils/ja/strings.texy @@ -0,0 +1,637 @@ +文字列の操作 +****** + +.[perex] +[api:Nette\Utils\Strings] は、主にUTF-8エンコーディングの文字列を操作するための便利な関数を持つ静的クラスです。 + + +インストール: + +```shell +composer require nette/utils +``` + +すべての例は、エイリアスが作成されていることを前提としています: + +```php +use Nette\Utils\Strings; +``` + + +大文字小文字の変更 +========= + +これらの関数にはPHP拡張機能 `mbstring` が必要です。 + + +lower(string $s): string .[method] +---------------------------------- + +UTF-8文字列を小文字に変換します。 + +```php +Strings::lower('Dobrý den'); // 'dobrý den' +``` + + +upper(string $s): string .[method] +---------------------------------- + +UTF-8文字列を大文字に変換します。 + +```php +Strings::upper('Dobrý den'); // 'DOBRÝ DEN' +``` + + +firstUpper(string $s): string .[method] +--------------------------------------- + +UTF-8文字列の最初の文字を大文字に変換し、残りは変更しません。 + +```php +Strings::firstUpper('dobrý den'); // 'Dobrý den' +``` + + +firstLower(string $s): string .[method] +--------------------------------------- + +UTF-8文字列の最初の文字を小文字に変換し、残りは変更しません。 + +```php +Strings::firstLower('Dobrý den'); // 'dobrý den' +``` + + +capitalize(string $s): string .[method] +--------------------------------------- + +UTF-8文字列の各単語の最初の文字を大文字に変換し、残りを小文字に変換します。 + +```php +Strings::capitalize('Dobrý den'); // 'Dobrý Den' +``` + + +文字列の編集 +====== + + +normalize(string $s): string .[method] +-------------------------------------- + +制御文字を削除し、改行を `\n` に正規化し、先頭と末尾の空行をトリムし、行末の右側のスペースをトリムし、UTF-8を正規形式NFCに正規化します。 + + +unixNewLines(string $s): string .[method] +----------------------------------------- + +改行をUnixシステムで使用される `\n` に変換します。改行は `\n`、`\r`、`\r\n`、U+2028 行区切り文字、U+2029 段落区切り文字です。 + +```php +$unixLikeLines = Strings::unixNewLines($string); +``` + + +platformNewLines(string $s): string .[method] +--------------------------------------------- + +改行を現在のプラットフォーム固有の文字に変換します。つまり、Windowsでは `\r\n`、その他では `\n` です。改行は `\n`、`\r`、`\r\n`、U+2028 行区切り文字、U+2029 段落区切り文字です。 + +```php +$platformLines = Strings::platformNewLines($string); +``` + + +webalize(string $s, ?string $charlist=null, bool $lower=true): string .[method] +------------------------------------------------------------------------------- + +UTF-8文字列をURLで使用される形式に編集します。つまり、発音区別符号を削除し、英字と数字以外のすべての文字をハイフンに置き換えます。 + +```php +Strings::webalize('náš produkt'); // 'nas-produkt' +``` + +他の文字も保持したい場合は、関数の2番目のパラメータで指定できます。 + +```php +Strings::webalize('10. obrázek_id', '._'); // '10.-obrazek_id' +``` + +3番目のパラメータで小文字への変換を抑制できます。 + +```php +Strings::webalize('Dobrý den', null, false); // 'Dobry-den' +``` + +.[caution] +PHP拡張機能 `intl` が必要です。 + + +trim(string $s, ?string $charlist=null): string .[method] +--------------------------------------------------------- + +UTF-8文字列の先頭と末尾から空白(または2番目のパラメータで指定された他の文字)をトリムします。 + +```php +Strings::trim(' Hello '); // 'Hello' +``` + + +truncate(string $s, int $maxLen, string $append=`'…'`): string .[method] +------------------------------------------------------------------------ + +UTF-8文字列を指定された最大長に切り詰めますが、単語全体を保持しようとします。文字列が短縮された場合、末尾に三点リーダーを追加します(3番目のパラメータで変更可能)。 + +```php +$text = 'Řekněte, jak se máte?'; +Strings::truncate($text, 5); // 'Řekn…' +Strings::truncate($text, 20); // 'Řekněte, jak se…' +Strings::truncate($text, 30); // 'Řekněte, jak se máte?' +Strings::truncate($text, 20, '~'); // 'Řekněte, jak se~' +``` + + +indent(string $s, int $level=1, string $indentationChar=`"\t"`): string .[method] +--------------------------------------------------------------------------------- + +複数行テキストを左からインデントします。インデントの数は2番目のパラメータで、インデントに使用する文字は3番目のパラメータで指定します(デフォルト値はタブ)。 + +```php +Strings::indent('Nette'); // "\tNette" +Strings::indent('Nette', 2, '+'); // '++Nette' +``` + + +padLeft(string $s, int $length, string $pad=`' '`): string .[method] +-------------------------------------------------------------------- + +UTF-8文字列を、左から文字列 `$pad` を繰り返して指定された長さに補完します。 + +```php +Strings::padLeft('Nette', 6); // ' Nette' +Strings::padLeft('Nette', 8, '+*'); // '+*+Nette' +``` + + +padRight(string $s, int $length, string $pad=`' '`): string .[method] +--------------------------------------------------------------------- + +UTF-8文字列を、右から文字列 `$pad` を繰り返して指定された長さに補完します。 + +```php +Strings::padRight('Nette', 6); // 'Nette ' +Strings::padRight('Nette', 8, '+*'); // 'Nette+*+' +``` + + +substring(string $s, int $start, ?int $length=null): string .[method] +--------------------------------------------------------------------- + +UTF-8文字列 `$s` の一部を、開始位置 `$start` と長さ `$length` で指定して返します。`$start` が負の場合、返される文字列は末尾から `-`$start` 番目の文字から始まります。 + +```php +Strings::substring('Nette Framework', 0, 5); // 'Nette' +Strings::substring('Nette Framework', 6); // 'Framework' +Strings::substring('Nette Framework', -4); // 'work' +``` + + +reverse(string $s): string .[method] +------------------------------------ + +UTF-8文字列を反転します。 + +```php +Strings::reverse('Nette'); // 'etteN' +``` + + +length(string $s): int .[method] +-------------------------------- + +UTF-8文字列の文字数(バイト数ではない)を返します。 + +これはUnicodeコードポイントの数であり、書記素の数とは異なる場合があります。 + +```php +Strings::length('Nette'); // 5 +Strings::length('červená'); // 7 +``` + + +startsWith(string $haystack, string $needle): bool .[method deprecated] +----------------------------------------------------------------------- + +文字列 `$haystack` が文字列 `$needle` で始まるかどうかを調べます。 + +```php +$haystack = 'Začíná'; +$needle = 'Za'; +Strings::startsWith($haystack, $needle); // true +``` + +.[note] +ネイティブの `str_starts_with()`:https://www.php.net/manual/en/function.str-starts-with.php を使用してください。 + + +endsWith(string $haystack, string $needle): bool .[method deprecated] +--------------------------------------------------------------------- + +文字列 `$haystack` が文字列 `$needle` で終わるかどうかを調べます。 + +```php +$haystack = 'Končí'; +$needle = 'čí'; +Strings::endsWith($haystack, $needle); // true +``` + +.[note] +ネイティブの `str_ends_with()`:https://www.php.net/manual/en/function.str-ends-with.php を使用してください。 + + +contains(string $haystack, string $needle): bool .[method deprecated] +--------------------------------------------------------------------- + +文字列 `$haystack` が `$needle` を含むかどうかを調べます。 + +```php +$haystack = 'Posluchárna'; +$needle = 'sluch'; +Strings::contains($haystack, $needle); // true +``` + +.[note] +ネイティブの `str_contains()`:https://www.php.net/manual/en/function.str-contains.php を使用してください。 + + +compare(string $left, string $right, ?int $length=null): bool .[method] +----------------------------------------------------------------------- + +2つのUTF-8文字列またはその一部を、大文字小文字を区別せずに比較します。`$length` がnullの場合、文字列全体が比較されます。負の場合、文字列の末尾から対応する文字数が比較されます。それ以外の場合、文字列の先頭から対応する文字数が比較されます。 + +```php +Strings::compare('Nette', 'nette'); // true +Strings::compare('Nette', 'next', 2); // true - 最初の2文字が一致 +Strings::compare('Nette', 'Latte', -2); // true - 最後の2文字が一致 +``` + + +findPrefix(...$strings): string .[method] +----------------------------------------- + +文字列の共通の接頭辞を見つけます。共通の接頭辞が見つからない場合は空の文字列を返します。 + +```php +Strings::findPrefix('prefix-a', 'prefix-bb', 'prefix-c'); // 'prefix-' +Strings::findPrefix(['prefix-a', 'prefix-bb', 'prefix-c']); // 'prefix-' +Strings::findPrefix('Nette', 'is', 'great'); // '' +``` + + +before(string $haystack, string $needle, int $nth=1): ?string .[method] +----------------------------------------------------------------------- + +文字列 `$haystack` の中で、文字列 `$needle` のn番目(`$nth`)の出現箇所の前の部分を返します。`$needle` が見つからない場合は `null` を返します。`$nth` が負の値の場合、文字列の末尾から検索します。 + +```php +Strings::before('Nette_is_great', '_', 1); // 'Nette' +Strings::before('Nette_is_great', '_', -2); // 'Nette' +Strings::before('Nette_is_great', ' '); // null +Strings::before('Nette_is_great', '_', 3); // null +``` + + +after(string $haystack, string $needle, int $nth=1): ?string .[method] +---------------------------------------------------------------------- + +文字列 `$haystack` の中で、文字列 `$needle` のn番目(`$nth`)の出現箇所の後の部分を返します。`$needle` が見つからない場合は `null` を返します。`$nth` が負の値の場合、文字列の末尾から検索します。 + +```php +Strings::after('Nette_is_great', '_', 2); // 'great' +Strings::after('Nette_is_great', '_', -1); // 'great' +Strings::after('Nette_is_great', ' '); // null +Strings::after('Nette_is_great', '_', 3); // null +``` + + +indexOf(string $haystack, string $needle, int $nth=1): ?int .[method] +--------------------------------------------------------------------- + +文字列 `$haystack` 内の文字列 `$needle` のn番目(`$nth`)の出現位置を文字数で返します。`$needle` が見つからない場合は `null` を返します。`$nth` が負の値の場合、文字列の末尾から検索します。 + +```php +Strings::indexOf('abc abc abc', 'abc', 2); // 4 +Strings::indexOf('abc abc abc', 'abc', -1); // 8 +Strings::indexOf('abc abc abc', 'd'); // null +``` + + +エンコーディング +======== + + +fixEncoding(string $s): string .[method] +---------------------------------------- + +文字列から無効なUTF-8文字を削除します。 + +```php +$correctStrings = Strings::fixEncoding($string); +``` + + +checkEncoding(string $s): bool .[method deprecated] +--------------------------------------------------- + +有効なUTF-8文字列かどうかを調べます。 + +```php +$isUtf8 = Strings::checkEncoding($string); +``` + +.[note] +[Nette\Utils\Validator::isUnicode() |validators#isUnicode] を使用してください。 + + +toAscii(string $s): string .[method] +------------------------------------ + +UTF-8文字列をASCIIに変換します。つまり、発音区別符号などを削除します。 + +```php +Strings::toAscii('žluťoučký kůň'); // 'zlutoucky kun' +``` + +.[caution] +PHP拡張機能 `intl` が必要です。 + + +chr(int $code): string .[method] +-------------------------------- + +コードポイント(0x0000..D7FFおよび0xE000..10FFFFの範囲の数値)から特定のUTF-8文字を返します。 + +```php +Strings::chr(0xA9); // UTF-8エンコーディングの '©' +``` + + +ord(string $char): int .[method] +-------------------------------- + +特定のUTF-8文字のコードポイント(0x0000..D7FFまたは0xE000..10FFFFの範囲の数値)を返します。 + +```php +Strings::ord('©'); // 0xA9 +``` + + +正規表現 +==== + +Stringsクラスは正規表現を扱うための関数を提供します。ネイティブのPHP関数とは異なり、よりわかりやすいAPI、より良いUnicodeサポート、そして特にエラー検出を備えています。式のコンパイルまたは処理中のエラーはすべて `Nette\RegexpException` 例外をスローします。 + + +split(string $subject, string $pattern, bool $captureOffset=false, bool $skipEmpty=false, int $limit=-1, bool $utf8=false): array .[method] +------------------------------------------------------------------------------------------------------------------------------------------- + +正規表現に従って文字列を配列に分割します。括弧内の式もキャプチャされ、返されます。 + +```php +Strings::split('hello, world', '~,\s*~'); +// ['hello', 'world'] + +Strings::split('hello, world', '~(,)\s*~'); +// ['hello', ',', 'world']`` +``` + +`$skipEmpty` が `true` の場合、空でない項目のみが返されます: + +```php +Strings::split('hello, world, ', '~,\s*~'); +// ['hello', 'world', ''] + +Strings::split('hello, world, ', '~,\s*~', skipEmpty: true); +// ['hello', 'world'] +``` + +`$limit` が指定されている場合、制限までの部分文字列のみが返され、文字列の残りは最後の要素に配置されます。制限-1または0は制限なしを意味します。 + +```php +Strings::split('hello, world, third', '~,\s*~', limit: 2); +// ['hello', 'world, third'] +``` + +`$utf8` が `true` の場合、評価はUnicodeモードに切り替わります。`u` 修飾子を指定した場合と同様です。 + +`$captureOffset` が `true` の場合、発生する各一致について、文字列内の位置(バイト単位、`$utf8` が設定されている場合は文字単位)も返されます。これにより、戻り値が配列に変更され、各要素は一致した文字列とその位置のペアになります。 + +```php +Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true); +// [['žlutý', 0], ['kůň', 9]] + +Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true, utf8: true); +// [['žlutý', 0], ['kůň', 7]] +``` + + +match(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $utf8=false): ?array .[method] +-------------------------------------------------------------------------------------------------------------------------------------------------- + +文字列内で正規表現に一致する部分を検索し、見つかった式と個々の部分式を含む配列、または `null` を返します。 + +```php +Strings::match('hello!', '~\w+(!+)~'); +// ['hello!', '!'] + +Strings::match('hello!', '~X~'); +// null +``` + +`$unmatchedAsNull` が `true` の場合、キャプチャされなかった部分パターンはnullとして返されます。それ以外の場合は、空の文字列として返されるか、返されません: + +```php +Strings::match('hello', '~\w+(!+)?~'); +// ['hello'] + +Strings::match('hello', '~\w+(!+)?~', unmatchedAsNull: true); +// ['hello', null] +``` + +`$utf8` が `true` の場合、評価はUnicodeモードに切り替わります。`u` 修飾子を指定した場合と同様です: + +```php +Strings::match('žlutý kůň', '~\w+~'); +// ['lut'] + +Strings::match('žlutý kůň', '~\w+~', utf8: true); +// ['žlutý'] +``` + +パラメータ `$offset` を使用して、検索を開始する位置(バイト単位、`$utf8` が設定されている場合は文字単位)を指定できます。 + +`$captureOffset` が `true` の場合、発生する各一致について、文字列内の位置(バイト単位、`$utf8` が設定されている場合は文字単位)も返されます。これにより、戻り値が配列に変更され、各要素は一致した文字列とそのオフセットのペアになります: + +```php +Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true); +// [['lut', 2]] + +Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true, utf8: true); +// [['žlutý!', 0], ['!', 5]] +``` + + +matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false, bool $lazy=false): array|Generator .[method] +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +文字列内で正規表現に一致するすべての出現箇所を検索し、見つかった式と個々の部分式を含む配列の配列を返します。 + +```php +Strings::matchAll('hello, world!!', '~\w+(!+)?~'); +/* [ + 0 => ['hello'], + 1 => ['world!!', '!!'], +] */ +``` + +`$patternOrder` が `true` の場合、結果の構造が変更され、最初の項目が完全なパターン一致の配列、2番目の項目が最初の括弧内の部分パターンに一致する文字列の配列、というようになります: + +```php +Strings::matchAll('hello, world!!', '~\w+(!+)?~', patternOrder: true); +/* [ + 0 => ['hello', 'world!!'], + 1 => ['', '!!'], +] */ +``` + +`$unmatchedAsNull` が `true` の場合、キャプチャされなかった部分パターンはnullとして返されます。それ以外の場合は、空の文字列として返されるか、返されません: + +```php +Strings::matchAll('hello, world!!', '~\w+(!+)?~', unmatchedAsNull: true); +/* [ + 0 => ['hello', null], + 1 => ['world!!', '!!'], +] */ +``` + +`$utf8` が `true` の場合、評価はUnicodeモードに切り替わります。`u` 修飾子を指定した場合と同様です: + +```php +Strings::matchAll('žlutý kůň', '~\w+~'); +/* [ + 0 => ['lut'], + 1 => ['k'], +] */ + +Strings::matchAll('žlutý kůň', '~\w+~', utf8: true); +/* [ + 0 => ['žlutý'], + 1 => ['kůň'], +] */ +``` + +パラメータ `$offset` を使用して、検索を開始する位置(バイト単位、`$utf8` が設定されている場合は文字単位)を指定できます。 + +`$captureOffset` が `true` の場合、発生する各一致について、文字列内の位置(バイト単位、`$utf8` が設定されている場合は文字単位)も返されます。これにより、戻り値が配列に変更され、各要素は一致した文字列とその位置のペアになります: + +```php +Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true); +/* [ + 0 => [['lut', 2]], + 1 => [['k', 8]], +] */ + +Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true, utf8: true); +/* [ + 0 => [['žlutý', 0]], + 1 => [['kůň', 6]], +] */ +``` + +`$lazy` が `true` の場合、関数は配列の代わりに `Generator` を返します。これは、大きな文字列を扱う際に重要なパフォーマンス上の利点をもたらします。ジェネレータを使用すると、文字列全体を一度にではなく、一致を段階的に検索できます。これにより、非常に大きな入力テキストでも効率的に作業できます。さらに、探している一致が見つかった時点でいつでも処理を中断できるため、計算時間を節約できます。 + +```php +$matches = Strings::matchAll($largeText, '~\w+~', lazy: true); +foreach ($matches as $match) { + echo "見つかりました: $match[0]\n"; + // 処理はいつでも中断できます +} +``` + + +replace(string $subject, string|array $pattern, string|callable $replacement='', int $limit=-1, bool $captureOffset=false, bool $unmatchedAsNull=false, bool $utf8=false): string .[method] +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +正規表現に一致するすべての出現箇所を置換します。`$replacement` は置換文字列のマスクまたはコールバックです。 + +```php +Strings::replace('hello, world!', '~\w+~', '--'); +// '--, --!' + +Strings::replace('hello, world!', '~\w+~', fn($m) => strrev($m[0])); +// 'olleh, dlrow!' +``` + +この関数は、2番目のパラメータに `pattern => replacement` の形式で配列を渡すことで、複数の置換を実行することもできます: + +```php +Strings::replace('hello, world!', [ + '~\w+~' => '--', + '~,\s+~' => ' ', +]); +// '-- --!' +``` + +パラメータ `$limit` は実行される置換の数を制限します。制限-1は制限なしを意味します。 + +`$utf8` が `true` の場合、評価はUnicodeモードに切り替わります。`u` 修飾子を指定した場合と同様です。 + +```php +Strings::replace('žlutý kůň', '~\w+~', '--'); +// 'ž--ý --ůň' + +Strings::replace('žlutý kůň', '~\w+~', '--', utf8: true); +// '-- --' +``` + +`$captureOffset` が `true` の場合、発生する各一致について、文字列内の位置(バイト単位、`$utf8` が設定されている場合は文字単位)もコールバックに渡されます。これにより、渡される配列の形式が変更され、各要素は一致した文字列とその位置のペアになります。 + +```php +Strings::replace( + 'žlutý kůň', + '~\w+~', + function (array $m) { dump($m); return ''; }, + captureOffset: true, +); +// dumps [['lut', 2]] と [['k', 8]] + +Strings::replace( + 'žlutý kůň', + '~\w+~', + function (array $m) { dump($m); return ''; }, + captureOffset: true, + utf8: true, +); +// dumps [['žlutý', 0]] と [['kůň', 6]] +``` + +`$unmatchedAsNull` が `true` の場合、キャプチャされなかった部分パターンはnullとしてコールバックに渡されます。それ以外の場合は、空の文字列として渡されるか、渡されません: + +```php +Strings::replace( + 'ac', + '~(a)(b)*(c)~', + function (array $m) { dump($m); return ''; }, +); +// dumps ['ac', 'a', '', 'c'] + +Strings::replace( + 'ac', + '~(a)(b)*(c)~', + function (array $m) { dump($m); return ''; }, + unmatchedAsNull: true, +); +// dumps ['ac', 'a', null, 'c'] +``` diff --git a/utils/ja/type.texy b/utils/ja/type.texy new file mode 100644 index 0000000000..2abc2917a3 --- /dev/null +++ b/utils/ja/type.texy @@ -0,0 +1,199 @@ +PHP 型 +***** + +.[perex] +[api:Nette\Utils\Type] は、PHPのデータ型を扱うためのクラスです。 + + +インストール: + +```shell +composer require nette/utils +``` + +すべての例は、エイリアスが作成されていることを前提としています: + +```php +use Nette\Utils\Type; +``` + + +fromReflection($reflection): ?Type .[method] +-------------------------------------------- + +静的メソッドは、リフレクションに基づいてTypeオブジェクトを作成します。パラメータは `ReflectionMethod` または `ReflectionFunction` オブジェクト(戻り値の型を返す)または `ReflectionParameter` または `ReflectionProperty` です。`self`、`static`、`parent` を実際のクラス名に変換します。サブジェクトに型がない場合は `null` を返します。 + +```php +class DemoClass +{ + public self $foo; +} + +$prop = new ReflectionProperty(DemoClass::class, 'foo'); +echo Type::fromReflection($prop); // 'DemoClass' +``` + + +fromString(string $type): Type .[method] +---------------------------------------- + +静的メソッドは、テキスト表記に基づいてTypeオブジェクトを作成します。 + +```php +$type = Type::fromString('Foo|Bar'); +echo $type; // 'Foo|Bar' +``` + + +getNames(): (string|array)[] .[method] +-------------------------------------- + +複合型を構成するサブタイプの配列を文字列として返します。 + +```php +$type = Type::fromString('string|null'); // または '?string' +$type->getNames(); // ['string', 'null'] + +$type = Type::fromString('(Foo&Bar)|string'); +$type->getNames(); // [['Foo', 'Bar'], 'string'] +``` + + +getTypes(): Type[] .[method] +---------------------------- + +複合型を構成するサブタイプの配列を `ReflectionType` オブジェクトとして返します: + +```php +$type = Type::fromString('string|null'); // or '?string' +$type->getTypes(); // [Type::fromString('string'), Type::fromString('null')] + +$type = Type::fromString('(Foo&Bar)|string'); +$type->getTypes(); // [Type::fromString('Foo&Bar'), Type::fromString('string')] + +$type = Type::fromString('Foo&Bar'); +$type->getTypes(); // [Type::fromString('Foo'), Type::fromString('Bar')] +``` + + +getSingleName(): ?string .[method] +---------------------------------- + +単純な型の場合、型名を返します。それ以外の場合はnullを返します。 + +```php +$type = Type::fromString('string|null'); +echo $type; // '?string' +echo $type->getSingleName(); // 'string' + +$type = Type::fromString('?Foo'); +echo $type; // '?Foo' +echo $type->getSingleName(); // 'Foo' + +$type = Type::fromString('Foo|Bar'); +echo $type; // 'Foo|Bar' +echo $type->getSingleName(); // null +``` + + +isSimple(): bool .[method] +-------------------------- + +単純な型かどうかを返します。単純なnull許容型も単純な型と見なされます: + +```php +$type = Type::fromString('string'); +$type->isSimple(); // true +$type->isUnion(); // false + +$type = Type::fromString('?Foo'); // または 'Foo|null' +$type->isSimple(); // true +$type->isUnion(); // true +``` + + +isUnion(): bool .[method] +------------------------- + +共用体型かどうかを返します。 + +```php +$type = Type::fromString('string|int'); +$type->isUnion(); // true +``` + + +isIntersection(): bool .[method] +-------------------------------- + +交差型かどうかを返します。 + + +```php +$type = Type::fromString('Foo&Bar'); +$type->isIntersection(); // true +``` + + +isBuiltin(): bool .[method] +--------------------------- + +型が単純であり、かつPHPの組み込み型であるかどうかを返します。 + +```php +$type = Type::fromString('string'); +$type->isBuiltin(); // true + +$type = Type::fromString('string|int'); +$type->isBuiltin(); // false + +$type = Type::fromString('Foo'); +$type->isBuiltin(); // false +``` + + +isClass(): bool .[method] +------------------------- + +型が単純であり、かつクラス名であるかどうかを返します。 + +```php +$type = Type::fromString('string'); +$type->isClass(); // false + +$type = Type::fromString('Foo|null'); +$type->isClass(); // true + +$type = Type::fromString('Foo|Bar'); +$type->isClass(); // false +``` + + +isClassKeyword(): bool .[method] +-------------------------------- + +型が内部型 `self`、`parent`、`static` のいずれかであるかどうかを返します。 + +```php +$type = Type::fromString('self'); +$type->isClassKeyword(); // true + +$type = Type::fromString('Foo'); +$type->isClassKeyword(); // false +``` + + +allows(string $type): bool .[method] +------------------------------------ + +`allows()` メソッドは型の互換性を検証します。例えば、特定の型の値がパラメータとして渡される可能性があるかどうかを判断できます。 + +```php +$type = Type::fromString('string|null'); +$type->allows('string'); // true +$type->allows('null'); // true +$type->allows('Foo'); // false + +$type = Type::fromString('mixed'); +$type->allows('null'); // true +``` diff --git a/utils/ja/validators.texy b/utils/ja/validators.texy new file mode 100644 index 0000000000..def77fdf0d --- /dev/null +++ b/utils/ja/validators.texy @@ -0,0 +1,315 @@ +値バリデータ +****** + +.[perex] +変数に有効なメールアドレスなどが含まれているかを迅速かつ簡単に検証する必要がありますか?その場合、値の検証に役立つ関数を持つ静的クラス [api:Nette\Utils\Validators] が便利です。 + + +インストール: + +```shell +composer require nette/utils +``` + +すべての例は、エイリアスが作成されていることを前提としています: + +```php +use Nette\Utils\Validators; +``` + + +基本的な使用法 +======= + +このクラスには、[Unicodeかどうか |#isUnicode]、[Eメールかどうか |#isEmail]、[URLかどうか |#isUrl] など、コードで使用するための値をチェックする多くのメソッドがあります: + +```php +if (!Validators::isEmail($email)) { + throw new InvalidArgumentException; +} +``` + +さらに、値がいわゆる [#期待される型] であるかどうかを検証できます。これは、個々のオプションがパイプ `|` で区切られた文字列です。これにより、[#is()] を使用して複数の型を簡単に検証できます: + +```php +if (!Validators::is($val, 'int|string|bool')) { + // ... +} +``` + +しかし、これはまた、期待値を文字列として記述する必要があるシステム(例えば、アノテーションや設定ファイル内)を作成し、それに基づいて値を検証する可能性も与えてくれます。 + +期待される型に対して [#assert()] 要求を適用することもできます。これが満たされない場合、例外がスローされます。 + + +期待される型 +====== + +期待される型は、PHPで型を記述する方法と同様に、パイプ `|` で区切られた1つ以上のバリアントで構成される文字列です(例: `'int|string|bool'`)。null許容表記 `?int` も受け入れられます。 + +すべての要素が特定の型である配列は、`int[]` の形式で記述されます。 + +一部の型の後には、コロンと長さ `:length` または範囲 `:[min]..[max]` が続くことがあります。例: `string:10`(長さ10バイトの文字列)、`float:10..`(10以上の数値)、`array:..10`(10要素以下の配列)、`list:10..20`(10〜20要素のリスト)、または `pattern:[0-9]+` の正規表現。 + +型とルールの概要: + +.[wide] +| PHP 型 || +|-------------------------- +| `array` .{width: 140px} | 要素数の範囲を指定できます +| `bool` | +| `float` | 値の範囲を指定できます +| `int` | 値の範囲を指定できます +| `null` | +| `object` | +| `resource` | +| `scalar` | int\|float\|bool\|string +| `string` | バイト単位の長さの範囲を指定できます +| `callable` | +| `iterable` | +| `mixed` | +|-------------------------- +| 疑似型 || +|------------------------------------------------ +| `list` | インデックス付き配列、要素数の範囲を指定できます +| `none` | 空の値: `''`、`null`、`false` +| `number` | int\|float +| `numeric` | [テキスト表現を含む数値 |#isNumeric] +| `numericint`| [テキスト表現を含む整数 |#isNumericInt] +| `unicode` | [UTF-8文字列 |#isUnicode]、文字単位の長さの範囲を指定できます +|-------------------------- +| 文字クラス (空文字列であってはなりません) || +|------------------------------------------------ +| `alnum` | すべての文字が英数字 +| `alpha` | すべての文字がアルファベット `[A-Za-z]` +| `digit` | すべての文字が数字 +| `lower` | すべての文字が小文字 `[a-z]` +| `space` | すべての文字が空白文字 +| `upper` | すべての文字が大文字 `[A-Z]` +| `xdigit` | すべての文字が16進数 `[0-9A-Fa-f]` +|-------------------------- +| 構文検証 || +|------------------------------------------------ +| `pattern` | **文字列全体**に一致する必要がある正規表現 +| `email` | [Eメール |#isEmail] +| `identifier`| [PHP識別子 |#isPhpIdentifier] +| `url` | [URL |#isUrl] +| `uri` | [URI |#isUri] +|-------------------------- +| 環境検証 || +|------------------------------------------------ +| `class` | 存在するクラス +| `interface` | 存在するインターフェース +| `directory` | 存在するディレクトリ +| `file` | 存在するファイル + + +アサーション +====== + + +assert($value, string $expected, string $label='variable'): void .[method] +-------------------------------------------------------------------------- + +値がパイプで区切られた [#期待される型] のいずれかであることを検証します。そうでない場合、[api:Nette\Utils\AssertionException] 例外をスローします。例外メッセージ内の単語 `variable` は、パラメータ `$label` で別の単語に置き換えることができます。 + +```php +Validators::assert('Nette', 'string:5'); // OK +Validators::assert('Lorem ipsum dolor sit', 'string:78'); +// AssertionException: The variable expects to be string:78, string 'Lorem ipsum dolor sit' given. +``` + + +assertField(array $array, string|int $key, ?string $expected=null, ?string $label=null): void .[method] +------------------------------------------------------------------------------------------------------- + +配列 `$array` 内のキー `$key` の下の要素が、パイプで区切られた [#期待される型] のいずれかであることを検証します。そうでない場合、[api:Nette\Utils\AssertionException] 例外をスローします。例外メッセージ内の文字列 `item '%' in array` は、パラメータ `$label` で別の文字列に置き換えることができます。 + +```php +$arr = ['foo' => 'Nette']; + +Validators::assertField($arr, 'foo', 'string:5'); // OK +Validators::assertField($arr, 'bar', 'string:15'); +// AssertionException: Missing item 'bar' in array. +Validators::assertField($arr, 'foo', 'int'); +// AssertionException: The item 'foo' in array expects to be int, string 'Nette' given. +``` + + +バリデータ +===== + + +is($value, string $expected): bool .[method] +-------------------------------------------- + +値がパイプで区切られた [#期待される型] のいずれかであるかどうかを検証します。 + +```php +Validators::is(1, 'int|float'); // true +Validators::is(23, 'int:0..10'); // false +Validators::is('Nette Framework', 'string:15'); // true、長さは15バイトです +Validators::is('Nette Framework', 'string:8..'); // true +Validators::is('Nette Framework', 'string:30..40'); // false +``` + + +isEmail(mixed $value): bool .[method] +------------------------------------- + +値が有効なメールアドレスであるかどうかを検証します。ドメインが実際に存在するかどうかは検証されず、構文のみが検証されます。この関数は、Unicodeである可能性のある将来の [TLD|https://ja.wikipedia.org/wiki/トップレベルドメイン] も考慮します。 + +```php +Validators::isEmail('example@nette.org'); // true +Validators::isEmail('example@localhost'); // false +Validators::isEmail('nette'); // false +``` + + +isInRange(mixed $value, array $range): bool .[method] +----------------------------------------------------- + +値が指定された範囲 `[min, max]` 内にあるかどうかを検証します。上限または下限は省略できます(`null`)。数値、文字列、DateTimeオブジェクトを比較できます。 + +両方の境界が欠落している場合(`[null, null]`)または値が `null` の場合、`false` を返します。 + +```php +Validators::isInRange(5, [0, 5]); // true +Validators::isInRange(23, [null, 5]); // false +Validators::isInRange(23, [5]); // true +Validators::isInRange(1, [5]); // false +``` + + +isNone(mixed $value): bool .[method] +------------------------------------ + +値が `0`、`''`、`false`、または `null` であるかどうかを検証します。 + +```php +Validators::isNone(0); // true +Validators::isNone(''); // true +Validators::isNone(false); // true +Validators::isNone(null); // true +Validators::isNone('nette'); // false +``` + + +isNumeric(mixed $value): bool .[method] +--------------------------------------- + +値が数値または文字列で記述された数値であるかどうかを検証します。 + +```php +Validators::isNumeric(23); // true +Validators::isNumeric(1.78); // true +Validators::isNumeric('+42'); // true +Validators::isNumeric('3.14'); // true +Validators::isNumeric('nette'); // false +Validators::isNumeric('1e6'); // false +``` + + +isNumericInt(mixed $value): bool .[method] +------------------------------------------ + +値が整数または文字列で記述された整数であるかどうかを検証します。 + +```php +Validators::isNumericInt(23); // true +Validators::isNumericInt(1.78); // false +Validators::isNumericInt('+42'); // true +Validators::isNumericInt('3.14'); // false +Validators::isNumericInt('nette'); // false +``` + + +isPhpIdentifier(string $value): bool .[method] +---------------------------------------------- + +値がPHPで構文的に有効な識別子(クラス名、メソッド名、関数名など)であるかどうかを検証します。 + +```php +Validators::isPhpIdentifier(''); // false +Validators::isPhpIdentifier('Hello1'); // true +Validators::isPhpIdentifier('1Hello'); // false +Validators::isPhpIdentifier('one two'); // false +``` + + +isBuiltinType(string $type): bool .[method] +------------------------------------------- + +`$type` がPHPの組み込み型かどうかを調べます。そうでない場合は、クラス名です。 + +```php +Validators::isBuiltinType('string'); // true +Validators::isBuiltinType('Foo'); // false +``` + + +isTypeDeclaration(string $type): bool .[method] +----------------------------------------------- + +指定された型宣言が構文的に有効かどうかをチェックします。 + +```php +Validators::isTypeDeclaration('?string'); // true +Validators::isTypeDeclaration('string|null'); // true +Validators::isTypeDeclaration('Foo&Bar'); // true +Validators::isTypeDeclaration('(A&C)|null'); // true + +Validators::isTypeDeclaration('?string|null'); // false +Validators::isTypeDeclaration('|foo'); // false +Validators::isTypeDeclaration('(A|B)'); // false +``` + + +isClassKeyword(string $type): bool .[method] +-------------------------------------------- + +`$type` が内部型 `self`、`parent`、`static` のいずれかであるかどうかを調べます。 + +```php +Validators::isClassKeyword('self'); // true +Validators::isClassKeyword('Foo'); // false +``` + + +isUnicode(mixed $value): bool .[method] +--------------------------------------- + +値が有効なUTF-8文字列であるかどうかを検証します。 + +```php +Validators::isUnicode('nette'); // true +Validators::isUnicode(''); // true +Validators::isUnicode("\xA0"); // false +``` + + +isUrl(mixed $value): bool .[method] +----------------------------------- + +値が有効なURLアドレスであるかどうかを検証します。 + +```php +Validators::isUrl('https://nette.org:8080/path?query#fragment'); // true +Validators::isUrl('http://localhost'); // true +Validators::isUrl('http://192.168.1.1'); // true +Validators::isUrl('http://[::1]'); // true +Validators::isUrl('http://user:pass@nette.org'); // false +Validators::isUrl('nette.org'); // false +``` + + +isUri(string $value): bool .[method] +------------------------------------ + +値が有効なURIアドレス、つまり構文的に有効なスキームで始まる文字列であるかどうかを検証します。 + +```php +Validators::isUri('https://nette.org'); // true +Validators::isUri('mailto:gandalf@example.org'); // true +Validators::isUri('nette.org'); // false +``` diff --git a/utils/pl/@home.texy b/utils/pl/@home.texy index 6912d59579..20611d1da5 100644 --- a/utils/pl/@home.texy +++ b/utils/pl/@home.texy @@ -1,42 +1,46 @@ -Narzędzia -********* +Nette Utils +*********** .[perex] W pakiecie `nette/utils` znajdziesz zestaw przydatnych klas do codziennego użytku: -| [Callback |Callback] +| [Callback |Callback] | Nette\Utils\Callback | [Data i czas |datetime] | Nette\Utils\DateTime | [Finder |Finder] | Nette\Utils\Finder -| [elementów HTML |html-elements] | Nette\Utils\Html -| [JSON |json] | Nette\Utils\Json -| [Losowe ciągi znaków |random] | Nette\Utils\Random -| [Model Obiektów |smartobject] | Nette\SmartObject & Nette\StaticClass -| [Obrazki |images] | Nette\Utils\Image -| [Odbicie w PHP |reflection] | Nette\Utils\Reflection -| [typy PHP |type] Nette\Utils\Type -| [Arrays |arrays] | Nette\Utils\Arrays -| [Helper Functions |helpers] | Nette\Utils\Helpers -| [Porównywanie pływaków |floats] | Nette\Utils\Floats -| [Strings |strings] | Nette\Utils\Strings -| [System plików |filesystem] | Nette\Utils\Filesystem -| [Paginator |paginator] | Nette\Utils\Paginator -| [Walidator |validators] | Nette\Utils\Validator +| [Elementy HTML |html-elements] | Nette\Utils\Html +| [Iteratory |iterables] | Nette\Utils\Iterables +| [JSON |utils:JSON] | Nette\Utils\Json +| [Losowe ciągi znaków |random] | Nette\Utils\Random +| [Obrazy |images] | Nette\Utils\Image +| [Refleksja PHP |reflection] | Nette\Utils\Reflection +| [Typy PHP |type] | Nette\Utils\Type +| [Tablice |arrays] | Nette\Utils\Arrays +| [Funkcje pomocnicze |helpers] | Nette\Utils\Helpers +| [Porównywanie floatów |floats] | Nette\Utils\Floats +| [Ciągi znaków |strings] | Nette\Utils\Strings +| [System plików |filesystem] | Nette\Utils\FileSystem +| [Paginacja |paginator] | Nette\Utils\Paginator +| [SmartObject |SmartObject] & [StaticClass |StaticClass] | Nette\SmartObject & Nette\StaticClass +| [Walidator |validators] | Nette\Utils\Validators Instalacja ---------- -Pobierz i zainstaluj bibliotekę za pomocą [Composera |best-practices:composer]: +Bibliotekę pobierzesz i zainstalujesz za pomocą narzędzia [Composer|best-practices:composer]: ```shell composer require nette/utils ``` -| wersja zgodna z PHP +| wersja | kompatybilna z PHP |-----------|------------------- -| Nette Utils 4.0 | PHP 8.0 - 8.2 -| Nette Utils 3.2 | PHP 7.2 - 8.2 -| Nette Utils 3.0 - 3.1 | PHP 7.1 - 8.0 -| Nette Utils 2.5 | PHP 5.6 - 8.0 +| Nette Utils 4.0 | PHP 8.0 – 8.4 +| Nette Utils 3.2 | PHP 7.2 – 8.3 +| Nette Utils 3.0 – 3.1 | PHP 7.1 – 8.0 +| Nette Utils 2.5 | PHP 5.6 – 8.0 -Ważne dla najnowszej wersji poprawki. +Dotyczy ostatniej wersji patch. + + +Jeśli aktualizujesz pakiet do nowszej wersji, zajrzyj na stronę [aktualizacji|en:upgrading]. diff --git a/utils/pl/@left-menu.texy b/utils/pl/@left-menu.texy index 73189365a3..eef255c2c4 100644 --- a/utils/pl/@left-menu.texy +++ b/utils/pl/@left-menu.texy @@ -1,26 +1,28 @@ -Pakiet Nette/utils -****************** -- [Wywołania zwrotne |callback] -- [Data i godzina |datetime] -- [Wyszukiwarka |Finder] -- [Pływaki |Floats] +Nette Utils +*********** +- [Callbacki |callback] +- [Data i czas |datetime] +- [Finder |Finder] +- [Floats |Floats] - [Elementy HTML |html-elements] -- [JSON |JSON] -- [Losowe ciągi |random] +- [Iteratory |iterables] +- [JSON |utils:JSON] +- [Losowe ciągi znaków |random] - [Obrazy |images] - [Paginator |paginator] -- [Refleksja w PHP |reflection] -- [Rodzaje PHP |type] +- [Refleksja PHP |reflection] +- [Typy PHP |type] - [Tablice |arrays] - [Funkcje pomocnicze |helpers] -- [Struny |strings] -- [SmartObject |smartobject] +- [Ciągi znaków |strings] +- [SmartObject |SmartObject] +- [StaticClass |StaticClass] - [System plików |filesystem] - [Walidator |validators] Inne narzędzia ************** -- [NEON |neon:] -- [Hashing hasła |security:passwords] -- [Parsowanie i składanie adresów URL |http:urls] +- [NEON|neon:] +- [Haszowanie haseł |security:passwords] +- [Parsowanie i składanie URL |http:urls] diff --git a/utils/pl/@meta.texy b/utils/pl/@meta.texy new file mode 100644 index 0000000000..61ac92d1af --- /dev/null +++ b/utils/pl/@meta.texy @@ -0,0 +1 @@ +{{sitename: Dokumentacja Nette}} diff --git a/utils/pl/arrays.texy b/utils/pl/arrays.texy index f53b6e0e62..7273b72ee4 100644 --- a/utils/pl/arrays.texy +++ b/utils/pl/arrays.texy @@ -1,8 +1,8 @@ -Praca w terenie -*************** +Praca z tablicami +***************** .[perex] -Ta strona jest poświęcona klasom [Nette\Utils\Arrays |#Arrays], [ArrayHash |#ArrayHash] i [ArrayList |#ArrayList], które są związane z tablicami. +Ta strona poświęcona jest klasom [Nette\Utils\Arrays |#Arrays], [#ArrayHash] i [#ArrayList], które dotyczą tablic. Instalacja: @@ -12,22 +12,63 @@ composer require nette/utils ``` -Tablice .[#toc-arrays] -====================== +Arrays +====== -[api:Nette\Utils\Arrays] jest klasą statyczną zawierającą przydatne funkcje do pracy z tablicami. +[api:Nette\Utils\Arrays] to statyczna klasa zawierająca przydatne funkcje do pracy z tablicami. Jej odpowiednikiem dla iteratorów jest [Nette\Utils\Iterables|iterables]. -Poniższe przykłady zakładają, że alias został utworzony: +Poniższe przykłady zakładają utworzony alias: ```php use Nette\Utils\Arrays; ``` +associate(array $array, mixed $path): array|\stdClass .[method] +--------------------------------------------------------------- + +Funkcja elastycznie przekształca tablicę `$array` w tablicę asocjacyjną lub obiekty zgodnie z podaną ścieżką `$path`. Ścieżka może być ciągiem znaków lub tablicą. Składa się z nazw kluczy tablicy wejściowej oraz operatorów takich jak '[]', '->', '=', i '|'. Rzuca `Nette\InvalidArgumentException` w przypadku, gdy ścieżka jest nieprawidłowa. + +```php +// konwersja na tablicę asocjacyjną według prostego klucza +$arr = [ + ['name' => 'John', 'age' => 11], + ['name' => 'Mary', 'age' => null], + // ... +]; +$result = Arrays::associate($arr, 'name'); +// $result = ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// przypisanie wartości z jednego klucza do innego za pomocą operatora = +$result = Arrays::associate($arr, 'name=age'); // lub ['name', '=', 'age'] +// $result = ['John' => 11, 'Mary' => null, ...] +``` + +```php +// utworzenie obiektu za pomocą operatora -> +$result = Arrays::associate($arr, '->name'); // lub ['->', 'name'] +// $result = (object) ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// kombinacja kluczy za pomocą operatora | +$result = Arrays::associate($arr, 'name|age'); // lub ['name', '|', 'age'] +// $result: ['John' => ['name' => 'John', 'age' => 11], 'Paul' => ['name' => 'Paul', 'age' => 44]] +``` + +```php +// dodanie do tablicy za pomocą [] +$result = Arrays::associate($arr, 'name[]'); // lub ['name', '[]'] +// $result: ['John' => [['name' => 'John', 'age' => 22], ['name' => 'John', 'age' => 11]]] +``` + + contains(array $array, $value): bool .[method] ---------------------------------------------- -Sprawdza tablicę pod kątem obecności wartości. Stosuje porównanie ścisłe (`===`). +Sprawdza tablicę pod kątem obecności wartości. Używa ścisłego porównania (`===`). ```php Arrays::contains([1, 2, 3], 1); // true @@ -35,10 +76,10 @@ Arrays::contains(['1', false], 1); // false ``` -every(iterable $array, callable $callback): bool .[method] ----------------------------------------------------------- +every(array $array, callable $predicate): bool .[method] +-------------------------------------------------------- -Testuje czy wszystkie elementy w tablicy przechodzą test zaimplementowany w `$callback` z podpisem `function ($value, $key, array $array): bool`. +Sprawdza, czy wszystkie elementy w tablicy przechodzą test zaimplementowany w `$predicate` o sygnaturze `function ($value, $key, array $array): bool`. ```php $array = [1, 30, 39, 29, 10, 13]; @@ -46,24 +87,59 @@ $isBelowThreshold = fn($value) => $value < 40; $res = Arrays::every($array, $isBelowThreshold); // true ``` -Zobacz [some() |#some]. +Zobacz [#some()]. -first(array $array): mixed .[method] ------------------------------------- +filter(array $array, callable $predicate): array .[method]{data-version:4.0.4} +------------------------------------------------------------------------------ + +Zwraca nową tablicę zawierającą wszystkie pary klucz-wartość pasujące do podanego predykatu. Callback ma sygnaturę `function ($value, int|string $key, array $array): bool`. + +```php +Arrays::filter( + ['a' => 1, 'b' => 2, 'c' => 3], + fn($v) => $v < 3, +); +// ['a' => 1, 'b' => 2] +``` + + +first(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------- -Zwraca pierwszy wpis z tablicy lub null jeśli tablica jest pusta. Nie zmienia wewnętrznego wskaźnika w przeciwieństwie do `reset()`. +Zwraca pierwszy element (pasujący do predykatu, jeśli jest podany). Jeśli taki element nie istnieje, zwraca wynik wywołania `$else` lub null. Parametr `$predicate` ma sygnaturę `function ($value, int|string $key, array $array): bool`. + +Nie zmienia wewnętrznego wskaźnika w przeciwieństwie do `reset()`. Parametry `$predicate` i `$else` istnieją od wersji 4.0.4. + +```php +Arrays::first([1, 2, 3]); // 1 +Arrays::first([1, 2, 3], fn($v) => $v > 2); // 3 +Arrays::first([]); // null +Arrays::first([], else: fn() => false); // false +``` + +Zobacz [#last()]. + + +firstKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +------------------------------------------------------------------------------------------------ + +Zwraca klucz pierwszego elementu (pasującego do predykatu, jeśli jest podany) lub null, jeśli taki element nie istnieje. Predykat `$predicate` ma sygnaturę `function ($value, int|string $key, array $array): bool`. ```php -Arrays::first([1, 2, 3]); // 1 -Arrays::first([]); // null +Arrays::firstKey([1, 2, 3]); // 0 +Arrays::firstKey([1, 2, 3], fn($v) => $v > 2); // 2 +Arrays::firstKey(['a' => 1, 'b' => 2]); // 'a' +Arrays::firstKey([]); // null ``` +Zobacz [#lastKey()]. + flatten(array $array, bool $preserveKeys=false): array .[method] ---------------------------------------------------------------- -Konsoliduje wielopoziomową tablicę w jedną płaską. +Spłaszcza wielopoziomową tablicę do płaskiej. ```php $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); @@ -71,51 +147,51 @@ $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); ``` -get(array $array, string|int|array $key, mixed $default=null): mixed .[method] ------------------------------------------------------------------------------- +get(array $array, string|int|array $key, ?mixed $default=null): mixed .[method] +------------------------------------------------------------------------------- -Zwraca element `$array[$key]`. Jeśli nie istnieje, to albo rzuca wyjątek `Nette\InvalidArgumentException`, albo jeśli podano trzeci parametr `$default`, to zwraca to. +Zwraca element `$array[$key]`. Jeśli nie istnieje, rzuca wyjątek `Nette\InvalidArgumentException` lub, jeśli podano trzeci parametr `$default`, zwraca go. ```php // jeśli $array['foo'] nie istnieje, rzuca wyjątek $value = Arrays::get($array, 'foo'); -// jeśli $array['foo'] nie istnieje, zwróć 'bar' +// jeśli $array['foo'] nie istnieje, zwraca 'bar' $value = Arrays::get($array, 'foo', 'bar'); ``` -Klucz `$key` może być również tablicą. +Kluczem `$key` może być również tablica. ```php $array = ['color' => ['favorite' => 'red'], 5]; $value = Arrays::get($array, ['color', 'favorite']); -// vrátí 'red' +// zwraca 'red' ``` getRef(array &$array, string|int|array $key): mixed .[method] ------------------------------------------------------------- -Uzyskuje referencję do określonego elementu tablicy. Jeśli element nie istnieje, zostanie utworzony z wartością null. +Pobiera referencję do określonego elementu tablicy. Jeśli element nie istnieje, zostanie utworzony z wartością null. ```php $valueRef = & Arrays::getRef($array, 'foo'); -// zwraca odwołanie do $array['foo'] +// zwraca referencję do $array['foo'] ``` -Podobnie jak funkcja [get() |#get], może ona pracować z tablicami wielowymiarowymi. +Podobnie jak funkcja [#get()], potrafi pracować z tablicami wielowymiarowymi. ```php $value = & Arrays::getRef($array, ['color', 'favorite']); -// vrátí referenci na $array['color']['favorite'] +// zwraca referencję do $array['color']['favorite'] ``` grep(array $array, string $pattern, bool $invert=false): array .[method] ------------------------------------------------------------------------ -Zwraca tylko te elementy tablicy, których wartość pasuje do wyrażenia regularnego `$pattern`. Jeśli `$invert` jest `true`, to z drugiej strony zwraca elementy, które nie pasują. Błąd kompilacji lub przetwarzania wyrażeń rzuca wyjątek `Nette\RegexpException`. +Zwraca tylko te elementy tablicy, których wartość pasuje do wyrażenia regularnego `$pattern`. Jeśli `$invert` jest `true`, zwraca elementy, które nie pasują. Błąd podczas kompilacji lub przetwarzania wyrażenia rzuca wyjątek `Nette\RegexpException`. ```php $filteredArray = Arrays::grep($array, '~^\d+$~'); @@ -126,7 +202,7 @@ $filteredArray = Arrays::grep($array, '~^\d+$~'); insertAfter(array &$array, string|int|null $key, array $inserted): void .[method] --------------------------------------------------------------------------------- -Wstawia zawartość pola `$inserted` do pola `$array` bezpośrednio za elementem o kluczu `$key`. Jeśli `$key` jest `null` (lub nie ma go w polu), to jest wstawiany na końcu. +Wstawia zawartość tablicy `$inserted` do tablicy `$array` tuż za elementem o kluczu `$key`. Jeśli `$key` jest `null` (lub nie ma go w tablicy), wstawia na koniec. ```php $array = ['first' => 10, 'second' => 20]; @@ -138,7 +214,7 @@ Arrays::insertAfter($array, 'first', ['hello' => 'world']); insertBefore(array &$array, string|int|null $key, array $inserted): void .[method] ---------------------------------------------------------------------------------- -Wstawia zawartość pola `$inserted` do pola `$array` przed elementem o kluczu `$key`. Jeśli `$key` jest `null` (lub nie ma go w polu), jest wstawiany na początku. +Wstawia zawartość tablicy `$inserted` do tablicy `$array` przed elementem o kluczu `$key`. Jeśli `$key` jest `null` (lub nie ma go w tablicy), wstawia na początek. ```php $array = ['first' => 10, 'second' => 20]; @@ -150,7 +226,7 @@ Arrays::insertBefore($array, 'first', ['hello' => 'world']); invoke(iterable $callbacks, ...$args): array .[method] ------------------------------------------------------ -Wywołuje wszystkie wywołania zwrotne i zwraca tablicę wyników. +Wywołuje wszystkie callbacki i zwraca tablicę wyników. ```php $callbacks = [ @@ -179,7 +255,7 @@ $array = Arrays::invokeMethod($objects, 'foo', 1, 2); isList(array $array): bool .[method] ------------------------------------ -Sprawdza, czy tablica jest indeksowana przez rosnącą serię kluczy numerycznych zaczynających się od zera, a.k.a. lista. +Sprawdza, czy tablica jest indeksowana według rosnącej serii kluczy numerycznych od zera, a.k.a lista. ```php Arrays::isList(['a', 'b', 'c']); // true @@ -188,21 +264,42 @@ Arrays::isList(['a' => 1, 'b' => 2]); // false ``` -last(array $array): mixed .[method] ------------------------------------ +last(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------ + +Zwraca ostatni element (pasujący do predykatu, jeśli jest podany). Jeśli taki element nie istnieje, zwraca wynik wywołania `$else` lub null. Parametr `$predicate` ma sygnaturę `function ($value, int|string $key, array $array): bool`. + +Nie zmienia wewnętrznego wskaźnika w przeciwieństwie do `end()`. Parametry `$predicate` i `$else` istnieją od wersji 4.0.4. + +```php +Arrays::last([1, 2, 3]); // 3 +Arrays::last([1, 2, 3], fn($v) => $v < 3); // 2 +Arrays::last([]); // null +Arrays::last([], else: fn() => false); // false +``` + +Zobacz [#first()]. + + +lastKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +----------------------------------------------------------------------------------------------- -Zwraca ostatni wpis tablicy lub null jeśli tablica jest pusta. Nie zmienia wewnętrznego wskaźnika w przeciwieństwie do `end()`. +Zwraca klucz ostatniego elementu (pasującego do predykatu, jeśli jest podany) lub null, jeśli taki element nie istnieje. Predykat `$predicate` ma sygnaturę `function ($value, int|string $key, array $array): bool`. ```php -Arrays::last([1, 2, 3]); // 3 -Arrays::last([]); // null +Arrays::lastKey([1, 2, 3]); // 2 +Arrays::lastKey([1, 2, 3], fn($v) => $v < 3); // 1 +Arrays::lastKey(['a' => 1, 'b' => 2]); // 'b' +Arrays::lastKey([]); // null ``` +Zobacz [#firstKey()]. -map(iterable $array, callable $callback): array .[method] + +map(array $array, callable $transformer): array .[method] --------------------------------------------------------- -Wywołuje `$callback` na wszystkich elementach tablicy i zwraca tablicę wartości zwrotnych. Callback ma podpis `function ($value, $key, array $array): bool`. +Wywołuje `$transformer` na wszystkich elementach w tablicy i zwraca tablicę zwróconych wartości. Callback ma sygnaturę `function ($value, $key, array $array): mixed`. ```php $array = ['foo', 'bar', 'baz']; @@ -211,10 +308,24 @@ $res = Arrays::map($array, fn($value) => $value . $value); ``` +mapWithKeys(array $array, callable $transformer): array .[method] +----------------------------------------------------------------- + +Tworzy nową tablicę przez transformację wartości i kluczy oryginalnej tablicy. Funkcja `$transformer` ma sygnaturę `function ($value, $key, array $array): ?array{$newKey, $newValue}`. Jeśli `$transformer` zwróci `null`, element jest pomijany. Dla zachowanych elementów pierwszy element zwróconej tablicy jest używany jako nowy klucz, a drugi element jako nowa wartość. + +```php +$array = ['a' => 1, 'b' => 2]; +$result = Arrays::mapWithKeys($array, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +Ta metoda jest przydatna w sytuacjach, gdy potrzebujesz zmienić strukturę tablicy (klucze i wartości jednocześnie) lub filtrować elementy podczas transformacji (zwracając null dla niechcianych elementów). + + mergeTree(array $array1, array $array2): array .[method] -------------------------------------------------------- -Rekursywnie łączy dwa pola. Jest to przydatne na przykład do łączenia struktur drzewiastych. Przy scalaniu kieruje się tymi samymi zasadami co operator `+` stosowany do tablic, czyli dodaje parę klucz/wartość z drugiej tablicy do pierwszej i pozostawia wartość z pierwszej tablicy w przypadku kolizji kluczy. +Rekurencyjnie łączy dwie tablice. Przydaje się na przykład do łączenia struktur drzewiastych. Podczas łączenia kieruje się tymi samymi zasadami co operator `+` zastosowany do tablic, tj. do pierwszej tablicy dodaje pary klucz/wartość z drugiej tablicy, a w przypadku kolizji kluczy pozostawia wartość z pierwszej tablicy. ```php $array1 = ['color' => ['favorite' => 'red'], 5]; @@ -224,13 +335,13 @@ $array = Arrays::mergeTree($array1, $array2); // $array = ['color' => ['favorite' => 'red', 'blue'], 5]; ``` -Wartości z drugiej tablicy są zawsze dodawane do końca pierwszej. Zniknięcie wartości `10` z drugiego pola może wydawać się nieco mylące. Zauważ, że ta wartość, podobnie jak wartość `5` v poli prvním mají přiřazený stejný numerický klíč `0`, dlatego w tablicy wynikowej znajduje się tylko element z pierwszego pola. +Wartości z drugiej tablicy są zawsze dodawane na koniec pierwszej. Trochę mylące może wydawać się zniknięcie wartości `10` z drugiej tablicy. Należy zdać sobie sprawę, że ta wartość, podobnie jak wartość `5` w pierwszej tablicy, mają przypisany ten sam klucz numeryczny `0`, dlatego w wynikowej tablicy znajduje się tylko element z pierwszej tablicy. -normalize(array $array, string $filling=null): array .[method] --------------------------------------------------------------- +normalize(array $array, ?string $filling=null): array .[method] +--------------------------------------------------------------- -Normalizuje tablicę do tablicy asocjacyjnej. Zastępuje klawisze numeryczne ich wartościami, nowa wartość będzie miała postać `$filling`. +Normalizuje tablicę do tablicy asocjacyjnej. Klucze numeryczne zastępuje ich wartościami, nową wartością będzie `$filling`. ```php $array = Arrays::normalize([1 => 'first', 'a' => 'second']); @@ -243,14 +354,14 @@ $array = Arrays::normalize([1 => 'first', 'a' => 'second'], 'foobar'); ``` -pick(array &$array, string|int $key, mixed $default=null): mixed .[method] --------------------------------------------------------------------------- +pick(array &$array, string|int $key, ?mixed $default=null): mixed .[method] +--------------------------------------------------------------------------- -Zwraca i usuwa wartość elementu z tablicy. Jeśli nie istnieje, rzuca wyjątek lub zwraca wartość `$default`, jeśli jest obecny. +Zwraca i usuwa wartość elementu z tablicy. Jeśli nie istnieje, rzuca wyjątek lub zwraca wartość `$default`, jeśli jest podana. ```php -$array = [1 => 'foo', null => 'bar']; -$a = Arrays::pick($array, null); +$array = [1 => 'foo', 'x' => 'bar']; +$a = Arrays::pick($array, 'x'); // $a = 'bar' $b = Arrays::pick($array, 'not-exists', 'foobar'); // $b = 'foobar' @@ -274,20 +385,20 @@ Arrays::renameKey($array, 'first', 'renamed'); getKeyOffset(array $array, string|int $key): ?int .[method] ----------------------------------------------------------- -Zwraca pozycję podanego klucza w tablicy. Pozycja jest numerowana od 0. Jeśli klucz nie zostanie znaleziony, funkcja zwraca `null`. +Zwraca pozycję danego klucza w tablicy. Pozycja jest numerowana od 0. W przypadku, gdy klucz nie zostanie znaleziony, funkcja zwróci `null`. ```php $array = ['first' => 10, 'second' => 20]; -$position = Arrays::getKeyOffset($array, 'first'); // vrátí 0 -$position = Arrays::getKeyOffset($array, 'second'); // vrátí 1 -$position = Arrays::getKeyOffset($array, 'not-exists'); // vrátí null +$position = Arrays::getKeyOffset($array, 'first'); // zwraca 0 +$position = Arrays::getKeyOffset($array, 'second'); // zwraca 1 +$position = Arrays::getKeyOffset($array, 'not-exists'); // zwraca null ``` -some(iterable $array, callable $callback): bool .[method] ---------------------------------------------------------- +some(array $array, callable $predicate): bool .[method] +------------------------------------------------------- -Testuje czy przynajmniej jeden element w tablicy przechodzi test zaimplementowany w `$callback` z podpisem `function ($value, $key, array $array): bool`. +Sprawdza, czy przynajmniej jeden element w tablicy przechodzi test zaimplementowany w `$predicate` o sygnaturze `function ($value, $key, array $array): bool`. ```php $array = [1, 2, 3, 4]; @@ -295,13 +406,13 @@ $isEven = fn($value) => $value % 2 === 0; $res = Arrays::some($array, $isEven); // true ``` -Zobacz [every() |#every]. +Zobacz [#every()]. toKey(mixed $key): string|int .[method] --------------------------------------- -Konwertuje wartość na klucz tablicy, który jest albo liczbą całkowitą albo łańcuchem. +Konwertuje wartość na klucz tablicy, który jest albo liczbą całkowitą, albo ciągiem znaków. ```php Arrays::toKey('1'); // 1 @@ -317,14 +428,14 @@ Kopiuje elementy tablicy `$array` do obiektu `$object`, który następnie zwraca ```php $obj = new stdClass; $array = ['foo' => 1, 'bar' => 2]; -Arrays::toObject($array, $obj); // nastaví $obj->foo = 1; $obj->bar = 2; +Arrays::toObject($array, $obj); // ustawia $obj->foo = 1; $obj->bar = 2; ``` -wrap(iterable $array, string $prefix='', string $suffix=''): array .[method] ----------------------------------------------------------------------------- +wrap(array $array, string $prefix='', string $suffix=''): array .[method] +------------------------------------------------------------------------- -Wypisuje każdy element tablicy do łańcucha i opakowuje go prefiksem `$prefix` i sufiksem `$suffix`. +Każdy element w tablicy rzutuje na ciąg znaków i otacza prefiksem `$prefix` oraz sufiksem `$suffix`. ```php $array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); @@ -332,21 +443,21 @@ $array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); ``` -ArrayHash .[#toc-arrayhash] -=========================== +ArrayHash +========= -Obiekt [api:Nette\Utils\ArrayHash] jest potomkiem generycznej klasy stdClass i rozszerza ją o możliwość traktowania jej jako tablicy, czyli np. dostępu do członków poprzez nawiasy kwadratowe: +Obiekt [api:Nette\Utils\ArrayHash] jest potomkiem generycznej klasy stdClass i rozszerza ją o możliwość traktowania go jak tablicy, czyli na przykład dostępu do elementów za pomocą nawiasów kwadratowych: ```php $hash = new Nette\Utils\ArrayHash; $hash['foo'] = 123; -$hash->bar = 456; // notacja obiektowa działa jednocześnie +$hash->bar = 456; // jednocześnie działa również zapis obiektowy $hash->foo; // 123 ``` -Możesz użyć funkcji `count($hash)`, aby uzyskać liczbę członków. +Można używać funkcji `count($hash)` do sprawdzania liczby elementów. -Możesz iterować nad obiektem, jak w przypadku tablicy, nawet z referencją: +Nad obiektem można iterować tak samo jak w przypadku tablicy, również z referencją: ```php foreach ($hash as $key => $value) { @@ -358,7 +469,7 @@ foreach ($hash as $key => &$value) { } ``` -Istniejącą tablicę możemy przekształcić w `ArrayHash` za pomocą metody `from()`: +Istniejącą tablicę możemy przekształcić na `ArrayHash` za pomocą metody `from()`: ```php $array = ['foo' => 123, 'bar' => 456]; @@ -368,35 +479,35 @@ $hash->foo; // 123 $hash->bar; // 456 ``` -Konwersja ma charakter rekurencyjny: +Konwersja jest rekurencyjna: ```php $array = ['foo' => 123, 'inner' => ['a' => 'b']]; $hash = Nette\Utils\ArrayHash::from($array); -$hash->inner; // objekt ArrayHash +$hash->inner; // obiekt ArrayHash $hash->inner->a; // 'b' $hash['inner']['a']; // 'b' ``` -Można temu zapobiec za pomocą drugiego parametru: +Można temu zapobiec drugim parametrem: ```php $hash = Nette\Utils\ArrayHash::from($array, false); -$hash->inner; // biegun +$hash->inner; // tablica ``` -Przekształć z powrotem do tablicy: +Transformacja z powrotem na tablicę: ```php $array = (array) $hash; ``` -ArrayList .[#toc-arraylist] -=========================== +ArrayList +========= -[api:Nette\Utils\ArrayList] reprezentuje tablicę liniową, w której indeksy są tylko liczbami całkowitymi rosnącymi od 0. +[api:Nette\Utils\ArrayList] reprezentuje tablicę liniową, gdzie indeksy są wyłącznie liczbami całkowitymi rosnącymi od 0. ```php $list = new Nette\Utils\ArrayList; @@ -407,16 +518,16 @@ $list[] = 'c'; count($list); // 3 ``` -Istniejące tablice mogą być przekształcone na `ArrayList` za pomocą metody `from()`: +Istniejącą tablicę możemy przekształcić na `ArrayList` za pomocą metody `from()`: ```php $array = ['foo', 'bar']; $list = Nette\Utils\ArrayList::from($array); ``` -Możesz użyć funkcji `count($list)`, aby uzyskać liczbę elementów. +Można używać funkcji `count($list)` do sprawdzania liczby elementów. -Możesz iterować nad obiektem, jak w przypadku tablicy, nawet z referencją: +Nad obiektem można iterować tak samo jak w przypadku tablicy, również z referencją: ```php foreach ($list as $key => $value) { @@ -428,21 +539,21 @@ foreach ($list as $key => &$value) { } ``` -Uzyskanie dostępu do kluczy poza dozwolonymi wartościami rzuca wyjątek `Nette\OutOfRangeException`: +Dostęp do kluczy spoza dozwolonych wartości rzuca wyjątek `Nette\OutOfRangeException`: ```php -echo $list[-1]; // throws Nette\OutOfRangeException -unset($list[30]); // throws Nette\OutOfRangeException +echo $list[-1]; // rzuca Nette\OutOfRangeException +unset($list[30]); // rzuca Nette\OutOfRangeException ``` -Usunięcie klucza powoduje zmianę numeracji elementów: +Usunięcie klucza powoduje przenumerowanie elementów: ```php unset($list[1]); // ArrayList(0 => 'a', 1 => 'c') ``` -Nowy element można dodać do początku za pomocą metody `prepend()`: +Nowy element można dodać na początek za pomocą metody `prepend()`: ```php $list->prepend('d'); diff --git a/utils/pl/callback.texy b/utils/pl/callback.texy index d8200bbf83..5e73945fc7 100644 --- a/utils/pl/callback.texy +++ b/utils/pl/callback.texy @@ -1,8 +1,8 @@ -Praca z wywołaniami zwrotnymi -***************************** +Praca z callbackami +******************* .[perex] -[api:Nette\Utils\Callback] jest klasą statyczną zawierającą funkcje do pracy z [wywołaniami zwrotnymi PHP |https://www.php.net/manual/en/language.types.callable.php]. +[api:Nette\Utils\Callback] to statyczna klasa z funkcjami do pracy z [callbackami PHP |https://www.php.net/manual/en/language.types.callable.php]. Instalacja: @@ -11,7 +11,7 @@ Instalacja: composer require nette/utils ``` -Wszystkie przykłady zakładają, że alias został utworzony: +Wszystkie przykłady zakładają utworzony alias: ```php use Nette\Utils\Callback; @@ -21,21 +21,21 @@ use Nette\Utils\Callback; check($callable, bool $syntax=false): callable .[method] -------------------------------------------------------- -Sprawdza, czy zmienna `$callable` jest prawidłowym callbackiem. W przeciwnym razie rzuca `Nette\InvalidArgumentException`. Jeśli `$syntax` jest prawdziwe, funkcja sprawdza tylko, czy `$callable` ma strukturę wywołania zwrotnego, ale nie sprawdza, czy klasa lub metoda rzeczywiście istnieje. Zwraca on `$callable`. +Sprawdza, czy zmienna `$callable` jest prawidłowym callbackiem. W przeciwnym razie rzuca `Nette\InvalidArgumentException`. Jeśli `$syntax` jest true, funkcja tylko weryfikuje, czy `$callable` ma strukturę callbacku, ale nie sprawdza, czy dana klasa lub metoda faktycznie istnieje. Zwraca `$callable`. ```php Callback::check('trim'); // nie rzuca wyjątku -Callback::check(['NonExistentClass', 'method']); // throws Nette\InvalidArgumentException +Callback::check(['NonExistentClass', 'method']); // rzuca Nette\InvalidArgumentException Callback::check(['NonExistentClass', 'method'], true); // nie rzuca wyjątku Callback::check(function () {}); // nie rzuca wyjątku -Callback::check(null); // throws Nette\InvalidArgumentException +Callback::check(null); // rzuca Nette\InvalidArgumentException ``` toString($callable): string .[method] ------------------------------------- -Konwertuje wywołanie zwrotne PHP do postaci tekstowej. Klasa lub metoda nie musi istnieć. +Konwertuje callback PHP do formy tekstowej. Klasa lub metoda nie musi istnieć. ```php Callback::toString('trim'); // 'trim' @@ -46,21 +46,21 @@ Callback::toString(['MyClass', 'method']); // 'MyClass::method' toReflection($callable): ReflectionMethod|ReflectionFunction .[method] ---------------------------------------------------------------------- -Zwraca odbicie dla metody lub funkcji w wywołaniu zwrotnym PHP. +Zwraca refleksję dla metody lub funkcji w callbacku PHP. ```php $ref = Callback::toReflection('trim'); -// $ref je ReflectionFunction('trim') +// $ref jest ReflectionFunction('trim') $ref = Callback::toReflection(['MyClass', 'method']); -// $ref je ReflectionMethod('MyClass', 'method') +// $ref jest ReflectionMethod('MyClass', 'method') ``` isStatic($callable): bool .[method] ----------------------------------- -Określa, czy wywołanie zwrotne PHP jest funkcją czy metodą statyczną. +Sprawdza, czy callback PHP jest funkcją lub metodą statyczną. ```php Callback::isStatic('trim'); // true @@ -73,7 +73,7 @@ Callback::isStatic(function () {}); // false unwrap(Closure $closure): callable|array .[method] -------------------------------------------------- -Rozpakowuje zamknięcie utworzone za pomocą `Closure::fromCallable`:https://www.php.net/manual/en/closure.fromcallable.php. +Odwrotnie rozwija Closure utworzone za pomocą `Closure::fromCallable`:https://www.php.net/manual/en/closure.fromcallable.php. ```php $closure = Closure::fromCallable(['MyClass', 'method']); diff --git a/utils/pl/datetime.texy b/utils/pl/datetime.texy index 0351423ed4..e9cc00d8ae 100644 --- a/utils/pl/datetime.texy +++ b/utils/pl/datetime.texy @@ -1,17 +1,17 @@ -Data a čas -********** +Data i czas +*********** .[perex] -[api:Nette\Utils\DateTime] jest třída, która rozszyfrowuje natywną [php:DateTime] o dalszą funkce. +[api:Nette\Utils\DateTime] to klasa, która rozszerza natywną [php:DateTime] o dodatkowe funkcje. -Instalace: +Instalacja: ```shell composer require nette/utils ``` -Všechny příklady předpokládají vytvořený alias: +Wszystkie przykłady zakładają utworzony alias: ```php use Nette\Utils\DateTime; @@ -20,27 +20,27 @@ use Nette\Utils\DateTime; static from(string|int|\DateTimeInterface $time): DateTime .[method] -------------------------------------------------------------------- -Wyświetlenie obiektu DateTime z řetězce, UNIX timestamp lub innego obiektu [php:DateTimeInterface]. Wyświetlenie obiektu `Exception`, pokud datum a čas není platný. +Tworzy obiekt DateTime z ciągu znaków, znacznika czasu UNIX lub innego obiektu [php:DateTimeInterface]. Rzuca wyjątek `Exception`, jeśli data i czas nie są prawidłowe. ```php -DateTime::from(1138013640); // vytvoří DateTime z UNIX timestamp s výchozí timezone -DateTime::from(42); // vytvoří DateTime z aktuálního času plus 42 sekund -DateTime::from('1994-02-26 04:15:32'); // vytvoří DateTime podle řetězce -DateTime::from('1994-02-26'); // vytvoří DateTime podle data, čas bude 00:00:00 +DateTime::from(1138013640); // tworzy DateTime ze znacznika czasu UNIX z domyślną strefą czasową +DateTime::from(42); // tworzy DateTime z bieżącego czasu plus 42 sekundy +DateTime::from('1994-02-26 04:15:32'); // tworzy DateTime na podstawie ciągu znaków +DateTime::from('1994-02-26'); // tworzy DateTime na podstawie daty, czas będzie 00:00:00 ``` static fromParts(int $year, int $month, int $day, int $hour=0, int $minute=0, float $second=0.0): DateTime .[method] -------------------------------------------------------------------------------------------------------------------- -Wyświetlenie obiektu DateTime lub jego nazwy `Nette\InvalidArgumentException`, w którym znajdują się dane i informacje. +Tworzy obiekt DateTime lub rzuca wyjątek `Nette\InvalidArgumentException`, jeśli data i czas nie są prawidłowe. ```php DateTime::fromParts(1994, 2, 26, 4, 15, 32); ``` -static createFromFormat(string $format, string $time, string|\DateTimeZone $timezone=null): DateTime|false .[method] --------------------------------------------------------------------------------------------------------------------- -Rozšiřuje [DateTime::createFromFormat() |https://www.php.net/manual/en/datetime.createfromformat.php] o možnost zadatku timezone jako řetězec. +static createFromFormat(string $format, string $time, ?string|\DateTimeZone $timezone=null): DateTime|false .[method] +--------------------------------------------------------------------------------------------------------------------- +Rozszerza [DateTime::createFromFormat()|https://www.php.net/manual/en/datetime.createfromformat.php] o możliwość podania strefy czasowej jako ciągu znaków. ```php DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); ``` @@ -48,7 +48,7 @@ DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); modifyClone(string $modify=''): static .[method] ------------------------------------------------ -Vytvoří kopii s upraveným časem. +Tworzy kopię ze zmodyfikowanym czasem. ```php $original = DateTime::from('2017-02-03'); $clone = $original->modifyClone('+1 day'); @@ -59,15 +59,15 @@ $clone->format('Y-m-d'); // '2017-02-04' __toString(): string .[method] ------------------------------ -Vrací datum a čas ve formátu `Y-m-d H:i:s`. +Zwraca datę i czas w formacie `Y-m-d H:i:s`. ```php echo $dateTime; // '2017-02-03 04:15:32' ``` -implementuje JsonSerializable .[#toc-implements-jsonserializable] ------------------------------------------------------------------ -Vrací datum a čas ve formátu ISO 8601, který je používán třeba v JavaScriptu. +implementuje JsonSerializable +----------------------------- +Zwraca datę i czas w formacie ISO 8601, który jest używany na przykład w JavaScript. ```php $date = DateTime::from('2017-02-03'); echo json_encode($date); diff --git a/utils/pl/filesystem.texy b/utils/pl/filesystem.texy index 05514e25fd..c5f4da062a 100644 --- a/utils/pl/filesystem.texy +++ b/utils/pl/filesystem.texy @@ -1,41 +1,43 @@ -Funkcje systemu plików -********************** +System plików +************* .[perex] -[api:Nette\Utils\FileSystem] jest statyczną klasą, która zawiera przydatne funkcje do pracy z systemem plików. Jedną z zalet w stosunku do natywnych funkcji PHP jest to, że w przypadku błędów rzucają one wyjątki. +[api:Nette\Utils\FileSystem] to klasa z przydatnymi funkcjami do pracy z systemem plików. Jedną z zalet w porównaniu do natywnych funkcji PHP jest to, że w przypadku błędu rzucają wyjątki. +Jeśli potrzebujesz wyszukiwać pliki na dysku, użyj [Findera|finder]. + Instalacja: ```shell composer require nette/utils ``` -Poniższe przykłady zakładają, że zdefiniowany jest następujący alias klasy: +Poniższe przykłady zakładają, że został utworzony alias: ```php use Nette\Utils\FileSystem; ``` -Manipulacja .[#toc-manipulation] -================================ +Manipulacja +=========== copy(string $origin, string $target, bool $overwrite=true): void .[method] -------------------------------------------------------------------------- -Kopiuje plik lub cały katalog. Domyślnie nadpisuje istniejące pliki i katalogi. Jeśli `$overwrite` jest ustawione na `false`, a `$target` już istnieje, rzuca wyjątek `Nette\InvalidStateException`. Rzuca wyjątek `Nette\IOException` w przypadku wystąpienia błędu. +Kopiuje plik lub cały katalog. Domyślnie nadpisuje istniejące pliki i katalogi. Z parametrem `$overwrite` ustawionym na `false` rzuca wyjątek `Nette\InvalidStateException`, jeśli docelowy plik lub katalog `$target` istnieje. W przypadku błędu rzuca wyjątek `Nette\IOException`. ```php FileSystem::copy('/path/to/source', '/path/to/dest', overwrite: true); ``` -createDir(string $directory, int $mode=0777): void .[method] ------------------------------------------------------------- +createDir(string $dir, int $mode=0777): void .[method] +------------------------------------------------------ -Tworzy katalog, jeśli nie istnieje, łącznie z katalogami nadrzędnymi. Rzuca wyjątek `Nette\IOException` w przypadku wystąpienia błędu. +Tworzy katalog, jeśli nie istnieje, włącznie z katalogami nadrzędnymi. W przypadku błędu rzuca wyjątek `Nette\IOException`. ```php FileSystem::createDir('/path/to/dir'); @@ -45,7 +47,7 @@ FileSystem::createDir('/path/to/dir'); delete(string $path): void .[method] ------------------------------------ -Usuwa plik lub cały katalog, jeśli istnieje. Jeśli katalog nie jest pusty, najpierw usuwa jego zawartość. Rzuca wyjątek `Nette\IOException` w przypadku wystąpienia błędu. +Usuwa plik lub cały katalog, jeśli istnieje. Jeśli katalog nie jest pusty, najpierw usuwa jego zawartość. W przypadku błędu rzuca wyjątek `Nette\IOException`. ```php FileSystem::delete('/path/to/fileOrDir'); @@ -55,7 +57,7 @@ FileSystem::delete('/path/to/fileOrDir'); makeWritable(string $path, int $dirMode=0777, int $fileMode=0666): void .[method] --------------------------------------------------------------------------------- -Ustawia uprawnienia do plików na `$fileMode` lub uprawnienia do katalogów na `$dirMode`. Rekursywnie przemierza i ustawia uprawnienia również na całej zawartości katalogu. +Ustawia uprawnienia pliku na `$fileMode` lub katalogu na `$dirMode`. Rekurencyjnie przechodzi i ustawia uprawnienia również dla całej zawartości katalogu. ```php FileSystem::makeWritable('/path/to/fileOrDir'); @@ -65,7 +67,7 @@ FileSystem::makeWritable('/path/to/fileOrDir'); open(string $path, string $mode): resource .[method] ---------------------------------------------------- -Otwiera plik i zwraca zasób. Parametr `$mode` działa tak samo jak natywna funkcja `fopen()`:https://www.php.net/manual/en/function.fopen.php. Jeśli wystąpi błąd, podnosi wyjątek `Nette\IOException`. +Otwiera plik i zwraca zasób (resource). Parametr `$mode` działa tak samo jak w natywnej funkcji `fopen()`:https://www.php.net/manual/en/function.fopen.php. W przypadku błędu rzuca wyjątek `Nette\IOException`. ```php $res = FileSystem::open('/path/to/file', 'r'); @@ -75,7 +77,7 @@ $res = FileSystem::open('/path/to/file', 'r'); read(string $file): string .[method] ------------------------------------ -Odczytuje zawartość pliku `$file`. Rzuca wyjątek `Nette\IOException` w przypadku wystąpienia błędu. +Zwraca zawartość pliku `$file`. W przypadku błędu rzuca wyjątek `Nette\IOException`. ```php $content = FileSystem::read('/path/to/file'); @@ -85,8 +87,7 @@ $content = FileSystem::read('/path/to/file'); readLines(string $file, bool $stripNewLines=true): \Generator .[method] ----------------------------------------------------------------------- -Odczytuje zawartość pliku linia po linii. W odróżnieniu od natywnej funkcji `file()`, nie czyta ona całego pliku do pamięci, lecz czyta go w sposób ciągły, dzięki czemu można odczytać pliki większe niż dostępna pamięć. Parametr `$stripNewLines` określa, czy usuwać znaki przerwania linii `\r` i `\n`. -W przypadku błędu podnosi wyjątek `Nette\IOException`. +Czyta zawartość pliku linia po linii. W przeciwieństwie do natywnej funkcji `file()`, nie wczytuje całego pliku do pamięci, ale czyta go na bieżąco, dzięki czemu można czytać pliki większe niż dostępna pamięć. `$stripNewLines` określa, czy usuwać znaki końca linii `\r` i `\n`. W przypadku błędu rzuca wyjątek `Nette\IOException`. ```php $lines = FileSystem::readLines('/path/to/file'); @@ -100,7 +101,7 @@ foreach ($lines as $lineNum => $line) { rename(string $origin, string $target, bool $overwrite=true): void .[method] ---------------------------------------------------------------------------- -Zmienia nazwę lub przenosi plik lub katalog określony przez `$origin` na `$target`. Domyślnie nadpisuje istniejące pliki i katalogi. Jeśli `$overwrite` jest ustawione na `false` a `$target` już istnieje, rzuca wyjątek `Nette\InvalidStateException`. Rzuca wyjątek `Nette\IOException` w przypadku wystąpienia błędu. +Zmienia nazwę lub przenosi plik lub katalog `$origin`. Domyślnie nadpisuje istniejące pliki i katalogi. Z parametrem `$overwrite` ustawionym na `false` rzuca wyjątek `Nette\InvalidStateException`, jeśli docelowy plik lub katalog `$target` istnieje. W przypadku błędu rzuca wyjątek `Nette\IOException`. ```php FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); @@ -110,21 +111,21 @@ FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); write(string $file, string $content, int $mode=0666): void .[method] -------------------------------------------------------------------- -Zapisuje adres `$content` na adres `$file`. Wyrzuca wyjątek `Nette\IOException` w przypadku wystąpienia błędu. +Zapisuje ciąg znaków `$content` do pliku `$file`. W przypadku błędu rzuca wyjątek `Nette\IOException`. ```php FileSystem::write('/path/to/file', $content); ``` -Ścieżki .[#toc-paths] -===================== +Ścieżki +======= isAbsolute(string $path): bool .[method] ---------------------------------------- -Określa, czy strona `$path` jest bezwzględna. +Sprawdza, czy ścieżka `$path` jest absolutna. ```php FileSystem::isAbsolute('../backup'); // false @@ -146,7 +147,7 @@ FileSystem::joinPaths('/a/', '/../b'); // '/b' normalizePath(string $path): string .[method] --------------------------------------------- -Normalizuje `..` i `.` oraz separatory katalogów w ścieżce. +Normalizuje `..` i `.` oraz separatory katalogów w ścieżce do systemowych. ```php FileSystem::normalizePath('/file/.'); // '/file/' @@ -169,8 +170,45 @@ $path = FileSystem::unixSlashes($path); platformSlashes(string $path): string .[method] ----------------------------------------------- -Konwertuje ukośniki na znaki specyficzne dla bieżącej platformy, tj. `\` w systemie Windows i `/` gdzie indziej. +Konwertuje ukośniki na znaki specyficzne dla aktualnej platformy, tj. `\` w Windows i `/` gdzie indziej. ```php $path = FileSystem::platformSlashes($path); ``` + + +resolvePath(string $basePath, string $path): string .[method]{data-version:4.0.6} +--------------------------------------------------------------------------------- + +Wyprowadza ostateczną ścieżkę ze ścieżki `$path` względem katalogu bazowego `$basePath`. Ścieżki absolutne (`/foo`, `C:/foo`) pozostawia bez zmian (tylko normalizuje ukośniki), ścieżki względne dołącza do ścieżki bazowej. + +```php +// W systemie Windows ukośniki w wyniku byłyby odwrotne (\) +FileSystem::resolvePath('/base/dir', '/abs/path'); // '/abs/path' +FileSystem::resolvePath('/base/dir', 'rel'); // '/base/dir/rel' +FileSystem::resolvePath('base/dir', '../file.txt'); // 'base/file.txt' +FileSystem::resolvePath('base', ''); // 'base' +``` + + +Dostęp statyczny vs niestatyczny +================================ + +Aby na przykład do celów testowania można było łatwo zastąpić klasę inną (mockiem), używaj jej niestatycznie: + +```php +class AnyClassUsingFileSystem +{ + public function __construct( + private FileSystem $fileSystem, + ) { + } + + public function readConfig(): string + { + return $this->fileSystem->read(/* ... */); + } + + ... +} +``` diff --git a/utils/pl/finder.texy b/utils/pl/finder.texy index 4b2d449b2c..48540eb500 100644 --- a/utils/pl/finder.texy +++ b/utils/pl/finder.texy @@ -2,7 +2,7 @@ Finder: wyszukiwanie plików *************************** .[perex] -Potrzebujesz znaleźć pliki pasujące do określonej maski? Finder może ci pomóc. Jest to wszechstronne i szybkie narzędzie do przeglądania struktury katalogów. +Potrzebujesz znaleźć pliki pasujące do określonej maski? Finder Ci w tym pomoże. Jest to wszechstronne i szybkie narzędzie do przeglądania struktury katalogów. Instalacja: @@ -11,17 +11,17 @@ Instalacja: composer require nette/utils ``` -W przykładach założono, że alias został utworzony: +Przykłady zakładają, że został utworzony alias: ```php use Nette\Utils\Finder; ``` -Korzystanie z -------------- +Użycie +------ -Najpierw zobaczmy, jak można użyć [api:Nette\Utils\Finder] do wypisania nazw plików z rozszerzeniami `.txt` i `.md` w bieżącym katalogu: +Najpierw pokażemy, jak za pomocą [api:Nette\Utils\Finder] można wypisać nazwy plików z rozszerzeniami `.txt` i `.md` w bieżącym katalogu: ```php foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { @@ -29,110 +29,111 @@ foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { } ``` -Domyślnym katalogiem dla wyszukiwania jest katalog bieżący, ale możesz go zmienić używając metod [in() lub from() |#Kde se má hledat?]. -Zmienna `$file` jest instancją klasy [FileInfo |#FileInfo] z wieloma przydatnymi metodami. Klucz `$name` zawiera ścieżkę do pliku jako ciąg znaków. +Domyślnym katalogiem do wyszukiwania jest bieżący katalog, ale można go zmienić za pomocą metod [in() lub from() |#Gdzie szukać]. Zmienna `$file` jest instancją klasy [#FileInfo] z wieloma przydatnymi metodami. W kluczu `$name` znajduje się ścieżka do pliku jako ciąg znaków. -Na co zwrócić uwagę? .[#toc-co-se-ma-hledat] --------------------------------------------- +Czego szukać? +------------- -Oprócz metody `findFiles()` istnieje również `findDirectories()`, która przeszukuje tylko katalogi, oraz `find()`, która przeszukuje oba katalogi. Te metody są statyczne, więc można je wywołać bez tworzenia instancji. Parametr maski jest opcjonalny, jeśli go nie określisz, wszystko jest przeszukiwane. +Oprócz metody `findFiles()` istnieje również `findDirectories()`, która szuka tylko katalogów, oraz `find()`, która szuka obu. Te metody są statyczne, więc można je wywoływać bez tworzenia instancji. Parametr z maską jest opcjonalny, jeśli go nie podasz, wyszukane zostanie wszystko. ```php foreach (Finder::find() as $file) { - echo $file; // wszystkie pliki i katalogi są teraz wymienione + echo $file; // teraz zostaną wypisane wszystkie pliki i katalogi } ``` -Użyj metod `files()` i `directories()`, aby dodać, co jeszcze należy wyszukać. Metody te mogą być wywoływane wielokrotnie, a jako parametr można podać tablicę masek: +Za pomocą metod `files()` i `directories()` możesz uzupełniać, co jeszcze ma być wyszukiwane. Metody można wywoływać wielokrotnie, a jako parametr można podać również tablicę masek: ```php Finder::findDirectories('vendor') // wszystkie katalogi ->files(['*.php', '*.phpt']); // plus wszystkie pliki PHP ``` -Alternatywą dla metod statycznych jest utworzenie instancji za pomocą `new Finder` (świeży obiekt utworzony w ten sposób niczego nie wyszukuje) i określenie, czego ma szukać za pomocą `files()` i `directories()`: +Alternatywą dla metod statycznych jest utworzenie instancji za pomocą `new Finder` (tak utworzony świeży obiekt niczego nie wyszukuje) i określenie, czego szukać za pomocą `files()` i `directories()`: ```php (new Finder) - ->directories() // wszystkie katalogi - ->files('*.php'); // plus wszystkie pliki PHP + ->directories() // wszystkie katalogi + ->files('*.php'); // plus wszystkie pliki PHP ``` -W masce można używać [znaków wieloznacznych |#Excluding] `*`, `**`, `?` a `[...]`. Możesz nawet określić w katalogach, na przykład `src/*.php` będzie szukać wszystkich plików PHP w katalogu `src`. +W masce można używać [symboli wieloznacznych |#Symbole wieloznaczne] `*`, `**`, `?` i `[...]`. Można nawet określić katalogi, na przykład `src/*.php` wyszuka wszystkie pliki PHP w katalogu `src`. +Linki symboliczne są również traktowane jako katalogi lub pliki. -Gdzie szukać? .[#toc-kde-se-ma-hledat] --------------------------------------- -Domyślnym katalogiem wyszukiwania jest katalog bieżący. Możesz to zmienić używając metod `in()` i `from()` Jak widać z nazw metod, `in()` przeszukuje tylko bieżący katalog, natomiast `from()` przeszukuje jego podkatalogi (rekurencyjnie). Jeśli chcesz szukać rekurencyjnie w bieżącym katalogu, możesz użyć `from('.')`. +Gdzie szukać? +------------- + +Domyślnym katalogiem do wyszukiwania jest bieżący katalog. Zmienisz go za pomocą metod `in()` i `from()`. Jak wynika z nazw metod, `in()` szuka tylko w danym katalogu, podczas gdy `from()` szuka również w jego podkatalogach (rekurencyjnie). Jeśli chcesz wyszukiwać rekurencyjnie w bieżącym katalogu, możesz użyć `from('.')`. -Możesz wywołać te metody wielokrotnie lub przekazać do nich wiele ścieżek jako tablice, wtedy pliki będą przeszukiwane we wszystkich katalogach. Jeśli jeden z katalogów nie istnieje, rzucany jest wyjątek `Nette\UnexpectedValueException`. +Te metody można wywoływać wielokrotnie lub przekazać im wiele ścieżek jako tablicę, pliki będą wtedy szukane we wszystkich katalogach. Jeśli któryś z katalogów nie istnieje, zostanie rzucony wyjątek `Nette\UnexpectedValueException`. ```php Finder::findFiles('*.php') - ->in(['src', 'tests']) // wyszukuje bezpośrednio w src/ i tests/ - ->from('vendor'); // wyszukuje również w podkatalogach vendor/ + ->in(['src', 'tests']) // szuka bezpośrednio w src/ i tests/ + ->from('vendor'); // szuka również w podkatalogach vendor/ ``` -Ścieżki relatywne są względne w stosunku do bieżącego katalogu. Oczywiście można również określić ścieżki bezwzględne: +Ścieżki względne są względne względem bieżącego katalogu. Oczywiście można również podać ścieżki absolutne: ```php Finder::findFiles('*.php') ->in('/var/www/html'); ``` -Możesz użyć symboli wieloznacznych `*`, `**`, `?`. Můžete tak třeba pomocí cesty `src/*/*.php` w ścieżce, aby wyszukać wszystkie pliki PHP w katalogach drugiego poziomu w katalogu `src`. Symbol [wieloznaczny |#Excluding] `**`, zwany globstar, jest potężnym atutem, ponieważ pozwala na wyszukiwanie również w podkatalogach: użyj `src/**/tests/*.php`, aby wyszukać wszystkie pliki PHP w katalogu `tests` znajdujące się w `src` lub dowolnym jego podkatalogu. +W ścieżce można używać [symboli wieloznacznych |#Symbole wieloznaczne] `*`, `**`, `?`. Możesz na przykład za pomocą ścieżki `src/*/*.php` szukać wszystkich plików PHP w katalogach drugiego poziomu w katalogu `src`. Znak `**` nazywany globstar jest potężnym atutem, ponieważ umożliwia szukanie również w podkatalogach: za pomocą `src/**/tests/*.php` szukasz wszystkich plików PHP w katalogu `tests` znajdującym się w `src` lub dowolnym jego podkatalogu. -Z drugiej strony, symbole wieloznaczne `[...]` znaki nie są obsługiwane w ścieżce, tzn. nie mają specjalnego znaczenia, aby uniknąć niepożądanego zachowania w przypadku, gdy użytkownik szuka `in(__DIR__)` i przypadkowo w ścieżce pojawiają się znaki `[]`. +Natomiast symbole wieloznaczne `[...]` w ścieżce nie są obsługiwane, tj. nie mają specjalnego znaczenia, aby nie dochodziło do niepożądanego zachowania w przypadku, gdy będziesz szukać na przykład `in(__DIR__)` i przypadkiem w ścieżce pojawią się znaki `[]`. -Podczas wyszukiwania plików i katalogów w głąb, najpierw zwracany jest katalog nadrzędny, a następnie pliki w nim zawarte, co można odwrócić za pomocą `childFirst()`. +Podczas wyszukiwania plików i katalogów w głąb, najpierw zwracany jest katalog nadrzędny, a dopiero potem pliki w nim zawarte, co można odwrócić za pomocą `childFirst()`. -Żbiki ------ +Symbole wieloznaczne +-------------------- -W masce można użyć kilku znaków specjalnych: +W masce można używać kilku specjalnych znaków: -- `*` - nahrazuje libovolný počet libovolných znaků (kromě `/`) -- `**` - zastępuje dowolną liczbę dowolnych znaków, w tym `/` (czyli może być przeszukiwany wielopoziomowo) -- `?` - nahrazuje jeden libovolný znak (kromě `/`) +- `*` - zastępuje dowolną liczbę dowolnych znaków (oprócz `/`) +- `**` - zastępuje dowolną liczbę dowolnych znaków, w tym `/` (tj. można szukać wielopoziomowo) +- `?` - zastępuje jeden dowolny znak (oprócz `/`) - `[a-z]` - zastępuje jeden znak z listy znaków w nawiasach kwadratowych - `[!a-z]` - zastępuje jeden znak spoza listy znaków w nawiasach kwadratowych -Przykłady zastosowania: +Przykłady użycia: -- `img/?.png` - pliki o jednoliterowej nazwie `0.png`, `1.png`, `x.png`, itd. -- `logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log` - pliki dziennika w formacie `YYYY-MM-DD` +- `img/?.png` - pliki o jednoliterowej nazwie `0.png`, `1.png`, `x.png`, itp. +- `logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log` - logi w formacie `YYYY-MM-DD` - `src/**/tests/*` - pliki w katalogu `src/tests`, `src/foo/tests`, `src/foo/bar/tests` i tak dalej. - `docs/**.md` - wszystkie pliki z rozszerzeniem `.md` we wszystkich podkatalogach katalogu `docs` -Z wyłączeniem -------------- +Wykluczenie +----------- -Użyj metody `exclude()`, aby wykluczyć pliki i katalogi z wyszukiwań. Określasz maskę, do której plik nie może pasować. Przykład wyszukiwania plików `*.txt` z wyjątkiem tych zawierających w nazwie literę `X`: +Za pomocą metody `exclude()` można wykluczyć pliki i katalogi z wyszukiwania. Podajesz maskę, której plik nie może pasować. Przykład wyszukiwania plików `*.txt` oprócz tych, które zawierają w nazwie literę `X`: ```php Finder::findFiles('*.txt') ->exclude('*X*'); ``` -Użyj `exclude()` aby pominąć przeglądane podkatalogi: +Aby pominąć przeglądane podkatalogi, użyj `exclude()`: ```php Finder::findFiles('*.php') ->from($dir) - ->exclude('temp', '.git') + ->exclude('temp', '.git'); ``` -Filtrowanie .[#toc-filtrovani] ------------------------------- +Filtrowanie +----------- -Finder oferuje kilka metod filtrowania wyników (czyli ich zmniejszania). Można je łączyć i nazywać wielokrotnie. +Finder oferuje kilka metod filtrowania wyników (tj. ich redukcji). Możesz je łączyć i wywoływać wielokrotnie. -Użyj `size()`, aby filtrować według rozmiaru pliku. W ten sposób znajdujemy pliki o rozmiarach od 100 do 200 bajtów: +Za pomocą `size()` filtrujemy według rozmiaru pliku. W ten sposób znajdziemy pliki o rozmiarze w zakresie od 100 do 200 bajtów: ```php Finder::findFiles('*.php') @@ -140,19 +141,19 @@ Finder::findFiles('*.php') ->size('<=', 200); ``` -Metoda `date()` filtruje według daty ostatniej modyfikacji pliku. Wartości mogą być bezwzględne lub względne w stosunku do bieżącej daty i czasu, na przykład w ten sposób można znaleźć pliki zmienione w ciągu ostatnich dwóch tygodni: +Metoda `date()` filtruje według daty ostatniej modyfikacji pliku. Wartości mogą być absolutne lub względne względem bieżącej daty i czasu, na przykład w ten sposób znajdziemy pliki zmodyfikowane w ciągu ostatnich dwóch tygodni: ```php Finder::findFiles('*.php') ->date('>', '-2 weeks') - ->from($dir) + ->from($dir); ``` Obie funkcje rozumieją operatory `>`, `>=`, `<`, `<=`, `=`, `!=`, `<>`. -Finder umożliwia również filtrowanie wyników przy użyciu funkcji niestandardowych. Funkcja otrzymuje jako parametr obiekt `Nette\Utils\FileInfo` i musi zwrócić `true`, aby włączyć plik do wyników. +Finder umożliwia również filtrowanie wyników za pomocą własnych funkcji. Funkcja otrzymuje jako parametr obiekt `Nette\Utils\FileInfo` i musi zwrócić `true`, aby plik został uwzględniony w wynikach. -Przykład: wyszukaj pliki PHP, które zawierają ciąg `Nette` (wielkość liter nie ma znaczenia): +Przykład: wyszukiwanie plików PHP, które zawierają ciąg znaków `Nette` (bez względu na wielkość liter): ```php Finder::findFiles('*.php') @@ -160,51 +161,51 @@ Finder::findFiles('*.php') ``` -Filtrowanie głębokości .[#toc-filtrovani-do-hloubky] ----------------------------------------------------- +Filtrowanie w głąb +------------------ -Podczas wyszukiwania rekurencyjnego można ustawić maksymalną głębokość indeksowania za pomocą metody `limitDepth()`. Jeśli ustawisz `limitDepth(1)`, indeksowane są tylko pierwsze podkatalogi, `limitDepth(0)` wyłącza indeksowanie głębokości, a wartość -1 anuluje limit. +Podczas wyszukiwania rekurencyjnego możesz ustawić maksymalną głębokość przeglądania za pomocą metody `limitDepth()`. Jeśli ustawisz `limitDepth(1)`, przeglądane są tylko pierwsze podkatalogi, `limitDepth(0)` wyłącza przeglądanie w głąb, a wartość -1 znosi limit. -Finder pozwala na wykorzystanie własnych funkcji do decydowania, do którego katalogu wejść podczas przeglądania. Funkcja otrzymuje jako parametr obiekt `Nette\Utils\FileInfo` i musi zwrócić `true`, aby wejść do katalogu: +Finder umożliwia za pomocą własnych funkcji decydowanie, do którego katalogu wejść podczas przeglądania. Funkcja otrzymuje jako parametr obiekt `Nette\Utils\FileInfo` i musi zwrócić `true`, aby wejść do katalogu: ```php Finder::findFiles('*.php') - ->descentFilter($file->getBasename() !== 'temp'); + ->descentFilter(fn($file) => $file->getBasename() !== 'temp'); ``` -Sortowanie .[#toc-razeni] -------------------------- +Sortowanie +---------- -Finder oferuje również kilka funkcji sortowania wyników. +Finder oferuje również kilka funkcji do sortowania wyników. -Metoda `sortByName()` sortuje wyniki według nazwy pliku. Sortowanie jest naturalne, tzn. poprawnie obsługuje liczby w nazwach i zwraca np. `foo1.txt` przed `foo10.txt`. +Metoda `sortByName()` sortuje wyniki według nazw plików. Sortowanie jest naturalne, czyli poprawnie radzi sobie z liczbami w nazwach i zwraca np. `foo1.txt` przed `foo10.txt`. -Finder umożliwia również sortowanie przy użyciu funkcji niestandardowej. Przyjmuje dwa obiekty `Nette\Utils\FileInfo` jako parametry i musi zwrócić wynik porównania za pomocą operatora `<=>`, czyli `-1`, `0` nebo `1`. Na przykład w ten sposób sortujemy pliki według wielkości: +Finder umożliwia również sortowanie za pomocą własnej funkcji. Otrzymuje ona jako parametr dwa obiekty `Nette\Utils\FileInfo` i musi zwrócić wynik porównania operatorem `<=>`, czyli `-1`, `0` lub `1`. Na przykład w ten sposób posortujemy pliki według rozmiaru: ```php $finder->sortBy(fn($a, $b) => $a->getSize() <=> $b->getSize()); ``` -Wiele różnych wyszukiwań .[#toc-vice-ruznych-hledani] ------------------------------------------------------ +Wiele różnych wyszukiwań +------------------------ -Jeśli musisz znaleźć wiele różnych plików w różnych lokalizacjach lub spełniających różne kryteria, użyj metody `append()`. Zwraca ona nowy obiekt `Finder`, dzięki czemu możesz łączyć wywołania metod: +Jeśli potrzebujesz znaleźć więcej różnych plików w różnych lokalizacjach lub spełniających inne kryteria, użyj metody `append()`. Zwraca ona nowy obiekt `Finder`, więc możliwe jest łączenie wywołań metod w łańcuch: ```php -($finder = new Finder) // przechowuj pierwszego Findera w zmiennej $finder! - ->files('*.php') // szukaj plików *.php w src/ +($finder = new Finder) // do zmiennej $finder zapisujemy pierwszy Finder! + ->files('*.php') // w src/ szukamy plików *.php ->from('src') ->append() - ->files('*.md') // w docs/ szukaj plików *.md + ->files('*.md') // w docs/ szukamy plików *.md ->from('docs') ->append() - ->files('*.json'); // w bieżącym folderze szukaj plików *.json + ->files('*.json'); // w bieżącym folderze szukamy plików *.json ``` -Alternatywnie możesz użyć metody `append()`, aby dodać określony plik (lub tablicę plików). Następnie zwraca ten sam obiekt `Finder`: +Alternatywnie można użyć metody `append()` do dodania konkretnego pliku (lub tablicy plików). Wtedy zwraca ten sam obiekt `Finder`: ```php $finder = Finder::findFiles('*.txt') @@ -212,12 +213,12 @@ $finder = Finder::findFiles('*.txt') ``` -FileInfo .[#toc-fileinfo] -------------------------- +FileInfo +-------- -[Nette\Utils\FileInfo |api:] to klasa reprezentująca plik lub katalog w wynikach wyszukiwania. Jest to rozszerzenie klasy [SplFileInfo |php:SplFileInfo], które dostarcza informacji takich jak rozmiar pliku, data ostatniej modyfikacji, nazwa, ścieżka itp. +[Nette\Utils\FileInfo |api:] to klasa reprezentująca plik lub katalog w wynikach wyszukiwania. Jest to rozszerzenie klasy [SplFileInfo |php:SplFileInfo], która dostarcza informacji takich jak rozmiar pliku, data ostatniej modyfikacji, nazwa, ścieżka itp. -Dodatkowo zapewnia metody zwracania ścieżek względnych, co jest przydatne podczas głębokiego przeglądania: +Dodatkowo dostarcza metody do zwracania ścieżki względnej, co jest przydatne podczas przeglądania w głąb: ```php foreach (Finder::findFiles('*.jpg')->from('.') as $file) { @@ -226,7 +227,7 @@ foreach (Finder::findFiles('*.jpg')->from('.') as $file) { } ``` -Masz również metody odczytu i zapisu zawartości pliku: +Ponadto masz do dyspozycji metody do odczytu i zapisu zawartości pliku: ```php foreach ($finder as $file) { @@ -237,12 +238,12 @@ foreach ($finder as $file) { ``` -Zwracanie wyników w postaci tablicy .[#toc-vraceni-vysledku-jako-pole] ----------------------------------------------------------------------- +Zwracanie wyników jako tablica +------------------------------ -Jak widać w przykładach, Finder implementuje interfejs `IteratorAggregate`, więc możesz użyć `foreach` do przeglądania wyników. Jest zaprogramowany tak, że wyniki są ładowane tylko w trakcie przeglądania, więc jeśli masz dużą liczbę plików, nie czeka na odczytanie ich wszystkich. +Jak było widać na przykładach, Finder implementuje interfejs `IteratorAggregate`, więc możesz użyć `foreach` do przeglądania wyników. Jest zaprogramowany tak, że wyniki są ładowane tylko w trakcie przeglądania, więc jeśli masz dużą liczbę plików, nie czeka się, aż wszystkie zostaną odczytane. -Możesz również zlecić zwrócenie wyników w postaci tablicy obiektów `Nette\Utils\FileInfo`, używając metody `collect()` Tablica nie jest asocjacyjna, lecz numeryczna. +Wyniki można również otrzymać jako tablicę obiektów `Nette\Utils\FileInfo`, za pomocą metody `collect()`. Tablica nie jest asocjacyjna, lecz numeryczna. ```php $array = $finder->findFiles('*.php')->collect(); diff --git a/utils/pl/floats.texy b/utils/pl/floats.texy index d10b3e72ae..4555a98dc9 100644 --- a/utils/pl/floats.texy +++ b/utils/pl/floats.texy @@ -1,8 +1,8 @@ -Praca z pływakami -***************** +Praca z liczbami zmiennoprzecinkowymi (float) +********************************************* .[perex] -[api:Nette\Utils\Floats] jest klasą statyczną zawierającą przydatne funkcje do porównywania liczb dziesiętnych. +[api:Nette\Utils\Floats] to statyczna klasa z przydatnymi funkcjami do porównywania liczb dziesiętnych (zmiennoprzecinkowych). Instalacja: @@ -11,18 +11,17 @@ Instalacja: composer require nette/utils ``` -Wszystkie przykłady zakładają, że alias został utworzony: +Wszystkie przykłady zakładają, że został utworzony alias: ```php use Nette\Utils\Floats; ``` -Motywacja .[#toc-motivation] -============================ +Motywacja +========= -Dlaczego klasa do porównywania spławików, zapytacie? To znaczy, mogę użyć operatorów `<`, `>`, `===` i jestem dobrej myśli. -To nie do końca prawda. Jak myślisz, co ten kod wyprowadzi? +Zastanawiasz się, po co właściwie klasa do porównywania liczb zmiennoprzecinkowych? Przecież mogę użyć operatorów `<`, `>`, `===` i mam spokój. Nie jest to do końca prawda. Co myślisz, że wypisze ten kod? ```php $a = 0.1 + 0.2; @@ -30,27 +29,30 @@ $b = 0.3; echo $a === $b ? 'same' : 'not same'; ``` -Jeśli uruchomisz kod, niektórzy z was będą zaskoczeni, że program drukuje `not same`. +Jeśli uruchomisz kod, niektórzy z was na pewno będą zaskoczeni, że program wypisał `not same`. -W operacjach matematycznych z liczbami dziesiętnymi pojawiają się błędy wynikające z konwersji między liczbami dziesiętnymi a binarnymi. Na przykład `0.1 + 0.2` wyprowadza `0.300000000000000044…`. Dlatego przy porównaniach musimy tolerować niewielką różnicę od pewnego miejsca po przecinku. +Podczas operacji matematycznych na liczbach dziesiętnych dochodzi do błędów wynikających z konwersji między systemem dziesiętnym a binarnym. Na przykład `0.1 + 0.2` daje `0.300000000000000044…`. Dlatego przy porównywaniu musimy tolerować niewielką różnicę od pewnego miejsca dziesiętnego. -Tym właśnie zajmuje się klasa `Floats`. Poniższe porównanie będzie działać zgodnie z oczekiwaniami: +I to właśnie robi klasa `Floats`. Poniższe porównanie będzie już działać zgodnie z oczekiwaniami: ```php echo Floats::areEqual($a, $b) ? 'same' : 'not same'; // same ``` -Podczas próby porównania `NAN` rzuca wyjątek `\LogicException`. +Przy próbie porównania `NAN` rzuca wyjątek `\LogicException`. +.[tip] +Klasa `Floats` toleruje różnice mniejsze niż `1e-10`. Jeśli potrzebujesz pracować z większą precyzją, użyj biblioteki BCMath. -Porównywanie pływaków .[#toc-float-comparison] -============================================== + +Porównywanie liczb zmiennoprzecinkowych +======================================= areEqual(float $a, float $b): bool .[method] -------------------------------------------- -Zwraca `true` jeśli `$a` = `$b`. +Zwraca `true`, jeśli `$a` = `$b`. ```php Floats::areEqual(10, 10.0); // true @@ -60,7 +62,7 @@ Floats::areEqual(10, 10.0); // true isLessThan(float $a, float $b): bool .[method] ---------------------------------------------- -Zwraca `true` jeśli `$a` < `$b`. +Zwraca `true`, jeśli zachodzi `$a` < `$b`. ```php Floats::isLessThan(9.5, 10.2); // true @@ -71,7 +73,7 @@ Floats::isLessThan(INF, 10.2); // false isLessThanOrEqualTo(float $a, float $b): bool .[method] ------------------------------------------------------- -Zwraca `true` jeśli `$a` <= `$b`. +Zwraca `true`, jeśli zachodzi `$a` <= `$b`. ```php Floats::isLessThanOrEqualTo(9.5, 10.2); // true @@ -82,7 +84,7 @@ Floats::isLessThanOrEqualTo(10.25, 10.25); // true isGreaterThan(float $a, float $b): bool .[method] ------------------------------------------------- -Zwraca `true` jeśli `$a` > `$b` dotyczy . +Zwraca `true`, jeśli zachodzi `$a` > `$b`. ```php Floats::isGreaterThan(9.5, -10.2); // true @@ -93,7 +95,7 @@ Floats::isGreaterThan(9.5, 10.2); // false isGreaterThanOrEqualTo(float $a, float $b): bool .[method] ---------------------------------------------------------- -Zwraca `true` jeśli `$a` >= `$b`. +Zwraca `true`, jeśli zachodzi `$a` >= `$b`. ```php Floats::isGreaterThanOrEqualTo(9.5, 10.2); // false @@ -104,25 +106,25 @@ Floats::isGreaterThanOrEqualTo(10.2, 10.2); // true compare(float $a, float $b): int .[method] ------------------------------------------ -Jeśli `$a` < `$b`, zwraca `-1`, jeśli równe zwraca `0` a pokud je `$a` > `$b` zwraca `1`. +Jeśli `$a` < `$b`, zwraca `-1`, jeśli są równe, zwraca `0`, a jeśli `$a` > `$b`, zwraca `1`. -Może być używany np. z funkcją `usort`. +Można użyć na przykład z funkcją `usort`. ```php $arr = [1, 5, 2, -3.5]; -usort($arr, [Float::class, 'compare']); -// $arr je nyní [-3.5, 1, 2, 5] +usort($arr, [Floats::class, 'compare']); +// $arr jest teraz [-3.5, 1, 2, 5] ``` -Funkcje pomocnicze .[#toc-helpers-functions] -============================================ +Funkcje pomocnicze +================== isZero(float $value): bool .[method] ------------------------------------ -Zwraca `true`, jeśli wartość jest zerowa. +Zwraca `true`, jeśli wartość jest równa zero. ```php Floats::isZero(0.0); // true diff --git a/utils/pl/helpers.texy b/utils/pl/helpers.texy index 0ef2406543..c4d3a6f1e0 100644 --- a/utils/pl/helpers.texy +++ b/utils/pl/helpers.texy @@ -2,7 +2,7 @@ Funkcje pomocnicze ****************** .[perex] -[api:Nette\Utils\Helpers] jest klasą statyczną z przydatnymi funkcjami. +[api:Nette\Utils\Helpers] to statyczna klasa z przydatnymi funkcjami. Instalacja: @@ -11,7 +11,7 @@ Instalacja: composer require nette/utils ``` -Wszystkie przykłady zakładają, że alias został utworzony: +Wszystkie przykłady zakładają, że został utworzony alias: ```php use Nette\Utils\Helpers; @@ -21,7 +21,7 @@ use Nette\Utils\Helpers; capture(callable $cb): string .[method] --------------------------------------- -Wykonuje wywołanie zwrotne i zwraca przechwycone wyjście jako ciąg znaków. +Wykonuje callback i zwraca przechwycone wyjście jako ciąg znaków. ```php $res = Helpers::capture(function () use ($template) { @@ -33,7 +33,7 @@ $res = Helpers::capture(function () use ($template) { clamp(int|float $value, int|float $min, int|float $max): int|float .[method] ---------------------------------------------------------------------------- -Ogranicza wartość do podanego zakresu włączenia min i max. +Ogranicza wartość do danego zakresu włącznego min i max. ```php Helpers::clamp($level, 0, 255); @@ -43,8 +43,7 @@ Helpers::clamp($level, 0, 255); compare(mixed $left, string $operator, mixed $right): bool .[method] -------------------------------------------------------------------- -Porównuje dwie wartości w taki sam sposób jak robi to PHP. Wyróżnia operatory `>`, `>=`, `<`, `<=`, `=`, `==`, `===`, `!=`, `!==`, `<>`. -Funkcja jest przydatna w sytuacjach, gdy operator jest mutowalny. +Porównuje dwie wartości w ten sam sposób, jak robi to PHP. Rozróżnia operatory `>`, `>=`, `<`, `<=`, `=`, `==`, `===`, `!=`, `!==`, `<>`. Funkcja jest przydatna w sytuacjach, gdy operator jest zmienny. ```php Helpers::compare(10, '<', 20); // true @@ -54,7 +53,7 @@ Helpers::compare(10, '<', 20); // true falseToNull(mixed $value): mixed .[method] ------------------------------------------ -Konwertuje `false` na `null`, nie zmienia innych wartości. +Konwertuje `false` na `null`, innych wartości nie zmienia. ```php Helpers::falseToNull(false); // null @@ -65,7 +64,7 @@ Helpers::falseToNull(123); // 123 getLastError(): string .[method] -------------------------------- -Zwraca ostatni błąd w PHP lub pusty łańcuch, jeśli nie wystąpił żaden błąd. W przeciwieństwie do `error_get_last()`, na nie wpływa dyrektywa PHP `html_errors` i zawsze zwraca tekst, a nie HTML. +Zwraca ostatni błąd w PHP lub pusty ciąg znaków, jeśli nie wystąpił żaden błąd. W przeciwieństwie do `error_get_last()`, nie podlega wpływowi dyrektywy PHP `html_errors` i zawsze zwraca tekst, a nie HTML. ```php Helpers::getLastError(); @@ -75,13 +74,13 @@ Helpers::getLastError(); getSuggestion(string[] $possibilities, string $value): ?string .[method] ------------------------------------------------------------------------ -Spośród oferowanych opcji, `$possibilities` szuka ciągu, który jest najbardziej podobny do `$value`, ale nie taki sam. Obsługuje on tylko kodowanie 8-bitowe. +Z oferowanych możliwości `$possibilities` szuka ciągu znaków, który jest najbardziej podobny do `$value`, ale nie identyczny. Obsługuje tylko kodowanie 8-bitowe. -Przydaje się, gdy dana opcja jest nieważna i chcemy doradzić użytkownikowi podobną (ale inną, więc ten sam ciąg jest ignorowany). W ten sposób Nette tworzy wiadomości `did you mean ...?`. +Przydaje się w przypadku, gdy określona opcja jest nieprawidłowa i chcemy doradzić użytkownikowi podobną (ale inną, dlatego ignorowany jest identyczny ciąg). W ten sposób Nette tworzy komunikaty `did you mean ...?`. ```php $items = ['foo', 'bar', 'baz']; Helpers::getSuggestion($items, 'fo'); // 'foo' Helpers::getSuggestion($items, 'barr'); // 'bar' -Helpers::getSuggestion($items, 'baz'); // 'bar', ne 'baz' +Helpers::getSuggestion($items, 'baz'); // 'bar', nie 'baz' ``` diff --git a/utils/pl/html-elements.texy b/utils/pl/html-elements.texy index 34de52bedf..67107d729f 100644 --- a/utils/pl/html-elements.texy +++ b/utils/pl/html-elements.texy @@ -2,15 +2,15 @@ Elementy HTML ************* .[perex] -Klasa [api:Nette\Utils\Html] jest pomocnikiem do generowania kodu HTML, który nie pozwala na wykorzystanie luk Cross Site Scripting (XSS). +Klasa [api:Nette\Utils\Html] jest pomocnikiem do generowania kodu HTML, który zapobiega powstawaniu podatności Cross Site Scripting (XSS). -Sposób, w jaki działa, polega na tym, że jego obiekty są elementami HTML, do których ustawiamy parametry i pozwalamy im renderować: +Działa tak, że jej obiekty reprezentują elementy HTML, którym ustawiamy parametry i pozwalamy je wyrenderować: ```php -$el = Html::el('img'); // tworzy element +$el = Html::el('img'); // tworzy element $el->src = 'image.jpg'; // ustawia atrybut src -echo $el; // drukuje '' +echo $el; // wypisuje '' ``` Instalacja: @@ -19,29 +19,29 @@ Instalacja: composer require nette/utils ``` -Wszystkie przykłady zakładają, że alias został utworzony: +Wszystkie przykłady zakładają, że został utworzony alias: ```php use Nette\Utils\Html; ``` -Tworzenie elementu HTML .[#toc-creating-an-html-element] -======================================================== +Tworzenie elementu HTML +======================= -Utwórz element za pomocą metody `Html::el()`: +Element tworzymy metodą `Html::el()`: ```php $el = Html::el('img'); // tworzy element ``` -Oprócz nazwy można określić inne atrybuty w składni HTML: +Oprócz nazwy możesz podać również inne atrybuty w składni HTML: ```php $el = Html::el('input type=text class="red important"'); ``` -Lub przekazać je jako pole asocjacyjne z drugim parametrem: +Lub przekazać je jako tablicę asocjacyjną drugim parametrem: ```php $el = Html::el('input', [ @@ -50,39 +50,39 @@ $el = Html::el('input', [ ]); ``` -Zmień i zwróć nazwę elementu: +Zmiana i zwrócenie nazwy elementu: ```php $el->setName('img'); $el->getName(); // 'img' -$el->isEmpty(); // prawda, ponieważ jest pustym elementem +$el->isEmpty(); // true, ponieważ jest pustym elementem ``` -Atrybuty HTML .[#toc-html-attributes] -===================================== +Atrybuty HTML +============= -Istnieją trzy sposoby zmiany i odczytu poszczególnych atrybutów HTML, to od Ciebie zależy, który z nich bardziej Ci się podoba. Pierwszy z nich odbywa się za pośrednictwem nieruchomości: +Poszczególne atrybuty HTML możemy zmieniać i odczytywać na trzy sposoby, zależy od Ciebie, który bardziej Ci się spodoba. Pierwszy z nich to poprzez właściwości (properties): ```php $el->src = 'image.jpg'; // ustawia atrybut src echo $el->src; // 'image.jpg' -unset($el->src); // unset atrybut +unset($el->src); // usuwa atrybut // lub $el->src = null; ``` -Drugim sposobem jest wywołanie metod, które w przeciwieństwie do ustawiania właściwości można łączyć w łańcuchy: +Drugim sposobem jest wywoływanie metod, które w przeciwieństwie do ustawiania właściwości możemy łączyć w łańcuch: ```php $el = Html::el('img')->src('image.jpg')->alt('photo'); // photo -$el->alt(null); // anulowanie atrybutu +$el->alt(null); // usunięcie atrybutu ``` -A trzeci sposób jest najbardziej dosłowny: +A trzeci sposób jest najbardziej rozbudowany: ```php $el = Html::el('img') @@ -94,9 +94,9 @@ echo $el->getAttribute('src'); // 'image.jpg' $el->removeAttribute('alt'); ``` -Atrybuty mogą być ustawiane masowo za pomocą `addAttributes(array $attrs)` i usuwane za pomocą `removeAttributes(array $attrNames)`. +Zbiorczo można ustawić atrybuty za pomocą `addAttributes(array $attrs)` i usunąć za pomocą `removeAttributes(array $attrNames)`. -Wartość atrybutu nie musi być ciągiem znaków, możesz również użyć wartości logicznych dla atrybutów logicznych: +Wartością atrybutu nie musi być tylko ciąg znaków, można używać również wartości logicznych dla atrybutów logicznych: ```php $checkbox = Html::el('input')->type('checkbox'); @@ -104,17 +104,17 @@ $checkbox->checked = true; // $checkbox->checked = false; // ``` -Atrybut może być również tablicą wartości, które są wypisane oddzielone spacjami, co jest przydatne na przykład dla klas CSS: +Atrybutem może być również tablica wartości, które zostaną wypisane oddzielone spacjami, co przydaje się na przykład dla klas CSS: ```php $el = Html::el('input'); $el->class[] = 'active'; -$el->class[] = null; // null se ignoruje +$el->class[] = null; // null jest ignorowane $el->class[] = 'top'; echo $el; // '' ``` -Alternatywą jest tablica asocjacyjna, gdzie wartości mówią, czy klucz powinien być wyprowadzony: +Alternatywą jest tablica asocjacyjna, gdzie wartości mówią, czy klucz ma być wypisany: ```php $el = Html::el('input'); @@ -123,7 +123,7 @@ $el->class['top'] = false; echo $el; // '' ``` -Style CSS mogą być zapisane jako pola asocjacyjne: +Style CSS można zapisywać w formie tablic asocjacyjnych: ```php $el = Html::el('input'); @@ -132,7 +132,7 @@ $el->style['display'] = 'block'; echo $el; // '' ``` -Teraz użyliśmy właściwości, ale to samo można napisać używając metod: +Teraz używaliśmy właściwości, ale to samo można zapisać za pomocą metod: ```php $el = Html::el('input'); @@ -141,7 +141,7 @@ $el->style('display', 'block'); echo $el; // '' ``` -Albo nawet w najbardziej dosadny sposób: +Lub nawet najbardziej rozbudowanym sposobem: ```php $el = Html::el('input'); @@ -150,7 +150,7 @@ $el->appendAttribute('style', 'display', 'block'); echo $el; // '' ``` -Jeden ostatni szczegół: metoda `href()` może ułatwić komponowanie parametrów zapytania w adresach URL: +Jeszcze drobiazg na koniec: metoda `href()` potrafi ułatwić składanie parametrów query w URL: ```php echo Html::el('a')->href('index.php', [ @@ -161,19 +161,19 @@ echo Html::el('a')->href('index.php', [ ``` -Atrybuty danych .[#toc-data-attributes] ---------------------------------------- +Atrybuty danych (Data attributes) +--------------------------------- -Atrybuty danych mają specjalne wsparcie. Ponieważ ich nazwy zawierają myślniki, dostęp poprzez właściwości i metody nie jest tak elegancki, więc istnieje metoda `data()`: +Specjalne wsparcie mają atrybuty danych. Ponieważ ich nazwy zawierają myślniki, dostęp przez właściwości i metody nie jest tak elegancki, dlatego istnieje metoda `data()`: ```php $el = Html::el('input'); -$el->{'data-max-size'} = '500x300'; // nie tak elegancko -$el->data('max-size', '500x300'); // jest elegancko +$el->{'data-max-size'} = '500x300'; // nie jest tak eleganckie +$el->data('max-size', '500x300'); // jest eleganckie echo $el; // '' ``` -Jeśli wartość atrybutu danych jest tablicą, jest ona automatycznie serializowana do JSON: +Jeśli wartością atrybutu danych jest tablica, automatycznie serializuje się do JSON: ```php $el = Html::el('input'); @@ -182,10 +182,10 @@ echo $el; // '' ``` -Zawartość elementu .[#toc-element-content] -========================================== +Zawartość elementu +================== -Ustawia wewnętrzną zawartość elementu metodami `setHtml()` lub `setText()`. Użyj tego pierwszego tylko wtedy, gdy wiesz, że przekazujesz niezawodnie bezpieczny ciąg HTML w parametrze. +Wewnętrzną zawartość elementu ustawiamy metodami `setHtml()` lub `setText()`. Pierwszej z nich używaj tylko w przypadku, gdy wiesz, że w parametrze przekazujesz niezawodnie bezpieczny ciąg HTML. ```php echo Html::el('span')->setHtml('hello
                                                                                                                            '); @@ -195,7 +195,7 @@ echo Html::el('span')->setText('10 < 20'); // '10 < 20' ``` -I odwrotnie, użyj metod `getHtml()` lub `getText()`, aby uzyskać wewnętrzną zawartość. Ta ostatnia metoda usuwa znaczniki HTML z wyjścia i konwertuje encje HTML na znaki. +I odwrotnie, wewnętrzną zawartość uzyskamy metodami `getHtml()` lub `getText()`. Druga z nich usuwa z wyniku znaczniki HTML i konwertuje encje HTML na znaki. ```php echo $el->getHtml(); // '10 < 20' @@ -203,10 +203,10 @@ echo $el->getText(); // '10 < 20' ``` -Węzły dzieci .[#toc-child-nodes] --------------------------------- +Węzły podrzędne +--------------- -Wnętrze elementu może być również tablicą węzłów dziecięcych. Każdy z nich może być ciągiem znaków lub innym elementem `Html`. Wstawia się je za pomocą strony `addHtml()` lub `addText()`: +Wnętrze elementu może być również tablicą węzłów podrzędnych (children). Każdy z nich może być albo ciągiem znaków, albo kolejnym elementem `Html`. Wstawiamy je za pomocą `addHtml()` lub `addText()`: ```php $el = Html::el('span') @@ -216,16 +216,16 @@ $el = Html::el('span') // hello
                                                                                                                            10 < 20
                                                                                                                            ``` -Inny sposób tworzenia i wstawiania nowego węzła `Html`: +Inny sposób na utworzenie i wstawienie nowego węzła `Html`: ```php -$el = Html::el('ul') - ->create('li', ['class' => 'first']) - ->setText('první'); -//
                                                                                                                            • první
                                                                                                                            +$ul = Html::el('ul'); +$ul->create('li', ['class' => 'first']) + ->setText('pierwszy'); +//
                                                                                                                            • pierwszy
                                                                                                                            ``` -Możesz pracować z węzłami tak, jakby były tablicami. To znaczy, uzyskać dostęp do każdego z nich za pomocą nawiasów kwadratowych, policzyć je za pomocą `count()` i iterować nad nimi: +Z węzłami można pracować tak samo, jakby były tablicą. Czyli uzyskiwać dostęp do poszczególnych z nich za pomocą nawiasów kwadratowych, liczyć je za pomocą `count()` i iterować po nich: ```php $el = Html::el('div'); @@ -238,20 +238,20 @@ foreach ($el as $child) { /* ... */ } echo count($el); // 2 ``` -Nowy węzeł może być wstawiony w określonym miejscu za pomocą `insert(?int $index, $child, bool $replace = false)`. Jeśli `$replace = false`, to wstawi element na pozycji `$index` i przesunie pozostałe. Jeśli jest to `$index = null`, to dodaje element jako ostatni. +Nowy węzeł można wstawić w konkretne miejsce za pomocą `insert(?int $index, $child, bool $replace = false)`. Jeśli `$replace = false`, wstawia element na pozycję `$index` i przesuwa pozostałe. Jeśli `$index = null`, dodaje element na koniec. ```php -// wstawia element na pierwszej pozycji i przesuwa pozostałe +// wstawia element na pierwszą pozycję i przesuwa pozostałe $el->insert(0, Html::el('span')); ``` -Wszystkie węzły są pobierane za pomocą metody `getChildren()` i usuwane za pomocą metody `removeChildren()`. +Wszystkie węzły uzyskamy metodą `getChildren()` i usuniemy je metodą `removeChildren()`. -Tworzenie fragmentu dokumentu .[#toc-creating-a-document-fragment] ------------------------------------------------------------------- +Tworzenie fragmentu dokumentu +----------------------------- -Jeśli chcemy pracować z tablicą węzłów i nie interesuje nas element wrapper, możemy stworzyć *fragment dokumentu* przekazując `null` zamiast nazwy elementu: +Jeśli chcemy pracować z tablicą węzłów i nie interesuje nas element otaczający, możemy utworzyć tzw. *fragment dokumentu*, przekazując `null` zamiast nazwy elementu: ```php $el = Html::el(null) @@ -261,7 +261,7 @@ $el = Html::el(null) // hello
                                                                                                                            10 < 20
                                                                                                                            ``` -Metody `fromHtml()` i `fromText()` oferują szybszy sposób tworzenia fragmentu: +Szybszy sposób tworzenia fragmentu oferują metody `fromHtml()` i `fromText()`: ```php $el = Html::fromHtml('hello
                                                                                                                            '); @@ -272,10 +272,10 @@ echo $el; // '10 < 20' ``` -Generowanie danych wyjściowych HTML .[#toc-generating-html-output] -================================================================== +Generowanie wyniku HTML +======================= -Najprostszym sposobem wyprowadzenia elementu HTML jest użycie `echo` lub przepisanie obiektu na `(string)`. Można również wyprowadzić osobno znaczniki otwierające lub zamykające oraz atrybuty: +Najprostszym sposobem na wypisanie elementu HTML jest użycie `echo` lub rzutowanie obiektu na `(string)`. Można również osobno wypisać znaczniki otwierające lub zamykające oraz atrybuty: ```php $el = Html::el('div class=header')->setText('hello'); @@ -289,7 +289,7 @@ echo $el->endTag(); // '' echo $el->attributes(); // 'class="header"' ``` -Ważną cechą jest automatyczna ochrona przed [Cross Site Scripting (XSS) |nette:glossary#Cross-Site-Scripting-XSS]. Wszelkie wartości atrybutów lub treści wstawiane za pośrednictwem `setText()` lub `addText()` są niezawodnie unikane: +Ważną cechą jest automatyczna ochrona przed [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS]. Wszystkie wartości atrybutów lub zawartość wstawiona przez `setText()` lub `addText()` są niezawodnie eskejpowane: ```php echo Html::el('div') @@ -300,17 +300,17 @@ echo Html::el('div') ``` -Konwersja HTML ↔ tekst .[#toc-conversion-html-text] -=================================================== +Konwersja HTML ↔ tekst +====================== -Do konwersji HTML na tekst można użyć statycznej metody `htmlToText()`: +Do konwersji HTML na tekst możesz użyć statycznej metody `htmlToText()`: ```php echo Html::htmlToText('One & Two'); // 'One & Two' ``` -HtmlStringable .[#toc-htmlstringable] -===================================== +HtmlStringable +============== -Obiekt `Nette\Utils\Html` implementuje interfejs `Nette\HtmlStringable`, który jest używany np. przez Latte lub formularze do rozróżniania obiektów posiadających metodę `__toString()` zwracającą kod HTML. Tak więc nie będzie podwójnego escapingu, jeśli na przykład obiekt zostanie wypisany w szablonie za pomocą `{$el}`. +Obiekt `Nette\Utils\Html` implementuje interfejs `Nette\HtmlStringable`, za pomocą którego na przykład Latte lub formularze rozróżniają obiekty, które mają metodę `__toString()` zwracającą kod HTML. Dzięki temu nie dojdzie do podwójnego eskejpowania, jeśli na przykład wypiszemy obiekt w szablonie za pomocą `{$el}`. diff --git a/utils/pl/images.texy b/utils/pl/images.texy index aff3da3f5d..319f088ef8 100644 --- a/utils/pl/images.texy +++ b/utils/pl/images.texy @@ -2,10 +2,10 @@ Praca z obrazami **************** .[perex] -Klasa [api:Nette\Utils\Image] ułatwia manipulowanie obrazami, takie jak zmiana rozmiaru, przycinanie, wyostrzanie, rysowanie czy łączenie wielu obrazów. +Klasa [api:Nette\Utils\Image] upraszcza manipulację obrazami, takimi jak zmiana rozmiaru, przycinanie, wyostrzanie, rysowanie lub łączenie wielu obrazów. -PHP posiada rozbudowany zestaw funkcji do manipulowania obrazami. Jednak ich API nie jest zbyt wygodne. To nie byłby Nette Framework, żeby nie wymyślić seksownego API. +PHP dysponuje obszernym zestawem funkcji do manipulacji obrazami. Ale ich API nie jest zbyt wygodne. Nie byłby to Nette Framework, gdyby nie wymyślił seksownego API. Instalacja: @@ -13,157 +13,168 @@ Instalacja: composer require nette/utils ``` -Wszystkie przykłady zakładają, że alias został utworzony: +Wszystkie przykłady zakładają, że został utworzony alias: ```php use Nette\Utils\Image; +use Nette\Utils\ImageColor; +use Nette\Utils\ImageType; ``` -Tworzenie obrazu .[#toc-creating-an-image] -========================================== +Tworzenie obrazu +================ -Utwórz nowy obraz w kolorze rzeczywistym, na przykład o wymiarach 100×200: +Tworzymy nowy obraz true color, na przykład o wymiarach 100×200: ```php $image = Image::fromBlank(100, 200); ``` -Możesz opcjonalnie określić kolor tła (domyślnie jest to kolor czarny): +Opcjonalnie można określić kolor tła (domyślny to czarny): ```php -$image = Image::fromBlank(100, 200, Image::rgb(125, 0, 0)); +$image = Image::fromBlank(100, 200, ImageColor::rgb(125, 0, 0)); ``` -Albo załadować obraz z pliku: +Lub wczytamy obraz z pliku: ```php $image = Image::fromFile('nette.jpg'); ``` -Obsługiwane formaty to JPEG, PNG, GIF, WebP, AVIF i BMP, ale twoja wersja PHP musi je również obsługiwać (sprawdź `phpinfo()`, sekcja GD). Animacje nie są obsługiwane. -Czy musisz wykryć format obrazu podczas ładowania? Metoda zwraca go w drugim parametrze: +Zapisywanie obrazu +================== + +Obraz można zapisać do pliku: ```php -$image = Image::fromFile('nette.jpg', $type); -// $type to Image::JPEG, Image::PNG, Image::GIF, Image::WEBP, Image::AVIF lub Image::BMP +$image->save('resampled.jpg'); ``` -Tylko wykrywanie bez ładowania obrazu odbywa się przez `Image::detectTypeFromFile()`. - +Możemy określić jakość kompresji w zakresie 0..100 dla JPEG (domyślna 85), WEBP (domyślna 80) i AVIF (domyślna 30) oraz 0..9 dla PNG (domyślna 9): -Zapisywanie obrazu .[#toc-save-the-image] -========================================= +```php +$image->save('resampled.jpg', 80); // JPEG, jakość 80% +``` -Obraz można zapisać do pliku: +Jeśli z rozszerzenia pliku nie jest oczywisty format, można go określić [stałą |#Formaty]: ```php -$image->save('resampled.jpg'); +$image->save('resampled.tmp', null, ImageType::JPEG); ``` -Możemy określić jakość kompresji w zakresie 0..100 dla JPEG (domyślnie 85), WEBP (domyślnie 80) i AVIF (domyślnie 30) oraz 0..9 dla PNG (domyślnie 9): +Obraz można zamiast na dysk zapisać do zmiennej: ```php -$image->save('resampled.jpg', 80); // JPEG, jakość 80% +$data = $image->toString(ImageType::JPEG, 80); // JPEG, jakość 80% ``` -Jeśli format nie jest oczywisty na podstawie rozszerzenia pliku, może być określony przez jedną ze stałych `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF`, oraz `Image::BMP`: +lub wysłać bezpośrednio do przeglądarki z odpowiednim nagłówkiem HTTP `Content-Type`: ```php -$image->save('resampled.tmp', null, Image::JPEG); +// wysyła nagłówek Content-Type: image/png +$image->send(ImageType::PNG); ``` -Obraz może być zapisany do zmiennej zamiast na dysk: + +Formaty +======= + +Obsługiwane formaty to JPEG, PNG, GIF, WebP, AVIF i BMP, jednak muszą być one również obsługiwane przez Twoją wersję PHP, co sprawdzisz funkcją [#isTypeSupported()]. Animacje nie są obsługiwane. + +Format reprezentują stałe `ImageType::JPEG`, `ImageType::PNG`, `ImageType::GIF`, `ImageType::WEBP`, `ImageType::AVIF` i `ImageType::BMP`. ```php -$data = $image->toString(Image::JPEG, 80); // JPEG, qualita 80% +$supported = Image::isTypeSupported(ImageType::JPEG); ``` -lub wysyłane bezpośrednio do przeglądarki z odpowiednim nagłówkiem HTTP `Content-Type`: +Potrzebujesz wykryć format obrazu podczas wczytywania? Metoda zwróci go w drugim parametrze: ```php -// wysyła nagłówek Content-Type: image/png -$image->send(Image::PNG); +$image = Image::fromFile('nette.jpg', $type); ``` +Samo wykrywanie bez wczytywania obrazu wykonuje `Image::detectTypeFromFile()`. + -Zmień rozmiar .[#toc-image-resize] -================================== +Zmiana rozmiaru +=============== -Często wykonywaną operacją jest zmiana rozmiaru obrazu. Rzeczywiste wymiary są zwracane przez metody `getWidth()` i `getHeight()`. +Częstą operacją jest zmiana wymiarów obrazu. Aktualne wymiary zwracają metody `getWidth()` i `getHeight()`. -Metoda `resize()` służy do zmiany rozmiaru obrazu tak, aby nie przekraczał on 500x300 pikseli (albo szerokość będzie wynosiła dokładnie 500 px, albo wysokość dokładnie 300 px, jeden z wymiarów jest obliczany dla zachowania proporcji): +Do zmiany służy metoda `resize()`. Przykład proporcjonalnej zmiany rozmiaru tak, aby nie przekroczył wymiarów 500x300 pikseli (albo szerokość będzie dokładnie 500 px, albo wysokość będzie dokładnie 300 px, jeden z wymiarów zostanie obliczony tak, aby zachowany był stosunek stron): ```php $image->resize(500, 300); ``` -Możliwe jest określenie tylko jednego wymiaru, a drugi zostanie obliczony: +Można określić tylko jeden wymiar, a drugi zostanie obliczony: ```php -$image->resize(500, null); // szerokość 500px, wysokość jest obliczana +$image->resize(500, null); // szerokość 500px, wysokość zostanie obliczona -$image->resize(null, 300); // szerokość obliczona, wysokość 300px +$image->resize(null, 300); // szerokość zostanie obliczona, wysokość 300px ``` -Każdy wymiar może być określony jako procent: +Każdy wymiar można podać również w procentach: ```php -$image->resize('75%', 300); // 75% × 300px +$image->resize('75%', 300); // 75 % × 300px ``` -Na zachowanie `resize` mogą wpływać następujące objawy. Wszystkie poza `Image::Stretch` zachowują proporcje. +Zachowanie `resize` można wpłynąć za pomocą następujących flag. Wszystkie oprócz `Image::Stretch` zachowują stosunek stron. |--------------------------------------------------------------------------------------- -| Flaga | Opis +| Flaga | Opis |--------------------------------------------------------------------------------------- -| `Image::OrSmaller` (domyślnie) | wymiary wynikowe będą mniejsze lub równe żądanym wymiarom -| `Image::OrBigger` | wypełnia (i ewentualnie przekracza w jednym wymiarze) obszar docelowy -| `Image::Cover` | wypełnia obszar docelowy i przycina to, co go przekracza -| `Image::ShrinkOnly` | tylko zmniejszanie (unikanie rozciągania małego obrazu) -| `Image::Stretch` | nie zachowuj proporcji +| `Image::OrSmaller` (domyślna) | wynikowe wymiary będą mniejsze lub równe wymaganym wymiarom +| `Image::OrBigger` | wypełni (i ewentualnie przekroczy w jednym wymiarze) obszar docelowy +| `Image::Cover` | wypełni obszar docelowy i przytnie to, co przekroczy +| `Image::ShrinkOnly` | tylko zmniejszanie (zapobiegnie rozciągnięciu małego obrazu) +| `Image::Stretch` | nie zachowywać stosunku stron -Flagi są podawane jako trzeci argument funkcji: +Flagi podaje się jako trzeci argument funkcji: ```php $image->resize(500, 300, Image::OrBigger); ``` -Flagi mogą być łączone: +Flagi można łączyć: ```php $image->resize(500, 300, Image::ShrinkOnly | Image::Stretch); ``` -Obrazy można obrócić w pionie lub poziomie, nadając jednemu z wymiarów (lub obu) wartość ujemną: +Obrazy można odwrócić pionowo lub poziomo, podając jeden z wymiarów (lub oba) jako liczbę ujemną: ```php -$flipped = $image->resize(null, '-100%'); // przerzuć w pionie +$flipped = $image->resize(null, '-100%'); // odwrócenie pionowe -$flipped = $image->resize('-100%', '-100%'); // obróć o 180°. +$flipped = $image->resize('-100%', '-100%'); // obrót o 180° -$flipped = $image->resize(-125, 500); // zmiana rozmiaru i odwrócenie w poziomie +$flipped = $image->resize(-125, 500); // zmiana rozmiaru i odwrócenie poziome ``` -Po zmniejszeniu obrazu można poprawić jego wygląd poprzez dokładne wyostrzenie: +Po zmniejszeniu obrazu można poprawić jego wygląd delikatnym wyostrzeniem: ```php $image->sharpen(); ``` -Uprawa .[#toc-cropping] -======================= +Przycinanie +=========== -Do uprawy stosuje się metodę `crop()`: +Do przycinania służy metoda `crop()`: ```php $image->crop($left, $top, $width, $height); ``` -Podobnie jak w przypadku `resize()`, wszystkie wartości mogą być podane w procentach. Procenty dla `$left` i `$top` są obliczane z pozostałej przestrzeni, podobnie jak właściwość CSS `background-position`: +Podobnie jak w `resize()`, wszystkie wartości mogą być podane w procentach. Procenty dla `$left` i `$top` są obliczane z pozostałego miejsca, podobnie jak we właściwości CSS `background-position`: ```php $image->crop('100%', '50%', '80%', '80%'); @@ -172,188 +183,213 @@ $image->crop('100%', '50%', '80%', '80%'); [* crop.svg *] -Obraz można również przyciąć automatycznie, na przykład przycinając czarne obramowania: +Obraz można również przyciąć automatycznie, na przykład przycięcie czarnych krawędzi: ```php $image->cropAuto(IMG_CROP_BLACK); ``` -Metoda `cropAuto()` jest obiektowym zamiennikiem funkcji `imagecropauto()`, więcej informacji można znaleźć w [jej dokumentacji |https://www.php.net/manual/en/function.imagecropauto]. +Metoda `cropAuto()` jest obiektowym zamiennikiem funkcji `imagecropauto()`, w [jej dokumentacji|https://www.php.net/manual/en/function.imagecropauto] znajdziesz więcej informacji. + + +Kolory .{data-version:4.0.2} +============================ + +Metoda `ImageColor::rgb()` pozwala definiować kolor za pomocą wartości czerwonej, zielonej i niebieskiej (RGB). Opcjonalnie można również określić wartość przezroczystości w zakresie od 0 (całkowicie przezroczysty) do 1 (całkowicie nieprzezroczysty), czyli tak samo jak w CSS. + +```php +$color = ImageColor::rgb(255, 0, 0); // Czerwony +$transparentBlue = ImageColor::rgb(0, 0, 255, 0.5); // Półprzezroczysty niebieski +``` + +Metoda `ImageColor::hex()` pozwala definiować kolor za pomocą formatu heksadecymalnego, podobnie jak w CSS. Obsługuje formaty `#rgb`, `#rrggbb`, `#rgba` i `#rrggbbaa`: +```php +$color = ImageColor::hex("#F00"); // Czerwony +$transparentGreen = ImageColor::hex("#00FF0080"); // Półprzezroczysta zielony +``` -Rysowanie i edycja .[#toc-drawing-and-editing] -============================================== +Kolory można użyć w innych metodach, takich jak `ellipse()`, `fill()` itp. -Możesz rysować, możesz pisać, ale nie drzyj kartek. Wszystkie funkcje PHP do pracy z obrazami, takie jak [imagefilledellipse |https://www.php.net/manual/en/function.imagefilledellipse.php], są dostępne dla Ciebie, ale w obiektowym domyśle: + +Rysowanie i edycja +================== + +Możesz rysować, możesz pisać, ale listy netrhat. Do dyspozycji są wszystkie funkcje PHP do pracy z obrazami, zobacz [#Przegląd metod], ale w obiektowej szacie: ```php -$image->filledEllipse($cx, $cy, $width, $height, Image::rgb(255, 0, 0, 63)); +$image->filledEllipse($centerX, $centerY, $width, $height, ImageColor::rgb(255, 0, 0)); ``` -Zobacz [Przegląd metod |#Overview-of-Methods]. +Ponieważ funkcje PHP do rysowania prostokątów są niepraktyczne ze względu na określenie współrzędnych, klasa `Image` oferuje ich zamienniki w postaci funkcji [#rectangleWH()] i [#filledRectangleWH()]. -Łączenie wielu obrazów .[#toc-merge-multiple-images] -==================================================== +Łączenie wielu obrazów +====================== -Możesz łatwo wstawić inny obraz do obrazu: +Do obrazu można łatwo wstawić inny obraz: ```php $logo = Image::fromFile('logo.png'); -$blank = Image::fromBlank(320, 240, Image::rgb(52, 132, 210)); +$blank = Image::fromBlank(320, 240, ImageColor::rgb(52, 132, 210)); -// współrzędne mogą być ponownie podane w procentach -$blank->place($logo, '80%', '80%'); // wstawiamy w pobliżu prawego dolnego rogu +// współrzędne można ponownie podać w procentach +$blank->place($logo, '80%', '80%'); // wstawimy w pobliżu prawego dolnego rogu ``` -Alfabet jest respektowany podczas wstawiania, a my możemy wpływać na przezroczystość wstawianego obrazu (tworzymy znak wodny): +Podczas wstawiania respektowany jest kanał alfa, ponadto możemy wpłynąć na przezroczystość wstawianego obrazu (tworzymy tzw. znak wodny): ```php -$blank->place($image, '80%', '80%', 25); // przezroczystość wynosi 25% +$blank->place($image, '80%', '80%', 25); // przezroczystość wynosi 25 % ``` -To API to prawdziwa radość z użytkowania! +Takie API to prawdziwa przyjemność używać! -Przegląd metod .[#toc-overview-of-methods] -========================================== +Przegląd metod +============== -static fromBlank(int $width, int $height, array $color=null): Image .[method] ------------------------------------------------------------------------------ -Tworzy nowy obraz w kolorze rzeczywistym o podanych wymiarach. Domyślnym kolorem jest czarny. +static fromBlank(int $width, int $height, ?ImageColor $color=null): Image .[method] +----------------------------------------------------------------------------------- +Tworzy nowy obraz true color o podanych wymiarach. Domyślny kolor to czarny. static fromFile(string $file, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------- -Odczytuje obraz z pliku i zwraca jego typ w `$detectedFormat`. Obsługiwane typy to `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` oraz `Image::BMP`. +Wczytuje obraz z pliku i zwraca jego [typ |#Formaty] w `$detectedFormat`. static fromString(string $s, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------ -Odczytuje obraz z łańcucha i zwraca jego typ w `$detectedFormat`. Obsługiwane typy to `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF`, oraz `Image::BMP`. +Wczytuje obraz z ciągu znaków i zwraca jego [typ |#Formaty] w `$detectedFormat`. -static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method] ---------------------------------------------------------------------------------- -Tworzy kolor, który może być używany w innych metodach, takich jak `ellipse()`, `fill()`, itp. +static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method][deprecated] +--------------------------------------------------------------------------------------------- +Tę funkcję zastąpiła klasa `ImageColor`, zobacz [#kolory]. static typeToExtension(int $type): string .[method] --------------------------------------------------- -Zwraca rozszerzenie pliku dla podanej stałej `Image::XXX`. +Zwraca rozszerzenie pliku dla danego [typu |#Formaty]. static typeToMimeType(int $type): string .[method] -------------------------------------------------- -Zwraca typ mime dla podanej stałej `Image::XXX`. +Zwraca typ mime dla danego [typu |#Formaty]. static extensionToType(string $extension): int .[method] -------------------------------------------------------- -Zwraca typ obrazu jako stałą `Image::XXX` zgodnie z rozszerzeniem pliku. +Zwraca [typ |#Formaty] obrazu na podstawie rozszerzenia pliku. static detectTypeFromFile(string $file, int &$width=null, int &$height=null): ?int .[method] -------------------------------------------------------------------------------------------- -Zwraca typ obrazu jako stałą `Image::XXX`, a także jego wymiary w parametrach `$width` i `$height`. +Zwraca [typ |#Formaty] obrazu, a w parametrach `$width` i `$height` także jego wymiary. static detectTypeFromString(string $s, int &$width=null, int &$height=null): ?int .[method] ------------------------------------------------------------------------------------------- -Zwraca typ obrazu z ciągu jako stałą `Image::XXX` oraz jego wymiary w parametrach `$width` i `$height`. +Zwraca [typ |#Formaty] obrazu z ciągu znaków, a w parametrach `$width` i `$height` także jego wymiary. -affine(array $affine, array $clip=null): Image .[method] --------------------------------------------------------- -Zwróć obraz zawierający affine-transformed src image używając opcjonalnego regionu przycinania ([więcej |https://www.php.net/manual/en/function.imageaffine]). +static isTypeSupported(int $type): bool .[method] +------------------------------------------------- +Sprawdza, czy obsługiwany jest dany [typ |#Formaty] obrazu. + + +static getSupportedTypes(): array .[method]{data-version:4.0.4} +--------------------------------------------------------------- +Zwraca tablicę obsługiwanych [typów |#Formaty] obrazu. + + +static calculateTextBox(string $text, string $fontFile, float $size, float $angle=0, array $options=[]): array .[method] +------------------------------------------------------------------------------------------------------------------------ +Oblicza wymiary prostokąta, który otacza tekst w określonej czcionce i rozmiarze. Zwraca tablicę asocjacyjną zawierającą klucze `left`, `top`, `width`, `height`. Lewa krawędź może być ujemna, jeśli tekst zaczyna się od lewego podcięcia. + + +affine(array $affine, ?array $clip=null): Image .[method] +--------------------------------------------------------- +Zwraca obraz zawierający afinicznie przekształcony obraz src przy użyciu opcjonalnego obszaru przycinania. ([więcej |https://www.php.net/manual/en/function.imageaffine]). affineMatrixConcat(array $m1, array $m2): array .[method] --------------------------------------------------------- -Zwraca konkatenację dwóch macierzy przekształceń afinicznych, co jest przydatne, gdy do tego samego obrazu należy zastosować wiele przekształceń jednocześnie. ([więcej |https://www.php.net/manual/en/function.imageaffinematrixconcat]) +Zwraca konkatenację dwóch macierzy transformacji afinicznej, co jest przydatne, jeśli do tego samego obrazu należy zastosować jednocześnie wiele transformacji. ([więcej |https://www.php.net/manual/en/function.imageaffinematrixconcat]) -affineMatrixGet(int $type, mixed $options=null): array .[method] ----------------------------------------------------------------- -Zwraca macierz przekształcenia macierzy ([więcej |https://www.php.net/manual/en/function.imageaffinematrixget]). +affineMatrixGet(int $type, ?mixed $options=null): array .[method] +----------------------------------------------------------------- +Zwraca macierz transformacji afinicznej. ([więcej |https://www.php.net/manual/en/function.imageaffinematrixget]) alphaBlending(bool $on): void .[method] --------------------------------------- -Umożliwia dwa różne tryby rysowania w obrazach truecolor. W trybie mieszania składowa kanału alfa koloru używanego we wszystkich funkcjach rysunkowych, takich jak `setPixel()`, określa, w jakim stopniu należy pozwolić, aby kolor podstawowy prześwitywał. W rezultacie w tym momencie istniejący kolor jest automatycznie mieszany z kolorem rysunku, a wynik jest zapisywany w obrazie. Powstały w ten sposób piksel jest nieprzezroczysty. W trybie nie mieszania, kolor kreskówki jest kopiowany dosłownie z informacją o kanale alfa i zastępowany pikselem docelowym. Tryb blend nie jest dostępny podczas rysowania na obrazkach paletowych. ([więcej |https://www.php.net/manual/en/function.imagealphablending]) +Umożliwia dwa różne tryby rysowania w obrazach truecolor. W trybie mieszania składnik kanału alfa koloru używanego we wszystkich funkcjach rysowania, takich jak `setPixel()`, określa, w jakim stopniu kolor podstawowy powinien być widoczny. W rezultacie istniejący kolor w tym punkcie jest automatycznie mieszany z kolorem rysowanym, a wynik jest zapisywany w obrazie. Wynikowy piksel jest nieprzezroczysty. W trybie bez mieszania rysowany kolor jest kopiowany dosłownie wraz z informacjami o kanale alfa i zastępuje piksel docelowy. Tryb mieszania nie jest dostępny podczas rysowania na obrazach paletowych. ([więcej |https://www.php.net/manual/en/function.imagealphablending]) antialias(bool $on): void .[method] ----------------------------------- -Aktywuj rysowanie linii wygładzonych i wielokątów. Nie obsługuje kanałów alfa. Działa tylko z obrazami truecolor. +Aktywuje rysowanie wygładzonych linii i wielokątów. Nie obsługuje kanałów alfa. Działa tylko w przypadku obrazów truecolor. -Użycie antyaliasowanego prymitywu z przezroczystym kolorem tła może skończyć się nieoczekiwanymi rezultatami. Metoda blend wykorzystuje kolor tła jak każdy inny kolor. ([więcej |https://www.php.net/manual/en/function.imageantialias]) +Używanie wygładzonych prymitywów z przezroczystym kolorem tła może prowadzić do nieoczekiwanych wyników. Metoda mieszania używa koloru tła tak jak wszystkich innych kolorów. ([więcej |https://www.php.net/manual/en/function.imageantialias]) -arc(int $x, int $y, int $w, int $h, int $start, int $end, int $color): void .[method] -------------------------------------------------------------------------------------- -Rysuje łuk okręgu o środku w podanych współrzędnych. ([więcej |https://www.php.net/manual/en/function.imagearc]) - - -char(int $font, int $x, int $y, string $char, int $color): void .[method] -------------------------------------------------------------------------- -Rysuje pierwszy znak `$char` w obrazie z lewym górnym rogiem na `$x`, `$y` (lewy górny to 0, 0) w kolorze `$color`. ([więcej |https://www.php.net/manual/en/function.imagechar]) - - -charUp(int $font, int $x, int $y, string $char, int $color): void .[method] ---------------------------------------------------------------------------- -Rysuje znak `$char` w pionie na określonej współrzędnej w podanym obrazie. ([więcej |https://www.php.net/manual/en/function.imagecharup]) +arc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color): void .[method] +--------------------------------------------------------------------------------------------------------------------------- +Rysuje łuk koła ze środkiem w podanych współrzędnych. ([więcej |https://www.php.net/manual/en/function.imagearc]) colorAllocate(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------- -Zwraca identyfikator koloru reprezentujący kolor złożony z podanych składowych RGB. Musi być wywołany, aby stworzyć każdy kolor, który ma być użyty w obrazie. ([więcej |https://www.php.net/manual/en/function.imagecolorallocate]) +Zwraca identyfikator koloru reprezentujący kolor złożony z podanych składników RGB. Musi być wywołana, aby utworzyć każdy kolor, który ma być użyty w obrazie. ([więcej |https://www.php.net/manual/en/function.imagecolorallocate]) colorAllocateAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ------------------------------------------------------------------------------ -Zachowuje się tak samo jak `colorAllocate()` z dodaniem parametru przejrzystości `$alpha`. ([więcej |https://www.php.net/manual/en/function.imagecolorallocatealpha]) +Działa tak samo jak `colorAllocate()` z dodaniem parametru przezroczystości `$alpha`. ([więcej |https://www.php.net/manual/en/function.imagecolorallocatealpha]) colorAt(int $x, int $y): int .[method] -------------------------------------- -Zwraca indeks koloru piksela w określonym miejscu obrazu. Jeśli obraz jest truecolor, funkcja zwraca wartość RGB tego piksela jako liczbę całkowitą. Użyj przesunięcia bitów i maskowania bitów, aby uzyskać dostęp do oddzielnych wartości dla składowej czerwonej, zielonej i niebieskiej: ([więcej |https://www.php.net/manual/en/function.imagecolorat]) +Zwraca indeks koloru piksela w określonym miejscu obrazu. Jeśli obraz jest typu truecolor, funkcja ta zwraca wartość RGB tego piksela jako liczbę całkowitą. Użyj przesunięcia bitowego i maskowania bitowego, aby uzyskać dostęp do poszczególnych wartości składowych czerwonej, zielonej i niebieskiej: ([więcej |https://www.php.net/manual/en/function.imagecolorat]) colorClosest(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------ -Zwraca indeks koloru w palecie obrazów, który jest "najbliższy" określonej wartości RGB. "Odległość" między pożądanym kolorem a każdym kolorem w palecie jest obliczana tak, jakby wartości RGB reprezentowały punkty w przestrzeni trójwymiarowej. ([więcej |https://www.php.net/manual/en/function.imagecolorclosest]) +Zwraca indeks koloru w palecie obrazu, który jest „najbliższy” podanej wartości RGB. "Odległość" między żądanym kolorem a każdym kolorem w palecie jest obliczana tak, jakby wartości RGB reprezentowały punkty w przestrzeni trójwymiarowej. ([więcej |https://www.php.net/manual/en/function.imagecolorclosest]) colorClosestAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ----------------------------------------------------------------------------- -Zwraca indeks koloru w palecie obrazów, który jest "najbliższy" podanej wartości RGB i poziomu `$alpha`. ([więcej |https://www.php.net/manual/en/function.imagecolorclosestalpha]) +Zwraca indeks koloru w palecie obrazu, który jest „najbliższy” podanej wartości RGB i poziomowi `$alpha`. ([więcej |https://www.php.net/manual/en/function.imagecolorclosestalpha]) colorClosestHWB(int $red, int $green, int $blue): int .[method] --------------------------------------------------------------- -Uzyskaj indeks koloru, który ma odcień, biel i czerń najbliższe podanemu kolorowi. ([więcej |https://www.php.net/manual/en/function.imagecolorclosesthwb]) +Pobiera indeks koloru, który ma odcień, biel i czerń najbliższe danemu kolorowi. ([więcej |https://www.php.net/manual/en/function.imagecolorclosesthwb]) colorDeallocate(int $color): void .[method] ------------------------------------------- -De-allocates a color previously assigned by `colorAllocate()` or `colorAllocateAlpha()`. ([więcej |https://www.php.net/manual/en/function.imagecolordeallocate]) +Dealokuje kolor wcześniej przydzielony za pomocą `colorAllocate()` lub `colorAllocateAlpha()`. ([więcej |https://www.php.net/manual/en/function.imagecolordeallocate]) colorExact(int $red, int $green, int $blue): int .[method] ---------------------------------------------------------- -Zwraca indeks określonego koloru w palecie obrazów. ([więcej |https://www.php.net/manual/en/function.imagecolorexact]) +Zwraca indeks określonego koloru w palecie obrazu. ([więcej |https://www.php.net/manual/en/function.imagecolorexact]) colorExactAlpha(int $red, int $green, int $blue, int $alpha): int .[method] --------------------------------------------------------------------------- -Zwraca indeks określonego koloru + alfa w palecie obrazów. ([więcej |https://www.php.net/manual/en/function.imagecolorexactalpha]) +Zwraca indeks określonego koloru + alfa w palecie obrazu. ([więcej |https://www.php.net/manual/en/function.imagecolorexactalpha]) colorMatch(Image $image2): void .[method] ----------------------------------------- -Dopasowuje kolory palety do drugiego panelu. ([więcej |https://www.php.net/manual/en/function.imagecolormatch]) +Dopasowuje kolory palety do drugiego obrazu. ([więcej |https://www.php.net/manual/en/function.imagecolormatch]) colorResolve(int $red, int $green, int $blue): int .[method] @@ -373,103 +409,108 @@ Ustawia określony indeks w palecie na określony kolor. ([więcej |https://www. colorsForIndex(int $index): array .[method] ------------------------------------------- -Uzyskuje kolor określonego indeksu ([więcej |https://www.php.net/manual/en/function.imagecolorsforindex]). +Pobiera kolor dla określonego indeksu. ([więcej |https://www.php.net/manual/en/function.imagecolorsforindex]) colorsTotal(): int .[method] ---------------------------- -Zwraca liczbę kolorów w palecie obrazów. ([więcej |https://www.php.net/manual/en/function.imagecolorstotal]) +Zwraca liczbę kolorów w palecie obrazu. ([więcej |https://www.php.net/manual/en/function.imagecolorstotal]) -colorTransparent(int $color=null): int .[method] ------------------------------------------------- -Uzyskuje lub ustawia kolor przezroczysty w obrazie. ([więcej |https://www.php.net/manual/en/function.imagecolortransparent]) +colorTransparent(?int $color=null): int .[method] +------------------------------------------------- +Pobiera lub ustawia przezroczysty kolor w obrazie. ([więcej |https://www.php.net/manual/en/function.imagecolortransparent]) convolution(array $matrix, float $div, float $offset): void .[method] --------------------------------------------------------------------- -Stosuje macierz konwolucji do obrazu, używając danego współczynnika i przesunięcia. ([więcej |https://www.php.net/manual/en/function.imageconvolution]) +Stosuje macierz splotu do obrazu, używając podanego współczynnika i przesunięcia. ([więcej |https://www.php.net/manual/en/function.imageconvolution]) .[note] -Wymaga obecności rozszerzenia *Bundled GD*, więc może nie działać wszędzie. +Wymaga obecności *Bundled GD extension*, więc może nie działać wszędzie. copy(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH): void .[method] -------------------------------------------------------------------------------------------------- -Kopiuje fragment strony `$src` do obrazu rozpoczynającego się na współrzędnych `$srcX`, `$srcY` o szerokości `$srcW` i wysokości `$srcH`. Zdefiniowana część zostanie skopiowana do współrzędnych `$dstX` i `$dstY`. ([więcej |https://www.php.net/manual/en/function.imagecopy]) +Kopiuje część `$src` do obrazu zaczynając od współrzędnych `$srcX`, `$srcY` o szerokości `$srcW` i wysokości `$srcH`. Zdefiniowana część zostanie skopiowana na współrzędne `$dstX` i `$dstY`. ([więcej |https://www.php.net/manual/en/function.imagecopy]) copyMerge(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] --------------------------------------------------------------------------------------------------------------------- -Kopiuje fragment strony `$src` do obrazu rozpoczynającego się na współrzędnych `$srcX`, `$srcY` o szerokości `$srcW` i wysokości `$srcH`. Zdefiniowana część zostanie skopiowana do współrzędnych `$dstX` i `$dstY`. ([więcej |https://www.php.net/manual/en/function.imagecopymerge]) +Kopiuje część `$src` do obrazu zaczynając od współrzędnych `$srcX`, `$srcY` o szerokości `$srcW` i wysokości `$srcH`. Zdefiniowana część zostanie skopiowana na współrzędne `$dstX` i `$dstY`. ([więcej |https://www.php.net/manual/en/function.imagecopymerge]) copyMergeGray(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] ------------------------------------------------------------------------------------------------------------------------- -Kopiuje fragment strony `$src` do obrazu rozpoczynającego się na współrzędnych `$srcX`, `$srcY` o szerokości `$srcW` i wysokości `$srcH`. Zdefiniowana część zostanie skopiowana na współrzędne `$dstX` i `$dstY`. +Kopiuje część `$src` do obrazu zaczynając od współrzędnych `$srcX`, `$srcY` o szerokości `$srcW` i wysokości `$srcH`. Zdefiniowana część zostanie skopiowana na współrzędne `$dstX` i `$dstY`. -Funkcja ta jest identyczna jak `copyMerge()` z tym wyjątkiem, że zachowuje źródłowy odcień podczas scalania poprzez konwersję pikseli docelowych do skali szarości przed operacją kopiowania. ([więcej |https://www.php.net/manual/en/function.imagecopymergegray]) +Ta funkcja jest identyczna z `copyMerge()` z tą różnicą, że podczas łączenia zachowuje odcień źródła poprzez konwersję pikseli docelowych na skalę szarości przed operacją kopiowania. ([więcej |https://www.php.net/manual/en/function.imagecopymergegray]) copyResampled(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] --------------------------------------------------------------------------------------------------------------------------------- -Kopiuje prostokątny fragment jednego obrazu do drugiego, płynnie interpolując wartości pikseli, dzięki czemu, zwłaszcza przy zmniejszaniu rozmiaru, obraz nadal zachowuje dużą wyrazistość. +Kopiuje prostokątną część jednego obrazu do drugiego obrazu, płynnie interpolując wartości pikseli, dzięki czemu zwłaszcza zmniejszenie rozmiaru obrazu nadal zachowuje dużą klarowność. -Innymi słowy, `copyResampled()` pobiera prostokątny region z `$src` o szerokości `$srcW` i wysokości `$srcH` w pozycji (`$srcX`, `$srcY`) i umieszcza go w prostokątnym regionie obrazu o szerokości `$dstW` i wysokości `$dstH` w pozycji (`$dstX`, `$dstY`). +Innymi słowy, `copyResampled()` pobiera prostokątny obszar z `$src` o szerokości `$srcW` i wysokości `$srcH` w pozycji (`$srcX`, `$srcY`) i umieszcza go w prostokątnym obszarze obrazu o szerokości `$dstW` i wysokości `$dstH` w pozycji (`$dstX`, `$dstY`). -Jeśli współrzędne źródła i miejsca docelowego, szerokość i wysokość są różne, fragment obrazu jest odpowiednio rozciągany lub zmniejszany. Współrzędne odnoszą się do lewego górnego rogu. Funkcja ta może być używana do kopiowania obszarów na tym samym obrazie, ale jeśli obszary będą się nakładać, wyniki nie będą przewidywalne. ([więcej |https://www.php.net/manual/en/function.imagecopyresampled]) +Jeśli współrzędne źródłowe i docelowe, szerokość i wysokość różnią się, zostanie wykonane odpowiednie rozciągnięcie lub zmniejszenie fragmentu obrazu. Współrzędne odnoszą się do lewego górnego rogu. Ta funkcja może być używana do kopiowania obszarów w tym samym obrazie, ale jeśli obszary się pokrywają, wyniki nie będą przewidywalne. ([więcej |https://www.php.net/manual/en/function.imagecopyresampled]) copyResized(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] ------------------------------------------------------------------------------------------------------------------------------- -Kopiuje prostokątny fragment jednego obrazu do innego obrazu. Innymi słowy, `copyResized()` pobiera prostokątny region z `$src` o szerokości `$srcW` i wysokości `$srcH` w pozycji (`$srcX`, `$srcY`) i umieszcza go w prostokątnym regionie obrazu o szerokości `$dstW` ] i wysokości `$dstH` w pozycji (`$dstX`, `$dstY`). +Kopiuje prostokątną część jednego obrazu do innego obrazu. Innymi słowy, `copyResized()` pobiera prostokątny obszar z `$src` o szerokości `$srcW` i wysokości `$srcH` w pozycji (`$srcX`, `$srcY`) i umieszcza go w prostokątnym obszarze obrazu o szerokości `$dstW` i wysokości `$dstH` w pozycji (`$dstX`, `$dstY`). -Jeśli współrzędne źródła i miejsca docelowego, szerokość i wysokość są różne, fragment obrazu jest odpowiednio rozciągany lub zmniejszany. Współrzędne odnoszą się do lewego górnego rogu. Funkcja ta może być używana do kopiowania obszarów na tym samym obrazie, ale jeśli obszary będą się nakładać, wyniki nie będą przewidywalne. ([więcej |https://www.php.net/manual/en/function.imagecopyresized]) +Jeśli współrzędne źródłowe i docelowe, szerokość i wysokość różnią się, zostanie wykonane odpowiednie rozciągnięcie lub zmniejszenie fragmentu obrazu. Współrzędne odnoszą się do lewego górnego rogu. Ta funkcja może być używana do kopiowania obszarów w tym samym obrazie, ale jeśli obszary się pokrywają, wyniki nie będą przewidywalne. ([więcej |https://www.php.net/manual/en/function.imagecopyresized]) crop(int|string $left, int|string $top, int|string $width, int|string $height): Image .[method] ----------------------------------------------------------------------------------------------- -Przycina obraz do zadanego prostokątnego obszaru. Wymiary mogą być określone jako liczby całkowite w pikselach lub ciągi znaków w procentach (na przykład `'50%'`). +Przycina obraz do podanego prostokątnego obszaru. Wymiary można podawać jako liczby całkowite w pikselach lub ciągi znaków w procentach (na przykład `'50%'`). -cropAuto(int $mode=-1, float $threshold=.5, int $color=-1): Image .[method] ---------------------------------------------------------------------------- +cropAuto(int $mode=-1, float $threshold=.5, ?ImageColor $color=null): Image .[method] +------------------------------------------------------------------------------------- Automatycznie przycina obraz zgodnie z podanym `$mode`. ([więcej |https://www.php.net/manual/en/function.imagecropauto]) -ellipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------- -Rysuje elipsę wyśrodkowaną na podanych współrzędnych. ([więcej |https://www.php.net/manual/en/function.imageellipse]) +ellipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------- +Rysuje elipsę ze środkiem w podanych współrzędnych. ([więcej |https://www.php.net/manual/en/function.imageellipse]) -fill(int $x, int $y, int $color): void .[method] ------------------------------------------------- -Wypełnia obszar zaczynający się na podanej współrzędnej (lewa górna to 0, 0) podaną `$color`. ([więcej |https://www.php.net/manual/en/function.imagefill]) +fill(int $x, int $y, ImageColor $color): void .[method] +------------------------------------------------------- +Wypełnia obszar zaczynając od podanych współrzędnych (lewy górny róg to 0, 0) podanym kolorem `$color`. ([więcej |https://www.php.net/manual/en/function.imagefill]) -filledArc(int $cx, int $cy, int $w, int $h, int $s, int $e, int $color, int $style): void .[method] ---------------------------------------------------------------------------------------------------- -Rysuje częściowy łuk o środku w podanych współrzędnych. ([więcej |https://www.php.net/manual/en/function.imagefilledarc]) +filledArc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color, int $style): void .[method] +--------------------------------------------------------------------------------------------------------------------------------------------- +Rysuje częściowy łuk ze środkiem w podanych współrzędnych. ([więcej |https://www.php.net/manual/en/function.imagefilledarc]) -filledEllipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------------- -Rysuje elipsę wyśrodkowaną na podanych współrzędnych. ([więcej |https://www.php.net/manual/en/function.imagefilledellipse]) +filledEllipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------------- +Rysuje wypełnioną elipsę ze środkiem w podanych współrzędnych. ([więcej |https://www.php.net/manual/en/function.imagefilledellipse]) -filledPolygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------------- -Tworzy wypełniony wielokąt w obrazie. ([więcej |https://www.php.net/manual/en/function.imagefilledpolygon]) +filledPolygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------------- +Tworzy w obrazie wypełniony wielokąt. ([więcej |https://www.php.net/manual/en/function.imagefilledpolygon]) -filledRectangle(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] -------------------------------------------------------------------------------- -Tworzy prostokąt wypełniony stroną `$color` w obrazie zaczynając od punktu 1 i kończąc w punkcie 2. Punkt 0, 0 jest lewym górnym rogiem obrazu. ([więcej |https://www.php.net/manual/en/function.imagefilledrectangle]) +filledRectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------- +Tworzy prostokąt wypełniony kolorem `$color` w obrazie, zaczynając od punktu `$x1` & `$y1` i kończąc na punkcie `$x2` & `$y2`. Punkt 0, 0 to lewy górny róg obrazu. ([więcej |https://www.php.net/manual/en/function.imagefilledrectangle]) -fillToBorder(int $x, int $y, int $border, int $color): void .[method] ---------------------------------------------------------------------- -Tworzy wypełnienie, którego kolor jest określony przez `$border`. Punktem początkowym wypełnienia jest `$x`, `$y` (lewa górna część to 0, 0), a obszar jest wypełniony kolorem `$color`. ([więcej |https://www.php.net/manual/en/function.imagefilltoborder]) +filledRectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------------- +Tworzy prostokąt wypełniony kolorem `$color` w obrazie, zaczynając od punktu `$left` & `$top` o szerokości `$width` i wysokości `$height`. Punkt 0, 0 to lewy górny róg obrazu. + + +fillToBorder(int $x, int $y, int $border, ImageColor $color): void .[method] +---------------------------------------------------------------------------- +Rysuje wypełnienie, którego kolor krawędzi jest zdefiniowany za pomocą `$border`. Punktem początkowym wypełnienia jest `$x`, `$y` (lewy górny róg to 0, 0), a obszar jest wypełniany kolorem `$color`. ([więcej |https://www.php.net/manual/en/function.imagefilltoborder]) filter(int $filtertype, int ...$args): void .[method] @@ -482,19 +523,19 @@ flip(int $mode): void .[method] Odwraca obraz za pomocą podanego `$mode`. ([więcej |https://www.php.net/manual/en/function.imageflip]) -ftText(int $size, int $angle, int $x, int $y, int $col, string $fontFile, string $text, array $extrainfo=null): array .[method] -------------------------------------------------------------------------------------------------------------------------------- -Napisz tekst w obrazie za pomocą czcionek z FreeType 2. ([więcej |https://www.php.net/manual/en/function.imagefttext]) +ftText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +---------------------------------------------------------------------------------------------------------------------------------------- +Wpisuje tekst do obrazu. ([więcej |https://www.php.net/manual/en/function.imagefttext]) gammaCorrect(float $inputgamma, float $outputgamma): void .[method] ------------------------------------------------------------------- -Zastosuj korekcję gamma do obrazu względem gamma wejściowego i wyjściowego. ([więcej |https://www.php.net/manual/en/function.imagegammacorrect]) +Stosuje korekcję gamma do obrazu względem wejściowej i wyjściowej wartości gamma. ([więcej |https://www.php.net/manual/en/function.imagegammacorrect]) getClip(): array .[method] -------------------------- -Zwraca aktualny crop, czyli obszar, poza którym nie będą rysowane piksele. ([więcej |https://www.php.net/manual/en/function.imagegetclip]) +Zwraca bieżący obszar przycinania, tj. obszar, poza którym żadne piksele nie będą rysowane. ([więcej |https://www.php.net/manual/en/function.imagegetclip]) getHeight(): int .[method] @@ -512,29 +553,29 @@ getWidth(): int .[method] Zwraca szerokość obrazu. -interlace(int $interlace=null): int .[method] ---------------------------------------------- -Włącza lub wyłącza tryb przeplotu. Jeśli ustawiony jest tryb z przeplotem, a obraz jest zapisywany jako JPEG, zostanie on zapisany jako JPEG progresywny. ([więcej |https://www.php.net/manual/en/function.imageinterlace]) +interlace(?int $interlace=null): int .[method] +---------------------------------------------- +Włącza lub wyłącza tryb przeplotu. Jeśli tryb przeplotu jest ustawiony, a obraz jest zapisywany jako JPEG, zostanie zapisany jako progresywny JPEG. ([więcej |https://www.php.net/manual/en/function.imageinterlace]) isTrueColor(): bool .[method] ----------------------------- -Określ, czy obraz jest truecolor ([więcej |https://www.php.net/manual/en/function.imageistruecolor]). +Sprawdza, czy obraz jest typu truecolor. ([więcej |https://www.php.net/manual/en/function.imageistruecolor]) layerEffect(int $effect): void .[method] ---------------------------------------- -Ustaw flagę alfa blend, aby użyć efektów warstwowych. ([więcej |https://www.php.net/manual/en/function.imagelayereffect]) +Ustawia flagę mieszania alfa, aby używać efektów warstw. ([więcej |https://www.php.net/manual/en/function.imagelayereffect]) -line(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] --------------------------------------------------------------------- -Rysuje linię pomiędzy dwoma podanymi punktami. ([więcej |https://www.php.net/manual/en/function.imageline]) +line(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +--------------------------------------------------------------------------- +Rysuje linię między dwoma podanymi punktami. ([więcej |https://www.php.net/manual/en/function.imageline]) -openPolygon(array $points, int $numPoints, int $color): void .[method] ----------------------------------------------------------------------- -Rysuje na obrazie otwarty wielokąt. W odróżnieniu od `polygon()`, między ostatnim a pierwszym punktem nie rysuje się linii. ([więcej |https://www.php.net/manual/en/function.imageopenpolygon]) +openPolygon(array $points, ImageColor $color): void .[method] +------------------------------------------------------------- +Rysuje na obrazie otwarty wielokąt. W przeciwieństwie do `polygon()`, między ostatnim a pierwszym punktem nie jest rysowana żadna linia. ([więcej |https://www.php.net/manual/en/function.imageopenpolygon]) paletteCopy(Image $source): void .[method] @@ -544,78 +585,83 @@ Kopiuje paletę z `$source` do obrazu. ([więcej |https://www.php.net/manual/en/ paletteToTrueColor(): void .[method] ------------------------------------ -Konwertuje obraz oparty na paletach na obraz w pełnym kolorze. ([więcej |https://www.php.net/manual/en/function.imagepalettetotruecolor]) +Konwertuje obraz oparty na palecie na obraz pełnokolorowy (truecolor). ([więcej |https://www.php.net/manual/en/function.imagepalettetotruecolor]) place(Image $image, int|string $left=0, int|string $top=0, int $opacity=100): Image .[method] --------------------------------------------------------------------------------------------- -Kopiuje stronę `$image` do obrazu na współrzędnych `$left` i `$top`. Współrzędne mogą być określone jako liczby całkowite w pikselach lub łańcuchy w procentach (na przykład `'50%'`). +Kopiuje `$image` do obrazu na współrzędne `$left` i `$top`. Współrzędne można podawać jako liczby całkowite w pikselach lub ciągi znaków w procentach (na przykład `'50%'`). -polygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------- -Tworzy wielokąt w obrazie. ([więcej |https://www.php.net/manual/en/function.imagepolygon]) +polygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------- +Tworzy w obrazie wielokąt. ([więcej |https://www.php.net/manual/en/function.imagepolygon]) -rectangle(int $x1, int $y1, int $x2, int $y2, int $col): void .[method] ------------------------------------------------------------------------ -Tworzy prostokąt na podanych współrzędnych ([więcej |https://www.php.net/manual/en/function.imagerectangle]). +rectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------- +Tworzy prostokąt o podanych współrzędnych. ([więcej |https://www.php.net/manual/en/function.imagerectangle]) + + +rectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------- +Tworzy prostokąt o podanych współrzędnych. resize(int|string $width, int|string $height, int $flags=Image::OrSmaller): Image .[method] ------------------------------------------------------------------------------------------- -Zmienia rozmiar obrazu, [więcej informacji |#Image-Resize]. Wymiary mogą być określone jako liczby całkowite w pikselach lub ciągi znaków w procentach (na przykład `'50%'`). +Zmienia rozmiar obrazu, [więcej informacji |#Zmiana rozmiaru]. Wymiary można podawać jako liczby całkowite w pikselach lub ciągi znaków w procentach (na przykład `'50%'`). -resolution(int $resX=null, int $resY=null): mixed .[method] ------------------------------------------------------------ -Ustawia lub zwraca rozdzielczość obrazu w DPI (punkty na cal). Jeśli żaden z opcjonalnych parametrów nie zostanie określony, bieżąca rozdzielczość jest zwracana jako indeksowane pole. Jeśli określona jest tylko `$resX`, rozdzielczość pozioma i pionowa są ustawione na tę wartość. Jeśli oba opcjonalne parametry są określone, rozdzielczości pozioma i pionowa są ustawione na te wartości. +resolution(?int $resX=null, ?int $resY=null): mixed .[method] +------------------------------------------------------------- +Ustawia lub zwraca rozdzielczość obrazu w DPI (punkty na cal). Jeśli żaden z opcjonalnych parametrów nie jest podany, bieżąca rozdzielczość jest zwracana jako tablica indeksowana. Jeśli podano tylko `$resX`, rozdzielczość pozioma i pionowa są ustawiane na tę wartość. Jeśli podano oba opcjonalne parametry, rozdzielczość pozioma i pionowa są ustawiane na te wartości. -Rozdzielczość jest używana jako meta informacja tylko wtedy, gdy obrazy są odczytywane i zapisywane w formatach obsługujących ten rodzaj informacji (obecnie PNG i JPEG). Nie ma to wpływu na żadne operacje rysunkowe. Domyślna rozdzielczość nowych zdjęć to 96 DPI. ([więcej |https://www.php.net/manual/en/function.imageresolution]) +Rozdzielczość jest używana tylko jako meta informacja, gdy obrazy są odczytywane i zapisywane w formatach obsługujących ten rodzaj informacji (obecnie PNG i JPEG). Nie ma to wpływu na żadne operacje rysowania. Domyślna rozdzielczość nowych obrazów to 96 DPI. ([więcej |https://www.php.net/manual/en/function.imageresolution]) rotate(float $angle, int $backgroundColor): Image .[method] ----------------------------------------------------------- -Obraca obraz przy użyciu określonej `$angle` w stopniach. Środek obrotu jest środkiem obrazu, a obrócony obraz może mieć inne wymiary niż obraz oryginalny. ([więcej |https://www.php.net/manual/en/function.imagerotate]) +Obraca obraz o podany kąt `$angle` w stopniach. Środek obrotu to środek obrazu, a obrócony obraz może mieć inne wymiary niż obraz oryginalny. ([więcej |https://www.php.net/manual/en/function.imagerotate]) .[note] -Wymaga obecności rozszerzenia *Bundled GD*, więc może nie działać wszędzie. +Wymaga obecności *Bundled GD extension*, więc może nie działać wszędzie. -save(string $file, int $quality=null, int $type=null): void .[method] ---------------------------------------------------------------------- +save(string $file, ?int $quality=null, ?int $type=null): void .[method] +----------------------------------------------------------------------- Zapisuje obraz do pliku. -Jakość kompresji jest w zakresie 0..100 dla JPEG (domyślnie 85), WEBP (domyślnie 80) i AVIF (domyślnie 30) oraz 0..9 dla PNG (domyślnie 9). Jeśli typ nie jest oczywisty na podstawie rozszerzenia pliku, można go określić za pomocą jednej ze stałych `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF`, oraz `Image::BMP`. +Jakość kompresji jest w zakresie 0..100 dla JPEG (domyślnie 85), WEBP (domyślnie 80) i AVIF (domyślnie 30) oraz 0..9 dla PNG (domyślnie 9). Jeśli typ nie jest oczywisty z rozszerzenia pliku, można go określić za pomocą jednej ze stałych `ImageType`. saveAlpha(bool $saveflag): void .[method] ----------------------------------------- -Ustawia flagę, aby zachować pełną informację o kanale alfa (w przeciwieństwie do przezroczystości monochromatycznej) podczas zapisywania obrazów PNG. +Ustawia flagę określającą, czy podczas zapisywania obrazów PNG zachować pełne informacje o kanale alfa (w przeciwieństwie do przezroczystości jednokolorowej). -Alfablending musi być wyłączony (`alphaBlending(false)`), aby zachować kanał alfa w pierwszej kolejności. ([więcej |https://www.php.net/manual/en/function.imagesavealpha]) +Mieszanie alfa musi być wyłączone (`alphaBlending(false)`), aby kanał alfa został zachowany w pierwszej kolejności. ([więcej |https://www.php.net/manual/en/function.imagesavealpha]) scale(int $newWidth, int $newHeight=-1, int $mode=IMG_BILINEAR_FIXED): Image .[method] -------------------------------------------------------------------------------------- -Skalowanie obrazu z wykorzystaniem podanego algorytmu interpolacji. ([więcej |https://www.php.net/manual/en/function.imagescale]) +Skaluje obraz przy użyciu podanego algorytmu interpolacji. ([więcej |https://www.php.net/manual/en/function.imagescale]) -send(int $type=Image::JPEG, int $quality=null): void .[method] --------------------------------------------------------------- -Drukuje obraz do przeglądarki. +send(int $type=ImageType::JPEG, ?int $quality=null): void .[method] +------------------------------------------------------------------- +Wysyła obraz do przeglądarki. -Jakość kompresji jest w zakresie 0..100 dla JPEG (domyślnie 85), WEBP (domyślnie 80) i AVIF (domyślnie 30) oraz 0..9 dla PNG (domyślnie 9). Typ jest jedną ze stałych `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` oraz `Image::BMP`. +Jakość kompresji jest w zakresie 0..100 dla JPEG (domyślnie 85), WEBP (domyślnie 80) i AVIF (domyślnie 30) oraz 0..9 dla PNG (domyślnie 9). setBrush(Image $brush): void .[method] -------------------------------------- -Ustawia obraz pędzla, który ma być używany we wszystkich funkcjach rysowania linii (na przykład `line()` i `polygon()`) podczas rysowania z użyciem specjalnych kolorów IMG_COLOR_BRUSHED lub IMG_COLOR_STYLEDBRUSHED. ([więcej |https://www.php.net/manual/en/function.imagesetbrush]) +Ustawia obraz pędzla, który będzie używany we wszystkich funkcjach rysowania linii (np. `line()` i `polygon()`) podczas rysowania specjalnymi kolorami IMG_COLOR_BRUSHED lub IMG_COLOR_STYLEDBRUSHED. ([więcej |https://www.php.net/manual/en/function.imagesetbrush]) setClip(int $x1, int $y1, int $x2, int $y2): void .[method] ----------------------------------------------------------- -Ustawia aktualny crop, czyli obszar poza którym nie będą rysowane piksele. ([więcej |https://www.php.net/manual/en/function.imagesetclip]) +Ustawia bieżący obszar przycinania, tj. obszar, poza którym żadne piksele nie będą rysowane. ([więcej |https://www.php.net/manual/en/function.imagesetclip]) setInterpolation(int $method=IMG_BILINEAR_FIXED): void .[method] @@ -623,26 +669,26 @@ setInterpolation(int $method=IMG_BILINEAR_FIXED): void .[method] Ustawia metodę interpolacji, która wpływa na metody `rotate()` i `affine()`. ([więcej |https://www.php.net/manual/en/function.imagesetinterpolation]) -setPixel(int $x, int $y, int $color): void .[method] ----------------------------------------------------- -Rysuje piksel na określonej współrzędnej. ([więcej |https://www.php.net/manual/en/function.imagesetpixel]) +setPixel(int $x, int $y, ImageColor $color): void .[method] +----------------------------------------------------------- +Rysuje piksel na podanych współrzędnych. ([więcej |https://www.php.net/manual/en/function.imagesetpixel]) setStyle(array $style): void .[method] -------------------------------------- -Ustawia styl, który ma być używany przez wszystkie funkcje rysowania linii (na przykład `line()` i `polygon()`) podczas rysowania z kolorem specjalnym IMG_COLOR_STYLED lub linii obrazu z kolorem IMG_COLOR_STYLEDBRUSHED. ([więcej |https://www.php.net/manual/en/function.imagesetstyle]) +Ustawia styl, który ma być używany przez wszystkie funkcje rysowania linii (np. `line()` i `polygon()`) podczas rysowania specjalnym kolorem IMG_COLOR_STYLED lub liniami obrazów kolorem IMG_COLOR_STYLEDBRUSHED. ([więcej |https://www.php.net/manual/en/function.imagesetstyle]) setThickness(int $thickness): void .[method] -------------------------------------------- -Ustawia grubość linii podczas rysowania prostokątów, wielokątów, łuków itp. Na stronie `$thickness` pikseli ([więcej |https://www.php.net/manual/en/function.imagesetthickness]) +Ustawia grubość linii podczas rysowania prostokątów, wielokątów, łuków itp. na `$thickness` pikseli. ([więcej |https://www.php.net/manual/en/function.imagesetthickness]) setTile(Image $tile): void .[method] ------------------------------------ -Ustawia obraz kafelka, który będzie używany we wszystkich funkcjach wypełniania regionów (na przykład `fill()` i `filledPolygon()`), gdy wypełniony jest specjalnym kolorem IMG_COLOR_TILED. +Ustawia obraz kafelka, który będzie używany we wszystkich funkcjach wypełniania regionów (np. `fill()` i `filledPolygon()`) podczas wypełniania specjalnym kolorem IMG_COLOR_TILED. -Kafelek jest obrazem używanym do wypełnienia regionu powtarzającym się wzorem. Jako kafelek można użyć dowolnego obrazu, a ustawiając indeks przezroczystego koloru obrazu kafelka za pomocą strony `colorTransparent()`, można stworzyć kafelek, w którym pewne części bazowego regionu będą prześwitywać. ([więcej |https://www.php.net/manual/en/function.imagesettile]) +Kafelek to obraz używany do wypełniania obszaru powtarzającym się wzorem. Dowolny obraz może być użyty jako kafelek, a ustawienie przezroczystego indeksu koloru obrazu kafelka za pomocą `colorTransparent()` pozwala utworzyć kafelek, w którym pewne części podległego obszaru będą prześwitywać. ([więcej |https://www.php.net/manual/en/function.imagesettile]) sharpen(): Image .[method] @@ -650,31 +696,21 @@ sharpen(): Image .[method] Wyostrza obraz. .[note] -Wymaga obecności rozszerzenia *Bundled GD*, więc może nie działać wszędzie. - - -string(int $font, int $x, int $y, string $str, int $col): void .[method] ------------------------------------------------------------------------- -Wypisuje ciąg znaków na podane współrzędne. ([więcej |https://www.php.net/manual/en/function.imagestring]) - +Wymaga obecności *Bundled GD extension*, więc może nie działać wszędzie. -stringUp(int $font, int $x, int $y, string $s, int $col): void .[method] ------------------------------------------------------------------------- -Wypisuje ciąg znaków w pionie na podane współrzędne. ([więcej |https://www.php.net/manual/en/function.imagestringup]) +toString(int $type=ImageType::JPEG, ?int $quality=null): string .[method] +------------------------------------------------------------------------- +Zapisuje obraz do ciągu znaków. -toString(int $type=Image::JPEG, int $quality=null): string .[method] --------------------------------------------------------------------- -Zapisuje obraz w postaci ciągu znaków. - -Jakość kompresji jest w zakresie 0..100 dla JPEG (domyślnie 85), WEBP (domyślnie 80) i AVIF (domyślnie 30) oraz 0..9 dla PNG (domyślnie 9). Typ jest jedną ze stałych `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` oraz `Image::BMP`. +Jakość kompresji jest w zakresie 0..100 dla JPEG (domyślnie 85), WEBP (domyślnie 80) i AVIF (domyślnie 30) oraz 0..9 dla PNG (domyślnie 9). trueColorToPalette(bool $dither, int $ncolors): void .[method] -------------------------------------------------------------- -Konwertuje obraz truecolor na obraz palety. ([więcej |https://www.php.net/manual/en/function.imagetruecolortopalette]) +Konwertuje obraz truecolor na obraz paletowy. ([więcej |https://www.php.net/manual/en/function.imagetruecolortopalette]) -ttfText(int $size, int $angle, int $x, int $y, int $color, string $fontfile, string $text): array .[method] ------------------------------------------------------------------------------------------------------------ -Drukuje podany tekst do obrazu przy użyciu czcionek TrueType. ([więcej |https://www.php.net/manual/en/function.imagettftext]) +ttfText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +----------------------------------------------------------------------------------------------------------------------------------------- +Wypisuje podany tekst do obrazu. ([więcej |https://www.php.net/manual/en/function.imagettftext]) diff --git a/utils/pl/iterables.texy b/utils/pl/iterables.texy new file mode 100644 index 0000000000..8ff6e9ed35 --- /dev/null +++ b/utils/pl/iterables.texy @@ -0,0 +1,170 @@ +Praca z iteratorami +******************* + +.[perex]{data-version:4.0.4} +[api:Nette\Utils\Iterables] to klasa statyczna z funkcjami do pracy z iteratorami. Jej odpowiednikiem dla tablic jest [Nette\Utils\Arrays|arrays]. + + +Instalacja: + +```shell +composer require nette/utils +``` + +Wszystkie przykłady zakładają, że został utworzony alias: + +```php +use Nette\Utils\Iterables; +``` + + +contains(iterable $iterable, $value): bool .[method] +---------------------------------------------------- + +Szuka podanej wartości w iteratorze. Używa ścisłego porównania (`===`) do sprawdzenia zgodności. Zwraca `true`, jeśli wartość zostanie znaleziona, w przeciwnym razie `false`. + +```php +Iterables::contains(new ArrayIterator([1, 2, 3]), 1); // true +Iterables::contains(new ArrayIterator([1, 2, 3]), '1'); // false +``` + +Ta metoda jest przydatna, gdy potrzebujesz szybko sprawdzić, czy określona wartość znajduje się w iteratorze, bez konieczności ręcznego przeglądania wszystkich elementów. + + +containsKey(iterable $iterable, $key): bool .[method] +----------------------------------------------------- + +Szuka podanego klucza w iteratorze. Używa ścisłego porównania (`===`) do sprawdzenia zgodności. Zwraca `true`, jeśli klucz zostanie znaleziony, w przeciwnym razie `false`. + +```php +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 0); // true +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 4); // false +``` + + +every(iterable $iterable, callable $predicate): bool .[method] +-------------------------------------------------------------- + +Sprawdza, czy wszystkie elementy iteratora spełniają warunek zdefiniowany w `$predicate`. Funkcja `$predicate` ma sygnaturę `function ($value, $key, iterable $iterable): bool` i musi zwracać `true` dla każdego elementu, aby metoda `every()` zwróciła `true`. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isBelowThreshold = fn($value) => $value < 40; +$res = Iterables::every($iterator, $isBelowThreshold); // true +``` + +Ta metoda jest przydatna do sprawdzenia, czy wszystkie elementy w kolekcji spełniają określony warunek, na przykład czy wszystkie liczby są mniejsze od określonej wartości. + + +filter(iterable $iterable, callable $predicate): Generator .[method] +-------------------------------------------------------------------- + +Tworzy nowy iterator, który zawiera tylko te elementy z oryginalnego iteratora, które spełniają warunek zdefiniowany w `$predicate`. Funkcja `$predicate` ma sygnaturę `function ($value, $key, iterable $iterable): bool` i musi zwracać `true` dla elementów, które mają być zachowane. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::filter($iterator, fn($v) => $v < 3); +// 1, 2 +``` + +Metoda wykorzystuje generator, co oznacza, że filtrowanie odbywa się stopniowo podczas przeglądania wyniku. Jest to efektywne pod względem pamięci i pozwala przetwarzać nawet bardzo duże kolekcje. Jeśli nie przejdziesz wszystkich elementów wynikowego iteratora, oszczędzisz moc obliczeniową, ponieważ nie przetworzą się wszystkie elementy oryginalnego iteratora. + + +first(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------------- + +Zwraca pierwszy element iteratora. Jeśli podano `$predicate`, zwraca pierwszy element, który spełnia podany warunek. Funkcja `$predicate` ma sygnaturę `function ($value, $key, iterable $iterable): bool`. Jeśli nie znaleziono żadnego pasującego elementu, wywoływana jest funkcja `$else` (jeśli jest podana) i zwracany jest jej wynik. Jeśli `$else` nie jest podane, zwracane jest `null`. + +```php +Iterables::first(new ArrayIterator([1, 2, 3])); // 1 +Iterables::first(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 3 +Iterables::first(new ArrayIterator([])); // null +Iterables::first(new ArrayIterator([]), else: fn() => false); // false +``` + +Ta metoda jest przydatna, gdy potrzebujesz szybko uzyskać pierwszy element kolekcji lub pierwszy element spełniający określony warunek, bez konieczności ręcznego przeglądania całej kolekcji. + + +firstKey(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +---------------------------------------------------------------------------------------------- + +Zwraca klucz pierwszego elementu iteratora. Jeśli podano `$predicate`, zwraca klucz pierwszego elementu, który spełnia podany warunek. Funkcja `$predicate` ma sygnaturę `function ($value, $key, iterable $iterable): bool`. Jeśli nie znaleziono żadnego pasującego elementu, wywoływana jest funkcja `$else` (jeśli jest podana) i zwracany jest jej wynik. Jeśli `$else` nie jest podane, zwracane jest `null`. + +```php +Iterables::firstKey(new ArrayIterator([1, 2, 3])); // 0 +Iterables::firstKey(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 2 +Iterables::firstKey(new ArrayIterator(['a' => 1, 'b' => 2])); // 'a' +Iterables::firstKey(new ArrayIterator([])); // null +``` + + +map(iterable $iterable, callable $transformer): Generator .[method] +------------------------------------------------------------------- + +Tworzy nowy iterator, stosując funkcję `$transformer` do każdego elementu oryginalnego iteratora. Funkcja `$transformer` ma sygnaturę `function ($value, $key, iterable $iterable): mixed` i jej wartość zwracana jest używana jako nowa wartość elementu. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::map($iterator, fn($v) => $v * 2); +// 2, 4, 6 +``` + +Metoda wykorzystuje generator, co oznacza, że transformacja odbywa się stopniowo podczas przeglądania wyniku. Jest to efektywne pod względem pamięci i pozwala przetwarzać nawet bardzo duże kolekcje. Jeśli nie przejdziesz wszystkich elementów wynikowego iteratora, oszczędzisz moc obliczeniową, ponieważ nie przetworzą się wszystkie elementy oryginalnego iteratora. + + +mapWithKeys(iterable $iterable, callable $transformer): Generator .[method] +--------------------------------------------------------------------------- + +Tworzy nowy iterator, transformując wartości i klucze oryginalnego iteratora. Funkcja `$transformer` ma sygnaturę `function ($value, $key, iterable $iterable): ?array{$newKey, $newValue}`. Jeśli `$transformer` zwróci `null`, element jest pomijany. Dla zachowanych elementów pierwszy element zwróconej tablicy jest używany jako nowy klucz, a drugi element jako nowa wartość. + +```php +$iterator = new ArrayIterator(['a' => 1, 'b' => 2]); +$iterator = Iterables::mapWithKeys($iterator, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +Podobnie jak `map()`, ta metoda wykorzystuje generator do stopniowego przetwarzania i efektywnej pracy z pamięcią. Pozwala to pracować z dużymi kolekcjami i oszczędzać moc obliczeniową przy częściowym przejściu przez wynik. + + +memoize(iterable $iterable): IteratorAggregate .[method] +-------------------------------------------------------- + +Tworzy opakowanie wokół iteratora, które podczas iteracji zapisuje w pamięci podręcznej jego klucze i wartości. Pozwala to na wielokrotną iterację danych bez konieczności ponownego przechodzenia przez oryginalne źródło danych. + +```php +$iterator = /* dane, których nie można iterować wielokrotnie */ +$memoized = Iterables::memoize($iterator); +// Teraz możesz iterować $memoized wielokrotnie bez utraty danych +``` + +Ta metoda jest przydatna w sytuacjach, gdy potrzebujesz wielokrotnie przejść przez ten sam zestaw danych, ale oryginalny iterator nie pozwala na wielokrotną iterację lub ponowne przejście byłoby kosztowne (np. przy odczycie danych z bazy danych lub pliku). + + +some(iterable $iterable, callable $predicate): bool .[method] +------------------------------------------------------------- + +Sprawdza, czy przynajmniej jeden element iteratora spełnia warunek zdefiniowany w `$predicate`. Funkcja `$predicate` ma sygnaturę `function ($value, $key, iterable $iterable): bool` i musi zwracać `true` dla przynajmniej jednego elementu, aby metoda `some()` zwróciła `true`. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isEven = fn($value) => $value % 2 === 0; +$res = Iterables::some($iterator, $isEven); // true +``` + +Ta metoda jest przydatna do szybkiego sprawdzenia, czy w kolekcji istnieje przynajmniej jeden element spełniający określony warunek, na przykład czy kolekcja zawiera przynajmniej jedną liczbę parzystą. + +Zobacz [#every()]. + + +toIterator(iterable $iterable): Iterator .[method] +-------------------------------------------------- + +Konwertuje dowolny obiekt iterowalny (array, Traversable) na Iterator. Jeśli wejście jest już Iteratorem, zwraca go bez zmian. + +```php +$array = [1, 2, 3]; +$iterator = Iterables::toIterator($array); +// Teraz masz Iterator zamiast tablicy +``` + +Ta metoda jest przydatna, gdy potrzebujesz zapewnić, że masz do dyspozycji Iterator, niezależnie od typu danych wejściowych. Może to być przydatne przy tworzeniu funkcji, które pracują z różnymi typami danych iterowalnych. diff --git a/utils/pl/json.texy b/utils/pl/json.texy index 379ea35dbb..76ca13d972 100644 --- a/utils/pl/json.texy +++ b/utils/pl/json.texy @@ -2,7 +2,7 @@ Praca z JSON ************ .[perex] -[api:Nette\Utils\Json] jest klasą statyczną zawierającą funkcje do kodowania i dekodowania formatu JSON. Obsługuje luki w różnych wersjach PHP i rzuca wyjątki w przypadku wystąpienia błędów. +[api:Nette\Utils\Json] to klasa statyczna z funkcjami do kodowania i dekodowania formatu JSON. Obsługuje podatności różnych wersji PHP i rzuca wyjątki w przypadku błędów. Instalacja: @@ -11,44 +11,44 @@ Instalacja: composer require nette/utils ``` -Wszystkie przykłady zakładają, że alias został utworzony: +Wszystkie przykłady zakładają, że został utworzony alias: ```php use Nette\Utils\Json; ``` -Korzystanie z .[#toc-usage] -=========================== +Użycie +====== encode(mixed $value, bool $pretty=false, bool $asciiSafe=false, bool $htmlSafe=false, bool $forceObjects=false): string .[method] --------------------------------------------------------------------------------------------------------------------------------- -Konwertuje stronę `$value` na format JSON. +Konwertuje `$value` do formatu JSON. -Po ustawieniu na `$pretty`, formatuje JSON dla łatwiejszego czytania i przejrzystości: +Przy ustawieniu `$pretty` na `true` sformatuje JSON dla łatwiejszego czytania i przejrzystości: ```php Json::encode($value); // zwraca JSON Json::encode($value, pretty: true); // zwraca bardziej przejrzysty JSON ``` -Na stronie `$asciiSafe` generuje wyjście w ASCII, tzn. zastępuje znaki unicode ciągiem `\uxxxx`: +Przy `$asciiSafe` na `true` wygeneruje wyjście w ASCII, tj. znaki unicode zastąpi sekwencjami `\uxxxx`: ```php Json::encode('žluťoučký', asciiSafe: true); // '"\u017elu\u0165ou\u010dk\u00fd"' ``` -Parametr `$htmlSafe` zapewnia, że wyjście nie zawiera znaków, które mają specjalne znaczenie w HTML: +Parametr `$htmlSafe` na `true` zapewni, że wyjście nie będzie zawierać znaków mających specjalne znaczenie w HTML: ```php Json::encode('onesendJson($data)`, którą można wywołać w metodzie `action*()`, na przykład zobacz [Wysyłanie odpowiedzi |application:presenters#Sending-a-Response]. +Można do tego użyć metody `$this->sendJson($data)`, którą możemy wywołać na przykład w metodzie `action*()`, zobacz [Wysyłanie odpowiedzi |application:presenters#Wysłanie odpowiedzi]. diff --git a/utils/pl/paginator.texy b/utils/pl/paginator.texy index 2b7f76b8e1..033643d283 100644 --- a/utils/pl/paginator.texy +++ b/utils/pl/paginator.texy @@ -2,7 +2,7 @@ Paginator ********* .[perex] -Potrzebujesz paginować zrzut danych? Ponieważ matematyka paginacji może być skomplikowana, [api:Nette\Utils\Paginator] może ci w tym pomóc. +Potrzebujesz stronicować listę danych? Ponieważ matematyka stronicowania bywa podstępna, pomoże ci w tym [api:Nette\Utils\Paginator]. Instalacja: @@ -11,38 +11,38 @@ Instalacja: composer require nette/utils ``` -Utwórz obiekt stronicowania i ustaw jego podstawowe informacje: +Tworzymy obiekt paginatora i ustawiamy mu podstawowe informacje: ```php $paginator = new Nette\Utils\Paginator; -$paginator->setPage(1); // aktualny numer strony -$paginator->setItemsPerPage(30); // ilość elementów na stronie +$paginator->setPage(1); // numer bieżącej strony +$paginator->setItemsPerPage(30); // liczba elementów na stronie $paginator->setItemCount(356); // całkowita liczba elementów, jeśli jest znana ``` -Strony są numerowane od 1. Możemy to zmienić używając `setBase()`: +Strony są numerowane od 1. Możemy to zmienić za pomocą `setBase()`: ```php -$paginator->setBase(0); // numeracja od 0 +$paginator->setBase(0); // numerujemy od 0 ``` -Obiekt będzie teraz dostarczał wszystkich podstawowych informacji przydatnych podczas tworzenia strony stronicowania. Na przykład możesz przekazać go do szablonu i użyć go tam. +Obiekt teraz dostarczy wszystkich podstawowych informacji przydatnych przy tworzeniu paginatora. Możesz go na przykład przekazać do szablonu i tam go wykorzystać. ```php -$paginator->isFirst(); // czy jesteśmy na pierwszej stronie? -$paginator->isLast(); // czy jesteśmy na ostatniej stronie? -$paginator->getPage(); // aktualny numer strony +$paginator->isFirst(); // jesteśmy na pierwszej stronie? +$paginator->isLast(); // jesteśmy na ostatniej stronie? +$paginator->getPage(); // numer bieżącej strony $paginator->getFirstPage(); // numer pierwszej strony $paginator->getLastPage(); // numer ostatniej strony -$paginator->getFirstItemOnPage(); // numer kolejny pierwszego elementu na stronie +$paginator->getFirstItemOnPage(); // numer porządkowy pierwszego elementu na stronie $paginator->getLastItemOnPage(); // numer porządkowy ostatniego elementu na stronie $paginator->getPageIndex(); // numer bieżącej strony numerowany od 0 $paginator->getPageCount(); // całkowita liczba stron -$paginator->getItemsPerPage(); // ilość elementów na stronie +$paginator->getItemsPerPage(); // liczba elementów na stronie $paginator->getItemCount(); // całkowita liczba elementów, jeśli jest znana ``` -Konstruktor stron pomoże w formułowaniu zapytań SQL. Metody `getLength()` i `getOffset()` zwracają wartości do wykorzystania w klauzulach LIMIT i OFFSET: +Paginator pomoże przy formułowaniu zapytania SQL. Metody `getLength()` i `getOffset()` zwracają wartości, które użyjemy w klauzulach LIMIT i OFFSET: ```php $result = $database->query( @@ -52,7 +52,7 @@ $result = $database->query( ); ``` -Jeśli potrzebujemy paginować w odwrotnej kolejności, tzn. strona 1 odpowiada najwyższemu offsetowi, używamy `getCountdownOffset()`: +Jeśli potrzebujemy stronicować w odwrotnej kolejności, tj. strona nr 1 odpowiada najwyższemu offsetowi, użyjemy `getCountdownOffset()`: ```php $result = $database->query( @@ -62,4 +62,4 @@ $result = $database->query( ); ``` -Zobacz [Database Results Pagination |best-practices:pagination] Cookbook, aby zobaczyć przykład, jak użyć tego w aplikacji. +Przykład użycia w aplikacji znajdziesz w książce kucharskiej [Stronicowanie wyników bazy danych |best-practices:pagination]. diff --git a/utils/pl/random.texy b/utils/pl/random.texy index d3603f34b0..fe64ab0fed 100644 --- a/utils/pl/random.texy +++ b/utils/pl/random.texy @@ -2,7 +2,7 @@ Generowanie losowych ciągów znaków ********************************** .[perex] -[api:Nette\Utils\Random] jest klasą statyczną służącą do generowania kryptograficznie bezpiecznych ciągów pseudolosowych. +[api:Nette\Utils\Random] to klasa statyczna do generowania kryptograficznie bezpiecznych pseudolosowych ciągów znaków. Instalacja: @@ -15,7 +15,7 @@ composer require nette/utils generate(int $length=10, string $charlist=`'0-9a-z'`): string .[method] ----------------------------------------------------------------------- -Generuje losowy ciąg o zadanej długości ze znaków określonych przez `$charlist`. Można również użyć interwałów zapisanych jako np. `0-9`. +Wygeneruje losowy ciąg znaków o podanej długości ze znaków określonych parametrem `$charlist`. Można również używać interwałów zapisanych jako na przykład `0-9`. ```php use Nette\Utils\Random; diff --git a/utils/pl/reflection.texy b/utils/pl/reflection.texy index cccf9aa058..cb8103a2a5 100644 --- a/utils/pl/reflection.texy +++ b/utils/pl/reflection.texy @@ -1,8 +1,8 @@ -Refleksja w PHP -*************** +Refleksja PHP +************* .[perex] -[api:Nette\Utils\Reflection] jest klasą statyczną zawierającą przydatne funkcje do refleksji w PHP. Jego celem jest naprawienie niedociągnięć klas natywnych i ujednolicenie zachowania w różnych wersjach PHP. +[api:Nette\Utils\Reflection] to klasa statyczna z przydatnymi funkcjami do refleksji PHP. Jej zadaniem jest naprawianie niedociągnięć natywnych klas i ujednolicanie zachowania w różnych wersjach PHP. Instalacja: @@ -11,7 +11,7 @@ Instalacja: composer require nette/utils ``` -Wszystkie przykłady zakładają, że alias został utworzony: +Wszystkie przykłady zakładają, że został utworzony alias: ```php use Nette\Utils\Reflection; @@ -21,13 +21,13 @@ use Nette\Utils\Reflection; areCommentsAvailable(): bool .[method] -------------------------------------- -Określenie, czy refleksja ma dostęp do komentarzy PHPdoc. Komentarze mogą być niedostępne z powodu pamięci podręcznej opcode, zobacz na przykład dyrektywę [opcache.save-comments |https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments]. +Sprawdza, czy refleksja ma dostęp do komentarzy PHPdoc. Komentarze mogą być niedostępne z powodu pamięci podręcznej opcode, zobacz na przykład dyrektywę [opcache.save-comments|https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments]. expandClassName(string $name, ReflectionClass $context): string .[method] ------------------------------------------------------------------------- -Rozwija nazwę klasy `$name` do jej pełnej nazwy w kontekście klasy `$context`, czyli w kontekście jej przestrzeni nazw i zdefiniowanych aliasów. Tak więc w rzeczywistości mówi, jak parser PHP `$name` zrozumiałby , gdyby był napisany w ciele klasy `$context`. +Rozwija nazwę klasy `$name` do jej pełnej nazwy w kontekście klasy `$context`, czyli w kontekście jej przestrzeni nazw i zdefiniowanych aliasów. Czyli właściwie mówi, jak parser PHP zrozumiałby `$name`, gdyby był zapisany w ciele klasy `$context`. ```php namespace Foo; @@ -47,9 +47,9 @@ Reflection::expandClassName('Baz', $context); // 'Foo\Baz' getMethodDeclaringMethod(ReflectionMethod $method): ReflectionMethod .[method] ------------------------------------------------------------------------------ -Zwraca odbicie metody, które zawiera deklarację metody `$method`. Zazwyczaj każda metoda jest swoją własną deklaracją, ale ciało metody może znajdować się w cechach i pod inną nazwą. +Zwraca refleksję metody, która zawiera deklarację metody `$method`. Zwykle każda metoda jest swoją własną deklaracją, ale ciało metody może znajdować się również w trait i pod inną nazwą. -Ponieważ PHP nie dostarcza wystarczających informacji, za pomocą których można określić rzeczywistą deklarację, Nette używa własnej heurystyki, która **powinna** być wiarygodna. +Ponieważ PHP nie dostarcza wystarczających informacji, za pomocą których można ustalić rzeczywistą deklarację, Nette wykorzystuje własną heurystykę, która **powinna być** niezawodna. ```php trait DemoTrait @@ -76,9 +76,9 @@ Reflection::getMethodDeclaringMethod($method); // ReflectionMethod('DemoTrait::f getPropertyDeclaringClass(ReflectionProperty $prop): ReflectionClass .[method] ------------------------------------------------------------------------------ -Zwraca odzwierciedlenie klasy lub cechy, która zawiera deklarację właściwości `$prop`. Właściwość może być w rzeczywistości zadeklarowana w cechach. +Zwraca refleksję klasy lub traitu, która zawiera deklarację właściwości `$prop`. Właściwość może być bowiem zadeklarowana również w trait. -Ponieważ PHP nie dostarcza wystarczających informacji, aby określić rzeczywistą deklarację, Nette używa własnej heurystyki, która jest **nie** wiarygodna. +Ponieważ PHP nie dostarcza wystarczających informacji, za pomocą których można ustalić rzeczywistą deklarację, Nette wykorzystuje własną heurystykę, która **nie jest** niezawodna. ```php trait DemoTrait @@ -100,7 +100,7 @@ Reflection::getPropertyDeclaringClass($prop); // ReflectionClass('DemoTrait') isBuiltinType(string $type): bool .[method deprecated] ------------------------------------------------------ -Określ, czy `$type` jest wbudowanym typem PHP. W przeciwnym razie jest to nazwa klasy. +Sprawdza, czy `$type` jest wbudowanym typem PHP. W przeciwnym razie jest to nazwa klasy. ```php Reflection::isBuiltinType('string'); // true @@ -108,13 +108,13 @@ Reflection::isBuiltinType('Foo'); // false ``` .[note] -Użyj funkcji [Nette\Utils\Validator::isBuiltinType() |validators#isBuiltinType]. +Użyj [Nette\Utils\Validator::isBuiltinType() |validators#isBuiltinType]. toString($reflection): string .[method] --------------------------------------- -Konwertuje odbicie na zrozumiały dla człowieka ciąg znaków. +Konwertuje refleksję na zrozumiały dla człowieka ciąg znaków. ```php $func = new ReflectionFunction('func'); diff --git a/utils/pl/smartobject.texy b/utils/pl/smartobject.texy index 138a67a5fc..4a0a7a2af8 100644 --- a/utils/pl/smartobject.texy +++ b/utils/pl/smartobject.texy @@ -1,8 +1,8 @@ -SmartObject i StaticClass -************************* +SmartObject +*********** .[perex] -SmartObject dodaje do klas PHP wsparcie dla *property*. StaticClass służy do oznaczania klas statycznych. +SmartObject przez lata ulepszał zachowanie obiektów w PHP. Od wersji PHP 8.4 wszystkie jego funkcje są już częścią samego PHP, czym zakończył swoją historyczną misję bycia pionierem nowoczesnego podejścia obiektowego w PHP. Instalacja: @@ -11,19 +11,64 @@ Instalacja: composer require nette/utils ``` +SmartObject powstał w 2007 roku jako rewolucyjne rozwiązanie niedociągnięć ówczesnego modelu obiektowego PHP. W czasach, gdy PHP cierpiało z powodu wielu problemów z projektowaniem obiektowym, przyniósł znaczące ulepszenie i uproszczenie pracy dla deweloperów. Stał się legendarną częścią frameworku Nette. Oferował funkcjonalność, którą PHP uzyskało dopiero wiele lat później - od kontroli dostępu do właściwości obiektów po wyrafinowany lukier składniowy. Wraz z nadejściem PHP 8.4 zakończył swoją historyczną misję, ponieważ wszystkie jego funkcje stały się natywną częścią języka. Wyprzedził rozwój PHP o niezwykłe 17 lat. -Właściwości, gettery i settery .[#toc-properties-gettery-a-settery] -=================================================================== +Technicznie SmartObject przeszedł interesujący rozwój. Początkowo był zaimplementowany jako klasa `Nette\Object`, od której inne klasy dziedziczyły potrzebną funkcjonalność. Zasadnicza zmiana przyszła wraz z PHP 5.4, które przyniosło wsparcie dla traitów. Umożliwiło to transformację do postaci traitu `Nette\SmartObject`, co przyniosło większą elastyczność - deweloperzy mogli wykorzystać funkcjonalność również w klasach, które już dziedziczyły po innej klasie. Podczas gdy oryginalna klasa `Nette\Object` zniknęła wraz z nadejściem PHP 7.2 (które zakazało nazywania klas słowem `Object`), trait `Nette\SmartObject` żyje dalej. -W nowoczesnych językach obiektowych (np. C#, Python, Ruby, JavaScript) termin *property* odnosi się do [specjalnych członków klas |https://en.wikipedia.org/wiki/Property_(programming)], które wyglądają jak zmienne, ale w rzeczywistości są reprezentowane przez metody. Kiedy wartość tej "zmiennej" jest przypisywana lub odczytywana, wywoływana jest odpowiednia metoda (zwana getterem lub setterem). Jest to bardzo przydatna rzecz, daje nam pełną kontrolę nad dostępem do zmiennych. Możemy zatwierdzić dane wejściowe lub wygenerować wyniki tylko wtedy, gdy właściwość zostanie odczytana. +Przejdźmy przez właściwości, które kiedyś oferowały `Nette\Object`, a później `Nette\SmartObject`. Każda z tych funkcji w swoim czasie stanowiła znaczący krok naprzód w dziedzinie programowania obiektowego w PHP. -Właściwości PHP nie są obsługiwane, ale traita `Nette\SmartObject` może je imitować. Jak to zrobić? -- Dodaj adnotację do klasy w postaci `@property $xyz` -- Utwórz getter o nazwie `getXyz()` lub `isXyz()`, setter o nazwie `setXyz()` -- Getter i setter muszą być *public* lub *protected* i są opcjonalne, więc może być właściwość *read-only* lub *write-only*. +Spójne stany błędów +------------------- +Jednym z najbardziej palących problemów wczesnego PHP było niespójne zachowanie podczas pracy z obiektami. `Nette\Object` wprowadził do tego chaosu porządek i przewidywalność. Spójrzmy, jak wyglądało oryginalne zachowanie PHP: + +```php +echo $obj->undeclared; // E_NOTICE, później E_WARNING +$obj->undeclared = 1; // przejdzie cicho bez zgłoszenia +$obj->unknownMethod(); // Fatal error (nie do przechwycenia za pomocą try/catch) +``` + +Błąd typu Fatal error kończył aplikację bez możliwości jakiejkolwiek reakcji. Cichy zapis do nieistniejących składowych bez ostrzeżenia mógł prowadzić do poważnych błędów, które były trudne do wykrycia. `Nette\Object` wszystkie te przypadki przechwytywał i rzucał wyjątek `MemberAccessException`, co pozwalało programistom reagować na błędy i je rozwiązywać. + +```php +echo $obj->undeclared; // rzuci Nette\MemberAccessException +$obj->undeclared = 1; // rzuci Nette\MemberAccessException +$obj->unknownMethod(); // rzuci Nette\MemberAccessException +``` + +Od PHP 7.0 język już nie powoduje nieprzechwytywalnych błędów fatalnych, a od PHP 8.2 dostęp do niezadeklarowanych składowych jest uważany za błąd. -Wykorzystamy własność dla klasy Circle, aby zapewnić, że do zmiennej `$radius` wstawiane są tylko liczby nieujemne. Zamień `public $radius` na własność: + +Pomoc "Did you mean?" +--------------------- +`Nette\Object` przyszedł z bardzo przyjemną funkcją: inteligentną pomocą przy literówkach. Gdy deweloper popełnił błąd w nazwie metody lub zmiennej, nie tylko zgłaszał błąd, ale także oferował pomocną dłoń w postaci sugestii poprawnej nazwy. Ten ikoniczny komunikat, znany jako "did you mean?", oszczędził programistom godziny szukania literówek: + +```php +class Foo extends Nette\Object +{ + public static function from($var) + { + } +} + +$foo = Foo::form($var); +// rzuci Nette\MemberAccessException +// "Wywołanie niezdefiniowanej metody statycznej Foo::form(), czy chodziło o from()?" +``` + +Dzisiejsze PHP wprawdzie nie ma żadnej formy „did you mean?”, ale ten dopisek potrafi dodawać do błędów [Tracy|tracy:]. A nawet takie błędy potrafi [samodzielnie poprawiać |tracy:open-files-in-ide#Przykłady]. + + +Właściwości z kontrolowanym dostępem +------------------------------------ +Znaczącą innowacją, którą SmartObject wprowadził do PHP, były właściwości z kontrolowanym dostępem. Ten koncept, powszechny w językach takich jak C# czy Python, umożliwił deweloperom elegancko kontrolować dostęp do danych obiektu i zapewnić ich spójność. Właściwości są potężnym narzędziem programowania obiektowego. Działają jak zmienne, ale w rzeczywistości są reprezentowane przez metody (gettery i settery). To pozwala walidować dane wejściowe lub generować wartości dopiero w momencie odczytu. + +Aby używać właściwości, musisz: +- Dodać klasie adnotację w postaci `@property $xyz` +- Stworzyć getter o nazwie `getXyz()` lub `isXyz()`, setter o nazwie `setXyz()` +- Zapewnić, aby getter i setter były *public* lub *protected*. Są opcjonalne - mogą więc istnieć jako właściwości *read-only* lub *write-only* + +Pokażmy praktyczny przykład na klasie Circle, gdzie wykorzystamy właściwości do zapewnienia, że promień będzie zawsze liczbą nieujemną. Zastąpimy oryginalny `public $radius` właściwością: ```php /** @@ -34,22 +79,22 @@ class Circle { use Nette\SmartObject; - private float $radius = 0.0; // není publiczne! + private float $radius = 0.0; // nie jest public! - // getter pro właściwość $radius + // getter dla właściwości $radius protected function getRadius(): float { return $this->radius; } - // setter pro właściwość $radius + // setter dla właściwości $radius protected function setRadius(float $radius): void { - // hodnotu před uložením sanitizujeme + // sanityzujemy wartość przed zapisaniem $this->radius = max(0.0, $radius); } - // getter pro właściwość $visible + // getter dla właściwości $visible protected function isVisible(): bool { return $this->radius > 0; @@ -57,84 +102,30 @@ class Circle } $circle = new Circle; -$circle->radius = 10; // ve skutečnosti volá setRadius(10) -echo $circle->radius; // volá getRadius() -echo $circle->visible; // volá isVisible() +$circle->radius = 10; // w rzeczywistości wywołuje setRadius(10) +echo $circle->radius; // wywołuje getRadius() +echo $circle->visible; // wywołuje isVisible() ``` -Właściwości to przede wszystkim "cukier syntaktyczny"((syntactic sugar)), który ma uczynić życie programisty słodszym poprzez uproszczenie kodu. Jeśli ich nie chcesz, nie musisz z nich korzystać. - - -Klasy statyczne .[#toc-staticke-tridy] -====================================== - -Klasy statyczne, czyli takie, które nie są przeznaczone do instancjonowania, mogą być oznaczone cechą `Nette\StaticClass`: +Od PHP 8.4 można osiągnąć tę samą funkcjonalność za pomocą property hooks, które oferują znacznie bardziej elegancką i zwięzłą składnię: ```php -class Strings +class Circle { - use Nette\StaticClass; -} -``` - -Podczas próby utworzenia instancji rzucany jest wyjątek `Error` wskazujący, że klasa jest statyczna. - - -Spojrzenie na historię -====================== - -SmartObject kiedyś poprawiał i naprawiał zachowanie klasy na wiele sposobów, ale ewolucja PHP sprawiła, że większość oryginalnych funkcji stała się zbędna. Poniżej przedstawiamy więc spojrzenie na historię tego, jak sprawy się rozwijały. - -Model obiektowy PHP od początku cierpiał na szereg poważnych wad i nieefektywności. Z tego powodu powstała klasa `Nette\Object` (w 2007 roku), która starała się im zaradzić i poprawić doświadczenia związane z używaniem PHP. Wystarczyło, aby inne klasy dziedziczyły po niej i czerpały korzyści, które przynosiła. Kiedy w PHP 5.4 pojawiła się obsługa cech, klasa `Nette\Object` została zastąpiona przez `Nette\SmartObject`. Tym samym nie było już konieczności dziedziczenia po wspólnym przodku. Dodatkowo trait można było wykorzystać w klasach, które już dziedziczyły po innej klasie. Ostateczny koniec `Nette\Object` nastąpił wraz z wydaniem PHP 7.2, który zabronił klasom nadawania nazw `Object`. - -Wraz z rozwojem PHP udoskonalano model obiektowy i możliwości języka. Poszczególne funkcje klasy `SmartObject` stały się zbędne. Od wydania PHP 8.2 jedyną cechą, która pozostała, a która nie jest jeszcze bezpośrednio wspierana w PHP, jest możliwość korzystania z tzw. [właściwości |#Properties, gettery a settery]. - -Jakie funkcje oferowały kiedyś `Nette\Object` i `Nette\Object`? Oto przegląd. (W przykładach użyto klasy `Nette\Object`, ale większość właściwości dotyczy również cechy `Nette\SmartObject`). - - -Niespójne błędy .[#toc-nekonzistentni-chyby] --------------------------------------------- -PHP zachowywał się niespójnie podczas dostępu do niezadeklarowanych członków. Stan w momencie wejścia na stronę `Nette\Object` był następujący: - -```php -echo $obj->undeclared; // E_NOTICE, później E_WARNING -$obj->undeclared = 1; // przechodzi cicho bez zgłaszania -$obj->unknownMethod(); // Błąd fatalny (nie do wychwycenia przez try/catch) -``` - -Fatal error zakończył działanie aplikacji bez możliwości reakcji. Ciche pisanie do nieistniejących członków bez ostrzeżenia mogło prowadzić do poważnych błędów trudnych do wykrycia. `Nette\Object` Wszystkie te przypadki zostały złapane i wyjątek rzucony przez `MemberAccessException`. - -```php -echo $obj->undeclared; // vyhodí Nette\MemberAccessException -$obj->undeclared = 1; // vyhodí Nette\MemberAccessException -$obj->unknownMethod(); // vyhodí Nette\MemberAccessException -``` -Od PHP 7.0, PHP nie powoduje już nieśledzonych błędów fatalnych, a dostęp do niezadeklarowanych członków jest błędem od PHP 8.2. - - -Miałeś na myśli? .[#toc-did-you-mean] -------------------------------------- -Jeśli został rzucony błąd `Nette\MemberAccessException`, być może z powodu literówki przy dostępie do zmiennej obiektu lub wywołaniu metody, `Nette\Object` próbował w komunikacie o błędzie dać podpowiedź, jak naprawić błąd, w postaci ikonicznego dodatku "czy miałeś na myśli?". + public float $radius = 0.0 { + set => max(0.0, $value); + } -```php -class Foo extends Nette\Object -{ - public static function from($var) - { + public bool $visible { + get => $this->radius > 0; } } - -$foo = Foo::form($var); -// vyhodí Nette\MemberAccessException -// "Call to undefined static method Foo::form(), did you mean from()?" ``` -Dzisiejszy PZP może nie ma żadnej formy "czy miałeś na myśli?", ale [Tracy |tracy:] może dodać ten dodatek do błędów. I może nawet [sam naprawić |tracy:open-files-in-ide#Demos] takie błędy. - -Metody rozszerzania .[#toc-extension-methods] ---------------------------------------------- -Zainspirowany metodami rozszerzającymi z języka C#. Dawały one możliwość dodawania nowych metod do istniejących klas. Na przykład możesz dodać metodę `addDateTime()` do formularza, aby dodać własny DateTimePicker. +Metody rozszerzeń +----------------- +`Nette\Object` wprowadził do PHP kolejny interesujący koncept inspirowany nowoczesnymi językami programowania - metody rozszerzeń. Ta funkcja, przejęta z C#, umożliwiła deweloperom elegancko rozszerzać istniejące klasy o nowe metody bez konieczności ich modyfikowania lub dziedziczenia po nich. Na przykład, można było dodać do formularza metodę `addDateTime()`, która doda własny DateTimePicker: ```php Form::extensionMethod( @@ -146,22 +137,22 @@ $form = new Form; $form->addDateTime('date'); ``` -Metody rozszerzania okazały się niepraktyczne, ponieważ ich nazwy nie sugerowały redaktorów, zamiast tego informowały, że dana metoda nie istnieje. Dlatego też zaprzestano ich wspierania. +Metody rozszerzeń okazały się niepraktyczne, ponieważ ich nazwy nie były podpowiadane przez edytory, wręcz przeciwnie, zgłaszały, że metoda nie istnieje. Dlatego ich wsparcie zostało zakończone. Dziś częściej wykorzystuje się kompozycję lub dziedziczenie do rozszerzania funkcjonalności klas. -Uzyskanie nazwy klasy: +Pobieranie nazwy klasy ---------------------- +Do pobierania nazwy klasy SmartObject oferował prostą metodę: ```php -$class = $obj->getClass(); // używając Nette. -$class = $obj::class; // od PHP 8.0 +$class = $obj->getClass(); // za pomocą Nette\Object +$class = $obj::class; // od PHP 8.0 ``` Dostęp do refleksji i adnotacji ------------------------------- - -`Nette\Object` zaoferował dostęp do refleksji i adnotacji za pomocą metod `getReflection()` i `getAnnotation()`: +`Nette\Object` oferował dostęp do refleksji i adnotacji za pomocą metod `getReflection()` i `getAnnotation()`. To podejście znacznie uprościło pracę z metainformacjami klas: ```php /** @@ -173,10 +164,10 @@ class Foo extends Nette\Object $obj = new Foo; $reflection = $obj->getReflection(); -$reflection->getAnnotation('author'); // vrátí 'John Doe +$reflection->getAnnotation('author'); // zwróci 'John Doe' ``` -Od PHP 8.0 możliwy jest dostęp do metainformacji w postaci atrybutów: +Od PHP 8.0 możliwe jest uzyskanie dostępu do metainformacji w postaci atrybutów, które oferują jeszcze większe możliwości i lepszą kontrolę typów: ```php #[Author('John Doe')] @@ -190,10 +181,9 @@ $reflection->getAttributes(Author::class)[0]; ``` -Metoda gettery --------------- - -`Nette\Object` oferował elegancki sposób przekazywania metod tak, jakby były one zmiennymi: +Gettery metod +------------- +`Nette\Object` oferował elegancki sposób, jak przekazywać metody, jakby były zmiennymi: ```php class Foo extends Nette\Object @@ -209,7 +199,7 @@ $method = $obj->adder; echo $method(2, 3); // 5 ``` -Od PHP 8.1 można używać tzw. składni wywoływalnej pierwszej klasy:https://www.php.net/manual/en/functions.first_class_callable_syntax: +Od PHP 8.1 można wykorzystać tzw. "first-class callable syntax":https://www.php.net/manual/en/functions.first_class_callable_syntax, która przenosi ten koncept jeszcze dalej: ```php $obj = new Foo; @@ -218,10 +208,9 @@ echo $method(2, 3); // 5 ``` -Wydarzenia ----------- - -`Nette\Object` zaoferował cukier syntaktyczny do wywołania [zdarzenia |nette:glossary#Events]: +Zdarzenia +--------- +SmartObject oferuje uproszczoną składnię do pracy ze [zdarzeniami |nette:glossary#Eventy zdarzenia]. Zdarzenia pozwalają obiektom informować inne części aplikacji o zmianach swojego stanu: ```php class Circle extends Nette\Object @@ -231,12 +220,12 @@ class Circle extends Nette\Object public function setRadius(float $radius): void { $this->onChange($this, $radius); - $this->radius = $radius + $this->radius = $radius; } } ``` -Kod `$this->onChange($this, $radius)` jest równoważny z następującym: +Kod `$this->onChange($this, $radius)` jest równoważny następującej pętli: ```php foreach ($this->onChange as $callback) { @@ -244,7 +233,7 @@ foreach ($this->onChange as $callback) { } ``` -Dla jasności, zalecamy unikanie metody magicznej `$this->onChange()`. Praktycznym substytutem jest funkcja [Nette\Utils\Arrays::invoke |arrays#invoke]: +Ze względu na czytelność zalecamy unikanie magicznej metody `$this->onChange()`. Praktycznym zamiennikiem jest na przykład funkcja [Nette\Utils\Arrays::invoke |arrays#invoke]: ```php Nette\Utils\Arrays::invoke($this->onChange, $this, $radius); diff --git a/utils/pl/staticclass.texy b/utils/pl/staticclass.texy new file mode 100644 index 0000000000..32c915ee2f --- /dev/null +++ b/utils/pl/staticclass.texy @@ -0,0 +1,21 @@ +Klasy statyczne +*************** + +.[perex] +StaticClass służy do oznaczania klas statycznych. + + +Instalacja: + +```shell +composer require nette/utils +``` + +Klasy statyczne, czyli klasy, które nie są przeznaczone do tworzenia instancji, można oznaczyć traitem [api:Nette\StaticClass]: + +```php +class Strings +{ + use Nette\StaticClass; +} +``` diff --git a/utils/pl/strings.texy b/utils/pl/strings.texy index 0b9b0abd1d..d99904c16a 100644 --- a/utils/pl/strings.texy +++ b/utils/pl/strings.texy @@ -1,8 +1,8 @@ -Praca z łańcuchami -****************** +Praca z ciągami znaków +********************** .[perex] -[api:Nette\Utils\Strings] jest klasą statyczną zawierającą przydatne funkcje do pracy z ciągami znaków, głównie w kodowaniu UTF-8. +[api:Nette\Utils\Strings] to statyczna klasa z przydatnymi funkcjami do pracy z ciągami znaków, głównie w kodowaniu UTF-8. Instalacja: @@ -11,17 +11,17 @@ Instalacja: composer require nette/utils ``` -Wszystkie przykłady zakładają, że alias został utworzony: +Wszystkie przykłady zakładają utworzenie aliasu: ```php use Nette\Utils\Strings; ``` -Rozróżnianie wielkości liter .[#toc-letter-case] -================================================ +Zmiana wielkości liter +====================== -Funkcje te wymagają rozszerzenia PHP `mbstring`. +Te funkcje wymagają rozszerzenia PHP `mbstring`. lower(string $s): string .[method] @@ -37,7 +37,7 @@ Strings::lower('Dobrý den'); // 'dobrý den' upper(string $s): string .[method] ---------------------------------- -Konwertuje ciąg znaków UTF-8 na wielkie litery. +Konwertuje ciąg UTF-8 na wielkie litery. ```php Strings::upper('Dobrý den'); // 'DOBRÝ DEN' @@ -47,7 +47,7 @@ Strings::upper('Dobrý den'); // 'DOBRÝ DEN' firstUpper(string $s): string .[method] --------------------------------------- -Konwertuje pierwszą literę łańcucha UTF-8 na wielkie litery, nie zmienia pozostałych. +Konwertuje pierwszą literę ciągu UTF-8 na wielką, pozostałych nie zmienia. ```php Strings::firstUpper('dobrý den'); // 'Dobrý den' @@ -57,7 +57,7 @@ Strings::firstUpper('dobrý den'); // 'Dobrý den' firstLower(string $s): string .[method] --------------------------------------- -Konwertuje pierwszą literę łańcucha UTF-8 na małe litery, nie zmienia reszty. +Konwertuje pierwszą literę ciągu UTF-8 na małą, pozostałych nie zmienia. ```php Strings::firstLower('Dobrý den'); // 'dobrý den' @@ -67,27 +67,27 @@ Strings::firstLower('Dobrý den'); // 'dobrý den' capitalize(string $s): string .[method] --------------------------------------- -Konwertuje pierwszą literę każdego słowa w łańcuchu UTF-8 na duże litery, resztę na małe. +Konwertuje pierwszą literę każdego słowa w ciągu UTF-8 na wielką, pozostałe na małe. ```php Strings::capitalize('Dobrý den'); // 'Dobrý Den' ``` -Ciąg dalszy .[#toc-editing-a-string] -==================================== +Modyfikacja ciągu znaków +======================== normalize(string $s): string .[method] -------------------------------------- -Usuwa znaki sterujące, normalizuje przerwy w linii do `\n`, przycina wiodące i ciągnące się puste linie, przycina prawe przerwy w linii, normalizuje UTF-8 do normalnej postaci NFC. +Usuwa znaki kontrolne, normalizuje końce linii do `\n`, usuwa początkowe i końcowe puste linie, usuwa spacje po prawej stronie linii, normalizuje UTF-8 do normalnej formy NFC. unixNewLines(string $s): string .[method] ----------------------------------------- -Konwertuje podziały linii na `\n` używane w systemach uniksowych. Podziałami linii są: `\n`, `\r`, `\r\n`, U+2028 separator linii, U+2029 separator akapitu. +Konwertuje końce linii na `\n` używane w systemach uniksowych. Końce linii to: `\n`, `\r`, `\r\n`, U+2028 line separator, U+2029 paragraph separator. ```php $unixLikeLines = Strings::unixNewLines($string); @@ -97,29 +97,29 @@ $unixLikeLines = Strings::unixNewLines($string); platformNewLines(string $s): string .[method] --------------------------------------------- -Konwertuje przerwy między wierszami na znaki specyficzne dla bieżącej platformy, tj. `\r\n` w Windows i `\n` w innych miejscach. Podziałami linii są: `\n`, `\r`, `\r\n`, U+2028 separator linii, U+2029 separator akapitu. +Konwertuje końce linii na znaki specyficzne dla bieżącej platformy, tj. `\r\n` w systemie Windows i `\n` gdzie indziej. Końce linii to: `\n`, `\r`, `\r\n`, U+2028 line separator, U+2029 paragraph separator. ```php $platformLines = Strings::platformNewLines($string); ``` -webalize(string $s, string $charlist=null, bool $lower=true): string .[method] ------------------------------------------------------------------------------- +webalize(string $s, ?string $charlist=null, bool $lower=true): string .[method] +------------------------------------------------------------------------------- -Modyfikuje ciąg UTF-8 do formatu używanego w adresie URL, tj. usuwa znaki diakrytyczne i zastępuje wszystkie znaki z wyjątkiem liter alfabetu angielskiego i cyfr myślnikiem. +Modyfikuje ciąg UTF-8 do postaci używanej w adresach URL, tj. usuwa znaki diakrytyczne i wszystkie znaki, oprócz liter alfabetu angielskiego i cyfr, zastępuje myślnikiem. ```php Strings::webalize('náš produkt'); // 'nas-produkt' ``` -Jeśli mają być zachowane inne znaki, można je podać w drugim parametrze funkcji. +Jeśli inne znaki mają zostać zachowane, można je określić w drugim parametrze funkcji. ```php Strings::webalize('10. obrázek_id', '._'); // '10.-obrazek_id' ``` -Trzeci parametr może być użyty do tłumienia konwersji na małe litery. +Trzeci parametr pozwala pominąć konwersję na małe litery. ```php Strings::webalize('Dobrý den', null, false); // 'Dobry-den' @@ -129,10 +129,10 @@ Strings::webalize('Dobrý den', null, false); // 'Dobry-den' Wymaga rozszerzenia PHP `intl`. -trim(string $s, string $charlist=null): string .[method] --------------------------------------------------------- +trim(string $s, ?string $charlist=null): string .[method] +--------------------------------------------------------- -Wycina spacje (lub inne znaki określone przez drugi parametr) z początku i końca łańcucha UTF-8. +Usuwa spacje (lub inne znaki określone przez drugi parametr) z początku i końca ciągu UTF-8. ```php Strings::trim(' Hello '); // 'Hello' @@ -142,7 +142,7 @@ Strings::trim(' Hello '); // 'Hello' truncate(string $s, int $maxLen, string $append=`'…'`): string .[method] ------------------------------------------------------------------------ -Obcina łańcuch UTF-8 do określonej maksymalnej długości, starając się zachować całe słowa. Jeśli łańcuch jest obcięty, dodaje triplet na koniec (można go zmienić za pomocą trzeciego parametru). +Skraca ciąg UTF-8 do podanej maksymalnej długości, starając się zachować całe słowa. Jeśli ciąg zostanie skrócony, na końcu dodaje wielokropek (można to zmienić trzecim parametrem). ```php $text = 'Řekněte, jak se máte?'; @@ -156,7 +156,7 @@ Strings::truncate($text, 20, '~'); // 'Řekněte, jak se~' indent(string $s, int $level=1, string $indentationChar=`"\t"`): string .[method] --------------------------------------------------------------------------------- -Wcięcie tekstu wielowierszowego od lewej strony. Ilość wcięć określa drugi parametr, który służy do wcięcia trzeciego parametru (domyślną wartością jest tabulator). +Wcina tekst wielowierszowy od lewej. Liczbę wcięć określa drugi parametr, a czym wcięcie ma być wykonane - trzeci parametr (domyślnie jest to tabulator). ```php Strings::indent('Nette'); // "\tNette" @@ -167,7 +167,7 @@ Strings::indent('Nette', 2, '+'); // '++Nette' padLeft(string $s, int $length, string $pad=`' '`): string .[method] -------------------------------------------------------------------- -Uzupełnia łańcuch UTF-8 do określonej długości, powtarzając łańcuch `$pad` od lewej. +Uzupełnia ciąg UTF-8 do podanej długości, powtarzając ciąg `$pad` od lewej strony. ```php Strings::padLeft('Nette', 6); // ' Nette' @@ -178,7 +178,7 @@ Strings::padLeft('Nette', 8, '+*'); // '+*+Nette' padRight(string $s, int $length, string $pad=`' '`): string .[method] --------------------------------------------------------------------- -Uzupełnia łańcuch UTF-8 do określonej długości, powtarzając łańcuch `$pad` od prawej. +Uzupełnia ciąg UTF-8 do podanej długości, powtarzając ciąg `$pad` od prawej strony. ```php Strings::padRight('Nette', 6); // 'Nette ' @@ -186,10 +186,10 @@ Strings::padRight('Nette', 8, '+*'); // 'Nette+*+' ``` -substring(string $s, int $start, int $length=null): string .[method] --------------------------------------------------------------------- +substring(string $s, int $start, ?int $length=null): string .[method] +--------------------------------------------------------------------- -Zwraca część łańcucha UTF-8 `$s` określoną przez pozycję początkową `$start` i długość `$length`. Jeśli `$start` jest ujemny, zwrócony łańcuch rozpocznie się od znaku -`$start` znak od końca. +Zwraca część ciągu UTF-8 `$s` określoną przez pozycję początkową `$start` i długość `$length`. Jeśli `$start` jest ujemny, zwracany ciąg będzie zaczynał się od znaku -`$start` od końca. ```php Strings::substring('Nette Framework', 0, 5); // 'Nette' @@ -201,7 +201,7 @@ Strings::substring('Nette Framework', -4); // 'work' reverse(string $s): string .[method] ------------------------------------ -Odwraca łańcuch UTF-8. +Odwraca ciąg UTF-8. ```php Strings::reverse('Nette'); // 'etteN' @@ -211,9 +211,9 @@ Strings::reverse('Nette'); // 'etteN' length(string $s): int .[method] -------------------------------- -Zwraca liczbę znaków (nie bajtów) w łańcuchu UTF-8. +Zwraca liczbę znaków (nie bajtów) w ciągu UTF-8. -Jest to liczba punktów kodowych Unicode, która może się różnić od liczby grafemów. +Jest to liczba punktów kodowych Unicode, która może różnić się od liczby grafemów. ```php Strings::length('Nette'); // 5 @@ -224,7 +224,7 @@ Strings::length('červená'); // 7 startsWith(string $haystack, string $needle): bool .[method deprecated] ----------------------------------------------------------------------- -Ustalić, czy ciąg `$haystack` zaczyna się od ciągu `$needle`. +Sprawdza, czy ciąg `$haystack` zaczyna się od ciągu `$needle`. ```php $haystack = 'Začíná'; @@ -233,13 +233,13 @@ Strings::startsWith($haystack, $needle); // true ``` .[note] -Korzystaj z rodzimego `str_starts_with()`:https://www.php.net/manual/en/function.str-starts-with.php. +Użyj natywnej funkcji `str_starts_with()`:https://www.php.net/manual/en/function.str-starts-with.php. endsWith(string $haystack, string $needle): bool .[method deprecated] --------------------------------------------------------------------- -Dowiedz się, czy ciąg `$haystack` kończy się ciągiem `$needle`. +Sprawdza, czy ciąg `$haystack` kończy się ciągiem `$needle`. ```php $haystack = 'Končí'; @@ -248,13 +248,13 @@ Strings::endsWith($haystack, $needle); // true ``` .[note] -Korzystaj z rodzimego `str_ends_with()`:https://www.php.net/manual/en/function.str-ends-with.php. +Użyj natywnej funkcji `str_ends_with()`:https://www.php.net/manual/en/function.str-ends-with.php. contains(string $haystack, string $needle): bool .[method deprecated] --------------------------------------------------------------------- -Ustalić, czy ciąg `$haystack` zawiera `$needle`. +Sprawdza, czy ciąg `$haystack` zawiera ciąg `$needle`. ```php $haystack = 'Posluchárna'; @@ -263,25 +263,25 @@ Strings::contains($haystack, $needle); // true ``` .[note] -Korzystaj z rodzimego `str_contains()`:https://www.php.net/manual/en/function.str-contains.php. +Użyj natywnej funkcji `str_contains()`:https://www.php.net/manual/en/function.str-contains.php. -compare(string $left, string $right, int $length=null): bool .[method] ----------------------------------------------------------------------- +compare(string $left, string $right, ?int $length=null): bool .[method] +----------------------------------------------------------------------- -Porównanie bez rozróżniania wielkości liter dwóch łańcuchów UTF-8 lub ich części. Jeśli `$length` zawiera null, to porównywane są całe ciągi, jeśli ujemnie, to porównywana jest odpowiednia liczba znaków z końca ciągów, w przeciwnym razie porównywana jest odpowiednia liczba znaków z początku. +Porównuje dwa ciągi UTF-8 lub ich części, ignorując wielkość liter. Jeśli `$length` zawiera null, porównywane są całe ciągi, jeśli jest ujemny, porównywana jest odpowiednia liczba znaków od końca ciągów, w przeciwnym razie porównywana jest odpowiednia liczba znaków od początku. ```php Strings::compare('Nette', 'nette'); // true -Strings::compare('Nette', 'next', 2); // true - shoda prvních 2 znaků -Strings::compare('Nette', 'Latte', -2); // true - shoda posledních 2 znaků +Strings::compare('Nette', 'next', 2); // true - zgodność pierwszych 2 znaków +Strings::compare('Nette', 'Latte', -2); // true - zgodność ostatnich 2 znaków ``` findPrefix(...$strings): string .[method] ----------------------------------------- -Znajduje wspólny początek ciągów znaków. Lub zwraca pusty łańcuch, jeśli nie znaleziono wspólnego prefiksu. +Znajduje wspólny początek ciągów. Lub zwraca pusty ciąg, jeśli wspólny prefiks nie został znaleziony. ```php Strings::findPrefix('prefix-a', 'prefix-bb', 'prefix-c'); // 'prefix-' @@ -293,7 +293,7 @@ Strings::findPrefix('Nette', 'is', 'great'); // '' before(string $haystack, string $needle, int $nth=1): ?string .[method] ----------------------------------------------------------------------- -Zwraca część ciągu `$haystack` przed n-tym `$nth` wystąpieniem ciągu `$needle`. Lub `null`, jeśli nie znaleziono `$needle`. Jeśli `$nth` jest ujemne, to szuka od końca łańcucha. +Zwraca część ciągu `$haystack` przed n-tym `$nth` wystąpieniem ciągu `$needle`. Lub `null`, jeśli `$needle` nie został znaleziony. Przy ujemnej wartości `$nth` wyszukiwanie odbywa się od końca ciągu. ```php Strings::before('Nette_is_great', '_', 1); // 'Nette' @@ -306,7 +306,7 @@ Strings::before('Nette_is_great', '_', 3); // null after(string $haystack, string $needle, int $nth=1): ?string .[method] ---------------------------------------------------------------------- -Zwraca część ciągu `$haystack` po n-tym `$nth` wystąpieniu ciągu `$needle`. Lub `null`, jeśli nie znaleziono `$needle`. Jeśli `$nth` jest ujemne, to szuka od końca łańcucha. +Zwraca część ciągu `$haystack` po n-tym `$nth` wystąpieniu ciągu `$needle`. Lub `null`, jeśli `$needle` nie został znaleziony. Przy ujemnej wartości `$nth` wyszukiwanie odbywa się od końca ciągu. ```php Strings::after('Nette_is_great', '_', 2); // 'great' @@ -319,7 +319,7 @@ Strings::after('Nette_is_great', '_', 3); // null indexOf(string $haystack, string $needle, int $nth=1): ?int .[method] --------------------------------------------------------------------- -Zwraca pozycję w znakach n-tego `$nth` wystąpienia ciągu `$needle` w ciągu `$haystack`. Lub `null`, jeśli nie znaleziono `$needle`. Jeśli `$nth` jest ujemne, to jest wyszukiwane od końca łańcucha. +Zwraca pozycję w znakach n-tego `$nth` wystąpienia ciągu `$needle` w ciągu `$haystack`. Lub `null`, jeśli `$needle` nie został znaleziony. Przy ujemnej wartości `$nth` wyszukiwanie odbywa się od końca ciągu. ```php Strings::indexOf('abc abc abc', 'abc', 2); // 4 @@ -328,14 +328,14 @@ Strings::indexOf('abc abc abc', 'd'); // null ``` -Kodowanie .[#toc-encoding] -========================== +Kodowanie +========= fixEncoding(string $s): string .[method] ---------------------------------------- -Usuwa niepoprawne znaki UTF-8 z łańcucha. +Usuwa z ciągu nieprawidłowe znaki UTF-8. ```php $correctStrings = Strings::fixEncoding($string); @@ -345,20 +345,20 @@ $correctStrings = Strings::fixEncoding($string); checkEncoding(string $s): bool .[method deprecated] --------------------------------------------------- -Określ, czy jest to prawidłowy ciąg znaków UTF-8. +Sprawdza, czy ciąg jest prawidłowym ciągiem UTF-8. ```php $isUtf8 = Strings::checkEncoding($string); ``` .[note] -Użyj funkcji [Nette\Validator::isUnicode() |validators#isUnicode]. +Użyj [Nette\Utils\Validator::isUnicode() |validators#isUnicode]. toAscii(string $s): string .[method] ------------------------------------ -Konwertuje ciąg UTF-8 na ASCII, tzn. usuwa znaki diakrytyczne itp. +Konwertuje ciąg UTF-8 na ASCII, tj. usuwa znaki diakrytyczne itp. ```php Strings::toAscii('žluťoučký kůň'); // 'zlutoucky kun' @@ -371,33 +371,33 @@ Wymaga rozszerzenia PHP `intl`. chr(int $code): string .[method] -------------------------------- -Zwraca określony znak UTF-8 z punktu kodowego (liczba z zakresu 0x0000..D7FF i 0xE000..10FFFF). +Zwraca określony znak w UTF-8 z punktu kodowego (liczba w zakresie 0x0000..D7FF i 0xE000..10FFFF). ```php -Strings::chr(0xA9); // '©' v kódování UTF-8 +Strings::chr(0xA9); // '©' w kodowaniu UTF-8 ``` ord(string $char): int .[method] -------------------------------- -Zwraca punkt kodowy UTF-8 określonego znaku (liczba z zakresu 0x0000..D7FF lub 0xE000..10FFFF). +Zwraca punkt kodowy określonego znaku w UTF-8 (liczba w zakresie 0x0000..D7FF lub 0xE000..10FFFF). ```php Strings::ord('©'); // 0xA9 ``` -Wyrażenia regularne .[#toc-regular-expressions] -=============================================== +Wyrażenia regularne +=================== -Klasa Strings dostarcza funkcji do pracy z wyrażeniami regularnymi. W przeciwieństwie do natywnych funkcji PHP, mają one bardziej zrozumiałe API, lepszą obsługę Unicode, a co najważniejsze - wykrywanie błędów. Każdy błąd w kompilacji lub przetwarzaniu wyrażenia rzuci wyjątek `Nette\RegexpException`. +Klasa Strings oferuje funkcje do pracy z wyrażeniami regularnymi. W przeciwieństwie do natywnych funkcji PHP, dysponują one bardziej zrozumiałym API, lepszym wsparciem dla Unicode i przede wszystkim wykrywaniem błędów. Jakikolwiek błąd podczas kompilacji lub przetwarzania wyrażenia zgłosi wyjątek `Nette\RegexpException`. split(string $subject, string $pattern, bool $captureOffset=false, bool $skipEmpty=false, int $limit=-1, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------- -Dzieli łańcuch na tablicę zgodnie z wyrażeniem regularnym. Wyrażenia w nawiasach będą również przechwytywane i zwracane. +Dzieli ciąg na tablicę według wyrażenia regularnego. Wyrażenia w nawiasach również zostaną przechwycone i zwrócone. ```php Strings::split('hello, world', '~,\s*~'); @@ -407,7 +407,7 @@ Strings::split('hello, world', '~(,)\s*~'); // ['hello', ',', 'world']`` ``` -Jeśli `$skipEmpty` to `true`, zwrócone zostaną tylko niepuste wpisy: +Jeśli `$skipEmpty` jest `true`, zwrócone zostaną tylko niepuste elementy: ```php Strings::split('hello, world, ', '~,\s*~'); @@ -417,16 +417,16 @@ Strings::split('hello, world, ', '~,\s*~', skipEmpty: true); // ['hello', 'world'] ``` -Jeśli `$limit`, zwrócone zostaną tylko podciągi do limitu, a reszta łańcucha zostanie umieszczona w ostatnim elemencie. Limit o wartości -1 lub 0 oznacza brak limitu. +Jeśli podano `$limit`, zwrócone zostaną tylko podciągi do limitu, a reszta ciągu zostanie umieszczona w ostatnim elemencie. Limit -1 lub 0 oznacza brak ograniczeń. ```php Strings::split('hello, world, third', '~,\s*~', limit: 2); // ['hello', 'world, third'] ``` -Jeśli `$utf8` to `true`, ocena przełączy się na tryb Unicode. Podobnie jak w przypadku określenia modyfikatora `u`. +Jeśli `$utf8` jest `true`, przetwarzanie przełącza się w tryb Unicode. Podobnie jak w przypadku użycia modyfikatora `u`. -Jeśli `$captureOffset` jest `true`, dla każdego występującego dopasowania, zwrócona zostanie także jego pozycja w łańcuchu (w bajtach; w znakach, jeśli ustawiono `$utf8` ). Zmienia to wartość zwracaną na tablicę, gdzie każdy element jest parą składającą się z dopasowanego łańcucha i jego pozycji. +Jeśli `$captureOffset` jest `true`, dla każdego dopasowania zwrócona zostanie również jego pozycja w ciągu (w bajtach; jeśli ustawiono `$utf8`, to w znakach). Zmienia to wartość zwracaną na tablicę, w której każdy element jest parą składającą się z dopasowanego ciągu i jego pozycji. ```php Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true); @@ -440,7 +440,7 @@ Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true, utf8: true); match(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $utf8=false): ?array .[method] -------------------------------------------------------------------------------------------------------------------------------------------------- -Przeszukuje łańcuch w poszukiwaniu części pasującej do wyrażenia regularnego i zwraca tablicę ze znalezionym wyrażeniem i każdym podwyrażeniem, lub `null`. +Wyszukuje w ciągu część pasującą do wyrażenia regularnego i zwraca tablicę ze znalezionym wyrażeniem i poszczególnymi podwyrażeniami lub `null`. ```php Strings::match('hello!', '~\w+(!+)~'); @@ -450,7 +450,7 @@ Strings::match('hello!', '~X~'); // null ``` -Jeśli `$unmatchedAsNull` jest `true`, to nie przechwycone podwyrażenia są zwracane jako null; w przeciwnym razie są zwracane jako pusty łańcuch lub nie są zwracane: +Jeśli `$unmatchedAsNull` jest `true`, niedopasowane podwzorce są zwracane jako null; w przeciwnym razie są zwracane jako pusty ciąg lub nie są zwracane: ```php Strings::match('hello', '~\w+(!+)?~'); @@ -460,7 +460,7 @@ Strings::match('hello', '~\w+(!+)?~', unmatchedAsNull: true); // ['hello', null] ``` -Jeśli `$utf8` to `true`, ocena przełącza się na tryb Unicode. Podobnie jak w przypadku określenia modyfikatora `u`: +Jeśli `$utf8` jest `true`, przetwarzanie przełącza się w tryb Unicode. Podobnie jak w przypadku użycia modyfikatora `u`: ```php Strings::match('žlutý kůň', '~\w+~'); @@ -470,9 +470,9 @@ Strings::match('žlutý kůň', '~\w+~', utf8: true); // ['žlutý'] ``` -Za pomocą parametru `$offset` można określić pozycję, od której ma się rozpocząć wyszukiwanie (w bajtach; w znakach, jeśli ustawiono `$utf8` ). +Parametr `$offset` można użyć do określenia pozycji, od której należy rozpocząć wyszukiwanie (w bajtach; jeśli ustawiono `$utf8`, to w znakach). -Jeśli `$captureOffset` to `true`, dla każdego występującego dopasowania, zwrócona zostanie także jego pozycja w łańcuchu (w bajtach; jeśli ustawiono `$utf8`, w znakach). To zamienia wartość zwrotną w tablicę, gdzie każdy element jest parą składającą się z dopasowanego łańcucha i jego przesunięcia: +Jeśli `$captureOffset` jest `true`, dla każdego dopasowania zwrócona zostanie również jego pozycja w ciągu (w bajtach; jeśli ustawiono `$utf8`, to w znakach). Zmienia to wartość zwracaną na tablicę, w której każdy element jest parą składającą się z dopasowanego ciągu i jego offsetu: ```php Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true); @@ -483,10 +483,10 @@ Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true, utf8: true); ``` -matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false, bool $lazy=false): array|Generator .[method] +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Przeszukuje łańcuch w poszukiwaniu wszystkich wystąpień pasujących do wyrażenia regularnego i zwraca tablicę tablic zawierających dopasowane wyrażenie i każde podwyrażenie. +Wyszukuje w ciągu wszystkie wystąpienia pasujące do wyrażenia regularnego i zwraca tablicę tablic ze znalezionym wyrażeniem i poszczególnymi podwyrażeniami. ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~'); @@ -496,7 +496,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~'); ] */ ``` -Jeśli `$patternOrder` jest `true`, struktura wyników zmienia się tak, że pierwszy wpis jest tablicą pełnych dopasowań wzorca, drugi jest tablicą łańcuchów dopasowanych przez pierwszy podwzorzec w nawiasach, i tak dalej: +Jeśli `$patternOrder` jest `true`, struktura wyników zmieni się tak, że pierwszy element będzie zawierał tablicę pełnych dopasowań wzorca, drugi element będzie zawierał tablicę ciągów pasujących do pierwszego podwzorca w nawiasach itd.: ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~', patternOrder: true); @@ -506,7 +506,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~', patternOrder: true); ] */ ``` -Jeśli `$unmatchedAsNull` jest `true`, niedopasowane podwzorce są zwracane jako null; w przeciwnym razie są zwracane jako puste łańcuchy lub nie są zwracane: +Jeśli `$unmatchedAsNull` jest `true`, niedopasowane podwzorce są zwracane jako null; w przeciwnym razie są zwracane jako pusty ciąg lub nie są zwracane: ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~', unmatchedAsNull: true); @@ -516,7 +516,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~', unmatchedAsNull: true); ] */ ``` -Jeśli `$utf8` to `true`, ocena przełącza się na tryb Unicode. Podobnie jak w przypadku określenia modyfikatora `u`: +Jeśli `$utf8` jest `true`, przetwarzanie przełącza się w tryb Unicode. Podobnie jak w przypadku użycia modyfikatora `u`: ```php Strings::matchAll('žlutý kůň', '~\w+~'); @@ -532,9 +532,9 @@ Strings::matchAll('žlutý kůň', '~\w+~', utf8: true); ] */ ``` -Za pomocą parametru `$offset` można określić pozycję, od której ma się rozpocząć wyszukiwanie (w bajtach; w znakach, jeśli ustawiono `$utf8` ). +Parametr `$offset` można użyć do określenia pozycji, od której należy rozpocząć wyszukiwanie (w bajtach; jeśli ustawiono `$utf8`, to w znakach). -Jeśli `$captureOffset` to `true`, dla każdego występującego dopasowania, zwrócona zostanie także jego pozycja w łańcuchu (w bajtach; jeśli ustawiono `$utf8`, w znakach). Zmienia to wartość zwracaną na tablicę, gdzie każdy element jest parą składającą się z dopasowanego łańcucha i jego pozycji: +Jeśli `$captureOffset` jest `true`, dla każdego dopasowania zwrócona zostanie również jego pozycja w ciągu (w bajtach; jeśli ustawiono `$utf8`, to w znakach). Zmienia to wartość zwracaną na tablicę, w której każdy element jest parą składającą się z dopasowanego ciągu i jego pozycji: ```php Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true); @@ -550,11 +550,21 @@ Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true, utf8: true); ] */ ``` +Jeśli `$lazy` jest `true`, funkcja zwraca `Generator` zamiast tablicy, co przynosi znaczące korzyści wydajnościowe podczas pracy z dużymi ciągami. Generator pozwala na wyszukiwanie dopasowań stopniowo, zamiast przeszukiwania całego ciągu naraz. Umożliwia to efektywną pracę nawet z bardzo dużymi tekstami wejściowymi. Ponadto można w dowolnym momencie przerwać przetwarzanie, jeśli znajdzie się szukane dopasowanie, co oszczędza czas obliczeniowy. + +```php +$matches = Strings::matchAll($largeText, '~\w+~', lazy: true); +foreach ($matches as $match) { + echo "Znaleziono: $match[0]\n"; + // Przetwarzanie może zostać przerwane w dowolnym momencie +} +``` + replace(string $subject, string|array $pattern, string|callable $replacement='', int $limit=-1, bool $captureOffset=false, bool $unmatchedAsNull=false, bool $utf8=false): string .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Zastępuje wszystkie wystąpienia pasujące do wyrażenia regularnego. `$replacement` jest maską łańcucha zastępczego lub wywołaniem zwrotnym. +Zastępuje wszystkie wystąpienia pasujące do wyrażenia regularnego. `$replacement` jest albo maską ciągu zastępczego, albo funkcją zwrotną (callback). ```php Strings::replace('hello, world!', '~\w+~', '--'); @@ -564,7 +574,7 @@ Strings::replace('hello, world!', '~\w+~', fn($m) => strrev($m[0])); // 'olleh, dlrow!' ``` -Funkcja umożliwia również wielokrotne podstawianie, przekazując w drugim parametrze tablicę o postaci `pattern => replacement`: +Funkcja umożliwia również wykonanie wielu zamian, przekazując w drugim parametrze tablicę w formacie `pattern => replacement`: ```php Strings::replace('hello, world!', [ @@ -574,9 +584,9 @@ Strings::replace('hello, world!', [ // '-- --!' ``` -Parametr `$limit` ogranicza liczbę podstawień, które można wykonać. Limit o wartości -1 oznacza brak limitu. +Parametr `$limit` ogranicza liczbę wykonanych zamian. Limit -1 oznacza brak ograniczeń. -Jeśli `$utf8` to `true`, ocena przełącza się na tryb Unicode. Jest to podobne do określenia modyfikatora `u`. +Jeśli `$utf8` jest `true`, przetwarzanie przełącza się w tryb Unicode. Podobnie jak w przypadku użycia modyfikatora `u`. ```php Strings::replace('žlutý kůň', '~\w+~', '--'); @@ -586,7 +596,7 @@ Strings::replace('žlutý kůň', '~\w+~', '--', utf8: true); // '-- --' ``` -Jeśli `$captureOffset` to `true`, dla każdego występującego dopasowania, jego pozycja w łańcuchu (w bajtach; w znakach, jeśli ustawiono `$utf8` ) również zostanie przekazana do callbacka. Zmienia to postać przekazywanej tablicy, gdzie każdy element jest parą składającą się z dopasowanego łańcucha i jego pozycji. +Jeśli `$captureOffset` jest `true`, dla każdego dopasowania do funkcji zwrotnej przekazana zostanie również jego pozycja w ciągu (w bajtach; jeśli ustawiono `$utf8`, to w znakach). Zmienia to postać przekazywanej tablicy, w której każdy element jest parą składającą się z dopasowanego ciągu i jego pozycji. ```php Strings::replace( @@ -595,7 +605,7 @@ Strings::replace( function (array $m) { dump($m); return ''; }, captureOffset: true, ); -// dumps [['lut', 2]] a [['k', 8]] +// dumps [['lut', 2]] oraz [['k', 8]] Strings::replace( 'žlutý kůň', @@ -604,10 +614,10 @@ Strings::replace( captureOffset: true, utf8: true, ); -// dumps [['žlutý', 0]] a [['kůň', 6]] +// dumps [['žlutý', 0]] oraz [['kůň', 6]] ``` -Jeśli `$unmatchedAsNull` to `true`, niedopasowane podwzorce są przekazywane do callbacka jako null; w przeciwnym razie są przekazywane jako pusty łańcuch lub nie są przekazywane: +Jeśli `$unmatchedAsNull` jest `true`, niedopasowane podwzorce są przekazywane do funkcji zwrotnej jako null; w przeciwnym razie są przekazywane jako pusty ciąg lub nie są przekazywane: ```php Strings::replace( diff --git a/utils/pl/type.texy b/utils/pl/type.texy index b1f8920f6e..ba5d2a07eb 100644 --- a/utils/pl/type.texy +++ b/utils/pl/type.texy @@ -2,7 +2,7 @@ Typ PHP ******* .[perex] -[api:Nette\Utils\Type] jest klasą służącą do pracy z typami danych PHP. +[api:Nette\Utils\Type] to klasa do pracy z typami danych PHP. Instalacja: @@ -11,7 +11,7 @@ Instalacja: composer require nette/utils ``` -Wszystkie przykłady zakładają, że alias został utworzony: +Wszystkie przykłady zakładają utworzenie aliasu: ```php use Nette\Utils\Type; @@ -21,7 +21,7 @@ use Nette\Utils\Type; fromReflection($reflection): ?Type .[method] -------------------------------------------- -Metoda statyczna tworzy obiekt Type w oparciu o refleksję. Parametrem może być obiekt `ReflectionMethod` lub `ReflectionFunction` (zwraca typ wartości zwracanej) lub `ReflectionParameter` lub `ReflectionProperty`. Przekłada `self`, `static` i `parent` na rzeczywistą nazwę klasy. Jeśli podmiot nie ma typu, zwraca `null`. +Metoda statyczna tworzy obiekt Type na podstawie refleksji. Parametrem może być obiekt `ReflectionMethod` lub `ReflectionFunction` (zwraca typ wartości zwracanej) lub `ReflectionParameter` albo `ReflectionProperty`. Tłumaczy `self`, `static` i `parent` na rzeczywistą nazwę klasy. Jeśli podmiot nie ma typu, zwraca `null`. ```php class DemoClass @@ -37,7 +37,7 @@ echo Type::fromReflection($prop); // 'DemoClass' fromString(string $type): Type .[method] ---------------------------------------- -Metoda statyczna tworzy obiekt Type zgodnie z notacją tekstową. +Metoda statyczna tworzy obiekt Type na podstawie zapisu tekstowego. ```php $type = Type::fromString('Foo|Bar'); @@ -48,10 +48,10 @@ echo $type; // 'Foo|Bar' getNames(): (string|array)[] .[method] -------------------------------------- -Zwraca tablicę podtypów składających się na typ złożony, jako łańcuchy. +Zwraca tablicę podtypów, z których składa się typ złożony, jako ciągi znaków. ```php -$type = Type::fromString('string|null'); // nebo '?string' +$type = Type::fromString('string|null'); // lub '?string' $type->getNames(); // ['string', 'null'] $type = Type::fromString('(Foo&Bar)|string'); @@ -62,10 +62,10 @@ $type->getNames(); // [['Foo', 'Bar'], 'string'] getTypes(): Type[] .[method] ---------------------------- -Zwraca tablicę podtypów składających się na typ złożony, jako obiekty `ReflectionType`: +Zwraca tablicę podtypów, z których składa się typ złożony, jako obiekty `ReflectionType`: ```php -$type = Type::fromString('string|null'); // or '?string' +$type = Type::fromString('string|null'); // lub '?string' $type->getTypes(); // [Type::fromString('string'), Type::fromString('null')] $type = Type::fromString('(Foo&Bar)|string'); @@ -79,7 +79,7 @@ $type->getTypes(); // [Type::fromString('Foo'), Type::fromString('Bar')] getSingleName(): ?string .[method] ---------------------------------- -Zwraca nazwę typu dla typów prostych, w przeciwnym razie null. +Dla typów prostych zwraca nazwę typu, w przeciwnym razie null. ```php $type = Type::fromString('string|null'); @@ -99,14 +99,14 @@ echo $type->getSingleName(); // null isSimple(): bool .[method] -------------------------- -Zwraca, czy jest to typ prosty. Za typy proste uważa się również proste typy nullable: +Zwraca, czy jest to typ prosty. Za typy proste uważa się również proste typy dopuszczające wartość null: ```php $type = Type::fromString('string'); $type->isSimple(); // true $type->isUnion(); // false -$type = Type::fromString('?Foo'); // nebo 'Foo|null' +$type = Type::fromString('?Foo'); // lub 'Foo|null' $type->isSimple(); // true $type->isUnion(); // true ``` @@ -115,7 +115,7 @@ $type->isUnion(); // true isUnion(): bool .[method] ------------------------- -Zwraca, czy istnieje typ unii. +Zwraca, czy jest to typ unii. ```php $type = Type::fromString('string|int'); @@ -126,7 +126,7 @@ $type->isUnion(); // true isIntersection(): bool .[method] -------------------------------- -Zwraca, czy o jest typem przecięcia. +Zwraca, czy jest to typ przecięcia. ```php @@ -138,7 +138,7 @@ $type->isIntersection(); // true isBuiltin(): bool .[method] --------------------------- -Zwraca czy typ jest zarówno prosty jak i wbudowany w PHP. +Zwraca, czy typ jest prosty i jednocześnie jest wbudowanym typem PHP. ```php $type = Type::fromString('string'); @@ -155,7 +155,7 @@ $type->isBuiltin(); // false isClass(): bool .[method] ------------------------- -Zwraca, czy typ jest jednocześnie prosty i nazwą klasy. +Zwraca, czy typ jest prosty i jednocześnie jest nazwą klasy. ```php $type = Type::fromString('string'); @@ -172,7 +172,7 @@ $type->isClass(); // false isClassKeyword(): bool .[method] -------------------------------- -Zwraca, czy typ jest jednym z typów wewnętrznych `self`, `parent`, `static`. +Zwraca, czy typ jest jednym z wewnętrznych typów `self`, `parent`, `static`. ```php $type = Type::fromString('self'); @@ -186,7 +186,7 @@ $type->isClassKeyword(); // false allows(string $type): bool .[method] ------------------------------------ -Metoda `allows()` weryfikuje zgodność typów. Na przykład pozwala sprawdzić, czy wartość określonego typu może być przekazana jako parametr. +Metoda `allows()` weryfikuje kompatybilność typów. Na przykład pozwala sprawdzić, czy wartość określonego typu może być przekazana jako parametr. ```php $type = Type::fromString('string|null'); diff --git a/utils/pl/validators.texy b/utils/pl/validators.texy index 16941729a4..aef9f3c2fd 100644 --- a/utils/pl/validators.texy +++ b/utils/pl/validators.texy @@ -2,7 +2,7 @@ Walidatory wartości ******************* .[perex] -Potrzebujesz szybko i łatwo sprawdzić, czy zmienna zawiera np. poprawny adres e-mail? To właśnie tam przydaje się [api:Nette\Utils\Validators], statyczna klasa z przydatnymi funkcjami do sprawdzania poprawności wartości. +Potrzebujesz szybko i łatwo sprawdzić, czy zmienna zawiera na przykład prawidłowy adres e-mail? Przyda Ci się [api:Nette\Utils\Validators], statyczna klasa z przydatnymi funkcjami do walidacji wartości. Instalacja: @@ -11,17 +11,17 @@ Instalacja: composer require nette/utils ``` -Wszystkie przykłady zakładają, że alias został utworzony: +Wszystkie przykłady zakładają utworzenie aliasu: ```php use Nette\Utils\Validators; ``` -Podstawowe zastosowanie .[#toc-basic-usage] -=========================================== +Podstawowe użycie +================= -Klasa posiada szereg metod sprawdzających wartości, takich jak [isList() |#isList], [isUnicode( |#isUnicode]), [isEmail |#isEmail](), [isUrl |#isUrl] (), itp. do wykorzystania w Twoim kodzie: +Klasa dysponuje szeregiem metod do kontroli wartości, takich jak [#isUnicode], [#isEmail], [#isUrl] itp., do wykorzystania w Twoim kodzie: ```php if (!Validators::isEmail($email)) { @@ -29,7 +29,7 @@ if (!Validators::isEmail($email)) { } ``` -Może również sprawdzić, czy wartość jest [oczekiwanym typem |#Expected-Types], który jest ciągiem, w którym opcje są oddzielone przez `|` slash. Możemy więc łatwo sprawdzić wiele typów używając [if() |#if()]: +Ponadto potrafi zweryfikować, czy wartość jest tzw. [oczekiwanym typem |#Oczekiwane typy], czyli ciągiem znaków, gdzie poszczególne opcje oddziela się pionową kreską `|`. Możemy więc łatwo zweryfikować wiele typów za pomocą [#is()]: ```php if (!Validators::is($val, 'int|string|bool')) { @@ -37,81 +37,81 @@ if (!Validators::is($val, 'int|string|bool')) { } ``` -Ale daje nam również możliwość stworzenia systemu, w którym musimy napisać oczekiwania jako ciągi (na przykład w adnotacjach lub konfiguracji), a następnie zweryfikować wartości przeciwko nim. +Ale daje nam to również możliwość stworzenia systemu, w którym oczekiwania muszą być zapisywane jako ciągi znaków (na przykład w adnotacjach lub konfiguracji), a następnie według nich weryfikować wartości. -Możemy również umieścić na oczekiwanych typach żądanie [assert() |#assert], które rzuca wyjątek, jeśli nie jest spełnione. +Na oczekiwane typy można również nałożyć wymaganie [##assert()], które, jeśli nie zostanie spełnione, zgłosi wyjątek. -Przewidywane rodzaje .[#toc-expected-types] -=========================================== +Oczekiwane typy +=============== -Oczekiwane typy tworzą ciąg składający się z jednego lub więcej wariantów oddzielonych pionowym paskiem `|`, podobně jako se zapisují typy v PHP (např. `'int|string|bool')`. Akceptowany jest również zapis zerowy `?int`. +Oczekiwane typy tworzą ciąg znaków składający się z jednej lub więcej wariantów oddzielonych pionową kreską `|`, podobnie jak zapisuje się typy w PHP (np. `'int|string|bool'`). Akceptowany jest również zapis nullable `?int`. -Tablica, w której wszystkie elementy są określonego typu, jest zapisywana jako `int[]`. +Tablica, w której wszystkie elementy są określonego typu, zapisuje się w postaci `int[]`. -Po niektórych typach może następować dwukropek i długość `:length` lub zakres `:[min]..[max]`, np. `string:10` (ciąg 10 bajtów), `float:10..` (liczba 10 lub więcej), `array:..10` (tablica do dziesięciu elementów) lub `list:10..20` (lista od 10 do 20 elementów), lub wyrażenie regularne u `pattern:[0-9]+`. +Za niektórymi typami może następować dwukropek i długość `:length` lub zakres `:[min]..[max]`, np. `string:10` (ciąg o długości 10 bajtów), `float:10..` (liczba 10 i większa), `array:..10` (tablica do dziesięciu elementów) lub `list:10..20` (lista z 10 do 20 elementami), ewentualnie wyrażenie regularne przy `pattern:[0-9]+`. -Przegląd rodzajów i zasad: +Przegląd typów i reguł: .[wide] -| PHP types | +| Typy PHP || |-------------------------- -| `array` .{width: 140px} | można określić zakres dla liczby elementów -| `bool` | -| `float` | można określić zakres dla wartości -| `int` | można określić zakres wartości -| `null` | -| `object` | +| `array` .{width: 140px} | można podać zakres liczby elementów +| `bool` | +| `float` | można podać zakres wartości +| `int` | można podać zakres wartości +| `null` | +| `object` | | `resource` | -| `scalar` | int|float|bool|string -| `string` | można określić zakres długości w bajtach +| `scalar` | int\|float\|bool\|string +| `string` | można podać zakres długości w bajtach | `callable` | | `iterable` | -| `mixed` | +| `mixed` | |-------------------------- -| pseudo-typów || +| pseudo-typy || |------------------------------------------------ -| `list` | [tablica indeksowana |#isList], można określić zakres dla liczby elementów -| `none` | wartość pusta: `''`, `null`, `false` -| `number` | int||float -| `numeric` | [liczba wraz z reprezentacją tekstową |#isNumeric] -| `numericint`| liczba [całkowita wraz z reprezentacją tekstową |#isNumericInt] -| `unicode` | [UTF-8 ciąg |#isUnicode], można określić zakres długości w znakach +| `list` | tablica indeksowana, można podać zakres liczby elementów +| `none` | pusta wartość: `''`, `null`, `false` +| `number` | int\|float +| `numeric` | [liczba wraz z reprezentacją tekstową |#isNumeric] +| `numericint`| [liczba całkowita wraz z reprezentacją tekstową |#isNumericInt] +| `unicode` | [ciąg UTF-8 |#isUnicode], można podać zakres długości w znakach |-------------------------- -| Klasa postaci (nie może być pustym ciągiem) |. +| klasa znaków (nie może być pustym ciągiem) || |------------------------------------------------ -| `alnum` | wszystkie znaki są alfanumeryczne -| `alpha` | wszystkie znaki to litery `[A-Za-z]` -| `digit` | wszystkie znaki są cyframi -| `lower` | wszystkie znaki są małymi literami `[a-z]` -| `space` | wszystkie znaki są spacjami -| `upper` | wszystkie znaki są wielkie `[A-Z]` -| `xdigit` | wszystkie znaki są cyframi szesnastkowymi `[0-9A-Fa-f]` +| `alnum` | wszystkie znaki są alfanumeryczne +| `alpha` | wszystkie znaki są literami `[A-Za-z]` +| `digit` | wszystkie znaki są cyframi +| `lower` | wszystkie znaki są małymi literami `[a-z]` +| `space` | wszystkie znaki są spacjami +| `upper` | wszystkie znaki są wielkimi literami `[A-Z]` +| `xdigit` | wszystkie znaki są cyframi szesnastkowymi `[0-9A-Fa-f]` |-------------------------- -| sprawdzanie składni | | +| weryfikacja składni || |------------------------------------------------ -| `pattern` | wyrażenie regularne, które musi pasować do **całego** łańcucha -| `email` | [E-mail |#isEmail] -| `identifier`| [identyfikator PHP |#isPhpIdentifier] -| `url` | [URL |#isUrl] -| `uri` | [URI |#isUri] +| `pattern` | wyrażenie regularne, któremu musi odpowiadać **cały** ciąg +| `email` | [E-mail |#isEmail] +| `identifier`| [Identyfikator PHP |#isPhpIdentifier] +| `url` | [URL |#isUrl] +| `uri` | [URI |#isUri] |-------------------------- -| uwierzytelnienie środowiska | | +| weryfikacja środowiska || |------------------------------------------------ -| `class` | jest istniejącą klasą -| `interface` | to istniejący interfejs -| `directory` | to istniejący katalog -| `file` | to istniejący plik +| `class` | jest istniejącą klasą +| `interface` | jest istniejącym interfejsem +| `directory` | jest istniejącym katalogiem +| `file` | jest istniejącym plikiem -Asercja .[#toc-assertion] -========================= +Asercje +======= assert($value, string $expected, string $label='variable'): void .[method] -------------------------------------------------------------------------- -Sprawdza, czy wartość jest jednym z [oczekiwanych typów |#Expected-Types] oddzielonych gwiazdką. Jeśli nie, rzuca wyjątek [api:Nette\Utils\AssertionException]. Słowo `variable` w tekście wyjątku może być zastąpione innym parametrem `$label`. +Sprawdza, czy wartość jest jednym z [oczekiwanych typów |#Oczekiwane typy] oddzielonych pionową kreską. Jeśli nie, zgłasza wyjątek [api:Nette\Utils\AssertionException]. Słowo `variable` w tekście wyjątku można zastąpić innym za pomocą parametru `$label`. ```php Validators::assert('Nette', 'string:5'); // OK @@ -120,10 +120,10 @@ Validators::assert('Lorem ipsum dolor sit', 'string:78'); ``` -assertField(array $array, string|int $key, string $expected=null, string $label=null): void .[method] ------------------------------------------------------------------------------------------------------ +assertField(array $array, string|int $key, ?string $expected=null, ?string $label=null): void .[method] +------------------------------------------------------------------------------------------------------- -Sprawdza, czy element pod kluczem `$key` w polu `$array` jest jednym z [oczekiwanych typów |#Expected-Types] oddzielonych odwrotnym ukośnikiem. Jeśli nie, rzuca wyjątek [api:Nette\Utils\AssertionException]. Ciąg `item '%' in array` w tekście wyjątku można zastąpić innym parametrem `$label`. +Sprawdza, czy element pod kluczem `$key` w tablicy `$array` jest jednym z [oczekiwanych typów |#Oczekiwane typy] oddzielonych pionową kreską. Jeśli nie, zgłasza wyjątek [api:Nette\Utils\AssertionException]. Ciąg `item '%' in array` w tekście wyjątku można zastąpić innym za pomocą parametru `$label`. ```php $arr = ['foo' => 'Nette']; @@ -136,19 +136,19 @@ Validators::assertField($arr, 'foo', 'int'); ``` -Walidatory .[#toc-validators] -============================= +Walidatory +========== is($value, string $expected): bool .[method] -------------------------------------------- -Sprawdza, czy wartość jest jednym z [oczekiwanych typów |#Expected-Types] oddzielonych gwiazdką. +Sprawdza, czy wartość jest jednym z [oczekiwanych typów |#Oczekiwane typy] oddzielonych pionową kreską. ```php Validators::is(1, 'int|float'); // true Validators::is(23, 'int:0..10'); // false -Validators::is('Nette Framework', 'string:15'); // true, délka je 15 bytů +Validators::is('Nette Framework', 'string:15'); // true, długość to 15 bajtów Validators::is('Nette Framework', 'string:8..'); // true Validators::is('Nette Framework', 'string:30..40'); // false ``` @@ -157,7 +157,7 @@ Validators::is('Nette Framework', 'string:30..40'); // false isEmail(mixed $value): bool .[method] ------------------------------------- -Sprawdza, czy podana wartość jest prawidłowym adresem e-mail. Nie sprawdza, czy domena faktycznie istnieje, sprawdzana jest tylko składnia. Funkcja ta uwzględnia również przyszłe [TLD |https://cs.wikipedia.org/wiki/Doména_nejvyššího_řádu], które mogą być w unicode. +Sprawdza, czy wartość jest prawidłowym adresem e-mail. Nie sprawdza, czy domena faktycznie istnieje, weryfikowana jest tylko składnia. Funkcja uwzględnia również przyszłe [TLD|https://pl.wikipedia.org/wiki/Domena_najwyższego_poziomu], które mogą być również w unicode. ```php Validators::isEmail('example@nette.org'); // true @@ -169,7 +169,7 @@ Validators::isEmail('nette'); // false isInRange(mixed $value, array $range): bool .[method] ----------------------------------------------------- -Sprawdza czy wartość jest w podanym zakresie `[min, max]`gdzie górna lub dolna granica może być pominięta (`null`). Można porównywać liczby, ciągi znaków i obiekty DateTime. +Sprawdza, czy wartość znajduje się w danym zakresie `[min, max]`, gdzie górną lub dolną granicę można pominąć (`null`). Można porównywać liczby, ciągi znaków i obiekty DateTime. Jeśli brakuje obu granic (`[null, null]`) lub wartość jest `null`, zwraca `false`. @@ -198,7 +198,7 @@ Validators::isNone('nette'); // false isNumeric(mixed $value): bool .[method] --------------------------------------- -Sprawdza, czy wartość jest liczbą lub liczbą zapisaną w łańcuchu. +Sprawdza, czy wartość jest liczbą lub liczbą zapisaną w ciągu znaków. ```php Validators::isNumeric(23); // true @@ -213,7 +213,7 @@ Validators::isNumeric('1e6'); // false isNumericInt(mixed $value): bool .[method] ------------------------------------------ -Sprawdza, czy wartość jest liczbą całkowitą lub liczbą zapisaną w łańcuchu. +Sprawdza, czy wartość jest liczbą całkowitą lub liczbą całkowitą zapisaną w ciągu znaków. ```php Validators::isNumericInt(23); // true @@ -227,7 +227,7 @@ Validators::isNumericInt('nette'); // false isPhpIdentifier(string $value): bool .[method] ---------------------------------------------- -Sprawdza czy wartość jest poprawnym składniowo identyfikatorem w PHP, na przykład dla nazw klas, metod, funkcji itp. +Sprawdza, czy wartość jest składniowo poprawnym identyfikatorem w PHP, na przykład dla nazw klas, metod, funkcji itp. ```php Validators::isPhpIdentifier(''); // false @@ -251,7 +251,7 @@ Validators::isBuiltinType('Foo'); // false isTypeDeclaration(string $type): bool .[method] ----------------------------------------------- -Sprawdza, czy podana deklaracja typu jest poprawna składniowo. +Sprawdza, czy podana deklaracja typu jest składniowo poprawna. ```php Validators::isTypeDeclaration('?string'); // true @@ -268,7 +268,7 @@ Validators::isTypeDeclaration('(A|B)'); // false isClassKeyword(string $type): bool .[method] -------------------------------------------- -Sprawdza, czy `$type` jest jednym z typów wewnętrznych `self`, `parent`, `static`. +Sprawdza, czy `$type` jest jednym z wewnętrznych typów `self`, `parent`, `static`. ```php Validators::isClassKeyword('self'); // true @@ -279,7 +279,7 @@ Validators::isClassKeyword('Foo'); // false isUnicode(mixed $value): bool .[method] --------------------------------------- -Sprawdza, czy wartość jest prawidłowym ciągiem znaków UTF-8. +Sprawdza, czy wartość jest prawidłowym ciągiem UTF-8. ```php Validators::isUnicode('nette'); // true @@ -306,7 +306,7 @@ Validators::isUrl('nette.org'); // false isUri(string $value): bool .[method] ------------------------------------ -Sprawdza, czy wartość jest prawidłowym adresem URI, który w rzeczywistości jest ciągiem rozpoczynającym się od poprawnego składniowo schematu. +Sprawdza, czy wartość jest prawidłowym adresem URI, czyli właściwie ciągiem zaczynającym się od składniowo poprawnego schematu. ```php Validators::isUri('https://nette.org'); // true diff --git a/utils/pt/@home.texy b/utils/pt/@home.texy index f9219966f1..3dcdba0791 100644 --- a/utils/pt/@home.texy +++ b/utils/pt/@home.texy @@ -1,42 +1,46 @@ -Utilidades -********** +Nette Utils +*********** .[perex] -No pacote `nette/utils` você encontrará um conjunto de aulas úteis para o uso diário: +No pacote `nette/utils`, você encontrará um conjunto de classes úteis para uso diário: -| [Arrays |Arrays] | Nette\Utils\Arrays | [Callback |Callback] | Nette\Utils\Callback -| [Data e Hora |datetime] | Nette\Utils\DateTime -| [Sistema de arquivos |filesystem] | Nette\Utils\FileSystem +| [Data e hora |datetime] | Nette\Utils\DateTime | [Finder |Finder] | Nette\Utils\Finder -| [Flutuantes |Floats] | Nette\Utils\Floats -| [Ajudantes |helpers] | Nette\Utils\Helpers -| [Elementos HTML |HTML Elements] | Nette\Utils\Html -| [Imagens |Images] | Nette\Utils\Image -| [JSON |JSON] | Nette\Utils\Json -| [Modelo do objeto |smartobject] | Nette\SmartObject & Nette\StaticClass -| [Paginador de Paginadores |paginator] | Nette\Utils\Paginator -| [Reflexão PHP Ref |reflection] | Nette\Utils\Reflection -| [Tipos de PHP |type] | Nette\Utils\Type -| [Random Strings |random] | Nette\Utils\Random -| [Cordas |Strings] | Nette\Utils\Strings -| [Validadores |validators] Nette\Utils\Validators +| [Elementos HTML |html-elements] | Nette\Utils\Html +| [Iteradores |iterables] | Nette\Utils\Iterables +| [JSON |json] | Nette\Utils\Json +| [Strings aleatórias |random] | Nette\Utils\Random +| [Imagens |images] | Nette\Utils\Image +| [Reflexão PHP |reflection] | Nette\Utils\Reflection +| [Tipos PHP |type] | Nette\Utils\Type +| [Arrays |arrays] | Nette\Utils\Arrays +| [Funções auxiliares |helpers] | Nette\Utils\Helpers +| [Comparação de floats |floats] | Nette\Utils\Floats +| [Strings |strings] | Nette\Utils\Strings +| [Sistema de arquivos |filesystem] | Nette\Utils\FileSystem +| [Paginação |paginator] | Nette\Utils\Paginator +| [SmartObject |SmartObject] & [StaticClass |StaticClass] | Nette\SmartObject & Nette\StaticClass +| [Validador |validators] | Nette\Utils\Validators Instalação ---------- -Baixe e instale o pacote usando [o Composer |best-practices:composer]: +Baixe e instale a biblioteca usando o [Composer|best-practices:composer]: ```shell composer require nette/utils ``` -| versão | compatível com PHP +| versão | compatível com PHP |-----------|------------------- -| Nette Utils 4.0 | PHP 8.0 - 8.2 -| Nette Utils 3.2 | PHP 7.2 - 8.2 -| Nette Utils 3.0 - 3.1 | PHP 7.1 - 8.0 -| Nette Utils 2.5 | PHP 5.6 - 8.0 +| Nette Utils 4.0 | PHP 8.0 – 8.4 +| Nette Utils 3.2 | PHP 7.2 – 8.3 +| Nette Utils 3.0 – 3.1 | PHP 7.1 – 8.0 +| Nette Utils 2.5 | PHP 5.6 – 8.0 + +Aplica-se à última versão de patch. + -Aplica-se às últimas versões de remendos. +Se você estiver atualizando o pacote para uma versão mais recente, consulte a página [upgrade|en:upgrading]. diff --git a/utils/pt/@left-menu.texy b/utils/pt/@left-menu.texy index 487e5158bd..a9fbda2761 100644 --- a/utils/pt/@left-menu.texy +++ b/utils/pt/@left-menu.texy @@ -1,26 +1,28 @@ -Pacote nette/utils -****************** -- [Arrays |Arrays] -- [Chamada de retorno |Callback] +Nette Utils +*********** +- [Callbacks |callback] - [Data e hora |datetime] -- [Sistema de arquivos |filesystem] - [Finder |Finder] -- [Ajudantes |helpers] -- [Elementos HTML |HTML Elements] -- [Imagens |Images] +- [Floats |Floats] +- [Elementos HTML |html-elements] +- [Iteradores |iterables] - [JSON |JSON] -- [Paginador |paginator] -- [Cordas Aleatórias |random] -- [SmartObject |SmartObject] +- [Strings aleatórias |random] +- [Imagens |images] +- [Paginator |paginator] - [Reflexão PHP |reflection] -- [Cordas |Strings] -- [Flutuadores |Floats] -- [Tipos de PHP |type] -- [Validadores |validators] +- [Tipos PHP |type] +- [Arrays |arrays] +- [Funções auxiliares |helpers] +- [Strings |strings] +- [SmartObject |SmartObject] +- [StaticClass |StaticClass] +- [Sistema de arquivos |filesystem] +- [Validador |validators] -Outras Utilidades -***************** -- [NEON |neon:] -- [Hashing de senha |security:passwords] -- [URL Parser e Builder |http:urls] +Outras ferramentas +****************** +- [NEON|neon:] +- [Hashing de senhas |security:passwords] +- [Análise e composição de URL |http:urls] diff --git a/utils/pt/@meta.texy b/utils/pt/@meta.texy new file mode 100644 index 0000000000..41a853b6aa --- /dev/null +++ b/utils/pt/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentação Nette}} diff --git a/utils/pt/arrays.texy b/utils/pt/arrays.texy index c7b6db12bb..bef58ccebb 100644 --- a/utils/pt/arrays.texy +++ b/utils/pt/arrays.texy @@ -1,8 +1,8 @@ -Funções do Array -**************** +Trabalhando com arrays +********************** .[perex] -Esta página é sobre as classes [Arrays |#Arrays], [ArrayHash |#ArrayHash] e [ArrayList |#ArrayList], que estão relacionadas a arrays. +Esta página é dedicada às classes [Nette\Utils\Arrays |#Arrays], [#ArrayHash] e [#ArrayList], que dizem respeito a arrays. Instalação: @@ -12,22 +12,63 @@ composer require nette/utils ``` -Arrays .[#toc-arrays] -===================== +Arrays +====== -[api:Nette\Utils\Arrays] é uma classe estática, que contém um punhado de funções de matriz úteis. +[api:Nette\Utils\Arrays] é uma classe estática que contém funções úteis para trabalhar com arrays. Seu equivalente para iteradores é [Nette\Utils\Iterables|iterables]. -Os exemplos a seguir assumem que a seguinte classe está definida: +Os exemplos a seguir pressupõem a criação de um alias: ```php use Nette\Utils\Arrays; ``` +associate(array $array, mixed $path): array|\stdClass .[method] +--------------------------------------------------------------- + +A função transforma flexivelmente o array `$array` em um array associativo ou objetos de acordo com o caminho `$path` especificado. O caminho pode ser uma string ou um array. É composto pelos nomes das chaves do array de entrada e operadores como '[]', '->', '=', e '|'. Lança `Nette\InvalidArgumentException` se o caminho for inválido. + +```php +// conversão para array associativo por chave simples +$arr = [ + ['name' => 'John', 'age' => 11], + ['name' => 'Mary', 'age' => null], + // ... +]; +$result = Arrays::associate($arr, 'name'); +// $result = ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// atribuição de valores de uma chave para outra usando o operador = +$result = Arrays::associate($arr, 'name=age'); // ou ['name', '=', 'age'] +// $result = ['John' => 11, 'Mary' => null, ...] +``` + +```php +// criação de objeto usando o operador -> +$result = Arrays::associate($arr, '->name'); // ou ['->', 'name'] +// $result = (object) ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// combinação de chaves usando o operador | +$result = Arrays::associate($arr, 'name|age'); // ou ['name', '|', 'age'] +// $result: ['John' => ['name' => 'John', 'age' => 11], 'Paul' => ['name' => 'Paul', 'age' => 44]] +``` + +```php +// adição ao array usando [] +$result = Arrays::associate($arr, 'name[]'); // ou ['name', '[]'] +// $result: ['John' => [['name' => 'John', 'age' => 22], ['name' => 'John', 'age' => 11]]] +``` + + contains(array $array, $value): bool .[method] ---------------------------------------------- -Testa uma matriz para a presença de valor. Utiliza uma comparação rigorosa (`===`) +Testa o array quanto à presença de um valor. Usa comparação estrita (`===`). ```php Arrays::contains([1, 2, 3], 1); // true @@ -35,10 +76,10 @@ Arrays::contains(['1', false], 1); // false ``` -every(iterable $array, callable $callback): bool .[method] ----------------------------------------------------------- +every(array $array, callable $predicate): bool .[method] +-------------------------------------------------------- -Testa se todos os elementos da matriz passam no teste implementado pela função fornecida, que tem a assinatura `function ($value, $key, array $array): bool`. +Testa se todos os elementos no array passam no teste implementado em `$predicate` com a assinatura `function ($value, $key, array $array): bool`. ```php $array = [1, 30, 39, 29, 10, 13]; @@ -46,24 +87,59 @@ $isBelowThreshold = fn($value) => $value < 40; $res = Arrays::every($array, $isBelowThreshold); // true ``` -Veja [algumas() |#some()]. +Veja [#some()]. -first(array $array): mixed .[method] ------------------------------------- +filter(array $array, callable $predicate): array .[method]{data-version:4.0.4} +------------------------------------------------------------------------------ -Devolve o primeiro item da matriz ou nulo se a matriz estiver vazia. Ele não muda o ponteiro interno ao contrário de `reset()`. +Retorna um novo array contendo todos os pares chave-valor que correspondem ao predicado especificado. O callback tem a assinatura `function ($value, int|string $key, array $array): bool`. ```php -Arrays::first([1, 2, 3]); // 1 -Arrays::first([]); // null +Arrays::filter( + ['a' => 1, 'b' => 2, 'c' => 3], + fn($v) => $v < 3, +); +// ['a' => 1, 'b' => 2] ``` +first(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------- + +Retorna o primeiro item (correspondente ao predicado, se especificado). Se tal item não existir, retorna o resultado da chamada de `$else` ou null. O parâmetro `$predicate` tem a assinatura `function ($value, int|string $key, array $array): bool`. + +Não altera o ponteiro interno, ao contrário de `reset()`. Os parâmetros `$predicate` e `$else` existem desde a versão 4.0.4. + +```php +Arrays::first([1, 2, 3]); // 1 +Arrays::first([1, 2, 3], fn($v) => $v > 2); // 3 +Arrays::first([]); // null +Arrays::first([], else: fn() => false); // false +``` + +Veja [#last()]. + + +firstKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +------------------------------------------------------------------------------------------------ + +Retorna a chave do primeiro item (correspondente ao predicado, se fornecido) ou null se tal item não existir. O predicado `$predicate` tem a assinatura `function ($value, int|string $key, array $array): bool`. + +```php +Arrays::firstKey([1, 2, 3]); // 0 +Arrays::firstKey([1, 2, 3], fn($v) => $v > 2); // 2 +Arrays::firstKey(['a' => 1, 'b' => 2]); // 'a' +Arrays::firstKey([]); // null +``` + +Veja [#lastKey()]. + + flatten(array $array, bool $preserveKeys=false): array .[method] ---------------------------------------------------------------- -Transforma a matriz multidimensional em matriz plana. +Achata um array multidimensional em um array plano. ```php $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); @@ -71,62 +147,62 @@ $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); ``` -get(array $array, string|int|array $key, mixed $default=null): mixed .[method] ------------------------------------------------------------------------------- +get(array $array, string|int|array $key, ?mixed $default=null): mixed .[method] +------------------------------------------------------------------------------- -Devoluções `$array[$key]` item. Se ele não existir, `Nette\InvalidArgumentException` é lançado, a menos que um valor padrão seja definido como terceiro argumento. +Retorna o elemento `$array[$key]`. Se não existir, lança uma exceção `Nette\InvalidArgumentException` ou, se o terceiro parâmetro `$default` for fornecido, retorna esse valor. ```php // se $array['foo'] não existir, lança uma exceção $value = Arrays::get($array, 'foo'); -// se $array['foo'] não existir, retorna 'bar +// se $array['foo'] não existir, retorna 'bar' $value = Arrays::get($array, 'foo', 'bar'); ``` -O argumento `$key` pode também ser uma matriz. +A chave `$key` também pode ser um array. ```php $array = ['color' => ['favorite' => 'red'], 5]; $value = Arrays::get($array, ['color', 'favorite']); -// retorna 'vermelho'. +// retorna 'red' ``` getRef(array &$array, string|int|array $key): mixed .[method] ------------------------------------------------------------- -Obtém referência a dado `$array[$key]`. Se o índice não existir, um novo índice é criado com valor `null`. +Obtém uma referência para o elemento especificado do array. Se o elemento não existir, ele será criado com o valor null. ```php $valueRef = & Arrays::getRef($array, 'foo'); -// retorna $array['foo'] referência +// retorna uma referência para $array['foo'] ``` -Trabalha com matrizes multidimensionais, assim como [get() |#get()]. +Assim como a função [#get()], pode trabalhar com arrays multidimensionais. ```php -$value = & Arrays::get($array, ['color', 'favorite']); -// retorna $array['color']['favorite'] referência +$value = & Arrays::getRef($array, ['color', 'favorite']); +// retorna uma referência para $array['color']['favorite'] ``` grep(array $array, string $pattern, bool $invert=false): array .[method] ------------------------------------------------------------------------ -Devolve apenas os itens da matriz, que correspondem a uma expressão regular `$pattern`. Se `$invert` é `true`, ele devolve elementos que não correspondem. A compilação Regex ou erro de tempo de execução lança `Nette\RegexpException`. +Retorna apenas os elementos do array cujo valor corresponde à expressão regular `$pattern`. Se `$invert` for `true`, retorna os elementos que não correspondem. Um erro durante a compilação ou processamento da expressão lançará uma exceção `Nette\RegexpException`. ```php $filteredArray = Arrays::grep($array, '~^\d+$~'); -// devolve apenas itens numéricos +// retorna apenas elementos do array compostos por dígitos ``` insertAfter(array &$array, string|int|null $key, array $inserted): void .[method] --------------------------------------------------------------------------------- -Insere o conteúdo da matriz `$inserted` no `$array` imediatamente após o `$key`. Se `$key` for `null` (ou não existir), ele é inserido no final. +Insere o conteúdo do array `$inserted` no array `$array` imediatamente após o elemento com a chave `$key`. Se `$key` for `null` (ou não estiver no array), ele é inserido no final. ```php $array = ['first' => 10, 'second' => 20]; @@ -138,7 +214,7 @@ Arrays::insertAfter($array, 'first', ['hello' => 'world']); insertBefore(array &$array, string|int|null $key, array $inserted): void .[method] ---------------------------------------------------------------------------------- -Insere o conteúdo da matriz `$inserted` no `$array` antes do `$key`. Se `$key` é `null` (ou não existe), ele é inserido no início. +Insere o conteúdo do array `$inserted` no array `$array` antes do elemento com a chave `$key`. Se `$key` for `null` (ou não estiver no array), ele é inserido no início. ```php $array = ['first' => 10, 'second' => 20]; @@ -150,7 +226,7 @@ Arrays::insertBefore($array, 'first', ['hello' => 'world']); invoke(iterable $callbacks, ...$args): array .[method] ------------------------------------------------------ -Convoca todos os callbacks e devolve uma série de resultados. +Invoca todos os callbacks e retorna um array de resultados. ```php $callbacks = [ @@ -166,7 +242,7 @@ $array = Arrays::invoke($callbacks, 5, 11); invokeMethod(iterable $objects, string $method, ...$args): array .[method] -------------------------------------------------------------------------- -Invoca o método em cada objeto de uma matriz e retorna uma série de resultados. +Chama um método em cada objeto no array e retorna um array de resultados. ```php $objects = ['a' => $obj1, 'b' => $obj2]; @@ -179,7 +255,7 @@ $array = Arrays::invokeMethod($objects, 'foo', 1, 2); isList(array $array): bool .[method] ------------------------------------ -Verifica se a matriz está indexada em ordem ascendente de chaves numéricas a partir de zero, também conhecida como lista. +Verifica se o array é indexado por uma série ascendente de chaves numéricas a partir de zero, também conhecido como lista. ```php Arrays::isList(['a', 'b', 'c']); // true @@ -188,21 +264,42 @@ Arrays::isList(['a' => 1, 'b' => 2]); // false ``` -last(array $array): mixed .[method] ------------------------------------ +last(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------ -Devolve o último item da matriz ou nulo se a matriz estiver vazia. Ele não muda o ponteiro interno ao contrário de `end()`. +Retorna o último item (correspondente ao predicado, se especificado). Se tal item não existir, retorna o resultado da chamada de `$else` ou null. O parâmetro `$predicate` tem a assinatura `function ($value, int|string $key, array $array): bool`. + +Não altera o ponteiro interno, ao contrário de `end()`. Os parâmetros `$predicate` e `$else` existem desde a versão 4.0.4. ```php -Arrays::last([1, 2, 3]); // 3 -Arrays::last([]); // null +Arrays::last([1, 2, 3]); // 3 +Arrays::last([1, 2, 3], fn($v) => $v < 3); // 2 +Arrays::last([]); // null +Arrays::last([], else: fn() => false); // false ``` +Veja [#first()]. + -map(iterable $array, callable $callback): array .[method] +lastKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +----------------------------------------------------------------------------------------------- + +Retorna a chave do último item (correspondente ao predicado, se fornecido) ou null se tal item não existir. O predicado `$predicate` tem a assinatura `function ($value, int|string $key, array $array): bool`. + +```php +Arrays::lastKey([1, 2, 3]); // 2 +Arrays::lastKey([1, 2, 3], fn($v) => $v < 3); // 1 +Arrays::lastKey(['a' => 1, 'b' => 2]); // 'b' +Arrays::lastKey([]); // null +``` + +Veja [#firstKey()]. + + +map(array $array, callable $transformer): array .[method] --------------------------------------------------------- -Liga para `$callback` sobre todos os elementos da matriz e retorna a matriz de valores de retorno. A chamada de retorno tem a assinatura `function ($value, $key, array $array): bool`. +Chama `$transformer` em todos os elementos do array e retorna um array dos valores retornados. O callback tem a assinatura `function ($value, $key, array $array): bool`. ```php $array = ['foo', 'bar', 'baz']; @@ -211,10 +308,24 @@ $res = Arrays::map($array, fn($value) => $value . $value); ``` +mapWithKeys(array $array, callable $transformer): array .[method] +----------------------------------------------------------------- + +Cria um novo array transformando os valores e chaves do array original. A função `$transformer` tem a assinatura `function ($value, $key, array $array): ?array{$newKey, $newValue}`. Se `$transformer` retornar `null`, o elemento é ignorado. Para os elementos mantidos, o primeiro elemento do array retornado é usado como a nova chave e o segundo elemento como o novo valor. + +```php +$array = ['a' => 1, 'b' => 2]; +$result = Arrays::mapWithKeys($array, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +Este método é útil em situações onde você precisa alterar a estrutura do array (chaves e valores simultaneamente) ou filtrar elementos durante a transformação (retornando null para elementos indesejados). + + mergeTree(array $array1, array $array2): array .[method] -------------------------------------------------------- -Recursivamente, funde dois campos. É útil, por exemplo, para fundir estruturas de árvores. Ele se comporta como o operador `+` para array, ou seja, adiciona um par chave/valor do segundo array ao primeiro e mantém o valor do primeiro array no caso de uma colisão de chave. +Mescla recursivamente dois arrays. É útil, por exemplo, para mesclar estruturas de árvore. Ao mesclar, segue as mesmas regras que o operador `+` aplicado a arrays, ou seja, adiciona pares chave/valor do segundo array ao primeiro e, em caso de colisão de chaves, mantém o valor do primeiro array. ```php $array1 = ['color' => ['favorite' => 'red'], 5]; @@ -224,13 +335,13 @@ $array = Arrays::mergeTree($array1, $array2); // $array = ['color' => ['favorite' => 'red', 'blue'], 5]; ``` -Os valores da segunda matriz são sempre anexados à primeira. O desaparecimento do valor `10` da segunda matriz pode parecer um pouco confuso. Deve-se notar que tanto este valor quanto o valor `5` in the first array have the same numeric key `0`, portanto, no campo resultante há apenas um elemento do primeiro array. +Os valores do segundo array são sempre adicionados ao final do primeiro. O desaparecimento do valor `10` do segundo array pode parecer um pouco confuso. É preciso perceber que este valor, assim como o valor `5` no primeiro array, têm a mesma chave numérica `0` atribuída, portanto, no array resultante, há apenas o elemento do primeiro array. -normalize(array $array, string $filling=null): array .[method] --------------------------------------------------------------- +normalize(array $array, ?string $filling=null): array .[method] +--------------------------------------------------------------- -Normaliza o array para o array associativo. Substituindo as chaves numéricas por seus valores, o novo valor será `$filling`. +Normaliza um array para um array associativo. As chaves numéricas são substituídas por seus valores, e o novo valor será `$filling`. ```php $array = Arrays::normalize([1 => 'first', 'a' => 'second']); @@ -243,14 +354,14 @@ $array = Arrays::normalize([1 => 'first', 'a' => 'second'], 'foobar'); ``` -pick(array &$array, string|int $key, mixed $default=null): mixed .[method] --------------------------------------------------------------------------- +pick(array &$array, string|int $key, ?mixed $default=null): mixed .[method] +--------------------------------------------------------------------------- -Devolve e remove o valor de um item de uma matriz. Se ele não existir, ele lança uma exceção, ou retorna `$default`, se fornecido. +Retorna e remove o valor de um elemento do array. Se não existir, lança uma exceção ou retorna o valor `$default`, se fornecido. ```php -$array = [1 => 'foo', null => 'bar']; -$a = Arrays::pick($array, null); +$array = [1 => 'foo', 'x' => 'bar']; +$a = Arrays::pick($array, 'x'); // $a = 'bar' $b = Arrays::pick($array, 'not-exists', 'foobar'); // $b = 'foobar' @@ -262,7 +373,7 @@ $c = Arrays::pick($array, 'not-exists'); renameKey(array &$array, string|int $oldKey, string|int $newKey): bool .[method] -------------------------------------------------------------------------------- -Renomeia uma chave. Retorna `true` se a chave foi encontrada na matriz. +Renomeia uma chave em um array. Retorna `true` se a chave foi encontrada no array. ```php $array = ['first' => 10, 'second' => 20]; @@ -274,20 +385,20 @@ Arrays::renameKey($array, 'first', 'renamed'); getKeyOffset(array $array, string|int $key): ?int .[method] ----------------------------------------------------------- -Retorna a posição indexada a zero de determinada chave de matriz. Retorna `null` se a chave não for encontrada. +Retorna a posição de uma determinada chave no array. A posição é numerada a partir de 0. Se a chave não for encontrada, a função retorna `null`. ```php $array = ['first' => 10, 'second' => 20]; -$position = Arrays::getKeyOffset($array, 'first'); // returns 0 -$position = Arrays::getKeyOffset($array, 'second'); // returns 1 -$position = Arrays::getKeyOffset($array, 'not-exists'); // returns null +$position = Arrays::getKeyOffset($array, 'first'); // retorna 0 +$position = Arrays::getKeyOffset($array, 'second'); // retorna 1 +$position = Arrays::getKeyOffset($array, 'not-exists'); // retorna null ``` -some(iterable $array, callable $callback): bool .[method] ---------------------------------------------------------- +some(array $array, callable $predicate): bool .[method] +------------------------------------------------------- -Testa se pelo menos um elemento da matriz passa no teste implementado pela callback fornecida com a assinatura `function ($value, $key, array $array): bool`. +Testa se pelo menos um elemento no array passa no teste implementado em `$predicate` com a assinatura `function ($value, $key, array $array): bool`. ```php $array = [1, 2, 3, 4]; @@ -295,13 +406,13 @@ $isEven = fn($value) => $value % 2 === 0; $res = Arrays::some($array, $isEven); // true ``` -Veja [cada() |#every()]. +Veja [#every()]. toKey(mixed $key): string|int .[method] --------------------------------------- -Converte um valor em uma chave de array, que é um número inteiro ou uma string. +Converte um valor para uma chave de array, que é um inteiro ou uma string. ```php Arrays::toKey('1'); // 1 @@ -312,19 +423,19 @@ Arrays::toKey('01'); // '01' toObject(iterable $array, object $object): object .[method] ----------------------------------------------------------- -Copia os elementos da matriz `$array` para o objeto `$object` e depois o devolve. +Copia os elementos do array `$array` para o objeto `$object`, que é então retornado. ```php $obj = new stdClass; $array = ['foo' => 1, 'bar' => 2]; -Arrays::toObject($array, $obj); // it sets $obj->foo = 1; $obj->bar = 2; +Arrays::toObject($array, $obj); // define $obj->foo = 1; $obj->bar = 2; ``` -wrap(iterable $array, string $prefix='', string $suffix=''): array .[method] ----------------------------------------------------------------------------- +wrap(array $array, string $prefix='', string $suffix=''): array .[method] +------------------------------------------------------------------------- -Ele lança cada elemento da matriz para cordelar e o encerra com `$prefix` e `$suffix`. +Converte cada item no array para uma string e o envolve com o prefixo `$prefix` e o sufixo `$suffix`. ```php $array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); @@ -332,21 +443,21 @@ $array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); ``` -ArrayHash .[#toc-arrayhash] -=========================== +ArrayHash +========= -O objeto [api:Nette\Utils\ArrayHash] é o descendente da classe genérica stdClass e o estende à capacidade de tratá-lo como uma matriz, por exemplo, acessando membros usando parênteses rectos: +O objeto [api:Nette\Utils\ArrayHash] é um descendente da classe genérica stdClass e a estende com a capacidade de tratá-lo como um array, por exemplo, acessando membros usando colchetes: ```php $hash = new Nette\Utils\ArrayHash; $hash['foo'] = 123; -$hash->bar = 456; // também funciona notação de objeto +$hash->bar = 456; // a notação de objeto também funciona $hash->foo; // 123 ``` -Você pode usar a função `count($hash)` para obter o número de elementos. +A função `count($hash)` pode ser usada para obter o número de elementos. -Você pode iterar sobre um objeto como faria com uma matriz, mesmo com uma referência: +É possível iterar sobre o objeto da mesma forma que um array, inclusive com referência: ```php foreach ($hash as $key => $value) { @@ -358,7 +469,7 @@ foreach ($hash as $key => &$value) { } ``` -As arrays existentes podem ser transformadas para `ArrayHash` usando `from()`: +Podemos transformar um array existente em um `ArrayHash` usando o método `from()`: ```php $array = ['foo' => 123, 'bar' => 456]; @@ -368,35 +479,35 @@ $hash->foo; // 123 $hash->bar; // 456 ``` -A transformação é recursiva: +A conversão é recursiva: ```php $array = ['foo' => 123, 'inner' => ['a' => 'b']]; $hash = Nette\Utils\ArrayHash::from($array); -$hash->inner; // object ArrayHash +$hash->inner; // objeto ArrayHash $hash->inner->a; // 'b' $hash['inner']['a']; // 'b' ``` -Pode ser evitado pelo segundo parâmetro: +Isso pode ser evitado com o segundo parâmetro: ```php $hash = Nette\Utils\ArrayHash::from($array, false); $hash->inner; // array ``` -Transformar de volta para a matriz: +Transformação de volta para array: ```php $array = (array) $hash; ``` -ArrayList .[#toc-arraylist] -=========================== +ArrayList +========= -[api:Nette\Utils\ArrayList] representa uma matriz linear onde os índices são apenas números inteiros subindo de 0. +[api:Nette\Utils\ArrayList] representa um array linear onde os índices são apenas inteiros crescentes a partir de 0. ```php $list = new Nette\Utils\ArrayList; @@ -407,9 +518,16 @@ $list[] = 'c'; count($list); // 3 ``` -Você pode usar a função `count($list)` para obter o número de itens. +Podemos transformar um array existente em um `ArrayList` usando o método `from()`: + +```php +$array = ['foo', 'bar']; +$list = Nette\Utils\ArrayList::from($array); +``` -Você pode iterar sobre um objeto como faria com uma matriz, mesmo com uma referência: +A função `count($list)` pode ser usada para obter o número de elementos. + +É possível iterar sobre o objeto da mesma forma que um array, inclusive com referência: ```php foreach ($list as $key => $value) { @@ -417,32 +535,25 @@ foreach ($list as $key => $value) { } foreach ($list as $key => &$value) { - $valor = 'novo valor'; + $value = 'new value'; } ``` -As arrays existentes podem ser transformadas para `ArrayList` usando `from()`: - -```php -$array = ['foo', 'bar']; -$list = Nette\Utils\ArrayList::from($array); -``` - -O acesso a chaves além dos valores permitidos lança uma exceção `Nette\OutOfRangeException`: +O acesso a chaves fora dos valores permitidos lançará uma exceção `Nette\OutOfRangeException`: ```php -echo $list[-1]; // throws Nette\OutOfRangeException -unset($list[30]); // throws Nette\OutOfRangeException +echo $list[-1]; // lança Nette\OutOfRangeException +unset($list[30]); // lança Nette\OutOfRangeException ``` -A remoção da chave resultará na renumeração dos elementos: +A remoção de uma chave causa a renumeração dos elementos: ```php unset($list[1]); // ArrayList(0 => 'a', 1 => 'c') ``` -Você pode adicionar um novo elemento ao início usando `prepend()`: +Um novo elemento pode ser adicionado ao início usando o método `prepend()`: ```php $list->prepend('d'); diff --git a/utils/pt/callback.texy b/utils/pt/callback.texy index 6ffe3532c7..9fff69071d 100644 --- a/utils/pt/callback.texy +++ b/utils/pt/callback.texy @@ -1,8 +1,8 @@ -Funções de retorno de chamada -***************************** +Trabalhando com callbacks +************************* .[perex] -[api:Nette\Utils\Callback] é uma classe estática, que contém funções para trabalhar com [callbacks PHP |https://www.php.net/manual/en/language.types.callable.php]. +[api:Nette\Utils\Callback] é uma classe estática com funções para trabalhar com [callbacks PHP |https://www.php.net/manual/en/language.types.callable.php]. Instalação: @@ -11,7 +11,7 @@ Instalação: composer require nette/utils ``` -Todos os exemplos assumem que a seguinte classe está definida: +Todos os exemplos pressupõem a criação de um alias: ```php use Nette\Utils\Callback; @@ -21,32 +21,32 @@ use Nette\Utils\Callback; check($callable, bool $syntax=false): callable .[method] -------------------------------------------------------- -Verifica se `$callable` é um retorno de chamada válido em PHP. Caso contrário, lança `Nette\InvalidArgumentException`. Se o `$syntax` for definido como verdadeiro, a função apenas verifica se `$callable` tem uma estrutura válida para ser usada como retorno de chamada, mas não verifica se a classe ou método realmente existe. Devolve `$callable`. +Verifica se a variável `$callable` é um callback válido. Caso contrário, lança `Nette\InvalidArgumentException`. Se `$syntax` for true, a função apenas verifica se `$callable` tem a estrutura de um callback, mas não verifica se a classe ou método especificado realmente existe. Retorna `$callable`. ```php -Callback::check('trim'); // sem exceção +Callback::check('trim'); // não lança exceção Callback::check(['NonExistentClass', 'method']); // lança Nette\InvalidArgumentException -Callback::check(['NonExistentClass', 'method'], true); // sem exceção -Callback::check(function () {}); // sem exceção -Callback::check(null); // joga Nette\InvalidArgumentException +Callback::check(['NonExistentClass', 'method'], true); // não lança exceção +Callback::check(function () {}); // não lança exceção +Callback::check(null); // lança Nette\InvalidArgumentException ``` toString($callable): string .[method] ------------------------------------- -Converte a chamada de retorno PHP em forma de texto. A classe ou método pode não existir. +Converte um callback PHP para forma textual. A classe ou método não precisa existir. ```php -Callback::toString('trim'); // "trim -Callback::toString(['MyClass', 'método']); // 'MyClass::método') +Callback::toString('trim'); // 'trim' +Callback::toString(['MyClass', 'method']); // 'MyClass::method' ``` toReflection($callable): ReflectionMethod|ReflectionFunction .[method] ---------------------------------------------------------------------- -Retorna reflexão para método ou função usada em PHP callback. +Retorna a reflexão para o método ou função no callback PHP. ```php $ref = Callback::toReflection('trim'); @@ -60,7 +60,7 @@ $ref = Callback::toReflection(['MyClass', 'method']); isStatic($callable): bool .[method] ----------------------------------- -Verifica se o retorno de chamada PHP é função ou método estático. +Verifica se o callback PHP é uma função ou um método estático. ```php Callback::isStatic('trim'); // true @@ -73,7 +73,7 @@ Callback::isStatic(function () {}); // false unwrap(Closure $closure): callable|array .[method] -------------------------------------------------- -Desembrulhe o fechamento criado por `Closure::fromCallable`:https://www.php.net/manual/en/closure.fromcallable.php. +Desembrulha uma Closure criada usando `Closure::fromCallable`:https://www.php.net/manual/en/closure.fromcallable.php. ```php $closure = Closure::fromCallable(['MyClass', 'method']); diff --git a/utils/pt/datetime.texy b/utils/pt/datetime.texy index 538e888c19..c68f308791 100644 --- a/utils/pt/datetime.texy +++ b/utils/pt/datetime.texy @@ -1,8 +1,8 @@ -Data e hora +Data e Hora *********** .[perex] -[api:Nette\Utils\DateTime] é uma classe que se estende nativa [php:DateTime]. +[api:Nette\Utils\DateTime] é uma classe que estende a classe nativa [php:DateTime] com funções adicionais. Instalação: @@ -11,7 +11,7 @@ Instalação: composer require nette/utils ``` -Todos os exemplos assumem que a seguinte classe está definida: +Todos os exemplos pressupõem a criação de um alias: ```php use Nette\Utils\DateTime; @@ -20,35 +20,35 @@ use Nette\Utils\DateTime; static from(string|int|\DateTimeInterface $time): DateTime .[method] -------------------------------------------------------------------- -Cria um objeto DateTime a partir de um string, UNIX timestamp, ou outro objeto [php:DateTimeInterface]. Lança um `Exception` se a data e a hora não forem válidas. +Cria um objeto DateTime a partir de uma string, timestamp UNIX ou outro objeto [php:DateTimeInterface]. Lança uma exceção `Exception` se a data e hora não forem válidas. ```php -DateTime::from(1138013640); // creates a DateTime from the UNIX timestamp with a default timezamp -DateTime::from(42); // creates a DateTime from the current time plus 42 seconds -DateTime::from('1994-02-26 04:15:32'); // creates a DateTime based on a string -DateTime::from('1994-02-26'); // create DateTime by date, time will be 00:00:00 +DateTime::from(1138013640); // cria DateTime a partir de timestamp UNIX com fuso horário padrão +DateTime::from(42); // cria DateTime a partir do tempo atual mais 42 segundos +DateTime::from('1994-02-26 04:15:32'); // cria DateTime de acordo com a string +DateTime::from('1994-02-26'); // cria DateTime de acordo com a data, a hora será 00:00:00 ``` static fromParts(int $year, int $month, int $day, int $hour=0, int $minute=0, float $second=0.0): DateTime .[method] -------------------------------------------------------------------------------------------------------------------- -Cria o objeto DateTime ou lança uma exceção `Nette\InvalidArgumentException` se a data e a hora não forem válidas. +Cria um objeto DateTime ou lança uma exceção `Nette\InvalidArgumentException` se a data e hora não forem válidas. ```php DateTime::fromParts(1994, 2, 26, 4, 15, 32); ``` -static createFromFormat(string $format, string $time, string|\DateTimeZone $timezone=null): DateTime|false .[method] --------------------------------------------------------------------------------------------------------------------- -Prolonga o [DateTime::createFromFormat() |https://www.php.net/manual/en/datetime.createfromformat.php] com a capacidade de especificar um fuso horário como um fio. +static createFromFormat(string $format, string $time, ?string|\DateTimeZone $timezone=null): DateTime|false .[method] +--------------------------------------------------------------------------------------------------------------------- +Estende [DateTime::createFromFormat()|https://www.php.net/manual/en/datetime.createfromformat.php] com a opção de especificar o fuso horário como uma string. ```php -DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); // create with custom timezone +DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); ``` modifyClone(string $modify=''): static .[method] ------------------------------------------------ -Cria uma cópia com um tempo modificado. +Cria uma cópia com a hora modificada. ```php $original = DateTime::from('2017-02-03'); $clone = $original->modifyClone('+1 day'); @@ -59,15 +59,15 @@ $clone->format('Y-m-d'); // '2017-02-04' __toString(): string .[method] ------------------------------ -Devolve a data e hora no formato `Y-m-d H:i:s`. +Retorna a data e hora no formato `Y-m-d H:i:s`. ```php echo $dateTime; // '2017-02-03 04:15:32' ``` -Implementos JsonSerializáveis .[#toc-implements-jsonserializable] ------------------------------------------------------------------ -Retorna a data e a hora no formato ISO 8601, que é usado em JavaScript, por exemplo. +implementa JsonSerializable +--------------------------- +Retorna a data e hora no formato ISO 8601, que é usado, por exemplo, em JavaScript. ```php $date = DateTime::from('2017-02-03'); echo json_encode($date); diff --git a/utils/pt/filesystem.texy b/utils/pt/filesystem.texy index 573e5b0859..888dbc6604 100644 --- a/utils/pt/filesystem.texy +++ b/utils/pt/filesystem.texy @@ -1,41 +1,43 @@ -Funções do sistema de arquivos -****************************** +Sistema de arquivos +******************* .[perex] -[api:Nette\Utils\FileSystem] é uma classe estática, que contém funções úteis para trabalhar com um sistema de arquivos. Uma vantagem sobre as funções PHP nativas é que elas lançam exceções em caso de erros. +[api:Nette\Utils\FileSystem] é uma classe com funções úteis para trabalhar com o sistema de arquivos. Uma vantagem sobre as funções nativas do PHP é que elas lançam exceções em caso de erro. +Se você precisa procurar arquivos no disco, use o [Finder|finder]. + Instalação: ```shell composer require nette/utils ``` -Os exemplos a seguir assumem que a seguinte classe está definida: +Os exemplos a seguir pressupõem a criação de um alias: ```php use Nette\Utils\FileSystem; ``` -Manipulação .[#toc-manipulation] -================================ +Manipulação +=========== copy(string $origin, string $target, bool $overwrite=true): void .[method] -------------------------------------------------------------------------- -Copia um arquivo ou um diretório inteiro. Sobrescreve arquivos e diretórios existentes por padrão. Se `$overwrite` estiver configurado para `false` e um `$target` já existir, lança uma exceção `Nette\InvalidStateException`. Lança uma exceção `Nette\IOException` por erro ocorrido. +Copia um arquivo ou um diretório inteiro. Por padrão, sobrescreve arquivos e diretórios existentes. Com o parâmetro `$overwrite` definido como `false`, lança uma exceção `Nette\InvalidStateException` se o arquivo ou diretório de destino `$target` existir. Em caso de erro, lança uma exceção `Nette\IOException`. ```php FileSystem::copy('/path/to/source', '/path/to/dest', overwrite: true); ``` -createDir(string $directory, int $mode=0777): void .[method] ------------------------------------------------------------- +createDir(string $dir, int $mode=0777): void .[method] +------------------------------------------------------ -Cria um diretório se ele não existir, incluindo os diretórios dos pais. Lança uma exceção `Nette\IOException` sobre erro ocorrido. +Cria um diretório se ele não existir, incluindo diretórios pai. Em caso de erro, lança uma exceção `Nette\IOException`. ```php FileSystem::createDir('/path/to/dir'); @@ -45,7 +47,7 @@ FileSystem::createDir('/path/to/dir'); delete(string $path): void .[method] ------------------------------------ -Apaga um arquivo ou um diretório inteiro, caso exista. Se o diretório não estiver vazio, ele apaga seu conteúdo primeiro. Lança uma exceção `Nette\IOException` sobre erro ocorrido. +Exclui um arquivo ou um diretório inteiro, se existir. Se o diretório não estiver vazio, exclui primeiro seu conteúdo. Em caso de erro, lança uma exceção `Nette\IOException`. ```php FileSystem::delete('/path/to/fileOrDir'); @@ -55,7 +57,7 @@ FileSystem::delete('/path/to/fileOrDir'); makeWritable(string $path, int $dirMode=0777, int $fileMode=0666): void .[method] --------------------------------------------------------------------------------- -Define as permissões de arquivo para `$fileMode` ou as permissões de diretório para `$dirMode`. Atravessa recursivamente e estabelece as permissões também em todo o conteúdo do diretório. +Define as permissões do arquivo para `$fileMode` ou do diretório para `$dirMode`. Percorre recursivamente e define as permissões para todo o conteúdo do diretório também. ```php FileSystem::makeWritable('/path/to/fileOrDir'); @@ -65,7 +67,7 @@ FileSystem::makeWritable('/path/to/fileOrDir'); open(string $path, string $mode): resource .[method] ---------------------------------------------------- -Abre arquivo e devolve recurso. O parâmetro `$mode` funciona da mesma forma que a função nativa `fopen()`:https://www.php.net/manual/en/function.fopen.php. Se ocorrer um erro, ele eleva a exceção `Nette\IOException`. +Abre um arquivo e retorna um resource. O parâmetro `$mode` funciona da mesma forma que na função nativa `fopen()`:https://www.php.net/manual/en/function.fopen.php. Em caso de erro, lança uma exceção `Nette\IOException`. ```php $res = FileSystem::open('/path/to/file', 'r'); @@ -75,7 +77,7 @@ $res = FileSystem::open('/path/to/file', 'r'); read(string $file): string .[method] ------------------------------------ -Lê o conteúdo de um `$file`. Abre uma exceção `Nette\IOException` sobre erro ocorrido. +Retorna o conteúdo do arquivo `$file`. Em caso de erro, lança uma exceção `Nette\IOException`. ```php $content = FileSystem::read('/path/to/file'); @@ -85,14 +87,13 @@ $content = FileSystem::read('/path/to/file'); readLines(string $file, bool $stripNewLines=true): \Generator .[method] ----------------------------------------------------------------------- -Lê o conteúdo do arquivo linha por linha. Ao contrário da função nativa `file()`, ela não lê o arquivo inteiro na memória, mas o lê continuamente, de modo que arquivos maiores do que a memória disponível possam ser lidos. O `$stripNewLines` especifica se deve retirar os caracteres de quebra de linha `\r` e `\n`. -No caso de um erro, ele levanta uma exceção `Nette\IOException`. +Lê o conteúdo do arquivo linha por linha. Ao contrário da função nativa `file()`, não carrega o arquivo inteiro na memória, mas o lê continuamente, tornando possível ler arquivos maiores que a memória disponível. `$stripNewLines` indica se os caracteres de fim de linha `\r` e `\n` devem ser removidos. Em caso de erro, lança uma exceção `Nette\IOException`. ```php $lines = FileSystem::readLines('/path/to/file'); foreach ($lines as $lineNum => $line) { - echo "Line $lineNum: $line\n"; + echo "Linha $lineNum: $line\n"; } ``` @@ -100,7 +101,7 @@ foreach ($lines as $lineNum => $line) { rename(string $origin, string $target, bool $overwrite=true): void .[method] ---------------------------------------------------------------------------- -Renomeia ou move um arquivo ou um diretório especificado por `$origin` para `$target`. Sobrescreve arquivos e diretórios existentes por padrão. Se `$overwrite` estiver definido como `false` e `$target` já existe, lança uma exceção `Nette\InvalidStateException`. Lança uma exceção `Nette\IOException` por erro ocorrido. +Renomeia ou move o arquivo ou diretório `$origin`. Por padrão, sobrescreve arquivos e diretórios existentes. Com o parâmetro `$overwrite` definido como `false`, lança uma exceção `Nette\InvalidStateException` se o arquivo ou diretório de destino `$target` existir. Em caso de erro, lança uma exceção `Nette\IOException`. ```php FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); @@ -110,21 +111,21 @@ FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); write(string $file, string $content, int $mode=0666): void .[method] -------------------------------------------------------------------- -Escreve o `$content` para um `$file`. Lança uma exceção `Nette\IOException` sobre erro ocorrido. +Escreve a string `$content` no arquivo `$file`. Em caso de erro, lança uma exceção `Nette\IOException`. ```php FileSystem::write('/path/to/file', $content); ``` -Caminhos .[#toc-paths] -====================== +Caminhos +======== isAbsolute(string $path): bool .[method] ---------------------------------------- -Determina se o `$path` é absoluto. +Verifica se o caminho `$path` é absoluto. ```php FileSystem::isAbsolute('../backup'); // false @@ -135,7 +136,7 @@ FileSystem::isAbsolute('C:/backup'); // true joinPaths(string ...$segments): string .[method] ------------------------------------------------ -Junta-se a todos os segmentos do caminho e normaliza o resultado. +Junta todos os segmentos do caminho e normaliza o resultado. ```php FileSystem::joinPaths('a', 'b', 'file.txt'); // 'a/b/file.txt' @@ -146,7 +147,7 @@ FileSystem::joinPaths('/a/', '/../b'); // '/b' normalizePath(string $path): string .[method] --------------------------------------------- -Normaliza `..` e `.` e separadores de diretórios no caminho. +Normaliza `..` e `.` e os separadores de diretório no caminho para os do sistema. ```php FileSystem::normalizePath('/file/.'); // '/file/' @@ -159,7 +160,7 @@ FileSystem::normalizePath('file/../../bar'); // '/../bar' unixSlashes(string $path): string .[method] ------------------------------------------- -Converte cortes para `/` usados em sistemas Unix. +Converte barras para `/`, usadas em sistemas Unix. ```php $path = FileSystem::unixSlashes($path); @@ -169,8 +170,45 @@ $path = FileSystem::unixSlashes($path); platformSlashes(string $path): string .[method] ----------------------------------------------- -Converte cortes em caracteres específicos da plataforma atual, ou seja, `\` no Windows e `/` em outros lugares. +Converte barras para caracteres específicos da plataforma atual, ou seja, `\` no Windows e `/` em outros lugares. ```php $path = FileSystem::platformSlashes($path); ``` + + +resolvePath(string $basePath, string $path): string .[method]{data-version:4.0.6} +--------------------------------------------------------------------------------- + +Deriva o caminho final a partir do caminho `$path` em relação ao diretório base `$basePath`. Caminhos absolutos (`/foo`, `C:/foo`) são deixados inalterados (apenas normaliza as barras), caminhos relativos são anexados ao caminho base. + +```php +// No Windows, as barras na saída seriam invertidas (\) +FileSystem::resolvePath('/base/dir', '/abs/path'); // '/abs/path' +FileSystem::resolvePath('/base/dir', 'rel'); // '/base/dir/rel' +FileSystem::resolvePath('base/dir', '../file.txt'); // 'base/file.txt' +FileSystem::resolvePath('base', ''); // 'base' +``` + + +Acesso estático vs. não estático +================================ + +Para poder substituir facilmente a classe por outra (mock) para fins de teste, por exemplo, use-a de forma não estática: + +```php +class AnyClassUsingFileSystem +{ + public function __construct( + private FileSystem $fileSystem, + ) { + } + + public function readConfig(): string + { + return $this->fileSystem->read(/* ... */); + } + + ... +} +``` diff --git a/utils/pt/finder.texy b/utils/pt/finder.texy index 94e1cd48b0..0ff5db7a4c 100644 --- a/utils/pt/finder.texy +++ b/utils/pt/finder.texy @@ -1,8 +1,8 @@ -Finder: Busca de arquivos -************************* +Finder: procurando arquivos +*************************** .[perex] -Precisa encontrar arquivos que combinem com uma determinada máscara? O Finder pode lhe ajudar. É uma ferramenta versátil e rápida para navegar na estrutura do diretório. +Precisa encontrar arquivos que correspondam a uma determinada máscara? O Finder pode ajudá-lo com isso. É uma ferramenta versátil e rápida para navegar na estrutura de diretórios. Instalação: @@ -11,17 +11,17 @@ Instalação: composer require nette/utils ``` -Os exemplos assumem que um pseudônimo foi criado: +Os exemplos pressupõem a criação de um alias: ```php use Nette\Utils\Finder; ``` -Usando .[#toc-using] --------------------- +Uso +--- -Primeiro, vamos ver como você pode usar [api:Nette\Utils\Finder] para listar os nomes dos arquivos com as extensões `.txt` e `.md` no diretório atual: +Primeiro, vamos mostrar como você pode usar [api:Nette\Utils\Finder] para listar os nomes dos arquivos com as extensões `.txt` e `.md` no diretório atual: ```php foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { @@ -29,29 +29,28 @@ foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { } ``` -O diretório padrão para a busca é o diretório atual, mas você pode mudá-lo usando os métodos [in() ou from() |#Where to search?]. -A variável `$file` é uma instância da classe [FileInfo |#FileInfo] com muitos métodos úteis. A chave `$name` contém o caminho para o arquivo como uma string. +O diretório padrão para busca é o diretório atual, mas você pode alterá-lo usando os métodos [in() ou from() |#Onde procurar]. A variável `$file` é uma instância da classe [#FileInfo] com muitos métodos úteis. A chave `$name` contém o caminho para o arquivo como uma string. -O que pesquisar? .[#toc-what-to-search-for] -------------------------------------------- +O que procurar? +--------------- -Além do método `findFiles()`, há também `findDirectories()`, que busca somente diretórios, e `find()`, que busca ambos. Estes métodos são estáticos, portanto, podem ser chamados sem criar uma instância. O parâmetro da máscara é opcional, se você não especificá-lo, tudo é pesquisado. +Além do método `findFiles()`, existem também `findDirectories()`, que busca apenas diretórios, e `find()`, que busca ambos. Esses métodos são estáticos, portanto, podem ser chamados sem criar uma instância. O parâmetro com a máscara é opcional; se você não o fornecer, tudo será pesquisado. ```php foreach (Finder::find() as $file) { - echo $file; // agora todos os arquivos e diretórios estão listados + echo $file; // agora todos os arquivos e diretórios serão listados } ``` -Use os métodos `files()` e `directories()` para adicionar o que mais procurar. Os métodos podem ser chamados repetidamente e um conjunto de máscaras pode ser fornecido como parâmetro: +Usando os métodos `files()` e `directories()`, você pode especificar o que mais procurar. Os métodos podem ser chamados repetidamente, e um array de máscaras também pode ser fornecido como parâmetro: ```php Finder::findDirectories('vendor') // todos os diretórios ->files(['*.php', '*.phpt']); // mais todos os arquivos PHP ``` -Uma alternativa aos métodos estáticos é criar uma instância usando `new Finder` (o objeto fresco criado desta forma não procura por nada) e especificar o que procurar usando `files()` e `directories()`: +Uma alternativa aos métodos estáticos é criar uma instância usando `new Finder` (um objeto recém-criado assim não procura nada) e especificar o que procurar usando `files()` e `directories()`: ```php (new Finder) @@ -59,66 +58,68 @@ Uma alternativa aos métodos estáticos é criar uma instância usando `new Find ->files('*.php'); // mais todos os arquivos PHP ``` -Você pode usar [wildcards |#wildcards] `*`, `**`, `?` and `[...]` na máscara. Você pode até mesmo especificar em diretórios, por exemplo `src/*.php` procurará por todos os arquivos PHP no diretório `src`. +Na máscara, você pode usar [#caracteres curinga] `*`, `**`, `?` e `[...]`. Você pode até especificar diretórios, por exemplo, `src/*.php` encontrará todos os arquivos PHP no diretório `src`. +Links simbólicos também são considerados diretórios ou arquivos. -Onde pesquisar? .[#toc-where-to-search] ---------------------------------------- -O diretório padrão de busca é o diretório atual. Você pode mudar isto usando os métodos `in()` e `from()`. Como você pode ver pelos nomes dos métodos, `in()` pesquisa somente o diretório atual, enquanto `from()` pesquisa também seus subdiretórios (recursivamente). Se você quiser pesquisar recursivamente no diretório atual, você pode usar `from('.')`. +Onde procurar? +-------------- -Estes métodos podem ser chamados várias vezes ou você pode passar vários caminhos para eles como matrizes, então os arquivos serão pesquisados em todos os diretórios. Se um dos diretórios não existir, um `Nette\UnexpectedValueException` é lançado. +O diretório padrão para busca é o diretório atual. Você pode alterá-lo usando os métodos `in()` e `from()`. Como os nomes dos métodos sugerem, `in()` busca apenas no diretório especificado, enquanto `from()` busca também em seus subdiretórios (recursivamente). Se você quiser buscar recursivamente no diretório atual, pode usar `from('.')`. + +Esses métodos podem ser chamados várias vezes ou podem receber vários caminhos como um array; os arquivos serão então pesquisados em todos os diretórios. Se algum dos diretórios não existir, uma exceção `Nette\UnexpectedValueException` será lançada. ```php Finder::findFiles('*.php') - ->in(['src', 'tests']) // pesquisa diretamente em src/ e testes/ - ->from('vendor'); // pesquisas também em vendor/ subdiretórios + ->in(['src', 'tests']) // busca diretamente em src/ e tests/ + ->from('vendor'); // busca também nos subdiretórios de vendor/ ``` -Os caminhos relativos são relativos ao diretório atual. Naturalmente, caminhos absolutos também podem ser especificados: +Caminhos relativos são relativos ao diretório atual. Claro, caminhos absolutos também podem ser fornecidos: ```php Finder::findFiles('*.php') ->in('/var/www/html'); ``` -Wildcards [wildcards |#wildcards] `*`, `**`, `?` can be used in the path. For example, you can use the path `src/*/*.php` para procurar por todos os arquivos PHP nos diretórios de segundo nível no diretório `src`. O personagem `**`, chamado globstar, é um poderoso trunfo porque permite que você procure também nos subdiretórios: use `src/**/tests/*.php` para procurar todos os arquivos PHP no diretório `tests` localizado em `src` ou em qualquer um de seus subdiretórios. +É possível usar [#caracteres curinga] `*`, `**`, `?` no caminho. Por exemplo, com o caminho `src/*/*.php`, você pode procurar todos os arquivos PHP nos diretórios de segundo nível dentro do diretório `src`. O caractere `**`, chamado globstar, é um trunfo poderoso, pois permite buscar também em subdiretórios: com `src/**/tests/*.php`, você procura todos os arquivos PHP no diretório `tests` localizado em `src` ou em qualquer um de seus subdiretórios. -Por outro lado, os wildcards `[...]` Os caracteres não são suportados no caminho, ou seja, eles não têm nenhum significado especial para evitar comportamentos indesejados caso você procure por exemplo `in(__DIR__)` e por acaso `[]` caracteres aparecem no caminho. +Por outro lado, os caracteres curinga `[...]` não são suportados no caminho, ou seja, não têm significado especial, para evitar comportamento indesejado caso você procure, por exemplo, `in(__DIR__)` e os caracteres `[]` apareçam acidentalmente no caminho. -Ao pesquisar arquivos e diretórios em profundidade, o diretório pai é retornado primeiro e depois os arquivos contidos nele, que podem ser revertidos com `childFirst()`. +Ao buscar arquivos e diretórios em profundidade, o diretório pai é retornado primeiro, seguido pelos arquivos contidos nele. Isso pode ser invertido usando `childFirst()`. -Wildcards .[#toc-wildcards] ---------------------------- +Caracteres curinga +------------------ -Você pode usar vários caracteres especiais na máscara: +Na máscara, você pode usar vários caracteres especiais: -- `*` - replaces any number of arbitrary characters (except `/`) -- `**` - substitui qualquer número de caracteres arbitrários, incluindo `/` (ou seja, pode ser pesquisado em vários níveis) -- `?` - replaces one arbitrary character (except `/`) -- `[a-z]` - substitui um caractere da lista de caracteres entre parênteses rectos -- `[!a-z]` - substitui um caractere fora da lista de caracteres entre parênteses rectos +- `*` - substitui qualquer número de caracteres (exceto `/`) +- `**` - substitui qualquer número de caracteres, incluindo `/` (ou seja, permite busca multinível) +- `?` - substitui um único caractere qualquer (exceto `/`) +- `[a-z]` - substitui um único caractere da lista de caracteres entre colchetes +- `[!a-z]` - substitui um único caractere fora da lista de caracteres entre colchetes Exemplos de uso: -- `img/?.png` - arquivos com o nome de uma única letra `0.png`, `1.png`, `x.png`, etc. -- `logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log` - arquivos de log no formato `YYYY-MM-DD` -- `src/**/tests/*` - arquivos no diretório `src/tests`, `src/foo/tests`, `src/foo/bar/tests` e assim por diante. +- `img/?.png` - arquivos com nomes de uma letra `0.png`, `1.png`, `x.png`, etc. +- `logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log` - logs no formato `YYYY-MM-DD` +- `src/**/tests/*` - arquivos no diretório `src/tests`, `src/foo/tests`, `src/foo/bar/tests`, e assim por diante. - `docs/**.md` - todos os arquivos com a extensão `.md` em todos os subdiretórios do diretório `docs` -Excluindo .[#toc-excluding] ---------------------------- +Exclusão +-------- -Use o método `exclude()` para excluir arquivos e diretórios das buscas. Você especifica uma máscara que o arquivo não deve corresponder. Exemplo de busca de arquivos `*.txt`, exceto aqueles que contêm a letra `X` no nome: +Usando o método `exclude()`, você pode excluir arquivos e diretórios da busca. Você fornece uma máscara que o arquivo não deve corresponder. Exemplo de busca por arquivos `*.txt`, exceto aqueles que contêm a letra `X` no nome: ```php Finder::findFiles('*.txt') ->exclude('*X*'); ``` -Use `exclude()` para pular os subdiretórios navegados: +Para pular a travessia de subdiretórios, use `exclude()`: ```php Finder::findFiles('*.php') @@ -127,12 +128,12 @@ Finder::findFiles('*.php') ``` -Filtragem .[#toc-filtering] ---------------------------- +Filtragem +--------- O Finder oferece vários métodos para filtrar os resultados (ou seja, reduzi-los). Você pode combiná-los e chamá-los repetidamente. -Use `size()` para filtrar por tamanho de arquivo. Desta forma, encontramos arquivos com tamanhos entre 100 e 200 bytes: +Usando `size()`, filtramos pelo tamanho do arquivo. Desta forma, encontramos arquivos com tamanho entre 100 e 200 bytes: ```php Finder::findFiles('*.php') @@ -140,7 +141,7 @@ Finder::findFiles('*.php') ->size('<=', 200); ``` -O método `date()` filtra até a data em que o arquivo foi modificado pela última vez. Os valores podem ser absolutos ou relativos à data e hora atual, por exemplo, é assim que se encontram os arquivos modificados nas duas últimas semanas: +O método `date()` filtra pela data da última modificação do arquivo. Os valores podem ser absolutos ou relativos à data e hora atuais; por exemplo, desta forma encontramos arquivos modificados nas últimas duas semanas: ```php Finder::findFiles('*.php') @@ -150,9 +151,9 @@ Finder::findFiles('*.php') Ambas as funções entendem os operadores `>`, `>=`, `<`, `<=`, `=`, `!=`, `<>`. -O Finder também permite filtrar resultados usando funções personalizadas. A função recebe um objeto `Nette\Utils\FileInfo` como parâmetro e deve retornar `true` para incluir o arquivo nos resultados. +O Finder também permite filtrar resultados usando funções personalizadas. A função recebe um objeto `Nette\Utils\FileInfo` como parâmetro e deve retornar `true` para que o arquivo seja incluído nos resultados. -Exemplo: procure por arquivos PHP que contenham a seqüência `Nette` (não sensível a maiúsculas e minúsculas): +Exemplo: busca por arquivos PHP que contêm a string `Nette` (sem diferenciar maiúsculas de minúsculas): ```php Finder::findFiles('*.php') @@ -160,51 +161,51 @@ Finder::findFiles('*.php') ``` -Filtragem de profundidade .[#toc-depth-filtering] -------------------------------------------------- +Filtragem em profundidade +------------------------- -Ao realizar buscas recorrentes, você pode definir a profundidade máxima de rastejamento usando o método `limitDepth()`. Se você definir `limitDepth(1)`, somente os primeiros subdiretórios são rastreados, `limitDepth(0)` desativa a profundidade de rastreamento, e um valor de -1 cancela o limite. +Ao buscar recursivamente, você pode definir a profundidade máxima de travessia usando o método `limitDepth()`. Se você definir `limitDepth(1)`, apenas os primeiros subdiretórios são percorridos; `limitDepth(0)` desativa a travessia em profundidade, e um valor de -1 remove o limite. -O Finder permite que você use suas próprias funções para decidir qual diretório entrar quando estiver navegando. A função recebe um objeto `Nette\Utils\FileInfo` como parâmetro e deve retornar `true` para entrar no diretório: +O Finder permite decidir em qual diretório entrar durante a travessia usando funções personalizadas. A função recebe um objeto `Nette\Utils\FileInfo` como parâmetro e deve retornar `true` para entrar no diretório: ```php Finder::findFiles('*.php') - ->descentFilter($file->getBasename() !== 'temp'); + ->descentFilter(fn($file) => $file->getBasename() !== 'temp'); ``` -Ordenação .[#toc-sorting] -------------------------- +Ordenação +--------- -O Finder também oferece várias funções para classificar os resultados. +O Finder também oferece várias funções para ordenar os resultados. -O método `sortByName()` ordena os resultados por nome de arquivo. A ordenação é natural, ou seja, trata corretamente os números nos nomes e retorna, por exemplo, `foo1.txt` antes de `foo10.txt`. +O método `sortByName()` ordena os resultados pelos nomes dos arquivos. A ordenação é natural, o que significa que lida corretamente com números nos nomes e retorna, por exemplo, `foo1.txt` antes de `foo10.txt`. -O Finder também permite que você faça a classificação usando uma função personalizada. Ele toma dois objetos `Nette\Utils\FileInfo` como parâmetros e deve retornar o resultado da comparação com o operador `<=>`ou seja, `-1`, `0` nebo `1`. Por exemplo, é assim que classificamos os arquivos por tamanho: +O Finder também permite ordenar usando uma função personalizada. Ela recebe dois objetos `Nette\Utils\FileInfo` como parâmetros e deve retornar o resultado da comparação usando o operador `<=>`, ou seja, `-1`, `0` ou `1`. Por exemplo, desta forma ordenamos os arquivos por tamanho: ```php $finder->sortBy(fn($a, $b) => $a->getSize() <=> $b->getSize()); ``` -Múltiplas buscas diferentes .[#toc-multiple-different-searches] ---------------------------------------------------------------- +Múltiplas buscas diferentes +--------------------------- -Se você precisar encontrar vários arquivos diferentes em locais diferentes ou que atendam a critérios diferentes, use o método `append()`. Ele retorna um novo objeto `Finder` para que você possa fazer chamadas em cadeia pelo método : +Se você precisa encontrar vários arquivos diferentes em locais diferentes ou que atendam a critérios diferentes, use o método `append()`. Ele retorna um novo objeto `Finder`, permitindo encadear chamadas de método: ```php -($finder = new Finder) // armazenar o primeiro Finder na variável $finder! - ->files('*.php') // procure por arquivos *.php em src/ +($finder = new Finder) // armazenamos o primeiro Finder na variável $finder! + ->files('*.php') // em src/ procuramos arquivos *.php ->from('src') ->append() - ->files('*.md') // em docs/ procurar por arquivos *.md + ->files('*.md') // em docs/ procuramos arquivos *.md ->from('docs') ->append() - ->files('*.json'); // na pasta atual procurar por arquivos *.json + ->files('*.json'); // na pasta atual procuramos arquivos *.json ``` -Alternativamente, você pode usar o método `append()` para adicionar um arquivo específico (ou um conjunto de arquivos). Em seguida, ele retorna o mesmo objeto `Finder`: +Alternativamente, o método `append()` pode ser usado para adicionar um arquivo específico (ou um array de arquivos). Ele então retorna o mesmo objeto `Finder`: ```php $finder = Finder::findFiles('*.txt') @@ -212,12 +213,12 @@ $finder = Finder::findFiles('*.txt') ``` -FileInfo .[#toc-fileinfo] -------------------------- +FileInfo +-------- -[Nette\Utils\FileInfo |api:] é uma classe que representa um arquivo ou diretório nos resultados da busca. É uma extensão da classe [SplFileInfo |php:SplFileInfo] que fornece informações como tamanho do arquivo, data da última modificação, nome, caminho, etc. +[Nette\Utils\FileInfo |api:] é uma classe que representa um arquivo ou diretório nos resultados da busca. É uma extensão da classe [SplFileInfo |php:SplFileInfo], que fornece informações como tamanho do arquivo, data da última modificação, nome, caminho, etc. -Além disso, fornece métodos para retornar caminhos relativos, o que é útil quando se navega em profundidade: +Além disso, fornece métodos para retornar o caminho relativo, o que é útil ao percorrer em profundidade: ```php foreach (Finder::findFiles('*.jpg')->from('.') as $file) { @@ -226,7 +227,7 @@ foreach (Finder::findFiles('*.jpg')->from('.') as $file) { } ``` -Você também tem métodos para ler e escrever o conteúdo de um arquivo: +Você também tem métodos disponíveis para ler e escrever o conteúdo do arquivo: ```php foreach ($finder as $file) { @@ -237,12 +238,12 @@ foreach ($finder as $file) { ``` -Retorno de Resultados como um Array .[#toc-returning-results-as-an-array] -------------------------------------------------------------------------- +Retornando resultados como um array +----------------------------------- -Como visto nos exemplos, o Finder implementa a interface `IteratorAggregate`, assim você pode usar `foreach` para pesquisar os resultados. Ele é programado para que os resultados sejam carregados apenas enquanto você navega, assim, se você tiver um grande número de arquivos, ele não espera que todos eles sejam lidos. +Como visto nos exemplos, o Finder implementa a interface `IteratorAggregate`, então você pode usar `foreach` para percorrer os resultados. Ele é programado de forma que os resultados sejam carregados apenas durante a iteração, então, se você tiver um grande número de arquivos, não há espera até que todos sejam lidos. -Você também pode ter os resultados retornados como um conjunto de `Nette\Utils\FileInfo` objetos, usando o método `collect()`. A matriz não é associativa, mas numérica. +Você também pode ter os resultados retornados como um array de objetos `Nette\Utils\FileInfo` usando o método `collect()`. O array não é associativo, mas numérico. ```php $array = $finder->findFiles('*.php')->collect(); diff --git a/utils/pt/floats.texy b/utils/pt/floats.texy index 7ef9b4f49b..55048d32c2 100644 --- a/utils/pt/floats.texy +++ b/utils/pt/floats.texy @@ -1,8 +1,8 @@ -Funções dos flutuadores -*********************** +Trabalhando com floats +********************** .[perex] -[api:Nette\Utils\Floats] é uma classe estática com funções úteis para comparar números de flutuadores. +[api:Nette\Utils\Floats] é uma classe estática com funções úteis para comparar números de ponto flutuante. Instalação: @@ -11,40 +11,42 @@ Instalação: composer require nette/utils ``` -Todos os exemplos assumem que a seguinte classe está definida: +Todos os exemplos pressupõem a criação de um alias: ```php use Nette\Utils\Floats; ``` -Motivação .[#toc-motivation] -============================ +Motivação +========= -Querendo saber para que serve uma classe de comparação de flutuadores? Você pode usar operadores `<`, `>`, `===`, você acha. -Isto não é inteiramente verdade. O que você acha que irá imprimir este código? +Você pode se perguntar, por que uma classe para comparar floats? Afinal, posso usar os operadores `<`, `>`, `===` e está tudo resolvido. Não é bem verdade. O que você acha que este código imprimirá? ```php $a = 0.1 + 0.2; $b = 0.3; -echo $a === $b ? 'same' : 'not same'; +echo $a === $b ? 'iguais' : 'não iguais'; ``` -Se você executar o código, alguns de vocês ficarão surpresos que o programa tenha impresso `not same`. +Se você executar o código, alguns de vocês certamente ficarão surpresos que o programa imprimiu `não iguais`. -As operações matemáticas com números de flutuação causam erros devido à conversão entre sistemas decimais e binários. Por exemplo, `0.1 + 0.2` equivale a `0.300000000000000044…`. Portanto, ao comparar as bóias flutuantes, devemos tolerar uma pequena diferença a partir de uma determinada casa decimal. +Operações matemáticas com números decimais podem levar a erros devido à conversão entre os sistemas decimal e binário. Por exemplo, `0.1 + 0.2` resulta em `0.300000000000000044…`. Portanto, ao comparar, devemos tolerar uma pequena diferença a partir de uma certa casa decimal. -E é isso que a classe `Floats` está fazendo. A comparação a seguir irá funcionar como esperado: +E é exatamente isso que a classe `Floats` faz. A seguinte comparação agora funcionará como esperado: ```php -echo Floats::areEqual($a, $b) ? 'same' : 'not same'; // same +echo Floats::areEqual($a, $b) ? 'iguais' : 'não iguais'; // iguais ``` -Ao tentar comparar `NAN`, ele lança uma exceção `\LogicException`. +Tentar comparar `NAN` lançará uma exceção `\LogicException`. +.[tip] +A classe `Floats` tolera diferenças menores que `1e-10`. Se você precisar trabalhar com maior precisão, use a biblioteca BCMath. -Comparação de flutuadores .[#toc-float-comparison] -================================================== + +Comparando floats +================= areEqual(float $a, float $b): bool .[method] @@ -104,25 +106,25 @@ Floats::isGreaterThanOrEqualTo(10.2, 10.2); // true compare(float $a, float $b): int .[method] ------------------------------------------ -Se `$a` < `$b`, retorna `-1`, se forem iguais retorna `0` and if `$a` > `$b` retorna `1`. +Se `$a` < `$b`, retorna `-1`; se forem iguais, retorna `0`; e se `$a` > `$b`, retorna `1`. -Ele pode ser usado, por exemplo, com a função `usort`. +Pode ser usado, por exemplo, com a função `usort`. ```php $arr = [1, 5, 2, -3.5]; -usort($arr, [Float::class, 'compare']); -// $arr is [-3.5, 1, 2, 5] +usort($arr, [Floats::class, 'compare']); +// $arr agora é [-3.5, 1, 2, 5] ``` -Funções dos auxiliares .[#toc-helpers-functions] -================================================ +Funções auxiliares +================== isZero(float $value): bool .[method] ------------------------------------ -Retorna `true` se o valor for zero. +Retorna `true` se o valor for igual a zero. ```php Floats::isZero(0.0); // true @@ -133,7 +135,7 @@ Floats::isZero(0); // true isInteger(float $value): bool .[method] --------------------------------------- -Retorna `true` se o valor for inteiro. +Retorna `true` se o valor for um número inteiro. ```php Floats::isInteger(0); // true diff --git a/utils/pt/helpers.texy b/utils/pt/helpers.texy index ba2fbf2780..e4ef6490d9 100644 --- a/utils/pt/helpers.texy +++ b/utils/pt/helpers.texy @@ -1,5 +1,5 @@ -Funções de ajuda -**************** +Funções auxiliares +****************** .[perex] [api:Nette\Utils\Helpers] é uma classe estática com funções úteis. @@ -11,7 +11,7 @@ Instalação: composer require nette/utils ``` -Todos os exemplos assumem que a seguinte classe está definida: +Todos os exemplos pressupõem a criação de um alias: ```php use Nette\Utils\Helpers; @@ -21,7 +21,7 @@ use Nette\Utils\Helpers; capture(callable $cb): string .[method] --------------------------------------- -Executa uma chamada de retorno e retorna a saída capturada como um fio. +Executa o callback e retorna a saída capturada como uma string. ```php $res = Helpers::capture(function () use ($template) { @@ -33,7 +33,7 @@ $res = Helpers::capture(function () use ($template) { clamp(int|float $value, int|float $min, int|float $max): int|float .[method] ---------------------------------------------------------------------------- -Retorna o valor fixado para a faixa inclusiva de min e max. +Limita um valor ao intervalo inclusivo fornecido de min e max. ```php Helpers::clamp($level, 0, 255); @@ -43,8 +43,7 @@ Helpers::clamp($level, 0, 255); compare(mixed $left, string $operator, mixed $right): bool .[method] -------------------------------------------------------------------- -Compara dois valores da mesma forma que o PHP. Ele distingue entre os operadores `>`, `>=`, `<`, `<=`, `=`, `==`, `!=`, `===`, , `!==`, `<>`. -A função é útil em situações em que o operador é variável. +Compara dois valores da mesma forma que o PHP faz. Distingue os operadores `>`, `>=`, `<`, `<=`, `=`, `==`, `===`, `!=`, `!==`, `<>`. A função é útil em situações onde o operador é variável. ```php Helpers::compare(10, '<', 20); // true @@ -54,7 +53,7 @@ Helpers::compare(10, '<', 20); // true falseToNull(mixed $value): mixed .[method] ------------------------------------------ -Converte `false` para `null`, não altera outros valores. +Converte `false` para `null`, outros valores permanecem inalterados. ```php Helpers::falseToNull(false); // null @@ -65,7 +64,7 @@ Helpers::falseToNull(123); // 123 getLastError(): string .[method] -------------------------------- -Retorna o último erro PHP ocorrido ou uma string vazia se nenhum erro tiver ocorrido. Ao contrário de `error_get_last()`, não é afetado pela diretiva PHP `html_errors` e sempre retorna texto, não HTML. +Retorna o último erro no PHP ou uma string vazia se nenhum erro ocorreu. Ao contrário de `error_get_last()`, não é afetado pela diretiva PHP `html_errors` e sempre retorna texto, não HTML. ```php Helpers::getLastError(); @@ -75,13 +74,13 @@ Helpers::getLastError(); getSuggestion(string[] $possibilities, string $value): ?string .[method] ------------------------------------------------------------------------ -Procura um fio de `$possibilities` que é mais parecido com `$value`, mas não o mesmo. Suporta apenas codificações de 8 bits. +A partir das opções fornecidas `$possibilities`, procura a string que é mais semelhante a `$value`, mas não idêntica. Suporta apenas codificação de 8 bits. -É útil se uma determinada opção não for válida e quisermos sugerir ao usuário uma opção semelhante (mas diferente, portanto a mesma seqüência é ignorada). Desta forma, a Nette cria mensagens `did you mean ...?`. +É útil quando uma determinada opção não é válida e queremos sugerir uma semelhante ao usuário (mas diferente, por isso a string idêntica é ignorada). Desta forma, o Nette cria as mensagens `você quis dizer ...?`. ```php $items = ['foo', 'bar', 'baz']; Helpers::getSuggestion($items, 'fo'); // 'foo' Helpers::getSuggestion($items, 'barr'); // 'bar' -Helpers::getSuggestion($items, 'baz'); // 'bar', ne 'baz' +Helpers::getSuggestion($items, 'baz'); // 'bar', não 'baz' ``` diff --git a/utils/pt/html-elements.texy b/utils/pt/html-elements.texy index 670d28deba..3b7ebe5929 100644 --- a/utils/pt/html-elements.texy +++ b/utils/pt/html-elements.texy @@ -2,15 +2,15 @@ Elementos HTML ************** .[perex] -A classe [api:Nette\Utils\Html] é uma ajuda para gerar código HTML que evita a vulnerabilidade de Cross Site Scripting (XSS). +A classe [api:Nette\Utils\Html] é um auxiliar para gerar código HTML que previne a vulnerabilidade de Cross Site Scripting (XSS). -Ela funciona de tal forma que seus objetos representam elementos HTML, nós definimos seus parâmetros e os deixamos renderizar: +Funciona de tal forma que seus objetos representam elementos HTML, aos quais definimos parâmetros e os deixamos renderizar: ```php -$el = Html::el('img'); // cria elemento +$el = Html::el('img'); // cria o elemento $el->src = 'image.jpg'; // define o atributo src -echo $el; // imprime '' +echo $el; // imprime '' ``` Instalação: @@ -19,29 +19,29 @@ Instalação: composer require nette/utils ``` -Todos os exemplos assumem que a seguinte classe está definida: +Todos os exemplos pressupõem a criação de um alias: ```php use Nette\Utils\Html; ``` -Criação de um Elemento HTML .[#toc-creating-an-html-element] -============================================================ +Criação de elementos HTML +========================= -O elemento é criado usando o método `Html::el()`: +Criamos um elemento usando o método `Html::el()`: ```php -$el = Html::el('img'); // cria elemento +$el = Html::el('img'); // cria o elemento ``` -Além do nome, você pode inserir outros atributos na sintaxe HTML: +Além do nome, você também pode especificar outros atributos na sintaxe HTML: ```php $el = Html::el('input type=text class="red important"'); ``` -Ou passe-as como uma matriz associativa para o segundo parâmetro: +Ou passá-los como um array associativo no segundo parâmetro: ```php $el = Html::el('input', [ @@ -50,39 +50,39 @@ $el = Html::el('input', [ ]); ``` -Para mudar e devolver um nome de elemento: +Alterando e retornando o nome do elemento: ```php $el->setName('img'); $el->getName(); // 'img' -$el->isEmpty(); // true, as is void element +$el->isEmpty(); // true, pois é um elemento vazio ``` -Atributos HTML .[#toc-html-attributes] -====================================== +Atributos HTML +============== -Você pode definir e obter atributos HTML individuais de três maneiras, cabe a você quem você mais gosta. A primeira é através das propriedades: +Podemos alterar e ler atributos HTML individuais de três maneiras; depende de você qual delas prefere. A primeira é através de propriedades: ```php $el->src = 'image.jpg'; // define o atributo src echo $el->src; // 'image.jpg' -unset($el->src); // remove atributo +unset($el->src); // remove o atributo // ou $el->src = null; ``` -A segunda maneira é chamar métodos que, em contraste com a definição de propriedades, podemos encadear juntos: +A segunda maneira é chamando métodos, que, ao contrário da definição de propriedades, podemos encadear: ```php $el = Html::el('img')->src('image.jpg')->alt('photo'); -// photo +// foto -$el->alt(null); // remove atributo +$el->alt(null); // remoção do atributo ``` -E a terceira maneira é a mais faladora: +E a terceira maneira é a mais verbosa: ```php $el = Html::el('img') @@ -94,9 +94,9 @@ echo $el->getAttribute('src'); // 'image.jpg' $el->removeAttribute('alt'); ``` -Em grandes quantidades, os atributos podem ser definidos com `addAttributes(array $attrs)` e excluídos com `removeAttributes(array $attrNames)`. +Atributos podem ser definidos em massa usando `addAttributes(array $attrs)` e removidos usando `removeAttributes(array $attrNames)`. -O valor de um atributo não tem que ser apenas uma string, também podem ser usados valores lógicos para atributos lógicos: +O valor de um atributo não precisa ser apenas uma string; valores booleanos também podem ser usados para atributos lógicos: ```php $checkbox = Html::el('input')->type('checkbox'); @@ -104,17 +104,17 @@ $checkbox->checked = true; // $checkbox->checked = false; // ``` -Um atributo também pode ser um conjunto de fichas, que são listadas separadas por espaços, o que é adequado para classes CSS, por exemplo: +Um atributo também pode ser um array de valores, que serão impressos separados por espaços, o que é útil, por exemplo, para classes CSS: ```php $el = Html::el('input'); $el->class[] = 'active'; -$el->class[] = null; // null is ignored +$el->class[] = null; // null é ignorado $el->class[] = 'top'; echo $el; // '' ``` -Uma alternativa é uma matriz associativa, onde os valores dizem se a chave deve ser listada: +Uma alternativa é um array associativo, onde os valores indicam se a chave deve ser impressa: ```php $el = Html::el('input'); @@ -123,7 +123,7 @@ $el->class['top'] = false; echo $el; // '' ``` -Os estilos CSS podem ser escritos sob a forma de matrizes associativas: +Estilos CSS podem ser escritos na forma de arrays associativos: ```php $el = Html::el('input'); @@ -132,7 +132,7 @@ $el->style['display'] = 'block'; echo $el; // '' ``` -Agora usamos propriedades, mas o mesmo pode ser feito com os métodos: +Até agora usamos propriedades, mas o mesmo pode ser escrito usando métodos: ```php $el = Html::el('input'); @@ -141,7 +141,7 @@ $el->style('display', 'block'); echo $el; // '' ``` -Ou mesmo da maneira mais faladora: +Ou mesmo da maneira mais verbosa: ```php $el = Html::el('input'); @@ -150,7 +150,7 @@ $el->appendAttribute('style', 'display', 'block'); echo $el; // '' ``` -Uma última coisa: o método `href()` pode facilitar a composição de parâmetros de consulta em uma URL: +Um pequeno detalhe para concluir: o método `href()` pode facilitar a composição de parâmetros de consulta na URL: ```php echo Html::el('a')->href('index.php', [ @@ -161,19 +161,19 @@ echo Html::el('a')->href('index.php', [ ``` -Atributos de dados .[#toc-data-attributes] ------------------------------------------- +Atributos de dados +------------------ -Os atributos de dados têm suporte especial. Como seus nomes contêm hífens, o acesso através de propriedades e métodos não é tão elegante, por isso existe um método `data()`: +Atributos de dados têm suporte especial. Como seus nomes contêm hífens, o acesso via propriedades e métodos não é tão elegante, por isso existe o método `data()`: ```php $el = Html::el('input'); -$el->{'data-max-size'} = '500x300'; // não tão elegante +$el->{'data-max-size'} = '500x300'; // não é tão elegante $el->data('max-size', '500x300'); // é elegante echo $el; // '' ``` -Se o valor do atributo de dados for um array, ele é automaticamente serializado para o JSON: +Se o valor de um atributo de dados for um array, ele é automaticamente serializado para JSON: ```php $el = Html::el('input'); @@ -182,20 +182,20 @@ echo $el; // '' ``` -Conteúdo do Elemento .[#toc-element-content] -============================================ +Conteúdo do elemento +==================== -O conteúdo interno do elemento é definido pelos métodos `setHtml()` ou `setText()`. Use o primeiro somente se você souber que está passando com segurança uma string HTML segura no parâmetro. +Definimos o conteúdo interno do elemento usando os métodos `setHtml()` ou `setText()`. Use o primeiro apenas se souber que está passando uma string HTML confiavelmente segura no parâmetro. ```php -echo Html::el('span')->setHtml('hello
                                                                                                                            '); -// 'hello
                                                                                                                            ' +echo Html::el('span')->setHtml('olá
                                                                                                                            '); +// 'olá
                                                                                                                            ' echo Html::el('span')->setText('10 < 20'); // '10 < 20' ``` -Por outro lado, o conteúdo interno é obtido pelos métodos `getHtml()` ou `getText()`. O segundo remove as tags da saída HTML e converte as entidades HTML em caracteres. +E, inversamente, obtemos o conteúdo interno usando os métodos `getHtml()` ou `getText()`. O segundo remove tags HTML da saída e converte entidades HTML em caracteres. ```php echo $el->getHtml(); // '10 < 20' @@ -203,33 +203,33 @@ echo $el->getText(); // '10 < 20' ``` -Nodos Infantis .[#toc-child-nodes] ----------------------------------- +Nós filhos +---------- -O conteúdo interno de um elemento também pode ser um conjunto de crianças. Cada um deles pode ser um fio ou outro elemento `Html`. Eles são inseridos usando `addHtml()` ou `addText()`: +O interior de um elemento também pode ser um array de nós filhos (children). Cada um deles pode ser uma string ou outro elemento `Html`. Nós os inserimos usando `addHtml()` ou `addText()`: ```php $el = Html::el('span') - ->addHtml('hello
                                                                                                                            ') + ->addHtml('olá
                                                                                                                            ') ->addText('10 < 20') ->addHtml( Html::el('br') ); -// hello
                                                                                                                            10 < 20
                                                                                                                            +// olá
                                                                                                                            10 < 20
                                                                                                                            ``` -Outra maneira de criar e inserir um novo nódulo `Html`: +Outra maneira de criar e inserir um novo nó `Html`: ```php -$el = Html::el('ul') - ->create('li', ['class' => 'first']) - ->setText('hello'); -//
                                                                                                                            • hello
                                                                                                                            +$ul = Html::el('ul'); +$ul->create('li', ['class' => 'first']) + ->setText('primeiro'); +//
                                                                                                                            • primeiro
                                                                                                                            ``` -Você pode trabalhar com nós como se fossem itens de matriz. Portanto, acesse os individuais usando colchetes, conte-os com `count()` e itere sobre eles: +É possível trabalhar com nós da mesma forma que com arrays. Ou seja, acessar nós individuais usando colchetes, contá-los usando `count()` e iterar sobre eles: ```php $el = Html::el('div'); -$el[] = 'hello'; +$el[] = 'olá'; $el[] = Html::el('span'); echo $el[1]; // '' @@ -238,58 +238,58 @@ foreach ($el as $child) { /* ... */ } echo count($el); // 2 ``` -Um novo nó pode ser inserido em uma posição específica usando `insert(?int $index, $child, bool $replace = false)`. Se `$replace = false`, ele insere o elemento na posição `$index` e move os outros. Se `$index = null`, ele anexará um elemento no final. +Um novo nó pode ser inserido em uma posição específica usando `insert(?int $index, $child, bool $replace = false)`. Se `$replace = false`, ele insere o elemento na posição `$index` e desloca os outros. Se `$index = null`, ele adiciona o elemento ao final. ```php -// insere o elemento na primeira posição e avança os outros +// insere o elemento na primeira posição e desloca os outros $el->insert(0, Html::el('span')); ``` -Todos os nós são devolvidos pelo método `getChildren()` e removidos pelo método `removeChildren()`. +Obtemos todos os nós usando o método `getChildren()` e os removemos usando o método `removeChildren()`. -Criação de um fragmento de documento .[#toc-creating-a-document-fragment] -------------------------------------------------------------------------- +Criação de fragmento de documento +--------------------------------- -Se você quiser trabalhar com um conjunto de nós e não estiver interessado no elemento de embalagem, você pode criar um fragmento chamado *documento* passando `null` em vez do nome do elemento: +Se quisermos trabalhar com um array de nós e não nos importarmos com o elemento envolvente, podemos criar um chamado *fragmento de documento* passando `null` em vez do nome do elemento: ```php $el = Html::el(null) - ->addHtml('hello
                                                                                                                            ') + ->addHtml('olá
                                                                                                                            ') ->addText('10 < 20') ->addHtml( Html::el('br') ); -// hello
                                                                                                                            10 < 20
                                                                                                                            +// olá
                                                                                                                            10 < 20
                                                                                                                            ``` -Os métodos `fromHtml()` e `fromText()` oferecem uma maneira mais rápida de criar um fragmento: +Uma maneira mais rápida de criar um fragmento é oferecida pelos métodos `fromHtml()` e `fromText()`: ```php -$el = Html::fromHtml('hello
                                                                                                                            '); -echo $el; // 'hello
                                                                                                                            ' +$el = Html::fromHtml('olá
                                                                                                                            '); +echo $el; // 'olá
                                                                                                                            ' $el = Html::fromText('10 < 20'); echo $el; // '10 < 20' ``` -Geração de saída HTML .[#toc-generating-html-output] -==================================================== +Geração de saída HTML +===================== -A maneira mais fácil de gerar um elemento HTML é usar `echo` ou lançar um objeto para `(string)`. Você também pode imprimir tags e atributos de abertura ou fechamento separadamente: +A maneira mais simples de imprimir um elemento HTML é usar `echo` ou converter o objeto para `(string)`. Também é possível imprimir separadamente as tags de abertura ou fechamento e os atributos: ```php -$el = Html::el('div class=header')->setText('hello'); +$el = Html::el('div class=header')->setText('olá'); -echo $el; // '
                                                                                                                            ' -$s = (string) $el; // '
                                                                                                                            hello
                                                                                                                            ' -$s = $el->toHtml(); // '
                                                                                                                            hello
                                                                                                                            ' -$s = $el->toText(); // 'hello' +echo $el; // '
                                                                                                                            olá
                                                                                                                            ' +$s = (string) $el; // '
                                                                                                                            olá
                                                                                                                            ' +$s = $el->toHtml(); // '
                                                                                                                            olá
                                                                                                                            ' +$s = $el->toText(); // 'olá' echo $el->startTag(); // '
                                                                                                                            ' echo $el->endTag(); // '
                                                                                                                            ' echo $el->attributes(); // 'class="header"' ``` -Uma característica importante é a proteção automática contra o [Cross Site Scripting (XSS |nette:glossary#cross-site-scripting-xss]). Todos os valores de atributos ou conteúdos inseridos usando `setText()` ou `addText()` escapam de forma confiável: +Uma característica importante é a proteção automática contra [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS]. Todos os valores de atributos ou conteúdo inserido via `setText()` ou `addText()` são escapados de forma confiável: ```php echo Html::el('div') @@ -300,17 +300,17 @@ echo Html::el('div') ``` -Conversão HTML ↔ Texto .[#toc-conversion-html-text] -=================================================== +Conversão HTML ↔ texto +====================== -Você pode usar o método estático `htmlToText()` para converter HTML em texto: +Para converter HTML em texto, você pode usar o método estático `htmlToText()`: ```php -echo Html::htmlToText('One & Two'); // 'One & Two' +echo Html::htmlToText('Um & Dois'); // 'Um & Dois' ``` -HtmlStringable .[#toc-htmlstringable] -===================================== +HtmlStringable +============== -O objeto `Nette\Utils\Html` implementa a interface `Nette\HtmlStringable`, que, por exemplo, Latte ou formulários usam para distinguir objetos que têm um método `__toString()` que retorna o código HTML. Assim, a dupla fuga não ocorre se, por exemplo, imprimirmos o objeto no modelo usando `{$el}`. +O objeto `Nette\Utils\Html` implementa a interface `Nette\HtmlStringable`, que, por exemplo, o Latte ou os formulários usam para distinguir objetos que possuem um método `__toString()` retornando código HTML. Assim, não ocorrerá dupla escapagem se, por exemplo, imprimirmos o objeto em um template usando `{$el}`. diff --git a/utils/pt/images.texy b/utils/pt/images.texy index fdf21d8eaa..39019f652e 100644 --- a/utils/pt/images.texy +++ b/utils/pt/images.texy @@ -1,11 +1,11 @@ -Funções de imagem -***************** +Trabalhando com imagens +*********************** .[perex] -A classe [api:Nette\Utils\Image] simplifica a manipulação de imagens, tais como redimensionamento, corte, nitidez, desenho ou fusão de múltiplas imagens. +A classe [api:Nette\Utils\Image] simplifica a manipulação de imagens, como redimensionamento, recorte, nitidez, desenho ou combinação de várias imagens. -O PHP tem um extenso conjunto de funções para manipulação de imagens. Mas a API não é muito agradável. Não seria uma boa estrutura para se criar um API sexy. +O PHP possui um extenso conjunto de funções para manipulação de imagens. Mas a sua API não é muito conveniente. Não seria o Nette Framework se não viesse com uma API sexy. Instalação: @@ -13,26 +13,28 @@ Instalação: composer require nette/utils ``` -Os exemplos a seguir assumem que a seguinte classe está definida: +Todos os exemplos assumem que um alias foi criado: ```php use Nette\Utils\Image; +use Nette\Utils\ImageColor; +use Nette\Utils\ImageType; ``` -Criação de uma imagem .[#toc-creating-an-image] -=============================================== +Criação de imagem +================= -Vamos criar uma nova imagem colorida verdadeira, por exemplo, com dimensões de 100×200: +Criamos uma nova imagem true color, por exemplo, com dimensões 100×200: ```php $image = Image::fromBlank(100, 200); ``` -Opcionalmente, você pode especificar uma cor de fundo (o padrão é preto): +Opcionalmente, pode-se especificar a cor de fundo (o padrão é preto): ```php -$image = Image::fromBlank(100, 200, Image::rgb(125, 0, 0)); +$image = Image::fromBlank(100, 200, ImageColor::rgb(125, 0, 0)); ``` Ou carregamos a imagem de um arquivo: @@ -41,129 +43,138 @@ Ou carregamos a imagem de um arquivo: $image = Image::fromFile('nette.jpg'); ``` -Os formatos suportados são JPEG, PNG, GIF, WebP, AVIF e BMP, mas sua versão do PHP também deve apoiá-los (verifique a `phpinfo()`, seção GD). As animações não são suportadas. -Necessidade de detectar o formato da imagem ao carregar? O formato de retorno do método no segundo parâmetro: +Salvando a imagem +================= + +A imagem pode ser salva em um arquivo: ```php -$image = Image::fromFile('nette.jpg', $type); -// $type é Image::JPEG, Image::PNG, Image::GIF, Image::WEBP, Image::AVIF ou Image::BMP +$image->save('resampled.jpg'); ``` -Somente a detecção sem carregar a imagem é feita por `Image::detectTypeFromFile()`. - +Podemos especificar a qualidade da compressão na faixa de 0..100 para JPEG (padrão 85), WEBP (padrão 80) e AVIF (padrão 30) e 0..9 para PNG (padrão 9): -Salvar a imagem .[#toc-save-the-image] -====================================== +```php +$image->save('resampled.jpg', 80); // JPEG, qualidade 80% +``` -A imagem pode ser salva em um arquivo: +Se o formato não for óbvio pela extensão do arquivo, ele pode ser especificado por uma [constante |#Formatos]: ```php -$image->save('resampled.jpg'); +$image->save('resampled.tmp', null, ImageType::JPEG); ``` -Podemos especificar a qualidade de compressão na faixa 0..100 para JPEG (padrão 85), WEBP (padrão 80) e AVIF (padrão 30) e 0..9 para PNG (padrão 9): +A imagem pode ser escrita em uma variável em vez de no disco: ```php -$image->save('resampled.jpg', 80); // JPEG, qualidade 80% +$data = $image->toString(ImageType::JPEG, 80); // JPEG, qualidade 80% ``` -Se o formato não for óbvio pela extensão do arquivo, ele pode ser especificado por uma das constantes `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF`, e `Image::BMP`: +ou enviada diretamente para o navegador com o cabeçalho HTTP `Content-Type` apropriado: ```php -$image->save('resampled.tmp', null, Image::JPEG); +// envia o cabeçalho Content-Type: image/png +$image->send(ImageType::PNG); ``` -A imagem pode ser escrita em uma variável ao invés de em disco: + +Formatos +======== + +Os formatos suportados são JPEG, PNG, GIF, WebP, AVIF e BMP, no entanto, eles também devem ser suportados pela sua versão do PHP, o que pode ser verificado com a função [#isTypeSupported()]. Animações não são suportadas. + +O formato é representado pelas constantes `ImageType::JPEG`, `ImageType::PNG`, `ImageType::GIF`, `ImageType::WEBP`, `ImageType::AVIF` e `ImageType::BMP`. ```php -$data = $image->toString(Image::JPEG, 80); // JPEG, qualidade 80%. +$supported = Image::isTypeSupported(ImageType::JPEG); ``` -ou enviar diretamente para o navegador com o cabeçalho HTTP apropriado `Content-Type`: +Precisa detectar o formato da imagem ao carregar? O método o retorna no segundo parâmetro: ```php -// envia cabeçalho Content-Type: imagem/png -$image->send(Image::PNG); +$image = Image::fromFile('nette.jpg', $type); ``` +A detecção por si só, sem carregar a imagem, é feita por `Image::detectTypeFromFile()`. + -Redimensionamento de imagem .[#toc-image-resize] -================================================ +Redimensionamento +================= -Uma operação comum é redimensionar uma imagem. As dimensões atuais são devolvidas pelos métodos `getWidth()` e `getHeight()`. +Uma operação comum é redimensionar a imagem. As dimensões atuais são retornadas pelos métodos `getWidth()` e `getHeight()`. -O método `resize()` é utilizado para o redimensionamento. Este é um exemplo de mudança de tamanho proporcional para que não exceda 500×300 pixels (ou a largura será exatamente 500px ou a altura será exatamente 300px, uma das dimensões é calculada para manter a relação de aspecto): +O método `resize()` é usado para a alteração. Exemplo de redimensionamento proporcional para que não exceda as dimensões de 500x300 pixels (ou a largura será exatamente 500 px ou a altura será exatamente 300 px, uma das dimensões será calculada para manter a proporção): ```php $image->resize(500, 300); ``` -É possível definir apenas uma dimensão e a segunda será calculada: +É possível especificar apenas uma dimensão e a outra será calculada: ```php -$image->resize(500, null); // largura 500px, altura auto +$image->resize(500, null); // largura 500px, altura será calculada -$image->resize(null, 300); // largura auto, altura 300px +$image->resize(null, 300); // largura será calculada, altura 300px ``` -Qualquer dimensão pode ser especificada em porcentagens: +Qualquer dimensão também pode ser especificada em porcentagem: ```php $image->resize('75%', 300); // 75 % × 300px ``` -O comportamento de `resize` pode ser influenciado pelas seguintes bandeiras. Todos, exceto `Image::Stretch`, preservam a relação de aspecto. +O comportamento de `resize` pode ser influenciado pelos seguintes flags. Todos, exceto `Image::Stretch`, mantêm a proporção. |--------------------------------------------------------------------------------------- -| Bandeira | Descrição +| Flag | Descrição |--------------------------------------------------------------------------------------- -| `Image::OrSmaller` (padrão) | as dimensões resultantes serão menores ou iguais, conforme especificado -| `Image::OrBigger` | preenche a área de destino e possivelmente a estende em uma direção -| `Image::Cover` | preenche toda a área e corta o que a excede -| `Image::ShrinkOnly` | apenas balança para baixo (não estende uma pequena imagem) -| `Image::Stretch` | não mantém a relação de aspecto +| `Image::OrSmaller` (padrão) | as dimensões resultantes serão menores ou iguais às dimensões solicitadas +| `Image::OrBigger` | preenche (e possivelmente excede em uma dimensão) a área de destino +| `Image::Cover` | preenche a área de destino e recorta o que exceder +| `Image::ShrinkOnly` | apenas redução (evita o esticamento de uma imagem pequena) +| `Image::Stretch` | não manter a proporção -As bandeiras são passadas como o terceiro argumento da função: +Os flags são especificados como o terceiro argumento da função: ```php $image->resize(500, 300, Image::OrBigger); ``` -As bandeiras podem ser combinadas: +Os flags podem ser combinados: ```php $image->resize(500, 300, Image::ShrinkOnly | Image::Stretch); ``` -As imagens podem ser viradas verticalmente ou horizontalmente especificando uma das dimensões (ou ambas) como um número negativo: +As imagens podem ser invertidas verticalmente ou horizontalmente especificando uma das dimensões (ou ambas) como um número negativo: ```php -$flipped = $image->resize(null, '-100%'); // inverter vertical +$flipped = $image->resize(null, '-100%'); // inverter verticalmente -$flipped = $image->resize('-100%', '-100%'); // girar em 180° +$flipped = $image->resize('-100%', '-100%'); // rotacionar 180° -$flipped = $image->resize(-125, 500); // redimensionar e virar horizontal +$flipped = $image->resize(-125, 500); // redimensionar e inverter horizontalmente ``` -Depois de reduzir a imagem, podemos melhorá-la através da nitidez: +Após reduzir a imagem, é possível melhorar sua aparência com uma leve nitidez: ```php $image->sharpen(); ``` -Cultivo .[#toc-cropping] -======================== +Recorte +======= -O método `crop()` é utilizado para o cultivo: +O método `crop()` é usado para recortar: ```php $image->crop($left, $top, $width, $height); ``` -Como em `resize()`, todos os valores podem ser especificados em porcentagens. As porcentagens para `$left` e `$top` são calculadas a partir do espaço restante, semelhante à propriedade do CSS `background-position`: +Assim como em `resize()`, todos os valores podem ser especificados em porcentagens. As porcentagens para `$left` e `$top` são calculadas a partir do espaço restante, semelhante à propriedade CSS `background-position`: ```php $image->crop('100%', '50%', '80%', '80%'); @@ -172,173 +183,198 @@ $image->crop('100%', '50%', '80%', '80%'); [* crop.svg *] -A imagem também pode ser cortada automaticamente, por exemplo, bordas pretas cortadas: +A imagem também pode ser recortada automaticamente, por exemplo, recortando as bordas pretas: ```php $image->cropAuto(IMG_CROP_BLACK); ``` -O método `cropAuto()` é um encapsulamento de objeto da função `imagecropauto()`, veja [sua documentação |https://www.php.net/manual/en/function.imagecropauto] para mais informações. +O método `cropAuto()` é um substituto orientado a objetos para a função `imagecropauto()`, você pode encontrar mais informações em [sua documentação|https://www.php.net/manual/en/function.imagecropauto]. -Desenho e Edição .[#toc-drawing-and-editing] -============================================ +Cores .{data-version:4.0.2} +=========================== -Você pode desenhar, você pode escrever, você pode usar todas as funções PHP para trabalhar com imagens, tais como [imagefilledellipse() |https://www.php.net/manual/en/function.imagefilledellipse.php], mas usando o estilo de objeto: +O método `ImageColor::rgb()` permite definir uma cor usando os valores de vermelho, verde e azul (RGB). Opcionalmente, você também pode especificar o valor de transparência na faixa de 0 (totalmente transparente) a 1 (totalmente opaco), assim como no CSS. ```php -$image->filledEllipse($cx, $cy, $width, $height, Image::rgb(255, 0, 0, 63)); +$color = ImageColor::rgb(255, 0, 0); // Vermelho +$transparentBlue = ImageColor::rgb(0, 0, 255, 0.5); // Azul semitransparente ``` -Veja [Visão geral dos métodos |#Overview of Methods]. +O método `ImageColor::hex()` permite definir uma cor usando o formato hexadecimal, semelhante ao CSS. Suporta os formatos `#rgb`, `#rrggbb`, `#rgba` e `#rrggbbaa`: +```php +$color = ImageColor::hex("#F00"); // Vermelho +$transparentGreen = ImageColor::hex("#00FF0080"); // Verde semitransparente +``` + +As cores podem ser usadas em outros métodos, como `ellipse()`, `fill()`, etc. -Fundir várias imagens .[#toc-merge-multiple-images] -=================================================== -Você pode facilmente colocar outra imagem dentro da imagem: +Desenho e edições +================= + +Você pode desenhar e escrever. Todas as funções PHP para trabalhar com imagens estão disponíveis para você, veja [#Visão geral dos métodos], mas em um invólucro orientado a objetos: + +```php +$image->filledEllipse($centerX, $centerY, $width, $height, ImageColor::rgb(255, 0, 0)); +``` + +Como as funções PHP para desenhar retângulos são impraticáveis devido à especificação de coordenadas, a classe `Image` oferece substitutos na forma das funções [#rectangleWH()] e [#filledRectangleWH()]. + + +Combinação de múltiplas imagens +=============================== + +É fácil inserir outra imagem em uma imagem: ```php $logo = Image::fromFile('logo.png'); -$blank = Image::fromBlank(320, 240, Image::rgb(52, 132, 210)); +$blank = Image::fromBlank(320, 240, ImageColor::rgb(52, 132, 210)); -// as coordenadas também podem ser definidas em porcentagem -$blank->place($logo, '80%', '80%'); // perto do canto inferior direito +// as coordenadas podem ser especificadas novamente em porcentagens +$blank->place($logo, '80%', '80%'); // inserimos perto do canto inferior direito ``` -Ao colar, o canal alfa é respeitado, além disso, podemos influenciar a transparência da imagem inserida (criaremos uma chamada marca d'água): +Ao inserir, o canal alfa é respeitado, além disso, podemos influenciar a transparência da imagem inserida (criamos a chamada marca d'água): ```php -$blank->place($image, '80%', '80%', 25); // transparência é 25 +$blank->place($image, '80%', '80%', 25); // a transparência é de 25% ``` -Tal API é realmente um prazer de usar, não é? +É realmente um prazer usar essa API! -Visão geral dos métodos .[#toc-overview-of-methods] -=================================================== +Visão geral dos métodos +======================= -static fromBlank(int $width, int $height, array $color=null): Image .[method] ------------------------------------------------------------------------------ -Cria uma nova imagem colorida verdadeira das dimensões dadas. A cor padrão é o preto. +static fromBlank(int $width, int $height, ?ImageColor $color=null): Image .[method] +----------------------------------------------------------------------------------- +Cria uma nova imagem true color das dimensões fornecidas. A cor padrão é preta. static fromFile(string $file, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------- -Lê uma imagem de um arquivo e retorna seu tipo em `$detectedFormat`. Os tipos suportados são `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` e `Image::BMP`. +Carrega uma imagem de um arquivo e retorna seu [tipo |#Formatos] em `$detectedFormat`. static fromString(string $s, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------ -Lê uma imagem de um fio e retorna seu tipo em `$detectedFormat`. Os tipos suportados são `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::AVIF`, `Image::WEBP`, , e `Image::BMP`. +Carrega uma imagem de uma string e retorna seu [tipo |#Formatos] em `$detectedFormat`. -static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method] ---------------------------------------------------------------------------------- -Cria uma cor que pode ser usada em outros métodos, tais como `ellipse()`, `fill()` e assim por diante. +static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method][deprecated] +--------------------------------------------------------------------------------------------- +Esta função foi substituída pela classe `ImageColor`, veja [#cores]. static typeToExtension(int $type): string .[method] --------------------------------------------------- -Devolve a extensão do arquivo para a constante `Image::XXX` dada. +Retorna a extensão do arquivo para o [tipo |#Formatos] fornecido. static typeToMimeType(int $type): string .[method] -------------------------------------------------- -Devolve o tipo de mímica para a constante `Image::XXX` dada. +Retorna o tipo mime para o [tipo |#Formatos] fornecido. static extensionToType(string $extension): int .[method] -------------------------------------------------------- -Retorna o tipo de imagem como uma constante `Image::XXX` de acordo com a extensão do arquivo. +Retorna o [tipo |#Formatos] da imagem com base na extensão do arquivo. static detectTypeFromFile(string $file, int &$width=null, int &$height=null): ?int .[method] -------------------------------------------------------------------------------------------- -Retorna o tipo de arquivo de imagem como constante `Image::XXX` e nos parâmetros `$width` e `$height` também suas dimensões. +Retorna o [tipo |#Formatos] da imagem e, nos parâmetros `$width` e `$height`, também suas dimensões. static detectTypeFromString(string $s, int &$width=null, int &$height=null): ?int .[method] ------------------------------------------------------------------------------------------- -Retorna o tipo de imagem de corda como constante `Image::XXX` e nos parâmetros `$width` e `$height` também suas dimensões. +Retorna o [tipo |#Formatos] da imagem a partir de uma string e, nos parâmetros `$width` e `$height`, também suas dimensões. -affine(array $affine, array $clip=null): Image .[method] --------------------------------------------------------- -Retorna uma imagem contendo a imagem src transformada afim, usando uma área de recorte opcional. ([mais |https://www.php.net/manual/en/function.imageaffine]). +static isTypeSupported(int $type): bool .[method] +------------------------------------------------- +Verifica se o [tipo |#Formatos] de imagem fornecido é suportado. + + +static getSupportedTypes(): array .[method]{data-version:4.0.4} +--------------------------------------------------------------- +Retorna um array dos [tipos |#Formatos] de imagem suportados. + + +static calculateTextBox(string $text, string $fontFile, float $size, float $angle=0, array $options=[]): array .[method] +------------------------------------------------------------------------------------------------------------------------ +Calcula as dimensões do retângulo que envolve o texto em uma determinada fonte e tamanho. Retorna um array associativo contendo as chaves `left`, `top`, `width`, `height`. A margem esquerda pode ser negativa se o texto começar com um kerning esquerdo. + + +affine(array $affine, ?array $clip=null): Image .[method] +--------------------------------------------------------- +Retorna uma imagem contendo a imagem src transformada afinamente usando uma área de recorte opcional. ([mais |https://www.php.net/manual/en/function.imageaffine]). affineMatrixConcat(array $m1, array $m2): array .[method] --------------------------------------------------------- -Retorna a concatenação de duas matrizes de transformação afins, que é útil se múltiplas transformações devem ser aplicadas à mesma imagem de uma só vez. ([mais |https://www.php.net/manual/en/function.imageaffinematrixconcat]) +Retorna a concatenação de duas matrizes de transformação afim, o que é útil se várias transformações devem ser aplicadas à mesma imagem de uma vez. ([mais |https://www.php.net/manual/en/function.imageaffinematrixconcat]) -affineMatrixGet(int $type, mixed $options=null): array .[method] ----------------------------------------------------------------- -Devolve uma matriz de transformação afim. ([mais |https://www.php.net/manual/en/function.imageaffinematrixget]) +affineMatrixGet(int $type, ?mixed $options=null): array .[method] +----------------------------------------------------------------- +Retorna uma matriz de transformação afim. ([mais |https://www.php.net/manual/en/function.imageaffinematrixget]) alphaBlending(bool $on): void .[method] --------------------------------------- -Permite dois modos diferentes de desenhar em imagens truecolor. No modo de mistura, o componente de canal alfa da cor fornecida a todas as funções de desenho, como `setPixel()`, determina quanto da cor subjacente deve ser permitido brilhar. Como resultado, ele mistura automaticamente a cor existente naquele ponto com a cor do desenho, e armazena o resultado na imagem. O pixel resultante é opaco. No modo sem mistura, a cor do desenho é copiada literalmente com suas informações do canal alfa, substituindo o pixel de destino. O modo de mistura não está disponível quando se desenha em imagens de paleta. ([mais |https://www.php.net/manual/en/function.imagealphablending]) +Permite dois modos diferentes de desenho em imagens truecolor. No modo de mesclagem, o componente do canal alfa da cor usada em todas as funções de desenho, como `setPixel()`, determina até que ponto a cor base deve ser permitida a transparecer. Como resultado, a cor existente neste ponto é automaticamente misturada com a cor desenhada e o resultado é salvo na imagem. O pixel resultante é opaco. No modo sem mesclagem, a cor desenhada é copiada literalmente com as informações do canal alfa e substitui o pixel de destino. O modo de mesclagem não está disponível ao desenhar em imagens de paleta. ([mais |https://www.php.net/manual/en/function.imagealphablending]) antialias(bool $on): void .[method] ----------------------------------- -Ativar os métodos de desenho rápido antialiased para linhas e polígonos com fio. Ele não suporta componentes alfa. Funciona através de uma operação de mistura direta. Funciona apenas com imagens trégua-cores. +Ativa o desenho de linhas e polígonos suavizados. Não suporta canais alfa. Funciona apenas com imagens truecolor. -O uso de primitivos antialiased com cor de fundo transparente pode terminar com alguns resultados inesperados. O método de mistura utiliza a cor de fundo como qualquer outra cor. A falta de suporte de componentes alfa não permite um método antialiasing baseado em alfa. ([mais |https://www.php.net/manual/en/function.imageantialias]) +O uso de primitivas suavizadas com uma cor de fundo transparente pode resultar em alguns resultados inesperados. O método de mesclagem usa a cor de fundo como todas as outras cores. ([mais |https://www.php.net/manual/en/function.imageantialias]) -arc(int $x, int $y, int $w, int $h, int $start, int $end, int $color): void .[method] -------------------------------------------------------------------------------------- -Desenha um arco de círculo centrado nas coordenadas dadas. ([mais |https://www.php.net/manual/en/function.imagearc]) - - -char(int $font, int $x, int $y, string $char, int $color): void .[method] -------------------------------------------------------------------------- -Traça o primeiro caractere de `$char` na imagem com sua parte superior esquerda em `$x`,`$y` (parte superior esquerda é 0, 0) com a cor `$color`. ([mais |https://www.php.net/manual/en/function.imagechar]) - - -charUp(int $font, int $x, int $y, string $char, int $color): void .[method] ---------------------------------------------------------------------------- -Traça o caracter `$char` verticalmente na coordenada especificada na imagem dada. ([mais |https://www.php.net/manual/en/function.imagecharup]) +arc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color): void .[method] +--------------------------------------------------------------------------------------------------------------------------- +Desenha um arco de círculo centrado nas coordenadas fornecidas. ([mais |https://www.php.net/manual/en/function.imagearc]) colorAllocate(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------- -Retorna um identificador de cor que representa a cor composta dos componentes RGB. Ela deve ser chamada para criar cada cor a ser usada na imagem. ([mais |https://www.php.net/manual/en/function.imagecolorallocate]) +Retorna um identificador de cor representando a cor composta pelos componentes RGB fornecidos. Deve ser chamado para criar cada cor a ser usada na imagem. ([mais |https://www.php.net/manual/en/function.imagecolorallocate]) colorAllocateAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ------------------------------------------------------------------------------ -Comportamento idêntico ao de `colorAllocate()` com a adição do parâmetro de transparência `$alpha`. ([mais |https://www.php.net/manual/en/function.imagecolorallocatealpha]) +Comporta-se da mesma forma que `colorAllocate()` com a adição do parâmetro de transparência `$alpha`. ([mais |https://www.php.net/manual/en/function.imagecolorallocatealpha]) colorAt(int $x, int $y): int .[method] -------------------------------------- -Retorna o índice da cor do pixel no local especificado na imagem. Se a imagem for uma imagem verdadeira, esta função retorna o valor RGB daquele pixel como inteiro. Use bitshifting e mascaramento para acessar os distintos valores dos componentes vermelho, verde e azul: ([mais |https://www.php.net/manual/en/function.imagecolorat]) +Retorna o índice da cor do pixel na localização especificada na imagem. Se a imagem for truecolor, esta função retorna o valor RGB desse pixel como um inteiro. Use deslocamento de bits e mascaramento de bits para acessar os valores dos componentes vermelho, verde e azul separadamente: ([mais |https://www.php.net/manual/en/function.imagecolorat]) colorClosest(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------ -Retorna o índice da cor na paleta da imagem que é "o mais próximo" do valor RGB especificado. A "distância" entre a cor desejada e cada cor da paleta é calculada como se os valores RGB representassem pontos no espaço tridimensional. ([mais |https://www.php.net/manual/en/function.imagecolorclosest]) +Retorna o índice da cor na paleta da imagem que está "mais próxima" do valor RGB especificado. A "distância" entre a cor desejada e cada cor na paleta é calculada como se os valores RGB representassem pontos em um espaço tridimensional. ([mais |https://www.php.net/manual/en/function.imagecolorclosest]) colorClosestAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ----------------------------------------------------------------------------- -Retorna o índice da cor na paleta da imagem que é "mais próxima" do valor RGB especificado e do nível `$alpha`. ([mais |https://www.php.net/manual/en/function.imagecolorclosestalpha]) +Retorna o índice da cor na paleta da imagem que está "mais próxima" do valor RGB especificado e do nível `$alpha`. ([mais |https://www.php.net/manual/en/function.imagecolorclosestalpha]) colorClosestHWB(int $red, int $green, int $blue): int .[method] --------------------------------------------------------------- -Obtenha o índice da cor que tem a tonalidade, branco e negro mais próximo da cor dada. ([mais |https://www.php.net/manual/en/function.imagecolorclosesthwb]) +Obtém o índice da cor que tem a matiz, o branco e o preto mais próximos da cor fornecida. ([mais |https://www.php.net/manual/en/function.imagecolorclosesthwb]) colorDeallocate(int $color): void .[method] ------------------------------------------- -Desaloca uma cor previamente alocada com `colorAllocate()` ou `colorAllocateAlpha()`. ([mais |https://www.php.net/manual/en/function.imagecolordeallocate]) +Desaloca uma cor previamente alocada usando `colorAllocate()` ou `colorAllocateAlpha()`. ([mais |https://www.php.net/manual/en/function.imagecolordeallocate]) colorExact(int $red, int $green, int $blue): int .[method] @@ -348,27 +384,27 @@ Retorna o índice da cor especificada na paleta da imagem. ([mais |https://www.p colorExactAlpha(int $red, int $green, int $blue, int $alpha): int .[method] --------------------------------------------------------------------------- -Retorna o índice da cor+alfa especificada na paleta da imagem. ([mais |https://www.php.net/manual/en/function.imagecolorexactalpha]) +Retorna o índice da cor especificada + alfa na paleta da imagem. ([mais |https://www.php.net/manual/en/function.imagecolorexactalpha]) colorMatch(Image $image2): void .[method] ----------------------------------------- -Faz com que as cores da versão paleta de uma imagem correspondam mais de perto à versão de cor verdadeira. ([mais |https://www.php.net/manual/en/function.imagecolormatch]) +Corresponde as cores da paleta à segunda imagem. ([mais |https://www.php.net/manual/en/function.imagecolormatch]) colorResolve(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------ -Retorna um índice de cor para uma cor solicitada, seja a cor exata ou a alternativa mais próxima possível. ([mais |https://www.php.net/manual/en/function.imagecolorresolve]) +Retorna um índice de cor para a cor solicitada, seja a cor exata ou a alternativa mais próxima possível. ([mais |https://www.php.net/manual/en/function.imagecolorresolve]) colorResolveAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ----------------------------------------------------------------------------- -Retorna um índice de cor para uma cor solicitada, seja a cor exata ou a alternativa mais próxima possível. ([mais |https://www.php.net/manual/en/function.imagecolorresolvealpha]) +Retorna um índice de cor para a cor solicitada, seja a cor exata ou a alternativa mais próxima possível. ([mais |https://www.php.net/manual/en/function.imagecolorresolvealpha]) colorSet(int $index, int $red, int $green, int $blue): void .[method] --------------------------------------------------------------------- -Isto define o índice especificado na paleta para a cor especificada. ([mais |https://www.php.net/manual/en/function.imagecolorset]) +Define o índice especificado na paleta para a cor especificada. ([mais |https://www.php.net/manual/en/function.imagecolorset]) colorsForIndex(int $index): array .[method] @@ -378,123 +414,128 @@ Obtém a cor para um índice especificado. ([mais |https://www.php.net/manual/en colorsTotal(): int .[method] ---------------------------- -Retorna o número de cores em uma paleta de imagens ([mais |https://www.php.net/manual/en/function.imagecolorstotal]). +Retorna o número de cores na paleta de imagens. ([mais |https://www.php.net/manual/en/function.imagecolorstotal]) -colorTransparent(int $color=null): int .[method] ------------------------------------------------- +colorTransparent(?int $color=null): int .[method] +------------------------------------------------- Obtém ou define a cor transparente na imagem. ([mais |https://www.php.net/manual/en/function.imagecolortransparent]) convolution(array $matrix, float $div, float $offset): void .[method] --------------------------------------------------------------------- -Aplica uma matriz de convolução sobre a imagem, usando o coeficiente dado e o offset. ([mais |https://www.php.net/manual/en/function.imageconvolution]) +Aplica uma matriz de convolução na imagem, usando o coeficiente e o deslocamento fornecidos. ([mais |https://www.php.net/manual/en/function.imageconvolution]) .[note] -Exige *extensão de GD fundida*, por isso não é certo que funcione em todos os lugares. +Requer a presença da *Extensão GD Agrupada*, portanto, pode não funcionar em todos os lugares. copy(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH): void .[method] -------------------------------------------------------------------------------------------------- -Copia uma parte de `$src` na imagem a partir das coordenadas `$srcX`, `$srcY` com uma largura de `$srcW` e uma altura de `$srcH`. A parte definida será copiada para as coordenadas, `$dstX` e `$dstY`. ([mais |https://www.php.net/manual/en/function.imagecopy]) +Copia parte de `$src` para a imagem começando nas coordenadas `$srcX`, `$srcY` com largura `$srcW` e altura `$srcH`. A parte definida será copiada para as coordenadas `$dstX` e `$dstY`. ([mais |https://www.php.net/manual/en/function.imagecopy]) copyMerge(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] --------------------------------------------------------------------------------------------------------------------- -Copia uma parte de `$src` na imagem a partir das coordenadas `$srcX`, `$srcY` com uma largura de `$srcW` e uma altura de `$srcH`. A parte definida será copiada para as coordenadas, `$dstX` e `$dstY`. ([mais |https://www.php.net/manual/en/function.imagecopymerge]) +Copia parte de `$src` para a imagem começando nas coordenadas `$srcX`, `$srcY` com largura `$srcW` e altura `$srcH`. A parte definida será copiada para as coordenadas `$dstX` e `$dstY`. ([mais |https://www.php.net/manual/en/function.imagecopymerge]) copyMergeGray(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] ------------------------------------------------------------------------------------------------------------------------- -Copia uma parte de `$src` na imagem a partir das coordenadas `$srcX`, `$srcY` com uma largura de `$srcW` e uma altura de `$srcH`. A parte definida será copiada para as coordenadas, `$dstX` e `$dstY`. +Copia parte de `$src` para a imagem começando nas coordenadas `$srcX`, `$srcY` com largura `$srcW` e altura `$srcH`. A parte definida será copiada para as coordenadas `$dstX` e `$dstY`. -Esta função é idêntica a `copyMerge()` exceto que ao fundir preserva a tonalidade da fonte ao converter os pixels de destino em escala de cinza antes da operação de cópia. ([mais |https://www.php.net/manual/en/function.imagecopymergegray]) +Esta função é idêntica a `copyMerge()` com a exceção de que, ao mesclar, preserva a matiz da fonte convertendo os pixels de destino para escala de cinza antes da operação de cópia. ([mais |https://www.php.net/manual/en/function.imagecopymergegray]) copyResampled(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] --------------------------------------------------------------------------------------------------------------------------------- -Copia uma porção retangular de uma imagem para outra imagem, interpolando suavemente os valores de pixels para que, em particular, a redução do tamanho de uma imagem ainda retenha uma grande quantidade de clareza. +Copia uma porção retangular de uma imagem para outra imagem, interpolando suavemente os valores dos pixels para que, em particular, a redução do tamanho de uma imagem ainda retenha grande clareza. -Em outras palavras, `copyResampled()` tomará uma área retangular de `$src` de largura `$srcW` e altura `$srcH` na posição (`$srcX`,`$srcY`) e a colocará em uma área retangular de imagem de largura `$dstW` e altura `$dstH` na posição (`$dstX`,`$dstY`). +Em outras palavras, `copyResampled()` pega uma área retangular de `$src` de largura `$srcW` e altura `$srcH` na posição (`$srcX`, `$srcY`) e a coloca em uma área retangular da imagem de largura `$dstW` e altura `$dstH` na posição (`$dstX`, `$dstY`). -Se as coordenadas de origem e destino e a largura e altura diferirem, será realizado o estiramento ou encolhimento apropriado do fragmento de imagem. As coordenadas se referem ao canto superior esquerdo. Esta função pode ser usada para copiar regiões dentro da mesma imagem, mas se as regiões se sobrepuserem, os resultados serão imprevisíveis. ([mais |https://www.php.net/manual/en/function.imagecopyresampled]) +Se as coordenadas de origem e destino, largura e altura diferirem, um alongamento ou encolhimento apropriado do fragmento da imagem será realizado. As coordenadas referem-se ao canto superior esquerdo. Esta função pode ser usada para copiar regiões dentro da mesma imagem, mas se as regiões se sobrepuserem, os resultados serão imprevisíveis. ([mais |https://www.php.net/manual/en/function.imagecopyresampled]) copyResized(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] ------------------------------------------------------------------------------------------------------------------------------- -Copia uma porção retangular de uma imagem para outra imagem. Em outras palavras, `copyResized()` tomará uma área retangular de `$src` de largura `$srcW` e altura `$srcH` na posição (`$srcX`,`$srcY`) e a colocará em uma área retangular de imagem de largura `$dstW` e altura `$dstH` na posição (`$dstX`,`$dstY`). +Copia uma porção retangular de uma imagem para outra imagem. Em outras palavras, `copyResized()` pega uma área retangular de `$src` de largura `$srcW` e altura `$srcH` na posição (`$srcX`, `$srcY`) e a coloca em uma área retangular da imagem de largura `$dstW` e altura `$dstH` na posição (`$dstX`, `$dstY`). -Se as coordenadas de origem e destino e a largura e altura diferirem, será realizado o estiramento ou encolhimento apropriado do fragmento de imagem. As coordenadas se referem ao canto superior esquerdo. Esta função pode ser usada para copiar regiões dentro da mesma imagem, mas se as regiões se sobrepuserem, os resultados serão imprevisíveis. ([mais |https://www.php.net/manual/en/function.imagecopyresized]) +Se as coordenadas de origem e destino, largura e altura diferirem, um alongamento ou encolhimento apropriado do fragmento da imagem será realizado. As coordenadas referem-se ao canto superior esquerdo. Esta função pode ser usada para copiar regiões dentro da mesma imagem, mas se as regiões se sobrepuserem, os resultados serão imprevisíveis. ([mais |https://www.php.net/manual/en/function.imagecopyresized]) crop(int|string $left, int|string $top, int|string $width, int|string $height): Image .[method] ----------------------------------------------------------------------------------------------- -Cortar uma imagem para a área retangular dada. As dimensões podem ser passadas como números inteiros em pixels ou cadeias em porcentagem (ou seja, `'50%'`). +Recorta uma imagem para a área retangular fornecida. As dimensões podem ser especificadas como inteiros em pixels ou strings em porcentagens (por exemplo, `'50%'`). -cropAuto(int $mode=-1, float $threshold=.5, int $color=-1): Image .[method] ---------------------------------------------------------------------------- -Automaticamente cultiva uma imagem de acordo com o dado `$mode`. ([mais |https://www.php.net/manual/en/function.imagecropauto]) +cropAuto(int $mode=-1, float $threshold=.5, ?ImageColor $color=null): Image .[method] +------------------------------------------------------------------------------------- +Recorta automaticamente uma imagem de acordo com o `$mode` fornecido. ([mais |https://www.php.net/manual/en/function.imagecropauto]) -ellipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------- +ellipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------- Desenha uma elipse centrada nas coordenadas especificadas. ([mais |https://www.php.net/manual/en/function.imageellipse]) -fill(int $x, int $y, int $color): void .[method] ------------------------------------------------- -Realiza um enchimento de inundação a partir da coordenada dada (no canto superior esquerdo é 0, 0) com o dado `$color` na imagem. ([mais |https://www.php.net/manual/en/function.imagefill]) +fill(int $x, int $y, ImageColor $color): void .[method] +------------------------------------------------------- +Preenche uma região começando na coordenada fornecida (canto superior esquerdo é 0, 0) com a `$color` fornecida. ([mais |https://www.php.net/manual/en/function.imagefill]) -filledArc(int $cx, int $cy, int $w, int $h, int $s, int $e, int $color, int $style): void .[method] ---------------------------------------------------------------------------------------------------- -Desenha um arco parcial centrado na coordenada especificada na imagem. ([mais |https://www.php.net/manual/en/function.imagefilledarc]) +filledArc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color, int $style): void .[method] +--------------------------------------------------------------------------------------------------------------------------------------------- +Desenha um arco parcial centrado nas coordenadas especificadas. ([mais |https://www.php.net/manual/en/function.imagefilledarc]) -filledEllipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------------- -Desenha uma elipse centrada na coordenada especificada na imagem. ([mais |https://www.php.net/manual/en/function.imagefilledellipse]) +filledEllipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------------- +Desenha uma elipse preenchida centrada nas coordenadas especificadas. ([mais |https://www.php.net/manual/en/function.imagefilledellipse]) -filledPolygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------------- -Cria um polígono cheio na imagem de $. ([mais |https://www.php.net/manual/en/function.imagefilledpolygon]) +filledPolygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------------- +Cria um polígono preenchido na imagem. ([mais |https://www.php.net/manual/en/function.imagefilledpolygon]) -filledRectangle(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] -------------------------------------------------------------------------------- -Cria um retângulo preenchido com `$color` na imagem começando no ponto 1 e terminando no ponto 2. 0, 0 é o canto superior esquerdo da imagem. ([mais |https://www.php.net/manual/en/function.imagefilledrectangle]) +filledRectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------- +Cria um retângulo preenchido com `$color` na imagem começando no ponto `$x1` & `$y1` e terminando no ponto `$x2` & `$y2`. O ponto 0, 0 é o canto superior esquerdo da imagem. ([mais |https://www.php.net/manual/en/function.imagefilledrectangle]) -fillToBorder(int $x, int $y, int $border, int $color): void .[method] ---------------------------------------------------------------------- -Realiza um enchimento de inundação cuja cor da borda é definida por `$border`. O ponto de partida para o preenchimento é `$x`, `$y` (canto superior esquerdo é 0, 0) e a região é preenchida com a cor `$color`. ([mais |https://www.php.net/manual/en/function.imagefilltoborder]) +filledRectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------------- +Cria um retângulo preenchido com `$color` na imagem começando no ponto `$left` & `$top` com largura `$width` e altura `$height`. O ponto 0, 0 é o canto superior esquerdo da imagem. + + +fillToBorder(int $x, int $y, int $border, ImageColor $color): void .[method] +---------------------------------------------------------------------------- +Realiza um preenchimento cuja cor da borda é definida por `$border`. O ponto inicial para o preenchimento é `$x`, `$y` (canto superior esquerdo é 0, 0) e a região é preenchida com a cor `$color`. ([mais |https://www.php.net/manual/en/function.imagefilltoborder]) filter(int $filtertype, int ...$args): void .[method] ----------------------------------------------------- -Aplica o filtro dado `$filtertype` na imagem. ([mais |https://www.php.net/manual/en/function.imagefilter]) +Aplica o filtro `$filtertype` fornecido à imagem. ([mais |https://www.php.net/manual/en/function.imagefilter]) flip(int $mode): void .[method] ------------------------------- -Vira a imagem usando o dado `$mode`. ([mais |https://www.php.net/manual/en/function.imageflip]) +Inverte a imagem usando o `$mode` fornecido. ([mais |https://www.php.net/manual/en/function.imageflip]) -ftText(int $size, int $angle, int $x, int $y, int $col, string $fontFile, string $text, array $extrainfo=null): array .[method] -------------------------------------------------------------------------------------------------------------------------------- -Escrever texto na imagem usando fontes usando FreeType 2. ([mais |https://www.php.net/manual/en/function.imagefttext]) +ftText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +---------------------------------------------------------------------------------------------------------------------------------------- +Escreve texto na imagem. ([mais |https://www.php.net/manual/en/function.imagefttext]) gammaCorrect(float $inputgamma, float $outputgamma): void .[method] ------------------------------------------------------------------- -Aplica correção gama à imagem dada uma gama de entrada e uma gama de saída. ([mais |https://www.php.net/manual/en/function.imagegammacorrect]) +Aplica correção gama à imagem em relação ao gama de entrada e saída. ([mais |https://www.php.net/manual/en/function.imagegammacorrect]) getClip(): array .[method] -------------------------- -Recupera o atual retângulo de recorte, ou seja, a área além da qual nenhum pixel será desenhado. ([mais |https://www.php.net/manual/en/function.imagegetclip]) +Retorna o recorte atual, ou seja, a área além da qual nenhum pixel será desenhado. ([mais |https://www.php.net/manual/en/function.imagegetclip]) getHeight(): int .[method] @@ -504,7 +545,7 @@ Retorna a altura da imagem. getImageResource(): resource|GdImage .[method] ---------------------------------------------- -Devolve o recurso original. +Retorna o resource original. getWidth(): int .[method] @@ -512,162 +553,157 @@ getWidth(): int .[method] Retorna a largura da imagem. -interlace(int $interlace=null): int .[method] ---------------------------------------------- -Liga ou desliga o fio entrelaçado. Se a parte entrelaçada for definida e a imagem for usada como uma imagem JPEG, a imagem é criada como um JPEG progressivo. ([mais |https://www.php.net/manual/en/function.imageinterlace]) +interlace(?int $interlace=null): int .[method] +---------------------------------------------- +Ativa ou desativa o modo entrelaçado. Se o modo entrelaçado estiver definido e a imagem for salva como JPEG, ela será salva como JPEG progressivo. ([mais |https://www.php.net/manual/en/function.imageinterlace]) isTrueColor(): bool .[method] ----------------------------- -Descobre se a imagem é uma verdadeira cor. ([mais |https://www.php.net/manual/en/function.imageistruecolor]) +Verifica se a imagem é truecolor. ([mais |https://www.php.net/manual/en/function.imageistruecolor]) layerEffect(int $effect): void .[method] ---------------------------------------- -Coloque a bandeira de mistura alfa para usar efeitos de camadas. ([mais |https://www.php.net/manual/en/function.imagelayereffect]) +Define o sinalizador de mesclagem alfa para usar efeitos de camadas. ([mais |https://www.php.net/manual/en/function.imagelayereffect]) -line(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] --------------------------------------------------------------------- -Traça uma linha entre os dois pontos dados. ([mais |https://www.php.net/manual/en/function.imageline]) +line(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +--------------------------------------------------------------------------- +Desenha uma linha entre dois pontos fornecidos. ([mais |https://www.php.net/manual/en/function.imageline]) -openPolygon(array $points, int $numPoints, int $color): void .[method] ----------------------------------------------------------------------- -Desenha um polígono aberto sobre a imagem. Ao contrário de `polygon()`, nenhuma linha é traçada entre o último e o primeiro ponto. ([mais |https://www.php.net/manual/en/function.imageopenpolygon]) +openPolygon(array $points, ImageColor $color): void .[method] +------------------------------------------------------------- +Desenha um polígono aberto na imagem. Ao contrário de `polygon()`, nenhuma linha é desenhada entre o último e o primeiro ponto. ([mais |https://www.php.net/manual/en/function.imageopenpolygon]) paletteCopy(Image $source): void .[method] ------------------------------------------ -Copia a paleta do site `$source` para a imagem. ([mais |https://www.php.net/manual/en/function.imagepalettecopy]) +Copia a paleta de `$source` para a imagem. ([mais |https://www.php.net/manual/en/function.imagepalettecopy]) paletteToTrueColor(): void .[method] ------------------------------------ -Converte uma imagem baseada em paleta, criada por funções como `create()`, para uma imagem colorida verdadeira, como `createtruecolor()`. ([mais |https://www.php.net/manual/en/function.imagepalettetotruecolor]) +Converte uma imagem baseada em paleta em uma imagem truecolor. ([mais |https://www.php.net/manual/en/function.imagepalettetotruecolor]) place(Image $image, int|string $left=0, int|string $top=0, int $opacity=100): Image .[method] --------------------------------------------------------------------------------------------- -Copia `$image` para a imagem nas coordenadas `$left` e `$top`. As coordenadas podem ser passadas como números inteiros em pixels ou cadeias em porcentagem (ou seja, `'50%'`). +Copia `$image` para a imagem nas coordenadas `$left` e `$top`. As coordenadas podem ser especificadas como inteiros em pixels ou strings em porcentagens (por exemplo, `'50%'`). -polygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------- +polygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------- Cria um polígono na imagem. ([mais |https://www.php.net/manual/en/function.imagepolygon]) -rectangle(int $x1, int $y1, int $x2, int $y2, int $col): void .[method] ------------------------------------------------------------------------ -Cria um retângulo começando nas coordenadas especificadas. ([mais |https://www.php.net/manual/en/function.imagerectangle]) +rectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------- +Cria um retângulo nas coordenadas especificadas. ([mais |https://www.php.net/manual/en/function.imagerectangle]) + + +rectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------- +Cria um retângulo nas coordenadas especificadas. resize(int|string $width, int|string $height, int $flags=Image::OrSmaller): Image .[method] ------------------------------------------------------------------------------------------- -Escala uma imagem, veja [mais informações |#Image Resize]. As dimensões podem ser passadas como números inteiros em pixels ou cadeias em porcentagem (ou seja, `'50%'`). +Redimensiona a imagem, [mais informações |#Redimensionamento]. As dimensões podem ser especificadas como inteiros em pixels ou strings em porcentagens (por exemplo, `'50%'`). -resolution(int $resX=null, int $resY=null): mixed .[method] ------------------------------------------------------------ -Permite definir e obter a resolução de uma imagem em DPI (dots per inch). Se nenhum dos parâmetros opcionais for dado, a resolução atual é devolvida como matriz indexada. Se apenas `$resX` for dado, a resolução horizontal e vertical é definida para este valor. Se ambos os parâmetros opcionais são dados, a resolução horizontal e vertical são definidas para estes valores, respectivamente. +resolution(?int $resX=null, ?int $resY=null): mixed .[method] +------------------------------------------------------------- +Define ou retorna a resolução da imagem em DPI (pontos por polegada). Se nenhum dos parâmetros opcionais for especificado, a resolução atual é retornada como um array indexado. Se apenas `$resX` for especificado, as resoluções horizontal e vertical são definidas para este valor. Se ambos os parâmetros opcionais forem especificados, as resoluções horizontal e vertical são definidas para esses valores. -A resolução só é usada como meta informação quando as imagens são lidas e escritas em formatos que suportam este tipo de informação (curently PNG e JPEG). Não afeta nenhuma operação de desenho. A resolução padrão para novas imagens é 96 DPI. ([mais |https://www.php.net/manual/en/function.imageresolution]) +A resolução é usada apenas como metainformação quando as imagens são lidas e escritas em formatos que suportam este tipo de informação (atualmente PNG e JPEG). Não afeta nenhuma operação de desenho. A resolução padrão para novas imagens é 96 DPI. ([mais |https://www.php.net/manual/en/function.imageresolution]) rotate(float $angle, int $backgroundColor): Image .[method] ----------------------------------------------------------- -Gira a imagem usando o dado `$angle` em graus. O centro de rotação é o centro da imagem, e a imagem rodada pode ter dimensões diferentes das da imagem original. ([mais |https://www.php.net/manual/en/function.imagerotate]) +Rotaciona a imagem usando o `$angle` especificado em graus. O centro de rotação é o centro da imagem e a imagem rotacionada pode ter dimensões diferentes da imagem original. ([mais |https://www.php.net/manual/en/function.imagerotate]) .[note] -Exige *extensão de GD fundida*, por isso não é certo que funcione em todos os lugares. +Requer a presença da *Extensão GD Agrupada*, portanto, pode não funcionar em todos os lugares. -save(string $file, int $quality=null, int $type=null): void .[method] ---------------------------------------------------------------------- -Salva uma imagem em um arquivo. +save(string $file, ?int $quality=null, ?int $type=null): void .[method] +----------------------------------------------------------------------- +Salva a imagem em um arquivo. -A qualidade da compressão está na faixa 0..100 para JPEG (padrão 85), WEBP (padrão 80) e AVIF (padrão 30) e 0..9 para PNG (padrão 9). Se o tipo não for óbvio a partir da extensão do arquivo, você pode especificá-lo usando uma das constantes `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF`, e `Image::BMP`. +A qualidade da compressão está na faixa de 0..100 para JPEG (padrão 85), WEBP (padrão 80) e AVIF (padrão 30) e 0..9 para PNG (padrão 9). Se o tipo não for óbvio pela extensão do arquivo, você pode especificá-lo usando uma das constantes `ImageType`. saveAlpha(bool $saveflag): void .[method] ----------------------------------------- -Define a bandeira que determina se deve reter informações completas do canal alfa (em oposição à transparência monocromática) ao salvar imagens PNG. +Define o sinalizador se deve preservar informações completas do canal alfa ao salvar imagens PNG (em oposição à transparência de cor única). -O alfabetização tem que ser desativado (`alphaBlending(false)`) para manter o canal alfa em primeiro lugar. ([mais |https://www.php.net/manual/en/function.imagesavealpha]) +A mesclagem alfa deve ser desativada (`alphaBlending(false)`) para que o canal alfa seja mantido em primeiro lugar. ([mais |https://www.php.net/manual/en/function.imagesavealpha]) scale(int $newWidth, int $newHeight=-1, int $mode=IMG_BILINEAR_FIXED): Image .[method] -------------------------------------------------------------------------------------- -Escala uma imagem usando o algoritmo de interpolação dado. ([mais |https://www.php.net/manual/en/function.imagescale]) +Escala uma imagem usando o algoritmo de interpolação fornecido. ([mais |https://www.php.net/manual/en/function.imagescale]) -send(int $type=Image::JPEG, int $quality=null): void .[method] --------------------------------------------------------------- -Produz uma imagem para o navegador. +send(int $type=ImageType::JPEG, ?int $quality=null): void .[method] +------------------------------------------------------------------- +Envia a imagem para o navegador. -A qualidade da compressão está na faixa 0..100 para JPEG (padrão 85), WEBP (padrão 80) e AVIF (padrão 30) e 0..9 para PNG (padrão 9). O tipo é uma das constantes `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::AVIF`, `Image::WEBP`, e `Image::BMP`. +A qualidade da compressão está na faixa de 0..100 para JPEG (padrão 85), WEBP (padrão 80) e AVIF (padrão 30) e 0..9 para PNG (padrão 9). setBrush(Image $brush): void .[method] -------------------------------------- -Define a imagem do pincel a ser utilizada por todas as funções de desenho de linhas (como `line()` e `polygon()`) ao desenhar com as cores especiais IMG_COLOR_BRUSHED ou IMG_COLOR_STYLEDBRUSHED. ([mais |https://www.php.net/manual/en/function.imagesetbrush]) +Define a imagem do pincel a ser usada em todas as funções de desenho de linha (como `line()` e `polygon()`) ao desenhar com as cores especiais IMG_COLOR_BRUSHED ou IMG_COLOR_STYLEDBRUSHED. ([mais |https://www.php.net/manual/en/function.imagesetbrush]) setClip(int $x1, int $y1, int $x2, int $y2): void .[method] ----------------------------------------------------------- -Define o retângulo de recorte atual, ou seja, a área além da qual nenhum pixel será desenhado. ([mais |https://www.php.net/manual/en/function.imagesetclip]) +Define o recorte atual, ou seja, a área além da qual nenhum pixel será desenhado. ([mais |https://www.php.net/manual/en/function.imagesetclip]) setInterpolation(int $method=IMG_BILINEAR_FIXED): void .[method] ---------------------------------------------------------------- -Define o método de interpolação que afeta os métodos `rotate()` e `affine()`. ([mais |https://www.php.net/manual/en/function.imagesetinterpolation]) +Define o método de interpolação, que afeta os métodos `rotate()` e `affine()`. ([mais |https://www.php.net/manual/en/function.imagesetinterpolation]) -setPixel(int $x, int $y, int $color): void .[method] ----------------------------------------------------- -Traça um pixel na coordenada especificada. ([mais |https://www.php.net/manual/en/function.imagesetpixel]) +setPixel(int $x, int $y, ImageColor $color): void .[method] +----------------------------------------------------------- +Desenha um pixel na coordenada especificada. ([mais |https://www.php.net/manual/en/function.imagesetpixel]) setStyle(array $style): void .[method] -------------------------------------- -Define o estilo a ser usado por todas as funções de desenho de linhas (tais como `line()` e `polygon()`) ao desenhar com a cor especial IMG_COLOR_STYLED ou linhas de imagens com a cor IMG_COLOR_STYLEDBRUSHED. ([mais |https://www.php.net/manual/en/function.imagesetstyle]) +Define o estilo a ser usado por todas as funções de desenho de linha (como `line()` e `polygon()`) ao desenhar com a cor especial IMG_COLOR_STYLED ou linhas de imagens com a cor IMG_COLOR_STYLEDBRUSHED. ([mais |https://www.php.net/manual/en/function.imagesetstyle]) setThickness(int $thickness): void .[method] -------------------------------------------- -Define a espessura das linhas desenhadas ao desenhar retângulos, polígonos, arcos, etc. para `$thickness` pixels. ([mais |https://www.php.net/manual/en/function.imagesetthickness]) +Define a espessura das linhas ao desenhar retângulos, polígonos, arcos, etc. para `$thickness` pixels. ([mais |https://www.php.net/manual/en/function.imagesetthickness]) setTile(Image $tile): void .[method] ------------------------------------ -Define a imagem da telha a ser usada por todas as funções de preenchimento da região (como `fill()` e `filledPolygon()`) ao preencher com a cor especial IMG_COLOR_TILED. +Define a imagem do ladrilho a ser usada em todas as funções de preenchimento de região (como `fill()` e `filledPolygon()`) ao preencher com a cor especial IMG_COLOR_TILED. -Uma telha é uma imagem usada para preencher uma área com um padrão repetido. Qualquer imagem pode ser usada como azulejo, e ao definir o índice de cor transparente da imagem do azulejo com `colorTransparent()`, um azulejo permite que certas partes da área subjacente brilhem através dele podem ser criadas. ([mais |https://www.php.net/manual/en/function.imagesettile]) +Um ladrilho é uma imagem usada para preencher uma área com um padrão repetido. Qualquer imagem pode ser usada como ladrilho e, definindo o índice de cor transparente da imagem do ladrilho com `colorTransparent()`, pode-se criar um ladrilho onde certas partes da área subjacente transparecerão. ([mais |https://www.php.net/manual/en/function.imagesettile]) sharpen(): Image .[method] -------------------------- -Aguça um pouco a imagem. +Torna a imagem mais nítida. .[note] -Requer uma extensão GD *Bundled GD, por isso não é certo que funcione em todos os lugares. - - -string(int $font, int $x, int $y, string $str, int $col): void .[method] ------------------------------------------------------------------------- -Traça um fio nas coordenadas dadas. ([mais |https://www.php.net/manual/en/function.imagestring]) - +Requer a presença da *Extensão GD Agrupada*, portanto, pode não funcionar em todos os lugares. -stringUp(int $font, int $x, int $y, string $s, int $col): void .[method] ------------------------------------------------------------------------- -Traça um cordel verticalmente nas coordenadas dadas. ([mais |https://www.php.net/manual/en/function.imagestringup]) +toString(int $type=ImageType::JPEG, ?int $quality=null): string .[method] +------------------------------------------------------------------------- +Salva a imagem em uma string. -toString(int $type=Image::JPEG, int $quality=null): string .[method] --------------------------------------------------------------------- -Produz uma imagem a fio. - -A qualidade da compressão está na faixa 0..100 para JPEG (padrão 85), WEBP (padrão 80) e AVIF (padrão 30) e 0..9 para PNG (padrão 9). O tipo é uma das constantes `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::AVIF`, `Image::WEBP`, e `Image::BMP`. +A qualidade da compressão está na faixa de 0..100 para JPEG (padrão 85), WEBP (padrão 80) e AVIF (padrão 30) e 0..9 para PNG (padrão 9). trueColorToPalette(bool $dither, int $ncolors): void .[method] @@ -675,6 +711,6 @@ trueColorToPalette(bool $dither, int $ncolors): void .[method] Converte uma imagem truecolor em uma imagem de paleta. ([mais |https://www.php.net/manual/en/function.imagetruecolortopalette]) -ttfText(int $size, int $angle, int $x, int $y, int $color, string $fontfile, string $text): array .[method] ------------------------------------------------------------------------------------------------------------ -Escreve o texto dado na imagem usando fontes TrueType. ([mais |https://www.php.net/manual/en/function.imagettftext]) +ttfText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +----------------------------------------------------------------------------------------------------------------------------------------- +Imprime o texto fornecido na imagem. ([mais |https://www.php.net/manual/en/function.imagettftext]) diff --git a/utils/pt/iterables.texy b/utils/pt/iterables.texy new file mode 100644 index 0000000000..a89d05fb0e --- /dev/null +++ b/utils/pt/iterables.texy @@ -0,0 +1,170 @@ +Trabalhando com iteradores +************************** + +.[perex]{data-version:4.0.4} +[api:Nette\Utils\Iterables] é uma classe estática com funções para trabalhar com iteradores. Seu equivalente para arrays é [Nette\Utils\Arrays|arrays]. + + +Instalação: + +```shell +composer require nette/utils +``` + +Todos os exemplos assumem que um alias foi criado: + +```php +use Nette\Utils\Iterables; +``` + + +contains(iterable $iterable, $value): bool .[method] +---------------------------------------------------- + +Procura o valor especificado no iterador. Usa comparação estrita (`===`) para verificar a correspondência. Retorna `true` se o valor for encontrado, caso contrário `false`. + +```php +Iterables::contains(new ArrayIterator([1, 2, 3]), 1); // true +Iterables::contains(new ArrayIterator([1, 2, 3]), '1'); // false +``` + +Este método é útil quando você precisa descobrir rapidamente se um valor específico está no iterador sem ter que percorrer todos os elementos manualmente. + + +containsKey(iterable $iterable, $key): bool .[method] +----------------------------------------------------- + +Procura a chave especificada no iterador. Usa comparação estrita (`===`) para verificar a correspondência. Retorna `true` se a chave for encontrada, caso contrário `false`. + +```php +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 0); // true +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 4); // false +``` + + +every(iterable $iterable, callable $predicate): bool .[method] +-------------------------------------------------------------- + +Verifica se todos os elementos do iterador satisfazem a condição definida em `$predicate`. A função `$predicate` tem a assinatura `function ($value, $key, iterable $iterable): bool` e deve retornar `true` para cada elemento para que o método `every()` retorne `true`. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isBelowThreshold = fn($value) => $value < 40; +$res = Iterables::every($iterator, $isBelowThreshold); // true +``` + +Este método é útil para verificar se todos os elementos em uma coleção satisfazem uma determinada condição, por exemplo, se todos os números são menores que um determinado valor. + + +filter(iterable $iterable, callable $predicate): Generator .[method] +-------------------------------------------------------------------- + +Cria um novo iterador que contém apenas os elementos do iterador original que satisfazem a condição definida em `$predicate`. A função `$predicate` tem a assinatura `function ($value, $key, iterable $iterable): bool` e deve retornar `true` para os elementos que devem ser mantidos. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::filter($iterator, fn($v) => $v < 3); +// 1, 2 +``` + +O método utiliza um gerador, o que significa que a filtragem ocorre gradualmente ao percorrer o resultado. Isso é eficiente em termos de memória e permite processar coleções muito grandes. Se você não percorrer todos os elementos do iterador resultante, economizará poder de processamento, pois nem todos os elementos do iterador original serão processados. + + +first(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------------- + +Retorna o primeiro elemento do iterador. Se `$predicate` for especificado, retorna o primeiro elemento que satisfaz a condição dada. A função `$predicate` tem a assinatura `function ($value, $key, iterable $iterable): bool`. Se nenhum elemento correspondente for encontrado, a função `$else` é chamada (se especificada) e seu resultado é retornado. Se `$else` não for especificado, retorna `null`. + +```php +Iterables::first(new ArrayIterator([1, 2, 3])); // 1 +Iterables::first(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 3 +Iterables::first(new ArrayIterator([])); // null +Iterables::first(new ArrayIterator([]), else: fn() => false); // false +``` + +Este método é útil quando você precisa obter rapidamente o primeiro elemento de uma coleção ou o primeiro elemento que satisfaz uma determinada condição, sem ter que percorrer toda a coleção manualmente. + + +firstKey(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +---------------------------------------------------------------------------------------------- + +Retorna a chave do primeiro elemento do iterador. Se `$predicate` for especificado, retorna a chave do primeiro elemento que satisfaz a condição dada. A função `$predicate` tem a assinatura `function ($value, $key, iterable $iterable): bool`. Se nenhum elemento correspondente for encontrado, a função `$else` é chamada (se especificada) e seu resultado é retornado. Se `$else` não for especificado, retorna `null`. + +```php +Iterables::firstKey(new ArrayIterator([1, 2, 3])); // 0 +Iterables::firstKey(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 2 +Iterables::firstKey(new ArrayIterator(['a' => 1, 'b' => 2])); // 'a' +Iterables::firstKey(new ArrayIterator([])); // null +``` + + +map(iterable $iterable, callable $transformer): Generator .[method] +------------------------------------------------------------------- + +Cria um novo iterador aplicando a função `$transformer` a cada elemento do iterador original. A função `$transformer` tem a assinatura `function ($value, $key, iterable $iterable): mixed` e seu valor de retorno é usado como o novo valor do elemento. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::map($iterator, fn($v) => $v * 2); +// 2, 4, 6 +``` + +O método utiliza um gerador, o que significa que a transformação ocorre gradualmente ao percorrer o resultado. Isso é eficiente em termos de memória e permite processar coleções muito grandes. Se você não percorrer todos os elementos do iterador resultante, economizará poder de processamento, pois nem todos os elementos do iterador original serão processados. + + +mapWithKeys(iterable $iterable, callable $transformer): Generator .[method] +--------------------------------------------------------------------------- + +Cria um novo iterador transformando os valores e chaves do iterador original. A função `$transformer` tem a assinatura `function ($value, $key, iterable $iterable): ?array{$newKey, $newValue}`. Se `$transformer` retornar `null`, o elemento é ignorado. Para os elementos mantidos, o primeiro elemento do array retornado é usado como a nova chave e o segundo elemento como o novo valor. + +```php +$iterator = new ArrayIterator(['a' => 1, 'b' => 2]); +$iterator = Iterables::mapWithKeys($iterator, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +Assim como `map()`, este método utiliza um gerador para processamento gradual e trabalho eficiente com a memória. Isso permite trabalhar com grandes coleções e economizar poder de processamento ao percorrer parcialmente o resultado. + + +memoize(iterable $iterable): IteratorAggregate .[method] +-------------------------------------------------------- + +Cria um invólucro em torno do iterador que armazena em cache suas chaves e valores durante a iteração. Isso permite a iteração repetida dos dados sem a necessidade de percorrer novamente a fonte de dados original. + +```php +$iterator = /* dados que não podem ser iterados mais de uma vez */; +$memoized = Iterables::memoize($iterator); +// Agora você pode iterar $memoized várias vezes sem perder dados +``` + +Este método é útil em situações em que você precisa percorrer o mesmo conjunto de dados várias vezes, mas o iterador original não permite iteração repetida ou a passagem repetida seria cara (por exemplo, ao ler dados de um banco de dados ou arquivo). + + +some(iterable $iterable, callable $predicate): bool .[method] +------------------------------------------------------------- + +Verifica se pelo menos um elemento do iterador satisfaz a condição definida em `$predicate`. A função `$predicate` tem a assinatura `function ($value, $key, iterable $iterable): bool` e deve retornar `true` para pelo menos um elemento para que o método `some()` retorne `true`. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isEven = fn($value) => $value % 2 === 0; +$res = Iterables::some($iterator, $isEven); // true +``` + +Este método é útil para verificar rapidamente se existe pelo menos um elemento na coleção que satisfaz uma determinada condição, por exemplo, se a coleção contém pelo menos um número par. + +Veja [#every()]. + + +toIterator(iterable $iterable): Iterator .[method] +-------------------------------------------------- + +Converte qualquer objeto iterável (array, Traversable) em um Iterator. Se a entrada já for um Iterator, retorna-o sem alterações. + +```php +$array = [1, 2, 3]; +$iterator = Iterables::toIterator($array); +// Agora você tem um Iterator em vez de um array +``` + +Este método é útil quando você precisa garantir que tem um Iterator disponível, independentemente do tipo de dados de entrada. Isso pode ser útil ao criar funções que trabalham com diferentes tipos de dados iteráveis. diff --git a/utils/pt/json.texy b/utils/pt/json.texy index 58bbc765a3..2e4105cb19 100644 --- a/utils/pt/json.texy +++ b/utils/pt/json.texy @@ -1,8 +1,8 @@ -Funções do JSON -*************** +Trabalhando com JSON +******************** .[perex] -[api:Nette\Utils\Json] é uma classe estática com funções de codificação e decodificação JSON. Ela trata de vulnerabilidades em diferentes versões do PHP e lança exceções sobre erros. +[api:Nette\Utils\Json] é uma classe estática com funções para codificar e decodificar o formato JSON. Ela lida com vulnerabilidades de diferentes versões do PHP e lança exceções em caso de erros. Instalação: @@ -11,15 +11,15 @@ Instalação: composer require nette/utils ``` -Todos os exemplos assumem que a seguinte classe está definida: +Todos os exemplos assumem que um alias foi criado: ```php use Nette\Utils\Json; ``` -Utilização .[#toc-usage] -======================== +Uso +=== encode(mixed $value, bool $pretty=false, bool $asciiSafe=false, bool $htmlSafe=false, bool $forceObjects=false): string .[method] @@ -27,14 +27,14 @@ encode(mixed $value, bool $pretty=false, bool $asciiSafe=false, bool $htmlSafe=f Converte `$value` para o formato JSON. -Quando `$pretty` é configurado, ele formata o JSON para facilitar a leitura e a clareza: +Quando `$pretty` está definido, formata o JSON para facilitar a leitura e a clareza: ```php -Json::encode($value); // devolve JSON -Json::encode($value, pretty: true); // devolve JSON mais claro +Json::encode($value); // retorna JSON +Json::encode($value, pretty: true); // retorna JSON mais legível ``` -Quando `$asciiSafe` é definido, gera a saída ASCII, ou seja, substitui os caracteres unicode por seqüências `\uxxxx`: +Com `$asciiSafe`, gera a saída em ASCII, ou seja, substitui caracteres unicode por sequências `\uxxxx`: ```php Json::encode('žluťoučký', asciiSafe: true); @@ -48,7 +48,7 @@ Json::encode('onesendJson($data)`, que pode ser chamado, por exemplo, no método `action*()`, veja [Enviando uma Resposta |application:presenters#Sending a Response]. +Você pode usar o método `$this->sendJson($data)`, que pode ser chamado, por exemplo, no método `action*()`, veja [Enviar uma resposta |application:presenters#Envio da resposta]. diff --git a/utils/pt/paginator.texy b/utils/pt/paginator.texy index 3b4aedcaeb..5997405377 100644 --- a/utils/pt/paginator.texy +++ b/utils/pt/paginator.texy @@ -1,8 +1,8 @@ -Paginador +Paginator ********* .[perex] -Necessidade de uma listagem de dados? Como a matemática por trás da paginação pode ser complicada, [api:Nette\Utils\Paginator] o ajudará. +Precisa paginar a exibição de dados? Como a matemática de paginação pode ser complicada, [api:Nette\Utils\Paginator] irá ajudá-lo com isso. Instalação: @@ -11,38 +11,38 @@ Instalação: composer require nette/utils ``` -Vamos criar um objeto de paginação e definir informações básicas para ele: +Criamos um objeto paginador e definimos suas informações básicas: ```php $paginator = new Nette\Utils\Paginator; -$paginator->setPage(1); // o número da página atual (numerada a partir de 1) -$paginator->setItemsPerPage(30); // o número de registros por página -$paginator->setItemCount(356); // o número total de registros (se disponível) +$paginator->setPage(1); // número da página atual +$paginator->setItemsPerPage(30); // número de itens por página +$paginator->setItemCount(356); // número total de itens, se conhecido ``` -As páginas são numeradas a partir de 1. Podemos mudá-las usando `setBase()`: +As páginas são numeradas a partir de 1. Podemos alterar isso usando `setBase()`: ```php -$paginator->setBase(0); // numerada a partir de 0 +$paginator->setBase(0); // numeramos a partir de 0 ``` -O objeto fornecerá agora todas as informações básicas úteis para a criação de um paginador. Você pode, por exemplo, passá-lo para um modelo e usá-lo lá. +O objeto agora fornecerá todas as informações básicas úteis ao criar um paginador. Você pode, por exemplo, passá-lo para um template e usá-lo lá. ```php -$paginator->isFirst(); // esta é a primeira página? -$paginator->isLast(); // esta é a última página? +$paginator->isFirst(); // estamos na primeira página? +$paginator->isLast(); // estamos na última página? $paginator->getPage(); // número da página atual -$paginator->getFirstPage(); // o número da primeira página -$paginator->getLastPage(); // o número da última página -$paginator->getFirstItemOnPage(); // número seqüencial do primeiro item da página -$paginator->getLastItemOnPage(); // número seqüencial do último item da página -$paginator->getPageIndex(); // número de página atual se numerado a partir de 0 -$paginator->getPageCount(); // o número total de páginas -$paginator->getItemsPerPage(); // o número de registros por página -$paginator->getItemCount(); // o número total de registros (se disponível) +$paginator->getFirstPage(); // número da primeira página +$paginator->getLastPage(); // número da última página +$paginator->getFirstItemOnPage(); // número de série do primeiro item na página +$paginator->getLastItemOnPage(); // número de série do último item na página +$paginator->getPageIndex(); // número da página atual numerado a partir de 0 +$paginator->getPageCount(); // número total de páginas +$paginator->getItemsPerPage(); // número de itens por página +$paginator->getItemCount(); // número total de itens, se conhecido ``` -O paginador ajudará na formulação da consulta SQL. Os métodos `getLength()` e `getOffset()` retornam os valores que você pode usar nas cláusulas LIMIT e OFFSET: +O paginador ajuda na formulação de consultas SQL. Os métodos `getLength()` e `getOffset()` retornam valores que usamos nas cláusulas LIMIT e OFFSET: ```php $result = $database->query( @@ -52,7 +52,7 @@ $result = $database->query( ); ``` -Se você precisar paginar em ordem inversa, ou seja, a página nº. 1 corresponde ao deslocamento mais alto, você pode usar `getCountdownOffset()`: +Se precisarmos paginar na ordem inversa, ou seja, a página nº 1 corresponde ao maior offset, usamos `getCountdownOffset()`: ```php $result = $database->query( @@ -62,4 +62,4 @@ $result = $database->query( ); ``` -Um exemplo de uso na aplicação pode ser encontrado no livro de receitas [Paginação de Resultados do Banco de Dados |best-practices:pagination]. +Um exemplo de uso em uma aplicação pode ser encontrado no cookbook [Paginação de resultados do banco de dados |best-practices:pagination]. diff --git a/utils/pt/random.texy b/utils/pt/random.texy index 78250671fe..44948e478c 100644 --- a/utils/pt/random.texy +++ b/utils/pt/random.texy @@ -1,8 +1,8 @@ -Gerador de Cordas Aleatórias -**************************** +Geração de strings aleatórias +***************************** .[perex] -[api:Nette\Utils\Random] é uma classe estática para gerar cordas criptográficas pseudo-aleatórias seguras. +[api:Nette\Utils\Random] é uma classe estática para gerar strings pseudo-aleatórias criptograficamente seguras. Instalação: @@ -15,7 +15,7 @@ composer require nette/utils generate(int $length=10, string $charlist=`'0-9a-z'`): string .[method] ----------------------------------------------------------------------- -Gera uma seqüência aleatória de caracteres de determinado comprimento a partir de caracteres especificados no segundo argumento. Suporta intervalos, tais como `0-9` ou `A-Z`. +Gera uma string aleatória do comprimento fornecido a partir dos caracteres especificados pelo parâmetro `$charlist`. Intervalos escritos como `0-9` também podem ser usados. ```php use Nette\Utils\Random; diff --git a/utils/pt/reflection.texy b/utils/pt/reflection.texy index b8543df6ed..d9d1d8123d 100644 --- a/utils/pt/reflection.texy +++ b/utils/pt/reflection.texy @@ -2,7 +2,7 @@ Reflexão PHP ************ .[perex] -[api:Nette\Utils\Reflection] é uma classe estática com funções úteis para a reflexão em PHP. Seu propósito é corrigir falhas em classes nativas e unificar o comportamento através de diferentes versões do PHP. +[api:Nette\Utils\Reflection] é uma classe estática com funções úteis para reflexão PHP. Sua tarefa é corrigir as deficiências das classes nativas e unificar o comportamento em diferentes versões do PHP. Instalação: @@ -11,7 +11,7 @@ Instalação: composer require nette/utils ``` -Todos os exemplos assumem que a seguinte classe está definida: +Todos os exemplos assumem que um alias foi criado: ```php use Nette\Utils\Reflection; @@ -21,13 +21,13 @@ use Nette\Utils\Reflection; areCommentsAvailable(): bool .[method] -------------------------------------- -Descobre se a reflexão tem acesso aos comentários do PHPdoc. Comentários podem não estar disponíveis devido ao cache opcode, veja por exemplo a diretiva [opcache.save-comments. |https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments] +Verifica se a reflexão tem acesso aos comentários PHPdoc. Os comentários podem não estar disponíveis devido ao cache de opcode, veja por exemplo a diretiva [opcache.save-comments|https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments]. expandClassName(string $name, ReflectionClass $context): string .[method] ------------------------------------------------------------------------- -Expande o `$name` da classe para nome completo no contexto do `$context`, ou seja, no contexto de seu namespace e pseudônimos definidos. Assim, ele retorna como o analisador do PHP entenderia `$name` se fosse escrito no corpo do `$context`. +Expande o nome da classe `$name` para seu nome completo no contexto da classe `$context`, ou seja, no contexto de seu namespace e aliases definidos. Em outras palavras, diz como o parser PHP entenderia `$name` se estivesse escrito no corpo da classe `$context`. ```php namespace Foo; @@ -47,9 +47,9 @@ Reflection::expandClassName('Baz', $context); // 'Foo\Baz' getMethodDeclaringMethod(ReflectionMethod $method): ReflectionMethod .[method] ------------------------------------------------------------------------------ -Retorna uma reflexão de um método que contém uma declaração de `$method`. Normalmente, cada método é sua própria declaração, mas o corpo do método também pode estar no traço e sob um nome diferente. +Retorna a reflexão do método que contém a declaração do método `$method`. Normalmente, cada método é sua própria declaração, mas o corpo do método também pode estar em um trait e com um nome diferente. -Como o PHP não fornece informações suficientes para determinar a declaração real, a Nette utiliza sua própria heurística, que **deveria ser*** confiável. +Como o PHP não fornece informações suficientes para determinar a declaração real, o Nette usa sua própria heurística, que **deve ser** confiável. ```php trait DemoTrait @@ -76,9 +76,9 @@ Reflection::getMethodDeclaringMethod($method); // ReflectionMethod('DemoTrait::f getPropertyDeclaringClass(ReflectionProperty $prop): ReflectionClass .[method] ------------------------------------------------------------------------------ -Retorna um reflexo de uma classe ou traço que contém uma declaração de propriedade `$prop`. Os bens também podem ser declarados no traço. +Retorna a reflexão da classe ou trait que contém a declaração da propriedade `$prop`. A propriedade também pode ser declarada em um trait. -Como o PHP não fornece informações suficientes para determinar a declaração real, Nette utiliza sua própria heurística, a qual ** não é*** confiável. +Como o PHP não fornece informações suficientes para determinar a declaração real, o Nette usa sua própria heurística, que **não é** confiável. ```php trait DemoTrait @@ -100,7 +100,7 @@ Reflection::getPropertyDeclaringClass($prop); // ReflectionClass('DemoTrait') isBuiltinType(string $type): bool .[method deprecated] ------------------------------------------------------ -Determina se `$type` é do tipo PHP incorporado. Caso contrário, é o nome da classe. +Verifica se `$type` é um tipo embutido do PHP. Caso contrário, é um nome de classe. ```php Reflection::isBuiltinType('string'); // true @@ -108,13 +108,13 @@ Reflection::isBuiltinType('Foo'); // false ``` .[note] -Use [Nette\UtilsValidator::isBuiltinType() |validators#isBuiltinType]. +Use [Nette\Utils\Validator::isBuiltinType() |validators#isBuiltinType]. toString($reflection): string .[method] --------------------------------------- -Converte um reflexo em um fio legível por humanos. +Converte a reflexão em uma string legível por humanos. ```php $func = new ReflectionFunction('func'); @@ -127,7 +127,7 @@ $method = new ReflectionMethod('DemoClass', 'foo'); echo Reflection::toString($method); // 'DemoClass::foo()' $param = new ReflectionParameter(['DemoClass', 'foo'], 'param'); -echo Reflection::toString($param); // '$param in DemoClass::foo()' +echo Reflection::toString($param); // '$param em DemoClass::foo()' $prop = new ReflectionProperty('DemoClass', 'foo'); echo Reflection::toString($prop); // 'DemoClass::$foo' diff --git a/utils/pt/smartobject.texy b/utils/pt/smartobject.texy index cc43032dd3..d6a2fd7f90 100644 --- a/utils/pt/smartobject.texy +++ b/utils/pt/smartobject.texy @@ -1,8 +1,8 @@ -SmartObject e StaticClass -************************* +SmartObject +*********** .[perex] -O SmartObject adiciona suporte para *propriedade* às classes PHP. O StaticClass é usado para denotar classes estáticas. +O SmartObject aprimorou o comportamento dos objetos em PHP por anos. Desde a versão PHP 8.4, todas as suas funções já fazem parte do próprio PHP, completando assim sua missão histórica de ser um pioneiro da abordagem moderna orientada a objetos em PHP. Instalação: @@ -11,19 +11,64 @@ Instalação: composer require nette/utils ``` +O SmartObject surgiu em 2007 como uma solução revolucionária para as deficiências do modelo de objetos do PHP da época. Em um tempo em que o PHP sofria de vários problemas com o design de objetos, ele trouxe melhorias significativas e simplificou o trabalho para os desenvolvedores. Tornou-se uma parte lendária do framework Nette. Oferecia funcionalidades que o PHP só adquiriu muitos anos depois - desde o controle de acesso às propriedades dos objetos até sofisticados açúcares sintáticos. Com a chegada do PHP 8.4, ele completou sua missão histórica, pois todas as suas funções se tornaram parte nativa da linguagem. Ele antecipou o desenvolvimento do PHP em notáveis 17 anos. -Propriedades, Getters e Setters .[#toc-properties-getters-and-setters] -====================================================================== +Tecnicamente, o SmartObject passou por um desenvolvimento interessante. Originalmente, foi implementado como uma classe `Nette\Object`, da qual outras classes herdavam a funcionalidade necessária. Uma mudança fundamental veio com o PHP 5.4, que introduziu o suporte a traits. Isso permitiu a transformação na forma da trait `Nette\SmartObject`, o que trouxe maior flexibilidade - os desenvolvedores podiam usar a funcionalidade mesmo em classes que já herdavam de outra classe. Enquanto a classe original `Nette\Object` desapareceu com a chegada do PHP 7.2 (que proibiu a nomeação de classes com a palavra `Object`), a trait `Nette\SmartObject` continua viva. -Em linguagens modernas orientadas a objetos (por exemplo, C#, Python, Ruby, JavaScript), o termo *propriedade* se refere a [membros especiais de classes |https://en.wikipedia.org/wiki/Property_(programming)] que se parecem com variáveis, mas que na verdade são representadas por métodos. Quando o valor desta "variável" é atribuído ou lido, o método correspondente (chamado getter ou setter) é chamado. Isto é uma coisa muito útil de se fazer, nos dá total controle sobre o acesso às variáveis. Podemos validar a entrada ou gerar resultados somente quando a propriedade é lida. +Vamos analisar as funcionalidades que `Nette\Object` e, posteriormente, `Nette\SmartObject` ofereceram. Cada uma dessas funções, em sua época, representou um passo significativo no campo da programação orientada a objetos em PHP. -As propriedades do PHP não são suportadas, mas o traço `Nette\SmartObject` pode imitá-las. Como utilizá-lo? -- Acrescentar uma anotação à classe no formulário `@property $xyz` -- Crie um getter chamado `getXyz()` ou `isXyz()`, um setter chamado `setXyz()` -- O getter e o setter devem ser *públicos* ou *protegidos* e são opcionais, portanto pode haver uma propriedade *somente de leitura* ou *somente de escrita*. +Estados de erro consistentes +---------------------------- +Um dos problemas mais urgentes do PHP inicial era o comportamento inconsistente ao trabalhar com objetos. `Nette\Object` trouxe ordem e previsibilidade a esse caos. Vejamos como era o comportamento original do PHP: + +```php +echo $obj->undeclared; // E_NOTICE, posteriormente E_WARNING +$obj->undeclared = 1; // passa silenciosamente sem aviso +$obj->unknownMethod(); // Fatal error (não capturável com try/catch) +``` + +Um erro fatal encerrava a aplicação sem a possibilidade de reagir de forma alguma. A escrita silenciosa em membros inexistentes sem aviso poderia levar a erros graves que eram difíceis de detectar. `Nette\Object` capturava todos esses casos e lançava a exceção `MemberAccessException`, o que permitia aos programadores reagir aos erros e resolvê-los. + +```php +echo $obj->undeclared; // lança Nette\MemberAccessException +$obj->undeclared = 1; // lança Nette\MemberAccessException +$obj->unknownMethod(); // lança Nette\MemberAccessException +``` + +Desde o PHP 7.0, a linguagem não causa mais erros fatais não capturáveis e, desde o PHP 8.2, o acesso a membros não declarados é considerado um erro. -Utilizaremos a propriedade da classe Circle para garantir que somente números não-negativos sejam colocados na variável `$radius`. Substituir `public $radius` por propriedade: + +Ajuda "Did you mean?" +--------------------- +`Nette\Object` introduziu uma função muito agradável: ajuda inteligente para erros de digitação. Quando um desenvolvedor cometia um erro no nome de um método ou variável, ele não apenas relatava o erro, mas também oferecia uma mão amiga na forma de uma sugestão do nome correto. Esta mensagem icônica, conhecida como "did you mean?", economizou horas de busca por erros de digitação para os programadores: + +```php +class Foo extends Nette\Object +{ + public static function from($var) + { + } +} + +$foo = Foo::form($var); +// lança Nette\MemberAccessException +// "Chamada para método estático indefinido Foo::form(), você quis dizer from()?" +``` + +O PHP atual não tem nenhuma forma de "did you mean?", mas [Tracy|tracy:] pode adicionar este adendo aos erros. E até mesmo [corrigir automaticamente |tracy:open-files-in-ide#Exemplos] tais erros. + + +Propriedades com acesso controlado +---------------------------------- +Uma inovação significativa que o SmartObject trouxe para o PHP foram as propriedades com acesso controlado. Este conceito, comum em linguagens como C# ou Python, permitiu aos desenvolvedores controlar elegantemente o acesso aos dados do objeto e garantir sua consistência. As propriedades são uma ferramenta poderosa da programação orientada a objetos. Elas funcionam como variáveis, mas na realidade são representadas por métodos (getters e setters). Isso permite validar entradas ou gerar valores apenas no momento da leitura. + +Para usar propriedades, você deve: +- Adicionar uma anotação à classe no formato `@property $xyz` +- Criar um getter com o nome `getXyz()` ou `isXyz()`, um setter com o nome `setXyz()` +- Garantir que o getter e o setter sejam *public* ou *protected*. Eles são opcionais - podem existir como propriedades *read-only* ou *write-only* + +Vejamos um exemplo prático na classe Circle, onde usamos propriedades para garantir que o raio seja sempre um número não negativo. Substituímos o `public $radius` original por uma propriedade: ```php /** @@ -34,22 +79,22 @@ class Circle { use Nette\SmartObject; - private float $radius = 0.0; // não público + private float $radius = 0.0; // não é public! - // getter para propriedade $radius + // getter para a propriedade $radius protected function getRadius(): float { return $this->radius; } - // setter para propriedade $radius + // setter para a propriedade $radius protected function setRadius(float $radius): void { - // valor higienizante antes de salvá-lo + // sanitizamos o valor antes de salvar $this->radius = max(0.0, $radius); } - // adquirente por propriedade $visível + // getter para a propriedade $visible protected function isVisible(): bool { return $this->radius > 0; @@ -57,84 +102,30 @@ class Circle } $circle = new Circle; -$circle->radius = 10; // na verdade chama setRadius(10) -echo $circle->radius; // chama getRadius() -echo $circle->visible; // chamadas isVisible() +$circle->radius = 10; // na verdade chama setRadius(10) +echo $circle->radius; // chama getRadius() +echo $circle->visible; // chama isVisible() ``` -As propriedades são principalmente o "açúcar sintático"((açúcar sintático)), que se destina a tornar a vida do programador mais doce, simplificando o código. Se você não os quer, não precisa usá-los. - - -Classes estáticas .[#toc-static-classes] -======================================== - -As classes estáticas, ou seja, classes que não se destinam a ser instanciadas, podem ser marcadas com o traço `Nette\StaticClass`: +Desde o PHP 8.4, a mesma funcionalidade pode ser alcançada usando property hooks, que oferecem uma sintaxe muito mais elegante e concisa: ```php -class Strings +class Circle { - use Nette\StaticClass; -} -``` - -Quando você tenta criar uma instância, a exceção `Error` é lançada, indicando que a classe é estática. - - -Um olhar sobre a história .[#toc-a-look-into-the-history] -========================================================= - -SmartObject usado para melhorar e corrigir o comportamento de classe de muitas maneiras, mas a evolução do PHP tornou redundante a maioria das características originais. Portanto, o seguinte é um olhar sobre a história de como as coisas evoluíram. - -Desde o início, o modelo de objeto PHP sofreu uma série de falhas graves e ineficiências. Esta foi a razão da criação da classe `Nette\Object` (em 2007), que tentou remediá-los e melhorar a experiência de uso do PHP. Era o suficiente para que outras classes herdassem dela, e ganhassem os benefícios que ela trazia. Quando o PHP 5.4 veio com suporte de características, a classe `Nette\Object` foi substituída por `Nette\SmartObject`. Assim, não era mais necessário herdar de um ancestral comum. Além disso, a característica podia ser usada em classes que já herdaram de outra classe. O fim final de `Nette\Object` veio com o lançamento do PHP 7.2, que proibiu as classes de serem nomeadas `Object`. - -Com o desenvolvimento do PHP em andamento, o modelo de objeto e as capacidades de linguagem foram melhorados. As funções individuais da classe `SmartObject` se tornaram redundantes. Desde o lançamento do PHP 8.2, a única característica que ainda não é suportada diretamente no PHP é a capacidade de usar as chamadas [propriedades |#Properties, Getters and Setters]. - -Que características `Nette\Object` e `Nette\Object` ofereceram uma vez? Aqui está uma visão geral. (Os exemplos utilizam a classe `Nette\Object`, mas a maioria das propriedades também se aplica ao traço `Nette\SmartObject` ). - - -Erros Inconsistentes .[#toc-inconsistent-errors] ------------------------------------------------- -O PHP tinha um comportamento inconsistente ao acessar membros não declarados. O estado, na época de `Nette\Object`, era o seguinte: - -```php -echo $obj->undeclared; // E_NOTICE, mais tarde E_WARNING -$obj->undeclared = 1; // passa silenciosamente sem informar -$obj->unknownMethod(); // Erro fatal (não detectável por tentativa/captura) -``` - -O erro fatal terminou a aplicação sem qualquer possibilidade de reação. Escrever silenciosamente a membros inexistentes sem aviso prévio poderia levar a erros graves que eram difíceis de detectar. `Nette\Object` Todos estes casos foram pegos e uma exceção `MemberAccessException` foi lançada. - -```php -echo $obj->undeclared; // jogue Nette\MemberAccessException -$obj->undeclared = 1; // jogar Nette\MemberAccessException -$obj->unknownMethod(); // jogar Nette\MemberAccessException -``` -Desde o PHP 7.0, o PHP não causa mais erros fatais não detectáveis, e acessar membros não declarados tem sido um erro desde o PHP 8.2. - - -Você quis dizer? .[#toc-did-you-mean] -------------------------------------- -Se um erro `Nette\MemberAccessException` foi lançado, talvez devido a um erro de digitação ao acessar uma variável de objeto ou ao chamar um método, `Nette\Object` tentou dar uma dica na mensagem de erro sobre como corrigir o erro, na forma do icônico adendo "você quis dizer...". + public float $radius = 0.0 { + set => max(0.0, $value); + } -```php -class Foo extends Nette\Object -{ - public static function from($var) - { + public bool $visible { + get => $this->radius > 0; } } - -$foo = Foo::form($var); -// throw Nette\MemberAccessException -// "Call to undefined static method Foo::form(), did you mean from()?" ``` -O PHP de hoje pode não ter nenhuma forma de "você quis dizer?", mas [Tracy |tracy:] acrescenta este adendo aos erros. E ele pode até [corrigir |tracy:open-files-in-ide#toc-demos] tais erros por si só. - -Métodos de extensão .[#toc-extension-methods] ---------------------------------------------- -Inspirado pelos métodos de extensão da C#. Eles deram a possibilidade de adicionar novos métodos às classes existentes. Por exemplo, você poderia adicionar o método `addDateTime()` a um formulário para adicionar seu próprio DateTimePicker. +Métodos de extensão +------------------- +`Nette\Object` trouxe outro conceito interessante para o PHP, inspirado em linguagens de programação modernas - métodos de extensão. Esta função, emprestada do C#, permitiu aos desenvolvedores estender elegantemente classes existentes com novos métodos sem a necessidade de modificá-las ou herdar delas. Por exemplo, você poderia adicionar um método `addDateTime()` ao seu formulário, que adiciona um DateTimePicker personalizado: ```php Form::extensionMethod( @@ -146,22 +137,22 @@ $form = new Form; $form->addDateTime('date'); ``` -Os métodos de extensão se mostraram impraticáveis porque seus nomes não foram autocompletados pelos editores, em vez disso, eles relataram que o método não existia. Portanto, seu apoio foi descontinuado. +Os métodos de extensão mostraram-se impraticáveis porque seus nomes não eram sugeridos pelos editores; pelo contrário, relatavam que o método não existia. Portanto, seu suporte foi encerrado. Hoje, é mais comum usar composição ou herança para estender a funcionalidade das classes. -Como obter o nome da classe .[#toc-getting-the-class-name] ----------------------------------------------------------- +Obtendo o nome da classe +------------------------ +Para obter o nome da classe, o SmartObject oferecia um método simples: ```php $class = $obj->getClass(); // usando Nette\Object -$class = $obj::class; // desde PHP 8.0 +$class = $obj::class; // desde PHP 8.0 ``` -Acesso à Reflexão e Anotações .[#toc-access-to-reflection-and-annotations] --------------------------------------------------------------------------- - -`Nette\Object` ofereceu acesso à reflexão e à anotação utilizando os métodos `getReflection()` e `getAnnotation()`: +Acesso à reflexão e anotações +----------------------------- +`Nette\Object` oferecia acesso à reflexão e anotações usando os métodos `getReflection()` e `getAnnotation()`. Esta abordagem simplificou significativamente o trabalho com metainformações de classes: ```php /** @@ -173,10 +164,10 @@ class Foo extends Nette\Object $obj = new Foo; $reflection = $obj->getReflection(); -$reflection->getAnnotation('author'); // devolve 'John Doe +$reflection->getAnnotation('author'); // retorna 'John Doe' ``` -A partir do PHP 8.0, é possível acessar meta-informação sob a forma de atributos: +Desde o PHP 8.0, é possível acessar metainformações na forma de atributos, que oferecem ainda mais possibilidades e melhor controle de tipo: ```php #[Author('John Doe')] @@ -190,10 +181,9 @@ $reflection->getAttributes(Author::class)[0]; ``` -Método Getters .[#toc-method-getters] -------------------------------------- - -`Nette\Object` oferecia uma maneira elegante de lidar com métodos como se fossem variáveis: +Getters de método +----------------- +`Nette\Object` oferecia uma maneira elegante de passar métodos como se fossem variáveis: ```php class Foo extends Nette\Object @@ -209,7 +199,7 @@ $method = $obj->adder; echo $method(2, 3); // 5 ``` -A partir do PHP 8.1, você pode usar a chamada "sintaxe de primeira classe que pode ser chamada":https://www.php.net/manual/en/functions.first_class_callable_syntax: +Desde o PHP 8.1, é possível usar a chamada "sintaxe callable de primeira classe":https://www.php.net/manual/en/functions.first_class_callable_syntax, que leva este conceito ainda mais longe: ```php $obj = new Foo; @@ -218,10 +208,9 @@ echo $method(2, 3); // 5 ``` -Eventos .[#toc-events] ----------------------- - -`Nette\Object` ofereceu açúcar sintáctico para acionar o [evento |nette:glossary#events]: +Eventos +------- +SmartObject oferece uma sintaxe simplificada para trabalhar com [eventos |nette:glossary#Eventos]. Eventos permitem que objetos informem outras partes da aplicação sobre mudanças em seu estado: ```php class Circle extends Nette\Object @@ -231,12 +220,12 @@ class Circle extends Nette\Object public function setRadius(float $radius): void { $this->onChange($this, $radius); - $this->radius = $radius + $this->radius = $radius; } } ``` -O código `$this->onChange($this, $radius)` é equivalente ao seguinte: +O código `$this->onChange($this, $radius)` é equivalente ao seguinte ciclo: ```php foreach ($this->onChange as $callback) { @@ -244,7 +233,7 @@ foreach ($this->onChange as $callback) { } ``` -Por uma questão de clareza, recomendamos evitar o método mágico `$this->onChange()`. Um substituto prático é a [Arrays Nette\Utils::invoke |arrays#invoke] function: +Por questões de clareza, recomendamos evitar o método mágico `$this->onChange()`. Uma substituição prática é, por exemplo, a função [Nette\Utils\Arrays::invoke |arrays#invoke]: ```php Nette\Utils\Arrays::invoke($this->onChange, $this, $radius); diff --git a/utils/pt/staticclass.texy b/utils/pt/staticclass.texy new file mode 100644 index 0000000000..1e1d14c229 --- /dev/null +++ b/utils/pt/staticclass.texy @@ -0,0 +1,21 @@ +Classes estáticas +***************** + +.[perex] +StaticClass é usada para marcar classes estáticas. + + +Instalação: + +```shell +composer require nette/utils +``` + +Classes estáticas, ou seja, classes que não se destinam a serem instanciadas, podem ser marcadas com a trait [api:Nette\StaticClass]: + +```php +class Strings +{ + use Nette\StaticClass; +} +``` diff --git a/utils/pt/strings.texy b/utils/pt/strings.texy index 0c771b5c15..8f5379ad1b 100644 --- a/utils/pt/strings.texy +++ b/utils/pt/strings.texy @@ -1,8 +1,8 @@ -Funções das cordas -****************** +Trabalhando com strings +*********************** .[perex] -[api:Nette\Utils\Strings] é uma classe estática, que contém muitas funções úteis para trabalhar com cordas codificadas UTF-8. +[api:Nette\Utils\Strings] é uma classe estática com funções úteis para trabalhar com strings, principalmente na codificação UTF-8. Instalação: @@ -11,15 +11,15 @@ Instalação: composer require nette/utils ``` -Todos os exemplos assumem que a seguinte classe está definida: +Todos os exemplos assumem que um alias foi criado: ```php use Nette\Utils\Strings; ``` -Estojo de carta .[#toc-letter-case] -=================================== +Mudança de caixa +================ Estas funções requerem a extensão PHP `mbstring`. @@ -27,67 +27,67 @@ Estas funções requerem a extensão PHP `mbstring`. lower(string $s): string .[method] ---------------------------------- -Converte todos os caracteres de corda UTF-8 em minúsculas. +Converte uma string UTF-8 para minúsculas. ```php -Strings::lower('Hello world'); // 'hello world' +Strings::lower('Bom Dia'); // 'bom dia' ``` upper(string $s): string .[method] ---------------------------------- -Converte todos os caracteres de uma corda UTF-8 em maiúsculas. +Converte uma string UTF-8 para maiúsculas. ```php -Strings::upper('Hello world'); // 'HELLO WORLD' +Strings::upper('Bom Dia'); // 'BOM DIA' ``` firstUpper(string $s): string .[method] --------------------------------------- -Converte o primeiro caractere de um fio UTF-8 em maiúsculas e deixa os outros caracteres inalterados. +Converte a primeira letra de uma string UTF-8 para maiúscula, as outras permanecem inalteradas. ```php -Strings::firstUpper('hello world'); // 'Hello world' +Strings::firstUpper('bom dia'); // 'Bom dia' ``` firstLower(string $s): string .[method] --------------------------------------- -Converte o primeiro caractere de uma corda UTF-8 para minúscula e deixa os outros caracteres inalterados. +Converte a primeira letra de uma string UTF-8 para minúscula, as outras permanecem inalteradas. ```php -Strings::firstLower('Hello world'); // 'hello world' +Strings::firstLower('Bom Dia'); // 'bom dia' ``` capitalize(string $s): string .[method] --------------------------------------- -Converte o primeiro caractere de cada palavra de uma cadeia UTF-8 para maiúscula e os outros para minúscula. +Converte a primeira letra de cada palavra em uma string UTF-8 para maiúscula, as outras para minúsculas. ```php -Strings::capitalize('Hello world'); // 'Hello World' +Strings::capitalize('Bom Dia'); // 'Bom Dia' ``` -Edição de uma corda .[#toc-editing-a-string] -============================================ +Modificação de string +===================== normalize(string $s): string .[method] -------------------------------------- -Remove caracteres de controle, normaliza as quebras de linha para `\n`, remove linhas em branco, corrige os espaços finais nas linhas, normaliza UTF-8 para a forma normal de NFC. +Remove caracteres de controle, normaliza as quebras de linha para `\n`, remove linhas em branco iniciais e finais, remove espaços à direita nas linhas, normaliza UTF-8 para a forma normal NFC. unixNewLines(string $s): string .[method] ----------------------------------------- -Converte as quebras de linha para `\n` utilizadas em sistemas Unix. As quebras de linha são: `\n`, `\r`, `\r\n`, Separador de linha U+2028, Separador de parágrafo U+2029. +Converte quebras de linha para `\n` usado em sistemas Unix. As quebras de linha são: `\n`, `\r`, `\r\n`, separador de linha U+2028, separador de parágrafo U+2029. ```php $unixLikeLines = Strings::unixNewLines($string); @@ -97,66 +97,66 @@ $unixLikeLines = Strings::unixNewLines($string); platformNewLines(string $s): string .[method] --------------------------------------------- -Converte as quebras de linha em caracteres específicos da plataforma atual, ou seja, `\r\n` no Windows e `\n` em outros lugares. As quebras de linha são `\n`, `\r`, `\r\n`, separador de linha U+2028, separador de parágrafo U+2029. +Converte quebras de linha para caracteres específicos da plataforma atual, ou seja, `\r\n` no Windows e `\n` em outros lugares. As quebras de linha são: `\n`, `\r`, `\r\n`, separador de linha U+2028, separador de parágrafo U+2029. ```php $platformLines = Strings::platformNewLines($string); ``` -webalize(string $s, string $charlist=null, bool $lower=true): string .[method] ------------------------------------------------------------------------------- +webalize(string $s, ?string $charlist=null, bool $lower=true): string .[method] +------------------------------------------------------------------------------- -Modifica a cadeia UTF-8 para a forma usada na URL, ou seja, remove os diacríticos e substitui todos os caracteres exceto letras do alfabeto inglês e números por um hífen. +Modifica uma string UTF-8 para o formato usado em URLs, ou seja, remove diacríticos e substitui todos os caracteres, exceto letras do alfabeto inglês e números, por hífens. ```php -Strings::webalize('žluťoučký kůň'); // 'zlutoucky-kun' +Strings::webalize('nosso produto'); // 'nosso-produto' ``` -Outros caracteres também podem ser preservados, mas devem ser passados como segundo argumento. +Se outros caracteres devem ser preservados, eles podem ser listados no segundo parâmetro da função. ```php -Strings::webalize('10. image_id', '._'); // '10.-image_id' +Strings::webalize('10. imagem_id', '._'); // '10.-imagem_id' ``` -O terceiro argumento pode suprimir a conversão da corda em minúscula. +O terceiro parâmetro pode suprimir a conversão para minúsculas. ```php -Strings::webalize('Hello world', null, false); // 'Hello-world' +Strings::webalize('Bom Dia', null, false); // 'Bom-Dia' ``` .[caution] -Requer extensão PHP `intl`. +Requer a extensão PHP `intl`. -trim(string $s, string $charlist=null): string .[method] --------------------------------------------------------- +trim(string $s, ?string $charlist=null): string .[method] +--------------------------------------------------------- -Remove todos os espaços do lado esquerdo e direito (ou os caracteres passados como segundo argumento) de uma cadeia codificada UTF-8. +Remove espaços (ou outros caracteres especificados pelo segundo parâmetro) do início e do fim de uma string UTF-8. ```php -Strings::trim(' Hello '); // 'Hello' +Strings::trim(' Olá '); // 'Olá' ``` truncate(string $s, int $maxLen, string $append=`'…'`): string .[method] ------------------------------------------------------------------------ -Truncata um fio UTF-8 para dar o comprimento máximo, enquanto tenta não dividir palavras inteiras. Somente se a corda for truncada, uma elipse (ou qualquer outra coisa com terceiro argumento) é anexada à corda. +Trunca uma string UTF-8 para o comprimento máximo especificado, tentando preservar palavras inteiras. Se a string for encurtada, adiciona reticências no final (pode ser alterado com o terceiro parâmetro). ```php -$text = 'Hello, how are you today?'; -Strings::truncate($text, 5); // 'Hell…' -Strings::truncate($text, 20); // 'Hello, how are you…' -Strings::truncate($text, 30); // 'Hello, how are you today?' -Strings::truncate($text, 20, '~'); // 'Hello, how are you~' +$text = 'Diga-me, como você está?'; +Strings::truncate($text, 5); // 'Diga…' +Strings::truncate($text, 20); // 'Diga-me, como você…' +Strings::truncate($text, 30); // 'Diga-me, como você está?' +Strings::truncate($text, 20, '~'); // 'Diga-me, como você~' ``` indent(string $s, int $level=1, string $indentationChar=`"\t"`): string .[method] --------------------------------------------------------------------------------- -Traz um texto com várias linhas da esquerda. O segundo argumento estabelece quantos caracteres de recuo devem ser usados, enquanto o próprio recuo é o terceiro argumento (*tab* por padrão). +Indenta um texto de várias linhas pela esquerda. O número de indentações é determinado pelo segundo parâmetro, o caractere de indentação pelo terceiro (o valor padrão é tabulação). ```php Strings::indent('Nette'); // "\tNette" @@ -167,7 +167,7 @@ Strings::indent('Nette', 2, '+'); // '++Nette' padLeft(string $s, int $length, string $pad=`' '`): string .[method] -------------------------------------------------------------------- -Coloque uma corda UTF-8 em um determinado comprimento, pré-pendendo a corda `$pad` para o início. +Preenche uma string UTF-8 até o comprimento especificado repetindo a string `$pad` pela esquerda. ```php Strings::padLeft('Nette', 6); // ' Nette' @@ -178,7 +178,7 @@ Strings::padLeft('Nette', 8, '+*'); // '+*+Nette' padRight(string $s, int $length, string $pad=`' '`): string .[method] --------------------------------------------------------------------- -Comprimento do cordão UTF-8, anexando o cordão `$pad` ao final. +Preenche uma string UTF-8 até o comprimento especificado repetindo a string `$pad` pela direita. ```php Strings::padRight('Nette', 6); // 'Nette ' @@ -186,10 +186,10 @@ Strings::padRight('Nette', 8, '+*'); // 'Nette+*+' ``` -substring(string $s, int $start, int $length=null): string .[method] --------------------------------------------------------------------- +substring(string $s, int $start, ?int $length=null): string .[method] +--------------------------------------------------------------------- -Retorna uma parte da cadeia UTF-8 especificada pela posição inicial `$start` e comprimento `$length`. Se `$start` for negativo, a cadeia de caracteres retornada começará no `$start`'th character from the end of string'. +Retorna uma parte da string UTF-8 `$s` especificada pela posição inicial `$start` e comprimento `$length`. Se `$start` for negativo, a string retornada começará no caractere -`$start` a partir do final. ```php Strings::substring('Nette Framework', 0, 5); // 'Nette' @@ -201,7 +201,7 @@ Strings::substring('Nette Framework', -4); // 'work' reverse(string $s): string .[method] ------------------------------------ -Inverte a corda UTF-8. +Inverte uma string UTF-8. ```php Strings::reverse('Nette'); // 'etteN' @@ -211,77 +211,77 @@ Strings::reverse('Nette'); // 'etteN' length(string $s): int .[method] -------------------------------- -Retorna o número de caracteres (não bytes) em cadeia UTF-8. +Retorna o número de caracteres (não bytes) em uma string UTF-8. -Esse é o número de pontos de código Unicode que pode diferir do número de grafemas. +Este é o número de pontos de código Unicode, que pode diferir do número de grafemas. ```php -Strings::length('Nette'); // 5 -Strings::length('red'); // 3 +Strings::length('Nette'); // 5 +Strings::length('červená'); // 7 ``` startsWith(string $haystack, string $needle): bool .[method deprecated] ----------------------------------------------------------------------- -Verifica se `$haystack` string começa com `$needle`. +Verifica se a string `$haystack` começa com a string `$needle`. ```php -$haystack = 'Begins'; -$needle = 'Be'; +$haystack = 'Começa'; +$needle = 'Co'; Strings::startsWith($haystack, $needle); // true ``` .[note] -Use nativo `str_starts_with()`:https://www.php.net/manual/en/function.str-starts-with.php. +Use a função nativa `str_starts_with()`:https://www.php.net/manual/en/function.str-starts-with.php. endsWith(string $haystack, string $needle): bool .[method deprecated] --------------------------------------------------------------------- -Verifica se `$haystack` string end com `$needle`. +Verifica se a string `$haystack` termina com a string `$needle`. ```php -$haystack = 'Ends'; -$needle = 'ds'; +$haystack = 'Termina'; +$needle = 'ina'; Strings::endsWith($haystack, $needle); // true ``` .[note] -Use nativo `str_ends_with()`:https://www.php.net/manual/en/function.str-ends-with.php. +Use a função nativa `str_ends_with()`:https://www.php.net/manual/en/function.str-ends-with.php. contains(string $haystack, string $needle): bool .[method deprecated] --------------------------------------------------------------------- -Verifica se o texto `$haystack` contém `$needle`. +Verifica se a string `$haystack` contém `$needle`. ```php -$haystack = 'Contains'; -$needle = 'tai'; +$haystack = 'Auditório'; +$needle = 'dit'; Strings::contains($haystack, $needle); // true ``` .[note] -Use nativo `str_contains()`:https://www.php.net/manual/en/function.str-contains.php. +Use a função nativa `str_contains()`:https://www.php.net/manual/en/function.str-contains.php. -compare(string $left, string $right, int $length=null): bool .[method] ----------------------------------------------------------------------- +compare(string $left, string $right, ?int $length=null): bool .[method] +----------------------------------------------------------------------- -Compara duas cordas UTF-8 ou suas partes, sem levar em conta o caso de caracteres. Se `$length` for nulo, cordas inteiras são comparadas, se for negativo, o número correspondente de caracteres do final das cordas é comparado, caso contrário, o número apropriado de caracteres do início é comparado. +Compara duas strings UTF-8 ou suas partes, ignorando maiúsculas e minúsculas. Se `$length` for null, as strings inteiras são comparadas; se for negativo, o número correspondente de caracteres do final das strings é comparado; caso contrário, o número correspondente de caracteres do início é comparado. ```php Strings::compare('Nette', 'nette'); // true -Strings::compare('Nette', 'next', 2); // true - two first characters match -Strings::compare('Nette', 'Latte', -2); // true - two last characters match +Strings::compare('Nette', 'next', 2); // true - correspondência dos primeiros 2 caracteres +Strings::compare('Nette', 'Latte', -2); // true - correspondência dos últimos 2 caracteres ``` findPrefix(...$strings): string .[method] ----------------------------------------- -Encontra o prefixo comum de cordas ou devolve cordas vazias se o prefixo não foi encontrado. +Encontra o prefixo comum das strings. Ou retorna uma string vazia se nenhum prefixo comum for encontrado. ```php Strings::findPrefix('prefix-a', 'prefix-bb', 'prefix-c'); // 'prefix-' @@ -293,7 +293,7 @@ Strings::findPrefix('Nette', 'is', 'great'); // '' before(string $haystack, string $needle, int $nth=1): ?string .[method] ----------------------------------------------------------------------- -Devolve parte de `$haystack` antes da ocorrência de `$nth` `$needle` ou devolve `null` se a agulha não foi encontrada. Valor negativo significa busca a partir do final. +Retorna a parte da string `$haystack` antes da n-ésima ocorrência `$nth` da string `$needle`. Ou `null` se `$needle` não for encontrado. Com um valor negativo para `$nth`, a busca é feita a partir do final da string. ```php Strings::before('Nette_is_great', '_', 1); // 'Nette' @@ -306,7 +306,7 @@ Strings::before('Nette_is_great', '_', 3); // null after(string $haystack, string $needle, int $nth=1): ?string .[method] ---------------------------------------------------------------------- -Devolve parte de `$haystack` após `$nth` ocorrência de `$needle` ou retorna `null` se o `$needle` não foi encontrado. O valor negativo de `$nth` significa uma busca a partir do final. +Retorna a parte da string `$haystack` após a n-ésima ocorrência `$nth` da string `$needle`. Ou `null` se `$needle` não for encontrado. Com um valor negativo para `$nth`, a busca é feita a partir do final da string. ```php Strings::after('Nette_is_great', '_', 2); // 'great' @@ -319,7 +319,7 @@ Strings::after('Nette_is_great', '_', 3); // null indexOf(string $haystack, string $needle, int $nth=1): ?int .[method] --------------------------------------------------------------------- -Retorna posição em caracteres de `$nth` ocorrência de `$needle` em `$haystack` ou `null` se o `$needle` não foi encontrado. O valor negativo de `$nth` significa pesquisa a partir do final. +Retorna a posição em caracteres da n-ésima ocorrência `$nth` da string `$needle` na string `$haystack`. Ou `null` se `$needle` não for encontrado. Com um valor negativo para `$nth`, a busca é feita a partir do final da string. ```php Strings::indexOf('abc abc abc', 'abc', 2); // 4 @@ -328,14 +328,14 @@ Strings::indexOf('abc abc abc', 'd'); // null ``` -Codificação .[#toc-encoding] -============================ +Codificação +=========== fixEncoding(string $s): string .[method] ---------------------------------------- -Remove todos os caracteres UTF-8 inválidos de uma corda. +Remove caracteres UTF-8 inválidos da string. ```php $correctStrings = Strings::fixEncoding($string); @@ -345,59 +345,59 @@ $correctStrings = Strings::fixEncoding($string); checkEncoding(string $s): bool .[method deprecated] --------------------------------------------------- -Verifica se a corda é válida na codificação UTF-8. +Verifica se é uma string UTF-8 válida. ```php $isUtf8 = Strings::checkEncoding($string); ``` .[note] -Use [Nette\UtilsValidator::isUnicode() |validators#isUnicode]. +Use [Nette\Utils\Validator::isUnicode() |validators#isUnicode]. toAscii(string $s): string .[method] ------------------------------------ -Converte o fio UTF-8 em ASCII, ou seja, remove diacríticos, etc. +Converte uma string UTF-8 para ASCII, ou seja, remove diacríticos, etc. ```php Strings::toAscii('žluťoučký kůň'); // 'zlutoucky kun' ``` .[caution] -Requer extensão PHP `intl`. +Requer a extensão PHP `intl`. chr(int $code): string .[method] -------------------------------- -Retorna um caractere específico em UTF-8 do ponto de código (número na faixa 0x0000..D7FF ou 0xE000..10FFFFFF). +Retorna um caractere específico em UTF-8 a partir do ponto de código (número no intervalo 0x0000..D7FF e 0xE000..10FFFF). ```php -Strings::chr(0xA9); // '©' +Strings::chr(0xA9); // '©' na codificação UTF-8 ``` ord(string $char): int .[method] -------------------------------- -Retorna um ponto de código de caráter específico em UTF-8 (número na faixa 0x0000..D7FF ou 0xE000..10FFFFFF). +Retorna o ponto de código de um caractere específico em UTF-8 (número no intervalo 0x0000..D7FF ou 0xE000..10FFFF). ```php Strings::ord('©'); // 0xA9 ``` -Expressões regulares .[#toc-regular-expressions] -================================================ +Expressões Regulares +==================== -A classe Strings fornece funções para trabalhar com expressões regulares. Ao contrário das funções PHP nativas, elas têm uma API mais compreensível, melhor suporte a Unicode e, o mais importante, detecção de erros. Qualquer erro de compilação ou de processamento de expressões lançará uma exceção `Nette\RegexpException`. +A classe Strings oferece funções para trabalhar com expressões regulares. Ao contrário das funções nativas do PHP, elas possuem uma API mais compreensível, melhor suporte a Unicode e, acima de tudo, detecção de erros. Qualquer erro durante a compilação ou processamento da expressão lançará uma exceção `Nette\RegexpException`. split(string $subject, string $pattern, bool $captureOffset=false, bool $skipEmpty=false, int $limit=-1, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------- -Divide o cordão em arrays de acordo com a expressão regular. Expressões entre parênteses serão capturadas e devolvidas também. +Divide uma string em um array usando uma expressão regular. Expressões entre parênteses também serão capturadas e retornadas. ```php Strings::split('hello, world', '~,\s*~'); @@ -407,7 +407,7 @@ Strings::split('hello, world', '~(,)\s*~'); // ['hello', ',', 'world']`` ``` -Se `$skipEmpty` é `true`, somente os itens não vazios serão devolvidos: +Se `$skipEmpty` for `true`, apenas itens não vazios serão retornados: ```php Strings::split('hello, world, ', '~,\s*~'); @@ -417,16 +417,16 @@ Strings::split('hello, world, ', '~,\s*~', skipEmpty: true); // ['hello', 'world'] ``` -Se `$limit` for especificado, somente substratos até o limite serão devolvidos e o resto do cordel será colocado no último elemento. Um limite de -1 ou 0 significa que não há limite. +Se `$limit` for especificado, apenas substrings até o limite serão retornadas, e o restante da string será colocado no último elemento. Um limite de -1 ou 0 significa sem limite. ```php Strings::split('hello, world, third', '~,\s*~', limit: 2); // ['hello', 'world, third'] ``` -Se `$utf8` é `true`, a avaliação muda para o modo Unicode. Isto é semelhante a especificar o modificador `u`. +Se `$utf8` for `true`, a avaliação muda para o modo Unicode. Semelhante a especificar o modificador `u`. -Se `$captureOffset` for `true`, para cada partida ocorrida, sua posição na cadeia também será devolvida (em bytes; em caracteres se `$utf8` estiver definido). Isto muda o valor de retorno para uma matriz onde cada elemento é um par que consiste da corda combinada e sua posição. +Se `$captureOffset` for `true`, para cada correspondência encontrada, sua posição na string (em bytes; se `$utf8` estiver definido, em caracteres) também será retornada. Isso altera o valor de retorno para um array onde cada elemento é um par composto pela string correspondente e sua posição. ```php Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true); @@ -440,7 +440,7 @@ Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true, utf8: true); match(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $utf8=false): ?array .[method] -------------------------------------------------------------------------------------------------------------------------------------------------- -Procura na cadeia a peça que corresponde à expressão regular e retorna uma matriz com a expressão encontrada e subexpressões individuais, ou `null`. +Procura na string uma parte que corresponda à expressão regular e retorna um array com a expressão encontrada e subexpressões individuais, ou `null`. ```php Strings::match('hello!', '~\w+(!+)~'); @@ -450,7 +450,7 @@ Strings::match('hello!', '~X~'); // null ``` -Se `$unmatchedAsNull` é `true`, subpadrões inigualáveis são devolvidos como nulos; caso contrário, são devolvidos como uma cadeia vazia ou não devolvidos: +Se `$unmatchedAsNull` for `true`, subpadrões não capturados são retornados como null; caso contrário, são retornados como uma string vazia ou não são retornados: ```php Strings::match('hello', '~\w+(!+)?~'); @@ -460,7 +460,7 @@ Strings::match('hello', '~\w+(!+)?~', unmatchedAsNull: true); // ['hello', null] ``` -Se `$utf8` é `true`, a avaliação muda para o modo Unicode. Isto é semelhante a especificar o modificador `u`: +Se `$utf8` for `true`, a avaliação muda para o modo Unicode. Semelhante a especificar o modificador `u`: ```php Strings::match('žlutý kůň', '~\w+~'); @@ -470,9 +470,9 @@ Strings::match('žlutý kůň', '~\w+~', utf8: true); // ['žlutý'] ``` -O parâmetro `$offset` pode ser usado para especificar a posição a partir da qual se inicia a busca (em bytes; em caracteres se `$utf8` estiver definido). +O parâmetro `$offset` pode ser usado para especificar a posição a partir da qual a busca deve começar (em bytes; se `$utf8` estiver definido, em caracteres). -Se `$captureOffset` for `true`, para cada partida ocorrida, sua posição na cadeia também será devolvida (em bytes; em caracteres se `$utf8` estiver definido). Isto muda o valor de retorno para um array onde cada elemento é um par que consiste na string correspondente e seu offset: +Se `$captureOffset` for `true`, para cada correspondência encontrada, sua posição na string (em bytes; se `$utf8` estiver definido, em caracteres) também será retornada. Isso altera o valor de retorno para um array onde cada elemento é um par composto pela string correspondente e seu offset: ```php Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true); @@ -483,10 +483,10 @@ Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true, utf8: true); ``` -matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false, bool $lazy=false): array|Generator .[method] +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Procura na cadeia todas as ocorrências que correspondem à expressão regular e retorna uma matriz de matrizes contendo a expressão encontrada e cada subexpressão. +Procura na string todas as ocorrências que correspondem à expressão regular e retorna um array de arrays com a expressão encontrada e subexpressões individuais. ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~'); @@ -496,7 +496,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~'); ] */ ``` -Se `$patternOrder` é `true`, a estrutura dos resultados muda para que o primeiro item seja um conjunto de correspondências de padrões completos, o segundo é um conjunto de cordas correspondentes ao primeiro subpadrão entre parênteses, e assim por diante: +Se `$patternOrder` for `true`, a estrutura dos resultados muda de forma que o primeiro item é um array de correspondências completas do padrão, o segundo é um array de strings correspondentes ao primeiro subpadrão entre parênteses, e assim por diante: ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~', patternOrder: true); @@ -506,7 +506,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~', patternOrder: true); ] */ ``` -Se `$unmatchedAsNull` é `true`, subpadrões inigualáveis são devolvidos como nulos; caso contrário, são devolvidos como uma cadeia vazia ou não devolvidos: +Se `$unmatchedAsNull` for `true`, subpadrões não capturados são retornados como null; caso contrário, são retornados como uma string vazia ou não são retornados: ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~', unmatchedAsNull: true); @@ -516,7 +516,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~', unmatchedAsNull: true); ] */ ``` -Se `$utf8` é `true`, a avaliação muda para o modo Unicode. Isto é semelhante a especificar o modificador `u`: +Se `$utf8` for `true`, a avaliação muda para o modo Unicode. Semelhante a especificar o modificador `u`: ```php Strings::matchAll('žlutý kůň', '~\w+~'); @@ -532,9 +532,9 @@ Strings::matchAll('žlutý kůň', '~\w+~', utf8: true); ] */ ``` -O parâmetro `$offset` pode ser usado para especificar a posição a partir da qual se inicia a busca (em bytes; em caracteres se `$utf8` estiver definido). +O parâmetro `$offset` pode ser usado para especificar a posição a partir da qual a busca deve começar (em bytes; se `$utf8` estiver definido, em caracteres). -Se `$captureOffset` for `true`, para cada partida ocorrida, sua posição na cadeia também será devolvida (em bytes; em caracteres se `$utf8` estiver definido). Isto muda o valor de retorno para uma matriz onde cada elemento é um par que consiste da corda combinada e sua posição: +Se `$captureOffset` for `true`, para cada correspondência encontrada, sua posição na string (em bytes; se `$utf8` estiver definido, em caracteres) também será retornada. Isso altera o valor de retorno para um array onde cada elemento é um par composto pela string correspondente e sua posição: ```php Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true); @@ -550,11 +550,21 @@ Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true, utf8: true); ] */ ``` +Se `$lazy` for `true`, a função retorna um `Generator` em vez de um array, o que traz vantagens significativas de desempenho ao trabalhar com strings grandes. O gerador permite buscar correspondências gradualmente, em vez de toda a string de uma vez. Isso permite trabalhar eficientemente mesmo com textos de entrada extremamente grandes. Além disso, você pode interromper o processamento a qualquer momento se encontrar a correspondência desejada, economizando tempo de computação. + +```php +$matches = Strings::matchAll($largeText, '~\w+~', lazy: true); +foreach ($matches as $match) { + echo "Encontrado: $match[0]\n"; + // O processamento pode ser interrompido a qualquer momento +} +``` + replace(string $subject, string|array $pattern, string|callable $replacement='', int $limit=-1, bool $captureOffset=false, bool $unmatchedAsNull=false, bool $utf8=false): string .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Substitui todas as ocorrências que correspondem à expressão regular. O `$replacement` é ou uma máscara de corda substituta ou uma callback. +Substitui todas as ocorrências que correspondem à expressão regular. `$replacement` é uma máscara de string de substituição ou um callback. ```php Strings::replace('hello, world!', '~\w+~', '--'); @@ -564,7 +574,7 @@ Strings::replace('hello, world!', '~\w+~', fn($m) => strrev($m[0])); // 'olleh, dlrow!' ``` -A função também permite múltiplas substituições, passando um array do formulário `pattern => replacement` no segundo parâmetro: +A função também permite realizar múltiplas substituições passando um array no segundo parâmetro no formato `pattern => replacement`: ```php Strings::replace('hello, world!', [ @@ -574,9 +584,9 @@ Strings::replace('hello, world!', [ // '-- --!' ``` -O parâmetro `$limit` limita o número de substituições. O limite -1 significa sem limite. +O parâmetro `$limit` restringe o número de substituições realizadas. Um limite de -1 significa sem limite. -Se `$utf8` é `true`, a avaliação muda para o modo Unicode. Isto é semelhante a especificar o modificador `u`. +Se `$utf8` for `true`, a avaliação muda para o modo Unicode. Semelhante a especificar o modificador `u`. ```php Strings::replace('žlutý kůň', '~\w+~', '--'); @@ -586,7 +596,7 @@ Strings::replace('žlutý kůň', '~\w+~', '--', utf8: true); // '-- --' ``` -Se `$captureOffset` for `true`, para cada partida ocorrida, sua posição na cadeia (em bytes; em caracteres se `$utf8` estiver definido) também é passada para o retorno da chamada. Isto muda a forma da matriz passada, onde cada elemento é um par composto pela corda correspondente e sua posição. +Se `$captureOffset` for `true`, para cada correspondência encontrada, sua posição na string (em bytes; se `$utf8` estiver definido, em caracteres) também será passada para o callback. Isso altera a forma do array passado, onde cada elemento é um par composto pela string correspondente e sua posição. ```php Strings::replace( @@ -595,7 +605,7 @@ Strings::replace( function (array $m) { dump($m); return ''; }, captureOffset: true, ); -// dumps [['lut', 2]] a [['k', 8]] +// exibe [['lut', 2]] e [['k', 8]] Strings::replace( 'žlutý kůň', @@ -604,10 +614,10 @@ Strings::replace( captureOffset: true, utf8: true, ); -// dumps [['žlutý', 0]] a [['kůň', 6]] +// exibe [['žlutý', 0]] e [['kůň', 6]] ``` -Se `$unmatchedAsNull` é `true`, subpadrões inigualáveis são passados como nulos; caso contrário, são passados como um fio vazio ou não são passados: +Se `$unmatchedAsNull` for `true`, subpadrões não capturados são passados para o callback como null; caso contrário, são passados como uma string vazia ou não são passados: ```php Strings::replace( @@ -615,7 +625,7 @@ Strings::replace( '~(a)(b)*(c)~', function (array $m) { dump($m); return ''; }, ); -// dumps ['ac', 'a', '', 'c'] +// exibe ['ac', 'a', '', 'c'] Strings::replace( 'ac', @@ -623,5 +633,5 @@ Strings::replace( function (array $m) { dump($m); return ''; }, unmatchedAsNull: true, ); -// dumps ['ac', 'a', null, 'c'] +// exibe ['ac', 'a', null, 'c'] ``` diff --git a/utils/pt/type.texy b/utils/pt/type.texy index 3435ce9752..8b8eae1ebc 100644 --- a/utils/pt/type.texy +++ b/utils/pt/type.texy @@ -2,7 +2,7 @@ Tipo PHP ******** .[perex] -[api:Nette\Utils\Type] é uma classe do tipo de dados PHP. +[api:Nette\Utils\Type] é uma classe para trabalhar com tipos de dados PHP. Instalação: @@ -11,7 +11,7 @@ Instalação: composer require nette/utils ``` -Todos os exemplos assumem que a seguinte classe está definida: +Todos os exemplos assumem que um alias foi criado: ```php use Nette\Utils\Type; @@ -21,7 +21,7 @@ use Nette\Utils\Type; fromReflection($reflection): ?Type .[method] -------------------------------------------- -O método estático cria um objeto do tipo baseado na reflexão. O parâmetro pode ser um objeto `ReflectionMethod` ou `ReflectionFunction` (retorna o tipo do valor de retorno) ou um objeto `ReflectionParameter` ou `ReflectionProperty`. Resolve `self`, `static` e `parent` para o nome da classe real. Se o objeto não tem tipo, ele retorna `null`. +O método estático cria um objeto Type com base na reflexão. O parâmetro pode ser um objeto `ReflectionMethod` ou `ReflectionFunction` (retorna o tipo de valor de retorno) ou `ReflectionParameter` ou `ReflectionProperty`. Traduz `self`, `static` e `parent` para o nome real da classe. Se o sujeito não tiver tipo, retorna `null`. ```php class DemoClass @@ -37,7 +37,7 @@ echo Type::fromReflection($prop); // 'DemoClass' fromString(string $type): Type .[method] ---------------------------------------- -O método estático cria o objeto Tipo de acordo com a notação de texto. +O método estático cria um objeto Type a partir de uma notação de texto. ```php $type = Type::fromString('Foo|Bar'); @@ -48,10 +48,10 @@ echo $type; // 'Foo|Bar' getNames(): (string|array)[] .[method] -------------------------------------- -Retorna o conjunto de subtipos que compõem o tipo composto como cordas. +Retorna um array de subtipos que compõem o tipo composto, como strings. ```php -$type = Type::fromString('string|null'); // nebo '?string' +$type = Type::fromString('string|null'); // ou '?string' $type->getNames(); // ['string', 'null'] $type = Type::fromString('(Foo&Bar)|string'); @@ -62,10 +62,10 @@ $type->getNames(); // [['Foo', 'Bar'], 'string'] getTypes(): Type[] .[method] ---------------------------- -Retorna o conjunto de subtipos que compõem o tipo composto como objetos `Type`: +Retorna um array de subtipos que compõem o tipo composto, como objetos `ReflectionType`: ```php -$type = Type::fromString('string|null'); // or '?string' +$type = Type::fromString('string|null'); // ou '?string' $type->getTypes(); // [Type::fromString('string'), Type::fromString('null')] $type = Type::fromString('(Foo&Bar)|string'); @@ -79,7 +79,7 @@ $type->getTypes(); // [Type::fromString('Foo'), Type::fromString('Bar')] getSingleName(): ?string .[method] ---------------------------------- -Devolve o nome do tipo para tipos simples, caso contrário, nulo. +Para tipos simples, retorna o nome do tipo, caso contrário, null. ```php $type = Type::fromString('string|null'); @@ -99,14 +99,14 @@ echo $type->getSingleName(); // null isSimple(): bool .[method] -------------------------- -Retorna se é um tipo simples. Os tipos simples anuláveis também são considerados tipos simples: +Retorna se é um tipo simples. Tipos simples anuláveis também são considerados tipos simples: ```php $type = Type::fromString('string'); $type->isSimple(); // true $type->isUnion(); // false -$type = Type::fromString('?Foo'); // nebo 'Foo|null' +$type = Type::fromString('?Foo'); // ou 'Foo|null' $type->isSimple(); // true $type->isUnion(); // true ``` @@ -115,10 +115,10 @@ $type->isUnion(); // true isUnion(): bool .[method] ------------------------- -Retorna se é um tipo de sindicato. +Retorna se é um tipo de união. ```php -$type = Type::fromString('Foo&Bar'); +$type = Type::fromString('string|int'); $type->isUnion(); // true ``` @@ -130,7 +130,7 @@ Retorna se é um tipo de interseção. ```php -$type = Type::fromString('string&int'); +$type = Type::fromString('Foo&Bar'); $type->isIntersection(); // true ``` @@ -138,7 +138,7 @@ $type->isIntersection(); // true isBuiltin(): bool .[method] --------------------------- -Retorna se o tipo é ao mesmo tempo um tipo simples e um tipo PHP embutido. +Retorna se o tipo é simples e também um tipo embutido do PHP. ```php $type = Type::fromString('string'); @@ -155,7 +155,7 @@ $type->isBuiltin(); // false isClass(): bool .[method] ------------------------- -Retorna se o tipo é tanto um nome simples como um nome de classe. +Retorna se o tipo é simples e também um nome de classe. ```php $type = Type::fromString('string'); @@ -172,7 +172,7 @@ $type->isClass(); // false isClassKeyword(): bool .[method] -------------------------------- -Determinar se o tipo é um dos tipos internos `self`, `parent`, `static`. +Retorna se o tipo é um dos tipos internos `self`, `parent`, `static`. ```php $type = Type::fromString('self'); @@ -186,7 +186,7 @@ $type->isClassKeyword(); // false allows(string $type): bool .[method] ------------------------------------ -O método `allows()` verifica a compatibilidade de tipo. Por exemplo, ele permite verificar se um valor de um determinado tipo pode ser passado como parâmetro. +O método `allows()` verifica a compatibilidade de tipos. Por exemplo, permite verificar se um valor de um determinado tipo pode ser passado como parâmetro. ```php $type = Type::fromString('string|null'); diff --git a/utils/pt/validators.texy b/utils/pt/validators.texy index c5f3592942..8d023aa563 100644 --- a/utils/pt/validators.texy +++ b/utils/pt/validators.texy @@ -1,8 +1,8 @@ -Validadores de valor -******************** +Validadores de valores +********************** .[perex] -Precisa verificar rápida e facilmente se uma variável contém, por exemplo, um endereço de e-mail válido? Então [api:Nette\Utils\Validators] virá a calhar, uma classe estática com funções úteis para a validação de valores. +Precisa verificar rápida e facilmente se uma variável contém, por exemplo, um endereço de e-mail válido? Então [api:Nette\Utils\Validators], uma classe estática com funções úteis para validação de valores, será útil para você. Instalação: @@ -11,17 +11,17 @@ Instalação: composer require nette/utils ``` -Todos os exemplos assumem que a seguinte classe está definida: +Todos os exemplos assumem que um alias foi criado: ```php use Nette\Utils\Validators; ``` -Utilização básica .[#toc-basic-usage] -===================================== +Uso Básico +========== -A classe `Validators` tem vários métodos para validação de valores, tais como [isList() |#isList()], [isUnicode() |#isUnicode()], [isEmail() |#isEmail()], [isUrl() |#isUrl()], etc., para uso em seu código: +A classe possui vários métodos para verificar valores, como [#isUnicode], [#isEmail], [#isUrl], etc., para uso em seu código: ```php if (!Validators::isEmail($email)) { @@ -29,7 +29,7 @@ if (!Validators::isEmail($email)) { } ``` -Além disso, pode verificar se o valor satisfaz os chamados [tipos esperados |#expected types], que é um fio onde as opções individuais são separadas por uma barra vertical `|`. Isto facilita a verificação dos tipos de união utilizando [if() |#if()]: +Além disso, pode verificar se um valor é um dos chamados [#tipos esperados], que é uma string onde as opções individuais são separadas por uma barra vertical `|`. Podemos facilmente verificar vários tipos usando [#is()]: ```php if (!Validators::is($val, 'int|string|bool')) { @@ -37,118 +37,118 @@ if (!Validators::is($val, 'int|string|bool')) { } ``` -Mas também lhe dá a oportunidade de criar um sistema onde é necessário escrever as expectativas como cordas (por exemplo, em anotações ou configuração) e depois verificar de acordo com elas. +Mas isso também nos dá a possibilidade de criar um sistema onde as expectativas precisam ser escritas como strings (por exemplo, em anotações ou configuração) e depois verificar os valores de acordo com elas. -Você também pode declarar a [afirmação |#assert], que lança uma exceção se não for cumprida. +Para tipos esperados, também podemos usar a asserção [#assert()], que lança uma exceção se não for satisfeita. -Tipos Esperados .[#toc-expected-types] -====================================== +Tipos Esperados +=============== -Um tipo esperado é uma cadeia que consiste em uma ou mais variantes separadas por uma barra vertical `|`, similar to writing types in PHP (ie. `'int|string|bool')`. Notação nula também é permitida `?int`. +Os tipos esperados formam uma string que consiste em uma ou mais variantes separadas por uma barra vertical `|`, semelhante à forma como os tipos são escritos em PHP (por exemplo, `'int|string|bool'`). A notação anulável `?int` também é aceita. -Um array onde todos os elementos são de um certo tipo está escrito no formulário `int[]`. +Arrays onde todos os elementos são de um certo tipo são escritos na forma `int[]`. -Alguns tipos podem ser seguidos por um cólon e o comprimento `:length` ou a faixa `:[min]..[max]`por exemplo `string:10` (um fio com 10 bytes de comprimento), `float:10..` (número 10 e maior), `array:..10` (conjunto de até dez elementos) ou `list:10..20` (lista com 10 a 20 elementos), ou uma expressão regular para `pattern:[0-9]+`. +Alguns tipos podem ser seguidos por dois pontos e um comprimento `:length` ou um intervalo `:[min]..[max]`, por exemplo, `string:10` (string com 10 bytes de comprimento), `float:10..` (número 10 ou maior), `array:..10` (array com até dez elementos) ou `list:10..20` (lista com 10 a 20 elementos), ou uma expressão regular para `pattern:[0-9]+`. -Visão geral dos tipos e regras: +Visão geral de tipos e regras: .[wide] -| Tipos de PHP ||| +| Tipos PHP || |-------------------------- -| `array` .{width: 140px} | intervalo para o número de itens pode ser dado -| `bool` | -| `float` | intervalo para o valor pode ser dado -| `int` | intervalo para o valor pode ser dado -| `null` | -| `object` | +| `array` .{width: 140px} | pode especificar um intervalo para o número de elementos +| `bool` | +| `float` | pode especificar um intervalo para o valor +| `int` | pode especificar um intervalo para o valor +| `null` | +| `object` | | `resource` | -| `scalar` | int|float |bool |string -| `string` | o intervalo para o comprimento em bytes pode ser dado +| `scalar` | int\|float\|bool\|string +| `string` | pode especificar um intervalo para o comprimento em bytes | `callable` | | `iterable` | -| `mixed` | -|------------------------------------------------ -| pseudo-tipos ||| -|------------------------------------------------ -| `list` | [matriz indexada |#isList], o intervalo para o número de itens pode ser dado -| `none` | valor vazio: `''`| `null`, `false` -| `number` | int\\ |float -| `numeric` | [número incluindo representação textual |#isNumeric] -| `numericint`| [integer incluindo representação textual |#isNumericInt] -| `unicode` | [Cordel UTF-8 |#isUnicode], o intervalo para o comprimento em caracteres pode ser dado -|------------------------------------------------ -| classe de caracteres (não pode ser uma corda vazia) ||| +| `mixed` | +|-------------------------- +| pseudo-tipos || |------------------------------------------------ -| `alnum` | todos os caracteres são alfanuméricos -| `alpha` | todos os caracteres são letras `[A-Za-z]` -| `digit` | todos os caracteres são dígitos -| `lower` | todos os caracteres são letras minúsculas `[a-z]` -| `space` | todos os caracteres são espaços -| `upper` | todos os caracteres são letras maiúsculas `[A-Z]` -| `xdigit` | todos os caracteres são dígitos hexadecimais `[0-9A-Fa-f]` +| `list` | array indexado, pode especificar um intervalo para o número de elementos +| `none` | valor vazio: `''`, `null`, `false` +| `number` | int\|float +| `numeric` | [número incluindo representação textual |#isNumeric] +| `numericint`| [número inteiro incluindo representação textual |#isNumericInt] +| `unicode` | [string UTF-8 |#isUnicode], pode especificar um intervalo para o comprimento em caracteres +|-------------------------- +| classe de caracteres (não pode ser uma string vazia) || |------------------------------------------------ -| validação da sintaxe ||| +| `alnum` | todos os caracteres são alfanuméricos +| `alpha` | todos os caracteres são letras `[A-Za-z]` +| `digit` | todos os caracteres são dígitos +| `lower` | todos os caracteres são letras minúsculas `[a-z]` +| `space` | todos os caracteres são espaços +| `upper` | todos os caracteres são letras maiúsculas `[A-Z]` +| `xdigit` | todos os caracteres são dígitos hexadecimais `[0-9A-Fa-f]` +|-------------------------- +| validação de sintaxe || |------------------------------------------------ -| `pattern` | uma expressão regular que a string **entire*** deve combinar -| `email` | [Email |#isEmail] +| `pattern` | expressão regular que deve corresponder à string **inteira** +| `email` | [E-mail |#isEmail] | `identifier`| [Identificador PHP |#isPhpIdentifier] -| `url` | [URL |#isUrl] -| `uri` | [URI |#isUri] -|------------------------------------------------ -| validação do ambiente ||| +| `url` | [URL |#isUrl] +| `uri` | [URI |#isUri] +|-------------------------- +| verificação de ambiente || |------------------------------------------------ -| `class` | é uma classe existente -| `interface` | é a interface existente +| `class` | é uma classe existente +| `interface` | é uma interface existente | `directory` | é um diretório existente -| `file` | é um arquivo existente +| `file` | é um arquivo existente -Asserção .[#toc-assertion] -========================== +Asserções +========= assert($value, string $expected, string $label='variable'): void .[method] -------------------------------------------------------------------------- -Verifica que o valor é do [tipo esperado |#expected types], separado por tubo. Caso contrário, lança a exceção [api:Nette\Utils\AssertionException]. A palavra `variable` na mensagem de exceção pode ser substituída pelo parâmetro `$label`. +Verifica se o valor é um dos [#tipos esperados] separados por uma barra vertical. Se não for, lança uma exceção [api:Nette\Utils\AssertionException]. A palavra `variable` no texto da exceção pode ser substituída por outra usando o parâmetro `$label`. ```php Validators::assert('Nette', 'string:5'); // OK Validators::assert('Lorem ipsum dolor sit', 'string:78'); -// AssertionException: The variable expects to be string:78, string 'Lorem ipsum dolor sit' given. +// AssertionException: A variável espera ser string:78, string 'Lorem ipsum dolor sit' fornecida. ``` -assertField(array $array, string|int $key, string $expected=null, string $label=null): void .[method] ------------------------------------------------------------------------------------------------------ +assertField(array $array, string|int $key, ?string $expected=null, ?string $label=null): void .[method] +------------------------------------------------------------------------------------------------------- -Verifica que o elemento `$key` na matriz `$array` é de [tipos esperados |#expected types] separados por tubo. Caso contrário, ele lança a exceção [api:Nette\Utils\AssertionException]. A string `item '%' in array` na mensagem de exceção pode ser substituída pelo parâmetro `$label`. +Verifica se o elemento sob a chave `$key` no array `$array` é um dos [#tipos esperados] separados por uma barra vertical. Se não for, lança uma exceção [api:Nette\Utils\AssertionException]. A string `item '%' in array` no texto da exceção pode ser substituída por outra usando o parâmetro `$label`. ```php $arr = ['foo' => 'Nette']; Validators::assertField($arr, 'foo', 'string:5'); // OK Validators::assertField($arr, 'bar', 'string:15'); -// AssertionException: Missing item 'bar' in array. +// AssertionException: Item 'bar' ausente no array. Validators::assertField($arr, 'foo', 'int'); -// AssertionException: The item 'foo' in array expects to be int, string 'Nette' given. +// AssertionException: O item 'foo' no array espera ser int, string 'Nette' fornecida. ``` -Validadores .[#toc-validators] -============================== +Validadores +=========== is($value, string $expected): bool .[method] -------------------------------------------- -Verifica se o valor é do [tipo esperado |#expected types], separado por tubo. +Verifica se o valor é um dos [#tipos esperados] separados por uma barra vertical. ```php Validators::is(1, 'int|float'); // true Validators::is(23, 'int:0..10'); // false -Validators::is('Nette Framework', 'string:15'); // true, length is 15 bytes +Validators::is('Nette Framework', 'string:15'); // true, o comprimento é de 15 bytes Validators::is('Nette Framework', 'string:8..'); // true Validators::is('Nette Framework', 'string:30..40'); // false ``` @@ -157,7 +157,7 @@ Validators::is('Nette Framework', 'string:30..40'); // false isEmail(mixed $value): bool .[method] ------------------------------------- -Verifica que o valor é um endereço de e-mail válido. Não verifica que o domínio realmente existe, apenas a sintaxe é verificada. A função também conta com [TLDs |https://en.wikipedia.org/wiki/Top-level_domain] futuros, que também podem estar em unicode. +Verifica se o valor é um endereço de e-mail válido. Não verifica se o domínio realmente existe, apenas a sintaxe é verificada. A função também considera futuros [TLDs|https://pt.wikipedia.org/wiki/Dom%C3%ADnio_de_n%C3%ADvel_superior], que também podem estar em unicode. ```php Validators::isEmail('example@nette.org'); // true @@ -169,9 +169,9 @@ Validators::isEmail('nette'); // false isInRange(mixed $value, array $range): bool .[method] ----------------------------------------------------- -Verifica se o valor está na faixa determinada `[min, max]`onde o limite superior ou inferior pode ser omitido (`null`). Números, cordas e objetos DateTime podem ser comparados. +Verifica se o valor está dentro do intervalo fornecido `[min, max]`, onde o limite superior ou inferior pode ser omitido (`null`). Números, strings e objetos DateTime podem ser comparados. -Se ambos os limites estiverem ausentes (`[null, null]`) ou o valor é `null`, ele retorna `false`. +Se ambos os limites estiverem faltando (`[null, null]`) ou o valor for `null`, retorna `false`. ```php Validators::isInRange(5, [0, 5]); // true @@ -198,7 +198,7 @@ Validators::isNone('nette'); // false isNumeric(mixed $value): bool .[method] --------------------------------------- -Verifica se o valor é um número ou um número escrito em um fio. +Verifica se o valor é um número ou um número escrito em uma string. ```php Validators::isNumeric(23); // true @@ -213,7 +213,7 @@ Validators::isNumeric('1e6'); // false isNumericInt(mixed $value): bool .[method] ------------------------------------------ -Verifica se o valor é um número inteiro ou um número inteiro escrito em um fio. +Verifica se o valor é um número inteiro ou um número inteiro escrito em uma string. ```php Validators::isNumericInt(23); // true @@ -227,7 +227,7 @@ Validators::isNumericInt('nette'); // false isPhpIdentifier(string $value): bool .[method] ---------------------------------------------- -Verifica se o valor é um identificador sintáctico válido em PHP, por exemplo, para nomes de classes, métodos, funções, etc. +Verifica se o valor é um identificador sintaticamente válido em PHP, por exemplo, para nomes de classes, métodos, funções, etc. ```php Validators::isPhpIdentifier(''); // false @@ -240,7 +240,7 @@ Validators::isPhpIdentifier('one two'); // false isBuiltinType(string $type): bool .[method] ------------------------------------------- -Determina se `$type` é do tipo PHP incorporado. Caso contrário, é o nome da classe. +Verifica se `$type` é um tipo embutido do PHP. Caso contrário, é um nome de classe. ```php Validators::isBuiltinType('string'); // true @@ -251,7 +251,7 @@ Validators::isBuiltinType('Foo'); // false isTypeDeclaration(string $type): bool .[method] ----------------------------------------------- -Verifica se a declaração de tipo está sintaticamente correta. +Verifica se a declaração de tipo fornecida é sintaticamente válida. ```php Validators::isTypeDeclaration('?string'); // true @@ -268,7 +268,7 @@ Validators::isTypeDeclaration('(A|B)'); // false isClassKeyword(string $type): bool .[method] -------------------------------------------- -Determinar se `$type` é um dos tipos internos `self`, `parent`, `static`. +Verifica se `$type` é um dos tipos internos `self`, `parent`, `static`. ```php Validators::isClassKeyword('self'); // true @@ -279,7 +279,7 @@ Validators::isClassKeyword('Foo'); // false isUnicode(mixed $value): bool .[method] --------------------------------------- -Verifica se o valor é um fio UTF-8 válido. +Verifica se o valor é uma string UTF-8 válida. ```php Validators::isUnicode('nette'); // true @@ -306,7 +306,7 @@ Validators::isUrl('nette.org'); // false isUri(string $value): bool .[method] ------------------------------------ -Verifica que o valor é um endereço URI válido, ou seja, na verdade, uma cadeia que começa com um esquema sintáctico válido. +Verifica se o valor é um endereço URI válido, ou seja, uma string que começa com um esquema sintaticamente válido. ```php Validators::isUri('https://nette.org'); // true diff --git a/utils/ro/@home.texy b/utils/ro/@home.texy index a9e20302c0..c1099bd73d 100644 --- a/utils/ro/@home.texy +++ b/utils/ro/@home.texy @@ -1,42 +1,46 @@ -Utilități -********* +Nette Utils +*********** .[perex] În pachetul `nette/utils` veți găsi un set de clase utile pentru utilizarea zilnică: - [Array |Arrays] | Arrays | Nette\Utils\Arrays | [Callback |Callback] | Nette\Utils\Callback | [Data și ora |datetime] | Nette\Utils\DateTime -| [Sistem de fișiere |filesystem] | Nette\Utils\FileSystem | [Finder |Finder] | Nette\Utils\Finder -| [Flotări |Floats] | Nette\Utils\Floats - [Ajutoare |helpers] | Helpers | Nette\Utils\Helpers -| [Elemente HTML |HTML Elements] | Nette\Utils\Html - [Imagini |Images] | Imagini | Nette\Utils\Image - [JSON |JSON] | JSON | Nette\Utils\Json -| [Model de obiect |smartobject] | Nette\SmartObject & Nette\StaticClass -| [Paginator |paginator] | Nette\Utils\Paginator - [PHP Reflection |reflection] | [PHP |reflection] Reflection | Nette\Utils\Reflection - [PHP Types |type] | [PHP Types |type] | Nette\Utils\Type +| [Elemente HTML |html-elements] | Nette\Utils\Html +| [Iteratori |iterables] | Nette\Utils\Iterables +| [JSON |json] | Nette\Utils\Json | [Șiruri aleatorii |random] | Nette\Utils\Random -| [Șiruri de caractere |Strings] | Nette\Utils\Strings - [Validatori |validators] | Validators | Nette\Utils\Validators +| [Imagini |images] | Nette\Utils\Image +| [Reflexie PHP |reflection] | Nette\Utils\Reflection +| [Tipuri PHP |type] | Nette\Utils\Type +| [Array |arrays] | Nette\Utils\Arrays +| [Funcții auxiliare |helpers] | Nette\Utils\Helpers +| [Compararea numerelor float |floats] | Nette\Utils\Floats +| [Șiruri |strings] | Nette\Utils\Strings +| [Sistem de fișiere |filesystem] | Nette\Utils\FileSystem +| [Paginare |paginator] | Nette\Utils\Paginator +| [SmartObject |SmartObject] & [StaticClass |StaticClass] | Nette\SmartObject & Nette\StaticClass +| [Validator |validators] | Nette\Utils\Validators Instalare --------- -Descărcați și instalați pachetul folosind [Composer |best-practices:composer]: +Puteți descărca și instala biblioteca folosind instrumentul [Composer|best-practices:composer]: ```shell composer require nette/utils ``` -| versiune | compatibil cu PHP +| versiune | compatibil cu PHP |-----------|------------------- -| Nette Utils 4.0 | PHP 8.0 - 8.2 -| Nette Utils 3.2 | PHP 7.2 - 8.2 -| Nette Utils 3.0 - 3.1 | PHP 7.1 - 8.0 -| Nette Utils 2.5 | PHP 5.6 - 8.0 +| Nette Utils 4.0 | PHP 8.0 – 8.4 +| Nette Utils 3.2 | PHP 7.2 – 8.3 +| Nette Utils 3.0 – 3.1 | PHP 7.1 – 8.0 +| Nette Utils 2.5 | PHP 5.6 – 8.0 + +Se aplică pentru ultima versiune patch. + -Se aplică la cele mai recente versiuni de patch-uri. +Dacă actualizați pachetul la o versiune mai nouă, consultați pagina [upgrade|en:upgrading]. diff --git a/utils/ro/@left-menu.texy b/utils/ro/@left-menu.texy index 8c32b60f04..b4912ea832 100644 --- a/utils/ro/@left-menu.texy +++ b/utils/ro/@left-menu.texy @@ -1,26 +1,28 @@ -Pachetul nette/utils -******************** -- [Array-uri |Arrays] -- [Callback |Callback] -- [Data și ora |datetime] -- [Sistem de fișiere |filesystem] -- [Căutător |Finder] -- [Ajutători |helpers] -- [Elemente HTML |HTML Elements] -- [Imagini |Images] +Nette Utils +*********** +- [Callback-uri |callback] +- [Dată și oră |datetime] +- [Finder |Finder] +- [Floats |Floats] +- [Elemente HTML |html-elements] +- [Iteratori |iterables] - [JSON |JSON] +- [Șiruri aleatoare |random] +- [Imagini |images] - [Paginator |paginator] -- [Șiruri aleatorii |random] -- [SmartObject |SmartObject] -- [Reflecție PHP |reflection] -- [Șiruri de caractere |Strings] -- [Flote |Floats] +- [PHP Reflection |reflection] - [Tipuri PHP |type] -- [Validatori |validators] +- [Array-uri |arrays] +- [Funcții ajutătoare |helpers] +- [Șiruri |strings] +- [SmartObject |SmartObject] +- [StaticClass |StaticClass] +- [Sistem de fișiere |filesystem] +- [Validator |validators] -Alte utilități -************** -- [NEON |neon:] -- [Hashing de parole |security:passwords] -- [URL Parser și Builder |http:urls] +Alte instrumente +**************** +- [NEON|neon:] +- [Hasharea parolelor |security:passwords] +- [Parsarea și compunerea URL-urilor |http:urls] diff --git a/utils/ro/@meta.texy b/utils/ro/@meta.texy new file mode 100644 index 0000000000..9c744b37d6 --- /dev/null +++ b/utils/ro/@meta.texy @@ -0,0 +1 @@ +{{sitename: Documentație Nette}} diff --git a/utils/ro/arrays.texy b/utils/ro/arrays.texy index 7c76a41abd..cae59f62dd 100644 --- a/utils/ro/arrays.texy +++ b/utils/ro/arrays.texy @@ -1,8 +1,8 @@ -Funcții de matrice -****************** +Lucrul cu array-uri +******************* .[perex] -Această pagină se referă la clasele [Nette\Utils\Arrays |#Arrays], [ArrayHash |#ArrayHash] și [ArrayList |#ArrayList], care sunt legate de array-uri. +Această pagină este dedicată claselor [Nette\Utils\Arrays |#Arrays], [#ArrayHash] și [#ArrayList], care se referă la array-uri. Instalare: @@ -12,22 +12,63 @@ composer require nette/utils ``` -Array-uri .[#toc-arrays] -======================== +Arrays +====== -[api:Nette\Utils\Arrays] este o clasă statică, care conține o serie de funcții practice de matrice. +[api:Nette\Utils\Arrays] este o clasă statică ce conține funcții utile pentru lucrul cu array-uri. Echivalentul său pentru iteratori este [Nette\Utils\Iterables|iterables]. -Exemplele următoare presupun că este definit următorul alias de clasă: +Următoarele exemple presupun crearea unui alias: ```php use Nette\Utils\Arrays; ``` +associate(array $array, mixed $path): array|\stdClass .[method] +--------------------------------------------------------------- + +Funcția transformă flexibil array-ul `$array` într-un array asociativ sau obiecte conform căii specificate `$path`. Calea poate fi un șir sau un array. Este formată din numele cheilor array-ului de intrare și operatori precum '[]', '->', '=', și '|'. Aruncă `Nette\InvalidArgumentException` în cazul în care calea este invalidă. + +```php +// conversie la array asociativ după o cheie simplă +$arr = [ + ['name' => 'John', 'age' => 11], + ['name' => 'Mary', 'age' => null], + // ... +]; +$result = Arrays::associate($arr, 'name'); +// $result = ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// atribuirea valorilor de la o cheie la alta folosind operatorul = +$result = Arrays::associate($arr, 'name=age'); // sau ['name', '=', 'age'] +// $result = ['John' => 11, 'Mary' => null, ...] +``` + +```php +// crearea unui obiect folosind operatorul -> +$result = Arrays::associate($arr, '->name'); // sau ['->', 'name'] +// $result = (object) ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// combinarea cheilor folosind operatorul | +$result = Arrays::associate($arr, 'name|age'); // sau ['name', '|', 'age'] +// $result: ['John' => ['name' => 'John', 'age' => 11], 'Paul' => ['name' => 'Paul', 'age' => 44]] +``` + +```php +// adăugarea într-un array folosind [] +$result = Arrays::associate($arr, 'name[]'); // sau ['name', '[]'] +// $result: ['John' => [['name' => 'John', 'age' => 22], ['name' => 'John', 'age' => 11]]] +``` + + contains(array $array, $value): bool .[method] ---------------------------------------------- -Testează un tablou pentru prezența unei valori. Utilizează o comparație strictă (`===`) +Testează array-ul pentru prezența unei valori. Utilizează comparația strictă (`===`). ```php Arrays::contains([1, 2, 3], 1); // true @@ -35,10 +76,10 @@ Arrays::contains(['1', false], 1); // false ``` -every(iterable $array, callable $callback): bool .[method] ----------------------------------------------------------- +every(array $array, callable $predicate): bool .[method] +-------------------------------------------------------- -Testează dacă toate elementele din matrice trec testul implementat de funcția furnizată, care are semnătura `function ($value, $key, array $array): bool`. +Testează dacă toate elementele din array trec testul implementat în `$predicate` cu semnătura `function ($value, $key, array $array): bool`. ```php $array = [1, 30, 39, 29, 10, 13]; @@ -46,24 +87,59 @@ $isBelowThreshold = fn($value) => $value < 40; $res = Arrays::every($array, $isBelowThreshold); // true ``` -A se vedea [some() |#some()]. +Vezi [#some()]. -first(array $array): mixed .[method] ------------------------------------- +filter(array $array, callable $predicate): array .[method]{data-version:4.0.4} +------------------------------------------------------------------------------ + +Returnează un nou array conținând toate perechile cheie-valoare care corespund predicatului specificat. Callback-ul are semnătura `function ($value, int|string $key, array $array): bool`. + +```php +Arrays::filter( + ['a' => 1, 'b' => 2, 'c' => 3], + fn($v) => $v < 3, +); +// ['a' => 1, 'b' => 2] +``` + + +first(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------- + +Returnează primul element (care corespunde predicatului, dacă este specificat). Dacă un astfel de element nu există, returnează rezultatul apelării `$else` sau null. Parametrul `$predicate` are semnătura `function ($value, int|string $key, array $array): bool`. + +Nu modifică pointerul intern, spre deosebire de `reset()`. Parametrii `$predicate` și `$else` există începând cu versiunea 4.0.4. + +```php +Arrays::first([1, 2, 3]); // 1 +Arrays::first([1, 2, 3], fn($v) => $v > 2); // 3 +Arrays::first([]); // null +Arrays::first([], else: fn() => false); // false +``` + +Vezi [#last()]. -Returnează primul element din tablou sau nul dacă tabloul este gol. Nu modifică pointerul intern, spre deosebire de `reset()`. + +firstKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +------------------------------------------------------------------------------------------------ + +Returnează cheia primului element (care corespunde predicatului, dacă este specificat) sau null dacă un astfel de element nu există. Predicatul `$predicate` are semnătura `function ($value, int|string $key, array $array): bool`. ```php -Arrays::first([1, 2, 3]); // 1 -Arrays::first([]); // null +Arrays::firstKey([1, 2, 3]); // 0 +Arrays::firstKey([1, 2, 3], fn($v) => $v > 2); // 2 +Arrays::firstKey(['a' => 1, 'b' => 2]); // 'a' +Arrays::firstKey([]); // null ``` +Vezi [#lastKey()]. + flatten(array $array, bool $preserveKeys=false): array .[method] ---------------------------------------------------------------- -Transformă matricea multidimensională în matrice plată. +Unifică un array multi-nivel într-unul plat. ```php $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); @@ -71,62 +147,62 @@ $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); ``` -get(array $array, string|int|array $key, mixed $default=null): mixed .[method] ------------------------------------------------------------------------------- +get(array $array, string|int|array $key, ?mixed $default=null): mixed .[method] +------------------------------------------------------------------------------- -Returnează `$array[$key]` item. Dacă nu există, se aruncă `Nette\InvalidArgumentException`, cu excepția cazului în care se stabilește o valoare implicită ca al treilea argument. +Returnează elementul `$array[$key]`. Dacă nu există, aruncă fie excepția `Nette\InvalidArgumentException`, fie, dacă este specificat al treilea parametru `$default`, îl returnează pe acesta. ```php -// dacă $array['foo'] nu există, se aruncă o excepție +// dacă $array['foo'] nu există, aruncă o excepție $value = Arrays::get($array, 'foo'); -// dacă $array['foo'] nu există, se returnează 'bar'. +// dacă $array['foo'] nu există, returnează 'bar' $value = Arrays::get($array, 'foo', 'bar'); ``` -Argumentul `$key` poate fi la fel de bine o matrice. +Cheia `$key` poate fi și un array. ```php $array = ['color' => ['favorite' => 'red'], 5]; $value = Arrays::get($array, ['color', 'favorite']); -// returns 'red' +// returnează 'red' ``` getRef(array &$array, string|int|array $key): mixed .[method] ------------------------------------------------------------- -Obține o referință la `$array[$key]`. Dacă indexul nu există, se creează unul nou cu valoarea `null`. +Obține o referință la elementul specificat al array-ului. Dacă elementul nu există, va fi creat cu valoarea null. ```php $valueRef = & Arrays::getRef($array, 'foo'); -// returnează referința $array['foo'] +// returnează o referință la $array['foo'] ``` -Funcționează cu array-uri multidimensionale, precum și cu [get() |#get()]. +La fel ca funcția [#get()], poate lucra cu array-uri multidimensionale. ```php -$value = & Arrays::get($array, ['color', 'favorite']); -// returnează referința $array['color']['favorite']]. +$value = & Arrays::getRef($array, ['color', 'favorite']); +// returnează o referință la $array['color']['favorite'] ``` grep(array $array, string $pattern, bool $invert=false): array .[method] ------------------------------------------------------------------------ -Returnează numai acele elemente din matrice care corespund unei expresii regulate `$pattern`. Dacă `$invert` este `true`, returnează elementele care nu se potrivesc. O eroare de compilare sau de execuție a expresiei regizorale aruncă `Nette\RegexpException`. +Returnează doar acele elemente ale array-ului a căror valoare corespunde expresiei regulate `$pattern`. Dacă `$invert` este `true`, returnează invers, elementele care nu corespund. O eroare la compilarea sau procesarea expresiei aruncă excepția `Nette\RegexpException`. ```php $filteredArray = Arrays::grep($array, '~^\d+$~'); -// returnează numai elemente numerice +// returnează doar elementele array-ului formate din cifre ``` insertAfter(array &$array, string|int|null $key, array $inserted): void .[method] --------------------------------------------------------------------------------- -Introduce conținutul matricei `$inserted` în `$array` imediat după `$key`. Dacă `$key` este `null` (sau nu există), se introduce la sfârșit. +Inserează conținutul array-ului `$inserted` în array-ul `$array` imediat după elementul cu cheia `$key`. Dacă `$key` este `null` (sau nu se află în array), se inserează la sfârșit. ```php $array = ['first' => 10, 'second' => 20]; @@ -138,7 +214,7 @@ Arrays::insertAfter($array, 'first', ['hello' => 'world']); insertBefore(array &$array, string|int|null $key, array $inserted): void .[method] ---------------------------------------------------------------------------------- -Introduce conținutul tabloului `$inserted` în `$array` înainte de `$key`. Dacă `$key` este `null` (sau nu există), se introduce la început. +Inserează conținutul array-ului `$inserted` în array-ul `$array` înainte de elementul cu cheia `$key`. Dacă `$key` este `null` (sau nu se află în array), se inserează la început. ```php $array = ['first' => 10, 'second' => 20]; @@ -150,7 +226,7 @@ Arrays::insertBefore($array, 'first', ['hello' => 'world']); invoke(iterable $callbacks, ...$args): array .[method] ------------------------------------------------------ -Invocă toate callback-urile și returnează matricea de rezultate. +Invocă toate callback-urile și returnează un array de rezultate. ```php $callbacks = [ @@ -166,7 +242,7 @@ $array = Arrays::invoke($callbacks, 5, 11); invokeMethod(iterable $objects, string $method, ...$args): array .[method] -------------------------------------------------------------------------- -Invocă metoda pe fiecare obiect dintr-un tablou și returnează un tablou de rezultate. +Apelează o metodă pe fiecare obiect din array și returnează un array de rezultate. ```php $objects = ['a' => $obj1, 'b' => $obj2]; @@ -179,7 +255,7 @@ $array = Arrays::invokeMethod($objects, 'foo', 1, 2); isList(array $array): bool .[method] ------------------------------------ -Verifică dacă matricea este indexată în ordinea crescătoare a cheilor numerice de la zero, adică o listă. +Verifică dacă array-ul este indexat conform unei serii ascendente de chei numerice de la zero, a.k.a list. ```php Arrays::isList(['a', 'b', 'c']); // true @@ -188,21 +264,42 @@ Arrays::isList(['a' => 1, 'b' => 2]); // false ``` -last(array $array): mixed .[method] ------------------------------------ +last(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------ + +Returnează ultimul element (care corespunde predicatului, dacă este specificat). Dacă un astfel de element nu există, returnează rezultatul apelării `$else` sau null. Parametrul `$predicate` are semnătura `function ($value, int|string $key, array $array): bool`. -Returnează ultimul element din tablou sau zero dacă tabloul este gol. Spre deosebire de `end()`, nu modifică pointerul intern. +Nu modifică pointerul intern, spre deosebire de `end()`. Parametrii `$predicate` și `$else` există începând cu versiunea 4.0.4. ```php -Arrays::last([1, 2, 3]); // 3 -Arrays::last([]); // null +Arrays::last([1, 2, 3]); // 3 +Arrays::last([1, 2, 3], fn($v) => $v < 3); // 2 +Arrays::last([]); // null +Arrays::last([], else: fn() => false); // false ``` +Vezi [#first()]. -map(iterable $array, callable $callback): array .[method] + +lastKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +----------------------------------------------------------------------------------------------- + +Returnează cheia ultimului element (care corespunde predicatului, dacă este specificat) sau null dacă un astfel de element nu există. Predicatul `$predicate` are semnătura `function ($value, int|string $key, array $array): bool`. + +```php +Arrays::lastKey([1, 2, 3]); // 2 +Arrays::lastKey([1, 2, 3], fn($v) => $v < 3); // 1 +Arrays::lastKey(['a' => 1, 'b' => 2]); // 'b' +Arrays::lastKey([]); // null +``` + +Vezi [#firstKey()]. + + +map(array $array, callable $transformer): array .[method] --------------------------------------------------------- -Solicită `$callback` pentru toate elementele din matrice și returnează matricea de valori de returnare. Callback-ul are semnătura `function ($value, $key, array $array): bool`. +Apelează `$transformer` pe toate elementele din array și returnează un array de valori returnate. Callback-ul are semnătura `function ($value, $key, array $array): mixed`. ```php $array = ['foo', 'bar', 'baz']; @@ -211,10 +308,24 @@ $res = Arrays::map($array, fn($value) => $value . $value); ``` +mapWithKeys(array $array, callable $transformer): array .[method] +----------------------------------------------------------------- + +Creează un nou array prin transformarea valorilor și cheilor array-ului original. Funcția `$transformer` are semnătura `function ($value, $key, array $array): ?array{$newKey, $newValue}`. Dacă `$transformer` returnează `null`, elementul este omis. Pentru elementele păstrate, primul element al array-ului returnat este folosit ca nouă cheie și al doilea element ca nouă valoare. + +```php +$array = ['a' => 1, 'b' => 2]; +$result = Arrays::mapWithKeys($array, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +Această metodă este utilă în situații în care trebuie să schimbați structura unui array (cheile și valorile simultan) sau să filtrați elemente în timpul transformării (returnând null pentru elementele nedorite). + + mergeTree(array $array1, array $array2): array .[method] -------------------------------------------------------- -Fuzionează recurent două câmpuri. Este util, de exemplu, pentru fuzionarea structurilor arborescente. Se comportă ca operatorul `+` pentru array, adică adaugă o pereche cheie/valoare din al doilea array la primul array și păstrează valoarea din primul array în cazul unei coliziuni de chei. +Combină recursiv două array-uri. Este util, de exemplu, pentru combinarea structurilor arborescente. La combinare, respectă aceleași reguli ca operatorul `+` aplicat pe array-uri, adică adaugă la primul array perechile cheie/valoare din al doilea array și, în caz de coliziune a cheilor, păstrează valoarea din primul array. ```php $array1 = ['color' => ['favorite' => 'red'], 5]; @@ -224,13 +335,13 @@ $array = Arrays::mergeTree($array1, $array2); // $array = ['color' => ['favorite' => 'red', 'blue'], 5]; ``` -Valorile din al doilea tablou sunt întotdeauna adăugate la primul. Dispariția valorii `10` din cel de-al doilea tablou poate părea puțin confuză. Trebuie remarcat faptul că această valoare, precum și valoarea `5` in the first array have the same numeric key `0`, astfel încât în câmpul rezultat există doar un element din prima matrice. +Valorile din al doilea array sunt întotdeauna adăugate la sfârșitul primului. Dispariția valorii `10` din al doilea array poate părea puțin derutantă. Trebuie să realizăm că această valoare, la fel ca valoarea `5` din primul array, au atribuită aceeași cheie numerică `0`, de aceea în array-ul rezultat este doar elementul din primul array. -normalize(array $array, string $filling=null): array .[method] --------------------------------------------------------------- +normalize(array $array, ?string $filling=null): array .[method] +--------------------------------------------------------------- -Normalizează matricea în matrice asociativă. Înlocuiește cheile numerice cu valorile lor, noua valoare va fi `$filling`. +Normalizează array-ul la un array asociativ. Cheile numerice le înlocuiește cu valorile lor, noua valoare va fi `$filling`. ```php $array = Arrays::normalize([1 => 'first', 'a' => 'second']); @@ -243,26 +354,26 @@ $array = Arrays::normalize([1 => 'first', 'a' => 'second'], 'foobar'); ``` -pick(array &$array, string|int $key, mixed $default=null): mixed .[method] --------------------------------------------------------------------------- +pick(array &$array, string|int $key, ?mixed $default=null): mixed .[method] +--------------------------------------------------------------------------- -Returnează și elimină valoarea unui element dintr-un tablou. Dacă acesta nu există, se aruncă o excepție sau se returnează `$default`, dacă este furnizat. +Returnează și elimină valoarea unui element din array. Dacă nu există, aruncă o excepție sau returnează valoarea `$default`, dacă este specificată. ```php -$array = [1 => 'foo', null => 'bar']; -$a = Arrays::pick($array, null); +$array = [1 => 'foo', 'x' => 'bar']; +$a = Arrays::pick($array, 'x'); // $a = 'bar' $b = Arrays::pick($array, 'not-exists', 'foobar'); // $b = 'foobar' $c = Arrays::pick($array, 'not-exists'); -// aruncă Nette\InvalidArgumentException +// throws Nette\InvalidArgumentException ``` renameKey(array &$array, string|int $oldKey, string|int $newKey): bool .[method] -------------------------------------------------------------------------------- -Redenumește o cheie. Returnează `true` dacă cheia a fost găsită în matrice. +Redenumește o cheie în array. Returnează `true` dacă cheia a fost găsită în array. ```php $array = ['first' => 10, 'second' => 20]; @@ -274,7 +385,7 @@ Arrays::renameKey($array, 'first', 'renamed'); getKeyOffset(array $array, string|int $key): ?int .[method] ----------------------------------------------------------- -Returnează poziția indexată cu zero a cheii date în matrice. În cazul în care cheia nu este găsită, se returnează `null`. +Returnează poziția cheii date în array. Poziția este numerotată de la 0. În cazul în care cheia nu este găsită, funcția returnează `null`. ```php $array = ['first' => 10, 'second' => 20]; @@ -284,10 +395,10 @@ $position = Arrays::getKeyOffset($array, 'not-exists'); // returnează null ``` -some(iterable $array, callable $callback): bool .[method] ---------------------------------------------------------- +some(array $array, callable $predicate): bool .[method] +------------------------------------------------------- -Testează dacă cel puțin un element din matrice trece testul implementat de callback-ul furnizat cu semnătura `function ($value, $key, array $array): bool`. +Testează dacă cel puțin un element din array trece testul implementat în `$predicate` cu semnătura `function ($value, $key, array $array): bool`. ```php $array = [1, 2, 3, 4]; @@ -295,13 +406,13 @@ $isEven = fn($value) => $value % 2 === 0; $res = Arrays::some($array, $isEven); // true ``` -A se vedea [every() |#every()]. +Vezi [#every()]. toKey(mixed $key): string|int .[method] --------------------------------------- -Convertește o valoare într-o cheie de matrice, care este fie un număr întreg, fie un șir de caractere. +Convertește o valoare într-o cheie de array, care este fie un integer, fie un șir. ```php Arrays::toKey('1'); // 1 @@ -312,7 +423,7 @@ Arrays::toKey('01'); // '01' toObject(iterable $array, object $object): object .[method] ----------------------------------------------------------- -Copiază elementele din tabloul `$array` în obiectul `$object` și apoi îl returnează. +Copiază elementele array-ului `$array` în obiectul `$object`, pe care apoi îl returnează. ```php $obj = new stdClass; @@ -321,10 +432,10 @@ Arrays::toObject($array, $obj); // setează $obj->foo = 1; $obj->bar = 2; ``` -wrap(iterable $array, string $prefix='', string $suffix=''): array .[method] ----------------------------------------------------------------------------- +wrap(array $array, string $prefix='', string $suffix=''): array .[method] +------------------------------------------------------------------------- -Transformă fiecare element al tabloului în șir de caractere și îl închide cu `$prefix` și `$suffix`. +Fiecare element din array este convertit la șir și înconjurat cu prefixul `$prefix` și sufixul `$suffix`. ```php $array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); @@ -332,21 +443,21 @@ $array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); ``` -ArrayHash .[#toc-arrayhash] -=========================== +ArrayHash +========= -Obiectul [api:Nette\Utils\ArrayHash] este descendentul clasei generice stdClass și o extinde cu posibilitatea de a o trata ca pe o matrice, de exemplu, accesând membrii folosind paranteze pătrate: +Obiectul [api:Nette\Utils\ArrayHash] este un descendent al clasei generice `stdClass` și o extinde cu capacitatea de a fi tratat ca un array, adică, de exemplu, accesarea membrilor prin paranteze drepte: ```php $hash = new Nette\Utils\ArrayHash; $hash['foo'] = 123; -$hash->bar = 456; // de asemenea, funcționează notația obiect +$hash->bar = 456; // funcționează și scrierea obiectuală $hash->foo; // 123 ``` -Puteți utiliza funcția `count($hash)` pentru a obține numărul de elemente. +Se poate utiliza funcția `count($hash)` pentru a afla numărul de elemente. -Puteți itera peste un obiect la fel ca peste o matrice, chiar și cu o referință: +Peste obiect se poate itera la fel ca în cazul unui array, chiar și cu referință: ```php foreach ($hash as $key => $value) { @@ -358,7 +469,7 @@ foreach ($hash as $key => &$value) { } ``` -Array-urile existente pot fi transformate în `ArrayHash` folosind `from()`: +Un array existent poate fi transformat în `ArrayHash` prin metoda `from()`: ```php $array = ['foo' => 123, 'bar' => 456]; @@ -368,7 +479,7 @@ $hash->foo; // 123 $hash->bar; // 456 ``` -Transformarea este recursivă: +Conversia este recursivă: ```php $array = ['foo' => 123, 'inner' => ['a' => 'b']]; @@ -379,24 +490,24 @@ $hash->inner->a; // 'b' $hash['inner']['a']; // 'b' ``` -Ea poate fi evitată cu ajutorul celui de-al doilea parametru: +Acest lucru poate fi prevenit cu al doilea parametru: ```php $hash = Nette\Utils\ArrayHash::from($array, false); $hash->inner; // array ``` -Transformă înapoi în matrice: +Transformarea înapoi în array: ```php $array = (array) $hash; ``` -ArrayList .[#toc-arraylist] -=========================== +ArrayList +========= -[api:Nette\Utils\ArrayList] reprezintă o matrice liniară în care indicii sunt numai numere întregi care cresc de la 0. +[api:Nette\Utils\ArrayList] reprezintă un array liniar, unde indicii sunt doar numere întregi crescătoare de la 0. ```php $list = new Nette\Utils\ArrayList; @@ -407,9 +518,16 @@ $list[] = 'c'; count($list); // 3 ``` -Puteți utiliza funcția `count($list)` pentru a obține numărul de elemente. +Un array existent poate fi transformat în `ArrayList` prin metoda `from()`: -Puteți itera peste un obiect la fel ca peste o matrice, chiar și cu o referință: +```php +$array = ['foo', 'bar']; +$list = Nette\Utils\ArrayList::from($array); +``` + +Se poate utiliza funcția `count($list)` pentru a afla numărul de elemente. + +Peste obiect se poate itera la fel ca în cazul unui array, chiar și cu referință: ```php foreach ($list as $key => $value) { @@ -421,28 +539,21 @@ foreach ($list as $key => &$value) { } ``` -Rețelele existente pot fi transformate în `ArrayList` utilizând `from()`: - -```php -$array = ['foo', 'bar']; -$list = Nette\Utils\ArrayList::from($array); -``` - -Accesarea cheilor dincolo de valorile permise aruncă o excepție `Nette\OutOfRangeException`: +Accesarea cheilor în afara valorilor permise aruncă excepția `Nette\OutOfRangeException`: ```php -echo $list[-1]; // aruncă Nette\OutOutOfRangeException +echo $list[-1]; // aruncă Nette\OutOfRangeException unset($list[30]); // aruncă Nette\OutOfRangeException ``` -Eliminarea cheii va duce la renumerotarea elementelor: +Eliminarea unei chei cauzează renumerotarea elementelor: ```php unset($list[1]); // ArrayList(0 => 'a', 1 => 'c') ``` -Puteți adăuga un nou element la început folosind `prepend()`: +Un element nou poate fi adăugat la început prin metoda `prepend()`: ```php $list->prepend('d'); diff --git a/utils/ro/callback.texy b/utils/ro/callback.texy index 2bd416d021..af521d221d 100644 --- a/utils/ro/callback.texy +++ b/utils/ro/callback.texy @@ -1,8 +1,8 @@ -Funcții de apelare -****************** +Lucrul cu callback-uri +********************** .[perex] -[api:Nette\Utils\Callback] este o clasă statică, care conține funcții pentru lucrul cu [callback-urile PHP |https://www.php.net/manual/en/language.types.callable.php]. +[api:Nette\Utils\Callback] este o clasă statică cu funcții pentru lucrul cu [Callback-uri PHP |https://www.php.net/manual/en/language.types.callable.php]. Instalare: @@ -11,7 +11,7 @@ Instalare: composer require nette/utils ``` -Toate exemplele presupun că este definit următorul alias de clasă: +Toate exemplele presupun crearea unui alias: ```php use Nette\Utils\Callback; @@ -21,13 +21,13 @@ use Nette\Utils\Callback; check($callable, bool $syntax=false): callable .[method] -------------------------------------------------------- -Verifică dacă `$callable` este un callback PHP valid. În caz contrar, aruncă `Nette\InvalidArgumentException`. Dacă `$syntax` este setat la true, funcția verifică doar dacă `$callable` are o structură validă pentru a fi utilizată ca callback, dar nu verifică dacă clasa sau metoda există efectiv. Returnează `$callable`. +Verifică dacă variabila `$callable` este un callback valid. Altfel, aruncă `Nette\InvalidArgumentException`. Dacă `$syntax` este true, funcția verifică doar dacă `$callable` are structura unui callback, dar nu verifică dacă clasa sau metoda respectivă există efectiv. Returnează `$callable`. ```php -Callback::check('trim'); // fără excepție +Callback::check('trim'); // nu aruncă excepție Callback::check(['NonExistentClass', 'method']); // aruncă Nette\InvalidArgumentException -Callback::check(['NonExistentClass', 'method'], true); // nicio excepție -Callback::check(function () {}); // nicio excepție +Callback::check(['NonExistentClass', 'method'], true); // nu aruncă excepție +Callback::check(function () {}); // nu aruncă excepție Callback::check(null); // aruncă Nette\InvalidArgumentException ``` @@ -35,7 +35,7 @@ Callback::check(null); // aruncă Nette\InvalidArgumentException toString($callable): string .[method] ------------------------------------- -Convertește callback-ul PHP în formă textuală. Clasa sau metoda poate să nu existe. +Convertește un callback PHP într-o formă textuală. Clasa sau metoda nu trebuie să existe. ```php Callback::toString('trim'); // 'trim' @@ -46,7 +46,7 @@ Callback::toString(['MyClass', 'method']); // 'MyClass::method' toReflection($callable): ReflectionMethod|ReflectionFunction .[method] ---------------------------------------------------------------------- -Returnează reflectarea metodei sau a funcției utilizate în callback-ul PHP. +Returnează reflecția pentru metoda sau funcția din callback-ul PHP. ```php $ref = Callback::toReflection('trim'); @@ -60,7 +60,7 @@ $ref = Callback::toReflection(['MyClass', 'method']); isStatic($callable): bool .[method] ----------------------------------- -Verifică dacă callback-ul PHP este o funcție sau o metodă statică. +Verifică dacă un callback PHP este o funcție sau o metodă statică. ```php Callback::isStatic('trim'); // true @@ -73,7 +73,7 @@ Callback::isStatic(function () {}); // false unwrap(Closure $closure): callable|array .[method] -------------------------------------------------- -Desfășoară închiderea creată de `Closure::fromCallable`:https://www.php.net/manual/en/closure.fromcallable.php. +Despachetează invers o Closure creată folosind `Closure::fromCallable`:https://www.php.net/manual/en/closure.fromcallable.php. ```php $closure = Closure::fromCallable(['MyClass', 'method']); diff --git a/utils/ro/datetime.texy b/utils/ro/datetime.texy index 482a4b9c7a..b09fcf837f 100644 --- a/utils/ro/datetime.texy +++ b/utils/ro/datetime.texy @@ -2,7 +2,7 @@ Data și ora *********** .[perex] -[api:Nette\Utils\DateTime] este o clasă care extinde clasa nativă [php:DateTime]. +[api:Nette\Utils\DateTime] este o clasă care extinde clasa nativă [php:DateTime] cu funcții suplimentare. Instalare: @@ -11,7 +11,7 @@ Instalare: composer require nette/utils ``` -Toate exemplele presupun că este definit următorul alias de clasă: +Toate exemplele presupun crearea unui alias: ```php use Nette\Utils\DateTime; @@ -20,35 +20,35 @@ use Nette\Utils\DateTime; static from(string|int|\DateTimeInterface $time): DateTime .[method] -------------------------------------------------------------------- -Creează un obiect DateTime dintr-un șir de caractere, un timestamp UNIX sau un alt obiect [php:DateTimeInterface]. Lansează un mesaj `Exception` dacă data și ora nu sunt valide. +Creează un obiect DateTime dintr-un șir, timestamp UNIX sau alt obiect [php:DateTimeInterface]. Aruncă excepția `Exception` dacă data și ora nu sunt valide. ```php -DateTime::from(1138013640); // creează un DateTime din timestamp-ul UNIX cu un timezamp implicit. -DateTime::from(42); // creează un DateTime din ora curentă plus 42 de secunde -DateTime::from('1994-02-26 04:15:32'); // creează un DateTime pe baza unui șir de caractere -DateTime::from('1994-02-26'); // creează un DateTime în funcție de dată, ora va fi 00:00:00:00 +DateTime::from(1138013640); // creează DateTime dintr-un timestamp UNIX cu timezone implicit +DateTime::from(42); // creează DateTime din timpul curent plus 42 de secunde +DateTime::from('1994-02-26 04:15:32'); // creează DateTime conform șirului +DateTime::from('1994-02-26'); // creează DateTime conform datei, ora va fi 00:00:00 ``` static fromParts(int $year, int $month, int $day, int $hour=0, int $minute=0, float $second=0.0): DateTime .[method] -------------------------------------------------------------------------------------------------------------------- -Creează un obiect DateTime sau aruncă o excepție `Nette\InvalidArgumentException` dacă data și ora nu sunt valide. +Creează un obiect DateTime sau aruncă excepția `Nette\InvalidArgumentException` dacă data și ora nu sunt valide. ```php DateTime::fromParts(1994, 2, 26, 4, 15, 32); ``` -static createFromFormat(string $format, string $time, string|\DateTimeZone $timezone=null): DateTime|false .[method] --------------------------------------------------------------------------------------------------------------------- -Extinde [DateTime::createFromFormat() |https://www.php.net/manual/en/datetime.createfromformat.php] cu posibilitatea de a specifica un fus orar sub forma unui șir de caractere. +static createFromFormat(string $format, string $time, ?string|\DateTimeZone $timezone=null): DateTime|false .[method] +--------------------------------------------------------------------------------------------------------------------- +Extinde [DateTime::createFromFormat()|https://www.php.net/manual/en/datetime.createfromformat.php] cu posibilitatea de a specifica timezone-ul ca șir. ```php -DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); // creați cu un fus orar personalizat +DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); ``` modifyClone(string $modify=''): static .[method] ------------------------------------------------ -Creează o copie cu o oră modificată. +Creează o copie cu ora modificată. ```php $original = DateTime::from('2017-02-03'); $clone = $original->modifyClone('+1 day'); @@ -65,9 +65,9 @@ echo $dateTime; // '2017-02-03 04:15:32' ``` -Implementează JsonSerializable .[#toc-implements-jsonserializable] ------------------------------------------------------------------- -Returnează data și ora în formatul ISO 8601, care este utilizat în JavaScript, de exemplu. +implementează JsonSerializable +------------------------------ +Returnează data și ora în formatul ISO 8601, care este utilizat, de exemplu, în JavaScript. ```php $date = DateTime::from('2017-02-03'); echo json_encode($date); diff --git a/utils/ro/filesystem.texy b/utils/ro/filesystem.texy index 08c0be3261..009611a8f1 100644 --- a/utils/ro/filesystem.texy +++ b/utils/ro/filesystem.texy @@ -1,41 +1,43 @@ -Funcțiile sistemului de fișiere -******************************* +Sistemul de fișiere +******************* .[perex] -[api:Nette\Utils\FileSystem] este o clasă statică, care conține funcții utile pentru lucrul cu un sistem de fișiere. Un avantaj față de funcțiile native PHP este că acestea aruncă excepții în caz de erori. +[api:Nette\Utils\FileSystem] este o clasă cu funcții utile pentru lucrul cu sistemul de fișiere. Un avantaj față de funcțiile native PHP este că aruncă excepții în caz de eroare. +Dacă aveți nevoie să căutați fișiere pe disc, utilizați [Finder|finder]. + Instalare: ```shell composer require nette/utils ``` -Următoarele exemple presupun că este definit următorul alias de clasă: +Următoarele exemple presupun că a fost creat un alias: ```php use Nette\Utils\FileSystem; ``` -Manipulare .[#toc-manipulation] -=============================== +Manipulare +========== copy(string $origin, string $target, bool $overwrite=true): void .[method] -------------------------------------------------------------------------- -Copiază un fișier sau un întreg director. Suprascrie în mod implicit fișierele și directoarele existente. Dacă `$overwrite` este setat la `false` și există deja un `$target`, se aruncă o excepție `Nette\InvalidStateException`. Lansează o excepție `Nette\IOException` în cazul în care apare o eroare. +Copiază un fișier sau un întreg director. În mod implicit, suprascrie fișierele și directoarele existente. Dacă parametrul `$overwrite` este setat la `false`, aruncă o excepție `Nette\InvalidStateException` dacă fișierul sau directorul țintă `$target` există. Aruncă o excepție `Nette\IOException` în caz de eroare. ```php FileSystem::copy('/path/to/source', '/path/to/dest', overwrite: true); ``` -createDir(string $directory, int $mode=0777): void .[method] ------------------------------------------------------------- +createDir(string $dir, int $mode=0777): void .[method] +------------------------------------------------------ -Creează un director dacă acesta nu există, inclusiv directoarele părinte. Lansează o excepție `Nette\IOException` în cazul în care apare o eroare. +Creează un director dacă nu există, inclusiv directoarele părinte. Aruncă o excepție `Nette\IOException` în caz de eroare. ```php FileSystem::createDir('/path/to/dir'); @@ -45,7 +47,7 @@ FileSystem::createDir('/path/to/dir'); delete(string $path): void .[method] ------------------------------------ -Șterge un fișier sau un întreg director, dacă există. În cazul în care directorul nu este gol, șterge mai întâi conținutul acestuia. Aruncă o excepție `Nette\IOException` în cazul în care apare o eroare. +Șterge un fișier sau un întreg director, dacă există. Dacă directorul nu este gol, șterge mai întâi conținutul acestuia. Aruncă o excepție `Nette\IOException` în caz de eroare. ```php FileSystem::delete('/path/to/fileOrDir'); @@ -55,7 +57,7 @@ FileSystem::delete('/path/to/fileOrDir'); makeWritable(string $path, int $dirMode=0777, int $fileMode=0666): void .[method] --------------------------------------------------------------------------------- -Stabilește permisiunile fișierelor la `$fileMode` sau permisiunile directoarelor la `$dirMode`. Traversează și stabilește în mod recursiv permisiunile pentru întregul conținut al directorului. +Setează permisiunile fișierului la `$fileMode` sau ale directorului la `$dirMode`. Parcurge recursiv și setează permisiunile pentru întregul conținut al directorului. ```php FileSystem::makeWritable('/path/to/fileOrDir'); @@ -65,7 +67,7 @@ FileSystem::makeWritable('/path/to/fileOrDir'); open(string $path, string $mode): resource .[method] ---------------------------------------------------- -Deschide fișierul și returnează resursa. Parametrul `$mode` funcționează la fel ca funcția nativă `fopen()`:https://www.php.net/manual/en/function.fopen.php. În cazul în care apare o eroare, se ridică excepția `Nette\IOException`. +Deschide un fișier și returnează resursa. Parametrul `$mode` funcționează la fel ca în funcția nativă [`fopen()` |https://www.php.net/manual/en/function.fopen.php]. Aruncă o excepție `Nette\IOException` în caz de eroare. ```php $res = FileSystem::open('/path/to/file', 'r'); @@ -75,7 +77,7 @@ $res = FileSystem::open('/path/to/file', 'r'); read(string $file): string .[method] ------------------------------------ -Citește conținutul unui fișier `$file`. Aruncă o excepție `Nette\IOException` în cazul în care apare o eroare. +Returnează conținutul fișierului `$file`. Aruncă o excepție `Nette\IOException` în caz de eroare. ```php $content = FileSystem::read('/path/to/file'); @@ -85,8 +87,7 @@ $content = FileSystem::read('/path/to/file'); readLines(string $file, bool $stripNewLines=true): \Generator .[method] ----------------------------------------------------------------------- -Citește conținutul fișierului linie cu linie. Spre deosebire de funcția nativă `file()`, aceasta nu citește întregul fișier în memorie, ci îl citește continuu, astfel încât să poată fi citite fișiere mai mari decât memoria disponibilă. `$stripNewLines` specifică dacă se elimină sau nu caracterele de întrerupere a liniei `\r` și `\n`. -În caz de eroare, se ridică o excepție `Nette\IOException`. +Citește conținutul fișierului linie cu linie. Spre deosebire de funcția nativă `file()`, nu încarcă întregul fișier în memorie, ci îl citește continuu, permițând citirea fișierelor mai mari decât memoria disponibilă. `$stripNewLines` specifică dacă caracterele de sfârșit de linie `\r` și `\n` trebuie eliminate. Aruncă o excepție `Nette\IOException` în caz de eroare. ```php $lines = FileSystem::readLines('/path/to/file'); @@ -100,7 +101,7 @@ foreach ($lines as $lineNum => $line) { rename(string $origin, string $target, bool $overwrite=true): void .[method] ---------------------------------------------------------------------------- -Redenumește sau mută un fișier sau un director specificat de `$origin` în `$target`. Suprascrie în mod implicit fișierele și directoarele existente. Dacă `$overwrite` este setat la `false` și `$target` există deja, se aruncă o excepție `Nette\InvalidStateException`. Lansează o excepție `Nette\IOException` în cazul în care apare o eroare. +Redenumește sau mută fișierul sau directorul `$origin`. În mod implicit, suprascrie fișierele și directoarele existente. Dacă parametrul `$overwrite` este setat la `false`, aruncă o excepție `Nette\InvalidStateException` dacă fișierul sau directorul țintă `$target` există. Aruncă o excepție `Nette\IOException` în caz de eroare. ```php FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); @@ -110,21 +111,21 @@ FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); write(string $file, string $content, int $mode=0666): void .[method] -------------------------------------------------------------------- -Scrie `$content` pe un fișier `$file`. Aruncă o excepție `Nette\IOException` în caz de eroare. +Scrie șirul `$content` în fișierul `$file`. Aruncă o excepție `Nette\IOException` în caz de eroare. ```php FileSystem::write('/path/to/file', $content); ``` -Căi de acces .[#toc-paths] -========================== +Căi +=== isAbsolute(string $path): bool .[method] ---------------------------------------- -Determină dacă `$path` este absolut. +Verifică dacă calea `$path` este absolută. ```php FileSystem::isAbsolute('../backup'); // false @@ -135,7 +136,7 @@ FileSystem::isAbsolute('C:/backup'); // true joinPaths(string ...$segments): string .[method] ------------------------------------------------ -Unește toate segmentele traseului și normalizează rezultatul. +Unește toate segmentele căii și normalizează rezultatul. ```php FileSystem::joinPaths('a', 'b', 'file.txt'); // 'a/b/file.txt' @@ -146,7 +147,7 @@ FileSystem::joinPaths('/a/', '/../b'); // '/b' normalizePath(string $path): string .[method] --------------------------------------------- -Normalizează `..` și `.` și separatorii de directoare din calea de acces. +Normalizează `..` și `.` și separatoarele de directoare din cale la cele specifice sistemului. ```php FileSystem::normalizePath('/file/.'); // '/file/' @@ -159,7 +160,7 @@ FileSystem::normalizePath('file/../../bar'); // '/../bar' unixSlashes(string $path): string .[method] ------------------------------------------- -Convertește slash-urile în `/` utilizate pe sistemele Unix. +Convertește slash-urile la `/`, utilizate în sistemele Unix. ```php $path = FileSystem::unixSlashes($path); @@ -169,8 +170,45 @@ $path = FileSystem::unixSlashes($path); platformSlashes(string $path): string .[method] ----------------------------------------------- -Convertește slash-urile în caractere specifice platformei curente, de exemplu `\` pe Windows și `/` în alte sisteme. +Convertește slash-urile la caracterele specifice platformei curente, adică `\` în Windows și `/` în alte sisteme. ```php $path = FileSystem::platformSlashes($path); ``` + + +resolvePath(string $basePath, string $path): string .[method]{data-version:4.0.6} +--------------------------------------------------------------------------------- + +Derivează calea finală din calea `$path` relativ la directorul de bază `$basePath`. Căile absolute (`/foo`, `C:/foo`) sunt lăsate neschimbate (doar normalizează slash-urile), căile relative sunt anexate la calea de bază. + +```php +// Pe Windows, slash-urile din ieșire ar fi inverse (\) +FileSystem::resolvePath('/base/dir', '/abs/path'); // '/abs/path' +FileSystem::resolvePath('/base/dir', 'rel'); // '/base/dir/rel' +FileSystem::resolvePath('base/dir', '../file.txt'); // 'base/file.txt' +FileSystem::resolvePath('base', ''); // 'base' +``` + + +Acces static vs. non-static +=========================== + +Pentru a putea înlocui ușor clasa cu alta (mock), de exemplu în scopuri de testare, utilizați-o în mod non-static: + +```php +class AnyClassUsingFileSystem +{ + public function __construct( + private FileSystem $fileSystem, + ) { + } + + public function readConfig(): string + { + return $this->fileSystem->read(/* ... */); + } + + ... +} +``` diff --git a/utils/ro/finder.texy b/utils/ro/finder.texy index a2a2fafd0e..4554f1cebb 100644 --- a/utils/ro/finder.texy +++ b/utils/ro/finder.texy @@ -1,8 +1,8 @@ -Finder: Căutare fișiere -*********************** +Finder: căutarea fișierelor +*************************** .[perex] -Aveți nevoie să găsiți fișiere care se potrivesc cu o anumită mască? Finder vă poate ajuta. Este un instrument versatil și rapid de navigare în structura directoarelor. +Aveți nevoie să găsiți fișiere care corespund unei anumite măști? Finder vă va ajuta. Este un instrument versatil și rapid pentru parcurgerea structurii de directoare. Instalare: @@ -18,10 +18,10 @@ use Nette\Utils\Finder; ``` -Utilizarea .[#toc-using] ------------------------- +Utilizare +--------- -În primul rând, să vedem cum puteți utiliza [api:Nette\Utils\Finder] pentru a lista numele fișierelor cu extensiile `.txt` și `.md` din directorul curent: +Mai întâi vom arăta cum puteți folosi [api:Nette\Utils\Finder] pentru a afișa numele fișierelor cu extensiile `.txt` și `.md` în directorul curent: ```php foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { @@ -29,29 +29,28 @@ foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { } ``` -Directorul implicit pentru căutare este directorul curent, dar îl puteți schimba folosind metodele [in() sau from() |#Where to search?]. -Variabila `$file` este o instanță a clasei [FileInfo |#FileInfo] cu o mulțime de metode utile. Cheia `$name` conține calea către fișier sub forma unui șir de caractere. +Directorul implicit pentru căutare este directorul curent, dar îl puteți schimba folosind metodele [in() sau from() |#Unde se caută]. Variabila `$file` este o instanță a clasei [#FileInfo] cu o mulțime de metode utile. Cheia `$name` conține calea către fișier sub formă de șir. -Ce trebuie să căutați? .[#toc-what-to-search-for] -------------------------------------------------- +Ce se caută? +------------ -Pe lângă metoda `findFiles()`, există și `findDirectories()`, care caută numai în directoare, și `find()`, care caută în ambele. Aceste metode sunt statice, deci pot fi apelate fără a crea o instanță. Parametrul mască este opțional, dacă nu îl specificați, se caută totul. +Pe lângă metoda `findFiles()`, există și `findDirectories()`, care caută doar directoare, și `find()`, care le caută pe ambele. Aceste metode sunt statice, deci pot fi apelate fără a crea o instanță. Parametrul cu masca este opțional; dacă nu îl specificați, se caută totul. ```php foreach (Finder::find() as $file) { - echo $file; // acum sunt listate toate fișierele și directoarele + echo $file; // acum se vor afișa toate fișierele și directoarele } ``` -Utilizați metodele `files()` și `directories()` pentru a adăuga ce altceva trebuie căutat. Metodele pot fi apelate în mod repetat, iar ca parametru poate fi furnizată o matrice de măști: +Cu ajutorul metodelor `files()` și `directories()` puteți specifica ce altceva să se caute. Metodele pot fi apelate repetat, iar ca parametru se poate specifica și un array de măști: ```php Finder::findDirectories('vendor') // toate directoarele ->files(['*.php', '*.phpt']); // plus toate fișierele PHP ``` -O alternativă la metodele statice este de a crea o instanță folosind `new Finder` (obiectul nou creat în acest mod nu caută nimic) și de a specifica ce să caute folosind `files()` și `directories()`: +O alternativă la metodele statice este crearea unei instanțe folosind `new Finder` (un obiect nou creat astfel nu caută nimic) și specificarea a ceea ce se caută folosind `files()` și `directories()`: ```php (new Finder) @@ -59,80 +58,82 @@ O alternativă la metodele statice este de a crea o instanță folosind `new Fin ->files('*.php'); // plus toate fișierele PHP ``` -Puteți utiliza [caractere wildcards |#wildcards] `*`, `**`, `?` and `[...]` în mască. Puteți specifica chiar și în directoare, de exemplu `src/*.php` va căuta toate fișierele PHP din directorul `src`. +În mască puteți folosi [metacaracterele |#Metacaractere] `*`, `**`, `?` și `[...]`. Puteți chiar specifica directoare, de exemplu `src/*.php` va căuta toate fișierele PHP din directorul `src`. +Link-urile simbolice (symlinks) sunt, de asemenea, considerate directoare sau fișiere. -Unde să căutați? .[#toc-where-to-search] ----------------------------------------- -Directorul de căutare implicit este directorul curent. Puteți schimba acest lucru utilizând metodele `in()` și `from()`. După cum puteți vedea din numele metodelor, `in()` caută numai în directorul curent, în timp ce `from()` caută și în subdirectoarele acestuia (recursiv). Dacă doriți să căutați recursiv în directorul curent, puteți utiliza `from('.')`. +Unde se caută? +-------------- -Aceste metode pot fi apelate de mai multe ori sau le puteți transmite mai multe căi de acces sub formă de matrice, după care fișierele vor fi căutate în toate directoarele. În cazul în care unul dintre directoare nu există, se trimite un mesaj `Nette\UnexpectedValueException`. +Directorul implicit pentru căutare este directorul curent. Îl puteți schimba folosind metodele `in()` și `from()`. După cum sugerează numele metodelor, `in()` caută doar în directorul specificat, în timp ce `from()` caută și în subdirectoarele sale (recursiv). Dacă doriți să căutați recursiv în directorul curent, puteți folosi `from('.')`. + +Aceste metode pot fi apelate de mai multe ori sau li se pot transmite mai multe căi sub formă de array; fișierele vor fi căutate apoi în toate directoarele specificate. Dacă unul dintre directoare nu există, se va arunca o excepție `Nette\UnexpectedValueException`. ```php Finder::findFiles('*.php') ->in(['src', 'tests']) // caută direct în src/ și tests/ - ->from('vendor'); // caută, de asemenea, în subdirectoarele vendor/ + ->from('vendor'); // caută și în subdirectoarele vendor/ ``` -Căile relative sunt relative la directorul curent. Desigur, pot fi specificate și căi absolute: +Căile relative sunt considerate relative la directorul curent. Desigur, se pot specifica și căi absolute: ```php Finder::findFiles('*.php') ->in('/var/www/html'); ``` - [Wildcards wildcards wildcards |#wildcards] `*`, `**`, `?` can be used in the path. For example, you can use the path `src/*/*.php` pentru a căuta toate fișierele PHP din directoarele de al doilea nivel din directorul `src`. Caracterul `**`, numit globstar, este un atu puternic, deoarece vă permite să căutați și în subdirectoare: utilizați `src/**/tests/*.php` pentru a căuta toate fișierele PHP din directorul `tests` aflate în `src` sau în oricare dintre subdirectoarele acestuia. +În cale se pot folosi [metacaracterele |#Metacaractere] `*`, `**`, `?`. De exemplu, folosind calea `src/*/*.php` puteți căuta toate fișierele PHP din directoarele de nivelul al doilea din directorul `src`. Caracterul `**`, numit globstar, este un atu puternic, deoarece permite căutarea și în subdirectoare: folosind `src/**/tests/*.php` căutați toate fișierele PHP din directorul `tests` aflat în `src` sau în oricare dintre subdirectoarele sale. -Pe de altă parte, caracterele wildcard `[...]` nu sunt acceptate în calea de acces, adică nu au o semnificație specială pentru a evita un comportament nedorit în cazul în care căutați, de exemplu, `in(__DIR__)` și, din întâmplare, în calea de acces apar caracterele `[]`. +Pe de altă parte, metacaracterele `[...]` nu sunt suportate în cale, adică nu au o semnificație specială, pentru a evita comportamente nedorite în cazul în care căutați, de exemplu, `in(__DIR__)` și, întâmplător, în cale apar caracterele `[]`. -La căutarea în profunzime a fișierelor și directoarelor, este returnat mai întâi directorul părinte și apoi fișierele conținute în acesta, ceea ce poate fi inversat cu `childFirst()`. +La căutarea fișierelor și directoarelor în adâncime, se returnează mai întâi directorul părinte și abia apoi fișierele conținute în el, comportament ce poate fi inversat folosind `childFirst()`. -Caractere wildcard .[#toc-wildcards] ------------------------------------- +Metacaractere +------------- -Puteți utiliza mai multe caractere speciale în mască: +În mască puteți folosi mai multe caractere speciale: -- `*` - replaces any number of arbitrary characters (except `/`) -- `**` - înlocuiește orice număr de caractere arbitrare, inclusiv `/` (adică poate fi căutat pe mai multe niveluri) -- `?` - replaces one arbitrary character (except `/`) -- `[a-z]` - înlocuiește un caracter din lista de caractere din parantezele pătrate -- `[!a-z]` - înlocuiește un caracter din afara listei de caractere din parantezele pătrate +- `*` - înlocuiește orice număr de caractere (cu excepția `/`) +- `**` - înlocuiește orice număr de caractere, inclusiv `/` (adică se poate căuta pe mai multe niveluri) +- `?` - înlocuiește un singur caracter (cu excepția `/`) +- `[a-z]` - înlocuiește un singur caracter din lista de caractere din parantezele drepte +- `[!a-z]` - înlocuiește un singur caracter din afara listei de caractere din parantezele drepte Exemple de utilizare: -- `img/?.png` - fișiere cu numele cu o singură literă `0.png`, `1.png`, `x.png`, etc. -- `logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log` - fișiere jurnal în format `YYYY-MM-DD` -- `src/**/tests/*` - fișiere din directorul `src/tests`, `src/foo/tests`, `src/foo/bar/tests` și așa mai departe. -- `docs/**.md` - toate fișierele cu extensia `.md` din toate subdirectoarele din directorul `docs` +- `img/?.png` - fișiere cu nume dintr-o singură literă, cum ar fi `0.png`, `1.png`, `x.png`, etc. +- `logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log` - loguri în formatul `YYYY-MM-DD` +- `src/**/tests/*` - fișiere în directorul `src/tests`, `src/foo/tests`, `src/foo/bar/tests` și așa mai departe. +- `docs/**.md` - toate fișierele cu extensia `.md` din toate subdirectoarele directorului `docs` -Cu excepția .[#toc-excluding] ------------------------------ +Excludere +--------- -Utilizați metoda `exclude()` pentru a exclude fișierele și directoarele din căutări. Specificați o mască la care fișierul nu trebuie să corespundă. Exemplu de căutare a fișierelor `*.txt` cu excepția celor care conțin litera `X` în nume: +Cu ajutorul metodei `exclude()`, puteți exclude fișiere și directoare din căutare. Specificați o mască căreia fișierul nu trebuie să îi corespundă. Exemplu de căutare a fișierelor `*.txt`, cu excepția celor care conțin litera `X` în nume: ```php Finder::findFiles('*.txt') ->exclude('*X*'); ``` -Utilizați `exclude()` pentru a sări peste subdirectoarele parcurse: +Pentru a omite parcurgerea anumitor subdirectoare, folosiți `exclude()`: ```php Finder::findFiles('*.php') ->from($dir) - ->exclude('temp', '.git') + ->exclude('temp', '.git'); ``` -Filtrarea .[#toc-filtering] ---------------------------- +Filtrare +-------- -Finder oferă mai multe metode de filtrare a rezultatelor (adică de reducere a acestora). Puteți să le combinați și să le apelați în mod repetat. +Finder oferă mai multe metode pentru filtrarea rezultatelor (adică reducerea lor). Le puteți combina și apela repetat. -Utilizați `size()` pentru a filtra în funcție de dimensiunea fișierului. În acest fel, găsim fișiere cu dimensiuni între 100 și 200 de octeți: +Cu ajutorul `size()`, filtrăm după dimensiunea fișierului. Astfel, găsim fișiere cu dimensiunea în intervalul 100 - 200 de octeți: ```php Finder::findFiles('*.php') @@ -140,19 +141,19 @@ Finder::findFiles('*.php') ->size('<=', 200); ``` -Metoda `date()` filtrează în funcție de data la care fișierul a fost modificat ultima dată. Valorile pot fi absolute sau relative la data și ora curentă, de exemplu, în acest fel se pot găsi fișiere modificate în ultimele două săptămâni: +Metoda `date()` filtrează după data ultimei modificări a fișierului. Valorile pot fi absolute sau relative la data și ora curentă; de exemplu, astfel găsim fișiere modificate în ultimele două săptămâni: ```php Finder::findFiles('*.php') ->date('>', '-2 weeks') - ->from($dir) + ->from($dir); ``` Ambele funcții înțeleg operatorii `>`, `>=`, `<`, `<=`, `=`, `!=`, `<>`. -Finder vă permite, de asemenea, să filtrați rezultatele folosind funcții personalizate. Funcția primește ca parametru un obiect `Nette\Utils\FileInfo` și trebuie să returneze `true` pentru a include fișierul în rezultate. +Finder permite, de asemenea, filtrarea rezultatelor folosind funcții personalizate. Funcția primește ca parametru obiectul `Nette\Utils\FileInfo` și trebuie să returneze `true` pentru ca fișierul să fie inclus în rezultate. -Exemplu: Căutați fișierele PHP care conțin șirul `Nette` (fără a ține cont de majuscule și minuscule): +Exemplu: căutarea fișierelor PHP care conțin șirul `Nette` (indiferent de majuscule/minuscule): ```php Finder::findFiles('*.php') @@ -160,51 +161,51 @@ Finder::findFiles('*.php') ``` -Filtrarea în profunzime .[#toc-depth-filtering] ------------------------------------------------ +Filtrare în adâncime +-------------------- -Atunci când efectuați o căutare recursivă, puteți seta adâncimea maximă de căutare utilizând metoda `limitDepth()`. Dacă setați `limitDepth(1)`, sunt cercetate numai primele subdirectoare, `limitDepth(0)` dezactivează cercetarea în profunzime, iar o valoare de -1 anulează limita. +La căutarea recursivă, puteți seta adâncimea maximă de parcurgere folosind metoda `limitDepth()`. Dacă setați `limitDepth(1)`, se parcurg doar primele subdirectoare; `limitDepth(0)` dezactivează parcurgerea în adâncime, iar valoarea -1 anulează limita. -Finder vă permite să utilizați propriile funcții pentru a decide în ce director să intrați atunci când navigați. Funcția primește ca parametru un obiect `Nette\Utils\FileInfo` și trebuie să returneze `true` pentru a intra în directorul respectiv: +Finder permite, folosind funcții personalizate, să decideți în ce director să se intre la parcurgere. Funcția primește ca parametru obiectul `Nette\Utils\FileInfo` reprezentând directorul și trebuie să returneze `true` pentru a intra în acel director: ```php Finder::findFiles('*.php') - ->descentFilter($file->getBasename() !== 'temp'); + ->descentFilter(fn($file) => $file->getBasename() !== 'temp'); ``` -Sortare .[#toc-sorting] ------------------------ +Sortare +------- -Finder oferă, de asemenea, mai multe funcții de sortare a rezultatelor. +Finder oferă, de asemenea, mai multe funcții pentru sortarea rezultatelor. -Metoda `sortByName()` sortează rezultatele în funcție de numele fișierului. Sortarea este naturală, adică gestionează corect numerele din nume și returnează, de exemplu, `foo1.txt` înainte de `foo10.txt`. +Metoda `sortByName()` sortează rezultatele după numele fișierelor. Sortarea este naturală, adică gestionează corect numerele din nume și returnează, de exemplu, `foo1.txt` înainte de `foo10.txt`. -Finder vă permite, de asemenea, să sortați utilizând o funcție personalizată. Aceasta primește ca parametri două obiecte `Nette\Utils\FileInfo` și trebuie să returneze rezultatul comparației cu operatorul `<=>`, de exemplu `-1`, `0` nebo `1`. De exemplu, iată cum sortăm fișierele în funcție de dimensiune: +Finder permite, de asemenea, sortarea folosind o funcție personalizată. Aceasta primește ca parametru două obiecte `Nette\Utils\FileInfo` și trebuie să returneze rezultatul comparației folosind operatorul spaceship `<=>` (adică `-1`, `0` sau `1`). De exemplu, astfel sortăm fișierele după dimensiune: ```php $finder->sortBy(fn($a, $b) => $a->getSize() <=> $b->getSize()); ``` -Mai multe căutări diferite .[#toc-multiple-different-searches] --------------------------------------------------------------- +Mai multe căutări diferite +-------------------------- -Dacă aveți nevoie să găsiți mai multe fișiere diferite în locații diferite sau care îndeplinesc criterii diferite, utilizați metoda `append()`. Aceasta returnează un nou obiect `Finder`, astfel încât să puteți apela metodele în lanț: +Dacă aveți nevoie să găsiți mai multe fișiere diferite în locații diferite sau care îndeplinesc alte criterii, utilizați metoda `append()`. Aceasta returnează un nou obiect `Finder`, permițând înlănțuirea apelurilor de metode: ```php -($finder = new Finder) // stochează primul Finder în variabila $finder! - ->files('*.php') // caută fișiere *.php în src/ +($finder = new Finder) // salvăm primul Finder în variabila $finder! + ->files('*.php') // în src/ căutăm fișiere *.php ->from('src') ->append() - ->files('*.md') // în docs/ caută fișiere *.md + ->files('*.md') // în docs/ căutăm fișiere *.md ->from('docs') ->append() - ->files('*.json'); // în folderul curent caută fișiere *.json + ->files('*.json'); // în directorul curent căutăm fișiere *.json ``` -Alternativ, puteți utiliza metoda `append()` pentru a adăuga un anumit fișier (sau o matrice de fișiere). În acest caz, se returnează același obiect `Finder`: +Alternativ, metoda `append()` poate fi folosită pentru a adăuga un fișier specific (sau un array de fișiere). În acest caz, returnează același obiect `Finder`: ```php $finder = Finder::findFiles('*.txt') @@ -212,12 +213,12 @@ $finder = Finder::findFiles('*.txt') ``` -FileInfo .[#toc-fileinfo] -------------------------- +FileInfo +-------- -[Nette\Utils\FileInfo |api:] este o clasă care reprezintă un fișier sau un director în rezultatele căutării. Este o extensie a clasei [SplFileInfo |php:SplFileInfo] care oferă informații precum dimensiunea fișierului, data ultimei modificări, numele, calea de acces etc. +[Nette\Utils\FileInfo |api:] este o clasă care reprezintă un fișier sau un director în rezultatele căutării. Este o extensie a clasei [php:SplFileInfo], care oferă informații precum dimensiunea fișierului, data ultimei modificări, numele, calea etc. -În plus, oferă metode pentru returnarea căilor relative, ceea ce este util atunci când se efectuează o căutare în profunzime: +În plus, oferă metode pentru returnarea căii relative, ceea ce este util la parcurgerea în adâncime: ```php foreach (Finder::findFiles('*.jpg')->from('.') as $file) { @@ -226,7 +227,7 @@ foreach (Finder::findFiles('*.jpg')->from('.') as $file) { } ``` -De asemenea, există metode de citire și scriere a conținutului unui fișier: +De asemenea, aveți la dispoziție metode pentru citirea și scrierea conținutului fișierului: ```php foreach ($finder as $file) { @@ -237,12 +238,12 @@ foreach ($finder as $file) { ``` -Returnarea rezultatelor sub forma unui tablou .[#toc-returning-results-as-an-array] ------------------------------------------------------------------------------------ +Returnarea rezultatelor ca array +-------------------------------- -După cum s-a văzut în exemple, Finder implementează interfața `IteratorAggregate`, astfel încât puteți utiliza `foreach` pentru a răsfoi rezultatele. Este programat astfel încât rezultatele să fie încărcate doar pe măsură ce navigați, astfel încât, dacă aveți un număr mare de fișiere, nu așteaptă să fie citite toate. +După cum s-a văzut în exemple, Finder implementează interfața `IteratorAggregate`, astfel încât puteți folosi `foreach` pentru a parcurge rezultatele. Este implementat astfel încât rezultatele sunt încărcate doar pe parcursul iterării, deci dacă aveți un număr mare de fișiere, nu se așteaptă până când toate sunt citite. -Puteți, de asemenea, să primiți rezultatele ca o matrice de obiecte `Nette\Utils\FileInfo`, utilizând metoda `collect()`. Matricea nu este asociativă, ci numerică. +Rezultatele pot fi, de asemenea, returnate ca un array de obiecte `Nette\Utils\FileInfo`, folosind metoda `collect()`. Array-ul nu este asociativ, ci indexat numeric. ```php $array = $finder->findFiles('*.php')->collect(); diff --git a/utils/ro/floats.texy b/utils/ro/floats.texy index 373fe66b1c..0649c0ae27 100644 --- a/utils/ro/floats.texy +++ b/utils/ro/floats.texy @@ -1,8 +1,8 @@ -Funcții flotoare -**************** +Lucrul cu numere în virgulă mobilă +********************************** .[perex] -[api:Nette\Utils\Floats] este o clasă statică cu funcții utile pentru compararea numerelor float. +[api:Nette\Utils\Floats] este o clasă statică cu funcții utile pentru compararea numerelor zecimale (în virgulă mobilă). Instalare: @@ -11,18 +11,17 @@ Instalare: composer require nette/utils ``` -Toate exemplele presupun că este definit următorul alias de clasă: +Toate exemplele presupun că a fost creat un alias: ```php use Nette\Utils\Floats; ``` -Motivație .[#toc-motivation] -============================ +Motivație +========= -Vă întrebați la ce servește o clasă de comparație float? Puteți utiliza operatorii `<`, `>`, `===`, credeți dumneavoastră. -Acest lucru nu este în întregime adevărat. Ce credeți că va imprima acest cod? +Vă întrebați de ce este necesară o clasă pentru compararea numerelor în virgulă mobilă? Puteți folosi operatorii `<`, `>`, `===` și gata. Nu este chiar așa. Ce credeți că va afișa acest cod? ```php $a = 0.1 + 0.2; @@ -30,27 +29,30 @@ $b = 0.3; echo $a === $b ? 'same' : 'not same'; ``` -Dacă executați codul, unii dintre voi vor fi surprinși că programul a tipărit `not same`. +Dacă rulați codul, unii dintre voi vor fi cu siguranță surprinși că programul a afișat `not same`. -Operațiile matematice cu numere float provoacă erori din cauza conversiei între sistemele zecimal și binar. De exemplu, `0.1 + 0.2` echivalează cu `0.300000000000000044…`. Prin urmare, atunci când comparăm numere flotante, trebuie să tolerăm o mică diferență de la o anumită zecimală. +La operațiile matematice cu numere zecimale, apar erori din cauza conversiei între sistemul zecimal și cel binar. De exemplu, `0.1 + 0.2` rezultă `0.300000000000000044…`. De aceea, la comparare trebuie să tolerăm o mică diferență începând de la o anumită zecimală. -Și asta este ceea ce face clasa `Floats`. Următoarea comparație va funcționa așa cum era de așteptat: +Și exact asta face clasa `Floats`. Următoarea comparație va funcționa conform așteptărilor: ```php -echo Floats::areEqual($a, $b) ? 'same': 'not same'; // același +echo Floats::areEqual($a, $b) ? 'same' : 'not same'; // same ``` -Când se încearcă să se compare `NAN`, se aruncă o excepție `\LogicException`. +Încercarea de a compara `NAN` va arunca o excepție `\LogicException`. +.[tip] +Clasa `Floats` tolerează diferențe mai mici de `1e-10`. Dacă aveți nevoie să lucrați cu o precizie mai mare, utilizați mai degrabă biblioteca BCMath. -Compararea flotorilor .[#toc-float-comparison] -============================================== + +Compararea numerelor în virgulă mobilă +====================================== areEqual(float $a, float $b): bool .[method] -------------------------------------------- -Se returnează `true` dacă `$a` = `$b`. +Returnează `true` dacă `$a` = `$b`. ```php Floats::areEqual(10, 10.0); // true @@ -60,7 +62,7 @@ Floats::areEqual(10, 10.0); // true isLessThan(float $a, float $b): bool .[method] ---------------------------------------------- -Se returnează `true` dacă `$a` < `$b`. +Returnează `true` dacă `$a` < `$b`. ```php Floats::isLessThan(9.5, 10.2); // true @@ -71,7 +73,7 @@ Floats::isLessThan(INF, 10.2); // false isLessThanOrEqualTo(float $a, float $b): bool .[method] ------------------------------------------------------- -Se returnează `true` dacă `$a` <= `$b`. +Returnează `true` dacă `$a` <= `$b`. ```php Floats::isLessThanOrEqualTo(9.5, 10.2); // true @@ -82,7 +84,7 @@ Floats::isLessThanOrEqualTo(10.25, 10.25); // true isGreaterThan(float $a, float $b): bool .[method] ------------------------------------------------- -Se returnează `true` dacă `$a` > `$b`. +Returnează `true` dacă `$a` > `$b`. ```php Floats::isGreaterThan(9.5, -10.2); // true @@ -93,7 +95,7 @@ Floats::isGreaterThan(9.5, 10.2); // false isGreaterThanOrEqualTo(float $a, float $b): bool .[method] ---------------------------------------------------------- -Se returnează `true` dacă `$a` >= `$b`. +Returnează `true` dacă `$a` >= `$b`. ```php Floats::isGreaterThanOrEqualTo(9.5, 10.2); // false @@ -104,25 +106,25 @@ Floats::isGreaterThanOrEqualTo(10.2, 10.2); // true compare(float $a, float $b): int .[method] ------------------------------------------ -Dacă `$a` < `$b`, se returnează `-1`, dacă sunt egale se returnează `0` and if `$a` > `$b` se returnează `1`. +Returnează `-1` dacă `$a` < `$b`, `0` dacă sunt egale și `1` dacă `$a` > `$b`. Poate fi utilizată, de exemplu, cu funcția `usort`. ```php $arr = [1, 5, 2, -3.5]; -usort($arr, [Float::class, 'compare']); -// $arr este [-3.5, 1, 2, 5] +usort($arr, [Floats::class, 'compare']); +// $arr este acum [-3.5, 1, 2, 5] ``` -Funcții ajutătoare .[#toc-helpers-functions] -============================================ +Funcții auxiliare +================= isZero(float $value): bool .[method] ------------------------------------ -Returnează `true` dacă valoarea este zero. +Returnează `true` dacă valoarea este egală cu zero. ```php Floats::isZero(0.0); // true diff --git a/utils/ro/helpers.texy b/utils/ro/helpers.texy index 070101c55d..7c241b6c08 100644 --- a/utils/ro/helpers.texy +++ b/utils/ro/helpers.texy @@ -11,7 +11,7 @@ Instalare: composer require nette/utils ``` -Toate exemplele presupun că este definit următorul alias de clasă: +Toate exemplele presupun că a fost creat un alias: ```php use Nette\Utils\Helpers; @@ -21,7 +21,7 @@ use Nette\Utils\Helpers; capture(callable $cb): string .[method] --------------------------------------- -Execută un callback și returnează ieșirea capturată sub forma unui șir de caractere. +Execută callback-ul și returnează ieșirea capturată ca șir. ```php $res = Helpers::capture(function () use ($template) { @@ -33,7 +33,7 @@ $res = Helpers::capture(function () use ($template) { clamp(int|float $value, int|float $min, int|float $max): int|float .[method] ---------------------------------------------------------------------------- -Returnează valoarea fixată în intervalul inclusiv dintre min și max. +Limitează valoarea la intervalul inclusiv specificat de min și max. ```php Helpers::clamp($level, 0, 255); @@ -43,8 +43,7 @@ Helpers::clamp($level, 0, 255); compare(mixed $left, string $operator, mixed $right): bool .[method] -------------------------------------------------------------------- -Compară două valori în același mod în care o face PHP. Distinge între operatorii `>`, `>=`, `<`, `<=`, `=`, `==`, , `===`, `!=`, `!==`, `<>`. -Funcția este utilă în situațiile în care operatorul este variabil. +Compară două valori în același mod în care o face PHP. Distinge operatorii `>`, `>=`, `<`, `<=`, `=`, `==`, `===`, `!=`, `!==`, `<>`. Funcția este utilă în situații în care operatorul este variabil. ```php Helpers::compare(10, '<', 20); // true @@ -54,7 +53,7 @@ Helpers::compare(10, '<', 20); // true falseToNull(mixed $value): mixed .[method] ------------------------------------------ -Convertește `false` în `null`, nu modifică alte valori. +Convertește `false` la `null`, alte valori nu le modifică. ```php Helpers::falseToNull(false); // null @@ -65,7 +64,7 @@ Helpers::falseToNull(123); // 123 getLastError(): string .[method] -------------------------------- -Returnează ultima eroare PHP apărută sau un șir de caractere gol dacă nu s-a produs nicio eroare. Spre deosebire de `error_get_last()`, nu este afectat de directiva PHP `html_errors` și returnează întotdeauna text, nu HTML. +Returnează ultima eroare PHP sau un șir gol dacă nu a apărut nicio eroare. Spre deosebire de `error_get_last()`, nu este influențată de directiva PHP `html_errors` și returnează întotdeauna text, nu HTML. ```php Helpers::getLastError(); @@ -75,13 +74,13 @@ Helpers::getLastError(); getSuggestion(string[] $possibilities, string $value): ?string .[method] ------------------------------------------------------------------------ -Caută un șir de caractere din `$possibilities` care este cel mai asemănător cu `$value`, dar nu același. Suportă numai codificări pe 8 biți. +Din opțiunile oferite `$possibilities`, caută șirul care este cel mai similar cu `$value`, dar nu identic. Suportă doar codificări pe 8 biți. -Este utilă dacă o anumită opțiune nu este validă și dorim să sugerăm utilizatorului una similară (dar diferită, deci același șir este ignorat). În acest fel, Nette creează mesajele `did you mean ...?`. +Este util în cazul în care o anumită opțiune nu este validă și dorim să îi sugerăm utilizatorului una similară (dar diferită, de aceea se ignoră șirul identic). În acest mod, Nette creează mesajele `did you mean ...?`. ```php $items = ['foo', 'bar', 'baz']; Helpers::getSuggestion($items, 'fo'); // 'foo' Helpers::getSuggestion($items, 'barr'); // 'bar' -Helpers::getSuggestion($items, 'baz'); // 'bar', ne 'baz' +Helpers::getSuggestion($items, 'baz'); // 'bar', nu 'baz' ``` diff --git a/utils/ro/html-elements.texy b/utils/ro/html-elements.texy index fbad55d212..548034ea6b 100644 --- a/utils/ro/html-elements.texy +++ b/utils/ro/html-elements.texy @@ -2,15 +2,15 @@ Elemente HTML ************* .[perex] -Clasa [api:Nette\Utils\Html] este un ajutor pentru generarea de cod HTML care previne vulnerabilitatea Cross Site Scripting (XSS). +Clasa [api:Nette\Utils\Html] este un ajutor pentru generarea codului HTML care previne vulnerabilitatea Cross Site Scripting (XSS). -Funcționează în așa fel încât obiectele sale reprezintă elemente HTML, le setăm parametrii și le lăsăm să se redea: +Funcționează astfel: obiectele sale reprezintă elemente HTML cărora le setăm atributele și apoi le randăm: ```php $el = Html::el('img'); // creează elementul -$el->src = 'image.jpg'; // stabilește atributul src -echo $el; // tipărește '' +$el->src = 'image.jpg'; // setează atributul src +echo $el; // afișează '' ``` Instalare: @@ -19,29 +19,29 @@ Instalare: composer require nette/utils ``` -Toate exemplele presupun că este definit următorul alias de clasă: +Toate exemplele presupun că a fost creat un alias: ```php use Nette\Utils\Html; ``` -Crearea unui element HTML .[#toc-creating-an-html-element] -========================================================== +Crearea unui element HTML +========================= -Elementul este creat folosind metoda `Html::el()`: +Elementul se creează folosind metoda `Html::el()`: ```php $el = Html::el('img'); // creează elementul ``` -În plus față de nume, puteți introduce și alte atribute în sintaxa HTML: +Pe lângă nume, puteți specifica și alte atribute folosind sintaxa HTML: ```php $el = Html::el('input type=text class="red important"'); ``` -Sau treceți-le ca o matrice asociativă la al doilea parametru: +Sau le puteți transmite ca array asociativ în al doilea parametru: ```php $el = Html::el('input', [ @@ -50,22 +50,22 @@ $el = Html::el('input', [ ]); ``` -Pentru a modifica și a returna un nume de element: +Modificarea și obținerea numelui elementului: ```php $el->setName('img'); $el->getName(); // 'img' -$el->isEmpty(); // true, deoarece este un element void +$el->isEmpty(); // true, deoarece este un element gol ``` -Atribute HTML .[#toc-html-attributes] -===================================== +Atribute HTML +============= -Puteți seta și obține atribute HTML individuale în trei moduri, depinde de dumneavoastră care vă place mai mult. Primul este prin intermediul proprietăților: +Atributele HTML individuale pot fi setate și citite în trei moduri; depinde de dvs. care preferați. Primul mod este prin proprietăți: ```php -$el->src = 'image.jpg'; // stabilește atributul src +$el->src = 'image.jpg'; // setează atributul src echo $el->src; // 'image.jpg' @@ -73,16 +73,16 @@ unset($el->src); // elimină atributul // sau $el->src = null; ``` -Al doilea mod este de a apela metode care, spre deosebire de setarea proprietăților, putem să le înlănțuim: +Al doilea mod este prin apelarea metodelor, care, spre deosebire de setarea proprietăților, pot fi înlănțuite: ```php $el = Html::el('img')->src('image.jpg')->alt('photo'); // photo -$el->alt(null); // elimină atributul +$el->alt(null); // eliminarea atributului ``` -Iar al treilea mod este cel mai vorbăreț: +Și al treilea mod este cel mai verbos: ```php $el = Html::el('img') @@ -94,9 +94,9 @@ echo $el->getAttribute('src'); // 'image.jpg' $el->removeAttribute('alt'); ``` -În bloc, atributele pot fi setate cu `addAttributes(array $attrs)` și șterse cu `removeAttributes(array $attrNames)`. +Atributele pot fi setate în bloc folosind `addAttributes(array $attrs)` și eliminate folosind `removeAttributes(array $attrNames)`. -Valoarea unui atribut nu trebuie să fie neapărat un șir de caractere, ci se pot utiliza și valori logice pentru atributele logice: +Valoarea atributului nu trebuie să fie doar un șir; se pot folosi și valori booleene pentru atributele booleene: ```php $checkbox = Html::el('input')->type('checkbox'); @@ -104,7 +104,7 @@ $checkbox->checked = true; // $checkbox->checked = false; // ``` -Un atribut poate fi, de asemenea, o matrice de simboluri, care sunt listate separate prin spații, ceea ce este potrivit pentru clasele CSS, de exemplu: +Atributul poate fi și un array de valori, care se afișează separate prin spații, util, de exemplu, pentru clasele CSS: ```php $el = Html::el('input'); @@ -114,7 +114,7 @@ $el->class[] = 'top'; echo $el; // '' ``` -O alternativă este un tablou asociativ, în care valorile indică dacă cheia trebuie să fie listată sau nu: +O alternativă este un array asociativ, unde valorile indică dacă cheia trebuie afișată: ```php $el = Html::el('input'); @@ -123,7 +123,7 @@ $el->class['top'] = false; echo $el; // '' ``` -Stilurile CSS pot fi scrise sub formă de tablouri asociative: +Stilurile CSS pot fi scrise sub formă de array-uri asociative: ```php $el = Html::el('input'); @@ -132,7 +132,7 @@ $el->style['display'] = 'block'; echo $el; // '' ``` -Am folosit acum proprietățile, dar același lucru se poate face și cu ajutorul metodelor: +Am folosit proprietăți, dar același lucru se poate scrie folosind metode: ```php $el = Html::el('input'); @@ -141,7 +141,7 @@ $el->style('display', 'block'); echo $el; // '' ``` -Sau chiar în cel mai vorbăreț mod: +Sau chiar în modul cel mai verbos: ```php $el = Html::el('input'); @@ -150,7 +150,7 @@ $el->appendAttribute('style', 'display', 'block'); echo $el; // '' ``` -Un ultim lucru: metoda `href()` poate facilita compunerea parametrilor de interogare într-un URL: +Un mic detaliu la final: metoda `href()` poate facilita compunerea parametrilor query în URL: ```php echo Html::el('a')->href('index.php', [ @@ -161,31 +161,31 @@ echo Html::el('a')->href('index.php', [ ``` -Atribute de date .[#toc-data-attributes] ----------------------------------------- +Atribute data-* +--------------- -Atributele de date au un suport special. Deoarece numele lor conțin cratime, accesul prin intermediul proprietăților și metodelor nu este atât de elegant, așa că există o metodă `data()`: +Atributele data-* (data attributes) au un suport special. Deoarece numele lor conțin cratime, accesul prin proprietăți și metode nu este la fel de elegant, de aceea există metoda `data()`: ```php $el = Html::el('input'); -$el->{'data-max-size'} = '500x300'; // nu atât de elegant +$el->{'data-max-size'} = '500x300'; // nu este la fel de elegant $el->data('max-size', '500x300'); // este elegant echo $el; // '' ``` -Dacă valoarea atributului de date este o matrice, aceasta este serializată automat în JSON: +Dacă valoarea unui atribut data-* este un array, acesta se serializează automat în JSON: ```php $el = Html::el('input'); -$el->data('items', [1,2,3]); +$el->data('items', [1, 2, 3]); echo $el; // '' ``` -Conținutul elementului .[#toc-element-content] -============================================== +Conținutul elementului +====================== -Conținutul interior al elementului este stabilit prin metodele `setHtml()` sau `setText()`. Folosiți prima metodă numai dacă știți că transmiteți în mod fiabil un șir HTML securizat în parametru. +Conținutul intern al elementului se setează cu metodele `setHtml()` sau `setText()`. Folosiți `setHtml()` doar dacă sunteți sigur că parametrul conține un șir HTML sigur. ```php echo Html::el('span')->setHtml('hello
                                                                                                                            '); @@ -195,7 +195,7 @@ echo Html::el('span')->setText('10 < 20'); // '10 < 20' ``` -Dimpotrivă, conținutul interior este obținut prin metodele `getHtml()` sau `getText()`. Cea de-a doua elimină etichetele din ieșirea HTML și convertește entitățile HTML în caractere. +Invers, conținutul intern se obține cu metodele `getHtml()` sau `getText()`. `getText()` elimină tag-urile HTML din conținut și convertește entitățile HTML în caractere. ```php echo $el->getHtml(); // '10 < 20' @@ -203,10 +203,10 @@ echo $el->getText(); // '10 < 20' ``` -Noduri copil .[#toc-child-nodes] --------------------------------- +Noduri copil +------------ -Conținutul interior al unui element poate fi, de asemenea, o matrice de copii. Fiecare dintre ei poate fi fie un șir de caractere, fie un alt element `Html`. Aceștia sunt introduși cu ajutorul `addHtml()` sau `addText()`: +Conținutul unui element poate fi, de asemenea, un array de noduri copil (children). Fiecare nod poate fi fie un șir, fie un alt element `Html`. Le adăugăm folosind `addHtml()` sau `addText()`: ```php $el = Html::el('span') @@ -216,16 +216,16 @@ $el = Html::el('span') // hello
                                                                                                                            10 < 20
                                                                                                                            ``` -O altă modalitate de a crea și insera un nou nod `Html`: +Altă modalitate de a crea și insera un nou nod `Html`: ```php -$el = Html::el('ul') - ->create('li', ['class' => 'first']) - ->setText('hello'); -//
                                                                                                                            • hello
                                                                                                                            +$ul = Html::el('ul'); +$ul->create('li', ['class' => 'first']) + ->setText('primul'); +//
                                                                                                                            • primul
                                                                                                                            ``` -Puteți lucra cu nodurile ca și cum ar fi elemente de matrice. Deci, accesați-le pe cele individuale folosind paranteze pătrate, numărați-le cu `count()` și iterați peste ele: +Cu nodurile se poate lucra la fel ca și cu un array. Adică, accesarea individuală a acestora folosind paranteze drepte, numărarea lor folosind `count()` și iterarea peste ele: ```php $el = Html::el('div'); @@ -238,20 +238,20 @@ foreach ($el as $child) { /* ... */ } echo count($el); // 2 ``` -Un nou nod poate fi inserat într-o anumită poziție folosind `insert(?int $index, $child, bool $replace = false)`. Dacă `$replace = false`, se inserează elementul la poziția `$index` și se mută celelalte. În cazul în care `$index = null`, se adaugă un element la sfârșit. +Un nod nou poate fi inserat într-o poziție specifică folosind `insert(?int $index, $child, bool $replace = false)`. Dacă `$replace` este `false`, inserează elementul la poziția `$index` și le deplasează pe celelalte. Dacă `$index` este `null`, adaugă elementul la sfârșit. ```php -// inserează elementul în prima poziție și avansează celelalte. +// inserează elementul la prima poziție și le deplasează pe celelalte $el->insert(0, Html::el('span')); ``` -Toate nodurile sunt returnate prin metoda `getChildren()` și eliminate prin metoda `removeChildren()`. +Toate nodurile copil se obțin cu metoda `getChildren()` și se elimină cu metoda `removeChildren()`. -Crearea unui fragment de document .[#toc-creating-a-document-fragment] ----------------------------------------------------------------------- +Crearea unui fragment de document +--------------------------------- -Dacă doriți să lucrați cu o matrice de noduri și nu vă interesează elementul de înfășurare, puteți crea un așa-numit *fragment de document*, trecând `null` în locul numelui elementului: +Dacă dorim să lucrăm cu un array de noduri fără un element înconjurător, putem crea un *fragment de document* transmițând `null` în loc de numele elementului: ```php $el = Html::el(null) @@ -272,15 +272,15 @@ echo $el; // '10 < 20' ``` -Generarea de ieșire HTML .[#toc-generating-html-output] -======================================================= +Generarea ieșirii HTML +====================== -Cel mai simplu mod de a genera un element HTML este de a utiliza `echo` sau de a transforma un obiect în `(string)`. De asemenea, puteți imprima separat etichetele și atributele de deschidere sau de închidere: +Cel mai simplu mod de a afișa un element HTML este să folosiți `echo` sau să convertiți obiectul la `(string)`. Se pot, de asemenea, afișa separat tag-urile de deschidere sau închidere și atributele: ```php $el = Html::el('div class=header')->setText('hello'); -echo $el; // '
                                                                                                                            ' +echo $el; // '
                                                                                                                            hello
                                                                                                                            ' $s = (string) $el; // '
                                                                                                                            hello
                                                                                                                            ' $s = $el->toHtml(); // '
                                                                                                                            hello
                                                                                                                            ' $s = $el->toText(); // 'hello' @@ -289,7 +289,7 @@ echo $el->endTag(); // '' echo $el->attributes(); // 'class="header"' ``` -O caracteristică importantă este protecția automată împotriva [Cross Site Scripting (XSS |nette:glossary#cross-site-scripting-xss]). Toate valorile atributului sau conținutul inserat folosind `setText()` sau `addText()` sunt scăpate în mod fiabil: +O caracteristică importantă este protecția automată împotriva [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS]. Toate valorile atributelor sau conținutul inserat prin `setText()` sau `addText()` sunt escapate în mod fiabil: ```php echo Html::el('div') @@ -300,17 +300,17 @@ echo Html::el('div') ``` -Conversie HTML ↔ Text .[#toc-conversion-html-text] -================================================== +Conversie HTML ↔ text +===================== -Puteți utiliza metoda statică `htmlToText()` pentru a converti HTML în text: +Pentru a converti HTML în text, puteți utiliza metoda statică `htmlToText()`: ```php echo Html::htmlToText('One & Two'); // 'One & Two' ``` -HtmlStringable .[#toc-htmlstringable] -===================================== +HtmlStringable +============== -Obiectul `Nette\Utils\Html` implementează interfața `Nette\HtmlStringable`, pe care, de exemplu, Latte sau formularele o folosesc pentru a distinge obiectele care au o metodă `__toString()` care returnează codul HTML. Astfel, scăparea dublă nu apare dacă, de exemplu, imprimăm obiectul din șablon folosind `{$el}`. +Obiectul `Nette\Utils\Html` implementează interfața `Nette\HtmlStringable`. Latte și formularele folosesc această interfață pentru a distinge obiectele care au o metodă `__toString()` ce returnează cod HTML. Astfel, nu se produce dublă escapare dacă, de exemplu, afișăm obiectul într-un șablon folosind `{$el}`. diff --git a/utils/ro/images.texy b/utils/ro/images.texy index 996e346bc6..01e2d76ead 100644 --- a/utils/ro/images.texy +++ b/utils/ro/images.texy @@ -1,11 +1,11 @@ -Funcții de imagine -****************** +Lucrul cu imagini +***************** .[perex] -Clasa [api:Nette\Utils\Image] simplifică manipularea imaginilor, cum ar fi redimensionarea, decuparea, ascuțirea, desenarea sau fuzionarea mai multor imagini. +Clasa [api:Nette\Utils\Image] simplifică manipularea imaginilor, cum ar fi redimensionarea, decuparea, ascuțirea, desenarea sau combinarea mai multor imagini. -PHP dispune de un set extins de funcții pentru manipularea imaginilor. Dar API-ul nu este foarte frumos. Nu ar fi un cadru de lucru îngrijit pentru a veni cu un API sexy. +PHP dispune de un set extins de funcții pentru manipularea imaginilor, dar API-ul lor nu este foarte convenabil. Nette Framework vine, așa cum era de așteptat, cu un API elegant. Instalare: @@ -13,26 +13,28 @@ Instalare: composer require nette/utils ``` -Următoarele exemple presupun că este definit următorul alias de clasă: +Toate exemplele presupun că a fost creat un alias: ```php use Nette\Utils\Image; +use Nette\Utils\ImageColor; +use Nette\Utils\ImageType; ``` -Crearea unei imagini .[#toc-creating-an-image] -============================================== +Crearea unei imagini +==================== -Vom crea o nouă imagine în culori reale, de exemplu, cu dimensiunile de 100×200: +Creăm o nouă imagine true color, de exemplu, cu dimensiunile 100×200: ```php $image = Image::fromBlank(100, 200); ``` -Opțional, puteți specifica o culoare de fundal (culoarea implicită este negru): +Opțional, se poate specifica culoarea de fundal (implicit este negru): ```php -$image = Image::fromBlank(100, 200, Image::rgb(125, 0, 0)); +$image = Image::fromBlank(100, 200, ImageColor::rgb(125, 0, 0)); ``` Sau încărcăm imaginea dintr-un fișier: @@ -41,20 +43,9 @@ Sau încărcăm imaginea dintr-un fișier: $image = Image::fromFile('nette.jpg'); ``` -Formatele acceptate sunt JPEG, PNG, GIF, WebP, AVIF și BMP, dar versiunea dvs. de PHP trebuie să le accepte și ea (consultați `phpinfo()`, secțiunea GD). Animațiile nu sunt acceptate. -Trebuie să detectați formatul imaginii la încărcare? Metoda returnează formatul în cel de-al doilea parametru: - -```php -$image = Image::fromFile('nette.jpg', $type); -// $type este Image::JPEG, Image::PNG, Image::GIF, Image::WEBP, Image::AVIF sau Image::BMP -``` - -Doar detecția fără încărcarea imaginii este realizată de `Image::detectTypeFromFile()`. - - -Salvați imaginea .[#toc-save-the-image] -======================================= +Salvarea imaginii +================= Imaginea poate fi salvată într-un fișier: @@ -68,102 +59,122 @@ Putem specifica calitatea compresiei în intervalul 0..100 pentru JPEG (implicit $image->save('resampled.jpg', 80); // JPEG, calitate 80% ``` -În cazul în care formatul nu este evident din extensia fișierului, acesta poate fi specificat prin una dintre constantele `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF`, și `Image::BMP`: +Dacă formatul nu este evident din extensia fișierului, acesta poate fi specificat folosind o [constantă |#Formate]: ```php -$image->save('resampled.tmp', null, Image::JPEG); +$image->save('resampled.tmp', null, ImageType::JPEG); ``` Imaginea poate fi scrisă într-o variabilă în loc de pe disc: ```php -$data = $image->toString(Image::JPEG, 80); // JPEG, calitate 80% +$data = $image->toString(ImageType::JPEG, 80); // JPEG, calitate 80% ``` -sau trimiteți direct la browser cu antetul HTTP corespunzător `Content-Type`: +sau trimisă direct în browser cu antetul HTTP `Content-Type` corespunzător: ```php -// trimite antet Content-Type: image/png -$image->send(Image::PNG); +// trimite antetul Content-Type: image/png +$image->send(ImageType::PNG); ``` -Redimensionarea imaginii .[#toc-image-resize] -============================================= +Formate +======= + +Formatele acceptate sunt JPEG, PNG, GIF, WebP, AVIF și BMP, însă acestea trebuie să fie acceptate și de versiunea dvs. de PHP, lucru pe care îl puteți verifica cu funcția [#isTypeSupported()]. Animațiile nu sunt acceptate. + +Formatul este reprezentat de constantele `ImageType::JPEG`, `ImageType::PNG`, `ImageType::GIF`, `ImageType::WEBP`, `ImageType::AVIF` și `ImageType::BMP`. -O operațiune obișnuită este redimensionarea unei imagini. Dimensiunile curente sunt returnate prin metodele `getWidth()` și `getHeight()`. +```php +$supported = Image::isTypeSupported(ImageType::JPEG); +``` -Metoda `resize()` este utilizată pentru redimensionare. Acesta este un exemplu de modificare proporțională a dimensiunii, astfel încât aceasta să nu depășească 500×300 pixeli (fie lățimea va fi exact 500px, fie înălțimea va fi exact 300px, una dintre dimensiuni fiind calculată pentru a menține raportul de aspect): +Aveți nevoie să detectați formatul imaginii la încărcare? Metoda îl returnează în al doilea parametru: + +```php +$image = Image::fromFile('nette.jpg', $type); +``` + +Detecția formatului fără încărcarea imaginii este realizată de `Image::detectTypeFromFile()`. + + +Redimensionare +============== + +O operație frecventă este modificarea dimensiunilor imaginii. Dimensiunile actuale sunt returnate de metodele `getWidth()` și `getHeight()`. + +Metoda `resize()` este utilizată pentru redimensionare. Exemplu de redimensionare proporțională astfel încât imaginea să nu depășească dimensiunile de 500x300 pixeli (fie lățimea va fi exact 500 px, fie înălțimea va fi exact 300 px, cealaltă dimensiune fiind calculată pentru a menține raportul de aspect): ```php $image->resize(500, 300); ``` -Este posibil să setați doar o singură dimensiune, iar cea de-a doua va fi calculată: +Este posibil să specificați doar o dimensiune, iar cealaltă va fi calculată: ```php -$image->resize(500, null); // lățime 500px, înălțime auto +$image->resize(500, null); // lățime 500px, înălțimea se calculează -$image->resize(null, 300); // lățime auto, înălțime 300px +$image->resize(null, 300); // lățimea se calculează, înălțime 300px ``` -Orice dimensiune poate fi specificată în procente: +Oricare dimensiune poate fi specificată și în procente: ```php -$image->resize('75%', 300); // 75% × 300px +$image->resize('75%', 300); // 75 % × 300px ``` -Comportamentul `resize` poate fi influențat de următoarele indicatori. Toate, cu excepția `Image::Stretch`, păstrează raportul de aspect. +Comportamentul metodei `resize` poate fi influențat de următoarele flag-uri. Toate, cu excepția `Image::Stretch`, mențin raportul de aspect. |--------------------------------------------------------------------------------------- -| Steag | Descriere +| Flag | Descriere |--------------------------------------------------------------------------------------- -| `Image::OrSmaller` (implicit) | dimensiunile rezultate vor fi mai mici sau egale cu cele specificate -| `Image::OrBigger` | umple zona țintă și, eventual, o extinde într-o singură direcție -| `Image::Cover` | umple întreaga zonă și taie ceea ce o depășește -| `Image::ShrinkOnly` | doar se micșorează (nu extinde o imagine mică) -| `Image::Stretch` | nu păstrează raportul de aspect +| `Image::OrSmaller` (implicit) | dimensiunile rezultate vor fi mai mici sau egale cu dimensiunile solicitate +| `Image::OrBigger` | umple (și eventual depășește într-o dimensiune) suprafața țintă +| `Image::Cover` | umple suprafața țintă și decupează ceea ce depășește +| `Image::ShrinkOnly` | doar micșorare (previne mărirea unei imagini mici) +| `Image::Stretch` | nu menține raportul de aspect -Steagurile sunt transmise ca al treilea argument al funcției: +Flag-urile sunt specificate ca al treilea argument al funcției: ```php $image->resize(500, 300, Image::OrBigger); ``` -Steagurile pot fi combinate: +Flag-urile pot fi combinate: ```php $image->resize(500, 300, Image::ShrinkOnly | Image::Stretch); ``` -Imaginile pot fi răsturnate pe verticală sau pe orizontală prin specificarea uneia dintre dimensiuni (sau a ambelor) ca număr negativ: +Imaginile pot fi răsturnate vertical sau orizontal specificând una dintre dimensiuni (sau ambele) ca număr negativ: ```php -$flipped = $image->resize(null, '-100%'); // flip vertical +$flipped = $image->resize(null, '-100%'); // răsturnare verticală -$flipped = $image->resize('-100%', '-100%'); // rotiți cu 180°. +$flipped = $image->resize('-100%', '-100%'); // rotire 180° -$flipped = $image->resize(-125, 500); // redimensionare și întoarcere orizontală +$flipped = $image->resize(-125, 500); // redimensionare & răsturnare orizontală ``` -După reducerea imaginii, o putem îmbunătăți prin sharppening: +După micșorarea imaginii, aspectul acesteia poate fi îmbunătățit printr-o ușoară ascuțire (sharpening): ```php $image->sharpen(); ``` -Recoltare .[#toc-cropping] -========================== +Decupare +======== -Metoda `crop()` este utilizată pentru recoltare: +Metoda `crop()` este utilizată pentru decupare: ```php $image->crop($left, $top, $width, $height); ``` -Ca și în cazul `resize()`, toate valorile pot fi specificate în procente. Procentele pentru `$left` și `$top` sunt calculate din spațiul rămas, similar cu proprietatea CSS `background-position`: +Similar cu `resize()`, toate valorile pot fi specificate în procente. Procentele pentru `$left` și `$top` sunt calculate din spațiul rămas, similar proprietății CSS `background-position`: ```php $image->crop('100%', '50%', '80%', '80%'); @@ -172,106 +183,141 @@ $image->crop('100%', '50%', '80%', '80%'); [* crop.svg *] -Imaginea poate fi, de asemenea, decupată automat, de exemplu, marginile negre decupate: +Imaginea poate fi, de asemenea, decupată automat, de exemplu, pentru a elimina marginile negre: ```php $image->cropAuto(IMG_CROP_BLACK); ``` -Metoda `cropAuto()` este o încapsulare în obiect a funcției `imagecropauto()`, a se vedea [documentația acesteia |https://www.php.net/manual/en/function.imagecropauto] pentru mai multe informații. +Metoda `cropAuto()` este un înlocuitor orientat pe obiecte pentru funcția `imagecropauto()`; puteți găsi mai multe informații în [documentația sa |https://www.php.net/manual/en/function.imagecropauto]. + + +Culori .{data-version:4.0.2} +============================ + +Metoda `ImageColor::rgb()` vă permite să definiți o culoare folosind valorile roșu, verde și albastru (RGB). Opțional, puteți specifica și valoarea transparenței în intervalul 0 (complet transparent) până la 1 (complet opac), la fel ca în CSS. + +```php +$color = ImageColor::rgb(255, 0, 0); // Roșu +$transparentBlue = ImageColor::rgb(0, 0, 255, 0.5); // Albastru semitransparent +``` + +Metoda `ImageColor::hex()` permite definirea unei culori folosind formatul hexazecimal, similar cu CSS. Acceptă formatele `#rgb`, `#rrggbb`, `#rgba` și `#rrggbbaa`: +```php +$color = ImageColor::hex("#F00"); // Roșu +$transparentGreen = ImageColor::hex("#00FF0080"); // Verde semitransparent +``` + +Culorile pot fi utilizate în alte metode, cum ar fi `ellipse()`, `fill()` etc. -Desen și editare .[#toc-drawing-and-editing] -============================================ -Puteți desena, puteți scrie, puteți utiliza toate funcțiile PHP pentru a lucra cu imagini, cum ar fi [imagefilledellipse() |https://www.php.net/manual/en/function.imagefilledellipse.php], dar folosind stilul obiect: +Desenare și modificări +====================== + +Puteți desena, puteți scrie. Toate funcțiile PHP pentru lucrul cu imagini vă stau la dispoziție, vezi [#Prezentare generală a metodelor], dar într-o formă orientată pe obiecte: ```php -$image->filledEllipse($cx, $cy, $width, $height, Image::rgb(255, 0, 0, 63)); +$image->filledEllipse($centerX, $centerY, $width, $height, ImageColor::rgb(255, 0, 0)); ``` -A se vedea [Prezentare generală a metodelor |#Overview of Methods]. +Deoarece funcțiile PHP pentru desenarea dreptunghiurilor sunt nepractice din cauza modului de specificare a coordonatelor, clasa `Image` oferă înlocuitori sub forma funcțiilor [#rectangleWH()] și [#filledRectangleWH()]. -Îmbinarea mai multor imagini .[#toc-merge-multiple-images] -========================================================== +Combinarea mai multor imagini +============================= -Puteți plasa cu ușurință o altă imagine în imagine: +O altă imagine poate fi inserată cu ușurință în imaginea curentă: ```php $logo = Image::fromFile('logo.png'); -$blank = Image::fromBlank(320, 240, Image::rgb(52, 132, 210)); +$blank = Image::fromBlank(320, 240, ImageColor::rgb(52, 132, 210)); -// coordonatele pot fi stabilite și în procente -$blank->place($logo, '80%', '80%'); // lângă colțul din dreapta jos +// coordonatele pot fi specificate din nou în procente +$blank->place($logo, '80%', '80%'); // inserăm lângă colțul din dreapta jos ``` -La lipire, canalul alfa este respectat, în plus, putem influența transparența imaginii inserate (vom crea un așa-numit filigran): +La inserare, canalul alfa este respectat; în plus, putem influența opacitatea imaginii inserate (creăm un așa-numit watermark): ```php -$blank->place($image, '80%', '80%', 25); // transparența este de 25 %. +$blank->place($image, '80%', '80%', 25); // opacitatea este de 25 % ``` -O astfel de API este într-adevăr o plăcere de utilizat, nu-i așa? +Un astfel de API este cu adevărat o plăcere de utilizat! -Prezentare generală a metodelor .[#toc-overview-of-methods] -=========================================================== +Prezentare generală a metodelor +=============================== -static fromBlank(int $width, int $height, array $color=null): Image .[method] ------------------------------------------------------------------------------ -Creează o nouă imagine în culori reale cu dimensiunile date. Culoarea implicită este negru. +static fromBlank(int $width, int $height, ?ImageColor $color=null): Image .[method] +----------------------------------------------------------------------------------- +Creează o nouă imagine true color cu dimensiunile specificate. Culoarea implicită este negru. static fromFile(string $file, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------- -Citește o imagine dintr-un fișier și returnează tipul acesteia în `$detectedFormat`. Tipurile acceptate sunt `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` și `Image::BMP`. +Încarcă o imagine dintr-un fișier și returnează [tipul |#Formate] acesteia în `$detectedFormat`. static fromString(string $s, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------ -Citește o imagine dintr-un șir de caractere și returnează tipul acesteia în `$detectedFormat`. Tipurile acceptate sunt `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF`, și `Image::BMP`. +Încarcă o imagine dintr-un șir și returnează [tipul |#Formate] acesteia în `$detectedFormat`. -static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method] ---------------------------------------------------------------------------------- -Creează o culoare care poate fi utilizată în alte metode, cum ar fi `ellipse()`, `fill()`, etc. +static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method][deprecated] +--------------------------------------------------------------------------------------------- +Această funcție a fost înlocuită de clasa `ImageColor`, vezi [#Culori]. static typeToExtension(int $type): string .[method] --------------------------------------------------- -Returnează extensia de fișier pentru constanta `Image::XXX` dată. +Returnează extensia fișierului pentru [tipul |#Formate] specificat. static typeToMimeType(int $type): string .[method] -------------------------------------------------- -Returnează tipul mime pentru constanta `Image::XXX` dată. +Returnează tipul MIME pentru [tipul |#Formate] specificat. static extensionToType(string $extension): int .[method] -------------------------------------------------------- -Returnează tipul de imagine ca o constantă `Image::XXX` în funcție de extensia fișierului. +Returnează [tipul |#Formate] imaginii în funcție de extensia fișierului. static detectTypeFromFile(string $file, int &$width=null, int &$height=null): ?int .[method] -------------------------------------------------------------------------------------------- -Returnează tipul de fișier de imagine ca și constantă `Image::XXX`, iar în parametrii `$width` și `$height` și dimensiunile sale. +Returnează [tipul |#Formate] imaginii și, în parametrii `$width` și `$height`, și dimensiunile acesteia. static detectTypeFromString(string $s, int &$width=null, int &$height=null): ?int .[method] ------------------------------------------------------------------------------------------- -Returnează tipul de imagine din șirul de caractere ca și constanta `Image::XXX`, iar în parametrii `$width` și `$height` și dimensiunile acesteia. +Returnează [tipul |#Formate] imaginii dintr-un șir și, în parametrii `$width` și `$height`, și dimensiunile acesteia. -affine(array $affine, array $clip=null): Image .[method] --------------------------------------------------------- -Returnează o imagine care conține imaginea sursă transformată afine, folosind o zonă de tăiere opțională. ([mai mult |https://www.php.net/manual/en/function.imageaffine]). +static isTypeSupported(int $type): bool .[method] +------------------------------------------------- +Verifică dacă [tipul |#Formate] de imagine specificat este acceptat. + + +static getSupportedTypes(): array .[method]{data-version:4.0.4} +--------------------------------------------------------------- +Returnează un array de [tipuri |#Formate] de imagine acceptate. + + +static calculateTextBox(string $text, string $fontFile, float $size, float $angle=0, array $options=[]): array .[method] +------------------------------------------------------------------------------------------------------------------------ +Calculează dimensiunile dreptunghiului care înconjoară textul într-un anumit font și dimensiune. Returnează un array asociativ care conține cheile `left`, `top`, `width`, `height`. Marginea stângă poate fi negativă dacă textul începe cu un kern stâng. + + +affine(array $affine, ?array $clip=null): Image .[method] +--------------------------------------------------------- +Returnează o imagine care conține imaginea sursă transformată afin, folosind o zonă de decupare opțională. ([mai mult |https://www.php.net/manual/en/function.imageaffine]). affineMatrixConcat(array $m1, array $m2): array .[method] --------------------------------------------------------- -Returnează concatenarea a două matrici de transformare afină, ceea ce este util în cazul în care mai multe transformări trebuie aplicate aceleiași imagini dintr-o singură dată. ([mai mult |https://www.php.net/manual/en/function.imageaffinematrixconcat]) +Returnează concatenarea a două matrici de transformare afină, ceea ce este util dacă mai multe transformări ar trebui aplicate simultan aceleiași imagini. ([mai mult |https://www.php.net/manual/en/function.imageaffinematrixconcat]) affineMatrixGet(int $type, mixed $options=null): array .[method] @@ -281,400 +327,390 @@ Returnează o matrice de transformare afină. ([mai mult |https://www.php.net/ma alphaBlending(bool $on): void .[method] --------------------------------------- -Permite două moduri diferite de a desena pe imaginile truecolor. În modul de amestecare, componenta canalului alfa a culorii furnizată tuturor funcțiilor de desen, cum ar fi `setPixel()`, determină cât de mult din culoarea de bază trebuie să fie lăsată să strălucească. Ca urmare, amestecă automat culoarea existentă în acel punct cu culoarea desenului și stochează rezultatul în imagine. Pixelul rezultat este opac. În modul de neamestec, culoarea desenului este copiată literal cu informațiile canalului său alfa, înlocuind pixelul de destinație. Modul de amestecare nu este disponibil atunci când se desenează pe imagini de paletă. ([mai mult |https://www.php.net/manual/en/function.imagealphablending]) +Permite două moduri diferite de desenare în imagini truecolor. În modul de amestecare, componenta canalului alfa a culorii utilizate în toate funcțiile de desenare, cum ar fi `setPixel()`, determină în ce măsură ar trebui să fie permisă transparența culorii de bază. Ca rezultat, culoarea existentă în acel punct este amestecată automat cu culoarea desenată, iar rezultatul este salvat în imagine. Pixelul rezultat este opac. În modul fără amestecare, culoarea desenată este copiată literal cu informațiile canalului alfa și înlocuiește pixelul țintă. Modul de amestecare nu este disponibil la desenarea pe imagini cu paletă. ([mai mult |https://www.php.net/manual/en/function.imagealphablending]) antialias(bool $on): void .[method] ----------------------------------- -Activați metodele de desenare rapidă antialiasată pentru linii și poligoane cablate. Nu acceptă componente alfa. Funcționează cu ajutorul unei operațiuni de amestecare directă. Funcționează numai cu imagini în culori reale. +Activează desenarea cu antialiasing pentru linii și poligoane. Nu suportă canale alfa. Funcționează numai cu imagini truecolor. -Utilizarea primitivelor antialiasate cu o culoare de fundal transparentă poate avea rezultate neașteptate. Metoda de amestecare utilizează culoarea de fundal ca orice altă culoare. Lipsa suportului pentru componenta alfa nu permite o metodă de antialiasing bazată pe alfa. ([mai mult |https://www.php.net/manual/en/function.imageantialias]) +Utilizarea primitivelor cu antialiasing împreună cu o culoare de fundal transparentă poate duce la rezultate neașteptate. Metoda de amestecare tratează culoarea de fundal ca pe orice altă culoare. ([mai mult |https://www.php.net/manual/en/function.imageantialias]) -arc(int $x, int $y, int $w, int $h, int $start, int $end, int $color): void .[method] -------------------------------------------------------------------------------------- -Desenează un arc de cerc centrat pe coordonatele date. ([mai mult |https://www.php.net/manual/en/function.imagearc]) - - -char(int $font, int $x, int $y, string $char, int $color): void .[method] -------------------------------------------------------------------------- -Desenează primul caracter din `$char` în imagine cu partea superioară stângă la `$x`,`$y` (partea superioară stângă este 0, 0) cu culoarea `$color`. ([mai mult |https://www.php.net/manual/en/function.imagechar]) - - -charUp(int $font, int $x, int $y, string $char, int $color): void .[method] ---------------------------------------------------------------------------- -Desenează caracterul `$char` pe verticală la coordonatele specificate pe imaginea dată. ([mai mult |https://www.php.net/manual/en/function.imagecharup]) +arc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color): void .[method] +--------------------------------------------------------------------------------------------------------------------------- +Desenează un arc de cerc centrat la coordonatele date. ([mai mult |https://www.php.net/manual/en/function.imagearc]) colorAllocate(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------- -Returnează un identificator de culoare care reprezintă culoarea compusă din componentele RGB date. Acesta trebuie apelat pentru a crea fiecare culoare care urmează să fie utilizată în imagine. ([mai mult |https://www.php.net/manual/en/function.imagecolorallocate]) +Returnează un identificator de culoare reprezentând culoarea compusă din componentele RGB date. Trebuie apelată pentru a crea fiecare culoare care urmează să fie utilizată în imagine. ([mai mult |https://www.php.net/manual/en/function.imagecolorallocate]) colorAllocateAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ------------------------------------------------------------------------------ -Se comportă în mod identic cu `colorAllocate()`, cu adăugarea parametrului de transparență `$alpha`. ([mai mult |https://www.php.net/manual/en/function.imagecolorallocatealpha]) +Se comportă la fel ca `colorAllocate()`, adăugând parametrul de transparență `$alpha`. ([mai mult |https://www.php.net/manual/en/function.imagecolorallocatealpha]) colorAt(int $x, int $y): int .[method] -------------------------------------- -Returnează indicele culorii pixelului din locația specificată în imagine. În cazul în care imaginea este o imagine în culori reale, această funcție returnează valoarea RGB a pixelului respectiv ca număr întreg. Utilizați deplasarea de biți și mascarea pentru a accesa valorile distincte ale componentelor roșu, verde și albastru: ([mai mult |https://www.php.net/manual/en/function.imagecolorat]) +Returnează indexul culorii pixelului de la locația specificată în imagine. Dacă imaginea este truecolor, această funcție returnează valoarea RGB a acelui pixel ca număr întreg. Utilizați operații de deplasare și mascare a biților pentru a accesa valorile individuale ale componentelor roșu, verde și albastru. ([mai mult |https://www.php.net/manual/en/function.imagecolorat]) colorClosest(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------ -Returnează indicele culorii din paleta imaginii care este "cea mai apropiată" de valoarea RGB specificată. "Distanța" dintre culoarea dorită și fiecare culoare din paletă este calculată ca și cum valorile RGB ar reprezenta puncte în spațiul tridimensional. ([mai mult |https://www.php.net/manual/en/function.imagecolorclosest]) +Returnează indexul culorii din paleta imaginii care este „cea mai apropiată” de valoarea RGB specificată. "Distanța" dintre culoarea dorită și fiecare culoare din paletă este calculată ca și cum valorile RGB ar reprezenta puncte într-un spațiu tridimensional. ([mai mult |https://www.php.net/manual/en/function.imagecolorclosest]) colorClosestAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ----------------------------------------------------------------------------- -Returnează indicele culorii din paleta imaginii care este "cel mai apropiat" de valoarea RGB și de nivelul `$alpha` specificate. ([mai mult |https://www.php.net/manual/en/function.imagecolorclosestalpha]) +Returnează indexul culorii din paleta imaginii care este „cea mai apropiată” de valoarea RGB specificată și nivelul `$alpha`. ([mai mult |https://www.php.net/manual/en/function.imagecolorclosestalpha]) colorClosestHWB(int $red, int $green, int $blue): int .[method] --------------------------------------------------------------- -Obține indexul culorii care are nuanța, albul și negrul cele mai apropiate de culoarea dată. ([mai mult |https://www.php.net/manual/en/function.imagecolorclosesthwb]) +Obține indexul culorii care are nuanța, albul și negrul cel mai apropiate de culoarea dată. ([mai mult |https://www.php.net/manual/en/function.imagecolorclosesthwb]) colorDeallocate(int $color): void .[method] ------------------------------------------- -De-alocă o culoare alocată anterior cu `colorAllocate()` sau `colorAllocateAlpha()`. ([mai mult |https://www.php.net/manual/en/function.imagecolordeallocate]) +Dealocă o culoare alocată anterior folosind `colorAllocate()` sau `colorAllocateAlpha()`. ([mai mult |https://www.php.net/manual/en/function.imagecolordeallocate]) colorExact(int $red, int $green, int $blue): int .[method] ---------------------------------------------------------- -Returnează indicele culorii specificate în paleta imaginii. ([mai mult |https://www.php.net/manual/en/function.imagecolorexact]) +Returnează indexul culorii specificate în paleta imaginii. ([mai mult |https://www.php.net/manual/en/function.imagecolorexact]) colorExactAlpha(int $red, int $green, int $blue, int $alpha): int .[method] --------------------------------------------------------------------------- -Returnează indicele culorii+alfa specificate în paleta de culori a imaginii. ([mai mult |https://www.php.net/manual/en/function.imagecolorexactalpha]) +Returnează indexul culorii specificate + alfa în paleta imaginii. ([mai mult |https://www.php.net/manual/en/function.imagecolorexactalpha]) colorMatch(Image $image2): void .[method] ----------------------------------------- -Face ca culorile din versiunea de paletă a unei imagini să se apropie mai mult de versiunea în culori reale. ([mai mult |https://www.php.net/manual/en/function.imagecolormatch]) +Potrivește culorile paletei imaginii curente cu cele ale imaginii `$image2`. ([mai mult |https://www.php.net/manual/en/function.imagecolormatch]) colorResolve(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------ -Returnează un indice de culoare pentru o culoare solicitată, fie culoarea exactă, fie cea mai apropiată alternativă posibilă. ([mai mult |https://www.php.net/manual/en/function.imagecolorresolve]) +Returnează indexul culorii pentru culoarea solicitată, fie culoarea exactă, fie cea mai apropiată alternativă posibilă. ([mai mult |https://www.php.net/manual/en/function.imagecolorresolve]) colorResolveAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ----------------------------------------------------------------------------- -Returnează un indice de culoare pentru o culoare solicitată, fie culoarea exactă, fie cea mai apropiată alternativă posibilă. ([mai mult |https://www.php.net/manual/en/function.imagecolorresolvealpha]) +Returnează indexul culorii pentru culoarea solicitată, fie culoarea exactă, fie cea mai apropiată alternativă posibilă. ([mai mult |https://www.php.net/manual/en/function.imagecolorresolvealpha]) colorSet(int $index, int $red, int $green, int $blue): void .[method] --------------------------------------------------------------------- -Aceasta setează indexul specificat în paletă la culoarea specificată. ([mai mult |https://www.php.net/manual/en/function.imagecolorset]) +Setează culoarea specificată la indexul dat în paletă. ([mai mult |https://www.php.net/manual/en/function.imagecolorset]) colorsForIndex(int $index): array .[method] ------------------------------------------- -Obține culoarea pentru un indice specificat. ([mai mult |https://www.php.net/manual/en/function.imagecolorsforindex]) +Obține culoarea pentru indexul specificat. ([mai mult |https://www.php.net/manual/en/function.imagecolorsforindex]) colorsTotal(): int .[method] ---------------------------- -Returnează numărul de culori dintr-o paletă de imagini ([mai multe |https://www.php.net/manual/en/function.imagecolorstotal]). +Returnează numărul total de culori din paleta imaginii. ([mai mult |https://www.php.net/manual/en/function.imagecolorstotal]) -colorTransparent(int $color=null): int .[method] ------------------------------------------------- -Obține sau stabilește culoarea transparentă a imaginii. ([mai mult |https://www.php.net/manual/en/function.imagecolortransparent]) +colorTransparent(?int $color=null): int .[method] +------------------------------------------------- +Obține sau setează culoarea transparentă în imagine. ([mai mult |https://www.php.net/manual/en/function.imagecolortransparent]) convolution(array $matrix, float $div, float $offset): void .[method] --------------------------------------------------------------------- -Aplică o matrice de convoluție pe imagine, folosind coeficientul și decalajul date. ([mai mult |https://www.php.net/manual/en/function.imageconvolution]) +Aplică o matrice de convoluție imaginii, folosind coeficientul și offset-ul dat. ([mai mult |https://www.php.net/manual/en/function.imageconvolution]) .[note] -Necesită *Extensie GD la pachet*, deci nu este sigur că va funcționa peste tot. +Necesită prezența extensiei *Bundled GD*, deci s-ar putea să nu funcționeze peste tot. copy(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH): void .[method] -------------------------------------------------------------------------------------------------- -Copiază o parte din `$src` pe imagine începând de la coordonatele `$srcX`, `$srcY` cu o lățime de `$srcW` și o înălțime de `$srcH`. Porțiunea definită va fi copiată pe coordonatele, `$dstX` și `$dstY`. ([mai mult |https://www.php.net/manual/en/function.imagecopy]) +Copiază o parte din `$src` în imagine începând de la coordonatele `$srcX`, `$srcY` cu lățimea `$srcW` și înălțimea `$srcH`. Partea definită va fi copiată la coordonatele `$dstX` și `$dstY`. ([mai mult |https://www.php.net/manual/en/function.imagecopy]) copyMerge(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] --------------------------------------------------------------------------------------------------------------------- -Copiază o parte din `$src` pe imagine începând de la coordonatele `$srcX`, `$srcY` cu o lățime de `$srcW` și o înălțime de `$srcH`. Porțiunea definită va fi copiată pe coordonatele, `$dstX` și `$dstY`. ([mai mult |https://www.php.net/manual/en/function.imagecopymerge]) +Copiază o porțiune din imaginea `$src` în imaginea curentă, similar cu `copy()`, dar îmbină pixelii sursă și destinație în funcție de `$opacity`. ([mai mult |https://www.php.net/manual/en/function.imagecopymerge]) copyMergeGray(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] ------------------------------------------------------------------------------------------------------------------------- -Copiază o parte din `$src` pe imagine începând de la coordonatele `$srcX`, `$srcY` cu o lățime de `$srcW` și o înălțime de `$srcH`. Porțiunea definită va fi copiată pe coordonatele, `$dstX` și `$dstY`. +Copiază o porțiune din imaginea `$src` în imaginea curentă. Această funcție este identică cu `copyMerge()`, cu excepția faptului că, la îmbinare, păstrează nuanța sursei prin conversia pixelilor țintă la scară de gri înainte de operația de copiere. -Această funcție este identică cu `copyMerge()`, cu excepția faptului că, la fuziune, păstrează nuanța sursei prin convertirea pixelilor de destinație în scala de gri înainte de operațiunea de copiere. ([mai mult |https://www.php.net/manual/en/function.imagecopymergegray]) +([mai mult |https://www.php.net/manual/en/function.imagecopymergegray]) copyResampled(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] --------------------------------------------------------------------------------------------------------------------------------- -Copiază o porțiune dreptunghiulară dintr-o imagine în altă imagine, interpolând lin valorile pixelilor, astfel încât, în special, reducerea dimensiunii unei imagini să păstreze în continuare o mare claritate. +Copiază o porțiune dreptunghiulară dintr-o imagine în alta, interpolând lin valorile pixelilor. Acest lucru asigură o claritate mai bună, în special la micșorarea imaginii. -Cu alte cuvinte, `copyResampled()` va lua o zonă dreptunghiulară din `$src` cu lățimea `$srcW` și înălțimea `$srcH` în poziția (`$srcX`,`$srcY`) și o va plasa într-o zonă dreptunghiulară a imaginii cu lățimea `$dstW` și înălțimea `$dstH` în poziția (`$dstX`,`$dstY`). +Pe scurt, `copyResampled()` ia o zonă dreptunghiulară din `$src` (lățime `$srcW`, înălțime `$srcH`, poziție (`$srcX`, `$srcY`)) și o plasează într-o zonă dreptunghiulară a imaginii curente (lățime `$dstW`, înălțime `$dstH`, poziție (`$dstX`, `$dstY`)). -În cazul în care coordonatele, lățimea și înălțimile sursei și ale destinației diferă, se va efectua o întindere sau o micșorare corespunzătoare a fragmentului de imagine. Coordonatele se referă la colțul din stânga sus. Această funcție poate fi utilizată pentru a copia regiuni din cadrul aceleiași imagini, dar dacă regiunile se suprapun, rezultatele vor fi imprevizibile. ([mai mult |https://www.php.net/manual/en/function.imagecopyresampled]) +Dacă dimensiunile și coordonatele sursă și țintă diferă, se efectuează extinderea sau micșorarea corespunzătoare a fragmentului de imagine. Coordonatele se referă la colțul din stânga sus. Funcția poate fi utilizată pentru a copia zone în aceeași imagine, dar rezultatele pot fi imprevizibile dacă zonele se suprapun. ([mai mult |https://www.php.net/manual/en/function.imagecopyresampled]) copyResized(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] ------------------------------------------------------------------------------------------------------------------------------- -Copiază o porțiune dreptunghiulară dintr-o imagine în altă imagine. Cu alte cuvinte, `copyResized()` va lua o zonă dreptunghiulară din `$src` cu lățimea `$srcW` și înălțimea `$srcH` în poziția (`$srcX`,`$srcY`) și o va plasa într-o zonă dreptunghiulară a imaginii cu lățimea `$dstW` și înălțimea `$dstH` în poziția (`$dstX`,`$dstY`). +Pe scurt, `copyResized()` ia o zonă dreptunghiulară din `$src` (lățime `$srcW`, înălțime `$srcH`, poziție (`$srcX`, `$srcY`)) și o plasează într-o zonă dreptunghiulară a imaginii curente (lățime `$dstW`, înălțime `$dstH`, poziție (`$dstX`, `$dstY`)). -În cazul în care coordonatele, lățimea și înălțimile sursei și ale destinației diferă, se va efectua o întindere sau o micșorare corespunzătoare a fragmentului de imagine. Coordonatele se referă la colțul din stânga sus. Această funcție poate fi utilizată pentru a copia regiuni din cadrul aceleiași imagini, dar dacă regiunile se suprapun, rezultatele vor fi imprevizibile. ([mai mult |https://www.php.net/manual/en/function.imagecopyresized]) +Dacă dimensiunile și coordonatele sursă și țintă diferă, se efectuează extinderea sau micșorarea corespunzătoare a fragmentului de imagine. Coordonatele se referă la colțul din stânga sus. Funcția poate fi utilizată pentru a copia zone în aceeași imagine, dar rezultatele pot fi imprevizibile dacă zonele se suprapun. ([mai mult |https://www.php.net/manual/en/function.imagecopyresized]) crop(int|string $left, int|string $top, int|string $width, int|string $height): Image .[method] ----------------------------------------------------------------------------------------------- -Recuperează o imagine în zona dreptunghiulară dată. Dimensiunile pot fi transmise ca numere întregi în pixeli sau ca șiruri de caractere în procente (de exemplu, `'50%'`). +Decupează imaginea la zona dreptunghiulară specificată. Dimensiunile pot fi specificate ca numere întregi în pixeli sau șiruri de caractere reprezentând procente (de exemplu, `'50%'`). -cropAuto(int $mode=-1, float $threshold=.5, int $color=-1): Image .[method] ---------------------------------------------------------------------------- -Recuperează automat o imagine în funcție de datele furnizate `$mode`. ([mai mult |https://www.php.net/manual/en/function.imagecropauto]) +cropAuto(int $mode=-1, float $threshold=.5, ?ImageColor $color=null): Image .[method] +------------------------------------------------------------------------------------- +Decupează automat imaginea în funcție de `$mode` specificat. ([mai mult |https://www.php.net/manual/en/function.imagecropauto]) -ellipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------- +ellipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------- Desenează o elipsă centrată la coordonatele specificate. ([mai mult |https://www.php.net/manual/en/function.imageellipse]) -fill(int $x, int $y, int $color): void .[method] ------------------------------------------------- -Efectuează o umplere de inundație începând de la coordonata dată (stânga sus este 0, 0) cu `$color` în imagine. ([mai mult |https://www.php.net/manual/en/function.imagefill]) +fill(int $x, int $y, ImageColor $color): void .[method] +------------------------------------------------------- +Umple o regiune începând de la coordonata specificată (`$x`, `$y`; stânga sus este 0, 0) cu culoarea `$color`. ([mai mult |https://www.php.net/manual/en/function.imagefill]) -filledArc(int $cx, int $cy, int $w, int $h, int $s, int $e, int $color, int $style): void .[method] ---------------------------------------------------------------------------------------------------- -Desenează un arc parțial centrat pe coordonata specificată în imagine. ([mai mult |https://www.php.net/manual/en/function.imagefilledarc]) +filledArc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color, int $style): void .[method] +--------------------------------------------------------------------------------------------------------------------------------------------- +Desenează un arc parțial umplut, centrat la coordonatele specificate. ([mai mult |https://www.php.net/manual/en/function.imagefilledarc]) -filledEllipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------------- -Desenează o elipsă centrată la coordonatele specificate în imagine. ([mai mult |https://www.php.net/manual/en/function.imagefilledellipse]) +filledEllipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------------- +Desenează o elipsă umplută, centrată la coordonatele specificate. ([mai mult |https://www.php.net/manual/en/function.imagefilledellipse]) -filledPolygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------------- -Creează un poligon umplut în imaginea $image. ([mai mult |https://www.php.net/manual/en/function.imagefilledpolygon]) +filledPolygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------------- +Creează un poligon umplut în imagine. `$points` este un array de coordonate (x0, y0, x1, y1, ...). ([mai mult |https://www.php.net/manual/en/function.imagefilledpolygon]) -filledRectangle(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] -------------------------------------------------------------------------------- -Creează un dreptunghi umplut cu `$color` în imagine începând cu punctul 1 și terminând cu punctul 2. 0, 0 este colțul din stânga sus al imaginii. ([mai mult |https://www.php.net/manual/en/function.imagefilledrectangle]) +filledRectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------- +Creează un dreptunghi umplut cu `$color` în imagine, având colțurile opuse la (`$x1`, `$y1`) și (`$x2`, `$y2`). Punctul (0, 0) este colțul din stânga sus al imaginii. ([mai mult |https://www.php.net/manual/en/function.imagefilledrectangle]) -fillToBorder(int $x, int $y, int $border, int $color): void .[method] ---------------------------------------------------------------------- -Efectuează o umplere de inundație a cărei culoare a marginii este definită de `$border`. Punctul de plecare pentru umplere este `$x`, `$y` (stânga sus este 0, 0) și regiunea este umplută cu culoarea `$color`. ([mai mult |https://www.php.net/manual/en/function.imagefilltoborder]) +filledRectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------------- +Creează un dreptunghi umplut cu `$color` în imagine, începând de la punctul (`$left`, `$top`) cu lățimea `$width` și înălțimea `$height`. Punctul (0, 0) este colțul din stânga sus al imaginii. + + +fillToBorder(int $x, int $y, int $border, ImageColor $color): void .[method] +---------------------------------------------------------------------------- +Realizează o umplere (flood fill) a cărei culoare de margine este definită de `$border`. Punctul de pornire al umplerii este (`$x`, `$y`) (stânga sus este 0, 0), iar zona este umplută cu culoarea `$color`. ([mai mult |https://www.php.net/manual/en/function.imagefilltoborder]) filter(int $filtertype, int ...$args): void .[method] ----------------------------------------------------- -Aplică filtrul dat `$filtertype` pe imagine. ([mai mult |https://www.php.net/manual/en/function.imagefilter]) +Aplică filtrul specificat `$filtertype` imaginii. Argumentele suplimentare `$args` depind de tipul filtrului. ([mai mult |https://www.php.net/manual/en/function.imagefilter]) flip(int $mode): void .[method] ------------------------------- -Întoarce imaginea folosind datele `$mode`. ([mai mult |https://www.php.net/manual/en/function.imageflip]) +Răstoarnă imaginea folosind modul `$mode` specificat (ex. `IMG_FLIP_HORIZONTAL`). ([mai mult |https://www.php.net/manual/en/function.imageflip]) -ftText(int $size, int $angle, int $x, int $y, int $col, string $fontFile, string $text, array $extrainfo=null): array .[method] -------------------------------------------------------------------------------------------------------------------------------- -Scrieți text pe imagine folosind fonturi cu FreeType 2. ([mai mult |https://www.php.net/manual/en/function.imagefttext]) +ftText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +---------------------------------------------------------------------------------------------------------------------------------------- +Scrie text în imagine folosind fonturi FreeType. ([mai mult |https://www.php.net/manual/en/function.imagefttext]) gammaCorrect(float $inputgamma, float $outputgamma): void .[method] ------------------------------------------------------------------- -Aplică corecția gamma imaginii, având în vedere o gamă de intrare și una de ieșire. ([mai mult |https://www.php.net/manual/en/function.imagegammacorrect]) +Aplică corecția gamma imaginii, transformând de la `$inputgamma` la `$outputgamma`. ([mai mult |https://www.php.net/manual/en/function.imagegammacorrect]) getClip(): array .[method] -------------------------- -Obține dreptunghiul de tăiere curent, adică zona dincolo de care nu se va desena niciun pixel. ([mai mult |https://www.php.net/manual/en/function.imagegetclip]) +Returnează zona de decupare curentă (clipping area), adică dreptunghiul în afara căruia nu vor fi desenați pixeli. ([mai mult |https://www.php.net/manual/en/function.imagegetclip]) getHeight(): int .[method] -------------------------- -Returnează înălțimea imaginii. +Returnează înălțimea imaginii în pixeli. getImageResource(): resource|GdImage .[method] ---------------------------------------------- -Returnează resursa originală. +Returnează resursa GD internă a imaginii. getWidth(): int .[method] ------------------------- -Returnează lățimea imaginii. +Returnează lățimea imaginii în pixeli. -interlace(int $interlace=null): int .[method] ---------------------------------------------- -Activează sau dezactivează bitul de întrepătrundere. Dacă bitul de întrepătrundere este setat și imaginea este utilizată ca imagine JPEG, imaginea este creată ca o imagine JPEG progresivă. ([mai mult |https://www.php.net/manual/en/function.imageinterlace]) +interlace(?int $interlace=null): int .[method] +---------------------------------------------- +Activează sau dezactivează modul intercalat (interlacing). Dacă este activat și imaginea este salvată ca JPEG, va fi salvată ca JPEG progresiv. Returnează 1 dacă interlace bit este setat, 0 altfel. ([mai mult |https://www.php.net/manual/en/function.imageinterlace]) isTrueColor(): bool .[method] ----------------------------- -Află dacă imaginea este un truecolor. ([mai mult |https://www.php.net/manual/en/function.imageistruecolor]) +Verifică dacă imaginea este truecolor (spre deosebire de cele bazate pe paletă). ([mai mult |https://www.php.net/manual/en/function.imageistruecolor]) layerEffect(int $effect): void .[method] ---------------------------------------- -Setați indicatorul de amestecare alfa pentru a utiliza efectele de stratificare. ([mai mult |https://www.php.net/manual/en/function.imagelayereffect]) +Setează flag-ul de amestecare alfa pentru a utiliza efecte de stratificare (layer effects). ([mai mult |https://www.php.net/manual/en/function.imagelayereffect]) -line(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] --------------------------------------------------------------------- -Trasează o linie între cele două puncte date. ([mai mult |https://www.php.net/manual/en/function.imageline]) +line(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +--------------------------------------------------------------------------- +Desenează o linie între două puncte date. ([mai mult |https://www.php.net/manual/en/function.imageline]) -openPolygon(array $points, int $numPoints, int $color): void .[method] ----------------------------------------------------------------------- -Desenează un poligon deschis pe imagine. Spre deosebire de `polygon()`, nu se trasează nicio linie între ultimul și primul punct. ([mai mult |https://www.php.net/manual/en/function.imageopenpolygon]) +openPolygon(array $points, ImageColor $color): void .[method] +------------------------------------------------------------- +Desenează un poligon deschis pe imagine. Spre deosebire de `polygon()`, nu se desenează nicio linie între ultimul și primul punct. ([mai mult |https://www.php.net/manual/en/function.imageopenpolygon]) paletteCopy(Image $source): void .[method] ------------------------------------------ -Copiază paleta de pe `$source` pe imagine. ([mai mult |https://www.php.net/manual/en/function.imagepalettecopy]) +Copiază paleta din imaginea `$source` în imaginea curentă. ([mai mult |https://www.php.net/manual/en/function.imagepalettecopy]) paletteToTrueColor(): void .[method] ------------------------------------ -Convertește o imagine bazată pe palete, creată de funcții precum `create()`, într-o imagine în culori reale, precum `createtruecolor()`. ([mai mult |https://www.php.net/manual/en/function.imagepalettetotruecolor]) +Convertește o imagine bazată pe paletă într-o imagine truecolor. ([mai mult |https://www.php.net/manual/en/function.imagepalettetotruecolor]) place(Image $image, int|string $left=0, int|string $top=0, int $opacity=100): Image .[method] --------------------------------------------------------------------------------------------- -Copiază `$image` în imagine la coordonatele `$left` și `$top`. Coordonatele pot fi transmise ca numere întregi în pixeli sau ca șiruri de caractere în procente (de exemplu, `'50%'`). +Suprapune imaginea `$image` peste imaginea curentă la coordonatele (`$left`, `$top`). Coordonatele pot fi specificate ca numere întregi în pixeli sau șiruri de caractere reprezentând procente (de exemplu, `'50%'`). `$opacity` controlează transparența imaginii suprapuse (0-100). + +polygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------- +Creează un poligon închis în imagine. `$points` este un array de coordonate. ([mai mult |https://www.php.net/manual/en/function.imagepolygon]) -polygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------- -Creează un poligon în imagine. ([mai mult |https://www.php.net/manual/en/function.imagepolygon]) +rectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------- +Desenează un dreptunghi cu colțurile opuse la coordonatele specificate. ([mai mult |https://www.php.net/manual/en/function.imagerectangle]) -rectangle(int $x1, int $y1, int $x2, int $y2, int $col): void .[method] ------------------------------------------------------------------------ -Creează un dreptunghi care începe la coordonatele specificate. ([mai mult |https://www.php.net/manual/en/function.imagerectangle]) + +rectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------- +Desenează un dreptunghi pornind de la coordonatele (`$left`, `$top`) cu lățimea `$width` și înălțimea `$height`. resize(int|string $width, int|string $height, int $flags=Image::OrSmaller): Image .[method] ------------------------------------------------------------------------------------------- -Scală o imagine, vezi [mai multe informații |#Image Resize]. Dimensiunile pot fi transmise ca numere întregi în pixeli sau ca șiruri de caractere în procente (de exemplu, `'50%'`). +Redimensionează imaginea, [mai multe informații |#Redimensionare]. Dimensiunile pot fi specificate ca numere întregi în pixeli sau șiruri de caractere reprezentând procente (de exemplu, `'50%'`). -resolution(int $resX=null, int $resY=null): mixed .[method] ------------------------------------------------------------ -Permite setarea și obținerea rezoluției unei imagini în DPI (dots per inch). Dacă nu se furnizează niciunul dintre parametrii opționali, rezoluția curentă este returnată sub formă de matrice indexată. Dacă se indică doar `$resX`, rezoluția orizontală și verticală este setată la această valoare. În cazul în care sunt furnizați ambii parametri opționali, rezoluția orizontală și verticală sunt stabilite la aceste valori. +resolution(?int $resX=null, ?int $resY=null): mixed .[method] +------------------------------------------------------------- +Setează sau returnează rezoluția imaginii în DPI (dots per inch). Dacă nu este specificat niciun parametru, returnează rezoluția curentă ca array indexat (`[x, y]`). Dacă este specificat doar `$resX`, rezoluția orizontală și verticală sunt setate la această valoare. Dacă sunt specificați ambii parametri, rezoluția orizontală și verticală sunt setate la aceste valori respective. -Rezoluția este utilizată ca meta-informație doar atunci când imaginile sunt citite și scrise în formate care acceptă acest tip de informații (în prezent PNG și JPEG). Aceasta nu afectează nicio operațiune de desenare. Rezoluția implicită pentru imaginile noi este de 96 DPI. ([mai mult |https://www.php.net/manual/en/function.imageresolution]) +Rezoluția este utilizată doar ca meta-informație la citirea și scrierea imaginilor în formate care suportă aceste informații (în prezent PNG și JPEG). Nu afectează operațiile de desenare. Rezoluția implicită pentru imaginile noi este 96 DPI. ([mai mult |https://www.php.net/manual/en/function.imageresolution]) rotate(float $angle, int $backgroundColor): Image .[method] ----------------------------------------------------------- -Rotește imaginea folosind `$angle` în grade. Centrul de rotație este centrul imaginii, iar imaginea rotită poate avea dimensiuni diferite de cele ale imaginii originale. ([mai mult |https://www.php.net/manual/en/function.imagerotate]) +Rotește imaginea cu unghiul `$angle` specificat în grade. Centrul de rotație este centrul imaginii. `$backgroundColor` specifică culoarea zonelor descoperite după rotație. Imaginea rotită poate avea dimensiuni diferite față de cea originală. ([mai mult |https://www.php.net/manual/en/function.imagerotate]) .[note] -Necesită *Extensie GD la pachet*, deci nu este sigur că va funcționa peste tot. +Necesită prezența extensiei *Bundled GD*, deci s-ar putea să nu funcționeze peste tot. -save(string $file, int $quality=null, int $type=null): void .[method] ---------------------------------------------------------------------- -Salvează o imagine într-un fișier. +save(string $file, ?int $quality=null, ?int $type=null): void .[method] +----------------------------------------------------------------------- +Salvează imaginea într-un fișier. -Calitatea compresiei este cuprinsă în intervalul 0...100 pentru JPEG (implicit 85), WEBP (implicit 80) și AVIF (implicit 30) și 0...9 pentru PNG (implicit 9). În cazul în care tipul nu este evident din extensia fișierului, îl puteți specifica folosind una dintre constantele `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF`, și `Image::BMP`. +Calitatea compresiei este în intervalul 0..100 pentru JPEG (implicit 85), WEBP (implicit 80) și AVIF (implicit 30) și 0..9 pentru PNG (implicit 9). Dacă tipul nu este evident din extensia fișierului, îl puteți specifica folosind una dintre constantele `ImageType`. saveAlpha(bool $saveflag): void .[method] ----------------------------------------- -Setează stegulețul care determină dacă se păstrează informațiile complete ale canalului alfa (spre deosebire de transparența unei singure culori) la salvarea imaginilor PNG. +Setează flag-ul care determină dacă informațiile complete ale canalului alfa trebuie păstrate la salvarea imaginilor PNG (spre deosebire de transparența cu o singură culoare). -Alphablending trebuie să fie dezactivat (`alphaBlending(false)`) pentru a păstra canalul alfa în primul rând. ([mai mult |https://www.php.net/manual/en/function.imagesavealpha]) +Amestecarea alfa trebuie dezactivată (`alphaBlending(false)`) pentru a păstra canalul alfa. ([mai mult |https://www.php.net/manual/en/function.imagesavealpha]) scale(int $newWidth, int $newHeight=-1, int $mode=IMG_BILINEAR_FIXED): Image .[method] -------------------------------------------------------------------------------------- -Redimensionează o imagine folosind algoritmul de interpolare dat. ([mai mult |https://www.php.net/manual/en/function.imagescale]) +Scalează imaginea folosind algoritmul de interpolare dat. ([mai mult |https://www.php.net/manual/en/function.imagescale]) -send(int $type=Image::JPEG, int $quality=null): void .[method] --------------------------------------------------------------- -Trimite o imagine în browser. +send(int $type=ImageType::JPEG, ?int $quality=null): void .[method] +------------------------------------------------------------------- +Trimite imaginea către browser. -Calitatea compresiei este cuprinsă în intervalul 0...100 pentru JPEG (implicit 85), WEBP (implicit 80) și AVIF (implicit 30) și 0...9 pentru PNG (implicit 9). Tipul este una dintre constantele `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` și `Image::BMP`. +Calitatea compresiei este în intervalul 0..100 pentru JPEG (implicit 85), WEBP (implicit 80) și AVIF (implicit 30) și 0..9 pentru PNG (implicit 9). setBrush(Image $brush): void .[method] -------------------------------------- -Stabilește imaginea pensulei care va fi utilizată de toate funcțiile de desenare a liniilor (cum ar fi `line()` și `polygon()`) atunci când se desenează cu culorile speciale IMG_COLOR_BRUSHED sau IMG_COLOR_STYLEDBRUSHED. ([mai mult |https://www.php.net/manual/en/function.imagesetbrush]) +Setează imaginea pensulei care va fi utilizată în toate funcțiile de desenare a liniilor (de exemplu, `line()` și `polygon()`) la desenarea cu culorile speciale IMG_COLOR_BRUSHED sau IMG_COLOR_STYLEDBRUSHED. ([mai mult |https://www.php.net/manual/en/function.imagesetbrush]) setClip(int $x1, int $y1, int $x2, int $y2): void .[method] ----------------------------------------------------------- -Stabilește dreptunghiul de decupaj curent, adică zona dincolo de care nu se va desena niciun pixel. ([mai mult |https://www.php.net/manual/en/function.imagesetclip]) +Setează decuparea curentă, adică zona dincolo de care nu vor fi desenați pixeli. ([mai mult |https://www.php.net/manual/en/function.imagesetclip]) setInterpolation(int $method=IMG_BILINEAR_FIXED): void .[method] ---------------------------------------------------------------- -Stabilește metoda de interpolare care afectează metodele `rotate()` și `affine()`. ([mai mult |https://www.php.net/manual/en/function.imagesetinterpolation]) +Setează metoda de interpolare, care va afecta metodele `rotate()` și `affine()`. ([mai mult |https://www.php.net/manual/en/function.imagesetinterpolation]) -setPixel(int $x, int $y, int $color): void .[method] ----------------------------------------------------- -Desenează un pixel la coordonatele specificate. ([mai mult |https://www.php.net/manual/en/function.imagesetpixel]) +setPixel(int $x, int $y, ImageColor $color): void .[method] +----------------------------------------------------------- +Desenează un pixel la coordonata specificată. ([mai mult |https://www.php.net/manual/en/function.imagesetpixel]) setStyle(array $style): void .[method] -------------------------------------- -Stabilește stilul care va fi utilizat de toate funcțiile de desenare a liniilor (cum ar fi `line()` și `polygon()`) atunci când se desenează cu culoarea specială IMG_COLOR_STYLED sau linii de imagini cu culoarea IMG_COLOR_STYLEDBRUSHED. ([mai mult |https://www.php.net/manual/en/function.imagesetstyle]) +Setează stilul care trebuie utilizat de toate funcțiile de desenare a liniilor (de exemplu, `line()` și `polygon()`) la desenarea cu culoarea specială IMG_COLOR_STYLED sau a liniilor de imagini cu culoarea IMG_COLOR_STYLEDBRUSHED. ([mai mult |https://www.php.net/manual/en/function.imagesetstyle]) setThickness(int $thickness): void .[method] -------------------------------------------- -Stabilește grosimea liniilor desenate atunci când se desenează dreptunghiuri, poligoane, arcuri etc. la `$thickness` pixeli. ([mai mult |https://www.php.net/manual/en/function.imagesetthickness]) +Setează grosimea liniilor la desenarea dreptunghiurilor, poligoanelor, arcelor etc. la `$thickness` pixeli. ([mai mult |https://www.php.net/manual/en/function.imagesetthickness]) setTile(Image $tile): void .[method] ------------------------------------ -Definește imaginea de țiglă care trebuie utilizată de toate funcțiile de umplere a regiunii (cum ar fi `fill()` și `filledPolygon()`) atunci când se completează cu culoarea specială IMG_COLOR_TILED. +Setează imaginea mozaicului care va fi utilizată în toate funcțiile de umplere a regiunilor (de exemplu, `fill()` și `filledPolygon()`) la umplerea cu culoarea specială IMG_COLOR_TILED. -O țiglă este o imagine utilizată pentru a umple o zonă cu un model repetat. Orice imagine poate fi utilizată ca o țiglă, iar prin setarea indicelui de culoare transparentă a imaginii țiglă cu `colorTransparent()`, se poate crea o țiglă care permite strălucirea anumitor părți ale zonei de bază. ([mai mult |https://www.php.net/manual/en/function.imagesettile]) +Un mozaic este o imagine utilizată pentru a umple o zonă cu un model repetitiv. Orice imagine poate fi utilizată ca mozaic. Setând o culoare transparentă pentru imaginea mozaic folosind `colorTransparent()`, se poate crea un mozaic prin care anumite părți ale zonei de dedesubt vor fi vizibile. ([mai mult |https://www.php.net/manual/en/function.imagesettile]) sharpen(): Image .[method] -------------------------- -Acutizează puțin imaginea. +Ascute imaginea. .[note] -Necesită *Extensie GD la pachet*, deci nu este sigur că va funcționa peste tot. - - -string(int $font, int $x, int $y, string $str, int $col): void .[method] ------------------------------------------------------------------------- -Desenează un șir de caractere la coordonatele date. ([mai mult |https://www.php.net/manual/en/function.imagestring]) - +Necesită prezența extensiei *Bundled GD*, deci s-ar putea să nu funcționeze peste tot. -stringUp(int $font, int $x, int $y, string $s, int $col): void .[method] ------------------------------------------------------------------------- -Desenează un șir pe verticală la coordonatele date. ([mai mult |https://www.php.net/manual/en/function.imagestringup]) +toString(int $type=ImageType::JPEG, ?int $quality=null): string .[method] +------------------------------------------------------------------------- +Returnează conținutul imaginii sub formă de șir. -toString(int $type=Image::JPEG, int $quality=null): string .[method] --------------------------------------------------------------------- -Produce o imagine în șir de caractere. - -Calitatea compresiei este cuprinsă în intervalul 0...100 pentru JPEG (implicit 85), WEBP (implicit 80) și AVIF (implicit 30) și 0...9 pentru PNG (implicit 9). Tipul este una dintre constantele `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` și `Image::BMP`. +Calitatea compresiei este în intervalul 0..100 pentru JPEG (implicit 85), WEBP (implicit 80) și AVIF (implicit 30) și 0..9 pentru PNG (implicit 9). trueColorToPalette(bool $dither, int $ncolors): void .[method] -------------------------------------------------------------- -Convertește o imagine truecolor într-o imagine paletă. ([mai mult |https://www.php.net/manual/en/function.imagetruecolortopalette]) +Convertește o imagine truecolor într-o imagine cu paletă. ([mai mult |https://www.php.net/manual/en/function.imagetruecolortopalette]) -ttfText(int $size, int $angle, int $x, int $y, int $color, string $fontfile, string $text): array .[method] ------------------------------------------------------------------------------------------------------------ +ttfText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +----------------------------------------------------------------------------------------------------------------------------------------- Scrie textul dat în imagine folosind fonturi TrueType. ([mai mult |https://www.php.net/manual/en/function.imagettftext]) diff --git a/utils/ro/iterables.texy b/utils/ro/iterables.texy new file mode 100644 index 0000000000..b45b0b8252 --- /dev/null +++ b/utils/ro/iterables.texy @@ -0,0 +1,170 @@ +Lucrul cu iteratorii +******************** + +.[perex]{data-version:4.0.4} +[api:Nette\Utils\Iterables] este o clasă statică cu funcții pentru lucrul cu iteratorii. Echivalentul său pentru array-uri este [Nette\Utils\Arrays |arrays]. + + +Instalare: + +```shell +composer require nette/utils +``` + +Toate exemplele presupun că a fost creat un alias: + +```php +use Nette\Utils\Iterables; +``` + + +contains(iterable $iterable, $value): bool .[method] +---------------------------------------------------- + +Caută valoarea specificată în iterator. Utilizează comparație strictă (`===`) pentru a verifica potrivirea. Returnează `true` dacă valoarea este găsită, altfel `false`. + +```php +Iterables::contains(new ArrayIterator([1, 2, 3]), 1); // true +Iterables::contains(new ArrayIterator([1, 2, 3]), '1'); // false +``` + +Această metodă este utilă pentru a determina rapid dacă o anumită valoare se află în iterator, fără a parcurge manual toate elementele. + + +containsKey(iterable $iterable, $key): bool .[method] +----------------------------------------------------- + +Caută cheia specificată în iterator. Utilizează comparație strictă (`===`) pentru a verifica potrivirea. Returnează `true` dacă cheia este găsită, altfel `false`. + +```php +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 0); // true +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 4); // false +``` + + +every(iterable $iterable, callable $predicate): bool .[method] +-------------------------------------------------------------- + +Verifică dacă *toate* elementele iteratorului `$iterable` satisfac condiția definită în `$predicate`. Funcția `$predicate` are semnătura `function ($value, $key, iterable $iterable): bool`. Metoda returnează `true` doar dacă `$predicate` returnează `true` pentru *fiecare* element. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isBelowThreshold = fn($value) => $value < 40; +$res = Iterables::every($iterator, $isBelowThreshold); // true +``` + +Această metodă este utilă pentru a valida dacă toate elementele dintr-o colecție îndeplinesc o anumită condiție. + + +filter(iterable $iterable, callable $predicate): Generator .[method] +-------------------------------------------------------------------- + +Creează un nou iterator care conține doar acele elemente din iteratorul original care îndeplinesc condiția definită în `$predicate`. Funcția `$predicate` are semnătura `function ($value, $key, iterable $iterable): bool` și trebuie să returneze `true` pentru elementele care trebuie păstrate. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::filter($iterator, fn($v) => $v < 3); +// 1, 2 +``` + +Metoda utilizează un generator, ceea ce înseamnă că filtrarea are loc treptat pe măsură ce rezultatul este parcurs. Acest lucru este eficient din punct de vedere al memoriei și permite procesarea colecțiilor very large. Dacă nu parcurgeți toate elementele iteratorului rezultat, veți economisi putere de calcul, deoarece nu toate elementele iteratorului original vor fi procesate. + + +first(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------------- + +Returnează primul element al iteratorului. Dacă este specificat `$predicate`, returnează primul element care îndeplinește condiția dată. Funcția `$predicate` are semnătura `function ($value, $key, iterable $iterable): bool`. Dacă nu se găsește niciun element corespunzător, se apelează funcția `$else` (dacă este specificată) și se returnează rezultatul acesteia. Dacă `$else` nu este specificat, se returnează `null`. + +```php +Iterables::first(new ArrayIterator([1, 2, 3])); // 1 +Iterables::first(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 3 +Iterables::first(new ArrayIterator([])); // null +Iterables::first(new ArrayIterator([]), else: fn() => false); // false +``` + +Această metodă este utilă atunci când trebuie să obțineți rapid primul element al unei colecții sau primul element care îndeplinește o anumită condiție, fără a fi nevoie să parcurgeți manual întreaga colecție. + + +firstKey(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +---------------------------------------------------------------------------------------------- + +Returnează cheia primului element al iteratorului. Dacă este specificat `$predicate`, returnează cheia primului element care îndeplinește condiția dată. Funcția `$predicate` are semnătura `function ($value, $key, iterable $iterable): bool`. Dacă nu se găsește niciun element corespunzător, se apelează funcția `$else` (dacă este specificată) și se returnează rezultatul acesteia. Dacă `$else` nu este specificat, se returnează `null`. + +```php +Iterables::firstKey(new ArrayIterator([1, 2, 3])); // 0 +Iterables::firstKey(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 2 +Iterables::firstKey(new ArrayIterator(['a' => 1, 'b' => 2])); // 'a' +Iterables::firstKey(new ArrayIterator([])); // null +``` + + +map(iterable $iterable, callable $transformer): Generator .[method] +------------------------------------------------------------------- + +Creează un nou iterator prin aplicarea funcției `$transformer` fiecărui element al iteratorului original. Funcția `$transformer` are semnătura `function ($value, $key, iterable $iterable): mixed`, iar valoarea sa returnată este utilizată ca nouă valoare a elementului. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::map($iterator, fn($v) => $v * 2); +// 2, 4, 6 +``` + +Metoda utilizează un generator, asigurând o transformare treptată (lazy) și eficientă din punct de vedere al memoriei. Permite procesarea colecțiilor mari și economisește resurse dacă nu se iterează peste toate elementele rezultatului. + + +mapWithKeys(iterable $iterable, callable $transformer): Generator .[method] +--------------------------------------------------------------------------- + +Creează un nou iterator prin transformarea valorilor și cheilor iteratorului original. Funcția `$transformer` are semnătura `function ($value, $key, iterable $iterable): ?array{$newKey, $newValue}`. Dacă `$transformer` returnează `null`, elementul este omis. Pentru elementele păstrate, primul element al array-ului returnat este utilizat ca nouă cheie, iar al doilea element ca nouă valoare. + +```php +$iterator = new ArrayIterator(['a' => 1, 'b' => 2]); +$iterator = Iterables::mapWithKeys($iterator, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +Similar cu `map()`, această metodă utilizează un generator pentru procesare treptată și eficientă din punct de vedere al memoriei. + + +memoize(iterable $iterable): IteratorAggregate .[method] +-------------------------------------------------------- + +Creează un wrapper în jurul iteratorului care memorează în cache cheile și valorile sale în timpul iterației. Acest lucru permite iterația repetată a datelor fără a fi nevoie să parcurgeți din nou sursa originală de date. + +```php +$iterator = /* date care nu pot fi iterate de mai multe ori */ +$memoized = Iterables::memoize($iterator); +// Acum puteți itera $memoized de mai multe ori fără pierderea datelor +``` + +Această metodă este utilă în situațiile în care trebuie să parcurgeți același set de date de mai multe ori, dar iteratorul original nu permite iterația repetată sau aceasta ar fi ineficientă (ex. citirea din baza de date sau fișier). + + +some(iterable $iterable, callable $predicate): bool .[method] +------------------------------------------------------------- + +Verifică dacă cel puțin un element al iteratorului îndeplinește condiția definită în `$predicate`. Funcția `$predicate` are semnătura `function ($value, $key, iterable $iterable): bool` și trebuie să returneze `true` pentru cel puțin un element pentru ca metoda `some()` să returneze `true`. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isEven = fn($value) => $value % 2 === 0; +$res = Iterables::some($iterator, $isEven); // true +``` + +Această metodă este utilă pentru a verifica rapid dacă există cel puțin un element într-o colecție care îndeplinește o anumită condiție. + +Vezi și [#every()]. + + +toIterator(iterable $iterable): Iterator .[method] +-------------------------------------------------- + +Convertește orice obiect iterabil (array, `Traversable`) într-un `Iterator`. Dacă intrarea `$iterable` este deja un `Iterator`, o returnează neschimbată. + +```php +$array = [1, 2, 3]; +$iterator = Iterables::toIterator($array); +// Acum aveți un Iterator în loc de un array +``` + +Această metodă este utilă atunci când aveți nevoie să garantați că lucrați cu un `Iterator`, indiferent de tipul datelor de intrare iterabile. diff --git a/utils/ro/json.texy b/utils/ro/json.texy index c9e39ef738..985602cdb7 100644 --- a/utils/ro/json.texy +++ b/utils/ro/json.texy @@ -1,8 +1,8 @@ -Funcții JSON -************ +Lucrul cu JSON +************** .[perex] -[api:Nette\Utils\Json] este o clasă statică cu funcții de codificare și decodificare JSON. Aceasta gestionează vulnerabilitățile din diferite versiuni PHP și aruncă excepții în caz de eroare. +[api:Nette\Utils\Json] este o clasă statică cu funcții pentru codificarea și decodarea formatului JSON. Gestionează vulnerabilitățile diferitelor versiuni PHP și aruncă excepții în caz de erori. Instalare: @@ -11,44 +11,44 @@ Instalare: composer require nette/utils ``` -Toate exemplele presupun că este definit următorul alias de clasă: +Toate exemplele presupun că a fost creat un alias: ```php use Nette\Utils\Json; ``` -Utilizare .[#toc-usage] -======================= +Utilizare +========= encode(mixed $value, bool $pretty=false, bool $asciiSafe=false, bool $htmlSafe=false, bool $forceObjects=false): string .[method] --------------------------------------------------------------------------------------------------------------------------------- -Convertește `$value` în format JSON. +Convertește valoarea `$value` în format JSON. -Când `$pretty` este setat, formatează JSON pentru o citire mai ușoară și mai clară: +Când `$pretty` este setat, formatează JSON-ul pentru o citire și claritate mai ușoară: ```php -Json::encode($value); // returnează JSON -Json::encode($value, pretty: true); // returnează JSON mai clar +Json::encode($value); // returnează JSON compact +Json::encode($value, pretty: true); // returnează JSON formatat (pretty-printed) ``` -Când este setat `$asciiSafe`, acesta generează o ieșire ASCII, adică înlocuiește caracterele Unicode cu secvențe `\uxxxx`: +Cu `$asciiSafe`, generează ieșire în ASCII, adică înlocuiește caracterele unicode cu secvențe `\uxxxx`: ```php -Json::encode('žluťoučký', asciiSafe: true); -// '"\u017elu\u0165ou\u010dk\u00fd"' +Json::encode('țâșni', asciiSafe: true); +// '"\u021b\u00e2\u0219ni"' ``` -Parametrul `$htmlSafe` garantează că ieșirea nu conține caractere cu semnificație specială în HTML: +Parametrul `$htmlSafe` asigură că ieșirea nu va conține caractere cu semnificație specială în HTML (`<`, `>`, `&`): ```php -Json::encode('one true] ``` -În caz de eroare, se aruncă o excepție `Nette\Utils\JsonException`. +Aruncă o excepție `Nette\Utils\JsonException` în caz de eroare de parsare. ```php try { $value = Json::decode($json); } catch (Nette\Utils\JsonException $e) { - // Tratarea excepțiilor + // Tratarea excepției } ``` -Cum se trimite un JSON de la un prezentator? .[#toc-how-to-send-a-json-from-a-presenter] -======================================================================================== +Cum se trimite JSON dintr-un presenter? +======================================= -Puteți utiliza metoda `$this->sendJson($data)`, care poate fi apelată, de exemplu, în metoda `action*()`, a se vedea [Trimiterea unui răspuns |application:presenters#Sending a Response]. +Puteți utiliza metoda `$this->sendJson($data)`, pe care o puteți apela, de exemplu, într-o metodă `action*()`, vezi [Trimiterea unui răspuns |application:presenters#Trimiterea răspunsului]. diff --git a/utils/ro/paginator.texy b/utils/ro/paginator.texy index 876222586d..096f1c9c13 100644 --- a/utils/ro/paginator.texy +++ b/utils/ro/paginator.texy @@ -2,7 +2,7 @@ Paginator ********* .[perex] -Aveți nevoie să paginați o listă de date? Deoarece matematica din spatele paginării poate fi complicată, [api:Nette\Utils\Paginator] vă va ajuta. +Aveți nevoie să paginați afișarea datelor? Deoarece matematica paginării poate fi înșelătoare, [api:Nette\Utils\Paginator] vă poate ajuta. Instalare: @@ -11,55 +11,55 @@ Instalare: composer require nette/utils ``` -Să creăm un obiect de paginare și să setăm informațiile de bază pentru acesta: +Creăm un obiect paginator și îi setăm informațiile de bază: ```php $paginator = new Nette\Utils\Paginator; -$paginator->setPage(1); // numărul paginii curente (numerotat de la 1) -$paginator->setItemsPerPage(30); // numărul de înregistrări pe pagină -$paginator->setItemCount(356); // numărul total de înregistrări (dacă este disponibil) +$paginator->setPage(1); // numărul paginii curente +$paginator->setItemsPerPage(30); // numărul de elemente pe pagină +$paginator->setItemCount(356); // numărul total de elemente (dacă este cunoscut) ``` -Paginile sunt numerotate de la 1. Le putem schimba folosind `setBase()`: +Paginile sunt numerotate implicit de la 1. Putem schimba acest lucru folosind `setBase()`: ```php -$paginator->setBase(0); // numerotate de la 0 +$paginator->setBase(0); // numerotăm paginile de la 0 ``` -Obiectul va furniza acum toate informațiile de bază utile în crearea unui paginator. Puteți, de exemplu, să-l treceți într-un șablon și să-l utilizați acolo. +Obiectul oferă acum toate informațiile de bază utile la crearea unui sistem de paginare. Puteți, de exemplu, să îl transmiteți unui șablon și să îl utilizați acolo. ```php -$paginator->isFirst(); // Aceasta este prima pagină? -$paginator->isLast(); // Este ultima pagină? +$paginator->isFirst(); // suntem pe prima pagină? +$paginator->isLast(); // suntem pe ultima pagină? $paginator->getPage(); // numărul paginii curente $paginator->getFirstPage(); // numărul primei pagini $paginator->getLastPage(); // numărul ultimei pagini -$paginator->getFirstItemOnPage(); // numărul secvențial al primului articol de pe pagină +$paginator->getFirstItemOnPage(); // numărul de ordine al primului element de pe pagină $paginator->getLastItemOnPage(); // numărul de ordine al ultimului element de pe pagină -$paginator->getPageIndex(); // numărul paginii curente, dacă este numerotată de la 0 +$paginator->getPageIndex(); // indexul paginii curente (bazat pe 0) $paginator->getPageCount(); // numărul total de pagini -$paginator->getItemsPerPage(); // numărul de înregistrări pe pagină -$paginator->getItemCount(); // numărul total de înregistrări (dacă este disponibil) +$paginator->getItemsPerPage(); // numărul de elemente pe pagină +$paginator->getItemCount(); // numărul total de elemente (dacă este cunoscut) ``` -Paginatorul va ajuta la formularea interogării SQL. Metodele `getLength()` și `getOffset()` returnează valorile pe care le puteți utiliza în clauzele LIMIT și OFFSET: +Paginatorul ajută la formularea interogărilor SQL. Metodele `getLength()` și `getOffset()` returnează valorile pe care le vom folosi în clauzele `LIMIT` și `OFFSET`: ```php $result = $database->query( 'SELECT * FROM items LIMIT ? OFFSET ?', - $paginator->getLength(), + $paginator->getLength(), // Echivalent cu ItemsPerPage $paginator->getOffset(), ); ``` -Dacă aveți nevoie de paginare în ordine inversă, adică pagina nr. 1 corespunde celui mai mare decalaj, puteți utiliza `getCountdownOffset()`: +Dacă trebuie să paginăm în ordine inversă (de exemplu, ultimele înregistrări primele), unde pagina 1 corespunde celui mai mare offset, folosim `getCountdownOffset()`: ```php $result = $database->query( - 'SELECT * FROM items LIMIT ? OFFSET ?', + 'SELECT * FROM items ORDER BY id DESC LIMIT ? OFFSET ?', // Exemplu cu sortare desc $paginator->getLength(), $paginator->getCountdownOffset(), ); ``` -Un exemplu de utilizare în aplicație poate fi găsit în cartea de bucate [Paginarea rezultatelor bazei de date |best-practices:pagination]. +Un exemplu de utilizare în aplicație poate fi găsit în cookbook-ul [Paginarea rezultatelor bazei de date |best-practices:pagination]. diff --git a/utils/ro/random.texy b/utils/ro/random.texy index b17124acc2..0f0af629ec 100644 --- a/utils/ro/random.texy +++ b/utils/ro/random.texy @@ -1,8 +1,8 @@ -Generator de șiruri aleatorii +Generarea șirurilor aleatoare ***************************** .[perex] -[api:Nette\Utils\Random] este o clasă statică pentru generarea de șiruri pseudoaleatorii sigure din punct de vedere criptografic. +[api:Nette\Utils\Random] este o clasă statică pentru generarea șirurilor pseudo-aleatoare sigure din punct de vedere criptografic. Instalare: @@ -15,7 +15,7 @@ composer require nette/utils generate(int $length=10, string $charlist=`'0-9a-z'`): string .[method] ----------------------------------------------------------------------- -Generează un șir aleatoriu de o lungime dată din caracterele specificate în al doilea argument. Suportă intervale, cum ar fi `0-9` sau `A-Z`. +Generează un șir aleatoriu de lungimea `$length` specificată, folosind caracterele din `$charlist`. Se pot utiliza și intervale, cum ar fi `0-9` sau `a-z`. ```php use Nette\Utils\Random; diff --git a/utils/ro/reflection.texy b/utils/ro/reflection.texy index 48610f8370..d915161076 100644 --- a/utils/ro/reflection.texy +++ b/utils/ro/reflection.texy @@ -1,8 +1,8 @@ -Reflectarea PHP -*************** +Reflecție PHP +************* .[perex] -[api:Nette\Utils\Reflection] este o clasă statică cu funcții utile pentru reflecția PHP. Scopul său este de a corecta defectele din clasele native și de a unifica comportamentul în diferite versiuni ale PHP. +[api:Nette\Utils\Reflection] este o clasă statică cu funcții utile pentru reflecția PHP. Scopul său este de a corecta deficiențele claselor native și de a unifica comportamentul între diferite versiuni PHP. Instalare: @@ -11,7 +11,7 @@ Instalare: composer require nette/utils ``` -Toate exemplele presupun că este definit următorul alias de clasă: +Toate exemplele presupun că a fost creat un alias: ```php use Nette\Utils\Reflection; @@ -21,13 +21,13 @@ use Nette\Utils\Reflection; areCommentsAvailable(): bool .[method] -------------------------------------- -Află dacă reflecția are acces la comentariile PHPdoc. Este posibil ca comentariile să nu fie disponibile din cauza cache-ului [opcode |https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments], a se vedea, de exemplu, directiva [opcache.save-comments |https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments]. +Verifică dacă reflecția are acces la comentariile PHPdoc. Comentariile pot fi indisponibile din cauza cache-ului opcode, vezi de exemplu directiva [opcache.save-comments |https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments]. expandClassName(string $name, ReflectionClass $context): string .[method] ------------------------------------------------------------------------- -Extinde `$name` a clasei la numele complet în contextul `$context`, adică în contextul spațiului său de nume și al aliasurilor definite. Astfel, returnează modul în care parserul PHP ar înțelege `$name` dacă ar fi scris în corpul `$context`. +Extinde numele clasei `$name` la numele său complet calificat (FQCN) în contextul clasei `$context` (luând în considerare namespace-ul și aliasurile `use` definite în fișierul clasei `$context`). Practic, determină cum ar interpreta PHP parserul numele `$name` dacă ar fi scris în corpul clasei `$context`. ```php namespace Foo; @@ -47,9 +47,9 @@ Reflection::expandClassName('Baz', $context); // 'Foo\Baz' getMethodDeclaringMethod(ReflectionMethod $method): ReflectionMethod .[method] ------------------------------------------------------------------------------ -Returnează o reflectare a unei metode care conține o declarație `$method`. De obicei, fiecare metodă este o declarație proprie, dar corpul metodei poate fi, de asemenea, în trăsătură și sub un nume diferit. +Returnează reflecția metodei care conține declarația metodei `$method`. De obicei, fiecare metodă este propria sa declarație, dar corpul metodei poate fi găsit și într-un trait și sub un alt nume. -Deoarece PHP nu oferă suficiente informații pentru a determina declarația reală, Nette utilizează propria euristică, care **ar trebui să fie** fiabilă. +Deoarece PHP nu oferă informații suficiente pentru a determina declarația reală, Nette utilizează propria sa heuristică, care **ar trebui să fie** fiabilă. ```php trait DemoTrait @@ -76,9 +76,9 @@ Reflection::getMethodDeclaringMethod($method); // ReflectionMethod('DemoTrait::f getPropertyDeclaringClass(ReflectionProperty $prop): ReflectionClass .[method] ------------------------------------------------------------------------------ -Returnează o reflectare a unei clase sau trăsături care conține o declarație a proprietății `$prop`. Proprietatea poate fi, de asemenea, declarată în trăsătură. +Returnează reflecția clasei sau trait-ului care conține declarația proprietății `$prop`. Proprietatea poate fi, de asemenea, declarată într-un trait. -Deoarece PHP nu oferă suficiente informații pentru a determina declarația reală, Nette utilizează propriile euristici, care **nu sunt** fiabile. +Deoarece PHP nu oferă informații suficiente pentru a determina declarația reală, Nette utilizează propria sa heuristică, care **nu este** fiabilă. ```php trait DemoTrait @@ -100,7 +100,7 @@ Reflection::getPropertyDeclaringClass($prop); // ReflectionClass('DemoTrait') isBuiltinType(string $type): bool .[method deprecated] ------------------------------------------------------ -Determină dacă `$type` este un tip încorporat în PHP. În caz contrar, este numele clasei. +Verifică dacă `$type` este un tip încorporat PHP. În caz contrar, este un nume de clasă. ```php Reflection::isBuiltinType('string'); // true @@ -114,7 +114,7 @@ Utilizați [Nette\Utils\Validator::isBuiltinType() |validators#isBuiltinType]. toString($reflection): string .[method] --------------------------------------- -Convertește o reflecție într-un șir de caractere lizibil pentru oameni. +Convertește reflecția într-un șir inteligibil pentru om. ```php $func = new ReflectionFunction('func'); diff --git a/utils/ro/smartobject.texy b/utils/ro/smartobject.texy index d487e98cfd..043f092b9a 100644 --- a/utils/ro/smartobject.texy +++ b/utils/ro/smartobject.texy @@ -1,8 +1,8 @@ -SmartObject și StaticClass -************************** +SmartObject +*********** .[perex] -SmartObject adaugă suport pentru *proprietăți* la clasele PHP. StaticClass este utilizat pentru a desemna clasele statice. +SmartObject a îmbunătățit comportamentul obiectelor în PHP timp de ani de zile. Începând cu versiunea PHP 8.4, toate funcțiile sale fac deja parte din PHP însuși, încheindu-și astfel misiunea istorică de a fi pionier al abordării moderne orientate pe obiecte în PHP. Instalare: @@ -11,19 +11,64 @@ Instalare: composer require nette/utils ``` +SmartObject a apărut în 2007 ca o soluție revoluționară la deficiențele modelului de obiecte PHP de atunci. Într-o perioadă în care PHP suferea de numeroase probleme de design de obiecte, a adus o îmbunătățire semnificativă și o simplificare a muncii pentru dezvoltatori. A devenit o parte legendară a framework-ului Nette. Oferea funcționalități pe care PHP le-a dobândit abia mulți ani mai târziu - de la controlul accesului la proprietățile obiectelor până la facilități sintactice (syntactic sugar) sofisticate. Odată cu apariția PHP 8.4, și-a încheiat misiunea istorică, deoarece toate funcțiile sale au devenit parte nativă a limbajului. A devansat dezvoltarea PHP cu remarcabili 17 ani. -Proprietăți, Getters și Setters .[#toc-properties-getters-and-setters] -====================================================================== +Din punct de vedere tehnic, SmartObject a trecut printr-o evoluție interesantă. Inițial a fost implementat ca o clasă `Nette\Object`, de la care celelalte clase moșteneau funcționalitatea necesară. O schimbare fundamentală a venit cu PHP 5.4, care a introdus suportul pentru Trait-uri. Acest lucru a permis transformarea în forma Trait-ului `Nette\SmartObject`, ceea ce a adus o flexibilitate mai mare - dezvoltatorii puteau utiliza funcționalitatea chiar și în clasele care moșteneau deja de la o altă clasă. În timp ce clasa originală `Nette\Object` a dispărut odată cu apariția PHP 7.2 (care a interzis denumirea claselor cu cuvântul `Object`), Trait-ul `Nette\SmartObject` trăiește în continuare. -În limbajele moderne orientate pe obiecte (de exemplu, C#, Python, Ruby, JavaScript), termenul *proprietate* se referă la [membrii speciali ai claselor |https://en.wikipedia.org/wiki/Property_(programming)] care seamănă cu variabilele, dar care sunt de fapt reprezentate de metode. Atunci când valoarea acestei "variabile" este atribuită sau citită, se apelează metoda corespunzătoare (numită getter sau setter). Acesta este un lucru foarte util, deoarece ne oferă un control total asupra accesului la variabile. Putem valida intrarea sau genera rezultate numai atunci când proprietatea este citită. +Să trecem în revistă caracteristicile pe care `Nette\Object` și, mai târziu, `Nette\SmartObject` le-au oferit odată. Fiecare dintre aceste funcții, la vremea sa, a reprezentat un pas semnificativ înainte în domeniul programării orientate pe obiecte în PHP. -Proprietățile PHP nu sunt acceptate, dar trait `Nette\SmartObject` le poate imita. Cum se utilizează? -- Adăugați o adnotare la clasă sub forma `@property $xyz` -- Creați un getter numit `getXyz()` sau `isXyz()`, un setter numit `setXyz()` -- Getterul și setterul trebuie să fie *public* sau *protected* și sunt opționale, astfel încât poate exista o proprietate *read-only* sau *write-only*. +Stări de eroare consistente +--------------------------- +Una dintre cele mai stringente probleme ale PHP-ului timpuriu a fost comportamentul inconsistent în lucrul cu obiectele. `Nette\Object` a adus ordine și predictibilitate în acest haos. Să vedem cum arăta comportamentul original al PHP: -Vom utiliza proprietatea pentru clasa Circle pentru a ne asigura că în variabila `$radius` sunt introduse numai numere non-negative. Înlocuiți `public $radius` cu proprietatea: +```php +echo $obj->undeclared; // E_NOTICE, mai târziu E_WARNING +$obj->undeclared = 1; // trece silențios fără raportare +$obj->unknownMethod(); // Eroare fatală (neinterceptabilă cu try/catch) +``` + +Eroarea fatală încheia aplicația fără nicio posibilitate de a reacționa. Scrierea silențioasă în membrii inexistenți fără avertisment putea duce la erori grave, greu de detectat. `Nette\Object` intercepta toate aceste cazuri și arunca excepția `MemberAccessException`, permițând programatorilor să reacționeze la erori și să le rezolve. + +```php +echo $obj->undeclared; // aruncă Nette\MemberAccessException +$obj->undeclared = 1; // aruncă Nette\MemberAccessException +$obj->unknownMethod(); // aruncă Nette\MemberAccessException +``` + +Începând cu PHP 7.0, limbajul nu mai provoacă erori fatale neinterceptabile, iar de la PHP 8.2, accesul la membrii nedeclarați este considerat o eroare. + + +Ajutor "Did you mean?" +---------------------- +`Nette\Object` a venit cu o funcție foarte plăcută: ajutor inteligent pentru greșelile de tipar. Când un dezvoltator făcea o greșeală în numele unei metode sau variabile, nu numai că raporta eroarea, dar oferea și o mână de ajutor sub forma unei sugestii a numelui corect. Acest mesaj iconic, cunoscut sub numele de "did you mean?", a economisit programatorilor ore întregi de căutare a greșelilor de tipar: + +```php +class Foo extends Nette\Object +{ + public static function from($var) + { + } +} + +$foo = Foo::form($var); +// aruncă Nette\MemberAccessException +// "Call to undefined static method Foo::form(), did you mean from()?" +``` + +PHP-ul de astăzi nu are nicio formă de „did you mean?”, dar această adăugire poate fi completată în erori de către [Tracy |tracy:]. Și chiar [corectează automat |tracy:open-files-in-ide#Exemple] astfel de erori. + + +Proprietăți cu acces controlat +------------------------------ +O inovație semnificativă adusă de SmartObject în PHP au fost proprietățile cu acces controlat. Acest concept, comun în limbaje precum C# sau Python, a permis dezvoltatorilor să controleze elegant accesul la datele obiectului și să asigure consistența acestora. Proprietățile sunt un instrument puternic al programării orientate pe obiecte. Funcționează ca variabile, dar în realitate sunt reprezentate de metode (gettere și settere). Acest lucru permite validarea intrărilor sau generarea valorilor doar în momentul citirii. + +Pentru a utiliza proprietățile, trebuie să: +- Adăugați clasei o adnotare de forma `@property $xyz` +- Creați un getter cu numele `getXyz()` sau `isXyz()`, un setter cu numele `setXyz()` +- Asigurați-vă că getter-ul și setter-ul sunt `public` sau `protected`. Sunt opționale - pot exista deci ca proprietăți *read-only* sau *write-only* + +Să vedem un exemplu practic cu clasa `Circle`, unde vom folosi proprietățile pentru a ne asigura că raza este întotdeauna un număr non-negativ. Vom înlocui `public $radius` original cu o proprietate: ```php /** @@ -34,7 +79,7 @@ class Circle { use Nette\SmartObject; - private float $radius = 0.0; // nu sunt publice + private float $radius = 0.0; // nu este public! // getter pentru proprietatea $radius protected function getRadius(): float @@ -45,7 +90,7 @@ class Circle // setter pentru proprietatea $radius protected function setRadius(float $radius): void { - // curățarea valorii înainte de a o salva + // sanitizăm valoarea înainte de salvare $this->radius = max(0.0, $radius); } @@ -57,84 +102,30 @@ class Circle } $circle = new Circle; -$circle->radius = 10; // apelează de fapt setRadius(10) +$circle->radius = 10; // de fapt apelează setRadius(10) echo $circle->radius; // apelează getRadius() -echo $circle->visible; // solicită isVisible() +echo $circle->visible; // apelează isVisible() ``` -Proprietățile sunt în primul rând "zahăr sintactic"((zahăr sintactic)), care are rolul de a face viața programatorului mai ușoară prin simplificarea codului. Dacă nu le doriți, nu trebuie să le folosiți. - - -Clase statice .[#toc-static-classes] -==================================== - -Clasele statice, adică clasele care nu sunt destinate a fi instanțiate, pot fi marcate cu trăsătura `Nette\StaticClass`: +Începând cu PHP 8.4, aceeași funcționalitate poate fi obținută folosind property hooks, care oferă o sintaxă mult mai elegantă și concisă: ```php -class Strings +class Circle { - use Nette\StaticClass; -} -``` - -Atunci când încercați să creați o instanță, se aruncă excepția `Error`, indicând că clasa este statică. - - -O privire în istorie .[#toc-a-look-into-the-history] -==================================================== - -SmartObject obișnuia să îmbunătățească și să corecteze comportamentul clasei în multe feluri, dar evoluția PHP a făcut ca majoritatea caracteristicilor originale să fie redundante. Așadar, în cele ce urmează este o privire în istoria evoluției lucrurilor. - -Încă de la început, modelul de obiecte PHP a suferit de o serie de defecte și ineficiențe grave. Acesta a fost motivul pentru crearea clasei `Nette\Object` (în 2007), care a încercat să le remedieze și să îmbunătățească experiența de utilizare a PHP. A fost suficient pentru ca alte clase să moștenească din ea și să obțină beneficiile pe care le-a adus. Când PHP 5.4 a venit cu suport pentru trăsături, clasa `Nette\Object` a fost înlocuită cu `Nette\SmartObject`. Astfel, nu a mai fost necesară moștenirea de la un strămoș comun. În plus, trait putea fi utilizat în clase care moșteneau deja de la o altă clasă. Sfârșitul final al `Nette\Object` a venit odată cu lansarea PHP 7.2, care a interzis ca clasele să fie denumite `Object`. - -Pe măsură ce dezvoltarea PHP a continuat, modelul de obiecte și capacitățile limbajului au fost îmbunătățite. Funcțiile individuale ale clasei `SmartObject` au devenit redundante. De la lansarea PHP 8.2, singura caracteristică rămasă care nu este încă direct suportată în PHP este capacitatea de a utiliza așa-numitele [proprietăți |#Properties, Getters and Setters]. - -Ce caracteristici ofereau cândva `Nette\Object` și `Nette\Object`? Iată o prezentare generală. (Exemplele folosesc clasa `Nette\Object`, dar majoritatea proprietăților se aplică și la trăsătura `Nette\SmartObject` ). - - -Erori inconsistente .[#toc-inconsistent-errors] ------------------------------------------------ -PHP avea un comportament inconsecvent la accesarea membrilor nedeclarați. Starea la momentul `Nette\Object` era următoarea: - -```php -echo $obj->undeclared; // E_NOTICE, ulterior E_WARNING -$obj->undeclared = 1; // trece în tăcere fără a raporta -$obj->unknownMethod(); // Eroare fatală (care nu poate fi prinsă prin try/catch) -``` - -O eroare fatală a încheiat aplicația fără nicio posibilitate de reacție. Scrierea silențioasă în membri inexistenți fără avertisment putea duce la erori grave, greu de detectat. `Nette\Object` Toate aceste cazuri au fost detectate și a fost lansată o excepție `MemberAccessException`. - -```php -echo $obj->undeclared; // aruncați Nette\MemberAccessException -$obj->undeclared = 1; // throw Nette\MemberAccessException -$obj->unknownMethod(); // throw Nette\MemberAccessException -``` -Începând cu PHP 7.0, PHP nu mai provoacă erori fatale care nu pot fi prinse, iar accesarea membrilor nedeclarați este un bug începând cu PHP 8.2. - - -Ați vrut să spuneți? .[#toc-did-you-mean] ------------------------------------------ -În cazul în care se producea o eroare `Nette\MemberAccessException`, poate din cauza unei greșeli de scriere la accesarea unei variabile de obiect sau la apelarea unei metode, `Nette\Object` încerca să ofere un indiciu în mesajul de eroare cu privire la modul de corectare a erorii, sub forma unui addendum iconic "did you mean?". + public float $radius = 0.0 { + set => max(0.0, $value); + } -```php -class Foo extends Nette\Object -{ - public static function from($var) - { + public bool $visible { + get => $this->radius > 0; } } - -$foo = Foo::form($var); -// throw Nette\MemberAccessException -// "Call to undefined static method Foo::form(), did you mean from()?" ``` -Este posibil ca PHP-ul de astăzi să nu aibă nicio formă de "ai vrut să spui?", dar [Tracy |tracy:] adaugă acest addendum la erori. Și poate chiar să [corecteze |tracy:open-files-in-ide#toc-demos] singur astfel de erori. - -Metode de extensie .[#toc-extension-methods] --------------------------------------------- -Inspirat de metodele de extensie din C#. Acestea au oferit posibilitatea de a adăuga noi metode la clasele existente. De exemplu, ați putea adăuga metoda `addDateTime()` la un formular pentru a adăuga propriul DateTimePicker. +Metode de extensie +------------------ +`Nette\Object` a adus în PHP un alt concept interesant inspirat de limbajele de programare moderne - metodele de extensie. Această funcție, preluată din C#, a permis dezvoltatorilor să extindă elegant clasele existente cu noi metode fără a fi nevoie să le modifice sau să moștenească de la ele. De exemplu, ați putea adăuga la formular o metodă `addDateTime()` care adaugă un `DateTimePicker` personalizat: ```php Form::extensionMethod( @@ -146,22 +137,22 @@ $form = new Form; $form->addDateTime('date'); ``` -Metodele de extensie s-au dovedit a fi nepractice deoarece numele lor nu era completat automat de către editori, în schimb aceștia raportau că metoda nu există. Prin urmare, suportul lor a fost întrerupt. +Metodele de extensie s-au dovedit a fi nepractice, deoarece numele lor nu erau sugerate de editori, dimpotrivă, raportau că metoda nu există. De aceea, suportul lor a fost întrerupt. Astăzi, este mai comun să se utilizeze compoziția sau moștenirea pentru extinderea funcționalității claselor. -Obținerea numelui clasei .[#toc-getting-the-class-name] -------------------------------------------------------- +Obținerea numelui clasei +------------------------ +Pentru a obține numele clasei, SmartObject oferea o metodă simplă: ```php $class = $obj->getClass(); // folosind Nette\Object -$class = $obj::class; // din PHP 8.0 +$class = $obj::class; // de la PHP 8.0 ``` -Acces la reflecție și adnotări .[#toc-access-to-reflection-and-annotations] ---------------------------------------------------------------------------- - -`Nette\Object` a oferit acces la reflectare și adnotare prin intermediul metodelor `getReflection()` și `getAnnotation()`: +Acces la reflecție și adnotări +------------------------------ +`Nette\Object` oferea acces la reflecție și adnotări prin metodele `getReflection()` și `getAnnotation()`. Acest acces a simplificat semnificativ lucrul cu metainformațiile claselor: ```php /** @@ -173,10 +164,10 @@ class Foo extends Nette\Object $obj = new Foo; $reflection = $obj->getReflection(); -$reflection->getAnnotation('author'); // returnează "John Doe +$reflection->getAnnotation('author'); // returnează 'John Doe' ``` -Începând cu PHP 8.0, este posibilă accesarea meta-informațiilor sub formă de atribute: +Începând cu PHP 8.0, este posibil să accesați metainformații sub formă de atribute, care oferă și mai multe posibilități și un control mai bun al tipurilor: ```php #[Author('John Doe')] @@ -190,10 +181,9 @@ $reflection->getAttributes(Author::class)[0]; ``` -Metode Getters .[#toc-method-getters] -------------------------------------- - -`Nette\Object` a oferit o modalitate elegantă de a trata metodele ca și cum ar fi fost variabile: +Getter-e de metode +------------------ +`Nette\Object` oferea o modalitate elegantă de a transmite metode ca și cum ar fi variabile: ```php class Foo extends Nette\Object @@ -209,7 +199,7 @@ $method = $obj->adder; echo $method(2, 3); // 5 ``` -Începând cu PHP 8.1, puteți utiliza așa-numita "sintaxă de primă clasă pentru metode apelabile":https://www.php.net/manual/en/functions.first_class_callable_syntax: +Începând cu PHP 8.1, este posibil să utilizați așa-numita "first-class callable syntax":https://www.php.net/manual/en/functions.first_class_callable_syntax, care duce acest concept și mai departe: ```php $obj = new Foo; @@ -218,10 +208,9 @@ echo $method(2, 3); // 5 ``` -Evenimente .[#toc-events] -------------------------- - -`Nette\Object` a oferit zahăr sintactic pentru a declanșa [evenimentul |nette:glossary#events]: +Evenimente +---------- +SmartObject oferă o sintaxă simplificată pentru lucrul cu [evenimente |nette:glossary#Evenimente]. Evenimentele permit obiectelor să informeze alte părți ale aplicației despre schimbările stării lor: ```php class Circle extends Nette\Object @@ -231,12 +220,12 @@ class Circle extends Nette\Object public function setRadius(float $radius): void { $this->onChange($this, $radius); - $this->radius = $radius + $this->radius = $radius; } } ``` -Codul `$this->onChange($this, $radius)` este echivalent cu următoarele: +Codul `$this->onChange($this, $radius)` este echivalent cu următorul ciclu: ```php foreach ($this->onChange as $callback) { @@ -244,7 +233,7 @@ foreach ($this->onChange as $callback) { } ``` -Din motive de claritate, vă recomandăm să evitați metoda magică `$this->onChange()`. Un substitut practic este funcția [Nette\Utils\Arrays::invoke |arrays#invoke]: +Din motive de claritate, recomandăm evitarea metodei magice `$this->onChange()`. Un înlocuitor practic este, de exemplu, funcția [Nette\Utils\Arrays::invoke |arrays#invoke]: ```php Nette\Utils\Arrays::invoke($this->onChange, $this, $radius); diff --git a/utils/ro/staticclass.texy b/utils/ro/staticclass.texy new file mode 100644 index 0000000000..6167f124a5 --- /dev/null +++ b/utils/ro/staticclass.texy @@ -0,0 +1,21 @@ +Clase statice +************* + +.[perex] +StaticClass servește pentru marcarea claselor statice. + + +Instalare: + +```shell +composer require nette/utils +``` + +Clasele statice, adică clasele care nu sunt destinate creării de instanțe, pot fi marcate cu Trait-ul [api:Nette\StaticClass]: + +```php +class Strings +{ + use Nette\StaticClass; +} +``` diff --git a/utils/ro/strings.texy b/utils/ro/strings.texy index 2f57b83871..d36a16ca0d 100644 --- a/utils/ro/strings.texy +++ b/utils/ro/strings.texy @@ -1,8 +1,8 @@ -Funcții de șiruri de caractere -****************************** +Lucrul cu șiruri de caractere +***************************** .[perex] -[api:Nette\Utils\Strings] este o clasă statică, care conține multe funcții utile pentru lucrul cu șiruri de caractere codificate UTF-8. +[api:Nette\Utils\Strings] este o clasă statică cu funcții utile pentru lucrul cu șiruri de caractere, în principal în codificarea UTF-8. Instalare: @@ -11,15 +11,15 @@ Instalare: composer require nette/utils ``` -Toate exemplele presupun că este definit următorul alias de clasă: +Toate exemplele presupun crearea unui alias: ```php use Nette\Utils\Strings; ``` -Litere Cazul literelor .[#toc-letter-case] -========================================== +Modificarea majusculelor/minusculelor +===================================== Aceste funcții necesită extensia PHP `mbstring`. @@ -27,67 +27,67 @@ Aceste funcții necesită extensia PHP `mbstring`. lower(string $s): string .[method] ---------------------------------- -Convertește toate caracterele din șirul UTF-8 în minuscule. +Convertește un șir UTF-8 în litere mici. ```php -Strings::lower('Hello world'); // 'hello world' +Strings::lower('Bună ziua'); // 'bună ziua' ``` upper(string $s): string .[method] ---------------------------------- -Convertește toate caracterele unui șir UTF-8 în majuscule. +Convertește un șir UTF-8 în litere mari. ```php -Strings::upper('Hello world'); // 'HELLO WORLD' +Strings::upper('Bună ziua'); // 'BUNĂ ZIUA' ``` firstUpper(string $s): string .[method] --------------------------------------- -Convertește primul caracter al unui șir UTF-8 în majuscule și lasă celelalte caractere neschimbate. +Convertește prima literă a unui șir UTF-8 în majusculă, restul rămânând neschimbate. ```php -Strings::firstUpper('hello world'); // 'Hello world' +Strings::firstUpper('bună ziua'); // 'Bună ziua' ``` firstLower(string $s): string .[method] --------------------------------------- -Convertește primul caracter al unui șir UTF-8 în minuscule și lasă celelalte caractere neschimbate. +Convertește prima literă a unui șir UTF-8 în minusculă, restul rămânând neschimbate. ```php -Strings::firstLower('Hello world'); // 'hello world' +Strings::firstLower('Bună ziua'); // 'bună ziua' ``` capitalize(string $s): string .[method] --------------------------------------- -Convertește primul caracter al fiecărui cuvânt dintr-un șir UTF-8 în majuscule și celelalte caractere în minuscule. +Convertește prima literă a fiecărui cuvânt dintr-un șir UTF-8 în majusculă, restul în minuscule. ```php -Strings::capitalize('Hello world'); // 'Hello World' +Strings::capitalize('bună ziua'); // 'Bună Ziua' ``` -Editarea unui șir de caractere .[#toc-editing-a-string] -======================================================= +Modificarea șirului +=================== normalize(string $s): string .[method] -------------------------------------- -Elimină caracterele de control, normalizează pauzele de linie la `\n`, elimină liniile goale de început și de sfârșit de linie, taie spațiile de sfârșit de linie, normalizează UTF-8 la forma normală a NFC. +Elimină caracterele de control, normalizează sfârșiturile de linie la `\n`, elimină liniile goale de la început și sfârșit, elimină spațiile de la sfârșitul liniilor, normalizează UTF-8 la forma normală NFC. unixNewLines(string $s): string .[method] ----------------------------------------- -Convertește întreruperile de linie în `\n` utilizate pe sistemele Unix. Pauzele de linie sunt: `\n`, `\r`, `\r\n`, U+2028 separator de linie, U+2029 separator de paragraf. +Convertește sfârșiturile de linie la `\n` utilizate în sistemele Unix. Sfârșiturile de linie sunt: `\n`, `\r`, `\r\n`, U+2028 separator de linie, U+2029 separator de paragraf. ```php $unixLikeLines = Strings::unixNewLines($string); @@ -97,66 +97,66 @@ $unixLikeLines = Strings::unixNewLines($string); platformNewLines(string $s): string .[method] --------------------------------------------- -Convertește întreruperile de linie în caractere specifice platformei curente, adică `\r\n` pe Windows și `\n` în altă parte. Întreruperile de linie sunt `\n`, `\r`, `\r\n`, U+2028 separator de linie, U+2029 separator de paragraf. +Convertește sfârșiturile de linie la caracterele specifice platformei curente, adică `\r\n` pe Windows și `\n` în altă parte. Sfârșiturile de linie sunt: `\n`, `\r`, `\r\n`, U+2028 separator de linie, U+2029 separator de paragraf. ```php $platformLines = Strings::platformNewLines($string); ``` -webalize(string $s, string $charlist=null, bool $lower=true): string .[method] ------------------------------------------------------------------------------- +webalize(string $s, ?string $charlist=null, bool $lower=true): string .[method] +------------------------------------------------------------------------------- -Modifică șirul UTF-8 în forma utilizată în URL, adică elimină diacriticele și înlocuiește toate caracterele, cu excepția literelor din alfabetul englez și a numerelor, cu o cratimă. +Modifică un șir UTF-8 în forma utilizată în URL-uri, adică elimină diacriticele și înlocuiește toate caracterele, cu excepția literelor alfabetului englez și a cifrelor, cu cratime. ```php -Strings::webalize('žluťoučký kůň'); // 'zlutoucky-kun' +Strings::webalize('produsul nostru'); // 'produsul-nostru' ``` -Pot fi păstrate și alte caractere, dar acestea trebuie să fie trecute ca al doilea argument. +Dacă trebuie păstrate și alte caractere, acestea pot fi specificate în al doilea parametru al funcției. ```php -Strings::webalize('10. image_id', '._'); // '10.-image_id' +Strings::webalize('10. imagine_id', '._'); // '10.-imagine_id' ``` -Cel de-al treilea argument poate suprima convertirea șirului în minuscule. +Cu al treilea parametru, conversia la litere mici poate fi suprimată. ```php -Strings::webalize('Hello world', null, false); // 'Hello-world' +Strings::webalize('Bună ziua', null, false); // 'Buna-ziua' ``` .[caution] Necesită extensia PHP `intl`. -trim(string $s, string $charlist=null): string .[method] --------------------------------------------------------- +trim(string $s, ?string $charlist=null): string .[method] +--------------------------------------------------------- -Îndepărtează toate spațiile din stânga și din dreapta (sau caracterele trecute ca al doilea argument) dintr-un șir de caractere codificat UTF-8. +Elimină spațiile (sau alte caractere specificate de al doilea parametru) de la începutul și sfârșitul unui șir UTF-8. ```php -Strings::trim(' Hello '); // 'Hello' +Strings::trim(' Salut '); // 'Salut' ``` truncate(string $s, int $maxLen, string $append=`'…'`): string .[method] ------------------------------------------------------------------------ -Trunchiază un șir de caractere UTF-8 la o lungime maximă dată, încercând în același timp să nu despartă cuvinte întregi. Numai dacă șirul este trunchiat, o elipsă (sau altceva stabilit cu al treilea argument) este adăugată la șir. +Trunchiază un șir UTF-8 la lungimea maximă specificată, încercând în același timp să păstreze cuvintele întregi. Dacă șirul este scurtat, adaugă puncte de suspensie la sfârșit (poate fi schimbat cu al treilea parametru). ```php -$text = 'Hello, how are you today?'; -Strings::truncate($text, 5); // 'Hell…' -Strings::truncate($text, 20); // 'Hello, how are you…' -Strings::truncate($text, 30); // 'Hello, how are you today?' -Strings::truncate($text, 20, '~'); // 'Hello, how are you~' +$text = 'Spuneți-mi, ce mai faceți?'; +Strings::truncate($text, 5); // 'Spune…' +Strings::truncate($text, 20); // 'Spuneți-mi, ce mai…' +Strings::truncate($text, 30); // 'Spuneți-mi, ce mai faceți?' +Strings::truncate($text, 20, '~'); // 'Spuneți-mi, ce mai~' ``` indent(string $s, int $level=1, string $indentationChar=`"\t"`): string .[method] --------------------------------------------------------------------------------- -Indentează un text multiliniar din stânga. Al doilea argument stabilește câte caractere de indentare trebuie folosite, în timp ce indentarea însăși este al treilea argument (*tab* în mod implicit). +Indentează textul multi-linie de la stânga. Numărul de indentări este specificat de al doilea parametru, iar caracterul de indentare de al treilea parametru (valoarea implicită este tabulator). ```php Strings::indent('Nette'); // "\tNette" @@ -167,7 +167,7 @@ Strings::indent('Nette', 2, '+'); // '++Nette' padLeft(string $s, int $length, string $pad=`' '`): string .[method] -------------------------------------------------------------------- -Adună un șir UTF-8 la o lungime dată prin adăugarea la început a șirului `$pad`. +Completează un șir UTF-8 la lungimea specificată prin repetarea șirului `$pad` la stânga. ```php Strings::padLeft('Nette', 6); // ' Nette' @@ -178,7 +178,7 @@ Strings::padLeft('Nette', 8, '+*'); // '+*+Nette' padRight(string $s, int $length, string $pad=`' '`): string .[method] --------------------------------------------------------------------- -Adaugă un șir UTF-8 la o lungime dată prin adăugarea la sfârșit a șirului `$pad`. +Completează un șir UTF-8 la lungimea specificată prin repetarea șirului `$pad` la dreapta. ```php Strings::padRight('Nette', 6); // 'Nette ' @@ -186,10 +186,10 @@ Strings::padRight('Nette', 8, '+*'); // 'Nette+*+' ``` -substring(string $s, int $start, int $length=null): string .[method] --------------------------------------------------------------------- +substring(string $s, int $start, ?int $length=null): string .[method] +--------------------------------------------------------------------- -Returnează o parte din șirul UTF-8 specificat prin poziția de pornire `$start` și lungimea `$length`. Dacă `$start` este negativ, șirul returnat va începe la al `$start`-lea caracter de la sfârșitul șirului. +Returnează o parte a șirului UTF-8 `$s` specificată de poziția de start `$start` și lungimea `$length`. Dacă `$start` este negativ, șirul returnat va începe cu al `-`$start`-lea caracter de la sfârșit. ```php Strings::substring('Nette Framework', 0, 5); // 'Nette' @@ -201,7 +201,7 @@ Strings::substring('Nette Framework', -4); // 'work' reverse(string $s): string .[method] ------------------------------------ -Inversează șirul UTF-8. +Inversează un șir UTF-8. ```php Strings::reverse('Nette'); // 'etteN' @@ -211,44 +211,44 @@ Strings::reverse('Nette'); // 'etteN' length(string $s): int .[method] -------------------------------- -Returnează numărul de caractere (nu de octeți) din șirul UTF-8. +Returnează numărul de caractere (nu de octeți) dintr-un șir UTF-8. -Acesta este numărul de puncte de cod Unicode, care poate fi diferit de numărul de grafeme. +Acesta este numărul de puncte de cod Unicode, care poate diferi de numărul de grafeme. ```php -Strings::length('Nette'); // 5 -Strings::length('red'); // 3 +Strings::length('Nette'); // 5 +Strings::length('șapte'); // 5 ``` startsWith(string $haystack, string $needle): bool .[method deprecated] ----------------------------------------------------------------------- -Verifică dacă șirul `$haystack` începe cu `$needle`. +Verifică dacă șirul `$haystack` începe cu șirul `$needle`. ```php -$haystack = 'Begins'; -$needle = 'Be'; +$haystack = 'Începe'; +$needle = 'În'; Strings::startsWith($haystack, $needle); // true ``` .[note] -Utilizează codul nativ `str_starts_with()`:https://www.php.net/manual/en/function.str-starts-with.php. +Utilizați funcția nativă `str_starts_with()`:https://www.php.net/manual/en/function.str-starts-with.php. endsWith(string $haystack, string $needle): bool .[method deprecated] --------------------------------------------------------------------- -Verifică dacă șirul `$haystack` se termină cu `$needle`. +Verifică dacă șirul `$haystack` se termină cu șirul `$needle`. ```php -$haystack = 'Ends'; -$needle = 'ds'; +$haystack = 'Termină'; +$needle = 'ină'; Strings::endsWith($haystack, $needle); // true ``` .[note] -Folosește codul nativ `str_ends_with()`:https://www.php.net/manual/en/function.str-ends-with.php. +Utilizați funcția nativă `str_ends_with()`:https://www.php.net/manual/en/function.str-ends-with.php. contains(string $haystack, string $needle): bool .[method deprecated] @@ -257,69 +257,69 @@ contains(string $haystack, string $needle): bool .[method deprecated] Verifică dacă șirul `$haystack` conține `$needle`. ```php -$haystack = 'Contains'; -$needle = 'tai'; +$haystack = 'Auditoriu'; +$needle = 'dito'; Strings::contains($haystack, $needle); // true ``` .[note] -Utilizează codul nativ `str_contains()`:https://www.php.net/manual/en/function.str-contains.php. +Utilizați funcția nativă `str_contains()`:https://www.php.net/manual/en/function.str-contains.php. -compare(string $left, string $right, int $length=null): bool .[method] ----------------------------------------------------------------------- +compare(string $left, string $right, ?int $length=null): bool .[method] +----------------------------------------------------------------------- -Compară două șiruri UTF-8 sau părți ale acestora, fără a lua în considerare cazul caracterelor. Dacă `$length` este nul, se compară șiruri întregi, dacă este negativ, se compară numărul corespunzător de caractere de la sfârșitul șirurilor, altfel se compară numărul corespunzător de caractere de la început. +Compară două șiruri UTF-8 sau părți ale acestora, ignorând majusculele/minusculele. Dacă `$length` este null, se compară șirurile întregi; dacă este negativ, se compară numărul corespunzător de caractere de la sfârșitul șirurilor; altfel, se compară numărul corespunzător de caractere de la început. ```php Strings::compare('Nette', 'nette'); // true -Strings::compare('Nette', 'next', 2); // true - two first characters match -Strings::compare('Nette', 'Latte', -2); // true - two last characters match +Strings::compare('Nette', 'next', 2); // true - potrivire primele 2 caractere +Strings::compare('Nette', 'Latte', -2); // true - potrivire ultimele 2 caractere ``` findPrefix(...$strings): string .[method] ----------------------------------------- -Găsește prefixul comun al șirurilor de caractere sau returnează un șir gol dacă prefixul nu a fost găsit. +Găsește prefixul comun al șirurilor. Sau returnează un șir gol dacă nu a fost găsit niciun prefix comun. ```php Strings::findPrefix('prefix-a', 'prefix-bb', 'prefix-c'); // 'prefix-' Strings::findPrefix(['prefix-a', 'prefix-bb', 'prefix-c']); // 'prefix-' -Strings::findPrefix('Nette', 'is', 'great'); // '' +Strings::findPrefix('Nette', 'este', 'grozav'); // '' ``` before(string $haystack, string $needle, int $nth=1): ?string .[method] ----------------------------------------------------------------------- -Returnează o parte din `$haystack` înainte de `$nth` apariția lui `$needle` sau returnează `null` dacă acul nu a fost găsit. Valoarea negativă înseamnă că se caută de la sfârșit. +Returnează partea șirului `$haystack` dinaintea celei de-a `$nth`-a apariții a șirului `$needle`. Sau `null` dacă `$needle` nu a fost găsit. Cu o valoare negativă pentru `$nth`, căutarea se face de la sfârșitul șirului. ```php -Strings::before('Nette_is_great', '_', 1); // 'Nette' -Strings::before('Nette_is_great', '_', -2); // 'Nette' -Strings::before('Nette_is_great', ' '); // null -Strings::before('Nette_is_great', '_', 3); // null +Strings::before('Nette_este_grozav', '_', 1); // 'Nette' +Strings::before('Nette_este_grozav', '_', -2); // 'Nette' +Strings::before('Nette_este_grozav', ' '); // null +Strings::before('Nette_este_grozav', '_', 3); // null ``` after(string $haystack, string $needle, int $nth=1): ?string .[method] ---------------------------------------------------------------------- -Returnează o parte din `$haystack` după ce `$nth` apare în `$needle` sau returnează `null` dacă `$needle` nu a fost găsit. Valoarea negativă a `$nth` înseamnă că se caută de la sfârșit. +Returnează partea șirului `$haystack` de după a `$nth`-a apariție a șirului `$needle`. Sau `null` dacă `$needle` nu a fost găsit. Cu o valoare negativă pentru `$nth`, căutarea se face de la sfârșitul șirului. ```php -Strings::after('Nette_is_great', '_', 2); // 'great' -Strings::after('Nette_is_great', '_', -1); // 'great' -Strings::after('Nette_is_great', ' '); // null -Strings::after('Nette_is_great', '_', 3); // null +Strings::after('Nette_este_grozav', '_', 2); // 'grozav' +Strings::after('Nette_este_grozav', '_', -1); // 'grozav' +Strings::after('Nette_este_grozav', ' '); // null +Strings::after('Nette_este_grozav', '_', 3); // null ``` indexOf(string $haystack, string $needle, int $nth=1): ?int .[method] --------------------------------------------------------------------- -Returnează poziția în caractere a `$nth` apariției `$needle` în `$haystack` sau `null` dacă `$needle` nu a fost găsit. Valoarea negativă a `$nth` înseamnă că se caută de la sfârșit. +Returnează poziția în caractere a celei de-a `$nth`-a apariții a șirului `$needle` în șirul `$haystack`. Sau `null` dacă `$needle` nu a fost găsit. Cu o valoare negativă pentru `$nth`, căutarea se face de la sfârșitul șirului. ```php Strings::indexOf('abc abc abc', 'abc', 2); // 4 @@ -328,14 +328,14 @@ Strings::indexOf('abc abc abc', 'd'); // null ``` -Codificarea .[#toc-encoding] -============================ +Codificare +========== fixEncoding(string $s): string .[method] ---------------------------------------- -Îndepărtează toate caracterele UTF-8 nevalabile dintr-un șir de caractere. +Elimină caracterele UTF-8 invalide din șir. ```php $correctStrings = Strings::fixEncoding($string); @@ -345,7 +345,7 @@ $correctStrings = Strings::fixEncoding($string); checkEncoding(string $s): bool .[method deprecated] --------------------------------------------------- -Verifică dacă șirul este valid în codificarea UTF-8. +Verifică dacă este un șir UTF-8 valid. ```php $isUtf8 = Strings::checkEncoding($string); @@ -358,10 +358,10 @@ Utilizați [Nette\Utils\Validator::isUnicode() |validators#isUnicode]. toAscii(string $s): string .[method] ------------------------------------ -Convertește șirul UTF-8 în ASCII, adică elimină diacriticele etc. +Convertește un șir UTF-8 în ASCII, adică elimină diacriticele etc. ```php -Strings::toAscii('žluťoučký kůň'); // 'zlutoucky kun' +Strings::toAscii('căluțul galben'); // 'calutul galben' ``` .[caution] @@ -371,212 +371,222 @@ Necesită extensia PHP `intl`. chr(int $code): string .[method] -------------------------------- -Returnează un anumit caracter în UTF-8 din punctul de cod (număr în intervalul 0x0000..D7FF sau 0xE000..10FFFF). +Returnează un caracter specific în UTF-8 dintr-un punct de cod (număr în intervalul 0x0000..D7FF și 0xE000..10FFFF). ```php -Strings::chr(0xA9); // '©' +Strings::chr(0xA9); // '©' în codificare UTF-8 ``` ord(string $char): int .[method] -------------------------------- -Returnează un punct de cod al unui anumit caracter în UTF-8 (număr în intervalul 0x0000..D7FF sau 0xE000..10FFFF). +Returnează punctul de cod al unui caracter specific în UTF-8 (număr în intervalul 0x0000..D7FF sau 0xE000..10FFFF). ```php Strings::ord('©'); // 0xA9 ``` -Expresii regulate .[#toc-regular-expressions] -============================================= +Expresii regulate +================= -Clasa Strings oferă funcții pentru lucrul cu expresiile regulate. Spre deosebire de funcțiile PHP native, acestea au o API mai ușor de înțeles, un suport Unicode mai bun și, cel mai important, detectarea erorilor. Orice eroare de compilare sau de procesare a expresiilor va arunca o excepție `Nette\RegexpException`. +Clasa `Strings` oferă funcții pentru lucrul cu expresii regulate. Spre deosebire de funcțiile native PHP, acestea au un API mai inteligibil, suport Unicode mai bun și, mai presus de toate, detectarea erorilor. Orice eroare la compilarea sau procesarea expresiei aruncă o excepție `Nette\RegexpException`. split(string $subject, string $pattern, bool $captureOffset=false, bool $skipEmpty=false, int $limit=-1, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------- -Împarte șirul de caractere în matrice în conformitate cu expresia regulată. Expresiile din paranteze vor fi, de asemenea, capturate și returnate. +Împarte un șir într-un array conform unei expresii regulate. Expresiile din paranteze vor fi, de asemenea, capturate și returnate. ```php -Strings::split('hello, world', '~,\s*~'); -// ['hello', 'world'] +Strings::split('salut, lume', '~,\s*~'); +// ['salut', 'lume'] -Strings::split('hello, world', '~(,)\s*~'); -// ['hello', ',', 'world']`` +Strings::split('salut, lume', '~(,)\s*~'); +// ['salut', ',', 'lume'] ``` -Dacă `$skipEmpty` este `true`, vor fi returnate numai elementele care nu sunt goale: +Dacă `$skipEmpty` este `true`, vor fi returnate doar elementele ne-goale: ```php -Strings::split('hello, world, ', '~,\s*~'); -// ['hello', 'world', ''] +Strings::split('salut, lume, ', '~,\s*~'); +// ['salut', 'lume', ''] -Strings::split('hello, world, ', '~,\s*~', skipEmpty: true); -// ['hello', 'world'] +Strings::split('salut, lume, ', '~,\s*~', skipEmpty: true); +// ['salut', 'lume'] ``` -Dacă este specificat `$limit`, vor fi returnate numai subșirurile până la limită, iar restul șirului va fi plasat în ultimul element. O limită de -1 sau 0 înseamnă că nu există limită. +Dacă este specificat `$limit`, vor fi returnate doar subșirurile până la limită, iar restul șirului va fi plasat în ultimul element. O limită de -1 sau 0 înseamnă nicio restricție. ```php -Strings::split('hello, world, third', '~,\s*~', limit: 2); -// ['hello', 'world, third'] +Strings::split('salut, lume, al treilea', '~,\s*~', limit: 2); +// ['salut', 'lume, al treilea'] ``` -Dacă `$utf8` este `true`, evaluarea trece la modul Unicode. Acest lucru este similar cu specificarea modificatorului `u`. +Dacă `$utf8` este `true`, evaluarea trece în modul Unicode. Similar cu specificarea modificatorului `u`. -Dacă `$captureOffset` este `true`, pentru fiecare potrivire care apare, se va returna și poziția acesteia în șir (în octeți; în caractere dacă este setat `$utf8` ). Acest lucru schimbă valoarea de returnare într-o matrice în care fiecare element este o pereche formată din șirul de caractere care corespunde și poziția acestuia. +Dacă `$captureOffset` este `true`, pentru fiecare potrivire găsită va fi returnată și poziția sa în șir (în octeți; dacă `$utf8` este setat, atunci în caractere). Acest lucru schimbă valoarea returnată într-un array în care fiecare element este o pereche formată din șirul potrivit și poziția sa. ```php -Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true); -// [['žlutý', 0], ['kůň', 9]] +Strings::split('galben, cal', '~,\s*~', captureOffset: true); +// [['galben', 0], ['cal', 8]] -Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true, utf8: true); -// [['žlutý', 0], ['kůň', 7]] +Strings::split('galben, cal', '~,\s*~', captureOffset: true, utf8: true); +// [['galben', 0], ['cal', 7]] ``` match(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $utf8=false): ?array .[method] -------------------------------------------------------------------------------------------------------------------------------------------------- -Caută în șirul de caractere partea care corespunde expresiei regulate și returnează un tablou cu expresia găsită și subexpresiile individuale sau `null`. +Caută în șir o parte care corespunde expresiei regulate și returnează un array cu expresia găsită și subexpresiile individuale, sau `null`. ```php -Strings::match('hello!', '~\w+(!+)~'); -// ['hello!', '!'] +Strings::match('salut!', '~\w+(!+)~'); +// ['salut!', '!'] -Strings::match('hello!', '~X~'); +Strings::match('salut!', '~X~'); // null ``` -Dacă `$unmatchedAsNull` este `true`, submodelele nepotrivite sunt returnate ca fiind nule; în caz contrar, acestea sunt returnate ca un șir gol sau nu sunt returnate: +Dacă `$unmatchedAsNull` este `true`, submodelele necapturate sunt returnate ca `null`; altfel, sunt returnate ca șir gol sau nu sunt returnate: ```php -Strings::match('hello', '~\w+(!+)?~'); -// ['hello'] +Strings::match('salut', '~\w+(!+)?~'); +// ['salut'] -Strings::match('hello', '~\w+(!+)?~', unmatchedAsNull: true); -// ['hello', null] +Strings::match('salut', '~\w+(!+)?~', unmatchedAsNull: true); +// ['salut', null] ``` -În cazul în care `$utf8` este `true`, evaluarea trece la modul Unicode. Acest lucru este similar cu specificarea modificatorului `u`: +Dacă `$utf8` este `true`, evaluarea trece în modul Unicode. Similar cu specificarea modificatorului `u`: ```php -Strings::match('žlutý kůň', '~\w+~'); -// ['lut'] +Strings::match('cal galben', '~\w+~'); +// ['cal'] -Strings::match('žlutý kůň', '~\w+~', utf8: true); -// ['žlutý'] +Strings::match('cal galben', '~\w+~', utf8: true); +// ['cal'] ``` -Parametrul `$offset` poate fi utilizat pentru a specifica poziția de la care se începe căutarea (în octeți; în caractere dacă este setat `$utf8` ). +Parametrul `$offset` poate fi utilizat pentru a specifica poziția de la care să înceapă căutarea (în octeți; dacă `$utf8` este setat, atunci în caractere). -Dacă `$captureOffset` este `true`, pentru fiecare potrivire care apare, se va returna și poziția acesteia în șirul de caractere (în octeți; în caractere, dacă este setat `$utf8` ). Astfel, valoarea returnată se transformă într-o matrice în care fiecare element este o pereche formată din șirul de caractere care corespunde și decalajul acestuia: +Dacă `$captureOffset` este `true`, pentru fiecare potrivire găsită va fi returnată și poziția sa în șir (în octeți; dacă `$utf8` este setat, atunci în caractere). Acest lucru schimbă valoarea returnată într-un array în care fiecare element este o pereche formată din șirul potrivit și offset-ul său: ```php -Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true); -// [['lut', 2]] +Strings::match('galben!', '~\w+(!+)?~', captureOffset: true); +// [['galben', 0]] -Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true, utf8: true); -// [['žlutý!', 0], ['!', 5]] +Strings::match('galben!', '~\w+(!+)?~', captureOffset: true, utf8: true); +// [['galben!', 0], ['!', 6]] ``` -matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false, bool $lazy=false): array|Generator .[method] +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Caută în șirul de caractere toate ocurențele care corespund expresiei regulate și returnează o matrice de matrici care conține expresia găsită și fiecare subexpresie. +Caută în șir toate aparițiile care corespund expresiei regulate și returnează un array de array-uri cu expresia găsită și subexpresiile individuale. ```php -Strings::matchAll('hello, world!!', '~\w+(!+)?~'); +Strings::matchAll('salut, lume!!', '~\w+(!+)?~'); /* [ - 0 => ['hello'], - 1 => ['world!!', '!!'], + 0 => ['salut'], + 1 => ['lume!!', '!!'], ] */ ``` -Dacă `$patternOrder` este `true`, structura rezultatelor se modifică astfel încât primul element este un tablou de corespondențe complete ale modelului, al doilea este un tablou de șiruri de caractere care corespund primului submodel între paranteze, și așa mai departe: +Dacă `$patternOrder` este `true`, structura rezultatelor se schimbă astfel încât primul element este un array de potriviri complete ale modelului, al doilea este un array de șiruri care corespund primului submodel din paranteze, și așa mai departe: ```php -Strings::matchAll('hello, world!!', '~\w+(!+)?~', patternOrder: true); +Strings::matchAll('salut, lume!!', '~\w+(!+)?~', patternOrder: true); /* [ - 0 => ['hello', 'world!!'], - 1 => ['', '!!'], + 0 => ['salut', 'lume!!'], + 1 => [null, '!!'], ] */ ``` -În cazul în care `$unmatchedAsNull` este `true`, submodelele nepotrivite sunt returnate ca fiind nule; în caz contrar, acestea sunt returnate ca un șir gol sau nu sunt returnate: +Dacă `$unmatchedAsNull` este `true`, submodelele necapturate sunt returnate ca `null`; altfel, sunt returnate ca șir gol sau nu sunt returnate: ```php -Strings::matchAll('hello, world!!', '~\w+(!+)?~', unmatchedAsNull: true); +Strings::matchAll('salut, lume!!', '~\w+(!+)?~', unmatchedAsNull: true); /* [ - 0 => ['hello', null], - 1 => ['world!!', '!!'], + 0 => ['salut', null], + 1 => ['lume!!', '!!'], ] */ ``` -În cazul în care `$utf8` este `true`, evaluarea trece la modul Unicode. Acest lucru este similar cu specificarea modificatorului `u`: +Dacă `$utf8` este `true`, evaluarea trece în modul Unicode. Similar cu specificarea modificatorului `u`: ```php -Strings::matchAll('žlutý kůň', '~\w+~'); +Strings::matchAll('cal galben', '~\w+~'); /* [ - 0 => ['lut'], - 1 => ['k'], + 0 => ['cal'], + 1 => ['galben'], ] */ -Strings::matchAll('žlutý kůň', '~\w+~', utf8: true); +Strings::matchAll('cal galben', '~\w+~', utf8: true); /* [ - 0 => ['žlutý'], - 1 => ['kůň'], + 0 => ['cal'], + 1 => ['galben'], ] */ ``` -Parametrul `$offset` poate fi utilizat pentru a specifica poziția de la care se începe căutarea (în octeți; în caractere dacă este setat `$utf8` ). +Parametrul `$offset` poate fi utilizat pentru a specifica poziția de la care să înceapă căutarea (în octeți; dacă `$utf8` este setat, atunci în caractere). -Dacă `$captureOffset` este `true`, pentru fiecare potrivire care apare, se va returna și poziția acesteia în șirul de caractere (în octeți; în caractere, dacă este setat `$utf8` ). Astfel, valoarea returnată se transformă într-o matrice în care fiecare element este o pereche formată din șirul de caractere care corespunde și poziția acestuia: +Dacă `$captureOffset` este `true`, pentru fiecare potrivire găsită va fi returnată și poziția sa în șir (în octeți; dacă `$utf8` este setat, atunci în caractere). Acest lucru schimbă valoarea returnată într-un array în care fiecare element este o pereche formată din șirul potrivit și poziția sa: ```php -Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true); +Strings::matchAll('cal galben', '~\w+~', captureOffset: true); /* [ - 0 => [['lut', 2]], - 1 => [['k', 8]], + 0 => [['cal', 0]], + 1 => [['galben', 4]], ] */ -Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true, utf8: true); +Strings::matchAll('cal galben', '~\w+~', captureOffset: true, utf8: true); /* [ - 0 => [['žlutý', 0]], - 1 => [['kůň', 6]], + 0 => [['cal', 0]], + 1 => [['galben', 4]], ] */ ``` +Dacă `$lazy` este `true`, funcția returnează un `Generator` în loc de un array, ceea ce aduce avantaje semnificative de performanță la lucrul cu șiruri mari. Generatorul permite căutarea potrivirilor treptat, în loc de întregul șir deodată. Acest lucru permite lucrul eficient chiar și cu texte de intrare extrem de mari. În plus, puteți întrerupe procesarea oricând dacă găsiți potrivirea căutată, economisind timp de calcul. + +```php +$matches = Strings::matchAll($largeText, '~\w+~', lazy: true); +foreach ($matches as $match) { + echo "Găsit: $match[0]\n"; + // Procesarea poate fi întreruptă oricând +} +``` + replace(string $subject, string|array $pattern, string|callable $replacement='', int $limit=-1, bool $captureOffset=false, bool $unmatchedAsNull=false, bool $utf8=false): string .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Înlocuiește toate aparițiile care corespund expresiei regulate. `$replacement` este fie o mască de șir de caractere de înlocuire, fie un callback. +Înlocuiește toate aparițiile care corespund expresiei regulate. `$replacement` este fie o mască de șir de înlocuire, fie un callback. ```php -Strings::replace('hello, world!', '~\w+~', '--'); +Strings::replace('salut, lume!', '~\w+~', '--'); // '--, --!' -Strings::replace('hello, world!', '~\w+~', fn($m) => strrev($m[0])); -// 'olleh, dlrow!' +Strings::replace('salut, lume!', '~\w+~', fn($m) => strrev($m[0])); +// 'tulas, emul!' ``` -Funcția permite, de asemenea, înlocuiri multiple prin trecerea unui array de forma `pattern => replacement` în al doilea parametru: +Funcția permite, de asemenea, efectuarea mai multor înlocuiri prin transmiterea unui array în al doilea parametru sub forma `pattern => replacement`: ```php -Strings::replace('hello, world!', [ +Strings::replace('salut, lume!', [ '~\w+~' => '--', '~,\s+~' => ' ', ]); // '-- --!' ``` -Parametrul `$limit` limitează numărul de înlocuiri. Limit -1 înseamnă că nu există limită. +Parametrul `$limit` limitează numărul de înlocuiri efectuate. O limită de -1 înseamnă nicio restricție. -Dacă `$utf8` este `true`, evaluarea trece la modul Unicode. Acest lucru este similar cu specificarea modificatorului `u`. +Dacă `$utf8` este `true`, evaluarea trece în modul Unicode. Similar cu specificarea modificatorului `u`. ```php Strings::replace('žlutý kůň', '~\w+~', '--'); @@ -586,7 +596,7 @@ Strings::replace('žlutý kůň', '~\w+~', '--', utf8: true); // '-- --' ``` -Dacă `$captureOffset` este `true`, pentru fiecare potrivire care apare, poziția sa în șir (în octeți; în caractere, dacă este setat `$utf8` ) este, de asemenea, transmisă către callback. Acest lucru modifică forma matricei transmise, în care fiecare element este o pereche formată din șirul de caractere care corespunde și poziția acestuia. +Dacă `$captureOffset` este `true`, pentru fiecare potrivire găsită va fi transmisă callback-ului și poziția sa în șir (în octeți; dacă `$utf8` este setat, atunci în caractere). Acest lucru schimbă forma array-ului transmis, unde fiecare element este o pereche formată din șirul potrivit și poziția sa. ```php Strings::replace( @@ -595,7 +605,7 @@ Strings::replace( function (array $m) { dump($m); return ''; }, captureOffset: true, ); -// dumps [['lut', 2]] a [['k', 8]] +// dumps [['lut', 2]] și [['k', 8]] Strings::replace( 'žlutý kůň', @@ -604,10 +614,10 @@ Strings::replace( captureOffset: true, utf8: true, ); -// dumps [['žlutý', 0]] a [['kůň', 6]] +// dumps [['žlutý', 0]] și [['kůň', 6]] ``` -În cazul în care `$unmatchedAsNull` este `true`, submodelele nepotrivite sunt transmise la callback ca fiind nule; în caz contrar, acestea sunt transmise ca șir gol sau nu sunt transmise: +Dacă `$unmatchedAsNull` este `true`, submodelele necapturate sunt transmise callback-ului ca `null`; altfel, sunt transmise ca șir gol sau nu sunt transmise: ```php Strings::replace( @@ -615,7 +625,7 @@ Strings::replace( '~(a)(b)*(c)~', function (array $m) { dump($m); return ''; }, ); -// dumps ['ac', 'a', '', 'c'] +// afișează ['ac', 'a', '', 'c'] Strings::replace( 'ac', @@ -623,5 +633,5 @@ Strings::replace( function (array $m) { dump($m); return ''; }, unmatchedAsNull: true, ); -// dumps ['ac', 'a', null, 'c'] +// afișează ['ac', 'a', null, 'c'] ``` diff --git a/utils/ro/type.texy b/utils/ro/type.texy index c8e401073a..83c2bd75f6 100644 --- a/utils/ro/type.texy +++ b/utils/ro/type.texy @@ -2,7 +2,7 @@ Tip PHP ******* .[perex] -[api:Nette\Utils\Type] este o clasă de tip de date PHP. +[api:Nette\Utils\Type] este o clasă pentru lucrul cu tipurile de date PHP. Instalare: @@ -11,7 +11,7 @@ Instalare: composer require nette/utils ``` -Toate exemplele presupun că este definit următorul alias de clasă: +Toate exemplele presupun crearea unui alias: ```php use Nette\Utils\Type; @@ -21,7 +21,7 @@ use Nette\Utils\Type; fromReflection($reflection): ?Type .[method] -------------------------------------------- -Metoda statică creează un obiect Type bazat pe reflecție. Parametrul poate fi un obiect `ReflectionMethod` sau `ReflectionFunction` (returnează tipul valorii de returnare) sau un obiect `ReflectionParameter` sau `ReflectionProperty`. Rezolvă `self`, `static` și `parent` cu numele real al clasei. Dacă obiectul nu are un tip, returnează `null`. +Metoda statică creează un obiect `Type` pe baza reflecției. Parametrul poate fi un obiect `ReflectionMethod` sau `ReflectionFunction` (returnează tipul valorii returnate) sau `ReflectionParameter` ori `ReflectionProperty`. Traduce `self`, `static` și `parent` în numele real al clasei. Dacă subiectul nu are niciun tip, returnează `null`. ```php class DemoClass @@ -37,7 +37,7 @@ echo Type::fromReflection($prop); // 'DemoClass' fromString(string $type): Type .[method] ---------------------------------------- -Metoda statică creează obiectul Type în conformitate cu notația text. +Metoda statică creează un obiect `Type` pe baza notației textuale. ```php $type = Type::fromString('Foo|Bar'); @@ -48,10 +48,10 @@ echo $type; // 'Foo|Bar' getNames(): (string|array)[] .[method] -------------------------------------- -Returnează matricea de subtipuri care alcătuiesc tipul compus ca șiruri de caractere. +Returnează un array de subtipuri din care este compus tipul compus, ca șiruri de caractere. ```php -$type = Type::fromString('string|null'); // nebo '?string' +$type = Type::fromString('string|null'); // sau '?string' $type->getNames(); // ['string', 'null'] $type = Type::fromString('(Foo&Bar)|string'); @@ -62,10 +62,10 @@ $type->getNames(); // [['Foo', 'Bar'], 'string'] getTypes(): Type[] .[method] ---------------------------- -Returnează matricea de subtipuri care alcătuiesc tipul compus ca obiecte `Type`: +Returnează un array de subtipuri din care este compus tipul compus, ca obiecte `ReflectionType`: ```php -$type = Type::fromString('string|null'); // or '?string' +$type = Type::fromString('string|null'); // sau '?string' $type->getTypes(); // [Type::fromString('string'), Type::fromString('null')] $type = Type::fromString('(Foo&Bar)|string'); @@ -79,7 +79,7 @@ $type->getTypes(); // [Type::fromString('Foo'), Type::fromString('Bar')] getSingleName(): ?string .[method] ---------------------------------- -Returnează numele tipului pentru tipurile simple, în caz contrar este nul. +Pentru tipurile simple, returnează numele tipului, altfel `null`. ```php $type = Type::fromString('string|null'); @@ -99,14 +99,14 @@ echo $type->getSingleName(); // null isSimple(): bool .[method] -------------------------- -Returnează dacă este un tip simplu. Tipurile simple care pot fi anulate sunt, de asemenea, considerate ca fiind tipuri simple: +Returnează dacă este un tip simplu. Tipurile simple nullable sunt, de asemenea, considerate tipuri simple: ```php $type = Type::fromString('string'); $type->isSimple(); // true $type->isUnion(); // false -$type = Type::fromString('?Foo'); // nebo 'Foo|null' +$type = Type::fromString('?Foo'); // sau 'Foo|null' $type->isSimple(); // true $type->isUnion(); // true ``` @@ -115,10 +115,10 @@ $type->isUnion(); // true isUnion(): bool .[method] ------------------------- -Returnează dacă este un tip de uniune. +Returnează dacă este un tip union. ```php -$type = Type::fromString('Foo&Bar'); +$type = Type::fromString('string|int'); $type->isUnion(); // true ``` @@ -126,11 +126,11 @@ $type->isUnion(); // true isIntersection(): bool .[method] -------------------------------- -Returnează dacă este un tip de intersecție. +Returnează dacă este un tip intersection. ```php -$type = Type::fromString('string&int'); +$type = Type::fromString('Foo&Bar'); $type->isIntersection(); // true ``` @@ -138,7 +138,7 @@ $type->isIntersection(); // true isBuiltin(): bool .[method] --------------------------- -Returnează dacă tipul este atât un tip simplu, cât și un tip încorporat în PHP. +Returnează dacă tipul este simplu și, în același timp, un tip încorporat PHP. ```php $type = Type::fromString('string'); @@ -155,7 +155,7 @@ $type->isBuiltin(); // false isClass(): bool .[method] ------------------------- -Returnează dacă tipul este atât un simplu cât și un nume de clasă. +Returnează dacă tipul este simplu și, în același timp, numele unei clase. ```php $type = Type::fromString('string'); @@ -172,7 +172,7 @@ $type->isClass(); // false isClassKeyword(): bool .[method] -------------------------------- -Determină dacă tipul este unul dintre tipurile interne `self`, `parent`, `static`. +Returnează dacă tipul este unul dintre tipurile interne `self`, `parent`, `static`. ```php $type = Type::fromString('self'); @@ -186,7 +186,7 @@ $type->isClassKeyword(); // false allows(string $type): bool .[method] ------------------------------------ -Metoda `allows()` verifică compatibilitatea tipurilor. De exemplu, aceasta permite să se verifice dacă o valoare de un anumit tip poate fi transmisă ca parametru. +Metoda `allows()` verifică compatibilitatea tipurilor. De exemplu, permite să se determine dacă o valoare de un anumit tip ar putea fi transmisă ca parametru. ```php $type = Type::fromString('string|null'); diff --git a/utils/ro/validators.texy b/utils/ro/validators.texy index 8c2e721b57..f604e30549 100644 --- a/utils/ro/validators.texy +++ b/utils/ro/validators.texy @@ -2,7 +2,7 @@ Validatori de valori ******************** .[perex] -Aveți nevoie să verificați rapid și ușor dacă o variabilă conține, de exemplu, o adresă de e-mail validă? Atunci vă va fi de folos [api:Nette\Utils\Validators], o clasă statică cu funcții utile pentru validarea valorilor. +Aveți nevoie să verificați rapid și simplu dacă o variabilă conține, de exemplu, o adresă de e-mail validă? Atunci vă va fi utilă [api:Nette\Utils\Validators], o clasă statică cu funcții utile pentru validarea valorilor. Instalare: @@ -11,17 +11,17 @@ Instalare: composer require nette/utils ``` -Toate exemplele presupun că este definit următorul alias de clasă: +Toate exemplele presupun crearea unui alias: ```php use Nette\Utils\Validators; ``` -Utilizare de bază .[#toc-basic-usage] -===================================== +Utilizare de bază +================= -Clasa `Validators` are o serie de metode de validare a valorilor, cum ar fi [isList() |#isList()], [isUnicode() |#isUnicode()], [isEmail() |#isEmail()], [isUrl() |#isUrl()] etc., care pot fi utilizate în codul dumneavoastră: +Clasa dispune de o serie de metode pentru verificarea valorilor, cum ar fi [#isUnicode()], [#isEmail()], [#isUrl()] etc., pentru a fi utilizate în codul dumneavoastră: ```php if (!Validators::isEmail($email)) { @@ -29,7 +29,7 @@ if (!Validators::isEmail($email)) { } ``` -În plus, aceasta poate verifica dacă valoarea satisface așa-numitele [tipuri așteptate |#expected types], care este un șir de caractere în care opțiunile individuale sunt separate de o bară verticală `|`. Acest lucru facilitează verificarea tipurilor de uniune folosind [if() |#if()]: +În plus, poate verifica dacă valoarea este unul dintre așa-numitele [#tipuri așteptate], care este un șir în care opțiunile individuale sunt separate prin bară verticală `|`. Putem astfel verifica ușor mai multe tipuri folosind [#is()]: ```php if (!Validators::is($val, 'int|string|bool')) { @@ -37,81 +37,81 @@ if (!Validators::is($val, 'int|string|bool')) { } ``` -Dar vă oferă, de asemenea, posibilitatea de a crea un sistem în care este necesar să scrieți așteptările sub formă de șiruri de caractere (de exemplu, în adnotări sau în configurare) și apoi să verificați în funcție de acestea. +Dar ne oferă și posibilitatea de a crea un sistem în care este necesar să scriem așteptările ca șiruri (de exemplu, în adnotări sau configurație) și apoi să verificăm valorile conform acestora. -De asemenea, puteți declara o [aserțiune |#assert], care aruncă o excepție dacă nu este îndeplinită. +Pentru tipurile așteptate se poate aplica și o cerință [#assert()], care, dacă nu este îndeplinită, aruncă o excepție. -Tipuri de așteptări .[#toc-expected-types] -========================================== +Tipuri așteptate +================ -Un tip așteptat este un șir de caractere format din una sau mai multe variante separate de o bară verticală `|`, similar to writing types in PHP (ie. `'int|string|bool')`. Este permisă, de asemenea, o notație nulă `?int`. +Tipurile așteptate formează un șir compus dintr-una sau mai multe variante separate prin bară verticală `|`, similar modului în care se scriu tipurile în PHP (de ex. `'int|string|bool'`). Se acceptă și notația nullable `?int`. -O matrice în care toate elementele sunt de un anumit tip se scrie sub forma `int[]`. +Un array în care toate elementele sunt de un anumit tip se scrie în forma `int[]`. -Unele tipuri pot fi urmate de două puncte și de lungimea `:length` sau de intervalul `:[min]..[max]`, de exemplu `string:10` (un șir de caractere cu o lungime de 10 octeți), `float:10..` (număr mai mare sau egal cu 10), `array:..10` (matrice de până la zece elemente) sau `list:10..20` (listă cu 10-20 de elemente), sau o expresie regulată pentru `pattern:[0-9]+`. +După unele tipuri poate urma două puncte și lungimea `:length` sau intervalul `:[min]..[max]`, de ex. `string:10` (șir cu lungimea de 10 octeți), `float:10..` (număr 10 și mai mare), `array:..10` (array cu până la zece elemente) sau `list:10..20` (listă cu 10 până la 20 de elemente), sau o expresie regulată la `pattern:[0-9]+`. -Prezentare generală a tipurilor și a regulilor: +Prezentare generală a tipurilor și regulilor: .[wide] -| Tipuri PHP || +| Tipuri PHP || |-------------------------- -| `array` .{width: 140px} | se poate da un interval pentru numărul de elemente -| `bool` | -| `float` | se poate da un interval pentru valoarea -| `int` | se poate indica intervalul pentru valoarea -| `null` | -| `object` | +| `array` .{width: 140px} | se poate specifica un interval pentru numărul de elemente +| `bool` | +| `float` | se poate specifica un interval pentru valoare +| `int` | se poate specifica un interval pentru valoare +| `null` | +| `object` | | `resource` | -| `scalar` | int\|float\|bool\|stringă -| `string` | se poate indica intervalul pentru lungimea în octeți +| `scalar` | int\|float\|bool\|string +| `string` | se poate specifica un interval pentru lungimea în octeți | `callable` | | `iterable` | -| `mixed` | -|------------------------------------------------ +| `mixed` | +|-------------------------- | pseudo-tipuri || |------------------------------------------------ -| `list` | [array indexat |#isList], se poate da un interval pentru numărul de elemente -| `none` | valoare goală: `''`, `null`, `false` -| `number` | int\|float -| `numeric` | [număr care include o reprezentare textuală |#isNumeric] -| `numericint`| [număr întreg, inclusiv reprezentare textuală |#isNumericInt] -| `unicode` | [șir UTF-8 |#isUnicode], se poate da un interval pentru lungimea în caractere -|------------------------------------------------ -| clasa de caractere (nu poate fi un șir gol) || -|------------------------------------------------ -| `alnum` | toate caracterele sunt alfanumerice -| `alpha` | toate caracterele sunt litere `[A-Za-z]` -| `digit` | toate caracterele sunt cifre -| `lower` | toate caracterele sunt litere minuscule `[a-z]` -| `space` | toate caracterele sunt spații -| `upper` | toate caracterele sunt litere majuscule `[A-Z]` -| `xdigit` | toate caracterele sunt cifre hexazecimale `[0-9A-Fa-f]` -|------------------------------------------------ -| validarea sintaxei || +| `list` | array indexat, se poate specifica un interval pentru numărul de elemente +| `none` | valoare goală: `''`, `null`, `false` +| `number` | int\|float +| `numeric` | [număr inclusiv reprezentarea textuală |#isNumeric] +| `numericint`| [număr întreg inclusiv reprezentarea textuală |#isNumericInt] +| `unicode` | [șir UTF-8 |#isUnicode], se poate specifica un interval pentru lungimea în caractere +|-------------------------- +| clasă de caractere (nu poate fi șir gol) || |------------------------------------------------ -| `pattern` | o expresie regulată cu care trebuie să se potrivească **întregul** șir de caractere -| `email` | [Email |#isEmail] -| `identifier`| [Identificatorul PHP |#isPhpIdentifier] -| `url` | [URL |#isUrl] -| `uri` | [URI |#isUri] +| `alnum` | toate caracterele sunt alfanumerice +| `alpha` | toate caracterele sunt litere `[A-Za-z]` +| `digit` | toate caracterele sunt cifre +| `lower` | toate caracterele sunt litere mici `[a-z]` +| `space` | toate caracterele sunt spații +| `upper` | toate caracterele sunt litere mari `[A-Z]` +| `xdigit` | toate caracterele sunt cifre hexazecimale `[0-9A-Fa-f]` +|-------------------------- +| verificare sintaxă || |------------------------------------------------ -| validarea mediului || +| `pattern` | expresie regulată căreia trebuie să-i corespundă **întregul** șir +| `email` | [E-mail |#isEmail] +| `identifier`| [identificator PHP |#isPhpIdentifier] +| `url` | [URL |#isUrl] +| `uri` | [URI |#isUri] +|-------------------------- +| verificare mediu || |------------------------------------------------ -| `class` | este o clasă existentă +| `class` | este o clasă existentă | `interface` | este o interfață existentă | `directory` | este un director existent -| `file` | este un fișier existent +| `file` | este un fișier existent -Afirmație .[#toc-assertion] -=========================== +Aserțiuni +========= assert($value, string $expected, string $label='variable'): void .[method] -------------------------------------------------------------------------- -Verifică dacă valoarea este de [tipurile așteptate |#expected types], separate prin pipe. În caz contrar, se aruncă o excepție [api:Nette\Utils\AssertionException]. Cuvântul `variable` din mesajul de excepție poate fi înlocuit cu parametrul `$label`. +Verifică dacă valoarea este unul dintre [tipurile așteptate |#Tipuri așteptate] separate prin bară verticală. Dacă nu, aruncă excepția [api:Nette\Utils\AssertionException]. Cuvântul `variable` din textul excepției poate fi înlocuit cu altul prin parametrul `$label`. ```php Validators::assert('Nette', 'string:5'); // OK @@ -120,10 +120,10 @@ Validators::assert('Lorem ipsum dolor sit', 'string:78'); ``` -assertField(array $array, string|int $key, string $expected=null, string $label=null): void .[method] ------------------------------------------------------------------------------------------------------ +assertField(array $array, string|int $key, ?string $expected=null, ?string $label=null): void .[method] +------------------------------------------------------------------------------------------------------- -Verifică dacă elementul `$key` din array-ul `$array` este format din [tipurile așteptate |#expected types], separate prin pipe. În caz contrar, se aruncă excepția [api:Nette\Utils\AssertionException]. Șirul `item '%' in array` din mesajul de excepție poate fi înlocuit cu parametrul `$label`. +Verifică dacă elementul cu cheia `$key` din array-ul `$array` este unul dintre [tipurile așteptate |#Tipuri așteptate] separate prin bară verticală. Dacă nu, aruncă excepția [api:Nette\Utils\AssertionException]. Șirul `item '%' in array` din textul excepției poate fi înlocuit cu altul prin parametrul `$label`. ```php $arr = ['foo' => 'Nette']; @@ -136,19 +136,19 @@ Validators::assertField($arr, 'foo', 'int'); ``` -Validatori .[#toc-validators] -============================= +Validatori +========== is($value, string $expected): bool .[method] -------------------------------------------- -Verifică dacă valoarea este formată din [tipurile așteptate |#expected types], separate prin pipe. +Verifică dacă valoarea este unul dintre [tipurile așteptate |#Tipuri așteptate] separate prin bară verticală. ```php Validators::is(1, 'int|float'); // true Validators::is(23, 'int:0..10'); // false -Validators::is('Nette Framework', 'string:15'); // true, length is 15 bytes +Validators::is('Nette Framework', 'string:15'); // true, lungimea este 15 octeți Validators::is('Nette Framework', 'string:8..'); // true Validators::is('Nette Framework', 'string:30..40'); // false ``` @@ -157,7 +157,7 @@ Validators::is('Nette Framework', 'string:30..40'); // false isEmail(mixed $value): bool .[method] ------------------------------------- -Verifică dacă valoarea este o adresă de e-mail validă. Nu se verifică dacă domeniul există cu adevărat, ci doar sintaxa. Funcția ia în calcul și viitoarele [TLD-uri |https://en.wikipedia.org/wiki/Top-level_domain], care pot fi, de asemenea, în unicode. +Verifică dacă valoarea este o adresă de e-mail validă. Nu se verifică dacă domeniul există efectiv, se verifică doar sintaxa. Funcția ia în considerare și viitoarele [TLD |https://cs.wikipedia.org/wiki/Doména_nejvyššího_řádu], care pot fi și în unicode. ```php Validators::isEmail('example@nette.org'); // true @@ -169,9 +169,9 @@ Validators::isEmail('nette'); // false isInRange(mixed $value, array $range): bool .[method] ----------------------------------------------------- -Verifică dacă valoarea se află în intervalul dat `[min, max]`, unde limita superioară sau inferioară poate fi omisă (`null`). Pot fi comparate numere, șiruri de caractere și obiecte DateTime. +Verifică dacă valoarea se află în intervalul dat `[min, max]`, unde limita superioară sau inferioară poate fi omisă (`null`). Se pot compara numere, șiruri și obiecte `DateTime`. -În cazul în care ambele limite lipsesc (`[null, null]`) sau valoarea este `null`, se returnează `false`. +Dacă lipsesc ambele limite (`[null, null]`) sau valoarea este `null`, returnează `false`. ```php Validators::isInRange(5, [0, 5]); // true @@ -198,7 +198,7 @@ Validators::isNone('nette'); // false isNumeric(mixed $value): bool .[method] --------------------------------------- -Verifică dacă valoarea este un număr sau un număr scris într-un șir de caractere. +Verifică dacă valoarea este un număr sau un număr scris într-un șir. ```php Validators::isNumeric(23); // true @@ -213,7 +213,7 @@ Validators::isNumeric('1e6'); // false isNumericInt(mixed $value): bool .[method] ------------------------------------------ -Verifică dacă valoarea este un număr întreg sau un număr întreg scris într-un șir de caractere. +Verifică dacă valoarea este un număr întreg sau un număr întreg scris într-un șir. ```php Validators::isNumericInt(23); // true @@ -227,20 +227,20 @@ Validators::isNumericInt('nette'); // false isPhpIdentifier(string $value): bool .[method] ---------------------------------------------- -Verifică dacă valoarea este un identificator valid din punct de vedere sintactic în PHP, de exemplu pentru nume de clase, metode, funcții etc. +Verifică dacă valoarea este un identificator sintactic valid în PHP, de exemplu pentru nume de clase, metode, funcții etc. ```php Validators::isPhpIdentifier(''); // false -Validators::isPhpIdentifier('Hello1'); // true -Validators::isPhpIdentifier('1Hello'); // false -Validators::isPhpIdentifier('one two'); // false +Validators::isPhpIdentifier('Salut1'); // true +Validators::isPhpIdentifier('1Salut'); // false +Validators::isPhpIdentifier('unu doi'); // false ``` isBuiltinType(string $type): bool .[method] ------------------------------------------- -Determină dacă `$type` este un tip încorporat în PHP. În caz contrar, este numele clasei. +Determină dacă `$type` este un tip încorporat PHP. În caz contrar, este numele unei clase. ```php Validators::isBuiltinType('string'); // true @@ -251,7 +251,7 @@ Validators::isBuiltinType('Foo'); // false isTypeDeclaration(string $type): bool .[method] ----------------------------------------------- -Verifică dacă declarația de tip este corectă din punct de vedere sintactic. +Verifică dacă declarația de tip dată este sintactic validă. ```php Validators::isTypeDeclaration('?string'); // true @@ -294,7 +294,7 @@ isUrl(mixed $value): bool .[method] Verifică dacă valoarea este o adresă URL validă. ```php -Validators::isUrl('https://nette.org:8080/path?query#fragment'); // true +Validators::isUrl('https://john:xyz%2A12@nette.org:8080/path?query#fragment'); // true Validators::isUrl('http://localhost'); // true Validators::isUrl('http://192.168.1.1'); // true Validators::isUrl('http://[::1]'); // true @@ -306,7 +306,7 @@ Validators::isUrl('nette.org'); // false isUri(string $value): bool .[method] ------------------------------------ -Verifică dacă valoarea este o adresă URI validă, adică, de fapt, un șir care începe cu o schemă validă din punct de vedere sintactic. +Verifică dacă valoarea este o adresă URI validă, adică de fapt un șir care începe cu o schemă sintactic validă. ```php Validators::isUri('https://nette.org'); // true diff --git a/utils/ru/@home.texy b/utils/ru/@home.texy index e75c6199af..bf4492b9d5 100644 --- a/utils/ru/@home.texy +++ b/utils/ru/@home.texy @@ -1,42 +1,46 @@ -Инструменты +Nette Utils *********** .[perex] В пакете `nette/utils` вы найдете набор полезных классов для повседневного использования: -| [Валидация переменных |validators] | Nette\Utils\Validators -| [Callback] | Nette\Utils\Callback -| [Finder] | Nette\Utils\Finder -| [Floats] | Nette\Utils\Floats -| [Генерация случайных строк |random] | Nette\Utils\Random +| [Callback |Callback] | Nette\Utils\Callback | [Дата и время |datetime] | Nette\Utils\DateTime +| [Finder |Finder] | Nette\Utils\Finder +| [HTML-элементы |html-elements] | Nette\Utils\Html +| [Итераторы |iterables] | Nette\Utils\Iterables +| [JSON |json] | Nette\Utils\Json +| [Случайные строки |random] | Nette\Utils\Random | [Изображения |images] | Nette\Utils\Image -| [Модель объекта |smartobject] | Nette\SmartObject & Nette\StaticClass -| [Пагинация |paginator] | Nette\Utils\Paginator -| [Парсинг и генерация JSON |json] | Nette\Utils\Json -| [Поле |arrays] | Nette\Utils\Arrays +| [PHP-рефлексия |reflection] | Nette\Utils\Reflection +| [Типы PHP |type] | Nette\Utils\Type +| [Массивы |arrays] | Nette\Utils\Arrays +| [Вспомогательные функции |helpers] | Nette\Utils\Helpers +| [Сравнение float |floats] | Nette\Utils\Floats | [Строки |strings] | Nette\Utils\Strings -| [Типы |type] | Nette\Utils\Type | [Файловая система |filesystem] | Nette\Utils\FileSystem -| [Функции-помощники |helpers] | Nette\Utils\Helpers -| [Элементы HTML |html-elements] | Nette\Utils\Html -| [PHP Отражение |reflection] | Nette\Utils\Reflection +| [Пагинация |paginator] | Nette\Utils\Paginator +| [SmartObject |SmartObject] & [StaticClass |StaticClass] | Nette\SmartObject & Nette\StaticClass +| [Валидатор |validators] | Nette\Utils\Validators Установка --------- -Загрузите и установите библиотеку с помощью [Composer |best-practices:composer]: +Вы можете скачать и установить библиотеку с помощью [Composer|best-practices:composer]: ```shell composer require nette/utils ``` -| Версия, совместимая с PHP +| версия | совместима с PHP |-----------|------------------- -| Nette Utils 4.0 | PHP 8.0 - 8.2 -| Nette Utils 3.2 | PHP 7.2 - 8.2 -| Nette Utils 3.0 - 3.1 | PHP 7.1 - 8.0 -| Nette Utils 2.5 | PHP 5.6 - 8.0 +| Nette Utils 4.0 | PHP 8.0 – 8.4 +| Nette Utils 3.2 | PHP 7.2 – 8.3 +| Nette Utils 3.0 – 3.1 | PHP 7.1 – 8.0 +| Nette Utils 2.5 | PHP 5.6 – 8.0 + +Применимо к последней патч-версии. + -Действителен для последней версии патча. +Если вы обновляете пакет до новой версии, посмотрите страницу [обновление|en:upgrading]. diff --git a/utils/ru/@left-menu.texy b/utils/ru/@left-menu.texy index 4c34b12008..3e9c48538a 100644 --- a/utils/ru/@left-menu.texy +++ b/utils/ru/@left-menu.texy @@ -1,26 +1,28 @@ -Пакет nette/utils -***************** -- [Валидатор |validators] -- [Finder] -- [Floats] -- [JSON] +Nette Utils +*********** +- [Колбэки |callback] - [Дата и время |datetime] -- [Изображения |images] -- [Обратные вызовы |callback] -- [Пагинатор |paginator] -- [Поле |arrays] +- [Finder |finder] +- [Floats |floats] +- [HTML-элементы |html-elements] +- [Итераторы |iterables] +- [JSON |json] - [Случайные строки |random] -- [Строки |strings] +- [Изображения |images] +- [Paginator |paginator] +- [PHP Рефлексия |reflection] - [Типы PHP |type] -- [Файловая система |filesystem] -- [Функции-помощники |helpers] -- [Элементы HTML |html-elements] -- [PHP Отражение |reflection] +- [Массивы |arrays] +- [Вспомогательные функции |helpers] +- [Строки |strings] - [SmartObject |smartobject] +- [StaticClass |staticclass] +- [Файловая система |filesystem] +- [Валидатор |validators] Другие инструменты ****************** -- [НЕОН |neon:] +- [NEON|neon:] - [Хеширование паролей |security:passwords] -- [Разбор и сворачивание URL-адресов |http:urls] +- [Разбор и сборка URL |http:urls] diff --git a/utils/ru/@meta.texy b/utils/ru/@meta.texy new file mode 100644 index 0000000000..7f329adfce --- /dev/null +++ b/utils/ru/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документация Nette}} diff --git a/utils/ru/arrays.texy b/utils/ru/arrays.texy index 05dca37498..dec3f21649 100644 --- a/utils/ru/arrays.texy +++ b/utils/ru/arrays.texy @@ -1,8 +1,8 @@ -Работа с полем -************** +Работа с массивами +****************** .[perex] -Эта страница посвящена классам [Nette\Utils\Arrays |#Arrays], [ArrayHash |#ArrayHash] и [ArrayList |#ArrayList], которые связаны с массивами. +Эта страница посвящена классам [Nette\Utils\Arrays |#Arrays], [#ArrayHash] и [#ArrayList], которые относятся к массивам. Установка: @@ -12,18 +12,59 @@ composer require nette/utils ``` -Массивы .[#toc-arrays] -====================== +Arrays +====== -[api:Nette\Utils\Arrays] это статический класс, содержащий полезные функции для работы с массивами. +[api:Nette\Utils\Arrays] — это статический класс, содержащий полезные функции для работы с массивами. Ее аналогом для итераторов является [Nette\Utils\Iterables|iterables]. -В следующих примерах предполагается, что псевдоним уже создан: +Следующие примеры предполагают созданный псевдоним (alias): ```php use Nette\Utils\Arrays; ``` +associate(array $array, mixed $path): array|\stdClass .[method] +--------------------------------------------------------------- + +Функция гибко преобразует массив `$array` в ассоциативный массив или объекты в соответствии с указанным путем `$path`. Путь может быть строкой или массивом. Он состоит из имен ключей входного массива и операторов, таких как '[]', '->', '=', и '|'. Выбрасывает `Nette\InvalidArgumentException` в случае, если путь недействителен. + +```php +// преобразование в ассоциативный массив по простому ключу +$arr = [ + ['name' => 'John', 'age' => 11], + ['name' => 'Mary', 'age' => null], + // ... +]; +$result = Arrays::associate($arr, 'name'); +// $result = ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// присвоение значений из одного ключа другому с использованием оператора = +$result = Arrays::associate($arr, 'name=age'); // или ['name', '=', 'age'] +// $result = ['John' => 11, 'Mary' => null, ...] +``` + +```php +// создание объекта с использованием оператора -> +$result = Arrays::associate($arr, '->name'); // или ['->', 'name'] +// $result = (object) ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// комбинация ключей с помощью оператора | +$result = Arrays::associate($arr, 'name|age'); // или ['name', '|', 'age'] +// $result: ['John' => ['name' => 'John', 'age' => 11], 'Paul' => ['name' => 'Paul', 'age' => 44]] +``` + +```php +// добавление в массив с использованием [] +$result = Arrays::associate($arr, 'name[]'); // или ['name', '[]'] +// $result: ['John' => [['name' => 'John', 'age' => 22], ['name' => 'John', 'age' => 11]]] +``` + + contains(array $array, $value): bool .[method] ---------------------------------------------- @@ -35,10 +76,10 @@ Arrays::contains(['1', false], 1); // false ``` -every(iterable $array, callable $callback): bool .[method] ----------------------------------------------------------- +every(array $array, callable $predicate): bool .[method] +-------------------------------------------------------- -Проверяет, все ли элементы в массиве проходят тест, реализованный в `$callback` с сигнатурой `function ($value, $key, array $array): bool`. +Проверяет, проходят ли все элементы массива тест, реализованный в `$predicate` с сигнатурой `function ($value, $key, array $array): bool`. ```php $array = [1, 30, 39, 29, 10, 13]; @@ -46,24 +87,59 @@ $isBelowThreshold = fn($value) => $value < 40; $res = Arrays::every($array, $isBelowThreshold); // true ``` -См. [some() |#some]. +См. [#some()]. -first(array $array): mixed .[method] ------------------------------------- +filter(array $array, callable $predicate): array .[method]{data-version:4.0.4} +------------------------------------------------------------------------------ -Возвращает первую запись из массива или null, если массив пуст. Не изменяет внутренний указатель, в отличие от `reset()`. +Возвращает новый массив, содержащий все пары ключ-значение, соответствующие указанному предикату. Callback имеет сигнатуру `function ($value, int|string $key, array $array): bool`. ```php -Arrays::first([1, 2, 3]); // 1 -Arrays::first([]); // null +Arrays::filter( + ['a' => 1, 'b' => 2, 'c' => 3], + fn($v) => $v < 3, +); +// ['a' => 1, 'b' => 2] ``` +first(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------- + +Возвращает первый элемент (соответствующий предикату, если он указан). Если такого элемента не существует, возвращает результат вызова `$else` или null. Параметр `$predicate` имеет сигнатуру `function ($value, int|string $key, array $array): bool`. + +Не изменяет внутренний указатель в отличие от `reset()`. Параметры `$predicate` и `$else` существуют с версии 4.0.4. + +```php +Arrays::first([1, 2, 3]); // 1 +Arrays::first([1, 2, 3], fn($v) => $v > 2); // 3 +Arrays::first([]); // null +Arrays::first([], else: fn() => false); // false +``` + +См. [#last()]. + + +firstKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +------------------------------------------------------------------------------------------------ + +Возвращает ключ первого элемента (соответствующий предикату, если он указан) или null, если такого элемента не существует. Предикат `$predicate` имеет сигнатуру `function ($value, int|string $key, array $array): bool`. + +```php +Arrays::firstKey([1, 2, 3]); // 0 +Arrays::firstKey([1, 2, 3], fn($v) => $v > 2); // 2 +Arrays::firstKey(['a' => 1, 'b' => 2]); // 'a' +Arrays::firstKey([]); // null +``` + +См. [#lastKey()]. + + flatten(array $array, bool $preserveKeys=false): array .[method] ---------------------------------------------------------------- -Консолидирует многоуровневый массив в плоский. +Объединяет многоуровневый массив в плоский. ```php $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); @@ -71,26 +147,26 @@ $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); ``` -get(array $array, string|int|array $key, mixed $default=null): mixed .[method] ------------------------------------------------------------------------------- +get(array $array, string|int|array $key, ?mixed $default=null): mixed .[method] +------------------------------------------------------------------------------- -Возвращает элемент `$array[$key]`. Если он не существует, то либо выдается исключение `Nette\InvalidArgumentException`, либо, если задан третий параметр `$default`, то возвращается этот параметр. +Возвращает элемент `$array[$key]`. Если он не существует, выбрасывает либо исключение `Nette\InvalidArgumentException`, либо, если указан третий параметр `$default`, возвращает его. ```php -// pokud $array['foo'] neexistuje, vyhodí výjimku +// если $array['foo'] не существует, выбрасывает исключение $value = Arrays::get($array, 'foo'); -// pokud $array['foo'] neexistuje, vrátí 'bar' +// если $array['foo'] не существует, возвращает 'bar' $value = Arrays::get($array, 'foo', 'bar'); ``` -Ключ `$key` также может быть массивом. +Ключом `$key` может быть и массив. ```php $array = ['color' => ['favorite' => 'red'], 5]; $value = Arrays::get($array, ['color', 'favorite']); -// vrátí 'red' +// возвращает 'red' ``` @@ -101,32 +177,32 @@ getRef(array &$array, string|int|array $key): mixed .[method] ```php $valueRef = & Arrays::getRef($array, 'foo'); -// vrátí referenci na $array['foo'] +// возвращает ссылку на $array['foo'] ``` -Как и функция [get() |#get], она может работать с многомерными массивами. +Так же, как функция [#get()], умеет работать с многомерными массивами. ```php $value = & Arrays::getRef($array, ['color', 'favorite']); -// vrátí referenci na $array['color']['favorite'] +// возвращает ссылку на $array['color']['favorite'] ``` grep(array $array, string $pattern, bool $invert=false): array .[method] ------------------------------------------------------------------------ -Возвращает только те элементы массива, значение которых совпадает с регулярным выражением `$pattern`. Если `$invert` равно `true`, то возвращает элементы, которые не совпадают. Ошибка компиляции или обработки выражения вызывает исключение `Nette\RegexpException`. +Возвращает только те элементы массива, значение которых соответствует регулярному выражению `$pattern`. Если `$invert` равно `true`, наоборот, возвращает элементы, которые не соответствуют. Ошибка при компиляции или обработке выражения выбрасывает исключение `Nette\RegexpException`. ```php $filteredArray = Arrays::grep($array, '~^\d+$~'); -// vrátí pouze prvky pole tvořené číslicemi +// возвращает только элементы массива, состоящие из цифр ``` insertAfter(array &$array, string|int|null $key, array $inserted): void .[method] --------------------------------------------------------------------------------- -Вставляет содержимое поля `$inserted` в поле `$array` сразу после элемента с ключом `$key`. Если `$key` является `null` (или отсутствует в поле), оно вставляется в конец. +Вставляет содержимое массива `$inserted` в массив `$array` сразу после элемента с ключом `$key`. Если `$key` равен `null` (или его нет в массиве), вставляется в конец. ```php $array = ['first' => 10, 'second' => 20]; @@ -138,7 +214,7 @@ Arrays::insertAfter($array, 'first', ['hello' => 'world']); insertBefore(array &$array, string|int|null $key, array $inserted): void .[method] ---------------------------------------------------------------------------------- -Вставляет содержимое поля `$inserted` в поле `$array` перед элементом с ключом `$key`. Если `$key` является `null` (или отсутствует в поле), оно вставляется в начало. +Вставляет содержимое массива `$inserted` в массив `$array` перед элементом с ключом `$key`. Если `$key` равен `null` (или его нет в массиве), вставляется в начало. ```php $array = ['first' => 10, 'second' => 20]; @@ -150,7 +226,7 @@ Arrays::insertBefore($array, 'first', ['hello' => 'world']); invoke(iterable $callbacks, ...$args): array .[method] ------------------------------------------------------ -Вызывает все обратные вызовы и возвращает массив результатов. +Вызывает все callback'и и возвращает массив результатов. ```php $callbacks = [ @@ -166,7 +242,7 @@ $array = Arrays::invoke($callbacks, 5, 11); invokeMethod(iterable $objects, string $method, ...$args): array .[method] -------------------------------------------------------------------------- -Вызывает метод на каждом объекте в массиве и возвращает массив результатов. +Вызывает метод для каждого объекта в массиве и возвращает массив результатов. ```php $objects = ['a' => $obj1, 'b' => $obj2]; @@ -179,7 +255,7 @@ $array = Arrays::invokeMethod($objects, 'foo', 1, 2); isList(array $array): bool .[method] ------------------------------------ -Проверяет, индексирован ли массив возрастающим рядом числовых ключей, начиная с нуля, т.е. списком. +Проверяет, является ли массив индексированным по возрастающей последовательности числовых ключей от нуля, т.е. списком (list). ```php Arrays::isList(['a', 'b', 'c']); // true @@ -188,21 +264,42 @@ Arrays::isList(['a' => 1, 'b' => 2]); // false ``` -last(array $array): mixed .[method] ------------------------------------ +last(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------ + +Возвращает последний элемент (соответствующий предикату, если он указан). Если такого элемента не существует, возвращает результат вызова `$else` или null. Параметр `$predicate` имеет сигнатуру `function ($value, int|string $key, array $array): bool`. -Возвращает последнюю запись массива или null, если массив пуст. Не изменяет внутренний указатель, в отличие от `end()`. +Не изменяет внутренний указатель в отличие от `end()`. Параметры `$predicate` и `$else` существуют с версии 4.0.4. ```php -Arrays::last([1, 2, 3]); // 3 -Arrays::last([]); // null +Arrays::last([1, 2, 3]); // 3 +Arrays::last([1, 2, 3], fn($v) => $v < 3); // 2 +Arrays::last([]); // null +Arrays::last([], else: fn() => false); // false ``` +См. [#first()]. + + +lastKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +----------------------------------------------------------------------------------------------- + +Возвращает ключ последнего элемента (соответствующий предикату, если он указан) или null, если такого элемента не существует. Предикат `$predicate` имеет сигнатуру `function ($value, int|string $key, array $array): bool`. -map(iterable $array, callable $callback): array .[method] +```php +Arrays::lastKey([1, 2, 3]); // 2 +Arrays::lastKey([1, 2, 3], fn($v) => $v < 3); // 1 +Arrays::lastKey(['a' => 1, 'b' => 2]); // 'b' +Arrays::lastKey([]); // null +``` + +См. [#firstKey()]. + + +map(array $array, callable $transformer): array .[method] --------------------------------------------------------- -Вызывает `$callback` на всех элементах массива и возвращает массив возвращаемых значений. Обратный вызов имеет сигнатуру `function ($value, $key, array $array): bool`. +Вызывает `$transformer` для всех элементов массива и возвращает массив возвращенных значений. Callback имеет сигнатуру `function ($value, $key, array $array): mixed`. ```php $array = ['foo', 'bar', 'baz']; @@ -211,10 +308,24 @@ $res = Arrays::map($array, fn($value) => $value . $value); ``` +mapWithKeys(array $array, callable $transformer): array .[method] +----------------------------------------------------------------- + +Создает новый массив путем преобразования значений и ключей исходного массива. Функция `$transformer` имеет сигнатуру `function ($value, $key, array $array): ?array{$newKey, $newValue}`. Если `$transformer` возвращает `null`, элемент пропускается. Для сохраненных элементов первый элемент возвращенного массива используется как новый ключ, а второй элемент - как новое значение. + +```php +$array = ['a' => 1, 'b' => 2]; +$result = Arrays::mapWithKeys($array, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +Этот метод полезен в ситуациях, когда вам нужно изменить структуру массива (ключи и значения одновременно) или отфильтровать элементы при преобразовании (возвращая null для нежелательных элементов). + + mergeTree(array $array1, array $array2): array .[method] -------------------------------------------------------- -Рекурсивно объединяет два поля. Это полезно, например, для объединения древовидных структур. При объединении он следует тем же правилам, что и оператор `+`, применяемый к массивам, т.е. добавляет пару ключ/значение из второго массива в первый массив и оставляет значение из первого массива в случае столкновения ключей. +Рекурсивно объединяет два массива. Подходит, например, для объединения древовидных структур. При объединении руководствуется теми же правилами, что и оператор `+`, применяемый к массивам, т.е. к первому массиву добавляет пары ключ/значение из второго массива и в случае коллизии ключей оставляет значение из первого массива. ```php $array1 = ['color' => ['favorite' => 'red'], 5]; @@ -224,13 +335,13 @@ $array = Arrays::mergeTree($array1, $array2); // $array = ['color' => ['favorite' => 'red', 'blue'], 5]; ``` -Значения из второго массива всегда добавляются к концу первого. Исчезновение значения `10` из второго поля может показаться немного непонятным. Обратите внимание, что это значение, как и значение `5` v poli prvním mají přiřazený stejný numerický klíč `0`, поэтому в результирующий массив попадает только элемент из первого поля. +Значения из второго массива всегда добавляются в конец первого. Немного сбивающим с толку может показаться исчезновение значения `10` из второго массива. Необходимо понимать, что этому значению, а также значению `5` в первом массиве, присвоен один и тот же числовой ключ `0`, поэтому в результирующем массиве есть только элемент из первого поля. -normalize(array $array, string $filling=null): array .[method] --------------------------------------------------------------- +normalize(array $array, ?string $filling=null): array .[method] +--------------------------------------------------------------- -Нормализует массив к ассоциативному массиву. Заменяет цифровые клавиши их значениями, новое значение будет `$filling`. +Нормализует массив в ассоциативный массив. Числовые ключи заменяет их значениями, новым значением будет `$filling`. ```php $array = Arrays::normalize([1 => 'first', 'a' => 'second']); @@ -246,11 +357,11 @@ $array = Arrays::normalize([1 => 'first', 'a' => 'second'], 'foobar'); pick(array &$array, string|int $key, mixed $default=null): mixed .[method] -------------------------------------------------------------------------- -Возвращает и удаляет значение элемента из массива. Если он не существует, выдает исключение или возвращает значение `$default`, если оно существует. +Возвращает и удаляет значение элемента из массива. Если он не существует, выбрасывает исключение, или возвращает значение `$default`, если оно указано. ```php -$array = [1 => 'foo', null => 'bar']; -$a = Arrays::pick($array, null); +$array = [1 => 'foo', 'x' => 'bar']; +$a = Arrays::pick($array, 'x'); // $a = 'bar' $b = Arrays::pick($array, 'not-exists', 'foobar'); // $b = 'foobar' @@ -274,20 +385,20 @@ Arrays::renameKey($array, 'first', 'renamed'); getKeyOffset(array $array, string|int $key): ?int .[method] ----------------------------------------------------------- -Возвращает позицию заданного ключа в массиве. Позиция нумеруется от 0. Если ключ не найден, функция возвращает `null`. +Возвращает позицию данного ключа в массиве. Позиция нумеруется с 0. В случае, если ключ не будет найден, функция вернет `null`. ```php $array = ['first' => 10, 'second' => 20]; -$position = Arrays::getKeyOffset($array, 'first'); // vrátí 0 -$position = Arrays::getKeyOffset($array, 'second'); // vrátí 1 -$position = Arrays::getKeyOffset($array, 'not-exists'); // vrátí null +$position = Arrays::getKeyOffset($array, 'first'); // возвращает 0 +$position = Arrays::getKeyOffset($array, 'second'); // возвращает 1 +$position = Arrays::getKeyOffset($array, 'not-exists'); // возвращает null ``` -some(iterable $array, callable $callback): bool .[method] ---------------------------------------------------------- +some(array $array, callable $predicate): bool .[method] +------------------------------------------------------- -Проверяет, проходит ли хотя бы один элемент в массиве тест, реализованный в `$callback` с сигнатурой `function ($value, $key, array $array): bool`. +Проверяет, проходит ли хотя бы один элемент массива тест, реализованный в `$predicate` с сигнатурой `function ($value, $key, array $array): bool`. ```php $array = [1, 2, 3, 4]; @@ -295,13 +406,13 @@ $isEven = fn($value) => $value % 2 === 0; $res = Arrays::some($array, $isEven); // true ``` -См. раздел [Каждый() |#every]. +См. [#every()]. toKey(mixed $key): string|int .[method] --------------------------------------- -Преобразует значение в ключ массива, который является либо целым числом, либо строкой. +Преобразует значение в ключ массива, который является либо целым числом (integer), либо строкой. ```php Arrays::toKey('1'); // 1 @@ -317,14 +428,14 @@ toObject(iterable $array, object $object): object .[method] ```php $obj = new stdClass; $array = ['foo' => 1, 'bar' => 2]; -Arrays::toObject($array, $obj); // nastaví $obj->foo = 1; $obj->bar = 2; +Arrays::toObject($array, $obj); // устанавливает $obj->foo = 1; $obj->bar = 2; ``` -wrap(iterable $array, string $prefix='', string $suffix=''): array .[method] ----------------------------------------------------------------------------- +wrap(array $array, string $prefix='', string $suffix=''): array .[method] +------------------------------------------------------------------------- -Выводит каждый элемент массива в строку и оборачивает ее префиксом `$prefix` и суффиксом `$suffix`. +Каждый элемент массива преобразует в строку и обрамляет префиксом `$prefix` и суффиксом `$suffix`. ```php $array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); @@ -332,21 +443,21 @@ $array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); ``` -ArrayHash .[#toc-arrayhash] -=========================== +ArrayHash +========= -Объект [api:Nette\Utils\ArrayHash] является потомком общего класса stdClass и расширяет его возможностью обращаться с ним как с массивом, т.е., например, обращаться к членам через квадратные скобки: +Объект [api:Nette\Utils\ArrayHash] является потомком универсального класса stdClass и расширяет его возможностью обращаться с ним как с массивом, то есть, например, получать доступ к членам через квадратные скобки: ```php $hash = new Nette\Utils\ArrayHash; $hash['foo'] = 123; -$hash->bar = 456; // zároveň funguje i objektový zápis +$hash->bar = 456; // одновременно работает и объектная запись $hash->foo; // 123 ``` -Вы можете использовать функцию `count($hash)`, чтобы получить количество членов. +Можно использовать функцию `count($hash)` для определения количества элементов. -Вы можете выполнять итерации над объектом, как в случае с массивом, даже со ссылкой: +По объекту можно итерировать так же, как и по массиву, в том числе и по ссылке: ```php foreach ($hash as $key => $value) { @@ -358,7 +469,7 @@ foreach ($hash as $key => &$value) { } ``` -Мы можем преобразовать существующий массив в `ArrayHash` с помощью метода `from()`: +Существующий массив можно преобразовать в `ArrayHash` с помощью метода `from()`: ```php $array = ['foo' => 123, 'bar' => 456]; @@ -368,22 +479,22 @@ $hash->foo; // 123 $hash->bar; // 456 ``` -Преобразование является рекурсивным: +Преобразование рекурсивно: ```php $array = ['foo' => 123, 'inner' => ['a' => 'b']]; $hash = Nette\Utils\ArrayHash::from($array); -$hash->inner; // objekt ArrayHash +$hash->inner; // объект ArrayHash $hash->inner->a; // 'b' $hash['inner']['a']; // 'b' ``` -Это можно предотвратить с помощью второго параметра: +Это можно предотвратить вторым параметром: ```php $hash = Nette\Utils\ArrayHash::from($array, false); -$hash->inner; // pole +$hash->inner; // массив ``` Преобразование обратно в массив: @@ -393,10 +504,10 @@ $array = (array) $hash; ``` -ArrayList .[#toc-arraylist] -=========================== +ArrayList +========= -[api:Nette\Utils\ArrayList] представляет собой линейный массив, в котором индексами являются только целые числа, возрастающие от 0. +[api:Nette\Utils\ArrayList] представляет собой линейный массив, где индексами являются только целые числа по возрастанию от 0. ```php $list = new Nette\Utils\ArrayList; @@ -407,16 +518,16 @@ $list[] = 'c'; count($list); // 3 ``` -Существующие массивы могут быть преобразованы в `ArrayList` с помощью метода `from()`: +Существующий массив можно преобразовать в `ArrayList` с помощью метода `from()`: ```php $array = ['foo', 'bar']; $list = Nette\Utils\ArrayList::from($array); ``` -Вы можете использовать функцию `count($list)` для получения количества элементов. +Можно использовать функцию `count($list)` для определения количества элементов. -Вы можете выполнять итерации над объектом, как в случае с массивом, даже со ссылкой: +По объекту можно итерировать так же, как и по массиву, в том числе и по ссылке: ```php foreach ($list as $key => $value) { @@ -428,21 +539,21 @@ foreach ($list as $key => &$value) { } ``` -Доступ к ключам за пределами допустимых значений вызывает исключение `Nette\OutOfRangeException`: +Доступ к ключам вне допустимых значений выбросит исключение `Nette\OutOfRangeException`: ```php -echo $list[-1]; // приводит Nette\OutOfRangeException -unset($list[30]); // приводит Nette\OutOfRangeException +echo $list[-1]; // выбросит Nette\OutOfRangeException +unset($list[30]); // выбросит Nette\OutOfRangeException ``` -Удаление ключа приводит к изменению нумерации элементов: +Удаление ключа вызывает перенумерацию элементов: ```php unset($list[1]); // ArrayList(0 => 'a', 1 => 'c') ``` -Новый элемент может быть добавлен в начало с помощью метода `prepend()`: +Новый элемент можно добавить в начало с помощью метода `prepend()`: ```php $list->prepend('d'); diff --git a/utils/ru/callback.texy b/utils/ru/callback.texy index 1439e67219..3e32734142 100644 --- a/utils/ru/callback.texy +++ b/utils/ru/callback.texy @@ -1,8 +1,8 @@ -Работа с обратными вызовами -*************************** +Работа с callback'ами +********************* .[perex] -[api:Nette\Utils\Callback] это статический класс с функциями для работы с [обратными вызовами PHP |https://www.php.net/manual/en/language.types.callable.php]. +[api:Nette\Utils\Callback] — это статический класс с функциями для работы с [PHP callback'ами |https://www.php.net/manual/en/language.types.callable.php]. Установка: @@ -11,7 +11,7 @@ composer require nette/utils ``` -Во всех примерах предполагается, что псевдоним уже создан: +Все примеры предполагают созданный псевдоним (alias): ```php use Nette\Utils\Callback; @@ -21,21 +21,21 @@ use Nette\Utils\Callback; check($callable, bool $syntax=false): callable .[method] -------------------------------------------------------- -Проверяет, является ли переменная `$callable` допустимым обратным вызовом. В противном случае выбрасывается `Nette\InvalidArgumentException`. Если `$syntax` истина, функция только проверяет, что `$callable` имеет структуру обратного вызова, но не проверяет, существует ли класс или метод на самом деле. Он возвращает `$callable`. +Проверяет, является ли переменная `$callable` действительным callback'ом. В противном случае выбрасывает `Nette\InvalidArgumentException`. Если `$syntax` равно true, функция только проверяет, что `$callable` имеет структуру callback'а, но не проверяет, действительно ли существует данный класс или метод. Возвращает `$callable`. ```php Callback::check('trim'); // не выбрасывает исключение -Callback::check(['NonExistentClass', 'method']); // бросает Nette\InvalidArgumentException +Callback::check(['NonExistentClass', 'method']); // выбрасывает Nette\InvalidArgumentException Callback::check(['NonExistentClass', 'method'], true); // не выбрасывает исключение Callback::check(function () {}); // не выбрасывает исключение -Callback::check(null); // бросает Nette\InvalidArgumentException +Callback::check(null); // выбрасывает Nette\InvalidArgumentException ``` -toString($callable): string .[method] -------------------------------------- +toString(callable $callable): string .[method] +---------------------------------------------- -Преобразует обратный вызов PHP в текстовую форму. Класс или метод не обязательно должен существовать. +Преобразует PHP callback в текстовую форму. Класс или метод могут не существовать. ```php Callback::toString('trim'); // 'trim' @@ -46,21 +46,21 @@ Callback::toString(['MyClass', 'method']); // 'MyClass::method' toReflection($callable): ReflectionMethod|ReflectionFunction .[method] ---------------------------------------------------------------------- -Возвращает отражение для метода или функции в обратном вызове PHP. +Возвращает рефлексию для метода или функции в PHP callback'е. ```php $ref = Callback::toReflection('trim'); -// $ref je ReflectionFunction('trim') +// $ref - это ReflectionFunction('trim') $ref = Callback::toReflection(['MyClass', 'method']); -// $ref je ReflectionMethod('MyClass', 'method') +// $ref - это ReflectionMethod('MyClass', 'method') ``` -isStatic($callable): bool .[method] ------------------------------------ +isStatic(callable $callable): bool .[method] +-------------------------------------------- -Определяет, является ли обратный вызов PHP функцией или статическим методом. +Определяет, является ли PHP callback функцией или статическим методом. ```php Callback::isStatic('trim'); // true @@ -73,7 +73,7 @@ Callback::isStatic(function () {}); // false unwrap(Closure $closure): callable|array .[method] -------------------------------------------------- -Распаковывает закрытие, созданное с помощью `Closure::fromCallable`:https://www.php.net/manual/en/closure.fromcallable.php. +Обратно распаковывает Closure, созданную с помощью `Closure::fromCallable`:https://www.php.net/manual/en/closure.fromcallable.php. ```php $closure = Closure::fromCallable(['MyClass', 'method']); diff --git a/utils/ru/datetime.texy b/utils/ru/datetime.texy index d6e82906ca..9412811950 100644 --- a/utils/ru/datetime.texy +++ b/utils/ru/datetime.texy @@ -2,16 +2,16 @@ ************ .[perex] -[api:Nette\Utils\DateTime] - это класс, который расширяет родную DateTime дополнительными функциями. +[api:Nette\Utils\DateTime] — это класс, который расширяет нативный [php:DateTime] дополнительными функциями. -Инсталляция: +Установка: ```shell composer require nette/utils ``` -Все примеры предполагают наличие созданного псевдонима: +Все примеры предполагают созданный псевдоним (alias): ```php use Nette\Utils\DateTime; @@ -20,27 +20,27 @@ use Nette\Utils\DateTime; static from(string|int|\DateTimeInterface $time): DateTime .[method] -------------------------------------------------------------------- -Вы можете выбрать объект DateTime z řetězce, UNIX timestamp nebo jiného objektu [php:DateTimeInterface]. Vyhodí výjimku `Exception`, pokud datum a čas není platný. +Создает объект DateTime из строки, UNIX timestamp или другого объекта [php:DateTimeInterface]. Выбрасывает исключение `Exception`, если дата и время недействительны. ```php -DateTime::from(1138013640); // создает DateTime из временной метки UNIX с часовым поясом по умолчанию +DateTime::from(1138013640); // создает DateTime из UNIX timestamp с временной зоной по умолчанию DateTime::from(42); // создает DateTime из текущего времени плюс 42 секунды -DateTime::from('1994-02-26 04:15:32'); // создает DateTime из строки +DateTime::from('1994-02-26 04:15:32'); // создает DateTime по строке DateTime::from('1994-02-26'); // создает DateTime по дате, время будет 00:00:00 ``` static fromParts(int $year, int $month, int $day, int $hour=0, int $minute=0, float $second=0.0): DateTime .[method] -------------------------------------------------------------------------------------------------------------------- -Вытворите объект DateTime и выведите его на сайт `Nette\InvalidArgumentException`, где будет указана дата и ее значение. +Создает объект DateTime или выбрасывает исключение `Nette\InvalidArgumentException`, если дата и время недействительны. ```php DateTime::fromParts(1994, 2, 26, 4, 15, 32); ``` -static createFromFormat(string $format, string $time, string|\DateTimeZone $timezone=null): DateTime|false .[method] --------------------------------------------------------------------------------------------------------------------- -Расширяет [DateTime::createFromFormat() |https://www.php.net/manual/en/datetime.createfromformat.php] возможностью ввода часового пояса в виде строки. +static createFromFormat(string $format, string $time, ?string|\DateTimeZone $timezone=null): DateTime|false .[method] +--------------------------------------------------------------------------------------------------------------------- +Расширяет [DateTime::createFromFormat()|https://www.php.net/manual/en/datetime.createfromformat.php] возможностью указать временную зону (timezone) в виде строки. ```php DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); ``` @@ -48,7 +48,7 @@ DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); modifyClone(string $modify=''): static .[method] ------------------------------------------------ -Вытворжи копию с поднятым часом. +Создает копию с измененным временем. ```php $original = DateTime::from('2017-02-03'); $clone = $original->modifyClone('+1 day'); @@ -65,8 +65,8 @@ echo $dateTime; // '2017-02-03 04:15:32' ``` -implementuje JsonSerializable .[#toc-implements-jsonserializable] ------------------------------------------------------------------ +реализует JsonSerializable +-------------------------- Возвращает дату и время в формате ISO 8601, который используется, например, в JavaScript. ```php $date = DateTime::from('2017-02-03'); diff --git a/utils/ru/filesystem.texy b/utils/ru/filesystem.texy index 0e83acb07b..e8610ac45c 100644 --- a/utils/ru/filesystem.texy +++ b/utils/ru/filesystem.texy @@ -2,40 +2,42 @@ **************** .[perex] -[api:Nette\Utils\FileSystem] это статический класс, содержащий полезные функции для работы с файловой системой. Одно из преимуществ перед собственными функциями PHP заключается в том, что они бросают исключения в случае ошибок. +[api:Nette\Utils\FileSystem] — это класс с полезными функциями для работы с файловой системой. Одним из преимуществ по сравнению с нативными функциями PHP является то, что в случае ошибки они выбрасывают исключения. +Если вам нужно искать файлы на диске, используйте [Finder|finder]. + Установка: ```shell composer require nette/utils ``` -В следующих примерах предполагается, что определен следующий псевдоним класса: +Следующие примеры предполагают, что создан псевдоним: ```php use Nette\Utils\FileSystem; ``` -Манипуляция .[#toc-manipulation] -================================ +Манипуляции +=========== copy(string $origin, string $target, bool $overwrite=true): void .[method] -------------------------------------------------------------------------- -Копирует файл или целый каталог. По умолчанию перезаписывает существующие файлы и каталоги. Если `$overwrite` установлен в `false`, а `$target` уже существует, выбрасывает исключение `Nette\InvalidStateException`. Выбрасывает исключение `Nette\IOException` при возникновении ошибки. +Копирует файл или весь каталог. По умолчанию перезаписывает существующие файлы и каталоги. Если параметр `$overwrite` установлен в `false`, выбрасывает исключение `Nette\InvalidStateException`, если целевой файл или каталог `$target` существует. При ошибке выбрасывает исключение `Nette\IOException`. ```php FileSystem::copy('/path/to/source', '/path/to/dest', overwrite: true); ``` -createDir(string $directory, int $mode=0777): void .[method] ------------------------------------------------------------- +createDir(string $dir, int $mode=0777): void .[method] +------------------------------------------------------ -Создает каталог, если он не существует, включая родительские каталоги. Выбрасывает исключение `Nette\IOException` при возникновении ошибки. +Создает каталог, если он не существует, включая родительские каталоги. При ошибке выбрасывает исключение `Nette\IOException`. ```php FileSystem::createDir('/path/to/dir'); @@ -45,7 +47,7 @@ FileSystem::createDir('/path/to/dir'); delete(string $path): void .[method] ------------------------------------ -Удаляет файл или весь каталог, если он существует. Если каталог не пуст, то сначала удаляется его содержимое. При возникновении ошибки выбрасывает исключение `Nette\IOException`. +Удаляет файл или весь каталог, если он существует. Если каталог не пуст, сначала удаляет его содержимое. При ошибке выбрасывает исключение `Nette\IOException`. ```php FileSystem::delete('/path/to/fileOrDir'); @@ -55,7 +57,7 @@ FileSystem::delete('/path/to/fileOrDir'); makeWritable(string $path, int $dirMode=0777, int $fileMode=0666): void .[method] --------------------------------------------------------------------------------- -Устанавливает разрешения для файлов на `$fileMode` или разрешения для каталогов на `$dirMode`. Рекурсивно обходит и устанавливает разрешения на все содержимое каталога. +Устанавливает права доступа к файлу `$fileMode` или к каталогу `$dirMode`. Рекурсивно проходит и устанавливает права доступа также всему содержимому каталога. ```php FileSystem::makeWritable('/path/to/fileOrDir'); @@ -65,7 +67,7 @@ FileSystem::makeWritable('/path/to/fileOrDir'); open(string $path, string $mode): resource .[method] ---------------------------------------------------- -Открывает файл и возвращает ресурс. Параметр `$mode` работает так же, как и родная функция `fopen()`:https://www.php.net/manual/en/function.fopen.php. Если возникает ошибка, он вызывает исключение `Nette\IOException`. +Открывает файл и возвращает ресурс. Параметр `$mode` работает так же, как у нативной функции `fopen()`:https://www.php.net/manual/en/function.fopen.php. В случае ошибки выбрасывает исключение `Nette\IOException`. ```php $res = FileSystem::open('/path/to/file', 'r'); @@ -75,7 +77,7 @@ $res = FileSystem::open('/path/to/file', 'r'); read(string $file): string .[method] ------------------------------------ -Читает содержимое файла `$file`. При возникновении ошибки выбрасывает исключение `Nette\IOException`. +Возвращает содержимое файла `$file`. При ошибке выбрасывает исключение `Nette\IOException`. ```php $content = FileSystem::read('/path/to/file'); @@ -85,8 +87,7 @@ $content = FileSystem::read('/path/to/file'); readLines(string $file, bool $stripNewLines=true): \Generator .[method] ----------------------------------------------------------------------- -Читает содержимое файла построчно. В отличие от родной функции `file()`, она не читает весь файл в память, а читает его непрерывно, что позволяет читать файлы, размер которых превышает объем доступной памяти. Параметр `$stripNewLines` указывает, следует ли удалять символы перевода строки `\r` и `\n`. -В случае ошибки она вызывает исключение `Nette\IOException`. +Читает содержимое файла строка за строкой. В отличие от нативной функции `file()`, не загружает весь файл в память, а читает его постепенно, так что можно читать и файлы, размер которых превышает доступную память. `$stripNewLines` указывает, следует ли удалять символы конца строки `\r` и `\n`. В случае ошибки выбрасывает исключение `Nette\IOException`. ```php $lines = FileSystem::readLines('/path/to/file'); @@ -100,7 +101,7 @@ foreach ($lines as $lineNum => $line) { rename(string $origin, string $target, bool $overwrite=true): void .[method] ---------------------------------------------------------------------------- -Переименовывает или перемещает файл или каталог, указанный `$origin`, на `$target`. По умолчанию перезаписывает существующие файлы и каталоги. Если `$overwrite` установлен в `false` и `$target` уже существует, выбрасывается исключение `Nette\InvalidStateException`. Выбрасывает исключение `Nette\IOException` при возникновении ошибки. +Переименовывает или перемещает файл или каталог `$origin`. По умолчанию перезаписывает существующие файлы и каталоги. Если параметр `$overwrite` установлен в `false`, выбрасывает исключение `Nette\InvalidStateException`, если целевой файл или каталог `$target` существует. При ошибке выбрасывает исключение `Nette\IOException`. ```php FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); @@ -110,21 +111,21 @@ FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); write(string $file, string $content, int $mode=0666): void .[method] -------------------------------------------------------------------- -Записывает `$content` на `$file`. При возникновении ошибки выбрасывает исключение `Nette\IOException`. +Записывает строку `$content` в файл `$file`. При ошибке выбрасывает исключение `Nette\IOException`. ```php FileSystem::write('/path/to/file', $content); ``` -Пути .[#toc-paths] -================== +Пути +==== isAbsolute(string $path): bool .[method] ---------------------------------------- -Определяет, является ли `$path` абсолютным. +Определяет, является ли путь `$path` абсолютным. ```php FileSystem::isAbsolute('../backup'); // false @@ -135,7 +136,7 @@ FileSystem::isAbsolute('C:/backup'); // true joinPaths(string ...$segments): string .[method] ------------------------------------------------ -Объединяет все сегменты пути и нормализует результат. +Соединяет все сегменты пути и нормализует результат. ```php FileSystem::joinPaths('a', 'b', 'file.txt'); // 'a/b/file.txt' @@ -146,7 +147,7 @@ FileSystem::joinPaths('/a/', '/../b'); // '/b' normalizePath(string $path): string .[method] --------------------------------------------- -Нормализует `..` и `.` и разделители каталогов в пути. +Нормализует `..` и `.` и разделители каталогов в пути к системным (`/`). ```php FileSystem::normalizePath('/file/.'); // '/file/' @@ -159,7 +160,7 @@ FileSystem::normalizePath('file/../../bar'); // '/../bar' unixSlashes(string $path): string .[method] ------------------------------------------- -Преобразует косые черты в `/`, используемые в системах Unix. +Преобразует обратные слеши в `/`, используемые в Unix-системах. ```php $path = FileSystem::unixSlashes($path); @@ -169,8 +170,45 @@ $path = FileSystem::unixSlashes($path); platformSlashes(string $path): string .[method] ----------------------------------------------- -Преобразует косые черты в символы, характерные для текущей платформы, т.е. `\` в Windows и `/` в других системах. +Преобразует слеши в символы, специфичные для текущей платформы, т.е. `\` в Windows и `/` в других системах. ```php $path = FileSystem::platformSlashes($path); ``` + + +resolvePath(string $basePath, string $path): string .[method]{data-version:4.0.6} +--------------------------------------------------------------------------------- + +Выводит окончательный путь из пути `$path` относительно базового каталога `$basePath`. Абсолютные пути (`/foo`, `C:/foo`) оставляет без изменений (только нормализует слеши), относительные пути присоединяет к базовому пути. + +```php +// В Windows слеши в выводе были бы обратными (\) +FileSystem::resolvePath('/base/dir', '/abs/path'); // '/abs/path' +FileSystem::resolvePath('/base/dir', 'rel'); // '/base/dir/rel' +FileSystem::resolvePath('base/dir', '../file.txt'); // 'base/file.txt' +FileSystem::resolvePath('base', ''); // 'base' +``` + + +Статический vs нестатический доступ +=================================== + +Чтобы, например, для целей тестирования можно было легко заменить класс другим (mock-объектом), используйте его нестатически: + +```php +class AnyClassUsingFileSystem +{ + public function __construct( + private FileSystem $fileSystem, + ) { + } + + public function readConfig(): string + { + return $this->fileSystem->read(/* ... */); + } + + ... +} +``` diff --git a/utils/ru/finder.texy b/utils/ru/finder.texy index f873f51610..70faf05b5b 100644 --- a/utils/ru/finder.texy +++ b/utils/ru/finder.texy @@ -1,8 +1,8 @@ -Finder: Поиск файлов +Finder: поиск файлов ******************** .[perex] -Нужно найти файлы, соответствующие определенной маске? Вам поможет программа Finder. Это универсальный и быстрый инструмент для просмотра структуры каталогов. +Вам нужно найти файлы, соответствующие определенной маске? Finder вам в этом поможет. Это универсальный и быстрый инструмент для обхода структуры каталогов. Установка: @@ -11,17 +11,17 @@ Finder: Поиск файлов composer require nette/utils ``` -В примерах предполагается, что псевдоним уже создан: +Примеры предполагают, что создан псевдоним: ```php use Nette\Utils\Finder; ``` -Использование .[#toc-using] ---------------------------- +Использование +------------- -Сначала посмотрим, как можно использовать [api:Nette\Utils\Finder] для перечисления имен файлов с расширениями `.txt` и `.md` в текущем каталоге: +Сначала мы покажем, как вы можете с помощью [api:Nette\Utils\Finder] вывести имена файлов с расширениями `.txt` и `.md` в текущем каталоге: ```php foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { @@ -29,96 +29,97 @@ foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { } ``` -По умолчанию для поиска используется текущий каталог, но вы можете изменить его с помощью методов [in() или from() |#Where to search?]. -Переменная `$file` является экземпляром класса [FileInfo |#FileInfo] с множеством полезных методов. Ключ `$name` содержит путь к файлу в виде строки. +Каталог по умолчанию для поиска — текущий каталог, но вы можете изменить его с помощью методов [in() или from() |#Где искать]. Переменная `$file` является экземпляром класса [#FileInfo] с множеством полезных методов. В ключе `$name` находится путь к файлу в виде строки. -Что искать? .[#toc-what-to-search-for] --------------------------------------- +Что искать? +----------- -В дополнение к методу `findFiles()` существует также `findDirectories()`, который ищет только в каталогах, и `find()`, который ищет в обоих каталогах. Эти методы статические, поэтому их можно вызывать без создания экземпляра. Параметр mask является необязательным, если вы его не укажете, поиск будет производиться во всех каталогах. +Кроме метода `findFiles()`, существует также `findDirectories()`, который ищет только каталоги, и `find()`, который ищет и то, и другое. Эти методы статические, поэтому их можно вызывать без создания экземпляра. Параметр с маской необязателен, если его не указать, будет найдено все. ```php foreach (Finder::find() as $file) { - echo $file; // теперь все файлы и каталоги перечислены + echo $file; // теперь будут выведены все файлы и каталоги } ``` -Используйте методы `files()` и `directories()`, чтобы добавить, что еще нужно искать. Методы могут вызываться многократно, а в качестве параметра может быть предоставлен массив масок: +С помощью методов `files()` и `directories()` вы можете добавлять, что еще нужно искать. Методы можно вызывать повторно, и в качестве параметра можно указать массив масок: ```php Finder::findDirectories('vendor') // все каталоги - ->files(['*.php', '*.phpt']); // плюс все PHP файлы + ->files(['*.php', '*.phpt']); // плюс все PHP-файлы ``` -Альтернативой статическим методам является создание экземпляра с помощью `new Finder` (свежий объект, созданный таким образом, ничего не ищет) и указание того, что искать с помощью `files()` и `directories()`: +Альтернативой статическим методам является создание экземпляра с помощью `new Finder` (так созданный "свежий" объект ничего не ищет) и указание, что искать, с помощью `files()` и `directories()`: ```php (new Finder) - ->directories() // все каталоги - ->files('*.php'); // плюс все файлы PHP + ->directories() // все каталоги + ->files('*.php'); // плюс все PHP-файлы ``` -Вы можете использовать [подстановочные знаки |#wildcards] `*`, `**`, `?` and `[...]` в маске. Можно даже указывать в каталогах, например, `src/*.php` будет искать все файлы PHP в каталоге `src`. +В маске можно использовать [метасимволы |#Метасимволы Wildcards] `*`, `**`, `?` и `[...]`. Вы даже можете указать каталоги, например `src/*.php` найдет все PHP-файлы в каталоге `src`. +Символические ссылки также считаются каталогами или файлами. -Где искать? .[#toc-where-to-search] ------------------------------------ -Директория поиска по умолчанию - это текущая директория. Вы можете изменить это, используя методы `in()` и `from()`. Как видно из названий методов, `in()` ищет только в текущем каталоге, а `from()` ищет и в его подкаталогах (рекурсивно). Если вам нужен рекурсивный поиск в текущем каталоге, вы можете использовать `from('.')`. +Где искать? +----------- -Эти методы можно вызывать несколько раз или передавать им несколько путей в виде массивов, тогда поиск файлов будет производиться во всех каталогах. Если один из каталогов не существует, то произойдет ошибка `Nette\UnexpectedValueException`. +Каталог по умолчанию для поиска — текущий каталог. Вы измените его с помощью методов `in()` и `from()`. Как видно из названий методов, `in()` ищет только в данном каталоге, в то время как `from()` ищет и в его подкаталогах (рекурсивно). Если вы хотите искать рекурсивно в текущем каталоге, вы можете использовать `from('.')`. + +Эти методы можно вызывать несколько раз или передавать им несколько путей в виде массива, файлы тогда будут искаться во всех каталогах. Если какой-либо из каталогов не существует, будет выброшено исключение `Nette\UnexpectedValueException`. ```php Finder::findFiles('*.php') ->in(['src', 'tests']) // ищет непосредственно в src/ и tests/ - ->from('vendor'); // ищет также в подкаталогах vendor/ + ->from('vendor'); // ищет и в подкаталогах vendor/ ``` -Относительные пути являются относительными по отношению к текущему каталогу. Конечно, можно указать и абсолютные пути: +Относительные пути указываются относительно текущего каталога. Конечно, можно указать и абсолютные пути: ```php Finder::findFiles('*.php') ->in('/var/www/html'); ``` -[Символы |#wildcards] подстановки `*`, `**`, `?` can be used in the path. For example, you can use the path `src/*/*.php` для поиска всех файлов PHP в каталогах второго уровня в каталоге `src`. Символ `**`, называемый globstar, является мощным козырем, поскольку позволяет искать и в подкаталогах: используйте `src/**/tests/*.php` для поиска всех файлов PHP в каталоге `tests`, расположенных в `src` или любом из его подкаталогов. +В пути можно использовать [метасимволы |#Метасимволы Wildcards] `*`, `**`, `?`. Так, например, с помощью пути `src/*/*.php` вы можете искать все PHP-файлы в каталогах второго уровня в каталоге `src`. Символ `**`, называемый globstar, является мощным козырем, потому что позволяет искать и в подкаталогах: с помощью `src/**/tests/*.php` вы ищете все PHP-файлы в каталоге `tests`, находящемся в `src` или любом его подкаталоге. -С другой стороны, подстановочные символы `[...]` не поддерживаются в пути, т.е. они не имеют специального значения, чтобы избежать нежелательного поведения в случае, если вы ищете, например, `in(__DIR__)` и случайно в пути появляются символы `[]`. +Наоборот, метасимволы `[...]` в пути не поддерживаются, т.е. не имеют специального значения, чтобы не возникало нежелательного поведения в случае, если вы будете искать, например, `in(__DIR__)`, и случайно в пути будут встречаться символы `[]`. -При глубоком поиске файлов и каталогов сначала возвращается родительский каталог, а затем содержащиеся в нем файлы, что можно изменить на противоположное с помощью `childFirst()`. +При поиске файлов и каталогов в глубину сначала возвращается родительский каталог, и только потом файлы, содержащиеся в нем, что можно изменить с помощью `childFirst()`. -Дикие символы .[#toc-wildcards] -------------------------------- +Метасимволы (Wildcards) +----------------------- В маске можно использовать несколько специальных символов: -- `*` - replaces any number of arbitrary characters (except `/`) -- `**` - заменяет любое количество произвольных символов, включая `/` (т.е. может осуществляться многоуровневый поиск) -- `?` - replaces one arbitrary character (except `/`) +- `*` - заменяет любое количество любых символов (кроме `/`) +- `**` - заменяет любое количество любых символов, включая `/` (т.е. можно искать многоуровнево) +- `?` - заменяет один любой символ (кроме `/`) - `[a-z]` - заменяет один символ из списка символов в квадратных скобках - `[!a-z]` - заменяет один символ вне списка символов в квадратных скобках Примеры использования: - `img/?.png` - файлы с однобуквенным именем `0.png`, `1.png`, `x.png` и т.д. -- `logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log` - файлы журналов в формате `YYYY-MM-DD` -- `src/**/tests/*` - файлы в директории `src/tests`, `src/foo/tests`, `src/foo/bar/tests` и т.д. +- `logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log` - логи в формате `YYYY-MM-DD` +- `src/**/tests/*` - файлы в каталоге `src/tests`, `src/foo/tests`, `src/foo/bar/tests` и так далее. - `docs/**.md` - все файлы с расширением `.md` во всех подкаталогах каталога `docs` -Исключая .[#toc-excluding] --------------------------- +Исключение +---------- -Используйте метод `exclude()` для исключения файлов и каталогов из поиска. Вы указываете маску, которой не должен соответствовать файл. Пример поиска файлов `*.txt`, кроме тех, которые содержат букву `X` в имени: +С помощью метода `exclude()` можно исключить файлы и каталоги из поиска. Вы указываете маску, которой файл не должен соответствовать. Пример поиска файлов `*.txt`, кроме тех, которые содержат в имени букву `X`: ```php Finder::findFiles('*.txt') ->exclude('*X*'); ``` -Используйте `exclude()`, чтобы пропустить просмотренные подкаталоги: +Для пропуска обходимых подкаталогов используйте `exclude()`: ```php Finder::findFiles('*.php') @@ -127,12 +128,12 @@ Finder::findFiles('*.php') ``` -Фильтрация .[#toc-filtering] ----------------------------- +Фильтрация +---------- -Finder предлагает несколько методов фильтрации результатов (т.е. их сокращения). Их можно комбинировать и вызывать многократно. +Finder предлагает несколько методов для фильтрации результатов (т.е. их сокращения). Вы можете их комбинировать и вызывать повторно. -Используйте `size()` для фильтрации по размеру файла. Таким образом, мы находим файлы размером от 100 до 200 байт: +С помощью `size()` мы фильтруем по размеру файла. Так мы найдем файлы размером в диапазоне от 100 до 200 байт: ```php Finder::findFiles('*.php') @@ -140,7 +141,7 @@ Finder::findFiles('*.php') ->size('<=', 200); ``` -Метод `date()` фильтрует по дате последнего изменения файла. Значения могут быть абсолютными или относительными к текущей дате и времени, например, так можно найти файлы, измененные за последние две недели: +Метод `date()` фильтрует по дате последнего изменения файла. Значения могут быть абсолютными или относительными к текущей дате и времени, например, так мы найдем файлы, измененные за последние две недели: ```php Finder::findFiles('*.php') @@ -150,9 +151,9 @@ Finder::findFiles('*.php') Обе функции понимают операторы `>`, `>=`, `<`, `<=`, `=`, `!=`, `<>`. -Finder также позволяет фильтровать результаты с помощью пользовательских функций. Функция получает в качестве параметра объект `Nette\Utils\FileInfo` и должна вернуть `true`, чтобы включить файл в результаты. +Finder также позволяет фильтровать результаты с помощью пользовательских функций. Функция получает в качестве параметра объект `Nette\Utils\FileInfo` и должна вернуть `true`, чтобы файл был включен в результаты. -Пример: поиск файлов PHP, содержащих строку `Nette` (без учета регистра): +Пример: поиск PHP-файлов, содержащих строку `Nette` (без учета регистра): ```php Finder::findFiles('*.php') @@ -160,42 +161,42 @@ Finder::findFiles('*.php') ``` -Глубинная фильтрация .[#toc-depth-filtering] --------------------------------------------- +Фильтрация в глубину +-------------------- -При рекурсивном поиске можно задать максимальную глубину проползания с помощью метода `limitDepth()`. Если вы зададите `limitDepth(1)`, то будут просмотрены только первые подкаталоги, `limitDepth(0)` отключает фильтрацию глубины, а значение -1 отменяет ограничение. +При рекурсивном поиске вы можете установить максимальную глубину обхода с помощью метода `limitDepth()`. Если вы установите `limitDepth(1)`, обходятся только первые подкаталоги, `limitDepth(0)` отключает обход в глубину, а значение -1 отменяет лимит. -Finder позволяет использовать свои собственные функции, чтобы решить, в какой каталог войти при просмотре. Функция получает в качестве параметра объект `Nette\Utils\FileInfo` и должна вернуть `true`, чтобы войти в каталог: +Finder позволяет с помощью пользовательских функций решать, в какой каталог входить при обходе. Функция получает в качестве параметра объект `Nette\Utils\FileInfo` представляющий каталог и должна вернуть `true`, чтобы войти в него: ```php Finder::findFiles('*.php') - ->descentFilter($file->getBasename() !== 'temp'); + ->descentFilter(fn($file) => $file->getBasename() !== 'temp'); ``` -Сортировка .[#toc-sorting] --------------------------- +Сортировка +---------- Finder также предлагает несколько функций для сортировки результатов. -Метод `sortByName()` сортирует результаты по имени файла. Сортировка является естественной, т.е. она правильно обрабатывает цифры в именах и возвращает, например, `foo1.txt` перед `foo10.txt`. +Метод `sortByName()` сортирует результаты по именам файлов. Сортировка естественная, то есть правильно справляется с числами в именах и возвращает, например, `foo1.txt` перед `foo10.txt`. -Finder также позволяет сортировать с помощью пользовательской функции. Она принимает в качестве параметров два объекта `Nette\Utils\FileInfo` и должна возвращать результат сравнения с оператором `<=>`т.е. `-1`, `0` nebo `1`. Например, так мы сортируем файлы по размеру: +Finder также позволяет сортировать с помощью пользовательской функции. Она получает в качестве параметра два объекта `Nette\Utils\FileInfo` и должна вернуть результат сравнения оператором `<=>`, то есть `-1`, `0` или `1`. Например, так мы отсортируем файлы по размеру: ```php $finder->sortBy(fn($a, $b) => $a->getSize() <=> $b->getSize()); ``` -Несколько разных поисков .[#toc-multiple-different-searches] ------------------------------------------------------------- +Несколько разных поисков +------------------------ -Если вам нужно найти несколько разных файлов в разных местах или отвечающих разным критериям, используйте метод `append()`. Он возвращает новый объект `Finder`, поэтому вы можете использовать цепочку вызовов методов: +Если вам нужно найти несколько разных файлов в разных местах или удовлетворяющих другим критериям, используйте метод `append()`. Он возвращает новый объект `Finder`, так что можно цепочкой вызывать методы: ```php -($finder = new Finder) // сохраняем первый Finder в переменной $finder! - ->files('*.php') // поиск *.php файлов в src/ +($finder = new Finder) // в переменную $finder мы сохраним первый Finder! + ->files('*.php') // в src/ ищем файлы *.php ->from('src') ->append() ->files('*.md') // в docs/ ищем файлы *.md @@ -204,7 +205,7 @@ $finder->sortBy(fn($a, $b) => $a->getSize() <=> $b->getSize()); ->files('*.json'); // в текущей папке ищем файлы *.json ``` -В качестве альтернативы вы можете использовать метод `append()` для добавления определенного файла (или массива файлов). Тогда он возвращает тот же объект `Finder`: +Альтернативно можно использовать метод `append()` для добавления конкретного файла (или массива файлов). Тогда он возвращает тот же объект `Finder`: ```php $finder = Finder::findFiles('*.txt') @@ -212,12 +213,12 @@ $finder = Finder::findFiles('*.txt') ``` -FileInfo .[#toc-fileinfo] -------------------------- +FileInfo +-------- -[Nette\Utils\FileInfo |api:] - это класс, представляющий файл или каталог в результатах поиска. Он является расширением класса [SplFileInfo |php:SplFileInfo], который предоставляет такую информацию, как размер файла, дата последнего изменения, имя, путь и т.д. +[Nette\Utils\FileInfo |api:] — это класс, представляющий файл или каталог в результатах поиска. Это расширение класса [SplFileInfo |php:SplFileInfo], которое предоставляет информацию, такую как размер файла, дата последнего изменения, имя, путь и т.д. -Кроме того, он предоставляет методы для возврата относительных путей, что полезно при углубленном поиске: +Кроме того, он предоставляет методы для возврата относительного пути, что полезно при обходе в глубину: ```php foreach (Finder::findFiles('*.jpg')->from('.') as $file) { @@ -226,7 +227,7 @@ foreach (Finder::findFiles('*.jpg')->from('.') as $file) { } ``` -У вас также есть методы для чтения и записи содержимого файла: +Далее вам доступны методы для чтения и записи содержимого файла: ```php foreach ($finder as $file) { @@ -237,12 +238,12 @@ foreach ($finder as $file) { ``` -Возвращение результатов в виде массива .[#toc-returning-results-as-an-array] ----------------------------------------------------------------------------- +Возврат результатов в виде массива +---------------------------------- -Как видно из примеров, Finder реализует интерфейс `IteratorAggregate`, поэтому вы можете использовать `foreach` для просмотра результатов. Он запрограммирован так, что результаты загружаются только по мере просмотра, поэтому если у вас большое количество файлов, он не будет ждать, пока все они будут прочитаны. +Как было видно в примерах, Finder реализует интерфейс `IteratorAggregate`, так что вы можете использовать `foreach` для обхода результатов. Он запрограммирован так, что результаты загружаются только в процессе обхода, так что если у вас большое количество файлов, не нужно ждать, пока все они будут прочитаны. -Вы также можете вернуть результаты в виде массива объектов `Nette\Utils\FileInfo`, используя метод `collect()`. Массив не ассоциативный, а числовой. +Результаты также можно получить в виде массива объектов `Nette\Utils\FileInfo`, с помощью метода `collect()`. Массив не ассоциативный, а числовой. ```php $array = $finder->findFiles('*.php')->collect(); diff --git a/utils/ru/floats.texy b/utils/ru/floats.texy index ff06b4f298..a7eec8da27 100644 --- a/utils/ru/floats.texy +++ b/utils/ru/floats.texy @@ -1,8 +1,8 @@ -Работа с поплавками -******************* +Работа с числами с плавающей точкой (float) +******************************************* .[perex] -[api:Nette\Utils\Floats] это статический класс с полезными функциями для сравнения десятичных чисел. +[api:Nette\Utils\Floats] — это статический класс с полезными функциями для сравнения десятичных чисел (float). Установка: @@ -11,18 +11,17 @@ composer require nette/utils ``` -Во всех примерах предполагается, что псевдоним уже создан: +Все примеры предполагают, что создан псевдоним: ```php use Nette\Utils\Floats; ``` -Мотивация .[#toc-motivation] -============================ +Мотивация +========= -Почему класс для сравнения поплавков, спросите вы? Я имею в виду, я могу использовать операторы `<`, `>`, `===` и все готово. -Это не совсем так. Как вы думаете, что выведет этот код? +Вы спрашиваете себя, зачем вообще класс для сравнения float? Ведь я могу использовать операторы `<`, `>`, `===` и все будет в порядке. Это не совсем так. Как вы думаете, что выведет этот код? ```php $a = 0.1 + 0.2; @@ -30,21 +29,24 @@ $b = 0.3; echo $a === $b ? 'same' : 'not same'; ``` -Если вы запустите этот код, некоторые из вас удивятся, увидев, что программа печатает `not same`. +Если вы запустите код, некоторые из вас наверняка удивятся, что программа вывела `not same`. -При математических операциях с десятичными числами ошибки возникают из-за преобразования десятичных чисел в двоичные. Например, `0.1 + 0.2` выводит `0.300000000000000044…`. Поэтому при проведении сравнений мы должны допускать небольшое расхождение с определенным десятичным знаком. +При математических операциях с десятичными числами возникают ошибки из-за преобразования между десятичной и двоичной системами счисления. Например, `0.1 + 0.2` дает `0.300000000000000044…`. Поэтому при сравнении мы должны допускать небольшую погрешность начиная с определенного десятичного знака. -Вот что делает класс `Floats`. Следующее сравнение будет работать так, как ожидается: +И именно это делает класс `Floats`. Следующее сравнение уже будет работать как ожидалось: ```php echo Floats::areEqual($a, $b) ? 'same' : 'not same'; // same ``` -При попытке сравнить `NAN` возникает исключение `\LogicException`. +При попытке сравнить `NAN` выбрасывает исключение `\LogicException`. +.[tip] +Класс `Floats` допускает различия меньше `1e-10`. Если вам нужно работать с большей точностью, лучше используйте библиотеку [BCMath|https://php.net/manual/ru/book.bc.php]. -Сравнение плавающих значений .[#toc-float-comparison] -===================================================== + +Сравнение float +=============== areEqual(float $a, float $b): bool .[method] @@ -82,7 +84,7 @@ Floats::isLessThanOrEqualTo(10.25, 10.25); // true isGreaterThan(float $a, float $b): bool .[method] ------------------------------------------------- -Возвращает `true`, если `$a` > `$b` применяется . +Возвращает `true`, если `$a` > `$b`. ```php Floats::isGreaterThan(9.5, -10.2); // true @@ -104,19 +106,19 @@ Floats::isGreaterThanOrEqualTo(10.2, 10.2); // true compare(float $a, float $b): int .[method] ------------------------------------------ -Если `$a` < `$b`, возвращается `-1`, если равно `0` a pokud je `$a` > `$b` возвращается `1`. +Если `$a` < `$b`, возвращает `-1`, если они равны, возвращает `0`, и если `$a` > `$b`, возвращает `1`. -Может использоваться, например, с функцией `usort`. +Можно использовать, например, с функцией `usort`. ```php $arr = [1, 5, 2, -3.5]; -usort($arr, [Float::class, 'compare']); -// $arr je nyní [-3.5, 1, 2, 5] +usort($arr, [Floats::class, 'compare']); +// $arr теперь [-3.5, 1, 2, 5] ``` -Вспомогательные функции .[#toc-helpers-functions] -================================================= +Вспомогательные функции +======================= isZero(float $value): bool .[method] diff --git a/utils/ru/helpers.texy b/utils/ru/helpers.texy index c86388467f..6c3bed1951 100644 --- a/utils/ru/helpers.texy +++ b/utils/ru/helpers.texy @@ -1,8 +1,8 @@ -Функции-помощники -***************** +Вспомогательные функции +*********************** .[perex] -[api:Nette\Utils\Helpers] это статический класс с полезными функциями. +[api:Nette\Utils\Helpers] — это статический класс с полезными функциями. Установка: @@ -11,7 +11,7 @@ composer require nette/utils ``` -Во всех примерах предполагается, что псевдоним уже создан: +Все примеры предполагают, что создан псевдоним: ```php use Nette\Utils\Helpers; @@ -21,7 +21,7 @@ use Nette\Utils\Helpers; capture(callable $cb): string .[method] --------------------------------------- -Выполняет обратный вызов и возвращает захваченный вывод в виде строки. +Выполняет callback и возвращает захваченный вывод в виде строки. ```php $res = Helpers::capture(function () use ($template) { @@ -33,7 +33,7 @@ $res = Helpers::capture(function () use ($template) { clamp(int|float $value, int|float $min, int|float $max): int|float .[method] ---------------------------------------------------------------------------- -Ограничивает значение заданным диапазоном включения min и max. +Ограничивает значение заданным включительным диапазоном min и max. ```php Helpers::clamp($level, 0, 255); @@ -43,8 +43,7 @@ Helpers::clamp($level, 0, 255); compare(mixed $left, string $operator, mixed $right): bool .[method] -------------------------------------------------------------------- -Сравнивает два значения так же, как это делает PHP. Различает операторы `>`, `>=`, `<`, `<=`, `=`, `==`, `===`, `!=`, `!==`, `<>`. -Эта функция полезна в ситуациях, когда оператор является изменяемым. +Сравнивает два значения так же, как это делает PHP. Различает операторы `>`, `>=`, `<`, `<=`, `=`, `==`, `===`, `!=`, `!==`, `<>`. Функция полезна в ситуациях, когда оператор является переменной. ```php Helpers::compare(10, '<', 20); // true @@ -54,7 +53,7 @@ Helpers::compare(10, '<', 20); // true falseToNull(mixed $value): mixed .[method] ------------------------------------------ -Конвертирует `false` в `null`, не изменяет другие значения. +Преобразует `false` в `null`, другие значения не изменяет. ```php Helpers::falseToNull(false); // null @@ -65,7 +64,7 @@ Helpers::falseToNull(123); // 123 getLastError(): string .[method] -------------------------------- -Возвращает последнюю ошибку в PHP или пустую строку, если ошибка не произошла. В отличие от `error_get_last()`, не зависит от директивы PHP `html_errors` и всегда возвращает текст, а не HTML. +Возвращает последнюю ошибку в PHP или пустую строку, если ошибки не произошло. В отличие от `error_get_last()`, не подвержен влиянию директивы PHP `html_errors` и всегда возвращает текст, а не HTML. ```php Helpers::getLastError(); @@ -75,13 +74,13 @@ Helpers::getLastError(); getSuggestion(string[] $possibilities, string $value): ?string .[method] ------------------------------------------------------------------------ -Из предложенных вариантов `$possibilities` ищет строку, которая наиболее похожа на `$value`, но не совпадает с ней. Он поддерживает только 8-битное кодирование. +Из предложенных вариантов `$possibilities` ищет строку, которая наиболее похожа на `$value`, но не совпадает с ней. Поддерживает только 8-битную кодировку. -Это полезно, если определенная опция недействительна, и мы хотим предложить пользователю аналогичную (но другую, поэтому та же строка игнорируется). Вот как Nette создает сообщения `did you mean ...?`. +Подходит в случае, если определенный вариант недействителен, и мы хотим посоветовать пользователю похожий (но другой, поэтому одинаковая строка игнорируется). Таким образом Nette формирует сообщения `did you mean ...?`. ```php $items = ['foo', 'bar', 'baz']; Helpers::getSuggestion($items, 'fo'); // 'foo' Helpers::getSuggestion($items, 'barr'); // 'bar' -Helpers::getSuggestion($items, 'baz'); // 'bar', ne 'baz' +Helpers::getSuggestion($items, 'baz'); // 'bar', не 'baz' ``` diff --git a/utils/ru/html-elements.texy b/utils/ru/html-elements.texy index dc898cf51e..ee6ae3469a 100644 --- a/utils/ru/html-elements.texy +++ b/utils/ru/html-elements.texy @@ -1,16 +1,16 @@ -Элементы HTML +HTML-элементы ************* .[perex] -Класс [api:Nette\Utils\Html] - это помощник для генерации HTML-кода, который не допускает уязвимости Cross Site Scripting (XSS). +Класс [api:Nette\Utils\Html] является помощником для генерации HTML-кода, который предотвращает возникновение уязвимости Cross Site Scripting (XSS). -Принцип его работы заключается в том, что его объекты - это элементы HTML, которым мы задаем параметры и позволяем их отрисовывать: +Принцип работы заключается в том, что его объекты представляют HTML-элементы, которым мы устанавливаем параметры и затем отображаем их: ```php -$el = Html::el('img'); // создает элемент +$el = Html::el('img'); // создает элемент $el->src = 'image.jpg'; // устанавливает атрибут src -echo $el; // печатает '' +echo $el; // выводит '' ``` Установка: @@ -19,29 +19,29 @@ echo $el; // печатает '' composer require nette/utils ``` -Во всех примерах предполагается, что псевдоним уже создан: +Все примеры предполагают, что создан псевдоним: ```php use Nette\Utils\Html; ``` -Создание элемента HTML .[#toc-creating-an-html-element] -======================================================= +Создание HTML-элемента +====================== -Создайте элемент, используя метод `Html::el()`: +Элемент создаем методом `Html::el()`: ```php $el = Html::el('img'); // создает элемент ``` -Помимо имени, вы можете указать другие атрибуты в синтаксисе HTML: +Кроме имени, вы можете указать и другие атрибуты в синтаксисе HTML: ```php $el = Html::el('input type=text class="red important"'); ``` -Или передайте их как ассоциативное поле со вторым параметром: +Или передать их как ассоциативный массив вторым параметром: ```php $el = Html::el('input', [ @@ -50,39 +50,39 @@ $el = Html::el('input', [ ]); ``` -Изменение и возвращение имени элемента: +Изменение и возврат имени элемента: ```php $el->setName('img'); $el->getName(); // 'img' -$el->isEmpty(); // true, так как является пустым элементом +$el->isEmpty(); // true, поскольку является пустым элементом (void element) ``` -Атрибуты HTML .[#toc-html-attributes] -===================================== +HTML-атрибуты +============= -Существует три способа изменения и чтения отдельных атрибутов HTML, и вы сами решаете, какой из них вам больше нравится. Первый - через собственность: +Отдельные HTML-атрибуты мы можем изменять и читать тремя способами, какой из них вам больше понравится, зависит от вас. Первый — через свойства: ```php $el->src = 'image.jpg'; // устанавливает атрибут src echo $el->src; // 'image.jpg' -unset($el->src); // снимаем атрибут +unset($el->src); // удаляет атрибут // или $el->src = null; ``` -Второй способ - вызов методов, которые, в отличие от установки свойств, можно объединять в цепочки: +Второй путь — вызов методов, которые, в отличие от установки свойств, мы можем вызывать цепочкой: ```php $el = Html::el('img')->src('image.jpg')->alt('photo'); // photo -$el->alt(null); // отмена атрибута +$el->alt(null); // удаление атрибута ``` -И третий способ - самый многословный: +И третий способ — самый многословный: ```php $el = Html::el('img') @@ -94,9 +94,9 @@ echo $el->getAttribute('src'); // 'image.jpg' $el->removeAttribute('alt'); ``` -Атрибуты могут быть установлены массово с помощью `addAttributes(array $attrs)` и удалены с помощью `removeAttributes(array $attrNames)`. +Массово атрибуты можно установить с помощью `addAttributes(array $attrs)` и удалить с помощью `removeAttributes(array $attrNames)`. -Значение атрибута не обязательно должно быть строкой, вы также можете использовать логические значения для логических атрибутов: +Значением атрибута не обязательно должна быть только строка, можно использовать и логические значения для логических атрибутов (boolean attributes): ```php $checkbox = Html::el('input')->type('checkbox'); @@ -104,17 +104,17 @@ $checkbox->checked = true; // $checkbox->checked = false; // ``` -Атрибут также может быть массивом значений, которые выводятся через пробелы, что полезно, например, для классов CSS: +Атрибутом может быть и массив значений, которые выводятся разделенными пробелами, что подходит, например, для CSS-классов: ```php $el = Html::el('input'); $el->class[] = 'active'; -$el->class[] = null; // null se ignoruje +$el->class[] = null; // null игнорируется $el->class[] = 'top'; echo $el; // '' ``` -Альтернативой является ассоциативный массив, где значения указывают, должен ли ключ быть выведен: +Альтернативой является ассоциативный массив, где значения `true`/`false` указывают, должен ли ключ быть выведен: ```php $el = Html::el('input'); @@ -123,7 +123,7 @@ $el->class['top'] = false; echo $el; // '' ``` -Стили CSS могут быть записаны в виде ассоциативных полей: +CSS-стили можно записывать в виде ассоциативных массивов: ```php $el = Html::el('input'); @@ -132,7 +132,7 @@ $el->style['display'] = 'block'; echo $el; // '' ``` -Сейчас мы использовали свойство, но то же самое можно написать, используя методы: +Сейчас мы использовали свойства, но то же самое можно записать с помощью методов: ```php $el = Html::el('input'); @@ -141,7 +141,7 @@ $el->style('display', 'block'); echo $el; // '' ``` -Или даже в самом кратком виде: +Или даже самым многословным способом: ```php $el = Html::el('input'); @@ -150,7 +150,7 @@ $el->appendAttribute('style', 'display', 'block'); echo $el; // '' ``` -И последняя деталь: метод `href()` может облегчить составление параметров запроса в URL: +Еще одна мелочь напоследок: метод `href()` умеет упрощать составление query-параметров в URL: ```php echo Html::el('a')->href('index.php', [ @@ -161,19 +161,19 @@ echo Html::el('a')->href('index.php', [ ``` -Атрибуты данных .[#toc-data-attributes] ---------------------------------------- +Data-атрибуты +------------- -Атрибуты данных имеют специальную поддержку. Поскольку их имена содержат дефисы, доступ через свойства и методы не так элегантен, поэтому существует метод `data()`: +Особую поддержку имеют data-атрибуты (`data-*`). Поскольку их имена содержат дефисы, доступ через свойства и методы не так элегантен, поэтому существует метод `data()`: ```php $el = Html::el('input'); -$el->{'data-max-size'} = '500x300'; // není tolik elegantní -$el->data('max-size', '500x300'); // je elegatní +$el->{'data-max-size'} = '500x300'; // не так элегантно +$el->data('max-size', '500x300'); // элегантно echo $el; // '' ``` -Если значением атрибута данных является массив, он автоматически сериализуется в JSON: +Если значением data-атрибута является массив, он автоматически сериализуется в JSON: ```php $el = Html::el('input'); @@ -182,10 +182,10 @@ echo $el; // '' ``` -Содержание элемента .[#toc-element-content] -=========================================== +Содержимое элемента +=================== -Установите внутреннее содержимое элемента с помощью методов `setHtml()` или `setText()`. Используйте первый вариант только в том случае, если вы знаете, что передаете в параметре надежно защищенную строку HTML. +Внутреннее содержимое элемента устанавливаем методами `setHtml()` или `setText()`. Первый из них используйте только в случае, если вы уверены, что в параметре передаете надежно безопасную HTML-строку. ```php echo Html::el('span')->setHtml('hello
                                                                                                                            '); @@ -195,7 +195,7 @@ echo Html::el('span')->setText('10 < 20'); // '10 < 20' ``` -И наоборот, для получения внутреннего содержимого используйте методы `getHtml()` или `getText()`. Последний метод удаляет HTML-теги из вывода и преобразует HTML-сущности в символы. +И наоборот, внутреннее содержимое получаем методами `getHtml()` или `getText()`. Вторая из них удаляет из вывода HTML-теги и преобразует HTML-сущности в символы. ```php echo $el->getHtml(); // '10 < 20' @@ -203,10 +203,10 @@ echo $el->getText(); // '10 < 20' ``` -Дочерние узлы .[#toc-child-nodes] ---------------------------------- +Дочерние узлы +------------- -Внутри элемента также может быть массив дочерних узлов. Каждый из них может быть либо строкой, либо другим элементом `Html`. Они вставляются с помощью `addHtml()` или `addText()`: +Внутреннее содержимое элемента может быть также массивом дочерних узлов. Каждый из них может быть либо строкой, либо другим `Html` элементом. Вставляем их с помощью `addHtml()` или `addText()`: ```php $el = Html::el('span') @@ -216,16 +216,16 @@ $el = Html::el('span') // hello
                                                                                                                            10 < 20
                                                                                                                            ``` -Другой способ создания и вставки нового узла `Html`: +Другой способ создания и вставки нового `Html` узла: ```php -$el = Html::el('ul') - ->create('li', ['class' => 'first']) - ->setText('první'); +$ul = Html::el('ul'); +$ul->create('li', ['class' => 'first']) + ->setText('první'); //
                                                                                                                            • první
                                                                                                                            ``` -Вы можете работать с узлами так, как если бы они были массивами. То есть, обращайтесь к каждому из них с помощью квадратных скобок, считайте их с помощью `count()` и выполняйте итерации: +С узлами можно работать так же, как если бы это был массив. То есть получать доступ к отдельным из них с помощью квадратных скобок, посчитать их с помощью `count()` и итерировать по ним: ```php $el = Html::el('div'); @@ -238,20 +238,20 @@ foreach ($el as $child) { /* ... */ } echo count($el); // 2 ``` -Новый узел может быть вставлен в определенное место с помощью `insert(?int $index, $child, bool $replace = false)`. Если `$replace = false`, то будет вставлен элемент в позицию `$index` и перемещены остальные. Если это `$index = null`, то элемент добавляется последним. +Новый узел можно вставить в конкретное место с помощью `insert(?int $index, $child, bool $replace = false)`. Если `$replace = false`, вставляет элемент на позицию `$index` и сдвигает остальные. Если `$index = null`, добавляет элемент в конец. ```php -// vloží prvek na první pozici a ostatní posune +// вставляет элемент на первую позицию и сдвигает остальные $el->insert(0, Html::el('span')); ``` -Все узлы извлекаются с помощью метода `getChildren()` и удаляются с помощью метода `removeChildren()`. +Все узлы получаем методом `getChildren()` и удаляем их методом `removeChildren()`. -Создание фрагмента документа .[#toc-creating-a-document-fragment] ------------------------------------------------------------------ +Создание document fragment +-------------------------- -Если мы хотим работать с массивом узлов и нас не интересует элемент обертки, мы можем создать *фрагмент документа*, передав `null` вместо имени элемента: +Если мы хотим работать с массивом узлов и нас не интересует обрамляющий элемент, мы можем создать так называемый *document fragment*, передав `null` вместо имени элемента: ```php $el = Html::el(null) @@ -261,7 +261,7 @@ $el = Html::el(null) // hello
                                                                                                                            10 < 20
                                                                                                                            ``` -Методы `fromHtml()` и `fromText()` предлагают более быстрый способ создания фрагмента: +Более быстрый способ создания фрагмента предлагают методы `fromHtml()` и `fromText()`: ```php $el = Html::fromHtml('hello
                                                                                                                            '); @@ -272,10 +272,10 @@ echo $el; // '10 < 20' ``` -Генерирование вывода HTML .[#toc-generating-html-output] -======================================================== +Генерация HTML-вывода +===================== -Самый простой способ вывода элемента HTML - использовать `echo` или переписать объект на `(string)`. Вы также можете выводить открывающие или закрывающие теги и атрибуты отдельно: +Самый простой способ вывести HTML-элемент — использовать `echo` или преобразовать объект в `(string)`. Можно также отдельно вывести открывающие или закрывающие теги и атрибуты: ```php $el = Html::el('div class=header')->setText('hello'); @@ -289,7 +289,7 @@ echo $el->endTag(); // '' echo $el->attributes(); // 'class="header"' ``` -Важной особенностью является автоматическая защита от [межсайтового скриптинга (XSS) |nette:glossary#Cross-Site-Scripting-XSS]. Любые значения атрибутов или содержимое, вставленное через `setText()` или `addText()`, надежно экранируется: +Важной особенностью является автоматическая защита от [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS]. Все значения атрибутов или содержимое, вставленное через `setText()` или `addText()`, надежно экранируются: ```php echo Html::el('div') @@ -300,17 +300,17 @@ echo Html::el('div') ``` -HTML ↔ преобразование текста .[#toc-conversion-html-text] -========================================================= +Преобразование HTML ↔ текст +=========================== -Для преобразования HTML в текст можно использовать статический метод `htmlToText()`: +Для преобразования HTML в текст вы можете использовать статический метод `htmlToText()`: ```php echo Html::htmlToText('One & Two'); // 'One & Two' ``` -HtmlStringable .[#toc-htmlstringable] -===================================== +HtmlStringable +============== -Объект `Nette\Utils\Html` реализует интерфейс `Nette\HtmlStringable`, который, например, Latte или Forms использует для различения объектов, имеющих метод `__toString()`, возвращающий HTML-код. Поэтому не будет двойного экранирования, если, например, мы перечислим объект в шаблоне, используя `{$el}`. +Объект `Nette\Utils\Html` реализует интерфейс `Nette\HtmlStringable`, с помощью которого, например, Latte или формы различают объекты, имеющие метод `__toString()`, возвращающий HTML-код. Таким образом, не произойдет двойного экранирования, если, например, мы выведем объект в шаблоне с помощью `{$el}`. diff --git a/utils/ru/images.texy b/utils/ru/images.texy index e7c5cba4c3..d05889e3e3 100644 --- a/utils/ru/images.texy +++ b/utils/ru/images.texy @@ -2,10 +2,10 @@ ********************** .[perex] -Класс [api:Nette\Utils\Image] позволяет легко манипулировать изображениями, например, изменять размер, обрезать, повышать резкость, рисовать или соединять несколько изображений. +Класс [api:Nette\Utils\Image] упрощает манипулирование изображениями, например, изменение размера, обрезку, повышение резкости, рисование или объединение нескольких изображений. -PHP имеет обширный набор функций для работы с изображениями. Но их API не очень удобен. Это был бы не Nette Framework, если бы не придумали сексуальный API. +PHP имеет обширный набор функций для работы с изображениями. Но их API не очень удобен. Это был бы не Nette Framework, если бы он не предложил привлекательный API. Установка: @@ -13,119 +13,130 @@ PHP имеет обширный набор функций для работы с composer require nette/utils ``` -Во всех примерах предполагается, что псевдоним уже создан: +Все примеры предполагают, что создан псевдоним: ```php use Nette\Utils\Image; +use Nette\Utils\ImageColor; +use Nette\Utils\ImageType; ``` -Создание изображения .[#toc-creating-an-image] -============================================== +Создание изображения +==================== -Создайте новое истинно цветное изображение, например, размером 100×200: +Создадим новое true color изображение, например, размером 100×200: ```php $image = Image::fromBlank(100, 200); ``` -По желанию можно указать цвет фона (по умолчанию черный): +Можно также указать цвет фона (по умолчанию черный): ```php -$image = Image::fromBlank(100, 200, Image::rgb(125, 0, 0)); +$image = Image::fromBlank(100, 200, ImageColor::rgb(125, 0, 0)); ``` -Или загрузите изображение из файла: +Или загрузим изображение из файла: ```php $image = Image::fromFile('nette.jpg'); ``` -Поддерживаются форматы JPEG, PNG, GIF, WebP, AVIF и BMP, но ваша версия PHP также должна их поддерживать (проверьте `phpinfo()`, раздел GD). Анимация не поддерживается. -Нужно ли определять формат изображения при загрузке? Метод возвращает его во втором параметре: +Сохранение изображения +====================== + +Изображение можно сохранить в файл: ```php -$image = Image::fromFile('nette.jpg', $type); -// $type - Image::JPEG, Image::PNG, Image::GIF, Image::WEBP, Image::AVIF или Image::BMP +$image->save('resampled.jpg'); ``` -Только обнаружение без загрузки изображения выполняется `Image::detectTypeFromFile()`. - +Мы можем указать качество сжатия в диапазоне 0..100 для JPEG (по умолчанию 85), WEBP (по умолчанию 80) и AVIF (по умолчанию 30) и 0..9 для PNG (по умолчанию 9): -Сохранение изображения .[#toc-save-the-image] -============================================= +```php +$image->save('resampled.jpg', 80); // JPEG, качество 80% +``` -Изображение можно сохранить в файл: +Если формат не очевиден из расширения файла, его можно указать [константой |#Форматы]: ```php -$image->save('resampled.jpg'); +$image->save('resampled.tmp', null, ImageType::JPEG); ``` -Мы можем задать качество сжатия в диапазоне 0...100 для JPEG (по умолчанию 85), WEBP (по умолчанию 80) и AVIF (по умолчанию 30) и 0...9 для PNG (по умолчанию 9): +Изображение можно записать в переменную вместо сохранения на диск: ```php -$image->save('resampled.jpg', 80); // JPEG, 80% качества +$data = $image->toString(ImageType::JPEG, 80); // JPEG, качество 80% ``` -Если формат не очевиден из расширения файла, он может быть указан одной из констант `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` и `Image::BMP`: +или отправить прямо в браузер с соответствующим HTTP-заголовком `Content-Type`: ```php -$image->save('resampled.tmp', null, Image::JPEG); +// отправляет заголовок Content-Type: image/png +$image->send(ImageType::PNG); ``` -Изображение может быть записано не на диск, а в переменную: + +Форматы +======= + +Поддерживаемые форматы: JPEG, PNG, GIF, WebP, AVIF и BMP, однако они также должны поддерживаться вашей версией PHP, что можно проверить с помощью функции [#isTypeSupported()]. Анимация не поддерживается. + +Формат представлен константами `ImageType::JPEG`, `ImageType::PNG`, `ImageType::GIF`, `ImageType::WEBP`, `ImageType::AVIF` и `ImageType::BMP`. ```php -$data = $image->toString(Image::JPEG, 80); // JPEG, 80% качества +$supported = Image::isTypeSupported(ImageType::JPEG); ``` -или отправляется непосредственно в браузер с соответствующим HTTP-заголовком `Content-Type`: +Нужно определить формат изображения при загрузке? Метод вернет его во втором параметре: ```php -// отправляет заголовок Content-Type: image/png -$image->send(Image::PNG); +$image = Image::fromFile('nette.jpg', $type); ``` +Само определение без загрузки изображения выполняет `Image::detectTypeFromFile()`. + -Изменить размер .[#toc-image-resize] -==================================== +Изменение размера +================= -Частой операцией является изменение размера изображения. Фактические размеры возвращаются методами `getWidth()` и `getHeight()`. +Частой операцией является изменение размеров изображения. Текущие размеры возвращают методы `getWidth()` и `getHeight()`. -Метод `resize()` используется для изменения размера изображения так, чтобы оно не превышало 500x300 пикселей (либо ширина будет ровно 500 px, либо высота ровно 300 px, один из размеров вычисляется для сохранения соотношения сторон): +Для изменения используется метод `resize()`. Пример пропорционального изменения размера так, чтобы он не превышал размеры 500x300 пикселей (либо ширина будет ровно 500 px, либо высота будет ровно 300 px, один из размеров будет рассчитан так, чтобы сохранить соотношение сторон): ```php $image->resize(500, 300); ``` -Можно указать только одно измерение, а другое будет рассчитано: +Можно указать только один размер, а второй будет рассчитан: ```php $image->resize(500, null); // ширина 500px, высота рассчитывается -$image->resize(null, 300); // ширина рассчитана, высота 300px +$image->resize(null, 300); // ширина рассчитывается, высота 300px ``` -Любое измерение может быть указано в процентах: +Любой размер можно указать и в процентах: ```php $image->resize('75%', 300); // 75 % × 300px ``` -На поведение `resize` могут влиять следующие симптомы. Все, кроме `Image::Stretch`, сохраняют соотношение сторон. +Поведение `resize` можно изменить с помощью следующих флагов. Все, кроме `Image::Stretch`, сохраняют соотношение сторон. |--------------------------------------------------------------------------------------- -| Флаг | Описание +| Флаг | Описание |--------------------------------------------------------------------------------------- -| `Image::OrSmaller` (по умолчанию)| результирующие размеры будут меньше или равны запрашиваемым размерам. -| `Image::OrBigger` | заполняет (и, возможно, превышает в одном измерении) целевую область -| `Image::Cover` | заполняет целевую область и обрезает то, что выходит за ее пределы. -| `Image::ShrinkOnly` | только уменьшение (позволяет избежать растягивания маленького изображения) -| `Image::Stretch` | не сохранять соотношение сторон +| `Image::OrSmaller` (по умолчанию) | итоговые размеры будут меньше или равны требуемым размерам +| `Image::OrBigger` | заполняет (и при необходимости превышает в одном измерении) целевую область +| `Image::Cover` | заполняет целевую область и обрезает то, что выходит за ее пределы +| `Image::ShrinkOnly` | только уменьшение (предотвращает растягивание маленького изображения) +| `Image::Stretch` | не сохранять соотношение сторон -Флаги передаются в качестве третьего аргумента функции: +Флаги указываются как третий аргумент функции: ```php $image->resize(500, 300, Image::OrBigger); @@ -137,33 +148,33 @@ $image->resize(500, 300, Image::OrBigger); $image->resize(500, 300, Image::ShrinkOnly | Image::Stretch); ``` -Изображения можно перевернуть по вертикали или горизонтали, указав один из размеров (или оба) как отрицательное число: +Изображения можно переворачивать по вертикали или горизонтали, указав один из размеров (или оба) как отрицательное число: ```php -$flipped = $image->resize(null, '-100%'); // flip vertical +$flipped = $image->resize(null, '-100%'); // перевернуть по вертикали -$flipped = $image->resize('-100%', '-100%'); // rotate 180° +$flipped = $image->resize('-100%', '-100%'); // повернуть на 180° -$flipped = $image->resize(-125, 500); // resize & flip horizontal +$flipped = $image->resize(-125, 500); // изменить размер и перевернуть по горизонтали ``` -После уменьшения изображения можно улучшить его внешний вид с помощью тонкой настройки резкости: +После уменьшения изображения можно улучшить его внешний вид легким повышением резкости: ```php $image->sharpen(); ``` -Растениеводство .[#toc-cropping] -================================ +Обрезка +======= -Для возделывания используется метод `crop()`: +Для обрезки используется метод `crop()`: ```php $image->crop($left, $top, $width, $height); ``` -Как и в случае с `resize()`, все значения могут быть представлены в процентах. Проценты для `$left` и `$top` рассчитываются из оставшегося пространства, аналогично свойству CSS `background-position`: +Аналогично `resize()`, все значения могут быть указаны в процентах. Проценты для `$left` и `$top` рассчитываются от оставшегося места, подобно CSS-свойству `background-position`: ```php $image->crop('100%', '50%', '80%', '80%'); @@ -172,173 +183,198 @@ $image->crop('100%', '50%', '80%', '80%'); [* crop.svg *] -Изображение можно также автоматически обрезать, например, обрезать черные границы: +Изображение также можно обрезать автоматически, например, обрезать черные края: ```php $image->cropAuto(IMG_CROP_BLACK); ``` -Метод `cropAuto()` является объектной заменой функции `imagecropauto()`, более подробную информацию см. в [документации к ней |https://www.php.net/manual/en/function.imagecropauto]. +Метод `cropAuto()` является объектной заменой функции `imagecropauto()`, в [ее документации|https://www.php.net/manual/en/function.imagecropauto] вы найдете дополнительную информацию. -Рисование и редактирование .[#toc-drawing-and-editing] -====================================================== +Цвета .{data-version:4.0.2} +=========================== -Вы можете рисовать, можете писать, но не рвите страницы. Все функции PHP для работы с изображениями, такие как [imagefilledellipse |https://www.php.net/manual/en/function.imagefilledellipse.php], доступны вам, но в объектно-ориентированном обличье: +Метод `ImageColor::rgb()` позволяет определить цвет с помощью значений красного, зеленого и синего (RGB). Опционально можно также указать значение прозрачности в диапазоне от 0 (полностью прозрачный) до 1 (полностью непрозрачный), то есть так же, как в CSS. ```php -$image->filledEllipse($cx, $cy, $width, $height, Image::rgb(255, 0, 0, 63)); +$color = ImageColor::rgb(255, 0, 0); // Красный +$transparentBlue = ImageColor::rgb(0, 0, 255, 0.5); // Полупрозрачный синий ``` -См. раздел [Обзор методов |#Overview-of-Methods]. +Метод `ImageColor::hex()` позволяет определить цвет с помощью шестнадцатеричного формата, аналогично CSS. Поддерживает форматы `#rgb`, `#rrggbb`, `#rgba` и `#rrggbbaa`: +```php +$color = ImageColor::hex("#F00"); // Красный +$transparentGreen = ImageColor::hex("#00FF0080"); // Полупрозрачный зеленый +``` + +Цвета можно использовать в других методах, таких как `ellipse()`, `fill()` и т. д. -Объединение нескольких изображений .[#toc-merge-multiple-images] -================================================================ -Вы можете легко вставить другое изображение в фотографию: +Рисование и редактирование +========================== + +Можешь рисовать, можешь писать, но листья не рвать. Вам доступны все функции PHP для работы с изображениями, см. [#Обзор методов], но в объектной обертке: + +```php +$image->filledEllipse($centerX, $centerY, $width, $height, ImageColor::rgb(255, 0, 0)); +``` + +Поскольку функции PHP для рисования прямоугольников неудобны из-за указания координат, класс `Image` предлагает их замены в виде функций [#rectangleWH()] и [#filledRectangleWH()]. + + +Объединение нескольких изображений +================================== + +В изображение можно легко вставить другое изображение: ```php $logo = Image::fromFile('logo.png'); -$blank = Image::fromBlank(320, 240, Image::rgb(52, 132, 210)); +$blank = Image::fromBlank(320, 240, ImageColor::rgb(52, 132, 210)); -// координаты могут быть снова заданы в процентах -$blank->place($logo, '80%', '80%'); // вставьте в правый нижний угол +// координаты можно снова указать в процентах +$blank->place($logo, '80%', '80%'); // вставляем рядом с правым нижним углом ``` -Альфаканал соблюдается во время вставки, и мы можем влиять на прозрачность вставленного изображения (мы создаем водяной знак): +При вставке учитывается альфа-канал, кроме того, можно влиять на прозрачность вставляемого изображения (создаем так называемый водяной знак): ```php -$blank->place($image, '80%', '80%', 25); // прозрачность составляет 25% +$blank->place($image, '80%', '80%', 25); // прозрачность 25 % ``` -Этот API - настоящее удовольствие от использования! +Такой API действительно приятно использовать! -Обзор методов .[#toc-overview-of-methods] -========================================= +Обзор методов +============= -static fromBlank(int $width, int $height, array $color=null): Image .[method] ------------------------------------------------------------------------------ -Создает новое истинно цветное изображение заданных размеров. По умолчанию используется черный цвет. +static fromBlank(int $width, int $height, ?ImageColor $color=null): Image .[method] +----------------------------------------------------------------------------------- +Создает новое true color изображение заданных размеров. Цвет по умолчанию - черный. static fromFile(string $file, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------- -Считывает изображение из файла и возвращает его тип в формате `$detectedFormat`. Поддерживаются следующие типы: `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` и `Image::BMP`. +Загружает изображение из файла и возвращает его [тип |#Форматы] в `$detectedFormat`. static fromString(string $s, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------ -Считывает изображение из строки и возвращает его тип в формате `$detectedFormat`. Поддерживаются следующие типы: `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` и `Image::BMP`. +Загружает изображение из строки и возвращает его [тип |#Форматы] в `$detectedFormat`. -static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method] ---------------------------------------------------------------------------------- -Создает цвет, который может быть использован в других методах, таких как `ellipse()`, `fill()` и т.д. +static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method][deprecated] +--------------------------------------------------------------------------------------------- +Эта функция заменена классом `ImageColor`, см. [#цвета]. static typeToExtension(int $type): string .[method] --------------------------------------------------- -Возвращает расширение файла для заданной константы `Image::XXX`. +Возвращает расширение файла для данного [типа |#Форматы]. static typeToMimeType(int $type): string .[method] -------------------------------------------------- -Возвращает тип mime для заданной константы `Image::XXX`. +Возвращает mime-тип для данного [типа |#Форматы]. static extensionToType(string $extension): int .[method] -------------------------------------------------------- -Возвращает тип изображения в виде константы `Image::XXX` в соответствии с расширением файла. +Возвращает [тип |#Форматы] изображения по расширению файла. static detectTypeFromFile(string $file, int &$width=null, int &$height=null): ?int .[method] -------------------------------------------------------------------------------------------- -Возвращает тип изображения в виде константы `Image::XXX`, а также его размеры в параметрах `$width` и `$height`. +Возвращает [тип |#Форматы] изображения и в параметрах `$width` и `$height` также его размеры. static detectTypeFromString(string $s, int &$width=null, int &$height=null): ?int .[method] ------------------------------------------------------------------------------------------- -Возвращает тип изображения из строки в виде константы `Image::XXX` и его размеры в параметрах `$width` и `$height`. +Возвращает [тип |#Форматы] изображения из строки и в параметрах `$width` и `$height` также его размеры. -affine(array $affine, array $clip=null): Image .[method] --------------------------------------------------------- -Возвращает изображение, содержащее аффинно-трансформированное изображение src с использованием необязательной области обрезания. ([подробнее |https://www.php.net/manual/en/function.imageaffine]). +static isTypeSupported(int $type): bool .[method] +------------------------------------------------- +Проверяет, поддерживается ли данный [тип |#Форматы] изображения. + + +static getSupportedTypes(): array .[method]{data-version:4.0.4} +--------------------------------------------------------------- +Возвращает массив поддерживаемых [типов |#Форматы] изображений. + + +static calculateTextBox(string $text, string $fontFile, float $size, float $angle=0, array $options=[]): array .[method] +------------------------------------------------------------------------------------------------------------------------ +Вычисляет размеры прямоугольника, который охватывает текст определенного шрифта и размера. Возвращает ассоциативный массив, содержащий ключи `left`, `top`, `width`, `height`. Левый край может быть отрицательным, если текст начинается с левого выступающего элемента (kerning). + + +affine(array $affine, ?array $clip=null): Image .[method] +--------------------------------------------------------- +Возвращает изображение, содержащее аффинно преобразованное изображение `src` с использованием необязательной области обрезки. ([подробнее |https://www.php.net/manual/en/function.imageaffine]). affineMatrixConcat(array $m1, array $m2): array .[method] --------------------------------------------------------- -Возвращает конкатенацию двух матриц аффинного преобразования, что полезно, если к одному изображению необходимо применить сразу несколько преобразований. ([подробнее |https://www.php.net/manual/en/function.imageaffinematrixconcat]) +Возвращает конкатенацию двух аффинных матриц преобразования, что полезно, если к одному и тому же изображению нужно применить несколько преобразований одновременно. ([подробнее |https://www.php.net/manual/en/function.imageaffinematrixconcat]) -affineMatrixGet(int $type, mixed $options=null): array .[method] ----------------------------------------------------------------- -Возвращает матрицу преобразования матрицы. ([подробнее |https://www.php.net/manual/en/function.imageaffinematrixget]) +affineMatrixGet(int $type, ?mixed $options=null): array .[method] +----------------------------------------------------------------- +Возвращает матрицу аффинного преобразования. ([подробнее |https://www.php.net/manual/en/function.imageaffinematrixget]) alphaBlending(bool $on): void .[method] --------------------------------------- -Позволяет использовать два различных режима рисования в трехцветных изображениях. В режиме наложения компонент альфа-канала цвета, используемый во всех функциях рисования, таких как `setPixel()`, определяет, в какой степени базовый цвет должен просвечивать. В результате в этот момент существующий цвет автоматически смешивается с цветом рисунка, и результат сохраняется в изображении. В результате пиксель становится непрозрачным. В режиме без смешивания цвет мультфильма копируется дословно с информацией альфа-канала и заменяется на целевой пиксель. Режим наложения недоступен при рисовании на изображениях палитры. ([подробнее |https://www.php.net/manual/en/function.imagealphablending]) +Позволяет использовать два разных режима рисования в изображениях truecolor. В режиме смешивания компонент альфа-канала цвета, используемый во всех функциях рисования, таких как `setPixel()`, определяет, в какой степени должна просвечивать основная краска. В результате существующий цвет в этой точке автоматически смешивается с рисуемым цветом, и результат сохраняется в изображении. Полученный пиксель непрозрачен. В режиме без смешивания рисуемый цвет копируется буквально с информацией альфа-канала и заменяет целевой пиксель. Режим смешивания недоступен при рисовании на палитровых изображениях. ([подробнее |https://www.php.net/manual/en/function.imagealphablending]) antialias(bool $on): void .[method] ----------------------------------- -Активация рисования сглаженных линий и многоугольников. Не поддерживает альфа-каналы. Работает только с трехцветными изображениями. +Активирует рисование сглаженных линий и полигонов. Не поддерживает альфа-каналы. Работает только с изображениями truecolor. -Использование сглаженного примитива с прозрачным цветом фона может привести к неожиданным результатам. Метод смешивания использует цвет фона как любой другой цвет. ([подробнее |https://www.php.net/manual/en/function.imageantialias]) +Использование сглаженных примитивов с прозрачным цветом фона может привести к неожиданным результатам. Метод смешивания использует цвет фона так же, как и все остальные цвета. ([подробнее |https://www.php.net/manual/en/function.imageantialias]) -arc(int $x, int $y, int $w, int $h, int $start, int $end, int $color): void .[method] -------------------------------------------------------------------------------------- +arc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color): void .[method] +--------------------------------------------------------------------------------------------------------------------------- Рисует дугу окружности с центром в заданных координатах. ([подробнее |https://www.php.net/manual/en/function.imagearc]) -char(int $font, int $x, int $y, string $char, int $color): void .[method] -------------------------------------------------------------------------- -Нарисует первый символ `$char` в изображении с левым верхним углом `$x`, `$y` (левый верхний угол равен 0, 0) цветом `$color`. ([подробнее |https://www.php.net/manual/en/function.imagechar]) - - -charUp(int $font, int $x, int $y, string $char, int $color): void .[method] ---------------------------------------------------------------------------- -Рисует символ `$char` вертикально по указанной координате в заданном изображении. ([подробнее |https://www.php.net/manual/en/function.imagecharup]) - - colorAllocate(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------- -Возвращает идентификатор цвета, представляющий цвет, состоящий из заданных компонентов RGB. Должен быть вызван для создания каждого цвета, который будет использоваться в изображении. ([подробнее |https://www.php.net/manual/en/function.imagecolorallocate]) +Возвращает идентификатор цвета, представляющий цвет, составленный из заданных RGB-компонентов. Должна быть вызвана для создания каждого цвета, который будет использоваться в изображении. ([подробнее |https://www.php.net/manual/en/function.imagecolorallocate]) colorAllocateAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ------------------------------------------------------------------------------ -Действует так же, как и `colorAllocate()`, с добавлением параметра прозрачности `$alpha`. ([подробнее |https://www.php.net/manual/en/function.imagecolorallocatealpha]) +Ведет себя так же, как `colorAllocate()`, с добавлением параметра прозрачности `$alpha`. ([подробнее |https://www.php.net/manual/en/function.imagecolorallocatealpha]) colorAt(int $x, int $y): int .[method] -------------------------------------- -Возвращает индекс цвета пикселя в указанном месте изображения. Если изображение является truecolor, эта функция возвращает значение RGB для данного пикселя в виде целого числа. Используйте сдвиг битов и битовую маску для доступа к отдельным значениям для красного, зеленого и синего компонентов. ([подробнее |https://www.php.net/manual/en/function.imagecolorat]) +Возвращает индекс цвета пикселя в указанном месте изображения. Если изображение является truecolor, эта функция вернет RGB-значение этого пикселя как целое число. Используйте битовый сдвиг и битовую маску для доступа к отдельным значениям красного, зеленого и синего компонентов: ([подробнее |https://www.php.net/manual/en/function.imagecolorat]) colorClosest(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------ -Возвращает индекс цвета в палитре изображения, который "ближе всего" к указанному значению RGB. Расстояние" между желаемым цветом и каждым цветом в палитре рассчитывается так, как если бы значения RGB представляли собой точки в трехмерном пространстве. ([подробнее |https://www.php.net/manual/en/function.imagecolorclosest]) +Возвращает индекс цвета в палитре изображения, который «наиболее близок» к указанному RGB-значению. "Расстояние" между требуемым цветом и каждым цветом в палитре вычисляется так, как если бы значения RGB представляли точки в трехмерном пространстве. ([подробнее |https://www.php.net/manual/en/function.imagecolorclosest]) colorClosestAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ----------------------------------------------------------------------------- -Возвращает индекс цвета в палитре изображений, который "ближе всего" к указанному значению RGB и уровню `$alpha`. ([подробнее |https://www.php.net/manual/en/function.imagecolorclosestalpha]) +Возвращает индекс цвета в палитре изображения, который «наиболее близок» к указанному RGB-значению и уровню `$alpha`. ([подробнее |https://www.php.net/manual/en/function.imagecolorclosestalpha]) colorClosestHWB(int $red, int $green, int $blue): int .[method] --------------------------------------------------------------- -Получить индекс цвета, который имеет оттенок, белый и черный цвета, наиболее близкие к заданному цвету. ([подробнее |https://www.php.net/manual/en/function.imagecolorclosesthwb]) +Получает индекс цвета, который имеет оттенок, белый и черный цвета, наиболее близкие к заданному цвету. ([подробнее |https://www.php.net/manual/en/function.imagecolorclosesthwb]) colorDeallocate(int $color): void .[method] ------------------------------------------- -Удаляет цвет, ранее назначенный с помощью `colorAllocate()` или `colorAllocateAlpha()`. ([подробнее |https://www.php.net/manual/en/function.imagecolordeallocate]) +Деаллоцирует цвет, ранее выделенный с помощью `colorAllocate()` или `colorAllocateAlpha()`. ([подробнее |https://www.php.net/manual/en/function.imagecolordeallocate]) colorExact(int $red, int $green, int $blue): int .[method] @@ -348,27 +384,27 @@ colorExact(int $red, int $green, int $blue): int .[method] colorExactAlpha(int $red, int $green, int $blue, int $alpha): int .[method] --------------------------------------------------------------------------- -Возвращает индекс указанного цвета + альфа в палитре изображений. ([подробнее |https://www.php.net/manual/en/function.imagecolorexactalpha]) +Возвращает индекс указанного цвета + альфа в палитре изображения. ([подробнее |https://www.php.net/manual/en/function.imagecolorexactalpha]) colorMatch(Image $image2): void .[method] ----------------------------------------- -Совмещает цвета палитры с цветами другой панели. ([подробнее |https://www.php.net/manual/en/function.imagecolormatch]) +Подгоняет цвета палитры ко второму изображению. ([подробнее |https://www.php.net/manual/en/function.imagecolormatch]) colorResolve(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------ -Возвращает индекс цвета для желаемого цвета, либо точный цвет, либо ближайший возможный альтернативный. ([подробнее |https://www.php.net/manual/en/function.imagecolorresolve]) +Возвращает индекс цвета для требуемого цвета, либо точный цвет, либо ближайшую возможную альтернативу. ([подробнее |https://www.php.net/manual/en/function.imagecolorresolve]) colorResolveAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ----------------------------------------------------------------------------- -Возвращает индекс цвета для желаемого цвета, либо точный цвет, либо ближайший возможный альтернативный. ([подробнее |https://www.php.net/manual/en/function.imagecolorresolvealpha]) +Возвращает индекс цвета для требуемого цвета, либо точный цвет, либо ближайшую возможную альтернативу. ([подробнее |https://www.php.net/manual/en/function.imagecolorresolvealpha]) colorSet(int $index, int $red, int $green, int $blue): void .[method] --------------------------------------------------------------------- -Устанавливает указанный индекс в палитре на указанный цвет. ([подробнее |https://www.php.net/manual/en/function.imagecolorset]) +Устанавливает указанный индекс в палитре на заданный цвет. ([подробнее |https://www.php.net/manual/en/function.imagecolorset]) colorsForIndex(int $index): array .[method] @@ -381,14 +417,14 @@ colorsTotal(): int .[method] Возвращает количество цветов в палитре изображения. ([подробнее |https://www.php.net/manual/en/function.imagecolorstotal]) -colorTransparent(int $color=null): int .[method] ------------------------------------------------- -Получает или устанавливает прозрачный цвет изображения. ([подробнее |https://www.php.net/manual/en/function.imagecolortransparent]) +colorTransparent(?int $color=null): int .[method] +------------------------------------------------- +Получает или устанавливает прозрачный цвет в изображении. ([подробнее |https://www.php.net/manual/en/function.imagecolortransparent]) convolution(array $matrix, float $div, float $offset): void .[method] --------------------------------------------------------------------- -Применяет матрицу свертки к изображению, используя заданный коэффициент и смещение. ([подробнее |https://www.php.net/manual/en/function.imageconvolution]) +Применяет к изображению сверточную матрицу, используя заданный коэффициент и смещение. ([подробнее |https://www.php.net/manual/en/function.imageconvolution]) .[note] Требует наличия *Bundled GD extension*, поэтому может работать не везде. @@ -396,105 +432,110 @@ convolution(array $matrix, float $div, float $offset): void .[method] copy(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH): void .[method] -------------------------------------------------------------------------------------------------- -Копирует часть `$src` в изображение, начинающееся в координатах `$srcX`, `$srcY` с шириной `$srcW` и высотой `$srcH`. Определенная часть будет скопирована в координаты `$dstX` и `$dstY`. ([подробнее |https://www.php.net/manual/en/function.imagecopy]) +Копирует часть `$src` на изображение, начиная с координат `$srcX`, `$srcY` с шириной `$srcW` и высотой `$srcH`. Определенная часть будет скопирована на координаты `$dstX` и `$dstY`. ([подробнее |https://www.php.net/manual/en/function.imagecopy]) copyMerge(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] --------------------------------------------------------------------------------------------------------------------- -Копирует часть `$src` в изображение, начинающееся в координатах `$srcX`, `$srcY` с шириной `$srcW` и высотой `$srcH`. Определенная часть будет скопирована в координаты `$dstX` и `$dstY`. ([подробнее |https://www.php.net/manual/en/function.imagecopymerge]) +Копирует часть `$src` на изображение, начиная с координат `$srcX`, `$srcY` с шириной `$srcW` и высотой `$srcH`. Определенная часть будет скопирована на координаты `$dstX` и `$dstY`. ([подробнее |https://www.php.net/manual/en/function.imagecopymerge]) copyMergeGray(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] ------------------------------------------------------------------------------------------------------------------------- -Копирует часть `$src` в изображение, начинающееся в координатах `$srcX`, `$srcY` с шириной `$srcW` и высотой `$srcH`. Определенная часть будет скопирована в координаты `$dstX` и `$dstY`. +Копирует часть `$src` на изображение, начиная с координат `$srcX`, `$srcY` с шириной `$srcW` и высотой `$srcH`. Определенная часть будет скопирована на координаты `$dstX` и `$dstY`. -Эта функция идентична `copyMerge()`, за исключением того, что она сохраняет исходный оттенок при объединении, преобразуя целевые пиксели в оттенки серого перед операцией копирования. ([подробнее |https://www.php.net/manual/en/function.imagecopymergegray]) +Эта функция идентична `copyMerge()`, за исключением того, что при слиянии она сохраняет оттенок источника, преобразуя целевые пиксели в оттенки серого перед операцией копирования. ([подробнее |https://www.php.net/manual/en/function.imagecopymergegray]) copyResampled(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] --------------------------------------------------------------------------------------------------------------------------------- -Копирует прямоугольную часть одного изображения в другое изображение, плавно интерполируя значения пикселей так, чтобы при уменьшении размера изображение сохраняло высокую четкость. +Копирует прямоугольную часть одного изображения на другое изображение, плавно интерполируя значения пикселей, так что, в частности, уменьшение размера изображения по-прежнему сохраняет большую четкость. -Другими словами, `copyResampled()` берет прямоугольную область из `$src` шириной `$srcW` и высотой `$srcH` в позиции (`$srcX`, `$srcY`) и помещает ее в прямоугольную область изображения шириной `$dstW` и высотой `$dstH` в позиции (`$dstX`, `$dstY`). +Другими словами, `copyResampled()` берет прямоугольную область из `$src` шириной `$srcW` и высотой `$srcH` в положении (`$srcX`, `$srcY`) и помещает ее в прямоугольную область изображения шириной `$dstW` и высотой `$dstH` в положении (`$dstX`, `$dstY`). -Если координаты источника и назначения, ширина и высота отличаются, фрагмент изображения растягивается или сжимается соответственно. Координаты относятся к левому верхнему углу. Эту функцию можно использовать для копирования областей одного и того же изображения, но если области перекрываются, результаты не будут предсказуемыми. ([подробнее |https://www.php.net/manual/en/function.imagecopyresampled]) +Если исходные и целевые координаты, ширина и высота различаются, выполняется соответствующее растяжение или сжатие фрагмента изображения. Координаты относятся к левому верхнему углу. Эту функцию можно использовать для копирования областей в одном и том же изображении, но если области перекрываются, результаты будут непредсказуемыми. ([подробнее |https://www.php.net/manual/en/function.imagecopyresampled]) copyResized(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] ------------------------------------------------------------------------------------------------------------------------------- -Копирует прямоугольную часть одного изображения на другое изображение. Другими словами, `copyResized()` получает прямоугольную область из `$src` шириной `$srcW` и высотой `$srcH` в позиции (`$srcX`, `$srcY`) и помещает ее в прямоугольную область изображения шириной `$dstW` ] и высотой `$dstH` в позиции (`$dstX`, `$dstY`). +Копирует прямоугольную часть одного изображения на другое изображение. Другими словами, `copyResized()` берет прямоугольную область из `$src` шириной `$srcW` и высотой `$srcH` в положении (`$srcX`, `$srcY`) и помещает ее в прямоугольную область изображения шириной `$dstW` и высотой `$dstH` в положении (`$dstX`, `$dstY`). -Если координаты источника и назначения, ширина и высота отличаются, фрагмент изображения растягивается или сжимается соответственно. Координаты относятся к левому верхнему углу. Эту функцию можно использовать для копирования областей одного и того же изображения, но если области перекрываются, результаты не будут предсказуемыми. ([подробнее |https://www.php.net/manual/en/function.imagecopyresized]) +Если исходные и целевые координаты, ширина и высота различаются, выполняется соответствующее растяжение или сжатие фрагмента изображения. Координаты относятся к левому верхнему углу. Эту функцию можно использовать для копирования областей в одном и том же изображении, но если области перекрываются, результаты будут непредсказуемыми. ([подробнее |https://www.php.net/manual/en/function.imagecopyresized]) crop(int|string $left, int|string $top, int|string $width, int|string $height): Image .[method] ----------------------------------------------------------------------------------------------- -Обрезает изображение до заданной прямоугольной области. Размеры могут быть указаны как целые числа в пикселях или строки в процентах (например, `'50%'`). +Обрезает изображение до заданной прямоугольной области. Размеры можно указывать как целые числа в пикселях или строки в процентах (например, `'50%'`). -cropAuto(int $mode=-1, float $threshold=.5, int $color=-1): Image .[method] ---------------------------------------------------------------------------- -Автоматическое кадрирование изображения в соответствии с заданным `$mode`. ([подробнее |https://www.php.net/manual/en/function.imagecropauto]) +cropAuto(int $mode=-1, float $threshold=.5, ?ImageColor $color=null): Image .[method] +------------------------------------------------------------------------------------- +Автоматически обрезает изображение в соответствии с заданным `$mode`. ([подробнее |https://www.php.net/manual/en/function.imagecropauto]) -ellipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------- +ellipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------- Рисует эллипс с центром в заданных координатах. ([подробнее |https://www.php.net/manual/en/function.imageellipse]) -fill(int $x, int $y, int $color): void .[method] ------------------------------------------------- -Заполняет область, начинающуюся в заданной координате (слева вверху 0, 0), заданным `$color`. ([подробнее |https://www.php.net/manual/en/function.imagefill]) +fill(int $x, int $y, ImageColor $color): void .[method] +------------------------------------------------------- +Выполняет заливку области, начиная с заданной координаты (верхний левый угол - 0, 0), заданным `$color`. ([подробнее |https://www.php.net/manual/en/function.imagefill]) -filledArc(int $cx, int $cy, int $w, int $h, int $s, int $e, int $color, int $style): void .[method] ---------------------------------------------------------------------------------------------------- -Рисует неполную дугу с центром в заданных координатах. ([подробнее |https://www.php.net/manual/en/function.imagefilledarc]) +filledArc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color, int $style): void .[method] +--------------------------------------------------------------------------------------------------------------------------------------------- +Рисует частичную дугу с центром в заданных координатах и заполняет ее. ([подробнее |https://www.php.net/manual/en/function.imagefilledarc]) -filledEllipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------------- -Рисует эллипс с центром в заданных координатах. ([подробнее |https://www.php.net/manual/en/function.imagefilledellipse]) +filledEllipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------------- +Рисует эллипс с центром в заданных координатах и заполняет его. ([подробнее |https://www.php.net/manual/en/function.imagefilledellipse]) -filledPolygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------------- -Создает заполненный многоугольник на изображении. ([подробнее |https://www.php.net/manual/en/function.imagefilledpolygon]) +filledPolygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------------- +Создает в изображении заполненный многоугольник. ([подробнее |https://www.php.net/manual/en/function.imagefilledpolygon]) -filledRectangle(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] -------------------------------------------------------------------------------- -Создает прямоугольник, заполненный `$color` на изображении, начиная с точки 1 и заканчивая точкой 2. Точка 0, 0 - левый верхний угол изображения. ([подробнее |https://www.php.net/manual/en/function.imagefilledrectangle]) +filledRectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------- +Создает прямоугольник, заполненный `$color`, в изображении, начиная с точки `$x1` & `$y1` и заканчивая точкой `$x2` & `$y2`. Точка 0, 0 - это левый верхний угол изображения. ([подробнее |https://www.php.net/manual/en/function.imagefilledrectangle]) -fillToBorder(int $x, int $y, int $border, int $color): void .[method] ---------------------------------------------------------------------- -Создает заливку, цвет границы которой определяется `$border`. Начальная точка заливки - `$x`, `$y` (левый верхний угол - 0, 0), а область заливается цветом `$color`. ([подробнее |https://www.php.net/manual/en/function.imagefilltoborder]) +filledRectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------------- +Создает прямоугольник, заполненный `$color`, в изображении, начиная с точки `$left` & `$top`, шириной `$width` и высотой `$height`. Точка 0, 0 - это левый верхний угол изображения. + + +fillToBorder(int $x, int $y, int $border, ImageColor $color): void .[method] +---------------------------------------------------------------------------- +Выполняет заливку, цвет границы которой определяется `$border`. Начальная точка заливки - `$x`, `$y` (верхний левый угол - 0, 0), и область заполняется цветом `$color`. ([подробнее |https://www.php.net/manual/en/function.imagefilltoborder]) filter(int $filtertype, int ...$args): void .[method] ----------------------------------------------------- -Применяет заданный фильтр `$filtertype` к изображению. ([подробнее |https://www.php.net/manual/en/function.imagefilter]) +Применяет данный фильтр `$filtertype` к изображению. ([подробнее |https://www.php.net/manual/en/function.imagefilter]) flip(int $mode): void .[method] ------------------------------- -Инвертирует изображение по заданному адресу `$mode`. ([подробнее |https://www.php.net/manual/en/function.imageflip]) +Переворачивает изображение с использованием заданного `$mode`. ([подробнее |https://www.php.net/manual/en/function.imageflip]) -ftText(int $size, int $angle, int $x, int $y, int $col, string $fontFile, string $text, array $extrainfo=null): array .[method] -------------------------------------------------------------------------------------------------------------------------------- -Пишите текст на изображении, используя шрифты FreeType 2. ([подробнее |https://www.php.net/manual/en/function.imagefttext]) +ftText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +---------------------------------------------------------------------------------------------------------------------------------------- +Записывает текст на изображение, используя шрифты FreeType. ([подробнее |https://www.php.net/manual/en/function.imagefttext]) gammaCorrect(float $inputgamma, float $outputgamma): void .[method] ------------------------------------------------------------------- -Применить гамма-коррекцию к изображению относительно входной и выходной гаммы. ([подробнее |https://www.php.net/manual/en/function.imagegammacorrect]) +Применяет гамма-коррекцию к изображению относительно входной и выходной гаммы. ([подробнее |https://www.php.net/manual/en/function.imagegammacorrect]) getClip(): array .[method] -------------------------- -Возвращает текущий обрез, т.е. область, за пределами которой не будут рисоваться пиксели. ([подробнее |https://www.php.net/manual/en/function.imagegetclip]) +Возвращает текущую область обрезки, т.е. область, за пределами которой пиксели не будут нарисованы. ([подробнее |https://www.php.net/manual/en/function.imagegetclip]) getHeight(): int .[method] @@ -504,7 +545,7 @@ getHeight(): int .[method] getImageResource(): resource|GdImage .[method] ---------------------------------------------- -Возвращает исходный ресурс. +Возвращает исходный ресурс изображения GD. getWidth(): int .[method] @@ -512,137 +553,142 @@ getWidth(): int .[method] Возвращает ширину изображения. -interlace(int $interlace=null): int .[method] ---------------------------------------------- -Включение или выключение режима чересстрочной развертки. Если установлен чересстрочный режим и изображение сохраняется в формате JPEG, оно будет сохранено как прогрессивный JPEG. ([подробнее |https://www.php.net/manual/en/function.imageinterlace]) +interlace(?int $interlace=null): int .[method] +---------------------------------------------- +Включает или выключает режим чересстрочной развертки. Если режим чересстрочной развертки установлен и изображение сохраняется как JPEG, оно будет сохранено как прогрессивный JPEG. ([подробнее |https://www.php.net/manual/en/function.imageinterlace]) isTrueColor(): bool .[method] ----------------------------- -Определите, является ли изображение truecolor. ([подробнее |https://www.php.net/manual/en/function.imageistruecolor]) +Определяет, является ли изображение truecolor. ([подробнее |https://www.php.net/manual/en/function.imageistruecolor]) layerEffect(int $effect): void .[method] ---------------------------------------- -Установите флаг альфа-смешения для использования эффектов наслоения. ([подробнее |https://www.php.net/manual/en/function.imagelayereffect]) +Устанавливает флаг смешивания альфа для использования эффектов наложения слоев. ([подробнее |https://www.php.net/manual/en/function.imagelayereffect]) -line(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] --------------------------------------------------------------------- -Проводит линию между двумя заданными точками. ([подробнее |https://www.php.net/manual/en/function.imageline]) +line(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +--------------------------------------------------------------------------- +Рисует линию между двумя заданными точками. ([подробнее |https://www.php.net/manual/en/function.imageline]) -openPolygon(array $points, int $numPoints, int $color): void .[method] ----------------------------------------------------------------------- -Рисует открытый многоугольник на изображении. В отличие от `polygon()`, между последней и первой точкой не проводится линия. ([подробнее |https://www.php.net/manual/en/function.imageopenpolygon]) +openPolygon(array $points, ImageColor $color): void .[method] +------------------------------------------------------------- +Рисует на изображении открытый многоугольник. В отличие от `polygon()`, линия между последней и первой точкой не рисуется. ([подробнее |https://www.php.net/manual/en/function.imageopenpolygon]) paletteCopy(Image $source): void .[method] ------------------------------------------ -Копирует палитру с сайта `$source` в изображение. ([подробнее |https://www.php.net/manual/en/function.imagepalettecopy]) +Копирует палитру из `$source` в изображение. ([подробнее |https://www.php.net/manual/en/function.imagepalettecopy]) paletteToTrueColor(): void .[method] ------------------------------------ -Преобразует изображение на основе палитры в полноцветное изображение. ([подробнее |https://www.php.net/manual/en/function.imagepalettetotruecolor]) +Преобразует изображение на основе палитры в полноцветное изображение (truecolor). ([подробнее |https://www.php.net/manual/en/function.imagepalettetotruecolor]) place(Image $image, int|string $left=0, int|string $top=0, int $opacity=100): Image .[method] --------------------------------------------------------------------------------------------- -Копирует `$image` в изображение по координатам `$left` и `$top`. Координаты могут быть указаны как целые числа в пикселях или строки в процентах (например, `'50%'`). +Копирует `$image` в изображение по координатам `$left` и `$top`. Координаты можно указывать как целые числа в пикселях или строки в процентах (например, `'50%'`). -polygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------- -Создает многоугольник на изображении. ([подробнее |https://www.php.net/manual/en/function.imagepolygon]) +polygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------- +Создает в изображении многоугольник. ([подробнее |https://www.php.net/manual/en/function.imagepolygon]) -rectangle(int $x1, int $y1, int $x2, int $y2, int $col): void .[method] ------------------------------------------------------------------------ +rectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------- Создает прямоугольник по заданным координатам. ([подробнее |https://www.php.net/manual/en/function.imagerectangle]) +rectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------- +Создает прямоугольник по заданным координатам и размерам. + + resize(int|string $width, int|string $height, int $flags=Image::OrSmaller): Image .[method] ------------------------------------------------------------------------------------------- -Изменение размеров изображения, [дополнительная информация |#Image-Resize]. Размеры могут быть указаны как целые числа в пикселях или строки в процентах (например, `'50%'`). +Изменяет размер изображения, [подробнее |#Изменение размера]. Размеры можно указывать как целые числа в пикселях или строки в процентах (например, `'50%'`). -resolution(int $resX=null, int $resY=null): mixed .[method] ------------------------------------------------------------ -Устанавливает или возвращает разрешение изображения в DPI (точках на дюйм). Если ни один из дополнительных параметров не указан, текущее разрешение возвращается в виде индексированного поля. Если указано только `$resX`, то горизонтальное и вертикальное разрешение устанавливается на это значение. Если указаны оба дополнительных параметра, горизонтальное и вертикальное разрешения устанавливаются на эти значения. +resolution(?int $resX=null, ?int $resY=null): mixed .[method] +------------------------------------------------------------- +Устанавливает или возвращает разрешение изображения в DPI (точек на дюйм). Если ни один из необязательных параметров не указан, текущее разрешение возвращается как индексированный массив. Если указан только `$resX`, горизонтальное и вертикальное разрешение устанавливаются на это значение. Если указаны оба необязательных параметра, горизонтальное и вертикальное разрешение устанавливаются на эти значения. -Разрешение используется в качестве метаинформации только при чтении и записи изображений в форматы, поддерживающие такую информацию (в настоящее время это PNG и JPEG). Это не влияет ни на какие операции рисования. Разрешение новых изображений по умолчанию составляет 96 DPI. ([подробнее |https://www.php.net/manual/en/function.imageresolution]) +Разрешение используется только как метаинформация, когда изображения читаются и записываются в форматы, поддерживающие этот тип информации (в настоящее время PNG и JPEG). Это не влияет ни на какие операции рисования. Разрешение по умолчанию для новых изображений - 96 DPI. ([подробнее |https://www.php.net/manual/en/function.imageresolution]) rotate(float $angle, int $backgroundColor): Image .[method] ----------------------------------------------------------- -Поворачивает изображение на указанное значение `$angle` в градусах. Центром вращения является центр изображения, и повернутое изображение может иметь размеры, отличные от размеров исходного изображения. ([подробнее |https://www.php.net/manual/en/function.imagerotate]) +Поворачивает изображение на заданный `$angle` в градусах. Центр вращения - центр изображения, и повернутое изображение может иметь другие размеры, чем исходное изображение. ([подробнее |https://www.php.net/manual/en/function.imagerotate]) .[note] Требует наличия *Bundled GD extension*, поэтому может работать не везде. -save(string $file, int $quality=null, int $type=null): void .[method] ---------------------------------------------------------------------- +save(string $file, ?int $quality=null, ?int $type=null): void .[method] +----------------------------------------------------------------------- Сохраняет изображение в файл. -Качество сжатия находится в диапазоне 0...100 для JPEG (по умолчанию 85), WEBP (по умолчанию 80) и AVIF (по умолчанию 30) и 0...9 для PNG (по умолчанию 9). Если тип не очевиден из расширения файла, вы можете указать его с помощью одной из констант `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` и `Image::BMP`. +Качество сжатия находится в диапазоне 0..100 для JPEG (по умолчанию 85), WEBP (по умолчанию 80) и AVIF (по умолчанию 30) и 0..9 для PNG (по умолчанию 9). Если тип не очевиден из расширения файла, вы можете указать его с помощью одной из констант `ImageType`. saveAlpha(bool $saveflag): void .[method] ----------------------------------------- -Устанавливает флаг сохранения полной информации альфа-канала (в отличие от монохромной прозрачности) при сохранении изображений PNG. +Устанавливает флаг, указывающий, следует ли сохранять полную информацию альфа-канала при сохранении изображений PNG (в отличие от одноцветной прозрачности). -Для сохранения альфа-канала альфа-квантование должно быть отключено (`alphaBlending(false)`). ([подробнее |https://www.php.net/manual/en/function.imagesavealpha]) +Альфа-смешивание должно быть отключено (`alphaBlending(false)`), чтобы альфа-канал сохранялся. ([подробнее |https://www.php.net/manual/en/function.imagesavealpha]) scale(int $newWidth, int $newHeight=-1, int $mode=IMG_BILINEAR_FIXED): Image .[method] -------------------------------------------------------------------------------------- -Масштабирование изображения с использованием заданного алгоритма интерполяции. ([подробнее |https://www.php.net/manual/en/function.imagescale]) +Масштабирует изображение с использованием заданного алгоритма интерполяции. ([подробнее |https://www.php.net/manual/en/function.imagescale]) -send(int $type=Image::JPEG, int $quality=null): void .[method] --------------------------------------------------------------- +send(int $type=ImageType::JPEG, ?int $quality=null): void .[method] +------------------------------------------------------------------- Выводит изображение в браузер. -Качество сжатия находится в диапазоне 0...100 для JPEG (по умолчанию 85), WEBP (по умолчанию 80) и AVIF (по умолчанию 30) и 0...9 для PNG (по умолчанию 9). Тип - одна из констант `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` и `Image::BMP`. +Качество сжатия находится в диапазоне 0..100 для JPEG (по умолчанию 85), WEBP (по умолчанию 80) и AVIF (по умолчанию 30) и 0..9 для PNG (по умолчанию 9). setBrush(Image $brush): void .[method] -------------------------------------- -Устанавливает изображение кисти, которое будет использоваться во всех функциях рисования линий (например, `line()` и `polygon()`) при рисовании специальными цветами IMG_COLOR_BRUSHED или IMG_COLOR_STYLEDBRUSHED. ([подробнее |https://www.php.net/manual/en/function.imagesetbrush]) +Устанавливает изображение кисти, которое будет использоваться во всех функциях рисования линий (например, `line()` и `polygon()`) при рисовании специальными цветами `IMG_COLOR_BRUSHED` или `IMG_COLOR_STYLEDBRUSHED`. ([подробнее |https://www.php.net/manual/en/function.imagesetbrush]) setClip(int $x1, int $y1, int $x2, int $y2): void .[method] ----------------------------------------------------------- -Устанавливает текущий обрез, т.е. область, за пределами которой не будут рисоваться пиксели. ([подробнее |https://www.php.net/manual/en/function.imagesetclip]) +Устанавливает текущую область обрезки, т.е. область, за пределами которой пиксели не будут нарисованы. ([подробнее |https://www.php.net/manual/en/function.imagesetclip]) setInterpolation(int $method=IMG_BILINEAR_FIXED): void .[method] ---------------------------------------------------------------- -Задает метод интерполяции, который влияет на методы `rotate()` и `affine()`. ([подробнее |https://www.php.net/manual/en/function.imagesetinterpolation]) +Устанавливает метод интерполяции, который влияет на методы `rotate()` и `affine()`. ([подробнее |https://www.php.net/manual/en/function.imagesetinterpolation]) -setPixel(int $x, int $y, int $color): void .[method] ----------------------------------------------------- -Рисует пиксель в указанной координате. ([подробнее |https://www.php.net/manual/en/function.imagesetpixel]) +setPixel(int $x, int $y, ImageColor $color): void .[method] +----------------------------------------------------------- +Рисует пиксель в заданной координате. ([подробнее |https://www.php.net/manual/en/function.imagesetpixel]) setStyle(array $style): void .[method] -------------------------------------- -Задает стиль, который будет использоваться всеми функциями рисования линий (например, `line()` и `polygon()`) при рисовании специальным цветом IMG_COLOR_STYLED или линий изображения цветом IMG_COLOR_STYLEDBRUSHED. ([подробнее |https://www.php.net/manual/en/function.imagesetstyle]) +Устанавливает стиль, который должны использовать все функции рисования линий (например, `line()` и `polygon()`) при рисовании специальным цветом `IMG_COLOR_STYLED` или линиями изображений с цветом `IMG_COLOR_STYLEDBRUSHED`. ([подробнее |https://www.php.net/manual/en/function.imagesetstyle]) setThickness(int $thickness): void .[method] -------------------------------------------- -Устанавливает толщину линий при рисовании прямоугольников, многоугольников, дуг и т.д. На сайте `$thickness` пикселей. ([подробнее |https://www.php.net/manual/en/function.imagesetthickness]) +Устанавливает толщину линий при рисовании прямоугольников, многоугольников, дуг и т. д. на `$thickness` пикселей. ([подробнее |https://www.php.net/manual/en/function.imagesetthickness]) setTile(Image $tile): void .[method] ------------------------------------ -Устанавливает изображение плитки, которое будет использоваться во всех функциях заполнения региона (например, `fill()` и `filledPolygon()`) при заполнении специальным цветом IMG_COLOR_TILED. +Устанавливает изображение плитки, которое будет использоваться во всех функциях заполнения областей (например, `fill()` и `filledPolygon()`) при заполнении специальным цветом `IMG_COLOR_TILED`. -Плитка - это изображение, используемое для заполнения области повторяющимся рисунком. В качестве плитки можно использовать любое изображение, а задав индекс прозрачного цвета изображения плитки с помощью `colorTransparent()`, можно создать плитку, в которой будут просвечивать определенные части нижележащего региона. ([подробнее |https://www.php.net/manual/en/function.imagesettile]) +Плитка - это изображение, используемое для заполнения области повторяющимся узором. Любое изображение можно использовать как плитку, и, установив прозрачный индекс цвета изображения плитки с помощью `colorTransparent()`, можно создать плитку, через которую будут просвечивать определенные части подлежащей области. ([подробнее |https://www.php.net/manual/en/function.imagesettile]) sharpen(): Image .[method] @@ -653,28 +699,18 @@ sharpen(): Image .[method] Требует наличия *Bundled GD extension*, поэтому может работать не везде. -string(int $font, int $x, int $y, string $str, int $col): void .[method] ------------------------------------------------------------------------- -Выводит строку по заданным координатам. ([подробнее |https://www.php.net/manual/en/function.imagestring]) - - -stringUp(int $font, int $x, int $y, string $s, int $col): void .[method] ------------------------------------------------------------------------- -Выводит строку по вертикали в заданных координатах. ([подробнее |https://www.php.net/manual/en/function.imagestringup]) - - -toString(int $type=Image::JPEG, int $quality=null): string .[method] --------------------------------------------------------------------- -Сохраняет изображение в строке. +toString(int $type=ImageType::JPEG, ?int $quality=null): string .[method] +------------------------------------------------------------------------- +Сохраняет изображение в строку. -Качество сжатия находится в диапазоне 0...100 для JPEG (по умолчанию 85), WEBP (по умолчанию 80) и AVIF (по умолчанию 30) и 0...9 для PNG (по умолчанию 9). Тип - одна из констант `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` и `Image::BMP`. +Качество сжатия находится в диапазоне 0..100 для JPEG (по умолчанию 85), WEBP (по умолчанию 80) и AVIF (по умолчанию 30) и 0..9 для PNG (по умолчанию 9). trueColorToPalette(bool $dither, int $ncolors): void .[method] -------------------------------------------------------------- -Преобразует truecolor изображение в палитру. ([подробнее |https://www.php.net/manual/en/function.imagetruecolortopalette]) +Преобразует изображение truecolor в палитровое. ([подробнее |https://www.php.net/manual/en/function.imagetruecolortopalette]) -ttfText(int $size, int $angle, int $x, int $y, int $color, string $fontfile, string $text): array .[method] ------------------------------------------------------------------------------------------------------------ -Печатает заданный текст в изображение с использованием шрифтов TrueType. ([подробнее |https://www.php.net/manual/en/function.imagettftext]) +ttfText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +----------------------------------------------------------------------------------------------------------------------------------------- +Выводит заданный текст на изображение, используя шрифты TrueType. ([подробнее |https://www.php.net/manual/en/function.imagettftext]) diff --git a/utils/ru/iterables.texy b/utils/ru/iterables.texy new file mode 100644 index 0000000000..ca865f9d7a --- /dev/null +++ b/utils/ru/iterables.texy @@ -0,0 +1,170 @@ +Работа с итераторами +******************** + +.[perex]{data-version:4.0.4} +[api:Nette\Utils\Iterables] - это статический класс с функциями для работы с итераторами. Его аналогом для массивов является [Nette\Utils\Arrays|arrays]. + + +Установка: + +```shell +composer require nette/utils +``` + +Все примеры предполагают, что создан псевдоним: + +```php +use Nette\Utils\Iterables; +``` + + +contains(iterable $iterable, $value): bool .[method] +---------------------------------------------------- + +Ищет указанное значение в итераторе. Использует строгое сравнение (`===`) для проверки совпадения. Возвращает `true`, если значение найдено, иначе `false`. + +```php +Iterables::contains(new ArrayIterator([1, 2, 3]), 1); // true +Iterables::contains(new ArrayIterator([1, 2, 3]), '1'); // false +``` + +Этот метод полезен, когда нужно быстро определить, находится ли конкретное значение в итераторе, без необходимости проходить все элементы вручную. + + +containsKey(iterable $iterable, $key): bool .[method] +----------------------------------------------------- + +Ищет указанный ключ в итераторе. Использует строгое сравнение (`===`) для проверки совпадения. Возвращает `true`, если ключ найден, иначе `false`. + +```php +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 0); // true +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 4); // false +``` + + +every(iterable $iterable, callable $predicate): bool .[method] +-------------------------------------------------------------- + +Проверяет, все ли элементы итератора удовлетворяют условию, определенному в `$predicate`. Функция `$predicate` имеет сигнатуру `function ($value, $key, iterable $iterable): bool` и должна возвращать `true` для каждого элемента, чтобы метод `every()` вернул `true`. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isBelowThreshold = fn($value) => $value < 40; +$res = Iterables::every($iterator, $isBelowThreshold); // true +``` + +Этот метод полезен для проверки, удовлетворяют ли все элементы в коллекции определенному условию, например, все ли числа меньше определенного значения. + + +filter(iterable $iterable, callable $predicate): Generator .[method] +-------------------------------------------------------------------- + +Создает новый итератор (генератор), который содержит только те элементы из исходного итератора, которые удовлетворяют условию, определенному в `$predicate`. Функция `$predicate` имеет сигнатуру `function ($value, $key, iterable $iterable): bool` и должна возвращать `true` для элементов, которые должны быть сохранены. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::filter($iterator, fn($v) => $v < 3); +// 1, 2 +``` + +Метод использует генератор, что означает, что фильтрация происходит постепенно при прохождении результата. Это эффективно с точки зрения памяти и позволяет обрабатывать даже очень большие коллекции. Если вы не пройдете все элементы результирующего итератора, вы сэкономите вычислительную мощность, так как не все элементы исходного итератора будут обработаны. + + +first(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------------- + +Возвращает первый элемент итератора. Если указан `$predicate`, возвращает первый элемент, удовлетворяющий данному условию. Функция `$predicate` имеет сигнатуру `function ($value, $key, iterable $iterable): bool`. Если не найден ни один подходящий элемент, вызывается функция `$else` (если она указана) и возвращается ее результат. Если `$else` не указано, возвращается `null`. + +```php +Iterables::first(new ArrayIterator([1, 2, 3])); // 1 +Iterables::first(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 3 +Iterables::first(new ArrayIterator([])); // null +Iterables::first(new ArrayIterator([]), else: fn() => false); // false +``` + +Этот метод полезен, когда нужно быстро получить первый элемент коллекции или первый элемент, удовлетворяющий определенному условию, без необходимости проходить всю коллекцию вручную. + + +firstKey(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +---------------------------------------------------------------------------------------------- + +Возвращает ключ первого элемента итератора. Если указан `$predicate`, возвращает ключ первого элемента, удовлетворяющего данному условию. Функция `$predicate` имеет сигнатуру `function ($value, $key, iterable $iterable): bool`. Если не найден ни один подходящий элемент, вызывается функция `$else` (если она указана) и возвращается ее результат. Если `$else` не указано, возвращается `null`. + +```php +Iterables::firstKey(new ArrayIterator([1, 2, 3])); // 0 +Iterables::firstKey(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 2 +Iterables::firstKey(new ArrayIterator(['a' => 1, 'b' => 2])); // 'a' +Iterables::firstKey(new ArrayIterator([])); // null +``` + + +map(iterable $iterable, callable $transformer): Generator .[method] +------------------------------------------------------------------- + +Создает новый итератор (генератор), применяя функцию `$transformer` к каждому элементу исходного итератора. Функция `$transformer` имеет сигнатуру `function ($value, $key, iterable $iterable): mixed`, и ее возвращаемое значение используется как новое значение элемента. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::map($iterator, fn($v) => $v * 2); +// 2, 4, 6 +``` + +Метод использует генератор, что означает, что трансформация происходит постепенно при прохождении результата. Это эффективно с точки зрения памяти и позволяет обрабатывать даже очень большие коллекции. Если вы не пройдете все элементы результирующего итератора, вы сэкономите вычислительную мощность, так как не все элементы исходного итератора будут обработаны. + + +mapWithKeys(iterable $iterable, callable $transformer): Generator .[method] +--------------------------------------------------------------------------- + +Создает новый итератор (генератор) путем трансформации значений и ключей исходного итератора. Функция `$transformer` имеет сигнатуру `function ($value, $key, iterable $iterable): ?array{$newKey, $newValue}`. Если `$transformer` возвращает `null`, элемент пропускается. Для сохраненных элементов первый элемент возвращенного массива используется как новый ключ, а второй элемент - как новое значение. + +```php +$iterator = new ArrayIterator(['a' => 1, 'b' => 2]); +$iterator = Iterables::mapWithKeys($iterator, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +Как и `map()`, этот метод использует генератор для постепенной обработки и эффективной работы с памятью. Это позволяет работать с большими коллекциями и экономить вычислительную мощность при частичном прохождении результата. + + +memoize(iterable $iterable): IteratorAggregate .[method] +-------------------------------------------------------- + +Создает обертку вокруг итератора, которая во время итерации кеширует его ключи и значения. Это позволяет повторно итерировать данные без необходимости снова проходить исходный источник данных. + +```php +$iterator = /* данные, которые нельзя итерировать несколько раз */ +$memoized = Iterables::memoize($iterator); +// Теперь вы можете итерировать $memoized несколько раз без потери данных +``` + +Этот метод полезен в ситуациях, когда вам нужно несколько раз пройти один и тот же набор данных, но исходный итератор не позволяет повторную итерацию или повторное прохождение было бы затратным (например, при чтении данных из базы данных или файла). + + +some(iterable $iterable, callable $predicate): bool .[method] +------------------------------------------------------------- + +Проверяет, удовлетворяет ли хотя бы один элемент итератора условию, определенному в `$predicate`. Функция `$predicate` имеет сигнатуру `function ($value, $key, iterable $iterable): bool` и должна возвращать `true` хотя бы для одного элемента, чтобы метод `some()` вернул `true`. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isEven = fn($value) => $value % 2 === 0; +$res = Iterables::some($iterator, $isEven); // true +``` + +Этот метод полезен для быстрой проверки, существует ли в коллекции хотя бы один элемент, удовлетворяющий определенному условию, например, содержит ли коллекция хотя бы одно четное число. + +См. [#every()]. + + +toIterator(iterable $iterable): Iterator .[method] +-------------------------------------------------- + +Преобразует любой итерируемый объект (array, Traversable) в Iterator. Если входные данные уже являются Iterator, возвращает их без изменений. + +```php +$array = [1, 2, 3]; +$iterator = Iterables::toIterator($array); +// Теперь у вас есть Iterator вместо массива +``` + +Этот метод полезен, когда нужно убедиться, что у вас есть Iterator, независимо от типа входных данных. Это может быть полезно при создании функций, которые работают с различными типами итерируемых данных. diff --git a/utils/ru/json.texy b/utils/ru/json.texy index be4e3967f5..6bdddb4a6e 100644 --- a/utils/ru/json.texy +++ b/utils/ru/json.texy @@ -2,7 +2,7 @@ ************* .[perex] -[api:Nette\Utils\Json] это статический класс с функциями для кодирования и декодирования формата JSON. Он обрабатывает уязвимости в различных версиях PHP и выбрасывает исключения при возникновении ошибок. +[api:Nette\Utils\Json] - это статический класс с функциями для кодирования и декодирования формата JSON. Он обрабатывает уязвимости различных версий PHP и выбрасывает исключения при ошибках. Установка: @@ -11,44 +11,44 @@ composer require nette/utils ``` -Во всех примерах предполагается, что псевдоним уже создан: +Все примеры предполагают, что создан псевдоним: ```php use Nette\Utils\Json; ``` -Использование .[#toc-usage] -=========================== +Использование +============= encode(mixed $value, bool $pretty=false, bool $asciiSafe=false, bool $htmlSafe=false, bool $forceObjects=false): string .[method] --------------------------------------------------------------------------------------------------------------------------------- -Конвертирует `$value` в формат JSON. +Преобразует `$value` в формат JSON. -Если установлено значение `$pretty`, он форматирует JSON для более легкого чтения и ясности: +При установке `$pretty` форматирует JSON для облегчения чтения и наглядности: ```php Json::encode($value); // возвращает JSON -Json::encode($value, pretty: true); // возвращает более чистый JSON +Json::encode($value, pretty: true); // возвращает более читаемый JSON ``` -На `$asciiSafe` он генерирует вывод в ASCII, т.е. заменяет символы юникода последовательностью `\uxxxx`: +При `$asciiSafe` генерирует вывод в ASCII, т.е. символы unicode заменяются последовательностями `\uxxxx`: ```php Json::encode('žluťoučký', asciiSafe: true); // '"\u017elu\u0165ou\u010dk\u00fd"' ``` -Параметр `$htmlSafe` гарантирует, что вывод не содержит символов, имеющих специальное значение в HTML: +Параметр `$htmlSafe` гарантирует, что вывод не будет содержать символов, имеющих специальное значение в HTML: ```php Json::encode('onesendJson($data)`, который можно вызвать в методе `action*()`, например, см. раздел [Отправка ответа |application:presenters#Sending a Response]. +Для этого можно использовать метод `$this->sendJson($data)`, который можно вызвать, например, в методе `action*()`, см. [Отправка ответа |application:presenters#Отправка ответа]. diff --git a/utils/ru/paginator.texy b/utils/ru/paginator.texy index 092e63e499..28bd45860a 100644 --- a/utils/ru/paginator.texy +++ b/utils/ru/paginator.texy @@ -1,8 +1,8 @@ -Пагинатор +Paginator ********* .[perex] -Нужно разбить на страницы дамп данных? Поскольку математика пагинации может быть сложной, [api:Nette\Utils\Paginator] может помочь вам в этом. +Нужно разбить вывод данных на страницы? Поскольку математика пагинации может быть сложной, вам поможет [api:Nette\Utils\Paginator]. Установка: @@ -11,7 +11,7 @@ composer require nette/utils ``` -Создайте объект подкачки и задайте его основную информацию: +Создадим объект пагинатора и установим ему основную информацию: ```php $paginator = new Nette\Utils\Paginator; @@ -23,10 +23,10 @@ $paginator->setItemCount(356); // общее количество элемент Страницы нумеруются с 1. Мы можем изменить это с помощью `setBase()`: ```php -$paginator->setBase(0); // нумерация с 0 +$paginator->setBase(0); // нумеруем с 0 ``` -Теперь объект будет предоставлять всю основную информацию, полезную при создании страницы подкачки. Например, вы можете передать его в шаблон и использовать его там. +Объект теперь предоставляет всю основную информацию, полезную при создании пагинатора. Вы можете, например, передать его в шаблон и использовать там. ```php $paginator->isFirst(); // мы на первой странице? @@ -36,13 +36,13 @@ $paginator->getFirstPage(); // номер первой страницы $paginator->getLastPage(); // номер последней страницы $paginator->getFirstItemOnPage(); // порядковый номер первого элемента на странице $paginator->getLastItemOnPage(); // порядковый номер последнего элемента на странице -$paginator->getPageIndex(); // номер текущей страницы, нумерация от 0 +$paginator->getPageIndex(); // номер текущей страницы, нумерованный с 0 $paginator->getPageCount(); // общее количество страниц $paginator->getItemsPerPage(); // количество элементов на странице $paginator->getItemCount(); // общее количество элементов, если известно ``` -Конструктор страниц поможет в составлении SQL-запросов. Методы `getLength()` и `getOffset()` возвращают значения для использования в пунктах LIMIT и OFFSET: +Пагинатор поможет при формировании SQL-запроса. Методы `getLength()` и `getOffset()` возвращают значения, которые мы используем в конструкциях LIMIT и OFFSET: ```php $result = $database->query( @@ -52,7 +52,7 @@ $result = $database->query( ); ``` -Если нам нужно расположить страницы в обратном порядке, т.е. страница 1 соответствует самому высокому смещению, мы используем `getCountdownOffset()`: +Если нам нужно разбить на страницы в обратном порядке, т.е. страница № 1 соответствует наибольшему смещению, используем `getCountdownOffset()`: ```php $result = $database->query( @@ -62,4 +62,4 @@ $result = $database->query( ); ``` -Пример того, как использовать это в приложении, приведен в книге [Database Results Pagination |best-practices:pagination] Cookbook. +Пример использования в приложении вы найдете в руководстве [Пагинация результатов базы данных |best-practices:pagination]. diff --git a/utils/ru/random.texy b/utils/ru/random.texy index c47a570d0a..03031263a4 100644 --- a/utils/ru/random.texy +++ b/utils/ru/random.texy @@ -2,7 +2,7 @@ ************************* .[perex] -[api:Nette\Utils\Random] это статический класс для генерации криптографически безопасных псевдослучайных строк. +[api:Nette\Utils\Random] - это статический класс для генерации криптографически безопасных псевдослучайных строк. Установка: @@ -15,7 +15,7 @@ composer require nette/utils generate(int $length=10, string $charlist=`'0-9a-z'`): string .[method] ----------------------------------------------------------------------- -Генерирует случайную строку заданной длины из символов, указанных в `$charlist`. Вы также можете использовать интервалы, записанные, например, как `0-9`. +Генерирует случайную строку заданной длины из символов, указанных параметром `$charlist`. Можно использовать интервалы, записанные как, например, `0-9`. ```php use Nette\Utils\Random; diff --git a/utils/ru/reflection.texy b/utils/ru/reflection.texy index 7efdd5251b..682c6b06fc 100644 --- a/utils/ru/reflection.texy +++ b/utils/ru/reflection.texy @@ -1,8 +1,8 @@ -PHP Отражение +Рефлексия PHP ************* .[perex] -[api:Nette\Utils\Reflection] это статический класс с полезными функциями для отражения PHP. Его цель - исправить недостатки родных классов и унифицировать поведение в разных версиях PHP. +[api:Nette\Utils\Reflection] - это статический класс с полезными функциями для рефлексии PHP. Его задача - исправлять недостатки нативных классов и унифицировать поведение в разных версиях PHP. Установка: @@ -11,7 +11,7 @@ PHP Отражение composer require nette/utils ``` -Во всех примерах предполагается, что псевдоним уже создан: +Все примеры предполагают, что создан псевдоним: ```php use Nette\Utils\Reflection; @@ -21,13 +21,13 @@ use Nette\Utils\Reflection; areCommentsAvailable(): bool .[method] -------------------------------------- -Определите, имеет ли reflection доступ к комментариям PHPdoc. Комментарии могут быть недоступны из-за кэша опкодов, см. например, директиву [opcache.save-comments |https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments]. +Проверяет, имеет ли рефлексия доступ к комментариям PHPdoc. Комментарии могут быть недоступны из-за opcode cache, см., например, директиву [opcache.save-comments|https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments]. expandClassName(string $name, ReflectionClass $context): string .[method] ------------------------------------------------------------------------- -Расширяет имя класса `$name` до его полного имени в контексте класса `$context`, то есть в контексте его пространства имен и определенных псевдонимов. Таким образом, здесь фактически говорится о том, как парсер PHP `$name` понял бы , если бы он был написан в теле класса `$context`. +Разрешает имя класса `$name` в его полное имя в контексте класса `$context`, то есть в контексте его пространства имен и определенных псевдонимов (`use` statements). Фактически, это говорит о том, как PHP-парсер понял бы `$name`, если бы он был записан в теле класса `$context`. ```php namespace Foo; @@ -47,9 +47,9 @@ Reflection::expandClassName('Baz', $context); // 'Foo\Baz' getMethodDeclaringMethod(ReflectionMethod $method): ReflectionMethod .[method] ------------------------------------------------------------------------------ -Возвращает отражение метода, содержащее объявление метода `$method`. Обычно каждый метод является собственным объявлением, но тело метода может находиться в трейте и под другим именем. +Возвращает рефлексию метода, который содержит объявление метода `$method`. Обычно каждый метод является своим собственным объявлением, но тело метода может находиться и в трейте под другим именем. -Поскольку PHP не предоставляет достаточной информации для определения фактического объявления, Nette использует свою собственную эвристику, которая **должна** быть надежной. +Поскольку PHP не предоставляет достаточной информации, с помощью которой можно определить фактическое объявление, Nette использует собственную эвристику, которая **должна быть** надежной. ```php trait DemoTrait @@ -76,9 +76,9 @@ Reflection::getMethodDeclaringMethod($method); // ReflectionMethod('DemoTrait::f getPropertyDeclaringClass(ReflectionProperty $prop): ReflectionClass .[method] ------------------------------------------------------------------------------ -Возвращает отражение класса или трейта, который содержит объявление свойства `$prop`. Свойство может быть объявлено в трейте. +Возвращает рефлексию класса или трейта, который содержит объявление свойства `$prop`. Свойство может быть объявлено и в трейте. -Поскольку PHP не предоставляет достаточной информации для определения фактического объявления, Nette использует свою собственную эвристику, которая **не** надежна. +Поскольку PHP не предоставляет достаточной информации, с помощью которой можно определить фактическое объявление, Nette использует собственную эвристику, которая **не является** надежной. ```php trait DemoTrait @@ -96,25 +96,25 @@ $prop = new ReflectionProperty(DemoClass::class, 'foo'); Reflection::getPropertyDeclaringClass($prop); // ReflectionClass('DemoTrait') ``` -/--comment - - - - - - - +isBuiltinType(string $type): bool .[method deprecated] +------------------------------------------------------ +Проверяет, является ли `$type` встроенным типом PHP (например, `string`, `int`, `bool`). В противном случае это имя класса. +```php +Reflection::isBuiltinType('string'); // true +Reflection::isBuiltinType('Foo'); // false +``` -\-- +.[note] +Используйте [Nette\Utils\Validator::isBuiltinType() |validators#isBuiltinType]. toString($reflection): string .[method] --------------------------------------- -Преобразует отражение в понятную человеку строку. +Преобразует объект рефлексии в понятную человеку строку. ```php $func = new ReflectionFunction('func'); diff --git a/utils/ru/smartobject.texy b/utils/ru/smartobject.texy index 458fd548d2..849b14e3ab 100644 --- a/utils/ru/smartobject.texy +++ b/utils/ru/smartobject.texy @@ -1,8 +1,8 @@ -SmartObject и StaticClass -************************* +SmartObject +*********** .[perex] -SmartObject добавляет поддержку *свойств* в классы PHP. StaticClass используется для обозначения статических классов. +SmartObject годами улучшал поведение объектов в PHP. Начиная с версии PHP 8.4, все его функции уже стали частью самого PHP, тем самым завершив свою историческую миссию быть пионером современного объектного подхода в PHP. Установка: @@ -11,19 +11,64 @@ SmartObject добавляет поддержку *свойств* в класс composer require nette/utils ``` +SmartObject был создан в 2007 году как революционное решение недостатков тогдашней объектной модели PHP. В то время, когда PHP страдал от множества проблем с объектным дизайном, он принес значительное улучшение и упрощение работы для разработчиков. Он стал легендарной частью фреймворка Nette. Он предлагал функциональность, которую PHP получил лишь много лет спустя — от контроля доступа к свойствам объектов до сложных синтаксических конструкций. С приходом PHP 8.4 он завершил свою историческую миссию, так как все его функции стали нативной частью языка. Он опередил развитие PHP на удивительные 17 лет. -Свойства, геттери и сеттери .[#toc-properties-getters-and-setters] -================================================================== +Технически SmartObject прошел интересный путь развития. Изначально он был реализован как класс `Nette\Object`, от которого другие классы наследовали необходимую функциональность. Принципиальное изменение произошло с PHP 5.4, который принес поддержку трейтов. Это позволило трансформировать его в трейт `Nette\SmartObject`, что принесло большую гибкость — разработчики могли использовать функциональность и в классах, которые уже наследовались от другого класса. В то время как исходный класс `Nette\Object` исчез с приходом PHP 7.2 (который запретил именовать классы словом `Object`), трейт `Nette\SmartObject` продолжает существовать. -В современных объектно-ориентированных языках (например, C#, Python, Ruby, JavaScript) термин *свойство* относится к [специальным членам классов |https://en.wikipedia.org/wiki/Property_(programming)], которые выглядят как переменные, но на самом деле представлены методами. Когда значение такой "переменной" присваивается или считывается, вызывается соответствующий метод (называемый getter или setter). Это очень удобная вещь, она дает нам полный контроль над доступом к переменным. Мы можем проверять вводимые данные или генерировать результаты только тогда, когда свойство прочитано. +Давайте рассмотрим свойства, которые когда-то предлагали `Nette\Object`, а позже `Nette\SmartObject`. Каждая из этих функций в свое время представляла собой значительный шаг вперед в области объектно-ориентированного программирования в PHP. -Свойства PHP не поддерживаются, но trait `Nette\SmartObject` может их имитировать. Как это использовать? -- Добавьте аннотацию к классу в виде `@property $xyz` -- Создайте геттер с именем `getXyz()` или `isXyz()`, сеттер с именем `setXyz()` -- Геттер и сеттер должны быть *публичными* или *защищенными* и необязательными, поэтому может быть свойство *только для чтения* или *только для записи*. +Согласованные состояния ошибок +------------------------------ +Одной из самых острых проблем раннего PHP было несогласованное поведение при работе с объектами. `Nette\Object` внес порядок и предсказуемость в этот хаос. Посмотрим, как выглядело исходное поведение PHP: -Мы будем использовать свойство для класса Circle, чтобы гарантировать, что в переменную `$radius` будут помещаться только неотрицательные числа. Замените `public $radius` на property: +```php +echo $obj->undeclared; // E_NOTICE, позже E_WARNING +$obj->undeclared = 1; // проходит тихо без сообщения +$obj->unknownMethod(); // Fatal error (неперехватываемая с помощью try/catch) +``` + +Fatal error завершал приложение без возможности как-либо отреагировать. Тихая запись в несуществующие члены без предупреждения могла привести к серьезным ошибкам, которые было трудно обнаружить. `Nette\Object` перехватывал все эти случаи и выбрасывал исключение `MemberAccessException`, что позволяло программистам реагировать на ошибки и решать их. + +```php +echo $obj->undeclared; // выбрасывает Nette\MemberAccessException +$obj->undeclared = 1; // выбрасывает Nette\MemberAccessException +$obj->unknownMethod(); // выбрасывает Nette\MemberAccessException +``` + +Начиная с PHP 7.0, язык больше не вызывает неперехватываемые фатальные ошибки, а с PHP 8.2 доступ к необъявленным членам считается ошибкой. + + +Подсказка "Did you mean?" +------------------------- +`Nette\Object` пришел с очень приятной функцией: интеллектуальной подсказкой при опечатках. Когда разработчик делал ошибку в названии метода или переменной, он не только сообщал об ошибке, но и предлагал руку помощи в виде предложения правильного названия. Это знаковое сообщение, известное как "did you mean?", сэкономило программистам часы поиска опечаток: + +```php +class Foo extends Nette\Object +{ + public static function from($var) + { + } +} + +$foo = Foo::form($var); +// выбрасывает Nette\MemberAccessException +// "Call to undefined static method Foo::form(), did you mean from()?" +``` + +Хотя сегодняшнее PHP не имеет аналога „did you mean?“, это дополнение умеет добавлять в ошибки [Tracy |tracy:]. И даже такие ошибки [самостоятельно исправлять |tracy:open-files-in-ide#Примеры]. + + +Свойства с контролируемым доступом +---------------------------------- +Значительной инновацией, которую SmartObject привнес в PHP, были свойства с контролируемым доступом. Эта концепция, распространенная в языках вроде C# или Python, позволила разработчикам элегантно контролировать доступ к данным объекта и обеспечивать их согласованность. Свойства являются мощным инструментом объектно-ориентированного программирования. Они работают как переменные, но на самом деле представлены методами (геттерами и сеттерами). Это позволяет валидировать входы или генерировать значения только в момент чтения. + +Для использования свойств необходимо: +- Добавить классу аннотацию в виде `@property $xyz` +- Создать геттер с именем `getXyz()` или `isXyz()`, сеттер с именем `setXyz()` +- Убедиться, что геттер и сеттер являются *public* или *protected*. Они необязательны — могут существовать как *read-only* или *write-only* свойства + +Покажем практический пример на классе Circle, где мы используем свойства для обеспечения того, чтобы радиус всегда был неотрицательным числом. Заменим исходное `public $radius` на свойство: ```php /** @@ -34,22 +79,22 @@ class Circle { use Nette\SmartObject; - private float $radius = 0.0; // not public + private float $radius = 0.0; // не public! - // getter for property $radius + // геттер для свойства $radius protected function getRadius(): float { return $this->radius; } - // setter for property $radius + // сеттер для свойства $radius protected function setRadius(float $radius): void { - // sanitizing value before saving it + // санируем значение перед сохранением $this->radius = max(0.0, $radius); } - // getter for property $visible + // геттер для свойства $visible protected function isVisible(): bool { return $this->radius > 0; @@ -57,84 +102,30 @@ class Circle } $circle = new Circle; -$circle->radius = 10; // actually calls setRadius(10) -echo $circle->radius; // calls getRadius() -echo $circle->visible; // calls isVisible() +$circle->radius = 10; // на самом деле вызывает setRadius(10) +echo $circle->radius; // вызывает getRadius() +echo $circle->visible; // вызывает isVisible() ``` -Свойства - это прежде всего "синтаксический сахар"((syntactic sugar)), который призван сделать жизнь программиста слаще за счет упрощения кода. Если они вам не нужны, вы не обязаны их использовать. - - -Статические классы .[#toc-static-classes] -========================================= - -Статические классы, т.е. классы, которые не предназначены для инстанцирования, могут быть помечены признаком `Nette\StaticClass`: +Начиная с PHP 8.4, можно достичь той же функциональности с помощью property hooks, которые предлагают гораздо более элегантный и краткий синтаксис: ```php -class Strings +class Circle { - use Nette\StaticClass; -} -``` - -Когда вы пытаетесь создать экземпляр, возникает исключение `Error`, указывающее на то, что класс является статическим. - - -Взгляд в историю .[#toc-a-look-into-the-history] -================================================ - -SmartObject использовался для улучшения и исправления поведения класса во многих отношениях, но развитие PHP сделало большинство первоначальных функций излишними. Поэтому ниже мы рассмотрим историю развития этих функций. - -С самого начала объектная модель PHP страдала от ряда серьезных недостатков и неэффективности. Это послужило причиной создания класса `Nette\Object` (в 2007 году), который попытался устранить их и улучшить опыт использования PHP. Этого оказалось достаточно, чтобы другие классы унаследовали от него и получили те преимущества, которые он принес. Когда в PHP 5.4 появилась поддержка трейтов, класс `Nette\Object` был заменен на `Nette\SmartObject`. Таким образом, больше не было необходимости наследоваться от общего предка. Кроме того, trait можно было использовать в классах, которые уже наследовались от другого класса. Окончательный конец `Nette\Object` наступил с выходом PHP 7.2, в котором было запрещено давать классам имена `Object`. - -По мере развития PHP объектная модель и возможности языка совершенствовались. Отдельные функции класса `SmartObject` стали излишними. После выхода PHP 8.2 единственной функцией, которая пока не поддерживается в PHP напрямую, осталась возможность использовать так называемые [свойства |#Properties, getters and setters]. - -Какие функции предлагали `Nette\Object` и `Nette\Object`? Вот обзор. (В примерах используется класс `Nette\Object`, но большинство свойств применимо и к признаку `Nette\SmartObject` ). - - -Непоследовательные ошибки .[#toc-inconsistent-errors] ------------------------------------------------------ -PHP имел непоследовательное поведение при обращении к необъявленным членам. Состояние на момент публикации `Nette\Object` было следующим: - -```php -echo $obj->undeclared; // E_NOTICE, later E_WARNING -$obj->undeclared = 1; // passes silently without reporting -$obj->unknownMethod(); // Fatal error (not catchable by try/catch) -``` - -Фатальная ошибка завершала приложение без какой-либо возможности отреагировать. Тихая запись в несуществующие члены без предупреждения могла привести к серьезным ошибкам, которые было трудно обнаружить. `Nette\Object` Все эти случаи были пойманы, и было выброшено исключение `MemberAccessException`. - -```php -echo $obj->undeclared; // throw Nette\MemberAccessException -$obj->undeclared = 1; // throw Nette\MemberAccessException -$obj->unknownMethod(); // throw Nette\MemberAccessException -``` -Начиная с PHP 7.0, PHP больше не вызывает неперехватываемых фатальных ошибок, а доступ к необъявленным членам стал ошибкой начиная с PHP 8.2. - - -Вы имели в виду? .[#toc-did-you-mean] -------------------------------------- -Если возникала ошибка `Nette\MemberAccessException`, возможно, из-за опечатки при обращении к объектной переменной или вызове метода, `Nette\Object` пытался дать подсказку в сообщении об ошибке, как исправить ошибку, в виде знакового дополнения "Вы имели в виду?". + public float $radius = 0.0 { + set => max(0.0, $value); + } -```php -class Foo extends Nette\Object -{ - public static function from($var) - { + public bool $visible { + get => $this->radius > 0; } } - -$foo = Foo::form($var); -// throw Nette\MemberAccessException -// "Call to undefined static method Foo::form(), did you mean from()?" ``` -Современный PHP, возможно, не имеет формы "Вы имели в виду?", но [Tracy |tracy:] добавляет это дополнение к ошибкам. И он даже может сам [исправлять |tracy:open-files-in-ide#toc-demos] такие ошибки. - -Методы расширения .[#toc-extension-methods] -------------------------------------------- -Вдохновлен методами расширения из C#. Они дают возможность добавлять новые методы к существующим классам. Например, вы можете добавить метод `addDateTime()` к форме, чтобы добавить свой собственный DateTimePicker. +Методы расширения +----------------- +`Nette\Object` привнес в PHP еще одну интересную концепцию, вдохновленную современными языками программирования — методы расширения. Эта функция, заимствованная из C#, позволила разработчикам элегантно расширять существующие классы новыми методами без необходимости их изменять или наследоваться от них. Например, вы могли добавить в форму метод `addDateTime()`, который добавит собственный DateTimePicker: ```php Form::extensionMethod( @@ -146,22 +137,22 @@ $form = new Form; $form->addDateTime('date'); ``` -Методы расширения оказались непрактичными, поскольку их имена не автозаполнялись редакторами, вместо этого они сообщали, что метод не существует. Поэтому их поддержка была прекращена. +Методы расширения оказались непрактичными, так как их имена не подсказывались редакторами, наоборот, сообщали, что метод не существует. Поэтому их поддержка была прекращена. Сегодня чаще используется композиция или наследование для расширения функциональности классов. -Получение имени класса .[#toc-getting-the-class-name] ------------------------------------------------------ +Получение имени класса +---------------------- +Для получения имени класса SmartObject предлагал простой метод: ```php -$class = $obj->getClass(); // using Nette\Object -$class = $obj::class; // since PHP 8.0 +$class = $obj->getClass(); // с помощью Nette\Object +$class = $obj::class; // с PHP 8.0 ``` -Доступ к размышлениям и аннотациям .[#toc-access-to-reflection-and-annotations] -------------------------------------------------------------------------------- - -`Nette\Object` предложен доступ к размышлениям и аннотациям с помощью методов `getReflection()` и `getAnnotation()`: +Доступ к рефлексии и аннотациям +------------------------------- +`Nette\Object` предлагал доступ к рефлексии и аннотациям с помощью методов `getReflection()` и `getAnnotation()`. Этот подход значительно упростил работу с метаинформацией классов: ```php /** @@ -173,10 +164,10 @@ class Foo extends Nette\Object $obj = new Foo; $reflection = $obj->getReflection(); -$reflection->getAnnotation('author'); // returns 'John Doe +$reflection->getAnnotation('author'); // возвращает 'John Doe' ``` -Начиная с PHP 8.0, появилась возможность доступа к мета-информации в виде атрибутов: +Начиная с PHP 8.0, можно получать доступ к метаинформации в виде атрибутов, которые предлагают еще большие возможности и лучший контроль типов: ```php #[Author('John Doe')] @@ -190,10 +181,9 @@ $reflection->getAttributes(Author::class)[0]; ``` -Геттеры методов .[#toc-method-getters] --------------------------------------- - -`Nette\Object` предлагают элегантный способ работы с методами, как если бы они были переменными: +Метод-геттеры +------------- +`Nette\Object` предлагал элегантный способ передавать методы так, как если бы это были переменные: ```php class Foo extends Nette\Object @@ -209,7 +199,7 @@ $method = $obj->adder; echo $method(2, 3); // 5 ``` -Начиная с PHP 8.1, вы можете использовать так называемый "первоклассный синтаксис вызываемых методов":https://www.php.net/manual/en/functions.first_class_callable_syntax: +Начиная с PHP 8.1, можно использовать так называемый "first-class callable syntax":https://www.php.net/manual/en/functions.first_class_callable_syntax, который развивает эту концепцию еще дальше: ```php $obj = new Foo; @@ -218,10 +208,9 @@ echo $method(2, 3); // 5 ``` -События .[#toc-events] ----------------------- - -`Nette\Object` предлагает синтаксический сахар для запуска [события |nette:glossary#events]: +События +------- +SmartObject предлагает упрощенный синтаксис для работы с [событиями |nette:glossary#События Events]. События позволяют объектам информировать другие части приложения об изменениях своего состояния: ```php class Circle extends Nette\Object @@ -231,12 +220,12 @@ class Circle extends Nette\Object public function setRadius(float $radius): void { $this->onChange($this, $radius); - $this->radius = $radius + $this->radius = $radius; } } ``` -Код `$this->onChange($this, $radius)` эквивалентен следующему: +Код `$this->onChange($this, $radius)` эквивалентен следующему циклу: ```php foreach ($this->onChange as $callback) { @@ -244,7 +233,7 @@ foreach ($this->onChange as $callback) { } ``` -Для ясности мы рекомендуем избегать магического метода `$this->onChange()`. Хорошей заменой будет [Nette\Utils\Arrays::invoke |arrays#invoke]: +Из соображений понятности рекомендуется избегать магического метода `$this->onChange()`. Практической заменой является, например, функция [Nette\Utils\Arrays::invoke |arrays#invoke]: ```php Nette\Utils\Arrays::invoke($this->onChange, $this, $radius); diff --git a/utils/ru/staticclass.texy b/utils/ru/staticclass.texy new file mode 100644 index 0000000000..e90572c085 --- /dev/null +++ b/utils/ru/staticclass.texy @@ -0,0 +1,21 @@ +Статические классы +****************** + +.[perex] +StaticClass используется для обозначения статических классов. + + +Установка: + +```shell +composer require nette/utils +``` + +Статические классы, то есть классы, которые не предназначены для создания экземпляров, можно пометить трейтом [api:Nette\StaticClass]: + +```php +class Strings +{ + use Nette\StaticClass; +} +``` diff --git a/utils/ru/strings.texy b/utils/ru/strings.texy index 7a15e209a4..25ccac2510 100644 --- a/utils/ru/strings.texy +++ b/utils/ru/strings.texy @@ -2,7 +2,7 @@ ****************** .[perex] -[api:Nette\Utils\Strings] статический класс, содержащий полезные функции для работы со строками, в основном в кодировке UTF-8. +[api:Nette\Utils\Strings] — это статический класс с полезными функциями для работы со строками, преимущественно в кодировке UTF-8. Установка: @@ -11,26 +11,26 @@ composer require nette/utils ``` -Во всех примерах предполагается, что псевдоним уже создан: +Все примеры предполагают созданный псевдоним: ```php use Nette\Utils\Strings; ``` -С учетом регистра .[#toc-letter-case] -===================================== +Изменение регистра букв +======================= -Для этих функций требуется расширение PHP `mbstring`. +Эти функции требуют расширения PHP `mbstring`. lower(string $s): string .[method] ---------------------------------- -Преобразует строку UTF-8 в строчный регистр. +Преобразует строку UTF-8 в нижний регистр. ```php -Strings::lower('Dobrý den'); // 'dobrý den' +Strings::lower('Добрый день'); // 'добрый день' ``` @@ -40,54 +40,54 @@ upper(string $s): string .[method] Преобразует строку UTF-8 в верхний регистр. ```php -Strings::upper('Dobrý den'); // 'DOBRÝ DEN' +Strings::upper('Добрый день'); // 'ДОБРЫЙ ДЕНЬ' ``` firstUpper(string $s): string .[method] --------------------------------------- -Преобразует первую букву строки UTF-8 в верхний регистр, остальные не изменяет. +Преобразует первую букву строки UTF-8 в верхний регистр, остальные не меняет. ```php -Strings::firstUpper('dobrý den'); // 'Dobrý den' +Strings::firstUpper('добрый день'); // 'Добрый день' ``` firstLower(string $s): string .[method] --------------------------------------- -Преобразует первую букву строки UTF-8 в строчную, остальные не изменяет. +Преобразует первую букву строки UTF-8 в нижний регистр, остальные не меняет. ```php -Strings::firstLower('Dobrý den'); // 'dobrý den' +Strings::firstLower('Добрый день'); // 'добрый день' ``` capitalize(string $s): string .[method] --------------------------------------- -Преобразует первую букву каждого слова в строке UTF-8 в верхний регистр, остальные - в нижний. +Преобразует первую букву каждого слова в строке UTF-8 в верхний регистр, остальные в нижний. ```php -Strings::capitalize('Dobrý den'); // 'Dobrý Den' +Strings::capitalize('Добрый день'); // 'Добрый День' ``` -Редактировать строку .[#toc-editing-a-string] -============================================= +Редактирование строки +===================== normalize(string $s): string .[method] -------------------------------------- -Удаляет управляющие символы, нормализует переносы строк до `\n`, обрезает ведущие и отстающие пустые строки, обрезает правые переносы строк, нормализует UTF-8 до нормальной формы NFC. +Удаляет управляющие символы, нормализует концы строк в `\n`, обрезает начальные и конечные пустые строки, обрезает пробелы справа на строках, нормализует UTF-8 в нормальную форму NFC. unixNewLines(string $s): string .[method] ----------------------------------------- -Преобразует переносы строк в `\n`, используемые в системах Unix. Разрывы строк: `\n`, `\r`, `\r\n`, U+2028 разделитель строк, U+2029 разделитель абзацев. +Преобразует концы строк в `\n`, используемые в unix-системах. Концы строк: `\n`, `\r`, `\r\n`, U+2028 line separator, U+2029 paragraph separator. ```php $unixLikeLines = Strings::unixNewLines($string); @@ -97,52 +97,52 @@ $unixLikeLines = Strings::unixNewLines($string); platformNewLines(string $s): string .[method] --------------------------------------------- -Преобразует переводы строк в символы, характерные для текущей платформы, т.е. `\r\n` в Windows и `\n` в другом месте. Разрывы строк: `\n`, `\r`, `\r\n`, U+2028 разделитель строк, U+2029 разделитель абзацев. +Преобразует концы строк в символы, специфичные для текущей платформы, т.е. `\r\n` в Windows и `\n` в других системах. Концы строк: `\n`, `\r`, `\r\n`, U+2028 line separator, U+2029 paragraph separator. ```php $platformLines = Strings::platformNewLines($string); ``` -webalize(string $s, string $charlist=null, bool $lower=true): string .[method] ------------------------------------------------------------------------------- +webalize(string $s, ?string $charlist=null, bool $lower=true): string .[method] +------------------------------------------------------------------------------- -Изменяет строку UTF-8 до формата, используемого в URL, т.е. удаляет диакритические знаки и заменяет все символы, кроме букв английского алфавита и цифр, на дефис. +Преобразует строку UTF-8 в форму, используемую в URL, т.е. удаляет диакритику и все символы, кроме букв английского алфавита и цифр, заменяет дефисом. ```php -Strings::webalize('náš produkt'); // 'nas-produkt' +Strings::webalize('наш продукт'); // 'nash-produkt' ``` -Если необходимо сохранить другие символы, их можно указать во втором параметре функции. +Если нужно сохранить и другие символы, их можно указать во втором параметре функции. ```php -Strings::webalize('10. obrázek_id', '._'); // '10.-obrazek_id' +Strings::webalize('10. изображение_id', '._'); // '10.-izobrazhenie_id' ``` -Третий параметр можно использовать для подавления преобразования в нижний регистр. +Третьим параметром можно отключить преобразование в нижний регистр. ```php -Strings::webalize('Dobrý den', null, false); // 'Dobry-den' +Strings::webalize('Добрый день', null, false); // 'Dobryy-den' ``` .[caution] -Требуется расширение PHP `intl`. +Требует расширения PHP `intl`. -trim(string $s, string $charlist=null): string .[method] --------------------------------------------------------- +trim(string $s, ?string $charlist=null): string .[method] +--------------------------------------------------------- -Обрезает пробелы (или другие символы, указанные вторым параметром) из начала и конца строки UTF-8. +Обрезает пробелы (или другие символы, указанные вторым параметром) с начала и конца строки UTF-8. ```php -Strings::trim(' Hello '); // 'Hello' +Strings::trim(' Привет '); // 'Привет' ``` truncate(string $s, int $maxLen, string $append=`'…'`): string .[method] ------------------------------------------------------------------------ -Усекает строку UTF-8 до указанной максимальной длины, стараясь сохранить целые слова. Если строка усеченная, то в конец добавляется триплет (можно изменить с помощью третьего параметра). +Обрезает строку UTF-8 до указанной максимальной длины, стараясь сохранять целые слова. Если строка сокращается, в конец добавляется многоточие (можно изменить третьим параметром). ```php $text = 'Řekněte, jak se máte?'; @@ -156,7 +156,7 @@ Strings::truncate($text, 20, '~'); // 'Řekněte, jak se~' indent(string $s, int $level=1, string $indentationChar=`"\t"`): string .[method] --------------------------------------------------------------------------------- -Отступ многострочного текста слева. Количество отступов определяется вторым параметром, который используется для отступов третьего параметра (значение по умолчанию - tab). +Добавляет отступ слева к многострочному тексту. Количество отступов определяет второй параметр, чем отступать — третий параметр (по умолчанию табуляция). ```php Strings::indent('Nette'); // "\tNette" @@ -167,7 +167,7 @@ Strings::indent('Nette', 2, '+'); // '++Nette' padLeft(string $s, int $length, string $pad=`' '`): string .[method] -------------------------------------------------------------------- -Завершает строку UTF-8 до указанной длины, повторяя строку `$pad` слева. +Дополняет строку UTF-8 до заданной длины, повторяя строку `$pad` слева. ```php Strings::padLeft('Nette', 6); // ' Nette' @@ -178,7 +178,7 @@ Strings::padLeft('Nette', 8, '+*'); // '+*+Nette' padRight(string $s, int $length, string $pad=`' '`): string .[method] --------------------------------------------------------------------- -Завершает строку UTF-8 до указанной длины, повторяя строку `$pad` справа. +Дополняет строку UTF-8 до заданной длины, повторяя строку `$pad` справа. ```php Strings::padRight('Nette', 6); // 'Nette ' @@ -186,10 +186,10 @@ Strings::padRight('Nette', 8, '+*'); // 'Nette+*+' ``` -substring(string $s, int $start, int $length=null): string .[method] --------------------------------------------------------------------- +substring(string $s, int $start, ?int $length=null): string .[method] +--------------------------------------------------------------------- -Возвращает часть строки UTF-8 `$s`, заданную начальной позицией `$start` и длиной `$length`. Если `$start` отрицательный, то возвращаемая строка будет начинаться с символа -`$start` символа с конца. +Возвращает часть строки UTF-8 `$s`, заданную начальной позицией `$start` и длиной `$length`. Если `$start` отрицательный, возвращаемая строка будет начинаться с символа -`$start` от конца. ```php Strings::substring('Nette Framework', 0, 5); // 'Nette' @@ -201,7 +201,7 @@ Strings::substring('Nette Framework', -4); // 'work' reverse(string $s): string .[method] ------------------------------------ -Изменяет строку UTF-8. +Обращает строку UTF-8. ```php Strings::reverse('Nette'); // 'etteN' @@ -217,71 +217,71 @@ length(string $s): int .[method] ```php Strings::length('Nette'); // 5 -Strings::length('červená'); // 7 +Strings::length('красный'); // 7 ``` -/--comment - - - - - - - - - - - - - - - - - - - - - - - - - - - - +startsWith(string $haystack, string $needle): bool .[method deprecated] +----------------------------------------------------------------------- +Проверяет, начинается ли строка `$haystack` со строки `$needle`. +```php +$haystack = 'Начинается'; +$needle = 'На'; +Strings::startsWith($haystack, $needle); // true +``` +.[note] +Используйте нативную `str_starts_with()`:https://www.php.net/manual/en/function.str-starts-with.php. +endsWith(string $haystack, string $needle): bool .[method deprecated] +--------------------------------------------------------------------- +Проверяет, заканчивается ли строка `$haystack` строкой `$needle`. +```php +$haystack = 'Заканчивается'; +$needle = 'ется'; +Strings::endsWith($haystack, $needle); // true +``` +.[note] +Используйте нативную `str_ends_with()`:https://www.php.net/manual/en/function.str-ends-with.php. +contains(string $haystack, string $needle): bool .[method deprecated] +--------------------------------------------------------------------- +Проверяет, содержит ли строка `$haystack` строку `$needle`. +```php +$haystack = 'Аудитория'; +$needle = 'дитор'; +Strings::contains($haystack, $needle); // true +``` -\-- +.[note] +Используйте нативную `str_contains()`:https://www.php.net/manual/en/function.str-contains.php. -compare(string $left, string $right, int $length=null): bool .[method] ----------------------------------------------------------------------- +compare(string $left, string $right, ?int $length=null): bool .[method] +----------------------------------------------------------------------- -Сравнение двух строк UTF-8 или частей строк без учета регистра. Если `$length` содержит null, то сравниваются целые строки, если отрицательно, то сравнивается соответствующее количество символов с конца строк, иначе сравнивается соответствующее количество символов с начала. +Сравнение двух строк UTF-8 или их частей без учета регистра. Если `$length` содержит null, сравниваются целые строки, если отрицательный, сравнивается соответствующее количество символов с конца строк, иначе сравнивается соответствующее количество символов с начала. ```php Strings::compare('Nette', 'nette'); // true -Strings::compare('Nette', 'next', 2); // true - shoda prvních 2 znaků -Strings::compare('Nette', 'Latte', -2); // true - shoda posledních 2 znaků +Strings::compare('Nette', 'next', 2); // true - совпадение первых 2 символов +Strings::compare('Nette', 'Latte', -2); // true - совпадение последних 2 символов ``` findPrefix(...$strings): string .[method] ----------------------------------------- -Находит общее начало строк. Или возвращает пустую строку, если общий префикс не найден. +Находит общий префикс строк. Или возвращает пустую строку, если общий префикс не найден. ```php Strings::findPrefix('prefix-a', 'prefix-bb', 'prefix-c'); // 'prefix-' @@ -293,7 +293,7 @@ Strings::findPrefix('Nette', 'is', 'great'); // '' before(string $haystack, string $needle, int $nth=1): ?string .[method] ----------------------------------------------------------------------- -Возвращает часть строки `$haystack` перед n-м `$nth` вхождением строки `$needle`. Или `null`, если `$needle` не был найден. Если `$nth` отрицательный, то поиск ведется с конца строки. +Возвращает часть строки `$haystack` перед n-м `$nth` вхождением строки `$needle`. Или `null`, если `$needle` не найден. При отрицательном значении `$nth` поиск ведется с конца строки. ```php Strings::before('Nette_is_great', '_', 1); // 'Nette' @@ -306,7 +306,7 @@ Strings::before('Nette_is_great', '_', 3); // null after(string $haystack, string $needle, int $nth=1): ?string .[method] ---------------------------------------------------------------------- -Возвращает часть строки `$haystack` после n-го `$nth` вхождения строки `$needle`. Или `null`, если `$needle` не был найден. Если `$nth` отрицательный, то поиск ведется с конца строки. +Возвращает часть строки `$haystack` после n-го `$nth` вхождения строки `$needle`. Или `null`, если `$needle` не найден. При отрицательном значении `$nth` поиск ведется с конца строки. ```php Strings::after('Nette_is_great', '_', 2); // 'great' @@ -319,7 +319,7 @@ Strings::after('Nette_is_great', '_', 3); // null indexOf(string $haystack, string $needle, int $nth=1): ?int .[method] --------------------------------------------------------------------- -Возвращает позицию в символах n-го `$nth` вхождения строки `$needle` в строку `$haystack`. Или `null`, если `$needle` не был найден. Если `$nth` отрицательный, то поиск ведется с конца строки. +Возвращает позицию в символах n-го `$nth` вхождения строки `$needle` в строке `$haystack`. Или `null`, если `$needle` не найден. При отрицательном значении `$nth` поиск ведется с конца строки. ```php Strings::indexOf('abc abc abc', 'abc', 2); // 4 @@ -328,76 +328,76 @@ Strings::indexOf('abc abc abc', 'd'); // null ``` -Кодирование .[#toc-kodovani] -============================ +Кодировка +========= fixEncoding(string $s): string .[method] ---------------------------------------- -Удаляет из строки недопустимые символы UTF-8. +Удаляет из строки недействительные символы UTF-8. ```php $correctStrings = Strings::fixEncoding($string); ``` -/--comment - - - - - - +checkEncoding(string $s): bool .[method deprecated] +--------------------------------------------------- +Проверяет, является ли строка действительной UTF-8. +```php +$isUtf8 = Strings::checkEncoding($string); +``` -\-- +.[note] +Используйте [Nette\Utils\Validator::isUnicode() |validators#isUnicode]. toAscii(string $s): string .[method] ------------------------------------ -Преобразует строку UTF-8 в ASCII, т.е. удаляет диакритические знаки и т.д. +Преобразует строку UTF-8 в ASCII, т.е. удаляет диакритику и т.д. ```php -Strings::toAscii('žluťoučký kůň'); // 'zlutoucky kun' +Strings::toAscii('желтая лошадь'); // 'zheltaya loshad' ``` .[caution] -Требуется расширение PHP `intl`. +Требует расширения PHP `intl`. chr(int $code): string .[method] -------------------------------- -Возвращает определенный символ UTF-8 из кодовой точки (число в диапазоне 0x0000...D7FF и 0xE000...10FFFF). +Возвращает специфический символ в UTF-8 из кодовой точки (число в диапазоне 0x0000..D7FF и 0xE000..10FFFF). ```php -Strings::chr(0xA9); // '©' v kódování UTF-8 +Strings::chr(0xA9); // '©' в кодировке UTF-8 ``` ord(string $char): int .[method] -------------------------------- -Возвращает кодовую точку UTF-8 определенного символа (число в диапазоне 0x0000...D7FF или 0xE000...10FFFF). +Возвращает кодовую точку конкретного символа в UTF-8 (число в диапазоне 0x0000..D7FF или 0xE000..10FFFF). ```php Strings::ord('©'); // 0xA9 ``` -Регулярные выражения .[#toc-fixencoding] -======================================== +Регулярные выражения +==================== -Класс Strings предоставляет функции для работы с регулярными выражениями. В отличие от собственных функций PHP, они имеют более понятный API, лучшую поддержку Unicode и, что самое важное, обнаружение ошибок. Любая ошибка при компиляции или обработке выражения приведет к возникновению исключения `Nette\RegexpException`. +Класс Strings предлагает функции для работы с регулярными выражениями. В отличие от нативных функций PHP, они обладают более понятным API, лучшей поддержкой Unicode и, прежде всего, обнаружением ошибок. Любая ошибка при компиляции или обработке выражения вызовет исключение `Nette\RegexpException`. split(string $subject, string $pattern, bool $captureOffset=false, bool $skipEmpty=false, int $limit=-1, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------- -Разделяет строку на массив в соответствии с регулярным выражением. Выражения в круглых скобках также будут перехвачены и возвращены. +Разделяет строку на массив по регулярному выражению. Выражения в скобках будут захвачены и также возвращены. ```php Strings::split('hello, world', '~,\s*~'); @@ -407,7 +407,7 @@ Strings::split('hello, world', '~(,)\s*~'); // ['hello', ',', 'world']`` ``` -Если `$skipEmpty` равен `true`, то будут возвращены только непустые записи: +Если `$skipEmpty` равно `true`, будут возвращены только непустые элементы: ```php Strings::split('hello, world, ', '~,\s*~'); @@ -417,30 +417,30 @@ Strings::split('hello, world, ', '~,\s*~', skipEmpty: true); // ['hello', 'world'] ``` -Если `$limit`, то будут возвращены только подстроки до предела, а остальная часть строки будет помещена в последний элемент. Предел -1 или 0 означает отсутствие предела. +Если указан `$limit`, будут возвращены только подстроки до лимита, а остаток строки будет помещен в последний элемент. Лимит -1 или 0 означает отсутствие ограничений. ```php Strings::split('hello, world, third', '~,\s*~', limit: 2); // ['hello', 'world, third'] ``` -Если `$utf8` равно `true`, то оценка переключится в режим Unicode. Это аналогично указанию модификатора `u`. +Если `$utf8` равно `true`, оценка переключается в режим Unicode. Аналогично указанию модификатора `u`. -Если `$captureOffset` равен `true`, то для каждого встречающегося совпадения будет возвращена его позиция в строке (в байтах; в символах, если установлен `$utf8` ). Это изменяет возвращаемое значение на массив, каждый элемент которого представляет собой пару, состоящую из совпадающей строки и ее позиции. +Если `$captureOffset` равно `true`, для каждого найденного совпадения будет также возвращена его позиция в строке (в байтах; если установлено `$utf8`, то в символах). Это изменяет возвращаемое значение на массив, где каждый элемент является парой, состоящей из совпавшей строки и ее позиции. ```php -Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true); -// [['žlutý', 0], ['kůň', 9]] +Strings::split('желтый, конь', '~,\s*~', captureOffset: true); +// [['желтый', 0], ['конь', 11]] -Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true, utf8: true); -// [['žlutý', 0], ['kůň', 7]] +Strings::split('желтый, конь', '~,\s*~', captureOffset: true, utf8: true); +// [['желтый', 0], ['конь', 7]] ``` match(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $utf8=false): ?array .[method] -------------------------------------------------------------------------------------------------------------------------------------------------- -Ищет в строке часть, соответствующую регулярному выражению, и возвращает массив с найденным выражением и каждым подвыражением, или `null`. +Ищет в строке часть, соответствующую регулярному выражению, и возвращает массив с найденным выражением и отдельными подвыражениями, или `null`. ```php Strings::match('hello!', '~\w+(!+)~'); @@ -450,7 +450,7 @@ Strings::match('hello!', '~X~'); // null ``` -Если `$unmatchedAsNull` равен `true`, то не захваченные подвыражения возвращаются как нулевые; в противном случае они возвращаются как пустая строка или не возвращаются: +Если `$unmatchedAsNull` равно `true`, незахваченные подшаблоны возвращаются как null; в противном случае они возвращаются как пустая строка или не возвращаются: ```php Strings::match('hello', '~\w+(!+)?~'); @@ -460,7 +460,7 @@ Strings::match('hello', '~\w+(!+)?~', unmatchedAsNull: true); // ['hello', null] ``` -Если `$utf8` равно `true`, то оценка переключается в режим Unicode. Аналогично указанию модификатора `u`: +Если `$utf8` равно `true`, оценка переключается в режим Unicode. Аналогично указанию модификатора `u`: ```php Strings::match('žlutý kůň', '~\w+~'); @@ -470,9 +470,9 @@ Strings::match('žlutý kůň', '~\w+~', utf8: true); // ['žlutý'] ``` -Параметр `$offset` может использоваться для указания позиции, с которой следует начинать поиск (в байтах; в символах, если установлен `$utf8` ). +Параметр `$offset` можно использовать для указания позиции, с которой следует начать поиск (в байтах; если установлено `$utf8`, то в символах). -Если `$captureOffset` равен `true`, то для каждого встречающегося совпадения будет также возвращена его позиция в строке (в байтах; если установлен `$utf8`, то в символах). Это превращает возвращаемое значение в массив, каждый элемент которого представляет собой пару, состоящую из совпадающей строки и ее смещения: +Если `$captureOffset` равно `true`, для каждого найденного совпадения будет также возвращена его позиция в строке (в байтах; если установлено `$utf8`, то в символах). Это изменяет возвращаемое значение на массив, где каждый элемент является парой, состоящей из совпавшей строки и ее смещения: ```php Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true); @@ -483,10 +483,10 @@ Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true, utf8: true); ``` -matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false, bool $lazy=false): array|Generator .[method] +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Ищет в строке все вхождения, соответствующие регулярному выражению, и возвращает массив массивов, содержащих найденное выражение и каждое подвыражение. +Ищет в строке все вхождения, соответствующие регулярному выражению, и возвращает массив массивов с найденным выражением и отдельными подвыражениями. ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~'); @@ -496,7 +496,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~'); ] */ ``` -Если `$patternOrder` равен `true`, то структура результатов изменяется таким образом, что первая запись представляет собой массив полных совпадений шаблона, вторая - массив строк, совпадающих с первым подшаблоном в круглых скобках, и так далее: +Если `$patternOrder` равно `true`, структура результатов изменяется так, что в первом элементе находится массив полных совпадений шаблона, во втором — массив строк, которым соответствует первый подшаблон в скобках, и так далее: ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~', patternOrder: true); @@ -506,7 +506,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~', patternOrder: true); ] */ ``` -Если `$unmatchedAsNull` равен `true`, то несовпадающие подшаблоны возвращаются как нулевые; в противном случае они возвращаются как пустые строки или не возвращаются: +Если `$unmatchedAsNull` равно `true`, незахваченные подшаблоны возвращаются как null; в противном случае они возвращаются как пустая строка или не возвращаются: ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~', unmatchedAsNull: true); @@ -516,7 +516,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~', unmatchedAsNull: true); ] */ ``` -Если `$utf8` равно `true`, то оценка переключается в режим Unicode. Аналогично указанию модификатора `u`: +Если `$utf8` равно `true`, оценка переключается в режим Unicode. Аналогично указанию модификатора `u`: ```php Strings::matchAll('žlutý kůň', '~\w+~'); @@ -532,9 +532,9 @@ Strings::matchAll('žlutý kůň', '~\w+~', utf8: true); ] */ ``` -Параметр `$offset` может использоваться для указания позиции, с которой следует начинать поиск (в байтах; в символах, если установлен `$utf8` ). +Параметр `$offset` можно использовать для указания позиции, с которой следует начать поиск (в байтах; если установлено `$utf8`, то в символах). -Если `$captureOffset` равен `true`, то для каждого встречающегося совпадения будет также возвращена его позиция в строке (в байтах; если установлен `$utf8`, то в символах). Это изменяет возвращаемое значение на массив, каждый элемент которого представляет собой пару, состоящую из совпадающей строки и ее позиции: +Если `$captureOffset` равно `true`, для каждого найденного совпадения будет также возвращена его позиция в строке (в байтах; если установлено `$utf8`, то в символах). Это изменяет возвращаемое значение на массив, где каждый элемент является парой, состоящей из совпавшей строки и ее позиции: ```php Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true); @@ -550,11 +550,21 @@ Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true, utf8: true); ] */ ``` +Если `$lazy` равно `true`, функция возвращает `Generator` вместо массива, что дает значительные преимущества в производительности при работе с большими строками. Генератор позволяет искать совпадения постепенно, а не во всей строке сразу. Это позволяет эффективно работать даже с чрезвычайно большими входными текстами. Кроме того, вы можете в любой момент прервать обработку, если найдете искомое совпадение, что экономит вычислительное время. + +```php +$matches = Strings::matchAll($largeText, '~\w+~', lazy: true); +foreach ($matches as $match) { + echo "Найдено: $match[0]\n"; + // Обработка может быть прервана в любой момент +} +``` + replace(string $subject, string|array $pattern, string|callable $replacement='', int $limit=-1, bool $captureOffset=false, bool $unmatchedAsNull=false, bool $utf8=false): string .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Заменяет все вхождения, соответствующие регулярному выражению. `$replacement` - это либо маска строки замены, либо обратный вызов. +Заменяет все вхождения, соответствующие регулярному выражению. `$replacement` — это либо маска заменяющей строки, либо callback. ```php Strings::replace('hello, world!', '~\w+~', '--'); @@ -564,7 +574,7 @@ Strings::replace('hello, world!', '~\w+~', fn($m) => strrev($m[0])); // 'olleh, dlrow!' ``` -Функция также допускает множественную замену, передавая во втором параметре массив вида `pattern => replacement`: +Функция также позволяет выполнить несколько замен, передав во втором параметре массив в виде `pattern => replacement`: ```php Strings::replace('hello, world!', [ @@ -574,9 +584,9 @@ Strings::replace('hello, world!', [ // '-- --!' ``` -Параметр `$limit` ограничивает количество замен, которые могут быть сделаны. Предел -1 означает отсутствие предела. +Параметр `$limit` ограничивает количество выполненных замен. Лимит -1 означает отсутствие ограничений. -Если `$utf8` равно `true`, то оценка переключается в режим Unicode. Это аналогично указанию модификатора `u`. +Если `$utf8` равно `true`, оценка переключается в режим Unicode. Аналогично указанию модификатора `u`. ```php Strings::replace('žlutý kůň', '~\w+~', '--'); @@ -586,7 +596,7 @@ Strings::replace('žlutý kůň', '~\w+~', '--', utf8: true); // '-- --' ``` -Если `$captureOffset` равен `true`, то для каждого встречающегося совпадения его позиция в строке (в байтах; в символах, если установлен `$utf8` ) также будет передана обратному вызову. Это изменяет форму передаваемого массива, где каждый элемент представляет собой пару, состоящую из совпадающей строки и ее позиции. +Если `$captureOffset` равно `true`, для каждого найденного совпадения в callback будет также передана его позиция в строке (в байтах; если установлено `$utf8`, то в символах). Это изменяет вид передаваемого массива, где каждый элемент является парой, состоящей из совпавшей строки и ее позиции. ```php Strings::replace( @@ -595,7 +605,7 @@ Strings::replace( function (array $m) { dump($m); return ''; }, captureOffset: true, ); -// dumps [['lut', 2]] a [['k', 8]] +// dumps [['lut', 2]] и [['k', 8]] Strings::replace( 'žlutý kůň', @@ -604,10 +614,10 @@ Strings::replace( captureOffset: true, utf8: true, ); -// dumps [['žlutý', 0]] a [['kůň', 6]] +// dumps [['žlutý', 0]] и [['kůň', 6]] ``` -Если `$unmatchedAsNull` - `true`, то несовпадающие подшаблоны передаются в обратный вызов как null; в противном случае они передаются как пустая строка или не передаются: +Если `$unmatchedAsNull` равно `true`, незахваченные подшаблоны передаются в callback как null; в противном случае они передаются как пустая строка или не передаются: ```php Strings::replace( diff --git a/utils/ru/type.texy b/utils/ru/type.texy index 87ee31d2ae..558477ae7f 100644 --- a/utils/ru/type.texy +++ b/utils/ru/type.texy @@ -1,8 +1,8 @@ -Типы PHP -******** +Тип PHP +******* .[perex] -[api:Nette\Utils\Type] это класс для работы с типами данных PHP. +[api:Nette\Utils\Type] — это класс для работы с типами данных PHP. Установка: @@ -11,7 +11,7 @@ composer require nette/utils ``` -Во всех примерах предполагается, что псевдоним уже создан: +Все примеры предполагают созданный псевдоним: ```php use Nette\Utils\Type; @@ -21,7 +21,7 @@ use Nette\Utils\Type; fromReflection($reflection): ?Type .[method] -------------------------------------------- -Статический метод создает объект Type на основе отражения. Параметр может быть объектом `ReflectionMethod` или `ReflectionFunction` (возвращает тип возвращаемого значения) или `ReflectionParameter` или `ReflectionProperty`. Он переводит `self`, `static` и `parent` в реальное имя класса. Если объект не имеет типа, возвращается `null`. +Статический метод создает объект Type на основе рефлексии. Параметром может быть объект `ReflectionMethod` или `ReflectionFunction` (возвращает тип возвращаемого значения) или `ReflectionParameter` или `ReflectionProperty`. Переводит `self`, `static` и `parent` в фактическое имя класса. Если субъект не имеет типа, возвращает `null`. ```php class DemoClass @@ -37,7 +37,7 @@ echo Type::fromReflection($prop); // 'DemoClass' fromString(string $type): Type .[method] ---------------------------------------- -Статический метод создает объект Type в соответствии с текстовой нотацией. +Статический метод создает объект Type по текстовой записи. ```php $type = Type::fromString('Foo|Bar'); @@ -48,10 +48,10 @@ echo $type; // 'Foo|Bar' getNames(): (string|array)[] .[method] -------------------------------------- -Возвращает массив подтипов, составляющих составной тип, в виде строк. +Возвращает массив подтипов, из которых состоит составной тип, в виде строк. ```php -$type = Type::fromString('string|null'); // nebo '?string' +$type = Type::fromString('string|null'); // или '?string' $type->getNames(); // ['string', 'null'] $type = Type::fromString('(Foo&Bar)|string'); @@ -62,10 +62,10 @@ $type->getNames(); // [['Foo', 'Bar'], 'string'] getTypes(): Type[] .[method] ---------------------------- -Возвращает массив подтипов, составляющих составной тип, в виде объектов `ReflectionType`: +Возвращает массив подтипов, из которых состоит составной тип, в виде объектов `ReflectionType`: ```php -$type = Type::fromString('string|null'); // or '?string' +$type = Type::fromString('string|null'); // или '?string' $type->getTypes(); // [Type::fromString('string'), Type::fromString('null')] $type = Type::fromString('(Foo&Bar)|string'); @@ -79,7 +79,7 @@ $type->getTypes(); // [Type::fromString('Foo'), Type::fromString('Bar')] getSingleName(): ?string .[method] ---------------------------------- -Возвращает имя типа для простых типов, иначе null. +Для простых типов возвращает имя типа, иначе null. ```php $type = Type::fromString('string|null'); @@ -99,14 +99,14 @@ echo $type->getSingleName(); // null isSimple(): bool .[method] -------------------------- -Возвращает, является ли данный тип простым типом. Простые типы также считаются простыми nullable типами: +Возвращает, является ли тип простым. Простыми типами также считаются простые nullable типы: ```php $type = Type::fromString('string'); $type->isSimple(); // true $type->isUnion(); // false -$type = Type::fromString('?Foo'); // nebo 'Foo|null' +$type = Type::fromString('?Foo'); // или 'Foo|null' $type->isSimple(); // true $type->isUnion(); // true ``` @@ -115,7 +115,7 @@ $type->isUnion(); // true isUnion(): bool .[method] ------------------------- -Возвращает, существует ли тип объединения. +Возвращает, является ли тип union типом. ```php $type = Type::fromString('string|int'); @@ -126,7 +126,7 @@ $type->isUnion(); // true isIntersection(): bool .[method] -------------------------------- -Возвращает, является ли o типом пересечения. +Возвращает, является ли тип intersection типом. ```php @@ -138,7 +138,7 @@ $type->isIntersection(); // true isBuiltin(): bool .[method] --------------------------- -Возвращает, является ли тип одновременно простым и встроенным типом PHP. +Возвращает, является ли тип простым и одновременно встроенным типом PHP. ```php $type = Type::fromString('string'); @@ -155,7 +155,7 @@ $type->isBuiltin(); // false isClass(): bool .[method] ------------------------- -Возвращает, является ли тип одновременно простым и именем класса. +Возвращает, является ли тип простым и одновременно именем класса. ```php $type = Type::fromString('string'); @@ -186,7 +186,7 @@ $type->isClassKeyword(); // false allows(string $type): bool .[method] ------------------------------------ -Метод `allows()` проверяет совместимость типов. Например, он позволяет проверить, может ли значение определенного типа быть передано в качестве параметра. +Метод `allows()` проверяет совместимость типов. Например, позволяет определить, может ли значение определенного типа быть передано в качестве параметра. ```php $type = Type::fromString('string|null'); diff --git a/utils/ru/validators.texy b/utils/ru/validators.texy index 09e97bebeb..e29877549e 100644 --- a/utils/ru/validators.texy +++ b/utils/ru/validators.texy @@ -2,7 +2,7 @@ ******************* .[perex] -Нужно быстро и легко проверить, что переменная содержит, например, действительный адрес электронной почты? Вот тут-то и пригодится [api:Nette\Utils\Validators], статический класс с полезными функциями для проверки значений. +Нужно быстро и просто проверить, что в переменной, например, действительный адрес электронной почты? Вам пригодится [api:Nette\Utils\Validators], статический класс с полезными функциями для валидации значений. Установка: @@ -11,17 +11,17 @@ composer require nette/utils ``` -Во всех примерах предполагается, что псевдоним уже создан: +Все примеры предполагают созданный псевдоним: ```php use Nette\Utils\Validators; ``` -Основное использование .[#toc-basic-usage] -========================================== +Основное использование +====================== -Класс имеет ряд методов для проверки значений, таких как [isList() |#isList], [isUnicode() |#isUnicode], [isEmail() |#isEmail], [isUrl() |#isUrl] и т.д. для использования в вашем коде: +Класс располагает рядом методов для проверки значений, таких как [#isUnicode()], [#isEmail()], [#isUrl()] и т.д., для использования в вашем коде: ```php if (!Validators::isEmail($email)) { @@ -29,7 +29,7 @@ if (!Validators::isEmail($email)) { } ``` -Он также может проверить, является ли значение [ожидаемым типом |#Expected-Types], который представляет собой строку, где опции разделены косой чертой `|`. Таким образом, мы можем легко проверить несколько типов с помощью [if() |#if()]: +Кроме того, он умеет проверять, является ли значение так называемым [ожидаемым типом |#Ожидаемые типы], что представляет собой строку, где отдельные варианты разделяются вертикальной чертой `|`. Таким образом, мы можем легко проверить несколько типов с помощью [#is()]: ```php if (!Validators::is($val, 'int|string|bool')) { @@ -37,81 +37,81 @@ if (!Validators::is($val, 'int|string|bool')) { } ``` -Но это также дает нам возможность создать систему, в которой нам нужно записывать ожидания в виде строк (например, в аннотациях или конфигурации), а затем проверять значения по ним. +Но это также дает нам возможность создать систему, где ожидания нужно записывать в виде строк (например, в аннотациях или конфигурации), а затем проверять значения по ним. -Мы также можем поставить запрос [assert() |#assert] на ожидаемые типы, который в случае невыполнения выбрасывает исключение. +К ожидаемым типам можно также применить требование [##assert()], которое, если не выполнено, выбрасывает исключение. -Ожидаемые типы .[#toc-expected-types] -===================================== +Ожидаемые типы +============== -Ожидаемые типы образуют строку, состоящую из одного или нескольких вариантов, разделенных вертикальной полосой `|`, podobně jako se zapisují typy v PHP (např. `'int|string|bool')`. Также принимается нулевая нотация `?int`. +Ожидаемые типы представляют собой строку, состоящую из одного или нескольких вариантов, разделенных вертикальной чертой `|`, подобно тому, как типы записываются в PHP (например, `'int|string|bool'`). Также принимается nullable запись `?int`. -Массив, в котором все элементы имеют определенный тип, записывается как `int[]`. +Массив, где все элементы определенного типа, записывается в виде `int[]`. -За некоторыми типами может следовать двоеточие и длина `:length` или диапазон. `:[min]..[max]`например, `string:10` (строка из 10 байт), `float:10..` (число 10 или более), `array:..10` (массив до десяти элементов) или `list:10..20` (список от 10 до 20 элементов), или регулярное выражение u `pattern:[0-9]+`. +За некоторыми типами может следовать двоеточие и длина `:length` или диапазон `:[min]..[max]`, например, `string:10` (строка длиной 10 байт), `float:10..` (число 10 и больше), `array:..10` (массив до десяти элементов) или `list:10..20` (список с 10 до 20 элементами), или регулярное выражение у `pattern:[0-9]+`. Обзор типов и правил: .[wide] -| PHP types || +| Типы PHP || |-------------------------- -| `array` .{width: 140px} | Для количества элементов может быть задан диапазон. -| `bool` | -| `float` | Для значения может быть указан диапазон. -| `int` | может быть указан диапазон значений. -| `null` | -| `object` | +| `array` .{width: 140px} | можно указать диапазон для количества элементов +| `bool` | +| `float` | можно указать диапазон для значения +| `int` | можно указать диапазон для значения +| `null` | +| `object` | | `resource` | -| `scalar` | int\|float\|bool\|string -| `string` | Для длины в байтах может быть указан диапазон. +| `scalar` | int\|float\|bool\|string +| `string` | можно указать диапазон для длины в байтах | `callable` | | `iterable` | -| `mixed` | +| `mixed` | |-------------------------- | псевдо-типы || |------------------------------------------------ -| `list` | [индексированный массив |#isList], для количества элементов может быть задан диапазон -| `none` | пустое значение: `''`, `null`, `false` -| `number` | int\|float -| `numeric` | [число, включая текстовое представление |#isNumeric] +| `list` | индексированный массив, можно указать диапазон для количества элементов +| `none` | пустое значение: `''`, `null`, `false` +| `number` | int\|float +| `numeric` | [число, включая текстовое представление |#isNumeric] | `numericint`| [целое число, включая текстовое представление |#isNumericInt] -| `unicode` | [UTF-8 строка |#isUnicode], может быть указан диапазон длины в символах. +| `unicode` | [строка UTF-8 |#isUnicode], можно указать диапазон для длины в символах |-------------------------- -| класс символов (не должен быть пустой строкой)|| +| символьный класс (не должна быть пустой строкой) || |------------------------------------------------ -| `alnum` | все символы буквенно-цифровые -| `alpha` | все символы - буквы `[A-Za-z]` -| `digit` | все символы являются цифрами -| `lower` | все символы в нижнем регистре `[a-z]` -| `space` | все символы - пробелы -| `upper` | все символы в верхнем регистре `[A-Z]` -| `xdigit` | все символы являются шестнадцатеричными цифрами `[0-9A-Fa-f]` +| `alnum` | все символы являются буквенно-цифровыми +| `alpha` | все символы являются буквами `[A-Za-z]` +| `digit` | все символы являются цифрами +| `lower` | все символы являются строчными буквами `[a-z]` +| `space` | все символы являются пробелами +| `upper` | все символы являются прописными буквами `[A-Z]` +| `xdigit` | все символы являются шестнадцатеричными цифрами `[0-9A-Fa-f]` |-------------------------- | проверка синтаксиса || |------------------------------------------------ -| `pattern` | регулярное выражение, которое должно соответствовать **всей** строке -| `email` | [E-mail |#isEmail] -| `identifier`| [PHP-идентификатор |#isPhpIdentifier] -| `url` | [URL |#isUrl] -| `uri` | [URI |#isUri] +| `pattern` | регулярное выражение, которому должна соответствовать **вся** строка +| `email` | [E-mail |#isEmail] +| `identifier`| [идентификатор PHP |#isPhpIdentifier] +| `url` | [URL |#isUrl] +| `uri` | [URI |#isUri] |-------------------------- -| аутентификация среды || +| проверка среды || |------------------------------------------------ -| `class` | это существующий класс -| `interface` | это существующий интерфейс -| `directory` | это существующий каталог -| `file` | это существующий файл +| `class` | существующий класс +| `interface` | существующий интерфейс +| `directory` | существующий каталог +| `file` | существующий файл -Утверждение .[#toc-assertion] -============================= +Утверждения +=========== assert($value, string $expected, string $label='variable'): void .[method] -------------------------------------------------------------------------- -Проверяет, что значение является одним из [ожидаемых типов |#Expected-Types], разделенных звездочкой. Если нет, то выбрасывается исключение [api:Nette\Utils\AssertionException]. Слово `variable` в тексте исключения может быть заменено другим параметром `$label`. +Проверяет, что значение является одним из [ожидаемых типов |#Ожидаемые типы], разделенных вертикальной чертой. Если нет, выбрасывает исключение [api:Nette\Utils\AssertionException]. Слово `variable` в тексте исключения можно заменить другим с помощью параметра `$label`. ```php Validators::assert('Nette', 'string:5'); // OK @@ -120,10 +120,10 @@ Validators::assert('Lorem ipsum dolor sit', 'string:78'); ``` -assertField(array $array, string|int $key, string $expected=null, string $label=null): void .[method] ------------------------------------------------------------------------------------------------------ +assertField(array $array, string|int $key, ?string $expected=null, ?string $label=null): void .[method] +------------------------------------------------------------------------------------------------------- -Проверяет, что элемент под ключом `$key` в поле `$array` является одним из [ожидаемых типов |#Expected-Types], разделенных звездочкой. Если нет, то выбрасывается исключение [api:Nette\Utils\AssertionException]. Строка `item '%' in array` в тексте исключения может быть заменена другим параметром `$label`. +Проверяет, является ли элемент под ключом `$key` в массиве `$array` одним из [ожидаемых типов |#Ожидаемые типы], разделенных вертикальной чертой. Если нет, выбрасывает исключение [api:Nette\Utils\AssertionException]. Строку `item '%' in array` в тексте исключения можно заменить другой с помощью параметра `$label`. ```php $arr = ['foo' => 'Nette']; @@ -136,19 +136,19 @@ Validators::assertField($arr, 'foo', 'int'); ``` -Валидаторы .[#toc-validators] -============================= +Валидаторы +========== is($value, string $expected): bool .[method] -------------------------------------------- -Проверяет, что значение является одним из [ожидаемых типов |#Expected-Types], разделенных звездочкой. +Проверяет, является ли значение одним из [ожидаемых типов |#Ожидаемые типы], разделенных вертикальной чертой. ```php Validators::is(1, 'int|float'); // true Validators::is(23, 'int:0..10'); // false -Validators::is('Nette Framework', 'string:15'); // true, délka je 15 bytů +Validators::is('Nette Framework', 'string:15'); // true, длина 15 байт Validators::is('Nette Framework', 'string:8..'); // true Validators::is('Nette Framework', 'string:30..40'); // false ``` @@ -157,7 +157,7 @@ Validators::is('Nette Framework', 'string:30..40'); // false isEmail(mixed $value): bool .[method] ------------------------------------- -Проверяет, является ли значение действительным адресом электронной почты. Он не проверяет, существует ли домен на самом деле, проверяется только синтаксис. Функция также учитывает будущие [ДВУ |https://cs.wikipedia.org/wiki/Doména_nejvyššího_řádu], которые могут быть в юникоде. +Проверяет, является ли значение действительным адресом электронной почты. Не проверяется, существует ли домен на самом деле, проверяется только синтаксис. Функция учитывает и будущие [TLD |https://cs.wikipedia.org/wiki/Doména_nejvyššího_řádu], которые могут быть и в unicode. ```php Validators::isEmail('example@nette.org'); // true @@ -169,9 +169,9 @@ Validators::isEmail('nette'); // false isInRange(mixed $value, array $range): bool .[method] ----------------------------------------------------- -Проверяет, находится ли значение в заданном диапазоне `[min, max]`где верхняя или нижняя граница может быть опущена (`null`). Сравнивать можно числа, строки и объекты DateTime. +Проверяет, находится ли значение в заданном диапазоне `[min, max]`, где верхнюю или нижнюю границу можно опустить (`null`). Можно сравнивать числа, строки и объекты DateTime. -Если обе границы отсутствуют (`[null, null]`) или значение `null`, возвращается `false`. +Если отсутствуют обе границы (`[null, null]`) или значение равно `null`, возвращает `false`. ```php Validators::isInRange(5, [0, 5]); // true @@ -184,7 +184,7 @@ Validators::isInRange(1, [5]); // false isNone(mixed $value): bool .[method] ------------------------------------ -Проверяет, что значение равно `0`, `''`, `false` или `null`. +Проверяет, является ли значение `0`, `''`, `false` или `null`. ```php Validators::isNone(0); // true @@ -251,7 +251,7 @@ Validators::isBuiltinType('Foo'); // false isTypeDeclaration(string $type): bool .[method] ----------------------------------------------- -Проверяет, является ли данное объявление типа синтаксически допустимым. +Проверяет, является ли данное объявление типа синтаксически верным. ```php Validators::isTypeDeclaration('?string'); // true @@ -279,7 +279,7 @@ Validators::isClassKeyword('Foo'); // false isUnicode(mixed $value): bool .[method] --------------------------------------- -Проверяет, что значение является допустимой строкой UTF-8. +Проверяет, является ли значение действительной строкой UTF-8. ```php Validators::isUnicode('nette'); // true @@ -291,7 +291,7 @@ Validators::isUnicode("\xA0"); // false isUrl(mixed $value): bool .[method] ----------------------------------- -Проверяет, является ли значение действительным URL. +Проверяет, является ли значение действительным URL-адресом. ```php Validators::isUrl('https://nette.org:8080/path?query#fragment'); // true @@ -306,7 +306,7 @@ Validators::isUrl('nette.org'); // false isUri(string $value): bool .[method] ------------------------------------ -Проверяет, является ли значение действительным адресом URI, который фактически представляет собой строку, начинающуюся с синтаксически допустимой схемы. +Проверяет, является ли значение действительным URI-адресом, то есть строкой, начинающейся с синтаксически правильной схемы. ```php Validators::isUri('https://nette.org'); // true diff --git a/utils/sl/@home.texy b/utils/sl/@home.texy index 131197c536..6e63178317 100644 --- a/utils/sl/@home.texy +++ b/utils/sl/@home.texy @@ -1,32 +1,33 @@ -Storitve -******** +Nette Utils +*********** .[perex] -V paketu `nette/utils` boste našli niz uporabnih razredov za vsakdanjo uporabo: +V paketu `nette/utils` najdete nabor uporabnih razredov za vsakodnevno uporabo: -| [Matrike |Arrays] | Nette\Utils\Arrays | [Callback |Callback] | Nette\Utils\Callback | [Datum in čas |datetime] | Nette\Utils\DateTime -| [Datotečni sistem |filesystem] | Nette\Utils\FileSystem -| [Iskalnik |Finder] | Nette\Utils\Finder -| [Floats |Floats] | Nette\Utils\Floats -| [Pomočniki |helpers] | Nette\Utils\Helpers -| [Elementi HTML |HTML Elements] | Nette\Utils\Html -| [Slike |Images] | Nette\Utils\Image -| [JSON |JSON] | Nette\Utils\Json -| [Objektni model |smartobject] | Nette\SmartObject & Nette\StaticClass -| [Paginator |paginator] | Nette\Utils\Paginator -| [PHP Reflection |reflection] | Nette\Utils\Reflection -| [Tipi PHP |type] | Nette\Utils\Type +| [Finder |Finder] | Nette\Utils\Finder +| [HTML elementi |html-elements] | Nette\Utils\Html +| [Iteratorji |iterables] | Nette\Utils\Iterables +| [JSON |json] | Nette\Utils\Json | [Naključni nizi |random] | Nette\Utils\Random - [Nizi |Strings] | Nette\Utils\Nizi | Nette\Utils\Nizi -| [Validatorji |validators] | Nette\Utils\Validators +| [Slike |images] | Nette\Utils\Image +| [PHP refleksija |reflection] | Nette\Utils\Reflection +| [PHP tipi |type] | Nette\Utils\Type +| [Polja |arrays] | Nette\Utils\Arrays +| [Pomožne funkcije |helpers] | Nette\Utils\Helpers +| [Primerjava floatov |floats] | Nette\Utils\Floats +| [Nizi |strings] | Nette\Utils\Strings +| [Datotečni sistem |filesystem] | Nette\Utils\FileSystem +| [Oštevilčevanje strani |paginator] | Nette\Utils\Paginator +| [SmartObject |SmartObject] & [StaticClass |StaticClass] | Nette\SmartObject & Nette\StaticClass +| [Validator |validators] | Nette\Utils\Validators Namestitev ---------- -Prenesite in namestite paket s [programom Composer |best-practices:composer]: +Knjižnico prenesete in namestite z orodjem [Composer|best-practices:composer]: ```shell composer require nette/utils @@ -34,9 +35,12 @@ composer require nette/utils | različica | združljivo s PHP |-----------|------------------- -| Nette Utils 4.0 | PHP 8.0 - 8.2 -| Nette Utils 3.2 | PHP 7.2 - 8.2 -| Nette Utils 3.0 - 3.1 | PHP 7.1 - 8.0 -| Nette Utils 2.5 | PHP 5.6 - 8.0 +| Nette Utils 4.0 | PHP 8.0 – 8.4 +| Nette Utils 3.2 | PHP 7.2 – 8.3 +| Nette Utils 3.0 – 3.1 | PHP 7.1 – 8.0 +| Nette Utils 2.5 | PHP 5.6 – 8.0 + +Velja za zadnjo patch različico. + -Velja za najnovejše različice popravkov. +Če posodabljate paket na novejšo različico, si oglejte stran [nadgradnja|en:upgrading]. diff --git a/utils/sl/@left-menu.texy b/utils/sl/@left-menu.texy index 0e2cbfd550..a3716336c3 100644 --- a/utils/sl/@left-menu.texy +++ b/utils/sl/@left-menu.texy @@ -1,26 +1,28 @@ -Paket nette/utils -***************** -- [Matrike |Arrays] -- [Povratni klic |Callback] +Nette Utils +*********** +- [Callbacki |callback] - [Datum in čas |datetime] -- [Datotečni sistem |filesystem] -- [Iskalnik |Finder] -- [Pomočniki |helpers] -- [Elementi HTML |HTML Elements] -- [Slike |Images] +- [Finder |Finder] +- [Floats |Floats] +- [HTML elementi |html-elements] +- [Iteratorji |iterables] - [JSON |JSON] -- [Paginator |paginator] - [Naključni nizi |random] +- [Slike |images] +- [Paginator |paginator] +- [PHP Refleksija |reflection] +- [PHP tipi |type] +- [Polja |arrays] +- [Pomožne funkcije |helpers] +- [Nizi |strings] - [SmartObject |SmartObject] -- [Odsev PHP |reflection] -- [Nizi |Strings] -- [Plavaji |Floats] -- [Tipi PHP |type] -- [Validatorji |validators] +- [StaticClass |StaticClass] +- [Datotečni sistem |filesystem] +- [Validator |validators] -Drugi pripomočki -**************** -- [NEON |neon:] -- [Hashing gesel |security:passwords] -- [Razhroščevalnik in graditelj URL |http:urls] +Druga orodja +************ +- [NEON|neon:] +- [Zgoščevanje gesel |security:passwords] +- [Razčlenjevanje in sestavljanje URL |http:urls] diff --git a/utils/sl/@meta.texy b/utils/sl/@meta.texy new file mode 100644 index 0000000000..724324bee5 --- /dev/null +++ b/utils/sl/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokumentacija}} diff --git a/utils/sl/arrays.texy b/utils/sl/arrays.texy index ba1f546b28..9c82a7842c 100644 --- a/utils/sl/arrays.texy +++ b/utils/sl/arrays.texy @@ -1,8 +1,8 @@ -Funkcije polja -************** +Delo s polji +************ .[perex] -Ta stran govori o razredih [Nette\Utils\Arrays |#Arrays], [ArrayHash |#ArrayHash] in [ArrayList |#ArrayList], ki so povezani z polji. +Ta stran je posvečena razredom [Nette\Utils\Arrays |#Arrays], [#ArrayHash] in [#ArrayList], ki se nanašajo na polja. Namestitev: @@ -12,22 +12,63 @@ composer require nette/utils ``` -Mreže .[#toc-arrays] -==================== +Arrays +====== -[api:Nette\Utils\Arrays] je statični razred, ki vsebuje nekaj priročnih funkcij za polja. +[api:Nette\Utils\Arrays] je statični razred, ki vsebuje uporabne funkcije za delo s polji. Njegov ekvivalent za iteratorje je [Nette\Utils\Iterables |iterables]. -Naslednji primeri predpostavljajo, da je definiran naslednji vzdevek razreda: +Naslednji primeri predpostavljajo ustvarjen alias: ```php use Nette\Utils\Arrays; ``` +associate(array $array, mixed $path): array|\stdClass .[method] +--------------------------------------------------------------- + +Funkcija fleksibilno pretvori polje `$array` v asociativno polje ali objekte glede na podano pot `$path`. Pot je lahko niz ali polje. Sestavljajo jo imena ključev vhodnega polja in operatorji, kot so '[]', '->', '=', in '|'. V primeru neveljavne poti sproži `Nette\InvalidArgumentException`. + +```php +// pretvorba v asociativno polje po enostavnem ključu +$arr = [ + ['name' => 'John', 'age' => 11], + ['name' => 'Mary', 'age' => null], + // ... +]; +$result = Arrays::associate($arr, 'name'); +// $result = ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// dodelitev vrednosti iz enega ključa drugemu z uporabo operatorja = +$result = Arrays::associate($arr, 'name=age'); // ali ['name', '=', 'age'] +// $result = ['John' => 11, 'Mary' => null, ...] +``` + +```php +// ustvarjanje objekta z uporabo operatorja -> +$result = Arrays::associate($arr, '->name'); // ali ['->', 'name'] +// $result = (object) ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// kombinacija ključev z uporabo operatorja | +$result = Arrays::associate($arr, 'name|age'); // ali ['name', '|', 'age'] +// $result: ['John' => ['name' => 'John', 'age' => 11], 'Paul' => ['name' => 'Paul', 'age' => 44]] +``` + +```php +// dodajanje v polje z uporabo [] +$result = Arrays::associate($arr, 'name[]'); // ali ['name', '[]'] +// $result: ['John' => [['name' => 'John', 'age' => 22], ['name' => 'John', 'age' => 11]]] +``` + + contains(array $array, $value): bool .[method] ---------------------------------------------- -Preizkusi polje glede prisotnosti vrednosti. Uporablja strogo primerjavo (`===`) +Preveri polje za prisotnost vrednosti. Uporablja strogo primerjavo (`===`). ```php Arrays::contains([1, 2, 3], 1); // true @@ -35,10 +76,10 @@ Arrays::contains(['1', false], 1); // false ``` -every(iterable $array, callable $callback): bool .[method] ----------------------------------------------------------- +every(array $array, callable $predicate): bool .[method] +-------------------------------------------------------- -Preizkusi, ali vsi elementi v polju prestanejo test, ki ga izvaja navedena funkcija s podpisom `function ($value, $key, array $array): bool`. +Preveri, ali vsi elementi v polju prestanejo test, implementiran v `$predicate` s signaturo `function ($value, $key, array $array): bool`. ```php $array = [1, 30, 39, 29, 10, 13]; @@ -46,24 +87,59 @@ $isBelowThreshold = fn($value) => $value < 40; $res = Arrays::every($array, $isBelowThreshold); // true ``` -Glejte [some( |#some()]). +Glejte [#some()]. -first(array $array): mixed .[method] ------------------------------------- +filter(array $array, callable $predicate): array .[method]{data-version:4.0.4} +------------------------------------------------------------------------------ + +Vrne novo polje, ki vsebuje vse pare ključ-vrednost, ki ustrezajo podanemu predikatu. Povratni klic ima signaturo `function ($value, int|string $key, array $array): bool`. + +```php +Arrays::filter( + ['a' => 1, 'b' => 2, 'c' => 3], + fn($v) => $v < 3, +); +// ['a' => 1, 'b' => 2] +``` + + +first(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------- + +Vrne prvi element (ki ustreza predikatu, če je podan). Če tak element ne obstaja, vrne rezultat klica `$else` ali null. Parameter `$predicate` ima signaturo `function ($value, int|string $key, array $array): bool`. + +Ne spremeni notranjega kazalca za razliko od `reset()`. Parametra `$predicate` in `$else` obstajata od različice 4.0.4. + +```php +Arrays::first([1, 2, 3]); // 1 +Arrays::first([1, 2, 3], fn($v) => $v > 2); // 3 +Arrays::first([]); // null +Arrays::first([], else: fn() => false); // false +``` + +Glejte [#last()]. -Vrne prvi element iz polja ali nič, če je polje prazno. Za razliko od `reset()` ne spremeni notranjega kazalca. + +firstKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +------------------------------------------------------------------------------------------------ + +Vrne ključ prvega elementa (ki ustreza predikatu, če je podan) ali null, če tak element ne obstaja. Predikat `$predicate` ima signaturo `function ($value, int|string $key, array $array): bool`. ```php -Arrays::first([1, 2, 3]); // 1 -Arrays::first([]); // null +Arrays::firstKey([1, 2, 3]); // 0 +Arrays::firstKey([1, 2, 3], fn($v) => $v > 2); // 2 +Arrays::firstKey(['a' => 1, 'b' => 2]); // 'a' +Arrays::firstKey([]); // null ``` +Glejte [#lastKey()]. + flatten(array $array, bool $preserveKeys=false): array .[method] ---------------------------------------------------------------- -Večdimenzionalno polje pretvori v ravno polje. +Združi večnivojsko polje v ravno. ```php $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); @@ -71,20 +147,20 @@ $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); ``` -get(array $array, string|int|array $key, mixed $default=null): mixed .[method] ------------------------------------------------------------------------------- +get(array $array, string|int|array $key, ?mixed $default=null): mixed .[method] +------------------------------------------------------------------------------- -Vrne `$array[$key]` item. Če ne obstaja, se vrže `Nette\InvalidArgumentException`, razen če je kot tretji argument določena privzeta vrednost. +Vrne element `$array[$key]`. Če ne obstaja, sproži bodisi izjemo `Nette\InvalidArgumentException` ali, če je podan tretji parameter `$default`, vrne tega. ```php -// če $array['foo'] ne obstaja, vrže izjemo +// če $array['foo'] ne obstaja, sproži izjemo $value = Arrays::get($array, 'foo'); // če $array['foo'] ne obstaja, vrne 'bar' $value = Arrays::get($array, 'foo', 'bar'); ``` -Argument `$key` je lahko tudi polje. +Ključ `$key` je lahko tudi polje. ```php $array = ['color' => ['favorite' => 'red'], 5]; @@ -97,36 +173,36 @@ $value = Arrays::get($array, ['color', 'favorite']); getRef(array &$array, string|int|array $key): mixed .[method] ------------------------------------------------------------- -Pridobi referenco na dani `$array[$key]`. Če indeks ne obstaja, se ustvari nov indeks z vrednostjo `null`. +Pridobi referenco na določen element polja. Če element ne obstaja, bo ustvarjen z vrednostjo null. ```php $valueRef = & Arrays::getRef($array, 'foo'); -// vrne referenco $array['foo'] +// vrne referenco na $array['foo'] ``` -Deluje z večdimenzionalnimi polji in funkcijo [get() |#get()]. +Tako kot funkcija [#get()] zna delati z večdimenzionalnimi polji. ```php -$value = & Arrays::get($array, ['color', 'favorite']); -// vrne referenco $array['color']['favorite'] +$value = & Arrays::getRef($array, ['color', 'favorite']); +// vrne referenco na $array['color']['favorite'] ``` grep(array $array, string $pattern, bool $invert=false): array .[method] ------------------------------------------------------------------------ -Vrne samo tiste elemente polja, ki ustrezajo regularnemu izrazu `$pattern`. Če je `$invert` `true` , vrne elemente, ki se ne ujemajo. Napaka pri sestavljanju ali izvajanju regexa vrže `Nette\RegexpException`. +Vrne samo tiste elemente polja, katerih vrednost ustreza regularnemu izrazu `$pattern`. Če je `$invert` `true`, vrne nasprotno elemente, ki ne ustrezajo. Napaka pri prevajanju ali obdelavi izraza sproži izjemo `Nette\RegexpException`. ```php $filteredArray = Arrays::grep($array, '~^\d+$~'); -// vrne samo številčne elemente +// vrne samo elemente polja, sestavljene iz števk ``` insertAfter(array &$array, string|int|null $key, array $inserted): void .[method] --------------------------------------------------------------------------------- -Vsebino polja `$inserted` vstavi v polje `$array` takoj za `$key`. Če je `$key` `null` (ali ne obstaja), se vstavi na konec. +Vstavi vsebino polja `$inserted` v polje `$array` takoj za element s ključem `$key`. Če je `$key` `null` (ali ga v polju ni), se vstavi na konec. ```php $array = ['first' => 10, 'second' => 20]; @@ -138,7 +214,7 @@ Arrays::insertAfter($array, 'first', ['hello' => 'world']); insertBefore(array &$array, string|int|null $key, array $inserted): void .[method] ---------------------------------------------------------------------------------- -Vsebino polja `$inserted` vstavi v polje `$array` pred `$key`. Če je `$key` `null` (ali ne obstaja), se vstavi na začetek. +Vstavi vsebino polja `$inserted` v polje `$array` pred element s ključem `$key`. Če je `$key` `null` (ali ga v polju ni), se vstavi na začetek. ```php $array = ['first' => 10, 'second' => 20]; @@ -150,7 +226,7 @@ Arrays::insertBefore($array, 'first', ['hello' => 'world']); invoke(iterable $callbacks, ...$args): array .[method] ------------------------------------------------------ -Prikliče vse povratne klice in vrne polje rezultatov. +Kliče vse povratne klice in vrne polje rezultatov. ```php $callbacks = [ @@ -166,7 +242,7 @@ $array = Arrays::invoke($callbacks, 5, 11); invokeMethod(iterable $objects, string $method, ...$args): array .[method] -------------------------------------------------------------------------- -Prikliče metodo za vsak predmet v polju in vrne polje rezultatov. +Kliče metodo na vsakem objektu v polju in vrne polje rezultatov. ```php $objects = ['a' => $obj1, 'b' => $obj2]; @@ -179,7 +255,7 @@ $array = Arrays::invokeMethod($objects, 'foo', 1, 2); isList(array $array): bool .[method] ------------------------------------ -Preveri, ali je polje indeksirano v naraščajočem vrstnem redu številskih ključev od nič, tj. seznam. +Preveri, ali je polje indeksirano po naraščajočem zaporedju numeričnih ključev od nič, t.i. seznam (list). ```php Arrays::isList(['a', 'b', 'c']); // true @@ -188,21 +264,42 @@ Arrays::isList(['a' => 1, 'b' => 2]); // false ``` -last(array $array): mixed .[method] ------------------------------------ +last(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------ + +Vrne zadnji element (ki ustreza predikatu, če je podan). Če tak element ne obstaja, vrne rezultat klica `$else` ali null. Parameter `$predicate` ima signaturo `function ($value, int|string $key, array $array): bool`. -Vrne zadnji element iz polja ali nič, če je polje prazno. Za razliko od `end()` ne spremeni notranjega kazalca. +Ne spremeni notranjega kazalca za razliko od `end()`. Parametra `$predicate` in `$else` obstajata od različice 4.0.4. ```php -Arrays::last([1, 2, 3]); // 3 -Arrays::last([]); // null +Arrays::last([1, 2, 3]); // 3 +Arrays::last([1, 2, 3], fn($v) => $v < 3); // 2 +Arrays::last([]); // null +Arrays::last([], else: fn() => false); // false ``` +Glejte [#first()]. -map(iterable $array, callable $callback): array .[method] + +lastKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +----------------------------------------------------------------------------------------------- + +Vrne ključ zadnjega elementa (ki ustreza predikatu, če je podan) ali null, če tak element ne obstaja. Predikat `$predicate` ima signaturo `function ($value, int|string $key, array $array): bool`. + +```php +Arrays::lastKey([1, 2, 3]); // 2 +Arrays::lastKey([1, 2, 3], fn($v) => $v < 3); // 1 +Arrays::lastKey(['a' => 1, 'b' => 2]); // 'b' +Arrays::lastKey([]); // null +``` + +Glejte [#firstKey()]. + + +map(array $array, callable $transformer): array .[method] --------------------------------------------------------- -Kliče `$callback` na vse elemente v polju in vrne polje vrnjenih vrednosti. Povratni klic ima podpis `function ($value, $key, array $array): bool`. +Kliče `$transformer` na vse elemente v polju in vrne polje vrnjenih vrednosti. Povratni klic ima signaturo `function ($value, $key, array $array): mixed`. ```php $array = ['foo', 'bar', 'baz']; @@ -211,10 +308,24 @@ $res = Arrays::map($array, fn($value) => $value . $value); ``` +mapWithKeys(array $array, callable $transformer): array .[method] +----------------------------------------------------------------- + +Ustvari novo polje s transformacijo vrednosti in ključev prvotnega polja. Funkcija `$transformer` ima signaturo `function ($value, $key, array $array): ?array{$newKey, $newValue}`. Če `$transformer` vrne `null`, je element preskočen. Za ohranjene elemente se prvi element vrnjenega polja uporabi kot nov ključ, drugi element pa kot nova vrednost. + +```php +$array = ['a' => 1, 'b' => 2]; +$result = Arrays::mapWithKeys($array, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +Ta metoda je uporabna v situacijah, ko morate spremeniti strukturo polja (ključe in vrednosti hkrati) ali filtrirati elemente med transformacijo (z vračanjem `null` za neželene elemente). + + mergeTree(array $array1, array $array2): array .[method] -------------------------------------------------------- -Rekurzivno združi dve polji. Uporabna je na primer za združevanje drevesnih struktur. Obnaša se kot operator `+` za polje, tj. doda par ključ/vrednost iz drugega polja v prvo in v primeru trka ključev ohrani vrednost iz prvega polja. +Rekurzivno združi dve polji. Uporabno je na primer za združevanje drevesnih struktur. Pri združevanju sledi istim pravilom kot operator `+`, uporabljen na poljih, tj. prvemu polju dodaja pare ključ/vrednost iz drugega polja in v primeru kolizije ključev ohrani vrednost iz prvega polja. ```php $array1 = ['color' => ['favorite' => 'red'], 5]; @@ -224,13 +335,13 @@ $array = Arrays::mergeTree($array1, $array2); // $array = ['color' => ['favorite' => 'red', 'blue'], 5]; ``` -Vrednosti iz drugega polja se vedno dodajo prvemu. Izginotje vrednosti `10` iz drugega polja se lahko zdi nekoliko zmedeno. Opozoriti je treba, da je ta vrednost kot tudi vrednost `5` in the first array have the same numeric key `0`, zato je v dobljenem polju samo element iz prvega polja. +Vrednosti iz drugega polja so vedno dodane na konec prvega. Malo zmedeno se lahko zdi izginotje vrednosti `10` iz drugega polja. Treba se je zavedati, da imata ta vrednost in prav tako vrednost `5` v prvem polju dodeljen isti numerični ključ `0`, zato je v končnem polju samo element iz prvega polja. -normalize(array $array, string $filling=null): array .[method] --------------------------------------------------------------- +normalize(array $array, ?string $filling=null): array .[method] +--------------------------------------------------------------- -Normalizira polje v asociativno polje. Numerične ključe zamenja z njihovimi vrednostmi, nova vrednost bo `$filling`. +Normalizira polje v asociativno polje. Numerične ključe nadomesti z njihovimi vrednostmi, nova vrednost bo `$filling`. ```php $array = Arrays::normalize([1 => 'first', 'a' => 'second']); @@ -243,26 +354,26 @@ $array = Arrays::normalize([1 => 'first', 'a' => 'second'], 'foobar'); ``` -pick(array &$array, string|int $key, mixed $default=null): mixed .[method] --------------------------------------------------------------------------- +pick(array &$array, string|int $key, ?mixed $default=null): mixed .[method] +--------------------------------------------------------------------------- -Vrne in odstrani vrednost elementa iz polja. Če ne obstaja, vrže izjemo ali vrne `$default`, če je naveden. +Vrne in odstrani vrednost elementa iz polja. Če ne obstaja, sproži izjemo ali vrne vrednost `$default`, če je podana. ```php -$array = [1 => 'foo', null => 'bar']; -$a = Arrays::pick($array, null); +$array = [1 => 'foo', 'x' => 'bar']; +$a = Arrays::pick($array, 'x'); // $a = 'bar' $b = Arrays::pick($array, 'not-exists', 'foobar'); // $b = 'foobar' $c = Arrays::pick($array, 'not-exists'); -// vrže Nette\InvalidArgumentException +// sproži Nette\InvalidArgumentException ``` renameKey(array &$array, string|int $oldKey, string|int $newKey): bool .[method] -------------------------------------------------------------------------------- -Preimenuje ključ. Če je bil ključ najden v polju, vrne `true`. +Preimenuje ključ v polju. Vrne `true`, če je bil ključ najden v polju. ```php $array = ['first' => 10, 'second' => 20]; @@ -274,20 +385,20 @@ Arrays::renameKey($array, 'first', 'renamed'); getKeyOffset(array $array, string|int $key): ?int .[method] ----------------------------------------------------------- -Vrne ničelno indeksiran položaj danega ključa v polju. Vrne `null`, če ključa ni mogoče najti. +Vrne položaj danega ključa v polju. Položaj je oštevilčen od 0. V primeru, da ključ ni najden, funkcija vrne `null`. ```php $array = ['first' => 10, 'second' => 20]; $position = Arrays::getKeyOffset($array, 'first'); // vrne 0 $position = Arrays::getKeyOffset($array, 'second'); // vrne 1 -$position = Arrays::getKeyOffset($array, 'not-exists'); // vrne nič +$position = Arrays::getKeyOffset($array, 'not-exists'); // vrne null ``` -some(iterable $array, callable $callback): bool .[method] ---------------------------------------------------------- +some(array $array, callable $predicate): bool .[method] +------------------------------------------------------- -Preizkusi, ali vsaj en element v polju prestane test, ki ga izvaja posredovani povratni klic s podpisom `function ($value, $key, array $array): bool`. +Preveri, ali vsaj en element v polju prestane test, implementiran v `$predicate` s signaturo `function ($value, $key, array $array): bool`. ```php $array = [1, 2, 3, 4]; @@ -295,13 +406,13 @@ $isEven = fn($value) => $value % 2 === 0; $res = Arrays::some($array, $isEven); // true ``` -Glej [every( |#every()]). +Glejte [#every()]. toKey(mixed $key): string|int .[method] --------------------------------------- -Pretvori vrednost v ključ polja, ki je bodisi celo število bodisi niz. +Pretvori vrednost v ključ polja, ki je bodisi celo število (integer) ali niz. ```php Arrays::toKey('1'); // 1 @@ -312,7 +423,7 @@ Arrays::toKey('01'); // '01' toObject(iterable $array, object $object): object .[method] ----------------------------------------------------------- -Kopira elemente polja `$array` v objekt `$object` in ga nato vrne. +Kopira elemente polja `$array` v objekt `$object`, ki ga nato vrne. ```php $obj = new stdClass; @@ -321,10 +432,10 @@ Arrays::toObject($array, $obj); // nastavi $obj->foo = 1; $obj->bar = 2; ``` -wrap(iterable $array, string $prefix='', string $suffix=''): array .[method] ----------------------------------------------------------------------------- +wrap(array $array, string $prefix='', string $suffix=''): array .[method] +------------------------------------------------------------------------- -Vsak element polja pretvori v niz in ga obkroži z objektoma `$prefix` in `$suffix`. +Vsak element v polju pretvori v niz in ga ovije s predpono `$prefix` in pripono `$suffix`. ```php $array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); @@ -332,21 +443,21 @@ $array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); ``` -ArrayHash .[#toc-arrayhash] -=========================== +ArrayHash +========= -Objekt [api:Nette\Utils\ArrayHash] je potomec generičnega razreda stdClass in ga razširi na možnost, da ga obravnava kot polje, na primer dostop do članov z uporabo oglatih oklepajev: +Objekt [api:Nette\Utils\ArrayHash] je potomec generičnega razreda `stdClass` in ga razširja z zmožnostjo obravnavanja kot polja, torej na primer dostopanja do članov prek oglatih oklepajev: ```php $hash = new Nette\Utils\ArrayHash; $hash['foo'] = 123; -$hash->bar = 456; // deluje tudi zapis predmeta +$hash->bar = 456; // hkrati deluje tudi objektni zapis $hash->foo; // 123 ``` -Za pridobitev števila elementov lahko uporabite funkcijo `count($hash)`. +Lahko uporabite funkcijo `count($hash)` za ugotavljanje števila elementov. -Po objektu lahko iterirate tako kot po polju, tudi z referenco: +Nad objektom je mogoče iterirati enako kot v primeru polja, tudi z referenco: ```php foreach ($hash as $key => $value) { @@ -358,7 +469,7 @@ foreach ($hash as $key => &$value) { } ``` -Obstoječa polja lahko pretvorite v `ArrayHash` z uporabo `from()`: +Obstoječe polje lahko pretvorite v `ArrayHash` z metodo `from()`: ```php $array = ['foo' => 123, 'bar' => 456]; @@ -379,24 +490,24 @@ $hash->inner->a; // 'b' $hash['inner']['a']; // 'b' ``` -Lahko se ji izognemo z drugim parametrom: +To lahko preprečite z drugim parametrom: ```php $hash = Nette\Utils\ArrayHash::from($array, false); $hash->inner; // polje ``` -Preoblikovanje nazaj v polje: +Transformacija nazaj v polje: ```php $array = (array) $hash; ``` -ArrayList .[#toc-arraylist] -=========================== +ArrayList +========= -[api:Nette\Utils\ArrayList] predstavlja linearno polje, v katerem so indeksi samo cela števila, naraščajoča od 0. +[api:Nette\Utils\ArrayList] predstavlja linearno polje, kjer so indeksi samo cela števila naraščajoče od 0. ```php $list = new Nette\Utils\ArrayList; @@ -407,9 +518,16 @@ $list[] = 'c'; count($list); // 3 ``` -Za pridobitev števila elementov lahko uporabite funkcijo `count($list)`. +Obstoječe polje lahko pretvorite v `ArrayList` z metodo `from()`: -Po predmetu lahko iterirate tako kot po polju, tudi z referenco: +```php +$array = ['foo', 'bar']; +$list = Nette\Utils\ArrayList::from($array); +``` + +Lahko uporabite funkcijo `count($list)` za ugotavljanje števila elementov. + +Nad objektom je mogoče iterirati enako kot v primeru polja, tudi z referenco: ```php foreach ($list as $key => $value) { @@ -421,18 +539,11 @@ foreach ($list as $key => &$value) { } ``` -Obstoječa polja lahko pretvorite v `ArrayList` z uporabo `from()`: - -```php -$array = ['foo', 'bar']; -$list = Nette\Utils\ArrayList::from($array); -``` - -Dostop do ključev, ki presegajo dovoljene vrednosti, vrže izjemo `Nette\OutOfRangeException`: +Dostop do ključev izven dovoljenih vrednosti sproži izjemo `Nette\OutOfRangeException`: ```php -echo $list[-1]; // vrže Nette\OutOfRangeException -unset($list[30]); // vrže Nette\OutOfRangeException +echo $list[-1]; // sproži Nette\OutOfRangeException +unset($list[30]); // sproži Nette\OutOfRangeException ``` Odstranitev ključa povzroči preštevilčenje elementov: @@ -442,7 +553,7 @@ unset($list[1]); // ArrayList(0 => 'a', 1 => 'c') ``` -Na začetek lahko dodate nov element z uporabo `prepend()`: +Nov element lahko dodate na začetek z metodo `prepend()`: ```php $list->prepend('d'); diff --git a/utils/sl/callback.texy b/utils/sl/callback.texy index 843003462b..9627c1d628 100644 --- a/utils/sl/callback.texy +++ b/utils/sl/callback.texy @@ -1,8 +1,8 @@ -Funkcije povratnih klicev -************************* +Delo s povratnimi klici +*********************** .[perex] -[api:Nette\Utils\Callback] je statični razred, ki vsebuje funkcije za delo s [povratnimi klici PHP |https://www.php.net/manual/en/language.types.callable.php]. +[api:Nette\Utils\Callback] je statični razred s funkcijami za delo s [PHP povratnimi klici |https://www.php.net/manual/en/language.types.callable.php]. Namestitev: @@ -11,7 +11,7 @@ Namestitev: composer require nette/utils ``` -Vsi primeri predpostavljajo, da je definiran naslednji vzdevek razreda: +Vsi primeri predpostavljajo ustvarjen alias: ```php use Nette\Utils\Callback; @@ -21,21 +21,21 @@ use Nette\Utils\Callback; check($callable, bool $syntax=false): callable .[method] -------------------------------------------------------- -Preveri, ali je `$callable` veljavni povratni klic PHP. V nasprotnem primeru vrže `Nette\InvalidArgumentException`. Če je `$syntax` nastavljen na true, funkcija samo preveri, ali ima `$callable` veljavno strukturo za uporabo kot povratni klic, ne preveri pa, ali razred ali metoda dejansko obstajata. Vrne `$callable`. +Preveri, ali je spremenljivka `$callable` veljaven povratni klic. Sicer sproži `Nette\InvalidArgumentException`. Če je `$syntax` `true`, funkcija samo preveri, ali ima `$callable` strukturo povratnega klica, vendar ne preverja, ali dani razred ali metoda dejansko obstaja. Vrne `$callable`. ```php -Callback::check('trim'); // brez izjeme -Callback::check(['NonExistentClass', 'method']); // vrže Nette\InvalidArgumentException -Callback::check(['NonExistentClass', 'method'], true); // ni izjeme -Callback::check(function () {}); // ni izjeme -Callback::check(null); // vrže Nette\InvalidArgumentException +Callback::check('trim'); // ne sproži izjeme +Callback::check(['NonExistentClass', 'method']); // sproži Nette\InvalidArgumentException +Callback::check(['NonExistentClass', 'method'], true); // ne sproži izjeme +Callback::check(function () {}); // ne sproži izjeme +Callback::check(null); // sproži Nette\InvalidArgumentException ``` toString($callable): string .[method] ------------------------------------- -Pretvori povratni klic PHP v besedilno obliko. Razred ali metoda morda ne obstajata. +Pretvori PHP povratni klic v besedilno obliko. Razred ali metoda ni nujno, da obstajata. ```php Callback::toString('trim'); // 'trim' @@ -46,7 +46,7 @@ Callback::toString(['MyClass', 'method']); // 'MyClass::method' toReflection($callable): ReflectionMethod|ReflectionFunction .[method] ---------------------------------------------------------------------- -Vrne odsev za metodo ali funkcijo, uporabljeno v povratnem klicu PHP. +Vrne refleksijo za metodo ali funkcijo v PHP povratnem klicu. ```php $ref = Callback::toReflection('trim'); @@ -60,7 +60,7 @@ $ref = Callback::toReflection(['MyClass', 'method']); isStatic($callable): bool .[method] ----------------------------------- -Preveri, ali je povratni klic PHP funkcija ali statična metoda. +Ugotavlja, ali je PHP povratni klic funkcija ali statična metoda. ```php Callback::isStatic('trim'); // true @@ -73,7 +73,7 @@ Callback::isStatic(function () {}); // false unwrap(Closure $closure): callable|array .[method] -------------------------------------------------- -Razkrije zaključek, ki ga je ustvaril `Closure::fromCallable`:https://www.php.net/manual/en/closure.fromcallable.php. +Povratno razširi Closure, ustvarjeno s pomočjo `Closure::fromCallable`:https://www.php.net/manual/en/closure.fromcallable.php. ```php $closure = Closure::fromCallable(['MyClass', 'method']); diff --git a/utils/sl/datetime.texy b/utils/sl/datetime.texy index f16a86ba84..7f18622cc9 100644 --- a/utils/sl/datetime.texy +++ b/utils/sl/datetime.texy @@ -1,8 +1,8 @@ -Datum in ura +Datum in čas ************ .[perex] -[api:Nette\Utils\DateTime] je razred, ki razširja domači [php:DateTime]. +[api:Nette\Utils\DateTime] je razred, ki razširja izvorni [php:DateTime] z dodatnimi funkcijami. Namestitev: @@ -11,7 +11,7 @@ Namestitev: composer require nette/utils ``` -Vsi primeri predpostavljajo, da je definiran naslednji vzdevek razreda: +Vsi primeri predpostavljajo ustvarjen alias: ```php use Nette\Utils\DateTime; @@ -20,35 +20,35 @@ use Nette\Utils\DateTime; static from(string|int|\DateTimeInterface $time): DateTime .[method] -------------------------------------------------------------------- -Ustvari objekt DateTime iz niza, časovnega žiga UNIX ali drugega objekta [php:DateTimeInterface]. Če datum in čas nista veljavna, vrže sporočilo `Exception`. +Ustvari objekt `DateTime` iz niza, časovnega žiga UNIX ali drugega objekta [php:DateTimeInterface]. Sproži izjemo `Exception`, če datum in čas nista veljavna. ```php -DateTime::from(1138013640); // ustvari DateTime iz časovnega žiga UNIX s privzetim časovnim žigom -DateTime::from(42); // ustvari DateTime iz trenutnega časa in 42 sekund -DateTime::from('1994-02-26 04:15:32'); // ustvari DateTime na podlagi niza -DateTime::from('1994-02-26'); // ustvari DateTime z datumom, čas bo 00:00:00 +DateTime::from(1138013640); // ustvari DateTime iz časovnega žiga UNIX s privzeto časovno cono +DateTime::from(42); // ustvari DateTime iz trenutnega časa plus 42 sekund +DateTime::from('1994-02-26 04:15:32'); // ustvari DateTime glede na niz +DateTime::from('1994-02-26'); // ustvari DateTime glede na datum, čas bo 00:00:00 ``` static fromParts(int $year, int $month, int $day, int $hour=0, int $minute=0, float $second=0.0): DateTime .[method] -------------------------------------------------------------------------------------------------------------------- -Ustvari objekt DateTime ali vrže izjemo `Nette\InvalidArgumentException`, če datum in čas nista veljavna. +Ustvari objekt `DateTime` ali sproži izjemo `Nette\InvalidArgumentException`, če datum in čas nista veljavna. ```php DateTime::fromParts(1994, 2, 26, 4, 15, 32); ``` -static createFromFormat(string $format, string $time, string|\DateTimeZone $timezone=null): DateTime|false .[method] --------------------------------------------------------------------------------------------------------------------- -Razširi [DateTime::createFromFormat() |https://www.php.net/manual/en/datetime.createfromformat.php] z možnostjo določitve časovnega pasu kot niza. +static createFromFormat(string $format, string $time, ?string|\DateTimeZone $timezone=null): DateTime|false .[method] +--------------------------------------------------------------------------------------------------------------------- +Razširja [DateTime::createFromFormat() |https://www.php.net/manual/en/datetime.createfromformat.php] z možnostjo vnosa časovne cone kot niza. ```php -DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); // ustvarite s časovnim območjem po meri +DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); ``` modifyClone(string $modify=''): static .[method] ------------------------------------------------ -Ustvari kopijo s spremenjenim časom. +Ustvari kopijo s prilagojenim časom. ```php $original = DateTime::from('2017-02-03'); $clone = $original->modifyClone('+1 day'); @@ -59,15 +59,15 @@ $clone->format('Y-m-d'); // '2017-02-04' __toString(): string .[method] ------------------------------ -Vrne datum in čas v obliki `Y-m-d H:i:s`. +Vrne datum in čas v formatu `Y-m-d H:i:s`. ```php echo $dateTime; // '2017-02-03 04:15:32' ``` -Implementira JsonSerializable .[#toc-implements-jsonserializable] ------------------------------------------------------------------ -Vrne datum in čas v formatu ISO 8601, ki se uporablja na primer v javascriptu. +Implementira `JsonSerializable` +------------------------------- +Vrne datum in čas v formatu ISO 8601, ki se uporablja na primer v JavaScriptu. ```php $date = DateTime::from('2017-02-03'); echo json_encode($date); diff --git a/utils/sl/filesystem.texy b/utils/sl/filesystem.texy index e51281d06c..c54e201297 100644 --- a/utils/sl/filesystem.texy +++ b/utils/sl/filesystem.texy @@ -1,41 +1,43 @@ -Funkcije datotečnega sistema -**************************** +Datotečni sistem +**************** .[perex] -[api:Nette\Utils\FileSystem] je statični razred, ki vsebuje uporabne funkcije za delo z datotečnim sistemom. Ena od prednosti v primerjavi z izvornimi funkcijami PHP je, da v primeru napak mečejo izjeme. +[api:Nette\Utils\FileSystem] je razred z uporabnimi funkcijami za delo z datotečnim sistemom. Ena od prednosti v primerjavi z izvornimi PHP funkcijami je, da v primeru napake sprožijo izjeme. +Če potrebujete iskati datoteke na disku, uporabite [Finder |finder]. + Namestitev: ```shell composer require nette/utils ``` -Naslednji primeri predpostavljajo, da je definiran naslednji vzdevek razreda: +Naslednji primeri predpostavljajo ustvarjen vzdevek: ```php use Nette\Utils\FileSystem; ``` -Manipulacija .[#toc-manipulation] -================================= +Manipulacija +============ copy(string $origin, string $target, bool $overwrite=true): void .[method] -------------------------------------------------------------------------- -Kopira datoteko ali celoten imenik. Privzeto prepiše obstoječe datoteke in imenike. Če je `$overwrite` nastavljen na `false` in `$target` že obstaja, vrže izjemo `Nette\InvalidStateException`. Ob pojavu napake vrže izjemo `Nette\IOException`. +Kopira datoteko ali celoten imenik. Privzeto prepiše obstoječe datoteke in imenike. S parametrom `$overwrite`, nastavljenim na vrednost `false`, sproži izjemo `Nette\InvalidStateException`, če ciljna datoteka ali imenik `$target` obstaja. Ob napaki sproži izjemo `Nette\IOException`. ```php FileSystem::copy('/path/to/source', '/path/to/dest', overwrite: true); ``` -createDir(string $directory, int $mode=0777): void .[method] ------------------------------------------------------------- +createDir(string $dir, int $mode=0777): void .[method] +------------------------------------------------------ -Ustvari imenik, če ne obstaja, vključno z nadrejenimi imeniki. Ob pojavu napake vrže izjemo `Nette\IOException`. +Ustvari imenik, če ne obstaja, vključno z nadrejenimi imeniki. Ob napaki sproži izjemo `Nette\IOException`. ```php FileSystem::createDir('/path/to/dir'); @@ -45,7 +47,7 @@ FileSystem::createDir('/path/to/dir'); delete(string $path): void .[method] ------------------------------------ -Izbriše datoteko ali celoten imenik, če obstaja. Če imenik ni prazen, najprej izbriše njegovo vsebino. Ob pojavu napake vrže izjemo `Nette\IOException`. +Izbriše datoteko ali celoten imenik, če obstaja. Če imenik ni prazen, najprej izbriše njegovo vsebino. Ob napaki sproži izjemo `Nette\IOException`. ```php FileSystem::delete('/path/to/fileOrDir'); @@ -55,7 +57,7 @@ FileSystem::delete('/path/to/fileOrDir'); makeWritable(string $path, int $dirMode=0777, int $fileMode=0666): void .[method] --------------------------------------------------------------------------------- -Nastavi dovoljenja datotek na `$fileMode` ali dovoljenja imenikov na `$dirMode`. Rekurzivno preleti in nastavi dovoljenja tudi za celotno vsebino imenika. +Nastavi dovoljenja datoteke na `$fileMode` ali imenika na `$dirMode`. Rekurzivno preide in nastavi dovoljenja tudi celotni vsebini imenika. ```php FileSystem::makeWritable('/path/to/fileOrDir'); @@ -65,7 +67,7 @@ FileSystem::makeWritable('/path/to/fileOrDir'); open(string $path, string $mode): resource .[method] ---------------------------------------------------- -Odpre datoteko in vrne vir. Parameter `$mode` deluje enako kot izvirna funkcija `fopen()`:https://www.php.net/manual/en/function.fopen.php. Če pride do napake, se sproži izjema `Nette\IOException`. +Odpre datoteko in vrne vir (resource). Parameter `$mode` deluje enako kot pri izvorni funkciji `fopen()`:https://www.php.net/manual/en/function.fopen.php. V primeru napake sproži izjemo `Nette\IOException`. ```php $res = FileSystem::open('/path/to/file', 'r'); @@ -75,7 +77,7 @@ $res = FileSystem::open('/path/to/file', 'r'); read(string $file): string .[method] ------------------------------------ -Prebere vsebino zapisa `$file`. Ob pojavu napake vrže izjemo `Nette\IOException`. +Vrne vsebino datoteke `$file`. Ob napaki sproži izjemo `Nette\IOException`. ```php $content = FileSystem::read('/path/to/file'); @@ -85,8 +87,7 @@ $content = FileSystem::read('/path/to/file'); readLines(string $file, bool $stripNewLines=true): \Generator .[method] ----------------------------------------------------------------------- -Prebere vsebino datoteke vrstico za vrstico. Za razliko od izvorne funkcije `file()` ne prebere celotne datoteke v pomnilnik, temveč jo bere neprekinjeno, tako da lahko prebere datoteke, ki so večje od razpoložljivega pomnilnika. Funkcija `$stripNewLines` določa, ali se odstranijo znaki za prekinitev vrstic `\r` in `\n`. -V primeru napake sproži izjemo `Nette\IOException`. +Prebere vsebino datoteke vrstico po vrstico. Za razliko od izvorne funkcije `file()` ne naloži celotne datoteke v pomnilnik, ampak jo bere sproti, tako da je mogoče brati tudi datoteke, večje od razpoložljivega pomnilnika. `$stripNewLines` pove, ali naj se odstranijo znaki konca vrstice `\r` in `\n`. V primeru napake sproži izjemo `Nette\IOException`. ```php $lines = FileSystem::readLines('/path/to/file'); @@ -100,7 +101,7 @@ foreach ($lines as $lineNum => $line) { rename(string $origin, string $target, bool $overwrite=true): void .[method] ---------------------------------------------------------------------------- -Preimenuje ali premakne datoteko ali imenik, ki ga določa `$origin`, v `$target`. Privzeto prepiše obstoječe datoteke in imenike. Če je `$overwrite` nastavljen na `false` in `$target` že obstaja, vrže izjemo `Nette\InvalidStateException`. Ob pojavu napake vrže izjemo `Nette\IOException`. +Preimenuje ali premakne datoteko ali imenik `$origin`. Privzeto prepiše obstoječe datoteke in imenike. S parametrom `$overwrite`, nastavljenim na vrednost `false`, sproži izjemo `Nette\InvalidStateException`, če ciljna datoteka ali imenik `$target` obstaja. Ob napaki sproži izjemo `Nette\IOException`. ```php FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); @@ -110,21 +111,21 @@ FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); write(string $file, string $content, int $mode=0666): void .[method] -------------------------------------------------------------------- -Zapiše `$content` v `$file`. Ob pojavu napake vrže izjemo `Nette\IOException`. +Zapiše niz `$content` v datoteko `$file`. Ob napaki sproži izjemo `Nette\IOException`. ```php FileSystem::write('/path/to/file', $content); ``` -Poti .[#toc-paths] -================== +Poti +==== isAbsolute(string $path): bool .[method] ---------------------------------------- -Določi, ali je `$path` absoluten. +Ugotavlja, ali je pot `$path` absolutna. ```php FileSystem::isAbsolute('../backup'); // false @@ -135,7 +136,7 @@ FileSystem::isAbsolute('C:/backup'); // true joinPaths(string ...$segments): string .[method] ------------------------------------------------ -Poveže vse segmente poti in normalizira rezultat. +Združi vse segmente poti in rezultat normalizira. ```php FileSystem::joinPaths('a', 'b', 'file.txt'); // 'a/b/file.txt' @@ -146,7 +147,7 @@ FileSystem::joinPaths('/a/', '/../b'); // '/b' normalizePath(string $path): string .[method] --------------------------------------------- -Normalizira `..` in `.` ter ločevalnike imenikov v poti. +Normalizira `..` in `.` ter ločila imenikov v poti na sistemska. ```php FileSystem::normalizePath('/file/.'); // '/file/' @@ -159,7 +160,7 @@ FileSystem::normalizePath('file/../../bar'); // '/../bar' unixSlashes(string $path): string .[method] ------------------------------------------- -Pretvarja poševnice v `/`, ki se uporabljajo v sistemih Unix. +Pretvori poševnice v `/`, ki se uporabljajo v sistemih Unix. ```php $path = FileSystem::unixSlashes($path); @@ -169,8 +170,45 @@ $path = FileSystem::unixSlashes($path); platformSlashes(string $path): string .[method] ----------------------------------------------- -Pretvarja poševnice v znake, značilne za trenutno platformo, tj. `\` v sistemu Windows in `/` drugje. +Pretvori poševnice v znake, specifične za trenutno platformo, tj. `\` v Windows in `/` drugje. ```php $path = FileSystem::platformSlashes($path); ``` + + +resolvePath(string $basePath, string $path): string .[method]{data-version:4.0.6} +--------------------------------------------------------------------------------- + +Izpelje končno pot iz poti `$path` glede na osnovni imenik `$basePath`. Absolutne poti (`/foo`, `C:/foo`) pusti nespremenjene (samo normalizira poševnice), relativne poti pripne k osnovni poti. + +```php +// V Windows bi bile poševnice v izpisu obrnjene (\) +FileSystem::resolvePath('/base/dir', '/abs/path'); // '/abs/path' +FileSystem::resolvePath('/base/dir', 'rel'); // '/base/dir/rel' +FileSystem::resolvePath('base/dir', '../file.txt'); // 'base/file.txt' +FileSystem::resolvePath('base', ''); // 'base' +``` + + +Statični vs nestatični pristop +============================== + +Da bi lahko na primer za namene testiranja razred enostavno nadomestili z drugim (mock), ga uporabljajte nestatično: + +```php +class AnyClassUsingFileSystem +{ + public function __construct( + private FileSystem $fileSystem, + ) { + } + + public function readConfig(): string + { + return $this->fileSystem->read(/* ... */); + } + + ... +} +``` diff --git a/utils/sl/finder.texy b/utils/sl/finder.texy index c548de7150..19eb48dfe4 100644 --- a/utils/sl/finder.texy +++ b/utils/sl/finder.texy @@ -1,8 +1,8 @@ -Iskalnik: Iskanje datotek -************************* +Finder: iskanje datotek +*********************** .[perex] -Potrebujete najti datoteke, ki ustrezajo določeni maski? Iskalnik vam lahko pomaga. Je vsestransko in hitro orodje za pregledovanje strukture imenikov. +Potrebujete najti datoteke, ki ustrezajo določeni maski? Finder vam bo pri tem pomagal. Je vsestransko in hitro orodje za brskanje po strukturi imenikov. Namestitev: @@ -11,17 +11,17 @@ Namestitev: composer require nette/utils ``` -Primeri predpostavljajo, da je bil ustvarjen vzdevek: +Primeri predpostavljajo ustvarjen vzdevek: ```php use Nette\Utils\Finder; ``` -Uporaba .[#toc-using] ---------------------- +Uporaba +------- -Najprej si oglejmo, kako lahko s pomočjo [api:Nette\Utils\Finder] izpišete imena datotek s končnicama `.txt` in `.md` v trenutnem imeniku: +Najprej si poglejmo, kako lahko z uporabo [api:Nette\Utils\Finder] izpišete imena datotek s končnicami `.txt` in `.md` v trenutnem imeniku: ```php foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { @@ -29,45 +29,46 @@ foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { } ``` -Privzet imenik za iskanje je trenutni imenik, vendar ga lahko spremenite z uporabo metod [in() ali from( |#Where to search?] ). -Spremenljivka `$file` je primerek razreda [FileInfo |#FileInfo] z veliko uporabnimi metodami. Ključ `$name` vsebuje pot do datoteke kot niz. +Privzeti imenik za iskanje je trenutni imenik, vendar ga lahko spremenite z metodama [in() ali from() |#Kje iskati]. Spremenljivka `$file` je instanca razreda [#FileInfo] z veliko uporabnimi metodami. V ključu `$name` je pot do datoteke kot niz. -Kaj iskati? .[#toc-what-to-search-for] --------------------------------------- +Kaj iskati? +----------- -Poleg metode `findFiles()` obstajata še `findDirectories()`, ki išče samo po imenikih, in `find()`, ki išče po obeh. Ti metodi sta statični, zato ju lahko kličete, ne da bi ustvarili primerek. Parameter maska ni obvezen, če ga ne navedete, se poišče vse. +Poleg metode `findFiles()` obstaja tudi `findDirectories()`, ki išče samo imenike, in `find()`, ki išče oboje. Te metode so statične, zato jih je mogoče klicati brez ustvarjanja instance. Parameter z masko je neobvezen, če ga ne navedete, se poišče vse. ```php foreach (Finder::find() as $file) { - echo $file; // zdaj so na seznamu vse datoteke in imeniki + echo $file; // zdaj se izpišejo vse datoteke in imeniki } ``` -Z metodama `files()` in `directories()` lahko dodate, kaj naj se še išče. Metodi lahko pokličete večkrat, kot parameter pa lahko navedete polje mask: +Z metodama `files()` in `directories()` lahko dodate, kaj še želite iskati. Metode je mogoče klicati večkrat in kot parameter lahko navedete tudi polje mask: ```php Finder::findDirectories('vendor') // vsi imeniki - ->files(['*.php', '*.phpt']); // ter vse datoteke PHP + ->files(['*.php', '*.phpt']); // plus vse PHP datoteke ``` -Alternativa statičnim metodam je, da ustvarite primerek z uporabo `new Finder` (tako ustvarjen svež objekt ne išče ničesar) in določite, kaj naj se išče, z uporabo `files()` in `directories()`: +Alternativa statičnim metodam je ustvarjanje instance z `new Finder` (tako ustvarjen svež objekt ne išče ničesar) in navedbo, kaj iskati, z uporabo `files()` in `directories()`: ```php (new Finder) ->directories() // vsi imeniki - ->files('*.php'); // ter vse datoteke PHP + ->files('*.php'); // plus vse PHP datoteke ``` -Uporabite lahko [nadomestne znake |#wildcards] `*`, `**`, `?` and `[...]` v maski. Navedete lahko celo imenike, na primer `src/*.php` bo poiskal vse datoteke PHP v imeniku `src`. +V maski lahko uporabljate [nadomestne znake |#Nadomestni znaki] `*`, `**`, `?` in `[...]`. Lahko celo določite imenike, na primer `src/*.php` poišče vse PHP datoteke v imeniku `src`. + +Simbolične povezave se prav tako štejejo za imenike ali datoteke. -Kje iskati? .[#toc-where-to-search] ------------------------------------ +Kje iskati? +----------- -Privzet imenik za iskanje je trenutni imenik. To lahko spremenite z uporabo metod `in()` in `from()`. Kot je razvidno iz imen metod, `in()` išče samo po trenutnem imeniku, medtem ko `from()` išče tudi po njegovih podimenikih (rekurzivno). Če želite rekurzivno iskati v trenutnem imeniku, lahko uporabite `from('.')`. +Privzeti imenik za iskanje je trenutni imenik. Spremenite ga z metodama `in()` in `from()`. Kot je razvidno iz imen metod, `in()` išče samo v danem imeniku, medtem ko `from()` išče tudi v njegovih podimenikih (rekurzivno). Če želite iskati rekurzivno v trenutnem imeniku, lahko uporabite `from('.')`. -Ti metodi lahko pokličete večkrat ali pa jima posredujete več poti kot polja, potem se bodo datoteke iskale v vseh imenikih. Če eden od imenikov ne obstaja, se vrže sporočilo `Nette\UnexpectedValueException`. +Te metode je mogoče klicati večkrat ali jim posredovati več poti kot polje, datoteke se bodo nato iskale v vseh imenikih. Če kateri od imenikov ne obstaja, se sproži izjema `Nette\UnexpectedValueException`. ```php Finder::findFiles('*.php') @@ -75,50 +76,50 @@ Finder::findFiles('*.php') ->from('vendor'); // išče tudi v podimenikih vendor/ ``` -Relativne poti so relativne glede na trenutni imenik. Seveda lahko določite tudi absolutne poti: +Relativne poti so relativne glede na trenutni imenik. Seveda lahko navedete tudi absolutne poti: ```php Finder::findFiles('*.php') ->in('/var/www/html'); ``` - `*`, `**`, `?` can be used in the path. For example, you can use the path `src/*/*.php` [za |#wildcards] iskanje vseh datotek PHP v imenikih druge ravni v imeniku `src`. Znak `**`, imenovan globstar, je močan adut, saj omogoča iskanje tudi po podimenikih: uporabite `src/**/tests/*.php` za iskanje vseh datotek PHP v imeniku `tests`, ki se nahajajo v imeniku `src` ali katerem koli njegovem podimeniku. +V poti je mogoče uporabiti [nadomestne znake |#Nadomestni znaki] `*`, `**`, `?`. Tako lahko na primer s potjo `src/*/*.php` iščete vse PHP datoteke v imenikih druge ravni v imeniku `src`. Znak `**`, imenovan globstar, je močan adut, saj omogoča iskanje tudi v podimenikih: z `src/**/tests/*.php` iščete vse PHP datoteke v imeniku `tests`, ki se nahaja v `src` ali katerem koli njegovem podimeniku. -Po drugi strani pa so nadomestni znaki `[...]` znaki v poti niso podprti, tj. nimajo posebnega pomena, da bi se izognili nezaželenemu obnašanju v primeru, da iščete na primer `in(__DIR__)` in se po naključju v poti pojavijo znaki `[]`. +Nasprotno pa nadomestni znaki `[...]` v poti niso podprti, tj. nimajo posebnega pomena, da ne bi prišlo do nezaželenega vedenja v primeru, da bi iskali na primer `in(__DIR__)` in bi se v poti slučajno pojavili znaki `[]`. -Pri poglobljenem iskanju datotek in imenikov se najprej vrne nadrejeni imenik, nato pa datoteke, ki jih vsebuje, kar lahko obrnete s `childFirst()`. +Pri iskanju datotek in imenikov v globino se najprej vrne nadrejeni imenik in šele nato datoteke, ki jih vsebuje, kar je mogoče obrniti z uporabo `childFirst()`. -Zaščitni znaki .[#toc-wildcards] --------------------------------- +Nadomestni znaki +---------------- -V maski lahko uporabite več posebnih znakov: +V maski lahko uporabljate več posebnih znakov: -- `*` - replaces any number of arbitrary characters (except `/`) -- `**` - nadomesti poljubno število poljubnih znakov, vključno z `/` (tj. lahko se išče na več ravneh) -- `?` - replaces one arbitrary character (except `/`) -- `[a-z]` - nadomesti en znak s seznama znakov v oglatih oklepajih -- `[!a-z]` - nadomesti en znak zunaj seznama znakov v oglatih oklepajih +- `*` - nadomešča poljubno število poljubnih znakov (razen `/`) +- `**` - nadomešča poljubno število poljubnih znakov, vključno z `/` (tj. mogoče je iskati večnivojsko) +- `?` - nadomešča en poljuben znak (razen `/`) +- `[a-z]` - nadomešča en znak s seznama znakov v oglatih oklepajih +- `[!a-z]` - nadomešča en znak izven seznama znakov v oglatih oklepajih Primeri uporabe: - `img/?.png` - datoteke z enočrkovnim imenom `0.png`, `1.png`, `x.png`, itd. -- `logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log` - dnevniške datoteke v obliki `YYYY-MM-DD` -- `src/**/tests/*` - datoteke v imeniku `src/tests`, `src/foo/tests`, `src/foo/bar/tests` itd. -- `docs/**.md` - vse datoteke s končnico `.md` v vseh podimenikih imenika. `docs` +- `logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log` - dnevniki v formatu `YYYY-MM-DD` +- `src/**/tests/*` - datoteke v imenikih `src/tests`, `src/foo/tests`, `src/foo/bar/tests` in tako naprej. +- `docs/**.md` - vse datoteke s končnico `.md` v vseh podimenikih imenika `docs` -Z izjemo .[#toc-excluding] --------------------------- +Izključitev +----------- -Za izključitev datotek in imenikov iz iskanja uporabite metodo `exclude()`. Določite masko, ki ji datoteka ne sme ustrezati. Primer iskanja datotek `*.txt`, razen tistih, ki v imenu vsebujejo črko `X`: +Z metodo `exclude()` je mogoče izključiti datoteke in imenike iz iskanja. Navedete masko, ki ji datoteka ne sme ustrezati. Primer iskanja datotek `*.txt`, razen tistih, ki v imenu vsebujejo črko `X`: ```php Finder::findFiles('*.txt') ->exclude('*X*'); ``` -Uporabite `exclude()`, če želite preskočiti pregledane podimenike: +Za izpustitev pregledanih podimenikov uporabite `exclude()`: ```php Finder::findFiles('*.php') @@ -127,12 +128,12 @@ Finder::findFiles('*.php') ``` -Filtriranje .[#toc-filtering] ------------------------------ +Filtriranje +----------- -Iskalnik ponuja več načinov za filtriranje rezultatov (tj. njihovo zmanjševanje). Te metode lahko kombinirate in jih večkrat prikličete. +Finder ponuja več metod za filtriranje rezultatov (tj. njihovo zmanjšanje). Lahko jih kombinirate in kličete večkrat. -Za filtriranje po velikosti datoteke uporabite `size()`. Na ta način poiščemo datoteke z velikostjo med 100 in 200 bajti: +Z `size()` filtriramo glede na velikost datoteke. Tako najdemo datoteke z velikostjo v območju od 100 do 200 bajtov: ```php Finder::findFiles('*.php') @@ -140,7 +141,7 @@ Finder::findFiles('*.php') ->size('<=', 200); ``` -Metoda `date()` filtrira po datumu zadnje spremembe datoteke. Vrednosti so lahko absolutne ali relativne glede na trenutni datum in čas, tako na primer najdemo datoteke, spremenjene v zadnjih dveh tednih: +Metoda `date()` filtrira glede na datum zadnje spremembe datoteke. Vrednosti so lahko absolutne ali relativne glede na trenutni datum in čas, na primer, tako najdemo datoteke, spremenjene v zadnjih dveh tednih: ```php Finder::findFiles('*.php') @@ -150,9 +151,9 @@ Finder::findFiles('*.php') Obe funkciji razumeta operatorje `>`, `>=`, `<`, `<=`, `=`, `!=`, `<>`. -Iskalnik omogoča tudi filtriranje rezultatov z uporabo funkcij po meri. Funkcija prejme kot parameter objekt `Nette\Utils\FileInfo` in mora vrniti `true`, da se datoteka vključi v rezultate. +Finder omogoča tudi filtriranje rezultatov z uporabo lastnih funkcij. Funkcija prejme kot parameter objekt `Nette\Utils\FileInfo` in mora vrniti `true`, da se datoteka vključi v rezultate. -Primer: poiščite datoteke PHP, ki vsebujejo niz `Nette` (brez upoštevanja velikih in malih črk): +Primer: iskanje PHP datotek, ki vsebujejo niz `Nette` (ne glede na velikost črk): ```php Finder::findFiles('*.php') @@ -160,51 +161,51 @@ Finder::findFiles('*.php') ``` -Globinsko filtriranje .[#toc-depth-filtering] ---------------------------------------------- +Globinsko filtriranje +--------------------- -Pri rekurzivnem iskanju lahko največjo globino iskanja nastavite z metodo `limitDepth()`. Če nastavite `limitDepth(1)`, se prečešejo samo prvi podimeniki, `limitDepth(0)` onemogoči globinsko pregledovanje, vrednost -1 pa omejitev prekliče. +Pri rekurzivnem iskanju lahko nastavite največjo globino brskanja z metodo `limitDepth()`. Če nastavite `limitDepth(1)`, se preiskujejo samo prvi podimeniki, `limitDepth(0)` izklopi globinsko brskanje in vrednost -1 prekliče omejitev. -Iskalnik omogoča uporabo lastnih funkcij za odločanje o tem, v kateri imenik naj vstopite pri brskanju. Funkcija prejme kot parameter objekt `Nette\Utils\FileInfo` in mora vrniti `true`, da vstopi v imenik: +Finder omogoča z uporabo lastnih funkcij odločanje, v kateri imenik naj se vstopi med brskanjem. Funkcija prejme kot parameter objekt `Nette\Utils\FileInfo` in mora vrniti `true`, da se vstopi v imenik: ```php Finder::findFiles('*.php') - ->descentFilter($file->getBasename() !== 'temp'); + ->descentFilter(fn($file) => $file->getBasename() !== 'temp'); ``` -Razvrščanje .[#toc-sorting] ---------------------------- +Razvrščanje +----------- -Iskalnik ponuja tudi več funkcij za razvrščanje rezultatov. +Finder ponuja tudi več funkcij za razvrščanje rezultatov. -Metoda `sortByName()` razvršča rezultate po imenu datoteke. Sortiranje je naravno, tj. pravilno obravnava številke v imenih in vrne npr. `foo1.txt` pred `foo10.txt`. +Metoda `sortByName()` razvršča rezultate po imenih datotek. Razvrščanje je naravno, torej pravilno obravnava števila v imenih in vrača npr. `foo1.txt` pred `foo10.txt`. -Iskalnik omogoča tudi razvrščanje z uporabo funkcije po meri. Ta kot parametra sprejme dva predmeta `Nette\Utils\FileInfo` in mora vrniti rezultat primerjave z operatorjem `<=>`, tj. `-1`, `0` nebo `1`. Tako na primer razvrstimo datoteke po velikosti: +Finder omogoča tudi razvrščanje z uporabo lastne funkcije. Ta prejme kot parameter dva objekta `Nette\Utils\FileInfo` in mora vrniti rezultat primerjave z operatorjem `<=>`, torej `-1`, `0` ali `1`. Na primer, tako razvrstimo datoteke po velikosti: ```php $finder->sortBy(fn($a, $b) => $a->getSize() <=> $b->getSize()); ``` -Več različnih iskanj .[#toc-multiple-different-searches] --------------------------------------------------------- +Več različnih iskanj +-------------------- -Če morate poiskati več različnih datotek na različnih lokacijah ali ki izpolnjujejo različna merila, uporabite metodo `append()`. Ta vrne nov objekt `Finder`, tako da lahko verižno kličete metode: +Če morate najti več različnih datotek na različnih lokacijah ali ki izpolnjujejo druga merila, uporabite metodo `append()`. Vrne nov objekt `Finder`, zato je mogoče verižiti klice metod: ```php -($finder = new Finder) // shranite prvi iskalnik v spremenljivko $finder! - ->files('*.php') // iskanje datotek *.php v src/ +($finder = new Finder) // v spremenljivko $finder shranimo prvi Finder! + ->files('*.php') // v src/ iščemo datoteke *.php ->from('src') ->append() - ->files('*.md') // v docs/ poiščite datoteke *.md + ->files('*.md') // v docs/ iščemo datoteke *.md ->from('docs') ->append() - ->files('*.json'); // v trenutni mapi poiščite datoteke *.json + ->files('*.json'); // v trenutni mapi iščemo datoteke *.json ``` -Lahko pa uporabite tudi metodo `append()` za dodajanje določene datoteke (ali niza datotek). Nato vrne isti predmet `Finder`: +Alternativno lahko uporabite metodo `append()` za dodajanje določene datoteke (ali polja datotek). Nato vrne isti objekt `Finder`: ```php $finder = Finder::findFiles('*.txt') @@ -212,12 +213,12 @@ $finder = Finder::findFiles('*.txt') ``` -FileInfo .[#toc-fileinfo] -------------------------- +FileInfo +-------- -[Nette\Utils\FileInfo |api:] je razred, ki predstavlja datoteko ali imenik v rezultatih iskanja. Je razširitev razreda [SplFileInfo |php:SplFileInfo], ki zagotavlja informacije, kot so velikost datoteke, datum zadnje spremembe, ime, pot itd. +[Nette\Utils\FileInfo |api:] je razred, ki predstavlja datoteko ali imenik v rezultatih iskanja. Gre za razširitev razreda [SplFileInfo |php:SplFileInfo], ki zagotavlja informacije, kot so velikost datoteke, datum zadnje spremembe, ime, pot itd. -Poleg tega zagotavlja metode za vračanje relativnih poti, kar je uporabno pri poglobljenem iskanju: +Poleg tega ponuja metode za vračanje relativne poti, kar je uporabno pri globinskem brskanju: ```php foreach (Finder::findFiles('*.jpg')->from('.') as $file) { @@ -226,7 +227,7 @@ foreach (Finder::findFiles('*.jpg')->from('.') as $file) { } ``` -Na voljo so tudi metode za branje in pisanje vsebine datoteke: +Nadalje imate na voljo metode za branje in pisanje vsebine datoteke: ```php foreach ($finder as $file) { @@ -237,12 +238,12 @@ foreach ($finder as $file) { ``` -Vračanje rezultatov v obliki polja .[#toc-returning-results-as-an-array] ------------------------------------------------------------------------- +Vračanje rezultatov kot polje +----------------------------- -Kot je razvidno iz primerov, iskalnik implementira vmesnik `IteratorAggregate`, zato lahko za brskanje po rezultatih uporabite `foreach`. Programiran je tako, da se rezultati nalagajo šele med brskanjem, tako da če imate veliko število datotek, ne čaka, da se vse preberejo. +Kot je bilo videti v primerih, Finder implementira vmesnik `IteratorAggregate`, zato lahko uporabite `foreach` za pregledovanje rezultatov. Programiran je tako, da se rezultati nalagajo samo med pregledovanjem, tako da če imate veliko število datotek, ni treba čakati, da se vse preberejo. -Rezultate lahko vrnete tudi kot polje predmetov `Nette\Utils\FileInfo` z uporabo metode `collect()`. Polje ni asociativno, temveč številsko. +Rezultate lahko dobite tudi kot polje objektov `Nette\Utils\FileInfo` z metodo `collect()`. Polje ni asociativno, ampak numerično. ```php $array = $finder->findFiles('*.php')->collect(); diff --git a/utils/sl/floats.texy b/utils/sl/floats.texy index 6832099a97..4398f59e9e 100644 --- a/utils/sl/floats.texy +++ b/utils/sl/floats.texy @@ -1,8 +1,8 @@ -Funkcije Floats -*************** +Delo s plavajočimi števili (floats) +*********************************** .[perex] -[api:Nette\Utils\Floats] je statični razred z uporabnimi funkcijami za primerjavo števil float. +[api:Nette\Utils\Floats] je statični razred z uporabnimi funkcijami za primerjavo decimalnih števil. Namestitev: @@ -11,18 +11,17 @@ Namestitev: composer require nette/utils ``` -Vsi primeri predpostavljajo, da je definiran naslednji vzdevek razreda: +Vsi primeri predpostavljajo ustvarjen vzdevek: ```php use Nette\Utils\Floats; ``` -Motivacija .[#toc-motivation] -============================= +Motivacija +========== -Se sprašujete, čemu je namenjen razred za primerjavo float? Uporabite lahko operatorje `<`, `>`, `===`, si mislite. -To ni povsem res. Kaj mislite, da se bo izpisala ta koda? +Se sprašujete, zakaj pravzaprav razred za primerjavo floatov? Saj lahko uporabim operatorje `<`, `>`, `===` in sem končal. Ni povsem res. Kaj mislite, da bo izpisala ta koda? ```php $a = 0.1 + 0.2; @@ -30,21 +29,24 @@ $b = 0.3; echo $a === $b ? 'same' : 'not same'; ``` -Če boste zagnali kodo, boste nekateri presenečeni, da je program natisnil `not same`. +Če zaženete kodo, bodo nekateri med vami zagotovo presenečeni, da je program izpisal `not same`. -Matematične operacije s float števili povzročajo napake zaradi pretvorbe med desetiškim in dvojiškim sistemom. Na primer `0.1 + 0.2` je enako `0.300000000000000044…`. Zato moramo pri primerjanju plavajočih števil dopuščati majhno razliko od določenega decimalnega mesta. +Pri matematičnih operacijah z decimalnimi števili prihaja do napak zaradi pretvorbe med desetiškim in dvojiškim sistemom. Na primer, `0.1 + 0.2` je `0.300000000000000044…`. Zato moramo pri primerjavi tolerirati majhno razliko od določenega decimalnega mesta. -In to počne razred `Floats`. Naslednja primerjava bo delovala po pričakovanjih: +In prav to počne razred `Floats`. Naslednja primerjava bo delovala po pričakovanjih: ```php -echo Floats::areEqual($a, $b) ? 'same': 'not same'; // enako +echo Floats::areEqual($a, $b) ? 'same' : 'not same'; // enako ``` -Ko poskuša primerjati `NAN`, pa vrže izjemo `\LogicException`. +Pri poskusu primerjave `NAN` sproži izjemo `\LogicException`. +.[tip] +Razred `Floats` tolerira razlike, manjše od `1e-10`. Če morate delati z večjo natančnostjo, raje uporabite knjižnico BCMath. -Primerjava floatov .[#toc-float-comparison] -=========================================== + +Primerjava floatov +================== areEqual(float $a, float $b): bool .[method] @@ -60,7 +62,7 @@ Floats::areEqual(10, 10.0); // true isLessThan(float $a, float $b): bool .[method] ---------------------------------------------- -Vrne `true`, če je `$a` < `$b`. +Vrne `true`, če velja `$a` < `$b`. ```php Floats::isLessThan(9.5, 10.2); // true @@ -71,7 +73,7 @@ Floats::isLessThan(INF, 10.2); // false isLessThanOrEqualTo(float $a, float $b): bool .[method] ------------------------------------------------------- -Vrne `true`, če `$a` <= `$b`. +Vrne `true`, če velja `$a` <= `$b`. ```php Floats::isLessThanOrEqualTo(9.5, 10.2); // true @@ -82,7 +84,7 @@ Floats::isLessThanOrEqualTo(10.25, 10.25); // true isGreaterThan(float $a, float $b): bool .[method] ------------------------------------------------- -Vrne `true`, če je `$a` > `$b`. +Vrne `true`, če velja `$a` > `$b`. ```php Floats::isGreaterThan(9.5, -10.2); // true @@ -93,7 +95,7 @@ Floats::isGreaterThan(9.5, 10.2); // false isGreaterThanOrEqualTo(float $a, float $b): bool .[method] ---------------------------------------------------------- -Vrne `true`, če `$a` >= `$b`. +Vrne `true`, če velja `$a` >= `$b`. ```php Floats::isGreaterThanOrEqualTo(9.5, 10.2); // false @@ -104,19 +106,19 @@ Floats::isGreaterThanOrEqualTo(10.2, 10.2); // true compare(float $a, float $b): int .[method] ------------------------------------------ -Če je `$a` < `$b`, vrne `-1`, če sta enaka, vrne `0` and if `$a` > `$b` vrne `1`. +Če je `$a` < `$b`, vrne `-1`, če sta enaka, vrne `0`, in če je `$a` > `$b`, vrne `1`. -Uporablja se lahko na primer s funkcijo `usort`. +Lahko se uporablja na primer s funkcijo `usort`. ```php $arr = [1, 5, 2, -3.5]; -usort($arr, [Float::class, 'compare']); -// $arr is [-3.5, 1, 2, 5] +usort($arr, [Floats::class, 'compare']); +// $arr je zdaj [-3.5, 1, 2, 5] ``` -Pomožne funkcije .[#toc-helpers-functions] -========================================== +Pomožne funkcije +================ isZero(float $value): bool .[method] diff --git a/utils/sl/helpers.texy b/utils/sl/helpers.texy index e65379a074..820eaa3b43 100644 --- a/utils/sl/helpers.texy +++ b/utils/sl/helpers.texy @@ -2,7 +2,7 @@ Pomožne funkcije **************** .[perex] -[api:Nette\Utils\Helpers] je statični razred s koristnimi funkcijami. +[api:Nette\Utils\Helpers] je statični razred z uporabnimi funkcijami. Namestitev: @@ -11,7 +11,7 @@ Namestitev: composer require nette/utils ``` -Vsi primeri predpostavljajo, da je definiran naslednji vzdevek razreda: +Vsi primeri predpostavljajo ustvarjen vzdevek: ```php use Nette\Utils\Helpers; @@ -21,7 +21,7 @@ use Nette\Utils\Helpers; capture(callable $cb): string .[method] --------------------------------------- -Izvede povratni klic in vrne zajeti izhod kot niz. +Izvede povratni klic (callback) in vrne zajeti izpis kot niz. ```php $res = Helpers::capture(function () use ($template) { @@ -33,7 +33,7 @@ $res = Helpers::capture(function () use ($template) { clamp(int|float $value, int|float $min, int|float $max): int|float .[method] ---------------------------------------------------------------------------- -Vrne vrednost, ki je vpeta v vključujoče območje min in max. +Omeji vrednost na dano inkluzivno območje min in max. ```php Helpers::clamp($level, 0, 255); @@ -43,8 +43,7 @@ Helpers::clamp($level, 0, 255); compare(mixed $left, string $operator, mixed $right): bool .[method] -------------------------------------------------------------------- -Primerja dve vrednosti na enak način kot PHP. Razlikuje med operatorji `>`, `>=`, `<`, `<=`, `=`, `==`, `===`, `!=`, `!==`, `<>`. -Funkcija je uporabna v primerih, ko je operator spremenljiv. +Primerja dve vrednosti na enak način, kot to počne PHP. Razlikuje operatorje `>`, `>=`, `<`, `<=`, `=`, `==`, `===`, `!=`, `!==`, `<>`. Funkcija je uporabna v situacijah, ko je operator spremenljiv. ```php Helpers::compare(10, '<', 20); // true @@ -54,7 +53,7 @@ Helpers::compare(10, '<', 20); // true falseToNull(mixed $value): mixed .[method] ------------------------------------------ -Pretvori `false` v `null`, drugih vrednosti ne spremeni. +Pretvori `false` v `null`, drugih vrednosti ne spreminja. ```php Helpers::falseToNull(false); // null @@ -65,7 +64,7 @@ Helpers::falseToNull(123); // 123 getLastError(): string .[method] -------------------------------- -Vrne zadnjo nastalo napako PHP ali prazen niz, če do napake ni prišlo. Za razliko od `error_get_last()`, direktiva PHP `html_errors` nanj ne vpliva in vedno vrne besedilo in ne HTML. +Vrne zadnjo napako v PHP ali prazen niz, če ni prišlo do napake. V primerjavi z `error_get_last()` ni pod vplivom PHP direktive `html_errors` in vedno vrne besedilo, ne HTML. ```php Helpers::getLastError(); @@ -75,9 +74,9 @@ Helpers::getLastError(); getSuggestion(string[] $possibilities, string $value): ?string .[method] ------------------------------------------------------------------------ -Poišče niz iz `$possibilities`, ki je najbolj podoben `$value`, vendar ni enak. Podpira samo 8-bitna kodiranja. +Iz ponujenih možnosti `$possibilities` išče niz, ki je najbolj podoben `$value`, vendar ni enak. Podpira samo 8-bitno kodiranje. -Uporaben je, če določena možnost ni veljavna in želimo uporabniku predlagati podobno možnost (vendar drugačno, zato enakega niza ne upoštevamo). Na ta način Nette ustvari sporočila `did you mean ...?`. +Uporabno je v primeru, ko določena izbira ni veljavna in želimo uporabniku svetovati podobno (vendar drugačno, zato se enak niz ignorira). Na ta način Nette ustvarja sporočila `did you mean ...?`. ```php $items = ['foo', 'bar', 'baz']; diff --git a/utils/sl/html-elements.texy b/utils/sl/html-elements.texy index ed97eb4f06..8413b484d8 100644 --- a/utils/sl/html-elements.texy +++ b/utils/sl/html-elements.texy @@ -1,11 +1,11 @@ -Elementi HTML +HTML elementi ************* .[perex] -Razred [api:Nette\Utils\Html] je pomočnik za generiranje kode HTML, ki preprečuje ranljivost XSS (Cross Site Scripting). +Razred [api:Nette\Utils\Html] je pomočnik za generiranje HTML kode, ki preprečuje nastanek ranljivosti Cross Site Scripting (XSS). -Deluje tako, da njegovi predmeti predstavljajo elemente HTML, nastavimo njihove parametre in jih pustimo, da se izrišejo: +Deluje tako, da njegovi objekti predstavljajo HTML elemente, katerim nastavimo parametre in jih pustimo izrisati: ```php $el = Html::el('img'); // ustvari element @@ -19,29 +19,29 @@ Namestitev: composer require nette/utils ``` -Vsi primeri predpostavljajo, da je definiran naslednji vzdevek razreda: +Vsi primeri predpostavljajo ustvarjen vzdevek: ```php use Nette\Utils\Html; ``` -Ustvarjanje elementa HTML .[#toc-creating-an-html-element] -========================================================== +Ustvarjanje HTML elementa +========================= -Element je ustvarjen z metodo `Html::el()`: +Element ustvarimo z metodo `Html::el()`: ```php $el = Html::el('img'); // ustvari element ``` -Poleg imena lahko v sintaksi HTML vpišete tudi druge atribute: +Poleg imena lahko navedete tudi druge atribute v HTML sintaksi: ```php $el = Html::el('input type=text class="red important"'); ``` -Lahko pa jih posredujete kot asociativno polje v drugem parametru: +Ali pa jih posredujete kot asociativno polje kot drugi parameter: ```php $el = Html::el('input', [ @@ -50,7 +50,7 @@ $el = Html::el('input', [ ]); ``` -Če želite spremeniti in vrniti ime elementa: +Sprememba in vrnitev imena elementa: ```php $el->setName('img'); @@ -59,30 +59,30 @@ $el->isEmpty(); // true, ker je prazen element ``` -Atributi HTML .[#toc-html-attributes] -===================================== +HTML atributi +============= -Posamezne atribute HTML lahko nastavite in pridobite na tri načine, od vas pa je odvisno, kateri vam je bolj všeč. Prvi je prek lastnosti: +Posamezne HTML atribute lahko spreminjamo in beremo na tri načine, odvisno od vas, kateri vam bo bolj všeč. Prvi je preko lastnosti (properties): ```php $el->src = 'image.jpg'; // nastavi atribut src echo $el->src; // 'image.jpg' -unset($el->src); // odstrani atribut +unset($el->src); // prekliče atribut // ali $el->src = null; ``` -Drugi način je klicanje metod, ki jih lahko v nasprotju z nastavljanjem lastnosti verižno povežemo: +Drugi način je klicanje metod, ki jih za razliko od nastavljanja lastnosti lahko verižimo: ```php $el = Html::el('img')->src('image.jpg')->alt('photo'); // photo -$el->alt(null); // odstrani atribut +$el->alt(null); // preklic atributa ``` -Tretji način je najbolj zgovoren: +In tretji način je najbolj zgovoren: ```php $el = Html::el('img') @@ -94,9 +94,9 @@ echo $el->getAttribute('src'); // 'image.jpg' $el->removeAttribute('alt'); ``` -Atribute je mogoče množično nastaviti s `addAttributes(array $attrs)` in izbrisati s `removeAttributes(array $attrNames)`. +Množično lahko atribute nastavite z `addAttributes(array $attrs)` in odstranite z `removeAttributes(array $attrNames)`. -Vrednost atributa ni nujno samo niz, za logične atribute lahko uporabite tudi logične vrednosti: +Vrednost atributa ni nujno samo niz, lahko uporabljate tudi logične vrednosti za logične atribute: ```php $checkbox = Html::el('input')->type('checkbox'); @@ -104,17 +104,17 @@ $checkbox->checked = true; // $checkbox->checked = false; // ``` -Atribut je lahko tudi polje žetonov, ki so na seznamu ločeni s presledki, kar je primerno na primer za razrede CSS: +Atribut je lahko tudi polje vrednosti, ki se izpišejo ločene s presledki, kar je uporabno na primer za CSS razrede: ```php $el = Html::el('input'); $el->class[] = 'active'; -$el->class[] = null; // ničla se ne upošteva +$el->class[] = null; // null se ignorira $el->class[] = 'top'; echo $el; // '' ``` -Druga možnost je asociativno polje, kjer vrednosti povedo, ali naj bo ključ naveden: +Alternativa je asociativno polje, kjer vrednosti povedo, ali naj se ključ izpiše: ```php $el = Html::el('input'); @@ -123,7 +123,7 @@ $el->class['top'] = false; echo $el; // '' ``` -sloge CSS lahko zapišemo v obliki asociativnih polj: +CSS stile je mogoče zapisati v obliki asociativnih polj: ```php $el = Html::el('input'); @@ -132,7 +132,7 @@ $el->style['display'] = 'block'; echo $el; // '' ``` -Uporabili smo lastnosti, enako pa lahko storimo tudi z metodami: +Zdaj smo uporabljali lastnosti, vendar se enako lahko zapiše z metodami: ```php $el = Html::el('input'); @@ -141,7 +141,7 @@ $el->style('display', 'block'); echo $el; // '' ``` -Ali celo na najbolj pogovoren način: +Ali celo na najbolj zgovoren način: ```php $el = Html::el('input'); @@ -150,7 +150,7 @@ $el->appendAttribute('style', 'display', 'block'); echo $el; // '' ``` -Še nekaj: metoda `href()` lahko olajša sestavljanje parametrov poizvedbe v naslovu URL: +Še majhna podrobnost za konec: metoda `href()` lahko olajša sestavljanje query parametrov v URL: ```php echo Html::el('a')->href('index.php', [ @@ -161,15 +161,15 @@ echo Html::el('a')->href('index.php', [ ``` -Podatkovni atributi .[#toc-data-attributes] -------------------------------------------- +Data atributi +------------- -Podatkovni atributi imajo posebno podporo. Ker njihova imena vsebujejo pomišljaje, dostop prek lastnosti in metod ni tako eleganten, zato je na voljo metoda `data()`: +Posebno podporo imajo podatkovni atributi (data attributes). Ker njihova imena vsebujejo vezaje, dostop preko lastnosti in metod ni tako eleganten, zato obstaja metoda `data()`: ```php $el = Html::el('input'); -$el->{'data-max-size'} = '500x300'; // ne tako elegantno -$el->data('max-size', '500x300'); // je eleganten +$el->{'data-max-size'} = '500x300'; // ni tako elegantno +$el->data('max-size', '500x300'); // je elegantno echo $el; // '' ``` @@ -182,10 +182,10 @@ echo $el; // '' ``` -Vsebina elementa .[#toc-element-content] -======================================== +Vsebina elementa +================ -Notranja vsebina elementa se nastavi z metodama `setHtml()` ali `setText()`. Prvo metodo uporabite le, če veste, da v parametru zanesljivo posredujete varen niz HTML. +Notranjo vsebino elementa nastavimo z metodama `setHtml()` ali `setText()`. Prvo uporabite samo v primeru, da veste, da v parametru posredujete zanesljivo varen HTML niz. ```php echo Html::el('span')->setHtml('hello
                                                                                                                            '); @@ -195,7 +195,7 @@ echo Html::el('span')->setText('10 < 20'); // '10 < 20' ``` -Nasprotno pa notranjo vsebino pridobimo z metodama `getHtml()` ali `getText()`. Druga odstrani oznake iz izpisa HTML in pretvori entitete HTML v znake. +In obratno, notranjo vsebino dobimo z metodama `getHtml()` ali `getText()`. Druga odstrani iz izpisa HTML oznake in HTML entitete pretvori v znake. ```php echo $el->getHtml(); // '10 < 20' @@ -203,10 +203,10 @@ echo $el->getText(); // '10 < 20' ``` -Podrejena vozlišča .[#toc-child-nodes] --------------------------------------- +Podrejeni vozli +--------------- -Notranja vsebina elementa je lahko tudi polje otrok. Vsak od njih je lahko niz ali drug element `Html`. Vstavljamo jih z uporabo `addHtml()` ali `addText()`: +Notranjost elementa je lahko tudi polje podrejenih (children) vozlov. Vsak izmed njih je lahko bodisi niz ali drug `Html` element. Vstavljamo jih z `addHtml()` ali `addText()`: ```php $el = Html::el('span') @@ -216,16 +216,16 @@ $el = Html::el('span') // hello
                                                                                                                            10 < 20
                                                                                                                            ``` -Drug način za ustvarjanje in vstavljanje novega vozlišča `Html`: +Drug način za ustvarjanje in vstavljanje novega `Html` vozla: ```php -$el = Html::el('ul') - ->create('li', ['class' => 'first']) - ->setText('hello'); -//
                                                                                                                            • hello
                                                                                                                            +$ul = Html::el('ul'); +$ul->create('li', ['class' => 'first']) + ->setText('prvi'); +//
                                                                                                                            • prvi
                                                                                                                            ``` -Z vozlišči lahko delate, kot da so elementi polja. Tako do posameznih dostopate z uporabo oglatih oklepajev, jih preštejete s `count()` in iterirate nad njimi: +Z vozli lahko delamo enako, kot da bi šlo za polje. Torej dostopati do posameznih z oglatimi oklepaji, jih prešteti z `count()` in iterirati nad njimi: ```php $el = Html::el('div'); @@ -238,20 +238,20 @@ foreach ($el as $child) { /* ... */ } echo count($el); // 2 ``` -Novo vozlišče lahko vstavite na določeno mesto z uporabo `insert(?int $index, $child, bool $replace = false)`. Če je `$replace = false`, vstavi element na mesto `$index` in premakne ostale. Če je `$index = null`, se element doda na konec. +Nov vozel lahko vstavite na določeno mesto z `insert(?int $index, $child, bool $replace = false)`. Če je `$replace = false`, vstavi element na pozicijo `$index` in ostale premakne. Če je `$index = null`, doda element na konec. ```php -// vstavi element v prvi položaj in premakne druge +// vstavi element na prvo pozicijo in ostale premakne $el->insert(0, Html::el('span')); ``` -Vsa vozlišča se vrnejo z metodo `getChildren()` in odstranijo z metodo `removeChildren()`. +Vse vozle dobimo z metodo `getChildren()` in jih odstranimo z metodo `removeChildren()`. -Ustvarjanje fragmenta dokumenta .[#toc-creating-a-document-fragment] --------------------------------------------------------------------- +Ustvarjanje fragmenta dokumenta +------------------------------- -Če želite delati z nizom vozlišč in vas zavijalni element ne zanima, lahko ustvarite tako imenovani *dokumentni fragment* tako, da namesto imena elementa posredujete `null`: +Če želimo delati s poljem vozlov in nas ne zanima ovojni element, lahko ustvarimo t.i. *document fragment* s posredovanjem `null` namesto imena elementa: ```php $el = Html::el(null) @@ -261,7 +261,7 @@ $el = Html::el(null) // hello
                                                                                                                            10 < 20
                                                                                                                            ``` -Metodi `fromHtml()` in `fromText()` ponujata hitrejši način ustvarjanja fragmenta: +Hitrejši način ustvarjanja fragmenta ponujata metodi `fromHtml()` in `fromText()`: ```php $el = Html::fromHtml('hello
                                                                                                                            '); @@ -272,15 +272,15 @@ echo $el; // '10 < 20' ``` -Ustvarjanje izpisa HTML .[#toc-generating-html-output] -====================================================== +Generiranje HTML izpisa +======================= -Element HTML najlažje generirate tako, da uporabite `echo` ali pa predmet oddate v `(string)`. Ločeno lahko natisnete tudi odpiralne ali zapiralne oznake in atribute: +Najenostavnejši način za izpis HTML elementa je uporaba `echo` ali pretvorba objekta v `(string)`. Lahko tudi ločeno izpišete odpiralne ali zapiralne oznake in atribute: ```php $el = Html::el('div class=header')->setText('hello'); -echo $el; // '
                                                                                                                            ' +echo $el; // '
                                                                                                                            hello
                                                                                                                            ' $s = (string) $el; // '
                                                                                                                            hello
                                                                                                                            ' $s = $el->toHtml(); // '
                                                                                                                            hello
                                                                                                                            ' $s = $el->toText(); // 'hello' @@ -289,7 +289,7 @@ echo $el->endTag(); // '' echo $el->attributes(); // 'class="header"' ``` -Pomembna lastnost je samodejna zaščita pred [križanjem spletnih strani (XSS) |nette:glossary#cross-site-scripting-xss]. Vse vrednosti atributov ali vsebine, vstavljene z uporabo `setText()` ali `addText()`, so zanesljivo izločene: +Pomembna značilnost je samodejna zaščita pred [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS]. Vse vrednosti atributov ali vsebina, vstavljena preko `setText()` ali `addText()`, se zanesljivo ubežijo (escape): ```php echo Html::el('div') @@ -300,8 +300,8 @@ echo Html::el('div') ``` -Pretvorba HTML ↔ Besedilo .[#toc-conversion-html-text] -====================================================== +Pretvorba HTML ↔ besedilo +========================= Za pretvorbo HTML v besedilo lahko uporabite statično metodo `htmlToText()`: @@ -310,7 +310,7 @@ echo Html::htmlToText('One & Two'); // 'One & Two' ``` -HtmlStringable .[#toc-htmlstringable] -===================================== +HtmlStringable +============== -Predmet `Nette\Utils\Html` implementira vmesnik `Nette\HtmlStringable`, ki ga na primer Latte ali obrazci uporabljajo za razlikovanje predmetov, ki imajo metodo `__toString()`, ki vrača kodo HTML. Tako do dvojnega izrivanja ne pride, če na primer objekt v predlogi izpišemo z uporabo `{$el}`. +Objekt `Nette\Utils\Html` implementira vmesnik `Nette\HtmlStringable`, s katerim na primer Latte ali obrazci razlikujejo objekte, ki imajo metodo `__toString()`, ki vrača HTML kodo. Tako ne pride do dvojnega ubežanja (escaping), če na primer objekt izpišemo v predlogi z `{$el}`. diff --git a/utils/sl/images.texy b/utils/sl/images.texy index 88c5d52f1f..67232457d3 100644 --- a/utils/sl/images.texy +++ b/utils/sl/images.texy @@ -1,11 +1,11 @@ -Slikovne funkcije -***************** +Delo s slikami +************** .[perex] -Razred [api:Nette\Utils\Image] poenostavlja manipulacijo slik, kot so spreminjanje velikosti, obrezovanje, ostrenje, risanje ali združevanje več slik. +Razred [api:Nette\Utils\Image] poenostavlja manipulacijo s slikami, kot je spreminjanje velikosti, obrezovanje, ostrenje, risanje ali združevanje več slik. -PHP ima obsežen nabor funkcij za obdelavo slik. Vendar pa vmesnik API ni preveč prijeten. Ne bi bil Neat Framework, če bi pripravil seksi API. +PHP ima obsežen nabor funkcij za manipulacijo s slikami. Vendar njihov API ni zelo priročen. To ne bi bil Nette Framework, če ne bi ponudil seksi API-ja. Namestitev: @@ -13,157 +13,168 @@ Namestitev: composer require nette/utils ``` -Naslednji primeri predpostavljajo, da je definiran naslednji vzdevek razreda: +Vsi primeri predpostavljajo ustvarjen vzdevek: ```php use Nette\Utils\Image; +use Nette\Utils\ImageColor; +use Nette\Utils\ImageType; ``` -Ustvarjanje slike .[#toc-creating-an-image] -=========================================== +Ustvarjanje slike +================= -Ustvarili bomo novo pravo barvno sliko, na primer z dimenzijami 100 × 200: +Ustvarimo novo true color sliko, na primer z dimenzijami 100×200: ```php $image = Image::fromBlank(100, 200); ``` -Po želji lahko določite barvo ozadja (privzeto je črna): +Izbirno lahko določimo barvo ozadja (privzeta je črna): ```php -$image = Image::fromBlank(100, 200, Image::rgb(125, 0, 0)); +$image = Image::fromBlank(100, 200, ImageColor::rgb(125, 0, 0)); ``` -Sliko lahko naložimo iz datoteke: +Ali pa sliko naložimo iz datoteke: ```php $image = Image::fromFile('nette.jpg'); ``` -Podprti formati so JPEG, PNG, GIF, WebP, AVIF in BMP, vendar jih mora podpirati tudi vaša različica PHP (preverite `phpinfo()`, oddelek GD). Animacije niso podprte. -Potrebujete zaznati format slike pri nalaganju? Metoda vrne format v drugem parametru: +Shranjevanje slike +================== + +Sliko lahko shranimo v datoteko: ```php -$image = Image::fromFile('nette.jpg', $type); -// $type je Image::JPEG, Image::PNG, Image::GIF, Image::WEBP, Image::AVIF ali Image::BMP +$image->save('resampled.jpg'); ``` -Samo zaznavanje brez nalaganja slike se izvede s spletno stranjo `Image::detectTypeFromFile()`. +Določimo lahko kakovost stiskanja v obsegu 0..100 za JPEG (privzeto 85), WEBP (privzeto 80) in AVIF (privzeto 30) ter 0..9 za PNG (privzeto 9): +```php +$image->save('resampled.jpg', 80); // JPEG, kakovost 80% +``` -Shranjevanje slike .[#toc-save-the-image] -========================================= - -Sliko lahko shranite v datoteko: +Če iz končnice datoteke ni razviden format, ga lahko določimo s [konstanto |#Formati]: ```php -$image->save('resampled.jpg'); +$image->save('resampled.tmp', null, ImageType::JPEG); ``` -Določimo lahko kakovost stiskanja v območju 0..100 za JPEG (privzeto 85), WEBP (privzeto 80) in AVIF (privzeto 30) ter 0..9 za PNG (privzeto 9): +Sliko lahko namesto na disk zapišemo v spremenljivko: ```php -$image->save('resampled.jpg', 80); // JPEG, kakovost 80% +$data = $image->toString(ImageType::JPEG, 80); // JPEG, kakovost 80% ``` -Če format ni razviden iz končnice datoteke, ga lahko določite z eno od konstant `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` in `Image::BMP`: +ali pa jo pošljemo neposredno v brskalnik z ustrezno HTTP glavo `Content-Type`: ```php -$image->save('resampled.tmp', null, Image::JPEG); +// pošlje glavo Content-Type: image/png +$image->send(ImageType::PNG); ``` -Sliko lahko namesto na disk zapišete v spremenljivko: + +Formati +======= + +Podprti formati so JPEG, PNG, GIF, WebP, AVIF in BMP, vendar jih mora podpirati tudi vaša različica PHP, kar preverite s funkcijo [#isTypeSupported()]. Animacije niso podprte. + +Format predstavljajo konstante `ImageType::JPEG`, `ImageType::PNG`, `ImageType::GIF`, `ImageType::WEBP`, `ImageType::AVIF` in `ImageType::BMP`. ```php -$data = $image->toString(Image::JPEG, 80); // JPEG, kakovost 80% +$supported = Image::isTypeSupported(ImageType::JPEG); ``` -ali pošljete neposredno brskalniku z ustreznim glavo HTTP `Content-Type`: +Ali morate pri nalaganju zaznati format slike? Metoda ga vrne v drugem parametru: ```php -// pošilja glavo Content-Type: image/png -$image->send(Image::PNG); +$image = Image::fromFile('nette.jpg', $type); ``` +Samo zaznavanje brez nalaganja slike izvaja `Image::detectTypeFromFile()`. -Spreminjanje velikosti slike .[#toc-image-resize] -================================================= -Pogosta operacija je spreminjanje velikosti slike. Trenutne dimenzije se vrnejo z metodama `getWidth()` in `getHeight()`. +Spreminjanje velikosti +====================== -Za spreminjanje velikosti se uporablja metoda `resize()`. To je primer sorazmerne spremembe velikosti, tako da ne presega 500 × 300 pikslov (širina bo točno 500 pikslov ali višina bo točno 300 pikslov, ena od dimenzij se izračuna za ohranitev razmerja stranic): +Pogosta operacija je spreminjanje dimenzij slike. Trenutne dimenzije vračata metodi `getWidth()` in `getHeight()`. + +Za spreminjanje služi metoda `resize()`. Primer proporcionalnega spreminjanja velikosti tako, da ne preseže dimenzij 500x300 pikslov (bodisi bo širina natančno 500 px ali pa bo višina natančno 300 px, ena od dimenzij se izračuna tako, da se ohrani razmerje stranic): ```php $image->resize(500, 300); ``` -Nastavite lahko samo eno dimenzijo, druga pa bo izračunana: +Možno je določiti samo eno dimenzijo, druga pa se izračuna: ```php -$image->resize(500, null); // širina 500px, višina auto +$image->resize(500, null); // širina 500px, višina se izračuna -$image->resize(null, 300); // širina auto, višina 300px +$image->resize(null, 300); // širina se izračuna, višina 300px ``` -Vsako dimenzijo lahko določite v odstotkih: +Katerokoli dimenzijo je mogoče navesti tudi v odstotkih: ```php -$image->resize('75%', 300); // 75% × 300px +$image->resize('75%', 300); // 75 % × 300px ``` -Na obnašanje spletne strani `resize` lahko vplivate z naslednjimi zastavicami. Vsi razen `Image::Stretch` ohranijo razmerje stranic. +Obnašanje `resize` lahko vplivamo z naslednjimi zastavicami. Vse razen `Image::Stretch` ohranjajo razmerje stranic. |--------------------------------------------------------------------------------------- -| Zastava | Opis +| Zastavica | Opis |--------------------------------------------------------------------------------------- -| `Image::OrSmaller` (privzeto) | dobljene dimenzije bodo manjše ali enake, kot je določeno -| `Image::OrBigger` | zapolni ciljno območje in ga po možnosti razširi v eno smer -| `Image::Cover` | zapolni celotno območje in obreže, kar ga presega -| `Image::ShrinkOnly` | samo zmanjša velikost (ne razširi majhne slike) -| `Image::Stretch` | ne ohrani razmerja stranic +| `Image::OrSmaller` (privzeto) | končne dimenzije bodo manjše ali enake zahtevanim dimenzijam +| `Image::OrBigger` | zapolni (in po potrebi preseže v eni dimenziji) ciljno površino +| `Image::Cover` | zapolni ciljno površino in obreže tisto, kar presega +| `Image::ShrinkOnly` | samo pomanjševanje (prepreči raztegovanje majhne slike) +| `Image::Stretch` | ne ohranjati razmerja stranic -Zastave se posredujejo kot tretji argument funkcije: +Zastavice se navedejo kot tretji argument funkcije: ```php $image->resize(500, 300, Image::OrBigger); ``` -Zastave je mogoče kombinirati: +Zastavice je mogoče kombinirati: ```php $image->resize(500, 300, Image::ShrinkOnly | Image::Stretch); ``` -Slike lahko obrnete navpično ali vodoravno tako, da eno od dimenzij (ali obe) določite kot negativno število: +Slike je mogoče navpično ali vodoravno obrniti tako, da eno od dimenzij (ali obe) navedemo kot negativno število: ```php -$flipped = $image->resize(null, '-100%'); // flip vertikalno +$flipped = $image->resize(null, '-100%'); // navpično obračanje -$flipped = $image->resize('-100%', '-100%'); // obračanje za 180° +$flipped = $image->resize('-100%', '-100%'); // zasuk za 180° -$flipped = $image->resize(-125, 500); // spreminjanje velikosti in obračanje v vodoravni smeri +$flipped = $image->resize(-125, 500); // spremeni velikost & vodoravno obračanje ``` -Ko sliko zmanjšamo, jo lahko izboljšamo z ostrenjem: +Po pomanjšanju slike je mogoče njen videz izboljšati z nežnim ostrenjem: ```php $image->sharpen(); ``` -Obrezovanje .[#toc-cropping] -============================ +Obrezovanje +=========== -Metoda `crop()` se uporablja za pridelavo: +Za obrezovanje služi metoda `crop()`: ```php $image->crop($left, $top, $width, $height); ``` -Tako kot pri `resize()` lahko vse vrednosti navedete v odstotkih. Odstotki za `$left` in `$top` so izračunani iz preostalega prostora, podobno kot pri lastnosti CSS `background-position`: +Podobno kot pri `resize()` so lahko vse vrednosti navedene v odstotkih. Odstotki pri `$left` in `$top` se izračunajo iz preostalega prostora, podobno kot pri CSS lastnosti `background-position`: ```php $image->crop('100%', '50%', '80%', '80%'); @@ -172,329 +183,359 @@ $image->crop('100%', '50%', '80%', '80%'); [* crop.svg *] -Sliko lahko tudi samodejno obrežete, npr. obrežete črne robove: +Sliko je mogoče obrezati tudi samodejno, na primer obrezovanje črnih robov: ```php $image->cropAuto(IMG_CROP_BLACK); ``` -Metoda `cropAuto()` je objektna enkapsulacija funkcije `imagecropauto()`, za več informacij si oglejte [njeno dokumentacijo |https://www.php.net/manual/en/function.imagecropauto]. +Metoda `cropAuto()` je objektna zamenjava funkcije `imagecropauto()`, v [njeni dokumentaciji |https://www.php.net/manual/en/function.imagecropauto] najdete več informacij. + + +Barve .{data-version:4.0.2} +=========================== + +Metoda `ImageColor::rgb()` vam omogoča definiranje barve z vrednostmi rdeče, zelene in modre (RGB). Izbirno lahko določite tudi vrednost prosojnosti v obsegu od 0 (popolnoma prosojno) do 1 (popolnoma neprosojno), torej enako kot v CSS. + +```php +$color = ImageColor::rgb(255, 0, 0); // Rdeča +$transparentBlue = ImageColor::rgb(0, 0, 255, 0.5); // Polprosojna modra +``` + +Metoda `ImageColor::hex()` omogoča definiranje barve s šestnajstiškim formatom, podobno kot v CSS. Podpira formate `#rgb`, `#rrggbb`, `#rgba` in `#rrggbbaa`: + +```php +$color = ImageColor::hex("#F00"); // Rdeča +$transparentGreen = ImageColor::hex("#00FF0080"); // Polprosojna zelena +``` + +Barve lahko uporabite v drugih metodah, kot so `ellipse()`, `fill()` itd. -Risanje in urejanje .[#toc-drawing-and-editing] -=============================================== +Risanje in urejanje +=================== -Lahko rišete, pišete, uporabljate vse funkcije PHP za delo s slikami, kot je [imagefilledellipse() |https://www.php.net/manual/en/function.imagefilledellipse.php], vendar v objektnem slogu: +Lahko rišeš, lahko pišeš, a listov ne trgaj. Na voljo so vam vse funkcije PHP za delo s slikami, glejte [#Pregled metod], vendar v objektnem ovoju: ```php -$image->filledEllipse($cx, $cy, $width, $height, Image::rgb(255, 0, 0, 63)); +$image->filledEllipse($centerX, $centerY, $width, $height, ImageColor::rgb(255, 0, 0)); ``` -Glejte [Pregled metod |#Overview of Methods]. +Ker so PHP funkcije za risanje pravokotnikov nepraktične zaradi določanja koordinat, razred `Image` ponuja njihove zamenjave v obliki funkcij [#rectangleWH()] in [#filledRectangleWH()]. -Združitev več slik .[#toc-merge-multiple-images] -================================================ +Združevanje več slik +==================== -V sliko lahko preprosto vstavite drugo sliko: +V sliko je mogoče enostavno vstaviti drugo sliko: ```php $logo = Image::fromFile('logo.png'); -$blank = Image::fromBlank(320, 240, Image::rgb(52, 132, 210)); +$blank = Image::fromBlank(320, 240, ImageColor::rgb(52, 132, 210)); -// koordinate lahko nastavite tudi v odstotkih. -$blank->place($logo, '80%', '80%'); // v desnem spodnjem kotu +// koordinate je mogoče spet navesti v odstotkih +$blank->place($logo, '80%', '80%'); // vstavimo blizu spodnjega desnega kota ``` -Pri lepljenju se upošteva kanal alfa, poleg tega lahko vplivamo na prosojnost vstavljene slike (ustvarili bomo tako imenovani vodni znak): +Pri vstavljanju se upošteva alfa kanal, poleg tega lahko vplivamo na prosojnost vstavljene slike (ustvarimo t.i. vodni žig): ```php -$blank->place($image, '80%', '80%', 25); // preglednost je 25 %. +$blank->place($image, '80%', '80%', 25); // prosojnost je 25 % ``` -Tak API je res užitek uporabljati, kajne? +Takšen API je resnično užitek uporabljati! -Pregled metod .[#toc-overview-of-methods] -========================================= +Pregled metod +============= -static fromBlank(int $width, int $height, array $color=null): Image .[method] ------------------------------------------------------------------------------ -Ustvari novo pravo barvno sliko danih dimenzij. Privzeta barva je črna. +static fromBlank(int $width, int $height, ?ImageColor $color=null): Image .[method] +----------------------------------------------------------------------------------- +Ustvari novo true color sliko danih dimenzij. Privzeta barva je črna. static fromFile(string $file, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------- -Prebere sliko iz datoteke in vrne njeno vrsto v `$detectedFormat`. Podprte vrste so `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` in `Image::BMP`. +Naloži sliko iz datoteke in vrne njen [tip |#Formati] v `$detectedFormat`. static fromString(string $s, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------ -Preberi sliko iz niza in vrne njeno vrsto v `$detectedFormat`. Podprte vrste so `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` in `Image::BMP`. +Naloži sliko iz niza in vrne njen [tip |#Formati] v `$detectedFormat`. -static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method] ---------------------------------------------------------------------------------- -Ustvari barvo, ki jo lahko uporabite v drugih metodah, kot so `ellipse()`, `fill()` in tako naprej. +static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method][deprecated] +--------------------------------------------------------------------------------------------- +To funkcijo je nadomestil razred `ImageColor`, glejte [#barve]. static typeToExtension(int $type): string .[method] --------------------------------------------------- -Vrne končnico datoteke za podano konstanto `Image::XXX`. +Vrne končnico datoteke za dani [tip |#Formati]. static typeToMimeType(int $type): string .[method] -------------------------------------------------- -Vrne vrsto mime za podano konstanto `Image::XXX`. +Vrne mime type za dani [tip |#Formati]. static extensionToType(string $extension): int .[method] -------------------------------------------------------- -Vrne vrsto slike kot konstanto `Image::XXX` glede na končnico datoteke. +Vrne [tip |#Formati] slike glede na končnico datoteke. static detectTypeFromFile(string $file, int &$width=null, int &$height=null): ?int .[method] -------------------------------------------------------------------------------------------- -Vrne vrsto slikovne datoteke kot konstanto `Image::XXX`, v parametrih `$width` in `$height` pa tudi njene dimenzije. +Vrne [tip |#Formati] slike in v parametrih `$width` in `$height` tudi njene dimenzije. static detectTypeFromString(string $s, int &$width=null, int &$height=null): ?int .[method] ------------------------------------------------------------------------------------------- -Vrne vrsto slike iz niza kot konstanto `Image::XXX`, v parametrih `$width` in `$height` pa tudi njene dimenzije. +Vrne [tip |#Formati] slike iz niza in v parametrih `$width` in `$height` tudi njene dimenzije. -affine(array $affine, array $clip=null): Image .[method] --------------------------------------------------------- -Vrne sliko, ki vsebuje afino transformirano sliko src z uporabo neobveznega območja obrezovanja. ([več |https://www.php.net/manual/en/function.imageaffine]). +static isTypeSupported(int $type): bool .[method] +------------------------------------------------- +Ugotavlja, ali je podprt dani [tip |#Formati] slike. + + +static getSupportedTypes(): array .[method]{data-version:4.0.4} +--------------------------------------------------------------- +Vrne polje podprtih [tipov |#Formati] slike. + + +static calculateTextBox(string $text, string $fontFile, float $size, float $angle=0, array $options=[]): array .[method] +------------------------------------------------------------------------------------------------------------------------ +Izračuna dimenzije pravokotnika, ki obdaja besedilo v določeni pisavi in velikosti. Vrne asociativno polje, ki vsebuje ključe `left`, `top`, `width`, `height`. Levi rob je lahko tudi negativen, če se besedilo začne z levim podrezavanjem. + + +affine(array $affine, ?array $clip=null): Image .[method] +--------------------------------------------------------- +Vrnite sliko, ki vsebuje afino transformirano sliko src z uporabo izbirnega območja obrezovanja. ([več |https://www.php.net/manual/en/function.imageaffine]). affineMatrixConcat(array $m1, array $m2): array .[method] --------------------------------------------------------- -Vrne združitev dveh matrik afine transformacije, kar je uporabno, če je treba na isto sliko uporabiti več transformacij naenkrat. ([več |https://www.php.net/manual/en/function.imageaffinematrixconcat]) +Vrne združitev dveh afinih transformacijskih matrik, kar je uporabno, če bi se na isto sliko moralo hkrati uporabiti več transformacij. ([več |https://www.php.net/manual/en/function.imageaffinematrixconcat]). -affineMatrixGet(int $type, mixed $options=null): array .[method] ----------------------------------------------------------------- -Vrne matriko afine transformacije. ([več |https://www.php.net/manual/en/function.imageaffinematrixget]) +affineMatrixGet(int $type, ?mixed $options=null): array .[method] +----------------------------------------------------------------- +Vrne matrično transformacijsko matriko. ([več |https://www.php.net/manual/en/function.imageaffinematrixget]). alphaBlending(bool $on): void .[method] --------------------------------------- -Omogoča dva različna načina risanja na slike v pravih barvah. V načinu mešanja komponenta kanala alfa barve, ki se posreduje vsem funkcijam risanja, kot je `setPixel()`, določa, koliko osnovne barve naj se preliva skozi. Zato samodejno zmeša obstoječo barvo na tej točki z barvo risbe in rezultat shrani v sliko. Nastali piksel je neprosojen. V načinu brez mešanja se barva risbe dobesedno kopira z informacijami kanala alfa in nadomesti ciljno piko. Način mešanja ni na voljo pri risanju na slike iz palete. ([več |https://www.php.net/manual/en/function.imagealphablending]) +Omogoča dva različna načina risanja v slikah truecolor. V načinu mešanja določa komponenta alfa kanala barve, uporabljene v vseh funkcijah risanja, kot je na primer `setPixel()`, do kakšne mere naj bi bilo omogočeno prosojnost osnovne barve. Rezultat je, da se na tej točki samodejno zmeša obstoječa barva z risano barvo in rezultat shrani v sliko. Končni piksel je neprosojen. V načinu brez mešanja se risana barva kopira dobesedno z informacijami o alfa kanalu in nadomesti ciljni piksel. Način mešanja ni na voljo pri risanju na paletnih slikah. ([več |https://www.php.net/manual/en/function.imagealphablending]). antialias(bool $on): void .[method] ----------------------------------- -Aktivirajte metode hitrega risanja z izravnavo za črte in žične poligone. Ne podpira sestavin alfa. Deluje z neposrednim mešanjem. Deluje samo s slikami v pravih barvah. +Aktivirajte risanje zglajenih črt in poligonov. Ne podpira alfa kanalov. Deluje samo pri slikah truecolor. -Uporaba izravnanih primitivov s prozorno barvo ozadja lahko privede do nepričakovanih rezultatov. Metoda mešanja uporablja barvo ozadja kot vse druge barve. Pomanjkanje podpore za komponento alfa ne omogoča metode za izravnavo na podlagi alfa. ([več |https://www.php.net/manual/en/function.imageantialias]) +Uporaba antialiased primitivov s prozorno barvo ozadja lahko povzroči nekatere nepričakovane rezultate. Metoda mešanja uporablja barvo ozadja kot vse druge barve. ([več |https://www.php.net/manual/en/function.imageantialias]). -arc(int $x, int $y, int $w, int $h, int $start, int $end, int $color): void .[method] -------------------------------------------------------------------------------------- -Nariše lok kroga s središčem na danih koordinatah. ([več |https://www.php.net/manual/en/function.imagearc]) - - -char(int $font, int $x, int $y, string $char, int $color): void .[method] -------------------------------------------------------------------------- -Nariše prvi znak `$char` na sliki z zgornjim levim delom na `$x`,`$y` (zgornji levi del je 0, 0) z barvo `$color`. ([več |https://www.php.net/manual/en/function.imagechar]) - - -charUp(int $font, int $x, int $y, string $char, int $color): void .[method] ---------------------------------------------------------------------------- -Nariše znak `$char` navpično na določeno koordinato na dani sliki. ([več |https://www.php.net/manual/en/function.imagecharup]) +arc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color): void .[method] +--------------------------------------------------------------------------------------------------------------------------- +Nariše lok kroga s središčem v danih koordinatah. ([več |https://www.php.net/manual/en/function.imagearc]). colorAllocate(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------- -Vrne identifikator barve, ki predstavlja barvo, sestavljeno iz danih komponent RGB. Klicati ga je treba za ustvarjanje vsake barve, ki bo uporabljena v sliki. ([več |https://www.php.net/manual/en/function.imagecolorallocate]) +Vrne identifikator barve, ki predstavlja barvo, sestavljeno iz danih komponent RGB. Poklicati jo je treba za ustvarjanje vsake barve, ki naj bi se uporabila v sliki. ([več |https://www.php.net/manual/en/function.imagecolorallocate]). colorAllocateAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ------------------------------------------------------------------------------ -Obnaša se enako kot `colorAllocate()` z dodatkom parametra preglednosti `$alpha`. ([več |https://www.php.net/manual/en/function.imagecolorallocatealpha]) +Obnaša se enako kot `colorAllocate()` z dodatkom parametra prosojnosti `$alpha`. ([več |https://www.php.net/manual/en/function.imagecolorallocatealpha]). colorAt(int $x, int $y): int .[method] -------------------------------------- -Vrne indeks barve piksla na določenem mestu v sliki. Če je slika pravi barvni prikaz, ta funkcija vrne vrednost RGB te pike kot celo število. Za dostop do različnih vrednosti rdeče, zelene in modre komponente uporabite premikanje bitov in maskiranje: ([več |https://www.php.net/manual/en/function.imagecolorat]) +Vrne indeks barve piksla na določenem mestu v sliki. Če je slika truecolor, ta funkcija vrne vrednost RGB tega piksla kot celo število. Uporabite bitni pomik in bitno maskiranje za dostop do ločenih vrednosti rdeče, zelene in modre komponente: ([več |https://www.php.net/manual/en/function.imagecolorat]). colorClosest(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------ -Vrne indeks barve v paleti slike, ki je "najbližja" določeni vrednosti RGB. "Razdalja" med želeno barvo in vsako barvo v paleti se izračuna, kot da bi vrednosti RGB predstavljale točke v tridimenzionalnem prostoru. ([več |https://www.php.net/manual/en/function.imagecolorclosest]) +Vrne indeks barve v paleti slike, ki je „najbližja“ podani vrednosti RGB. "Razdalja" med želeno barvo in vsako barvo v paleti se izračuna, kot da bi vrednosti RGB predstavljale točke v tridimenzionalnem prostoru. ([več |https://www.php.net/manual/en/function.imagecolorclosest]). colorClosestAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ----------------------------------------------------------------------------- -Vrne indeks barve v paleti slike, ki je "najbližja" določeni vrednosti RGB in ravni `$alpha`. ([več |https://www.php.net/manual/en/function.imagecolorclosestalpha]) +Vrne indeks barve v paleti slike, ki je „najbližja“ podani vrednosti RGB in ravni `$alpha`. ([več |https://www.php.net/manual/en/function.imagecolorclosestalpha]). colorClosestHWB(int $red, int $green, int $blue): int .[method] --------------------------------------------------------------- -Pridobi indeks barve, ki je po odtenku, belini in črnini najbližja dani barvi. ([več |https://www.php.net/manual/en/function.imagecolorclosesthwb]) +Pridobite indeks barve, ki ima odtenek, belino in črnino najbližje dani barvi. ([več |https://www.php.net/manual/en/function.imagecolorclosesthwb]). colorDeallocate(int $color): void .[method] ------------------------------------------- -Odstrani barvo, ki je bila prej dodeljena s `colorAllocate()` ali `colorAllocateAlpha()`. ([več |https://www.php.net/manual/en/function.imagecolordeallocate]) +De-alocira barvo, ki je bila prej dodeljena z `colorAllocate()` ali `colorAllocateAlpha()`. ([več |https://www.php.net/manual/en/function.imagecolordeallocate]). colorExact(int $red, int $green, int $blue): int .[method] ---------------------------------------------------------- -Vrne indeks določene barve v paleti slike. ([več |https://www.php.net/manual/en/function.imagecolorexact]) +Vrne indeks podane barve v paleti slike. ([več |https://www.php.net/manual/en/function.imagecolorexact]). colorExactAlpha(int $red, int $green, int $blue, int $alpha): int .[method] --------------------------------------------------------------------------- -Vrne indeks določene barve+alfa v paleti slike. ([več |https://www.php.net/manual/en/function.imagecolorexactalpha]) +Vrne indeks podane barve + alfa v paleti slike. ([več |https://www.php.net/manual/en/function.imagecolorexactalpha]). colorMatch(Image $image2): void .[method] ----------------------------------------- -Barve različice slike na paleti se bolj približajo pravi barvni različici. ([več |https://www.php.net/manual/en/function.imagecolormatch]) +Prilagodi barve palete drugi sliki. ([več |https://www.php.net/manual/en/function.imagecolormatch]). colorResolve(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------ -Vrne barvni indeks za zahtevano barvo, bodisi točno določeno barvo bodisi najbližjo možno alternativo. ([več |https://www.php.net/manual/en/function.imagecolorresolve]) +Vrne indeks barve za želeno barvo, bodisi natančno barvo ali najbližjo možno alternativo. ([več |https://www.php.net/manual/en/function.imagecolorresolve]). colorResolveAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ----------------------------------------------------------------------------- -Vrne barvni indeks za zahtevano barvo, bodisi točno določeno barvo bodisi najbližjo možno alternativo. ([več |https://www.php.net/manual/en/function.imagecolorresolvealpha]) +Vrne indeks barv za želeno barvo, bodisi natančno barvo ali najbližjo možno alternativo. ([več |https://www.php.net/manual/en/function.imagecolorresolvealpha]). colorSet(int $index, int $red, int $green, int $blue): void .[method] --------------------------------------------------------------------- -To nastavi določen indeks v paleti na določeno barvo. ([več |https://www.php.net/manual/en/function.imagecolorset]) +Nastavi podani indeks v paleti na podano barvo. ([več |https://www.php.net/manual/en/function.imagecolorset]). colorsForIndex(int $index): array .[method] ------------------------------------------- -Pridobi barvo za določen indeks. ([več |https://www.php.net/manual/en/function.imagecolorsforindex]) +Pridobi barvo določenega indeksa. ([več |https://www.php.net/manual/en/function.imagecolorsforindex]). colorsTotal(): int .[method] ---------------------------- -Vrne število barv v slikovni paleti ([več |https://www.php.net/manual/en/function.imagecolorstotal]). +Vrne število barv v slikovni paleti. ([več |https://www.php.net/manual/en/function.imagecolorstotal]). -colorTransparent(int $color=null): int .[method] ------------------------------------------------- -Pridobi ali nastavi prozorno barvo na sliki. ([več |https://www.php.net/manual/en/function.imagecolortransparent]) +colorTransparent(?int $color=null): int .[method] +------------------------------------------------- +Pridobi ali nastavi prozorno barvo v sliki. ([več |https://www.php.net/manual/en/function.imagecolortransparent]). convolution(array $matrix, float $div, float $offset): void .[method] --------------------------------------------------------------------- -Uporabi konvolucijsko matriko na sliki z uporabo danega koeficienta in odmika. ([več |https://www.php.net/manual/en/function.imageconvolution]) +Uporabi na sliki konvolucijsko matriko, uporablja dani koeficient in odmik. ([več |https://www.php.net/manual/en/function.imageconvolution]). .[note] -Zahteva razširitev *Bundled GD extension*, zato ni gotovo, da bo delovala povsod. +Zahteva prisotnost *Bundled GD extension*, zato morda ne bo delovala povsod. copy(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH): void .[method] -------------------------------------------------------------------------------------------------- -Kopira del `$src` na sliko z začetkom na koordinatah `$srcX`, `$srcY` s širino `$srcW` in višino `$srcH`. Določeni del bo kopiran na koordinate `$dstX` in `$dstY`. ([več |https://www.php.net/manual/en/function.imagecopy]) +Kopira del `$src` na sliko, začenši s koordinatami `$srcX`, `$srcY` s širino `$srcW` in višino `$srcH`. Definirani del bo kopiran na koordinate `$dstX` in `$dstY`. ([več |https://www.php.net/manual/en/function.imagecopy]). copyMerge(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] --------------------------------------------------------------------------------------------------------------------- -Kopira del `$src` na sliko z začetkom na koordinatah `$srcX`, `$srcY` s širino `$srcW` in višino `$srcH`. Določeni del bo kopiran na koordinate `$dstX` in `$dstY`. ([več |https://www.php.net/manual/en/function.imagecopymerge]) +Kopira del `$src` na sliko, začenši s koordinatami `$srcX`, `$srcY` s širino `$srcW` in višino `$srcH`. Definirani del bo kopiran na koordinate `$dstX` in `$dstY`. ([več |https://www.php.net/manual/en/function.imagecopymerge]). copyMergeGray(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] ------------------------------------------------------------------------------------------------------------------------- -Kopira del `$src` na sliko z začetkom na koordinatah `$srcX`, `$srcY` s širino `$srcW` in višino `$srcH`. Določeni del bo kopiran na koordinate `$dstX` in `$dstY`. +Kopira del `$src` na sliko, začenši s koordinatami `$srcX`, `$srcY` s širino `$srcW` in višino `$srcH`. Definirani del bo kopiran na koordinate `$dstX` in `$dstY`. -Ta funkcija je enaka funkciji `copyMerge()`, le da pri združevanju ohrani odtenek vira tako, da ciljne piksle pred kopiranjem pretvori v sivo lestvico. ([več |https://www.php.net/manual/en/function.imagecopymergegray]) +Ta funkcija je identična `copyMerge()` z izjemo, da pri združevanju ohranja odtenek vira s pretvorbo ciljnih pikslov v sivo lestvico pred operacijo kopiranja. ([več |https://www.php.net/manual/en/function.imagecopymergegray]). copyResampled(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] --------------------------------------------------------------------------------------------------------------------------------- -Kopira pravokotni del ene slike na drugo sliko in gladko interpolira vrednosti slikovnih pik, tako da zlasti pri zmanjšanju velikosti slike še vedno ohrani veliko jasnost. +Kopira pravokotni del ene slike na drugo sliko, gladko interpolira vrednosti slikovnih pik, tako da zlasti pomanjšanje velikosti slike še vedno ohranja veliko jasnost. -Z drugimi besedami, `copyResampled()` vzame pravokotno območje iz `$src` širine `$srcW` in višine `$srcH` na položaju (`$srcX`,`$srcY`) in ga postavi v pravokotno območje slike širine `$dstW` in višine `$dstH` na položaj (`$dstX`,`$dstY`). +Z drugimi besedami, `copyResampled()` vzame pravokotno območje iz `$src` širine `$srcW` in višine `$srcH` na položaju (`$srcX`, `$srcY`) in ga postavi v pravokotno območje slike širine `$dstW` in višine `$dstH` na položaju (`$dstX`, `$dstY`). -Če se izvorne in ciljne koordinate ter širine in višine razlikujejo, se fragment slike ustrezno raztegne ali skrči. Koordinate se nanašajo na zgornji levi kot. To funkcijo lahko uporabite za kopiranje območij znotraj iste slike, vendar bodo rezultati nepredvidljivi, če se območja prekrivajo. ([več |https://www.php.net/manual/en/function.imagecopyresampled]) +Če se izvorne in ciljne koordinate, širina in višina razlikujejo, se izvede ustrezno raztezanje ali pomanjšanje fragmenta slike. Koordinate se nanašajo na zgornji levi kot. To funkcijo je mogoče uporabiti za kopiranje območij znotraj iste slike, vendar če se območja prekrivajo, rezultati ne bodo predvidljivi. ([več |https://www.php.net/manual/en/function.imagecopyresampled]). copyResized(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] ------------------------------------------------------------------------------------------------------------------------------- -Kopira pravokotni del ene slike na drugo sliko. Z drugimi besedami, `copyResized()` vzame pravokotno območje iz `$src` širine `$srcW` in višine `$srcH` na položaju (`$srcX`,`$srcY`) in ga postavi v pravokotno območje slike širine `$dstW` in višine `$dstH` na položaj (`$dstX`,`$dstY`). +Kopira pravokotni del ene slike na drugo sliko. Z drugimi besedami, `copyResized()` pridobi pravokotno območje iz `$src` širine `$srcW` in višine `$srcH` na položaju (`$srcX`, `$srcY`) in ga postavi v pravokotno območje slike širine `$dstW` in višine `$dstH` na položaju (`$dstX`, `$dstY`). -Če se izvorne in ciljne koordinate ter širine in višine razlikujejo, se fragment slike ustrezno raztegne ali skrči. Koordinate se nanašajo na zgornji levi kot. To funkcijo lahko uporabite za kopiranje območij znotraj iste slike, vendar bodo rezultati nepredvidljivi, če se območja prekrivajo. ([več |https://www.php.net/manual/en/function.imagecopyresized]) +Če se izvorne in ciljne koordinate, širina in višina razlikujejo, se izvede ustrezno raztezanje ali pomanjšanje fragmenta slike. Koordinate se nanašajo na zgornji levi kot. To funkcijo je mogoče uporabiti za kopiranje območij znotraj iste slike, vendar če se območja prekrivajo, rezultati ne bodo predvidljivi. ([več |https://www.php.net/manual/en/function.imagecopyresized]). crop(int|string $left, int|string $top, int|string $width, int|string $height): Image .[method] ----------------------------------------------------------------------------------------------- -Obreže sliko na podano pravokotno območje. Dimenzije lahko posredujete kot cela števila v pikslih ali kot nize v odstotkih (npr. `'50%'`). +Obreže sliko na dano pravokotno območje. Dimenzije je mogoče vnesti kot cela števila v pikslih ali nize v odstotkih (na primer `'50%'`). -cropAuto(int $mode=-1, float $threshold=.5, int $color=-1): Image .[method] ---------------------------------------------------------------------------- -Samodejno obreže sliko v skladu z danim `$mode`. ([več |https://www.php.net/manual/en/function.imagecropauto]) +cropAuto(int $mode=-1, float $threshold=.5, ?ImageColor $color=null): Image .[method] +------------------------------------------------------------------------------------- +Samodejno obreže sliko glede na dani `$mode`. ([več |https://www.php.net/manual/en/function.imagecropauto]). -ellipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------- -Nariše elipso s središčem na določenih koordinatah. ([več |https://www.php.net/manual/en/function.imageellipse]) +ellipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------- +Nariše elipso s središčem na podanih koordinatah. ([več |https://www.php.net/manual/en/function.imageellipse]). -fill(int $x, int $y, int $color): void .[method] ------------------------------------------------- -Izvede zapolnitev z zalivko, ki se začne na dani koordinati (levo zgoraj je 0, 0) z danim `$color` v sliki. ([več |https://www.php.net/manual/en/function.imagefill]) +fill(int $x, int $y, ImageColor $color): void .[method] +------------------------------------------------------- +Zapolni območje, začenši z dano koordinato (zgoraj levo je 0, 0) z dano `$color`. ([več |https://www.php.net/manual/en/function.imagefill]). -filledArc(int $cx, int $cy, int $w, int $h, int $s, int $e, int $color, int $style): void .[method] ---------------------------------------------------------------------------------------------------- -Nariše delni lok s središčem na določeni koordinati na sliki. ([več |https://www.php.net/manual/en/function.imagefilledarc]) +filledArc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color, int $style): void .[method] +--------------------------------------------------------------------------------------------------------------------------------------------- +Nariše delni lok s središčem na podanih koordinatah. ([več |https://www.php.net/manual/en/function.imagefilledarc]). -filledEllipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------------- -Nariše elipso s središčem na določeni koordinati na sliki. ([več |https://www.php.net/manual/en/function.imagefilledellipse]) +filledEllipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------------- +Nariše elipso s središčem na podanih koordinatah. ([več |https://www.php.net/manual/en/function.imagefilledellipse]). -filledPolygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------------- -Ustvari zapolnjen poligon v $sliki. ([več |https://www.php.net/manual/en/function.imagefilledpolygon]) +filledPolygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------------- +Ustvari v sliki zapolnjen mnogokotnik. ([več |https://www.php.net/manual/en/function.imagefilledpolygon]). -filledRectangle(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] -------------------------------------------------------------------------------- -Ustvari pravokotnik, zapolnjen s `$color` na sliki, ki se začne v točki 1 in konča v točki 2. 0, 0 je zgornji levi kot slike. ([več |https://www.php.net/manual/en/function.imagefilledrectangle]) +filledRectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------- +Ustvari pravokotnik, zapolnjen z `$color`, v sliki, začenši s točko `$x1` & `$y1` in končajoč s točko `$x2` & `$y2`. Točka 0, 0 je zgornji levi kot slike. ([več |https://www.php.net/manual/en/function.imagefilledrectangle]). -fillToBorder(int $x, int $y, int $border, int $color): void .[method] ---------------------------------------------------------------------- -Izvede zalivko, katere barva roba je določena s `$border`. Začetna točka za zapolnitev je `$x`, `$y` (levo zgoraj je 0, 0), območje pa je zapolnjeno z barvo `$color`. ([več |https://www.php.net/manual/en/function.imagefilltoborder]) +filledRectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------------- +Ustvari pravokotnik, zapolnjen z `$color`, v sliki, začenši s točko `$left` & `$top` s širino `$width` in višino `$height`. Točka 0, 0 je zgornji levi kot slike. + + +fillToBorder(int $x, int $y, int $border, ImageColor $color): void .[method] +---------------------------------------------------------------------------- +Izriše zapolnitev, katere barva roba je definirana z `$border`. Začetna točka zapolnitve je `$x`, `$y` (zgoraj levo je 0, 0) in območje je zapolnjeno z barvo `$color`. ([več |https://www.php.net/manual/en/function.imagefilltoborder]). filter(int $filtertype, int ...$args): void .[method] ----------------------------------------------------- -Na sliko uporabi dani filter `$filtertype`. ([več |https://www.php.net/manual/en/function.imagefilter]) +Uporabi dani filter `$filtertype` na sliki. ([več |https://www.php.net/manual/en/function.imagefilter]). flip(int $mode): void .[method] ------------------------------- -Obrne sliko z uporabo danega `$mode`. ([več |https://www.php.net/manual/en/function.imageflip]) +Obrne sliko z uporabo danega `$mode`. ([več |https://www.php.net/manual/en/function.imageflip]). -ftText(int $size, int $angle, int $x, int $y, int $col, string $fontFile, string $text, array $extrainfo=null): array .[method] -------------------------------------------------------------------------------------------------------------------------------- -Besedilo v sliko zapišite s pisavami FreeType 2. ([več |https://www.php.net/manual/en/function.imagefttext]) +ftText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +---------------------------------------------------------------------------------------------------------------------------------------- +Zapišite besedilo v sliko. ([več |https://www.php.net/manual/en/function.imagefttext]). gammaCorrect(float $inputgamma, float $outputgamma): void .[method] ------------------------------------------------------------------- -Uporabi popravek gama za sliko, če sta podana vhodna in izhodna gama. ([več |https://www.php.net/manual/en/function.imagegammacorrect]) +Uporabi gama korekcijo na sliki glede na vhodno in izhodno gamo. ([več |https://www.php.net/manual/en/function.imagegammacorrect]). getClip(): array .[method] -------------------------- -Pridobi trenutni obrezovalni pravokotnik, tj. območje, zunaj katerega se ne bodo izrisale nobene piksle. ([več |https://www.php.net/manual/en/function.imagegetclip]) +Vrne trenutno obrezovanje, tj. območje, preko katerega ne bodo narisani nobeni piksli. ([več |https://www.php.net/manual/en/function.imagegetclip]). getHeight(): int .[method] @@ -504,7 +545,7 @@ Vrne višino slike. getImageResource(): resource|GdImage .[method] ---------------------------------------------- -Vrne izvirni vir. +Vrne izvirni resource. getWidth(): int .[method] @@ -512,169 +553,164 @@ getWidth(): int .[method] Vrne širino slike. -interlace(int $interlace=null): int .[method] ---------------------------------------------- -Vklopi ali izklopi bit prepletanja. Če je nastavljen bit prepletanja in se slika uporablja kot slika JPEG, se slika ustvari kot progresivni JPEG. ([več |https://www.php.net/manual/en/function.imageinterlace]) +interlace(?int $interlace=null): int .[method] +---------------------------------------------- +Vklopi ali izklopi prepleteni način. Če je prepleteni način nastavljen in je slika shranjena kot JPEG, bo shranjena kot progresivni JPEG. ([več |https://www.php.net/manual/en/function.imageinterlace]). isTrueColor(): bool .[method] ----------------------------- -Ugotovi, ali je slika prava barva. ([več |https://www.php.net/manual/en/function.imageistruecolor]) +Ugotovi, ali je slika truecolor. ([več |https://www.php.net/manual/en/function.imageistruecolor]). layerEffect(int $effect): void .[method] ---------------------------------------- -Nastavite zastavico mešanja alfa, če želite uporabiti učinke plastenja. ([več |https://www.php.net/manual/en/function.imagelayereffect]) +Nastavite zastavico mešanja alfa za uporabo učinkov plastenja. ([več |https://www.php.net/manual/en/function.imagelayereffect]). -line(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] --------------------------------------------------------------------- -Nariše črto med dvema danima točkama. ([več |https://www.php.net/manual/en/function.imageline]) +line(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +--------------------------------------------------------------------------- +Nariše črto med dvema danima točkama. ([več |https://www.php.net/manual/en/function.imageline]). -openPolygon(array $points, int $numPoints, int $color): void .[method] ----------------------------------------------------------------------- -Na sliko nariše odprt poligon. V nasprotju s spletno stranjo `polygon()` med zadnjo in prvo točko ni potegnjena črta. ([več |https://www.php.net/manual/en/function.imageopenpolygon]) +openPolygon(array $points, ImageColor $color): void .[method] +------------------------------------------------------------- +Nariše na sliko odprt mnogokotnik. V nasprotju z `polygon()` med zadnjo in prvo točko ni narisana nobena črta. ([več |https://www.php.net/manual/en/function.imageopenpolygon]). paletteCopy(Image $source): void .[method] ------------------------------------------ -Kopira paleto s spletne strani `$source` na sliko. ([več |https://www.php.net/manual/en/function.imagepalettecopy]) +Kopira paleto iz `$source` v sliko. ([več |https://www.php.net/manual/en/function.imagepalettecopy]). paletteToTrueColor(): void .[method] ------------------------------------ -Pretvarja sliko na podlagi palete, ustvarjeno s funkcijami, kot je `create()`, v pravo barvno sliko, kot je `createtruecolor()`. ([več |https://www.php.net/manual/en/function.imagepalettetotruecolor]) +Pretvori sliko, ki temelji na paleti, v polnobarvno sliko. ([več |https://www.php.net/manual/en/function.imagepalettetotruecolor]). place(Image $image, int|string $left=0, int|string $top=0, int $opacity=100): Image .[method] --------------------------------------------------------------------------------------------- -Kopira `$image` v sliko na koordinatah `$left` in `$top`. Koordinate lahko posredujete kot cela števila v pikslih ali kot nize v odstotkih (npr. `'50%'`). +Kopira `$image` v sliko na koordinate `$left` in `$top`. Koordinate je mogoče vnesti kot cela števila v pikslih ali nize v odstotkih (na primer `'50%'`). -polygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------- -Ustvari poligon na sliki. ([več |https://www.php.net/manual/en/function.imagepolygon]) +polygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------- +Ustvari v sliki mnogokotnik. ([več |https://www.php.net/manual/en/function.imagepolygon]). -rectangle(int $x1, int $y1, int $x2, int $y2, int $col): void .[method] ------------------------------------------------------------------------ -Ustvari pravokotnik, ki se začne na določenih koordinatah. ([več |https://www.php.net/manual/en/function.imagerectangle]) +rectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------- +Ustvari pravokotnik na podanih koordinatah. ([več |https://www.php.net/manual/en/function.imagerectangle]). + + +rectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------- +Ustvari pravokotnik na podanih koordinatah. resize(int|string $width, int|string $height, int $flags=Image::OrSmaller): Image .[method] ------------------------------------------------------------------------------------------- -Skaliranje slike, glejte [več informacij |#Image Resize]. Dimenzije lahko posredujete kot cela števila v pikslih ali kot nize v odstotkih (npr. `'50%'`). +Spremeni velikost slike, [več informacij |#Spreminjanje velikosti]. Dimenzije je mogoče vnesti kot cela števila v pikslih ali nize v odstotkih (na primer `'50%'`). -resolution(int $resX=null, int $resY=null): mixed .[method] ------------------------------------------------------------ -Omogoča nastavitev in pridobitev ločljivosti slike v DPI (točkah na palec). Če ni podan nobeden od neobveznih parametrov, se trenutna ločljivost vrne kot indeksirano polje. Če je podan samo naslov `$resX`, se vodoravna in navpična ločljivost nastavita na to vrednost. Če sta podana oba neobvezna parametra, se vodoravna in navpična ločljivost nastavita na ti vrednosti. +resolution(?int $resX=null, ?int $resY=null): mixed .[method] +------------------------------------------------------------- +Nastavi ali vrne ločljivost slike v DPI (pike na palec). Če ni podan noben od izbirnih parametrov, je trenutna ločljivost vrnjena kot indeksirano polje. Če je podan samo `$resX`, se vodoravna in navpična ločljivost nastavita na to vrednost. Če sta podana oba izbirna parametra, se vodoravna in navpična ločljivost nastavita na ti vrednosti. -Ločljivost se kot meta informacija uporablja le pri branju slik iz formatov, ki podpirajo tovrstne informacije (trenutno sta to formata PNG in JPEG), in pri zapisovanju v te formate. To ne vpliva na nobeno risanje. Privzeta ločljivost novih slik je 96 DPI. ([več |https://www.php.net/manual/en/function.imageresolution]) +Ločljivost se uporablja samo kot meta informacija, ko se slike berejo in zapisujejo v formate, ki podpirajo to vrsto informacij (trenutno PNG in JPEG). Nima vpliva na nobene operacije risanja. Privzeta ločljivost novih slik je 96 DPI. ([več |https://www.php.net/manual/en/function.imageresolution]). rotate(float $angle, int $backgroundColor): Image .[method] ----------------------------------------------------------- -Obrne sliko z uporabo danega `$angle` v stopinjah. Središče vrtenja je središče slike, zasukana slika pa ima lahko drugačne dimenzije kot izvirna slika. ([več |https://www.php.net/manual/en/function.imagerotate]) +Zasuče sliko z uporabo podanega `$angle` v stopinjah. Središče zasuka je središče slike in zasukana slika ima lahko drugačne dimenzije kot izvirna slika. ([več |https://www.php.net/manual/en/function.imagerotate]). .[note] -Zahteva razširitev *Bundled GD extension*, zato ni gotovo, da bo delovala povsod. +Zahteva prisotnost *Bundled GD extension*, zato morda ne bo delovala povsod. -save(string $file, int $quality=null, int $type=null): void .[method] ---------------------------------------------------------------------- -Sliko shrani v datoteko. +save(string $file, ?int $quality=null, ?int $type=null): void .[method] +----------------------------------------------------------------------- +Shrani sliko v datoteko. -Kakovost stiskanja je v območju 0..100 za JPEG (privzeto 85), WEBP (privzeto 80) in AVIF (privzeto 30) ter 0..9 za PNG (privzeto 9). Če tip ni razviden iz končnice datoteke, ga lahko določite z eno od konstant `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` in `Image::BMP`. +Kakovost stiskanja je v obsegu 0..100 za JPEG (privzeto 85), WEBP (privzeto 80) in AVIF (privzeto 30) ter 0..9 za PNG (privzeto 9). Če tip ni razviden iz končnice datoteke, ga lahko določite z eno od konstant `ImageType`. saveAlpha(bool $saveflag): void .[method] ----------------------------------------- -Nastavi zastavico, ki določa, ali naj se pri shranjevanju slik PNG ohranijo popolne informacije o kanalu alfa (v nasprotju z enobarvno prosojnostjo). +Nastavi zastavico, ali naj se pri shranjevanju slik PNG ohranijo popolne informacije o alfa kanalu (v nasprotju z enobarvno prosojnostjo). -Če želite ohraniti kanal alfa, morate onemogočiti funkcijo alphablending (`alphaBlending(false)`). ([več |https://www.php.net/manual/en/function.imagesavealpha]) +Alfablending mora biti deaktiviran (`alphaBlending(false)`), da se alfa kanal najprej ohrani. ([več |https://www.php.net/manual/en/function.imagesavealpha]). scale(int $newWidth, int $newHeight=-1, int $mode=IMG_BILINEAR_FIXED): Image .[method] -------------------------------------------------------------------------------------- -Skalira sliko z uporabo danega algoritma interpolacije. ([več |https://www.php.net/manual/en/function.imagescale]) +Spremeni merilo slike z uporabo danega interpolacijskega algoritma. ([več |https://www.php.net/manual/en/function.imagescale]). -send(int $type=Image::JPEG, int $quality=null): void .[method] --------------------------------------------------------------- +send(int $type=ImageType::JPEG, ?int $quality=null): void .[method] +------------------------------------------------------------------- Izpiše sliko v brskalnik. -Kakovost stiskanja je v območju 0..100 za JPEG (privzeto 85), WEBP (privzeto 80) in AVIF (privzeto 30) ter 0..9 za PNG (privzeto 9). Tip je ena od konstant `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` in `Image::BMP`. +Kakovost stiskanja je v obsegu 0..100 za JPEG (privzeto 85), WEBP (privzeto 80) in AVIF (privzeto 30) ter 0..9 za PNG (privzeto 9). setBrush(Image $brush): void .[method] -------------------------------------- -Nastavi sliko čopiča, ki jo uporabljajo vse funkcije za risanje črt (kot sta `line()` in `polygon()`) pri risanju s posebnimi barvami IMG_COLOR_BRUSHED ali IMG_COLOR_STYLEDBRUSHED. ([več |https://www.php.net/manual/en/function.imagesetbrush]) +Nastavi sliko čopiča, ki se uporabi v vseh funkcijah risanja črt (na primer `line()` in `polygon()`) pri risanju s posebnimi barvami IMG_COLOR_BRUSHED ali IMG_COLOR_STYLEDBRUSHED. ([več |https://www.php.net/manual/en/function.imagesetbrush]). setClip(int $x1, int $y1, int $x2, int $y2): void .[method] ----------------------------------------------------------- -Nastavi trenutni pravokotnik za obrezovanje, tj. območje, zunaj katerega se piksli ne bodo izrisali. ([več |https://www.php.net/manual/en/function.imagesetclip]) +Nastavi trenutno obrezovanje, tj. območje, preko katerega ne bodo narisani nobeni piksli. ([več |https://www.php.net/manual/en/function.imagesetclip]). setInterpolation(int $method=IMG_BILINEAR_FIXED): void .[method] ---------------------------------------------------------------- -Nastavi metodo interpolacije, ki vpliva na metodi `rotate()` in `affine()`. ([več |https://www.php.net/manual/en/function.imagesetinterpolation]) +Nastavi metodo interpolacije, ki vpliva na metodi `rotate()` in `affine()`. ([več |https://www.php.net/manual/en/function.imagesetinterpolation]). -setPixel(int $x, int $y, int $color): void .[method] ----------------------------------------------------- -Nariše piksel na določeni koordinati. ([več |https://www.php.net/manual/en/function.imagesetpixel]) +setPixel(int $x, int $y, ImageColor $color): void .[method] +----------------------------------------------------------- +Nariše piksel na podani koordinati. ([več |https://www.php.net/manual/en/function.imagesetpixel]). setStyle(array $style): void .[method] -------------------------------------- -Nastavi slog, ki ga bodo uporabljale vse funkcije za risanje črt (na primer `line()` in `polygon()`), ko bodo risale s posebno barvo IMG_COLOR_STYLED ali črte slik z barvo IMG_COLOR_STYLEDBRUSHED. ([več |https://www.php.net/manual/en/function.imagesetstyle]) +Nastavi slog, ki naj ga uporabljajo vse funkcije risanja črt (na primer `line()` in `polygon()`) pri risanju s posebno barvo IMG_COLOR_STYLED ali črt slik z barvo IMG_COLOR_STYLEDBRUSHED. ([več |https://www.php.net/manual/en/function.imagesetstyle]). setThickness(int $thickness): void .[method] -------------------------------------------- -Nastavi debelino črt, narisanih pri risanju pravokotnikov, mnogokotnikov, lokov itd., na `$thickness` pikslov. ([več |https://www.php.net/manual/en/function.imagesetthickness]) +Nastavi debelino črt pri risanju pravokotnikov, mnogokotnikov, lokov itd. na `$thickness` pikslov. ([več |https://www.php.net/manual/en/function.imagesetthickness]). setTile(Image $tile): void .[method] ------------------------------------ -Nastavi sliko ploščice, ki jo uporabljajo vse funkcije za zapolnitev območja (kot sta `fill()` in `filledPolygon()`) pri zapolnjevanju s posebno barvo IMG_COLOR_TILED. +Nastavi sliko ploščice, ki bo uporabljena v vseh funkcijah zapolnjevanja regij (na primer `fill()` in `filledPolygon()`), ko se zapolni s posebno barvo IMG_COLOR_TILED. -Ploščica je slika, ki se uporablja za zapolnitev območja s ponavljajočim se vzorcem. Kot ploščico lahko uporabite katero koli sliko, z nastavitvijo indeksa prozorne barve slike ploščice s `colorTransparent()` pa lahko ustvarite ploščico, ki omogoča, da določeni deli osnovnega območja prosevajo. ([več |https://www.php.net/manual/en/function.imagesettile]) +Ploščica je slika, ki se uporablja za zapolnitev območja s ponavljajočim se vzorcem. Vsako sliko je mogoče uporabiti kot ploščico in z nastavitvijo prozornega barvnega indeksa slike ploščice z `colorTransparent()` je mogoče ustvariti ploščico, kjer bodo določeni deli podložnega območja prosojni. ([več |https://www.php.net/manual/en/function.imagesettile]). sharpen(): Image .[method] -------------------------- -Nekoliko izostri sliko. +Ostrenje slike. .[note] -Zahteva razširitev *Bundled GD extension*, zato ni gotovo, da bo delovala povsod. - - -string(int $font, int $x, int $y, string $str, int $col): void .[method] ------------------------------------------------------------------------- -Nariše niz na danih koordinatah. ([več |https://www.php.net/manual/en/function.imagestring]) +Zahteva prisotnost *Bundled GD extension*, zato morda ne bo delovala povsod. -stringUp(int $font, int $x, int $y, string $s, int $col): void .[method] ------------------------------------------------------------------------- -Vertikalno nariše niz na danih koordinatah. ([več |https://www.php.net/manual/en/function.imagestringup]) - - -toString(int $type=Image::JPEG, int $quality=null): string .[method] --------------------------------------------------------------------- -Izpiše sliko v niz. +toString(int $type=ImageType::JPEG, ?int $quality=null): string .[method] +------------------------------------------------------------------------- +Shrani sliko v niz. -Kakovost stiskanja je v območju 0..100 za JPEG (privzeto 85), WEBP (privzeto 80) in AVIF (privzeto 30) ter 0..9 za PNG (privzeto 9). Tip je ena od konstant `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` in `Image::BMP`. +Kakovost stiskanja je v obsegu 0..100 za JPEG (privzeto 85), WEBP (privzeto 80) in AVIF (privzeto 30) ter 0..9 za PNG (privzeto 9). trueColorToPalette(bool $dither, int $ncolors): void .[method] -------------------------------------------------------------- -Pretvori sliko truecolor v sliko palete. ([več |https://www.php.net/manual/en/function.imagetruecolortopalette]) +Pretvori sliko truecolor v paletno. ([več |https://www.php.net/manual/en/function.imagetruecolortopalette]). -ttfText(int $size, int $angle, int $x, int $y, int $color, string $fontfile, string $text): array .[method] ------------------------------------------------------------------------------------------------------------ -V sliko zapiše podano besedilo z uporabo pisav TrueType. ([več |https://www.php.net/manual/en/function.imagettftext]) +ttfText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +----------------------------------------------------------------------------------------------------------------------------------------- +Izpiše dano besedilo v sliko. ([več |https://www.php.net/manual/en/function.imagettftext]). diff --git a/utils/sl/iterables.texy b/utils/sl/iterables.texy new file mode 100644 index 0000000000..df92004bec --- /dev/null +++ b/utils/sl/iterables.texy @@ -0,0 +1,170 @@ +Delo z iteratorji +***************** + +.[perex]{data-version:4.0.4} +[api:Nette\Utils\Iterables] je statični razred s funkcijami za delo z iteratorji. Njegova ustreznica za polja je [Nette\Utils\Arrays |arrays]. + + +Namestitev: + +```shell +composer require nette/utils +``` + +Vsi primeri predpostavljajo ustvarjen vzdevek: + +```php +use Nette\Utils\Iterables; +``` + + +contains(iterable $iterable, $value): bool .[method] +---------------------------------------------------- + +Išče podano vrednost v iteratorju. Uporablja strogo primerjavo (`===`) za preverjanje ujemanja. Vrne `true`, če je vrednost najdena, sicer `false`. + +```php +Iterables::contains(new ArrayIterator([1, 2, 3]), 1); // true +Iterables::contains(new ArrayIterator([1, 2, 3]), '1'); // false +``` + +Ta metoda je uporabna, ko morate hitro ugotoviti, ali se določena vrednost nahaja v iteratorju, ne da bi morali ročno prehajati skozi vse elemente. + + +containsKey(iterable $iterable, $key): bool .[method] +----------------------------------------------------- + +Išče podani ključ v iteratorju. Uporablja strogo primerjavo (`===`) za preverjanje ujemanja. Vrne `true`, če je ključ najden, sicer `false`. + +```php +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 0); // true +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 4); // false +``` + + +every(iterable $iterable, callable $predicate): bool .[method] +-------------------------------------------------------------- + +Preverja, ali vsi elementi iteratorja izpolnjujejo pogoj, definiran v `$predicate`. Funkcija `$predicate` ima signaturo `function ($value, $key, iterable $iterable): bool` in mora vrniti `true` za vsak element, da metoda `every()` vrne `true`. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isBelowThreshold = fn($value) => $value < 40; +$res = Iterables::every($iterator, $isBelowThreshold); // true +``` + +Ta metoda je uporabna za preverjanje, ali vsi elementi v zbirki izpolnjujejo določen pogoj, na primer, ali so vsa števila manjša od določene vrednosti. + + +filter(iterable $iterable, callable $predicate): Generator .[method] +-------------------------------------------------------------------- + +Ustvari nov iterator, ki vsebuje samo tiste elemente iz izvirnega iteratorja, ki izpolnjujejo pogoj, definiran v `$predicate`. Funkcija `$predicate` ima signaturo `function ($value, $key, iterable $iterable): bool` in mora vrniti `true` za elemente, ki naj bodo ohranjeni. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::filter($iterator, fn($v) => $v < 3); +// 1, 2 +``` + +Metoda uporablja generator, kar pomeni, da filtriranje poteka postopoma med prehajanjem rezultata. To je učinkovito z vidika pomnilnika in omogoča obdelavo tudi zelo velikih zbirk. Če ne preidete vseh elementov rezultirajočega iteratorja, prihranite računsko moč, saj se ne obdelajo vsi elementi izvirnega iteratorja. + + +first(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------------- + +Vrne prvi element iteratorja. Če je podan `$predicate`, vrne prvi element, ki izpolnjuje dani pogoj. Funkcija `$predicate` ima signaturo `function ($value, $key, iterable $iterable): bool`. Če ni najden noben ustrezen element, se pokliče funkcija `$else` (če je podana) in vrne se njen rezultat. Če `$else` ni podan, se vrne `null`. + +```php +Iterables::first(new ArrayIterator([1, 2, 3])); // 1 +Iterables::first(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 3 +Iterables::first(new ArrayIterator([])); // null +Iterables::first(new ArrayIterator([]), else: fn() => false); // false +``` + +Ta metoda je uporabna, ko morate hitro pridobiti prvi element zbirke ali prvi element, ki izpolnjuje določen pogoj, ne da bi morali ročno prehajati skozi celotno zbirko. + + +firstKey(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +---------------------------------------------------------------------------------------------- + +Vrne ključ prvega elementa iteratorja. Če je podan `$predicate`, vrne ključ prvega elementa, ki izpolnjuje dani pogoj. Funkcija `$predicate` ima signaturo `function ($value, $key, iterable $iterable): bool`. Če ni najden noben ustrezen element, se pokliče funkcija `$else` (če je podana) in vrne se njen rezultat. Če `$else` ni podan, se vrne `null`. + +```php +Iterables::firstKey(new ArrayIterator([1, 2, 3])); // 0 +Iterables::firstKey(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 2 +Iterables::firstKey(new ArrayIterator(['a' => 1, 'b' => 2])); // 'a' +Iterables::firstKey(new ArrayIterator([])); // null +``` + + +map(iterable $iterable, callable $transformer): Generator .[method] +------------------------------------------------------------------- + +Ustvari nov iterator z uporabo funkcije `$transformer` na vsakem elementu izvirnega iteratorja. Funkcija `$transformer` ima signaturo `function ($value, $key, iterable $iterable): mixed` in njena vrnjena vrednost se uporabi kot nova vrednost elementa. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::map($iterator, fn($v) => $v * 2); +// 2, 4, 6 +``` + +Metoda uporablja generator, kar pomeni, da transformacija poteka postopoma med prehajanjem rezultata. To je učinkovito z vidika pomnilnika in omogoča obdelavo tudi zelo velikih zbirk. Če ne preidete vseh elementov rezultirajočega iteratorja, prihranite računsko moč, saj se ne obdelajo vsi elementi izvirnega iteratorja. + + +mapWithKeys(iterable $iterable, callable $transformer): Generator .[method] +--------------------------------------------------------------------------- + +Ustvari nov iterator s transformacijo vrednosti in ključev izvirnega iteratorja. Funkcija `$transformer` ima signaturo `function ($value, $key, iterable $iterable): ?array{$newKey, $newValue}`. Če `$transformer` vrne `null`, je element preskočen. Za ohranjene elemente se prvi element vrnjenega polja uporabi kot nov ključ, drugi element pa kot nova vrednost. + +```php +$iterator = new ArrayIterator(['a' => 1, 'b' => 2]); +$iterator = Iterables::mapWithKeys($iterator, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +Tako kot `map()`, ta metoda uporablja generator za postopno obdelavo in učinkovito delo s pomnilnikom. To omogoča delo z velikimi zbirkami in prihranek računske moči pri delnem prehodu rezultata. + + +memoize(iterable $iterable): IteratorAggregate .[method] +-------------------------------------------------------- + +Ustvari ovoj okoli iteratorja, ki med iteracijo shranjuje njegove ključe in vrednosti v predpomnilnik. To omogoča ponovno iteracijo podatkov brez potrebe po ponovnem prehodu izvirnega vira podatkov. + +```php +$iterator = /* podatki, ki jih ni mogoče iterirati večkrat */; +$memoized = Iterables::memoize($iterator); +// Zdaj lahko $memoized iterirate večkrat brez izgube podatkov +``` + +Ta metoda je uporabna v situacijah, ko morate večkrat preiti isti nabor podatkov, vendar izvirni iterator ne omogoča ponovne iteracije ali pa bi bilo ponovno prehajanje drago (npr. pri branju podatkov iz podatkovne baze ali datoteke). + + +some(iterable $iterable, callable $predicate): bool .[method] +------------------------------------------------------------- + +Preverja, ali vsaj en element iteratorja izpolnjuje pogoj, definiran v `$predicate`. Funkcija `$predicate` ima signaturo `function ($value, $key, iterable $iterable): bool` in mora vrniti `true` za vsaj en element, da metoda `some()` vrne `true`. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isEven = fn($value) => $value % 2 === 0; +$res = Iterables::some($iterator, $isEven); // true +``` + +Ta metoda je uporabna za hitro preverjanje, ali v zbirki obstaja vsaj en element, ki izpolnjuje določen pogoj, na primer, ali zbirka vsebuje vsaj eno sodo število. + +Glejte [#every()]. + + +toIterator(iterable $iterable): Iterator .[method] +-------------------------------------------------- + +Pretvori kateri koli iterabilni objekt (array, Traversable) v Iterator. Če je vhod že Iterator, ga vrne nespremenjenega. + +```php +$array = [1, 2, 3]; +$iterator = Iterables::toIterator($array); +// Zdaj imate Iterator namesto polja +``` + +Ta metoda je uporabna, ko morate zagotoviti, da imate na voljo Iterator, ne glede na vrsto vhodnih podatkov. To je lahko uporabno pri ustvarjanju funkcij, ki delajo z različnimi vrstami iterabilnih podatkov. diff --git a/utils/sl/json.texy b/utils/sl/json.texy index 0ef442e444..88338f7891 100644 --- a/utils/sl/json.texy +++ b/utils/sl/json.texy @@ -1,8 +1,8 @@ -Funkcije JSON -************* +Delo z JSON +*********** .[perex] -[api:Nette\Utils\Json] je statični razred s funkcijami kodiranja in dekodiranja JSON. Obravnava ranljivosti v različnih različicah PHP in ob napakah vrže izjeme. +[api:Nette\Utils\Json] je statični razred s funkcijami za kodiranje in dekodiranje formata JSON. Obravnava ranljivosti različnih različic PHP in vrže izjeme pri napakah. Namestitev: @@ -11,44 +11,44 @@ Namestitev: composer require nette/utils ``` -Vsi primeri predpostavljajo, da je definiran naslednji vzdevek razreda: +Vsi primeri predpostavljajo ustvarjen vzdevek: ```php use Nette\Utils\Json; ``` -Uporaba .[#toc-usage] -===================== +Uporaba +======= encode(mixed $value, bool $pretty=false, bool $asciiSafe=false, bool $htmlSafe=false, bool $forceObjects=false): string .[method] --------------------------------------------------------------------------------------------------------------------------------- -Pretvori `$value` v obliko JSON. +Pretvori `$value` v format JSON. -Če je nastavljena vrednost `$pretty`, se za lažje branje in preglednost oblikuje JSON: +Pri nastavitvi `$pretty` formatira JSON za lažje branje in preglednost: ```php Json::encode($value); // vrne JSON -Json::encode($value, pretty: true); // vrne jasnejši JSON +Json::encode($value, pretty: true); // vrne preglednejši JSON ``` -Ko je nastavljena vrednost `$asciiSafe`, ustvari izhod ASCII, tj. zamenja znake unicode z zaporedji `\uxxxx`: +Pri `$asciiSafe` generira izhod v ASCII, tj. unicode znake nadomesti z zaporedji `\uxxxx`: ```php Json::encode('žluťoučký', asciiSafe: true); // '"\u017elu\u0165ou\u010dk\u00fd"' ``` -Parameter `$htmlSafe` zagotavlja, da izpis ne vsebuje znakov s posebnim pomenom v jeziku HTML: +Parameter `$htmlSafe` zagotovi, da izhod ne bo vseboval znakov, ki imajo v HTML poseben pomen: ```php Json::encode('onesendJson($data)`, ki jo lahko prikličete na primer v metodi `action*()`, glejte poglavje [Pošiljanje odgovora |application:presenters#Sending a Response]. +Za to lahko uporabimo metodo `$this->sendJson($data)`, ki jo lahko pokličemo na primer v metodi `action*()`, glejte [Pošiljanje odgovora |application:presenters#Pošiljanje odgovora]. diff --git a/utils/sl/paginator.texy b/utils/sl/paginator.texy index 2285176ce0..6b3d258616 100644 --- a/utils/sl/paginator.texy +++ b/utils/sl/paginator.texy @@ -2,7 +2,7 @@ Paginator ********* .[perex] -Potrebujete stran za seznam podatkov? Ker je matematika v ozadju paginacije lahko zapletena, vam bo pomagal [api:Nette\Utils\Paginator]. +Ali morate razdeliti izpis podatkov na strani? Ker je matematika straničenja lahko zapletena, vam bo pri tem pomagal [api:Nette\Utils\Paginator]. Namestitev: @@ -11,38 +11,38 @@ Namestitev: composer require nette/utils ``` -Ustvarimo objekt za paging in mu določimo osnovne informacije: +Ustvarimo objekt straničnika in mu nastavimo osnovne informacije: ```php $paginator = new Nette\Utils\Paginator; -$paginator->setPage(1); // številka trenutne strani (oštevilčena od 1). -$paginator->setItemsPerPage(30); // število zapisov na strani -$paginator->setItemCount(356); // skupno število zapisov (če je na voljo) +$paginator->setPage(1); // številka trenutne strani +$paginator->setItemsPerPage(30); // število postavk na strani +$paginator->setItemCount(356); // skupno število postavk, če je znano ``` -Stranke so oštevilčene od 1. To lahko spremenimo z uporabo `setBase()`: +Strani se številčijo od 1. To lahko spremenimo z `setBase()`: ```php -$paginator->setBase(0); // oštevilčeni od 0 +$paginator->setBase(0); // številčimo od 0 ``` -Predmet bo zdaj vseboval vse osnovne informacije, ki so uporabne pri ustvarjanju paginatorja. Lahko ga na primer posredujete predlogi in ga tam uporabite. +Objekt zdaj ponuja vse osnovne informacije, uporabne pri ustvarjanju straničnika. Lahko ga na primer posredujete v predlogo in ga tam uporabite. ```php -$paginator->isFirst(); // je to prva stran? -$paginator->isLast(); // je to zadnja stran? +$paginator->isFirst(); // smo na prvi strani? +$paginator->isLast(); // smo na zadnji strani? $paginator->getPage(); // številka trenutne strani $paginator->getFirstPage(); // številka prve strani $paginator->getLastPage(); // številka zadnje strani -$paginator->getFirstItemOnPage(); // zaporedna številka prvega elementa na strani -$paginator->getLastItemOnPage(); // zaporedna številka zadnjega elementa na strani -$paginator->getPageIndex(); // tekoča številka strani, če je oštevilčena od 0 +$paginator->getFirstItemOnPage(); // zaporedna številka prve postavke na strani +$paginator->getLastItemOnPage(); // zaporedna številka zadnje postavke na strani +$paginator->getPageIndex(); // številka trenutne strani, številčena od 0 $paginator->getPageCount(); // skupno število strani -$paginator->getItemsPerPage(); // število zapisov na stran -$paginator->getItemCount(); // skupno število zapisov (če je na voljo) +$paginator->getItemsPerPage(); // število postavk na stran +$paginator->getItemCount(); // skupno število postavk, če je znano ``` -Paginator bo pomagal pri oblikovanju poizvedbe SQL. Metodi `getLength()` in `getOffset()` vrneta vrednosti, ki jih lahko uporabite v stavkih LIMIT in OFFSET: +Straničnik pomaga pri oblikovanju SQL poizvedbe. Metodi `getLength()` in `getOffset()` vračata vrednosti, ki jih uporabimo v klavzulah LIMIT in OFFSET: ```php $result = $database->query( @@ -52,7 +52,7 @@ $result = $database->query( ); ``` -Če želite paginirati v obratnem vrstnem redu, tj. stran št. 1 ustreza najvišjemu odmiku, lahko uporabite `getCountdownOffset()`: +Če moramo straničiti v obratnem vrstnem redu, tj. stran št. 1 ustreza najvišjemu odmiku, uporabimo `getCountdownOffset()`: ```php $result = $database->query( @@ -62,4 +62,4 @@ $result = $database->query( ); ``` -Primer uporabe v aplikaciji je na voljo v kuharski knjigi [Paginating Database Results |best-practices:pagination]. +Primer uporabe v aplikaciji najdete v kuharski knjigi [Straničenje rezultatov podatkovne baze |best-practices:pagination]. diff --git a/utils/sl/random.texy b/utils/sl/random.texy index 66de2b3344..cbd61ec487 100644 --- a/utils/sl/random.texy +++ b/utils/sl/random.texy @@ -1,5 +1,5 @@ -Generator naključnih nizov -************************** +Generiranje naključnih nizov +**************************** .[perex] [api:Nette\Utils\Random] je statični razred za generiranje kriptografsko varnih psevdonaključnih nizov. @@ -15,7 +15,7 @@ composer require nette/utils generate(int $length=10, string $charlist=`'0-9a-z'`): string .[method] ----------------------------------------------------------------------- -Iz znakov, določenih v drugem argumentu, ustvari naključni niz dane dolžine. Podpira intervale, kot sta `0-9` ali `A-Z`. +Generira naključni niz dane dolžine iz znakov, določenih s parametrom `$charlist`. Uporabljati je mogoče tudi intervale, zapisane kot na primer `0-9`. ```php use Nette\Utils\Random; diff --git a/utils/sl/reflection.texy b/utils/sl/reflection.texy index bef8e3e28c..41fa220dbe 100644 --- a/utils/sl/reflection.texy +++ b/utils/sl/reflection.texy @@ -1,8 +1,8 @@ -Odsev PHP -********* +PHP Refleksija +************** .[perex] -[api:Nette\Utils\Reflection] je statični razred z uporabnimi funkcijami za refleksijo PHP. Njegov namen je odpraviti pomanjkljivosti v izvirnih razredih in poenotiti obnašanje v različnih različicah PHP. +[api:Nette\Utils\Reflection] je statični razred z uporabnimi funkcijami za PHP refleksijo. Njegova naloga je popravljati pomanjkljivosti izvornih razredov in poenotiti obnašanje med različnimi različicami PHP. Namestitev: @@ -11,7 +11,7 @@ Namestitev: composer require nette/utils ``` -Vsi primeri predpostavljajo, da je definiran naslednji vzdevek razreda: +Vsi primeri predpostavljajo ustvarjen vzdevek: ```php use Nette\Utils\Reflection; @@ -21,13 +21,13 @@ use Nette\Utils\Reflection; areCommentsAvailable(): bool .[method] -------------------------------------- -Ugotovi, ali ima refleksija dostop do komentarjev PHPdoc. Komentarji morda niso na voljo zaradi predpomnilnika opkod, glejte na primer direktivo [opcache.save-comments |https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments]. +Ugotovi, ali ima refleksija dostop do PHPdoc komentarjev. Komentarji so lahko nedostopni zaradi opcode predpomnilnika, glejte na primer direktivo [opcache.save-comments|https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments]. expandClassName(string $name, ReflectionClass $context): string .[method] ------------------------------------------------------------------------- -Razširi `$name` razreda na polno ime v kontekstu `$context`, tj. v kontekstu njegovega imenskega prostora in opredeljenih vzdevkov. Tako vrne, kako bi razčlenjevalnik PHP razumel `$name`, če bi bil zapisan v telesu `$context`. +Razširi ime razreda `$name` na njegovo polno ime v kontekstu razreda `$context`, torej v kontekstu njegovega imenskega prostora in definiranih aliasov. Torej dejansko pove, kako bi PHP razčlenjevalnik razumel `$name`, če bi bil zapisan v telesu razreda `$context`. ```php namespace Foo; @@ -47,9 +47,9 @@ Reflection::expandClassName('Baz', $context); // 'Foo\Baz' getMethodDeclaringMethod(ReflectionMethod $method): ReflectionMethod .[method] ------------------------------------------------------------------------------ -Vrne odsev metode, ki vsebuje deklaracijo `$method`. Običajno je vsaka metoda samostojna deklaracija, telo metode pa je lahko tudi v lastnosti in pod drugim imenom. +Vrne refleksijo metode, ki vsebuje deklaracijo metode `$method`. Običajno je vsaka metoda svoja lastna deklaracija, vendar se telo metode lahko nahaja tudi v lastnosti (trait) in pod drugim imenom. -Ker PHP ne zagotavlja dovolj informacij za določitev dejanske deklaracije, Nette uporabi lastno hevristiko, ki bi morala biti **zanesljiva**. +Ker PHP ne zagotavlja zadostnih informacij, s katerimi bi bilo mogoče ugotoviti dejansko deklaracijo, Nette uporablja lastno hevristiko, ki **bi morala biti** zanesljiva. ```php trait DemoTrait @@ -76,9 +76,9 @@ Reflection::getMethodDeclaringMethod($method); // ReflectionMethod('DemoTrait::f getPropertyDeclaringClass(ReflectionProperty $prop): ReflectionClass .[method] ------------------------------------------------------------------------------ -Vrne odsev razreda ali lastnosti, ki vsebuje deklaracijo lastnosti `$prop`. Lastnost je lahko deklarirana tudi v lastnosti. +Vrne refleksijo razreda ali lastnosti (trait), ki vsebuje deklaracijo lastnosti `$prop`. Lastnost je namreč lahko deklarirana tudi v lastnosti (trait). -Ker PHP ne zagotavlja dovolj informacij za določitev dejanske deklaracije, Nette uporabi lastno hevristiko, ki pa **ni** zanesljiva. +Ker PHP ne zagotavlja zadostnih informacij, s katerimi bi bilo mogoče ugotoviti dejansko deklaracijo, Nette uporablja lastno hevristiko, ki **ni** zanesljiva. ```php trait DemoTrait @@ -100,7 +100,7 @@ Reflection::getPropertyDeclaringClass($prop); // ReflectionClass('DemoTrait') isBuiltinType(string $type): bool .[method deprecated] ------------------------------------------------------ -Določi, ali je `$type` vgrajena vrsta PHP. V nasprotnem primeru je ime razreda. +Ugotovi, ali je `$type` vgrajen tip PHP. V nasprotnem primeru gre za ime razreda. ```php Reflection::isBuiltinType('string'); // true @@ -108,13 +108,13 @@ Reflection::isBuiltinType('Foo'); // false ``` .[note] -Uporabite [Nette\Utils\Validator::isBuiltinType(). |validators#isBuiltinType] +Uporabite [Nette\Utils\Validator::isBuiltinType() |validators#isBuiltinType]. toString($reflection): string .[method] --------------------------------------- -Pretvori odsev v človeku berljiv niz. +Pretvori refleksijo v človeško razumljiv niz. ```php $func = new ReflectionFunction('func'); @@ -127,7 +127,7 @@ $method = new ReflectionMethod('DemoClass', 'foo'); echo Reflection::toString($method); // 'DemoClass::foo()' $param = new ReflectionParameter(['DemoClass', 'foo'], 'param'); -echo Reflection::toString($param); // '$param in DemoClass::foo()' +echo Reflection::toString($param); // '$param v DemoClass::foo()' $prop = new ReflectionProperty('DemoClass', 'foo'); echo Reflection::toString($prop); // 'DemoClass::$foo' diff --git a/utils/sl/smartobject.texy b/utils/sl/smartobject.texy index 212a575673..a4cfc4824a 100644 --- a/utils/sl/smartobject.texy +++ b/utils/sl/smartobject.texy @@ -1,8 +1,8 @@ -SmartObject in StaticClass -************************** +SmartObject +*********** .[perex] -SmartObject razredom PHP doda podporo za *lastnosti*. StaticClass se uporablja za označevanje statičnih razredov. +SmartObject je leta izboljševal obnašanje objektov v PHP. Od različice PHP 8.4 so vse njegove funkcije že del samega PHP, s čimer je zaključil svojo zgodovinsko misijo biti pionir sodobnega objektnega pristopa v PHP. Namestitev: @@ -11,19 +11,64 @@ Namestitev: composer require nette/utils ``` +SmartObject je nastal leta 2007 kot revolucionarna rešitev za pomanjkljivosti takratnega objektnega modela PHP. V času, ko je PHP trpel zaradi številnih težav z objektnim načrtovanjem, je prinesel znatno izboljšanje in poenostavitev dela za razvijalce. Postal je legendarni del ogrodja Nette. Ponujal je funkcionalnost, ki jo je PHP pridobil šele mnogo let kasneje - od nadzora dostopa do lastnosti objektov do sofisticiranih sintaktičnih sladkorčkov. S prihodom PHP 8.4 je zaključil svojo zgodovinsko misijo, saj so vse njegove funkcije postale naravni del jezika. Prehitel je razvoj PHP za izjemnih 17 let. -Lastnosti, nastavljalci in pridobitelji .[#toc-properties-getters-and-setters] -============================================================================== +Tehnično je SmartObject doživel zanimiv razvoj. Prvotno je bil implementiran kot razred `Nette\Object`, od katerega so drugi razredi podedovali potrebno funkcionalnost. Ključna sprememba je prišla s PHP 5.4, ki je prinesel podporo za traite. To je omogočilo preoblikovanje v obliko traite `Nette\SmartObject`, kar je prineslo večjo prilagodljivost - razvijalci so lahko funkcionalnost uporabili tudi v razredih, ki so že dedovali od drugega razreda. Medtem ko je prvotni razred `Nette\Object` izginil s prihodom PHP 7.2 (ki je prepovedal poimenovanje razredov z besedo `Object`), traita `Nette\SmartObject` živi naprej. -V sodobnih objektno usmerjenih jezikih (npr. C#, Python, Ruby, JavaScript) se izraz *lastnost* nanaša na [posebne člane razredov |https://en.wikipedia.org/wiki/Property_(programming)], ki so videti kot spremenljivke, v resnici pa jih predstavljajo metode. Ko se vrednost te "spremenljivke" dodeli ali prebere, se pokliče ustrezna metoda (imenovana getter ali setter). To je zelo priročno, saj nam omogoča popoln nadzor nad dostopom do spremenljivk. Potrdimo lahko vnos ali ustvarimo rezultate samo takrat, ko je lastnost prebrana. +Poglejmo si lastnosti, ki sta jih nekoč ponujala `Nette\Object` in kasneje `Nette\SmartObject`. Vsaka od teh funkcij je v svojem času predstavljala pomemben korak naprej na področju objektno usmerjenega programiranja v PHP. -Lastnosti PHP niso podprte, vendar jih lahko posnemamo z lastnostmi `Nette\SmartObject`. Kako jo uporabiti? -- V razred dodajte opombo v obliki `@property $xyz` -- Ustvarite getter z imenom `getXyz()` ali `isXyz()`, setter z imenom `setXyz()` -- Getter in setter morata biti *javna* ali *zaščitena* in sta neobvezna, zato je lahko lastnost *samo za branje* ali *samo za pisanje* +Konsistentna stanja napak +------------------------- +Eden najbolj perečih problemov zgodnjega PHP je bilo nekonsistentno obnašanje pri delu z objekti. `Nette\Object` je v ta kaos vnesel red in predvidljivost. Poglejmo, kako je izgledalo prvotno obnašanje PHP: -Lastnost za razred Circle bomo uporabili za zagotovitev, da se v spremenljivko `$radius` vnesejo samo nenegativna števila. Zamenjajte `public $radius` z lastnostjo: +```php +echo $obj->undeclared; // E_NOTICE, kasneje E_WARNING +$obj->undeclared = 1; // gre tiho skozi brez poročanja +$obj->unknownMethod(); // Fatal error (neulovljiv s try/catch) +``` + +Fatal error je končal aplikacijo brez možnosti kakršnega koli odziva. Tiho zapisovanje v neobstoječe člane brez opozorila je lahko vodilo do resnih napak, ki jih je bilo težko odkriti. `Nette\Object` je vse te primere prestregel in vrgel izjemo `MemberAccessException`, kar je programerjem omogočilo, da se na napake odzovejo in jih rešijo. + +```php +echo $obj->undeclared; // vrže Nette\MemberAccessException +$obj->undeclared = 1; // vrže Nette\MemberAccessException +$obj->unknownMethod(); // vrže Nette\MemberAccessException +``` + +Od PHP 7.0 jezik ne povzroča več neulovljivih fatalnih napak in od PHP 8.2 se dostop do nedeklariranih članov šteje za napako. + + +Pomoč "Did you mean?" +--------------------- +`Nette\Object` je prišel z zelo prijetno funkcijo: inteligentno pomočjo pri tipkarskih napakah. Ko je razvijalec naredil napako v imenu metode ali spremenljivke, ni samo sporočil napake, ampak je ponudil tudi pomoč v obliki predloga pravilnega imena. To ikonično sporočilo, znano kot "did you mean?", je programerjem prihranilo ure iskanja tipkarskih napak: + +```php +class Foo extends Nette\Object +{ + public static function from($var) + { + } +} + +$foo = Foo::form($var); +// vrže Nette\MemberAccessException +// "Call to undefined static method Foo::form(), did you mean from()?" +``` + +Današnji PHP sicer nima nobene oblike „did you mean?“, vendar ta dodatek zna v napake dodajati [Tracy|tracy:]. In celo takšne napake [samodejno popravljati |tracy:open-files-in-ide#Primeri]. + + +Lastnosti z nadzorovanim dostopom +--------------------------------- +Pomembna inovacija, ki jo je SmartObject prinesel v PHP, so bile lastnosti z nadzorovanim dostopom. Ta koncept, običajen v jezikih, kot sta C# ali Python, je razvijalcem omogočil elegantno nadzorovanje dostopa do podatkov objekta in zagotavljanje njihove konsistentnosti. Lastnosti so močno orodje objektno usmerjenega programiranja. Delujejo kot spremenljivke, vendar so dejansko predstavljene z metodami (getterji in setterji). To omogoča validacijo vnosov ali generiranje vrednosti šele v trenutku branja. + +Za uporabo lastnosti morate: +- Dodati razredu anotacijo v obliki `@property $xyz` +- Ustvariti getter z imenom `getXyz()` ali `isXyz()`, setter z imenom `setXyz()` +- Zagotoviti, da sta getter in setter *public* ali *protected*. Sta izbirna - lahko torej obstajata kot *read-only* ali *write-only* lastnost + +Poglejmo si praktičen primer na razredu Circle, kjer lastnosti uporabimo za zagotovitev, da bo polmer vedno nenegativno število. Nadomestimo prvotno `public $radius` z lastnostjo: ```php /** @@ -34,7 +79,7 @@ class Circle { use Nette\SmartObject; - private float $radius = 0.0; // ni javno + private float $radius = 0.0; // ni public! // getter za lastnost $radius protected function getRadius(): float @@ -45,7 +90,7 @@ class Circle // setter za lastnost $radius protected function setRadius(float $radius): void { - // sanitizacija vrednosti pred shranjevanjem + // vrednost pred shranjevanjem saniramo $this->radius = max(0.0, $radius); } @@ -57,84 +102,30 @@ class Circle } $circle = new Circle; -$circle->radius = 10; // dejansko pokliče setRadius(10) -echo $circle->radius; // pokliče getRadius() -echo $circle->visible; // pokliče isVisible() +$circle->radius = 10; // dejansko kliče setRadius(10) +echo $circle->radius; // kliče getRadius() +echo $circle->visible; // kliče isVisible() ``` -Lastnosti so predvsem "sintaktični sladkor"((sintactic sugar)), ki je namenjen temu, da programerju s poenostavitvijo kode osladi življenje. Če jih ne želite, vam jih ni treba uporabljati. - - -Statični razredi .[#toc-static-classes] -======================================= - -Statične razrede, tj. razrede, ki niso namenjeni instanciranju, lahko označimo z lastnostjo `Nette\StaticClass`: +Od PHP 8.4 je mogoče doseči enako funkcionalnost z uporabo property hooks, ki ponujajo veliko bolj elegantno in jedrnato sintakso: ```php -class Strings +class Circle { - use Nette\StaticClass; -} -``` - -Pri poskusu ustvarjanja instance se vrže izjema `Error`, ki označuje, da je razred statičen. - - -Pogled v zgodovino .[#toc-a-look-into-the-history] -================================================== - -SmartObject je v preteklosti na različne načine izboljševal in popravljal obnašanje razredov, vendar je zaradi razvoja PHP večina prvotnih funkcij postala odveč. Zato je v nadaljevanju podan pogled v zgodovino, kako so se stvari razvijale. - -Objektni model PHP je že od začetka trpel zaradi številnih resnih pomanjkljivosti in neučinkovitosti. Zato je bil leta 2007 oblikovan razred `Nette\Object`, ki jih je poskušal odpraviti in izboljšati izkušnjo uporabe PHP. Dovolj je bilo, da so ga podedovali tudi drugi razredi in pridobili prednosti, ki jih je prinesel. Ko je PHP 5.4 začel podpirati lastnosti, je razred `Nette\Object` nadomestil razred `Nette\SmartObject`. Tako ni bilo več treba dedovati po skupnem predniku. Poleg tega je bilo trait mogoče uporabiti v razredih, ki so že dedovali od drugega razreda. Dokončni konec razreda `Nette\Object` se je zgodil z izdajo PHP 7.2, ki je prepovedal, da bi se razredi imenovali `Object`. - -Z nadaljnjim razvojem jezika PHP sta se izboljšala objektni model in zmožnosti jezika. Posamezne funkcije razreda `SmartObject` so postale nepotrebne. Od izdaje PHP 8.2 je ostala edina funkcija, ki še ni neposredno podprta v PHP, možnost uporabe tako imenovanih [lastnosti |#Properties, Getters and Setters]. - -Katere funkcije sta nekoč ponujala razreda `Nette\Object` in `Nette\Object`? Tukaj je pregled. (Primeri uporabljajo razred `Nette\Object`, vendar večina lastnosti velja tudi za lastnost `Nette\SmartObject`.) - - -Nedosledne napake .[#toc-inconsistent-errors] ---------------------------------------------- -PHP se je nedosledno obnašal pri dostopu do nereklariranih članov. Stanje v času `Nette\Object` je bilo naslednje: - -```php -echo $obj->undeclared; // E_NOTICE, pozneje E_WARNING -$obj->undeclared = 1; // poteka tiho, brez poročanja -$obj->unknownMethod(); // usodna napaka (ki je ni mogoče ujeti s funkcijo try/catch) -``` - -Usodna napaka je prekinila aplikacijo brez možnosti odziva. Tiho pisanje v neobstoječe člene brez opozorila bi lahko privedlo do resnih napak, ki bi jih bilo težko odkriti. `Nette\Object` Vsi ti primeri so bili ujeti in zavržena je bila izjema `MemberAccessException`. - -```php -echo $obj->undeclared; // vrgel Izjemo Nette\MemberAccessException -$obj->undeclared = 1; // throw Nette\MemberAccessException -$obj->unknownMethod(); // throw Nette\MemberAccessException -``` -Od PHP 7.0 PHP ne povzroča več usodnih napak, ki jih ni mogoče ujeti, dostop do nedeklariranih članov pa je napaka od PHP 8.2. - - -Ali ste mislili? .[#toc-did-you-mean] -------------------------------------- -Če se je vrgla napaka `Nette\MemberAccessException`, morda zaradi tiskarske napake pri dostopu do predmetne spremenljivke ali klicu metode, je `Nette\Object` v sporočilu o napaki poskušal podati namig, kako napako odpraviti, in sicer v obliki ikoničnega dodatka "Ali ste mislili?". + public float $radius = 0.0 { + set => max(0.0, $value); + } -```php -class Foo extends Nette\Object -{ - public static function from($var) - { + public bool $visible { + get => $this->radius > 0; } } - -$foo = Foo::form($var); -// throw Nette\MemberAccessException -// "Call to undefined static method Foo::form(), did you mean from()?" ``` -Današnji PHP morda nima oblike "ali ste mislili?", vendar [Tracy |tracy:] napakam dodaja ta dodatek. In take napake lahko celo sam [popravi |tracy:open-files-in-ide#toc-demos]. - -Metode razširitve .[#toc-extension-methods] -------------------------------------------- -Navdih za razširitvene metode iz C#. Omogočale so dodajanje novih metod obstoječim razredom. Na primer, obrazcu lahko dodate metodo `addDateTime()` in tako dodate svoj DateTimePicker. +Razširitvene metode +------------------- +`Nette\Object` je v PHP prinesel še en zanimiv koncept, navdihnjen s sodobnimi programskimi jeziki - razširitvene metode. Ta funkcija, prevzeta iz C#, je razvijalcem omogočila elegantno razširjanje obstoječih razredov z novimi metodami brez potrebe po njihovem urejanju ali dedovanju od njih. Na primer, lahko ste v obrazec dodali metodo `addDateTime()`, ki doda lasten DateTimePicker: ```php Form::extensionMethod( @@ -146,11 +137,12 @@ $form = new Form; $form->addDateTime('date'); ``` -Razširitvene metode so se izkazale za nepraktične, saj urejevalniki njihovih imen niso samodejno dopolnili, temveč so sporočili, da metoda ne obstaja. Zato je bila njihova podpora ukinjena. +Razširitvene metode so se izkazale za nepraktične, saj njihova imena niso bila predlagana s strani urejevalnikov, nasprotno, poročali so, da metoda ne obstaja. Zato je bila njihova podpora ukinjena. Danes je bolj običajno uporabljati kompozicijo ali dedovanje za razširitev funkcionalnosti razredov. -Pridobivanje imena razreda .[#toc-getting-the-class-name] ---------------------------------------------------------- +Ugotavljanje imena razreda +-------------------------- +Za ugotavljanje imena razreda je SmartObject ponujal preprosto metodo: ```php $class = $obj->getClass(); // z uporabo Nette\Object @@ -158,10 +150,9 @@ $class = $obj::class; // od PHP 8.0 ``` -Dostop do razmisleka in opomb .[#toc-access-to-reflection-and-annotations] --------------------------------------------------------------------------- - -`Nette\Object` ponujen dostop do refleksije in anotacij z metodama `getReflection()` in `getAnnotation()`: +Dostop do refleksije in anotacij +-------------------------------- +`Nette\Object` je ponujal dostop do refleksije in anotacij z metodama `getReflection()` in `getAnnotation()`. Ta pristop je znatno poenostavil delo z meta-informacijami razredov: ```php /** @@ -173,10 +164,10 @@ class Foo extends Nette\Object $obj = new Foo; $reflection = $obj->getReflection(); -$reflection->getAnnotation('author'); // vrne 'John Doe +$reflection->getAnnotation('author'); // vrne 'John Doe' ``` -Od različice PHP 8.0 je mogoče dostopati do metainformacij v obliki atributov: +Od PHP 8.0 je mogoče dostopati do meta-informacij v obliki atributov, ki ponujajo še več možnosti in boljši tipski nadzor: ```php #[Author('John Doe')] @@ -190,10 +181,9 @@ $reflection->getAttributes(Author::class)[0]; ``` -Metode, ki pridobivajo .[#toc-method-getters] ---------------------------------------------- - -`Nette\Object` je ponujal eleganten način za ravnanje z metodami, kot da bi bile spremenljivke: +Metodni getterji +---------------- +`Nette\Object` je ponujal eleganten način za posredovanje metod, kot da bi šlo za spremenljivke: ```php class Foo extends Nette\Object @@ -209,7 +199,7 @@ $method = $obj->adder; echo $method(2, 3); // 5 ``` -Od različice PHP 8.1 lahko uporabljate tako imenovano sintakso "first-class callable syntax"::https://www.php.net/manual/en/functions.first_class_callable_syntax +Od PHP 8.1 je mogoče uporabiti t.i. "first-class callable syntax":https://www.php.net/manual/en/functions.first_class_callable_syntax, ki ta koncept pelje še dlje: ```php $obj = new Foo; @@ -218,10 +208,9 @@ echo $method(2, 3); // 5 ``` -Dogodki .[#toc-events] ----------------------- - -`Nette\Object` ponujen sintaktični sladkor za sprožitev [dogodka |nette:glossary#events]: +Dogodki +------- +SmartObject ponuja poenostavljeno sintakso za delo z [dogodki |nette:glossary#Dogodki eventi]. Dogodki omogočajo objektom, da obveščajo druge dele aplikacije o spremembah svojega stanja: ```php class Circle extends Nette\Object @@ -231,12 +220,12 @@ class Circle extends Nette\Object public function setRadius(float $radius): void { $this->onChange($this, $radius); - $this->radius = $radius + $this->radius = $radius; } } ``` -Koda `$this->onChange($this, $radius)` je enakovredna naslednjemu: +Koda `$this->onChange($this, $radius)` je ekvivalentna naslednji zanki: ```php foreach ($this->onChange as $callback) { @@ -244,7 +233,7 @@ foreach ($this->onChange as $callback) { } ``` -Zaradi jasnosti priporočamo, da se izognete čarobni metodi `$this->onChange()`. Praktično nadomestilo je funkcija [Nette\Utils\Arrays::invoke |arrays#invoke]: +Zaradi razumljivosti priporočamo, da se izogibate magični metodi `$this->onChange()`. Praktična zamenjava je na primer funkcija [Nette\Utils\Arrays::invoke |arrays#invoke]: ```php Nette\Utils\Arrays::invoke($this->onChange, $this, $radius); diff --git a/utils/sl/staticclass.texy b/utils/sl/staticclass.texy new file mode 100644 index 0000000000..75aef8cffb --- /dev/null +++ b/utils/sl/staticclass.texy @@ -0,0 +1,21 @@ +Statični razredi +**************** + +.[perex] +StaticClass služi za označevanje statičnih razredov. + + +Namestitev: + +```shell +composer require nette/utils +``` + +Statične razrede, torej razrede, ki niso namenjeni ustvarjanju instanc, lahko označite s traito [api:Nette\StaticClass]: + +```php +class Strings +{ + use Nette\StaticClass; +} +``` diff --git a/utils/sl/strings.texy b/utils/sl/strings.texy index 9289376977..91c486923e 100644 --- a/utils/sl/strings.texy +++ b/utils/sl/strings.texy @@ -1,8 +1,8 @@ -Funkcije nizov -************** +Delo z nizi +*********** .[perex] -[api:Nette\Utils\Strings] je statični razred, ki vsebuje številne uporabne funkcije za delo z nizi, kodiranimi v zapisu UTF-8. +[api:Nette\Utils\Strings] je statični razred z uporabnimi funkcijami za delo z nizi, pretežno v kodiranju UTF-8. Namestitev: @@ -11,83 +11,83 @@ Namestitev: composer require nette/utils ``` -Vsi primeri predpostavljajo, da je definiran naslednji vzdevek razreda: +Vsi primeri predpostavljajo ustvarjen alias: ```php use Nette\Utils\Strings; ``` -Velikost črke .[#toc-letter-case] -================================= +Spreminjanje velikosti črk +========================== -Te funkcije zahtevajo razširitev PHP `mbstring`. +Te funkcije zahtevajo PHP razširitev `mbstring`. lower(string $s): string .[method] ---------------------------------- -Pretvori vse znake niza UTF-8 v male črke. +Pretvori UTF-8 niz v male črke. ```php -Strings::lower('Hello world'); // 'hello world' +Strings::lower('Dobrý den'); // 'dobrý den' ``` upper(string $s): string .[method] ---------------------------------- -Pretvori vse znake niza UTF-8 v velike črke. +Pretvori UTF-8 niz v velike črke. ```php -Strings::upper('Hello world'); // 'HELLO WORLD' +Strings::upper('Dobrý den'); // 'DOBRÝ DEN' ``` firstUpper(string $s): string .[method] --------------------------------------- -Prvi znak niza UTF-8 pretvori v velike črke, drugi znaki ostanejo nespremenjeni. +Pretvori prvo črko UTF-8 niza v veliko, ostalih ne spreminja. ```php -Strings::firstUpper('hello world'); // 'Hello world' +Strings::firstUpper('dobrý den'); // 'Dobrý den' ``` firstLower(string $s): string .[method] --------------------------------------- -Prvi znak niza UTF-8 pretvori v male črke, drugi znaki pa ostanejo nespremenjeni. +Pretvori prvo črko UTF-8 niza v malo, ostalih ne spreminja. ```php -Strings::firstLower('Hello world'); // 'hello world' +Strings::firstLower('Dobrý den'); // 'dobrý den' ``` capitalize(string $s): string .[method] --------------------------------------- -Pretvori prvi znak vsake besede v nizu UTF-8 v velike črke, druge pa v male črke. +Pretvori prvo črko vsake besede v UTF-8 nizu v veliko, ostale v male. ```php -Strings::capitalize('Hello world'); // 'Hello World' +Strings::capitalize('Dobrý den'); // 'Dobrý Den' ``` -Urejanje niza .[#toc-editing-a-string] -====================================== +Urejanje niza +============= normalize(string $s): string .[method] -------------------------------------- -Odstrani kontrolne znake, normalizira prelome vrstic na `\n`, odstrani vodilne in zadnje prazne vrstice, obreže končne presledke v vrsticah, normalizira UTF-8 na normalno obliko NFC. +Odstrani kontrolne znake, normalizira konce vrstic na `\n`, obreže začetne in končne prazne vrstice, obreže desne presledke v vrsticah, normalizira UTF-8 v normalno obliko NFC. unixNewLines(string $s): string .[method] ----------------------------------------- -Pretvarja prelome vrstic v `\n`, ki se uporabljajo v sistemih Unix. Prelomi vrstic so: `\n`, `\r`, `\r\n`, U+2028 ločilo vrstic, U+2029 ločilo odstavkov. +Pretvori konce vrstic v `\n`, ki se uporablja v unix sistemih. Konci vrstic so: `\n`, `\r`, `\r\n`, U+2028 line separator, U+2029 paragraph separator. ```php $unixLikeLines = Strings::unixNewLines($string); @@ -97,42 +97,42 @@ $unixLikeLines = Strings::unixNewLines($string); platformNewLines(string $s): string .[method] --------------------------------------------- -Pretvarja prelome vrstic v znake, značilne za trenutno platformo, tj. `\r\n` v operacijskem sistemu Windows in `\n` drugje. Prekinitve vrstic so `\n`, `\r`, `\r\n`, U+2028 ločilo vrstic, U+2029 ločilo odstavkov. +Pretvori konce vrstic v znake, specifične za trenutno platformo, tj. `\r\n` na Windows in `\n` drugje. Konci vrstic so: `\n`, `\r`, `\r\n`, U+2028 line separator, U+2029 paragraph separator. ```php $platformLines = Strings::platformNewLines($string); ``` -webalize(string $s, string $charlist=null, bool $lower=true): string .[method] ------------------------------------------------------------------------------- +webalize(string $s, ?string $charlist=null, bool $lower=true): string .[method] +------------------------------------------------------------------------------- -Spremeni niz UTF-8 v obliko, ki se uporablja v naslovu URL, tj. odstrani diakritiko in vse znake razen črk angleške abecede in številk nadomesti s pomišljajem. +Uredi UTF-8 niz v obliko, ki se uporablja v URL-jih, tj. odstrani diakritiko in vse znake, razen črk angleške abecede in številk, nadomesti z vezajem. ```php -Strings::webalize('žluťoučký kůň'); // 'zlutoucky-kun' +Strings::webalize('náš produkt'); // 'nas-produkt' ``` -Ohranijo se lahko tudi drugi znaki, vendar jih je treba posredovati kot drugi argument. +Če naj se ohranijo tudi drugi znaki, jih lahko navedete v drugem parametru funkcije. ```php -Strings::webalize('10. image_id', '._'); // '10.-image_id' +Strings::webalize('10. obrázek_id', '._'); // '10.-obrazek_id' ``` -Tretji argument lahko prepreči pretvorbo niza v male črke. +S tretjim parametrom lahko preprečite pretvorbo v male črke. ```php -Strings::webalize('Hello world', null, false); // 'Hello-world' +Strings::webalize('Dobrý den', null, false); // 'Dobry-den' ``` .[caution] -Zahteva razširitev PHP `intl`. +Zahteva PHP razširitev `intl`. -trim(string $s, string $charlist=null): string .[method] --------------------------------------------------------- +trim(string $s, ?string $charlist=null): string .[method] +--------------------------------------------------------- -Odstrani vse leve in desne presledke (ali znake, posredovane kot drugi argument) iz niza, kodiranega v UTF-8. +Obreže presledke (ali druge znake, določene z drugim parametrom) z začetka in konca UTF-8 niza. ```php Strings::trim(' Hello '); // 'Hello' @@ -142,21 +142,21 @@ Strings::trim(' Hello '); // 'Hello' truncate(string $s, int $maxLen, string $append=`'…'`): string .[method] ------------------------------------------------------------------------ -Skrajša niz UTF-8 na določeno največjo dolžino, pri čemer poskuša preprečiti deljenje celih besed. Samo če je niz skrajšan, se nizu doda elipsa (ali kaj drugega, nastavljenega s tretjim argumentom). +Obreže UTF-8 niz na navedeno največjo dolžino, pri čemer poskuša ohraniti cele besede. Če pride do skrajšanja niza, na konec doda tri pike (lahko spremenite s tretjim parametrom). ```php -$text = 'Hello, how are you today?'; -Strings::truncate($text, 5); // 'Hell…' -Strings::truncate($text, 20); // 'Hello, how are you…' -Strings::truncate($text, 30); // 'Hello, how are you today?' -Strings::truncate($text, 20, '~'); // 'Hello, how are you~' +$text = 'Povejte, kako ste?'; // Povejte, kako ste? +Strings::truncate($text, 5); // 'Povej…' +Strings::truncate($text, 20); // 'Povejte, kako se…' +Strings::truncate($text, 30); // 'Povejte, kako ste?' +Strings::truncate($text, 20, '~'); // 'Povejte, kako se~' ``` indent(string $s, int $level=1, string $indentationChar=`"\t"`): string .[method] --------------------------------------------------------------------------------- -Odmik večvrstičnega besedila z leve strani. Drugi argument določa, koliko znakov za alinejo naj se uporabi, medtem ko je sama alineja tretji argument (privzeto *tab*). +Zamakne večvrstično besedilo z leve. Število zamikov določa drugi parameter, s čim zamakniti pa tretji parameter (privzeta vrednost je tabulator). ```php Strings::indent('Nette'); // "\tNette" @@ -167,7 +167,7 @@ Strings::indent('Nette', 2, '+'); // '++Nette' padLeft(string $s, int $length, string $pad=`' '`): string .[method] -------------------------------------------------------------------- -Izpolni niz UTF-8 na določeno dolžino tako, da na začetek doda niz `$pad`. +Dopolni UTF-8 niz do podane dolžine s ponavljanjem niza `$pad` z leve. ```php Strings::padLeft('Nette', 6); // ' Nette' @@ -178,7 +178,7 @@ Strings::padLeft('Nette', 8, '+*'); // '+*+Nette' padRight(string $s, int $length, string $pad=`' '`): string .[method] --------------------------------------------------------------------- -Podloži niz UTF-8 na določeno dolžino tako, da na konec doda niz `$pad`. +Dopolni UTF-8 niz do podane dolžine s ponavljanjem niza `$pad` z desne. ```php Strings::padRight('Nette', 6); // 'Nette ' @@ -186,10 +186,10 @@ Strings::padRight('Nette', 8, '+*'); // 'Nette+*+' ``` -substring(string $s, int $start, int $length=null): string .[method] --------------------------------------------------------------------- +substring(string $s, int $start, ?int $length=null): string .[method] +--------------------------------------------------------------------- -Vrne del niza UTF-8, ki ga določata začetni položaj `$start` in dolžina `$length`. Če je `$start` negativen, se vrnjen niz začne pri `$start`'-tem znaku od konca niza. +Vrne del UTF-8 niza `$s`, določen z začetno pozicijo `$start` in dolžino `$length`. Če je `$start` negativen, se bo vrnjeni niz začel z znakom -`$start` od konca. ```php Strings::substring('Nette Framework', 0, 5); // 'Nette' @@ -201,7 +201,7 @@ Strings::substring('Nette Framework', -4); // 'work' reverse(string $s): string .[method] ------------------------------------ -Obrne niz UTF-8. +Obrne UTF-8 niz. ```php Strings::reverse('Nette'); // 'etteN' @@ -213,75 +213,75 @@ length(string $s): int .[method] Vrne število znakov (ne bajtov) v nizu UTF-8. -To je število kodnih točk Unicode, ki se lahko razlikuje od števila grafemov. +To je število kodnih točk Unicode, ki se lahko razlikujejo od števila grafemov. ```php -Strings::length('Nette'); // 5 -Strings::length('red'); // 3 +Strings::length('Nette'); // 5 +Strings::length('červená'); // 7 ``` startsWith(string $haystack, string $needle): bool .[method deprecated] ----------------------------------------------------------------------- -Preveri, ali se niz `$haystack` začne z `$needle`. +Ugotovi, ali se niz `$haystack` začne z nizom `$needle`. ```php -$haystack = 'Begins'; -$needle = 'Be'; +$haystack = 'Začenja se'; // Začenja se +$needle = 'Za'; Strings::startsWith($haystack, $needle); // true ``` .[note] -Uporabi izvirno `str_starts_with()`:https://www.php.net/manual/en/function.str-starts-with.php. +Uporabljajte izvorno `str_starts_with()`:https://www.php.net/manual/en/function.str-starts-with.php. endsWith(string $haystack, string $needle): bool .[method deprecated] --------------------------------------------------------------------- -Preveri, ali se niz `$haystack` konča s `$needle`. +Ugotovi, ali se niz `$haystack` konča z nizom `$needle`. ```php -$haystack = 'Ends'; -$needle = 'ds'; +$haystack = 'Končuje se'; // Končuje se +$needle = 'se'; Strings::endsWith($haystack, $needle); // true ``` .[note] -Uporabite domači `str_ends_with()`:https://www.php.net/manual/en/function.str-ends-with.php. +Uporabljajte izvorno `str_ends_with()`:https://www.php.net/manual/en/function.str-ends-with.php. contains(string $haystack, string $needle): bool .[method deprecated] --------------------------------------------------------------------- -Preveri, ali niz `$haystack` vsebuje `$needle`. +Ugotovi, ali niz `$haystack` vsebuje `$needle`. ```php -$haystack = 'Contains'; -$needle = 'tai'; +$haystack = 'Predavalnica'; // Predavalnica +$needle = 'dava'; Strings::contains($haystack, $needle); // true ``` .[note] -Uporabite izvirni `str_contains()`:https://www.php.net/manual/en/function.str-contains.php. +Uporabljajte izvorno `str_contains()`:https://www.php.net/manual/en/function.str-contains.php. -compare(string $left, string $right, int $length=null): bool .[method] ----------------------------------------------------------------------- +compare(string $left, string $right, ?int $length=null): bool .[method] +----------------------------------------------------------------------- -Primerja dva niza UTF-8 ali njune dele, pri čemer ne upošteva velikosti znakov. Če je `$length` nič, se primerjajo celi nizi, če je negativen, se primerja ustrezno število znakov od konca niza, sicer se primerja ustrezno število znakov od začetka. +Primerjava dveh UTF-8 nizov ali njunih delov ne glede na velikost črk. Če `$length` vsebuje null, se primerjata cela niza, če je negativen, se primerja ustrezno število znakov od konca nizov, sicer se primerja ustrezno število znakov od začetka. ```php Strings::compare('Nette', 'nette'); // true -Strings::compare('Nette', 'next', 2); // true - two first characters match -Strings::compare('Nette', 'Latte', -2); // true - two last characters match +Strings::compare('Nette', 'next', 2); // true - ujemanje prvih 2 znakov +Strings::compare('Nette', 'Latte', -2); // true - ujemanje zadnjih 2 znakov ``` findPrefix(...$strings): string .[method] ----------------------------------------- -Poišče skupno predpono nizov ali vrne prazen niz, če predpona ni bila najdena. +Najde skupni začetek nizov. Ali vrne prazen niz, če skupna predpona ni bila najdena. ```php Strings::findPrefix('prefix-a', 'prefix-bb', 'prefix-c'); // 'prefix-' @@ -293,7 +293,7 @@ Strings::findPrefix('Nette', 'is', 'great'); // '' before(string $haystack, string $needle, int $nth=1): ?string .[method] ----------------------------------------------------------------------- -Vrne del `$haystack` pred pojavom `$nth` `$needle` ali vrne `null`, če igla ni bila najdena. Negativna vrednost pomeni iskanje od konca. +Vrne del niza `$haystack` pred n-tim `$nth` pojavom niza `$needle`. Ali `null`, če `$needle` ni bil najden. Pri negativni vrednosti `$nth` se išče od konca niza. ```php Strings::before('Nette_is_great', '_', 1); // 'Nette' @@ -306,7 +306,7 @@ Strings::before('Nette_is_great', '_', 3); // null after(string $haystack, string $needle, int $nth=1): ?string .[method] ---------------------------------------------------------------------- -Vrne del `$haystack` po `$nth` pojavu `$needle` ali vrne `null`, če `$needle` ni bil najden. Negativna vrednost `$nth` pomeni iskanje od konca. +Vrne del niza `$haystack` po n-tem `$nth` pojavu niza `$needle`. Ali `null`, če `$needle` ni bil najden. Pri negativni vrednosti `$nth` se išče od konca niza. ```php Strings::after('Nette_is_great', '_', 2); // 'great' @@ -319,7 +319,7 @@ Strings::after('Nette_is_great', '_', 3); // null indexOf(string $haystack, string $needle, int $nth=1): ?int .[method] --------------------------------------------------------------------- -Vrne položaj v znakih `$nth` pojavljanja `$needle` v `$haystack` ali `null`, če `$needle` ni bil najden. Negativna vrednost `$nth` pomeni iskanje od konca. +Vrne pozicijo v znakih n-tega `$nth` pojava niza `$needle` v nizu `$haystack`. Ali `null`, če `$needle` ni bil najden. Pri negativni vrednosti `$nth` se išče od konca niza. ```php Strings::indexOf('abc abc abc', 'abc', 2); // 4 @@ -328,14 +328,14 @@ Strings::indexOf('abc abc abc', 'd'); // null ``` -Kodiranje .[#toc-encoding] -========================== +Kodiranje +========= fixEncoding(string $s): string .[method] ---------------------------------------- -Iz niza odstrani vse neveljavne znake UTF-8. +Odstrani iz niza neveljavne UTF-8 znake. ```php $correctStrings = Strings::fixEncoding($string); @@ -345,14 +345,14 @@ $correctStrings = Strings::fixEncoding($string); checkEncoding(string $s): bool .[method deprecated] --------------------------------------------------- -Preveri, ali je niz veljaven v kodiranju UTF-8. +Ugotovi, ali gre za veljaven UTF-8 niz. ```php $isUtf8 = Strings::checkEncoding($string); ``` .[note] -Uporabite [Nette\Utils\Validator::isUnicode(). |validators#isUnicode] +Uporabite [Nette\Utils\Validator::isUnicode() |validators#isUnicode]. toAscii(string $s): string .[method] @@ -365,39 +365,39 @@ Strings::toAscii('žluťoučký kůň'); // 'zlutoucky kun' ``` .[caution] -Zahteva razširitev PHP `intl`. +Zahteva PHP razširitev `intl`. chr(int $code): string .[method] -------------------------------- -Vrne določen znak v UTF-8 iz kodne točke (številka v območju 0x0000..D7FF ali 0xE000..10FFFF). +Vrne specifičen znak v UTF-8 iz kodne točke (število v obsegu 0x0000..D7FF in 0xE000..10FFFF). ```php -Strings::chr(0xA9); // '©' +Strings::chr(0xA9); // '©' v kodiranju UTF-8 ``` ord(string $char): int .[method] -------------------------------- -Vrne kodno točko določenega znaka v UTF-8 (število v območju 0x0000..D7FF ali 0xE000..10FFFF). +Vrne kodno točko določenega znaka v UTF-8 (število v obsegu 0x0000..D7FF ali 0xE000..10FFFF). ```php Strings::ord('©'); // 0xA9 ``` -Redni izrazi .[#toc-regular-expressions] -======================================== +Regularni izrazi +================ -Razred Strings ponuja funkcije za delo z regularnimi izrazi. Za razliko od izvornih funkcij PHP imajo razumljivejši API, boljšo podporo Unicode in, kar je najpomembneje, zaznavanje napak. Vsaka napaka pri sestavljanju ali obdelavi izraza vrže izjemo `Nette\RegexpException`. +Razred Strings ponuja funkcije za delo z regularnimi izrazi. V nasprotju z izvornimi PHP funkcijami imajo bolj razumljiv API, boljšo podporo za Unicode in predvsem zaznavanje napak. Vsaka napaka pri prevajanju ali obdelavi izraza vrže izjemo `Nette\RegexpException`. split(string $subject, string $pattern, bool $captureOffset=false, bool $skipEmpty=false, int $limit=-1, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------- -Razdeli niz v polja v skladu z regularnim izrazom. Zajeti in vrnjeni bodo tudi izrazi v oklepajih. +Razdeli niz v polje glede na regularni izraz. Izrazi v oklepajih bodo zajeti in vrnjeni tudi. ```php Strings::split('hello, world', '~,\s*~'); @@ -407,7 +407,7 @@ Strings::split('hello, world', '~(,)\s*~'); // ['hello', ',', 'world']`` ``` -Če je `$skipEmpty` `true` , bodo vrnjeni samo neprazni elementi: +Če je `$skipEmpty` `true`, bodo vrnjene samo neprazne postavke: ```php Strings::split('hello, world, ', '~,\s*~'); @@ -417,16 +417,16 @@ Strings::split('hello, world, ', '~,\s*~', skipEmpty: true); // ['hello', 'world'] ``` -Če je naveden `$limit`, bodo vrnjeni samo podrezi do omejitve, preostanek niza pa bo umeščen v zadnji element. Omejitev -1 ali 0 pomeni, da omejitve ni. +Če je podan `$limit`, bodo vrnjeni samo podnizi do limita, preostanek niza pa bo umeščen v zadnji element. Limit -1 ali 0 pomeni brez omejitve. ```php Strings::split('hello, world, third', '~,\s*~', limit: 2); // ['hello', 'world, third'] ``` -Če je `$utf8` `true` , se vrednotenje preklopi v način Unicode. To je podobno, kot če bi določili modifikator `u`. +Če je `$utf8` `true`, se preklopi vrednotenje v Unicode način. Podobno kot če navedete modifikator `u`. -Če je `$captureOffset` `true` , se za vsako pojavljeno ujemanje vrne tudi njegov položaj v nizu (v bajtih; v znakih, če je nastavljen `$utf8` ). To spremeni vrnjeno vrednost v polje, kjer je vsak element par, sestavljen iz ujemajočega se niza in njegovega položaja. +Če je `$captureOffset` `true`, bo za vsako pojavljajočo se ujemanje vrnjena tudi njena pozicija v nizu (v bajtih; če je nastavljeno `$utf8`, pa v znakih). S tem se spremeni vrnjena vrednost v polje, kjer je vsak element par, sestavljen iz ujemajočega se niza in njegove pozicije. ```php Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true); @@ -440,7 +440,7 @@ Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true, utf8: true); match(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $utf8=false): ?array .[method] -------------------------------------------------------------------------------------------------------------------------------------------------- -Preišče niz za del, ki ustreza regularnemu izrazu, in vrne polje z najdenim izrazom in posameznimi podizrazi ali `null`. +Išče v nizu del, ki ustreza regularnemu izrazu, in vrne polje z najdenim izrazom in posameznimi podizrazi, ali `null`. ```php Strings::match('hello!', '~\w+(!+)~'); @@ -450,7 +450,7 @@ Strings::match('hello!', '~X~'); // null ``` -Če je `$unmatchedAsNull` `true` , se neskladni podvzorci vrnejo kot nič, sicer se vrnejo kot prazen niz ali se ne vrnejo: +Če je `$unmatchedAsNull` `true`, so nezajeti podvzorci vrnjeni kot null; sicer so vrnjeni kot prazen niz ali pa niso vrnjeni: ```php Strings::match('hello', '~\w+(!+)?~'); @@ -460,7 +460,7 @@ Strings::match('hello', '~\w+(!+)?~', unmatchedAsNull: true); // ['hello', null] ``` -Če je `$utf8` `true` , se vrednotenje preklopi na način Unicode. To je podobno, kot če bi določili modifikator `u`: +Če je `$utf8` `true`, se preklopi vrednotenje v Unicode način. Podobno kot če navedete modifikator `u`: ```php Strings::match('žlutý kůň', '~\w+~'); @@ -470,9 +470,9 @@ Strings::match('žlutý kůň', '~\w+~', utf8: true); // ['žlutý'] ``` -S parametrom `$offset` lahko določimo položaj, s katerega se začne iskanje (v bajtih; v znakih, če je nastavljen `$utf8` ). +Parameter `$offset` lahko uporabite za določitev pozicije, od katere naj se začne iskanje (v bajtih; če je nastavljeno `$utf8`, pa v znakih). -Če je `$captureOffset` `true` , bo za vsako najdeno ujemanje vrnjen tudi njegov položaj v nizu (v bajtih; v znakih, če je nastavljen `$utf8` ). To spremeni vrnjeno vrednost v polje, kjer je vsak element par, sestavljen iz ustreznega niza in njegovega odmika: +Če je `$captureOffset` `true`, bo za vsako pojavljajočo se ujemanje vrnjena tudi njena pozicija v nizu (v bajtih; če je nastavljeno `$utf8`, pa v znakih). S tem se spremeni vrnjena vrednost v polje, kjer je vsak element par, sestavljen iz ujemajočega se niza in njegovega odmika: ```php Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true); @@ -483,10 +483,10 @@ Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true, utf8: true); ``` -matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false, bool $lazy=false): array|Generator .[method] +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -V nizu poišče vse pojavitve, ki ustrezajo regularnemu izrazu, in vrne polje polj, ki vsebuje najden izraz in vsak podizraz. +Išče v nizu vse pojavitve, ki ustrezajo regularnemu izrazu, in vrne polje polj z najdenim izrazom in posameznimi podizrazi. ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~'); @@ -496,7 +496,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~'); ] */ ``` -Če je `$patternOrder` `true` , se struktura rezultatov spremeni tako, da je prva postavka polje popolnih ujemanj vzorca, druga je polje nizov, ki ustrezajo prvemu podvzorcu v oklepaju, in tako naprej: +Če je `$patternOrder` `true`, se struktura rezultatov spremeni tako, da je v prvi postavki polje popolnih ujemanj vzorca, v drugi je polje nizov, ki ustrezajo prvemu podvzorcu v oklepaju, in tako naprej: ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~', patternOrder: true); @@ -506,7 +506,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~', patternOrder: true); ] */ ``` -Če je `$unmatchedAsNull` `true` , se neskladni podvzorci vrnejo kot nič, sicer se vrnejo kot prazen niz ali se ne vrnejo: +Če je `$unmatchedAsNull` `true`, so nezajeti podvzorci vrnjeni kot null; sicer so vrnjeni kot prazen niz ali pa niso vrnjeni: ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~', unmatchedAsNull: true); @@ -516,7 +516,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~', unmatchedAsNull: true); ] */ ``` -Če je `$utf8` `true` , se vrednotenje preklopi na način Unicode. To je podobno, kot če bi določili modifikator `u`: +Če je `$utf8` `true`, se preklopi vrednotenje v Unicode način. Podobno kot če navedete modifikator `u`: ```php Strings::matchAll('žlutý kůň', '~\w+~'); @@ -532,9 +532,9 @@ Strings::matchAll('žlutý kůň', '~\w+~', utf8: true); ] */ ``` -S parametrom `$offset` lahko določimo položaj, s katerega se začne iskanje (v bajtih; v znakih, če je nastavljen `$utf8` ). +Parameter `$offset` lahko uporabite za določitev pozicije, od katere naj se začne iskanje (v bajtih; če je nastavljeno `$utf8`, pa v znakih). -Če je `$captureOffset` `true` , bo za vsako najdeno ujemanje vrnjen tudi njegov položaj v nizu (v bajtih; v znakih, če je nastavljen `$utf8` ). To spremeni vrnjeno vrednost v polje, kjer je vsak element par, sestavljen iz ustreznega niza in njegovega položaja: +Če je `$captureOffset` `true`, bo za vsako pojavljajočo se ujemanje vrnjena tudi njena pozicija v nizu (v bajtih; če je nastavljeno `$utf8`, pa v znakih). S tem se spremeni vrnjena vrednost v polje, kjer je vsak element par, sestavljen iz ujemajočega se niza in njegove pozicije: ```php Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true); @@ -550,11 +550,21 @@ Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true, utf8: true); ] */ ``` +Če je `$lazy` `true`, funkcija vrne `Generator` namesto polja, kar prinaša znatne prednosti pri zmogljivosti pri delu z velikimi nizi. Generator omogoča postopno iskanje ujemanj, namesto celotnega niza naenkrat. To omogoča učinkovito delo tudi z izjemno velikimi vhodnimi besedili. Poleg tega lahko kadar koli prekinete obdelavo, če najdete iskano ujemanje, kar prihrani računski čas. + +```php +$matches = Strings::matchAll($largeText, '~\w+~', lazy: true); +foreach ($matches as $match) { + echo "Najdeno: $match[0]\n"; + // Obdelavo je mogoče kadar koli prekiniti +} +``` + replace(string $subject, string|array $pattern, string|callable $replacement='', int $limit=-1, bool $captureOffset=false, bool $unmatchedAsNull=false, bool $utf8=false): string .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Zamenja vse pojavitve, ki ustrezajo regularnemu izrazu. `$replacement` je bodisi maska nadomestnega niza bodisi povratni klic. +Nadomešča vse pojavitve, ki ustrezajo regularnemu izrazu. `$replacement` je bodisi maska nadomestnega niza ali povratni klic (callback). ```php Strings::replace('hello, world!', '~\w+~', '--'); @@ -564,7 +574,7 @@ Strings::replace('hello, world!', '~\w+~', fn($m) => strrev($m[0])); // 'olleh, dlrow!' ``` -Funkcija omogoča tudi večkratno zamenjavo, če v drugem parametru posreduje polje v obliki `pattern => replacement`: +Funkcija omogoča tudi izvedbo več zamenjav tako, da v drugem parametru posredujemo polje v obliki `pattern => replacement`: ```php Strings::replace('hello, world!', [ @@ -574,9 +584,9 @@ Strings::replace('hello, world!', [ // '-- --!' ``` -Parameter `$limit` omejuje število zamenjav. Omejitev -1 pomeni, da omejitve ni. +Parameter `$limit` omejuje število izvedenih zamenjav. Limit -1 pomeni brez omejitve. -Če je `$utf8` `true` , se vrednotenje preklopi v način Unicode. To je podobno, kot če bi določili modifikator `u`. +Če je `$utf8` `true`, se preklopi vrednotenje v Unicode način. Podobno kot če navedete modifikator `u`. ```php Strings::replace('žlutý kůň', '~\w+~', '--'); @@ -586,7 +596,7 @@ Strings::replace('žlutý kůň', '~\w+~', '--', utf8: true); // '-- --' ``` -Če je `$captureOffset` `true` , se povratnemu klicu za vsako nastalo ujemanje posreduje tudi njegov položaj v nizu (v bajtih; v znakih, če je nastavljen `$utf8` ). To spremeni obliko posredovanega polja, v katerem je vsak element par, sestavljen iz ujemajočega se niza in njegovega položaja. +Če je `$captureOffset` `true`, bo za vsako pojavljajočo se ujemanje povratnemu klicu (callbacku) posredovana tudi njena pozicija v nizu (v bajtih; če je nastavljeno `$utf8`, pa v znakih). S tem se spremeni oblika posredovanega polja, kjer je vsak element par, sestavljen iz ujemajočega se niza in njegove pozicije. ```php Strings::replace( @@ -595,7 +605,7 @@ Strings::replace( function (array $m) { dump($m); return ''; }, captureOffset: true, ); -// dumps [['lut', 2]] a [['k', 8]] +// izpiše [['lut', 2]] in [['k', 8]] Strings::replace( 'žlutý kůň', @@ -604,10 +614,10 @@ Strings::replace( captureOffset: true, utf8: true, ); -// dumps [['žlutý', 0]] a [['kůň', 6]] +// izpiše [['žlutý', 0]] in [['kůň', 6]] ``` -Če je `$unmatchedAsNull` `true` , se neusklajeni podvzorci posredujejo povratnemu klicu kot nič, sicer se posredujejo kot prazen niz ali pa se ne posredujejo: +Če je `$unmatchedAsNull` `true`, so nezajeti podvzorci posredovani povratnemu klicu (callbacku) kot null; sicer so posredovani kot prazen niz ali pa niso posredovani: ```php Strings::replace( @@ -615,7 +625,7 @@ Strings::replace( '~(a)(b)*(c)~', function (array $m) { dump($m); return ''; }, ); -// dumps ['ac', 'a', '', 'c'] +// izpiše ['ac', 'a', '', 'c'] Strings::replace( 'ac', @@ -623,5 +633,5 @@ Strings::replace( function (array $m) { dump($m); return ''; }, unmatchedAsNull: true, ); -// dumps ['ac', 'a', null, 'c'] +// izpiše ['ac', 'a', null, 'c'] ``` diff --git a/utils/sl/type.texy b/utils/sl/type.texy index 36c22948aa..7f67dd0398 100644 --- a/utils/sl/type.texy +++ b/utils/sl/type.texy @@ -1,8 +1,8 @@ -Vrsta PHP -********* +PHP Tip +******* .[perex] -[api:Nette\Utils\Type] je razred podatkovnih tipov PHP. +[api:Nette\Utils\Type] je razred za delo s podatkovnimi tipi PHP. Namestitev: @@ -11,7 +11,7 @@ Namestitev: composer require nette/utils ``` -Vsi primeri predpostavljajo, da je definiran naslednji vzdevek razreda: +Vsi primeri predpostavljajo ustvarjen alias: ```php use Nette\Utils\Type; @@ -21,7 +21,7 @@ use Nette\Utils\Type; fromReflection($reflection): ?Type .[method] -------------------------------------------- -Statična metoda ustvari objekt Type na podlagi refleksije. Parameter je lahko objekt `ReflectionMethod` ali `ReflectionFunction` (vrne tip povratne vrednosti) ali objekt `ReflectionParameter` ali `ReflectionProperty`. Rešuje `self`, `static` in `parent` na dejansko ime razreda. Če predmet nima tipa, vrne `null`. +Statična metoda ustvari objekt Type na podlagi refleksije. Parameter je lahko objekt `ReflectionMethod` ali `ReflectionFunction` (vrne tip vrnjene vrednosti) ali `ReflectionParameter` ali `ReflectionProperty`. Prevede `self`, `static` in `parent` v dejansko ime razreda. Če subjekt nima nobenega tipa, vrne `null`. ```php class DemoClass @@ -37,7 +37,7 @@ echo Type::fromReflection($prop); // 'DemoClass' fromString(string $type): Type .[method] ---------------------------------------- -Statična metoda ustvari predmet Type v skladu z besedilnim zapisom. +Statična metoda ustvari objekt Type glede na besedilni zapis. ```php $type = Type::fromString('Foo|Bar'); @@ -48,10 +48,10 @@ echo $type; // 'Foo|Bar' getNames(): (string|array)[] .[method] -------------------------------------- -Vrne niz podtipov, ki sestavljajo sestavljeni tip, v obliki niza. +Vrne polje podtipov, iz katerih je sestavljen sestavljeni tip, kot nize. ```php -$type = Type::fromString('string|null'); // or '?string' +$type = Type::fromString('string|null'); // ali '?string' $type->getNames(); // ['string', 'null'] $type = Type::fromString('(Foo&Bar)|string'); @@ -62,10 +62,10 @@ $type->getNames(); // [['Foo', 'Bar'], 'string'] getTypes(): Type[] .[method] ---------------------------- -Vrne niz podtipov, ki sestavljajo sestavljeni tip, kot predmete `Type`: +Vrne polje podtipov, iz katerih je sestavljen sestavljeni tip, kot objekte `ReflectionType`: ```php -$type = Type::fromString('string|null'); // or '?string' +$type = Type::fromString('string|null'); // ali '?string' $type->getTypes(); // [Type::fromString('string'), Type::fromString('null')] $type = Type::fromString('(Foo&Bar)|string'); @@ -79,7 +79,7 @@ $type->getTypes(); // [Type::fromString('Foo'), Type::fromString('Bar')] getSingleName(): ?string .[method] ---------------------------------- -Vrne ime tipa za enostavne tipe, sicer je null. +Pri preprostih tipih vrne ime tipa, sicer null. ```php $type = Type::fromString('string|null'); @@ -99,14 +99,14 @@ echo $type->getSingleName(); // null isSimple(): bool .[method] -------------------------- -Vrne, ali je tip enostaven. Za enostavne tipe se štejejo tudi tipi, ki jih je mogoče izničiti: +Vrne, ali gre za preprost tip. Za preproste tipe se štejejo tudi preprosti nullable tipi: ```php $type = Type::fromString('string'); $type->isSimple(); // true $type->isUnion(); // false -$type = Type::fromString('?Foo'); // nebo 'Foo|null' +$type = Type::fromString('?Foo'); // ali 'Foo|null' $type->isSimple(); // true $type->isUnion(); // true ``` @@ -115,10 +115,10 @@ $type->isUnion(); // true isUnion(): bool .[method] ------------------------- -Vrne, ali je tip unija. +Vrne, ali gre za union tip. ```php -$type = Type::fromString('Foo&Bar'); +$type = Type::fromString('string|int'); $type->isUnion(); // true ``` @@ -126,11 +126,11 @@ $type->isUnion(); // true isIntersection(): bool .[method] -------------------------------- -Vrne, ali je tip presečišče. +Vrne, ali gre za intersection tip. ```php -$type = Type::fromString('string&int'); +$type = Type::fromString('Foo&Bar'); $type->isIntersection(); // true ``` @@ -138,7 +138,7 @@ $type->isIntersection(); // true isBuiltin(): bool .[method] --------------------------- -Vrne, ali je tip enostaven in vgrajen tip PHP. +Vrne, ali je tip preprost in hkrati vgrajen tip PHP. ```php $type = Type::fromString('string'); @@ -155,7 +155,7 @@ $type->isBuiltin(); // false isClass(): bool .[method] ------------------------- -Vrne, ali je tip hkrati preprosto ime in ime razreda. +Vrne, ali je tip preprost in hkrati ime razreda. ```php $type = Type::fromString('string'); @@ -172,7 +172,7 @@ $type->isClass(); // false isClassKeyword(): bool .[method] -------------------------------- -Določi, ali je tip eden od notranjih tipov `self`, `parent`, `static`. +Vrne, ali je tip eden od notranjih tipov `self`, `parent`, `static`. ```php $type = Type::fromString('self'); @@ -186,7 +186,7 @@ $type->isClassKeyword(); // false allows(string $type): bool .[method] ------------------------------------ -Metoda `allows()` preveri združljivost tipov. Z njo lahko na primer preverimo, ali je mogoče vrednost določenega tipa posredovati kot parameter. +Metoda `allows()` preverja združljivost tipov. Na primer, omogoča ugotoviti, ali bi vrednost določenega tipa lahko bila posredovana kot parameter. ```php $type = Type::fromString('string|null'); diff --git a/utils/sl/validators.texy b/utils/sl/validators.texy index 81f2b88709..56d6f66632 100644 --- a/utils/sl/validators.texy +++ b/utils/sl/validators.texy @@ -1,8 +1,8 @@ -Preverjevalniki vrednosti -************************* +Validatorji vrednosti +********************* .[perex] -Potrebujete hitro in preprosto preveriti, ali spremenljivka vsebuje na primer veljaven e-poštni naslov? Potem vam bo prišel prav [api:Nette\Utils\Validators], statični razred z uporabnimi funkcijami za preverjanje vrednosti. +Potrebujete hitro in enostavno preveriti, ali je v spremenljivki na primer veljaven e-poštni naslov? Za to vam bo prišel prav [api:Nette\Utils\Validators], statični razred z uporabnimi funkcijami za validacijo vrednosti. Namestitev: @@ -11,17 +11,17 @@ Namestitev: composer require nette/utils ``` -Vsi primeri predpostavljajo, da je definiran naslednji vzdevek razreda: +Vsi primeri predpostavljajo ustvarjen vzdevek: ```php use Nette\Utils\Validators; ``` -Osnovna uporaba .[#toc-basic-usage] -=================================== +Osnovna uporaba +=============== -Razred `Validators` ima številne metode za preverjanje vrednosti, kot so [isList() |#isList()], [isUnicode() |#isUnicode()], [isEmail( |#isEmail()]), [isUrl() |#isUrl()] itd., ki jih lahko uporabite v svoji kodi: +Razred ponuja vrsto metod za preverjanje vrednosti, kot so [#isUnicode()], [#isEmail()], [#isUrl()] itd., za uporabo v vaši kodi: ```php if (!Validators::isEmail($email)) { @@ -29,7 +29,7 @@ if (!Validators::isEmail($email)) { } ``` -Poleg tega lahko preveri, ali vrednost ustreza tako imenovanim [pričakovanim tipom |#expected types], kar je niz, v katerem so posamezne možnosti ločene z navpično črto `|`. Tako je preverjanje tipov zvez enostavno z uporabo [funkcije if() |#if()]: +Poleg tega lahko preveri, ali je vrednost t.i. [#pričakovani tipi], kar je niz, kjer so posamezne možnosti ločene z navpičnico `|`. Tako lahko enostavno preverimo več tipov z uporabo [#is()]: ```php if (!Validators::is($val, 'int|string|bool')) { @@ -37,98 +37,98 @@ if (!Validators::is($val, 'int|string|bool')) { } ``` -Prav tako pa vam omogoča, da ustvarite sistem, v katerem je treba pričakovanja zapisati kot nize (na primer v anotacijah ali konfiguraciji) in nato preverjati v skladu z njimi. +Vendar nam to daje tudi možnost ustvariti sistem, kjer je treba pričakovanja zapisati kot nize (na primer v anotacijah ali konfiguraciji) in nato glede na njih preverjati vrednosti. -Prav tako lahko razglasite [trditev, |#assert] ki vrže izjemo, če ni izpolnjena. +Za pričakovane tipe lahko postavimo tudi zahtevo [#assert()], ki, če ni izpolnjena, sproži izjemo. -Pričakovani tipi .[#toc-expected-types] -======================================= +Pričakovani tipi +================ -Pričakovani tipi so niz, sestavljen iz ene ali več različic, ločenih z navpično črto `|`, similar to writing types in PHP (ie. `'int|string|bool')`. Dovoljen je tudi ničelni zapis `?int`. +Pričakovani tipi tvorijo niz, sestavljen iz ene ali več različic, ločenih z navpičnico `|`, podobno kot se zapisujejo tipi v PHP (npr. `'int|string|bool')`. Sprejema se tudi nullable zapis `?int`. Polje, kjer so vsi elementi določenega tipa, se zapiše v obliki `int[]`. -Nekaterim tipom lahko sledita dvopičje in dolžina `:length` ali razpon `:[min]..[max]`npr. `string:10` (niz z dolžino 10 bajtov), `float:10..` (število 10 in večje), `array:..10` (polje z do desetimi elementi) ali `list:10..20` (seznam z 10 do 20 elementi), ali regularni izraz za `pattern:[0-9]+`. +Nekaterim tipom lahko sledi dvopičje in dolžina `:length` ali obseg `:[min]..[max]`, npr. `string:10` (niz dolžine 10 bajtov), `float:10..` (število 10 in večje), `array:..10` (polje do desetih elementov) ali `list:10..20` (seznam z 10 do 20 elementi), ali pa regularni izraz pri `pattern:[0-9]+`. -Pregled vrst in pravil: +Pregled tipov in pravil: .[wide] -| Tipi PHP || +| PHP tipi || |-------------------------- -| `array` .{width: 140px} | podano je lahko območje števila elementov -| `bool` | -| `float` | obseg za vrednost je lahko podan -| `int` | lahko se navede razpon vrednosti -| `null` | -| `object` | +| `array` .{width: 140px} | lahko navedete obseg za število elementov +| `bool` | +| `float` | lahko navedete obseg za vrednost +| `int` | lahko navedete obseg za vrednost +| `null` | +| `object` | | `resource` | -| `scalar` | int\|float\|bool\|string -| `string` | lahko se navede razpon za dolžino v bajtih +| `scalar` | int\|float\|bool\|string +| `string` | lahko navedete obseg za dolžino v bajtih | `callable` | | `iterable` | -| `mixed` | -|------------------------------------------------ -| psevdotipi || -|------------------------------------------------ -| `list` | [indeksirano polje |#isList], podan je lahko razpon števila elementov -| `none` | prazna vrednost: `''`, `null`, `false` -| `number` | int\|float -| `numeric` | [število, vključno z besedilno predstavitvijo |#isNumeric] -| `numericint`| [celo število, vključno z besedilno predstavitvijo |#isNumericInt] -| `unicode` | [UTF-8 niz |#isUnicode], podan je lahko razpon dolžine v znakih -|------------------------------------------------ -| razred znakov (ne more biti prazen niz) || +| `mixed` | +|-------------------------- +| psevdo-tipi || |------------------------------------------------ -| `alnum` | vsi znaki so alfanumerični -| `alpha` | vsi znaki so črke `[A-Za-z]` -| `digit` | vsi znaki so številke -| `lower` | vsi znaki so male črke `[a-z]` -| `space` | vsi znaki so presledki -| `upper` | vsi znaki so velike črke `[A-Z]` -| `xdigit` | vsi znaki so šestnajstiške številke `[0-9A-Fa-f]` +| `list` | indeksirano polje, lahko navedete obseg za število elementov +| `none` | prazna vrednost: `''`, `null`, `false` +| `number` | int\|float +| `numeric` | [število vključno z besedilno reprezentacijo |#isNumeric] +| `numericint`| [celo število vključno z besedilno reprezentacijo |#isNumericInt] +| `unicode` | [UTF-8 niz |#isUnicode], lahko navedete obseg za dolžino v znakih +|-------------------------- +| znakovni razred (ne sme biti prazen niz) || |------------------------------------------------ -| preverjanje skladnje || +| `alnum` | vsi znaki so alfanumerični +| `alpha` | vsi znaki so črke `[A-Za-z]` +| `digit` | vsi znaki so števke +| `lower` | vsi znaki so male črke `[a-z]` +| `space` | vsi znaki so presledki +| `upper` | vsi znaki so velike črke `[A-Z]` +| `xdigit` | vsi znaki so šestnajstiške števke `[0-9A-Fa-f]` +|-------------------------- +| preverjanje sintakse || |------------------------------------------------ -| `pattern` | regularni izraz, ki mu mora ustrezati celoten** niz -| `email` | [Elektronska pošta |#isEmail] +| `pattern` | regularni izraz, ki mu mora ustrezati **cel** niz +| `email` | [E-pošta |#isEmail] | `identifier`| [PHP identifikator |#isPhpIdentifier] -| `url` | [URL |#isUrl] -| `uri` | [URI |#isUri] -|------------------------------------------------ -| potrditev okolja || +| `url` | [URL |#isUrl] +| `uri` | [URI |#isUri] +|-------------------------- +| preverjanje okolja || |------------------------------------------------ -| `class` | je obstoječi razred -| `interface` | je obstoječi vmesnik -| `directory` | je obstoječi imenik -| `file` | je obstoječa datoteka +| `class` | je obstoječ razred +| `interface` | je obstoječ vmesnik +| `directory` | je obstoječ imenik +| `file` | je obstoječa datoteka -Trditev .[#toc-assertion] -========================= +Asercije +======== assert($value, string $expected, string $label='variable'): void .[method] -------------------------------------------------------------------------- -Preveri, ali je vrednost sestavljena iz [pričakovanih tipov |#expected types], ločenih s cevjo. V nasprotnem primeru vrže izjemo [api:Nette\Utils\AssertionException]. Besedo `variable` v sporočilu o izjemi lahko nadomestite s parametrom `$label`. +Preverja, ali je vrednost eden od [pričakovanih tipov |#Pričakovani tipi], ločenih z navpičnico. Če ne, sproži izjemo [api:Nette\Utils\AssertionException]. Besedo `variable` v besedilu izjeme lahko nadomestite z drugo s parametrom `$label`. ```php -Validators::assert('Nette', 'string:5'); // V REDU +Validators::assert('Nette', 'string:5'); // OK Validators::assert('Lorem ipsum dolor sit', 'string:78'); // AssertionException: The variable expects to be string:78, string 'Lorem ipsum dolor sit' given. ``` -assertField(array $array, string|int $key, string $expected=null, string $label=null): void .[method] ------------------------------------------------------------------------------------------------------ +assertField(array $array, string|int $key, ?string $expected=null, ?string $label=null): void .[method] +------------------------------------------------------------------------------------------------------- -Preveri, ali je element `$key` v polju `$array` iz [pričakovanih tipov |#expected types], ločenih s cevjo. V nasprotnem primeru vrže izjemo [api:Nette\Utils\AssertionException]. Niz `item '%' in array` v sporočilu o izjemi je mogoče nadomestiti s parametrom `$label`. +Preverja, ali je element pod ključem `$key` v polju `$array` eden od [pričakovanih tipov |#Pričakovani tipi], ločenih z navpičnico. Če ne, sproži izjemo [api:Nette\Utils\AssertionException]. Niz `item '%' in array` v besedilu izjeme lahko nadomestite z drugim s parametrom `$label`. ```php $arr = ['foo' => 'Nette']; -Validators::assertField($arr, 'foo', 'string:5'); // V REDU +Validators::assertField($arr, 'foo', 'string:5'); // OK Validators::assertField($arr, 'bar', 'string:15'); // AssertionException: Missing item 'bar' in array. Validators::assertField($arr, 'foo', 'int'); @@ -136,19 +136,19 @@ Validators::assertField($arr, 'foo', 'int'); ``` -Validatorji .[#toc-validators] -============================== +Validatorji +=========== is($value, string $expected): bool .[method] -------------------------------------------- -Preveri, ali je vrednost iz [pričakovanih tipov, |#expected types] ločenih s cevjo. +Preveri, ali je vrednost eden od [pričakovanih tipov |#Pričakovani tipi], ločenih z navpičnico. ```php Validators::is(1, 'int|float'); // true Validators::is(23, 'int:0..10'); // false -Validators::is('Nette Framework', 'string:15'); // true, length is 15 bytes +Validators::is('Nette Framework', 'string:15'); // true, dolžina je 15 bajtov Validators::is('Nette Framework', 'string:8..'); // true Validators::is('Nette Framework', 'string:30..40'); // false ``` @@ -157,7 +157,7 @@ Validators::is('Nette Framework', 'string:30..40'); // false isEmail(mixed $value): bool .[method] ------------------------------------- -Preveri, ali je vrednost veljaven e-poštni naslov. Ne preveri, ali domena dejansko obstaja, preveri se le sintaksa. Funkcija računa tudi na prihodnje [vrhnje domene, |https://en.wikipedia.org/wiki/Top-level_domain] ki so lahko tudi v enokodni obliki. +Preveri, ali je vrednost veljaven e-poštni naslov. Ne preverja se, ali domena dejansko obstaja, preverja se samo sintaksa. Funkcija upošteva tudi prihodnje [TLD|https://sl.wikipedia.org/wiki/Vrhnja_domena], ki so lahko tudi v unicode. ```php Validators::isEmail('example@nette.org'); // true @@ -169,7 +169,7 @@ Validators::isEmail('nette'); // false isInRange(mixed $value, array $range): bool .[method] ----------------------------------------------------- -Preveri, ali je vrednost v danem območju `[min, max]`, pri čemer se lahko zgornja ali spodnja meja izpusti (`null`). Primerjajo se lahko številke, nizi in objekti DateTime. +Preveri, ali je vrednost v danem obsegu `[min, max]`, kjer lahko zgornjo ali spodnjo mejo izpustimo (`null`). Lahko se primerjajo števila, nizi in objekti DateTime. Če manjkata obe meji (`[null, null]`) ali je vrednost `null`, vrne `false`. @@ -240,7 +240,7 @@ Validators::isPhpIdentifier('one two'); // false isBuiltinType(string $type): bool .[method] ------------------------------------------- -Ugotovi, ali je `$type` vgrajeni tip PHP. V nasprotnem primeru je ime razreda. +Ugotovi, ali je `$type` vgrajen tip PHP. V nasprotnem primeru gre za ime razreda. ```php Validators::isBuiltinType('string'); // true @@ -251,7 +251,7 @@ Validators::isBuiltinType('Foo'); // false isTypeDeclaration(string $type): bool .[method] ----------------------------------------------- -Preveri, ali je deklaracija tipa skladenjsko pravilna. +Preveri, ali je dana deklaracija tipa sintaktično veljavna. ```php Validators::isTypeDeclaration('?string'); // true @@ -268,7 +268,7 @@ Validators::isTypeDeclaration('(A|B)'); // false isClassKeyword(string $type): bool .[method] -------------------------------------------- -Določi, ali je `$type` eden od notranjih tipov `self`, `parent`, `static`. +Ugotovi, ali je `$type` eden od internih tipov `self`, `parent`, `static`. ```php Validators::isClassKeyword('self'); // true @@ -279,7 +279,7 @@ Validators::isClassKeyword('Foo'); // false isUnicode(mixed $value): bool .[method] --------------------------------------- -Preveri, ali je vrednost veljavni niz UTF-8. +Preveri, ali je vrednost veljaven UTF-8 niz. ```php Validators::isUnicode('nette'); // true @@ -291,7 +291,7 @@ Validators::isUnicode("\xA0"); // false isUrl(mixed $value): bool .[method] ----------------------------------- -Preveri, ali je vrednost veljaven naslov URL. +Preveri, ali je vrednost veljaven URL naslov. ```php Validators::isUrl('https://nette.org:8080/path?query#fragment'); // true @@ -306,7 +306,7 @@ Validators::isUrl('nette.org'); // false isUri(string $value): bool .[method] ------------------------------------ -Preveri, ali je vrednost veljaven naslov URI, torej dejansko niz, ki se začne s sintaktično veljavno shemo. +Preveri, ali je vrednost veljaven URI naslov, torej pravzaprav niz, ki se začne s sintaktično veljavno shemo. ```php Validators::isUri('https://nette.org'); // true diff --git a/utils/tr/@home.texy b/utils/tr/@home.texy index 45bc74103b..f33ad1f2c8 100644 --- a/utils/tr/@home.texy +++ b/utils/tr/@home.texy @@ -1,42 +1,46 @@ -Yardımcı Programlar -******************* +Nette Utils +*********** .[perex] -`nette/utils` paketinde günlük kullanım için bir dizi yararlı sınıf bulacaksınız: +`nette/utils` paketinde günlük kullanım için bir dizi kullanışlı sınıf bulacaksınız: -| [Diziler |Arrays] | Nette\Utils\Arrays -| [Geri |Callback] Çağırma | Nette\Utils\Callback +| [Geri Aramalar |Callback] | Nette\Utils\Callback | [Tarih ve Saat |datetime] | Nette\Utils\DateTime -| [Dosya Sistemi |filesystem] | Nette\Utils\FileSystem | [Finder |Finder] | Nette\Utils\Finder -| [Floats |Floats] | Nette\Utils\Floats -| [Yardımcılar |helpers] | Nette\Utils\Helpers -| [HTML Elemanları |HTML Elements] | Nette\Utils\Html -| [Görüntüler |Images] | Nette\Utils\Image -| [JSON |JSON] | Nette\Utils\Json -| [Nesne modeli |smartobject] | Nette\SmartObject & Nette\StaticClass -| [Paginator |paginator] | Nette\Utils\Paginator -| [PHP |reflection] Yansıması | Nette\Utils\Reflection -| [PHP Türleri |type] | Nette\Utils\Type +| [HTML Öğeleri |html-elements] | Nette\Utils\Html +| [Yineleyiciler |iterables] | Nette\Utils\Iterables +| [JSON |json] | Nette\Utils\Json | [Rastgele Dizeler |random] | Nette\Utils\Random -| [Dizeler |Strings] | Nette\Utils\Strings -| [Doğrulayıcılar |validators] | Nette\Utils\Validators +| [Resimler |images] | Nette\Utils\Image +| [PHP Yansıması |reflection] | Nette\Utils\Reflection +| [PHP Tipleri |type] | Nette\Utils\Type +| [Diziler |arrays] | Nette\Utils\Arrays +| [Yardımcı Fonksiyonlar |helpers] | Nette\Utils\Helpers +| [Kayan Nokta Karşılaştırması |floats] | Nette\Utils\Floats +| [Karakter Dizileri |strings] | Nette\Utils\Strings +| [Dosya Sistemi |filesystem] | Nette\Utils\FileSystem +| [Sayfalama |paginator] | Nette\Utils\Paginator +| [SmartObject |SmartObject] & [StaticClass |StaticClass] | Nette\SmartObject & Nette\StaticClass +| [Doğrulayıcı |validators] | Nette\Utils\Validators Kurulum ------- -[Composer'ı |best-practices:composer] kullanarak paketi indirin ve yükleyin: +Kütüphaneyi [Composer|best-practices:composer] aracını kullanarak indirip kurun: ```shell composer require nette/utils ``` -| sürüm | PHP ile uyumlu +| sürüm | PHP ile uyumlu |-----------|------------------- -| Nette Utils 4.0 | PHP 8.0 - 8.2 -| Nette Utils 3.2 | PHP 7.2 - 8.2 -| Nette Utils 3.0 - 3.1 | PHP 7.1 - 8.0 -| Nette Utils 2.5 | PHP 5.6 - 8.0 +| Nette Utils 4.0 | PHP 8.0 – 8.4 +| Nette Utils 3.2 | PHP 7.2 – 8.3 +| Nette Utils 3.0 – 3.1 | PHP 7.1 – 8.0 +| Nette Utils 2.5 | PHP 5.6 – 8.0 + +Son yama sürümü için geçerlidir. + -En son yama sürümleri için geçerlidir. +Paketi daha yeni bir sürüme güncelliyorsanız, [yükseltme |en:upgrading] sayfasına bakın. diff --git a/utils/tr/@left-menu.texy b/utils/tr/@left-menu.texy index 738761e936..0128fc9c9a 100644 --- a/utils/tr/@left-menu.texy +++ b/utils/tr/@left-menu.texy @@ -1,26 +1,28 @@ -Paket nette/utils -***************** -- [Diziler |Arrays] -- [Geri arama |Callback] +Nette Utils +*********** +- [Geri Aramalar |callback] - [Tarih ve Saat |datetime] -- [Dosya Sistemi |filesystem] -- [Bulucu |Finder] -- [Yardımcılar |helpers] -- [HTML Öğeleri |HTML Elements] -- [Görüntüler |Images] +- [Finder |Finder] +- [Floats |Floats] +- [HTML Öğeleri |html-elements] +- [Yineleyiciler |iterables] - [JSON |JSON] -- [Paginator |paginator] - [Rastgele Dizeler |random] +- [Resimler |images] +- [Paginator |paginator] +- [PHP Yansıması |reflection] +- [PHP Tipleri |type] +- [Diziler |arrays] +- [Yardımcı Fonksiyonlar |helpers] +- [Karakter Dizileri |strings] - [SmartObject |SmartObject] -- [PHP Yansıması|reflection] -- [Dizeler |Strings] -- [Yüzer |Floats] -- [PHP Türleri |type] -- [Doğrulayıcılar |validators] +- [StaticClass |StaticClass] +- [Dosya Sistemi |filesystem] +- [Doğrulayıcı |validators] -Diğer Yardımcı Programlar -************************* -- [NEON |neon:] -- [Parola Hashing |security:passwords] -- [URL Ayrıştırıcı ve Oluşturucu |http:urls] +Diğer Araçlar +************* +- [NEON|neon:] +- [Şifre Hashleme |security:passwords] +- [URL Ayrıştırma ve Oluşturma |http:urls] diff --git a/utils/tr/@meta.texy b/utils/tr/@meta.texy new file mode 100644 index 0000000000..8dfe82f311 --- /dev/null +++ b/utils/tr/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette Dokümantasyonu}} diff --git a/utils/tr/arrays.texy b/utils/tr/arrays.texy index 85e7b2610d..e47a08a863 100644 --- a/utils/tr/arrays.texy +++ b/utils/tr/arrays.texy @@ -1,8 +1,8 @@ -Dizi İşlevleri -************** +Dizilerle Çalışma +***************** .[perex] -Bu sayfa dizilerle ilgili olan [Nette\Utils\Arrays |#Arrays], [ArrayHash |#ArrayHash] ve [ArrayList |#ArrayList] sınıfları hakkındadır. +Bu sayfa, dizilerle ilgili olan [Nette\Utils\Arrays |#Arrays], [#ArrayHash] ve [#ArrayList] sınıflarına ayrılmıştır. Kurulum: @@ -12,22 +12,63 @@ composer require nette/utils ``` -Diziler .[#toc-arrays] -====================== +Arrays +====== -[api:Nette\Utils\Arrays] bir avuç kullanışlı dizi işlevi içeren statik bir sınıftır. +[api:Nette\Utils\Arrays], dizilerle çalışmak için yararlı fonksiyonlar içeren statik bir sınıftır. Yineleyiciler için eşdeğeri [Nette\Utils\Iterables |iterables]'dır. -Aşağıdaki örneklerde, aşağıdaki sınıf takma adının tanımlandığı varsayılmaktadır: +Aşağıdaki örnekler, oluşturulmuş bir takma ad varsayar: ```php use Nette\Utils\Arrays; ``` +associate(array $array, mixed $path): array|\stdClass .[method] +--------------------------------------------------------------- + +Fonksiyon, `$array` dizisini belirtilen `$path` yoluna göre esnek bir şekilde ilişkisel dizilere veya nesnelere dönüştürür. Yol bir karakter dizisi veya dizi olabilir. Giriş dizisinin anahtar adlarından ve '[]', '->', '=', ve '|' gibi operatörlerden oluşur. Yol geçersizse `Nette\InvalidArgumentException` istisnası fırlatır. + +```php +// basit bir anahtara göre ilişkisel diziye dönüştürme +$arr = [ + ['name' => 'John', 'age' => 11], + ['name' => 'Mary', 'age' => null], + // ... +]; +$result = Arrays::associate($arr, 'name'); +// $result = ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// = operatörünü kullanarak bir anahtardan diğerine değer atama +$result = Arrays::associate($arr, 'name=age'); // veya ['name', '=', 'age'] +// $result = ['John' => 11, 'Mary' => null, ...] +``` + +```php +// -> operatörünü kullanarak bir nesne oluşturma +$result = Arrays::associate($arr, '->name'); // veya ['->', 'name'] +// $result = (object) ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// | operatörünü kullanarak anahtarları birleştirme +$result = Arrays::associate($arr, 'name|age'); // veya ['name', '|', 'age'] +// $result: ['John' => ['name' => 'John', 'age' => 11], 'Paul' => ['name' => 'Paul', 'age' => 44]] +``` + +```php +// [] kullanarak diziye ekleme +$result = Arrays::associate($arr, 'name[]'); // veya ['name', '[]'] +// $result: ['John' => [['name' => 'John', 'age' => 22], ['name' => 'John', 'age' => 11]]] +``` + + contains(array $array, $value): bool .[method] ---------------------------------------------- -Bir dizide değer olup olmadığını sınar. Sıkı bir karşılaştırma kullanır (`===`) +Dizinin bir değer içerip içermediğini test eder. Katı karşılaştırma (`===`) kullanır. ```php Arrays::contains([1, 2, 3], 1); // true @@ -35,10 +76,10 @@ Arrays::contains(['1', false], 1); // false ``` -every(iterable $array, callable $callback): bool .[method] ----------------------------------------------------------- +every(array $array, callable $predicate): bool .[method] +-------------------------------------------------------- -Dizideki tüm öğelerin, `function ($value, $key, array $array): bool` imzasına sahip olan sağlanan işlev tarafından uygulanan testi geçip geçmediğini sınar. +Dizideki tüm öğelerin `$predicate` içinde uygulanan testi geçip geçmediğini test eder. İmza `function ($value, $key, array $array): bool` şeklindedir. ```php $array = [1, 30, 39, 29, 10, 13]; @@ -46,24 +87,59 @@ $isBelowThreshold = fn($value) => $value < 40; $res = Arrays::every($array, $isBelowThreshold); // true ``` -[some() |#some()] işlevine bakın. +Bkz. [#some()]. -first(array $array): mixed .[method] ------------------------------------- +filter(array $array, callable $predicate): array .[method]{data-version:4.0.4} +------------------------------------------------------------------------------ + +Belirtilen koşula uyan tüm anahtar-değer çiftlerini içeren yeni bir dizi döndürür. Geri arama imzası `function ($value, int|string $key, array $array): bool` şeklindedir. + +```php +Arrays::filter( + ['a' => 1, 'b' => 2, 'c' => 3], + fn($v) => $v < 3, +); +// ['a' => 1, 'b' => 2] +``` + + +first(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------- + +İlk öğeyi döndürür (eğer belirtilmişse koşula uyan). Böyle bir öğe yoksa, `$else` çağrısının sonucunu veya null döndürür. `$predicate` parametresinin imzası `function ($value, int|string $key, array $array): bool` şeklindedir. + +`reset()` fonksiyonunun aksine iç işaretçiyi değiştirmez. `$predicate` ve `$else` parametreleri sürüm 4.0.4'ten beri mevcuttur. + +```php +Arrays::first([1, 2, 3]); // 1 +Arrays::first([1, 2, 3], fn($v) => $v > 2); // 3 +Arrays::first([]); // null +Arrays::first([], else: fn() => false); // false +``` + +Bkz. [#last()]. -Dizideki ilk öğeyi veya dizi boşsa null döndürür. `reset()` adresinden farklı olarak dahili göstericiyi değiştirmez. + +firstKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +------------------------------------------------------------------------------------------------ + +İlk öğenin anahtarını (eğer belirtilmişse koşula uyan) veya böyle bir öğe yoksa null döndürür. `$predicate` koşulunun imzası `function ($value, int|string $key, array $array): bool` şeklindedir. ```php -Arrays::first([1, 2, 3]); // 1 -Arrays::first([]); // null +Arrays::firstKey([1, 2, 3]); // 0 +Arrays::firstKey([1, 2, 3], fn($v) => $v > 2); // 2 +Arrays::firstKey(['a' => 1, 'b' => 2]); // 'a' +Arrays::firstKey([]); // null ``` +Bkz. [#lastKey()]. + flatten(array $array, bool $preserveKeys=false): array .[method] ---------------------------------------------------------------- -Çok boyutlu diziyi düz diziye dönüştürür. +Çok seviyeli bir diziyi düz bir diziye birleştirir. ```php $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); @@ -71,62 +147,62 @@ $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); ``` -get(array $array, string|int|array $key, mixed $default=null): mixed .[method] ------------------------------------------------------------------------------- +get(array $array, string|int|array $key, ?mixed $default=null): mixed .[method] +------------------------------------------------------------------------------- -Geri dönüşler `$array[$key]` öğesi. Eğer mevcut değilse, üçüncü bağımsız değişken olarak varsayılan bir değer ayarlanmadığı sürece `Nette\InvalidArgumentException` adresi atılır. +`$array[$key]` öğesini döndürür. Eğer mevcut değilse, ya `Nette\InvalidArgumentException` istisnası fırlatır ya da üçüncü parametre `$default` belirtilmişse onu döndürür. ```php -// eğer $array['foo'] mevcut değilse, bir istisna atar +// eğer $array['foo'] mevcut değilse, istisna fırlatır $value = Arrays::get($array, 'foo'); // eğer $array['foo'] mevcut değilse, 'bar' döndürür $value = Arrays::get($array, 'foo', 'bar'); ``` -Argüman `$key` bir dizi de olabilir. +`$key` anahtarı bir dizi de olabilir. ```php $array = ['color' => ['favorite' => 'red'], 5]; $value = Arrays::get($array, ['color', 'favorite']); -// returns 'red' +// 'red' döndürür ``` getRef(array &$array, string|int|array $key): mixed .[method] ------------------------------------------------------------- -Verilen referansı döndürür `$array[$key]`. Dizin mevcut değilse, `null` değeriyle yeni bir dizin oluşturulur. +Belirtilen dizi öğesine bir referans alır. Eğer öğe mevcut değilse, null değeriyle oluşturulur. ```php $valueRef = & Arrays::getRef($array, 'foo'); -// $array['foo'] referansını döndürür +// $array['foo']'ya bir referans döndürür ``` -[get() |#get()] işlevinin yanı sıra çok boyutlu dizilerle de çalışır. +[#get()] fonksiyonu gibi, çok boyutlu dizilerle çalışabilir. ```php -$value = & Arrays::get($array, ['color', 'favorite']); -// $array['color']['favorite'] referansını döndürür +$value = & Arrays::getRef($array, ['color', 'favorite']); +// $array['color']['favorite']'a bir referans döndürür ``` grep(array $array, string $pattern, bool $invert=false): array .[method] ------------------------------------------------------------------------ -Yalnızca `$pattern` düzenli ifadesiyle eşleşen dizi öğelerini döndürür. `$invert` `true` ise, eşleşmeyen öğeleri döndürür. Regex derleme veya çalışma zamanı hatası `Nette\RegexpException` atar. +Yalnızca değeri `$pattern` düzenli ifadesiyle eşleşen dizi öğelerini döndürür. Eğer `$invert` `true` ise, tam tersine eşleşmeyen öğeleri döndürür. İfadenin derlenmesi veya işlenmesi sırasında bir hata oluşursa `Nette\RegexpException` istisnası fırlatır. ```php $filteredArray = Arrays::grep($array, '~^\d+$~'); -// sadece sayısal öğeleri döndürür +// yalnızca rakamlardan oluşan dizi öğelerini döndürür ``` insertAfter(array &$array, string|int|null $key, array $inserted): void .[method] --------------------------------------------------------------------------------- -`$inserted` dizisinin içeriğini `$key` dizisinden hemen sonra `$array` dizisine ekler. `$key` dizisi `null` dizisi ise (veya yoksa), dizinin sonuna eklenir. +`$inserted` dizisinin içeriğini `$array` dizisine, `$key` anahtarına sahip öğeden hemen sonra ekler. Eğer `$key` `null` ise (veya dizide yoksa), sona eklenir. ```php $array = ['first' => 10, 'second' => 20]; @@ -138,7 +214,7 @@ Arrays::insertAfter($array, 'first', ['hello' => 'world']); insertBefore(array &$array, string|int|null $key, array $inserted): void .[method] ---------------------------------------------------------------------------------- -`$inserted` dizisinin içeriğini `$key`'den önce `$array` 'e yerleştirir. `$key`, `null` ise (veya yoksa), başa yerleştirilir. +`$inserted` dizisinin içeriğini `$array` dizisine, `$key` anahtarına sahip öğeden önce ekler. Eğer `$key` `null` ise (veya dizide yoksa), başa eklenir. ```php $array = ['first' => 10, 'second' => 20]; @@ -150,7 +226,7 @@ Arrays::insertBefore($array, 'first', ['hello' => 'world']); invoke(iterable $callbacks, ...$args): array .[method] ------------------------------------------------------ -Tüm geri çağırmaları çağırır ve sonuç dizisini döndürür. +Tüm geri aramaları çağırır ve sonuçların bir dizisini döndürür. ```php $callbacks = [ @@ -166,7 +242,7 @@ $array = Arrays::invoke($callbacks, 5, 11); invokeMethod(iterable $objects, string $method, ...$args): array .[method] -------------------------------------------------------------------------- -Bir dizideki her nesne üzerinde yöntem çağırır ve sonuç dizisini döndürür. +Dizideki her nesne üzerinde bir metot çağırır ve sonuçların bir dizisini döndürür. ```php $objects = ['a' => $obj1, 'b' => $obj2]; @@ -179,7 +255,7 @@ $array = Arrays::invokeMethod($objects, 'foo', 1, 2); isList(array $array): bool .[method] ------------------------------------ -Dizinin sıfırdan itibaren artan sırada sayısal anahtarlarla indislenip indislenmediğini denetler. +Dizinin sıfırdan başlayarak artan bir dizi sayısal anahtara göre indekslenip indekslenmediğini, yani bir liste (list) olup olmadığını kontrol eder. ```php Arrays::isList(['a', 'b', 'c']); // true @@ -188,21 +264,42 @@ Arrays::isList(['a' => 1, 'b' => 2]); // false ``` -last(array $array): mixed .[method] ------------------------------------ +last(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------ + +Son öğeyi döndürür (eğer belirtilmişse koşula uyan). Böyle bir öğe yoksa, `$else` çağrısının sonucunu veya null döndürür. `$predicate` parametresinin imzası `function ($value, int|string $key, array $array): bool` şeklindedir. -Dizideki son öğeyi veya dizi boşsa null döndürür. `end()` adresinden farklı olarak dahili göstericiyi değiştirmez. +`end()` fonksiyonunun aksine iç işaretçiyi değiştirmez. `$predicate` ve `$else` parametreleri sürüm 4.0.4'ten beri mevcuttur. ```php -Arrays::last([1, 2, 3]); // 3 -Arrays::last([]); // null +Arrays::last([1, 2, 3]); // 3 +Arrays::last([1, 2, 3], fn($v) => $v < 3); // 2 +Arrays::last([]); // null +Arrays::last([], else: fn() => false); // false ``` +Bkz. [#first()]. -map(iterable $array, callable $callback): array .[method] + +lastKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +----------------------------------------------------------------------------------------------- + +Son öğenin anahtarını (eğer belirtilmişse koşula uyan) veya böyle bir öğe yoksa null döndürür. `$predicate` koşulunun imzası `function ($value, int|string $key, array $array): bool` şeklindedir. + +```php +Arrays::lastKey([1, 2, 3]); // 2 +Arrays::lastKey([1, 2, 3], fn($v) => $v < 3); // 1 +Arrays::lastKey(['a' => 1, 'b' => 2]); // 'b' +Arrays::lastKey([]); // null +``` + +Bkz. [#firstKey()]. + + +map(array $array, callable $transformer): array .[method] --------------------------------------------------------- -Dizideki tüm öğeler üzerinde `$callback` adresini çağırır ve dönüş değerleri dizisini döndürür. Geri arama `function ($value, $key, array $array): bool` imzasına sahiptir. +Dizideki tüm öğeler üzerinde `$transformer`'ı çağırır ve döndürülen değerlerin bir dizisini döndürür. Geri arama imzası `function ($value, $key, array $array): mixed` şeklindedir. ```php $array = ['foo', 'bar', 'baz']; @@ -211,10 +308,24 @@ $res = Arrays::map($array, fn($value) => $value . $value); ``` +mapWithKeys(array $array, callable $transformer): array .[method] +----------------------------------------------------------------- + +Orijinal dizinin değerlerini ve anahtarlarını dönüştürerek yeni bir dizi oluşturur. `$transformer` fonksiyonunun imzası `function ($value, $key, array $array): ?array{$newKey, $newValue}` şeklindedir. Eğer `$transformer` `null` döndürürse, öğe atlanır. Korunan öğeler için, döndürülen dizinin ilk öğesi yeni anahtar olarak ve ikinci öğesi yeni değer olarak kullanılır. + +```php +$array = ['a' => 1, 'b' => 2]; +$result = Arrays::mapWithKeys($array, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +Bu metot, dizinin yapısını (anahtarları ve değerleri aynı anda) değiştirmeniz veya dönüşüm sırasında öğeleri filtrelemeniz (istenmeyen öğeler için null döndürerek) gerektiğinde kullanışlıdır. + + mergeTree(array $array1, array $array2): array .[method] -------------------------------------------------------- -İki alanı özyinelemeli olarak birleştirir. Örneğin, ağaç yapılarını birleştirmek için kullanışlıdır. Dizi için `+` operatörü gibi davranır, yani ikinci diziden bir anahtar/değer çiftini birincisine ekler ve bir anahtar çakışması durumunda ilk dizideki değeri korur. +İki diziyi özyinelemeli olarak birleştirir. Örneğin, ağaç yapılarını birleştirmek için kullanışlıdır. Birleştirme sırasında, dizilere uygulanan `+` operatörü ile aynı kuralları izler, yani ikinci diziden anahtar/değer çiftlerini birinci diziye ekler ve anahtar çakışması durumunda birinci dizideki değeri korur. ```php $array1 = ['color' => ['favorite' => 'red'], 5]; @@ -224,13 +335,13 @@ $array = Arrays::mergeTree($array1, $array2); // $array = ['color' => ['favorite' => 'red', 'blue'], 5]; ``` -İkinci dizideki değerler her zaman birinciye eklenir. İkinci diziden `10` değerinin kaybolması biraz kafa karıştırıcı görünebilir. Bu değerin yanı sıra `5` in the first array have the same numeric key `0` değerinin de kaybolduğuna dikkat edilmelidir, bu nedenle ortaya çıkan alanda yalnızca ilk diziden bir öğe vardır. +İkinci dizideki değerler her zaman birinci dizinin sonuna eklenir. İkinci dizideki `10` değerinin kaybolması biraz kafa karıştırıcı görünebilir. Bu değerin ve aynı şekilde birinci dizideki `5` değerinin aynı sayısal anahtar `0`'a atandığını unutmamak gerekir, bu nedenle sonuçtaki dizide yalnızca birinci dizideki öğe bulunur. -normalize(array $array, string $filling=null): array .[method] --------------------------------------------------------------- +normalize(array $array, ?string $filling=null): array .[method] +--------------------------------------------------------------- -Diziyi ilişkisel diziye normalleştirir. Sayısal anahtarları değerleriyle değiştirin, yeni değer `$filling` olacaktır. +Diziyi ilişkisel bir diziye normalleştirir. Sayısal anahtarları değerleriyle değiştirir, yeni değer `$filling` olur. ```php $array = Arrays::normalize([1 => 'first', 'a' => 'second']); @@ -243,26 +354,26 @@ $array = Arrays::normalize([1 => 'first', 'a' => 'second'], 'foobar'); ``` -pick(array &$array, string|int $key, mixed $default=null): mixed .[method] --------------------------------------------------------------------------- +pick(array &$array, string|int $key, ?mixed $default=null): mixed .[method] +--------------------------------------------------------------------------- -Bir diziden bir öğenin değerini döndürür ve kaldırır. Mevcut değilse, bir istisna atar veya sağlanmışsa `$default` döndürür. +Bir öğenin değerini diziden döndürür ve kaldırır. Eğer mevcut değilse, istisna fırlatır veya belirtilmişse `$default` değerini döndürür. ```php -$array = [1 => 'foo', null => 'bar']; -$a = Arrays::pick($array, null); +$array = [1 => 'foo', 'x' => 'bar']; +$a = Arrays::pick($array, 'x'); // $a = 'bar' $b = Arrays::pick($array, 'not-exists', 'foobar'); // $b = 'foobar' $c = Arrays::pick($array, 'not-exists'); -// throws Nette\InvalidArgumentException +// Nette\InvalidArgumentException fırlatır ``` renameKey(array &$array, string|int $oldKey, string|int $newKey): bool .[method] -------------------------------------------------------------------------------- -Bir anahtarı yeniden adlandırır. Anahtar dizide bulunmuşsa `true` döndürür. +Dizideki bir anahtarı yeniden adlandırır. Anahtar dizide bulunursa `true` döndürür. ```php $array = ['first' => 10, 'second' => 20]; @@ -274,20 +385,20 @@ Arrays::renameKey($array, 'first', 'renamed'); getKeyOffset(array $array, string|int $key): ?int .[method] ----------------------------------------------------------- -Verilen dizi anahtarının sıfır indeksli konumunu döndürür. Anahtar bulunamazsa `null` döndürür. +Belirtilen anahtarın dizideki konumunu döndürür. Konum 0'dan başlar. Anahtar bulunamazsa, fonksiyon `null` döndürür. ```php $array = ['first' => 10, 'second' => 20]; -$position = Arrays::getKeyOffset($array, 'first'); // returns 0 -$position = Arrays::getKeyOffset($array, 'second'); // returns 1 -$position = Arrays::getKeyOffset($array, 'not-exists'); // returns null +$position = Arrays::getKeyOffset($array, 'first'); // 0 döndürür +$position = Arrays::getKeyOffset($array, 'second'); // 1 döndürür +$position = Arrays::getKeyOffset($array, 'not-exists'); // null döndürür ``` -some(iterable $array, callable $callback): bool .[method] ---------------------------------------------------------- +some(array $array, callable $predicate): bool .[method] +------------------------------------------------------- -Dizideki en az bir öğenin `function ($value, $key, array $array): bool` imzasıyla sağlanan geri arama tarafından uygulanan testi geçip geçmediğini sınar. +Dizideki en az bir öğenin `$predicate` içinde uygulanan testi geçip geçmediğini test eder. İmza `function ($value, $key, array $array): bool` şeklindedir. ```php $array = [1, 2, 3, 4]; @@ -295,13 +406,13 @@ $isEven = fn($value) => $value % 2 === 0; $res = Arrays::some($array, $isEven); // true ``` -Bkz. [every() |#every()]. +Bkz. [#every()]. toKey(mixed $key): string|int .[method] --------------------------------------- -Bir değeri, tamsayı veya dize olan bir dizi anahtarına dönüştürür. +Bir değeri dizi anahtarına dönüştürür; bu ya bir tamsayı ya da bir karakter dizisidir. ```php Arrays::toKey('1'); // 1 @@ -312,19 +423,19 @@ Arrays::toKey('01'); // '01' toObject(iterable $array, object $object): object .[method] ----------------------------------------------------------- -`$array` dizisinin öğelerini `$object` nesnesine kopyalar ve sonra geri döndürür. +`$array` dizisinin öğelerini `$object` nesnesine kopyalar ve ardından nesneyi döndürür. ```php $obj = new stdClass; $array = ['foo' => 1, 'bar' => 2]; -Arrays::toObject($array, $obj); // it sets $obj->foo = 1; $obj->bar = 2; +Arrays::toObject($array, $obj); // $obj->foo = 1; $obj->bar = 2; ayarlar ``` -wrap(iterable $array, string $prefix='', string $suffix=''): array .[method] ----------------------------------------------------------------------------- +wrap(array $array, string $prefix='', string $suffix=''): array .[method] +------------------------------------------------------------------------- -Dizinin her bir elemanını dizeye dönüştürür ve `$prefix` ve `$suffix` ile çevreler. +Dizideki her öğeyi bir karakter dizisine dönüştürür ve `$prefix` öneki ve `$suffix` soneki ile sarar. ```php $array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); @@ -332,21 +443,21 @@ $array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); ``` -ArrayHash .[#toc-arrayhash] -=========================== +ArrayHash +========= -Object [api:Nette\Utils\ArrayHash] genel sınıf stdClass'ın soyundan gelir ve onu bir dizi olarak ele alma, örneğin köşeli parantez kullanarak üyelere erişme becerisine genişletir: +[api:Nette\Utils\ArrayHash] nesnesi, genel `stdClass` sınıfının bir alt sınıfıdır ve onu bir dizi gibi ele alma yeteneğiyle genişletir, yani örneğin üyelere köşeli parantezlerle erişme: ```php $hash = new Nette\Utils\ArrayHash; $hash['foo'] = 123; -$hash->bar = 456; // nesne gösteriminde de çalışır +$hash->bar = 456; // aynı zamanda nesne gösterimi de çalışır $hash->foo; // 123 ``` -Eleman sayısını elde etmek için `count($hash)` işlevini kullanabilirsiniz. +Öğe sayısını öğrenmek için `count($hash)` fonksiyonunu kullanabilirsiniz. -Bir nesne üzerinde, bir dizide yaptığınız gibi, hatta bir referansla bile yineleme yapabilirsiniz: +Nesne üzerinde, bir dizi durumunda olduğu gibi, referansla bile yinelenebilir: ```php foreach ($hash as $key => $value) { @@ -354,11 +465,11 @@ foreach ($hash as $key => $value) { } foreach ($hash as $key => &$value) { - $value = 'yeni değer'; + $value = 'new value'; } ``` -Mevcut diziler `from()` kullanılarak `ArrayHash` adresine dönüştürülebilir: +Mevcut bir diziyi `from()` metoduyla `ArrayHash`'e dönüştürebiliriz: ```php $array = ['foo' => 123, 'bar' => 456]; @@ -374,29 +485,29 @@ Dönüşüm özyinelemelidir: $array = ['foo' => 123, 'inner' => ['a' => 'b']]; $hash = Nette\Utils\ArrayHash::from($array); -$hash->inner; // object ArrayHash +$hash->inner; // ArrayHash nesnesi $hash->inner->a; // 'b' $hash['inner']['a']; // 'b' ``` -İkinci parametre ile önlenebilir: +Bu, ikinci parametre ile önlenebilir: ```php $hash = Nette\Utils\ArrayHash::from($array, false); -$hash->inner; // array +$hash->inner; // dizi ``` -Diziye geri dönüştürün: +Tekrar diziye dönüştürme: ```php $array = (array) $hash; ``` -ArrayList .[#toc-arraylist] -=========================== +ArrayList +========= -[api:Nette\Utils\ArrayList] indislerin yalnızca 0'dan itibaren artan tamsayılar olduğu doğrusal bir diziyi temsil eder. +[api:Nette\Utils\ArrayList], indekslerin yalnızca 0'dan başlayarak artan tamsayılar olduğu doğrusal bir diziyi temsil eder. ```php $list = new Nette\Utils\ArrayList; @@ -407,9 +518,16 @@ $list[] = 'c'; count($list); // 3 ``` -Öğe sayısını almak için `count($list)` işlevini kullanabilirsiniz. +Mevcut bir diziyi `from()` metoduyla `ArrayList`'e dönüştürebiliriz: -Bir nesne üzerinde, bir dizide yaptığınız gibi, hatta bir referansla bile yineleme yapabilirsiniz: +```php +$array = ['foo', 'bar']; +$list = Nette\Utils\ArrayList::from($array); +``` + +Öğe sayısını öğrenmek için `count($list)` fonksiyonunu kullanabilirsiniz. + +Nesne üzerinde, bir dizi durumunda olduğu gibi, referansla bile yinelenebilir: ```php foreach ($list as $key => $value) { @@ -417,32 +535,25 @@ foreach ($list as $key => $value) { } foreach ($list as $key => &$value) { - $value = 'yeni değer'; + $value = 'new value'; } ``` -Mevcut diziler `from()` kullanılarak `ArrayList` adresine dönüştürülebilir: - -```php -$array = ['foo', 'bar']; -$list = Nette\Utils\ArrayList::from($array); -``` - -İzin verilen değerlerin ötesindeki anahtarlara erişmek bir istisna oluşturur `Nette\OutOfRangeException`: +İzin verilen değerlerin dışındaki anahtarlara erişim `Nette\OutOfRangeException` istisnası fırlatır: ```php -echo $list[-1]; // throws Nette\OutOfRangeException -unset($list[30]); // throws Nette\OutOfRangeException +echo $list[-1]; // Nette\OutOfRangeException fırlatır +unset($list[30]); // Nette\OutOfRangeException fırlatır ``` -Anahtarın çıkarılması elemanların yeniden numaralandırılmasına neden olacaktır: +Bir anahtarın kaldırılması öğelerin yeniden numaralandırılmasına neden olur: ```php unset($list[1]); // ArrayList(0 => 'a', 1 => 'c') ``` -`prepend()` adresini kullanarak başlangıca yeni bir öğe ekleyebilirsiniz: +`prepend()` metoduyla başa yeni bir öğe eklenebilir: ```php $list->prepend('d'); diff --git a/utils/tr/callback.texy b/utils/tr/callback.texy index acfc62ac47..8027b6e5ab 100644 --- a/utils/tr/callback.texy +++ b/utils/tr/callback.texy @@ -1,8 +1,8 @@ -Geri Çağırma İşlevleri +Callback'lerle Çalışma ********************** .[perex] -[api:Nette\Utils\Callback] [PHP geri aramalarıyla |https://www.php.net/manual/en/language.types.callable.php] çalışmak için işlevler içeren statik bir sınıftır. +[api:Nette\Utils\Callback], [PHP callback'leri |https://www.php.net/manual/en/language.types.callable.php] ile çalışmak için fonksiyonlar içeren statik bir sınıftır. Kurulum: @@ -11,7 +11,7 @@ Kurulum: composer require nette/utils ``` -Tüm örnekler aşağıdaki sınıf takma adının tanımlandığını varsayar: +Tüm örnekler, oluşturulmuş bir takma ad varsayar: ```php use Nette\Utils\Callback; @@ -21,21 +21,21 @@ use Nette\Utils\Callback; check($callable, bool $syntax=false): callable .[method] -------------------------------------------------------- -`$callable` adresinin geçerli bir PHP geri araması olup olmadığını kontrol eder. Aksi takdirde `Nette\InvalidArgumentException` atar. `$syntax` true olarak ayarlanırsa, işlev yalnızca `$callable` adresinin geriçağırım olarak kullanılacak geçerli bir yapıya sahip olduğunu doğrular, ancak sınıf veya yöntemin gerçekten var olup olmadığını doğrulamaz. `$callable` döndürür. +`$callable` değişkeninin geçerli bir callback olup olmadığını kontrol eder. Aksi takdirde `Nette\InvalidArgumentException` istisnası fırlatır. Eğer `$syntax` true ise, fonksiyon yalnızca `$callable`'ın bir callback yapısına sahip olduğunu doğrular, ancak belirtilen sınıfın veya metodun gerçekten var olup olmadığını doğrulamaz. `$callable`'ı döndürür. ```php -Callback::check('trim'); // istisna yok -Callback::check(['NonExistentClass', 'method']); // throws Nette\InvalidArgumentException -Callback::check(['NonExistentClass', 'method'], true); // istisna yok -Callback::check(function () {}); // istisna yok -Callback::check(null); // throws Nette\InvalidArgumentException +Callback::check('trim'); // istisna fırlatmaz +Callback::check(['NonExistentClass', 'method']); // Nette\InvalidArgumentException fırlatır +Callback::check(['NonExistentClass', 'method'], true); // istisna fırlatmaz +Callback::check(function () {}); // istisna fırlatmaz +Callback::check(null); // Nette\InvalidArgumentException fırlatır ``` toString($callable): string .[method] ------------------------------------- -PHP geri aramasını metinsel biçime dönüştürür. Sınıf veya yöntem mevcut olmayabilir. +PHP callback'ini metin biçimine dönüştürür. Sınıf veya metot mevcut olmayabilir. ```php Callback::toString('trim'); // 'trim' @@ -46,21 +46,21 @@ Callback::toString(['MyClass', 'method']); // 'MyClass::method' toReflection($callable): ReflectionMethod|ReflectionFunction .[method] ---------------------------------------------------------------------- -PHP geriçağırımında kullanılan yöntem veya işlev için yansıma döndürür. +PHP callback'indeki metot veya fonksiyon için yansımayı (reflection) döndürür. ```php $ref = Callback::toReflection('trim'); -// $ref is ReflectionFunction('trim') +// $ref, ReflectionFunction('trim')'dir $ref = Callback::toReflection(['MyClass', 'method']); -// $ref is ReflectionMethod('MyClass', 'method') +// $ref, ReflectionMethod('MyClass', 'method')'dir ``` isStatic($callable): bool .[method] ----------------------------------- -PHP geriçağırımının işlev mi yoksa duruk yöntem mi olduğunu denetler. +PHP callback'inin bir fonksiyon mu yoksa statik bir metot mu olduğunu belirler. ```php Callback::isStatic('trim'); // true @@ -73,7 +73,7 @@ Callback::isStatic(function () {}); // false unwrap(Closure $closure): callable|array .[method] -------------------------------------------------- -`Closure::fromCallable`:https://www.php.net/manual/en/closure.fromcallable.php tarafından oluşturulan Unwraps kapanışı. +`Closure::fromCallable()` kullanılarak oluşturulan bir Closure'ı geri açar: https://www.php.net/manual/en/closure.fromcallable.php. ```php $closure = Closure::fromCallable(['MyClass', 'method']); diff --git a/utils/tr/datetime.texy b/utils/tr/datetime.texy index d764a9531f..f2379e7d78 100644 --- a/utils/tr/datetime.texy +++ b/utils/tr/datetime.texy @@ -2,7 +2,7 @@ Tarih ve Saat ************* .[perex] -[api:Nette\Utils\DateTime] yerel [php:DateTime] adresini genişleten bir sınıftır. +[api:Nette\Utils\DateTime], yerel [php:DateTime] sınıfını ek fonksiyonlarla genişleten bir sınıftır. Kurulum: @@ -11,7 +11,7 @@ Kurulum: composer require nette/utils ``` -Tüm örnekler aşağıdaki sınıf takma adının tanımlandığını varsayar: +Tüm örnekler, oluşturulmuş bir takma ad varsayar: ```php use Nette\Utils\DateTime; @@ -20,35 +20,35 @@ use Nette\Utils\DateTime; static from(string|int|\DateTimeInterface $time): DateTime .[method] -------------------------------------------------------------------- -Bir dizeden, UNIX zaman damgasından veya başka bir [php:DateTimeInterface] nesnesinden DateTime nesnesi oluşturur. Tarih ve saat geçerli değilse `Exception` adresini atar. +Bir karakter dizisi, UNIX zaman damgası veya başka bir [php:DateTimeInterface] nesnesinden bir DateTime nesnesi oluşturur. Tarih ve saat geçerli değilse `Exception` istisnası fırlatır. ```php -DateTime::from(1138013640); // creates a DateTime from the UNIX timestamp with a default timezamp -DateTime::from(42); // creates a DateTime from the current time plus 42 seconds -DateTime::from('1994-02-26 04:15:32'); // creates a DateTime based on a string -DateTime::from('1994-02-26'); // create DateTime by date, time will be 00:00:00 +DateTime::from(1138013640); // varsayılan zaman dilimiyle UNIX zaman damgasından DateTime oluşturur +DateTime::from(42); // geçerli zamandan artı 42 saniye ile DateTime oluşturur +DateTime::from('1994-02-26 04:15:32'); // karakter dizisine göre DateTime oluşturur +DateTime::from('1994-02-26'); // tarihe göre DateTime oluşturur, saat 00:00:00 olur ``` static fromParts(int $year, int $month, int $day, int $hour=0, int $minute=0, float $second=0.0): DateTime .[method] -------------------------------------------------------------------------------------------------------------------- -DateTime nesnesini oluşturur veya tarih ve saat geçerli değilse bir `Nette\InvalidArgumentException` istisnası atar. +Bir DateTime nesnesi oluşturur veya tarih ve saat geçerli değilse `Nette\InvalidArgumentException` istisnası fırlatır. ```php DateTime::fromParts(1994, 2, 26, 4, 15, 32); ``` -static createFromFormat(string $format, string $time, string|\DateTimeZone $timezone=null): DateTime|false .[method] --------------------------------------------------------------------------------------------------------------------- -[DateTime::createFromFormat() |https://www.php.net/manual/en/datetime.createfromformat.php] işlevini, bir zaman dilimini dize olarak belirtme özelliğiyle genişletir. +static createFromFormat(string $format, string $time, ?string|\DateTimeZone $timezone=null): DateTime|false .[method] +--------------------------------------------------------------------------------------------------------------------- +[DateTime::createFromFormat() |https://www.php.net/manual/en/datetime.createfromformat.php] fonksiyonunu, zaman dilimini bir karakter dizisi olarak belirtme yeteneğiyle genişletir. ```php -DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); // create with custom timezone +DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); ``` modifyClone(string $modify=''): static .[method] ------------------------------------------------ -Değiştirilmiş zamana sahip bir kopya oluşturur. +Değiştirilmiş zamanla bir kopya oluşturur. ```php $original = DateTime::from('2017-02-03'); $clone = $original->modifyClone('+1 day'); @@ -59,15 +59,15 @@ $clone->format('Y-m-d'); // '2017-02-04' __toString(): string .[method] ------------------------------ -Tarih ve saati `Y-m-d H:i:s` biçiminde döndürür. +Tarih ve saati `Y-m-d H:i:s` formatında döndürür. ```php echo $dateTime; // '2017-02-03 04:15:32' ``` -Implements JsonSerializable .[#toc-implements-jsonserializable] ---------------------------------------------------------------- -Tarih ve saati, örneğin JavaScript'te kullanılan ISO 8601 biçiminde döndürür. +JsonSerializable uygular +------------------------ +Tarih ve saati, örneğin JavaScript'te kullanılan ISO 8601 formatında döndürür. ```php $date = DateTime::from('2017-02-03'); echo json_encode($date); diff --git a/utils/tr/filesystem.texy b/utils/tr/filesystem.texy index 8ed0f75b1a..891f743540 100644 --- a/utils/tr/filesystem.texy +++ b/utils/tr/filesystem.texy @@ -1,41 +1,43 @@ -Dosya Sistemi İşlevleri -*********************** +Dosya Sistemi +************* .[perex] -[api:Nette\Utils\FileSystem] bir dosya sistemi ile çalışmak için yararlı işlevler içeren duruk bir sınıftır. Yerel PHP işlevlerine göre bir avantajı, hata durumunda istisnalar atmasıdır. +[api:Nette\Utils\FileSystem], dosya sistemiyle çalışmak için yararlı fonksiyonlar içeren bir sınıftır. Yerel PHP fonksiyonlarına göre avantajlarından biri, hata durumunda istisnalar fırlatmasıdır. +Diskte dosya aramanız gerekiyorsa, [Finder|finder]'ı kullanın. + Kurulum: ```shell composer require nette/utils ``` -Aşağıdaki örneklerde, aşağıdaki sınıf takma adının tanımlandığı varsayılmaktadır: +Aşağıdaki örnekler, oluşturulmuş bir takma ad varsayar: ```php use Nette\Utils\FileSystem; ``` -Manipülasyon .[#toc-manipulation] -================================= +Manipülasyon +============ copy(string $origin, string $target, bool $overwrite=true): void .[method] -------------------------------------------------------------------------- -Bir dosyayı veya tüm dizini kopyalar. Varsayılan olarak mevcut dosya ve dizinlerin üzerine yazar. `$overwrite` öğesi `false` olarak ayarlanmışsa ve bir `$target` zaten mevcutsa, bir `Nette\InvalidStateException` istisnası atar. Hata oluştuğunda `Nette\IOException` istisnası atar. +Bir dosyayı veya tüm dizini kopyalar. Varsayılan olarak mevcut dosyaları ve dizinleri üzerine yazar. `$overwrite` parametresi `false` olarak ayarlandığında, hedef dosya veya dizin `$target` mevcutsa `Nette\InvalidStateException` istisnası fırlatır. Hata durumunda `Nette\IOException` istisnası fırlatır. ```php FileSystem::copy('/path/to/source', '/path/to/dest', overwrite: true); ``` -createDir(string $directory, int $mode=0777): void .[method] ------------------------------------------------------------- +createDir(string $dir, int $mode=0777): void .[method] +------------------------------------------------------ -Üst dizinler de dahil olmak üzere, mevcut değilse bir dizin oluşturur. Hata oluştuğunda `Nette\IOException` şeklinde bir istisna atar. +Mevcut değilse, üst dizinler dahil olmak üzere bir dizin oluşturur. Hata durumunda `Nette\IOException` istisnası fırlatır. ```php FileSystem::createDir('/path/to/dir'); @@ -45,7 +47,7 @@ FileSystem::createDir('/path/to/dir'); delete(string $path): void .[method] ------------------------------------ -Bir dosyayı veya varsa bir dizinin tamamını siler. Dizin boş değilse, önce içeriğini siler. Hata oluştuğunda `Nette\IOException` şeklinde bir istisna atar. +Mevcutsa bir dosyayı veya tüm dizini siler. Dizin boş değilse, önce içeriğini siler. Hata durumunda `Nette\IOException` istisnası fırlatır. ```php FileSystem::delete('/path/to/fileOrDir'); @@ -55,7 +57,7 @@ FileSystem::delete('/path/to/fileOrDir'); makeWritable(string $path, int $dirMode=0777, int $fileMode=0666): void .[method] --------------------------------------------------------------------------------- -Dosya izinlerini `$fileMode` veya dizin izinlerini `$dirMode` olarak ayarlar. Özyinelemeli olarak dizinin tüm içeriğini dolaşır ve izinleri de ayarlar. +Dosya izinlerini `$fileMode` veya dizin izinlerini `$dirMode` olarak ayarlar. Özyinelemeli olarak geçer ve dizinin tüm içeriğinin izinlerini de ayarlar. ```php FileSystem::makeWritable('/path/to/fileOrDir'); @@ -65,7 +67,7 @@ FileSystem::makeWritable('/path/to/fileOrDir'); open(string $path, string $mode): resource .[method] ---------------------------------------------------- -Dosya açar ve kaynak döndürür. `$mode` parametresi, yerel `fopen()`:https://www.php.net/manual/en/function.fopen.php işleviyle aynı şekilde çalışır. Bir hata oluşursa, `Nette\IOException` istisnasını yükseltir. +Bir dosyayı açar ve kaynağı döndürür. `$mode` parametresi, yerel `fopen()`:https://www.php.net/manual/en/function.fopen.php fonksiyonundaki gibi çalışır. Hata durumunda `Nette\IOException` istisnası fırlatır. ```php $res = FileSystem::open('/path/to/file', 'r'); @@ -75,7 +77,7 @@ $res = FileSystem::open('/path/to/file', 'r'); read(string $file): string .[method] ------------------------------------ -Bir `$file` içeriğini okur. Hata oluştuğunda `Nette\IOException` istisnası atar. +`$file` dosyasının içeriğini döndürür. Hata durumunda `Nette\IOException` istisnası fırlatır. ```php $content = FileSystem::read('/path/to/file'); @@ -85,14 +87,13 @@ $content = FileSystem::read('/path/to/file'); readLines(string $file, bool $stripNewLines=true): \Generator .[method] ----------------------------------------------------------------------- -Dosya içeriğini satır satır okur. Yerel `file()` işlevinden farklı olarak, dosyanın tamamını belleğe okumaz, ancak mevcut bellekten daha büyük dosyaların okunabilmesi için sürekli olarak okur. `$stripNewLines` , `\r` ve `\n` satır sonu karakterlerinin çıkarılıp çıkarılmayacağını belirtir. -Hata durumunda, bir `Nette\IOException` istisnası yükseltir. +Dosyanın içeriğini satır satır okur. Yerel `file()` fonksiyonunun aksine, tüm dosyayı belleğe yüklemez, ancak sürekli olarak okur, bu nedenle mevcut bellekten daha büyük dosyaları okumak mümkündür. `$stripNewLines`, satır sonu karakterleri `\r` ve `\n`'nin kaldırılıp kaldırılmayacağını belirtir. Hata durumunda `Nette\IOException` istisnası fırlatır. ```php $lines = FileSystem::readLines('/path/to/file'); foreach ($lines as $lineNum => $line) { - echo "Line $lineNum: $line\n"; + echo "Satır $lineNum: $line\n"; } ``` @@ -100,7 +101,7 @@ foreach ($lines as $lineNum => $line) { rename(string $origin, string $target, bool $overwrite=true): void .[method] ---------------------------------------------------------------------------- - `$origin` tarafından belirtilen bir dosyayı veya dizini `$target` adresine yeniden adlandırır veya taşır. Varsayılan olarak mevcut dosya ve dizinlerin üzerine yazar. `$overwrite` öğesi `false` olarak ayarlanmışsa ve `$target` zaten mevcutsa, bir `Nette\InvalidStateException` istisnası atar. Hata oluştuğunda `Nette\IOException` istisnası atar. +`$origin` dosyasını veya dizinini yeniden adlandırır veya taşır. Varsayılan olarak mevcut dosyaları ve dizinleri üzerine yazar. `$overwrite` parametresi `false` olarak ayarlandığında, hedef dosya veya dizin `$target` mevcutsa `Nette\InvalidStateException` istisnası fırlatır. Hata durumunda `Nette\IOException` istisnası fırlatır. ```php FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); @@ -110,21 +111,21 @@ FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); write(string $file, string $content, int $mode=0666): void .[method] -------------------------------------------------------------------- - `$content` adresini bir `$file` adresine yazar. Hata oluştuğunda bir `Nette\IOException` istisnası atar. +`$content` karakter dizisini `$file` dosyasına yazar. Hata durumunda `Nette\IOException` istisnası fırlatır. ```php FileSystem::write('/path/to/file', $content); ``` -Yollar .[#toc-paths] -==================== +Yollar +====== isAbsolute(string $path): bool .[method] ---------------------------------------- - `$path` adresinin mutlak olup olmadığını belirler. +`$path` yolunun mutlak olup olmadığını kontrol eder. ```php FileSystem::isAbsolute('../backup'); // false @@ -146,7 +147,7 @@ FileSystem::joinPaths('/a/', '/../b'); // '/b' normalizePath(string $path): string .[method] --------------------------------------------- - `..` ve `.` adreslerini ve yol içindeki dizin ayırıcılarını normalleştirir. +Yoldaki `..` ve `.` ve dizin ayırıcılarını sistem ayırıcılarına normalleştirir. ```php FileSystem::normalizePath('/file/.'); // '/file/' @@ -159,7 +160,7 @@ FileSystem::normalizePath('file/../../bar'); // '/../bar' unixSlashes(string $path): string .[method] ------------------------------------------- -Eğik çizgileri Unix sistemlerinde kullanılan `/` adresine dönüştürür. +Eğik çizgileri Unix sistemlerinde kullanılan `/` karakterine dönüştürür. ```php $path = FileSystem::unixSlashes($path); @@ -169,8 +170,45 @@ $path = FileSystem::unixSlashes($path); platformSlashes(string $path): string .[method] ----------------------------------------------- -Eğik çizgileri geçerli platforma özgü karakterlere dönüştürür, yani Windows'ta `\` ve başka yerlerde `/`. +Eğik çizgileri geçerli platforma özgü karakterlere dönüştürür, yani Windows'ta `\` ve diğerlerinde `/`. ```php $path = FileSystem::platformSlashes($path); ``` + + +resolvePath(string $basePath, string $path): string .[method]{data-version:4.0.6} +--------------------------------------------------------------------------------- + +`$path` yolundan temel dizin `$basePath`'e göre son yolu türetir. Mutlak yolları (`/foo`, `C:/foo`) değiştirmeden bırakır (yalnızca eğik çizgileri normalleştirir), göreli yolları temel yola ekler. + +```php +// Windows'ta çıktıdaki eğik çizgiler ters olurdu (\) +FileSystem::resolvePath('/base/dir', '/abs/path'); // '/abs/path' +FileSystem::resolvePath('/base/dir', 'rel'); // '/base/dir/rel' +FileSystem::resolvePath('base/dir', '../file.txt'); // 'base/file.txt' +FileSystem::resolvePath('base', ''); // 'base' +``` + + +Statik vs Statik Olmayan Erişim +=============================== + +Örneğin test amacıyla sınıfı kolayca başka bir sınıfla (mock ile) değiştirebilmek için statik olmayan şekilde kullanın: + +```php +class AnyClassUsingFileSystem +{ + public function __construct( + private FileSystem $fileSystem, + ) { + } + + public function readConfig(): string + { + return $this->fileSystem->read(/* ... */); + } + + ... +} +``` diff --git a/utils/tr/finder.texy b/utils/tr/finder.texy index cd5045c6fb..68cdee831b 100644 --- a/utils/tr/finder.texy +++ b/utils/tr/finder.texy @@ -2,7 +2,7 @@ Finder: Dosya Arama ******************* .[perex] -Belirli bir maskeyle eşleşen dosyaları mı bulmanız gerekiyor? Finder size yardımcı olabilir. Dizin yapısına göz atmak için çok yönlü ve hızlı bir araçtır. +Belirli bir maskeyle eşleşen dosyaları bulmanız mı gerekiyor? Finder size bu konuda yardımcı olacaktır. Dizin yapısında gezinmek için çok yönlü ve hızlı bir araçtır. Kurulum: @@ -11,17 +11,17 @@ Kurulum: composer require nette/utils ``` -Örneklerde bir takma ad oluşturulduğu varsayılmaktadır: +Örnekler, oluşturulmuş bir takma ad varsayar: ```php use Nette\Utils\Finder; ``` -Kullanma .[#toc-using] ----------------------- +Kullanım +-------- -İlk olarak, geçerli dizindeki `.txt` ve `.md` uzantılı dosya adlarını listelemek için [api:Nette\Utils\Finder] adresini nasıl kullanabileceğinizi görelim: +Öncelikle, [api:Nette\Utils\Finder] kullanarak geçerli dizindeki `.txt` ve `.md` uzantılı dosya adlarını nasıl listeleyebileceğinizi gösterelim: ```php foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { @@ -29,96 +29,97 @@ foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { } ``` -Arama için varsayılan dizin geçerli dizindir, ancak [in() veya from() |#Where to search?] yöntemlerini kullanarak bunu değiştirebilirsiniz. -`$file` değişkeni, birçok yararlı yöntemi olan [FileInfo |#FileInfo] sınıfının bir örneğidir. `$name` anahtarı, dosyanın yolunu bir dize olarak içerir. +Arama için varsayılan dizin geçerli dizindir, ancak bunu [in() veya from() |#Nerede Aranacak] metotlarıyla değiştirebilirsiniz. `$file` değişkeni, birçok yararlı metoda sahip [#FileInfo] sınıfının bir örneğidir. `$name` anahtarında, dosyanın yolu bir karakter dizisi olarak bulunur. -Ne Aranmalı? .[#toc-what-to-search-for] ---------------------------------------- +Ne Aranacak? +------------ -`findFiles()` yöntemine ek olarak, yalnızca dizinleri arayan `findDirectories()` ve her ikisini de arayan `find()` yöntemleri de vardır. Bu yöntemler statiktir, yani bir örnek oluşturmadan çağrılabilirler. Maske parametresi isteğe bağlıdır, belirtmezseniz her şey aranır. +`findFiles()` metodunun yanı sıra, yalnızca dizinleri arayan `findDirectories()` ve her ikisini de arayan `find()` metotları da vardır. Bu metotlar statiktir, bu nedenle bir örnek oluşturmadan çağrılabilirler. Maske parametresi isteğe bağlıdır, belirtmezseniz her şey aranır. ```php foreach (Finder::find() as $file) { - echo $file; // şimdi tüm dosyalar ve dizinler listeleniyor + echo $file; // şimdi tüm dosyalar ve dizinler yazdırılacak } ``` -Başka nelerin aranacağını eklemek için `files()` ve `directories()` yöntemlerini kullanın. Yöntemler tekrar tekrar çağrılabilir ve parametre olarak bir dizi maske sağlanabilir: +`files()` ve `directories()` metotlarını kullanarak neyin aranacağını daha fazla belirleyebilirsiniz. Metotlar tekrar tekrar çağrılabilir ve parametre olarak bir maske dizisi de verilebilir: ```php Finder::findDirectories('vendor') // tüm dizinler ->files(['*.php', '*.phpt']); // artı tüm PHP dosyaları ``` -Statik yöntemlere bir alternatif de `new Finder` adresini kullanarak bir örnek oluşturmak (bu şekilde oluşturulan yeni nesne hiçbir şey aramaz) ve `files()` ve `directories()` adreslerini kullanarak ne aranacağını belirtmektir: +Statik metotlara alternatif olarak, `new Finder` kullanarak bir örnek oluşturmak (bu şekilde oluşturulan yeni nesne hiçbir şey aramaz) ve `files()` ve `directories()` kullanarak neyin aranacağını belirtmektir: ```php (new Finder) - ->directories() // tüm dizinler - ->files('*.php'); // artı tüm PHP dosyaları + ->directories() // tüm dizinler + ->files('*.php'); // artı tüm PHP dosyaları ``` -[Joker karakter |#wildcards] leri `*`, `**` adresinde kullanabilirsiniz, `?` and `[...]` maske içinde. Dizinleri bile belirtebilirsiniz, örneğin `src/*.php`, `src` dizinindeki tüm PHP dosyalarını arayacaktır. +Maskede [#joker karakterler] `*`, `**`, `?` ve `[...]` kullanabilirsiniz. Hatta dizinleri de belirtebilirsiniz, örneğin `src/*.php`, `src` dizinindeki tüm PHP dosyalarını arar. +Sembolik bağlantılar da dizin veya dosya olarak kabul edilir. -Nerede Arama Yapmalı? .[#toc-where-to-search] ---------------------------------------------- -Varsayılan arama dizini geçerli dizindir. Bunu `in()` ve `from()` yöntemlerini kullanarak değiştirebilirsiniz. Yöntem adlarından da görebileceğiniz gibi, `in()` yalnızca geçerli dizini ararken, `from()` alt dizinleri de arar (özyinelemeli olarak). Geçerli dizinde özyinelemeli arama yapmak istiyorsanız, `from('.')` adresini kullanabilirsiniz. +Nerede Aranacak? +---------------- -Bu yöntemler birden çok kez çağrılabilir veya bu yöntemlere birden çok yol diziler olarak aktarılabilir, bu durumda dosyalar tüm dizinlerde aranacaktır. Dizinlerden biri mevcut değilse, bir `Nette\UnexpectedValueException` atılır. +Arama için varsayılan dizin geçerli dizindir. Bunu `in()` ve `from()` metotlarıyla değiştirirsiniz. Metot adlarından da anlaşılacağı gibi, `in()` yalnızca belirtilen dizinde arama yapar, `from()` ise alt dizinlerinde de (özyinelemeli olarak) arama yapar. Geçerli dizinde özyinelemeli olarak arama yapmak istiyorsanız, `from('.')` kullanabilirsiniz. + +Bu metotlar birden çok kez çağrılabilir veya bir dizi olarak birden çok yol iletilebilir, dosyalar daha sonra tüm dizinlerde aranır. Dizinlerden biri mevcut değilse, `Nette\UnexpectedValueException` istisnası fırlatılır. ```php Finder::findFiles('*.php') - ->in(['src', 'tests']) // doğrudan src/ ve tests/ içinde arama yapar - ->from('vendor'); // vendor/ alt dizinlerinde de arama yapar + ->in(['src', 'tests']) // doğrudan src/ ve tests/ içinde arar + ->from('vendor'); // vendor/ alt dizinlerinde de arar ``` -Göreceli yollar geçerli dizine görelidir. Elbette mutlak yollar da belirtilebilir: +Göreli yollar geçerli dizine göredir. Elbette mutlak yollar da belirtilebilir: ```php Finder::findFiles('*.php') ->in('/var/www/html'); ``` -[Joker |#wildcards] karakterler `*`, `**`, `?` can be used in the path. For example, you can use the path `src/*/*.php` `src` dizinindeki ikinci seviye dizinlerdeki tüm PHP dosyalarını aramak için. Globstar olarak adlandırılan `**` karakteri güçlü bir kozdur, çünkü alt dizinleri de aramanıza izin verir: `src` veya alt dizinlerinden herhangi birinde bulunan `tests` dizinindeki tüm PHP dosyalarını aramak için `src/**/tests/*.php` kullanın. +Yolda [#joker karakterler] `*`, `**`, `?` kullanmak mümkündür. Örneğin, `src/*/*.php` yoluyla `src` dizinindeki ikinci seviye dizinlerdeki tüm PHP dosyalarını arayabilirsiniz. Globstar olarak adlandırılan `**` karakteri güçlü bir kozdur, çünkü alt dizinlerde de arama yapmanızı sağlar: `src/**/tests/*.php` ile `src` veya herhangi bir alt dizininde bulunan `tests` dizinindeki tüm PHP dosyalarını ararsınız. -Öte yandan, joker karakterler `[...]` karakterleri yolda desteklenmez, yani örneğin `in(__DIR__)` için arama yapmanız ve şans eseri `[]` karakterlerinin yolda görünmesi durumunda istenmeyen davranışı önlemek için özel bir anlamı yoktur. +Tersine, yoldaki `[...]` joker karakterleri desteklenmez, yani özel bir anlamı yoktur, böylece örneğin `in(__DIR__)` aradığınızda ve yolda tesadüfen `[]` karakterleri bulunursa istenmeyen davranışlar oluşmaz. -Dosya ve dizinleri derinlemesine ararken, önce üst dizin ve ardından içinde bulunan dosyalar döndürülür, bu `childFirst()` ile tersine çevrilebilir. +Dosyaları ve dizinleri derinlemesine ararken, önce üst dizin döndürülür ve ancak daha sonra içindeki dosyalar döndürülür, bu `childFirst()` ile tersine çevrilebilir. -Wildcards .[#toc-wildcards] ---------------------------- +Joker Karakterler +----------------- Maskede birkaç özel karakter kullanabilirsiniz: -- `*` - replaces any number of arbitrary characters (except `/`) -- `**` - `/` dahil olmak üzere herhangi bir sayıda rastgele karakterin yerini alır (yani çok seviyeli olarak aranabilir) -- `?` - replaces one arbitrary character (except `/`) +- `*` - herhangi bir sayıda herhangi bir karakteri değiştirir ( `/` hariç) +- `**` - `/` dahil herhangi bir sayıda herhangi bir karakteri değiştirir (yani çok seviyeli arama yapılabilir) +- `?` - herhangi bir tek karakteri değiştirir ( `/` hariç) - `[a-z]` - köşeli parantez içindeki karakter listesinden bir karakteri değiştirir -- `[!a-z]` - köşeli parantez içindeki karakter listesinin dışındaki bir karakteri değiştirir +- `[!a-z]` - köşeli parantez içindeki karakter listesi dışındaki bir karakteri değiştirir Kullanım örnekleri: -- `img/?.png` - `0.png`, `1.png`, `x.png`, vb. tek harfli adlara sahip dosyalar. -- `logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log` - biçimindeki günlük dosyaları `YYYY-MM-DD` -- `src/**/tests/*` - `src/tests`, `src/foo/tests`, `src/foo/bar/tests` ve benzeri dizinlerdeki dosyalar. -- `docs/**.md` - dizinin tüm alt dizinlerinde bulunan `.md` uzantılı tüm dosyalar `docs` +- `img/?.png` - tek harfli ada sahip dosyalar `0.png`, `1.png`, `x.png`, vb. +- `logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log` - `YYYY-AA-GG` formatındaki günlükler +- `src/**/tests/*` - `src/tests`, `src/foo/tests`, `src/foo/bar/tests` vb. dizinlerdeki dosyalar. +- `docs/**.md` - `docs` dizininin tüm alt dizinlerindeki `.md` uzantılı tüm dosyalar -Hariç .[#toc-excluding] ------------------------ +Hariç Tutma +----------- -Dosyaları ve dizinleri aramaların dışında tutmak için `exclude()` yöntemini kullanın. Dosyanın eşleşmemesi gereken bir maske belirtirsiniz. Adında `X` harfi bulunanlar dışında `*.txt` dosyalarının aranmasına örnek: +`exclude()` metodunu kullanarak dosyaları ve dizinleri aramadan hariç tutabilirsiniz. Dosyanın uymaması gereken bir maske belirtirsiniz. Örneğin, adında `X` harfi içerenler hariç `*.txt` dosyalarını arama: ```php Finder::findFiles('*.txt') ->exclude('*X*'); ``` -Taranan alt dizinleri atlamak için `exclude()` adresini kullanın: +Gezilen alt dizinleri atlamak için `exclude()` kullanın: ```php Finder::findFiles('*.php') @@ -127,12 +128,12 @@ Finder::findFiles('*.php') ``` -Filtreleme .[#toc-filtering] ----------------------------- +Filtreleme +---------- -Finder, sonuçları filtrelemek (yani azaltmak) için çeşitli yöntemler sunar. Bunları birleştirebilir ve tekrar tekrar çağırabilirsiniz. +Finder, sonuçları filtrelemek (yani azaltmak) için birkaç metot sunar. Bunları birleştirebilir ve tekrar tekrar çağırabilirsiniz. -Dosya boyutuna göre filtrelemek için `size()` adresini kullanın. Bu şekilde, boyutları 100 ile 200 bayt arasında olan dosyaları buluruz: +`size()` ile dosya boyutuna göre filtreleriz. Bu şekilde 100 ila 200 bayt arasında boyuta sahip dosyaları buluruz: ```php Finder::findFiles('*.php') @@ -140,7 +141,7 @@ Finder::findFiles('*.php') ->size('<=', 200); ``` -`date()` yöntemi, dosyanın en son değiştirildiği tarihe göre filtreler. Değerler, geçerli tarih ve saate göre mutlak veya göreli olabilir; örneğin, son iki hafta içinde değiştirilen dosyalar bu şekilde bulunabilir: +`date()` metodu, dosyanın son değiştirilme tarihine göre filtreler. Değerler mutlak veya geçerli tarih ve saate göre göreli olabilir, örneğin bu şekilde son iki hafta içinde değiştirilen dosyaları buluruz: ```php Finder::findFiles('*.php') @@ -148,11 +149,11 @@ Finder::findFiles('*.php') ->from($dir) ``` -Her iki işlev de `>`, `>=`, `<`, `<=`, `=`, `!=`, `<>` operatörlerini anlar. +Her iki fonksiyon da `>`, `>=`, `<`, `<=`, `=`, `!=`, `<>` operatörlerini anlar. -Finder ayrıca özel işlevler kullanarak sonuçları filtrelemenize de olanak tanır. İşlev, parametre olarak bir `Nette\Utils\FileInfo` nesnesi alır ve dosyayı sonuçlara dahil etmek için `true` döndürmelidir. +Finder ayrıca sonuçları özel fonksiyonlar kullanarak filtrelemeye de olanak tanır. Fonksiyon, parametre olarak `Nette\Utils\FileInfo` nesnesini alır ve dosyanın sonuçlara dahil edilmesi için `true` döndürmelidir. -Örnek: `Nette` dizesini içeren PHP dosyalarını arayın (büyük/küçük harfe duyarlı değil): +Örnek: `Nette` karakter dizisini içeren (büyük/küçük harf duyarsız) PHP dosyalarını arama: ```php Finder::findFiles('*.php') @@ -160,51 +161,51 @@ Finder::findFiles('*.php') ``` -Derinlik Filtreleme .[#toc-depth-filtering] -------------------------------------------- +Derinlemesine Filtreleme +------------------------ -Özyinelemeli arama yaparken, `limitDepth()` yöntemini kullanarak maksimum tarama derinliğini ayarlayabilirsiniz. `limitDepth(1)` ayarlarsanız, yalnızca ilk alt dizinler taranır, `limitDepth(0)` derinlik taramasını devre dışı bırakır ve -1 değeri sınırı iptal eder. +Özyinelemeli arama yaparken, `limitDepth()` metodunu kullanarak maksimum gezinme derinliğini ayarlayabilirsiniz. `limitDepth(1)` ayarlarsanız, yalnızca ilk alt dizinler gezilir, `limitDepth(0)` derinlemesine gezinmeyi kapatır ve -1 değeri sınırı kaldırır. -Finder, tarama yaparken hangi dizine girileceğine karar vermek için kendi işlevlerini kullanmanıza izin verir. İşlev parametre olarak bir `Nette\Utils\FileInfo` nesnesi alır ve dizine girmek için `true` döndürmelidir: +Finder, özel fonksiyonlar kullanarak gezinme sırasında hangi dizine girileceğine karar vermenizi sağlar. Fonksiyon, parametre olarak `Nette\Utils\FileInfo` nesnesini alır ve dizine girilmesi için `true` döndürmelidir: ```php Finder::findFiles('*.php') - ->descentFilter($file->getBasename() !== 'temp'); + ->descentFilter(fn($file) => $file->getBasename() !== 'temp'); ``` -Sıralama .[#toc-sorting] ------------------------- +Sıralama +-------- -Finder ayrıca sonuçları sıralamak için çeşitli işlevler sunar. +Finder ayrıca sonuçları sıralamak için birkaç fonksiyon sunar. -`sortByName()` yöntemi sonuçları dosya adına göre sıralar. Sıralama doğaldır, yani adlardaki sayıları doğru bir şekilde işler ve örneğin `foo1.txt` 'u `foo10.txt`'dan önce döndürür. +`sortByName()` metodu, sonuçları dosya adlarına göre sıralar. Sıralama doğaldır, yani adlardaki sayıları doğru bir şekilde işler ve örneğin `foo1.txt`'yi `foo10.txt`'den önce döndürür. -Finder ayrıca özel bir fonksiyon kullanarak sıralama yapmanıza da olanak tanır. Parametre olarak iki `Nette\Utils\FileInfo` nesnesi alır ve operatör ile karşılaştırmanın sonucunu döndürmelidir `<=>`yani `-1`, `0` nebo `1`. Örneğin, dosyaları boyutlarına göre bu şekilde sıralayabiliriz: +Finder ayrıca özel bir fonksiyon kullanarak sıralamaya da olanak tanır. Bu fonksiyon, parametre olarak iki `Nette\Utils\FileInfo` nesnesi alır ve `<=>` operatörüyle karşılaştırma sonucunu, yani `-1`, `0` veya `1` döndürmelidir. Örneğin, bu şekilde dosyaları boyutlarına göre sıralarız: ```php $finder->sortBy(fn($a, $b) => $a->getSize() <=> $b->getSize()); ``` -Çoklu Farklı Aramalar .[#toc-multiple-different-searches] ---------------------------------------------------------- +Birden Fazla Farklı Arama +------------------------- -Farklı konumlarda veya farklı kriterleri karşılayan birden fazla farklı dosya bulmanız gerekiyorsa, `append()` yöntemini kullanın. Yöntem çağrılarını zincirleyebilmeniz için yeni bir `Finder` nesnesi döndürür: +Farklı konumlarda veya farklı kriterleri karşılayan birden fazla farklı dosya bulmanız gerekiyorsa, `append()` metodunu kullanın. Yeni bir `Finder` nesnesi döndürür, bu nedenle metot çağrılarını zincirlemek mümkündür: ```php -($finder = new Finder) // ilk Finder'ı $finder değişkeninde saklayın! - ->files('*.php') // src/ içinde *.php dosyalarını ara +($finder = new Finder) // ilk Finder'ı $finder değişkenine kaydediyoruz! + ->files('*.php') // src/ içinde *.php dosyalarını arıyoruz ->from('src') ->append() - ->files('*.md') // docs/ içinde *.md dosyalarını arayın + ->files('*.md') // docs/ içinde *.md dosyalarını arıyoruz ->from('docs') ->append() - ->files('*.json'); // geçerli klasörde *.json dosyalarını arayın + ->files('*.json'); // geçerli klasörde *.json dosyalarını arıyoruz ``` -Alternatif olarak, belirli bir dosyayı (veya bir dizi dosyayı) eklemek için `append()` yöntemini kullanabilirsiniz. O zaman aynı nesneyi döndürür `Finder`: +Alternatif olarak, belirli bir dosyayı (veya bir dosya dizisini) eklemek için `append()` metodunu kullanabilirsiniz. O zaman aynı `Finder` nesnesini döndürür: ```php $finder = Finder::findFiles('*.txt') @@ -212,12 +213,12 @@ $finder = Finder::findFiles('*.txt') ``` -FileInfo .[#toc-fileinfo] -------------------------- +FileInfo +-------- -[Nette\Utils\FileInfo |api:], arama sonuçlarındaki bir dosya veya dizini temsil eden bir sınıftır. Dosya boyutu, son değiştirilme tarihi, adı, yolu gibi bilgileri sağlayan [SplFileInfo |php:SplFileInfo] sınıfının bir uzantısıdır. +[Nette\Utils\FileInfo |api:], arama sonuçlarındaki bir dosyayı veya dizini temsil eden bir sınıftır. Dosya boyutu, son değiştirilme tarihi, adı, yolu vb. gibi bilgiler sağlayan [SplFileInfo |php:SplFileInfo] sınıfının bir uzantısıdır. -Ayrıca, derinlemesine tarama yaparken yararlı olan göreli yolları döndürmek için yöntemler sağlar: +Ayrıca, derinlemesine gezinirken yararlı olan göreli yolu döndürmek için metotlar sağlar: ```php foreach (Finder::findFiles('*.jpg')->from('.') as $file) { @@ -226,7 +227,7 @@ foreach (Finder::findFiles('*.jpg')->from('.') as $file) { } ``` -Bir dosyanın içeriğini okumak ve yazmak için de yöntemleriniz vardır: +Ayrıca dosya içeriğini okumak ve yazmak için metotlarınız vardır: ```php foreach ($finder as $file) { @@ -237,12 +238,12 @@ foreach ($finder as $file) { ``` -Sonuçları Dizi Olarak Döndürme .[#toc-returning-results-as-an-array] --------------------------------------------------------------------- +Sonuçları Dizi Olarak Döndürme +------------------------------ -Örneklerde görüldüğü gibi, Finder `IteratorAggregate` arayüzünü uygular, böylece sonuçlara göz atmak için `foreach` adresini kullanabilirsiniz. Sonuçlar yalnızca siz göz attıkça yüklenecek şekilde programlanmıştır, bu nedenle çok sayıda dosyanız varsa, hepsinin okunmasını beklemez. +Örneklerde görüldüğü gibi, Finder `IteratorAggregate` arayüzünü uygular, bu nedenle sonuçları gezinmek için `foreach` kullanabilirsiniz. Sonuçların yalnızca gezinme sırasında yükleneceği şekilde programlanmıştır, bu nedenle çok sayıda dosyanız varsa, hepsi okunana kadar beklemezsiniz. -Ayrıca `collect()` yöntemini kullanarak sonuçların `Nette\Utils\FileInfo` nesnelerinden oluşan bir dizi olarak döndürülmesini sağlayabilirsiniz. Dizi ilişkisel değil, sayısaldır. +Sonuçları ayrıca `Nette\Utils\FileInfo` nesneleri dizisi olarak `collect()` metoduyla döndürebilirsiniz. Dizi ilişkisel değil, sayısaldır. ```php $array = $finder->findFiles('*.php')->collect(); diff --git a/utils/tr/floats.texy b/utils/tr/floats.texy index 44e716a09c..f047e0793e 100644 --- a/utils/tr/floats.texy +++ b/utils/tr/floats.texy @@ -1,8 +1,8 @@ -Float Fonksiyonları +Float'larla Çalışma ******************* .[perex] -[api:Nette\Utils\Floats] float sayıları karşılaştırmak için kullanışlı fonksiyonlara sahip statik bir sınıftır. +[api:Nette\Utils\Floats], ondalık sayıları karşılaştırmak için yararlı fonksiyonlar içeren statik bir sınıftır. Kurulum: @@ -11,46 +11,48 @@ Kurulum: composer require nette/utils ``` -Tüm örnekler aşağıdaki sınıf takma adının tanımlandığını varsayar: +Tüm örnekler, oluşturulmuş bir takma ad varsayar: ```php use Nette\Utils\Floats; ``` -Motivasyon .[#toc-motivation] -============================= +Motivasyon +========== -Float karşılaştırma sınıfının ne işe yaradığını merak ediyor musunuz? Operatörleri kullanabilirsiniz `<`, `>`, `===`, diye düşünüyorsunuz. -Bu tamamen doğru değil. Sizce bu kodu ne yazdıracak? +Float'ları karşılaştırmak için neden bir sınıfa ihtiyacım var diye düşünüyor olabilirsiniz? Sonuçta `<`, `>`, `===` operatörlerini kullanabilirim ve işim biter. Bu tam olarak doğru değil. Sizce bu kod ne yazdırır? ```php $a = 0.1 + 0.2; $b = 0.3; -echo $a === $b ? 'same' : 'not same'; +echo $a === $b ? 'aynı' : 'aynı değil'; // same -> aynı, not same -> aynı değil ``` -Kodu çalıştırırsanız, bazılarınız programın `not same` adresini yazdırmasına şaşıracaktır. +Kodu çalıştırırsanız, bazılarınız programın `aynı değil` yazdırmasına kesinlikle şaşıracaktır. -Float sayılarla yapılan matematiksel işlemler, ondalık ve ikili sistemler arasındaki dönüşüm nedeniyle hatalara neden olur. Örneğin `0.1 + 0.2` eşittir `0.300000000000000044…`. Bu nedenle, kayan sayıları karşılaştırırken, belirli bir ondalık basamaktan küçük bir farkı tolere etmeliyiz. +Ondalık sayılarla yapılan matematiksel işlemlerde, onluk ve ikilik sistemler arasındaki dönüşüm nedeniyle hatalar oluşur. Örneğin, `0.1 + 0.2` sonucu `0.300000000000000044…` olur. Bu nedenle, karşılaştırma yaparken belirli bir ondalık basamaktan sonra küçük bir farkı tolere etmemiz gerekir. -Ve `Floats` sınıfının yaptığı da budur. Aşağıdaki karşılaştırma beklendiği gibi çalışacaktır: +Ve işte `Floats` sınıfı tam olarak bunu yapar. Aşağıdaki karşılaştırma artık beklendiği gibi çalışacaktır: ```php -echo Floats::areEqual($a, $b) ? 'same' : 'not same'; // same +echo Floats::areEqual($a, $b) ? 'aynı' : 'aynı değil'; // aynı ``` -`NAN` adresini karşılaştırmaya çalışırken `\LogicException` istisnası atar. +`NAN` karşılaştırmaya çalışıldığında `\LogicException` istisnası fırlatır. +.[tip] +`Floats` sınıfı `1e-10`'dan küçük farkları tolere eder. Daha yüksek hassasiyetle çalışmanız gerekiyorsa, bunun yerine BCMath kütüphanesini kullanın. -Şamandıra Karşılaştırması .[#toc-float-comparison] -================================================== + +Float Karşılaştırması +===================== areEqual(float $a, float $b): bool .[method] -------------------------------------------- -`$a` = `$b` ise `true` döndürür. +Eğer `$a` = `$b` ise `true` döndürür. ```php Floats::areEqual(10, 10.0); // true @@ -60,7 +62,7 @@ Floats::areEqual(10, 10.0); // true isLessThan(float $a, float $b): bool .[method] ---------------------------------------------- -`$a` < `$b` ise `true` döndürür. +Eğer `$a` < `$b` ise `true` döndürür. ```php Floats::isLessThan(9.5, 10.2); // true @@ -71,7 +73,7 @@ Floats::isLessThan(INF, 10.2); // false isLessThanOrEqualTo(float $a, float $b): bool .[method] ------------------------------------------------------- -`$a` <= `$b` ise `true` döndürür. +Eğer `$a` <= `$b` ise `true` döndürür. ```php Floats::isLessThanOrEqualTo(9.5, 10.2); // true @@ -82,7 +84,7 @@ Floats::isLessThanOrEqualTo(10.25, 10.25); // true isGreaterThan(float $a, float $b): bool .[method] ------------------------------------------------- -`$a` > `$b` ise `true` döndürür. +Eğer `$a` > `$b` ise `true` döndürür. ```php Floats::isGreaterThan(9.5, -10.2); // true @@ -93,7 +95,7 @@ Floats::isGreaterThan(9.5, 10.2); // false isGreaterThanOrEqualTo(float $a, float $b): bool .[method] ---------------------------------------------------------- -`$a` >= `$b` ise `true` döndürür. +Eğer `$a` >= `$b` ise `true` döndürür. ```php Floats::isGreaterThanOrEqualTo(9.5, 10.2); // false @@ -104,25 +106,25 @@ Floats::isGreaterThanOrEqualTo(10.2, 10.2); // true compare(float $a, float $b): int .[method] ------------------------------------------ -`$a` < `$b` ise `-1` döndürür, eşitlerse `0` and if `$a` > `$b` `1` döndürür. +Eğer `$a` < `$b` ise `-1`, eğer eşitlerse `0` ve eğer `$a` > `$b` ise `1` döndürür. -Örneğin `usort` işleviyle birlikte kullanılabilir. +Örneğin `usort` fonksiyonu ile kullanılabilir. ```php $arr = [1, 5, 2, -3.5]; -usort($arr, [Float::class, 'compare']); -// $arr is [-3.5, 1, 2, 5] +usort($arr, [Floats::class, 'compare']); +// $arr şimdi [-3.5, 1, 2, 5] ``` -Yardımcı Fonksiyonlar .[#toc-helpers-functions] -=============================================== +Yardımcı Fonksiyonlar +===================== isZero(float $value): bool .[method] ------------------------------------ -Değer sıfırsa `true` döndürür. +Değer sıfıra eşitse `true` döndürür. ```php Floats::isZero(0.0); // true @@ -133,7 +135,7 @@ Floats::isZero(0); // true isInteger(float $value): bool .[method] --------------------------------------- -Değer tamsayı ise `true` döndürür. +Değer bir tamsayı ise `true` döndürür. ```php Floats::isInteger(0); // true diff --git a/utils/tr/helpers.texy b/utils/tr/helpers.texy index e9ce8a4713..2dd4e4e6d2 100644 --- a/utils/tr/helpers.texy +++ b/utils/tr/helpers.texy @@ -1,8 +1,8 @@ -Yardımcı İşlevler -***************** +Yardımcı Fonksiyonlar +********************* .[perex] -[api:Nette\Utils\Helpers] yararlı işlevlere sahip statik bir sınıftır. +[api:Nette\Utils\Helpers], yararlı fonksiyonlar içeren statik bir sınıftır. Kurulum: @@ -11,7 +11,7 @@ Kurulum: composer require nette/utils ``` -Tüm örnekler aşağıdaki sınıf takma adının tanımlandığını varsayar: +Tüm örnekler, oluşturulmuş bir takma ad varsayar: ```php use Nette\Utils\Helpers; @@ -21,7 +21,7 @@ use Nette\Utils\Helpers; capture(callable $cb): string .[method] --------------------------------------- -Bir geri çağırmayı çalıştırır ve yakalanan çıktıyı bir dize olarak döndürür. +Geri aramayı yürütür ve yakalanan çıktıyı bir karakter dizisi olarak döndürür. ```php $res = Helpers::capture(function () use ($template) { @@ -33,7 +33,7 @@ $res = Helpers::capture(function () use ($template) { clamp(int|float $value, int|float $min, int|float $max): int|float .[method] ---------------------------------------------------------------------------- -Min ve maks. dahil aralığına sıkıştırılmış değeri döndürür. +Değeri belirtilen kapsayıcı min ve max aralığına sınırlar. ```php Helpers::clamp($level, 0, 255); @@ -43,8 +43,7 @@ Helpers::clamp($level, 0, 255); compare(mixed $left, string $operator, mixed $right): bool .[method] -------------------------------------------------------------------- -PHP'nin yaptığı gibi iki değeri karşılaştırır. `>`, `>=` , `<`, `<=`, `=`, `==`, `===`, `!=`, `!==`, `<>` operatörleri arasında ayrım yapar. -Fonksiyon, operatörün değişken olduğu durumlarda kullanışlıdır. +İki değeri PHP'nin yaptığı gibi karşılaştırır. `>`, `>=`, `<`, `<=`, `=`, `==`, `===`, `!=`, `!==`, `<>` operatörlerini ayırt eder. Fonksiyon, operatörün değişken olduğu durumlarda kullanışlıdır. ```php Helpers::compare(10, '<', 20); // true @@ -54,7 +53,7 @@ Helpers::compare(10, '<', 20); // true falseToNull(mixed $value): mixed .[method] ------------------------------------------ -`false` adresini `null` adresine dönüştürür, diğer değerleri değiştirmez. +`false` değerini `null`'a dönüştürür, diğer değerleri değiştirmez. ```php Helpers::falseToNull(false); // null @@ -65,7 +64,7 @@ Helpers::falseToNull(123); // 123 getLastError(): string .[method] -------------------------------- -En son oluşan PHP hatasını veya hata oluşmamışsa boş bir dizge döndürür. `error_get_last()` adresinden farklı olarak, `html_errors` PHP yönergesinden etkilenmez ve HTML değil her zaman metin döndürür. +PHP'deki son hatayı veya hiç hata oluşmadıysa boş bir karakter dizisi döndürür. `error_get_last()`'ın aksine, PHP yönergesi `html_errors`'dan etkilenmez ve her zaman HTML değil, metin döndürür. ```php Helpers::getLastError(); @@ -75,13 +74,13 @@ Helpers::getLastError(); getSuggestion(string[] $possibilities, string $value): ?string .[method] ------------------------------------------------------------------------ -`$possibilities` adresinden `$value` adresine en çok benzeyen, ancak aynı olmayan bir dize arar. Yalnızca 8 bit kodlamaları destekler. +Sunulan `$possibilities` seçenekleri arasından `$value`'ya en çok benzeyen, ancak aynı olmayan karakter dizisini arar. Yalnızca 8 bit kodlamayı destekler. -Belirli bir seçenek geçerli değilse ve kullanıcıya benzer bir seçenek önermek istiyorsak (ancak farklıysa, bu nedenle aynı dize göz ardı edilir) kullanışlıdır. Bu şekilde, Nette `did you mean ...?` mesajlarını oluşturur. +Belirli bir seçeneğin geçerli olmadığı ve kullanıcıya benzer bir seçenek önermek istediğimiz durumlarda kullanışlıdır (ancak farklı, bu nedenle aynı karakter dizisi göz ardı edilir). Nette bu şekilde `bunu mu demek istediniz...?` mesajlarını oluşturur. ```php $items = ['foo', 'bar', 'baz']; Helpers::getSuggestion($items, 'fo'); // 'foo' Helpers::getSuggestion($items, 'barr'); // 'bar' -Helpers::getSuggestion($items, 'baz'); // 'bar', ne 'baz' +Helpers::getSuggestion($items, 'baz'); // 'bar', 'baz' değil ``` diff --git a/utils/tr/html-elements.texy b/utils/tr/html-elements.texy index 1369029391..65abd91811 100644 --- a/utils/tr/html-elements.texy +++ b/utils/tr/html-elements.texy @@ -1,16 +1,16 @@ -HTML Öğeleri -************ +HTML Elemanları +*************** .[perex] -[api:Nette\Utils\Html] sınıfı, Siteler Arası Komut Dosyası (XSS) güvenlik açığını önleyen HTML kodu oluşturmaya yönelik bir yardımcıdır. +[api:Nette\Utils\Html] sınıfı, Siteler Arası Betik Çalıştırma (XSS) güvenlik açığının oluşmasını engelleyen HTML kodu oluşturmak için bir yardımcıdır. -Nesneleri HTML öğelerini temsil edecek şekilde çalışır, parametrelerini ayarlarız ve render edilmelerine izin veririz: +Şöyle çalışır: nesneleri HTML elemanlarını temsil eder, bunlara parametreler ayarlarız ve oluşturulmalarını sağlarız: ```php -$el = Html::el('img'); // öğesini oluşturur -$el->src = 'image.jpg'; // src özniteliğini ayarlar -echo $el; // '' yazdırır +$el = Html::el('img'); // elemanı oluşturur +$el->src = 'image.jpg'; // src niteliğini ayarlar +echo $el; // '' yazdırır ``` Kurulum: @@ -19,29 +19,29 @@ Kurulum: composer require nette/utils ``` -Tüm örnekler aşağıdaki sınıf takma adının tanımlandığını varsayar: +Tüm örnekler, oluşturulmuş bir takma ad varsayar: ```php use Nette\Utils\Html; ``` -HTML Öğesi Oluşturma .[#toc-creating-an-html-element] -===================================================== +HTML Elemanı Oluşturma +====================== -Eleman `Html::el()` yöntemi kullanılarak oluşturulur: +Elemanı `Html::el()` metoduyla oluştururuz: ```php -$el = Html::el('img'); // öğesini oluşturur +$el = Html::el('img'); // elemanı oluşturur ``` -Adın yanı sıra, HTML sözdiziminde başka nitelikler de girebilirsiniz: +Adın yanı sıra, HTML sözdiziminde başka nitelikler de belirtebilirsiniz: ```php $el = Html::el('input type=text class="red important"'); ``` -Ya da bunları ikinci parametreye ilişkisel bir dizi olarak aktarın: +Veya bunları ikinci parametre olarak ilişkisel bir dizi olarak iletebilirsiniz: ```php $el = Html::el('input', [ @@ -50,53 +50,53 @@ $el = Html::el('input', [ ]); ``` -Bir öğe adını değiştirmek ve döndürmek için: +Eleman adını değiştirme ve döndürme: ```php $el->setName('img'); $el->getName(); // 'img' -$el->isEmpty(); // true, boş bir eleman olduğu için +$el->isEmpty(); // true, çünkü boş bir elemandır ``` -HTML Nitelikleri .[#toc-html-attributes] -======================================== +HTML Nitelikleri +================ -Bireysel HTML niteliklerini üç şekilde ayarlayabilir ve alabilirsiniz, hangisini daha çok seveceğiniz size kalmış. Birincisi özellikler aracılığıyla: +Bireysel HTML niteliklerini üç şekilde değiştirebilir ve okuyabiliriz, hangisini daha çok beğeneceğiniz size bağlıdır. Bunlardan ilki özellikler aracılığıyladır: ```php -$el->src = 'image.jpg'; // src özniteliğini ayarlar +$el->src = 'image.jpg'; // src niteliğini ayarlar echo $el->src; // 'image.jpg' -unset($el->src); // özniteliği kaldırır +unset($el->src); // niteliği kaldırır // veya $el->src = null; ``` -İkinci yol, özellikleri ayarlamanın aksine, birbirlerine zincirleme bağlayabileceğimiz yöntemleri çağırmaktır: +İkinci yol, özellik ayarlamaya göre zincirleyebileceğimiz metotları çağırmaktır: ```php -$el = Html::el('img')->src('image.jpg')->alt('photo'); -// photo +$el = Html::el('img')->src('image.jpg')->alt('fotoğraf'); +// fotoğraf -$el->alt(null); // özniteliği kaldırır +$el->alt(null); // niteliği kaldırma ``` -Üçüncü yol ise en konuşkan olanıdır: +Ve üçüncü yol en konuşkan olanıdır: ```php $el = Html::el('img') ->setAttribute('src', 'image.jpg') - ->setAttribute('alt', 'photo'); + ->setAttribute('alt', 'fotoğraf'); echo $el->getAttribute('src'); // 'image.jpg' $el->removeAttribute('alt'); ``` -Toplu olarak, öznitelikler `addAttributes(array $attrs)` ile ayarlanabilir ve `removeAttributes(array $attrNames)` ile silinebilir. +Nitelikler toplu olarak `addAttributes(array $attrs)` ile ayarlanabilir ve `removeAttributes(array $attrNames)` ile kaldırılabilir. -Bir özniteliğin değeri yalnızca bir dize olmak zorunda değildir, mantıksal öznitelikler için mantıksal değerler de kullanılabilir: +Niteliğin değeri yalnızca bir karakter dizisi olmak zorunda değildir, mantıksal nitelikler için mantıksal değerler de kullanılabilir: ```php $checkbox = Html::el('input')->type('checkbox'); @@ -104,7 +104,7 @@ $checkbox->checked = true; // $checkbox->checked = false; // ``` -Bir nitelik, örneğin CSS sınıfları için uygun olan, boşluklarla ayrılmış olarak listelenen bir belirteç dizisi de olabilir: +Niteliğin değeri, boşluklarla ayrılmış olarak yazdırılacak bir değerler dizisi de olabilir, bu örneğin CSS sınıfları için kullanışlıdır: ```php $el = Html::el('input'); @@ -114,7 +114,7 @@ $el->class[] = 'top'; echo $el; // '' ``` -Bir alternatif, değerlerin anahtarın listelenip listelenmeyeceğini belirttiği bir ilişkisel dizidir: +Alternatif, değerlerin anahtarın yazdırılıp yazdırılmayacağını söylediği ilişkisel bir dizidir: ```php $el = Html::el('input'); @@ -132,7 +132,7 @@ $el->style['display'] = 'block'; echo $el; // '' ``` -Şimdi özellikleri kullandık, ancak aynı şey yöntemler kullanılarak da yapılabilir: +Şimdi özellikleri kullandık, ancak aynı şey metotlarla da yazılabilir: ```php $el = Html::el('input'); @@ -141,7 +141,7 @@ $el->style('display', 'block'); echo $el; // '' ``` -Ya da en konuşkan şekilde bile: +Veya en konuşkan şekilde bile: ```php $el = Html::el('input'); @@ -150,7 +150,7 @@ $el->appendAttribute('style', 'display', 'block'); echo $el; // '' ``` -Son bir şey: `href()` yöntemi, bir URL'de sorgu parametreleri oluşturmayı kolaylaştırabilir: +Son olarak küçük bir ayrıntı: `href()` metodu, URL'deki sorgu parametrelerini birleştirmeyi kolaylaştırabilir: ```php echo Html::el('a')->href('index.php', [ @@ -161,19 +161,19 @@ echo Html::el('a')->href('index.php', [ ``` -Veri Öznitelikleri .[#toc-data-attributes] ------------------------------------------- +Veri Nitelikleri +---------------- -Veri nitelikleri özel bir desteğe sahiptir. Adları tire içerdiğinden, özellikler ve yöntemler aracılığıyla erişim çok zarif değildir, bu nedenle `data()` yöntemi vardır: +Veri nitelikleri için özel destek vardır. Adları tire içerdiğinden, özellikler ve metotlar aracılığıyla erişim o kadar zarif değildir, bu nedenle bir `data()` metodu vardır: ```php $el = Html::el('input'); -$el->{'data-max-size'} = '500x300'; // pek şık değil +$el->{'data-max-size'} = '500x300'; // o kadar zarif değil $el->data('max-size', '500x300'); // zarif echo $el; // '' ``` -Veri özniteliğinin değeri bir dizi ise, otomatik olarak JSON'a serileştirilir: +Veri niteliğinin değeri bir dizi ise, otomatik olarak JSON'a serileştirilir: ```php $el = Html::el('input'); @@ -182,20 +182,20 @@ echo $el; // '' ``` -Element İçeriği .[#toc-element-content] -======================================= +Eleman İçeriği +============== -Öğenin iç içeriği `setHtml()` veya `setText()` yöntemleri tarafından ayarlanır. İlkini yalnızca parametreye güvenli bir HTML dizesi aktardığınızı biliyorsanız kullanın. +Elemanın iç içeriğini `setHtml()` veya `setText()` metotlarıyla ayarlarız. Bunlardan ilkini yalnızca parametrede güvenilir bir şekilde güvenli bir HTML karakter dizisi ilettiğinizi biliyorsanız kullanın. ```php -echo Html::el('span')->setHtml('hello
                                                                                                                            '); -// 'hello
                                                                                                                            ' +echo Html::el('span')->setHtml('merhaba
                                                                                                                            '); +// 'merhaba
                                                                                                                            ' echo Html::el('span')->setText('10 < 20'); // '10 < 20' ``` -Tersine, iç içerik `getHtml()` veya `getText()` yöntemleriyle elde edilir. İkincisi, HTML çıktısından etiketleri kaldırır ve HTML varlıklarını karakterlere dönüştürür. +Ve tersine, iç içeriği `getHtml()` veya `getText()` metotlarıyla alırız. İkincisi, çıktıdan HTML etiketlerini kaldırır ve HTML varlıklarını karakterlere dönüştürür. ```php echo $el->getHtml(); // '10 < 20' @@ -203,33 +203,33 @@ echo $el->getText(); // '10 < 20' ``` -Çocuk Düğümleri .[#toc-child-nodes] ------------------------------------ +Alt Düğümler +------------ -Bir elemanın iç içeriği bir çocuk dizisi de olabilir. Bunların her biri bir dize ya da başka bir `Html` öğesi olabilir. Bunlar `addHtml()` veya `addText()` kullanılarak eklenir: +Elemanın içi ayrıca alt (children) düğümler dizisi de olabilir. Her biri ya bir karakter dizisi ya da başka bir `Html` elemanı olabilir. Bunları `addHtml()` veya `addText()` kullanarak ekleriz: ```php $el = Html::el('span') - ->addHtml('hello
                                                                                                                            ') + ->addHtml('merhaba
                                                                                                                            ') ->addText('10 < 20') ->addHtml( Html::el('br') ); -// hello
                                                                                                                            10 < 20
                                                                                                                            +// merhaba
                                                                                                                            10 < 20
                                                                                                                            ``` -Yeni bir `Html` düğümü oluşturmanın ve eklemenin başka bir yolu: +Yeni bir `Html` düğümü oluşturmak ve eklemek için başka bir yol: ```php -$el = Html::el('ul') - ->create('li', ['class' => 'first']) - ->setText('hello'); -//
                                                                                                                            • hello
                                                                                                                            +$ul = Html::el('ul'); +$ul->create('li', ['class' => 'first']) + ->setText('birinci'); +//
                                                                                                                            • birinci
                                                                                                                            ``` -Düğümlerle dizi öğeleriymiş gibi çalışabilirsiniz. Yani köşeli parantez kullanarak tek tek olanlara erişin, `count()` ile sayın ve üzerlerinde yineleyin: +Düğümlerle, sanki bir diziymiş gibi aynı şekilde çalışılabilir. Yani, köşeli parantez kullanarak bireysel olanlara erişebilir, `count()` kullanarak sayabilir ve üzerlerinde yinelenebilir: ```php $el = Html::el('div'); -$el[] = 'hello'; +$el[] = 'merhaba'; $el[] = Html::el('span'); echo $el[1]; // '' @@ -238,58 +238,58 @@ foreach ($el as $child) { /* ... */ } echo count($el); // 2 ``` -`insert(?int $index, $child, bool $replace = false)` kullanılarak belirli bir konuma yeni bir düğüm eklenebilir. `$replace = false` ise, öğeyi `$index` konumuna ekler ve diğerlerini taşır. Eğer `$index = null` ise, sonuna bir eleman ekler. +Yeni bir düğüm, `insert(?int $index, $child, bool $replace = false)` kullanılarak belirli bir yere eklenebilir. Eğer `$replace = false` ise, öğeyi `$index` konumuna ekler ve diğerlerini kaydırır. Eğer `$index = null` ise, öğeyi sona ekler. ```php -// öğeyi ilk konuma ekler ve diğerlerini ilerletir +// öğeyi ilk konuma ekler ve diğerlerini kaydırır $el->insert(0, Html::el('span')); ``` -Tüm düğümler `getChildren()` yöntemi ile döndürülür ve `removeChildren()` yöntemi ile kaldırılır. +Tüm düğümleri `getChildren()` metoduyla alırız ve `removeChildren()` metoduyla kaldırırız. -Belge Parçası Oluşturma .[#toc-creating-a-document-fragment] ------------------------------------------------------------- +Belge Parçası Oluşturma +----------------------- -Bir dizi düğümle çalışmak istiyorsanız ve sarmalayan öğeyle ilgilenmiyorsanız, öğe adı yerine `null` adresini geçerek *document fragment* oluşturabilirsiniz: +Bir düğüm dizisiyle çalışmak istiyorsak ve çevreleyen elemanla ilgilenmiyorsak, eleman adı yerine `null` ileterek sözde bir *belge parçası* oluşturabiliriz: ```php $el = Html::el(null) - ->addHtml('hello
                                                                                                                            ') + ->addHtml('merhaba
                                                                                                                            ') ->addText('10 < 20') ->addHtml( Html::el('br') ); -// hello
                                                                                                                            10 < 20
                                                                                                                            +// merhaba
                                                                                                                            10 < 20
                                                                                                                            ``` -`fromHtml()` ve `fromText()` yöntemleri bir parça oluşturmak için daha hızlı bir yol sunar: +Parça oluşturmanın daha hızlı bir yolu `fromHtml()` ve `fromText()` metotlarını sunar: ```php -$el = Html::fromHtml('hello
                                                                                                                            '); -echo $el; // 'hello
                                                                                                                            ' +$el = Html::fromHtml('merhaba
                                                                                                                            '); +echo $el; // 'merhaba
                                                                                                                            ' $el = Html::fromText('10 < 20'); echo $el; // '10 < 20' ``` -HTML Çıktısı Oluşturma .[#toc-generating-html-output] -===================================================== +HTML Çıktısı Oluşturma +====================== -Bir HTML öğesi oluşturmanın en kolay yolu `echo` adresini kullanmak veya bir nesneyi `(string)` adresine atamaktır. Ayrıca açılış veya kapanış etiketlerini ve niteliklerini ayrı ayrı yazdırabilirsiniz: +Bir HTML elemanını yazdırmanın en basit yolu `echo` kullanmak veya nesneyi `(string)`'e dönüştürmektir. Açılış veya kapanış etiketlerini ve nitelikleri ayrı ayrı yazdırmak da mümkündür: ```php -$el = Html::el('div class=header')->setText('hello'); +$el = Html::el('div class=header')->setText('merhaba'); -echo $el; // '
                                                                                                                            ' -$s = (string) $el; // '
                                                                                                                            hello
                                                                                                                            ' -$s = $el->toHtml(); // '
                                                                                                                            hello
                                                                                                                            ' -$s = $el->toText(); // 'hello' +echo $el; // '
                                                                                                                            merhaba
                                                                                                                            ' +$s = (string) $el; // '
                                                                                                                            merhaba
                                                                                                                            ' +$s = $el->toHtml(); // '
                                                                                                                            merhaba
                                                                                                                            ' +$s = $el->toText(); // 'merhaba' echo $el->startTag(); // '
                                                                                                                            ' echo $el->endTag(); // '
                                                                                                                            ' echo $el->attributes(); // 'class="header"' ``` -Önemli bir özellik de [Çapraz Site Komut Dosyalarına (XSS) |nette:glossary#cross-site-scripting-xss] karşı otomatik korumadır. `setText()` veya `addText()` kullanılarak eklenen tüm öznitelik değerleri veya içerik güvenilir bir şekilde önlenir: +Önemli bir özellik, [Siteler Arası Betik Çalıştırma (XSS) |nette:glossary#Cross-Site Scripting XSS]'ye karşı otomatik korumadır. `setText()` veya `addText()` aracılığıyla eklenen tüm nitelik değerleri veya içerik güvenilir bir şekilde kaçış işlemine tabi tutulur: ```php echo Html::el('div') @@ -300,17 +300,17 @@ echo Html::el('div') ``` -HTML ↔ Metin Dönüştürme .[#toc-conversion-html-text] -==================================================== +HTML ↔ Metin Dönüşümü +===================== -HTML'yi metne dönüştürmek için `htmlToText()` statik yöntemini kullanabilirsiniz: +HTML'yi metne dönüştürmek için statik `htmlToText()` metodunu kullanabilirsiniz: ```php -echo Html::htmlToText('One & Two'); // 'One & Two' +echo Html::htmlToText('Bir & İki'); // 'Bir & İki' ``` -HtmlStringable .[#toc-htmlstringable] -===================================== +HtmlStringable +============== -`Nette\Utils\Html` nesnesi, örneğin Latte veya formların HTML kodu döndüren `__toString()` yöntemine sahip nesneleri ayırt etmek için kullandığı `Nette\HtmlStringable` arabirimini uygular. Dolayısıyla, örneğin şablondaki nesneyi `{$el}` kullanarak yazdırırsak çift kaçış oluşmaz. +`Nette\Utils\Html` nesnesi, örneğin Latte veya formların HTML kodu döndüren `__toString()` metoduna sahip nesneleri ayırt ettiği `Nette\HtmlStringable` arayüzünü uygular. Bu nedenle, örneğin nesneyi şablonda `{$el}` kullanarak yazdırırsak çift kaçış işlemi gerçekleşmez. diff --git a/utils/tr/images.texy b/utils/tr/images.texy index c041d8ec49..7733132577 100644 --- a/utils/tr/images.texy +++ b/utils/tr/images.texy @@ -1,11 +1,11 @@ -Görüntü Fonksiyonları -********************* +Resimlerle Çalışma +****************** .[perex] -[api:Nette\Utils\Image] sınıfı, yeniden boyutlandırma, kırpma, keskinleştirme, çizim veya birden fazla görüntüyü birleştirme gibi görüntü manipülasyonunu basitleştirir. +[api:Nette\Utils\Image] sınıfı, yeniden boyutlandırma, kırpma, keskinleştirme, çizim yapma veya birden fazla resmi birleştirme gibi resim manipülasyonlarını basitleştirir. -PHP, görüntüleri işlemek için kapsamlı bir işlev setine sahiptir. Ancak API pek hoş değil. Seksi bir API ile ortaya çıkmak için Neat Framework olmazdı. +PHP, resim manipülasyonu için kapsamlı bir fonksiyon setine sahiptir. Ancak API'leri pek kullanışlı değildir. Nette Framework olmasaydı, çekici bir API ile gelmezdi. Kurulum: @@ -13,119 +13,130 @@ Kurulum: composer require nette/utils ``` -Aşağıdaki örneklerde, aşağıdaki sınıf takma adının tanımlandığı varsayılmaktadır: +Tüm örnekler, bir takma ad oluşturulduğunu varsayar: ```php use Nette\Utils\Image; +use Nette\Utils\ImageColor; +use Nette\Utils\ImageType; ``` -Görüntü Oluşturma .[#toc-creating-an-image] -=========================================== +Resim Oluşturma +=============== -Örneğin 100×200 boyutlarında yeni bir gerçek renkli görüntü oluşturacağız: +Örneğin 100×200 boyutlarında yeni bir true color resim oluşturalım: ```php $image = Image::fromBlank(100, 200); ``` -İsteğe bağlı olarak bir arka plan rengi belirtebilirsiniz (varsayılan renk siyahtır): +İsteğe bağlı olarak, arka plan rengi belirtilebilir (varsayılan siyahtır): ```php -$image = Image::fromBlank(100, 200, Image::rgb(125, 0, 0)); +$image = Image::fromBlank(100, 200, ImageColor::rgb(125, 0, 0)); ``` -Ya da görüntüyü bir dosyadan yükleriz: +Veya resmi bir dosyadan yükleyelim: ```php $image = Image::fromFile('nette.jpg'); ``` -Desteklenen formatlar JPEG, PNG, GIF, WebP, AVIF ve BMP'dir, ancak PHP sürümünüz de bunları desteklemelidir ( `phpinfo()`, GD bölümünü kontrol edin). Animasyonlar desteklenmez. -Yükleme sırasında görüntü formatını tespit etmeniz mi gerekiyor? Yöntem, ikinci parametrede biçimi döndürür: +Resmi Kaydetme +============== + +Resim bir dosyaya kaydedilebilir: ```php -$image = Image::fromFile('nette.jpg', $type); -// $type Image::JPEG, Image::PNG, Image::GIF, Image::WEBP, Image::AVIF veya Image::BMP ise +$image->save('resampled.jpg'); ``` -Sadece görüntü yüklenmeden algılama `Image::detectTypeFromFile()` tarafından yapılır. +JPEG (varsayılan 85), WEBP (varsayılan 80) ve AVIF (varsayılan 30) için 0..100 aralığında ve PNG (varsayılan 9) için 0..9 aralığında sıkıştırma kalitesini belirleyebiliriz: +```php +$image->save('resampled.jpg', 80); // JPEG, kalite %80 +``` -Görüntüyü Kaydet .[#toc-save-the-image] -======================================= - -Görüntü bir dosyaya kaydedilebilir: +Dosya uzantısından format belli değilse, [sabit |#Formatlar] ile belirtilebilir: ```php -$image->save('resampled.jpg'); +$image->save('resampled.tmp', null, ImageType::JPEG); ``` -Sıkıştırma kalitesini JPEG (varsayılan 85), WEBP (varsayılan 80) ve AVIF (varsayılan 30) için 0..100 ve PNG (varsayılan 9) için 0..9 aralığında belirleyebiliriz: +Resim, diske yerine bir değişkene yazılabilir: ```php -$image->save('resampled.jpg', 80); // JPEG, kalite %80 +$data = $image->toString(ImageType::JPEG, 80); // JPEG, kalite %80 ``` -Format dosya uzantısından anlaşılmıyorsa, `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` ve `Image::BMP` sabitlerinden biriyle belirtilebilir: +veya uygun HTTP başlığı `Content-Type` ile doğrudan tarayıcıya gönderilebilir: ```php -$image->save('resampled.tmp', null, Image::JPEG); +// Content-Type: image/png başlığını gönderir +$image->send(ImageType::PNG); ``` -Görüntü disk yerine bir değişkene yazılabilir: + +Formatlar +========= + +Desteklenen formatlar JPEG, PNG, GIF, WebP, AVIF ve BMP'dir, ancak PHP sürümünüzün de bunları desteklemesi gerekir, bunu [#isTypeSupported()] fonksiyonu ile doğrulayabilirsiniz. Animasyonlar desteklenmez. + +Format, `ImageType::JPEG`, `ImageType::PNG`, `ImageType::GIF`, `ImageType::WEBP`, `ImageType::AVIF` ve `ImageType::BMP` sabitleri ile temsil edilir. ```php -$data = $image->toString(Image::JPEG, 80); // JPEG, kalite %80. +$supported = Image::isTypeSupported(ImageType::JPEG); ``` -veya uygun HTTP başlığıyla doğrudan tarayıcıya gönderin `Content-Type`: +Yükleme sırasında resim formatını tespit etmeniz mi gerekiyor? Metot, ikinci parametrede formatı döndürür: ```php -// başlık gönderir Content-Type: image/png -$image->send(Image::PNG); +$image = Image::fromFile('nette.jpg', $type); ``` +Resmi yüklemeden sadece tespit işlemini `Image::detectTypeFromFile()` yapar. -Görüntü Yeniden Boyutlandırma .[#toc-image-resize] -================================================== -Yaygın bir işlem, bir görüntüyü yeniden boyutlandırmaktır. Geçerli boyutlar `getWidth()` ve `getHeight()` yöntemleri tarafından döndürülür. +Yeniden Boyutlandırma +===================== -Yeniden boyutlandırma için `resize()` yöntemi kullanılır. Bu, 500×300 pikseli aşmayacak şekilde orantılı boyut değişikliği örneğidir (genişlik tam olarak 500 piksel veya yükseklik tam olarak 300 piksel olacaktır, boyutlardan biri en boy oranını korumak için hesaplanır): +Sık yapılan bir işlem, resmin boyutlarını değiştirmektir. Mevcut boyutları `getWidth()` ve `getHeight()` metotları döndürür. + +Değişiklik için `resize()` metodu kullanılır. 500x300 piksel boyutlarını aşmayacak şekilde orantılı yeniden boyutlandırma örneği (ya genişlik tam olarak 500 piksel olacak ya da yükseklik tam olarak 300 piksel olacak, en boy oranını korumak için boyutlardan biri hesaplanacaktır): ```php $image->resize(500, 300); ``` -Yalnızca bir boyut ayarlamak mümkündür ve ikincisi hesaplanacaktır: +Sadece bir boyut belirtmek ve diğerinin hesaplanmasını sağlamak mümkündür: ```php -$image->resize(500, null); // genişlik 500px, yükseklik otomatik +$image->resize(500, null); // genişlik 500px, yükseklik hesaplanır -$image->resize(null, 300); // genişlik otomatik, yükseklik 300px +$image->resize(null, 300); // genişlik hesaplanır, yükseklik 300px ``` -Herhangi bir boyut yüzde olarak belirtilebilir: +Herhangi bir boyut yüzde olarak da belirtilebilir: ```php -$image->resize('75%', 300); // 75 % × 300px +$image->resize('75%', 300); // %75 × 300px ``` -`resize` adresinin davranışı aşağıdaki bayraklardan etkilenebilir. `Image::Stretch` hariç hepsi en boy oranını korur. +`resize` davranışını aşağıdaki bayraklarla etkileyebilirsiniz. `Image::Stretch` dışındaki tümü en boy oranını korur. |--------------------------------------------------------------------------------------- -| Bayrak | Açıklama +| Bayrak | Açıklama |--------------------------------------------------------------------------------------- -| `Image::OrSmaller` (varsayılan) | sonuçta elde edilen boyutlar belirtilenden küçük veya eşit olacaktır -| `Image::OrBigger` | hedef alanı doldurur ve muhtemelen bir yönde genişletir -| `Image::Cover` | tüm alanı doldurur ve onu aşanları keser -| `Image::ShrinkOnly` | sadece aşağı ölçeklendirir (küçük bir görüntüyü genişletmez) -| `Image::Stretch` | en boy oranını korumaz +| `Image::OrSmaller` (varsayılan) | sonuç boyutları istenen boyutlara eşit veya daha küçük olacaktır +| `Image::OrBigger` | hedef alanı doldurur (ve muhtemelen bir boyutta aşar) +| `Image::Cover` | hedef alanı doldurur ve aşan kısmı kırpar +| `Image::ShrinkOnly` | sadece küçültme (küçük bir resmin büyütülmesini önler) +| `Image::Stretch` | en boy oranını korumaz -Bayraklar fonksiyonun üçüncü argümanı olarak aktarılır: +Bayraklar, fonksiyonun üçüncü argümanı olarak belirtilir: ```php $image->resize(500, 300, Image::OrBigger); @@ -137,7 +148,7 @@ Bayraklar birleştirilebilir: $image->resize(500, 300, Image::ShrinkOnly | Image::Stretch); ``` -Boyutlardan biri (veya her ikisi) negatif sayı olarak belirtilerek görüntüler dikey veya yatay olarak çevrilebilir: +Resimler, boyutlardan birini (veya her ikisini) negatif bir sayı olarak belirterek dikey veya yatay olarak çevrilebilir: ```php $flipped = $image->resize(null, '-100%'); // dikey çevir @@ -147,23 +158,23 @@ $flipped = $image->resize('-100%', '-100%'); // 180° döndür $flipped = $image->resize(-125, 500); // yeniden boyutlandır ve yatay çevir ``` -Görüntüyü küçülttükten sonra keskinleştirerek iyileştirebiliriz: +Resmi küçülttükten sonra, görünümünü hafif bir keskinleştirme ile iyileştirmek mümkündür: ```php $image->sharpen(); ``` -Kırpma .[#toc-cropping] -======================= +Kırpma +====== -Kırpma için `crop()` yöntemi kullanılır: +Kırpma için `crop()` metodu kullanılır: ```php $image->crop($left, $top, $width, $height); ``` -`resize()` adresinde olduğu gibi, tüm değerler yüzde olarak belirtilebilir. `$left` ve `$top` için yüzdeler, `background-position` CSS özelliğine benzer şekilde kalan alandan hesaplanır: +`resize()` metodunda olduğu gibi, tüm değerler yüzde olarak belirtilebilir. `$left` ve `$top` için yüzdeler, CSS özelliği `background-position`'a benzer şekilde kalan alandan hesaplanır: ```php $image->crop('100%', '50%', '80%', '80%'); @@ -172,334 +183,364 @@ $image->crop('100%', '50%', '80%', '80%'); [* crop.svg *] -Görüntü otomatik olarak da kırpılabilir, örneğin siyah kenarlar kırpılabilir: +Resim ayrıca otomatik olarak da kırpılabilir, örneğin siyah kenarları kırpma: ```php $image->cropAuto(IMG_CROP_BLACK); ``` -`cropAuto()` yöntemi, `imagecropauto()` işlevinin bir nesne kapsüllemesidir, daha fazla bilgi için [belgelerine |https://www.php.net/manual/en/function.imagecropauto] bakın. +`cropAuto()` metodu, `imagecropauto()` fonksiyonunun nesneye yönelik bir alternatifidir, [dokümantasyonunda|https://www.php.net/manual/en/function.imagecropauto] daha fazla bilgi bulabilirsiniz. + + +Renkler .{data-version:4.0.2} +============================= + +`ImageColor::rgb()` metodu, kırmızı, yeşil ve mavi (RGB) değerlerini kullanarak bir renk tanımlamanıza olanak tanır. İsteğe bağlı olarak, 0 (tamamen şeffaf) ile 1 (tamamen opak) arasında bir şeffaflık değeri de belirtebilirsiniz, yani CSS'deki gibi. + +```php +$color = ImageColor::rgb(255, 0, 0); // Kırmızı +$transparentBlue = ImageColor::rgb(0, 0, 255, 0.5); // Yarı şeffaf mavi +``` + +`ImageColor::hex()` metodu, CSS'dekine benzer şekilde onaltılık format kullanarak bir renk tanımlamanıza olanak tanır. `#rgb`, `#rrggbb`, `#rgba` ve `#rrggbbaa` formatlarını destekler: + +```php +$color = ImageColor::hex("#F00"); // Kırmızı +$transparentGreen = ImageColor::hex("#00FF0080"); // Yarı şeffaf yeşil +``` + +Renkler, `ellipse()`, `fill()` vb. gibi diğer metotlarda kullanılabilir. -Çizim ve Düzenleme .[#toc-drawing-and-editing] -============================================== +Çizim ve Düzenleme +================== -Çizebilir, yazabilir, [imagefilledellipse() |https://www.php.net/manual/en/function.imagefilledellipse.php] gibi resimlerle çalışmak için tüm PHP işlevlerini kullanabilirsiniz, ancak nesne stilini kullanarak: +Çizebilirsin, yazabilirsin, ama yaprakları koparamazsın. Resimlerle çalışmak için tüm PHP fonksiyonları size sunulmuştur, bkz. [#Metotlara Genel Bakış], ancak nesneye yönelik bir biçimde: ```php -$image->filledEllipse($cx, $cy, $width, $height, Image::rgb(255, 0, 0, 63)); +$image->filledEllipse($centerX, $centerY, $width, $height, ImageColor::rgb(255, 0, 0)); ``` -[Yöntemlere Genel Bakış |#Overview of Methods] bölümüne bakınız. +PHP'nin dikdörtgen çizme fonksiyonları koordinat belirleme nedeniyle pratik olmadığından, `Image` sınıfı [#rectangleWH()] ve [#filledRectangleWH()] fonksiyonları şeklinde bunların yerine geçenleri sunar. -Birden Fazla Görüntüyü Birleştirme .[#toc-merge-multiple-images] -================================================================ +Birden Fazla Resmi Birleştirme +============================== -Görüntünün içine kolayca başka bir görüntü yerleştirebilirsiniz: +Bir resme kolayca başka bir resim eklenebilir: ```php $logo = Image::fromFile('logo.png'); -$blank = Image::fromBlank(320, 240, Image::rgb(52, 132, 210)); +$blank = Image::fromBlank(320, 240, ImageColor::rgb(52, 132, 210)); -// koordinatlar yüzde olarak da ayarlanabilir -$blank->place($logo, '80%', '80%'); // sağ alt köşeye yakın +// koordinatlar yine yüzde olarak belirtilebilir +$blank->place($logo, '80%', '80%'); // sağ alt köşeye yakın bir yere ekleriz ``` -Yapıştırırken, alfa kanalına saygı duyulur, ayrıca eklenen görüntünün şeffaflığını etkileyebiliriz (sözde bir filigran oluşturacağız): +Ekleme sırasında alfa kanalı dikkate alınır, ayrıca eklenen resmin şeffaflığını etkileyebiliriz (bir filigran oluştururuz): ```php -$blank->place($image, '80%', '80%', 25); // şeffaflık %25 +$blank->place($image, '80%', '80%', 25); // şeffaflık %25'tir ``` -Böyle bir API'yi kullanmak gerçekten bir zevk, değil mi? +Böyle bir API kullanmak gerçekten bir zevktir! -Yöntemlere Genel Bakış .[#toc-overview-of-methods] -================================================== +Metotlara Genel Bakış +===================== -static fromBlank(int $width, int $height, array $color=null): Image .[method] ------------------------------------------------------------------------------ -Verilen boyutlarda yeni bir gerçek renkli görüntü oluşturur. Varsayılan renk siyahtır. +static fromBlank(int $width, int $height, ?ImageColor $color=null): Image .[method] +----------------------------------------------------------------------------------- +Verilen boyutlarda yeni bir true color resim oluşturur. Varsayılan renk siyahtır. static fromFile(string $file, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------- -Dosyadan bir görüntü okur ve türünü `$detectedFormat` olarak döndürür. Desteklenen türler `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` ve `Image::BMP`'dir. +Dosyadan bir resim yükler ve `$detectedFormat` içinde [tipini |#Formatlar] döndürür. static fromString(string $s, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------ -Bir dizeden bir görüntü okur ve türünü `$detectedFormat` olarak döndürür. Desteklenen türler `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` ve `Image::BMP`'dir. +Bir karakter dizisinden bir resim yükler ve `$detectedFormat` içinde [tipini |#Formatlar] döndürür. -static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method] ---------------------------------------------------------------------------------- -`ellipse()`, `fill()` gibi diğer yöntemlerde kullanılabilecek bir renk oluşturur. +static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method][deprecated] +--------------------------------------------------------------------------------------------- +Bu fonksiyonun yerini `ImageColor` sınıfı almıştır, bkz. [#renkler]. static typeToExtension(int $type): string .[method] --------------------------------------------------- -Verilen `Image::XXX` sabiti için dosya uzantısını döndürür. +Verilen [tip |#Formatlar] için dosya uzantısını döndürür. static typeToMimeType(int $type): string .[method] -------------------------------------------------- -Verilen `Image::XXX` sabiti için mime türünü döndürür. +Verilen [tip |#Formatlar] için mime türünü döndürür. static extensionToType(string $extension): int .[method] -------------------------------------------------------- -Dosya uzantısına göre görüntü türünü sabit `Image::XXX` olarak döndürür. +Dosya uzantısına göre resmin [tipini |#Formatlar] döndürür. static detectTypeFromFile(string $file, int &$width=null, int &$height=null): ?int .[method] -------------------------------------------------------------------------------------------- -Görüntü dosyasının türünü `Image::XXX` sabiti olarak ve `$width` ve `$height` parametrelerinde de boyutlarını döndürür. +Resmin [tipini |#Formatlar] ve `$width` ve `$height` parametrelerinde boyutlarını döndürür. static detectTypeFromString(string $s, int &$width=null, int &$height=null): ?int .[method] ------------------------------------------------------------------------------------------- -Resmin türünü dizeden `Image::XXX` sabiti olarak ve `$width` ve `$height` parametrelerinde de boyutlarını döndürür. +Bir karakter dizisinden resmin [tipini |#Formatlar] ve `$width` ve `$height` parametrelerinde boyutlarını döndürür. -affine(array $affine, array $clip=null): Image .[method] --------------------------------------------------------- -İsteğe bağlı bir kırpma alanı kullanarak, afin dönüşümü yapılmış src görüntüsünü içeren bir görüntü döndürür. ([daha fazla |https://www.php.net/manual/en/function.imageaffine]). +static isTypeSupported(int $type): bool .[method] +------------------------------------------------- +Verilen resim [tipinin |#Formatlar] desteklenip desteklenmediğini kontrol eder. + + +static getSupportedTypes(): array .[method]{data-version:4.0.4} +--------------------------------------------------------------- +Desteklenen resim [tiplerinin |#Formatlar] bir dizisini döndürür. + + +static calculateTextBox(string $text, string $fontFile, float $size, float $angle=0, array $options=[]): array .[method] +------------------------------------------------------------------------------------------------------------------------ +Belirli bir yazı tipi ve boyutunda metni çevreleyecek dikdörtgenin boyutlarını hesaplar. `left`, `top`, `width`, `height` anahtarlarını içeren ilişkisel bir dizi döndürür. Metin sol alt kesme ile başlıyorsa sol kenar negatif olabilir. + + +affine(array $affine, ?array $clip=null): Image .[method] +--------------------------------------------------------- +İsteğe bağlı bir kırpma alanı kullanarak src'nin afin olarak dönüştürülmüş görüntüsünü içeren bir resim döndürün. ([daha fazla |https://www.php.net/manual/en/function.imageaffine]). affineMatrixConcat(array $m1, array $m2): array .[method] --------------------------------------------------------- -İki afin dönüşüm matrisinin birleştirilmesini döndürür; bu, aynı görüntüye tek seferde birden fazla dönüşüm uygulanması gerektiğinde kullanışlıdır. ([daha fazla |https://www.php.net/manual/en/function.imageaffinematrixconcat]) +Aynı resme aynı anda birden fazla dönüşüm uygulanacaksa kullanışlı olan iki afin dönüşüm matrisinin birleşimini döndürür. ([daha fazla |https://www.php.net/manual/en/function.imageaffinematrixconcat]) -affineMatrixGet(int $type, mixed $options=null): array .[method] ----------------------------------------------------------------- -Bir afin dönüşüm matrisi döndürür. ([daha fazla |https://www.php.net/manual/en/function.imageaffinematrixget]) +affineMatrixGet(int $type, ?mixed $options=null): array .[method] +----------------------------------------------------------------- +Bir matris dönüşüm matrisi döndürür. ([daha fazla |https://www.php.net/manual/en/function.imageaffinematrixget]) alphaBlending(bool $on): void .[method] --------------------------------------- -Gerçek renkli görüntüler üzerinde iki farklı çizim moduna izin verir. Karıştırma modunda, `setPixel()` gibi tüm çizim işlevlerine sağlanan rengin alfa kanalı bileşeni, altta yatan rengin ne kadarının parlamasına izin verilmesi gerektiğini belirler. Sonuç olarak, o noktadaki mevcut rengi çizim rengiyle otomatik olarak karıştırır ve sonucu görüntüde saklar. Ortaya çıkan piksel opaktır. Karışımsız modda, çizim rengi alfa kanalı bilgisiyle birlikte tam anlamıyla kopyalanır ve hedef pikselin yerini alır. Palet görüntüleri üzerinde çizim yaparken karıştırma modu kullanılamaz. ([daha fazla |https://www.php.net/manual/en/function.imagealphablending]) +Truecolor resimlerde iki farklı çizim moduna izin verir. Karıştırma modunda, `setPixel()` gibi tüm çizim fonksiyonlarında kullanılan rengin alfa kanalı bileşeni, alttaki rengin ne ölçüde görünmesine izin verilmesi gerektiğini belirler. Sonuç olarak, bu noktada mevcut renk otomatik olarak çizilen renkle karıştırılır ve sonuç resme kaydedilir. Sonuç piksel opaktır. Karıştırma olmayan modda, çizilen renk alfa kanalı bilgileriyle birlikte tam olarak kopyalanır ve hedef pikselin yerini alır. Palet resimlerine çizim yaparken karıştırma modu kullanılamaz. ([daha fazla |https://www.php.net/manual/en/function.imagealphablending]) antialias(bool $on): void .[method] ----------------------------------- -Çizgiler ve kablolu çokgenler için hızlı çizim antialiased yöntemlerini etkinleştirin. Alfa bileşenlerini desteklemez. Doğrudan harmanlama işlemi kullanılarak çalışır. Yalnızca gerçek renkli görüntülerle çalışır. +Yumuşatılmış çizgilerin ve çokgenlerin çizimini etkinleştirin. Alfa kanallarını desteklemez. Yalnızca truecolor resimlerde çalışır. -Şeffaf arka plan rengiyle kenar yumuşatılmış ilkel öğelerin kullanılması bazı beklenmedik sonuçlara yol açabilir. Karışım yöntemi, arka plan rengini diğer renkler gibi kullanır. Alfa bileşeni desteğinin olmaması, alfa tabanlı bir kenar yumuşatma yöntemine izin vermez. ([daha fazla |https://www.php.net/manual/en/function.imageantialias]) +Kenarları yumuşatılmış ilkelleri şeffaf bir arka plan rengiyle kullanmak bazı beklenmedik sonuçlarla sonuçlanabilir. Karıştırma yöntemi, diğer tüm renkler gibi arka plan rengini kullanır. ([daha fazla |https://www.php.net/manual/en/function.imageantialias]) -arc(int $x, int $y, int $w, int $h, int $start, int $end, int $color): void .[method] -------------------------------------------------------------------------------------- -Verilen koordinatları merkez alan bir daire yayı çizer. ([daha fazla |https://www.php.net/manual/en/function.imagearc]) - - -char(int $font, int $x, int $y, string $char, int $color): void .[method] -------------------------------------------------------------------------- -Görüntüdeki `$char` öğesinin ilk karakterini sol üst köşesi `$x`,`$y` (sol üst köşesi 0, 0) olacak şekilde `$color` rengiyle çizer. ([daha fazla |https://www.php.net/manual/en/function.imagechar]) - - -charUp(int $font, int $x, int $y, string $char, int $color): void .[method] ---------------------------------------------------------------------------- -Verilen görüntü üzerinde belirtilen koordinatta `$char` karakterini dikey olarak çizer. ([daha fazla |https://www.php.net/manual/en/function.imagecharup]) +arc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color): void .[method] +--------------------------------------------------------------------------------------------------------------------------- +Verilen koordinatlarda merkezlenmiş bir daire yayı çizer. ([daha fazla |https://www.php.net/manual/en/function.imagearc]) colorAllocate(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------- -Verilen RGB bileşenlerinden oluşan rengi temsil eden bir renk tanımlayıcısı döndürür. Görüntüde kullanılacak her bir rengi oluşturmak için çağrılmalıdır. ([daha fazla |https://www.php.net/manual/en/function.imagecolorallocate]) +Verilen RGB bileşenlerinden oluşan rengi temsil eden bir renk tanımlayıcısı döndürür. Resimde kullanılacak her renk için oluşturulması gerekir. ([daha fazla |https://www.php.net/manual/en/function.imagecolorallocate]) colorAllocateAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ------------------------------------------------------------------------------ -Şeffaflık parametresinin eklenmesiyle `colorAllocate()` ile aynı şekilde davranır `$alpha`. ([daha fazla |https://www.php.net/manual/en/function.imagecolorallocatealpha]) +Şeffaflık parametresi `$alpha` eklenmesiyle `colorAllocate()` ile aynı şekilde davranır. ([daha fazla |https://www.php.net/manual/en/function.imagecolorallocatealpha]) colorAt(int $x, int $y): int .[method] -------------------------------------- -Görüntüde belirtilen konumdaki pikselin renginin indeksini döndürür. Görüntü gerçek renkli bir görüntüyse, bu fonksiyon o pikselin RGB değerini tamsayı olarak döndürür. Farklı kırmızı, yeşil ve mavi bileşen değerlerine erişmek için bit kaydırma ve maskeleme kullanın: ([devamı |https://www.php.net/manual/en/function.imagecolorat]) +Resimdeki belirtilen konumdaki pikselin renk indeksini döndürür. Resim truecolor ise, bu fonksiyon o pikselin RGB değerini bir tamsayı olarak döndürür. Ayrı kırmızı, yeşil ve mavi bileşen değerlerine erişmek için bit kaydırma ve bit maskeleme kullanın: ([daha fazla |https://www.php.net/manual/en/function.imagecolorat]) colorClosest(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------ -Görüntünün paletinde belirtilen RGB değerine "en yakın" olan rengin indeksini döndürür. İstenen renk ile paletteki her renk arasındaki "mesafe", RGB değerleri üç boyutlu uzaydaki noktaları temsil ediyormuş gibi hesaplanır. ([daha fazla |https://www.php.net/manual/en/function.imagecolorclosest]) +Resim paletindeki, belirtilen RGB değerine „en yakın“ olan rengin indeksini döndürür. "Uzaklık" istenen renk ile paletteki her renk arasında, RGB değerleri üç boyutlu uzaydaki noktaları temsil ediyormuş gibi hesaplanır. ([daha fazla |https://www.php.net/manual/en/function.imagecolorclosest]) colorClosestAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ----------------------------------------------------------------------------- -Görüntünün paletinde belirtilen RGB değerine ve `$alpha` düzeyine "en yakın" rengin indeksini döndürür. ([daha fazla |https://www.php.net/manual/en/function.imagecolorclosestalpha]) +Resim paletindeki, belirtilen RGB değerine ve `$alpha` seviyesine „en yakın“ olan rengin indeksini döndürür. ([daha fazla |https://www.php.net/manual/en/function.imagecolorclosestalpha]) colorClosestHWB(int $red, int $green, int $blue): int .[method] --------------------------------------------------------------- -Verilen renge en yakın ton, beyaz ve siyahlığa sahip rengin indeksini alır. ([daha fazla |https://www.php.net/manual/en/function.imagecolorclosesthwb]) +Tonu, beyazı ve siyahı verilen renge en yakın olan rengin indeksini alın. ([daha fazla |https://www.php.net/manual/en/function.imagecolorclosesthwb]) colorDeallocate(int $color): void .[method] ------------------------------------------- -Daha önce `colorAllocate()` veya `colorAllocateAlpha()` ile tahsis edilmiş bir rengin tahsisini kaldırır. ([daha fazla |https://www.php.net/manual/en/function.imagecolordeallocate]) +Daha önce `colorAllocate()` veya `colorAllocateAlpha()` ile ayrılmış bir rengin ayrılmasını kaldırır. ([daha fazla |https://www.php.net/manual/en/function.imagecolordeallocate]) colorExact(int $red, int $green, int $blue): int .[method] ---------------------------------------------------------- -Görüntünün paletinde belirtilen rengin indeksini döndürür. ([daha fazla |https://www.php.net/manual/en/function.imagecolorexact]) +Resim paletindeki belirtilen rengin indeksini döndürür. ([daha fazla |https://www.php.net/manual/en/function.imagecolorexact]) colorExactAlpha(int $red, int $green, int $blue, int $alpha): int .[method] --------------------------------------------------------------------------- -Görüntünün paletinde belirtilen renk+alfa indeksini döndürür. ([daha fazla |https://www.php.net/manual/en/function.imagecolorexactalpha]) +Resim paletindeki belirtilen renk + alfa indeksini döndürür. ([daha fazla |https://www.php.net/manual/en/function.imagecolorexactalpha]) colorMatch(Image $image2): void .[method] ----------------------------------------- -Bir görüntünün palet sürümünün renklerinin gerçek renk sürümüne daha yakın olmasını sağlar. ([daha fazla |https://www.php.net/manual/en/function.imagecolormatch]) +Palet renklerini ikinci resme uyarlar. ([daha fazla |https://www.php.net/manual/en/function.imagecolormatch]) colorResolve(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------ -İstenen renk için bir renk indeksi döndürür, ya tam renk ya da mümkün olan en yakın alternatif. ([daha fazla |https://www.php.net/manual/en/function.imagecolorresolve]) +İstenen renk için, ya tam rengi ya da mümkün olan en yakın alternatifi olan renk indeksini döndürür. ([daha fazla |https://www.php.net/manual/en/function.imagecolorresolve]) colorResolveAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ----------------------------------------------------------------------------- -İstenen renk için bir renk indeksi döndürür, ya tam renk ya da mümkün olan en yakın alternatif. ([daha fazla |https://www.php.net/manual/en/function.imagecolorresolvealpha]) +İstenen renk için, ya tam rengi ya da mümkün olan en yakın alternatifi olan renk indeksini döndürür. ([daha fazla |https://www.php.net/manual/en/function.imagecolorresolvealpha]) colorSet(int $index, int $red, int $green, int $blue): void .[method] --------------------------------------------------------------------- -Bu, palette belirtilen indeksi belirtilen renge ayarlar. ([daha fazla |https://www.php.net/manual/en/function.imagecolorset]) +Paletteki belirtilen indeksi belirtilen renge ayarlar. ([daha fazla |https://www.php.net/manual/en/function.imagecolorset]) colorsForIndex(int $index): array .[method] ------------------------------------------- -Belirtilen bir indeks için rengi döndürür. ([daha fazla |https://www.php.net/manual/en/function.imagecolorsforindex]) +Belirtilen indeksin rengini alır. ([daha fazla |https://www.php.net/manual/en/function.imagecolorsforindex]) colorsTotal(): int .[method] ---------------------------- -Bir görüntü paletindeki renk sayısını döndürür ([daha fazla |https://www.php.net/manual/en/function.imagecolorstotal]). +Resim paletindeki renk sayısını döndürür. ([daha fazla |https://www.php.net/manual/en/function.imagecolorstotal]) -colorTransparent(int $color=null): int .[method] ------------------------------------------------- -Görüntüdeki saydam rengi alır veya ayarlar. ([daha fazla |https://www.php.net/manual/en/function.imagecolortransparent]) +colorTransparent(?int $color=null): int .[method] +------------------------------------------------- +Resimdeki şeffaf rengi alır veya ayarlar. ([daha fazla |https://www.php.net/manual/en/function.imagecolortransparent]) convolution(array $matrix, float $div, float $offset): void .[method] --------------------------------------------------------------------- -Verilen katsayı ve ofseti kullanarak görüntü üzerinde bir konvolüsyon matrisi uygular. ([daha fazla |https://www.php.net/manual/en/function.imageconvolution]) +Verilen katsayı ve ofseti kullanarak resme bir konvolüsyon matrisi uygular. ([daha fazla |https://www.php.net/manual/en/function.imageconvolution]) .[note] -Paketlenmiş GD uzantısı* gerektirir, bu nedenle her yerde çalışacağından emin değildir. +*Birlikte gelen GD uzantısının* varlığını gerektirir, bu nedenle her yerde çalışmayabilir. copy(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH): void .[method] -------------------------------------------------------------------------------------------------- -`$src` adresinin bir bölümünü `$srcX`, `$srcY` koordinatlarından başlayarak `$srcW` genişliğinde ve `$srcH` yüksekliğinde görüntü üzerine kopyalar. Tanımlanan kısım `$dstX` ve `$dstY` koordinatlarına kopyalanacaktır. ([daha fazla |https://www.php.net/manual/en/function.imagecopy]) +`$src` öğesinin bir bölümünü, `$srcX`, `$srcY` koordinatlarından başlayarak `$srcW` genişliği ve `$srcH` yüksekliği ile resme kopyalar. Tanımlanan bölüm `$dstX` ve `$dstY` koordinatlarına kopyalanacaktır. ([daha fazla |https://www.php.net/manual/en/function.imagecopy]) copyMerge(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] --------------------------------------------------------------------------------------------------------------------- -`$src` adresinin bir bölümünü `$srcX`, `$srcY` koordinatlarından başlayarak `$srcW` genişliğinde ve `$srcH` yüksekliğinde görüntü üzerine kopyalar. Tanımlanan kısım `$dstX` ve `$dstY` koordinatlarına kopyalanacaktır. ([daha fazla |https://www.php.net/manual/en/function.imagecopymerge]) +`$src` öğesinin bir bölümünü, `$srcX`, `$srcY` koordinatlarından başlayarak `$srcW` genişliği ve `$srcH` yüksekliği ile resme kopyalar. Tanımlanan bölüm `$dstX` ve `$dstY` koordinatlarına kopyalanacaktır. ([daha fazla |https://www.php.net/manual/en/function.imagecopymerge]) copyMergeGray(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] ------------------------------------------------------------------------------------------------------------------------- -`$src` adresinin bir bölümünü `$srcX`, `$srcY` koordinatlarından başlayarak `$srcW` genişliğinde ve `$srcH` yüksekliğinde görüntü üzerine kopyalar. Tanımlanan kısım `$dstX` ve `$dstY` koordinatlarına kopyalanacaktır. +`$src` öğesinin bir bölümünü, `$srcX`, `$srcY` koordinatlarından başlayarak `$srcW` genişliği ve `$srcH` yüksekliği ile resme kopyalar. Tanımlanan bölüm `$dstX` ve `$dstY` koordinatlarına kopyalanacaktır. -Bu işlev `copyMerge()` ile aynıdır, ancak birleştirme sırasında hedef pikselleri kopyalama işleminden önce gri ölçeğe dönüştürerek kaynağın tonunu korur. ([daha fazla |https://www.php.net/manual/en/function.imagecopymergegray]) +Bu fonksiyon, birleştirme sırasında hedef pikselleri kopyalama işleminden önce gri tonlamaya dönüştürerek kaynağın tonunu koruması dışında `copyMerge()` ile aynıdır. ([daha fazla |https://www.php.net/manual/en/function.imagecopymergegray]) copyResampled(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] --------------------------------------------------------------------------------------------------------------------------------- -Bir görüntünün dikdörtgen bir bölümünü başka bir görüntüye kopyalar, piksel değerlerini düzgün bir şekilde enterpole eder, böylece özellikle bir görüntünün boyutunun küçültülmesi hala büyük ölçüde netliği korur. +Bir resmin dikdörtgen bir bölümünü başka bir resme kopyalar, piksel değerlerini düzgün bir şekilde enterpole eder, böylece özellikle bir resmin boyutunu küçültmek hala büyük bir netliği korur. -Başka bir deyişle, `copyResampled()` `$src` adresinden (`$srcX`,`$srcY`) konumunda `$srcW` genişliğinde ve `$srcH` yüksekliğinde dikdörtgen bir alan alacak ve bunu (`$dstX`,`$dstY`) konumunda `$dstW` genişliğinde ve `$dstH` yüksekliğinde dikdörtgen bir görüntü alanına yerleştirecektir. +Başka bir deyişle, `copyResampled()` `$src` öğesinden (`$srcX`, `$srcY`) konumunda `$srcW` genişliğinde ve `$srcH` yüksekliğinde dikdörtgen bir alan alır ve bunu (`$dstX`, `$dstY`) konumunda `$dstW` genişliğinde ve `$dstH` yüksekliğinde resmin dikdörtgen bir alanına yerleştirir. -Kaynak ve hedef koordinatları ile genişlik ve yükseklikler farklıysa, görüntü parçasında uygun germe veya küçültme işlemi gerçekleştirilir. Koordinatlar sol üst köşeyi ifade eder. Bu fonksiyon aynı görüntü içindeki bölgeleri kopyalamak için kullanılabilir ancak bölgeler çakışırsa sonuçlar tahmin edilemez olacaktır. ([daha fazla |https://www.php.net/manual/en/function.imagecopyresampled]) +Kaynak ve hedef koordinatları, genişlik ve yükseklik farklıysa, resim parçasının karşılık gelen bir genişletilmesi veya küçültülmesi gerçekleştirilir. Koordinatlar sol üst köşeye göredir. Bu fonksiyon aynı resimdeki alanları kopyalamak için kullanılabilir, ancak alanlar örtüşüyorsa sonuçlar öngörülemez olacaktır. ([daha fazla |https://www.php.net/manual/en/function.imagecopyresampled]) copyResized(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] ------------------------------------------------------------------------------------------------------------------------------- -Bir görüntünün dikdörtgen bir bölümünü başka bir görüntüye kopyalar. Başka bir deyişle, `copyResized()` `$src` adresinden (`$srcX`,`$srcY`) konumunda `$srcW` genişliğinde ve `$srcH` yüksekliğinde dikdörtgen bir alan alacak ve bunu (`$dstX`,`$dstY`) konumunda `$dstW` genişliğinde ve `$dstH` yüksekliğinde dikdörtgen bir görüntü alanına yerleştirecektir. +Bir resmin dikdörtgen bir bölümünü başka bir resme kopyalar. Başka bir deyişle, `copyResized()` `$src` öğesinden (`$srcX`, `$srcY`) konumunda `$srcW` genişliğinde ve `$srcH` yüksekliğinde dikdörtgen bir alan alır ve bunu (`$dstX`, `$dstY`) konumunda `$dstW` genişliğinde ve `$dstH` yüksekliğinde resmin dikdörtgen bir alanına yerleştirir. -Kaynak ve hedef koordinatları ile genişlik ve yükseklikler farklıysa, görüntü parçasında uygun germe veya küçültme işlemi gerçekleştirilir. Koordinatlar sol üst köşeyi ifade eder. Bu fonksiyon aynı görüntü içindeki bölgeleri kopyalamak için kullanılabilir ancak bölgeler üst üste gelirse sonuçlar tahmin edilemez olacaktır. ([daha fazla |https://www.php.net/manual/en/function.imagecopyresized]) +Kaynak ve hedef koordinatları, genişlik ve yükseklik farklıysa, resim parçasının karşılık gelen bir genişletilmesi veya küçültülmesi gerçekleştirilir. Koordinatlar sol üst köşeye göredir. Bu fonksiyon aynı resimdeki alanları kopyalamak için kullanılabilir, ancak alanlar örtüşüyorsa sonuçlar öngörülemez olacaktır. ([daha fazla |https://www.php.net/manual/en/function.imagecopyresized]) crop(int|string $left, int|string $top, int|string $width, int|string $height): Image .[method] ----------------------------------------------------------------------------------------------- -Bir görüntüyü verilen dikdörtgen alana kırpar. Boyutlar piksel cinsinden tamsayılar veya yüzde cinsinden dizeler olarak aktarılabilir (örn. `'50%'`). +Resmi verilen dikdörtgen alana kırpar. Boyutlar piksel cinsinden tamsayılar veya yüzde cinsinden karakter dizileri (örneğin `'50%'`) olarak belirtilebilir. -cropAuto(int $mode=-1, float $threshold=.5, int $color=-1): Image .[method] ---------------------------------------------------------------------------- -Bir görüntüyü verilen `$mode` adresine göre otomatik olarak kırpar. ([daha fazla |https://www.php.net/manual/en/function.imagecropauto]) +cropAuto(int $mode=-1, float $threshold=.5, ?ImageColor $color=null): Image .[method] +------------------------------------------------------------------------------------- +Resmi verilen `$mode`'a göre otomatik olarak kırpar. ([daha fazla |https://www.php.net/manual/en/function.imagecropauto]) -ellipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------- -Belirtilen koordinatlarda ortalanmış bir elips çizer. ([daha fazla |https://www.php.net/manual/en/function.imageellipse]) +ellipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------- +Belirtilen koordinatlarda merkezlenmiş bir elips çizer. ([daha fazla |https://www.php.net/manual/en/function.imageellipse]) -fill(int $x, int $y, int $color): void .[method] ------------------------------------------------- -Verilen koordinattan başlayarak (sol üst 0, 0'dır) görüntüde verilen `$color` ile bir sel dolgusu gerçekleştirir. ([daha fazla |https://www.php.net/manual/en/function.imagefill]) +fill(int $x, int $y, ImageColor $color): void .[method] +------------------------------------------------------- +Verilen koordinattan başlayarak (sol üst 0, 0) alanı verilen `$color` ile doldurur. ([daha fazla |https://www.php.net/manual/en/function.imagefill]) -filledArc(int $cx, int $cy, int $w, int $h, int $s, int $e, int $color, int $style): void .[method] ---------------------------------------------------------------------------------------------------- -Görüntüde belirtilen koordinatı merkez alan kısmi bir yay çizer. ([daha fazla |https://www.php.net/manual/en/function.imagefilledarc]) +filledArc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color, int $style): void .[method] +--------------------------------------------------------------------------------------------------------------------------------------------- +Belirtilen koordinatlarda merkezlenmiş kısmi bir yay çizer. ([daha fazla |https://www.php.net/manual/en/function.imagefilledarc]) -filledEllipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------------- -Görüntüde belirtilen koordinatı merkez alan bir elips çizer. ([daha fazla |https://www.php.net/manual/en/function.imagefilledellipse]) +filledEllipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------------- +Belirtilen koordinatlarda merkezlenmiş bir elips çizer. ([daha fazla |https://www.php.net/manual/en/function.imagefilledellipse]) -filledPolygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------------- -Resim içinde dolu bir çokgen oluşturur. ([daha fazla |https://www.php.net/manual/en/function.imagefilledpolygon]) +filledPolygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------------- +Resimde doldurulmuş bir çokgen oluşturur. ([daha fazla |https://www.php.net/manual/en/function.imagefilledpolygon]) -filledRectangle(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] -------------------------------------------------------------------------------- -Görüntüde 1 noktasından başlayıp 2 noktasında biten `$color` ile doldurulmuş bir dikdörtgen oluşturur. 0, 0 görüntünün sol üst köşesidir. ([daha fazla |https://www.php.net/manual/en/function.imagefilledrectangle]) +filledRectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------- +Resimde `$x1` & `$y1` noktasından başlayıp `$x2` & `$y2` noktasında biten `$color` ile doldurulmuş bir dikdörtgen oluşturur. 0, 0 noktası resmin sol üst köşesidir. ([daha fazla |https://www.php.net/manual/en/function.imagefilledrectangle]) -fillToBorder(int $x, int $y, int $border, int $color): void .[method] ---------------------------------------------------------------------- -Kenarlık rengi `$border` tarafından tanımlanan bir taşma dolgusu gerçekleştirir. Dolgu için başlangıç noktası `$x`, `$y` (sol üst 0, 0'dır) ve bölge `$color` rengi ile doldurulur. ([daha fazla |https://www.php.net/manual/en/function.imagefilltoborder]) +filledRectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------------- +Resimde `$left` & `$top` noktasından başlayarak `$width` genişliğinde ve `$height` yüksekliğinde `$color` ile doldurulmuş bir dikdörtgen oluşturur. 0, 0 noktası resmin sol üst köşesidir. + + +fillToBorder(int $x, int $y, int $border, ImageColor $color): void .[method] +---------------------------------------------------------------------------- +Kenar rengi `$border` ile tanımlanan bir dolgu çizer. Dolgunun başlangıç noktası `$x`, `$y`'dir (sol üst 0, 0) ve alan `$color` rengiyle doldurulur. ([daha fazla |https://www.php.net/manual/en/function.imagefilltoborder]) filter(int $filtertype, int ...$args): void .[method] ----------------------------------------------------- -Verilen filtreyi `$filtertype` görüntü üzerinde uygular. ([daha fazla |https://www.php.net/manual/en/function.imagefilter]) +Verilen `$filtertype` filtresini resme uygular. ([daha fazla |https://www.php.net/manual/en/function.imagefilter]) flip(int $mode): void .[method] ------------------------------- -Verilen `$mode` adresini kullanarak görüntüyü çevirir. ([daha fazla |https://www.php.net/manual/en/function.imageflip]) +Resmi verilen `$mode` kullanarak çevirir. ([daha fazla |https://www.php.net/manual/en/function.imageflip]) -ftText(int $size, int $angle, int $x, int $y, int $col, string $fontFile, string $text, array $extrainfo=null): array .[method] -------------------------------------------------------------------------------------------------------------------------------- -FreeType 2 kullanan yazı tiplerini kullanarak görüntüye metin yazın. ([daha fazla |https://www.php.net/manual/en/function.imagefttext]) +ftText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +---------------------------------------------------------------------------------------------------------------------------------------- +Metni resme yazdırır. ([daha fazla |https://www.php.net/manual/en/function.imagefttext]) gammaCorrect(float $inputgamma, float $outputgamma): void .[method] ------------------------------------------------------------------- -Bir giriş ve bir çıkış gaması verilen görüntüye gama düzeltmesi uygular. ([daha fazla |https://www.php.net/manual/en/function.imagegammacorrect]) +Giriş ve çıkış gammalarına göre resme gama düzeltmesi uygular. ([daha fazla |https://www.php.net/manual/en/function.imagegammacorrect]) getClip(): array .[method] -------------------------- -Geçerli kırpma dikdörtgenini, yani ötesinde hiçbir pikselin çizilmeyeceği alanı alır. ([daha fazla |https://www.php.net/manual/en/function.imagegetclip]) +Mevcut kırpmayı, yani hiçbir pikselin çizilmeyeceği alanı döndürür. ([daha fazla |https://www.php.net/manual/en/function.imagegetclip]) getHeight(): int .[method] -------------------------- -Görüntünün yüksekliğini döndürür. +Resmin yüksekliğini döndürür. getImageResource(): resource|GdImage .[method] @@ -509,172 +550,167 @@ Orijinal kaynağı döndürür. getWidth(): int .[method] ------------------------- -Görüntünün genişliğini döndürür. +Resmin genişliğini döndürür. -interlace(int $interlace=null): int .[method] ---------------------------------------------- -Geçiş bitini açar veya kapatır. Geçiş biti ayarlanmışsa ve görüntü bir JPEG görüntüsü olarak kullanılıyorsa, görüntü aşamalı bir JPEG olarak oluşturulur. ([daha fazla |https://www.php.net/manual/en/function.imageinterlace]) +interlace(?int $interlace=null): int .[method] +---------------------------------------------- +Geçişli modu açar veya kapatır. Geçişli mod ayarlanmışsa ve resim JPEG olarak kaydedilmişse, aşamalı JPEG olarak kaydedilir. ([daha fazla |https://www.php.net/manual/en/function.imageinterlace]) isTrueColor(): bool .[method] ----------------------------- -Görüntünün gerçek renk olup olmadığını bulur. ([daha fazla |https://www.php.net/manual/en/function.imageistruecolor]) +Resmin truecolor olup olmadığını belirler. ([daha fazla |https://www.php.net/manual/en/function.imageistruecolor]) layerEffect(int $effect): void .[method] ---------------------------------------- -Katmanlama efektlerini kullanmak için alfa karıştırma bayrağını ayarlayın. ([daha fazla |https://www.php.net/manual/en/function.imagelayereffect]) +Katman efektlerini kullanmak için alfa karıştırma bayrağını ayarlayın. ([daha fazla |https://www.php.net/manual/en/function.imagelayereffect]) -line(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] --------------------------------------------------------------------- -Verilen iki nokta arasında bir çizgi çizer. ([daha fazla |https://www.php.net/manual/en/function.imageline]) +line(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +--------------------------------------------------------------------------- +Verilen iki nokta arasına bir çizgi çizer. ([daha fazla |https://www.php.net/manual/en/function.imageline]) -openPolygon(array $points, int $numPoints, int $color): void .[method] ----------------------------------------------------------------------- -Görüntü üzerine açık bir çokgen çizer. `polygon()` 'un aksine, son nokta ile ilk nokta arasında bir çizgi çizilmez. ([daha fazla |https://www.php.net/manual/en/function.imageopenpolygon]) +openPolygon(array $points, ImageColor $color): void .[method] +------------------------------------------------------------- +Resme açık bir çokgen çizer. `polygon()`'un aksine, son ve ilk nokta arasına çizgi çizilmez. ([daha fazla |https://www.php.net/manual/en/function.imageopenpolygon]) paletteCopy(Image $source): void .[method] ------------------------------------------ -Paleti `$source` adresinden görüntüye kopyalar. ([daha fazla |https://www.php.net/manual/en/function.imagepalettecopy]) +Paleti `$source` öğesinden resme kopyalar. ([daha fazla |https://www.php.net/manual/en/function.imagepalettecopy]) paletteToTrueColor(): void .[method] ------------------------------------ -`create()` gibi işlevler tarafından oluşturulan palet tabanlı bir görüntüyü `createtruecolor()` gibi gerçek renkli bir görüntüye dönüştürür. ([daha fazla |https://www.php.net/manual/en/function.imagepalettetotruecolor]) +Palet tabanlı bir resmi tam renkli bir resme dönüştürür. ([daha fazla |https://www.php.net/manual/en/function.imagepalettetotruecolor]) place(Image $image, int|string $left=0, int|string $top=0, int $opacity=100): Image .[method] --------------------------------------------------------------------------------------------- -`$image` adresini `$left` ve `$top` koordinatlarındaki görüntüye kopyalar. Koordinatlar piksel cinsinden tamsayılar veya yüzde cinsinden dizeler olarak aktarılabilir (örn. `'50%'`). +`$image` öğesini `$left` ve `$top` koordinatlarında resme kopyalar. Koordinatlar piksel cinsinden tamsayılar veya yüzde cinsinden karakter dizileri (örneğin `'50%'`) olarak belirtilebilir. -polygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------- -Görüntüde bir çokgen oluşturur. ([daha fazla |https://www.php.net/manual/en/function.imagepolygon]) +polygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------- +Resimde bir çokgen oluşturur. ([daha fazla |https://www.php.net/manual/en/function.imagepolygon]) -rectangle(int $x1, int $y1, int $x2, int $y2, int $col): void .[method] ------------------------------------------------------------------------ -Belirtilen koordinatlardan başlayan bir dikdörtgen oluşturur. ([daha fazla |https://www.php.net/manual/en/function.imagerectangle]) +rectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------- +Belirtilen koordinatlarda bir dikdörtgen oluşturur. ([daha fazla |https://www.php.net/manual/en/function.imagerectangle]) + + +rectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------- +Belirtilen koordinatlarda bir dikdörtgen oluşturur. resize(int|string $width, int|string $height, int $flags=Image::OrSmaller): Image .[method] ------------------------------------------------------------------------------------------- -Bir görüntüyü ölçeklendirir, [daha fazla bilgi |#Image Resize] için bkz. Boyutlar piksel cinsinden tamsayılar veya yüzde cinsinden dizeler olarak aktarılabilir (örn. `'50%'`). +Resmi yeniden boyutlandırır, [daha fazla bilgi |#Yeniden Boyutlandırma]. Boyutlar piksel cinsinden tamsayılar veya yüzde cinsinden karakter dizileri (örneğin `'50%'`) olarak belirtilebilir. -resolution(int $resX=null, int $resY=null): mixed .[method] ------------------------------------------------------------ -DPI (inç başına nokta) cinsinden bir görüntünün çözünürlüğünü ayarlamaya ve almaya izin verir. İsteğe bağlı parametrelerden hiçbiri verilmezse, geçerli çözünürlük indeksli dizi olarak döndürülür. Yalnızca `$resX` adresi verilirse, yatay ve dikey çözünürlük bu değere ayarlanır. Her iki isteğe bağlı parametre de verilirse, yatay ve dikey çözünürlük sırasıyla bu değerlere ayarlanır. +resolution(?int $resX=null, ?int $resY=null): mixed .[method] +------------------------------------------------------------- +Resmin çözünürlüğünü DPI (inç başına nokta) cinsinden ayarlar veya döndürür. İsteğe bağlı parametrelerden hiçbiri belirtilmezse, mevcut çözünürlük indekslenmiş bir dizi olarak döndürülür. Yalnızca `$resX` belirtilirse, yatay ve dikey çözünürlük bu değere ayarlanır. Her iki isteğe bağlı parametre de belirtilirse, yatay ve dikey çözünürlük bu değerlere ayarlanır. -Çözünürlük yalnızca görüntüler bu tür bilgileri destekleyen formatlardan (şu anda PNG ve JPEG) okunduğunda ve bu formatlara yazıldığında meta bilgi olarak kullanılır. Herhangi bir çizim işlemini etkilemez. Yeni görüntüler için varsayılan çözünürlük 96 DPI'dır. ([daha fazla |https://www.php.net/manual/en/function.imageresolution]) +Çözünürlük yalnızca, bu tür bilgileri destekleyen formatlarda (şu anda PNG ve JPEG) resimler okunurken ve yazılırken meta bilgi olarak kullanılır. Herhangi bir çizim işlemini etkilemez. Yeni görüntülerin varsayılan çözünürlüğü 96 DPI'dır. ([daha fazla |https://www.php.net/manual/en/function.imageresolution]) rotate(float $angle, int $backgroundColor): Image .[method] ----------------------------------------------------------- -Verilen `$angle` adresini derece cinsinden kullanarak görüntüyü döndürür. Döndürme merkezi görüntünün merkezidir ve döndürülen görüntü orijinal görüntüden farklı boyutlara sahip olabilir. ([daha fazla |https://www.php.net/manual/en/function.imagerotate]) +Resmi belirtilen `$angle` ile derece cinsinden döndürür. Döndürme merkezi resmin merkezidir ve döndürülen resim orijinal resimden farklı boyutlara sahip olabilir. ([daha fazla |https://www.php.net/manual/en/function.imagerotate]) .[note] -Paketlenmiş GD uzantısı* gerektirir, bu nedenle her yerde çalışacağından emin değildir. +*Birlikte gelen GD uzantısının* varlığını gerektirir, bu nedenle her yerde çalışmayabilir. -save(string $file, int $quality=null, int $type=null): void .[method] ---------------------------------------------------------------------- -Bir görüntüyü bir dosyaya kaydeder. +save(string $file, ?int $quality=null, ?int $type=null): void .[method] +----------------------------------------------------------------------- +Resmi bir dosyaya kaydeder. -Sıkıştırma kalitesi JPEG (varsayılan 85), WEBP (varsayılan 80) ve AVIF (varsayılan 30) için 0..100 ve PNG (varsayılan 9) için 0..9 aralığındadır. Dosya uzantısından tür belli değilse, `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` ve `Image::BMP` sabitlerinden birini kullanarak belirtebilirsiniz. +Sıkıştırma kalitesi JPEG (varsayılan 85), WEBP (varsayılan 80) ve AVIF (varsayılan 30) için 0..100 aralığında ve PNG (varsayılan 9) için 0..9 aralığındadır. Tür dosya uzantısından belli değilse, `ImageType` sabitlerinden birini kullanarak belirtebilirsiniz. saveAlpha(bool $saveflag): void .[method] ----------------------------------------- -PNG görüntüleri kaydedilirken tam alfa kanalı bilgisinin (tek renkli saydamlığın aksine) korunup korunmayacağını belirleyen bayrağı ayarlar. +PNG görüntülerini kaydederken tam alfa kanalı bilgilerini (tek renkli şeffaflığın aksine) koruyup korumayacağını belirleyen bayrağı ayarlar. -İlk etapta alfa kanalını korumak için alfabe oluşturma devre dışı bırakılmalıdır (`alphaBlending(false)`). ([daha fazla |https://www.php.net/manual/en/function.imagesavealpha]) +Alfa kanalını ilk etapta korumak için alfablending devre dışı bırakılmalıdır (`alphaBlending(false)`). ([daha fazla |https://www.php.net/manual/en/function.imagesavealpha]) scale(int $newWidth, int $newHeight=-1, int $mode=IMG_BILINEAR_FIXED): Image .[method] -------------------------------------------------------------------------------------- -Verilen enterpolasyon algoritmasını kullanarak bir görüntüyü ölçeklendirir. ([daha fazla |https://www.php.net/manual/en/function.imagescale]) +Verilen interpolasyon algoritmasını kullanarak resmi ölçeklendirir. ([daha fazla |https://www.php.net/manual/en/function.imagescale]) -send(int $type=Image::JPEG, int $quality=null): void .[method] --------------------------------------------------------------- -Tarayıcıya bir görüntü çıktısı verir. +send(int $type=ImageType::JPEG, ?int $quality=null): void .[method] +------------------------------------------------------------------- +Resmi tarayıcıya yazdırır. -Sıkıştırma kalitesi JPEG (varsayılan 85), WEBP (varsayılan 80) ve AVIF (varsayılan 30) için 0..100 ve PNG (varsayılan 9) için 0..9 aralığındadır. Tip `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` ve `Image::BMP` sabitlerinden biridir. +Sıkıştırma kalitesi JPEG (varsayılan 85), WEBP (varsayılan 80) ve AVIF (varsayılan 30) için 0..100 aralığında ve PNG (varsayılan 9) için 0..9 aralığındadır. setBrush(Image $brush): void .[method] -------------------------------------- -IMG_COLOR_BRUSHED veya IMG_COLOR_STYLEDBRUSHED özel renkleriyle çizim yaparken tüm çizgi çizim fonksiyonları ( `line()` ve `polygon()` gibi) tarafından kullanılacak fırça görüntüsünü ayarlar. ([daha fazla |https://www.php.net/manual/en/function.imagesetbrush]) +Özel renkler IMG_COLOR_BRUSHED veya IMG_COLOR_STYLEDBRUSHED ile çizim yaparken tüm çizgi çizim fonksiyonlarında (örneğin `line()` ve `polygon()`) kullanılacak fırça görüntüsünü ayarlar. ([daha fazla |https://www.php.net/manual/en/function.imagesetbrush]) setClip(int $x1, int $y1, int $x2, int $y2): void .[method] ----------------------------------------------------------- -Geçerli kırpma dikdörtgenini, yani ötesinde hiçbir pikselin çizilmeyeceği alanı ayarlar. ([daha fazla |https://www.php.net/manual/en/function.imagesetclip]) +Mevcut kırpmayı, yani hiçbir pikselin çizilmeyeceği alanı ayarlar. ([daha fazla |https://www.php.net/manual/en/function.imagesetclip]) setInterpolation(int $method=IMG_BILINEAR_FIXED): void .[method] ---------------------------------------------------------------- -`rotate()` ve `affine()` yöntemlerini etkileyen enterpolasyon yöntemini ayarlar. ([daha fazla |https://www.php.net/manual/en/function.imagesetinterpolation]) +`rotate()` ve `affine()` metotlarını etkileyen interpolasyon yöntemini ayarlar. ([daha fazla |https://www.php.net/manual/en/function.imagesetinterpolation]) -setPixel(int $x, int $y, int $color): void .[method] ----------------------------------------------------- -Belirtilen koordinatta bir piksel çizer. ([devamı |https://www.php.net/manual/en/function.imagesetpixel]) +setPixel(int $x, int $y, ImageColor $color): void .[method] +----------------------------------------------------------- +Belirtilen koordinata bir piksel çizer. ([daha fazla |https://www.php.net/manual/en/function.imagesetpixel]) setStyle(array $style): void .[method] -------------------------------------- -IMG_COLOR_STYLED özel rengiyle veya IMG_COLOR_STYLEDBRUSHED rengine sahip görüntülerin çizgileriyle çizim yaparken tüm çizgi çizim işlevleri ( `line()` ve `polygon()` gibi) tarafından kullanılacak stili ayarlar. ([daha fazla |https://www.php.net/manual/en/function.imagesetstyle]) +Özel renk IMG_COLOR_STYLED veya renk IMG_COLOR_STYLEDBRUSHED ile resim çizgileri çizerken tüm çizgi çizim fonksiyonları (örneğin `line()` ve `polygon()`) tarafından kullanılacak stili ayarlar. ([daha fazla |https://www.php.net/manual/en/function.imagesetstyle]) setThickness(int $thickness): void .[method] -------------------------------------------- -Dikdörtgenler, çokgenler, yaylar vb. çizerken çizilen çizgilerin kalınlığını `$thickness` piksel olarak ayarlar. ([devamı |https://www.php.net/manual/en/function.imagesetthickness]) +Dikdörtgenler, çokgenler, yaylar vb. çizerken çizgilerin kalınlığını `$thickness` piksel olarak ayarlar. ([daha fazla |https://www.php.net/manual/en/function.imagesetthickness]) setTile(Image $tile): void .[method] ------------------------------------ -IMG_COLOR_TILED özel rengiyle doldururken tüm bölge doldurma işlevleri ( `fill()` ve `filledPolygon()` gibi) tarafından kullanılacak döşeme görüntüsünü ayarlar. +Özel renk IMG_COLOR_TILED ile doldururken tüm bölge doldurma fonksiyonlarında (örneğin `fill()` ve `filledPolygon()`) kullanılacak döşeme görüntüsünü ayarlar. -Karo, bir alanı tekrarlanan bir desenle doldurmak için kullanılan bir görüntüdür. Herhangi bir görüntü karo olarak kullanılabilir ve karo görüntüsünün şeffaf renk indeksi `colorTransparent()` ile ayarlanarak, alttaki alanın belirli kısımlarının parlamasına izin veren bir karo oluşturulabilir. ([daha fazla |https://www.php.net/manual/en/function.imagesettile]) +Döşeme, bir alanı tekrarlayan bir desenle doldurmak için kullanılan bir resimdir. Herhangi bir resim döşeme olarak kullanılabilir ve döşeme resminin şeffaf renk indeksini `colorTransparent()` ile ayarlayarak, alttaki alanın belirli bölümlerinin görüneceği bir döşeme oluşturulabilir. ([daha fazla |https://www.php.net/manual/en/function.imagesettile]) sharpen(): Image .[method] -------------------------- -Görüntüyü biraz keskinleştirir. +Resmi keskinleştirir. .[note] -Paketlenmiş GD uzantısı* gerektirir, bu nedenle her yerde çalışacağından emin değiliz. - - -string(int $font, int $x, int $y, string $str, int $col): void .[method] ------------------------------------------------------------------------- -Verilen koordinatlarda bir dize çizer. ([daha fazla |https://www.php.net/manual/en/function.imagestring]) +*Birlikte gelen GD uzantısının* varlığını gerektirir, bu nedenle her yerde çalışmayabilir. -stringUp(int $font, int $x, int $y, string $s, int $col): void .[method] ------------------------------------------------------------------------- -Verilen koordinatlarda dikey olarak bir dize çizer. ([daha fazla |https://www.php.net/manual/en/function.imagestringup]) - - -toString(int $type=Image::JPEG, int $quality=null): string .[method] --------------------------------------------------------------------- -Bir görüntüyü dizeye çıktı olarak verir. +toString(int $type=ImageType::JPEG, ?int $quality=null): string .[method] +------------------------------------------------------------------------- +Resmi bir karakter dizisine kaydeder. -Sıkıştırma kalitesi JPEG (varsayılan 85), WEBP (varsayılan 80) ve AVIF (varsayılan 30) için 0..100 ve PNG (varsayılan 9) için 0..9 aralığındadır. Tip `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` ve `Image::BMP` sabitlerinden biridir. +Sıkıştırma kalitesi JPEG (varsayılan 85), WEBP (varsayılan 80) ve AVIF (varsayılan 30) için 0..100 aralığında ve PNG (varsayılan 9) için 0..9 aralığındadır. trueColorToPalette(bool $dither, int $ncolors): void .[method] -------------------------------------------------------------- -Bir gerçek renkli görüntüyü bir palet görüntüsüne dönüştürür. ([daha fazla |https://www.php.net/manual/en/function.imagetruecolortopalette]) +Truecolor bir resmi palet resmine dönüştürür. ([daha fazla |https://www.php.net/manual/en/function.imagetruecolortopalette]) -ttfText(int $size, int $angle, int $x, int $y, int $color, string $fontfile, string $text): array .[method] ------------------------------------------------------------------------------------------------------------ -TrueType yazı tiplerini kullanarak verilen metni görüntüye yazar. ([daha fazla |https://www.php.net/manual/en/function.imagettftext]) +ttfText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +----------------------------------------------------------------------------------------------------------------------------------------- +Verilen metni resme yazdırır. ([daha fazla |https://www.php.net/manual/en/function.imagettftext]) diff --git a/utils/tr/iterables.texy b/utils/tr/iterables.texy new file mode 100644 index 0000000000..7cbe7fe317 --- /dev/null +++ b/utils/tr/iterables.texy @@ -0,0 +1,170 @@ +İteratörlerle Çalışma +********************* + +.[perex]{data-version:4.0.4} +[api:Nette\Utils\Iterables], iteratörlerle çalışmak için fonksiyonlar içeren statik bir sınıftır. Diziler için benzeri [Nette\Utils\Arrays|arrays]'dir. + + +Kurulum: + +```shell +composer require nette/utils +``` + +Tüm örnekler, bir takma ad oluşturulduğunu varsayar: + +```php +use Nette\Utils\Iterables; +``` + + +contains(iterable $iterable, $value): bool .[method] +---------------------------------------------------- + +Belirtilen değeri iteratörde arar. Eşleşmeyi doğrulamak için katı karşılaştırma (`===`) kullanır. Değer bulunursa `true`, aksi takdirde `false` döndürür. + +```php +Iterables::contains(new ArrayIterator([1, 2, 3]), 1); // true +Iterables::contains(new ArrayIterator([1, 2, 3]), '1'); // false +``` + +Bu metot, tüm öğeleri manuel olarak dolaşmadan belirli bir değerin iteratörde olup olmadığını hızlıca öğrenmeniz gerektiğinde kullanışlıdır. + + +containsKey(iterable $iterable, $key): bool .[method] +----------------------------------------------------- + +Belirtilen anahtarı iteratörde arar. Eşleşmeyi doğrulamak için katı karşılaştırma (`===`) kullanır. Anahtar bulunursa `true`, aksi takdirde `false` döndürür. + +```php +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 0); // true +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 4); // false +``` + + +every(iterable $iterable, callable $predicate): bool .[method] +-------------------------------------------------------------- + +İteratördeki tüm öğelerin `$predicate` içinde tanımlanan koşulu karşılayıp karşılamadığını doğrular. `$predicate` fonksiyonu `function ($value, $key, iterable $iterable): bool` imzasına sahiptir ve `every()` metodunun `true` döndürmesi için her öğe için `true` döndürmelidir. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isBelowThreshold = fn($value) => $value < 40; +$res = Iterables::every($iterator, $isBelowThreshold); // true +``` + +Bu metot, bir koleksiyondaki tüm öğelerin belirli bir koşulu karşılayıp karşılamadığını doğrulamak için kullanışlıdır, örneğin tüm sayıların belirli bir değerden küçük olup olmadığını kontrol etmek gibi. + + +filter(iterable $iterable, callable $predicate): Generator .[method] +-------------------------------------------------------------------- + +Orijinal iteratörden yalnızca `$predicate` içinde tanımlanan koşulu karşılayan öğeleri içeren yeni bir iteratör (jeneratör) oluşturur. `$predicate` fonksiyonu `function ($value, $key, iterable $iterable): bool` imzasına sahiptir ve korunacak öğeler için `true` döndürmelidir. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::filter($iterator, fn($v) => $v < 3); +// 1, 2 +``` + +Metot bir jeneratör kullanır, bu da filtrelemenin sonuç üzerinde gezinirken aşamalı olarak gerçekleştiği anlamına gelir. Bu, bellek açısından verimlidir ve çok büyük koleksiyonların işlenmesine olanak tanır. Sonuç iteratörünün tüm öğelerini dolaşmazsanız, orijinal iteratörün tüm öğeleri işlenmeyeceği için hesaplama gücünden tasarruf edersiniz. + + +first(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------------- + +İteratörün ilk öğesini döndürür. Eğer `$predicate` belirtilmişse, verilen koşulu karşılayan ilk öğeyi döndürür. `$predicate` fonksiyonu `function ($value, $key, iterable $iterable): bool` imzasına sahiptir. Uygun öğe bulunamazsa, `$else` fonksiyonu (eğer belirtilmişse) çağrılır ve sonucu döndürülür. Eğer `$else` belirtilmemişse, `null` döndürülür. + +```php +Iterables::first(new ArrayIterator([1, 2, 3])); // 1 +Iterables::first(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 3 +Iterables::first(new ArrayIterator([])); // null +Iterables::first(new ArrayIterator([]), else: fn() => false); // false +``` + +Bu metot, tüm koleksiyonu manuel olarak dolaşmadan bir koleksiyonun ilk öğesini veya belirli bir koşulu karşılayan ilk öğeyi hızlıca almanız gerektiğinde kullanışlıdır. + + +firstKey(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +---------------------------------------------------------------------------------------------- + +İteratörün ilk öğesinin anahtarını döndürür. Eğer `$predicate` belirtilmişse, verilen koşulu karşılayan ilk öğenin anahtarını döndürür. `$predicate` fonksiyonu `function ($value, $key, iterable $iterable): bool` imzasına sahiptir. Uygun öğe bulunamazsa, `$else` fonksiyonu (eğer belirtilmişse) çağrılır ve sonucu döndürülür. Eğer `$else` belirtilmemişse, `null` döndürülür. + +```php +Iterables::firstKey(new ArrayIterator([1, 2, 3])); // 0 +Iterables::firstKey(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 2 +Iterables::firstKey(new ArrayIterator(['a' => 1, 'b' => 2])); // 'a' +Iterables::firstKey(new ArrayIterator([])); // null +``` + + +map(iterable $iterable, callable $transformer): Generator .[method] +------------------------------------------------------------------- + +`$transformer` fonksiyonunu orijinal iteratörün her öğesine uygulayarak yeni bir iteratör oluşturur. `$transformer` fonksiyonu `function ($value, $key, iterable $iterable): mixed` imzasına sahiptir ve dönüş değeri öğenin yeni değeri olarak kullanılır. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::map($iterator, fn($v) => $v * 2); +// 2, 4, 6 +``` + +Metot bir jeneratör kullanır, bu da dönüşümün sonuç üzerinde gezinirken aşamalı olarak gerçekleştiği anlamına gelir. Bu, bellek açısından verimlidir ve çok büyük koleksiyonların işlenmesine olanak tanır. Sonuç iteratörünün tüm öğelerini dolaşmazsanız, orijinal iteratörün tüm öğeleri işlenmeyeceği için hesaplama gücünden tasarruf edersiniz. + + +mapWithKeys(iterable $iterable, callable $transformer): Generator .[method] +--------------------------------------------------------------------------- + +Orijinal iteratörün değerlerini ve anahtarlarını dönüştürerek yeni bir iteratör oluşturur. `$transformer` fonksiyonu `function ($value, $key, iterable $iterable): ?array{$newKey, $newValue}` imzasına sahiptir. Eğer `$transformer` `null` döndürürse, öğe atlanır. Korunan öğeler için, döndürülen dizinin ilk öğesi yeni anahtar olarak ve ikinci öğesi yeni değer olarak kullanılır. + +```php +$iterator = new ArrayIterator(['a' => 1, 'b' => 2]); +$iterator = Iterables::mapWithKeys($iterator, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +`map()` gibi, bu metot da aşamalı işleme ve bellek ile verimli çalışma için bir jeneratör kullanır. Bu, büyük koleksiyonlarla çalışmaya ve sonucun kısmi geçişi sırasında hesaplama gücünden tasarruf etmeye olanak tanır. + + +memoize(iterable $iterable): IteratorAggregate .[method] +-------------------------------------------------------- + +İterasyon sırasında anahtarlarını ve değerlerini önbelleğe alan bir iteratör etrafında bir sarmalayıcı oluşturur. Bu, orijinal veri kaynağını tekrar dolaşmaya gerek kalmadan verilerin tekrar tekrar itere edilmesini sağlar. + +```php +$iterator = /* birden fazla kez itere edilemeyen veriler */ +$memoized = Iterables::memoize($iterator); +// Artık $memoized'ı veri kaybı olmadan birden fazla kez itere edebilirsiniz +``` + +Bu metot, aynı veri kümesini birden fazla kez dolaşmanız gereken ancak orijinal iteratörün tekrarlanan iterasyona izin vermediği veya tekrarlanan dolaşımın maliyetli olacağı durumlarda (örneğin, veritabanından veya dosyadan veri okurken) kullanışlıdır. + + +some(iterable $iterable, callable $predicate): bool .[method] +------------------------------------------------------------- + +İteratördeki en az bir öğenin `$predicate` içinde tanımlanan koşulu karşılayıp karşılamadığını doğrular. `$predicate` fonksiyonu `function ($value, $key, iterable $iterable): bool` imzasına sahiptir ve `some()` metodunun `true` döndürmesi için en az bir öğe için `true` döndürmelidir. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isEven = fn($value) => $value % 2 === 0; +$res = Iterables::some($iterator, $isEven); // true +``` + +Bu metot, bir koleksiyonda belirli bir koşulu karşılayan en az bir öğenin olup olmadığını hızlıca doğrulamak için kullanışlıdır, örneğin koleksiyonun en az bir çift sayı içerip içermediğini kontrol etmek gibi. + +Bkz. [#every()]. + + +toIterator(iterable $iterable): Iterator .[method] +-------------------------------------------------- + +Herhangi bir itere edilebilir nesneyi (array, Traversable) Iterator'a dönüştürür. Girdi zaten bir Iterator ise, değişiklik yapmadan onu döndürür. + +```php +$array = [1, 2, 3]; +$iterator = Iterables::toIterator($array); +// Artık dizi yerine bir Iterator'ınız var +``` + +Bu metot, girdi verilerinin türünden bağımsız olarak bir Iterator'a sahip olduğunuzdan emin olmanız gerektiğinde kullanışlıdır. Bu, farklı türlerde itere edilebilir verilerle çalışan fonksiyonlar oluştururken faydalı olabilir. diff --git a/utils/tr/json.texy b/utils/tr/json.texy index 404b3a4601..296f87719f 100644 --- a/utils/tr/json.texy +++ b/utils/tr/json.texy @@ -1,8 +1,8 @@ -JSON İşlevleri -************** +JSON ile Çalışma +**************** .[perex] -[api:Nette\Utils\Json] JSON kodlama ve kod çözme işlevlerine sahip durağan bir sınıftır. Farklı PHP sürümlerindeki güvenlik açıklarını ele alır ve hatalarda istisnalar atar. +[api:Nette\Utils\Json], JSON formatını kodlamak ve kodunu çözmek için fonksiyonlar içeren statik bir sınıftır. Farklı PHP sürümlerinin güvenlik açıklarını ele alır ve hatalarda istisnalar fırlatır. Kurulum: @@ -11,30 +11,30 @@ Kurulum: composer require nette/utils ``` -Tüm örnekler aşağıdaki sınıf takma adının tanımlandığını varsayar: +Tüm örnekler, bir takma ad oluşturulduğunu varsayar: ```php use Nette\Utils\Json; ``` -Kullanım .[#toc-usage] -====================== +Kullanım +======== encode(mixed $value, bool $pretty=false, bool $asciiSafe=false, bool $htmlSafe=false, bool $forceObjects=false): string .[method] --------------------------------------------------------------------------------------------------------------------------------- -`$value` adresini JSON biçimine dönüştürür. +`$value` değerini JSON formatına dönüştürür. -`$pretty` ayarlandığında, JSON'u daha kolay okuma ve anlaşılırlık için biçimlendirir: +`$pretty` ayarı yapıldığında, JSON'u daha kolay okunabilirlik ve netlik için biçimlendirir: ```php Json::encode($value); // JSON döndürür -Json::encode($value, pretty: true); // daha net JSON döndürür +Json::encode($value, pretty: true); // daha okunaklı JSON döndürür ``` -`$asciiSafe` ayarlandığında, ASCII çıktısı üretir, yani unicode karakterleri `\uxxxx` dizileriyle değiştirir: +`$asciiSafe` ile çıktı ASCII olarak üretilir, yani unicode karakterler `\uxxxx` dizileriyle değiştirilir: ```php Json::encode('žluťoučký', asciiSafe: true); @@ -48,7 +48,7 @@ Json::encode('onesendJson($data)` yöntemini kullanabilirsiniz, [Yanıt Gönderme |application:presenters#Sending a Response] bölümüne bakın. +Bunun için `$this->sendJson($data)` metodunu kullanabilirsiniz, örneğin `action*()` metodunda çağırabilirsiniz, bkz. [Yanıt Gönderme |application:presenters#Yanıt Gönderme]. diff --git a/utils/tr/paginator.texy b/utils/tr/paginator.texy index 80fa985411..a456f93ad4 100644 --- a/utils/tr/paginator.texy +++ b/utils/tr/paginator.texy @@ -2,7 +2,7 @@ Paginator ********* .[perex] -Bir veri listesini sayfalandırmanız mı gerekiyor? Sayfalandırmanın arkasındaki matematik zor olabileceğinden, [api:Nette\Utils\Paginator] size yardımcı olacaktır. +Veri listesini sayfalamanız mı gerekiyor? Sayfalama matematiği yanıltıcı olabileceğinden, [api:Nette\Utils\Paginator] size yardımcı olacaktır. Kurulum: @@ -11,38 +11,38 @@ Kurulum: composer require nette/utils ``` -Bir sayfalama nesnesi oluşturalım ve bunun için temel bilgileri ayarlayalım: +Bir sayfalayıcı nesnesi oluşturalım ve temel bilgileri ayarlayalım: ```php $paginator = new Nette\Utils\Paginator; -$paginator->setPage(1); // geçerli sayfanın numarası (1'den itibaren numaralandırılır) -$paginator->setItemsPerPage(30); // sayfa başına kayıt sayısı -$paginator->setItemCount(356); // toplam kayıt sayısı (varsa) +$paginator->setPage(1); // mevcut sayfa numarası +$paginator->setItemsPerPage(30); // sayfa başına öğe sayısı +$paginator->setItemCount(356); // toplam öğe sayısı (biliniyorsa) ``` -Sayfalar 1'den itibaren numaralandırılır. `setBase()` adresini kullanarak bunu değiştirebiliriz: +Sayfalar 1'den başlar. Bunu `setBase()` kullanarak değiştirebiliriz: ```php -$paginator->setBase(0); // 0'dan itibaren numaralandırılır +$paginator->setBase(0); // 0'dan numaralandırıyoruz ``` -Nesne artık bir sayfalayıcı oluşturmada yararlı olan tüm temel bilgileri sağlayacaktır. Örneğin, bunu bir şablona aktarabilir ve orada kullanabilirsiniz. +Nesne şimdi sayfalayıcı oluştururken yararlı olan tüm temel bilgileri sağlayacaktır. Örneğin, onu bir şablona aktarabilir ve orada kullanabilirsiniz. ```php -$paginator->isFirst(); // bu ilk sayfa mı? -$paginator->isLast(); // bu son sayfa mı? -$paginator->getPage(); // geçerli sayfa numarası +$paginator->isFirst(); // ilk sayfada mıyız? +$paginator->isLast(); // son sayfada mıyız? +$paginator->getPage(); // mevcut sayfa numarası $paginator->getFirstPage(); // ilk sayfa numarası $paginator->getLastPage(); // son sayfa numarası $paginator->getFirstItemOnPage(); // sayfadaki ilk öğenin sıra numarası $paginator->getLastItemOnPage(); // sayfadaki son öğenin sıra numarası -$paginator->getPageIndex(); // 0'dan itibaren numaralandırılmışsa geçerli sayfa numarası +$paginator->getPageIndex(); // 0'dan başlayan mevcut sayfa numarası $paginator->getPageCount(); // toplam sayfa sayısı -$paginator->getItemsPerPage(); // sayfa başına kayıt sayısı -$paginator->getItemCount(); // toplam kayıt sayısı (varsa) +$paginator->getItemsPerPage(); // sayfa başına öğe sayısı +$paginator->getItemCount(); // toplam öğe sayısı (biliniyorsa) ``` -Sayfalandırıcı SQL sorgusunun formüle edilmesine yardımcı olacaktır. `getLength()` ve `getOffset()` yöntemleri LIMIT ve OFFSET cümlelerinde kullanabileceğiniz değerleri döndürür: +Sayfalayıcı, SQL sorgusu oluştururken yardımcı olur. `getLength()` ve `getOffset()` metotları, LIMIT ve OFFSET yan tümcelerinde kullanacağımız değerleri döndürür: ```php $result = $database->query( @@ -52,7 +52,7 @@ $result = $database->query( ); ``` -Ters sırada sayfalandırmanız gerekiyorsa, yani sayfa no. 1 en yüksek ofsete karşılık gelir, `getCountdownOffset()` adresini kullanabilirsiniz: +Ters sırada sayfalamamız gerekiyorsa, yani sayfa no. 1 en yüksek ofsete karşılık geliyorsa, `getCountdownOffset()` kullanırız: ```php $result = $database->query( @@ -62,4 +62,4 @@ $result = $database->query( ); ``` -Uygulamadaki bir kullanım örneği [Veritabanı Sonuçlarını Sayfalandırma |best-practices:pagination] adlı yemek kitabında bulunabilir. +Uygulamada kullanım örneğini [Veritabanı sonuçlarını sayfalama |best-practices:pagination] tarifinde bulabilirsiniz. diff --git a/utils/tr/random.texy b/utils/tr/random.texy index c542cdfb59..bb9ba7914b 100644 --- a/utils/tr/random.texy +++ b/utils/tr/random.texy @@ -1,8 +1,8 @@ -Rastgele Dizeler Oluşturucu -*************************** +Rastgele Karakter Dizileri Oluşturma +************************************ .[perex] -[api:Nette\Utils\Random] kriptografik olarak güvenli sözde rasgele dizeler üretmek için statik bir sınıftır. +[api:Nette\Utils\Random], kriptografik olarak güvenli sözde rastgele karakter dizileri oluşturmak için statik bir sınıftır. Kurulum: @@ -15,7 +15,7 @@ composer require nette/utils generate(int $length=10, string $charlist=`'0-9a-z'`): string .[method] ----------------------------------------------------------------------- -İkinci bağımsız değişkende belirtilen karakterlerden verilen uzunlukta rastgele bir dize oluşturur. `0-9` veya `A-Z` gibi aralıkları destekler. +`$charlist` parametresiyle belirtilen karakterlerden verilen uzunlukta rastgele bir karakter dizisi oluşturur. `0-9` gibi yazılan aralıkları da kullanabilirsiniz. ```php use Nette\Utils\Random; diff --git a/utils/tr/reflection.texy b/utils/tr/reflection.texy index d731643884..fee13905b9 100644 --- a/utils/tr/reflection.texy +++ b/utils/tr/reflection.texy @@ -1,8 +1,8 @@ -PHP Yansıması -************* +PHP Yansıma (Reflection) +************************ .[perex] -[api:Nette\Utils\Reflection] PHP yansıması için yararlı işlevler içeren statik bir sınıftır. Amacı, yerel sınıflardaki kusurları düzeltmek ve PHP'nin farklı sürümlerindeki davranışları birleştirmektir. +[api:Nette\Utils\Reflection], PHP yansıması için yararlı fonksiyonlar içeren statik bir sınıftır. Görevi, yerel sınıfların eksikliklerini düzeltmek ve farklı PHP sürümleri arasında davranışı birleştirmektir. Kurulum: @@ -11,7 +11,7 @@ Kurulum: composer require nette/utils ``` -Tüm örnekler aşağıdaki sınıf takma adının tanımlandığını varsayar: +Tüm örnekler, bir takma ad oluşturulduğunu varsayar: ```php use Nette\Utils\Reflection; @@ -21,13 +21,13 @@ use Nette\Utils\Reflection; areCommentsAvailable(): bool .[method] -------------------------------------- -Yansımanın PHPdoc yorumlarına erişimi olup olmadığını bulur. Opcode önbelleği nedeniyle yorumlar mevcut olmayabilir, örneğin [opcache.save-comments |https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments] yönergesine bakınız. +Yansımanın PHPdoc yorumlarına erişimi olup olmadığını kontrol eder. Yorumlar, opcode önbelleği nedeniyle kullanılamayabilir, örneğin [opcache.save-comments|https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments] direktifine bakın. expandClassName(string $name, ReflectionClass $context): string .[method] ------------------------------------------------------------------------- -Sınıfın `$name` adresini `$context` bağlamında, yani isim alanı ve tanımlı takma adları bağlamında tam isme genişletir. Böylece, `$context` gövdesinde yazılmış olsaydı PHP çözümleyicisinin `$name` 'u nasıl anlayacağını döndürür. +`$name` sınıf adını, `$context` sınıfının bağlamında, yani ad alanı ve tanımlanmış takma adlar bağlamında tam adına genişletir. Yani aslında, `$name` `$context` sınıfının gövdesinde yazılsaydı PHP ayrıştırıcısının onu nasıl anlayacağını söyler. ```php namespace Foo; @@ -47,9 +47,9 @@ Reflection::expandClassName('Baz', $context); // 'Foo\Baz' getMethodDeclaringMethod(ReflectionMethod $method): ReflectionMethod .[method] ------------------------------------------------------------------------------ -`$method` bildirimini içeren bir yöntemin yansımasını döndürür. Genellikle, her yöntem kendi bildirimidir, ancak yöntemin gövdesi trait içinde ve farklı bir ad altında da olabilir. +`$method` metodunun bildirimini içeren metodun yansımasını döndürür. Genellikle her metot kendi bildirimidir, ancak metodun gövdesi bir trait içinde ve farklı bir ad altında da bulunabilir. -PHP gerçek bildirimi belirlemek için yeterli bilgi sağlamadığından, Nette **güvenilir** olması gereken kendi sezgisel yöntemlerini kullanır. +PHP, gerçek bildirimin belirlenebileceği yeterli bilgi sağlamadığından, Nette güvenilir **olması gereken** kendi sezgisellerini kullanır. ```php trait DemoTrait @@ -76,9 +76,9 @@ Reflection::getMethodDeclaringMethod($method); // ReflectionMethod('DemoTrait::f getPropertyDeclaringClass(ReflectionProperty $prop): ReflectionClass .[method] ------------------------------------------------------------------------------ -`$prop` özelliğinin bildirimini içeren bir sınıf veya özelliğin yansımasını döndürür. Özellik, özellik içinde de bildirilebilir. +`$prop` özelliğinin bildirimini içeren sınıfın veya trait'in yansımasını döndürür. Özellik bir trait içinde de bildirilebilir. -PHP gerçek bildirimi belirlemek için yeterli bilgi sağlamadığından, Nette **güvenilir olmayan** kendi sezgisel yöntemlerini kullanır. +PHP, gerçek bildirimin belirlenebileceği yeterli bilgi sağlamadığından, Nette güvenilir **olmayan** kendi sezgisellerini kullanır. ```php trait DemoTrait @@ -100,7 +100,7 @@ Reflection::getPropertyDeclaringClass($prop); // ReflectionClass('DemoTrait') isBuiltinType(string $type): bool .[method deprecated] ------------------------------------------------------ -`$type` PHP yerleşik türü olup olmadığını belirler. Aksi takdirde, sınıf adıdır. +`$type` öğesinin yerleşik bir PHP türü olup olmadığını kontrol eder. Aksi takdirde, bir sınıf adıdır. ```php Reflection::isBuiltinType('string'); // true @@ -108,13 +108,13 @@ Reflection::isBuiltinType('Foo'); // false ``` .[note] -[Nette\Utils\Validator::isBuiltinType() |validators#isBuiltinType] işlevini kullanın. +[Nette\Utils\Validator::isBuiltinType() |validators#isBuiltinType] kullanın. toString($reflection): string .[method] --------------------------------------- -Bir yansımayı insan tarafından okunabilir bir dizeye dönüştürür. +Yansımayı insanlar tarafından okunabilir bir karakter dizisine dönüştürür. ```php $func = new ReflectionFunction('func'); diff --git a/utils/tr/smartobject.texy b/utils/tr/smartobject.texy index 41a488dbaa..cacaa377ac 100644 --- a/utils/tr/smartobject.texy +++ b/utils/tr/smartobject.texy @@ -1,8 +1,8 @@ -SmartObject ve StaticClass -************************** +SmartObject +*********** .[perex] -SmartObject PHP sınıflarına *property* desteği ekler. StaticClass statik sınıfları belirtmek için kullanılır. +SmartObject, yıllarca PHP'deki nesnelerin davranışını iyileştirdi. PHP 8.4 sürümünden itibaren, tüm fonksiyonları PHP'nin kendisinin bir parçası haline geldi ve böylece PHP'de modern nesne yönelimli yaklaşımın öncüsü olma tarihi misyonunu tamamladı. Kurulum: @@ -11,19 +11,64 @@ Kurulum: composer require nette/utils ``` +SmartObject, 2007 yılında o zamanki PHP nesne modelinin eksikliklerine devrim niteliğinde bir çözüm olarak ortaya çıktı. PHP'nin nesne tasarımıyla ilgili bir dizi sorun yaşadığı bir zamanda, geliştiriciler için işleri önemli ölçüde iyileştirdi ve basitleştirdi. Nette framework'ünün efsanevi bir parçası haline geldi. PHP'nin ancak yıllar sonra kazandığı işlevselliği sundu - nesne özelliklerine erişim kontrolünden sofistike sözdizimsel şekerlemelere kadar. PHP 8.4'ün gelişiyle, tüm fonksiyonları dilin yerel bir parçası haline geldiği için tarihi misyonunu tamamladı. PHP'nin gelişiminin dikkat çekici bir şekilde 17 yıl önüne geçti. -Özellikler, Getiriciler ve Ayarlayıcılar .[#toc-properties-getters-and-setters] -=============================================================================== +Teknik olarak SmartObject ilginç bir gelişim geçirdi. Başlangıçta, diğer sınıfların gerekli işlevselliği miras aldığı `Nette\Object` sınıfı olarak uygulandı. Önemli bir değişiklik, trait desteğini getiren PHP 5.4 ile geldi. Bu, `Nette\SmartObject` trait'ine dönüşümü mümkün kıldı ve daha fazla esneklik getirdi - geliştiriciler işlevselliği zaten başka bir sınıftan miras alan sınıflarda da kullanabildiler. Orijinal `Nette\Object` sınıfı PHP 7.2'nin (sınıfların `Object` kelimesiyle adlandırılmasını yasaklayan) gelişiyle ortadan kalkarken, `Nette\SmartObject` trait'i yaşamaya devam ediyor. -Modern nesne yönelimli dillerde (örneğin C#, Python, Ruby, JavaScript) *property* terimi, [sınıfların |https://en.wikipedia.org/wiki/Property_(programming)] değişken gibi görünen ancak aslında yöntemlerle temsil edilen [özel üyelerini |https://en.wikipedia.org/wiki/Property_(programming)] ifade eder. Bu "değişkenin" değeri atandığında veya okunduğunda, ilgili yöntem (getter veya setter olarak adlandırılır) çağrılır. Bu çok kullanışlı bir şeydir, bize değişkenlere erişim üzerinde tam kontrol sağlar. Girdiyi doğrulayabilir veya yalnızca özellik okunduğunda sonuç üretebiliriz. +Bir zamanlar `Nette\Object` ve daha sonra `Nette\SmartObject`'ın sunduğu özelliklere bir göz atalım. Bu fonksiyonların her biri, kendi zamanında PHP'de nesne yönelimli programlama alanında önemli bir adım temsil ediyordu. -PHP özellikleri desteklenmez, ancak trait `Nette\SmartObject` bunları taklit edebilir. Nasıl kullanılır? -- Sınıfa şu biçimde bir ek açıklama ekleyin `@property $xyz` -- `getXyz()` veya `isXyz()` adında bir getter, adında bir setter oluşturun. `setXyz()` -- Getter ve setter *public* veya *protected* olmalıdır ve isteğe bağlıdır, bu nedenle *read-only* veya *write-only* özelliği olabilir +Tutarlı Hata Durumları +---------------------- +Erken PHP'nin en can sıkıcı sorunlarından biri, nesnelerle çalışırken tutarsız davranıştı. `Nette\Object` bu kaosa düzen ve öngörülebilirlik getirdi. PHP'nin orijinal davranışına bir göz atalım: -`$radius` değişkenine yalnızca negatif olmayan sayıların girilmesini sağlamak için Circle sınıfının özelliğini kullanacağız. `public $radius` öğesini özellik ile değiştirin: +```php +echo $obj->undeclared; // E_NOTICE, daha sonra E_WARNING +$obj->undeclared = 1; // bildirim olmadan sessizce geçer +$obj->unknownMethod(); // Fatal error (try/catch ile yakalanamaz) +``` + +Fatal error, herhangi bir şekilde tepki verme olasılığı olmadan uygulamayı sonlandırdı. Var olmayan üyelere uyarı olmadan sessizce yazmak, tespit edilmesi zor ciddi hatalara yol açabilirdi. `Nette\Object` tüm bu durumları yakaladı ve `MemberAccessException` istisnası fırlattı, bu da programcıların hatalara tepki vermesine ve bunları çözmesine olanak tanıdı. + +```php +echo $obj->undeclared; // Nette\MemberAccessException fırlatır +$obj->undeclared = 1; // Nette\MemberAccessException fırlatır +$obj->unknownMethod(); // Nette\MemberAccessException fırlatır +``` + +PHP 7.0'dan itibaren dil artık yakalanamayan fatal error'lara neden olmuyor ve PHP 8.2'den itibaren bildirilmemiş üyelere erişim bir hata olarak kabul ediliyor. + + +"Did you mean?" Yardımı +----------------------- +`Nette\Object` çok hoş bir özellik getirdi: yazım hatalarında akıllı yardım. Geliştirici bir metot veya değişken adında hata yaptığında, sadece hatayı bildirmekle kalmadı, aynı zamanda doğru ad önerisi şeklinde yardımcı bir el de uzattı. "did you mean?" olarak bilinen bu ikonik mesaj, programcıların yazım hatalarını aramak için saatler harcamasını engelledi: + +```php +class Foo extends Nette\Object +{ + public static function from($var) + { + } +} + +$foo = Foo::form($var); +// Nette\MemberAccessException fırlatır +// "Call to undefined static method Foo::form(), did you mean from()?" +``` + +Günümüz PHP'sinin "did you mean?" benzeri bir özelliği olmasa da, [Tracy|tracy:] bu eki hatalara ekleyebilir. Hatta bu tür hataları [otomatik olarak düzeltme |tracy:open-files-in-ide#Örnekler] yeteneğine sahiptir. + + +Kontrollü Erişime Sahip Özellikler +---------------------------------- +SmartObject'ın PHP'ye getirdiği önemli bir yenilik, kontrollü erişime sahip özelliklerdi. C# veya Python gibi dillerde yaygın olan bu konsept, geliştiricilerin nesne verilerine erişimi zarif bir şekilde kontrol etmelerini ve tutarlılıklarını sağlamalarını sağladı. Özellikler, nesne yönelimli programlamanın güçlü bir aracıdır. Değişkenler gibi çalışırlar, ancak aslında metotlarla (getter'lar ve setter'lar) temsil edilirler. Bu, girdileri doğrulamayı veya değerleri okuma anında oluşturmayı mümkün kılar. + +Özellikleri kullanmak için şunları yapmalısınız: +- Sınıfa `@property $xyz` şeklinde bir ek açıklama ekleyin +- `getXyz()` veya `isXyz()` adında bir getter, `setXyz()` adında bir setter oluşturun +- Getter ve setter'ın *public* veya *protected* olduğundan emin olun. İsteğe bağlıdırlar - yani *salt okunur* veya *salt yazılır* özellik olarak var olabilirler + +Yarıçapın her zaman negatif olmayan bir sayı olmasını sağlamak için özellikleri kullanacağımız Circle sınıfında pratik bir örnek gösterelim. Orijinal `public $radius` öğesini bir özellikle değiştireceğiz: ```php /** @@ -34,22 +79,22 @@ class Circle { use Nette\SmartObject; - private float $radius = 0.0; // herkese açık değil + private float $radius = 0.0; // public değil! - //radius özelliği için getter + // $radius özelliği için getter protected function getRadius(): float { return $this->radius; } - //radius özelliği için setter + // $radius özelliği için setter protected function setRadius(float $radius): void { - // kaydetmeden önce değeri sanitize etme + // değeri kaydetmeden önce temizliyoruz $this->radius = max(0.0, $radius); } - // özellik için getter $visible + // $visible özelliği için getter protected function isVisible(): bool { return $this->radius > 0; @@ -57,84 +102,30 @@ class Circle } $circle = new Circle; -$circle->radius = 10; // aslında setRadius(10) çağırır -echo $circle->radius; // getRadius() işlevini çağırır -echo $circle->visible; // isVisible() işlevini çağırır +$circle->radius = 10; // aslında setRadius(10)'u çağırır +echo $circle->radius; // getRadius()'u çağırır +echo $circle->visible; // isVisible()'ı çağırır ``` -Özellikler öncelikle "sözdizimsel şeker"((syntactic sugar)) olup, kodu basitleştirerek programcının hayatını daha tatlı hale getirmeyi amaçlar. Eğer onları istemiyorsanız, kullanmak zorunda değilsiniz. - - -Statik Sınıflar .[#toc-static-classes] -====================================== - -Statik sınıflar, yani örneklenmesi amaçlanmayan sınıflar, `Nette\StaticClass` özelliği ile işaretlenebilir: +PHP 8.4'ten itibaren, çok daha zarif ve kısa bir sözdizimi sunan property hook'ları kullanarak aynı işlevselliğe ulaşılabilir: ```php -class Strings +class Circle { - use Nette\StaticClass; -} -``` - -Bir örnek oluşturmaya çalıştığınızda, sınıfın statik olduğunu belirten `Error` istisnası atılır. - - -Tarihe Bir Bakış .[#toc-a-look-into-the-history] -================================================ - -SmartObject eskiden sınıf davranışını birçok yönden geliştirir ve düzeltirdi, ancak PHP'nin evrimi orijinal özelliklerin çoğunu gereksiz hale getirdi. Bu nedenle aşağıda, işlerin nasıl geliştiğinin geçmişine bir bakış yer almaktadır. - -PHP nesne modeli en başından beri bir dizi ciddi kusur ve verimsizlikten muzdaripti. Bu eksiklikleri gidermeye ve PHP kullanım deneyimini iyileştirmeye çalışan `Nette\Object` sınıfının (2007'de) yaratılmasının nedeni buydu. Diğer sınıfların bu sınıftan miras alması ve sağladığı avantajlardan yararlanması için yeterliydi. PHP 5.4 trait desteği ile geldiğinde, `Nette\Object` sınıfı `Nette\SmartObject` ile değiştirildi. Böylece artık ortak bir atadan miras almaya gerek kalmamıştı. Ayrıca, trait zaten başka bir sınıftan miras alan sınıflarda da kullanılabiliyordu. `Nette\Object` 'un nihai sonu, sınıfların `Object` olarak adlandırılmasını yasaklayan PHP 7.2 sürümüyle geldi. - -PHP'nin gelişimi devam ettikçe, nesne modeli ve dil yetenekleri geliştirildi. `SmartObject` sınıfının bireysel işlevleri gereksiz hale geldi. PHP 8.2'nin yayınlanmasından bu yana, PHP'de henüz doğrudan desteklenmeyen tek özellik, sözde [özellikleri |#Properties, Getters and Setters] kullanma yeteneğidir. - -`Nette\Object` ve `Nette\Object` bir zamanlar hangi özellikleri sunuyordu? İşte genel bir bakış. (Örnekler `Nette\Object` sınıfını kullanmaktadır, ancak özelliklerin çoğu `Nette\SmartObject` özelliği için de geçerlidir). - - -Tutarsız Hatalar .[#toc-inconsistent-errors] --------------------------------------------- -PHP, bildirilmemiş üyelere erişirken tutarsız davranışlar sergiliyordu. `Nette\Object` adresinin durumu aşağıdaki gibiydi: - -```php -echo $obj->undeclared; // E_NOTICE, daha sonra E_WARNING -$obj->undeclared = 1; // raporlama yapmadan sessizce geçer -$obj->unknownMethod(); // Ölümcül hata (try/catch ile yakalanamaz) -``` - -Ölümcül hata, herhangi bir tepki verme imkanı olmadan uygulamayı sonlandırdı. Var olmayan üyelere uyarı vermeden sessizce yazmak, tespit edilmesi zor ciddi hatalara yol açabilirdi. `Nette\Object` Tüm bu durumlar yakalandı ve bir istisna `MemberAccessException` atıldı. - -```php -echo $obj->undeclared; // throw Nette\MemberAccessException -$obj->undeclared = 1; // throw Nette\MemberAccessException -$obj->unknownMethod(); // throw Nette\MemberAccessException -``` -PHP 7.0'dan beri PHP artık yakalanamayan ölümcül hatalara neden olmamaktadır ve bildirilmemiş üyelere erişim PHP 8.2'den beri bir hatadır. - - -Ne demek istiyorsun? .[#toc-did-you-mean] ------------------------------------------ -Bir nesne değişkenine erişirken veya bir yöntemi çağırırken yapılan yazım hatası nedeniyle `Nette\MemberAccessException` hatası atıldığında, `Nette\Object` hata mesajında hatanın nasıl düzeltileceğine dair ikonik "demek istediniz mi?" eki şeklinde bir ipucu vermeye çalışmıştır. + public float $radius = 0.0 { + set => max(0.0, $value); + } -```php -class Foo extends Nette\Object -{ - public static function from($var) - { + public bool $visible { + get => $this->radius > 0; } } - -$foo = Foo::form($var); -// throw Nette\MemberAccessException -// "Call to undefined static method Foo::form(), did you mean from()?" ``` -Günümüz PHP'sinde "demek istediniz mi?" gibi bir ifade bulunmayabilir, ancak [Tracy |tracy:] hatalara bu eki ekler. Ve bu tür hataları kendisi bile [düzeltebilir |tracy:open-files-in-ide#toc-demos]. - -Uzatma yöntemleri .[#toc-extension-methods] -------------------------------------------- -C#'ın uzantı yöntemlerinden esinlenilmiştir. Mevcut sınıflara yeni yöntemler ekleme imkanı verdiler. Örneğin, kendi DateTimePicker'ınızı eklemek için bir forma `addDateTime()` yöntemini ekleyebilirsiniz. +Uzantı Metotları +---------------- +`Nette\Object`, modern programlama dillerinden esinlenen başka bir ilginç konsepti PHP'ye getirdi - uzantı metotları. C#'dan alınan bu özellik, geliştiricilerin mevcut sınıfları değiştirmeden veya onlardan miras almadan yeni metotlarla zarif bir şekilde genişletmelerini sağladı. Örneğin, formunuza özel bir DateTimePicker ekleyen `addDateTime()` metodunu ekleyebilirsiniz: ```php Form::extensionMethod( @@ -146,22 +137,22 @@ $form = new Form; $form->addDateTime('date'); ``` -Uzatma yöntemlerinin pratik olmadığı kanıtlandı çünkü isimleri editörler tarafından otomatik olarak tamamlanmadı, bunun yerine yöntemin mevcut olmadığını bildirdiler. Bu nedenle destekleri kesilmiştir. +Uzantı metotları pratik olmadıklarını kanıtladılar çünkü adları editörler tarafından önerilmiyordu, aksine metodun var olmadığını bildiriyorlardı. Bu nedenle destekleri sona erdirildi. Bugün, sınıfların işlevselliğini genişletmek için kompozisyon veya kalıtım kullanmak daha yaygındır. -Sınıf Adını Alma .[#toc-getting-the-class-name] ------------------------------------------------ +Sınıf Adını Alma +---------------- +Sınıf adını almak için SmartObject basit bir metot sunuyordu: ```php -$class = $obj->getClass(); // using Nette\Object -$class = $obj::class; // PHP 8.0'dan beri +$class = $obj->getClass(); // Nette\Object kullanarak +$class = $obj::class; // PHP 8.0'dan itibaren ``` -Yansıma ve Ek Açıklamalara Erişim .[#toc-access-to-reflection-and-annotations] ------------------------------------------------------------------------------- - -`Nette\Object` `getReflection()` ve `getAnnotation()` yöntemlerini kullanarak yansıtma ve ek açıklamaya erişim sundu: +Yansıma ve Ek Açıklamalara Erişim +--------------------------------- +`Nette\Object`, `getReflection()` ve `getAnnotation()` metotlarını kullanarak yansıma ve ek açıklamalara erişim sunuyordu. Bu yaklaşım, sınıfların meta bilgileriyle çalışmayı önemli ölçüde basitleştirdi: ```php /** @@ -176,7 +167,7 @@ $reflection = $obj->getReflection(); $reflection->getAnnotation('author'); // 'John Doe' döndürür ``` -PHP 8.0'dan itibaren meta-bilgilere öznitelikler şeklinde erişmek mümkündür: +PHP 8.0'dan itibaren, meta bilgilere nitelikler şeklinde erişmek mümkündür, bu da daha da fazla olanak ve daha iyi tür kontrolü sunar: ```php #[Author('John Doe')] @@ -190,10 +181,9 @@ $reflection->getAttributes(Author::class)[0]; ``` -Yöntem Getiriciler .[#toc-method-getters] ------------------------------------------ - -`Nette\Object` metotları değişkenlermiş gibi ele almak için zarif bir yol sundu: +Metot Getters +------------- +`Nette\Object`, metotları sanki değişkenlermiş gibi aktarmanın zarif bir yolunu sunuyordu: ```php class Foo extends Nette\Object @@ -209,7 +199,7 @@ $method = $obj->adder; echo $method(2, 3); // 5 ``` -PHP 8.1'den itibaren, "birinci sınıf çağrılabilir sözdizimi":https://www.php.net/manual/en/functions.first_class_callable_syntax olarak adlandırılan sözdizimini:https://www.php.net/manual/en/functions.first_class_callable_syntax kullanabilirsiniz: +PHP 8.1'den itibaren, bu konsepti daha da ileri götüren "first-class callable syntax":https://www.php.net/manual/en/functions.first_class_callable_syntax'ı kullanmak mümkündür: ```php $obj = new Foo; @@ -218,10 +208,9 @@ echo $method(2, 3); // 5 ``` -Etkinlikler .[#toc-events] --------------------------- - -`Nette\Object` [olayı |nette:glossary#events] tetiklemek için sözdizimsel şeker sundu: +Olaylar +------- +SmartObject, [olaylar |nette:glossary#Olaylar Events] ile çalışmak için basitleştirilmiş bir sözdizimi sunar. Olaylar, nesnelerin durumlarındaki değişiklikler hakkında uygulamanın diğer bölümlerini bilgilendirmelerini sağlar: ```php class Circle extends Nette\Object @@ -231,12 +220,12 @@ class Circle extends Nette\Object public function setRadius(float $radius): void { $this->onChange($this, $radius); - $this->radius = $radius + $this->radius = $radius; } } ``` -`$this->onChange($this, $radius)` kodu aşağıdakine eşdeğerdir: +`$this->onChange($this, $radius)` kodu aşağıdaki döngüye eşdeğerdir: ```php foreach ($this->onChange as $callback) { @@ -244,7 +233,7 @@ foreach ($this->onChange as $callback) { } ``` -Netlik açısından sihirli yöntemden kaçınmanızı öneririz `$this->onChange()`. Pratik bir ikame [Nette\Utils\Arrays::invoke |arrays#invoke] fonksiyonudur: +Anlaşılırlık nedeniyle sihirli `$this->onChange()` metodundan kaçınmanızı öneririz. Pratik bir alternatif, örneğin [Nette\Utils\Arrays::invoke |arrays#invoke] fonksiyonudur: ```php Nette\Utils\Arrays::invoke($this->onChange, $this, $radius); diff --git a/utils/tr/staticclass.texy b/utils/tr/staticclass.texy new file mode 100644 index 0000000000..e6fa626b85 --- /dev/null +++ b/utils/tr/staticclass.texy @@ -0,0 +1,21 @@ +Statik Sınıflar +*************** + +.[perex] +StaticClass, statik sınıfları işaretlemek için kullanılır. + + +Kurulum: + +```shell +composer require nette/utils +``` + +Statik sınıfları, yani örnek oluşturmak için tasarlanmamış sınıfları, [api:Nette\StaticClass] trait'i ile işaretleyebilirsiniz: + +```php +class Strings +{ + use Nette\StaticClass; +} +``` diff --git a/utils/tr/strings.texy b/utils/tr/strings.texy index 43579b10ed..112735e9a1 100644 --- a/utils/tr/strings.texy +++ b/utils/tr/strings.texy @@ -1,8 +1,8 @@ -Dize İşlevleri -************** +Karakter Dizileriyle Çalışma +**************************** .[perex] -[api:Nette\Utils\Strings] UTF-8 kodlu dizelerle çalışmak için birçok yararlı işlev içeren statik bir sınıftır. +[api:Nette\Utils\Strings], çoğunlukla UTF-8 kodlamasındaki karakter dizileriyle çalışmak için yararlı fonksiyonlar içeren statik bir sınıftır. Kurulum: @@ -11,83 +11,83 @@ Kurulum: composer require nette/utils ``` -Tüm örnekler aşağıdaki sınıf takma adının tanımlandığını varsayar: +Tüm örnekler, bir takma ad oluşturulduğunu varsayar: ```php use Nette\Utils\Strings; ``` -Harf Kutusu .[#toc-letter-case] -=============================== +Harf Büyüklüğünü Değiştirme +=========================== -Bu işlevler `mbstring` PHP uzantısını gerektirir. +Bu fonksiyonlar PHP `mbstring` uzantısını gerektirir. lower(string $s): string .[method] ---------------------------------- -UTF-8 dizesinin tüm karakterlerini küçük harfe dönüştürür. +Bir UTF-8 karakter dizisini küçük harfe dönüştürür. ```php -Strings::lower('Hello world'); // 'hello world' +Strings::lower('Dobrý den'); // 'dobrý den' ``` upper(string $s): string .[method] ---------------------------------- -UTF-8 dizesinin tüm karakterlerini büyük harfe dönüştürür. +Bir UTF-8 karakter dizisini büyük harfe dönüştürür. ```php -Strings::upper('Hello world'); // 'HELLO WORLD' +Strings::upper('Dobrý den'); // 'DOBRÝ DEN' ``` firstUpper(string $s): string .[method] --------------------------------------- -UTF-8 dizesinin ilk karakterini büyük harfe dönüştürür ve diğer karakterleri değiştirmeden bırakır. +Bir UTF-8 karakter dizisinin ilk harfini büyük harfe dönüştürür, diğerlerini değiştirmez. ```php -Strings::firstUpper('hello world'); // 'Hello world' +Strings::firstUpper('dobrý den'); // 'Dobrý den' ``` firstLower(string $s): string .[method] --------------------------------------- -UTF-8 dizesinin ilk karakterini küçük harfe dönüştürür ve diğer karakterleri değiştirmeden bırakır. +Bir UTF-8 karakter dizisinin ilk harfini küçük harfe dönüştürür, diğerlerini değiştirmez. ```php -Strings::firstLower('Hello world'); // 'hello world' +Strings::firstLower('Dobrý den'); // 'dobrý den' ``` capitalize(string $s): string .[method] --------------------------------------- -UTF-8 dizesindeki her sözcüğün ilk karakterini büyük harfe, diğerlerini küçük harfe dönüştürür. +Bir UTF-8 karakter dizisindeki her kelimenin ilk harfini büyük harfe, diğerlerini küçük harfe dönüştürür. ```php -Strings::capitalize('Hello world'); // 'Hello World' +Strings::capitalize('Dobrý den'); // 'Dobrý Den' ``` -Dizeyi Düzenleme .[#toc-editing-a-string] -========================================= +Karakter Dizisini Düzenleme +=========================== normalize(string $s): string .[method] -------------------------------------- -Kontrol karakterlerini kaldırır, satır sonlarını `\n` olarak normalleştirir, baştaki ve sondaki boş satırları kaldırır, satırlardaki son boşlukları keser, UTF-8'i NFC'nin normal biçimine normalleştirir. +Kontrol karakterlerini kaldırır, satır sonlarını `\n` olarak normalleştirir, baştaki ve sondaki boş satırları kırpar, satırlardaki sağdaki boşlukları kırpar, UTF-8'i normal NFC formuna normalleştirir. unixNewLines(string $s): string .[method] ----------------------------------------- -Satır sonlarını Unix sistemlerinde kullanılan `\n` adresine dönüştürür. Satır sonları şunlardır: `\n`, `\r`, `\r\n`, U+2028 satır ayırıcı, U+2029 paragraf ayırıcı. +Satır sonlarını unix sistemlerinde kullanılan `\n` karakterine dönüştürür. Satır sonları şunlardır: `\n`, `\r`, `\r\n`, U+2028 satır ayırıcı, U+2029 paragraf ayırıcı. ```php $unixLikeLines = Strings::unixNewLines($string); @@ -97,42 +97,42 @@ $unixLikeLines = Strings::unixNewLines($string); platformNewLines(string $s): string .[method] --------------------------------------------- -Satır sonlarını geçerli platforma özgü karakterlere dönüştürür, yani Windows'ta `\r\n` ve başka yerlerde `\n`. Satır sonları `\n`, `\r`, `\r\n`, U+2028 satır ayırıcı, U+2029 paragraf ayırıcıdır. +Satır sonlarını mevcut platforma özgü karakterlere dönüştürür, yani Windows'ta `\r\n` ve diğerlerinde `\n`. Satır sonları şunlardır: `\n`, `\r`, `\r\n`, U+2028 satır ayırıcı, U+2029 paragraf ayırıcı. ```php $platformLines = Strings::platformNewLines($string); ``` -webalize(string $s, string $charlist=null, bool $lower=true): string .[method] ------------------------------------------------------------------------------- +webalize(string $s, ?string $charlist=null, bool $lower=true): string .[method] +------------------------------------------------------------------------------- -UTF-8 dizesini URL'de kullanılan biçime dönüştürür, yani aksan işaretlerini kaldırır ve İngilizce alfabenin harfleri ve sayılar dışındaki tüm karakterleri tire ile değiştirir. +Bir UTF-8 karakter dizisini URL'lerde kullanılan forma dönüştürür, yani aksanları kaldırır ve İngiliz alfabesi harfleri ve rakamlar dışındaki tüm karakterleri tire ile değiştirir. ```php -Strings::webalize('žluťoučký kůň'); // 'zlutoucky-kun' +Strings::webalize('náš produkt'); // 'nas-produkt' ``` -Diğer karakterler de korunabilir, ancak bunlar ikinci argüman olarak aktarılmalıdır. +Başka karakterlerin de korunması gerekiyorsa, fonksiyonun ikinci parametresinde belirtilebilirler. ```php -Strings::webalize('10. image_id', '._'); // '10.-image_id' +Strings::webalize('10. obrázek_id', '._'); // '10.-obrazek_id' ``` -Üçüncü bağımsız değişken dizenin küçük harfe dönüştürülmesini engelleyebilir. +Üçüncü parametre ile küçük harfe dönüştürme engellenebilir. ```php -Strings::webalize('Hello world', null, false); // 'Hello-world' +Strings::webalize('Dobrý den', null, false); // 'Dobry-den' ``` .[caution] -PHP uzantısı gerektirir `intl`. +PHP `intl` uzantısını gerektirir. -trim(string $s, string $charlist=null): string .[method] --------------------------------------------------------- +trim(string $s, ?string $charlist=null): string .[method] +--------------------------------------------------------- -UTF-8 kodlu bir dizeden sol ve sağ taraftaki tüm boşlukları (veya ikinci bağımsız değişken olarak aktarılan karakterleri) kaldırır. +Bir UTF-8 karakter dizisinin başından ve sonundan boşlukları (veya ikinci parametre ile belirtilen diğer karakterleri) kırpar. ```php Strings::trim(' Hello '); // 'Hello' @@ -142,21 +142,21 @@ Strings::trim(' Hello '); // 'Hello' truncate(string $s, int $maxLen, string $append=`'…'`): string .[method] ------------------------------------------------------------------------ -Bir UTF-8 dizesini, tüm sözcükleri bölmemeye çalışarak, verilen maksimum uzunlukta keser. Yalnızca dize kesilirse, dizeye bir üç nokta (veya üçüncü bağımsız değişkenle ayarlanan başka bir şey) eklenir. +Bir UTF-8 karakter dizisini belirtilen maksimum uzunluğa kırpar, bu sırada tam kelimeleri korumaya çalışır. Karakter dizisi kısaltılırsa, sonuna üç nokta ekler (üçüncü parametre ile değiştirilebilir). ```php -$text = 'Hello, how are you today?'; -Strings::truncate($text, 5); // 'Hell…' -Strings::truncate($text, 20); // 'Hello, how are you…' -Strings::truncate($text, 30); // 'Hello, how are you today?' -Strings::truncate($text, 20, '~'); // 'Hello, how are you~' +$text = 'Řekněte, jak se máte?'; +Strings::truncate($text, 5); // 'Řekn…' +Strings::truncate($text, 20); // 'Řekněte, jak se…' +Strings::truncate($text, 30); // 'Řekněte, jak se máte?' +Strings::truncate($text, 20, '~'); // 'Řekněte, jak se~' ``` indent(string $s, int $level=1, string $indentationChar=`"\t"`): string .[method] --------------------------------------------------------------------------------- -Çok satırlı bir metni soldan girintiler. İkinci bağımsız değişken kaç girinti karakteri kullanılacağını belirlerken, girintinin kendisi üçüncü bağımsız değişkendir (varsayılan olarak *tab*). +Çok satırlı metni soldan girintiler. Girinti sayısı ikinci parametre ile, ne ile girintileneceği üçüncü parametre ile belirlenir (varsayılan değer tab karakteridir). ```php Strings::indent('Nette'); // "\tNette" @@ -167,7 +167,7 @@ Strings::indent('Nette', 2, '+'); // '++Nette' padLeft(string $s, int $length, string $pad=`' '`): string .[method] -------------------------------------------------------------------- -Bir UTF-8 dizesini, `$pad` dizesinin başına ekleyerek verilen uzunlukta doldurur. +Bir UTF-8 karakter dizisini, `$pad` karakter dizisini soldan tekrarlayarak belirtilen uzunluğa tamamlar. ```php Strings::padLeft('Nette', 6); // ' Nette' @@ -178,7 +178,7 @@ Strings::padLeft('Nette', 8, '+*'); // '+*+Nette' padRight(string $s, int $length, string $pad=`' '`): string .[method] --------------------------------------------------------------------- -`$pad` dizesini sonuna ekleyerek UTF-8 dizesini verilen uzunlukta tamponlar. +Bir UTF-8 karakter dizisini, `$pad` karakter dizisini sağdan tekrarlayarak belirtilen uzunluğa tamamlar. ```php Strings::padRight('Nette', 6); // 'Nette ' @@ -186,10 +186,10 @@ Strings::padRight('Nette', 8, '+*'); // 'Nette+*+' ``` -substring(string $s, int $start, int $length=null): string .[method] --------------------------------------------------------------------- +substring(string $s, int $start, ?int $length=null): string .[method] +--------------------------------------------------------------------- -Başlangıç konumu `$start` ve uzunluğu `$length` ile belirtilen UTF-8 dizesinin bir bölümünü döndürür. `$start` negatifse, döndürülen dize dizenin sonundan itibaren `$start`'inci karakterden başlar. +`$s` UTF-8 karakter dizisinin, `$start` başlangıç konumu ve `$length` uzunluğu ile belirtilen bir bölümünü döndürür. `$start` negatif ise, döndürülen karakter dizisi sondan -`$start` karakteri ile başlar. ```php Strings::substring('Nette Framework', 0, 5); // 'Nette' @@ -201,7 +201,7 @@ Strings::substring('Nette Framework', -4); // 'work' reverse(string $s): string .[method] ------------------------------------ -UTF-8 dizesini tersine çevirir. +Bir UTF-8 karakter dizisini ters çevirir. ```php Strings::reverse('Nette'); // 'etteN' @@ -211,77 +211,77 @@ Strings::reverse('Nette'); // 'etteN' length(string $s): int .[method] -------------------------------- -UTF-8 dizesindeki karakter sayısını (bayt değil) döndürür. +Bir UTF-8 karakter dizisindeki karakter sayısını (bayt sayısını değil) döndürür. Bu, grafem sayısından farklı olabilen Unicode kod noktalarının sayısıdır. ```php -Strings::length('Nette'); // 5 -Strings::length('red'); // 3 +Strings::length('Nette'); // 5 +Strings::length('červená'); // 7 ``` startsWith(string $haystack, string $needle): bool .[method deprecated] ----------------------------------------------------------------------- -`$haystack` dizesinin `$needle` ile başlayıp başlamadığını kontrol eder. +`$haystack` karakter dizisinin `$needle` karakter dizisiyle başlayıp başlamadığını kontrol eder. ```php -$haystack = 'Begins'; -$needle = 'Be'; +$haystack = 'Začíná'; +$needle = 'Za'; Strings::startsWith($haystack, $needle); // true ``` .[note] -Yerel `str_starts_with()`:https://www.php.net/manual/en/function.str-starts-with.php adresini kullanın. +Yerel `str_starts_with()`:https://www.php.net/manual/en/function.str-starts-with.php kullanın. endsWith(string $haystack, string $needle): bool .[method deprecated] --------------------------------------------------------------------- -`$haystack` dizesinin `$needle` ile bitip bitmediğini kontrol eder. +`$haystack` karakter dizisinin `$needle` karakter dizisiyle bitip bitmediğini kontrol eder. ```php -$haystack = 'Ends'; -$needle = 'ds'; +$haystack = 'Končí'; +$needle = 'čí'; Strings::endsWith($haystack, $needle); // true ``` .[note] -Yerel `str_ends_with()`:https://www.php.net/manual/en/function.str-ends-with.php adresini kullanın. +Yerel `str_ends_with()`:https://www.php.net/manual/en/function.str-ends-with.php kullanın. contains(string $haystack, string $needle): bool .[method deprecated] --------------------------------------------------------------------- -`$haystack` dizesinin `$needle` içerip içermediğini kontrol eder. +`$haystack` karakter dizisinin `$needle` içerip içermediğini kontrol eder. ```php -$haystack = 'Contains'; -$needle = 'tai'; +$haystack = 'Posluchárna'; +$needle = 'sluch'; Strings::contains($haystack, $needle); // true ``` .[note] -Yerel `str_contains()`:https://www.php.net/manual/en/function.str-contains.php adresini kullanın. +Yerel `str_contains()`:https://www.php.net/manual/en/function.str-contains.php kullanın. -compare(string $left, string $right, int $length=null): bool .[method] ----------------------------------------------------------------------- +compare(string $left, string $right, ?int $length=null): bool .[method] +----------------------------------------------------------------------- -Karakter durumunu dikkate almadan iki UTF-8 dizgisini veya parçalarını karşılaştırır. `$length` boşsa, tüm dizgiler karşılaştırılır, negatifse, dizgilerin sonundan itibaren karşılık gelen karakter sayısı karşılaştırılır, aksi takdirde başlangıçtan itibaren uygun karakter sayısı karşılaştırılır. +İki UTF-8 karakter dizisini veya bunların bölümlerini büyük/küçük harf duyarlılığı olmadan karşılaştırır. `$length` null içeriyorsa, tüm karakter dizileri karşılaştırılır, negatif ise, karakter dizilerinin sonundan ilgili sayıda karakter karşılaştırılır, aksi takdirde baştan ilgili sayıda karakter karşılaştırılır. ```php Strings::compare('Nette', 'nette'); // true -Strings::compare('Nette', 'next', 2); // true - two first characters match -Strings::compare('Nette', 'Latte', -2); // true - two last characters match +Strings::compare('Nette', 'next', 2); // true - ilk 2 karakter eşleşiyor +Strings::compare('Nette', 'Latte', -2); // true - son 2 karakter eşleşiyor ``` findPrefix(...$strings): string .[method] ----------------------------------------- -Dizelerin ortak önekini bulur veya önek bulunamazsa boş dize döndürür. +Karakter dizilerinin ortak başlangıcını bulur. Veya ortak önek bulunamazsa boş bir karakter dizisi döndürür. ```php Strings::findPrefix('prefix-a', 'prefix-bb', 'prefix-c'); // 'prefix-' @@ -293,7 +293,7 @@ Strings::findPrefix('Nette', 'is', 'great'); // '' before(string $haystack, string $needle, int $nth=1): ?string .[method] ----------------------------------------------------------------------- -`$haystack` öğesinin `$nth` öğesinin `$needle` öğesinden önceki kısmını döndürür veya iğne bulunamazsa `null` öğesini döndürür. Negatif değer, sondan itibaren arama anlamına gelir. +`$haystack` karakter dizisinin, `$needle` karakter dizisinin n'inci `$nth` oluşumundan önceki bölümünü döndürür. Veya `$needle` bulunamazsa `null`. `$nth` negatif bir değerse, karakter dizisinin sonundan aranır. ```php Strings::before('Nette_is_great', '_', 1); // 'Nette' @@ -306,7 +306,7 @@ Strings::before('Nette_is_great', '_', 3); // null after(string $haystack, string $needle, int $nth=1): ?string .[method] ---------------------------------------------------------------------- -`$needle` 'nin `$nth` oluşumundan sonra `$haystack` 'un bir kısmını döndürür veya `$needle` bulunamazsa `null` 'u döndürür. Negatif `$nth` değeri sondan arama anlamına gelir. +`$haystack` karakter dizisinin, `$needle` karakter dizisinin n'inci `$nth` oluşumundan sonraki bölümünü döndürür. Veya `$needle` bulunamazsa `null`. `$nth` negatif bir değerse, karakter dizisinin sonundan aranır. ```php Strings::after('Nette_is_great', '_', 2); // 'great' @@ -319,7 +319,7 @@ Strings::after('Nette_is_great', '_', 3); // null indexOf(string $haystack, string $needle, int $nth=1): ?int .[method] --------------------------------------------------------------------- -`$needle` 'in `$haystack` 'de veya `$needle` bulunamadıysa `null` 'de `$nth` oluşumunun karakter cinsinden konumunu verir. Negatif `$nth` değeri sondan arama anlamına gelir. +`$haystack` karakter dizisindeki `$needle` karakter dizisinin n'inci `$nth` oluşumunun karakter cinsinden konumunu döndürür. Veya `$needle` bulunamazsa `null`. `$nth` negatif bir değerse, karakter dizisinin sonundan aranır. ```php Strings::indexOf('abc abc abc', 'abc', 2); // 4 @@ -328,14 +328,14 @@ Strings::indexOf('abc abc abc', 'd'); // null ``` -Kodlama .[#toc-encoding] -======================== +Kodlama +======= fixEncoding(string $s): string .[method] ---------------------------------------- -Bir dizeden tüm geçersiz UTF-8 karakterlerini kaldırır. +Karakter dizisinden geçersiz UTF-8 karakterlerini kaldırır. ```php $correctStrings = Strings::fixEncoding($string); @@ -345,59 +345,59 @@ $correctStrings = Strings::fixEncoding($string); checkEncoding(string $s): bool .[method deprecated] --------------------------------------------------- -Dizenin UTF-8 kodlamasında geçerli olup olmadığını denetler. +Geçerli bir UTF-8 karakter dizisi olup olmadığını kontrol eder. ```php $isUtf8 = Strings::checkEncoding($string); ``` .[note] -[Nette\Utils\Validator::isUnicode() |validators#isUnicode] işlevini kullanın. +[Nette\Utils\Validator::isUnicode() |validators#isUnicode] kullanın. toAscii(string $s): string .[method] ------------------------------------ -UTF-8 dizesini ASCII'ye dönüştürür, yani aksan işaretlerini vb. kaldırır. +Bir UTF-8 karakter dizisini ASCII'ye dönüştürür, yani aksanları vb. kaldırır. ```php Strings::toAscii('žluťoučký kůň'); // 'zlutoucky kun' ``` .[caution] -PHP uzantısı gerektirir `intl`. +PHP `intl` uzantısını gerektirir. chr(int $code): string .[method] -------------------------------- -Kod noktasından UTF-8'de belirli bir karakteri döndürür (0x0000..D7FF veya 0xE000..10FFFF aralığındaki sayı). +Bir kod noktasından (0x0000..D7FF ve 0xE000..10FFFF aralığında bir sayı) belirli bir UTF-8 karakterini döndürür. ```php -Strings::chr(0xA9); // '©' +Strings::chr(0xA9); // UTF-8 kodlamasında '©' ``` ord(string $char): int .[method] -------------------------------- -UTF-8'de belirli bir karakterin kod noktasını döndürür (0x0000..D7FF veya 0xE000..10FFFF aralığında sayı). +Belirli bir UTF-8 karakterinin kod noktasını döndürür (0x0000..D7FF veya 0xE000..10FFFF aralığında bir sayı). ```php Strings::ord('©'); // 0xA9 ``` -Düzenli İfadeler .[#toc-regular-expressions] -============================================ +Düzenli İfadeler +================ -Strings sınıfı düzenli ifadelerle çalışmak için işlevler sağlar. Yerel PHP işlevlerinin aksine, daha anlaşılır bir API'ye, daha iyi Unicode desteğine ve en önemlisi hata algılamaya sahiptirler. Herhangi bir derleme veya ifade işleme hatası bir `Nette\RegexpException` istisnası fırlatacaktır. +Strings sınıfı, düzenli ifadelerle çalışmak için fonksiyonlar sunar. Yerel PHP fonksiyonlarının aksine, daha anlaşılır bir API'ye, daha iyi Unicode desteğine ve hepsinden önemlisi hata tespitine sahiptirler. İfadenin derlenmesi veya işlenmesi sırasında herhangi bir hata `Nette\RegexpException` istisnası fırlatır. split(string $subject, string $pattern, bool $captureOffset=false, bool $skipEmpty=false, int $limit=-1, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------- -Dizeyi düzenli ifadeye göre dizilere böler. Parantez içindeki ifadeler de yakalanacak ve döndürülecektir. +Bir karakter dizisini düzenli bir ifadeye göre bir diziye böler. Parantez içindeki ifadeler de yakalanacak ve döndürülecektir. ```php Strings::split('hello, world', '~,\s*~'); @@ -407,7 +407,7 @@ Strings::split('hello, world', '~(,)\s*~'); // ['hello', ',', 'world']`` ``` -`$skipEmpty` `true` ise, yalnızca boş olmayan öğeler iade edilecektir: +Eğer `$skipEmpty` `true` ise, yalnızca boş olmayan öğeler döndürülür: ```php Strings::split('hello, world, ', '~,\s*~'); @@ -417,16 +417,16 @@ Strings::split('hello, world, ', '~,\s*~', skipEmpty: true); // ['hello', 'world'] ``` -`$limit` belirtilirse, yalnızca sınıra kadar olan alt dizeler döndürülür ve dizenin geri kalanı son öğeye yerleştirilir. Limitin -1 veya 0 olması limit olmadığı anlamına gelir. +Eğer `$limit` belirtilmişse, yalnızca limite kadar olan alt diziler döndürülür ve karakter dizisinin geri kalanı son öğeye yerleştirilir. -1 veya 0 limiti kısıtlama olmadığı anlamına gelir. ```php Strings::split('hello, world, third', '~,\s*~', limit: 2); // ['hello', 'world, third'] ``` -Eğer `$utf8` `true` ise, değerlendirme Unicode moduna geçer. Bu, `u` değiştiricisini belirtmeye benzer. +Eğer `$utf8` `true` ise, değerlendirme Unicode moduna geçer. `u` değiştiricisini belirtmişsiniz gibi. -Eğer `$captureOffset` `true` ise, oluşan her eşleşme için dizedeki konumu da döndürülür (bayt cinsinden; `$utf8` ayarlanmışsa karakter cinsinden). Bu, dönüş değerini, her bir elemanın eşleşen dize ve konumundan oluşan bir çift olduğu bir diziye dönüştürür. +Eğer `$captureOffset` `true` ise, her eşleşen oluşum için karakter dizisindeki konumu da döndürülür (bayt cinsinden; eğer `$utf8` ayarlanmışsa karakter cinsinden). Bu, dönüş değerini, her öğenin eşleşen karakter dizisi ve konumundan oluşan bir çift olduğu bir diziye değiştirir. ```php Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true); @@ -440,7 +440,7 @@ Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true, utf8: true); match(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $utf8=false): ?array .[method] -------------------------------------------------------------------------------------------------------------------------------------------------- -Dizeyi düzenli ifadeyle eşleşen kısım için arar ve bulunan ifadeyi ve tek tek alt ifadeleri içeren bir dizi veya `null` döndürür. +Bir karakter dizisinde düzenli bir ifadeye uyan bölümü arar ve bulunan ifade ve tek tek alt ifadelerle birlikte bir dizi veya `null` döndürür. ```php Strings::match('hello!', '~\w+(!+)~'); @@ -450,7 +450,7 @@ Strings::match('hello!', '~X~'); // null ``` -`$unmatchedAsNull` `true` ise, eşleşmeyen alt örüntüler null olarak döndürülür; aksi takdirde boş bir dize olarak döndürülür veya döndürülmez: +Eğer `$unmatchedAsNull` `true` ise, yakalanmayan alt desenler null olarak döndürülür; aksi takdirde boş bir karakter dizisi olarak döndürülür veya döndürülmez: ```php Strings::match('hello', '~\w+(!+)?~'); @@ -460,7 +460,7 @@ Strings::match('hello', '~\w+(!+)?~', unmatchedAsNull: true); // ['hello', null] ``` -Eğer `$utf8` `true` ise, değerlendirme Unicode moduna geçer. Bu, `u` değiştiricisini belirtmeye benzer: +Eğer `$utf8` `true` ise, değerlendirme Unicode moduna geçer. `u` değiştiricisini belirtmişsiniz gibi: ```php Strings::match('žlutý kůň', '~\w+~'); @@ -470,9 +470,9 @@ Strings::match('žlutý kůň', '~\w+~', utf8: true); // ['žlutý'] ``` -`$offset` parametresi, aramanın başlatılacağı konumu belirtmek için kullanılabilir (bayt cinsinden; `$utf8` ayarlanmışsa karakter cinsinden). +`$offset` parametresi, aramaya başlanacak konumu belirtmek için kullanılabilir (bayt cinsinden; eğer `$utf8` ayarlanmışsa karakter cinsinden). -Eğer `$captureOffset` `true` ise, oluşan her eşleşme için dizedeki konumu da döndürülür (bayt cinsinden; `$utf8` ayarlanmışsa karakter cinsinden). Bu, dönüş değerini, her bir elemanın eşleşen dize ve ofsetinden oluşan bir çift olduğu bir diziye dönüştürür: +Eğer `$captureOffset` `true` ise, her eşleşen oluşum için karakter dizisindeki konumu da döndürülür (bayt cinsinden; eğer `$utf8` ayarlanmışsa karakter cinsinden). Bu, dönüş değerini, her öğenin eşleşen karakter dizisi ve ofsetinden oluşan bir çift olduğu bir diziye değiştirir: ```php Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true); @@ -483,10 +483,10 @@ Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true, utf8: true); ``` -matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false, bool $lazy=false): array|Generator .[method] +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Dizeyi, düzenli ifadeyle eşleşen tüm oluşumlar için arar ve bulunan ifadeyi ve her bir alt ifadeyi içeren bir dizi dizisi döndürür. +Bir karakter dizisindeki düzenli bir ifadeye uyan tüm oluşumları arar ve bulunan ifade ve tek tek alt ifadelerle birlikte bir dizi dizisi döndürür. ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~'); @@ -496,7 +496,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~'); ] */ ``` -`$patternOrder` `true` ise, sonuçların yapısı değişir, böylece ilk öğe tam kalıp eşleşmelerinin bir dizisi olur, ikincisi parantez içindeki ilk alt kalıba karşılık gelen dizelerin bir dizisi olur ve bu böyle devam eder: +Eğer `$patternOrder` `true` ise, sonuçların yapısı değişir, öyle ki ilk öğe tam desen eşleşmeleri dizisidir, ikinci öğe parantez içindeki ilk alt desene karşılık gelen karakter dizileri dizisidir ve bu şekilde devam eder: ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~', patternOrder: true); @@ -506,7 +506,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~', patternOrder: true); ] */ ``` -`$unmatchedAsNull` `true` ise, eşleşmeyen alt örüntüler null olarak döndürülür; aksi takdirde boş bir dize olarak döndürülür veya döndürülmez: +Eğer `$unmatchedAsNull` `true` ise, yakalanmayan alt desenler null olarak döndürülür; aksi takdirde boş bir karakter dizisi olarak döndürülür veya döndürülmez: ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~', unmatchedAsNull: true); @@ -516,7 +516,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~', unmatchedAsNull: true); ] */ ``` -Eğer `$utf8` `true` ise, değerlendirme Unicode moduna geçer. Bu, `u` değiştiricisini belirtmeye benzer: +Eğer `$utf8` `true` ise, değerlendirme Unicode moduna geçer. `u` değiştiricisini belirtmişsiniz gibi: ```php Strings::matchAll('žlutý kůň', '~\w+~'); @@ -532,9 +532,9 @@ Strings::matchAll('žlutý kůň', '~\w+~', utf8: true); ] */ ``` -`$offset` parametresi, aramanın başlatılacağı konumu belirtmek için kullanılabilir (bayt cinsinden; `$utf8` ayarlanmışsa karakter cinsinden). +`$offset` parametresi, aramaya başlanacak konumu belirtmek için kullanılabilir (bayt cinsinden; eğer `$utf8` ayarlanmışsa karakter cinsinden). -Eğer `$captureOffset` `true` ise, oluşan her eşleşme için dizedeki konumu da döndürülür (bayt cinsinden; `$utf8` ayarlanmışsa karakter cinsinden). Bu, dönüş değerini, her bir elemanın eşleşen dize ve konumundan oluşan bir çift olduğu bir diziye dönüştürür: +Eğer `$captureOffset` `true` ise, her eşleşen oluşum için karakter dizisindeki konumu da döndürülür (bayt cinsinden; eğer `$utf8` ayarlanmışsa karakter cinsinden). Bu, dönüş değerini, her öğenin eşleşen karakter dizisi ve konumundan oluşan bir çift olduğu bir diziye değiştirir: ```php Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true); @@ -550,11 +550,21 @@ Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true, utf8: true); ] */ ``` +Eğer `$lazy` `true` ise, fonksiyon bir dizi yerine bir `Generator` döndürür, bu da büyük karakter dizileriyle çalışırken önemli performans avantajları sağlar. Jeneratör, tüm karakter dizisini bir kerede yerine eşleşmeleri aşamalı olarak aramanıza olanak tanır. Bu, son derece büyük girdi metinleriyle bile verimli bir şekilde çalışmanıza olanak tanır. Ayrıca, aradığınız eşleşmeyi bulduğunuzda işlemi istediğiniz zaman kesebilirsiniz, bu da hesaplama süresinden tasarruf sağlar. + +```php +$matches = Strings::matchAll($largeText, '~\w+~', lazy: true); +foreach ($matches as $match) { + echo "Bulundu: $match[0]\n"; + // İşlem herhangi bir zamanda kesilebilir +} +``` + replace(string $subject, string|array $pattern, string|callable $replacement='', int $limit=-1, bool $captureOffset=false, bool $unmatchedAsNull=false, bool $utf8=false): string .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Düzenli ifadeyle eşleşen tüm oluşumları değiştirir. `$replacement` ya bir değiştirme dizesi maskesi ya da bir geri aramadır. +Düzenli bir ifadeye uyan tüm oluşumları değiştirir. `$replacement` ya bir değiştirme karakter dizisi maskesi ya da bir geri çağırmadır. ```php Strings::replace('hello, world!', '~\w+~', '--'); @@ -564,7 +574,7 @@ Strings::replace('hello, world!', '~\w+~', fn($m) => strrev($m[0])); // 'olleh, dlrow!' ``` -Fonksiyon ayrıca ikinci parametreye `pattern => replacement` şeklinde bir dizi geçirerek çoklu değiştirmelere izin verir: +Fonksiyon ayrıca, ikinci parametrede `pattern => replacement` şeklinde bir dizi ileterek birden fazla değiştirme yapılmasına da olanak tanır: ```php Strings::replace('hello, world!', [ @@ -574,9 +584,9 @@ Strings::replace('hello, world!', [ // '-- --!' ``` -`$limit` parametresi ikame sayısını sınırlar. Limit -1, limit olmadığı anlamına gelir. +`$limit` parametresi yapılan değiştirme sayısını sınırlar. -1 limiti kısıtlama olmadığı anlamına gelir. -Eğer `$utf8` `true` ise, değerlendirme Unicode moduna geçer. Bu, `u` değiştiricisini belirtmeye benzer. +Eğer `$utf8` `true` ise, değerlendirme Unicode moduna geçer. `u` değiştiricisini belirtmişsiniz gibi. ```php Strings::replace('žlutý kůň', '~\w+~', '--'); @@ -586,7 +596,7 @@ Strings::replace('žlutý kůň', '~\w+~', '--', utf8: true); // '-- --' ``` -Eğer `$captureOffset` `true` ise, oluşan her eşleşme için dizedeki konumu (bayt cinsinden; `$utf8` ayarlanmışsa karakter cinsinden) da geri aramaya aktarılır. Bu, aktarılan dizinin biçimini değiştirir; burada her eleman eşleşen dize ve konumundan oluşan bir çifttir. +Eğer `$captureOffset` `true` ise, her eşleşen oluşum için karakter dizisindeki konumu da geri çağırmaya iletilir (bayt cinsinden; eğer `$utf8` ayarlanmışsa karakter cinsinden). Bu, iletilen dizinin biçimini değiştirir, burada her öğe eşleşen karakter dizisi ve konumundan oluşan bir çifttir. ```php Strings::replace( @@ -595,7 +605,7 @@ Strings::replace( function (array $m) { dump($m); return ''; }, captureOffset: true, ); -// dumps [['lut', 2]] a [['k', 8]] +// dumps [['lut', 2]] ve [['k', 8]] Strings::replace( 'žlutý kůň', @@ -604,10 +614,10 @@ Strings::replace( captureOffset: true, utf8: true, ); -// dumps [['žlutý', 0]] a [['kůň', 6]] +// dumps [['žlutý', 0]] ve [['kůň', 6]] ``` -`$unmatchedAsNull` `true` ise, eşleşmeyen alt örüntüler geri aramaya null olarak aktarılır; aksi takdirde boş bir dize olarak aktarılır veya aktarılmaz: +Eğer `$unmatchedAsNull` `true` ise, yakalanmayan alt desenler geri çağırmaya null olarak iletilir; aksi takdirde boş bir karakter dizisi olarak iletilir veya iletilmez: ```php Strings::replace( diff --git a/utils/tr/type.texy b/utils/tr/type.texy index c979ead159..0af0fc96b6 100644 --- a/utils/tr/type.texy +++ b/utils/tr/type.texy @@ -1,8 +1,8 @@ -PHP Türü +PHP Tipi ******** .[perex] -[api:Nette\Utils\Type] bir PHP veri türü sınıfıdır. +[api:Nette\Utils\Type], PHP veri tipleriyle çalışmak için bir sınıftır. Kurulum: @@ -11,7 +11,7 @@ Kurulum: composer require nette/utils ``` -Tüm örnekler aşağıdaki sınıf takma adının tanımlandığını varsayar: +Tüm örnekler, bir takma ad oluşturulduğunu varsayar: ```php use Nette\Utils\Type; @@ -21,7 +21,7 @@ use Nette\Utils\Type; fromReflection($reflection): ?Type .[method] -------------------------------------------- -Statik yöntem, yansımaya dayalı bir Type nesnesi oluşturur. Parametre bir `ReflectionMethod` veya `ReflectionFunction` nesnesi (dönüş değerinin türünü döndürür) ya da bir `ReflectionParameter` veya `ReflectionProperty` nesnesi olabilir. `self` , `static` ve `parent` öğelerini gerçek sınıf adına çözümler. Öznenin türü yoksa, `null` döndürür. +Statik metot, yansımaya dayalı olarak bir Type nesnesi oluşturur. Parametre `ReflectionMethod` veya `ReflectionFunction` nesnesi (dönüş değerinin türünü döndürür) veya `ReflectionParameter` ya da `ReflectionProperty` olabilir. `self`, `static` ve `parent`'ı gerçek sınıf adına çevirir. Konunun bir türü yoksa `null` döndürür. ```php class DemoClass @@ -37,7 +37,7 @@ echo Type::fromReflection($prop); // 'DemoClass' fromString(string $type): Type .[method] ---------------------------------------- -Statik yöntem, metin gösterimine göre Type nesnesini oluşturur. +Statik metot, metin gösterimine göre bir Type nesnesi oluşturur. ```php $type = Type::fromString('Foo|Bar'); @@ -48,10 +48,10 @@ echo $type; // 'Foo|Bar' getNames(): (string|array)[] .[method] -------------------------------------- -Bileşik türü oluşturan alt türlerin dizisini dizeler olarak döndürür. +Bileşik türü oluşturan alt türlerin dizisini karakter dizileri olarak döndürür. ```php -$type = Type::fromString('string|null'); // nebo '?string' +$type = Type::fromString('string|null'); // veya '?string' $type->getNames(); // ['string', 'null'] $type = Type::fromString('(Foo&Bar)|string'); @@ -62,7 +62,7 @@ $type->getNames(); // [['Foo', 'Bar'], 'string'] getTypes(): Type[] .[method] ---------------------------- -Bileşik türü oluşturan alt türlerin dizisini `Type` nesneleri olarak döndürür: +Bileşik türü oluşturan alt türlerin dizisini `ReflectionType` nesneleri olarak döndürür: ```php $type = Type::fromString('string|null'); // or '?string' @@ -99,14 +99,14 @@ echo $type->getSingleName(); // null isSimple(): bool .[method] -------------------------- -Basit bir tür olup olmadığını döndürür. Basit nullable tipler de basit tip olarak kabul edilir: +Basit bir tür olup olmadığını döndürür. Basit null atanabilir türler de basit türler olarak kabul edilir: ```php $type = Type::fromString('string'); $type->isSimple(); // true $type->isUnion(); // false -$type = Type::fromString('?Foo'); // nebo 'Foo|null' +$type = Type::fromString('?Foo'); // veya 'Foo|null' $type->isSimple(); // true $type->isUnion(); // true ``` @@ -115,10 +115,10 @@ $type->isUnion(); // true isUnion(): bool .[method] ------------------------- -Bir birlik türü olup olmadığını döndürür. +Bir birleşim (union) türü olup olmadığını döndürür. ```php -$type = Type::fromString('Foo&Bar'); +$type = Type::fromString('string|int'); $type->isUnion(); // true ``` @@ -126,11 +126,11 @@ $type->isUnion(); // true isIntersection(): bool .[method] -------------------------------- -Bir kesişim türü olup olmadığını döndürür. +Bir kesişim (intersection) türü olup olmadığını döndürür. ```php -$type = Type::fromString('string&int'); +$type = Type::fromString('Foo&Bar'); $type->isIntersection(); // true ``` @@ -138,7 +138,7 @@ $type->isIntersection(); // true isBuiltin(): bool .[method] --------------------------- -Türün hem basit hem de PHP yerleşik türü olup olmadığını döndürür. +Türün basit ve aynı zamanda yerleşik bir PHP türü olup olmadığını döndürür. ```php $type = Type::fromString('string'); @@ -155,7 +155,7 @@ $type->isBuiltin(); // false isClass(): bool .[method] ------------------------- -Türün hem basit hem de bir sınıf adı olup olmadığını döndürür. +Türün basit ve aynı zamanda bir sınıf adı olup olmadığını döndürür. ```php $type = Type::fromString('string'); @@ -172,7 +172,7 @@ $type->isClass(); // false isClassKeyword(): bool .[method] -------------------------------- -Türün `self`, `parent`, `static` dahili türlerinden biri olup olmadığını belirleyin. +Türün `self`, `parent`, `static` dahili türlerinden biri olup olmadığını döndürür. ```php $type = Type::fromString('self'); @@ -186,7 +186,7 @@ $type->isClassKeyword(); // false allows(string $type): bool .[method] ------------------------------------ -`allows()` yöntemi tür uyumluluğunu doğrular. Örneğin, belirli bir türdeki bir değerin parametre olarak geçirilip geçirilemeyeceğini kontrol etmeyi sağlar. +`allows()` metodu tür uyumluluğunu doğrular. Örneğin, belirli bir türdeki bir değerin parametre olarak iletilip iletilemeyeceğini belirlemeye olanak tanır. ```php $type = Type::fromString('string|null'); diff --git a/utils/tr/validators.texy b/utils/tr/validators.texy index 37daf1a341..0598ad7064 100644 --- a/utils/tr/validators.texy +++ b/utils/tr/validators.texy @@ -1,8 +1,8 @@ -Değer Doğrulayıcılar -******************** +Değer Doğrulayıcıları +********************* .[perex] -Bir değişkenin örneğin geçerli bir e-posta adresi içerdiğini hızlı ve kolay bir şekilde doğrulamanız mı gerekiyor? O zaman [api:Nette\Utils\Validators] kullanışlı olacaktır, değerleri doğrulamak için yararlı işlevlere sahip statik bir sınıf. +Bir değişkenin geçerli bir e-posta adresi olup olmadığını hızlı ve kolay bir şekilde doğrulamanız mı gerekiyor? Değerleri doğrulamak için yararlı fonksiyonlar içeren statik bir sınıf olan [api:Nette\Utils\Validators] işinize yarayacaktır. Kurulum: @@ -11,17 +11,17 @@ Kurulum: composer require nette/utils ``` -Tüm örnekler aşağıdaki sınıf takma adının tanımlandığını varsayar: +Tüm örnekler, bir takma ad oluşturulduğunu varsayar: ```php use Nette\Utils\Validators; ``` -Temel Kullanım .[#toc-basic-usage] -================================== +Temel Kullanım +============== -`Validators` sınıfı, kodunuzda kullanmak üzere [isList() |#isList()], [isUnicode |#isUnicode()](), [isEmail |#isEmail()](), [isUrl |#isUrl()]() vb. gibi değerleri doğrulamaya yönelik bir dizi yönteme sahiptir: +Sınıf, kodunuzda kullanmak üzere [#isUnicode()], [#isEmail()], [#isUrl()] vb. gibi değerleri kontrol etmek için bir dizi metoda sahiptir: ```php if (!Validators::isEmail($email)) { @@ -29,7 +29,7 @@ if (!Validators::isEmail($email)) { } ``` -Ayrıca, değerin [beklenen türleri |#expected types] karşılayıp karşılamadığını doğrulayabilir; bu, tek tek seçeneklerin dikey bir çubuk `|` ile ayrıldığı bir dizedir. Bu, [if() |#if()] kullanarak birlik türlerini doğrulamayı kolaylaştırır: +Ayrıca, bir değerin [#beklenen tipler] olup olmadığını doğrulayabilir, bu, bireysel seçeneklerin dikey çizgi `|` ile ayrıldığı bir karakter dizisidir. Böylece [#is()] kullanarak birden fazla türü kolayca doğrulayabiliriz: ```php if (!Validators::is($val, 'int|string|bool')) { @@ -37,81 +37,81 @@ if (!Validators::is($val, 'int|string|bool')) { } ``` -Ancak aynı zamanda beklentileri dizeler halinde yazmanın (örneğin ek açıklamalarda veya yapılandırmada) ve ardından bunlara göre doğrulamanın gerekli olduğu bir sistem oluşturma fırsatı da verir. +Ancak bu aynı zamanda bize, beklentilerin karakter dizileri olarak yazılması gereken (örneğin ek açıklamalarda veya yapılandırmada) ve ardından değerlerin bunlara göre doğrulanması gereken bir sistem oluşturma yeteneği de verir. -Ayrıca, karşılanmadığı takdirde bir istisna fırlatan [assertion |#assert] da bildirebilirsiniz. +Beklenen türler için [#assert()] gereksinimi de uygulanabilir, bu gereksinim karşılanmazsa bir istisna fırlatılır. -Beklenen Türler .[#toc-expected-types] -====================================== +Beklenen Tipler +=============== -Beklenen tipler, dikey bir çubukla ayrılmış bir veya daha fazla varyanttan oluşan bir dizedir `|`, similar to writing types in PHP (ie. `'int|string|bool')`. Nullable gösterime de izin verilir `?int`. +Beklenen tipler, PHP'de türlerin yazıldığına benzer şekilde (örneğin `'int|string|bool'`) dikey çizgi `|` ile ayrılmış bir veya daha fazla varyanttan oluşan bir karakter dizisidir. Null atanabilir gösterim `?int` de kabul edilir. -Tüm elemanları belirli bir türden olan bir dizi `int[]` biçiminde yazılır. +Tüm öğelerin belirli bir türde olduğu diziler `int[]` şeklinde yazılır. -Bazı türlerin ardından iki nokta üst üste işareti ve `:length` uzunluğu veya aralığı gelebilir `:[min]..[max]`örneğin `string:10` (10 bayt uzunluğunda bir dize), `float:10..` (10 ve daha büyük sayı), `array:..10` (on öğeye kadar dizi) veya `list:10..20` (10 ila 20 öğeli liste) veya `pattern:[0-9]+`. +Bazı türlerin ardından iki nokta üst üste ve uzunluk `:length` veya aralık `:[min]..[max]` gelebilir, örn. `string:10` (10 bayt uzunluğunda karakter dizisi), `float:10..` (10 ve daha büyük sayı), `array:..10` (en fazla on öğeli dizi) veya `list:10..20` (10 ila 20 öğeli liste) veya `pattern:[0-9]+` için düzenli ifade. -Türlere ve kurallara genel bakış: +Türlerin ve kuralların özeti: .[wide] -| PHP türleri || +| PHP tipleri || |-------------------------- -| `array` .{width: 140px} | öğe sayısı için aralık verilebilir -| `bool` | -| `float` | değer için aralık verilebilir -| `int` | değer için aralık verilebilir -| `null` | -| `object` | +| `array` .{width: 140px} | öğe sayısı için bir aralık belirtilebilir +| `bool` | +| `float` | değer için bir aralık belirtilebilir +| `int` | değer için bir aralık belirtilebilir +| `null` | +| `object` | | `resource` | -| `scalar` | int\|float\|bool\|string -Bayt cinsinden uzunluk için | `string` | aralığı verilebilir +| `scalar` | int\|float\|bool\|string +| `string` | bayt cinsinden uzunluk için bir aralık belirtilebilir | `callable` | | `iterable` | -| `mixed` | -|------------------------------------------------ +| `mixed` | +|-------------------------- | sözde tipler || |------------------------------------------------ -| `list` | [dizinlenmiş dizi |#isList], öğe sayısı için aralık verilebilir -| `none` | boş değer: `''`, `null`, `false` -| `number` | int\|float -| `numeric` | [metinsel gösterim dahil sayı |#isNumeric] -| `numericint`| [metinsel gösterim dahil tamsayı |#isNumericInt] -| `unicode` | [UTF-8 dizesi |#isUnicode], karakter cinsinden uzunluk için aralık verilebilir -|------------------------------------------------ -| karakter sınıfı (boş bir dize olamaz) || -|------------------------------------------------ -| `alnum` | tüm karakterler alfanümeriktir -| `alpha` | tüm karakterler harftir `[A-Za-z]` -| `digit` | tüm karakterler rakamdır -| `lower` | tüm karakterler küçük harftir `[a-z]` -| `space` | tüm karakterler boşluktur -| `upper` | tüm karakterler büyük harftir `[A-Z]` -| `xdigit` | tüm karakterler onaltılık basamaklardır `[0-9A-Fa-f]` +| `list` | indeksli dizi, öğe sayısı için bir aralık belirtilebilir +| `none` | boş değer: `''`, `null`, `false` +| `number` | int\|float +| `numeric` | [metin gösterimi dahil sayı |#isNumeric] +| `numericint`| [metin gösterimi dahil tamsayı |#isNumericInt] +| `unicode` | [UTF-8 karakter dizisi |#isUnicode], karakter cinsinden uzunluk için bir aralık belirtilebilir +|-------------------------- +| karakter sınıfı (boş karakter dizisi olamaz) || |------------------------------------------------ +| `alnum` | tüm karakterler alfanümeriktir +| `alpha` | tüm karakterler harftir `[A-Za-z]` +| `digit` | tüm karakterler rakamdır +| `lower` | tüm karakterler küçük harftir `[a-z]` +| `space` | tüm karakterler boşluktur +| `upper` | tüm karakterler büyük harftir `[A-Z]` +| `xdigit` | tüm karakterler onaltılık rakamlardır `[0-9A-Fa-f]` +|-------------------------- | sözdizimi doğrulaması || |------------------------------------------------ -| `pattern` | **tüm** dizenin eşleşmesi gereken bir düzenli ifade -| `email` | [E-posta |#isEmail] +| `pattern` | **tüm** karakter dizisinin eşleşmesi gereken düzenli ifade +| `email` | [E-posta |#isEmail] | `identifier`| [PHP tanımlayıcısı |#isPhpIdentifier] -| `url` | [URL |#isUrl] -| `uri` | [URI |#isUri] -|------------------------------------------------ -| ortam doğrulama || +| `url` | [URL |#isUrl] +| `uri` | [URI |#isUri] +|-------------------------- +| ortam doğrulaması || |------------------------------------------------ -| `class` | mevcut sınıftır -| `interface` | mevcut arayüzdür -| `directory` | mevcut dizindir -| `file` | mevcut dosya +| `class` | mevcut bir sınıftır +| `interface` | mevcut bir arayüzdür +| `directory` | mevcut bir dizindir +| `file` | mevcut bir dosyadır -İddia .[#toc-assertion] -======================= +Doğrulama İfadeleri (Assertions) +================================ assert($value, string $expected, string $label='variable'): void .[method] -------------------------------------------------------------------------- -Değerin boru ile ayrılmış [beklenen türlerden |#expected types] olduğunu doğrular. Değilse, [api:Nette\Utils\AssertionException] istisnasını atar. İstisna mesajındaki `variable` kelimesi `$label` parametresi ile değiştirilebilir. +Değerin, dikey çizgi ile ayrılmış [beklenen tiplerden |#Beklenen Tipler] biri olup olmadığını doğrular. Değilse, [api:Nette\Utils\AssertionException] istisnası fırlatır. İstisna metnindeki `variable` kelimesi `$label` parametresiyle başka bir kelimeyle değiştirilebilir. ```php Validators::assert('Nette', 'string:5'); // OK @@ -120,10 +120,10 @@ Validators::assert('Lorem ipsum dolor sit', 'string:78'); ``` -assertField(array $array, string|int $key, string $expected=null, string $label=null): void .[method] ------------------------------------------------------------------------------------------------------ +assertField(array $array, string|int $key, ?string $expected=null, ?string $label=null): void .[method] +------------------------------------------------------------------------------------------------------- -`$array` dizisindeki `$key` öğesinin boru ile ayrılmış [beklenen türlerden |#expected types] olduğunu doğrular. Değilse, [api:Nette\Utils\AssertionException] istisnasını atar. İstisna mesajındaki `item '%' in array` dizesi `$label` parametresi ile değiştirilebilir. +`$array` dizisindeki `$key` anahtarı altındaki öğenin, dikey çizgi ile ayrılmış [beklenen tiplerden |#Beklenen Tipler] biri olup olmadığını doğrular. Değilse, [api:Nette\Utils\AssertionException] istisnası fırlatır. İstisna metnindeki `item '%' in array` karakter dizisi `$label` parametresiyle başka bir karakter dizisiyle değiştirilebilir. ```php $arr = ['foo' => 'Nette']; @@ -136,19 +136,19 @@ Validators::assertField($arr, 'foo', 'int'); ``` -Doğrulayıcılar .[#toc-validators] -================================= +Doğrulayıcılar +============== is($value, string $expected): bool .[method] -------------------------------------------- -Değerin boru ile ayrılmış [beklenen türlerden |#expected types] olup olmadığını kontrol eder. +Değerin, dikey çizgi ile ayrılmış [beklenen tiplerden |#Beklenen Tipler] biri olup olmadığını doğrular. ```php Validators::is(1, 'int|float'); // true Validators::is(23, 'int:0..10'); // false -Validators::is('Nette Framework', 'string:15'); // true, length is 15 bytes +Validators::is('Nette Framework', 'string:15'); // true, uzunluk 15 bayttır Validators::is('Nette Framework', 'string:8..'); // true Validators::is('Nette Framework', 'string:30..40'); // false ``` @@ -157,7 +157,7 @@ Validators::is('Nette Framework', 'string:30..40'); // false isEmail(mixed $value): bool .[method] ------------------------------------- -Değerin geçerli bir e-posta adresi olduğunu doğrular. Alan adının gerçekten var olduğunu doğrulamaz, yalnızca sözdizimi doğrulanır. İşlev, unicode da olabilen gelecekteki [TLD'leri |https://en.wikipedia.org/wiki/Top-level_domain] de sayar. +Değerin geçerli bir e-posta adresi olup olmadığını doğrular. Alan adının gerçekten var olup olmadığı kontrol edilmez, yalnızca sözdizimi kontrol edilir. Fonksiyon, unicode olabilecek gelecekteki [TLD|https://cs.wikipedia.org/wiki/Doména_nejvyššího_řádu]'leri de hesaba katar. ```php Validators::isEmail('example@nette.org'); // true @@ -169,7 +169,7 @@ Validators::isEmail('nette'); // false isInRange(mixed $value, array $range): bool .[method] ----------------------------------------------------- -Değerin verilen aralıkta olup olmadığını kontrol eder `[min, max]`burada üst veya alt sınır atlanabilir (`null`). Sayılar, dizeler ve DateTime nesneleri karşılaştırılabilir. +Değerin verilen `[min, max]` aralığında olup olmadığını doğrular, burada üst veya alt sınır atlanabilir (`null`). Sayılar, karakter dizileri ve DateTime nesneleri karşılaştırılabilir. Her iki sınır da eksikse (`[null, null]`) veya değer `null` ise, `false` döndürür. @@ -184,7 +184,7 @@ Validators::isInRange(1, [5]); // false isNone(mixed $value): bool .[method] ------------------------------------ -Değerin `0`, `''`, `false` veya `null` olup olmadığını kontrol eder. +Değerin `0`, `''`, `false` veya `null` olup olmadığını doğrular. ```php Validators::isNone(0); // true @@ -198,7 +198,7 @@ Validators::isNone('nette'); // false isNumeric(mixed $value): bool .[method] --------------------------------------- -Değerin bir sayı mı yoksa bir dize içinde yazılmış bir sayı mı olduğunu kontrol eder. +Değerin bir sayı veya bir karakter dizisinde yazılmış bir sayı olup olmadığını doğrular. ```php Validators::isNumeric(23); // true @@ -213,7 +213,7 @@ Validators::isNumeric('1e6'); // false isNumericInt(mixed $value): bool .[method] ------------------------------------------ -Değerin bir tamsayı mı yoksa bir dize içinde yazılmış bir tamsayı mı olduğunu kontrol eder. +Değerin bir tamsayı veya bir karakter dizisinde yazılmış bir tamsayı olup olmadığını doğrular. ```php Validators::isNumericInt(23); // true @@ -227,7 +227,7 @@ Validators::isNumericInt('nette'); // false isPhpIdentifier(string $value): bool .[method] ---------------------------------------------- -Değerin PHP'de sözdizimsel olarak geçerli bir tanımlayıcı olup olmadığını denetler, örneğin sınıf, yöntem, işlev vb. isimleri için. +Değerin PHP'de sözdizimsel olarak geçerli bir tanımlayıcı olup olmadığını doğrular, örneğin sınıf, metot, fonksiyon adları vb. için. ```php Validators::isPhpIdentifier(''); // false @@ -240,7 +240,7 @@ Validators::isPhpIdentifier('one two'); // false isBuiltinType(string $type): bool .[method] ------------------------------------------- -`$type` PHP yerleşik türü olup olmadığını belirler. Aksi takdirde, sınıf adıdır. +`$type`'ın yerleşik bir PHP türü olup olmadığını belirler. Aksi takdirde, bir sınıf adıdır. ```php Validators::isBuiltinType('string'); // true @@ -251,7 +251,7 @@ Validators::isBuiltinType('Foo'); // false isTypeDeclaration(string $type): bool .[method] ----------------------------------------------- -Tür bildiriminin sözdizimsel olarak doğru olup olmadığını denetler. +Verilen tür bildiriminin sözdizimsel olarak geçerli olup olmadığını kontrol eder. ```php Validators::isTypeDeclaration('?string'); // true @@ -268,7 +268,7 @@ Validators::isTypeDeclaration('(A|B)'); // false isClassKeyword(string $type): bool .[method] -------------------------------------------- -`$type` adresinin `self`, `parent`, `static` dahili türlerinden biri olup olmadığını belirleyin. +`$type`'ın `self`, `parent`, `static` dahili türlerinden biri olup olmadığını belirler. ```php Validators::isClassKeyword('self'); // true @@ -279,7 +279,7 @@ Validators::isClassKeyword('Foo'); // false isUnicode(mixed $value): bool .[method] --------------------------------------- -Değerin geçerli bir UTF-8 dizesi olup olmadığını kontrol eder. +Değerin geçerli bir UTF-8 karakter dizisi olup olmadığını doğrular. ```php Validators::isUnicode('nette'); // true @@ -291,7 +291,7 @@ Validators::isUnicode("\xA0"); // false isUrl(mixed $value): bool .[method] ----------------------------------- -Değerin geçerli bir URL adresi olup olmadığını kontrol eder. +Değerin geçerli bir URL adresi olup olmadığını doğrular. ```php Validators::isUrl('https://nette.org:8080/path?query#fragment'); // true @@ -306,7 +306,7 @@ Validators::isUrl('nette.org'); // false isUri(string $value): bool .[method] ------------------------------------ -Değerin geçerli bir URI adresi olduğunu, yani aslında sözdizimsel olarak geçerli bir şema ile başlayan bir dize olduğunu doğrular. +Değerin geçerli bir URI adresi olup olmadığını doğrular, yani aslında sözdizimsel olarak geçerli bir şema ile başlayan bir karakter dizisi. ```php Validators::isUri('https://nette.org'); // true diff --git a/utils/uk/@home.texy b/utils/uk/@home.texy index da37bd7e7d..7db5ba0404 100644 --- a/utils/uk/@home.texy +++ b/utils/uk/@home.texy @@ -1,42 +1,46 @@ -Інструменти +Nette Utils *********** .[perex] -У пакеті `nette/utils` ви знайдете набір корисних класів для повсякденного використання: +У пакеті `nette/utils` ви знайдете набір корисних класів для щоденного використання: -| [Валідація змінних |validators] | Nette\Utils\Validators | [Callback |Callback] | Nette\Utils\Callback +| [Дата та час |datetime] | Nette\Utils\DateTime | [Finder |Finder] | Nette\Utils\Finder -| [Floats |Floats] | Nette\Utils\Floats -| [Генерація випадкових рядків |random] | Nette\Utils\Random -| [Дата і час |datetime] | Nette\Utils\DateTime +| [HTML елементи |html-elements] | Nette\Utils\Html +| [Ітератори |iterables] | Nette\Utils\Iterables +| [JSON |json] | Nette\Utils\Json +| [Випадкові рядки |random] | Nette\Utils\Random | [Зображення |images] | Nette\Utils\Image -| [Модель об'єкта |smartobject] | Nette\SmartObject & Nette\StaticClass -| [Пагінація |paginator] | Nette\Utils\Paginator -| [Парсинг і генерація JSON |json] | Nette\Utils\Json -| [Поле |arrays] | Nette\Utils\Arrays +| [PHP рефлексія |reflection] | Nette\Utils\Reflection +| [PHP типи |type] | Nette\Utils\Type +| [Масиви |arrays] | Nette\Utils\Arrays +| [Допоміжні функції |helpers] | Nette\Utils\Helpers +| [Порівняння float |floats] | Nette\Utils\Floats | [Рядки |strings] | Nette\Utils\Strings -| [Типи |type] | Nette\Utils\Type | [Файлова система |filesystem] | Nette\Utils\FileSystem -| [Функції-помічники |helpers] | Nette\Utils\Helpers -| [Елементи HTML |html-elements] | Nette\Utils\Html -| [PHP Відображення |reflection] | Nette\Utils\Reflection +| [Пагінація |paginator] | Nette\Utils\Paginator +| [SmartObject |SmartObject] & [StaticClass |StaticClass] | Nette\SmartObject & Nette\StaticClass +| [Валідатор |validators] | Nette\Utils\Validators -Установка ---------- +Встановлення +------------ -Завантажте та встановіть бібліотеку за допомогою [Composer |best-practices:composer]: +Завантажте та встановіть бібліотеку за допомогою [Composer|best-practices:composer]: ```shell composer require nette/utils ``` -| Версія, сумісна з PHP +| версія | сумісна з PHP |-----------|------------------- -| Nette Utils 4.0 | PHP 8.0 - 8.2 -| Nette Utils 3.2 | PHP 7.2 - 8.2 -| Nette Utils 3.0 - 3.1 | PHP 7.1 - 8.0 -| Nette Utils 2.5 | PHP 5.6 - 8.0 +| Nette Utils 4.0 | PHP 8.0 – 8.4 +| Nette Utils 3.2 | PHP 7.2 – 8.3 +| Nette Utils 3.0 – 3.1 | PHP 7.1 – 8.0 +| Nette Utils 2.5 | PHP 5.6 – 8.0 + +Застосовується до останньої версії патча. + -Дійсний для останньої версії патча. +Якщо ви оновлюєте пакет до новішої версії, перегляньте сторінку [оновлення|en:upgrading]. diff --git a/utils/uk/@left-menu.texy b/utils/uk/@left-menu.texy index 6f4022e2d2..f8964b69c7 100644 --- a/utils/uk/@left-menu.texy +++ b/utils/uk/@left-menu.texy @@ -1,26 +1,28 @@ -Пакет nette/utils -***************** -- [Валідатор |validators] +Nette Utils +*********** +- [Callback |callback] +- [Дата та час |datetime] - [Finder |Finder] - [Floats |Floats] +- [HTML елементи |html-elements] +- [Ітератори |iterables] - [JSON |JSON] -- [Дата та час |datetime] +- [Випадкові рядки |random] - [Зображення |images] -- [Зворотні виклики |callback] - [Пагінатор |paginator] -- [Поле |arrays] -- [Випадкові рядки |random] +- [PHP Рефлексія |reflection] +- [PHP типи |type] +- [Масиви |arrays] +- [Допоміжні функції |helpers] - [Рядки |strings] -- [Типи PHP |type] +- [SmartObject |SmartObject] +- [StaticClass |StaticClass] - [Файлова система |filesystem] -- [Функції-помічники |helpers] -- [Елементи HTML |html-elements] -- [PHP Відображення |reflection] -- [SmartObject |smartobject] +- [Валідатор |validators] Інші інструменти **************** -- [НЕОН |neon:] +- [NEON|neon:] - [Хешування паролів |security:passwords] -- [Розбір і згортання URL-адрес |http:urls] +- [Парсинг та складання URL |http:urls] diff --git a/utils/uk/@meta.texy b/utils/uk/@meta.texy new file mode 100644 index 0000000000..96e2d9752a --- /dev/null +++ b/utils/uk/@meta.texy @@ -0,0 +1 @@ +{{sitename: Документація Nette}} diff --git a/utils/uk/arrays.texy b/utils/uk/arrays.texy index 52b1e384ab..2a0e7fc547 100644 --- a/utils/uk/arrays.texy +++ b/utils/uk/arrays.texy @@ -1,8 +1,8 @@ -Робота з полем -************** +Робота з масивами +***************** .[perex] -Ця сторінка присвячена класам [Nette\Utils\Arrays |#Arrays], [ArrayHash |#ArrayHash] і [ArrayList |#ArrayList], які пов'язані з масивами. +Ця сторінка присвячена класам [Nette\Utils\Arrays |#Arrays], [#ArrayHash] та [#ArrayList], які стосуються масивів. Встановлення: @@ -12,22 +12,63 @@ composer require nette/utils ``` -Масиви .[#toc-arrays] -===================== +Arrays +====== -[api:Nette\Utils\Arrays] це статичний клас, що містить корисні функції для роботи з масивами. +[api:Nette\Utils\Arrays] — це статичний клас, що містить корисні функції для роботи з масивами. Його аналогом для ітераторів є [Nette\Utils\Iterables|iterables]. -У наступних прикладах передбачається, що псевдонім уже створено: +Наступні приклади передбачають створений псевдонім: ```php use Nette\Utils\Arrays; ``` +associate(array $array, mixed $path): array|\stdClass .[method] +--------------------------------------------------------------- + +Функція гнучко перетворює масив `$array` на асоціативний масив або об'єкти відповідно до вказаного шляху `$path`. Шлях може бути рядком або масивом. Він складається з назв ключів вхідного масиву та операторів, таких як '[]', '->', '=', та '|'. Викликає `Nette\InvalidArgumentException`, якщо шлях недійсний. + +```php +// перетворення на асоціативний масив за простим ключем +$arr = [ + ['name' => 'John', 'age' => 11], + ['name' => 'Mary', 'age' => null], + // ... +]; +$result = Arrays::associate($arr, 'name'); +// $result = ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// присвоєння значень з одного ключа іншому за допомогою оператора = +$result = Arrays::associate($arr, 'name=age'); // або ['name', '=', 'age'] +// $result = ['John' => 11, 'Mary' => null, ...] +``` + +```php +// створення об'єкта за допомогою оператора -> +$result = Arrays::associate($arr, '->name'); // або ['->', 'name'] +// $result = (object) ['John' => ['name' => 'John', 'age' => 11], 'Mary' => ['name' => 'Mary', 'age' => null]] +``` + +```php +// комбінація ключів за допомогою оператора | +$result = Arrays::associate($arr, 'name|age'); // або ['name', '|', 'age'] +// $result: ['John' => ['name' => 'John', 'age' => 11], 'Paul' => ['name' => 'Paul', 'age' => 44]] +``` + +```php +// додавання до масиву за допомогою [] +$result = Arrays::associate($arr, 'name[]'); // або ['name', '[]'] +// $result: ['John' => [['name' => 'John', 'age' => 22], ['name' => 'John', 'age' => 11]]] +``` + + contains(array $array, $value): bool .[method] ---------------------------------------------- -Перевіряє масив на наявність значення. Використовує суворе порівняння (`===`). +Перевіряє масив на наявність значення. Використовує строге порівняння (`===`). ```php Arrays::contains([1, 2, 3], 1); // true @@ -35,10 +76,10 @@ Arrays::contains(['1', false], 1); // false ``` -every(iterable $array, callable $callback): bool .[method] ----------------------------------------------------------- +every(array $array, callable $predicate): bool .[method] +-------------------------------------------------------- -Перевіряє, чи всі елементи в масиві проходять тест, реалізований у `$callback` з сигнатурою `function ($value, $key, array $array): bool`. +Перевіряє, чи всі елементи масиву проходять тест, реалізований у `$predicate` із сигнатурою `function ($value, $key, array $array): bool`. ```php $array = [1, 30, 39, 29, 10, 13]; @@ -46,24 +87,59 @@ $isBelowThreshold = fn($value) => $value < 40; $res = Arrays::every($array, $isBelowThreshold); // true ``` -Див. [some() |#some]. +Див. [#some()]. -first(array $array): mixed .[method] ------------------------------------- +filter(array $array, callable $predicate): array .[method]{data-version:4.0.4} +------------------------------------------------------------------------------ + +Повертає новий масив, що містить усі пари ключ-значення, які відповідають заданому предикату. Callback має сигнатуру `function ($value, int|string $key, array $array): bool`. + +```php +Arrays::filter( + ['a' => 1, 'b' => 2, 'c' => 3], + fn($v) => $v < 3, +); +// ['a' => 1, 'b' => 2] +``` + + +first(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------- + +Повертає перший елемент (що відповідає предикату, якщо він вказаний). Якщо такого елемента не існує, повертає результат виклику `$else` або null. Параметр `$predicate` має сигнатуру `function ($value, int|string $key, array $array): bool`. -Повертає перший запис із масиву або null, якщо масив порожній. Не змінює внутрішній покажчик, на відміну від `reset()`. +Не змінює внутрішній покажчик на відміну від `reset()`. Параметри `$predicate` та `$else` існують з версії 4.0.4. ```php -Arrays::first([1, 2, 3]); // 1 -Arrays::first([]); // null +Arrays::first([1, 2, 3]); // 1 +Arrays::first([1, 2, 3], fn($v) => $v > 2); // 3 +Arrays::first([]); // null +Arrays::first([], else: fn() => false); // false ``` +Див. [#last()]. + + +firstKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +------------------------------------------------------------------------------------------------ + +Повертає ключ першого елемента (що відповідає предикату, якщо він вказаний) або null, якщо такого елемента не існує. Предикат `$predicate` має сигнатуру `function ($value, int|string $key, array $array): bool`. + +```php +Arrays::firstKey([1, 2, 3]); // 0 +Arrays::firstKey([1, 2, 3], fn($v) => $v > 2); // 2 +Arrays::firstKey(['a' => 1, 'b' => 2]); // 'a' +Arrays::firstKey([]); // null +``` + +Див. [#lastKey()]. + flatten(array $array, bool $preserveKeys=false): array .[method] ---------------------------------------------------------------- -Консолідує багаторівневий масив у плоский. +Об'єднує багаторівневий масив у плоский. ```php $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); @@ -71,26 +147,26 @@ $array = Arrays::flatten([1, 2, [3, 4, [5, 6]]]); ``` -get(array $array, string|int|array $key, mixed $default=null): mixed .[method] ------------------------------------------------------------------------------- +get(array $array, string|int|array $key, ?mixed $default=null): mixed .[method] +------------------------------------------------------------------------------- -Повертає елемент `$array[$key]`. Якщо він не існує, то або видається виняток `Nette\InvalidArgumentException`, або, якщо задано третій параметр `$default`, то повертається цей параметр. +Повертає елемент `$array[$key]`. Якщо він не існує, викликає виняток `Nette\InvalidArgumentException`, або, якщо вказано третій параметр `$default`, повертає його. ```php -// якщо $array['foo'] не існує, згенерувати виключення +// якщо $array['foo'] не існує, викликає виняток $value = Arrays::get($array, 'foo'); -// якщо $array['foo'] не існує, повернути 'bar' +// якщо $array['foo'] не існує, повертає 'bar' $value = Arrays::get($array, 'foo', 'bar'); ``` -Ключ `$key` також може бути масивом. +Ключем `$key` може бути і масив. ```php $array = ['color' => ['favorite' => 'red'], 5]; $value = Arrays::get($array, ['color', 'favorite']); -// vrátí 'red' +// повертає 'red' ``` @@ -104,29 +180,29 @@ $valueRef = & Arrays::getRef($array, 'foo'); // повертає посилання на $array['foo'] ``` -Як і функція [get() |#get], вона може працювати з багатовимірними масивами. +Так само, як функція [#get()], вміє працювати з багатовимірними масивами. ```php $value = & Arrays::getRef($array, ['color', 'favorite']); -// отримати посилання на $array['color']['favorite'] +// повертає посилання на $array['color']['favorite'] ``` grep(array $array, string $pattern, bool $invert=false): array .[method] ------------------------------------------------------------------------ -Повертає тільки ті елементи масиву, значення яких збігається з регулярним виразом `$pattern`. Якщо `$invert` дорівнює `true`, то повертає елементи, які не збігаються. Помилка компіляції або обробки виразу викликає виключення `Nette\RegexpException`. +Повертає лише ті елементи масиву, значення яких відповідають регулярному виразу `$pattern`. Якщо `$invert` дорівнює `true`, повертає, навпаки, елементи, які не відповідають. Помилка під час компіляції або обробки виразу викликає виняток `Nette\RegexpException`. ```php $filteredArray = Arrays::grep($array, '~^\d+$~'); -// повертає тільки елементи масиву, що складаються з цифр +// повертає лише елементи масиву, що складаються з цифр ``` insertAfter(array &$array, string|int|null $key, array $inserted): void .[method] --------------------------------------------------------------------------------- -Вставляє вміст поля `$inserted` у поле `$array` відразу після елемента з ключем `$key`. Якщо `$key` є `null` (або відсутнє в полі), воно вставляється в кінець. +Вставляє вміст масиву `$inserted` у масив `$array` одразу після елемента з ключем `$key`. Якщо `$key` дорівнює `null` (або його немає в масиві), вставляється в кінець. ```php $array = ['first' => 10, 'second' => 20]; @@ -138,7 +214,7 @@ Arrays::insertAfter($array, 'first', ['hello' => 'world']); insertBefore(array &$array, string|int|null $key, array $inserted): void .[method] ---------------------------------------------------------------------------------- -Вставляє вміст поля `$inserted` у поле `$array` перед елементом із ключем `$key`. Якщо `$key` є `null` (або відсутнє в полі), воно вставляється на початок. +Вставляє вміст масиву `$inserted` у масив `$array` перед елементом з ключем `$key`. Якщо `$key` дорівнює `null` (або його немає в масиві), вставляється на початок. ```php $array = ['first' => 10, 'second' => 20]; @@ -150,7 +226,7 @@ Arrays::insertBefore($array, 'first', ['hello' => 'world']); invoke(iterable $callbacks, ...$args): array .[method] ------------------------------------------------------ -Викликає всі зворотні виклики і повертає масив результатів. +Викликає всі callback'и та повертає масив результатів. ```php $callbacks = [ @@ -166,7 +242,7 @@ $array = Arrays::invoke($callbacks, 5, 11); invokeMethod(iterable $objects, string $method, ...$args): array .[method] -------------------------------------------------------------------------- -Викликає метод на кожному об'єкті в масиві та повертає масив результатів. +Викликає метод для кожного об'єкта в масиві та повертає масив результатів. ```php $objects = ['a' => $obj1, 'b' => $obj2]; @@ -179,7 +255,7 @@ $array = Arrays::invokeMethod($objects, 'foo', 1, 2); isList(array $array): bool .[method] ------------------------------------ -Перевіряє, чи індексований масив зростаючим рядом числових ключів, починаючи з нуля, тобто списком. +Перевіряє, чи масив індексований за зростаючою послідовністю числових ключів від нуля, тобто є списком (list). ```php Arrays::isList(['a', 'b', 'c']); // true @@ -188,21 +264,42 @@ Arrays::isList(['a' => 1, 'b' => 2]); // false ``` -last(array $array): mixed .[method] ------------------------------------ +last(array $array, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------ + +Повертає останній елемент (що відповідає предикату, якщо він вказаний). Якщо такого елемента не існує, повертає результат виклику `$else` або null. Параметр `$predicate` має сигнатуру `function ($value, int|string $key, array $array): bool`. + +Не змінює внутрішній покажчик на відміну від `end()`. Параметри `$predicate` та `$else` існують з версії 4.0.4. + +```php +Arrays::last([1, 2, 3]); // 3 +Arrays::last([1, 2, 3], fn($v) => $v < 3); // 2 +Arrays::last([]); // null +Arrays::last([], else: fn() => false); // false +``` + +Див. [#first()]. + -Повертає останній запис масиву або null, якщо масив порожній. Не змінює внутрішній покажчик, на відміну від `end()`. +lastKey(array $array, ?callable $predicate=null): int|string|null .[method]{data-version:4.0.4} +----------------------------------------------------------------------------------------------- + +Повертає ключ останнього елемента (що відповідає предикату, якщо він вказаний) або null, якщо такого елемента не існує. Предикат `$predicate` має сигнатуру `function ($value, int|string $key, array $array): bool`. ```php -Arrays::last([1, 2, 3]); // 3 -Arrays::last([]); // null +Arrays::lastKey([1, 2, 3]); // 2 +Arrays::lastKey([1, 2, 3], fn($v) => $v < 3); // 1 +Arrays::lastKey(['a' => 1, 'b' => 2]); // 'b' +Arrays::lastKey([]); // null ``` +Див. [#firstKey()]. + -map(iterable $array, callable $callback): array .[method] +map(array $array, callable $transformer): array .[method] --------------------------------------------------------- -Викликає `$callback` на всіх елементах масиву і повертає масив значень, що повертаються. Зворотний виклик має сигнатуру `function ($value, $key, array $array): bool`. +Викликає `$transformer` для всіх елементів масиву та повертає масив повернутих значень. Callback має сигнатуру `function ($value, $key, array $array): mixed`. ```php $array = ['foo', 'bar', 'baz']; @@ -211,10 +308,24 @@ $res = Arrays::map($array, fn($value) => $value . $value); ``` +mapWithKeys(array $array, callable $transformer): array .[method] +----------------------------------------------------------------- + +Створює новий масив шляхом трансформації значень та ключів вихідного масиву. Функція `$transformer` має сигнатуру `function ($value, $key, array $array): ?array{$newKey, $newValue}`. Якщо `$transformer` повертає `null`, елемент пропускається. Для збережених елементів перший елемент повернутого масиву використовується як новий ключ, а другий елемент — як нове значення. + +```php +$array = ['a' => 1, 'b' => 2]; +$result = Arrays::mapWithKeys($array, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +Цей метод корисний у ситуаціях, коли потрібно змінити структуру масиву (ключі та значення одночасно) або фільтрувати елементи під час трансформації (повертаючи null для небажаних елементів). + + mergeTree(array $array1, array $array2): array .[method] -------------------------------------------------------- -Рекурсивно об'єднує два поля. Це корисно, наприклад, для об'єднання деревоподібних структур. При об'єднанні він слідує тим самим правилам, що й оператор `+`, який застосовується до масивів, тобто додає пару ключ/значення з другого масиву в перший масив і залишає значення з першого масиву в разі зіткнення ключів. +Рекурсивно об'єднує два масиви. Це корисно, наприклад, для об'єднання деревоподібних структур. При об'єднанні керується тими ж правилами, що й оператор `+`, застосований до масивів, тобто до першого масиву додає пари ключ/значення з другого масиву, а в разі колізії ключів залишає значення з першого масиву. ```php $array1 = ['color' => ['favorite' => 'red'], 5]; @@ -224,13 +335,13 @@ $array = Arrays::mergeTree($array1, $array2); // $array = ['color' => ['favorite' => 'red', 'blue'], 5]; ``` -Значення з другого масиву завжди додаються до кінця першого. Зникнення значення `10` з другого поля може здатися трохи незрозумілим. Зверніть увагу, що це значення, як і значення `5` v poli prvním mají přiřazený stejný numerický klíč `0`, тому в результуючий масив потрапляє тільки елемент із першого поля. +Значення з другого масиву завжди додаються в кінець першого. Дещо заплутаним може здатися зникнення значення `10` з другого масиву. Слід усвідомити, що це значення, так само як і значення `5` у першому масиві, мають однаковий числовий ключ `0`, тому у результуючому масиві є лише елемент з першого масиву. -normalize(array $array, string $filling=null): array .[method] --------------------------------------------------------------- +normalize(array $array, ?string $filling=null): array .[method] +--------------------------------------------------------------- -Нормалізує масив до асоціативного масиву. Замінює цифрові клавіші їхніми значеннями, нове значення буде `$filling`. +Нормалізує масив до асоціативного масиву. Числові ключі замінює їхніми значеннями, новим значенням буде `$filling`. ```php $array = Arrays::normalize([1 => 'first', 'a' => 'second']); @@ -243,14 +354,14 @@ $array = Arrays::normalize([1 => 'first', 'a' => 'second'], 'foobar'); ``` -pick(array &$array, string|int $key, mixed $default=null): mixed .[method] --------------------------------------------------------------------------- +pick(array &$array, string|int $key, ?mixed $default=null): mixed .[method] +--------------------------------------------------------------------------- -Повертає і видаляє значення елемента з масиву. Якщо він не існує, видає виняток або повертає значення `$default`, якщо воно існує. +Повертає та видаляє значення елемента з масиву. Якщо він не існує, викликає виняток або повертає значення `$default`, якщо воно вказане. ```php -$array = [1 => 'foo', null => 'bar']; -$a = Arrays::pick($array, null); +$array = [1 => 'foo', 'x' => 'bar']; +$a = Arrays::pick($array, 'x'); // $a = 'bar' $b = Arrays::pick($array, 'not-exists', 'foobar'); // $b = 'foobar' @@ -274,20 +385,20 @@ Arrays::renameKey($array, 'first', 'renamed'); getKeyOffset(array $array, string|int $key): ?int .[method] ----------------------------------------------------------- -Повертає позицію заданого ключа в масиві. Позиція нумерується від 0. Якщо ключ не знайдено, функція повертає `null`. +Повертає позицію вказаного ключа в масиві. Позиція нумерується з 0. У випадку, якщо ключ не буде знайдено, функція поверне `null`. ```php $array = ['first' => 10, 'second' => 20]; -$position = Arrays::getKeyOffset($array, 'first'); // vrátí 0 -$position = Arrays::getKeyOffset($array, 'second'); // vrátí 1 -$position = Arrays::getKeyOffset($array, 'not-exists'); // vrátí null +$position = Arrays::getKeyOffset($array, 'first'); // повертає 0 +$position = Arrays::getKeyOffset($array, 'second'); // повертає 1 +$position = Arrays::getKeyOffset($array, 'not-exists'); // повертає null ``` -some(iterable $array, callable $callback): bool .[method] ---------------------------------------------------------- +some(array $array, callable $predicate): bool .[method] +------------------------------------------------------- -Перевіряє, чи проходить хоча б один елемент у масиві тест, реалізований у `$callback` із сигнатурою `function ($value, $key, array $array): bool`. +Перевіряє, чи хоча б один елемент масиву проходить тест, реалізований у `$predicate` із сигнатурою `function ($value, $key, array $array): bool`. ```php $array = [1, 2, 3, 4]; @@ -295,13 +406,13 @@ $isEven = fn($value) => $value % 2 === 0; $res = Arrays::some($array, $isEven); // true ``` -Див. розділ [Кожен() |#every]. +Див. [#every()]. toKey(mixed $key): string|int .[method] --------------------------------------- -Перетворює значення в ключ масиву, який є або цілим числом, або рядком. +Перетворює значення на ключ масиву, який є або цілим числом, або рядком. ```php Arrays::toKey('1'); // 1 @@ -317,14 +428,14 @@ toObject(iterable $array, object $object): object .[method] ```php $obj = new stdClass; $array = ['foo' => 1, 'bar' => 2]; -Arrays::toObject($array, $obj); // додаємо $obj->foo = 1; $obj->bar = 2; +Arrays::toObject($array, $obj); // встановлює $obj->foo = 1; $obj->bar = 2; ``` -wrap(iterable $array, string $prefix='', string $suffix=''): array .[method] ----------------------------------------------------------------------------- +wrap(array $array, string $prefix='', string $suffix=''): array .[method] +------------------------------------------------------------------------- -Виводить кожен елемент масиву в рядок і обертає його префіксом `$prefix` і суфіксом `$suffix`. +Кожен елемент у масиві перетворює на рядок і обгортає префіксом `$prefix` та суфіксом `$suffix`. ```php $array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); @@ -332,21 +443,21 @@ $array = Arrays::wrap(['a' => 'red', 'b' => 'green'], '<<', '>>'); ``` -ArrayHash .[#toc-arrayhash] -=========================== +ArrayHash +========= -Об'єкт [api:Nette\Utils\ArrayHash] є нащадком загального класу stdClass і розширює його можливістю поводитися з ним як з масивом, тобто, наприклад, звертатися до членів через квадратні дужки: +Об'єкт [api:Nette\Utils\ArrayHash] є нащадком загального класу stdClass і розширює його можливістю поводитися з ним як з масивом, тобто, наприклад, отримувати доступ до членів через квадратні дужки: ```php $hash = new Nette\Utils\ArrayHash; $hash['foo'] = 123; -$hash->bar = 456; // одночасно працює об'єктна нотація +$hash->bar = 456; // одночасно працює і об'єктний запис $hash->foo; // 123 ``` -Ви можете використовувати функцію `count($hash)`, щоб отримати кількість членів. +Можна використовувати функцію `count($hash)` для визначення кількості елементів. -Ви можете виконувати ітерації над об'єктом, як у випадку з масивом, навіть із посиланням: +Над об'єктом можна ітерувати так само, як і над масивом, навіть за посиланням: ```php foreach ($hash as $key => $value) { @@ -354,11 +465,11 @@ foreach ($hash as $key => $value) { } foreach ($hash as $key => &$value) { - $value = 'new value'; + $value = 'нове значення'; } ``` -Ми можемо перетворити існуючий масив на `ArrayHash` за допомогою методу `from()`: +Існуючий масив можна перетворити на `ArrayHash` за допомогою методу `from()`: ```php $array = ['foo' => 123, 'bar' => 456]; @@ -383,20 +494,20 @@ $hash['inner']['a']; // 'b' ```php $hash = Nette\Utils\ArrayHash::from($array, false); -$hash->inner; // полюс +$hash->inner; // масив ``` -Перетворення назад у масив: +Перетворення назад на масив: ```php $array = (array) $hash; ``` -ArrayList .[#toc-arraylist] -=========================== +ArrayList +========= -[api:Nette\Utils\ArrayList] являє собою лінійний масив, у якому індексами є тільки цілі числа, що зростають від 0. +[api:Nette\Utils\ArrayList] представляє лінійний масив, де індекси є лише цілими числами, що зростають від 0. ```php $list = new Nette\Utils\ArrayList; @@ -407,16 +518,16 @@ $list[] = 'c'; count($list); // 3 ``` -Існуючі масиви можуть бути перетворені в `ArrayList` за допомогою методу `from()`: +Існуючий масив можна перетворити на `ArrayList` за допомогою методу `from()`: ```php $array = ['foo', 'bar']; $list = Nette\Utils\ArrayList::from($array); ``` -Ви можете використовувати функцію `count($list)` для отримання кількості елементів. +Можна використовувати функцію `count($list)` для визначення кількості елементів. -Ви можете виконувати ітерації над об'єктом, як у випадку з масивом, навіть із посиланням: +Над об'єктом можна ітерувати так само, як і над масивом, навіть за посиланням: ```php foreach ($list as $key => $value) { @@ -428,21 +539,21 @@ foreach ($list as $key => &$value) { } ``` -Доступ до ключів за межами допустимих значень викликає виняток `Nette\OutOfRangeException`: +Доступ до ключів поза дозволеними значеннями викликає виняток `Nette\OutOfRangeException`: ```php -echo $list[-1]; // призводить Nette\OutOfRangeException -unset($list[30]); // призводить Nette\OutOfRangeException +echo $list[-1]; // викликає Nette\OutOfRangeException +unset($list[30]); // викликає Nette\OutOfRangeException ``` -Видалення ключа призводить до зміни нумерації елементів: +Видалення ключа спричиняє перенумерацію елементів: ```php unset($list[1]); // ArrayList(0 => 'a', 1 => 'c') ``` -Новий елемент може бути доданий на початок за допомогою методу `prepend()`: +Новий елемент можна додати на початок за допомогою методу `prepend()`: ```php $list->prepend('d'); diff --git a/utils/uk/callback.texy b/utils/uk/callback.texy index 2b5c10bc59..50d4156ec4 100644 --- a/utils/uk/callback.texy +++ b/utils/uk/callback.texy @@ -1,8 +1,8 @@ -Робота зі зворотними викликами -****************************** +Робота з callback'ами +********************* .[perex] -[api:Nette\Utils\Callback] це статичний клас із функціями для роботи зі [зворотними викликами PHP |https://www.php.net/manual/en/language.types.callable.php]. +[api:Nette\Utils\Callback] — це статичний клас з функціями для роботи з [PHP callback'ами |https://www.php.net/manual/en/language.types.callable.php]. Встановлення: @@ -11,7 +11,7 @@ composer require nette/utils ``` -У всіх прикладах передбачається, що псевдонім уже створено: +Усі приклади передбачають створений псевдонім: ```php use Nette\Utils\Callback; @@ -21,21 +21,21 @@ use Nette\Utils\Callback; check($callable, bool $syntax=false): callable .[method] -------------------------------------------------------- -Перевіряє, чи є змінна `$callable` допустимим зворотним викликом. В іншому випадку викидається `Nette\InvalidArgumentException`. Якщо `$syntax` істина, функція тільки перевіряє, що `$callable` має структуру зворотного виклику, але не перевіряє, чи існує клас або метод насправді. Вона повертає `$callable`. +Перевіряє, чи змінна `$callable` є дійсним callback'ом. В іншому випадку викликає `Nette\InvalidArgumentException`. Якщо `$syntax` дорівнює true, функція лише перевіряє, що `$callable` має структуру callback'а, але не перевіряє, чи дійсно існує вказаний клас або метод. Повертає `$callable`. ```php -Callback::check('trim'); // не викидає виняток -Callback::check(['NonExistentClass', 'method']); // кидає Nette\InvalidArgumentException -Callback::check(['NonExistentClass', 'method'], true); // не викидає виняток -Callback::check(function () {}); // не викидає виняток -Callback::check(null); // кидає Nette\InvalidArgumentException +Callback::check('trim'); // не викликає виняток +Callback::check(['NonExistentClass', 'method']); // викликає Nette\InvalidArgumentException +Callback::check(['NonExistentClass', 'method'], true); // не викликає виняток +Callback::check(function () {}); // не викликає виняток +Callback::check(null); // викликає Nette\InvalidArgumentException ``` toString($callable): string .[method] ------------------------------------- -Перетворює зворотний виклик PHP на текстову форму. Клас або метод не обов'язково повинен існувати. +Перетворює PHP callback на текстову форму. Клас або метод не обов'язково повинні існувати. ```php Callback::toString('trim'); // 'trim' @@ -46,21 +46,21 @@ Callback::toString(['MyClass', 'method']); // 'MyClass::method' toReflection($callable): ReflectionMethod|ReflectionFunction .[method] ---------------------------------------------------------------------- -Повертає відображення для методу або функції у зворотному виклику PHP. +Повертає рефлексію для методу або функції в PHP callback'і. ```php $ref = Callback::toReflection('trim'); -// $ref je ReflectionFunction('trim') +// $ref є ReflectionFunction('trim') $ref = Callback::toReflection(['MyClass', 'method']); -// $ref je ReflectionMethod('MyClass', 'method') +// $ref є ReflectionMethod('MyClass', 'method') ``` isStatic($callable): bool .[method] ----------------------------------- -Визначає, чи є зворотний виклик PHP функцією або статичним методом. +Визначає, чи є PHP callback функцією або статичним методом. ```php Callback::isStatic('trim'); // true @@ -73,7 +73,7 @@ Callback::isStatic(function () {}); // false unwrap(Closure $closure): callable|array .[method] -------------------------------------------------- -Розпаковує закриття, створене за допомогою `Closure::fromCallable`:https://www.php.net/manual/en/closure.fromcallable.php. +Розпаковує Closure, створену за допомогою `Closure::fromCallable`:https://www.php.net/manual/en/closure.fromcallable.php. ```php $closure = Closure::fromCallable(['MyClass', 'method']); diff --git a/utils/uk/datetime.texy b/utils/uk/datetime.texy index 8649d1600d..b1fc8c5e0a 100644 --- a/utils/uk/datetime.texy +++ b/utils/uk/datetime.texy @@ -1,17 +1,17 @@ -Дата і час -********** +Дата та час +*********** .[perex] -[api:Nette\Utils\DateTime] - це клас, який розширює рідну DateTime додатковими функціями. +[api:Nette\Utils\DateTime] — це клас, який розширює нативний [php:DateTime] додатковими функціями. -Інсталяція: +Встановлення: ```shell composer require nette/utils ``` -Усі приклади передбачають наявність створеного псевдоніма: +Усі приклади передбачають створений псевдонім: ```php use Nette\Utils\DateTime; @@ -20,27 +20,27 @@ use Nette\Utils\DateTime; static from(string|int|\DateTimeInterface $time): DateTime .[method] -------------------------------------------------------------------- -Ви можете вибрати об'єкт DateTime z řetězce, UNIX timestamp nebo jiného objektu [php:DateTimeInterface]. Vyhodí výjimku `Exception`, pokud datum a čas není platný. +Створює об'єкт DateTime з рядка, UNIX timestamp або іншого об'єкта [php:DateTimeInterface]. Викликає виняток `Exception`, якщо дата та час недійсні. ```php -DateTime::from(1138013640); // создает DateTime из временной метки UNIX с часовым поясом по умолчанию -DateTime::from(42); // создает DateTime из текущего времени плюс 42 секунды -DateTime::from('1994-02-26 04:15:32'); // создает DateTime из строки -DateTime::from('1994-02-26'); // создает DateTime по дате, время будет 00:00:00 +DateTime::from(1138013640); // створює DateTime з UNIX timestamp зі стандартною часовою зоною +DateTime::from(42); // створює DateTime з поточного часу плюс 42 секунди +DateTime::from('1994-02-26 04:15:32'); // створює DateTime за рядком +DateTime::from('1994-02-26'); // створює DateTime за датою, час буде 00:00:00 ``` static fromParts(int $year, int $month, int $day, int $hour=0, int $minute=0, float $second=0.0): DateTime .[method] -------------------------------------------------------------------------------------------------------------------- -Створіть об'єкт DateTime і виведіть його на сайт `Nette\InvalidArgumentException`, де буде вказано дату та її значення. +Створює об'єкт DateTime або викликає виняток `Nette\InvalidArgumentException`, якщо дата та час недійсні. ```php DateTime::fromParts(1994, 2, 26, 4, 15, 32); ``` -static createFromFormat(string $format, string $time, string|\DateTimeZone $timezone=null): DateTime|false .[method] --------------------------------------------------------------------------------------------------------------------- -Розширює [DateTime::createFromFormat() |https://www.php.net/manual/en/datetime.createfromformat.php] можливістю введення часового поясу у вигляді рядка. +static createFromFormat(string $format, string $time, ?string|\DateTimeZone $timezone=null): DateTime|false .[method] +--------------------------------------------------------------------------------------------------------------------- +Розширює [DateTime::createFromFormat()|https://www.php.net/manual/en/datetime.createfromformat.php] можливістю вказати часову зону як рядок. ```php DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); ``` @@ -48,7 +48,7 @@ DateTime::createFromFormat('d.m.Y', '26.02.1994', 'Europe/London'); modifyClone(string $modify=''): static .[method] ------------------------------------------------ -Створи копію з піднятою годиною. +Створює копію зі зміненим часом. ```php $original = DateTime::from('2017-02-03'); $clone = $original->modifyClone('+1 day'); @@ -65,9 +65,9 @@ echo $dateTime; // '2017-02-03 04:15:32' ``` -implementuje JsonSerializable .[#toc-implements-jsonserializable] ------------------------------------------------------------------ -Повертає дату і час у форматі ISO 8601, який використовується, наприклад, у JavaScript. +implementuje JsonSerializable +----------------------------- +Повертає дату та час у форматі ISO 8601, який використовується, наприклад, у JavaScript. ```php $date = DateTime::from('2017-02-03'); echo json_encode($date); diff --git a/utils/uk/filesystem.texy b/utils/uk/filesystem.texy index 1940925dfb..86271df95b 100644 --- a/utils/uk/filesystem.texy +++ b/utils/uk/filesystem.texy @@ -1,41 +1,43 @@ -Функції файлової системи -************************ +Файлова система +*************** .[perex] -[api:Nette\Utils\FileSystem] це статичний клас, який містить корисні функції для роботи з файловою системою. Однією з переваг над нативними функціями PHP є те, що вони генерують виключення у випадку помилок. +[api:Nette\Utils\FileSystem] — це клас з корисними функціями для роботи з файловою системою. Однією з переваг порівняно з нативними функціями PHP є те, що в разі помилки вони викликають винятки. +Якщо вам потрібно шукати файли на диску, використовуйте [Finder|finder]. + Встановлення: ```shell composer require nette/utils ``` -Наступні приклади припускають, що визначено наступний псевдонім класу: +Наступні приклади передбачають створений псевдонім: ```php use Nette\Utils\FileSystem; ``` -Маніпуляція .[#toc-manipulation] -================================ +Маніпуляції +=========== copy(string $origin, string $target, bool $overwrite=true): void .[method] -------------------------------------------------------------------------- -Копіює файл або цілий каталог. За замовчуванням перезаписує існуючі файли і каталоги. Якщо `$overwrite` встановлено на `false` і вже існує `$target`, згенерує виключення `Nette\InvalidStateException`. Згенерує виключення `Nette\IOException` у разі виникнення помилки. +Копіює файл або весь каталог. За замовчуванням перезаписує існуючі файли та каталоги. Якщо параметр `$overwrite` встановлено на `false`, викликає виняток `Nette\InvalidStateException`, якщо цільовий файл або каталог `$target` існує. У разі помилки викликає виняток `Nette\IOException`. ```php FileSystem::copy('/path/to/source', '/path/to/dest', overwrite: true); ``` -createDir(string $directory, int $mode=0777): void .[method] ------------------------------------------------------------- +createDir(string $dir, int $mode=0777): void .[method] +------------------------------------------------------ -Створює каталог, якщо він не існує, включаючи батьківські каталоги. Згенерує виключення `Nette\IOException` у разі виникнення помилки. +Створює каталог, якщо він не існує, включно з батьківськими каталогами. У разі помилки викликає виняток `Nette\IOException`. ```php FileSystem::createDir('/path/to/dir'); @@ -45,7 +47,7 @@ FileSystem::createDir('/path/to/dir'); delete(string $path): void .[method] ------------------------------------ -Видаляє файл або весь каталог, якщо він існує. Якщо каталог не порожній, спочатку видаляється його вміст. Згенерує виключення `Nette\IOException` у разі виникнення помилки. +Видаляє файл або весь каталог, якщо він існує. Якщо каталог не порожній, спочатку видаляє його вміст. У разі помилки викликає виняток `Nette\IOException`. ```php FileSystem::delete('/path/to/fileOrDir'); @@ -55,7 +57,7 @@ FileSystem::delete('/path/to/fileOrDir'); makeWritable(string $path, int $dirMode=0777, int $fileMode=0666): void .[method] --------------------------------------------------------------------------------- -Встановлює права доступу до файлу `$fileMode` або до каталогу `$dirMode`. Рекурсивно переглядає і встановлює дозволи на весь вміст каталогу. +Встановлює права доступу файлу на `$fileMode` або каталогу на `$dirMode`. Рекурсивно проходить і встановлює права доступу також для всього вмісту каталогу. ```php FileSystem::makeWritable('/path/to/fileOrDir'); @@ -65,7 +67,7 @@ FileSystem::makeWritable('/path/to/fileOrDir'); open(string $path, string $mode): resource .[method] ---------------------------------------------------- -Відкриває файл і повертає ресурс. Параметр `$mode` працює так само, як і рідна функція `fopen()`:https://www.php.net/manual/en/function.fopen.php. Якщо виникає помилка, згенерує виключення `Nette\IOException`. +Відкриває файл і повертає ресурс. Параметр `$mode` працює так само, як і в нативній функції `fopen()`:https://www.php.net/manual/en/function.fopen.php. У разі помилки викликає виняток `Nette\IOException`. ```php $res = FileSystem::open('/path/to/file', 'r'); @@ -75,7 +77,7 @@ $res = FileSystem::open('/path/to/file', 'r'); read(string $file): string .[method] ------------------------------------ -Читає вміст `$file`. При виникненні помилки генерує виключення `Nette\IOException`. +Повертає вміст файлу `$file`. У разі помилки викликає виняток `Nette\IOException`. ```php $content = FileSystem::read('/path/to/file'); @@ -85,14 +87,13 @@ $content = FileSystem::read('/path/to/file'); readLines(string $file, bool $stripNewLines=true): \Generator .[method] ----------------------------------------------------------------------- -Читає вміст файлу рядок за рядком. На відміну від стандартної функції `file()`, вона не зчитує весь файл у пам'ять, а читає його безперервно, що дає змогу читати файли, розмір яких перевищує обсяг доступної пам'яті. Функція `$stripNewLines` визначає, чи потрібно вилучати символи переходу на новий рядок `\r` і `\n`. -У разі помилки вона згенерує виключення `Nette\IOException`. +Читає вміст файлу рядок за рядком. На відміну від нативної функції `file()`, не завантажує весь файл у пам'ять, а читає його поступово, тому можна читати файли, більші за доступну пам'ять. `$stripNewLines` вказує, чи слід видаляти символи кінця рядка `\r` та `\n`. У разі помилки викликає виняток `Nette\IOException`. ```php $lines = FileSystem::readLines('/path/to/file'); foreach ($lines as $lineNum => $line) { - echo "Line $lineNum: $line\n"; + echo "Рядок $lineNum: $line\n"; } ``` @@ -100,7 +101,7 @@ foreach ($lines as $lineNum => $line) { rename(string $origin, string $target, bool $overwrite=true): void .[method] ---------------------------------------------------------------------------- -Перейменовує або переміщує файл або каталог, вказаний командою `$origin`, до `$target`. За замовчуванням перезаписує існуючі файли і каталоги. Якщо `$overwrite` задано як `false`, а `$target` вже існує, згенерує виключення `Nette\InvalidStateException`. Згенерує виняток `Nette\IOException` у разі виникнення помилки. +Перейменовує або переміщує файл чи каталог `$origin`. За замовчуванням перезаписує існуючі файли та каталоги. Якщо параметр `$overwrite` встановлено на `false`, викликає виняток `Nette\InvalidStateException`, якщо цільовий файл або каталог `$target` існує. У разі помилки викликає виняток `Nette\IOException`. ```php FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); @@ -110,21 +111,21 @@ FileSystem::rename('/path/to/source', '/path/to/dest', overwrite: true); write(string $file, string $content, int $mode=0666): void .[method] -------------------------------------------------------------------- -Записує `$content` в `$file`. Згенерує виключення `Nette\IOException` про виникнення помилки. +Записує рядок `$content` у файл `$file`. У разі помилки викликає виняток `Nette\IOException`. ```php FileSystem::write('/path/to/file', $content); ``` -Шляхи .[#toc-paths] -=================== +Шляхи +===== isAbsolute(string $path): bool .[method] ---------------------------------------- -Визначає, чи є `$path` абсолютним. +Визначає, чи є шлях `$path` абсолютним. ```php FileSystem::isAbsolute('../backup'); // false @@ -135,7 +136,7 @@ FileSystem::isAbsolute('C:/backup'); // true joinPaths(string ...$segments): string .[method] ------------------------------------------------ -Об'єднує всі відрізки шляху і нормалізує результат. +Об'єднує всі сегменти шляху та нормалізує результат. ```php FileSystem::joinPaths('a', 'b', 'file.txt'); // 'a/b/file.txt' @@ -146,7 +147,7 @@ FileSystem::joinPaths('/a/', '/../b'); // '/b' normalizePath(string $path): string .[method] --------------------------------------------- -Нормалізує `..` і `.` та роздільники каталогів у шляху. +Нормалізує `..` та `.` і роздільники каталогів у шляху до системних (використовує `/`). ```php FileSystem::normalizePath('/file/.'); // '/file/' @@ -159,7 +160,7 @@ FileSystem::normalizePath('file/../../bar'); // '/../bar' unixSlashes(string $path): string .[method] ------------------------------------------- -Перетворює слеші на `/`, що використовується у системах Unix. +Перетворює скісні риски на `/`, що використовуються в Unix-системах. ```php $path = FileSystem::unixSlashes($path); @@ -169,8 +170,45 @@ $path = FileSystem::unixSlashes($path); platformSlashes(string $path): string .[method] ----------------------------------------------- -Перетворює косі риски на символи, характерні для поточної платформи, наприклад, `\` у Windows і `/` в інших системах. +Перетворює скісні риски на символи, специфічні для поточної платформи, тобто `\` у Windows та `/` в інших системах. ```php $path = FileSystem::platformSlashes($path); ``` + + +resolvePath(string $basePath, string $path): string .[method]{data-version:4.0.6} +--------------------------------------------------------------------------------- + +Визначає кінцевий шлях зі шляху `$path` відносно базового каталогу `$basePath`. Абсолютні шляхи (`/foo`, `C:/foo`) залишає без змін (лише нормалізує скісні риски), відносні шляхи приєднує до базового шляху. + +```php +// У Windows скісні риски у виводі були б зворотними (\) +FileSystem::resolvePath('/base/dir', '/abs/path'); // '/abs/path' +FileSystem::resolvePath('/base/dir', 'rel'); // '/base/dir/rel' +FileSystem::resolvePath('base/dir', '../file.txt'); // 'base/file.txt' +FileSystem::resolvePath('base', ''); // 'base' +``` + + +Статичний проти нестатичного доступу +==================================== + +Щоб, наприклад, для цілей тестування ви могли легко замінити клас іншим (моком), використовуйте його нестатично: + +```php +class AnyClassUsingFileSystem +{ + public function __construct( + private FileSystem $fileSystem, + ) { + } + + public function readConfig(): string + { + return $this->fileSystem->read(/* ... */); + } + + ... +} +``` diff --git a/utils/uk/finder.texy b/utils/uk/finder.texy index 7ed58e04f4..f96e8838a6 100644 --- a/utils/uk/finder.texy +++ b/utils/uk/finder.texy @@ -1,8 +1,8 @@ -Finder: Пошук файлів +Finder: пошук файлів ******************** .[perex] -Потрібно знайти файли, що відповідають певній масці? Вам допоможе програма Finder. Це універсальний і швидкий інструмент для перегляду структури каталогів. +Потрібно знайти файли, що відповідають певній масці? Finder вам у цьому допоможе. Це універсальний і швидкий інструмент для обходу структури каталогів. Встановлення: @@ -11,17 +11,17 @@ Finder: Пошук файлів composer require nette/utils ``` -У прикладах передбачається, що псевдонім уже створено: +Приклади передбачають створений псевдонім: ```php use Nette\Utils\Finder; ``` -Використання .[#toc-using] --------------------------- +Використання +------------ -Спочатку подивимося, як можна використовувати [api:Nette\Utils\Finder] для перерахування імен файлів із розширеннями `.txt` і `.md` у поточному каталозі: +Спочатку покажемо, як за допомогою [api:Nette\Utils\Finder] можна вивести імена файлів з розширеннями `.txt` та `.md` у поточному каталозі: ```php foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { @@ -29,110 +29,111 @@ foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) { } ``` -За замовчуванням для пошуку використовується поточний каталог, але ви можете змінити його за допомогою методів [in() або from() |#Where to search?]. -Змінна `$file` є екземпляром класу [FileInfo |#FileInfo] з безліччю корисних методів. Ключ `$name` містить шлях до файлу у вигляді рядка. +Каталог для пошуку за замовчуванням — це поточний каталог, але його можна змінити за допомогою методів [in() або from() |#Де шукати]. Змінна `$file` є екземпляром класу [#FileInfo] з багатьма корисними методами. У ключі `$name` міститься шлях до файлу у вигляді рядка. -Що шукати? .[#toc-what-to-search-for] -------------------------------------- +Що шукати? +---------- -На додаток до методу `findFiles()` існує також `findDirectories()`, який шукає тільки в каталогах, і `find()`, який шукає в обох каталогах. Ці методи статичні, тому їх можна викликати без створення екземпляра. Параметр mask є необов'язковим, якщо ви його не вкажете, пошук здійснюватиметься у всіх каталогах. +Крім методу `findFiles()`, існує також `findDirectories()`, який шукає лише каталоги, та `find()`, який шукає і те, й інше. Ці методи статичні, тому їх можна викликати без створення екземпляра. Параметр з маскою необов'язковий, якщо його не вказати, буде знайдено все. ```php foreach (Finder::find() as $file) { - echo $file; // тепер усі файли і каталоги перераховані + echo $file; // тепер виводяться всі файли та каталоги } ``` -Використовуйте методи `files()` і `directories()`, щоб додати, що ще потрібно шукати. Методи можуть викликатися багаторазово, а як параметр може бути надано масив масок: +За допомогою методів `files()` та `directories()` можна доповнювати, що ще потрібно шукати. Методи можна викликати повторно, а як параметр можна вказати масив масок: ```php -Finder::findDirectories('vendor') // все каталоги - ->files(['*.php', '*.phpt']); // плюс все PHP файлы +Finder::findDirectories('vendor') // всі каталоги + ->files(['*.php', '*.phpt']); // плюс усі PHP-файли ``` -Альтернативою статичним методам є створення екземпляра за допомогою `new Finder` (свіжий об'єкт, створений у такий спосіб, нічого не шукає) і вказівка того, що шукати за допомогою `files()` і `directories()`: +Альтернативою статичним методам є створення екземпляра за допомогою `new Finder` (такий свіжостворений об'єкт нічого не шукає) та вказівка, що шукати, за допомогою `files()` та `directories()`: ```php (new Finder) - ->directories() // усі каталоги - ->files('*.php'); // плюс усі файли PHP + ->directories() // всі каталоги + ->files('*.php'); // плюс усі PHP-файли ``` -Ви можете використовувати [підстановні знаки |#wildcards] `*`, `**`, `?` and `[...]` у масці. Можна навіть вказувати в каталогах, наприклад, `src/*.php` буде шукати всі файли PHP в каталозі `src`. +У масці можна використовувати [#замісники] `*`, `**`, `?` та `[...]`. Ви навіть можете вказати каталоги, наприклад, `src/*.php` знайде всі PHP-файли в каталозі `src`. +Символічні посилання також вважаються каталогами або файлами. -Де шукати? .[#toc-where-to-search] ----------------------------------- -Директорія пошуку за замовчуванням - це поточна директорія. Ви можете змінити це, використовуючи методи `in()` і `from()`. Як видно з назв методів, `in()` шукає тільки в поточному каталозі, а `from()` шукає і в його підкаталогах (рекурсивно). Якщо вам потрібен рекурсивний пошук у поточному каталозі, ви можете використовувати `from('.')`. +Де шукати? +---------- -Ці методи можна викликати кілька разів або передавати їм кілька шляхів у вигляді масивів, тоді пошук файлів буде здійснюватися у всіх каталогах. Якщо один із каталогів не існує, то відбудеться помилка `Nette\UnexpectedValueException`. +Каталог для пошуку за замовчуванням — це поточний каталог. Його можна змінити за допомогою методів `in()` та `from()`. Як видно з назв методів, `in()` шукає лише у вказаному каталозі, тоді як `from()` шукає також у його підкаталогах (рекурсивно). Якщо ви хочете шукати рекурсивно в поточному каталозі, можна використати `from('.')`. + +Ці методи можна викликати кілька разів або передати їм кілька шляхів у вигляді масиву, тоді файли шукатимуться у всіх каталогах. Якщо якийсь із каталогів не існує, буде викликано виняток `Nette\UnexpectedValueException`. ```php Finder::findFiles('*.php') - ->in(['src', 'tests']) // шукає безпосередньо в src/ і tests/ - ->from('vendor'); // шукає також у підкаталогах vendor/ + ->in(['src', 'tests']) // шукає безпосередньо в src/ та tests/ + ->from('vendor'); // шукає також у підкаталогах vendor/ ``` -Відносні шляхи є відносними по відношенню до поточного каталогу. Звичайно, можна вказати й абсолютні шляхи: +Відносні шляхи є відносними до поточного каталогу. Звичайно, можна вказати й абсолютні шляхи: ```php Finder::findFiles('*.php') ->in('/var/www/html'); ``` -[Символи |#wildcards] підстановки `*`, `**`, `?` can be used in the path. For example, you can use the path `src/*/*.php` для пошуку всіх файлів PHP у каталогах другого рівня в каталозі `src`. Символ `**`, званий globstar, є потужним козирем, оскільки дає змогу шукати і в підкаталогах: використовуйте `src/**/tests/*.php` для пошуку всіх файлів PHP у каталозі `tests`, розміщених у `src` або в будь-якому з його підкаталогів. +У шляху можна використовувати [#замісники] `*`, `**`, `?`. Наприклад, за допомогою шляху `src/*/*.php` можна шукати всі PHP-файли в каталогах другого рівня в каталозі `src`. Символ `**`, що називається globstar, є потужним інструментом, оскільки дозволяє шукати і в підкаталогах: за допомогою `src/**/tests/*.php` ви шукаєте всі PHP-файли в каталозі `tests`, що знаходиться в `src` або будь-якому його підкаталозі. -З іншого боку, підстановні символи `[...]` не підтримуються в шляху, тобто вони не мають спеціального значення, щоб уникнути небажаної поведінки в разі, якщо ви шукаєте, наприклад, `in(__DIR__)` і випадково в шляху з'являються символи `[]`. +Навпаки, замісники `[...]` у шляху не підтримуються, тобто не мають спеціального значення, щоб уникнути небажаної поведінки у випадку, якщо ви шукатимете, наприклад, `in(__DIR__)`, і випадково в шляху зустрінуться символи `[]`. -Під час глибокого пошуку файлів і каталогів спочатку повертається батьківський каталог, а потім файли, що містяться в ньому, що можна змінити на протилежне за допомогою `childFirst()`. +При пошуку файлів і каталогів у глибину спочатку повертається батьківський каталог, а потім файли, що містяться в ньому, що можна змінити за допомогою `childFirst()`. -Дикі символи .[#toc-wildcards] ------------------------------- +Замісники +--------- У масці можна використовувати кілька спеціальних символів: -- `*` - replaces any number of arbitrary characters (except `/`) -- `**` - замінює будь-яку кількість довільних символів, включаючи `/` (тобто може здійснюватися багаторівневий пошук) -- `?` - replaces one arbitrary character (except `/`) +- `*` - замінює будь-яку кількість будь-яких символів (крім `/`) +- `**` - замінює будь-яку кількість будь-яких символів, включно з `/` (тобто можна шукати багаторівнево) +- `?` - замінює один будь-який символ (крім `/`) - `[a-z]` - замінює один символ зі списку символів у квадратних дужках - `[!a-z]` - замінює один символ поза списком символів у квадратних дужках Приклади використання: -- `img/?.png` - файли з однобуквеним ім'ям `0.png`, `1.png`, `x.png` тощо. -- `logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log` - файли журналів у форматі `YYYY-MM-DD` -- `src/**/tests/*` - файли в директорії `src/tests`, `src/foo/tests`, `src/foo/bar/tests` тощо. +- `img/?.png` - файли з однолітерною назвою `0.png`, `1.png`, `x.png` тощо. +- `logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log` - лог-файли у форматі `YYYY-MM-DD` +- `src/**/tests/*` - файли в каталозі `src/tests`, `src/foo/tests`, `src/foo/bar/tests` і так далі. - `docs/**.md` - усі файли з розширенням `.md` у всіх підкаталогах каталогу `docs` -За винятком .[#toc-excluding] ------------------------------ +Виключення +---------- -Використовуйте метод `exclude()` для виключення файлів і каталогів з пошуку. Ви вказуєте маску, якій не повинен відповідати файл. Приклад пошуку файлів `*.txt`, крім тих, які містять букву `X` в імені: +За допомогою методу `exclude()` можна виключити файли та каталоги з пошуку. Ви вказуєте маску, якій файл не повинен відповідати. Приклад пошуку файлів `*.txt`, крім тих, що містять у назві літеру `X`: ```php Finder::findFiles('*.txt') ->exclude('*X*'); ``` -Використовуйте `exclude()`, щоб пропустити переглянуті підкаталоги: +Для пропуску обходу підкаталогів використовуйте `exclude()`: ```php Finder::findFiles('*.php') ->from($dir) - ->exclude('temp', '.git') + ->exclude('temp', '.git'); ``` -Фільтрація .[#toc-filtering] ----------------------------- +Фільтрація +---------- -Finder пропонує кілька методів фільтрації результатів (тобто їх скорочення). Їх можна комбінувати і викликати багаторазово. +Finder пропонує кілька методів для фільтрації результатів (тобто їх скорочення). Їх можна комбінувати та викликати повторно. -Використовуйте `size()` для фільтрації за розміром файлу. Таким чином, ми знаходимо файли розміром від 100 до 200 байт: +За допомогою `size()` ми фільтруємо за розміром файлу. Таким чином знаходимо файли розміром від 100 до 200 байт: ```php Finder::findFiles('*.php') @@ -140,19 +141,19 @@ Finder::findFiles('*.php') ->size('<=', 200); ``` -Метод `date()` фільтрує за датою останньої зміни файлу. Значення можуть бути абсолютними або відносними до поточної дати і часу, наприклад, так можна знайти файли, змінені за останні два тижні: +Метод `date()` фільтрує за датою останньої зміни файлу. Значення можуть бути абсолютними або відносними до поточної дати та часу, наприклад, так ми знайдемо файли, змінені за останні два тижні: ```php Finder::findFiles('*.php') ->date('>', '-2 weeks') - ->from($dir) + ->from($dir); ``` Обидві функції розуміють оператори `>`, `>=`, `<`, `<=`, `=`, `!=`, `<>`. -Finder також дозволяє фільтрувати результати за допомогою користувацьких функцій. Функція отримує як параметр об'єкт `Nette\Utils\FileInfo` і повинна повернути `true`, щоб включити файл у результати. +Finder також дозволяє фільтрувати результати за допомогою власних функцій. Функція отримує як параметр об'єкт `Nette\Utils\FileInfo` і повинна повернути `true`, щоб файл був включений до результатів. -Приклад: пошук файлів PHP, що містять рядок `Nette` (без урахування регістру): +Приклад: пошук PHP-файлів, які містять рядок `Nette` (незалежно від регістру): ```php Finder::findFiles('*.php') @@ -160,51 +161,51 @@ Finder::findFiles('*.php') ``` -Глибинна фільтрація .[#toc-depth-filtering] -------------------------------------------- +Фільтрація в глибину +-------------------- -При рекурсивному пошуку можна задати максимальну глибину проповзання за допомогою методу `limitDepth()`. Якщо ви задасте `limitDepth(1)`, то будуть переглянуті тільки перші підкаталоги, `limitDepth(0)` відключає фільтрацію глибини, а значення -1 скасовує обмеження. +При рекурсивному пошуку можна встановити максимальну глибину обходу за допомогою методу `limitDepth()`. Якщо встановити `limitDepth(1)`, обходяться лише перші підкаталоги, `limitDepth(0)` вимикає обхід у глибину, а значення -1 скасовує ліміт. -Finder дає змогу використовувати свої власні функції, щоб вирішити, у який каталог увійти під час перегляду. Функція отримує як параметр об'єкт `Nette\Utils\FileInfo` і повинна повернути `true`, щоб увійти в каталог: +Finder дозволяє за допомогою власних функцій вирішувати, до якого каталогу входити під час обходу. Функція отримує як параметр об'єкт `Nette\Utils\FileInfo` і повинна повернути `true`, щоб увійти до каталогу: ```php Finder::findFiles('*.php') - ->descentFilter($file->getBasename() !== 'temp'); + ->descentFilter(fn($file) => $file->getBasename() !== 'temp'); ``` -Сортування .[#toc-sorting] --------------------------- +Сортування +---------- Finder також пропонує кілька функцій для сортування результатів. -Метод `sortByName()` сортує результати за ім'ям файлу. Сортування є природним, тобто воно правильно обробляє цифри в іменах і повертає, наприклад, `foo1.txt` перед `foo10.txt`. +Метод `sortByName()` сортує результати за назвами файлів. Сортування є природним, тобто воно правильно обробляє числа в назвах і повертає, наприклад, `foo1.txt` перед `foo10.txt`. -Finder також дозволяє сортувати за допомогою користувацької функції. Вона приймає як параметри два об'єкти `Nette\Utils\FileInfo` і повинна повертати результат порівняння з оператором `<=>`тобто. `-1`, `0` nebo `1`. Наприклад, так ми сортуємо файли за розміром: +Finder також дозволяє сортувати за допомогою власної функції. Вона отримує як параметр два об'єкти `Nette\Utils\FileInfo` і повинна повернути результат порівняння оператором `<=>`, тобто `-1`, `0` або `1`. Наприклад, так ми відсортуємо файли за розміром: ```php $finder->sortBy(fn($a, $b) => $a->getSize() <=> $b->getSize()); ``` -Кілька різних пошуків .[#toc-multiple-different-searches] ---------------------------------------------------------- +Кілька різних пошуків +--------------------- -Якщо вам потрібно знайти кілька різних файлів у різних місцях або таких, що відповідають різним критеріям, використовуйте метод `append()`. Він повертає новий об'єкт `Finder`, тому ви можете використовувати ланцюжок викликів методів: +Якщо вам потрібно знайти кілька різних файлів у різних місцях або таких, що відповідають іншим критеріям, використовуйте метод `append()`. Він повертає новий об'єкт `Finder`, тому можна ланцюжком викликати методи: ```php -($finder = new Finder) // зберігаємо перший Finder у змінній $finder! - ->files('*.php') // пошук *.php файлів у src/ +($finder = new Finder) // у змінну $finder зберігаємо перший Finder! + ->files('*.php') // у src/ шукаємо файли *.php ->from('src') ->append() - ->files('*.md') // у docs/ шукаємо файли *.md + ->files('*.md') // у docs/ шукаємо файли *.md ->from('docs') ->append() - ->files('*.json'); // у поточній папці шукаємо файли *.json + ->files('*.json'); // у поточному каталозі шукаємо файли *.json ``` -В якості альтернативи ви можете використовувати метод `append()` для додавання певного файлу (або масиву файлів). Тоді він повертає той самий об'єкт `Finder`: +Альтернативно можна використати метод `append()` для додавання конкретного файлу (або масиву файлів). Тоді він повертає той самий об'єкт `Finder`: ```php $finder = Finder::findFiles('*.txt') @@ -212,12 +213,12 @@ $finder = Finder::findFiles('*.txt') ``` -FileInfo .[#toc-fileinfo] -------------------------- +FileInfo +-------- -[Nette\Utils\FileInfo |api:] - це клас, що представляє файл або каталог у результатах пошуку. Він є розширенням класу [SplFileInfo |php:SplFileInfo], який надає таку інформацію, як розмір файлу, дата останньої зміни, ім'я, шлях тощо. +[Nette\Utils\FileInfo |api:] — це клас, що представляє файл або каталог у результатах пошуку. Це розширення класу [SplFileInfo |php:SplFileInfo], яке надає інформацію, таку як розмір файлу, дата останньої зміни, ім'я, шлях тощо. -Крім того, він надає методи для повернення відносних шляхів, що корисно під час поглибленого пошуку: +Крім того, він надає методи для повернення відносного шляху, що корисно при обході в глибину: ```php foreach (Finder::findFiles('*.jpg')->from('.') as $file) { @@ -226,7 +227,7 @@ foreach (Finder::findFiles('*.jpg')->from('.') as $file) { } ``` -У вас також є методи для читання і запису вмісту файлу: +Також вам доступні методи для читання та запису вмісту файлу: ```php foreach ($finder as $file) { @@ -237,12 +238,12 @@ foreach ($finder as $file) { ``` -Повернення результатів у вигляді масиву .[#toc-returning-results-as-an-array] ------------------------------------------------------------------------------ +Повернення результатів у вигляді масиву +--------------------------------------- -Як видно з прикладів, Finder реалізує інтерфейс `IteratorAggregate`, тому ви можете використовувати `foreach` для перегляду результатів. Він запрограмований так, що результати завантажуються тільки в міру перегляду, тому якщо у вас велика кількість файлів, він не буде чекати, поки всі вони будуть прочитані. +Як було видно з прикладів, Finder реалізує інтерфейс `IteratorAggregate`, тому ви можете використовувати `foreach` для обходу результатів. Він запрограмований так, що результати завантажуються лише під час обходу, тому якщо у вас велика кількість файлів, не потрібно чекати, поки всі вони будуть прочитані. -Ви також можете повернути результати у вигляді масиву об'єктів `Nette\Utils\FileInfo`, використовуючи метод `collect()`. Масив не асоціативний, а числовий. +Результати також можна повернути у вигляді масиву об'єктів `Nette\Utils\FileInfo` за допомогою методу `collect()`. Масив не є асоціативним, а числовим. ```php $array = $finder->findFiles('*.php')->collect(); diff --git a/utils/uk/floats.texy b/utils/uk/floats.texy index 8ef3c63555..2dcca310b0 100644 --- a/utils/uk/floats.texy +++ b/utils/uk/floats.texy @@ -1,8 +1,8 @@ -Робота з поплавками -******************* +Робота з float +************** .[perex] -[api:Nette\Utils\Floats] це статичний клас із корисними функціями для порівняння десяткових чисел. +[api:Nette\Utils\Floats] — це статичний клас з корисними функціями для порівняння десяткових чисел. Встановлення: @@ -11,40 +11,42 @@ composer require nette/utils ``` -У всіх прикладах передбачається, що псевдонім уже створено: +Усі приклади передбачають створений псевдонім: ```php use Nette\Utils\Floats; ``` -Мотивація .[#toc-motivation] -============================ +Мотивація +========= -Чому клас для порівняння поплавців, запитаєте ви? Я маю на увазі, я можу використовувати оператори `<`, `>`, `===` і все готово. -Це не зовсім так. Як ви думаєте, що виведе цей код? +Ви питаєте, навіщо взагалі клас для порівняння float? Адже можна використовувати оператори `<`, `>`, `===` і все буде гаразд. Це не зовсім так. Як ви думаєте, що виведе цей код? ```php $a = 0.1 + 0.2; $b = 0.3; -echo $a === $b ? 'same' : 'not same'; +echo $a === $b ? 'однакові' : 'не однакові'; ``` -Якщо ви запустите цей код, деякі з вас здивуються, побачивши, що програма друкує `not same`. +Якщо ви запустите код, деякі з вас, напевно, будуть здивовані, що програма вивела `не однакові`. -Під час математичних операцій з десятковими числами помилки виникають через перетворення десяткових чисел у двійкові. Наприклад, `0.1 + 0.2` виводить `0.300000000000000044…`. Тому під час проведення порівнянь ми повинні допускати невелику розбіжність із певним десятковим знаком. +При математичних операціях з десятковими числами виникають помилки через перетворення між десятковою та двійковою системами. Наприклад, `0.1 + 0.2` дає `0.300000000000000044…`. Тому при порівнянні ми повинні допускати невелику різницю від певного десяткового знака. -Ось що робить клас `Floats`. Наступне порівняння працюватиме так, як очікується: +І саме це робить клас `Floats`. Наступне порівняння вже працюватиме як очікувалося: ```php -echo Floats::areEqual($a, $b) ? 'same' : 'not same'; // same +echo Floats::areEqual($a, $b) ? 'однакові' : 'не однакові'; // однакові ``` -При спробі порівняти `NAN` виникає виняток `\LogicException`. +При спробі порівняти `NAN` викликає виняток `\LogicException`. +.[tip] +Клас `Floats` допускає різницю меншу за `1e-10`. Якщо вам потрібна більша точність, краще використовуйте бібліотеку BCMath. -Порівняння плаваючих значень .[#toc-float-comparison] -===================================================== + +Порівняння float +================ areEqual(float $a, float $b): bool .[method] @@ -60,7 +62,7 @@ Floats::areEqual(10, 10.0); // true isLessThan(float $a, float $b): bool .[method] ---------------------------------------------- -Повертає `true`, якщо `$a` < `$b`. +Повертає `true`, якщо виконується `$a` < `$b`. ```php Floats::isLessThan(9.5, 10.2); // true @@ -71,7 +73,7 @@ Floats::isLessThan(INF, 10.2); // false isLessThanOrEqualTo(float $a, float $b): bool .[method] ------------------------------------------------------- -Повертає `true`, якщо `$a` <= `$b`. +Повертає `true`, якщо виконується `$a` <= `$b`. ```php Floats::isLessThanOrEqualTo(9.5, 10.2); // true @@ -82,7 +84,7 @@ Floats::isLessThanOrEqualTo(10.25, 10.25); // true isGreaterThan(float $a, float $b): bool .[method] ------------------------------------------------- -Повертає `true`, якщо `$a` > `$b` застосовується . +Повертає `true`, якщо виконується `$a` > `$b`. ```php Floats::isGreaterThan(9.5, -10.2); // true @@ -93,7 +95,7 @@ Floats::isGreaterThan(9.5, 10.2); // false isGreaterThanOrEqualTo(float $a, float $b): bool .[method] ---------------------------------------------------------- -Повертає `true`, якщо `$a` >= `$b`. +Повертає `true`, якщо виконується `$a` >= `$b`. ```php Floats::isGreaterThanOrEqualTo(9.5, 10.2); // false @@ -104,19 +106,19 @@ Floats::isGreaterThanOrEqualTo(10.2, 10.2); // true compare(float $a, float $b): int .[method] ------------------------------------------ -Якщо `$a` < `$b`, повертається `-1`, якщо дорівнює `0` a pokud je `$a` > `$b` повертається `1`. +Якщо `$a` < `$b`, повертає `-1`, якщо вони рівні, повертає `0`, а якщо `$a` > `$b`, повертає `1`. -Може використовуватися, наприклад, з функцією `usort`. +Можна використовувати, наприклад, з функцією `usort`. ```php $arr = [1, 5, 2, -3.5]; -usort($arr, [Float::class, 'compare']); -// $arr je nyní [-3.5, 1, 2, 5] +usort($arr, [Floats::class, 'compare']); +// $arr тепер [-3.5, 1, 2, 5] ``` -Допоміжні функції .[#toc-helpers-functions] -=========================================== +Допоміжні функції +================= isZero(float $value): bool .[method] diff --git a/utils/uk/helpers.texy b/utils/uk/helpers.texy index 1f55183b23..1c6b28fe7a 100644 --- a/utils/uk/helpers.texy +++ b/utils/uk/helpers.texy @@ -1,8 +1,8 @@ -Функції-помічники +Допоміжні функції ***************** .[perex] -[api:Nette\Utils\Helpers] це статичний клас із корисними функціями. +[api:Nette\Utils\Helpers] — це статичний клас з корисними функціями. Встановлення: @@ -11,7 +11,7 @@ composer require nette/utils ``` -У всіх прикладах передбачається, що псевдонім уже створено: +Усі приклади передбачають створений псевдонім: ```php use Nette\Utils\Helpers; @@ -21,7 +21,7 @@ use Nette\Utils\Helpers; capture(callable $cb): string .[method] --------------------------------------- -Виконує зворотний виклик і повертає захоплений вивід у вигляді рядка. +Виконує callback і повертає захоплений вивід як рядок. ```php $res = Helpers::capture(function () use ($template) { @@ -33,7 +33,7 @@ $res = Helpers::capture(function () use ($template) { clamp(int|float $value, int|float $min, int|float $max): int|float .[method] ---------------------------------------------------------------------------- -Обмежує значення заданим діапазоном включення min і max. +Обмежує значення заданим інклюзивним діапазоном min та max. ```php Helpers::clamp($level, 0, 255); @@ -43,8 +43,7 @@ Helpers::clamp($level, 0, 255); compare(mixed $left, string $operator, mixed $right): bool .[method] -------------------------------------------------------------------- -Порівнює два значення так само, як це робить PHP. Розрізняє оператори `>`, `>=`, `<`, `<=`, `=`, `==`, `===`, `!=`, `!==`, `<>`. -Ця функція корисна в ситуаціях, коли оператор є змінним. +Порівнює два значення так само, як це робить PHP. Розрізняє оператори `>`, `>=`, `<`, `<=`, `=`, `==`, `===`, `!=`, `!==`, `<>`. Функція корисна в ситуаціях, коли оператор є змінним. ```php Helpers::compare(10, '<', 20); // true @@ -54,7 +53,7 @@ Helpers::compare(10, '<', 20); // true falseToNull(mixed $value): mixed .[method] ------------------------------------------ -Конвертує `false` в `null`, не змінює інші значення. +Перетворює `false` на `null`, інші значення не змінює. ```php Helpers::falseToNull(false); // null @@ -65,7 +64,7 @@ Helpers::falseToNull(123); // 123 getLastError(): string .[method] -------------------------------- -Повертає останню помилку в PHP або порожній рядок, якщо помилка не сталася. На відміну від `error_get_last()`, не залежить від директиви PHP `html_errors` і завжди повертає текст, а не HTML. +Повертає останню помилку в PHP або порожній рядок, якщо помилки не сталося. На відміну від `error_get_last()`, не піддається впливу директиви PHP `html_errors` і завжди повертає текст, а не HTML. ```php Helpers::getLastError(); @@ -75,13 +74,13 @@ Helpers::getLastError(); getSuggestion(string[] $possibilities, string $value): ?string .[method] ------------------------------------------------------------------------ -Із запропонованих варіантів `$possibilities` шукає рядок, який найбільше схожий на `$value`, але не збігається з ним. Він підтримує тільки 8-бітове кодування. +З запропонованих варіантів `$possibilities` шукає рядок, який найбільше схожий на `$value`, але не такий самий. Підтримує лише 8-бітне кодування. -Це корисно, якщо певна опція недійсна, і ми хочемо запропонувати користувачеві аналогічну (але іншу, тому той самий рядок ігнорується). Ось як Nette створює повідомлення `did you mean ...?`. +Це корисно у випадку, коли певний вибір недійсний, і ми хочемо порадити користувачеві схожий (але інший, тому ігнорується той самий рядок). Таким чином Nette створює повідомлення `можливо, ви мали на увазі ...?`. ```php $items = ['foo', 'bar', 'baz']; Helpers::getSuggestion($items, 'fo'); // 'foo' Helpers::getSuggestion($items, 'barr'); // 'bar' -Helpers::getSuggestion($items, 'baz'); // 'bar', ne 'baz' +Helpers::getSuggestion($items, 'baz'); // 'bar', не 'baz' ``` diff --git a/utils/uk/html-elements.texy b/utils/uk/html-elements.texy index 5c3a642887..30f2d4a309 100644 --- a/utils/uk/html-elements.texy +++ b/utils/uk/html-elements.texy @@ -1,16 +1,16 @@ -Елементи HTML +HTML елементи ************* .[perex] -Клас [api:Nette\Utils\Html] - це помічник для генерації HTML-коду, який не допускає вразливості Cross Site Scripting (XSS). +Клас [api:Nette\Utils\Html] є помічником для генерації HTML-коду, який запобігає виникненню вразливості Cross Site Scripting (XSS). -Принцип його роботи полягає в тому, що його об'єкти - це елементи HTML, яким ми задаємо параметри і даємо змогу їх відтворювати: +Він працює так, що його об'єкти представляють HTML-елементи, яким ми встановлюємо параметри та дозволяємо їх відобразити: ```php -$el = Html::el('img'); // створює елемент +$el = Html::el('img'); // створює елемент $el->src = 'image.jpg'; // встановлює атрибут src -echo $el; // друкує '' +echo $el; // виводить '' ``` Встановлення: @@ -19,29 +19,29 @@ echo $el; // друкує '' composer require nette/utils ``` -У всіх прикладах передбачається, що псевдонім уже створено: +Усі приклади передбачають створений псевдонім: ```php use Nette\Utils\Html; ``` -Створення елемента HTML .[#toc-creating-an-html-element] -======================================================== +Створення HTML-елемента +======================= -Створіть елемент, використовуючи метод `Html::el()`: +Елемент створюємо методом `Html::el()`: ```php $el = Html::el('img'); // створює елемент ``` -Крім імені, ви можете вказати інші атрибути в синтаксисі HTML: +Крім назви, ви можете вказати й інші атрибути в HTML-синтаксисі: ```php $el = Html::el('input type=text class="red important"'); ``` -Або передайте їх як асоціативне поле з другим параметром: +Або передати їх як асоціативний масив другим параметром: ```php $el = Html::el('input', [ @@ -50,7 +50,7 @@ $el = Html::el('input', [ ]); ``` -Зміна та повернення імені елемента: +Зміна та повернення назви елемента: ```php $el->setName('img'); @@ -59,21 +59,21 @@ $el->isEmpty(); // true, оскільки є порожнім елемен ``` -Атрибути HTML .[#toc-html-attributes] -===================================== +HTML атрибути +============= -Існує три способи зміни та читання окремих атрибутів HTML, і ви самі вирішуєте, який із них вам більше до вподоби. Перший - через власність: +Окремі HTML-атрибути ми можемо змінювати та читати трьома способами, залежно від того, який вам більше сподобається. Перший з них — через властивості: ```php $el->src = 'image.jpg'; // встановлює атрибут src echo $el->src; // 'image.jpg' -unset($el->src); // знімаємо атрибут +unset($el->src); // скасовує атрибут // або $el->src = null; ``` -Другий спосіб - виклик методів, які, на відміну від встановлення властивостей, можна об'єднувати в ланцюжки: +Другий шлях — виклик методів, які, на відміну від встановлення властивостей, можна ланцюжком викликати один за одним: ```php $el = Html::el('img')->src('image.jpg')->alt('photo'); @@ -82,7 +82,7 @@ $el = Html::el('img')->src('image.jpg')->alt('photo'); $el->alt(null); // скасування атрибута ``` -І третій спосіб - найбільш багатослівний: +А третій спосіб — найбільш багатослівний: ```php $el = Html::el('img') @@ -94,9 +94,9 @@ echo $el->getAttribute('src'); // 'image.jpg' $el->removeAttribute('alt'); ``` -Атрибути можуть бути встановлені масово за допомогою `addAttributes(array $attrs)` і видалені за допомогою `removeAttributes(array $attrNames)`. +Масово атрибути можна встановити за допомогою `addAttributes(array $attrs)` та видалити за допомогою `removeAttributes(array $attrNames)`. -Значення атрибута не обов'язково має бути рядком, ви також можете використовувати логічні значення для логічних атрибутів: +Значенням атрибута не обов'язково має бути рядок, можна використовувати й логічні значення для логічних атрибутів: ```php $checkbox = Html::el('input')->type('checkbox'); @@ -104,17 +104,17 @@ $checkbox->checked = true; // $checkbox->checked = false; // ``` -Атрибут також може бути масивом значень, які виводяться через пробіли, що корисно, наприклад, для класів CSS: +Атрибутом може бути й масив значень, які будуть виведені, розділені пробілами, що зручно, наприклад, для CSS-класів: ```php $el = Html::el('input'); $el->class[] = 'active'; -$el->class[] = null; // null se ignoruje +$el->class[] = null; // null ігнорується $el->class[] = 'top'; echo $el; // '' ``` -Альтернативою є асоціативний масив, де значення вказують, чи повинен ключ бути виведений: +Альтернативою є асоціативний масив, де значення вказують, чи має бути виведений ключ: ```php $el = Html::el('input'); @@ -123,7 +123,7 @@ $el->class['top'] = false; echo $el; // '' ``` -Стилі CSS можуть бути записані у вигляді асоціативних полів: +CSS-стилі можна записувати у формі асоціативних масивів: ```php $el = Html::el('input'); @@ -132,7 +132,7 @@ $el->style['display'] = 'block'; echo $el; // '' ``` -Зараз ми використовували властивість, але те ж саме можна написати, використовуючи методи: +Зараз ми використовували властивості, але те саме можна записати за допомогою методів: ```php $el = Html::el('input'); @@ -141,7 +141,7 @@ $el->style('display', 'block'); echo $el; // '' ``` -Або навіть у найкоротшому вигляді: +Або навіть найбільш багатослівним способом: ```php $el = Html::el('input'); @@ -150,7 +150,7 @@ $el->appendAttribute('style', 'display', 'block'); echo $el; // '' ``` -І остання деталь: метод `href()` може полегшити складання параметрів запиту в URL: +Ще одна дрібниця на завершення: метод `href()` може полегшити складання параметрів запиту в URL: ```php echo Html::el('a')->href('index.php', [ @@ -161,10 +161,10 @@ echo Html::el('a')->href('index.php', [ ``` -Атрибути даних .[#toc-data-attributes] --------------------------------------- +Data атрибути +------------- -Атрибути даних мають спеціальну підтримку. Оскільки їхні імена містять дефіси, доступ через властивості та методи не такий елегантний, тому існує метод `data()`: +Спеціальну підтримку мають data-атрибути. Оскільки їхні назви містять дефіси, доступ через властивості та методи не такий елегантний, тому існує метод `data()`: ```php $el = Html::el('input'); @@ -173,7 +173,7 @@ $el->data('max-size', '500x300'); // елегантно echo $el; // '' ``` -Якщо значенням атрибута даних є масив, він автоматично серіалізується в JSON: +Якщо значенням data-атрибута є масив, він автоматично серіалізується в JSON: ```php $el = Html::el('input'); @@ -182,10 +182,10 @@ echo $el; // '' ``` -Зміст елемента .[#toc-element-content] -====================================== +Вміст елемента +============== -Встановіть внутрішній вміст елемента за допомогою методів `setHtml()` або `setText()`. Використовуйте перший варіант тільки в тому разі, якщо ви знаєте, що передаєте в параметрі надійно захищений рядок HTML. +Внутрішній вміст елемента встановлюємо методами `setHtml()` чи `setText()`. Перший з них використовуйте лише в тому випадку, якщо ви знаєте, що в параметрі передаєте надійно безпечний HTML-рядок. ```php echo Html::el('span')->setHtml('hello
                                                                                                                            '); @@ -195,7 +195,7 @@ echo Html::el('span')->setText('10 < 20'); // '10 < 20' ``` -І навпаки, для отримання внутрішнього вмісту використовуйте методи `getHtml()` або `getText()`. Останній метод видаляє HTML-теги з виводу і перетворює HTML-сутності на символи. +І навпаки, внутрішній вміст отримуємо методами `getHtml()` чи `getText()`. Другий з них видаляє з виводу HTML-теги та перетворює HTML-сутності на символи. ```php echo $el->getHtml(); // '10 < 20' @@ -203,10 +203,10 @@ echo $el->getText(); // '10 < 20' ``` -Дочірні вузли .[#toc-child-nodes] ---------------------------------- +Дочірні вузли +------------- -Усередині елемента також може бути масив дочірніх вузлів. Кожен із них може бути або рядком, або іншим елементом `Html`. Вони вставляються за допомогою `addHtml()` або `addText()`: +Внутрішній вміст елемента може бути також масивом дочірніх (children) вузлів. Кожен з них може бути або рядком, або іншим `Html` елементом. Вставляємо їх за допомогою `addHtml()` чи `addText()`: ```php $el = Html::el('span') @@ -216,16 +216,16 @@ $el = Html::el('span') // hello
                                                                                                                            10 < 20
                                                                                                                            ``` -Інший спосіб створення та вставки нового вузла `Html`: +Інший спосіб для створення та вставки нового `Html` вузла: ```php -$el = Html::el('ul') - ->create('li', ['class' => 'first']) - ->setText('první'); -//
                                                                                                                            • první
                                                                                                                            +$ul = Html::el('ul'); +$ul->create('li', ['class' => 'first']) + ->setText('перший'); +//
                                                                                                                            • перший
                                                                                                                            ``` -Ви можете працювати з вузлами так, як якщо б вони були масивами. Тобто, звертайтеся до кожного з них за допомогою квадратних дужок, рахуйте їх за допомогою `count()` і виконуйте ітерації: +З вузлами можна працювати так само, якби це був масив. Тобто звертатися до окремих з них за допомогою квадратних дужок, підраховувати їх за допомогою `count()` та ітерувати по них: ```php $el = Html::el('div'); @@ -238,20 +238,20 @@ foreach ($el as $child) { /* ... */ } echo count($el); // 2 ``` -Новий вузол може бути вставлений у певне місце за допомогою `insert(?int $index, $child, bool $replace = false)`. Якщо `$replace = false`, то буде вставлено елемент у позицію `$index` і переміщено інші. Якщо це `$index = null`, то елемент додається останнім. +Новий вузол можна вставити на конкретне місце за допомогою `insert(?int $index, $child, bool $replace = false)`. Якщо `$replace = false`, він вставить елемент на позицію `$index` та зсуне інші. Якщо `$index = null`, додасть елемент в кінець. ```php -// вставляє елемент на першу позицію і зсуває інші +// вставити елемент на першу позицію та зсунути інші $el->insert(0, Html::el('span')); ``` -Усі вузли витягуються за допомогою методу `getChildren()` і видаляються за допомогою методу `removeChildren()`. +Усі вузли отримуємо методом `getChildren()` та видаляємо їх методом `removeChildren()`. -Створення фрагмента документа .[#toc-creating-a-document-fragment] ------------------------------------------------------------------- +Створення фрагмента документа +----------------------------- -Якщо ми хочемо працювати з масивом вузлів і нас не цікавить елемент обгортки, ми можемо створити *фрагмент документа*, передавши `null` замість імені елемента: +Якщо ми хочемо працювати з масивом вузлів і нас не цікавить обгортаючий елемент, ми можемо створити так званий *document fragment*, передавши `null` замість імені елемента: ```php $el = Html::el(null) @@ -261,7 +261,7 @@ $el = Html::el(null) // hello
                                                                                                                            10 < 20
                                                                                                                            ``` -Методи `fromHtml()` і `fromText()` пропонують швидший спосіб створення фрагмента: +Швидший спосіб створення фрагмента пропонують методи `fromHtml()` та `fromText()`: ```php $el = Html::fromHtml('hello
                                                                                                                            '); @@ -272,10 +272,10 @@ echo $el; // '10 < 20' ``` -Генерування виведення HTML .[#toc-generating-html-output] -========================================================= +Генерація HTML-виводу +===================== -Найпростіший спосіб виведення елемента HTML - використовувати `echo` або переписати об'єкт на `(string)`. Ви також можете виводити відкриваючі або закриваючі теги й атрибути окремо: +Найпростішим способом вивести HTML-елемент є використання `echo` або перетворення об'єкта на `(string)`. Можна також окремо вивести відкриваючі або закриваючі теги та атрибути: ```php $el = Html::el('div class=header')->setText('hello'); @@ -289,7 +289,7 @@ echo $el->endTag(); // '' echo $el->attributes(); // 'class="header"' ``` -Важливою особливістю є автоматичний захист від [міжсайтового скриптингу (XSS) |nette:glossary#Cross-Site-Scripting-XSS]. Будь-які значення атрибутів або вміст, вставлений через `setText()` або `addText()`, надійно екранується: +Важливою рисою є автоматичний захист від [Cross Site Scripting (XSS) |nette:glossary#Cross-Site Scripting XSS]. Усі значення атрибутів або вміст, вставлений через `setText()` чи `addText()`, надійно екрануються: ```php echo Html::el('div') @@ -300,17 +300,17 @@ echo Html::el('div') ``` -HTML ↔ перетворення тексту .[#toc-conversion-html-text] -======================================================= +Перетворення HTML ↔ текст +========================= -Для перетворення HTML у текст можна використовувати статичний метод `htmlToText()`: +Для перетворення HTML на текст ви можете використати статичний метод `htmlToText()`: ```php echo Html::htmlToText('One & Two'); // 'One & Two' ``` -HtmlStringable .[#toc-htmlstringable] -===================================== +HtmlStringable +============== -Об'єкт `Nette\Utils\Html` реалізує інтерфейс `Nette\HtmlStringable`, який, наприклад, Latte або Forms використовує для розрізнення об'єктів, що мають метод `__toString()`, який повертає HTML-код. Тому не буде подвійного екранування, якщо, наприклад, ми перерахуємо об'єкт у шаблоні, використовуючи `{$el}`. +Об'єкт `Nette\Utils\Html` реалізує інтерфейс `Nette\HtmlStringable`, за допомогою якого, наприклад, Latte або форми розрізняють об'єкти, що мають метод `__toString()`, який повертає HTML-код. Таким чином, не відбувається подвійного екранування, якщо, наприклад, ми виводимо об'єкт у шаблоні за допомогою `{$el}`. diff --git a/utils/uk/images.texy b/utils/uk/images.texy index 33392a2839..ee59ef5c10 100644 --- a/utils/uk/images.texy +++ b/utils/uk/images.texy @@ -2,10 +2,10 @@ ********************** .[perex] -Клас [api:Nette\Utils\Image] дає змогу легко маніпулювати зображеннями, наприклад, змінювати розмір, обрізати, підвищувати різкість, малювати або з'єднувати кілька зображень. +Клас [api:Nette\Utils\Image] спрощує маніпуляції із зображеннями, такі як зміна розміру, обрізка, підвищення різкості, малювання або об'єднання кількох зображень. -PHP має великий набір функцій для роботи із зображеннями. Але їхній API не дуже зручний. Це був би не Nette Framework, якби не придумали сексуальний API. +PHP має великий набір функцій для маніпуляції зображеннями. Але їхній API не дуже зручний. Це був би не Nette Framework, якби він не запропонував привабливий API. Встановлення: @@ -13,17 +13,19 @@ PHP має великий набір функцій для роботи із з composer require nette/utils ``` -У всіх прикладах передбачається, що псевдонім уже створено: +Усі приклади передбачають створений псевдонім: ```php use Nette\Utils\Image; +use Nette\Utils\ImageColor; +use Nette\Utils\ImageType; ``` -Створення зображення .[#toc-creating-an-image] -============================================== +Створення зображення +==================== -Створіть нове істинно кольорове зображення, наприклад, розміром 100×200: +Створимо нове true color зображення, наприклад, з розмірами 100×200: ```php $image = Image::fromBlank(100, 200); @@ -32,138 +34,147 @@ $image = Image::fromBlank(100, 200); За бажанням можна вказати колір фону (за замовчуванням чорний): ```php -$image = Image::fromBlank(100, 200, Image::rgb(125, 0, 0)); +$image = Image::fromBlank(100, 200, ImageColor::rgb(125, 0, 0)); ``` -Або завантажте зображення з файлу: +Або завантажимо зображення з файлу: ```php $image = Image::fromFile('nette.jpg'); ``` -Підтримуються формати JPEG, PNG, GIF, WebP, AVIF і BMP, але ваша версія PHP також повинна їх підтримувати (перевірте `phpinfo()`, розділ GD). Анімація не підтримується. -Чи потрібно визначати формат зображення під час завантаження? Метод повертає його в другому параметрі: +Збереження зображення +===================== + +Зображення можна зберегти у файл: ```php -$image = Image::fromFile('nette.jpg', $type); -// $type є Image::JPEG, Image::PNG, Image::GIF, Image::WEBP, Image::AVIF або Image::BMP +$image->save('resampled.jpg'); ``` -За допомогою `Image::detectTypeFromFile()` виконується лише детектування без завантаження зображення. - +Ми можемо вказати якість стиснення в діапазоні 0..100 для JPEG (за замовчуванням 85), WEBP (за замовчуванням 80) та AVIF (за замовчуванням 30) і 0..9 для PNG (за замовчуванням 9): -Збереження зображення .[#toc-save-the-image] -============================================ +```php +$image->save('resampled.jpg', 80); // JPEG, якість 80% +``` -Зображення можна зберегти у файл: +Якщо з розширення файлу не зрозумілий формат, його можна вказати [константою |#Формати]: ```php -$image->save('resampled.jpg'); +$image->save('resampled.tmp', null, ImageType::JPEG); ``` -Ми можемо вказати якість стиснення в діапазоні 0...100 для JPEG (за замовчуванням 85), WEBP (за замовчуванням 80) і AVIF (за замовчуванням 30) та 0...9 для PNG (за замовчуванням 9): +Зображення можна замість диска записати у змінну: ```php -$image->save('resampled.jpg', 80); // JPEG, 80% якості +$data = $image->toString(ImageType::JPEG, 80); // JPEG, якість 80% ``` -Якщо формат не очевидний з розширення файлу, його можна вказати за допомогою однієї з констант `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` та `Image::BMP`: +або надіслати безпосередньо в браузер із відповідним HTTP заголовком `Content-Type`: ```php -$image->save('resampled.tmp', null, Image::JPEG); +// надсилає заголовок Content-Type: image/png +$image->send(ImageType::PNG); ``` -Зображення може бути записано не на диск, а у змінну: + +Формати +======= + +Підтримувані формати: JPEG, PNG, GIF, WebP, AVIF та BMP, однак їх також має підтримувати ваша версія PHP, що можна перевірити функцією [#isTypeSupported()]. Анімація не підтримується. + +Формат представлений константами `ImageType::JPEG`, `ImageType::PNG`, `ImageType::GIF`, `ImageType::WEBP`, `ImageType::AVIF` та `ImageType::BMP`. ```php -$data = $image->toString(Image::JPEG, 80); // JPEG, 80% якості +$supported = Image::isTypeSupported(ImageType::JPEG); ``` -або надсилається безпосередньо в браузер із відповідним HTTP-заголовком `Content-Type`: +Потрібно визначити формат зображення під час завантаження? Метод поверне його у другому параметрі: ```php -// відправляє заголовок Content-Type: image/png -$image->send(Image::PNG); +$image = Image::fromFile('nette.jpg', $type); ``` +Саме визначення без завантаження зображення виконує `Image::detectTypeFromFile()`. + -Змінити розмір .[#toc-image-resize] -=================================== +Зміна розміру +============= -Частою операцією є зміна розміру зображення. Фактичні розміри повертаються методами `getWidth()` та `getHeight()`. +Частою операцією є зміна розмірів зображення. Поточні розміри повертають методи `getWidth()` та `getHeight()`. -Метод `resize()` використовується для зміни розміру зображення так, щоб воно не перевищувало 500x300 пікселів (або ширина буде рівно 500 px, або висота рівно 300 px, один із розмірів обчислюється для збереження співвідношення сторін): +Для зміни служить метод `resize()`. Приклад пропорційної зміни розміру так, щоб він не перевищував розміри 500x300 пікселів (або ширина буде точно 500 px, або висота буде точно 300 px, один із розмірів обчислюється так, щоб зберегти співвідношення сторін): ```php $image->resize(500, 300); ``` -Можна вказати тільки один вимір, а інший буде розраховано: +Можна вказати лише один розмір, а другий обчислиться: ```php -$image->resize(500, null); // ширина 500px, висота розраховується +$image->resize(500, null); // ширина 500px, висота обчислюється -$image->resize(null, 300); // ширина розрахована, висота 300px +$image->resize(null, 300); // ширина обчислюється, висота 300px ``` -Будь-який вимір може бути вказано у відсотках: +Будь-який розмір можна вказати також у відсотках: ```php $image->resize('75%', 300); // 75 % × 300px ``` -На поведінку `resize` можуть впливати такі симптоми. Усі, крім `Image::Stretch`, зберігають співвідношення сторін. +Поведінку `resize` можна змінити за допомогою таких прапорців. Усі, крім `Image::Stretch`, зберігають співвідношення сторін. |--------------------------------------------------------------------------------------- -| Прапор | Опис +| Прапорець | Опис |--------------------------------------------------------------------------------------- -| `Image::OrSmaller` (за замовчуванням)| результуючі розміри будуть менші або дорівнювати запитуваним розмірам. -| `Image::OrBigger` | заповнює (і, можливо, перевищує в одному вимірі) цільову область -| `Image::Cover` | заповнює цільову область і обрізає те, що виходить за її межі. -| `Image::ShrinkOnly` | тільки зменшення (дозволяє уникнути розтягування маленького зображення) -| `Image::Stretch` | не зберігати співвідношення сторін +| `Image::OrSmaller` (за замовчуванням) | кінцеві розміри будуть меншими або рівними заданим розмірам +| `Image::OrBigger` | заповнює (і, можливо, перевищує в одному вимірі) цільову область +| `Image::Cover` | заповнює цільову область і обрізає те, що виходить за межі +| `Image::ShrinkOnly` | тільки зменшення (запобігає розтягуванню маленького зображення) +| `Image::Stretch` | не зберігати співвідношення сторін -Прапори передаються як третій аргумент функції: +Прапорці вказуються як третій аргумент функції: ```php $image->resize(500, 300, Image::OrBigger); ``` -Прапори можна комбінувати: +Прапорці можна комбінувати: ```php $image->resize(500, 300, Image::ShrinkOnly | Image::Stretch); ``` -Зображення можна перевернути по вертикалі або горизонталі, вказавши один із розмірів (або обидва) як від'ємне число: +Зображення можна вертикально або горизонтально перевернути, вказавши один із розмірів (або обидва) як від'ємне число: ```php -$flipped = $image->resize(null, '-100%'); // перевернемо вертикально +$flipped = $image->resize(null, '-100%'); // перевернути вертикально -$flipped = $image->resize('-100%', '-100%'); // повертаємо на 180 +$flipped = $image->resize('-100%', '-100%'); // повернути на 180° -$flipped = $image->resize(-125, 500); // змінити розмір та перевернути по горизонталі +$flipped = $image->resize(-125, 500); // змінити розмір і перевернути горизонтально ``` -Після зменшення зображення можна поліпшити його зовнішній вигляд за допомогою тонкого налаштування різкості: +Після зменшення зображення можна покращити його вигляд легким підвищенням різкості: ```php $image->sharpen(); ``` -Рослинництво .[#toc-cropping] -============================= +Обрізка +======= -Для обробітку використовується метод `crop()`: +Для обрізки служить метод `crop()`: ```php $image->crop($left, $top, $width, $height); ``` -Як і у випадку з `resize()`, усі значення можуть бути представлені у відсотках. Відсотки для `$left` і `$top` розраховуються з простору, що залишився, аналогічно властивості CSS `background-position`: +Подібно до `resize()`, усі значення можуть бути вказані у відсотках. Відсотки для `$left` та `$top` обчислюються з решти місця, подібно до CSS властивості `background-position`: ```php $image->crop('100%', '50%', '80%', '80%'); @@ -172,329 +183,359 @@ $image->crop('100%', '50%', '80%', '80%'); [* crop.svg *] -Зображення можна також автоматично обрізати, наприклад, обрізати чорні межі: +Зображення також можна обрізати автоматично, наприклад, обрізати чорні краї: ```php $image->cropAuto(IMG_CROP_BLACK); ``` -Метод `cropAuto()` є об'єктною заміною функції `imagecropauto()`, більш детальну інформацію див. у [документації до неї |https://www.php.net/manual/en/function.imagecropauto]. +Метод `cropAuto()` є об'єктною заміною функції `imagecropauto()`, в [її документації|https://www.php.net/manual/en/function.imagecropauto] ви знайдете додаткову інформацію. + +Кольори .{data-version:4.0.2} +============================= -Малювання та редагування .[#toc-drawing-and-editing] -==================================================== +Метод `ImageColor::rgb()` дозволяє визначити колір за допомогою значень червоного, зеленого та синього (RGB). За бажанням ви також можете вказати значення прозорості в діапазоні від 0 (повністю прозорий) до 1 (повністю непрозорий), так само, як у CSS. -Ви можете малювати, можете писати, але не рвіть сторінки. Усі функції PHP для роботи із зображеннями, такі як [imagefilledellipse |https://www.php.net/manual/en/function.imagefilledellipse.php], доступні вам, але в об'єктно-орієнтованому образі: +```php +$color = ImageColor::rgb(255, 0, 0); // Червоний +$transparentBlue = ImageColor::rgb(0, 0, 255, 0.5); // Напівпрозорий синій +``` + +Метод `ImageColor::hex()` дозволяє визначити колір за допомогою шістнадцяткового формату, подібно до CSS. Підтримує формати `#rgb`, `#rrggbb`, `#rgba` та `#rrggbbaa`: ```php -$image->filledEllipse($cx, $cy, $width, $height, Image::rgb(255, 0, 0, 63)); +$color = ImageColor::hex("#F00"); // Червоний +$transparentGreen = ImageColor::hex("#00FF0080"); // Напівпрозорий зелений ``` -Див. розділ [Огляд методів |#Overview-of-Methods]. +Кольори можна використовувати в інших методах, таких як `ellipse()`, `fill()` тощо. -Об'єднання декількох зображень .[#toc-merge-multiple-images] -============================================================ +Малювання та редагування +======================== -Ви можете легко вставити інше зображення у фотографію: +Можна малювати, можна писати, але листя не рвати. Вам доступні всі функції PHP для роботи із зображеннями, див. [#Огляд методів], але в об'єктній обгортці: + +```php +$image->filledEllipse($centerX, $centerY, $width, $height, ImageColor::rgb(255, 0, 0)); +``` + +Оскільки функції PHP для малювання прямокутників непрактичні через визначення координат, клас `Image` пропонує їхні заміни у вигляді функцій [#rectangleWH()] та [#filledRectangleWH()]. + + +Об'єднання кількох зображень +============================ + +В зображення можна легко вставити інше зображення: ```php $logo = Image::fromFile('logo.png'); -$blank = Image::fromBlank(320, 240, Image::rgb(52, 132, 210)); +$blank = Image::fromBlank(320, 240, ImageColor::rgb(52, 132, 210)); -// координати можуть бути знову задані у відсотках -$blank->place($logo, '80%', '80%'); // вставте в правий нижній кут +// координати можна вказати знову у відсотках +$blank->place($logo, '80%', '80%'); // вставимо поблизу правого нижнього кута ``` -Альфаканал дотримується під час вставки, і ми можемо впливати на прозорість вставленого зображення (ми створюємо водяний знак): +При вставці враховується альфа-канал, крім того, ми можемо впливати на прозорість вставлюваного зображення (створимо так званий водяний знак): ```php -$blank->place($image, '80%', '80%', 25); // прозорість становить 25% +$blank->place($image, '80%', '80%', 25); // прозорість 25 % ``` -Цей API - справжнє задоволення від використання! +Такий API справді приємно використовувати! -Огляд методів .[#toc-overview-of-methods] -========================================= +Огляд методів +============= -static fromBlank(int $width, int $height, array $color=null): Image .[method] ------------------------------------------------------------------------------ -Створює нове істинно кольорове зображення заданих розмірів. За замовчуванням використовується чорний колір. +static fromBlank(int $width, int $height, ?ImageColor $color=null): Image .[method] +----------------------------------------------------------------------------------- +Створює нове true color зображення заданих розмірів. Колір за замовчуванням — чорний. static fromFile(string $file, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------- -Читає зображення з файлу і повертає його тип у `$detectedFormat`. Підтримуються типи `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` і `Image::BMP`. +Завантажує зображення з файлу і повертає його [тип |#Формати] у `$detectedFormat`. static fromString(string $s, int &$detectedFormat=null): Image .[method] ------------------------------------------------------------------------ -Читає зображення з рядка і повертає його тип у вигляді `$detectedFormat`. Підтримуються типи `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` та `Image::BMP`. +Завантажує зображення з рядка і повертає його [тип |#Формати] у `$detectedFormat`. -static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method] ---------------------------------------------------------------------------------- -Створює колір, який може бути використаний в інших методах, таких як `ellipse()`, `fill()` тощо. +static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method][deprecated] +--------------------------------------------------------------------------------------------- +Цю функцію замінив клас `ImageColor`, див. [#кольори]. static typeToExtension(int $type): string .[method] --------------------------------------------------- -Повертає розширення файлу для заданої константи `Image::XXX`. +Повертає розширення файлу для даного [типу |#Формати]. static typeToMimeType(int $type): string .[method] -------------------------------------------------- -Повертає тип mime для заданої константи `Image::XXX`. +Повертає mime-тип для даного [типу |#Формати]. static extensionToType(string $extension): int .[method] -------------------------------------------------------- -Повертає тип зображення у вигляді константи `Image::XXX` відповідно до розширення файлу. +Повертає [тип |#Формати] зображення за розширенням файлу. static detectTypeFromFile(string $file, int &$width=null, int &$height=null): ?int .[method] -------------------------------------------------------------------------------------------- -Повертає тип зображення у вигляді константи `Image::XXX`, а також його розміри в параметрах `$width` і `$height`. +Повертає [тип |#Формати] зображення, а в параметрах `$width` та `$height` також його розміри. static detectTypeFromString(string $s, int &$width=null, int &$height=null): ?int .[method] ------------------------------------------------------------------------------------------- -Повертає тип зображення з рядка у вигляді константи `Image::XXX` і його розміри в параметрах `$width` і `$height`. +Повертає [тип |#Формати] зображення з рядка, а в параметрах `$width` та `$height` також його розміри. -affine(array $affine, array $clip=null): Image .[method] --------------------------------------------------------- -Повертає зображення, що містить афінно-трансформоване зображення src з використанням необов'язкової області обрізання. ([докладніше |https://www.php.net/manual/en/function.imageaffine]). +static isTypeSupported(int $type): bool .[method] +------------------------------------------------- +Перевіряє, чи підтримується даний [тип |#Формати] зображення. + + +static getSupportedTypes(): array .[method]{data-version:4.0.4} +--------------------------------------------------------------- +Повертає масив підтримуваних [типів |#Формати] зображення. + + +static calculateTextBox(string $text, string $fontFile, float $size, float $angle=0, array $options=[]): array .[method] +------------------------------------------------------------------------------------------------------------------------ +Обчислює розміри прямокутника, який охоплює текст певним шрифтом та розміром. Повертає асоціативний масив, що містить ключі `left`, `top`, `width`, `height`. Лівий край може бути від'ємним, якщо текст починається з лівого підрізання. + + +affine(array $affine, ?array $clip=null): Image .[method] +--------------------------------------------------------- +Повертає зображення, що містить афінно трансформоване зображення src, використовуючи необов'язкову область обрізки. ([більше |https://www.php.net/manual/en/function.imageaffine]). affineMatrixConcat(array $m1, array $m2): array .[method] --------------------------------------------------------- -Повертає конкатенацію двох матриць афінного перетворення, що корисно, якщо до одного зображення необхідно застосувати одразу кілька перетворень. ([докладніше |https://www.php.net/manual/en/function.imageaffinematrixconcat]) +Повертає конкатенацію двох афінних матриць трансформації, що корисно, якщо до одного зображення потрібно застосувати кілька трансформацій одночасно. ([більше |https://www.php.net/manual/en/function.imageaffinematrixconcat]) -affineMatrixGet(int $type, mixed $options=null): array .[method] ----------------------------------------------------------------- -Повертає матрицю перетворення матриці. ([докладніше |https://www.php.net/manual/en/function.imageaffinematrixget]) +affineMatrixGet(int $type, ?mixed $options=null): array .[method] +----------------------------------------------------------------- +Повертає матрицю трансформації. ([більше |https://www.php.net/manual/en/function.imageaffinematrixget]) alphaBlending(bool $on): void .[method] --------------------------------------- -Дозволяє використовувати два різні режими малювання в триколірних зображеннях. У режимі накладення компонент альфа-каналу кольору, який використовується у всіх функціях малювання, таких як `setPixel()`, визначає, якою мірою базовий колір повинен просвічувати. У результаті в цей момент наявний колір автоматично змішується з кольором малюнка, і результат зберігається в зображенні. У результаті піксель стає непрозорим. У режимі без змішування колір мультфільму копіюється дослівно з інформацією альфа-каналу і замінюється на цільовий піксель. Режим накладення недоступний під час малювання на зображеннях палітри. ([докладніше |https://www.php.net/manual/en/function.imagealphablending]) +Дозволяє два різних режими малювання в зображеннях truecolor. У режимі змішування компонент альфа-каналу кольору, що використовується у всіх функціях малювання, таких як `setPixel()`, визначає, наскільки має бути дозволено просвічування основного кольору. В результаті існуючий колір автоматично змішується з кольором малювання в цій точці, і результат зберігається в зображенні. Отриманий піксель є непрозорим. У режимі без змішування колір малювання копіюється буквально з інформацією про альфа-канал і замінює цільовий піксель. Режим змішування недоступний при малюванні на палітрових зображеннях. ([більше |https://www.php.net/manual/en/function.imagealphablending]) antialias(bool $on): void .[method] ----------------------------------- -Активація малювання згладжених ліній і багатокутників. Не підтримує альфа-канали. Працює тільки з триколірними зображеннями. +Активує малювання згладжених ліній та полігонів. Не підтримує альфа-канали. Працює тільки з зображеннями truecolor. -Використання згладженого примітиву з прозорим кольором фону може призвести до несподіваних результатів. Метод змішування використовує колір фону як будь-який інший колір. ([докладніше |https://www.php.net/manual/en/function.imageantialias]) +Використання згладжених примітивів з прозорим кольором фону може призвести до несподіваних результатів. Метод змішування використовує колір фону так само, як і всі інші кольори. ([більше |https://www.php.net/manual/en/function.imageantialias]) -arc(int $x, int $y, int $w, int $h, int $start, int $end, int $color): void .[method] -------------------------------------------------------------------------------------- -Малює дугу кола з центром у заданих координатах. ([докладніше |https://www.php.net/manual/en/function.imagearc]) - - -char(int $font, int $x, int $y, string $char, int $color): void .[method] -------------------------------------------------------------------------- -Намалює перший символ `$char` у зображенні з лівим верхнім кутом `$x`, `$y` (лівий верхній кут дорівнює 0, 0) кольором `$color`. ([докладніше |https://www.php.net/manual/en/function.imagechar]) - - -charUp(int $font, int $x, int $y, string $char, int $color): void .[method] ---------------------------------------------------------------------------- -Малює символ `$char` вертикально за вказаною координатою в заданому зображенні. ([докладніше |https://www.php.net/manual/en/function.imagecharup]) +arc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color): void .[method] +--------------------------------------------------------------------------------------------------------------------------- +Малює дугу кола з центром у заданих координатах. ([більше |https://www.php.net/manual/en/function.imagearc]) colorAllocate(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------- -Повертає ідентифікатор кольору, що представляє колір, який складається із заданих компонентів RGB. Повинен бути викликаний для створення кожного кольору, який буде використовуватися в зображенні. ([докладніше |https://www.php.net/manual/en/function.imagecolorallocate]) +Повертає ідентифікатор кольору, що представляє колір, складений із заданих компонентів RGB. Має бути викликаний для створення кожного кольору, який буде використовуватися в зображенні. ([більше |https://www.php.net/manual/en/function.imagecolorallocate]) colorAllocateAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ------------------------------------------------------------------------------ -Діє так само, як і `colorAllocate()`, з додаванням параметра прозорості `$alpha`. ([докладніше |https://www.php.net/manual/en/function.imagecolorallocatealpha]) +Поводиться так само, як `colorAllocate()`, з додаванням параметра прозорості `$alpha`. ([більше |https://www.php.net/manual/en/function.imagecolorallocatealpha]) colorAt(int $x, int $y): int .[method] -------------------------------------- -Повертає індекс кольору пікселя у вказаному місці зображення. Якщо зображення є truecolor, ця функція повертає значення RGB для даного пікселя у вигляді цілого числа. Використовуйте зсув бітів і бітову маску для доступу до окремих значень для червоного, зеленого і синього компонентів. ([докладніше |https://www.php.net/manual/en/function.imagecolorat]) +Повертає індекс кольору пікселя у вказаному місці зображення. Якщо зображення є truecolor, ця функція повертає значення RGB цього пікселя як ціле число. Використовуйте бітовий зсув та бітову маску для доступу до окремих значень червоної, зеленої та синьої компонент: ([більше |https://www.php.net/manual/en/function.imagecolorat]) colorClosest(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------ -Повертає індекс кольору в палітрі зображення, який "найближче" до вказаного значення RGB. Відстань" між бажаним кольором і кожним кольором у палітрі розраховується так, як якщо б значення RGB були точками в тривимірному просторі. ([докладніше |https://www.php.net/manual/en/function.imagecolorclosest]) +Повертає індекс кольору в палітрі зображення, який є «найближчим» до заданого значення RGB. "Відстань" між бажаним кольором та кожним кольором у палітрі обчислюється так, ніби значення RGB представляють точки в тривимірному просторі. ([більше |https://www.php.net/manual/en/function.imagecolorclosest]) colorClosestAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ----------------------------------------------------------------------------- -Повертає індекс кольору в палітрі зображень, який "найближче" до вказаного значення RGB і рівня `$alpha`. ([докладніше |https://www.php.net/manual/en/function.imagecolorclosestalpha]) +Повертає індекс кольору в палітрі зображення, який є «найближчим» до заданого значення RGB та рівня `$alpha`. ([більше |https://www.php.net/manual/en/function.imagecolorclosestalpha]) colorClosestHWB(int $red, int $green, int $blue): int .[method] --------------------------------------------------------------- -Отримати індекс кольору, який має відтінок, білий і чорний кольори, найближчі до заданого кольору. ([докладніше |https://www.php.net/manual/en/function.imagecolorclosesthwb]) +Отримує індекс кольору, який має відтінок, білий та чорний колір, найближчі до заданого кольору. ([більше |https://www.php.net/manual/en/function.imagecolorclosesthwb]) colorDeallocate(int $color): void .[method] ------------------------------------------- -Видаляє колір, раніше призначений за допомогою `colorAllocate()` або `colorAllocateAlpha()`. ([докладніше |https://www.php.net/manual/en/function.imagecolordeallocate]) +Де-алокує колір, раніше виділений за допомогою `colorAllocate()` або `colorAllocateAlpha()`. ([більше |https://www.php.net/manual/en/function.imagecolordeallocate]) colorExact(int $red, int $green, int $blue): int .[method] ---------------------------------------------------------- -Повертає індекс зазначеного кольору в палітрі зображення. ([докладніше |https://www.php.net/manual/en/function.imagecolorexact]) +Повертає індекс заданого кольору в палітрі зображення. ([більше |https://www.php.net/manual/en/function.imagecolorexact]) colorExactAlpha(int $red, int $green, int $blue, int $alpha): int .[method] --------------------------------------------------------------------------- -Повертає індекс зазначеного кольору + альфа в палітрі зображень. ([докладніше |https://www.php.net/manual/en/function.imagecolorexactalpha]) +Повертає індекс заданого кольору + альфа в палітрі зображення. ([більше |https://www.php.net/manual/en/function.imagecolorexactalpha]) colorMatch(Image $image2): void .[method] ----------------------------------------- -Поєднує кольори палітри з кольорами іншої панелі. ([докладніше |https://www.php.net/manual/en/function.imagecolormatch]) +Пристосовує кольори палітри до другого зображення. ([більше |https://www.php.net/manual/en/function.imagecolormatch]) colorResolve(int $red, int $green, int $blue): int .[method] ------------------------------------------------------------ -Повертає індекс кольору для бажаного кольору, або точний колір, або найближчий можливий альтернативний. ([докладніше |https://www.php.net/manual/en/function.imagecolorresolve]) +Повертає індекс кольору для бажаного кольору, або точний колір, або найближчу можливу альтернативу. ([більше |https://www.php.net/manual/en/function.imagecolorresolve]) colorResolveAlpha(int $red, int $green, int $blue, int $alpha): int .[method] ----------------------------------------------------------------------------- -Повертає індекс кольору для бажаного кольору, або точний колір, або найближчий можливий альтернативний. ([докладніше |https://www.php.net/manual/en/function.imagecolorresolvealpha]) +Повертає індекс кольору для бажаного кольору, або точний колір, або найближчу можливу альтернативу. ([більше |https://www.php.net/manual/en/function.imagecolorresolvealpha]) colorSet(int $index, int $red, int $green, int $blue): void .[method] --------------------------------------------------------------------- -Встановлює вказаний індекс у палітрі на вказаний колір. ([докладніше |https://www.php.net/manual/en/function.imagecolorset]) +Встановлює заданий індекс у палітрі на заданий колір. ([більше |https://www.php.net/manual/en/function.imagecolorset]) colorsForIndex(int $index): array .[method] ------------------------------------------- -Отримує колір зазначеного індексу. ([докладніше |https://www.php.net/manual/en/function.imagecolorsforindex]) +Отримує колір зазначеного індексу. ([більше |https://www.php.net/manual/en/function.imagecolorsforindex]) colorsTotal(): int .[method] ---------------------------- -Повертає кількість кольорів у палітрі зображення. ([докладніше |https://www.php.net/manual/en/function.imagecolorstotal]) +Повертає кількість кольорів у палітрі зображення. ([більше |https://www.php.net/manual/en/function.imagecolorstotal]) -colorTransparent(int $color=null): int .[method] ------------------------------------------------- -Отримує або встановлює прозорий колір зображення. ([докладніше |https://www.php.net/manual/en/function.imagecolortransparent]) +colorTransparent(?int $color=null): int .[method] +------------------------------------------------- +Отримує або встановлює прозорий колір у зображенні. ([більше |https://www.php.net/manual/en/function.imagecolortransparent]) convolution(array $matrix, float $div, float $offset): void .[method] --------------------------------------------------------------------- -Застосовує матрицю згортки до зображення, використовуючи заданий коефіцієнт і зміщення. ([докладніше |https://www.php.net/manual/en/function.imageconvolution]) +Застосовує до зображення матрицю згортки, використовуючи заданий коефіцієнт та зсув. ([більше |https://www.php.net/manual/en/function.imageconvolution]) .[note] -Вимагає наявності *Bundled GD extension*, тому може працювати не скрізь. +Потребує наявності *Bundled GD extension*, тому може працювати не скрізь. copy(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH): void .[method] -------------------------------------------------------------------------------------------------- -Копіює частину `$src` у зображення, що починається в координатах `$srcX`, `$srcY` з шириною `$srcW` і висотою `$srcH`. Певна частина буде скопійована в координати `$dstX` і `$dstY`. ([докладніше |https://www.php.net/manual/en/function.imagecopy]) +Копіює частину `$src` на зображення, починаючи з координат `$srcX`, `$srcY` з шириною `$srcW` та висотою `$srcH`. Визначена частина буде скопійована на координати `$dstX` та `$dstY`. ([більше |https://www.php.net/manual/en/function.imagecopy]) copyMerge(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] --------------------------------------------------------------------------------------------------------------------- -Копіює частину `$src` у зображення, що починається в координатах `$srcX`, `$srcY` з шириною `$srcW` і висотою `$srcH`. Певна частина буде скопійована в координати `$dstX` і `$dstY`. ([докладніше |https://www.php.net/manual/en/function.imagecopymerge]) +Копіює частину `$src` на зображення, починаючи з координат `$srcX`, `$srcY` з шириною `$srcW` та висотою `$srcH`. Визначена частина буде скопійована на координати `$dstX` та `$dstY`. ([більше |https://www.php.net/manual/en/function.imagecopymerge]) copyMergeGray(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $opacity): void .[method] ------------------------------------------------------------------------------------------------------------------------- -Копіює частину `$src` у зображення, що починається в координатах `$srcX`, `$srcY` з шириною `$srcW` і висотою `$srcH`. Певна частина буде скопійована в координати `$dstX` і `$dstY`. +Копіює частину `$src` на зображення, починаючи з координат `$srcX`, `$srcY` з шириною `$srcW` та висотою `$srcH`. Визначена частина буде скопійована на координати `$dstX` та `$dstY`. -Ця функція ідентична `copyMerge()`, за винятком того, що вона зберігає вихідний відтінок під час об'єднання, перетворюючи цільові пікселі у відтінки сірого перед операцією копіювання. ([докладніше |https://www.php.net/manual/en/function.imagecopymergegray]) +Ця функція ідентична `copyMerge()`, за винятком того, що при злитті зберігає відтінок джерела, перетворюючи цільові пікселі на сіру шкалу перед операцією копіювання. ([більше |https://www.php.net/manual/en/function.imagecopymergegray]) copyResampled(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] --------------------------------------------------------------------------------------------------------------------------------- -Копіює прямокутну частину одного зображення в інше зображення, плавно інтерполюючи значення пікселів так, щоб при зменшенні розміру зображення зберігало високу чіткість. +Копіює прямокутну частину одного зображення на інше зображення, плавно інтерполюючи значення пікселів, так що, зокрема, зменшення розміру зображення все ще зберігає велику чіткість. -Іншими словами, `copyResampled()` бере прямокутну область з `$src` шириною `$srcW` і висотою `$srcH` у позиції (`$srcX`, `$srcY`) і поміщає її в прямокутну область зображення шириною `$dstW` і висотою `$dstH` у позиції (`$dstX`, `$dstY`). +Іншими словами, `copyResampled()` бере прямокутну область з `$src` шириною `$srcW` та висотою `$srcH` в позиції (`$srcX`, `$srcY`) і розміщує її в прямокутній області зображення шириною `$dstW` та висотою `$dstH` в позиції (`$dstX`, `$dstY`). -Якщо координати джерела і призначення, ширина і висота відрізняються, фрагмент зображення розтягується або стискається відповідно. Координати відносяться до лівого верхнього кута. Цю функцію можна використовувати для копіювання областей одного і того ж зображення, але якщо області перекриваються, результати не будуть передбачуваними. ([докладніше |https://www.php.net/manual/en/function.imagecopyresampled]) +Якщо вихідні та цільові координати, ширина та висота відрізняються, виконується відповідне розтягування або зменшення фрагмента зображення. Координати відносяться до лівого верхнього кута. Цю функцію можна використовувати для копіювання областей в тому ж зображенні, але якщо області перекриваються, результати будуть непередбачуваними. ([більше |https://www.php.net/manual/en/function.imagecopyresampled]) copyResized(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH): void .[method] ------------------------------------------------------------------------------------------------------------------------------- -Копіює прямокутну частину одного зображення на інше зображення. Інакше кажучи, `copyResized()` отримує прямокутну область з `$src` шириною `$srcW` і висотою `$srcH` у позиції (`$srcX`, `$srcY`) і поміщає її в прямокутну область зображення шириною `$dstW` ] і висотою `$dstH` у позиції (`$dstX`, `$dstY`). +Копіює прямокутну частину одного зображення на інше зображення. Іншими словами, `copyResized()` отримує прямокутну область з `$src` шириною `$srcW` та висотою `$srcH` в позиції (`$srcX`, `$srcY`) і розміщує її в прямокутній області зображення шириною `$dstW` та висотою `$dstH` в позиції (`$dstX`, `$dstY`). -Якщо координати джерела і призначення, ширина і висота відрізняються, фрагмент зображення розтягується або стискається відповідно. Координати відносяться до лівого верхнього кута. Цю функцію можна використовувати для копіювання областей одного і того ж зображення, але якщо області перекриваються, результати не будуть передбачуваними. ([докладніше |https://www.php.net/manual/en/function.imagecopyresized]) +Якщо вихідні та цільові координати, ширина та висота відрізняються, виконується відповідне розтягування або зменшення фрагмента зображення. Координати відносяться до лівого верхнього кута. Цю функцію можна використовувати для копіювання областей в тому ж зображенні, але якщо області перекриваються, результати будуть непередбачуваними. ([більше |https://www.php.net/manual/en/function.imagecopyresized]) crop(int|string $left, int|string $top, int|string $width, int|string $height): Image .[method] ----------------------------------------------------------------------------------------------- -Обрізає зображення до заданої прямокутної області. Розміри можуть бути вказані як цілі числа в пікселях або рядки у відсотках (наприклад, `'50%'`). +Обрізає зображення до заданої прямокутної області. Розміри можна задавати як цілі числа в пікселях або рядки у відсотках (наприклад, `'50%'`). -cropAuto(int $mode=-1, float $threshold=.5, int $color=-1): Image .[method] ---------------------------------------------------------------------------- -Автоматичне кадрування зображення відповідно до заданого `$mode`. ([докладніше |https://www.php.net/manual/en/function.imagecropauto]) +cropAuto(int $mode=-1, float $threshold=.5, ?ImageColor $color=null): Image .[method] +------------------------------------------------------------------------------------- +Автоматично обрізає зображення відповідно до заданого `$mode`. ([більше |https://www.php.net/manual/en/function.imagecropauto]) -ellipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------- -Малює еліпс із центром у заданих координатах. ([докладніше |https://www.php.net/manual/en/function.imageellipse]) +ellipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------- +Малює еліпс з центром у заданих координатах. ([більше |https://www.php.net/manual/en/function.imageellipse]) -fill(int $x, int $y, int $color): void .[method] ------------------------------------------------- -Заповнює область, що починається в заданій координаті (зліва вгорі 0, 0), заданим `$color`. ([докладніше |https://www.php.net/manual/en/function.imagefill]) +fill(int $x, int $y, ImageColor $color): void .[method] +------------------------------------------------------- +Заповнює область, починаючи з заданої координати (лівий верхній кут — 0, 0), заданим `$color`. ([більше |https://www.php.net/manual/en/function.imagefill]) -filledArc(int $cx, int $cy, int $w, int $h, int $s, int $e, int $color, int $style): void .[method] ---------------------------------------------------------------------------------------------------- -Малює неповну дугу з центром у заданих координатах. ([докладніше |https://www.php.net/manual/en/function.imagefilledarc]) +filledArc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color, int $style): void .[method] +--------------------------------------------------------------------------------------------------------------------------------------------- +Малює часткову дугу з центром у заданих координатах. ([більше |https://www.php.net/manual/en/function.imagefilledarc]) -filledEllipse(int $cx, int $cy, int $w, int $h, int $color): void .[method] ---------------------------------------------------------------------------- -Малює еліпс із центром у заданих координатах. ([докладніше |https://www.php.net/manual/en/function.imagefilledellipse]) +filledEllipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color): void .[method] +----------------------------------------------------------------------------------------------------- +Малює еліпс з центром у заданих координатах. ([більше |https://www.php.net/manual/en/function.imagefilledellipse]) -filledPolygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------------- -Створює заповнений багатокутник на зображенні. ([докладніше |https://www.php.net/manual/en/function.imagefilledpolygon]) +filledPolygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------------- +Створює в зображенні заповнений багатокутник. ([більше |https://www.php.net/manual/en/function.imagefilledpolygon]) -filledRectangle(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] -------------------------------------------------------------------------------- -Створює прямокутник, заповнений `$color` на зображенні, починаючи з точки 1 і закінчуючи точкою 2. Точка 0, 0 - лівий верхній кут зображення. ([докладніше |https://www.php.net/manual/en/function.imagefilledrectangle]) +filledRectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------- +Створює прямокутник, заповнений `$color` в зображенні, починаючи з точки `$x1` & `$y1` і закінчуючи точкою `$x2` & `$y2`. Точка 0, 0 — це лівий верхній кут зображення. ([більше |https://www.php.net/manual/en/function.imagefilledrectangle]) -fillToBorder(int $x, int $y, int $border, int $color): void .[method] ---------------------------------------------------------------------- -Створює заливку, колір межі якої визначається `$border`. Початкова точка заливки - `$x`, `$y` (лівий верхній кут - 0, 0), а область заливається кольором `$color`. ([докладніше |https://www.php.net/manual/en/function.imagefilltoborder]) +filledRectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------------- +Створює прямокутник, заповнений `$color` в зображенні, починаючи з точки `$left` & `$top` з шириною `$width` та висотою `$height`. Точка 0, 0 — це лівий верхній кут зображення. + + +fillToBorder(int $x, int $y, int $border, ImageColor $color): void .[method] +---------------------------------------------------------------------------- +Виконує заливку, колір межі якої визначено за допомогою `$border`. Початковою точкою заливки є `$x`, `$y` (лівий верхній кут — 0, 0), а область заповнюється кольором `$color`. ([більше |https://www.php.net/manual/en/function.imagefilltoborder]) filter(int $filtertype, int ...$args): void .[method] ----------------------------------------------------- -Застосовує заданий фільтр `$filtertype` до зображення. ([докладніше |https://www.php.net/manual/en/function.imagefilter]) +Застосовує заданий фільтр `$filtertype` до зображення. ([більше |https://www.php.net/manual/en/function.imagefilter]) flip(int $mode): void .[method] ------------------------------- -Інвертує зображення за заданою адресою `$mode`. ([докладніше |https://www.php.net/manual/en/function.imageflip]) +Перевертає зображення за допомогою заданого `$mode`. ([більше |https://www.php.net/manual/en/function.imageflip]) -ftText(int $size, int $angle, int $x, int $y, int $col, string $fontFile, string $text, array $extrainfo=null): array .[method] -------------------------------------------------------------------------------------------------------------------------------- -Пишіть текст на зображенні, використовуючи шрифти FreeType 2. ([докладніше |https://www.php.net/manual/en/function.imagefttext]) +ftText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +---------------------------------------------------------------------------------------------------------------------------------------- +Написує текст на зображенні. ([більше |https://www.php.net/manual/en/function.imagefttext]) gammaCorrect(float $inputgamma, float $outputgamma): void .[method] ------------------------------------------------------------------- -Застосувати гамма-корекцію до зображення відносно вхідної та вихідної гами. ([докладніше |https://www.php.net/manual/en/function.imagegammacorrect]) +Застосовує гамма-корекцію до зображення відносно вхідної та вихідної гамми. ([більше |https://www.php.net/manual/en/function.imagegammacorrect]) getClip(): array .[method] -------------------------- -Повертає поточний обріз, тобто область, за межами якої не будуть малюватися пікселі. ([докладніше |https://www.php.net/manual/en/function.imagegetclip]) +Повертає поточну область обрізки, тобто область, за межами якої не будуть намальовані жодні пікселі. ([більше |https://www.php.net/manual/en/function.imagegetclip]) getHeight(): int .[method] @@ -504,7 +545,7 @@ getHeight(): int .[method] getImageResource(): resource|GdImage .[method] ---------------------------------------------- -Повертає вихідний ресурс. +Повертає оригінальний ресурс. getWidth(): int .[method] @@ -512,137 +553,142 @@ getWidth(): int .[method] Повертає ширину зображення. -interlace(int $interlace=null): int .[method] ---------------------------------------------- -Увімкнення або вимкнення режиму черезрядкової розгортки. Якщо встановлено черезрядковий режим і зображення зберігається у форматі JPEG, воно буде збережено як прогресивний JPEG. ([докладніше |https://www.php.net/manual/en/function.imageinterlace]) +interlace(?int $interlace=null): int .[method] +---------------------------------------------- +Вмикає або вимикає режим черезрядкової розгортки. Якщо режим черезрядкової розгортки встановлено і зображення зберігається як JPEG, воно буде збережено як прогресивний JPEG. ([більше |https://www.php.net/manual/en/function.imageinterlace]) isTrueColor(): bool .[method] ----------------------------- -Визначте, чи є зображення truecolor. ([докладніше |https://www.php.net/manual/en/function.imageistruecolor]) +Визначає, чи є зображення truecolor. ([більше |https://www.php.net/manual/en/function.imageistruecolor]) layerEffect(int $effect): void .[method] ---------------------------------------- -Встановіть прапор альфа-змішання для використання ефектів нашарування. ([докладніше |https://www.php.net/manual/en/function.imagelayereffect]) +Встановлює прапорець змішування альфа, щоб використовувати ефекти шарів. ([більше |https://www.php.net/manual/en/function.imagelayereffect]) -line(int $x1, int $y1, int $x2, int $y2, int $color): void .[method] --------------------------------------------------------------------- -Проводить лінію між двома заданими точками. ([докладніше |https://www.php.net/manual/en/function.imageline]) +line(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +--------------------------------------------------------------------------- +Малює лінію між двома заданими точками. ([більше |https://www.php.net/manual/en/function.imageline]) -openPolygon(array $points, int $numPoints, int $color): void .[method] ----------------------------------------------------------------------- -Малює відкритий багатокутник на зображенні. На відміну від `polygon()`, між останньою і першою точкою не проводиться лінія. ([докладніше |https://www.php.net/manual/en/function.imageopenpolygon]) +openPolygon(array $points, ImageColor $color): void .[method] +------------------------------------------------------------- +Малює на зображенні відкритий багатокутник. На відміну від `polygon()`, між останньою та першою точкою лінія не малюється. ([більше |https://www.php.net/manual/en/function.imageopenpolygon]) paletteCopy(Image $source): void .[method] ------------------------------------------ -Копіює палітру з сайту `$source` у зображення. ([докладніше |https://www.php.net/manual/en/function.imagepalettecopy]) +Копіює палітру з `$source` до зображення. ([більше |https://www.php.net/manual/en/function.imagepalettecopy]) paletteToTrueColor(): void .[method] ------------------------------------ -Перетворює зображення на основі палітри в повнокольорове зображення. ([докладніше |https://www.php.net/manual/en/function.imagepalettetotruecolor]) +Перетворює зображення на основі палітри на повнокольорове зображення. ([більше |https://www.php.net/manual/en/function.imagepalettetotruecolor]) place(Image $image, int|string $left=0, int|string $top=0, int $opacity=100): Image .[method] --------------------------------------------------------------------------------------------- -Копіює `$image` у зображення за координатами `$left` і `$top`. Координати можуть бути вказані як цілі числа в пікселях або рядки у відсотках (наприклад, `'50%'`). +Копіює `$image` до зображення на координати `$left` та `$top`. Координати можна задавати як цілі числа в пікселях або рядки у відсотках (наприклад, `'50%'`). -polygon(array $points, int $numPoints, int $color): void .[method] ------------------------------------------------------------------- -Створює багатокутник на зображенні. ([докладніше |https://www.php.net/manual/en/function.imagepolygon]) +polygon(array $points, ImageColor $color): void .[method] +--------------------------------------------------------- +Створює в зображенні багатокутник. ([більше |https://www.php.net/manual/en/function.imagepolygon]) -rectangle(int $x1, int $y1, int $x2, int $y2, int $col): void .[method] ------------------------------------------------------------------------ -Створює прямокутник за заданими координатами. ([докладніше |https://www.php.net/manual/en/function.imagerectangle]) +rectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color): void .[method] +-------------------------------------------------------------------------------- +Створює прямокутник на заданих координатах. ([більше |https://www.php.net/manual/en/function.imagerectangle]) + + +rectangleWH(int $left, int $top, int $width, int $height, ImageColor $color): void .[method] +-------------------------------------------------------------------------------------------- +Створює прямокутник на заданих координатах. resize(int|string $width, int|string $height, int $flags=Image::OrSmaller): Image .[method] ------------------------------------------------------------------------------------------- -Зміна розмірів зображення, [додаткова інформація |#Image-Resize]. Розміри можуть бути вказані як цілі числа в пікселях або рядки у відсотках (наприклад, `'50%'`). +Змінює розмір зображення, [більше інформації |#Зміна розміру]. Розміри можна задавати як цілі числа в пікселях або рядки у відсотках (наприклад, `'50%'`). -resolution(int $resX=null, int $resY=null): mixed .[method] ------------------------------------------------------------ -Встановлює або повертає роздільну здатність зображення в DPI (точках на дюйм). Якщо жоден із додаткових параметрів не вказано, поточну роздільну здатність повертають у вигляді індексованого поля. Якщо вказано тільки `$resX`, то горизонтальна і вертикальна роздільна здатність встановлюється на це значення. Якщо вказано обидва додаткові параметри, горизонтальну та вертикальну роздільну здатність встановлюють на ці значення. +resolution(?int $resX=null, ?int $resY=null): mixed .[method] +------------------------------------------------------------- +Встановлює або повертає роздільну здатність зображення в DPI (точки на дюйм). Якщо не задано жодного з необов'язкових параметрів, поточна роздільна здатність повертається як індексований масив. Якщо задано лише `$resX`, горизонтальна та вертикальна роздільна здатність встановлюються на це значення. Якщо задано обидва необов'язкові параметри, горизонтальна та вертикальна роздільна здатність встановлюються на ці значення. -Роздільна здатність використовується як метаінформація тільки під час читання і запису зображень у формати, що підтримують таку інформацію (наразі це PNG і JPEG). Це не впливає ні на які операції малювання. Роздільна здатність нових зображень за замовчуванням становить 96 DPI. ([докладніше |https://www.php.net/manual/en/function.imageresolution]) +Роздільна здатність використовується лише як метаінформація, коли зображення читаються та записуються у формати, що підтримують цей тип інформації (наразі PNG та JPEG). Це не впливає на жодні операції малювання. Стандартна роздільна здатність нових зображень — 96 DPI. ([більше |https://www.php.net/manual/en/function.imageresolution]) rotate(float $angle, int $backgroundColor): Image .[method] ----------------------------------------------------------- -Повертає зображення на вказане значення `$angle` у градусах. Центром обертання є центр зображення, і повернуте зображення може мати розміри, відмінні від розмірів вихідного зображення. ([докладніше |https://www.php.net/manual/en/function.imagerotate]) +Повертає зображення за допомогою заданого `$angle` у градусах. Центр обертання — це центр зображення, і повернуте зображення може мати інші розміри, ніж оригінальне зображення. ([більше |https://www.php.net/manual/en/function.imagerotate]) .[note] -Вимагає наявності *Bundled GD extension*, тому може працювати не скрізь. +Потребує наявності *Bundled GD extension*, тому може працювати не скрізь. -save(string $file, int $quality=null, int $type=null): void .[method] ---------------------------------------------------------------------- +save(string $file, ?int $quality=null, ?int $type=null): void .[method] +----------------------------------------------------------------------- Зберігає зображення у файл. -Якість стиснення знаходиться в діапазоні 0...100 для JPEG (за замовчуванням 85), WEBP (за замовчуванням 80) і AVIF (за замовчуванням 30) та 0...9 для PNG (за замовчуванням 9). Якщо тип не очевидний з розширення файлу, ви можете вказати його за допомогою однієї з констант `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` та `Image::BMP`. +Якість стиснення знаходиться в діапазоні 0..100 для JPEG (за замовчуванням 85), WEBP (за замовчуванням 80) та AVIF (за замовчуванням 30) і 0..9 для PNG (за замовчуванням 9). Якщо тип не зрозумілий з розширення файлу, ви можете вказати його за допомогою однієї з констант `ImageType`. saveAlpha(bool $saveflag): void .[method] ----------------------------------------- -Встановлює прапор збереження повної інформації альфа-каналу (на відміну від монохромної прозорості) під час збереження зображень PNG. +Встановлює прапорець, чи зберігати повну інформацію про альфа-канал при збереженні зображень PNG (на відміну від одноколірної прозорості). -Для збереження альфа-каналу альфа-квантування має бути вимкнено (`alphaBlending(false)`). ([докладніше |https://www.php.net/manual/en/function.imagesavealpha]) +Альфа-змішування має бути вимкнено (`alphaBlending(false)`), щоб альфа-канал зберігся на першому місці. ([більше |https://www.php.net/manual/en/function.imagesavealpha]) scale(int $newWidth, int $newHeight=-1, int $mode=IMG_BILINEAR_FIXED): Image .[method] -------------------------------------------------------------------------------------- -Масштабування зображення з використанням заданого алгоритму інтерполяції. ([докладніше |https://www.php.net/manual/en/function.imagescale]) +Масштабує зображення за допомогою заданого алгоритму інтерполяції. ([більше |https://www.php.net/manual/en/function.imagescale]) -send(int $type=Image::JPEG, int $quality=null): void .[method] --------------------------------------------------------------- +send(int $type=ImageType::JPEG, ?int $quality=null): void .[method] +------------------------------------------------------------------- Виводить зображення в браузер. -Якість стиснення знаходиться в діапазоні 0...100 для JPEG (за замовчуванням 85), WEBP (за замовчуванням 80) і AVIF (за замовчуванням 30) та 0...9 для PNG (за замовчуванням 9). Тип - одна з констант `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` та `Image::BMP`. +Якість стиснення знаходиться в діапазоні 0..100 для JPEG (за замовчуванням 85), WEBP (за замовчуванням 80) та AVIF (за замовчуванням 30) і 0..9 для PNG (за замовчуванням 9). setBrush(Image $brush): void .[method] -------------------------------------- -Встановлює зображення пензля, яке буде використовуватися у всіх функціях малювання ліній (наприклад, `line()` і `polygon()`) під час малювання спеціальними кольорами IMG_COLOR_BRUSHED або IMG_COLOR_STYLEDBRUSHED. ([докладніше |https://www.php.net/manual/en/function.imagesetbrush]) +Встановлює зображення пензля, яке буде використовуватися у всіх функціях малювання ліній (наприклад, `line()` та `polygon()`) при малюванні спеціальними кольорами IMG_COLOR_BRUSHED або IMG_COLOR_STYLEDBRUSHED. ([більше |https://www.php.net/manual/en/function.imagesetbrush]) setClip(int $x1, int $y1, int $x2, int $y2): void .[method] ----------------------------------------------------------- -Встановлює поточний обріз, тобто область, за межами якої не будуть малюватися пікселі. ([докладніше |https://www.php.net/manual/en/function.imagesetclip]) +Встановлює поточну область обрізки, тобто область, за межами якої не будуть намальовані жодні пікселі. ([більше |https://www.php.net/manual/en/function.imagesetclip]) setInterpolation(int $method=IMG_BILINEAR_FIXED): void .[method] ---------------------------------------------------------------- -Задає метод інтерполяції, який впливає на методи `rotate()` і `affine()`. ([докладніше |https://www.php.net/manual/en/function.imagesetinterpolation]) +Встановлює метод інтерполяції, який впливає на методи `rotate()` та `affine()`. ([більше |https://www.php.net/manual/en/function.imagesetinterpolation]) -setPixel(int $x, int $y, int $color): void .[method] ----------------------------------------------------- -Малює піксель у вказаній координаті. ([докладніше |https://www.php.net/manual/en/function.imagesetpixel]) +setPixel(int $x, int $y, ImageColor $color): void .[method] +----------------------------------------------------------- +Малює піксель на заданій координаті. ([більше |https://www.php.net/manual/en/function.imagesetpixel]) setStyle(array $style): void .[method] -------------------------------------- -Задає стиль, який буде використовуватися всіма функціями малювання ліній (наприклад, `line()` і `polygon()`) під час малювання спеціальним кольором IMG_COLOR_STYLED або ліній зображення кольором IMG_COLOR_STYLEDBRUSHED ([докладніше |https://www.php.net/manual/en/function.imagesetstyle]). +Встановлює стиль, який мають використовувати всі функції малювання ліній (наприклад, `line()` та `polygon()`) при малюванні спеціальним кольором IMG_COLOR_STYLED або ліній зображень з кольором IMG_COLOR_STYLEDBRUSHED. ([більше |https://www.php.net/manual/en/function.imagesetstyle]) setThickness(int $thickness): void .[method] -------------------------------------------- -Встановлює товщину ліній під час малювання прямокутників, багатокутників, дуг тощо. На сайті `$thickness` пікселів. ([докладніше |https://www.php.net/manual/en/function.imagesetthickness]) +Встановлює товщину ліній при малюванні прямокутників, багатокутників, дуг тощо на `$thickness` пікселів. ([більше |https://www.php.net/manual/en/function.imagesetthickness]) setTile(Image $tile): void .[method] ------------------------------------ -Встановлює зображення плитки, яке буде використовуватися у всіх функціях заповнення регіону (наприклад, `fill()` і `filledPolygon()`) при заповненні спеціальним кольором IMG_COLOR_TILED. +Встановлює зображення плитки, яке буде використовуватися у всіх функціях заповнення областей (наприклад, `fill()` та `filledPolygon()`), коли заповнюється спеціальним кольором IMG_COLOR_TILED. -Плитка - це зображення, що використовується для заповнення області повторюваним малюнком. Як плитку можна використовувати будь-яке зображення, а задавши індекс прозорого кольору зображення плитки за допомогою `colorTransparent()`, можна створити плитку, в якій будуть просвічувати певні частини нижчого регіону. ([докладніше |https://www.php.net/manual/en/function.imagesettile]) +Плитка — це зображення, яке використовується для заповнення області повторюваним візерунком. Будь-яке зображення можна використовувати як плитку, а встановивши прозорий індекс кольору зображення плитки за допомогою `colorTransparent()`, можна створити плитку, де будуть просвічувати певні частини базової області. ([більше |https://www.php.net/manual/en/function.imagesettile]) sharpen(): Image .[method] @@ -650,31 +696,21 @@ sharpen(): Image .[method] Підвищує різкість зображення. .[note] -Вимагає наявності *Bundled GD extension*, тому може працювати не скрізь. - - -string(int $font, int $x, int $y, string $str, int $col): void .[method] ------------------------------------------------------------------------- -Виводить рядок за заданими координатами. ([докладніше |https://www.php.net/manual/en/function.imagestring]) - +Потребує наявності *Bundled GD extension*, тому може працювати не скрізь. -stringUp(int $font, int $x, int $y, string $s, int $col): void .[method] ------------------------------------------------------------------------- -Виводить рядок по вертикалі в заданих координатах. ([докладніше |https://www.php.net/manual/en/function.imagestringup]) +toString(int $type=ImageType::JPEG, ?int $quality=null): string .[method] +------------------------------------------------------------------------- +Зберігає зображення в рядок. -toString(int $type=Image::JPEG, int $quality=null): string .[method] --------------------------------------------------------------------- -Зберігає зображення в рядку. - -Якість стиснення знаходиться в діапазоні 0...100 для JPEG (за замовчуванням 85), WEBP (за замовчуванням 80) і AVIF (за замовчуванням 30) та 0...9 для PNG (за замовчуванням 9). Тип - одна з констант `Image::JPEG`, `Image::PNG`, `Image::GIF`, `Image::WEBP`, `Image::AVIF` та `Image::BMP`. +Якість стиснення знаходиться в діапазоні 0..100 для JPEG (за замовчуванням 85), WEBP (за замовчуванням 80) та AVIF (за замовчуванням 30) і 0..9 для PNG (за замовчуванням 9). trueColorToPalette(bool $dither, int $ncolors): void .[method] -------------------------------------------------------------- -Перетворює truecolor зображення в палітру. ([докладніше |https://www.php.net/manual/en/function.imagetruecolortopalette]) +Перетворює зображення truecolor на палітрове. ([більше |https://www.php.net/manual/en/function.imagetruecolortopalette]) -ttfText(int $size, int $angle, int $x, int $y, int $color, string $fontfile, string $text): array .[method] ------------------------------------------------------------------------------------------------------------ -Друкує заданий текст у зображення з використанням шрифтів TrueType. ([докладніше |https://www.php.net/manual/en/function.imagettftext]) +ttfText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options=[]): array .[method] +----------------------------------------------------------------------------------------------------------------------------------------- +Виводить заданий текст на зображення. ([більше |https://www.php.net/manual/en/function.imagettftext]) diff --git a/utils/uk/iterables.texy b/utils/uk/iterables.texy new file mode 100644 index 0000000000..979211b4aa --- /dev/null +++ b/utils/uk/iterables.texy @@ -0,0 +1,170 @@ +Робота з ітераторами +******************** + +.[perex]{data-version:4.0.4} +[api:Nette\Utils\Iterables] — це статичний клас з функціями для роботи з ітераторами. Його аналогом для масивів є [Nette\Utils\Arrays|arrays]. + + +Встановлення: + +```shell +composer require nette/utils +``` + +Усі приклади передбачають створений псевдонім: + +```php +use Nette\Utils\Iterables; +``` + + +contains(iterable $iterable, $value): bool .[method] +---------------------------------------------------- + +Шукає задане значення в ітераторі. Використовує строге порівняння (`===`) для перевірки збігу. Повертає `true`, якщо значення знайдено, інакше `false`. + +```php +Iterables::contains(new ArrayIterator([1, 2, 3]), 1); // true +Iterables::contains(new ArrayIterator([1, 2, 3]), '1'); // false +``` + +Цей метод корисний, коли потрібно швидко дізнатися, чи знаходиться конкретне значення в ітераторі, не перебираючи всі елементи вручну. + + +containsKey(iterable $iterable, $key): bool .[method] +----------------------------------------------------- + +Шукає заданий ключ в ітераторі. Використовує строге порівняння (`===`) для перевірки збігу. Повертає `true`, якщо ключ знайдено, інакше `false`. + +```php +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 0); // true +Iterables::containsKey(new ArrayIterator([1, 2, 3]), 4); // false +``` + + +every(iterable $iterable, callable $predicate): bool .[method] +-------------------------------------------------------------- + +Перевіряє, чи всі елементи ітератора задовольняють умову, визначену в `$predicate`. Функція `$predicate` має сигнатуру `function ($value, $key, iterable $iterable): bool` і повинна повертати `true` для кожного елемента, щоб метод `every()` повернув `true`. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isBelowThreshold = fn($value) => $value < 40; +$res = Iterables::every($iterator, $isBelowThreshold); // true +``` + +Цей метод корисний для перевірки, чи всі елементи в колекції задовольняють певну умову, наприклад, чи всі числа менші за певне значення. + + +filter(iterable $iterable, callable $predicate): Generator .[method] +-------------------------------------------------------------------- + +Створює новий ітератор, який містить лише ті елементи з вихідного ітератора, які задовольняють умову, визначену в `$predicate`. Функція `$predicate` має сигнатуру `function ($value, $key, iterable $iterable): bool` і повинна повертати `true` для елементів, які мають бути збережені. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::filter($iterator, fn($v) => $v < 3); +// 1, 2 +``` + +Метод використовує генератор, що означає, що фільтрація відбувається поступово під час перебору результату. Це ефективно з точки зору пам'яті та дозволяє обробляти навіть дуже великі колекції. Якщо ви не пройдете всі елементи результуючого ітератора, ви заощадите обчислювальну потужність, оскільки не всі елементи вихідного ітератора будуть оброблені. + + +first(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +------------------------------------------------------------------------------------------- + +Повертає перший елемент ітератора. Якщо задано `$predicate`, повертає перший елемент, який задовольняє задану умову. Функція `$predicate` має сигнатуру `function ($value, $key, iterable $iterable): bool`. Якщо не знайдено жодного відповідного елемента, викликається функція `$else` (якщо вона задана) і повертається її результат. Якщо `$else` не задано, повертається `null`. + +```php +Iterables::first(new ArrayIterator([1, 2, 3])); // 1 +Iterables::first(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 3 +Iterables::first(new ArrayIterator([])); // null +Iterables::first(new ArrayIterator([]), else: fn() => false); // false +``` + +Цей метод корисний, коли потрібно швидко отримати перший елемент колекції або перший елемент, що задовольняє певну умову, не перебираючи всю колекцію вручну. + + +firstKey(iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed .[method] +---------------------------------------------------------------------------------------------- + +Повертає ключ першого елемента ітератора. Якщо задано `$predicate`, повертає ключ першого елемента, який задовольняє задану умову. Функція `$predicate` має сигнатуру `function ($value, $key, iterable $iterable): bool`. Якщо не знайдено жодного відповідного елемента, викликається функція `$else` (якщо вона задана) і повертається її результат. Якщо `$else` не задано, повертається `null`. + +```php +Iterables::firstKey(new ArrayIterator([1, 2, 3])); // 0 +Iterables::firstKey(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 2 +Iterables::firstKey(new ArrayIterator(['a' => 1, 'b' => 2])); // 'a' +Iterables::firstKey(new ArrayIterator([])); // null +``` + + +map(iterable $iterable, callable $transformer): Generator .[method] +------------------------------------------------------------------- + +Створює новий ітератор, застосовуючи функцію `$transformer` до кожного елемента вихідного ітератора. Функція `$transformer` має сигнатуру `function ($value, $key, iterable $iterable): mixed`, і її повернене значення використовується як нове значення елемента. + +```php +$iterator = new ArrayIterator([1, 2, 3]); +$iterator = Iterables::map($iterator, fn($v) => $v * 2); +// 2, 4, 6 +``` + +Метод використовує генератор, що означає, що трансформація відбувається поступово під час перебору результату. Це ефективно з точки зору пам'яті та дозволяє обробляти навіть дуже великі колекції. Якщо ви не пройдете всі елементи результуючого ітератора, ви заощадите обчислювальну потужність, оскільки не всі елементи вихідного ітератора будуть оброблені. + + +mapWithKeys(iterable $iterable, callable $transformer): Generator .[method] +--------------------------------------------------------------------------- + +Створює новий ітератор, трансформуючи значення та ключі вихідного ітератора. Функція `$transformer` має сигнатуру `function ($value, $key, iterable $iterable): ?array{$newKey, $newValue}`. Якщо `$transformer` повертає `null`, елемент пропускається. Для збережених елементів перший елемент повернутого масиву використовується як новий ключ, а другий елемент — як нове значення. + +```php +$iterator = new ArrayIterator(['a' => 1, 'b' => 2]); +$iterator = Iterables::mapWithKeys($iterator, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null); +// [4 => 'B'] +``` + +Так само, як `map()`, цей метод використовує генератор для поступової обробки та ефективної роботи з пам'яттю. Це дозволяє працювати з великими колекціями та економити обчислювальну потужність при частковому переборі результату. + + +memoize(iterable $iterable): IteratorAggregate .[method] +-------------------------------------------------------- + +Створює обгортку навколо ітератора, яка під час ітерації кешує його ключі та значення. Це дозволяє повторно ітерувати дані без необхідності знову проходити вихідне джерело даних. + +```php +$iterator = /* дані, які не можна ітерувати більше одного разу */ +$memoized = Iterables::memoize($iterator); +// Тепер ви можете ітерувати $memoized кілька разів без втрати даних +``` + +Цей метод корисний у ситуаціях, коли потрібно кілька разів пройти один і той же набір даних, але вихідний ітератор не дозволяє повторної ітерації або повторний перебір був би витратним (наприклад, при читанні даних з бази даних або файлу). + + +some(iterable $iterable, callable $predicate): bool .[method] +------------------------------------------------------------- + +Перевіряє, чи хоча б один елемент ітератора задовольняє умову, визначену в `$predicate`. Функція `$predicate` має сигнатуру `function ($value, $key, iterable $iterable): bool` і повинна повертати `true` для хоча б одного елемента, щоб метод `some()` повернув `true`. + +```php +$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]); +$isEven = fn($value) => $value % 2 === 0; +$res = Iterables::some($iterator, $isEven); // true +``` + +Цей метод корисний для швидкої перевірки, чи існує в колекції хоча б один елемент, що задовольняє певну умову, наприклад, чи містить колекція хоча б одне парне число. + +Див. [#every()]. + + +toIterator(iterable $iterable): Iterator .[method] +-------------------------------------------------- + +Перетворює будь-який ітерований об'єкт (array, Traversable) на Iterator. Якщо вхідні дані вже є Iterator, повертає його без змін. + +```php +$array = [1, 2, 3]; +$iterator = Iterables::toIterator($array); +// Тепер у вас є Iterator замість масиву +``` + +Цей метод корисний, коли потрібно забезпечити наявність Iterator, незалежно від типу вхідних даних. Це може бути корисним при створенні функцій, які працюють з різними типами ітерованих даних. diff --git a/utils/uk/json.texy b/utils/uk/json.texy index 63cea39d9e..f9f422a1fc 100644 --- a/utils/uk/json.texy +++ b/utils/uk/json.texy @@ -2,7 +2,7 @@ ************* .[perex] -[api:Nette\Utils\Json] це статичний клас із функціями для кодування та декодування формату JSON. Він обробляє вразливості в різних версіях PHP і викидає винятки в разі виникнення помилок. +[api:Nette\Utils\Json] — це статичний клас з функціями для кодування та декодування формату JSON. Він обробляє вразливості різних версій PHP та викидає винятки при помилках. Встановлення: @@ -11,44 +11,44 @@ composer require nette/utils ``` -У всіх прикладах передбачається, що псевдонім уже створено: +Усі приклади передбачають створений псевдонім: ```php use Nette\Utils\Json; ``` -Використання .[#toc-usage] -========================== +Використання +============ encode(mixed $value, bool $pretty=false, bool $asciiSafe=false, bool $htmlSafe=false, bool $forceObjects=false): string .[method] --------------------------------------------------------------------------------------------------------------------------------- -Конвертує `$value` у формат JSON. +Перетворює `$value` у формат JSON. -Якщо встановлено значення `$pretty`, він форматує JSON для легшого читання та ясності: +При встановленні `$pretty` форматує JSON для легшого читання та оглядності: ```php Json::encode($value); // повертає JSON -Json::encode($value, pretty: true); // повертає чистіший JSON +Json::encode($value, pretty: true); // повертає більш оглядний JSON ``` -На `$asciiSafe` він генерує виведення в ASCII, тобто замінює символи юнікоду послідовністю `\uxxxx`: +При `$asciiSafe` генерує вивід у ASCII, тобто символи unicode замінюються послідовностями `\uxxxx`: ```php -Json::encode('žluťoučký', asciiSafe: true); -// '"\u017elu\u0165ou\u010dk\u00fd"' +Json::encode('жовтий', asciiSafe: true); +// '"\u0436\u043e\u0432\u0442\u0438\u0439"' ``` -Параметр `$htmlSafe` гарантує, що виведення не містить символів, які мають спеціальне значення в HTML: +Параметр `$htmlSafe` забезпечує, що вивід не міститиме символів, що мають спеціальне значення в HTML: ```php Json::encode('onesendJson($data)`, який можна викликати в методі `action*()`, наприклад, див. розділ [Надсилання відповіді |application:presenters#Sending a Response]. +Для цього можна використати метод `$this->sendJson($data)`, який можна викликати, наприклад, у методі `action*()`, див. [Надсилання відповіді |application:presenters#Надсилання відповіді]. diff --git a/utils/uk/paginator.texy b/utils/uk/paginator.texy index a2b9d2de10..c2b10eca98 100644 --- a/utils/uk/paginator.texy +++ b/utils/uk/paginator.texy @@ -1,8 +1,8 @@ -Пагінатор +Paginator ********* .[perex] -Потрібно розбити на сторінки дамп даних? Оскільки математика пагінації може бути складною, [api:Nette\Utils\Paginator] може допомогти вам у цьому. +Потрібно розбити вивід даних на сторінки? Оскільки математика пагінації може бути підступною, вам допоможе [api:Nette\Utils\Paginator]. Встановлення: @@ -11,22 +11,22 @@ composer require nette/utils ``` -Створіть об'єкт підкачки і задайте його основну інформацію: +Створимо об'єкт пагінатора та встановимо йому основну інформацію: ```php $paginator = new Nette\Utils\Paginator; $paginator->setPage(1); // номер поточної сторінки $paginator->setItemsPerPage(30); // кількість елементів на сторінці -$paginator->setItemCount(356); // загальна кількість елементів, якщо відомо +$paginator->setItemCount(356); // загальна кількість елементів, якщо відома ``` Сторінки нумеруються з 1. Ми можемо змінити це за допомогою `setBase()`: ```php -$paginator->setBase(0); // нумерація з 0 +$paginator->setBase(0); // нумеруємо з 0 ``` -Тепер об'єкт надаватиме всю основну інформацію, корисну при створенні сторінки підкачки. Наприклад, ви можете передати його в шаблон і використовувати його там. +Об'єкт тепер надасть всю основну інформацію, корисну при створенні пагінатора. Ви можете, наприклад, передати його в шаблон і там використовувати. ```php $paginator->isFirst(); // ми на першій сторінці? @@ -36,13 +36,13 @@ $paginator->getFirstPage(); // номер першої сторінки $paginator->getLastPage(); // номер останньої сторінки $paginator->getFirstItemOnPage(); // порядковий номер першого елемента на сторінці $paginator->getLastItemOnPage(); // порядковий номер останнього елемента на сторінці -$paginator->getPageIndex(); // номер поточної сторінки, нумерація від 0 +$paginator->getPageIndex(); // номер поточної сторінки, нумерованої з 0 $paginator->getPageCount(); // загальна кількість сторінок -$paginator->getItemsPerPage(); // кількість елементів на сторінці -$paginator->getItemCount(); // загальна кількість елементів, якщо відомо +$paginator->getItemsPerPage(); // кількість елементів на сторінку +$paginator->getItemCount(); // загальна кількість елементів, якщо відома ``` -Конструктор сторінок допоможе в складанні SQL-запитів. Методи `getLength()` і `getOffset()` повертають значення для використання в пунктах LIMIT і OFFSET: +Пагінатор допоможе при формулюванні SQL запиту. Методи `getLength()` та `getOffset()` повертають значення, які ми використаємо в клаузулах LIMIT та OFFSET: ```php $result = $database->query( @@ -52,7 +52,7 @@ $result = $database->query( ); ``` -Якщо нам потрібно розташувати сторінки у зворотному порядку, тобто сторінка 1 відповідає найвищому зміщенню, ми використовуємо `getCountdownOffset()`: +Якщо потрібно розбити на сторінки у зворотному порядку, тобто сторінка № 1 відповідає найбільшому зміщенню, використаємо `getCountdownOffset()`: ```php $result = $database->query( @@ -62,4 +62,4 @@ $result = $database->query( ); ``` -Приклад того, як використовувати це в додатку, наведено в книзі [Database Results Pagination |best-practices:pagination] Cookbook. +Приклад використання в додатку знайдете в кулінарній книзі [Пагінація результатів бази даних |best-practices:pagination]. diff --git a/utils/uk/random.texy b/utils/uk/random.texy index 60f05e66ee..989cc33c4e 100644 --- a/utils/uk/random.texy +++ b/utils/uk/random.texy @@ -2,7 +2,7 @@ *************************** .[perex] -[api:Nette\Utils\Random] це статичний клас для генерації криптографічно безпечних псевдовипадкових рядків. +[api:Nette\Utils\Random] — це статичний клас для генерації криптографічно безпечних псевдовипадкових рядків. Встановлення: @@ -15,11 +15,11 @@ composer require nette/utils generate(int $length=10, string $charlist=`'0-9a-z'`): string .[method] ----------------------------------------------------------------------- -Генерує випадковий рядок заданої довжини із символів, зазначених у `$charlist`. Ви також можете використовувати інтервали, записані, наприклад, як `0-9`. +Генерує випадковий рядок заданої довжини з символів, вказаних параметром `$charlist`. Можна використовувати також інтервали, записані як, наприклад, `0-9`. ```php use Nette\Utils\Random; -Random::generate(10); // '6zq3a1nl8n' -Random::generate(5, 'A-Z'); // 'HLKUR' +Random::generate(10); // напр. '6zq3a1nl8n' +Random::generate(5, 'A-Z'); // напр. 'HLKUR' ``` diff --git a/utils/uk/reflection.texy b/utils/uk/reflection.texy index b3a9934c62..3ac160b0e4 100644 --- a/utils/uk/reflection.texy +++ b/utils/uk/reflection.texy @@ -1,8 +1,8 @@ -PHP Відображення -**************** +PHP Рефлексія +************* .[perex] -[api:Nette\Utils\Reflection] це статичний клас із корисними функціями для відображення PHP. Його мета - виправити недоліки рідних класів і уніфікувати поведінку в різних версіях PHP. +[api:Nette\Utils\Reflection] — це статичний клас з корисними функціями для PHP рефлексії. Його завданням є виправлення недоліків нативних класів та уніфікація поведінки між різними версіями PHP. Встановлення: @@ -11,7 +11,7 @@ PHP Відображення composer require nette/utils ``` -У всіх прикладах передбачається, що псевдонім уже створено: +Усі приклади передбачають створений псевдонім: ```php use Nette\Utils\Reflection; @@ -21,13 +21,13 @@ use Nette\Utils\Reflection; areCommentsAvailable(): bool .[method] -------------------------------------- -Визначте, чи має reflection доступ до коментарів PHPdoc. Коментарі можуть бути недоступні через кеш опкодів, див. наприклад, директиву [opcache.save-comments |https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments]. +Перевіряє, чи має рефлексія доступ до коментарів PHPdoc. Коментарі можуть бути недоступними через кеш опкодів, див. наприклад директиву [opcache.save-comments|https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments]. expandClassName(string $name, ReflectionClass $context): string .[method] ------------------------------------------------------------------------- -Розширює ім'я класу `$name` до його повного імені в контексті класу `$context`, тобто в контексті його простору імен і певних псевдонімів. Таким чином, тут фактично йдеться про те, як парсер PHP `$name` зрозумів би , якби він був написаний у тілі класу `$context`. +Розширює ім'я класу `$name` до його повного імені в контексті класу `$context`, тобто в контексті його простору імен та визначених псевдонімів. Тобто, фактично, каже, як PHP парсер зрозумів би `$name`, якби він був записаний у тілі класу `$context`. ```php namespace Foo; @@ -47,9 +47,9 @@ Reflection::expandClassName('Baz', $context); // 'Foo\Baz' getMethodDeclaringMethod(ReflectionMethod $method): ReflectionMethod .[method] ------------------------------------------------------------------------------ -Повертає відображення методу, що містить оголошення методу `$method`. Зазвичай кожен метод є власним оголошенням, але тіло методу може перебувати в трейте і під іншим ім'ям. +Повертає рефлексію методу, який містить декларацію методу `$method`. Зазвичай кожен метод є своєю власною декларацією, але тіло методу може знаходитися також у трейті та під іншим ім'ям. -Оскільки PHP не надає достатньої інформації для визначення фактичного оголошення, Nette використовує свою власну евристику, яка **повинна** бути надійною. +Оскільки PHP не надає достатньої інформації, за допомогою якої можна визначити справжню декларацію, Nette використовує власну евристику, яка **має бути** надійною. ```php trait DemoTrait @@ -76,9 +76,9 @@ Reflection::getMethodDeclaringMethod($method); // ReflectionMethod('DemoTrait::f getPropertyDeclaringClass(ReflectionProperty $prop): ReflectionClass .[method] ------------------------------------------------------------------------------ -Повертає відображення класу або трейта, який містить оголошення властивості `$prop`. Властивість може бути оголошена в трейте. +Повертає рефлексію класу або трейту, який містить декларацію властивості `$prop`. Властивість може бути оголошена також у трейті. -Оскільки PHP не надає достатньої інформації для визначення фактичного оголошення, Nette використовує свою власну евристику, яка **не** надійна. +Оскільки PHP не надає достатньої інформації, за допомогою якої можна визначити справжню декларацію, Nette використовує власну евристику, яка **не є** надійною. ```php trait DemoTrait @@ -96,25 +96,25 @@ $prop = new ReflectionProperty(DemoClass::class, 'foo'); Reflection::getPropertyDeclaringClass($prop); // ReflectionClass('DemoTrait') ``` -/--comment - - - - - - - +isBuiltinType(string $type): bool .[method deprecated] +------------------------------------------------------ +Перевіряє, чи є `$type` вбудованим типом PHP. В іншому випадку це ім'я класу. +```php +Reflection::isBuiltinType('string'); // true +Reflection::isBuiltinType('Foo'); // false +``` -\-- +.[note] +Використовуйте [Nette\Utils\Validator::isBuiltinType() |validators#isBuiltinType]. toString($reflection): string .[method] --------------------------------------- -Перетворює відображення на зрозумілий людині рядок. +Перетворює рефлексію на зрозумілий людині рядок. ```php $func = new ReflectionFunction('func'); diff --git a/utils/uk/smartobject.texy b/utils/uk/smartobject.texy index 4695c2fde2..9a83f32ae4 100644 --- a/utils/uk/smartobject.texy +++ b/utils/uk/smartobject.texy @@ -1,8 +1,8 @@ -SmartObject і StaticClass -************************* +SmartObject +*********** .[perex] -SmartObject додає підтримку *властивостей* у класи PHP. StaticClass використовується для позначення статичних класів. +SmartObject роками покращував поведінку об'єктів у PHP. З версії PHP 8.4 усі його функції вже є частиною самого PHP, чим він завершив свою історичну місію бути піонером сучасного об'єктного підходу в PHP. Встановлення: @@ -11,19 +11,64 @@ SmartObject додає підтримку *властивостей* у клас composer require nette/utils ``` +SmartObject виник у 2007 році як революційне вирішення недоліків тодішньої об'єктної моделі PHP. У той час, коли PHP страждав від низки проблем з об'єктним дизайном, він приніс значне покращення та спрощення роботи для розробників. Став легендарною частиною фреймворку Nette. Пропонував функціональність, яку PHP отримав лише через багато років – від контролю доступу до властивостей об'єктів до складних синтаксичних цукерок. З приходом PHP 8.4 він завершив свою історичну місію, оскільки всі його функції стали нативною частиною мови. Він випередив розвиток PHP на вражаючих 17 років. -Властивості, геттери та сеттери .[#toc-properties-getters-and-setters] -====================================================================== +Технічно SmartObject пройшов цікавий розвиток. Спочатку він був реалізований як клас `Nette\Object`, від якого інші класи успадковували необхідну функціональність. Кардинальна зміна відбулася з PHP 5.4, який приніс підтримку трейтів. Це дозволило трансформувати його у вигляд трейту `Nette\SmartObject`, що принесло більшу гнучкість – розробники могли використовувати функціональність навіть у класах, які вже успадковували від іншого класу. У той час як початковий клас `Nette\Object` зник з приходом PHP 7.2 (який заборонив називати класи словом `Object`), трейт `Nette\SmartObject` живе далі. -У сучасних об'єктно-орієнтованих мовах (наприклад, C#, Python, Ruby, JavaScript) термін *властивість* відноситься до [спеціальних членів класів |https://en.wikipedia.org/wiki/Property_(programming)], що виглядають як змінні, але насправді представлені методами. Коли значення такої "змінної" присвоюється або зчитується, викликається відповідний метод (званий getter або setter). Це дуже зручна річ, вона дає нам повний контроль над доступом до змінних. Ми можемо перевіряти дані, що вводяться, або генерувати результати тільки тоді, коли властивість прочитано. +Розглянемо властивості, які колись пропонували `Nette\Object`, а пізніше `Nette\SmartObject`. Кожна з цих функцій свого часу представляла значний крок вперед у галузі об'єктно-орієнтованого програмування в PHP. -Властивості PHP не підтримуються, але trait `Nette\SmartObject` може їх імітувати. Як це використовувати? -- Додайте анотацію до класу у вигляді `@property $xyz` -- Створіть геттер з іменем `getXyz()` або `isXyz()`, сеттер з іменем `setXyz()` -- Геттер і сеттер мають бути *публічними* або *захищеними* і необов'язковими, тому може бути властивість *тільки для читання* або *тільки для запису*. +Консистентні стани помилок +-------------------------- +Однією з найболючіших проблем раннього PHP була неконсистентна поведінка при роботі з об'єктами. `Nette\Object` приніс порядок і передбачуваність у цей хаос. Подивимося, як виглядала початкова поведінка PHP: -Ми будемо використовувати властивість для класу Circle, щоб гарантувати, що в змінну `$radius` будуть поміщатися тільки невід'ємні числа. Замініть `public $radius` на property: +```php +echo $obj->undeclared; // E_NOTICE, пізніше E_WARNING +$obj->undeclared = 1; // проходить тихо без повідомлення +$obj->unknownMethod(); // Fatal error (неможливо перехопити за допомогою try/catch) +``` + +Fatal error завершував додаток без можливості будь-яким чином реагувати. Тихий запис у неіснуючі члени без попередження міг призвести до серйозних помилок, які було важко виявити. `Nette\Object` перехоплював усі ці випадки та викидав виняток `MemberAccessException`, що дозволяло програмістам реагувати на помилки та вирішувати їх. + +```php +echo $obj->undeclared; // викидає Nette\MemberAccessException +$obj->undeclared = 1; // викидає Nette\MemberAccessException +$obj->unknownMethod(); // викидає Nette\MemberAccessException +``` + +З PHP 7.0 мова вже не спричиняє неперехоплювані fatal error, а з PHP 8.2 доступ до неоголошених членів вважається помилкою. + + +Підказка "Did you mean?" +------------------------ +`Nette\Object` приніс дуже приємну функцію: інтелектуальну підказку при опечатках. Коли розробник робив помилку в назві методу або змінної, він не тільки повідомляв про помилку, але й пропонував допомогу у вигляді пропозиції правильної назви. Це культове повідомлення, відоме як "did you mean?", заощадило програмістам години пошуку опечаток: + +```php +class Foo extends Nette\Object +{ + public static function from($var) + { + } +} + +$foo = Foo::form($var); +// викидає Nette\MemberAccessException +// "Call to undefined static method Foo::form(), did you mean from()?" +``` + +Сучасний PHP хоч і не має жодної форми „did you mean?“, але цей додаток вміє доповнювати помилки [Tracy|tracy:]. І навіть такі помилки [самостійно виправляти |tracy:open-files-in-ide#Приклади]. + + +Властивості з контрольованим доступом +------------------------------------- +Значною інновацією, яку SmartObject приніс у PHP, були властивості з контрольованим доступом. Ця концепція, поширена в мовах як C# або Python, дозволила розробникам елегантно контролювати доступ до даних об'єкта та забезпечувати їхню консистенцію. Властивості є потужним інструментом об'єктно-орієнтованого програмування. Вони функціонують як змінні, але насправді представлені методами (гетерами та сетерами). Це дозволяє валідувати вхідні дані або генерувати значення лише в момент читання. + +Для використання властивостей потрібно: +- Додати класу анотацію у вигляді `@property $xyz` +- Створити гетер з назвою `getXyz()` або `isXyz()`, сетер з назвою `setXyz()` +- Забезпечити, щоб гетер та сетер були *public* або *protected*. Вони є необов'язковими – отже, можуть існувати як *read-only* або *write-only* властивості. + +Покажемо практичний приклад на класі Circle, де властивості використаємо для забезпечення того, щоб радіус завжди був невід'ємним числом. Замінимо початковий `public $radius` на властивість: ```php /** @@ -34,22 +79,22 @@ class Circle { use Nette\SmartObject; - private float $radius = 0.0; // not public + private float $radius = 0.0; // не public! - // геттер для властивості $radius + // гетер для властивості $radius protected function getRadius(): float { return $this->radius; } - // задатчик для властивості $radius + // сетер для властивості $radius protected function setRadius(float $radius): void { - // очищення значення перед збереженням + // значення перед збереженням санітизуємо $this->radius = max(0.0, $radius); } - // геттер для властивості $visible + // гетер для властивості $visible protected function isVisible(): bool { return $this->radius > 0; @@ -57,84 +102,30 @@ class Circle } $circle = new Circle; -$circle->radius = 10; // власне викликає setRadius(10) -echo $circle->radius; // викликаємо getRadius() -echo $circle->visible; // викликаємо isVisible() +$circle->radius = 10; // насправді викликає setRadius(10) +echo $circle->radius; // викликає getRadius() +echo $circle->visible; // викликає isVisible() ``` -Властивості - це насамперед "синтаксичний цукор"((syntactic sugar)), який покликаний зробити життя програміста солодшим за рахунок спрощення коду. Якщо вони вам не потрібні, ви не зобов'язані їх використовувати. - - -Статичні класи .[#toc-static-classes] -===================================== - -Статичні класи, тобто класи, які не призначені для інстанціювання, можуть бути позначені ознакою `Nette\StaticClass`: +З PHP 8.4 можна досягти такої ж функціональності за допомогою property hooks, які пропонують набагато елегантніший та коротший синтаксис: ```php -class Strings +class Circle { - use Nette\StaticClass; -} -``` - -Коли ви намагаєтеся створити екземпляр, виникає виняток `Error`, який вказує на те, що клас є статичним. - - -Погляд в історію .[#toc-a-look-into-the-history] -================================================ - -SmartObject використовували для поліпшення і виправлення поведінки класу в багатьох відношеннях, але розвиток PHP зробив більшість початкових функцій зайвими. Тому нижче ми розглянемо історію розвитку цих функцій. - -Від самого початку об'єктна модель PHP страждала від низки серйозних недоліків і неефективності. Це стало причиною створення класу `Nette\Object` (у 2007 році), який спробував усунути їх і поліпшити досвід використання PHP. Цього виявилося достатньо, щоб інші класи успадкували від нього і отримали ті переваги, які він приніс. Коли в PHP 5.4 з'явилася підтримка трейтів, клас `Nette\Object` було замінено на `Nette\SmartObject`. Таким чином, більше не було необхідності успадковуватися від спільного предка. Крім того, trait можна було використовувати в класах, які вже успадковувалися від іншого класу. Остаточний кінець `Nette\Object` настав з виходом PHP 7.2, в якому було заборонено давати класам імена `Object`. - -У міру розвитку PHP об'єктна модель і можливості мови вдосконалювалися. Окремі функції класу `SmartObject` стали зайвими. Після виходу PHP 8.2 єдиною функцією, яка поки що не підтримується в PHP безпосередньо, залишилася можливість використовувати так звані [властивості |#Properties, getters and setters]. - -Які функції пропонували `Nette\Object` і `Nette\Object`? Ось огляд. (У прикладах використовується клас `Nette\Object`, але більшість властивостей може бути застосована і до ознаки `Nette\SmartObject` ). - - -Непослідовні помилки .[#toc-inconsistent-errors] ------------------------------------------------- -PHP мав непослідовну поведінку при зверненні до неоголошених членів. Стан на момент публікації `Nette\Object` був таким: - -```php -echo $obj->undeclared; // E_NOTICE, пізніше E_WARNING -$obj->undeclared = 1; // проходить мовчки без повідомлення -$obj->unknownMethod(); // Фатальна помилка (не перехоплюється try/catch) -``` - -Фатальна помилка завершувала додаток без будь-якої можливості відреагувати. Тихий запис у неіснуючі члени без попередження міг призвести до серйозних помилок, які було важко виявити. `Nette\Object` Всі ці випадки були спіймані, і було викинуто виняток `MemberAccessException`. - -```php -echo $obj->undeclared; // згенерувати виключення Nette\MemberAccessException -$obj->undeclared = 1; // згенерувати виключення Nette\MemberAccessException -$obj->unknownMethod(); // згенерувати виключення Nette\MemberAccessException -``` -Починаючи з PHP 7.0, PHP більше не спричиняє неперехоплюваних фатальних помилок, а доступ до неоголошених членів став помилкою, починаючи з PHP 8.2. - - -Ви мали на увазі? .[#toc-did-you-mean] --------------------------------------- -Якщо виникала помилка `Nette\MemberAccessException`, можливо, через друкарську помилку під час звернення до об'єктної змінної або виклику методу, `Nette\Object` намагався дати підказку в повідомленні про помилку, як виправити помилку, у вигляді знакового доповнення "Ви мали на увазі?". + public float $radius = 0.0 { + set => max(0.0, $value); + } -```php -class Foo extends Nette\Object -{ - public static function from($var) - { + public bool $visible { + get => $this->radius > 0; } } - -$foo = Foo::form($var); -// throw Nette\MemberAccessException -// "Call to undefined static method Foo::form(), did you mean from()?" ``` -Сучасний PHP, можливо, не має форми "Ви мали на увазі?", але [Tracy |tracy:] додає це доповнення до помилок. І він навіть може сам [виправляти |tracy:open-files-in-ide#toc-demos] такі помилки. - -Методи розширення .[#toc-extension-methods] -------------------------------------------- -Натхненний методами розширення з C#. Вони дають можливість додавати нові методи до наявних класів. Наприклад, ви можете додати метод `addDateTime()` до форми, щоб додати свій власний DateTimePicker. +Методи розширення +----------------- +`Nette\Object` приніс у PHP ще одну цікаву концепцію, натхненну сучасними мовами програмування – методи розширення. Ця функція, запозичена з C#, дозволила розробникам елегантно розширювати існуючі класи новими методами без необхідності їх змінювати або успадковувати від них. Наприклад, ви могли додати до форми метод `addDateTime()`, який додасть власний DateTimePicker: ```php Form::extensionMethod( @@ -146,22 +137,22 @@ $form = new Form; $form->addDateTime('date'); ``` -Методи розширення виявилися непрактичними, оскільки їхні імена не автозаповнювалися редакторами, натомість вони повідомляли, що метод не існує. Тому їхню підтримку було припинено. +Методи розширення виявилися непрактичними, оскільки їхні назви не підказували редактори, навпаки, повідомляли, що метод не існує. Тому їхня підтримка була припинена. Сьогодні більш поширеним є використання композиції або успадкування для розширення функціональності класів. -Отримання імені класу .[#toc-getting-the-class-name] ----------------------------------------------------- +Визначення назви класу +---------------------- +Для визначення назви класу SmartObject пропонував простий метод: ```php -$class = $obj->getClass(); // використання Nette\Object -$class = $obj::class; // починаючи з PHP 8.0 +$class = $obj->getClass(); // за допомогою Nette\Object +$class = $obj::class; // з PHP 8.0 ``` -Доступ до роздумів та анотацій .[#toc-access-to-reflection-and-annotations] ---------------------------------------------------------------------------- - -`Nette\Object` запропоновано доступ до роздумів і анотацій за допомогою методів `getReflection()` і `getAnnotation()`: +Доступ до рефлексії та анотацій +------------------------------- +`Nette\Object` пропонував доступ до рефлексії та анотацій за допомогою методів `getReflection()` та `getAnnotation()`. Цей підхід значно спростив роботу з метаінформацією класів: ```php /** @@ -173,10 +164,10 @@ class Foo extends Nette\Object $obj = new Foo; $reflection = $obj->getReflection(); -$reflection->getAnnotation('author'); // повертає 'John Doe +$reflection->getAnnotation('author'); // поверне 'John Doe' ``` -Починаючи з PHP 8.0, з'явилася можливість доступу до мета-інформації у вигляді атрибутів: +З PHP 8.0 можна отримувати доступ до метаінформації у вигляді атрибутів, які пропонують ще більші можливості та кращий контроль типів: ```php #[Author('John Doe')] @@ -190,10 +181,9 @@ $reflection->getAttributes(Author::class)[0]; ``` -Геттери методів .[#toc-method-getters] --------------------------------------- - -`Nette\Object` пропонують елегантний спосіб роботи з методами, наче вони є змінними: +Метод-гетери +------------ +`Nette\Object` пропонував елегантний спосіб передавати методи так, ніби це змінні: ```php class Foo extends Nette\Object @@ -209,7 +199,7 @@ $method = $obj->adder; echo $method(2, 3); // 5 ``` -Починаючи з PHP 8.1, ви можете використовувати так званий "першокласний синтаксис методів, що викликаються":https://www.php.net/manual/en/functions.first_class_callable_syntax: +З PHP 8.1 можна використовувати так званий "first-class callable syntax":https://www.php.net/manual/en/functions.first_class_callable_syntax.php, який цю концепцію розвиває ще далі: ```php $obj = new Foo; @@ -218,10 +208,9 @@ echo $method(2, 3); // 5 ``` -Події .[#toc-events] --------------------- - -`Nette\Object` пропонує синтаксичний цукор для запуску [події |nette:glossary#events]: +Події +----- +SmartObject пропонує спрощений синтаксис для роботи з [подіями |nette:glossary#Події události]. Події дозволяють об'єктам інформувати інші частини програми про зміни свого стану: ```php class Circle extends Nette\Object @@ -231,12 +220,12 @@ class Circle extends Nette\Object public function setRadius(float $radius): void { $this->onChange($this, $radius); - $this->radius = $radius + $this->radius = $radius; } } ``` -Код `$this->onChange($this, $radius)` еквівалентний наступному: +Код `$this->onChange($this, $radius)` є еквівалентним наступному циклу: ```php foreach ($this->onChange as $callback) { @@ -244,7 +233,7 @@ foreach ($this->onChange as $callback) { } ``` -Для ясності ми рекомендуємо уникати магічного методу `$this->onChange()`. Хорошою заміною буде [Nette\Utils\Arrays::invoke |arrays#invoke]: +Для зрозумілості рекомендуємо уникати магічного методу `$this->onChange()`. Практичною заміною є, наприклад, функція [Nette\Utils\Arrays::invoke |arrays#invoke]: ```php Nette\Utils\Arrays::invoke($this->onChange, $this, $radius); diff --git a/utils/uk/staticclass.texy b/utils/uk/staticclass.texy new file mode 100644 index 0000000000..a60c93258f --- /dev/null +++ b/utils/uk/staticclass.texy @@ -0,0 +1,21 @@ +Статичні класи +************** + +.[perex] +StaticClass служить для позначення статичних класів. + + +Встановлення: + +```shell +composer require nette/utils +``` + +Статичні класи, тобто класи, які не призначені для створення екземплярів, можна позначити трейтом [api:Nette\StaticClass]: + +```php +class Strings +{ + use Nette\StaticClass; +} +``` diff --git a/utils/uk/strings.texy b/utils/uk/strings.texy index d0bb35d4b0..80a6db7a7e 100644 --- a/utils/uk/strings.texy +++ b/utils/uk/strings.texy @@ -2,7 +2,7 @@ **************** .[perex] -[api:Nette\Utils\Strings] статичний клас, що містить корисні функції для роботи з рядками, переважно в кодуванні UTF-8. +[api:Nette\Utils\Strings] — це статичний клас з корисними функціями для роботи з рядками, переважно в кодуванні UTF-8. Встановлення: @@ -11,46 +11,46 @@ composer require nette/utils ``` -У всіх прикладах передбачається, що псевдонім уже створено: +Усі приклади передбачають створений псевдонім: ```php use Nette\Utils\Strings; ``` -З урахуванням регістру .[#toc-letter-case] -========================================== +Зміна регістру літер +==================== -Для цих функцій потрібне розширення PHP `mbstring`. +Ці функції вимагають розширення PHP `mbstring`. lower(string $s): string .[method] ---------------------------------- -Перетворює рядок UTF-8 у малий регістр. +Перетворює рядок UTF-8 на малі літери. ```php -Strings::lower('Dobrý den'); // 'dobrý den' +Strings::lower('Добрий день'); // 'добрий день' ``` upper(string $s): string .[method] ---------------------------------- -Перетворює рядок UTF-8 у верхній регістр. +Перетворює рядок UTF-8 на великі літери. ```php -Strings::upper('Dobrý den'); // 'DOBRÝ DEN' +Strings::upper('Добрий день'); // 'ДОБРИЙ ДЕНЬ' ``` firstUpper(string $s): string .[method] --------------------------------------- -Перетворює першу літеру рядка UTF-8 у верхній регістр, інші не змінює. +Перетворює першу літеру рядка UTF-8 на велику, інші не змінює. ```php -Strings::firstUpper('dobrý den'); // 'Dobrý den' +Strings::firstUpper('добрий день'); // 'Добрий день' ``` @@ -60,34 +60,34 @@ firstLower(string $s): string .[method] Перетворює першу літеру рядка UTF-8 на малу, інші не змінює. ```php -Strings::firstLower('Dobrý den'); // 'dobrý den' +Strings::firstLower('Добрий день'); // 'добрий день' ``` capitalize(string $s): string .[method] --------------------------------------- -Перетворює першу літеру кожного слова в рядку UTF-8 на верхній регістр, інші - на нижній. +Перетворює першу літеру кожного слова в рядку UTF-8 на велику, інші на малі. ```php -Strings::capitalize('Dobrý den'); // 'Dobrý Den' +Strings::capitalize('Добрий день'); // 'Добрий День' ``` -Редагувати рядок .[#toc-editing-a-string] -========================================= +Редагування рядка +================= normalize(string $s): string .[method] -------------------------------------- -Видаляє керівні символи, нормалізує перенесення рядків до `\n`, обрізає провідні та відстаючі порожні рядки, обрізає праві перенесення рядків, нормалізує UTF-8 до нормальної форми NFC. +Видаляє керуючі символи, нормалізує кінці рядків до `\n`, обрізає початкові та кінцеві порожні рядки, обрізає пробіли справа на рядках, нормалізує UTF-8 до нормальної форми NFC. unixNewLines(string $s): string .[method] ----------------------------------------- -Перетворює перенесення рядків на `\n`, використовувані в системах Unix. Розриви рядків: `\n`, `\r`, `\r\n`, U+2028 роздільник рядків, U+2029 роздільник абзаців. +Перетворює кінці рядків на `\n`, що використовуються в unix-системах. Кінці рядків: `\n`, `\r`, `\r\n`, U+2028 line separator, U+2029 paragraph separator. ```php $unixLikeLines = Strings::unixNewLines($string); @@ -97,66 +97,66 @@ $unixLikeLines = Strings::unixNewLines($string); platformNewLines(string $s): string .[method] --------------------------------------------- -Перетворює переклади рядків на символи, характерні для поточної платформи, тобто `\r\n` у Windows і `\n` в іншому місці. Розриви рядків: `\n`, `\r`, `\r\n`, U+2028 роздільник рядків, U+2029 роздільник абзаців. +Перетворює кінці рядків на символи, специфічні для поточної платформи, тобто `\r\n` у Windows та `\n` в інших місцях. Кінці рядків: `\n`, `\r`, `\r\n`, U+2028 line separator, U+2029 paragraph separator. ```php $platformLines = Strings::platformNewLines($string); ``` -webalize(string $s, string $charlist=null, bool $lower=true): string .[method] ------------------------------------------------------------------------------- +webalize(string $s, ?string $charlist=null, bool $lower=true): string .[method] +------------------------------------------------------------------------------- -Змінює рядок UTF-8 до формату, використовуваного в URL, тобто видаляє діакритичні знаки та замінює всі символи, окрім букв англійського алфавіту та цифр, на дефіс. +Редагує рядок UTF-8 до форми, що використовується в URL, тобто видаляє діакритику та всі символи, крім літер англійського алфавіту та цифр, замінює дефісом. ```php -Strings::webalize('náš produkt'); // 'nas-produkt' +Strings::webalize('наш продукт'); // 'nash-produkt' ``` -Якщо необхідно зберегти інші символи, їх можна вказати в другому параметрі функції. +Якщо потрібно зберегти інші символи, їх можна вказати у другому параметрі функції. ```php -Strings::webalize('10. obrázek_id', '._'); // '10.-obrazek_id' +Strings::webalize('10. малюнок_id', '._'); // '10.-malyunok_id' ``` -Третій параметр можна використовувати для придушення перетворення в нижній регістр. +Третім параметром можна придушити перетворення на малі літери. ```php -Strings::webalize('Dobrý den', null, false); // 'Dobry-den' +Strings::webalize('Добрий день', null, false); // 'Dobryi-den' ``` .[caution] -Потрібне розширення PHP `intl`. +Потребує розширення PHP `intl`. -trim(string $s, string $charlist=null): string .[method] --------------------------------------------------------- +trim(string $s, ?string $charlist=null): string .[method] +--------------------------------------------------------- -Обрізає пробіли (або інші символи, зазначені другим параметром) з початку і кінця рядка UTF-8. +Обрізає пробіли (або інші символи, вказані другим параметром) з початку та кінця рядка UTF-8. ```php -Strings::trim(' Hello '); // 'Hello' +Strings::trim(' Привіт '); // 'Привіт' ``` truncate(string $s, int $maxLen, string $append=`'…'`): string .[method] ------------------------------------------------------------------------ -Усікає рядок UTF-8 до вказаної максимальної довжини, намагаючись зберегти цілі слова. Якщо рядок усічений, то в кінець додається триплет (можна змінити за допомогою третього параметра). +Обрізає рядок UTF-8 до вказаної максимальної довжини, намагаючись при цьому зберігати цілі слова. Якщо рядок скорочується, додає в кінець три крапки (можна змінити третім параметром). ```php -$text = 'Řekněte, jak se máte?'; -Strings::truncate($text, 5); // 'Řekn…' -Strings::truncate($text, 20); // 'Řekněte, jak se…' -Strings::truncate($text, 30); // 'Řekněte, jak se máte?' -Strings::truncate($text, 20, '~'); // 'Řekněte, jak se~' +$text = 'Скажіть, як справи?'; +Strings::truncate($text, 5); // 'Скаж…' +Strings::truncate($text, 20); // 'Скажіть, як спра…' +Strings::truncate($text, 30); // 'Скажіть, як справи?' +Strings::truncate($text, 20, '~'); // 'Скажіть, як спра~' ``` indent(string $s, int $level=1, string $indentationChar=`"\t"`): string .[method] --------------------------------------------------------------------------------- -Відступ багаторядкового тексту зліва. Кількість відступів визначається другим параметром, який використовується для відступів третього параметра (значення за замовчуванням - tab). +Робить відступ багаторядкового тексту зліва. Кількість відступів визначає другий параметр, чим робити відступ — третій параметр (значення за замовчуванням — табуляція). ```php Strings::indent('Nette'); // "\tNette" @@ -167,7 +167,7 @@ Strings::indent('Nette', 2, '+'); // '++Nette' padLeft(string $s, int $length, string $pad=`' '`): string .[method] -------------------------------------------------------------------- -Завершує рядок UTF-8 до вказаної довжини, повторюючи рядок `$pad` зліва. +Доповнює рядок UTF-8 до заданої довжини, повторюючи рядок `$pad` зліва. ```php Strings::padLeft('Nette', 6); // ' Nette' @@ -178,7 +178,7 @@ Strings::padLeft('Nette', 8, '+*'); // '+*+Nette' padRight(string $s, int $length, string $pad=`' '`): string .[method] --------------------------------------------------------------------- -Завершує рядок UTF-8 до зазначеної довжини, повторюючи рядок `$pad` праворуч. +Доповнює рядок UTF-8 до заданої довжини, повторюючи рядок `$pad` справа. ```php Strings::padRight('Nette', 6); // 'Nette ' @@ -186,10 +186,10 @@ Strings::padRight('Nette', 8, '+*'); // 'Nette+*+' ``` -substring(string $s, int $start, int $length=null): string .[method] --------------------------------------------------------------------- +substring(string $s, int $start, ?int $length=null): string .[method] +--------------------------------------------------------------------- -Повертає частину рядка UTF-8 `$s`, задану початковою позицією `$start` і довжиною `$length`. Якщо `$start` від'ємний, то рядок, що повертається, починатиметься з символу -`$start` символу з кінця. +Повертає частину рядка UTF-8 `$s`, задану початковою позицією `$start` та довжиною `$length`. Якщо `$start` від'ємний, повернутий рядок починатиметься з символу -`$start` від кінця. ```php Strings::substring('Nette Framework', 0, 5); // 'Nette' @@ -201,7 +201,7 @@ Strings::substring('Nette Framework', -4); // 'work' reverse(string $s): string .[method] ------------------------------------ -Змінює рядок UTF-8. +Обертає рядок UTF-8. ```php Strings::reverse('Nette'); // 'etteN' @@ -217,71 +217,71 @@ length(string $s): int .[method] ```php Strings::length('Nette'); // 5 -Strings::length('červená'); // 7 +Strings::length('червона'); // 7 ``` -/--comment - - - - - - - - - - - - - - - - - - - - - - - - - - - - +startsWith(string $haystack, string $needle): bool .[method deprecated] +----------------------------------------------------------------------- +Перевіряє, чи починається рядок `$haystack` рядком `$needle`. +```php +$haystack = 'Починається'; +$needle = 'По'; +Strings::startsWith($haystack, $needle); // true +``` +.[note] +Використовуйте нативну [`str_starts_with()`|https://www.php.net/manual/en/function.str-starts-with.php]. +endsWith(string $haystack, string $needle): bool .[method deprecated] +--------------------------------------------------------------------- +Перевіряє, чи закінчується рядок `$haystack` рядком `$needle`. +```php +$haystack = 'Закінчується'; +$needle = 'ється'; +Strings::endsWith($haystack, $needle); // true +``` +.[note] +Використовуйте нативну [`str_ends_with()`|https://www.php.net/manual/en/function.str-ends-with.php]. +contains(string $haystack, string $needle): bool .[method deprecated] +--------------------------------------------------------------------- +Перевіряє, чи містить рядок `$haystack` рядок `$needle`. +```php +$haystack = 'Аудиторія'; +$needle = 'дитор'; +Strings::contains($haystack, $needle); // true +``` -\-- +.[note] +Використовуйте нативну [`str_contains()`|https://www.php.net/manual/en/function.str-contains.php]. -compare(string $left, string $right, int $length=null): bool .[method] ----------------------------------------------------------------------- +compare(string $left, string $right, ?int $length=null): bool .[method] +----------------------------------------------------------------------- -Порівняння двох рядків UTF-8 або частин рядків без урахування регістру. Якщо `$length` містить null, то порівнюються цілі рядки, якщо від'ємно, то порівнюється відповідна кількість символів з кінця рядків, інакше порівнюється відповідна кількість символів з початку. +Порівняння двох рядків UTF-8 або їхніх частин без урахування регістру літер. Якщо `$length` містить null, порівнюються цілі рядки, якщо він від'ємний, порівнюється відповідна кількість символів з кінця рядків, інакше порівнюється відповідна кількість символів з початку. ```php Strings::compare('Nette', 'nette'); // true -Strings::compare('Nette', 'next', 2); // true - shoda prvních 2 znaků -Strings::compare('Nette', 'Latte', -2); // true - shoda posledních 2 znaků +Strings::compare('Nette', 'next', 2); // true - збіг перших 2 символів +Strings::compare('Nette', 'Latte', -2); // true - збіг останніх 2 символів ``` findPrefix(...$strings): string .[method] ----------------------------------------- -Знаходить загальний початок рядків. Або повертає порожній рядок, якщо загальний префікс не знайдено. +Знаходить спільний початок рядків. Або повертає порожній рядок, якщо спільний префікс не знайдено. ```php Strings::findPrefix('prefix-a', 'prefix-bb', 'prefix-c'); // 'prefix-' @@ -293,7 +293,7 @@ Strings::findPrefix('Nette', 'is', 'great'); // '' before(string $haystack, string $needle, int $nth=1): ?string .[method] ----------------------------------------------------------------------- -Повертає частину рядка `$haystack` перед n-м `$nth` входженням рядка `$needle`. Або `null`, якщо `$needle` не було знайдено. Якщо `$nth` від'ємний, то пошук ведеться з кінця рядка. +Повертає частину рядка `$haystack` перед n-тим `$nth` входженням рядка `$needle`. Або `null`, якщо `$needle` не знайдено. При від'ємному значенні `$nth` пошук ведеться з кінця рядка. ```php Strings::before('Nette_is_great', '_', 1); // 'Nette' @@ -306,7 +306,7 @@ Strings::before('Nette_is_great', '_', 3); // null after(string $haystack, string $needle, int $nth=1): ?string .[method] ---------------------------------------------------------------------- -Повертає частину рядка `$haystack` після n-го `$nth` входження рядка `$needle`. Або `null`, якщо `$needle` не було знайдено. Якщо `$nth` від'ємний, то пошук ведеться з кінця рядка. +Повертає частину рядка `$haystack` після n-того `$nth` входження рядка `$needle`. Або `null`, якщо `$needle` не знайдено. При від'ємному значенні `$nth` пошук ведеться з кінця рядка. ```php Strings::after('Nette_is_great', '_', 2); // 'great' @@ -319,7 +319,7 @@ Strings::after('Nette_is_great', '_', 3); // null indexOf(string $haystack, string $needle, int $nth=1): ?int .[method] --------------------------------------------------------------------- -Повертає позицію в символах n-го `$nth` входження рядка `$needle` в рядок `$haystack`. Або `null`, якщо `$needle` не було знайдено. Якщо `$nth` від'ємний, то пошук ведеться з кінця рядка. +Повертає позицію у символах n-того `$nth` входження рядка `$needle` в рядку `$haystack`. Або `null`, якщо `$needle` не знайдено. При від'ємному значенні `$nth` пошук ведеться з кінця рядка. ```php Strings::indexOf('abc abc abc', 'abc', 2); // 4 @@ -328,76 +328,76 @@ Strings::indexOf('abc abc abc', 'd'); // null ``` -Кодування .[#toc-kodovani] -========================== +Кодування +========= fixEncoding(string $s): string .[method] ---------------------------------------- -Видаляє з рядка неприпустимі символи UTF-8. +Видаляє з рядка недійсні символи UTF-8. ```php $correctStrings = Strings::fixEncoding($string); ``` -/--comment - - - - - - +checkEncoding(string $s): bool .[method deprecated] +--------------------------------------------------- +Перевіряє, чи є рядок дійсним UTF-8. +```php +$isUtf8 = Strings::checkEncoding($string); +``` -\-- +.[note] +Використовуйте [Nette\Utils\Validator::isUnicode() |validators#isUnicode]. toAscii(string $s): string .[method] ------------------------------------ -Перетворює рядок UTF-8 на ASCII, тобто видаляє діакритичні знаки тощо. +Перетворює рядок UTF-8 на ASCII, тобто видаляє діакритику тощо. ```php -Strings::toAscii('žluťoučký kůň'); // 'zlutoucky kun' +Strings::toAscii('жовтий кінь'); // 'zhovtyi kin' ``` .[caution] -Потрібне розширення PHP `intl`. +Потребує розширення PHP `intl`. chr(int $code): string .[method] -------------------------------- -Повертає певний символ UTF-8 з кодової точки (число в діапазоні 0x0000...D7FF і 0xE000...10FFFF). +Повертає специфічний символ в UTF-8 з кодової точки (число в діапазоні 0x0000..D7FF та 0xE000..10FFFF). ```php -Strings::chr(0xA9); // '©' v kódování UTF-8 +Strings::chr(0xA9); // '©' в кодуванні UTF-8 ``` ord(string $char): int .[method] -------------------------------- -Повертає кодову точку UTF-8 певного символу (число в діапазоні 0x0000...D7FF або 0xE000...10FFFF). +Повертає кодову точку конкретного символу в UTF-8 (число в діапазоні 0x0000..D7FF або 0xE000..10FFFF). ```php Strings::ord('©'); // 0xA9 ``` -Регулярні вирази .[#toc-fixencoding] -==================================== +Регулярні вирази +================ -Клас Strings надає функції для роботи з регулярними виразами. На відміну від власних функцій PHP, вони мають більш зрозумілий API, кращу підтримку Unicode і, що найважливіше, виявлення помилок. Будь-яка помилка під час компіляції або обробки виразу призведе до виникнення виключення `Nette\RegexpException`. +Клас Strings пропонує функції для роботи з регулярними виразами. На відміну від нативних функцій PHP, вони мають зрозуміліший API, кращу підтримку Unicode та, перш за все, виявлення помилок. Будь-яка помилка під час компіляції або обробки виразу викине виняток `Nette\RegexpException`. split(string $subject, string $pattern, bool $captureOffset=false, bool $skipEmpty=false, int $limit=-1, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------- -Розділяє рядок на масив відповідно до регулярного виразу. Вирази в круглих дужках також будуть перехоплені та повернуті. +Розділяє рядок на масив за регулярним виразом. Вирази в дужках будуть захоплені та також повернуті. ```php Strings::split('hello, world', '~,\s*~'); @@ -407,7 +407,7 @@ Strings::split('hello, world', '~(,)\s*~'); // ['hello', ',', 'world']`` ``` -Якщо `$skipEmpty` дорівнює `true`, то будуть повернуті тільки непорожні записи: +Якщо `$skipEmpty` дорівнює `true`, будуть повернуті лише непорожні елементи: ```php Strings::split('hello, world, ', '~,\s*~'); @@ -417,16 +417,16 @@ Strings::split('hello, world, ', '~,\s*~', skipEmpty: true); // ['hello', 'world'] ``` -Якщо `$limit`, то будуть повернуті тільки підрядки до межі, а інша частина рядка буде поміщена в останній елемент. Межа -1 або 0 означає відсутність межі. +Якщо задано `$limit`, будуть повернуті лише підрядки до ліміту, а решта рядка буде розміщена в останньому елементі. Ліміт -1 або 0 означає відсутність обмеження. ```php Strings::split('hello, world, third', '~,\s*~', limit: 2); // ['hello', 'world, third'] ``` -Якщо `$utf8` дорівнює `true`, то оцінка переключиться в режим Unicode. Це аналогічно вказівці модифікатора `u`. +Якщо `$utf8` дорівнює `true`, перемикається обчислення в режим Unicode. Подібно до того, якби ви вказали модифікатор `u`. -Якщо `$captureOffset` дорівнює `true`, то для кожного збігу, що трапляється, буде повернуто його позицію в рядку (у байтах; у символах, якщо встановлено `$utf8` ). Це змінює значення, що повертається, на масив, кожний елемент якого є парою, що складається з рядка, що збігається, та його позиції. +Якщо `$captureOffset` дорівнює `true`, для кожного знайденого збігу буде також повернута його позиція в рядку (в байтах; якщо встановлено `$utf8`, то в символах). Це змінить повернене значення на масив, де кожен елемент є парою, що складається зі знайденого рядка та його позиції. ```php Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true); @@ -440,7 +440,7 @@ Strings::split('žlutý, kůň', '~,\s*~', captureOffset: true, utf8: true); match(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $utf8=false): ?array .[method] -------------------------------------------------------------------------------------------------------------------------------------------------- -Шукає в рядку частину, що відповідає регулярному виразу, і повертає масив зі знайденим виразом і кожним підвиразом, або `null`. +Шукає в рядку частину, що відповідає регулярному виразу, і повертає масив зі знайденим виразом та окремими підвиразами, або `null`. ```php Strings::match('hello!', '~\w+(!+)~'); @@ -450,7 +450,7 @@ Strings::match('hello!', '~X~'); // null ``` -Якщо `$unmatchedAsNull` дорівнює `true`, то не захоплені підвирази повертаються як нульові; в іншому разі вони повертаються як порожній рядок або не повертаються: +Якщо `$unmatchedAsNull` дорівнює `true`, незахоплені підвирази повертаються як null; інакше вони повертаються як порожній рядок або не повертаються: ```php Strings::match('hello', '~\w+(!+)?~'); @@ -460,7 +460,7 @@ Strings::match('hello', '~\w+(!+)?~', unmatchedAsNull: true); // ['hello', null] ``` -Якщо `$utf8` дорівнює `true`, то оцінка перемикається в режим Unicode. Аналогічно до вказівки модифікатора `u`: +Якщо `$utf8` дорівнює `true`, перемикається обчислення в режим Unicode. Подібно до того, якби ви вказали модифікатор `u`: ```php Strings::match('žlutý kůň', '~\w+~'); @@ -470,9 +470,9 @@ Strings::match('žlutý kůň', '~\w+~', utf8: true); // ['žlutý'] ``` -Параметр `$offset` може використовуватися для вказівки позиції, з якої слід починати пошук (у байтах; у символах, якщо встановлено `$utf8` ). +Параметр `$offset` можна використовувати для визначення позиції, з якої слід почати пошук (в байтах; якщо встановлено `$utf8`, то в символах). -Якщо `$captureOffset` дорівнює `true`, то для кожного збігу, що трапляється, буде також повернуто його позицію в рядку (у байтах; якщо встановлено `$utf8`, то в символах). Це перетворює значення, що повертається, на масив, кожен елемент якого є парою, що складається з рядка, що збігається, та його зміщення: +Якщо `$captureOffset` дорівнює `true`, для кожного знайденого збігу буде також повернута його позиція в рядку (в байтах; якщо встановлено `$utf8`, то в символах). Це змінить повернене значення на масив, де кожен елемент є парою, що складається зі знайденого рядка та його зміщення: ```php Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true); @@ -483,10 +483,10 @@ Strings::match('žlutý!', '~\w+(!+)?~', captureOffset: true, utf8: true); ``` -matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false): array .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +matchAll(string $subject, string $pattern, bool $captureOffset=false, int $offset=0, bool $unmatchedAsNull=false, bool $patternOrder=false, bool $utf8=false, bool $lazy=false): array|Generator .[method] +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Шукає в рядку всі входження, що відповідають регулярному виразу, і повертає масив масивів, що містять знайдений вираз і кожен підвираз. +Шукає в рядку всі входження, що відповідають регулярному виразу, і повертає масив масивів зі знайденим виразом та окремими підвиразами. ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~'); @@ -496,7 +496,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~'); ] */ ``` -Якщо `$patternOrder` дорівнює `true`, то структура результатів змінюється таким чином, що перший запис являє собою масив повних збігів шаблону, другий - масив рядків, що збігаються з першим підшаблоном у круглих дужках, і так далі: +Якщо `$patternOrder` дорівнює `true`, структура результатів змінюється так, що в першому елементі знаходиться масив повних збігів шаблону, у другому — масив рядків, яким відповідає перший підвираз у дужках, і так далі: ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~', patternOrder: true); @@ -506,7 +506,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~', patternOrder: true); ] */ ``` -Якщо `$unmatchedAsNull` дорівнює `true`, то підшаблони, що не збігаються, повертаються як нульові; в іншому разі вони повертаються як порожні рядки або не повертаються: +Якщо `$unmatchedAsNull` дорівнює `true`, незахоплені підвирази повертаються як null; інакше вони повертаються як порожній рядок або не повертаються: ```php Strings::matchAll('hello, world!!', '~\w+(!+)?~', unmatchedAsNull: true); @@ -516,7 +516,7 @@ Strings::matchAll('hello, world!!', '~\w+(!+)?~', unmatchedAsNull: true); ] */ ``` -Якщо `$utf8` дорівнює `true`, то оцінка перемикається в режим Unicode. Аналогічно до вказівки модифікатора `u`: +Якщо `$utf8` дорівнює `true`, перемикається обчислення в режим Unicode. Подібно до того, якби ви вказали модифікатор `u`: ```php Strings::matchAll('žlutý kůň', '~\w+~'); @@ -532,9 +532,9 @@ Strings::matchAll('žlutý kůň', '~\w+~', utf8: true); ] */ ``` -Параметр `$offset` може використовуватися для вказівки позиції, з якої слід починати пошук (у байтах; у символах, якщо встановлено `$utf8` ). +Параметр `$offset` можна використовувати для визначення позиції, з якої слід почати пошук (в байтах; якщо встановлено `$utf8`, то в символах). -Якщо `$captureOffset` дорівнює `true`, то для кожного збігу, що трапляється, буде також повернуто його позицію в рядку (у байтах; якщо встановлено `$utf8`, то в символах). Це змінює значення, що повертається, на масив, кожен елемент якого є парою, що складається з рядка, що збігається, та його позиції: +Якщо `$captureOffset` дорівнює `true`, для кожного знайденого збігу буде також повернута його позиція в рядку (в байтах; якщо встановлено `$utf8`, то в символах). Це змінить повернене значення на масив, де кожен елемент є парою, що складається зі знайденого рядка та його позиції: ```php Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true); @@ -550,11 +550,21 @@ Strings::matchAll('žlutý kůň', '~\w+~', captureOffset: true, utf8: true); ] */ ``` +Якщо `$lazy` дорівнює `true`, функція повертає `Generator` замість масиву, що забезпечує значні переваги у продуктивності при роботі з великими рядками. Генератор дозволяє шукати збіги поступово, замість обробки всього рядка одразу. Це дозволяє ефективно працювати навіть з надзвичайно великими вхідними текстами. Крім того, ви можете будь-коли перервати обробку, якщо знайдете потрібний збіг, що економить обчислювальний час. + +```php +$matches = Strings::matchAll($largeText, '~\w+~', lazy: true); +foreach ($matches as $match) { + echo "Знайдено: $match[0]\n"; + // Обробку можна будь-коли перервати +} +``` + replace(string $subject, string|array $pattern, string|callable $replacement='', int $limit=-1, bool $captureOffset=false, bool $unmatchedAsNull=false, bool $utf8=false): string .[method] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Замінює всі входження, що відповідають регулярному виразу. `$replacement` - це або маска рядка заміни, або зворотний виклик. +Замінює всі входження, що відповідають регулярному виразу. `$replacement` є або маскою рядка заміни, або callback-функцією. ```php Strings::replace('hello, world!', '~\w+~', '--'); @@ -564,7 +574,7 @@ Strings::replace('hello, world!', '~\w+~', fn($m) => strrev($m[0])); // 'olleh, dlrow!' ``` -Функція також допускає множинну заміну, передаючи в другому параметрі масив виду `pattern => replacement`: +Функція також дозволяє виконати кілька замін, передавши у другому параметрі масив у вигляді `pattern => replacement`: ```php Strings::replace('hello, world!', [ @@ -574,9 +584,9 @@ Strings::replace('hello, world!', [ // '-- --!' ``` -Параметр `$limit` обмежує кількість замін, які можуть бути зроблені. Межа -1 означає відсутність межі. +Параметр `$limit` обмежує кількість виконаних замін. Ліміт -1 означає відсутність обмеження. -Якщо `$utf8` дорівнює `true`, то оцінка перемикається в режим Unicode. Це аналогічно вказівці модифікатора `u`. +Якщо `$utf8` дорівнює `true`, перемикається обчислення в режим Unicode. Подібно до того, якби ви вказали модифікатор `u`. ```php Strings::replace('žlutý kůň', '~\w+~', '--'); @@ -586,7 +596,7 @@ Strings::replace('žlutý kůň', '~\w+~', '--', utf8: true); // '-- --' ``` -Якщо `$captureOffset` дорівнює `true`, то для кожного збігу, що трапляється, його позиція в рядку (у байтах; у символах, якщо встановлено `$utf8` ) також буде передана зворотному виклику. Це змінює форму переданого масиву, де кожен елемент являє собою пару, що складається з рядка, що збігається, та його позиції. +Якщо `$captureOffset` дорівнює `true`, для кожного знайденого збігу буде передана callback-функції також його позиція в рядку (в байтах; якщо встановлено `$utf8`, то в символах). Це змінить вигляд переданого масиву, де кожен елемент є парою, що складається зі знайденого рядка та його позиції. ```php Strings::replace( @@ -595,7 +605,7 @@ Strings::replace( function (array $m) { dump($m); return ''; }, captureOffset: true, ); -// dumps [['lut', 2]] a [['k', 8]] +// dumps [['lut', 2]] та [['k', 8]] Strings::replace( 'žlutý kůň', @@ -604,10 +614,10 @@ Strings::replace( captureOffset: true, utf8: true, ); -// dumps [['žlutý', 0]] a [['kůň', 6]] +// dumps [['žlutý', 0]] та [['kůň', 6]] ``` -Якщо `$unmatchedAsNull` - `true`, то підшаблони, що не збігаються, передаються у зворотний виклик як null; в іншому разі вони передаються як порожній рядок або не передаються: +Якщо `$unmatchedAsNull` дорівнює `true`, незахоплені підвирази передаються в callback-функцію як null; інакше вони передаються як порожній рядок або не передаються: ```php Strings::replace( diff --git a/utils/uk/type.texy b/utils/uk/type.texy index c6df5fea73..34a60f43f8 100644 --- a/utils/uk/type.texy +++ b/utils/uk/type.texy @@ -1,8 +1,8 @@ -Типи PHP -******** +PHP Тип +******* .[perex] -[api:Nette\Utils\Type] це клас для роботи з типами даних PHP. +[api:Nette\Utils\Type] — це клас для роботи з типами даних PHP. Встановлення: @@ -11,7 +11,7 @@ composer require nette/utils ``` -У всіх прикладах передбачається, що псевдонім уже створено: +Усі приклади передбачають створений псевдонім: ```php use Nette\Utils\Type; @@ -21,7 +21,7 @@ use Nette\Utils\Type; fromReflection($reflection): ?Type .[method] -------------------------------------------- -Статичний метод створює об'єкт Type на основі відображення. Параметр може бути об'єктом `ReflectionMethod` або `ReflectionFunction` (повертає тип значення, що повертається) або `ReflectionParameter` або `ReflectionProperty`. Він переводить `self`, `static` і `parent` у реальне ім'я класу. Якщо об'єкт не має типу, повертається `null`. +Статичний метод створює об'єкт Type на основі рефлексії. Параметром може бути об'єкт `ReflectionMethod` або `ReflectionFunction` (повертає тип значення, що повертається) або `ReflectionParameter` чи `ReflectionProperty`. Перекладає `self`, `static` та `parent` на справжню назву класу. Якщо суб'єкт не має типу, повертає `null`. ```php class DemoClass @@ -37,7 +37,7 @@ echo Type::fromReflection($prop); // 'DemoClass' fromString(string $type): Type .[method] ---------------------------------------- -Статичний метод створює об'єкт Type відповідно до текстової нотації. +Статичний метод створює об'єкт Type за текстовим записом. ```php $type = Type::fromString('Foo|Bar'); @@ -48,10 +48,10 @@ echo $type; // 'Foo|Bar' getNames(): (string|array)[] .[method] -------------------------------------- -Повертає масив підтипів, що складають складовий тип, у вигляді рядків. +Повертає масив підтипів, з яких складається складений тип, як рядки. ```php -$type = Type::fromString('string|null'); // nebo '?string' +$type = Type::fromString('string|null'); // або '?string' $type->getNames(); // ['string', 'null'] $type = Type::fromString('(Foo&Bar)|string'); @@ -62,10 +62,10 @@ $type->getNames(); // [['Foo', 'Bar'], 'string'] getTypes(): Type[] .[method] ---------------------------- -Повертає масив підтипів, що складають складовий тип, у вигляді об'єктів `ReflectionType`: +Повертає масив підтипів, з яких складається складений тип, як об'єкти `Type`: ```php -$type = Type::fromString('string|null'); // or '?string' +$type = Type::fromString('string|null'); // або '?string' $type->getTypes(); // [Type::fromString('string'), Type::fromString('null')] $type = Type::fromString('(Foo&Bar)|string'); @@ -79,7 +79,7 @@ $type->getTypes(); // [Type::fromString('Foo'), Type::fromString('Bar')] getSingleName(): ?string .[method] ---------------------------------- -Повертає ім'я типу для простих типів, інакше null. +Для простих типів повертає назву типу, інакше null. ```php $type = Type::fromString('string|null'); @@ -99,14 +99,14 @@ echo $type->getSingleName(); // null isSimple(): bool .[method] -------------------------- -Повертає, чи є даний тип простим типом. Прості типи також вважаються простими nullable типами: +Повертає, чи є тип простим. До простих типів відносяться також прості nullable типи: ```php $type = Type::fromString('string'); $type->isSimple(); // true $type->isUnion(); // false -$type = Type::fromString('?Foo'); // nebo 'Foo|null' +$type = Type::fromString('?Foo'); // або 'Foo|null' $type->isSimple(); // true $type->isUnion(); // true ``` @@ -115,7 +115,7 @@ $type->isUnion(); // true isUnion(): bool .[method] ------------------------- -Повертає, чи існує тип об'єднання. +Повертає, чи є тип union типом. ```php $type = Type::fromString('string|int'); @@ -126,7 +126,7 @@ $type->isUnion(); // true isIntersection(): bool .[method] -------------------------------- -Повертає, чи є o типом перетину. +Повертає, чи є тип intersection типом. ```php @@ -138,7 +138,7 @@ $type->isIntersection(); // true isBuiltin(): bool .[method] --------------------------- -Повертає, чи є тип одночасно простим і вбудованим типом PHP. +Повертає, чи є тип простим і водночас вбудованим типом PHP. ```php $type = Type::fromString('string'); @@ -155,7 +155,7 @@ $type->isBuiltin(); // false isClass(): bool .[method] ------------------------- -Повертає, чи є тип одночасно простим та іменем класу. +Повертає, чи є тип простим і водночас назвою класу. ```php $type = Type::fromString('string'); @@ -172,7 +172,7 @@ $type->isClass(); // false isClassKeyword(): bool .[method] -------------------------------- -Повертає, чи є тип одним із внутрішніх типів `self`, `parent`, `static`. +Повертає, чи є тип одним з внутрішніх типів `self`, `parent`, `static`. ```php $type = Type::fromString('self'); @@ -186,7 +186,7 @@ $type->isClassKeyword(); // false allows(string $type): bool .[method] ------------------------------------ -Метод `allows()` перевіряє сумісність типів. Наприклад, він дозволяє перевірити, чи може значення певного типу бути передано як параметр. +Метод `allows()` перевіряє сумісність типів. Наприклад, дозволяє дізнатися, чи може значення певного типу бути передане як параметр. ```php $type = Type::fromString('string|null'); diff --git a/utils/uk/validators.texy b/utils/uk/validators.texy index 3af96283f4..5c8d115a90 100644 --- a/utils/uk/validators.texy +++ b/utils/uk/validators.texy @@ -2,7 +2,7 @@ ****************** .[perex] -Потрібно швидко і легко перевірити, що змінна містить, наприклад, дійсну адресу електронної пошти? Ось тут і стане в пригоді [api:Nette\Utils\Validators], статичний клас із корисними функціями для перевірки значень. +Потрібно швидко та просто перевірити, чи є в змінній, наприклад, дійсна електронна адреса? Для цього вам знадобиться [api:Nette\Utils\Validators], статичний клас з корисними функціями для валідації значень. Встановлення: @@ -11,17 +11,17 @@ composer require nette/utils ``` -У всіх прикладах передбачається, що псевдонім уже створено: +Усі приклади передбачають створений псевдонім: ```php use Nette\Utils\Validators; ``` -Основне використання .[#toc-basic-usage] -======================================== +Основне використання +==================== -Клас має низку методів для перевірки значень, таких як [isList() |#isList], [isUnicode() |#isUnicode], [isEmail() |#isEmail], [isUrl() |#isUrl] тощо для використання у вашому коді: +Клас має ряд методів для перевірки значень, таких як [#isUnicode()], [#isEmail()], [#isUrl()] тощо, для використання у вашому коді: ```php if (!Validators::isEmail($email)) { @@ -29,7 +29,7 @@ if (!Validators::isEmail($email)) { } ``` -Він також може перевірити, чи є значення [очікуваним типом |#Expected-Types], який являє собою рядок, де опції розділені косою рискою `|`. Таким чином, ми можемо легко перевірити кілька типів за допомогою [if() |#if()]: +Крім того, він може перевірити, чи є значення так званим [очікуваним типом |#Очікувані типи], що є рядком, де окремі варіанти розділені вертикальною рискою `|`. Таким чином, ми можемо легко перевірити кілька типів за допомогою [#is()]: ```php if (!Validators::is($val, 'int|string|bool')) { @@ -37,81 +37,81 @@ if (!Validators::is($val, 'int|string|bool')) { } ``` -Але це також дає нам можливість створити систему, в якій нам потрібно записувати очікування у вигляді рядків (наприклад, в анотаціях або конфігурації), а потім перевіряти значення за ними. +Але це також дає нам можливість створити систему, де очікування потрібно записувати як рядки (наприклад, в анотаціях або конфігурації), а потім перевіряти значення відповідно до них. -Ми також можемо поставити запит [assert() |#assert] на очікувані типи, який у разі невиконання викидає виняток. +До очікуваних типів можна також застосувати вимогу [#assert()], яка, якщо не виконана, викидає виняток. -Очікувані типи .[#toc-expected-types] -===================================== +Очікувані типи +============== -Очікувані типи утворюють рядок, що складається з одного або декількох варіантів, розділених вертикальною смугою `|`, podobně jako se zapisují typy v PHP (např. `'int|string|bool')`. Також приймається нульова нотація `?int`. +Очікувані типи утворюють рядок, що складається з одного або декількох варіантів, розділених вертикальною рискою `|`, подібно до того, як записуються типи в PHP (наприклад, `'int|string|bool'`). Також приймається запис nullable `?int`. -Масив, у якому всі елементи мають певний тип, записується як `int[]`. +Масив, де всі елементи мають певний тип, записується у вигляді `int[]`. -За деякими типами може слідувати двокрапка і довжина `:length` або діапазон. `:[min]..[max]`наприклад, `string:10` (рядок із 10 байт), `float:10..` (число 10 або більше), `array:..10` (масив до десяти елементів) або `list:10..20` (список від 10 до 20 елементів), або регулярний вираз u `pattern:[0-9]+`. +За деякими типами може слідувати двокрапка та довжина `:length` або діапазон `:[min]..[max]`, наприклад, `string:10` (рядок довжиною 10 байтів), `float:10..` (число 10 і більше), `array:..10` (масив до десяти елементів) або `list:10..20` (список з 10 до 20 елементів), або регулярний вираз у `pattern:[0-9]+`. -Огляд типів і правил: +Огляд типів та правил: .[wide] -| PHP types || +| PHP типи || |-------------------------- -| `array` .{width: 140px} | Для кількості елементів може бути заданий діапазон. -| `bool` | -| `float` | Для значення може бути вказано діапазон. -| `int` | може бути вказано діапазон значень. -| `null` | -| `object` | +| `array` .{width: 140px} | можна вказати діапазон для кількості елементів +| `bool` | +| `float` | можна вказати діапазон для значення +| `int` | можна вказати діапазон для значення +| `null` | +| `object` | | `resource` | -| `scalar` | int\|float\|bool\|string -| `string` | Для довжини в байтах може бути вказано діапазон. +| `scalar` | int\|float\|bool\|string +| `string` | можна вказати діапазон для довжини в байтах | `callable` | | `iterable` | -| `mixed` | +| `mixed` | |-------------------------- | псевдо-типи || |------------------------------------------------ -| `list` | [індексований масив |#isList], для кількості елементів може бути заданий діапазон -| `none` | порожнє значення: `''`, `null`, `false` -| `number` | int\|float -| `numeric` | [число, включаючи текстове представлення |#isNumeric] -| `numericint`| [ціле число, включаючи текстове представ лення|#isNumericInt] -| `unicode` | [UTF-8 рядок |#isUnicode], може бути вказано діапазон довжини в символах. +| `list` | індексований масив, можна вказати діапазон для кількості елементів +| `none` | порожнє значення: `''`, `null`, `false` +| `number` | int\|float +| `numeric` | [число, включаючи текстове представлення |#isNumeric] +| `numericint`| [ціле число, включаючи текстове представлення |#isNumericInt] +| `unicode` | [рядок UTF-8 |#isUnicode], можна вказати діапазон для довжини в символах |-------------------------- -| клас символів (не повинен бути порожнім рядком)|| +| символьний клас (не може бути порожнім рядком) || |------------------------------------------------ -| `alnum` | всі символи буквено-цифрові -| `alpha` | всі символи - літери `[A-Za-z]` -| `digit` | всі символи є цифрами -| `lower` | всі символи в нижньому регістрі `[a-z]` -| `space` | всі символи - пробіли -| `upper` | всі символи у верхньому регістрі `[A-Z]` -| `xdigit` | усі символи є шістнадцятковими цифрами `[0-9A-Fa-f]` +| `alnum` | всі символи є буквено-цифровими +| `alpha` | всі символи є літерами `[A-Za-z]` +| `digit` | всі символи є цифрами +| `lower` | всі символи є малими літерами `[a-z]` +| `space` | всі символи є пробілами +| `upper` | всі символи є великими літерами `[A-Z]` +| `xdigit` | всі символи є шістнадцятковими цифрами `[0-9A-Fa-f]` |-------------------------- -| перевірка синтаксису || +| перевірка синтаксису || |------------------------------------------------ -| `pattern` | регулярний вираз, який повинен відповідати **всьому** рядку -| `email` | [E-mail |#isEmail] -| `identifier`| [PHP-ідентифікатор |#isPhpIdentifier] -| `url` | [URL |#isUrl] -| `uri` | [URI |#isUri] +| `pattern` | регулярний вираз, якому повинен відповідати **весь** рядок +| `email` | [E-mail |#isEmail] +| `identifier`| [ідентифікатор PHP |#isPhpIdentifier] +| `url` | [URL |#isUrl] +| `uri` | [URI |#isUri] |-------------------------- -| аутентифікація середовища || +| перевірка середовища || |------------------------------------------------ -| `class` | це існуючий клас -| `interface` | це існуючий інтерфейс -| `directory` | це існуючий каталог -| `file` | це існуючий файл +| `class` | є існуючим класом +| `interface` | є існуючим інтерфейсом +| `directory` | є існуючою директорією +| `file` | є існуючим файлом -Затвердження .[#toc-assertion] -============================== +Assertions +========== assert($value, string $expected, string $label='variable'): void .[method] -------------------------------------------------------------------------- -Перевіряє, що значення є одним з [очікуваних типів |#Expected-Types], розділених зірочкою. Якщо ні, то викидається виняток [api:Nette\Utils\AssertionException]. Слово `variable` у тексті виключення може бути замінено іншим параметром `$label`. +Перевіряє, що значення є одним з [очікуваних типів |#Очікувані типи], розділених вертикальною рискою. Якщо ні, викидає виняток [api:Nette\Utils\AssertionException]. Слово `variable` у тексті винятку можна замінити іншим за допомогою параметра `$label`. ```php Validators::assert('Nette', 'string:5'); // OK @@ -120,10 +120,10 @@ Validators::assert('Lorem ipsum dolor sit', 'string:78'); ``` -assertField(array $array, string|int $key, string $expected=null, string $label=null): void .[method] ------------------------------------------------------------------------------------------------------ +assertField(array $array, string|int $key, ?string $expected=null, ?string $label=null): void .[method] +------------------------------------------------------------------------------------------------------- -Перевіряє, що елемент під ключем `$key` у полі `$array` є одним з [очікуваних типів |#Expected-Types], розділених зірочкою. Якщо ні, то викидається виняток [api:Nette\Utils\AssertionException]. Рядок `item '%' in array` у тексті виключення може бути замінено іншим параметром `$label`. +Перевіряє, чи елемент під ключем `$key` у масиві `$array` є одним з [очікуваних типів |#Очікувані типи], розділених вертикальною рискою. Якщо ні, викидає виняток [api:Nette\Utils\AssertionException]. Рядок `item '%' in array` у тексті винятку можна замінити іншим за допомогою параметра `$label`. ```php $arr = ['foo' => 'Nette']; @@ -136,19 +136,19 @@ Validators::assertField($arr, 'foo', 'int'); ``` -Валідатори .[#toc-validators] -============================= +Валідатори +========== is($value, string $expected): bool .[method] -------------------------------------------- -Перевіряє, що значення є одним з [очікуваних типів |#Expected-Types], розділених зірочкою. +Перевіряє, чи значення є одним з [очікуваних типів |#Очікувані типи], розділених вертикальною рискою. ```php Validators::is(1, 'int|float'); // true Validators::is(23, 'int:0..10'); // false -Validators::is('Nette Framework', 'string:15'); // true, délka je 15 bytů +Validators::is('Nette Framework', 'string:15'); // true, довжина 15 байтів Validators::is('Nette Framework', 'string:8..'); // true Validators::is('Nette Framework', 'string:30..40'); // false ``` @@ -157,7 +157,7 @@ Validators::is('Nette Framework', 'string:30..40'); // false isEmail(mixed $value): bool .[method] ------------------------------------- -Перевіряє, чи є значення дійсною адресою електронної пошти. Він не перевіряє, чи існує домен насправді, перевіряється тільки синтаксис. Функція також враховує майбутні [ДВУ |https://cs.wikipedia.org/wiki/Doména_nejvyššího_řádu], які можуть бути в юнікоді. +Перевіряє, чи є значення дійсною електронною адресою. Не перевіряється, чи домен дійсно існує, перевіряється лише синтаксис. Функція враховує також майбутні [TLD|https://uk.wikipedia.org/wiki/Домен_найвищого_рівня], які можуть бути також в unicode. ```php Validators::isEmail('example@nette.org'); // true @@ -169,9 +169,9 @@ Validators::isEmail('nette'); // false isInRange(mixed $value, array $range): bool .[method] ----------------------------------------------------- -Перевіряє, чи знаходиться значення в заданому діапазоні `[min, max]`де верхня або нижня межа може бути опущена (`null`). Порівнювати можна числа, рядки та об'єкти DateTime. +Перевіряє, чи значення знаходиться в заданому діапазоні `[min, max]`, де верхню або нижню межу можна опустити (`null`). Можна порівнювати числа, рядки та об'єкти DateTime. -Якщо обидві межі відсутні (`[null, null]`) або значення `null`, повертається `false`. +Якщо відсутні обидві межі (`[null, null]`) або значення є `null`, повертає `false`. ```php Validators::isInRange(5, [0, 5]); // true @@ -184,7 +184,7 @@ Validators::isInRange(1, [5]); // false isNone(mixed $value): bool .[method] ------------------------------------ -Перевіряє, що значення дорівнює `0`, `''`, `false` або `null`. +Перевіряє, чи є значення `0`, `''`, `false` або `null`. ```php Validators::isNone(0); // true @@ -213,7 +213,7 @@ Validators::isNumeric('1e6'); // false isNumericInt(mixed $value): bool .[method] ------------------------------------------ -Перевіряє, чи є значення цілим числом або числом, записаним у рядку. +Перевіряє, чи є значення цілим числом або цілим числом, записаним у рядку. ```php Validators::isNumericInt(23); // true @@ -227,7 +227,7 @@ Validators::isNumericInt('nette'); // false isPhpIdentifier(string $value): bool .[method] ---------------------------------------------- -Перевіряє, чи є значення синтаксично допустимим ідентифікатором у PHP, наприклад, для імен класів, методів, функцій тощо. +Перевіряє, чи є значення синтаксично дійсним ідентифікатором у PHP, наприклад, для назв класів, методів, функцій тощо. ```php Validators::isPhpIdentifier(''); // false @@ -240,7 +240,7 @@ Validators::isPhpIdentifier('one two'); // false isBuiltinType(string $type): bool .[method] ------------------------------------------- -Перевіряє, чи є `$type` вбудованим типом PHP. В іншому випадку це ім'я класу. +Перевіряє, чи є `$type` вбудованим типом PHP. В іншому випадку це назва класу. ```php Validators::isBuiltinType('string'); // true @@ -251,7 +251,7 @@ Validators::isBuiltinType('Foo'); // false isTypeDeclaration(string $type): bool .[method] ----------------------------------------------- -Перевіряє, чи є дане оголошення типу синтаксично допустимим. +Перевіряє, чи задана декларація типу є синтаксично правильною. ```php Validators::isTypeDeclaration('?string'); // true @@ -268,7 +268,7 @@ Validators::isTypeDeclaration('(A|B)'); // false isClassKeyword(string $type): bool .[method] -------------------------------------------- -Перевіряє, чи є `$type` одним із внутрішніх типів `self`, `parent`, `static`. +Перевіряє, чи є `$type` одним з внутрішніх типів `self`, `parent`, `static`. ```php Validators::isClassKeyword('self'); // true @@ -279,7 +279,7 @@ Validators::isClassKeyword('Foo'); // false isUnicode(mixed $value): bool .[method] --------------------------------------- -Перевіряє, що значення є допустимим рядком UTF-8. +Перевіряє, чи є значення дійсним рядком UTF-8. ```php Validators::isUnicode('nette'); // true @@ -291,7 +291,7 @@ Validators::isUnicode("\xA0"); // false isUrl(mixed $value): bool .[method] ----------------------------------- -Перевіряє, чи є значення дійсним URL. +Перевіряє, чи є значення дійсною URL-адресою. ```php Validators::isUrl('https://nette.org:8080/path?query#fragment'); // true @@ -306,7 +306,7 @@ Validators::isUrl('nette.org'); // false isUri(string $value): bool .[method] ------------------------------------ -Перевіряє, чи є значення дійсною адресою URI, яка фактично являє собою рядок, що починається з синтаксично допустимої схеми. +Перевіряє, чи є значення дійсною URI-адресою, тобто фактично рядком, що починається синтаксично дійсним схемою. ```php Validators::isUri('https://nette.org'); // true diff --git a/www/bg/10-reasons-why-nette.texy b/www/bg/10-reasons-why-nette.texy index 790c674c8c..947ba46269 100644 --- a/www/bg/10-reasons-why-nette.texy +++ b/www/bg/10-reasons-why-nette.texy @@ -1,53 +1,93 @@ -Защо да използвате Nette? -************************* +7 причини да използвате Nette +*****************************
                                                                                                                            -Уморени сте от повтарящи се задачи, от хиляди дребни неща, които ви разсейват от работата и превръщат програмирането в скучно занимание? Намираш се на правилното място! **Фреймуъркът ще улесни работата ви, ще пишете по-малко, ще имате по-чист код и ще се забавлявате повече: +Представете си PHP framework, който ви позволява да се съсредоточите върху това, което обичате да правите най-много. Води ви към писане на чист код. Сам се грижи за сигурността. Спрете да мечтаете и се запознайте с Nette. Поемете по пътя, който ще ви отвори вратите към нови възможности за разработка. Ще си кажем: -- страхотна система за шаблони -- несравними инструменти за персонализация -- изключително ефективен слой за бази данни -- силна защита срещу уязвимости -- модерна рамка с поддръжка на HTML5, AJAX или SEO -- Качествена документация и динамична общност -- зрял и изчистен обектно-ориентиран дизайн, използващ най-новите функции на PHP -- решения, които се насърчават, но не се използват в практиката. +- как да създавате уебсайтове с максимално удобство +- как да пишете елегантен код +- какво означава мъдростта „по-малко код = по-голяма сигурност“ +- как да изграждате уебсайт като конструктор +- и как да станете част от успешна общност
                                                                                                                            -И е напълно безплатен. Струва си да опитате, нали? +Nette носи удобство и ефективност в света на уеб разработчиците благодарение на иновативни инструменти и техники. Какви са ключовите характеристики, които правят Nette уникален и незаменим елемент от набора на разработчика? Нека ги разгледаме! -**Рамката на Nette е проектирана така, че да бъде възможно най-удобна за потребителя. Това е рамка, с която е не само лесно, но и забавно да се работи. Тя ви дава ясен и опростен синтаксис, лесна е за програмиране и отстраняване на грешки и ви позволява да се съсредоточите върху творческата страна на разработката. Той премахва рисковете за сигурността. Можете да създавате онлайн магазини, уикита, блогове, CMS и всичко друго, което можете да си представите, по-бързо и по-добре от всякога. +#1 Nette ви глези +----------------- -Nette се [използва от големи компании |https://builtwith.nette.org] като T-Systems, GE Money, Mladá fronta, VLTAVA-LABE-PRESS, Internet Info, DHL, Logio, ESET, Actum, Slevomat, Socialbakers, SUPRAPHON. Понастоящем на него работи уебсайтът на бившия президент на Чешката република Вацлав Клаус. В проучване, проведено от [Zdroják |https://www.zdrojak.cz/clanky/vysledky-technologie-na-ceskem-webu/], тя печели приза за най-популярната и най-използваната рамка в Чешката република. +Когато преди двадесет години започна да се ражда Nette Framework, всичко се въртеше около една цел: Как да създаваме уебсайтове възможно най-удобно? Как да направим работата на програмистите възможно най-приятна? Как да направим създаването на уебсайтове секси? -След като овладеете Nette, няма да ви липсват интересни предложения за работа. +Nette с тази концепция привлече много програмисти и бързо спечели популярност. Тогава наричахме тази философия Netteway, а днес вече съществува термин за нея, *Developer Experience* (DX). Nette е framework, който има DX в своята ДНК. Разликата ще усетите в хиляда и едно нещо – от съвсем дребни неща до основни иновации. Оставете се framework-ът да ви глези. -Да започнем работа .[#toc-get-on-board] +#2 Nette ви води към чист код +----------------------------- + +Искате ли да пишете чист код? Да имате правилно проектирани приложения? Кой не би искал! И точно тук започва ролята на framework-а. Ако самият той не дава пример, не може да се създаде отлично проектирано приложение. + +Nette дава пример. Той е ментор, който учи на добри навици и писане на код според утвърдени методики. Като пионер и евангелизатор на dependency injection, той предлага качествена основа за устойчиви, разширяеми и лесно четими приложения. Nette е проектиран така, че да бъде разбираем за начинаещи, като същевременно предлага достатъчна дълбочина за опитни разработчици. + +Общността около Nette е възпитала редица личности, които днес стоят зад успешни и важни проекти. За хиляди програмисти Nette се превърна в ментор по пътя им към професионален растеж. Присъединете се и открийте как Nette ще повлияе положително на качеството на вашия код и приложения. + + +#3 Надежден пазител на вашите приложения +---------------------------------------- + +Nette защитава вашите приложения. През годините си спечели репутацията на инструмент, който приема сигурността изключително сериозно. Предоставя усъвършенствана защита срещу уязвимости. Винаги се стреми да улесни работата на програмистите, но никога за сметка на сигурността. + +Неговото мото "по-малко код = достатъчно сигурност" означава, че отделните елементи се държат безопасно още в основата си. Не е необходимо да се активират защитни елементи чрез дописване на допълнителен код. Следователно не можете да забравите за това или да се страхувате, че сте пропуснали нещо. Програмистите често дори не подозират колко много неща, свързани със сигурността, Nette прави вместо тях и са изненадани, когато научат за това. + +Представяме ви framework, който ви води към чист код и същевременно бди над сигурността на вашите приложения. Nette е надежден партньор, който ви позволява да се съсредоточите върху създаването на страхотни уеб приложения със спокойствие. + + +#4 Изграждайте уебсайт като конструктор --------------------------------------- -Следвайте ръководството и [създайте първото си приложение |quickstart:] стъпка по стъпка. Пълното [ръководство за програмисти |@home] и удобната [документация на API с |https://api.nette.org/] преглед на класовете и методите ще бъдат винаги на една ръка разстояние. +В Nette изграждате страници от повторно използваеми UI компоненти. Това напомня на разработката на десктоп приложения и Nette успешно пренесе този подход в уеб. Нуждаете се от datagrid в администрацията? Просто го намерете на пазара на open source компоненти, инсталирайте го и лесно го вмъкнете в страницата. Освен това можете да създавате собствени компоненти за повтарящи се елементи на страниците, като по този начин елиминирате дублирането и подобрявате организацията на кода. + +Тази уникална характеристика отличава Nette от всички останали значими играчи на пазара. Тя ви позволява ефективно да създавате и поддържате уеб приложения. С Nette работата с UI се превръща в гладко и приятно изживяване. + + +#5 Гъвкав набор от пакети +------------------------- + +Nette е набор от [самостоятелно използваеми пакети |www:packages]. Сред тях са пристрастяващият [инструмент за дебъгване Tracy |tracy:], [система за шаблони от ново поколение Latte |latte:], [отличен Dependency Injection Container |dependency-injection:], [форми |forms:] и много други. Всеки пакет има четивна подробна документация и се намира в отделно хранилище в GitHub. Пакетите можете да използвате самостоятелно или да комбинирате с други инструменти и технологии, които вече използвате. Например Latte може да се внедри в WordPress или Slim Framework, DI контейнерът може да бъде ядрото на фирмен framework, а Tracy ще визуализира съобщенията за грешки. + +Или можете да използвате Nette като цяло, като framework, и да създадете в него цялостно уеб приложение. Независимо дали разработвате малък личен проект или стабилно корпоративно приложение, Nette ще бъде вашият надежден партньор. + + +#6 Стабилност & иновации +------------------------ + +Nette е зрял и доказан framework с дългогодишна история. Въпреки това се поддържа гъвкав и адаптивен благодарение на интелигентния си дизайн. Потребителите оценяват, че това е малък и гъвкав framework, а не тромав гигант. + +Винаги е готов предварително за новите версии на PHP и отчита най-новите иновации в областта на разработката на уеб приложения. Това е ключово за минимизиране на технологичния дълг. + +Следователно Nette е комбинация от дългогодишна стабилност и иновации, която ви позволява да разчитате на доказано решение, докато можете да използвате най-новите технологии и тенденции. С Nette имате сигурността, че вашият проект ще бъде изграден върху основи, които непрекъснато вървят в крак с бързо развиващия се свят на уеб разработката. + -Ако се затрудните, вижте раздела за [решаване на проблеми |nette:troubleshooting] или [официалния форум |https://forum.nette.org/en/]. +#7 Бъдете в най-добрата компания +-------------------------------- -Струва ли ви се, че самообучението е неефективен начин? Ние разбираме и предлагаме [обучение по рамката Nette |https://www.skoleniphp.cz/skoleni-nette-vyvoj-webovych-aplikaci] (на чешки език). Отзиви от бивши участници можете да намерите на [skoleniphp.cz/ohlasy |https://www.skoleniphp.cz/ohlasy]. +Nette Framework е популярен сред професионалистите. На него разчитат [значими компании |https://builtwith.nette.org] като O2, BOSCH, ESET, Zásilkovna, DHL, SUPRAPHON и стотици други. В няколко анкети е печелил като най-популярния и най-използвания framework в Чешката република. +Започнете с Nette и ще ви се отворят нови възможности за работа. Ще получите не само мощен инструмент, но и най-активната общност в Чехия, която е готова да ви подкрепи по пътя ви към професионален растеж. Редовно се събират на [Posobota |www.posobota.cz], събития, където обменят опит. Елате да се срещнем! -Повече информация .[#toc-more-information] ------------------------------------------- -Имаме [блог, пълен със съвети |https://blog.nette.org], и колекция от различни [добавки и компоненти, които |https://componette.org] можете да използвате в приложенията си. +Открийте Nette +-------------- -Освен това всеки месец общността на Нете се събира на събитие, наречено [Последната събота |https://www.posobota.cz], на което членовете на общността споделят своя опит и просто се забавляват. Елате и изпийте по бира! +Присъединете се към хилядите доволни разработчици, които вече са открили предимствата на Nette и започнете да пишете по-чист, по-сигурен и по-ефективен код с този уникален framework. +Създайте [според ръководството |quickstart:] първото си приложение, стъпка по стъпка. Постоянно ще ви бъде под ръка [обширна документация |nette:] и практичен [преглед на API |https://api.nette.org]. Посетете нашия [блог, пълен със съвети |https://blog.nette.org] и колекция от [допълнения и компоненти |https://componette.org], разширяващи възможностите на Nette. -Включете се! .[#toc-get-involved] ---------------------------------- +Имате въпроси? Обърнете се към страницата с [често задавани въпроси|nette:troubleshooting] или дискутирайте на [чешкия форум|https://forum.nette.org/cs/]. Предпочитате лично обучение? Предлагаме [обучение за Nette Framework |https://www.skoleniphp.cz/skoleni-nette-vyvoj-webovych-aplikaci] с [отлични отзиви |https://www.skoleniphp.cz/ohlasy]. -Ще се радваме, ако допринесете за развитието на рамката. Препоръчайте Nette на приятелите си, поставете [икона |www:en:logo] на уебсайта си или [подкрепете |www:en:donate] финансово [разработката |www:en:donate]. Благодаря ви ♥. +**Запознайте се с framework-а, който ще ви глези, води и вдъхновява.** {{leftbar: @menu-common}} diff --git a/www/bg/@home.texy b/www/bg/@home.texy index 056ce3859f..272d8a44eb 100644 --- a/www/bg/@home.texy +++ b/www/bg/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Nette - лесно и сигурно разработване на уеб приложения на PHP}} -{{description: Nette е семейство от усъвършенствани и самостоятелни компоненти за PHP. Радвайте се на тях. Заедно те са третата най-популярна рамка в света. Философията на Nette набляга на производителността, най-добрите практики и сигурността}} +{{maintitle:Nette – Удобна и сигурна разработка на уеб приложения в PHP}} +{{description: Nette е семейство от зрели и самостоятелно използваеми компоненти за PHP. Позволете им да ви вдъхновят. Заедно те образуват framework, оценен като 3-тият най-популярен в света. Философията на Nette поставя изключителен акцент върху производителността, най-добрите практики и сигурността.}} diff --git a/www/bg/@menu-common.texy b/www/bg/@menu-common.texy index e867022ec0..c73571ce15 100644 --- a/www/bg/@menu-common.texy +++ b/www/bg/@menu-common.texy @@ -1,20 +1,22 @@ -Въведение -********* +Запознаване +*********** - [Защо да използвате Nette? |www:10-reasons-why-nette] - [Инсталация |nette:installation] -- [Създайте първото си приложение! |quickstart:] +- [Пишем първото приложение! |quickstart:] Общи теми ********* - [Списък на пакетите |www:packages] -- [Поддръжка и PHP |www:maintenance] -- [Бележки към изданието |https://nette.org/releases] -- [Ръководство за надграждане |migrations:en] -- [Решаване на проблеми |nette:troubleshooting] -- [Създатели на Nette |https://nette.org/contributors] -- [Историята на Nette |history] +- [Поддръжка и PHP версии |www:maintenance] +- [Бележки по изданието |https://nette.org/releases] +- [Преминаване към по-нови версии |migrations:en] +- [Отстраняване на проблеми |nette:troubleshooting] +- [Кой създава Nette |https://nette.org/contributors] +- [История на Nette |history] - [Включете се |contributing:] -- [Развитие на спонсори |https://nette.org/en/donate] -- [Референтно ръководство за API |https://api.nette.org] -- [Най-добри практики |best-practices:] +- [Подкрепете разработката |https://nette.org/cs/donate] +- [API референция |https://api.nette.org/] +- [Ръководства и практики |best-practices:] + +- [Сигурността преди всичко |nette:vulnerability-protection] diff --git a/www/bg/donate.texy b/www/bg/donate.texy new file mode 100644 index 0000000000..bccb065b66 --- /dev/null +++ b/www/bg/donate.texy @@ -0,0 +1,20 @@ +Подкрепете разработката на Nette +******************************** + +
                                                                                                                            +Всеки, който изгражда върху Nette, има интерес framework-ът да се развива активно. Да поддържа новите версии на PHP. Да се поправят грешки. Да се появяват нови функции, които улесняват работата или спестяват време и пари. Framework-ът да има страхотна документация и около него да съществува полезно съдържание, било то под формата на статии, ръководства или видеа. + +Редица части на Nette представляват световен връх и искаме това да продължи. + +Без адекватно финансиране нищо от това не може да бъде осигурено. При това, за да можете да разчитате, че ще излязат следващи версии, е необходимо съвсем малко: да го подкрепяте всеки месец дори с малка финансова сума. + +Включете се и станете партньор на Nette! + +Така ще осигурите здравословното функциониране на проекта, на който разчитате. И същевременно **ще получите цял ред ексклузивни предимства** (вижте *Нива на партньорство* в дясната колона). Ще получите достъп до бонус съдържание. До техническа поддръжка. Ще повишите приоритета на решаване на вашите issues. А също така ще направите вашата компания видима и ще привлечете към себе си разработчици. + +Как ще ви направим видими? Например чрез публикуване на вашето лого на този уебсайт (т.е. на тази страница, на началната страница, в документацията, във форума и на [специална страница |https://nette.org/partner/vitalita], към която можете да препращате). Ще имате възможност да публикувате [обяви за работа |https://forum.nette.org/cs/f30-prace-a-zakazky], да рекламирате във форума ([пример |https://forum.nette.org/cs/30798-problem-s-cizim-klicem-pri-mazani#p198093]) или в документацията ([пример |https://doc.nette.org/cs/application/components#toc-flash-zpravy]), т.е. на места с абсолютно най-добър обхват сред групата на Nette разработчиците. + +На партньорите издаваме фактури, за да могат да включат подкрепата в разходите си, било то месечно, тримесечно, полугодишно или годишно. + +{{include: buttons}} +
                                                                                                                            diff --git a/www/bg/history.texy b/www/bg/history.texy index 86524096bf..670cfbbf03 100644 --- a/www/bg/history.texy +++ b/www/bg/history.texy @@ -2,34 +2,34 @@ **************** .[perex] -Корените на Nette датират от 2004 г., когато нейният автор Дейвид Грудъл започва да търси рамка за писане на приложения, тъй като чистият PHP вече не е достатъчен. Нито едно от наличните по това време решения не го устройва, затова постепенно започва да набелязва характеристиките на нова рамка, която по-късно е наречена Nette. +Началото на Nette датира от 2004 г., когато неговият автор Давид Грудл започва да търси подходящ framework, в който да може да пише приложения, тъй като чистият PHP вече не е достатъчен за това. Нито едно от тогавашните налични решения не му допада, така че постепенно започва да очертава характеристиките на нов framework, който по-късно получава името Nette. -По онова време все още не съществуваха модерни рамки като Symfony, Laravel или Ruby on Rails. В света на Java стандартът беше JSF (JavaServer Faces), а в конкурентния свят на .NET стандартът беше ASP.NET Webforms. И двете позволяват създаването на страници, използващи компоненти на потребителския интерфейс за многократна употреба. Дейвид намери техните методи за абстракция и опити за създаване на безсъстоятелност в безсъстоятелния протокол HTTP с помощта на сесии или обратни връзки за погрешни и фундаментално неприложими. Те създадоха много трудности за потребителите и търсачките. Например, ако сте запазили връзка, по-късно с изненада сте открили под нея друго съдържание. +По това време все още не съществуват съвременните framework-ове като Symfony, Laravel или Ruby on Rails. В света на Java стандартът е framework JSF (JavaServer Faces), а в конкурентния .NET - ASP.NET Webforms. И двата позволяват изграждането на страници с помощта на повторно използваеми UI компоненти. Техните начини на абстракция и опити да се създаде състояние над безсъстоянийния протокол HTTP с помощта на сесия или т.нар. postback Давид счита за погрешни и фундаментално нефункционални. Те причиняват редица трудности както на потребителите, така и на търсачките. Например, ако сте запазили линк, по-късно с изненада сте намирали под него различно съдържание. -Самата възможност за съставяне на страници от компоненти на потребителския интерфейс за многократна употреба очарова Дейвид и той я познаваше добре от Delphi, популярен по това време инструмент за разработка на настолни приложения. Той харесва пазарите на компоненти с отворен код за Delphi. Затова се опита да измисли как да създаде компонентна рамка, която на свой ред да работи в пълна хармония с Delphi. Той търсеше концепция, която да е удобна за потребителите, SEO и за разработчиците. Така се ражда Нете. +Самата възможност за съставяне на страници от повторно използваеми UI компоненти очарова Давид, той я познава добре от Delphi, тогава популярен инструмент за създаване на десктоп приложения. Харесват му пазарите с opensource компоненти за Delphi. Затова се опитва да реши въпроса как да създаде компонентен framework, който, напротив, да работи в пълно съответствие с безсъстоянийния HTTP. Търси концепция, която да бъде приятелска за потребителите, SEO и разработчиците. И така започва да се ражда Nette. .[note] -Името Нете се появява случайно в банята, когато авторът забелязва контейнер с гел за бръснене Gillette, обърнат така, че се вижда само *llette*. +Името Nette възниква случайно в банята, когато авторът забелязва опаковка с гел за бръснене Gillette, обърната така, че се вижда само *llette*. -Последваха хиляди часове на проучване, обмисляне и пренаписване. В един прашен гараж в село край Бърно се създават първите очертания на бъдещата рамка. Архитектурата е базирана на модела MVC, който след това е използван в забравената вече PHP рамка Mojavi, а по-късно е популяризиран от шумотевицата Ruby on Rails. Един от източниците на вдъхновение беше никога непубликуваната рамка phpBase на Honza Tichy. +Следват хиляди часове изследвания, размишления и пренаписвания. В прашен гараж в село някъде зад Бърно възникват първите очертания на бъдещия framework. Основата на архитектурата става моделът MVC, който тогава използва днес вече забравен PHP framework Mojavi и по-късно е популяризиран благодарение на шума около Ruby on Rails. Един от източниците на вдъхновение е дори никога непубликуваният framework phpBase на Хонза Тихи. -В блога на автора започват да се появяват статии за предстоящото излизане на Nette. Шегуваха се, че става дума за Steam. Но през октомври 2007 г., на конференцията PHP Seminar в Прага, Дейвид публично представи Nette. Между другото, година по-късно тази конференция се превърна в WebExpo, която впоследствие стана една от най-големите ИТ конференции в Европа. Още тогава Nette се отличаваше с редица оригинални концепции, като например гореспоменатия модел на компонентите, двупосочен маршрутизатор, специален начин на комуникация между презентаторите и др. Имаше формуляри, удостоверяване, кеширане и др. Всички те се използват и днес в Нете в първоначалната си концепция. +В блога на автора започват да излизат статии за предстоящия Nette. Шегува се, че става въпрос за vaporware. Но след това през октомври 2007 г. на пражката конференция PHP Seminář Давид публично представя Nette. Между другото, от тази конференция година по-късно се развива WebExpo, по-късно една от най-големите IT конференции в Европа. Още тогава Nette се похвали с редица оригинални концепции, като споменатия компонентен модел, двупосочен рутер, специфичен начин на препращане между презентери и т.н. Имаше форми, решена автентикация, кеширане и т.н. Всичко това се използва в Nette в оригиналната си концепция и до днес. .[note] -Nette използва *presenter* вместо *controller*, защото се предполага, че в кода е имало твърде много думи, започващи с *con* (controller, front controller, control, config, container, ...). +В Nette вместо термина *controller* се използва *presenter*, защото в кода уж имало твърде много думи, започващи с *con* (controller, front controller, control, config, container, ...) -В края на 2007 г. Дейвид Грудъл публикува кода и излиза Nette 0.7. Около него се формира общност от ентусиасти по програмиране, които започват да се срещат всеки месец на събитие на Posobota. Общността включваше много от съвременните светила, като Ондрей Миртес, автор на чудесния инструмент PHPStan. Разработката на Nette продължи напред и през следващите две години бяха издадени версии 0.8 и 0.9, които положиха основите на почти всички съвременни части на рамката. Включително AJAX фрагменти, които в продължение на 14 години предшестваха Hotwire за Ruby on Rails или Symfony UX Turbo. +В края на 2007 г. Давид Грудл публикува и кода и така светът вижда версия Nette 0.7. Framework-ът веднага привлича огромно внимание. Около него се формира ентусиазирана общност от програмисти, която започва да се събира всеки месец на събитието Posobota. В общността има редица днешни личности, например Ондржей Миртес, автор на страхотния инструмент PHPStan. Развитието на Nette напредва бързо и през следващите две години излизат версии 0.8 и 0.9, където са положени основите на почти всички днешни части на framework-а. Включително AJAX снипетите, които с 14 години изпреварват Hotwire за Ruby on Rails или Symfony UX Turbo. -Но по онова време Нете е пропускала едно важно нещо. Контейнер за инжектиране на зависимости (DIC). Nette използваше *service locator* и имаше намерение да премине към dependecy injection. Но как да се създаде такова нещо? Дейвид Грудъл, който по това време нямаше никакъв опит с DI, отиде на обяд с Ваке Перчарт, който използваше DI от около шест месеца. Заедно те обсъждат темата и Дейвид започва работа по Nette DI - библиотека, която изцяло променя начина, по който мислим за дизайна на приложенията. Контейнерът DI се превърна в една от най-успешните части на рамката. Това доведе до създаването на два допълнителни продукта: формата Neon и библиотеката Schema. +Едно основно нещо обаче липсва в тогавашния Nette. Dependency injection container (DIC). Nette използва т.нар. *service locator* и намерението е да се премине именно към dependency injection. Но как да се проектира такова нещо? Давид Грудл, който тогава няма опит с DI, отива на обяд с Вашек Пурхарт, който използва DI от около половин година. Заедно обсъждат темата и Давид започва работа по Nette DI, библиотека, която напълно преобръща начина, по който се мисли за дизайна на приложенията. DI контейнерът става една от най-успешните части на framework-а. И по-късно дава началото и на два spin-off-а: формата Neon и библиотеката Schema. .[note] -Преходът към внедряване на зависимости отне много време, а новата версия на Nette се подготвяше няколко години. Ето защо, когато най-накрая излиза, е номериран с номер 2. Така Nette версия 1 не съществува. +Преходът към dependency injection отнема доста време и за новата версия на Nette се чака няколко години. Затова, когато най-накрая излиза, носи направо номер 2. Версия Nette 1 следователно не съществува. -Nette започва своята модерна история през 2012 г. с версия 2.0. Тя също така донесе Nette Database, която включваше изключително удобен инструмент за бази данни, сега наречен Explorer. Тази библиотека първоначално е програмирана от Якуб Врана, съсед на Дейвид Грудел и автор на популярния инструмент Adminer. За по-нататъшното развитие в продължение на три години се грижи Ян Шкрашек. +Nette през 2012 г. с версия 2.0 стартира своята модерна история. Донесе и Nette Database, чиято част беше и необикновено удобен инструмент за работа с база данни, днес наричан Explorer. Тази библиотека първоначално е програмирана от Якуб Врана, съсед на Давид Грудл и автор на популярния инструмент Adminer. След това нейното по-нататъшно развитие за три години поема Ян Шкрашек. -През 2014 г. беше пусната версия Nette 2.1, а скоро след това и Nette 2.2. Версия 2.2 беше същата като версия 2.1, само че разделена на двадесет отделни пакета. Инструментът Composer пусна корени в света на PHP и промени начина, по който мислехме за създаването на библиотеки. Nette е престанала да бъде монолит и се е разпаднала на по-малки, независими части. Всяка от тях има собствено хранилище, система за проследяване на проблеми и собствен темп на разработка и създаване на версии. По този начин на Nette не се налага да преминава през абсурдите, характерни за монолитните рамки, когато се пуска нова версия на даден пакет, въпреки че нищо не се е променило. Действителното разделяне на хранилищата на Git отне седмици подготовка и стотици часове машинно време. +През 2014 г. излиза Nette 2.1, последвана скоро от Nette 2.2. Как е възможно това? Версия 2.2 е същата като версия 2.1, само че разделена на двадесет самостоятелни пакета. В света на PHP се установява инструментът Composer и променя начина, по който се възприема създаването на библиотеки. Така Nette престава да бъде монолит и се разпада на по-малки независими части. Всяка със свое хранилище, issue tracker и собствен темп на развитие и версиониране. В Nette така не се налага да се стига до абсурди, обичайни в монолитните framework-ове, когато излиза нова версия на пакет, въпреки че в него изобщо нищо не се е променило. Самото разделяне на Git хранилищата отнема няколко седмици подготовка и стотици часове машинно време. -Nette също така зае невероятното 3-то място в глобалното проучване за най-добра PHP рамка, организирано от списание Sitepoint. +Nette също така се класира на удивителното 3-то място в световна анкета за най-добър PHP framework, организирана от списание Sitepoint. {{toc:no}} diff --git a/www/bg/license.texy b/www/bg/license.texy new file mode 100644 index 0000000000..4ec016feb4 --- /dev/null +++ b/www/bg/license.texy @@ -0,0 +1,44 @@ +Лицензионна политика +******************** + +Nette Framework се разпространява като свободен софтуер, за да може всеки да го използва. Можете да изберете дали ви е по-удобен лицензът [Нов BSD |#New BSD License] или [#GNU General Public License (GPL)] във версия 2 или 3. + +Лицензът BSD се препоръчва за повечето проекти, тъй като е лесен за разбиране и не поставя почти никакви ограничения върху това какво можете да правите с framework-а. Можете да използвате Nette и в комерсиални проекти. Ако обаче GPL е по-подходящ за вашия проект, изберете него. При това не е необходимо да информирате никого как сте решили. Винаги запазвайте оригиналните авторски права. + +Трябва да знаете, че името "Nette Framework" е защитена търговска марка и нейното използване има определени ограничения. Затова не използвайте "Nette" в името на вашия проект или домейн от най-високо ниво, изберете по-скоро име, което ще стои на собствени основи. Ако вашият проект е добър, няма да отнеме много време и ще си създаде собствена репутация. + +Ако сте доволни от Nette Framework, а вярваме, че ще бъдете, можете да го [подкрепите с дарение |donate]. Благодарим ви. + + +New BSD License +--------------- + +Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com) All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of "Nette Framework" nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +GNU General Public License (GPL) +-------------------------------- + +Текстовете на GPL лицензите са много дълги, затова тук са посочени връзки към тях: + +- GPL version 2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +- GPL version 3: https://www.gnu.org/licenses/gpl-3.0.html + + +{{toc:yes}} +{{priority: -2}} diff --git a/www/bg/maintenance.texy b/www/bg/maintenance.texy index 64053467c0..a749a900ce 100644 --- a/www/bg/maintenance.texy +++ b/www/bg/maintenance.texy @@ -2,22 +2,21 @@ ****************************** .[perex] -Nette е рамка с изключително дълъг период на поддръжка за всяка версия. Всеки клон е версия LTS (Long-Term Support Release) с поне 2 години поддръжка. +Nette е framework с изключително дълъг период на поддръжка за отделните издания. Всяка главна версия е LTS (Long-Term Support Release) с поддръжка най-малко 2 години. -Всяка версия се поддържа активно в продължение на една година (или повече) от датата на първата стабилна версия. -Критичните уязвимости и уязвимостите в сигурността се поправят в рамките на две години. +Всяка версия се поддържа активно за период от една година (или повече) от първоначалното стабилно издание. Критичните грешки и грешките в сигурността се поправят в продължение на две години. -Календар за пускане на продукти на Nette .[#toc-release-calendar-roadmap] -========================================================================= +Календар на изданията на Nette +============================== {{include: doc-roadmap-table}} -Съвместимост с PHP .[#toc-php-compatibility] -============================================ +Съвместимост с PHP +================== -Съвместимостта винаги се отнася за последната версия на всяка серия. +Съвместимостта винаги важи за най-новото издание от всяка серия. {{include: doc-roadmap-versions}} @@ -25,4 +24,4 @@ Nette е рамка с изключително дълъг период на п {{leftbar: @menu-common}} {{toc: no}} -{{description: Издаване на Nette, пътна карта, таблици за поддръжка и съвместимост с PHP}} +{{description: Издания на Nette, пътна карта, таблици за поддръжка и съвместимост с PHP}} diff --git a/www/bg/packages.texy b/www/bg/packages.texy index 235f1f965d..cc362f1f78 100644 --- a/www/bg/packages.texy +++ b/www/bg/packages.texy @@ -1,26 +1,27 @@ -Списък на пакета -**************** +Списък на пакетите на Nette +*************************** -| **Application**:[application:how-it-works] | web application core | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] -| **Bootstrap**:[bootstrap:] | bootstrap на вашето приложение | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] -| **Caching**:[caching:] | слой кеш с набор за съхранение | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] -| **Component Model**:[component-model:] | рамка за компонентни системи | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] -| **DI**:[dependency-injection:] | Контейнер за инжектиране на зависимости | [GitHub |https://github.com/nette/di] [API |https://api.nette.org/di/] -| **Database**:[database:] | слой бази данни | [GitHub |https://github.com/nette/database] [API |https://api.nette.org/database/] -| **Forms**:[forms:] | улеснява значително защитата на уеб формуляри | [GitHub |https://github.com/nette/forms] [API |https://api.nette.org/forms/] -| **Http**:[http:] | Слой за HTTP заявки и отговори | [GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] -| **Latte**:[latte:] | Удивителна машина за шаблони | [GitHub |https://github.com/nette/latte] [API |https://api.nette.org/latte/] -| **Mail**:[mail:] | Изпращане на имейл | [GitHub |https://github.com/nette/mail] [API |https://api.nette.org/mail/] -| **Neon**:[neon:] | зареждане и нулиране на формата [NEON |https://ne-on.org] | [GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] +| **Application**:[application:how-it-works] | Ядро на уеб приложения | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] +| **Активи**:[assets:] | Управление на статични файлове | [GitHub |https://github.com/nette/assets] [API |https://api.nette.org/assets/] +| **Bootstrap**:[bootstrap:] | Bootstrap на уеб приложение | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] +| **Caching**:[caching:] | Кеширащ слой със хранилища | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] +| **Component Model**:[component-model:] | Основа на компонентната система | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] +| **DI**:[dependency-injection:] | Dependency Injection Container | [GitHub |https://github.com/nette/di] [API |https://api.nette.org/di/] +| **Database**:[database:] | Слой за база данни | [GitHub |https://github.com/nette/database] [API |https://api.nette.org/database/] +| **Forms**:[forms:] | Удобни и сигурни уеб форми | [GitHub |https://github.com/nette/forms] [API |https://api.nette.org/forms/] +| **Http**:[http:] | Слой, капсулиращ HTTP заявка & отговор | [GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] +| **Latte**:[latte:] | Страхотна система за шаблони | [GitHub |https://github.com/nette/latte] [API |https://api.nette.org/latte/] +| **Mail**:[mail:] | Изпращане на имейли | [GitHub |https://github.com/nette/mail] [API |https://api.nette.org/mail/] +| **Neon**:[neon:] | Четене и запис на формат [NEON |https://ne-on.org] | [GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] | **Php Generator**:[php-generator:] | Генератор на PHP код | [GitHub |https://github.com/nette/php-generator] [API |https://api.nette.org/php-generator/] -| **RobotLoader**:[robot-loader:] | най-удобният автозареждащ модул | [GitHub |https://github.com/nette/robot-loader] [API |https://api.nette.org/robot-loader/] -| **Routing**:[application:routing] | Маршрутизиране | [GitHub |https://github.com/nette/routing] [API |https://api.nette.org/routing/] -| **SafeStream**:[safe-stream:] | безопасни атомарни операции с файлове | [GitHub |https://github.com/nette/safe-stream] [API |https://api.nette.org/safe-stream/] -| **Security**:[security:authentication] | Предоставя система за контрол на достъпа | [GitHub |https://github.com/nette/security] [API |https://api.nette.org/security/] -| **Schema**:[schema:] | валидиране на данните на потребителите | [GitHub |https://github.com/nette/schema] [API |https://api.nette.org/schema/] -| **Tester**:[tester:] | Лесно тестване на единици в PHP | [GitHub |https://github.com/nette/tester] [API |https://api.nette.org/tester/] -| **Tracy**:[tracy:] | инструмент за отстраняване на грешки, който ще ви хареса | [GitHub |https://github.com/nette/tracy] [API |https://api.nette.org/tracy/] -| **Utils**:[utils:] | Помощни програми и основни класове | [GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] +| **Robot Loader**:[robot-loader:] | Най-удобното автоматично зареждане | [GitHub |https://github.com/nette/robot-loader] [API |https://api.nette.org/robot-loader/] +| **Routing**:[application:routing] | Маршрутизация | [GitHub |https://github.com/nette/routing] [API |https://api.nette.org/routing/] +| **Safe Stream**:[safe-stream:] | Безопасни атомарни операции с файлове | [GitHub |https://github.com/nette/safe-stream] [API |https://api.nette.org/safe-stream/] +| **Schema**:[schema:] | Валидация на потребителски данни | [GitHub |https://github.com/nette/schema] [API |https://api.nette.org/schema/] +| **Security**:[security:authentication] | Управление на правата за достъп | [GitHub |https://github.com/nette/security] [API |https://api.nette.org/security/] +| **Tester**:[tester:] | Удобни единични тестове в PHP | [GitHub |https://github.com/nette/tester] [API |https://api.nette.org/tester/] +| **Tracy**:[tracy:] | Инструмент за дебъгване, който ще обикнете ♥ | [GitHub |https://github.com/nette/tracy] [API |https://api.nette.org/tracy/] +| **Utils**:[utils:] | Основни класове и инструменти | [GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] {{leftbar: @menu-common}} {{toc: no}} diff --git a/www/cs/10-reasons-why-nette.texy b/www/cs/10-reasons-why-nette.texy index dcf39bd9e1..4ba51e4de8 100644 --- a/www/cs/10-reasons-why-nette.texy +++ b/www/cs/10-reasons-why-nette.texy @@ -1,53 +1,93 @@ -Proč používat Nette? -******************** +7 důvodů, proč používat Nette +*****************************
                                                                                                                            -Už vás nebaví řešit opakující se úkoly, tisíce drobností, které odvádějí pozornost od práce a dělají z programování nudnou činnost? Jste na správném místě! **Framework vám ulehčí práci, budete méně psát, mít přehlednější kód a radost z práce.** Získáte: +Představte si PHP framework, který vám umožní soustředit se na to, co děláte nejraději. Vede vás k psaní čistého kódu. Sám dbá na bezpečnost. Přestaňte snít a poznejte Nette. Vydejte se na cestu, která vám otevře dveře k novým možnostem vývoje. Řekneme si: -- excelentní šablonovací systém -- bezkonkurenční ladící nástroje -- neobyčejně efektivní databázovou vrstvu -- důmyslné zabezpečení před zranitelnostmi -- moderní framework s podporou HTML5, AJAX nebo SEO -- s kvalitní dokumentací a nejaktivnější komunitou v ČR -- s vyzrálým a čistým objektovým návrhem -- vedoucím k dobrým návykům a dávajícím dostatek volnosti +- jak tvořit weby s maximálním pohodlím +- jak psát elegantní kód +- co znamená moudrost „méně kódu = větší bezpečnost“ +- jak stavět web jako stavebnici +- a jak se stát součástí úspěšné komunity
                                                                                                                            -A to všechno zcela zdarma. To za pokus stojí, ne? +Nette přináší pohodlí a efektivitu do světa webových vývojářů díky inovativním nástrojům a technikám. Jaké jsou klíčové vlastnosti, které činí Nette unikátním a nezbytným prvkem vývojářské sady? Pojďme se na ně podívat! -Jak se Nette Framework liší od jiných frameworků? **Nette Framework je stavěný tak, aby byl co nejpoužitelnější a nejvstřícnější.** Jde o framework, s nímž je nejen snadné, ale i zábavné pracovat. Dává vám srozumitelnou a úspornou syntaxi, vychází vám vstříc při programování a ladění, nechává vás soustředit se na kreativní stránku vývoje a nepřidělává vám vrásky. Eliminuje bezpečnostní rizika. Můžete v něm tvořit e-shopy, wiki, blogy, CMS, co jen vymyslíte. +#1 Nette vás rozmazluje +----------------------- -Nette Framework [používají významné společnosti |https://builtwith.nette.org] jako třeba T-Systems, GE Money, Mladá fronta, VLTAVA-LABE-PRESS, Internet Info, DHL, Logio, ESET, Actum, Slevomat, Socialbakers, SUPRAPHON, pohání i stránky bývalého prezidenta Václava Klause. V anketě serveru [Zdroják |https://www.zdrojak.cz/clanky/vysledky-technologie-na-ceskem-webu/] byl zvolen jako nejpopulárnější a nejpoužívanější framework v České republice. +Když se před dvaceti lety začal rodit framework Nette, vše se točilo kolem jednoho cíle: Jak tvořit weby co nejpohodlněji? Jak co nejvíce zpříjemnit práci programátorům? Jak udělat tvorbu webů sexy? -Když se naučíte Nette Framework, nebudete mít nouzi o zajímavé pracovní nabídky. +Nette tímto pojetím oslovilo mnoho programátorů a rychle získalo popularitu. Tehdy jsme této filosofii říkali Netteway a dnes už pro ni existuje termín, *Developer Experience* (DX). Nette je framework, který má DX ve své DNA. Rozdíl pocítíte v tisíci a jedné věci – od úplných drobností až po zásadní inovace. Nechte se frameworkem rozmazlovat. -Chci začít! ------------ +#2 Nette vás vede k čistému kódu +-------------------------------- -Podle návodu krok za krokem [vytvořte první aplikaci |quickstart:]. Neustále vám bude po ruce kompletní [příručka programátora |@home] a praktická [dokumentace API |https://api.nette.org/] s přehledem tříd a metod. +Chcete psát čistý kód? Mít správně navržené aplikace? Kdo by nechtěl! A právě zde začíná role frameworku. Pokud sám nejde příkladem, nelze vytvořit skvěle navrženou aplikaci. -Když narazíte na nejasnost, můžete ji konzultovat se stránkou [řešící časté dotazy |nette:troubleshooting] nebo na [českém diskusním fóru |https://forum.nette.org/cs/]. +Nette příkladem jde. Je to mentor učící dobré návyky a psaní kódu podle osvědčených metodik. Jako průkopník a evangelizátor dependency injection nabízí kvalitní základ pro udržitelné, rozšiřitelné a snadno čitelné aplikace. Nette je navrženo tak, aby bylo pochopitelné pro začátečníky, zároveň však nabízelo dostatečnou hloubku pro zkušené vývojáře. -Zdá se vám výuka samostudiem jako neefektivní cesta? Rozumíme a nabízíme [školení Nette Framework |https://www.skoleniphp.cz/skoleni-nette-vyvoj-webovych-aplikaci], můžete si přečíst [ohlasy na školení |https://www.skoleniphp.cz/ohlasy]. +Komunita kolem Nette vychovala řadu osobností, které dnes stojí za úspěšnými a důležitými projekty. Pro tisíce programátorů se Nette stalo mentorem na jejich cestě k profesionálnímu růstu. Přidejte se a objevte, jak Nette pozitivně ovlivní kvalitu vašeho kódu a aplikací. -Chci víc informací! -------------------- +#3 Spolehlivý strážce vašich aplikací +------------------------------------- -Máme pro vás připravený [blog plný tipů |https://blog.nette.org], sbírku nejrůznějších [doplňků a komponent |https://componette.org] pro použití ve vašich aplikacích. +Nette chrání vaše aplikace. Během let si získalo pověst nástroje, který to s bezpečností myslí nesmírně vážně. Poskytuje důmyslné zabezpečení před zranitelnostmi. Vždy dbá na to, aby programátorům usnadnil práci, ale nikdy na úkor bezpečí. -Ale to není vše. Komunita kolem Nette Frameworku se každý měsíc schází na [Posledních sobotách |www.posobota.cz], kde si vyměňuje své zkušenosti. Stavte se někdy na pivko! +Jeho motto "méně kódu = dostatek bezpečí" znamená, že jednotlivé elementy se bezpečně chovají již v základu. Není třeba aktivovat bezpečnostní prvky dopisováním dalšího kódu. Nemůžete na to tedy zapomenout, nebo se bát, že jste něco přehlédli. Programátoři často ani netuší, kolik bezpečnostních věcí Nette za ně dělá, a jsou překvapeni, když se o tom dozví. +Představujeme vám framework, který vás vede k čistému kódu a zároveň bdí nad bezpečností vašich aplikací. Nette je spolehlivý partner, který vám umožní soustředit se na vytváření skvělých webových aplikací s klidem v duši. -Chci se zapojit! ----------------- -Budeme moc rádi, pokud přispějete svým dílem k vývoji frameworku. Třeba tím, že framework doporučíte, umístíte si na web [ikonku |www:logo], nebo [podpoříte vývoj finančně |www:donate]. Děkujeme ♥ +#4 Stavte web jako stavebnici +----------------------------- + +V Nette stavíte stránky ze znovupoužitelných UI komponent. Připomíná to vývoj desktopových aplikací, a Nette tento přístup úspěšně přeneslo na web. Potřebujete v administraci datagrid? Stačí si jej najít na tržišti open source komponent, nainstalovat a jednoduše vložit do stránky. Navíc můžete vytvářet vlastní komponenty pro opakující se prvky na stránkách, čímž eliminujete duplicity a zlepšíte organizaci kódu. + +Tato unikátní vlastnost odlišuje Nette od všech ostatních významných hráčů na trhu. Umožní vám efektivně vytvářet a udržovat webové aplikace. S Nette se práce s UI stává hladkou a příjemnou zkušeností. + + +#5 Flexibilní sada balíčků +-------------------------- + +Nette je sada [samostatně použitelných balíčků |www:packages]. Patří mezi ně návykový [ladící nástroj Tracy |tracy:], [šablonovací systém nové generace Latte |latte:], [excelentní Dependency Injection Container |dependency-injection:], [formuláře |forms:] a mnoho dalších. Každý balíček má čtivou detailní dokumentaci a sídlí v samostatném repozitáři na GitHubu. Balíčky můžete používat samostatně nebo kombinovat s jinými nástroji a technologiemi, které už používáte. Třeba Latte lze nasadit do WordPressu či Slim Frameworku, DI kontejner může být jádrem firemního frameworku a Tracy bude vizualizovat chybové zprávy. + +Nebo můžete Nette použít jako celek, jako framework, a vytvořit v něm kompletní webovou aplikaci. Ať už vyvíjíte malý osobní projekt nebo robustní podnikovou aplikaci, Nette bude vaším spolehlivým partnerem. + + +#6 Stabilita & inovace +---------------------- + +Nette je vyzrálý a ověřený framework s dlouholetou historií. Navzdory tomu se udržuje agilní a pružný díky chytrému návrhu. Uživatelé oceňují, že jde o malý a pružný framework, žádný moloch. + +Vždy je s předstihem připraven na nové verze PHP a zohledňuje nejnovější inovace v oblasti vývoje webových aplikací. To je klíčové pro minimalizaci technologického dluhu. + +Nette je tedy kombinací dlouholeté stability a inovace, která vám umožní spolehnout se na ověřené řešení, zatímco budete moci využívat nejnovější technologie a trendy. S Nette máte jistotu, že váš projekt bude postaven na základech, které neustále drží krok s rychle se vyvíjejícím světem webového vývoje. + + +#7 Buďte v nejlepší společnosti +------------------------------- + +Nette Framework je oblíbený mezi profesionály. Spoléhají na něj [významné společnosti |https://builtwith.nette.org] jako O2, BOSCH, ESET, Zásilkovna, DHL, SUPRAPHON a stovky dalších. V několika anketách zvítězil jako nejpopulárnější a nejpoužívanější framework v České republice. + +Začněte s Nette a otevřou se vám nové pracovní příležitosti. Získáte nejen výkonný nástroj, ale také nejaktivnější komunitu v ČR, která je připravena podpořit vás na vaší cestě k profesnímu růstu. Pravidelně se schází na [Posobotách |www.posobota.cz], akcích, kde si vyměňuje zkušenosti. Přijďte se s námi potkat! + + +Objevte Nette +------------- + +Přidejte se k tisícům spokojených vývojářů, kteří již objevili přednosti Nette a začněte psát čistší, bezpečnější a efektivnější kód s tímto jedinečným frameworkem. + +Vytvořte si [podle návodu |quickstart:] první aplikaci, krok za krokem. Neustále vám bude po ruce [rozsáhlá dokumentace |nette:] a praktický [přehled API |https://api.nette.org]. Navštivte náš [blog plný tipů |https://blog.nette.org] a sbírku [doplňků a komponent |https://componette.org] rozšiřující schopnosti Nette. + +Máte otázky? Obraťte se na stránku s [častými dotazy|nette:troubleshooting] nebo diskutujte na [českém fóru|https://forum.nette.org/cs/]. Upřednostňujete osobní školení? Nabízíme [školení Nette Framework |https://www.skoleniphp.cz/skoleni-nette-vyvoj-webovych-aplikaci] s [vynikajícími ohlasy |https://www.skoleniphp.cz/ohlasy]. + +**Seznamte se s frameworkem, který vás bude rozmazlovat, vést a inspirovat.** {{leftbar: @menu-common}} diff --git a/www/cs/@menu-common.texy b/www/cs/@menu-common.texy index 8f30813a24..57bc422743 100644 --- a/www/cs/@menu-common.texy +++ b/www/cs/@menu-common.texy @@ -19,6 +19,4 @@ Obecné témata - [API reference |https://api.nette.org/] - [Návody a postupy |best-practices:] -/--comment - [Bezpečnost především |nette:vulnerability-protection] -\-- diff --git a/www/cs/donate.texy b/www/cs/donate.texy index 948cbc6e2b..124d6be8f1 100644 --- a/www/cs/donate.texy +++ b/www/cs/donate.texy @@ -12,8 +12,7 @@ Pojďte do toho a staňte se partnerem Nette! Zajistíte tak zdravé fungování projektu, na který spoléháte. A zároveň **získáte celou řadu exkluzivních výhod** (viz *Úrovně partnerství* v pravém sloupci). Získáte přístup k bonusovému obsahu. K technické podpoře. Zvýšíte prioritu řešení vašich issues. A také zviditelníte svoji společnost a přilákáte k sobě vývojáře. -Jak vás zviditelníme? Například uvedením vašeho loga na tomto webu (tj. na této stránce, na homepage, v dokumentaci, na fóru, a na [speciální stránce |https://nette.org/partner/vitalita], kterou můžete odkazovat). -Budete mít možnost vkládat [pracovní nabídky |https://forum.nette.org/cs/f30-prace-a-zakazky], inzerovat na fóru ([ukázka |https://forum.nette.org/cs/30798-problem-s-cizim-klicem-pri-mazani#p198093]) nebo v dokumentaci ([ukázka |https://doc.nette.org/cs/application/components#toc-flash-zpravy]), tedy v místech se zcela nejlepším zásahem do skupiny Nette vývojářů. +Jak vás zviditelníme? Například uvedením vašeho loga na tomto webu (tj. na této stránce, na homepage, v dokumentaci, na fóru, a na [speciální stránce |https://nette.org/partner/vitalita], kterou můžete odkazovat). Budete mít možnost vkládat [pracovní nabídky |https://forum.nette.org/cs/f30-prace-a-zakazky], inzerovat na fóru ([ukázka |https://forum.nette.org/cs/30798-problem-s-cizim-klicem-pri-mazani#p198093]) nebo v dokumentaci ([ukázka |https://doc.nette.org/cs/application/components#toc-flash-zpravy]), tedy v místech se zcela nejlepším zásahem do skupiny Nette vývojářů. Partnerům vystavujeme faktury, aby si mohli podporu dát do nákladů, a to buď měsíčně, čtvrtletně, půlročně nebo ročně. diff --git a/www/cs/license.texy b/www/cs/license.texy index 5d1eeb1485..49f5a75f62 100644 --- a/www/cs/license.texy +++ b/www/cs/license.texy @@ -13,8 +13,7 @@ Budete-li s Nette Frameworkem spokojeni, a věříme, že ano, můžete jej [pod New BSD License --------------- -Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com) -All rights reserved. +Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/www/cs/maintenance.texy b/www/cs/maintenance.texy index fac702fbe8..ebc325f01e 100644 --- a/www/cs/maintenance.texy +++ b/www/cs/maintenance.texy @@ -4,8 +4,7 @@ .[perex] Nette je framework s mimořádně dlouhou dobou podpory jednotlivých vydání. Každá větěv je LTS (Long-Term Support Release) s podporou nejméně 2 roky. -Každá verze je aktivně udržována po období jednoho roku (nebo i déle) od počátečního stabilního vydání. -Kritické a bezpečností chyby jsou opravovány po dva roky. +Každá verze je aktivně udržována po období jednoho roku (nebo i déle) od počátečního stabilního vydání. Kritické a bezpečností chyby jsou opravovány po dva roky. Kalendář vydávání Nette diff --git a/www/cs/packages.texy b/www/cs/packages.texy index 96568b8724..22e1d9d7e2 100644 --- a/www/cs/packages.texy +++ b/www/cs/packages.texy @@ -2,6 +2,7 @@ Seznam balíčků Nette ******************** | **Application**:[application:how-it-works] | Jádro webových aplikací | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] +| **Assets**:[assets:] | Správa assetů | [GitHub |https://github.com/nette/assets] [API |https://api.nette.org/assets/] | **Bootstrap**:[bootstrap:] | Bootstrap webové aplikace | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] | **Caching**:[caching:] | Kešovací vrstva s úložišti | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] | **Component Model**:[component-model:] | Základ komponentového systému | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] diff --git a/www/de/10-reasons-why-nette.texy b/www/de/10-reasons-why-nette.texy new file mode 100644 index 0000000000..87f2b87b36 --- /dev/null +++ b/www/de/10-reasons-why-nette.texy @@ -0,0 +1,93 @@ +7 Gründe, Nette zu verwenden +**************************** + +
                                                                                                                            + +Stellen Sie sich ein PHP-Framework vor, das es Ihnen ermöglicht, sich auf das zu konzentrieren, was Sie am liebsten tun. Es führt Sie dazu, sauberen Code zu schreiben. Es achtet selbst auf Sicherheit. Hören Sie auf zu träumen und lernen Sie Nette kennen. Begeben Sie sich auf eine Reise, die Ihnen Türen zu neuen Entwicklungsmöglichkeiten öffnet. Wir werden besprechen: + +- wie man Websites mit maximalem Komfort erstellt +- wie man eleganten Code schreibt +- was die Weisheit „weniger Code = mehr Sicherheit“ bedeutet +- wie man eine Website wie einen Baukasten baut +- und wie man Teil einer erfolgreichen Community wird + +
                                                                                                                            + +Nette bringt Komfort und Effizienz in die Welt der Webentwickler durch innovative Werkzeuge und Techniken. Was sind die Schlüsseleigenschaften, die Nette einzigartig und zu einem unverzichtbaren Bestandteil des Entwickler-Toolkits machen? Schauen wir sie uns an! + + +#1 Nette verwöhnt Sie +--------------------- + +Als das Nette Framework vor zwanzig Jahren entstand, drehte sich alles um ein Ziel: Wie erstellt man Websites so komfortabel wie möglich? Wie macht man die Arbeit für Programmierer so angenehm wie möglich? Wie macht man die Website-Erstellung sexy? + +Mit diesem Konzept sprach Nette viele Programmierer an und gewann schnell an Popularität. Damals nannten wir diese Philosophie Netteway, heute gibt es dafür den Begriff *Developer Experience* (DX). Nette ist ein Framework, das DX in seiner DNA hat. Den Unterschied spüren Sie in tausendundeiner Sache – von kleinen Details bis hin zu grundlegenden Innovationen. Lassen Sie sich vom Framework verwöhnen. + + +#2 Nette führt Sie zu sauberem Code +----------------------------------- + +Möchten Sie sauberen Code schreiben? Gut gestaltete Anwendungen haben? Wer möchte das nicht! Und genau hier beginnt die Rolle des Frameworks. Wenn es selbst kein Beispiel gibt, kann keine hervorragend gestaltete Anwendung erstellt werden. + +Nette geht mit gutem Beispiel voran. Es ist ein Mentor, der gute Gewohnheiten und das Schreiben von Code nach bewährten Methoden lehrt. Als Pionier und Evangelist der Dependency Injection bietet es eine solide Grundlage für nachhaltige, erweiterbare und leicht lesbare Anwendungen. Nette ist so konzipiert, dass es für Anfänger verständlich ist, aber auch genügend Tiefe für erfahrene Entwickler bietet. + +Die Community um Nette hat eine Reihe von Persönlichkeiten hervorgebracht, die heute hinter erfolgreichen und wichtigen Projekten stehen. Für Tausende von Programmierern wurde Nette zu einem Mentor auf ihrem Weg zum beruflichen Wachstum. Schließen Sie sich an und entdecken Sie, wie Nette die Qualität Ihres Codes und Ihrer Anwendungen positiv beeinflussen wird. + + +#3 Zuverlässiger Wächter Ihrer Anwendungen +------------------------------------------ + +Nette schützt Ihre Anwendungen. Im Laufe der Jahre hat es sich den Ruf eines Werkzeugs erworben, das Sicherheit äußerst ernst nimmt. Es bietet ausgeklügelten Schutz vor Schwachstellen. Es achtet immer darauf, Programmierern die Arbeit zu erleichtern, aber niemals auf Kosten der Sicherheit. + +Sein Motto "weniger Code = ausreichend Sicherheit" bedeutet, dass sich die einzelnen Elemente bereits standardmäßig sicher verhalten. Es ist nicht nötig, Sicherheitsfunktionen durch Hinzufügen von weiterem Code zu aktivieren. Sie können es also nicht vergessen oder befürchten, etwas übersehen zu haben. Programmierer ahnen oft gar nicht, wie viele Sicherheitsaufgaben Nette für sie erledigt, und sind überrascht, wenn sie davon erfahren. + +Wir präsentieren Ihnen ein Framework, das Sie zu sauberem Code führt und gleichzeitig über die Sicherheit Ihrer Anwendungen wacht. Nette ist ein zuverlässiger Partner, der es Ihnen ermöglicht, sich beruhigt auf die Erstellung großartiger Webanwendungen zu konzentrieren. + + +#4 Bauen Sie Ihre Website wie einen Baukasten +--------------------------------------------- + +In Nette bauen Sie Seiten aus wiederverwendbaren UI-Komponenten. Das erinnert an die Entwicklung von Desktop-Anwendungen, und Nette hat diesen Ansatz erfolgreich ins Web übertragen. Benötigen Sie ein Datagrid in der Administration? Suchen Sie es einfach auf dem Marktplatz für Open-Source-Komponenten, installieren Sie es und fügen Sie es einfach in die Seite ein. Darüber hinaus können Sie eigene Komponenten für wiederkehrende Elemente auf Seiten erstellen, wodurch Duplikate vermieden und die Codeorganisation verbessert wird. + +Diese einzigartige Eigenschaft unterscheidet Nette von allen anderen wichtigen Akteuren auf dem Markt. Sie ermöglicht es Ihnen, Webanwendungen effizient zu erstellen und zu warten. Mit Nette wird die Arbeit mit der UI zu einer reibungslosen und angenehmen Erfahrung. + + +#5 Flexibles Paketset +--------------------- + +Nette ist ein Satz [eigenständig verwendbarer Pakete |www:packages]. Dazu gehören das süchtig machende [Debugging-Werkzeug Tracy |tracy:], das [Template-System der nächsten Generation Latte |latte:], der [exzellente Dependency Injection Container |dependency-injection:], [Formulare |forms:] und viele andere. Jedes Paket hat eine lesbare, detaillierte Dokumentation und befindet sich in einem separaten Repository auf GitHub. Sie können die Pakete einzeln verwenden oder mit anderen Werkzeugen und Technologien kombinieren, die Sie bereits nutzen. Zum Beispiel kann Latte in WordPress oder dem Slim Framework eingesetzt werden, der DI-Container kann der Kern eines Firmenframeworks sein und Tracy visualisiert Fehlermeldungen. + +Oder Sie können Nette als Ganzes, als Framework, verwenden und damit eine komplette Webanwendung erstellen. Egal, ob Sie ein kleines persönliches Projekt oder eine robuste Unternehmensanwendung entwickeln, Nette wird Ihr zuverlässiger Partner sein. + + +#6 Stabilität & Innovation +-------------------------- + +Nette ist ein ausgereiftes und bewährtes Framework mit einer langen Geschichte. Trotzdem bleibt es dank seines cleveren Designs agil und flexibel. Benutzer schätzen, dass es sich um ein kleines und flexibles Framework handelt, keinen Moloch. + +Es ist immer im Voraus auf neue PHP-Versionen vorbereitet und berücksichtigt die neuesten Innovationen im Bereich der Webanwendungsentwicklung. Dies ist entscheidend für die Minimierung der technologischen Schuld. + +Nette ist also eine Kombination aus langjähriger Stabilität und Innovation, die es Ihnen ermöglicht, sich auf eine bewährte Lösung zu verlassen, während Sie die neuesten Technologien und Trends nutzen können. Mit Nette haben Sie die Gewissheit, dass Ihr Projekt auf einem Fundament aufgebaut wird, das ständig mit der sich schnell entwickelnden Welt der Webentwicklung Schritt hält. + + +#7 Seien Sie in bester Gesellschaft +----------------------------------- + +Das Nette Framework ist bei Profis beliebt. [Bedeutende Unternehmen |https://builtwith.nette.org] wie O2, BOSCH, ESET, Zásilkovna, DHL, SUPRAPHON und Hunderte weitere verlassen sich darauf. In mehreren Umfragen wurde es als das beliebteste und meistgenutzte Framework in der Tschechischen Republik ausgezeichnet. + +Beginnen Sie mit Nette und es eröffnen sich Ihnen neue Arbeitsmöglichkeiten. Sie erhalten nicht nur ein leistungsstarkes Werkzeug, sondern auch die aktivste Community in Tschechien, die bereit ist, Sie auf Ihrem Weg zum beruflichen Wachstum zu unterstützen. Sie trifft sich regelmäßig bei den [Posobotas |www.posobota.cz], Veranstaltungen, bei denen Erfahrungen ausgetauscht werden. Kommen Sie und treffen Sie uns! + + +Entdecken Sie Nette +------------------- + +Schließen Sie sich Tausenden zufriedener Entwickler an, die bereits die Vorteile von Nette entdeckt haben, und beginnen Sie, mit diesem einzigartigen Framework saubereren, sichereren und effizienteren Code zu schreiben. + +Erstellen Sie Ihre erste Anwendung [Schritt für Schritt nach Anleitung |quickstart:]. Ihnen stehen ständig eine [umfangreiche Dokumentation |nette:] und eine praktische [API-Übersicht |https://api.nette.org] zur Verfügung. Besuchen Sie unseren [Blog voller Tipps |https://blog.nette.org] und die Sammlung von [Add-ons und Komponenten |https://componette.org], die die Fähigkeiten von Nette erweitern. + +Haben Sie Fragen? Wenden Sie sich an die Seite mit [häufig gestellten Fragen|nette:troubleshooting] oder diskutieren Sie im [tschechischen Forum|https://forum.nette.org/cs/]. Bevorzugen Sie eine persönliche Schulung? Wir bieten [Nette Framework Schulungen |https://www.skoleniphp.cz/skoleni-nette-vyvoj-webovych-aplikaci] mit [hervorragenden Bewertungen |https://www.skoleniphp.cz/ohlasy] an. + +**Lernen Sie das Framework kennen, das Sie verwöhnen, führen und inspirieren wird.** + + +{{leftbar: @menu-common}} diff --git a/www/de/@home.texy b/www/de/@home.texy index f4dc7131e2..ec9633a4b4 100644 --- a/www/de/@home.texy +++ b/www/de/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Nette - Komfortable und sichere Webentwicklung in PHP}} -{{description: Nette ist eine Familie ausgereifter und eigenständiger Komponenten für PHP. Sind Sie bereit, sich zu verlieben? Gemeinsam bilden sie ein Framework, das als das drittbeliebteste der Welt eingestuft wurde. Unsere Philosophie ist es, uns auf Produktivität, Best Practices und Sicherheit zu konzentrieren.}} +{{maintitle:Nette – Komfortable und sichere Entwicklung von Webanwendungen in PHP}} +{{description: Nette ist eine Familie ausgereifter und eigenständig verwendbarer Komponenten für PHP. Lassen Sie sich davon begeistern. Zusammen bilden sie ein Framework, das als das drittbeliebteste der Welt bewertet wurde. Die Philosophie von Nette legt besonderen Wert auf Produktivität, Best Practices und Sicherheit.}} diff --git a/www/de/@menu-common.texy b/www/de/@menu-common.texy index 0572b7962a..50f7f5e016 100644 --- a/www/de/@menu-common.texy +++ b/www/de/@menu-common.texy @@ -1,20 +1,22 @@ -Einführung -********** +Kennenlernen +************ - [Warum Nette verwenden? |www:10-reasons-why-nette] -- [Die Installation |nette:installation] -- [Erstellen Sie Ihre erste Anwendung! |quickstart:] +- [Installation |nette:installation] +- [Schreiben wir die erste Anwendung! |quickstart:] Allgemeine Themen ***************** -- [Liste der Pakete |www:packages] -- [Wartung und PHP |www:maintenance] -- [Hinweise zum Release |https://nette.org/releases] -- [Upgrade-Leitfaden |migrations:en] -- [Fehlersuche |nette:Troubleshooting] +- [Paketliste |www:packages] +- [Wartung und PHP-Versionen |www:maintenance] +- [Release Notes |https://nette.org/releases] +- [Übergang zu neueren Versionen |migrations:en] +- [Fehlerbehebung |nette:troubleshooting] - [Wer erstellt Nette |https://nette.org/contributors] - [Geschichte von Nette |history] -- [Beteiligen Sie sich |contributing:] -- [Entwicklung des Sponsors |https://nette.org/en/donate] -- [API-Referenz |https://api.nette.org] -- [Bewährte Praktiken |best-practices:] +- [Machen Sie mit |contributing:] +- [Unterstützen Sie die Entwicklung |https://nette.org/cs/donate] +- [API-Referenz |https://api.nette.org/] +- [Anleitungen und Verfahren |best-practices:] + +- [Sicherheit zuerst |nette:vulnerability-protection] diff --git a/www/de/donate.texy b/www/de/donate.texy new file mode 100644 index 0000000000..9a6493192b --- /dev/null +++ b/www/de/donate.texy @@ -0,0 +1,20 @@ +Unterstützen Sie die Entwicklung von Nette +****************************************** + +
                                                                                                                            +Jeder, der auf Nette aufbaut, hat ein Interesse daran, dass das Framework aktiv weiterentwickelt wird. Damit es neue PHP-Versionen unterstützt. Damit Fehler behoben werden. Damit es weitere Neuerungen bringt, die die Arbeit erleichtern oder Zeit und Geld sparen. Damit das Framework eine hervorragende Dokumentation hat und nützliche Inhalte darum herum existieren, sei es in Form von Artikeln, Anleitungen oder Videos. + +Viele Teile von Nette gehören zur Weltspitze, und wir möchten, dass dies auch so bleibt. + +Ohne angemessene Finanzierung kann nichts davon sichergestellt werden. Dabei braucht es nur sehr wenig, damit Sie sich darauf verlassen können, dass weitere Versionen erscheinen: dass Sie es jeden Monat auch nur mit einem kleinen finanziellen Betrag unterstützen. + +Machen Sie mit und werden Sie Partner von Nette! + +So sichern Sie das gesunde Funktionieren des Projekts, auf das Sie sich verlassen. Und gleichzeitig **erhalten Sie eine ganze Reihe exklusiver Vorteile** (siehe *Partnerschaftsstufen* in der rechten Spalte). Sie erhalten Zugang zu Bonusinhalten. Zu technischem Support. Sie erhöhen die Priorität bei der Bearbeitung Ihrer Issues. Und Sie machen Ihr Unternehmen sichtbarer und ziehen Entwickler an. + +Wie machen wir Sie sichtbar? Zum Beispiel durch die Anzeige Ihres Logos auf dieser Website (d.h. auf dieser Seite, auf der Homepage, in der Dokumentation, im Forum und auf einer [speziellen Seite |https://nette.org/partner/vitalita], auf die Sie verlinken können). Sie haben die Möglichkeit, [Stellenangebote |https://forum.nette.org/cs/f30-prace-a-zakazky] zu veröffentlichen, im Forum ([Beispiel |https://forum.nette.org/cs/30798-problem-s-cizim-klicem-pri-mazani#p198093]) oder in der Dokumentation ([Beispiel |https://doc.nette.org/cs/application/components#toc-flash-zpravy]) zu werben, also an Orten mit der absolut besten Reichweite innerhalb der Nette-Entwicklergruppe. + +Wir stellen Partnern Rechnungen aus, damit sie die Unterstützung als Betriebsausgabe absetzen können, entweder monatlich, vierteljährlich, halbjährlich oder jährlich. + +{{include: buttons}} +
                                                                                                                            diff --git a/www/de/history.texy b/www/de/history.texy index 2557936400..3d49386219 100644 --- a/www/de/history.texy +++ b/www/de/history.texy @@ -2,35 +2,34 @@ Geschichte von Nette ******************** .[perex] -Die Ursprünge von Nette gehen auf das Jahr 2004 zurück, als der Autor David Grudl begann, nach einem geeigneten Framework zu suchen, um Anwendungen zu schreiben, da reines PHP nicht mehr ausreichte. Keine der damals verfügbaren Lösungen passte zu ihm, also begann er nach und nach, die Merkmale eines neuen Frameworks zu skizzieren, das später den Namen Nette erhielt. +Die Entstehung von Nette reicht bis ins Jahr 2004 zurück, als sein Autor David Grudl nach einem geeigneten Framework suchte, in dem er Anwendungen schreiben konnte, da reines PHP dafür nicht mehr ausreichte. Keine der damals verfügbaren Lösungen passte ihm, also begann er nach und nach, die Konturen eines neuen Frameworks zu skizzieren, das später den Namen Nette erhielt. -Zu dieser Zeit gab es aktuelle Frameworks wie Symfony, Laravel oder Ruby on Rails noch nicht. In der Java-Welt war JSF (JavaServer Faces) der Standard, und im konkurrierenden .NET-Bereich war ASP.NET Webforms das dominierende Framework. Beide ermöglichten die Erstellung von Seiten mit wiederverwendbaren UI-Komponenten. David hielt ihre Abstraktionsmethoden und ihre Versuche, über das zustandslose HTTP-Protokoll mit Hilfe von Sessionen oder Postbacks Zustandslosigkeit zu schaffen, für fehlerhaft und grundlegend gescheitert. Sie verursachten viele Schwierigkeiten für Benutzer und Suchmaschinen. Wenn man zum Beispiel einen Link speicherte, war man überrascht, wenn man später andere Inhalte darunter fand. +Zu dieser Zeit existierten die heutigen Frameworks wie Symfony, Laravel oder Ruby on Rails noch nicht. In der Java-Welt war das Framework JSF (JavaServer Faces) Standard, im konkurrierenden .NET wiederum ASP.NET Webforms. Beide ermöglichten den Aufbau von Seiten mithilfe wiederverwendbarer UI-Komponenten. Ihre Abstraktionsmethoden und Versuche, über das zustandslose HTTP-Protokoll mithilfe von Sessions oder sogenannten Postbacks einen Zustand zu schaffen, hielt David für fehlerhaft und von Grund auf nicht funktionsfähig. Sie verursachten eine Reihe von Schwierigkeiten für Benutzer und Suchmaschinen. Wenn Sie beispielsweise einen Link speicherten, fanden Sie später überraschenderweise anderen Inhalt darunter. -Die Möglichkeit, Seiten aus wiederverwendbaren UI-Komponenten zusammenzustellen, faszinierte David, der sie von Delphi her kannte, einem damals beliebten Tool zur Erstellung von Desktop-Anwendungen. Ihm gefielen die Marktplätze mit Open-Source-Komponenten für Delphi. Also versuchte er, die Frage zu lösen, wie man ein Komponenten-Framework erstellen könnte, das wiederum in völliger Harmonie mit zustandslosem HTTP funktionieren würde. Er suchte nach einem Konzept, das benutzer-, SEO- und entwicklerfreundlich sein sollte. Und so wurde Nette geboren. +Die Möglichkeit selbst, Seiten aus wiederverwendbaren UI-Komponenten zusammenzusetzen, faszinierte David; er kannte sie gut aus Delphi, einem damals beliebten Werkzeug zur Erstellung von Desktop-Anwendungen. Ihm gefielen die Marktplätze mit Open-Source-Komponenten für Delphi. Er versuchte daher, die Frage zu lösen, wie man ein Komponenten-Framework schaffen könnte, das im Gegensatz dazu in völliger Übereinstimmung mit dem zustandslosen HTTP funktionieren würde. Er suchte nach einem Konzept, das benutzer-, SEO- und entwicklerfreundlich wäre. Und so begann Nette zu entstehen. .[note] -Der Name Nette entstand zufällig im Badezimmer, als der Autor eine Flasche Gillette-Rasiergel entdeckte, die so gedreht war, dass nur die *llette* zu sehen war. +Der Name Nette entstand zufällig im Badezimmer, als der Autor einen Behälter mit Gillette-Rasiergel sah, der so gedreht war, dass nur *llette* zu sehen war. -Es folgten Tausende von Stunden der Recherche, des Nachdenkens und des Umschreibens. In einer staubigen Garage in einem Dorf irgendwo außerhalb von Brünn entstanden die ersten Umrisse des zukünftigen Rahmens. Grundlage der Architektur war das MVC-Pattern, das damals von dem heute vergessenen PHP-Framework Mojavi verwendet und später durch den Hype um Ruby on Rails populär gemacht wurde. Eine der Inspirationsquellen war sogar das nie veröffentlichte phpBase-Framework von Honza Tichý. +Es folgten Tausende Stunden des Forschens, Nachdenkens und Umschreibens. In einer staubigen Garage in einem Dorf irgendwo hinter Brünn entstanden die ersten Umrisse des zukünftigen Frameworks. Die Grundlage der Architektur bildete das MVC-Muster, das damals das heute vergessene PHP-Framework Mojavi verwendete und später durch den Hype um Ruby on Rails populär wurde. Eine der Inspirationsquellen war sogar das nie veröffentlichte Framework phpBase von Honza Tichý. -Im Blog des Autors tauchten Artikel über das kommende Nette auf. Es wurde gescherzt, dass es sich um Vaporware handele. Doch dann, im Oktober 2007, stellte David Nette auf der Prager PHP-Seminar-Konferenz öffentlich vor. Aus dieser Konferenz entwickelte sich übrigens ein Jahr später die WebExpo, später eine der größten IT-Konferenzen in Europa. Schon damals präsentierte Nette stolz eine Reihe origineller Konzepte, wie das bereits erwähnte Komponentenmodell, den bidirektionalen Router, die spezielle Art der Verknüpfung zwischen den Moderatoren usw. Es gab Formulare, Authentifizierung, Caching usw. Alles wird in Nette bis heute in seinem ursprünglichen Konzept verwendet. +Auf dem Blog des Autors erschienen erste Artikel über das kommende Nette. Es wurde gescherzt, dass es sich um Vaporware handele. Aber dann, im Oktober 2007, stellte David Nette auf der Prager Konferenz PHP Seminář öffentlich vor. Übrigens entwickelte sich aus dieser Konferenz ein Jahr später das WebExpo, später eine der größten IT-Konferenzen Europas. Schon damals konnte Nette mit einer Reihe origineller Konzepte aufwarten, wie dem erwähnten Komponentenmodell, einem bidirektionalen Router, einer spezifischen Art der Verlinkung zwischen Presentern usw. Es hatte Formulare, gelöste Authentifizierung, Caching usw. Alles wird in Nette bis heute im ursprünglichen Konzept verwendet. .[note] -Nette verwendet *Presenter* anstelle von *Controller*, weil es angeblich zu viele Wörter, die mit *con* beginnen, im Code gab (Controller, Front Controller, Control, Config, Container, ...). +In Nette wird anstelle des Begriffs *controller* der Begriff *presenter* verwendet, da es im Code angeblich zu viele Wörter gab, die mit *con* begannen (controller, front controller, control, config, container, ...) -Ende 2007 veröffentlichte David Grudl den Code und Nette 0.7 wurde freigegeben. Es bildete sich eine enthusiastische Gemeinschaft von Programmierern, die sich jeden Monat auf dem Posobota-Event trafen. Zu dieser Gemeinschaft gehörten viele der heutigen Koryphäen, wie zum Beispiel Ondrej Mirtes, der Autor des großartigen PHPStan-Tools. Die Entwicklung von Nette schritt voran, und in den nächsten zwei Jahren wurden die Versionen 0.8 und 0.9 veröffentlicht, die den Grundstein für fast alle heutigen Teile des Frameworks legten. Dazu gehören auch AJAX-Snippets, die Hotwire für Ruby on Rails oder Symfony UX Turbo um 14 Jahre vorausgehen. +Ende 2007 veröffentlichte David Grudl auch den Code, und so erblickte die Version Nette 0.7 das Licht der Welt. Das Framework zog sofort immense Aufmerksamkeit auf sich. Um es herum bildete sich eine begeisterte Gemeinschaft von Programmierern, die sich jeden Monat bei der Veranstaltung Posobota trafen. In der Community gab es eine Reihe heutiger Persönlichkeiten, zum Beispiel Ondřej Mirtes, den Autor des großartigen Werkzeugs PHPStan. Die Entwicklung von Nette schritt schnell voran, und in den nächsten zwei Jahren erschienen die Versionen 0.8 und 0.9, in denen die Grundlagen für fast alle heutigen Teile des Frameworks gelegt wurden. Einschließlich AJAX-Snippets, die Hotwire für Ruby on Rails oder Symfony UX Turbo um 14 Jahre voraus waren. -Aber eine entscheidende Sache fehlte damals in Nette. Dependecy Injection Container (DIC). Nette benutzte einen *Service Locator* und die Absicht war, zu Dependecy Injection überzugehen. Aber wie sollte man so etwas entwerfen? David Grudl, der zu diesem Zeitpunkt noch keine Erfahrung mit DI hatte, ging zum Mittagessen mit Vasek Purchart, der DI seit etwa einem halben Jahr einsetzte. Gemeinsam diskutierten sie das Thema, und David begann mit der Arbeit an Nette DI, einer Bibliothek, die die Art und Weise, wie wir über Anwendungsdesign nachdenken, völlig revolutionierte. Der DI-Container wurde zu einem der erfolgreichsten Teile des Frameworks. Daraus entstanden zwei Spin-offs: das Neon-Format und die Schema-Bibliothek. +Eine wesentliche Sache fehlte jedoch im damaligen Nette. Der Dependency Injection Container (DIC). Nette verwendete einen sogenannten *Service Locator*, und die Absicht war, genau auf Dependency Injection umzusteigen. Aber wie entwirft man so etwas? David Grudl, der damals keine Erfahrung mit DI hatte, ging mit Vašek Purchart zum Mittagessen, der DI seit etwa einem halben Jahr verwendete. Sie diskutierten das Thema gemeinsam, und David begann mit der Arbeit an Nette DI, einer Bibliothek, die die Art und Weise, wie über Anwendungsdesign nachgedacht wird, völlig revolutionierte. Der DI-Container wurde zu einem der gelungensten Teile des Frameworks. Und brachte später auch zwei Spin-offs hervor: das Neon-Format und die Schema-Bibliothek. .[note] -Die Umstellung auf Dependency Injection hat viel Zeit in Anspruch genommen, und wir haben ein paar Jahre auf eine neue Version von Nette gewartet. Als sie schließlich herauskam, trug sie die Nummer 2. Nette Version 1 gibt es also nicht mehr. +Der Übergang zur Dependency Injection erforderte viel Zeit, und auf die neue Version von Nette musste man einige Jahre warten. Als sie endlich erschien, trug sie daher direkt die Versionsnummer 2. Die Version Nette 1 existiert also nicht. -Nette begann seine moderne Geschichte im Jahr 2012 mit der Version 2.0. Sie brachte auch Nette Database mit sich, die ein äußerst praktisches Datenbank-Tool enthielt, das jetzt Explorer heißt. Diese Bibliothek wurde ursprünglich von Jakub Vrána programmiert, dem Nachbarn von David Grudl und Autor des beliebten Adminer-Tools. Die weitere Entwicklung wurde dann von Jan Škrášek für drei Jahre übernommen. +Mit der Version 2.0 startete Nette 2012 seine moderne Geschichte. Es brachte auch Nette Database mit, dessen Bestandteil auch ein außerordentlich praktisches Werkzeug für die Arbeit mit Datenbanken war, heute Explorer genannt. Diese Bibliothek wurde ursprünglich von Jakub Vrána programmiert, einem Nachbarn von David Grudl und Autor des beliebten Werkzeugs Adminer. Ihre Weiterentwicklung übernahm anschließend für drei Jahre Jan Škrášek. -Im Jahr 2014 wurde Nette 2.1 veröffentlicht, kurz darauf folgte Nette 2.2. Wie ist das möglich? Die Version 2.2 war die gleiche wie die Version 2.1, nur aufgeteilt in zwanzig separate Pakete. Das Composer-Tool setzte sich in der PHP-Welt durch und veränderte die Art und Weise, wie wir über die Erstellung von Bibliotheken denken. Nette hörte auf, ein Monolith zu sein und zerfiel in kleinere unabhängige Teile. Jeder Teil hat sein eigenes Repository, seinen eigenen Issue Tracker und seinen eigenen Entwicklungs- und Versionsfluss. Auf diese Weise muss Nette nicht die Absurditäten durchmachen, die bei monolithischen Frameworks üblich sind, wo eine neue Version eines Pakets herauskommt, obwohl sich nichts geändert hat. Die eigentliche Aufteilung der Git-Repositories erforderte mehrere Wochen Vorbereitung und Hunderte von Stunden an Arbeitszeit. - -Nette belegte außerdem einen erstaunlichen 3. Platz in der weltweiten Umfrage des Magazins Sitepoint für das beste PHP-Framework. +2014 erschien Nette 2.1, kurz darauf folgte Nette 2.2. Wie ist das möglich? Version 2.2 war identisch mit Version 2.1, nur aufgeteilt in zwanzig separate Pakete. In der PHP-Welt etablierte sich das Werkzeug Composer und veränderte die Art und Weise, wie die Erstellung von Bibliotheken angegangen wurde. Nette hörte somit auf, ein Monolith zu sein, und zerfiel in kleinere, unabhängige Teile. Jeder mit eigenem Repository, Issue Tracker und eigenem Entwicklungstempo und Versionierung. So muss es in Nette nicht zu Absurditäten kommen, die in monolithischen Frameworks üblich sind, wenn eine neue Version eines Pakets erscheint, obwohl sich darin überhaupt nichts geändert hat. Die eigentliche Aufteilung der Git-Repositories erforderte mehrere Wochen Vorbereitung und Hunderte von Stunden Maschinenzeit. +Nette erreichte außerdem einen erstaunlichen 3. Platz in der weltweiten Umfrage zum besten PHP-Framework, die vom Magazin Sitepoint durchgeführt wurde. {{toc:no}} diff --git a/www/de/license.texy b/www/de/license.texy new file mode 100644 index 0000000000..63ec401b73 --- /dev/null +++ b/www/de/license.texy @@ -0,0 +1,44 @@ +Lizenzpolitik +************* + +Das Nette Framework wird als freie Software verbreitet, damit jeder es nutzen kann. Sie können wählen, ob Ihnen die [New BSD Lizenz |#New BSD License] oder die [#GNU General Public License (GPL)] in Version 2 oder 3 besser passt. + +Die BSD-Lizenz wird für die meisten Projekte empfohlen, da sie leicht zu verstehen ist und fast keine Einschränkungen hinsichtlich dessen auferlegt, was Sie mit dem Framework tun können. Sie können Nette auch in kommerziellen Projekten verwenden. Wenn jedoch die GPL besser zu Ihrem Projekt passt, wählen Sie diese. Dabei ist es nicht notwendig, jemanden darüber zu informieren, wie Sie sich entschieden haben. Behalten Sie immer die ursprünglichen Copyrights bei. + +Sie sollten wissen, dass der Name "Nette Framework" eine geschützte Marke ist und seine Verwendung bestimmten Einschränkungen unterliegt. Verwenden Sie daher "Nette" nicht im Namen Ihres Projekts oder Ihrer Top-Level-Domain, wählen Sie lieber einen Namen, der auf eigenen Füßen steht. Wenn Ihr Projekt gut ist, wird es nicht lange dauern, bis es sich einen eigenen Ruf aufgebaut hat. + +Wenn Sie mit dem Nette Framework zufrieden sind, und wir glauben, dass Sie es sein werden, können Sie es [mit einem Beitrag unterstützen |donate]. Vielen Dank. + + +New BSD License +--------------- + +Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com) All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of "Nette Framework" nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +GNU General Public License (GPL) +-------------------------------- + +Die Texte der GPL-Lizenzen sind sehr lang, daher sind hier Links dazu aufgeführt: + +- GPL version 2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +- GPL version 3: https://www.gnu.org/licenses/gpl-3.0.html + + +{{toc:yes}} +{{priority: -2}} diff --git a/www/de/maintenance.texy b/www/de/maintenance.texy index 718eff71a5..a1f6fe72fe 100644 --- a/www/de/maintenance.texy +++ b/www/de/maintenance.texy @@ -1,27 +1,27 @@ -Wartung und PHP-Versionen -************************* +Wartung und PHP-Kompatibilität +****************************** .[perex] -Nette ist ein Framework mit einem außergewöhnlich langen Supportzeitraum für einzelne Releases. Jeder Zweig ist ein LTS (Long-Term Support Release) mit einem Supportzeitraum von mindestens 2 Jahren. +Nette ist ein Framework mit einer außergewöhnlich langen Supportdauer für einzelne Releases. Jeder Zweig ist ein LTS (Long-Term Support Release) mit mindestens 2 Jahren Support. -Jede Version wird für einen Zeitraum von einem Jahr (oder länger) ab der ersten stabilen Version aktiv gepflegt. -Kritische und Sicherheitsprobleme werden zwei Jahre lang behoben. +Jede Version wird für einen Zeitraum von einem Jahr (oder länger) ab dem ersten stabilen Release aktiv gewartet. Kritische Fehler und Sicherheitslücken werden zwei Jahre lang behoben. -Veröffentlichungskalender (Roadmap) .[#toc-release-calendar-roadmap] -==================================================================== +Nette Release-Kalender +====================== {{include: doc-roadmap-table}} -PHP-Kompatibilität .[#toc-php-compatibility] -============================================ +PHP-Kompatibilität +================== + +Die Kompatibilität gilt immer für das neueste Release jeder Serie. -Die Kompatibilität bezieht sich immer auf die neueste Version der jeweiligen Serie. {{include: doc-roadmap-versions}} {{leftbar: @menu-common}} {{toc: no}} -{{description: Nette Releases, Roadmap, Wartung und PHP-Kompatibilitätstabellen}} +{{description: Nette-Veröffentlichungen, Roadmap, Wartungstabellen und PHP-Kompatibilität}} diff --git a/www/de/packages.texy b/www/de/packages.texy index 8f464f2c70..1eabb71e2e 100644 --- a/www/de/packages.texy +++ b/www/de/packages.texy @@ -1,26 +1,27 @@ -Liste der Pakete -**************** +Liste der Nette-Pakete +********************** -| **Application**:[application:how-it-works] | Der Kern der Webanwendung | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] -| **Bootstrap**:[bootstrap:] | Bootstrap Ihrer Anwendung | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] -| **Caching**:[caching:] | Cache-Schicht mit einer Reihe von Speicherplätzen | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] -| **Component Model**:[component-model:] | Grundlage für Komponentensysteme | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] -| **DI**:[dependency-injection:] | Dependency Injection Container | [GitHub |https://github.com/nette/di] [API |https://api.nette.org/di/] -| **Database**:[database:] | Datenbankschicht | [GitHub |https://github.com/nette/database] [API |https://api.nette.org/database/] -| **Forms**:[forms:] | Erleichtert sichere Webformulare | [GitHub |https://github.com/nette/forms] [API |https://api.nette.org/forms/] -| **Http**:[http:] | Schicht für die HTTP-Anfrage und -Antwort | [GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] -| **Latte**:[latte:] | Erstaunliche Template-Engine | [GitHub |https://github.com/nette/latte] [API |https://api.nette.org/latte/] -| **Mail**:[mail:] | Senden von E-Mails | [GitHub |https://github.com/nette/mail] [API |https://api.nette.org/mail/] -| **Neon**:[neon:] | Lädt und speichert das [NEON format|https://ne-on.org] | [GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] -| **Php Generator**:[php-generator:] | PHP-Code-Generator | [GitHub |https://github.com/nette/php-generator] [API |https://api.nette.org/php-generator/] -| **Robot Loader**:[robot-loader:] | Das komfortabelste Autoloading | [GitHub |https://github.com/nette/robot-loader] [API |https://api.nette.org/robot-loader/] -| **Routing**:[application:routing] | Routing | [GitHub |https://github.com/nette/routing] [API |https://api.nette.org/routing/] -| **Safe Stream**:[safe-stream:] | Sichere atomare Operationen mit Dateien | [GitHub |https://github.com/nette/safe-stream] [API |https://api.nette.org/safe-stream/] -| **Security**:[security:authentication] | Bietet Zugriffskontrollsystem | [GitHub |https://github.com/nette/security] [API |https://api.nette.org/security/] -| **Schema**:[schema:] | Validierung von Benutzerdaten | [GitHub |https://github.com/nette/schema] [API |https://api.nette.org/schema/] -| **Tester**:[tester:] | Angenehme Unit-Tests in PHP | [GitHub |https://github.com/nette/tester] [API |https://api.nette.org/tester/] -| **Tracy**:[tracy:] | Debugging-Tool, das Sie lieben werden ♥ | [GitHub |https://github.com/nette/tracy] [API |https://api.nette.org/tracy/] -| **Utils**:[utils:] | Dienstprogramme und Kernklassen | [GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] +| **Application**:[application:how-it-works] | Kern der Webanwendungen | GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] +| **Assets**:[assets:] | Statische Dateiverwaltung | [GitHub |https://github.com/nette/assets] [API |https://api.nette.org/assets/] +| **Bootstrap**:[bootstrap:] | Bootstrap der Webanwendung | GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] +| **Caching**:[caching:] | Caching-Schicht mit Speichern | GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] +| **Component Model**:[component-model:] | Grundlage des Komponentensystems | GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] +| **DI**:[dependency-injection:] | Dependency Injection Container | GitHub |https://github.com/nette/di] [API |https://api.nette.org/di/] +| **Database**:[database:] | Datenbankschicht | GitHub |https://github.com/nette/database] [API |https://api.nette.org/database/] +| **Forms**:[forms:] | Bequeme und sichere Webformulare | GitHub |https://github.com/nette/forms] [API |https://api.nette.org/forms/] +| **Http**:[http:] | Schicht zur Kapselung von HTTP Request & Response | GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] +| **Latte**:[latte:] | Großartiges Template-System | GitHub |https://github.com/nette/latte] [API |https://api.nette.org/latte/] +| **Mail**:[mail:] | Senden von E-Mails | GitHub |https://github.com/nette/mail] [API |https://api.nette.org/mail/] +| **Neon**:[neon:] | Lesen und Schreiben des [NEON |https://ne-on.org] Formats | GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] +| **Php Generator**:[php-generator:] | PHP-Code-Generator | GitHub |https://github.com/nette/php-generator] [API |https://api.nette.org/php-generator/] +| **Robot Loader**:[robot-loader:] | Das komfortabelste Autoloading | GitHub |https://github.com/nette/robot-loader] [API |https://api.nette.org/robot-loader/] +| **Routing**:[application:routing] | Routing | GitHub |https://github.com/nette/routing] [API |https://api.nette.org/routing/] +| **Safe Stream**:[safe-stream:] | Sichere atomare Dateioperationen | GitHub |https://github.com/nette/safe-stream] [API |https://api.nette.org/safe-stream/] +| **Schema**:[schema:] | Validierung von Benutzerdaten | GitHub |https://github.com/nette/schema] [API |https://api.nette.org/schema/] +| **Security**:[security:authentication] | Verwaltung von Zugriffsrechten | GitHub |https://github.com/nette/security] [API |https://api.nette.org/security/] +| **Tester**:[tester:] | Entspannte Unit-Tests in PHP | GitHub |https://github.com/nette/tester] [API |https://api.nette.org/tester/] +| **Tracy**:[tracy:] | Debugging-Werkzeug, das Sie lieben werden ♥ | GitHub |https://github.com/nette/tracy] [API |https://api.nette.org/tracy/] +| **Utils**:[utils:] | Grundlegende Klassen und Werkzeuge | GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] {{leftbar: @menu-common}} {{toc: no}} diff --git a/www/el/10-reasons-why-nette.texy b/www/el/10-reasons-why-nette.texy new file mode 100644 index 0000000000..f9803eb25a --- /dev/null +++ b/www/el/10-reasons-why-nette.texy @@ -0,0 +1,93 @@ +7 λόγοι για να χρησιμοποιήσετε το Nette +*************************************** + +
                                                                                                                            + +Φανταστείτε ένα PHP framework που σας επιτρέπει να επικεντρωθείτε σε αυτό που κάνετε καλύτερα. Σας οδηγεί στη συγγραφή καθαρού κώδικα. Φροντίζει από μόνο του για την ασφάλεια. Σταματήστε να ονειρεύεστε και γνωρίστε το Nette. Ξεκινήστε ένα ταξίδι που θα σας ανοίξει τις πόρτες σε νέες δυνατότητες ανάπτυξης. Θα μιλήσουμε για: + +- πώς να δημιουργείτε ιστότοπους με μέγιστη άνεση +- πώς να γράφετε κομψό κώδικα +- τι σημαίνει η σοφία "λιγότερος κώδικας = μεγαλύτερη ασφάλεια" +- πώς να χτίζετε έναν ιστότοπο σαν ένα κιτ κατασκευής +- και πώς να γίνετε μέλος μιας επιτυχημένης κοινότητας + +
                                                                                                                            + +Το Nette φέρνει άνεση και αποδοτικότητα στον κόσμο των προγραμματιστών ιστού χάρη σε καινοτόμα εργαλεία και τεχνικές. Ποια είναι τα βασικά χαρακτηριστικά που καθιστούν το Nette μοναδικό και απαραίτητο στοιχείο της εργαλειοθήκης ενός προγραμματιστή; Ας τα δούμε! + + +#1 Το Nette σας κακομαθαίνει +---------------------------- + +Όταν πριν από είκοσι χρόνια άρχισε να γεννιέται το framework Nette, όλα περιστρέφονταν γύρω από έναν στόχο: Πώς να δημιουργούμε ιστότοπους όσο το δυνατόν πιο άνετα; Πώς να κάνουμε τη δουλειά των προγραμματιστών όσο το δυνατόν πιο ευχάριστη; Πώς να κάνουμε τη δημιουργία ιστοτόπων σέξι; + +Το Nette με αυτή την προσέγγιση προσέλκυσε πολλούς προγραμματιστές και γρήγορα κέρδισε δημοτικότητα. Τότε ονομάζαμε αυτή τη φιλοσοφία Netteway και σήμερα υπάρχει ένας όρος γι' αυτήν, *Developer Experience* (DX). Το Nette είναι ένα framework που έχει το DX στο DNA του. Θα νιώσετε τη διαφορά σε χίλια και ένα πράγματα – από πολύ μικρές λεπτομέρειες έως θεμελιώδεις καινοτομίες. Αφήστε το framework να σας κακομάθει. + + +#2 Το Nette σας οδηγεί σε καθαρό κώδικα +--------------------------------------- + +Θέλετε να γράφετε καθαρό κώδικα; Να έχετε σωστά σχεδιασμένες εφαρμογές; Ποιος δεν θα ήθελε! Και ακριβώς εδώ ξεκινά ο ρόλος του framework. Εάν το ίδιο δεν δίνει το παράδειγμα, δεν μπορεί να δημιουργηθεί μια άριστα σχεδιασμένη εφαρμογή. + +Το Nette δίνει το παράδειγμα. Είναι ένας μέντορας που διδάσκει καλές συνήθειες και τη συγγραφή κώδικα σύμφωνα με δοκιμασμένες μεθοδολογίες. Ως πρωτοπόρος και ευαγγελιστής της dependency injection, προσφέρει μια ποιοτική βάση για βιώσιμες, επεκτάσιμες και εύκολα αναγνώσιμες εφαρμογές. Το Nette είναι σχεδιασμένο έτσι ώστε να είναι κατανοητό για αρχάριους, ενώ ταυτόχρονα προσφέρει επαρκές βάθος για έμπειρους προγραμματιστές. + +Η κοινότητα γύρω από το Nette έχει αναδείξει πολλές προσωπικότητες που σήμερα βρίσκονται πίσω από επιτυχημένα και σημαντικά έργα. Για χιλιάδες προγραμματιστές, το Nette έγινε μέντορας στην πορεία τους προς την επαγγελματική ανάπτυξη. Ελάτε μαζί μας και ανακαλύψτε πώς το Nette θα επηρεάσει θετικά την ποιότητα του κώδικά σας και των εφαρμογών σας. + + +#3 Αξιόπιστος φύλακας των εφαρμογών σας +--------------------------------------- + +Το Nette προστατεύει τις εφαρμογές σας. Με την πάροδο των ετών, έχει αποκτήσει τη φήμη ενός εργαλείου που παίρνει την ασφάλεια εξαιρετικά σοβαρά. Παρέχει έξυπνη προστασία από ευπάθειες. Πάντα φροντίζει να διευκολύνει τη δουλειά των προγραμματιστών, αλλά ποτέ εις βάρος της ασφάλειας. + +Το μότο του "λιγότερος κώδικας = επαρκής ασφάλεια" σημαίνει ότι τα μεμονωμένα στοιχεία συμπεριφέρονται με ασφάλεια ήδη από τη βάση τους. Δεν χρειάζεται να ενεργοποιήσετε χαρακτηριστικά ασφαλείας γράφοντας επιπλέον κώδικα. Δεν μπορείτε λοιπόν να το ξεχάσετε, ή να φοβάστε ότι παραβλέψατε κάτι. Οι προγραμματιστές συχνά δεν γνωρίζουν καν πόσα πράγματα ασφαλείας κάνει το Nette γι' αυτούς, και εκπλήσσονται όταν το μαθαίνουν. + +Σας παρουσιάζουμε ένα framework που σας οδηγεί σε καθαρό κώδικα και ταυτόχρονα επαγρυπνεί για την ασφάλεια των εφαρμογών σας. Το Nette είναι ένας αξιόπιστος συνεργάτης που σας επιτρέπει να επικεντρωθείτε στη δημιουργία εξαιρετικών εφαρμογών ιστού με ηρεμία. + + +#4 Δημιουργήστε τον ιστότοπο σαν ένα κιτ κατασκευής +--------------------------------------------------- + +Στο Nette, χτίζετε σελίδες από επαναχρησιμοποιήσιμα UI components. Θυμίζει την ανάπτυξη desktop εφαρμογών, και το Nette μετέφερε με επιτυχία αυτή την προσέγγιση στον ιστό. Χρειάζεστε ένα datagrid στη διαχείριση; Απλά βρείτε το στην αγορά open source components, εγκαταστήστε το και απλά εισαγάγετέ το στη σελίδα. Επιπλέον, μπορείτε να δημιουργήσετε τα δικά σας components για επαναλαμβανόμενα στοιχεία στις σελίδες, εξαλείφοντας έτσι τις διπλοτυπίες και βελτιώνοντας την οργάνωση του κώδικα. + +Αυτό το μοναδικό χαρακτηριστικό διακρίνει το Nette από όλους τους άλλους σημαντικούς παίκτες στην αγορά. Σας επιτρέπει να δημιουργείτε και να συντηρείτε αποτελεσματικά εφαρμογές ιστού. Με το Nette, η εργασία με το UI γίνεται μια ομαλή και ευχάριστη εμπειρία. + + +#5 Ευέλικτη σουίτα πακέτων +-------------------------- + +Το Nette είναι μια σουίτα [αυτόνομα χρησιμοποιήσιμων πακέτων |www:packages]. Μεταξύ αυτών είναι το εθιστικό [εργαλείο εντοπισμού σφαλμάτων Tracy |tracy:], το [σύστημα προτύπων νέας γενιάς Latte |latte:], το [εξαιρετικό Dependency Injection Container |dependency-injection:], οι [φόρμες |forms:] και πολλά άλλα. Κάθε πακέτο έχει ευανάγνωστη λεπτομερή τεκμηρίωση και φιλοξενείται σε ξεχωριστό αποθετήριο στο GitHub. Μπορείτε να χρησιμοποιήσετε τα πακέτα αυτόνομα ή να τα συνδυάσετε με άλλα εργαλεία και τεχνολογίες που ήδη χρησιμοποιείτε. Για παράδειγμα, το Latte μπορεί να αναπτυχθεί στο WordPress ή στο Slim Framework, το DI container μπορεί να είναι ο πυρήνας ενός εταιρικού framework και το Tracy θα οπτικοποιεί τα μηνύματα σφαλμάτων. + +Ή μπορείτε να χρησιμοποιήσετε το Nette ως σύνολο, ως framework, και να δημιουργήσετε σε αυτό μια πλήρη εφαρμογή ιστού. Είτε αναπτύσσετε ένα μικρό προσωπικό έργο είτε μια στιβαρή εταιρική εφαρμογή, το Nette θα είναι ο αξιόπιστος συνεργάτης σας. + + +#6 Σταθερότητα & Καινοτομία +--------------------------- + +Το Nette είναι ένα ώριμο και δοκιμασμένο framework με μακρόχρονη ιστορία. Παρόλα αυτά, διατηρείται ευέλικτο και προσαρμόσιμο χάρη στον έξυπνο σχεδιασμό του. Οι χρήστες εκτιμούν ότι είναι ένα μικρό και ευέλικτο framework, όχι ένας γίγαντας. + +Είναι πάντα έτοιμο εκ των προτέρων για νέες εκδόσεις PHP και λαμβάνει υπόψη τις τελευταίες καινοτομίες στον τομέα της ανάπτυξης εφαρμογών ιστού. Αυτό είναι κρίσιμο για την ελαχιστοποίηση του τεχνολογικού χρέους. + +Το Nette είναι λοιπόν ένας συνδυασμός μακρόχρονης σταθερότητας και καινοτομίας, που σας επιτρέπει να βασίζεστε σε μια δοκιμασμένη λύση, ενώ ταυτόχρονα μπορείτε να αξιοποιείτε τις τελευταίες τεχνολογίες και τάσεις. Με το Nette, έχετε τη βεβαιότητα ότι το έργο σας θα βασίζεται σε θεμέλια που συμβαδίζουν συνεχώς με τον ταχέως εξελισσόμενο κόσμο της ανάπτυξης ιστού. + + +#7 Γίνετε μέλος της καλύτερης παρέας +------------------------------------ + +Το Nette Framework είναι δημοφιλές μεταξύ των επαγγελματιών. Βασίζονται σε αυτό [σημαντικές εταιρείες |https://builtwith.nette.org] όπως οι O2, BOSCH, ESET, Zásilkovna, DHL, SUPRAPHON και εκατοντάδες άλλες. Σε αρκετές δημοσκοπήσεις, κέρδισε ως το πιο δημοφιλές και ευρέως χρησιμοποιούμενο framework στην Τσεχική Δημοκρατία. + +Ξεκινήστε με το Nette και θα σας ανοιχτούν νέες ευκαιρίες εργασίας. Θα αποκτήσετε όχι μόνο ένα ισχυρό εργαλείο, αλλά και την πιο ενεργή κοινότητα στην Τσεχία, η οποία είναι έτοιμη να σας υποστηρίξει στην πορεία σας προς την επαγγελματική ανάπτυξη. Συναντιούνται τακτικά στα [Posoboty |www.posobota.cz], εκδηλώσεις όπου ανταλλάσσουν εμπειρίες. Ελάτε να μας συναντήσετε! + + +Ανακαλύψτε το Nette +------------------- + +Γίνετε μέλος των χιλιάδων ικανοποιημένων προγραμματιστών που έχουν ήδη ανακαλύψει τα πλεονεκτήματα του Nette και αρχίστε να γράφετε καθαρότερο, ασφαλέστερο και πιο αποδοτικό κώδικα με αυτό το μοναδικό framework. + +Δημιουργήστε την [πρώτη σας εφαρμογή σύμφωνα με τις οδηγίες |quickstart:], βήμα προς βήμα. Θα έχετε συνεχώς στη διάθεσή σας την [εκτενή τεκμηρίωση |nette:] και την πρακτική [επισκόπηση API |https://api.nette.org]. Επισκεφθείτε το [blog μας γεμάτο συμβουλές |https://blog.nette.org] και τη συλλογή [πρόσθετων και components |https://componette.org] που επεκτείνουν τις δυνατότητες του Nette. + +Έχετε ερωτήσεις; Απευθυνθείτε στη σελίδα με τις [συχνές ερωτήσεις|nette:troubleshooting] ή συζητήστε στο [τσεχικό φόρουμ|https://forum.nette.org/cs/]. Προτιμάτε την προσωπική εκπαίδευση; Προσφέρουμε [εκπαίδευση Nette Framework |https://www.skoleniphp.cz/skoleni-nette-vyvoj-webovych-aplikaci] με [εξαιρετικές κριτικές |https://www.skoleniphp.cz/ohlasy]. + +**Γνωρίστε το framework που θα σας κακομάθει, θα σας καθοδηγήσει και θα σας εμπνεύσει.** + + +{{leftbar: @menu-common}} diff --git a/www/el/@home.texy b/www/el/@home.texy index 16a0b980cf..97ac9f5efa 100644 --- a/www/el/@home.texy +++ b/www/el/@home.texy @@ -1,2 +1,2 @@ -{{maintitle:Nette - Άνετη και ασφαλής ανάπτυξη ιστοσελίδων σε PHP}} -{{description: Το Nette είναι μια οικογένεια ώριμων και αυτόνομων στοιχείων για την PHP. Είστε έτοιμοι να γοητευτείτε; Μαζί, δημιουργούν ένα πλαίσιο που είχε αξιολογηθεί ως το 3ο πιο δημοφιλές στον κόσμο. Η φιλοσοφία μας είναι να εστιάζουμε στην παραγωγικότητα, τις βέλτιστες πρακτικές και την ασφάλεια.}} +{{maintitle:Nette – Άνετη και ασφαλής ανάπτυξη εφαρμογών ιστού σε PHP}} +{{description: Το Nette είναι μια οικογένεια προηγμένων και αυτόνομων components για PHP. Αφήστε τα να σας εμπνεύσουν. Μαζί σχηματίζουν ένα framework, που αξιολογήθηκε ως το 3ο πιο δημοφιλές στον κόσμο. Η φιλοσοφία του Nette δίνει εξαιρετική έμφαση στην παραγωγικότητα, τις βέλτιστες πρακτικές και την ασφάλεια.}} diff --git a/www/el/@menu-common.texy b/www/el/@menu-common.texy index 2e4913cbf2..7131ba95b4 100644 --- a/www/el/@menu-common.texy +++ b/www/el/@menu-common.texy @@ -1,20 +1,22 @@ Εισαγωγή ******** -- [Γιατί να χρησιμοποιήσετε τη Nette; |www:10-reasons-why-nette] +- [Γιατί να χρησιμοποιήσετε το Nette; |www:10-reasons-why-nette] - [Εγκατάσταση |nette:installation] -- [Δημιουργήστε την πρώτη σας εφαρμογή! |quickstart:] +- [Γράφοντας την πρώτη σας εφαρμογή! |quickstart:] Γενικά θέματα ************* -- [Κατάλογος πακέτων |www:packages] -- [Συντήρηση και PHP |www:maintenance] +- [Λίστα πακέτων |www:packages] +- [Συντήρηση και εκδόσεις PHP |www:maintenance] - [Σημειώσεις έκδοσης |https://nette.org/releases] -- [Οδηγός αναβάθμισης |migrations:en] -- [nette:Αντιμετώπιση προβλημάτων |nette:Troubleshooting] +- [Μετάβαση σε νεότερες εκδόσεις|migrations:en] +- [Αντιμετώπιση προβλημάτων |nette:troubleshooting] - [Ποιος δημιουργεί το Nette |https://nette.org/contributors] -- [Ιστορία της Nette |history] +- [Ιστορία του Nette |history] - [Συμμετέχετε |contributing:] -- [Ανάπτυξη χορηγού |https://nette.org/en/donate] -- [Αναφορά API |https://api.nette.org] -- [Βέλτιστες πρακτικές |best-practices:] +- [Υποστηρίξτε την ανάπτυξη |https://nette.org/cs/donate] +- [Αναφορά API |https://api.nette.org/] +- [Οδηγοί και βέλτιστες πρακτικές |best-practices:] + +- [Η ασφάλεια πάνω απ' όλα |nette:vulnerability-protection] diff --git a/www/el/donate.texy b/www/el/donate.texy new file mode 100644 index 0000000000..f088ed2d57 --- /dev/null +++ b/www/el/donate.texy @@ -0,0 +1,20 @@ +Υποστηρίξτε την ανάπτυξη του Nette +********************************** + +
                                                                                                                            +Όλοι όσοι βασίζονται στο Nette ενδιαφέρονται για την ενεργή ανάπτυξη του framework. Για να υποστηρίζει νέες εκδόσεις PHP. Για να διορθώνονται τα σφάλματα. Για να έρχονται νέες καινοτομίες που διευκολύνουν την εργασία ή εξοικονομούν χρόνο και χρήμα. Για να έχει το framework εξαιρετική τεκμηρίωση και να υπάρχει γύρω του χρήσιμο περιεχόμενο, είτε με τη μορφή άρθρων, οδηγών ή βίντεο. + +Πολλά μέρη του Nette αντιπροσωπεύουν την παγκόσμια πρωτοπορία και θέλουμε να συνεχίσει να είναι έτσι. + +Χωρίς επαρκή χρηματοδότηση, τίποτα από αυτά δεν μπορεί να διασφαλιστεί. Ωστόσο, για να μπορείτε να βασίζεστε στην κυκλοφορία επόμενων εκδόσεων, χρειάζεται πολύ λίγο: να το υποστηρίζετε κάθε μήνα έστω και με ένα μικρό χρηματικό ποσό. + +Ελάτε μαζί μας και γίνετε συνεργάτης του Nette! + +Έτσι θα διασφαλίσετε την υγιή λειτουργία του έργου στο οποίο βασίζεστε. Και ταυτόχρονα **θα αποκτήσετε μια σειρά από αποκλειστικά προνόμια** (δείτε *Επίπεδα Συνεργασίας* στη δεξιά στήλη). Θα αποκτήσετε πρόσβαση σε μπόνους περιεχόμενο. Σε τεχνική υποστήριξη. Θα αυξήσετε την προτεραιότητα επίλυσης των ζητημάτων σας. Και επίσης θα προβάλετε την εταιρεία σας και θα προσελκύσετε προγραμματιστές. + +Πώς θα σας προβάλουμε; Για παράδειγμα, αναφέροντας το λογότυπό σας σε αυτόν τον ιστότοπο (δηλαδή σε αυτή τη σελίδα, στην αρχική σελίδα, στην τεκμηρίωση, στο φόρουμ και σε [ειδική σελίδα |https://nette.org/partner/vitalita], την οποία μπορείτε να παραπέμπετε). Θα έχετε τη δυνατότητα να δημοσιεύετε [αγγελίες εργασίας |https://forum.nette.org/cs/f30-prace-a-zakazky], να διαφημίζεστε στο φόρουμ ([παράδειγμα |https://forum.nette.org/cs/30798-problem-s-cizim-klicem-pri-mazani#p198093]) ή στην τεκμηρίωση ([παράδειγμα |https://doc.nette.org/cs/application/components#toc-flash-zpravy]), δηλαδή σε μέρη με την καλύτερη δυνατή απήχηση στην ομάδα των προγραμματιστών Nette. + +Εκδίδουμε τιμολόγια στους συνεργάτες, ώστε να μπορούν να καταχωρήσουν την υποστήριξη στα έξοδά τους, είτε μηνιαία, τριμηνιαία, εξαμηνιαία ή ετήσια. + +{{include: buttons}} +
                                                                                                                            diff --git a/www/el/history.texy b/www/el/history.texy index ec7e460c13..c8a42a74d9 100644 --- a/www/el/history.texy +++ b/www/el/history.texy @@ -1,36 +1,35 @@ -Ιστορία της Nette +Ιστορία του Nette ***************** .[perex] -Η προέλευση του Nette χρονολογείται από το 2004, όταν ο συγγραφέας του David Grudl άρχισε να αναζητά ένα κατάλληλο πλαίσιο για τη συγγραφή εφαρμογών, καθώς η καθαρή PHP δεν ήταν πλέον επαρκής. Καμία από τις διαθέσιμες λύσεις που υπήρχαν εκείνη την εποχή δεν του ταίριαζε, οπότε άρχισε σταδιακά να σκιαγραφεί τα χαρακτηριστικά ενός νέου πλαισίου, το οποίο αργότερα πήρε το όνομα Nette. +Η αρχή της δημιουργίας του Nette χρονολογείται από το 2004, όταν ο δημιουργός του, David Grudl, άρχισε να αναζητά ένα κατάλληλο framework στο οποίο θα μπορούσε να γράφει εφαρμογές, καθώς η καθαρή PHP δεν ήταν πλέον επαρκής γι' αυτό. Καμία από τις τότε διαθέσιμες λύσεις δεν τον ικανοποιούσε, οπότε άρχισε σταδιακά να σκιαγραφεί τα χαρακτηριστικά ενός νέου framework, το οποίο αργότερα ονομάστηκε Nette. -Εκείνη την εποχή δεν υπήρχαν ακόμη τα σημερινά πλαίσια όπως το Symfony, το Laravel ή το Ruby on Rails. Στον κόσμο της Java, το JSF (JavaServer Faces) ήταν το πρότυπο, και στον ανταγωνιστικό χώρο του .NET, το ASP.NET Webforms ήταν το κυρίαρχο πλαίσιο. Και τα δύο επέτρεπαν τη δημιουργία σελίδων με τη χρήση επαναχρησιμοποιήσιμων στοιχείων UI. Ο David θεώρησε ότι οι μέθοδοι αφαίρεσης και οι προσπάθειές τους να δημιουργήσουν απάτη μέσω του stateless πρωτοκόλλου HTTP με τη χρήση συνεδριών ή postbacks ήταν ελαττωματικές και θεμελιωδώς προβληματικές. Προκάλεσαν πολλές δυσκολίες για τους χρήστες και τις μηχανές αναζήτησης. Για παράδειγμα, αν αποθηκεύατε έναν σύνδεσμο, εκπλαγείτε όταν βρίσκατε αργότερα διαφορετικό περιεχόμενο κάτω από αυτόν. +Εκείνη την εποχή, δεν υπήρχαν ακόμη τα σημερινά frameworks όπως το Symfony, το Laravel ή ακόμη και το Ruby on Rails. Στον κόσμο της Java, το πρότυπο ήταν το framework JSF (JavaServer Faces) και στον ανταγωνιστικό .NET, το ASP.NET Webforms. Και τα δύο επέτρεπαν τη δημιουργία σελίδων χρησιμοποιώντας επαναχρησιμοποιήσιμα UI components. Ο David θεωρούσε τους τρόπους αφαίρεσής τους και τις προσπάθειες δημιουργίας κατάστασης πάνω στο ασταθές πρωτόκολλο HTTP μέσω session ή του λεγόμενου postback ως λανθασμένους και θεμελιωδώς μη λειτουργικούς. Προκαλούσαν πολλές δυσκολίες στους χρήστες και στις μηχανές αναζήτησης. Για παράδειγμα, αν αποθηκεύατε έναν σύνδεσμο, αργότερα με έκπληξη βρίσκατε διαφορετικό περιεχόμενο κάτω από αυτόν. -Η δυνατότητα σύνθεσης σελίδων από επαναχρησιμοποιήσιμα στοιχεία UI γοήτευσε τον David, ο οποίος το γνώριζε καλά από τους Delphi, ένα δημοφιλές εργαλείο για την κατασκευή εφαρμογών γραφείου εκείνη την εποχή. Του άρεσαν οι αγορές με συστατικά ανοιχτού κώδικα για τους Delphi. Έτσι προσπάθησε να λύσει το ερώτημα πώς να δημιουργήσει ένα πλαίσιο συστατικών που με τη σειρά του θα λειτουργούσε σε πλήρη αρμονία με το stateless HTTP. Έψαχνε για μια ιδέα που θα ήταν φιλική προς τους χρήστες, το SEO και τους προγραμματιστές. Και έτσι γεννήθηκε η Nette. +Η ίδια η δυνατότητα σύνθεσης σελίδων από επαναχρησιμοποιήσιμα UI components γοήτευσε τον David, την ήξερε καλά από το Delphi, ένα τότε δημοφιλές εργαλείο για τη δημιουργία desktop εφαρμογών. Του άρεσαν οι αγορές με opensource components για το Delphi. Προσπάθησε λοιπόν να λύσει το ζήτημα του πώς να δημιουργήσει ένα component framework που, αντίθετα, θα λειτουργούσε σε απόλυτη αρμονία με το ασταθές HTTP. Αναζητούσε μια ιδέα που θα ήταν φιλική προς τους χρήστες, το SEO και τους προγραμματιστές. Και έτσι άρχισε να γεννιέται το Nette. .[note] -Το όνομα Nette προέκυψε τυχαία στο μπάνιο, όταν ο συγγραφέας εντόπισε ένα μπουκάλι με τζελ ξυρίσματος Gillette, περιστραμμένο έτσι ώστε να φαίνεται μόνο το *llette*. +Το όνομα Nette προέκυψε τυχαία στο μπάνιο, όταν ο συγγραφέας είδε ένα δοχείο με τζελ ξυρίσματος Gillette, γυρισμένο έτσι ώστε να φαίνεται μόνο το *llette*. -Ακολούθησαν χιλιάδες ώρες έρευνας, σκέψης και επαναδιατύπωσης. Σε ένα σκονισμένο γκαράζ σε ένα χωριό κάπου έξω από το Μπρνο, δημιουργήθηκαν τα πρώτα περιγράμματα του μελλοντικού πλαισίου. Η βάση της αρχιτεκτονικής ήταν το μοτίβο MVC, το οποίο χρησιμοποιήθηκε τότε από το ξεχασμένο πλέον πλαίσιο PHP Mojavi και αργότερα έγινε δημοφιλές από τη διαφημιστική εκστρατεία γύρω από το Ruby on Rails. Μια από τις πηγές έμπνευσης ήταν ακόμη και το πλαίσιο phpBase που δεν δημοσιεύτηκε ποτέ από τον Honza Tichý. +Ακολούθησαν χιλιάδες ώρες έρευνας, σκέψης και ξαναγραψίματος. Σε ένα σκονισμένο γκαράζ σε ένα χωριό κάπου έξω από το Μπρνο, δημιουργήθηκαν τα πρώτα περιγράμματα του μελλοντικού framework. Η βάση της αρχιτεκτονικής έγινε το πρότυπο MVC, το οποίο τότε χρησιμοποιούσε το ξεχασμένο πλέον PHP framework Mojavi και αργότερα έγινε δημοφιλές χάρη στον θόρυβο γύρω από το Ruby on Rails. Μία από τις πηγές έμπνευσης ήταν ακόμη και το ποτέ δημοσιευμένο framework phpBase του Honza Tichý. -Άρθρα για το επερχόμενο Nette άρχισαν να εμφανίζονται στο blog του συγγραφέα. Αστειεύονταν ότι επρόκειτο για vaporware. Αλλά τότε, τον Οκτώβριο του 2007, στο συνέδριο PHP Seminar της Πράγας, ο David παρουσίασε δημοσίως το Nette. Παρεμπιπτόντως, αυτό το συνέδριο εξελίχθηκε σε WebExpo ένα χρόνο αργότερα, αργότερα ένα από τα μεγαλύτερα συνέδρια πληροφορικής στην Ευρώπη. Ακόμα και τότε το Nette παρουσίαζε με υπερηφάνεια μια σειρά από πρωτότυπες έννοιες, όπως το προαναφερθέν μοντέλο συστατικών, ο αμφίδρομος δρομολογητής, ο ειδικός τρόπος σύνδεσης μεταξύ των παρουσιαστών κ.λπ. Είχε φόρμες, έλεγχο ταυτότητας, προσωρινή αποθήκευση κ.λπ. Τα πάντα εξακολουθούν να χρησιμοποιούνται στο Nette με την αρχική του αντίληψη μέχρι σήμερα. +Στο blog του συγγραφέα άρχισαν να δημοσιεύονται άρθρα για το επερχόμενο Nette. Αστειεύονταν ότι ήταν vaporware. Στη συνέχεια όμως, τον Οκτώβριο του 2007 στο συνέδριο PHP Seminář της Πράγας, ο David παρουσίασε δημόσια το Nette. Παρεμπιπτόντως, από αυτό το συνέδριο εξελίχθηκε ένα χρόνο αργότερα το WebExpo, αργότερα ένα από τα μεγαλύτερα συνέδρια πληροφορικής στην Ευρώπη. Ήδη τότε, το Nette καυχιόταν για πολλές πρωτότυπες ιδέες, όπως το αναφερόμενο component model, ο αμφίδρομος router, ο συγκεκριμένος τρόπος σύνδεσης μεταξύ των presenters κ.λπ. Είχε φόρμες, λυμένη αυθεντικοποίηση, caching κ.λπ. Όλα αυτά χρησιμοποιούνται στο Nette στην αρχική τους μορφή μέχρι σήμερα. .[note] -Το Nette χρησιμοποιεί *παρουσιαστή* αντί για *ελεγκτή* επειδή υπήρχαν υποτίθεται πάρα πολλές λέξεις που άρχιζαν με *con* στον κώδικα (ελεγκτής, front controller, control, config, container, ...). +Στο Nette, αντί του όρου *controller* χρησιμοποιείται ο όρος *presenter*, επειδή στον κώδικα υπήρχαν, λέει, πάρα πολλές λέξεις που άρχιζαν με *con* (controller, front controller, control, config, container, ...) -Στα τέλη του 2007, ο David Grudl δημοσίευσε τον κώδικα και το Nette 0.7 κυκλοφόρησε. Μια ενθουσιώδης κοινότητα προγραμματιστών σχηματίστηκε γύρω από αυτό και άρχισε να συναντιέται κάθε μήνα στην εκδήλωση Posobota. Στην κοινότητα συμμετείχαν πολλοί από τους σημερινούς φωστήρες, όπως ο Ondrej Mirtes, συγγραφέας του σπουδαίου εργαλείου PHPStan. Η ανάπτυξη του Nette προχώρησε και τα επόμενα δύο χρόνια κυκλοφόρησαν οι εκδόσεις 0.8 και 0.9, θέτοντας τις βάσεις για όλα σχεδόν τα σημερινά μέρη του πλαισίου. Συμπεριλαμβανομένων των αποσπασμάτων AJAX που προηγούνται του Hotwire για Ruby on Rails ή του Symfony UX Turbo κατά 14 χρόνια. +Στα τέλη του 2007, ο David Grudl δημοσίευσε και τον κώδικα και έτσι είδε το φως της δημοσιότητας η έκδοση Nette 0.7. Το framework τράβηξε αμέσως τεράστια προσοχή. Δημιουργήθηκε γύρω του μια ενθουσιώδης κοινότητα προγραμματιστών, η οποία άρχισε να συναντιέται κάθε μήνα στην εκδήλωση Posobota. Στην κοινότητα υπήρχαν πολλές σημερινές προσωπικότητες, για παράδειγμα ο Ondřej Mirtes, δημιουργός του εξαιρετικού εργαλείου PHPStan. Η ανάπτυξη του Nette προχωρούσε γρήγορα και τα επόμενα δύο χρόνια κυκλοφόρησαν οι εκδόσεις 0.8 και 0.9, όπου τέθηκαν οι βάσεις σχεδόν όλων των σημερινών τμημάτων του framework. Συμπεριλαμβανομένων των AJAX snippets, τα οποία προηγήθηκαν κατά 14 χρόνια του Hotwire για το Ruby on Rails ή του Symfony UX Turbo. -Αλλά ένα κρίσιμο πράγμα έλειπε από το Nette τότε. Το Dependecy injection container (DIC). Η Nette χρησιμοποιούσε ένα *service locator* και η πρόθεση ήταν να μεταβεί σε dependecy injection. Αλλά πώς να σχεδιαστεί ένα τέτοιο πράγμα; Ο David Grudl, ο οποίος δεν είχε καμία εμπειρία με το DI εκείνη την εποχή, πήγε για φαγητό με τον Vasek Purchart, ο οποίος χρησιμοποιούσε το DI εδώ και περίπου μισό χρόνο. Μαζί συζήτησαν το θέμα και ο David άρχισε να εργάζεται πάνω στο Nette DI, μια βιβλιοθήκη που έφερε πλήρη επανάσταση στον τρόπο με τον οποίο σκεφτόμαστε για τη σχεδίαση εφαρμογών. Το δοχείο DI έγινε ένα από τα πιο επιτυχημένα μέρη του πλαισίου. Και έδωσε το έναυσμα για δύο παρακλάδια: τη μορφή Neon και τη βιβλιοθήκη Schema. +Ένα θεμελιώδες πράγμα όμως έλειπε από το τότε Nette. Το Dependecy injection container (DIC). Το Nette χρησιμοποιούσε το λεγόμενο *service locator* και η πρόθεση ήταν να μεταβεί ακριβώς στην dependecy injection. Αλλά πώς να σχεδιάσεις κάτι τέτοιο; Ο David Grudl, ο οποίος τότε δεν είχε εμπειρία με το DI, πήγε για μεσημεριανό με τον Vašek Purchart, ο οποίος χρησιμοποιούσε το DI για περίπου μισό χρόνο. Συζήτησαν μαζί το θέμα και ο David ξεκίνησε την εργασία στο Nette DI, μια βιβλιοθήκη που ανέτρεψε εντελώς τον τρόπο σκέψης για τον σχεδιασμό εφαρμογών. Το DI container έγινε ένα από τα πιο επιτυχημένα μέρη του framework. Και αργότερα οδήγησε στη δημιουργία δύο spin-offs: της μορφής Neon και της βιβλιοθήκης Schema. .[note] -Η μετάβαση στην έγχυση εξαρτήσεων χρειάστηκε πολύ χρόνο και περιμέναμε δύο χρόνια για μια νέα έκδοση του Nette. Ως εκ τούτου, όταν τελικά κυκλοφόρησε, είχε την αρίθμηση 2. Επομένως, η έκδοση 1 της Nette δεν υπάρχει. +Η μετάβαση στην dependency injection απαίτησε αρκετό χρόνο και η νέα έκδοση του Nette άργησε μερικά χρόνια. Γι' αυτό, όταν τελικά κυκλοφόρησε, έφερε απευθείας τον αριθμό 2. Η έκδοση Nette 1 δηλαδή δεν υπάρχει. -Η Nette ξεκίνησε τη σύγχρονη ιστορία της το 2012 με την έκδοση 2.0. Έφερε επίσης τη βάση δεδομένων Nette Database, η οποία περιλάμβανε ένα εξαιρετικά εύχρηστο εργαλείο βάσης δεδομένων, που τώρα ονομάζεται Explorer. Αυτή η βιβλιοθήκη προγραμματίστηκε αρχικά από τον Jakub Vrána, γείτονα του David Grudl και συγγραφέα του δημοφιλούς εργαλείου Adminer. Την περαιτέρω ανάπτυξή της ανέλαβε στη συνέχεια ο Jan Škrášek για τρία χρόνια. +Το Nette το 2012 με την έκδοση 2.0 ξεκίνησε τη σύγχρονη ιστορία του. Έφερε επίσης το Nette Database, μέρος του οποίου ήταν και ένα εξαιρετικά εύχρηστο εργαλείο για την εργασία με βάσεις δεδομένων, που σήμερα ονομάζεται Explorer. Αυτή η βιβλιοθήκη είχε αρχικά προγραμματιστεί από τον Jakub Vrána, γείτονα του David Grudl και δημιουργό του δημοφιλούς εργαλείου Adminer. Την περαιτέρω ανάπτυξή της ανέλαβε στη συνέχεια για τρία χρόνια ο Jan Škrášek. -Το 2014 κυκλοφόρησε η έκδοση Nette 2.1, την οποία ακολούθησε σύντομα η έκδοση Nette 2.2. Πώς είναι αυτό δυνατόν; Η έκδοση 2.2 ήταν η ίδια με την έκδοση 2.1, απλά χωρισμένη σε είκοσι ξεχωριστά πακέτα. Το εργαλείο Composer επικράτησε στον κόσμο της PHP και άλλαξε τον τρόπο με τον οποίο σκεφτόμαστε τη δημιουργία βιβλιοθηκών. Η Nette έπαψε να είναι ένας μονόλιθος και διασπάστηκε σε μικρότερα ανεξάρτητα μέρη. Το καθένα με το δικό του αποθετήριο, issue tracker και τη δική του ροή ανάπτυξης και έκδοσης. Με αυτόν τον τρόπο η Nette δεν χρειάζεται να περάσει από τον παραλογισμό που συνηθίζεται στα μονολιθικά frameworks, όπου βγαίνει μια νέα έκδοση ενός πακέτου παρόλο που τίποτα δεν έχει αλλάξει. Ο πραγματικός διαχωρισμός των αποθετηρίων Git απαιτούσε αρκετές εβδομάδες προετοιμασίας και εκατοντάδες ώρες μηχανογραφικού χρόνου. - -Το Nette κατέλαβε επίσης την εκπληκτική 3η θέση στην παγκόσμια ψηφοφορία για το καλύτερο πλαίσιο PHP που διοργάνωσε το περιοδικό Sitepoint. +Το 2014 κυκλοφόρησε το Nette 2.1 και σε σύντομο χρονικό διάστημα ακολούθησε το Nette 2.2. Πώς είναι δυνατόν αυτό; Η έκδοση 2.2 ήταν η ίδια με την έκδοση 2.1, απλώς χωρισμένη σε είκοσι ξεχωριστά πακέτα. Στον κόσμο της PHP, είχε καθιερωθεί το εργαλείο Composer και άλλαξε τον τρόπο θεώρησης της δημιουργίας βιβλιοθηκών. Έτσι, το Nette έπαψε να είναι μονολιθικό και διασπάστηκε σε μικρότερα ανεξάρτητα μέρη. Κάθε ένα με το δικό του αποθετήριο, issue tracker και το δικό του ρυθμό ανάπτυξης και έκδοσης. Έτσι, στο Nette δεν χρειάζεται να συμβαίνουν οι παραλογισμοί που είναι συνηθισμένοι σε μονολιθικά frameworks, όπου κυκλοφορεί μια νέα έκδοση ενός πακέτου, παρόλο που δεν έχει αλλάξει τίποτα σε αυτό. Ο ίδιος ο διαχωρισμός των αποθετηρίων Git περιλάμβανε αρκετές εβδομάδες προετοιμασίας και εκατοντάδες ώρες μηχανικού χρόνου. +Το Nette κατέλαβε επίσης την εκπληκτική 3η θέση στην παγκόσμια δημοσκόπηση για το καλύτερο PHP framework που διοργάνωσε το περιοδικό Sitepoint. {{toc:no}} diff --git a/www/el/license.texy b/www/el/license.texy new file mode 100644 index 0000000000..9d0dd89065 --- /dev/null +++ b/www/el/license.texy @@ -0,0 +1,44 @@ +Πολιτική αδειοδότησης +********************* + +Το Nette Framework διανέμεται ως ελεύθερο λογισμικό, ώστε ο καθένας να μπορεί να το χρησιμοποιήσει. Μπορείτε να επιλέξετε εάν σας ταιριάζει καλύτερα η άδεια [#New BSD License] ή η [#GNU General Public License (GPL)] στην έκδοση 2 ή 3. + +Η άδεια BSD συνιστάται για τα περισσότερα έργα, καθώς είναι εύκολο να την κατανοήσετε και δεν θέτει σχεδόν κανέναν περιορισμό στο τι μπορείτε να κάνετε με το framework. Μπορείτε να χρησιμοποιήσετε το Nette και σε εμπορικά έργα. Εάν, ωστόσο, η GPL ταιριάζει καλύτερα στο έργο σας, επιλέξτε την. Δεν χρειάζεται να ενημερώσετε κανέναν για την απόφασή σας. Πάντα διατηρείτε τα αρχικά πνευματικά δικαιώματα. + +Θα πρέπει να γνωρίζετε ότι το όνομα "Nette Framework" είναι προστατευμένο εμπορικό σήμα και η χρήση του έχει ορισμένους περιορισμούς. Μην χρησιμοποιείτε λοιπόν το "Nette" στο όνομα του έργου σας ή του τομέα ανώτατου επιπέδου, επιλέξτε καλύτερα ένα όνομα που θα στέκεται στα δικά του θεμέλια. Εάν το έργο σας είναι καλό, δεν θα αργήσει να δημιουργήσει τη δική του φήμη. + +Εάν είστε ικανοποιημένοι με το Nette Framework, και πιστεύουμε ότι θα είστε, μπορείτε να το [υποστηρίξετε με μια συνεισφορά |donate]. Σας ευχαριστούμε. + + +New BSD License +--------------- + +Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com) All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of "Nette Framework" nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +GNU General Public License (GPL) +-------------------------------- + +Τα κείμενα των αδειών GPL είναι πολύ μεγάλα, γι' αυτό παρατίθενται εδώ οι σύνδεσμοι προς αυτά: + +- GPL version 2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +- GPL version 3: https://www.gnu.org/licenses/gpl-3.0.html + + +{{toc:yes}} +{{priority: -2}} diff --git a/www/el/maintenance.texy b/www/el/maintenance.texy index 144b2f416f..1b83d6a119 100644 --- a/www/el/maintenance.texy +++ b/www/el/maintenance.texy @@ -1,27 +1,27 @@ -Συντήρηση και εκδόσεις PHP -************************** +Συντήρηση και συμβατότητα με την PHP +************************************ .[perex] -Η Nette είναι ένα πλαίσιο με εξαιρετικά μεγάλη περίοδο υποστήριξης για μεμονωμένες εκδόσεις. Κάθε κλάδος είναι μια έκδοση LTS (Long-Term Support Release) με τουλάχιστον 2 χρόνια υποστήριξης. +Το Nette είναι ένα framework με εξαιρετικά μεγάλο χρόνο υποστήριξης για κάθε έκδοση. Κάθε κλάδος είναι LTS (Long-Term Support Release) με υποστήριξη τουλάχιστον 2 ετών. -Κάθε έκδοση συντηρείται ενεργά για περίοδο ενός έτους (ή περισσότερο) από την αρχική σταθερή έκδοση. -Τα κρίσιμα σφάλματα και τα σφάλματα ασφαλείας διορθώνονται για δύο χρόνια. +Κάθε έκδοση συντηρείται ενεργά για περίοδο ενός έτους (ή και περισσότερο) από την αρχική σταθερή κυκλοφορία. Τα κρίσιμα σφάλματα και τα σφάλματα ασφαλείας διορθώνονται για δύο χρόνια. -Ημερολόγιο εκδόσεων (οδικός χάρτης) .[#toc-release-calendar-roadmap] -==================================================================== +Ημερολόγιο εκδόσεων Nette +========================= {{include: doc-roadmap-table}} -Συμβατότητα PHP .[#toc-php-compatibility] -========================================= +Συμβατότητα με την PHP +====================== + +Η συμβατότητα ισχύει πάντα για την πιο πρόσφατη έκδοση από κάθε σειρά. -Η συμβατότητα ισχύει πάντα για την τελευταία έκδοση κάθε σειράς. {{include: doc-roadmap-versions}} {{leftbar: @menu-common}} {{toc: no}} -{{description: Πίνακες με τις κυκλοφορίες της Nette, τον οδικό χάρτη, τη συντήρηση και τη συμβατότητα με την PHP}} +{{description: Εκδόσεις Nette, χάρτης πορείας, πίνακες συντήρησης και συμβατότητα με την PHP}} diff --git a/www/el/packages.texy b/www/el/packages.texy index ab63ceb71e..b4289bca58 100644 --- a/www/el/packages.texy +++ b/www/el/packages.texy @@ -1,26 +1,27 @@ -Λίστα πακέτων -************* +Λίστα πακέτων Nette +******************* -| **Application**:[application:how-it-works] | Ο πυρήνας της διαδικτυακής εφαρμογής | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] -| **Bootstrap**:[bootstrap:] | Bootstrap της εφαρμογής σας | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] -| **Caching**:[caching:] | Cache layer with set of storages | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] -| **Component Model**:[component-model:] | Θεμέλιο για συστήματα συστατικών | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] +| **Application**:[application:how-it-works] | Πυρήνας των web εφαρμογών | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] +| **Assets**:[assets:] | Διαχείριση στατικών αρχείων | [GitHub |https://github.com/nette/assets] [API |https://api.nette.org/assets/] +| **Bootstrap**:[bootstrap:] | Bootstrap της web εφαρμογής | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] +| **Caching**:[caching:] | Επίπεδο caching με αποθήκες | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] +| **Component Model**:[component-model:] | Βάση του συστήματος components | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] | **DI**:[dependency-injection:] | Dependency Injection Container | [GitHub |https://github.com/nette/di] [API |https://api.nette.org/di/] -| **Database**:[database:] | Database layer | [GitHub |https://github.com/nette/database] [API |https://api.nette.org/database/] -| **Forms**:[forms:] | Greatly facilitates secure web forms | [GitHub |https://github.com/nette/forms] [API |https://api.nette.org/forms/] -| **Http**:[http:] | Στρώμα για το αίτημα και την απόκριση HTTP | [GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] -| **Latte**:[latte:] | Καταπληκτική μηχανή προτύπων | [GitHub |https://github.com/nette/latte] [API |https://api.nette.org/latte/] -| **Mail**:[mail:] | Αποστολή ηλεκτρονικών μηνυμάτων | [GitHub |https://github.com/nette/mail] [API |https://api.nette.org/mail/] -| **Neon**:[neon:] | Φόρτωση και απόρριψη αρχείων σε [μορφή NEON|https://ne-on.org] | [GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] +| **Database**:[database:] | Επίπεδο βάσης δεδομένων | [GitHub |https://github.com/nette/database] [API |https://api.nette.org/database/] +| **Forms**:[forms:] | Άνετες και ασφαλείς φόρμες web | [GitHub |https://github.com/nette/forms] [API |https://api.nette.org/forms/] +| **Http**:[http:] | Επίπεδο που ενσωματώνει το HTTP request & response | [GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] +| **Latte**:[latte:] | Εξαιρετικό σύστημα προτύπων | [GitHub |https://github.com/nette/latte] [API |https://api.nette.org/latte/] +| **Mail**:[mail:] | Αποστολή e-mail | [GitHub |https://github.com/nette/mail] [API |https://api.nette.org/mail/] +| **Neon**:[neon:] | Ανάγνωση και εγγραφή της μορφής [NEON |https://ne-on.org] | [GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] | **Php Generator**:[php-generator:] | Γεννήτρια κώδικα PHP | [GitHub |https://github.com/nette/php-generator] [API |https://api.nette.org/php-generator/] | **Robot Loader**:[robot-loader:] | Η πιο άνετη αυτόματη φόρτωση | [GitHub |https://github.com/nette/robot-loader] [API |https://api.nette.org/robot-loader/] | **Routing**:[application:routing] | Δρομολόγηση | [GitHub |https://github.com/nette/routing] [API |https://api.nette.org/routing/] | **Safe Stream**:[safe-stream:] | Ασφαλείς ατομικές λειτουργίες με αρχεία | [GitHub |https://github.com/nette/safe-stream] [API |https://api.nette.org/safe-stream/] -| **Security**:[security:authentication] | Παρέχει σύστημα ελέγχου πρόσβασης | [GitHub |https://github.com/nette/security] [API |https://api.nette.org/security/] | **Schema**:[schema:] | Επικύρωση δεδομένων χρήστη | [GitHub |https://github.com/nette/schema] [API |https://api.nette.org/schema/] -| **Tester**:[tester:] | Απολαυστικός έλεγχος μονάδας σε PHP | [GitHub |https://github.com/nette/tester] [API |https://api.nette.org/tester/] -| **Tracy**:[tracy:] | Εργαλείο εντοπισμού σφαλμάτων που θα αγαπήσετε ♥ | [GitHub |https://github.com/nette/tracy] [API |https://api.nette.org/tracy/] -| **Utils**:[utils:] | Βοηθητικά προγράμματα και βασικές κλάσεις | [GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] +| **Security**:[security:authentication] | Διαχείριση δικαιωμάτων πρόσβασης | [GitHub |https://github.com/nette/security] [API |https://api.nette.org/security/] +| **Tester**:[tester:] | Άνετες δοκιμές μονάδας στην PHP | [GitHub |https://github.com/nette/tester] [API |https://api.nette.org/tester/] +| **Tracy**:[tracy:] | Εργαλείο εντοπισμού σφαλμάτων που θα λατρέψετε ♥ | [GitHub |https://github.com/nette/tracy] [API |https://api.nette.org/tracy/] +| **Utils**:[utils:] | Βασικές κλάσεις και εργαλεία | [GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] {{leftbar: @menu-common}} {{toc: no}} diff --git a/www/en/10-reasons-why-nette.texy b/www/en/10-reasons-why-nette.texy index 248e0371a9..e5bff3379d 100644 --- a/www/en/10-reasons-why-nette.texy +++ b/www/en/10-reasons-why-nette.texy @@ -1,53 +1,93 @@ -Why Use Nette? -************** +7 Reasons to Use Nette +**********************
                                                                                                                            -Stop solving repetitive tasks and getting distracted with details that make programming dull and unproductive. **Nette Framework lets you work more effectively, focus on what's important and makes your code more readable and well-structured in the process**. Some features include: +Imagine a PHP framework that lets you focus on what you love doing most. It guides you towards writing clean code and takes care of security itself. Stop dreaming and discover Nette. Embark on a journey that opens doors to new development possibilities. We'll discuss: -- an excellent templating system -- unbeatable diagnostic tools -- extraordinarily effective database layer -- rock-solid protection against known vulnerabilities -- HTML5 and AJAX support, SEO friendly -- well written documentation and an active open source community -- mature and clean object oriented design leveraging the latest PHP features -- best-practice solutions that are encouraged, but not enforced +- how to create websites with maximum comfort +- how to write elegant code +- what the wisdom "less code = more security" truly means +- how to build a website like a modular kit +- and how to become part of a thriving community
                                                                                                                            -And it's completely free of charge. We think it's worth a try. +Nette brings comfort and efficiency to the world of web developers through innovative tools and techniques. What are the key features that make Nette unique and an essential part of a developer's toolkit? Let's dive in! -**Nette Framework lets you focus on the creative part of being a developer**. It's built to be extremely usable, friendly and a joy to use. Its comprehensible yet efficient syntax, a cutting edge debugger and industry-leading security features let you write e-commerce sites, wikis, blogs, CMS or anything you can imagine faster and better than ever. +#1 Nette Pampers You +-------------------- -Nette Framework is [used by major companies |https://builtwith.nette.org], such as T-Systems, GE Money, Mladá fronta, VLTAVA-LABE-PRESS, Internet Info, DHL, Logio, ESET or Actum. It currently propels the website of the former president of the Czech Republic Václav Klaus. In the poll held by [Zdroják |https://www.zdrojak.cz/clanky/vysledky-technologie-na-ceskem-webu/] it was awarded the prize of The most popular and the most widely used framework in the Czech Republic. +When the Nette Framework began to take shape twenty years ago, it revolved around a single goal: How to create websites as comfortably as possible? How to make life easier for programmers? How to make web development appealing? -Learning Nette Framework will guarantee no shortage of interesting job offers. +This approach resonated with many programmers, and Nette quickly gained popularity. Back then, we called this philosophy "Netteway," and today there's a term for it: Developer Experience (DX). Nette is a framework with DX embedded in its DNA. You'll notice the difference in countless ways – from tiny details to groundbreaking innovations. Let the framework pamper you. -Get on Board ------------- +#2 Nette Guides You to Clean Code +--------------------------------- -Follow the guide and [create your first application |quickstart:] step by step. A handy [Programmer's guide |@home] is here to help you at any time and if you want to dig deeper, check out the practical [API documentation |https://api.nette.org/] which overviews all the classes and methods. +Do you want to write clean code? Have well-designed applications? Who doesn't! This is where the role of a framework begins. If it doesn't lead by example, creating a brilliantly designed application is impossible. -If you get stuck, check out the [frequently asked questions |nette:troubleshooting] or the [official forum |https://forum.nette.org/en/] where experienced users are eager to help. +Nette leads by example. It acts as a mentor, teaching good habits and promoting code written according to proven methodologies. As a pioneer and evangelist of dependency injection, it provides a solid foundation for sustainable, extensible, and easily readable applications. Nette is designed to be understandable for beginners while offering sufficient depth for experienced developers. -Are you not committed enough to learn it yourself? Roger that - we offer [Nette Framework training |https://www.skoleniphp.cz/skoleni-nette-vyvoj-webovych-aplikaci] (Czech). Feedback from former participants is to be found at [skoleniphp.cz/ohlasy |https://www.skoleniphp.cz/ohlasy]. +The Nette community has nurtured many individuals who are now behind successful and important projects. For thousands of programmers, Nette has been a mentor on their path to professional growth. Join us and discover how Nette can positively influence the quality of your code and applications. -More Information ----------------- +#3 A Reliable Guardian for Your Applications +-------------------------------------------- -We've got a [blog full of tips |https://blog.nette.org] and a collection of various [addons and components |https://componette.org] for use in your applications. +Nette protects your applications. Over the years, it has earned a reputation as a tool that takes security extremely seriously. It provides ingenious protection against vulnerabilities, always striving to make developers' work easier without compromising security. -In addition, the community around Nette Framework meets every month on so on an event called [Poslední sobota |https://www.posobota.cz] (Czech), where the members share their experience and just have a good time. Come grab a beer! +Its philosophy "less code = more security" means that individual elements are secure by default. There's no need to activate security features by writing additional code. This means you can't forget or worry about overlooking something. Programmers often don't even realize how many security tasks Nette handles for them and are surprised when they find out. +We present a framework that guides you towards clean code while diligently watching over the security of your applications. Nette is a reliable partner that allows you to focus on creating great web applications with peace of mind. -Get Involved! -------------- -Any contribution to the development is greatly appreciated. You are welcome to share a word of mouth, place an [icon |www:logo] on your website, or [support the development |www:donate] financially. Thank you ♥. +#4 Build Your Website Like a Modular Kit +---------------------------------------- + +With Nette, you build pages from reusable UI components. This approach is reminiscent of desktop application development, and Nette has successfully brought it to the web. Need a datagrid in your administration panel? Just find one in the open-source components marketplace, install it, and easily insert it into your page. Furthermore, you can create your own components for recurring elements on your pages, eliminating duplication and improving code organization. + +This unique feature sets Nette apart from other major players in the market. It enables you to efficiently create and maintain web applications. With Nette, working with the UI becomes a smooth and enjoyable experience. + + +#5 A Flexible Set of Packages +----------------------------- + +Nette is a collection of [standalone packages |www:packages]. These include the addictive [Tracy debugging tool |tracy:], the [next-generation Latte templating system |latte:], the [excellent Dependency Injection Container |dependency-injection:], powerful [forms |forms:], and many others. Each package has comprehensive, readable documentation and resides in a separate repository on GitHub. You can use these packages individually or combine them with other tools and technologies you already employ. For instance, Latte can be deployed in WordPress or the Slim Framework, the DI container can serve as the core of a corporate framework, and Tracy can visualize error messages anywhere. + +Alternatively, you can use Nette as a whole – as a complete framework – to build your entire web application. Whether you're developing a small personal project or a robust enterprise application, Nette will be your reliable partner. + + +#6 Stability & Innovation +------------------------- + +Nette is a mature and proven framework with a long history. Despite this, it remains agile and flexible thanks to its smart design. Users appreciate that it is a lean and nimble framework, not a monolith. + +It is always prepared in advance for new PHP versions and incorporates the latest innovations in web application development. This is crucial for minimizing technical debt. + +Nette, therefore, offers a combination of long-term stability and continuous innovation, allowing you to rely on a proven solution while leveraging the latest technologies and trends. With Nette, you can be confident that your project is built on foundations that consistently keep pace with the rapidly evolving world of web development. + + +#7 Be in the Best Company +------------------------- + +Nette Framework is popular among professionals. It is trusted by [prominent companies |https://builtwith.nette.org] such as O2, BOSCH, ESET, Packeta (Zásilkovna), DHL, SUPRAPHON, and hundreds of others. It has consistently ranked high in surveys as one of the most popular and widely used frameworks in the Czech Republic. + +Start with Nette and open up new job opportunities. You'll gain not only a powerful tool but also access to one of the most active communities in the Czech Republic, ready to support you on your journey to professional growth. The community regularly meets at [Posobota |www.posobota.cz] events to exchange experiences. Come meet us! + + +Discover Nette +-------------- + +Join thousands of satisfied developers who have already discovered the benefits of Nette and start writing cleaner, safer, and more efficient code with this unique framework. + +Create your first application [step by step with our guide |quickstart:]. Comprehensive [documentation |nette:] and a practical [API overview |https://api.nette.org] will always be at your fingertips. Visit our [blog full of tips |https://blog.nette.org] and the collection of [add-ons and components |https://componette.org] that extend Nette's capabilities. + +Do you have questions? Visit the [frequently asked questions |nette:troubleshooting] page or discuss on the [Czech forum |https://forum.nette.org/en/]. + +**Get to know the framework that will pamper, guide, and inspire you.** {{leftbar: @menu-common}} diff --git a/www/en/@home.texy b/www/en/@home.texy index 4f310833cb..d2bee678ae 100644 --- a/www/en/@home.texy +++ b/www/en/@home.texy @@ -1,2 +1,2 @@ {{maintitle:Nette – Comfortable and Safe Web Development in PHP}} -{{description: Nette is a family of mature and stand-alone components for PHP. Ready to be smitten? Together, they create a framework that had been rated as the 3rd most popular in the world. Our philosophy is to focus on productivity, best practices, and security.}} +{{description: Nette is a family of mature and stand-alone components for PHP. Let them inspire you. Together, they create a framework rated as the 3rd most popular in the world. The Nette philosophy places extraordinary emphasis on productivity, best practices, and security.}} diff --git a/www/en/@menu-common.texy b/www/en/@menu-common.texy index 24153933a3..6b8259699e 100644 --- a/www/en/@menu-common.texy +++ b/www/en/@menu-common.texy @@ -8,17 +8,15 @@ Introduction General Topics ************** - [List of Packages |www:packages] -- [Maintenance and PHP |www:maintenance] +- [Maintenance and PHP Versions |www:maintenance] - [Release Notes |https://nette.org/releases] - [Upgrade Guide |migrations:] - [nette:Troubleshooting] - [Who Creates Nette |https://nette.org/contributors] - [History of Nette |history] - [Get Involved |contributing:] -- [Sponsor development |https://nette.org/en/donate] -- [API Reference |https://api.nette.org] -- [Best Practices |best-practices:] +- [Sponsor Development |https://nette.org/en/donate] +- [API Reference |https://api.nette.org/] +- [Tutorials and Best Practices |best-practices:] -/-- comment -- [Security Features |nette:vulnerability-protection] -\-- +- [Security First |nette:vulnerability-protection] diff --git a/www/en/donate.texy b/www/en/donate.texy index 70f707a6e0..fad488a5a6 100644 --- a/www/en/donate.texy +++ b/www/en/donate.texy @@ -2,20 +2,19 @@ Sponsor Nette Development *************************
                                                                                                                            -Anyone who builds sites on Nette has an interest in seeing the framework actively evolve. To support new versions of PHP. That bugs are fixed. To come up with "more news":https://blog.nette.org/en/what-is-coming-in-the-next-versions that will make things easier or save time and money. That the framework has great documentation and there is useful content around it, whether in the form of articles, tutorials or videos. +Anyone who builds applications on Nette has an interest in seeing the framework actively evolve. To support new PHP versions. To fix bugs. To introduce "new features":https://blog.nette.org/en/what-is-coming-in-the-next-versions that simplify work or save time and money. To ensure the framework has excellent documentation and that useful content, such as articles, tutorials, or videos, is available. -Many parts of Nette are world-leading and we want to keep it that way. +Many parts of Nette are world-class, and we want to keep it that way. -Without adequate funding, none of this can be assured. Yet to be able to count on more versions coming out, you only have to do quite a bit: support it with even a small amount of money each month. +Without adequate funding, none of this can be guaranteed. Yet, ensuring future versions are released requires relatively little: your support, even with a small monthly contribution. -Come on in and become a Nette partner! +Join us and become a Nette partner! -You will ensure the healthy functioning of the project you rely on. And at the same time, **you'll get a host of exclusive benefits** (see *Partnership Levels* in the right column). You'll get access to bonus content. Access to technical support. You'll increase the priority of resolving your issues. And you'll raise your company's profile and attract developers to your company. +You will ensure the healthy functioning of the project you rely on. At the same time, **you'll receive a host of exclusive benefits** (see *Partnership Levels* in the right column). You'll gain access to bonus content and technical support. You'll increase the priority of resolving your issues. And you'll also enhance your company's visibility and attract developers. -How do we make you visible? For example, by putting your logo on this site (i.e. on this page, on the homepage, in the documentation, on the forum, and on [special page |https://nette.org/partner/vitalita] that you can link to). -You will be able to post [job postings |https://forum.nette.org/cs/f30-prace-a-zakazky], advertise on the forum ([sample |https://forum.nette.org/cs/30798-problem-s-cizim-klicem-pri-mazani#p198093]) or in the documentation ([sample |https://doc.nette.org/en/application/components#toc-flash-messages]), the places with the best reach into the Nette developer community. +How do we increase your visibility? For example, by displaying your logo on this website (i.e., on this page, the homepage, in the documentation, on the forum, and on a [special page |https://nette.org/en/partners]). You will have the opportunity to post [job offers |https://forum.nette.org/en/f30-jobs], advertise on the forum ([sample |https://forum.nette.org/cs/30798-problem-s-cizim-klicem-pri-mazani#p198093]) or in the documentation ([sample |https://doc.nette.org/en/application/components#toc-flash-messages]), reaching the Nette developer community effectively. -We send invoices to partners so they can put the support into their costs, either monthly, quarterly, semi-annually or annually. +We issue invoices to partners so they can include the support in their expenses, either monthly, quarterly, semi-annually, or annually. {{include: buttons}}
                                                                                                                            diff --git a/www/en/history.texy b/www/en/history.texy index 5903aa6f7a..6ef3a63438 100644 --- a/www/en/history.texy +++ b/www/en/history.texy @@ -2,35 +2,34 @@ History of Nette **************** .[perex] -The origins of Nette date back to 2004, when its author David Grudl started looking for a suitable framework in which to write applications, as pure PHP was no longer sufficient. None of the solutions available at the time suited him, so he gradually began to outline the features of a new framework, which later got the name Nette. +The origins of Nette date back to 2004, when its author, David Grudl, began searching for a suitable framework to write applications, as pure PHP was no longer sufficient. None of the solutions available at the time met his needs, so he gradually started outlining the features of a new framework, which later received the name Nette. -At that time, current frameworks like Symfony, Laravel or Ruby on Rails did not exist yet. In the Java world, JSF (JavaServer Faces) was the standard, and in the competing .NET realm, ASP.NET Webforms was the dominant framework. Both allowed building pages using reusable UI components. David considered their abstraction methods and attempts to create statelessness over the stateless HTTP protocol using sessions or postbacks to be flawed and fundamentally broken. They caused many difficulties for users and search engines. For example, if you saved a link, you were surprised to find different content under it later. +At that time, modern frameworks like Symfony, Laravel, or Ruby on Rails did not yet exist. In the Java world, JSF (JavaServer Faces) was the standard, while in the competing .NET realm, ASP.NET WebForms dominated. Both allowed building pages using reusable UI components. However, David considered their abstraction methods and attempts to simulate statefulness over the stateless HTTP protocol using sessions or so-called postbacks to be flawed and fundamentally broken. They caused numerous difficulties for users and search engines. For instance, if you saved a link, you might later be surprised to find different content under it. -The possibility of composing pages from reusable UI components fascinated David, who knew it well from Delphi, a popular tool for building desktop applications at the time. He liked the marketplaces with opensource components for Delphi. So he tried to solve the question of how to create a component framework that would, in turn, work in complete harmony with stateless HTTP. He was looking for a concept that would be user, SEO and developer friendly. And so Nette was born. +The very idea of composing pages from reusable UI components fascinated David, who was familiar with it from Delphi, a popular tool for building desktop applications at the time. He liked the marketplaces offering open-source components for Delphi. Thus, he sought to answer the question: how to create a component-based framework that would work in complete harmony with stateless HTTP? He looked for a concept that would be friendly to users, SEO, and developers alike. And so, Nette began to take shape. .[note] -The name Nette came about by chance in the bathroom, when the author spotted a bottle of Gillette shaving gel, rotated so that only the *llette* could be seen. +The name Nette originated by chance in the bathroom when the author noticed a can of Gillette shaving gel, turned so that only *llette* was visible. -Thousands of hours of research, thinking and rewriting followed. In a dusty garage in a village somewhere outside Brno, the first outlines of the future framework were being created. The basis of the architecture was the MVC pattern, which was then used by the now forgotten PHP framework Mojavi and later popularized by the hype around Ruby on Rails. One of the inspirational sources was even the never published phpBase framework by Honza Tichý. +Thousands of hours of research, contemplation, and rewriting followed. In a dusty garage in a village somewhere outside Brno, the first outlines of the future framework emerged. The MVC (Model-View-Controller) pattern became the architectural foundation, used then by the now-forgotten PHP framework Mojavi and later popularized by the buzz around Ruby on Rails. One source of inspiration was even the never-released phpBase framework by Honza Tichý. -Articles about the upcoming Nette started to appear on the author's blog. It was joked that it was about vaporware. But then in October 2007, at the Prague PHP Seminar conference, David publicly introduced Nette. By the way, this conference evolved into WebExpo a year later, later one of the biggest IT conferences in Europe. Even then Nette proudly presented a number of original concepts, such as the aforementioned component model, bidirectional router, specific way of linking between presenters, etc. It had forms, authentication, caching, etc. Everything is still used in Nette in its original concept until today. +Articles about the upcoming Nette started appearing on the author's blog. Jokes circulated about it being vaporware. But then, in October 2007, at the PHP Seminar conference in Prague, David publicly introduced Nette. Incidentally, this conference evolved a year later into WebExpo, which became one of the largest IT conferences in Europe. Even back then, Nette boasted several original concepts, such as the aforementioned component model, a bidirectional router, a specific way of linking between presenters, etc. It had forms, solved authentication, caching, and more. All these core concepts are still used in Nette today in their original form. .[note] -Nette uses *presenter* instead of *controller* because there were supposedly too many words starting with *con* in the code (controller, front controller, control, config, container, ...). +In Nette, the term *presenter* is used instead of *controller*, supposedly because there were too many words starting with *con* in the codebase (controller, front controller, control, config, container, ...). -At the end of 2007, David Grudl published the code and Nette 0.7 was released. An enthusiastic community of programmers formed around it and started to meet every month at the Posobota event. The community included many of today's luminaries, such as Ondrej Mirtes, author of the great PHPStan tool. The development of Nette moved forward, and in the next two years versions 0.8 and 0.9 were released, laying the foundations for almost all of today's parts of the framework. Including AJAX snippets that predate Hotwire for Ruby on Rails or Symfony UX Turbo by 14 years. +At the end of 2007, David Grudl released the code, and Nette 0.7 saw the light of day. The framework immediately garnered immense attention. An enthusiastic community of programmers formed around it, starting monthly meetups called Posobota. This community included many figures who are prominent today, such as Ondřej Mirtes, the author of the excellent tool PHPStan. Nette's development raced forward, and in the following two years, versions 0.8 and 0.9 were released, laying the groundwork for almost all of the framework's current parts. This included AJAX snippets, which predated technologies like Hotwire for Ruby on Rails or Symfony UX Turbo by 14 years. -But one crucial thing was missing from Nette back then. Dependecy injection container (DIC). Nette was using a *service locator* and the intention was to move to dependecy injection. But how to design such a thing? David Grudl, who had no experience with DI at the time, went to lunch with Vasek Purchart, who had been using DI for about half a year. Together they discussed the topic and David started work on Nette DI, a library that completely revolutionized the way we think about application design. The DI container became one of the most successful parts of the framework. And it gave rise to two spin-offs: the Neon format and the Schema library. +However, one crucial element was missing in the early Nette: a Dependency Injection Container (DIC). Nette used a *service locator*, and the plan was to transition to dependency injection. But how to design such a thing? David Grudl, lacking experience with DI at the time, had lunch with Václav Purchart, who had been using DI for about six months. They discussed the topic, and David began working on Nette DI, a library that completely revolutionized the way application design was approached. The DI container became one of the framework's most successful parts and later gave rise to two spin-offs: the NEON format and the Schema library. .[note] -The shift to dependency injection took a lot of time, and we waited a couple of years for a new version of Nette. Therefore, when it finally came out, it was numbered 2. So Nette version 1 does not exist. +The transition to dependency injection took considerable time, and the community waited a few years for the new version of Nette. Therefore, when it was finally released, it was numbered 2.0 directly. Thus, Nette version 1 does not exist. -Nette started its modern history in 2012 with version 2.0. It also brought Nette Database, which included an extremely handy database tool, now called Explorer. This library was originally programmed by Jakub Vrána, David Grudl's neighbor and author of the popular Adminer tool. Its further development was then taken over by Jan Škrášek for three years. +Nette began its modern era in 2012 with version 2.0. It also introduced Nette Database, which included an exceptionally handy tool for working with databases, now known as Explorer. This library was originally programmed by Jakub Vrána, David Grudl's neighbor and the author of the popular Adminer tool. Jan Škrášek then took over its further development for three years. -In 2014, Nette 2.1 was released, followed shortly by Nette 2.2. How is this possible? Version 2.2 was the same as version 2.1, just split into twenty separate packages. The Composer tool took hold in the PHP world and changed the way we think about library creation. Nette ceased to be a monolith and broke up into smaller independent parts. Each with its own repository, issue tracker and its own flow of development and versioning. This way Nette doesn't have to go through the absurdities common in monolithic frameworks, where a new version of a package comes out even though nothing has changed. The actual splitting of the Git repositories involved several weeks of preparation and hundreds of hours of machine time. - -Nette also came in an amazing 3rd place in the global poll for the best PHP framework organized by Sitepoint magazine. +In 2014, Nette 2.1 was released, followed shortly by Nette 2.2. How was this possible? Version 2.2 was essentially the same as 2.1, but split into twenty separate packages. The Composer tool had become established in the PHP world, changing the way libraries were conceived. Nette ceased to be a monolith and was broken down into smaller, independent parts, each with its own repository, issue tracker, and development pace and versioning. This avoids the absurdities common in monolithic frameworks, where a new version of a package might be released even if nothing within it has changed. The actual splitting of the Git repositories involved several weeks of preparation and hundreds of hours of machine time. +Nette also achieved an impressive 3rd place in the global poll for the best PHP framework organized by SitePoint magazine. {{toc:no}} diff --git a/www/en/license.texy b/www/en/license.texy index 5728023c13..dc6a10a33f 100644 --- a/www/en/license.texy +++ b/www/en/license.texy @@ -1,20 +1,19 @@ Licensing Policy **************** -The Nette Framework is distributed as free software for anyone to use. You can choose whether you are more comfortable with the [New BSD |#New BSD License] or the [#GNU General Public License (GPL)] version 2 or 3. +Nette Framework is distributed as free software for anyone to use. You can choose whether the [New BSD |#New BSD License] or the [#GNU General Public License (GPL)], version 2 or 3, better suits your needs. -The BSD license is recommended for most projects because it is easy to understand and places almost no restrictions on what you can do with the framework. You can use Nette in commercial projects as well. However, if the GPL is a better fit for your project, choose it. You don't need to tell anyone how you made your decision. Always keep the original copyright. +The BSD license is recommended for most projects because it is easy to understand and places almost no restrictions on what you can do with the framework. You can use Nette in commercial projects as well. However, if the GPL is a better fit for your project, feel free to choose it. There is no need to inform anyone of your decision. Always preserve the original copyright notices. -You should know that the name "Nette Framework" is a protected trademark and its use has certain restrictions. So do not use "Nette" in your project or top-level domain name, rather choose a name that stands on its own merits. If your project is a good one, it won't take long for it to establish its reputation. +You should be aware that the name "Nette Framework" is a registered trademark, and its use has certain restrictions. Therefore, do not use "Nette" in your project name or top-level domain name; instead, choose a name that stands on its own merits. If your project is good, it won't take long to establish its own reputation. -If you are happy with the Nette Framework, and we believe you are, you can [support it with a donate |donate]. Thank you. +If you are satisfied with the Nette Framework, and we believe you will be, you can [support it with a donation |donate]. Thank you. New BSD License --------------- -Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com) -All rights reserved. +Copyright (c) 2004, 2023 David Grudl (https://davidgrudl.com) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -35,7 +34,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND GNU General Public License (GPL) -------------------------------- -GPL licenses are very very long, so instead of including them here we offer you URLs with full text: +The texts of the GPL licenses are very long, so links are provided here instead: - GPL version 2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html - GPL version 3: https://www.gnu.org/licenses/gpl-3.0.html diff --git a/www/en/maintenance.texy b/www/en/maintenance.texy index cf89ed819e..66b0760802 100644 --- a/www/en/maintenance.texy +++ b/www/en/maintenance.texy @@ -2,10 +2,9 @@ Maintenance and PHP Versions **************************** .[perex] -Nette is a framework with an exceptionally long support period for individual releases. Each branch is an LTS (Long-Term Support Release) with at least 2 years of support. +Nette is a framework with an exceptionally long support period for individual releases. Each branch is an LTS (Long-Term Support Release) with support for at least 2 years. -Each version is actively maintained for a period of one year (or longer) from its initial stable release. -Critical and security bugs are fixed for two years. +Each version is actively maintained for a period of one year (or longer) from its initial stable release. Critical and security bugs are fixed for two years. Release Calendar (Roadmap) @@ -17,11 +16,12 @@ Release Calendar (Roadmap) PHP Compatibility ================= -Compatibility always applies to the latest release in each series. +Compatibility always applies to the latest patch release within each minor version series. + {{include: doc-roadmap-versions}} {{leftbar: @menu-common}} {{toc: no}} -{{description: Nette releases, roadmap, maintenance and PHP compatibility tables}} +{{description: Nette releases, roadmap, maintenance schedule, and PHP compatibility tables}} diff --git a/www/en/packages.texy b/www/en/packages.texy index 24c260b967..61945fc199 100644 --- a/www/en/packages.texy +++ b/www/en/packages.texy @@ -1,26 +1,27 @@ List of Packages **************** -| **Application**:[application:how-it-works] | The kernel of web application | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] -| **Bootstrap**:[bootstrap:] | Bootstrap of your application | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] -| **Caching**:[caching:] | Cache layer with set of storages | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] -| **Component Model**:[component-model:] | Foundation for component systems | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] +| **Application**:[application:how-it-works] | Core of web applications | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] +| **Assets**:[assets:] | Static file management | [GitHub |https://github.com/nette/assets] [API |https://api.nette.org/assets/] +| **Bootstrap**:[bootstrap:] | Bootstraps the web application | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] +| **Caching**:[caching:] | Caching layer with various storages | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] +| **Component Model**:[component-model:] | Foundation for the component system | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] | **DI**:[dependency-injection:] | Dependency Injection Container | [GitHub |https://github.com/nette/di] [API |https://api.nette.org/di/] -| **Database**:[database:] | Database layer | [GitHub |https://github.com/nette/database] [API |https://api.nette.org/database/] -| **Forms**:[forms:] | Greatly facilitates secure web forms | [GitHub |https://github.com/nette/forms] [API |https://api.nette.org/forms/] -| **Http**:[http:] | Layer for the HTTP request & response | [GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] +| **Database**:[database:] | Database abstraction layer | [GitHub |https://github.com/nette/database] [API |https://api.nette.org/database/] +| **Forms**:[forms:] | Convenient and secure web forms | [GitHub |https://github.com/nette/forms] [API |https://api.nette.org/forms/] +| **Http**:[http:] | Layer encapsulating HTTP request & response | [GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] | **Latte**:[latte:] | Amazing template engine | [GitHub |https://github.com/nette/latte] [API |https://api.nette.org/latte/] -| **Mail**:[mail:] | Sending E-mails | [GitHub |https://github.com/nette/mail] [API |https://api.nette.org/mail/] -| **Neon**:[neon:] | Loads and dumps [NEON format|https://ne-on.org] | [GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] +| **Mail**:[mail:] | Sending emails | [GitHub |https://github.com/nette/mail] [API |https://api.nette.org/mail/] +| **Neon**:[neon:] | Reading and writing [NEON format |https://ne-on.org] | [GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] | **Php Generator**:[php-generator:] | PHP code generator | [GitHub |https://github.com/nette/php-generator] [API |https://api.nette.org/php-generator/] -| **Robot Loader**:[robot-loader:] | The most comfortable autoloading | [GitHub |https://github.com/nette/robot-loader] [API |https://api.nette.org/robot-loader/] -| **Routing**:[application:routing] | Routing | [GitHub |https://github.com/nette/routing] [API |https://api.nette.org/routing/] -| **Safe Stream**:[safe-stream:] | Safe atomic operations with files | [GitHub |https://github.com/nette/safe-stream] [API |https://api.nette.org/safe-stream/] -| **Security**:[security:authentication] | Provides access control system | [GitHub |https://github.com/nette/security] [API |https://api.nette.org/security/] -| **Schema**:[schema:] | User data validation | [GitHub |https://github.com/nette/schema] [API |https://api.nette.org/schema/] +| **Robot Loader**:[robot-loader:] | The most comfortable class autoloading | [GitHub |https://github.com/nette/robot-loader] [API |https://api.nette.org/robot-loader/] +| **Routing**:[application:routing] | URL routing | [GitHub |https://github.com/nette/routing] [API |https://api.nette.org/routing/] +| **Safe Stream**:[safe-stream:] | Safe atomic file operations | [GitHub |https://github.com/nette/safe-stream] [API |https://api.nette.org/safe-stream/] +| **Schema**:[schema:] | Validation of data structures | [GitHub |https://github.com/nette/schema] [API |https://api.nette.org/schema/] +| **Security**:[security:authentication] | Access rights management | [GitHub |https://github.com/nette/security] [API |https://api.nette.org/security/] | **Tester**:[tester:] | Enjoyable unit testing in PHP | [GitHub |https://github.com/nette/tester] [API |https://api.nette.org/tester/] | **Tracy**:[tracy:] | Debugging tool you will love ♥ | [GitHub |https://github.com/nette/tracy] [API |https://api.nette.org/tracy/] -| **Utils**:[utils:] | Utilities and Core Classes | [GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] +| **Utils**:[utils:] | Essential classes and utilities | [GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] {{leftbar: @menu-common}} {{toc: no}} diff --git a/www/es/10-reasons-why-nette.texy b/www/es/10-reasons-why-nette.texy new file mode 100644 index 0000000000..2d82fa6996 --- /dev/null +++ b/www/es/10-reasons-why-nette.texy @@ -0,0 +1,93 @@ +7 razones para usar Nette +************************* + +
                                                                                                                            + +Imagina un framework PHP que te permita concentrarte en lo que más te gusta hacer. Que te guíe para escribir código limpio. Que se preocupe por la seguridad por sí mismo. Deja de soñar y conoce Nette. Emprende un viaje que te abrirá las puertas a nuevas posibilidades de desarrollo. Te contaremos: + +- cómo crear sitios web con la máxima comodidad +- cómo escribir código elegante +- qué significa el lema «menos código = mayor seguridad» +- cómo construir un sitio web como si fuera un juego de construcción +- y cómo formar parte de una comunidad exitosa + +
                                                                                                                            + +Nette aporta comodidad y eficiencia al mundo de los desarrolladores web gracias a herramientas y técnicas innovadoras. ¿Cuáles son las características clave que hacen de Nette un elemento único e indispensable en el conjunto de herramientas de un desarrollador? ¡Echémosles un vistazo! + + +#1 Nette te mima +---------------- + +Cuando el framework Nette comenzó a gestarse hace veinte años, todo giraba en torno a un objetivo: ¿Cómo crear sitios web de la manera más cómoda posible? ¿Cómo hacer el trabajo de los programadores lo más agradable posible? ¿Cómo hacer que la creación de sitios web sea *sexy*? + +Nette atrajo a muchos programadores con este enfoque y rápidamente ganó popularidad. En aquel entonces, llamábamos a esta filosofía *Netteway*, y hoy en día existe un término para ella: *Developer Experience* (DX). Nette es un framework que lleva la DX en su ADN. Sentirás la diferencia en mil y un detalles, desde los más pequeños hasta innovaciones fundamentales. Déjate mimar por el framework. + + +#2 Nette te guía hacia un código limpio +--------------------------------------- + +¿Quieres escribir código limpio? ¿Tener aplicaciones bien diseñadas? ¡Quién no querría! Y aquí es donde entra en juego el framework. Si él mismo no da ejemplo, no se puede crear una aplicación excelentemente diseñada. + +Nette da ejemplo. Es un mentor que enseña buenos hábitos y a escribir código siguiendo metodologías probadas. Como pionero y evangelizador de la inyección de dependencias, ofrece una base sólida para crear aplicaciones sostenibles, extensibles y fáciles de leer. Nette está diseñado para ser comprensible para principiantes, pero al mismo tiempo ofrece la profundidad suficiente para desarrolladores experimentados. + +La comunidad que rodea a Nette ha formado a muchas personalidades que hoy están detrás de proyectos exitosos e importantes. Para miles de programadores, Nette se ha convertido en un mentor en su camino hacia el crecimiento profesional. Únete y descubre cómo Nette puede influir positivamente en la calidad de tu código y tus aplicaciones. + + +#3 Un guardián fiable para tus aplicaciones +------------------------------------------- + +Nette protege tus aplicaciones. A lo largo de los años, se ha ganado la reputación de ser una herramienta que se toma la seguridad muy en serio. Proporciona una seguridad sofisticada contra vulnerabilidades. Siempre se preocupa por facilitar el trabajo a los programadores, pero nunca a costa de la seguridad. + +Su lema «menos código = suficiente seguridad» significa que los componentes individuales se comportan de forma segura desde el principio. No es necesario activar funciones de seguridad escribiendo código adicional. Por lo tanto, no puedes olvidarte de ello ni temer haber pasado algo por alto. Los programadores a menudo ni siquiera son conscientes de cuántas medidas de seguridad implementa Nette por ellos, y se sorprenden cuando se enteran. + +Te presentamos un framework que te guía hacia un código limpio y, al mismo tiempo, vela por la seguridad de tus aplicaciones. Nette es un socio fiable que te permitirá concentrarte en crear excelentes aplicaciones web con total tranquilidad. + + +#4 Construye la web como un juego de construcción +------------------------------------------------- + +En Nette, construyes páginas a partir de componentes de UI reutilizables. Recuerda al desarrollo de aplicaciones de escritorio, y Nette ha trasladado con éxito este enfoque a la web. ¿Necesitas un *datagrid* en la administración? Simplemente búscalo en el mercado de componentes de código abierto, instálalo e insértalo fácilmente en la página. Además, puedes crear tus propios componentes para elementos repetitivos en las páginas, eliminando así duplicidades y mejorando la organización del código. + +Esta característica única distingue a Nette de todos los demás actores importantes del mercado. Te permitirá crear y mantener aplicaciones web de manera eficiente. Con Nette, trabajar con la interfaz de usuario (UI) se convierte en una experiencia fluida y agradable. + + +#5 Un conjunto flexible de paquetes +----------------------------------- + +Nette es un conjunto de [paquetes utilizables de forma independiente |www:packages]. Entre ellos se encuentran la adictiva [herramienta de depuración Tracy |tracy:], el [sistema de plantillas de nueva generación Latte |latte:], el [excelente Contenedor de Inyección de Dependencias |dependency-injection:], los [formularios |forms:] y muchos otros. Cada paquete tiene una documentación detallada y legible y reside en un repositorio independiente en GitHub. Puedes usar los paquetes de forma independiente o combinarlos con otras herramientas y tecnologías que ya estés utilizando. Por ejemplo, Latte se puede implementar en WordPress o Slim Framework, el contenedor DI puede ser el núcleo de un framework corporativo y Tracy visualizará los mensajes de error. + +O puedes usar Nette como un todo, como un framework, y crear una aplicación web completa con él. Ya sea que estés desarrollando un pequeño proyecto personal o una aplicación empresarial robusta, Nette será tu socio fiable. + + +#6 Estabilidad e innovación +--------------------------- + +Nette es un framework maduro y probado con una larga trayectoria. A pesar de ello, se mantiene ágil y flexible gracias a un diseño inteligente. Los usuarios aprecian que sea un framework pequeño y flexible, no un monolito. + +Siempre está preparado con antelación para las nuevas versiones de PHP y tiene en cuenta las últimas innovaciones en el campo del desarrollo de aplicaciones web. Esto es crucial para minimizar la deuda tecnológica. + +Nette es, por lo tanto, una combinación de estabilidad a largo plazo e innovación, que te permitirá confiar en una solución probada, mientras puedes aprovechar las últimas tecnologías y tendencias. Con Nette tienes la certeza de que tu proyecto se construirá sobre cimientos que se mantienen constantemente al día con el mundo del desarrollo web en rápida evolución. + + +#7 Estar en la mejor compañía +----------------------------- + +Nette Framework es popular entre los profesionales. Confían en él [empresas importantes |https://builtwith.nette.org] como O2, BOSCH, ESET, Zásilkovna, DHL, SUPRAPHON y cientos de otras. Ha ganado varias encuestas como el framework más popular y utilizado en la República Checa. + +Empieza con Nette y se te abrirán nuevas oportunidades laborales. Obtendrás no solo una herramienta potente, sino también la comunidad más activa de la República Checa, lista para apoyarte en tu camino hacia el crecimiento profesional. Se reúnen regularmente en [Posobotas |www.posobota.cz], eventos donde intercambian experiencias. ¡Ven a conocernos! + + +Descubre Nette +-------------- + +Únete a miles de desarrolladores satisfechos que ya han descubierto las ventajas de Nette y empieza a escribir código más limpio, seguro y eficiente con este framework único. + +Crea tu primera aplicación [siguiendo el tutorial |quickstart:], paso a paso. Siempre tendrás a mano una [extensa documentación |nette:] y un práctico [resumen de la API |https://api.nette.org]. Visita nuestro [blog lleno de consejos |https://blog.nette.org] y la colección de [complementos y componentes |https://componette.org] que amplían las capacidades de Nette. + +¿Tienes preguntas? Consulta la página de [preguntas frecuentes|nette:troubleshooting] o participa en el [foro checo|https://forum.nette.org/cs/]. ¿Prefieres la formación presencial? Ofrecemos [formación Nette Framework |https://www.skoleniphp.cz/skoleni-nette-vyvoj-webovych-aplikaci] con [excelentes testimonios |https://www.skoleniphp.cz/ohlasy]. + +**Conoce el framework que te mimará, guiará e inspirará.** + + +{{leftbar: @menu-common}} diff --git a/www/es/@home.texy b/www/es/@home.texy index 62623e6560..9ca616555c 100644 --- a/www/es/@home.texy +++ b/www/es/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Nette - Desarrollo cómodo y seguro de aplicaciones web en PHP}} -{{description: Nette es una familia de componentes avanzados y autocontenidos para PHP. Emociónate con ellos. Juntos forman un entramado considerado el tercero más popular del mundo. La filosofía de Nette pone un énfasis extraordinario en la productividad, las mejores prácticas y la seguridad}} +{{maintitle:Nette – Desarrollo web cómodo y seguro en PHP}} +{{description: Nette es una familia de componentes avanzados y utilizables de forma independiente para PHP. Déjese inspirar por ellos. Juntos forman un framework, evaluado como el tercero más popular del mundo. La filosofía de Nette pone un énfasis extraordinario en la productividad, las mejores prácticas y la seguridad.}} diff --git a/www/es/@menu-common.texy b/www/es/@menu-common.texy index ef7eeb059b..edc149d1d1 100644 --- a/www/es/@menu-common.texy +++ b/www/es/@menu-common.texy @@ -1,20 +1,22 @@ Introducción ************ -- [¿Por qué utilizar Nette? |www:10-reasons-why-nette] +- [¿Por qué usar Nette? |www:10-reasons-why-nette] - [Instalación |nette:installation] -- [Cree su primera aplicación |quickstart:] +- [¡Escribamos nuestra primera aplicación! |quickstart:] Temas generales *************** - [Lista de paquetes |www:packages] -- [Mantenimiento y PHP |www:maintenance] -- [Notas de la versión |https://nette.org/releases] -- [Guía de actualización |migrations:en] +- [Mantenimiento y versiones de PHP |www:maintenance] +- [Release Notes |https://nette.org/releases] +- [Migración a versiones más recientes|migrations:en] - [Solución de problemas |nette:troubleshooting] -- [Quién crea Nette |https://nette.org/contributors] -- [Historia de Nette|history] -- [Implíquese |contributing:] -- [Desarrollo del patrocinio |https://nette.org/en/donate] -- [Referencia API |https://api.nette.org] -- [Buenas prácticas |best-practices:] +- [Quién hace Nette |https://nette.org/contributors] +- [Historia de Nette |history] +- [Participa |contributing:] +- [Apoya el desarrollo |https://nette.org/cs/donate] +- [Referencia API |https://api.nette.org/] +- [Tutoriales y mejores prácticas |best-practices:] + +- [La seguridad es lo primero |nette:vulnerability-protection] diff --git a/www/es/donate.texy b/www/es/donate.texy new file mode 100644 index 0000000000..72b092855d --- /dev/null +++ b/www/es/donate.texy @@ -0,0 +1,20 @@ +Apoya el desarrollo de Nette +**************************** + +
                                                                                                                            +Cualquiera que construya sobre Nette tiene interés en que el framework se desarrolle activamente. Que soporte nuevas versiones de PHP. Que se corrijan errores. Que presente nuevas características que faciliten el trabajo o ahorren tiempo y dinero. Que el framework tenga una excelente documentación y que exista contenido útil a su alrededor, ya sea en forma de artículos, tutoriales o vídeos. + +Muchas partes de Nette representan la vanguardia mundial y queremos que siga siendo así. + +Sin una financiación adecuada, nada de esto se puede garantizar. Sin embargo, para poder confiar en que saldrán nuevas versiones, basta con muy poco: que lo apoyes cada mes, aunque sea con una pequeña cantidad de dinero. + +¡Anímate y conviértete en socio de Nette! + +Asegurarás así el funcionamiento saludable del proyecto en el que confías. Y al mismo tiempo, **obtendrás toda una serie de beneficios exclusivos** (ver *Niveles de patrocinio* en la columna de la derecha). Obtendrás acceso a contenido adicional. A soporte técnico. Aumentarás la prioridad de resolución de tus *issues*. Y también darás visibilidad a tu empresa y atraerás desarrolladores. + +¿Cómo te daremos visibilidad? Por ejemplo, mostrando tu logo en este sitio web (es decir, en esta página, en la página de inicio, en la documentación, en el foro y en una [página especial |https://nette.org/partner/vitalita] que puedes enlazar). Tendrás la posibilidad de publicar [ofertas de trabajo |https://forum.nette.org/cs/f30-prace-a-zakazky], anunciarte en el foro ([ejemplo |https://forum.nette.org/cs/30798-problem-s-cizim-klicem-pri-mazani#p198093]) o en la documentación ([ejemplo |https://doc.nette.org/cs/application/components#toc-flash-zpravy]), es decir, en los lugares con el mejor alcance dentro del grupo de desarrolladores de Nette. + +Emitimos facturas a los patrocinadores para que puedan incluir el apoyo en sus gastos, ya sea mensual, trimestral, semestral o anualmente. + +{{include: buttons}} +
                                                                                                                            diff --git a/www/es/history.texy b/www/es/history.texy index 0c1dc26a9d..a85045460a 100644 --- a/www/es/history.texy +++ b/www/es/history.texy @@ -2,35 +2,34 @@ Historia de Nette ***************** .[perex] -Los orígenes de Nette se remontan a 2004, cuando su autor, David Grudl, empezó a buscar un framework adecuado para escribir aplicaciones, ya que PHP puro ya no era suficiente. Ninguna de las soluciones disponibles en ese momento le convencía, por lo que poco a poco comenzó a esbozar las características de un nuevo framework, que más tarde recibió el nombre de Nette. +El origen de Nette se remonta al año 2004, cuando su autor, David Grudl, comenzó a buscar un framework adecuado en el que pudiera escribir aplicaciones, ya que PHP puro ya no era suficiente. Ninguna de las soluciones disponibles en ese momento le satisfacía, por lo que comenzó a esbozar gradualmente las características de un nuevo framework, que más tarde recibiría el nombre de Nette. -Por aquel entonces, aún no existían frameworks actuales como Symfony, Laravel o Ruby on Rails. En el mundo Java, JSF (JavaServer Faces) era el estándar, y en el reino competidor .NET, ASP.NET Webforms era el marco dominante. Ambos permitían crear páginas con componentes de interfaz de usuario reutilizables. David consideraba que sus métodos de abstracción y sus intentos de crear apatridia a través del protocolo HTTP sin estado mediante sesiones o postbacks eran defectuosos y no funcionaban. Causaban muchas dificultades a los usuarios y a los motores de búsqueda. Por ejemplo, si uno guardaba un enlace, se sorprendía al encontrar más tarde un contenido diferente debajo de él. +En aquella época, aún no existían los frameworks actuales como Symfony, Laravel o Ruby on Rails. En el mundo de Java, el estándar era el framework JSF (JavaServer Faces) y en su competidor .NET, ASP.NET Web Forms. Ambos permitían construir páginas utilizando componentes de UI reutilizables. David consideraba que sus métodos de abstracción y los intentos de crear estado sobre el protocolo HTTP sin estado mediante sesiones o el llamado *postback* eran erróneos y fundamentalmente disfuncionales. Causaban muchas dificultades tanto a los usuarios como a los motores de búsqueda. Por ejemplo, si guardabas un enlace, más tarde te encontrabas con sorpresa con un contenido diferente bajo él. -La posibilidad de componer páginas a partir de componentes de interfaz de usuario reutilizables fascinó a David, que la conocía bien de Delphi, una popular herramienta para crear aplicaciones de escritorio en aquella época. Le gustaban los mercados con componentes de código abierto para Delphi. Así que intentó resolver la cuestión de cómo crear un marco de componentes que, a su vez, funcionara en completa armonía con HTTP sin estado. Buscaba un concepto que fuera amigable para el usuario, el SEO y el desarrollador. Y así nació Nette. +La posibilidad misma de componer páginas a partir de componentes de UI reutilizables fascinaba a David; la conocía bien de Delphi, una herramienta popular en ese momento para crear aplicaciones de escritorio. Le gustaban los mercados con componentes de código abierto para Delphi. Por lo tanto, intentó resolver la cuestión de cómo crear un framework de componentes que, al contrario, funcionara en completa armonía con el protocolo HTTP sin estado. Buscaba un concepto que fuera amigable para los usuarios, el SEO y los desarrolladores. Y así comenzó a nacer Nette. .[note] -El nombre Nette surgió por casualidad en el cuarto de baño, cuando el autor vio un bote de gel de afeitar Gillette, girado de forma que sólo se viera la *llette*. +El nombre Nette surgió por casualidad en el baño, cuando el autor vio un bote de gel de afeitar Gillette, girado de tal manera que solo se veía *llette*. -Siguieron miles de horas de investigación, reflexión y reescritura. En un garaje polvoriento de un pueblo de las afueras de Brno se estaban creando los primeros esbozos del futuro marco. La base de la arquitectura era el patrón MVC, utilizado entonces por el ahora olvidado framework PHP Mojavi y popularizado más tarde por el revuelo en torno a Ruby on Rails. Una de las fuentes de inspiración fue incluso el nunca publicado framework phpBase de Honza Tichý. +Siguieron miles de horas de investigación, reflexión y reescritura. En un garaje polvoriento en un pueblo cerca de Brno, surgieron los primeros esbozos del futuro framework. La base de la arquitectura se convirtió en el patrón MVC, que en ese momento utilizaba el ya olvidado framework PHP Mojavi y que más tarde se popularizó gracias al revuelo en torno a Ruby on Rails. Una de las fuentes de inspiración fue incluso el framework nunca publicado phpBase de Honza Tichý. -En el blog del autor empezaron a aparecer artículos sobre el futuro Nette. Se bromeaba diciendo que se trataba de vaporware. Pero entonces, en octubre de 2007, en la conferencia PHP Seminar de Praga, David presentó públicamente Nette. Por cierto, esta conferencia evolucionó hasta convertirse un año después en WebExpo, más tarde una de las mayores conferencias de TI de Europa. Ya entonces Nette presentaba con orgullo una serie de conceptos originales, como el mencionado modelo de componentes, el enrutador bidireccional, la forma específica de enlazar entre presentadores, etc. Tenía formularios, autenticación, caché, etc. Todo ello se sigue utilizando en Nette en su concepto original hasta el día de hoy. +En el blog del autor comenzaron a aparecer artículos sobre el futuro Nette. Se bromeaba diciendo que era *vaporware*. Pero luego, en octubre de 2007, en la conferencia PHP Seminář en Praga, David presentó públicamente Nette. Por cierto, de esta conferencia evolucionó un año después WebExpo, que más tarde se convirtió en una de las conferencias de TI más grandes de Europa. Ya entonces, Nette presumía de una serie de conceptos originales, como el mencionado modelo de componentes, el enrutador bidireccional, una forma específica de enlazar entre *presenters*, etc. Tenía formularios, autenticación resuelta, almacenamiento en caché, etc. Todo en Nette se utiliza en su concepción original hasta el día de hoy. .[note] -Nette utiliza *presentador* en lugar de *controlador* porque supuestamente había demasiadas palabras que empezaban por *con* en el código (controller, front controller, control, config, container, ...). +En Nette, en lugar del término *controller*, se utiliza *presenter*, porque supuestamente había demasiadas palabras en el código que comenzaban con *con* (controller, front controller, control, config, container, ...). -A finales de 2007, David Grudl publicó el código y se lanzó Nette 0.7. A su alrededor se formó una entusiasta comunidad de programadores que empezó a reunirse todos los meses en el evento Posobota. La comunidad incluía a muchas de las luminarias actuales, como Ondrej Mirtes, autor de la gran herramienta PHPStan. El desarrollo de Nette siguió adelante, y en los dos años siguientes se publicaron las versiones 0.8 y 0.9, que sentaron las bases de casi todas las partes actuales del framework. Incluyendo fragmentos de AJAX que preceden en 14 años a Hotwire para Ruby on Rails o Symfony UX Turbo. +A finales de 2007, David Grudl también publicó el código y así vio la luz la versión Nette 0.7. El framework atrajo inmediatamente una enorme atención. Se creó a su alrededor una entusiasta comunidad de programadores que comenzó a reunirse cada mes en el evento Posobota. En la comunidad había muchas personalidades actuales, como Ondřej Mirtes, autor de la excelente herramienta PHPStan. El desarrollo de Nette avanzó rápidamente y en los dos años siguientes se lanzaron las versiones 0.8 y 0.9, donde se sentaron las bases de casi todas las partes actuales del framework. Incluidos los *snippets* AJAX, que se adelantaron 14 años a Hotwire para Ruby on Rails o Symfony UX Turbo. -Pero entonces faltaba algo crucial en Nette. Contenedor de inyección de dependencias (DIC). Nette utilizaba un *service locator* y la intención era pasar a dependecy injection. Pero, ¿cómo diseñar algo así? David Grudl, que por aquel entonces no tenía experiencia con DI, fue a comer con Vasek Purchart, que llevaba medio año utilizando DI. Juntos discutieron el tema y David empezó a trabajar en Nette DI, una biblioteca que revolucionó por completo la forma de pensar en el diseño de aplicaciones. El contenedor DI se convirtió en una de las partes más exitosas del framework. Y dio lugar a dos spin-offs: el formato Neon y la biblioteca Schema. +Sin embargo, faltaba una cosa fundamental en el Nette de entonces. Un contenedor de inyección de dependencias (DIC). Nette utilizaba el llamado *service locator* y la intención era pasar precisamente a la inyección de dependencias. ¿Pero cómo diseñar algo así? David Grudl, que entonces no tenía experiencia con DI, fue a almorzar con Vašek Purchart, que llevaba usando DI aproximadamente medio año. Discutieron juntos el tema y David inició el trabajo en Nette DI, una librería que revolucionó por completo la forma de pensar sobre el diseño de aplicaciones. El contenedor DI se convirtió en una de las partes más exitosas del framework. Y más tarde dio lugar a dos *spin-offs*: el formato Neon y la librería Schema. .[note] -El cambio a la inyección de dependencias llevó mucho tiempo, y esperamos un par de años a una nueva versión de Nette. Por eso, cuando por fin salió, llevaba el número 2. Así que la versión 1 de Nette no existe. +El cambio a la inyección de dependencias requirió bastante tiempo y hubo que esperar unos años para la nueva versión de Nette. Por eso, cuando finalmente salió, llevó directamente el número 2. Por lo tanto, la versión Nette 1 no existe. -Nette comenzó su historia moderna en 2012 con la versión 2.0. También trajo Nette Database, que incluía una herramienta de base de datos extremadamente práctica, ahora llamada Explorer. Esta librería fue programada originalmente por Jakub Vrána, vecino de David Grudl y autor de la popular herramienta Adminer. Jan Škrášek se encargó de su desarrollo durante tres años. +Nette inició su historia moderna en 2012 con la versión 2.0. También trajo Nette Database, que incluía una herramienta extraordinariamente útil para trabajar con bases de datos, hoy llamada Explorer. Esta librería fue programada originalmente por Jakub Vrána, vecino de David Grudl y autor de la popular herramienta Adminer. Jan Škrášek se hizo cargo de su desarrollo posterior durante tres años. -En 2014 se publicó Nette 2.1, a la que siguió en breve Nette 2.2. ¿Cómo es posible? La versión 2.2 era la misma que la 2.1, solo que dividida en veinte paquetes independientes. La herramienta Composer se impuso en el mundo PHP y cambió la forma de pensar sobre la creación de librerías. Nette dejó de ser un monolito y se dividió en partes más pequeñas e independientes. Cada una con su propio repositorio, gestor de incidencias y su propio flujo de desarrollo y versionado. De esta forma, Nette no tiene que pasar por los absurdos habituales en los frameworks monolíticos, en los que sale una nueva versión de un paquete aunque no haya cambiado nada. La división real de los repositorios Git supuso varias semanas de preparación y cientos de horas de máquina. - -Nette también obtuvo un sorprendente tercer puesto en la encuesta mundial sobre el mejor framework PHP organizada por la revista Sitepoint. +En 2014 se lanzó Nette 2.1, seguida en poco tiempo por Nette 2.2. ¿Cómo fue posible? La versión 2.2 era igual que la versión 2.1, solo que dividida en veinte paquetes independientes. En el mundo de PHP, se estableció la herramienta Composer y cambió la forma de concebir la creación de librerías. Nette dejó de ser un monolito y se descompuso en partes más pequeñas e independientes. Cada una con su propio repositorio, *issue tracker* y su propio ritmo de desarrollo y versionado. Así, en Nette no tienen por qué ocurrir absurdos comunes en frameworks monolíticos, donde se lanza una nueva versión de un paquete aunque no haya cambiado absolutamente nada en él. La propia división de los repositorios Git supuso varias semanas de preparación y cientos de horas de tiempo de máquina. +Nette también obtuvo un increíble 3er puesto en la encuesta mundial sobre el mejor framework PHP organizada por la revista Sitepoint. {{toc:no}} diff --git a/www/es/license.texy b/www/es/license.texy new file mode 100644 index 0000000000..73a76c43b2 --- /dev/null +++ b/www/es/license.texy @@ -0,0 +1,44 @@ +Política de licencias +********************* + +Nette Framework se distribuye como software libre para que cualquiera pueda utilizarlo. Puedes elegir si prefieres la licencia [New BSD |#New BSD License] o la [#GNU General Public License (GPL)] en su versión 2 o 3. + +La licencia BSD se recomienda para la mayoría de los proyectos, ya que es fácil de entender y apenas impone restricciones sobre lo que puedes hacer con el framework. Puedes usar Nette también en proyectos comerciales. Sin embargo, si la GPL se adapta mejor a tu proyecto, elígela. No es necesario informar a nadie sobre tu decisión. Conserva siempre los avisos de copyright originales. + +Debes saber que el nombre «Nette Framework» es una marca registrada y su uso tiene ciertas restricciones. Por lo tanto, no utilices «Nette» en el nombre de tu proyecto o dominio de nivel superior; elige mejor un nombre que se sostenga por sí mismo. Si tu proyecto es bueno, no tardará mucho en crearse su propia reputación. + +Si estás satisfecho con Nette Framework, y creemos que lo estarás, puedes [apoyarlo con una contribución |donate]. Gracias. + + +New BSD License +--------------- + +Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com) All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of "Nette Framework" nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +GNU General Public License (GPL) +-------------------------------- + +Los textos de las licencias GPL son muy largos, por lo que aquí se proporcionan enlaces a ellos: + +- GPL version 2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +- GPL version 3: https://www.gnu.org/licenses/gpl-3.0.html + + +{{toc:yes}} +{{priority: -2}} diff --git a/www/es/maintenance.texy b/www/es/maintenance.texy index 54630ecdde..94489bcce7 100644 --- a/www/es/maintenance.texy +++ b/www/es/maintenance.texy @@ -1,27 +1,27 @@ -Mantenimiento y versiones PHP -***************************** +Mantenimiento y compatibilidad con PHP +************************************** .[perex] -Nette es un framework con un periodo de soporte excepcionalmente largo para versiones individuales. Cada rama es una LTS (Long-Term Support Release) con al menos 2 años de soporte. +Nette es un framework con un periodo de soporte excepcionalmente largo para cada versión. Cada rama es una versión LTS (Long-Term Support Release) con soporte durante al menos 2 años. -Cada versión se mantiene activamente durante un periodo de un año (o más) desde su lanzamiento estable inicial. -Los errores críticos y de seguridad se corrigen durante dos años. +Cada versión se mantiene activamente durante un período de un año (o más) desde su lanzamiento estable inicial. Los errores críticos y de seguridad se corrigen durante dos años. -Calendario de versiones (hoja de ruta) -====================================== +Calendario de lanzamientos de Nette +=================================== {{include: doc-roadmap-table}} -Compatibilidad PHP -================== +Compatibilidad con PHP +====================== + +La compatibilidad siempre se aplica a la última versión de cada serie. -La compatibilidad se aplica siempre a la última versión de cada serie. {{include: doc-roadmap-versions}} {{leftbar: @menu-common}} {{toc: no}} -{{description: Lanzamientos de Nette, hoja de ruta, mantenimiento y tablas de compatibilidad con PHP}} +{{description: Versiones de Nette, hoja de ruta, tablas de mantenimiento y compatibilidad con PHP}} diff --git a/www/es/packages.texy b/www/es/packages.texy index d3dd7623ae..ae2279ea62 100644 --- a/www/es/packages.texy +++ b/www/es/packages.texy @@ -1,26 +1,27 @@ -Lista de paquetes -***************** +Lista de paquetes de Nette +************************** -| **Application**:[application:how-it-works] | El núcleo de la aplicación web | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] -| **Bootstrap**:[bootstrap:] | Bootstrap de tu aplicación | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] -| **Caching**:[caching:] | Capa de caché con un conjunto de almacenamientos | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] -| **Component Model**:[component-model:] | Base para sistemas de componentes | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] -| **DI**:[dependency-injection:] | Contenedor de inyección de dependencias | [GitHub |https://github.com/nette/di] [API |https://api.nette.org/di/] +| **Application**:[application:how-it-works] | Núcleo de aplicaciones web | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] +|[**Assets**:[assets:] | Gestión de archivos estáticos [API de |https://api.nette.org/assets/] [GitHub |https://github.com/nette/assets] +| **Bootstrap**:[bootstrap:] | Bootstrap de la aplicación web | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] +| **Caching**:[caching:] | Capa de caché con almacenamientos | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] +| **Component Model**:[component-model:] | Base del sistema de componentes | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] +| **DI**:[dependency-injection:] | Contenedor de Inyección de Dependencias | [GitHub |https://github.com/nette/di] [API |https://api.nette.org/di/] | **Database**:[database:] | Capa de base de datos | [GitHub |https://github.com/nette/database] [API |https://api.nette.org/database/] -| **Forms**:[forms:] | Facilita enormemente los formularios web seguros | [GitHub |https://github.com/nette/forms] [API |https://api.nette.org/forms/] -| **Http**:[http:] | Capa para la petición y respuesta HTTP | [GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] -| **Latte**:[latte:] | Increíble motor de plantillas | [GitHub |https://github.com/nette/latte] [API |https://api.nette.org/latte/] +| **Forms**:[forms:] | Formularios web cómodos y seguros | [GitHub |https://github.com/nette/forms] [API |https://api.nette.org/forms/] +| **Http**:[http:] | Capa que encapsula la petición y respuesta HTTP | [GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] +| **Latte**:[latte:] | Excelente sistema de plantillas | [GitHub |https://github.com/nette/latte] [API |https://api.nette.org/latte/] | **Mail**:[mail:] | Envío de correos electrónicos | [GitHub |https://github.com/nette/mail] [API |https://api.nette.org/mail/] -| **Neon**:[neon:] | Carga y vuelca el formato [NEON |https://ne-on.org] | [GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] -| **PHP Generator**:[php-generator:] | Generador de código PHP | [GitHub |https://github.com/nette/php-generator] [API |https://api.nette.org/php-generator/] -| **Robot Loader**:[robot-loader:] | La carga automática más cómoda | [GitHub |https://github.com/nette/robot-loader] [API |https://api.nette.org/robot-loader/] +| **Neon**:[neon:] | Lectura y escritura del formato [NEON |https://ne-on.org] | [GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] +| **Php Generator**:[php-generator:] | Generador de código PHP | [GitHub |https://github.com/nette/php-generator] [API |https://api.nette.org/php-generator/] +| **Robot Loader**:[robot-loader:] | El autoloading más cómodo | [GitHub |https://github.com/nette/robot-loader] [API |https://api.nette.org/robot-loader/] | **Routing**:[application:routing] | Enrutamiento | [GitHub |https://github.com/nette/routing] [API |https://api.nette.org/routing/] -| **Safe Stream**:[safe-stream:] | Operaciones atómicas seguras con ficheros | [GitHub |https://github.com/nette/safe-stream] [API |https://api.nette.org/safe-stream/] -| **Security**:[security:authentication] | Proporciona sistema de control de acceso | [GitHub |https://github.com/nette/security] [API |https://api.nette.org/security/] +| **Safe Stream**:[safe-stream:] | Operaciones atómicas seguras con archivos | [GitHub |https://github.com/nette/safe-stream] [API |https://api.nette.org/safe-stream/] | **Schema**:[schema:] | Validación de datos de usuario | [GitHub |https://github.com/nette/schema] [API |https://api.nette.org/schema/] -| **Tester**:[tester:] | Pruebas unitarias divertidas en PHP | [GitHub |https://github.com/nette/tester] [API |https://api.nette.org/tester/] +| **Security**:[security:authentication] | Gestión de permisos de acceso | [GitHub |https://github.com/nette/security] [API |https://api.nette.org/security/] +| **Tester**:[tester:] | Pruebas unitarias cómodas en PHP | [GitHub |https://github.com/nette/tester] [API |https://api.nette.org/tester/] | **Tracy**:[tracy:] | Herramienta de depuración que te encantará ♥ | [GitHub |https://github.com/nette/tracy] [API |https://api.nette.org/tracy/] -| **Utils**:[utils:] | Utilidades y clases básicas | [GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] +| **Utils**:[utils:] | Clases y herramientas básicas | [GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] {{leftbar: @menu-common}} {{toc: no}} diff --git a/www/fr/10-reasons-why-nette.texy b/www/fr/10-reasons-why-nette.texy new file mode 100644 index 0000000000..a8d0e1ffb8 --- /dev/null +++ b/www/fr/10-reasons-why-nette.texy @@ -0,0 +1,93 @@ +7 raisons d'utiliser Nette +************************** + +
                                                                                                                            + +Imaginez un framework PHP qui vous permet de vous concentrer sur ce que vous aimez le plus faire. Il vous guide pour écrire du code propre. Il veille lui-même à la sécurité. Arrêtez de rêver et découvrez Nette. Embarquez pour un voyage qui vous ouvrira les portes de nouvelles possibilités de développement. Nous allons vous dire : + +- comment créer des sites web avec un maximum de confort +- comment écrire du code élégant +- ce que signifie la sagesse « moins de code = plus de sécurité » +- comment construire un site web comme un jeu de construction +- et comment faire partie d'une communauté prospère + +
                                                                                                                            + +Nette apporte confort et efficacité au monde des développeurs web grâce à des outils et techniques innovants. Quelles sont les caractéristiques clés qui rendent Nette unique et indispensable dans la boîte à outils du développeur ? Jetons-y un coup d'œil ! + + +#1 Nette vous gâte +------------------ + +Lorsque le framework Nette a commencé à naître il y a vingt ans, tout tournait autour d'un seul objectif : Comment créer des sites web le plus confortablement possible ? Comment rendre le travail des programmeurs aussi agréable que possible ? Comment rendre la création de sites web sexy ? + +Nette a séduit de nombreux programmeurs avec cette approche et a rapidement gagné en popularité. À l'époque, nous appelions cette philosophie Netteway, et aujourd'hui, il existe un terme pour cela, *Developer Experience* (DX). Nette est un framework qui a le DX dans son ADN. Vous ressentirez la différence dans mille et une choses – des détails infimes aux innovations majeures. Laissez le framework vous gâter. + + +#2 Nette vous guide vers un code propre +--------------------------------------- + +Voulez-vous écrire du code propre ? Avoir des applications bien conçues ? Qui ne le voudrait pas ! Et c'est là que le rôle du framework commence. S'il ne donne pas lui-même l'exemple, il est impossible de créer une application parfaitement conçue. + +Nette donne l'exemple. C'est un mentor qui enseigne les bonnes habitudes et l'écriture de code selon des méthodologies éprouvées. En tant que pionnier et évangéliste de l'injection de dépendances, il offre une base de qualité pour des applications maintenables, extensibles et faciles à lire. Nette est conçu pour être compréhensible par les débutants, tout en offrant une profondeur suffisante pour les développeurs expérimentés. + +La communauté autour de Nette a formé de nombreuses personnalités qui sont aujourd'hui à l'origine de projets réussis et importants. Pour des milliers de programmeurs, Nette est devenu un mentor sur leur chemin vers la croissance professionnelle. Rejoignez-nous et découvrez comment Nette influencera positivement la qualité de votre code et de vos applications. + + +#3 Le gardien fiable de vos applications +---------------------------------------- + +Nette protège vos applications. Au fil des ans, il s'est forgé une réputation d'outil qui prend la sécurité extrêmement au sérieux. Il offre une protection sophistiquée contre les vulnérabilités. Il veille toujours à faciliter le travail des programmeurs, mais jamais au détriment de la sécurité. + +Sa devise « moins de code = sécurité suffisante » signifie que les éléments individuels se comportent de manière sécurisée dès le départ. Il n'est pas nécessaire d'activer des fonctionnalités de sécurité en ajoutant du code supplémentaire. Vous ne pouvez donc pas l'oublier ou craindre d'avoir manqué quelque chose. Les programmeurs ne réalisent souvent même pas combien de choses Nette fait pour eux en matière de sécurité, et sont surpris lorsqu'ils l'apprennent. + +Nous vous présentons un framework qui vous guide vers un code propre tout en veillant à la sécurité de vos applications. Nette est un partenaire fiable qui vous permet de vous concentrer sur la création d'excellentes applications web en toute tranquillité d'esprit. + + +#4 Construisez votre site web comme un jeu de construction +---------------------------------------------------------- + +Dans Nette, vous construisez des pages à partir de composants UI réutilisables. Cela rappelle le développement d'applications de bureau, et Nette a transposé avec succès cette approche sur le web. Besoin d'une grille de données dans l'administration ? Il suffit de la trouver sur le marché des composants open source, de l'installer et de l'insérer simplement dans la page. De plus, vous pouvez créer vos propres composants pour les éléments répétitifs sur les pages, éliminant ainsi la duplication et améliorant l'organisation du code. + +Cette caractéristique unique distingue Nette de tous les autres acteurs majeurs du marché. Elle vous permet de créer et de maintenir efficacement des applications web. Avec Nette, travailler avec l'UI devient une expérience fluide et agréable. + + +#5 Un ensemble flexible de paquets +---------------------------------- + +Nette est un ensemble de [paquets utilisables indépendamment |www:packages]. Parmi eux figurent l'[outil de débogage Tracy |tracy:] addictif, le [système de template nouvelle génération Latte |latte:], l'[excellent Conteneur d'Injection de Dépendances |dependency-injection:], les [formulaires |forms:] et bien d'autres. Chaque paquet dispose d'une documentation détaillée et lisible et réside dans un dépôt GitHub distinct. Vous pouvez utiliser les paquets individuellement ou les combiner avec d'autres outils et technologies que vous utilisez déjà. Par exemple, Latte peut être déployé dans WordPress ou Slim Framework, le conteneur DI peut être le cœur d'un framework d'entreprise, et Tracy visualisera les messages d'erreur. + +Ou vous pouvez utiliser Nette dans son ensemble, comme un framework, et créer une application web complète avec lui. Que vous développiez un petit projet personnel ou une application d'entreprise robuste, Nette sera votre partenaire fiable. + + +#6 Stabilité & innovation +------------------------- + +Nette est un framework mature et éprouvé avec une longue histoire. Malgré cela, il reste agile et flexible grâce à une conception intelligente. Les utilisateurs apprécient qu'il s'agisse d'un framework petit et flexible, pas d'un mastodonte. + +Il est toujours prêt à l'avance pour les nouvelles versions de PHP et prend en compte les dernières innovations dans le domaine du développement d'applications web. Ceci est crucial pour minimiser la dette technologique. + +Nette est donc une combinaison de stabilité à long terme et d'innovation, vous permettant de vous fier à une solution éprouvée tout en pouvant utiliser les dernières technologies et tendances. Avec Nette, vous avez l'assurance que votre projet sera construit sur des bases qui suivent constamment le rythme du monde en évolution rapide du développement web. + + +#7 Soyez en meilleure compagnie +------------------------------- + +Nette Framework est populaire auprès des professionnels. Des [entreprises importantes |https://builtwith.nette.org] comme O2, BOSCH, ESET, Zásilkovna, DHL, SUPRAPHON et des centaines d'autres lui font confiance. Il a remporté plusieurs sondages en tant que framework le plus populaire et le plus utilisé en République tchèque. + +Commencez avec Nette et de nouvelles opportunités d'emploi s'ouvriront à vous. Vous obtiendrez non seulement un outil puissant, mais aussi la communauté la plus active de République tchèque, prête à vous soutenir sur votre chemin vers la croissance professionnelle. Elle se réunit régulièrement aux [Posobota |www.posobota.cz], des événements où les expériences sont échangées. Venez nous rencontrer ! + + +Découvrez Nette +--------------- + +Rejoignez les milliers de développeurs satisfaits qui ont déjà découvert les avantages de Nette et commencez à écrire du code plus propre, plus sûr et plus efficace avec ce framework unique. + +Créez votre première application [selon le tutoriel |quickstart:], étape par étape. Vous aurez constamment à portée de main une [documentation complète |nette:] et un [aperçu de l'API |https://api.nette.org] pratique. Visitez notre [blog plein d'astuces |https://blog.nette.org] et la collection d'[extensions et composants |https://componette.org] qui étendent les capacités de Nette. + +Vous avez des questions ? Consultez la page des [questions fréquemment posées |nette:troubleshooting] ou discutez sur le [forum tchèque |https://forum.nette.org/cs/]. Vous préférez une formation en personne ? Nous proposons une [formation Nette Framework |https://www.skoleniphp.cz/skoleni-nette-vyvoj-webovych-aplikaci] avec d'[excellents témoignages |https://www.skoleniphp.cz/ohlasy]. + +**Faites connaissance avec le framework qui vous gâtera, vous guidera et vous inspirera.** + + +{{leftbar: @menu-common}} diff --git a/www/fr/@home.texy b/www/fr/@home.texy index 0959abd0fb..b17fb084c9 100644 --- a/www/fr/@home.texy +++ b/www/fr/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Nette - Développement Web confortable et sûr en PHP}} -{{description: Nette est une famille de composants matures et autonomes pour PHP. Prêt à être séduit ? Ensemble, ils créent un framework qui a été classé comme le 3ème plus populaire au monde. Notre philosophie est de nous concentrer sur la productivité, les meilleures pratiques et la sécurité.}} +{{maintitle:Nette – Développement d'applications web confortable et sécurisé en PHP}} +{{description: Nette est une famille de composants matures et utilisables indépendamment pour PHP. Laissez-vous inspirer par eux. Ensemble, ils forment un framework, classé 3ème plus populaire au monde. La philosophie de Nette met un accent particulier sur la productivité, les meilleures pratiques et la sécurité.}} diff --git a/www/fr/@menu-common.texy b/www/fr/@menu-common.texy index 462b7cbbad..edb861f0df 100644 --- a/www/fr/@menu-common.texy +++ b/www/fr/@menu-common.texy @@ -1,20 +1,22 @@ Introduction ************ - [Pourquoi utiliser Nette ? |www:10-reasons-why-nette] -- [L'installation |nette:installation] -- [Créez votre première application ! |quickstart:] +- [Installation |nette:installation] +- [Écrivons notre première application ! |quickstart:] Sujets généraux *************** - [Liste des paquets |www:packages] -- [Maintenance et PHP |www:maintenance] +- [Maintenance et versions PHP |www:maintenance] - [Notes de version |https://nette.org/releases] -- [Guide de mise à niveau |migrations:en] -- [Dépannage |nette:Troubleshooting] -- [Qui crée Nette |https://nette.org/contributors] +- [Passer aux versions plus récentes|migrations:en] +- [Résolution de problèmes |nette:troubleshooting] +- [Qui fait Nette |https://nette.org/contributors] - [Histoire de Nette |history] -- [S'impliquer |contributing:] -- [Développement du parrainage |https://nette.org/en/donate] -- [Référence API |https://api.nette.org] -- [Meilleures pratiques |best-practices:] +- [Contribuer |contributing:] +- [Soutenir le développement |https://nette.org/fr/donate] +- [Référence API |https://api.nette.org/] +- [Tutoriels et bonnes pratiques |best-practices:] + +- [La sécurité avant tout |nette:vulnerability-protection] diff --git a/www/fr/donate.texy b/www/fr/donate.texy new file mode 100644 index 0000000000..5277be94da --- /dev/null +++ b/www/fr/donate.texy @@ -0,0 +1,20 @@ +Soutenez le développement de Nette +********************************** + +
                                                                                                                            +Tous ceux qui construisent sur Nette ont intérêt à ce que le framework se développe activement. Pour qu'il supporte les nouvelles versions de PHP. Pour que les erreurs soient corrigées. Pour qu'il apporte de nouvelles fonctionnalités qui facilitent le travail ou économisent du temps et de l'argent. Pour que le framework ait une excellente documentation et qu'il existe autour de lui un contenu utile, que ce soit sous forme d'articles, de tutoriels ou de vidéos. + +De nombreuses parties de Nette représentent le sommet mondial et nous voulons qu'il en soit ainsi à l'avenir. + +Sans financement adéquat, rien de tout cela ne peut être assuré. Pourtant, pour pouvoir compter sur la sortie de nouvelles versions, il suffit de peu : que vous le souteniez chaque mois, même avec une petite contribution financière. + +Lancez-vous et devenez partenaire de Nette ! + +Vous assurerez ainsi le bon fonctionnement du projet sur lequel vous comptez. Et en même temps, **vous obtiendrez toute une série d'avantages exclusifs** (voir *Niveaux de partenariat* dans la colonne de droite). Vous aurez accès à du contenu bonus. À un support technique. Vous augmenterez la priorité de résolution de vos issues. Et vous rendrez également votre entreprise visible et attirerez des développeurs. + +Comment allons-nous vous rendre visible ? Par exemple, en affichant votre logo sur ce site (c'est-à-dire sur cette page, sur la page d'accueil, dans la documentation, sur le forum, et sur une [page spéciale |https://nette.org/partner/vitalita] que vous pouvez lier). Vous aurez la possibilité de publier des [offres d'emploi |https://forum.nette.org/cs/f30-prace-a-zakazky], de faire de la publicité sur le forum ([exemple |https://forum.nette.org/cs/30798-problem-s-cizim-klicem-pri-mazani#p198093]) ou dans la documentation ([exemple |https://doc.nette.org/cs/application/components#toc-flash-zpravy]), c'est-à-dire aux endroits ayant la meilleure portée auprès du groupe de développeurs Nette. + +Nous émettons des factures aux partenaires afin qu'ils puissent inclure le soutien dans leurs dépenses, que ce soit mensuellement, trimestriellement, semestriellement ou annuellement. + +{{include: buttons}} +
                                                                                                                            diff --git a/www/fr/history.texy b/www/fr/history.texy index 657ed6e098..3c21662387 100644 --- a/www/fr/history.texy +++ b/www/fr/history.texy @@ -2,35 +2,34 @@ Histoire de Nette ***************** .[perex] -Les origines de Nette remontent à 2004, lorsque son auteur, David Grudl, a commencé à chercher un framework approprié pour écrire des applications, car le PHP pur ne suffisait plus. Aucune des solutions disponibles à l'époque ne lui convenait. Il a donc commencé à esquisser les caractéristiques d'un nouveau framework, qui a ensuite été baptisé Nette. +Les origines de Nette remontent à 2004, lorsque son auteur, David Grudl, a commencé à chercher un framework approprié pour écrire des applications, car le PHP pur ne suffisait plus. Aucune des solutions disponibles à l'époque ne lui convenait, il a donc progressivement esquissé les contours d'un nouveau framework, qui a plus tard reçu le nom de Nette. -À l'époque, les frameworks actuels comme Symfony, Laravel ou Ruby on Rails n'existaient pas encore. Dans le monde Java, JSF (JavaServer Faces) était la norme, et dans le domaine concurrent de .NET, ASP.NET Webforms était le cadre dominant. Tous deux permettaient de construire des pages en utilisant des composants d'interface utilisateur réutilisables. David considérait que leurs méthodes d'abstraction et leurs tentatives de créer l'apatridie sur le protocole HTTP sans état à l'aide de sessions ou de postbacks étaient défectueuses et fondamentalement cassées. Elles causaient de nombreuses difficultés aux utilisateurs et aux moteurs de recherche. Par exemple, si vous sauvegardiez un lien, vous étiez surpris de trouver un contenu différent sous ce lien plus tard. +À cette époque, les frameworks actuels tels que Symfony, Laravel ou même Ruby on Rails n'existaient pas encore. Dans le monde Java, le standard était le framework JSF (JavaServer Faces), et dans le monde concurrent .NET, c'était ASP.NET Webforms. Les deux permettaient de construire des pages à l'aide de composants UI réutilisables. David considérait leurs méthodes d'abstraction et leurs tentatives de créer un état sur le protocole HTTP sans état à l'aide de sessions ou de ce qu'on appelle le postback comme erronées et fondamentalement non fonctionnelles. Elles causaient de nombreuses difficultés aux utilisateurs et aux moteurs de recherche. Par exemple, si vous enregistriez un lien, vous trouviez plus tard avec surprise un contenu différent sous celui-ci. -La possibilité de composer des pages à partir de composants d'interface utilisateur réutilisables a fasciné David, qui la connaissait bien grâce à Delphi, un outil populaire pour la création d'applications de bureau à l'époque. Il aimait les places de marché proposant des composants open source pour Delphi. Il a donc essayé de résoudre la question de savoir comment créer un cadre de composants qui fonctionnerait en parfaite harmonie avec le protocole HTTP sans état. Il cherchait un concept qui serait convivial pour l'utilisateur, le référencement et le développeur. Et c'est ainsi que Nette est né. +La possibilité même de composer des pages à partir de composants UI réutilisables fascinait David, il la connaissait bien de Delphi, un outil alors populaire pour la création d'applications de bureau. Il aimait les marchés de composants open source pour Delphi. Il a donc essayé de résoudre la question de savoir comment créer un framework à composants qui fonctionnerait, au contraire, en parfaite harmonie avec le protocole HTTP sans état. Il cherchait un concept qui serait convivial pour les utilisateurs, le SEO et les développeurs. C'est ainsi que Nette a commencé à naître. .[note] -Le nom de Nette est apparu par hasard dans la salle de bains, lorsque l'auteur a aperçu un flacon de gel à raser Gillette, tourné de manière à ce que seule la *llette* soit visible. +Le nom Nette est né par hasard dans la salle de bain, lorsque l'auteur a aperçu un pot de gel à raser Gillette, tourné de telle sorte que seul *llette* était visible. -Des milliers d'heures de recherche, de réflexion et de réécriture ont suivi. Dans un garage poussiéreux d'un village aux alentours de Brno, les premiers contours du futur cadre étaient en train de se dessiner. La base de l'architecture était le modèle MVC, qui a ensuite été utilisé par le framework PHP Mojavi, aujourd'hui oublié, puis popularisé par le battage médiatique autour de Ruby on Rails. L'une des sources d'inspiration était même le framework phpBase de Honza Tichý, jamais publié. +Ont suivi des milliers d'heures de recherche, de réflexion et de réécriture. Dans un garage poussiéreux d'un village quelque part près de Brno, les premiers contours du futur framework prenaient forme. La base de l'architecture est devenue le modèle MVC, qui était alors utilisé par le framework PHP aujourd'hui oublié Mojavi et qui a ensuite été popularisé grâce au battage médiatique autour de Ruby on Rails. L'une des sources d'inspiration était même le framework jamais publié phpBase de Honza Tichý. -Des articles sur le futur Nette ont commencé à apparaître sur le blog de l'auteur. On plaisantait en disant qu'il s'agissait d'un vaporware. Mais en octobre 2007, lors de la conférence Prague PHP Seminar, David a présenté publiquement Nette. À propos, cette conférence est devenue un an plus tard WebExpo, qui est devenue l'une des plus grandes conférences informatiques d'Europe. À l'époque déjà, Nette présentait fièrement un certain nombre de concepts originaux, tels que le modèle de composants susmentionné, le routeur bidirectionnel, la manière spécifique de créer des liens entre les présentateurs, etc. Il y avait des formulaires, l'authentification, la mise en cache, etc. Tout est encore utilisé dans Nette dans son concept original jusqu'à aujourd'hui. +Sur le blog de l'auteur, des articles sur le futur Nette ont commencé à paraître. On plaisantait en disant qu'il s'agissait d'un vaporware. Puis, en octobre 2007, lors de la conférence PHP Seminář à Prague, David a présenté publiquement Nette. D'ailleurs, de cette conférence est née un an plus tard WebExpo, devenue plus tard l'une des plus grandes conférences informatiques d'Europe. Déjà à l'époque, Nette se vantait de nombreux concepts originaux, tels que le modèle de composants mentionné, le routeur bidirectionnel, une méthode spécifique de liaison entre les presenters, etc. Il avait des formulaires, l'authentification résolue, la mise en cache, etc. Tout dans Nette est utilisé dans sa conception originale jusqu'à aujourd'hui. .[note] -Nette utilise *presenter* au lieu de *controller* car il y avait soi-disant trop de mots commençant par *con* dans le code (controller, front controller, control, config, container, ...). +Dans Nette, le terme *presenter* est utilisé à la place de *controller*, car il y avait apparemment trop de mots commençant par *con* dans le code (controller, front controller, control, config, container, ...) -Fin 2007, David Grudl a publié le code et Nette 0.7 est sorti. Une communauté enthousiaste de programmeurs s'est formée autour de ce projet et a commencé à se réunir tous les mois lors de l'événement Posobota. La communauté comprenait de nombreuses sommités d'aujourd'hui, comme Ondrej Mirtes, auteur du grand outil PHPStan. Le développement de Nette a progressé et, au cours des deux années suivantes, les versions 0.8 et 0.9 ont été publiées, jetant les bases de presque toutes les parties actuelles du framework. Y compris les snippets AJAX qui précèdent de 14 ans Hotwire pour Ruby on Rails ou Symfony UX Turbo. +Fin 2007, David Grudl a également publié le code et la version Nette 0.7 a ainsi vu le jour. Le framework a immédiatement attiré une attention considérable. Une communauté enthousiaste de programmeurs s'est formée autour de lui, qui a commencé à se réunir chaque mois lors de l'événement Posobota. La communauté comptait de nombreuses personnalités d'aujourd'hui, par exemple Ondřej Mirtes, l'auteur du formidable outil PHPStan. Le développement de Nette a progressé rapidement et au cours des deux années suivantes, les versions 0.8 et 0.9 sont sorties, où les bases de presque toutes les parties actuelles du framework ont été posées. Y compris les snippets AJAX, qui ont devancé de 14 ans Hotwire pour Ruby on Rails ou Symfony UX Turbo. -Mais une chose cruciale manquait à Nette à l'époque. Le Dependecy Injection Container (DIC). Nette utilisait un *service locator* et l'intention était de passer à l'injection de dépendances. Mais comment concevoir une telle chose ? David Grudl, qui n'avait aucune expérience de la DI à l'époque, est allé déjeuner avec Vasek Purchart, qui utilisait la DI depuis environ six mois. Ils ont discuté ensemble du sujet et David a commencé à travailler sur Nette DI, une bibliothèque qui a complètement révolutionné notre façon de concevoir les applications. Le conteneur DI est devenu l'une des parties les plus réussies du framework. Il a donné naissance à deux produits dérivés : le format Neon et la bibliothèque Schema. +Cependant, une chose essentielle manquait dans le Nette de l'époque. Le conteneur d'injection de dépendances (DIC). Nette utilisait ce qu'on appelle un *service locator* et l'intention était de passer justement à l'injection de dépendances. Mais comment concevoir une telle chose ? David Grudl, qui n'avait alors aucune expérience avec DI, est allé déjeuner avec Vašek Purchart, qui utilisait DI depuis environ six mois. Ensemble, ils ont discuté du sujet et David a commencé à travailler sur Nette DI, une bibliothèque qui a complètement renversé la façon de penser la conception d'applications. Le conteneur DI est devenu l'une des parties les plus réussies du framework. Et il a donné naissance plus tard à deux spin-offs : le format Neon et la bibliothèque Schema. .[note] -Le passage à l'injection de dépendances a pris beaucoup de temps, et nous avons attendu une nouvelle version de Nette pendant deux ans. C'est pourquoi, lorsqu'elle est finalement sortie, elle portait le numéro 2. La version 1 de Nette n'existe donc pas. +Le passage à l'injection de dépendances a demandé beaucoup de temps et il a fallu attendre quelques années pour une nouvelle version de Nette. C'est pourquoi, lorsqu'elle est enfin sortie, elle portait directement le numéro 2. La version Nette 1 n'existe donc pas. -Nette a commencé son histoire moderne en 2012 avec la version 2.0. Elle a également apporté Nette Database, qui comprenait un outil de base de données extrêmement pratique, maintenant appelé Explorer. Cette bibliothèque a été programmée à l'origine par Jakub Vrána, voisin de David Grudl et auteur du populaire outil Adminer. Son développement a ensuite été repris par Jan Škrášek pendant trois ans. +En 2012, avec la version 2.0, Nette a lancé son histoire moderne. Elle a également apporté Nette Database, qui comprenait un outil extraordinairement pratique pour travailler avec la base de données, aujourd'hui appelé Explorer. Cette bibliothèque avait été initialement programmée par Jakub Vrána, voisin de David Grudl et auteur de l'outil populaire Adminer. Jan Škrášek a ensuite pris en charge son développement ultérieur pendant trois ans. -En 2014, Nette 2.1 est sorti, suivi de peu par Nette 2.2. Comment cela est-il possible ? La version 2.2 était la même que la version 2.1, juste divisée en vingt paquets distincts. L'outil Composer s'est imposé dans le monde PHP et a changé notre façon de concevoir la création de bibliothèques. Nette a cessé d'être un monolithe et s'est divisé en petites parties indépendantes. Chacune d'entre elles dispose de son propre dépôt, de son propre système de suivi des problèmes et de son propre flux de développement et de gestion des versions. De cette façon, Nette n'a pas à passer par les absurdités communes dans les cadres monolithiques, où une nouvelle version d'un paquet sort même si rien n'a changé. La division effective des dépôts Git a nécessité plusieurs semaines de préparation et des centaines d'heures de travail. - -Nette a également obtenu une étonnante 3e place dans le sondage mondial pour le meilleur framework PHP organisé par le magazine Sitepoint. +En 2014, Nette 2.1 est sorti, suivi peu de temps après par Nette 2.2. Comment est-ce possible ? La version 2.2 était identique à la version 2.1, mais divisée en vingt paquets distincts. Dans le monde PHP, l'outil Composer s'était installé et avait changé la façon de concevoir la création de bibliothèques. Nette a ainsi cessé d'être un monolithe et s'est décomposé en parties plus petites et indépendantes. Chacune avec son propre dépôt, son issue tracker et son propre rythme de développement et de versionnement. Ainsi, dans Nette, il n'est pas nécessaire que se produisent les absurdités courantes dans les frameworks monolithiques, où une nouvelle version d'un paquet sort alors que rien n'y a changé du tout. La division elle-même des dépôts Git a nécessité plusieurs semaines de préparation et des centaines d'heures de temps machine. +Nette s'est également classé à une incroyable 3ème place dans le sondage mondial sur le meilleur framework PHP organisé par le magazine Sitepoint. {{toc:no}} diff --git a/www/fr/license.texy b/www/fr/license.texy new file mode 100644 index 0000000000..9cdf42cb17 --- /dev/null +++ b/www/fr/license.texy @@ -0,0 +1,44 @@ +Politique de licence +******************** + +Nette Framework est distribué comme logiciel libre, afin que tout le monde puisse l'utiliser. Vous pouvez choisir si la licence [New BSD |#New BSD License] ou la [#GNU General Public License (GPL)] en version 2 ou 3 vous convient le mieux. + +La licence BSD est recommandée pour la plupart des projets, car elle est facile à comprendre et n'impose presque aucune restriction sur ce que vous pouvez faire avec le framework. Vous pouvez également utiliser Nette dans des projets commerciaux. Cependant, si la GPL convient mieux à votre projet, choisissez-la. Il n'est pas nécessaire d'informer qui que ce soit de votre décision. Conservez toujours les copyrights originaux. + +Vous devez savoir que le nom "Nette Framework" est une marque déposée et que son utilisation est soumise à certaines restrictions. N'utilisez donc pas "Nette" dans le nom de votre projet ou de votre domaine de premier niveau, choisissez plutôt un nom qui reposera sur ses propres fondations. Si votre projet est bon, il ne tardera pas à se forger sa propre réputation. + +Si vous êtes satisfait de Nette Framework, et nous pensons que vous le serez, vous pouvez le [soutenir par une contribution |donate]. Merci. + + +New BSD License +--------------- + +Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com) All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of "Nette Framework" nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +GNU General Public License (GPL) +-------------------------------- + +Les textes des licences GPL sont très longs, c'est pourquoi des liens vers ceux-ci sont fournis ici : + +- GPL version 2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +- GPL version 3: https://www.gnu.org/licenses/gpl-3.0.html + + +{{toc:yes}} +{{priority: -2}} diff --git a/www/fr/maintenance.texy b/www/fr/maintenance.texy index 6f30fbf06e..00345e4851 100644 --- a/www/fr/maintenance.texy +++ b/www/fr/maintenance.texy @@ -1,27 +1,27 @@ -Maintenance et versions de PHP -****************************** +Maintenance et compatibilité avec PHP +************************************* .[perex] -Nette est un framework avec une période de support exceptionnellement longue pour les versions individuelles. Chaque branche est une LTS (Long-Term Support Release) avec au moins 2 ans de support. +Nette est un framework avec une durée de support exceptionnellement longue pour chaque version. Chaque branche est une LTS (Long-Term Support Release) avec un support d'au moins 2 ans. -Chaque version est activement maintenue pendant une période d'un an (ou plus) à partir de sa version stable initiale. -Les bogues critiques et de sécurité sont corrigés pendant deux ans. +Chaque version est activement maintenue pendant une période d'un an (ou plus) à compter de sa sortie stable initiale. Les erreurs critiques et de sécurité sont corrigées pendant deux ans. -Calendrier des versions (feuille de route) .[#toc-release-calendar-roadmap] -=========================================================================== +Calendrier des sorties de Nette +=============================== {{include: doc-roadmap-table}} -Compatibilité avec PHP .[#toc-php-compatibility] -================================================ +Compatibilité avec PHP +====================== La compatibilité s'applique toujours à la dernière version de chaque série. + {{include: doc-roadmap-versions}} {{leftbar: @menu-common}} {{toc: no}} -{{description: Versions de Nette, feuille de route, maintenance et tableaux de compatibilité PHP}} +{{description: Versions de Nette, feuille de route, tableaux de maintenance et compatibilité avec PHP}} diff --git a/www/fr/packages.texy b/www/fr/packages.texy index 670aaef17d..99c919906e 100644 --- a/www/fr/packages.texy +++ b/www/fr/packages.texy @@ -1,26 +1,27 @@ -Liste des paquets -***************** +Liste des paquets Nette +*********************** -| **Application**:[application:how-it-works] | Le noyau de l'application web | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] -| **Bootstrap**:[bootstrap:] | Bootstrap de votre application | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] -| **Caching**:[caching:] | Couche de cache avec un ensemble de stockages | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] -| **Component Model**:[component-model:] | Fondation pour les systèmes de composants | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] -| **DI**:[dependency-injection:] | Conteneur d'injection de dépendances | [GitHub |https://github.com/nette/di] [API |https://api.nette.org/di/] -| **Database**:[database:] | Couche de base de données | [GitHub |https://github.com/nette/database] [API |https://api.nette.org/database/] -| **Forms**:[forms:] | Facilite grandement les formulaires web sécurisés | [GitHub |https://github.com/nette/forms] [API |https://api.nette.org/forms/] -| **Http**:[http:] | Couche pour la requête et la réponse HTTP & response | [GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] -| **Latte**:[latte:] | Incroyable moteur de modèles | [GitHub |https://github.com/nette/latte] [API |https://api.nette.org/latte/] -| **Mail**:[mail:] | Envoi de courriels | [GitHub |https://github.com/nette/mail] [API |https://api.nette.org/mail/] -| **Neon**:[neon:] | Chargement et vidage du [format NEON |https://ne-on.org] | [GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] -| **Php Generator**:[php-generator:] | Générateur de code PHP | [GitHub |https://github.com/nette/php-generator] [API |https://api.nette.org/php-generator/] -| **Robot Loader**:[robot-loader:] | Le plus confortable des autoloaders | [GitHub |https://github.com/nette/robot-loader] [API |https://api.nette.org/robot-loader/] -| **Routing**:[application:routing] | Routage | [GitHub |https://github.com/nette/routing] [API |https://api.nette.org/routing/] -| **Safe Stream**:[safe-stream:] | Opérations atomiques sûres avec les fichiers | [GitHub |https://github.com/nette/safe-stream] [API |https://api.nette.org/safe-stream/] -| **Security**:[security:authentication] | Fournit un système de contrôle d'accès | [GitHub |https://github.com/nette/security] [API |https://api.nette.org/security/] -| **Schema**:[schema:] | Validation des données utilisateur | [GitHub |https://github.com/nette/schema] [API |https://api.nette.org/schema/] -| **Tester**:[tester:] | Appréciez les tests unitaires en PHP | [GitHub |https://github.com/nette/tester] [API |https://api.nette.org/tester/] -| **Tracy**:[tracy:] | Outil de débogage que vous allez adorer ♥ | [GitHub |https://github.com/nette/tracy] [API |https://api.nette.org/tracy/] -| **Utils**:[utils:] | Utilitaires et classes de base | [GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] +| **[Application |application:how-it-works]** | Cœur des applications web | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] +| **Assets**:[assets:] | Gestion des fichiers statiques | [API |https://api.nette.org/assets/] [GitHub |https://github.com/nette/assets] +| **[Bootstrap |bootstrap:]** | Bootstrap de l'application web | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] +| **[Caching |caching:]** | Couche de cache avec stockages | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] +| **[Component Model |component-model:]** | Base du système de composants | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] +| **[DI |dependency-injection:]** | Conteneur d'Injection de Dépendances | [GitHub |https://github.com/nette/di] [API |https://api.nette.org/di/] +| **[Database |database:]** | Couche de base de données | [GitHub |https://github.com/nette/database] [API |https://api.nette.org/database/] +| **[Forms |forms:]** | Formulaires web pratiques et sécurisés | [GitHub |https://github.com/nette/forms] [API |https://api.nette.org/forms/] +| **[Http |http:]** | Couche encapsulant la requête & réponse HTTP | [GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] +| **[Latte |latte:]** | Excellent système de template | [GitHub |https://github.com/nette/latte] [API |https://api.nette.org/latte/] +| **[Mail |mail:]** | Envoi d'e-mails | [GitHub |https://github.com/nette/mail] [API |https://api.nette.org/mail/] +| **[Neon |neon:]** | Lecture et écriture du format [NEON |https://ne-on.org] | [GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] +| **[Php Generator |php-generator:]** | Générateur de code PHP | [GitHub |https://github.com/nette/php-generator] [API |https://api.nette.org/php-generator/] +| **[Robot Loader |robot-loader:]** | Autoloading le plus confortable | [GitHub |https://github.com/nette/robot-loader] [API |https://api.nette.org/robot-loader/] +| **[Routing |application:routing]** | Routage | [GitHub |https://github.com/nette/routing] [API |https://api.nette.org/routing/] +| **[Safe Stream |safe-stream:]** | Opérations atomiques sécurisées avec les fichiers | [GitHub |https://github.com/nette/safe-stream] [API |https://api.nette.org/safe-stream/] +| **[Schema |schema:]** | Validation des données utilisateur | [GitHub |https://github.com/nette/schema] [API |https://api.nette.org/schema/] +| **[Security |security:authentication]** | Gestion des droits d'accès | [GitHub |https://github.com/nette/security] [API |https://api.nette.org/security/] +| **[Tester |tester:]** | Tests unitaires PHP agréables | [GitHub |https://github.com/nette/tester] [API |https://api.nette.org/tester/] +| **[Tracy |tracy:]** | Outil de débogage que vous allez adorer ♥ | [GitHub |https://github.com/nette/tracy] [API |https://api.nette.org/tracy/] +| **[Utils |utils:]** | Classes et outils de base | [GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] {{leftbar: @menu-common}} {{toc: no}} diff --git a/www/hu/10-reasons-why-nette.texy b/www/hu/10-reasons-why-nette.texy index 85fcb7bbb2..9a14ccf554 100644 --- a/www/hu/10-reasons-why-nette.texy +++ b/www/hu/10-reasons-why-nette.texy @@ -1,53 +1,93 @@ -Miért használja a Nette-et? -*************************** +7 ok, amiért érdemes a Nette-t használni +****************************************
                                                                                                                            -Hagyja abba az ismétlődő feladatok megoldását és a részletek elvonását, amelyek unalmassá és termékennyé teszik a programozást. **A Nette Framework segítségével hatékonyabban dolgozhatsz, a fontos dolgokra koncentrálhatsz, és eközben olvashatóbbá és jól strukturáltá válik a kódod**. Néhány funkció a következőkből: +Képzelj el egy PHP keretrendszert, amely lehetővé teszi, hogy arra összpontosíts, amit a legjobban szeretsz csinálni. Tiszta kód írására vezet. Maga gondoskodik a biztonságról. Hagyd abba az álmodozást és ismerd meg a Nette-t. Indulj el egy úton, amely új fejlesztési lehetőségek ajtaját nyitja meg előtted. Elmondjuk: -- kiváló templating rendszer -- verhetetlen diagnosztikai eszközök -- rendkívül hatékony adatbázis-réteg -- sziklaszilárd védelem az ismert sebezhetőségek ellen -- HTML5 és AJAX támogatás, SEO-barát -- jól megírt dokumentáció és aktív nyílt forráskódú közösség -- kiforrott és tiszta objektumorientált tervezés a legújabb PHP funkciók felhasználásával -- ösztönzött, de nem erőltetett, legjobb gyakorlatokat alkalmazó megoldások +- hogyan készíts weboldalakat maximális kényelemmel +- hogyan írj elegáns kódot +- mit jelent a „kevesebb kód = nagyobb biztonság” bölcsesség +- hogyan építs webet építőkészletként +- és hogyan válj egy sikeres közösség részévé
                                                                                                                            -És teljesen ingyenes. Úgy gondoljuk, hogy érdemes kipróbálni. +A Nette kényelmet és hatékonyságot hoz a webfejlesztők világába innovatív eszközök és technikák segítségével. Melyek azok a kulcsfontosságú tulajdonságok, amelyek a Nette-t egyedivé és a fejlesztői készlet nélkülözhetetlen elemévé teszik? Nézzük meg őket! -**A Nette Framework lehetővé teszi, hogy a fejlesztői munka kreatív részére összpontosítson**. Úgy építették fel, hogy rendkívül jól használható, barátságos és öröm legyen használni. Érthető, mégis hatékony szintaxisával, élvonalbeli hibakeresőjével és iparágvezető biztonsági funkcióival minden eddiginél gyorsabban és jobban írhat e-kereskedelmi oldalakat, wikiket, blogokat, CMS-t vagy bármit, amit csak el tud képzelni. +#1 A Nette elkényeztet +---------------------- -A Nette Frameworket olyan [nagyvállalatok használják |https://builtwith.nette.org], mint a T-Systems, a GE Money, a Mladá fronta, a VLTAVA-LABE-PRESS, az Internet Info, a DHL, a Logio, az ESET vagy az Actum. Jelenleg a Cseh Köztársaság korábbi elnökének, Václav Klausnak a weboldalát hajtja. A [Zdroják |https://www.zdrojak.cz/clanky/vysledky-technologie-na-ceskem-webu/] által tartott szavazáson elnyerte A legnépszerűbb és a legszélesebb körben használt keretrendszer díját a Cseh Köztársaságban. +Amikor húsz évvel ezelőtt elkezdett megszületni a Nette keretrendszer, minden egy cél körül forgott: Hogyan lehet a lehető legkényelmesebben weboldalakat készíteni? Hogyan lehet a programozók munkáját a lehető legkellemesebbé tenni? Hogyan lehet a weboldalkészítést szexivé tenni? -A Nette Framework elsajátítása garantálja, hogy nem lesz hiány érdekes állásajánlatokból. +A Nette ezzel a megközelítéssel sok programozót megszólított és gyorsan népszerűvé vált. Akkoriban ezt a filozófiát Netteway-nek neveztük, és ma már létezik rá egy kifejezés, a *Developer Experience* (DX). A Nette egy olyan keretrendszer, amelynek a DX a DNS-ében van. A különbséget ezer és egy dologban fogod érezni – az apróságoktól kezdve az alapvető innovációkig. Hagyd, hogy a keretrendszer elkényeztessen. -Szálljon be .[#toc-get-on-board] --------------------------------- +#2 A Nette tiszta kód írására vezet +----------------------------------- -Kövesse az útmutatót, és lépésről lépésre [készítse el első jelentkezését |quickstart:]. A praktikus [programozói útmutató |@home] bármikor segítségedre lehet, ha pedig mélyebbre szeretnél ásni, nézd meg a praktikus [API dokumentációt |https://api.nette.org/], amely áttekintést ad az összes osztályról és metódusról. +Tiszta kódot szeretnél írni? Jól megtervezett alkalmazásokat? Ki ne szeretne! És pont itt kezdődik a keretrendszer szerepe. Ha maga nem mutat példát, nem lehet kiválóan megtervezett alkalmazást létrehozni. -Ha elakadnál, nézd meg a [gyakran ismételt kérdéseket |nette:troubleshooting] vagy a [hivatalos fórumot |https://forum.nette.org/en/], ahol a tapasztalt felhasználók szívesen segítenek. +A Nette példát mutat. Mentor, aki jó szokásokra és bevált módszertanok szerinti kódírásra tanít. A dependency injection úttörőjeként és evangelistájaként minőségi alapot kínál fenntartható, bővíthető és könnyen olvasható alkalmazásokhoz. A Nette úgy van tervezve, hogy érthető legyen a kezdők számára, ugyanakkor elegendő mélységet kínáljon a tapasztalt fejlesztőknek. -Nem vagy elég elkötelezett ahhoz, hogy magadtól megtanuld? Vettem - [Nette Framework képzést |https://www.skoleniphp.cz/skoleni-nette-vyvoj-webovych-aplikaci] kínálunk (cseh nyelven). A korábbi résztvevők visszajelzései a következő címen olvashatók [skoleniphp.cz/ohlasy |https://www.skoleniphp.cz/ohlasy]. +A Nette körüli közösség számos olyan személyiséget nevelt ki, akik ma sikeres és fontos projektek mögött állnak. Több ezer programozó számára a Nette mentorrá vált szakmai fejlődésük útján. Csatlakozz te is, és fedezd fel, hogyan befolyásolja pozitívan a Nette a kódod és alkalmazásaid minőségét. -További információ .[#toc-more-information] -------------------------------------------- +#3 Alkalmazásaid megbízható őre +------------------------------- -Van egy [blogunk, amely tele van tippekkel |https://blog.nette.org], valamint különböző [kiegészítők és komponensek |https://componette.org] gyűjteménye, amelyeket alkalmazásaiban használhat. +A Nette védi az alkalmazásaidat. Az évek során olyan eszköz hírnevét szerezte meg, amely rendkívül komolyan veszi a biztonságot. Átgondolt védelmet nyújt a sebezhetőségekkel szemben. Mindig ügyel arra, hogy megkönnyítse a programozók munkáját, de soha nem a biztonság rovására. -Ezen kívül a Nette Framework körüli közösség minden hónapban találkozik így egy [Poslední sobota |https://www.posobota.cz] (cseh) nevű eseményen, ahol a tagok megosztják tapasztalataikat és egyszerűen csak jól érzik magukat. Gyere el egy sörre! +A "kevesebb kód = elegendő biztonság" mottója azt jelenti, hogy az egyes elemek már alapból biztonságosan viselkednek. Nincs szükség biztonsági elemek aktiválására további kód írásával. Tehát nem felejtheted el, vagy nem kell attól tartanod, hogy valamit figyelmen kívül hagytál. A programozók gyakran nem is tudják, mennyi biztonsági dolgot végez el helyettük a Nette, és meglepődnek, amikor erről értesülnek. +Bemutatunk egy keretrendszert, amely tiszta kód írására vezet, és egyben őrködik alkalmazásaid biztonsága felett. A Nette megbízható partner, amely lehetővé teszi, hogy a nagyszerű webalkalmazások létrehozására összpontosíts nyugodt lélekkel. -Vegyen részt! .[#toc-get-involved] + +#4 Építsd a webet építőkészletként ---------------------------------- -Minden, a fejlesztéshez való hozzájárulást nagyra értékelünk. Szívesen látjuk, ha megosztja a szájhagyományt, elhelyez egy [ikont |www:en:logo] a weboldalán, vagy anyagilag [támogatja a fejlesztést |www:en:donate]. Köszönjük ♥. +A Nette-ben újrafelhasználható UI komponensekből építed az oldalakat. Ez az asztali alkalmazások fejlesztésére emlékeztet, és a Nette ezt a megközelítést sikeresen átültette a webre. Szükséged van egy datagridre az adminisztrációban? Csak keresd meg az open source komponensek piacterén, telepítsd és egyszerűen illeszd be az oldalba. Ráadásul saját komponenseket is létrehozhatsz az oldalakon ismétlődő elemekhez, ezzel kiküszöbölve a duplikációt és javítva a kód szervezését. + +Ez az egyedi tulajdonság különbözteti meg a Nette-t az összes többi jelentős piaci szereplőtől. Lehetővé teszi a webalkalmazások hatékony létrehozását és karbantartását. A Nette-vel az UI-val való munka zökkenőmentes és kellemes tapasztalattá válik. + + +#5 Rugalmas csomagkészlet +------------------------- + +A Nette [önállóan használható csomagok |www:packages] készlete. Ide tartozik a függőséget okozó [Tracy hibakereső eszköz |tracy:], az [új generációs Latte sablonrendszer |latte:], a [kiváló Dependency Injection Container |dependency-injection:], az [űrlapok |forms:] és még sok más. Minden csomagnak olvasható, részletes dokumentációja van, és külön repozitóriumban található a GitHubon. A csomagokat használhatod önállóan vagy kombinálhatod más, már használt eszközökkel és technológiákkal. Például a Latte telepíthető WordPressbe vagy Slim Frameworkbe, a DI konténer lehet egy vállalati keretrendszer magja, a Tracy pedig vizualizálja a hibaüzeneteket. + +Vagy használhatod a Nette-t egészként, keretrendszerként, és létrehozhatsz benne egy teljes webalkalmazást. Akár egy kis személyes projektet, akár egy robusztus vállalati alkalmazást fejlesztesz, a Nette megbízható partnered lesz. + + +#6 Stabilitás & innováció +------------------------- + +A Nette egy érett és bevált keretrendszer, hosszú történelemmel. Ennek ellenére agilis és rugalmas marad az okos tervezésnek köszönhetően. A felhasználók értékelik, hogy ez egy kicsi és rugalmas keretrendszer, nem pedig egy monstrum. + +Mindig időben felkészül az új PHP verziókra, és figyelembe veszi a webalkalmazás-fejlesztés legújabb innovációit. Ez kulcsfontosságú a technológiai adósság minimalizálásához. + +A Nette tehát a hosszú távú stabilitás és innováció kombinációja, amely lehetővé teszi, hogy megbízz egy bevált megoldásban, miközben kihasználhatod a legújabb technológiákat és trendeket. A Nette-vel biztos lehetsz benne, hogy a projekted olyan alapokra épül, amelyek folyamatosan lépést tartanak a webfejlesztés gyorsan fejlődő világával. + + +#7 Légy a legjobb társaságban +----------------------------- + +A Nette Framework népszerű a profik körében. Olyan [jelentős vállalatok |https://builtwith.nette.org] támaszkodnak rá, mint az O2, BOSCH, ESET, Zásilkovna, DHL, SUPRAPHON és több száz másik. Több felmérésben is győzött, mint a legnépszerűbb és leggyakrabban használt keretrendszer Csehországban. + +Kezdd el a Nette-t, és új munkalehetőségek nyílnak meg előtted. Nemcsak egy erőteljes eszközt kapsz, hanem Csehország legaktívabb közösségét is, amely készen áll támogatni téged szakmai fejlődésed útján. Rendszeresen találkoznak a [Posobota |www.posobota.cz] eseményeken, ahol tapasztalatokat cserélnek. Gyere el és találkozz velünk! + + +Fedezd fel a Nette-t +-------------------- + +Csatlakozz a több ezer elégedett fejlesztőhöz, akik már felfedezték a Nette előnyeit, és kezdj el tisztább, biztonságosabb és hatékonyabb kódot írni ezzel az egyedülálló keretrendszerrel. + +Hozd létre első alkalmazásodat [útmutató szerint |quickstart:], lépésről lépésre. Folyamatosan kéznél lesz a [részletes dokumentáció |nette:] és a praktikus [API áttekintés |https://api.nette.org]. Látogass el a [tippekkel teli blogunkra |https://blog.nette.org] és a [kiegészítők és komponensek |https://componette.org] gyűjteményébe, amelyek bővítik a Nette képességeit. + +Kérdéseid vannak? Fordulj a [gyakran ismételt kérdésekkel|nette:troubleshooting] foglalkozó oldalhoz, vagy vitasd meg a [cseh fórumon|https://forum.nette.org/cs/]. Személyes képzést részesítesz előnyben? Kínálunk [Nette Framework képzést |https://www.skoleniphp.cz/skoleni-nette-vyvoj-webovych-aplikaci] [kiváló visszajelzésekkel |https://www.skoleniphp.cz/ohlasy]. + +**Ismerkedj meg a keretrendszerrel, amely elkényeztet, vezet és inspirál.** {{leftbar: @menu-common}} diff --git a/www/hu/@home.texy b/www/hu/@home.texy index e0def4ac12..ea8af6ae42 100644 --- a/www/hu/@home.texy +++ b/www/hu/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Nette - Kényelmes és biztonságos webfejlesztés PHP nyelven}} -{{description: A Nette egy kiforrott és önálló komponensekből álló család a PHP számára. Készen állsz, hogy elvarázsoljon? Együtt egy olyan keretrendszert hoznak létre, amelyet a 3. legnépszerűbbnek minősítettek a világon. Filozófiánk a termelékenységre, a legjobb gyakorlatokra és a biztonságra összpontosít.}} +{{maintitle:Nette – Kényelmes és biztonságos webalkalmazás-fejlesztés PHP-ban}} +{{description: A Nette egy fejlett és önállóan használható PHP komponensekből álló család. Hagyja, hogy lenyűgözzék. Együtt alkotnak egy keretrendszert, amelyet a világ 3. legnépszerűbbjeként értékeltek. A Nette filozófiája rendkívüli hangsúlyt fektet a termelékenységre, a legjobb gyakorlatokra és a biztonságra.}} diff --git a/www/hu/@menu-common.texy b/www/hu/@menu-common.texy index 8c27e1a26b..b0c976258c 100644 --- a/www/hu/@menu-common.texy +++ b/www/hu/@menu-common.texy @@ -1,20 +1,22 @@ -Bevezetés -********* -- [Miért használja a Nette-et? |www:10-reasons-why-nette] +Ismerkedés +********** +- [Miért használja a Nette-t? |www:10-reasons-why-nette] - [Telepítés |nette:installation] -- [Készítse el első alkalmazását! |quickstart:] +- [Írjuk meg az első alkalmazást! |quickstart:] Általános témák *************** - [Csomagok listája |www:packages] -- [Karbantartás és PHP |www:maintenance] +- [Karbantartás és PHP verziók |www:maintenance] - [Kiadási megjegyzések |https://nette.org/releases] -- [Frissítési útmutató |migrations:en] -- [Hibaelhárítás |nette:Troubleshooting] -- [Ki hozza létre a Nette-et |https://nette.org/contributors] +- [Frissítés újabb verziókra|migrations:en] +- [Hibaelhárítás |nette:troubleshooting] +- [Ki alkotja a Nette-t |https://nette.org/contributors] - [A Nette története |history] -- [Vegyen részt |contributing:] -- [Szponzorfejlesztés |https://nette.org/en/donate] -- [API hivatkozás |https://api.nette.org] -- [Legjobb gyakorlatok |best-practices:] +- [Csatlakozzon |contributing:] +- [Támogassa a fejlesztést |https://nette.org/cs/donate] +- [API referencia |https://api.nette.org/] +- [Útmutatók és eljárások |best-practices:] + +- [Biztonság mindenekelőtt |nette:vulnerability-protection] diff --git a/www/hu/donate.texy b/www/hu/donate.texy new file mode 100644 index 0000000000..0564818610 --- /dev/null +++ b/www/hu/donate.texy @@ -0,0 +1,20 @@ +Támogassa a Nette fejlesztését +****************************** + +
                                                                                                                            +Mindenki, aki a Nette-re épít, érdekelt abban, hogy a keretrendszer aktívan fejlődjön. Hogy támogassa az új PHP verziókat. Hogy a hibák javításra kerüljenek. Hogy további újdonságokkal álljon elő, amelyek megkönnyítik a munkát vagy időt és pénzt takarítanak meg. Hogy a keretrendszernek kiváló dokumentációja legyen, és hasznos tartalom létezzen körülötte, legyen az cikkek, útmutatók vagy videók formájában. + +A Nette számos része világszínvonalú, és szeretnénk, ha ez így is maradna. + +Megfelelő finanszírozás nélkül mindez nem biztosítható. Pedig ahhoz, hogy megbízhass abban, hogy további verziók jelennek meg, elég kevés kell: hogy minden hónapban támogasd akár csak egy kis pénzösszeggel. + +Csatlakozz és válj a Nette partnerévé! + +Ezzel biztosítod annak a projektnek az egészséges működését, amelyre támaszkodsz. És egyúttal **számos exkluzív előnyhöz jutsz** (lásd *Partnerségi szintek* a jobb oldali oszlopban). Hozzáférést kapsz bónusz tartalmakhoz. Technikai támogatáshoz. Növeled az issue-id megoldásának prioritását. És láthatóvá teszed a vállalatodat, vonzva ezzel a fejlesztőket. + +Hogyan teszünk láthatóvá? Például a logód elhelyezésével ezen a weboldalon (azaz ezen az oldalon, a főoldalon, a dokumentációban, a fórumon és egy [különleges oldalon |https://nette.org/partner/vitalita], amelyet hivatkozhatsz). Lehetőséged lesz [állásajánlatokat |https://forum.nette.org/cs/f30-prace-a-zakazky] közzétenni, hirdetni a fórumon ([példa |https://forum.nette.org/cs/30798-problem-s-cizim-klicem-pri-mazani#p198093]) vagy a dokumentációban ([példa |https://doc.nette.org/cs/application/components#toc-flash-zpravy]), azaz azokon a helyeken, amelyek a legjobb elérést biztosítják a Nette fejlesztők csoportjában. + +A partnereknek számlát állítunk ki, hogy a támogatást költségként elszámolhassák, havonta, negyedévente, félévente vagy évente. + +{{include: buttons}} +
                                                                                                                            diff --git a/www/hu/history.texy b/www/hu/history.texy index c406f22ae5..aa92082fb8 100644 --- a/www/hu/history.texy +++ b/www/hu/history.texy @@ -1,36 +1,35 @@ -Nette története -*************** +A Nette története +***************** .[perex] -A Nette eredete 2004-re nyúlik vissza, amikor szerzője, David Grudl elkezdett keresni egy megfelelő keretrendszert, amelyben alkalmazásokat írhat, mivel a tiszta PHP már nem volt elegendő. Az akkoriban elérhető megoldások egyike sem felelt meg neki, ezért fokozatosan elkezdte felvázolni egy új keretrendszer jellemzőit, amely később a Nette nevet kapta. +A Nette keletkezésének kezdete 2004-re nyúlik vissza, amikor szerzője, David Grudl elkezdett keresni egy megfelelő keretrendszert, amelyben alkalmazásokat írhatott volna, mivel a tiszta PHP ehhez már nem volt elegendő. Egyik akkori elérhető megoldás sem felelt meg neki, így fokozatosan elkezdte felvázolni egy új keretrendszer körvonalait, amely később a Nette nevet kapta. -Akkoriban még nem léteztek az olyan jelenlegi keretrendszerek, mint a Symfony, a Laravel vagy a Ruby on Rails. A Java világában a JSF (JavaServer Faces) volt a szabvány, a konkurens .NET birodalomban pedig az ASP.NET Webforms volt az uralkodó keretrendszer. Mindkettő lehetővé tette az oldalak építését újrafelhasználható UI-komponensek felhasználásával. David hibásnak és alapvetően elrontottnak tartotta absztrakciós módszereiket és a stateless HTTP protokollon keresztül, munkamenetek vagy postbackek segítségével történő állapotmentesség létrehozására tett kísérleteiket. Számos nehézséget okoztak a felhasználók és a keresőmotorok számára. Ha például elmentettünk egy linket, meglepődtünk, hogy később más tartalmat találtunk alatta. +Abban az időben még nem léteztek a mai keretrendszerek, mint a Symfony, a Laravel vagy akár a Ruby on Rails. A Java világában a JSF (JavaServer Faces) keretrendszer volt a standard, a konkurens .NET-ben pedig az ASP.NET Webforms. Mindkettő lehetővé tette az oldalak újrafelhasználható UI komponensek segítségével történő építését. Az absztrakciós módszereiket és az állapotosság létrehozására tett kísérleteiket az állapot nélküli HTTP protokoll felett session vagy ún. postback segítségével David hibásnak és alapvetően működésképtelennek tartotta. Számos nehézséget okoztak a felhasználóknak és a keresőmotoroknak is. Például, ha elmentett egy linket, később meglepődve más tartalmat talált alatta. -Az oldalak újrafelhasználható UI-komponensekből való összeállításának lehetősége lenyűgözte Davidet, aki ezt jól ismerte az akkoriban az asztali alkalmazások építésére használt népszerű Delphiből. Megtetszettek neki a Delphi nyílt forráskódú komponenseit tartalmazó piacterek. Ezért megpróbálta megoldani azt a kérdést, hogyan lehetne olyan komponens-keretet létrehozni, amely viszont teljes összhangban működne a stateless HTTP-vel. Olyan koncepciót keresett, amely felhasználó-, SEO- és fejlesztőbarát. Így született meg a Nette. +Maga az oldalak újrafelhasználható UI komponensekből való összeállításának lehetősége lenyűgözte Davidot, jól ismerte a Delphiből, amely akkoriban népszerű eszköz volt az asztali alkalmazások készítéséhez. Tetszettek neki a Delphihez készült nyílt forráskódú komponensek piacterei. Ezért megpróbálta megoldani azt a kérdést, hogyan lehet létrehozni egy olyan komponens keretrendszert, amely viszont tökéletes összhangban működik az állapot nélküli HTTP-vel. Olyan koncepciót keresett, amely barátságos a felhasználók, a SEO és a fejlesztők számára. És így kezdett megszületni a Nette. .[note] -A Nette név véletlenül jött létre a fürdőszobában, amikor a szerző megpillantott egy üveg Gillette borotvagélt, amelyet úgy forgattak el, hogy csak a *llette* látszott. +A Nette név véletlenül született a fürdőszobában, amikor a szerző meglátott egy Gillette borotvahabos flakont, úgy elfordítva, hogy csak a *llette* volt látható. -Több ezer óra kutatás, gondolkodás és újraírás következett. Egy poros garázsban, egy Brno melletti faluban, valahol Brno mellett, a jövőbeli keret első körvonalai készültek. Az architektúra alapja az MVC minta volt, amelyet akkoriban a mára már elfeledett PHP-keretrendszer, a Mojavi használt, majd később a Ruby on Rails körüli felhajtás népszerűsített. Az egyik inspiráló forrás még Honza Tichý soha meg nem jelent phpBase keretrendszere volt. +Ezt több ezer óra kutatás, gondolkodás és átírás követte. Egy poros garázsban, egy Brnón túli faluban születtek meg a jövőbeli keretrendszer első körvonalai. Az architektúra alapja az MVC minta lett, amelyet akkoriban a mára már elfeledett Mojavi PHP keretrendszer használt, és később a Ruby on Rails körüli felhajtásnak köszönhetően vált népszerűvé. Az egyik inspirációs forrás még Honza Tichý soha nem publikált phpBase keretrendszere is volt. -A készülő Nette-ről cikkek kezdtek megjelenni a szerző blogján. Viccelődtek, hogy vaporware-ről van szó. De aztán 2007 októberében, a prágai PHP Seminar konferencián David nyilvánosan bemutatta a Nette-et. Ebből a konferenciából egyébként egy évvel később WebExpo lett, amely később Európa egyik legnagyobb informatikai konferenciája lett. A Nette már akkor is büszkén mutatott be számos eredeti koncepciót, például a már említett komponensmodellt, a kétirányú útválasztót, az előadók közötti kapcsolat sajátos módját stb. Voltak űrlapok, hitelesítés, gyorsítótárazás stb. A Nette-ben a mai napig mindent az eredeti koncepcióban használnak. +A szerző blogján cikkek kezdtek megjelenni a készülő Nette-ről. Viccelődtek, hogy ez vaporware. Aztán 2007 októberében a prágai PHP Seminář konferencián David nyilvánosan bemutatta a Nette-t. Mellesleg, ebből a konferenciából egy évvel később fejlődött ki a WebExpo, később Európa egyik legnagyobb IT konferenciája. Már akkor is a Nette számos eredeti koncepcióval büszkélkedhetett, mint az említett komponens modell, a kétirányú router, a presenterek közötti specifikus hivatkozási mód stb. Voltak űrlapjai, megoldott hitelesítése, gyorsítótárazása stb. Mindent a Nette-ben az eredeti felfogás szerint használnak a mai napig. .[note] -A Nette a *presenter*-t használja a *controller* helyett, mert állítólag túl sok *con* kezdetű szó volt a kódban (controller, front controller, control, config, container, ...). +A Nette-ben a *controller* helyett a *presenter* kifejezést használják, mert állítólag túl sok *con*-nal kezdődő szó volt a kódban (controller, front controller, control, config, container, ...) -2007 végén David Grudl publikálta a kódot, és megjelent a Nette 0.7-es verziója. Egy lelkes programozói közösség alakult ki körülötte, és elkezdtek havonta találkozni a Posobota rendezvényen. A közösségben sok mai nagyágyú is helyet kapott, mint például Ondrej Mirtes, a nagyszerű PHPStan eszköz szerzője. A Nette fejlesztése előrehaladt, és a következő két évben megjelent a 0.8-as és a 0.9-es verzió, amelyek megalapozták a keretrendszer szinte minden mai részét. Beleértve az AJAX szeleteket, amelyek 14 évvel megelőzték a Hotwire for Ruby on Rails vagy a Symfony UX Turbo-t. +2007 végén David Grudl közzétette a kódot is, és így napvilágot látott a Nette 0.7 verziója. A keretrendszer azonnal óriási figyelmet keltett. Lelkes programozói közösség alakult ki körülötte, amely minden hónapban elkezdett találkozni a Posobota nevű eseményen. A közösségben számos mai személyiség volt, például Ondřej Mirtes, a nagyszerű PHPStan eszköz szerzője. A Nette fejlesztése előrehaladt, és a következő két évben megjelent a 0.8 és 0.9 verzió, ahol lefektették a keretrendszer szinte minden mai részének alapjait. Beleértve az AJAX snippetteket, amelyek 14 évvel megelőzték a Hotwire-t a Ruby on Railshez vagy a Symfony UX Turbo-t. -De egy döntő dolog hiányzott akkoriban a Nette-ből. A Dependecy injection container (DIC). A Nette egy *service locator*-t használt, és a dependecy injection-re akartak áttérni. De hogyan tervezzünk ilyet? David Grudl, akinek akkoriban még nem volt tapasztalata a DI-vel, elment ebédelni Vasek Purcharttal, aki már körülbelül fél éve használta a DI-t. Együtt megvitatták a témát, és David elkezdett dolgozni a Nette DI-n, egy olyan könyvtáron, amely teljesen forradalmasította az alkalmazás-tervezésről való gondolkodásunkat. A DI konténer a keretrendszer egyik legsikeresebb része lett. És két mellékágat is eredményezett: a Neon formátumot és a Schema könyvtárat. +Egy alapvető dolog azonban hiányzott az akkori Nette-ből. A Dependecy injection container (DIC). A Nette ún. *service locator*-t használt, és a szándék az volt, hogy áttérjenek a dependency injectionre. De hogyan tervezzenek meg egy ilyen dolgot? David Grudl, akinek akkoriban nem volt tapasztalata a DI-vel, elment ebédelni Vašek Purcharttal, aki körülbelül fél éve használta a DI-t. Közösen megvitatták a témát, és David megkezdte a munkát a Nette DI-n, egy könyvtáron, amely teljesen megváltoztatta az alkalmazástervezésről való gondolkodás módját. A DI konténer a keretrendszer egyik legsikeresebb részévé vált. És később két spin-offot is eredményezett: a Neon formátumot és a Schema könyvtárat. .[note] -A függőségi injektálásra való áttérés sok időt vett igénybe, és néhány évet vártunk a Nette új verziójára. Ezért, amikor végül megjelent, a 2. számot kapta. Tehát a Nette 1. verziója nem létezik. +A dependency injectionre való áttérés sok időt igényelt, és az új Nette verzióra pár évet kellett várni. Ezért, amikor végre megjelent, rögtön a 2-es számot viselte. Tehát a Nette 1-es verzió nem létezik. -A Nette modern története 2012-ben kezdődött a 2.0-s verzióval. Ez hozta el a Nette Database-t is, amely egy rendkívül praktikus adatbázis-kezelő eszközt tartalmazott, amelyet most Explorer-nek hívnak. Ezt a könyvtárat eredetileg Jakub Vrána, David Grudl szomszédja és a népszerű Adminer eszköz szerzője programozta. A további fejlesztését aztán három évig Jan Škrášek vette át. +A Nette 2012-ben a 2.0-s verzióval indította el modern történetét. Elhozta a Nette Database-t is, amelynek része volt egy rendkívül ügyes eszköz az adatbázissal való munkához, ma Explorer néven ismert. Ezt a könyvtárat eredetileg Jakub Vrána programozta, David Grudl szomszédja és a népszerű Adminer eszköz szerzője. További fejlesztését ezután három évig Jan Škrášek vette át. -2014-ben jelent meg a Nette 2.1, amelyet nem sokkal később a Nette 2.2 követett. Hogyan lehetséges ez? A 2.2-es verzió ugyanaz volt, mint a 2.1-es verzió, csak húsz külön csomagra osztva. A Composer eszköz meghódította a PHP világát, és megváltoztatta a könyvtárkészítésről való gondolkodásunkat. A Nette megszűnt monolit lenni, és kisebb független részekre bomlott. Mindegyiknek saját tárolóhelye, problémakövetője és saját fejlesztési és verziókezelési folyamata volt. Így a Nette-nek nem kell átesnie a monolitikus keretrendszereknél megszokott abszurditásokon, amikor egy csomag új verziója jön ki, holott semmi sem változott. A Git-tárházak tényleges felosztása több hetes előkészületet és több száz óra gépi időt vett igénybe. - -A Nette a Sitepoint magazin által szervezett, a legjobb PHP-keretrendszerről szóló globális szavazáson is elképesztő 3. helyezést ért el. +2014-ben jelent meg a Nette 2.1, amelyet rövid időn belül követett a Nette 2.2. Hogyan lehetséges ez? A 2.2-es verzió ugyanaz volt, mint a 2.1-es, csak húsz különálló csomagra osztva. A PHP világában elterjedt a Composer eszköz, és megváltoztatta a könyvtárak létrehozásának módját. A Nette így megszűnt monolit lenni, és kisebb, független részekre bomlott. Mindegyik saját repozitóriummal, issue trackerrel és saját fejlesztési ütemmel és verziózással. Így a Nette-ben nem kell olyan abszurditásoknak előfordulniuk, amelyek a monolitikus keretrendszerekben gyakoriak, amikor egy csomag új verziója jelenik meg, annak ellenére, hogy semmi sem változott benne. Maga a Git repozitóriumok szétválasztása több hét előkészületet és több száz óra gépidőt igényelt. +A Nette emellett lenyűgöző 3. helyezést ért el a Sitepoint magazin által rendezett világméretű PHP keretrendszer szavazáson. {{toc:no}} diff --git a/www/hu/license.texy b/www/hu/license.texy new file mode 100644 index 0000000000..f3f61873bb --- /dev/null +++ b/www/hu/license.texy @@ -0,0 +1,44 @@ +Licencpolitika +************** + +A Nette keretrendszer szabad szoftverként terjesztett, hogy bárki használhassa. Választhat, hogy az [Új BSD |#New BSD License] vagy a [#GNU General Public License (GPL)] 2-es vagy 3-as verziója felel-e meg jobban Önnek. + +A BSD licenc a legtöbb projekthez ajánlott, mivel könnyen érthető, és szinte semmilyen korlátozást nem szab arra vonatkozóan, hogy mit tehet a keretrendszerrel. A Nette-t kereskedelmi projektekben is használhatja. Ha azonban a GPL jobban illik a projektjéhez, válassza azt. Nem szükséges senkit tájékoztatni arról, hogyan döntött. Mindig őrizze meg az eredeti szerzői jogokat. + +Tudnia kell, hogy a "Nette Framework" név védett védjegy, és használata bizonyos korlátozásokkal jár. Ne használja tehát a "Nette" nevet a projektje vagy legfelső szintű domainje nevében, válasszon inkább olyan nevet, amely saját alapokon áll. Ha a projektje jó, nem tart sokáig, és kialakítja a saját hírnevét. + +Ha elégedett lesz a Nette keretrendszerrel, és hisszük, hogy igen, [támogathatja hozzájárulással |donate]. Köszönjük. + + +New BSD License +--------------- + +Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com) All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of "Nette Framework" nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +GNU General Public License (GPL) +-------------------------------- + +A GPL licencek szövegei nagyon hosszúak, ezért itt csak a rájuk mutató linkek találhatók: + +- GPL version 2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +- GPL version 3: https://www.gnu.org/licenses/gpl-3.0.html + + +{{toc:yes}} +{{priority: -2}} diff --git a/www/hu/maintenance.texy b/www/hu/maintenance.texy index c382e237a9..ca9b2bf982 100644 --- a/www/hu/maintenance.texy +++ b/www/hu/maintenance.texy @@ -1,27 +1,27 @@ -Karbantartás és PHP verziók -*************************** +Karbantartás és PHP kompatibilitás +********************************** .[perex] -A Nette egy olyan keretrendszer, amelynek egyes kiadásai kivételesen hosszú támogatási időszakkal rendelkeznek. Minden ág egy LTS (Long-Term Support Release), legalább 2 éves támogatással. +A Nette egy olyan keretrendszer, amely rendkívül hosszú támogatási időt biztosít az egyes kiadásokhoz. Minden ág LTS (Long-Term Support Release), legalább 2 év támogatással. -Minden egyes változatot a kezdeti stabil kiadástól számított egy évig (vagy tovább) aktívan karbantartanak. -A kritikus és biztonsági hibákat két évig javítják. +Minden verziót aktívan karbantartanak a kezdeti stabil kiadástól számított egy évig (vagy akár tovább). A kritikus és biztonsági hibákat két évig javítják. -Kiadási naptár (ütemterv) .[#toc-release-calendar-roadmap] -========================================================== +Nette kiadási ütemterv +====================== {{include: doc-roadmap-table}} -PHP kompatibilitás .[#toc-php-compatibility] -============================================ +PHP kompatibilitás +================== A kompatibilitás mindig az egyes sorozatok legújabb kiadására vonatkozik. + {{include: doc-roadmap-versions}} {{leftbar: @menu-common}} {{toc: no}} -{{description: Nette kiadások, ütemterv, karbantartás és PHP kompatibilitási táblázatok}} +{{description: Nette kiadások, ütemterv, karbantartási táblázatok és PHP kompatibilitás}} diff --git a/www/hu/packages.texy b/www/hu/packages.texy index 0c3c99582a..a7b9279ea5 100644 --- a/www/hu/packages.texy +++ b/www/hu/packages.texy @@ -1,26 +1,27 @@ -Csomagok listája -**************** +Nette csomagok listája +********************** -| **Application**:[application:how-it-works] | A webes alkalmazás magja | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] -| **Bootstrap**:[bootstrap:] | Az alkalmazás bootstrapje | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] -| **Caching**:[caching:] | Cache réteg tárolókészlettel | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] -| **ComponentModel**:[component-model:] | Komponensrendszerek alapja | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] +| **Application**:[application:how-it-works] | Webalkalmazások magja | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] +| **Assets**:[assets:] | Statikus fájlkezelés | [GitHub |https://github.com/nette/assets] [API |https://api.nette.org/assets/] +| **Bootstrap**:[bootstrap:] | Webalkalmazás bootstrap | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] +| **Caching**:[caching:] | Gyorsítótár réteg tárolókkal | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] +| **Component Model**:[component-model:] | Komponensrendszer alapja | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] | **DI**:[dependency-injection:] | Dependency Injection Container | [GitHub |https://github.com/nette/di] [API |https://api.nette.org/di/] | **Database**:[database:] | Adatbázis réteg | [GitHub |https://github.com/nette/database] [API |https://api.nette.org/database/] -| **Forms**:[forms:] | Nagymértékben megkönnyíti a biztonságos webes űrlapokat | [GitHub |https://github.com/nette/forms] [API |https://api.nette.org/forms/] -| **Http**:[http:] | HTTP-kérelem és -válasz réteg | [GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] -| **Latte**:[latte:] | Csodálatos sablonmotor | [GitHub |https://github.com/nette/latte] [API |https://api.nette.org/latte/] -| **Mail**:[mail:] | E-mail küldése | [GitHub |https://github.com/nette/mail] [API |https://api.nette.org/mail/] -| **Neon**:[neon:] | [NEON formátum |https://ne-on.org] betöltése és kitöltése | [GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] +| **Forms**:[forms:] | Kényelmes és biztonságos webes űrlapok | [GitHub |https://github.com/nette/forms] [API |https://api.nette.org/forms/] +| **Http**:[http:] | HTTP kérést és választ beágyazó réteg | [GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] +| **Latte**:[latte:] | Nagyszerű sablonrendszer | [GitHub |https://github.com/nette/latte] [API |https://api.nette.org/latte/] +| **Mail**:[mail:] | E-mailek küldése | [GitHub |https://github.com/nette/mail] [API |https://api.nette.org/mail/] +| **Neon**:[neon:] | [NEON |https://ne-on.org] formátum olvasása és írása | [GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] | **Php Generator**:[php-generator:] | PHP kódgenerátor | [GitHub |https://github.com/nette/php-generator] [API |https://api.nette.org/php-generator/] -| **Robot Loader**:[robot-loader:] | A legkényelmesebb automatikus betöltés | [GitHub |https://github.com/nette/robot-loader] [API |https://api.nette.org/robot-loader/] -| **Routing**:[application:routing] | Routing | [GitHub |https://github.com/nette/routing] [API |https://api.nette.org/routing/] -| **Safe Stream**:[safe-stream:] | Biztonságos atomi műveletek fájlokkal | [GitHub |https://github.com/nette/safe-stream] [API |https://api.nette.org/safe-stream/] -| **Security**:[security:authentication] | Hozzáférés-ellenőrzési rendszert biztosít | [GitHub |https://github.com/nette/security] [API |https://api.nette.org/security/] +| **Robot Loader**:[robot-loader:] | A legkényelmesebb autoloading | [GitHub |https://github.com/nette/robot-loader] [API |https://api.nette.org/robot-loader/] +| **Routing**:[application:routing] | Útválasztás | [GitHub |https://github.com/nette/routing] [API |https://api.nette.org/routing/] +| **Safe Stream**:[safe-stream:] | Biztonságos atomi fájlműveletek | [GitHub |https://github.com/nette/safe-stream] [API |https://api.nette.org/safe-stream/] | **Schema**:[schema:] | Felhasználói adatok validálása | [GitHub |https://github.com/nette/schema] [API |https://api.nette.org/schema/] -| **Tester**:[tester:] | Élvezhető egységtesztelés PHP-ben | [GitHub |https://github.com/nette/tester] [API |https://api.nette.org/tester/] -| **Tracy**:[tracy:] | Hibakereső eszköz, amit imádni fogsz ♥ | [GitHub |https://github.com/nette/tracy] [API |https://api.nette.org/tracy/] -| **Utils**:[utils:] | Segédprogramok és alapvető osztályok | [GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] +| **Security**:[security:authentication] | Hozzáférési jogok kezelése | [GitHub |https://github.com/nette/security] [API |https://api.nette.org/security/] +| **Tester**:[tester:] | Kényelmes egységtesztek PHP-ban | [GitHub |https://github.com/nette/tester] [API |https://api.nette.org/tester/] +| **Tracy**:[tracy:] | Hibakereső eszköz, amelyet imádni fog ♥ | [GitHub |https://github.com/nette/tracy] [API |https://api.nette.org/tracy/] +| **Utils**:[utils:] | Alapvető osztályok és eszközök | [GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] {{leftbar: @menu-common}} {{toc: no}} diff --git a/www/it/10-reasons-why-nette.texy b/www/it/10-reasons-why-nette.texy index 8e0c208cc6..b0387c7772 100644 --- a/www/it/10-reasons-why-nette.texy +++ b/www/it/10-reasons-why-nette.texy @@ -1,53 +1,93 @@ -Perché utilizzare Nette? +7 motivi per usare Nette ************************
                                                                                                                            -Smettete di risolvere compiti ripetitivi e di distrarvi con dettagli che rendono la programmazione noiosa e improduttiva. **Nette Framework vi permette di lavorare in modo più efficace, di concentrarvi sulle cose importanti e di rendere il vostro codice più leggibile e ben strutturato**. Alcune caratteristiche includono: +Immaginate un framework PHP che vi permetta di concentrarvi su ciò che amate fare di più. Vi guida a scrivere codice pulito. Si occupa da solo della sicurezza. Smettete di sognare e scoprite Nette. Intraprendete un viaggio che vi aprirà le porte a nuove possibilità di sviluppo. Vi diremo: -- un eccellente sistema di template -- strumenti di diagnostica imbattibili -- un livello di database straordinariamente efficace -- protezione solida come una roccia contro le vulnerabilità note -- supporto HTML5 e AJAX, SEO friendly -- documentazione ben scritta e una comunità open source attiva -- design orientato agli oggetti maturo e pulito che sfrutta le più recenti caratteristiche di PHP -- soluzioni di best practice che sono incoraggiate, ma non imposte +- come creare siti web con la massima comodità +- come scrivere codice elegante +- cosa significa la saggezza "meno codice = maggiore sicurezza" +- come costruire un sito web come un kit di costruzione +- e come diventare parte di una comunità di successo
                                                                                                                            -Ed è completamente gratuito. Pensiamo che valga la pena di provare. +Nette porta comodità ed efficienza nel mondo degli sviluppatori web grazie a strumenti e tecniche innovative. Quali sono le caratteristiche chiave che rendono Nette unico e un elemento indispensabile nel set di strumenti di uno sviluppatore? Diamo un'occhiata! -**Nette Framework vi permette di concentrarvi sulla parte creativa dell'essere uno sviluppatore**. È stato costruito per essere estremamente usabile, amichevole e piacevole da usare. La sua sintassi comprensibile ma efficiente, il debugger all'avanguardia e le caratteristiche di sicurezza leader del settore consentono di scrivere siti di e-commerce, wiki, blog, CMS o qualsiasi cosa si possa immaginare in modo più veloce e migliore che mai. +#1 Nette vi vizia +----------------- -Nette Framework è [utilizzato da importanti aziende |https://builtwith.nette.org], come T-Systems, GE Money, Mladá fronta, VLTAVA-LABE-PRESS, Internet Info, DHL, Logio, ESET o Actum. Attualmente è il sito web dell'ex presidente della Repubblica Ceca Václav Klaus. Nel sondaggio condotto da [Zdroják |https://www.zdrojak.cz/clanky/vysledky-technologie-na-ceskem-webu/] è stato premiato come il framework più popolare e più utilizzato nella Repubblica Ceca. +Quando vent'anni fa è nato il framework Nette, tutto ruotava attorno a un unico obiettivo: come creare siti web nel modo più comodo possibile? Come rendere il lavoro dei programmatori il più piacevole possibile? Come rendere sexy la creazione di siti web? -L'apprendimento di Nette Framework non mancherà di garantire interessanti offerte di lavoro. +Con questo approccio, Nette ha conquistato molti programmatori e ha rapidamente guadagnato popolarità. All'epoca chiamavamo questa filosofia Netteway e oggi esiste un termine per essa, *Developer Experience* (DX). Nette è un framework che ha il DX nel suo DNA. Sentirete la differenza in mille e una cosa - dalle piccole cose alle innovazioni fondamentali. Lasciatevi viziare dal framework. -Salite a bordo .[#toc-get-on-board] ------------------------------------ +#2 Nette vi guida a scrivere codice pulito +------------------------------------------ -Seguite la guida e [create la vostra prima applicazione |quickstart:] passo dopo passo. Una pratica [guida per il programmatore |@home] è a disposizione per aiutarvi in qualsiasi momento e, se volete approfondire, consultate la pratica [documentazione dell'API |https://api.nette.org/] che presenta una panoramica di tutte le classi e i metodi. +Volete scrivere codice pulito? Avere applicazioni progettate correttamente? Chi non lo vorrebbe! Ed è qui che entra in gioco il ruolo del framework. Se non dà l'esempio, non è possibile creare un'applicazione ben progettata. -Se vi bloccate, consultate le [domande frequenti |nette:troubleshooting] o il [forum ufficiale |https://forum.nette.org/en/], dove gli utenti esperti sono pronti ad aiutarvi. +Nette dà l'esempio. È un mentore che insegna buone abitudini e a scrivere codice secondo metodologie collaudate. Come pioniere ed evangelista della dependency injection, offre una base di qualità per applicazioni sostenibili, estensibili e facilmente leggibili. Nette è progettato per essere comprensibile per i principianti, ma allo stesso tempo offre una profondità sufficiente per gli sviluppatori esperti. -Non siete abbastanza impegnati per imparare da soli? Ricevuto: offriamo [corsi di formazione su Nette Framework |https://www.skoleniphp.cz/skoleni-nette-vyvoj-webovych-aplikaci] (in ceco). I feedback degli ex partecipanti sono disponibili all'indirizzo [skoleniphp.cz/ohlasy |https://www.skoleniphp.cz/ohlasy]. +La comunità attorno a Nette ha formato molte personalità che oggi stanno dietro a progetti di successo e importanti. Per migliaia di programmatori, Nette è diventato un mentore nel loro percorso di crescita professionale. Unitevi a noi e scoprite come Nette influenzerà positivamente la qualità del vostro codice e delle vostre applicazioni. -Ulteriori informazioni .[#toc-more-information] ------------------------------------------------ +#3 Un guardiano affidabile per le vostre applicazioni +----------------------------------------------------- -Abbiamo un [blog ricco di suggerimenti |https://blog.nette.org] e una raccolta di vari [addon e componenti |https://componette.org] da utilizzare nelle vostre applicazioni. +Nette protegge le vostre applicazioni. Nel corso degli anni si è guadagnato la reputazione di strumento che prende la sicurezza estremamente sul serio. Fornisce una sicurezza sofisticata contro le vulnerabilità. Si preoccupa sempre di facilitare il lavoro dei programmatori, ma mai a scapito della sicurezza. -Inoltre, la comunità di Nette Framework si riunisce ogni mese in un evento chiamato [Poslední sobota |https://www.posobota.cz] (in ceco), dove i membri condividono le loro esperienze e si divertono. Venite a bere una birra! +Il suo motto "meno codice = sicurezza sufficiente" significa che i singoli elementi si comportano in modo sicuro già di base. Non è necessario attivare elementi di sicurezza scrivendo codice aggiuntivo. Quindi non potete dimenticarvene o temere di aver trascurato qualcosa. I programmatori spesso non sanno nemmeno quante cose di sicurezza Nette fa per loro e rimangono sorpresi quando lo scoprono. +Vi presentiamo un framework che vi guida a scrivere codice pulito e allo stesso tempo veglia sulla sicurezza delle vostre applicazioni. Nette è un partner affidabile che vi permette di concentrarvi sulla creazione di fantastiche applicazioni web con tranquillità. -Fatevi coinvolgere! .[#toc-get-involved] ----------------------------------------- -Qualsiasi contributo allo sviluppo è molto apprezzato. Siete invitati a fare un passaparola, a inserire un'[icona |www:en:logo] sul vostro sito web o a [sostenere |www:en:donate] finanziariamente [lo sviluppo |www:en:donate]. Grazie ♥. +#4 Costruite il web come un kit di costruzione +---------------------------------------------- + +In Nette costruite pagine da [componenti |component-model:] UI riutilizzabili. Ricorda lo sviluppo di applicazioni desktop, e Nette ha trasferito con successo questo approccio al web. Avete bisogno di un datagrid nell'amministrazione? Basta cercarlo nel marketplace dei componenti open source, installarlo e inserirlo semplicemente nella pagina. Inoltre, potete creare componenti personalizzati per elementi ricorrenti nelle pagine, eliminando così le duplicazioni e migliorando l'organizzazione del codice. + +Questa caratteristica unica distingue Nette da tutti gli altri principali attori del mercato. Vi permetterà di creare e mantenere applicazioni web in modo efficiente. Con Nette, lavorare con l'UI diventa un'esperienza fluida e piacevole. + + +#5 Un set flessibile di pacchetti +--------------------------------- + +Nette è un set di [pacchetti utilizzabili separatamente |www:packages]. Tra questi ci sono l'avvincente [strumento di debugging Tracy |tracy:], il [sistema di template di nuova generazione Latte |latte:], l'[eccellente Dependency Injection Container |dependency-injection:], i [form |forms:] e molti altri. Ogni pacchetto ha una documentazione dettagliata e leggibile e risiede in un repository separato su GitHub. Potete utilizzare i pacchetti separatamente o combinarli con altri strumenti e tecnologie che già utilizzate. Ad esempio, Latte può essere implementato in WordPress o Slim Framework, il container DI può essere il nucleo di un framework aziendale e Tracy visualizzerà i messaggi di errore. + +Oppure potete usare Nette nel suo insieme, come framework, e creare un'applicazione web completa al suo interno. Che stiate sviluppando un piccolo progetto personale o una robusta applicazione enterprise, Nette sarà il vostro partner affidabile. + + +#6 Stabilità & innovazione +-------------------------- + +Nette è un framework maturo e collaudato con una lunga storia. Nonostante ciò, si mantiene agile e flessibile grazie a un design intelligente. Gli utenti apprezzano il fatto che sia un framework piccolo e flessibile, non un monolite. + +È sempre pronto in anticipo per le nuove versioni di PHP e tiene conto delle ultime innovazioni nel campo dello sviluppo di applicazioni web. Questo è fondamentale per minimizzare il debito tecnologico. + +Nette è quindi una combinazione di stabilità pluriennale e innovazione, che vi permetterà di fare affidamento su una soluzione collaudata, pur potendo utilizzare le ultime tecnologie e tendenze. Con Nette avete la certezza che il vostro progetto sarà costruito su basi che tengono costantemente il passo con il mondo in rapida evoluzione dello sviluppo web. + + +#7 Siate nella migliore compagnia +--------------------------------- + +Nette Framework è popolare tra i professionisti. Si affidano ad esso [aziende importanti |https://builtwith.nette.org] come O2, BOSCH, ESET, Zásilkovna, DHL, SUPRAPHON e centinaia di altre. In diversi sondaggi è risultato il framework più popolare e utilizzato nella Repubblica Ceca. + +Iniziate con Nette e vi si apriranno nuove opportunità di lavoro. Otterrete non solo uno strumento potente, ma anche la comunità più attiva nella Repubblica Ceca, pronta a sostenervi nel vostro percorso di crescita professionale. Si incontrano regolarmente ai [Posobota |www.posobota.cz], eventi in cui si scambiano esperienze. Venite a conoscerci! + + +Scoprite Nette +-------------- + +Unitevi a migliaia di sviluppatori soddisfatti che hanno già scoperto i vantaggi di Nette e iniziate a scrivere codice più pulito, sicuro ed efficiente con questo framework unico. + +Create la vostra prima applicazione [secondo la guida |quickstart:], passo dopo passo. Avrete costantemente a portata di mano una [documentazione completa |nette:] e una pratica [panoramica dell'API |https://api.nette.org]. Visitate il nostro [blog pieno di suggerimenti |https://blog.nette.org] e la raccolta di [add-on e componenti |https://componette.org] che estendono le capacità di Nette. + +Avete domande? Consultate la pagina con le [domande frequenti|nette:troubleshooting] o discutete sul [forum ceco|https://forum.nette.org/cs/]. Preferite una formazione personale? Offriamo [formazione Nette Framework |https://www.skoleniphp.cz/skoleni-nette-vyvoj-webovych-aplikaci] con [feedback eccellente |https://www.skoleniphp.cz/ohlasy]. + +**Scoprite il framework che vi vizierà, guiderà e ispirerà.** {{leftbar: @menu-common}} diff --git a/www/it/@home.texy b/www/it/@home.texy index b24403a57d..19a17a569e 100644 --- a/www/it/@home.texy +++ b/www/it/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Nette - Sviluppo web comodo e sicuro in PHP}} -{{description: Nette è una famiglia di componenti maturi e stand-alone per PHP. Siete pronti a innamorarvi? Insieme, creano un framework che è stato classificato come il terzo più popolare al mondo. La nostra filosofia si concentra sulla produttività, sulle best practice e sulla sicurezza.}} +{{maintitle:Nette – Sviluppo web comodo e sicuro in PHP}} +{{description: Nette è una famiglia di componenti avanzati e utilizzabili autonomamente per PHP. Lasciati ispirare da loro. Insieme formano un framework, valutato come il 3° più popolare al mondo. La filosofia di Nette pone un'enfasi straordinaria sulla produttività, le best practice e la sicurezza.}} diff --git a/www/it/@menu-common.texy b/www/it/@menu-common.texy index 234f4d30b7..d8495db569 100644 --- a/www/it/@menu-common.texy +++ b/www/it/@menu-common.texy @@ -2,19 +2,21 @@ Introduzione ************ - [Perché usare Nette? |www:10-reasons-why-nette] - [Installazione |nette:installation] -- [Create la vostra prima applicazione! |quickstart:] +- [Scriviamo la prima applicazione! |quickstart:] Argomenti generali ****************** - [Elenco dei pacchetti |www:packages] -- [Manutenzione e PHP |www:maintenance] +- [Manutenzione e versioni PHP |www:maintenance] - [Note di rilascio |https://nette.org/releases] -- [Guida all'aggiornamento |migrations:en] -- [nette:Risoluzione dei problemi |nette:Troubleshooting] +- [Passaggio a versioni più recenti|migrations:en] +- [Risoluzione dei problemi |nette:troubleshooting] - [Chi crea Nette |https://nette.org/contributors] -- [Storia di Nette |history] +- [Storia di Nette |www:history] - [Partecipa |contributing:] -- [Sviluppo degli sponsor |https://nette.org/en/donate] -- [Riferimento API |https://api.nette.org] -- [Migliori pratiche |best-practices:] +- [Sostieni lo sviluppo |https://nette.org/cs/donate] +- [Riferimento API |https://api.nette.org/] +- [Guide e procedure |best-practices:] + +- [La sicurezza prima di tutto |nette:vulnerability-protection] diff --git a/www/it/donate.texy b/www/it/donate.texy new file mode 100644 index 0000000000..a433e871f4 --- /dev/null +++ b/www/it/donate.texy @@ -0,0 +1,20 @@ +Sostenete lo sviluppo di Nette +****************************** + +
                                                                                                                            +Chiunque costruisca su Nette ha interesse che il framework si sviluppi attivamente. Che supporti le nuove versioni di PHP. Che vengano corretti gli errori. Che arrivino ulteriori novità che facilitino il lavoro o facciano risparmiare tempo e denaro. Che il framework abbia un'ottima documentazione e che esistano contenuti utili attorno ad esso, sia sotto forma di articoli, guide o video. + +Molte parti di Nette rappresentano l'eccellenza mondiale e vogliamo che continui ad essere così. + +Senza un finanziamento adeguato, nulla di tutto ciò può essere garantito. Eppure, per potersi affidare all'uscita di nuove versioni, basta davvero poco: sostenerlo ogni mese anche con un piccolo contributo finanziario. + +Unitevi a noi e diventate partner di Nette! + +Garantirete così il sano funzionamento del progetto su cui fate affidamento. E allo stesso tempo **otterrete tutta una serie di vantaggi esclusivi** (vedi *Livelli di partnership* nella colonna di destra). Otterrete l'accesso a contenuti bonus. Al supporto tecnico. Aumenterete la priorità nella risoluzione dei vostri issue. E renderete visibile la vostra azienda attirando sviluppatori. + +Come vi renderemo visibili? Ad esempio, inserendo il vostro logo su questo sito web (cioè su questa pagina, sulla homepage, nella documentazione, sul forum e su una [pagina speciale |https://nette.org/partner/vitalita] a cui potete linkare). Avrete la possibilità di inserire [offerte di lavoro |https://forum.nette.org/cs/f30-prace-a-zakazky], fare pubblicità sul forum ([esempio |https://forum.nette.org/cs/30798-problem-s-cizim-klicem-pri-mazani#p198093]) o nella documentazione ([esempio |https://doc.nette.org/cs/application/components#toc-flash-zpravy]), ovvero nei luoghi con il miglior impatto assoluto sul gruppo di sviluppatori Nette. + +Ai partner emettiamo fatture affinché possano inserire il supporto tra i costi, mensilmente, trimestralmente, semestralmente o annualmente. + +{{include: buttons}} +
                                                                                                                            diff --git a/www/it/history.texy b/www/it/history.texy index 15968725d9..6e516eebf3 100644 --- a/www/it/history.texy +++ b/www/it/history.texy @@ -2,35 +2,34 @@ Storia di Nette *************** .[perex] -Le origini di Nette risalgono al 2004, quando il suo autore David Grudl iniziò a cercare un framework adatto per scrivere applicazioni, poiché il PHP puro non era più sufficiente. Nessuna delle soluzioni disponibili all'epoca lo soddisfaceva, così iniziò gradualmente a delineare le caratteristiche di un nuovo framework, che in seguito prese il nome di Nette. +L'inizio della nascita di Nette risale al 2004, quando il suo autore David Grudl iniziò a cercare un framework adatto in cui poter scrivere applicazioni, poiché il PHP puro non era più sufficiente. Nessuna delle soluzioni disponibili all'epoca lo soddisfaceva, così iniziò gradualmente a delineare le caratteristiche di un nuovo framework, che in seguito prese il nome di Nette. -All'epoca non esistevano ancora framework come Symfony, Laravel o Ruby on Rails. Nel mondo Java, JSF (JavaServer Faces) era lo standard e nel regno concorrente .NET, ASP.NET Webforms era il framework dominante. Entrambi consentivano di creare pagine utilizzando componenti dell'interfaccia utente riutilizzabili. David considerava i loro metodi di astrazione e i tentativi di creare l'assenza di stato sul protocollo HTTP senza stato, utilizzando sessioni o postback, come difettosi e fondamentalmente non funzionanti. Creavano molte difficoltà agli utenti e ai motori di ricerca. Ad esempio, se si salvava un link, ci si sorprendeva di trovare sotto di esso contenuti diversi in un secondo momento. +A quel tempo non esistevano ancora framework attuali come Symfony, Laravel o Ruby on Rails. Nel mondo Java, lo standard era il framework JSF (JavaServer Faces) e nella concorrenza .NET c'erano ASP.NET Webforms. Entrambi permettevano di costruire pagine utilizzando componenti UI riutilizzabili. I loro metodi di astrazione e i tentativi di creare statefulness su un protocollo HTTP stateless utilizzando session o il cosiddetto postback erano considerati da David errati e fondamentalmente non funzionanti. Causavano numerose difficoltà agli utenti e ai motori di ricerca. Ad esempio, se si salvava un link, si scopriva con sorpresa che in seguito conteneva un contenuto diverso. -La possibilità di comporre pagine a partire da componenti dell'interfaccia utente riutilizzabili affascinava David, che la conosceva bene grazie a Delphi, uno strumento molto diffuso all'epoca per la creazione di applicazioni desktop. Gli piacevano i mercati con componenti opensource per Delphi. Così cercò di risolvere il problema di come creare un framework di componenti che, a sua volta, funzionasse in completa armonia con HTTP stateless. Era alla ricerca di un concetto che fosse adatto agli utenti, al SEO e agli sviluppatori. Così è nato Nette. +La possibilità stessa di comporre pagine da componenti UI riutilizzabili affascinava David, la conosceva bene da Delphi, uno strumento allora popolare per la creazione di applicazioni desktop. Gli piacevano i marketplace con componenti opensource per Delphi. Cercò quindi di risolvere la questione di come creare un framework a componenti che funzionasse invece in completa armonia con l'HTTP stateless. Cercava un concetto che fosse amichevole per gli utenti, per la SEO e per gli sviluppatori. E così iniziò a nascere Nette. .[note] -Il nome Nette è nato per caso in bagno, quando l'autore ha notato un flacone di gel da barba Gillette, ruotato in modo che si vedesse solo la *llette*. +Il nome Nette è nato per caso in bagno, quando l'autore ha visto un contenitore di gel da barba Gillette, girato in modo che si vedesse solo *llette*. -Seguirono migliaia di ore di ricerche, riflessioni e riscritture. In un garage polveroso di un villaggio alle porte di Brno, si stavano creando i primi contorni della struttura futura. La base dell'architettura era il pattern MVC, utilizzato poi dall'ormai dimenticato framework PHP Mojavi e successivamente reso popolare dal clamore suscitato da Ruby on Rails. Una delle fonti di ispirazione fu persino il framework phpBase di Honza Tichý, mai pubblicato. +Seguirono migliaia di ore di ricerca, riflessione e riscrittura. In un garage polveroso in un villaggio da qualche parte vicino a Brno nacquero i primi contorni del futuro framework. La base dell'architettura divenne il pattern MVC, che all'epoca era utilizzato dal framework PHP ormai dimenticato Mojavi e successivamente reso popolare grazie all'hype attorno a Ruby on Rails. Una delle fonti di ispirazione fu persino il framework mai pubblicato phpBase di Honza Tichý. -Sul blog dell'autore cominciarono ad apparire articoli sull'imminente Nette. Si scherzava sul fatto che si trattasse di un vaporware. Ma poi, nell'ottobre 2007, alla conferenza PHP Seminar di Praga, David presentò pubblicamente Nette. Tra l'altro, questa conferenza si è evoluta in WebExpo un anno dopo, diventando una delle più grandi conferenze IT in Europa. Già allora Nette presentava con orgoglio una serie di concetti originali, come il già citato modello a componenti, il router bidirezionale, il modo specifico di collegarsi tra i presentatori, ecc. Aveva moduli, autenticazione, caching, ecc. Tutto è ancora utilizzato in Nette nel suo concetto originale fino ad oggi. +Sul blog dell'autore iniziarono ad apparire articoli sul futuro Nette. Si scherzava sul fatto che fosse vaporware. Ma poi, nell'ottobre 2007, alla conferenza PHP Seminář di Praga, David presentò pubblicamente Nette. Tra l'altro, da questa conferenza si sviluppò un anno dopo WebExpo, successivamente una delle più grandi conferenze IT in Europa. Già allora Nette vantava numerosi concetti originali, come il menzionato modello a componenti, il router bidirezionale, un modo specifico di linking tra presenter, ecc. Aveva form, autenticazione risolta, caching, ecc. Tutto in Nette viene utilizzato nel concetto originale ancora oggi. .[note] -Nette utilizza *presenter* invece di *controller* perché nel codice c'erano presumibilmente troppe parole che iniziavano con *con* (controller, front controller, control, config, container, ...). +In Nette, invece del termine *controller*, si usa *presenter*, perché nel codice c'erano apparentemente troppe parole che iniziavano con *con* (controller, front controller, control, config, container, ...) -Alla fine del 2007, David Grudl pubblicò il codice e Nette 0.7 fu rilasciato. Attorno ad esso si formò una comunità entusiasta di programmatori che iniziò a riunirsi ogni mese all'evento Posobota. La comunità comprendeva molti dei luminari di oggi, come Ondrej Mirtes, autore del grande strumento PHPStan. Lo sviluppo di Nette andò avanti e nei due anni successivi vennero rilasciate le versioni 0.8 e 0.9, che gettarono le basi per quasi tutte le parti odierne del framework. Compresi gli snippet AJAX che precedono di 14 anni Hotwire per Ruby on Rails o Symfony UX Turbo. +Alla fine del 2007, David Grudl pubblicò anche il codice e così vide la luce la versione Nette 0.7. Il framework attirò immediatamente un'enorme attenzione. Si creò attorno ad esso una comunità entusiasta di programmatori, che iniziò a incontrarsi ogni mese all'evento Posobota. Nella comunità c'erano molte personalità di oggi, ad esempio Ondřej Mirtes, autore dell'ottimo strumento PHPStan. Lo sviluppo di Nette procedette rapidamente e nei due anni successivi uscirono le versioni 0.8 e 0.9, dove furono gettate le basi di quasi tutte le parti attuali del framework. Compresi gli snippet AJAX, che anticiparono di 14 anni Hotwire per Ruby on Rails o Symfony UX Turbo. -Ma una cosa cruciale mancava a Nette all'epoca. Il dependecy injection container (DIC). Nette utilizzava un *service locator* e l'intenzione era di passare alla dependecy injection. Ma come progettare una cosa del genere? David Grudl, che all'epoca non aveva alcuna esperienza con DI, andò a pranzo con Vasek Purchart, che utilizzava DI da circa un anno e mezzo. Insieme discussero dell'argomento e David iniziò a lavorare su Nette DI, una libreria che rivoluzionò completamente il modo di concepire la progettazione delle applicazioni. Il contenitore DI è diventato una delle parti di maggior successo del framework. E ha dato origine a due spin-off: il formato Neon e la libreria Schema. +Ma una cosa fondamentale mancava nel Nette di allora. Il Dependency injection container (DIC). Nette utilizzava il cosiddetto *service locator* e l'intenzione era di passare proprio alla dependency injection. Ma come progettare una cosa del genere? David Grudl, che all'epoca non aveva esperienza con la DI, andò a pranzo con Vašek Purchart, che usava la DI da circa sei mesi. Discussero insieme l'argomento e David iniziò a lavorare su Nette DI, una libreria che ribaltò completamente il modo di pensare alla progettazione delle applicazioni. Il container DI divenne una delle parti meglio riuscite del framework. E diede successivamente origine anche a due spin-off: il formato Neon e la libreria Schema. .[note] -Il passaggio alla dependency injection ha richiesto molto tempo e abbiamo aspettato un paio d'anni per una nuova versione di Nette. Pertanto, quando finalmente è uscita, è stata numerata 2. Quindi la versione 1 di Nette non esiste. +Il passaggio alla dependency injection richiese molto tempo e si dovette attendere qualche anno per la nuova versione di Nette. Pertanto, quando finalmente uscì, portava direttamente il numero 2. La versione Nette 1 quindi non esiste. -Nette ha iniziato la sua storia moderna nel 2012 con la versione 2.0. Con essa è stato introdotto anche Nette Database, che includeva uno strumento di database estremamente pratico, ora chiamato Explorer. Questa libreria è stata originariamente programmata da Jakub Vrána, vicino di casa di David Grudl e autore del popolare strumento Adminer. Il suo sviluppo è stato poi affidato a Jan Škrášek per tre anni. +Nette nel 2012 con la versione 2.0 diede inizio alla sua storia moderna. Portò anche Nette Database, di cui faceva parte anche uno strumento straordinariamente pratico per lavorare con il database, oggi chiamato Explorer. Questa libreria fu originariamente programmata da Jakub Vrána, vicino di casa di David Grudl e autore del popolare strumento Adminer. Del suo ulteriore sviluppo si occupò poi per tre anni Jan Škrášek. -Nel 2014 è stato rilasciato Nette 2.1, seguito a breve distanza da Nette 2.2. Come è possibile? La versione 2.2 era identica alla versione 2.1, solo suddivisa in venti pacchetti separati. Lo strumento Composer ha preso piede nel mondo PHP e ha cambiato il modo di pensare alla creazione di librerie. Nette ha smesso di essere un monolite e si è suddiviso in parti più piccole e indipendenti. Ognuna con il proprio repository, il proprio issue tracker e il proprio flusso di sviluppo e versionamento. In questo modo Nette non deve affrontare le assurdità comuni ai framework monolitici, dove viene rilasciata una nuova versione di un pacchetto anche se nulla è cambiato. L'effettiva divisione dei repository Git ha richiesto diverse settimane di preparazione e centinaia di ore di lavoro. - -Nette ha anche ottenuto un sorprendente terzo posto nel sondaggio globale per il miglior framework PHP organizzato dalla rivista Sitepoint. +Nel 2014 uscì Nette 2.1, seguito a breve da Nette 2.2. Com'è possibile? La versione 2.2 era uguale alla versione 2.1, solo divisa in venti pacchetti separati. Nel mondo PHP si era affermato lo strumento Composer e aveva cambiato il modo di concepire la creazione di librerie. Nette smise così di essere un monolite e si divise in parti più piccole e indipendenti. Ognuna con il proprio repository, issue tracker e il proprio ritmo di sviluppo e versioning. In Nette non devono quindi verificarsi le assurdità comuni nei framework monolitici, quando esce una nuova versione di un pacchetto anche se non è cambiato assolutamente nulla al suo interno. La divisione stessa dei repository Git richiese diverse settimane di preparazione e centinaia di ore di tempo macchina. +Nette si classificò anche all'incredibile 3° posto nel sondaggio mondiale sul miglior framework PHP organizzato dalla rivista Sitepoint. {{toc:no}} diff --git a/www/it/license.texy b/www/it/license.texy new file mode 100644 index 0000000000..6e70e9c67b --- /dev/null +++ b/www/it/license.texy @@ -0,0 +1,44 @@ +Politica di licenza +******************* + +Nette Framework è distribuito come software libero, in modo che chiunque possa utilizzarlo. Potete scegliere se vi si addice meglio la licenza [New BSD |#New BSD License] o la [#GNU General Public License (GPL)] nella versione 2 o 3. + +La licenza BSD è consigliata per la maggior parte dei progetti, poiché è facile da capire e non pone quasi nessuna restrizione su ciò che potete fare con il framework. Potete utilizzare Nette anche in progetti commerciali. Tuttavia, se la GPL si adatta meglio al vostro progetto, sceglietela. Non è necessario informare nessuno della vostra decisione. Conservate sempre i copyright originali. + +Dovreste sapere che il nome "Nette Framework" è un marchio registrato e il suo utilizzo ha alcune restrizioni. Non utilizzate quindi "Nette" nel nome del vostro progetto o dominio di primo livello, scegliete piuttosto un nome che poggi su basi proprie. Se il vostro progetto è buono, non ci vorrà molto a crearsi una propria reputazione. + +Se sarete soddisfatti di Nette Framework, e crediamo che lo sarete, potete [sostenerlo con un contributo |donate]. Grazie. + + +New BSD License +--------------- + +Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com) All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of "Nette Framework" nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +GNU General Public License (GPL) +-------------------------------- + +I testi delle licenze GPL sono molto lunghi, quindi qui sono forniti i link ad essi: + +- GPL versione 2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +- GPL versione 3: https://www.gnu.org/licenses/gpl-3.0.html + + +{{toc:yes}} +{{priority: -2}} diff --git a/www/it/maintenance.texy b/www/it/maintenance.texy index e2d1b88371..268b880f04 100644 --- a/www/it/maintenance.texy +++ b/www/it/maintenance.texy @@ -1,27 +1,27 @@ -Manutenzione e versioni PHP -*************************** +Manutenzione e compatibilità con PHP +************************************ .[perex] -Nette è un framework con un periodo di supporto eccezionalmente lungo per le singole release. Ogni ramo è una LTS (Long-Term Support Release) con almeno 2 anni di supporto. +Nette è un framework con un periodo di supporto eccezionalmente lungo per le singole release. Ogni ramo è una LTS (Long-Term Support Release) con supporto per almeno 2 anni. -Ogni versione viene mantenuta attivamente per un periodo di un anno (o più) dal rilascio stabile iniziale. -I bug critici e di sicurezza vengono corretti per due anni. +Ogni versione viene mantenuta attivamente per un periodo di un anno (o più) dalla release stabile iniziale. Gli errori critici e di sicurezza vengono corretti per due anni. -Calendario dei rilasci (Roadmap) .[#toc-release-calendar-roadmap] -================================================================= +Calendario delle release di Nette +================================= {{include: doc-roadmap-table}} -Compatibilità con PHP .[#toc-php-compatibility] -=============================================== +Compatibilità con PHP +===================== + +La compatibilità si applica sempre alla release più recente di ogni serie. -La compatibilità si applica sempre all'ultima release di ogni serie. {{include: doc-roadmap-versions}} {{leftbar: @menu-common}} {{toc: no}} -{{description: Rilasci di Nette, roadmap, manutenzione e tabelle di compatibilità con PHP}} +{{description: Release di Nette, roadmap, tabelle di manutenzione e compatibilità con PHP}} diff --git a/www/it/packages.texy b/www/it/packages.texy index 83d6b3f2ce..f0ba73804f 100644 --- a/www/it/packages.texy +++ b/www/it/packages.texy @@ -1,26 +1,27 @@ -Elenco dei pacchetti -******************** +Elenco dei pacchetti Nette +************************** -| **Application**:[application:how-it-works] | Il kernel dell'applicazione web | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] -| **Bootstrap**:[bootstrap:] | Il bootstrap della vostra applicazione | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] -| **Caching**:[caching:] | Livello di cache con un insieme di archivi | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] -| **Component Model**:[component-model:] | Fondazione per i sistemi a componenti | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] +| **Application**:[application:how-it-works] | Nucleo delle applicazioni web | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] +| **Assets**:[assets:] | Gestione dei file statici | [API |https://api.nette.org/assets/] [GitHub |https://github.com/nette/assets] +| **Bootstrap**:[bootstrap:] | Bootstrap dell'applicazione web | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] +| **Caching**:[caching:] | Livello di caching con storage | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] +| **Component Model**:[component-model:] | Base del sistema a componenti | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] | **DI**:[dependency-injection:] | Dependency Injection Container | [GitHub |https://github.com/nette/di] [API |https://api.nette.org/di/] -| **Database**:[database:] | Livello di database | [GitHub |https://github.com/nette/database] [API |https://api.nette.org/database/] -| **Forms**:[forms:] | Facilita notevolmente la sicurezza dei moduli web | [GitHub |https://github.com/nette/forms] [API |https://api.nette.org/forms/] -| **Http**:[http:] | Strato per le richieste e le risposte HTTP | [GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] -| **Latte**:[latte:] | Incredibile motore di template | [GitHub |https://github.com/nette/latte] [API |https://api.nette.org/latte/] +| **Database**:[database:] | Livello database | [GitHub |https://github.com/nette/database] [API |https://api.nette.org/database/] +| **Forms**:[forms:] | Form web comodi e sicuri | [GitHub |https://github.com/nette/forms] [API |https://api.nette.org/forms/] +| **Http**:[http:] | Livello che incapsula HTTP request & response | [GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] +| **Latte**:[latte:] | Fantastico sistema di template | [GitHub |https://github.com/nette/latte] [API |https://api.nette.org/latte/] | **Mail**:[mail:] | Invio di e-mail | [GitHub |https://github.com/nette/mail] [API |https://api.nette.org/mail/] -| **Neon**:[neon:] | Carica e scarica il [formato NEON |https://ne-on.org] | [GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] +| **Neon**:[neon:] | Lettura e scrittura del formato [NEON |https://ne-on.org] | [GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] | **Php Generator**:[php-generator:] | Generatore di codice PHP | [GitHub |https://github.com/nette/php-generator] [API |https://api.nette.org/php-generator/] -| **Robot Loader**:[robot-loader:] | Il più comodo caricamento automatico | [GitHub |https://github.com/nette/robot-loader] [API |https://api.nette.org/robot-loader/] +| **Robot Loader**:[robot-loader:] | L'autoloading più comodo | [GitHub |https://github.com/nette/robot-loader] [API |https://api.nette.org/robot-loader/] | **Routing**:[application:routing] | Routing | [GitHub |https://github.com/nette/routing] [API |https://api.nette.org/routing/] -| **Safe Stream**:[safe-stream:] | Operazioni atomiche sicure con i file | [GitHub |https://github.com/nette/safe-stream] [API |https://api.nette.org/safe-stream/] -| **Security**:[security:] | Fornisce un sistema di controllo degli accessi | [GitHub |https://github.com/nette/security] [API |https://api.nette.org/security/] -| **Schema**:[schema:] | Convalida dei dati utente | [GitHub |https://github.com/nette/schema] [API |https://api.nette.org/schema/] -| **Tester**:[tester:] | Test delle unità in PHP | [GitHub |https://github.com/nette/tester] [API |https://api.nette.org/tester/] -| **Tracy**:[tracy:] | Strumento di debug che ti piacerà ♥ | [GitHub |https://github.com/nette/tracy] [API |https://api.nette.org/tracy/] -| **Utils**:[utils:] | Utilità e classi fondamentali | [GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] +| **Safe Stream**:[safe-stream:] | Operazioni atomiche sicure sui file | [GitHub |https://github.com/nette/safe-stream] [API |https://api.nette.org/safe-stream/] +| **Schema**:[schema:] | Validazione dei dati utente | [GitHub |https://github.com/nette/schema] [API |https://api.nette.org/schema/] +| **Security**:[security:authentication] | Gestione dei diritti di accesso | [GitHub |https://github.com/nette/security] [API |https://api.nette.org/security/] +| **Tester**:[tester:] | Test unitari comodi in PHP | [GitHub |https://github.com/nette/tester] [API |https://api.nette.org/tester/] +| **Tracy**:[tracy:] | Strumento di debugging che amerete ♥ | [GitHub |https://github.com/nette/tracy] [API |https://api.nette.org/tracy/] +| **Utils**:[utils:] | Classi e strumenti di base | [GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] {{leftbar: @menu-common}} {{toc: no}} diff --git a/www/ja/10-reasons-why-nette.texy b/www/ja/10-reasons-why-nette.texy new file mode 100644 index 0000000000..ed0d554358 --- /dev/null +++ b/www/ja/10-reasons-why-nette.texy @@ -0,0 +1,93 @@ +Netteを使う7つの理由 +************* + +
                                                                                                                            + +あなたが最も好きなことに集中できるPHPフレームワークを想像してみてください。クリーンなコードを書くように導いてくれます。セキュリティにも配慮しています。夢を見るのをやめて、Netteを知ってください。開発の新たな可能性への扉を開く旅に出かけましょう。お話しします: + +- 最大限の快適さでウェブサイトを作成する方法 +- エレガントなコードを書く方法 +- 「コードが少ない=セキュリティが高い」という知恵の意味 +- ウェブを組み立てキットのように構築する方法 +- そして、成功したコミュニティの一員になる方法 + +
                                                                                                                            + +Netteは、革新的なツールとテクニックにより、ウェブ開発者の世界に快適さと効率性をもたらします。Netteをユニークで開発者キットに不可欠な要素にする主要な機能は何でしょうか?見ていきましょう! + + +#1 Netteはあなたを甘やかします +------------------- + +20年前、Netteフレームワークが生まれ始めたとき、すべては1つの目標を中心に展開していました:ウェブサイトをできるだけ快適に作成するにはどうすればよいか?プログラマの作業をできるだけ快適にするにはどうすればよいか?ウェブサイト作成をセクシーにするにはどうすればよいか? + +Netteはこのアプローチで多くのプログラマにアピールし、急速に人気を博しました。当時、私たちはこの哲学をNettewayと呼んでいましたが、今日では*Developer Experience*(DX)という用語が存在します。NetteはDXをDNAに持つフレームワークです。違いは、些細なことから根本的な革新まで、千差万別の事柄で感じられます。フレームワークに甘やかされてください。 + + +#2 Netteはクリーンなコードへと導きます +----------------------- + +クリーンなコードを書きたいですか?適切に設計されたアプリケーションを持ちたいですか?誰もがそう望んでいます!そして、ここでフレームワークの役割が始まります。フレームワーク自体が模範を示さなければ、優れた設計のアプリケーションを作成することはできません。 + +Netteは模範を示します。それは、良い習慣と実績のある方法論に従ってコードを書くことを教えるメンターです。依存性注入の先駆者であり伝道者として、持続可能で拡張可能で読みやすいアプリケーションのための質の高い基盤を提供します。Netteは初心者にも理解できるように設計されていますが、経験豊富な開発者には十分な深さを提供します。 + +Netteを取り巻くコミュニティは、今日、成功した重要なプロジェクトの背後にいる多くの人物を育ててきました。何千人ものプログラマにとって、Netteはプロフェッショナルな成長への道のりのメンターとなりました。参加して、Netteがあなたのコードとアプリケーションの品質にどのように肯定的な影響を与えるかを発見してください。 + + +#3 アプリケーションの信頼できる守護者 +-------------------- + +Netteはあなたのアプリケーションを保護します。長年にわたり、セキュリティを非常に真剣に考えているツールとしての評判を得てきました。脆弱性に対する巧妙なセキュリティを提供します。常にプログラマの作業を容易にすることに配慮しますが、決してセキュリティを犠牲にしません。 + +そのモットー「コードが少ない=十分なセキュリティ」は、個々の要素が基本的に安全に動作することを意味します。追加のコードを記述してセキュリティ機能を有効にする必要はありません。したがって、それを忘れたり、何かを見落としたのではないかと心配したりすることはできません。プログラマは、Netteが彼らのためにどれだけ多くのセキュリティ対策を行っているかを知らないことが多く、それを知って驚きます。 + +クリーンなコードへと導き、同時にアプリケーションのセキュリティを見守るフレームワークをご紹介します。Netteは、安心して素晴らしいウェブアプリケーションの作成に集中できる信頼できるパートナーです。 + + +#4 ウェブを組み立てキットのように構築する +---------------------- + +Netteでは、再利用可能なUIコンポーネントからページを構築します。これはデスクトップアプリケーション開発を彷彿とさせ、Netteはこのアプローチをウェブにうまく持ち込みました。管理画面にデータグリッドが必要ですか?オープンソースコンポーネントのマーケットプレイスで見つけてインストールし、ページに簡単に挿入するだけです。さらに、ページ上の繰り返し要素用の独自のコンポーネントを作成でき、重複を排除し、コードの構成を改善します。 + +このユニークな機能は、Netteを市場の他のすべての主要なプレーヤーと区別します。ウェブアプリケーションを効率的に作成および保守できます。Netteを使用すると、UIの操作がスムーズで快適な体験になります。 + + +#5 柔軟なパッケージセット +-------------- + +Netteは、[単独で使用可能なパッケージ |www:packages] のセットです。これらには、中毒性のある [デバッグツールTracy |tracy:]、[次世代テンプレートシステムLatte |latte:]、[優れた依存性注入コンテナ |dependency-injection:]、[フォーム |forms:] など、多数が含まれます。各パッケージには読みやすい詳細なドキュメントがあり、GitHubの個別のリポジトリにあります。 パッケージは単独で使用することも、すでに使用している他のツールやテクノロジーと組み合わせることもできます。例えば、LatteはWordPressやSlim Frameworkに導入でき、DIコンテナは企業フレームワークのコアになる可能性があり、Tracyはエラーメッセージを視覚化します。 + +または、Netteをフレームワーク全体として使用し、完全なウェブアプリケーションを作成することもできます。小規模な個人プロジェクトを開発している場合でも、堅牢なエンタープライズアプリケーションを開発している場合でも、Netteは信頼できるパートナーになります。 + + +#6 安定性 & 革新 +----------- + +Netteは、長年の歴史を持つ成熟した実績のあるフレームワークです。それにもかかわらず、巧妙な設計のおかげで機敏で柔軟性を保っています。ユーザーは、それが巨大なものではなく、小さくて柔軟なフレームワークであることを高く評価しています。 + +常に新しいPHPバージョンに事前に準備されており、ウェブアプリケーション開発分野の最新の革新を考慮に入れています。これは、技術的負債を最小限に抑えるために重要です。 + +したがって、Netteは長年の安定性と革新の組み合わせであり、実績のあるソリューションに頼りながら、最新のテクノロジーとトレンドを活用できます。Netteを使用すると、急速に進化するウェブ開発の世界に常に追いついている基盤の上にプロジェクトが構築されるという確信が得られます。 + + +#7 最高の仲間といる +----------- + +Nette Frameworkはプロフェッショナルに人気があります。O2、BOSCH、ESET、Zásilkovna、DHL、SUPRAPHONなどの [著名な企業 |https://builtwith.nette.org] や、その他数百社がNetteに依存しています。いくつかのアンケートで、チェコ共和国で最も人気があり、最も使用されているフレームワークとして勝利しました。 + +Netteを始めると、新しい仕事の機会が開かれます。強力なツールだけでなく、チェコ共和国で最も活発なコミュニティも得られます。コミュニティは、プロフェッショナルな成長への道のりをサポートする準備ができています。定期的に [Posobota |www.posobota.cz](経験を交換するイベント)で会合しています。私たちに会いに来てください! + + +Netteを発見する +---------- + +Netteの利点をすでに発見し、このユニークなフレームワークでよりクリーンで、より安全で、より効率的なコードを書き始めた何千人もの満足している開発者に加わってください。 + +[クイックスタート|quickstart:] に従って、最初のアプリケーションを段階的に作成します。[広範なドキュメント |nette:] と実用的な [API概要 |https://api.nette.org] が常に手元にあります。[ヒント満載のブログ |https://blog.nette.org] や、Netteの機能を拡張する [アドオンとコンポーネント |https://componette.org] のコレクションをご覧ください。 + +質問がありますか?[よくある質問|nette:troubleshooting] ページを参照するか、[チェコのフォーラム|https://forum.nette.org/cs/]で議論してください。個人的なトレーニングを希望しますか?[優れたフィードバック |https://www.skoleniphp.cz/ohlasy] を持つ [Nette Frameworkトレーニング |https://www.skoleniphp.cz/skoleni-nette-vyvoj-webovych-aplikaci] を提供しています。 + +**あなたを甘やかし、導き、刺激するフレームワークに出会いましょう。** + + +{{leftbar: @menu-common}} diff --git a/www/ja/@home.texy b/www/ja/@home.texy new file mode 100644 index 0000000000..a699d9ef37 --- /dev/null +++ b/www/ja/@home.texy @@ -0,0 +1,2 @@ +{{maintitle:Nette – PHPでの快適で安全なWebアプリケーション開発}} +{{description: Netteは、PHP向けの先進的で独立して使用可能なコンポーネントのファミリーです。それらに触発されてください。これらを組み合わせることで、世界で3番目に人気のあるフレームワークと評価されたフレームワークが形成されます。Netteの哲学は、生産性、ベストプラクティス、およびセキュリティに特に重点を置いています。}} diff --git a/www/ja/@menu-common.texy b/www/ja/@menu-common.texy new file mode 100644 index 0000000000..b1e59054ea --- /dev/null +++ b/www/ja/@menu-common.texy @@ -0,0 +1,22 @@ +はじめに +**** +- [なぜNetteを使うのか? |www:10-reasons-why-nette] +- [インストール |nette:installation] +- [最初のアプリケーションを作成しましょう! |quickstart:] + + +一般的なトピック +******** +- [パッケージリスト |www:packages] +- [メンテナンスとPHPバージョン |www:maintenance] +- [リリースノート |https://nette.org/releases] +- [新しいバージョンへの移行|migrations:en] +- [トラブルシューティング |nette:troubleshooting] +- [Netteを作成しているのは誰か |https://nette.org/contributors] +- [Netteの歴史 |history] +- [貢献する |contributing:] +- [開発を支援する |https://nette.org/cs/donate] +- [APIリファレンス |https://api.nette.org/] +- [ガイドとベストプラクティス |best-practices:] + +- [セキュリティ第一 |nette:vulnerability-protection] diff --git a/www/ja/donate.texy b/www/ja/donate.texy new file mode 100644 index 0000000000..4df7dfe729 --- /dev/null +++ b/www/ja/donate.texy @@ -0,0 +1,20 @@ +Netteの開発を支援する +************* + +
                                                                                                                            +Nette上に構築する誰もが、フレームワークが積極的に開発されることに関心を持っています。新しいPHPバージョンをサポートするために。バグが修正されるために。作業を容易にしたり、時間とお金を節約したりするさらなる革新をもたらすために。フレームワークが優れたドキュメントを持ち、記事、チュートリアル、ビデオのいずれかの形式で、その周りに役立つコンテンツが存在するために。 + +Netteの多くの部分は世界のトップクラスであり、今後もそうあり続けたいと考えています。 + +適切な資金調達なしでは、これらのいずれも保証できません。それでも、次のバージョンがリリースされることを確実に頼りにできるようにするには、ほんの少しのことしか必要ありません:毎月、少額の寄付でそれをサポートすることです。 + +参加して、Netteのパートナーになりましょう! + +これにより、あなたが依存しているプロジェクトの健全な機能を確保できます。そして同時に、**多くの独占的な利点を得る**ことができます(右側の列の*パートナーシップレベル*を参照)。ボーナスコンテンツへのアクセスを得ます。テクニカルサポートへ。あなたの問題の解決優先度を高めます。そして、あなたの会社を可視化し、開発者を引き付けます。 + +どのようにあなたを可視化しますか?例えば、このウェブサイト(つまり、このページ、ホームページ、ドキュメント、フォーラム、そしてあなたがリンクできる[特別ページ |https://nette.org/partner/vitalita])にあなたのロゴを掲載することによって。[求人情報 |https://forum.nette.org/cs/f30-prace-a-zakazky] を投稿したり、フォーラム([例 |https://forum.nette.org/cs/30798-problem-s-cizim-klicem-pri-mazani#p198093])やドキュメント([例 |https://doc.nette.org/cs/application/components#toc-flash-zpravy])で広告を掲載する機会があります。つまり、Nette開発者グループに最も効果的にリーチできる場所です。 + +パートナーには請求書を発行し、サポート費用を経費として計上できるようにします。これは、月次、四半期ごと、半年ごと、または年次で行われます。 + +{{include: buttons}} +
                                                                                                                            diff --git a/www/ja/history.texy b/www/ja/history.texy new file mode 100644 index 0000000000..1065a7240b --- /dev/null +++ b/www/ja/history.texy @@ -0,0 +1,36 @@ +Netteの歴史 +******** + +.[perex] +Netteの起源は2004年に遡ります。その作者であるDavid Grudlが、純粋なPHPではもはやアプリケーションを作成するには不十分になったため、適切なフレームワークを探し始めたときです。当時利用可能だったソリューションのどれも彼には合わず、彼は後にNetteと名付けられる新しいフレームワークの特徴を徐々に描き始めました。 + +当時、Symfony、Laravel、あるいはRuby on Railsのような現代的なフレームワークはまだ存在しませんでした。Javaの世界では、JSF(JavaServer Faces)フレームワークが標準であり、競合する.NETではASP.NET Webformsが標準でした。どちらも再利用可能なUIコンポーネントを使用してページを構築することを可能にしました。彼らの抽象化の方法と、セッションやいわゆるポストバックを使用してステートレスなHTTPプロトコル上にステートフル性を構築しようとする試みは、Davidによって誤っており、根本的に機能しないと考えられました。それらはユーザーと検索エンジンの両方に多くの困難を引き起こしました。例えば、リンクを保存した場合、後でその下に異なるコンテンツが見つかって驚くことがありました。 + +再利用可能なUIコンポーネントからページを構成するという可能性自体がDavidを魅了しました。彼は当時デスクトップアプリケーションを作成するための人気ツールだったDelphiからそれをよく知っていました。彼はDelphi用のオープンソースコンポーネントのマーケットプレイスが好きでした。したがって、彼は、逆にステートレスなHTTPと完全に調和して機能するコンポーネントフレームワークを作成する方法という問題を解決しようとしました。彼は、ユーザー、SEO、開発者にとってフレンドリーなコンセプトを探していました。そして、Netteが生まれ始めました。 + +.[note] +Netteという名前は、作者がバスルームでGilletteシェービングジェルの容器が*llette*だけが見えるように回転しているのを見たときに偶然生まれました。 + +何千時間もの研究、思考、書き直しが続きました。ブルノのどこかの村にある埃っぽいガレージで、将来のフレームワークの最初の輪郭が形成されました。アーキテクチャの基礎はMVCパターンとなり、これは当時忘れ去られたPHPフレームワークMojaviで使用され、後にRuby on Railsを巡る誇大宣伝のおかげで普及しました。インスピレーションの源の1つは、Honza Tichýの決して公開されなかったフレームワークphpBaseでさえありました。 + +作者のブログで、来るべきNetteに関する記事が出始めました。それがベイパーウェアであると冗談が言われました。しかし、2007年10月、プラハで開催されたPHPセミナーカンファレンスで、DavidはNetteを公に発表しました。ちなみに、このカンファレンスは1年後にWebExpoに発展し、後にヨーロッパ最大のITカンファレンスの1つとなりました。当時すでに、Netteは前述のコンポーネントモデル、双方向ルーター、プレゼンター間の特定のリンク方法など、多くの独自のコンセプトを誇っていました。フォーム、解決済みの認証、キャッシングなどがありました。Netteのすべては、今日まで元のコンセプトで使用されています。 + +.[note] +Netteでは、*controller*という用語の代わりに*presenter*が使用されます。コード内に*con*で始まる単語が多すぎたためです(controller、front controller、control、config、container、...) + +2007年末、David Grudlはコードも公開し、Netteバージョン0.7が日の目を見ました。フレームワークはすぐに大きな注目を集めました。その周りには熱心なプログラマのコミュニティが形成され、毎月Posobotaイベントで会合し始めました。コミュニティには、優れたツールPHPStanの作者であるOndřej Mirtesなど、今日の多くの著名人がいました。Netteの開発は急速に進み、次の2年間でバージョン0.8と0.9がリリースされ、フレームワークのほぼすべての現在の部分の基礎が築かれました。14年前にRuby on Rails用のHotwireやSymfony UX Turboを先取りしたAJAXスニペットを含みます。 + +しかし、当時のNetteには1つの重要なものが欠けていました。依存性注入コンテナ(DIC)です。Netteはいわゆる*サービスロケータ*を使用しており、意図はまさに依存性注入に移行することでした。しかし、そのようなものをどのように設計するのでしょうか?当時DIの経験がなかったDavid Grudlは、約半年間DIを使用していたVašek Purchartと昼食をとりました。彼らは一緒にトピックについて議論し、DavidはNette DIの開発を開始しました。これは、アプリケーションの設計方法に関する考え方を完全に変えたライブラリです。DIコンテナは、フレームワークの最も成功した部分の1つになりました。そして後に、Neon形式とSchemaライブラリという2つのスピンオフを生み出しました。 + +.[note] +依存性注入への移行にはかなりの時間がかかり、Netteの新しいバージョンを数年待つ必要がありました。そのため、ついにリリースされたとき、バージョン番号は直接2でした。したがって、Netteバージョン1は存在しません。 + +2012年、Netteはバージョン2.0で現代史を開始しました。また、Nette Databaseももたらしました。これには、今日Explorerと呼ばれる、データベースを扱うための非常に便利なツールが含まれていました。このライブラリは元々、David Grudlの隣人であり、人気ツールAdminerの作者であるJakub Vránaによってプログラムされました。その後、Jan Škrášekが3年間そのさらなる開発を担当しました。 + +2014年、Nette 2.1がリリースされ、すぐにNette 2.2が続きました。どうしてそんなことが可能だったのでしょうか?バージョン2.2はバージョン2.1と同じでしたが、20の個別のパッケージに分割されていました。PHPの世界ではComposerツールが定着し、ライブラリ作成の考え方を変えました。したがって、Netteはモノリスであることをやめ、より小さく独立した部分に分解されました。それぞれが独自のリポジトリ、課題追跡システム、独自の開発ペースとバージョニングを持っています。したがって、Netteでは、モノリシックフレームワークで一般的な、パッケージにまったく変更がないにもかかわらず新しいバージョンがリリースされるという不条理は発生しません。Gitリポジトリ自体の分割には、数週間の準備と数百時間のマシン時間が必要でした。 + +Netteはまた、Sitepoint誌が主催する最高のPHPフレームワークに関する世界的なアンケートで、驚くべき3位にランクインしました。 + + +{{toc:no}} +{{leftbar: @menu-common}} diff --git a/www/ja/license.texy b/www/ja/license.texy new file mode 100644 index 0000000000..45a00193f6 --- /dev/null +++ b/www/ja/license.texy @@ -0,0 +1,44 @@ +ライセンスポリシー +********* + +Nette Frameworkは、誰もが使用できるようにフリーソフトウェアとして配布されています。ライセンス[New BSD |#New BSD License] または [#GNU General Public License (GPL)] バージョン2または3のどちらがより適しているかを選択できます。 + +BSDライセンスは、理解しやすく、フレームワークでできることに関してほとんど制限がないため、ほとんどのプロジェクトに推奨されます。Netteは商用プロジェクトでも使用できます。ただし、GPLがプロジェクトにより適している場合は、それを選択してください。どちらを選択したかを誰かに通知する必要はありません。常に元の著作権表示を保持してください。 + +「Nette Framework」という名前は保護された商標であり、その使用には特定の制限があることを知っておく必要があります。したがって、プロジェクト名やトップレベルドメイン名に「Nette」を使用しないでください。代わりに、独自の基盤に立つ名前を選択してください。プロジェクトが良ければ、評判を築くのに時間はかかりません。 + +Nette Frameworkに満足していただければ、そしてそう信じていますが、[寄付でサポート |donate] することができます。ありがとうございます。 + + +New BSD License +--------------- + +Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com) All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of "Nette Framework" nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +GNU General Public License (GPL) +-------------------------------- + +GPLライセンスの文言は非常に長いため、以下にリンクを示します: + +- GPL version 2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +- GPL version 3: https://www.gnu.org/licenses/gpl-3.0.html + + +{{toc:yes}} +{{priority: -2}} diff --git a/www/ja/maintenance.texy b/www/ja/maintenance.texy new file mode 100644 index 0000000000..c0b0195539 --- /dev/null +++ b/www/ja/maintenance.texy @@ -0,0 +1,27 @@ +メンテナンスとPHP互換性 +************* + +.[perex] +Netteは、個々のリリースのサポート期間が非常に長いフレームワークです。各ブランチはLTS(長期サポートリリース)であり、最低2年間のサポートがあります。 + +各バージョンは、最初の安定版リリースから1年間(またはそれ以上)積極的に維持されます。 重大なセキュリティバグは2年間修正されます。 + + +Netteリリース予定表 +============ + +{{include: doc-roadmap-table}} + + +PHP互換性 +====== + +互換性は常に各シリーズの最新リリースに適用されます。 + + +{{include: doc-roadmap-versions}} + + +{{leftbar: @menu-common}} +{{toc: no}} +{{description: Netteのリリース、ロードマップ、メンテナンス表、PHP互換性}} diff --git a/www/ja/packages.texy b/www/ja/packages.texy new file mode 100644 index 0000000000..f458e2463c --- /dev/null +++ b/www/ja/packages.texy @@ -0,0 +1,27 @@ +Netteパッケージ一覧 +************ + +| **Application**:[application:how-it-works] | ウェブアプリケーションのコア | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] +| **Assets**:[assets:]| 静的ファイル管理 | [GitHub |https://github.com/nette/assets] [API |https://api.nette.org/assets/] +| **Bootstrap**:[bootstrap:] | ウェブアプリケーションのブートストラップ | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] +| **Caching**:[caching:] | ストレージ付きキャッシング層 | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] +| **Component Model**:[component-model:] | コンポーネントシステムの基盤 | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] +| **DI**:[dependency-injection:] | 依存性注入コンテナ | [GitHub |https://github.com/nette/di] [API |https://api.nette.org/di/] +| **Database**:[database:] | データベース層 | [GitHub |https://github.com/nette/database] [API |https://api.nette.org/database/] +| **Forms**:[forms:] | 便利で安全なウェブフォーム | [GitHub |https://github.com/nette/forms] [API |https://api.nette.org/forms/] +| **Http**:[http:] | HTTPリクエスト&レスポンスをカプセル化する層 | [GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] +| **Latte**:[latte:] | 素晴らしいテンプレートシステム | [GitHub |https://github.com/nette/latte] [API |https://api.nette.org/latte/] +| **Mail**:[mail:] | メール送信 | [GitHub |https://github.com/nette/mail] [API |https://api.nette.org/mail/] +| **Neon**:[neon:] | [NEON |https://ne-on.org] 形式の読み書き | [GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] +| **Php Generator**:[php-generator:] | PHPコードジェネレータ | [GitHub |https://github.com/nette/php-generator] [API |https://api.nette.org/php-generator/] +| **Robot Loader**:[robot-loader:] | 最も快適なオートローディング | [GitHub |https://github.com/nette/robot-loader] [API |https://api.nette.org/robot-loader/] +| **Routing**:[application:routing] | ルーティング | [GitHub |https://github.com/nette/routing] [API |https://api.nette.org/routing/] +| **Safe Stream**:[safe-stream:] | 安全なアトミックファイル操作 | [GitHub |https://github.com/nette/safe-stream] [API |https://api.nette.org/safe-stream/] +| **Schema**:[schema:] | ユーザーデータの検証 | [GitHub |https://github.com/nette/schema] [API |https://api.nette.org/schema/] +| **Security**:[security:authentication] | アクセス権管理 | [GitHub |https://github.com/nette/security] [API |https://api.nette.org/security/] +| **Tester**:[tester:] | PHPでの快適なユニットテスト | [GitHub |https://github.com/nette/tester] [API |https://api.nette.org/tester/] +| **Tracy**:[tracy:] | あなたが恋に落ちるデバッグツール ♥ | [GitHub |https://github.com/nette/tracy] [API |https://api.nette.org/tracy/] +| **Utils**:[utils:] | 基本的なクラスとツール | [GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] + +{{leftbar: @menu-common}} +{{toc: no}} diff --git a/www/pl/10-reasons-why-nette.texy b/www/pl/10-reasons-why-nette.texy index 7254d3c392..73ab5d6576 100644 --- a/www/pl/10-reasons-why-nette.texy +++ b/www/pl/10-reasons-why-nette.texy @@ -1,53 +1,93 @@ -Dlaczego warto korzystać z Nette? -********************************* +7 powodów, dla których warto używać Nette +*****************************************
                                                                                                                            -Masz dość użerania się z powtarzalnymi zadaniami, tysiącami drobiazgów, które odciągają Cię od pracy i sprawiają, że programowanie staje się nudnym zajęciem? Trafiłeś we właściwe miejsce! **Framework ułatwi Ci pracę, będziesz pisał mniej, miał bardziej przejrzysty kod i cieszył się swoją pracą.** Otrzymasz: +Wyobraź sobie framework PHP, który pozwala Ci skupić się na tym, co robisz najlepiej. Prowadzi Cię do pisania czystego kodu. Sam dba o bezpieczeństwo. Przestań marzyć i poznaj Nette. Wyrusz w podróż, która otworzy Ci drzwi do nowych możliwości rozwoju. Powiemy sobie: -- doskonały system szablonów -- niezrównane narzędzia do debugowania -- niezwykle wydajna warstwa bazy danych -- zaawansowana ochrona przed podatnością na zagrożenia -- nowoczesny framework z obsługą HTML5, AJAX lub SEO -- z wysokiej jakości dokumentacją i najbardziej aktywną społecznością w kraju -- z dojrzałym i czystym projektem zorientowanym obiektowo -- prowadzące do dobrych nawyków i dające wystarczającą swobodę +- jak tworzyć strony internetowe z maksymalną wygodą +- jak pisać elegancki kod +- co oznacza mądrość „mniej kodu = większe bezpieczeństwo” +- jak budować strony internetowe jak z klocków +- i jak stać się częścią odnoszącej sukcesy społeczności
                                                                                                                            -I to wszystko zupełnie za darmo. Warto spróbować, prawda? +Nette wnosi wygodę i efektywność do świata deweloperów internetowych dzięki innowacyjnym narzędziom i technikom. Jakie są kluczowe cechy, które czynią Nette unikalnym i niezbędnym elementem zestawu deweloperskiego? Przyjrzyjmy się im! -Czym Nette Framework różni się od innych frameworków? The **Nette Framework jest zbudowany tak, aby był jak najbardziej użyteczny i przyjazny dla użytkownika.** Jest to framework, z którym nie tylko łatwo, ale i przyjemnie się pracuje. Daje ci jasną i oszczędną składnię, mieści się w programowaniu i debugowaniu, pozwala skupić się na kreatywnej stronie rozwoju i nie dodaje zmarszczek. Eliminuje zagrożenia dla bezpieczeństwa. Możesz tworzyć e-sklepy, wiki, blogi, CMS-y, co tylko przyjdzie Ci do głowy. +#1 Nette cię rozpieszcza +------------------------ -Nette Framework [jest używany przez duże firmy |https://builtwith.nette.org], takie jak T-Systems, GE Money, Mladá fronta, VLTAVA-LABE-PRESS, Internet Info, DHL, Logio, ESET, Actum, Slevomat, Socialbakers, SUPRAPHON, zasila nawet stronę internetową byłego prezydenta Václava Klausa. Został on uznany za najpopularniejszy i najczęściej używany framework w Czechach w ankiecie przeprowadzonej przez serwer [Zdroják |https://www.zdrojak.cz/clanky/vysledky-technologie-na-ceskem-webu/]. +Kiedy dwadzieścia lat temu zaczął się rodzić framework Nette, wszystko kręciło się wokół jednego celu: Jak tworzyć strony internetowe jak najwygodniej? Jak jak najbardziej uprzyjemnić pracę programistom? Jak uczynić tworzenie stron internetowych seksownym? -Jeśli poznasz Nette Framework, nie zabraknie Ci ciekawych ofert pracy. +Nette tym podejściem przemówiło do wielu programistów i szybko zyskało popularność. Wtedy nazywaliśmy tę filozofię Netteway, a dziś istnieje dla niej termin *Developer Experience* (DX). Nette to framework, który ma DX w swoim DNA. Różnicę poczujesz w tysiącu i jednej rzeczy – od zupełnych drobnostek po fundamentalne innowacje. Pozwól, aby framework Cię rozpieszczał. -Chcę zacząć! .[#toc-get-on-board] ---------------------------------- +#2 Nette prowadzi cię do czystego kodu +-------------------------------------- + +Chcesz pisać czysty kod? Mieć poprawnie zaprojektowane aplikacje? Kto by nie chciał! I właśnie tutaj zaczyna się rola frameworku. Jeśli sam nie daje przykładu, nie można stworzyć świetnie zaprojektowanej aplikacji. + +Nette daje przykład. Jest mentorem uczącym dobrych nawyków i pisania kodu według sprawdzonych metodyk. Jako pionier i ewangelizator dependency injection oferuje solidną podstawę dla zrównoważonych, rozszerzalnych i łatwych do odczytania aplikacji. Nette jest zaprojektowane tak, aby było zrozumiałe dla początkujących, jednocześnie oferując wystarczającą głębię dla doświadczonych deweloperów. + +Społeczność wokół Nette wychowała wiele osobistości, które dziś stoją za udanymi i ważnymi projektami. Dla tysięcy programistów Nette stało się mentorem na ich drodze do profesjonalnego rozwoju. Dołącz i odkryj, jak Nette pozytywnie wpłynie na jakość Twojego kodu i aplikacji. + + +#3 Niezawodny strażnik twoich aplikacji +--------------------------------------- + +Nette chroni Twoje aplikacje. Przez lata zyskało reputację narzędzia, które traktuje bezpieczeństwo niezwykle poważnie. Zapewnia przemyślane zabezpieczenia przed podatnościami. Zawsze dba o to, aby ułatwić programistom pracę, ale nigdy kosztem bezpieczeństwa. + +Jego motto "mniej kodu = wystarczające bezpieczeństwo" oznacza, że poszczególne elementy zachowują się bezpiecznie już w podstawie. Nie trzeba aktywować elementów bezpieczeństwa dopisując dodatkowy kod. Nie możesz więc o tym zapomnieć ani obawiać się, że coś przeoczyłeś. Programiści często nawet nie wiedzą, ile rzeczy związanych z bezpieczeństwem Nette robi za nich, i są zaskoczeni, gdy się o tym dowiadują. + +Przedstawiamy framework, który prowadzi Cię do czystego kodu i jednocześnie czuwa nad bezpieczeństwem Twoich aplikacji. Nette to niezawodny partner, który pozwoli Ci skupić się na tworzeniu świetnych aplikacji internetowych ze spokojem ducha. + + +#4 Buduj strony internetowe jak z klocków +----------------------------------------- + +W Nette budujesz strony z ponownie używalnych komponentów UI. Przypomina to rozwój aplikacji desktopowych, a Nette z powodzeniem przeniosło to podejście na strony internetowe. Potrzebujesz w administracji datagridu? Wystarczy znaleźć go na rynku komponentów open source, zainstalować i po prostu wstawić na stronę. Ponadto możesz tworzyć własne komponenty dla powtarzających się elementów na stronach, eliminując w ten sposób duplikaty i poprawiając organizację kodu. -Postępuj zgodnie z instrukcjami krok po kroku, [aby stworzyć swoją pierwszą aplikację |quickstart:]. Kompletny [przewodnik programisty |@home] i poręczna [dokumentacja API |https://api.nette.org/] z przeglądem klas i metod będą zawsze pod ręką. +Ta unikalna cecha odróżnia Nette od wszystkich innych znaczących graczy na rynku. Pozwoli Ci efektywnie tworzyć i utrzymywać aplikacje internetowe. Z Nette praca z UI staje się płynnym i przyjemnym doświadczeniem. -W razie niejasności można skorzystać ze strony [FAQ |nette:troubleshooting] lub [czeskiego forum dyskusyjnego |https://forum.nette.org/en/]. -Czy samokształcenie wydaje się być nieefektywnym sposobem? Rozumiemy i oferujemy [szkolenia Nette Framework |https://www.skoleniphp.cz/skoleni-nette-vyvoj-webovych-aplikaci], możesz przeczytać [referencje |https://www.skoleniphp.cz/ohlasy]. +#5 Elastyczny zestaw pakietów +----------------------------- + +Nette to zestaw [pakietów do samodzielnego użytku |www:packages]. Należą do nich uzależniające [narzędzie do debugowania Tracy |tracy:], [system szablonów nowej generacji Latte |latte:], [doskonały Kontener Wstrzykiwania Zależności |dependency-injection:], [formularze |forms:] i wiele innych. Każdy pakiet ma czytelną, szczegółową dokumentację i znajduje się w osobnym repozytorium na GitHubie. Pakiety możesz używać samodzielnie lub łączyć z innymi narzędziami i technologiami, których już używasz. Na przykład Latte można wdrożyć w WordPressie czy Slim Frameworku, kontener DI może być rdzeniem firmowego frameworku, a Tracy będzie wizualizować komunikaty o błędach. + +Lub możesz użyć Nette jako całości, jako frameworku, i stworzyć w nim kompletną aplikację internetową. Niezależnie od tego, czy rozwijasz mały projekt osobisty, czy solidną aplikację korporacyjną, Nette będzie Twoim niezawodnym partnerem. + + +#6 Stabilność i innowacja +------------------------- + +Nette to dojrzały i sprawdzony framework z wieloletnią historią. Mimo to utrzymuje się zwinny i elastyczny dzięki inteligentnemu projektowi. Użytkownicy doceniają, że jest to mały i elastyczny framework, a nie moloch. + +Zawsze jest z wyprzedzeniem przygotowany na nowe wersje PHP i uwzględnia najnowsze innowacje w dziedzinie rozwoju aplikacji internetowych. Jest to kluczowe dla minimalizacji długu technologicznego. + +Nette to zatem połączenie wieloletniej stabilności i innowacji, które pozwoli Ci polegać na sprawdzonym rozwiązaniu, jednocześnie mogąc korzystać z najnowszych technologii i trendów. Z Nette masz pewność, że Twój projekt będzie zbudowany na fundamentach, które nieustannie nadążają za szybko rozwijającym się światem rozwoju internetowego. + + +#7 Bądź w najlepszym towarzystwie +--------------------------------- + +Nette Framework jest popularny wśród profesjonalistów. Polegają na nim [znaczące firmy |https://builtwith.nette.org] takie jak O2, BOSCH, ESET, Zásilkovna, DHL, SUPRAPHON i setki innych. W kilku ankietach zwyciężył jako najpopularniejszy i najczęściej używany framework w Czechach. +Zacznij z Nette, a otworzą się przed Tobą nowe możliwości zawodowe. Zyskasz nie tylko potężne narzędzie, ale także najaktywniejszą społeczność w Czechach, która jest gotowa wspierać Cię na drodze do rozwoju zawodowego. Regularnie spotyka się na [Posobotach |www.posobota.cz], wydarzeniach, na których wymienia się doświadczeniami. Przyjdź się z nami spotkać! -Chcę więcej informacji! .[#toc-more-information] ------------------------------------------------- -Mamy [blog pełen porad |https://blog.nette.org], zbiór różnych [akcesoriów i komponentów |https://componette.org] do wykorzystania w Waszych aplikacjach. +Odkryj Nette +------------ -Ale to nie wszystko. Społeczność Nette Framework spotyka się co miesiąc w [Last Saturdays |www.posobota.cz], aby dzielić się swoimi doświadczeniami. Wpadnij kiedyś na piwo! +Dołącz do tysięcy zadowolonych deweloperów, którzy już odkryli zalety Nette i zacznij pisać czystszy, bezpieczniejszy i bardziej efektywny kod z tym wyjątkowym frameworkiem. +Stwórz [zgodnie z instrukcją |quickstart:] pierwszą aplikację, krok po kroku. Zawsze będziesz miał pod ręką [obszerną dokumentację |nette:] i praktyczny [przegląd API |https://api.nette.org]. Odwiedź nasz [blog pełen wskazówek |https://blog.nette.org] i kolekcję [dodatków i komponentów |https://componette.org] rozszerzających możliwości Nette. -Chcę się zaangażować! .[#toc-get-involved] ------------------------------------------- +Masz pytania? Zwróć się na stronę z [często zadawanymi pytaniami|nette:troubleshooting] lub dyskutuj na [czeskim forum|https://forum.nette.org/cs/]. Preferujesz szkolenia osobiste? Oferujemy [szkolenie Nette Framework |https://www.skoleniphp.cz/skoleni-nette-vyvoj-webovych-aplikaci] z [doskonałymi opiniami |https://www.skoleniphp.cz/ohlasy]. -Będzie nam bardzo miło, jeśli dołożysz swoją cegiełkę do rozwoju ram. Na przykład poprzez polecenie frameworka, umieszczenie [ikony |www:en:logo] na swojej stronie internetowej lub [wsparcie finansowe rozwoju |www:en:donate]. Dziękuję ♥. +**Poznaj framework, który Cię będzie rozpieszczał, prowadził i inspirował.** {{leftbar: @menu-common}} diff --git a/www/pl/@home.texy b/www/pl/@home.texy index 77c2faf2be..38e417956a 100644 --- a/www/pl/@home.texy +++ b/www/pl/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Nette - Wygodne i bezpieczne tworzenie aplikacji internetowych w PHP}} -{{description: Nette to rodzina dojrzałych i samodzielnych komponentów dla PHP. Podnieś się nimi. Razem tworzą ramę ocenianą jako 3. najpopularniejszą na świecie. Filozofia Nette kładzie ogromny nacisk na wydajność, najlepsze praktyki i bezpieczeństwo.}} +{{maintitle:Nette – Wygodny i bezpieczny rozwój aplikacji internetowych w PHP}} +{{description: Nette to rodzina zaawansowanych i samodzielnie użytecznych komponentów dla PHP. Daj się nimi zachwycić. Razem tworzą framework, oceniony jako 3. najpopularniejszy na świecie. Filozofia Nette kładzie szczególny nacisk na produktywność, najlepsze praktyki i bezpieczeństwo.}} diff --git a/www/pl/@menu-common.texy b/www/pl/@menu-common.texy index 7764c253f8..2553daf369 100644 --- a/www/pl/@menu-common.texy +++ b/www/pl/@menu-common.texy @@ -1,20 +1,22 @@ -Wstęp -***** -- [Dlaczego warto używać Nette? |www:10-reasons-why-nette] +Wprowadzenie +************ +- [Dlaczego używać Nette? |www:10-reasons-why-nette] - [Instalacja |nette:installation] -- [Stwórz swoją pierwszą aplikację! |quickstart:] +- [Pisanie pierwszej aplikacji! |quickstart:] -Tematy ogólne +Ogólne tematy ************* - [Lista pakietów |www:packages] -- [Konserwacja i PHP |www:maintenance] -- [Uwagi do wydania |https://nette.org/releases] -- [Przewodnik po aktualizacji |migrations:en] -- [Rozwiązywanie problemów |nette:Troubleshooting] +- [Utrzymanie i wersje PHP |www:maintenance] +- [Informacje o wydaniu |https://nette.org/releases] +- [Migracja do nowszych wersji|migrations:en] +- [Rozwiązywanie problemów |nette:troubleshooting] - [Kto tworzy Nette |https://nette.org/contributors] - [Historia Nette |history] - [Zaangażuj się |contributing:] -- [Rozwój sponsora |https://nette.org/en/donate] -- [Odnośnik do API |https://api.nette.org] -- [Najlepsze praktyki |best-practices:] +- [Wspieraj rozwój |https://nette.org/cs/donate] +- [Referencja API |https://api.nette.org/] +- [Poradniki i najlepsze praktyki |best-practices:] + +- [Bezpieczeństwo przede wszystkim |nette:vulnerability-protection] diff --git a/www/pl/donate.texy b/www/pl/donate.texy new file mode 100644 index 0000000000..6b55f4558a --- /dev/null +++ b/www/pl/donate.texy @@ -0,0 +1,20 @@ +Wspieraj rozwój Nette +********************* + +
                                                                                                                            +Każdy, kto buduje na Nette, jest zainteresowany aktywnym rozwojem frameworku. Aby wspierał nowe wersje PHP. Aby naprawiano błędy. Aby pojawiały się kolejne nowości, które ułatwią pracę lub zaoszczędzą czas i pieniądze. Aby framework miał świetną dokumentację i istniała wokół niego użyteczna treść, czy to w formie artykułów, poradników czy filmów. + +Wiele części Nette reprezentuje światową czołówkę i chcemy, aby tak pozostało. + +Bez odpowiedniego finansowania nic z tego nie da się zapewnić. Tymczasem, aby móc polegać na tym, że pojawią się kolejne wersje, wystarczy naprawdę niewiele: abyś wspierał go co miesiąc choćby niewielką kwotą finansową. + +Dołącz i zostań partnerem Nette! + +Zapewnisz w ten sposób zdrowe funkcjonowanie projektu, na którym polegasz. A jednocześnie **zyskasz cały szereg ekskluzywnych korzyści** (zobacz *Poziomy partnerstwa* w prawej kolumnie). Zyskasz dostęp do treści bonusowych. Do wsparcia technicznego. Zwiększysz priorytet rozwiązywania Twoich issues. A także zwiększysz widoczność swojej firmy i przyciągniesz do siebie deweloperów. + +Jak zwiększymy Twoją widoczność? Na przykład umieszczając Twoje logo na tej stronie (tj. na tej stronie, na stronie głównej, w dokumentacji, na forum i na [specjalnej stronie |https://nette.org/partner/vitalita], do której możesz linkować). Będziesz miał możliwość wstawiania [ofert pracy |https://forum.nette.org/cs/f30-prace-a-zakazky], reklamowania się na forum ([przykład |https://forum.nette.org/cs/30798-problem-s-cizim-klicem-pri-mazani#p198093]) lub w dokumentacji ([przykład |https://doc.nette.org/cs/application/components#toc-flash-zpravy]), czyli w miejscach o absolutnie najlepszym zasięgu wśród grupy deweloperów Nette. + +Partnerom wystawiamy faktury, aby mogli zaliczyć wsparcie do kosztów, miesięcznie, kwartalnie, półrocznie lub rocznie. + +{{include: buttons}} +
                                                                                                                            diff --git a/www/pl/history.texy b/www/pl/history.texy index 05b44c07ef..4982c3e9af 100644 --- a/www/pl/history.texy +++ b/www/pl/history.texy @@ -2,34 +2,34 @@ Historia Nette ************** .[perex] -Początki Nette sięgają 2004 roku, kiedy to jego autor David Grudl zaczął szukać odpowiedniego frameworka, w którym można by pisać aplikacje, gdyż czyste PHP już nie wystarczało. Żadne z dostępnych wówczas rozwiązań nie odpowiadało mu, więc stopniowo zaczął zarysowywać cechy nowego frameworka, który później otrzymał nazwę Nette. +Początek Nette sięga roku 2004, kiedy jego autor David Grudl zaczął szukać odpowiedniego frameworku, w którym mógłby pisać aplikacje, ponieważ czyste PHP już do tego nie wystarczało. Żadne z dostępnych wówczas rozwiązań mu nie odpowiadało, więc zaczął stopniowo szkicować zarysy nowego frameworku, który później otrzymał nazwę Nette. -Wtedy jeszcze nie istniały obecne frameworki takie jak Symfony, Laravel czy Ruby on Rails. W świecie Javy standardem był JSF (JavaServer Faces), a w konkurencyjnym świecie .NET - ASP.NET Webforms. Oba umożliwiały budowanie stron z wykorzystaniem komponentów UI wielokrotnego użytku. David uznał ich metody abstrakcji i próby stworzenia bezpaństwowości nad bezpaństwowym protokołem HTTP za pomocą sesji lub postbacks za wadliwe i fundamentalnie zepsute. Sprawiały one wiele trudności użytkownikom i wyszukiwarkom. Na przykład, jeśli zapisałeś link, byłeś zaskoczony, że później znalazłeś pod nim inne treści. +W tamtym czasie nie istniały jeszcze obecne frameworki takie jak Symfony, Laravel czy Ruby on Rails. W świecie Javy standardem był framework JSF (JavaServer Faces), a w konkurencyjnym .NET - ASP.NET Webforms. Oba pozwalały budować strony za pomocą ponownie używalnych komponentów UI. Ich sposoby abstrakcji i próby stworzenia stanowości nad bezstanowym protokołem HTTP za pomocą sesji lub tzw. postbacku David uważał za błędne i od podstaw niefunkcjonalne. Powodowały one szereg trudności użytkownikom i wyszukiwarkom. Na przykład, jeśli zapisałeś link, później z zaskoczeniem znajdowałeś pod nim inną treść. -Sama możliwość komponowania stron z komponentów UI wielokrotnego użytku fascynowała Davida, a znał ją dobrze z Delphi, popularnego wówczas narzędzia do tworzenia aplikacji desktopowych. Podobały mu się targowiska z komponentami opensource dla Delphi. Próbował więc rozwiązać problem, jak stworzyć ramy komponentów, które z kolei działałyby w pełnej harmonii z bezpaństwowym HTTP. Szukał koncepcji, która będzie przyjazna dla użytkowników, SEO i deweloperów. I tak narodziła się Nette. +Sama możliwość składania stron z ponownie używalnych komponentów UI fascynowała Davida, dobrze znał ją z Delphi, popularnego wówczas narzędzia do tworzenia aplikacji desktopowych. Podobały mu się rynki z komponentami open source dla Delphi. Starał się więc rozwiązać pytanie, jak stworzyć framework komponentowy, który działałby w całkowitej zgodzie z bezstanowym HTTP. Szukał koncepcji, która byłaby przyjazna dla użytkowników, SEO i deweloperów. I tak zaczęło rodzić się Nette. .[note] -Imię Nette powstało przypadkowo w łazience, kiedy autorka zauważyła pojemnik z żelem do golenia Gillette, obrócony tak, że widać było tylko *llette*. +Nazwa Nette powstała przypadkiem w łazience, gdy autor zauważył pojemnik z żelem do golenia Gillette, obrócony tak, że widać było tylko *llette*. -Potem nastąpiły tysiące godzin badań, myślenia i przepisywania. W zakurzonym garażu w wiosce gdzieś pod Brnem powstały pierwsze zarysy przyszłej ramy. Podstawą architektury był wzorzec MVC, wykorzystywany wówczas przez zapomniany dziś framework PHP Mojavi, a później spopularyzowany przez szum wokół Ruby on Rails. Jednym ze źródeł inspiracji był nawet nigdy nie opublikowany framework phpBase autorstwa Honzy Tichego. +Nastąpiły tysiące godzin badań, przemyśleń i przepisywania. W zakurzonym garażu we wsi gdzieś za Brnem powstawały pierwsze zarysy przyszłego frameworku. Podstawą architektury stał się wzorzec MVC, który wówczas używał dziś już zapomniany framework PHP Mojavi, a później został spopularyzowany dzięki szumowi wokół Ruby on Rails. Jednym ze źródeł inspiracji był nawet nigdy nieopublikowany framework phpBase Honzy Tichého. -Artykuły o nadchodzącym Nette zaczęły pojawiać się na blogu autora. Żartowano, że chodzi o vaporware. Ale potem, w październiku 2007 roku, na konferencji Prague PHP Seminar, David publicznie przedstawił Nette. Nawiasem mówiąc, ta konferencja rok później wyewoluowała w WebExpo, później jedną z największych konferencji IT w Europie. Już wtedy Nette mogło pochwalić się wieloma oryginalnymi koncepcjami, takimi jak wspomniany model komponentowy, dwukierunkowy router, specyficzny sposób łączenia prezenterów itp. Miał formularze, uwierzytelnianie, buforowanie itp. Wszystko jest do dziś używane w Nette w swojej pierwotnej koncepcji. +Na blogu autora zaczęły pojawiać się artykuły o planowanym Nette. Żartowano, że to vaporware. Ale potem w październiku 2007 roku na praskiej konferencji PHP Seminář David publicznie zaprezentował Nette. Nawiasem mówiąc, z tej konferencji rok później wyewoluowało WebExpo, później jedna z największych konferencji IT w Europie. Już wtedy Nette pochwaliło się szeregiem oryginalnych koncepcji, jak wspomniany model komponentowy, dwukierunkowy router, specyficzny sposób linkowania między prezenterami itp. Miało formularze, rozwiązaną autentykację, cache itp. Wszystko to w Nette jest używane w pierwotnym ujęciu do dziś. .[note] -W Nette zamiast *kontrolera* użyto terminu *prezenter*, ponieważ w kodzie było podobno za dużo słów zaczynających się od *con* (controller, front controller, control, config, container, ...). +W Nette zamiast pojęcia *controller* używa się *presenter*, ponieważ w kodzie było podobno zbyt wiele słów zaczynających się na *con* (controller, front controller, control, config, container, ...) -Pod koniec 2007 roku David Grudl opublikował kod i wydano Nette 0.7. Wokół niego uformowała się entuzjastyczna społeczność programistów, która zaczęła spotykać się co miesiąc na imprezie Posobota. W skład społeczności wchodziło wielu dzisiejszych luminarzy, takich jak Ondřej Mirtes, autor świetnego narzędzia PHPStan. Rozwój Nette posuwał się do przodu i w ciągu kolejnych dwóch lat wydano wersje 0.8 i 0.9, kładąc podwaliny pod niemal wszystkie dzisiejsze części frameworka. W tym AJAX snippets, które poprzedzają Hotwire dla Ruby on Rails lub Symfony UX Turbo o 14 lat. +Pod koniec 2007 roku David Grudl opublikował również kod i światło dzienne ujrzała wersja Nette 0.7. Framework natychmiast przyciągnął ogromną uwagę. Wokół niego utworzyła się entuzjastyczna społeczność programistów, która zaczęła spotykać się co miesiąc na wydarzeniu Posobota. W społeczności było wiele dzisiejszych osobistości, na przykład Ondřej Mirtes, autor świetnego narzędzia PHPStan. Rozwój Nette pędził naprzód i w kolejnych dwóch latach ukazały się wersje 0.8 i 0.9, gdzie położono fundamenty pod niemal wszystkie dzisiejsze części frameworku. W tym snippety AJAXowe, które o 14 lat wyprzedziły Hotwire dla Ruby on Rails czy Symfony UX Turbo. -Ale jednej zasadniczej rzeczy brakowało wtedy Nette. Dependecy injection container (DIC). Nette używało *service locator*, a intencją było przejście na dependecy injection. Ale jak zaprojektować coś takiego? David Grudl, który nie miał wtedy doświadczenia z DI, poszedł na lunch z Vaskiem Purchartem, który używał DI od około pół roku. Razem przedyskutowali temat i David rozpoczął pracę nad Nette DI, biblioteką, która całkowicie zrewolucjonizowała sposób myślenia o projektowaniu aplikacji. Kontener DI stał się jedną z najbardziej udanych części frameworka. I dał początek dwóm spin-offom: formatowi Neon i bibliotece Schema. +Jednej zasadniczej rzeczy jednak w ówczesnym Nette brakowało. Kontenera Wstrzykiwania Zależności (DIC). Nette używało tzw. *service locator* i zamiarem było przejście właśnie na wstrzykiwanie zależności. Ale jak zaprojektować coś takiego? David Grudl, który wówczas nie miał doświadczenia z DI, poszedł na obiad z Vaškem Purchartem, który używał DI od około pół roku. Wspólnie przedyskutowali temat i David rozpoczął pracę nad Nette DI, biblioteką, która całkowicie zmieniła sposób myślenia o projektowaniu aplikacji. Kontener DI stał się jedną z najbardziej udanych części frameworku. I dał później początek dwóm spin-offom: formatowi Neon i bibliotece Schema. .[note] -Przejście na wstrzykiwanie zależności zajęło dużo czasu, a nowa wersja Nette była w trakcie tworzenia kilka lat. Dlatego też, gdy w końcu się ukazał, został opatrzony numerem 2. Tak więc Nette w wersji 1 nie istnieje. +Przejście na wstrzykiwanie zależności wymagało sporo czasu i na nową wersję Nette trzeba było czekać kilka lat. Dlatego, gdy w końcu się ukazała, nosiła od razu numer 2. Wersja Nette 1 więc nie istnieje. -Nette rozpoczęło swoją nowoczesną historię w 2012 roku od wersji 2.0. Przyniósł również Nette Database, który zawierał niezwykle poręczne narzędzie bazodanowe, zwane teraz Explorerem. Biblioteka ta została pierwotnie zaprogramowana przez Jakuba Vrána, sąsiada Davida Grudela i autora popularnego narzędzia Adminer. Jego dalszy rozwój przejął następnie na trzy lata Jan Škrášek. +Nette w 2012 roku wersją 2.0 rozpoczęło swoją nowoczesną historię. Przyniosło również Nette Database, której częścią był niezwykle poręczny narzędzie do pracy z bazą danych, dziś nazywane Explorer. Tę bibliotekę pierwotnie zaprogramował Jakub Vrána, sąsiad Davida Grudla i autor popularnego narzędzia Adminer. Jej dalszym rozwojem zajął się następnie na trzy lata Jan Škrášek. -W 2014 roku ukazała się wersja Nette 2.1, a wkrótce po niej Nette 2.2. Wersja 2.2 była taka sama jak wersja 2.1, tylko podzielona na dwadzieścia oddzielnych pakietów. Narzędzie Composer zakorzeniło się w świecie PHP i zmieniło sposób myślenia o tworzeniu bibliotek. Nette przestało być monolitem i rozpadło się na mniejsze niezależne części. Każdy z nich ma swoje własne repozytorium, issue tracker i własne tempo rozwoju i wersjonowania. Dzięki temu Nette nie musi przechodzić przez absurdy powszechne w monolitycznych frameworkach, gdzie wychodzi nowa wersja pakietu, mimo że nic się nie zmieniło. Faktyczne rozdzielenie repozytoriów Git wymagało kilku tygodni przygotowań i setek godzin czasu maszynowego. +W 2014 roku ukazało się Nette 2.1, w krótkim czasie nastąpiło Nette 2.2. Jak to możliwe? Wersja 2.2 była taka sama jak wersja 2.1, tylko podzielona na dwadzieścia osobnych pakietów. W świecie PHP zadomowiło się narzędzie Composer i zmieniło sposób pojmowania tworzenia bibliotek. Nette przestało być monolitem i rozpadło się na mniejsze, niezależne części. Każda z własnym repozytorium, issue trackerem i własnym tempem rozwoju i wersjonowaniem. W Nette nie musi więc dochodzić do absurdów powszechnych w monolitycznych frameworkach, kiedy wychodzi nowa wersja pakietu, mimo że nic się w nim nie zmieniło. Samo podzielenie repozytoriów Git wymagało kilku tygodni przygotowań i setek godzin czasu maszynowego. -Nette zajęło również niesamowite 3 miejsce w globalnym plebiscycie na najlepszy framework PHP organizowanym przez magazyn Sitepoint. +Nette zajęło również niesamowite 3. miejsce w światowej ankiecie na najlepszy framework PHP organizowanej przez magazyn Sitepoint. {{toc:no}} diff --git a/www/pl/license.texy b/www/pl/license.texy new file mode 100644 index 0000000000..47f1704728 --- /dev/null +++ b/www/pl/license.texy @@ -0,0 +1,44 @@ +Polityka licencyjna +******************* + +Nette Framework jest rozpowszechniany jako wolne oprogramowanie, aby każdy mógł go używać. Możesz wybrać, czy bardziej odpowiada Ci licencja [New BSD |#New BSD License] czy [#GNU General Public License (GPL)] w wersji 2 lub 3. + +Licencja BSD jest zalecana dla większości projektów, ponieważ jest łatwa do zrozumienia i nie nakłada prawie żadnych ograniczeń na to, co możesz robić z frameworkiem. Możesz używać Nette również w projektach komercyjnych. Jeśli jednak GPL lepiej pasuje do Twojego projektu, wybierz ją. Przy czym nie trzeba nikogo informować, jak się zdecydowałeś. Zawsze zachowaj oryginalne prawa autorskie. + +Powinieneś wiedzieć, że nazwa "Nette Framework" jest chronionym znakiem towarowym i jej użycie ma pewne ograniczenia. Nie używaj więc "Nette" w nazwie swojego projektu lub domeny najwyższego poziomu, wybierz raczej nazwę, która będzie stała na własnych fundamentach. Jeśli Twój projekt jest dobry, nie potrwa długo, a zbuduje sobie własną reputację. + +Jeśli będziesz zadowolony z Nette Framework, a wierzymy, że tak, możesz go [wesprzeć darowizną |donate]. Dziękujemy. + + +New BSD License +--------------- + +Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com) All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of "Nette Framework" nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +GNU General Public License (GPL) +-------------------------------- + +Treść licencji GPL jest bardzo długa, dlatego podajemy tutaj linki do nich: + +- GPL version 2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +- GPL version 3: https://www.gnu.org/licenses/gpl-3.0.html + + +{{toc:yes}} +{{priority: -2}} diff --git a/www/pl/maintenance.texy b/www/pl/maintenance.texy index af194b06f1..0ea4383c42 100644 --- a/www/pl/maintenance.texy +++ b/www/pl/maintenance.texy @@ -1,23 +1,22 @@ -Utrzymanie i kompatybilność z PHP -********************************* +Konserwacja i kompatybilność z PHP +********************************** .[perex] -Nette to framework o niezwykle długim czasie wsparcia dla każdego wydania. Każdy oddział to LTS (Long-Term Support Release) z co najmniej 2-letnim wsparciem. +Nette to framework z wyjątkowo długim okresem wsparcia dla poszczególnych wydań. Każda gałąź jest LTS (Long-Term Support Release) ze wsparciem przez co najmniej 2 lata. -Każde wydanie jest aktywnie utrzymywane przez okres jednego roku (lub dłużej) od pierwszego stabilnego wydania. -Luki krytyczne i bezpieczeństwa są łatane przez dwa lata. +Każda wersja jest aktywnie utrzymywana przez okres jednego roku (lub dłużej) od początkowego stabilnego wydania. Krytyczne błędy i błędy bezpieczeństwa są naprawiane przez dwa lata. -Kalendarz wydawniczy Nette .[#toc-release-calendar-roadmap] -=========================================================== +Kalendarz wydań Nette +===================== {{include: doc-roadmap-table}} -Zgodność z PHP .[#toc-php-compatibility] -======================================== +Kompatybilność z PHP +==================== -Kompatybilność dotyczy zawsze najnowszego wydania w każdej serii. +Kompatybilność dotyczy zawsze najnowszego wydania z każdej serii. {{include: doc-roadmap-versions}} @@ -25,4 +24,4 @@ Kompatybilność dotyczy zawsze najnowszego wydania w każdej serii. {{leftbar: @menu-common}} {{toc: no}} -{{description: Wydanie Nette, mapa drogowa, tabele konserwacji i kompatybilność z PHP}} +{{description: Wydania Nette, roadmap, tabele konserwacji i kompatybilność z PHP}} diff --git a/www/pl/packages.texy b/www/pl/packages.texy index 065b783709..83d35268fa 100644 --- a/www/pl/packages.texy +++ b/www/pl/packages.texy @@ -1,17 +1,18 @@ -Wykaz pakietów -************** +Lista pakietów Nette +******************** -| **Application**:[application:how-it-works] | Web Application Core | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] -| **Bootstrap**:[bootstrap:] | Bootstrap web app | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] -| **Caching**:[caching:] | Warstwa buforowania z repozytoriami | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] -| **Component Model**:[component-model:] | Baza systemu komponentów | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] -| **DI**:[dependency-injection:] | Dependency Injection Container | [GitHub |https://github.com/nette/di] [API |https://api.nette.org/di/] -| **Database**:[database:] | Database Layer | [GitHub |https://github.com/nette/database] [API |https://api.nette.org/database/] +| **Application**:[application:how-it-works] | Rdzeń aplikacji internetowych | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] +| **Assets**:[assets:] | Zarządzanie plikami statycznymi | [API |https://api.nette.org/assets/] [GitHub |https://github.com/nette/assets] +| **Bootstrap**:[bootstrap:] | Bootstrap aplikacji internetowej | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] +| **Caching**:[caching:] | Warstwa cache z magazynami | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] +| **Component Model**:[component-model:] | Podstawa systemu komponentów | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] +| **DI**:[dependency-injection:] | Kontener Wstrzykiwania Zależności | [GitHub |https://github.com/nette/di] [API |https://api.nette.org/di/] +| **Database**:[database:] | Warstwa bazy danych | [GitHub |https://github.com/nette/database] [API |https://api.nette.org/database/] | **Forms**:[forms:] | Wygodne i bezpieczne formularze internetowe | [GitHub |https://github.com/nette/forms] [API |https://api.nette.org/forms/] -| **Http**:[http:] | Warstwa enkapsulacji żądania i odpowiedzi HTTP | [GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] -| **Latte**:[latte:] | Świetny system szablonów | [GitHub |https://github.com/nette/latte] [API |https://api.nette.org/latte/] +| **Http**:[http:] | Warstwa hermetyzująca żądanie i odpowiedź HTTP | [GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] +| **Latte**:[latte:] | Doskonały system szablonów | [GitHub |https://github.com/nette/latte] [API |https://api.nette.org/latte/] | **Mail**:[mail:] | Wysyłanie e-maili | [GitHub |https://github.com/nette/mail] [API |https://api.nette.org/mail/] -| **Neon**:[neon:] | Odczyt i zapis w formacie [NEON |https://ne-on.org] | [GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] +| **Neon**:[neon:] | Odczyt i zapis formatu [NEON |https://ne-on.org] | [GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] | **Php Generator**:[php-generator:] | Generator kodu PHP | [GitHub |https://github.com/nette/php-generator] [API |https://api.nette.org/php-generator/] | **Robot Loader**:[robot-loader:] | Najwygodniejszy autoloading | [GitHub |https://github.com/nette/robot-loader] [API |https://api.nette.org/robot-loader/] | **Routing**:[application:routing] | Routing | [GitHub |https://github.com/nette/routing] [API |https://api.nette.org/routing/] @@ -20,7 +21,7 @@ Wykaz pakietów | **Security**:[security:authentication] | Zarządzanie prawami dostępu | [GitHub |https://github.com/nette/security] [API |https://api.nette.org/security/] | **Tester**:[tester:] | Wygodne testy jednostkowe w PHP | [GitHub |https://github.com/nette/tester] [API |https://api.nette.org/tester/] | **Tracy**:[tracy:] | Narzędzie do debugowania, które pokochasz ♥ | [GitHub |https://github.com/nette/tracy] [API |https://api.nette.org/tracy/] -| **Utils**:[utils:] | Podstawowe klasy i narzędzia | [GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] +| **Utils**:[utils:] | Podstawowe klasy i narzędzia | [GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] {{leftbar: @menu-common}} {{toc: no}} diff --git a/www/pt/10-reasons-why-nette.texy b/www/pt/10-reasons-why-nette.texy index 2e1b7ca22a..1d25b7bfea 100644 --- a/www/pt/10-reasons-why-nette.texy +++ b/www/pt/10-reasons-why-nette.texy @@ -1,53 +1,93 @@ -Por que usar Nette? -******************* +7 razões para usar o Nette +**************************
                                                                                                                            -Pare de resolver tarefas repetitivas e se distraia com detalhes que tornam a programação monótona e improdutiva. **Nette Framework permite trabalhar com mais eficácia, concentrar-se no que é importante e tornar seu código mais legível e bem estruturado no processo**. Algumas características incluem: +Imagine um framework PHP que permite que você se concentre no que mais gosta de fazer. Que o guia a escrever código limpo. Que cuida da segurança por si só. Pare de sonhar e conheça o Nette. Embarque em uma jornada que abrirá portas para novas possibilidades de desenvolvimento. Vamos falar sobre: -- um excelente sistema de gabaritos -- ferramentas de diagnóstico imbatíveis -- camada de banco de dados extraordinariamente eficaz -- proteção sólida contra vulnerabilidades conhecidas -- Suporte HTML5 e AJAX, SEO amigável -- documentação bem escrita e uma comunidade ativa de código aberto -- design maduro e orientado a objetos limpos, aproveitando as últimas características do PHP -- soluções de melhores práticas que são encorajadas, mas não aplicadas +- como criar sites com o máximo conforto +- como escrever código elegante +- o que significa a sabedoria "menos código = maior segurança" +- como construir um site como um kit de construção +- e como se tornar parte de uma comunidade de sucesso
                                                                                                                            -E é completamente gratuito. Achamos que vale a pena tentar. +Nette traz conforto e eficiência para o mundo dos desenvolvedores web graças a ferramentas e técnicas inovadoras. Quais são as características chave que tornam o Nette único e um elemento essencial no kit de ferramentas do desenvolvedor? Vamos dar uma olhada nelas! -**Nette Framework permite que você se concentre na parte criativa de ser um desenvolvedor***. É construída para ser extremamente utilizável, amigável e uma alegria de usar. Sua sintaxe compreensível mas eficiente, um depurador de última geração e recursos de segurança líderes da indústria permitem escrever sites de comércio eletrônico, wikis, blogs, CMS ou qualquer coisa que você possa imaginar mais rápido e melhor do que nunca. +#1 Nette mima você +------------------ -Nette Framework é [utilizada por grandes empresas |https://builtwith.nette.org], tais como T-Systems, GE Money, Mladá fronta, VLTAVA-LABE-PRESS, Internet Info, DHL, Logio, ESET ou Actum. Atualmente, ela impulsiona o site do ex-presidente da República Tcheca Václav Klaus. Na pesquisa realizada por [Zdroják |https://www.zdrojak.cz/clanky/vysledky-technologie-na-ceskem-webu/] foi concedido o prêmio de O quadro mais popular e mais amplamente utilizado na República Tcheca. +Quando o framework Nette começou a nascer, há vinte anos, tudo girava em torno de um objetivo: Como criar sites da maneira mais confortável possível? Como tornar o trabalho dos programadores o mais agradável possível? Como tornar a criação de sites sexy? -O Learning Nette Framework não vai garantir falta de ofertas de trabalho interessantes. +Nette atraiu muitos programadores com essa abordagem e rapidamente ganhou popularidade. Na época, chamávamos essa filosofia de Netteway, e hoje existe um termo para ela, *Developer Experience* (DX). Nette é um framework que tem DX em seu DNA. Você sentirá a diferença em mil e uma coisas - desde pequenos detalhes até inovações fundamentais. Deixe o framework mimar você. -Suba a bordo .[#toc-get-on-board] ---------------------------------- +#2 Nette guia você para um código limpo +--------------------------------------- -Siga o guia e [crie sua primeira aplicação |quickstart:] passo a passo. Um prático [guia do programador |@home] está aqui para ajudá-lo a qualquer momento e se você quiser ir mais fundo, confira a [documentação API |https://api.nette.org/] prática que apresenta uma visão geral de todas as aulas e métodos. +Quer escrever código limpo? Ter aplicações bem projetadas? Quem não quer! E é aqui que o papel do framework começa. Se ele mesmo não der o exemplo, não é possível criar uma aplicação bem projetada. -Se você ficar preso, verifique as [perguntas mais frequentes |nette:troubleshooting] ou o [fórum oficial |https://forum.nette.org/en/] onde usuários experientes estão ansiosos para ajudar. +Nette dá o exemplo. É um mentor que ensina bons hábitos e a escrever código de acordo com metodologias comprovadas. Como pioneiro e evangelizador da injeção de dependência, oferece uma base de qualidade para aplicações sustentáveis, extensíveis e fáceis de ler. Nette é projetado para ser compreensível para iniciantes, ao mesmo tempo que oferece profundidade suficiente para desenvolvedores experientes. -Você não está comprometido o suficiente para aprender você mesmo? Entendido - oferecemos o [treinamento Nette Framework |https://www.skoleniphp.cz/skoleni-nette-vyvoj-webovych-aplikaci] (tcheco). O feedback de ex-participantes pode ser encontrado em [skoleniphp.cz/ohlasy |https://www.skoleniphp.cz/ohlasy]. +A comunidade em torno do Nette formou muitas personalidades que hoje estão por trás de projetos bem-sucedidos e importantes. Para milhares de programadores, Nette se tornou um mentor em sua jornada para o crescimento profissional. Junte-se a nós e descubra como Nette influenciará positivamente a qualidade do seu código e aplicações. -Mais informações .[#toc-more-information] ------------------------------------------ +#3 Guardião confiável de suas aplicações +---------------------------------------- -Temos um [blog cheio de dicas |https://blog.nette.org] e uma coleção de vários [addons e componentes |https://componette.org] para uso em suas aplicações. +Nette protege suas aplicações. Ao longo dos anos, ganhou a reputação de ser uma ferramenta que leva a segurança extremamente a sério. Fornece segurança sofisticada contra vulnerabilidades. Sempre se preocupa em facilitar o trabalho dos programadores, mas nunca em detrimento da segurança. -Além disso, a comunidade em torno de Nette Framework se reúne todos os meses em um evento chamado [Poslední sobota |https://www.posobota.cz] (tcheco), onde os membros compartilham sua experiência e apenas se divertem. Venha beber uma cerveja! +Seu lema "menos código = segurança suficiente" significa que os elementos individuais se comportam com segurança desde o início. Não é necessário ativar recursos de segurança escrevendo código adicional. Portanto, você não pode esquecer disso ou temer ter esquecido algo. Os programadores muitas vezes nem sabem quantas coisas de segurança Nette faz por eles e ficam surpresos quando descobrem. +Apresentamos um framework que o guia para um código limpo e, ao mesmo tempo, zela pela segurança de suas aplicações. Nette é um parceiro confiável que permite que você se concentre na criação de ótimas aplicações web com tranquilidade. -Envolva-se! .[#toc-get-involved] --------------------------------- -Qualquer contribuição para o desenvolvimento é muito apreciada. Você é bem-vindo a compartilhar uma palavra, colocar um [ícone |www:en:logo] em seu website ou [apoiar |www:en:donate] financeiramente [o desenvolvimento |www:en:donate]. Obrigado ♥. +#4 Construa o site como um kit de construção +-------------------------------------------- + +No Nette, você constrói páginas a partir de componentes de UI reutilizáveis. Lembra o desenvolvimento de aplicações desktop, e Nette trouxe essa abordagem com sucesso para a web. Precisa de um datagrid na administração? Basta encontrá-lo no mercado de componentes de código aberto, instalá-lo e simplesmente inseri-lo na página. Além disso, você pode criar seus próprios componentes para elementos repetidos nas páginas, eliminando duplicidades e melhorando a organização do código. + +Esta característica única distingue Nette de todos os outros principais players do mercado. Permite criar e manter aplicações web de forma eficiente. Com Nette, trabalhar com UI se torna uma experiência suave e agradável. + + +#5 Conjunto flexível de pacotes +------------------------------- + +Nette é um conjunto de [pacotes utilizáveis de forma independente |www:packages]. Entre eles estão a viciante [ferramenta de depuração Tracy |tracy:], o [sistema de templates de nova geração Latte |latte:], o [excelente Contêiner de Injeção de Dependência |dependency-injection:], [formulários |forms:] e muitos outros. Cada pacote tem documentação detalhada e legível e reside em um repositório separado no GitHub. Você pode usar os pacotes individualmente ou combiná-los com outras ferramentas e tecnologias que já utiliza. Por exemplo, Latte pode ser implantado no WordPress ou Slim Framework, o contêiner DI pode ser o núcleo de um framework corporativo e Tracy visualizará mensagens de erro. + +Ou você pode usar Nette como um todo, como um framework, e criar uma aplicação web completa nele. Quer esteja desenvolvendo um pequeno projeto pessoal ou uma robusta aplicação empresarial, Nette será seu parceiro confiável. + + +#6 Estabilidade & Inovação +-------------------------- + +Nette é um framework maduro e comprovado com uma longa história. Apesar disso, mantém-se ágil e flexível graças a um design inteligente. Os usuários apreciam que seja um framework pequeno e flexível, não um monstro. + +Está sempre preparado com antecedência para novas versões do PHP e leva em conta as últimas inovações no desenvolvimento de aplicações web. Isso é crucial para minimizar a dívida tecnológica. + +Nette é, portanto, uma combinação de estabilidade de longa data e inovação, que permite que você confie em uma solução comprovada, enquanto pode usar as tecnologias e tendências mais recentes. Com Nette, você tem a certeza de que seu projeto será construído sobre bases que acompanham constantemente o mundo em rápida evolução do desenvolvimento web. + + +#7 Esteja na melhor companhia +----------------------------- + +Nette Framework é popular entre os profissionais. [Empresas importantes |https://builtwith.nette.org] como O2, BOSCH, ESET, Zásilkovna, DHL, SUPRAPHON e centenas de outras confiam nele. Em várias pesquisas, foi eleito o framework mais popular e mais usado na República Tcheca. + +Comece com Nette e novas oportunidades de trabalho se abrirão para você. Você não só obterá uma ferramenta poderosa, mas também a comunidade mais ativa na República Tcheca, pronta para apoiá-lo em sua jornada para o crescimento profissional. Eles se reúnem regularmente nos [Posobotas |www.posobota.cz], eventos onde trocam experiências. Venha nos encontrar! + + +Descubra Nette +-------------- + +Junte-se a milhares de desenvolvedores satisfeitos que já descobriram as vantagens do Nette e comece a escrever código mais limpo, seguro e eficiente com este framework único. + +Crie sua primeira aplicação [seguindo o tutorial |quickstart:], passo a passo. Você sempre terá à mão a [documentação extensa |nette:] e uma prática [visão geral da API |https://api.nette.org]. Visite nosso [blog cheio de dicas |https://blog.nette.org] e a coleção de [plugins e componentes |https://componette.org] que expandem as capacidades do Nette. + +Tem perguntas? Consulte a página com [perguntas frequentes|nette:troubleshooting] ou discuta no [fórum tcheco|https://forum.nette.org/cs/]. Prefere treinamento presencial? Oferecemos [treinamento Nette Framework |https://www.skoleniphp.cz/skoleni-nette-vyvoj-webovych-aplikaci] com [excelentes depoimentos |https://www.skoleniphp.cz/ohlasy]. + +**Conheça o framework que vai mimar, guiar e inspirar você.** {{leftbar: @menu-common}} diff --git a/www/pt/@home.texy b/www/pt/@home.texy index e86ee6fc74..902a2da565 100644 --- a/www/pt/@home.texy +++ b/www/pt/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Nette - Desenvolvimento Web Confortável e Seguro em PHP}} -{{description: Nette é uma família de componentes maduros e autônomos para PHP. Pronto para ser apaixonado? Juntos, eles criam uma estrutura que tinha sido classificada como a 3ª mais popular do mundo. Nossa filosofia é focar na produtividade, nas melhores práticas e na segurança.}} +{{maintitle:Nette – Desenvolvimento de aplicações web confortável e seguro em PHP}} +{{description: Nette é uma família de componentes avançados e independentes para PHP. Deixe-se inspirar por eles. Juntos, eles formam um framework, classificado como o 3º mais popular do mundo. A filosofia Nette coloca ênfase extraordinária na produtividade, melhores práticas e segurança.}} diff --git a/www/pt/@menu-common.texy b/www/pt/@menu-common.texy index dadf8aaf84..d77e16dbc2 100644 --- a/www/pt/@menu-common.texy +++ b/www/pt/@menu-common.texy @@ -2,19 +2,21 @@ Introdução ********** - [Por que usar Nette? |www:10-reasons-why-nette] - [Instalação |nette:installation] -- [Crie sua primeira aplicação! |quickstart:] +- [Escrevendo a primeira aplicação! |quickstart:] Tópicos gerais ************** -- [Lista de Pacotes |www:packages] -- [Manutenção e PHP |www:maintenance] +- [Lista de pacotes |www:packages] +- [Manutenção e versões PHP |www:maintenance] - [Notas de Lançamento |https://nette.org/releases] -- [Guia de Atualização |migrations:en] -- [nette:Solução de problemas |nette:Troubleshooting] -- [Quem Cria a Nette |https://nette.org/contributors] -- [História da Nette |history] -- [Envolva-se |contributing:] -- [Desenvolvimento do patrocinador |https://nette.org/en/donate] -- [Referência API |https://api.nette.org] -- [Melhores Práticas |best-practices:] +- [Migrando para versões mais recentes|migrations:en] +- [Solução de problemas |nette:troubleshooting] +- [Quem faz o Nette |https://nette.org/contributors] +- [História do Nette |history] +- [Participe |contributing:] +- [Apoie o desenvolvimento |https://nette.org/pt/donate] +- [Referência da API |https://api.nette.org/] +- [Tutoriais e melhores práticas |best-practices:] + +- [Segurança em primeiro lugar |nette:vulnerability-protection] diff --git a/www/pt/donate.texy b/www/pt/donate.texy new file mode 100644 index 0000000000..5088ec560b --- /dev/null +++ b/www/pt/donate.texy @@ -0,0 +1,20 @@ +Apoie o desenvolvimento do Nette +******************************** + +
                                                                                                                            +Todos que constroem sobre Nette têm interesse em que o framework seja desenvolvido ativamente. Que suporte novas versões do PHP. Que os erros sejam corrigidos. Que traga novas funcionalidades que facilitem o trabalho ou economizem tempo e dinheiro. Que o framework tenha uma ótima documentação e que haja conteúdo útil em torno dele, seja na forma de artigos, tutoriais ou vídeos. + +Muitas partes do Nette representam o estado da arte mundial e queremos que continue assim. + +Sem financiamento adequado, nada disso pode ser garantido. No entanto, para garantir que as próximas versões sejam lançadas, basta muito pouco: que você o apoie todos os meses, mesmo com uma pequena quantia financeira. + +Participe e torne-se um parceiro Nette! + +Você garantirá o funcionamento saudável do projeto em que confia. E, ao mesmo tempo, **obterá uma série de benefícios exclusivos** (veja *Níveis de Parceria* na coluna da direita). Você terá acesso a conteúdo bônus. A suporte técnico. Aumentará a prioridade de resolução de seus issues. E também dará visibilidade à sua empresa e atrairá desenvolvedores. + +Como daremos visibilidade a você? Por exemplo, exibindo seu logotipo neste site (ou seja, nesta página, na página inicial, na documentação, no fórum e em uma [página especial |https://nette.org/partner/vitalita] que você pode linkar). Você terá a oportunidade de postar [ofertas de emprego |https://forum.nette.org/cs/f30-prace-a-zakazky], anunciar no fórum ([exemplo |https://forum.nette.org/cs/30798-problem-s-cizim-klicem-pri-mazani#p198093]) ou na documentação ([exemplo |https://doc.nette.org/cs/application/components#toc-flash-zpravy]), ou seja, nos locais com o melhor alcance absoluto para o grupo de desenvolvedores Nette. + +Emitimos faturas para os parceiros para que possam incluir o apoio em suas despesas, seja mensalmente, trimestralmente, semestralmente ou anualmente. + +{{include: buttons}} +
                                                                                                                            diff --git a/www/pt/history.texy b/www/pt/history.texy index f926ca7ac9..f8e8d8cae3 100644 --- a/www/pt/history.texy +++ b/www/pt/history.texy @@ -1,36 +1,35 @@ -História da Nette +História do Nette ***************** .[perex] -As origens da Nette remontam a 2004, quando seu autor David Grudl começou a procurar uma estrutura adequada para escrever aplicações, já que o PHP puro não era mais suficiente. Nenhuma das soluções disponíveis na época lhe convinha, então ele gradualmente começou a delinear as características de uma nova estrutura, que mais tarde recebeu o nome Nette. +O início do Nette remonta a 2004, quando seu autor, David Grudl, começou a procurar um framework adequado no qual pudesse escrever aplicações, pois o PHP puro já não era suficiente. Nenhuma das soluções disponíveis na época o satisfez, então ele começou gradualmente a esboçar as características de um novo framework, que mais tarde recebeu o nome de Nette. -Naquela época, as estruturas atuais como Symfony, Laravel ou Ruby on Rails ainda não existiam. No mundo Java, o JSF (JavaServer Faces) era o padrão, e no concorrente .NET, o ASP.NET Webforms era a estrutura dominante. Ambos permitiam a construção de páginas usando componentes UI reutilizáveis. David considerou que seus métodos de abstração e tentativas de criar apatridia sobre o protocolo HTTP sem Estado, utilizando sessões ou postbacks, tinham falhas e eram fundamentalmente quebrados. Eles causaram muitas dificuldades para os usuários e mecanismos de busca. Por exemplo, se você salvou um link, você ficou surpreso ao encontrar conteúdo diferente sob ele mais tarde. +Naquela época, frameworks atuais como Symfony, Laravel ou Ruby on Rails ainda não existiam. No mundo Java, o framework padrão era o JSF (JavaServer Faces) e, no concorrente .NET, o ASP.NET Webforms. Ambos permitiam construir páginas usando componentes de UI reutilizáveis. David considerou seus métodos de abstração e tentativas de criar estado sobre o protocolo HTTP sem estado usando sessão ou o chamado postback como falhos e fundamentalmente disfuncionais. Eles causavam muitas dificuldades para usuários e motores de busca. Por exemplo, se você salvasse um link, mais tarde encontraria um conteúdo diferente nele, para sua surpresa. -A possibilidade de compor páginas a partir de componentes reutilizáveis da IU fascinou David, que o conhecia bem da Delphi, uma ferramenta popular para a construção de aplicações desktop na época. Ele gostava dos mercados com componentes de código aberto para o Delphi. Então ele tentou resolver a questão de como criar uma estrutura de componentes que, por sua vez, funcionasse em completa harmonia com o HTTP sem estado. Ele estava procurando por um conceito que fosse amigável ao usuário, SEO e desenvolvedor. E assim nasceu Nette. +A própria possibilidade de compor páginas a partir de componentes de UI reutilizáveis fascinou David; ele a conhecia bem do Delphi, uma ferramenta popular na época para criar aplicações desktop. Ele gostava dos mercados com componentes de código aberto para Delphi. Portanto, ele tentou resolver a questão de como criar um framework de componentes que, ao contrário, funcionasse em completa harmonia com o HTTP sem estado. Ele procurava um conceito que fosse amigável para usuários, SEO e desenvolvedores. E assim Nette começou a nascer. .[note] -O nome Nette surgiu por acaso no banheiro, quando o autor avistou um frasco de gel de barbear Gillette, girado para que somente o *llette* pudesse ser visto. +O nome Nette surgiu por acaso no banheiro, quando o autor viu um pote de gel de barbear Gillette, virado de forma que apenas *llette* era visível. -Milhares de horas de pesquisa, pensamento e reescrita se seguiram. Em uma garagem empoeirada em uma vila em algum lugar fora de Brno, os primeiros contornos da futura estrutura estavam sendo criados. A base da arquitetura era o padrão MVC, que foi então utilizado pela agora esquecida estrutura PHP Mojavi e mais tarde popularizada pela propaganda em torno do Ruby on Rails. Uma das fontes inspiradoras foi até mesmo o framework phpBase nunca publicado por Honza Tichý. +Seguiram-se milhares de horas de pesquisa, reflexão e reescrita. Na garagem empoeirada de uma vila em algum lugar perto de Brno, os primeiros contornos do futuro framework surgiram. A base da arquitetura tornou-se o padrão MVC, que era usado na época pelo agora esquecido framework PHP Mojavi e mais tarde foi popularizado pelo hype em torno do Ruby on Rails. Uma das fontes de inspiração foi até mesmo o framework phpBase nunca publicado de Honza Tichý. -Artigos sobre a próxima Nette começaram a aparecer no blog do autor. Era uma piada que se tratava de vaporware. Mas então, em outubro de 2007, na conferência de Praga sobre PHP, David apresentou publicamente a Nette. A propósito, esta conferência evoluiu para WebExpo um ano depois, mais tarde uma das maiores conferências de TI da Europa. Mesmo assim, Nette orgulhosamente apresentou uma série de conceitos originais, tais como o modelo de componentes acima mencionado, roteador bidirecional, forma específica de ligação entre os apresentadores, etc. Tinha formulários, autenticação, caching, etc. Tudo ainda é utilizado na Nette em seu conceito original até hoje. +Artigos sobre o próximo Nette começaram a aparecer no blog do autor. Brincava-se que era vaporware. Mas então, em outubro de 2007, na conferência PHP Seminář em Praga, David apresentou publicamente o Nette. A propósito, esta conferência evoluiu um ano depois para o WebExpo, mais tarde uma das maiores conferências de TI da Europa. Já naquela época, Nette ostentava vários conceitos originais, como o modelo de componentes mencionado, o roteador bidirecional, uma forma específica de linkar entre presenters, etc. Tinha formulários, autenticação resolvida, cache, etc. Tudo em Nette ainda é usado em sua concepção original hoje. .[note] -Nette usa *presentador* em vez de *controlador* porque supostamente havia demasiadas palavras começando com *con* no código (controlador, controlador frontal, controle, configuração, recipiente, ...). +Em Nette, o termo *presenter* é usado em vez de *controller*, porque supostamente havia muitas palavras no código começando com *con* (controller, front controller, control, config, container, ...) -No final de 2007, David Grudl publicou o código e Nette 0,7 foi lançado. Uma comunidade entusiasta de programadores se formou em torno dele e começou a se reunir todos os meses no evento Posobota. A comunidade incluiu muitos dos luminares atuais, como Ondrej Mirtes, autor da grande ferramenta PHPStan. O desenvolvimento da Nette avançou, e nos dois anos seguintes foram lançadas as versões 0.8 e 0.9, lançando as bases para quase todas as partes da estrutura atual. Incluindo trechos AJAX que antecedem a Hotwire para Ruby on Rails ou Symfony UX Turbo por 14 anos. +No final de 2007, David Grudl também publicou o código, e assim a versão Nette 0.7 viu a luz do dia. O framework imediatamente atraiu enorme atenção. Uma comunidade entusiasmada de programadores se formou em torno dele, que começou a se reunir todos os meses no evento Posobota. A comunidade incluía muitas personalidades de hoje, como Ondřej Mirtes, autor da excelente ferramenta PHPStan. O desenvolvimento do Nette avançou rapidamente e, nos dois anos seguintes, as versões 0.8 e 0.9 foram lançadas, onde as bases de quase todas as partes atuais do framework foram estabelecidas. Incluindo snippets AJAX, que estavam 14 anos à frente do Hotwire para Ruby on Rails ou Symfony UX Turbo. -Mas uma coisa crucial estava faltando na Nette naquela época. Recipiente de injeção Dependecy (DIC). Nette estava usando um *service locator* e a intenção era mover-se para a injeção dependecy. Mas como projetar uma coisa dessas? David Grudl, que não tinha experiência com DI na época, foi almoçar com Vasek Purchart, que estava usando DI há cerca de meio ano. Juntos eles discutiram o tema e David começou a trabalhar na Nette DI, uma biblioteca que revolucionou completamente a maneira como pensamos sobre o design de aplicações. O recipiente de DI tornou-se uma das partes mais bem sucedidas da estrutura. E deu origem a dois spin-offs: o formato Neon e a biblioteca Schema. +Mas uma coisa essencial faltava no Nette da época. Um contêiner de injeção de dependência (DIC). Nette usava o chamado *service locator* e a intenção era mudar para a injeção de dependência. Mas como projetar tal coisa? David Grudl, que na época não tinha experiência com DI, almoçou com Vašek Purchart, que usava DI há cerca de meio ano. Eles discutiram o tópico juntos e David iniciou o trabalho no Nette DI, uma biblioteca que revolucionou completamente a forma como se pensa sobre o design de aplicações. O contêiner DI tornou-se uma das partes mais bem-sucedidas do framework. E mais tarde deu origem a dois spin-offs: o formato Neon e a biblioteca Schema. .[note] -A mudança para a injeção de dependência levou muito tempo, e nós esperamos alguns anos por uma nova versão do Nette. Portanto, quando finalmente saiu, ela foi numerada 2. Portanto, a versão 1 do Nette não existe. +A mudança para a injeção de dependência exigiu bastante tempo e esperou-se alguns anos pela nova versão do Nette. Portanto, quando finalmente foi lançada, recebeu diretamente o número 2. A versão Nette 1, portanto, não existe. -Nette iniciou sua história moderna em 2012 com a versão 2.0. Ela também trouxe o Nette Database, que incluía uma ferramenta de banco de dados extremamente útil, agora chamada Explorer. Esta biblioteca foi originalmente programada por Jakub Vrána, vizinho de David Grudl e autor da popular ferramenta Adminer. Seu desenvolvimento posterior foi então assumido por Jan Škrášek por três anos. +Nette iniciou sua história moderna em 2012 com a versão 2.0. Também trouxe o Nette Database, que incluía uma ferramenta extraordinariamente útil para trabalhar com bancos de dados, hoje chamada Explorer. Esta biblioteca foi originalmente programada por Jakub Vrána, vizinho de David Grudl e autor da popular ferramenta Adminer. Seu desenvolvimento posterior foi então assumido por Jan Škrášek por três anos. -Em 2014, Nette 2.1 foi lançado, seguido em breve por Nette 2.2. Como isto é possível? A versão 2.2 foi a mesma da versão 2.1, apenas dividida em vinte pacotes separados. A ferramenta Composer tomou posse no mundo PHP e mudou a maneira como pensamos sobre a criação de bibliotecas. Nette deixou de ser um monólito e se dividiu em partes independentes menores. Cada uma com seu próprio repositório, rastreador de problemas e seu próprio fluxo de desenvolvimento e criação de versões. Desta forma, a Nette não precisa passar pelos absurdos comuns em estruturas monolíticas, onde sai uma nova versão de um pacote, mesmo que nada tenha mudado. A divisão real dos repositórios Git envolveu várias semanas de preparação e centenas de horas de tempo de máquina. - -Nette também ficou em um surpreendente 3º lugar na pesquisa global para a melhor estrutura PHP organizada pela revista Sitepoint. +Em 2014, Nette 2.1 foi lançado, seguido em breve por Nette 2.2. Como isso é possível? A versão 2.2 era a mesma que a versão 2.1, apenas dividida em vinte pacotes separados. A ferramenta Composer se estabeleceu no mundo PHP e mudou a forma como a criação de bibliotecas era abordada. Nette deixou de ser um monólito e se dividiu em partes menores e independentes. Cada uma com seu próprio repositório, rastreador de issues e seu próprio ritmo de desenvolvimento e versionamento. Assim, em Nette, não precisam ocorrer absurdos comuns em frameworks monolíticos, onde uma nova versão de um pacote é lançada, embora nada tenha mudado nele. A própria divisão dos repositórios Git envolveu várias semanas de preparação e centenas de horas de tempo de máquina. +Nette também alcançou um incrível 3º lugar na pesquisa mundial sobre o melhor framework PHP organizada pela revista Sitepoint. {{toc:no}} diff --git a/www/pt/license.texy b/www/pt/license.texy new file mode 100644 index 0000000000..c6a1721684 --- /dev/null +++ b/www/pt/license.texy @@ -0,0 +1,44 @@ +Política de Licenciamento +************************* + +Nette Framework é distribuído como software livre para que qualquer pessoa possa usá-lo. Você pode escolher se a licença [New BSD |#New BSD License] ou a [#GNU General Public License (GPL)] na versão 2 ou 3 se adapta melhor a você. + +A licença BSD é recomendada para a maioria dos projetos, pois é fácil de entender e impõe quase nenhuma restrição sobre o que você pode fazer com o framework. Você também pode usar Nette em projetos comerciais. No entanto, se a GPL for mais adequada para o seu projeto, escolha-a. Não é necessário informar ninguém sobre sua decisão. Sempre preserve os direitos autorais originais. + +Você deve saber que o nome "Nette Framework" é uma marca registrada protegida e seu uso tem certas restrições. Portanto, não use "Nette" no nome do seu projeto ou domínio de nível superior; escolha um nome que se sustente por si só. Se o seu projeto for bom, não demorará muito para criar sua própria reputação. + +Se você estiver satisfeito com o Nette Framework, e acreditamos que estará, pode [apoiar com uma contribuição |donate]. Obrigado. + + +New BSD License +--------------- + +Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com) All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of "Nette Framework" nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +GNU General Public License (GPL) +-------------------------------- + +Os textos das licenças GPL são muito longos, portanto, links para eles são fornecidos aqui: + +- GPL versão 2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +- GPL versão 3: https://www.gnu.org/licenses/gpl-3.0.html + + +{{toc:yes}} +{{priority: -2}} diff --git a/www/pt/maintenance.texy b/www/pt/maintenance.texy index bcfc7015a6..4992b9c624 100644 --- a/www/pt/maintenance.texy +++ b/www/pt/maintenance.texy @@ -1,27 +1,27 @@ -Manutenção e Versões PHP -************************ +Manutenção e Compatibilidade com PHP +************************************ .[perex] -Nette é uma estrutura com um período de apoio excepcionalmente longo para lançamentos individuais. Cada ramo é um LTS (Long-Term Support Release) com pelo menos 2 anos de suporte. +Nette é um framework com um período de suporte excepcionalmente longo para lançamentos individuais. Cada branch é LTS (Long-Term Support Release) com suporte por pelo menos 2 anos. -Cada versão é mantida ativamente por um período de um ano (ou mais) a partir de seu lançamento estável inicial. -Os erros críticos e de segurança são corrigidos por dois anos. +Cada versão é mantida ativamente por um período de um ano (ou mais) a partir do lançamento estável inicial. Erros críticos e de segurança são corrigidos por dois anos. -Calendário de Lançamento (Roadmap) .[#toc-release-calendar-roadmap] -=================================================================== +Calendário de Lançamento do Nette +================================= {{include: doc-roadmap-table}} -Compatibilidade PHP .[#toc-php-compatibility] -============================================= +Compatibilidade com PHP +======================= + +A compatibilidade sempre se aplica ao lançamento mais recente de cada série. -A compatibilidade sempre se aplica ao último lançamento de cada série. {{include: doc-roadmap-versions}} {{leftbar: @menu-common}} {{toc: no}} -{{description: Nette releases, roadmap, manutenção e tabelas de compatibilidade PHP}} +{{description: Lançamentos do Nette, roadmap, tabelas de manutenção e compatibilidade com PHP}} diff --git a/www/pt/packages.texy b/www/pt/packages.texy index 3c2632031f..e017413f0a 100644 --- a/www/pt/packages.texy +++ b/www/pt/packages.texy @@ -1,26 +1,27 @@ -Lista de Pacotes -**************** +Lista de Pacotes Nette +********************** -| **Application**:[application:how-it-works] | O kernel da aplicação web | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] -| **Bootstrap**:[bootstrap:] | Bootstrap de sua aplicação | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] -| **Caching**:[caching:] | Camada de cache com conjunto de armazenamentos | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] -| **Component Model**:[component-model:] | Fundação para sistemas de componentes | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] -| **DI**:[dependency-injection:] | Recipiente de Injeção de Dependência | [GitHub |https://github.com/nette/di] [API |https://api.nette.org/di/] +| **Application**:[application:how-it-works] | Núcleo de aplicações web | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] +| **Assets**:[assets:] | Gerenciamento de arquivos estáticos | [API |https://api.nette.org/assets/] [do GitHub |https://github.com/nette/assets] +| **Bootstrap**:[bootstrap:] | Bootstrap da aplicação web | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] +| **Caching**:[caching:] | Camada de cache com armazenamentos | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] +| **Component Model**:[component-model:] | Base do sistema de componentes | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] +| **DI**:[dependency-injection:] | Contêiner de Injeção de Dependência | [GitHub |https://github.com/nette/di] [API |https://api.nette.org/di/] | **Database**:[database:] | Camada de banco de dados | [GitHub |https://github.com/nette/database] [API |https://api.nette.org/database/] -| **Forms**:[forms:] | | [GitHub |https://github.com/nette/forms] [API |https://api.nette.org/forms/] -| **Http**:[http:] | Layer for the HTTP request & response | [GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] -| **Latte**:[latte:] | Incrível motor de modelo | [GitHub |https://github.com/nette/latte] [API |https://api.nette.org/latte/] +| **Forms**:[forms:] | Formulários web convenientes e seguros | [GitHub |https://github.com/nette/forms] [API |https://api.nette.org/forms/] +| **Http**:[http:] | Camada encapsulando requisição & resposta HTTP | [GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] +| **Latte**:[latte:] | Ótimo sistema de templates | [GitHub |https://github.com/nette/latte] [API |https://api.nette.org/latte/] | **Mail**:[mail:] | Envio de e-mails | [GitHub |https://github.com/nette/mail] [API |https://api.nette.org/mail/] -| **Neon**:[neon:] | [NEON |https://ne-on.org] | [GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] -| **PHP Generator**:[php-generator:] | Gerador de código PHP | [GitHub |https://github.com/nette/php-generator] [API |https://api.nette.org/php-generator/] -| **Robot Loader**:[robot-loader:] | O carregamento automático mais confortável | [GitHub |https://github.com/nette/robot-loader] [API |https://api.nette.org/robot-loader/] +| **Neon**:[neon:] | Leitura e escrita do formato [NEON |https://ne-on.org] | [GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] +| **Php Generator**:[php-generator:] | Gerador de código PHP | [GitHub |https://github.com/nette/php-generator] [API |https://api.nette.org/php-generator/] +| **Robot Loader**:[robot-loader:] | O autoloading mais confortável | [GitHub |https://github.com/nette/robot-loader] [API |https://api.nette.org/robot-loader/] | **Routing**:[application:routing] | Roteamento | [GitHub |https://github.com/nette/routing] [API |https://api.nette.org/routing/] | **Safe Stream**:[safe-stream:] | Operações atômicas seguras com arquivos | [GitHub |https://github.com/nette/safe-stream] [API |https://api.nette.org/safe-stream/] -| **Security**:[security:authentication] | Fornece sistema de controle de acesso | [GitHub |https://github.com/nette/security] [API |https://api.nette.org/security/] | **Schema**:[schema:] | Validação de dados do usuário | [GitHub |https://github.com/nette/schema] [API |https://api.nette.org/schema/] -| **Tester**:[tester:] | Teste de unidade agradável em PHP | [GitHub |https://github.com/nette/tester] [API |https://api.nette.org/tester/] -| **Tracy**:[tracy:] | Debugging tool you will love ♥ | [GitHub |https://github.com/nette/tracy] [API |https://api.nette.org/tracy/] -| **Utils**:[utils:] | Utilitários e Classes Principais | [GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] +| **Security**:[security:authentication] | Gerenciamento de direitos de acesso | [GitHub |https://github.com/nette/security] [API |https://api.nette.org/security/] +| **Tester**:[tester:] | Testes unitários tranquilos em PHP | [GitHub |https://github.com/nette/tester] [API |https://api.nette.org/tester/] +| **Tracy**:[tracy:] | Ferramenta de depuração que você vai amar ♥ | [GitHub |https://github.com/nette/tracy] [API |https://api.nette.org/tracy/] +| **Utils**:[utils:] | Classes e ferramentas básicas | [GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] {{leftbar: @menu-common}} {{toc: no}} diff --git a/www/ro/10-reasons-why-nette.texy b/www/ro/10-reasons-why-nette.texy new file mode 100644 index 0000000000..41c40dbd4c --- /dev/null +++ b/www/ro/10-reasons-why-nette.texy @@ -0,0 +1,93 @@ +7 motive pentru a folosi Nette +****************************** + +
                                                                                                                            + +Imaginați-vă un framework PHP care vă permite să vă concentrați pe ceea ce vă place cel mai mult să faceți. Vă ghidează să scrieți cod curat. Are grijă de securitate singur. Nu mai visați și cunoașteți Nette. Porniți într-o călătorie care vă va deschide ușile către noi posibilități de dezvoltare. Vom discuta despre: + +- cum să creați site-uri web cu maxim confort +- cum să scrieți cod elegant +- ce înseamnă înțelepciunea „mai puțin cod = mai multă securitate” +- cum să construiți un site web ca un set de construcție +- și cum să deveniți parte dintr-o comunitate de succes + +
                                                                                                                            + +Nette aduce confort și eficiență în lumea dezvoltatorilor web datorită instrumentelor și tehnicilor inovatoare. Care sunt caracteristicile cheie care fac Nette unic și un element indispensabil în setul de instrumente al dezvoltatorului? Să le analizăm! + + +#1 Nette te răsfață +------------------- + +Când framework-ul Nette a început să prindă contur acum douăzeci de ani, totul se învârtea în jurul unui singur obiectiv: Cum să creăm site-uri web cât mai confortabil posibil? Cum să facem munca programatorilor cât mai plăcută? Cum să facem crearea de site-uri web sexy? + +Nette a atras mulți programatori cu această abordare și a câștigat rapid popularitate. Atunci numeam această filosofie Netteway, iar astăzi există un termen pentru ea, *Developer Experience* (DX). Nette este un framework care are DX în ADN-ul său. Veți simți diferența în o mie și una de lucruri – de la mici detalii la inovații majore. Lăsați-vă răsfățați de framework. + + +#2 Nette te ghidează spre cod curat +----------------------------------- + +Doriți să scrieți cod curat? Să aveți aplicații proiectate corect? Cine nu și-ar dori! Și tocmai aici începe rolul framework-ului. Dacă el însuși nu dă exemplu, nu se poate crea o aplicație excelent proiectată. + +Nette dă exemplu. Este un mentor care învață bunele obiceiuri și scrierea codului conform metodologiilor dovedite. Ca pionier și evanghelist al injecției de dependențe, oferă o bază de calitate pentru aplicații sustenabile, extensibile și ușor de citit. Nette este conceput pentru a fi ușor de înțeles pentru începători, oferind în același timp suficientă profunzime pentru dezvoltatorii experimentați. + +Comunitatea din jurul Nette a format numeroase personalități care astăzi stau la baza unor proiecte de succes și importante. Pentru mii de programatori, Nette a devenit un mentor în călătoria lor spre creșterea profesională. Alăturați-vă și descoperiți cum Nette va influența pozitiv calitatea codului și a aplicațiilor dumneavoastră. + + +#3 Gardian de încredere pentru aplicațiile tale +----------------------------------------------- + +Nette protejează aplicațiile dumneavoastră. De-a lungul anilor, și-a câștigat reputația unui instrument care ia securitatea extrem de în serios. Oferă o securitate sofisticată împotriva vulnerabilităților. Întotdeauna are grijă să faciliteze munca programatorilor, dar niciodată în detrimentul siguranței. + +Motto-ul său "mai puțin cod = suficientă securitate" înseamnă că elementele individuale se comportă în siguranță deja de la bază. Nu este nevoie să activați elemente de securitate scriind cod suplimentar. Prin urmare, nu puteți uita de ele sau să vă temeți că ați omis ceva. Programatorii adesea nici nu bănuiesc câte lucruri de securitate face Nette pentru ei și sunt surprinși când află despre asta. + +Vă prezentăm un framework care vă ghidează spre cod curat și, în același timp, veghează asupra securității aplicațiilor dumneavoastră. Nette este un partener de încredere care vă permite să vă concentrați pe crearea de aplicații web excelente cu liniște sufletească. + + +#4 Construiește web-ul ca un set de construcție +----------------------------------------------- + +În Nette, construiți pagini din componente UI reutilizabile. Amintește de dezvoltarea aplicațiilor desktop, iar Nette a transferat cu succes această abordare pe web. Aveți nevoie de un datagrid în administrare? Este suficient să-l găsiți pe piața de componente open source, să-l instalați și pur și simplu să-l inserați în pagină. În plus, puteți crea propriile componente pentru elementele repetitive de pe pagini, eliminând astfel duplicitatea și îmbunătățind organizarea codului. + +Această caracteristică unică distinge Nette de toți ceilalți jucători importanți de pe piață. Vă permite să creați și să mențineți eficient aplicații web. Cu Nette, lucrul cu UI devine o experiență lină și plăcută. + + +#5 Set flexibil de pachete +-------------------------- + +Nette este un set de [pachete utilizabile independent |www:packages]. Printre acestea se numără captivantul [instrumentul de depanare Tracy |tracy:], [sistemul de șabloane de nouă generație Latte |latte:], [excelentul Container DI |dependency-injection:], [formulare |forms:] și multe altele. Fiecare pachet are o documentație detaliată și ușor de citit și se află într-un depozit separat pe GitHub. Puteți utiliza pachetele independent sau le puteți combina cu alte instrumente și tehnologii pe care le folosiți deja. De exemplu, Latte poate fi implementat în WordPress sau Slim Framework, containerul DI poate fi nucleul unui framework corporativ, iar Tracy va vizualiza mesajele de eroare. + +Sau puteți utiliza Nette ca un întreg, ca un framework, și să creați în el o aplicație web completă. Fie că dezvoltați un mic proiect personal sau o aplicație robustă de întreprindere, Nette va fi partenerul dumneavoastră de încredere. + + +#6 Stabilitate & inovație +------------------------- + +Nette este un framework matur și verificat, cu o istorie îndelungată. Cu toate acestea, se menține agil și flexibil datorită unui design inteligent. Utilizatorii apreciază faptul că este un framework mic și flexibil, nu un colos. + +Este întotdeauna pregătit din timp pentru noile versiuni PHP și ia în considerare cele mai recente inovații în domeniul dezvoltării aplicațiilor web. Acest lucru este crucial pentru minimizarea datoriei tehnologice. + +Nette este, așadar, o combinație de stabilitate îndelungată și inovație, care vă permite să vă bazați pe o soluție verificată, în timp ce veți putea utiliza cele mai recente tehnologii și tendințe. Cu Nette, aveți certitudinea că proiectul dumneavoastră va fi construit pe baze care țin constant pasul cu lumea în rapidă evoluție a dezvoltării web. + + +#7 Fii în cea mai bună companie +------------------------------- + +Nette Framework este popular printre profesioniști. Se bazează pe el [companii importante |https://builtwith.nette.org] precum O2, BOSCH, ESET, Zásilkovna, DHL, SUPRAPHON și sute de altele. În mai multe sondaje, a câștigat ca cel mai popular și cel mai utilizat framework în Republica Cehă. + +Începeți cu Nette și vi se vor deschide noi oportunități de muncă. Veți obține nu numai un instrument puternic, ci și cea mai activă comunitate din Cehia, pregătită să vă sprijine în călătoria dumneavoastră spre creșterea profesională. Se întâlnește regulat la [Posobota |www.posobota.cz], evenimente unde își împărtășesc experiențele. Veniți să ne întâlniți! + + +Descoperă Nette +--------------- + +Alăturați-vă miilor de dezvoltatori mulțumiți care au descoperit deja avantajele Nette și începeți să scrieți cod mai curat, mai sigur și mai eficient cu acest framework unic. + +Creați prima aplicație [conform ghidului |quickstart:], pas cu pas. Veți avea mereu la îndemână [documentație extinsă |nette:] și o [prezentare generală API |https://api.nette.org] practică. Vizitați [blogul nostru plin de sfaturi |https://blog.nette.org] și colecția de [add-on-uri și componente |https://componette.org] care extind capacitățile Nette. + +Aveți întrebări? Consultați pagina cu [întrebări frecvente |nette:troubleshooting] sau discutați pe [forumul ceh |https://forum.nette.org/cs/]. Preferati training personal? Oferim [training Nette Framework |https://www.skoleniphp.cz/skoleni-nette-vyvoj-webovych-aplikaci] cu [recenzii excelente |https://www.skoleniphp.cz/ohlasy]. + +**Faceți cunoștință cu framework-ul care vă va răsfăța, ghida și inspira.** + + +{{leftbar: @menu-common}} diff --git a/www/ro/@home.texy b/www/ro/@home.texy index a908deb6e6..cf80c130b7 100644 --- a/www/ro/@home.texy +++ b/www/ro/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Nette - Dezvoltare web confortabilă și sigură în PHP}} -{{description: Nette este o familie de componente mature și autonome pentru PHP. Sunteți gata să fiți fermecat? Împreună, ele creează un framework care a fost evaluat ca fiind al 3-lea cel mai popular din lume. Filozofia noastră este să ne concentrăm pe productivitate, cele mai bune practici și securitate.}} +{{maintitle:Nette – Dezvoltare web confortabilă și sigură în PHP}} +{{description: Nette este o familie de componente mature și utilizabile independent pentru PHP. Lăsați-vă inspirați de ele. Împreună formează un framework, evaluat ca al 3-lea cel mai popular din lume. Filosofia Nette pune un accent deosebit pe productivitate, cele mai bune practici și securitate.}} diff --git a/www/ro/@menu-common.texy b/www/ro/@menu-common.texy index 776a885178..d2b027f02a 100644 --- a/www/ro/@menu-common.texy +++ b/www/ro/@menu-common.texy @@ -2,19 +2,21 @@ Introducere *********** - [De ce să folosiți Nette? |www:10-reasons-why-nette] - [Instalare |nette:installation] -- [Creați prima dumneavoastră aplicație! |quickstart:] +- [Scriem prima aplicație! |quickstart:] Subiecte generale ***************** -- [Lista de pachete |www:packages] -- [Întreținere și PHP |www:maintenance] +- [Lista pachetelor |www:packages] +- [Mentenanță și versiuni PHP |www:maintenance] - [Note de lansare |https://nette.org/releases] -- [Ghid de actualizare |migrations:en] -- [Depanare |nette:Troubleshooting] +- [Trecerea la versiuni mai noi|migrations:en] +- [Rezolvarea problemelor |nette:troubleshooting] - [Cine creează Nette |https://nette.org/contributors] - [Istoria Nette |history] -- [Implică-te |contributing:] -- [Dezvoltarea sponsorilor |https://nette.org/en/donate] -- [Referință API |https://api.nette.org] -- [Cele mai bune practici |best-practices:] +- [Implicați-vă |contributing:] +- [Susțineți dezvoltarea |https://nette.org/cs/donate] +- [Referință API |https://api.nette.org/] +- [Ghiduri și proceduri |best-practices:] + +- [Securitatea înainte de toate |nette:vulnerability-protection] diff --git a/www/ro/donate.texy b/www/ro/donate.texy new file mode 100644 index 0000000000..70c52625a9 --- /dev/null +++ b/www/ro/donate.texy @@ -0,0 +1,20 @@ +Susțineți dezvoltarea Nette +*************************** + +
                                                                                                                            +Oricine construiește pe Nette are interesul ca framework-ul să se dezvolte activ. Să suporte noile versiuni PHP. Să se corecteze erorile. Să vină cu noi inovații care să faciliteze munca sau să economisească timp și bani. Ca framework-ul să aibă o documentație excelentă și să existe conținut util în jurul său, fie sub formă de articole, ghiduri sau videoclipuri. + +Multe părți ale Nette reprezintă vârful mondial și dorim ca acest lucru să continue. + +Fără o finanțare adecvată, nimic din toate acestea nu poate fi asigurat. Cu toate acestea, pentru a vă putea baza pe lansarea următoarelor versiuni, este nevoie de destul de puțin: să îl susțineți în fiecare lună, chiar și cu o sumă mică de bani. + +Implicați-vă și deveniți partener Nette! + +Veți asigura astfel funcționarea sănătoasă a proiectului pe care vă bazați. Și, în același timp, **veți obține o serie întreagă de avantaje exclusive** (vezi *Niveluri de parteneriat* în coloana din dreapta). Veți obține acces la conținut bonus. La suport tehnic. Veți crește prioritatea rezolvării problemelor dumneavoastră. Și, de asemenea, veți spori vizibilitatea companiei dumneavoastră și veți atrage dezvoltatori. + +Cum vă vom spori vizibilitatea? De exemplu, prin afișarea logo-ului dumneavoastră pe acest site (adică pe această pagină, pe pagina principală, în documentație, pe forum și pe o [pagină specială |https://nette.org/partner/vitalita], la care puteți face trimitere). Veți avea posibilitatea de a posta [oferte de muncă |https://forum.nette.org/cs/f30-prace-a-zakazky], de a face publicitate pe forum ([exemplu |https://forum.nette.org/cs/30798-problem-s-cizim-klicem-pri-mazani#p198093]) sau în documentație ([exemplu |https://doc.nette.org/cs/application/components#toc-flash-zpravy]), adică în locurile cu cea mai bună acoperire în rândul grupului de dezvoltatori Nette. + +Partenerilor le emitem facturi, astfel încât să poată include suportul în cheltuieli, fie lunar, trimestrial, semestrial sau anual. + +{{include: buttons}} +
                                                                                                                            diff --git a/www/ro/history.texy b/www/ro/history.texy index 627200f00e..c4bc024c59 100644 --- a/www/ro/history.texy +++ b/www/ro/history.texy @@ -1,36 +1,35 @@ -Istoria lui Nette -***************** +Istoria Nette +************* .[perex] -Originile Nette datează din 2004, când autorul său, David Grudl, a început să caute un cadru de lucru adecvat pentru a scrie aplicații, deoarece PHP-ul pur nu mai era suficient. Niciuna dintre soluțiile disponibile la acea vreme nu i se potrivea, așa că a început treptat să schițeze caracteristicile unui nou framework, care a primit ulterior numele de Nette. +Începutul Nette datează din 2004, când autorul său, David Grudl, a început să caute un framework potrivit în care să poată scrie aplicații, deoarece PHP pur nu mai era suficient. Nicio soluție disponibilă la acea vreme nu i se potrivea, așa că a început treptat să schițeze trăsăturile unui nou framework, care mai târziu a primit numele Nette. -La acea vreme, cadrele actuale precum Symfony, Laravel sau Ruby on Rails nu existau încă. În lumea Java, JSF (JavaServer Faces) era standardul, iar în domeniul concurent .NET, ASP.NET Webforms era cadrul dominant. Ambele permiteau construirea de pagini folosind componente UI reutilizabile. David a considerat că metodele lor de abstractizare și încercările de a crea statelessness prin intermediul protocolului HTTP fără stateless, folosind sesiuni sau postback-uri, erau defectuoase și fundamental defecte. Acestea au cauzat multe dificultăți pentru utilizatori și pentru motoarele de căutare. De exemplu, dacă salvați un link, aveați surpriza să găsiți mai târziu un conținut diferit sub el. +La acea vreme, framework-urile actuale precum Symfony, Laravel sau Ruby on Rails nu existau încă. În lumea Java, standardul era framework-ul JSF (JavaServer Faces), iar în .NET-ul concurent, ASP.NET Webforms. Ambele permiteau construirea paginilor folosind componente UI reutilizabile. Modurile lor de abstractizare și încercările de a crea statefulness peste protocolul stateless HTTP folosind sesiuni sau așa-numitul postback au fost considerate de David ca fiind greșite și fundamental nefuncționale. Acestea cauzau numeroase dificultăți utilizatorilor și motoarelor de căutare. De exemplu, dacă salvați un link, mai târziu ați fi surprins să găsiți alt conținut sub el. -Posibilitatea de a compune pagini din componente UI reutilizabile l-a fascinat pe David, care o cunoștea bine din Delphi, un instrument popular pentru crearea de aplicații desktop la acea vreme. Îi plăceau piețele cu componente opensource pentru Delphi. Așa că a încercat să rezolve problema modului de a crea un cadru de componente care, la rândul său, să funcționeze în deplină armonie cu HTTP fără stat. Căuta un concept care să fie prietenos pentru utilizator, SEO și dezvoltator. Și astfel s-a născut Nette. +Posibilitatea însăși de a compune pagini din componente UI reutilizabile l-a fascinat pe David, o cunoștea bine din Delphi, un instrument popular la acea vreme pentru crearea aplicațiilor desktop. Îi plăceau piețele cu componente opensource pentru Delphi. Prin urmare, a încercat să rezolve întrebarea cum să creeze un framework bazat pe componente care, dimpotrivă, să funcționeze în perfectă armonie cu HTTP-ul stateless. Căuta un concept care să fie prietenos pentru utilizatori, SEO și dezvoltatori. Și așa a început să se nască Nette. .[note] -Numele Nette a apărut din întâmplare în baie, când autorul a zărit o sticlă de gel de ras Gillette, rotită astfel încât să se vadă doar *llette*. +Numele Nette a apărut întâmplător în baie, când autorul a văzut un recipient cu gel de ras Gillette, întors astfel încât se vedea doar *llette*. -Au urmat mii de ore de cercetare, gândire și rescriere. Într-un garaj prăfuit dintr-un sat undeva în afara orașului Brno, se creau primele schițe ale viitorului cadru. Baza arhitecturii a fost modelul MVC, care a fost folosit apoi de către cadrul PHP Mojavi, acum uitat, și mai târziu popularizat de agitația din jurul Ruby on Rails. Una dintre sursele de inspirație a fost chiar cadrul phpBase, niciodată publicat, de Honza Tichý. +Au urmat mii de ore de cercetare, gândire și rescriere. Într-un garaj prăfuit dintr-un sat undeva lângă Brno, au început să prindă contur primele schițe ale viitorului framework. Baza arhitecturii a devenit modelul MVC, folosit la acea vreme de framework-ul PHP Mojavi, acum uitat, și popularizat ulterior datorită agitației din jurul Ruby on Rails. Una dintre sursele de inspirație a fost chiar și framework-ul phpBase, niciodată publicat, al lui Honza Tichý. -Pe blogul autorului au început să apară articole despre viitorul Nette. S-a glumit pe seama faptului că era vorba despre vaporware. Dar apoi, în octombrie 2007, în cadrul conferinței Prague PHP Seminar, David a prezentat public Nette. Apropo, această conferință s-a transformat un an mai târziu în WebExpo, devenită ulterior una dintre cele mai mari conferințe IT din Europa. Chiar și atunci, Nette a prezentat cu mândrie o serie de concepte originale, cum ar fi modelul de componente menționat mai sus, routerul bidirecțional, modul specific de conectare între prezentatori etc. Avea formulare, autentificare, caching etc. Totul este încă folosit în Nette în conceptul său original până astăzi. +Pe blogul autorului au început să apară articole despre viitorul Nette. Se glumea că este vaporware. Apoi, însă, în octombrie 2007, la conferința PHP Seminář din Praga, David a prezentat public Nette. Apropo, din această conferință s-a dezvoltat un an mai târziu WebExpo, ulterior una dintre cele mai mari conferințe IT din Europa. Deja atunci, Nette se lăuda cu o serie de concepte originale, precum modelul de componente menționat, routerul bidirecțional, modul specific de legare între Presentere etc. Avea formulare, autentificare rezolvată, caching etc. Totul se folosește în Nette în conceptul original și astăzi. .[note] -Nette folosește *prezentator* în loc de *controler* pentru că se presupune că erau prea multe cuvinte care începeau cu *con* în cod (controller, front controller, control, config, container, ...). +În Nette, în loc de termenul *controller* se folosește *Presenter*, deoarece în cod existau, se pare, prea multe cuvinte care începeau cu *con* (controller, front controller, control, config, container, ...) -La sfârșitul anului 2007, David Grudl a publicat codul și a fost lansat Nette 0.7. O comunitate entuziastă de programatori s-a format în jurul acestuia și a început să se întâlnească în fiecare lună la evenimentul Posobota. Comunitatea a inclus multe dintre personalitățile de astăzi, cum ar fi Ondrej Mirtes, autorul marelui instrument PHPStan. Dezvoltarea lui Nette a avansat, iar în următorii doi ani au fost lansate versiunile 0.8 și 0.9, care au pus bazele pentru aproape toate părțile de astăzi ale cadrului. Inclusiv fragmente AJAX care au precedat cu 14 ani Hotwire pentru Ruby on Rails sau Symfony UX Turbo. +La sfârșitul anului 2007, David Grudl a publicat și codul, și astfel a văzut lumina zilei versiunea Nette 0.7. Framework-ul a atras imediat o atenție enormă. În jurul său s-a format o comunitate entuziastă de programatori, care a început să se întâlnească lunar la evenimentul Posobota. În comunitate se aflau multe dintre personalitățile de astăzi, de exemplu Ondřej Mirtes, autorul excelentului instrument PHPStan. Dezvoltarea Nette a avansat rapid și în următorii doi ani au fost lansate versiunile 0.8 și 0.9, unde au fost puse bazele aproape tuturor părților actuale ale framework-ului. Inclusiv snippet-urile AJAX, care au devansat cu 14 ani Hotwire pentru Ruby on Rails sau Symfony UX Turbo. -Dar un lucru crucial lipsea din Nette pe atunci. Dependecy injection container (DIC). Nette folosea un *service locator*, iar intenția era să se treacă la dependecy injection. Dar cum să proiectezi un astfel de lucru? David Grudl, care nu avea nicio experiență cu DI la acea vreme, a mers la prânz cu Vasek Purchart, care folosea DI de aproximativ o jumătate de an. Împreună au discutat subiectul și David a început să lucreze la Nette DI, o bibliotecă care a revoluționat complet modul în care ne gândim la proiectarea aplicațiilor. Containerul DI a devenit una dintre cele mai de succes părți ale cadrului. Și a dat naștere la două produse derivate: formatul Neon și biblioteca Schema. +Un lucru esențial lipsea însă în Nette de atunci. Containerul de injecție de dependențe (DIC). Nette folosea așa-numitul *service locator* și intenția era de a trece tocmai la injecția de dependențe. Dar cum să proiectezi așa ceva? David Grudl, care la acea vreme nu avea experiență cu DI, a mers la prânz cu Vašek Purchart, care folosea DI de aproximativ jumătate de an. Au discutat împreună subiectul și David a început lucrul la Nette DI, o bibliotecă care a revoluționat complet modul de gândire asupra proiectării aplicațiilor. Containerul DI a devenit una dintre cele mai reușite părți ale framework-ului. Și a dat naștere ulterior și la două spin-off-uri: formatul Neon și biblioteca Schema. .[note] -Trecerea la injecția de dependență a necesitat mult timp și am așteptat câțiva ani pentru o nouă versiune de Nette. Prin urmare, când a apărut în cele din urmă, aceasta a fost numerotată 2. Așadar, Nette versiunea 1 nu există. +Trecerea la injecția de dependențe a necesitat destul timp și s-a așteptat câțiva ani pentru noua versiune Nette. De aceea, când a fost lansată în sfârșit, a purtat direct numărul 2. Versiunea Nette 1 deci nu există. -Nette și-a început istoria modernă în 2012, cu versiunea 2.0. Aceasta a adus și Nette Database, care a inclus un instrument de baze de date extrem de util, numit acum Explorer. Această bibliotecă a fost programată inițial de Jakub Vrána, vecinul lui David Grudl și autorul popularului instrument Adminer. Dezvoltarea sa ulterioară a fost preluată apoi de Jan Škrášek timp de trei ani. +Nette a început istoria sa modernă în 2012 cu versiunea 2.0. A adus și Nette Database, parte a căruia era și un instrument neobișnuit de util pentru lucrul cu baza de date, numit astăzi Explorer. Această bibliotecă a fost programată inițial de Jakub Vrána, vecinul lui David Grudl și autorul popularului instrument Adminer. Dezvoltarea sa ulterioară a fost preluată apoi pentru trei ani de Jan Škrášek. -În 2014, a fost lansat Nette 2.1, urmat la scurt timp de Nette 2.2. Cum este posibil acest lucru? Versiunea 2.2 a fost aceeași cu versiunea 2.1, doar că a fost împărțită în douăzeci de pachete separate. Instrumentul Composer s-a impus în lumea PHP și a schimbat modul în care ne gândim la crearea de biblioteci. Nette a încetat să mai fie un monolit și s-a despărțit în părți independente mai mici. Fiecare cu propriul depozit, cu propriul tracker de probleme și cu propriul flux de dezvoltare și versiuni. În acest fel, Nette nu trebuie să treacă prin absurditățile obișnuite în cadrele monolitice, în care apare o nouă versiune a unui pachet chiar dacă nu s-a schimbat nimic. Împărțirea efectivă a depozitelor Git a implicat câteva săptămâni de pregătire și sute de ore de lucru la mașină. - -De asemenea, Nette s-a clasat pe un uimitor loc 3 în sondajul global pentru cel mai bun framework PHP organizat de revista Sitepoint. +În 2014 a fost lansat Nette 2.1, urmat la scurt timp de Nette 2.2. Cum este posibil? Versiunea 2.2 era aceeași cu versiunea 2.1, doar împărțită în douăzeci de pachete separate. În lumea PHP s-a impus instrumentul Composer și a schimbat modul de abordare a creării bibliotecilor. Nette a încetat astfel să mai fie un monolit și s-a descompus în părți mai mici, independente. Fiecare cu propriul său depozit, issue tracker și propriul ritm de dezvoltare și versionare. În Nette, astfel, nu trebuie să apară absurdități comune în framework-urile monolitice, când se lansează o nouă versiune a unui pachet, deși nu s-a schimbat absolut nimic în el. Împărțirea propriu-zisă a depozitelor Git a implicat câteva săptămâni de pregătire și sute de ore de timp de mașină. +Nette s-a clasat, de asemenea, pe uimitorul loc 3 în sondajul mondial privind cel mai bun framework PHP, organizat de revista Sitepoint. {{toc:no}} diff --git a/www/ro/license.texy b/www/ro/license.texy new file mode 100644 index 0000000000..9a794c6449 --- /dev/null +++ b/www/ro/license.texy @@ -0,0 +1,44 @@ +Politica de licențiere +********************** + +Nette Framework este distribuit ca software liber, astfel încât oricine să îl poată utiliza. Puteți alege dacă vă convine mai bine licența [New BSD |#New BSD License] sau [#GNU General Public License (GPL)] în versiunea 2 sau 3. + +Licența BSD este recomandată pentru majoritatea proiectelor, deoarece este ușor de înțeles și nu impune aproape nicio restricție asupra a ceea ce puteți face cu framework-ul. Puteți utiliza Nette și în proiecte comerciale. Dacă, totuși, GPL se potrivește mai bine proiectului dumneavoastră, alegeți-o. Nu este necesar să informați pe nimeni despre decizia dumneavoastră. Păstrați întotdeauna drepturile de autor originale. + +Ar trebui să știți că numele "Nette Framework" este o marcă înregistrată protejată și utilizarea sa are anumite restricții. Prin urmare, nu utilizați "Nette" în numele proiectului dumneavoastră sau al domeniului de nivel superior, alegeți mai degrabă un nume care să stea pe propriile baze. Dacă proiectul dumneavoastră este bun, nu va dura mult și își va crea propria reputație. + +Dacă veți fi mulțumit de Nette Framework, și credem că veți fi, îl puteți [susțineți cu o contribuție |donate]. Vă mulțumim. + + +New BSD License +--------------- + +Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com) All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of "Nette Framework" nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +GNU General Public License (GPL) +-------------------------------- + +Textele licențelor GPL sunt foarte lungi, de aceea sunt furnizate aici linkuri către ele: + +- GPL version 2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +- GPL version 3: https://www.gnu.org/licenses/gpl-3.0.html + + +{{toc:yes}} +{{priority: -2}} diff --git a/www/ro/maintenance.texy b/www/ro/maintenance.texy index 585e4897c4..b1bc9da409 100644 --- a/www/ro/maintenance.texy +++ b/www/ro/maintenance.texy @@ -1,27 +1,27 @@ -Întreținere și versiuni PHP -*************************** +Întreținere și compatibilitate cu PHP +************************************* .[perex] -Nette este un cadru de lucru cu o perioadă de suport excepțional de lungă pentru versiunile individuale. Fiecare ramură este o versiune LTS (Long-Term Support Release) cu cel puțin 2 ani de suport. +Nette este un framework cu o perioadă de suport excepțional de lungă pentru fiecare versiune. Fiecare ramură este LTS (Long-Term Support Release) cu suport de cel puțin 2 ani. -Fiecare versiune este întreținută în mod activ pentru o perioadă de un an (sau mai mult) de la lansarea stabilă inițială. -Bug-urile critice și de securitate sunt corectate timp de doi ani. +Fiecare versiune este întreținută activ pentru o perioadă de un an (sau chiar mai mult) de la lansarea stabilă inițială. Erorile critice și de securitate sunt corectate timp de doi ani. -Calendar de lansare (foaie de parcurs) .[#toc-release-calendar-roadmap] -======================================================================= +Calendarul lansărilor Nette +=========================== {{include: doc-roadmap-table}} -Compatibilitatea PHP .[#toc-php-compatibility] -============================================== +Compatibilitate cu PHP +====================== + +Compatibilitatea este valabilă întotdeauna pentru cea mai recentă versiune din fiecare serie. -Compatibilitatea se aplică întotdeauna la cea mai recentă versiune din fiecare serie. {{include: doc-roadmap-versions}} {{leftbar: @menu-common}} {{toc: no}} -{{description: Versiuni Nette, foaia de parcurs, tabele de întreținere și compatibilitate PHP}} +{{description: Lansări Nette, roadmap, tabele de întreținere și compatibilitate cu PHP}} diff --git a/www/ro/packages.texy b/www/ro/packages.texy index 15c936ac05..6dfbb6916f 100644 --- a/www/ro/packages.texy +++ b/www/ro/packages.texy @@ -1,26 +1,27 @@ -Lista de pachete -**************** +Lista pachetelor Nette +********************** -| **Application**:[application:how-it-works] | Nucleul aplicației web | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] -| **Bootstrap**:[bootstrap:] | Bootstrap al aplicației dvs. | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] -| **Caching**:[caching:] | Stratul de cache cu un set de stocări | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] -| **Component Model**:[component-model:] | Fundația pentru sistemele de componente | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] -| **DI**:[dependency-injection:] | Dependency Injection Container | [GitHub |https://github.com/nette/di] [API |https://api.nette.org/di/] +| **Application**:[application:how-it-works] | Nucleul aplicațiilor web | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] +| **Assets**:[assets:] | Gestionarea fișierelor statice | [API |https://api.nette.org/assets/] [GitHub |https://github.com/nette/assets] +| **Bootstrap**:[bootstrap:] | Bootstrap aplicație web | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] +| **Caching**:[caching:] | Strat de cache cu stocări | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] +| **Component Model**:[component-model:] | Baza sistemului de componente | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] +| **DI**:[dependency-injection:] | Container DI | [GitHub |https://github.com/nette/di] [API |https://api.nette.org/di/] | **Database**:[database:] | Strat de bază de date | [GitHub |https://github.com/nette/database] [API |https://api.nette.org/database/] -| **Forms**:[forms:] | Facilitează în mare măsură formularele web securizate | [GitHub |https://github.com/nette/forms] [API |https://api.nette.org/forms/] -| **Http**:[http:] | Strat pentru solicitarea și răspunsul HTTP | [GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] -| **Latte**:[latte:] | Motor de șabloane uimitor | [GitHub |https://github.com/nette/latte] [API |https://api.nette.org/latte/] +| **Forms**:[forms:] | Formulare web convenabile și sigure | [GitHub |https://github.com/nette/forms] [API |https://api.nette.org/forms/] +| **Http**:[http:] | Strat care încapsulează cererea & răspunsul HTTP | [GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] +| **Latte**:[latte:] | Sistem excelent de șabloane | [GitHub |https://github.com/nette/latte] [API |https://api.nette.org/latte/] | **Mail**:[mail:] | Trimiterea de e-mailuri | [GitHub |https://github.com/nette/mail] [API |https://api.nette.org/mail/] -| **Neon**:[neon:] | Încarcă și descarcă [formatul NEON|https://ne-on.org] | [GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] +| **Neon**:[neon:] | Citirea și scrierea formatului [NEON |https://ne-on.org] | [GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] | **Php Generator**:[php-generator:] | Generator de cod PHP | [GitHub |https://github.com/nette/php-generator] [API |https://api.nette.org/php-generator/] -| **Robot Loader**:[robot-loader:] | Cea mai confortabilă încărcare automată | [GitHub |https://github.com/nette/robot-loader] [API |https://api.nette.org/robot-loader/] -| **Routing**:[application:routing] | Routing | [GitHub |https://github.com/nette/routing] [API |https://api.nette.org/routing/] +| **Robot Loader**:[robot-loader:] | Cel mai confortabil autoloading | [GitHub |https://github.com/nette/robot-loader] [API |https://api.nette.org/robot-loader/] +| **Routing**:[application:routing] | Rutare | [GitHub |https://github.com/nette/routing] [API |https://api.nette.org/routing/] | **Safe Stream**:[safe-stream:] | Operații atomice sigure cu fișiere | [GitHub |https://github.com/nette/safe-stream] [API |https://api.nette.org/safe-stream/] -| **Security**:[security:authentication] | Oferă un sistem de control al accesului | [GitHub |https://github.com/nette/security] [API |https://api.nette.org/security/] -| **Schema**:[schema:] | Validarea datelor de utilizator | [GitHub |https://github.com/nette/schema] [API |https://api.nette.org/schema/] -| **Tester**:[tester:] | Testarea unitară plăcută în PHP | [GitHub |https://github.com/nette/tester] [API |https://api.nette.org/tester/] +| **Schema**:[schema:] | Validarea datelor utilizatorului | [GitHub |https://github.com/nette/schema] [API |https://api.nette.org/schema/] +| **Security**:[security:authentication] | Gestionarea drepturilor de acces | [GitHub |https://github.com/nette/security] [API |https://api.nette.org/security/] +| **Tester**:[tester:] | Teste unitare relaxate în PHP | [GitHub |https://github.com/nette/tester] [API |https://api.nette.org/tester/] | **Tracy**:[tracy:] | Instrument de depanare pe care îl veți iubi ♥ | [GitHub |https://github.com/nette/tracy] [API |https://api.nette.org/tracy/] -| **Utils**:[utils:] | Utilități și clase de bază | [GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] +| **Utils**:[utils:] | Clase și instrumente de bază | [GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] {{leftbar: @menu-common}} {{toc: no}} diff --git a/www/ru/10-reasons-why-nette.texy b/www/ru/10-reasons-why-nette.texy index b822b9f1d1..2fc406a010 100644 --- a/www/ru/10-reasons-why-nette.texy +++ b/www/ru/10-reasons-why-nette.texy @@ -1,53 +1,93 @@ -Зачем использовать Nette? -************************* +7 причин использовать Nette +***************************
                                                                                                                            -Устали от повторяющихся задач, тысяч мелочей, которые отвлекают вас от работы и превращают программирование в скучное занятие? Вы находитесь в правильном месте! **Framework облегчит вашу работу, вы будете писать меньше, иметь более чистый код и получать удовольствие от работы.** Вы получите: +Представьте себе PHP-фреймворк, который позволяет вам сосредоточиться на том, что вы делаете лучше всего. Он направляет вас к написанию чистого кода. Сам заботится о безопасности. Перестаньте мечтать и познакомьтесь с Nette. Отправьтесь в путешествие, которое откроет вам двери к новым возможностям разработки. Мы расскажем: -- отличную систему шаблонов -- непревзойденные инструменты для настройки -- чрезвычайно эффективный слой базы данных -- надежную защиту от уязвимостей -- современный фреймворк с поддержкой HTML5, AJAX или SEO -- качественную документацию и активное сообщество -- зрелый и чистый объектно-ориентированный дизайн с использованием новейших возможностей PHP -- решения, которые поощряются, но не применяются на практике +- как создавать сайты с максимальным комфортом +- как писать элегантный код +- что означает мудрость «меньше кода = больше безопасности» +- как строить веб-сайт как конструктор +- и как стать частью успешного сообщества
                                                                                                                            -И это совершенно бесплатно. Стоит попробовать, не так ли? +Nette привносит удобство и эффективность в мир веб-разработчиков благодаря инновационным инструментам и техникам. Каковы ключевые особенности, которые делают Nette уникальным и необходимым элементом набора разработчика? Давайте рассмотрим их! -**Фреймворк Nette создан, чтобы быть максимально удобным и дружественным к пользователю.**. Это фреймворк, с которым не только легко, но и интересно работать. Он дает вам ясный и экономичный синтаксис, удобен в программировании и отладке, позволяет сосредоточиться на творческой стороне разработки. Устраняет риски безопасности. Вы можете создавать интернет-магазины, вики, блоги, CMS или всё, что вы только можете себе представить, быстрее и лучше, чем когда-либо. +#1 Nette вас балует +------------------- -Nette [используется крупными компаниями |https://builtwith.nette.org], такими как T-Systems, GE Money, Mladá fronta, VLTAVA-LABE-PRESS, Internet Info, DHL, Logio, ESET,Actum, Slevomat, Socialbakers, SUPRAPHON. В настоящее время на нем работает сайт бывшего президента Чешской Республики Вацлава Клауса. В опросе, проведенном [Zdroják |https://www.zdrojak.cz/clanky/vysledky-technologie-na-ceskem-webu/], он получил приз как самый популярный и наиболее широко используемый фреймворк в Чешской Республике. +Когда двадцать лет назад начал зарождаться фреймворк Nette, все вращалось вокруг одной цели: Как создавать сайты максимально удобно? Как максимально облегчить работу программистам? Как сделать создание сайтов привлекательным? -Когда вы освоите Nette, у вас не будет недостатка в интересных предложениях о работе. +Nette этим подходом привлекло многих программистов и быстро завоевало популярность. Тогда мы называли эту философию Netteway, а сегодня для нее существует термин *Developer Experience* (DX). Nette — это фреймворк, у которого DX в его ДНК. Разницу вы почувствуете в тысяче и одной вещи – от самых мелких деталей до принципиальных инноваций. Позвольте фреймворку побаловать вас. -Приступим к работе .[#toc-get-on-board] ---------------------------------------- +#2 Nette ведет вас к чистому коду +--------------------------------- -Следуйте руководству и шаг за шагом [создайте свое первое приложение |quickstart:]. Полное [Руководство программиста |@home] и удобная [Документация по API |https://api.nette.org/] с обзором классов и методов всегда будут у вас под рукой. +Хотите писать чистый код? Иметь правильно спроектированные приложения? Кто бы не хотел! И именно здесь начинается роль фреймворка. Если он сам не подает пример, невозможно создать отлично спроектированное приложение. -Если вы застряли, загляните в раздел [Решение проблем |nette:troubleshooting] или на [официальный форум |https://forum.nette.org/en/]. +Nette подает пример. Это наставник, обучающий хорошим привычкам и написанию кода по проверенным методикам. Как пионер и евангелист dependency injection, он предлагает качественную основу для устойчивых, расширяемых и легко читаемых приложений. Nette разработан так, чтобы быть понятным для начинающих, но в то же время предлагать достаточную глубину для опытных разработчиков. -Самообучение кажется неэффективным способом? Мы понимаем и предлагаем [обучение фреймворку Nette |https://www.skoleniphp.cz/skoleni-nette-vyvoj-webovych-aplikaci] (на чешском). Отзывы бывших участников можно найти на сайте [skoleniphp.cz/ohlasy |https://www.skoleniphp.cz/ohlasy]. +Сообщество вокруг Nette воспитало ряд личностей, которые сегодня стоят за успешными и важными проектами. Для тысяч программистов Nette стало наставником на их пути к профессиональному росту. Присоединяйтесь и откройте для себя, как Nette положительно повлияет на качество вашего кода и приложений. -Дополнительная информация .[#toc-more-information] --------------------------------------------------- +#3 Надежный страж ваших приложений +---------------------------------- -У нас есть [блог, полный советов |https://blog.nette.org] и коллекция различных [аддонов и компонентов |https://componette.org] для использования в ваших приложениях. +Nette защищает ваши приложения. За годы он заработал репутацию инструмента, который относится к безопасности чрезвычайно серьезно. Он предоставляет продуманную защиту от уязвимостей. Всегда заботится о том, чтобы облегчить работу программистам, но никогда за счет безопасности. -Кроме того, каждый месяц сообщество Nette собирается на мероприятие под названием [Последняя суббота |https://www.posobota.cz], где члены сообщества делятся своим опытом и просто хорошо проводят время. Приходите выпить пива! +Его девиз «меньше кода = достаточная безопасность» означает, что отдельные элементы безопасно ведут себя уже по умолчанию. Нет необходимости активировать элементы безопасности, дописывая дополнительный код. Вы не можете об этом забыть или бояться, что что-то упустили. Программисты часто даже не подозревают, сколько вещей, связанных с безопасностью, Nette делает за них, и удивляются, когда узнают об этом. +Представляем вам фреймворк, который ведет вас к чистому коду и одновременно следит за безопасностью ваших приложений. Nette — надежный партнер, который позволит вам сосредоточиться на создании отличных веб-приложений со спокойной душой. -Участвуйте! .[#toc-get-involved] --------------------------------- -Мы будем очень рады, если вы внесете свой вклад в развитие фреймворка. Рекомендуйте Nette знакомым, размещайте [значок |www:en:logo] на своем сайте или [поддерживайте разработку |www:en:donate] финансово. Спасибо ♥. +#4 Стройте веб-сайт как конструктор +----------------------------------- + +В Nette вы строите страницы из повторно используемых UI-компонентов. Это напоминает разработку десктопных приложений, и Nette успешно перенесло этот подход в веб. Нужен datagrid в админке? Достаточно найти его на рынке open source компонентов, установить и просто вставить на страницу. Кроме того, вы можете создавать собственные компоненты для повторяющихся элементов на страницах, тем самым устраняя дублирование и улучшая организацию кода. + +Эта уникальная особенность отличает Nette от всех других значимых игроков на рынке. Она позволит вам эффективно создавать и поддерживать веб-приложения. С Nette работа с UI становится гладким и приятным опытом. + + +#5 Гибкий набор пакетов +----------------------- + +Nette — это набор [отдельно используемых пакетов |www:packages]. Среди них захватывающий [инструмент отладки Tracy |tracy:], [система шаблонов нового поколения Latte |latte:], [превосходный Dependency Injection Container |dependency-injection:], [формы |forms:] и многие другие. Каждый пакет имеет читаемую подробную документацию и размещается в отдельном репозитории на GitHub. Пакеты можно использовать отдельно или комбинировать с другими инструментами и технологиями, которые вы уже используете. Например, Latte можно развернуть в WordPress или Slim Framework, DI-контейнер может быть ядром корпоративного фреймворка, а Tracy будет визуализировать сообщения об ошибках. + +Или вы можете использовать Nette целиком, как фреймворк, и создать в нем полное веб-приложение. Независимо от того, разрабатываете ли вы небольшой личный проект или надежное корпоративное приложение, Nette будет вашим надежным партнером. + + +#6 Стабильность и инновации +--------------------------- + +Nette — это зрелый и проверенный фреймворк с многолетней историей. Несмотря на это, он остается гибким и эластичным благодаря умному дизайну. Пользователи ценят, что это небольшой и гибкий фреймворк, а не монстр. + +Он всегда заранее готов к новым версиям PHP и учитывает последние инновации в области разработки веб-приложений. Это ключевой момент для минимизации технологического долга. + +Nette — это сочетание многолетней стабильности и инноваций, которое позволит вам положиться на проверенное решение, в то же время имея возможность использовать новейшие технологии и тенденции. С Nette вы можете быть уверены, что ваш проект будет построен на фундаменте, который постоянно идет в ногу с быстро развивающимся миром веб-разработки. + + +#7 Будьте в лучшей компании +--------------------------- + +Nette Framework популярен среди профессионалов. На него полагаются [известные компании |https://builtwith.nette.org], такие как O2, BOSCH, ESET, Zásilkovna, DHL, SUPRAPHON и сотни других. В нескольких опросах он победил как самый популярный и наиболее используемый фреймворк в Чешской Республике. + +Начните с Nette, и вам откроются новые возможности для трудоустройства. Вы получите не только мощный инструмент, но и самое активное сообщество в Чехии, готовое поддержать вас на пути к профессиональному росту. Они регулярно встречаются на [Posobotách |www.posobota.cz], мероприятиях, где обмениваются опытом. Приходите познакомиться с нами! + + +Откройте для себя Nette +----------------------- + +Присоединяйтесь к тысячам довольных разработчиков, которые уже открыли для себя преимущества Nette, и начните писать более чистый, безопасный и эффективный код с этим уникальным фреймворком. + +Создайте [по инструкции |quickstart:] свое первое приложение, шаг за шагом. Вам всегда будет доступна [обширная документация |nette:] и практический [обзор API |https://api.nette.org]. Посетите наш [блог, полный советов |https://blog.nette.org] и коллекцию [дополнений и компонентов |https://componette.org], расширяющих возможности Nette. + +Есть вопросы? Обратитесь к странице с [часто задаваемыми вопросами |nette:troubleshooting] или обсудите на [чешском форуме |https://forum.nette.org/cs/]. Предпочитаете личное обучение? Мы предлагаем [обучение Nette Framework |https://www.skoleniphp.cz/skoleni-nette-vyvoj-webovych-aplikaci] с [отличными отзывами |https://www.skoleniphp.cz/ohlasy]. + +**Познакомьтесь с фреймворком, который будет вас баловать, направлять и вдохновлять.** {{leftbar: @menu-common}} diff --git a/www/ru/@home.texy b/www/ru/@home.texy index a7bf595482..a749b87522 100644 --- a/www/ru/@home.texy +++ b/www/ru/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Nette - удобная и безопасная разработка веб-приложений на PHP}} -{{description: Nette - это семейство продвинутых и самодостаточных компонентов для PHP. Радуйтесь им. Вместе они образуют основу, занимающую 3-е место по популярности в мире. В философии Nette особое внимание уделяется продуктивности, передовому опыту и безопасности}} +{{maintitle:Nette – Удобная и безопасная разработка веб-приложений на PHP}} +{{description: Nette — это семейство зрелых и независимо используемых компонентов для PHP. Вдохновитесь ими. Вместе они образуют фреймворк, оцененный как 3-й по популярности в мире. Философия Nette уделяет особое внимание производительности, лучшим практикам и безопасности.}} diff --git a/www/ru/@menu-common.texy b/www/ru/@menu-common.texy index 66ae5cfac1..b3e9b34774 100644 --- a/www/ru/@menu-common.texy +++ b/www/ru/@menu-common.texy @@ -1,20 +1,22 @@ -Введение -******** +Знакомство +********** - [Почему стоит использовать Nette? |www:10-reasons-why-nette] - [Установка |nette:installation] -- [Создайте свое первое приложение! |quickstart:] +- [Пишем первое приложение! |quickstart:] Общие темы ********** - [Список пакетов |www:packages] -- [Обслуживание и PHP |www:maintenance] -- [Заметки о выпуске |https://nette.org/releases] -- [Руководство по обновлению |migrations:en] -- [Решение проблем |nette:troubleshooting] -- [Создатели Nette |https://nette.org/contributors] -- [История Nette |history] -- [Принять участие |contributing:] -- [Развитие спонсоров |https://nette.org/en/donate] -- [Справочник по API |https://api.nette.org] -- [Лучшие практики |best-practices:] +- [Поддержка и версии PHP |www:maintenance] +- [Примечания к выпуску |https://nette.org/releases] +- [Переход на новые версии|migrations:en] +- [Устранение неполадок |nette:troubleshooting] +- [Кто создает Nette |https://nette.org/contributors] +- [История Nette |www:history] +- [Примите участие |contributing:] +- [Поддержите разработку |https://nette.org/ru/donate] +- [Справочник API |https://api.nette.org/] +- [Руководства и лучшие практики |best-practices:] + +- [Безопасность прежде всего |nette:vulnerability-protection] diff --git a/www/ru/donate.texy b/www/ru/donate.texy new file mode 100644 index 0000000000..57b8c0b7d6 --- /dev/null +++ b/www/ru/donate.texy @@ -0,0 +1,20 @@ +Поддержите разработку Nette +*************************** + +
                                                                                                                            +Каждый, кто строит на Nette, заинтересован в активном развитии фреймворка. Чтобы он поддерживал новые версии PHP. Чтобы исправлялись ошибки. Чтобы появлялись новые возможности, облегчающие работу или экономящие время и деньги. Чтобы у фреймворка была отличная документация и полезный контент вокруг него, будь то статьи, руководства или видео. + +Многие части Nette представляют собой мировой уровень, и мы хотим, чтобы так было и дальше. + +Без адекватного финансирования ничего из этого обеспечить нельзя. При этом, чтобы вы могли быть уверены, что выйдут следующие версии, нужно совсем немного: чтобы вы каждый месяц поддерживали его хотя бы небольшой финансовой суммой. + +Присоединяйтесь и станьте партнером Nette! + +Вы обеспечите здоровое функционирование проекта, на который полагаетесь. И одновременно **получите целый ряд эксклюзивных преимуществ** (см. *Уровни партнерства* в правом столбце). Вы получите доступ к бонусному контенту. К технической поддержке. Повысите приоритет решения ваших issues. А также сделаете свою компанию заметнее и привлечете к себе разработчиков. + +Как мы сделаем вас заметнее? Например, разместив ваш логотип на этом сайте (т.е. на этой странице, на главной странице, в документации, на форуме и на [специальной странице |https://nette.org/partner/vitalita], на которую вы можете ссылаться). У вас будет возможность размещать [предложения о работе |https://forum.nette.org/cs/f30-prace-a-zakazky], рекламировать на форуме ([пример |https://forum.nette.org/cs/30798-problem-s-cizim-klicem-pri-mazani#p198093]) или в документации ([пример |https://doc.nette.org/cs/application/components#toc-flash-zpravy]), то есть в местах с наилучшим охватом группы разработчиков Nette. + +Партнерам мы выставляем счета, чтобы они могли включить поддержку в расходы, ежемесячно, ежеквартально, раз в полгода или ежегодно. + +{{include: buttons}} +
                                                                                                                            diff --git a/www/ru/history.texy b/www/ru/history.texy index 650a01c051..dfeabe9e01 100644 --- a/www/ru/history.texy +++ b/www/ru/history.texy @@ -2,34 +2,34 @@ ************* .[perex] -Истоки Nette берут начало в 2004 году, когда его автор Дэвид Грудл начал искать подходящий фреймворк для написания приложений, поскольку чистого PHP было уже недостаточно. Ни одно из доступных на тот момент решений его не устраивало, поэтому он постепенно начал набрасывать черты нового фреймворка, который впоследствии получил название Nette. +Начало создания Nette относится к 2004 году, когда его автор Давид Грудл начал искать подходящий фреймворк, на котором он мог бы писать приложения, так как чистого PHP для этого уже было недостаточно. Ни одно из доступных на тот момент решений ему не подходило, поэтому он начал постепенно набрасывать черты нового фреймворка, который позже получил название Nette. -В то время таких современных фреймворков, как Symfony, Laravel или Ruby on Rails, еще не существовало. В мире Java стандартом был JSF (JavaServer Faces), а в конкурирующем мире .NET стандартом был ASP.NET Webforms. Оба они позволяли создавать страницы с использованием многократно используемых компонентов пользовательского интерфейса. Дэвид считал их методы абстракции и попытки создать безгражданственность по безгражданскому протоколу HTTP с помощью сессий или постбеков несовершенными и принципиально неработающими. Они создавали множество трудностей для пользователей и поисковых систем. Например, если вы сохранили ссылку, то с удивлением обнаружили под ней позже другой контент. +В то время еще не существовало современных фреймворков, таких как Symfony, Laravel или Ruby on Rails. В мире Java стандартом был фреймворк JSF (JavaServer Faces), а в конкурирующем .NET — ASP.NET Webforms. Оба позволяли создавать страницы с помощью повторно используемых UI-компонентов. Их способы абстракции и попытки создать состояние поверх безстатусного протокола HTTP с помощью сессий или так называемого postback Давид считал ошибочными и изначально неработоспособными. Они вызывали ряд трудностей у пользователей и поисковых систем. Например, если вы сохраняли ссылку, то позже с удивлением обнаруживали под ней другое содержимое. -Сама возможность составления страниц из многократно используемых компонентов пользовательского интерфейса завораживала Дэвида, и он хорошо знал ее по Delphi, популярному в то время средству разработки настольных приложений. Ему понравились торговые площадки с компонентами с открытым исходным кодом для Delphi. Поэтому он попытался решить вопрос о том, как создать компонентный фреймворк, который, в свою очередь, работал бы в полной гармонии с stateless HTTP. Он искал концепцию, которая была бы удобна для пользователей, SEO и разработчиков. Так родилась Nette. +Сама возможность собирать страницы из повторно используемых UI-компонентов очаровала Давида, он хорошо знал ее по Delphi, популярному в то время инструменту для создания десктопных приложений. Ему нравились рынки с opensource компонентами для Delphi. Поэтому он пытался решить вопрос, как создать компонентный фреймворк, который, наоборот, работал бы в полном соответствии с безстатусным HTTP. Он искал концепцию, которая была бы дружелюбной для пользователей, SEO и разработчиков. Так начал зарождаться Nette. .[note] -Имя Nette появилось случайно в ванной комнате, когда автор заметила контейнер с гелем для бритья Gillette, повернутый так, что была видна только *llette*. +Название Nette возникло случайно в ванной, когда автор увидел баночку с гелем для бритья Gillette, повернутую так, что было видно только *llette*. -Затем последовали тысячи часов исследований, размышлений и переписывания. В пыльном гараже в деревне где-то за пределами Брно были созданы первые очертания будущего каркаса. В основе архитектуры лежал паттерн MVC, который затем использовался в забытом ныне PHP-фреймворке Mojavi, а позже был популяризирован шумихой вокруг Ruby on Rails. Одним из источников вдохновения стал так и не опубликованный фреймворк phpBase Хонзы Тихи. +Последовали тысячи часов исследований, размышлений и переписываний. В пыльном гараже в деревне где-то под Брно возникали первые очертания будущего фреймворка. Основой архитектуры стал паттерн MVC, который тогда использовал уже забытый PHP-фреймворк Mojavi и позже был популяризирован благодаря шумихе вокруг Ruby on Rails. Одним из источников вдохновения был даже никогда не опубликованный фреймворк phpBase Яна Тихого. -В блоге автора начали появляться статьи о предстоящем выходе "Nette". Шутили, что речь идет о паровой продукции. Но затем в октябре 2007 года, на конференции Prague PHP Seminar, Дэвид публично представил Nette. Кстати, через год эта конференция превратилась в WebExpo, которая впоследствии стала одной из крупнейших ИТ-конференций в Европе. Уже тогда Nette мог похвастаться рядом оригинальных концепций, таких как вышеупомянутая компонентная модель, двунаправленный маршрутизатор, особый способ связи между ведущими и т.д. В нем были формы, аутентификация, кэширование и т.д. Все и сегодня используется в Nette в своей первоначальной концепции. +В блоге автора начали появляться статьи о готовящемся Nette. Шутили, что это vaporware. Но затем в октябре 2007 года на пражской конференции PHP Seminář Давид публично представил Nette. Кстати, из этой конференции через год выросло WebExpo, позже одна из крупнейших IT-конференций в Европе. Уже тогда Nette могло похвастаться рядом оригинальных концепций, таких как упомянутая компонентная модель, двунаправленный роутер, специфический способ ссылок между презентерами и т.д. У него были формы, решенная аутентификация, кеширование и т.д. Все это используется в Nette в первоначальном виде и по сей день. .[note] -Nette использует *presenter* вместо *controller*, потому что в коде якобы было слишком много слов, начинающихся с *con* (controller, front controller, control, config, container, ...). +В Nette вместо термина *controller* используется *presenter*, потому что в коде, якобы, было слишком много слов, начинающихся на *con* (controller, front controller, control, config, container, ...) -В конце 2007 года Дэвид Грудл опубликовал код, и была выпущена версия Nette 0.7. Вокруг него сформировалось сообщество программистов-энтузиастов, которые стали встречаться каждый месяц на мероприятии Posobota. В сообщество входили многие из современных светил, например, Ондржей Миртес, автор замечательного инструмента PHPStan. Разработка Nette продвигалась вперед, и в течение следующих двух лет были выпущены версии 0.8 и 0.9, заложившие основу для почти всех современных частей фреймворка. Включая фрагменты AJAX, которые на 14 лет предшествовали Hotwire для Ruby on Rails или Symfony UX Turbo. +В конце 2007 года Давид Грудл опубликовал и код, и так свет увидела версия Nette 0.7. Фреймворк сразу же привлек к себе огромное внимание. Вокруг него сформировалось восторженное сообщество программистов, которое начало каждый месяц встречаться на мероприятии Posobota. В сообществе было много сегодняшних знаменитостей, например, Ондржей Миртес, автор замечательного инструмента PHPStan. Разработка Nette шла вперед, и в следующие два года вышли версии 0.8 и 0.9, где были заложены основы почти всех сегодняшних частей фреймворка. Включая AJAX-сниппеты, которые на 14 лет опередили Hotwire для Ruby on Rails или Symfony UX Turbo. -Но в то время Nette не хватало одной важной вещи. Контейнер для инъекции зависимостей (DIC). Nette использовала *сервисный локатор*, и было намерение перейти на dependecy injection. Но как спроектировать такую вещь? Дэвид Грудл, который в то время не имел опыта работы с DI, пошел на обед с Вашеком Перчартом, который использовал DI около полугода. Вместе они обсудили эту тему, и Дэвид начал работу над Nette DI, библиотекой, которая полностью изменила наше представление о дизайне приложений. Контейнер DI стал одной из самых успешных частей фреймворка. Это привело к появлению двух побочных продуктов: формата Neon и библиотеки Schema. +Но одна важная вещь в тогдашнем Nette отсутствовала. Dependecy injection container (DIC). Nette использовало так называемый *service locator*, и намерение было перейти именно на dependecy injection. Но как спроектировать такую вещь? Давид Грудл, у которого тогда не было опыта с DI, пошел на обед с Вашеком Пурхартом, который использовал DI около полугода. Они вместе обсудили тему, и Давид начал работу над Nette DI, библиотекой, которая полностью перевернула способ мышления о проектировании приложений. DI-контейнер стал одной из самых удачных частей фреймворка. И позже породил два спин-оффа: формат Neon и библиотеку Schema. .[note] -Переход на внедрение зависимостей занял много времени, и новая версия Nette готовилась несколько лет. Вот почему, когда он наконец вышел, он был пронумерован как 2. Таким образом, Nette версии 1 не существует. +Переход на dependency injection потребовал довольно много времени, и новую версию Nette пришлось ждать несколько лет. Поэтому, когда она наконец вышла, она сразу получила номер 2. Версии Nette 1, таким образом, не существует. -Свою современную историю Nette начала в 2012 году с версии 2.0. Она также принесла Nette Database, которая включала чрезвычайно удобный инструмент для работы с базами данных, который теперь называется Explorer. Эта библиотека была первоначально запрограммирована Якубом Враной, соседом Дэвида Груделя и автором популярного инструмента Adminer. Дальнейшим развитием компании в течение трех лет занимался Ян Шкрашек. +Nette в 2012 году версией 2.0 начало свою современную историю. Оно также принесло Nette Database, частью которого был и необычайно удобный инструмент для работы с базой данных, сегодня называемый Explorer. Эту библиотеку изначально написал Якуб Врана, сосед Давида Грудла и автор популярного инструмента Adminer. Ее дальнейшей разработкой затем на три года занялся Ян Шкрашек. -В 2014 году была выпущена версия Nette 2.1, за которой вскоре последовала версия Nette 2.2. Версия 2.2 была такой же, как и версия 2.1, только разбита на двадцать отдельных пакетов. Инструмент Composer прижился в мире PHP и изменил наше представление о создании библиотек. Nette перестала быть монолитом и распалась на мелкие независимые части. Каждый из них имеет свой собственный репозиторий, трекер проблем и свой собственный темп разработки и версионирования. Таким образом, Nette не нужно проходить через абсурды, характерные для монолитных фреймворков, когда выходит новая версия пакета, хотя ничего не изменилось. Фактическое разделение репозиториев Git потребовало нескольких недель подготовки и сотен часов машинного времени. +В 2014 году вышло Nette 2.1, за которым вскоре последовало Nette 2.2. Как это возможно? Версия 2.2 была такой же, как версия 2.1, только разделенной на двадцать отдельных пакетов. В мире PHP прижился инструмент Composer и изменил способ восприятия создания библиотек. Nette перестало быть монолитом и распалось на меньшие независимые части. Каждая со своим репозиторием, трекером проблем и собственным темпом разработки и версионирования. В Nette, таким образом, не должно возникать абсурдов, обычных в монолитных фреймворках, когда выходит новая версия пакета, хотя в нем вообще ничего не изменилось. Само разделение Git-репозиториев потребовало нескольких недель подготовки и сотен часов машинного времени. -Nette также занял удивительное 3-е место в глобальном опросе на лучший PHP-фреймворк, организованном журналом Sitepoint. +Nette также заняло потрясающее 3-е место во всемирном опросе о лучшем PHP-фреймворке, проведенном журналом Sitepoint. {{toc:no}} diff --git a/www/ru/license.texy b/www/ru/license.texy new file mode 100644 index 0000000000..4a267565fd --- /dev/null +++ b/www/ru/license.texy @@ -0,0 +1,44 @@ +Лицензионная политика +********************* + +Nette Framework распространяется как свободное программное обеспечение, чтобы любой мог его использовать. Вы можете выбрать, какая лицензия вам больше подходит: [New BSD |#New BSD License] или [#GNU General Public License (GPL)] версии 2 или 3. + +Лицензия BSD рекомендуется для большинства проектов, так как ее легко понять и она почти не накладывает ограничений на то, что вы можете делать с фреймворком. Вы можете использовать Nette и в коммерческих проектах. Однако, если GPL лучше подходит для вашего проекта, выберите ее. При этом не нужно никого информировать о вашем решении. Всегда сохраняйте оригинальные авторские права. + +Вы должны знать, что название "Nette Framework" является зарегистрированной торговой маркой, и ее использование имеет определенные ограничения. Поэтому не используйте "Nette" в названии вашего проекта или домена верхнего уровня, выберите лучше имя, которое будет стоять на собственных основах. Если ваш проект хорош, не пройдет много времени, и он создаст себе репутацию. + +Если вы будете довольны Nette Framework, а мы верим, что да, вы можете [поддержать его взносом |donate]. Спасибо. + + +New BSD License +--------------- + +Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com) All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of "Nette Framework" nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +GNU General Public License (GPL) +-------------------------------- + +Тексты лицензий GPL очень длинные, поэтому здесь приведены ссылки на них: + +- GPL version 2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +- GPL version 3: https://www.gnu.org/licenses/gpl-3.0.html + + +{{toc:yes}} +{{priority: -2}} diff --git a/www/ru/maintenance.texy b/www/ru/maintenance.texy index 916f461e2b..247acc9341 100644 --- a/www/ru/maintenance.texy +++ b/www/ru/maintenance.texy @@ -2,20 +2,19 @@ ********************************** .[perex] -Nette - это фреймворк с чрезвычайно длительным сроком поддержки каждого релиза. Каждая ветка представляет собой LTS (Long-Term Support Release) с поддержкой не менее 2 лет. +Nette — это фреймворк с чрезвычайно долгим сроком поддержки отдельных выпусков. Каждая ветка является LTS (Long-Term Support Release) с поддержкой не менее 2 лет. -Каждый релиз активно поддерживается в течение одного года (или более) с момента выпуска первого стабильного релиза. -Критические и уязвимые места в системе безопасности исправляются в течение двух лет. +Каждая версия активно поддерживается в течение одного года (или дольше) с момента первоначального стабильного выпуска. Критические ошибки и ошибки безопасности исправляются в течение двух лет. -Календарь выпуска продукции Nette .[#toc-release-calendar-roadmap] -================================================================== +Календарь выпуска Nette +======================= {{include: doc-roadmap-table}} -Совместимость с PHP .[#toc-php-compatibility] -============================================= +Совместимость с PHP +=================== Совместимость всегда относится к последнему выпуску каждой серии. @@ -25,4 +24,4 @@ Nette - это фреймворк с чрезвычайно длительным {{leftbar: @menu-common}} {{toc: no}} -{{description: релиз Nette, дорожная карта, таблицы обслуживания и совместимость с PHP}} +{{description: Выпуски Nette, дорожная карта, таблицы обслуживания и совместимость с PHP}} diff --git a/www/ru/packages.texy b/www/ru/packages.texy index a8ac99ed1e..70d68370c5 100644 --- a/www/ru/packages.texy +++ b/www/ru/packages.texy @@ -1,26 +1,27 @@ -Список пакетов -************** +Список пакетов Nette +******************** -| **Application**:[application:how-it-works] | Ядро веб-приложения | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] -| **Bootstrap**:[bootstrap:] | Загрузочная платформа вашего приложения | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] -| **Caching**:[caching:] | Слой кэша с набором хранилищ | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] -| **Component Model**:[component-model:] | Основа для компонентных систем | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] -| **DI**:[dependency-injection:] | Контейнер инъекции зависимостей | [GitHub |https://github.com/nette/di] [API |https://api.nette.org/di/] -| **Database**:[database:] | Уровень базы данных | [GitHub |https://github.com/nette/database] [API |https://api.nette.org/database/] -| **Forms**:[forms:] | Значительно облегчает безопасные веб-формы | [GitHub |https://github.com/nette/forms] [API |https://api.nette.org/forms/] -| **Http**:[http:] | Слой для HTTP запроса и ответа | [GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] -| **Latte**:[latte:] | Удивительный движок шаблонов | [GitHub |https://github.com/nette/latte] [API |https://api.nette.org/latte/] +| **Application**:[application:how-it-works] | Ядро веб-приложений | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] +| **Assets**:[assets:] | Управление статическими файлами | [GitHub |https://github.com/nette/assets] [API |https://api.nette.org/assets/] +| **Bootstrap**:[bootstrap:] | Bootstrap веб-приложения | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] +| **Caching**:[caching:] | Слой кеширования с хранилищами | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] +| **Component Model**:[component-model:] | Основа компонентной системы | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] +| **DI**:[dependency-injection:] | Dependency Injection Container | [GitHub |https://github.com/nette/di] [API |https://api.nette.org/di/] +| **Database**:[database:] | Слой базы данных | [GitHub |https://github.com/nette/database] [API |https://api.nette.org/database/] +| **Forms**:[forms:] | Удобные и безопасные веб-формы | [GitHub |https://github.com/nette/forms] [API |https://api.nette.org/forms/] +| **Http**:[http:] | Слой, инкапсулирующий HTTP request & response | [GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] +| **Latte**:[latte:] | Отличная система шаблонов | [GitHub |https://github.com/nette/latte] [API |https://api.nette.org/latte/] | **Mail**:[mail:] | Отправка электронной почты | [GitHub |https://github.com/nette/mail] [API |https://api.nette.org/mail/] -| **Neon**:[neon:] | Загружает и сбрасывает формат [NEON |https://ne-on.org] | [GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] +| **Neon**:[neon:] | Чтение и запись формата [NEON |https://ne-on.org] | [GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] | **Php Generator**:[php-generator:] | Генератор PHP-кода | [GitHub |https://github.com/nette/php-generator] [API |https://api.nette.org/php-generator/] -| **Robot Loader**:[robot-loader:] | Самая удобная автозагрузка | [GitHub |https://github.com/nette/robot-loader] [API |https://api.nette.org/robot-loader/] +| **Robot Loader**:[robot-loader:] | Самый удобный автозагрузчик | [GitHub |https://github.com/nette/robot-loader] [API |https://api.nette.org/robot-loader/] | **Routing**:[application:routing] | Маршрутизация | [GitHub |https://github.com/nette/routing] [API |https://api.nette.org/routing/] | **Safe Stream**:[safe-stream:] | Безопасные атомарные операции с файлами | [GitHub |https://github.com/nette/safe-stream] [API |https://api.nette.org/safe-stream/] -| **Security**:[security:authentication] | Обеспечивает систему контроля доступа | [GitHub |https://github.com/nette/security] [API |https://api.nette.org/security/] -| **Schema**:[schema:] | Валидация данных пользователя | [GitHub |https://github.com/nette/schema] [API |https://api.nette.org/schema/] -| **Tester**:[tester:] | Удобное модульное тестирование в PHP | [GitHub |https://github.com/nette/tester] [API |https://api.nette.org/tester/] +| **Schema**:[schema:] | Валидация пользовательских данных | [GitHub |https://github.com/nette/schema] [API |https://api.nette.org/schema/] +| **Security**:[security:authentication] | Управление правами доступа | [GitHub |https://github.com/nette/security] [API |https://api.nette.org/security/] +| **Tester**:[tester:] | Удобные модульные тесты в PHP | [GitHub |https://github.com/nette/tester] [API |https://api.nette.org/tester/] | **Tracy**:[tracy:] | Инструмент отладки, который вы полюбите ♥ | [GitHub |https://github.com/nette/tracy] [API |https://api.nette.org/tracy/] -| **Utils**:[utils:] | Утилиты и основные классы | [GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] +| **Utils**:[utils:] | Основные классы и инструменты | [GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] {{leftbar: @menu-common}} {{toc: no}} diff --git a/www/sl/10-reasons-why-nette.texy b/www/sl/10-reasons-why-nette.texy new file mode 100644 index 0000000000..f461c9b278 --- /dev/null +++ b/www/sl/10-reasons-why-nette.texy @@ -0,0 +1,93 @@ +7 razlogov, zakaj uporabljati Nette +*********************************** + +
                                                                                                                            + +Predstavljajte si PHP ogrodje, ki vam omogoča, da se osredotočite na tisto, kar najraje počnete. Vodi vas k pisanju čiste kode. Sam skrbi za varnost. Nehajte sanjati in spoznajte Nette. Odpravite se na pot, ki vam bo odprla vrata do novih razvojnih možnosti. Povedali si bomo: + +- kako ustvarjati spletna mesta z največjim udobjem +- kako pisati elegantno kodo +- kaj pomeni modrost »manj kode = večja varnost« +- kako graditi splet kot gradbeni komplet +- in kako postati del uspešne skupnosti + +
                                                                                                                            + +Nette prinaša udobje in učinkovitost v svet spletnih razvijalcev zahvaljujoč inovativnim orodjem in tehnikam. Katere so ključne lastnosti, ki delajo Nette edinstven in nepogrešljiv element razvijalskega kompleta? Poglejmo si jih! + + +#1 Nette vas razvaja +-------------------- + +Ko se je pred dvajsetimi leti začelo rojevati ogrodje Nette, se je vse vrtelo okoli enega cilja: Kako ustvarjati spletna mesta čim bolj udobno? Kako čim bolj olajšati delo programerjem? Kako narediti ustvarjanje spletnih mest seksi? + +Nette je s tem pristopom nagovorilo številne programerje in hitro pridobilo popularnost. Takrat smo tej filozofiji rekli Netteway, danes pa zanjo obstaja izraz, *Developer Experience* (DX). Nette je ogrodje, ki ima DX v svoji DNK. Razliko boste občutili v tisoč in eni stvari – od popolnih malenkosti do bistvenih inovacij. Pustite se razvajati ogrodju. + + +#2 Nette vas vodi k čisti kodi +------------------------------ + +Želite pisati čisto kodo? Imeti pravilno zasnovane aplikacije? Kdo si ne bi želel! In prav tu se začne vloga ogrodja. Če sam ne daje zgleda, ni mogoče ustvariti odlično zasnovane aplikacije. + +Nette daje zgled. Je mentor, ki uči dobre navade in pisanje kode po preverjenih metodologijah. Kot pionir in evangelist dependency injection ponuja kakovostno osnovo za vzdržljive, razširljive in lahko berljive aplikacije. Nette je zasnovan tako, da je razumljiv začetnikom, hkrati pa ponuja zadostno globino za izkušene razvijalce. + +Skupnost okoli Nette je vzgojila vrsto osebnosti, ki danes stojijo za uspešnimi in pomembnimi projekti. Za tisoče programerjev je Nette postal mentor na njihovi poti k profesionalni rasti. Pridružite se in odkrijte, kako bo Nette pozitivno vplival na kakovost vaše kode in aplikacij. + + +#3 Zanesljiv varuh vaših aplikacij +---------------------------------- + +Nette ščiti vaše aplikacije. Skozi leta si je pridobil sloves orodja, ki varnost jemlje izjemno resno. Zagotavlja domiselno zaščito pred ranljivostmi. Vedno skrbi za to, da programerjem olajša delo, vendar nikoli na račun varnosti. + +Njegov moto 'manj kode = dovolj varnosti' pomeni, da se posamezni elementi varno obnašajo že v osnovi. Ni treba aktivirati varnostnih elementov z dodajanjem dodatne kode. Torej ne morete na to pozabiti ali se bati, da ste kaj spregledali. Programerji pogosto niti ne vedo, koliko varnostnih stvari Nette naredi zanje, in so presenečeni, ko za to izvedo. + +Predstavljamo vam ogrodje, ki vas vodi k čisti kodi in hkrati bdi nad varnostjo vaših aplikacij. Nette je zanesljiv partner, ki vam omogoča, da se osredotočite na ustvarjanje odličnih spletnih aplikacij z mirno vestjo. + + +#4 Gradite splet kot gradbeni komplet +------------------------------------- + +V Nette gradite strani iz ponovno uporabljivih UI komponent. Spominja na razvoj namiznih aplikacij, in Nette je ta pristop uspešno prenesel na splet. Potrebujete v administraciji datagrid? Samo poiščite ga na trgu odprtokodnih komponent, namestite in enostavno vstavite na stran. Poleg tega lahko ustvarjate lastne komponente za ponavljajoče se elemente na straneh, s čimer odpravite podvajanje in izboljšate organizacijo kode. + +Ta edinstvena lastnost ločuje Nette od vseh drugih pomembnih igralcev na trgu. Omogoča vam učinkovito ustvarjanje in vzdrževanje spletnih aplikacij. Z Nette postane delo z UI gladka in prijetna izkušnja. + + +#5 Fleksibilen nabor paketov +---------------------------- + +Nette je nabor [samostojno uporabnih paketov |www:packages]. Mednje spadajo zasvojljivo [orodje za razhroščevanje Tracy |tracy:], [sistem predlog nove generacije Latte |latte:], [odličen Dependency Injection vsebnik |dependency-injection:], [obrazci |forms:] in mnogi drugi. Vsak paket ima berljivo podrobno dokumentacijo in se nahaja v ločenem repozitoriju na GitHubu. Pakete lahko uporabljate samostojno ali jih kombinirate z drugimi orodji in tehnologijami, ki jih že uporabljate. Na primer, Latte lahko implementirate v WordPress ali Slim Framework, DI vsebnik je lahko jedro podjetniškega ogrodja, Tracy pa bo vizualiziral sporočila o napakah. + +Ali pa lahko Nette uporabite kot celoto, kot ogrodje, in v njem ustvarite celotno spletno aplikacijo. Ne glede na to, ali razvijate majhen osebni projekt ali robustno poslovno aplikacijo, bo Nette vaš zanesljiv partner. + + +#6 Stabilnost & inovacije +------------------------- + +Nette je zrelo in preverjeno ogrodje z dolgoletno zgodovino. Kljub temu ostaja agilen in prožen zahvaljujoč pametni zasnovi. Uporabniki cenijo, da gre za majhno in prožno ogrodje, ne za moloha. + +Vedno je vnaprej pripravljen na nove različice PHP in upošteva najnovejše inovacije na področju razvoja spletnih aplikacij. To je ključno za zmanjšanje tehnološkega dolga. + +Nette je torej kombinacija dolgoletne stabilnosti in inovacij, ki vam omogoča, da se zanesete na preverjeno rešitev, medtem ko boste lahko uporabljali najnovejše tehnologije in trende. Z Nette ste lahko prepričani, da bo vaš projekt zgrajen na temeljih, ki nenehno sledijo hitro razvijajočemu se svetu spletnega razvoja. + + +#7 Bodite v najboljši družbi +---------------------------- + +Nette Framework je priljubljen med profesionalci. Nanj se zanašajo [pomembna podjetja |https://builtwith.nette.org], kot so O2, BOSCH, ESET, Zásilkovna, DHL, SUPRAPHON in stotine drugih. V več anketah je zmagal kot najbolj priljubljeno in najpogosteje uporabljeno ogrodje na Češkem. + +Začnite z Nette in odprle se vam bodo nove zaposlitvene priložnosti. Pridobili boste ne le zmogljivo orodje, ampak tudi najbolj aktivno skupnost na Češkem, ki vas je pripravljena podpreti na vaši poti k profesionalni rasti. Redno se srečujejo na [Posobotah |www.posobota.cz], dogodkih, kjer si izmenjujejo izkušnje. Pridite se srečat z nami! + + +Odkrijte Nette +-------------- + +Pridružite se tisočim zadovoljnim razvijalcem, ki so že odkrili prednosti Nette, in začnite pisati čistejšo, varnejšo in učinkovitejšo kodo s tem edinstvenim ogrodjem. + +Ustvarite svojo prvo aplikacijo [po navodilih |quickstart:], korak za korakom. Vedno vam bo na voljo [obsežna dokumentacija |nette:] in praktičen [pregled API |https://api.nette.org]. Obiščite naš [blog, poln nasvetov |https://blog.nette.org] in zbirko [dodatkov in komponent |https://componette.org], ki širijo zmožnosti Nette. + +Imate vprašanja? Obrnite se na stran s [pogostimi vprašanji|nette:troubleshooting] ali razpravljajte na [češkem forumu|https://forum.nette.org/cs/]. Imate raje osebno usposabljanje? Ponujamo [usposabljanje za Nette Framework |https://www.skoleniphp.cz/skoleni-nette-vyvoj-webovych-aplikaci] z [odličnimi odzivi |https://www.skoleniphp.cz/ohlasy]. + +**Spoznajte ogrodje, ki vas bo razvajalo, vodilo in navdihovalo.** + + +{{leftbar: @menu-common}} diff --git a/www/sl/@home.texy b/www/sl/@home.texy index c31f867d9b..c50b71525f 100644 --- a/www/sl/@home.texy +++ b/www/sl/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Nette - Udoben in varen spletni razvoj v PHP}} -{{description: Nette je družina zrelih in samostojnih komponent za PHP. Ste pripravljeni, da se navdušite? Skupaj ustvarjajo ogrodje, ki je bilo ocenjeno kot tretje najbolj priljubljeno na svetu. Naša filozofija se osredotoča na produktivnost, najboljše prakse in varnost.}} +{{maintitle:Nette – Udoben in varen razvoj spletnih aplikacij v PHP}} +{{description: Nette je družina naprednih in samostojno uporabnih komponent za PHP. Naj vas navdušijo. Skupaj tvorijo ogrodje, ocenjeno kot 3. najbolj priljubljeno na svetu. Filozofija Nette daje izjemen poudarek na produktivnost, najboljše prakse in varnost.}} diff --git a/www/sl/@menu-common.texy b/www/sl/@menu-common.texy index b661fcc72e..a871acd113 100644 --- a/www/sl/@menu-common.texy +++ b/www/sl/@menu-common.texy @@ -1,20 +1,22 @@ -Uvod -**** +Spoznavanje +*********** - [Zakaj uporabljati Nette? |www:10-reasons-why-nette] - [Namestitev |nette:installation] -- [Ustvarite svojo prvo aplikacijo! |quickstart:] +- [Napišimo prvo aplikacijo! |quickstart:] Splošne teme ************ - [Seznam paketov |www:packages] -- [Vzdrževanje in PHP |www:maintenance] -- [Opombe k izdaji |https://nette.org/releases] -- [Vodnik za nadgradnjo |migrations:en] -- [nette:Odpravljanje težav |nette:Troubleshooting] +- [Vzdrževanje in različice PHP |www:maintenance] +- [Opombe ob izdaji |https://nette.org/releases] +- [Prehod na novejše različice|migrations:en] +- [Odpravljanje težav |nette:troubleshooting] - [Kdo ustvarja Nette |https://nette.org/contributors] -- [Zgodovina družbe Nette |history] -- [Vključite se |contributing:] -- [Razvoj sponzorjev |https://nette.org/en/donate] -- [Sklic na API |https://api.nette.org] -- [Najboljše prakse |best-practices:] +- [Zgodovina Nette |history] +- [Pridružite se |contributing:] +- [Podprite razvoj |https://nette.org/cs/donate] +- [API reference |https://api.nette.org/] +- [Navodila in postopki |best-practices:] + +- [Varnost na prvem mestu |nette:vulnerability-protection] diff --git a/www/sl/donate.texy b/www/sl/donate.texy new file mode 100644 index 0000000000..4501e3a00b --- /dev/null +++ b/www/sl/donate.texy @@ -0,0 +1,20 @@ +Podprite razvoj Nette +********************* + +
                                                                                                                            +Vsakdo, ki gradi na Nette, ima interes, da se ogrodje aktivno razvija. Da podpira nove različice PHP. Da se popravljajo napake. Da prinaša nove funkcije, ki olajšajo delo ali prihranijo čas in denar. Da ima ogrodje odlično dokumentacijo in da okoli njega obstaja uporabna vsebina, bodisi v obliki člankov, navodil ali videoposnetkov. + +Številni deli Nette predstavljajo svetovni vrh in želimo, da tako ostane. + +Brez ustreznega financiranja nič od tega ni mogoče zagotoviti. Da pa bi se lahko zanesli, da bodo izšle naslednje različice, je dovolj malo: da ga vsak mesec podprete z majhnim finančnim zneskom. + +Pridružite se in postanite partner Nette! + +Tako boste zagotovili zdravo delovanje projekta, na katerega se zanašate. Hkrati pa **boste prejeli celo vrsto ekskluzivnih ugodnosti** (glejte *Ravni partnerstva* v desnem stolpcu). Dobili boste dostop do bonus vsebine. Do tehnične podpore. Povečali boste prioriteto reševanja vaših težav (issues). Prav tako boste povečali prepoznavnost svojega podjetja in pritegnili razvijalce. + +Kako vas bomo naredili bolj prepoznavne? Na primer z objavo vašega logotipa na tej spletni strani (tj. na tej strani, na domači strani, v dokumentaciji, na forumu in na [posebni strani |https://nette.org/partner/vitalita], na katero lahko povežete). Imeli boste možnost objavljati [ponudbe za delo |https://forum.nette.org/cs/f30-prace-a-zakazky], oglaševati na forumu ([primer |https://forum.nette.org/cs/30798-problem-s-cizim-klicem-pri-mazani#p198093]) ali v dokumentaciji ([primer |https://doc.nette.org/cs/application/components#toc-flash-zpravy]), torej na mestih z absolutno najboljšim dosegom do skupine Nette razvijalcev. + +Partnerjem izdajamo račune, da lahko podporo vključijo med stroške, bodisi mesečno, četrtletno, polletno ali letno. + +{{include: buttons}} +
                                                                                                                            diff --git a/www/sl/history.texy b/www/sl/history.texy index 6b8b4ff687..0c0278965a 100644 --- a/www/sl/history.texy +++ b/www/sl/history.texy @@ -1,36 +1,35 @@ -Zgodovina mesta Nette -********************* +Zgodovina Nette +*************** .[perex] -Začetki Nette segajo v leto 2004, ko je njen avtor David Grudl začel iskati primerno ogrodje za pisanje aplikacij, saj čisti PHP ni več zadoščal. Nobena od takrat razpoložljivih rešitev mu ni ustrezala, zato je postopoma začel oblikovati značilnosti novega ogrodja, ki je pozneje dobilo ime Nette. +Začetki Nette segajo v leto 2004, ko je njegov avtor David Grudl začel iskati primerno ogrodje, v katerem bi lahko pisal aplikacije, saj čisti PHP za to ni več zadostoval. Nobena od takrat dostopnih rešitev mu ni ustrezala, zato je postopoma začel orisovati značilnosti novega ogrodja, ki je kasneje dobilo ime Nette. -Takrat še ni bilo sedanjih ogrodij, kot so Symfony, Laravel ali Ruby on Rails. V svetu Jave je bil standard JSF (JavaServer Faces), v konkurenčnem svetu .NET pa je prevladovalo ogrodje ASP.NET Webforms. Oboje je omogočalo izdelavo strani z uporabo komponent uporabniškega vmesnika za večkratno uporabo. David je menil, da so njune metode abstrakcije in poskusi ustvarjanja brezstavnosti prek brezstavnega protokola HTTP z uporabo sej ali povratnih sporočil pomanjkljivi in v osnovi pokvarjeni. Uporabnikom in iskalnikom sta povzročala številne težave. Če ste na primer shranili povezavo, ste bili presenečeni, ko ste kasneje pod njo našli drugačno vsebino. +Takrat še niso obstajala sodobna ogrodja, kot so Symfony, Laravel ali Ruby on Rails. V svetu Jave je bil standard ogrodje JSF (JavaServer Faces), v konkurenčnem .NET pa ASP.NET Webforms. Oba sta omogočala gradnjo strani z uporabo ponovno uporabljivih UI komponent. Njihove načine abstrakcije in poskuse ustvarjanja stanja nad brezstanjnim protokolom HTTP s pomočjo sej (session) ali t.i. postbacka je David smatral za napačne in v osnovi nedelujoče. Povzročali so številne težave uporabnikom in iskalnikom. Na primer, če ste shranili povezavo, ste kasneje pod njo presenečeno našli drugo vsebino. -Možnost sestavljanja strani iz komponent uporabniškega vmesnika za večkratno uporabo je navdušila Davida, ki jo je dobro poznal iz Delphija, takrat priljubljenega orodja za izdelavo namiznih aplikacij. Všeč so mu bile tržnice z odprtokodnimi komponentami za Delphi. Zato je poskušal rešiti vprašanje, kako ustvariti ogrodje komponent, ki bi delovalo v popolni harmoniji z brezstavnim protokolom HTTP. Iskal je koncept, ki bi bil prijazen do uporabnikov, SEO in razvijalcev. Tako se je rodil Nette. +Sama možnost sestavljanja strani iz ponovno uporabljivih UI komponent je Davida fascinirala, dobro jo je poznal iz Delphi, takrat priljubljenega orodja za ustvarjanje namiznih aplikacij. Všeč so mu bili trgi z odprtokodnimi komponentami za Delphi. Zato je poskušal rešiti vprašanje, kako ustvariti komponentno ogrodje, ki bi nasprotno delovalo v popolnem sozvočju z brezstanjnim HTTP. Iskal je koncept, ki bi bil prijazen do uporabnikov, SEO in razvijalcev. In tako se je začel rojevati Nette. .[note] -Ime Nette je nastalo po naključju v kopalnici, ko je avtor opazil stekleničko gela za britje Gillette, obrnjeno tako, da je bilo videti le *llette*. +Ime Nette je nastalo po naključju v kopalnici, ko je avtor zagledal posodo z gelom za britje Gillette, obrnjeno tako, da se je videlo samo *llette*. -Sledilo je na tisoče ur raziskovanja, razmišljanja in prepisovanja. V zaprašeni garaži v vasi nekje pri Brnu so nastajali prvi obrisi prihodnjega okvira. Osnova arhitekture je bil vzorec MVC, ki ga je takrat uporabljalo danes pozabljeno ogrodje PHP Mojavi, pozneje pa ga je populariziral hype okoli Ruby on Rails. Eden od virov navdiha je bilo celo nikoli objavljeno ogrodje phpBase Honze Tichýja. +Sledile so tisoče ur raziskovanja, razmišljanja in prepisovanja. V prašni garaži v vasi nekje za Brnom so nastajali prvi obrisi prihodnjega ogrodja. Osnova arhitekture je postal vzorec MVC, ki ga je takrat uporabljalo danes že pozabljeno PHP ogrodje Mojavi in je kasneje postal popularen zaradi pompa okoli Ruby on Rails. Eden od virov navdiha je bilo celo nikoli objavljeno ogrodje phpBase Honze Tichýja. -Na avtorjevem blogu so se začeli pojavljati članki o prihajajoči Nette. V šali je bilo rečeno, da gre za vaporware. Nato pa je David oktobra 2007 na praški konferenci PHP Seminar javno predstavil Nette. Mimogrede, ta konferenca se je leto pozneje razvila v WebExpo, pozneje eno največjih konferenc IT v Evropi. Že takrat je Nette ponosno predstavil številne izvirne koncepte, kot so omenjeni model komponent, dvosmerni usmerjevalnik, poseben način povezovanja med predavatelji itd. Imel je obrazce, avtentikacijo, predpomnjenje itd. Vse to se v Nette še danes uporablja v prvotnem konceptu. +Na avtorjevem blogu so se začeli pojavljati članki o prihajajočem Nette. Šalili so se, da gre za vaporware. Potem pa je oktobra 2007 na praški konferenci PHP Seminar David javno predstavil Nette. Mimogrede, iz te konference se je leto kasneje razvil WebExpo, kasneje ena največjih IT konferenc v Evropi. Že takrat se je Nette ponašal s številnimi izvirnimi koncepti, kot so omenjeni komponentni model, dvosmerni usmerjevalnik (router), specifičen način povezovanja med presenterji itd. Imel je obrazce, rešeno avtentikacijo, predpomnjenje itd. Vse se v Nette uporablja v prvotnem konceptu še danes. .[note] -Nette uporablja *presenter* namesto *controller*, ker naj bi bilo v kodi preveč besed, ki se začenjajo z *con* (controller, front controller, control, config, container, ...). +V Nette se namesto izraza *controller* uporablja *presenter*, ker naj bi bilo v kodi preveč besed, ki se začnejo na *con* (controller, front controller, control, config, container, ...) -Konec leta 2007 je David Grudl objavil kodo in izšla je različica Nette 0.7. Okoli nje se je oblikovala navdušena skupnost programerjev, ki se je začela vsak mesec srečevati na dogodku Posobota. V skupnosti je bilo veliko današnjih velikanov, kot je Ondrej Mirtes, avtor odličnega orodja PHPStan. Razvoj Nette je napredoval in v naslednjih dveh letih sta bili izdani različici 0.8 in 0.9, ki sta postavili temelje za skoraj vse današnje dele ogrodja. Vključno z utrinki AJAX, ki so bili pred Hotwire za Ruby on Rails ali Symfony UX Turbo za 14 let. +Konec leta 2007 je David Grudl objavil tudi kodo in tako je luč sveta ugledala različica Nette 0.7. Ogrodje je takoj pritegnilo ogromno pozornosti. Okoli njega se je oblikovala navdušena skupnost programerjev, ki se je začela vsak mesec srečevati na dogodku Posobota. V skupnosti so bile številne današnje osebnosti, na primer Ondřej Mirtes, avtor odličnega orodja PHPStan. Razvoj Nette je hitro napredoval in v naslednjih dveh letih sta izšli različici 0.8 in 0.9, kjer so bili postavljeni temelji skoraj vseh današnjih delov ogrodja. Vključno z AJAX odrezki (snippets), ki so za 14 let prehiteli Hotwire za Ruby on Rails ali Symfony UX Turbo. -Vendar je v Nette takrat manjkala ena ključna stvar. Zabojnik za vbrizgavanje odvisnosti (Dependecy injection container, DIC). Nette je uporabljal *iskalnik storitev* in namen je bil preiti na vbrizgavanje odvisnosti. Toda kako načrtovati takšno stvar? David Grudl, ki takrat ni imel izkušenj z DI, je šel na kosilo z Vaskom Purchartom, ki je DI uporabljal že približno pol leta. Skupaj sta razpravljala o tej temi in David je začel delati na Nette DI, knjižnici, ki je popolnoma spremenila način razmišljanja o načrtovanju aplikacij. Posoda DI je postala eden najuspešnejših delov ogrodja. Iz njega sta nastala dva stranska dela: format Neon in knjižnica Schema. +Ena bistvena stvar pa je v takratnem Nette manjkala. Dependency injection vsebnik (DIC). Nette je uporabljal t.i. *service locator* in namen je bil preiti prav na dependency injection. Kako pa takšno stvar zasnovati? David Grudl, ki takrat ni imel izkušenj z DI, je šel na kosilo z Vaškem Purchartom, ki je DI uporabljal približno pol leta. Skupaj sta razpravljala o temi in David je začel delati na Nette DI, knjižnici, ki je popolnoma spremenila način razmišljanja o načrtovanju aplikacij. DI vsebnik je postal eden najuspešnejših delov ogrodja. In kasneje je povzročil nastanek dveh spin-offov: formata Neon in knjižnice Schema. .[note] -Prehod na vbrizgavanje odvisnosti je zahteval veliko časa, zato smo na novo različico Nette čakali nekaj let. Ko je končno izšla, je bila oštevilčena z 2. Različica Nette 1 torej ne obstaja. +Premik na dependency injection je zahteval precej časa in na novo različico Nette se je čakalo nekaj let. Zato je, ko je končno izšla, nosila kar številko 2. Različica Nette 1 torej ne obstaja. -Nette je svojo sodobno zgodovino začel leta 2012 z različico 2.0. Ta je prinesla tudi zbirko podatkov Nette, ki je vključevala izjemno priročno orodje za delo s podatkovnimi zbirkami, ki se zdaj imenuje Raziskovalec. To knjižnico je prvotno programiral Jakub Vrána, sosed Davida Grudla in avtor priljubljenega orodja Adminer. Njen nadaljnji razvoj je nato za tri leta prevzel Jan Škrášek. +Nette je leta 2012 z različico 2.0 začel svojo moderno zgodovino. Prinesel je tudi Nette Database, katerega del je bilo tudi izjemno priročno orodje za delo s podatkovno bazo, danes imenovano Explorer. To knjižnico je prvotno programiral Jakub Vrána, sosed Davida Grudla in avtor priljubljenega orodja Adminer. Njenega nadaljnjega razvoja se je nato za tri leta lotil Jan Škrášek. -Leta 2014 je bila izdana različica Nette 2.1, ki ji je kmalu sledila različica Nette 2.2. Kako je to mogoče? Različica 2.2 je bila enaka različici 2.1, le da je bila razdeljena na dvajset ločenih paketov. Orodje Composer se je uveljavilo v svetu PHP in spremenilo način razmišljanja o ustvarjanju knjižnic. Nette ni bil več monolit in je razpadel na manjše neodvisne dele. Vsak s svojo shrambo, sledilnikom težav ter lastnim potekom razvoja in različic. Na ta način Nette ni bilo treba preživeti absurdov, ki so pogosti pri monolitnih ogrodjih, kjer izide nova različica paketa, čeprav se ni nič spremenilo. Dejanska razdelitev skladišč Git je zahtevala več tednov priprav in na stotine ur strojnega časa. - -Nette je zasedel tudi neverjetno tretje mesto v svetovni anketi za najboljše ogrodje PHP, ki jo je organizirala revija Sitepoint. +Leta 2014 je izšel Nette 2.1, kmalu zatem pa Nette 2.2. Kako je to mogoče? Različica 2.2 je bila enaka kot različica 2.1, le razdeljena na dvajset ločenih paketov. V svetu PHP se je uveljavilo orodje Composer in spremenilo način pojmovanja ustvarjanja knjižnic. Nette tako ni bil več monolit in se je razdelil na manjše neodvisne dele. Vsak s svojim repozitorijem, sledilnikom težav (issue tracker) in lastnim tempom razvoja ter različicami. V Nette tako ni treba prihajati do absurdov, običajnih v monolitnih ogrodjih, ko izide nova različica paketa, čeprav se v njem ni nič spremenilo. Sama razdelitev Git repozitorijev je zahtevala več tednov priprav in stotine ur strojnega časa. +Nette se je uvrstil tudi na odlično 3. mesto v svetovni anketi o najboljšem PHP ogrodju, ki jo je organizirala revija Sitepoint. {{toc:no}} diff --git a/www/sl/license.texy b/www/sl/license.texy new file mode 100644 index 0000000000..861c0c90a3 --- /dev/null +++ b/www/sl/license.texy @@ -0,0 +1,44 @@ +Licenčna politika +***************** + +Nette Framework se distribuira kot prosta programska oprema, da jo lahko uporablja kdorkoli. Izberete lahko, ali vam bolj ustreza licenca [New BSD |#New BSD License] ali [#GNU General Public License (GPL)] v različici 2 ali 3. + +Licenca BSD je priporočljiva za večino projektov, saj jo je enostavno razumeti in ne postavlja skoraj nobenih omejitev glede tega, kaj lahko počnete z ogrodjem. Nette lahko uporabljate tudi v komercialnih projektih. Če pa GPL bolj ustreza vašemu projektu, izberite njo. Pri tem ni treba nikogar obveščati, kako ste se odločili. Vedno ohranite prvotne avtorske pravice. + +Vedeti morate, da je ime 'Nette Framework' zaščitena blagovna znamka in njena uporaba ima določene omejitve. Zato ne uporabljajte 'Nette' v imenu svojega projekta ali domene najvišje ravni, raje izberite ime, ki bo stalo na lastnih temeljih. Če je vaš projekt dober, ne bo trajalo dolgo, da si ustvari svoj ugled. + +Če boste z Nette Framework zadovoljni, in verjamemo, da boste, ga lahko [podprete s prispevkom |donate]. Hvala. + + +New BSD License +--------------- + +Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com) All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of "Nette Framework" nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +GNU General Public License (GPL) +-------------------------------- + +Besedila licenc GPL so zelo dolga, zato so tukaj navedene povezave do njih: + +- GPL version 2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +- GPL version 3: https://www.gnu.org/licenses/gpl-3.0.html + + +{{toc:yes}} +{{priority: -2}} diff --git a/www/sl/maintenance.texy b/www/sl/maintenance.texy index 25a361743f..d6f6c89a0d 100644 --- a/www/sl/maintenance.texy +++ b/www/sl/maintenance.texy @@ -1,27 +1,27 @@ -Vzdrževanje in različice PHP -**************************** +Vzdrževanje in združljivost s PHP +********************************* .[perex] -Nette je ogrodje z izjemno dolgim obdobjem podpore za posamezne izdaje. Vsaka veja je izdaja LTS (Long-Term Support Release) z najmanj dvema letoma podpore. +Nette je ogrodje z izjemno dolgo dobo podpore za posamezne izdaje. Vsaka veja je LTS (Long-Term Support Release) s podporo najmanj 2 leti. -Vsaka različica se aktivno vzdržuje eno leto (ali dlje) od prve stabilne izdaje. -Kritične in varnostne napake se odpravljajo dve leti. +Vsaka različica se aktivno vzdržuje eno leto (ali dlje) od začetne stabilne izdaje. Kritične in varnostne napake se popravljajo dve leti. -Koledar izdaj (časovni načrt) .[#toc-release-calendar-roadmap] -============================================================== +Koledar izdaj Nette +=================== {{include: doc-roadmap-table}} -Združljivost s PHP .[#toc-php-compatibility] -============================================ +Združljivost s PHP +================== + +Združljivost vedno velja za najnovejšo izdajo vsake serije. -Združljivost vedno velja za najnovejšo izdajo v vsaki seriji. {{include: doc-roadmap-versions}} {{leftbar: @menu-common}} {{toc: no}} -{{description: Nette izdaje, časovni načrt, vzdrževanje in tabele združljivosti PHP}} +{{description: Izdaje Nette, načrt, tabele vzdrževanja in združljivost s PHP}} diff --git a/www/sl/packages.texy b/www/sl/packages.texy index b3b5a8965b..81a32cb6c7 100644 --- a/www/sl/packages.texy +++ b/www/sl/packages.texy @@ -1,26 +1,27 @@ -Seznam paketov -************** +Seznam paketov Nette +******************** -| **Application**:[application:how-it-works] | Jedro spletne aplikacije | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] -| **Bootstrap**:[bootstrap:] | Bootstrap vaše aplikacije | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] -| **Caching**:[caching:] | Predpomnilniška plast z naborom shramb | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] -| **Component Model**:[component-model:] | Osnova za komponentne sisteme | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] -| **DI**:[dependency-injection:] | Dependency Injection Container | [GitHub |https://github.com/nette/di] [API |https://api.nette.org/di/] -| **Database**:[database:] | Podatkovna baza | [GitHub |https://github.com/nette/database] [API |https://api.nette.org/database/] -| **Forms**:[forms:] | Veliko olajša varne spletne obrazce | [GitHub |https://github.com/nette/forms] [API |https://api.nette.org/forms/] -| **Http**:[http:] | Plast za zahteve in odzive HTTP | [GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] -| **Latte**:[latte:] | Neverjeten mehanizem za predloge | [GitHub |https://github.com/nette/latte] [API |https://api.nette.org/latte/] +| **Application**:[application:how-it-works] | Jedro spletnih aplikacij | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] +| **Assets**:[assets:] | Upravljanje statičnih datotek | [GitHub |https://github.com/nette/assets] [API |https://api.nette.org/assets/] +| **Bootstrap**:[bootstrap:] | Bootstrap spletne aplikacije | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] +| **Caching**:[caching:] | Plast predpomnjenja s shrambami | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] +| **Component Model**:[component-model:] | Osnova komponentnega sistema | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] +| **DI**:[dependency-injection:] | Dependency Injection vsebnik | [GitHub |https://github.com/nette/di] [API |https://api.nette.org/di/] +| **Database**:[database:] | Plast podatkovne baze | [GitHub |https://github.com/nette/database] [API |https://api.nette.org/database/] +| **Forms**:[forms:] | Udobni in varni spletni obrazci | [GitHub |https://github.com/nette/forms] [API |https://api.nette.org/forms/] +| **Http**:[http:] | Plast, ki inkapsulira HTTP zahtevo & odgovor | [GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] +| **Latte**:[latte:] | Odličen sistem predlog | [GitHub |https://github.com/nette/latte] [API |https://api.nette.org/latte/] | **Mail**:[mail:] | Pošiljanje e-pošte | [GitHub |https://github.com/nette/mail] [API |https://api.nette.org/mail/] -| **Neon**:[neon:] | Nalaganje in odlaganje [formata NEON|https://ne-on.org] | [GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] -| **Php Generator**:[php-generator:] | Generator kode PHP | [GitHub |https://github.com/nette/php-generator] [API |https://api.nette.org/php-generator/] -| **Robot Loader**:[robot-loader:] | Najbolj udobno samodejno nalaganje | [GitHub |https://github.com/nette/robot-loader] [API |https://api.nette.org/robot-loader/] -| **Routing**:[application:routing] | Routing | [GitHub |https://github.com/nette/routing] [API |https://api.nette.org/routing/] +| **Neon**:[neon:] | Branje in pisanje formata [NEON |https://ne-on.org] | [GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] +| **Php Generator**:[php-generator:] | Generator PHP kode | [GitHub |https://github.com/nette/php-generator] [API |https://api.nette.org/php-generator/] +| **Robot Loader**:[robot-loader:] | Najudobnejši autoloading | [GitHub |https://github.com/nette/robot-loader] [API |https://api.nette.org/robot-loader/] +| **Routing**:[application:routing] | Usmerjanje | [GitHub |https://github.com/nette/routing] [API |https://api.nette.org/routing/] | **Safe Stream**:[safe-stream:] | Varne atomske operacije z datotekami | [GitHub |https://github.com/nette/safe-stream] [API |https://api.nette.org/safe-stream/] -| **Security**:[security:authentication] | Zagotavlja sistem nadzora dostopa | [GitHub |https://github.com/nette/security] [API |https://api.nette.org/security/] -| **Schema**:[schema:] | Potrjevanje uporabniških podatkov | [GitHub |https://github.com/nette/schema] [API |https://api.nette.org/schema/] -| **Tester**:[tester:] | Prijetno testiranje enot v PHP | [GitHub |https://github.com/nette/tester] [API |https://api.nette.org/tester/] -| **Tracy**:[tracy:] | Orodje za odpravljanje napak, ki vam bo všeč ♥ | [GitHub |https://github.com/nette/tracy] [API |https://api.nette.org/tracy/] -| **Utils**:[utils:] | Utilities and Core Classes | [GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] +| **Schema**:[schema:] | Validacija uporabniških podatkov | [GitHub |https://github.com/nette/schema] [API |https://api.nette.org/schema/] +| **Security**:[security:authentication] | Upravljanje pravic dostopa | [GitHub |https://github.com/nette/security] [API |https://api.nette.org/security/] +| **Tester**:[tester:] | Sproščeni enotni testi v PHP | [GitHub |https://github.com/nette/tester] [API |https://api.nette.org/tester/] +| **Tracy**:[tracy:] | Orodje za razhroščevanje, ki ga boste vzljubili ♥ | [GitHub |https://github.com/nette/tracy] [API |https://api.nette.org/tracy/] +| **Utils**:[utils:] | Osnovni razredi in orodja | [GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] {{leftbar: @menu-common}} {{toc: no}} diff --git a/www/tr/10-reasons-why-nette.texy b/www/tr/10-reasons-why-nette.texy index f758766e15..be6ec5725d 100644 --- a/www/tr/10-reasons-why-nette.texy +++ b/www/tr/10-reasons-why-nette.texy @@ -1,53 +1,93 @@ -Neden Nette Kullanmalı? -*********************** +Nette Kullanmak İçin 7 Neden +****************************
                                                                                                                            -Tekrarlayan görevleri çözmeyi ve programlamayı sıkıcı ve verimsiz hale getiren ayrıntılarla dikkatinizi dağıtmayı bırakın. **Nette Framework daha etkili çalışmanızı, önemli olana odaklanmanızı sağlar ve bu süreçte kodunuzu daha okunabilir ve iyi yapılandırılmış hale getirir**. Bazı özellikler şunlardır: +En sevdiğiniz şeye odaklanmanızı sağlayan bir PHP framework'ü hayal edin. Sizi temiz kod yazmaya yönlendirir. Güvenliğe kendisi dikkat eder. Hayal kurmayı bırakın ve Nette ile tanışın. Size yeni geliştirme olanaklarının kapılarını açacak bir yolculuğa çıkın. Şunları konuşacağız: -- mükemmel bir şablonlama sistemi -- rakipsiz teşhis araçları -- olağanüstü etkili veritabanı katmanı -- Bilinen güvenlik açıklarına karşı kaya gibi sağlam koruma -- HTML5 ve AJAX desteği, SEO dostu -- iyi yazılmış belgeler ve aktif bir açık kaynak topluluğu -- En son PHP özelliklerinden yararlanan olgun ve temiz nesne yönelimli tasarım -- teşvik edilen ancak uygulanmayan en iyi uygulama çözümleri +- web sitelerini maksimum rahatlıkla nasıl oluşturacağınızı +- zarif kod nasıl yazacağınızı +- "daha az kod = daha fazla güvenlik" bilgeliğinin ne anlama geldiğini +- web'i bir yapı seti gibi nasıl inşa edeceğinizi +- ve başarılı bir topluluğun parçası nasıl olacağınızı
                                                                                                                            -Ve tamamen ücretsizdir. Denemeye değer olduğunu düşünüyoruz. +Nette, yenilikçi araçlar ve teknikler sayesinde web geliştiricileri dünyasına rahatlık ve verimlilik getiriyor. Nette'yi benzersiz ve geliştirici setinin vazgeçilmez bir unsuru yapan temel özellikler nelerdir? Hadi onlara bir göz atalım! -**Nette Framework, geliştirici olmanın yaratıcı kısmına odaklanmanızı sağlar**. Son derece kullanışlı, kolay ve kullanımı zevkli olacak şekilde tasarlanmıştır. Anlaşılır ancak etkili sözdizimi, son teknoloji hata ayıklayıcısı ve sektör lideri güvenlik özellikleri, e-ticaret siteleri, wiki'ler, bloglar, CMS veya hayal edebileceğiniz her şeyi her zamankinden daha hızlı ve daha iyi yazmanıza olanak tanır. +#1 Nette sizi şımartır +---------------------- -Nette Framework, T-Systems, GE Money, Mladá fronta, VLTAVA-LABE-PRESS, Internet Info, DHL, Logio, ESET veya Actum gibi [büyük şirketler tarafından kullanılmaktadır |https://builtwith.nette.org]. Şu anda Çek Cumhuriyeti'nin eski cumhurbaşkanı Václav Klaus'un web sitesini desteklemektedir. [Zdroják |https://www.zdrojak.cz/clanky/vysledky-technologie-na-ceskem-webu/] tarafından düzenlenen ankette, Çek Cumhuriyeti'nde en popüler ve en yaygın kullanılan çerçeve ödülüne layık görüldü. +Yirmi yıl önce Nette framework'ü doğmaya başladığında, her şey tek bir hedef etrafında dönüyordu: Web siteleri nasıl en rahat şekilde oluşturulur? Programcıların işi nasıl en keyifli hale getirilir? Web sitesi oluşturmayı nasıl seksi hale getirebiliriz? -Nette Framework'ü öğrenmek, ilginç iş teklifleri konusunda hiçbir sıkıntıyı garanti etmeyecektir. +Nette bu yaklaşımıyla birçok programcıya hitap etti ve hızla popülerlik kazandı. O zamanlar bu felsefeye Netteway diyorduk ve bugün bunun için bir terim var: *Developer Experience* (DX). Nette, DNA'sında DX olan bir framework'tür. Farkı bin bir şeyde hissedeceksiniz - en küçük ayrıntılardan temel yeniliklere kadar. Framework'ün sizi şımartmasına izin verin. -Gemiye binin .[#toc-get-on-board] ---------------------------------- +#2 Nette sizi temiz kod yazmaya yönlendirir +------------------------------------------- -Kılavuzu takip edin ve [ilk uygulamanızı |quickstart:] adım adım [oluşturun |quickstart:]. Kullanışlı bir [Programcı kılavuzu |@home] size her zaman yardımcı olmak için burada ve daha derine inmek istiyorsanız, tüm sınıfları ve yöntemleri gözden geçiren pratik [API belgelerine |https://api.nette.org/] göz atın. +Temiz kod yazmak mı istiyorsunuz? Doğru tasarlanmış uygulamalara sahip olmak mı? Kim istemez ki! İşte tam burada framework'ün rolü başlıyor. Kendisi örnek olmazsa, harika tasarlanmış bir uygulama oluşturmak mümkün değildir. -Takılırsanız, [sıkça sorulan sorulara |nette:troubleshooting] veya deneyimli kullanıcıların yardım etmeye istekli olduğu [resmi foruma |https://forum.nette.org/en/] göz atın. +Nette örnek oluyor. İyi alışkanlıklar öğreten ve kanıtlanmış metodolojilere göre kod yazmayı öğreten bir akıl hocasıdır. Bağımlılık enjeksiyonunun öncüsü ve savunucusu olarak, sürdürülebilir, genişletilebilir ve kolay okunabilir uygulamalar için kaliteli bir temel sunar. Nette, yeni başlayanlar için anlaşılır olacak şekilde tasarlanmıştır, ancak aynı zamanda deneyimli geliştiriciler için yeterli derinliği sunar. -Kendiniz öğrenecek kadar kararlı değil misiniz? Anlaşıldı - [Nette Framework eğitimi |https://www.skoleniphp.cz/skoleni-nette-vyvoj-webovych-aplikaci] (Çekçe) sunuyoruz. Eski katılımcıların geri bildirimlerini şu adreste bulabilirsiniz [skoleniphp.cz/ohlasy |https://www.skoleniphp.cz/ohlasy]. +Nette etrafındaki topluluk, bugün başarılı ve önemli projelerin arkasında duran birçok kişiliği yetiştirdi. Binlerce programcı için Nette, profesyonel gelişim yolculuklarında bir akıl hocası oldu. Katılın ve Nette'nin kodunuzun ve uygulamalarınızın kalitesini nasıl olumlu etkileyeceğini keşfedin. -Daha Fazla Bilgi .[#toc-more-information] +#3 Uygulamalarınızın güvenilir koruyucusu ----------------------------------------- -[İpuçlarıyla dolu |https://blog.nette.org] bir [blogumuz |https://blog.nette.org] ve uygulamalarınızda kullanabileceğiniz çeşitli [eklenti ve bileşenlerden |https://componette.org] oluşan bir koleksiyonumuz var. +Nette uygulamalarınızı korur. Yıllar içinde, güvenliği son derece ciddiye alan bir araç olarak ün kazandı. Güvenlik açıklarına karşı sofistike bir koruma sağlar. Her zaman programcıların işini kolaylaştırmaya özen gösterir, ancak asla güvenlik pahasına değil. -Buna ek olarak, Nette Framework etrafındaki topluluk her ay [Poslední sobota |https://www.posobota.cz] (Çekçe) adlı bir etkinlikte bir araya geliyor ve burada üyeler deneyimlerini paylaşıyor ve sadece iyi vakit geçiriyorlar. Gelin bir bira kapın! +"Daha az kod = yeterli güvenlik" sloganı, bireysel öğelerin zaten temel olarak güvenli davrandığı anlamına gelir. Ek kod yazarak güvenlik özelliklerini etkinleştirmenize gerek yoktur. Bu yüzden bunu unutamazsınız veya bir şeyi gözden kaçırdığınızdan korkamazsınız. Programcılar genellikle Nette'nin onlar için ne kadar çok güvenlik işi yaptığını bilmezler ve bunu öğrendiklerinde şaşırırlar. +Size temiz kod yazmaya yönlendiren ve aynı zamanda uygulamalarınızın güvenliğini gözeten bir framework sunuyoruz. Nette, gönül rahatlığıyla harika web uygulamaları oluşturmaya odaklanmanızı sağlayan güvenilir bir ortaktır. -Katılın! .[#toc-get-involved] ------------------------------ -Gelişime yapılacak her türlü katkı büyük takdir toplayacaktır. Ağızdan ağıza paylaşabilir, web sitenize bir [simge |www:en:logo] yerleştirebilir veya [geliştirmeyi |www:en:donate] finansal olarak [destekleyebilirsiniz |www:en:donate]. Teşekkür ederim ♥. +#4 Web'i bir yapı seti gibi inşa edin +------------------------------------- + +Nette'de sayfaları yeniden kullanılabilir UI bileşenlerinden oluşturursunuz. Bu, masaüstü uygulamaları geliştirmeyi andırır ve Nette bu yaklaşımı başarıyla web'e taşıdı. Yönetimde bir veri tablosuna mı ihtiyacınız var? Sadece açık kaynaklı bileşen pazarında bulun, kurun ve sayfaya kolayca ekleyin. Ayrıca, sayfalardaki tekrar eden öğeler için kendi bileşenlerinizi oluşturabilir, böylece tekrarları ortadan kaldırabilir ve kod organizasyonunu iyileştirebilirsiniz. + +Bu benzersiz özellik, Nette'yi piyasadaki diğer tüm önemli oyunculardan ayırır. Web uygulamalarını verimli bir şekilde oluşturmanıza ve sürdürmenize olanak tanır. Nette ile UI ile çalışmak sorunsuz ve keyifli bir deneyim haline gelir. + + +#5 Esnek paket seti +------------------- + +Nette, [bağımsız olarak kullanılabilen paketler |www:packages] setidir. Bunlar arasında bağımlılık yaratan [hata ayıklama aracı Tracy |tracy:], [yeni nesil şablonlama sistemi Latte |latte:], [mükemmel Bağımlılık Enjeksiyonu Konteyneri |dependency-injection:], [formlar |forms:] ve daha birçokları bulunur. Her paketin okunabilir ayrıntılı dokümantasyonu vardır ve GitHub'da ayrı bir depoda bulunur. Paketleri bağımsız olarak kullanabilir veya zaten kullandığınız diğer araçlar ve teknolojilerle birleştirebilirsiniz. Örneğin, Latte WordPress veya Slim Framework'e dağıtılabilir, DI konteyneri bir şirket framework'ünün çekirdeği olabilir ve Tracy hata mesajlarını görselleştirecektir. + +Veya Nette'yi bir bütün olarak, bir framework olarak kullanabilir ve içinde eksiksiz bir web uygulaması oluşturabilirsiniz. İster küçük bir kişisel proje ister sağlam bir kurumsal uygulama geliştiriyor olun, Nette güvenilir ortağınız olacaktır. + + +#6 İstikrar ve Yenilik +---------------------- + +Nette, uzun bir geçmişe sahip olgunlaşmış ve kanıtlanmış bir framework'tür. Buna rağmen, akıllı tasarımı sayesinde çevik ve esnek kalır. Kullanıcılar, hantal bir yapı değil, küçük ve esnek bir framework olmasını takdir ederler. + +Her zaman yeni PHP sürümlerine önceden hazırdır ve web uygulaması geliştirme alanındaki en son yenilikleri dikkate alır. Bu, teknolojik borcu en aza indirmek için çok önemlidir. + +Bu nedenle Nette, en son teknolojileri ve trendleri kullanmanıza izin verirken kanıtlanmış bir çözüme güvenmenizi sağlayan uzun süreli istikrar ve yeniliğin birleşimidir. Nette ile projenizin, web geliştirmenin hızla gelişen dünyasıyla sürekli olarak ayak uyduran temeller üzerine inşa edileceğinden emin olabilirsiniz. + + +#7 En iyi şirkette olun +----------------------- + +Nette Framework, profesyoneller arasında popülerdir. O2, BOSCH, ESET, Zásilkovna, DHL, SUPRAPHON gibi [önemli şirketler |https://builtwith.nette.org] ve yüzlercesi ona güveniyor. Çek Cumhuriyeti'nde yapılan çeşitli anketlerde en popüler ve en çok kullanılan framework olarak birinci seçildi. + +Nette ile başlayın ve yeni iş fırsatları size açılacaktır. Sadece güçlü bir araç değil, aynı zamanda profesyonel gelişim yolculuğunuzda sizi desteklemeye hazır Çek Cumhuriyeti'ndeki en aktif topluluğu da kazanacaksınız. Düzenli olarak deneyimlerini paylaştıkları etkinlikler olan [Posobota |www.posobota.cz]'da buluşuyorlar. Gelin ve bizimle tanışın! + + +Nette'yi Keşfedin +----------------- + +Nette'nin avantajlarını zaten keşfetmiş olan binlerce memnun geliştiriciye katılın ve bu benzersiz framework ile daha temiz, daha güvenli ve daha verimli kod yazmaya başlayın. + +[Talimatlara göre |quickstart:] ilk uygulamanızı adım adım oluşturun. [Kapsamlı dokümantasyon |nette:] ve pratik [API genel bakışı |https://api.nette.org] her zaman elinizin altında olacaktır. [İpuçlarıyla dolu blogumuzu |https://blog.nette.org] ve Nette'nin yeteneklerini genişleten [eklentiler ve bileşenler |https://componette.org] koleksiyonumuzu ziyaret edin. + +Sorularınız mı var? [Sıkça sorulan sorular|nette:troubleshooting] sayfasına başvurun veya [Çek forumunda|https://forum.nette.org/cs/] tartışın. Kişisel eğitimi mi tercih ediyorsunuz? [Mükemmel geri bildirimler |https://www.skoleniphp.cz/ohlasy] ile [Nette Framework eğitimi |https://www.skoleniphp.cz/skoleni-nette-vyvoj-webovych-aplikaci] sunuyoruz. + +**Sizi şımartacak, yönlendirecek ve ilham verecek framework ile tanışın.** {{leftbar: @menu-common}} diff --git a/www/tr/@home.texy b/www/tr/@home.texy index 9eaaac51ab..64ce946029 100644 --- a/www/tr/@home.texy +++ b/www/tr/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Nette - PHP'de Rahat ve Güvenli Web Geliştirme}} -{{description: Nette, PHP için olgun ve bağımsız bileşenlerden oluşan bir ailedir. Aşık olmaya hazır mısınız? Birlikte, dünyada en popüler 3. çerçeve olarak derecelendirilmiş bir çerçeve oluştururlar. Felsefemiz üretkenliğe, en iyi uygulamalara ve güvenliğe odaklanmaktır.}} +{{maintitle:Nette – PHP'de Rahat ve Güvenli Web Uygulaması Geliştirme}} +{{description: Nette, PHP için gelişmiş ve bağımsız olarak kullanılabilir bileşenlerden oluşan bir ailedir. Onlardan ilham alın. Birlikte, dünyanın en popüler 3. framework'ü olarak değerlendirilen bir framework oluştururlar. Nette felsefesi, üretkenliğe, en iyi uygulamalara ve güvenliğe olağanüstü bir vurgu yapar.}} diff --git a/www/tr/@menu-common.texy b/www/tr/@menu-common.texy index 881242c8a7..4375d1cd3e 100644 --- a/www/tr/@menu-common.texy +++ b/www/tr/@menu-common.texy @@ -1,20 +1,22 @@ -Giriş -***** -- [Neden Nette Kullanılmalı? |www:10-reasons-why-nette] +Tanıtım +******* +- [Neden Nette kullanmalı? |www:10-reasons-why-nette] - [Kurulum |nette:installation] -- [İlk Başvurunuzu Oluşturun! |quickstart:] +- [İlk uygulamamızı yazıyoruz! |quickstart:] Genel Konular ************* -- [Paketlerin Listesi |www:packages] -- [Bakım ve PHP |www:maintenance] +- [Paket listesi |www:packages] +- [Bakım ve PHP sürümleri |www:maintenance] - [Sürüm Notları |https://nette.org/releases] -- [Yükseltme Kılavuzu |migrations:en] -- [nette:Sorun Giderme |nette:Troubleshooting] -- [Nette'i Kim Yarattı |https://nette.org/contributors] -- [Nette'in Tarihçesi |history] -- [Katılın |contributing:] -- [Sponsor geliştirme |https://nette.org/en/donate] -- [API Referansı |https://api.nette.org] -- [En İyi Uygulamalar |best-practices:] +- [Daha yeni sürümlere geçiş|migrations:en] +- [Sorun Giderme |nette:troubleshooting] +- [Nette'yi kimler oluşturuyor |https://nette.org/contributors] +- [Nette Tarihçesi |history] +- [Katkıda Bulunun |contributing:] +- [Geliştirmeyi Destekleyin |https://nette.org/en/donate] +- [API Referansı |https://api.nette.org/] +- [Kılavuzlar ve yöntemler |best-practices:] + +- [Önce Güvenlik |nette:vulnerability-protection] diff --git a/www/tr/donate.texy b/www/tr/donate.texy new file mode 100644 index 0000000000..dad913d249 --- /dev/null +++ b/www/tr/donate.texy @@ -0,0 +1,20 @@ +Nette Gelişimini Destekleyin +**************************** + +
                                                                                                                            +Nette üzerine inşa eden herkes, framework'ün aktif olarak geliştirilmesiyle ilgilenir. Yeni PHP sürümlerini desteklemesi için. Hataların düzeltilmesi için. İşi kolaylaştıracak veya zaman ve para tasarrufu sağlayacak daha fazla yenilik getirmesi için. Framework'ün harika bir dokümantasyona sahip olması ve etrafında makaleler, kılavuzlar veya videolar şeklinde yararlı içeriklerin bulunması için. + +Nette'nin birçok parçası dünya standartlarındadır ve bunun böyle kalmasını istiyoruz. + +Yeterli finansman olmadan bunların hiçbiri sağlanamaz. Oysa sonraki sürümlerin çıkacağına güvenebilmeniz için oldukça az şey yeterlidir: onu her ay küçük bir finansal miktarla bile desteklemeniz. + +Hadi yapalım ve Nette ortağı olalım! + +Böylece güvendiğiniz projenin sağlıklı işleyişini sağlarsınız. Ve aynı zamanda **bir dizi özel avantaj elde edersiniz** (sağ sütundaki *Ortaklık Seviyeleri*'ne bakın). Bonus içeriğe erişim kazanırsınız. Teknik desteğe. Sorunlarınızın çözüm önceliğini artırırsınız. Ve ayrıca şirketinizi görünür kılar ve geliştiricileri kendinize çekersiniz. + +Sizi nasıl görünür kılacağız? Örneğin, logonuzu bu web sitesinde (yani bu sayfada, ana sayfada, dokümantasyonda, forumda ve yönlendirebileceğiniz [özel sayfada |https://nette.org/partner/vitalita]) listeleyerek. [İş ilanları |https://forum.nette.org/cs/f30-prace-a-zakazky] ekleme, forumda ([örnek |https://forum.nette.org/cs/30798-problem-s-cizim-klicem-pri-mazani#p198093]) veya dokümantasyonda ([örnek |https://doc.nette.org/cs/application/components#toc-flash-zpravy]) reklam verme olanağınız olacak, yani Nette geliştiricileri grubuna en iyi erişime sahip yerlerde. + +Ortaklara, desteği giderlerine dahil edebilmeleri için aylık, üç aylık, altı aylık veya yıllık olarak faturalar düzenliyoruz. + +{{include: buttons}} +
                                                                                                                            diff --git a/www/tr/history.texy b/www/tr/history.texy index 2d9b32aba4..cf4308a2a2 100644 --- a/www/tr/history.texy +++ b/www/tr/history.texy @@ -1,36 +1,35 @@ -Nette'in Tarihçesi -****************** +Nette Tarihçesi +*************** .[perex] -Nette'nin kökenleri, yazarı David Grudl'un saf PHP artık yeterli olmadığı için uygulama yazmak için uygun bir çerçeve aramaya başladığı 2004 yılına dayanıyor. O dönemde mevcut olan çözümlerin hiçbiri kendisine uygun değildi, bu nedenle daha sonra Nette adını alacak olan yeni bir çerçevenin özelliklerini yavaş yavaş ana hatlarıyla belirlemeye başladı. +Nette'nin başlangıcı, yazarı David Grudl'un saf PHP'nin artık yeterli olmadığı uygulamalar yazabileceği uygun bir framework aramaya başladığı 2004 yılına dayanmaktadır. O zamanlar mevcut çözümlerden hiçbiri ona uymadı, bu yüzden daha sonra Nette adını alacak yeni bir framework'ün özelliklerini yavaş yavaş çizmeye başladı. -O zamanlar Symfony, Laravel veya Ruby on Rails gibi güncel çerçeveler henüz mevcut değildi. Java dünyasında JSF (JavaServer Faces) standarttı ve rakip .NET aleminde ASP.NET Webforms baskın çerçeveydi. Her ikisi de yeniden kullanılabilir kullanıcı arayüzü bileşenleri kullanarak sayfalar oluşturmaya izin veriyordu. David, bunların soyutlama yöntemlerini ve oturumlar ya da geri dönüşler kullanarak vatansız HTTP protokolü üzerinden vatansızlık yaratma girişimlerini kusurlu ve temelden bozuk olarak değerlendirdi. Kullanıcılar ve arama motorları için birçok zorluğa neden oldular. Örneğin, bir bağlantıyı kaydettiğinizde daha sonra altında farklı bir içerik bulmanız sizi şaşırtıyordu. +O zamanlar Symfony, Laravel veya Ruby on Rails gibi mevcut framework'ler henüz yoktu. Java dünyasında standart JSF (JavaServer Faces) framework'üydü ve rakip .NET'te ise ASP.NET Webforms vardı. Her ikisi de yeniden kullanılabilir UI bileşenleri kullanarak sayfalar oluşturmaya izin veriyordu. David, onların soyutlama yöntemlerini ve oturum veya sözde postback kullanarak durumsuz HTTP protokolü üzerinde durum bilgisi oluşturma çabalarını hatalı ve temelden işlevsiz olarak değerlendirdi. Kullanıcılara ve arama motorlarına bir dizi zorluk çıkardılar. Örneğin, bir bağlantıyı kaydederseniz, daha sonra altında farklı bir içerik bulduğunuzda şaşırırdınız. -Yeniden kullanılabilir UI bileşenlerinden sayfalar oluşturma olasılığı, o zamanlar masaüstü uygulamaları oluşturmak için popüler bir araç olan Delphi'den iyi bilen David'i büyüledi. Delphi için açık kaynaklı bileşenlerin bulunduğu pazarları seviyordu. Bu yüzden, durumsuz HTTP ile tam bir uyum içinde çalışacak bir bileşen çerçevesinin nasıl oluşturulacağı sorusunu çözmeye çalıştı. Kullanıcı, SEO ve geliştirici dostu olacak bir konsept arıyordu. Ve böylece Nette doğdu. +Sayfaları yeniden kullanılabilir UI bileşenlerinden oluşturma olasılığı David'i büyüledi, bunu o zamanlar masaüstü uygulamaları oluşturmak için popüler bir araç olan Delphi'den iyi biliyordu. Delphi için açık kaynaklı bileşen pazarlarını beğendi. Bu nedenle, durumsuz HTTP ile tam uyum içinde çalışacak bir bileşen framework'ünün nasıl oluşturulacağı sorusunu çözmeye çalıştı. Kullanıcılar, SEO ve geliştiriciler için dostane olacak bir konsept arıyordu. Ve böylece Nette doğmaya başladı. .[note] -Nette ismi banyoda tesadüfen, yazarın sadece *llette* görülebilecek şekilde döndürülmüş bir şişe Gillette tıraş jeli görmesiyle ortaya çıktı. +Nette adı, yazarın banyoda Gillette tıraş jeli kabını sadece *llette* görülecek şekilde çevrilmiş halde fark etmesiyle tesadüfen ortaya çıktı. -Bunu binlerce saatlik araştırma, düşünme ve yeniden yazma süreci izledi. Brno'nun dışında bir köydeki tozlu bir garajda, geleceğin çerçevesinin ilk ana hatları oluşturuluyordu. Mimarinin temeli, o zamanlar artık unutulmuş olan PHP çerçevesi Mojavi tarafından kullanılan ve daha sonra Ruby on Rails etrafındaki heyecanla popülerleşen MVC modeliydi. İlham kaynaklarından biri de Honza Tichý'nin hiç yayınlanmamış phpBase framework'üydü. +Binlerce saatlik araştırma, düşünme ve yeniden yazma takip etti. Brno'nun arkasındaki bir köydeki tozlu bir garajda, gelecekteki framework'ün ilk taslakları ortaya çıktı. Mimarinin temeli, o zamanlar artık unutulmuş olan PHP framework'ü Mojavi tarafından kullanılan ve daha sonra Ruby on Rails etrafındaki heyecan sayesinde popülerleşen MVC deseni oldu. İlham kaynaklarından biri, Honza Tichý'nin hiç yayınlanmamış phpBase framework'üydü bile. -Yaklaşan Nette ile ilgili makaleler yazarın blogunda görünmeye başladı. Bunun bir vaporware olduğu şakası yapıldı. Ancak daha sonra Ekim 2007'de Prag PHP Seminar konferansında David Nette'i kamuoyuna tanıttı. Bu arada, bu konferans bir yıl sonra WebExpo'ya dönüştü ve daha sonra Avrupa'nın en büyük BT konferanslarından biri oldu. O zaman bile Nette, yukarıda bahsedilen bileşen modeli, çift yönlü yönlendirici, sunucular arasında bağlantı kurmanın özel yolu gibi bir dizi orijinal kavramı gururla sundu. Formlar, kimlik doğrulama, önbelleğe alma vs. vardı. Her şey Nette'de bugüne kadar orijinal konseptinde kullanılmaya devam ediyor. +Yazarın blogunda, yakında çıkacak olan Nette hakkında makaleler yayınlanmaya başladı. Bunun bir vaporware olduğu şakası yapıldı. Ancak Ekim 2007'de Prag'daki PHP Semineri konferansında David, Nette'yi halka tanıttı. Bu arada, bu konferanstan bir yıl sonra, daha sonra Avrupa'nın en büyük BT konferanslarından biri haline gelen WebExpo gelişti. O zaman bile Nette, bahsedilen bileşen modeli, çift yönlü yönlendirici, presenter'lar arasında belirli bir bağlantı kurma yöntemi vb. gibi bir dizi orijinal konseptle övündü. Formları, çözülmüş kimlik doğrulaması, önbellekleme vb. vardı. Nette'deki her şey bugün hala orijinal konseptinde kullanılıyor. .[note] -Nette *controller* yerine *presenter* kullanıyor çünkü kodda *con* ile başlayan çok fazla kelime varmış (controller, front controller, control, config, container, ...). +Nette'de *controller* terimi yerine *presenter* kullanılır, çünkü kodda *con* ile başlayan çok fazla kelime olduğu söylenir (controller, front controller, control, config, container, ...) -2007'nin sonunda David Grudl kodu yayınladı ve Nette 0.7 piyasaya sürüldü. Bunun etrafında hevesli bir programcı topluluğu oluştu ve her ay Posobota etkinliğinde buluşmaya başladılar. Topluluk, büyük PHPStan aracının yazarı Ondrej Mirtes gibi günümüzün birçok aydınını içeriyordu. Nette'in geliştirilmesi ilerledi ve sonraki iki yıl içinde 0.8 ve 0.9 sürümleri yayınlandı ve çerçevenin bugünkü bölümlerinin neredeyse tamamının temelleri atıldı. Ruby on Rails için Hotwire veya Symfony UX Turbo'dan 14 yıl öncesine dayanan AJAX parçacıkları dahil. +2007'nin sonunda David Grudl kodu da yayınladı ve böylece Nette sürüm 0.7 gün ışığına çıktı. Framework hemen büyük ilgi gördü. Etrafında, her ay Posobota etkinliğinde buluşmaya başlayan hevesli bir programcı topluluğu oluştu. Toplulukta, harika PHPStan aracının yazarı Ondřej Mirtes gibi bugünün birçok kişiliği vardı. Nette'nin gelişimi hızla ilerledi ve sonraki iki yılda, framework'ün bugünkü hemen hemen tüm bölümlerinin temellerinin atıldığı 0.8 ve 0.9 sürümleri yayınlandı. Ruby on Rails için Hotwire veya Symfony UX Turbo'dan 14 yıl önce gelen AJAX snippet'leri dahil. -Ancak o zamanlar Nette'de çok önemli bir şey eksikti. Dependcy injection container (DIC). Nette bir *service locator* kullanıyordu ve amaç dependecy injection'a geçmekti. Ama böyle bir şey nasıl tasarlanırdı? O sırada DI konusunda hiç deneyimi olmayan David Grudl, yaklaşık altı aydır DI kullanan Vasek Purchart ile öğle yemeğine çıktı. Birlikte konuyu tartıştılar ve David, uygulama tasarımı hakkındaki düşüncelerimizde tamamen devrim yaratan bir kütüphane olan Nette DI üzerinde çalışmaya başladı. DI konteyneri, çerçevenin en başarılı parçalarından biri haline geldi. Ve iki yan ürün ortaya çıkardı: Neon formatı ve Schema kütüphanesi. +Ancak o zamanki Nette'de önemli bir şey eksikti. Bağımlılık enjeksiyonu konteyneri (DIC). Nette, sözde *service locator* kullanıyordu ve amaç tam olarak bağımlılık enjeksiyonuna geçmekti. Ama böyle bir şey nasıl tasarlanırdı? O zamanlar DI deneyimi olmayan David Grudl, yaklaşık yarım yıldır DI kullanan Vašek Purchart ile öğle yemeğine gitti. Konuyu birlikte tartıştılar ve David, uygulamaların tasarımı hakkında düşünme şeklini tamamen değiştiren bir kütüphane olan Nette DI üzerinde çalışmaya başladı. DI konteyneri, framework'ün en başarılı bölümlerinden biri haline geldi. Ve daha sonra iki yan ürüne de yol açtı: Neon formatı ve Schema kütüphanesi. .[note] -Bağımlılık enjeksiyonuna geçiş çok zaman aldı ve Nette'in yeni bir sürümü için birkaç yıl bekledik. Bu nedenle, nihayet çıktığında, 2 numaralıydı. Yani Nette sürüm 1 mevcut değil. +Bağımlılık enjeksiyonuna geçiş oldukça zaman aldı ve Nette'nin yeni sürümü birkaç yıl beklendi. Bu nedenle, nihayet çıktığında, doğrudan 2 numarasını taşıyordu. Yani Nette sürüm 1 mevcut değil. -Nette modern tarihine 2012 yılında 2.0 sürümü ile başladı. Bu sürüm aynı zamanda, şimdi Explorer olarak adlandırılan son derece kullanışlı bir veritabanı aracı içeren Nette Database'i de beraberinde getirdi. Bu kütüphane ilk olarak David Grudl'un komşusu ve popüler Adminer aracının yazarı Jakub Vrána tarafından programlandı. Daha sonra geliştirilmesi üç yıl boyunca Jan Škrášek tarafından üstlenildi. +Nette, 2012 yılında 2.0 sürümüyle modern tarihini başlattı. Ayrıca, bugün Explorer olarak adlandırılan, veritabanıyla çalışmak için alışılmadık derecede kullanışlı bir araç içeren Nette Database'i de getirdi. Bu kütüphane başlangıçta David Grudl'un komşusu ve popüler Adminer aracının yazarı Jakub Vrána tarafından programlanmıştı. Daha sonraki gelişimi üç yıl boyunca Jan Škrášek tarafından üstlenildi. -2014 yılında Nette 2.1 ve kısa bir süre sonra da Nette 2.2 piyasaya sürüldü. Bu nasıl mümkün oldu? Sürüm 2.2, sürüm 2.1 ile aynıydı, sadece yirmi ayrı pakete bölünmüştü. Composer aracı PHP dünyasını etkisi altına aldı ve kütüphane oluşturma hakkındaki düşüncelerimizi değiştirdi. Nette bir monolit olmaktan çıktı ve daha küçük bağımsız parçalara ayrıldı. Her birinin kendi deposu, sorun izleyicisi ve kendi geliştirme ve sürüm akışı var. Bu şekilde Nette, hiçbir şey değişmemiş olsa bile bir paketin yeni bir sürümünün çıktığı monolitik çerçevelerde yaygın olan saçmalıklardan geçmek zorunda kalmaz. Git depolarının fiilen bölünmesi birkaç haftalık hazırlık ve yüzlerce saatlik makine zamanı gerektirdi. - -Nette ayrıca Sitepoint dergisi tarafından düzenlenen en iyi PHP çatısı için küresel ankette inanılmaz bir 3. sırada yer aldı. +2014 yılında Nette 2.1 yayınlandı ve kısa bir süre sonra Nette 2.2 geldi. Bu nasıl mümkün oldu? Sürüm 2.2, sürüm 2.1 ile aynıydı, sadece yirmi ayrı pakete bölünmüştü. PHP dünyasında Composer aracı yerleşti ve kütüphane oluşturma şeklini değiştirdi. Nette böylece monolit olmaktan çıktı ve daha küçük bağımsız parçalara ayrıldı. Her biri kendi deposu, sorun izleyicisi ve kendi geliştirme hızı ve sürümlemesiyle. Nette'de, monolitik framework'lerde yaygın olan, içinde hiçbir şey değişmemiş olmasına rağmen bir paketin yeni bir sürümünün yayınlanması gibi saçmalıkların yaşanması gerekmez. Git depolarının bölünmesi birkaç haftalık hazırlık ve yüzlerce saatlik makine süresi gerektirdi. +Nette ayrıca Sitepoint dergisi tarafından düzenlenen en iyi PHP framework'ü için dünya çapında yapılan ankette harika bir 3. sırada yer aldı. {{toc:no}} diff --git a/www/tr/license.texy b/www/tr/license.texy new file mode 100644 index 0000000000..b51429ab79 --- /dev/null +++ b/www/tr/license.texy @@ -0,0 +1,44 @@ +Lisans Politikası +***************** + +Nette Framework, herkesin kullanabilmesi için özgür yazılım olarak dağıtılır. [Yeni BSD |#Yeni BSD Lisansı] lisansının mı yoksa sürüm 2 veya 3'teki [#GNU Genel Kamu Lisansı (GPL)]'nin mi size daha uygun olduğunu seçebilirsiniz. + +BSD lisansı, anlaşılması kolay olduğu ve framework ile yapabilecekleriniz konusunda neredeyse hiçbir kısıtlama getirmediği için çoğu proje için önerilir. Nette'yi ticari projelerde de kullanabilirsiniz. Ancak, GPL projeniz için daha uygunsa, onu seçin. Nasıl karar verdiğiniz konusunda kimseyi bilgilendirmenize gerek yoktur. Her zaman orijinal telif haklarını koruyun. + +"Nette Framework" adının korumalı bir ticari marka olduğunu ve kullanımının belirli kısıtlamaları olduğunu bilmelisiniz. Bu nedenle, projenizin veya üst düzey alan adınızın adında "Nette" kullanmayın, bunun yerine kendi temelleri üzerinde duracak bir ad seçin. Projeniz iyiyse, kendi itibarını oluşturması uzun sürmeyecektir. + +Nette Framework'ten memnunsanız ve öyle olacağınıza inanıyoruz, onu [bağışla destekleyin |donate]. Teşekkür ederiz. + + +Yeni BSD Lisansı +---------------- + +Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com) All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of "Nette Framework" nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +GNU Genel Kamu Lisansı (GPL) +---------------------------- + +GPL lisanslarının metinleri çok uzun olduğundan, burada onlara bağlantılar verilmiştir: + +- GPL sürüm 2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +- GPL sürüm 3: https://www.gnu.org/licenses/gpl-3.0.html + + +{{toc:yes}} +{{priority: -2}} diff --git a/www/tr/maintenance.texy b/www/tr/maintenance.texy index e81d71e6aa..f6f9f8f2d7 100644 --- a/www/tr/maintenance.texy +++ b/www/tr/maintenance.texy @@ -1,27 +1,27 @@ -Bakım ve PHP Sürümleri -********************** +Bakım ve PHP Uyumluluğu +*********************** .[perex] -Nette, bireysel sürümler için son derece uzun bir destek süresine sahip bir çerçevedir. Her şube, en az 2 yıllık desteğe sahip bir LTS'dir (Uzun Vadeli Destek Sürümü). +Nette, bireysel sürümler için son derece uzun bir destek süresine sahip bir framework'tür. Her dal, en az 2 yıl desteklenen bir LTS'dir (Uzun Süreli Destek Sürümü). -Her sürüm, ilk kararlı sürümünden itibaren bir yıllık (veya daha uzun) bir süre boyunca aktif olarak korunur. -Kritik ve güvenlik hataları iki yıl boyunca giderilir. +Her sürüm, ilk kararlı sürümden itibaren bir yıl (veya daha uzun) süreyle aktif olarak korunur. Kritik ve güvenlik hataları iki yıl boyunca düzeltilir. -Sürüm Takvimi (Yol Haritası) .[#toc-release-calendar-roadmap] -============================================================= +Nette Yayın Takvimi +=================== {{include: doc-roadmap-table}} -PHP Uyumluluğu .[#toc-php-compatibility] -======================================== +PHP Uyumluluğu +============== Uyumluluk her zaman her serinin en son sürümü için geçerlidir. + {{include: doc-roadmap-versions}} {{leftbar: @menu-common}} {{toc: no}} -{{description: Nette sürümleri, yol haritası, bakım ve PHP uyumluluk tabloları}} +{{description: Nette sürümleri, yol haritası, bakım tabloları ve PHP uyumluluğu}} diff --git a/www/tr/packages.texy b/www/tr/packages.texy index 3699ab897a..d53e725ad0 100644 --- a/www/tr/packages.texy +++ b/www/tr/packages.texy @@ -1,26 +1,27 @@ -Paketlerin Listesi -****************** +Nette Paket Listesi +******************* -| **Application**:[application:how-it-works] | Web uygulamasının çekirdeği | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] -| **Bootstrap**:[bootstrap:] | Uygulamanızın önyüklemesi | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] -| **Caching**:[caching:] bellekleme: | Depo seti ile önbellek katmanı | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] -| **Component Model**:[component-model:] | Bileşen sistemleri için temel | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] -| **DI**:[dependency-injection:] | Dependency Injection Container | [GitHub |https://github.com/nette/di] [API |https://api.nette.org/di/] +| **Application**:[application:how-it-works] | Web uygulamalarının çekirdeği | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] +| **Assets**:[assets:] | Statik dosya yönetimi | [GitHub |https://github.com/nette/assets] [API |https://api.nette.org/assets/] +| **Bootstrap**:[bootstrap:] | Web uygulaması önyüklemesi | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] +| **Caching**:[caching:] | Depolama alanlarıyla önbellekleme katmanı | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] +| **Component Model**:[component-model:] | Bileşen sisteminin temeli | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] +| **DI**:[dependency-injection:] | Bağımlılık Enjeksiyonu Konteyneri | [GitHub |https://github.com/nette/di] [API |https://api.nette.org/di/] | **Database**:[database:] | Veritabanı katmanı | [GitHub |https://github.com/nette/database] [API |https://api.nette.org/database/] -| **Forms**:[forms:] | Güvenli web formlarını büyük ölçüde kolaylaştırır | [GitHub |https://github.com/nette/forms] [API |https://api.nette.org/forms/] -| **Http**:[http:] | HTTP isteği ve yanıtı için katman | [GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] -| **Latte**:[latte:] te: | Şaşırtıcı şablon motoru | [GitHub |https://github.com/nette/latte] [API |https://api.nette.org/latte/] -| **Mail**:[mail:] | E-posta Gönderme | [GitHub |https://github.com/nette/mail] [API |https://api.nette.org/mail/] -| **Neon**:[neon:] | [NEON |https://ne-on.org] yükler ve döker | [GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] -| **Php Generator**:[php-generator:] | PHP kod oluşturucu | [GitHub |https://github.com/nette/php-generator] [API |https://api.nette.org/php-generator/] -| **Robot Loader**:[robot-loader:] | En rahat otomatik yükleme | [GitHub |https://github.com/nette/robot-loader] [API |https://api.nette.org/robot-loader/] +| **Forms**:[forms:] | Kullanışlı ve güvenli web formları | [GitHub |https://github.com/nette/forms] [API |https://api.nette.org/forms/] +| **Http**:[http:] | HTTP isteğini ve yanıtını kapsayan katman | [GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] +| **Latte**:[latte:] | Harika şablonlama sistemi | [GitHub |https://github.com/nette/latte] [API |https://api.nette.org/latte/] +| **Mail**:[mail:] | E-posta gönderme | [GitHub |https://github.com/nette/mail] [API |https://api.nette.org/mail/] +| **Neon**:[neon:] | [NEON |https://ne-on.org] formatını okuma ve yazma | [GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] +| **Php Generator**:[php-generator:] | PHP kodu oluşturucu | [GitHub |https://github.com/nette/php-generator] [API |https://api.nette.org/php-generator/] +| **Robot Loader**:[robot-loader:] | En konforlu otomatik yükleme | [GitHub |https://github.com/nette/robot-loader] [API |https://api.nette.org/robot-loader/] | **Routing**:[application:routing] | Yönlendirme | [GitHub |https://github.com/nette/routing] [API |https://api.nette.org/routing/] | **Safe Stream**:[safe-stream:] | Dosyalarla güvenli atomik işlemler | [GitHub |https://github.com/nette/safe-stream] [API |https://api.nette.org/safe-stream/] -| **Security**:[security:authentication] | Erişim kontrol sistemi sağlar | [GitHub |https://github.com/nette/security] [API |https://api.nette.org/security/] -| **Schema**:[schema:] | Kullanıcı veri doğrulama | [GitHub |https://github.com/nette/schema] [API |https://api.nette.org/schema/] -| **Tester**:[tester:] | PHP'de keyifli birim testi | [GitHub |https://github.com/nette/tester] [API |https://api.nette.org/tester/] +| **Schema**:[schema:] | Kullanıcı verilerini doğrulama | [GitHub |https://github.com/nette/schema] [API |https://api.nette.org/schema/] +| **Security**:[security:authentication] | Erişim hakları yönetimi | [GitHub |https://github.com/nette/security] [API |https://api.nette.org/security/] +| **Tester**:[tester:] | PHP'de rahat birim testleri | [GitHub |https://github.com/nette/tester] [API |https://api.nette.org/tester/] | **Tracy**:[tracy:] | Seveceğiniz hata ayıklama aracı ♥ | [GitHub |https://github.com/nette/tracy] [API |https://api.nette.org/tracy/] -| **Utils**:[utils:] | Yardımcı Programlar ve Çekirdek Sınıflar | [GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] +| **Utils**:[utils:] | Temel sınıflar ve araçlar | [GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] {{leftbar: @menu-common}} {{toc: no}} diff --git a/www/uk/10-reasons-why-nette.texy b/www/uk/10-reasons-why-nette.texy index 54aa9a5843..b7bded2c4b 100644 --- a/www/uk/10-reasons-why-nette.texy +++ b/www/uk/10-reasons-why-nette.texy @@ -1,53 +1,93 @@ -Навіщо використовувати Nette? -***************************** +7 причин використовувати Nette +******************************
                                                                                                                            -Втомилися від завдань, що повторюються, тисяч дрібниць, які відволікають вас від роботи і перетворюють програмування на нудне заняття? Ви знаходитесь у правильному місці! **Framework полегшить вашу роботу, ви писатимете менше, матимете чистіший код і отримуватимете задоволення від роботи.** Ви отримаєте: +Уявіть собі PHP фреймворк, який дозволить вам зосередитися на тому, що ви робите найкраще. Веде вас до написання чистого коду. Сам дбає про безпеку. Перестаньте мріяти і познайомтеся з Nette. Вирушайте в подорож, яка відкриє вам двері до нових можливостей розробки. Розповімо: -- відмінну систему шаблонів -- неперевершені інструменти для налаштування -- надзвичайно ефективний шар бази даних -- надійний захист від уразливостей -- сучасний фреймворк із підтримкою HTML5, AJAX або SEO -- якісну документацію та активну спільноту -- зрілий і чистий об'єктно-орієнтований дизайн з використанням новітніх можливостей PHP -- рішення, які заохочуються, але не застосовуються на практиці +- як створювати веб-сайти з максимальним комфортом +- як писати елегантний код +- що означає мудрість «менше коду = більша безпека» +- як будувати веб як конструктор +- і як стати частиною успішної спільноти
                                                                                                                            -І це абсолютно безкоштовно. Варто спробувати, чи не так? +Nette приносить комфорт та ефективність у світ веб-розробників завдяки інноваційним інструментам та технікам. Які ключові властивості роблять Nette унікальним та необхідним елементом набору розробника? Давайте розглянемо їх! -**Фреймворк Nette створений, щоб бути максимально зручним і дружнім до користувача.**. Це фреймворк, з яким не тільки легко, а й цікаво працювати. Він дає вам ясний і економічний синтаксис, зручний у програмуванні та налагодженні, дає змогу зосередитися на творчій стороні розробки. Усуває ризики безпеки. Ви можете створювати інтернет-магазини, вікі, блоги, CMS або все, що ви тільки можете собі уявити, швидше і краще, ніж будь-коли. +#1 Nette вас балує +------------------ -Nette [використовується великими компаніями |https://builtwith.nette.org], такими як T-Systems, GE Money, Mladá fronta, VLTAVA-LABE-PRESS, Internet Info, DHL, Logio, ESET, Actum, Slevomat, Socialbakers, SUPRAPHON. Наразі на ньому працює сайт колишнього президента Чеської Республіки Вацлава Клауса. В опитуванні, проведеному [Zdroják |https://www.zdrojak.cz/clanky/vysledky-technologie-na-ceskem-webu/], він здобув приз як найпопулярніший і найбільш широко використовуваний фреймворк у Чеській Республіці. +Коли двадцять років тому почав зароджуватися фреймворк Nette, все оберталося навколо однієї мети: Як створювати веб-сайти максимально зручно? Як максимально полегшити роботу програмістам? Як зробити створення веб-сайтів привабливим? -Коли ви опануєте Nette, у вас не буде нестачі в цікавих пропозиціях про роботу. +Nette цим підходом зацікавило багатьох програмістів і швидко здобуло популярність. Тоді ми називали цю філософію Netteway, а сьогодні для неї існує термін *Developer Experience* (DX). Nette — це фреймворк, який має DX у своїй ДНК. Різницю ви відчуєте в тисячі й одній речі — від дрібниць до фундаментальних інновацій. Дозвольте фреймворку побалувати вас. -Приступимо до роботи .[#toc-get-on-board] ------------------------------------------ +#2 Nette веде вас до чистого коду +--------------------------------- -Дотримуйтесь керівництва і крок за кроком [створіть свій перший додаток |quickstart:]. Повне Керівництво [програміста |@home] і зручна [Документація по API |https://api.nette.org/] з оглядом класів і методів завжди будуть у вас під рукою. +Хочете писати чистий код? Мати правильно спроектовані додатки? Хто б не хотів! І саме тут починається роль фреймворку. Якщо він сам не подає приклад, неможливо створити чудово спроектований додаток. -Якщо ви застрягли, зазирніть у розділ [Вирішення проблем |nette:troubleshooting] або на [офіційний форум |https://forum.nette.org/en/]. +Nette подає приклад. Це наставник, що навчає добрим звичкам та написанню коду за перевіреними методиками. Як піонер та євангеліст dependency injection, він пропонує якісну основу для стійких, розширюваних та легко читабельних додатків. Nette розроблено так, щоб бути зрозумілим для початківців, водночас пропонуючи достатню глибину для досвідчених розробників. -Самонавчання здається неефективним способом? Ми розуміємо і пропонуємо [навчання фреймворку Nette |https://www.skoleniphp.cz/skoleni-nette-vyvoj-webovych-aplikaci] (чеською). Відгуки колишніх учасників можна знайти на сайті [skoleniphp.cz/ohlasy |https://www.skoleniphp.cz/ohlasy]. +Спільнота навколо Nette виховала низку особистостей, які сьогодні стоять за успішними та важливими проектами. Для тисяч програмістів Nette стало наставником на їхньому шляху до професійного зростання. Приєднуйтесь та відкрийте, як Nette позитивно вплине на якість вашого коду та додатків. -Додаткова інформація .[#toc-more-information] ---------------------------------------------- +#3 Надійний охоронець ваших додатків +------------------------------------ -У нас є [блог, сповнений порад |https://blog.nette.org] і колекція різноманітних [аддонів і компонентів |https://componette.org] для використання у ваших додатках. +Nette захищає ваші додатки. Протягом років він здобув репутацію інструменту, який надзвичайно серйозно ставиться до безпеки. Надає продуманий захист від вразливостей. Завжди дбає про те, щоб полегшити роботу програмістам, але ніколи за рахунок безпеки. -Крім того, щомісяця спільнота Nette збирається на захід під назвою [Остання субота |https://www.posobota.cz], де члени спільноти діляться своїм досвідом і просто добре проводять час. Приходьте випити пива! +Його девіз "менше коду = достатньо безпеки" означає, що окремі елементи поводяться безпечно вже за замовчуванням. Не потрібно активувати елементи безпеки, дописуючи додатковий код. Ви не можете про це забути або боятися, що щось пропустили. Програмісти часто навіть не підозрюють, скільки речей безпеки Nette робить за них, і дивуються, коли дізнаються про це. +Представляємо вам фреймворк, який веде вас до чистого коду і водночас пильнує за безпекою ваших додатків. Nette — це надійний партнер, який дозволить вам зосередитися на створенні чудових веб-додатків зі спокоєм у душі. -Беріть участь! .[#toc-get-involved] ------------------------------------ -Ми будемо дуже раді, якщо ви зробите свій внесок у розвиток фреймворку. Рекомендуйте Nette знайомим, розміщуйте [значок |www:en:logo] на своєму сайті або [підтримуйте розробку |www:en:donate] фінансово. Дякую ♥. +#4 Будуйте веб як конструктор +----------------------------- + +У Nette ви будуєте сторінки з повторно використовуваних UI компонентів. Це нагадує розробку десктопних додатків, і Nette успішно переніс цей підхід на веб. Потрібен в адміністрації datagrid? Достатньо знайти його на ринку open source компонентів, встановити та просто вставити на сторінку. Крім того, ви можете створювати власні компоненти для повторюваних елементів на сторінках, тим самим усуваючи дублювання та покращуючи організацію коду. + +Ця унікальна властивість відрізняє Nette від усіх інших значних гравців на ринку. Вона дозволить вам ефективно створювати та підтримувати веб-додатки. З Nette робота з UI стає гладкою та приємною. + + +#5 Гнучкий набір пакетів +------------------------ + +Nette — це набір [окремо використовуваних пакетів |www:packages]. До них належать захоплюючий [інструмент налагодження Tracy |tracy:], [система шаблонів нового покоління Latte |latte:], [відмінний Dependency Injection Container |dependency-injection:], [форми |forms:] та багато інших. Кожен пакет має читабельну детальну документацію та розміщений в окремому репозиторії на GitHub. Пакети можна використовувати окремо або комбінувати з іншими інструментами та технологіями, які ви вже використовуєте. Наприклад, Latte можна розгорнути в WordPress або Slim Framework, DI-контейнер може бути ядром корпоративного фреймворку, а Tracy візуалізуватиме повідомлення про помилки. + +Або ви можете використовувати Nette як ціле, як фреймворк, і створити в ньому повний веб-додаток. Незалежно від того, чи розробляєте ви невеликий особистий проект, чи надійний корпоративний додаток, Nette буде вашим надійним партнером. + + +#6 Стабільність та інновації +---------------------------- + +Nette — це зрілий та перевірений фреймворк з багаторічною історією. Незважаючи на це, він залишається гнучким та еластичним завдяки розумному дизайну. Користувачі цінують, що це невеликий та гнучкий фреймворк, а не монстр. + +Він завжди заздалегідь готовий до нових версій PHP та враховує найновіші інновації в галузі розробки веб-додатків. Це ключове для мінімізації технологічного боргу. + +Nette — це поєднання багаторічної стабільності та інновацій, що дозволить вам покладатися на перевірене рішення, водночас маючи можливість використовувати найновіші технології та тренди. З Nette ви маєте впевненість, що ваш проект буде побудований на засадах, які постійно йдуть в ногу зі світом веб-розробки, що швидко розвивається. + + +#7 Будьте в найкращій компанії +------------------------------ + +Nette Framework популярний серед професіоналів. На нього покладаються [значні компанії |https://builtwith.nette.org], такі як O2, BOSCH, ESET, Zásilkovna, DHL, SUPRAPHON та сотні інших. У кількох опитуваннях він переміг як найпопулярніший та найвикористовуваніший фреймворк у Чеській Республіці. + +Почніть з Nette, і вам відкриються нові можливості працевлаштування. Ви отримаєте не тільки потужний інструмент, але й найактивнішу спільноту в ЧР, яка готова підтримати вас на шляху до професійного зростання. Вона регулярно збирається на [Posobotách |www.posobota.cz], заходах, де обмінюється досвідом. Приходьте познайомитися з нами! + + +Відкрийте для себе Nette +------------------------ + +Приєднуйтесь до тисяч задоволених розробників, які вже відкрили переваги Nette, і почніть писати чистіший, безпечніший та ефективніший код з цим унікальним фреймворком. + +Створіть [за інструкцією |quickstart:] свій перший додаток, крок за кроком. Вам завжди буде під рукою [розширена документація |nette:] та практичний [огляд API |https://api.nette.org]. Відвідайте наш [блог, повний порад |https://blog.nette.org] та колекцію [доповнень та компонентів |https://componette.org], що розширюють можливості Nette. + +Маєте питання? Зверніться до сторінки з [частими запитаннями|nette:troubleshooting] або обговорюйте на [чеському форумі|https://forum.nette.org/cs/]. Віддаєте перевагу особистому навчанню? Ми пропонуємо [навчання Nette Framework |https://www.skoleniphp.cz/skoleni-nette-vyvoj-webovych-aplikaci] з [відмінними відгуками |https://www.skoleniphp.cz/ohlasy]. + +**Познайомтеся з фреймворком, який буде вас балувати, вести та надихати.** {{leftbar: @menu-common}} diff --git a/www/uk/@home.texy b/www/uk/@home.texy index c628a3405c..b34ca60b8e 100644 --- a/www/uk/@home.texy +++ b/www/uk/@home.texy @@ -1,2 +1,2 @@ -{{maintitle: Nette - зручна і безпечна розробка веб-додатків на PHP}} -{{description: Nette - це сімейство просунутих і самодостатніх компонентів для PHP. Радійте їм. Разом вони утворюють основу, що посідає 3-тє місце за популярністю у світі. У філософії Nette особлива увага приділяється продуктивності, передовому досвіду та безпеці}} +{{maintitle:Nette – Зручна та безпечна розробка веб-додатків на PHP}} +{{description: Nette — це сімейство зрілих і самостійно використовуваних компонентів для PHP. Дозвольте їм надихнути вас. Разом вони утворюють фреймворк, визнаний 3-м найпопулярнішим у світі. Філософія Nette надає особливого значення продуктивності, найкращим практикам та безпеці.}} diff --git a/www/uk/@menu-common.texy b/www/uk/@menu-common.texy index 15cea0f6b7..44ddbcd3d8 100644 --- a/www/uk/@menu-common.texy +++ b/www/uk/@menu-common.texy @@ -1,20 +1,22 @@ -Вступ -***** -- [Чому варто використовувати Nette? |www:10-reasons-why-nette] +Знайомство +********** +- [Чому використовувати Nette? |www:10-reasons-why-nette] - [Встановлення |nette:installation] -- [Створіть свій перший додаток! |quickstart:] +- [Пишемо першу програму! |quickstart:] -Загальні питання -**************** +Загальні теми +************* - [Список пакетів |www:packages] -- [Обслуговування та PHP |www:maintenance] -- [Примітки до випуску |https://nette.org/releases] -- [Посібник з оновлення |migrations:en] -- [Виправлення неполадок |nette:Troubleshooting] +- [Підтримка та версії PHP |www:maintenance] +- [Release Notes |https://nette.org/releases] +- [Перехід на новіші версії|migrations:en] +- [Вирішення проблем |nette:troubleshooting] - [Хто створює Nette |https://nette.org/contributors] - [Історія Nette |history] -- [Долучитися |contributing:] -- [Розвиток спонсорів |https://nette.org/en/donate] -- [Посилання на API |https://api.nette.org] -- [Кращі практики |best-practices:] +- [Долучайтеся |contributing:] +- [Підтримайте розробку |https://nette.org/cs/donate] +- [API reference |https://api.nette.org/] +- [Посібники та практики |best-practices:] + +- [Безпека перш за все |nette:vulnerability-protection] diff --git a/www/uk/donate.texy b/www/uk/donate.texy new file mode 100644 index 0000000000..8f1ce38d05 --- /dev/null +++ b/www/uk/donate.texy @@ -0,0 +1,20 @@ +Підтримайте розробку Nette +************************** + +
                                                                                                                            +Кожен, хто будує на Nette, зацікавлений у тому, щоб фреймворк активно розвивався. Щоб підтримував нові версії PHP. Щоб виправлялися помилки. Щоб з'являлися нові функції, які полегшать роботу або заощадять час та гроші. Щоб фреймворк мав чудову документацію та навколо нього існував корисний контент, чи то у вигляді статей, інструкцій чи відео. + +Багато частин Nette представляють світовий рівень, і ми хочемо, щоб так було й надалі. + +Без адекватного фінансування нічого з цього неможливо забезпечити. При цьому, щоб ви могли розраховувати на вихід наступних версій, потрібно зовсім небагато: щоб ви щомісяця підтримували його хоча б невеликою фінансовою сумою. + +Приєднуйтесь і станьте партнером Nette! + +Ви забезпечите здорове функціонування проекту, на який покладаєтеся. А водночас **отримаєте цілу низку ексклюзивних переваг** (див. *Рівні партнерства* у правій колонці). Ви отримаєте доступ до бонусного контенту. До технічної підтримки. Підвищите пріоритет вирішення ваших issues. А також зробите свою компанію більш видимою та привабите до себе розробників. + +Як ми зробимо вас видимими? Наприклад, розміщенням вашого логотипу на цьому сайті (тобто на цій сторінці, на головній сторінці, в документації, на форумі та на [спеціальній сторінці |https://nette.org/partner/vitalita], на яку ви можете посилатися). Ви матимете можливість розміщувати [пропозиції роботи |https://forum.nette.org/cs/f30-prace-a-zakazky], рекламувати на форумі ([приклад |https://forum.nette.org/cs/30798-problem-s-cizim-klicem-pri-mazani#p198093]) або в документації ([приклад |https://doc.nette.org/cs/application/components#toc-flash-zpravy]), тобто в місцях з найкращим охопленням групи розробників Nette. + +Партнерам ми виставляємо рахунки, щоб вони могли включити підтримку у витрати, і це можна робити щомісяця, щокварталу, щопівроку або щорічно. + +{{include: buttons}} +
                                                                                                                            diff --git a/www/uk/history.texy b/www/uk/history.texy index 09ca0133aa..3fa567af1a 100644 --- a/www/uk/history.texy +++ b/www/uk/history.texy @@ -2,34 +2,34 @@ ************* .[perex] -Витоки Nette беруть початок у 2004 році, коли його автор Девід Грудл почав шукати відповідний фреймворк для написання додатків, оскільки чистого PHP було вже недостатньо. Жодне з доступних на той момент рішень його не влаштовувало, тому він поступово почав накидати риси нового фреймворку, який згодом отримав назву Nette. +Початок виникнення Nette сягає 2004 року, коли його автор Давід Грудл почав шукати відповідний фреймворк, у якому міг би писати додатки, оскільки чистого PHP для цього вже не вистачало. Жодне з доступних на той час рішень йому не підходило, тому він почав поступово накреслювати риси нового фреймворку, який пізніше отримав назву Nette. -На той час таких сучасних фреймворків, як Symfony, Laravel або Ruby on Rails, ще не існувало. У світі Java стандартом був JSF (JavaServer Faces), а в конкуруючому світі .NET стандартом був ASP.NET Webforms. Обидва вони давали змогу створювати сторінки з використанням багаторазово використовуваних компонентів користувацького інтерфейсу. Девід вважав їхні методи абстракції та спроби створити безгромадянськість за безгромадянським протоколом HTTP за допомогою сесій або постбеків недосконалими і принципово непрацюючими. Вони створювали безліч труднощів для користувачів і пошукових систем. Наприклад, якщо ви зберегли посилання, то з подивом виявили під ним пізніше інший контент. +У той час ще не існувало сучасних фреймворків, таких як Symfony, Laravel або Ruby on Rails. У світі Java стандартом був фреймворк JSF (JavaServer Faces), а в конкурентному .NET — ASP.NET Webforms. Обидва дозволяли будувати сторінки за допомогою повторно використовуваних UI компонентів. Їхні способи абстракції та спроби створити стан над безстановим протоколом HTTP за допомогою сесії або так званого postback Давід вважав помилковими та принципово нефункціональними. Вони створювали низку труднощів користувачам та пошуковим системам. Наприклад, якщо ви зберігали посилання, пізніше ви з подивом знаходили під ним інший вміст. -Сама можливість складання сторінок із багаторазово використовуваних компонентів призначеного для користувача інтерфейсу заворожувала Девіда, і він добре знав її за Delphi, популярним на той час засобом розроблення настільних додатків. Йому сподобалися торгові майданчики з компонентами з відкритим вихідним кодом для Delphi. Тому він спробував розв'язати питання про те, як створити компонентний фреймворк, який, своєю чергою, працював би в повній гармонії зі stateless HTTP. Він шукав концепцію, яка була б зручна для користувачів, SEO та розробників. Так народилася Nette. +Сама можливість складати сторінки з повторно використовуваних UI компонентів захоплювала Давіда, він добре знав її з Delphi, тоді популярного інструменту для створення десктопних додатків. Йому подобалися ринки з opensource компонентами для Delphi. Тому він намагався вирішити питання, як створити компонентний фреймворк, який би, навпаки, працював у повній відповідності з безстановим HTTP. Він шукав концепцію, яка була б дружньою для користувачів, SEO та розробників. І так почало зароджуватися Nette. .[note] -Ім'я Nette з'явилося випадково у ванній кімнаті, коли авторка помітила контейнер із гелем для гоління Gillette, повернутий так, що було видно тільки *llette*. +Назва Nette виникла випадково у ванній, коли автор помітив баночку з гелем для гоління Gillette, повернуту так, що було видно лише *llette*. -Потім пішли тисячі годин досліджень, роздумів і переписування. У запиленому гаражі в селі десь за межами Брно було створено перші обриси майбутнього каркаса. В основі архітектури лежав патерн MVC, який потім використовували в забутому нині PHP-фреймворку Mojavi, а пізніше популяризували галасом навколо Ruby on Rails. Одним із джерел натхнення став так і не опублікований фреймворк phpBase Хонзи Тіхі. +Далі були тисячі годин досліджень, роздумів та переписувань. У запиленому гаражі в селі десь за Брно виникали перші обриси майбутнього фреймворку. Основою архітектури став патерн MVC, який тоді використовував сьогодні вже забутий PHP фреймворк Mojavi, а пізніше був популяризований завдяки галасу навколо Ruby on Rails. Одним із джерел натхнення був навіть ніколи не опублікований фреймворк phpBase Яна Тихого. -У блозі автора почали з'являтися статті про майбутній вихід "Nette". Жартували, що йдеться про парову продукцію. Але потім у жовтні 2007 року, на конференції Prague PHP Seminar, Девід публічно представив Nette. До речі, через рік ця конференція перетворилася на WebExpo, яка згодом стала однією з найбільших ІТ-конференцій у Європі. Уже тоді Nette міг похвалитися низкою оригінальних концепцій, таких як вищезгадана компонентна модель, двонаправлений маршрутизатор, особливий спосіб зв'язку між ведучими тощо. У ньому були форми, аутентифікація, кешування тощо. Усе і сьогодні використовується в Nette у своїй первісній концепції. +У блозі автора почали з'являтися статті про майбутній Nette. Жартували, що це vaporware. Але потім у жовтні 2007 року на празькій конференції PHP Seminář Давід публічно представив Nette. До речі, з цієї конференції через рік розвинулося WebExpo, пізніше одна з найбільших IT-конференцій у Європі. Вже тоді Nette похвалилося низкою оригінальних концепцій, таких як згадана компонентна модель, двосторонній роутер, специфічний спосіб посилань між презентерами тощо. Воно мало форми, вирішену автентифікацію, кешування тощо. Все це в Nette використовується в початковому вигляді донині. .[note] -Nette використовує *presenter* замість *controller*, тому що в коді нібито було занадто багато слів, що починаються з *con* (controller, front controller, control, config, container, ...). +У Nette замість терміну *controller* використовується *presenter*, оскільки в коді, нібито, було занадто багато слів, що починаються на *con* (controller, front controller, control, config, container, ...) -Наприкінці 2007 року Девід Грудл опублікував код, і було випущено версію Nette 0.7. Навколо нього сформувалася спільнота програмістів-ентузіастів, які стали зустрічатися щомісяця на заході Posobota. У співтовариство входили багато хто з сучасних світил, наприклад, Ондржей Міртес, автор чудового інструменту PHPStan. Розробка Nette просувалася вперед, і протягом наступних двох років було випущено версії 0.8 і 0.9, що заклали основу для майже всіх сучасних частин фреймворка. Включаючи фрагменти AJAX, які на 14 років передували Hotwire для Ruby on Rails або Symfony UX Turbo. +Наприкінці 2007 року Давід Грудл опублікував і код, і так світ побачила версія Nette 0.7. Фреймворк одразу привернув до себе величезну увагу. Навколо нього утворилася захоплена спільнота програмістів, яка почала щомісяця збиратися на заході Posobota. У спільноті була низка сьогоднішніх особистостей, наприклад, Ондржей Міртес, автор чудового інструменту PHPStan. Розробка Nette мчала вперед, і в наступні два роки вийшли версії 0.8 та 0.9, де були закладені основи майже всіх сьогоднішніх частин фреймворку. Включаючи AJAX-сніпети, які на 14 років випередили Hotwire для Ruby on Rails або Symfony UX Turbo. -Але в той час Nette не вистачало однієї важливої речі. Контейнер для ін'єкції залежностей (DIC). Nette використовувала *сервісний локатор*, і був намір перейти на dependecy injection. Але як спроектувати таку річ? Девід Грудл, який на той час не мав досвіду роботи з DI, пішов на обід із Вашеком Перчартом, який використовував DI близько півроку. Разом вони обговорили цю тему, і Девід почав роботу над Nette DI, бібліотекою, яка повністю змінила наше уявлення про дизайн додатків. Контейнер DI став однією з найуспішніших частин фреймворку. Це призвело до появи двох побічних продуктів: формату Neon і бібліотеки Schema. +Однак одна суттєва річ у тодішньому Nette була відсутня. Dependency injection container (DIC). Nette використовувало так званий *service locator*, і намір був перейти саме на dependency injection. Але як спроектувати таку річ? Давід Грудл, який тоді не мав досвіду з DI, пішов на обід з Вашеком Пурхартом, який використовував DI близько півроку. Вони разом обговорили тему, і Давід розпочав роботу над Nette DI, бібліотекою, яка повністю перевернула спосіб мислення над проектуванням додатків. DI-контейнер став однією з найвдаліших частин фреймворку. І пізніше дав початок двом спін-офам: формату Neon та бібліотеці Schema. .[note] -Перехід на впровадження залежностей зайняв багато часу, і нова версія Nette готувалася кілька років. Ось чому, коли він нарешті вийшов, його було пронумеровано як 2. Таким чином, Nette версії 1 не існує. +Перехід на dependency injection вимагав багато часу, і на нову версію Nette довелося чекати кілька років. Тому, коли вона нарешті вийшла, вона одразу отримала номер 2. Версії Nette 1, отже, не існує. -Свою сучасну історію Nette почала у 2012 році з версії 2.0. Вона також принесла Nette Database, яка включала надзвичайно зручний інструмент для роботи з базами даних, який тепер називається Explorer. Ця бібліотека була спочатку запрограмована Якубом Враною, сусідом Девіда Груделя і автором популярного інструменту Adminer. Подальшим розвитком компанії протягом трьох років займався Ян Шкрашек. +Nette у 2012 році версією 2.0 розпочало свою сучасну історію. Вона також принесла Nette Database, частиною якої був і надзвичайно зручний інструмент для роботи з базою даних, сьогодні званий Explorer. Цю бібліотеку спочатку написав Якуб Врана, сусід Давіда Грудла та автор популярного інструменту Adminer. Подальшою її розробкою потім три роки займався Ян Шкрашек. -У 2014 році було випущено версію Nette 2.1, за якою незабаром послідувала версія Nette 2.2. Версія 2.2 була такою ж, як і версія 2.1, тільки розбита на двадцять окремих пакетів. Інструмент Composer прижився у світі PHP і змінив наше уявлення про створення бібліотек. Nette перестала бути монолітом і розпалася на дрібні незалежні частини. Кожна з них має свій власний репозиторій, трекер проблем і свій власний темп розробки та версіонування. Таким чином, Nette не потрібно проходити через абсурди, характерні для монолітних фреймворків, коли виходить нова версія пакета, хоча нічого не змінилося. Фактичний поділ репозиторіїв Git вимагав кількох тижнів підготовки і сотень годин машинного часу. +У 2014 році вийшло Nette 2.1, за яким незабаром послідувало Nette 2.2. Як це можливо? Версія 2.2 була такою ж, як версія 2.1, тільки розділена на двадцять окремих пакетів. У світі PHP прижився інструмент Composer і змінив спосіб сприйняття створення бібліотек. Nette перестало бути монолітом і розпалося на менші незалежні частини. Кожна зі своїм репозиторієм, трекером проблем та власним темпом розробки та версіонуванням. У Nette, таким чином, не може бути абсурдів, поширених у монолітних фреймворках, коли виходить нова версія пакету, хоча в ньому взагалі нічого не змінилося. Саме розділення Git репозиторіїв вимагало кількох тижнів підготовки та сотень годин машинного часу. -Nette також посів дивовижне 3-е місце в глобальному опитуванні на найкращий PHP-фреймворк, організованому журналом Sitepoint. +Nette також посіло дивовижне 3-тє місце у всесвітньому опитуванні про найкращий PHP фреймворк, проведеному журналом Sitepoint. {{toc:no}} diff --git a/www/uk/license.texy b/www/uk/license.texy new file mode 100644 index 0000000000..45299b38c3 --- /dev/null +++ b/www/uk/license.texy @@ -0,0 +1,44 @@ +Ліцензійна політика +******************* + +Nette Framework поширюється як вільне програмне забезпечення, щоб кожен міг його використовувати. Ви можете вибрати, чи вам краще підходить ліцензія [New BSD |#New BSD License] чи [#GNU General Public License (GPL)] версії 2 або 3. + +Ліцензія BSD рекомендується для більшості проектів, оскільки її легко зрозуміти і вона майже не накладає обмежень на те, що ви можете робити з фреймворком. Ви можете використовувати Nette також у комерційних проектах. Однак, якщо GPL краще підходить для вашого проекту, виберіть її. При цьому не потрібно нікого інформувати про ваше рішення. Завжди зберігайте оригінальні авторські права. + +Вам слід знати, що назва "Nette Framework" є захищеною торговою маркою, і її використання має певні обмеження. Тому не використовуйте "Nette" у назві вашого проекту або домену найвищого рівня, оберіть краще ім'я, яке стоятиме на власних засадах. Якщо ваш проект хороший, не мине багато часу, і він створить собі репутацію. + +Якщо ви будете задоволені Nette Framework, а ми віримо, що так, ви можете [підтримати його внеском |donate]. Дякуємо. + + +New BSD License +--------------- + +Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com) All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of "Nette Framework" nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +GNU General Public License (GPL) +-------------------------------- + +Тексти ліцензій GPL дуже довгі, тому тут наведені посилання на них: + +- GPL version 2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +- GPL version 3: https://www.gnu.org/licenses/gpl-3.0.html + + +{{toc:yes}} +{{priority: -2}} diff --git a/www/uk/maintenance.texy b/www/uk/maintenance.texy index 5eec1913bc..95090db466 100644 --- a/www/uk/maintenance.texy +++ b/www/uk/maintenance.texy @@ -1,23 +1,22 @@ -Обслуговування та сумісність із PHP -*********************************** +Підтримка та сумісність з PHP +***************************** .[perex] -Nette - це фреймворк із надзвичайно тривалим терміном підтримки кожного релізу. Кожна гілка являє собою LTS (Long-Term Support Release) з підтримкою не менше 2 років. +Nette — це фреймворк з надзвичайно тривалим терміном підтримки окремих випусків. Кожна гілка є LTS (Long-Term Support Release) з підтримкою щонайменше 2 роки. -Кожен реліз активно підтримується протягом одного року (або більше) з моменту випуску першого стабільного релізу. -Критичні та вразливі місця в системі безпеки виправляються протягом двох років. +Кожна версія активно підтримується протягом одного року (або довше) з моменту початкового стабільного випуску. Критичні та безпекові помилки виправляються протягом двох років. -Календар випуску продукції Nette .[#toc-release-calendar-roadmap] -================================================================= +Календар випусків Nette +======================= {{include: doc-roadmap-table}} -Сумісність із PHP .[#toc-php-compatibility] -=========================================== +Сумісність з PHP +================ -Сумісність завжди відноситься до останнього випуску кожної серії. +Сумісність завжди стосується останнього випуску кожної серії. {{include: doc-roadmap-versions}} @@ -25,4 +24,4 @@ Nette - це фреймворк із надзвичайно тривалим т {{leftbar: @menu-common}} {{toc: no}} -{{description: реліз Nette, дорожня карта, таблиці обслуговування та сумісність із PHP}} +{{description: Випуски Nette, дорожня карта, таблиці підтримки та сумісність з PHP}} diff --git a/www/uk/packages.texy b/www/uk/packages.texy index 82f960773c..0e639722d6 100644 --- a/www/uk/packages.texy +++ b/www/uk/packages.texy @@ -1,26 +1,27 @@ -Список пакетів -************** +Список пакетів Nette +******************** -| **Application**:[application:how-it-works] | Ядро веб-додатка | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] -| **Bootstrap**:[bootstrap:] | Завантажувальна платформа вашого додатка | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] -| **Caching**:[caching:] | Шар кешу з набором сховищ | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] -| **Component Model**:[component-model:] | Основа для компонентних систем | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] -| **DI**:[dependency-injection:] | Контейнер ін'єкції залежностей | [GitHub |https://github.com/nette/di] [API |https://api.nette.org/di/] -| **Database**:[database:] | Рівень бази даних | [GitHub |https://github.com/nette/database] [API |https://api.nette.org/database/] -| **Forms**:[forms:] | Значно полегшує безпечні веб-форми | [GitHub |https://github.com/nette/forms] [API |https://api.nette.org/forms/] -| **Http**:[http:] | Шар для HTTP запиту і відповіді | [GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] -| **Latte**:[latte:] | Дивовижний движок шаблонів | [GitHub |https://github.com/nette/latte] [API |https://api.nette.org/latte/] -| **Mail**:[mail:] | Надсилання електронної пошти | [GitHub |https://github.com/nette/mail] [API |https://api.nette.org/mail/] -| **Neon**:[neon:] | Завантажує і скидає формат [NEON |https://ne-on.org] | [GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] -| **Php Generator**:[php-generator:] | Генератор PHP-коду | [GitHub |https://github.com/nette/php-generator] [API |https://api.nette.org/php-generator/] -| **Robot Loader**:[robot-loader:] | Найзручніше автозавантаження | [GitHub |https://github.com/nette/robot-loader] [API |https://api.nette.org/robot-loader/] +| **Application**:[application:how-it-works] | Ядро веб-додатків | [GitHub |https://github.com/nette/application] [API |https://api.nette.org/application/] +| **Assets**:[assets:] | Статичне керування файлами | [API |https://api.nette.org/assets/] [GitHub |https://github.com/nette/assets] +| **Bootstrap**:[bootstrap:] | Bootstrap веб-додатку | [GitHub |https://github.com/nette/bootstrap] [API |https://api.nette.org/bootstrap/] +| **Caching**:[caching:] | Шар кешування зі сховищами | [GitHub |https://github.com/nette/caching] [API |https://api.nette.org/caching/] +| **Component Model**:[component-model:] | Основа компонентної системи | [GitHub |https://github.com/nette/component-model] [API |https://api.nette.org/component-model/] +| **DI**:[dependency-injection:] | Dependency Injection Container | [GitHub |https://github.com/nette/di] [API |https://api.nette.org/di/] +| **Database**:[database:] | Шар бази даних | [GitHub |https://github.com/nette/database] [API |https://api.nette.org/database/] +| **Forms**:[forms:] | Зручні та безпечні веб-форми | [GitHub |https://github.com/nette/forms] [API |https://api.nette.org/forms/] +| **Http**:[http:] | Шар, що інкапсулює HTTP request & response | [GitHub |https://github.com/nette/http] [API |https://api.nette.org/http/] +| **Latte**:[latte:] | Чудова система шаблонів | [GitHub |https://github.com/nette/latte] [API |https://api.nette.org/latte/] +| **Mail**:[mail:] | Надсилання електронних листів | [GitHub |https://github.com/nette/mail] [API |https://api.nette.org/mail/] +| **Neon**:[neon:] | Читання та запис формату [NEON |https://ne-on.org] | [GitHub |https://github.com/nette/neon] [API |https://api.nette.org/neon/] +| **Php Generator**:[php-generator:] | Генератор PHP коду | [GitHub |https://github.com/nette/php-generator] [API |https://api.nette.org/php-generator/] +| **Robot Loader**:[robot-loader:] | Найкомфортніше автозавантаження | [GitHub |https://github.com/nette/robot-loader] [API |https://api.nette.org/robot-loader/] | **Routing**:[application:routing] | Маршрутизація | [GitHub |https://github.com/nette/routing] [API |https://api.nette.org/routing/] | **Safe Stream**:[safe-stream:] | Безпечні атомарні операції з файлами | [GitHub |https://github.com/nette/safe-stream] [API |https://api.nette.org/safe-stream/] -| **Security**:[security:authentication] | Забезпечує систему контролю доступу | [GitHub |https://github.com/nette/security] [API |https://api.nette.org/security/] | **Schema**:[schema:] | Валідація даних користувача | [GitHub |https://github.com/nette/schema] [API |https://api.nette.org/schema/] -| **Tester**:[tester:] | Зручне модульне тестування в PHP | [GitHub |https://github.com/nette/tester] [API |https://api.nette.org/tester/] +| **Security**:[security:authentication] | Управління правами доступу | [GitHub |https://github.com/nette/security] [API |https://api.nette.org/security/] +| **Tester**:[tester:] | Зручні юніт-тести в PHP | [GitHub |https://github.com/nette/tester] [API |https://api.nette.org/tester/] | **Tracy**:[tracy:] | Інструмент налагодження, який ви полюбите ♥ | [GitHub |https://github.com/nette/tracy] [API |https://api.nette.org/tracy/] -| **Utils**:[utils:] | Утиліти та основні класи | [GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] +| **Utils**:[utils:] | Основні класи та інструменти | [GitHub |https://github.com/nette/utils] [API |https://api.nette.org/utils/] {{leftbar: @menu-common}} {{toc: no}}